/**
 * Cesium - https://github.com/AnalyticalGraphicsInc/cesium
 *
 * Copyright 2011-2017 Cesium Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file 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.
 *
 * Columbus View (Pat. Pend.)
 *
 * Portions licensed separately.
 * See https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md for full licensing details.
 */
(function () {
/**
 * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
 * Released under MIT license, http://github.com/requirejs/almond/LICENSE
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
    var main, req, makeMap, handlers,
        defined = {},
        waiting = {},
        config = {},
        defining = {},
        hasOwn = Object.prototype.hasOwnProperty,
        aps = [].slice,
        jsSuffixRegExp = /\.js$/;

    function hasProp(obj, prop) {
        return hasOwn.call(obj, prop);
    }

    /**
     * Given a relative module name, like ./something, normalize it to
     * a real name that can be mapped to a path.
     * @param {String} name the relative name
     * @param {String} baseName a real name that the name arg is relative
     * to.
     * @returns {String} normalized name
     */
    function normalize(name, baseName) {
        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
            baseParts = baseName && baseName.split("/"),
            map = config.map,
            starMap = (map && map['*']) || {};

        //Adjust any relative paths.
        if (name) {
            name = name.split('/');
            lastIndex = name.length - 1;

            // If wanting node ID compatibility, strip .js from end
            // of IDs. Have to do this here, and not in nameToUrl
            // because node allows either .js or non .js to map
            // to same file.
            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
            }

            // Starts with a '.' so need the baseName
            if (name[0].charAt(0) === '.' && baseParts) {
                //Convert baseName to array, and lop off the last part,
                //so that . matches that 'directory' and not name of the baseName's
                //module. For instance, baseName of 'one/two/three', maps to
                //'one/two/three.js', but we want the directory, 'one/two' for
                //this normalization.
                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
                name = normalizedBaseParts.concat(name);
            }

            //start trimDots
            for (i = 0; i < name.length; i++) {
                part = name[i];
                if (part === '.') {
                    name.splice(i, 1);
                    i -= 1;
                } else if (part === '..') {
                    // If at the start, or previous value is still ..,
                    // keep them so that when converted to a path it may
                    // still work when converted to a path, even though
                    // as an ID it is less than ideal. In larger point
                    // releases, may be better to just kick out an error.
                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
                        continue;
                    } else if (i > 0) {
                        name.splice(i - 1, 2);
                        i -= 2;
                    }
                }
            }
            //end trimDots

            name = name.join('/');
        }

        //Apply map config if available.
        if ((baseParts || starMap) && map) {
            nameParts = name.split('/');

            for (i = nameParts.length; i > 0; i -= 1) {
                nameSegment = nameParts.slice(0, i).join("/");

                if (baseParts) {
                    //Find the longest baseName segment match in the config.
                    //So, do joins on the biggest to smallest lengths of baseParts.
                    for (j = baseParts.length; j > 0; j -= 1) {
                        mapValue = map[baseParts.slice(0, j).join('/')];

                        //baseName segment has  config, find if it has one for
                        //this name.
                        if (mapValue) {
                            mapValue = mapValue[nameSegment];
                            if (mapValue) {
                                //Match, update name to the new value.
                                foundMap = mapValue;
                                foundI = i;
                                break;
                            }
                        }
                    }
                }

                if (foundMap) {
                    break;
                }

                //Check for a star map match, but just hold on to it,
                //if there is a shorter segment match later in a matching
                //config, then favor over this star map.
                if (!foundStarMap && starMap && starMap[nameSegment]) {
                    foundStarMap = starMap[nameSegment];
                    starI = i;
                }
            }

            if (!foundMap && foundStarMap) {
                foundMap = foundStarMap;
                foundI = starI;
            }

            if (foundMap) {
                nameParts.splice(0, foundI, foundMap);
                name = nameParts.join('/');
            }
        }

        return name;
    }

    function makeRequire(relName, forceSync) {
        return function () {
            //A version of a require function that passes a moduleName
            //value for items that may need to
            //look up paths relative to the moduleName
            var args = aps.call(arguments, 0);

            //If first arg is not require('string'), and there is only
            //one arg, it is the array form without a callback. Insert
            //a null so that the following concat is correct.
            if (typeof args[0] !== 'string' && args.length === 1) {
                args.push(null);
            }
            return req.apply(undef, args.concat([relName, forceSync]));
        };
    }

    function makeNormalize(relName) {
        return function (name) {
            return normalize(name, relName);
        };
    }

    function makeLoad(depName) {
        return function (value) {
            defined[depName] = value;
        };
    }

    function callDep(name) {
        if (hasProp(waiting, name)) {
            var args = waiting[name];
            delete waiting[name];
            defining[name] = true;
            main.apply(undef, args);
        }

        if (!hasProp(defined, name) && !hasProp(defining, name)) {
            throw new Error('No ' + name);
        }
        return defined[name];
    }

    //Turns a plugin!resource to [plugin, resource]
    //with the plugin being undefined if the name
    //did not have a plugin prefix.
    function splitPrefix(name) {
        var prefix,
            index = name ? name.indexOf('!') : -1;
        if (index > -1) {
            prefix = name.substring(0, index);
            name = name.substring(index + 1, name.length);
        }
        return [prefix, name];
    }

    //Creates a parts array for a relName where first part is plugin ID,
    //second part is resource ID. Assumes relName has already been normalized.
    function makeRelParts(relName) {
        return relName ? splitPrefix(relName) : [];
    }

    /**
     * Makes a name map, normalizing the name, and using a plugin
     * for normalization if necessary. Grabs a ref to plugin
     * too, as an optimization.
     */
    makeMap = function (name, relParts) {
        var plugin,
            parts = splitPrefix(name),
            prefix = parts[0],
            relResourceName = relParts[1];

        name = parts[1];

        if (prefix) {
            prefix = normalize(prefix, relResourceName);
            plugin = callDep(prefix);
        }

        //Normalize according
        if (prefix) {
            if (plugin && plugin.normalize) {
                name = plugin.normalize(name, makeNormalize(relResourceName));
            } else {
                name = normalize(name, relResourceName);
            }
        } else {
            name = normalize(name, relResourceName);
            parts = splitPrefix(name);
            prefix = parts[0];
            name = parts[1];
            if (prefix) {
                plugin = callDep(prefix);
            }
        }

        //Using ridiculous property names for space reasons
        return {
            f: prefix ? prefix + '!' + name : name, //fullName
            n: name,
            pr: prefix,
            p: plugin
        };
    };

    function makeConfig(name) {
        return function () {
            return (config && config.config && config.config[name]) || {};
        };
    }

    handlers = {
        require: function (name) {
            return makeRequire(name);
        },
        exports: function (name) {
            var e = defined[name];
            if (typeof e !== 'undefined') {
                return e;
            } else {
                return (defined[name] = {});
            }
        },
        module: function (name) {
            return {
                id: name,
                uri: '',
                exports: defined[name],
                config: makeConfig(name)
            };
        }
    };

    main = function (name, deps, callback, relName) {
        var cjsModule, depName, ret, map, i, relParts,
            args = [],
            callbackType = typeof callback,
            usingExports;

        //Use name if no relName
        relName = relName || name;
        relParts = makeRelParts(relName);

        //Call the callback to define the module, if necessary.
        if (callbackType === 'undefined' || callbackType === 'function') {
            //Pull out the defined dependencies and pass the ordered
            //values to the callback.
            //Default to [require, exports, module] if no deps
            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
            for (i = 0; i < deps.length; i += 1) {
                map = makeMap(deps[i], relParts);
                depName = map.f;

                //Fast path CommonJS standard dependencies.
                if (depName === "require") {
                    args[i] = handlers.require(name);
                } else if (depName === "exports") {
                    //CommonJS module spec 1.1
                    args[i] = handlers.exports(name);
                    usingExports = true;
                } else if (depName === "module") {
                    //CommonJS module spec 1.1
                    cjsModule = args[i] = handlers.module(name);
                } else if (hasProp(defined, depName) ||
                           hasProp(waiting, depName) ||
                           hasProp(defining, depName)) {
                    args[i] = callDep(depName);
                } else if (map.p) {
                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
                    args[i] = defined[depName];
                } else {
                    throw new Error(name + ' missing ' + depName);
                }
            }

            ret = callback ? callback.apply(defined[name], args) : undefined;

            if (name) {
                //If setting exports via "module" is in play,
                //favor that over return value and exports. After that,
                //favor a non-undefined return value over exports use.
                if (cjsModule && cjsModule.exports !== undef &&
                        cjsModule.exports !== defined[name]) {
                    defined[name] = cjsModule.exports;
                } else if (ret !== undef || !usingExports) {
                    //Use the return value from the function.
                    defined[name] = ret;
                }
            }
        } else if (name) {
            //May just be an object definition for the module. Only
            //worry about defining if have a module name.
            defined[name] = callback;
        }
    };

    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
        if (typeof deps === "string") {
            if (handlers[deps]) {
                //callback in this case is really relName
                return handlers[deps](callback);
            }
            //Just return the module wanted. In this scenario, the
            //deps arg is the module name, and second arg (if passed)
            //is just the relName.
            //Normalize module name, if it contains . or ..
            return callDep(makeMap(deps, makeRelParts(callback)).f);
        } else if (!deps.splice) {
            //deps is a config object, not an array.
            config = deps;
            if (config.deps) {
                req(config.deps, config.callback);
            }
            if (!callback) {
                return;
            }

            if (callback.splice) {
                //callback is an array, which means it is a dependency list.
                //Adjust args if there are dependencies
                deps = callback;
                callback = relName;
                relName = null;
            } else {
                deps = undef;
            }
        }

        //Support require(['a'])
        callback = callback || function () {};

        //If relName is a function, it is an errback handler,
        //so remove it.
        if (typeof relName === 'function') {
            relName = forceSync;
            forceSync = alt;
        }

        //Simulate async callback;
        if (forceSync) {
            main(undef, deps, callback, relName);
        } else {
            //Using a non-zero value because of concern for what old browsers
            //do, and latest browsers "upgrade" to 4 if lower value is used:
            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
            //If want a value immediately, use require('id') instead -- something
            //that works in almond on the global level, but not guaranteed and
            //unlikely to work in other AMD implementations.
            setTimeout(function () {
                main(undef, deps, callback, relName);
            }, 4);
        }

        return req;
    };

    /**
     * Just drops the config on the floor, but returns req in case
     * the config return value is used.
     */
    req.config = function (cfg) {
        return req(cfg);
    };

    /**
     * Expose module registry for debugging and tooling
     */
    requirejs._defined = defined;

    define = function (name, deps, callback) {
        if (typeof name !== 'string') {
            throw new Error('See almond README: incorrect module build, no module name');
        }

        //This module may not have dependencies
        if (!deps.splice) {
            //deps is not an array, so probably means
            //an object literal or factory function for
            //the value. Adjust args.
            callback = deps;
            deps = [];
        }

        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
            waiting[name] = [name, deps, callback];
        }
    };

    define.amd = {
        jQuery: true
    };
}());

/*global define*/
define('Core/appendForwardSlash',[],function() {
    'use strict';

    /**
     * @private
     */
    function appendForwardSlash(url) {
        if (url.length === 0 || url[url.length - 1] !== '/') {
            url = url + '/';
        }
        return url;
    }

    return appendForwardSlash;
});

/**
  @license
  when.js - https://github.com/cujojs/when

  MIT License (c) copyright B Cavalier & J Hann

 * A lightweight CommonJS Promises/A and when() implementation
 * when is part of the cujo.js family of libraries (http://cujojs.com/)
 *
 * Licensed under the MIT License at:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * @version 1.7.1
 */

(function(define) { 'use strict';
define('ThirdParty/when',[],function () {
	var reduceArray, slice, undef;

	//
	// Public API
	//

	when.defer     = defer;     // Create a deferred
	when.resolve   = resolve;   // Create a resolved promise
	when.reject    = reject;    // Create a rejected promise

	when.join      = join;      // Join 2 or more promises

	when.all       = all;       // Resolve a list of promises
	when.map       = map;       // Array.map() for promises
	when.reduce    = reduce;    // Array.reduce() for promises

	when.any       = any;       // One-winner race
	when.some      = some;      // Multi-winner race

	when.chain     = chain;     // Make a promise trigger another resolver

	when.isPromise = isPromise; // Determine if a thing is a promise

	/**
	 * Register an observer for a promise or immediate value.
	 *
	 * @param {*} promiseOrValue
	 * @param {function?} [onFulfilled] callback to be called when promiseOrValue is
	 *   successfully fulfilled.  If promiseOrValue is an immediate value, callback
	 *   will be invoked immediately.
	 * @param {function?} [onRejected] callback to be called when promiseOrValue is
	 *   rejected.
	 * @param {function?} [onProgress] callback to be called when progress updates
	 *   are issued for promiseOrValue.
	 * @returns {Promise} a new {@link Promise} that will complete with the return
	 *   value of callback or errback or the completion value of promiseOrValue if
	 *   callback and/or errback is not supplied.
	 */
	function when(promiseOrValue, onFulfilled, onRejected, onProgress) {
		// Get a trusted promise for the input promiseOrValue, and then
		// register promise handlers
		return resolve(promiseOrValue).then(onFulfilled, onRejected, onProgress);
	}

	/**
	 * Returns promiseOrValue if promiseOrValue is a {@link Promise}, a new Promise if
	 * promiseOrValue is a foreign promise, or a new, already-fulfilled {@link Promise}
	 * whose value is promiseOrValue if promiseOrValue is an immediate value.
	 *
	 * @param {*} promiseOrValue
	 * @returns Guaranteed to return a trusted Promise.  If promiseOrValue is a when.js {@link Promise}
	 *   returns promiseOrValue, otherwise, returns a new, already-resolved, when.js {@link Promise}
	 *   whose resolution value is:
	 *   * the resolution value of promiseOrValue if it's a foreign promise, or
	 *   * promiseOrValue if it's a value
	 */
	function resolve(promiseOrValue) {
		var promise, deferred;

		if(promiseOrValue instanceof Promise) {
			// It's a when.js promise, so we trust it
			promise = promiseOrValue;

		} else {
			// It's not a when.js promise. See if it's a foreign promise or a value.
			if(isPromise(promiseOrValue)) {
				// It's a thenable, but we don't know where it came from, so don't trust
				// its implementation entirely.  Introduce a trusted middleman when.js promise
				deferred = defer();

				// IMPORTANT: This is the only place when.js should ever call .then() on an
				// untrusted promise. Don't expose the return value to the untrusted promise
				promiseOrValue.then(
					function(value)  { deferred.resolve(value); },
					function(reason) { deferred.reject(reason); },
					function(update) { deferred.progress(update); }
				);

				promise = deferred.promise;

			} else {
				// It's a value, not a promise.  Create a resolved promise for it.
				promise = fulfilled(promiseOrValue);
			}
		}

		return promise;
	}

	/**
	 * Returns a rejected promise for the supplied promiseOrValue.  The returned
	 * promise will be rejected with:
	 * - promiseOrValue, if it is a value, or
	 * - if promiseOrValue is a promise
	 *   - promiseOrValue's value after it is fulfilled
	 *   - promiseOrValue's reason after it is rejected
	 * @param {*} promiseOrValue the rejected value of the returned {@link Promise}
	 * @returns {Promise} rejected {@link Promise}
	 */
	function reject(promiseOrValue) {
		return when(promiseOrValue, rejected);
	}

	/**
	 * Trusted Promise constructor.  A Promise created from this constructor is
	 * a trusted when.js promise.  Any other duck-typed promise is considered
	 * untrusted.
	 * @constructor
	 * @name Promise
	 */
	function Promise(then) {
		this.then = then;
	}

	Promise.prototype = {
		/**
		 * Register a callback that will be called when a promise is
		 * fulfilled or rejected.  Optionally also register a progress handler.
		 * Shortcut for .then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress)
		 * @param {function?} [onFulfilledOrRejected]
		 * @param {function?} [onProgress]
		 * @returns {Promise}
		 */
		always: function(onFulfilledOrRejected, onProgress) {
			return this.then(onFulfilledOrRejected, onFulfilledOrRejected, onProgress);
		},

		/**
		 * Register a rejection handler.  Shortcut for .then(undefined, onRejected)
		 * @param {function?} onRejected
		 * @returns {Promise}
		 */
		otherwise: function(onRejected) {
			return this.then(undef, onRejected);
		},

		/**
		 * Shortcut for .then(function() { return value; })
		 * @param  {*} value
		 * @returns {Promise} a promise that:
		 *  - is fulfilled if value is not a promise, or
		 *  - if value is a promise, will fulfill with its value, or reject
		 *    with its reason.
		 */
		yield: function(value) {
			return this.then(function() {
				return value;
			});
		},

		/**
		 * Assumes that this promise will fulfill with an array, and arranges
		 * for the onFulfilled to be called with the array as its argument list
		 * i.e. onFulfilled.spread(undefined, array).
		 * @param {function} onFulfilled function to receive spread arguments
		 * @returns {Promise}
		 */
		spread: function(onFulfilled) {
			return this.then(function(array) {
				// array may contain promises, so resolve its contents.
				return all(array, function(array) {
					return onFulfilled.apply(undef, array);
				});
			});
		}
	};

	/**
	 * Create an already-resolved promise for the supplied value
	 * @private
	 *
	 * @param {*} value
	 * @returns {Promise} fulfilled promise
	 */
	function fulfilled(value) {
		var p = new Promise(function(onFulfilled) {
			// TODO: Promises/A+ check typeof onFulfilled
			try {
				return resolve(onFulfilled ? onFulfilled(value) : value);
			} catch(e) {
				return rejected(e);
			}
		});

		return p;
	}

	/**
	 * Create an already-rejected {@link Promise} with the supplied
	 * rejection reason.
	 * @private
	 *
	 * @param {*} reason
	 * @returns {Promise} rejected promise
	 */
	function rejected(reason) {
		var p = new Promise(function(_, onRejected) {
			// TODO: Promises/A+ check typeof onRejected
			try {
				return onRejected ? resolve(onRejected(reason)) : rejected(reason);
			} catch(e) {
				return rejected(e);
			}
		});

		return p;
	}

	/**
	 * Creates a new, Deferred with fully isolated resolver and promise parts,
	 * either or both of which may be given out safely to consumers.
	 * The Deferred itself has the full API: resolve, reject, progress, and
	 * then. The resolver has resolve, reject, and progress.  The promise
	 * only has then.
	 *
	 * @returns {Deferred}
	 */
	function defer() {
		var deferred, promise, handlers, progressHandlers,
			_then, _progress, _resolve;

		/**
		 * The promise for the new deferred
		 * @type {Promise}
		 */
		promise = new Promise(then);

		/**
		 * The full Deferred object, with {@link Promise} and {@link Resolver} parts
		 * @class Deferred
		 * @name Deferred
		 */
		deferred = {
			then:     then, // DEPRECATED: use deferred.promise.then
			resolve:  promiseResolve,
			reject:   promiseReject,
			// TODO: Consider renaming progress() to notify()
			progress: promiseProgress,

			promise:  promise,

			resolver: {
				resolve:  promiseResolve,
				reject:   promiseReject,
				progress: promiseProgress
			}
		};

		handlers = [];
		progressHandlers = [];

		/**
		 * Pre-resolution then() that adds the supplied callback, errback, and progback
		 * functions to the registered listeners
		 * @private
		 *
		 * @param {function?} [onFulfilled] resolution handler
		 * @param {function?} [onRejected] rejection handler
		 * @param {function?} [onProgress] progress handler
		 */
		_then = function(onFulfilled, onRejected, onProgress) {
			// TODO: Promises/A+ check typeof onFulfilled, onRejected, onProgress
			var deferred, progressHandler;

			deferred = defer();

			progressHandler = typeof onProgress === 'function'
				? function(update) {
					try {
						// Allow progress handler to transform progress event
						deferred.progress(onProgress(update));
					} catch(e) {
						// Use caught value as progress
						deferred.progress(e);
					}
				}
				: function(update) { deferred.progress(update); };

			handlers.push(function(promise) {
				promise.then(onFulfilled, onRejected)
					.then(deferred.resolve, deferred.reject, progressHandler);
			});

			progressHandlers.push(progressHandler);

			return deferred.promise;
		};

		/**
		 * Issue a progress event, notifying all progress listeners
		 * @private
		 * @param {*} update progress event payload to pass to all listeners
		 */
		_progress = function(update) {
			processQueue(progressHandlers, update);
			return update;
		};

		/**
		 * Transition from pre-resolution state to post-resolution state, notifying
		 * all listeners of the resolution or rejection
		 * @private
		 * @param {*} value the value of this deferred
		 */
		_resolve = function(value) {
			value = resolve(value);

			// Replace _then with one that directly notifies with the result.
			_then = value.then;
			// Replace _resolve so that this Deferred can only be resolved once
			_resolve = resolve;
			// Make _progress a noop, to disallow progress for the resolved promise.
			_progress = noop;

			// Notify handlers
			processQueue(handlers, value);

			// Free progressHandlers array since we'll never issue progress events
			progressHandlers = handlers = undef;

			return value;
		};

		return deferred;

		/**
		 * Wrapper to allow _then to be replaced safely
		 * @param {function?} [onFulfilled] resolution handler
		 * @param {function?} [onRejected] rejection handler
		 * @param {function?} [onProgress] progress handler
		 * @returns {Promise} new promise
		 */
		function then(onFulfilled, onRejected, onProgress) {
			// TODO: Promises/A+ check typeof onFulfilled, onRejected, onProgress
			return _then(onFulfilled, onRejected, onProgress);
		}

		/**
		 * Wrapper to allow _resolve to be replaced
		 */
		function promiseResolve(val) {
			return _resolve(val);
		}

		/**
		 * Wrapper to allow _reject to be replaced
		 */
		function promiseReject(err) {
			return _resolve(rejected(err));
		}

		/**
		 * Wrapper to allow _progress to be replaced
		 */
		function promiseProgress(update) {
			return _progress(update);
		}
	}

	/**
	 * Determines if promiseOrValue is a promise or not.  Uses the feature
	 * test from http://wiki.commonjs.org/wiki/Promises/A to determine if
	 * promiseOrValue is a promise.
	 *
	 * @param {*} promiseOrValue anything
	 * @returns {boolean} true if promiseOrValue is a {@link Promise}
	 */
	function isPromise(promiseOrValue) {
		return promiseOrValue && typeof promiseOrValue.then === 'function';
	}

	/**
	 * Initiates a competitive race, returning a promise that will resolve when
	 * howMany of the supplied promisesOrValues have resolved, or will reject when
	 * it becomes impossible for howMany to resolve, for example, when
	 * (promisesOrValues.length - howMany) + 1 input promises reject.
	 *
	 * @param {Array} promisesOrValues array of anything, may contain a mix
	 *      of promises and values
	 * @param howMany {number} number of promisesOrValues to resolve
	 * @param {function?} [onFulfilled] resolution handler
	 * @param {function?} [onRejected] rejection handler
	 * @param {function?} [onProgress] progress handler
	 * @returns {Promise} promise that will resolve to an array of howMany values that
	 * resolved first, or will reject with an array of (promisesOrValues.length - howMany) + 1
	 * rejection reasons.
	 */
	function some(promisesOrValues, howMany, onFulfilled, onRejected, onProgress) {

		checkCallbacks(2, arguments);

		return when(promisesOrValues, function(promisesOrValues) {

			var toResolve, toReject, values, reasons, deferred, fulfillOne, rejectOne, progress, len, i;

			len = promisesOrValues.length >>> 0;

			toResolve = Math.max(0, Math.min(howMany, len));
			values = [];

			toReject = (len - toResolve) + 1;
			reasons = [];

			deferred = defer();

			// No items in the input, resolve immediately
			if (!toResolve) {
				deferred.resolve(values);

			} else {
				progress = deferred.progress;

				rejectOne = function(reason) {
					reasons.push(reason);
					if(!--toReject) {
						fulfillOne = rejectOne = noop;
						deferred.reject(reasons);
					}
				};

				fulfillOne = function(val) {
					// This orders the values based on promise resolution order
					// Another strategy would be to use the original position of
					// the corresponding promise.
					values.push(val);

					if (!--toResolve) {
						fulfillOne = rejectOne = noop;
						deferred.resolve(values);
					}
				};

				for(i = 0; i < len; ++i) {
					if(i in promisesOrValues) {
						when(promisesOrValues[i], fulfiller, rejecter, progress);
					}
				}
			}

			return deferred.then(onFulfilled, onRejected, onProgress);

			function rejecter(reason) {
				rejectOne(reason);
			}

			function fulfiller(val) {
				fulfillOne(val);
			}

		});
	}

	/**
	 * Initiates a competitive race, returning a promise that will resolve when
	 * any one of the supplied promisesOrValues has resolved or will reject when
	 * *all* promisesOrValues have rejected.
	 *
	 * @param {Array|Promise} promisesOrValues array of anything, may contain a mix
	 *      of {@link Promise}s and values
	 * @param {function?} [onFulfilled] resolution handler
	 * @param {function?} [onRejected] rejection handler
	 * @param {function?} [onProgress] progress handler
	 * @returns {Promise} promise that will resolve to the value that resolved first, or
	 * will reject with an array of all rejected inputs.
	 */
	function any(promisesOrValues, onFulfilled, onRejected, onProgress) {

		function unwrapSingleResult(val) {
			return onFulfilled ? onFulfilled(val[0]) : val[0];
		}

		return some(promisesOrValues, 1, unwrapSingleResult, onRejected, onProgress);
	}

	/**
	 * Return a promise that will resolve only once all the supplied promisesOrValues
	 * have resolved. The resolution value of the returned promise will be an array
	 * containing the resolution values of each of the promisesOrValues.
	 * @memberOf when
	 *
	 * @param {Array|Promise} promisesOrValues array of anything, may contain a mix
	 *      of {@link Promise}s and values
	 * @param {function?} [onFulfilled] resolution handler
	 * @param {function?} [onRejected] rejection handler
	 * @param {function?} [onProgress] progress handler
	 * @returns {Promise}
	 */
	function all(promisesOrValues, onFulfilled, onRejected, onProgress) {
		checkCallbacks(1, arguments);
		return map(promisesOrValues, identity).then(onFulfilled, onRejected, onProgress);
	}

	/**
	 * Joins multiple promises into a single returned promise.
	 * @returns {Promise} a promise that will fulfill when *all* the input promises
	 * have fulfilled, or will reject when *any one* of the input promises rejects.
	 */
	function join(/* ...promises */) {
		return map(arguments, identity);
	}

	/**
	 * Traditional map function, similar to `Array.prototype.map()`, but allows
	 * input to contain {@link Promise}s and/or values, and mapFunc may return
	 * either a value or a {@link Promise}
	 *
	 * @param {Array|Promise} promise array of anything, may contain a mix
	 *      of {@link Promise}s and values
	 * @param {function} mapFunc mapping function mapFunc(value) which may return
	 *      either a {@link Promise} or value
	 * @returns {Promise} a {@link Promise} that will resolve to an array containing
	 *      the mapped output values.
	 */
	function map(promise, mapFunc) {
		return when(promise, function(array) {
			var results, len, toResolve, resolve, i, d;

			// Since we know the resulting length, we can preallocate the results
			// array to avoid array expansions.
			toResolve = len = array.length >>> 0;
			results = [];
			d = defer();

			if(!toResolve) {
				d.resolve(results);
			} else {

				resolve = function resolveOne(item, i) {
					when(item, mapFunc).then(function(mapped) {
						results[i] = mapped;

						if(!--toResolve) {
							d.resolve(results);
						}
					}, d.reject);
				};

				// Since mapFunc may be async, get all invocations of it into flight
				for(i = 0; i < len; i++) {
					if(i in array) {
						resolve(array[i], i);
					} else {
						--toResolve;
					}
				}

			}

			return d.promise;

		});
	}

	/**
	 * Traditional reduce function, similar to `Array.prototype.reduce()`, but
	 * input may contain promises and/or values, and reduceFunc
	 * may return either a value or a promise, *and* initialValue may
	 * be a promise for the starting value.
	 *
	 * @param {Array|Promise} promise array or promise for an array of anything,
	 *      may contain a mix of promises and values.
	 * @param {function} reduceFunc reduce function reduce(currentValue, nextValue, index, total),
	 *      where total is the total number of items being reduced, and will be the same
	 *      in each call to reduceFunc.
	 * @returns {Promise} that will resolve to the final reduced value
	 */
	function reduce(promise, reduceFunc /*, initialValue */) {
		var args = slice.call(arguments, 1);

		return when(promise, function(array) {
			var total;

			total = array.length;

			// Wrap the supplied reduceFunc with one that handles promises and then
			// delegates to the supplied.
			args[0] = function (current, val, i) {
				return when(current, function (c) {
					return when(val, function (value) {
						return reduceFunc(c, value, i, total);
					});
				});
			};

			return reduceArray.apply(array, args);
		});
	}

	/**
	 * Ensure that resolution of promiseOrValue will trigger resolver with the
	 * value or reason of promiseOrValue, or instead with resolveValue if it is provided.
	 *
	 * @param promiseOrValue
	 * @param {Object} resolver
	 * @param {function} resolver.resolve
	 * @param {function} resolver.reject
	 * @param {*} [resolveValue]
	 * @returns {Promise}
	 */
	function chain(promiseOrValue, resolver, resolveValue) {
		var useResolveValue = arguments.length > 2;

		return when(promiseOrValue,
			function(val) {
				val = useResolveValue ? resolveValue : val;
				resolver.resolve(val);
				return val;
			},
			function(reason) {
				resolver.reject(reason);
				return rejected(reason);
			},
			resolver.progress
		);
	}

	//
	// Utility functions
	//

	/**
	 * Apply all functions in queue to value
	 * @param {Array} queue array of functions to execute
	 * @param {*} value argument passed to each function
	 */
	function processQueue(queue, value) {
		var handler, i = 0;

		while (handler = queue[i++]) {
			handler(value);
		}
	}

	/**
	 * Helper that checks arrayOfCallbacks to ensure that each element is either
	 * a function, or null or undefined.
	 * @private
	 * @param {number} start index at which to start checking items in arrayOfCallbacks
	 * @param {Array} arrayOfCallbacks array to check
	 * @throws {Error} if any element of arrayOfCallbacks is something other than
	 * a functions, null, or undefined.
	 */
	function checkCallbacks(start, arrayOfCallbacks) {
		// TODO: Promises/A+ update type checking and docs
		var arg, i = arrayOfCallbacks.length;

		while(i > start) {
			arg = arrayOfCallbacks[--i];

			if (arg != null && typeof arg != 'function') {
				throw new Error('arg '+i+' must be a function');
			}
		}
	}

	/**
	 * No-Op function used in method replacement
	 * @private
	 */
	function noop() {}

	slice = [].slice;

	// ES5 reduce implementation if native not available
	// See: http://es5.github.com/#x15.4.4.21 as there are many
	// specifics and edge cases.
	reduceArray = [].reduce ||
		function(reduceFunc /*, initialValue */) {
			/*jshint maxcomplexity: 7*/

			// ES5 dictates that reduce.length === 1

			// This implementation deviates from ES5 spec in the following ways:
			// 1. It does not check if reduceFunc is a Callable

			var arr, args, reduced, len, i;

			i = 0;
			// This generates a jshint warning, despite being valid
			// "Missing 'new' prefix when invoking a constructor."
			// See https://github.com/jshint/jshint/issues/392
			arr = Object(this);
			len = arr.length >>> 0;
			args = arguments;

			// If no initialValue, use first item of array (we know length !== 0 here)
			// and adjust i to start at second item
			if(args.length <= 1) {
				// Skip to the first real element in the array
				for(;;) {
					if(i in arr) {
						reduced = arr[i++];
						break;
					}

					// If we reached the end of the array without finding any real
					// elements, it's a TypeError
					if(++i >= len) {
						throw new TypeError();
					}
				}
			} else {
				// If initialValue provided, use it
				reduced = args[1];
			}

			// Do the actual reduce
			for(;i < len; ++i) {
				// Skip holes
				if(i in arr) {
					reduced = reduceFunc(reduced, arr[i], i, arr);
				}
			}

			return reduced;
		};

	function identity(x) {
		return x;
	}

	return when;
});
})(typeof define == 'function' && define.amd
	? define
	: function (factory) { typeof exports === 'object'
		? (module.exports = factory())
		: (this.when      = factory());
	}
	// Boilerplate for AMD, Node, and browser global
);

/*global define*/
define('Core/defined',[],function() {
    'use strict';

    /**
     * @exports defined
     *
     * @param {Object} value The object.
     * @returns {Boolean} Returns true if the object is defined, returns false otherwise.
     *
     * @example
     * if (Cesium.defined(positions)) {
     *      doSomething();
     * } else {
     *      doSomethingElse();
     * }
     */
    function defined(value) {
        return value !== undefined && value !== null;
    }

    return defined;
});

/*global define*/
define('Core/defineProperties',[
        './defined'
    ], function(
        defined) {
    'use strict';

    var definePropertyWorks = (function() {
        try {
            return 'x' in Object.defineProperty({}, 'x', {});
        } catch (e) {
            return false;
        }
    })();

    /**
     * Defines properties on an object, using Object.defineProperties if available,
     * otherwise returns the object unchanged.  This function should be used in
     * setup code to prevent errors from completely halting JavaScript execution
     * in legacy browsers.
     *
     * @private
     *
     * @exports defineProperties
     */
    var defineProperties = Object.defineProperties;
    if (!definePropertyWorks || !defined(defineProperties)) {
        defineProperties = function(o) {
            return o;
        };
    }

    return defineProperties;
});

/*global define*/
define('Core/DeveloperError',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * Constructs an exception object that is thrown due to a developer error, e.g., invalid argument,
     * argument out of range, etc.  This exception should only be thrown during development;
     * it usually indicates a bug in the calling code.  This exception should never be
     * caught; instead the calling code should strive not to generate it.
     * <br /><br />
     * On the other hand, a {@link RuntimeError} indicates an exception that may
     * be thrown at runtime, e.g., out of memory, that the calling code should be prepared
     * to catch.
     *
     * @alias DeveloperError
     * @constructor
     * @extends Error
     *
     * @param {String} [message] The error message for this exception.
     *
     * @see RuntimeError
     */
    function DeveloperError(message) {
        /**
         * 'DeveloperError' indicating that this exception was thrown due to a developer error.
         * @type {String}
         * @readonly
         */
        this.name = 'DeveloperError';

        /**
         * The explanation for why this exception was thrown.
         * @type {String}
         * @readonly
         */
        this.message = message;

        //Browsers such as IE don't have a stack property until you actually throw the error.
        var stack;
        try {
            throw new Error();
        } catch (e) {
            stack = e.stack;
        }

        /**
         * The stack trace of this exception, if available.
         * @type {String}
         * @readonly
         */
        this.stack = stack;
    }

    if (defined(Object.create)) {
        DeveloperError.prototype = Object.create(Error.prototype);
        DeveloperError.prototype.constructor = DeveloperError;
    }

    DeveloperError.prototype.toString = function() {
        var str = this.name + ': ' + this.message;

        if (defined(this.stack)) {
            str += '\n' + this.stack.toString();
        }

        return str;
    };

    /**
     * @private
     */
    DeveloperError.throwInstantiationError = function() {
        throw new DeveloperError('This function defines an interface and should not be called directly.');
    };

    return DeveloperError;
});

/*global define*/
define('Core/Credit',[
        './defined',
        './defineProperties',
        './DeveloperError'
    ], function(
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    var nextCreditId = 0;
    var creditToId = {};

    /**
     * A credit contains data pertaining to how to display attributions/credits for certain content on the screen.
     *
     * @param {String} [text] The text to be displayed on the screen if no imageUrl is specified.
     * @param {String} [imageUrl] The source location for an image
     * @param {String} [link] A URL location for which the credit will be hyperlinked
     *
     * @alias Credit
     * @constructor
     *
     * @example
     * //Create a credit with a tooltip, image and link
     * var credit = new Cesium.Credit('Cesium', '/images/cesium_logo.png', 'http://cesiumjs.org/');
     */
    function Credit(text, imageUrl, link) {
        var hasLink = (defined(link));
        var hasImage = (defined(imageUrl));
        var hasText = (defined(text));

                if (!hasText && !hasImage && !hasLink) {
            throw new DeveloperError('text, imageUrl or link is required');
        }
        
        if (!hasText && !hasImage) {
            text = link;
        }

        this._text = text;
        this._imageUrl = imageUrl;
        this._link = link;
        this._hasLink = hasLink;
        this._hasImage = hasImage;

        // Credits are immutable so generate an id to use to optimize equal()
        var id;
        var key = JSON.stringify([text, imageUrl, link]);

        if (defined(creditToId[key])) {
            id = creditToId[key];
        } else {
            id = nextCreditId++;
            creditToId[key] = id;
        }

        this._id = id;
    }

    defineProperties(Credit.prototype, {
        /**
         * The credit text
         * @memberof Credit.prototype
         * @type {String}
         * @readonly
         */
        text : {
            get : function() {
                return this._text;
            }
        },

        /**
         * The source location for the image.
         * @memberof Credit.prototype
         * @type {String}
         * @readonly
         */
        imageUrl : {
            get : function() {
                return this._imageUrl;
            }
        },

        /**
         * A URL location for the credit hyperlink
         * @memberof Credit.prototype
         * @type {String}
         * @readonly
         */
        link : {
            get : function() {
                return this._link;
            }
        },

        /**
         * @memberof Credit.prototype
         * @type {Number}
         * @readonly
         *
         * @private
         */
        id : {
            get : function() {
                return this._id;
            }
        }
    });

    /**
     * Returns true if the credit has an imageUrl
     *
     * @returns {Boolean}
     */
    Credit.prototype.hasImage = function() {
        return this._hasImage;
    };

    /**
     * Returns true if the credit has a link
     *
     * @returns {Boolean}
     */
    Credit.prototype.hasLink = function() {
        return this._hasLink;
    };

    /**
     * Returns true if the credits are equal
     *
     * @param {Credit} left The first credit
     * @param {Credit} right The second credit
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Credit.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left._id === right._id));
    };

    /**
     * Returns true if the credits are equal
     *
     * @param {Credit} credit The credit to compare to.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Credit.prototype.equals = function(credit) {
        return Credit.equals(this, credit);
    };

    return Credit;
});

/*global define*/
define('Core/freezeObject',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * Freezes an object, using Object.freeze if available, otherwise returns
     * the object unchanged.  This function should be used in setup code to prevent
     * errors from completely halting JavaScript execution in legacy browsers.
     *
     * @private
     *
     * @exports freezeObject
     */
    var freezeObject = Object.freeze;
    if (!defined(freezeObject)) {
        freezeObject = function(o) {
            return o;
        };
    }

    return freezeObject;
});

/*global define*/
define('Core/defaultValue',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Returns the first parameter if not undefined, otherwise the second parameter.
     * Useful for setting a default value for a parameter.
     *
     * @exports defaultValue
     *
     * @param {*} a
     * @param {*} b
     * @returns {*} Returns the first parameter if not undefined, otherwise the second parameter.
     *
     * @example
     * param = Cesium.defaultValue(param, 'default');
     */
    function defaultValue(a, b) {
        if (a !== undefined) {
            return a;
        }
        return b;
    }

    /**
     * A frozen empty object that can be used as the default value for options passed as
     * an object literal.
     */
    defaultValue.EMPTY_OBJECT = freezeObject({});

    return defaultValue;
});

/*global define*/
define('Core/oneTimeWarning',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    "use strict";

    var warnings = {};

    /**
     * Logs a one time message to the console.  Use this function instead of
     * <code>console.log</code> directly since this does not log duplicate messages
     * unless it is called from multiple workers.
     *
     * @exports oneTimeWarning
     *
     * @param {String} identifier The unique identifier for this warning.
     * @param {String} [message=identifier] The message to log to the console.
     *
     * @example
     * for(var i=0;i<foo.length;++i) {
     *    if (!defined(foo[i].bar)) {
     *       // Something that can be recovered from but may happen a lot
     *       oneTimeWarning('foo.bar undefined', 'foo.bar is undefined. Setting to 0.');
     *       foo[i].bar = 0;
     *       // ...
     *    }
     * }
     *
     * @private
     */
    function oneTimeWarning(identifier, message) {
                if (!defined(identifier)) {
            throw new DeveloperError('identifier is required.');
        }
        
        if (!defined(warnings[identifier])) {
            warnings[identifier] = true;
            console.warn(defaultValue(message, identifier));
        }
    }

    oneTimeWarning.geometryOutlines = 'Entity geometry outlines are unsupported on terrain. Outlines will be disabled. To enable outlines, disable geometry terrain clamping by explicitly setting height to 0.';

    return oneTimeWarning;
});

/*global define*/
define('Core/deprecationWarning',[
        './defined',
        './DeveloperError',
        './oneTimeWarning'
    ], function(
        defined,
        DeveloperError,
        oneTimeWarning) {
    'use strict';
    
    /**
     * Logs a deprecation message to the console.  Use this function instead of
     * <code>console.log</code> directly since this does not log duplicate messages
     * unless it is called from multiple workers.
     *
     * @exports deprecationWarning
     *
     * @param {String} identifier The unique identifier for this deprecated API.
     * @param {String} message The message to log to the console.
     *
     * @example
     * // Deprecated function or class
     * function Foo() {
     *    deprecationWarning('Foo', 'Foo was deprecated in Cesium 1.01.  It will be removed in 1.03.  Use newFoo instead.');
     *    // ...
     * }
     *
     * // Deprecated function
     * Bar.prototype.func = function() {
     *    deprecationWarning('Bar.func', 'Bar.func() was deprecated in Cesium 1.01.  It will be removed in 1.03.  Use Bar.newFunc() instead.');
     *    // ...
     * };
     *
     * // Deprecated property
     * defineProperties(Bar.prototype, {
     *     prop : {
     *         get : function() {
     *             deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01.  It will be removed in 1.03.  Use Bar.newProp instead.');
     *             // ...
     *         },
     *         set : function(value) {
     *             deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01.  It will be removed in 1.03.  Use Bar.newProp instead.');
     *             // ...
     *         }
     *     }
     * });
     *
     * @private
     */
    function deprecationWarning(identifier, message) {
                if (!defined(identifier) || !defined(message)) {
            throw new DeveloperError('identifier and message are required.');
        }
        
        oneTimeWarning(identifier, message);
    }

    return deprecationWarning;
});

/*global define*/
define('Core/Check',[
        './defined',
        './DeveloperError'
    ], function(
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Contains functions for checking that supplied arguments are of a specified type
     * or meet specified conditions
     * @private
     */
    var Check = {};

    /**
     * Contains type checking functions, all using the typeof operator
     */
    Check.typeOf = {};

    function getUndefinedErrorMessage(name) {
        return name + ' is required, actual value was undefined';
    }

    function getFailedTypeErrorMessage(actual, expected, name) {
        return 'Expected ' + name + ' to be typeof ' + expected + ', actual typeof was ' + actual;
    }

    /**
     * Throws if test is not defined
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value that is to be checked
     * @exception {DeveloperError} test must be defined
     */
    Check.defined = function (name, test) {
        if (!defined(test)) {
            throw new DeveloperError(getUndefinedErrorMessage(name));
        }
    };

    /**
     * Throws if test is not typeof 'function'
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'function'
     */
    Check.typeOf.func = function (name, test) {
        if (typeof test !== 'function') {
            throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'function', name));
        }
    };

    /**
     * Throws if test is not typeof 'string'
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'string'
     */
    Check.typeOf.string = function (name, test) {
        if (typeof test !== 'string') {
            throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'string', name));
        }
    };

    /**
     * Throws if test is not typeof 'number'
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'number'
     */
    Check.typeOf.number = function (name, test) {
        if (typeof test !== 'number') {
            throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'number', name));
        }
    };

    /**
     * Throws if test is not typeof 'number' and less than limit
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @param {Number} limit The limit value to compare against
     * @exception {DeveloperError} test must be typeof 'number' and less than limit
     */
    Check.typeOf.number.lessThan = function (name, test, limit) {
        Check.typeOf.number(name, test);
        if (test >= limit) {
            throw new DeveloperError('Expected ' + name + ' to be less than ' + limit + ', actual value was ' + test);
        }
    };

    /**
     * Throws if test is not typeof 'number' and less than or equal to limit
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @param {Number} limit The limit value to compare against
     * @exception {DeveloperError} test must be typeof 'number' and less than or equal to limit
     */
    Check.typeOf.number.lessThanOrEquals = function (name, test, limit) {
        Check.typeOf.number(name, test);
        if (test > limit) {
            throw new DeveloperError('Expected ' + name + ' to be less than or equal to ' + limit + ', actual value was ' + test);
        }
    };

    /**
     * Throws if test is not typeof 'number' and greater than limit
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @param {Number} limit The limit value to compare against
     * @exception {DeveloperError} test must be typeof 'number' and greater than limit
     */
    Check.typeOf.number.greaterThan = function (name, test, limit) {
        Check.typeOf.number(name, test);
        if (test <= limit) {
            throw new DeveloperError('Expected ' + name + ' to be greater than ' + limit + ', actual value was ' + test);
        }
    };

    /**
     * Throws if test is not typeof 'number' and greater than or equal to limit
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @param {Number} limit The limit value to compare against
     * @exception {DeveloperError} test must be typeof 'number' and greater than or equal to limit
     */
    Check.typeOf.number.greaterThanOrEquals = function (name, test, limit) {
        Check.typeOf.number(name, test);
        if (test < limit) {
            throw new DeveloperError('Expected ' + name + ' to be greater than or equal to' + limit + ', actual value was ' + test);
        }
    };

    /**
     * Throws if test is not typeof 'object'
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'object'
     */
    Check.typeOf.object = function (name, test) {
        if (typeof test !== 'object') {
            throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'object', name));
        }
    };

    /**
     * Throws if test is not typeof 'boolean'
     *
     * @param {String} name The name of the variable being tested
     * @param {*} test The value to test
     * @exception {DeveloperError} test must be typeof 'boolean'
     */
    Check.typeOf.bool = function (name, test) {
        if (typeof test !== 'boolean') {
            throw new DeveloperError(getFailedTypeErrorMessage(typeof test, 'boolean', name));
        }
    };

    return Check;
});

/*
  I've wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
  so it's better encapsulated. Now you can have multiple random number generators
  and they won't stomp all over eachother's state.

  If you want to use this as a substitute for Math.random(), use the random()
  method like so:

  var m = new MersenneTwister();
  var randomNumber = m.random();

  You can also call the other genrand_{foo}() methods on the instance.

  If you want to use a specific seed in order to get a repeatable random
  sequence, pass an integer into the constructor:

  var m = new MersenneTwister(123);

  and that will always produce the same random sequence.

  Sean McCullough (banksean@gmail.com)
*/

/*
   A C-program for MT19937, with initialization improved 2002/1/26.
   Coded by Takuji Nishimura and Makoto Matsumoto.

   Before using, initialize the state by using init_genrand(seed)
   or init_by_array(init_key, key_length).
*/
/**
@license
mersenne-twister.js - https://gist.github.com/banksean/300494

   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

     1. Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.

     2. Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.

     3. The names of its contributors may not be used to endorse or promote
        products derived from this software without specific prior written
        permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
   Any feedback is very welcome.
   http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
   email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
define('ThirdParty/mersenne-twister',[],function() {
var MersenneTwister = function(seed) {
  if (seed == undefined) {
    seed = new Date().getTime();
  }
  /* Period parameters */
  this.N = 624;
  this.M = 397;
  this.MATRIX_A = 0x9908b0df;   /* constant vector a */
  this.UPPER_MASK = 0x80000000; /* most significant w-r bits */
  this.LOWER_MASK = 0x7fffffff; /* least significant r bits */

  this.mt = new Array(this.N); /* the array for the state vector */
  this.mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */

  this.init_genrand(seed);
}

/* initializes mt[N] with a seed */
MersenneTwister.prototype.init_genrand = function(s) {
  this.mt[0] = s >>> 0;
  for (this.mti=1; this.mti<this.N; this.mti++) {
      var s = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
   this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
  + this.mti;
      /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
      /* In the previous versions, MSBs of the seed affect   */
      /* only MSBs of the array mt[].                        */
      /* 2002/01/09 modified by Makoto Matsumoto             */
      this.mt[this.mti] >>>= 0;
      /* for >32 bit machines */
  }
}

/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
//MersenneTwister.prototype.init_by_array = function(init_key, key_length) {
//  var i, j, k;
//  this.init_genrand(19650218);
//  i=1; j=0;
//  k = (this.N>key_length ? this.N : key_length);
//  for (; k; k--) {
//    var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30)
//    this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
//      + init_key[j] + j; /* non linear */
//    this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
//    i++; j++;
//    if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
//    if (j>=key_length) j=0;
//  }
//  for (k=this.N-1; k; k--) {
//    var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
//    this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
//      - i; /* non linear */
//    this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
//    i++;
//    if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
//  }
//
//  this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
//}

/* generates a random number on [0,0xffffffff]-interval */
MersenneTwister.prototype.genrand_int32 = function() {
  var y;
  var mag01 = new Array(0x0, this.MATRIX_A);
  /* mag01[x] = x * MATRIX_A  for x=0,1 */

  if (this.mti >= this.N) { /* generate N words at one time */
    var kk;

    if (this.mti == this.N+1)   /* if init_genrand() has not been called, */
      this.init_genrand(5489); /* a default initial seed is used */

    for (kk=0;kk<this.N-this.M;kk++) {
      y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
      this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
    }
    for (;kk<this.N-1;kk++) {
      y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
      this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
    }
    y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
    this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];

    this.mti = 0;
  }

  y = this.mt[this.mti++];

  /* Tempering */
  y ^= (y >>> 11);
  y ^= (y << 7) & 0x9d2c5680;
  y ^= (y << 15) & 0xefc60000;
  y ^= (y >>> 18);

  return y >>> 0;
}

/* generates a random number on [0,0x7fffffff]-interval */
//MersenneTwister.prototype.genrand_int31 = function() {
//  return (this.genrand_int32()>>>1);
//}

/* generates a random number on [0,1]-real-interval */
//MersenneTwister.prototype.genrand_real1 = function() {
//  return this.genrand_int32()*(1.0/4294967295.0);
//  /* divided by 2^32-1 */
//}

/* generates a random number on [0,1)-real-interval */
MersenneTwister.prototype.random = function() {
  return this.genrand_int32()*(1.0/4294967296.0);
  /* divided by 2^32 */
}

/* generates a random number on (0,1)-real-interval */
//MersenneTwister.prototype.genrand_real3 = function() {
//  return (this.genrand_int32() + 0.5)*(1.0/4294967296.0);
//  /* divided by 2^32 */
//}

/* generates a random number on [0,1) with 53-bit resolution*/
//MersenneTwister.prototype.genrand_res53 = function() {
//  var a=this.genrand_int32()>>>5, b=this.genrand_int32()>>>6;
//  return(a*67108864.0+b)*(1.0/9007199254740992.0);
//}

/* These real versions are due to Isaku Wada, 2002/01/09 added */

return MersenneTwister;
});

/*global define*/
define('Core/Math',[
        '../ThirdParty/mersenne-twister',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        MersenneTwister,
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Math functions.
     *
     * @exports CesiumMath
     */
    var CesiumMath = {};

    /**
     * 0.1
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON1 = 0.1;

    /**
     * 0.01
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON2 = 0.01;

    /**
     * 0.001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON3 = 0.001;

    /**
     * 0.0001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON4 = 0.0001;

    /**
     * 0.00001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON5 = 0.00001;

    /**
     * 0.000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON6 = 0.000001;

    /**
     * 0.0000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON7 = 0.0000001;

    /**
     * 0.00000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON8 = 0.00000001;

    /**
     * 0.000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON9 = 0.000000001;

    /**
     * 0.0000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON10 = 0.0000000001;

    /**
     * 0.00000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON11 = 0.00000000001;

    /**
     * 0.000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON12 = 0.000000000001;

    /**
     * 0.0000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON13 = 0.0000000000001;

    /**
     * 0.00000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON14 = 0.00000000000001;

    /**
     * 0.000000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON15 = 0.000000000000001;

    /**
     * 0.0000000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON16 = 0.0000000000000001;

    /**
     * 0.00000000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON17 = 0.00000000000000001;

    /**
     * 0.000000000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON18 = 0.000000000000000001;

    /**
     * 0.0000000000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON19 = 0.0000000000000000001;

    /**
     * 0.00000000000000000001
     * @type {Number}
     * @constant
     */
    CesiumMath.EPSILON20 = 0.00000000000000000001;

    /**
     * 3.986004418e14
     * @type {Number}
     * @constant
     */
    CesiumMath.GRAVITATIONALPARAMETER = 3.986004418e14;

    /**
     * Radius of the sun in meters: 6.955e8
     * @type {Number}
     * @constant
     */
    CesiumMath.SOLAR_RADIUS = 6.955e8;

    /**
     * The mean radius of the moon, according to the "Report of the IAU/IAG Working Group on
     * Cartographic Coordinates and Rotational Elements of the Planets and satellites: 2000",
     * Celestial Mechanics 82: 83-110, 2002.
     * @type {Number}
     * @constant
     */
    CesiumMath.LUNAR_RADIUS = 1737400.0;

    /**
     * 64 * 1024
     * @type {Number}
     * @constant
     */
    CesiumMath.SIXTY_FOUR_KILOBYTES = 64 * 1024;

    /**
     * Returns the sign of the value; 1 if the value is positive, -1 if the value is
     * negative, or 0 if the value is 0.
     *
     * @param {Number} value The value to return the sign of.
     * @returns {Number} The sign of value.
     */
    CesiumMath.sign = function(value) {
        if (value > 0) {
            return 1;
        }
        if (value < 0) {
            return -1;
        }

        return 0;
    };

    /**
     * Returns 1.0 if the given value is positive or zero, and -1.0 if it is negative.
     * This is similar to {@link CesiumMath#sign} except that returns 1.0 instead of
     * 0.0 when the input value is 0.0.
     * @param {Number} value The value to return the sign of.
     * @returns {Number} The sign of value.
     */
    CesiumMath.signNotZero = function(value) {
        return value < 0.0 ? -1.0 : 1.0;
    };

    /**
     * Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the range [0, rangeMax]
     * @param {Number} value The scalar value in the range [-1.0, 1.0]
     * @param {Number} [rangeMax=255] The maximum value in the mapped range, 255 by default.
     * @returns {Number} A SNORM value, where 0 maps to -1.0 and rangeMax maps to 1.0.
     *
     * @see CesiumMath.fromSNorm
     */
    CesiumMath.toSNorm = function(value, rangeMax) {
        rangeMax = defaultValue(rangeMax, 255);
        return Math.round((CesiumMath.clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMax);
    };

    /**
     * Converts a SNORM value in the range [0, rangeMax] to a scalar in the range [-1.0, 1.0].
     * @param {Number} value SNORM value in the range [0, 255]
     * @param {Number} [rangeMax=255] The maximum value in the SNORM range, 255 by default.
     * @returns {Number} Scalar in the range [-1.0, 1.0].
     *
     * @see CesiumMath.toSNorm
     */
    CesiumMath.fromSNorm = function(value, rangeMax) {
        rangeMax = defaultValue(rangeMax, 255);
        return CesiumMath.clamp(value, 0.0, rangeMax) / rangeMax * 2.0 - 1.0;
    };

    /**
     * Returns the hyperbolic sine of a number.
     * The hyperbolic sine of <em>value</em> is defined to be
     * (<em>e<sup>x</sup>&nbsp;-&nbsp;e<sup>-x</sup></em>)/2.0
     * where <i>e</i> is Euler's number, approximately 2.71828183.
     *
     * <p>Special cases:
     *   <ul>
     *     <li>If the argument is NaN, then the result is NaN.</li>
     *
     *     <li>If the argument is infinite, then the result is an infinity
     *     with the same sign as the argument.</li>
     *
     *     <li>If the argument is zero, then the result is a zero with the
     *     same sign as the argument.</li>
     *   </ul>
     *</p>
     *
     * @param {Number} value The number whose hyperbolic sine is to be returned.
     * @returns {Number} The hyperbolic sine of <code>value</code>.
     */
    CesiumMath.sinh = function(value) {
        var part1 = Math.pow(Math.E, value);
        var part2 = Math.pow(Math.E, -1.0 * value);

        return (part1 - part2) * 0.5;
    };

    /**
     * Returns the hyperbolic cosine of a number.
     * The hyperbolic cosine of <strong>value</strong> is defined to be
     * (<em>e<sup>x</sup>&nbsp;+&nbsp;e<sup>-x</sup></em>)/2.0
     * where <i>e</i> is Euler's number, approximately 2.71828183.
     *
     * <p>Special cases:
     *   <ul>
     *     <li>If the argument is NaN, then the result is NaN.</li>
     *
     *     <li>If the argument is infinite, then the result is positive infinity.</li>
     *
     *     <li>If the argument is zero, then the result is 1.0.</li>
     *   </ul>
     *</p>
     *
     * @param {Number} value The number whose hyperbolic cosine is to be returned.
     * @returns {Number} The hyperbolic cosine of <code>value</code>.
     */
    CesiumMath.cosh = function(value) {
        var part1 = Math.pow(Math.E, value);
        var part2 = Math.pow(Math.E, -1.0 * value);

        return (part1 + part2) * 0.5;
    };

    /**
     * Computes the linear interpolation of two values.
     *
     * @param {Number} p The start value to interpolate.
     * @param {Number} q The end value to interpolate.
     * @param {Number} time The time of interpolation generally in the range <code>[0.0, 1.0]</code>.
     * @returns {Number} The linearly interpolated value.
     *
     * @example
     * var n = Cesium.Math.lerp(0.0, 2.0, 0.5); // returns 1.0
     */
    CesiumMath.lerp = function(p, q, time) {
        return ((1.0 - time) * p) + (time * q);
    };

    /**
     * pi
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.PI = Math.PI;

    /**
     * 1/pi
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.ONE_OVER_PI = 1.0 / Math.PI;

    /**
     * pi/2
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.PI_OVER_TWO = Math.PI * 0.5;

    /**
     * pi/3
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.PI_OVER_THREE = Math.PI / 3.0;

    /**
     * pi/4
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.PI_OVER_FOUR = Math.PI / 4.0;

    /**
     * pi/6
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.PI_OVER_SIX = Math.PI / 6.0;

    /**
     * 3pi/2
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.THREE_PI_OVER_TWO = (3.0 * Math.PI) * 0.5;

    /**
     * 2pi
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.TWO_PI = 2.0 * Math.PI;

    /**
     * 1/2pi
     *
     * @type {Number}
     * @constant
     */
    CesiumMath.ONE_OVER_TWO_PI = 1.0 / (2.0 * Math.PI);

    /**
     * The number of radians in a degree.
     *
     * @type {Number}
     * @constant
     * @default Math.PI / 180.0
     */
    CesiumMath.RADIANS_PER_DEGREE = Math.PI / 180.0;

    /**
     * The number of degrees in a radian.
     *
     * @type {Number}
     * @constant
     * @default 180.0 / Math.PI
     */
    CesiumMath.DEGREES_PER_RADIAN = 180.0 / Math.PI;

    /**
     * The number of radians in an arc second.
     *
     * @type {Number}
     * @constant
     * @default {@link CesiumMath.RADIANS_PER_DEGREE} / 3600.0
     */
    CesiumMath.RADIANS_PER_ARCSECOND = CesiumMath.RADIANS_PER_DEGREE / 3600.0;

    /**
     * Converts degrees to radians.
     * @param {Number} degrees The angle to convert in degrees.
     * @returns {Number} The corresponding angle in radians.
     */
    CesiumMath.toRadians = function(degrees) {
                if (!defined(degrees)) {
            throw new DeveloperError('degrees is required.');
        }
                return degrees * CesiumMath.RADIANS_PER_DEGREE;
    };

    /**
     * Converts radians to degrees.
     * @param {Number} radians The angle to convert in radians.
     * @returns {Number} The corresponding angle in degrees.
     */
    CesiumMath.toDegrees = function(radians) {
                if (!defined(radians)) {
            throw new DeveloperError('radians is required.');
        }
                return radians * CesiumMath.DEGREES_PER_RADIAN;
    };

    /**
     * Converts a longitude value, in radians, to the range [<code>-Math.PI</code>, <code>Math.PI</code>).
     *
     * @param {Number} angle The longitude value, in radians, to convert to the range [<code>-Math.PI</code>, <code>Math.PI</code>).
     * @returns {Number} The equivalent longitude value in the range [<code>-Math.PI</code>, <code>Math.PI</code>).
     *
     * @example
     * // Convert 270 degrees to -90 degrees longitude
     * var longitude = Cesium.Math.convertLongitudeRange(Cesium.Math.toRadians(270.0));
     */
    CesiumMath.convertLongitudeRange = function(angle) {
                if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }
                var twoPi = CesiumMath.TWO_PI;

        var simplified = angle - Math.floor(angle / twoPi) * twoPi;

        if (simplified < -Math.PI) {
            return simplified + twoPi;
        }
        if (simplified >= Math.PI) {
            return simplified - twoPi;
        }

        return simplified;
    };

    /**
     * Convenience function that clamps a latitude value, in radians, to the range [<code>-Math.PI/2</code>, <code>Math.PI/2</code>).
     * Useful for sanitizing data before use in objects requiring correct range.
     *
     * @param {Number} angle The latitude value, in radians, to clamp to the range [<code>-Math.PI/2</code>, <code>Math.PI/2</code>).
     * @returns {Number} The latitude value clamped to the range [<code>-Math.PI/2</code>, <code>Math.PI/2</code>).
     *
     * @example
     * // Clamp 108 degrees latitude to 90 degrees latitude
     * var latitude = Cesium.Math.clampToLatitudeRange(Cesium.Math.toRadians(108.0));
     */
    CesiumMath.clampToLatitudeRange = function(angle) {
                if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }
        
        return CesiumMath.clamp(angle, -1*CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO);
    };

    /**
     * Produces an angle in the range -Pi <= angle <= Pi which is equivalent to the provided angle.
     *
     * @param {Number} angle in radians
     * @returns {Number} The angle in the range [<code>-CesiumMath.PI</code>, <code>CesiumMath.PI</code>].
     */
    CesiumMath.negativePiToPi = function(angle) {
                if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }
                return CesiumMath.zeroToTwoPi(angle + CesiumMath.PI) - CesiumMath.PI;
    };

    /**
     * Produces an angle in the range 0 <= angle <= 2Pi which is equivalent to the provided angle.
     *
     * @param {Number} angle in radians
     * @returns {Number} The angle in the range [0, <code>CesiumMath.TWO_PI</code>].
     */
    CesiumMath.zeroToTwoPi = function(angle) {
                if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }
                var mod = CesiumMath.mod(angle, CesiumMath.TWO_PI);
        if (Math.abs(mod) < CesiumMath.EPSILON14 && Math.abs(angle) > CesiumMath.EPSILON14) {
            return CesiumMath.TWO_PI;
        }
        return mod;
    };

    /**
     * The modulo operation that also works for negative dividends.
     *
     * @param {Number} m The dividend.
     * @param {Number} n The divisor.
     * @returns {Number} The remainder.
     */
    CesiumMath.mod = function(m, n) {
                if (!defined(m)) {
            throw new DeveloperError('m is required.');
        }
        if (!defined(n)) {
            throw new DeveloperError('n is required.');
        }
                return ((m % n) + n) % n;
    };

    /**
     * Determines if two values are equal using an absolute or relative tolerance test. This is useful
     * to avoid problems due to roundoff error when comparing floating-point values directly. The values are
     * first compared using an absolute tolerance test. If that fails, a relative tolerance test is performed.
     * Use this test if you are unsure of the magnitudes of left and right.
     *
     * @param {Number} left The first value to compare.
     * @param {Number} right The other value to compare.
     * @param {Number} relativeEpsilon The maximum inclusive delta between <code>left</code> and <code>right</code> for the relative tolerance test.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The maximum inclusive delta between <code>left</code> and <code>right</code> for the absolute tolerance test.
     * @returns {Boolean} <code>true</code> if the values are equal within the epsilon; otherwise, <code>false</code>.
     *
     * @example
     * var a = Cesium.Math.equalsEpsilon(0.0, 0.01, Cesium.Math.EPSILON2); // true
     * var b = Cesium.Math.equalsEpsilon(0.0, 0.1, Cesium.Math.EPSILON2);  // false
     * var c = Cesium.Math.equalsEpsilon(3699175.1634344, 3699175.2, Cesium.Math.EPSILON7); // true
     * var d = Cesium.Math.equalsEpsilon(3699175.1634344, 3699175.2, Cesium.Math.EPSILON9); // false
     */
    CesiumMath.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
                if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required.');
        }
        if (!defined(relativeEpsilon)) {
            throw new DeveloperError('relativeEpsilon is required.');
        }
                absoluteEpsilon = defaultValue(absoluteEpsilon, relativeEpsilon);
        var absDiff = Math.abs(left - right);
        return absDiff <= absoluteEpsilon || absDiff <= relativeEpsilon * Math.max(Math.abs(left), Math.abs(right));
    };

    var factorials = [1];

    /**
     * Computes the factorial of the provided number.
     *
     * @param {Number} n The number whose factorial is to be computed.
     * @returns {Number} The factorial of the provided number or undefined if the number is less than 0.
     *
     * @exception {DeveloperError} A number greater than or equal to 0 is required.
     *
     *
     * @example
     * //Compute 7!, which is equal to 5040
     * var computedFactorial = Cesium.Math.factorial(7);
     *
     * @see {@link http://en.wikipedia.org/wiki/Factorial|Factorial on Wikipedia}
     */
    CesiumMath.factorial = function(n) {
                if (typeof n !== 'number' || n < 0) {
            throw new DeveloperError('A number greater than or equal to 0 is required.');
        }
        
        var length = factorials.length;
        if (n >= length) {
            var sum = factorials[length - 1];
            for (var i = length; i <= n; i++) {
                factorials.push(sum * i);
            }
        }
        return factorials[n];
    };

    /**
     * Increments a number with a wrapping to a minimum value if the number exceeds the maximum value.
     *
     * @param {Number} [n] The number to be incremented.
     * @param {Number} [maximumValue] The maximum incremented value before rolling over to the minimum value.
     * @param {Number} [minimumValue=0.0] The number reset to after the maximum value has been exceeded.
     * @returns {Number} The incremented number.
     *
     * @exception {DeveloperError} Maximum value must be greater than minimum value.
     *
     * @example
     * var n = Cesium.Math.incrementWrap(5, 10, 0); // returns 6
     * var n = Cesium.Math.incrementWrap(10, 10, 0); // returns 0
     */
    CesiumMath.incrementWrap = function(n, maximumValue, minimumValue) {
        minimumValue = defaultValue(minimumValue, 0.0);

                if (!defined(n)) {
            throw new DeveloperError('n is required.');
        }
        if (maximumValue <= minimumValue) {
            throw new DeveloperError('maximumValue must be greater than minimumValue.');
        }
        
        ++n;
        if (n > maximumValue) {
            n = minimumValue;
        }
        return n;
    };

    /**
     * Determines if a positive integer is a power of two.
     *
     * @param {Number} n The positive integer to test.
     * @returns {Boolean} <code>true</code> if the number if a power of two; otherwise, <code>false</code>.
     *
     * @exception {DeveloperError} A number greater than or equal to 0 is required.
     *
     * @example
     * var t = Cesium.Math.isPowerOfTwo(16); // true
     * var f = Cesium.Math.isPowerOfTwo(20); // false
     */
    CesiumMath.isPowerOfTwo = function(n) {
                if (typeof n !== 'number' || n < 0) {
            throw new DeveloperError('A number greater than or equal to 0 is required.');
        }
        
        return (n !== 0) && ((n & (n - 1)) === 0);
    };

    /**
     * Computes the next power-of-two integer greater than or equal to the provided positive integer.
     *
     * @param {Number} n The positive integer to test.
     * @returns {Number} The next power-of-two integer.
     *
     * @exception {DeveloperError} A number greater than or equal to 0 is required.
     *
     * @example
     * var n = Cesium.Math.nextPowerOfTwo(29); // 32
     * var m = Cesium.Math.nextPowerOfTwo(32); // 32
     */
    CesiumMath.nextPowerOfTwo = function(n) {
                if (typeof n !== 'number' || n < 0) {
            throw new DeveloperError('A number greater than or equal to 0 is required.');
        }
        
        // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
        --n;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        ++n;

        return n;
    };

    /**
     * Constraint a value to lie between two values.
     *
     * @param {Number} value The value to constrain.
     * @param {Number} min The minimum value.
     * @param {Number} max The maximum value.
     * @returns {Number} The value clamped so that min <= value <= max.
     */
    CesiumMath.clamp = function(value, min, max) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(min)) {
            throw new DeveloperError('min is required.');
        }
        if (!defined(max)) {
            throw new DeveloperError('max is required.');
        }
                return value < min ? min : value > max ? max : value;
    };

    var randomNumberGenerator = new MersenneTwister();

    /**
     * Sets the seed used by the random number generator
     * in {@link CesiumMath#nextRandomNumber}.
     *
     * @param {Number} seed An integer used as the seed.
     */
    CesiumMath.setRandomNumberSeed = function(seed) {
                if (!defined(seed)) {
            throw new DeveloperError('seed is required.');
        }
        
        randomNumberGenerator = new MersenneTwister(seed);
    };

    /**
     * Generates a random number in the range of [0.0, 1.0)
     * using a Mersenne twister.
     *
     * @returns {Number} A random number in the range of [0.0, 1.0).
     *
     * @see CesiumMath.setRandomNumberSeed
     * @see {@link http://en.wikipedia.org/wiki/Mersenne_twister|Mersenne twister on Wikipedia}
     */
    CesiumMath.nextRandomNumber = function() {
        return randomNumberGenerator.random();
    };

    /**
     * Computes <code>Math.acos(value)</acode>, but first clamps <code>value</code> to the range [-1.0, 1.0]
     * so that the function will never return NaN.
     *
     * @param {Number} value The value for which to compute acos.
     * @returns {Number} The acos of the value if the value is in the range [-1.0, 1.0], or the acos of -1.0 or 1.0,
     *          whichever is closer, if the value is outside the range.
     */
    CesiumMath.acosClamped = function(value) {
                if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }
                return Math.acos(CesiumMath.clamp(value, -1.0, 1.0));
    };

    /**
     * Computes <code>Math.asin(value)</acode>, but first clamps <code>value</code> to the range [-1.0, 1.0]
     * so that the function will never return NaN.
     *
     * @param {Number} value The value for which to compute asin.
     * @returns {Number} The asin of the value if the value is in the range [-1.0, 1.0], or the asin of -1.0 or 1.0,
     *          whichever is closer, if the value is outside the range.
     */
    CesiumMath.asinClamped = function(value) {
                if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }
                return Math.asin(CesiumMath.clamp(value, -1.0, 1.0));
    };

    /**
     * Finds the chord length between two points given the circle's radius and the angle between the points.
     *
     * @param {Number} angle The angle between the two points.
     * @param {Number} radius The radius of the circle.
     * @returns {Number} The chord length.
     */
    CesiumMath.chordLength = function(angle, radius) {
                if (!defined(angle)) {
            throw new DeveloperError('angle is required.');
        }
        if (!defined(radius)) {
            throw new DeveloperError('radius is required.');
        }
                return 2.0 * radius * Math.sin(angle * 0.5);
    };

    /**
     * Finds the logarithm of a number to a base.
     *
     * @param {Number} number The number.
     * @param {Number} base The base.
     * @returns {Number} The result.
     */
    CesiumMath.logBase = function(number, base) {
                if (!defined(number)) {
            throw new DeveloperError('number is required.');
        }
        if (!defined(base)) {
            throw new DeveloperError('base is required.');
        }
                return Math.log(number) / Math.log(base);
    };

    /**
     * @private
     */
    CesiumMath.fog = function(distanceToCamera, density) {
        var scalar = distanceToCamera * density;
        return 1.0 - Math.exp(-(scalar * scalar));
    };

    return CesiumMath;
});

/*global define*/
define('Core/Cartesian3',[
    './Check',
    './defaultValue',
    './defined',
    './DeveloperError',
    './freezeObject',
    './Math'
    ], function(
        Check,
        defaultValue,
        defined,
        DeveloperError,
        freezeObject,
        CesiumMath) {
    'use strict';

    /**
     * A 3D Cartesian point.
     * @alias Cartesian3
     * @constructor
     *
     * @param {Number} [x=0.0] The X component.
     * @param {Number} [y=0.0] The Y component.
     * @param {Number} [z=0.0] The Z component.
     *
     * @see Cartesian2
     * @see Cartesian4
     * @see Packable
     */
    function Cartesian3(x, y, z) {
        /**
         * The X component.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The Y component.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The Z component.
         * @type {Number}
         * @default 0.0
         */
        this.z = defaultValue(z, 0.0);
    }

    /**
     * Converts the provided Spherical into Cartesian3 coordinates.
     *
     * @param {Spherical} spherical The Spherical to be converted to Cartesian3.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     */
    Cartesian3.fromSpherical = function(spherical, result) {
                Check.typeOf.object('spherical', spherical);
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        var clock = spherical.clock;
        var cone = spherical.cone;
        var magnitude = defaultValue(spherical.magnitude, 1.0);
        var radial = magnitude * Math.sin(cone);
        result.x = radial * Math.cos(clock);
        result.y = radial * Math.sin(clock);
        result.z = magnitude * Math.cos(cone);
        return result;
    };

    /**
     * Creates a Cartesian3 instance from x, y and z coordinates.
     *
     * @param {Number} x The x coordinate.
     * @param {Number} y The y coordinate.
     * @param {Number} z The z coordinate.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     */
    Cartesian3.fromElements = function(x, y, z, result) {
        if (!defined(result)) {
            return new Cartesian3(x, y, z);
        }

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Duplicates a Cartesian3 instance.
     *
     * @param {Cartesian3} cartesian The Cartesian to duplicate.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided. (Returns undefined if cartesian is undefined)
     */
    Cartesian3.clone = function(cartesian, result) {
        if (!defined(cartesian)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Cartesian3(cartesian.x, cartesian.y, cartesian.z);
        }

        result.x = cartesian.x;
        result.y = cartesian.y;
        result.z = cartesian.z;
        return result;
    };

    /**
     * Creates a Cartesian3 instance from an existing Cartesian4.  This simply takes the
     * x, y, and z properties of the Cartesian4 and drops w.
     * @function
     *
     * @param {Cartesian4} cartesian The Cartesian4 instance to create a Cartesian3 instance from.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     */
    Cartesian3.fromCartesian4 = Cartesian3.clone;

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Cartesian3.packedLength = 3;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Cartesian3} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Cartesian3.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex++] = value.y;
        array[startingIndex] = value.z;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Cartesian3} [result] The object into which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     */
    Cartesian3.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Cartesian3();
        }
        result.x = array[startingIndex++];
        result.y = array[startingIndex++];
        result.z = array[startingIndex];
        return result;
    };

    /**
     * Flattens an array of Cartesian3s into an array of components.
     *
     * @param {Cartesian3[]} array The array of cartesians to pack.
     * @param {Number[]} result The array onto which to store the result.
     * @returns {Number[]} The packed array.
     */
    Cartesian3.packArray = function(array, result) {
                Check.defined('array', array);
        
        var length = array.length;
        if (!defined(result)) {
            result = new Array(length * 3);
        } else {
            result.length = length * 3;
        }

        for (var i = 0; i < length; ++i) {
            Cartesian3.pack(array[i], result, i * 3);
        }
        return result;
    };

    /**
     * Unpacks an array of cartesian components into an array of Cartesian3s.
     *
     * @param {Number[]} array The array of components to unpack.
     * @param {Cartesian3[]} result The array onto which to store the result.
     * @returns {Cartesian3[]} The unpacked array.
     */
    Cartesian3.unpackArray = function(array, result) {
                Check.defined('array', array);
        Check.typeOf.number.greaterThanOrEquals('array.length', array.length, 3);
        if (array.length % 3 !== 0) {
            throw new DeveloperError('array length must be a multiple of 3.');
        }
        
        var length = array.length;
        if (!defined(result)) {
            result = new Array(length / 3);
        } else {
            result.length = length / 3;
        }

        for (var i = 0; i < length; i += 3) {
            var index = i / 3;
            result[index] = Cartesian3.unpack(array, i, result[index]);
        }
        return result;
    };

    /**
     * Creates a Cartesian3 from three consecutive elements in an array.
     * @function
     *
     * @param {Number[]} array The array whose three consecutive elements correspond to the x, y, and z components, respectively.
     * @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to the x component.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     *
     * @example
     * // Create a Cartesian3 with (1.0, 2.0, 3.0)
     * var v = [1.0, 2.0, 3.0];
     * var p = Cesium.Cartesian3.fromArray(v);
     *
     * // Create a Cartesian3 with (1.0, 2.0, 3.0) using an offset into an array
     * var v2 = [0.0, 0.0, 1.0, 2.0, 3.0];
     * var p2 = Cesium.Cartesian3.fromArray(v2, 2);
     */
    Cartesian3.fromArray = Cartesian3.unpack;

    /**
     * Computes the value of the maximum component for the supplied Cartesian.
     *
     * @param {Cartesian3} cartesian The cartesian to use.
     * @returns {Number} The value of the maximum component.
     */
    Cartesian3.maximumComponent = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return Math.max(cartesian.x, cartesian.y, cartesian.z);
    };

    /**
     * Computes the value of the minimum component for the supplied Cartesian.
     *
     * @param {Cartesian3} cartesian The cartesian to use.
     * @returns {Number} The value of the minimum component.
     */
    Cartesian3.minimumComponent = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return Math.min(cartesian.x, cartesian.y, cartesian.z);
    };

    /**
     * Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
     *
     * @param {Cartesian3} first A cartesian to compare.
     * @param {Cartesian3} second A cartesian to compare.
     * @param {Cartesian3} result The object into which to store the result.
     * @returns {Cartesian3} A cartesian with the minimum components.
     */
    Cartesian3.minimumByComponent = function(first, second, result) {
                Check.typeOf.object('first', first);
        Check.typeOf.object('second', second);
        Check.typeOf.object('result', result);
        
        result.x = Math.min(first.x, second.x);
        result.y = Math.min(first.y, second.y);
        result.z = Math.min(first.z, second.z);

        return result;
    };

    /**
     * Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
     *
     * @param {Cartesian3} first A cartesian to compare.
     * @param {Cartesian3} second A cartesian to compare.
     * @param {Cartesian3} result The object into which to store the result.
     * @returns {Cartesian3} A cartesian with the maximum components.
     */
    Cartesian3.maximumByComponent = function(first, second, result) {
                Check.typeOf.object('first', first);
        Check.typeOf.object('second', second);
        Check.typeOf.object('result', result);
        
        result.x = Math.max(first.x, second.x);
        result.y = Math.max(first.y, second.y);
        result.z = Math.max(first.z, second.z);
        return result;
    };

    /**
     * Computes the provided Cartesian's squared magnitude.
     *
     * @param {Cartesian3} cartesian The Cartesian instance whose squared magnitude is to be computed.
     * @returns {Number} The squared magnitude.
     */
    Cartesian3.magnitudeSquared = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return cartesian.x * cartesian.x + cartesian.y * cartesian.y + cartesian.z * cartesian.z;
    };

    /**
     * Computes the Cartesian's magnitude (length).
     *
     * @param {Cartesian3} cartesian The Cartesian instance whose magnitude is to be computed.
     * @returns {Number} The magnitude.
     */
    Cartesian3.magnitude = function(cartesian) {
        return Math.sqrt(Cartesian3.magnitudeSquared(cartesian));
    };

    var distanceScratch = new Cartesian3();

    /**
     * Computes the distance between two points.
     *
     * @param {Cartesian3} left The first point to compute the distance from.
     * @param {Cartesian3} right The second point to compute the distance to.
     * @returns {Number} The distance between two points.
     *
     * @example
     * // Returns 1.0
     * var d = Cesium.Cartesian3.distance(new Cesium.Cartesian3(1.0, 0.0, 0.0), new Cesium.Cartesian3(2.0, 0.0, 0.0));
     */
    Cartesian3.distance = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        Cartesian3.subtract(left, right, distanceScratch);
        return Cartesian3.magnitude(distanceScratch);
    };

    /**
     * Computes the squared distance between two points.  Comparing squared distances
     * using this function is more efficient than comparing distances using {@link Cartesian3#distance}.
     *
     * @param {Cartesian3} left The first point to compute the distance from.
     * @param {Cartesian3} right The second point to compute the distance to.
     * @returns {Number} The distance between two points.
     *
     * @example
     * // Returns 4.0, not 2.0
     * var d = Cesium.Cartesian3.distanceSquared(new Cesium.Cartesian3(1.0, 0.0, 0.0), new Cesium.Cartesian3(3.0, 0.0, 0.0));
     */
    Cartesian3.distanceSquared = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        Cartesian3.subtract(left, right, distanceScratch);
        return Cartesian3.magnitudeSquared(distanceScratch);
    };

    /**
     * Computes the normalized form of the supplied Cartesian.
     *
     * @param {Cartesian3} cartesian The Cartesian to be normalized.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.normalize = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var magnitude = Cartesian3.magnitude(cartesian);

        result.x = cartesian.x / magnitude;
        result.y = cartesian.y / magnitude;
        result.z = cartesian.z / magnitude;

                if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z)) {
            throw new DeveloperError('normalized result is not a number');
        }
        
        return result;
    };

    /**
     * Computes the dot (scalar) product of two Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @returns {Number} The dot product.
     */
    Cartesian3.dot = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        return left.x * right.x + left.y * right.y + left.z * right.z;
    };

    /**
     * Computes the componentwise product of two Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.multiplyComponents = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x * right.x;
        result.y = left.y * right.y;
        result.z = left.z * right.z;
        return result;
    };

    /**
     * Computes the componentwise quotient of two Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.divideComponents = function(left, right, result) {
                if (!defined(left)) {
            throw new DeveloperError('left is required');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.x = left.x / right.x;
        result.y = left.y / right.y;
        result.z = left.z / right.z;
        return result;
    };

    /**
     * Computes the componentwise sum of two Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x + right.x;
        result.y = left.y + right.y;
        result.z = left.z + right.z;
        return result;
    };

    /**
     * Computes the componentwise difference of two Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x - right.x;
        result.y = left.y - right.y;
        result.z = left.z - right.z;
        return result;
    };

    /**
     * Multiplies the provided Cartesian componentwise by the provided scalar.
     *
     * @param {Cartesian3} cartesian The Cartesian to be scaled.
     * @param {Number} scalar The scalar to multiply with.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.multiplyByScalar = function(cartesian, scalar, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = cartesian.x * scalar;
        result.y = cartesian.y * scalar;
        result.z = cartesian.z * scalar;
        return result;
    };

    /**
     * Divides the provided Cartesian componentwise by the provided scalar.
     *
     * @param {Cartesian3} cartesian The Cartesian to be divided.
     * @param {Number} scalar The scalar to divide by.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.divideByScalar = function(cartesian, scalar, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = cartesian.x / scalar;
        result.y = cartesian.y / scalar;
        result.z = cartesian.z / scalar;
        return result;
    };

    /**
     * Negates the provided Cartesian.
     *
     * @param {Cartesian3} cartesian The Cartesian to be negated.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.negate = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result.x = -cartesian.x;
        result.y = -cartesian.y;
        result.z = -cartesian.z;
        return result;
    };

    /**
     * Computes the absolute value of the provided Cartesian.
     *
     * @param {Cartesian3} cartesian The Cartesian whose absolute value is to be computed.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.abs = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result.x = Math.abs(cartesian.x);
        result.y = Math.abs(cartesian.y);
        result.z = Math.abs(cartesian.z);
        return result;
    };

    var lerpScratch = new Cartesian3();
    /**
     * Computes the linear interpolation or extrapolation at t using the provided cartesians.
     *
     * @param {Cartesian3} start The value corresponding to t at 0.0.
     * @param {Cartesian3} end The value corresponding to t at 1.0.
     * @param {Number} t The point along t at which to interpolate.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Cartesian3.lerp = function(start, end, t, result) {
                Check.typeOf.object('start', start);
        Check.typeOf.object('end', end);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        Cartesian3.multiplyByScalar(end, t, lerpScratch);
        result = Cartesian3.multiplyByScalar(start, 1.0 - t, result);
        return Cartesian3.add(lerpScratch, result, result);
    };

    var angleBetweenScratch = new Cartesian3();
    var angleBetweenScratch2 = new Cartesian3();
    /**
     * Returns the angle, in radians, between the provided Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @returns {Number} The angle between the Cartesians.
     */
    Cartesian3.angleBetween = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        Cartesian3.normalize(left, angleBetweenScratch);
        Cartesian3.normalize(right, angleBetweenScratch2);
        var cosine = Cartesian3.dot(angleBetweenScratch, angleBetweenScratch2);
        var sine = Cartesian3.magnitude(Cartesian3.cross(angleBetweenScratch, angleBetweenScratch2, angleBetweenScratch));
        return Math.atan2(sine, cosine);
    };

    var mostOrthogonalAxisScratch = new Cartesian3();
    /**
     * Returns the axis that is most orthogonal to the provided Cartesian.
     *
     * @param {Cartesian3} cartesian The Cartesian on which to find the most orthogonal axis.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The most orthogonal axis.
     */
    Cartesian3.mostOrthogonalAxis = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var f = Cartesian3.normalize(cartesian, mostOrthogonalAxisScratch);
        Cartesian3.abs(f, f);

        if (f.x <= f.y) {
            if (f.x <= f.z) {
                result = Cartesian3.clone(Cartesian3.UNIT_X, result);
            } else {
                result = Cartesian3.clone(Cartesian3.UNIT_Z, result);
            }
        } else {
            if (f.y <= f.z) {
                result = Cartesian3.clone(Cartesian3.UNIT_Y, result);
            } else {
                result = Cartesian3.clone(Cartesian3.UNIT_Z, result);
            }
        }

        return result;
    };

    /**
     * Compares the provided Cartesians componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartesian3} [left] The first Cartesian.
     * @param {Cartesian3} [right] The second Cartesian.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Cartesian3.equals = function(left, right) {
            return (left === right) ||
              ((defined(left)) &&
               (defined(right)) &&
               (left.x === right.x) &&
               (left.y === right.y) &&
               (left.z === right.z));
    };

    /**
     * @private
     */
    Cartesian3.equalsArray = function(cartesian, array, offset) {
        return cartesian.x === array[offset] &&
               cartesian.y === array[offset + 1] &&
               cartesian.z === array[offset + 2];
    };

    /**
     * Compares the provided Cartesians componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {Cartesian3} [left] The first Cartesian.
     * @param {Cartesian3} [right] The second Cartesian.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartesian3.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                CesiumMath.equalsEpsilon(left.x, right.x, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.y, right.y, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.z, right.z, relativeEpsilon, absoluteEpsilon));
    };

    /**
     * Computes the cross (outer) product of two Cartesians.
     *
     * @param {Cartesian3} left The first Cartesian.
     * @param {Cartesian3} right The second Cartesian.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The cross product.
     */
    Cartesian3.cross = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        var leftX = left.x;
        var leftY = left.y;
        var leftZ = left.z;
        var rightX = right.x;
        var rightY = right.y;
        var rightZ = right.z;

        var x = leftY * rightZ - leftZ * rightY;
        var y = leftZ * rightX - leftX * rightZ;
        var z = leftX * rightY - leftY * rightX;

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Returns a Cartesian3 position from longitude and latitude values given in degrees.
     *
     * @param {Number} longitude The longitude, in degrees
     * @param {Number} latitude The latitude, in degrees
     * @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The position
     *
     * @example
     * var position = Cesium.Cartesian3.fromDegrees(-115.0, 37.0);
     */
    Cartesian3.fromDegrees = function(longitude, latitude, height, ellipsoid, result) {
                Check.typeOf.number('longitude', longitude);
        Check.typeOf.number('latitude', latitude);
        
        longitude = CesiumMath.toRadians(longitude);
        latitude = CesiumMath.toRadians(latitude);
        return Cartesian3.fromRadians(longitude, latitude, height, ellipsoid, result);
    };

    var scratchN = new Cartesian3();
    var scratchK = new Cartesian3();
    var wgs84RadiiSquared = new Cartesian3(6378137.0 * 6378137.0, 6378137.0 * 6378137.0, 6356752.3142451793 * 6356752.3142451793);

    /**
     * Returns a Cartesian3 position from longitude and latitude values given in radians.
     *
     * @param {Number} longitude The longitude, in radians
     * @param {Number} latitude The latitude, in radians
     * @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The position
     *
     * @example
     * var position = Cesium.Cartesian3.fromRadians(-2.007, 0.645);
     */
    Cartesian3.fromRadians = function(longitude, latitude, height, ellipsoid, result) {
                Check.typeOf.number('longitude', longitude);
        Check.typeOf.number('latitude', latitude);
        
        height = defaultValue(height, 0.0);
        var radiiSquared = defined(ellipsoid) ? ellipsoid.radiiSquared : wgs84RadiiSquared;

        var cosLatitude = Math.cos(latitude);
        scratchN.x = cosLatitude * Math.cos(longitude);
        scratchN.y = cosLatitude * Math.sin(longitude);
        scratchN.z = Math.sin(latitude);
        scratchN = Cartesian3.normalize(scratchN, scratchN);

        Cartesian3.multiplyComponents(radiiSquared, scratchN, scratchK);
        var gamma = Math.sqrt(Cartesian3.dot(scratchN, scratchK));
        scratchK = Cartesian3.divideByScalar(scratchK, gamma, scratchK);
        scratchN = Cartesian3.multiplyByScalar(scratchN, height, scratchN);

        if (!defined(result)) {
            result = new Cartesian3();
        }
        return Cartesian3.add(scratchK, scratchN, result);
    };

    /**
     * Returns an array of Cartesian3 positions given an array of longitude and latitude values given in degrees.
     *
     * @param {Number[]} coordinates A list of longitude and latitude values. Values alternate [longitude, latitude, longitude, latitude...].
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the coordinates lie.
     * @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
     * @returns {Cartesian3[]} The array of positions.
     *
     * @example
     * var positions = Cesium.Cartesian3.fromDegreesArray([-115.0, 37.0, -107.0, 33.0]);
     */
    Cartesian3.fromDegreesArray = function(coordinates, ellipsoid, result) {
                Check.defined('coordinates', coordinates);
        if (coordinates.length < 2 || coordinates.length % 2 !== 0) {
            throw new DeveloperError('the number of coordinates must be a multiple of 2 and at least 2');
        }
        
        var length = coordinates.length;
        if (!defined(result)) {
            result = new Array(length / 2);
        } else {
            result.length = length / 2;
        }

        for (var i = 0; i < length; i += 2) {
            var longitude = coordinates[i];
            var latitude = coordinates[i + 1];
            var index = i / 2;
            result[index] = Cartesian3.fromDegrees(longitude, latitude, 0, ellipsoid, result[index]);
        }

        return result;
    };

    /**
     * Returns an array of Cartesian3 positions given an array of longitude and latitude values given in radians.
     *
     * @param {Number[]} coordinates A list of longitude and latitude values. Values alternate [longitude, latitude, longitude, latitude...].
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the coordinates lie.
     * @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
     * @returns {Cartesian3[]} The array of positions.
     *
     * @example
     * var positions = Cesium.Cartesian3.fromRadiansArray([-2.007, 0.645, -1.867, .575]);
     */
    Cartesian3.fromRadiansArray = function(coordinates, ellipsoid, result) {
                Check.defined('coordinates', coordinates);
        if (coordinates.length < 2 || coordinates.length % 2 !== 0) {
            throw new DeveloperError('the number of coordinates must be a multiple of 2 and at least 2');
        }
        
        var length = coordinates.length;
        if (!defined(result)) {
            result = new Array(length / 2);
        } else {
            result.length = length / 2;
        }

        for (var i = 0; i < length; i += 2) {
            var longitude = coordinates[i];
            var latitude = coordinates[i + 1];
            var index = i / 2;
            result[index] = Cartesian3.fromRadians(longitude, latitude, 0, ellipsoid, result[index]);
        }

        return result;
    };

    /**
     * Returns an array of Cartesian3 positions given an array of longitude, latitude and height values where longitude and latitude are given in degrees.
     *
     * @param {Number[]} coordinates A list of longitude, latitude and height values. Values alternate [longitude, latitude, height, longitude, latitude, height...].
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
     * @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
     * @returns {Cartesian3[]} The array of positions.
     *
     * @example
     * var positions = Cesium.Cartesian3.fromDegreesArrayHeights([-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]);
     */
    Cartesian3.fromDegreesArrayHeights = function(coordinates, ellipsoid, result) {
                Check.defined('coordinates', coordinates);
        if (coordinates.length < 3 || coordinates.length % 3 !== 0) {
            throw new DeveloperError('the number of coordinates must be a multiple of 3 and at least 3');
        }
        
        var length = coordinates.length;
        if (!defined(result)) {
            result = new Array(length / 3);
        } else {
            result.length = length / 3;
        }

        for (var i = 0; i < length; i += 3) {
            var longitude = coordinates[i];
            var latitude = coordinates[i + 1];
            var height = coordinates[i + 2];
            var index = i / 3;
            result[index] = Cartesian3.fromDegrees(longitude, latitude, height, ellipsoid, result[index]);
        }

        return result;
    };

    /**
     * Returns an array of Cartesian3 positions given an array of longitude, latitude and height values where longitude and latitude are given in radians.
     *
     * @param {Number[]} coordinates A list of longitude, latitude and height values. Values alternate [longitude, latitude, height, longitude, latitude, height...].
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
     * @param {Cartesian3[]} [result] An array of Cartesian3 objects to store the result.
     * @returns {Cartesian3[]} The array of positions.
     *
     * @example
     * var positions = Cesium.Cartesian3.fromRadiansArrayHeights([-2.007, 0.645, 100000.0, -1.867, .575, 150000.0]);
     */
    Cartesian3.fromRadiansArrayHeights = function(coordinates, ellipsoid, result) {
                Check.defined('coordinates', coordinates);
        if (coordinates.length < 3 || coordinates.length % 3 !== 0) {
            throw new DeveloperError('the number of coordinates must be a multiple of 3 and at least 3');
        }
        
        var length = coordinates.length;
        if (!defined(result)) {
            result = new Array(length / 3);
        } else {
            result.length = length / 3;
        }

        for (var i = 0; i < length; i += 3) {
            var longitude = coordinates[i];
            var latitude = coordinates[i + 1];
            var height = coordinates[i + 2];
            var index = i / 3;
            result[index] = Cartesian3.fromRadians(longitude, latitude, height, ellipsoid, result[index]);
        }

        return result;
    };

    /**
     * An immutable Cartesian3 instance initialized to (0.0, 0.0, 0.0).
     *
     * @type {Cartesian3}
     * @constant
     */
    Cartesian3.ZERO = freezeObject(new Cartesian3(0.0, 0.0, 0.0));

    /**
     * An immutable Cartesian3 instance initialized to (1.0, 0.0, 0.0).
     *
     * @type {Cartesian3}
     * @constant
     */
    Cartesian3.UNIT_X = freezeObject(new Cartesian3(1.0, 0.0, 0.0));

    /**
     * An immutable Cartesian3 instance initialized to (0.0, 1.0, 0.0).
     *
     * @type {Cartesian3}
     * @constant
     */
    Cartesian3.UNIT_Y = freezeObject(new Cartesian3(0.0, 1.0, 0.0));

    /**
     * An immutable Cartesian3 instance initialized to (0.0, 0.0, 1.0).
     *
     * @type {Cartesian3}
     * @constant
     */
    Cartesian3.UNIT_Z = freezeObject(new Cartesian3(0.0, 0.0, 1.0));

    /**
     * Duplicates this Cartesian3 instance.
     *
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     */
    Cartesian3.prototype.clone = function(result) {
        return Cartesian3.clone(this, result);
    };

    /**
     * Compares this Cartesian against the provided Cartesian componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartesian3} [right] The right hand side Cartesian.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Cartesian3.prototype.equals = function(right) {
        return Cartesian3.equals(this, right);
    };

    /**
     * Compares this Cartesian against the provided Cartesian componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {Cartesian3} [right] The right hand side Cartesian.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartesian3.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
        return Cartesian3.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
    };

    /**
     * Creates a string representing this Cartesian in the format '(x, y, z)'.
     *
     * @returns {String} A string representing this Cartesian in the format '(x, y, z)'.
     */
    Cartesian3.prototype.toString = function() {
        return '(' + this.x + ', ' + this.y + ', ' + this.z + ')';
    };

    return Cartesian3;
});

/*global define*/
define('Core/scaleToGeodeticSurface',[
        './Cartesian3',
        './defined',
        './DeveloperError',
        './Math'
    ], function(
        Cartesian3,
        defined,
        DeveloperError,
        CesiumMath) {
    'use strict';

    var scaleToGeodeticSurfaceIntersection = new Cartesian3();
    var scaleToGeodeticSurfaceGradient = new Cartesian3();

    /**
     * Scales the provided Cartesian position along the geodetic surface normal
     * so that it is on the surface of this ellipsoid.  If the position is
     * at the center of the ellipsoid, this function returns undefined.
     *
     * @param {Cartesian3} cartesian The Cartesian position to scale.
     * @param {Cartesian3} oneOverRadii One over radii of the ellipsoid.
     * @param {Cartesian3} oneOverRadiiSquared One over radii squared of the ellipsoid.
     * @param {Number} centerToleranceSquared Tolerance for closeness to the center.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter, a new Cartesian3 instance if none was provided, or undefined if the position is at the center.
     *
     * @exports scaleToGeodeticSurface
     *
     * @private
     */
    function scaleToGeodeticSurface(cartesian, oneOverRadii, oneOverRadiiSquared, centerToleranceSquared, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        if (!defined(oneOverRadii)) {
            throw new DeveloperError('oneOverRadii is required.');
        }
        if (!defined(oneOverRadiiSquared)) {
            throw new DeveloperError('oneOverRadiiSquared is required.');
        }
        if (!defined(centerToleranceSquared)) {
            throw new DeveloperError('centerToleranceSquared is required.');
        }
        
        var positionX = cartesian.x;
        var positionY = cartesian.y;
        var positionZ = cartesian.z;

        var oneOverRadiiX = oneOverRadii.x;
        var oneOverRadiiY = oneOverRadii.y;
        var oneOverRadiiZ = oneOverRadii.z;

        var x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX;
        var y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY;
        var z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ;

        // Compute the squared ellipsoid norm.
        var squaredNorm = x2 + y2 + z2;
        var ratio = Math.sqrt(1.0 / squaredNorm);

        // As an initial approximation, assume that the radial intersection is the projection point.
        var intersection = Cartesian3.multiplyByScalar(cartesian, ratio, scaleToGeodeticSurfaceIntersection);

        // If the position is near the center, the iteration will not converge.
        if (squaredNorm < centerToleranceSquared) {
            return !isFinite(ratio) ? undefined : Cartesian3.clone(intersection, result);
        }

        var oneOverRadiiSquaredX = oneOverRadiiSquared.x;
        var oneOverRadiiSquaredY = oneOverRadiiSquared.y;
        var oneOverRadiiSquaredZ = oneOverRadiiSquared.z;

        // Use the gradient at the intersection point in place of the true unit normal.
        // The difference in magnitude will be absorbed in the multiplier.
        var gradient = scaleToGeodeticSurfaceGradient;
        gradient.x = intersection.x * oneOverRadiiSquaredX * 2.0;
        gradient.y = intersection.y * oneOverRadiiSquaredY * 2.0;
        gradient.z = intersection.z * oneOverRadiiSquaredZ * 2.0;

        // Compute the initial guess at the normal vector multiplier, lambda.
        var lambda = (1.0 - ratio) * Cartesian3.magnitude(cartesian) / (0.5 * Cartesian3.magnitude(gradient));
        var correction = 0.0;

        var func;
        var denominator;
        var xMultiplier;
        var yMultiplier;
        var zMultiplier;
        var xMultiplier2;
        var yMultiplier2;
        var zMultiplier2;
        var xMultiplier3;
        var yMultiplier3;
        var zMultiplier3;

        do {
            lambda -= correction;

            xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX);
            yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY);
            zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ);

            xMultiplier2 = xMultiplier * xMultiplier;
            yMultiplier2 = yMultiplier * yMultiplier;
            zMultiplier2 = zMultiplier * zMultiplier;

            xMultiplier3 = xMultiplier2 * xMultiplier;
            yMultiplier3 = yMultiplier2 * yMultiplier;
            zMultiplier3 = zMultiplier2 * zMultiplier;

            func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0;

            // "denominator" here refers to the use of this expression in the velocity and acceleration
            // computations in the sections to follow.
            denominator = x2 * xMultiplier3 * oneOverRadiiSquaredX + y2 * yMultiplier3 * oneOverRadiiSquaredY + z2 * zMultiplier3 * oneOverRadiiSquaredZ;

            var derivative = -2.0 * denominator;

            correction = func / derivative;
        } while (Math.abs(func) > CesiumMath.EPSILON12);

        if (!defined(result)) {
            return new Cartesian3(positionX * xMultiplier, positionY * yMultiplier, positionZ * zMultiplier);
        }
        result.x = positionX * xMultiplier;
        result.y = positionY * yMultiplier;
        result.z = positionZ * zMultiplier;
        return result;
    }

    return scaleToGeodeticSurface;
});

/*global define*/
define('Core/Cartographic',[
        './Cartesian3',
        './defaultValue',
        './defined',
        './DeveloperError',
        './freezeObject',
        './Math',
        './scaleToGeodeticSurface'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        DeveloperError,
        freezeObject,
        CesiumMath,
        scaleToGeodeticSurface) {
    'use strict';

    /**
     * A position defined by longitude, latitude, and height.
     * @alias Cartographic
     * @constructor
     *
     * @param {Number} [longitude=0.0] The longitude, in radians.
     * @param {Number} [latitude=0.0] The latitude, in radians.
     * @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
     *
     * @see Ellipsoid
     */
    function Cartographic(longitude, latitude, height) {
        /**
         * The longitude, in radians.
         * @type {Number}
         * @default 0.0
         */
        this.longitude = defaultValue(longitude, 0.0);

        /**
         * The latitude, in radians.
         * @type {Number}
         * @default 0.0
         */
        this.latitude = defaultValue(latitude, 0.0);

        /**
         * The height, in meters, above the ellipsoid.
         * @type {Number}
         * @default 0.0
         */
        this.height = defaultValue(height, 0.0);
    }

    /**
     * Creates a new Cartographic instance from longitude and latitude
     * specified in radians.
     *
     * @param {Number} longitude The longitude, in radians.
     * @param {Number} latitude The latitude, in radians.
     * @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
     */
    Cartographic.fromRadians = function(longitude, latitude, height, result) {
                if (!defined(longitude)) {
            throw new DeveloperError('longitude is required.');
        }
        if (!defined(latitude)) {
            throw new DeveloperError('latitude is required.');
        }
        
        height = defaultValue(height, 0.0);

        if (!defined(result)) {
            return new Cartographic(longitude, latitude, height);
        }

        result.longitude = longitude;
        result.latitude = latitude;
        result.height = height;
        return result;
    };

    /**
     * Creates a new Cartographic instance from longitude and latitude
     * specified in degrees.  The values in the resulting object will
     * be in radians.
     *
     * @param {Number} longitude The longitude, in degrees.
     * @param {Number} latitude The latitude, in degrees.
     * @param {Number} [height=0.0] The height, in meters, above the ellipsoid.
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
     */
    Cartographic.fromDegrees = function(longitude, latitude, height, result) {
                if (!defined(longitude)) {
            throw new DeveloperError('longitude is required.');
        }
        if (!defined(latitude)) {
            throw new DeveloperError('latitude is required.');
        }
                longitude = CesiumMath.toRadians(longitude);
        latitude = CesiumMath.toRadians(latitude);

        return Cartographic.fromRadians(longitude, latitude, height, result);
    };

    var cartesianToCartographicN = new Cartesian3();
    var cartesianToCartographicP = new Cartesian3();
    var cartesianToCartographicH = new Cartesian3();
    var wgs84OneOverRadii = new Cartesian3(1.0 / 6378137.0, 1.0 / 6378137.0, 1.0 / 6356752.3142451793);
    var wgs84OneOverRadiiSquared = new Cartesian3(1.0 / (6378137.0 * 6378137.0), 1.0 / (6378137.0 * 6378137.0), 1.0 / (6356752.3142451793 * 6356752.3142451793));
    var wgs84CenterToleranceSquared = CesiumMath.EPSILON1;

    /**
     * Creates a new Cartographic instance from a Cartesian position. The values in the
     * resulting object will be in radians.
     *
     * @param {Cartesian3} cartesian The Cartesian position to convert to cartographic representation.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the position lies.
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter, new Cartographic instance if none was provided, or undefined if the cartesian is at the center of the ellipsoid.
     */
    Cartographic.fromCartesian = function(cartesian, ellipsoid, result) {
        var oneOverRadii = defined(ellipsoid) ? ellipsoid.oneOverRadii : wgs84OneOverRadii;
        var oneOverRadiiSquared = defined(ellipsoid) ? ellipsoid.oneOverRadiiSquared : wgs84OneOverRadiiSquared;
        var centerToleranceSquared = defined(ellipsoid) ? ellipsoid._centerToleranceSquared : wgs84CenterToleranceSquared;

        //`cartesian is required.` is thrown from scaleToGeodeticSurface
        var p = scaleToGeodeticSurface(cartesian, oneOverRadii, oneOverRadiiSquared, centerToleranceSquared, cartesianToCartographicP);

        if (!defined(p)) {
            return undefined;
        }

        var n = Cartesian3.multiplyComponents(p, oneOverRadiiSquared, cartesianToCartographicN);
        n = Cartesian3.normalize(n, n);

        var h = Cartesian3.subtract(cartesian, p, cartesianToCartographicH);

        var longitude = Math.atan2(n.y, n.x);
        var latitude = Math.asin(n.z);
        var height = CesiumMath.sign(Cartesian3.dot(h, cartesian)) * Cartesian3.magnitude(h);

        if (!defined(result)) {
            return new Cartographic(longitude, latitude, height);
        }
        result.longitude = longitude;
        result.latitude = latitude;
        result.height = height;
        return result;
    };

    /**
     * Duplicates a Cartographic instance.
     *
     * @param {Cartographic} cartographic The cartographic to duplicate.
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided. (Returns undefined if cartographic is undefined)
     */
    Cartographic.clone = function(cartographic, result) {
        if (!defined(cartographic)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Cartographic(cartographic.longitude, cartographic.latitude, cartographic.height);
        }
        result.longitude = cartographic.longitude;
        result.latitude = cartographic.latitude;
        result.height = cartographic.height;
        return result;
    };

    /**
     * Compares the provided cartographics componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartographic} [left] The first cartographic.
     * @param {Cartographic} [right] The second cartographic.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Cartographic.equals = function(left, right) {
        return (left === right) ||
                ((defined(left)) &&
                 (defined(right)) &&
                 (left.longitude === right.longitude) &&
                 (left.latitude === right.latitude) &&
                 (left.height === right.height));
    };

    /**
     * Compares the provided cartographics componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Cartographic} [left] The first cartographic.
     * @param {Cartographic} [right] The second cartographic.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartographic.equalsEpsilon = function(left, right, epsilon) {
                if (typeof epsilon !== 'number') {
            throw new DeveloperError('epsilon is required and must be a number.');
        }
        
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (Math.abs(left.longitude - right.longitude) <= epsilon) &&
                (Math.abs(left.latitude - right.latitude) <= epsilon) &&
                (Math.abs(left.height - right.height) <= epsilon));
    };

    /**
     * An immutable Cartographic instance initialized to (0.0, 0.0, 0.0).
     *
     * @type {Cartographic}
     * @constant
     */
    Cartographic.ZERO = freezeObject(new Cartographic(0.0, 0.0, 0.0));

    /**
     * Duplicates this instance.
     *
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if one was not provided.
     */
    Cartographic.prototype.clone = function(result) {
        return Cartographic.clone(this, result);
    };

    /**
     * Compares the provided against this cartographic componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartographic} [right] The second cartographic.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Cartographic.prototype.equals = function(right) {
        return Cartographic.equals(this, right);
    };

    /**
     * Compares the provided against this cartographic componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Cartographic} [right] The second cartographic.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartographic.prototype.equalsEpsilon = function(right, epsilon) {
        return Cartographic.equalsEpsilon(this, right, epsilon);
    };

    /**
     * Creates a string representing this cartographic in the format '(longitude, latitude, height)'.
     *
     * @returns {String} A string representing the provided cartographic in the format '(longitude, latitude, height)'.
     */
    Cartographic.prototype.toString = function() {
        return '(' + this.longitude + ', ' + this.latitude + ', ' + this.height + ')';
    };

    return Cartographic;
});

/*global define*/
define('Core/Ellipsoid',[
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './freezeObject',
        './Math',
        './scaleToGeodeticSurface'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        freezeObject,
        CesiumMath,
        scaleToGeodeticSurface) {
    'use strict';

    function initialize(ellipsoid, x, y, z) {
        x = defaultValue(x, 0.0);
        y = defaultValue(y, 0.0);
        z = defaultValue(z, 0.0);

                if (x < 0.0 || y < 0.0 || z < 0.0) {
            throw new DeveloperError('All radii components must be greater than or equal to zero.');
        }
        
        ellipsoid._radii = new Cartesian3(x, y, z);

        ellipsoid._radiiSquared = new Cartesian3(x * x,
                                            y * y,
                                            z * z);

        ellipsoid._radiiToTheFourth = new Cartesian3(x * x * x * x,
                                                y * y * y * y,
                                                z * z * z * z);

        ellipsoid._oneOverRadii = new Cartesian3(x === 0.0 ? 0.0 : 1.0 / x,
                                            y === 0.0 ? 0.0 : 1.0 / y,
                                            z === 0.0 ? 0.0 : 1.0 / z);

        ellipsoid._oneOverRadiiSquared = new Cartesian3(x === 0.0 ? 0.0 : 1.0 / (x * x),
                                                   y === 0.0 ? 0.0 : 1.0 / (y * y),
                                                   z === 0.0 ? 0.0 : 1.0 / (z * z));

        ellipsoid._minimumRadius = Math.min(x, y, z);

        ellipsoid._maximumRadius = Math.max(x, y, z);

        ellipsoid._centerToleranceSquared = CesiumMath.EPSILON1;

        if (ellipsoid._radiiSquared.z !== 0) {
            ellipsoid._sqauredXOverSquaredZ = ellipsoid._radiiSquared.x / ellipsoid._radiiSquared.z;
        }
    }

    /**
     * A quadratic surface defined in Cartesian coordinates by the equation
     * <code>(x / a)^2 + (y / b)^2 + (z / c)^2 = 1</code>.  Primarily used
     * by Cesium to represent the shape of planetary bodies.
     *
     * Rather than constructing this object directly, one of the provided
     * constants is normally used.
     * @alias Ellipsoid
     * @constructor
     *
     * @param {Number} [x=0] The radius in the x direction.
     * @param {Number} [y=0] The radius in the y direction.
     * @param {Number} [z=0] The radius in the z direction.
     *
     * @exception {DeveloperError} All radii components must be greater than or equal to zero.
     *
     * @see Ellipsoid.fromCartesian3
     * @see Ellipsoid.WGS84
     * @see Ellipsoid.UNIT_SPHERE
     */
    function Ellipsoid(x, y, z) {
        this._radii = undefined;
        this._radiiSquared = undefined;
        this._radiiToTheFourth = undefined;
        this._oneOverRadii = undefined;
        this._oneOverRadiiSquared = undefined;
        this._minimumRadius = undefined;
        this._maximumRadius = undefined;
        this._centerToleranceSquared = undefined;
        this._sqauredXOverSquaredZ = undefined;

        initialize(this, x, y, z);
    }

    defineProperties(Ellipsoid.prototype, {
        /**
         * Gets the radii of the ellipsoid.
         * @memberof Ellipsoid.prototype
         * @type {Cartesian3}
         * @readonly
         */
        radii : {
            get: function() {
                return this._radii;
            }
        },
        /**
         * Gets the squared radii of the ellipsoid.
         * @memberof Ellipsoid.prototype
         * @type {Cartesian3}
         * @readonly
         */
        radiiSquared : {
            get : function() {
                return this._radiiSquared;
            }
        },
        /**
         * Gets the radii of the ellipsoid raise to the fourth power.
         * @memberof Ellipsoid.prototype
         * @type {Cartesian3}
         * @readonly
         */
        radiiToTheFourth : {
            get : function() {
                return this._radiiToTheFourth;
            }
        },
        /**
         * Gets one over the radii of the ellipsoid.
         * @memberof Ellipsoid.prototype
         * @type {Cartesian3}
         * @readonly
         */
        oneOverRadii : {
            get : function() {
                return this._oneOverRadii;
            }
        },
        /**
         * Gets one over the squared radii of the ellipsoid.
         * @memberof Ellipsoid.prototype
         * @type {Cartesian3}
         * @readonly
         */
        oneOverRadiiSquared : {
            get : function() {
                return this._oneOverRadiiSquared;
            }
        },
        /**
         * Gets the minimum radius of the ellipsoid.
         * @memberof Ellipsoid.prototype
         * @type {Number}
         * @readonly
         */
        minimumRadius : {
            get : function() {
                return this._minimumRadius;
            }
        },
        /**
         * Gets the maximum radius of the ellipsoid.
         * @memberof Ellipsoid.prototype
         * @type {Number}
         * @readonly
         */
        maximumRadius : {
            get : function() {
                return this._maximumRadius;
            }
        }
    });

    /**
     * Duplicates an Ellipsoid instance.
     *
     * @param {Ellipsoid} ellipsoid The ellipsoid to duplicate.
     * @param {Ellipsoid} [result] The object onto which to store the result, or undefined if a new
     *                    instance should be created.
     * @returns {Ellipsoid} The cloned Ellipsoid. (Returns undefined if ellipsoid is undefined)
     */
    Ellipsoid.clone = function(ellipsoid, result) {
        if (!defined(ellipsoid)) {
            return undefined;
        }
        var radii = ellipsoid._radii;

        if (!defined(result)) {
            return new Ellipsoid(radii.x, radii.y, radii.z);
        }

        Cartesian3.clone(radii, result._radii);
        Cartesian3.clone(ellipsoid._radiiSquared, result._radiiSquared);
        Cartesian3.clone(ellipsoid._radiiToTheFourth, result._radiiToTheFourth);
        Cartesian3.clone(ellipsoid._oneOverRadii, result._oneOverRadii);
        Cartesian3.clone(ellipsoid._oneOverRadiiSquared, result._oneOverRadiiSquared);
        result._minimumRadius = ellipsoid._minimumRadius;
        result._maximumRadius = ellipsoid._maximumRadius;
        result._centerToleranceSquared = ellipsoid._centerToleranceSquared;

        return result;
    };

    /**
     * Computes an Ellipsoid from a Cartesian specifying the radii in x, y, and z directions.
     *
     * @param {Cartesian3} [cartesian=Cartesian3.ZERO] The ellipsoid's radius in the x, y, and z directions.
     * @param {Ellipsoid} [result] The object onto which to store the result, or undefined if a new
     *                    instance should be created.
     * @returns {Ellipsoid} A new Ellipsoid instance.
     *
     * @exception {DeveloperError} All radii components must be greater than or equal to zero.
     *
     * @see Ellipsoid.WGS84
     * @see Ellipsoid.UNIT_SPHERE
     */
    Ellipsoid.fromCartesian3 = function(cartesian, result) {
        if (!defined(result)) {
            result = new Ellipsoid();
        }

        if (!defined(cartesian)) {
            return result;
        }

        initialize(result, cartesian.x, cartesian.y, cartesian.z);
        return result;
    };

    /**
     * An Ellipsoid instance initialized to the WGS84 standard.
     *
     * @type {Ellipsoid}
     * @constant
     */
    Ellipsoid.WGS84 = freezeObject(new Ellipsoid(6378137.0, 6378137.0, 6356752.3142451793));

    /**
     * An Ellipsoid instance initialized to radii of (1.0, 1.0, 1.0).
     *
     * @type {Ellipsoid}
     * @constant
     */
    Ellipsoid.UNIT_SPHERE = freezeObject(new Ellipsoid(1.0, 1.0, 1.0));

    /**
     * An Ellipsoid instance initialized to a sphere with the lunar radius.
     *
     * @type {Ellipsoid}
     * @constant
     */
    Ellipsoid.MOON = freezeObject(new Ellipsoid(CesiumMath.LUNAR_RADIUS, CesiumMath.LUNAR_RADIUS, CesiumMath.LUNAR_RADIUS));

    /**
     * Duplicates an Ellipsoid instance.
     *
     * @param {Ellipsoid} [result] The object onto which to store the result, or undefined if a new
     *                    instance should be created.
     * @returns {Ellipsoid} The cloned Ellipsoid.
     */
    Ellipsoid.prototype.clone = function(result) {
        return Ellipsoid.clone(this, result);
    };

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Ellipsoid.packedLength = Cartesian3.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Ellipsoid} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Ellipsoid.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._radii, array, startingIndex);

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Ellipsoid} [result] The object into which to store the result.
     * @returns {Ellipsoid} The modified result parameter or a new Ellipsoid instance if one was not provided.
     */
    Ellipsoid.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var radii = Cartesian3.unpack(array, startingIndex);
        return Ellipsoid.fromCartesian3(radii, result);
    };

    /**
     * Computes the unit vector directed from the center of this ellipsoid toward the provided Cartesian position.
     * @function
     *
     * @param {Cartesian3} cartesian The Cartesian for which to to determine the geocentric normal.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
     */
    Ellipsoid.prototype.geocentricSurfaceNormal = Cartesian3.normalize;

    /**
     * Computes the normal of the plane tangent to the surface of the ellipsoid at the provided position.
     *
     * @param {Cartographic} cartographic The cartographic position for which to to determine the geodetic normal.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
     */
    Ellipsoid.prototype.geodeticSurfaceNormalCartographic = function(cartographic, result) {
                if (!defined(cartographic)) {
            throw new DeveloperError('cartographic is required.');
        }
        
        var longitude = cartographic.longitude;
        var latitude = cartographic.latitude;
        var cosLatitude = Math.cos(latitude);

        var x = cosLatitude * Math.cos(longitude);
        var y = cosLatitude * Math.sin(longitude);
        var z = Math.sin(latitude);

        if (!defined(result)) {
            result = new Cartesian3();
        }
        result.x = x;
        result.y = y;
        result.z = z;
        return Cartesian3.normalize(result, result);
    };

    /**
     * Computes the normal of the plane tangent to the surface of the ellipsoid at the provided position.
     *
     * @param {Cartesian3} cartesian The Cartesian position for which to to determine the surface normal.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
     */
    Ellipsoid.prototype.geodeticSurfaceNormal = function(cartesian, result) {
        if (!defined(result)) {
            result = new Cartesian3();
        }
        result = Cartesian3.multiplyComponents(cartesian, this._oneOverRadiiSquared, result);
        return Cartesian3.normalize(result, result);
    };

    var cartographicToCartesianNormal = new Cartesian3();
    var cartographicToCartesianK = new Cartesian3();

    /**
     * Converts the provided cartographic to Cartesian representation.
     *
     * @param {Cartographic} cartographic The cartographic position.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
     *
     * @example
     * //Create a Cartographic and determine it's Cartesian representation on a WGS84 ellipsoid.
     * var position = new Cesium.Cartographic(Cesium.Math.toRadians(21), Cesium.Math.toRadians(78), 5000);
     * var cartesianPosition = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
     */
    Ellipsoid.prototype.cartographicToCartesian = function(cartographic, result) {
        //`cartographic is required` is thrown from geodeticSurfaceNormalCartographic.
        var n = cartographicToCartesianNormal;
        var k = cartographicToCartesianK;
        this.geodeticSurfaceNormalCartographic(cartographic, n);
        Cartesian3.multiplyComponents(this._radiiSquared, n, k);
        var gamma = Math.sqrt(Cartesian3.dot(n, k));
        Cartesian3.divideByScalar(k, gamma, k);
        Cartesian3.multiplyByScalar(n, cartographic.height, n);

        if (!defined(result)) {
            result = new Cartesian3();
        }
        return Cartesian3.add(k, n, result);
    };

    /**
     * Converts the provided array of cartographics to an array of Cartesians.
     *
     * @param {Cartographic[]} cartographics An array of cartographic positions.
     * @param {Cartesian3[]} [result] The object onto which to store the result.
     * @returns {Cartesian3[]} The modified result parameter or a new Array instance if none was provided.
     *
     * @example
     * //Convert an array of Cartographics and determine their Cartesian representation on a WGS84 ellipsoid.
     * var positions = [new Cesium.Cartographic(Cesium.Math.toRadians(21), Cesium.Math.toRadians(78), 0),
     *                  new Cesium.Cartographic(Cesium.Math.toRadians(21.321), Cesium.Math.toRadians(78.123), 100),
     *                  new Cesium.Cartographic(Cesium.Math.toRadians(21.645), Cesium.Math.toRadians(78.456), 250)];
     * var cartesianPositions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray(positions);
     */
    Ellipsoid.prototype.cartographicArrayToCartesianArray = function(cartographics, result) {
                if (!defined(cartographics)) {
            throw new DeveloperError('cartographics is required.');
        }
        
        var length = cartographics.length;
        if (!defined(result)) {
            result = new Array(length);
        } else {
            result.length = length;
        }
        for ( var i = 0; i < length; i++) {
            result[i] = this.cartographicToCartesian(cartographics[i], result[i]);
        }
        return result;
    };

    var cartesianToCartographicN = new Cartesian3();
    var cartesianToCartographicP = new Cartesian3();
    var cartesianToCartographicH = new Cartesian3();

    /**
     * Converts the provided cartesian to cartographic representation.
     * The cartesian is undefined at the center of the ellipsoid.
     *
     * @param {Cartesian3} cartesian The Cartesian position to convert to cartographic representation.
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter, new Cartographic instance if none was provided, or undefined if the cartesian is at the center of the ellipsoid.
     *
     * @example
     * //Create a Cartesian and determine it's Cartographic representation on a WGS84 ellipsoid.
     * var position = new Cesium.Cartesian3(17832.12, 83234.52, 952313.73);
     * var cartographicPosition = Cesium.Ellipsoid.WGS84.cartesianToCartographic(position);
     */
    Ellipsoid.prototype.cartesianToCartographic = function(cartesian, result) {
        //`cartesian is required.` is thrown from scaleToGeodeticSurface
        var p = this.scaleToGeodeticSurface(cartesian, cartesianToCartographicP);

        if (!defined(p)) {
            return undefined;
        }

        var n = this.geodeticSurfaceNormal(p, cartesianToCartographicN);
        var h = Cartesian3.subtract(cartesian, p, cartesianToCartographicH);

        var longitude = Math.atan2(n.y, n.x);
        var latitude = Math.asin(n.z);
        var height = CesiumMath.sign(Cartesian3.dot(h, cartesian)) * Cartesian3.magnitude(h);

        if (!defined(result)) {
            return new Cartographic(longitude, latitude, height);
        }
        result.longitude = longitude;
        result.latitude = latitude;
        result.height = height;
        return result;
    };

    /**
     * Converts the provided array of cartesians to an array of cartographics.
     *
     * @param {Cartesian3[]} cartesians An array of Cartesian positions.
     * @param {Cartographic[]} [result] The object onto which to store the result.
     * @returns {Cartographic[]} The modified result parameter or a new Array instance if none was provided.
     *
     * @example
     * //Create an array of Cartesians and determine their Cartographic representation on a WGS84 ellipsoid.
     * var positions = [new Cesium.Cartesian3(17832.12, 83234.52, 952313.73),
     *                  new Cesium.Cartesian3(17832.13, 83234.53, 952313.73),
     *                  new Cesium.Cartesian3(17832.14, 83234.54, 952313.73)]
     * var cartographicPositions = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray(positions);
     */
    Ellipsoid.prototype.cartesianArrayToCartographicArray = function(cartesians, result) {
                if (!defined(cartesians)) {
            throw new DeveloperError('cartesians is required.');
        }
        
        var length = cartesians.length;
        if (!defined(result)) {
            result = new Array(length);
        } else {
            result.length = length;
        }
        for ( var i = 0; i < length; ++i) {
            result[i] = this.cartesianToCartographic(cartesians[i], result[i]);
        }
        return result;
    };

    /**
     * Scales the provided Cartesian position along the geodetic surface normal
     * so that it is on the surface of this ellipsoid.  If the position is
     * at the center of the ellipsoid, this function returns undefined.
     *
     * @param {Cartesian3} cartesian The Cartesian position to scale.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter, a new Cartesian3 instance if none was provided, or undefined if the position is at the center.
     */
    Ellipsoid.prototype.scaleToGeodeticSurface = function(cartesian, result) {
        return scaleToGeodeticSurface(cartesian, this._oneOverRadii, this._oneOverRadiiSquared, this._centerToleranceSquared, result);
    };

    /**
     * Scales the provided Cartesian position along the geocentric surface normal
     * so that it is on the surface of this ellipsoid.
     *
     * @param {Cartesian3} cartesian The Cartesian position to scale.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if none was provided.
     */
    Ellipsoid.prototype.scaleToGeocentricSurface = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        var positionX = cartesian.x;
        var positionY = cartesian.y;
        var positionZ = cartesian.z;
        var oneOverRadiiSquared = this._oneOverRadiiSquared;

        var beta = 1.0 / Math.sqrt((positionX * positionX) * oneOverRadiiSquared.x +
                                   (positionY * positionY) * oneOverRadiiSquared.y +
                                   (positionZ * positionZ) * oneOverRadiiSquared.z);

        return Cartesian3.multiplyByScalar(cartesian, beta, result);
    };

    /**
     * Transforms a Cartesian X, Y, Z position to the ellipsoid-scaled space by multiplying
     * its components by the result of {@link Ellipsoid#oneOverRadii}.
     *
     * @param {Cartesian3} position The position to transform.
     * @param {Cartesian3} [result] The position to which to copy the result, or undefined to create and
     *        return a new instance.
     * @returns {Cartesian3} The position expressed in the scaled space.  The returned instance is the
     *          one passed as the result parameter if it is not undefined, or a new instance of it is.
     */
    Ellipsoid.prototype.transformPositionToScaledSpace = function(position, result) {
        if (!defined(result)) {
            result = new Cartesian3();
        }

        return Cartesian3.multiplyComponents(position, this._oneOverRadii, result);
    };

    /**
     * Transforms a Cartesian X, Y, Z position from the ellipsoid-scaled space by multiplying
     * its components by the result of {@link Ellipsoid#radii}.
     *
     * @param {Cartesian3} position The position to transform.
     * @param {Cartesian3} [result] The position to which to copy the result, or undefined to create and
     *        return a new instance.
     * @returns {Cartesian3} The position expressed in the unscaled space.  The returned instance is the
     *          one passed as the result parameter if it is not undefined, or a new instance of it is.
     */
    Ellipsoid.prototype.transformPositionFromScaledSpace = function(position, result) {
        if (!defined(result)) {
            result = new Cartesian3();
        }

        return Cartesian3.multiplyComponents(position, this._radii, result);
    };

    /**
     * Compares this Ellipsoid against the provided Ellipsoid componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Ellipsoid} [right] The other Ellipsoid.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Ellipsoid.prototype.equals = function(right) {
        return (this === right) ||
               (defined(right) &&
                Cartesian3.equals(this._radii, right._radii));
    };

    /**
     * Creates a string representing this Ellipsoid in the format '(radii.x, radii.y, radii.z)'.
     *
     * @returns {String} A string representing this ellipsoid in the format '(radii.x, radii.y, radii.z)'.
     */
    Ellipsoid.prototype.toString = function() {
        return this._radii.toString();
    };

    /**
     * Computes a point which is the intersection of the surface normal with the z-axis.
     *
     * @param {Cartesian3} position the position. must be on the surface of the ellipsoid.
     * @param {Number} [buffer = 0.0] A buffer to subtract from the ellipsoid size when checking if the point is inside the ellipsoid.
     *                                In earth case, with common earth datums, there is no need for this buffer since the intersection point is always (relatively) very close to the center.
     *                                In WGS84 datum, intersection point is at max z = +-42841.31151331382 (0.673% of z-axis).
     *                                Intersection point could be outside the ellipsoid if the ratio of MajorAxis / AxisOfRotation is bigger than the square root of 2
     * @param {Cartesian} [result] The cartesian to which to copy the result, or undefined to create and
     *        return a new instance.
     * @returns {Cartesian | undefined} the intersection point if it's inside the ellipsoid, undefined otherwise
     *
     * @exception {DeveloperError} position is required.
     * @exception {DeveloperError} Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y).
     * @exception {DeveloperError} Ellipsoid.radii.z must be greater than 0.
     */
    Ellipsoid.prototype.getSurfaceNormalIntersectionWithZAxis = function(position, buffer, result) {
                if (!defined(position)) {
            throw new DeveloperError('position is required.');
        }
        if (!CesiumMath.equalsEpsilon(this._radii.x, this._radii.y, CesiumMath.EPSILON15)) {
            throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)');
        }
        if (this._radii.z === 0) {
            throw new DeveloperError('Ellipsoid.radii.z must be greater than 0');
        }
        
        buffer = defaultValue(buffer, 0.0);

        var sqauredXOverSquaredZ = this._sqauredXOverSquaredZ;

        if (!defined(result)) {
            result = new Cartesian3();
        }

        result.x = 0.0;
        result.y = 0.0;
        result.z = position.z * (1 - sqauredXOverSquaredZ);

        if (Math.abs(result.z) >= this._radii.z - buffer) {
            return undefined;
        }

        return result;
    };

    return Ellipsoid;
});

/*global define*/
define('Core/Event',[
        './defined',
        './defineProperties',
        './DeveloperError'
    ], function(
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * A generic utility class for managing subscribers for a particular event.
     * This class is usually instantiated inside of a container class and
     * exposed as a property for others to subscribe to.
     *
     * @alias Event
     * @constructor
     *
     * @example
     * MyObject.prototype.myListener = function(arg1, arg2) {
     *     this.myArg1Copy = arg1;
     *     this.myArg2Copy = arg2;
     * }
     *
     * var myObjectInstance = new MyObject();
     * var evt = new Cesium.Event();
     * evt.addEventListener(MyObject.prototype.myListener, myObjectInstance);
     * evt.raiseEvent('1', '2');
     * evt.removeEventListener(MyObject.prototype.myListener);
     */
    function Event() {
        this._listeners = [];
        this._scopes = [];
        this._toRemove = [];
        this._insideRaiseEvent = false;
    }

    defineProperties(Event.prototype, {
        /**
         * The number of listeners currently subscribed to the event.
         * @memberof Event.prototype
         * @type {Number}
         * @readonly
         */
        numberOfListeners : {
            get : function() {
                return this._listeners.length - this._toRemove.length;
            }
        }
    });

    /**
     * Registers a callback function to be executed whenever the event is raised.
     * An optional scope can be provided to serve as the <code>this</code> pointer
     * in which the function will execute.
     *
     * @param {Function} listener The function to be executed when the event is raised.
     * @param {Object} [scope] An optional object scope to serve as the <code>this</code>
     *        pointer in which the listener function will execute.
     * @returns {Event~RemoveCallback} A function that will remove this event listener when invoked.
     *
     * @see Event#raiseEvent
     * @see Event#removeEventListener
     */
    Event.prototype.addEventListener = function(listener, scope) {
                if (typeof listener !== 'function') {
            throw new DeveloperError('listener is required and must be a function.');
        }
        
        this._listeners.push(listener);
        this._scopes.push(scope);

        var event = this;
        return function() {
            event.removeEventListener(listener, scope);
        };
    };

    /**
     * Unregisters a previously registered callback.
     *
     * @param {Function} listener The function to be unregistered.
     * @param {Object} [scope] The scope that was originally passed to addEventListener.
     * @returns {Boolean} <code>true</code> if the listener was removed; <code>false</code> if the listener and scope are not registered with the event.
     *
     * @see Event#addEventListener
     * @see Event#raiseEvent
     */
    Event.prototype.removeEventListener = function(listener, scope) {
                if (typeof listener !== 'function') {
            throw new DeveloperError('listener is required and must be a function.');
        }
        
        var listeners = this._listeners;
        var scopes = this._scopes;

        var index = -1;
        for (var i = 0; i < listeners.length; i++) {
            if (listeners[i] === listener && scopes[i] === scope) {
                index = i;
                break;
            }
        }

        if (index !== -1) {
            if (this._insideRaiseEvent) {
                //In order to allow removing an event subscription from within
                //a callback, we don't actually remove the items here.  Instead
                //remember the index they are at and undefined their value.
                this._toRemove.push(index);
                listeners[index] = undefined;
                scopes[index] = undefined;
            } else {
                listeners.splice(index, 1);
                scopes.splice(index, 1);
            }
            return true;
        }

        return false;
    };

    /**
     * Raises the event by calling each registered listener with all supplied arguments.
     *
     * @param {*} arguments This method takes any number of parameters and passes them through to the listener functions.
     *
     * @see Event#addEventListener
     * @see Event#removeEventListener
     */
    Event.prototype.raiseEvent = function() {
        this._insideRaiseEvent = true;

        var i;
        var listeners = this._listeners;
        var scopes = this._scopes;
        var length = listeners.length;

        for (i = 0; i < length; i++) {
            var listener = listeners[i];
            if (defined(listener)) {
                listeners[i].apply(scopes[i], arguments);
            }
        }

        //Actually remove items removed in removeEventListener.
        var toRemove = this._toRemove;
        length = toRemove.length;
        for (i = 0; i < length; i++) {
            var index = toRemove[i];
            listeners.splice(index, 1);
            scopes.splice(index, 1);
        }
        toRemove.length = 0;

        this._insideRaiseEvent = false;
    };

    /**
     * A function that removes a listener.
     * @callback Event~RemoveCallback
     */

    return Event;
});

/*global define*/
define('Core/Cartesian2',[
        './Check',
        './defaultValue',
        './defined',
        './DeveloperError',
        './freezeObject',
        './Math'
    ], function(
        Check,
        defaultValue,
        defined,
        DeveloperError,
        freezeObject,
        CesiumMath) {
    'use strict';

    /**
     * A 2D Cartesian point.
     * @alias Cartesian2
     * @constructor
     *
     * @param {Number} [x=0.0] The X component.
     * @param {Number} [y=0.0] The Y component.
     *
     * @see Cartesian3
     * @see Cartesian4
     * @see Packable
     */
    function Cartesian2(x, y) {
        /**
         * The X component.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The Y component.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);
    }

    /**
     * Creates a Cartesian2 instance from x and y coordinates.
     *
     * @param {Number} x The x coordinate.
     * @param {Number} y The y coordinate.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
     */
    Cartesian2.fromElements = function(x, y, result) {
        if (!defined(result)) {
            return new Cartesian2(x, y);
        }

        result.x = x;
        result.y = y;
        return result;
    };

    /**
     * Duplicates a Cartesian2 instance.
     *
     * @param {Cartesian2} cartesian The Cartesian to duplicate.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided. (Returns undefined if cartesian is undefined)
     */
    Cartesian2.clone = function(cartesian, result) {
        if (!defined(cartesian)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Cartesian2(cartesian.x, cartesian.y);
        }

        result.x = cartesian.x;
        result.y = cartesian.y;
        return result;
    };

    /**
     * Creates a Cartesian2 instance from an existing Cartesian3.  This simply takes the
     * x and y properties of the Cartesian3 and drops z.
     * @function
     *
     * @param {Cartesian3} cartesian The Cartesian3 instance to create a Cartesian2 instance from.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
     */
    Cartesian2.fromCartesian3 = Cartesian2.clone;

    /**
     * Creates a Cartesian2 instance from an existing Cartesian4.  This simply takes the
     * x and y properties of the Cartesian4 and drops z and w.
     * @function
     *
     * @param {Cartesian4} cartesian The Cartesian4 instance to create a Cartesian2 instance from.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
     */
    Cartesian2.fromCartesian4 = Cartesian2.clone;

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Cartesian2.packedLength = 2;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Cartesian2} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Cartesian2.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex] = value.y;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Cartesian2} [result] The object into which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
     */
    Cartesian2.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Cartesian2();
        }
        result.x = array[startingIndex++];
        result.y = array[startingIndex];
        return result;
    };

    /**
     * Flattens an array of Cartesian2s into and array of components.
     *
     * @param {Cartesian2[]} array The array of cartesians to pack.
     * @param {Number[]} result The array onto which to store the result.
     * @returns {Number[]} The packed array.
     */
    Cartesian2.packArray = function(array, result) {
                Check.defined('array', array);
        
        var length = array.length;
        if (!defined(result)) {
            result = new Array(length * 2);
        } else {
            result.length = length * 2;
        }

        for (var i = 0; i < length; ++i) {
            Cartesian2.pack(array[i], result, i * 2);
        }
        return result;
    };

    /**
     * Unpacks an array of cartesian components into and array of Cartesian2s.
     *
     * @param {Number[]} array The array of components to unpack.
     * @param {Cartesian2[]} result The array onto which to store the result.
     * @returns {Cartesian2[]} The unpacked array.
     */
    Cartesian2.unpackArray = function(array, result) {
                Check.defined('array', array);
        
        var length = array.length;
        if (!defined(result)) {
            result = new Array(length / 2);
        } else {
            result.length = length / 2;
        }

        for (var i = 0; i < length; i += 2) {
            var index = i / 2;
            result[index] = Cartesian2.unpack(array, i, result[index]);
        }
        return result;
    };

    /**
     * Creates a Cartesian2 from two consecutive elements in an array.
     * @function
     *
     * @param {Number[]} array The array whose two consecutive elements correspond to the x and y components, respectively.
     * @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to the x component.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
     *
     * @example
     * // Create a Cartesian2 with (1.0, 2.0)
     * var v = [1.0, 2.0];
     * var p = Cesium.Cartesian2.fromArray(v);
     *
     * // Create a Cartesian2 with (1.0, 2.0) using an offset into an array
     * var v2 = [0.0, 0.0, 1.0, 2.0];
     * var p2 = Cesium.Cartesian2.fromArray(v2, 2);
     */
    Cartesian2.fromArray = Cartesian2.unpack;

    /**
     * Computes the value of the maximum component for the supplied Cartesian.
     *
     * @param {Cartesian2} cartesian The cartesian to use.
     * @returns {Number} The value of the maximum component.
     */
    Cartesian2.maximumComponent = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return Math.max(cartesian.x, cartesian.y);
    };

    /**
     * Computes the value of the minimum component for the supplied Cartesian.
     *
     * @param {Cartesian2} cartesian The cartesian to use.
     * @returns {Number} The value of the minimum component.
     */
    Cartesian2.minimumComponent = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return Math.min(cartesian.x, cartesian.y);
    };

    /**
     * Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
     *
     * @param {Cartesian2} first A cartesian to compare.
     * @param {Cartesian2} second A cartesian to compare.
     * @param {Cartesian2} result The object into which to store the result.
     * @returns {Cartesian2} A cartesian with the minimum components.
     */
    Cartesian2.minimumByComponent = function(first, second, result) {
                Check.typeOf.object('first', first);
        Check.typeOf.object('second', second);
        Check.typeOf.object('result', result);
        

        result.x = Math.min(first.x, second.x);
        result.y = Math.min(first.y, second.y);

        return result;
    };

    /**
     * Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
     *
     * @param {Cartesian2} first A cartesian to compare.
     * @param {Cartesian2} second A cartesian to compare.
     * @param {Cartesian2} result The object into which to store the result.
     * @returns {Cartesian2} A cartesian with the maximum components.
     */
    Cartesian2.maximumByComponent = function(first, second, result) {
                Check.typeOf.object('first', first);
        Check.typeOf.object('second', second);
        Check.typeOf.object('result', result);
        
        result.x = Math.max(first.x, second.x);
        result.y = Math.max(first.y, second.y);
        return result;
    };

    /**
     * Computes the provided Cartesian's squared magnitude.
     *
     * @param {Cartesian2} cartesian The Cartesian instance whose squared magnitude is to be computed.
     * @returns {Number} The squared magnitude.
     */
    Cartesian2.magnitudeSquared = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return cartesian.x * cartesian.x + cartesian.y * cartesian.y;
    };

    /**
     * Computes the Cartesian's magnitude (length).
     *
     * @param {Cartesian2} cartesian The Cartesian instance whose magnitude is to be computed.
     * @returns {Number} The magnitude.
     */
    Cartesian2.magnitude = function(cartesian) {
        return Math.sqrt(Cartesian2.magnitudeSquared(cartesian));
    };

    var distanceScratch = new Cartesian2();

    /**
     * Computes the distance between two points.
     *
     * @param {Cartesian2} left The first point to compute the distance from.
     * @param {Cartesian2} right The second point to compute the distance to.
     * @returns {Number} The distance between two points.
     *
     * @example
     * // Returns 1.0
     * var d = Cesium.Cartesian2.distance(new Cesium.Cartesian2(1.0, 0.0), new Cesium.Cartesian2(2.0, 0.0));
     */
    Cartesian2.distance = function(left, right) {
                if (!defined(left) || !defined(right)) {
            throw new DeveloperError('left and right are required.');
        }
        
        Cartesian2.subtract(left, right, distanceScratch);
        return Cartesian2.magnitude(distanceScratch);
    };

    /**
     * Computes the squared distance between two points.  Comparing squared distances
     * using this function is more efficient than comparing distances using {@link Cartesian2#distance}.
     *
     * @param {Cartesian2} left The first point to compute the distance from.
     * @param {Cartesian2} right The second point to compute the distance to.
     * @returns {Number} The distance between two points.
     *
     * @example
     * // Returns 4.0, not 2.0
     * var d = Cesium.Cartesian2.distance(new Cesium.Cartesian2(1.0, 0.0), new Cesium.Cartesian2(3.0, 0.0));
     */
    Cartesian2.distanceSquared = function(left, right) {
                if (!defined(left) || !defined(right)) {
            throw new DeveloperError('left and right are required.');
        }
        
        Cartesian2.subtract(left, right, distanceScratch);
        return Cartesian2.magnitudeSquared(distanceScratch);
    };

    /**
     * Computes the normalized form of the supplied Cartesian.
     *
     * @param {Cartesian2} cartesian The Cartesian to be normalized.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.normalize = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var magnitude = Cartesian2.magnitude(cartesian);

        result.x = cartesian.x / magnitude;
        result.y = cartesian.y / magnitude;

                if (isNaN(result.x) || isNaN(result.y)) {
            throw new DeveloperError('normalized result is not a number');
        }
        
        return result;
    };

    /**
     * Computes the dot (scalar) product of two Cartesians.
     *
     * @param {Cartesian2} left The first Cartesian.
     * @param {Cartesian2} right The second Cartesian.
     * @returns {Number} The dot product.
     */
    Cartesian2.dot = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        return left.x * right.x + left.y * right.y;
    };

    /**
     * Computes the componentwise product of two Cartesians.
     *
     * @param {Cartesian2} left The first Cartesian.
     * @param {Cartesian2} right The second Cartesian.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.multiplyComponents = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x * right.x;
        result.y = left.y * right.y;
        return result;
    };

    /**
     * Computes the componentwise quotient of two Cartesians.
     *
     * @param {Cartesian2} left The first Cartesian.
     * @param {Cartesian2} right The second Cartesian.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.divideComponents = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x / right.x;
        result.y = left.y / right.y;
        return result;
    };

    /**
     * Computes the componentwise sum of two Cartesians.
     *
     * @param {Cartesian2} left The first Cartesian.
     * @param {Cartesian2} right The second Cartesian.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x + right.x;
        result.y = left.y + right.y;
        return result;
    };

    /**
     * Computes the componentwise difference of two Cartesians.
     *
     * @param {Cartesian2} left The first Cartesian.
     * @param {Cartesian2} right The second Cartesian.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x - right.x;
        result.y = left.y - right.y;
        return result;
    };

    /**
     * Multiplies the provided Cartesian componentwise by the provided scalar.
     *
     * @param {Cartesian2} cartesian The Cartesian to be scaled.
     * @param {Number} scalar The scalar to multiply with.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.multiplyByScalar = function(cartesian, scalar, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = cartesian.x * scalar;
        result.y = cartesian.y * scalar;
        return result;
    };

    /**
     * Divides the provided Cartesian componentwise by the provided scalar.
     *
     * @param {Cartesian2} cartesian The Cartesian to be divided.
     * @param {Number} scalar The scalar to divide by.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.divideByScalar = function(cartesian, scalar, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = cartesian.x / scalar;
        result.y = cartesian.y / scalar;
        return result;
    };

    /**
     * Negates the provided Cartesian.
     *
     * @param {Cartesian2} cartesian The Cartesian to be negated.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.negate = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result.x = -cartesian.x;
        result.y = -cartesian.y;
        return result;
    };

    /**
     * Computes the absolute value of the provided Cartesian.
     *
     * @param {Cartesian2} cartesian The Cartesian whose absolute value is to be computed.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.abs = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result.x = Math.abs(cartesian.x);
        result.y = Math.abs(cartesian.y);
        return result;
    };

    var lerpScratch = new Cartesian2();
    /**
     * Computes the linear interpolation or extrapolation at t using the provided cartesians.
     *
     * @param {Cartesian2} start The value corresponding to t at 0.0.
     * @param {Cartesian2} end The value corresponding to t at 1.0.
     * @param {Number} t The point along t at which to interpolate.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Cartesian2.lerp = function(start, end, t, result) {
                Check.typeOf.object('start', start);
        Check.typeOf.object('end', end);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        Cartesian2.multiplyByScalar(end, t, lerpScratch);
        result = Cartesian2.multiplyByScalar(start, 1.0 - t, result);
        return Cartesian2.add(lerpScratch, result, result);
    };

    var angleBetweenScratch = new Cartesian2();
    var angleBetweenScratch2 = new Cartesian2();
    /**
     * Returns the angle, in radians, between the provided Cartesians.
     *
     * @param {Cartesian2} left The first Cartesian.
     * @param {Cartesian2} right The second Cartesian.
     * @returns {Number} The angle between the Cartesians.
     */
    Cartesian2.angleBetween = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        Cartesian2.normalize(left, angleBetweenScratch);
        Cartesian2.normalize(right, angleBetweenScratch2);
        return CesiumMath.acosClamped(Cartesian2.dot(angleBetweenScratch, angleBetweenScratch2));
    };

    var mostOrthogonalAxisScratch = new Cartesian2();
    /**
     * Returns the axis that is most orthogonal to the provided Cartesian.
     *
     * @param {Cartesian2} cartesian The Cartesian on which to find the most orthogonal axis.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The most orthogonal axis.
     */
    Cartesian2.mostOrthogonalAxis = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var f = Cartesian2.normalize(cartesian, mostOrthogonalAxisScratch);
        Cartesian2.abs(f, f);

        if (f.x <= f.y) {
            result = Cartesian2.clone(Cartesian2.UNIT_X, result);
        } else {
            result = Cartesian2.clone(Cartesian2.UNIT_Y, result);
        }

        return result;
    };

    /**
     * Compares the provided Cartesians componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartesian2} [left] The first Cartesian.
     * @param {Cartesian2} [right] The second Cartesian.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Cartesian2.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.x === right.x) &&
                (left.y === right.y));
    };

    /**
     * @private
     */
    Cartesian2.equalsArray = function(cartesian, array, offset) {
        return cartesian.x === array[offset] &&
               cartesian.y === array[offset + 1];
    };

    /**
     * Compares the provided Cartesians componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {Cartesian2} [left] The first Cartesian.
     * @param {Cartesian2} [right] The second Cartesian.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartesian2.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                CesiumMath.equalsEpsilon(left.x, right.x, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.y, right.y, relativeEpsilon, absoluteEpsilon));
    };

    /**
     * An immutable Cartesian2 instance initialized to (0.0, 0.0).
     *
     * @type {Cartesian2}
     * @constant
     */
    Cartesian2.ZERO = freezeObject(new Cartesian2(0.0, 0.0));

    /**
     * An immutable Cartesian2 instance initialized to (1.0, 0.0).
     *
     * @type {Cartesian2}
     * @constant
     */
    Cartesian2.UNIT_X = freezeObject(new Cartesian2(1.0, 0.0));

    /**
     * An immutable Cartesian2 instance initialized to (0.0, 1.0).
     *
     * @type {Cartesian2}
     * @constant
     */
    Cartesian2.UNIT_Y = freezeObject(new Cartesian2(0.0, 1.0));

    /**
     * Duplicates this Cartesian2 instance.
     *
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
     */
    Cartesian2.prototype.clone = function(result) {
        return Cartesian2.clone(this, result);
    };

    /**
     * Compares this Cartesian against the provided Cartesian componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartesian2} [right] The right hand side Cartesian.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Cartesian2.prototype.equals = function(right) {
        return Cartesian2.equals(this, right);
    };

    /**
     * Compares this Cartesian against the provided Cartesian componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {Cartesian2} [right] The right hand side Cartesian.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartesian2.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
        return Cartesian2.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
    };

    /**
     * Creates a string representing this Cartesian in the format '(x, y)'.
     *
     * @returns {String} A string representing the provided Cartesian in the format '(x, y)'.
     */
    Cartesian2.prototype.toString = function() {
        return '(' + this.x + ', ' + this.y + ')';
    };

    return Cartesian2;
});

/*global define*/
define('Core/GeographicProjection',[
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid) {
    'use strict';

    /**
     * A simple map projection where longitude and latitude are linearly mapped to X and Y by multiplying
     * them by the {@link Ellipsoid#maximumRadius}.  This projection
     * is commonly known as geographic, equirectangular, equidistant cylindrical, or plate carrée.  It
     * is also known as EPSG:4326.
     *
     * @alias GeographicProjection
     * @constructor
     *
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid.
     *
     * @see WebMercatorProjection
     */
    function GeographicProjection(ellipsoid) {
        this._ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        this._semimajorAxis = this._ellipsoid.maximumRadius;
        this._oneOverSemimajorAxis = 1.0 / this._semimajorAxis;
    }

    defineProperties(GeographicProjection.prototype, {
        /**
         * Gets the {@link Ellipsoid}.
         *
         * @memberof GeographicProjection.prototype
         *
         * @type {Ellipsoid}
         * @readonly
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        }
    });

    /**
     * Projects a set of {@link Cartographic} coordinates, in radians, to map coordinates, in meters.
     * X and Y are the longitude and latitude, respectively, multiplied by the maximum radius of the
     * ellipsoid.  Z is the unmodified height.
     *
     * @param {Cartographic} cartographic The coordinates to project.
     * @param {Cartesian3} [result] An instance into which to copy the result.  If this parameter is
     *        undefined, a new instance is created and returned.
     * @returns {Cartesian3} The projected coordinates.  If the result parameter is not undefined, the
     *          coordinates are copied there and that instance is returned.  Otherwise, a new instance is
     *          created and returned.
     */
    GeographicProjection.prototype.project = function(cartographic, result) {
        // Actually this is the special case of equidistant cylindrical called the plate carree
        var semimajorAxis = this._semimajorAxis;
        var x = cartographic.longitude * semimajorAxis;
        var y = cartographic.latitude * semimajorAxis;
        var z = cartographic.height;

        if (!defined(result)) {
            return new Cartesian3(x, y, z);
        }

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Unprojects a set of projected {@link Cartesian3} coordinates, in meters, to {@link Cartographic}
     * coordinates, in radians.  Longitude and Latitude are the X and Y coordinates, respectively,
     * divided by the maximum radius of the ellipsoid.  Height is the unmodified Z coordinate.
     *
     * @param {Cartesian3} cartesian The Cartesian position to unproject with height (z) in meters.
     * @param {Cartographic} [result] An instance into which to copy the result.  If this parameter is
     *        undefined, a new instance is created and returned.
     * @returns {Cartographic} The unprojected coordinates.  If the result parameter is not undefined, the
     *          coordinates are copied there and that instance is returned.  Otherwise, a new instance is
     *          created and returned.
     */
    GeographicProjection.prototype.unproject = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required');
        }
        
        var oneOverEarthSemimajorAxis = this._oneOverSemimajorAxis;
        var longitude = cartesian.x * oneOverEarthSemimajorAxis;
        var latitude = cartesian.y * oneOverEarthSemimajorAxis;
        var height = cartesian.z;

        if (!defined(result)) {
            return new Cartographic(longitude, latitude, height);
        }

        result.longitude = longitude;
        result.latitude = latitude;
        result.height = height;
        return result;
    };

    return GeographicProjection;
});

/*global define*/
define('Core/Rectangle',[
        './Cartographic',
        './Check',
        './defaultValue',
        './defined',
        './defineProperties',
        './Ellipsoid',
        './freezeObject',
        './Math'
    ], function(
        Cartographic,
        Check,
        defaultValue,
        defined,
        defineProperties,
        Ellipsoid,
        freezeObject,
        CesiumMath) {
    'use strict';

    /**
     * A two dimensional region specified as longitude and latitude coordinates.
     *
     * @alias Rectangle
     * @constructor
     *
     * @param {Number} [west=0.0] The westernmost longitude, in radians, in the range [-Pi, Pi].
     * @param {Number} [south=0.0] The southernmost latitude, in radians, in the range [-Pi/2, Pi/2].
     * @param {Number} [east=0.0] The easternmost longitude, in radians, in the range [-Pi, Pi].
     * @param {Number} [north=0.0] The northernmost latitude, in radians, in the range [-Pi/2, Pi/2].
     *
     * @see Packable
     */
    function Rectangle(west, south, east, north) {
        /**
         * The westernmost longitude in radians in the range [-Pi, Pi].
         *
         * @type {Number}
         * @default 0.0
         */
        this.west = defaultValue(west, 0.0);

        /**
         * The southernmost latitude in radians in the range [-Pi/2, Pi/2].
         *
         * @type {Number}
         * @default 0.0
         */
        this.south = defaultValue(south, 0.0);

        /**
         * The easternmost longitude in radians in the range [-Pi, Pi].
         *
         * @type {Number}
         * @default 0.0
         */
        this.east = defaultValue(east, 0.0);

        /**
         * The northernmost latitude in radians in the range [-Pi/2, Pi/2].
         *
         * @type {Number}
         * @default 0.0
         */
        this.north = defaultValue(north, 0.0);
    }

    defineProperties(Rectangle.prototype, {
        /**
         * Gets the width of the rectangle in radians.
         * @memberof Rectangle.prototype
         * @type {Number}
         */
        width : {
            get : function() {
                return Rectangle.computeWidth(this);
            }
        },

        /**
         * Gets the height of the rectangle in radians.
         * @memberof Rectangle.prototype
         * @type {Number}
         */
        height : {
            get : function() {
                return Rectangle.computeHeight(this);
            }
        }
    });

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Rectangle.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Rectangle} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Rectangle.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.west;
        array[startingIndex++] = value.south;
        array[startingIndex++] = value.east;
        array[startingIndex] = value.north;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Rectangle} [result] The object into which to store the result.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if one was not provided.
     */
    Rectangle.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Rectangle();
        }

        result.west = array[startingIndex++];
        result.south = array[startingIndex++];
        result.east = array[startingIndex++];
        result.north = array[startingIndex];
        return result;
    };

    /**
     * Computes the width of a rectangle in radians.
     * @param {Rectangle} rectangle The rectangle to compute the width of.
     * @returns {Number} The width.
     */
    Rectangle.computeWidth = function(rectangle) {
                Check.typeOf.object('rectangle', rectangle);
                var east = rectangle.east;
        var west = rectangle.west;
        if (east < west) {
            east += CesiumMath.TWO_PI;
        }
        return east - west;
    };

    /**
     * Computes the height of a rectangle in radians.
     * @param {Rectangle} rectangle The rectangle to compute the height of.
     * @returns {Number} The height.
     */
    Rectangle.computeHeight = function(rectangle) {
                Check.typeOf.object('rectangle', rectangle);
                return rectangle.north - rectangle.south;
    };

    /**
     * Creates an rectangle given the boundary longitude and latitude in degrees.
     *
     * @param {Number} [west=0.0] The westernmost longitude in degrees in the range [-180.0, 180.0].
     * @param {Number} [south=0.0] The southernmost latitude in degrees in the range [-90.0, 90.0].
     * @param {Number} [east=0.0] The easternmost longitude in degrees in the range [-180.0, 180.0].
     * @param {Number} [north=0.0] The northernmost latitude in degrees in the range [-90.0, 90.0].
     * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
     *
     * @example
     * var rectangle = Cesium.Rectangle.fromDegrees(0.0, 20.0, 10.0, 30.0);
     */
    Rectangle.fromDegrees = function(west, south, east, north, result) {
        west = CesiumMath.toRadians(defaultValue(west, 0.0));
        south = CesiumMath.toRadians(defaultValue(south, 0.0));
        east = CesiumMath.toRadians(defaultValue(east, 0.0));
        north = CesiumMath.toRadians(defaultValue(north, 0.0));

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;

        return result;
    };

    /**
     * Creates an rectangle given the boundary longitude and latitude in radians.
     *
     * @param {Number} [west=0.0] The westernmost longitude in radians in the range [-Math.PI, Math.PI].
     * @param {Number} [south=0.0] The southernmost latitude in radians in the range [-Math.PI/2, Math.PI/2].
     * @param {Number} [east=0.0] The easternmost longitude in radians in the range [-Math.PI, Math.PI].
     * @param {Number} [north=0.0] The northernmost latitude in radians in the range [-Math.PI/2, Math.PI/2].
     * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
     *
     * @example
     * var rectangle = Cesium.Rectangle.fromRadians(0.0, Math.PI/4, Math.PI/8, 3*Math.PI/4);
     */
    Rectangle.fromRadians = function(west, south, east, north, result) {
        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = defaultValue(west, 0.0);
        result.south = defaultValue(south, 0.0);
        result.east = defaultValue(east, 0.0);
        result.north = defaultValue(north, 0.0);

        return result;
    };

    /**
     * Creates the smallest possible Rectangle that encloses all positions in the provided array.
     *
     * @param {Cartographic[]} cartographics The list of Cartographic instances.
     * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
     */
    Rectangle.fromCartographicArray = function(cartographics, result) {
                Check.defined('cartographics', cartographics);
        
        var west = Number.MAX_VALUE;
        var east = -Number.MAX_VALUE;
        var westOverIDL = Number.MAX_VALUE;
        var eastOverIDL = -Number.MAX_VALUE;
        var south = Number.MAX_VALUE;
        var north = -Number.MAX_VALUE;

        for ( var i = 0, len = cartographics.length; i < len; i++) {
            var position = cartographics[i];
            west = Math.min(west, position.longitude);
            east = Math.max(east, position.longitude);
            south = Math.min(south, position.latitude);
            north = Math.max(north, position.latitude);

            var lonAdjusted = position.longitude >= 0 ?  position.longitude : position.longitude +  CesiumMath.TWO_PI;
            westOverIDL = Math.min(westOverIDL, lonAdjusted);
            eastOverIDL = Math.max(eastOverIDL, lonAdjusted);
        }

        if(east - west > eastOverIDL - westOverIDL) {
            west = westOverIDL;
            east = eastOverIDL;

            if (east > CesiumMath.PI) {
                east = east - CesiumMath.TWO_PI;
            }
            if (west > CesiumMath.PI) {
                west = west - CesiumMath.TWO_PI;
            }
        }

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Creates the smallest possible Rectangle that encloses all positions in the provided array.
     *
     * @param {Cartesian[]} cartesians The list of Cartesian instances.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid the cartesians are on.
     * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
     */
    Rectangle.fromCartesianArray = function(cartesians, ellipsoid, result) {
                Check.defined('cartesians', cartesians);
        
        var west = Number.MAX_VALUE;
        var east = -Number.MAX_VALUE;
        var westOverIDL = Number.MAX_VALUE;
        var eastOverIDL = -Number.MAX_VALUE;
        var south = Number.MAX_VALUE;
        var north = -Number.MAX_VALUE;

        for ( var i = 0, len = cartesians.length; i < len; i++) {
            var position = ellipsoid.cartesianToCartographic(cartesians[i]);
            west = Math.min(west, position.longitude);
            east = Math.max(east, position.longitude);
            south = Math.min(south, position.latitude);
            north = Math.max(north, position.latitude);

            var lonAdjusted = position.longitude >= 0 ?  position.longitude : position.longitude +  CesiumMath.TWO_PI;
            westOverIDL = Math.min(westOverIDL, lonAdjusted);
            eastOverIDL = Math.max(eastOverIDL, lonAdjusted);
        }

        if(east - west > eastOverIDL - westOverIDL) {
            west = westOverIDL;
            east = eastOverIDL;

            if (east > CesiumMath.PI) {
                east = east - CesiumMath.TWO_PI;
            }
            if (west > CesiumMath.PI) {
                west = west - CesiumMath.TWO_PI;
            }
        }

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Duplicates an Rectangle.
     *
     * @param {Rectangle} rectangle The rectangle to clone.
     * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided. (Returns undefined if rectangle is undefined)
     */
    Rectangle.clone = function(rectangle, result) {
        if (!defined(rectangle)) {
            return undefined;
        }

        if (!defined(result)) {
            return new Rectangle(rectangle.west, rectangle.south, rectangle.east, rectangle.north);
        }

        result.west = rectangle.west;
        result.south = rectangle.south;
        result.east = rectangle.east;
        result.north = rectangle.north;
        return result;
    };

    /**
     * Duplicates this Rectangle.
     *
     * @param {Rectangle} [result] The object onto which to store the result.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
     */
    Rectangle.prototype.clone = function(result) {
        return Rectangle.clone(this, result);
    };

    /**
     * Compares the provided Rectangle with this Rectangle componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Rectangle} [other] The Rectangle to compare.
     * @returns {Boolean} <code>true</code> if the Rectangles are equal, <code>false</code> otherwise.
     */
    Rectangle.prototype.equals = function(other) {
        return Rectangle.equals(this, other);
    };

    /**
     * Compares the provided rectangles and returns <code>true</code> if they are equal,
     * <code>false</code> otherwise.
     *
     * @param {Rectangle} [left] The first Rectangle.
     * @param {Rectangle} [right] The second Rectangle.
     * @returns {Boolean} <code>true</code> if left and right are equal; otherwise <code>false</code>.
     */
    Rectangle.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.west === right.west) &&
                (left.south === right.south) &&
                (left.east === right.east) &&
                (left.north === right.north));
    };

    /**
     * Compares the provided Rectangle with this Rectangle componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Rectangle} [other] The Rectangle to compare.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if the Rectangles are within the provided epsilon, <code>false</code> otherwise.
     */
    Rectangle.prototype.equalsEpsilon = function(other, epsilon) {
                Check.typeOf.number('epsilon', epsilon);
        
        return defined(other) &&
               (Math.abs(this.west - other.west) <= epsilon) &&
               (Math.abs(this.south - other.south) <= epsilon) &&
               (Math.abs(this.east - other.east) <= epsilon) &&
               (Math.abs(this.north - other.north) <= epsilon);
    };

    /**
     * Checks an Rectangle's properties and throws if they are not in valid ranges.
     *
     * @param {Rectangle} rectangle The rectangle to validate
     *
     * @exception {DeveloperError} <code>north</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
     * @exception {DeveloperError} <code>south</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
     * @exception {DeveloperError} <code>east</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
     * @exception {DeveloperError} <code>west</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
     */
    Rectangle.validate = function(rectangle) {
                Check.typeOf.object('rectangle', rectangle);

        var north = rectangle.north;
        Check.typeOf.number.greaterThanOrEquals('north', north, -CesiumMath.PI_OVER_TWO);
        Check.typeOf.number.lessThanOrEquals('north', north, CesiumMath.PI_OVER_TWO);

        var south = rectangle.south;
        Check.typeOf.number.greaterThanOrEquals('south', south, -CesiumMath.PI_OVER_TWO);
        Check.typeOf.number.lessThanOrEquals('south', south, CesiumMath.PI_OVER_TWO);

        var west = rectangle.west;
        Check.typeOf.number.greaterThanOrEquals('west', west, -Math.PI);
        Check.typeOf.number.lessThanOrEquals('west', west, Math.PI);

        var east = rectangle.east;
        Check.typeOf.number.greaterThanOrEquals('east', east, -Math.PI);
        Check.typeOf.number.lessThanOrEquals('east', east, Math.PI);
            };

    /**
     * Computes the southwest corner of an rectangle.
     *
     * @param {Rectangle} rectangle The rectangle for which to find the corner
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
     */
    Rectangle.southwest = function(rectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        
        if (!defined(result)) {
            return new Cartographic(rectangle.west, rectangle.south);
        }
        result.longitude = rectangle.west;
        result.latitude = rectangle.south;
        result.height = 0.0;
        return result;
    };

    /**
     * Computes the northwest corner of an rectangle.
     *
     * @param {Rectangle} rectangle The rectangle for which to find the corner
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
     */
    Rectangle.northwest = function(rectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        
        if (!defined(result)) {
            return new Cartographic(rectangle.west, rectangle.north);
        }
        result.longitude = rectangle.west;
        result.latitude = rectangle.north;
        result.height = 0.0;
        return result;
    };

    /**
     * Computes the northeast corner of an rectangle.
     *
     * @param {Rectangle} rectangle The rectangle for which to find the corner
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
     */
    Rectangle.northeast = function(rectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        
        if (!defined(result)) {
            return new Cartographic(rectangle.east, rectangle.north);
        }
        result.longitude = rectangle.east;
        result.latitude = rectangle.north;
        result.height = 0.0;
        return result;
    };

    /**
     * Computes the southeast corner of an rectangle.
     *
     * @param {Rectangle} rectangle The rectangle for which to find the corner
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
     */
    Rectangle.southeast = function(rectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        
        if (!defined(result)) {
            return new Cartographic(rectangle.east, rectangle.south);
        }
        result.longitude = rectangle.east;
        result.latitude = rectangle.south;
        result.height = 0.0;
        return result;
    };

    /**
     * Computes the center of an rectangle.
     *
     * @param {Rectangle} rectangle The rectangle for which to find the center
     * @param {Cartographic} [result] The object onto which to store the result.
     * @returns {Cartographic} The modified result parameter or a new Cartographic instance if none was provided.
     */
    Rectangle.center = function(rectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        
        var east = rectangle.east;
        var west = rectangle.west;

        if (east < west) {
            east += CesiumMath.TWO_PI;
        }

        var longitude = CesiumMath.negativePiToPi((west + east) * 0.5);
        var latitude = (rectangle.south + rectangle.north) * 0.5;

        if (!defined(result)) {
            return new Cartographic(longitude, latitude);
        }

        result.longitude = longitude;
        result.latitude = latitude;
        result.height = 0.0;
        return result;
    };

    /**
     * Computes the intersection of two rectangles.  This function assumes that the rectangle's coordinates are
     * latitude and longitude in radians and produces a correct intersection, taking into account the fact that
     * the same angle can be represented with multiple values as well as the wrapping of longitude at the
     * anti-meridian.  For a simple intersection that ignores these factors and can be used with projected
     * coordinates, see {@link Rectangle.simpleIntersection}.
     *
     * @param {Rectangle} rectangle On rectangle to find an intersection
     * @param {Rectangle} otherRectangle Another rectangle to find an intersection
     * @param {Rectangle} [result] The object onto which to store the result.
     * @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection.
     */
    Rectangle.intersection = function(rectangle, otherRectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        Check.typeOf.object('otherRectangle', otherRectangle);
        
        var rectangleEast = rectangle.east;
        var rectangleWest = rectangle.west;

        var otherRectangleEast = otherRectangle.east;
        var otherRectangleWest = otherRectangle.west;

        if (rectangleEast < rectangleWest && otherRectangleEast > 0.0) {
            rectangleEast += CesiumMath.TWO_PI;
        } else if (otherRectangleEast < otherRectangleWest && rectangleEast > 0.0) {
            otherRectangleEast += CesiumMath.TWO_PI;
        }

        if (rectangleEast < rectangleWest && otherRectangleWest < 0.0) {
            otherRectangleWest += CesiumMath.TWO_PI;
        } else if (otherRectangleEast < otherRectangleWest && rectangleWest < 0.0) {
            rectangleWest += CesiumMath.TWO_PI;
        }

        var west = CesiumMath.negativePiToPi(Math.max(rectangleWest, otherRectangleWest));
        var east = CesiumMath.negativePiToPi(Math.min(rectangleEast, otherRectangleEast));

        if ((rectangle.west < rectangle.east || otherRectangle.west < otherRectangle.east) && east <= west) {
            return undefined;
        }

        var south = Math.max(rectangle.south, otherRectangle.south);
        var north = Math.min(rectangle.north, otherRectangle.north);

        if (south >= north) {
            return undefined;
        }

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }
        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Computes a simple intersection of two rectangles.  Unlike {@link Rectangle.intersection}, this function
     * does not attempt to put the angular coordinates into a consistent range or to account for crossing the
     * anti-meridian.  As such, it can be used for rectangles where the coordinates are not simply latitude
     * and longitude (i.e. projected coordinates).
     *
     * @param {Rectangle} rectangle On rectangle to find an intersection
     * @param {Rectangle} otherRectangle Another rectangle to find an intersection
     * @param {Rectangle} [result] The object onto which to store the result.
     * @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection.
     */
    Rectangle.simpleIntersection = function(rectangle, otherRectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        Check.typeOf.object('otherRectangle', otherRectangle);
        
        var west = Math.max(rectangle.west, otherRectangle.west);
        var south = Math.max(rectangle.south, otherRectangle.south);
        var east = Math.min(rectangle.east, otherRectangle.east);
        var north = Math.min(rectangle.north, otherRectangle.north);

        if (south >= north || west >= east) {
            return undefined;
        }

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Computes a rectangle that is the union of two rectangles.
     *
     * @param {Rectangle} rectangle A rectangle to enclose in rectangle.
     * @param {Rectangle} otherRectangle A rectangle to enclose in a rectangle.
     * @param {Rectangle} [result] The object onto which to store the result.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
     */
    Rectangle.union = function(rectangle, otherRectangle, result) {
                Check.typeOf.object('rectangle', rectangle);
        Check.typeOf.object('otherRectangle', otherRectangle);
        
        if (!defined(result)) {
            result = new Rectangle();
        }

        var rectangleEast = rectangle.east;
        var rectangleWest = rectangle.west;

        var otherRectangleEast = otherRectangle.east;
        var otherRectangleWest = otherRectangle.west;

        if (rectangleEast < rectangleWest && otherRectangleEast > 0.0) {
            rectangleEast += CesiumMath.TWO_PI;
        } else if (otherRectangleEast < otherRectangleWest && rectangleEast > 0.0) {
            otherRectangleEast += CesiumMath.TWO_PI;
        }

        if (rectangleEast < rectangleWest && otherRectangleWest < 0.0) {
            otherRectangleWest += CesiumMath.TWO_PI;
        } else if (otherRectangleEast < otherRectangleWest && rectangleWest < 0.0) {
            rectangleWest += CesiumMath.TWO_PI;
        }

        var west = CesiumMath.convertLongitudeRange(Math.min(rectangleWest, otherRectangleWest));
        var east = CesiumMath.convertLongitudeRange(Math.max(rectangleEast, otherRectangleEast));

        result.west = west;
        result.south = Math.min(rectangle.south, otherRectangle.south);
        result.east = east;
        result.north = Math.max(rectangle.north, otherRectangle.north);

        return result;
    };

    /**
     * Computes a rectangle by enlarging the provided rectangle until it contains the provided cartographic.
     *
     * @param {Rectangle} rectangle A rectangle to expand.
     * @param {Cartographic} cartographic A cartographic to enclose in a rectangle.
     * @param {Rectangle} [result] The object onto which to store the result.
     * @returns {Rectangle} The modified result parameter or a new Rectangle instance if one was not provided.
     */
    Rectangle.expand = function(rectangle, cartographic, result) {
                Check.typeOf.object('rectangle', rectangle);
        Check.typeOf.object('cartographic', cartographic);
        
        if (!defined(result)) {
            result = new Rectangle();
        }

        result.west = Math.min(rectangle.west, cartographic.longitude);
        result.south = Math.min(rectangle.south, cartographic.latitude);
        result.east = Math.max(rectangle.east, cartographic.longitude);
        result.north = Math.max(rectangle.north, cartographic.latitude);

        return result;
    };

    /**
     * Returns true if the cartographic is on or inside the rectangle, false otherwise.
     *
     * @param {Rectangle} rectangle The rectangle
     * @param {Cartographic} cartographic The cartographic to test.
     * @returns {Boolean} true if the provided cartographic is inside the rectangle, false otherwise.
     */
    Rectangle.contains = function(rectangle, cartographic) {
                Check.typeOf.object('rectangle', rectangle);
        Check.typeOf.object('cartographic', cartographic);
        
        var longitude = cartographic.longitude;
        var latitude = cartographic.latitude;

        var west = rectangle.west;
        var east = rectangle.east;

        if (east < west) {
            east += CesiumMath.TWO_PI;
            if (longitude < 0.0) {
                longitude += CesiumMath.TWO_PI;
            }
        }
        return (longitude > west || CesiumMath.equalsEpsilon(longitude, west, CesiumMath.EPSILON14)) &&
               (longitude < east || CesiumMath.equalsEpsilon(longitude, east, CesiumMath.EPSILON14)) &&
               latitude >= rectangle.south &&
               latitude <= rectangle.north;
    };

    var subsampleLlaScratch = new Cartographic();
    /**
     * Samples an rectangle so that it includes a list of Cartesian points suitable for passing to
     * {@link BoundingSphere#fromPoints}.  Sampling is necessary to account
     * for rectangles that cover the poles or cross the equator.
     *
     * @param {Rectangle} rectangle The rectangle to subsample.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use.
     * @param {Number} [surfaceHeight=0.0] The height of the rectangle above the ellipsoid.
     * @param {Cartesian3[]} [result] The array of Cartesians onto which to store the result.
     * @returns {Cartesian3[]} The modified result parameter or a new Array of Cartesians instances if none was provided.
     */
    Rectangle.subsample = function(rectangle, ellipsoid, surfaceHeight, result) {
                Check.typeOf.object('rectangle', rectangle);
        
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        surfaceHeight = defaultValue(surfaceHeight, 0.0);

        if (!defined(result)) {
            result = [];
        }
        var length = 0;

        var north = rectangle.north;
        var south = rectangle.south;
        var east = rectangle.east;
        var west = rectangle.west;

        var lla = subsampleLlaScratch;
        lla.height = surfaceHeight;

        lla.longitude = west;
        lla.latitude = north;
        result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
        length++;

        lla.longitude = east;
        result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
        length++;

        lla.latitude = south;
        result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
        length++;

        lla.longitude = west;
        result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
        length++;

        if (north < 0.0) {
            lla.latitude = north;
        } else if (south > 0.0) {
            lla.latitude = south;
        } else {
            lla.latitude = 0.0;
        }

        for ( var i = 1; i < 8; ++i) {
            lla.longitude = -Math.PI + i * CesiumMath.PI_OVER_TWO;
            if (Rectangle.contains(rectangle, lla)) {
                result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
                length++;
            }
        }

        if (lla.latitude === 0.0) {
            lla.longitude = west;
            result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
            length++;
            lla.longitude = east;
            result[length] = ellipsoid.cartographicToCartesian(lla, result[length]);
            length++;
        }
        result.length = length;
        return result;
    };

    /**
     * The largest possible rectangle.
     *
     * @type {Rectangle}
     * @constant
    */
    Rectangle.MAX_VALUE = freezeObject(new Rectangle(-Math.PI, -CesiumMath.PI_OVER_TWO, Math.PI, CesiumMath.PI_OVER_TWO));

    return Rectangle;
});

/*global define*/
define('Core/GeographicTilingScheme',[
        './Cartesian2',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './GeographicProjection',
        './Math',
        './Rectangle'
    ], function(
        Cartesian2,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        GeographicProjection,
        CesiumMath,
        Rectangle) {
    'use strict';

    /**
     * A tiling scheme for geometry referenced to a simple {@link GeographicProjection} where
     * longitude and latitude are directly mapped to X and Y.  This projection is commonly
     * known as geographic, equirectangular, equidistant cylindrical, or plate carrée.
     *
     * @alias GeographicTilingScheme
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
     * the WGS84 ellipsoid.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the tiling scheme.
     * @param {Number} [options.numberOfLevelZeroTilesX=2] The number of tiles in the X direction at level zero of
     * the tile tree.
     * @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
     * the tile tree.
     */
    function GeographicTilingScheme(options) {
        options = defaultValue(options, {});

        this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        this._rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
        this._projection = new GeographicProjection(this._ellipsoid);
        this._numberOfLevelZeroTilesX = defaultValue(options.numberOfLevelZeroTilesX, 2);
        this._numberOfLevelZeroTilesY = defaultValue(options.numberOfLevelZeroTilesY, 1);
    }

    defineProperties(GeographicTilingScheme.prototype, {
        /**
         * Gets the ellipsoid that is tiled by this tiling scheme.
         * @memberof GeographicTilingScheme.prototype
         * @type {Ellipsoid}
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        },

        /**
         * Gets the rectangle, in radians, covered by this tiling scheme.
         * @memberof GeographicTilingScheme.prototype
         * @type {Rectangle}
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        },

        /**
         * Gets the map projection used by this tiling scheme.
         * @memberof GeographicTilingScheme.prototype
         * @type {MapProjection}
         */
        projection : {
            get : function() {
                return this._projection;
            }
        }
    });

    /**
     * Gets the total number of tiles in the X direction at a specified level-of-detail.
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the X direction at the given level.
     */
    GeographicTilingScheme.prototype.getNumberOfXTilesAtLevel = function(level) {
        return this._numberOfLevelZeroTilesX << level;
    };

    /**
     * Gets the total number of tiles in the Y direction at a specified level-of-detail.
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the Y direction at the given level.
     */
    GeographicTilingScheme.prototype.getNumberOfYTilesAtLevel = function(level) {
        return this._numberOfLevelZeroTilesY << level;
    };

    /**
     * Transforms an rectangle specified in geodetic radians to the native coordinate system
     * of this tiling scheme.
     *
     * @param {Rectangle} rectangle The rectangle to transform.
     * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
     *          is undefined.
     */
    GeographicTilingScheme.prototype.rectangleToNativeRectangle = function(rectangle, result) {
                if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required.');
        }
        
        var west = CesiumMath.toDegrees(rectangle.west);
        var south = CesiumMath.toDegrees(rectangle.south);
        var east = CesiumMath.toDegrees(rectangle.east);
        var north = CesiumMath.toDegrees(rectangle.north);

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
     * of the tiling scheme.
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    GeographicTilingScheme.prototype.tileXYToNativeRectangle = function(x, y, level, result) {
        var rectangleRadians = this.tileXYToRectangle(x, y, level, result);
        rectangleRadians.west = CesiumMath.toDegrees(rectangleRadians.west);
        rectangleRadians.south = CesiumMath.toDegrees(rectangleRadians.south);
        rectangleRadians.east = CesiumMath.toDegrees(rectangleRadians.east);
        rectangleRadians.north = CesiumMath.toDegrees(rectangleRadians.north);
        return rectangleRadians;
    };

    /**
     * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    GeographicTilingScheme.prototype.tileXYToRectangle = function(x, y, level, result) {
        var rectangle = this._rectangle;

        var xTiles = this.getNumberOfXTilesAtLevel(level);
        var yTiles = this.getNumberOfYTilesAtLevel(level);

        var xTileWidth = rectangle.width / xTiles;
        var west = x * xTileWidth + rectangle.west;
        var east = (x + 1) * xTileWidth + rectangle.west;

        var yTileHeight = rectangle.height / yTiles;
        var north = rectangle.north - y * yTileHeight;
        var south = rectangle.north - (y + 1) * yTileHeight;

        if (!defined(result)) {
            result = new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Calculates the tile x, y coordinates of the tile containing
     * a given cartographic position.
     *
     * @param {Cartographic} position The position.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
     *          if 'result' is undefined.
     */
    GeographicTilingScheme.prototype.positionToTileXY = function(position, level, result) {
        var rectangle = this._rectangle;
        if (!Rectangle.contains(rectangle, position)) {
            // outside the bounds of the tiling scheme
            return undefined;
        }

        var xTiles = this.getNumberOfXTilesAtLevel(level);
        var yTiles = this.getNumberOfYTilesAtLevel(level);

        var xTileWidth = rectangle.width / xTiles;
        var yTileHeight = rectangle.height / yTiles;

        var longitude = position.longitude;
        if (rectangle.east < rectangle.west) {
            longitude += CesiumMath.TWO_PI;
        }

        var xTileCoordinate = (longitude - rectangle.west) / xTileWidth | 0;
        if (xTileCoordinate >= xTiles) {
            xTileCoordinate = xTiles - 1;
        }

        var yTileCoordinate = (rectangle.north - position.latitude) / yTileHeight | 0;
        if (yTileCoordinate >= yTiles) {
            yTileCoordinate = yTiles - 1;
        }

        if (!defined(result)) {
            return new Cartesian2(xTileCoordinate, yTileCoordinate);
        }

        result.x = xTileCoordinate;
        result.y = yTileCoordinate;
        return result;
    };

    return GeographicTilingScheme;
});

/*global define*/
define('Core/getImagePixels',[
        './defined'
    ], function(
        defined) {
    'use strict';

    var context2DsByWidthAndHeight = {};

    /**
     * Extract a pixel array from a loaded image.  Draws the image
     * into a canvas so it can read the pixels back.
     *
     * @exports getImagePixels
     *
     * @param {Image} image The image to extract pixels from.
     * @param {Number} width The width of the image. If not defined, then image.width is assigned.
     * @param {Number} height The height of the image. If not defined, then image.height is assigned.
     * @returns {CanvasPixelArray} The pixels of the image.
     */
    function getImagePixels(image, width, height) {
        if (!defined(width)) {
            width = image.width;
        }
        if (!defined(height)) {
            height = image.height;
        }

        var context2DsByHeight = context2DsByWidthAndHeight[width];
        if (!defined(context2DsByHeight)) {
            context2DsByHeight = {};
            context2DsByWidthAndHeight[width] = context2DsByHeight;
        }

        var context2d = context2DsByHeight[height];
        if (!defined(context2d)) {
            var canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            context2d = canvas.getContext('2d');
            context2d.globalCompositeOperation = 'copy';
            context2DsByHeight[height] = context2d;
        }

        context2d.drawImage(image, 0, 0, width, height);
        return context2d.getImageData(0, 0, width, height).data;
    }

    return getImagePixels;
});

/*global define*/
define('Core/Intersect',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * This enumerated type is used in determining where, relative to the frustum, an
     * object is located. The object can either be fully contained within the frustum (INSIDE),
     * partially inside the frustum and partially outside (INTERSECTING), or somwhere entirely
     * outside of the frustum's 6 planes (OUTSIDE).
     *
     * @exports Intersect
     */
    var Intersect = {
        /**
         * Represents that an object is not contained within the frustum.
         *
         * @type {Number}
         * @constant
         */
        OUTSIDE : -1,

        /**
         * Represents that an object intersects one of the frustum's planes.
         *
         * @type {Number}
         * @constant
         */
        INTERSECTING : 0,

        /**
         * Represents that an object is fully within the frustum.
         *
         * @type {Number}
         * @constant
         */
        INSIDE : 1
    };

    return freezeObject(Intersect);
});

/*global define*/
define('Core/AxisAlignedBoundingBox',[
        './Cartesian3',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Intersect'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        DeveloperError,
        Intersect) {
    'use strict';

    /**
     * Creates an instance of an AxisAlignedBoundingBox from the minimum and maximum points along the x, y, and z axes.
     * @alias AxisAlignedBoundingBox
     * @constructor
     *
     * @param {Cartesian3} [minimum=Cartesian3.ZERO] The minimum point along the x, y, and z axes.
     * @param {Cartesian3} [maximum=Cartesian3.ZERO] The maximum point along the x, y, and z axes.
     * @param {Cartesian3} [center] The center of the box; automatically computed if not supplied.
     *
     * @see BoundingSphere
     * @see BoundingRectangle
     */
    function AxisAlignedBoundingBox(minimum, maximum, center) {
        /**
         * The minimum point defining the bounding box.
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.minimum = Cartesian3.clone(defaultValue(minimum, Cartesian3.ZERO));

        /**
         * The maximum point defining the bounding box.
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.maximum = Cartesian3.clone(defaultValue(maximum, Cartesian3.ZERO));

        //If center was not defined, compute it.
        if (!defined(center)) {
            center = Cartesian3.add(this.minimum, this.maximum, new Cartesian3());
            Cartesian3.multiplyByScalar(center, 0.5, center);
        } else {
            center = Cartesian3.clone(center);
        }

        /**
         * The center point of the bounding box.
         * @type {Cartesian3}
         */
        this.center = center;
    }

    /**
     * Computes an instance of an AxisAlignedBoundingBox. The box is determined by
     * finding the points spaced the farthest apart on the x, y, and z axes.
     *
     * @param {Cartesian3[]} positions List of points that the bounding box will enclose.  Each point must have a <code>x</code>, <code>y</code>, and <code>z</code> properties.
     * @param {AxisAlignedBoundingBox} [result] The object onto which to store the result.
     * @returns {AxisAlignedBoundingBox} The modified result parameter or a new AxisAlignedBoundingBox instance if one was not provided.
     *
     * @example
     * // Compute an axis aligned bounding box enclosing two points.
     * var box = Cesium.AxisAlignedBoundingBox.fromPoints([new Cesium.Cartesian3(2, 0, 0), new Cesium.Cartesian3(-2, 0, 0)]);
     */
    AxisAlignedBoundingBox.fromPoints = function(positions, result) {
        if (!defined(result)) {
            result = new AxisAlignedBoundingBox();
        }

        if (!defined(positions) || positions.length === 0) {
            result.minimum = Cartesian3.clone(Cartesian3.ZERO, result.minimum);
            result.maximum = Cartesian3.clone(Cartesian3.ZERO, result.maximum);
            result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
            return result;
        }

        var minimumX = positions[0].x;
        var minimumY = positions[0].y;
        var minimumZ = positions[0].z;

        var maximumX = positions[0].x;
        var maximumY = positions[0].y;
        var maximumZ = positions[0].z;

        var length = positions.length;
        for ( var i = 1; i < length; i++) {
            var p = positions[i];
            var x = p.x;
            var y = p.y;
            var z = p.z;

            minimumX = Math.min(x, minimumX);
            maximumX = Math.max(x, maximumX);
            minimumY = Math.min(y, minimumY);
            maximumY = Math.max(y, maximumY);
            minimumZ = Math.min(z, minimumZ);
            maximumZ = Math.max(z, maximumZ);
        }

        var minimum = result.minimum;
        minimum.x = minimumX;
        minimum.y = minimumY;
        minimum.z = minimumZ;

        var maximum = result.maximum;
        maximum.x = maximumX;
        maximum.y = maximumY;
        maximum.z = maximumZ;

        var center = Cartesian3.add(minimum, maximum, result.center);
        Cartesian3.multiplyByScalar(center, 0.5, center);

        return result;
    };

    /**
     * Duplicates a AxisAlignedBoundingBox instance.
     *
     * @param {AxisAlignedBoundingBox} box The bounding box to duplicate.
     * @param {AxisAlignedBoundingBox} [result] The object onto which to store the result.
     * @returns {AxisAlignedBoundingBox} The modified result parameter or a new AxisAlignedBoundingBox instance if none was provided. (Returns undefined if box is undefined)
     */
    AxisAlignedBoundingBox.clone = function(box, result) {
        if (!defined(box)) {
            return undefined;
        }

        if (!defined(result)) {
            return new AxisAlignedBoundingBox(box.minimum, box.maximum);
        }

        result.minimum = Cartesian3.clone(box.minimum, result.minimum);
        result.maximum = Cartesian3.clone(box.maximum, result.maximum);
        result.center = Cartesian3.clone(box.center, result.center);
        return result;
    };

    /**
     * Compares the provided AxisAlignedBoundingBox componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {AxisAlignedBoundingBox} [left] The first AxisAlignedBoundingBox.
     * @param {AxisAlignedBoundingBox} [right] The second AxisAlignedBoundingBox.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    AxisAlignedBoundingBox.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                Cartesian3.equals(left.center, right.center) &&
                Cartesian3.equals(left.minimum, right.minimum) &&
                Cartesian3.equals(left.maximum, right.maximum));
    };

    var intersectScratch = new Cartesian3();
    /**
     * Determines which side of a plane a box is located.
     *
     * @param {AxisAlignedBoundingBox} box The bounding box to test.
     * @param {Plane} plane The plane to test against.
     * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
     *                      the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
     *                      on the opposite side, and {@link Intersect.INTERSECTING} if the box
     *                      intersects the plane.
     */
    AxisAlignedBoundingBox.intersectPlane = function(box, plane) {
                if (!defined(box)) {
            throw new DeveloperError('box is required.');
        }
        if (!defined(plane)) {
            throw new DeveloperError('plane is required.');
        }
        
        intersectScratch = Cartesian3.subtract(box.maximum, box.minimum, intersectScratch);
        var h = Cartesian3.multiplyByScalar(intersectScratch, 0.5, intersectScratch); //The positive half diagonal
        var normal = plane.normal;
        var e = h.x * Math.abs(normal.x) + h.y * Math.abs(normal.y) + h.z * Math.abs(normal.z);
        var s = Cartesian3.dot(box.center, normal) + plane.distance; //signed distance from center

        if (s - e > 0) {
            return Intersect.INSIDE;
        }

        if (s + e < 0) {
            //Not in front because normals point inward
            return Intersect.OUTSIDE;
        }

        return Intersect.INTERSECTING;
    };

    /**
     * Duplicates this AxisAlignedBoundingBox instance.
     *
     * @param {AxisAlignedBoundingBox} [result] The object onto which to store the result.
     * @returns {AxisAlignedBoundingBox} The modified result parameter or a new AxisAlignedBoundingBox instance if one was not provided.
     */
    AxisAlignedBoundingBox.prototype.clone = function(result) {
        return AxisAlignedBoundingBox.clone(this, result);
    };

    /**
     * Determines which side of a plane this box is located.
     *
     * @param {Plane} plane The plane to test against.
     * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
     *                      the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
     *                      on the opposite side, and {@link Intersect.INTERSECTING} if the box
     *                      intersects the plane.
     */
    AxisAlignedBoundingBox.prototype.intersectPlane = function(plane) {
        return AxisAlignedBoundingBox.intersectPlane(this, plane);
    };

    /**
     * Compares this AxisAlignedBoundingBox against the provided AxisAlignedBoundingBox componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {AxisAlignedBoundingBox} [right] The right hand side AxisAlignedBoundingBox.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    AxisAlignedBoundingBox.prototype.equals = function(right) {
        return AxisAlignedBoundingBox.equals(this, right);
    };

    return AxisAlignedBoundingBox;
});

/*global define*/
define('Core/Interval',[
        './defaultValue'
    ], function(
        defaultValue) {
    'use strict';

    /**
     * Represents the closed interval [start, stop].
     * @alias Interval
     * @constructor
     *
     * @param {Number} [start=0.0] The beginning of the interval.
     * @param {Number} [stop=0.0] The end of the interval.
     */
    function Interval(start, stop) {
        /**
         * The beginning of the interval.
         * @type {Number}
         * @default 0.0
         */
        this.start = defaultValue(start, 0.0);
        /**
         * The end of the interval.
         * @type {Number}
         * @default 0.0
         */
        this.stop = defaultValue(stop, 0.0);
    }

    return Interval;
});

/*global define*/
define('Core/Matrix3',[
        './Cartesian3',
        './Check',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './freezeObject',
        './Math'
    ], function(
        Cartesian3,
        Check,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        freezeObject,
        CesiumMath) {
    'use strict';

    /**
     * A 3x3 matrix, indexable as a column-major order array.
     * Constructor parameters are in row-major order for code readability.
     * @alias Matrix3
     * @constructor
     *
     * @param {Number} [column0Row0=0.0] The value for column 0, row 0.
     * @param {Number} [column1Row0=0.0] The value for column 1, row 0.
     * @param {Number} [column2Row0=0.0] The value for column 2, row 0.
     * @param {Number} [column0Row1=0.0] The value for column 0, row 1.
     * @param {Number} [column1Row1=0.0] The value for column 1, row 1.
     * @param {Number} [column2Row1=0.0] The value for column 2, row 1.
     * @param {Number} [column0Row2=0.0] The value for column 0, row 2.
     * @param {Number} [column1Row2=0.0] The value for column 1, row 2.
     * @param {Number} [column2Row2=0.0] The value for column 2, row 2.
     *
     * @see Matrix3.fromColumnMajorArray
     * @see Matrix3.fromRowMajorArray
     * @see Matrix3.fromQuaternion
     * @see Matrix3.fromScale
     * @see Matrix3.fromUniformScale
     * @see Matrix2
     * @see Matrix4
     */
    function Matrix3(column0Row0, column1Row0, column2Row0,
                           column0Row1, column1Row1, column2Row1,
                           column0Row2, column1Row2, column2Row2) {
        this[0] = defaultValue(column0Row0, 0.0);
        this[1] = defaultValue(column0Row1, 0.0);
        this[2] = defaultValue(column0Row2, 0.0);
        this[3] = defaultValue(column1Row0, 0.0);
        this[4] = defaultValue(column1Row1, 0.0);
        this[5] = defaultValue(column1Row2, 0.0);
        this[6] = defaultValue(column2Row0, 0.0);
        this[7] = defaultValue(column2Row1, 0.0);
        this[8] = defaultValue(column2Row2, 0.0);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Matrix3.packedLength = 9;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Matrix3} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Matrix3.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value[0];
        array[startingIndex++] = value[1];
        array[startingIndex++] = value[2];
        array[startingIndex++] = value[3];
        array[startingIndex++] = value[4];
        array[startingIndex++] = value[5];
        array[startingIndex++] = value[6];
        array[startingIndex++] = value[7];
        array[startingIndex++] = value[8];

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Matrix3} [result] The object into which to store the result.
     * @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided.
     */
    Matrix3.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Matrix3();
        }

        result[0] = array[startingIndex++];
        result[1] = array[startingIndex++];
        result[2] = array[startingIndex++];
        result[3] = array[startingIndex++];
        result[4] = array[startingIndex++];
        result[5] = array[startingIndex++];
        result[6] = array[startingIndex++];
        result[7] = array[startingIndex++];
        result[8] = array[startingIndex++];
        return result;
    };

    /**
     * Duplicates a Matrix3 instance.
     *
     * @param {Matrix3} matrix The matrix to duplicate.
     * @param {Matrix3} [result] The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided. (Returns undefined if matrix is undefined)
     */
    Matrix3.clone = function(matrix, result) {
        if (!defined(matrix)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Matrix3(matrix[0], matrix[3], matrix[6],
                               matrix[1], matrix[4], matrix[7],
                               matrix[2], matrix[5], matrix[8]);
        }
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        result[4] = matrix[4];
        result[5] = matrix[5];
        result[6] = matrix[6];
        result[7] = matrix[7];
        result[8] = matrix[8];
        return result;
    };

    /**
     * Creates a Matrix3 from 9 consecutive elements in an array.
     *
     * @param {Number[]} array The array whose 9 consecutive elements correspond to the positions of the matrix.  Assumes column-major order.
     * @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to first column first row position in the matrix.
     * @param {Matrix3} [result] The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Create the Matrix3:
     * // [1.0, 2.0, 3.0]
     * // [1.0, 2.0, 3.0]
     * // [1.0, 2.0, 3.0]
     *
     * var v = [1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0];
     * var m = Cesium.Matrix3.fromArray(v);
     *
     * // Create same Matrix3 with using an offset into an array
     * var v2 = [0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0];
     * var m2 = Cesium.Matrix3.fromArray(v2, 2);
     */
    Matrix3.fromArray = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Matrix3();
        }

        result[0] = array[startingIndex];
        result[1] = array[startingIndex + 1];
        result[2] = array[startingIndex + 2];
        result[3] = array[startingIndex + 3];
        result[4] = array[startingIndex + 4];
        result[5] = array[startingIndex + 5];
        result[6] = array[startingIndex + 6];
        result[7] = array[startingIndex + 7];
        result[8] = array[startingIndex + 8];
        return result;
    };

    /**
     * Creates a Matrix3 instance from a column-major order array.
     *
     * @param {Number[]} values The column-major order array.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     */
    Matrix3.fromColumnMajorArray = function(values, result) {
                Check.defined('values', values);
        
        return Matrix3.clone(values, result);
    };

    /**
     * Creates a Matrix3 instance from a row-major order array.
     * The resulting matrix will be in column-major order.
     *
     * @param {Number[]} values The row-major order array.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     */
    Matrix3.fromRowMajorArray = function(values, result) {
                Check.defined('values', values);
        
        if (!defined(result)) {
            return new Matrix3(values[0], values[1], values[2],
                               values[3], values[4], values[5],
                               values[6], values[7], values[8]);
        }
        result[0] = values[0];
        result[1] = values[3];
        result[2] = values[6];
        result[3] = values[1];
        result[4] = values[4];
        result[5] = values[7];
        result[6] = values[2];
        result[7] = values[5];
        result[8] = values[8];
        return result;
    };

    /**
     * Computes a 3x3 rotation matrix from the provided quaternion.
     *
     * @param {Quaternion} quaternion the quaternion to use.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The 3x3 rotation matrix from this quaternion.
     */
    Matrix3.fromQuaternion = function(quaternion, result) {
                Check.typeOf.object('quaternion', quaternion);
        
        var x2 = quaternion.x * quaternion.x;
        var xy = quaternion.x * quaternion.y;
        var xz = quaternion.x * quaternion.z;
        var xw = quaternion.x * quaternion.w;
        var y2 = quaternion.y * quaternion.y;
        var yz = quaternion.y * quaternion.z;
        var yw = quaternion.y * quaternion.w;
        var z2 = quaternion.z * quaternion.z;
        var zw = quaternion.z * quaternion.w;
        var w2 = quaternion.w * quaternion.w;

        var m00 = x2 - y2 - z2 + w2;
        var m01 = 2.0 * (xy - zw);
        var m02 = 2.0 * (xz + yw);

        var m10 = 2.0 * (xy + zw);
        var m11 = -x2 + y2 - z2 + w2;
        var m12 = 2.0 * (yz - xw);

        var m20 = 2.0 * (xz - yw);
        var m21 = 2.0 * (yz + xw);
        var m22 = -x2 - y2 + z2 + w2;

        if (!defined(result)) {
            return new Matrix3(m00, m01, m02,
                               m10, m11, m12,
                               m20, m21, m22);
        }
        result[0] = m00;
        result[1] = m10;
        result[2] = m20;
        result[3] = m01;
        result[4] = m11;
        result[5] = m21;
        result[6] = m02;
        result[7] = m12;
        result[8] = m22;
        return result;
    };

    /**
     * Computes a 3x3 rotation matrix from the provided headingPitchRoll. (see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles )
     *
     * @param {HeadingPitchRoll} headingPitchRoll the headingPitchRoll to use.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The 3x3 rotation matrix from this headingPitchRoll.
     */
    Matrix3.fromHeadingPitchRoll = function(headingPitchRoll, result) {
                Check.typeOf.object('headingPitchRoll', headingPitchRoll);
        
        var cosTheta = Math.cos(-headingPitchRoll.pitch);
        var cosPsi = Math.cos(-headingPitchRoll.heading);
        var cosPhi = Math.cos(headingPitchRoll.roll);
        var sinTheta = Math.sin(-headingPitchRoll.pitch);
        var sinPsi = Math.sin(-headingPitchRoll.heading);
        var sinPhi = Math.sin(headingPitchRoll.roll);

        var m00 = cosTheta * cosPsi;
        var m01 = -cosPhi * sinPsi + sinPhi * sinTheta * cosPsi;
        var m02 = sinPhi * sinPsi + cosPhi * sinTheta * cosPsi;

        var m10 = cosTheta * sinPsi;
        var m11 = cosPhi * cosPsi + sinPhi * sinTheta * sinPsi;
        var m12 = -sinTheta * cosPhi + cosPhi * sinTheta * sinPsi;

        var m20 = -sinTheta;
        var m21 = sinPhi * cosTheta;
        var m22 = cosPhi * cosTheta;

        if (!defined(result)) {
            return new Matrix3(m00, m01, m02,
                m10, m11, m12,
                m20, m21, m22);
        }
        result[0] = m00;
        result[1] = m10;
        result[2] = m20;
        result[3] = m01;
        result[4] = m11;
        result[5] = m21;
        result[6] = m02;
        result[7] = m12;
        result[8] = m22;
        return result;
    };

    /**
     * Computes a Matrix3 instance representing a non-uniform scale.
     *
     * @param {Cartesian3} scale The x, y, and z scale factors.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [7.0, 0.0, 0.0]
     * //   [0.0, 8.0, 0.0]
     * //   [0.0, 0.0, 9.0]
     * var m = Cesium.Matrix3.fromScale(new Cesium.Cartesian3(7.0, 8.0, 9.0));
     */
    Matrix3.fromScale = function(scale, result) {
                Check.typeOf.object('scale', scale);
        
        if (!defined(result)) {
            return new Matrix3(
                scale.x, 0.0,     0.0,
                0.0,     scale.y, 0.0,
                0.0,     0.0,     scale.z);
        }

        result[0] = scale.x;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = scale.y;
        result[5] = 0.0;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = scale.z;
        return result;
    };

    /**
     * Computes a Matrix3 instance representing a uniform scale.
     *
     * @param {Number} scale The uniform scale factor.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [2.0, 0.0, 0.0]
     * //   [0.0, 2.0, 0.0]
     * //   [0.0, 0.0, 2.0]
     * var m = Cesium.Matrix3.fromUniformScale(2.0);
     */
    Matrix3.fromUniformScale = function(scale, result) {
                Check.typeOf.number('scale', scale);
        
        if (!defined(result)) {
            return new Matrix3(
                scale, 0.0,   0.0,
                0.0,   scale, 0.0,
                0.0,   0.0,   scale);
        }

        result[0] = scale;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = scale;
        result[5] = 0.0;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = scale;
        return result;
    };

    /**
     * Computes a Matrix3 instance representing the cross product equivalent matrix of a Cartesian3 vector.
     *
     * @param {Cartesian3} vector the vector on the left hand side of the cross product operation.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [0.0, -9.0,  8.0]
     * //   [9.0,  0.0, -7.0]
     * //   [-8.0, 7.0,  0.0]
     * var m = Cesium.Matrix3.fromCrossProduct(new Cesium.Cartesian3(7.0, 8.0, 9.0));
     */
    Matrix3.fromCrossProduct = function(vector, result) {
                Check.typeOf.object('vector', vector);
        
        if (!defined(result)) {
            return new Matrix3(
                      0.0, -vector.z,  vector.y,
                 vector.z,       0.0, -vector.x,
                -vector.y,  vector.x,       0.0);
        }

        result[0] = 0.0;
        result[1] = vector.z;
        result[2] = -vector.y;
        result[3] = -vector.z;
        result[4] = 0.0;
        result[5] = vector.x;
        result[6] = vector.y;
        result[7] = -vector.x;
        result[8] = 0.0;
        return result;
    };

    /**
     * Creates a rotation matrix around the x-axis.
     *
     * @param {Number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Rotate a point 45 degrees counterclockwise around the x-axis.
     * var p = new Cesium.Cartesian3(5, 6, 7);
     * var m = Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(45.0));
     * var rotated = Cesium.Matrix3.multiplyByVector(m, p, new Cesium.Cartesian3());
     */
    Matrix3.fromRotationX = function(angle, result) {
                Check.typeOf.number('angle', angle);
        
        var cosAngle = Math.cos(angle);
        var sinAngle = Math.sin(angle);

        if (!defined(result)) {
            return new Matrix3(
                1.0, 0.0, 0.0,
                0.0, cosAngle, -sinAngle,
                0.0, sinAngle, cosAngle);
        }

        result[0] = 1.0;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = cosAngle;
        result[5] = sinAngle;
        result[6] = 0.0;
        result[7] = -sinAngle;
        result[8] = cosAngle;

        return result;
    };

    /**
     * Creates a rotation matrix around the y-axis.
     *
     * @param {Number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Rotate a point 45 degrees counterclockwise around the y-axis.
     * var p = new Cesium.Cartesian3(5, 6, 7);
     * var m = Cesium.Matrix3.fromRotationY(Cesium.Math.toRadians(45.0));
     * var rotated = Cesium.Matrix3.multiplyByVector(m, p, new Cesium.Cartesian3());
     */
    Matrix3.fromRotationY = function(angle, result) {
                Check.typeOf.number('angle', angle);
        
        var cosAngle = Math.cos(angle);
        var sinAngle = Math.sin(angle);

        if (!defined(result)) {
            return new Matrix3(
                cosAngle, 0.0, sinAngle,
                0.0, 1.0, 0.0,
                -sinAngle, 0.0, cosAngle);
        }

        result[0] = cosAngle;
        result[1] = 0.0;
        result[2] = -sinAngle;
        result[3] = 0.0;
        result[4] = 1.0;
        result[5] = 0.0;
        result[6] = sinAngle;
        result[7] = 0.0;
        result[8] = cosAngle;

        return result;
    };

    /**
     * Creates a rotation matrix around the z-axis.
     *
     * @param {Number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
     * @param {Matrix3} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix3} The modified result parameter, or a new Matrix3 instance if one was not provided.
     *
     * @example
     * // Rotate a point 45 degrees counterclockwise around the z-axis.
     * var p = new Cesium.Cartesian3(5, 6, 7);
     * var m = Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(45.0));
     * var rotated = Cesium.Matrix3.multiplyByVector(m, p, new Cesium.Cartesian3());
     */
    Matrix3.fromRotationZ = function(angle, result) {
                Check.typeOf.number('angle', angle);
        
        var cosAngle = Math.cos(angle);
        var sinAngle = Math.sin(angle);

        if (!defined(result)) {
            return new Matrix3(
                cosAngle, -sinAngle, 0.0,
                sinAngle, cosAngle, 0.0,
                0.0, 0.0, 1.0);
        }

        result[0] = cosAngle;
        result[1] = sinAngle;
        result[2] = 0.0;
        result[3] = -sinAngle;
        result[4] = cosAngle;
        result[5] = 0.0;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 1.0;

        return result;
    };

    /**
     * Creates an Array from the provided Matrix3 instance.
     * The array will be in column-major order.
     *
     * @param {Matrix3} matrix The matrix to use..
     * @param {Number[]} [result] The Array onto which to store the result.
     * @returns {Number[]} The modified Array parameter or a new Array instance if one was not provided.
     */
    Matrix3.toArray = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        
        if (!defined(result)) {
            return [matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6], matrix[7], matrix[8]];
        }
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        result[4] = matrix[4];
        result[5] = matrix[5];
        result[6] = matrix[6];
        result[7] = matrix[7];
        result[8] = matrix[8];
        return result;
    };

    /**
     * Computes the array index of the element at the provided row and column.
     *
     * @param {Number} row The zero-based index of the row.
     * @param {Number} column The zero-based index of the column.
     * @returns {Number} The index of the element at the provided row and column.
     *
     * @exception {DeveloperError} row must be 0, 1, or 2.
     * @exception {DeveloperError} column must be 0, 1, or 2.
     *
     * @example
     * var myMatrix = new Cesium.Matrix3();
     * var column1Row0Index = Cesium.Matrix3.getElementIndex(1, 0);
     * var column1Row0 = myMatrix[column1Row0Index]
     * myMatrix[column1Row0Index] = 10.0;
     */
    Matrix3.getElementIndex = function(column, row) {
                Check.typeOf.number.greaterThanOrEquals('row', row, 0);
        Check.typeOf.number.lessThanOrEquals('row', row, 2);
        Check.typeOf.number.greaterThanOrEquals('column', column, 0);
        Check.typeOf.number.lessThanOrEquals('column', column, 2);
        
        return column * 3 + row;
    };

    /**
     * Retrieves a copy of the matrix column at the provided index as a Cartesian3 instance.
     *
     * @param {Matrix3} matrix The matrix to use.
     * @param {Number} index The zero-based index of the column to retrieve.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, or 2.
     */
    Matrix3.getColumn = function(matrix, index, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 2);
        Check.typeOf.object('result', result);
        
        var startIndex = index * 3;
        var x = matrix[startIndex];
        var y = matrix[startIndex + 1];
        var z = matrix[startIndex + 2];

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian3 instance.
     *
     * @param {Matrix3} matrix The matrix to use.
     * @param {Number} index The zero-based index of the column to set.
     * @param {Cartesian3} cartesian The Cartesian whose values will be assigned to the specified column.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, or 2.
     */
    Matrix3.setColumn = function(matrix, index, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 2);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result = Matrix3.clone(matrix, result);
        var startIndex = index * 3;
        result[startIndex] = cartesian.x;
        result[startIndex + 1] = cartesian.y;
        result[startIndex + 2] = cartesian.z;
        return result;
    };

    /**
     * Retrieves a copy of the matrix row at the provided index as a Cartesian3 instance.
     *
     * @param {Matrix3} matrix The matrix to use.
     * @param {Number} index The zero-based index of the row to retrieve.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, or 2.
     */
    Matrix3.getRow = function(matrix, index, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 2);
        Check.typeOf.object('result', result);
        
        var x = matrix[index];
        var y = matrix[index + 3];
        var z = matrix[index + 6];

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian3 instance.
     *
     * @param {Matrix3} matrix The matrix to use.
     * @param {Number} index The zero-based index of the row to set.
     * @param {Cartesian3} cartesian The Cartesian whose values will be assigned to the specified row.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, or 2.
     */
    Matrix3.setRow = function(matrix, index, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 2);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result = Matrix3.clone(matrix, result);
        result[index] = cartesian.x;
        result[index + 3] = cartesian.y;
        result[index + 6] = cartesian.z;
        return result;
    };

    var scratchColumn = new Cartesian3();

    /**
     * Extracts the non-uniform scale assuming the matrix is an affine transformation.
     *
     * @param {Matrix3} matrix The matrix.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Matrix3.getScale = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result.x = Cartesian3.magnitude(Cartesian3.fromElements(matrix[0], matrix[1], matrix[2], scratchColumn));
        result.y = Cartesian3.magnitude(Cartesian3.fromElements(matrix[3], matrix[4], matrix[5], scratchColumn));
        result.z = Cartesian3.magnitude(Cartesian3.fromElements(matrix[6], matrix[7], matrix[8], scratchColumn));
        return result;
    };

    var scratchScale = new Cartesian3();

    /**
     * Computes the maximum scale assuming the matrix is an affine transformation.
     * The maximum scale is the maximum length of the column vectors.
     *
     * @param {Matrix3} matrix The matrix.
     * @returns {Number} The maximum scale.
     */
    Matrix3.getMaximumScale = function(matrix) {
        Matrix3.getScale(matrix, scratchScale);
        return Cartesian3.maximumComponent(scratchScale);
    };

    /**
     * Computes the product of two matrices.
     *
     * @param {Matrix3} left The first matrix.
     * @param {Matrix3} right The second matrix.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.multiply = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        var column0Row0 = left[0] * right[0] + left[3] * right[1] + left[6] * right[2];
        var column0Row1 = left[1] * right[0] + left[4] * right[1] + left[7] * right[2];
        var column0Row2 = left[2] * right[0] + left[5] * right[1] + left[8] * right[2];

        var column1Row0 = left[0] * right[3] + left[3] * right[4] + left[6] * right[5];
        var column1Row1 = left[1] * right[3] + left[4] * right[4] + left[7] * right[5];
        var column1Row2 = left[2] * right[3] + left[5] * right[4] + left[8] * right[5];

        var column2Row0 = left[0] * right[6] + left[3] * right[7] + left[6] * right[8];
        var column2Row1 = left[1] * right[6] + left[4] * right[7] + left[7] * right[8];
        var column2Row2 = left[2] * right[6] + left[5] * right[7] + left[8] * right[8];

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column0Row2;
        result[3] = column1Row0;
        result[4] = column1Row1;
        result[5] = column1Row2;
        result[6] = column2Row0;
        result[7] = column2Row1;
        result[8] = column2Row2;
        return result;
    };

    /**
     * Computes the sum of two matrices.
     *
     * @param {Matrix3} left The first matrix.
     * @param {Matrix3} right The second matrix.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = left[0] + right[0];
        result[1] = left[1] + right[1];
        result[2] = left[2] + right[2];
        result[3] = left[3] + right[3];
        result[4] = left[4] + right[4];
        result[5] = left[5] + right[5];
        result[6] = left[6] + right[6];
        result[7] = left[7] + right[7];
        result[8] = left[8] + right[8];
        return result;
    };

    /**
     * Computes the difference of two matrices.
     *
     * @param {Matrix3} left The first matrix.
     * @param {Matrix3} right The second matrix.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = left[0] - right[0];
        result[1] = left[1] - right[1];
        result[2] = left[2] - right[2];
        result[3] = left[3] - right[3];
        result[4] = left[4] - right[4];
        result[5] = left[5] - right[5];
        result[6] = left[6] - right[6];
        result[7] = left[7] - right[7];
        result[8] = left[8] - right[8];
        return result;
    };

    /**
     * Computes the product of a matrix and a column vector.
     *
     * @param {Matrix3} matrix The matrix.
     * @param {Cartesian3} cartesian The column.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Matrix3.multiplyByVector = function(matrix, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var vX = cartesian.x;
        var vY = cartesian.y;
        var vZ = cartesian.z;

        var x = matrix[0] * vX + matrix[3] * vY + matrix[6] * vZ;
        var y = matrix[1] * vX + matrix[4] * vY + matrix[7] * vZ;
        var z = matrix[2] * vX + matrix[5] * vY + matrix[8] * vZ;

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Computes the product of a matrix and a scalar.
     *
     * @param {Matrix3} matrix The matrix.
     * @param {Number} scalar The number to multiply by.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.multiplyByScalar = function(matrix, scalar, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0] * scalar;
        result[1] = matrix[1] * scalar;
        result[2] = matrix[2] * scalar;
        result[3] = matrix[3] * scalar;
        result[4] = matrix[4] * scalar;
        result[5] = matrix[5] * scalar;
        result[6] = matrix[6] * scalar;
        result[7] = matrix[7] * scalar;
        result[8] = matrix[8] * scalar;
        return result;
    };

    /**
     * Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix.
     *
     * @param {Matrix3} matrix The matrix on the left-hand side.
     * @param {Cartesian3} scale The non-uniform scale on the right-hand side.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     *
     *
     * @example
     * // Instead of Cesium.Matrix3.multiply(m, Cesium.Matrix3.fromScale(scale), m);
     * Cesium.Matrix3.multiplyByScale(m, scale, m);
     *
     * @see Matrix3.fromScale
     * @see Matrix3.multiplyByUniformScale
     */
    Matrix3.multiplyByScale = function(matrix, scale, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('scale', scale);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0] * scale.x;
        result[1] = matrix[1] * scale.x;
        result[2] = matrix[2] * scale.x;
        result[3] = matrix[3] * scale.y;
        result[4] = matrix[4] * scale.y;
        result[5] = matrix[5] * scale.y;
        result[6] = matrix[6] * scale.z;
        result[7] = matrix[7] * scale.z;
        result[8] = matrix[8] * scale.z;
        return result;
    };

    /**
     * Creates a negated copy of the provided matrix.
     *
     * @param {Matrix3} matrix The matrix to negate.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.negate = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = -matrix[0];
        result[1] = -matrix[1];
        result[2] = -matrix[2];
        result[3] = -matrix[3];
        result[4] = -matrix[4];
        result[5] = -matrix[5];
        result[6] = -matrix[6];
        result[7] = -matrix[7];
        result[8] = -matrix[8];
        return result;
    };

    /**
     * Computes the transpose of the provided matrix.
     *
     * @param {Matrix3} matrix The matrix to transpose.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.transpose = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        var column0Row0 = matrix[0];
        var column0Row1 = matrix[3];
        var column0Row2 = matrix[6];
        var column1Row0 = matrix[1];
        var column1Row1 = matrix[4];
        var column1Row2 = matrix[7];
        var column2Row0 = matrix[2];
        var column2Row1 = matrix[5];
        var column2Row2 = matrix[8];

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column0Row2;
        result[3] = column1Row0;
        result[4] = column1Row1;
        result[5] = column1Row2;
        result[6] = column2Row0;
        result[7] = column2Row1;
        result[8] = column2Row2;
        return result;
    };

    function computeFrobeniusNorm(matrix) {
        var norm = 0.0;
        for (var i = 0; i < 9; ++i) {
            var temp = matrix[i];
            norm += temp * temp;
        }

        return Math.sqrt(norm);
    }

    var rowVal = [1, 0, 0];
    var colVal = [2, 2, 1];

    function offDiagonalFrobeniusNorm(matrix) {
        // Computes the "off-diagonal" Frobenius norm.
        // Assumes matrix is symmetric.

        var norm = 0.0;
        for (var i = 0; i < 3; ++i) {
            var temp = matrix[Matrix3.getElementIndex(colVal[i], rowVal[i])];
            norm += 2.0 * temp * temp;
        }

        return Math.sqrt(norm);
    }

    function shurDecomposition(matrix, result) {
        // This routine was created based upon Matrix Computations, 3rd ed., by Golub and Van Loan,
        // section 8.4.2 The 2by2 Symmetric Schur Decomposition.
        //
        // The routine takes a matrix, which is assumed to be symmetric, and
        // finds the largest off-diagonal term, and then creates
        // a matrix (result) which can be used to help reduce it

        var tolerance = CesiumMath.EPSILON15;

        var maxDiagonal = 0.0;
        var rotAxis = 1;

        // find pivot (rotAxis) based on max diagonal of matrix
        for (var i = 0; i < 3; ++i) {
            var temp = Math.abs(matrix[Matrix3.getElementIndex(colVal[i], rowVal[i])]);
            if (temp > maxDiagonal) {
                rotAxis = i;
                maxDiagonal = temp;
            }
        }

        var c = 1.0;
        var s = 0.0;

        var p = rowVal[rotAxis];
        var q = colVal[rotAxis];

        if (Math.abs(matrix[Matrix3.getElementIndex(q, p)]) > tolerance) {
            var qq = matrix[Matrix3.getElementIndex(q, q)];
            var pp = matrix[Matrix3.getElementIndex(p, p)];
            var qp = matrix[Matrix3.getElementIndex(q, p)];

            var tau = (qq - pp) / 2.0 / qp;
            var t;

            if (tau < 0.0) {
                t = -1.0 / (-tau + Math.sqrt(1.0 + tau * tau));
            } else {
                t = 1.0 / (tau + Math.sqrt(1.0 + tau * tau));
            }

            c = 1.0 / Math.sqrt(1.0 + t * t);
            s = t * c;
        }

        result = Matrix3.clone(Matrix3.IDENTITY, result);

        result[Matrix3.getElementIndex(p, p)] = result[Matrix3.getElementIndex(q, q)] = c;
        result[Matrix3.getElementIndex(q, p)] = s;
        result[Matrix3.getElementIndex(p, q)] = -s;

        return result;
    }

    var jMatrix = new Matrix3();
    var jMatrixTranspose = new Matrix3();

    /**
     * Computes the eigenvectors and eigenvalues of a symmetric matrix.
     * <p>
     * Returns a diagonal matrix and unitary matrix such that:
     * <code>matrix = unitary matrix * diagonal matrix * transpose(unitary matrix)</code>
     * </p>
     * <p>
     * The values along the diagonal of the diagonal matrix are the eigenvalues. The columns
     * of the unitary matrix are the corresponding eigenvectors.
     * </p>
     *
     * @param {Matrix3} matrix The matrix to decompose into diagonal and unitary matrix. Expected to be symmetric.
     * @param {Object} [result] An object with unitary and diagonal properties which are matrices onto which to store the result.
     * @returns {Object} An object with unitary and diagonal properties which are the unitary and diagonal matrices, respectively.
     *
     * @example
     * var a = //... symetric matrix
     * var result = {
     *     unitary : new Cesium.Matrix3(),
     *     diagonal : new Cesium.Matrix3()
     * };
     * Cesium.Matrix3.computeEigenDecomposition(a, result);
     *
     * var unitaryTranspose = Cesium.Matrix3.transpose(result.unitary, new Cesium.Matrix3());
     * var b = Cesium.Matrix3.multiply(result.unitary, result.diagonal, new Cesium.Matrix3());
     * Cesium.Matrix3.multiply(b, unitaryTranspose, b); // b is now equal to a
     *
     * var lambda = Cesium.Matrix3.getColumn(result.diagonal, 0, new Cesium.Cartesian3()).x;  // first eigenvalue
     * var v = Cesium.Matrix3.getColumn(result.unitary, 0, new Cesium.Cartesian3());          // first eigenvector
     * var c = Cesium.Cartesian3.multiplyByScalar(v, lambda, new Cesium.Cartesian3());        // equal to Cesium.Matrix3.multiplyByVector(a, v)
     */
    Matrix3.computeEigenDecomposition = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        
        // This routine was created based upon Matrix Computations, 3rd ed., by Golub and Van Loan,
        // section 8.4.3 The Classical Jacobi Algorithm

        var tolerance = CesiumMath.EPSILON20;
        var maxSweeps = 10;

        var count = 0;
        var sweep = 0;

        if (!defined(result)) {
            result = {};
        }

        var unitaryMatrix = result.unitary = Matrix3.clone(Matrix3.IDENTITY, result.unitary);
        var diagMatrix = result.diagonal = Matrix3.clone(matrix, result.diagonal);

        var epsilon = tolerance * computeFrobeniusNorm(diagMatrix);

        while (sweep < maxSweeps && offDiagonalFrobeniusNorm(diagMatrix) > epsilon) {
            shurDecomposition(diagMatrix, jMatrix);
            Matrix3.transpose(jMatrix, jMatrixTranspose);
            Matrix3.multiply(diagMatrix, jMatrix, diagMatrix);
            Matrix3.multiply(jMatrixTranspose, diagMatrix, diagMatrix);
            Matrix3.multiply(unitaryMatrix, jMatrix, unitaryMatrix);

            if (++count > 2) {
                ++sweep;
                count = 0;
            }
        }

        return result;
    };

    /**
     * Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
     *
     * @param {Matrix3} matrix The matrix with signed elements.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     */
    Matrix3.abs = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = Math.abs(matrix[0]);
        result[1] = Math.abs(matrix[1]);
        result[2] = Math.abs(matrix[2]);
        result[3] = Math.abs(matrix[3]);
        result[4] = Math.abs(matrix[4]);
        result[5] = Math.abs(matrix[5]);
        result[6] = Math.abs(matrix[6]);
        result[7] = Math.abs(matrix[7]);
        result[8] = Math.abs(matrix[8]);

        return result;
    };

    /**
     * Computes the determinant of the provided matrix.
     *
     * @param {Matrix3} matrix The matrix to use.
     * @returns {Number} The value of the determinant of the matrix.
     */
    Matrix3.determinant = function(matrix) {
                Check.typeOf.object('matrix', matrix);
        
        var m11 = matrix[0];
        var m21 = matrix[3];
        var m31 = matrix[6];
        var m12 = matrix[1];
        var m22 = matrix[4];
        var m32 = matrix[7];
        var m13 = matrix[2];
        var m23 = matrix[5];
        var m33 = matrix[8];

        return m11 * (m22 * m33 - m23 * m32) + m12 * (m23 * m31 - m21 * m33) + m13 * (m21 * m32 - m22 * m31);
    };

    /**
     * Computes the inverse of the provided matrix.
     *
     * @param {Matrix3} matrix The matrix to invert.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     *
     * @exception {DeveloperError} matrix is not invertible.
     */
    Matrix3.inverse = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        var m11 = matrix[0];
        var m21 = matrix[1];
        var m31 = matrix[2];
        var m12 = matrix[3];
        var m22 = matrix[4];
        var m32 = matrix[5];
        var m13 = matrix[6];
        var m23 = matrix[7];
        var m33 = matrix[8];

        var determinant = Matrix3.determinant(matrix);

                if (Math.abs(determinant) <= CesiumMath.EPSILON15) {
            throw new DeveloperError('matrix is not invertible');
        }
        
        result[0] = m22 * m33 - m23 * m32;
        result[1] = m23 * m31 - m21 * m33;
        result[2] = m21 * m32 - m22 * m31;
        result[3] = m13 * m32 - m12 * m33;
        result[4] = m11 * m33 - m13 * m31;
        result[5] = m12 * m31 - m11 * m32;
        result[6] = m12 * m23 - m13 * m22;
        result[7] = m13 * m21 - m11 * m23;
        result[8] = m11 * m22 - m12 * m21;

       var scale = 1.0 / determinant;
       return Matrix3.multiplyByScalar(result, scale, result);
    };

    /**
     * Compares the provided matrices componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Matrix3} [left] The first matrix.
     * @param {Matrix3} [right] The second matrix.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Matrix3.equals = function(left, right) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                left[0] === right[0] &&
                left[1] === right[1] &&
                left[2] === right[2] &&
                left[3] === right[3] &&
                left[4] === right[4] &&
                left[5] === right[5] &&
                left[6] === right[6] &&
                left[7] === right[7] &&
                left[8] === right[8]);
    };

    /**
     * Compares the provided matrices componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Matrix3} [left] The first matrix.
     * @param {Matrix3} [right] The second matrix.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Matrix3.equalsEpsilon = function(left, right, epsilon) {
                Check.typeOf.number('epsilon', epsilon);
        
        return (left === right) ||
                (defined(left) &&
                defined(right) &&
                Math.abs(left[0] - right[0]) <= epsilon &&
                Math.abs(left[1] - right[1]) <= epsilon &&
                Math.abs(left[2] - right[2]) <= epsilon &&
                Math.abs(left[3] - right[3]) <= epsilon &&
                Math.abs(left[4] - right[4]) <= epsilon &&
                Math.abs(left[5] - right[5]) <= epsilon &&
                Math.abs(left[6] - right[6]) <= epsilon &&
                Math.abs(left[7] - right[7]) <= epsilon &&
                Math.abs(left[8] - right[8]) <= epsilon);
    };

    /**
     * An immutable Matrix3 instance initialized to the identity matrix.
     *
     * @type {Matrix3}
     * @constant
     */
    Matrix3.IDENTITY = freezeObject(new Matrix3(1.0, 0.0, 0.0,
                                                0.0, 1.0, 0.0,
                                                0.0, 0.0, 1.0));

    /**
     * An immutable Matrix3 instance initialized to the zero matrix.
     *
     * @type {Matrix3}
     * @constant
     */
    Matrix3.ZERO = freezeObject(new Matrix3(0.0, 0.0, 0.0,
                                            0.0, 0.0, 0.0,
                                            0.0, 0.0, 0.0));

    /**
     * The index into Matrix3 for column 0, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN0ROW0 = 0;

    /**
     * The index into Matrix3 for column 0, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN0ROW1 = 1;

    /**
     * The index into Matrix3 for column 0, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN0ROW2 = 2;

    /**
     * The index into Matrix3 for column 1, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN1ROW0 = 3;

    /**
     * The index into Matrix3 for column 1, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN1ROW1 = 4;

    /**
     * The index into Matrix3 for column 1, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN1ROW2 = 5;

    /**
     * The index into Matrix3 for column 2, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN2ROW0 = 6;

    /**
     * The index into Matrix3 for column 2, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN2ROW1 = 7;

    /**
     * The index into Matrix3 for column 2, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix3.COLUMN2ROW2 = 8;

    defineProperties(Matrix3.prototype, {
        /**
         * Gets the number of items in the collection.
         * @memberof Matrix3.prototype
         *
         * @type {Number}
         */
        length : {
            get : function() {
                return Matrix3.packedLength;
            }
        }
    });

    /**
     * Duplicates the provided Matrix3 instance.
     *
     * @param {Matrix3} [result] The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter or a new Matrix3 instance if one was not provided.
     */
    Matrix3.prototype.clone = function(result) {
        return Matrix3.clone(this, result);
    };

    /**
     * Compares this matrix to the provided matrix componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Matrix3} [right] The right hand side matrix.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Matrix3.prototype.equals = function(right) {
        return Matrix3.equals(this, right);
    };

    /**
     * @private
     */
    Matrix3.equalsArray = function(matrix, array, offset) {
        return matrix[0] === array[offset] &&
               matrix[1] === array[offset + 1] &&
               matrix[2] === array[offset + 2] &&
               matrix[3] === array[offset + 3] &&
               matrix[4] === array[offset + 4] &&
               matrix[5] === array[offset + 5] &&
               matrix[6] === array[offset + 6] &&
               matrix[7] === array[offset + 7] &&
               matrix[8] === array[offset + 8];
    };

    /**
     * Compares this matrix to the provided matrix componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Matrix3} [right] The right hand side matrix.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    Matrix3.prototype.equalsEpsilon = function(right, epsilon) {
        return Matrix3.equalsEpsilon(this, right, epsilon);
    };

    /**
     * Creates a string representing this Matrix with each row being
     * on a separate line and in the format '(column0, column1, column2)'.
     *
     * @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1, column2)'.
     */
    Matrix3.prototype.toString = function() {
        return '(' + this[0] + ', ' + this[3] + ', ' + this[6] + ')\n' +
               '(' + this[1] + ', ' + this[4] + ', ' + this[7] + ')\n' +
               '(' + this[2] + ', ' + this[5] + ', ' + this[8] + ')';
    };

    return Matrix3;
});

/*global define*/
define('Core/Cartesian4',[
        './Check',
        './defaultValue',
        './defined',
        './DeveloperError',
        './freezeObject',
        './Math'
    ], function(
        Check,
        defaultValue,
        defined,
        DeveloperError,
        freezeObject,
        CesiumMath) {
    'use strict';

    /**
     * A 4D Cartesian point.
     * @alias Cartesian4
     * @constructor
     *
     * @param {Number} [x=0.0] The X component.
     * @param {Number} [y=0.0] The Y component.
     * @param {Number} [z=0.0] The Z component.
     * @param {Number} [w=0.0] The W component.
     *
     * @see Cartesian2
     * @see Cartesian3
     * @see Packable
     */
    function Cartesian4(x, y, z, w) {
        /**
         * The X component.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The Y component.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The Z component.
         * @type {Number}
         * @default 0.0
         */
        this.z = defaultValue(z, 0.0);

        /**
         * The W component.
         * @type {Number}
         * @default 0.0
         */
        this.w = defaultValue(w, 0.0);
    }

    /**
     * Creates a Cartesian4 instance from x, y, z and w coordinates.
     *
     * @param {Number} x The x coordinate.
     * @param {Number} y The y coordinate.
     * @param {Number} z The z coordinate.
     * @param {Number} w The w coordinate.
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
     */
    Cartesian4.fromElements = function(x, y, z, w, result) {
        if (!defined(result)) {
            return new Cartesian4(x, y, z, w);
        }

        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    /**
     * Creates a Cartesian4 instance from a {@link Color}. <code>red</code>, <code>green</code>, <code>blue</code>,
     * and <code>alpha</code> map to <code>x</code>, <code>y</code>, <code>z</code>, and <code>w</code>, respectively.
     *
     * @param {Color} color The source color.
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
     */
    Cartesian4.fromColor = function(color, result) {
                Check.typeOf.object('color', color);
                if (!defined(result)) {
            return new Cartesian4(color.red, color.green, color.blue, color.alpha);
        }

        result.x = color.red;
        result.y = color.green;
        result.z = color.blue;
        result.w = color.alpha;
        return result;
    };

    /**
     * Duplicates a Cartesian4 instance.
     *
     * @param {Cartesian4} cartesian The Cartesian to duplicate.
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided. (Returns undefined if cartesian is undefined)
     */
    Cartesian4.clone = function(cartesian, result) {
        if (!defined(cartesian)) {
            return undefined;
        }

        if (!defined(result)) {
            return new Cartesian4(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
        }

        result.x = cartesian.x;
        result.y = cartesian.y;
        result.z = cartesian.z;
        result.w = cartesian.w;
        return result;
    };


    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Cartesian4.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Cartesian4} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Cartesian4.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex++] = value.y;
        array[startingIndex++] = value.z;
        array[startingIndex] = value.w;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Cartesian4} [result] The object into which to store the result.
     * @returns {Cartesian4}  The modified result parameter or a new Cartesian4 instance if one was not provided.
     */
    Cartesian4.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Cartesian4();
        }
        result.x = array[startingIndex++];
        result.y = array[startingIndex++];
        result.z = array[startingIndex++];
        result.w = array[startingIndex];
        return result;
    };

    /**
     * Flattens an array of Cartesian4s into and array of components.
     *
     * @param {Cartesian4[]} array The array of cartesians to pack.
     * @param {Number[]} result The array onto which to store the result.
     * @returns {Number[]} The packed array.
     */
    Cartesian4.packArray = function(array, result) {
                Check.defined('array', array);
        
        var length = array.length;
        if (!defined(result)) {
            result = new Array(length * 4);
        } else {
            result.length = length * 4;
        }

        for (var i = 0; i < length; ++i) {
            Cartesian4.pack(array[i], result, i * 4);
        }
        return result;
    };

    /**
     * Unpacks an array of cartesian components into and array of Cartesian4s.
     *
     * @param {Number[]} array The array of components to unpack.
     * @param {Cartesian4[]} result The array onto which to store the result.
     * @returns {Cartesian4[]} The unpacked array.
     */
    Cartesian4.unpackArray = function(array, result) {
                Check.defined('array', array);
        
        var length = array.length;
        if (!defined(result)) {
            result = new Array(length / 4);
        } else {
            result.length = length / 4;
        }

        for (var i = 0; i < length; i += 4) {
            var index = i / 4;
            result[index] = Cartesian4.unpack(array, i, result[index]);
        }
        return result;
    };

    /**
     * Creates a Cartesian4 from four consecutive elements in an array.
     * @function
     *
     * @param {Number[]} array The array whose four consecutive elements correspond to the x, y, z, and w components, respectively.
     * @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to the x component.
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4}  The modified result parameter or a new Cartesian4 instance if one was not provided.
     *
     * @example
     * // Create a Cartesian4 with (1.0, 2.0, 3.0, 4.0)
     * var v = [1.0, 2.0, 3.0, 4.0];
     * var p = Cesium.Cartesian4.fromArray(v);
     *
     * // Create a Cartesian4 with (1.0, 2.0, 3.0, 4.0) using an offset into an array
     * var v2 = [0.0, 0.0, 1.0, 2.0, 3.0, 4.0];
     * var p2 = Cesium.Cartesian4.fromArray(v2, 2);
     */
    Cartesian4.fromArray = Cartesian4.unpack;

    /**
     * Computes the value of the maximum component for the supplied Cartesian.
     *
     * @param {Cartesian4} cartesian The cartesian to use.
     * @returns {Number} The value of the maximum component.
     */
    Cartesian4.maximumComponent = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return Math.max(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
    };

    /**
     * Computes the value of the minimum component for the supplied Cartesian.
     *
     * @param {Cartesian4} cartesian The cartesian to use.
     * @returns {Number} The value of the minimum component.
     */
    Cartesian4.minimumComponent = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return Math.min(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
    };

    /**
     * Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
     *
     * @param {Cartesian4} first A cartesian to compare.
     * @param {Cartesian4} second A cartesian to compare.
     * @param {Cartesian4} result The object into which to store the result.
     * @returns {Cartesian4} A cartesian with the minimum components.
     */
    Cartesian4.minimumByComponent = function(first, second, result) {
                Check.typeOf.object('first', first);
        Check.typeOf.object('second', second);
        Check.typeOf.object('result', result);
        
        result.x = Math.min(first.x, second.x);
        result.y = Math.min(first.y, second.y);
        result.z = Math.min(first.z, second.z);
        result.w = Math.min(first.w, second.w);

        return result;
    };

    /**
     * Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
     *
     * @param {Cartesian4} first A cartesian to compare.
     * @param {Cartesian4} second A cartesian to compare.
     * @param {Cartesian4} result The object into which to store the result.
     * @returns {Cartesian4} A cartesian with the maximum components.
     */
    Cartesian4.maximumByComponent = function(first, second, result) {
                Check.typeOf.object('first', first);
        Check.typeOf.object('second', second);
        Check.typeOf.object('result', result);
        
        result.x = Math.max(first.x, second.x);
        result.y = Math.max(first.y, second.y);
        result.z = Math.max(first.z, second.z);
        result.w = Math.max(first.w, second.w);

        return result;
    };

    /**
     * Computes the provided Cartesian's squared magnitude.
     *
     * @param {Cartesian4} cartesian The Cartesian instance whose squared magnitude is to be computed.
     * @returns {Number} The squared magnitude.
     */
    Cartesian4.magnitudeSquared = function(cartesian) {
                Check.typeOf.object('cartesian', cartesian);
        
        return cartesian.x * cartesian.x + cartesian.y * cartesian.y + cartesian.z * cartesian.z + cartesian.w * cartesian.w;
    };

    /**
     * Computes the Cartesian's magnitude (length).
     *
     * @param {Cartesian4} cartesian The Cartesian instance whose magnitude is to be computed.
     * @returns {Number} The magnitude.
     */
    Cartesian4.magnitude = function(cartesian) {
        return Math.sqrt(Cartesian4.magnitudeSquared(cartesian));
    };

    var distanceScratch = new Cartesian4();

    /**
     * Computes the 4-space distance between two points.
     *
     * @param {Cartesian4} left The first point to compute the distance from.
     * @param {Cartesian4} right The second point to compute the distance to.
     * @returns {Number} The distance between two points.
     *
     * @example
     * // Returns 1.0
     * var d = Cesium.Cartesian4.distance(
     *   new Cesium.Cartesian4(1.0, 0.0, 0.0, 0.0),
     *   new Cesium.Cartesian4(2.0, 0.0, 0.0, 0.0));
     */
    Cartesian4.distance = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        Cartesian4.subtract(left, right, distanceScratch);
        return Cartesian4.magnitude(distanceScratch);
    };

    /**
     * Computes the squared distance between two points.  Comparing squared distances
     * using this function is more efficient than comparing distances using {@link Cartesian4#distance}.
     *
     * @param {Cartesian4} left The first point to compute the distance from.
     * @param {Cartesian4} right The second point to compute the distance to.
     * @returns {Number} The distance between two points.
     *
     * @example
     * // Returns 4.0, not 2.0
     * var d = Cesium.Cartesian4.distance(
     *   new Cesium.Cartesian4(1.0, 0.0, 0.0, 0.0),
     *   new Cesium.Cartesian4(3.0, 0.0, 0.0, 0.0));
     */
    Cartesian4.distanceSquared = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        Cartesian4.subtract(left, right, distanceScratch);
        return Cartesian4.magnitudeSquared(distanceScratch);
    };

    /**
     * Computes the normalized form of the supplied Cartesian.
     *
     * @param {Cartesian4} cartesian The Cartesian to be normalized.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.normalize = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var magnitude = Cartesian4.magnitude(cartesian);

        result.x = cartesian.x / magnitude;
        result.y = cartesian.y / magnitude;
        result.z = cartesian.z / magnitude;
        result.w = cartesian.w / magnitude;

                if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z) || isNaN(result.w)) {
            throw new DeveloperError('normalized result is not a number');
        }
        
        return result;
    };

    /**
     * Computes the dot (scalar) product of two Cartesians.
     *
     * @param {Cartesian4} left The first Cartesian.
     * @param {Cartesian4} right The second Cartesian.
     * @returns {Number} The dot product.
     */
    Cartesian4.dot = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        return left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w;
    };

    /**
     * Computes the componentwise product of two Cartesians.
     *
     * @param {Cartesian4} left The first Cartesian.
     * @param {Cartesian4} right The second Cartesian.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.multiplyComponents = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x * right.x;
        result.y = left.y * right.y;
        result.z = left.z * right.z;
        result.w = left.w * right.w;
        return result;
    };

    /**
     * Computes the componentwise quotient of two Cartesians.
     *
     * @param {Cartesian4} left The first Cartesian.
     * @param {Cartesian4} right The second Cartesian.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.divideComponents = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x / right.x;
        result.y = left.y / right.y;
        result.z = left.z / right.z;
        result.w = left.w / right.w;
        return result;
    };

    /**
     * Computes the componentwise sum of two Cartesians.
     *
     * @param {Cartesian4} left The first Cartesian.
     * @param {Cartesian4} right The second Cartesian.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x + right.x;
        result.y = left.y + right.y;
        result.z = left.z + right.z;
        result.w = left.w + right.w;
        return result;
    };

    /**
     * Computes the componentwise difference of two Cartesians.
     *
     * @param {Cartesian4} left The first Cartesian.
     * @param {Cartesian4} right The second Cartesian.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x - right.x;
        result.y = left.y - right.y;
        result.z = left.z - right.z;
        result.w = left.w - right.w;
        return result;
    };

    /**
     * Multiplies the provided Cartesian componentwise by the provided scalar.
     *
     * @param {Cartesian4} cartesian The Cartesian to be scaled.
     * @param {Number} scalar The scalar to multiply with.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.multiplyByScalar = function(cartesian, scalar, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = cartesian.x * scalar;
        result.y = cartesian.y * scalar;
        result.z = cartesian.z * scalar;
        result.w = cartesian.w * scalar;
        return result;
    };

    /**
     * Divides the provided Cartesian componentwise by the provided scalar.
     *
     * @param {Cartesian4} cartesian The Cartesian to be divided.
     * @param {Number} scalar The scalar to divide by.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.divideByScalar = function(cartesian, scalar, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = cartesian.x / scalar;
        result.y = cartesian.y / scalar;
        result.z = cartesian.z / scalar;
        result.w = cartesian.w / scalar;
        return result;
    };

    /**
     * Negates the provided Cartesian.
     *
     * @param {Cartesian4} cartesian The Cartesian to be negated.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.negate = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result.x = -cartesian.x;
        result.y = -cartesian.y;
        result.z = -cartesian.z;
        result.w = -cartesian.w;
        return result;
    };

    /**
     * Computes the absolute value of the provided Cartesian.
     *
     * @param {Cartesian4} cartesian The Cartesian whose absolute value is to be computed.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.abs = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result.x = Math.abs(cartesian.x);
        result.y = Math.abs(cartesian.y);
        result.z = Math.abs(cartesian.z);
        result.w = Math.abs(cartesian.w);
        return result;
    };

    var lerpScratch = new Cartesian4();
    /**
     * Computes the linear interpolation or extrapolation at t using the provided cartesians.
     *
     * @param {Cartesian4} start The value corresponding to t at 0.0.
     * @param {Cartesian4}end The value corresponding to t at 1.0.
     * @param {Number} t The point along t at which to interpolate.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Cartesian4.lerp = function(start, end, t, result) {
                Check.typeOf.object('start', start);
        Check.typeOf.object('end', end);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        Cartesian4.multiplyByScalar(end, t, lerpScratch);
        result = Cartesian4.multiplyByScalar(start, 1.0 - t, result);
        return Cartesian4.add(lerpScratch, result, result);
    };

    var mostOrthogonalAxisScratch = new Cartesian4();
    /**
     * Returns the axis that is most orthogonal to the provided Cartesian.
     *
     * @param {Cartesian4} cartesian The Cartesian on which to find the most orthogonal axis.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The most orthogonal axis.
     */
    Cartesian4.mostOrthogonalAxis = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var f = Cartesian4.normalize(cartesian, mostOrthogonalAxisScratch);
        Cartesian4.abs(f, f);

        if (f.x <= f.y) {
            if (f.x <= f.z) {
                if (f.x <= f.w) {
                    result = Cartesian4.clone(Cartesian4.UNIT_X, result);
                } else {
                    result = Cartesian4.clone(Cartesian4.UNIT_W, result);
                }
            } else if (f.z <= f.w) {
                result = Cartesian4.clone(Cartesian4.UNIT_Z, result);
            } else {
                result = Cartesian4.clone(Cartesian4.UNIT_W, result);
            }
        } else if (f.y <= f.z) {
            if (f.y <= f.w) {
                result = Cartesian4.clone(Cartesian4.UNIT_Y, result);
            } else {
                result = Cartesian4.clone(Cartesian4.UNIT_W, result);
            }
        } else if (f.z <= f.w) {
            result = Cartesian4.clone(Cartesian4.UNIT_Z, result);
        } else {
            result = Cartesian4.clone(Cartesian4.UNIT_W, result);
        }

        return result;
    };

    /**
     * Compares the provided Cartesians componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartesian4} [left] The first Cartesian.
     * @param {Cartesian4} [right] The second Cartesian.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Cartesian4.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.z === right.z) &&
                (left.w === right.w));
    };

    /**
     * @private
     */
    Cartesian4.equalsArray = function(cartesian, array, offset) {
        return cartesian.x === array[offset] &&
               cartesian.y === array[offset + 1] &&
               cartesian.z === array[offset + 2] &&
               cartesian.w === array[offset + 3];
    };

    /**
     * Compares the provided Cartesians componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {Cartesian4} [left] The first Cartesian.
     * @param {Cartesian4} [right] The second Cartesian.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartesian4.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                CesiumMath.equalsEpsilon(left.x, right.x, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.y, right.y, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.z, right.z, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.w, right.w, relativeEpsilon, absoluteEpsilon));
    };

    /**
     * An immutable Cartesian4 instance initialized to (0.0, 0.0, 0.0, 0.0).
     *
     * @type {Cartesian4}
     * @constant
     */
    Cartesian4.ZERO = freezeObject(new Cartesian4(0.0, 0.0, 0.0, 0.0));

    /**
     * An immutable Cartesian4 instance initialized to (1.0, 0.0, 0.0, 0.0).
     *
     * @type {Cartesian4}
     * @constant
     */
    Cartesian4.UNIT_X = freezeObject(new Cartesian4(1.0, 0.0, 0.0, 0.0));

    /**
     * An immutable Cartesian4 instance initialized to (0.0, 1.0, 0.0, 0.0).
     *
     * @type {Cartesian4}
     * @constant
     */
    Cartesian4.UNIT_Y = freezeObject(new Cartesian4(0.0, 1.0, 0.0, 0.0));

    /**
     * An immutable Cartesian4 instance initialized to (0.0, 0.0, 1.0, 0.0).
     *
     * @type {Cartesian4}
     * @constant
     */
    Cartesian4.UNIT_Z = freezeObject(new Cartesian4(0.0, 0.0, 1.0, 0.0));

    /**
     * An immutable Cartesian4 instance initialized to (0.0, 0.0, 0.0, 1.0).
     *
     * @type {Cartesian4}
     * @constant
     */
    Cartesian4.UNIT_W = freezeObject(new Cartesian4(0.0, 0.0, 0.0, 1.0));

    /**
     * Duplicates this Cartesian4 instance.
     *
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter or a new Cartesian4 instance if one was not provided.
     */
    Cartesian4.prototype.clone = function(result) {
        return Cartesian4.clone(this, result);
    };

    /**
     * Compares this Cartesian against the provided Cartesian componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Cartesian4} [right] The right hand side Cartesian.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Cartesian4.prototype.equals = function(right) {
        return Cartesian4.equals(this, right);
    };

    /**
     * Compares this Cartesian against the provided Cartesian componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {Cartesian4} [right] The right hand side Cartesian.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    Cartesian4.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
        return Cartesian4.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
    };

    /**
     * Creates a string representing this Cartesian in the format '(x, y)'.
     *
     * @returns {String} A string representing the provided Cartesian in the format '(x, y)'.
     */
    Cartesian4.prototype.toString = function() {
        return '(' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + ')';
    };

    return Cartesian4;
});

/*global define*/
define('Core/RuntimeError',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * Constructs an exception object that is thrown due to an error that can occur at runtime, e.g.,
     * out of memory, could not compile shader, etc.  If a function may throw this
     * exception, the calling code should be prepared to catch it.
     * <br /><br />
     * On the other hand, a {@link DeveloperError} indicates an exception due
     * to a developer error, e.g., invalid argument, that usually indicates a bug in the
     * calling code.
     *
     * @alias RuntimeError
     * @constructor
     * @extends Error
     *
     * @param {String} [message] The error message for this exception.
     *
     * @see DeveloperError
     */
    function RuntimeError(message) {
        /**
         * 'RuntimeError' indicating that this exception was thrown due to a runtime error.
         * @type {String}
         * @readonly
         */
        this.name = 'RuntimeError';

        /**
         * The explanation for why this exception was thrown.
         * @type {String}
         * @readonly
         */
        this.message = message;

        //Browsers such as IE don't have a stack property until you actually throw the error.
        var stack;
        try {
            throw new Error();
        } catch (e) {
            stack = e.stack;
        }

        /**
         * The stack trace of this exception, if available.
         * @type {String}
         * @readonly
         */
        this.stack = stack;
    }

    if (defined(Object.create)) {
        RuntimeError.prototype = Object.create(Error.prototype);
        RuntimeError.prototype.constructor = RuntimeError;
    }

    RuntimeError.prototype.toString = function() {
        var str = this.name + ': ' + this.message;

        if (defined(this.stack)) {
            str += '\n' + this.stack.toString();
        }

        return str;
    };

    return RuntimeError;
});

/*global define*/
define('Core/Matrix4',[
        './Cartesian3',
        './Cartesian4',
        './Check',
        './defaultValue',
        './defined',
        './defineProperties',
        './freezeObject',
        './Math',
        './Matrix3',
        './RuntimeError'
    ], function(
        Cartesian3,
        Cartesian4,
        Check,
        defaultValue,
        defined,
        defineProperties,
        freezeObject,
        CesiumMath,
        Matrix3,
        RuntimeError) {
    'use strict';

    /**
     * A 4x4 matrix, indexable as a column-major order array.
     * Constructor parameters are in row-major order for code readability.
     * @alias Matrix4
     * @constructor
     *
     * @param {Number} [column0Row0=0.0] The value for column 0, row 0.
     * @param {Number} [column1Row0=0.0] The value for column 1, row 0.
     * @param {Number} [column2Row0=0.0] The value for column 2, row 0.
     * @param {Number} [column3Row0=0.0] The value for column 3, row 0.
     * @param {Number} [column0Row1=0.0] The value for column 0, row 1.
     * @param {Number} [column1Row1=0.0] The value for column 1, row 1.
     * @param {Number} [column2Row1=0.0] The value for column 2, row 1.
     * @param {Number} [column3Row1=0.0] The value for column 3, row 1.
     * @param {Number} [column0Row2=0.0] The value for column 0, row 2.
     * @param {Number} [column1Row2=0.0] The value for column 1, row 2.
     * @param {Number} [column2Row2=0.0] The value for column 2, row 2.
     * @param {Number} [column3Row2=0.0] The value for column 3, row 2.
     * @param {Number} [column0Row3=0.0] The value for column 0, row 3.
     * @param {Number} [column1Row3=0.0] The value for column 1, row 3.
     * @param {Number} [column2Row3=0.0] The value for column 2, row 3.
     * @param {Number} [column3Row3=0.0] The value for column 3, row 3.
     *
     * @see Matrix4.fromColumnMajorArray
     * @see Matrix4.fromRowMajorArray
     * @see Matrix4.fromRotationTranslation
     * @see Matrix4.fromTranslationRotationScale
     * @see Matrix4.fromTranslationQuaternionRotationScale
     * @see Matrix4.fromTranslation
     * @see Matrix4.fromScale
     * @see Matrix4.fromUniformScale
     * @see Matrix4.fromCamera
     * @see Matrix4.computePerspectiveFieldOfView
     * @see Matrix4.computeOrthographicOffCenter
     * @see Matrix4.computePerspectiveOffCenter
     * @see Matrix4.computeInfinitePerspectiveOffCenter
     * @see Matrix4.computeViewportTransformation
     * @see Matrix4.computeView
     * @see Matrix2
     * @see Matrix3
     * @see Packable
     */
    function Matrix4(column0Row0, column1Row0, column2Row0, column3Row0,
                           column0Row1, column1Row1, column2Row1, column3Row1,
                           column0Row2, column1Row2, column2Row2, column3Row2,
                           column0Row3, column1Row3, column2Row3, column3Row3) {
        this[0] = defaultValue(column0Row0, 0.0);
        this[1] = defaultValue(column0Row1, 0.0);
        this[2] = defaultValue(column0Row2, 0.0);
        this[3] = defaultValue(column0Row3, 0.0);
        this[4] = defaultValue(column1Row0, 0.0);
        this[5] = defaultValue(column1Row1, 0.0);
        this[6] = defaultValue(column1Row2, 0.0);
        this[7] = defaultValue(column1Row3, 0.0);
        this[8] = defaultValue(column2Row0, 0.0);
        this[9] = defaultValue(column2Row1, 0.0);
        this[10] = defaultValue(column2Row2, 0.0);
        this[11] = defaultValue(column2Row3, 0.0);
        this[12] = defaultValue(column3Row0, 0.0);
        this[13] = defaultValue(column3Row1, 0.0);
        this[14] = defaultValue(column3Row2, 0.0);
        this[15] = defaultValue(column3Row3, 0.0);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Matrix4.packedLength = 16;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Matrix4} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Matrix4.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value[0];
        array[startingIndex++] = value[1];
        array[startingIndex++] = value[2];
        array[startingIndex++] = value[3];
        array[startingIndex++] = value[4];
        array[startingIndex++] = value[5];
        array[startingIndex++] = value[6];
        array[startingIndex++] = value[7];
        array[startingIndex++] = value[8];
        array[startingIndex++] = value[9];
        array[startingIndex++] = value[10];
        array[startingIndex++] = value[11];
        array[startingIndex++] = value[12];
        array[startingIndex++] = value[13];
        array[startingIndex++] = value[14];
        array[startingIndex] = value[15];

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Matrix4} [result] The object into which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
     */
    Matrix4.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Matrix4();
        }

        result[0] = array[startingIndex++];
        result[1] = array[startingIndex++];
        result[2] = array[startingIndex++];
        result[3] = array[startingIndex++];
        result[4] = array[startingIndex++];
        result[5] = array[startingIndex++];
        result[6] = array[startingIndex++];
        result[7] = array[startingIndex++];
        result[8] = array[startingIndex++];
        result[9] = array[startingIndex++];
        result[10] = array[startingIndex++];
        result[11] = array[startingIndex++];
        result[12] = array[startingIndex++];
        result[13] = array[startingIndex++];
        result[14] = array[startingIndex++];
        result[15] = array[startingIndex];
        return result;
    };

    /**
     * Duplicates a Matrix4 instance.
     *
     * @param {Matrix4} matrix The matrix to duplicate.
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided. (Returns undefined if matrix is undefined)
     */
    Matrix4.clone = function(matrix, result) {
        if (!defined(matrix)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Matrix4(matrix[0], matrix[4], matrix[8], matrix[12],
                               matrix[1], matrix[5], matrix[9], matrix[13],
                               matrix[2], matrix[6], matrix[10], matrix[14],
                               matrix[3], matrix[7], matrix[11], matrix[15]);
        }
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        result[4] = matrix[4];
        result[5] = matrix[5];
        result[6] = matrix[6];
        result[7] = matrix[7];
        result[8] = matrix[8];
        result[9] = matrix[9];
        result[10] = matrix[10];
        result[11] = matrix[11];
        result[12] = matrix[12];
        result[13] = matrix[13];
        result[14] = matrix[14];
        result[15] = matrix[15];
        return result;
    };

    /**
     * Creates a Matrix4 from 16 consecutive elements in an array.
     * @function
     *
     * @param {Number[]} array The array whose 16 consecutive elements correspond to the positions of the matrix.  Assumes column-major order.
     * @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to first column first row position in the matrix.
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
     *
     * @example
     * // Create the Matrix4:
     * // [1.0, 2.0, 3.0, 4.0]
     * // [1.0, 2.0, 3.0, 4.0]
     * // [1.0, 2.0, 3.0, 4.0]
     * // [1.0, 2.0, 3.0, 4.0]
     *
     * var v = [1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 4.0];
     * var m = Cesium.Matrix4.fromArray(v);
     *
     * // Create same Matrix4 with using an offset into an array
     * var v2 = [0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 4.0];
     * var m2 = Cesium.Matrix4.fromArray(v2, 2);
     */
    Matrix4.fromArray = Matrix4.unpack;

    /**
     * Computes a Matrix4 instance from a column-major order array.
     *
     * @param {Number[]} values The column-major order array.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     */
    Matrix4.fromColumnMajorArray = function(values, result) {
                Check.defined('values', values);
        
        return Matrix4.clone(values, result);
    };

    /**
     * Computes a Matrix4 instance from a row-major order array.
     * The resulting matrix will be in column-major order.
     *
     * @param {Number[]} values The row-major order array.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     */
    Matrix4.fromRowMajorArray = function(values, result) {
                Check.defined('values', values);
        
        if (!defined(result)) {
            return new Matrix4(values[0], values[1], values[2], values[3],
                               values[4], values[5], values[6], values[7],
                               values[8], values[9], values[10], values[11],
                               values[12], values[13], values[14], values[15]);
        }
        result[0] = values[0];
        result[1] = values[4];
        result[2] = values[8];
        result[3] = values[12];
        result[4] = values[1];
        result[5] = values[5];
        result[6] = values[9];
        result[7] = values[13];
        result[8] = values[2];
        result[9] = values[6];
        result[10] = values[10];
        result[11] = values[14];
        result[12] = values[3];
        result[13] = values[7];
        result[14] = values[11];
        result[15] = values[15];
        return result;
    };

    /**
     * Computes a Matrix4 instance from a Matrix3 representing the rotation
     * and a Cartesian3 representing the translation.
     *
     * @param {Matrix3} rotation The upper left portion of the matrix representing the rotation.
     * @param {Cartesian3} [translation=Cartesian3.ZERO] The upper right portion of the matrix representing the translation.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     */
    Matrix4.fromRotationTranslation = function(rotation, translation, result) {
                Check.typeOf.object('rotation', rotation);
        
        translation = defaultValue(translation, Cartesian3.ZERO);

        if (!defined(result)) {
            return new Matrix4(rotation[0], rotation[3], rotation[6], translation.x,
                               rotation[1], rotation[4], rotation[7], translation.y,
                               rotation[2], rotation[5], rotation[8], translation.z,
                                       0.0,         0.0,         0.0,           1.0);
        }

        result[0] = rotation[0];
        result[1] = rotation[1];
        result[2] = rotation[2];
        result[3] = 0.0;
        result[4] = rotation[3];
        result[5] = rotation[4];
        result[6] = rotation[5];
        result[7] = 0.0;
        result[8] = rotation[6];
        result[9] = rotation[7];
        result[10] = rotation[8];
        result[11] = 0.0;
        result[12] = translation.x;
        result[13] = translation.y;
        result[14] = translation.z;
        result[15] = 1.0;
        return result;
    };

    /**
     * Computes a Matrix4 instance from a translation, rotation, and scale (TRS)
     * representation with the rotation represented as a quaternion.
     *
     * @param {Cartesian3} translation The translation transformation.
     * @param {Quaternion} rotation The rotation transformation.
     * @param {Cartesian3} scale The non-uniform scale transformation.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     *
     * @example
     * var result = Cesium.Matrix4.fromTranslationQuaternionRotationScale(
     *   new Cesium.Cartesian3(1.0, 2.0, 3.0), // translation
     *   Cesium.Quaternion.IDENTITY,           // rotation
     *   new Cesium.Cartesian3(7.0, 8.0, 9.0), // scale
     *   result);
     */
    Matrix4.fromTranslationQuaternionRotationScale = function(translation, rotation, scale, result) {
                Check.typeOf.object('translation', translation);
        Check.typeOf.object('rotation', rotation);
        Check.typeOf.object('scale', scale);
        
        if (!defined(result)) {
            result = new Matrix4();
        }

        var scaleX = scale.x;
        var scaleY = scale.y;
        var scaleZ = scale.z;

        var x2 = rotation.x * rotation.x;
        var xy = rotation.x * rotation.y;
        var xz = rotation.x * rotation.z;
        var xw = rotation.x * rotation.w;
        var y2 = rotation.y * rotation.y;
        var yz = rotation.y * rotation.z;
        var yw = rotation.y * rotation.w;
        var z2 = rotation.z * rotation.z;
        var zw = rotation.z * rotation.w;
        var w2 = rotation.w * rotation.w;

        var m00 = x2 - y2 - z2 + w2;
        var m01 = 2.0 * (xy - zw);
        var m02 = 2.0 * (xz + yw);

        var m10 = 2.0 * (xy + zw);
        var m11 = -x2 + y2 - z2 + w2;
        var m12 = 2.0 * (yz - xw);

        var m20 = 2.0 * (xz - yw);
        var m21 = 2.0 * (yz + xw);
        var m22 = -x2 - y2 + z2 + w2;

        result[0]  = m00 * scaleX;
        result[1]  = m10 * scaleX;
        result[2]  = m20 * scaleX;
        result[3]  = 0.0;
        result[4]  = m01 * scaleY;
        result[5]  = m11 * scaleY;
        result[6]  = m21 * scaleY;
        result[7]  = 0.0;
        result[8]  = m02 * scaleZ;
        result[9]  = m12 * scaleZ;
        result[10] = m22 * scaleZ;
        result[11] = 0.0;
        result[12] = translation.x;
        result[13] = translation.y;
        result[14] = translation.z;
        result[15] = 1.0;

        return result;
    };

    /**
     * Creates a Matrix4 instance from a {@link TranslationRotationScale} instance.
     *
     * @param {TranslationRotationScale} translationRotationScale The instance.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     */
    Matrix4.fromTranslationRotationScale = function(translationRotationScale, result) {
                Check.typeOf.object('translationRotationScale', translationRotationScale);
        
        return Matrix4.fromTranslationQuaternionRotationScale(translationRotationScale.translation, translationRotationScale.rotation, translationRotationScale.scale, result);
    };

    /**
     * Creates a Matrix4 instance from a Cartesian3 representing the translation.
     *
     * @param {Cartesian3} translation The upper right portion of the matrix representing the translation.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     *
     * @see Matrix4.multiplyByTranslation
     */
    Matrix4.fromTranslation = function(translation, result) {
                Check.typeOf.object('translation', translation);
        
        return Matrix4.fromRotationTranslation(Matrix3.IDENTITY, translation, result);
    };

    /**
     * Computes a Matrix4 instance representing a non-uniform scale.
     *
     * @param {Cartesian3} scale The x, y, and z scale factors.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [7.0, 0.0, 0.0, 0.0]
     * //   [0.0, 8.0, 0.0, 0.0]
     * //   [0.0, 0.0, 9.0, 0.0]
     * //   [0.0, 0.0, 0.0, 1.0]
     * var m = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(7.0, 8.0, 9.0));
     */
    Matrix4.fromScale = function(scale, result) {
                Check.typeOf.object('scale', scale);
        
        if (!defined(result)) {
            return new Matrix4(
                scale.x, 0.0,     0.0,     0.0,
                0.0,     scale.y, 0.0,     0.0,
                0.0,     0.0,     scale.z, 0.0,
                0.0,     0.0,     0.0,     1.0);
        }

        result[0] = scale.x;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = scale.y;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 0.0;
        result[9] = 0.0;
        result[10] = scale.z;
        result[11] = 0.0;
        result[12] = 0.0;
        result[13] = 0.0;
        result[14] = 0.0;
        result[15] = 1.0;
        return result;
    };

    /**
     * Computes a Matrix4 instance representing a uniform scale.
     *
     * @param {Number} scale The uniform scale factor.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [2.0, 0.0, 0.0, 0.0]
     * //   [0.0, 2.0, 0.0, 0.0]
     * //   [0.0, 0.0, 2.0, 0.0]
     * //   [0.0, 0.0, 0.0, 1.0]
     * var m = Cesium.Matrix4.fromUniformScale(2.0);
     */
    Matrix4.fromUniformScale = function(scale, result) {
                Check.typeOf.number('scale', scale);
        
        if (!defined(result)) {
            return new Matrix4(scale, 0.0,   0.0,   0.0,
                               0.0,   scale, 0.0,   0.0,
                               0.0,   0.0,   scale, 0.0,
                               0.0,   0.0,   0.0,   1.0);
        }

        result[0] = scale;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = scale;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 0.0;
        result[9] = 0.0;
        result[10] = scale;
        result[11] = 0.0;
        result[12] = 0.0;
        result[13] = 0.0;
        result[14] = 0.0;
        result[15] = 1.0;
        return result;
    };

    var fromCameraF = new Cartesian3();
    var fromCameraR = new Cartesian3();
    var fromCameraU = new Cartesian3();

    /**
     * Computes a Matrix4 instance from a Camera.
     *
     * @param {Camera} camera The camera to use.
     * @param {Matrix4} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix4} The modified result parameter, or a new Matrix4 instance if one was not provided.
     */
    Matrix4.fromCamera = function(camera, result) {
                Check.typeOf.object('camera', camera);
        
        var position = camera.position;
        var direction = camera.direction;
        var up = camera.up;

                Check.typeOf.object('camera.position', position);
        Check.typeOf.object('camera.direction', direction);
        Check.typeOf.object('camera.up', up);
        
        Cartesian3.normalize(direction, fromCameraF);
        Cartesian3.normalize(Cartesian3.cross(fromCameraF, up, fromCameraR), fromCameraR);
        Cartesian3.normalize(Cartesian3.cross(fromCameraR, fromCameraF, fromCameraU), fromCameraU);

        var sX = fromCameraR.x;
        var sY = fromCameraR.y;
        var sZ = fromCameraR.z;
        var fX = fromCameraF.x;
        var fY = fromCameraF.y;
        var fZ = fromCameraF.z;
        var uX = fromCameraU.x;
        var uY = fromCameraU.y;
        var uZ = fromCameraU.z;
        var positionX = position.x;
        var positionY = position.y;
        var positionZ = position.z;
        var t0 = sX * -positionX + sY * -positionY+ sZ * -positionZ;
        var t1 = uX * -positionX + uY * -positionY+ uZ * -positionZ;
        var t2 = fX * positionX + fY * positionY + fZ * positionZ;

        // The code below this comment is an optimized
        // version of the commented lines.
        // Rather that create two matrices and then multiply,
        // we just bake in the multiplcation as part of creation.
        // var rotation = new Matrix4(
        //                 sX,  sY,  sZ, 0.0,
        //                 uX,  uY,  uZ, 0.0,
        //                -fX, -fY, -fZ, 0.0,
        //                 0.0,  0.0,  0.0, 1.0);
        // var translation = new Matrix4(
        //                 1.0, 0.0, 0.0, -position.x,
        //                 0.0, 1.0, 0.0, -position.y,
        //                 0.0, 0.0, 1.0, -position.z,
        //                 0.0, 0.0, 0.0, 1.0);
        // return rotation.multiply(translation);
        if (!defined(result)) {
            return new Matrix4(
                    sX,   sY,  sZ, t0,
                    uX,   uY,  uZ, t1,
                   -fX,  -fY, -fZ, t2,
                    0.0, 0.0, 0.0, 1.0);
        }
        result[0] = sX;
        result[1] = uX;
        result[2] = -fX;
        result[3] = 0.0;
        result[4] = sY;
        result[5] = uY;
        result[6] = -fY;
        result[7] = 0.0;
        result[8] = sZ;
        result[9] = uZ;
        result[10] = -fZ;
        result[11] = 0.0;
        result[12] = t0;
        result[13] = t1;
        result[14] = t2;
        result[15] = 1.0;
        return result;
    };

     /**
      * Computes a Matrix4 instance representing a perspective transformation matrix.
      *
      * @param {Number} fovY The field of view along the Y axis in radians.
      * @param {Number} aspectRatio The aspect ratio.
      * @param {Number} near The distance to the near plane in meters.
      * @param {Number} far The distance to the far plane in meters.
      * @param {Matrix4} result The object in which the result will be stored.
      * @returns {Matrix4} The modified result parameter.
      *
      * @exception {DeveloperError} fovY must be in (0, PI].
      * @exception {DeveloperError} aspectRatio must be greater than zero.
      * @exception {DeveloperError} near must be greater than zero.
      * @exception {DeveloperError} far must be greater than zero.
      */
    Matrix4.computePerspectiveFieldOfView = function(fovY, aspectRatio, near, far, result) {
                Check.typeOf.number.greaterThan('fovY', fovY, 0.0);
        Check.typeOf.number.lessThan('fovY', fovY, Math.PI);
        Check.typeOf.number.greaterThan('near', near, 0.0);
        Check.typeOf.number.greaterThan('far', far, 0.0);
        Check.typeOf.object('result', result);
        
        var bottom = Math.tan(fovY * 0.5);

        var column1Row1 = 1.0 / bottom;
        var column0Row0 = column1Row1 / aspectRatio;
        var column2Row2 = (far + near) / (near - far);
        var column3Row2 = (2.0 * far * near) / (near - far);

        result[0] = column0Row0;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = column1Row1;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 0.0;
        result[9] = 0.0;
        result[10] = column2Row2;
        result[11] = -1.0;
        result[12] = 0.0;
        result[13] = 0.0;
        result[14] = column3Row2;
        result[15] = 0.0;
        return result;
    };

    /**
    * Computes a Matrix4 instance representing an orthographic transformation matrix.
    *
    * @param {Number} left The number of meters to the left of the camera that will be in view.
    * @param {Number} right The number of meters to the right of the camera that will be in view.
    * @param {Number} bottom The number of meters below of the camera that will be in view.
    * @param {Number} top The number of meters above of the camera that will be in view.
    * @param {Number} near The distance to the near plane in meters.
    * @param {Number} far The distance to the far plane in meters.
    * @param {Matrix4} result The object in which the result will be stored.
    * @returns {Matrix4} The modified result parameter.
    */
    Matrix4.computeOrthographicOffCenter = function(left, right, bottom, top, near, far, result) {
                Check.typeOf.number('left', left);
        Check.typeOf.number('right', right);
        Check.typeOf.number('bottom', bottom);
        Check.typeOf.number('top', top);
        Check.typeOf.number('near', near);
        Check.typeOf.number('far', far);
        Check.typeOf.object('result', result);
        
        var a = 1.0 / (right - left);
        var b = 1.0 / (top - bottom);
        var c = 1.0 / (far - near);

        var tx = -(right + left) * a;
        var ty = -(top + bottom) * b;
        var tz = -(far + near) * c;
        a *= 2.0;
        b *= 2.0;
        c *= -2.0;

        result[0] = a;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = b;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 0.0;
        result[9] = 0.0;
        result[10] = c;
        result[11] = 0.0;
        result[12] = tx;
        result[13] = ty;
        result[14] = tz;
        result[15] = 1.0;
        return result;
    };

    /**
     * Computes a Matrix4 instance representing an off center perspective transformation.
     *
     * @param {Number} left The number of meters to the left of the camera that will be in view.
     * @param {Number} right The number of meters to the right of the camera that will be in view.
     * @param {Number} bottom The number of meters below of the camera that will be in view.
     * @param {Number} top The number of meters above of the camera that will be in view.
     * @param {Number} near The distance to the near plane in meters.
     * @param {Number} far The distance to the far plane in meters.
     * @param {Matrix4} result The object in which the result will be stored.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.computePerspectiveOffCenter = function(left, right, bottom, top, near, far, result) {
                Check.typeOf.number('left', left);
        Check.typeOf.number('right', right);
        Check.typeOf.number('bottom', bottom);
        Check.typeOf.number('top', top);
        Check.typeOf.number('near', near);
        Check.typeOf.number('far', far);
        Check.typeOf.object('result', result);
        
        var column0Row0 = 2.0 * near / (right - left);
        var column1Row1 = 2.0 * near / (top - bottom);
        var column2Row0 = (right + left) / (right - left);
        var column2Row1 = (top + bottom) / (top - bottom);
        var column2Row2 = -(far + near) / (far - near);
        var column2Row3 = -1.0;
        var column3Row2 = -2.0 * far * near / (far - near);

        result[0] = column0Row0;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = column1Row1;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = column2Row0;
        result[9] = column2Row1;
        result[10] = column2Row2;
        result[11] = column2Row3;
        result[12] = 0.0;
        result[13] = 0.0;
        result[14] = column3Row2;
        result[15] = 0.0;
        return result;
    };

    /**
     * Computes a Matrix4 instance representing an infinite off center perspective transformation.
     *
     * @param {Number} left The number of meters to the left of the camera that will be in view.
     * @param {Number} right The number of meters to the right of the camera that will be in view.
     * @param {Number} bottom The number of meters below of the camera that will be in view.
     * @param {Number} top The number of meters above of the camera that will be in view.
     * @param {Number} near The distance to the near plane in meters.
     * @param {Matrix4} result The object in which the result will be stored.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.computeInfinitePerspectiveOffCenter = function(left, right, bottom, top, near, result) {
                Check.typeOf.number('left', left);
        Check.typeOf.number('right', right);
        Check.typeOf.number('bottom', bottom);
        Check.typeOf.number('top', top);
        Check.typeOf.number('near', near);
        Check.typeOf.object('result', result);
        
        var column0Row0 = 2.0 * near / (right - left);
        var column1Row1 = 2.0 * near / (top - bottom);
        var column2Row0 = (right + left) / (right - left);
        var column2Row1 = (top + bottom) / (top - bottom);
        var column2Row2 = -1.0;
        var column2Row3 = -1.0;
        var column3Row2 = -2.0 * near;

        result[0] = column0Row0;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = column1Row1;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = column2Row0;
        result[9] = column2Row1;
        result[10] = column2Row2;
        result[11] = column2Row3;
        result[12] = 0.0;
        result[13] = 0.0;
        result[14] = column3Row2;
        result[15] = 0.0;
        return result;
    };

    /**
     * Computes a Matrix4 instance that transforms from normalized device coordinates to window coordinates.
     *
     * @param {Object}[viewport = { x : 0.0, y : 0.0, width : 0.0, height : 0.0 }] The viewport's corners as shown in Example 1.
     * @param {Number}[nearDepthRange=0.0] The near plane distance in window coordinates.
     * @param {Number}[farDepthRange=1.0] The far plane distance in window coordinates.
     * @param {Matrix4} result The object in which the result will be stored.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * // Create viewport transformation using an explicit viewport and depth range.
     * var m = Cesium.Matrix4.computeViewportTransformation({
     *     x : 0.0,
     *     y : 0.0,
     *     width : 1024.0,
     *     height : 768.0
     * }, 0.0, 1.0, new Cesium.Matrix4());
     */
    Matrix4.computeViewportTransformation = function(viewport, nearDepthRange, farDepthRange, result) {
                Check.typeOf.object('result', result);
        
        viewport = defaultValue(viewport, defaultValue.EMPTY_OBJECT);
        var x = defaultValue(viewport.x, 0.0);
        var y = defaultValue(viewport.y, 0.0);
        var width = defaultValue(viewport.width, 0.0);
        var height = defaultValue(viewport.height, 0.0);
        nearDepthRange = defaultValue(nearDepthRange, 0.0);
        farDepthRange = defaultValue(farDepthRange, 1.0);

        var halfWidth = width * 0.5;
        var halfHeight = height * 0.5;
        var halfDepth = (farDepthRange - nearDepthRange) * 0.5;

        var column0Row0 = halfWidth;
        var column1Row1 = halfHeight;
        var column2Row2 = halfDepth;
        var column3Row0 = x + halfWidth;
        var column3Row1 = y + halfHeight;
        var column3Row2 = nearDepthRange + halfDepth;
        var column3Row3 = 1.0;

        result[0] = column0Row0;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = 0.0;
        result[4] = 0.0;
        result[5] = column1Row1;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 0.0;
        result[9] = 0.0;
        result[10] = column2Row2;
        result[11] = 0.0;
        result[12] = column3Row0;
        result[13] = column3Row1;
        result[14] = column3Row2;
        result[15] = column3Row3;
        return result;
    };

    /**
     * Computes a Matrix4 instance that transforms from world space to view space.
     *
     * @param {Cartesian3} position The position of the camera.
     * @param {Cartesian3} direction The forward direction.
     * @param {Cartesian3} up The up direction.
     * @param {Cartesian3} right The right direction.
     * @param {Matrix4} result The object in which the result will be stored.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.computeView = function(position, direction, up, right, result) {
                Check.typeOf.object('position', position);
        Check.typeOf.object('direction', direction);
        Check.typeOf.object('up', up);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = right.x;
        result[1] = up.x;
        result[2] = -direction.x;
        result[3] = 0.0;
        result[4] = right.y;
        result[5] = up.y;
        result[6] = -direction.y;
        result[7] = 0.0;
        result[8] = right.z;
        result[9] = up.z;
        result[10] = -direction.z;
        result[11] = 0.0;
        result[12] = -Cartesian3.dot(right, position);
        result[13] = -Cartesian3.dot(up, position);
        result[14] = Cartesian3.dot(direction, position);
        result[15] = 1.0;
        return result;
    };

    /**
     * Computes an Array from the provided Matrix4 instance.
     * The array will be in column-major order.
     *
     * @param {Matrix4} matrix The matrix to use..
     * @param {Number[]} [result] The Array onto which to store the result.
     * @returns {Number[]} The modified Array parameter or a new Array instance if one was not provided.
     *
     * @example
     * //create an array from an instance of Matrix4
     * // m = [10.0, 14.0, 18.0, 22.0]
     * //     [11.0, 15.0, 19.0, 23.0]
     * //     [12.0, 16.0, 20.0, 24.0]
     * //     [13.0, 17.0, 21.0, 25.0]
     * var a = Cesium.Matrix4.toArray(m);
     *
     * // m remains the same
     * //creates a = [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0]
     */
    Matrix4.toArray = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        
        if (!defined(result)) {
            return [matrix[0], matrix[1], matrix[2], matrix[3],
                    matrix[4], matrix[5], matrix[6], matrix[7],
                    matrix[8], matrix[9], matrix[10], matrix[11],
                    matrix[12], matrix[13], matrix[14], matrix[15]];
        }
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        result[4] = matrix[4];
        result[5] = matrix[5];
        result[6] = matrix[6];
        result[7] = matrix[7];
        result[8] = matrix[8];
        result[9] = matrix[9];
        result[10] = matrix[10];
        result[11] = matrix[11];
        result[12] = matrix[12];
        result[13] = matrix[13];
        result[14] = matrix[14];
        result[15] = matrix[15];
        return result;
    };

    /**
     * Computes the array index of the element at the provided row and column.
     *
     * @param {Number} row The zero-based index of the row.
     * @param {Number} column The zero-based index of the column.
     * @returns {Number} The index of the element at the provided row and column.
     *
     * @exception {DeveloperError} row must be 0, 1, 2, or 3.
     * @exception {DeveloperError} column must be 0, 1, 2, or 3.
     *
     * @example
     * var myMatrix = new Cesium.Matrix4();
     * var column1Row0Index = Cesium.Matrix4.getElementIndex(1, 0);
     * var column1Row0 = myMatrix[column1Row0Index];
     * myMatrix[column1Row0Index] = 10.0;
     */
    Matrix4.getElementIndex = function(column, row) {
                Check.typeOf.number.greaterThanOrEquals('row', row, 0);
        Check.typeOf.number.lessThanOrEquals('row', row, 3);

        Check.typeOf.number.greaterThanOrEquals('column', column, 0);
        Check.typeOf.number.lessThanOrEquals('column', column, 3);
        
        return column * 4 + row;
    };

    /**
     * Retrieves a copy of the matrix column at the provided index as a Cartesian4 instance.
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Number} index The zero-based index of the column to retrieve.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, 2, or 3.
     *
     * @example
     * //returns a Cartesian4 instance with values from the specified column
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * //Example 1: Creates an instance of Cartesian
     * var a = Cesium.Matrix4.getColumn(m, 2, new Cesium.Cartesian4());
     *
     * @example
     * //Example 2: Sets values for Cartesian instance
     * var a = new Cesium.Cartesian4();
     * Cesium.Matrix4.getColumn(m, 2, a);
     *
     * // a.x = 12.0; a.y = 16.0; a.z = 20.0; a.w = 24.0;
     */
    Matrix4.getColumn = function(matrix, index, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 3);

        Check.typeOf.object('result', result);
        
        var startIndex = index * 4;
        var x = matrix[startIndex];
        var y = matrix[startIndex + 1];
        var z = matrix[startIndex + 2];
        var w = matrix[startIndex + 3];

        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    /**
     * Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian4 instance.
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Number} index The zero-based index of the column to set.
     * @param {Cartesian4} cartesian The Cartesian whose values will be assigned to the specified column.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, 2, or 3.
     *
     * @example
     * //creates a new Matrix4 instance with new column values from the Cartesian4 instance
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * var a = Cesium.Matrix4.setColumn(m, 2, new Cesium.Cartesian4(99.0, 98.0, 97.0, 96.0), new Cesium.Matrix4());
     *
     * // m remains the same
     * // a = [10.0, 11.0, 99.0, 13.0]
     * //     [14.0, 15.0, 98.0, 17.0]
     * //     [18.0, 19.0, 97.0, 21.0]
     * //     [22.0, 23.0, 96.0, 25.0]
     */
    Matrix4.setColumn = function(matrix, index, cartesian, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 3);

        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result = Matrix4.clone(matrix, result);
        var startIndex = index * 4;
        result[startIndex] = cartesian.x;
        result[startIndex + 1] = cartesian.y;
        result[startIndex + 2] = cartesian.z;
        result[startIndex + 3] = cartesian.w;
        return result;
    };

    /**
     * Computes a new matrix that replaces the translation in the rightmost column of the provided
     * matrix with the provided translation.  This assumes the matrix is an affine transformation
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Cartesian3} translation The translation that replaces the translation of the provided matrix.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.setTranslation = function(matrix, translation, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('translation', translation);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];

        result[4] = matrix[4];
        result[5] = matrix[5];
        result[6] = matrix[6];
        result[7] = matrix[7];

        result[8] = matrix[8];
        result[9] = matrix[9];
        result[10] = matrix[10];
        result[11] = matrix[11];

        result[12] = translation.x;
        result[13] = translation.y;
        result[14] = translation.z;
        result[15] = matrix[15];

        return result;
    };

    /**
     * Retrieves a copy of the matrix row at the provided index as a Cartesian4 instance.
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Number} index The zero-based index of the row to retrieve.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, 2, or 3.
     *
     * @example
     * //returns a Cartesian4 instance with values from the specified column
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * //Example 1: Returns an instance of Cartesian
     * var a = Cesium.Matrix4.getRow(m, 2, new Cesium.Cartesian4());
     *
     * @example
     * //Example 2: Sets values for a Cartesian instance
     * var a = new Cesium.Cartesian4();
     * Cesium.Matrix4.getRow(m, 2, a);
     *
     * // a.x = 18.0; a.y = 19.0; a.z = 20.0; a.w = 21.0;
     */
    Matrix4.getRow = function(matrix, index, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 3);

        Check.typeOf.object('result', result);
        
        var x = matrix[index];
        var y = matrix[index + 4];
        var z = matrix[index + 8];
        var w = matrix[index + 12];

        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    /**
     * Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian4 instance.
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Number} index The zero-based index of the row to set.
     * @param {Cartesian4} cartesian The Cartesian whose values will be assigned to the specified row.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0, 1, 2, or 3.
     *
     * @example
     * //create a new Matrix4 instance with new row values from the Cartesian4 instance
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * var a = Cesium.Matrix4.setRow(m, 2, new Cesium.Cartesian4(99.0, 98.0, 97.0, 96.0), new Cesium.Matrix4());
     *
     * // m remains the same
     * // a = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [99.0, 98.0, 97.0, 96.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     */
    Matrix4.setRow = function(matrix, index, cartesian, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 3);

        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result = Matrix4.clone(matrix, result);
        result[index] = cartesian.x;
        result[index + 4] = cartesian.y;
        result[index + 8] = cartesian.z;
        result[index + 12] = cartesian.w;
        return result;
    };

    var scratchColumn = new Cartesian3();

    /**
     * Extracts the non-uniform scale assuming the matrix is an affine transformation.
     *
     * @param {Matrix4} matrix The matrix.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter
     */
    Matrix4.getScale = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result.x = Cartesian3.magnitude(Cartesian3.fromElements(matrix[0], matrix[1], matrix[2], scratchColumn));
        result.y = Cartesian3.magnitude(Cartesian3.fromElements(matrix[4], matrix[5], matrix[6], scratchColumn));
        result.z = Cartesian3.magnitude(Cartesian3.fromElements(matrix[8], matrix[9], matrix[10], scratchColumn));
        return result;
    };

    var scratchScale = new Cartesian3();

    /**
     * Computes the maximum scale assuming the matrix is an affine transformation.
     * The maximum scale is the maximum length of the column vectors in the upper-left
     * 3x3 matrix.
     *
     * @param {Matrix4} matrix The matrix.
     * @returns {Number} The maximum scale.
     */
    Matrix4.getMaximumScale = function(matrix) {
        Matrix4.getScale(matrix, scratchScale);
        return Cartesian3.maximumComponent(scratchScale);
    };

    /**
     * Computes the product of two matrices.
     *
     * @param {Matrix4} left The first matrix.
     * @param {Matrix4} right The second matrix.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.multiply = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        var left0 = left[0];
        var left1 = left[1];
        var left2 = left[2];
        var left3 = left[3];
        var left4 = left[4];
        var left5 = left[5];
        var left6 = left[6];
        var left7 = left[7];
        var left8 = left[8];
        var left9 = left[9];
        var left10 = left[10];
        var left11 = left[11];
        var left12 = left[12];
        var left13 = left[13];
        var left14 = left[14];
        var left15 = left[15];

        var right0 = right[0];
        var right1 = right[1];
        var right2 = right[2];
        var right3 = right[3];
        var right4 = right[4];
        var right5 = right[5];
        var right6 = right[6];
        var right7 = right[7];
        var right8 = right[8];
        var right9 = right[9];
        var right10 = right[10];
        var right11 = right[11];
        var right12 = right[12];
        var right13 = right[13];
        var right14 = right[14];
        var right15 = right[15];

        var column0Row0 = left0 * right0 + left4 * right1 + left8 * right2 + left12 * right3;
        var column0Row1 = left1 * right0 + left5 * right1 + left9 * right2 + left13 * right3;
        var column0Row2 = left2 * right0 + left6 * right1 + left10 * right2 + left14 * right3;
        var column0Row3 = left3 * right0 + left7 * right1 + left11 * right2 + left15 * right3;

        var column1Row0 = left0 * right4 + left4 * right5 + left8 * right6 + left12 * right7;
        var column1Row1 = left1 * right4 + left5 * right5 + left9 * right6 + left13 * right7;
        var column1Row2 = left2 * right4 + left6 * right5 + left10 * right6 + left14 * right7;
        var column1Row3 = left3 * right4 + left7 * right5 + left11 * right6 + left15 * right7;

        var column2Row0 = left0 * right8 + left4 * right9 + left8 * right10 + left12 * right11;
        var column2Row1 = left1 * right8 + left5 * right9 + left9 * right10 + left13 * right11;
        var column2Row2 = left2 * right8 + left6 * right9 + left10 * right10 + left14 * right11;
        var column2Row3 = left3 * right8 + left7 * right9 + left11 * right10 + left15 * right11;

        var column3Row0 = left0 * right12 + left4 * right13 + left8 * right14 + left12 * right15;
        var column3Row1 = left1 * right12 + left5 * right13 + left9 * right14 + left13 * right15;
        var column3Row2 = left2 * right12 + left6 * right13 + left10 * right14 + left14 * right15;
        var column3Row3 = left3 * right12 + left7 * right13 + left11 * right14 + left15 * right15;

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column0Row2;
        result[3] = column0Row3;
        result[4] = column1Row0;
        result[5] = column1Row1;
        result[6] = column1Row2;
        result[7] = column1Row3;
        result[8] = column2Row0;
        result[9] = column2Row1;
        result[10] = column2Row2;
        result[11] = column2Row3;
        result[12] = column3Row0;
        result[13] = column3Row1;
        result[14] = column3Row2;
        result[15] = column3Row3;
        return result;
    };

    /**
     * Computes the sum of two matrices.
     *
     * @param {Matrix4} left The first matrix.
     * @param {Matrix4} right The second matrix.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = left[0] + right[0];
        result[1] = left[1] + right[1];
        result[2] = left[2] + right[2];
        result[3] = left[3] + right[3];
        result[4] = left[4] + right[4];
        result[5] = left[5] + right[5];
        result[6] = left[6] + right[6];
        result[7] = left[7] + right[7];
        result[8] = left[8] + right[8];
        result[9] = left[9] + right[9];
        result[10] = left[10] + right[10];
        result[11] = left[11] + right[11];
        result[12] = left[12] + right[12];
        result[13] = left[13] + right[13];
        result[14] = left[14] + right[14];
        result[15] = left[15] + right[15];
        return result;
    };

    /**
     * Computes the difference of two matrices.
     *
     * @param {Matrix4} left The first matrix.
     * @param {Matrix4} right The second matrix.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = left[0] - right[0];
        result[1] = left[1] - right[1];
        result[2] = left[2] - right[2];
        result[3] = left[3] - right[3];
        result[4] = left[4] - right[4];
        result[5] = left[5] - right[5];
        result[6] = left[6] - right[6];
        result[7] = left[7] - right[7];
        result[8] = left[8] - right[8];
        result[9] = left[9] - right[9];
        result[10] = left[10] - right[10];
        result[11] = left[11] - right[11];
        result[12] = left[12] - right[12];
        result[13] = left[13] - right[13];
        result[14] = left[14] - right[14];
        result[15] = left[15] - right[15];
        return result;
    };

    /**
     * Computes the product of two matrices assuming the matrices are
     * affine transformation matrices, where the upper left 3x3 elements
     * are a rotation matrix, and the upper three elements in the fourth
     * column are the translation.  The bottom row is assumed to be [0, 0, 0, 1].
     * The matrix is not verified to be in the proper form.
     * This method is faster than computing the product for general 4x4
     * matrices using {@link Matrix4.multiply}.
     *
     * @param {Matrix4} left The first matrix.
     * @param {Matrix4} right The second matrix.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * var m1 = new Cesium.Matrix4(1.0, 6.0, 7.0, 0.0, 2.0, 5.0, 8.0, 0.0, 3.0, 4.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0);
     * var m2 = Cesium.Transforms.eastNorthUpToFixedFrame(new Cesium.Cartesian3(1.0, 1.0, 1.0));
     * var m3 = Cesium.Matrix4.multiplyTransformation(m1, m2, new Cesium.Matrix4());
     */
    Matrix4.multiplyTransformation = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        var left0 = left[0];
        var left1 = left[1];
        var left2 = left[2];
        var left4 = left[4];
        var left5 = left[5];
        var left6 = left[6];
        var left8 = left[8];
        var left9 = left[9];
        var left10 = left[10];
        var left12 = left[12];
        var left13 = left[13];
        var left14 = left[14];

        var right0 = right[0];
        var right1 = right[1];
        var right2 = right[2];
        var right4 = right[4];
        var right5 = right[5];
        var right6 = right[6];
        var right8 = right[8];
        var right9 = right[9];
        var right10 = right[10];
        var right12 = right[12];
        var right13 = right[13];
        var right14 = right[14];

        var column0Row0 = left0 * right0 + left4 * right1 + left8 * right2;
        var column0Row1 = left1 * right0 + left5 * right1 + left9 * right2;
        var column0Row2 = left2 * right0 + left6 * right1 + left10 * right2;

        var column1Row0 = left0 * right4 + left4 * right5 + left8 * right6;
        var column1Row1 = left1 * right4 + left5 * right5 + left9 * right6;
        var column1Row2 = left2 * right4 + left6 * right5 + left10 * right6;

        var column2Row0 = left0 * right8 + left4 * right9 + left8 * right10;
        var column2Row1 = left1 * right8 + left5 * right9 + left9 * right10;
        var column2Row2 = left2 * right8 + left6 * right9 + left10 * right10;

        var column3Row0 = left0 * right12 + left4 * right13 + left8 * right14 + left12;
        var column3Row1 = left1 * right12 + left5 * right13 + left9 * right14 + left13;
        var column3Row2 = left2 * right12 + left6 * right13 + left10 * right14 + left14;

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column0Row2;
        result[3] = 0.0;
        result[4] = column1Row0;
        result[5] = column1Row1;
        result[6] = column1Row2;
        result[7] = 0.0;
        result[8] = column2Row0;
        result[9] = column2Row1;
        result[10] = column2Row2;
        result[11] = 0.0;
        result[12] = column3Row0;
        result[13] = column3Row1;
        result[14] = column3Row2;
        result[15] = 1.0;
        return result;
    };

    /**
     * Multiplies a transformation matrix (with a bottom row of <code>[0.0, 0.0, 0.0, 1.0]</code>)
     * by a 3x3 rotation matrix.  This is an optimization
     * for <code>Matrix4.multiply(m, Matrix4.fromRotationTranslation(rotation), m);</code> with less allocations and arithmetic operations.
     *
     * @param {Matrix4} matrix The matrix on the left-hand side.
     * @param {Matrix3} rotation The 3x3 rotation matrix on the right-hand side.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromRotationTranslation(rotation), m);
     * Cesium.Matrix4.multiplyByMatrix3(m, rotation, m);
     */
    Matrix4.multiplyByMatrix3 = function(matrix, rotation, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('rotation', rotation);
        Check.typeOf.object('result', result);
        
        var left0 = matrix[0];
        var left1 = matrix[1];
        var left2 = matrix[2];
        var left4 = matrix[4];
        var left5 = matrix[5];
        var left6 = matrix[6];
        var left8 = matrix[8];
        var left9 = matrix[9];
        var left10 = matrix[10];

        var right0 = rotation[0];
        var right1 = rotation[1];
        var right2 = rotation[2];
        var right4 = rotation[3];
        var right5 = rotation[4];
        var right6 = rotation[5];
        var right8 = rotation[6];
        var right9 = rotation[7];
        var right10 = rotation[8];

        var column0Row0 = left0 * right0 + left4 * right1 + left8 * right2;
        var column0Row1 = left1 * right0 + left5 * right1 + left9 * right2;
        var column0Row2 = left2 * right0 + left6 * right1 + left10 * right2;

        var column1Row0 = left0 * right4 + left4 * right5 + left8 * right6;
        var column1Row1 = left1 * right4 + left5 * right5 + left9 * right6;
        var column1Row2 = left2 * right4 + left6 * right5 + left10 * right6;

        var column2Row0 = left0 * right8 + left4 * right9 + left8 * right10;
        var column2Row1 = left1 * right8 + left5 * right9 + left9 * right10;
        var column2Row2 = left2 * right8 + left6 * right9 + left10 * right10;

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column0Row2;
        result[3] = 0.0;
        result[4] = column1Row0;
        result[5] = column1Row1;
        result[6] = column1Row2;
        result[7] = 0.0;
        result[8] = column2Row0;
        result[9] = column2Row1;
        result[10] = column2Row2;
        result[11] = 0.0;
        result[12] = matrix[12];
        result[13] = matrix[13];
        result[14] = matrix[14];
        result[15] = matrix[15];
        return result;
    };

    /**
     * Multiplies a transformation matrix (with a bottom row of <code>[0.0, 0.0, 0.0, 1.0]</code>)
     * by an implicit translation matrix defined by a {@link Cartesian3}.  This is an optimization
     * for <code>Matrix4.multiply(m, Matrix4.fromTranslation(position), m);</code> with less allocations and arithmetic operations.
     *
     * @param {Matrix4} matrix The matrix on the left-hand side.
     * @param {Cartesian3} translation The translation on the right-hand side.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromTranslation(position), m);
     * Cesium.Matrix4.multiplyByTranslation(m, position, m);
     */
    Matrix4.multiplyByTranslation = function(matrix, translation, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('translation', translation);
        Check.typeOf.object('result', result);
        
        var x = translation.x;
        var y = translation.y;
        var z = translation.z;

        var tx = (x * matrix[0]) + (y * matrix[4]) + (z * matrix[8]) + matrix[12];
        var ty = (x * matrix[1]) + (y * matrix[5]) + (z * matrix[9]) + matrix[13];
        var tz = (x * matrix[2]) + (y * matrix[6]) + (z * matrix[10]) + matrix[14];

        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        result[4] = matrix[4];
        result[5] = matrix[5];
        result[6] = matrix[6];
        result[7] = matrix[7];
        result[8] = matrix[8];
        result[9] = matrix[9];
        result[10] = matrix[10];
        result[11] = matrix[11];
        result[12] = tx;
        result[13] = ty;
        result[14] = tz;
        result[15] = matrix[15];
        return result;
    };

    var uniformScaleScratch = new Cartesian3();

    /**
     * Multiplies an affine transformation matrix (with a bottom row of <code>[0.0, 0.0, 0.0, 1.0]</code>)
     * by an implicit uniform scale matrix.  This is an optimization
     * for <code>Matrix4.multiply(m, Matrix4.fromUniformScale(scale), m);</code>, where
     * <code>m</code> must be an affine matrix.
     * This function performs fewer allocations and arithmetic operations.
     *
     * @param {Matrix4} matrix The affine matrix on the left-hand side.
     * @param {Number} scale The uniform scale on the right-hand side.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     *
     * @example
     * // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromUniformScale(scale), m);
     * Cesium.Matrix4.multiplyByUniformScale(m, scale, m);
     *
     * @see Matrix4.fromUniformScale
     * @see Matrix4.multiplyByScale
     */
    Matrix4.multiplyByUniformScale = function(matrix, scale, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number('scale', scale);
        Check.typeOf.object('result', result);
        
        uniformScaleScratch.x = scale;
        uniformScaleScratch.y = scale;
        uniformScaleScratch.z = scale;
        return Matrix4.multiplyByScale(matrix, uniformScaleScratch, result);
    };

    /**
     * Multiplies an affine transformation matrix (with a bottom row of <code>[0.0, 0.0, 0.0, 1.0]</code>)
     * by an implicit non-uniform scale matrix.  This is an optimization
     * for <code>Matrix4.multiply(m, Matrix4.fromUniformScale(scale), m);</code>, where
     * <code>m</code> must be an affine matrix.
     * This function performs fewer allocations and arithmetic operations.
     *
     * @param {Matrix4} matrix The affine matrix on the left-hand side.
     * @param {Cartesian3} scale The non-uniform scale on the right-hand side.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     *
     * @example
     * // Instead of Cesium.Matrix4.multiply(m, Cesium.Matrix4.fromScale(scale), m);
     * Cesium.Matrix4.multiplyByScale(m, scale, m);
     *
     * @see Matrix4.fromScale
     * @see Matrix4.multiplyByUniformScale
     */
    Matrix4.multiplyByScale = function(matrix, scale, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('scale', scale);
        Check.typeOf.object('result', result);
        
        var scaleX = scale.x;
        var scaleY = scale.y;
        var scaleZ = scale.z;

        // Faster than Cartesian3.equals
        if ((scaleX === 1.0) && (scaleY === 1.0) && (scaleZ === 1.0)) {
            return Matrix4.clone(matrix, result);
        }

        result[0] = scaleX * matrix[0];
        result[1] = scaleX * matrix[1];
        result[2] = scaleX * matrix[2];
        result[3] = 0.0;
        result[4] = scaleY * matrix[4];
        result[5] = scaleY * matrix[5];
        result[6] = scaleY * matrix[6];
        result[7] = 0.0;
        result[8] = scaleZ * matrix[8];
        result[9] = scaleZ * matrix[9];
        result[10] = scaleZ * matrix[10];
        result[11] = 0.0;
        result[12] = matrix[12];
        result[13] = matrix[13];
        result[14] = matrix[14];
        result[15] = 1.0;
        return result;
    };

    /**
     * Computes the product of a matrix and a column vector.
     *
     * @param {Matrix4} matrix The matrix.
     * @param {Cartesian4} cartesian The vector.
     * @param {Cartesian4} result The object onto which to store the result.
     * @returns {Cartesian4} The modified result parameter.
     */
    Matrix4.multiplyByVector = function(matrix, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var vX = cartesian.x;
        var vY = cartesian.y;
        var vZ = cartesian.z;
        var vW = cartesian.w;

        var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12] * vW;
        var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13] * vW;
        var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14] * vW;
        var w = matrix[3] * vX + matrix[7] * vY + matrix[11] * vZ + matrix[15] * vW;

        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    /**
     * Computes the product of a matrix and a {@link Cartesian3}.  This is equivalent to calling {@link Matrix4.multiplyByVector}
     * with a {@link Cartesian4} with a <code>w</code> component of zero.
     *
     * @param {Matrix4} matrix The matrix.
     * @param {Cartesian3} cartesian The point.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     *
     * @example
     * var p = new Cesium.Cartesian3(1.0, 2.0, 3.0);
     * var result = Cesium.Matrix4.multiplyByPointAsVector(matrix, p, new Cesium.Cartesian3());
     * // A shortcut for
     * //   Cartesian3 p = ...
     * //   Cesium.Matrix4.multiplyByVector(matrix, new Cesium.Cartesian4(p.x, p.y, p.z, 0.0), result);
     */
    Matrix4.multiplyByPointAsVector = function(matrix, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var vX = cartesian.x;
        var vY = cartesian.y;
        var vZ = cartesian.z;

        var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ;
        var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ;
        var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ;

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Computes the product of a matrix and a {@link Cartesian3}. This is equivalent to calling {@link Matrix4.multiplyByVector}
     * with a {@link Cartesian4} with a <code>w</code> component of 1, but returns a {@link Cartesian3} instead of a {@link Cartesian4}.
     *
     * @param {Matrix4} matrix The matrix.
     * @param {Cartesian3} cartesian The point.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     *
     * @example
     * var p = new Cesium.Cartesian3(1.0, 2.0, 3.0);
     * var result = Cesium.Matrix4.multiplyByPoint(matrix, p, new Cesium.Cartesian3());
     */
    Matrix4.multiplyByPoint = function(matrix, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var vX = cartesian.x;
        var vY = cartesian.y;
        var vZ = cartesian.z;

        var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12];
        var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13];
        var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14];

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Computes the product of a matrix and a scalar.
     *
     * @param {Matrix4} matrix The matrix.
     * @param {Number} scalar The number to multiply by.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * //create a Matrix4 instance which is a scaled version of the supplied Matrix4
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * var a = Cesium.Matrix4.multiplyByScalar(m, -2, new Cesium.Matrix4());
     *
     * // m remains the same
     * // a = [-20.0, -22.0, -24.0, -26.0]
     * //     [-28.0, -30.0, -32.0, -34.0]
     * //     [-36.0, -38.0, -40.0, -42.0]
     * //     [-44.0, -46.0, -48.0, -50.0]
     */
    Matrix4.multiplyByScalar = function(matrix, scalar, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0] * scalar;
        result[1] = matrix[1] * scalar;
        result[2] = matrix[2] * scalar;
        result[3] = matrix[3] * scalar;
        result[4] = matrix[4] * scalar;
        result[5] = matrix[5] * scalar;
        result[6] = matrix[6] * scalar;
        result[7] = matrix[7] * scalar;
        result[8] = matrix[8] * scalar;
        result[9] = matrix[9] * scalar;
        result[10] = matrix[10] * scalar;
        result[11] = matrix[11] * scalar;
        result[12] = matrix[12] * scalar;
        result[13] = matrix[13] * scalar;
        result[14] = matrix[14] * scalar;
        result[15] = matrix[15] * scalar;
        return result;
    };

    /**
     * Computes a negated copy of the provided matrix.
     *
     * @param {Matrix4} matrix The matrix to negate.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * //create a new Matrix4 instance which is a negation of a Matrix4
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * var a = Cesium.Matrix4.negate(m, new Cesium.Matrix4());
     *
     * // m remains the same
     * // a = [-10.0, -11.0, -12.0, -13.0]
     * //     [-14.0, -15.0, -16.0, -17.0]
     * //     [-18.0, -19.0, -20.0, -21.0]
     * //     [-22.0, -23.0, -24.0, -25.0]
     */
    Matrix4.negate = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = -matrix[0];
        result[1] = -matrix[1];
        result[2] = -matrix[2];
        result[3] = -matrix[3];
        result[4] = -matrix[4];
        result[5] = -matrix[5];
        result[6] = -matrix[6];
        result[7] = -matrix[7];
        result[8] = -matrix[8];
        result[9] = -matrix[9];
        result[10] = -matrix[10];
        result[11] = -matrix[11];
        result[12] = -matrix[12];
        result[13] = -matrix[13];
        result[14] = -matrix[14];
        result[15] = -matrix[15];
        return result;
    };

    /**
     * Computes the transpose of the provided matrix.
     *
     * @param {Matrix4} matrix The matrix to transpose.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     *
     * @example
     * //returns transpose of a Matrix4
     * // m = [10.0, 11.0, 12.0, 13.0]
     * //     [14.0, 15.0, 16.0, 17.0]
     * //     [18.0, 19.0, 20.0, 21.0]
     * //     [22.0, 23.0, 24.0, 25.0]
     *
     * var a = Cesium.Matrix4.transpose(m, new Cesium.Matrix4());
     *
     * // m remains the same
     * // a = [10.0, 14.0, 18.0, 22.0]
     * //     [11.0, 15.0, 19.0, 23.0]
     * //     [12.0, 16.0, 20.0, 24.0]
     * //     [13.0, 17.0, 21.0, 25.0]
     */
    Matrix4.transpose = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        var matrix1 = matrix[1];
        var matrix2 = matrix[2];
        var matrix3 = matrix[3];
        var matrix6 = matrix[6];
        var matrix7 = matrix[7];
        var matrix11 = matrix[11];

        result[0] = matrix[0];
        result[1] = matrix[4];
        result[2] = matrix[8];
        result[3] = matrix[12];
        result[4] = matrix1;
        result[5] = matrix[5];
        result[6] = matrix[9];
        result[7] = matrix[13];
        result[8] = matrix2;
        result[9] = matrix6;
        result[10] = matrix[10];
        result[11] = matrix[14];
        result[12] = matrix3;
        result[13] = matrix7;
        result[14] = matrix11;
        result[15] = matrix[15];
        return result;
    };

    /**
     * Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
     *
     * @param {Matrix4} matrix The matrix with signed elements.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.abs = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = Math.abs(matrix[0]);
        result[1] = Math.abs(matrix[1]);
        result[2] = Math.abs(matrix[2]);
        result[3] = Math.abs(matrix[3]);
        result[4] = Math.abs(matrix[4]);
        result[5] = Math.abs(matrix[5]);
        result[6] = Math.abs(matrix[6]);
        result[7] = Math.abs(matrix[7]);
        result[8] = Math.abs(matrix[8]);
        result[9] = Math.abs(matrix[9]);
        result[10] = Math.abs(matrix[10]);
        result[11] = Math.abs(matrix[11]);
        result[12] = Math.abs(matrix[12]);
        result[13] = Math.abs(matrix[13]);
        result[14] = Math.abs(matrix[14]);
        result[15] = Math.abs(matrix[15]);

        return result;
    };

    /**
     * Compares the provided matrices componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Matrix4} [left] The first matrix.
     * @param {Matrix4} [right] The second matrix.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     *
     * @example
     * //compares two Matrix4 instances
     *
     * // a = [10.0, 14.0, 18.0, 22.0]
     * //     [11.0, 15.0, 19.0, 23.0]
     * //     [12.0, 16.0, 20.0, 24.0]
     * //     [13.0, 17.0, 21.0, 25.0]
     *
     * // b = [10.0, 14.0, 18.0, 22.0]
     * //     [11.0, 15.0, 19.0, 23.0]
     * //     [12.0, 16.0, 20.0, 24.0]
     * //     [13.0, 17.0, 21.0, 25.0]
     *
     * if(Cesium.Matrix4.equals(a,b)) {
     *      console.log("Both matrices are equal");
     * } else {
     *      console.log("They are not equal");
     * }
     *
     * //Prints "Both matrices are equal" on the console
     */
    Matrix4.equals = function(left, right) {
        // Given that most matrices will be transformation matrices, the elements
        // are tested in order such that the test is likely to fail as early
        // as possible.  I _think_ this is just as friendly to the L1 cache
        // as testing in index order.  It is certainty faster in practice.
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                // Translation
                left[12] === right[12] &&
                left[13] === right[13] &&
                left[14] === right[14] &&

                // Rotation/scale
                left[0] === right[0] &&
                left[1] === right[1] &&
                left[2] === right[2] &&
                left[4] === right[4] &&
                left[5] === right[5] &&
                left[6] === right[6] &&
                left[8] === right[8] &&
                left[9] === right[9] &&
                left[10] === right[10] &&

                // Bottom row
                left[3] === right[3] &&
                left[7] === right[7] &&
                left[11] === right[11] &&
                left[15] === right[15]);
    };

    /**
     * Compares the provided matrices componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Matrix4} [left] The first matrix.
     * @param {Matrix4} [right] The second matrix.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     *
     * @example
     * //compares two Matrix4 instances
     *
     * // a = [10.5, 14.5, 18.5, 22.5]
     * //     [11.5, 15.5, 19.5, 23.5]
     * //     [12.5, 16.5, 20.5, 24.5]
     * //     [13.5, 17.5, 21.5, 25.5]
     *
     * // b = [10.0, 14.0, 18.0, 22.0]
     * //     [11.0, 15.0, 19.0, 23.0]
     * //     [12.0, 16.0, 20.0, 24.0]
     * //     [13.0, 17.0, 21.0, 25.0]
     *
     * if(Cesium.Matrix4.equalsEpsilon(a,b,0.1)){
     *      console.log("Difference between both the matrices is less than 0.1");
     * } else {
     *      console.log("Difference between both the matrices is not less than 0.1");
     * }
     *
     * //Prints "Difference between both the matrices is not less than 0.1" on the console
     */
    Matrix4.equalsEpsilon = function(left, right, epsilon) {
                Check.typeOf.number('epsilon', epsilon);
        
        return (left === right) ||
                (defined(left) &&
                defined(right) &&
                Math.abs(left[0] - right[0]) <= epsilon &&
                Math.abs(left[1] - right[1]) <= epsilon &&
                Math.abs(left[2] - right[2]) <= epsilon &&
                Math.abs(left[3] - right[3]) <= epsilon &&
                Math.abs(left[4] - right[4]) <= epsilon &&
                Math.abs(left[5] - right[5]) <= epsilon &&
                Math.abs(left[6] - right[6]) <= epsilon &&
                Math.abs(left[7] - right[7]) <= epsilon &&
                Math.abs(left[8] - right[8]) <= epsilon &&
                Math.abs(left[9] - right[9]) <= epsilon &&
                Math.abs(left[10] - right[10]) <= epsilon &&
                Math.abs(left[11] - right[11]) <= epsilon &&
                Math.abs(left[12] - right[12]) <= epsilon &&
                Math.abs(left[13] - right[13]) <= epsilon &&
                Math.abs(left[14] - right[14]) <= epsilon &&
                Math.abs(left[15] - right[15]) <= epsilon);
    };

    /**
     * Gets the translation portion of the provided matrix, assuming the matrix is a affine transformation matrix.
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Matrix4.getTranslation = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result.x = matrix[12];
        result.y = matrix[13];
        result.z = matrix[14];
        return result;
    };

    /**
     * Gets the upper left 3x3 rotation matrix of the provided matrix, assuming the matrix is a affine transformation matrix.
     *
     * @param {Matrix4} matrix The matrix to use.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter.
     *
     * @example
     * // returns a Matrix3 instance from a Matrix4 instance
     *
     * // m = [10.0, 14.0, 18.0, 22.0]
     * //     [11.0, 15.0, 19.0, 23.0]
     * //     [12.0, 16.0, 20.0, 24.0]
     * //     [13.0, 17.0, 21.0, 25.0]
     *
     * var b = new Cesium.Matrix3();
     * Cesium.Matrix4.getRotation(m,b);
     *
     * // b = [10.0, 14.0, 18.0]
     * //     [11.0, 15.0, 19.0]
     * //     [12.0, 16.0, 20.0]
     */
    Matrix4.getRotation = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[4];
        result[4] = matrix[5];
        result[5] = matrix[6];
        result[6] = matrix[8];
        result[7] = matrix[9];
        result[8] = matrix[10];
        return result;
    };

    var scratchInverseRotation = new Matrix3();
    var scratchMatrix3Zero = new Matrix3();
    var scratchBottomRow = new Cartesian4();
    var scratchExpectedBottomRow = new Cartesian4(0.0, 0.0, 0.0, 1.0);

     /**
      * Computes the inverse of the provided matrix using Cramers Rule.
      * If the determinant is zero, the matrix can not be inverted, and an exception is thrown.
      * If the matrix is an affine transformation matrix, it is more efficient
      * to invert it with {@link Matrix4.inverseTransformation}.
      *
      * @param {Matrix4} matrix The matrix to invert.
      * @param {Matrix4} result The object onto which to store the result.
      * @returns {Matrix4} The modified result parameter.
      *
      * @exception {RuntimeError} matrix is not invertible because its determinate is zero.
      */
    Matrix4.inverse = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        // Special case for a zero scale matrix that can occur, for example,
        // when a model's node has a [0, 0, 0] scale.
        if (Matrix3.equalsEpsilon(Matrix4.getRotation(matrix, scratchInverseRotation), scratchMatrix3Zero, CesiumMath.EPSILON7) &&
            Cartesian4.equals(Matrix4.getRow(matrix, 3, scratchBottomRow), scratchExpectedBottomRow)) {

            result[0] = 0.0;
            result[1] = 0.0;
            result[2] = 0.0;
            result[3] = 0.0;
            result[4] = 0.0;
            result[5] = 0.0;
            result[6] = 0.0;
            result[7] = 0.0;
            result[8] = 0.0;
            result[9] = 0.0;
            result[10] = 0.0;
            result[11] = 0.0;
            result[12] = -matrix[12];
            result[13] = -matrix[13];
            result[14] = -matrix[14];
            result[15] = 1.0;
            return result;
        }

        //
        // Ported from:
        //   ftp://download.intel.com/design/PentiumIII/sml/24504301.pdf
        //
        var src0 = matrix[0];
        var src1 = matrix[4];
        var src2 = matrix[8];
        var src3 = matrix[12];
        var src4 = matrix[1];
        var src5 = matrix[5];
        var src6 = matrix[9];
        var src7 = matrix[13];
        var src8 = matrix[2];
        var src9 = matrix[6];
        var src10 = matrix[10];
        var src11 = matrix[14];
        var src12 = matrix[3];
        var src13 = matrix[7];
        var src14 = matrix[11];
        var src15 = matrix[15];

        // calculate pairs for first 8 elements (cofactors)
        var tmp0 = src10 * src15;
        var tmp1 = src11 * src14;
        var tmp2 = src9 * src15;
        var tmp3 = src11 * src13;
        var tmp4 = src9 * src14;
        var tmp5 = src10 * src13;
        var tmp6 = src8 * src15;
        var tmp7 = src11 * src12;
        var tmp8 = src8 * src14;
        var tmp9 = src10 * src12;
        var tmp10 = src8 * src13;
        var tmp11 = src9 * src12;

        // calculate first 8 elements (cofactors)
        var dst0 = (tmp0 * src5 + tmp3 * src6 + tmp4 * src7) - (tmp1 * src5 + tmp2 * src6 + tmp5 * src7);
        var dst1 = (tmp1 * src4 + tmp6 * src6 + tmp9 * src7) - (tmp0 * src4 + tmp7 * src6 + tmp8 * src7);
        var dst2 = (tmp2 * src4 + tmp7 * src5 + tmp10 * src7) - (tmp3 * src4 + tmp6 * src5 + tmp11 * src7);
        var dst3 = (tmp5 * src4 + tmp8 * src5 + tmp11 * src6) - (tmp4 * src4 + tmp9 * src5 + tmp10 * src6);
        var dst4 = (tmp1 * src1 + tmp2 * src2 + tmp5 * src3) - (tmp0 * src1 + tmp3 * src2 + tmp4 * src3);
        var dst5 = (tmp0 * src0 + tmp7 * src2 + tmp8 * src3) - (tmp1 * src0 + tmp6 * src2 + tmp9 * src3);
        var dst6 = (tmp3 * src0 + tmp6 * src1 + tmp11 * src3) - (tmp2 * src0 + tmp7 * src1 + tmp10 * src3);
        var dst7 = (tmp4 * src0 + tmp9 * src1 + tmp10 * src2) - (tmp5 * src0 + tmp8 * src1 + tmp11 * src2);

        // calculate pairs for second 8 elements (cofactors)
        tmp0 = src2 * src7;
        tmp1 = src3 * src6;
        tmp2 = src1 * src7;
        tmp3 = src3 * src5;
        tmp4 = src1 * src6;
        tmp5 = src2 * src5;
        tmp6 = src0 * src7;
        tmp7 = src3 * src4;
        tmp8 = src0 * src6;
        tmp9 = src2 * src4;
        tmp10 = src0 * src5;
        tmp11 = src1 * src4;

        // calculate second 8 elements (cofactors)
        var dst8 = (tmp0 * src13 + tmp3 * src14 + tmp4 * src15) - (tmp1 * src13 + tmp2 * src14 + tmp5 * src15);
        var dst9 = (tmp1 * src12 + tmp6 * src14 + tmp9 * src15) - (tmp0 * src12 + tmp7 * src14 + tmp8 * src15);
        var dst10 = (tmp2 * src12 + tmp7 * src13 + tmp10 * src15) - (tmp3 * src12 + tmp6 * src13 + tmp11 * src15);
        var dst11 = (tmp5 * src12 + tmp8 * src13 + tmp11 * src14) - (tmp4 * src12 + tmp9 * src13 + tmp10 * src14);
        var dst12 = (tmp2 * src10 + tmp5 * src11 + tmp1 * src9) - (tmp4 * src11 + tmp0 * src9 + tmp3 * src10);
        var dst13 = (tmp8 * src11 + tmp0 * src8 + tmp7 * src10) - (tmp6 * src10 + tmp9 * src11 + tmp1 * src8);
        var dst14 = (tmp6 * src9 + tmp11 * src11 + tmp3 * src8) - (tmp10 * src11 + tmp2 * src8 + tmp7 * src9);
        var dst15 = (tmp10 * src10 + tmp4 * src8 + tmp9 * src9) - (tmp8 * src9 + tmp11 * src10 + tmp5 * src8);

        // calculate determinant
        var det = src0 * dst0 + src1 * dst1 + src2 * dst2 + src3 * dst3;

        if (Math.abs(det) < CesiumMath.EPSILON20) {
            throw new RuntimeError('matrix is not invertible because its determinate is zero.');
        }

        // calculate matrix inverse
        det = 1.0 / det;

        result[0] = dst0 * det;
        result[1] = dst1 * det;
        result[2] = dst2 * det;
        result[3] = dst3 * det;
        result[4] = dst4 * det;
        result[5] = dst5 * det;
        result[6] = dst6 * det;
        result[7] = dst7 * det;
        result[8] = dst8 * det;
        result[9] = dst9 * det;
        result[10] = dst10 * det;
        result[11] = dst11 * det;
        result[12] = dst12 * det;
        result[13] = dst13 * det;
        result[14] = dst14 * det;
        result[15] = dst15 * det;
        return result;
    };

    /**
     * Computes the inverse of the provided matrix assuming it is
     * an affine transformation matrix, where the upper left 3x3 elements
     * are a rotation matrix, and the upper three elements in the fourth
     * column are the translation.  The bottom row is assumed to be [0, 0, 0, 1].
     * The matrix is not verified to be in the proper form.
     * This method is faster than computing the inverse for a general 4x4
     * matrix using {@link Matrix4.inverse}.
     *
     * @param {Matrix4} matrix The matrix to invert.
     * @param {Matrix4} result The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter.
     */
    Matrix4.inverseTransformation = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        //This function is an optimized version of the below 4 lines.
        //var rT = Matrix3.transpose(Matrix4.getRotation(matrix));
        //var rTN = Matrix3.negate(rT);
        //var rTT = Matrix3.multiplyByVector(rTN, Matrix4.getTranslation(matrix));
        //return Matrix4.fromRotationTranslation(rT, rTT, result);

        var matrix0 = matrix[0];
        var matrix1 = matrix[1];
        var matrix2 = matrix[2];
        var matrix4 = matrix[4];
        var matrix5 = matrix[5];
        var matrix6 = matrix[6];
        var matrix8 = matrix[8];
        var matrix9 = matrix[9];
        var matrix10 = matrix[10];

        var vX = matrix[12];
        var vY = matrix[13];
        var vZ = matrix[14];

        var x = -matrix0 * vX - matrix1 * vY - matrix2 * vZ;
        var y = -matrix4 * vX - matrix5 * vY - matrix6 * vZ;
        var z = -matrix8 * vX - matrix9 * vY - matrix10 * vZ;

        result[0] = matrix0;
        result[1] = matrix4;
        result[2] = matrix8;
        result[3] = 0.0;
        result[4] = matrix1;
        result[5] = matrix5;
        result[6] = matrix9;
        result[7] = 0.0;
        result[8] = matrix2;
        result[9] = matrix6;
        result[10] = matrix10;
        result[11] = 0.0;
        result[12] = x;
        result[13] = y;
        result[14] = z;
        result[15] = 1.0;
        return result;
    };

    /**
     * An immutable Matrix4 instance initialized to the identity matrix.
     *
     * @type {Matrix4}
     * @constant
     */
    Matrix4.IDENTITY = freezeObject(new Matrix4(1.0, 0.0, 0.0, 0.0,
                                                0.0, 1.0, 0.0, 0.0,
                                                0.0, 0.0, 1.0, 0.0,
                                                0.0, 0.0, 0.0, 1.0));

    /**
     * An immutable Matrix4 instance initialized to the zero matrix.
     *
     * @type {Matrix4}
     * @constant
     */
    Matrix4.ZERO = freezeObject(new Matrix4(0.0, 0.0, 0.0, 0.0,
                                            0.0, 0.0, 0.0, 0.0,
                                            0.0, 0.0, 0.0, 0.0,
                                            0.0, 0.0, 0.0, 0.0));

    /**
     * The index into Matrix4 for column 0, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN0ROW0 = 0;

    /**
     * The index into Matrix4 for column 0, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN0ROW1 = 1;

    /**
     * The index into Matrix4 for column 0, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN0ROW2 = 2;

    /**
     * The index into Matrix4 for column 0, row 3.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN0ROW3 = 3;

    /**
     * The index into Matrix4 for column 1, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN1ROW0 = 4;

    /**
     * The index into Matrix4 for column 1, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN1ROW1 = 5;

    /**
     * The index into Matrix4 for column 1, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN1ROW2 = 6;

    /**
     * The index into Matrix4 for column 1, row 3.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN1ROW3 = 7;

    /**
     * The index into Matrix4 for column 2, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN2ROW0 = 8;

    /**
     * The index into Matrix4 for column 2, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN2ROW1 = 9;

    /**
     * The index into Matrix4 for column 2, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN2ROW2 = 10;

    /**
     * The index into Matrix4 for column 2, row 3.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN2ROW3 = 11;

    /**
     * The index into Matrix4 for column 3, row 0.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN3ROW0 = 12;

    /**
     * The index into Matrix4 for column 3, row 1.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN3ROW1 = 13;

    /**
     * The index into Matrix4 for column 3, row 2.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN3ROW2 = 14;

    /**
     * The index into Matrix4 for column 3, row 3.
     *
     * @type {Number}
     * @constant
     */
    Matrix4.COLUMN3ROW3 = 15;

    defineProperties(Matrix4.prototype, {
        /**
         * Gets the number of items in the collection.
         * @memberof Matrix4.prototype
         *
         * @type {Number}
         */
        length : {
            get : function() {
                return Matrix4.packedLength;
            }
        }
    });

    /**
     * Duplicates the provided Matrix4 instance.
     *
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if one was not provided.
     */
    Matrix4.prototype.clone = function(result) {
        return Matrix4.clone(this, result);
    };

    /**
     * Compares this matrix to the provided matrix componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Matrix4} [right] The right hand side matrix.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Matrix4.prototype.equals = function(right) {
        return Matrix4.equals(this, right);
    };

    /**
     * @private
     */
    Matrix4.equalsArray = function(matrix, array, offset) {
        return matrix[0] === array[offset] &&
               matrix[1] === array[offset + 1] &&
               matrix[2] === array[offset + 2] &&
               matrix[3] === array[offset + 3] &&
               matrix[4] === array[offset + 4] &&
               matrix[5] === array[offset + 5] &&
               matrix[6] === array[offset + 6] &&
               matrix[7] === array[offset + 7] &&
               matrix[8] === array[offset + 8] &&
               matrix[9] === array[offset + 9] &&
               matrix[10] === array[offset + 10] &&
               matrix[11] === array[offset + 11] &&
               matrix[12] === array[offset + 12] &&
               matrix[13] === array[offset + 13] &&
               matrix[14] === array[offset + 14] &&
               matrix[15] === array[offset + 15];
    };

    /**
     * Compares this matrix to the provided matrix componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Matrix4} [right] The right hand side matrix.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    Matrix4.prototype.equalsEpsilon = function(right, epsilon) {
        return Matrix4.equalsEpsilon(this, right, epsilon);
    };

    /**
     * Computes a string representing this Matrix with each row being
     * on a separate line and in the format '(column0, column1, column2, column3)'.
     *
     * @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1, column2, column3)'.
     */
    Matrix4.prototype.toString = function() {
        return '(' + this[0] + ', ' + this[4] + ', ' + this[8] + ', ' + this[12] +')\n' +
               '(' + this[1] + ', ' + this[5] + ', ' + this[9] + ', ' + this[13] +')\n' +
               '(' + this[2] + ', ' + this[6] + ', ' + this[10] + ', ' + this[14] +')\n' +
               '(' + this[3] + ', ' + this[7] + ', ' + this[11] + ', ' + this[15] +')';
    };

    return Matrix4;
});

/*global define*/
define('Core/BoundingSphere',[
        './Cartesian3',
        './Cartographic',
        './Check',
        './defaultValue',
        './defined',
        './Ellipsoid',
        './GeographicProjection',
        './Intersect',
        './Interval',
        './Matrix3',
        './Matrix4',
        './Rectangle'
    ], function(
        Cartesian3,
        Cartographic,
        Check,
        defaultValue,
        defined,
        Ellipsoid,
        GeographicProjection,
        Intersect,
        Interval,
        Matrix3,
        Matrix4,
        Rectangle) {
    'use strict';

    /**
     * A bounding sphere with a center and a radius.
     * @alias BoundingSphere
     * @constructor
     *
     * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the bounding sphere.
     * @param {Number} [radius=0.0] The radius of the bounding sphere.
     *
     * @see AxisAlignedBoundingBox
     * @see BoundingRectangle
     * @see Packable
     */
    function BoundingSphere(center, radius) {
        /**
         * The center point of the sphere.
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO));

        /**
         * The radius of the sphere.
         * @type {Number}
         * @default 0.0
         */
        this.radius = defaultValue(radius, 0.0);
    }

    var fromPointsXMin = new Cartesian3();
    var fromPointsYMin = new Cartesian3();
    var fromPointsZMin = new Cartesian3();
    var fromPointsXMax = new Cartesian3();
    var fromPointsYMax = new Cartesian3();
    var fromPointsZMax = new Cartesian3();
    var fromPointsCurrentPos = new Cartesian3();
    var fromPointsScratch = new Cartesian3();
    var fromPointsRitterCenter = new Cartesian3();
    var fromPointsMinBoxPt = new Cartesian3();
    var fromPointsMaxBoxPt = new Cartesian3();
    var fromPointsNaiveCenterScratch = new Cartesian3();

    /**
     * Computes a tight-fitting bounding sphere enclosing a list of 3D Cartesian points.
     * The bounding sphere is computed by running two algorithms, a naive algorithm and
     * Ritter's algorithm. The smaller of the two spheres is used to ensure a tight fit.
     *
     * @param {Cartesian3[]} positions An array of points that the bounding sphere will enclose.  Each point must have <code>x</code>, <code>y</code>, and <code>z</code> properties.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
     *
     * @see {@link http://blogs.agi.com/insight3d/index.php/2008/02/04/a-bounding/|Bounding Sphere computation article}
     */
    BoundingSphere.fromPoints = function(positions, result) {
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        if (!defined(positions) || positions.length === 0) {
            result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
            result.radius = 0.0;
            return result;
        }

        var currentPos = Cartesian3.clone(positions[0], fromPointsCurrentPos);

        var xMin = Cartesian3.clone(currentPos, fromPointsXMin);
        var yMin = Cartesian3.clone(currentPos, fromPointsYMin);
        var zMin = Cartesian3.clone(currentPos, fromPointsZMin);

        var xMax = Cartesian3.clone(currentPos, fromPointsXMax);
        var yMax = Cartesian3.clone(currentPos, fromPointsYMax);
        var zMax = Cartesian3.clone(currentPos, fromPointsZMax);

        var numPositions = positions.length;
        for (var i = 1; i < numPositions; i++) {
            Cartesian3.clone(positions[i], currentPos);

            var x = currentPos.x;
            var y = currentPos.y;
            var z = currentPos.z;

            // Store points containing the the smallest and largest components
            if (x < xMin.x) {
                Cartesian3.clone(currentPos, xMin);
            }

            if (x > xMax.x) {
                Cartesian3.clone(currentPos, xMax);
            }

            if (y < yMin.y) {
                Cartesian3.clone(currentPos, yMin);
            }

            if (y > yMax.y) {
                Cartesian3.clone(currentPos, yMax);
            }

            if (z < zMin.z) {
                Cartesian3.clone(currentPos, zMin);
            }

            if (z > zMax.z) {
                Cartesian3.clone(currentPos, zMax);
            }
        }

        // Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.).
        var xSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(xMax, xMin, fromPointsScratch));
        var ySpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(yMax, yMin, fromPointsScratch));
        var zSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(zMax, zMin, fromPointsScratch));

        // Set the diameter endpoints to the largest span.
        var diameter1 = xMin;
        var diameter2 = xMax;
        var maxSpan = xSpan;
        if (ySpan > maxSpan) {
            maxSpan = ySpan;
            diameter1 = yMin;
            diameter2 = yMax;
        }
        if (zSpan > maxSpan) {
            maxSpan = zSpan;
            diameter1 = zMin;
            diameter2 = zMax;
        }

        // Calculate the center of the initial sphere found by Ritter's algorithm
        var ritterCenter = fromPointsRitterCenter;
        ritterCenter.x = (diameter1.x + diameter2.x) * 0.5;
        ritterCenter.y = (diameter1.y + diameter2.y) * 0.5;
        ritterCenter.z = (diameter1.z + diameter2.z) * 0.5;

        // Calculate the radius of the initial sphere found by Ritter's algorithm
        var radiusSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(diameter2, ritterCenter, fromPointsScratch));
        var ritterRadius = Math.sqrt(radiusSquared);

        // Find the center of the sphere found using the Naive method.
        var minBoxPt = fromPointsMinBoxPt;
        minBoxPt.x = xMin.x;
        minBoxPt.y = yMin.y;
        minBoxPt.z = zMin.z;

        var maxBoxPt = fromPointsMaxBoxPt;
        maxBoxPt.x = xMax.x;
        maxBoxPt.y = yMax.y;
        maxBoxPt.z = zMax.z;

        var naiveCenter = Cartesian3.multiplyByScalar(Cartesian3.add(minBoxPt, maxBoxPt, fromPointsScratch), 0.5, fromPointsNaiveCenterScratch);

        // Begin 2nd pass to find naive radius and modify the ritter sphere.
        var naiveRadius = 0;
        for (i = 0; i < numPositions; i++) {
            Cartesian3.clone(positions[i], currentPos);

            // Find the furthest point from the naive center to calculate the naive radius.
            var r = Cartesian3.magnitude(Cartesian3.subtract(currentPos, naiveCenter, fromPointsScratch));
            if (r > naiveRadius) {
                naiveRadius = r;
            }

            // Make adjustments to the Ritter Sphere to include all points.
            var oldCenterToPointSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(currentPos, ritterCenter, fromPointsScratch));
            if (oldCenterToPointSquared > radiusSquared) {
                var oldCenterToPoint = Math.sqrt(oldCenterToPointSquared);
                // Calculate new radius to include the point that lies outside
                ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5;
                radiusSquared = ritterRadius * ritterRadius;
                // Calculate center of new Ritter sphere
                var oldToNew = oldCenterToPoint - ritterRadius;
                ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint;
                ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint;
                ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint;
            }
        }

        if (ritterRadius < naiveRadius) {
            Cartesian3.clone(ritterCenter, result.center);
            result.radius = ritterRadius;
        } else {
            Cartesian3.clone(naiveCenter, result.center);
            result.radius = naiveRadius;
        }

        return result;
    };

    var defaultProjection = new GeographicProjection();
    var fromRectangle2DLowerLeft = new Cartesian3();
    var fromRectangle2DUpperRight = new Cartesian3();
    var fromRectangle2DSouthwest = new Cartographic();
    var fromRectangle2DNortheast = new Cartographic();

    /**
     * Computes a bounding sphere from an rectangle projected in 2D.
     *
     * @param {Rectangle} rectangle The rectangle around which to create a bounding sphere.
     * @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.fromRectangle2D = function(rectangle, projection, result) {
        return BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, 0.0, 0.0, result);
    };

    /**
     * Computes a bounding sphere from an rectangle projected in 2D.  The bounding sphere accounts for the
     * object's minimum and maximum heights over the rectangle.
     *
     * @param {Rectangle} rectangle The rectangle around which to create a bounding sphere.
     * @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
     * @param {Number} [minimumHeight=0.0] The minimum height over the rectangle.
     * @param {Number} [maximumHeight=0.0] The maximum height over the rectangle.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.fromRectangleWithHeights2D = function(rectangle, projection, minimumHeight, maximumHeight, result) {
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        if (!defined(rectangle)) {
            result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
            result.radius = 0.0;
            return result;
        }

        projection = defaultValue(projection, defaultProjection);

        Rectangle.southwest(rectangle, fromRectangle2DSouthwest);
        fromRectangle2DSouthwest.height = minimumHeight;
        Rectangle.northeast(rectangle, fromRectangle2DNortheast);
        fromRectangle2DNortheast.height = maximumHeight;

        var lowerLeft = projection.project(fromRectangle2DSouthwest, fromRectangle2DLowerLeft);
        var upperRight = projection.project(fromRectangle2DNortheast, fromRectangle2DUpperRight);

        var width = upperRight.x - lowerLeft.x;
        var height = upperRight.y - lowerLeft.y;
        var elevation = upperRight.z - lowerLeft.z;

        result.radius = Math.sqrt(width * width + height * height + elevation * elevation) * 0.5;
        var center = result.center;
        center.x = lowerLeft.x + width * 0.5;
        center.y = lowerLeft.y + height * 0.5;
        center.z = lowerLeft.z + elevation * 0.5;
        return result;
    };

    var fromRectangle3DScratch = [];

    /**
     * Computes a bounding sphere from an rectangle in 3D. The bounding sphere is created using a subsample of points
     * on the ellipsoid and contained in the rectangle. It may not be accurate for all rectangles on all types of ellipsoids.
     *
     * @param {Rectangle} rectangle The valid rectangle used to create a bounding sphere.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the rectangle.
     * @param {Number} [surfaceHeight=0.0] The height above the surface of the ellipsoid.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.fromRectangle3D = function(rectangle, ellipsoid, surfaceHeight, result) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        surfaceHeight = defaultValue(surfaceHeight, 0.0);

        var positions;
        if (defined(rectangle)) {
            positions = Rectangle.subsample(rectangle, ellipsoid, surfaceHeight, fromRectangle3DScratch);
        }

        return BoundingSphere.fromPoints(positions, result);
    };

    /**
     * Computes a tight-fitting bounding sphere enclosing a list of 3D points, where the points are
     * stored in a flat array in X, Y, Z, order.  The bounding sphere is computed by running two
     * algorithms, a naive algorithm and Ritter's algorithm. The smaller of the two spheres is used to
     * ensure a tight fit.
     *
     * @param {Number[]} positions An array of points that the bounding sphere will enclose.  Each point
     *        is formed from three elements in the array in the order X, Y, Z.
     * @param {Cartesian3} [center=Cartesian3.ZERO] The position to which the positions are relative, which need not be the
     *        origin of the coordinate system.  This is useful when the positions are to be used for
     *        relative-to-center (RTC) rendering.
     * @param {Number} [stride=3] The number of array elements per vertex.  It must be at least 3, but it may
     *        be higher.  Regardless of the value of this parameter, the X coordinate of the first position
     *        is at array index 0, the Y coordinate is at array index 1, and the Z coordinate is at array index
     *        2.  When stride is 3, the X coordinate of the next position then begins at array index 3.  If
     *        the stride is 5, however, two array elements are skipped and the next position begins at array
     *        index 5.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
     *
     * @example
     * // Compute the bounding sphere from 3 positions, each specified relative to a center.
     * // In addition to the X, Y, and Z coordinates, the points array contains two additional
     * // elements per point which are ignored for the purpose of computing the bounding sphere.
     * var center = new Cesium.Cartesian3(1.0, 2.0, 3.0);
     * var points = [1.0, 2.0, 3.0, 0.1, 0.2,
     *               4.0, 5.0, 6.0, 0.1, 0.2,
     *               7.0, 8.0, 9.0, 0.1, 0.2];
     * var sphere = Cesium.BoundingSphere.fromVertices(points, center, 5);
     *
     * @see {@link http://blogs.agi.com/insight3d/index.php/2008/02/04/a-bounding/|Bounding Sphere computation article}
     */
    BoundingSphere.fromVertices = function(positions, center, stride, result) {
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        if (!defined(positions) || positions.length === 0) {
            result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
            result.radius = 0.0;
            return result;
        }

        center = defaultValue(center, Cartesian3.ZERO);

        stride = defaultValue(stride, 3);

                Check.typeOf.number.greaterThanOrEquals('stride', stride, 3);
        
        var currentPos = fromPointsCurrentPos;
        currentPos.x = positions[0] + center.x;
        currentPos.y = positions[1] + center.y;
        currentPos.z = positions[2] + center.z;

        var xMin = Cartesian3.clone(currentPos, fromPointsXMin);
        var yMin = Cartesian3.clone(currentPos, fromPointsYMin);
        var zMin = Cartesian3.clone(currentPos, fromPointsZMin);

        var xMax = Cartesian3.clone(currentPos, fromPointsXMax);
        var yMax = Cartesian3.clone(currentPos, fromPointsYMax);
        var zMax = Cartesian3.clone(currentPos, fromPointsZMax);

        var numElements = positions.length;
        for (var i = 0; i < numElements; i += stride) {
            var x = positions[i] + center.x;
            var y = positions[i + 1] + center.y;
            var z = positions[i + 2] + center.z;

            currentPos.x = x;
            currentPos.y = y;
            currentPos.z = z;

            // Store points containing the the smallest and largest components
            if (x < xMin.x) {
                Cartesian3.clone(currentPos, xMin);
            }

            if (x > xMax.x) {
                Cartesian3.clone(currentPos, xMax);
            }

            if (y < yMin.y) {
                Cartesian3.clone(currentPos, yMin);
            }

            if (y > yMax.y) {
                Cartesian3.clone(currentPos, yMax);
            }

            if (z < zMin.z) {
                Cartesian3.clone(currentPos, zMin);
            }

            if (z > zMax.z) {
                Cartesian3.clone(currentPos, zMax);
            }
        }

        // Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.).
        var xSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(xMax, xMin, fromPointsScratch));
        var ySpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(yMax, yMin, fromPointsScratch));
        var zSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(zMax, zMin, fromPointsScratch));

        // Set the diameter endpoints to the largest span.
        var diameter1 = xMin;
        var diameter2 = xMax;
        var maxSpan = xSpan;
        if (ySpan > maxSpan) {
            maxSpan = ySpan;
            diameter1 = yMin;
            diameter2 = yMax;
        }
        if (zSpan > maxSpan) {
            maxSpan = zSpan;
            diameter1 = zMin;
            diameter2 = zMax;
        }

        // Calculate the center of the initial sphere found by Ritter's algorithm
        var ritterCenter = fromPointsRitterCenter;
        ritterCenter.x = (diameter1.x + diameter2.x) * 0.5;
        ritterCenter.y = (diameter1.y + diameter2.y) * 0.5;
        ritterCenter.z = (diameter1.z + diameter2.z) * 0.5;

        // Calculate the radius of the initial sphere found by Ritter's algorithm
        var radiusSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(diameter2, ritterCenter, fromPointsScratch));
        var ritterRadius = Math.sqrt(radiusSquared);

        // Find the center of the sphere found using the Naive method.
        var minBoxPt = fromPointsMinBoxPt;
        minBoxPt.x = xMin.x;
        minBoxPt.y = yMin.y;
        minBoxPt.z = zMin.z;

        var maxBoxPt = fromPointsMaxBoxPt;
        maxBoxPt.x = xMax.x;
        maxBoxPt.y = yMax.y;
        maxBoxPt.z = zMax.z;

        var naiveCenter = Cartesian3.multiplyByScalar(Cartesian3.add(minBoxPt, maxBoxPt, fromPointsScratch), 0.5, fromPointsNaiveCenterScratch);

        // Begin 2nd pass to find naive radius and modify the ritter sphere.
        var naiveRadius = 0;
        for (i = 0; i < numElements; i += stride) {
            currentPos.x = positions[i] + center.x;
            currentPos.y = positions[i + 1] + center.y;
            currentPos.z = positions[i + 2] + center.z;

            // Find the furthest point from the naive center to calculate the naive radius.
            var r = Cartesian3.magnitude(Cartesian3.subtract(currentPos, naiveCenter, fromPointsScratch));
            if (r > naiveRadius) {
                naiveRadius = r;
            }

            // Make adjustments to the Ritter Sphere to include all points.
            var oldCenterToPointSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(currentPos, ritterCenter, fromPointsScratch));
            if (oldCenterToPointSquared > radiusSquared) {
                var oldCenterToPoint = Math.sqrt(oldCenterToPointSquared);
                // Calculate new radius to include the point that lies outside
                ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5;
                radiusSquared = ritterRadius * ritterRadius;
                // Calculate center of new Ritter sphere
                var oldToNew = oldCenterToPoint - ritterRadius;
                ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint;
                ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint;
                ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint;
            }
        }

        if (ritterRadius < naiveRadius) {
            Cartesian3.clone(ritterCenter, result.center);
            result.radius = ritterRadius;
        } else {
            Cartesian3.clone(naiveCenter, result.center);
            result.radius = naiveRadius;
        }

        return result;
    };

    /**
     * Computes a tight-fitting bounding sphere enclosing a list of {@link EncodedCartesian3}s, where the points are
     * stored in parallel flat arrays in X, Y, Z, order.  The bounding sphere is computed by running two
     * algorithms, a naive algorithm and Ritter's algorithm. The smaller of the two spheres is used to
     * ensure a tight fit.
     *
     * @param {Number[]} positionsHigh An array of high bits of the encoded cartesians that the bounding sphere will enclose.  Each point
     *        is formed from three elements in the array in the order X, Y, Z.
     * @param {Number[]} positionsLow An array of low bits of the encoded cartesians that the bounding sphere will enclose.  Each point
     *        is formed from three elements in the array in the order X, Y, Z.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
     *
     * @see {@link http://blogs.agi.com/insight3d/index.php/2008/02/04/a-bounding/|Bounding Sphere computation article}
     */
    BoundingSphere.fromEncodedCartesianVertices = function(positionsHigh, positionsLow, result) {
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        if (!defined(positionsHigh) || !defined(positionsLow) || positionsHigh.length !== positionsLow.length || positionsHigh.length === 0) {
            result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
            result.radius = 0.0;
            return result;
        }

        var currentPos = fromPointsCurrentPos;
        currentPos.x = positionsHigh[0] + positionsLow[0];
        currentPos.y = positionsHigh[1] + positionsLow[1];
        currentPos.z = positionsHigh[2] + positionsLow[2];

        var xMin = Cartesian3.clone(currentPos, fromPointsXMin);
        var yMin = Cartesian3.clone(currentPos, fromPointsYMin);
        var zMin = Cartesian3.clone(currentPos, fromPointsZMin);

        var xMax = Cartesian3.clone(currentPos, fromPointsXMax);
        var yMax = Cartesian3.clone(currentPos, fromPointsYMax);
        var zMax = Cartesian3.clone(currentPos, fromPointsZMax);

        var numElements = positionsHigh.length;
        for (var i = 0; i < numElements; i += 3) {
            var x = positionsHigh[i] + positionsLow[i];
            var y = positionsHigh[i + 1] + positionsLow[i + 1];
            var z = positionsHigh[i + 2] + positionsLow[i + 2];

            currentPos.x = x;
            currentPos.y = y;
            currentPos.z = z;

            // Store points containing the the smallest and largest components
            if (x < xMin.x) {
                Cartesian3.clone(currentPos, xMin);
            }

            if (x > xMax.x) {
                Cartesian3.clone(currentPos, xMax);
            }

            if (y < yMin.y) {
                Cartesian3.clone(currentPos, yMin);
            }

            if (y > yMax.y) {
                Cartesian3.clone(currentPos, yMax);
            }

            if (z < zMin.z) {
                Cartesian3.clone(currentPos, zMin);
            }

            if (z > zMax.z) {
                Cartesian3.clone(currentPos, zMax);
            }
        }

        // Compute x-, y-, and z-spans (Squared distances b/n each component's min. and max.).
        var xSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(xMax, xMin, fromPointsScratch));
        var ySpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(yMax, yMin, fromPointsScratch));
        var zSpan = Cartesian3.magnitudeSquared(Cartesian3.subtract(zMax, zMin, fromPointsScratch));

        // Set the diameter endpoints to the largest span.
        var diameter1 = xMin;
        var diameter2 = xMax;
        var maxSpan = xSpan;
        if (ySpan > maxSpan) {
            maxSpan = ySpan;
            diameter1 = yMin;
            diameter2 = yMax;
        }
        if (zSpan > maxSpan) {
            maxSpan = zSpan;
            diameter1 = zMin;
            diameter2 = zMax;
        }

        // Calculate the center of the initial sphere found by Ritter's algorithm
        var ritterCenter = fromPointsRitterCenter;
        ritterCenter.x = (diameter1.x + diameter2.x) * 0.5;
        ritterCenter.y = (diameter1.y + diameter2.y) * 0.5;
        ritterCenter.z = (diameter1.z + diameter2.z) * 0.5;

        // Calculate the radius of the initial sphere found by Ritter's algorithm
        var radiusSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(diameter2, ritterCenter, fromPointsScratch));
        var ritterRadius = Math.sqrt(radiusSquared);

        // Find the center of the sphere found using the Naive method.
        var minBoxPt = fromPointsMinBoxPt;
        minBoxPt.x = xMin.x;
        minBoxPt.y = yMin.y;
        minBoxPt.z = zMin.z;

        var maxBoxPt = fromPointsMaxBoxPt;
        maxBoxPt.x = xMax.x;
        maxBoxPt.y = yMax.y;
        maxBoxPt.z = zMax.z;

        var naiveCenter = Cartesian3.multiplyByScalar(Cartesian3.add(minBoxPt, maxBoxPt, fromPointsScratch), 0.5, fromPointsNaiveCenterScratch);

        // Begin 2nd pass to find naive radius and modify the ritter sphere.
        var naiveRadius = 0;
        for (i = 0; i < numElements; i += 3) {
            currentPos.x = positionsHigh[i] + positionsLow[i];
            currentPos.y = positionsHigh[i + 1] + positionsLow[i + 1];
            currentPos.z = positionsHigh[i + 2] + positionsLow[i + 2];

            // Find the furthest point from the naive center to calculate the naive radius.
            var r = Cartesian3.magnitude(Cartesian3.subtract(currentPos, naiveCenter, fromPointsScratch));
            if (r > naiveRadius) {
                naiveRadius = r;
            }

            // Make adjustments to the Ritter Sphere to include all points.
            var oldCenterToPointSquared = Cartesian3.magnitudeSquared(Cartesian3.subtract(currentPos, ritterCenter, fromPointsScratch));
            if (oldCenterToPointSquared > radiusSquared) {
                var oldCenterToPoint = Math.sqrt(oldCenterToPointSquared);
                // Calculate new radius to include the point that lies outside
                ritterRadius = (ritterRadius + oldCenterToPoint) * 0.5;
                radiusSquared = ritterRadius * ritterRadius;
                // Calculate center of new Ritter sphere
                var oldToNew = oldCenterToPoint - ritterRadius;
                ritterCenter.x = (ritterRadius * ritterCenter.x + oldToNew * currentPos.x) / oldCenterToPoint;
                ritterCenter.y = (ritterRadius * ritterCenter.y + oldToNew * currentPos.y) / oldCenterToPoint;
                ritterCenter.z = (ritterRadius * ritterCenter.z + oldToNew * currentPos.z) / oldCenterToPoint;
            }
        }

        if (ritterRadius < naiveRadius) {
            Cartesian3.clone(ritterCenter, result.center);
            result.radius = ritterRadius;
        } else {
            Cartesian3.clone(naiveCenter, result.center);
            result.radius = naiveRadius;
        }

        return result;
    };

    /**
     * Computes a bounding sphere from the corner points of an axis-aligned bounding box.  The sphere
     * tighly and fully encompases the box.
     *
     * @param {Cartesian3} [corner] The minimum height over the rectangle.
     * @param {Cartesian3} [oppositeCorner] The maximum height over the rectangle.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     *
     * @example
     * // Create a bounding sphere around the unit cube
     * var sphere = Cesium.BoundingSphere.fromCornerPoints(new Cesium.Cartesian3(-0.5, -0.5, -0.5), new Cesium.Cartesian3(0.5, 0.5, 0.5));
     */
    BoundingSphere.fromCornerPoints = function(corner, oppositeCorner, result) {
                Check.typeOf.object('corner', corner);
        Check.typeOf.object('oppositeCorner', oppositeCorner);
        
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        var center = result.center;
        Cartesian3.add(corner, oppositeCorner, center);
        Cartesian3.multiplyByScalar(center, 0.5, center);
        result.radius = Cartesian3.distance(center, oppositeCorner);
        return result;
    };

    /**
     * Creates a bounding sphere encompassing an ellipsoid.
     *
     * @param {Ellipsoid} ellipsoid The ellipsoid around which to create a bounding sphere.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     *
     * @example
     * var boundingSphere = Cesium.BoundingSphere.fromEllipsoid(ellipsoid);
     */
    BoundingSphere.fromEllipsoid = function(ellipsoid, result) {
                Check.typeOf.object('ellipsoid', ellipsoid);
        
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        Cartesian3.clone(Cartesian3.ZERO, result.center);
        result.radius = ellipsoid.maximumRadius;
        return result;
    };

    var fromBoundingSpheresScratch = new Cartesian3();

    /**
     * Computes a tight-fitting bounding sphere enclosing the provided array of bounding spheres.
     *
     * @param {BoundingSphere[]} boundingSpheres The array of bounding spheres.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.fromBoundingSpheres = function(boundingSpheres, result) {
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        if (!defined(boundingSpheres) || boundingSpheres.length === 0) {
            result.center = Cartesian3.clone(Cartesian3.ZERO, result.center);
            result.radius = 0.0;
            return result;
        }

        var length = boundingSpheres.length;
        if (length === 1) {
            return BoundingSphere.clone(boundingSpheres[0], result);
        }

        if (length === 2) {
            return BoundingSphere.union(boundingSpheres[0], boundingSpheres[1], result);
        }

        var positions = [];
        for (var i = 0; i < length; i++) {
            positions.push(boundingSpheres[i].center);
        }

        result = BoundingSphere.fromPoints(positions, result);

        var center = result.center;
        var radius = result.radius;
        for (i = 0; i < length; i++) {
            var tmp = boundingSpheres[i];
            radius = Math.max(radius, Cartesian3.distance(center, tmp.center, fromBoundingSpheresScratch) + tmp.radius);
        }
        result.radius = radius;

        return result;
    };

    var fromOrientedBoundingBoxScratchU = new Cartesian3();
    var fromOrientedBoundingBoxScratchV = new Cartesian3();
    var fromOrientedBoundingBoxScratchW = new Cartesian3();

    /**
     * Computes a tight-fitting bounding sphere enclosing the provided oriented bounding box.
     *
     * @param {OrientedBoundingBox} orientedBoundingBox The oriented bounding box.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.fromOrientedBoundingBox = function(orientedBoundingBox, result) {
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        var halfAxes = orientedBoundingBox.halfAxes;
        var u = Matrix3.getColumn(halfAxes, 0, fromOrientedBoundingBoxScratchU);
        var v = Matrix3.getColumn(halfAxes, 1, fromOrientedBoundingBoxScratchV);
        var w = Matrix3.getColumn(halfAxes, 2, fromOrientedBoundingBoxScratchW);

        var uHalf = Cartesian3.magnitude(u);
        var vHalf = Cartesian3.magnitude(v);
        var wHalf = Cartesian3.magnitude(w);

        result.center = Cartesian3.clone(orientedBoundingBox.center, result.center);
        result.radius = Math.max(uHalf, vHalf, wHalf);

        return result;
    };

    /**
     * Duplicates a BoundingSphere instance.
     *
     * @param {BoundingSphere} sphere The bounding sphere to duplicate.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided. (Returns undefined if sphere is undefined)
     */
    BoundingSphere.clone = function(sphere, result) {
        if (!defined(sphere)) {
            return undefined;
        }

        if (!defined(result)) {
            return new BoundingSphere(sphere.center, sphere.radius);
        }

        result.center = Cartesian3.clone(sphere.center, result.center);
        result.radius = sphere.radius;
        return result;
    };

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    BoundingSphere.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {BoundingSphere} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    BoundingSphere.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        var center = value.center;
        array[startingIndex++] = center.x;
        array[startingIndex++] = center.y;
        array[startingIndex++] = center.z;
        array[startingIndex] = value.radius;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {BoundingSphere} [result] The object into which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if one was not provided.
     */
    BoundingSphere.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new BoundingSphere();
        }

        var center = result.center;
        center.x = array[startingIndex++];
        center.y = array[startingIndex++];
        center.z = array[startingIndex++];
        result.radius = array[startingIndex];
        return result;
    };

    var unionScratch = new Cartesian3();
    var unionScratchCenter = new Cartesian3();
    /**
     * Computes a bounding sphere that contains both the left and right bounding spheres.
     *
     * @param {BoundingSphere} left A sphere to enclose in a bounding sphere.
     * @param {BoundingSphere} right A sphere to enclose in a bounding sphere.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.union = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        var leftCenter = left.center;
        var leftRadius = left.radius;
        var rightCenter = right.center;
        var rightRadius = right.radius;

        var toRightCenter = Cartesian3.subtract(rightCenter, leftCenter, unionScratch);
        var centerSeparation = Cartesian3.magnitude(toRightCenter);

        if (leftRadius >= (centerSeparation + rightRadius)) {
            // Left sphere wins.
            left.clone(result);
            return result;
        }

        if (rightRadius >= (centerSeparation + leftRadius)) {
            // Right sphere wins.
            right.clone(result);
            return result;
        }

        // There are two tangent points, one on far side of each sphere.
        var halfDistanceBetweenTangentPoints = (leftRadius + centerSeparation + rightRadius) * 0.5;

        // Compute the center point halfway between the two tangent points.
        var center = Cartesian3.multiplyByScalar(toRightCenter,
                (-leftRadius + halfDistanceBetweenTangentPoints) / centerSeparation, unionScratchCenter);
        Cartesian3.add(center, leftCenter, center);
        Cartesian3.clone(center, result.center);
        result.radius = halfDistanceBetweenTangentPoints;

        return result;
    };

    var expandScratch = new Cartesian3();
    /**
     * Computes a bounding sphere by enlarging the provided sphere to contain the provided point.
     *
     * @param {BoundingSphere} sphere A sphere to expand.
     * @param {Cartesian3} point A point to enclose in a bounding sphere.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.expand = function(sphere, point, result) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('point', point);
        
        result = BoundingSphere.clone(sphere, result);

        var radius = Cartesian3.magnitude(Cartesian3.subtract(point, result.center, expandScratch));
        if (radius > result.radius) {
            result.radius = radius;
        }

        return result;
    };

    /**
     * Determines which side of a plane a sphere is located.
     *
     * @param {BoundingSphere} sphere The bounding sphere to test.
     * @param {Plane} plane The plane to test against.
     * @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane
     *                      the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is
     *                      on the opposite side, and {@link Intersect.INTERSECTING} if the sphere
     *                      intersects the plane.
     */
    BoundingSphere.intersectPlane = function(sphere, plane) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('plane', plane);
        
        var center = sphere.center;
        var radius = sphere.radius;
        var normal = plane.normal;
        var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance;

        if (distanceToPlane < -radius) {
            // The center point is negative side of the plane normal
            return Intersect.OUTSIDE;
        } else if (distanceToPlane < radius) {
            // The center point is positive side of the plane, but radius extends beyond it; partial overlap
            return Intersect.INTERSECTING;
        }
        return Intersect.INSIDE;
    };

    /**
     * Applies a 4x4 affine transformation matrix to a bounding sphere.
     *
     * @param {BoundingSphere} sphere The bounding sphere to apply the transformation to.
     * @param {Matrix4} transform The transformation matrix to apply to the bounding sphere.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.transform = function(sphere, transform, result) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('transform', transform);
        
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        result.center = Matrix4.multiplyByPoint(transform, sphere.center, result.center);
        result.radius = Matrix4.getMaximumScale(transform) * sphere.radius;

        return result;
    };

    var distanceSquaredToScratch = new Cartesian3();

    /**
     * Computes the estimated distance squared from the closest point on a bounding sphere to a point.
     *
     * @param {BoundingSphere} sphere The sphere.
     * @param {Cartesian3} cartesian The point
     * @returns {Number} The estimated distance squared from the bounding sphere to the point.
     *
     * @example
     * // Sort bounding spheres from back to front
     * spheres.sort(function(a, b) {
     *     return Cesium.BoundingSphere.distanceSquaredTo(b, camera.positionWC) - Cesium.BoundingSphere.distanceSquaredTo(a, camera.positionWC);
     * });
     */
    BoundingSphere.distanceSquaredTo = function(sphere, cartesian) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('cartesian', cartesian);
        
        var diff = Cartesian3.subtract(sphere.center, cartesian, distanceSquaredToScratch);
        return Cartesian3.magnitudeSquared(diff) - sphere.radius * sphere.radius;
    };

    /**
     * Applies a 4x4 affine transformation matrix to a bounding sphere where there is no scale
     * The transformation matrix is not verified to have a uniform scale of 1.
     * This method is faster than computing the general bounding sphere transform using {@link BoundingSphere.transform}.
     *
     * @param {BoundingSphere} sphere The bounding sphere to apply the transformation to.
     * @param {Matrix4} transform The transformation matrix to apply to the bounding sphere.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     *
     * @example
     * var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(positionOnEllipsoid);
     * var boundingSphere = new Cesium.BoundingSphere();
     * var newBoundingSphere = Cesium.BoundingSphere.transformWithoutScale(boundingSphere, modelMatrix);
     */
    BoundingSphere.transformWithoutScale = function(sphere, transform, result) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('transform', transform);
        
        if (!defined(result)) {
            result = new BoundingSphere();
        }

        result.center = Matrix4.multiplyByPoint(transform, sphere.center, result.center);
        result.radius = sphere.radius;

        return result;
    };

    var scratchCartesian3 = new Cartesian3();
    /**
     * The distances calculated by the vector from the center of the bounding sphere to position projected onto direction
     * plus/minus the radius of the bounding sphere.
     * <br>
     * If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
     * closest and farthest planes from position that intersect the bounding sphere.
     *
     * @param {BoundingSphere} sphere The bounding sphere to calculate the distance to.
     * @param {Cartesian3} position The position to calculate the distance from.
     * @param {Cartesian3} direction The direction from position.
     * @param {Interval} [result] A Interval to store the nearest and farthest distances.
     * @returns {Interval} The nearest and farthest distances on the bounding sphere from position in direction.
     */
    BoundingSphere.computePlaneDistances = function(sphere, position, direction, result) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('position', position);
        Check.typeOf.object('direction', direction);
        
        if (!defined(result)) {
            result = new Interval();
        }

        var toCenter = Cartesian3.subtract(sphere.center, position, scratchCartesian3);
        var mag = Cartesian3.dot(direction, toCenter);

        result.start = mag - sphere.radius;
        result.stop = mag + sphere.radius;
        return result;
    };

    var projectTo2DNormalScratch = new Cartesian3();
    var projectTo2DEastScratch = new Cartesian3();
    var projectTo2DNorthScratch = new Cartesian3();
    var projectTo2DWestScratch = new Cartesian3();
    var projectTo2DSouthScratch = new Cartesian3();
    var projectTo2DCartographicScratch = new Cartographic();
    var projectTo2DPositionsScratch = new Array(8);
    for (var n = 0; n < 8; ++n) {
        projectTo2DPositionsScratch[n] = new Cartesian3();
    }

    var projectTo2DProjection = new GeographicProjection();
    /**
     * Creates a bounding sphere in 2D from a bounding sphere in 3D world coordinates.
     *
     * @param {BoundingSphere} sphere The bounding sphere to transform to 2D.
     * @param {Object} [projection=GeographicProjection] The projection to 2D.
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.projectTo2D = function(sphere, projection, result) {
                Check.typeOf.object('sphere', sphere);
        
        projection = defaultValue(projection, projectTo2DProjection);

        var ellipsoid = projection.ellipsoid;
        var center = sphere.center;
        var radius = sphere.radius;

        var normal = ellipsoid.geodeticSurfaceNormal(center, projectTo2DNormalScratch);
        var east = Cartesian3.cross(Cartesian3.UNIT_Z, normal, projectTo2DEastScratch);
        Cartesian3.normalize(east, east);
        var north = Cartesian3.cross(normal, east, projectTo2DNorthScratch);
        Cartesian3.normalize(north, north);

        Cartesian3.multiplyByScalar(normal, radius, normal);
        Cartesian3.multiplyByScalar(north, radius, north);
        Cartesian3.multiplyByScalar(east, radius, east);

        var south = Cartesian3.negate(north, projectTo2DSouthScratch);
        var west = Cartesian3.negate(east, projectTo2DWestScratch);

        var positions = projectTo2DPositionsScratch;

        // top NE corner
        var corner = positions[0];
        Cartesian3.add(normal, north, corner);
        Cartesian3.add(corner, east, corner);

        // top NW corner
        corner = positions[1];
        Cartesian3.add(normal, north, corner);
        Cartesian3.add(corner, west, corner);

        // top SW corner
        corner = positions[2];
        Cartesian3.add(normal, south, corner);
        Cartesian3.add(corner, west, corner);

        // top SE corner
        corner = positions[3];
        Cartesian3.add(normal, south, corner);
        Cartesian3.add(corner, east, corner);

        Cartesian3.negate(normal, normal);

        // bottom NE corner
        corner = positions[4];
        Cartesian3.add(normal, north, corner);
        Cartesian3.add(corner, east, corner);

        // bottom NW corner
        corner = positions[5];
        Cartesian3.add(normal, north, corner);
        Cartesian3.add(corner, west, corner);

        // bottom SW corner
        corner = positions[6];
        Cartesian3.add(normal, south, corner);
        Cartesian3.add(corner, west, corner);

        // bottom SE corner
        corner = positions[7];
        Cartesian3.add(normal, south, corner);
        Cartesian3.add(corner, east, corner);

        var length = positions.length;
        for (var i = 0; i < length; ++i) {
            var position = positions[i];
            Cartesian3.add(center, position, position);
            var cartographic = ellipsoid.cartesianToCartographic(position, projectTo2DCartographicScratch);
            projection.project(cartographic, position);
        }

        result = BoundingSphere.fromPoints(positions, result);

        // swizzle center components
        center = result.center;
        var x = center.x;
        var y = center.y;
        var z = center.z;
        center.x = z;
        center.y = x;
        center.z = y;

        return result;
    };

    /**
     * Determines whether or not a sphere is hidden from view by the occluder.
     *
     * @param {BoundingSphere} sphere The bounding sphere surrounding the occludee object.
     * @param {Occluder} occluder The occluder.
     * @returns {Boolean} <code>true</code> if the sphere is not visible; otherwise <code>false</code>.
     */
    BoundingSphere.isOccluded = function(sphere, occluder) {
                Check.typeOf.object('sphere', sphere);
        Check.typeOf.object('occluder', occluder);
                return !occluder.isBoundingSphereVisible(sphere);
    };

    /**
     * Compares the provided BoundingSphere componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {BoundingSphere} [left] The first BoundingSphere.
     * @param {BoundingSphere} [right] The second BoundingSphere.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    BoundingSphere.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                Cartesian3.equals(left.center, right.center) &&
                left.radius === right.radius);
    };

    /**
     * Determines which side of a plane the sphere is located.
     *
     * @param {Plane} plane The plane to test against.
     * @returns {Intersect} {@link Intersect.INSIDE} if the entire sphere is on the side of the plane
     *                      the normal is pointing, {@link Intersect.OUTSIDE} if the entire sphere is
     *                      on the opposite side, and {@link Intersect.INTERSECTING} if the sphere
     *                      intersects the plane.
     */
    BoundingSphere.prototype.intersectPlane = function(plane) {
        return BoundingSphere.intersectPlane(this, plane);
    };

    /**
     * Computes the estimated distance squared from the closest point on a bounding sphere to a point.
     *
     * @param {Cartesian3} cartesian The point
     * @returns {Number} The estimated distance squared from the bounding sphere to the point.
     *
     * @example
     * // Sort bounding spheres from back to front
     * spheres.sort(function(a, b) {
     *     return b.distanceSquaredTo(camera.positionWC) - a.distanceSquaredTo(camera.positionWC);
     * });
     */
    BoundingSphere.prototype.distanceSquaredTo = function(cartesian) {
        return BoundingSphere.distanceSquaredTo(this, cartesian);
    };

    /**
     * The distances calculated by the vector from the center of the bounding sphere to position projected onto direction
     * plus/minus the radius of the bounding sphere.
     * <br>
     * If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
     * closest and farthest planes from position that intersect the bounding sphere.
     *
     * @param {Cartesian3} position The position to calculate the distance from.
     * @param {Cartesian3} direction The direction from position.
     * @param {Interval} [result] A Interval to store the nearest and farthest distances.
     * @returns {Interval} The nearest and farthest distances on the bounding sphere from position in direction.
     */
    BoundingSphere.prototype.computePlaneDistances = function(position, direction, result) {
        return BoundingSphere.computePlaneDistances(this, position, direction, result);
    };

    /**
     * Determines whether or not a sphere is hidden from view by the occluder.
     *
     * @param {Occluder} occluder The occluder.
     * @returns {Boolean} <code>true</code> if the sphere is not visible; otherwise <code>false</code>.
     */
    BoundingSphere.prototype.isOccluded = function(occluder) {
        return BoundingSphere.isOccluded(this, occluder);
    };

    /**
     * Compares this BoundingSphere against the provided BoundingSphere componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {BoundingSphere} [right] The right hand side BoundingSphere.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    BoundingSphere.prototype.equals = function(right) {
        return BoundingSphere.equals(this, right);
    };

    /**
     * Duplicates this BoundingSphere instance.
     *
     * @param {BoundingSphere} [result] The object onto which to store the result.
     * @returns {BoundingSphere} The modified result parameter or a new BoundingSphere instance if none was provided.
     */
    BoundingSphere.prototype.clone = function(result) {
        return BoundingSphere.clone(this, result);
    };

    return BoundingSphere;
});

/*global define*/
define('Core/EllipsoidalOccluder',[
        './BoundingSphere',
        './Cartesian3',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Rectangle'
    ], function(
        BoundingSphere,
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Rectangle) {
    'use strict';

    /**
     * Determine whether or not other objects are visible or hidden behind the visible horizon defined by
     * an {@link Ellipsoid} and a camera position.  The ellipsoid is assumed to be located at the
     * origin of the coordinate system.  This class uses the algorithm described in the
     * {@link http://cesiumjs.org/2013/04/25/Horizon-culling/|Horizon Culling} blog post.
     *
     * @alias EllipsoidalOccluder
     *
     * @param {Ellipsoid} ellipsoid The ellipsoid to use as an occluder.
     * @param {Cartesian3} [cameraPosition] The coordinate of the viewer/camera.  If this parameter is not
     *        specified, {@link EllipsoidalOccluder#cameraPosition} must be called before
     *        testing visibility.
     *
     * @constructor
     *
     * @example
     * // Construct an ellipsoidal occluder with radii 1.0, 1.1, and 0.9.
     * var cameraPosition = new Cesium.Cartesian3(5.0, 6.0, 7.0);
     * var occluderEllipsoid = new Cesium.Ellipsoid(1.0, 1.1, 0.9);
     * var occluder = new Cesium.EllipsoidalOccluder(occluderEllipsoid, cameraPosition);
     *
     * @private
     */
    function EllipsoidalOccluder(ellipsoid, cameraPosition) {
                if (!defined(ellipsoid)) {
            throw new DeveloperError('ellipsoid is required.');
        }
        
        this._ellipsoid = ellipsoid;
        this._cameraPosition = new Cartesian3();
        this._cameraPositionInScaledSpace = new Cartesian3();
        this._distanceToLimbInScaledSpaceSquared = 0.0;

        // cameraPosition fills in the above values
        if (defined(cameraPosition)) {
            this.cameraPosition = cameraPosition;
        }
    }

    defineProperties(EllipsoidalOccluder.prototype, {
        /**
         * Gets the occluding ellipsoid.
         * @memberof EllipsoidalOccluder.prototype
         * @type {Ellipsoid}
         */
        ellipsoid : {
            get: function() {
                return this._ellipsoid;
            }
        },
        /**
         * Gets or sets the position of the camera.
         * @memberof EllipsoidalOccluder.prototype
         * @type {Cartesian3}
         */
        cameraPosition : {
            get : function() {
                return this._cameraPosition;
            },
            set : function(cameraPosition) {
                // See http://cesiumjs.org/2013/04/25/Horizon-culling/
                var ellipsoid = this._ellipsoid;
                var cv = ellipsoid.transformPositionToScaledSpace(cameraPosition, this._cameraPositionInScaledSpace);
                var vhMagnitudeSquared = Cartesian3.magnitudeSquared(cv) - 1.0;

                Cartesian3.clone(cameraPosition, this._cameraPosition);
                this._cameraPositionInScaledSpace = cv;
                this._distanceToLimbInScaledSpaceSquared = vhMagnitudeSquared;
            }
        }
    });

    var scratchCartesian = new Cartesian3();

    /**
     * Determines whether or not a point, the <code>occludee</code>, is hidden from view by the occluder.
     *
     * @param {Cartesian3} occludee The point to test for visibility.
     * @returns {Boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
     *
     * @example
     * var cameraPosition = new Cesium.Cartesian3(0, 0, 2.5);
     * var ellipsoid = new Cesium.Ellipsoid(1.0, 1.1, 0.9);
     * var occluder = new Cesium.EllipsoidalOccluder(ellipsoid, cameraPosition);
     * var point = new Cesium.Cartesian3(0, -3, -3);
     * occluder.isPointVisible(point); //returns true
     */
    EllipsoidalOccluder.prototype.isPointVisible = function(occludee) {
        var ellipsoid = this._ellipsoid;
        var occludeeScaledSpacePosition = ellipsoid.transformPositionToScaledSpace(occludee, scratchCartesian);
        return this.isScaledSpacePointVisible(occludeeScaledSpacePosition);
    };

    /**
     * Determines whether or not a point expressed in the ellipsoid scaled space, is hidden from view by the
     * occluder.  To transform a Cartesian X, Y, Z position in the coordinate system aligned with the ellipsoid
     * into the scaled space, call {@link Ellipsoid#transformPositionToScaledSpace}.
     *
     * @param {Cartesian3} occludeeScaledSpacePosition The point to test for visibility, represented in the scaled space.
     * @returns {Boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
     *
     * @example
     * var cameraPosition = new Cesium.Cartesian3(0, 0, 2.5);
     * var ellipsoid = new Cesium.Ellipsoid(1.0, 1.1, 0.9);
     * var occluder = new Cesium.EllipsoidalOccluder(ellipsoid, cameraPosition);
     * var point = new Cesium.Cartesian3(0, -3, -3);
     * var scaledSpacePoint = ellipsoid.transformPositionToScaledSpace(point);
     * occluder.isScaledSpacePointVisible(scaledSpacePoint); //returns true
     */
    EllipsoidalOccluder.prototype.isScaledSpacePointVisible = function(occludeeScaledSpacePosition) {
        // See http://cesiumjs.org/2013/04/25/Horizon-culling/
        var cv = this._cameraPositionInScaledSpace;
        var vhMagnitudeSquared = this._distanceToLimbInScaledSpaceSquared;
        var vt = Cartesian3.subtract(occludeeScaledSpacePosition, cv, scratchCartesian);
        var vtDotVc = -Cartesian3.dot(vt, cv);
        // If vhMagnitudeSquared < 0 then we are below the surface of the ellipsoid and
        // in this case, set the culling plane to be on V.
        var isOccluded = vhMagnitudeSquared < 0 ? vtDotVc > 0 : (vtDotVc > vhMagnitudeSquared &&
                         vtDotVc * vtDotVc / Cartesian3.magnitudeSquared(vt) > vhMagnitudeSquared);
        return !isOccluded;
    };

    /**
     * Computes a point that can be used for horizon culling from a list of positions.  If the point is below
     * the horizon, all of the positions are guaranteed to be below the horizon as well.  The returned point
     * is expressed in the ellipsoid-scaled space and is suitable for use with
     * {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
     *
     * @param {Cartesian3} directionToPoint The direction that the computed point will lie along.
     *                     A reasonable direction to use is the direction from the center of the ellipsoid to
     *                     the center of the bounding sphere computed from the positions.  The direction need not
     *                     be normalized.
     * @param {Cartesian3[]} positions The positions from which to compute the horizon culling point.  The positions
     *                       must be expressed in a reference frame centered at the ellipsoid and aligned with the
     *                       ellipsoid's axes.
     * @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
     * @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
     */
    EllipsoidalOccluder.prototype.computeHorizonCullingPoint = function(directionToPoint, positions, result) {
                if (!defined(directionToPoint)) {
            throw new DeveloperError('directionToPoint is required');
        }
        if (!defined(positions)) {
            throw new DeveloperError('positions is required');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        var ellipsoid = this._ellipsoid;
        var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
        var resultMagnitude = 0.0;

        for (var i = 0, len = positions.length; i < len; ++i) {
            var position = positions[i];
            var candidateMagnitude = computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint);
            resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
        }

        return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
    };

    var positionScratch = new Cartesian3();

    /**
     * Computes a point that can be used for horizon culling from a list of positions.  If the point is below
     * the horizon, all of the positions are guaranteed to be below the horizon as well.  The returned point
     * is expressed in the ellipsoid-scaled space and is suitable for use with
     * {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
     *
     * @param {Cartesian3} directionToPoint The direction that the computed point will lie along.
     *                     A reasonable direction to use is the direction from the center of the ellipsoid to
     *                     the center of the bounding sphere computed from the positions.  The direction need not
     *                     be normalized.
     * @param {Number[]} vertices  The vertices from which to compute the horizon culling point.  The positions
     *                   must be expressed in a reference frame centered at the ellipsoid and aligned with the
     *                   ellipsoid's axes.
     * @param {Number} [stride=3]
     * @param {Cartesian3} [center=Cartesian3.ZERO]
     * @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
     * @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
     */
    EllipsoidalOccluder.prototype.computeHorizonCullingPointFromVertices = function(directionToPoint, vertices, stride, center, result) {
                if (!defined(directionToPoint)) {
            throw new DeveloperError('directionToPoint is required');
        }
        if (!defined(vertices)) {
            throw new DeveloperError('vertices is required');
        }
        if (!defined(stride)) {
            throw new DeveloperError('stride is required');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        center = defaultValue(center, Cartesian3.ZERO);
        var ellipsoid = this._ellipsoid;
        var scaledSpaceDirectionToPoint = computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint);
        var resultMagnitude = 0.0;

        for (var i = 0, len = vertices.length; i < len; i += stride) {
            positionScratch.x = vertices[i] + center.x;
            positionScratch.y = vertices[i + 1] + center.y;
            positionScratch.z = vertices[i + 2] + center.z;

            var candidateMagnitude = computeMagnitude(ellipsoid, positionScratch, scaledSpaceDirectionToPoint);
            resultMagnitude = Math.max(resultMagnitude, candidateMagnitude);
        }

        return magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result);
    };

    var subsampleScratch = [];

    /**
     * Computes a point that can be used for horizon culling of an rectangle.  If the point is below
     * the horizon, the ellipsoid-conforming rectangle is guaranteed to be below the horizon as well.
     * The returned point is expressed in the ellipsoid-scaled space and is suitable for use with
     * {@link EllipsoidalOccluder#isScaledSpacePointVisible}.
     *
     * @param {Rectangle} rectangle The rectangle for which to compute the horizon culling point.
     * @param {Ellipsoid} ellipsoid The ellipsoid on which the rectangle is defined.  This may be different from
     *                    the ellipsoid used by this instance for occlusion testing.
     * @param {Cartesian3} [result] The instance on which to store the result instead of allocating a new instance.
     * @returns {Cartesian3} The computed horizon culling point, expressed in the ellipsoid-scaled space.
     */
    EllipsoidalOccluder.prototype.computeHorizonCullingPointFromRectangle = function(rectangle, ellipsoid, result) {
                if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required.');
        }
        
        var positions = Rectangle.subsample(rectangle, ellipsoid, 0.0, subsampleScratch);
        var bs = BoundingSphere.fromPoints(positions);

        // If the bounding sphere center is too close to the center of the occluder, it doesn't make
        // sense to try to horizon cull it.
        if (Cartesian3.magnitude(bs.center) < 0.1 * ellipsoid.minimumRadius) {
            return undefined;
        }

        return this.computeHorizonCullingPoint(bs.center, positions, result);
    };

    var scaledSpaceScratch = new Cartesian3();
    var directionScratch = new Cartesian3();

    function computeMagnitude(ellipsoid, position, scaledSpaceDirectionToPoint) {
        var scaledSpacePosition = ellipsoid.transformPositionToScaledSpace(position, scaledSpaceScratch);
        var magnitudeSquared = Cartesian3.magnitudeSquared(scaledSpacePosition);
        var magnitude = Math.sqrt(magnitudeSquared);
        var direction = Cartesian3.divideByScalar(scaledSpacePosition, magnitude, directionScratch);

        // For the purpose of this computation, points below the ellipsoid are consider to be on it instead.
        magnitudeSquared = Math.max(1.0, magnitudeSquared);
        magnitude = Math.max(1.0, magnitude);

        var cosAlpha = Cartesian3.dot(direction, scaledSpaceDirectionToPoint);
        var sinAlpha = Cartesian3.magnitude(Cartesian3.cross(direction, scaledSpaceDirectionToPoint, direction));
        var cosBeta = 1.0 / magnitude;
        var sinBeta = Math.sqrt(magnitudeSquared - 1.0) * cosBeta;

        return 1.0 / (cosAlpha * cosBeta - sinAlpha * sinBeta);
    }

    function magnitudeToPoint(scaledSpaceDirectionToPoint, resultMagnitude, result) {
        // The horizon culling point is undefined if there were no positions from which to compute it,
        // the directionToPoint is pointing opposite all of the positions,  or if we computed NaN or infinity.
        if (resultMagnitude <= 0.0 || resultMagnitude === 1.0 / 0.0 || resultMagnitude !== resultMagnitude) {
            return undefined;
        }

        return Cartesian3.multiplyByScalar(scaledSpaceDirectionToPoint, resultMagnitude, result);
    }

    var directionToPointScratch = new Cartesian3();

    function computeScaledSpaceDirectionToPoint(ellipsoid, directionToPoint) {
        if (Cartesian3.equals(directionToPoint, Cartesian3.ZERO)) {
            return directionToPoint;
        }

        ellipsoid.transformPositionToScaledSpace(directionToPoint, directionToPointScratch);
        return Cartesian3.normalize(directionToPointScratch, directionToPointScratch);
    }

    return EllipsoidalOccluder;
});

/*global define*/
define('Core/QuadraticRealPolynomial',[
        './DeveloperError',
        './Math'
    ], function(
        DeveloperError,
        CesiumMath) {
    'use strict';

    /**
     * Defines functions for 2nd order polynomial functions of one variable with only real coefficients.
     *
     * @exports QuadraticRealPolynomial
     */
    var QuadraticRealPolynomial = {};

    /**
     * Provides the discriminant of the quadratic equation from the supplied coefficients.
     *
     * @param {Number} a The coefficient of the 2nd order monomial.
     * @param {Number} b The coefficient of the 1st order monomial.
     * @param {Number} c The coefficient of the 0th order monomial.
     * @returns {Number} The value of the discriminant.
     */
    QuadraticRealPolynomial.computeDiscriminant = function(a, b, c) {
                if (typeof a !== 'number') {
            throw new DeveloperError('a is a required number.');
        }
        if (typeof b !== 'number') {
            throw new DeveloperError('b is a required number.');
        }
        if (typeof c !== 'number') {
            throw new DeveloperError('c is a required number.');
        }
        
        var discriminant = b * b - 4.0 * a * c;
        return discriminant;
    };

    function addWithCancellationCheck(left, right, tolerance) {
        var difference = left + right;
        if ((CesiumMath.sign(left) !== CesiumMath.sign(right)) &&
                Math.abs(difference / Math.max(Math.abs(left), Math.abs(right))) < tolerance) {
            return 0.0;
        }

        return difference;
    }

    /**
     * Provides the real valued roots of the quadratic polynomial with the provided coefficients.
     *
     * @param {Number} a The coefficient of the 2nd order monomial.
     * @param {Number} b The coefficient of the 1st order monomial.
     * @param {Number} c The coefficient of the 0th order monomial.
     * @returns {Number[]} The real valued roots.
     */
    QuadraticRealPolynomial.computeRealRoots = function(a, b, c) {
                if (typeof a !== 'number') {
            throw new DeveloperError('a is a required number.');
        }
        if (typeof b !== 'number') {
            throw new DeveloperError('b is a required number.');
        }
        if (typeof c !== 'number') {
            throw new DeveloperError('c is a required number.');
        }
        
        var ratio;
        if (a === 0.0) {
            if (b === 0.0) {
                // Constant function: c = 0.
                return [];
            }

            // Linear function: b * x + c = 0.
            return [-c / b];
        } else if (b === 0.0) {
            if (c === 0.0) {
                // 2nd order monomial: a * x^2 = 0.
                return [0.0, 0.0];
            }

            var cMagnitude = Math.abs(c);
            var aMagnitude = Math.abs(a);

            if ((cMagnitude < aMagnitude) && (cMagnitude / aMagnitude < CesiumMath.EPSILON14)) { // c ~= 0.0.
                // 2nd order monomial: a * x^2 = 0.
                return [0.0, 0.0];
            } else if ((cMagnitude > aMagnitude) && (aMagnitude / cMagnitude < CesiumMath.EPSILON14)) { // a ~= 0.0.
                // Constant function: c = 0.
                return [];
            }

            // a * x^2 + c = 0
            ratio = -c / a;

            if (ratio < 0.0) {
                // Both roots are complex.
                return [];
            }

            // Both roots are real.
            var root = Math.sqrt(ratio);
            return [-root, root];
        } else if (c === 0.0) {
            // a * x^2 + b * x = 0
            ratio = -b / a;
            if (ratio < 0.0) {
                return [ratio, 0.0];
            }

            return [0.0, ratio];
        }

        // a * x^2 + b * x + c = 0
        var b2 = b * b;
        var four_ac = 4.0 * a * c;
        var radicand = addWithCancellationCheck(b2, -four_ac, CesiumMath.EPSILON14);

        if (radicand < 0.0) {
            // Both roots are complex.
            return [];
        }

        var q = -0.5 * addWithCancellationCheck(b, CesiumMath.sign(b) * Math.sqrt(radicand), CesiumMath.EPSILON14);
        if (b > 0.0) {
            return [q / a, c / q];
        }

        return [c / q, q / a];
    };

    return QuadraticRealPolynomial;
});

/*global define*/
define('Core/CubicRealPolynomial',[
        './DeveloperError',
        './QuadraticRealPolynomial'
    ], function(
        DeveloperError,
        QuadraticRealPolynomial) {
    'use strict';

    /**
     * Defines functions for 3rd order polynomial functions of one variable with only real coefficients.
     *
     * @exports CubicRealPolynomial
     */
    var CubicRealPolynomial = {};

    /**
     * Provides the discriminant of the cubic equation from the supplied coefficients.
     *
     * @param {Number} a The coefficient of the 3rd order monomial.
     * @param {Number} b The coefficient of the 2nd order monomial.
     * @param {Number} c The coefficient of the 1st order monomial.
     * @param {Number} d The coefficient of the 0th order monomial.
     * @returns {Number} The value of the discriminant.
     */
    CubicRealPolynomial.computeDiscriminant = function(a, b, c, d) {
                if (typeof a !== 'number') {
            throw new DeveloperError('a is a required number.');
        }
        if (typeof b !== 'number') {
            throw new DeveloperError('b is a required number.');
        }
        if (typeof c !== 'number') {
            throw new DeveloperError('c is a required number.');
        }
        if (typeof d !== 'number') {
            throw new DeveloperError('d is a required number.');
        }
        
        var a2 = a * a;
        var b2 = b * b;
        var c2 = c * c;
        var d2 = d * d;

        var discriminant = 18.0 * a * b * c * d + b2 * c2 - 27.0 * a2 * d2 - 4.0 * (a * c2 * c + b2 * b * d);
        return discriminant;
    };

    function computeRealRoots(a, b, c, d) {
        var A = a;
        var B = b / 3.0;
        var C = c / 3.0;
        var D = d;

        var AC = A * C;
        var BD = B * D;
        var B2 = B * B;
        var C2 = C * C;
        var delta1 = A * C - B2;
        var delta2 = A * D - B * C;
        var delta3 = B * D - C2;

        var discriminant = 4.0 * delta1 * delta3 - delta2 * delta2;
        var temp;
        var temp1;

        if (discriminant < 0.0) {
            var ABar;
            var CBar;
            var DBar;

            if (B2 * BD >= AC * C2) {
                ABar = A;
                CBar = delta1;
                DBar = -2.0 * B * delta1 + A * delta2;
            } else {
                ABar = D;
                CBar = delta3;
                DBar = -D * delta2 + 2.0 * C * delta3;
            }

            var s = (DBar < 0.0) ? -1.0 : 1.0; // This is not Math.Sign()!
            var temp0 = -s * Math.abs(ABar) * Math.sqrt(-discriminant);
            temp1 = -DBar + temp0;

            var x = temp1 / 2.0;
            var p = x < 0.0 ? -Math.pow(-x, 1.0 / 3.0) : Math.pow(x, 1.0 / 3.0);
            var q = (temp1 === temp0) ? -p : -CBar / p;

            temp = (CBar <= 0.0) ? p + q : -DBar / (p * p + q * q + CBar);

            if (B2 * BD >= AC * C2) {
                return [(temp - B) / A];
            }

            return [-D / (temp + C)];
        }

        var CBarA = delta1;
        var DBarA = -2.0 * B * delta1 + A * delta2;

        var CBarD = delta3;
        var DBarD = -D * delta2 + 2.0 * C * delta3;

        var squareRootOfDiscriminant = Math.sqrt(discriminant);
        var halfSquareRootOf3 = Math.sqrt(3.0) / 2.0;

        var theta = Math.abs(Math.atan2(A * squareRootOfDiscriminant, -DBarA) / 3.0);
        temp = 2.0 * Math.sqrt(-CBarA);
        var cosine = Math.cos(theta);
        temp1 = temp * cosine;
        var temp3 = temp * (-cosine / 2.0 - halfSquareRootOf3 * Math.sin(theta));

        var numeratorLarge = (temp1 + temp3 > 2.0 * B) ? temp1 - B : temp3 - B;
        var denominatorLarge = A;

        var root1 = numeratorLarge / denominatorLarge;

        theta = Math.abs(Math.atan2(D * squareRootOfDiscriminant, -DBarD) / 3.0);
        temp = 2.0 * Math.sqrt(-CBarD);
        cosine = Math.cos(theta);
        temp1 = temp * cosine;
        temp3 = temp * (-cosine / 2.0 - halfSquareRootOf3 * Math.sin(theta));

        var numeratorSmall = -D;
        var denominatorSmall = (temp1 + temp3 < 2.0 * C) ? temp1 + C : temp3 + C;

        var root3 = numeratorSmall / denominatorSmall;

        var E = denominatorLarge * denominatorSmall;
        var F = -numeratorLarge * denominatorSmall - denominatorLarge * numeratorSmall;
        var G = numeratorLarge * numeratorSmall;

        var root2 = (C * F - B * G) / (-B * F + C * E);

        if (root1 <= root2) {
            if (root1 <= root3) {
                if (root2 <= root3) {
                    return [root1, root2, root3];
                }
                return [root1, root3, root2];
            }
            return [root3, root1, root2];
        }
        if (root1 <= root3) {
            return [root2, root1, root3];
        }
        if (root2 <= root3) {
            return [root2, root3, root1];
        }
        return [root3, root2, root1];
    }

    /**
     * Provides the real valued roots of the cubic polynomial with the provided coefficients.
     *
     * @param {Number} a The coefficient of the 3rd order monomial.
     * @param {Number} b The coefficient of the 2nd order monomial.
     * @param {Number} c The coefficient of the 1st order monomial.
     * @param {Number} d The coefficient of the 0th order monomial.
     * @returns {Number[]} The real valued roots.
     */
    CubicRealPolynomial.computeRealRoots = function(a, b, c, d) {
                if (typeof a !== 'number') {
            throw new DeveloperError('a is a required number.');
        }
        if (typeof b !== 'number') {
            throw new DeveloperError('b is a required number.');
        }
        if (typeof c !== 'number') {
            throw new DeveloperError('c is a required number.');
        }
        if (typeof d !== 'number') {
            throw new DeveloperError('d is a required number.');
        }
        
        var roots;
        var ratio;
        if (a === 0.0) {
            // Quadratic function: b * x^2 + c * x + d = 0.
            return QuadraticRealPolynomial.computeRealRoots(b, c, d);
        } else if (b === 0.0) {
            if (c === 0.0) {
                if (d === 0.0) {
                    // 3rd order monomial: a * x^3 = 0.
                    return [0.0, 0.0, 0.0];
                }

                // a * x^3 + d = 0
                ratio = -d / a;
                var root = (ratio < 0.0) ? -Math.pow(-ratio, 1.0 / 3.0) : Math.pow(ratio, 1.0 / 3.0);
                return [root, root, root];
            } else if (d === 0.0) {
                // x * (a * x^2 + c) = 0.
                roots = QuadraticRealPolynomial.computeRealRoots(a, 0, c);

                // Return the roots in ascending order.
                if (roots.Length === 0) {
                    return [0.0];
                }
                return [roots[0], 0.0, roots[1]];
            }

            // Deflated cubic polynomial: a * x^3 + c * x + d= 0.
            return computeRealRoots(a, 0, c, d);
        } else if (c === 0.0) {
            if (d === 0.0) {
                // x^2 * (a * x + b) = 0.
                ratio = -b / a;
                if (ratio < 0.0) {
                    return [ratio, 0.0, 0.0];
                }
                return [0.0, 0.0, ratio];
            }
            // a * x^3 + b * x^2 + d = 0.
            return computeRealRoots(a, b, 0, d);
        } else if (d === 0.0) {
            // x * (a * x^2 + b * x + c) = 0
            roots = QuadraticRealPolynomial.computeRealRoots(a, b, c);

            // Return the roots in ascending order.
            if (roots.length === 0) {
                return [0.0];
            } else if (roots[1] <= 0.0) {
                return [roots[0], roots[1], 0.0];
            } else if (roots[0] >= 0.0) {
                return [0.0, roots[0], roots[1]];
            }
            return [roots[0], 0.0, roots[1]];
        }

        return computeRealRoots(a, b, c, d);
    };

    return CubicRealPolynomial;
});

/*global define*/
define('Core/QuarticRealPolynomial',[
        './CubicRealPolynomial',
        './DeveloperError',
        './Math',
        './QuadraticRealPolynomial'
    ], function(
        CubicRealPolynomial,
        DeveloperError,
        CesiumMath,
        QuadraticRealPolynomial) {
    'use strict';

    /**
     * Defines functions for 4th order polynomial functions of one variable with only real coefficients.
     *
     * @exports QuarticRealPolynomial
     */
    var QuarticRealPolynomial = {};

    /**
     * Provides the discriminant of the quartic equation from the supplied coefficients.
     *
     * @param {Number} a The coefficient of the 4th order monomial.
     * @param {Number} b The coefficient of the 3rd order monomial.
     * @param {Number} c The coefficient of the 2nd order monomial.
     * @param {Number} d The coefficient of the 1st order monomial.
     * @param {Number} e The coefficient of the 0th order monomial.
     * @returns {Number} The value of the discriminant.
     */
    QuarticRealPolynomial.computeDiscriminant = function(a, b, c, d, e) {
                if (typeof a !== 'number') {
            throw new DeveloperError('a is a required number.');
        }
        if (typeof b !== 'number') {
            throw new DeveloperError('b is a required number.');
        }
        if (typeof c !== 'number') {
            throw new DeveloperError('c is a required number.');
        }
        if (typeof d !== 'number') {
            throw new DeveloperError('d is a required number.');
        }
        if (typeof e !== 'number') {
            throw new DeveloperError('e is a required number.');
        }
        
        var a2 = a * a;
        var a3 = a2 * a;
        var b2 = b * b;
        var b3 = b2 * b;
        var c2 = c * c;
        var c3 = c2 * c;
        var d2 = d * d;
        var d3 = d2 * d;
        var e2 = e * e;
        var e3 = e2 * e;

        var discriminant = (b2 * c2 * d2 - 4.0 * b3 * d3 - 4.0 * a * c3 * d2 + 18 * a * b * c * d3 - 27.0 * a2 * d2 * d2 + 256.0 * a3 * e3) +
            e * (18.0 * b3 * c * d - 4.0 * b2 * c3 + 16.0 * a * c2 * c2 - 80.0 * a * b * c2 * d - 6.0 * a * b2 * d2 + 144.0 * a2 * c * d2) +
            e2 * (144.0 * a * b2 * c - 27.0 * b2 * b2 - 128.0 * a2 * c2 - 192.0 * a2 * b * d);
        return discriminant;
    };

    function original(a3, a2, a1, a0) {
        var a3Squared = a3 * a3;

        var p = a2 - 3.0 * a3Squared / 8.0;
        var q = a1 - a2 * a3 / 2.0 + a3Squared * a3 / 8.0;
        var r = a0 - a1 * a3 / 4.0 + a2 * a3Squared / 16.0 - 3.0 * a3Squared * a3Squared / 256.0;

        // Find the roots of the cubic equations:  h^6 + 2 p h^4 + (p^2 - 4 r) h^2 - q^2 = 0.
        var cubicRoots = CubicRealPolynomial.computeRealRoots(1.0, 2.0 * p, p * p - 4.0 * r, -q * q);

        if (cubicRoots.length > 0) {
            var temp = -a3 / 4.0;

            // Use the largest positive root.
            var hSquared = cubicRoots[cubicRoots.length - 1];

            if (Math.abs(hSquared) < CesiumMath.EPSILON14) {
                // y^4 + p y^2 + r = 0.
                var roots = QuadraticRealPolynomial.computeRealRoots(1.0, p, r);

                if (roots.length === 2) {
                    var root0 = roots[0];
                    var root1 = roots[1];

                    var y;
                    if (root0 >= 0.0 && root1 >= 0.0) {
                        var y0 = Math.sqrt(root0);
                        var y1 = Math.sqrt(root1);

                        return [temp - y1, temp - y0, temp + y0, temp + y1];
                    } else if (root0 >= 0.0 && root1 < 0.0) {
                        y = Math.sqrt(root0);
                        return [temp - y, temp + y];
                    } else if (root0 < 0.0 && root1 >= 0.0) {
                        y = Math.sqrt(root1);
                        return [temp - y, temp + y];
                    }
                }
                return [];
            } else if (hSquared > 0.0) {
                var h = Math.sqrt(hSquared);

                var m = (p + hSquared - q / h) / 2.0;
                var n = (p + hSquared + q / h) / 2.0;

                // Now solve the two quadratic factors:  (y^2 + h y + m)(y^2 - h y + n);
                var roots1 = QuadraticRealPolynomial.computeRealRoots(1.0, h, m);
                var roots2 = QuadraticRealPolynomial.computeRealRoots(1.0, -h, n);

                if (roots1.length !== 0) {
                    roots1[0] += temp;
                    roots1[1] += temp;

                    if (roots2.length !== 0) {
                        roots2[0] += temp;
                        roots2[1] += temp;

                        if (roots1[1] <= roots2[0]) {
                            return [roots1[0], roots1[1], roots2[0], roots2[1]];
                        } else if (roots2[1] <= roots1[0]) {
                            return [roots2[0], roots2[1], roots1[0], roots1[1]];
                        } else if (roots1[0] >= roots2[0] && roots1[1] <= roots2[1]) {
                            return [roots2[0], roots1[0], roots1[1], roots2[1]];
                        } else if (roots2[0] >= roots1[0] && roots2[1] <= roots1[1]) {
                            return [roots1[0], roots2[0], roots2[1], roots1[1]];
                        } else if (roots1[0] > roots2[0] && roots1[0] < roots2[1]) {
                            return [roots2[0], roots1[0], roots2[1], roots1[1]];
                        }
                        return [roots1[0], roots2[0], roots1[1], roots2[1]];
                    }
                    return roots1;
                }

                if (roots2.length !== 0) {
                    roots2[0] += temp;
                    roots2[1] += temp;

                    return roots2;
                }
                return [];
            }
        }
        return [];
    }

    function neumark(a3, a2, a1, a0) {
        var a1Squared = a1 * a1;
        var a2Squared = a2 * a2;
        var a3Squared = a3 * a3;

        var p = -2.0 * a2;
        var q = a1 * a3 + a2Squared - 4.0 * a0;
        var r = a3Squared * a0 - a1 * a2 * a3 + a1Squared;

        var cubicRoots = CubicRealPolynomial.computeRealRoots(1.0, p, q, r);

        if (cubicRoots.length > 0) {
            // Use the most positive root
            var y = cubicRoots[0];

            var temp = (a2 - y);
            var tempSquared = temp * temp;

            var g1 = a3 / 2.0;
            var h1 = temp / 2.0;

            var m = tempSquared - 4.0 * a0;
            var mError = tempSquared + 4.0 * Math.abs(a0);

            var n = a3Squared - 4.0 * y;
            var nError = a3Squared + 4.0 * Math.abs(y);

            var g2;
            var h2;

            if (y < 0.0 || (m * nError < n * mError)) {
                var squareRootOfN = Math.sqrt(n);
                g2 = squareRootOfN / 2.0;
                h2 = squareRootOfN === 0.0 ? 0.0 : (a3 * h1 - a1) / squareRootOfN;
            } else {
                var squareRootOfM = Math.sqrt(m);
                g2 = squareRootOfM === 0.0 ? 0.0 : (a3 * h1 - a1) / squareRootOfM;
                h2 = squareRootOfM / 2.0;
            }

            var G;
            var g;
            if (g1 === 0.0 && g2 === 0.0) {
                G = 0.0;
                g = 0.0;
            } else if (CesiumMath.sign(g1) === CesiumMath.sign(g2)) {
                G = g1 + g2;
                g = y / G;
            } else {
                g = g1 - g2;
                G = y / g;
            }

            var H;
            var h;
            if (h1 === 0.0 && h2 === 0.0) {
                H = 0.0;
                h = 0.0;
            } else if (CesiumMath.sign(h1) === CesiumMath.sign(h2)) {
                H = h1 + h2;
                h = a0 / H;
            } else {
                h = h1 - h2;
                H = a0 / h;
            }

            // Now solve the two quadratic factors:  (y^2 + G y + H)(y^2 + g y + h);
            var roots1 = QuadraticRealPolynomial.computeRealRoots(1.0, G, H);
            var roots2 = QuadraticRealPolynomial.computeRealRoots(1.0, g, h);

            if (roots1.length !== 0) {
                if (roots2.length !== 0) {
                    if (roots1[1] <= roots2[0]) {
                        return [roots1[0], roots1[1], roots2[0], roots2[1]];
                    } else if (roots2[1] <= roots1[0]) {
                        return [roots2[0], roots2[1], roots1[0], roots1[1]];
                    } else if (roots1[0] >= roots2[0] && roots1[1] <= roots2[1]) {
                        return [roots2[0], roots1[0], roots1[1], roots2[1]];
                    } else if (roots2[0] >= roots1[0] && roots2[1] <= roots1[1]) {
                        return [roots1[0], roots2[0], roots2[1], roots1[1]];
                    } else if (roots1[0] > roots2[0] && roots1[0] < roots2[1]) {
                        return [roots2[0], roots1[0], roots2[1], roots1[1]];
                    } else {
                        return [roots1[0], roots2[0], roots1[1], roots2[1]];
                    }
                }
                return roots1;
            }
            if (roots2.length !== 0) {
                return roots2;
            }
        }
        return [];
    }

    /**
     * Provides the real valued roots of the quartic polynomial with the provided coefficients.
     *
     * @param {Number} a The coefficient of the 4th order monomial.
     * @param {Number} b The coefficient of the 3rd order monomial.
     * @param {Number} c The coefficient of the 2nd order monomial.
     * @param {Number} d The coefficient of the 1st order monomial.
     * @param {Number} e The coefficient of the 0th order monomial.
     * @returns {Number[]} The real valued roots.
     */
    QuarticRealPolynomial.computeRealRoots = function(a, b, c, d, e) {
                if (typeof a !== 'number') {
            throw new DeveloperError('a is a required number.');
        }
        if (typeof b !== 'number') {
            throw new DeveloperError('b is a required number.');
        }
        if (typeof c !== 'number') {
            throw new DeveloperError('c is a required number.');
        }
        if (typeof d !== 'number') {
            throw new DeveloperError('d is a required number.');
        }
        if (typeof e !== 'number') {
            throw new DeveloperError('e is a required number.');
        }
        
        if (Math.abs(a) < CesiumMath.EPSILON15) {
            return CubicRealPolynomial.computeRealRoots(b, c, d, e);
        }
        var a3 = b / a;
        var a2 = c / a;
        var a1 = d / a;
        var a0 = e / a;

        var k = (a3 < 0.0) ? 1 : 0;
        k += (a2 < 0.0) ? k + 1 : k;
        k += (a1 < 0.0) ? k + 1 : k;
        k += (a0 < 0.0) ? k + 1 : k;

        switch (k) {
        case 0:
            return original(a3, a2, a1, a0);
        case 1:
            return neumark(a3, a2, a1, a0);
        case 2:
            return neumark(a3, a2, a1, a0);
        case 3:
            return original(a3, a2, a1, a0);
        case 4:
            return original(a3, a2, a1, a0);
        case 5:
            return neumark(a3, a2, a1, a0);
        case 6:
            return original(a3, a2, a1, a0);
        case 7:
            return original(a3, a2, a1, a0);
        case 8:
            return neumark(a3, a2, a1, a0);
        case 9:
            return original(a3, a2, a1, a0);
        case 10:
            return original(a3, a2, a1, a0);
        case 11:
            return neumark(a3, a2, a1, a0);
        case 12:
            return original(a3, a2, a1, a0);
        case 13:
            return original(a3, a2, a1, a0);
        case 14:
            return original(a3, a2, a1, a0);
        case 15:
            return original(a3, a2, a1, a0);
        default:
            return undefined;
        }
    };

    return QuarticRealPolynomial;
});

/*global define*/
define('Core/Ray',[
        './Cartesian3',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Represents a ray that extends infinitely from the provided origin in the provided direction.
     * @alias Ray
     * @constructor
     *
     * @param {Cartesian3} [origin=Cartesian3.ZERO] The origin of the ray.
     * @param {Cartesian3} [direction=Cartesian3.ZERO] The direction of the ray.
     */
    function Ray(origin, direction) {
        direction = Cartesian3.clone(defaultValue(direction, Cartesian3.ZERO));
        if (!Cartesian3.equals(direction, Cartesian3.ZERO)) {
            Cartesian3.normalize(direction, direction);
        }

        /**
         * The origin of the ray.
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.origin = Cartesian3.clone(defaultValue(origin, Cartesian3.ZERO));

        /**
         * The direction of the ray.
         * @type {Cartesian3}
         */
        this.direction = direction;
    }

    /**
     * Computes the point along the ray given by r(t) = o + t*d,
     * where o is the origin of the ray and d is the direction.
     *
     * @param {Ray} ray The ray.
     * @param {Number} t A scalar value.
     * @param {Cartesian3} [result] The object in which the result will be stored.
     * @returns {Cartesian3} The modified result parameter, or a new instance if none was provided.
     *
     * @example
     * //Get the first intersection point of a ray and an ellipsoid.
     * var intersection = Cesium.IntersectionTests.rayEllipsoid(ray, ellipsoid);
     * var point = Cesium.Ray.getPoint(ray, intersection.start);
     */
    Ray.getPoint = function(ray, t, result) {
                if (!defined(ray)){
            throw new DeveloperError('ray is requred');
        }
        if (typeof t !== 'number') {
            throw new DeveloperError('t is a required number');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        result = Cartesian3.multiplyByScalar(ray.direction, t, result);
        return Cartesian3.add(ray.origin, result, result);
    };

    return Ray;
});

/*global define*/
define('Core/IntersectionTests',[
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Interval',
        './Math',
        './Matrix3',
        './QuadraticRealPolynomial',
        './QuarticRealPolynomial',
        './Ray'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        Interval,
        CesiumMath,
        Matrix3,
        QuadraticRealPolynomial,
        QuarticRealPolynomial,
        Ray) {
    'use strict';

    /**
     * Functions for computing the intersection between geometries such as rays, planes, triangles, and ellipsoids.
     *
     * @exports IntersectionTests
     */
    var IntersectionTests = {};

    /**
     * Computes the intersection of a ray and a plane.
     *
     * @param {Ray} ray The ray.
     * @param {Plane} plane The plane.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The intersection point or undefined if there is no intersections.
     */
    IntersectionTests.rayPlane = function(ray, plane, result) {
                if (!defined(ray)) {
            throw new DeveloperError('ray is required.');
        }
        if (!defined(plane)) {
            throw new DeveloperError('plane is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        var origin = ray.origin;
        var direction = ray.direction;
        var normal = plane.normal;
        var denominator = Cartesian3.dot(normal, direction);

        if (Math.abs(denominator) < CesiumMath.EPSILON15) {
            // Ray is parallel to plane.  The ray may be in the polygon's plane.
            return undefined;
        }

        var t = (-plane.distance - Cartesian3.dot(normal, origin)) / denominator;

        if (t < 0) {
            return undefined;
        }

        result = Cartesian3.multiplyByScalar(direction, t, result);
        return Cartesian3.add(origin, result, result);
    };

    var scratchEdge0 = new Cartesian3();
    var scratchEdge1 = new Cartesian3();
    var scratchPVec = new Cartesian3();
    var scratchTVec = new Cartesian3();
    var scratchQVec = new Cartesian3();

    /**
     * Computes the intersection of a ray and a triangle as a parametric distance along the input ray.
     *
     * Implements {@link https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf|
     * Fast Minimum Storage Ray/Triangle Intersection} by Tomas Moller and Ben Trumbore.
     *
     * @memberof IntersectionTests
     *
     * @param {Ray} ray The ray.
     * @param {Cartesian3} p0 The first vertex of the triangle.
     * @param {Cartesian3} p1 The second vertex of the triangle.
     * @param {Cartesian3} p2 The third vertex of the triangle.
     * @param {Boolean} [cullBackFaces=false] If <code>true</code>, will only compute an intersection with the front face of the triangle
     *                  and return undefined for intersections with the back face.
     * @returns {Number} The intersection as a parametric distance along the ray, or undefined if there is no intersection.
     */
    IntersectionTests.rayTriangleParametric  = function(ray, p0, p1, p2, cullBackFaces) {
                if (!defined(ray)) {
            throw new DeveloperError('ray is required.');
        }
        if (!defined(p0)) {
            throw new DeveloperError('p0 is required.');
        }
        if (!defined(p1)) {
            throw new DeveloperError('p1 is required.');
        }
        if (!defined(p2)) {
            throw new DeveloperError('p2 is required.');
        }
        
        cullBackFaces = defaultValue(cullBackFaces, false);

        var origin = ray.origin;
        var direction = ray.direction;

        var edge0 = Cartesian3.subtract(p1, p0, scratchEdge0);
        var edge1 = Cartesian3.subtract(p2, p0, scratchEdge1);

        var p = Cartesian3.cross(direction, edge1, scratchPVec);
        var det = Cartesian3.dot(edge0, p);

        var tvec;
        var q;

        var u;
        var v;
        var t;

        if (cullBackFaces) {
            if (det < CesiumMath.EPSILON6) {
                return undefined;
            }

            tvec = Cartesian3.subtract(origin, p0, scratchTVec);
            u = Cartesian3.dot(tvec, p);
            if (u < 0.0 || u > det) {
                return undefined;
            }

            q = Cartesian3.cross(tvec, edge0, scratchQVec);

            v = Cartesian3.dot(direction, q);
            if (v < 0.0 || u + v > det) {
                return undefined;
            }

            t = Cartesian3.dot(edge1, q) / det;
        } else {
            if (Math.abs(det) < CesiumMath.EPSILON6) {
                return undefined;
            }
            var invDet = 1.0 / det;

            tvec = Cartesian3.subtract(origin, p0, scratchTVec);
            u = Cartesian3.dot(tvec, p) * invDet;
            if (u < 0.0 || u > 1.0) {
                return undefined;
            }

            q = Cartesian3.cross(tvec, edge0, scratchQVec);

            v = Cartesian3.dot(direction, q) * invDet;
            if (v < 0.0 || u + v > 1.0) {
                return undefined;
            }

            t = Cartesian3.dot(edge1, q) * invDet;
        }

        return t;
    };

    /**
     * Computes the intersection of a ray and a triangle as a Cartesian3 coordinate.
     *
     * Implements {@link https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf|
     * Fast Minimum Storage Ray/Triangle Intersection} by Tomas Moller and Ben Trumbore.
     *
     * @memberof IntersectionTests
     *
     * @param {Ray} ray The ray.
     * @param {Cartesian3} p0 The first vertex of the triangle.
     * @param {Cartesian3} p1 The second vertex of the triangle.
     * @param {Cartesian3} p2 The third vertex of the triangle.
     * @param {Boolean} [cullBackFaces=false] If <code>true</code>, will only compute an intersection with the front face of the triangle
     *                  and return undefined for intersections with the back face.
     * @param {Cartesian3} [result] The <code>Cartesian3</code> onto which to store the result.
     * @returns {Cartesian3} The intersection point or undefined if there is no intersections.
     */
    IntersectionTests.rayTriangle = function(ray, p0, p1, p2, cullBackFaces, result) {
        var t = IntersectionTests.rayTriangleParametric(ray, p0, p1, p2, cullBackFaces);
        if (!defined(t) || t < 0.0) {
            return undefined;
        }

        if (!defined(result)) {
            result = new Cartesian3();
        }

        Cartesian3.multiplyByScalar(ray.direction, t, result);
        return Cartesian3.add(ray.origin, result, result);
    };

    var scratchLineSegmentTriangleRay = new Ray();

    /**
     * Computes the intersection of a line segment and a triangle.
     * @memberof IntersectionTests
     *
     * @param {Cartesian3} v0 The an end point of the line segment.
     * @param {Cartesian3} v1 The other end point of the line segment.
     * @param {Cartesian3} p0 The first vertex of the triangle.
     * @param {Cartesian3} p1 The second vertex of the triangle.
     * @param {Cartesian3} p2 The third vertex of the triangle.
     * @param {Boolean} [cullBackFaces=false] If <code>true</code>, will only compute an intersection with the front face of the triangle
     *                  and return undefined for intersections with the back face.
     * @param {Cartesian3} [result] The <code>Cartesian3</code> onto which to store the result.
     * @returns {Cartesian3} The intersection point or undefined if there is no intersections.
     */
    IntersectionTests.lineSegmentTriangle = function(v0, v1, p0, p1, p2, cullBackFaces, result) {
                if (!defined(v0)) {
            throw new DeveloperError('v0 is required.');
        }
        if (!defined(v1)) {
            throw new DeveloperError('v1 is required.');
        }
        if (!defined(p0)) {
            throw new DeveloperError('p0 is required.');
        }
        if (!defined(p1)) {
            throw new DeveloperError('p1 is required.');
        }
        if (!defined(p2)) {
            throw new DeveloperError('p2 is required.');
        }
        
        var ray = scratchLineSegmentTriangleRay;
        Cartesian3.clone(v0, ray.origin);
        Cartesian3.subtract(v1, v0, ray.direction);
        Cartesian3.normalize(ray.direction, ray.direction);

        var t = IntersectionTests.rayTriangleParametric(ray, p0, p1, p2, cullBackFaces);
        if (!defined(t) || t < 0.0 || t > Cartesian3.distance(v0, v1)) {
            return undefined;
        }

        if (!defined(result)) {
            result = new Cartesian3();
        }

        Cartesian3.multiplyByScalar(ray.direction, t, result);
        return Cartesian3.add(ray.origin, result, result);
    };

    function solveQuadratic(a, b, c, result) {
        var det = b * b - 4.0 * a * c;
        if (det < 0.0) {
            return undefined;
        } else if (det > 0.0) {
            var denom = 1.0 / (2.0 * a);
            var disc = Math.sqrt(det);
            var root0 = (-b + disc) * denom;
            var root1 = (-b - disc) * denom;

            if (root0 < root1) {
                result.root0 = root0;
                result.root1 = root1;
            } else {
                result.root0 = root1;
                result.root1 = root0;
            }

            return result;
        }

        var root = -b / (2.0 * a);
        if (root === 0.0) {
            return undefined;
        }

        result.root0 = result.root1 = root;
        return result;
    }

    var raySphereRoots = {
        root0 : 0.0,
        root1 : 0.0
    };

    function raySphere(ray, sphere, result) {
        if (!defined(result)) {
            result = new Interval();
        }

        var origin = ray.origin;
        var direction = ray.direction;

        var center = sphere.center;
        var radiusSquared = sphere.radius * sphere.radius;

        var diff = Cartesian3.subtract(origin, center, scratchPVec);

        var a = Cartesian3.dot(direction, direction);
        var b = 2.0 * Cartesian3.dot(direction, diff);
        var c = Cartesian3.magnitudeSquared(diff) - radiusSquared;

        var roots = solveQuadratic(a, b, c, raySphereRoots);
        if (!defined(roots)) {
            return undefined;
        }

        result.start = roots.root0;
        result.stop = roots.root1;
        return result;
    }

    /**
     * Computes the intersection points of a ray with a sphere.
     * @memberof IntersectionTests
     *
     * @param {Ray} ray The ray.
     * @param {BoundingSphere} sphere The sphere.
     * @param {Interval} [result] The result onto which to store the result.
     * @returns {Interval} The interval containing scalar points along the ray or undefined if there are no intersections.
     */
    IntersectionTests.raySphere = function(ray, sphere, result) {
                if (!defined(ray)) {
            throw new DeveloperError('ray is required.');
        }
        if (!defined(sphere)) {
            throw new DeveloperError('sphere is required.');
        }
        
        result = raySphere(ray, sphere, result);
        if (!defined(result) || result.stop < 0.0) {
            return undefined;
        }

        result.start = Math.max(result.start, 0.0);
        return result;
    };

    var scratchLineSegmentRay = new Ray();

    /**
     * Computes the intersection points of a line segment with a sphere.
     * @memberof IntersectionTests
     *
     * @param {Cartesian3} p0 An end point of the line segment.
     * @param {Cartesian3} p1 The other end point of the line segment.
     * @param {BoundingSphere} sphere The sphere.
     * @param {Interval} [result] The result onto which to store the result.
     * @returns {Interval} The interval containing scalar points along the ray or undefined if there are no intersections.
     */
    IntersectionTests.lineSegmentSphere = function(p0, p1, sphere, result) {
                if (!defined(p0)) {
            throw new DeveloperError('p0 is required.');
        }
        if (!defined(p1)) {
            throw new DeveloperError('p1 is required.');
        }
        if (!defined(sphere)) {
            throw new DeveloperError('sphere is required.');
        }
        
        var ray = scratchLineSegmentRay;
        Cartesian3.clone(p0, ray.origin);
        var direction = Cartesian3.subtract(p1, p0, ray.direction);

        var maxT = Cartesian3.magnitude(direction);
        Cartesian3.normalize(direction, direction);

        result = raySphere(ray, sphere, result);
        if (!defined(result) || result.stop < 0.0 || result.start > maxT) {
            return undefined;
        }

        result.start = Math.max(result.start, 0.0);
        result.stop = Math.min(result.stop, maxT);
        return result;
    };

    var scratchQ = new Cartesian3();
    var scratchW = new Cartesian3();

    /**
     * Computes the intersection points of a ray with an ellipsoid.
     *
     * @param {Ray} ray The ray.
     * @param {Ellipsoid} ellipsoid The ellipsoid.
     * @returns {Interval} The interval containing scalar points along the ray or undefined if there are no intersections.
     */
    IntersectionTests.rayEllipsoid = function(ray, ellipsoid) {
                if (!defined(ray)) {
            throw new DeveloperError('ray is required.');
        }
        if (!defined(ellipsoid)) {
            throw new DeveloperError('ellipsoid is required.');
        }
        
        var inverseRadii = ellipsoid.oneOverRadii;
        var q = Cartesian3.multiplyComponents(inverseRadii, ray.origin, scratchQ);
        var w = Cartesian3.multiplyComponents(inverseRadii, ray.direction, scratchW);

        var q2 = Cartesian3.magnitudeSquared(q);
        var qw = Cartesian3.dot(q, w);

        var difference, w2, product, discriminant, temp;

        if (q2 > 1.0) {
            // Outside ellipsoid.
            if (qw >= 0.0) {
                // Looking outward or tangent (0 intersections).
                return undefined;
            }

            // qw < 0.0.
            var qw2 = qw * qw;
            difference = q2 - 1.0; // Positively valued.
            w2 = Cartesian3.magnitudeSquared(w);
            product = w2 * difference;

            if (qw2 < product) {
                // Imaginary roots (0 intersections).
                return undefined;
            } else if (qw2 > product) {
                // Distinct roots (2 intersections).
                discriminant = qw * qw - product;
                temp = -qw + Math.sqrt(discriminant); // Avoid cancellation.
                var root0 = temp / w2;
                var root1 = difference / temp;
                if (root0 < root1) {
                    return new Interval(root0, root1);
                }

                return {
                    start : root1,
                    stop : root0
                };
            } else {
                // qw2 == product.  Repeated roots (2 intersections).
                var root = Math.sqrt(difference / w2);
                return new Interval(root, root);
            }
        } else if (q2 < 1.0) {
            // Inside ellipsoid (2 intersections).
            difference = q2 - 1.0; // Negatively valued.
            w2 = Cartesian3.magnitudeSquared(w);
            product = w2 * difference; // Negatively valued.

            discriminant = qw * qw - product;
            temp = -qw + Math.sqrt(discriminant); // Positively valued.
            return new Interval(0.0, temp / w2);
        } else {
            // q2 == 1.0. On ellipsoid.
            if (qw < 0.0) {
                // Looking inward.
                w2 = Cartesian3.magnitudeSquared(w);
                return new Interval(0.0, -qw / w2);
            }

            // qw >= 0.0.  Looking outward or tangent.
            return undefined;
        }
    };

    function addWithCancellationCheck(left, right, tolerance) {
        var difference = left + right;
        if ((CesiumMath.sign(left) !== CesiumMath.sign(right)) &&
                Math.abs(difference / Math.max(Math.abs(left), Math.abs(right))) < tolerance) {
            return 0.0;
        }

        return difference;
    }

    function quadraticVectorExpression(A, b, c, x, w) {
        var xSquared = x * x;
        var wSquared = w * w;

        var l2 = (A[Matrix3.COLUMN1ROW1] - A[Matrix3.COLUMN2ROW2]) * wSquared;
        var l1 = w * (x * addWithCancellationCheck(A[Matrix3.COLUMN1ROW0], A[Matrix3.COLUMN0ROW1], CesiumMath.EPSILON15) + b.y);
        var l0 = (A[Matrix3.COLUMN0ROW0] * xSquared + A[Matrix3.COLUMN2ROW2] * wSquared) + x * b.x + c;

        var r1 = wSquared * addWithCancellationCheck(A[Matrix3.COLUMN2ROW1], A[Matrix3.COLUMN1ROW2], CesiumMath.EPSILON15);
        var r0 = w * (x * addWithCancellationCheck(A[Matrix3.COLUMN2ROW0], A[Matrix3.COLUMN0ROW2]) + b.z);

        var cosines;
        var solutions = [];
        if (r0 === 0.0 && r1 === 0.0) {
            cosines = QuadraticRealPolynomial.computeRealRoots(l2, l1, l0);
            if (cosines.length === 0) {
                return solutions;
            }

            var cosine0 = cosines[0];
            var sine0 = Math.sqrt(Math.max(1.0 - cosine0 * cosine0, 0.0));
            solutions.push(new Cartesian3(x, w * cosine0, w * -sine0));
            solutions.push(new Cartesian3(x, w * cosine0, w * sine0));

            if (cosines.length === 2) {
                var cosine1 = cosines[1];
                var sine1 = Math.sqrt(Math.max(1.0 - cosine1 * cosine1, 0.0));
                solutions.push(new Cartesian3(x, w * cosine1, w * -sine1));
                solutions.push(new Cartesian3(x, w * cosine1, w * sine1));
            }

            return solutions;
        }

        var r0Squared = r0 * r0;
        var r1Squared = r1 * r1;
        var l2Squared = l2 * l2;
        var r0r1 = r0 * r1;

        var c4 = l2Squared + r1Squared;
        var c3 = 2.0 * (l1 * l2 + r0r1);
        var c2 = 2.0 * l0 * l2 + l1 * l1 - r1Squared + r0Squared;
        var c1 = 2.0 * (l0 * l1 - r0r1);
        var c0 = l0 * l0 - r0Squared;

        if (c4 === 0.0 && c3 === 0.0 && c2 === 0.0 && c1 === 0.0) {
            return solutions;
        }

        cosines = QuarticRealPolynomial.computeRealRoots(c4, c3, c2, c1, c0);
        var length = cosines.length;
        if (length === 0) {
            return solutions;
        }

        for ( var i = 0; i < length; ++i) {
            var cosine = cosines[i];
            var cosineSquared = cosine * cosine;
            var sineSquared = Math.max(1.0 - cosineSquared, 0.0);
            var sine = Math.sqrt(sineSquared);

            //var left = l2 * cosineSquared + l1 * cosine + l0;
            var left;
            if (CesiumMath.sign(l2) === CesiumMath.sign(l0)) {
                left = addWithCancellationCheck(l2 * cosineSquared + l0, l1 * cosine, CesiumMath.EPSILON12);
            } else if (CesiumMath.sign(l0) === CesiumMath.sign(l1 * cosine)) {
                left = addWithCancellationCheck(l2 * cosineSquared, l1 * cosine + l0, CesiumMath.EPSILON12);
            } else {
                left = addWithCancellationCheck(l2 * cosineSquared + l1 * cosine, l0, CesiumMath.EPSILON12);
            }

            var right = addWithCancellationCheck(r1 * cosine, r0, CesiumMath.EPSILON15);
            var product = left * right;

            if (product < 0.0) {
                solutions.push(new Cartesian3(x, w * cosine, w * sine));
            } else if (product > 0.0) {
                solutions.push(new Cartesian3(x, w * cosine, w * -sine));
            } else if (sine !== 0.0) {
                solutions.push(new Cartesian3(x, w * cosine, w * -sine));
                solutions.push(new Cartesian3(x, w * cosine, w * sine));
                ++i;
            } else {
                solutions.push(new Cartesian3(x, w * cosine, w * sine));
            }
        }

        return solutions;
    }

    var firstAxisScratch = new Cartesian3();
    var secondAxisScratch = new Cartesian3();
    var thirdAxisScratch = new Cartesian3();
    var referenceScratch = new Cartesian3();
    var bCart = new Cartesian3();
    var bScratch = new Matrix3();
    var btScratch = new Matrix3();
    var diScratch = new Matrix3();
    var dScratch = new Matrix3();
    var cScratch = new Matrix3();
    var tempMatrix = new Matrix3();
    var aScratch = new Matrix3();
    var sScratch = new Cartesian3();
    var closestScratch = new Cartesian3();
    var surfPointScratch = new Cartographic();

    /**
     * Provides the point along the ray which is nearest to the ellipsoid.
     *
     * @param {Ray} ray The ray.
     * @param {Ellipsoid} ellipsoid The ellipsoid.
     * @returns {Cartesian3} The nearest planetodetic point on the ray.
     */
    IntersectionTests.grazingAltitudeLocation = function(ray, ellipsoid) {
                if (!defined(ray)) {
            throw new DeveloperError('ray is required.');
        }
        if (!defined(ellipsoid)) {
            throw new DeveloperError('ellipsoid is required.');
        }
        
        var position = ray.origin;
        var direction = ray.direction;

        if (!Cartesian3.equals(position, Cartesian3.ZERO)) {
            var normal = ellipsoid.geodeticSurfaceNormal(position, firstAxisScratch);
            if (Cartesian3.dot(direction, normal) >= 0.0) { // The location provided is the closest point in altitude
                return position;
            }
        }

        var intersects = defined(this.rayEllipsoid(ray, ellipsoid));

        // Compute the scaled direction vector.
        var f = ellipsoid.transformPositionToScaledSpace(direction, firstAxisScratch);

        // Constructs a basis from the unit scaled direction vector. Construct its rotation and transpose.
        var firstAxis = Cartesian3.normalize(f, f);
        var reference = Cartesian3.mostOrthogonalAxis(f, referenceScratch);
        var secondAxis = Cartesian3.normalize(Cartesian3.cross(reference, firstAxis, secondAxisScratch), secondAxisScratch);
        var thirdAxis  = Cartesian3.normalize(Cartesian3.cross(firstAxis, secondAxis, thirdAxisScratch), thirdAxisScratch);
        var B = bScratch;
        B[0] = firstAxis.x;
        B[1] = firstAxis.y;
        B[2] = firstAxis.z;
        B[3] = secondAxis.x;
        B[4] = secondAxis.y;
        B[5] = secondAxis.z;
        B[6] = thirdAxis.x;
        B[7] = thirdAxis.y;
        B[8] = thirdAxis.z;

        var B_T = Matrix3.transpose(B, btScratch);

        // Get the scaling matrix and its inverse.
        var D_I = Matrix3.fromScale(ellipsoid.radii, diScratch);
        var D = Matrix3.fromScale(ellipsoid.oneOverRadii, dScratch);

        var C = cScratch;
        C[0] = 0.0;
        C[1] = -direction.z;
        C[2] = direction.y;
        C[3] = direction.z;
        C[4] = 0.0;
        C[5] = -direction.x;
        C[6] = -direction.y;
        C[7] = direction.x;
        C[8] = 0.0;

        var temp = Matrix3.multiply(Matrix3.multiply(B_T, D, tempMatrix), C, tempMatrix);
        var A = Matrix3.multiply(Matrix3.multiply(temp, D_I, aScratch), B, aScratch);
        var b = Matrix3.multiplyByVector(temp, position, bCart);

        // Solve for the solutions to the expression in standard form:
        var solutions = quadraticVectorExpression(A, Cartesian3.negate(b, firstAxisScratch), 0.0, 0.0, 1.0);

        var s;
        var altitude;
        var length = solutions.length;
        if (length > 0) {
            var closest = Cartesian3.clone(Cartesian3.ZERO, closestScratch);
            var maximumValue = Number.NEGATIVE_INFINITY;

            for ( var i = 0; i < length; ++i) {
                s = Matrix3.multiplyByVector(D_I, Matrix3.multiplyByVector(B, solutions[i], sScratch), sScratch);
                var v = Cartesian3.normalize(Cartesian3.subtract(s, position, referenceScratch), referenceScratch);
                var dotProduct = Cartesian3.dot(v, direction);

                if (dotProduct > maximumValue) {
                    maximumValue = dotProduct;
                    closest = Cartesian3.clone(s, closest);
                }
            }

            var surfacePoint = ellipsoid.cartesianToCartographic(closest, surfPointScratch);
            maximumValue = CesiumMath.clamp(maximumValue, 0.0, 1.0);
            altitude = Cartesian3.magnitude(Cartesian3.subtract(closest, position, referenceScratch)) * Math.sqrt(1.0 - maximumValue * maximumValue);
            altitude = intersects ? -altitude : altitude;
            surfacePoint.height = altitude;
            return ellipsoid.cartographicToCartesian(surfacePoint, new Cartesian3());
        }

        return undefined;
    };

    var lineSegmentPlaneDifference = new Cartesian3();

    /**
     * Computes the intersection of a line segment and a plane.
     *
     * @param {Cartesian3} endPoint0 An end point of the line segment.
     * @param {Cartesian3} endPoint1 The other end point of the line segment.
     * @param {Plane} plane The plane.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The intersection point or undefined if there is no intersection.
     *
     * @example
     * var origin = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
     * var normal = ellipsoid.geodeticSurfaceNormal(origin);
     * var plane = Cesium.Plane.fromPointNormal(origin, normal);
     *
     * var p0 = new Cesium.Cartesian3(...);
     * var p1 = new Cesium.Cartesian3(...);
     *
     * // find the intersection of the line segment from p0 to p1 and the tangent plane at origin.
     * var intersection = Cesium.IntersectionTests.lineSegmentPlane(p0, p1, plane);
     */
    IntersectionTests.lineSegmentPlane = function(endPoint0, endPoint1, plane, result) {
                if (!defined(endPoint0)) {
            throw new DeveloperError('endPoint0 is required.');
        }
        if (!defined(endPoint1)) {
            throw new DeveloperError('endPoint1 is required.');
        }
        if (!defined(plane)) {
            throw new DeveloperError('plane is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        var difference = Cartesian3.subtract(endPoint1, endPoint0, lineSegmentPlaneDifference);
        var normal = plane.normal;
        var nDotDiff = Cartesian3.dot(normal, difference);

        // check if the segment and plane are parallel
        if (Math.abs(nDotDiff) < CesiumMath.EPSILON6) {
            return undefined;
        }

        var nDotP0 = Cartesian3.dot(normal, endPoint0);
        var t = -(plane.distance + nDotP0) / nDotDiff;

        // intersection only if t is in [0, 1]
        if (t < 0.0 || t > 1.0) {
            return undefined;
        }

        // intersection is endPoint0 + t * (endPoint1 - endPoint0)
        Cartesian3.multiplyByScalar(difference, t, result);
        Cartesian3.add(endPoint0, result, result);
        return result;
    };

    /**
     * Computes the intersection of a triangle and a plane
     *
     * @param {Cartesian3} p0 First point of the triangle
     * @param {Cartesian3} p1 Second point of the triangle
     * @param {Cartesian3} p2 Third point of the triangle
     * @param {Plane} plane Intersection plane
     * @returns {Object} An object with properties <code>positions</code> and <code>indices</code>, which are arrays that represent three triangles that do not cross the plane. (Undefined if no intersection exists)
     *
     * @example
     * var origin = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
     * var normal = ellipsoid.geodeticSurfaceNormal(origin);
     * var plane = Cesium.Plane.fromPointNormal(origin, normal);
     *
     * var p0 = new Cesium.Cartesian3(...);
     * var p1 = new Cesium.Cartesian3(...);
     * var p2 = new Cesium.Cartesian3(...);
     *
     * // convert the triangle composed of points (p0, p1, p2) to three triangles that don't cross the plane
     * var triangles = Cesium.IntersectionTests.trianglePlaneIntersection(p0, p1, p2, plane);
     */
    IntersectionTests.trianglePlaneIntersection = function(p0, p1, p2, plane) {
                if ((!defined(p0)) ||
            (!defined(p1)) ||
            (!defined(p2)) ||
            (!defined(plane))) {
            throw new DeveloperError('p0, p1, p2, and plane are required.');
        }
        
        var planeNormal = plane.normal;
        var planeD = plane.distance;
        var p0Behind = (Cartesian3.dot(planeNormal, p0) + planeD) < 0.0;
        var p1Behind = (Cartesian3.dot(planeNormal, p1) + planeD) < 0.0;
        var p2Behind = (Cartesian3.dot(planeNormal, p2) + planeD) < 0.0;
        // Given these dots products, the calls to lineSegmentPlaneIntersection
        // always have defined results.

        var numBehind = 0;
        numBehind += p0Behind ? 1 : 0;
        numBehind += p1Behind ? 1 : 0;
        numBehind += p2Behind ? 1 : 0;

        var u1, u2;
        if (numBehind === 1 || numBehind === 2) {
            u1 = new Cartesian3();
            u2 = new Cartesian3();
        }

        if (numBehind === 1) {
            if (p0Behind) {
                IntersectionTests.lineSegmentPlane(p0, p1, plane, u1);
                IntersectionTests.lineSegmentPlane(p0, p2, plane, u2);

                return {
                    positions : [p0, p1, p2, u1, u2 ],
                    indices : [
                        // Behind
                        0, 3, 4,

                        // In front
                        1, 2, 4,
                        1, 4, 3
                    ]
                };
            } else if (p1Behind) {
                IntersectionTests.lineSegmentPlane(p1, p2, plane, u1);
                IntersectionTests.lineSegmentPlane(p1, p0, plane, u2);

                return {
                    positions : [p0, p1, p2, u1, u2 ],
                    indices : [
                        // Behind
                        1, 3, 4,

                        // In front
                        2, 0, 4,
                        2, 4, 3
                    ]
                };
            } else if (p2Behind) {
                IntersectionTests.lineSegmentPlane(p2, p0, plane, u1);
                IntersectionTests.lineSegmentPlane(p2, p1, plane, u2);

                return {
                    positions : [p0, p1, p2, u1, u2 ],
                    indices : [
                        // Behind
                        2, 3, 4,

                        // In front
                        0, 1, 4,
                        0, 4, 3
                    ]
                };
            }
        } else if (numBehind === 2) {
            if (!p0Behind) {
                IntersectionTests.lineSegmentPlane(p1, p0, plane, u1);
                IntersectionTests.lineSegmentPlane(p2, p0, plane, u2);

                return {
                    positions : [p0, p1, p2, u1, u2 ],
                    indices : [
                        // Behind
                        1, 2, 4,
                        1, 4, 3,

                        // In front
                        0, 3, 4
                    ]
                };
            } else if (!p1Behind) {
                IntersectionTests.lineSegmentPlane(p2, p1, plane, u1);
                IntersectionTests.lineSegmentPlane(p0, p1, plane, u2);

                return {
                    positions : [p0, p1, p2, u1, u2 ],
                    indices : [
                        // Behind
                        2, 0, 4,
                        2, 4, 3,

                        // In front
                        1, 3, 4
                    ]
                };
            } else if (!p2Behind) {
                IntersectionTests.lineSegmentPlane(p0, p2, plane, u1);
                IntersectionTests.lineSegmentPlane(p1, p2, plane, u2);

                return {
                    positions : [p0, p1, p2, u1, u2 ],
                    indices : [
                        // Behind
                        0, 1, 4,
                        0, 4, 3,

                        // In front
                        2, 3, 4
                    ]
                };
            }
        }

        // if numBehind is 3, the triangle is completely behind the plane;
        // otherwise, it is completely in front (numBehind is 0).
        return undefined;
    };

    return IntersectionTests;
});

/*global define*/
define('Core/Plane',[
        './Cartesian3',
        './defined',
        './DeveloperError',
        './freezeObject'
    ], function(
        Cartesian3,
        defined,
        DeveloperError,
        freezeObject) {
    'use strict';

    /**
     * A plane in Hessian Normal Form defined by
     * <pre>
     * ax + by + cz + d = 0
     * </pre>
     * where (a, b, c) is the plane's <code>normal</code>, d is the signed
     * <code>distance</code> to the plane, and (x, y, z) is any point on
     * the plane.
     *
     * @alias Plane
     * @constructor
     *
     * @param {Cartesian3} normal The plane's normal (normalized).
     * @param {Number} distance The shortest distance from the origin to the plane.  The sign of
     * <code>distance</code> determines which side of the plane the origin
     * is on.  If <code>distance</code> is positive, the origin is in the half-space
     * in the direction of the normal; if negative, the origin is in the half-space
     * opposite to the normal; if zero, the plane passes through the origin.
     *
     * @example
     * // The plane x=0
     * var plane = new Cesium.Plane(Cesium.Cartesian3.UNIT_X, 0.0);
     */
    function Plane(normal, distance) {
                if (!defined(normal))  {
            throw new DeveloperError('normal is required.');
        }
        if (!defined(distance)) {
            throw new DeveloperError('distance is required.');
        }
        
        /**
         * The plane's normal.
         *
         * @type {Cartesian3}
         */
        this.normal = Cartesian3.clone(normal);

        /**
         * The shortest distance from the origin to the plane.  The sign of
         * <code>distance</code> determines which side of the plane the origin
         * is on.  If <code>distance</code> is positive, the origin is in the half-space
         * in the direction of the normal; if negative, the origin is in the half-space
         * opposite to the normal; if zero, the plane passes through the origin.
         *
         * @type {Number}
         */
        this.distance = distance;
    }

    /**
     * Creates a plane from a normal and a point on the plane.
     *
     * @param {Cartesian3} point The point on the plane.
     * @param {Cartesian3} normal The plane's normal (normalized).
     * @param {Plane} [result] The object onto which to store the result.
     * @returns {Plane} A new plane instance or the modified result parameter.
     *
     * @example
     * var point = Cesium.Cartesian3.fromDegrees(-72.0, 40.0);
     * var normal = ellipsoid.geodeticSurfaceNormal(point);
     * var tangentPlane = Cesium.Plane.fromPointNormal(point, normal);
     */
    Plane.fromPointNormal = function(point, normal, result) {
                if (!defined(point)) {
            throw new DeveloperError('point is required.');
        }
        if (!defined(normal)) {
            throw new DeveloperError('normal is required.');
        }
        
        var distance = -Cartesian3.dot(normal, point);

        if (!defined(result)) {
            return new Plane(normal, distance);
        }

        Cartesian3.clone(normal, result.normal);
        result.distance = distance;
        return result;
    };

    var scratchNormal = new Cartesian3();
    /**
     * Creates a plane from the general equation
     *
     * @param {Cartesian4} coefficients The plane's normal (normalized).
     * @param {Plane} [result] The object onto which to store the result.
     * @returns {Plane} A new plane instance or the modified result parameter.
     */
    Plane.fromCartesian4 = function(coefficients, result) {
                if (!defined(coefficients)) {
            throw new DeveloperError('coefficients is required.');
        }
        
        var normal = Cartesian3.fromCartesian4(coefficients, scratchNormal);
        var distance = coefficients.w;

        if (!defined(result)) {
            return new Plane(normal, distance);
        } else {
            Cartesian3.clone(normal, result.normal);
            result.distance = distance;
            return result;
        }
    };

    /**
     * Computes the signed shortest distance of a point to a plane.
     * The sign of the distance determines which side of the plane the point
     * is on.  If the distance is positive, the point is in the half-space
     * in the direction of the normal; if negative, the point is in the half-space
     * opposite to the normal; if zero, the plane passes through the point.
     *
     * @param {Plane} plane The plane.
     * @param {Cartesian3} point The point.
     * @returns {Number} The signed shortest distance of the point to the plane.
     */
    Plane.getPointDistance = function(plane, point) {
                if (!defined(plane)) {
            throw new DeveloperError('plane is required.');
        }
        if (!defined(point)) {
            throw new DeveloperError('point is required.');
        }
        
        return Cartesian3.dot(plane.normal, point) + plane.distance;
    };

    /**
     * A constant initialized to the XY plane passing through the origin, with normal in positive Z.
     *
     * @type {Plane}
     * @constant
     */
    Plane.ORIGIN_XY_PLANE = freezeObject(new Plane(Cartesian3.UNIT_Z, 0.0));

    /**
     * A constant initialized to the YZ plane passing through the origin, with normal in positive X.
     *
     * @type {Plane}
     * @constant
     */
    Plane.ORIGIN_YZ_PLANE = freezeObject(new Plane(Cartesian3.UNIT_X, 0.0));

    /**
     * A constant initialized to the ZX plane passing through the origin, with normal in positive Y.
     *
     * @type {Plane}
     * @constant
     */
    Plane.ORIGIN_ZX_PLANE = freezeObject(new Plane(Cartesian3.UNIT_Y, 0.0));

    return Plane;
});

/*global define*/
define('Core/binarySearch',[
        './defined',
        './DeveloperError'
    ], function(
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Finds an item in a sorted array.
     *
     * @exports binarySearch
     *
     * @param {Array} array The sorted array to search.
     * @param {Object} itemToFind The item to find in the array.
     * @param {binarySearch~Comparator} comparator The function to use to compare the item to
     *        elements in the array.
     * @returns {Number} The index of <code>itemToFind</code> in the array, if it exists.  If <code>itemToFind</code>
     *        does not exist, the return value is a negative number which is the bitwise complement (~)
     *        of the index before which the itemToFind should be inserted in order to maintain the
     *        sorted order of the array.
     *
     * @example
     * // Create a comparator function to search through an array of numbers.
     * function comparator(a, b) {
     *     return a - b;
     * };
     * var numbers = [0, 2, 4, 6, 8];
     * var index = Cesium.binarySearch(numbers, 6, comparator); // 3
     */
    function binarySearch(array, itemToFind, comparator) {
                if (!defined(array)) {
            throw new DeveloperError('array is required.');
        }
        if (!defined(itemToFind)) {
            throw new DeveloperError('itemToFind is required.');
        }
        if (!defined(comparator)) {
            throw new DeveloperError('comparator is required.');
        }
        
        var low = 0;
        var high = array.length - 1;
        var i;
        var comparison;

        while (low <= high) {
            i = ~~((low + high) / 2);
            comparison = comparator(array[i], itemToFind);
            if (comparison < 0) {
                low = i + 1;
                continue;
            }
            if (comparison > 0) {
                high = i - 1;
                continue;
            }
            return i;
        }
        return ~(high + 1);
    }

    /**
     * A function used to compare two items while performing a binary search.
     * @callback binarySearch~Comparator
     *
     * @param {Object} a An item in the array.
     * @param {Object} b The item being searched for.
     * @returns {Number} Returns a negative value if <code>a</code> is less than <code>b</code>,
     *          a positive value if <code>a</code> is greater than <code>b</code>, or
     *          0 if <code>a</code> is equal to <code>b</code>.
     *
     * @example
     * function compareNumbers(a, b) {
     *     return a - b;
     * }
     */

    return binarySearch;
});

/*global define*/
define('Core/EarthOrientationParametersSample',[],function() {
    'use strict';

    /**
     * A set of Earth Orientation Parameters (EOP) sampled at a time.
     *
     * @alias EarthOrientationParametersSample
     * @constructor
     *
     * @param {Number} xPoleWander The pole wander about the X axis, in radians.
     * @param {Number} yPoleWander The pole wander about the Y axis, in radians.
     * @param {Number} xPoleOffset The offset to the Celestial Intermediate Pole (CIP) about the X axis, in radians.
     * @param {Number} yPoleOffset The offset to the Celestial Intermediate Pole (CIP) about the Y axis, in radians.
     * @param {Number} ut1MinusUtc The difference in time standards, UT1 - UTC, in seconds.
     *
     * @private
     */
    function EarthOrientationParametersSample(xPoleWander, yPoleWander, xPoleOffset, yPoleOffset, ut1MinusUtc) {
        /**
         * The pole wander about the X axis, in radians.
         * @type {Number}
         */
        this.xPoleWander = xPoleWander;

        /**
         * The pole wander about the Y axis, in radians.
         * @type {Number}
         */
        this.yPoleWander = yPoleWander;

        /**
         * The offset to the Celestial Intermediate Pole (CIP) about the X axis, in radians.
         * @type {Number}
         */
        this.xPoleOffset = xPoleOffset;

        /**
         * The offset to the Celestial Intermediate Pole (CIP) about the Y axis, in radians.
         * @type {Number}
         */
        this.yPoleOffset = yPoleOffset;

        /**
         * The difference in time standards, UT1 - UTC, in seconds.
         * @type {Number}
         */
        this.ut1MinusUtc = ut1MinusUtc;
    }

    return EarthOrientationParametersSample;
});

/**
@license
sprintf.js from the php.js project - https://github.com/kvz/phpjs
Directly from https://github.com/kvz/phpjs/blob/master/functions/strings/sprintf.js

php.js is copyright 2012 Kevin van Zonneveld.

Portions copyright Brett Zamir (http://brett-zamir.me), Kevin van Zonneveld
(http://kevin.vanzonneveld.net), Onno Marsman, Theriault, Michael White
(http://getsprink.com), Waldo Malqui Silva, Paulo Freitas, Jack, Jonas
Raoni Soares Silva (http://www.jsfromhell.com), Philip Peterson, Legaev
Andrey, Ates Goral (http://magnetiq.com), Alex, Ratheous, Martijn Wieringa,
Rafa? Kukawski (http://blog.kukawski.pl), lmeyrick
(https://sourceforge.net/projects/bcmath-js/), Nate, Philippe Baumann,
Enrique Gonzalez, Webtoolkit.info (http://www.webtoolkit.info/), Carlos R.
L. Rodrigues (http://www.jsfromhell.com), Ash Searle
(http://hexmen.com/blog/), Jani Hartikainen, travc, Ole Vrijenhoek,
Erkekjetter, Michael Grier, Rafa? Kukawski (http://kukawski.pl), Johnny
Mast (http://www.phpvrouwen.nl), T.Wild, d3x,
http://stackoverflow.com/questions/57803/how-to-convert-decimal-to-hex-in-javascript,
Rafa? Kukawski (http://blog.kukawski.pl/), stag019, pilus, WebDevHobo
(http://webdevhobo.blogspot.com/), marrtins, GeekFG
(http://geekfg.blogspot.com), Andrea Giammarchi
(http://webreflection.blogspot.com), Arpad Ray (mailto:arpad@php.net),
gorthaur, Paul Smith, Tim de Koning (http://www.kingsquare.nl), Joris, Oleg
Eremeev, Steve Hilder, majak, gettimeofday, KELAN, Josh Fraser
(http://onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/),
Marc Palau, Martin
(http://www.erlenwiese.de/), Breaking Par Consulting Inc
(http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256CFB006C45F7),
Chris, Mirek Slugen, saulius, Alfonso Jimenez
(http://www.alfonsojimenez.com), Diplom@t (http://difane.com/), felix,
Mailfaker (http://www.weedem.fr/), Tyler Akins (http://rumkin.com), Caio
Ariede (http://caioariede.com), Robin, Kankrelune
(http://www.webfaktory.info/), Karol Kowalski, Imgen Tata
(http://www.myipdf.com/), mdsjack (http://www.mdsjack.bo.it), Dreamer,
Felix Geisendoerfer (http://www.debuggable.com/felix), Lars Fischer, AJ,
David, Aman Gupta, Michael White, Public Domain
(http://www.json.org/json2.js), Steven Levithan
(http://blog.stevenlevithan.com), Sakimori, Pellentesque Malesuada,
Thunder.m, Dj (http://phpjs.org/functions/htmlentities:425#comment_134018),
Steve Clay, David James, Francois, class_exists, nobbler, T. Wild, Itsacon
(http://www.itsacon.net/), date, Ole Vrijenhoek (http://www.nervous.nl/),
Fox, Raphael (Ao RUDLER), Marco, noname, Mateusz "loonquawl" Zalega, Frank
Forte, Arno, ger, mktime, john (http://www.jd-tech.net), Nick Kolosov
(http://sammy.ru), marc andreu, Scott Cariss, Douglas Crockford
(http://javascript.crockford.com), madipta, Slawomir Kaniecki,
ReverseSyntax, Nathan, Alex Wilson, kenneth, Bayron Guevara, Adam Wallner
(http://web2.bitbaro.hu/), paulo kuong, jmweb, Lincoln Ramsay, djmix,
Pyerre, Jon Hohle, Thiago Mata (http://thiagomata.blog.com), lmeyrick
(https://sourceforge.net/projects/bcmath-js/this.), Linuxworld, duncan,
Gilbert, Sanjoy Roy, Shingo, sankai, Oskar Larsson H?gfeldt
(http://oskar-lh.name/), Denny Wardhana, 0m3r, Everlasto, Subhasis Deb,
josh, jd, Pier Paolo Ramon (http://www.mastersoup.com/), P, merabi, Soren
Hansen, Eugene Bulkin (http://doubleaw.com/), Der Simon
(http://innerdom.sourceforge.net/), echo is bad, Ozh, XoraX
(http://www.xorax.info), EdorFaus, JB, J A R, Marc Jansen, Francesco, LH,
Stoyan Kyosev (http://www.svest.org/), nord_ua, omid
(http://phpjs.org/functions/380:380#comment_137122), Brad Touesnard, MeEtc
(http://yass.meetcweb.com), Peter-Paul Koch
(http://www.quirksmode.org/js/beat.html), Olivier Louvignes
(http://mg-crea.com/), T0bsn, Tim Wiel, Bryan Elliott, Jalal Berrami,
Martin, JT, David Randall, Thomas Beaucourt (http://www.webapp.fr), taith,
vlado houba, Pierre-Luc Paour, Kristof Coomans (SCK-CEN Belgian Nucleair
Research Centre), Martin Pool, Kirk Strobeck, Rick Waldron, Brant Messenger
(http://www.brantmessenger.com/), Devan Penner-Woelk, Saulo Vallory, Wagner
B. Soares, Artur Tchernychev, Valentina De Rosa, Jason Wong
(http://carrot.org/), Christoph, Daniel Esteban, strftime, Mick@el, rezna,
Simon Willison (http://simonwillison.net), Anton Ongson, Gabriel Paderni,
Marco van Oort, penutbutterjelly, Philipp Lenssen, Bjorn Roesbeke
(http://www.bjornroesbeke.be/), Bug?, Eric Nagel, Tomasz Wesolowski,
Evertjan Garretsen, Bobby Drake, Blues (http://tech.bluesmoon.info/), Luke
Godfrey, Pul, uestla, Alan C, Ulrich, Rafal Kukawski, Yves Sucaet,
sowberry, Norman "zEh" Fuchs, hitwork, Zahlii, johnrembo, Nick Callen,
Steven Levithan (stevenlevithan.com), ejsanders, Scott Baker, Brian Tafoya
(http://www.premasolutions.com/), Philippe Jausions
(http://pear.php.net/user/jausions), Aidan Lister
(http://aidanlister.com/), Rob, e-mike, HKM, ChaosNo1, metjay, strcasecmp,
strcmp, Taras Bogach, jpfle, Alexander Ermolaev
(http://snippets.dzone.com/user/AlexanderErmolaev), DxGx, kilops, Orlando,
dptr1988, Le Torbi, James (http://www.james-bell.co.uk/), Pedro Tainha
(http://www.pedrotainha.com), James, Arnout Kazemier
(http://www.3rd-Eden.com), Chris McMacken, gabriel paderni, Yannoo,
FGFEmperor, baris ozdil, Tod Gentille, Greg Frazier, jakes, 3D-GRAF, Allan
Jensen (http://www.winternet.no), Howard Yeend, Benjamin Lupton, davook,
daniel airton wermann (http://wermann.com.br), Atli T¨®r, Maximusya, Ryan
W Tenney (http://ryan.10e.us), Alexander M Beedie, fearphage
(http://http/my.opera.com/fearphage/), Nathan Sepulveda, Victor, Matteo,
Billy, stensi, Cord, Manish, T.J. Leahy, Riddler
(http://www.frontierwebdev.com/), Rafa? Kukawski, FremyCompany, Matt
Bradley, Tim de Koning, Luis Salazar (http://www.freaky-media.com/), Diogo
Resende, Rival, Andrej Pavlovic, Garagoth, Le Torbi
(http://www.letorbi.de/), Dino, Josep Sanz (http://www.ws3.es/), rem,
Russell Walker (http://www.nbill.co.uk/), Jamie Beck
(http://www.terabit.ca/), setcookie, Michael, YUI Library:
http://developer.yahoo.com/yui/docs/YAHOO.util.DateLocale.html, Blues at
http://hacks.bluesmoon.info/strftime/strftime.js, Ben
(http://benblume.co.uk/), DtTvB
(http://dt.in.th/2008-09-16.string-length-in-bytes.html), Andreas, William,
meo, incidence, Cagri Ekin, Amirouche, Amir Habibi
(http://www.residence-mixte.com/), Luke Smith (http://lucassmith.name),
Kheang Hok Chin (http://www.distantia.ca/), Jay Klehr, Lorenzo Pisani,
Tony, Yen-Wei Liu, Greenseed, mk.keck, Leslie Hoare, dude, booeyOH, Ben
Bryan

Licensed under the MIT (MIT-LICENSE.txt) license.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL KEVIN VAN ZONNEVELD BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

/*global define*/
define('ThirdParty/sprintf',[],function() {

function sprintf () {
  // http://kevin.vanzonneveld.net
  // +   original by: Ash Searle (http://hexmen.com/blog/)
  // + namespaced by: Michael White (http://getsprink.com)
  // +    tweaked by: Jack
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +      input by: Paulo Freitas
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +      input by: Brett Zamir (http://brett-zamir.me)
  // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // +   improved by: Dj
  // +   improved by: Allidylls
  // *     example 1: sprintf("%01.2f", 123.1);
  // *     returns 1: 123.10
  // *     example 2: sprintf("[%10s]", 'monkey');
  // *     returns 2: '[    monkey]'
  // *     example 3: sprintf("[%'#10s]", 'monkey');
  // *     returns 3: '[####monkey]'
  // *     example 4: sprintf("%d", 123456789012345);
  // *     returns 4: '123456789012345'
  var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g;
  var a = arguments,
    i = 0,
    format = a[i++];

  // pad()
  var pad = function (str, len, chr, leftJustify) {
    if (!chr) {
      chr = ' ';
    }

    var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
    return leftJustify ? str + padding : padding + str;
  };

  // justify()
  var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
    var diff = minWidth - value.length;
    if (diff > 0) {
      if (leftJustify || !zeroPad) {
        value = pad(value, minWidth, customPadChar, leftJustify);
      } else {
        value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
      }
    }
    return value;
  };

  // formatBaseX()
  var formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
    // Note: casts negative numbers to positive ones
    var number = value >>> 0;
    prefix = prefix && number && {
      '2': '0b',
      '8': '0',
      '16': '0x'
    }[base] || '';
    value = prefix + pad(number.toString(base), precision || 0, '0', false);
    return justify(value, prefix, leftJustify, minWidth, zeroPad);
  };

  // formatString()
  var formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
    if (precision != null) {
      value = value.slice(0, precision);
    }
    return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
  };

  // doFormat()
  var doFormat = function (substring, valueIndex, flags, minWidth, _, precision, type) {
    var number;
    var prefix;
    var method;
    var textTransform;
    var value;

    if (substring == '%%') {
      return '%';
    }

    // parse flags
    var leftJustify = false,
      positivePrefix = '',
      zeroPad = false,
      prefixBaseX = false,
      customPadChar = ' ';
    var flagsl = flags.length;
    for (var j = 0; flags && j < flagsl; j++) {
      switch (flags.charAt(j)) {
      case ' ':
        positivePrefix = ' ';
        break;
      case '+':
        positivePrefix = '+';
        break;
      case '-':
        leftJustify = true;
        break;
      case "'":
        customPadChar = flags.charAt(j + 1);
        break;
      case '0':
        zeroPad = true;
        break;
      case '#':
        prefixBaseX = true;
        break;
      }
    }

    // parameters may be null, undefined, empty-string or real valued
    // we want to ignore null, undefined and empty-string values
    if (!minWidth) {
      minWidth = 0;
    } else if (minWidth == '*') {
      minWidth = +a[i++];
    } else if (minWidth.charAt(0) == '*') {
      minWidth = +a[minWidth.slice(1, -1)];
    } else {
      minWidth = +minWidth;
    }

    // Note: undocumented perl feature:
    if (minWidth < 0) {
      minWidth = -minWidth;
      leftJustify = true;
    }

    if (!isFinite(minWidth)) {
      throw new Error('sprintf: (minimum-)width must be finite');
    }

    if (!precision) {
      precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : undefined;
    } else if (precision == '*') {
      precision = +a[i++];
    } else if (precision.charAt(0) == '*') {
      precision = +a[precision.slice(1, -1)];
    } else {
      precision = +precision;
    }

    // grab value using valueIndex if required?
    value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

    switch (type) {
    case 's':
      return formatString(String(value), leftJustify, minWidth, precision, zeroPad, customPadChar);
    case 'c':
      return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
    case 'b':
      return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'o':
      return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'x':
      return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'X':
      return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
    case 'u':
      return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
    case 'i':
    case 'd':
      number = +value || 0;
      number = Math.round(number - number % 1); // Plain Math.round doesn't just truncate
      prefix = number < 0 ? '-' : positivePrefix;
      value = prefix + pad(String(Math.abs(number)), precision, '0', false);
      return justify(value, prefix, leftJustify, minWidth, zeroPad);
    case 'e':
    case 'E':
    case 'f': // Should handle locales (as per setlocale)
    case 'F':
    case 'g':
    case 'G':
      number = +value;
      prefix = number < 0 ? '-' : positivePrefix;
      method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
      textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
      value = prefix + Math.abs(number)[method](precision);
      return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
    default:
      return substring;
    }
  };

  return format.replace(regex, doFormat);
}

return sprintf;
});

/*global define*/
define('Core/GregorianDate',[],function() {
    'use strict';

    /**
     * Represents a Gregorian date in a more precise format than the JavaScript Date object.
     * In addition to submillisecond precision, this object can also represent leap seconds.
     * @alias GregorianDate
     * @constructor
     *
     * @see JulianDate#toGregorianDate
     */
    function GregorianDate(year, month, day, hour, minute, second, millisecond, isLeapSecond) {
        /**
         * Gets or sets the year as a whole number.
         * @type {Number}
         */
        this.year = year;
        /**
         * Gets or sets the month as a whole number with range [1, 12].
         * @type {Number}
         */
        this.month = month;
        /**
         * Gets or sets the day of the month as a whole number starting at 1.
         * @type {Number}
         */
        this.day = day;
        /**
         * Gets or sets the hour as a whole number with range [0, 23].
         * @type {Number}
         */
        this.hour = hour;
        /**
         * Gets or sets the minute of the hour as a whole number with range [0, 59].
         * @type {Number}
         */
        this.minute = minute;
        /**
         * Gets or sets the second of the minute as a whole number with range [0, 60], with 60 representing a leap second.
         * @type {Number}
         */
        this.second = second;
        /**
         * Gets or sets the millisecond of the second as a floating point number with range [0.0, 1000.0).
         * @type {Number}
         */
        this.millisecond = millisecond;
        /**
         * Gets or sets whether this time is during a leap second.
         * @type {Boolean}
         */
        this.isLeapSecond = isLeapSecond;
    }

    return GregorianDate;
});

/*global define*/
define('Core/isLeapYear',[
        './DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * Determines if a given date is a leap year.
     *
     * @exports isLeapYear
     *
     * @param {Number} year The year to be tested.
     * @returns {Boolean} True if <code>year</code> is a leap year.
     *
     * @example
     * var leapYear = Cesium.isLeapYear(2000); // true
     */
    function isLeapYear(year) {
                if (year === null || isNaN(year)) {
            throw new DeveloperError('year is required and must be a number.');
        }
        
        return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
    }

    return isLeapYear;
});

/*global define*/
define('Core/LeapSecond',[],function() {
    'use strict';

    /**
     * Describes a single leap second, which is constructed from a {@link JulianDate} and a
     * numerical offset representing the number of seconds TAI is ahead of the UTC time standard.
     * @alias LeapSecond
     * @constructor
     *
     * @param {JulianDate} [date] A Julian date representing the time of the leap second.
     * @param {Number} [offset] The cumulative number of seconds that TAI is ahead of UTC at the provided date.
     */
    function LeapSecond(date, offset) {
        /**
         * Gets or sets the date at which this leap second occurs.
         * @type {JulianDate}
         */
        this.julianDate = date;

        /**
         * Gets or sets the cumulative number of seconds between the UTC and TAI time standards at the time
         * of this leap second.
         * @type {Number}
         */
        this.offset = offset;
    }

    return LeapSecond;
});

/*global define*/
define('Core/TimeConstants',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Constants for time conversions like those done by {@link JulianDate}.
     *
     * @exports TimeConstants
     *
     * @see JulianDate
     *
     * @private
     */
    var TimeConstants = {
        /**
         * The number of seconds in one millisecond: <code>0.001</code>
         * @type {Number}
         * @constant
         */
        SECONDS_PER_MILLISECOND : 0.001,

        /**
         * The number of seconds in one minute: <code>60</code>.
         * @type {Number}
         * @constant
         */
        SECONDS_PER_MINUTE : 60.0,

        /**
         * The number of minutes in one hour: <code>60</code>.
         * @type {Number}
         * @constant
         */
        MINUTES_PER_HOUR : 60.0,

        /**
         * The number of hours in one day: <code>24</code>.
         * @type {Number}
         * @constant
         */
        HOURS_PER_DAY : 24.0,

        /**
         * The number of seconds in one hour: <code>3600</code>.
         * @type {Number}
         * @constant
         */
        SECONDS_PER_HOUR : 3600.0,

        /**
         * The number of minutes in one day: <code>1440</code>.
         * @type {Number}
         * @constant
         */
        MINUTES_PER_DAY : 1440.0,

        /**
         * The number of seconds in one day, ignoring leap seconds: <code>86400</code>.
         * @type {Number}
         * @constant
         */
        SECONDS_PER_DAY : 86400.0,

        /**
         * The number of days in one Julian century: <code>36525</code>.
         * @type {Number}
         * @constant
         */
        DAYS_PER_JULIAN_CENTURY : 36525.0,

        /**
         * One trillionth of a second.
         * @type {Number}
         * @constant
         */
        PICOSECOND : 0.000000001,

        /**
         * The number of days to subtract from a Julian date to determine the
         * modified Julian date, which gives the number of days since midnight
         * on November 17, 1858.
         * @type {Number}
         * @constant
         */
        MODIFIED_JULIAN_DATE_DIFFERENCE : 2400000.5
    };

    return freezeObject(TimeConstants);
});

/*global define*/
define('Core/TimeStandard',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Provides the type of time standards which JulianDate can take as input.
     *
     * @exports TimeStandard
     *
     * @see JulianDate
     */
    var TimeStandard = {
        /**
         * Represents the coordinated Universal Time (UTC) time standard.
         *
         * UTC is related to TAI according to the relationship
         * <code>UTC = TAI - deltaT</code> where <code>deltaT</code> is the number of leap
         * seconds which have been introduced as of the time in TAI.
         *
         */
        UTC : 0,

        /**
         * Represents the International Atomic Time (TAI) time standard.
         * TAI is the principal time standard to which the other time standards are related.
         */
        TAI : 1
    };

    return freezeObject(TimeStandard);
});

/*global define*/
define('Core/JulianDate',[
        '../ThirdParty/sprintf',
        './binarySearch',
        './defaultValue',
        './defined',
        './DeveloperError',
        './GregorianDate',
        './isLeapYear',
        './LeapSecond',
        './TimeConstants',
        './TimeStandard'
    ], function(
        sprintf,
        binarySearch,
        defaultValue,
        defined,
        DeveloperError,
        GregorianDate,
        isLeapYear,
        LeapSecond,
        TimeConstants,
        TimeStandard) {
    'use strict';

    var gregorianDateScratch = new GregorianDate();
    var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    var daysInLeapFeburary = 29;

    function compareLeapSecondDates(leapSecond, dateToFind) {
        return JulianDate.compare(leapSecond.julianDate, dateToFind.julianDate);
    }

    // we don't really need a leap second instance, anything with a julianDate property will do
    var binarySearchScratchLeapSecond = new LeapSecond();

    function convertUtcToTai(julianDate) {
        //Even though julianDate is in UTC, we'll treat it as TAI and
        //search the leap second table for it.
        binarySearchScratchLeapSecond.julianDate = julianDate;
        var leapSeconds = JulianDate.leapSeconds;
        var index = binarySearch(leapSeconds, binarySearchScratchLeapSecond, compareLeapSecondDates);

        if (index < 0) {
            index = ~index;
        }

        if (index >= leapSeconds.length) {
            index = leapSeconds.length - 1;
        }

        var offset = leapSeconds[index].offset;
        if (index > 0) {
            //Now we have the index of the closest leap second that comes on or after our UTC time.
            //However, if the difference between the UTC date being converted and the TAI
            //defined leap second is greater than the offset, we are off by one and need to use
            //the previous leap second.
            var difference = JulianDate.secondsDifference(leapSeconds[index].julianDate, julianDate);
            if (difference > offset) {
                index--;
                offset = leapSeconds[index].offset;
            }
        }

        JulianDate.addSeconds(julianDate, offset, julianDate);
    }

    function convertTaiToUtc(julianDate, result) {
        binarySearchScratchLeapSecond.julianDate = julianDate;
        var leapSeconds = JulianDate.leapSeconds;
        var index = binarySearch(leapSeconds, binarySearchScratchLeapSecond, compareLeapSecondDates);
        if (index < 0) {
            index = ~index;
        }

        //All times before our first leap second get the first offset.
        if (index === 0) {
            return JulianDate.addSeconds(julianDate, -leapSeconds[0].offset, result);
        }

        //All times after our leap second get the last offset.
        if (index >= leapSeconds.length) {
            return JulianDate.addSeconds(julianDate, -leapSeconds[index - 1].offset, result);
        }

        //Compute the difference between the found leap second and the time we are converting.
        var difference = JulianDate.secondsDifference(leapSeconds[index].julianDate, julianDate);

        if (difference === 0) {
            //The date is in our leap second table.
            return JulianDate.addSeconds(julianDate, -leapSeconds[index].offset, result);
        }

        if (difference <= 1.0) {
            //The requested date is during the moment of a leap second, then we cannot convert to UTC
            return undefined;
        }

        //The time is in between two leap seconds, index is the leap second after the date
        //we're converting, so we subtract one to get the correct LeapSecond instance.
        return JulianDate.addSeconds(julianDate, -leapSeconds[--index].offset, result);
    }

    function setComponents(wholeDays, secondsOfDay, julianDate) {
        var extraDays = (secondsOfDay / TimeConstants.SECONDS_PER_DAY) | 0;
        wholeDays += extraDays;
        secondsOfDay -= TimeConstants.SECONDS_PER_DAY * extraDays;

        if (secondsOfDay < 0) {
            wholeDays--;
            secondsOfDay += TimeConstants.SECONDS_PER_DAY;
        }

        julianDate.dayNumber = wholeDays;
        julianDate.secondsOfDay = secondsOfDay;
        return julianDate;
    }

    function computeJulianDateComponents(year, month, day, hour, minute, second, millisecond) {
        // Algorithm from page 604 of the Explanatory Supplement to the
        // Astronomical Almanac (Seidelmann 1992).

        var a = ((month - 14) / 12) | 0;
        var b = year + 4800 + a;
        var dayNumber = (((1461 * b) / 4) | 0) + (((367 * (month - 2 - 12 * a)) / 12) | 0) - (((3 * (((b + 100) / 100) | 0)) / 4) | 0) + day - 32075;

        // JulianDates are noon-based
        hour = hour - 12;
        if (hour < 0) {
            hour += 24;
        }

        var secondsOfDay = second + ((hour * TimeConstants.SECONDS_PER_HOUR) + (minute * TimeConstants.SECONDS_PER_MINUTE) + (millisecond * TimeConstants.SECONDS_PER_MILLISECOND));

        if (secondsOfDay >= 43200.0) {
            dayNumber -= 1;
        }

        return [dayNumber, secondsOfDay];
    }

    //Regular expressions used for ISO8601 date parsing.
    //YYYY
    var matchCalendarYear = /^(\d{4})$/;
    //YYYY-MM (YYYYMM is invalid)
    var matchCalendarMonth = /^(\d{4})-(\d{2})$/;
    //YYYY-DDD or YYYYDDD
    var matchOrdinalDate = /^(\d{4})-?(\d{3})$/;
    //YYYY-Www or YYYYWww or YYYY-Www-D or YYYYWwwD
    var matchWeekDate = /^(\d{4})-?W(\d{2})-?(\d{1})?$/;
    //YYYY-MM-DD or YYYYMMDD
    var matchCalendarDate = /^(\d{4})-?(\d{2})-?(\d{2})$/;
    // Match utc offset
    var utcOffset = /([Z+\-])?(\d{2})?:?(\d{2})?$/;
    // Match hours HH or HH.xxxxx
    var matchHours = /^(\d{2})(\.\d+)?/.source + utcOffset.source;
    // Match hours/minutes HH:MM HHMM.xxxxx
    var matchHoursMinutes = /^(\d{2}):?(\d{2})(\.\d+)?/.source + utcOffset.source;
    // Match hours/minutes HH:MM:SS HHMMSS.xxxxx
    var matchHoursMinutesSeconds = /^(\d{2}):?(\d{2}):?(\d{2})(\.\d+)?/.source + utcOffset.source;

    var iso8601ErrorMessage = 'Invalid ISO 8601 date.';

    /**
     * Represents an astronomical Julian date, which is the number of days since noon on January 1, -4712 (4713 BC).
     * For increased precision, this class stores the whole number part of the date and the seconds
     * part of the date in separate components.  In order to be safe for arithmetic and represent
     * leap seconds, the date is always stored in the International Atomic Time standard
     * {@link TimeStandard.TAI}.
     * @alias JulianDate
     * @constructor
     *
     * @param {Number} [julianDayNumber=0.0] The Julian Day Number representing the number of whole days.  Fractional days will also be handled correctly.
     * @param {Number} [secondsOfDay=0.0] The number of seconds into the current Julian Day Number.  Fractional seconds, negative seconds and seconds greater than a day will be handled correctly.
     * @param {TimeStandard} [timeStandard=TimeStandard.UTC] The time standard in which the first two parameters are defined.
     */
    function JulianDate(julianDayNumber, secondsOfDay, timeStandard) {
        /**
         * Gets or sets the number of whole days.
         * @type {Number}
         */
        this.dayNumber = undefined;

        /**
         * Gets or sets the number of seconds into the current day.
         * @type {Number}
         */
        this.secondsOfDay = undefined;

        julianDayNumber = defaultValue(julianDayNumber, 0.0);
        secondsOfDay = defaultValue(secondsOfDay, 0.0);
        timeStandard = defaultValue(timeStandard, TimeStandard.UTC);

        //If julianDayNumber is fractional, make it an integer and add the number of seconds the fraction represented.
        var wholeDays = julianDayNumber | 0;
        secondsOfDay = secondsOfDay + (julianDayNumber - wholeDays) * TimeConstants.SECONDS_PER_DAY;

        setComponents(wholeDays, secondsOfDay, this);

        if (timeStandard === TimeStandard.UTC) {
            convertUtcToTai(this);
        }
    }

    /**
     * Creates a new instance from a JavaScript Date.
     *
     * @param {Date} date A JavaScript Date.
     * @param {JulianDate} [result] An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter or a new instance if none was provided.
     *
     * @exception {DeveloperError} date must be a valid JavaScript Date.
     */
    JulianDate.fromDate = function(date, result) {
                if (!(date instanceof Date) || isNaN(date.getTime())) {
            throw new DeveloperError('date must be a valid JavaScript Date.');
        }
        
        var components = computeJulianDateComponents(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
        if (!defined(result)) {
            return new JulianDate(components[0], components[1], TimeStandard.UTC);
        }
        setComponents(components[0], components[1], result);
        convertUtcToTai(result);
        return result;
    };

    /**
     * Creates a new instance from a from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} date.
     * This method is superior to <code>Date.parse</code> because it will handle all valid formats defined by the ISO 8601
     * specification, including leap seconds and sub-millisecond times, which discarded by most JavaScript implementations.
     *
     * @param {String} iso8601String An ISO 8601 date.
     * @param {JulianDate} [result] An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter or a new instance if none was provided.
     *
     * @exception {DeveloperError} Invalid ISO 8601 date.
     */
    JulianDate.fromIso8601 = function(iso8601String, result) {
                if (typeof iso8601String !== 'string') {
            throw new DeveloperError(iso8601ErrorMessage);
        }
        
        //Comma and decimal point both indicate a fractional number according to ISO 8601,
        //start out by blanket replacing , with . which is the only valid such symbol in JS.
        iso8601String = iso8601String.replace(',', '.');

        //Split the string into its date and time components, denoted by a mandatory T
        var tokens = iso8601String.split('T');
        var year;
        var month = 1;
        var day = 1;
        var hour = 0;
        var minute = 0;
        var second = 0;
        var millisecond = 0;

        //Lacking a time is okay, but a missing date is illegal.
        var date = tokens[0];
        var time = tokens[1];
        var tmp;
        var inLeapYear;
                if (!defined(date)) {
            throw new DeveloperError(iso8601ErrorMessage);
        }

        var dashCount;
        
        //First match the date against possible regular expressions.
        tokens = date.match(matchCalendarDate);
        if (tokens !== null) {
                        dashCount = date.split('-').length - 1;
            if (dashCount > 0 && dashCount !== 2) {
                throw new DeveloperError(iso8601ErrorMessage);
            }
                        year = +tokens[1];
            month = +tokens[2];
            day = +tokens[3];
        } else {
            tokens = date.match(matchCalendarMonth);
            if (tokens !== null) {
                year = +tokens[1];
                month = +tokens[2];
            } else {
                tokens = date.match(matchCalendarYear);
                if (tokens !== null) {
                    year = +tokens[1];
                } else {
                    //Not a year/month/day so it must be an ordinal date.
                    var dayOfYear;
                    tokens = date.match(matchOrdinalDate);
                    if (tokens !== null) {

                        year = +tokens[1];
                        dayOfYear = +tokens[2];
                        inLeapYear = isLeapYear(year);

                        //This validation is only applicable for this format.
                                                if (dayOfYear < 1 || (inLeapYear && dayOfYear > 366) || (!inLeapYear && dayOfYear > 365)) {
                            throw new DeveloperError(iso8601ErrorMessage);
                        }
                                            } else {
                        tokens = date.match(matchWeekDate);
                        if (tokens !== null) {
                            //ISO week date to ordinal date from
                            //http://en.wikipedia.org/w/index.php?title=ISO_week_date&oldid=474176775
                            year = +tokens[1];
                            var weekNumber = +tokens[2];
                            var dayOfWeek = +tokens[3] || 0;

                                                        dashCount = date.split('-').length - 1;
                            if (dashCount > 0 &&
                               ((!defined(tokens[3]) && dashCount !== 1) ||
                               (defined(tokens[3]) && dashCount !== 2))) {
                                throw new DeveloperError(iso8601ErrorMessage);
                            }
                            
                            var january4 = new Date(Date.UTC(year, 0, 4));
                            dayOfYear = (weekNumber * 7) + dayOfWeek - january4.getUTCDay() - 3;
                        } else {
                            //None of our regular expressions succeeded in parsing the date properly.
                                                        throw new DeveloperError(iso8601ErrorMessage);
                                                    }
                    }
                    //Split an ordinal date into month/day.
                    tmp = new Date(Date.UTC(year, 0, 1));
                    tmp.setUTCDate(dayOfYear);
                    month = tmp.getUTCMonth() + 1;
                    day = tmp.getUTCDate();
                }
            }
        }

        //Now that we have all of the date components, validate them to make sure nothing is out of range.
        inLeapYear = isLeapYear(year);
                if (month < 1 || month > 12 || day < 1 || ((month !== 2 || !inLeapYear) && day > daysInMonth[month - 1]) || (inLeapYear && month === 2 && day > daysInLeapFeburary)) {
            throw new DeveloperError(iso8601ErrorMessage);
        }
        
        //Not move onto the time string, which is much simpler.
        var offsetIndex;
        if (defined(time)) {
            tokens = time.match(matchHoursMinutesSeconds);
            if (tokens !== null) {
                                dashCount = time.split(':').length - 1;
                if (dashCount > 0 && dashCount !== 2 && dashCount !== 3) {
                    throw new DeveloperError(iso8601ErrorMessage);
                }
                
                hour = +tokens[1];
                minute = +tokens[2];
                second = +tokens[3];
                millisecond = +(tokens[4] || 0) * 1000.0;
                offsetIndex = 5;
            } else {
                tokens = time.match(matchHoursMinutes);
                if (tokens !== null) {
                                        dashCount = time.split(':').length - 1;
                    if (dashCount > 2) {
                        throw new DeveloperError(iso8601ErrorMessage);
                    }
                    
                    hour = +tokens[1];
                    minute = +tokens[2];
                    second = +(tokens[3] || 0) * 60.0;
                    offsetIndex = 4;
                } else {
                    tokens = time.match(matchHours);
                    if (tokens !== null) {
                        hour = +tokens[1];
                        minute = +(tokens[2] || 0) * 60.0;
                        offsetIndex = 3;
                    } else {
                                                throw new DeveloperError(iso8601ErrorMessage);
                                            }
                }
            }

            //Validate that all values are in proper range.  Minutes and hours have special cases at 60 and 24.
                        if (minute >= 60 || second >= 61 || hour > 24 || (hour === 24 && (minute > 0 || second > 0 || millisecond > 0))) {
                throw new DeveloperError(iso8601ErrorMessage);
            }
            
            //Check the UTC offset value, if no value exists, use local time
            //a Z indicates UTC, + or - are offsets.
            var offset = tokens[offsetIndex];
            var offsetHours = +(tokens[offsetIndex + 1]);
            var offsetMinutes = +(tokens[offsetIndex + 2] || 0);
            switch (offset) {
            case '+':
                hour = hour - offsetHours;
                minute = minute - offsetMinutes;
                break;
            case '-':
                hour = hour + offsetHours;
                minute = minute + offsetMinutes;
                break;
            case 'Z':
                break;
            default:
                minute = minute + new Date(Date.UTC(year, month - 1, day, hour, minute)).getTimezoneOffset();
                break;
            }
        } else {
            //If no time is specified, it is considered the beginning of the day, local time.
            minute = minute + new Date(year, month - 1, day).getTimezoneOffset();
        }

        //ISO8601 denotes a leap second by any time having a seconds component of 60 seconds.
        //If that's the case, we need to temporarily subtract a second in order to build a UTC date.
        //Then we add it back in after converting to TAI.
        var isLeapSecond = second === 60;
        if (isLeapSecond) {
            second--;
        }

        //Even if we successfully parsed the string into its components, after applying UTC offset or
        //special cases like 24:00:00 denoting midnight, we need to normalize the data appropriately.

        //milliseconds can never be greater than 1000, and seconds can't be above 60, so we start with minutes
        while (minute >= 60) {
            minute -= 60;
            hour++;
        }

        while (hour >= 24) {
            hour -= 24;
            day++;
        }

        tmp = (inLeapYear && month === 2) ? daysInLeapFeburary : daysInMonth[month - 1];
        while (day > tmp) {
            day -= tmp;
            month++;

            if (month > 12) {
                month -= 12;
                year++;
            }

            tmp = (inLeapYear && month === 2) ? daysInLeapFeburary : daysInMonth[month - 1];
        }

        //If UTC offset is at the beginning/end of the day, minutes can be negative.
        while (minute < 0) {
            minute += 60;
            hour--;
        }

        while (hour < 0) {
            hour += 24;
            day--;
        }

        while (day < 1) {
            month--;
            if (month < 1) {
                month += 12;
                year--;
            }

            tmp = (inLeapYear && month === 2) ? daysInLeapFeburary : daysInMonth[month - 1];
            day += tmp;
        }

        //Now create the JulianDate components from the Gregorian date and actually create our instance.
        var components = computeJulianDateComponents(year, month, day, hour, minute, second, millisecond);

        if (!defined(result)) {
            result = new JulianDate(components[0], components[1], TimeStandard.UTC);
        } else {
            setComponents(components[0], components[1], result);
            convertUtcToTai(result);
        }

        //If we were on a leap second, add it back.
        if (isLeapSecond) {
            JulianDate.addSeconds(result, 1, result);
        }

        return result;
    };

    /**
     * Creates a new instance that represents the current system time.
     * This is equivalent to calling <code>JulianDate.fromDate(new Date());</code>.
     *
     * @param {JulianDate} [result] An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter or a new instance if none was provided.
     */
    JulianDate.now = function(result) {
        return JulianDate.fromDate(new Date(), result);
    };

    var toGregorianDateScratch = new JulianDate(0, 0, TimeStandard.TAI);

    /**
     * Creates a {@link GregorianDate} from the provided instance.
     *
     * @param {JulianDate} julianDate The date to be converted.
     * @param {GregorianDate} [result] An existing instance to use for the result.
     * @returns {GregorianDate} The modified result parameter or a new instance if none was provided.
     */
    JulianDate.toGregorianDate = function(julianDate, result) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        
        var isLeapSecond = false;
        var thisUtc = convertTaiToUtc(julianDate, toGregorianDateScratch);
        if (!defined(thisUtc)) {
            //Conversion to UTC will fail if we are during a leap second.
            //If that's the case, subtract a second and convert again.
            //JavaScript doesn't support leap seconds, so this results in second 59 being repeated twice.
            JulianDate.addSeconds(julianDate, -1, toGregorianDateScratch);
            thisUtc = convertTaiToUtc(toGregorianDateScratch, toGregorianDateScratch);
            isLeapSecond = true;
        }

        var julianDayNumber = thisUtc.dayNumber;
        var secondsOfDay = thisUtc.secondsOfDay;

        if (secondsOfDay >= 43200.0) {
            julianDayNumber += 1;
        }

        // Algorithm from page 604 of the Explanatory Supplement to the
        // Astronomical Almanac (Seidelmann 1992).
        var L = (julianDayNumber + 68569) | 0;
        var N = (4 * L / 146097) | 0;
        L = (L - (((146097 * N + 3) / 4) | 0)) | 0;
        var I = ((4000 * (L + 1)) / 1461001) | 0;
        L = (L - (((1461 * I) / 4) | 0) + 31) | 0;
        var J = ((80 * L) / 2447) | 0;
        var day = (L - (((2447 * J) / 80) | 0)) | 0;
        L = (J / 11) | 0;
        var month = (J + 2 - 12 * L) | 0;
        var year = (100 * (N - 49) + I + L) | 0;

        var hour = (secondsOfDay / TimeConstants.SECONDS_PER_HOUR) | 0;
        var remainingSeconds = secondsOfDay - (hour * TimeConstants.SECONDS_PER_HOUR);
        var minute = (remainingSeconds / TimeConstants.SECONDS_PER_MINUTE) | 0;
        remainingSeconds = remainingSeconds - (minute * TimeConstants.SECONDS_PER_MINUTE);
        var second = remainingSeconds | 0;
        var millisecond = ((remainingSeconds - second) / TimeConstants.SECONDS_PER_MILLISECOND);

        // JulianDates are noon-based
        hour += 12;
        if (hour > 23) {
            hour -= 24;
        }

        //If we were on a leap second, add it back.
        if (isLeapSecond) {
            second += 1;
        }

        if (!defined(result)) {
            return new GregorianDate(year, month, day, hour, minute, second, millisecond, isLeapSecond);
        }

        result.year = year;
        result.month = month;
        result.day = day;
        result.hour = hour;
        result.minute = minute;
        result.second = second;
        result.millisecond = millisecond;
        result.isLeapSecond = isLeapSecond;
        return result;
    };

    /**
     * Creates a JavaScript Date from the provided instance.
     * Since JavaScript dates are only accurate to the nearest millisecond and
     * cannot represent a leap second, consider using {@link JulianDate.toGregorianDate} instead.
     * If the provided JulianDate is during a leap second, the previous second is used.
     *
     * @param {JulianDate} julianDate The date to be converted.
     * @returns {Date} A new instance representing the provided date.
     */
    JulianDate.toDate = function(julianDate) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        
        var gDate = JulianDate.toGregorianDate(julianDate, gregorianDateScratch);
        var second = gDate.second;
        if (gDate.isLeapSecond) {
            second -= 1;
        }
        return new Date(Date.UTC(gDate.year, gDate.month - 1, gDate.day, gDate.hour, gDate.minute, second, gDate.millisecond));
    };

    /**
     * Creates an ISO8601 representation of the provided date.
     *
     * @param {JulianDate} julianDate The date to be converted.
     * @param {Number} [precision] The number of fractional digits used to represent the seconds component.  By default, the most precise representation is used.
     * @returns {String} The ISO8601 representation of the provided date.
     */
    JulianDate.toIso8601 = function(julianDate, precision) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        
        var gDate = JulianDate.toGregorianDate(julianDate, gDate);
        var millisecondStr;

        if (!defined(precision) && gDate.millisecond !== 0) {
            //Forces milliseconds into a number with at least 3 digits to whatever the default toString() precision is.
            millisecondStr = (gDate.millisecond * 0.01).toString().replace('.', '');
            return sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%sZ", gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second, millisecondStr);
        }

        //Precision is either 0 or milliseconds is 0 with undefined precision, in either case, leave off milliseconds entirely
        if (!defined(precision) || precision === 0) {
            return sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second);
        }

        //Forces milliseconds into a number with at least 3 digits to whatever the specified precision is.
        millisecondStr = (gDate.millisecond * 0.01).toFixed(precision).replace('.', '').slice(0, precision);
        return sprintf("%04d-%02d-%02dT%02d:%02d:%02d.%sZ", gDate.year, gDate.month, gDate.day, gDate.hour, gDate.minute, gDate.second, millisecondStr);
    };

    /**
     * Duplicates a JulianDate instance.
     *
     * @param {JulianDate} julianDate The date to duplicate.
     * @param {JulianDate} [result] An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter or a new instance if none was provided. Returns undefined if julianDate is undefined.
     */
    JulianDate.clone = function(julianDate, result) {
        if (!defined(julianDate)) {
            return undefined;
        }
        if (!defined(result)) {
            return new JulianDate(julianDate.dayNumber, julianDate.secondsOfDay, TimeStandard.TAI);
        }
        result.dayNumber = julianDate.dayNumber;
        result.secondsOfDay = julianDate.secondsOfDay;
        return result;
    };

    /**
     * Compares two instances.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Number} A negative value if left is less than right, a positive value if left is greater than right, or zero if left and right are equal.
     */
    JulianDate.compare = function(left, right) {
                if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required.');
        }
        
        var julianDayNumberDifference = left.dayNumber - right.dayNumber;
        if (julianDayNumberDifference !== 0) {
            return julianDayNumberDifference;
        }
        return left.secondsOfDay - right.secondsOfDay;
    };

    /**
     * Compares two instances and returns <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {JulianDate} [left] The first instance.
     * @param {JulianDate} [right] The second instance.
     * @returns {Boolean} <code>true</code> if the dates are equal; otherwise, <code>false</code>.
     */
    JulianDate.equals = function(left, right) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                left.dayNumber === right.dayNumber &&
                left.secondsOfDay === right.secondsOfDay);
    };

    /**
     * Compares two instances and returns <code>true</code> if they are within <code>epsilon</code> seconds of
     * each other.  That is, in order for the dates to be considered equal (and for
     * this function to return <code>true</code>), the absolute value of the difference between them, in
     * seconds, must be less than <code>epsilon</code>.
     *
     * @param {JulianDate} [left] The first instance.
     * @param {JulianDate} [right] The second instance.
     * @param {Number} epsilon The maximum number of seconds that should separate the two instances.
     * @returns {Boolean} <code>true</code> if the two dates are within <code>epsilon</code> seconds of each other; otherwise <code>false</code>.
     */
    JulianDate.equalsEpsilon = function(left, right, epsilon) {
                if (!defined(epsilon)) {
            throw new DeveloperError('epsilon is required.');
        }
        
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                Math.abs(JulianDate.secondsDifference(left, right)) <= epsilon);
    };

    /**
     * Computes the total number of whole and fractional days represented by the provided instance.
     *
     * @param {JulianDate} julianDate The date.
     * @returns {Number} The Julian date as single floating point number.
     */
    JulianDate.totalDays = function(julianDate) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
                return julianDate.dayNumber + (julianDate.secondsOfDay / TimeConstants.SECONDS_PER_DAY);
    };

    /**
     * Computes the difference in seconds between the provided instance.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Number} The difference, in seconds, when subtracting <code>right</code> from <code>left</code>.
     */
    JulianDate.secondsDifference = function(left, right) {
                if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required.');
        }
        
        var dayDifference = (left.dayNumber - right.dayNumber) * TimeConstants.SECONDS_PER_DAY;
        return (dayDifference + (left.secondsOfDay - right.secondsOfDay));
    };

    /**
     * Computes the difference in days between the provided instance.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Number} The difference, in days, when subtracting <code>right</code> from <code>left</code>.
     */
    JulianDate.daysDifference = function(left, right) {
                if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required.');
        }
        
        var dayDifference = (left.dayNumber - right.dayNumber);
        var secondDifference = (left.secondsOfDay - right.secondsOfDay) / TimeConstants.SECONDS_PER_DAY;
        return dayDifference + secondDifference;
    };

    /**
     * Computes the number of seconds the provided instance is ahead of UTC.
     *
     * @param {JulianDate} julianDate The date.
     * @returns {Number} The number of seconds the provided instance is ahead of UTC
     */
    JulianDate.computeTaiMinusUtc = function(julianDate) {
        binarySearchScratchLeapSecond.julianDate = julianDate;
        var leapSeconds = JulianDate.leapSeconds;
        var index = binarySearch(leapSeconds, binarySearchScratchLeapSecond, compareLeapSecondDates);
        if (index < 0) {
            index = ~index;
            --index;
            if (index < 0) {
                index = 0;
            }
        }
        return leapSeconds[index].offset;
    };

    /**
     * Adds the provided number of seconds to the provided date instance.
     *
     * @param {JulianDate} julianDate The date.
     * @param {Number} seconds The number of seconds to add or subtract.
     * @param {JulianDate} result An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter.
     */
    JulianDate.addSeconds = function(julianDate, seconds, result) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        if (!defined(seconds)) {
            throw new DeveloperError('seconds is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        return setComponents(julianDate.dayNumber, julianDate.secondsOfDay + seconds, result);
    };

    /**
     * Adds the provided number of minutes to the provided date instance.
     *
     * @param {JulianDate} julianDate The date.
     * @param {Number} minutes The number of minutes to add or subtract.
     * @param {JulianDate} result An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter.
     */
    JulianDate.addMinutes = function(julianDate, minutes, result) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        if (!defined(minutes)) {
            throw new DeveloperError('minutes is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var newSecondsOfDay = julianDate.secondsOfDay + (minutes * TimeConstants.SECONDS_PER_MINUTE);
        return setComponents(julianDate.dayNumber, newSecondsOfDay, result);
    };

    /**
     * Adds the provided number of hours to the provided date instance.
     *
     * @param {JulianDate} julianDate The date.
     * @param {Number} hours The number of hours to add or subtract.
     * @param {JulianDate} result An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter.
     */
    JulianDate.addHours = function(julianDate, hours, result) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        if (!defined(hours)) {
            throw new DeveloperError('hours is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var newSecondsOfDay = julianDate.secondsOfDay + (hours * TimeConstants.SECONDS_PER_HOUR);
        return setComponents(julianDate.dayNumber, newSecondsOfDay, result);
    };

    /**
     * Adds the provided number of days to the provided date instance.
     *
     * @param {JulianDate} julianDate The date.
     * @param {Number} days The number of days to add or subtract.
     * @param {JulianDate} result An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter.
     */
    JulianDate.addDays = function(julianDate, days, result) {
                if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        if (!defined(days)) {
            throw new DeveloperError('days is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var newJulianDayNumber = julianDate.dayNumber + days;
        return setComponents(newJulianDayNumber, julianDate.secondsOfDay, result);
    };

    /**
     * Compares the provided instances and returns <code>true</code> if <code>left</code> is earlier than <code>right</code>, <code>false</code> otherwise.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Boolean} <code>true</code> if <code>left</code> is earlier than <code>right</code>, <code>false</code> otherwise.
     */
    JulianDate.lessThan = function(left, right) {
        return JulianDate.compare(left, right) < 0;
    };

    /**
     * Compares the provided instances and returns <code>true</code> if <code>left</code> is earlier than or equal to <code>right</code>, <code>false</code> otherwise.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Boolean} <code>true</code> if <code>left</code> is earlier than or equal to <code>right</code>, <code>false</code> otherwise.
     */
    JulianDate.lessThanOrEquals = function(left, right) {
        return JulianDate.compare(left, right) <= 0;
    };

    /**
     * Compares the provided instances and returns <code>true</code> if <code>left</code> is later than <code>right</code>, <code>false</code> otherwise.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Boolean} <code>true</code> if <code>left</code> is later than <code>right</code>, <code>false</code> otherwise.
     */
    JulianDate.greaterThan = function(left, right) {
        return JulianDate.compare(left, right) > 0;
    };

    /**
     * Compares the provided instances and returns <code>true</code> if <code>left</code> is later than or equal to <code>right</code>, <code>false</code> otherwise.
     *
     * @param {JulianDate} left The first instance.
     * @param {JulianDate} right The second instance.
     * @returns {Boolean} <code>true</code> if <code>left</code> is later than or equal to <code>right</code>, <code>false</code> otherwise.
     */
    JulianDate.greaterThanOrEquals = function(left, right) {
        return JulianDate.compare(left, right) >= 0;
    };

    /**
     * Duplicates this instance.
     *
     * @param {JulianDate} [result] An existing instance to use for the result.
     * @returns {JulianDate} The modified result parameter or a new instance if none was provided.
     */
    JulianDate.prototype.clone = function(result) {
        return JulianDate.clone(this, result);
    };

    /**
     * Compares this and the provided instance and returns <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {JulianDate} [right] The second instance.
     * @returns {Boolean} <code>true</code> if the dates are equal; otherwise, <code>false</code>.
     */
    JulianDate.prototype.equals = function(right) {
        return JulianDate.equals(this, right);
    };

    /**
     * Compares this and the provided instance and returns <code>true</code> if they are within <code>epsilon</code> seconds of
     * each other.  That is, in order for the dates to be considered equal (and for
     * this function to return <code>true</code>), the absolute value of the difference between them, in
     * seconds, must be less than <code>epsilon</code>.
     *
     * @param {JulianDate} [right] The second instance.
     * @param {Number} epsilon The maximum number of seconds that should separate the two instances.
     * @returns {Boolean} <code>true</code> if the two dates are within <code>epsilon</code> seconds of each other; otherwise <code>false</code>.
     */
    JulianDate.prototype.equalsEpsilon = function(right, epsilon) {
        return JulianDate.equalsEpsilon(this, right, epsilon);
    };

    /**
     * Creates a string representing this date in ISO8601 format.
     *
     * @returns {String} A string representing this date in ISO8601 format.
     */
    JulianDate.prototype.toString = function() {
        return JulianDate.toIso8601(this);
    };

    /**
     * Gets or sets the list of leap seconds used throughout Cesium.
     * @memberof JulianDate
     * @type {LeapSecond[]}
     */
    JulianDate.leapSeconds = [
                               new LeapSecond(new JulianDate(2441317, 43210.0, TimeStandard.TAI), 10), // January 1, 1972 00:00:00 UTC
                               new LeapSecond(new JulianDate(2441499, 43211.0, TimeStandard.TAI), 11), // July 1, 1972 00:00:00 UTC
                               new LeapSecond(new JulianDate(2441683, 43212.0, TimeStandard.TAI), 12), // January 1, 1973 00:00:00 UTC
                               new LeapSecond(new JulianDate(2442048, 43213.0, TimeStandard.TAI), 13), // January 1, 1974 00:00:00 UTC
                               new LeapSecond(new JulianDate(2442413, 43214.0, TimeStandard.TAI), 14), // January 1, 1975 00:00:00 UTC
                               new LeapSecond(new JulianDate(2442778, 43215.0, TimeStandard.TAI), 15), // January 1, 1976 00:00:00 UTC
                               new LeapSecond(new JulianDate(2443144, 43216.0, TimeStandard.TAI), 16), // January 1, 1977 00:00:00 UTC
                               new LeapSecond(new JulianDate(2443509, 43217.0, TimeStandard.TAI), 17), // January 1, 1978 00:00:00 UTC
                               new LeapSecond(new JulianDate(2443874, 43218.0, TimeStandard.TAI), 18), // January 1, 1979 00:00:00 UTC
                               new LeapSecond(new JulianDate(2444239, 43219.0, TimeStandard.TAI), 19), // January 1, 1980 00:00:00 UTC
                               new LeapSecond(new JulianDate(2444786, 43220.0, TimeStandard.TAI), 20), // July 1, 1981 00:00:00 UTC
                               new LeapSecond(new JulianDate(2445151, 43221.0, TimeStandard.TAI), 21), // July 1, 1982 00:00:00 UTC
                               new LeapSecond(new JulianDate(2445516, 43222.0, TimeStandard.TAI), 22), // July 1, 1983 00:00:00 UTC
                               new LeapSecond(new JulianDate(2446247, 43223.0, TimeStandard.TAI), 23), // July 1, 1985 00:00:00 UTC
                               new LeapSecond(new JulianDate(2447161, 43224.0, TimeStandard.TAI), 24), // January 1, 1988 00:00:00 UTC
                               new LeapSecond(new JulianDate(2447892, 43225.0, TimeStandard.TAI), 25), // January 1, 1990 00:00:00 UTC
                               new LeapSecond(new JulianDate(2448257, 43226.0, TimeStandard.TAI), 26), // January 1, 1991 00:00:00 UTC
                               new LeapSecond(new JulianDate(2448804, 43227.0, TimeStandard.TAI), 27), // July 1, 1992 00:00:00 UTC
                               new LeapSecond(new JulianDate(2449169, 43228.0, TimeStandard.TAI), 28), // July 1, 1993 00:00:00 UTC
                               new LeapSecond(new JulianDate(2449534, 43229.0, TimeStandard.TAI), 29), // July 1, 1994 00:00:00 UTC
                               new LeapSecond(new JulianDate(2450083, 43230.0, TimeStandard.TAI), 30), // January 1, 1996 00:00:00 UTC
                               new LeapSecond(new JulianDate(2450630, 43231.0, TimeStandard.TAI), 31), // July 1, 1997 00:00:00 UTC
                               new LeapSecond(new JulianDate(2451179, 43232.0, TimeStandard.TAI), 32), // January 1, 1999 00:00:00 UTC
                               new LeapSecond(new JulianDate(2453736, 43233.0, TimeStandard.TAI), 33), // January 1, 2006 00:00:00 UTC
                               new LeapSecond(new JulianDate(2454832, 43234.0, TimeStandard.TAI), 34), // January 1, 2009 00:00:00 UTC
                               new LeapSecond(new JulianDate(2456109, 43235.0, TimeStandard.TAI), 35), // July 1, 2012 00:00:00 UTC
                               new LeapSecond(new JulianDate(2457204, 43236.0, TimeStandard.TAI), 36), // July 1, 2015 00:00:00 UTC
                               new LeapSecond(new JulianDate(2457754, 43237.0, TimeStandard.TAI), 37)  // January 1, 2017 00:00:00 UTC
                             ];

    return JulianDate;
});

/*global define*/
define('Core/clone',[
        './defaultValue'
    ], function(
        defaultValue) {
    'use strict';

    /**
     * Clones an object, returning a new object containing the same properties.
     *
     * @exports clone
     *
     * @param {Object} object The object to clone.
     * @param {Boolean} [deep=false] If true, all properties will be deep cloned recursively.
     * @returns {Object} The cloned object.
     */
    function clone(object, deep) {
        if (object === null || typeof object !== 'object') {
            return object;
        }

        deep = defaultValue(deep, false);

        var result = new object.constructor();
        for ( var propertyName in object) {
            if (object.hasOwnProperty(propertyName)) {
                var value = object[propertyName];
                if (deep) {
                    value = clone(value, deep);
                }
                result[propertyName] = value;
            }
        }

        return result;
    }

    return clone;
});

/*global define*/
define('Core/parseResponseHeaders',[], function() {
    'use strict';

    /**
     * Parses the result of XMLHttpRequest's getAllResponseHeaders() method into
     * a dictionary.
     *
     * @exports parseResponseHeaders
     *
     * @param {String} headerString The header string returned by getAllResponseHeaders().  The format is
     *                 described here: http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
     * @returns {Object} A dictionary of key/value pairs, where each key is the name of a header and the corresponding value
     *                   is that header's value.
     * 
     * @private
     */
    function parseResponseHeaders(headerString) {
        var headers = {};

        if (!headerString) {
          return headers;
        }

        var headerPairs = headerString.split('\u000d\u000a');

        for (var i = 0; i < headerPairs.length; ++i) {
          var headerPair = headerPairs[i];
          // Can't use split() here because it does the wrong thing
          // if the header value has the string ": " in it.
          var index = headerPair.indexOf('\u003a\u0020');
          if (index > 0) {
            var key = headerPair.substring(0, index);
            var val = headerPair.substring(index + 2);
            headers[key] = val;
          }
        }

        return headers;
    }

    return parseResponseHeaders;
});

/*global define*/
define('Core/RequestErrorEvent',[
        './defined',
        './parseResponseHeaders'
    ], function(
        defined,
        parseResponseHeaders) {
    'use strict';

    /**
     * An event that is raised when a request encounters an error.
     *
     * @constructor
     * @alias RequestErrorEvent
     *
     * @param {Number} [statusCode] The HTTP error status code, such as 404.
     * @param {Object} [response] The response included along with the error.
     * @param {String|Object} [responseHeaders] The response headers, represented either as an object literal or as a
     *                        string in the format returned by XMLHttpRequest's getAllResponseHeaders() function.
     */
    function RequestErrorEvent(statusCode, response, responseHeaders) {
        /**
         * The HTTP error status code, such as 404.  If the error does not have a particular
         * HTTP code, this property will be undefined.
         *
         * @type {Number}
         */
        this.statusCode = statusCode;

        /**
         * The response included along with the error.  If the error does not include a response,
         * this property will be undefined.
         *
         * @type {Object}
         */
        this.response = response;

        /**
         * The headers included in the response, represented as an object literal of key/value pairs.
         * If the error does not include any headers, this property will be undefined.
         *
         * @type {Object}
         */
        this.responseHeaders = responseHeaders;

        if (typeof this.responseHeaders === 'string') {
            this.responseHeaders = parseResponseHeaders(this.responseHeaders);
        }
    }

    /**
     * Creates a string representing this RequestErrorEvent.
     * @memberof RequestErrorEvent
     *
     * @returns {String} A string representing the provided RequestErrorEvent.
     */
    RequestErrorEvent.prototype.toString = function() {
        var str = 'Request has failed.';
        if (defined(this.statusCode)) {
            str += ' Status Code: ' + this.statusCode;
        }
        return str;
    };

    return RequestErrorEvent;
});

/**
 * @license
 *
 * Grauw URI utilities
 *
 * See: http://hg.grauw.nl/grauw-lib/file/tip/src/uri.js
 *
 * @author Laurens Holst (http://www.grauw.nl/)
 *
 *   Copyright 2012 Laurens Holst
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file 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.
 *
 */
/*global define*/
define('ThirdParty/Uri',[],function() {

	/**
	 * Constructs a URI object.
	 * @constructor
	 * @class Implementation of URI parsing and base URI resolving algorithm in RFC 3986.
	 * @param {string|URI} uri A string or URI object to create the object from.
	 */
	function URI(uri) {
		if (uri instanceof URI) {  // copy constructor
			this.scheme = uri.scheme;
			this.authority = uri.authority;
			this.path = uri.path;
			this.query = uri.query;
			this.fragment = uri.fragment;
		} else if (uri) {  // uri is URI string or cast to string
			var c = parseRegex.exec(uri);
			this.scheme = c[1];
			this.authority = c[2];
			this.path = c[3];
			this.query = c[4];
			this.fragment = c[5];
		}
	}
	// Initial values on the prototype
	URI.prototype.scheme    = null;
	URI.prototype.authority = null;
	URI.prototype.path      = '';
	URI.prototype.query     = null;
	URI.prototype.fragment  = null;

	// Regular expression from RFC 3986 appendix B
	var parseRegex = new RegExp('^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\\?([^#]*))?(?:#(.*))?$');

	/**
	 * Returns the scheme part of the URI.
	 * In "http://example.com:80/a/b?x#y" this is "http".
	 */
	URI.prototype.getScheme = function() {
		return this.scheme;
	};

	/**
	 * Returns the authority part of the URI.
	 * In "http://example.com:80/a/b?x#y" this is "example.com:80".
	 */
	URI.prototype.getAuthority = function() {
		return this.authority;
	};

	/**
	 * Returns the path part of the URI.
	 * In "http://example.com:80/a/b?x#y" this is "/a/b".
	 * In "mailto:mike@example.com" this is "mike@example.com".
	 */
	URI.prototype.getPath = function() {
		return this.path;
	};

	/**
	 * Returns the query part of the URI.
	 * In "http://example.com:80/a/b?x#y" this is "x".
	 */
	URI.prototype.getQuery = function() {
		return this.query;
	};

	/**
	 * Returns the fragment part of the URI.
	 * In "http://example.com:80/a/b?x#y" this is "y".
	 */
	URI.prototype.getFragment = function() {
		return this.fragment;
	};

	/**
	 * Tests whether the URI is an absolute URI.
	 * See RFC 3986 section 4.3.
	 */
	URI.prototype.isAbsolute = function() {
		return !!this.scheme && !this.fragment;
	};

	///**
	//* Extensive validation of the URI against the ABNF in RFC 3986
	//*/
	//URI.prototype.validate

	/**
	 * Tests whether the URI is a same-document reference.
	 * See RFC 3986 section 4.4.
	 *
	 * To perform more thorough comparison, you can normalise the URI objects.
	 */
	URI.prototype.isSameDocumentAs = function(uri) {
		return uri.scheme == this.scheme &&
		    uri.authority == this.authority &&
		         uri.path == this.path &&
		        uri.query == this.query;
	};

	/**
	 * Simple String Comparison of two URIs.
	 * See RFC 3986 section 6.2.1.
	 *
	 * To perform more thorough comparison, you can normalise the URI objects.
	 */
	URI.prototype.equals = function(uri) {
		return this.isSameDocumentAs(uri) && uri.fragment == this.fragment;
	};

	/**
	 * Normalizes the URI using syntax-based normalization.
	 * This includes case normalization, percent-encoding normalization and path segment normalization.
	 * XXX: Percent-encoding normalization does not escape characters that need to be escaped.
	 *      (Although that would not be a valid URI in the first place. See validate().)
	 * See RFC 3986 section 6.2.2.
	 */
	URI.prototype.normalize = function() {
		this.removeDotSegments();
		if (this.scheme)
			this.scheme = this.scheme.toLowerCase();
		if (this.authority)
			this.authority = this.authority.replace(authorityRegex, replaceAuthority).
									replace(caseRegex, replaceCase);
		if (this.path)
			this.path = this.path.replace(caseRegex, replaceCase);
		if (this.query)
			this.query = this.query.replace(caseRegex, replaceCase);
		if (this.fragment)
			this.fragment = this.fragment.replace(caseRegex, replaceCase);
	};

	var caseRegex = /%[0-9a-z]{2}/gi;
	var percentRegex = /[a-zA-Z0-9\-\._~]/;
	var authorityRegex = /(.*@)?([^@:]*)(:.*)?/;

	function replaceCase(str) {
		var dec = unescape(str);
		return percentRegex.test(dec) ? dec : str.toUpperCase();
	}

	function replaceAuthority(str, p1, p2, p3) {
		return (p1 || '') + p2.toLowerCase() + (p3 || '');
	}

	/**
	 * Resolve a relative URI (this) against a base URI.
	 * The base URI must be an absolute URI.
	 * See RFC 3986 section 5.2
	 */
	URI.prototype.resolve = function(baseURI) {
		var uri = new URI();
		if (this.scheme) {
			uri.scheme = this.scheme;
			uri.authority = this.authority;
			uri.path = this.path;
			uri.query = this.query;
		} else {
			uri.scheme = baseURI.scheme;
			if (this.authority) {
				uri.authority = this.authority;
				uri.path = this.path;
				uri.query = this.query;
			} else {
				uri.authority = baseURI.authority;
				if (this.path == '') {
					uri.path = baseURI.path;
					uri.query = this.query || baseURI.query;
				} else {
					if (this.path.charAt(0) == '/') {
						uri.path = this.path;
						uri.removeDotSegments();
					} else {
						if (baseURI.authority && baseURI.path == '') {
							uri.path = '/' + this.path;
						} else {
							uri.path = baseURI.path.substring(0, baseURI.path.lastIndexOf('/') + 1) + this.path;
						}
						uri.removeDotSegments();
					}
					uri.query = this.query;
				}
			}
		}
		uri.fragment = this.fragment;
		return uri;
	};

	/**
	 * Remove dot segments from path.
	 * See RFC 3986 section 5.2.4
	 * @private
	 */
	URI.prototype.removeDotSegments = function() {
		var input = this.path.split('/'),
			output = [],
			segment,
			absPath = input[0] == '';
		if (absPath)
			input.shift();
		var sFirst = input[0] == '' ? input.shift() : null;
		while (input.length) {
			segment = input.shift();
			if (segment == '..') {
				output.pop();
			} else if (segment != '.') {
				output.push(segment);
			}
		}
		if (segment == '.' || segment == '..')
			output.push('');
		if (absPath)
			output.unshift('');
		this.path = output.join('/');
	};

	// We don't like this function because it builds up a cache that is never cleared.
//	/**
//	 * Resolves a relative URI against an absolute base URI.
//	 * Convenience method.
//	 * @param {String} uri the relative URI to resolve
//	 * @param {String} baseURI the base URI (must be absolute) to resolve against
//	 */
//	URI.resolve = function(sURI, sBaseURI) {
//		var uri = cache[sURI] || (cache[sURI] = new URI(sURI));
//		var baseURI = cache[sBaseURI] || (cache[sBaseURI] = new URI(sBaseURI));
//		return uri.resolve(baseURI).toString();
//	};

//	var cache = {};

	/**
	 * Serialises the URI to a string.
	 */
	URI.prototype.toString = function() {
		var result = '';
		if (this.scheme)
			result += this.scheme + ':';
		if (this.authority)
			result += '//' + this.authority;
		result += this.path;
		if (this.query)
			result += '?' + this.query;
		if (this.fragment)
			result += '#' + this.fragment;
		return result;
	};

return URI;
});

/*global define*/
define('Core/TrustedServers',[
        '../ThirdParty/Uri',
        './defined',
        './DeveloperError'
    ], function(
        Uri,
        defined,
        DeveloperError) {
    'use strict';
    
    /**
     * A singleton that contains all of the servers that are trusted. Credentials will be sent with
     * any requests to these servers.
     *
     * @exports TrustedServers
     *
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     */
    var TrustedServers = {};
    var _servers = {};

    /**
     * Adds a trusted server to the registry
     *
     * @param {String} host The host to be added.
     * @param {Number} port The port used to access the host.
     *
     * @example
     * // Add a trusted server
     * TrustedServers.add('my.server.com', 80);
     */
    TrustedServers.add = function(host, port) {
                if (!defined(host)) {
            throw new DeveloperError('host is required.');
        }
        if (!defined(port) || port <= 0) {
            throw new DeveloperError('port is required to be greater than 0.');
        }
        
        var authority = host.toLowerCase() + ':' + port;
        if (!defined(_servers[authority])) {
            _servers[authority] = true;
        }
    };

    /**
     * Removes a trusted server from the registry
     *
     * @param {String} host The host to be removed.
     * @param {Number} port The port used to access the host.
     *
     * @example
     * // Remove a trusted server
     * TrustedServers.remove('my.server.com', 80);
     */
    TrustedServers.remove = function(host, port) {
                if (!defined(host)) {
            throw new DeveloperError('host is required.');
        }
        if (!defined(port) || port <= 0) {
            throw new DeveloperError('port is required to be greater than 0.');
        }
        
        var authority = host.toLowerCase() + ':' + port;
        if (defined(_servers[authority])) {
            delete _servers[authority];
        }
    };

    function getAuthority(url) {
        var uri = new Uri(url);
        uri.normalize();

        // Removes username:password@ so we just have host[:port]
        var authority = uri.getAuthority();
        if (!defined(authority)) {
            return undefined; // Relative URL
        }

        if (authority.indexOf('@') !== -1) {
            var parts = authority.split('@');
            authority = parts[1];
        }

        // If the port is missing add one based on the scheme
        if (authority.indexOf(':') === -1) {
            var scheme = uri.getScheme();
            if (!defined(scheme)) {
                scheme = window.location.protocol;
                scheme = scheme.substring(0, scheme.length-1);
            }
            if (scheme === 'http') {
                authority += ':80';
            } else if (scheme === 'https') {
                authority += ':443';
            } else {
                return undefined;
            }
        }

        return authority;
    }

    /**
     * Tests whether a server is trusted or not. The server must have been added with the port if it is included in the url.
     *
     * @param {String} url The url to be tested against the trusted list
     *
     * @returns {boolean} Returns true if url is trusted, false otherwise.
     *
     * @example
     * // Add server
     * TrustedServers.add('my.server.com', 81);
     *
     * // Check if server is trusted
     * if (TrustedServers.contains('https://my.server.com:81/path/to/file.png')) {
     *     // my.server.com:81 is trusted
     * }
     * if (TrustedServers.contains('https://my.server.com/path/to/file.png')) {
     *     // my.server.com isn't trusted
     * }
     */
    TrustedServers.contains = function(url) {
                if (!defined(url)) {
            throw new DeveloperError('url is required.');
        }
                var authority = getAuthority(url);
        if (defined(authority) && defined(_servers[authority])) {
            return true;
        }

        return false;
    };

    /**
     * Clears the registry
     *
     * @example
     * // Remove a trusted server
     * TrustedServers.clear();
     */
    TrustedServers.clear = function() {
        _servers = {};
    };
    
    return TrustedServers;
});

/*global define*/
define('Core/loadWithXhr',[
        '../ThirdParty/when',
        './defaultValue',
        './defined',
        './DeveloperError',
        './RequestErrorEvent',
        './RuntimeError',
        './TrustedServers'
    ], function(
        when,
        defaultValue,
        defined,
        DeveloperError,
        RequestErrorEvent,
        RuntimeError,
        TrustedServers) {
    'use strict';

    /**
     * Asynchronously loads the given URL.  Returns a promise that will resolve to
     * the result once loaded, or reject if the URL failed to load.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     *
     * @exports loadWithXhr
     *
     * @param {Object} options Object with the following properties:
     * @param {String|Promise.<String>} options.url The URL of the data, or a promise for the URL.
     * @param {String} [options.responseType] The type of response.  This controls the type of item returned.
     * @param {String} [options.method='GET'] The HTTP method to use.
     * @param {String} [options.data] The data to send with the request, if any.
     * @param {Object} [options.headers] HTTP headers to send with the request, if any.
     * @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server.
     * @returns {Promise.<Object>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // Load a single URL asynchronously. In real code, you should use loadBlob instead.
     * Cesium.loadWithXhr({
     *     url : 'some/url',
     *     responseType : 'blob'
     * }).then(function(blob) {
     *     // use the data
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     *
     * @see loadArrayBuffer
     * @see loadBlob
     * @see loadJson
     * @see loadText
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadWithXhr(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        var responseType = options.responseType;
        var method = defaultValue(options.method, 'GET');
        var data = options.data;
        var headers = options.headers;
        var overrideMimeType = options.overrideMimeType;

        return when(options.url, function(url) {
            var deferred = when.defer();

            loadWithXhr.load(url, responseType, method, data, headers, deferred, overrideMimeType);

            return deferred.promise;
        });
    }

    var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;

    function decodeDataUriText(isBase64, data) {
        var result = decodeURIComponent(data);
        if (isBase64) {
            return atob(result);
        }
        return result;
    }

    function decodeDataUriArrayBuffer(isBase64, data) {
        var byteString = decodeDataUriText(isBase64, data);
        var buffer = new ArrayBuffer(byteString.length);
        var view = new Uint8Array(buffer);
        for (var i = 0; i < byteString.length; i++) {
            view[i] = byteString.charCodeAt(i);
        }
        return buffer;
    }

    function decodeDataUri(dataUriRegexResult, responseType) {
        responseType = defaultValue(responseType, '');
        var mimeType = dataUriRegexResult[1];
        var isBase64 = !!dataUriRegexResult[2];
        var data = dataUriRegexResult[3];

        switch (responseType) {
            case '':
            case 'text':
                return decodeDataUriText(isBase64, data);
            case 'arraybuffer':
                return decodeDataUriArrayBuffer(isBase64, data);
            case 'blob':
                var buffer = decodeDataUriArrayBuffer(isBase64, data);
                return new Blob([buffer], {
                    type : mimeType
                });
            case 'document':
                var parser = new DOMParser();
                return parser.parseFromString(decodeDataUriText(isBase64, data), mimeType);
            case 'json':
                return JSON.parse(decodeDataUriText(isBase64, data));
            default:
                                throw new DeveloperError('Unhandled responseType: ' + responseType);
                        }
    }

    // This is broken out into a separate function so that it can be mocked for testing purposes.
    loadWithXhr.load = function(url, responseType, method, data, headers, deferred, overrideMimeType) {
        var dataUriRegexResult = dataUriRegex.exec(url);
        if (dataUriRegexResult !== null) {
            deferred.resolve(decodeDataUri(dataUriRegexResult, responseType));
            return;
        }

        var xhr = new XMLHttpRequest();

        if (TrustedServers.contains(url)) {
            xhr.withCredentials = true;
        }

        if (defined(overrideMimeType) && defined(xhr.overrideMimeType)) {
            xhr.overrideMimeType(overrideMimeType);
        }

        xhr.open(method, url, true);

        if (defined(headers)) {
            for (var key in headers) {
                if (headers.hasOwnProperty(key)) {
                    xhr.setRequestHeader(key, headers[key]);
                }
            }
        }

        if (defined(responseType)) {
            xhr.responseType = responseType;
        }

        xhr.onload = function() {
            if (xhr.status < 200 || xhr.status >= 300) {
                deferred.reject(new RequestErrorEvent(xhr.status, xhr.response, xhr.getAllResponseHeaders()));
                return;
            }

            var response = xhr.response;
            var browserResponseType = xhr.responseType;

            //All modern browsers will go into either the first if block or last else block.
            //Other code paths support older browsers that either do not support the supplied responseType
            //or do not support the xhr.response property.
            if (defined(response) && (!defined(responseType) || (browserResponseType === responseType))) {
                deferred.resolve(response);
            } else if ((responseType === 'json') && typeof response === 'string') {
                try {
                    deferred.resolve(JSON.parse(response));
                } catch (e) {
                    deferred.reject(e);
                }
            } else if ((browserResponseType === '' || browserResponseType === 'document') && defined(xhr.responseXML) && xhr.responseXML.hasChildNodes()) {
                deferred.resolve(xhr.responseXML);
            } else if ((browserResponseType === '' || browserResponseType === 'text') && defined(xhr.responseText)) {
                deferred.resolve(xhr.responseText);
            } else {
                deferred.reject(new RuntimeError('Invalid XMLHttpRequest response type.'));
            }
        };

        xhr.onerror = function(e) {
            deferred.reject(new RequestErrorEvent());
        };

        xhr.send(data);
    };

    loadWithXhr.defaultLoad = loadWithXhr.load;

    return loadWithXhr;
});

/*global define*/
define('Core/loadText',[
        './loadWithXhr'
    ], function(
        loadWithXhr) {
    'use strict';

    /**
     * Asynchronously loads the given URL as text.  Returns a promise that will resolve to
     * a String once loaded, or reject if the URL failed to load.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     *
     * @exports loadText
     *
     * @param {String|Promise.<String>} url The URL to request, or a promise for the URL.
     * @param {Object} [headers] HTTP headers to send with the request.
     * @returns {Promise.<String>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load text from a URL, setting a custom header
     * Cesium.loadText('http://someUrl.com/someJson.txt', {
     *   'X-Custom-Header' : 'some value'
     * }).then(function(text) {
     *     // Do something with the text
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadText(url, headers) {
        return loadWithXhr({
            url : url,
            headers : headers
        });
    }

    return loadText;
});

/*global define*/
define('Core/loadJson',[
        './clone',
        './defined',
        './DeveloperError',
        './loadText'
    ], function(
        clone,
        defined,
        DeveloperError,
        loadText) {
    'use strict';

    var defaultHeaders = {
        Accept : 'application/json,*/*;q=0.01'
    };

    // note: &#42;&#47;&#42; below is */* but that ends the comment block early
    /**
     * Asynchronously loads the given URL as JSON.  Returns a promise that will resolve to
     * a JSON object once loaded, or reject if the URL failed to load.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled. This function
     * adds 'Accept: application/json,&#42;&#47;&#42;;q=0.01' to the request headers, if not
     * already specified.
     *
     * @exports loadJson
     *
     * @param {String|Promise.<String>} url The URL to request, or a promise for the URL.
     * @param {Object} [headers] HTTP headers to send with the request.
     * 'Accept: application/json,&#42;&#47;&#42;;q=0.01' is added to the request headers automatically
     * if not specified.
     * @returns {Promise.<Object>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * Cesium.loadJson('http://someUrl.com/someJson.txt').then(function(jsonData) {
     *     // Do something with the JSON object
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see loadText
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadJson(url, headers) {
                if (!defined(url)) {
            throw new DeveloperError('url is required.');
        }
        
        if (!defined(headers)) {
            headers = defaultHeaders;
        } else if (!defined(headers.Accept)) {
            // clone before adding the Accept header
            headers = clone(headers);
            headers.Accept = defaultHeaders.Accept;
        }

        return loadText(url, headers).then(function(value) {
            return JSON.parse(value);
        });
    }

    return loadJson;
});

/*global define*/
define('Core/EarthOrientationParameters',[
        '../ThirdParty/when',
        './binarySearch',
        './defaultValue',
        './defined',
        './EarthOrientationParametersSample',
        './freezeObject',
        './JulianDate',
        './LeapSecond',
        './loadJson',
        './RuntimeError',
        './TimeConstants',
        './TimeStandard'
    ], function(
        when,
        binarySearch,
        defaultValue,
        defined,
        EarthOrientationParametersSample,
        freezeObject,
        JulianDate,
        LeapSecond,
        loadJson,
        RuntimeError,
        TimeConstants,
        TimeStandard) {
    'use strict';

    /**
     * Specifies Earth polar motion coordinates and the difference between UT1 and UTC.
     * These Earth Orientation Parameters (EOP) are primarily used in the transformation from
     * the International Celestial Reference Frame (ICRF) to the International Terrestrial
     * Reference Frame (ITRF).
     *
     * @alias EarthOrientationParameters
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.url] The URL from which to obtain EOP data.  If neither this
     *                 parameter nor options.data is specified, all EOP values are assumed
     *                 to be 0.0.  If options.data is specified, this parameter is
     *                 ignored.
     * @param {Object} [options.data] The actual EOP data.  If neither this
     *                 parameter nor options.data is specified, all EOP values are assumed
     *                 to be 0.0.
     * @param {Boolean} [options.addNewLeapSeconds=true] True if leap seconds that
     *                  are specified in the EOP data but not in {@link JulianDate.leapSeconds}
     *                  should be added to {@link JulianDate.leapSeconds}.  False if
     *                  new leap seconds should be handled correctly in the context
     *                  of the EOP data but otherwise ignored.
     *
     * @example
     * // An example EOP data file, EOP.json:
     * {
     *   "columnNames" : ["dateIso8601","modifiedJulianDateUtc","xPoleWanderRadians","yPoleWanderRadians","ut1MinusUtcSeconds","lengthOfDayCorrectionSeconds","xCelestialPoleOffsetRadians","yCelestialPoleOffsetRadians","taiMinusUtcSeconds"],
     *   "samples" : [
     *      "2011-07-01T00:00:00Z",55743.0,2.117957047295119e-7,2.111518721609984e-6,-0.2908948,-2.956e-4,3.393695767766752e-11,3.3452143996557983e-10,34.0,
     *      "2011-07-02T00:00:00Z",55744.0,2.193297093339541e-7,2.115460256837405e-6,-0.29065,-1.824e-4,-8.241832578862112e-11,5.623838700870617e-10,34.0,
     *      "2011-07-03T00:00:00Z",55745.0,2.262286080161428e-7,2.1191157519929706e-6,-0.2905572,1.9e-6,-3.490658503988659e-10,6.981317007977318e-10,34.0
     *   ]
     * }
     *
     * @example
     * // Loading the EOP data
     * var eop = new Cesium.EarthOrientationParameters({ url : 'Data/EOP.json' });
     * Cesium.Transforms.earthOrientationParameters = eop;
     *
     * @private
     */
    function EarthOrientationParameters(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._dates = undefined;
        this._samples = undefined;

        this._dateColumn = -1;
        this._xPoleWanderRadiansColumn = -1;
        this._yPoleWanderRadiansColumn = -1;
        this._ut1MinusUtcSecondsColumn = -1;
        this._xCelestialPoleOffsetRadiansColumn = -1;
        this._yCelestialPoleOffsetRadiansColumn = -1;
        this._taiMinusUtcSecondsColumn = -1;

        this._columnCount = 0;
        this._lastIndex = -1;

        this._downloadPromise = undefined;
        this._dataError = undefined;

        this._addNewLeapSeconds = defaultValue(options.addNewLeapSeconds, true);

        if (defined(options.data)) {
            // Use supplied EOP data.
            onDataReady(this, options.data);
        } else if (defined(options.url)) {
            // Download EOP data.
            var that = this;
            this._downloadPromise = when(loadJson(options.url), function(eopData) {
                onDataReady(that, eopData);
            }, function() {
                that._dataError = 'An error occurred while retrieving the EOP data from the URL ' + options.url + '.';
            });
        } else {
            // Use all zeros for EOP data.
            onDataReady(this, {
                'columnNames' : ['dateIso8601', 'modifiedJulianDateUtc', 'xPoleWanderRadians', 'yPoleWanderRadians', 'ut1MinusUtcSeconds', 'lengthOfDayCorrectionSeconds', 'xCelestialPoleOffsetRadians', 'yCelestialPoleOffsetRadians', 'taiMinusUtcSeconds'],
                'samples' : []
            });
        }
    }

    /**
     * A default {@link EarthOrientationParameters} instance that returns zero for all EOP values.
     */
    EarthOrientationParameters.NONE = freezeObject({
            getPromiseToLoad : function() {
                return when();
            },
            compute : function(date, result) {
                if (!defined(result)) {
                    result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
                } else {
                    result.xPoleWander = 0.0;
                    result.yPoleWander = 0.0;
                    result.xPoleOffset = 0.0;
                    result.yPoleOffset = 0.0;
                    result.ut1MinusUtc = 0.0;
                }
                return result;
            }
    });

    /**
     * Gets a promise that, when resolved, indicates that the EOP data has been loaded and is
     * ready to use.
     *
     * @returns {Promise.<undefined>} The promise.
     *
     * @see when
     */
    EarthOrientationParameters.prototype.getPromiseToLoad = function() {
        return when(this._downloadPromise);
    };

    /**
     * Computes the Earth Orientation Parameters (EOP) for a given date by interpolating.
     * If the EOP data has not yet been download, this method returns undefined.
     *
     * @param {JulianDate} date The date for each to evaluate the EOP.
     * @param {EarthOrientationParametersSample} [result] The instance to which to copy the result.
     *        If this parameter is undefined, a new instance is created and returned.
     * @returns {EarthOrientationParametersSample} The EOP evaluated at the given date, or
     *          undefined if the data necessary to evaluate EOP at the date has not yet been
     *          downloaded.
     *
     * @exception {RuntimeError} The loaded EOP data has an error and cannot be used.
     *
     * @see EarthOrientationParameters#getPromiseToLoad
     */
    EarthOrientationParameters.prototype.compute = function(date, result) {
        // We cannot compute until the samples are available.
        if (!defined(this._samples)) {
            if (defined(this._dataError)) {
                throw new RuntimeError(this._dataError);
            }

            return undefined;
        }

        if (!defined(result)) {
            result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
        }

        if (this._samples.length === 0) {
            result.xPoleWander = 0.0;
            result.yPoleWander = 0.0;
            result.xPoleOffset = 0.0;
            result.yPoleOffset = 0.0;
            result.ut1MinusUtc = 0.0;
            return result;
        }

        var dates = this._dates;
        var lastIndex = this._lastIndex;

        var before = 0;
        var after = 0;
        if (defined(lastIndex)) {
            var previousIndexDate = dates[lastIndex];
            var nextIndexDate = dates[lastIndex + 1];
            var isAfterPrevious = JulianDate.lessThanOrEquals(previousIndexDate, date);
            var isAfterLastSample = !defined(nextIndexDate);
            var isBeforeNext = isAfterLastSample || JulianDate.greaterThanOrEquals(nextIndexDate, date);

            if (isAfterPrevious && isBeforeNext) {
                before = lastIndex;

                if (!isAfterLastSample && nextIndexDate.equals(date)) {
                    ++before;
                }
                after = before + 1;

                interpolate(this, dates, this._samples, date, before, after, result);
                return result;
            }
        }

        var index = binarySearch(dates, date, JulianDate.compare, this._dateColumn);
        if (index >= 0) {
            // If the next entry is the same date, use the later entry.  This way, if two entries
            // describe the same moment, one before a leap second and the other after, then we will use
            // the post-leap second data.
            if (index < dates.length - 1 && dates[index + 1].equals(date)) {
                ++index;
            }
            before = index;
            after = index;
        } else {
            after = ~index;
            before = after - 1;

            // Use the first entry if the date requested is before the beginning of the data.
            if (before < 0) {
                before = 0;
            }
        }

        this._lastIndex = before;

        interpolate(this, dates, this._samples, date, before, after, result);
        return result;
    };

    function compareLeapSecondDates(leapSecond, dateToFind) {
        return JulianDate.compare(leapSecond.julianDate, dateToFind);
    }

    function onDataReady(eop, eopData) {
        if (!defined(eopData.columnNames)) {
            eop._dataError = 'Error in loaded EOP data: The columnNames property is required.';
            return;
        }

        if (!defined(eopData.samples)) {
            eop._dataError = 'Error in loaded EOP data: The samples property is required.';
            return;
        }

        var dateColumn = eopData.columnNames.indexOf('modifiedJulianDateUtc');
        var xPoleWanderRadiansColumn = eopData.columnNames.indexOf('xPoleWanderRadians');
        var yPoleWanderRadiansColumn = eopData.columnNames.indexOf('yPoleWanderRadians');
        var ut1MinusUtcSecondsColumn = eopData.columnNames.indexOf('ut1MinusUtcSeconds');
        var xCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf('xCelestialPoleOffsetRadians');
        var yCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf('yCelestialPoleOffsetRadians');
        var taiMinusUtcSecondsColumn = eopData.columnNames.indexOf('taiMinusUtcSeconds');

        if (dateColumn < 0 || xPoleWanderRadiansColumn < 0 || yPoleWanderRadiansColumn < 0 || ut1MinusUtcSecondsColumn < 0 || xCelestialPoleOffsetRadiansColumn < 0 || yCelestialPoleOffsetRadiansColumn < 0 || taiMinusUtcSecondsColumn < 0) {
            eop._dataError = 'Error in loaded EOP data: The columnNames property must include modifiedJulianDateUtc, xPoleWanderRadians, yPoleWanderRadians, ut1MinusUtcSeconds, xCelestialPoleOffsetRadians, yCelestialPoleOffsetRadians, and taiMinusUtcSeconds columns';
            return;
        }

        var samples = eop._samples = eopData.samples;
        var dates = eop._dates = [];

        eop._dateColumn = dateColumn;
        eop._xPoleWanderRadiansColumn = xPoleWanderRadiansColumn;
        eop._yPoleWanderRadiansColumn = yPoleWanderRadiansColumn;
        eop._ut1MinusUtcSecondsColumn = ut1MinusUtcSecondsColumn;
        eop._xCelestialPoleOffsetRadiansColumn = xCelestialPoleOffsetRadiansColumn;
        eop._yCelestialPoleOffsetRadiansColumn = yCelestialPoleOffsetRadiansColumn;
        eop._taiMinusUtcSecondsColumn = taiMinusUtcSecondsColumn;

        eop._columnCount = eopData.columnNames.length;
        eop._lastIndex = undefined;

        var lastTaiMinusUtc;

        var addNewLeapSeconds = eop._addNewLeapSeconds;

        // Convert the ISO8601 dates to JulianDates.
        for (var i = 0, len = samples.length; i < len; i += eop._columnCount) {
            var mjd = samples[i + dateColumn];
            var taiMinusUtc = samples[i + taiMinusUtcSecondsColumn];
            var day = mjd + TimeConstants.MODIFIED_JULIAN_DATE_DIFFERENCE;
            var date = new JulianDate(day, taiMinusUtc, TimeStandard.TAI);
            dates.push(date);

            if (addNewLeapSeconds) {
                if (taiMinusUtc !== lastTaiMinusUtc && defined(lastTaiMinusUtc)) {
                    // We crossed a leap second boundary, so add the leap second
                    // if it does not already exist.
                    var leapSeconds = JulianDate.leapSeconds;
                    var leapSecondIndex = binarySearch(leapSeconds, date, compareLeapSecondDates);
                    if (leapSecondIndex < 0) {
                        var leapSecond = new LeapSecond(date, taiMinusUtc);
                        leapSeconds.splice(~leapSecondIndex, 0, leapSecond);
                    }
                }
                lastTaiMinusUtc = taiMinusUtc;
            }
        }
    }

    function fillResultFromIndex(eop, samples, index, columnCount, result) {
        var start = index * columnCount;
        result.xPoleWander = samples[start + eop._xPoleWanderRadiansColumn];
        result.yPoleWander = samples[start + eop._yPoleWanderRadiansColumn];
        result.xPoleOffset = samples[start + eop._xCelestialPoleOffsetRadiansColumn];
        result.yPoleOffset = samples[start + eop._yCelestialPoleOffsetRadiansColumn];
        result.ut1MinusUtc = samples[start + eop._ut1MinusUtcSecondsColumn];
    }

    function linearInterp(dx, y1, y2) {
        return y1 + dx * (y2 - y1);
    }

    function interpolate(eop, dates, samples, date, before, after, result) {
        var columnCount = eop._columnCount;

        // First check the bounds on the EOP data
        // If we are after the bounds of the data, return zeros.
        // The 'before' index should never be less than zero.
        if (after > dates.length - 1) {
            result.xPoleWander = 0;
            result.yPoleWander = 0;
            result.xPoleOffset = 0;
            result.yPoleOffset = 0;
            result.ut1MinusUtc = 0;
            return result;
        }

        var beforeDate = dates[before];
        var afterDate = dates[after];
        if (beforeDate.equals(afterDate) || date.equals(beforeDate)) {
            fillResultFromIndex(eop, samples, before, columnCount, result);
            return result;
        } else if (date.equals(afterDate)) {
            fillResultFromIndex(eop, samples, after, columnCount, result);
            return result;
        }

        var factor = JulianDate.secondsDifference(date, beforeDate) / JulianDate.secondsDifference(afterDate, beforeDate);

        var startBefore = before * columnCount;
        var startAfter = after * columnCount;

        // Handle UT1 leap second edge case
        var beforeUt1MinusUtc = samples[startBefore + eop._ut1MinusUtcSecondsColumn];
        var afterUt1MinusUtc = samples[startAfter + eop._ut1MinusUtcSecondsColumn];

        var offsetDifference = afterUt1MinusUtc - beforeUt1MinusUtc;
        if (offsetDifference > 0.5 || offsetDifference < -0.5) {
            // The absolute difference between the values is more than 0.5, so we may have
            // crossed a leap second.  Check if this is the case and, if so, adjust the
            // afterValue to account for the leap second.  This way, our interpolation will
            // produce reasonable results.
            var beforeTaiMinusUtc = samples[startBefore + eop._taiMinusUtcSecondsColumn];
            var afterTaiMinusUtc = samples[startAfter + eop._taiMinusUtcSecondsColumn];
            if (beforeTaiMinusUtc !== afterTaiMinusUtc) {
                if (afterDate.equals(date)) {
                    // If we are at the end of the leap second interval, take the second value
                    // Otherwise, the interpolation below will yield the wrong side of the
                    // discontinuity
                    // At the end of the leap second, we need to start accounting for the jump
                    beforeUt1MinusUtc = afterUt1MinusUtc;
                } else {
                    // Otherwise, remove the leap second so that the interpolation is correct
                    afterUt1MinusUtc -= afterTaiMinusUtc - beforeTaiMinusUtc;
                }
            }
        }

        result.xPoleWander = linearInterp(factor, samples[startBefore + eop._xPoleWanderRadiansColumn], samples[startAfter + eop._xPoleWanderRadiansColumn]);
        result.yPoleWander = linearInterp(factor, samples[startBefore + eop._yPoleWanderRadiansColumn], samples[startAfter + eop._yPoleWanderRadiansColumn]);
        result.xPoleOffset = linearInterp(factor, samples[startBefore + eop._xCelestialPoleOffsetRadiansColumn], samples[startAfter + eop._xCelestialPoleOffsetRadiansColumn]);
        result.yPoleOffset = linearInterp(factor, samples[startBefore + eop._yCelestialPoleOffsetRadiansColumn], samples[startAfter + eop._yCelestialPoleOffsetRadiansColumn]);
        result.ut1MinusUtc = linearInterp(factor, beforeUt1MinusUtc, afterUt1MinusUtc);
        return result;
    }

    return EarthOrientationParameters;
});

/*global define*/
define('Core/getAbsoluteUri',[
        '../ThirdParty/Uri',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        Uri,
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Given a relative Uri and a base Uri, returns the absolute Uri of the relative Uri.
     * @exports getAbsoluteUri
     *
     * @param {String} relative The relative Uri.
     * @param {String} [base] The base Uri.
     * @returns {String} The absolute Uri of the given relative Uri.
     *
     * @example
     * //absolute Uri will be "https://test.com/awesome.png";
     * var absoluteUri = Cesium.getAbsoluteUri('awesome.png', 'https://test.com');
     */
    function getAbsoluteUri(relative, base) {
                if (!defined(relative)) {
            throw new DeveloperError('relative uri is required.');
        }
                base = defaultValue(base, document.location.href);
        var baseUri = new Uri(base);
        var relativeUri = new Uri(relative);
        return relativeUri.resolve(baseUri).toString();
    }

    return getAbsoluteUri;
});

/*global define*/
define('Core/joinUrls',[
        '../ThirdParty/Uri',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        Uri,
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Function for joining URLs in a manner that is aware of query strings and fragments.
     * This is useful when the base URL has a query string that needs to be maintained
     * (e.g. a presigned base URL).
     * @param {String|Uri} first The base URL.
     * @param {String|Uri} second The URL path to join to the base URL.  If this URL is absolute, it is returned unmodified.
     * @param {Boolean} [appendSlash=true] The boolean determining whether there should be a forward slash between first and second.
     * @private
     */
    function joinUrls(first, second, appendSlash) {
                if (!defined(first)) {
            throw new DeveloperError('first is required');
        }
        if (!defined(second)) {
            throw new DeveloperError('second is required');
        }
        
        appendSlash = defaultValue(appendSlash, true);

        if (!(first instanceof Uri)) {
            first = new Uri(first);
        }

        if (!(second instanceof Uri)) {
            second = new Uri(second);
        }

        // Uri.isAbsolute returns false for a URL like '//foo.com'.  So if we have an authority but
        // not a scheme, add a scheme matching the page's scheme.
        if (defined(second.authority) && !defined(second.scheme)) {
            if (typeof document !== 'undefined' && defined(document.location) && defined(document.location.href)) {
                second.scheme = new Uri(document.location.href).scheme;
            } else {
                // Not in a browser?  Use the first URL's scheme instead.
                second.scheme = first.scheme;
            }
        }

        // If the second URL is absolute, use it for the scheme, authority, and path.
        var baseUri = first;
        if (second.isAbsolute()) {
            baseUri = second;
        }

        var url = '';
        if (defined(baseUri.scheme)) {
            url += baseUri.scheme + ':';
        }
        if (defined(baseUri.authority)) {
            url += '//' + baseUri.authority;

            if (baseUri.path !== '' && baseUri.path !== '/') {
                url = url.replace(/\/?$/, '/');
                baseUri.path = baseUri.path.replace(/^\/?/g, '');
            }
        }

        // Combine the paths (only if second is relative).
        if (baseUri === first) {
            if (appendSlash) {
                url += first.path.replace(/\/?$/, '/') + second.path.replace(/^\/?/g, '');
            } else {
                url += first.path + second.path;
            }
        } else {
            url += second.path;
        }

        // Combine the queries and fragments.
        var hasFirstQuery = defined(first.query);
        var hasSecondQuery = defined(second.query);
        if (hasFirstQuery && hasSecondQuery) {
            url += '?' + first.query + '&' + second.query;
        } else if (hasFirstQuery && !hasSecondQuery) {
            url += '?' + first.query;
        } else if (!hasFirstQuery && hasSecondQuery) {
            url += '?' + second.query;
        }

        var hasSecondFragment = defined(second.fragment);
        if (defined(first.fragment) && !hasSecondFragment) {
            url += '#' + first.fragment;
        } else if (hasSecondFragment) {
            url += '#' + second.fragment;
        }

        return url;
    }

    return joinUrls;
});

/*global define*/
define('Core/buildModuleUrl',[
        '../ThirdParty/Uri',
        './defined',
        './DeveloperError',
        './getAbsoluteUri',
        './joinUrls',
        'require'
    ], function(
        Uri,
        defined,
        DeveloperError,
        getAbsoluteUri,
        joinUrls,
        require) {
    'use strict';
    /*global CESIUM_BASE_URL*/

    var cesiumScriptRegex = /((?:.*\/)|^)cesium[\w-]*\.js(?:\W|$)/i;
    function getBaseUrlFromCesiumScript() {
        var scripts = document.getElementsByTagName('script');
        for ( var i = 0, len = scripts.length; i < len; ++i) {
            var src = scripts[i].getAttribute('src');
            var result = cesiumScriptRegex.exec(src);
            if (result !== null) {
                return result[1];
            }
        }
        return undefined;
    }

    var baseUrl;
    function getCesiumBaseUrl() {
        if (defined(baseUrl)) {
            return baseUrl;
        }

        var baseUrlString;
        if (typeof CESIUM_BASE_URL !== 'undefined') {
            baseUrlString = CESIUM_BASE_URL;
        } else {
            baseUrlString = getBaseUrlFromCesiumScript();
        }

                if (!defined(baseUrlString)) {
            throw new DeveloperError('Unable to determine Cesium base URL automatically, try defining a global variable called CESIUM_BASE_URL.');
        }
        
        baseUrl = new Uri(getAbsoluteUri(baseUrlString));

        return baseUrl;
    }

    function buildModuleUrlFromRequireToUrl(moduleID) {
        //moduleID will be non-relative, so require it relative to this module, in Core.
        return require.toUrl('../' + moduleID);
    }

    function buildModuleUrlFromBaseUrl(moduleID) {
        return joinUrls(getCesiumBaseUrl(), moduleID);
    }

    var implementation;
    var a;

    /**
     * Given a non-relative moduleID, returns an absolute URL to the file represented by that module ID,
     * using, in order of preference, require.toUrl, the value of a global CESIUM_BASE_URL, or
     * the base URL of the Cesium.js script.
     *
     * @private
     */
    function buildModuleUrl(moduleID) {
        if (!defined(implementation)) {
            //select implementation
            if (defined(require.toUrl)) {
                implementation = buildModuleUrlFromRequireToUrl;
            } else {
                implementation = buildModuleUrlFromBaseUrl;
            }
        }

        if (!defined(a)) {
            a = document.createElement('a');
        }

        var url = implementation(moduleID);

        a.href = url;
        a.href = a.href; // IE only absolutizes href on get, not set

        return a.href;
    }

    // exposed for testing
    buildModuleUrl._cesiumScriptRegex = cesiumScriptRegex;

    /**
     * Sets the base URL for resolving modules.
     * @param {String} value The new base URL.
     */
    buildModuleUrl.setBaseUrl = function(value) {
        baseUrl = new Uri(value).resolve(new Uri(document.location.href));
    };

    return buildModuleUrl;
});

/*global define*/
define('Core/Iau2006XysSample',[],function() {
    'use strict';

    /**
     * An IAU 2006 XYS value sampled at a particular time.
     *
     * @alias Iau2006XysSample
     * @constructor
     *
     * @param {Number} x The X value.
     * @param {Number} y The Y value.
     * @param {Number} s The S value.
     *
     * @private
     */
    function Iau2006XysSample(x, y, s) {
        /**
         * The X value.
         * @type {Number}
         */
        this.x = x;

        /**
         * The Y value.
         * @type {Number}
         */
        this.y = y;

        /**
         * The S value.
         * @type {Number}
         */
        this.s = s;
    }

    return Iau2006XysSample;
});

/*global define*/
define('Core/Iau2006XysData',[
        '../ThirdParty/when',
        './buildModuleUrl',
        './defaultValue',
        './defined',
        './Iau2006XysSample',
        './JulianDate',
        './loadJson',
        './TimeStandard'
    ], function(
        when,
        buildModuleUrl,
        defaultValue,
        defined,
        Iau2006XysSample,
        JulianDate,
        loadJson,
        TimeStandard) {
    'use strict';

    /**
     * A set of IAU2006 XYS data that is used to evaluate the transformation between the International
     * Celestial Reference Frame (ICRF) and the International Terrestrial Reference Frame (ITRF).
     *
     * @alias Iau2006XysData
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.xysFileUrlTemplate='Assets/IAU2006_XYS/IAU2006_XYS_{0}.json'] A template URL for obtaining the XYS data.  In the template,
     *                 `{0}` will be replaced with the file index.
     * @param {Number} [options.interpolationOrder=9] The order of interpolation to perform on the XYS data.
     * @param {Number} [options.sampleZeroJulianEphemerisDate=2442396.5] The Julian ephemeris date (JED) of the
     *                 first XYS sample.
     * @param {Number} [options.stepSizeDays=1.0] The step size, in days, between successive XYS samples.
     * @param {Number} [options.samplesPerXysFile=1000] The number of samples in each XYS file.
     * @param {Number} [options.totalSamples=27426] The total number of samples in all XYS files.
     *
     * @private
     */
    function Iau2006XysData(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._xysFileUrlTemplate = options.xysFileUrlTemplate;
        this._interpolationOrder = defaultValue(options.interpolationOrder, 9);
        this._sampleZeroJulianEphemerisDate = defaultValue(options.sampleZeroJulianEphemerisDate, 2442396.5);
        this._sampleZeroDateTT = new JulianDate(this._sampleZeroJulianEphemerisDate, 0.0, TimeStandard.TAI);
        this._stepSizeDays = defaultValue(options.stepSizeDays, 1.0);
        this._samplesPerXysFile = defaultValue(options.samplesPerXysFile, 1000);
        this._totalSamples = defaultValue(options.totalSamples, 27426);
        this._samples = new Array(this._totalSamples * 3);
        this._chunkDownloadsInProgress = [];

        var order = this._interpolationOrder;

        // Compute denominators and X values for interpolation.
        var denom = this._denominators = new Array(order + 1);
        var xTable = this._xTable = new Array(order + 1);

        var stepN = Math.pow(this._stepSizeDays, order);

        for ( var i = 0; i <= order; ++i) {
            denom[i] = stepN;
            xTable[i] = i * this._stepSizeDays;

            for ( var j = 0; j <= order; ++j) {
                if (j !== i) {
                    denom[i] *= (i - j);
                }
            }

            denom[i] = 1.0 / denom[i];
        }

        // Allocate scratch arrays for interpolation.
        this._work = new Array(order + 1);
        this._coef = new Array(order + 1);
    }

    var julianDateScratch = new JulianDate(0, 0.0, TimeStandard.TAI);

    function getDaysSinceEpoch(xys, dayTT, secondTT) {
        var dateTT = julianDateScratch;
        dateTT.dayNumber = dayTT;
        dateTT.secondsOfDay = secondTT;
        return JulianDate.daysDifference(dateTT, xys._sampleZeroDateTT);
    }

    /**
     * Preloads XYS data for a specified date range.
     *
     * @param {Number} startDayTT The Julian day number of the beginning of the interval to preload, expressed in
     *                 the Terrestrial Time (TT) time standard.
     * @param {Number} startSecondTT The seconds past noon of the beginning of the interval to preload, expressed in
     *                 the Terrestrial Time (TT) time standard.
     * @param {Number} stopDayTT The Julian day number of the end of the interval to preload, expressed in
     *                 the Terrestrial Time (TT) time standard.
     * @param {Number} stopSecondTT The seconds past noon of the end of the interval to preload, expressed in
     *                 the Terrestrial Time (TT) time standard.
     * @returns {Promise.<undefined>} A promise that, when resolved, indicates that the requested interval has been
     *                    preloaded.
     */
    Iau2006XysData.prototype.preload = function(startDayTT, startSecondTT, stopDayTT, stopSecondTT) {
        var startDaysSinceEpoch = getDaysSinceEpoch(this, startDayTT, startSecondTT);
        var stopDaysSinceEpoch = getDaysSinceEpoch(this, stopDayTT, stopSecondTT);

        var startIndex = (startDaysSinceEpoch / this._stepSizeDays - this._interpolationOrder / 2) | 0;
        if (startIndex < 0) {
            startIndex = 0;
        }

        var stopIndex = (stopDaysSinceEpoch / this._stepSizeDays - this._interpolationOrder / 2) | 0 + this._interpolationOrder;
        if (stopIndex >= this._totalSamples) {
            stopIndex = this._totalSamples - 1;
        }

        var startChunk = (startIndex / this._samplesPerXysFile) | 0;
        var stopChunk = (stopIndex / this._samplesPerXysFile) | 0;

        var promises = [];
        for ( var i = startChunk; i <= stopChunk; ++i) {
            promises.push(requestXysChunk(this, i));
        }

        return when.all(promises);
    };

    /**
     * Computes the XYS values for a given date by interpolating.  If the required data is not yet downloaded,
     * this method will return undefined.
     *
     * @param {Number} dayTT The Julian day number for which to compute the XYS value, expressed in
     *                 the Terrestrial Time (TT) time standard.
     * @param {Number} secondTT The seconds past noon of the date for which to compute the XYS value, expressed in
     *                 the Terrestrial Time (TT) time standard.
     * @param {Iau2006XysSample} [result] The instance to which to copy the interpolated result.  If this parameter
     *                           is undefined, a new instance is allocated and returned.
     * @returns {Iau2006XysSample} The interpolated XYS values, or undefined if the required data for this
     *                             computation has not yet been downloaded.
     *
     * @see Iau2006XysData#preload
     */
    Iau2006XysData.prototype.computeXysRadians = function(dayTT, secondTT, result) {
        var daysSinceEpoch = getDaysSinceEpoch(this, dayTT, secondTT);
        if (daysSinceEpoch < 0.0) {
            // Can't evaluate prior to the epoch of the data.
            return undefined;
        }

        var centerIndex = (daysSinceEpoch / this._stepSizeDays) | 0;
        if (centerIndex >= this._totalSamples) {
            // Can't evaluate after the last sample in the data.
            return undefined;
        }

        var degree = this._interpolationOrder;

        var firstIndex = centerIndex - ((degree / 2) | 0);
        if (firstIndex < 0) {
            firstIndex = 0;
        }
        var lastIndex = firstIndex + degree;
        if (lastIndex >= this._totalSamples) {
            lastIndex = this._totalSamples - 1;
            firstIndex = lastIndex - degree;
            if (firstIndex < 0) {
                firstIndex = 0;
            }
        }

        // Are all the samples we need present?
        // We can assume so if the first and last are present
        var isDataMissing = false;
        var samples = this._samples;
        if (!defined(samples[firstIndex * 3])) {
            requestXysChunk(this, (firstIndex / this._samplesPerXysFile) | 0);
            isDataMissing = true;
        }

        if (!defined(samples[lastIndex * 3])) {
            requestXysChunk(this, (lastIndex / this._samplesPerXysFile) | 0);
            isDataMissing = true;
        }

        if (isDataMissing) {
            return undefined;
        }

        if (!defined(result)) {
            result = new Iau2006XysSample(0.0, 0.0, 0.0);
        } else {
            result.x = 0.0;
            result.y = 0.0;
            result.s = 0.0;
        }

        var x = daysSinceEpoch - firstIndex * this._stepSizeDays;

        var work = this._work;
        var denom = this._denominators;
        var coef = this._coef;
        var xTable = this._xTable;

        var i, j;
        for (i = 0; i <= degree; ++i) {
            work[i] = x - xTable[i];
        }

        for (i = 0; i <= degree; ++i) {
            coef[i] = 1.0;

            for (j = 0; j <= degree; ++j) {
                if (j !== i) {
                    coef[i] *= work[j];
                }
            }

            coef[i] *= denom[i];

            var sampleIndex = (firstIndex + i) * 3;
            result.x += coef[i] * samples[sampleIndex++];
            result.y += coef[i] * samples[sampleIndex++];
            result.s += coef[i] * samples[sampleIndex];
        }

        return result;
    };

    function requestXysChunk(xysData, chunkIndex) {
        if (xysData._chunkDownloadsInProgress[chunkIndex]) {
            // Chunk has already been requested.
            return xysData._chunkDownloadsInProgress[chunkIndex];
        }

        var deferred = when.defer();

        xysData._chunkDownloadsInProgress[chunkIndex] = deferred;

        var chunkUrl;
        var xysFileUrlTemplate = xysData._xysFileUrlTemplate;
        if (defined(xysFileUrlTemplate)) {
            chunkUrl = xysFileUrlTemplate.replace('{0}', chunkIndex);
        } else {
            chunkUrl = buildModuleUrl('Assets/IAU2006_XYS/IAU2006_XYS_' + chunkIndex + '.json');
        }

        when(loadJson(chunkUrl), function(chunk) {
            xysData._chunkDownloadsInProgress[chunkIndex] = false;

            var samples = xysData._samples;
            var newSamples = chunk.samples;
            var startIndex = chunkIndex * xysData._samplesPerXysFile * 3;

            for ( var i = 0, len = newSamples.length; i < len; ++i) {
                samples[startIndex + i] = newSamples[i];
            }

            deferred.resolve();
        });

        return deferred.promise;
    }

    return Iau2006XysData;
});

/*global define*/
define('Core/Fullscreen',[
        './defined',
        './defineProperties'
    ], function(
        defined,
        defineProperties) {
    'use strict';

    var _supportsFullscreen;
    var _names = {
        requestFullscreen : undefined,
        exitFullscreen : undefined,
        fullscreenEnabled : undefined,
        fullscreenElement : undefined,
        fullscreenchange : undefined,
        fullscreenerror : undefined
    };

    /**
     * Browser-independent functions for working with the standard fullscreen API.
     *
     * @exports Fullscreen
     *
     * @see {@link http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html|W3C Fullscreen Living Specification}
     */
    var Fullscreen = {};

    defineProperties(Fullscreen, {
        /**
         * The element that is currently fullscreen, if any.  To simply check if the
         * browser is in fullscreen mode or not, use {@link Fullscreen#fullscreen}.
         * @memberof Fullscreen
         * @type {Object}
         * @readonly
         */
        element : {
            get : function() {
                if (!Fullscreen.supportsFullscreen()) {
                    return undefined;
                }

                return document[_names.fullscreenElement];
            }
        },

        /**
         * The name of the event on the document that is fired when fullscreen is
         * entered or exited.  This event name is intended for use with addEventListener.
         * In your event handler, to determine if the browser is in fullscreen mode or not,
         * use {@link Fullscreen#fullscreen}.
         * @memberof Fullscreen
         * @type {String}
         * @readonly
         */
        changeEventName : {
            get : function() {
                if (!Fullscreen.supportsFullscreen()) {
                    return undefined;
                }

                return _names.fullscreenchange;
            }
        },

        /**
         * The name of the event that is fired when a fullscreen error
         * occurs.  This event name is intended for use with addEventListener.
         * @memberof Fullscreen
         * @type {String}
         * @readonly
         */
        errorEventName : {
            get : function() {
                if (!Fullscreen.supportsFullscreen()) {
                    return undefined;
                }

                return _names.fullscreenerror;
            }
        },

        /**
         * Determine whether the browser will allow an element to be made fullscreen, or not.
         * For example, by default, iframes cannot go fullscreen unless the containing page
         * adds an "allowfullscreen" attribute (or prefixed equivalent).
         * @memberof Fullscreen
         * @type {Boolean}
         * @readonly
         */
        enabled : {
            get : function() {
                if (!Fullscreen.supportsFullscreen()) {
                    return undefined;
                }

                return document[_names.fullscreenEnabled];
            }
        },

        /**
         * Determines if the browser is currently in fullscreen mode.
         * @memberof Fullscreen
         * @type {Boolean}
         * @readonly
         */
        fullscreen : {
            get : function() {
                if (!Fullscreen.supportsFullscreen()) {
                    return undefined;
                }

                return Fullscreen.element !== null;
            }
        }
    });

    /**
     * Detects whether the browser supports the standard fullscreen API.
     *
     * @returns {Boolean} <code>true</code> if the browser supports the standard fullscreen API,
     * <code>false</code> otherwise.
     */
    Fullscreen.supportsFullscreen = function() {
        if (defined(_supportsFullscreen)) {
            return _supportsFullscreen;
        }

        _supportsFullscreen = false;

        var body = document.body;
        if (typeof body.requestFullscreen === 'function') {
            // go with the unprefixed, standard set of names
            _names.requestFullscreen = 'requestFullscreen';
            _names.exitFullscreen = 'exitFullscreen';
            _names.fullscreenEnabled = 'fullscreenEnabled';
            _names.fullscreenElement = 'fullscreenElement';
            _names.fullscreenchange = 'fullscreenchange';
            _names.fullscreenerror = 'fullscreenerror';
            _supportsFullscreen = true;
            return _supportsFullscreen;
        }

        //check for the correct combination of prefix plus the various names that browsers use
        var prefixes = ['webkit', 'moz', 'o', 'ms', 'khtml'];
        var name;
        for (var i = 0, len = prefixes.length; i < len; ++i) {
            var prefix = prefixes[i];

            // casing of Fullscreen differs across browsers
            name = prefix + 'RequestFullscreen';
            if (typeof body[name] === 'function') {
                _names.requestFullscreen = name;
                _supportsFullscreen = true;
            } else {
                name = prefix + 'RequestFullScreen';
                if (typeof body[name] === 'function') {
                    _names.requestFullscreen = name;
                    _supportsFullscreen = true;
                }
            }

            // disagreement about whether it's "exit" as per spec, or "cancel"
            name = prefix + 'ExitFullscreen';
            if (typeof document[name] === 'function') {
                _names.exitFullscreen = name;
            } else {
                name = prefix + 'CancelFullScreen';
                if (typeof document[name] === 'function') {
                    _names.exitFullscreen = name;
                }
            }

            // casing of Fullscreen differs across browsers
            name = prefix + 'FullscreenEnabled';
            if (document[name] !== undefined) {
                _names.fullscreenEnabled = name;
            } else {
                name = prefix + 'FullScreenEnabled';
                if (document[name] !== undefined) {
                    _names.fullscreenEnabled = name;
                }
            }

            // casing of Fullscreen differs across browsers
            name = prefix + 'FullscreenElement';
            if (document[name] !== undefined) {
                _names.fullscreenElement = name;
            } else {
                name = prefix + 'FullScreenElement';
                if (document[name] !== undefined) {
                    _names.fullscreenElement = name;
                }
            }

            // thankfully, event names are all lowercase per spec
            name = prefix + 'fullscreenchange';
            // event names do not have 'on' in the front, but the property on the document does
            if (document['on' + name] !== undefined) {
                //except on IE
                if (prefix === 'ms') {
                    name = 'MSFullscreenChange';
                }
                _names.fullscreenchange = name;
            }

            name = prefix + 'fullscreenerror';
            if (document['on' + name] !== undefined) {
                //except on IE
                if (prefix === 'ms') {
                    name = 'MSFullscreenError';
                }
                _names.fullscreenerror = name;
            }
        }

        return _supportsFullscreen;
    };

    /**
     * Asynchronously requests the browser to enter fullscreen mode on the given element.
     * If fullscreen mode is not supported by the browser, does nothing.
     *
     * @param {Object} element The HTML element which will be placed into fullscreen mode.
     * @param {HMDVRDevice} [vrDevice] The VR device.
     *
     * @example
     * // Put the entire page into fullscreen.
     * Cesium.Fullscreen.requestFullscreen(document.body)
     *
     * // Place only the Cesium canvas into fullscreen.
     * Cesium.Fullscreen.requestFullscreen(scene.canvas)
     */
    Fullscreen.requestFullscreen = function(element, vrDevice) {
        if (!Fullscreen.supportsFullscreen()) {
            return;
        }

        element[_names.requestFullscreen]({ vrDisplay: vrDevice });
    };

    /**
     * Asynchronously exits fullscreen mode.  If the browser is not currently
     * in fullscreen, or if fullscreen mode is not supported by the browser, does nothing.
     */
    Fullscreen.exitFullscreen = function() {
        if (!Fullscreen.supportsFullscreen()) {
            return;
        }

        document[_names.exitFullscreen]();
    };

    return Fullscreen;
});

/*global define*/
define('Core/FeatureDetection',[
        './defaultValue',
        './defined',
        './Fullscreen'
    ], function(
        defaultValue,
        defined,
        Fullscreen) {
    'use strict';

    var theNavigator;
    if (typeof navigator !== 'undefined') {
        theNavigator = navigator;
    } else {
        theNavigator = {};
    }

    function extractVersion(versionString) {
        var parts = versionString.split('.');
        for (var i = 0, len = parts.length; i < len; ++i) {
            parts[i] = parseInt(parts[i], 10);
        }
        return parts;
    }

    var isChromeResult;
    var chromeVersionResult;
    function isChrome() {
        if (!defined(isChromeResult)) {
            isChromeResult = false;
            // Edge contains Chrome in the user agent too
            if (!isEdge()) {
                var fields = (/ Chrome\/([\.0-9]+)/).exec(theNavigator.userAgent);
                if (fields !== null) {
                    isChromeResult = true;
                    chromeVersionResult = extractVersion(fields[1]);
                }
            }
        }

        return isChromeResult;
    }

    function chromeVersion() {
        return isChrome() && chromeVersionResult;
    }

    var isSafariResult;
    var safariVersionResult;
    function isSafari() {
        if (!defined(isSafariResult)) {
            isSafariResult = false;

            // Chrome and Edge contain Safari in the user agent too
            if (!isChrome() && !isEdge() && (/ Safari\/[\.0-9]+/).test(theNavigator.userAgent)) {
                var fields = (/ Version\/([\.0-9]+)/).exec(theNavigator.userAgent);
                if (fields !== null) {
                    isSafariResult = true;
                    safariVersionResult = extractVersion(fields[1]);
                }
            }
        }

        return isSafariResult;
    }

    function safariVersion() {
        return isSafari() && safariVersionResult;
    }

    var isWebkitResult;
    var webkitVersionResult;
    function isWebkit() {
        if (!defined(isWebkitResult)) {
            isWebkitResult = false;

            var fields = (/ AppleWebKit\/([\.0-9]+)(\+?)/).exec(theNavigator.userAgent);
            if (fields !== null) {
                isWebkitResult = true;
                webkitVersionResult = extractVersion(fields[1]);
                webkitVersionResult.isNightly = !!fields[2];
            }
        }

        return isWebkitResult;
    }

    function webkitVersion() {
        return isWebkit() && webkitVersionResult;
    }

    var isInternetExplorerResult;
    var internetExplorerVersionResult;
    function isInternetExplorer() {
        if (!defined(isInternetExplorerResult)) {
            isInternetExplorerResult = false;

            var fields;
            if (theNavigator.appName === 'Microsoft Internet Explorer') {
                fields = /MSIE ([0-9]{1,}[\.0-9]{0,})/.exec(theNavigator.userAgent);
                if (fields !== null) {
                    isInternetExplorerResult = true;
                    internetExplorerVersionResult = extractVersion(fields[1]);
                }
            } else if (theNavigator.appName === 'Netscape') {
                fields = /Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(theNavigator.userAgent);
                if (fields !== null) {
                    isInternetExplorerResult = true;
                    internetExplorerVersionResult = extractVersion(fields[1]);
                }
            }
        }
        return isInternetExplorerResult;
    }

    function internetExplorerVersion() {
        return isInternetExplorer() && internetExplorerVersionResult;
    }

    var isEdgeResult;
    var edgeVersionResult;
    function isEdge() {
        if (!defined(isEdgeResult)) {
            isEdgeResult = false;
            var fields = (/ Edge\/([\.0-9]+)/).exec(theNavigator.userAgent);
            if (fields !== null) {
                isEdgeResult = true;
                edgeVersionResult = extractVersion(fields[1]);
            }
        }
        return isEdgeResult;
    }

    function edgeVersion() {
        return isEdge() && edgeVersionResult;
    }

    var isFirefoxResult;
    var firefoxVersionResult;
    function isFirefox() {
        if (!defined(isFirefoxResult)) {
            isFirefoxResult = false;

            var fields = /Firefox\/([\.0-9]+)/.exec(theNavigator.userAgent);
            if (fields !== null) {
                isFirefoxResult = true;
                firefoxVersionResult = extractVersion(fields[1]);
            }
        }
        return isFirefoxResult;
    }

    var isWindowsResult;
    function isWindows() {
        if (!defined(isWindowsResult)) {
            isWindowsResult = /Windows/i.test(theNavigator.appVersion);
        }
        return isWindowsResult;
    }


    function firefoxVersion() {
        return isFirefox() && firefoxVersionResult;
    }

    var hasPointerEvents;
    function supportsPointerEvents() {
        if (!defined(hasPointerEvents)) {
            //While navigator.pointerEnabled is deprecated in the W3C specification
            //we still need to use it if it exists in order to support browsers
            //that rely on it, such as the Windows WebBrowser control which defines
            //PointerEvent but sets navigator.pointerEnabled to false.
            hasPointerEvents = typeof PointerEvent !== 'undefined' && (!defined(theNavigator.pointerEnabled) || theNavigator.pointerEnabled);
        }
        return hasPointerEvents;
    }

    var imageRenderingValueResult;
    var supportsImageRenderingPixelatedResult;
    function supportsImageRenderingPixelated() {
        if (!defined(supportsImageRenderingPixelatedResult)) {
            var canvas = document.createElement('canvas');
            canvas.setAttribute('style',
                                'image-rendering: -moz-crisp-edges;' +
                                'image-rendering: pixelated;');
            //canvas.style.imageRendering will be undefined, null or an empty string on unsupported browsers.
            var tmp = canvas.style.imageRendering;
            supportsImageRenderingPixelatedResult = defined(tmp) && tmp !== '';
            if (supportsImageRenderingPixelatedResult) {
                imageRenderingValueResult = tmp;
            }
        }
        return supportsImageRenderingPixelatedResult;
    }

    function imageRenderingValue() {
        return supportsImageRenderingPixelated() ? imageRenderingValueResult : undefined;
    }

    /**
     * A set of functions to detect whether the current browser supports
     * various features.
     *
     * @exports FeatureDetection
     */
    var FeatureDetection = {
        isChrome : isChrome,
        chromeVersion : chromeVersion,
        isSafari : isSafari,
        safariVersion : safariVersion,
        isWebkit : isWebkit,
        webkitVersion : webkitVersion,
        isInternetExplorer : isInternetExplorer,
        internetExplorerVersion : internetExplorerVersion,
        isEdge : isEdge,
        edgeVersion : edgeVersion,
        isFirefox : isFirefox,
        firefoxVersion : firefoxVersion,
        isWindows : isWindows,
        hardwareConcurrency : defaultValue(theNavigator.hardwareConcurrency, 3),
        supportsPointerEvents : supportsPointerEvents,
        supportsImageRenderingPixelated: supportsImageRenderingPixelated,
        imageRenderingValue: imageRenderingValue
    };

    /**
     * Detects whether the current browser supports the full screen standard.
     *
     * @returns {Boolean} true if the browser supports the full screen standard, false if not.
     *
     * @see Fullscreen
     * @see {@link http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html|W3C Fullscreen Living Specification}
     */
    FeatureDetection.supportsFullscreen = function() {
        return Fullscreen.supportsFullscreen();
    };

    /**
     * Detects whether the current browser supports typed arrays.
     *
     * @returns {Boolean} true if the browser supports typed arrays, false if not.
     *
     * @see {@link http://www.khronos.org/registry/typedarray/specs/latest/|Typed Array Specification}
     */
    FeatureDetection.supportsTypedArrays = function() {
        return typeof ArrayBuffer !== 'undefined';
    };

    /**
     * Detects whether the current browser supports Web Workers.
     *
     * @returns {Boolean} true if the browsers supports Web Workers, false if not.
     *
     * @see {@link http://www.w3.org/TR/workers/}
     */
    FeatureDetection.supportsWebWorkers = function() {
        return typeof Worker !== 'undefined';
    };

    return FeatureDetection;
});

/*global define*/
define('Core/HeadingPitchRoll',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './Math'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        CesiumMath) {
    "use strict";

    /**
     * A rotation expressed as a heading, pitch, and roll. Heading is the rotation about the
     * negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about
     * the positive x axis.
     * @alias HeadingPitchRoll
     * @constructor
     *
     * @param {Number} [heading=0.0] The heading component in radians.
     * @param {Number} [pitch=0.0] The pitch component in radians.
     * @param {Number} [roll=0.0] The roll component in radians.
     */
    function HeadingPitchRoll(heading, pitch, roll) {
        this.heading = defaultValue(heading, 0.0);
        this.pitch = defaultValue(pitch, 0.0);
        this.roll = defaultValue(roll, 0.0);
    }

    /**
     * Computes the heading, pitch and roll from a quaternion (see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles )
     *
     * @param {Quaternion} quaternion The quaternion from which to retrieve heading, pitch, and roll, all expressed in radians.
     * @param {Quaternion} [result] The object in which to store the result. If not provided, a new instance is created and returned.
     * @returns {HeadingPitchRoll} The modified result parameter or a new HeadingPitchRoll instance if one was not provided.
     */
    HeadingPitchRoll.fromQuaternion = function(quaternion, result) {
                if (!defined(quaternion)) {
            throw new DeveloperError('quaternion is required');
        }
                if (!defined(result)) {
            result = new HeadingPitchRoll();
        }
        var test = 2 * (quaternion.w * quaternion.y - quaternion.z * quaternion.x);
        var denominatorRoll = 1 - 2 * (quaternion.x * quaternion.x + quaternion.y * quaternion.y);
        var numeratorRoll = 2 * (quaternion.w * quaternion.x + quaternion.y * quaternion.z);
        var denominatorHeading = 1 - 2 * (quaternion.y * quaternion.y + quaternion.z * quaternion.z);
        var numeratorHeading = 2 * (quaternion.w * quaternion.z + quaternion.x * quaternion.y);
        result.heading = -Math.atan2(numeratorHeading, denominatorHeading);
        result.roll = Math.atan2(numeratorRoll, denominatorRoll);
        result.pitch = -Math.asin(test);
        return result;
    };

    /**
     * Returns a new HeadingPitchRoll instance from angles given in degrees.
     *
     * @param {Number} heading the heading in degrees
     * @param {Number} pitch the pitch in degrees
     * @param {Number} roll the heading in degrees
     * @param {HeadingPitchRoll} [result] The object in which to store the result. If not provided, a new instance is created and returned.
     * @returns {HeadingPitchRoll} A new HeadingPitchRoll instance
     */
    HeadingPitchRoll.fromDegrees = function(heading, pitch, roll, result) {
                if (!defined(heading)) {
            throw new DeveloperError('heading is required');
        }
        if (!defined(pitch)) {
            throw new DeveloperError('pitch is required');
        }
        if (!defined(roll)) {
            throw new DeveloperError('roll is required');
        }
                if (!defined(result)) {
            result = new HeadingPitchRoll();
        }
        result.heading = heading * CesiumMath.RADIANS_PER_DEGREE;
        result.pitch = pitch * CesiumMath.RADIANS_PER_DEGREE;
        result.roll = roll * CesiumMath.RADIANS_PER_DEGREE;
        return result;
    };

    /**
     * Duplicates a HeadingPitchRoll instance.
     *
     * @param {HeadingPitchRoll} headingPitchRoll The HeadingPitchRoll to duplicate.
     * @param {HeadingPitchRoll} [result] The object onto which to store the result.
     * @returns {HeadingPitchRoll} The modified result parameter or a new HeadingPitchRoll instance if one was not provided. (Returns undefined if headingPitchRoll is undefined)
     */
    HeadingPitchRoll.clone = function(headingPitchRoll, result) {
        if (!defined(headingPitchRoll)) {
            return undefined;
        }
        if (!defined(result)) {
            return new HeadingPitchRoll(headingPitchRoll.heading, headingPitchRoll.pitch, headingPitchRoll.roll);
        }
        result.heading = headingPitchRoll.heading;
        result.pitch = headingPitchRoll.pitch;
        result.roll = headingPitchRoll.roll;
        return result;
    };

    /**
     * Compares the provided HeadingPitchRolls componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {HeadingPitchRoll} [left] The first HeadingPitchRoll.
     * @param {HeadingPitchRoll} [right] The second HeadingPitchRoll.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    HeadingPitchRoll.equals = function(left, right) {
        return (left === right) ||
            ((defined(left)) &&
                (defined(right)) &&
                (left.heading === right.heading) &&
                (left.pitch === right.pitch) &&
                (left.roll === right.roll));
    };

    /**
     * Compares the provided HeadingPitchRolls componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {HeadingPitchRoll} [left] The first HeadingPitchRoll.
     * @param {HeadingPitchRoll} [right] The second HeadingPitchRoll.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    HeadingPitchRoll.equalsEpsilon = function(left, right, relativeEpsilon, absoluteEpsilon) {
        return (left === right) ||
            (defined(left) &&
                defined(right) &&
                CesiumMath.equalsEpsilon(left.heading, right.heading, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.pitch, right.pitch, relativeEpsilon, absoluteEpsilon) &&
                CesiumMath.equalsEpsilon(left.roll, right.roll, relativeEpsilon, absoluteEpsilon));
    };

    /**
     * Duplicates this HeadingPitchRoll instance.
     *
     * @param {HeadingPitchRoll} [result] The object onto which to store the result.
     * @returns {HeadingPitchRoll} The modified result parameter or a new HeadingPitchRoll instance if one was not provided.
     */
    HeadingPitchRoll.prototype.clone = function(result) {
        return HeadingPitchRoll.clone(this, result);
    };

    /**
     * Compares this HeadingPitchRoll against the provided HeadingPitchRoll componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {HeadingPitchRoll} [right] The right hand side HeadingPitchRoll.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    HeadingPitchRoll.prototype.equals = function(right) {
        return HeadingPitchRoll.equals(this, right);
    };

    /**
     * Compares this HeadingPitchRoll against the provided HeadingPitchRoll componentwise and returns
     * <code>true</code> if they pass an absolute or relative tolerance test,
     * <code>false</code> otherwise.
     *
     * @param {HeadingPitchRoll} [right] The right hand side HeadingPitchRoll.
     * @param {Number} relativeEpsilon The relative epsilon tolerance to use for equality testing.
     * @param {Number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    HeadingPitchRoll.prototype.equalsEpsilon = function(right, relativeEpsilon, absoluteEpsilon) {
        return HeadingPitchRoll.equalsEpsilon(this, right, relativeEpsilon, absoluteEpsilon);
    };

    /**
     * Creates a string representing this HeadingPitchRoll in the format '(heading, pitch, roll)' in radians.
     *
     * @returns {String} A string representing the provided HeadingPitchRoll in the format '(heading, pitch, roll)'.
     */
    HeadingPitchRoll.prototype.toString = function() {
        return '(' + this.heading + ', ' + this.pitch + ', ' + this.roll + ')';
    };

    return HeadingPitchRoll;
});

/*global define*/
define('Core/Quaternion',[
        './Cartesian3',
        './Check',
        './defaultValue',
        './defined',
        './deprecationWarning',
        './FeatureDetection',
        './freezeObject',
        './HeadingPitchRoll',
        './Math',
        './Matrix3'
    ], function(
        Cartesian3,
        Check,
        defaultValue,
        defined,
        deprecationWarning,
        FeatureDetection,
        freezeObject,
        HeadingPitchRoll,
        CesiumMath,
        Matrix3) {
    'use strict';

    /**
     * A set of 4-dimensional coordinates used to represent rotation in 3-dimensional space.
     * @alias Quaternion
     * @constructor
     *
     * @param {Number} [x=0.0] The X component.
     * @param {Number} [y=0.0] The Y component.
     * @param {Number} [z=0.0] The Z component.
     * @param {Number} [w=0.0] The W component.
     *
     * @see PackableForInterpolation
     */
    function Quaternion(x, y, z, w) {
        /**
         * The X component.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The Y component.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The Z component.
         * @type {Number}
         * @default 0.0
         */
        this.z = defaultValue(z, 0.0);

        /**
         * The W component.
         * @type {Number}
         * @default 0.0
         */
        this.w = defaultValue(w, 0.0);
    }

    var fromAxisAngleScratch = new Cartesian3();

    /**
     * Computes a quaternion representing a rotation around an axis.
     *
     * @param {Cartesian3} axis The axis of rotation.
     * @param {Number} angle The angle in radians to rotate around the axis.
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
     */
    Quaternion.fromAxisAngle = function(axis, angle, result) {
                Check.typeOf.object('axis', axis);
        Check.typeOf.number('angle', angle);
        
        var halfAngle = angle / 2.0;
        var s = Math.sin(halfAngle);
        fromAxisAngleScratch = Cartesian3.normalize(axis, fromAxisAngleScratch);

        var x = fromAxisAngleScratch.x * s;
        var y = fromAxisAngleScratch.y * s;
        var z = fromAxisAngleScratch.z * s;
        var w = Math.cos(halfAngle);
        if (!defined(result)) {
            return new Quaternion(x, y, z, w);
        }
        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    var fromRotationMatrixNext = [1, 2, 0];
    var fromRotationMatrixQuat = new Array(3);
    /**
     * Computes a Quaternion from the provided Matrix3 instance.
     *
     * @param {Matrix3} matrix The rotation matrix.
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
     *
     * @see Matrix3.fromQuaternion
     */
    Quaternion.fromRotationMatrix = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        
        var root;
        var x;
        var y;
        var z;
        var w;

        var m00 = matrix[Matrix3.COLUMN0ROW0];
        var m11 = matrix[Matrix3.COLUMN1ROW1];
        var m22 = matrix[Matrix3.COLUMN2ROW2];
        var trace = m00 + m11 + m22;

        if (trace > 0.0) {
            // |w| > 1/2, may as well choose w > 1/2
            root = Math.sqrt(trace + 1.0); // 2w
            w = 0.5 * root;
            root = 0.5 / root; // 1/(4w)

            x = (matrix[Matrix3.COLUMN1ROW2] - matrix[Matrix3.COLUMN2ROW1]) * root;
            y = (matrix[Matrix3.COLUMN2ROW0] - matrix[Matrix3.COLUMN0ROW2]) * root;
            z = (matrix[Matrix3.COLUMN0ROW1] - matrix[Matrix3.COLUMN1ROW0]) * root;
        } else {
            // |w| <= 1/2
            var next = fromRotationMatrixNext;

            var i = 0;
            if (m11 > m00) {
                i = 1;
            }
            if (m22 > m00 && m22 > m11) {
                i = 2;
            }
            var j = next[i];
            var k = next[j];

            root = Math.sqrt(matrix[Matrix3.getElementIndex(i, i)] - matrix[Matrix3.getElementIndex(j, j)] - matrix[Matrix3.getElementIndex(k, k)] + 1.0);

            var quat = fromRotationMatrixQuat;
            quat[i] = 0.5 * root;
            root = 0.5 / root;
            w = (matrix[Matrix3.getElementIndex(k, j)] - matrix[Matrix3.getElementIndex(j, k)]) * root;
            quat[j] = (matrix[Matrix3.getElementIndex(j, i)] + matrix[Matrix3.getElementIndex(i, j)]) * root;
            quat[k] = (matrix[Matrix3.getElementIndex(k, i)] + matrix[Matrix3.getElementIndex(i, k)]) * root;

            x = -quat[0];
            y = -quat[1];
            z = -quat[2];
        }

        if (!defined(result)) {
            return new Quaternion(x, y, z, w);
        }
        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    var scratchHPRQuaternion = new Quaternion();
    var scratchHeadingQuaternion = new Quaternion();
    var scratchPitchQuaternion = new Quaternion();
    var scratchRollQuaternion = new Quaternion();

    /**
     * Computes a rotation from the given heading, pitch and roll angles. Heading is the rotation about the
     * negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about
     * the positive x axis.
     *
     * @param {Number} heading The heading angle in radians.
     * @param {Number} pitch The pitch angle in radians.
     * @param {Number} roll The roll angle in radians.
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if none was provided.
     */
    Quaternion.fromHeadingPitchRoll = function(headingOrHeadingPitchRoll, pitchOrResult, roll, result) {
                if (headingOrHeadingPitchRoll instanceof HeadingPitchRoll) {
            Check.typeOf.object('headingPitchRoll', headingOrHeadingPitchRoll);
        } else {
            Check.typeOf.number(headingOrHeadingPitchRoll, 'heading');
            Check.typeOf.number(pitchOrResult, 'pitch');
            Check.typeOf.number(roll, 'roll');
        }
                var hpr;
        if (headingOrHeadingPitchRoll instanceof HeadingPitchRoll) {
            hpr = headingOrHeadingPitchRoll;
            result = pitchOrResult;
        } else {
            deprecationWarning('Quaternion.fromHeadingPitchRoll(heading, pitch, roll,result)', 'The method was deprecated in Cesium 1.32 and will be removed in version 1.33. ' + 'Use Quaternion.fromHeadingPitchRoll(hpr,result) where hpr is a HeadingPitchRoll');
            hpr = new HeadingPitchRoll(headingOrHeadingPitchRoll, pitchOrResult, roll);
        }
        scratchRollQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_X, hpr.roll, scratchHPRQuaternion);
        scratchPitchQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -hpr.pitch, result);
        result = Quaternion.multiply(scratchPitchQuaternion, scratchRollQuaternion, scratchPitchQuaternion);
        scratchHeadingQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -hpr.heading, scratchHPRQuaternion);
        return Quaternion.multiply(scratchHeadingQuaternion, result, result);
    };

    var sampledQuaternionAxis = new Cartesian3();
    var sampledQuaternionRotation = new Cartesian3();
    var sampledQuaternionTempQuaternion = new Quaternion();
    var sampledQuaternionQuaternion0 = new Quaternion();
    var sampledQuaternionQuaternion0Conjugate = new Quaternion();

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Quaternion.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Quaternion} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Quaternion.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex++] = value.y;
        array[startingIndex++] = value.z;
        array[startingIndex] = value.w;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Quaternion} [result] The object into which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
     */
    Quaternion.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Quaternion();
        }
        result.x = array[startingIndex];
        result.y = array[startingIndex + 1];
        result.z = array[startingIndex + 2];
        result.w = array[startingIndex + 3];
        return result;
    };

    /**
     * The number of elements used to store the object into an array in its interpolatable form.
     * @type {Number}
     */
    Quaternion.packedInterpolationLength = 3;

    /**
     * Converts a packed array into a form suitable for interpolation.
     *
     * @param {Number[]} packedArray The packed array.
     * @param {Number} [startingIndex=0] The index of the first element to be converted.
     * @param {Number} [lastIndex=packedArray.length] The index of the last element to be converted.
     * @param {Number[]} result The object into which to store the result.
     */
    Quaternion.convertPackedArrayForInterpolation = function(packedArray, startingIndex, lastIndex, result) {
        Quaternion.unpack(packedArray, lastIndex * 4, sampledQuaternionQuaternion0Conjugate);
        Quaternion.conjugate(sampledQuaternionQuaternion0Conjugate, sampledQuaternionQuaternion0Conjugate);

        for (var i = 0, len = lastIndex - startingIndex + 1; i < len; i++) {
            var offset = i * 3;
            Quaternion.unpack(packedArray, (startingIndex + i) * 4, sampledQuaternionTempQuaternion);

            Quaternion.multiply(sampledQuaternionTempQuaternion, sampledQuaternionQuaternion0Conjugate, sampledQuaternionTempQuaternion);

            if (sampledQuaternionTempQuaternion.w < 0) {
                Quaternion.negate(sampledQuaternionTempQuaternion, sampledQuaternionTempQuaternion);
            }

            Quaternion.computeAxis(sampledQuaternionTempQuaternion, sampledQuaternionAxis);
            var angle = Quaternion.computeAngle(sampledQuaternionTempQuaternion);
            result[offset] = sampledQuaternionAxis.x * angle;
            result[offset + 1] = sampledQuaternionAxis.y * angle;
            result[offset + 2] = sampledQuaternionAxis.z * angle;
        }
    };

    /**
     * Retrieves an instance from a packed array converted with {@link convertPackedArrayForInterpolation}.
     *
     * @param {Number[]} array The array previously packed for interpolation.
     * @param {Number[]} sourceArray The original packed array.
     * @param {Number} [firstIndex=0] The firstIndex used to convert the array.
     * @param {Number} [lastIndex=packedArray.length] The lastIndex used to convert the array.
     * @param {Quaternion} [result] The object into which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
     */
    Quaternion.unpackInterpolationResult = function(array, sourceArray, firstIndex, lastIndex, result) {
        if (!defined(result)) {
            result = new Quaternion();
        }
        Cartesian3.fromArray(array, 0, sampledQuaternionRotation);
        var magnitude = Cartesian3.magnitude(sampledQuaternionRotation);

        Quaternion.unpack(sourceArray, lastIndex * 4, sampledQuaternionQuaternion0);

        if (magnitude === 0) {
            Quaternion.clone(Quaternion.IDENTITY, sampledQuaternionTempQuaternion);
        } else {
            Quaternion.fromAxisAngle(sampledQuaternionRotation, magnitude, sampledQuaternionTempQuaternion);
        }

        return Quaternion.multiply(sampledQuaternionTempQuaternion, sampledQuaternionQuaternion0, result);
    };

    /**
     * Duplicates a Quaternion instance.
     *
     * @param {Quaternion} quaternion The quaternion to duplicate.
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided. (Returns undefined if quaternion is undefined)
     */
    Quaternion.clone = function(quaternion, result) {
        if (!defined(quaternion)) {
            return undefined;
        }

        if (!defined(result)) {
            return new Quaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
        }

        result.x = quaternion.x;
        result.y = quaternion.y;
        result.z = quaternion.z;
        result.w = quaternion.w;
        return result;
    };

    /**
     * Computes the conjugate of the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to conjugate.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.conjugate = function(quaternion, result) {
                Check.typeOf.object('quaternion', quaternion);
        Check.typeOf.object('result', result);
        
        result.x = -quaternion.x;
        result.y = -quaternion.y;
        result.z = -quaternion.z;
        result.w = quaternion.w;
        return result;
    };

    /**
     * Computes magnitude squared for the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to conjugate.
     * @returns {Number} The magnitude squared.
     */
    Quaternion.magnitudeSquared = function(quaternion) {
                Check.typeOf.object('quaternion', quaternion);
        
        return quaternion.x * quaternion.x + quaternion.y * quaternion.y + quaternion.z * quaternion.z + quaternion.w * quaternion.w;
    };

    /**
     * Computes magnitude for the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to conjugate.
     * @returns {Number} The magnitude.
     */
    Quaternion.magnitude = function(quaternion) {
        return Math.sqrt(Quaternion.magnitudeSquared(quaternion));
    };

    /**
     * Computes the normalized form of the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to normalize.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.normalize = function(quaternion, result) {
                Check.typeOf.object('result', result);
        
        var inverseMagnitude = 1.0 / Quaternion.magnitude(quaternion);
        var x = quaternion.x * inverseMagnitude;
        var y = quaternion.y * inverseMagnitude;
        var z = quaternion.z * inverseMagnitude;
        var w = quaternion.w * inverseMagnitude;

        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    /**
     * Computes the inverse of the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to normalize.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.inverse = function(quaternion, result) {
                Check.typeOf.object('result', result);
        
        var magnitudeSquared = Quaternion.magnitudeSquared(quaternion);
        result = Quaternion.conjugate(quaternion, result);
        return Quaternion.multiplyByScalar(result, 1.0 / magnitudeSquared, result);
    };

    /**
     * Computes the componentwise sum of two quaternions.
     *
     * @param {Quaternion} left The first quaternion.
     * @param {Quaternion} right The second quaternion.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x + right.x;
        result.y = left.y + right.y;
        result.z = left.z + right.z;
        result.w = left.w + right.w;
        return result;
    };

    /**
     * Computes the componentwise difference of two quaternions.
     *
     * @param {Quaternion} left The first quaternion.
     * @param {Quaternion} right The second quaternion.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result.x = left.x - right.x;
        result.y = left.y - right.y;
        result.z = left.z - right.z;
        result.w = left.w - right.w;
        return result;
    };

    /**
     * Negates the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to be negated.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.negate = function(quaternion, result) {
                Check.typeOf.object('quaternion', quaternion);
        Check.typeOf.object('result', result);
        
        result.x = -quaternion.x;
        result.y = -quaternion.y;
        result.z = -quaternion.z;
        result.w = -quaternion.w;
        return result;
    };

    /**
     * Computes the dot (scalar) product of two quaternions.
     *
     * @param {Quaternion} left The first quaternion.
     * @param {Quaternion} right The second quaternion.
     * @returns {Number} The dot product.
     */
    Quaternion.dot = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        return left.x * right.x + left.y * right.y + left.z * right.z + left.w * right.w;
    };

    /**
     * Computes the product of two quaternions.
     *
     * @param {Quaternion} left The first quaternion.
     * @param {Quaternion} right The second quaternion.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.multiply = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        var leftX = left.x;
        var leftY = left.y;
        var leftZ = left.z;
        var leftW = left.w;

        var rightX = right.x;
        var rightY = right.y;
        var rightZ = right.z;
        var rightW = right.w;

        var x = leftW * rightX + leftX * rightW + leftY * rightZ - leftZ * rightY;
        var y = leftW * rightY - leftX * rightZ + leftY * rightW + leftZ * rightX;
        var z = leftW * rightZ + leftX * rightY - leftY * rightX + leftZ * rightW;
        var w = leftW * rightW - leftX * rightX - leftY * rightY - leftZ * rightZ;

        result.x = x;
        result.y = y;
        result.z = z;
        result.w = w;
        return result;
    };

    /**
     * Multiplies the provided quaternion componentwise by the provided scalar.
     *
     * @param {Quaternion} quaternion The quaternion to be scaled.
     * @param {Number} scalar The scalar to multiply with.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.multiplyByScalar = function(quaternion, scalar, result) {
                Check.typeOf.object('quaternion', quaternion);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = quaternion.x * scalar;
        result.y = quaternion.y * scalar;
        result.z = quaternion.z * scalar;
        result.w = quaternion.w * scalar;
        return result;
    };

    /**
     * Divides the provided quaternion componentwise by the provided scalar.
     *
     * @param {Quaternion} quaternion The quaternion to be divided.
     * @param {Number} scalar The scalar to divide by.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.divideByScalar = function(quaternion, scalar, result) {
                Check.typeOf.object('quaternion', quaternion);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result.x = quaternion.x / scalar;
        result.y = quaternion.y / scalar;
        result.z = quaternion.z / scalar;
        result.w = quaternion.w / scalar;
        return result;
    };

    /**
     * Computes the axis of rotation of the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to use.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Quaternion.computeAxis = function(quaternion, result) {
                Check.typeOf.object('quaternion', quaternion);
        Check.typeOf.object('result', result);
        
        var w = quaternion.w;
        if (Math.abs(w - 1.0) < CesiumMath.EPSILON6) {
            result.x = result.y = result.z = 0;
            return result;
        }

        var scalar = 1.0 / Math.sqrt(1.0 - (w * w));

        result.x = quaternion.x * scalar;
        result.y = quaternion.y * scalar;
        result.z = quaternion.z * scalar;
        return result;
    };

    /**
     * Computes the angle of rotation of the provided quaternion.
     *
     * @param {Quaternion} quaternion The quaternion to use.
     * @returns {Number} The angle of rotation.
     */
    Quaternion.computeAngle = function(quaternion) {
                Check.typeOf.object('quaternion', quaternion);
        
        if (Math.abs(quaternion.w - 1.0) < CesiumMath.EPSILON6) {
            return 0.0;
        }
        return 2.0 * Math.acos(quaternion.w);
    };

    var lerpScratch = new Quaternion();
    /**
     * Computes the linear interpolation or extrapolation at t using the provided quaternions.
     *
     * @param {Quaternion} start The value corresponding to t at 0.0.
     * @param {Quaternion} end The value corresponding to t at 1.0.
     * @param {Number} t The point along t at which to interpolate.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.lerp = function(start, end, t, result) {
                Check.typeOf.object('start', start);
        Check.typeOf.object('end', end);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        lerpScratch = Quaternion.multiplyByScalar(end, t, lerpScratch);
        result = Quaternion.multiplyByScalar(start, 1.0 - t, result);
        return Quaternion.add(lerpScratch, result, result);
    };

    var slerpEndNegated = new Quaternion();
    var slerpScaledP = new Quaternion();
    var slerpScaledR = new Quaternion();
    /**
     * Computes the spherical linear interpolation or extrapolation at t using the provided quaternions.
     *
     * @param {Quaternion} start The value corresponding to t at 0.0.
     * @param {Quaternion} end The value corresponding to t at 1.0.
     * @param {Number} t The point along t at which to interpolate.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     *
     * @see Quaternion#fastSlerp
     */
    Quaternion.slerp = function(start, end, t, result) {
                Check.typeOf.object('start', start);
        Check.typeOf.object('end', end);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        var dot = Quaternion.dot(start, end);

        // The angle between start must be acute. Since q and -q represent
        // the same rotation, negate q to get the acute angle.
        var r = end;
        if (dot < 0.0) {
            dot = -dot;
            r = slerpEndNegated = Quaternion.negate(end, slerpEndNegated);
        }

        // dot > 0, as the dot product approaches 1, the angle between the
        // quaternions vanishes. use linear interpolation.
        if (1.0 - dot < CesiumMath.EPSILON6) {
            return Quaternion.lerp(start, r, t, result);
        }

        var theta = Math.acos(dot);
        slerpScaledP = Quaternion.multiplyByScalar(start, Math.sin((1 - t) * theta), slerpScaledP);
        slerpScaledR = Quaternion.multiplyByScalar(r, Math.sin(t * theta), slerpScaledR);
        result = Quaternion.add(slerpScaledP, slerpScaledR, result);
        return Quaternion.multiplyByScalar(result, 1.0 / Math.sin(theta), result);
    };

    /**
     * The logarithmic quaternion function.
     *
     * @param {Quaternion} quaternion The unit quaternion.
     * @param {Cartesian3} result The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter.
     */
    Quaternion.log = function(quaternion, result) {
                Check.typeOf.object('quaternion', quaternion);
        Check.typeOf.object('result', result);
        
        var theta = CesiumMath.acosClamped(quaternion.w);
        var thetaOverSinTheta = 0.0;

        if (theta !== 0.0) {
            thetaOverSinTheta = theta / Math.sin(theta);
        }

        return Cartesian3.multiplyByScalar(quaternion, thetaOverSinTheta, result);
    };

    /**
     * The exponential quaternion function.
     *
     * @param {Cartesian3} cartesian The cartesian.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     */
    Quaternion.exp = function(cartesian, result) {
                Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var theta = Cartesian3.magnitude(cartesian);
        var sinThetaOverTheta = 0.0;

        if (theta !== 0.0) {
            sinThetaOverTheta = Math.sin(theta) / theta;
        }

        result.x = cartesian.x * sinThetaOverTheta;
        result.y = cartesian.y * sinThetaOverTheta;
        result.z = cartesian.z * sinThetaOverTheta;
        result.w = Math.cos(theta);

        return result;
    };

    var squadScratchCartesian0 = new Cartesian3();
    var squadScratchCartesian1 = new Cartesian3();
    var squadScratchQuaternion0 = new Quaternion();
    var squadScratchQuaternion1 = new Quaternion();

    /**
     * Computes an inner quadrangle point.
     * <p>This will compute quaternions that ensure a squad curve is C<sup>1</sup>.</p>
     *
     * @param {Quaternion} q0 The first quaternion.
     * @param {Quaternion} q1 The second quaternion.
     * @param {Quaternion} q2 The third quaternion.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     *
     * @see Quaternion#squad
     */
    Quaternion.computeInnerQuadrangle = function(q0, q1, q2, result) {
                Check.typeOf.object('q0', q0);
        Check.typeOf.object('q1', q1);
        Check.typeOf.object('q2', q2);
        Check.typeOf.object('result', result);
        
        var qInv = Quaternion.conjugate(q1, squadScratchQuaternion0);
        Quaternion.multiply(qInv, q2, squadScratchQuaternion1);
        var cart0 = Quaternion.log(squadScratchQuaternion1, squadScratchCartesian0);

        Quaternion.multiply(qInv, q0, squadScratchQuaternion1);
        var cart1 = Quaternion.log(squadScratchQuaternion1, squadScratchCartesian1);

        Cartesian3.add(cart0, cart1, cart0);
        Cartesian3.multiplyByScalar(cart0, 0.25, cart0);
        Cartesian3.negate(cart0, cart0);
        Quaternion.exp(cart0, squadScratchQuaternion0);

        return Quaternion.multiply(q1, squadScratchQuaternion0, result);
    };

    /**
     * Computes the spherical quadrangle interpolation between quaternions.
     *
     * @param {Quaternion} q0 The first quaternion.
     * @param {Quaternion} q1 The second quaternion.
     * @param {Quaternion} s0 The first inner quadrangle.
     * @param {Quaternion} s1 The second inner quadrangle.
     * @param {Number} t The time in [0,1] used to interpolate.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     *
     *
     * @example
     * // 1. compute the squad interpolation between two quaternions on a curve
     * var s0 = Cesium.Quaternion.computeInnerQuadrangle(quaternions[i - 1], quaternions[i], quaternions[i + 1], new Cesium.Quaternion());
     * var s1 = Cesium.Quaternion.computeInnerQuadrangle(quaternions[i], quaternions[i + 1], quaternions[i + 2], new Cesium.Quaternion());
     * var q = Cesium.Quaternion.squad(quaternions[i], quaternions[i + 1], s0, s1, t, new Cesium.Quaternion());
     *
     * // 2. compute the squad interpolation as above but where the first quaternion is a end point.
     * var s1 = Cesium.Quaternion.computeInnerQuadrangle(quaternions[0], quaternions[1], quaternions[2], new Cesium.Quaternion());
     * var q = Cesium.Quaternion.squad(quaternions[0], quaternions[1], quaternions[0], s1, t, new Cesium.Quaternion());
     *
     * @see Quaternion#computeInnerQuadrangle
     */
    Quaternion.squad = function(q0, q1, s0, s1, t, result) {
                Check.typeOf.object('q0', q0);
        Check.typeOf.object('q1', q1);
        Check.typeOf.object('s0', s0);
        Check.typeOf.object('s1', s1);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        var slerp0 = Quaternion.slerp(q0, q1, t, squadScratchQuaternion0);
        var slerp1 = Quaternion.slerp(s0, s1, t, squadScratchQuaternion1);
        return Quaternion.slerp(slerp0, slerp1, 2.0 * t * (1.0 - t), result);
    };

    var fastSlerpScratchQuaternion = new Quaternion();
    var opmu = 1.90110745351730037;
    var u = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
    var v = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
    var bT = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];
    var bD = FeatureDetection.supportsTypedArrays() ? new Float32Array(8) : [];

    for (var i = 0; i < 7; ++i) {
        var s = i + 1.0;
        var t = 2.0 * s + 1.0;
        u[i] = 1.0 / (s * t);
        v[i] = s / t;
    }

    u[7] = opmu / (8.0 * 17.0);
    v[7] = opmu * 8.0 / 17.0;

    /**
     * Computes the spherical linear interpolation or extrapolation at t using the provided quaternions.
     * This implementation is faster than {@link Quaternion#slerp}, but is only accurate up to 10<sup>-6</sup>.
     *
     * @param {Quaternion} start The value corresponding to t at 0.0.
     * @param {Quaternion} end The value corresponding to t at 1.0.
     * @param {Number} t The point along t at which to interpolate.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter.
     *
     * @see Quaternion#slerp
     */
    Quaternion.fastSlerp = function(start, end, t, result) {
                Check.typeOf.object('start', start);
        Check.typeOf.object('end', end);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        var x = Quaternion.dot(start, end);

        var sign;
        if (x >= 0) {
            sign = 1.0;
        } else {
            sign = -1.0;
            x = -x;
        }

        var xm1 = x - 1.0;
        var d = 1.0 - t;
        var sqrT = t * t;
        var sqrD = d * d;

        for (var i = 7; i >= 0; --i) {
            bT[i] = (u[i] * sqrT - v[i]) * xm1;
            bD[i] = (u[i] * sqrD - v[i]) * xm1;
        }

        var cT = sign * t * (
            1.0 + bT[0] * (1.0 + bT[1] * (1.0 + bT[2] * (1.0 + bT[3] * (
            1.0 + bT[4] * (1.0 + bT[5] * (1.0 + bT[6] * (1.0 + bT[7]))))))));
        var cD = d * (
            1.0 + bD[0] * (1.0 + bD[1] * (1.0 + bD[2] * (1.0 + bD[3] * (
            1.0 + bD[4] * (1.0 + bD[5] * (1.0 + bD[6] * (1.0 + bD[7]))))))));

        var temp = Quaternion.multiplyByScalar(start, cD, fastSlerpScratchQuaternion);
        Quaternion.multiplyByScalar(end, cT, result);
        return Quaternion.add(temp, result, result);
    };

    /**
     * Computes the spherical quadrangle interpolation between quaternions.
     * An implementation that is faster than {@link Quaternion#squad}, but less accurate.
     *
     * @param {Quaternion} q0 The first quaternion.
     * @param {Quaternion} q1 The second quaternion.
     * @param {Quaternion} s0 The first inner quadrangle.
     * @param {Quaternion} s1 The second inner quadrangle.
     * @param {Number} t The time in [0,1] used to interpolate.
     * @param {Quaternion} result The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new instance if none was provided.
     *
     * @see Quaternion#squad
     */
    Quaternion.fastSquad = function(q0, q1, s0, s1, t, result) {
                Check.typeOf.object('q0', q0);
        Check.typeOf.object('q1', q1);
        Check.typeOf.object('s0', s0);
        Check.typeOf.object('s1', s1);
        Check.typeOf.number('t', t);
        Check.typeOf.object('result', result);
        
        var slerp0 = Quaternion.fastSlerp(q0, q1, t, squadScratchQuaternion0);
        var slerp1 = Quaternion.fastSlerp(s0, s1, t, squadScratchQuaternion1);
        return Quaternion.fastSlerp(slerp0, slerp1, 2.0 * t * (1.0 - t), result);
    };

    /**
     * Compares the provided quaternions componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Quaternion} [left] The first quaternion.
     * @param {Quaternion} [right] The second quaternion.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Quaternion.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.z === right.z) &&
                (left.w === right.w));
    };

    /**
     * Compares the provided quaternions componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Quaternion} [left] The first quaternion.
     * @param {Quaternion} [right] The second quaternion.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Quaternion.equalsEpsilon = function(left, right, epsilon) {
                Check.typeOf.number('epsilon', epsilon);
        
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (Math.abs(left.x - right.x) <= epsilon) &&
                (Math.abs(left.y - right.y) <= epsilon) &&
                (Math.abs(left.z - right.z) <= epsilon) &&
                (Math.abs(left.w - right.w) <= epsilon));
    };

    /**
     * An immutable Quaternion instance initialized to (0.0, 0.0, 0.0, 0.0).
     *
     * @type {Quaternion}
     * @constant
     */
    Quaternion.ZERO = freezeObject(new Quaternion(0.0, 0.0, 0.0, 0.0));

    /**
     * An immutable Quaternion instance initialized to (0.0, 0.0, 0.0, 1.0).
     *
     * @type {Quaternion}
     * @constant
     */
    Quaternion.IDENTITY = freezeObject(new Quaternion(0.0, 0.0, 0.0, 1.0));

    /**
     * Duplicates this Quaternion instance.
     *
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if one was not provided.
     */
    Quaternion.prototype.clone = function(result) {
        return Quaternion.clone(this, result);
    };

    /**
     * Compares this and the provided quaternion componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Quaternion} [right] The right hand side quaternion.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Quaternion.prototype.equals = function(right) {
        return Quaternion.equals(this, right);
    };

    /**
     * Compares this and the provided quaternion componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Quaternion} [right] The right hand side quaternion.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Quaternion.prototype.equalsEpsilon = function(right, epsilon) {
        return Quaternion.equalsEpsilon(this, right, epsilon);
    };

    /**
     * Returns a string representing this quaternion in the format (x, y, z, w).
     *
     * @returns {String} A string representing this Quaternion.
     */
    Quaternion.prototype.toString = function() {
        return '(' + this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w + ')';
    };

    return Quaternion;
});

/*global define*/
define('Core/Transforms',[
        '../ThirdParty/when',
        './Cartesian2',
        './Cartesian3',
        './Cartesian4',
        './Cartographic',
        './Check',
        './defaultValue',
        './defined',
        './deprecationWarning',
        './DeveloperError',
        './EarthOrientationParameters',
        './EarthOrientationParametersSample',
        './Ellipsoid',
        './Iau2006XysData',
        './Iau2006XysSample',
        './JulianDate',
        './Math',
        './Matrix3',
        './Matrix4',
        './Quaternion',
        './TimeConstants'
    ], function(
        when,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        Check,
        defaultValue,
        defined,
        deprecationWarning,
        DeveloperError,
        EarthOrientationParameters,
        EarthOrientationParametersSample,
        Ellipsoid,
        Iau2006XysData,
        Iau2006XysSample,
        JulianDate,
        CesiumMath,
        Matrix3,
        Matrix4,
        Quaternion,
        TimeConstants) {
    'use strict';

    /**
     * Contains functions for transforming positions to various reference frames.
     *
     * @exports Transforms
     */
    var Transforms = {};

    var vectorProductLocalFrame = {
        up : {
            south : 'east',
            north : 'west',
            west : 'south',
            east : 'north'
        },
        down : {
            south : 'west',
            north : 'east',
            west : 'north',
            east : 'south'
        },
        south : {
            up : 'west',
            down : 'east',
            west : 'down',
            east : 'up'
        },
        north : {
            up : 'east',
            down : 'west',
            west : 'up',
            east : 'down'
        },
        west : {
            up : 'north',
            down : 'south',
            north : 'down',
            south : 'up'
        },
        east : {
            up : 'south',
            down : 'north',
            north : 'up',
            south : 'down'
        }
    };

    var degeneratePositionLocalFrame = {
        north : [-1, 0, 0],
        east : [0, 1, 0],
        up : [0, 0, 1],
        south : [1, 0, 0],
        west : [0, -1, 0],
        down : [0, 0, -1]
    };

    var localFrameToFixedFrameCache = {};

    var scratchCalculateCartesian = {
        east : new Cartesian3(),
        north : new Cartesian3(),
        up : new Cartesian3(),
        west : new Cartesian3(),
        south : new Cartesian3(),
        down : new Cartesian3()
    };
    var scratchFirstCartesian = new Cartesian3();
    var scratchSecondCartesian = new Cartesian3();
    var scratchThirdCartesian = new Cartesian3();
    /**
    * Generates a function that computes a 4x4 transformation matrix from a reference frame
    * centered at the provided origin to the provided ellipsoid's fixed reference frame.
    * @param  {String} firstAxis  name of the first axis of the local reference frame. Must be
    *  'east', 'north', 'up', 'west', 'south' or 'down'.
    * @param  {String} secondAxis  name of the second axis of the local reference frame. Must be
    *  'east', 'north', 'up', 'west', 'south' or 'down'.
    * @return {localFrameToFixedFrameGenerator~resultat} The function that will computes a
    * 4x4 transformation matrix from a reference frame, with first axis and second axis compliant with the parameters,
    */
    Transforms.localFrameToFixedFrameGenerator = function( firstAxis, secondAxis) {
      if (!vectorProductLocalFrame.hasOwnProperty(firstAxis) || !vectorProductLocalFrame[firstAxis].hasOwnProperty(secondAxis)) {
          throw new DeveloperError('firstAxis and secondAxis must be east, north, up, west, south or down.');
      }
      var thirdAxis = vectorProductLocalFrame[firstAxis][secondAxis];

      /**
       * Computes a 4x4 transformation matrix from a reference frame
       * centered at the provided origin to the provided ellipsoid's fixed reference frame.
       * @callback Transforms~LocalFrameToFixedFrame
       * @param {Cartesian3} origin The center point of the local reference frame.
       * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
       * @param {Matrix4} [result] The object onto which to store the result.
       * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
       */
      var resultat;
      var hashAxis = firstAxis + secondAxis;
      if (defined(localFrameToFixedFrameCache[hashAxis])) {
          resultat = localFrameToFixedFrameCache[hashAxis];
      } else {
          resultat = function(origin, ellipsoid, result) {
                            if (!defined(origin)) {
                  throw new DeveloperError('origin is required.');
              }
                            if (!defined(result)) {
                  result = new Matrix4();
              }
              // If x and y are zero, assume origin is at a pole, which is a special case.
              if (CesiumMath.equalsEpsilon(origin.x, 0.0, CesiumMath.EPSILON14) && CesiumMath.equalsEpsilon(origin.y, 0.0, CesiumMath.EPSILON14)) {
                  var sign = CesiumMath.sign(origin.z);

                  Cartesian3.unpack(degeneratePositionLocalFrame[firstAxis], 0, scratchFirstCartesian);
                  if (firstAxis !== 'east' && firstAxis !== 'west') {
                      Cartesian3.multiplyByScalar(scratchFirstCartesian, sign, scratchFirstCartesian);
                  }

                  Cartesian3.unpack(degeneratePositionLocalFrame[secondAxis], 0, scratchSecondCartesian);
                  if (secondAxis !== 'east' && secondAxis !== 'west') {
                      Cartesian3.multiplyByScalar(scratchSecondCartesian, sign, scratchSecondCartesian);
                  }

                  Cartesian3.unpack(degeneratePositionLocalFrame[thirdAxis], 0, scratchThirdCartesian);
                  if (thirdAxis !== 'east' && thirdAxis !== 'west') {
                      Cartesian3.multiplyByScalar(scratchThirdCartesian, sign, scratchThirdCartesian);
                  }
              } else {
                  ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
                  ellipsoid.geodeticSurfaceNormal(origin, scratchCalculateCartesian.up);

                  var up = scratchCalculateCartesian.up;
                  var east = scratchCalculateCartesian.east;
                  east.x = -origin.y;
                  east.y = origin.x;
                  east.z = 0.0;
                  Cartesian3.normalize(east, scratchCalculateCartesian.east);
                  Cartesian3.cross(up, east, scratchCalculateCartesian.north);

                  Cartesian3.multiplyByScalar(scratchCalculateCartesian.up, -1, scratchCalculateCartesian.down);
                  Cartesian3.multiplyByScalar(scratchCalculateCartesian.east, -1, scratchCalculateCartesian.west);
                  Cartesian3.multiplyByScalar(scratchCalculateCartesian.north, -1, scratchCalculateCartesian.south);

                  scratchFirstCartesian = scratchCalculateCartesian[firstAxis];
                  scratchSecondCartesian = scratchCalculateCartesian[secondAxis];
                  scratchThirdCartesian = scratchCalculateCartesian[thirdAxis];
              }
              result[0] = scratchFirstCartesian.x;
              result[1] = scratchFirstCartesian.y;
              result[2] = scratchFirstCartesian.z;
              result[3] = 0.0;
              result[4] = scratchSecondCartesian.x;
              result[5] = scratchSecondCartesian.y;
              result[6] = scratchSecondCartesian.z;
              result[7] = 0.0;
              result[8] = scratchThirdCartesian.x;
              result[9] = scratchThirdCartesian.y;
              result[10] = scratchThirdCartesian.z;
              result[11] = 0.0;
              result[12] = origin.x;
              result[13] = origin.y;
              result[14] = origin.z;
              result[15] = 1.0;
              return result;
          };
          localFrameToFixedFrameCache[hashAxis] = resultat;
      }
      return resultat;
    };

    /**
     * Computes a 4x4 transformation matrix from a reference frame with an east-north-up axes
     * centered at the provided origin to the provided ellipsoid's fixed reference frame.
     * The local axes are defined as:
     * <ul>
     * <li>The <code>x</code> axis points in the local east direction.</li>
     * <li>The <code>y</code> axis points in the local north direction.</li>
     * <li>The <code>z</code> axis points in the direction of the ellipsoid surface normal which passes through the position.</li>
     * </ul>
     *
     * @param {Cartesian3} origin The center point of the local reference frame.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
     *
     * @example
     * // Get the transform from local east-north-up at cartographic (0.0, 0.0) to Earth's fixed frame.
     * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
     */
    Transforms.eastNorthUpToFixedFrame = Transforms.localFrameToFixedFrameGenerator('east','north');

    /**
     * Computes a 4x4 transformation matrix from a reference frame with an north-east-down axes
     * centered at the provided origin to the provided ellipsoid's fixed reference frame.
     * The local axes are defined as:
     * <ul>
     * <li>The <code>x</code> axis points in the local north direction.</li>
     * <li>The <code>y</code> axis points in the local east direction.</li>
     * <li>The <code>z</code> axis points in the opposite direction of the ellipsoid surface normal which passes through the position.</li>
     * </ul>
     *
     * @param {Cartesian3} origin The center point of the local reference frame.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
     *
     * @example
     * // Get the transform from local north-east-down at cartographic (0.0, 0.0) to Earth's fixed frame.
     * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var transform = Cesium.Transforms.northEastDownToFixedFrame(center);
     */
    Transforms.northEastDownToFixedFrame = Transforms.localFrameToFixedFrameGenerator('north','east');

    /**
     * Computes a 4x4 transformation matrix from a reference frame with an north-up-east axes
     * centered at the provided origin to the provided ellipsoid's fixed reference frame.
     * The local axes are defined as:
     * <ul>
     * <li>The <code>x</code> axis points in the local north direction.</li>
     * <li>The <code>y</code> axis points in the direction of the ellipsoid surface normal which passes through the position.</li>
     * <li>The <code>z</code> axis points in the local east direction.</li>
     * </ul>
     *
     * @param {Cartesian3} origin The center point of the local reference frame.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
     *
     * @example
     * // Get the transform from local north-up-east at cartographic (0.0, 0.0) to Earth's fixed frame.
     * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var transform = Cesium.Transforms.northUpEastToFixedFrame(center);
     */
    Transforms.northUpEastToFixedFrame = Transforms.localFrameToFixedFrameGenerator('north','up');

    /**
    * Computes a 4x4 transformation matrix from a reference frame with an north-west-up axes
    * centered at the provided origin to the provided ellipsoid's fixed reference frame.
    * The local axes are defined as:
    * <ul>
    * <li>The <code>x</code> axis points in the local north direction.</li>
    * <li>The <code>y</code> axis points in the local west direction.</li>
    * <li>The <code>z</code> axis points in the direction of the ellipsoid surface normal which passes through the position.</li>
    * </ul>
    *
    * @param {Cartesian3} origin The center point of the local reference frame.
    * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
    * @param {Matrix4} [result] The object onto which to store the result.
    * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
    *
    * @example
    * // Get the transform from local north-West-Up at cartographic (0.0, 0.0) to Earth's fixed frame.
    * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
    * var transform = Cesium.Transforms.northWestUpToFixedFrame(center);
    */
   Transforms.northWestUpToFixedFrame = Transforms.localFrameToFixedFrameGenerator('north','west');

    var scratchHPRQuaternion = new Quaternion();
    var scratchScale = new Cartesian3(1.0, 1.0, 1.0);
    var scratchHPRMatrix4 = new Matrix4();

    /**
     * Computes a 4x4 transformation matrix from a reference frame with axes computed from the heading-pitch-roll angles
     * centered at the provided origin to the provided ellipsoid's fixed reference frame. Heading is the rotation from the local north
     * direction where a positive angle is increasing eastward. Pitch is the rotation from the local east-north plane. Positive pitch angles
     * are above the plane. Negative pitch angles are below the plane. Roll is the first rotation applied about the local east axis.
     *
     * @param {Cartesian3} origin The center point of the local reference frame.
     * @param {HeadingPitchRoll} headingPitchRoll The heading, pitch, and roll.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
     * @param {Transforms~LocalFrameToFixedFrame} [fixedFrameTransformOrResult=Transforms.eastNorthUpToFixedFrame] A 4x4 transformation
     *  matrix from a reference frame to the provided ellipsoid's fixed reference frame
     * @param {Matrix4} [result] The object onto which to store the result.
     * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided.
     *
     * @example
     * // Get the transform from local heading-pitch-roll at cartographic (0.0, 0.0) to Earth's fixed frame.
     * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var heading = -Cesium.Math.PI_OVER_TWO;
     * var pitch = Cesium.Math.PI_OVER_FOUR;
     * var roll = 0.0;
     * var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
     * var transform = Cesium.Transforms.headingPitchRollToFixedFrame(center, hpr);
     */
    Transforms.headingPitchRollToFixedFrame = function(origin, headingPitchRoll, ellipsoid, fixedFrameTransformOrResult, result) {
                Check.typeOf.object( 'HeadingPitchRoll', headingPitchRoll);
        
        // checks for required parameters happen in the called functions
        if(fixedFrameTransformOrResult instanceof Matrix4){
            result = fixedFrameTransformOrResult;
            fixedFrameTransformOrResult = undefined;
            deprecationWarning('Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, result)', 'The method was deprecated in Cesium 1.31 and will be removed in version 1.33. Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result) where fixedFrameTransform is a a 4x4 transformation matrix (see Transforms.localFrameToFixedFrameGenerator)');
        }
        fixedFrameTransformOrResult = defaultValue(fixedFrameTransformOrResult,Transforms.eastNorthUpToFixedFrame);
        var hprQuaternion = Quaternion.fromHeadingPitchRoll(headingPitchRoll, scratchHPRQuaternion);
        var hprMatrix = Matrix4.fromTranslationQuaternionRotationScale(Cartesian3.ZERO, hprQuaternion, scratchScale, scratchHPRMatrix4);
        result = fixedFrameTransformOrResult(origin, ellipsoid, result);
        return Matrix4.multiply(result, hprMatrix, result);
    };

    var scratchENUMatrix4 = new Matrix4();
    var scratchHPRMatrix3 = new Matrix3();

    /**
     * Computes a quaternion from a reference frame with axes computed from the heading-pitch-roll angles
     * centered at the provided origin. Heading is the rotation from the local north
     * direction where a positive angle is increasing eastward. Pitch is the rotation from the local east-north plane. Positive pitch angles
     * are above the plane. Negative pitch angles are below the plane. Roll is the first rotation applied about the local east axis.
     *
     * @param {Cartesian3} origin The center point of the local reference frame.
     * @param {HeadingPitchRoll} headingPitchRoll The heading, pitch, and roll.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation.
     * @param {Transforms~LocalFrameToFixedFrame} [fixedFrameTransformOrResult=Transforms.eastNorthUpToFixedFrame] A 4x4 transformation
     *  matrix from a reference frame to the provided ellipsoid's fixed reference frame
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new Quaternion instance if none was provided.
     *
     * @example
     * // Get the quaternion from local heading-pitch-roll at cartographic (0.0, 0.0) to Earth's fixed frame.
     * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var heading = -Cesium.Math.PI_OVER_TWO;
     * var pitch = Cesium.Math.PI_OVER_FOUR;
     * var roll = 0.0;
     * var hpr = new HeadingPitchRoll(heading, pitch, roll);
     * var quaternion = Cesium.Transforms.headingPitchRollQuaternion(center, hpr);
     */
    Transforms.headingPitchRollQuaternion = function(origin, headingPitchRoll, ellipsoid, fixedFrameTransformOrResult, result) {
                Check.typeOf.object( 'HeadingPitchRoll', headingPitchRoll);
                if (fixedFrameTransformOrResult instanceof Quaternion) {
            result = fixedFrameTransformOrResult;
            fixedFrameTransformOrResult = undefined;
            deprecationWarning('Transforms.headingPitchRollQuaternion(origin, headingPitchRoll, ellipsoid, result)', 'The method was deprecated in Cesium 1.31 and will be removed in version 1.33. Transforms.headingPitchRollQuaternion(origin, headingPitchRoll, ellipsoid, fixedFrameTransform, result) where fixedFrameTransform is a a 4x4 transformation matrix (see Transforms.localFrameToFixedFrameGenerator)');
        }
        var transform = Transforms.headingPitchRollToFixedFrame(origin, headingPitchRoll, ellipsoid,fixedFrameTransformOrResult, scratchENUMatrix4);
        var rotation = Matrix4.getRotation(transform, scratchHPRMatrix3);
        return Quaternion.fromRotationMatrix(rotation, result);
    };

    var gmstConstant0 = 6 * 3600 + 41 * 60 + 50.54841;
    var gmstConstant1 = 8640184.812866;
    var gmstConstant2 = 0.093104;
    var gmstConstant3 = -6.2E-6;
    var rateCoef = 1.1772758384668e-19;
    var wgs84WRPrecessing = 7.2921158553E-5;
    var twoPiOverSecondsInDay = CesiumMath.TWO_PI / 86400.0;
    var dateInUtc = new JulianDate();

    /**
     * Computes a rotation matrix to transform a point or vector from True Equator Mean Equinox (TEME) axes to the
     * pseudo-fixed axes at a given time.  This method treats the UT1 time standard as equivalent to UTC.
     *
     * @param {JulianDate} date The time at which to compute the rotation matrix.
     * @param {Matrix3} [result] The object onto which to store the result.
     * @returns {Matrix3} The modified result parameter or a new Matrix3 instance if none was provided.
     *
     * @example
     * //Set the view to in the inertial frame.
     * scene.preRender.addEventListener(function(scene, time) {
     *    var now = Cesium.JulianDate.now();
     *    var offset = Cesium.Matrix4.multiplyByPoint(camera.transform, camera.position, new Cesium.Cartesian3());
     *    var transform = Cesium.Matrix4.fromRotationTranslation(Cesium.Transforms.computeTemeToPseudoFixedMatrix(now));
     *    var inverseTransform = Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4());
     *    Cesium.Matrix4.multiplyByPoint(inverseTransform, offset, offset);
     *    camera.lookAtTransform(transform, offset);
     * });
     */
    Transforms.computeTemeToPseudoFixedMatrix = function (date, result) {
                if (!defined(date)) {
            throw new DeveloperError('date is required.');
        }
        
        // GMST is actually computed using UT1.  We're using UTC as an approximation of UT1.
        // We do not want to use the function like convertTaiToUtc in JulianDate because
        // we explicitly do not want to fail when inside the leap second.

        dateInUtc = JulianDate.addSeconds(date, -JulianDate.computeTaiMinusUtc(date), dateInUtc);
        var utcDayNumber = dateInUtc.dayNumber;
        var utcSecondsIntoDay = dateInUtc.secondsOfDay;

        var t;
        var diffDays = utcDayNumber - 2451545;
        if (utcSecondsIntoDay >= 43200.0) {
            t = (diffDays + 0.5) / TimeConstants.DAYS_PER_JULIAN_CENTURY;
        } else {
            t = (diffDays - 0.5) / TimeConstants.DAYS_PER_JULIAN_CENTURY;
        }

        var gmst0 = gmstConstant0 + t * (gmstConstant1 + t * (gmstConstant2 + t * gmstConstant3));
        var angle = (gmst0 * twoPiOverSecondsInDay) % CesiumMath.TWO_PI;
        var ratio = wgs84WRPrecessing + rateCoef * (utcDayNumber - 2451545.5);
        var secondsSinceMidnight = (utcSecondsIntoDay + TimeConstants.SECONDS_PER_DAY * 0.5) % TimeConstants.SECONDS_PER_DAY;
        var gha = angle + (ratio * secondsSinceMidnight);
        var cosGha = Math.cos(gha);
        var sinGha = Math.sin(gha);

        if (!defined(result)) {
            return new Matrix3(cosGha, sinGha, 0.0,
                              -sinGha, cosGha, 0.0,
                                  0.0,    0.0, 1.0);
        }
        result[0] = cosGha;
        result[1] = -sinGha;
        result[2] = 0.0;
        result[3] = sinGha;
        result[4] = cosGha;
        result[5] = 0.0;
        result[6] = 0.0;
        result[7] = 0.0;
        result[8] = 1.0;
        return result;
    };

    /**
     * The source of IAU 2006 XYS data, used for computing the transformation between the
     * Fixed and ICRF axes.
     * @type {Iau2006XysData}
     *
     * @see Transforms.computeIcrfToFixedMatrix
     * @see Transforms.computeFixedToIcrfMatrix
     *
     * @private
     */
    Transforms.iau2006XysData = new Iau2006XysData();

    /**
     * The source of Earth Orientation Parameters (EOP) data, used for computing the transformation
     * between the Fixed and ICRF axes.  By default, zero values are used for all EOP values,
     * yielding a reasonable but not completely accurate representation of the ICRF axes.
     * @type {EarthOrientationParameters}
     *
     * @see Transforms.computeIcrfToFixedMatrix
     * @see Transforms.computeFixedToIcrfMatrix
     *
     * @private
     */
    Transforms.earthOrientationParameters = EarthOrientationParameters.NONE;

    var ttMinusTai = 32.184;
    var j2000ttDays = 2451545.0;

    /**
     * Preloads the data necessary to transform between the ICRF and Fixed axes, in either
     * direction, over a given interval.  This function returns a promise that, when resolved,
     * indicates that the preload has completed.
     *
     * @param {TimeInterval} timeInterval The interval to preload.
     * @returns {Promise.<undefined>} A promise that, when resolved, indicates that the preload has completed
     *          and evaluation of the transformation between the fixed and ICRF axes will
     *          no longer return undefined for a time inside the interval.
     *
     *
     * @example
     * var interval = new Cesium.TimeInterval(...);
     * when(Cesium.Transforms.preloadIcrfFixed(interval), function() {
     *     // the data is now loaded
     * });
     *
     * @see Transforms.computeIcrfToFixedMatrix
     * @see Transforms.computeFixedToIcrfMatrix
     * @see when
     */
    Transforms.preloadIcrfFixed = function(timeInterval) {
        var startDayTT = timeInterval.start.dayNumber;
        var startSecondTT = timeInterval.start.secondsOfDay + ttMinusTai;
        var stopDayTT = timeInterval.stop.dayNumber;
        var stopSecondTT = timeInterval.stop.secondsOfDay + ttMinusTai;

        var xysPromise = Transforms.iau2006XysData.preload(startDayTT, startSecondTT, stopDayTT, stopSecondTT);
        var eopPromise = Transforms.earthOrientationParameters.getPromiseToLoad();

        return when.all([xysPromise, eopPromise]);
    };

    /**
     * Computes a rotation matrix to transform a point or vector from the International Celestial
     * Reference Frame (GCRF/ICRF) inertial frame axes to the Earth-Fixed frame axes (ITRF)
     * at a given time.  This function may return undefined if the data necessary to
     * do the transformation is not yet loaded.
     *
     * @param {JulianDate} date The time at which to compute the rotation matrix.
     * @param {Matrix3} [result] The object onto which to store the result.  If this parameter is
     *                  not specified, a new instance is created and returned.
     * @returns {Matrix3} The rotation matrix, or undefined if the data necessary to do the
     *                   transformation is not yet loaded.
     *
     *
     * @example
     * scene.preRender.addEventListener(function(scene, time) {
     *   var icrfToFixed = Cesium.Transforms.computeIcrfToFixedMatrix(time);
     *   if (Cesium.defined(icrfToFixed)) {
     *     var offset = Cesium.Matrix4.multiplyByPoint(camera.transform, camera.position, new Cesium.Cartesian3());
     *     var transform = Cesium.Matrix4.fromRotationTranslation(icrfToFixed)
     *     var inverseTransform = Cesium.Matrix4.inverseTransformation(transform, new Cesium.Matrix4());
     *     Cesium.Matrix4.multiplyByPoint(inverseTransform, offset, offset);
     *     camera.lookAtTransform(transform, offset);
     *   }
     * });
     *
     * @see Transforms.preloadIcrfFixed
     */
    Transforms.computeIcrfToFixedMatrix = function(date, result) {
                if (!defined(date)) {
            throw new DeveloperError('date is required.');
        }
                if (!defined(result)) {
            result = new Matrix3();
        }

        var fixedToIcrfMtx = Transforms.computeFixedToIcrfMatrix(date, result);
        if (!defined(fixedToIcrfMtx)) {
            return undefined;
        }

        return Matrix3.transpose(fixedToIcrfMtx, result);
    };

    var xysScratch = new Iau2006XysSample(0.0, 0.0, 0.0);
    var eopScratch = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
    var rotation1Scratch = new Matrix3();
    var rotation2Scratch = new Matrix3();

    /**
     * Computes a rotation matrix to transform a point or vector from the Earth-Fixed frame axes (ITRF)
     * to the International Celestial Reference Frame (GCRF/ICRF) inertial frame axes
     * at a given time.  This function may return undefined if the data necessary to
     * do the transformation is not yet loaded.
     *
     * @param {JulianDate} date The time at which to compute the rotation matrix.
     * @param {Matrix3} [result] The object onto which to store the result.  If this parameter is
     *                  not specified, a new instance is created and returned.
     * @returns {Matrix3} The rotation matrix, or undefined if the data necessary to do the
     *                   transformation is not yet loaded.
     *
     *
     * @example
     * // Transform a point from the ICRF axes to the Fixed axes.
     * var now = Cesium.JulianDate.now();
     * var pointInFixed = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var fixedToIcrf = Cesium.Transforms.computeIcrfToFixedMatrix(now);
     * var pointInInertial = new Cesium.Cartesian3();
     * if (Cesium.defined(fixedToIcrf)) {
     *     pointInInertial = Cesium.Matrix3.multiplyByVector(fixedToIcrf, pointInFixed, pointInInertial);
     * }
     *
     * @see Transforms.preloadIcrfFixed
     */
    Transforms.computeFixedToIcrfMatrix = function(date, result) {
                if (!defined(date)) {
            throw new DeveloperError('date is required.');
        }
        
        if (!defined(result)) {
            result = new Matrix3();
        }

        // Compute pole wander
        var eop = Transforms.earthOrientationParameters.compute(date, eopScratch);
        if (!defined(eop)) {
            return undefined;
        }

        // There is no external conversion to Terrestrial Time (TT).
        // So use International Atomic Time (TAI) and convert using offsets.
        // Here we are assuming that dayTT and secondTT are positive
        var dayTT = date.dayNumber;
        // It's possible here that secondTT could roll over 86400
        // This does not seem to affect the precision (unit tests check for this)
        var secondTT = date.secondsOfDay + ttMinusTai;

        var xys = Transforms.iau2006XysData.computeXysRadians(dayTT, secondTT, xysScratch);
        if (!defined(xys)) {
            return undefined;
        }

        var x = xys.x + eop.xPoleOffset;
        var y = xys.y + eop.yPoleOffset;

        // Compute XYS rotation
        var a = 1.0 / (1.0 + Math.sqrt(1.0 - x * x - y * y));

        var rotation1 = rotation1Scratch;
        rotation1[0] = 1.0 - a * x * x;
        rotation1[3] = -a * x * y;
        rotation1[6] = x;
        rotation1[1] = -a * x * y;
        rotation1[4] = 1 - a * y * y;
        rotation1[7] = y;
        rotation1[2] = -x;
        rotation1[5] = -y;
        rotation1[8] = 1 - a * (x * x + y * y);

        var rotation2 = Matrix3.fromRotationZ(-xys.s, rotation2Scratch);
        var matrixQ = Matrix3.multiply(rotation1, rotation2, rotation1Scratch);

        // Similar to TT conversions above
        // It's possible here that secondTT could roll over 86400
        // This does not seem to affect the precision (unit tests check for this)
        var dateUt1day = date.dayNumber;
        var dateUt1sec = date.secondsOfDay - JulianDate.computeTaiMinusUtc(date) + eop.ut1MinusUtc;

        // Compute Earth rotation angle
        // The IERS standard for era is
        //    era = 0.7790572732640 + 1.00273781191135448 * Tu
        // where
        //    Tu = JulianDateInUt1 - 2451545.0
        // However, you get much more precision if you make the following simplification
        //    era = a + (1 + b) * (JulianDayNumber + FractionOfDay - 2451545)
        //    era = a + (JulianDayNumber - 2451545) + FractionOfDay + b (JulianDayNumber - 2451545 + FractionOfDay)
        //    era = a + FractionOfDay + b (JulianDayNumber - 2451545 + FractionOfDay)
        // since (JulianDayNumber - 2451545) represents an integer number of revolutions which will be discarded anyway.
        var daysSinceJ2000 = dateUt1day - 2451545;
        var fractionOfDay = dateUt1sec / TimeConstants.SECONDS_PER_DAY;
        var era = 0.7790572732640 + fractionOfDay + 0.00273781191135448 * (daysSinceJ2000 + fractionOfDay);
        era = (era % 1.0) * CesiumMath.TWO_PI;

        var earthRotation = Matrix3.fromRotationZ(era, rotation2Scratch);

        // pseudoFixed to ICRF
        var pfToIcrf = Matrix3.multiply(matrixQ, earthRotation, rotation1Scratch);

        // Compute pole wander matrix
        var cosxp = Math.cos(eop.xPoleWander);
        var cosyp = Math.cos(eop.yPoleWander);
        var sinxp = Math.sin(eop.xPoleWander);
        var sinyp = Math.sin(eop.yPoleWander);

        var ttt = (dayTT - j2000ttDays) + secondTT / TimeConstants.SECONDS_PER_DAY;
        ttt /= 36525.0;

        // approximate sp value in rad
        var sp = -47.0e-6 * ttt * CesiumMath.RADIANS_PER_DEGREE / 3600.0;
        var cossp = Math.cos(sp);
        var sinsp = Math.sin(sp);

        var fToPfMtx = rotation2Scratch;
        fToPfMtx[0] = cosxp * cossp;
        fToPfMtx[1] = cosxp * sinsp;
        fToPfMtx[2] = sinxp;
        fToPfMtx[3] = -cosyp * sinsp + sinyp * sinxp * cossp;
        fToPfMtx[4] = cosyp * cossp + sinyp * sinxp * sinsp;
        fToPfMtx[5] = -sinyp * cosxp;
        fToPfMtx[6] = -sinyp * sinsp - cosyp * sinxp * cossp;
        fToPfMtx[7] = sinyp * cossp - cosyp * sinxp * sinsp;
        fToPfMtx[8] = cosyp * cosxp;

        return Matrix3.multiply(pfToIcrf, fToPfMtx, result);
    };

    var pointToWindowCoordinatesTemp = new Cartesian4();

    /**
     * Transform a point from model coordinates to window coordinates.
     *
     * @param {Matrix4} modelViewProjectionMatrix The 4x4 model-view-projection matrix.
     * @param {Matrix4} viewportTransformation The 4x4 viewport transformation.
     * @param {Cartesian3} point The point to transform.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided.
     */
    Transforms.pointToWindowCoordinates = function (modelViewProjectionMatrix, viewportTransformation, point, result) {
        result = Transforms.pointToGLWindowCoordinates(modelViewProjectionMatrix, viewportTransformation, point, result);
        result.y = 2.0 * viewportTransformation[5] - result.y;
        return result;
    };

    /**
     * @private
     */
    Transforms.pointToGLWindowCoordinates = function(modelViewProjectionMatrix, viewportTransformation, point, result) {
                if (!defined(modelViewProjectionMatrix)) {
            throw new DeveloperError('modelViewProjectionMatrix is required.');
        }

        if (!defined(viewportTransformation)) {
            throw new DeveloperError('viewportTransformation is required.');
        }

        if (!defined(point)) {
            throw new DeveloperError('point is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian2();
        }

        var tmp = pointToWindowCoordinatesTemp;

        Matrix4.multiplyByVector(modelViewProjectionMatrix, Cartesian4.fromElements(point.x, point.y, point.z, 1, tmp), tmp);
        Cartesian4.multiplyByScalar(tmp, 1.0 / tmp.w, tmp);
        Matrix4.multiplyByVector(viewportTransformation, tmp, tmp);
        return Cartesian2.fromCartesian4(tmp, result);
    };

    var normalScratch = new Cartesian3();
    var rightScratch = new Cartesian3();
    var upScratch = new Cartesian3();

    /**
     * @private
     */
    Transforms.rotationMatrixFromPositionVelocity = function(position, velocity, ellipsoid, result) {
                if (!defined(position)) {
            throw new DeveloperError('position is required.');
        }

        if (!defined(velocity)) {
            throw new DeveloperError('velocity is required.');
        }
        
        var normal = defaultValue(ellipsoid, Ellipsoid.WGS84).geodeticSurfaceNormal(position, normalScratch);
        var right = Cartesian3.cross(velocity, normal, rightScratch);
        if (Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
            right = Cartesian3.clone(Cartesian3.UNIT_X, right);
        }

        var up = Cartesian3.cross(right, velocity, upScratch);
        Cartesian3.cross(velocity, up, right);
        Cartesian3.negate(right, right);

        if (!defined(result)) {
            result = new Matrix3();
        }

        result[0] = velocity.x;
        result[1] = velocity.y;
        result[2] = velocity.z;
        result[3] = right.x;
        result[4] = right.y;
        result[5] = right.z;
        result[6] = up.x;
        result[7] = up.y;
        result[8] = up.z;

        return result;
    };

    var scratchCartographic = new Cartographic();
    var scratchCartesian3Projection = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchCartesian4Origin = new Cartesian4();
    var scratchCartesian4NewOrigin = new Cartesian4();
    var scratchCartesian4NewXAxis = new Cartesian4();
    var scratchCartesian4NewYAxis = new Cartesian4();
    var scratchCartesian4NewZAxis = new Cartesian4();
    var scratchFromENU = new Matrix4();
    var scratchToENU = new Matrix4();

    /**
     * @private
     */
    Transforms.basisTo2D = function(projection, matrix, result) {
                if (!defined(projection)) {
            throw new DeveloperError('projection is required.');
        }
        if (!defined(matrix)) {
            throw new DeveloperError('matrix is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var ellipsoid = projection.ellipsoid;

        var origin = Matrix4.getColumn(matrix, 3, scratchCartesian4Origin);
        var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic);

        var fromENU = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid, scratchFromENU);
        var toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);

        var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
        var newOrigin = scratchCartesian4NewOrigin;
        newOrigin.x = projectedPosition.z;
        newOrigin.y = projectedPosition.x;
        newOrigin.z = projectedPosition.y;
        newOrigin.w = 1.0;

        var xAxis = Matrix4.getColumn(matrix, 0, scratchCartesian3);
        var xScale = Cartesian3.magnitude(xAxis);
        var newXAxis = Matrix4.multiplyByVector(toENU, xAxis, scratchCartesian4NewXAxis);
        Cartesian4.fromElements(newXAxis.z, newXAxis.x, newXAxis.y, 0.0, newXAxis);

        var yAxis = Matrix4.getColumn(matrix, 1, scratchCartesian3);
        var yScale = Cartesian3.magnitude(yAxis);
        var newYAxis = Matrix4.multiplyByVector(toENU, yAxis, scratchCartesian4NewYAxis);
        Cartesian4.fromElements(newYAxis.z, newYAxis.x, newYAxis.y, 0.0, newYAxis);

        var zAxis = Matrix4.getColumn(matrix, 2, scratchCartesian3);
        var zScale = Cartesian3.magnitude(zAxis);

        var newZAxis = scratchCartesian4NewZAxis;
        Cartesian3.cross(newXAxis, newYAxis, newZAxis);
        Cartesian3.normalize(newZAxis, newZAxis);
        Cartesian3.cross(newYAxis, newZAxis, newXAxis);
        Cartesian3.normalize(newXAxis, newXAxis);
        Cartesian3.cross(newZAxis, newXAxis, newYAxis);
        Cartesian3.normalize(newYAxis, newYAxis);

        Cartesian3.multiplyByScalar(newXAxis, xScale, newXAxis);
        Cartesian3.multiplyByScalar(newYAxis, yScale, newYAxis);
        Cartesian3.multiplyByScalar(newZAxis, zScale, newZAxis);

        Matrix4.setColumn(result, 0, newXAxis, result);
        Matrix4.setColumn(result, 1, newYAxis, result);
        Matrix4.setColumn(result, 2, newZAxis, result);
        Matrix4.setColumn(result, 3, newOrigin, result);

        return result;
    };

    var swizzleMatrix = new Matrix4(
        0.0, 0.0, 1.0, 0.0,
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 1.0);

    /**
     * @private
     */
    Transforms.wgs84To2DModelMatrix = function(projection, center, result) {
                if (!defined(projection)) {
            throw new DeveloperError('projection is required.');
        }
        if (!defined(center)) {
            throw new DeveloperError('center is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var ellipsoid = projection.ellipsoid;

        var fromENU = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, scratchFromENU);
        var toENU = Matrix4.inverseTransformation(fromENU, scratchToENU);

        var cartographic = ellipsoid.cartesianToCartographic(center, scratchCartographic);
        var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
        var newOrigin = scratchCartesian4NewOrigin;
        newOrigin.x = projectedPosition.z;
        newOrigin.y = projectedPosition.x;
        newOrigin.z = projectedPosition.y;
        newOrigin.w = 1.0;

        var translation = Matrix4.fromTranslation(newOrigin, scratchFromENU);
        Matrix4.multiply(swizzleMatrix, toENU, result);
        Matrix4.multiply(translation, result, result);

        return result;
    };

    return Transforms;
});

/*global define*/
define('Core/EllipsoidTangentPlane',[
        './AxisAlignedBoundingBox',
        './Cartesian2',
        './Cartesian3',
        './Cartesian4',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './IntersectionTests',
        './Matrix4',
        './Plane',
        './Ray',
        './Transforms'
    ], function(
        AxisAlignedBoundingBox,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        IntersectionTests,
        Matrix4,
        Plane,
        Ray,
        Transforms) {
    'use strict';

    var scratchCart4 = new Cartesian4();
    /**
     * A plane tangent to the provided ellipsoid at the provided origin.
     * If origin is not on the surface of the ellipsoid, it's surface projection will be used.
     * If origin is at the center of the ellipsoid, an exception will be thrown.
     * @alias EllipsoidTangentPlane
     * @constructor
     *
     * @param {Cartesian3} origin The point on the surface of the ellipsoid where the tangent plane touches.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use.
     *
     * @exception {DeveloperError} origin must not be at the center of the ellipsoid.
     */
    function EllipsoidTangentPlane(origin, ellipsoid) {
                if (!defined(origin)) {
            throw new DeveloperError('origin is required.');
        }
        
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        origin = ellipsoid.scaleToGeodeticSurface(origin);

                if (!defined(origin)) {
            throw new DeveloperError('origin must not be at the center of the ellipsoid.');
        }
        
        var eastNorthUp = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid);
        this._ellipsoid = ellipsoid;
        this._origin = origin;
        this._xAxis = Cartesian3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 0, scratchCart4));
        this._yAxis = Cartesian3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 1, scratchCart4));

        var normal = Cartesian3.fromCartesian4(Matrix4.getColumn(eastNorthUp, 2, scratchCart4));
        this._plane = Plane.fromPointNormal(origin, normal);
    }

    defineProperties(EllipsoidTangentPlane.prototype, {
        /**
         * Gets the ellipsoid.
         * @memberof EllipsoidTangentPlane.prototype
         * @type {Ellipsoid}
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        },

        /**
         * Gets the origin.
         * @memberof EllipsoidTangentPlane.prototype
         * @type {Cartesian3}
         */
        origin : {
            get : function() {
                return this._origin;
            }
        },

        /**
         * Gets the plane which is tangent to the ellipsoid.
         * @memberof EllipsoidTangentPlane.prototype
         * @readonly
         * @type {Plane}
         */
        plane : {
            get : function() {
                return this._plane;
            }
        },

        /**
         * Gets the local X-axis (east) of the tangent plane.
         * @memberof EllipsoidTangentPlane.prototype
         * @readonly
         * @type {Cartesian3}
         */
        xAxis : {
            get : function() {
                return this._xAxis;
            }
        },

        /**
         * Gets the local Y-axis (north) of the tangent plane.
         * @memberof EllipsoidTangentPlane.prototype
         * @readonly
         * @type {Cartesian3}
         */
        yAxis : {
            get : function() {
                return this._yAxis;
            }
        },

        /**
         * Gets the local Z-axis (up) of the tangent plane.
         * @member EllipsoidTangentPlane.prototype
         * @readonly
         * @type {Cartesian3}
         */
        zAxis : {
            get : function() {
                return this._plane.normal;
            }
        }
    });

    var tmp = new AxisAlignedBoundingBox();
    /**
     * Creates a new instance from the provided ellipsoid and the center
     * point of the provided Cartesians.
     *
     * @param {Ellipsoid} ellipsoid The ellipsoid to use.
     * @param {Cartesian3} cartesians The list of positions surrounding the center point.
     */
    EllipsoidTangentPlane.fromPoints = function(cartesians, ellipsoid) {
                if (!defined(cartesians)) {
            throw new DeveloperError('cartesians is required.');
        }
        
        var box = AxisAlignedBoundingBox.fromPoints(cartesians, tmp);
        return new EllipsoidTangentPlane(box.center, ellipsoid);
    };

    var scratchProjectPointOntoPlaneRay = new Ray();
    var scratchProjectPointOntoPlaneCartesian3 = new Cartesian3();

    /**
     * Computes the projection of the provided 3D position onto the 2D plane, radially outward from the {@link EllipsoidTangentPlane.ellipsoid} coordinate system origin.
     *
     * @param {Cartesian3} cartesian The point to project.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided. Undefined if there is no intersection point
     */
    EllipsoidTangentPlane.prototype.projectPointOntoPlane = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        var ray = scratchProjectPointOntoPlaneRay;
        ray.origin = cartesian;
        Cartesian3.normalize(cartesian, ray.direction);

        var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
        if (!defined(intersectionPoint)) {
            Cartesian3.negate(ray.direction, ray.direction);
            intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
        }

        if (defined(intersectionPoint)) {
            var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint);
            var x = Cartesian3.dot(this._xAxis, v);
            var y = Cartesian3.dot(this._yAxis, v);

            if (!defined(result)) {
                return new Cartesian2(x, y);
            }
            result.x = x;
            result.y = y;
            return result;
        }
        return undefined;
    };

    /**
     * Computes the projection of the provided 3D positions onto the 2D plane (where possible), radially outward from the global origin.
     * The resulting array may be shorter than the input array - if a single projection is impossible it will not be included.
     *
     * @see EllipsoidTangentPlane.projectPointOntoPlane
     *
     * @param {Cartesian3[]} cartesians The array of points to project.
     * @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results.
     * @returns {Cartesian2[]} The modified result parameter or a new array of Cartesian2 instances if none was provided.
     */
    EllipsoidTangentPlane.prototype.projectPointsOntoPlane = function(cartesians, result) {
                if (!defined(cartesians)) {
            throw new DeveloperError('cartesians is required.');
        }
        
        if (!defined(result)) {
            result = [];
        }

        var count = 0;
        var length = cartesians.length;
        for ( var i = 0; i < length; i++) {
            var p = this.projectPointOntoPlane(cartesians[i], result[count]);
            if (defined(p)) {
                result[count] = p;
                count++;
            }
        }
        result.length = count;
        return result;
    };

    /**
     * Computes the projection of the provided 3D position onto the 2D plane, along the plane normal.
     *
     * @param {Cartesian3} cartesian The point to project.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if none was provided.
     */
    EllipsoidTangentPlane.prototype.projectPointToNearestOnPlane = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian2();
        }

        var ray = scratchProjectPointOntoPlaneRay;
        ray.origin = cartesian;
        Cartesian3.clone(this._plane.normal, ray.direction);

        var intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
        if (!defined(intersectionPoint)) {
            Cartesian3.negate(ray.direction, ray.direction);
            intersectionPoint = IntersectionTests.rayPlane(ray, this._plane, scratchProjectPointOntoPlaneCartesian3);
        }

        var v = Cartesian3.subtract(intersectionPoint, this._origin, intersectionPoint);
        var x = Cartesian3.dot(this._xAxis, v);
        var y = Cartesian3.dot(this._yAxis, v);

        result.x = x;
        result.y = y;
        return result;
    };

    /**
     * Computes the projection of the provided 3D positions onto the 2D plane, along the plane normal.
     *
     * @see EllipsoidTangentPlane.projectPointToNearestOnPlane
     *
     * @param {Cartesian3[]} cartesians The array of points to project.
     * @param {Cartesian2[]} [result] The array of Cartesian2 instances onto which to store results.
     * @returns {Cartesian2[]} The modified result parameter or a new array of Cartesian2 instances if none was provided. This will have the same length as <code>cartesians</code>.
     */
    EllipsoidTangentPlane.prototype.projectPointsToNearestOnPlane = function(cartesians, result) {
                if (!defined(cartesians)) {
            throw new DeveloperError('cartesians is required.');
        }
        
        if (!defined(result)) {
            result = [];
        }

        var length = cartesians.length;
        result.length = length;
        for (var i = 0; i < length; i++) {
            result[i] = this.projectPointToNearestOnPlane(cartesians[i], result[i]);
        }
        return result;
    };

    var projectPointsOntoEllipsoidScratch = new Cartesian3();
    /**
     * Computes the projection of the provided 2D positions onto the 3D ellipsoid.
     *
     * @param {Cartesian2[]} cartesians The array of points to project.
     * @param {Cartesian3[]} [result] The array of Cartesian3 instances onto which to store results.
     * @returns {Cartesian3[]} The modified result parameter or a new array of Cartesian3 instances if none was provided.
     */
    EllipsoidTangentPlane.prototype.projectPointsOntoEllipsoid = function(cartesians, result) {
                if (!defined(cartesians)) {
            throw new DeveloperError('cartesians is required.');
        }
        
        var length = cartesians.length;
        if (!defined(result)) {
            result = new Array(length);
        } else {
            result.length = length;
        }

        var ellipsoid = this._ellipsoid;
        var origin = this._origin;
        var xAxis = this._xAxis;
        var yAxis = this._yAxis;
        var tmp = projectPointsOntoEllipsoidScratch;

        for ( var i = 0; i < length; ++i) {
            var position = cartesians[i];
            Cartesian3.multiplyByScalar(xAxis, position.x, tmp);
            if (!defined(result[i])) {
                result[i] = new Cartesian3();
            }
            var point = Cartesian3.add(origin, tmp, result[i]);
            Cartesian3.multiplyByScalar(yAxis, position.y, tmp);
            Cartesian3.add(point, tmp, point);
            ellipsoid.scaleToGeocentricSurface(point, point);
        }

        return result;
    };

    return EllipsoidTangentPlane;
});

/*global define*/
define('Core/OrientedBoundingBox',[
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './EllipsoidTangentPlane',
        './Intersect',
        './Interval',
        './Math',
        './Matrix3',
        './Plane',
        './Rectangle'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        EllipsoidTangentPlane,
        Intersect,
        Interval,
        CesiumMath,
        Matrix3,
        Plane,
        Rectangle) {
    'use strict';

    /**
     * Creates an instance of an OrientedBoundingBox.
     * An OrientedBoundingBox of some object is a closed and convex cuboid. It can provide a tighter bounding volume than {@link BoundingSphere} or {@link AxisAlignedBoundingBox} in many cases.
     * @alias OrientedBoundingBox
     * @constructor
     *
     * @param {Cartesian3} [center=Cartesian3.ZERO] The center of the box.
     * @param {Matrix3} [halfAxes=Matrix3.ZERO] The three orthogonal half-axes of the bounding box.
     *                                          Equivalently, the transformation matrix, to rotate and scale a 2x2x2
     *                                          cube centered at the origin.
     *
     *
     * @example
     * // Create an OrientedBoundingBox using a transformation matrix, a position where the box will be translated, and a scale.
     * var center = new Cesium.Cartesian3(1.0, 0.0, 0.0);
     * var halfAxes = Cesium.Matrix3.fromScale(new Cesium.Cartesian3(1.0, 3.0, 2.0), new Cesium.Matrix3());
     *
     * var obb = new Cesium.OrientedBoundingBox(center, halfAxes);
     *
     * @see BoundingSphere
     * @see BoundingRectangle
     */
    function OrientedBoundingBox(center, halfAxes) {
        /**
         * The center of the box.
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO));
        /**
         * The transformation matrix, to rotate the box to the right position.
         * @type {Matrix3}
         * @default {@link Matrix3.IDENTITY}
         */
        this.halfAxes = Matrix3.clone(defaultValue(halfAxes, Matrix3.ZERO));
    }

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchCartesian4 = new Cartesian3();
    var scratchCartesian5 = new Cartesian3();
    var scratchCartesian6 = new Cartesian3();
    var scratchCovarianceResult = new Matrix3();
    var scratchEigenResult = {
        unitary : new Matrix3(),
        diagonal : new Matrix3()
    };

    /**
     * Computes an instance of an OrientedBoundingBox of the given positions.
     * This is an implementation of Stefan Gottschalk's Collision Queries using Oriented Bounding Boxes solution (PHD thesis).
     * Reference: http://gamma.cs.unc.edu/users/gottschalk/main.pdf
     *
     * @param {Cartesian3[]} positions List of {@link Cartesian3} points that the bounding box will enclose.
     * @param {OrientedBoundingBox} [result] The object onto which to store the result.
     * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
     *
     * @example
     * // Compute an object oriented bounding box enclosing two points.
     * var box = Cesium.OrientedBoundingBox.fromPoints([new Cesium.Cartesian3(2, 0, 0), new Cesium.Cartesian3(-2, 0, 0)]);
     */
    OrientedBoundingBox.fromPoints = function(positions, result) {
        if (!defined(result)) {
            result = new OrientedBoundingBox();
        }

        if (!defined(positions) || positions.length === 0) {
            result.halfAxes = Matrix3.ZERO;
            result.center = Cartesian3.ZERO;
            return result;
        }

        var i;
        var length = positions.length;

        var meanPoint = Cartesian3.clone(positions[0], scratchCartesian1);
        for (i = 1; i < length; i++) {
            Cartesian3.add(meanPoint, positions[i], meanPoint);
        }
        var invLength = 1.0 / length;
        Cartesian3.multiplyByScalar(meanPoint, invLength, meanPoint);

        var exx = 0.0;
        var exy = 0.0;
        var exz = 0.0;
        var eyy = 0.0;
        var eyz = 0.0;
        var ezz = 0.0;
        var p;

        for (i = 0; i < length; i++) {
            p = Cartesian3.subtract(positions[i], meanPoint, scratchCartesian2);
            exx += p.x * p.x;
            exy += p.x * p.y;
            exz += p.x * p.z;
            eyy += p.y * p.y;
            eyz += p.y * p.z;
            ezz += p.z * p.z;
        }

        exx *= invLength;
        exy *= invLength;
        exz *= invLength;
        eyy *= invLength;
        eyz *= invLength;
        ezz *= invLength;

        var covarianceMatrix = scratchCovarianceResult;
        covarianceMatrix[0] = exx;
        covarianceMatrix[1] = exy;
        covarianceMatrix[2] = exz;
        covarianceMatrix[3] = exy;
        covarianceMatrix[4] = eyy;
        covarianceMatrix[5] = eyz;
        covarianceMatrix[6] = exz;
        covarianceMatrix[7] = eyz;
        covarianceMatrix[8] = ezz;

        var eigenDecomposition = Matrix3.computeEigenDecomposition(covarianceMatrix, scratchEigenResult);
        var rotation = Matrix3.clone(eigenDecomposition.unitary, result.halfAxes);

        var v1 = Matrix3.getColumn(rotation, 0, scratchCartesian4);
        var v2 = Matrix3.getColumn(rotation, 1, scratchCartesian5);
        var v3 = Matrix3.getColumn(rotation, 2, scratchCartesian6);

        var u1 = -Number.MAX_VALUE;
        var u2 = -Number.MAX_VALUE;
        var u3 = -Number.MAX_VALUE;
        var l1 = Number.MAX_VALUE;
        var l2 = Number.MAX_VALUE;
        var l3 = Number.MAX_VALUE;

        for (i = 0; i < length; i++) {
            p = positions[i];
            u1 = Math.max(Cartesian3.dot(v1, p), u1);
            u2 = Math.max(Cartesian3.dot(v2, p), u2);
            u3 = Math.max(Cartesian3.dot(v3, p), u3);

            l1 = Math.min(Cartesian3.dot(v1, p), l1);
            l2 = Math.min(Cartesian3.dot(v2, p), l2);
            l3 = Math.min(Cartesian3.dot(v3, p), l3);
        }

        v1 = Cartesian3.multiplyByScalar(v1, 0.5 * (l1 + u1), v1);
        v2 = Cartesian3.multiplyByScalar(v2, 0.5 * (l2 + u2), v2);
        v3 = Cartesian3.multiplyByScalar(v3, 0.5 * (l3 + u3), v3);

        var center = Cartesian3.add(v1, v2, result.center);
        center = Cartesian3.add(center, v3, center);

        var scale = scratchCartesian3;
        scale.x = u1 - l1;
        scale.y = u2 - l2;
        scale.z = u3 - l3;
        Cartesian3.multiplyByScalar(scale, 0.5, scale);
        Matrix3.multiplyByScale(result.halfAxes, scale, result.halfAxes);

        return result;
    };

    var scratchOffset = new Cartesian3();
    var scratchScale = new Cartesian3();
    /**
     * Computes an OrientedBoundingBox given extents in the east-north-up space of the tangent plane.
     *
     * @param {Plane} tangentPlane The tangent place corresponding to east-north-up.
     * @param {Number} minimumX Minimum X extent in tangent plane space.
     * @param {Number} maximumX Maximum X extent in tangent plane space.
     * @param {Number} minimumY Minimum Y extent in tangent plane space.
     * @param {Number} maximumY Maximum Y extent in tangent plane space.
     * @param {Number} minimumZ Minimum Z extent in tangent plane space.
     * @param {Number} maximumZ Maximum Z extent in tangent plane space.
     * @param {OrientedBoundingBox} [result] The object onto which to store the result.
     * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
     */
    function fromTangentPlaneExtents(tangentPlane, minimumX, maximumX, minimumY, maximumY, minimumZ, maximumZ, result) {
                if (!defined(minimumX) ||
            !defined(maximumX) ||
            !defined(minimumY) ||
            !defined(maximumY) ||
            !defined(minimumZ) ||
            !defined(maximumZ)) {
            throw new DeveloperError('all extents (minimum/maximum X/Y/Z) are required.');
        }
        
        if (!defined(result)) {
            result = new OrientedBoundingBox();
        }

        var halfAxes = result.halfAxes;
        Matrix3.setColumn(halfAxes, 0, tangentPlane.xAxis, halfAxes);
        Matrix3.setColumn(halfAxes, 1, tangentPlane.yAxis, halfAxes);
        Matrix3.setColumn(halfAxes, 2, tangentPlane.zAxis, halfAxes);

        var centerOffset = scratchOffset;
        centerOffset.x = (minimumX + maximumX) / 2.0;
        centerOffset.y = (minimumY + maximumY) / 2.0;
        centerOffset.z = (minimumZ + maximumZ) / 2.0;

        var scale = scratchScale;
        scale.x = (maximumX - minimumX) / 2.0;
        scale.y = (maximumY - minimumY) / 2.0;
        scale.z = (maximumZ - minimumZ) / 2.0;

        var center = result.center;
        centerOffset = Matrix3.multiplyByVector(halfAxes, centerOffset, centerOffset);
        Cartesian3.add(tangentPlane.origin, centerOffset, center);
        Matrix3.multiplyByScale(halfAxes, scale, halfAxes);

        return result;
    }

    var scratchRectangleCenterCartographic = new Cartographic();
    var scratchRectangleCenter = new Cartesian3();
    var perimeterCartographicScratch = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
    var perimeterCartesianScratch = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
    var perimeterProjectedScratch = [new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2(), new Cartesian2()];
    /**
     * Computes an OrientedBoundingBox that bounds a {@link Rectangle} on the surface of an {@link Ellipsoid}.
     * There are no guarantees about the orientation of the bounding box.
     *
     * @param {Rectangle} rectangle The cartographic rectangle on the surface of the ellipsoid.
     * @param {Number} [minimumHeight=0.0] The minimum height (elevation) within the tile.
     * @param {Number} [maximumHeight=0.0] The maximum height (elevation) within the tile.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle is defined.
     * @param {OrientedBoundingBox} [result] The object onto which to store the result.
     * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided.
     *
     * @exception {DeveloperError} rectangle.width must be between 0 and pi.
     * @exception {DeveloperError} rectangle.height must be between 0 and pi.
     * @exception {DeveloperError} ellipsoid must be an ellipsoid of revolution (<code>radii.x == radii.y</code>)
     */
    OrientedBoundingBox.fromRectangle = function(rectangle, minimumHeight, maximumHeight, ellipsoid, result) {
                if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required');
        }
        if (rectangle.width < 0.0 || rectangle.width > CesiumMath.PI) {
            throw new DeveloperError('Rectangle width must be between 0 and pi');
        }
        if (rectangle.height < 0.0 || rectangle.height > CesiumMath.PI) {
            throw new DeveloperError('Rectangle height must be between 0 and pi');
        }
        if (defined(ellipsoid) && !CesiumMath.equalsEpsilon(ellipsoid.radii.x, ellipsoid.radii.y, CesiumMath.EPSILON15)) {
            throw new DeveloperError('Ellipsoid must be an ellipsoid of revolution (radii.x == radii.y)');
        }
        
        minimumHeight = defaultValue(minimumHeight, 0.0);
        maximumHeight = defaultValue(maximumHeight, 0.0);
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        // The bounding box will be aligned with the tangent plane at the center of the rectangle.
        var tangentPointCartographic = Rectangle.center(rectangle, scratchRectangleCenterCartographic);
        var tangentPoint = ellipsoid.cartographicToCartesian(tangentPointCartographic, scratchRectangleCenter);
        var tangentPlane = new EllipsoidTangentPlane(tangentPoint, ellipsoid);
        var plane = tangentPlane.plane;

        // Corner arrangement:
        //          N/+y
        //      [0] [1] [2]
        // W/-x [7]     [3] E/+x
        //      [6] [5] [4]
        //          S/-y
        // "C" refers to the central lat/long, which by default aligns with the tangent point (above).
        // If the rectangle spans the equator, CW and CE are instead aligned with the equator.
        var perimeterNW = perimeterCartographicScratch[0];
        var perimeterNC = perimeterCartographicScratch[1];
        var perimeterNE = perimeterCartographicScratch[2];
        var perimeterCE = perimeterCartographicScratch[3];
        var perimeterSE = perimeterCartographicScratch[4];
        var perimeterSC = perimeterCartographicScratch[5];
        var perimeterSW = perimeterCartographicScratch[6];
        var perimeterCW = perimeterCartographicScratch[7];

        var lonCenter = tangentPointCartographic.longitude;
        var latCenter = (rectangle.south < 0.0 && rectangle.north > 0.0) ? 0.0 : tangentPointCartographic.latitude;
        perimeterSW.latitude = perimeterSC.latitude = perimeterSE.latitude = rectangle.south;
        perimeterCW.latitude = perimeterCE.latitude = latCenter;
        perimeterNW.latitude = perimeterNC.latitude = perimeterNE.latitude = rectangle.north;
        perimeterSW.longitude = perimeterCW.longitude = perimeterNW.longitude = rectangle.west;
        perimeterSC.longitude = perimeterNC.longitude = lonCenter;
        perimeterSE.longitude = perimeterCE.longitude = perimeterNE.longitude = rectangle.east;

        // Compute XY extents using the rectangle at maximum height
        perimeterNE.height = perimeterNC.height = perimeterNW.height = perimeterCW.height = perimeterSW.height = perimeterSC.height = perimeterSE.height = perimeterCE.height = maximumHeight;

        ellipsoid.cartographicArrayToCartesianArray(perimeterCartographicScratch, perimeterCartesianScratch);
        tangentPlane.projectPointsToNearestOnPlane(perimeterCartesianScratch, perimeterProjectedScratch);
        // See the `perimeterXX` definitions above for what these are
        var minX = Math.min(perimeterProjectedScratch[6].x, perimeterProjectedScratch[7].x, perimeterProjectedScratch[0].x);
        var maxX = Math.max(perimeterProjectedScratch[2].x, perimeterProjectedScratch[3].x, perimeterProjectedScratch[4].x);
        var minY = Math.min(perimeterProjectedScratch[4].y, perimeterProjectedScratch[5].y, perimeterProjectedScratch[6].y);
        var maxY = Math.max(perimeterProjectedScratch[0].y, perimeterProjectedScratch[1].y, perimeterProjectedScratch[2].y);

        // Compute minimum Z using the rectangle at minimum height
        perimeterNE.height = perimeterNW.height = perimeterSE.height = perimeterSW.height = minimumHeight;
        ellipsoid.cartographicArrayToCartesianArray(perimeterCartographicScratch, perimeterCartesianScratch);
        var minZ = Math.min(Plane.getPointDistance(plane, perimeterCartesianScratch[0]),
                            Plane.getPointDistance(plane, perimeterCartesianScratch[2]),
                            Plane.getPointDistance(plane, perimeterCartesianScratch[4]),
                            Plane.getPointDistance(plane, perimeterCartesianScratch[6]));
        var maxZ = maximumHeight;  // Since the tangent plane touches the surface at height = 0, this is okay

        return fromTangentPlaneExtents(tangentPlane, minX, maxX, minY, maxY, minZ, maxZ, result);
    };

    /**
     * Duplicates a OrientedBoundingBox instance.
     *
     * @param {OrientedBoundingBox} box The bounding box to duplicate.
     * @param {OrientedBoundingBox} [result] The object onto which to store the result.
     * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided. (Returns undefined if box is undefined)
     */
    OrientedBoundingBox.clone = function(box, result) {
        if (!defined(box)) {
            return undefined;
        }

        if (!defined(result)) {
            return new OrientedBoundingBox(box.center, box.halfAxes);
        }

        Cartesian3.clone(box.center, result.center);
        Matrix3.clone(box.halfAxes, result.halfAxes);

        return result;
    };

    /**
     * Determines which side of a plane the oriented bounding box is located.
     *
     * @param {OrientedBoundingBox} box The oriented bounding box to test.
     * @param {Plane} plane The plane to test against.
     * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
     *                      the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
     *                      on the opposite side, and {@link Intersect.INTERSECTING} if the box
     *                      intersects the plane.
     */
    OrientedBoundingBox.intersectPlane = function(box, plane) {
                if (!defined(box)) {
            throw new DeveloperError('box is required.');
        }

        if (!defined(plane)) {
            throw new DeveloperError('plane is required.');
        }
        
        var center = box.center;
        var normal = plane.normal;
        var halfAxes = box.halfAxes;
        var normalX = normal.x, normalY = normal.y, normalZ = normal.z;
        // plane is used as if it is its normal; the first three components are assumed to be normalized
        var radEffective = Math.abs(normalX * halfAxes[Matrix3.COLUMN0ROW0] + normalY * halfAxes[Matrix3.COLUMN0ROW1] + normalZ * halfAxes[Matrix3.COLUMN0ROW2]) +
                           Math.abs(normalX * halfAxes[Matrix3.COLUMN1ROW0] + normalY * halfAxes[Matrix3.COLUMN1ROW1] + normalZ * halfAxes[Matrix3.COLUMN1ROW2]) +
                           Math.abs(normalX * halfAxes[Matrix3.COLUMN2ROW0] + normalY * halfAxes[Matrix3.COLUMN2ROW1] + normalZ * halfAxes[Matrix3.COLUMN2ROW2]);
        var distanceToPlane = Cartesian3.dot(normal, center) + plane.distance;

        if (distanceToPlane <= -radEffective) {
            // The entire box is on the negative side of the plane normal
            return Intersect.OUTSIDE;
        } else if (distanceToPlane >= radEffective) {
            // The entire box is on the positive side of the plane normal
            return Intersect.INSIDE;
        }
        return Intersect.INTERSECTING;
    };

    var scratchCartesianU = new Cartesian3();
    var scratchCartesianV = new Cartesian3();
    var scratchCartesianW = new Cartesian3();
    var scratchPPrime = new Cartesian3();

    /**
     * Computes the estimated distance squared from the closest point on a bounding box to a point.
     *
     * @param {OrientedBoundingBox} box The box.
     * @param {Cartesian3} cartesian The point
     * @returns {Number} The estimated distance squared from the bounding sphere to the point.
     *
     * @example
     * // Sort bounding boxes from back to front
     * boxes.sort(function(a, b) {
     *     return Cesium.OrientedBoundingBox.distanceSquaredTo(b, camera.positionWC) - Cesium.OrientedBoundingBox.distanceSquaredTo(a, camera.positionWC);
     * });
     */
    OrientedBoundingBox.distanceSquaredTo = function(box, cartesian) {
        // See Geometric Tools for Computer Graphics 10.4.2

                if (!defined(box)) {
            throw new DeveloperError('box is required.');
        }
        if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        var offset = Cartesian3.subtract(cartesian, box.center, scratchOffset);

        var halfAxes = box.halfAxes;
        var u = Matrix3.getColumn(halfAxes, 0, scratchCartesianU);
        var v = Matrix3.getColumn(halfAxes, 1, scratchCartesianV);
        var w = Matrix3.getColumn(halfAxes, 2, scratchCartesianW);

        var uHalf = Cartesian3.magnitude(u);
        var vHalf = Cartesian3.magnitude(v);
        var wHalf = Cartesian3.magnitude(w);

        Cartesian3.normalize(u, u);
        Cartesian3.normalize(v, v);
        Cartesian3.normalize(w, w);

        var pPrime = scratchPPrime;
        pPrime.x = Cartesian3.dot(offset, u);
        pPrime.y = Cartesian3.dot(offset, v);
        pPrime.z = Cartesian3.dot(offset, w);

        var distanceSquared = 0.0;
        var d;

        if (pPrime.x < -uHalf) {
            d = pPrime.x + uHalf;
            distanceSquared += d * d;
        } else if (pPrime.x > uHalf) {
            d = pPrime.x - uHalf;
            distanceSquared += d * d;
        }

        if (pPrime.y < -vHalf) {
            d = pPrime.y + vHalf;
            distanceSquared += d * d;
        } else if (pPrime.y > vHalf) {
            d = pPrime.y - vHalf;
            distanceSquared += d * d;
        }

        if (pPrime.z < -wHalf) {
            d = pPrime.z + wHalf;
            distanceSquared += d * d;
        } else if (pPrime.z > wHalf) {
            d = pPrime.z - wHalf;
            distanceSquared += d * d;
        }

        return distanceSquared;
    };

    var scratchCorner = new Cartesian3();
    var scratchToCenter = new Cartesian3();

    /**
     * The distances calculated by the vector from the center of the bounding box to position projected onto direction.
     * <br>
     * If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
     * closest and farthest planes from position that intersect the bounding box.
     *
     * @param {OrientedBoundingBox} box The bounding box to calculate the distance to.
     * @param {Cartesian3} position The position to calculate the distance from.
     * @param {Cartesian3} direction The direction from position.
     * @param {Interval} [result] A Interval to store the nearest and farthest distances.
     * @returns {Interval} The nearest and farthest distances on the bounding box from position in direction.
     */
    OrientedBoundingBox.computePlaneDistances = function(box, position, direction, result) {
                if (!defined(box)) {
            throw new DeveloperError('box is required.');
        }

        if (!defined(position)) {
            throw new DeveloperError('position is required.');
        }

        if (!defined(direction)) {
            throw new DeveloperError('direction is required.');
        }
        
        if (!defined(result)) {
            result = new Interval();
        }

        var minDist = Number.POSITIVE_INFINITY;
        var maxDist = Number.NEGATIVE_INFINITY;

        var center = box.center;
        var halfAxes = box.halfAxes;

        var u = Matrix3.getColumn(halfAxes, 0, scratchCartesianU);
        var v = Matrix3.getColumn(halfAxes, 1, scratchCartesianV);
        var w = Matrix3.getColumn(halfAxes, 2, scratchCartesianW);

        // project first corner
        var corner = Cartesian3.add(u, v, scratchCorner);
        Cartesian3.add(corner, w, corner);
        Cartesian3.add(corner, center, corner);

        var toCenter = Cartesian3.subtract(corner, position, scratchToCenter);
        var mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project second corner
        Cartesian3.add(center, u, corner);
        Cartesian3.add(corner, v, corner);
        Cartesian3.subtract(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project third corner
        Cartesian3.add(center, u, corner);
        Cartesian3.subtract(corner, v, corner);
        Cartesian3.add(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project fourth corner
        Cartesian3.add(center, u, corner);
        Cartesian3.subtract(corner, v, corner);
        Cartesian3.subtract(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project fifth corner
        Cartesian3.subtract(center, u, corner);
        Cartesian3.add(corner, v, corner);
        Cartesian3.add(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project sixth corner
        Cartesian3.subtract(center, u, corner);
        Cartesian3.add(corner, v, corner);
        Cartesian3.subtract(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project seventh corner
        Cartesian3.subtract(center, u, corner);
        Cartesian3.subtract(corner, v, corner);
        Cartesian3.add(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        // project eighth corner
        Cartesian3.subtract(center, u, corner);
        Cartesian3.subtract(corner, v, corner);
        Cartesian3.subtract(corner, w, corner);

        Cartesian3.subtract(corner, position, toCenter);
        mag = Cartesian3.dot(direction, toCenter);

        minDist = Math.min(mag, minDist);
        maxDist = Math.max(mag, maxDist);

        result.start = minDist;
        result.stop = maxDist;
        return result;
    };

    var scratchBoundingSphere = new BoundingSphere();

    /**
     * Determines whether or not a bounding box is hidden from view by the occluder.
     *
     * @param {OrientedBoundingBox} box The bounding box surrounding the occludee object.
     * @param {Occluder} occluder The occluder.
     * @returns {Boolean} <code>true</code> if the box is not visible; otherwise <code>false</code>.
     */
    OrientedBoundingBox.isOccluded = function(box, occluder) {
                if (!defined(box)) {
            throw new DeveloperError('box is required.');
        }
        if (!defined(occluder)) {
            throw new DeveloperError('occluder is required.');
        }
        
        var sphere = BoundingSphere.fromOrientedBoundingBox(box, scratchBoundingSphere);

        return !occluder.isBoundingSphereVisible(sphere);
    };

    /**
     * Determines which side of a plane the oriented bounding box is located.
     *
     * @param {Plane} plane The plane to test against.
     * @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
     *                      the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
     *                      on the opposite side, and {@link Intersect.INTERSECTING} if the box
     *                      intersects the plane.
     */
    OrientedBoundingBox.prototype.intersectPlane = function(plane) {
        return OrientedBoundingBox.intersectPlane(this, plane);
    };

    /**
     * Computes the estimated distance squared from the closest point on a bounding box to a point.
     *
     * @param {Cartesian3} cartesian The point
     * @returns {Number} The estimated distance squared from the bounding sphere to the point.
     *
     * @example
     * // Sort bounding boxes from back to front
     * boxes.sort(function(a, b) {
     *     return b.distanceSquaredTo(camera.positionWC) - a.distanceSquaredTo(camera.positionWC);
     * });
     */
    OrientedBoundingBox.prototype.distanceSquaredTo = function(cartesian) {
        return OrientedBoundingBox.distanceSquaredTo(this, cartesian);
    };

    /**
     * The distances calculated by the vector from the center of the bounding box to position projected onto direction.
     * <br>
     * If you imagine the infinite number of planes with normal direction, this computes the smallest distance to the
     * closest and farthest planes from position that intersect the bounding box.
     *
     * @param {Cartesian3} position The position to calculate the distance from.
     * @param {Cartesian3} direction The direction from position.
     * @param {Interval} [result] A Interval to store the nearest and farthest distances.
     * @returns {Interval} The nearest and farthest distances on the bounding box from position in direction.
     */
    OrientedBoundingBox.prototype.computePlaneDistances = function(position, direction, result) {
        return OrientedBoundingBox.computePlaneDistances(this, position, direction, result);
    };

    /**
     * Determines whether or not a bounding box is hidden from view by the occluder.
     *
     * @param {Occluder} occluder The occluder.
     * @returns {Boolean} <code>true</code> if the sphere is not visible; otherwise <code>false</code>.
     */
    OrientedBoundingBox.prototype.isOccluded = function(occluder) {
        return OrientedBoundingBox.isOccluded(this, occluder);
    };

    /**
     * Compares the provided OrientedBoundingBox componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {OrientedBoundingBox} left The first OrientedBoundingBox.
     * @param {OrientedBoundingBox} right The second OrientedBoundingBox.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    OrientedBoundingBox.equals = function(left, right) {
        return (left === right) ||
                ((defined(left)) &&
                 (defined(right)) &&
                 Cartesian3.equals(left.center, right.center) &&
                 Matrix3.equals(left.halfAxes, right.halfAxes));
    };

    /**
     * Duplicates this OrientedBoundingBox instance.
     *
     * @param {OrientedBoundingBox} [result] The object onto which to store the result.
     * @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
     */
    OrientedBoundingBox.prototype.clone = function(result) {
        return OrientedBoundingBox.clone(this, result);
    };

    /**
     * Compares this OrientedBoundingBox against the provided OrientedBoundingBox componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {OrientedBoundingBox} [right] The right hand side OrientedBoundingBox.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    OrientedBoundingBox.prototype.equals = function(right) {
        return OrientedBoundingBox.equals(this, right);
    };

    return OrientedBoundingBox;
});

/*global define*/
define('Core/AttributeCompression',[
        './Cartesian2',
        './Cartesian3',
        './defined',
        './DeveloperError',
        './Math'
    ], function(
        Cartesian2,
        Cartesian3,
        defined,
        DeveloperError,
        CesiumMath) {
    'use strict';

    /**
     * Attribute compression and decompression functions.
     *
     * @exports AttributeCompression
     *
     * @private
     */
    var AttributeCompression = {};

    /**
     * Encodes a normalized vector into 2 SNORM values in the range of [0-rangeMax] following the 'oct' encoding.
     *
     * Oct encoding is a compact representation of unit length vectors.
     * The 'oct' encoding is described in "A Survey of Efficient Representations of Independent Unit Vectors",
     * Cigolle et al 2014: {@link http://jcgt.org/published/0003/02/01/}
     *
     * @param {Cartesian3} vector The normalized vector to be compressed into 2 component 'oct' encoding.
     * @param {Cartesian2} result The 2 component oct-encoded unit length vector.
     * @param {Number} rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.
     * @returns {Cartesian2} The 2 component oct-encoded unit length vector.
     *
     * @exception {DeveloperError} vector must be normalized.
     *
     * @see AttributeCompression.octDecodeInRange
     */
    AttributeCompression.octEncodeInRange = function(vector, rangeMax, result) {
                if (!defined(vector)) {
            throw new DeveloperError('vector is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        var magSquared = Cartesian3.magnitudeSquared(vector);
        if (Math.abs(magSquared - 1.0) > CesiumMath.EPSILON6) {
            throw new DeveloperError('vector must be normalized.');
        }
        
        result.x = vector.x / (Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z));
        result.y = vector.y / (Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z));
        if (vector.z < 0) {
            var x = result.x;
            var y = result.y;
            result.x = (1.0 - Math.abs(y)) * CesiumMath.signNotZero(x);
            result.y = (1.0 - Math.abs(x)) * CesiumMath.signNotZero(y);
        }

        result.x = CesiumMath.toSNorm(result.x, rangeMax);
        result.y = CesiumMath.toSNorm(result.y, rangeMax);

        return result;
    };

    /**
     * Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding.
     *
     * @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding.
     * @param {Cartesian2} result The 2 byte oct-encoded unit length vector.
     * @returns {Cartesian2} The 2 byte oct-encoded unit length vector.
     *
     * @exception {DeveloperError} vector must be normalized.
     *
     * @see AttributeCompression.octEncodeInRange
     * @see AttributeCompression.octDecode
     */
    AttributeCompression.octEncode = function(vector, result) {
        return AttributeCompression.octEncodeInRange(vector, 255, result);
    };

    /**
     * Decodes a unit-length vector in 'oct' encoding to a normalized 3-component vector.
     *
     * @param {Number} x The x component of the oct-encoded unit length vector.
     * @param {Number} y The y component of the oct-encoded unit length vector.
     * @param {Number} rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.
     * @param {Cartesian3} result The decoded and normalized vector
     * @returns {Cartesian3} The decoded and normalized vector.
     *
     * @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and rangeMax.
     *
     * @see AttributeCompression.octEncodeInRange
     */
    AttributeCompression.octDecodeInRange = function(x, y, rangeMax, result) {
                if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        if (x < 0 || x > rangeMax || y < 0 || y > rangeMax) {
            throw new DeveloperError('x and y must be a signed normalized integer between 0 and ' + rangeMax);
        }
        
        result.x = CesiumMath.fromSNorm(x, rangeMax);
        result.y = CesiumMath.fromSNorm(y, rangeMax);
        result.z = 1.0 - (Math.abs(result.x) + Math.abs(result.y));

        if (result.z < 0.0)
        {
            var oldVX = result.x;
            result.x = (1.0 - Math.abs(result.y)) * CesiumMath.signNotZero(oldVX);
            result.y = (1.0 - Math.abs(oldVX)) * CesiumMath.signNotZero(result.y);
        }

        return Cartesian3.normalize(result, result);
    };

    /**
     * Decodes a unit-length vector in 2 byte 'oct' encoding to a normalized 3-component vector.
     *
     * @param {Number} x The x component of the oct-encoded unit length vector.
     * @param {Number} y The y component of the oct-encoded unit length vector.
     * @param {Cartesian3} result The decoded and normalized vector.
     * @returns {Cartesian3} The decoded and normalized vector.
     *
     * @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and 255.
     *
     * @see AttributeCompression.octDecodeInRange
     */
    AttributeCompression.octDecode = function(x, y, result) {
        return AttributeCompression.octDecodeInRange(x, y, 255, result);
    };

    /**
     * Packs an oct encoded vector into a single floating-point number.
     *
     * @param {Cartesian2} encoded The oct encoded vector.
     * @returns {Number} The oct encoded vector packed into a single float.
     *
     */
    AttributeCompression.octPackFloat = function(encoded) {
                if (!defined(encoded)) {
            throw new DeveloperError('encoded is required.');
        }
                return 256.0 * encoded.x + encoded.y;
    };

    var scratchEncodeCart2 = new Cartesian2();

    /**
     * Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding and
     * stores those values in a single float-point number.
     *
     * @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding.
     * @returns {Number} The 2 byte oct-encoded unit length vector.
     *
     * @exception {DeveloperError} vector must be normalized.
     */
    AttributeCompression.octEncodeFloat = function(vector) {
        AttributeCompression.octEncode(vector, scratchEncodeCart2);
        return AttributeCompression.octPackFloat(scratchEncodeCart2);
    };

    /**
     * Decodes a unit-length vector in 'oct' encoding packed in a floating-point number to a normalized 3-component vector.
     *
     * @param {Number} value The oct-encoded unit length vector stored as a single floating-point number.
     * @param {Cartesian3} result The decoded and normalized vector
     * @returns {Cartesian3} The decoded and normalized vector.
     *
     */
    AttributeCompression.octDecodeFloat = function(value, result) {
                if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }
        
        var temp = value / 256.0;
        var x = Math.floor(temp);
        var y = (temp - x) * 256.0;

        return AttributeCompression.octDecode(x, y, result);
    };

    /**
     * Encodes three normalized vectors into 6 SNORM values in the range of [0-255] following the 'oct' encoding and
     * packs those into two floating-point numbers.
     *
     * @param {Cartesian3} v1 A normalized vector to be compressed.
     * @param {Cartesian3} v2 A normalized vector to be compressed.
     * @param {Cartesian3} v3 A normalized vector to be compressed.
     * @param {Cartesian2} result The 'oct' encoded vectors packed into two floating-point numbers.
     * @returns {Cartesian2} The 'oct' encoded vectors packed into two floating-point numbers.
     *
     */
    AttributeCompression.octPack = function(v1, v2, v3, result) {
                if (!defined(v1)) {
            throw new DeveloperError('v1 is required.');
        }
        if (!defined(v2)) {
            throw new DeveloperError('v2 is required.');
        }
        if (!defined(v3)) {
            throw new DeveloperError('v3 is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var encoded1 = AttributeCompression.octEncodeFloat(v1);
        var encoded2 = AttributeCompression.octEncodeFloat(v2);

        var encoded3 = AttributeCompression.octEncode(v3, scratchEncodeCart2);
        result.x = 65536.0 * encoded3.x + encoded1;
        result.y = 65536.0 * encoded3.y + encoded2;
        return result;
    };

    /**
     * Decodes three unit-length vectors in 'oct' encoding packed into a floating-point number to a normalized 3-component vector.
     *
     * @param {Cartesian2} packed The three oct-encoded unit length vectors stored as two floating-point number.
     * @param {Cartesian3} v1 One decoded and normalized vector.
     * @param {Cartesian3} v2 One decoded and normalized vector.
     * @param {Cartesian3} v3 One decoded and normalized vector.
     */
    AttributeCompression.octUnpack = function(packed, v1, v2, v3) {
                if (!defined(packed)) {
            throw new DeveloperError('packed is required.');
        }
        if (!defined(v1)) {
            throw new DeveloperError('v1 is required.');
        }
        if (!defined(v2)) {
            throw new DeveloperError('v2 is required.');
        }
        if (!defined(v3)) {
            throw new DeveloperError('v3 is required.');
        }
        
        var temp = packed.x / 65536.0;
        var x = Math.floor(temp);
        var encodedFloat1 = (temp - x) * 65536.0;

        temp = packed.y / 65536.0;
        var y = Math.floor(temp);
        var encodedFloat2 = (temp - y) * 65536.0;

        AttributeCompression.octDecodeFloat(encodedFloat1, v1);
        AttributeCompression.octDecodeFloat(encodedFloat2, v2);
        AttributeCompression.octDecode(x, y, v3);
    };

    /**
     * Pack texture coordinates into a single float. The texture coordinates will only preserve 12 bits of precision.
     *
     * @param {Cartesian2} textureCoordinates The texture coordinates to compress.  Both coordinates must be in the range 0.0-1.0.
     * @returns {Number} The packed texture coordinates.
     *
     */
    AttributeCompression.compressTextureCoordinates = function(textureCoordinates) {
                if (!defined(textureCoordinates)) {
            throw new DeveloperError('textureCoordinates is required.');
        }
        
        // Move x and y to the range 0-4095;
        var x = (textureCoordinates.x * 4095.0) | 0;
        var y = (textureCoordinates.y * 4095.0) | 0;
        return 4096.0 * x + y;
    };

    /**
     * Decompresses texture coordinates that were packed into a single float.
     *
     * @param {Number} compressed The compressed texture coordinates.
     * @param {Cartesian2} result The decompressed texture coordinates.
     * @returns {Cartesian2} The modified result parameter.
     *
     */
    AttributeCompression.decompressTextureCoordinates = function(compressed, result) {
                if (!defined(compressed)) {
            throw new DeveloperError('compressed is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var temp = compressed / 4096.0;
        var xZeroTo4095 = Math.floor(temp);
        result.x = xZeroTo4095 / 4095.0;
        result.y = (compressed - xZeroTo4095 * 4096) / 4095;
        return result;
    };

    return AttributeCompression;
});

/*global define*/
define('Core/WebGLConstants',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Enum containing WebGL Constant values by name.
     * for use without an active WebGL context, or in cases where certain constants are unavailable using the WebGL context
     * (For example, in [Safari 9]{@link https://github.com/AnalyticalGraphicsInc/cesium/issues/2989}).
     *
     * These match the constants from the [WebGL 1.0]{@link https://www.khronos.org/registry/webgl/specs/latest/1.0/}
     * and [WebGL 2.0]{@link https://www.khronos.org/registry/webgl/specs/latest/2.0/}
     * specifications.
     *
     * @exports WebGLConstants
     */
    var WebGLConstants = {
        DEPTH_BUFFER_BIT : 0x00000100,
        STENCIL_BUFFER_BIT : 0x00000400,
        COLOR_BUFFER_BIT : 0x00004000,
        POINTS : 0x0000,
        LINES : 0x0001,
        LINE_LOOP : 0x0002,
        LINE_STRIP : 0x0003,
        TRIANGLES : 0x0004,
        TRIANGLE_STRIP : 0x0005,
        TRIANGLE_FAN : 0x0006,
        ZERO : 0,
        ONE : 1,
        SRC_COLOR : 0x0300,
        ONE_MINUS_SRC_COLOR : 0x0301,
        SRC_ALPHA : 0x0302,
        ONE_MINUS_SRC_ALPHA : 0x0303,
        DST_ALPHA : 0x0304,
        ONE_MINUS_DST_ALPHA : 0x0305,
        DST_COLOR : 0x0306,
        ONE_MINUS_DST_COLOR : 0x0307,
        SRC_ALPHA_SATURATE : 0x0308,
        FUNC_ADD : 0x8006,
        BLEND_EQUATION : 0x8009,
        BLEND_EQUATION_RGB : 0x8009, // same as BLEND_EQUATION
        BLEND_EQUATION_ALPHA : 0x883D,
        FUNC_SUBTRACT : 0x800A,
        FUNC_REVERSE_SUBTRACT : 0x800B,
        BLEND_DST_RGB : 0x80C8,
        BLEND_SRC_RGB : 0x80C9,
        BLEND_DST_ALPHA : 0x80CA,
        BLEND_SRC_ALPHA : 0x80CB,
        CONSTANT_COLOR : 0x8001,
        ONE_MINUS_CONSTANT_COLOR : 0x8002,
        CONSTANT_ALPHA : 0x8003,
        ONE_MINUS_CONSTANT_ALPHA : 0x8004,
        BLEND_COLOR : 0x8005,
        ARRAY_BUFFER : 0x8892,
        ELEMENT_ARRAY_BUFFER : 0x8893,
        ARRAY_BUFFER_BINDING : 0x8894,
        ELEMENT_ARRAY_BUFFER_BINDING : 0x8895,
        STREAM_DRAW : 0x88E0,
        STATIC_DRAW : 0x88E4,
        DYNAMIC_DRAW : 0x88E8,
        BUFFER_SIZE : 0x8764,
        BUFFER_USAGE : 0x8765,
        CURRENT_VERTEX_ATTRIB : 0x8626,
        FRONT : 0x0404,
        BACK : 0x0405,
        FRONT_AND_BACK : 0x0408,
        CULL_FACE : 0x0B44,
        BLEND : 0x0BE2,
        DITHER : 0x0BD0,
        STENCIL_TEST : 0x0B90,
        DEPTH_TEST : 0x0B71,
        SCISSOR_TEST : 0x0C11,
        POLYGON_OFFSET_FILL : 0x8037,
        SAMPLE_ALPHA_TO_COVERAGE : 0x809E,
        SAMPLE_COVERAGE : 0x80A0,
        NO_ERROR : 0,
        INVALID_ENUM : 0x0500,
        INVALID_VALUE : 0x0501,
        INVALID_OPERATION : 0x0502,
        OUT_OF_MEMORY : 0x0505,
        CW : 0x0900,
        CCW : 0x0901,
        LINE_WIDTH : 0x0B21,
        ALIASED_POINT_SIZE_RANGE : 0x846D,
        ALIASED_LINE_WIDTH_RANGE : 0x846E,
        CULL_FACE_MODE : 0x0B45,
        FRONT_FACE : 0x0B46,
        DEPTH_RANGE : 0x0B70,
        DEPTH_WRITEMASK : 0x0B72,
        DEPTH_CLEAR_VALUE : 0x0B73,
        DEPTH_FUNC : 0x0B74,
        STENCIL_CLEAR_VALUE : 0x0B91,
        STENCIL_FUNC : 0x0B92,
        STENCIL_FAIL : 0x0B94,
        STENCIL_PASS_DEPTH_FAIL : 0x0B95,
        STENCIL_PASS_DEPTH_PASS : 0x0B96,
        STENCIL_REF : 0x0B97,
        STENCIL_VALUE_MASK : 0x0B93,
        STENCIL_WRITEMASK : 0x0B98,
        STENCIL_BACK_FUNC : 0x8800,
        STENCIL_BACK_FAIL : 0x8801,
        STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802,
        STENCIL_BACK_PASS_DEPTH_PASS : 0x8803,
        STENCIL_BACK_REF : 0x8CA3,
        STENCIL_BACK_VALUE_MASK : 0x8CA4,
        STENCIL_BACK_WRITEMASK : 0x8CA5,
        VIEWPORT : 0x0BA2,
        SCISSOR_BOX : 0x0C10,
        COLOR_CLEAR_VALUE : 0x0C22,
        COLOR_WRITEMASK : 0x0C23,
        UNPACK_ALIGNMENT : 0x0CF5,
        PACK_ALIGNMENT : 0x0D05,
        MAX_TEXTURE_SIZE : 0x0D33,
        MAX_VIEWPORT_DIMS : 0x0D3A,
        SUBPIXEL_BITS : 0x0D50,
        RED_BITS : 0x0D52,
        GREEN_BITS : 0x0D53,
        BLUE_BITS : 0x0D54,
        ALPHA_BITS : 0x0D55,
        DEPTH_BITS : 0x0D56,
        STENCIL_BITS : 0x0D57,
        POLYGON_OFFSET_UNITS : 0x2A00,
        POLYGON_OFFSET_FACTOR : 0x8038,
        TEXTURE_BINDING_2D : 0x8069,
        SAMPLE_BUFFERS : 0x80A8,
        SAMPLES : 0x80A9,
        SAMPLE_COVERAGE_VALUE : 0x80AA,
        SAMPLE_COVERAGE_INVERT : 0x80AB,
        COMPRESSED_TEXTURE_FORMATS : 0x86A3,
        DONT_CARE : 0x1100,
        FASTEST : 0x1101,
        NICEST : 0x1102,
        GENERATE_MIPMAP_HINT : 0x8192,
        BYTE : 0x1400,
        UNSIGNED_BYTE : 0x1401,
        SHORT : 0x1402,
        UNSIGNED_SHORT : 0x1403,
        INT : 0x1404,
        UNSIGNED_INT : 0x1405,
        FLOAT : 0x1406,
        DEPTH_COMPONENT : 0x1902,
        ALPHA : 0x1906,
        RGB : 0x1907,
        RGBA : 0x1908,
        LUMINANCE : 0x1909,
        LUMINANCE_ALPHA : 0x190A,
        UNSIGNED_SHORT_4_4_4_4 : 0x8033,
        UNSIGNED_SHORT_5_5_5_1 : 0x8034,
        UNSIGNED_SHORT_5_6_5 : 0x8363,
        FRAGMENT_SHADER : 0x8B30,
        VERTEX_SHADER : 0x8B31,
        MAX_VERTEX_ATTRIBS : 0x8869,
        MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB,
        MAX_VARYING_VECTORS : 0x8DFC,
        MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D,
        MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C,
        MAX_TEXTURE_IMAGE_UNITS : 0x8872,
        MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD,
        SHADER_TYPE : 0x8B4F,
        DELETE_STATUS : 0x8B80,
        LINK_STATUS : 0x8B82,
        VALIDATE_STATUS : 0x8B83,
        ATTACHED_SHADERS : 0x8B85,
        ACTIVE_UNIFORMS : 0x8B86,
        ACTIVE_ATTRIBUTES : 0x8B89,
        SHADING_LANGUAGE_VERSION : 0x8B8C,
        CURRENT_PROGRAM : 0x8B8D,
        NEVER : 0x0200,
        LESS : 0x0201,
        EQUAL : 0x0202,
        LEQUAL : 0x0203,
        GREATER : 0x0204,
        NOTEQUAL : 0x0205,
        GEQUAL : 0x0206,
        ALWAYS : 0x0207,
        KEEP : 0x1E00,
        REPLACE : 0x1E01,
        INCR : 0x1E02,
        DECR : 0x1E03,
        INVERT : 0x150A,
        INCR_WRAP : 0x8507,
        DECR_WRAP : 0x8508,
        VENDOR : 0x1F00,
        RENDERER : 0x1F01,
        VERSION : 0x1F02,
        NEAREST : 0x2600,
        LINEAR : 0x2601,
        NEAREST_MIPMAP_NEAREST : 0x2700,
        LINEAR_MIPMAP_NEAREST : 0x2701,
        NEAREST_MIPMAP_LINEAR : 0x2702,
        LINEAR_MIPMAP_LINEAR : 0x2703,
        TEXTURE_MAG_FILTER : 0x2800,
        TEXTURE_MIN_FILTER : 0x2801,
        TEXTURE_WRAP_S : 0x2802,
        TEXTURE_WRAP_T : 0x2803,
        TEXTURE_2D : 0x0DE1,
        TEXTURE : 0x1702,
        TEXTURE_CUBE_MAP : 0x8513,
        TEXTURE_BINDING_CUBE_MAP : 0x8514,
        TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515,
        TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516,
        TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517,
        TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518,
        TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519,
        TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A,
        MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C,
        TEXTURE0 : 0x84C0,
        TEXTURE1 : 0x84C1,
        TEXTURE2 : 0x84C2,
        TEXTURE3 : 0x84C3,
        TEXTURE4 : 0x84C4,
        TEXTURE5 : 0x84C5,
        TEXTURE6 : 0x84C6,
        TEXTURE7 : 0x84C7,
        TEXTURE8 : 0x84C8,
        TEXTURE9 : 0x84C9,
        TEXTURE10 : 0x84CA,
        TEXTURE11 : 0x84CB,
        TEXTURE12 : 0x84CC,
        TEXTURE13 : 0x84CD,
        TEXTURE14 : 0x84CE,
        TEXTURE15 : 0x84CF,
        TEXTURE16 : 0x84D0,
        TEXTURE17 : 0x84D1,
        TEXTURE18 : 0x84D2,
        TEXTURE19 : 0x84D3,
        TEXTURE20 : 0x84D4,
        TEXTURE21 : 0x84D5,
        TEXTURE22 : 0x84D6,
        TEXTURE23 : 0x84D7,
        TEXTURE24 : 0x84D8,
        TEXTURE25 : 0x84D9,
        TEXTURE26 : 0x84DA,
        TEXTURE27 : 0x84DB,
        TEXTURE28 : 0x84DC,
        TEXTURE29 : 0x84DD,
        TEXTURE30 : 0x84DE,
        TEXTURE31 : 0x84DF,
        ACTIVE_TEXTURE : 0x84E0,
        REPEAT : 0x2901,
        CLAMP_TO_EDGE : 0x812F,
        MIRRORED_REPEAT : 0x8370,
        FLOAT_VEC2 : 0x8B50,
        FLOAT_VEC3 : 0x8B51,
        FLOAT_VEC4 : 0x8B52,
        INT_VEC2 : 0x8B53,
        INT_VEC3 : 0x8B54,
        INT_VEC4 : 0x8B55,
        BOOL : 0x8B56,
        BOOL_VEC2 : 0x8B57,
        BOOL_VEC3 : 0x8B58,
        BOOL_VEC4 : 0x8B59,
        FLOAT_MAT2 : 0x8B5A,
        FLOAT_MAT3 : 0x8B5B,
        FLOAT_MAT4 : 0x8B5C,
        SAMPLER_2D : 0x8B5E,
        SAMPLER_CUBE : 0x8B60,
        VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622,
        VERTEX_ATTRIB_ARRAY_SIZE : 0x8623,
        VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624,
        VERTEX_ATTRIB_ARRAY_TYPE : 0x8625,
        VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A,
        VERTEX_ATTRIB_ARRAY_POINTER : 0x8645,
        VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F,
        IMPLEMENTATION_COLOR_READ_TYPE : 0x8B9A,
        IMPLEMENTATION_COLOR_READ_FORMAT : 0x8B9B,
        COMPILE_STATUS : 0x8B81,
        LOW_FLOAT : 0x8DF0,
        MEDIUM_FLOAT : 0x8DF1,
        HIGH_FLOAT : 0x8DF2,
        LOW_INT : 0x8DF3,
        MEDIUM_INT : 0x8DF4,
        HIGH_INT : 0x8DF5,
        FRAMEBUFFER : 0x8D40,
        RENDERBUFFER : 0x8D41,
        RGBA4 : 0x8056,
        RGB5_A1 : 0x8057,
        RGB565 : 0x8D62,
        DEPTH_COMPONENT16 : 0x81A5,
        STENCIL_INDEX : 0x1901,
        STENCIL_INDEX8 : 0x8D48,
        DEPTH_STENCIL : 0x84F9,
        RENDERBUFFER_WIDTH : 0x8D42,
        RENDERBUFFER_HEIGHT : 0x8D43,
        RENDERBUFFER_INTERNAL_FORMAT : 0x8D44,
        RENDERBUFFER_RED_SIZE : 0x8D50,
        RENDERBUFFER_GREEN_SIZE : 0x8D51,
        RENDERBUFFER_BLUE_SIZE : 0x8D52,
        RENDERBUFFER_ALPHA_SIZE : 0x8D53,
        RENDERBUFFER_DEPTH_SIZE : 0x8D54,
        RENDERBUFFER_STENCIL_SIZE : 0x8D55,
        FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0,
        FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1,
        FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2,
        FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3,
        COLOR_ATTACHMENT0 : 0x8CE0,
        DEPTH_ATTACHMENT : 0x8D00,
        STENCIL_ATTACHMENT : 0x8D20,
        DEPTH_STENCIL_ATTACHMENT : 0x821A,
        NONE : 0,
        FRAMEBUFFER_COMPLETE : 0x8CD5,
        FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6,
        FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7,
        FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9,
        FRAMEBUFFER_UNSUPPORTED : 0x8CDD,
        FRAMEBUFFER_BINDING : 0x8CA6,
        RENDERBUFFER_BINDING : 0x8CA7,
        MAX_RENDERBUFFER_SIZE : 0x84E8,
        INVALID_FRAMEBUFFER_OPERATION : 0x0506,
        UNPACK_FLIP_Y_WEBGL : 0x9240,
        UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241,
        CONTEXT_LOST_WEBGL : 0x9242,
        UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243,
        BROWSER_DEFAULT_WEBGL : 0x9244,

        // WEBGL_compressed_texture_s3tc
        COMPRESSED_RGB_S3TC_DXT1_EXT : 0x83F0,
        COMPRESSED_RGBA_S3TC_DXT1_EXT : 0x83F1,
        COMPRESSED_RGBA_S3TC_DXT3_EXT : 0x83F2,
        COMPRESSED_RGBA_S3TC_DXT5_EXT : 0x83F3,

        // WEBGL_compressed_texture_pvrtc
        COMPRESSED_RGB_PVRTC_4BPPV1_IMG : 0x8C00,
        COMPRESSED_RGB_PVRTC_2BPPV1_IMG : 0x8C01,
        COMPRESSED_RGBA_PVRTC_4BPPV1_IMG : 0x8C02,
        COMPRESSED_RGBA_PVRTC_2BPPV1_IMG : 0x8C03,

        // WEBGL_compressed_texture_etc1
        COMPRESSED_RGB_ETC1_WEBGL : 0x8D64,

        // Desktop OpenGL
        DOUBLE : 0x140A,

        // WebGL 2
        READ_BUFFER : 0x0C02,
        UNPACK_ROW_LENGTH : 0x0CF2,
        UNPACK_SKIP_ROWS : 0x0CF3,
        UNPACK_SKIP_PIXELS : 0x0CF4,
        PACK_ROW_LENGTH : 0x0D02,
        PACK_SKIP_ROWS : 0x0D03,
        PACK_SKIP_PIXELS : 0x0D04,
        COLOR : 0x1800,
        DEPTH : 0x1801,
        STENCIL : 0x1802,
        RED : 0x1903,
        RGB8 : 0x8051,
        RGBA8 : 0x8058,
        RGB10_A2 : 0x8059,
        TEXTURE_BINDING_3D : 0x806A,
        UNPACK_SKIP_IMAGES : 0x806D,
        UNPACK_IMAGE_HEIGHT : 0x806E,
        TEXTURE_3D : 0x806F,
        TEXTURE_WRAP_R : 0x8072,
        MAX_3D_TEXTURE_SIZE : 0x8073,
        UNSIGNED_INT_2_10_10_10_REV : 0x8368,
        MAX_ELEMENTS_VERTICES : 0x80E8,
        MAX_ELEMENTS_INDICES : 0x80E9,
        TEXTURE_MIN_LOD : 0x813A,
        TEXTURE_MAX_LOD : 0x813B,
        TEXTURE_BASE_LEVEL : 0x813C,
        TEXTURE_MAX_LEVEL : 0x813D,
        MIN : 0x8007,
        MAX : 0x8008,
        DEPTH_COMPONENT24 : 0x81A6,
        MAX_TEXTURE_LOD_BIAS : 0x84FD,
        TEXTURE_COMPARE_MODE : 0x884C,
        TEXTURE_COMPARE_FUNC : 0x884D,
        CURRENT_QUERY : 0x8865,
        QUERY_RESULT : 0x8866,
        QUERY_RESULT_AVAILABLE : 0x8867,
        STREAM_READ : 0x88E1,
        STREAM_COPY : 0x88E2,
        STATIC_READ : 0x88E5,
        STATIC_COPY : 0x88E6,
        DYNAMIC_READ : 0x88E9,
        DYNAMIC_COPY : 0x88EA,
        MAX_DRAW_BUFFERS : 0x8824,
        DRAW_BUFFER0 : 0x8825,
        DRAW_BUFFER1 : 0x8826,
        DRAW_BUFFER2 : 0x8827,
        DRAW_BUFFER3 : 0x8828,
        DRAW_BUFFER4 : 0x8829,
        DRAW_BUFFER5 : 0x882A,
        DRAW_BUFFER6 : 0x882B,
        DRAW_BUFFER7 : 0x882C,
        DRAW_BUFFER8 : 0x882D,
        DRAW_BUFFER9 : 0x882E,
        DRAW_BUFFER10 : 0x882F,
        DRAW_BUFFER11 : 0x8830,
        DRAW_BUFFER12 : 0x8831,
        DRAW_BUFFER13 : 0x8832,
        DRAW_BUFFER14 : 0x8833,
        DRAW_BUFFER15 : 0x8834,
        MAX_FRAGMENT_UNIFORM_COMPONENTS : 0x8B49,
        MAX_VERTEX_UNIFORM_COMPONENTS : 0x8B4A,
        SAMPLER_3D : 0x8B5F,
        SAMPLER_2D_SHADOW : 0x8B62,
        FRAGMENT_SHADER_DERIVATIVE_HINT : 0x8B8B,
        PIXEL_PACK_BUFFER : 0x88EB,
        PIXEL_UNPACK_BUFFER : 0x88EC,
        PIXEL_PACK_BUFFER_BINDING : 0x88ED,
        PIXEL_UNPACK_BUFFER_BINDING : 0x88EF,
        FLOAT_MAT2x3 : 0x8B65,
        FLOAT_MAT2x4 : 0x8B66,
        FLOAT_MAT3x2 : 0x8B67,
        FLOAT_MAT3x4 : 0x8B68,
        FLOAT_MAT4x2 : 0x8B69,
        FLOAT_MAT4x3 : 0x8B6A,
        SRGB : 0x8C40,
        SRGB8 : 0x8C41,
        SRGB8_ALPHA8 : 0x8C43,
        COMPARE_REF_TO_TEXTURE : 0x884E,
        RGBA32F : 0x8814,
        RGB32F : 0x8815,
        RGBA16F : 0x881A,
        RGB16F : 0x881B,
        VERTEX_ATTRIB_ARRAY_INTEGER : 0x88FD,
        MAX_ARRAY_TEXTURE_LAYERS : 0x88FF,
        MIN_PROGRAM_TEXEL_OFFSET : 0x8904,
        MAX_PROGRAM_TEXEL_OFFSET : 0x8905,
        MAX_VARYING_COMPONENTS : 0x8B4B,
        TEXTURE_2D_ARRAY : 0x8C1A,
        TEXTURE_BINDING_2D_ARRAY : 0x8C1D,
        R11F_G11F_B10F : 0x8C3A,
        UNSIGNED_INT_10F_11F_11F_REV : 0x8C3B,
        RGB9_E5 : 0x8C3D,
        UNSIGNED_INT_5_9_9_9_REV : 0x8C3E,
        TRANSFORM_FEEDBACK_BUFFER_MODE : 0x8C7F,
        MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS : 0x8C80,
        TRANSFORM_FEEDBACK_VARYINGS : 0x8C83,
        TRANSFORM_FEEDBACK_BUFFER_START : 0x8C84,
        TRANSFORM_FEEDBACK_BUFFER_SIZE : 0x8C85,
        TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN : 0x8C88,
        RASTERIZER_DISCARD : 0x8C89,
        MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS : 0x8C8A,
        MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS : 0x8C8B,
        INTERLEAVED_ATTRIBS : 0x8C8C,
        SEPARATE_ATTRIBS : 0x8C8D,
        TRANSFORM_FEEDBACK_BUFFER : 0x8C8E,
        TRANSFORM_FEEDBACK_BUFFER_BINDING : 0x8C8F,
        RGBA32UI : 0x8D70,
        RGB32UI : 0x8D71,
        RGBA16UI : 0x8D76,
        RGB16UI : 0x8D77,
        RGBA8UI : 0x8D7C,
        RGB8UI : 0x8D7D,
        RGBA32I : 0x8D82,
        RGB32I : 0x8D83,
        RGBA16I : 0x8D88,
        RGB16I : 0x8D89,
        RGBA8I : 0x8D8E,
        RGB8I : 0x8D8F,
        RED_INTEGER : 0x8D94,
        RGB_INTEGER : 0x8D98,
        RGBA_INTEGER : 0x8D99,
        SAMPLER_2D_ARRAY : 0x8DC1,
        SAMPLER_2D_ARRAY_SHADOW : 0x8DC4,
        SAMPLER_CUBE_SHADOW : 0x8DC5,
        UNSIGNED_INT_VEC2 : 0x8DC6,
        UNSIGNED_INT_VEC3 : 0x8DC7,
        UNSIGNED_INT_VEC4 : 0x8DC8,
        INT_SAMPLER_2D : 0x8DCA,
        INT_SAMPLER_3D : 0x8DCB,
        INT_SAMPLER_CUBE : 0x8DCC,
        INT_SAMPLER_2D_ARRAY : 0x8DCF,
        UNSIGNED_INT_SAMPLER_2D : 0x8DD2,
        UNSIGNED_INT_SAMPLER_3D : 0x8DD3,
        UNSIGNED_INT_SAMPLER_CUBE : 0x8DD4,
        UNSIGNED_INT_SAMPLER_2D_ARRAY : 0x8DD7,
        DEPTH_COMPONENT32F : 0x8CAC,
        DEPTH32F_STENCIL8 : 0x8CAD,
        FLOAT_32_UNSIGNED_INT_24_8_REV : 0x8DAD,
        FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING : 0x8210,
        FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE : 0x8211,
        FRAMEBUFFER_ATTACHMENT_RED_SIZE : 0x8212,
        FRAMEBUFFER_ATTACHMENT_GREEN_SIZE : 0x8213,
        FRAMEBUFFER_ATTACHMENT_BLUE_SIZE : 0x8214,
        FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE : 0x8215,
        FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE : 0x8216,
        FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE : 0x8217,
        FRAMEBUFFER_DEFAULT : 0x8218,
        UNSIGNED_INT_24_8 : 0x84FA,
        DEPTH24_STENCIL8 : 0x88F0,
        UNSIGNED_NORMALIZED : 0x8C17,
        DRAW_FRAMEBUFFER_BINDING : 0x8CA6, // Same as FRAMEBUFFER_BINDING
        READ_FRAMEBUFFER : 0x8CA8,
        DRAW_FRAMEBUFFER : 0x8CA9,
        READ_FRAMEBUFFER_BINDING : 0x8CAA,
        RENDERBUFFER_SAMPLES : 0x8CAB,
        FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER : 0x8CD4,
        MAX_COLOR_ATTACHMENTS : 0x8CDF,
        COLOR_ATTACHMENT1 : 0x8CE1,
        COLOR_ATTACHMENT2 : 0x8CE2,
        COLOR_ATTACHMENT3 : 0x8CE3,
        COLOR_ATTACHMENT4 : 0x8CE4,
        COLOR_ATTACHMENT5 : 0x8CE5,
        COLOR_ATTACHMENT6 : 0x8CE6,
        COLOR_ATTACHMENT7 : 0x8CE7,
        COLOR_ATTACHMENT8 : 0x8CE8,
        COLOR_ATTACHMENT9 : 0x8CE9,
        COLOR_ATTACHMENT10 : 0x8CEA,
        COLOR_ATTACHMENT11 : 0x8CEB,
        COLOR_ATTACHMENT12 : 0x8CEC,
        COLOR_ATTACHMENT13 : 0x8CED,
        COLOR_ATTACHMENT14 : 0x8CEE,
        COLOR_ATTACHMENT15 : 0x8CEF,
        FRAMEBUFFER_INCOMPLETE_MULTISAMPLE : 0x8D56,
        MAX_SAMPLES : 0x8D57,
        HALF_FLOAT : 0x140B,
        RG : 0x8227,
        RG_INTEGER : 0x8228,
        R8 : 0x8229,
        RG8 : 0x822B,
        R16F : 0x822D,
        R32F : 0x822E,
        RG16F : 0x822F,
        RG32F : 0x8230,
        R8I : 0x8231,
        R8UI : 0x8232,
        R16I : 0x8233,
        R16UI : 0x8234,
        R32I : 0x8235,
        R32UI : 0x8236,
        RG8I : 0x8237,
        RG8UI : 0x8238,
        RG16I : 0x8239,
        RG16UI : 0x823A,
        RG32I : 0x823B,
        RG32UI : 0x823C,
        VERTEX_ARRAY_BINDING : 0x85B5,
        R8_SNORM : 0x8F94,
        RG8_SNORM : 0x8F95,
        RGB8_SNORM : 0x8F96,
        RGBA8_SNORM : 0x8F97,
        SIGNED_NORMALIZED : 0x8F9C,
        COPY_READ_BUFFER : 0x8F36,
        COPY_WRITE_BUFFER : 0x8F37,
        COPY_READ_BUFFER_BINDING : 0x8F36, // Same as COPY_READ_BUFFER
        COPY_WRITE_BUFFER_BINDING : 0x8F37, // Same as COPY_WRITE_BUFFER
        UNIFORM_BUFFER : 0x8A11,
        UNIFORM_BUFFER_BINDING : 0x8A28,
        UNIFORM_BUFFER_START : 0x8A29,
        UNIFORM_BUFFER_SIZE : 0x8A2A,
        MAX_VERTEX_UNIFORM_BLOCKS : 0x8A2B,
        MAX_FRAGMENT_UNIFORM_BLOCKS : 0x8A2D,
        MAX_COMBINED_UNIFORM_BLOCKS : 0x8A2E,
        MAX_UNIFORM_BUFFER_BINDINGS : 0x8A2F,
        MAX_UNIFORM_BLOCK_SIZE : 0x8A30,
        MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS : 0x8A31,
        MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS : 0x8A33,
        UNIFORM_BUFFER_OFFSET_ALIGNMENT : 0x8A34,
        ACTIVE_UNIFORM_BLOCKS : 0x8A36,
        UNIFORM_TYPE : 0x8A37,
        UNIFORM_SIZE : 0x8A38,
        UNIFORM_BLOCK_INDEX : 0x8A3A,
        UNIFORM_OFFSET : 0x8A3B,
        UNIFORM_ARRAY_STRIDE : 0x8A3C,
        UNIFORM_MATRIX_STRIDE : 0x8A3D,
        UNIFORM_IS_ROW_MAJOR : 0x8A3E,
        UNIFORM_BLOCK_BINDING : 0x8A3F,
        UNIFORM_BLOCK_DATA_SIZE : 0x8A40,
        UNIFORM_BLOCK_ACTIVE_UNIFORMS : 0x8A42,
        UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES : 0x8A43,
        UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER : 0x8A44,
        UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER : 0x8A46,
        INVALID_INDEX : 0xFFFFFFFF,
        MAX_VERTEX_OUTPUT_COMPONENTS : 0x9122,
        MAX_FRAGMENT_INPUT_COMPONENTS : 0x9125,
        MAX_SERVER_WAIT_TIMEOUT : 0x9111,
        OBJECT_TYPE : 0x9112,
        SYNC_CONDITION : 0x9113,
        SYNC_STATUS : 0x9114,
        SYNC_FLAGS : 0x9115,
        SYNC_FENCE : 0x9116,
        SYNC_GPU_COMMANDS_COMPLETE : 0x9117,
        UNSIGNALED : 0x9118,
        SIGNALED : 0x9119,
        ALREADY_SIGNALED : 0x911A,
        TIMEOUT_EXPIRED : 0x911B,
        CONDITION_SATISFIED : 0x911C,
        WAIT_FAILED : 0x911D,
        SYNC_FLUSH_COMMANDS_BIT : 0x00000001,
        VERTEX_ATTRIB_ARRAY_DIVISOR : 0x88FE,
        ANY_SAMPLES_PASSED : 0x8C2F,
        ANY_SAMPLES_PASSED_CONSERVATIVE : 0x8D6A,
        SAMPLER_BINDING : 0x8919,
        RGB10_A2UI : 0x906F,
        INT_2_10_10_10_REV : 0x8D9F,
        TRANSFORM_FEEDBACK : 0x8E22,
        TRANSFORM_FEEDBACK_PAUSED : 0x8E23,
        TRANSFORM_FEEDBACK_ACTIVE : 0x8E24,
        TRANSFORM_FEEDBACK_BINDING : 0x8E25,
        COMPRESSED_R11_EAC : 0x9270,
        COMPRESSED_SIGNED_R11_EAC : 0x9271,
        COMPRESSED_RG11_EAC : 0x9272,
        COMPRESSED_SIGNED_RG11_EAC : 0x9273,
        COMPRESSED_RGB8_ETC2 : 0x9274,
        COMPRESSED_SRGB8_ETC2 : 0x9275,
        COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 : 0x9276,
        COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 : 0x9277,
        COMPRESSED_RGBA8_ETC2_EAC : 0x9278,
        COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : 0x9279,
        TEXTURE_IMMUTABLE_FORMAT : 0x912F,
        MAX_ELEMENT_INDEX : 0x8D6B,
        TEXTURE_IMMUTABLE_LEVELS : 0x82DF,

        // Extensions
        MAX_TEXTURE_MAX_ANISOTROPY_EXT : 0x84FF
    };

    return freezeObject(WebGLConstants);
});

/*global define*/
define('Core/ComponentDatatype',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './FeatureDetection',
        './freezeObject',
        './WebGLConstants'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        FeatureDetection,
        freezeObject,
        WebGLConstants) {
    'use strict';

    // Bail out if the browser doesn't support typed arrays, to prevent the setup function
    // from failing, since we won't be able to create a WebGL context anyway.
    if (!FeatureDetection.supportsTypedArrays()) {
        return {};
    }

    /**
     * WebGL component datatypes.  Components are intrinsics,
     * which form attributes, which form vertices.
     *
     * @exports ComponentDatatype
     */
    var ComponentDatatype = {
        /**
         * 8-bit signed byte corresponding to <code>gl.BYTE</code> and the type
         * of an element in <code>Int8Array</code>.
         *
         * @type {Number}
         * @constant
         */
        BYTE : WebGLConstants.BYTE,

        /**
         * 8-bit unsigned byte corresponding to <code>UNSIGNED_BYTE</code> and the type
         * of an element in <code>Uint8Array</code>.
         *
         * @type {Number}
         * @constant
         */
        UNSIGNED_BYTE : WebGLConstants.UNSIGNED_BYTE,

        /**
         * 16-bit signed short corresponding to <code>SHORT</code> and the type
         * of an element in <code>Int16Array</code>.
         *
         * @type {Number}
         * @constant
         */
        SHORT : WebGLConstants.SHORT,

        /**
         * 16-bit unsigned short corresponding to <code>UNSIGNED_SHORT</code> and the type
         * of an element in <code>Uint16Array</code>.
         *
         * @type {Number}
         * @constant
         */
        UNSIGNED_SHORT : WebGLConstants.UNSIGNED_SHORT,

        /**
         * 32-bit signed int corresponding to <code>INT</code> and the type
         * of an element in <code>Int32Array</code>.
         *
         * @memberOf ComponentDatatype
         *
         * @type {Number}
         * @constant
         */
        INT : WebGLConstants.INT,

        /**
         * 32-bit unsigned int corresponding to <code>UNSIGNED_INT</code> and the type
         * of an element in <code>Uint32Array</code>.
         *
         * @memberOf ComponentDatatype
         *
         * @type {Number}
         * @constant
         */
        UNSIGNED_INT : WebGLConstants.UNSIGNED_INT,

        /**
         * 32-bit floating-point corresponding to <code>FLOAT</code> and the type
         * of an element in <code>Float32Array</code>.
         *
         * @type {Number}
         * @constant
         */
        FLOAT : WebGLConstants.FLOAT,

        /**
         * 64-bit floating-point corresponding to <code>gl.DOUBLE</code> (in Desktop OpenGL;
         * this is not supported in WebGL, and is emulated in Cesium via {@link GeometryPipeline.encodeAttribute})
         * and the type of an element in <code>Float64Array</code>.
         *
         * @memberOf ComponentDatatype
         *
         * @type {Number}
         * @constant
         * @default 0x140A
         */
        DOUBLE : WebGLConstants.DOUBLE
    };

    /**
     * Returns the size, in bytes, of the corresponding datatype.
     *
     * @param {ComponentDatatype} componentDatatype The component datatype to get the size of.
     * @returns {Number} The size in bytes.
     *
     * @exception {DeveloperError} componentDatatype is not a valid value.
     *
     * @example
     * // Returns Int8Array.BYTES_PER_ELEMENT
     * var size = Cesium.ComponentDatatype.getSizeInBytes(Cesium.ComponentDatatype.BYTE);
     */
    ComponentDatatype.getSizeInBytes = function(componentDatatype){
                if (!defined(componentDatatype)) {
            throw new DeveloperError('value is required.');
        }
        
        switch (componentDatatype) {
        case ComponentDatatype.BYTE:
            return Int8Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.UNSIGNED_BYTE:
            return Uint8Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.SHORT:
            return Int16Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.UNSIGNED_SHORT:
            return Uint16Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.INT:
            return Int32Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.UNSIGNED_INT:
            return Uint32Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.FLOAT:
            return Float32Array.BYTES_PER_ELEMENT;
        case ComponentDatatype.DOUBLE:
            return Float64Array.BYTES_PER_ELEMENT;
                default:
            throw new DeveloperError('componentDatatype is not a valid value.');
                }
    };

    /**
     * Gets the {@link ComponentDatatype} for the provided TypedArray instance.
     *
     * @param {TypedArray} array The typed array.
     * @returns {ComponentDatatype} The ComponentDatatype for the provided array, or undefined if the array is not a TypedArray.
     */
    ComponentDatatype.fromTypedArray = function(array) {
        if (array instanceof Int8Array) {
            return ComponentDatatype.BYTE;
        }
        if (array instanceof Uint8Array) {
            return ComponentDatatype.UNSIGNED_BYTE;
        }
        if (array instanceof Int16Array) {
            return ComponentDatatype.SHORT;
        }
        if (array instanceof Uint16Array) {
            return ComponentDatatype.UNSIGNED_SHORT;
        }
        if (array instanceof Int32Array) {
            return ComponentDatatype.INT;
        }
        if (array instanceof Uint32Array) {
            return ComponentDatatype.UNSIGNED_INT;
        }
        if (array instanceof Float32Array) {
            return ComponentDatatype.FLOAT;
        }
        if (array instanceof Float64Array) {
            return ComponentDatatype.DOUBLE;
        }
    };

    /**
     * Validates that the provided component datatype is a valid {@link ComponentDatatype}
     *
     * @param {ComponentDatatype} componentDatatype The component datatype to validate.
     * @returns {Boolean} <code>true</code> if the provided component datatype is a valid value; otherwise, <code>false</code>.
     *
     * @example
     * if (!Cesium.ComponentDatatype.validate(componentDatatype)) {
     *   throw new Cesium.DeveloperError('componentDatatype must be a valid value.');
     * }
     */
    ComponentDatatype.validate = function(componentDatatype) {
        return defined(componentDatatype) &&
               (componentDatatype === ComponentDatatype.BYTE ||
                componentDatatype === ComponentDatatype.UNSIGNED_BYTE ||
                componentDatatype === ComponentDatatype.SHORT ||
                componentDatatype === ComponentDatatype.UNSIGNED_SHORT ||
                componentDatatype === ComponentDatatype.INT ||
                componentDatatype === ComponentDatatype.UNSIGNED_INT ||
                componentDatatype === ComponentDatatype.FLOAT ||
                componentDatatype === ComponentDatatype.DOUBLE);
    };

    /**
     * Creates a typed array corresponding to component data type.
     *
     * @param {ComponentDatatype} componentDatatype The component data type.
     * @param {Number|Array} valuesOrLength The length of the array to create or an array.
     * @returns {Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} A typed array.
     *
     * @exception {DeveloperError} componentDatatype is not a valid value.
     *
     * @example
     * // creates a Float32Array with length of 100
     * var typedArray = Cesium.ComponentDatatype.createTypedArray(Cesium.ComponentDatatype.FLOAT, 100);
     */
    ComponentDatatype.createTypedArray = function(componentDatatype, valuesOrLength) {
                if (!defined(componentDatatype)) {
            throw new DeveloperError('componentDatatype is required.');
        }
        if (!defined(valuesOrLength)) {
            throw new DeveloperError('valuesOrLength is required.');
        }
        
        switch (componentDatatype) {
        case ComponentDatatype.BYTE:
            return new Int8Array(valuesOrLength);
        case ComponentDatatype.UNSIGNED_BYTE:
            return new Uint8Array(valuesOrLength);
        case ComponentDatatype.SHORT:
            return new Int16Array(valuesOrLength);
        case ComponentDatatype.UNSIGNED_SHORT:
            return new Uint16Array(valuesOrLength);
        case ComponentDatatype.INT:
            return new Int32Array(valuesOrLength);
        case ComponentDatatype.UNSIGNED_INT:
            return new Uint32Array(valuesOrLength);
        case ComponentDatatype.FLOAT:
            return new Float32Array(valuesOrLength);
        case ComponentDatatype.DOUBLE:
            return new Float64Array(valuesOrLength);
                default:
            throw new DeveloperError('componentDatatype is not a valid value.');
                }
    };

    /**
     * Creates a typed view of an array of bytes.
     *
     * @param {ComponentDatatype} componentDatatype The type of the view to create.
     * @param {ArrayBuffer} buffer The buffer storage to use for the view.
     * @param {Number} [byteOffset] The offset, in bytes, to the first element in the view.
     * @param {Number} [length] The number of elements in the view.
     * @returns {Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} A typed array view of the buffer.
     *
     * @exception {DeveloperError} componentDatatype is not a valid value.
     */
    ComponentDatatype.createArrayBufferView = function(componentDatatype, buffer, byteOffset, length) {
                if (!defined(componentDatatype)) {
            throw new DeveloperError('componentDatatype is required.');
        }
        if (!defined(buffer)) {
            throw new DeveloperError('buffer is required.');
        }
        
        byteOffset = defaultValue(byteOffset, 0);
        length = defaultValue(length, (buffer.byteLength - byteOffset) / ComponentDatatype.getSizeInBytes(componentDatatype));

        switch (componentDatatype) {
        case ComponentDatatype.BYTE:
            return new Int8Array(buffer, byteOffset, length);
        case ComponentDatatype.UNSIGNED_BYTE:
            return new Uint8Array(buffer, byteOffset, length);
        case ComponentDatatype.SHORT:
            return new Int16Array(buffer, byteOffset, length);
        case ComponentDatatype.UNSIGNED_SHORT:
            return new Uint16Array(buffer, byteOffset, length);
        case ComponentDatatype.INT:
            return new Int32Array(buffer, byteOffset, length);
        case ComponentDatatype.UNSIGNED_INT:
            return new Uint32Array(buffer, byteOffset, length);
        case ComponentDatatype.FLOAT:
            return new Float32Array(buffer, byteOffset, length);
        case ComponentDatatype.DOUBLE:
            return new Float64Array(buffer, byteOffset, length);
                default:
            throw new DeveloperError('componentDatatype is not a valid value.');
                }
    };

    /**
     * Get the ComponentDatatype from its name.
     *
     * @param {String} name The name of the ComponentDatatype.
     * @returns {ComponentDatatype} The ComponentDatatype.
     *
     * @exception {DeveloperError} name is not a valid value.
     */
    ComponentDatatype.fromName = function(name) {
        switch (name) {
            case 'BYTE':
                return ComponentDatatype.BYTE;
            case 'UNSIGNED_BYTE':
                return ComponentDatatype.UNSIGNED_BYTE;
            case 'SHORT':
                return ComponentDatatype.SHORT;
            case 'UNSIGNED_SHORT':
                return ComponentDatatype.UNSIGNED_SHORT;
            case 'INT':
                return ComponentDatatype.INT;
            case 'UNSIGNED_INT':
                return ComponentDatatype.UNSIGNED_INT;
            case 'FLOAT':
                return ComponentDatatype.FLOAT;
            case 'DOUBLE':
                return ComponentDatatype.DOUBLE;
                        default:
                throw new DeveloperError('name is not a valid value.');
                    }
    };

    return freezeObject(ComponentDatatype);
});

/*global define*/
define('Core/TerrainQuantization',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * This enumerated type is used to determine how the vertices of the terrain mesh are compressed.
     *
     * @exports TerrainQuantization
     *
     * @private
     */
    var TerrainQuantization = {
        /**
         * The vertices are not compressed.
         *
         * @type {Number}
         * @constant
         */
        NONE : 0,

        /**
         * The vertices are compressed to 12 bits.
         *
         * @type {Number}
         * @constant
         */
        BITS12 : 1
    };

    return freezeObject(TerrainQuantization);
});

/*global define*/
define('Core/TerrainEncoding',[
        './AttributeCompression',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './Math',
        './Matrix4',
        './TerrainQuantization'
    ], function(
        AttributeCompression,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        CesiumMath,
        Matrix4,
        TerrainQuantization) {
    'use strict';

    var cartesian3Scratch = new Cartesian3();
    var cartesian3DimScratch = new Cartesian3();
    var cartesian2Scratch = new Cartesian2();
    var matrix4Scratch = new Matrix4();
    var matrix4Scratch2 = new Matrix4();

    var SHIFT_LEFT_12 = Math.pow(2.0, 12.0);

    /**
     * Data used to quantize and pack the terrain mesh. The position can be unpacked for picking and all attributes
     * are unpacked in the vertex shader.
     *
     * @alias TerrainEncoding
     * @constructor
     *
     * @param {AxisAlignedBoundingBox} axisAlignedBoundingBox The bounds of the tile in the east-north-up coordinates at the tiles center.
     * @param {Number} minimumHeight The minimum height.
     * @param {Number} maximumHeight The maximum height.
     * @param {Matrix4} fromENU The east-north-up to fixed frame matrix at the center of the terrain mesh.
     * @param {Boolean} hasVertexNormals If the mesh has vertex normals.
     * @param {Boolean} [hasWebMercatorT=false] true if the terrain data includes a Web Mercator texture coordinate; otherwise, false.
     *
     * @private
     */
    function TerrainEncoding(axisAlignedBoundingBox, minimumHeight, maximumHeight, fromENU, hasVertexNormals, hasWebMercatorT) {
        var quantization;
        var center;
        var toENU;
        var matrix;

        if (defined(axisAlignedBoundingBox) && defined(minimumHeight) && defined(maximumHeight) && defined(fromENU)) {
            var minimum = axisAlignedBoundingBox.minimum;
            var maximum = axisAlignedBoundingBox.maximum;

            var dimensions = Cartesian3.subtract(maximum, minimum, cartesian3DimScratch);
            var hDim = maximumHeight - minimumHeight;
            var maxDim = Math.max(Cartesian3.maximumComponent(dimensions), hDim);

            if (maxDim < SHIFT_LEFT_12 - 1.0) {
                quantization = TerrainQuantization.BITS12;
            } else {
                quantization = TerrainQuantization.NONE;
            }

            center = axisAlignedBoundingBox.center;
            toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());

            var translation = Cartesian3.negate(minimum, cartesian3Scratch);
            Matrix4.multiply(Matrix4.fromTranslation(translation, matrix4Scratch), toENU, toENU);

            var scale = cartesian3Scratch;
            scale.x = 1.0 / dimensions.x;
            scale.y = 1.0 / dimensions.y;
            scale.z = 1.0 / dimensions.z;
            Matrix4.multiply(Matrix4.fromScale(scale, matrix4Scratch), toENU, toENU);

            matrix = Matrix4.clone(fromENU);
            Matrix4.setTranslation(matrix, Cartesian3.ZERO, matrix);

            fromENU = Matrix4.clone(fromENU, new Matrix4());

            var translationMatrix = Matrix4.fromTranslation(minimum, matrix4Scratch);
            var scaleMatrix =  Matrix4.fromScale(dimensions, matrix4Scratch2);
            var st = Matrix4.multiply(translationMatrix, scaleMatrix,matrix4Scratch);

            Matrix4.multiply(fromENU, st, fromENU);
            Matrix4.multiply(matrix, st, matrix);
        }

        /**
         * How the vertices of the mesh were compressed.
         * @type {TerrainQuantization}
         */
        this.quantization = quantization;

        /**
         * The minimum height of the tile including the skirts.
         * @type {Number}
         */
        this.minimumHeight = minimumHeight;

        /**
         * The maximum height of the tile.
         * @type {Number}
         */
        this.maximumHeight = maximumHeight;

        /**
         * The center of the tile.
         * @type {Cartesian3}
         */
        this.center = center;

        /**
         * A matrix that takes a vertex from the tile, transforms it to east-north-up at the center and scales
         * it so each component is in the [0, 1] range.
         * @type {Matrix4}
         */
        this.toScaledENU = toENU;

        /**
         * A matrix that restores a vertex transformed with toScaledENU back to the earth fixed reference frame
         * @type {Matrix4}
         */
        this.fromScaledENU = fromENU;

        /**
         * The matrix used to decompress the terrain vertices in the shader for RTE rendering.
         * @type {Matrix4}
         */
        this.matrix = matrix;

        /**
         * The terrain mesh contains normals.
         * @type {Boolean}
         */
        this.hasVertexNormals = hasVertexNormals;

        /**
         * The terrain mesh contains a vertical texture coordinate following the Web Mercator projection.
         * @type {Boolean}
         */
        this.hasWebMercatorT = defaultValue(hasWebMercatorT, false);
    }

    TerrainEncoding.prototype.encode = function(vertexBuffer, bufferIndex, position, uv, height, normalToPack, webMercatorT) {
        var u = uv.x;
        var v = uv.y;

        if (this.quantization === TerrainQuantization.BITS12) {
            position = Matrix4.multiplyByPoint(this.toScaledENU, position, cartesian3Scratch);

            position.x = CesiumMath.clamp(position.x, 0.0, 1.0);
            position.y = CesiumMath.clamp(position.y, 0.0, 1.0);
            position.z = CesiumMath.clamp(position.z, 0.0, 1.0);

            var hDim = this.maximumHeight - this.minimumHeight;
            var h = CesiumMath.clamp((height - this.minimumHeight) / hDim, 0.0, 1.0);

            Cartesian2.fromElements(position.x, position.y, cartesian2Scratch);
            var compressed0 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);

            Cartesian2.fromElements(position.z, h, cartesian2Scratch);
            var compressed1 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);

            Cartesian2.fromElements(u, v, cartesian2Scratch);
            var compressed2 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);

            vertexBuffer[bufferIndex++] = compressed0;
            vertexBuffer[bufferIndex++] = compressed1;
            vertexBuffer[bufferIndex++] = compressed2;

            if (this.hasWebMercatorT) {
                Cartesian2.fromElements(webMercatorT, 0.0, cartesian2Scratch);
                var compressed3 = AttributeCompression.compressTextureCoordinates(cartesian2Scratch);
                vertexBuffer[bufferIndex++] = compressed3;
            }
        } else {
            Cartesian3.subtract(position, this.center, cartesian3Scratch);

            vertexBuffer[bufferIndex++] = cartesian3Scratch.x;
            vertexBuffer[bufferIndex++] = cartesian3Scratch.y;
            vertexBuffer[bufferIndex++] = cartesian3Scratch.z;
            vertexBuffer[bufferIndex++] = height;
            vertexBuffer[bufferIndex++] = u;
            vertexBuffer[bufferIndex++] = v;

            if (this.hasWebMercatorT) {
                vertexBuffer[bufferIndex++] = webMercatorT;
            }
        }

        if (this.hasVertexNormals) {
            vertexBuffer[bufferIndex++] = AttributeCompression.octPackFloat(normalToPack);
        }

        return bufferIndex;
    };

    TerrainEncoding.prototype.decodePosition = function(buffer, index, result) {
        if (!defined(result)) {
            result = new Cartesian3();
        }

        index *= this.getStride();

        if (this.quantization === TerrainQuantization.BITS12) {
            var xy = AttributeCompression.decompressTextureCoordinates(buffer[index], cartesian2Scratch);
            result.x = xy.x;
            result.y = xy.y;

            var zh = AttributeCompression.decompressTextureCoordinates(buffer[index + 1], cartesian2Scratch);
            result.z = zh.x;

            return Matrix4.multiplyByPoint(this.fromScaledENU, result, result);
        }

        result.x = buffer[index];
        result.y = buffer[index + 1];
        result.z = buffer[index + 2];
        return Cartesian3.add(result, this.center, result);
    };

    TerrainEncoding.prototype.decodeTextureCoordinates = function(buffer, index, result) {
        if (!defined(result)) {
            result = new Cartesian2();
        }

        index *= this.getStride();

        if (this.quantization === TerrainQuantization.BITS12) {
            return AttributeCompression.decompressTextureCoordinates(buffer[index + 2], result);
        }

        return Cartesian2.fromElements(buffer[index + 4], buffer[index + 5], result);
    };

    TerrainEncoding.prototype.decodeHeight = function(buffer, index) {
        index *= this.getStride();

        if (this.quantization === TerrainQuantization.BITS12) {
            var zh = AttributeCompression.decompressTextureCoordinates(buffer[index + 1], cartesian2Scratch);
            return zh.y * (this.maximumHeight - this.minimumHeight) + this.minimumHeight;
        }

        return buffer[index + 3];
    };

    TerrainEncoding.prototype.getOctEncodedNormal = function(buffer, index, result) {
        var stride = this.getStride();
        index = (index + 1) * stride - 1;

        var temp = buffer[index] / 256.0;
        var x = Math.floor(temp);
        var y = (temp - x) * 256.0;

        return Cartesian2.fromElements(x, y, result);
    };

    TerrainEncoding.prototype.getStride = function() {
        var vertexStride;

        switch (this.quantization) {
            case TerrainQuantization.BITS12:
                vertexStride = 3;
                break;
            default:
                vertexStride = 6;
        }

        if (this.hasWebMercatorT) {
            ++vertexStride;
        }

        if (this.hasVertexNormals) {
            ++vertexStride;
        }

        return vertexStride;
    };

    var attributesNone = {
        position3DAndHeight : 0,
        textureCoordAndEncodedNormals : 1
    };
    var attributes = {
        compressed0 : 0,
        compressed1 : 1
    };

    TerrainEncoding.prototype.getAttributes = function(buffer) {
        var datatype = ComponentDatatype.FLOAT;
        var sizeInBytes = ComponentDatatype.getSizeInBytes(datatype);
        var stride;

        if (this.quantization === TerrainQuantization.NONE) {
            var position3DAndHeightLength = 4;
            var numTexCoordComponents = 2;

            if (this.hasWebMercatorT) {
                ++numTexCoordComponents;
            }

            if (this.hasVertexNormals) {
                ++numTexCoordComponents;
            }

            stride = (position3DAndHeightLength + numTexCoordComponents) * sizeInBytes;

            return [{
                index : attributesNone.position3DAndHeight,
                vertexBuffer : buffer,
                componentDatatype : datatype,
                componentsPerAttribute : position3DAndHeightLength,
                offsetInBytes : 0,
                strideInBytes : stride
            }, {
                index : attributesNone.textureCoordAndEncodedNormals,
                vertexBuffer : buffer,
                componentDatatype : datatype,
                componentsPerAttribute : numTexCoordComponents,
                offsetInBytes : position3DAndHeightLength * sizeInBytes,
                strideInBytes : stride
            }];
        }

        var numCompressed0 = 3;
        var numCompressed1 = 0;

        if (this.hasWebMercatorT || this.hasVertexNormals) {
            ++numCompressed0;
        }

        if (this.hasWebMercatorT && this.hasVertexNormals) {
            ++numCompressed1;

            stride = (numCompressed0 + numCompressed1) * sizeInBytes;

            return [{
                index : attributes.compressed0,
                vertexBuffer : buffer,
                componentDatatype : datatype,
                componentsPerAttribute : numCompressed0,
                offsetInBytes : 0,
                strideInBytes : stride
            }, {
                index : attributes.compressed1,
                vertexBuffer : buffer,
                componentDatatype : datatype,
                componentsPerAttribute : numCompressed1,
                offsetInBytes : numCompressed0 * sizeInBytes,
                strideInBytes : stride
            }];
        } else {
            return [{
                index : attributes.compressed0,
                vertexBuffer : buffer,
                componentDatatype : datatype,
                componentsPerAttribute : numCompressed0
            }];
        }
    };

    TerrainEncoding.prototype.getAttributeLocations = function() {
        if (this.quantization === TerrainQuantization.NONE) {
            return attributesNone;
        } else {
            return attributes;
        }
    };

    TerrainEncoding.clone = function(encoding, result) {
        if (!defined(result)) {
            result = new TerrainEncoding();
        }

        result.quantization = encoding.quantization;
        result.minimumHeight = encoding.minimumHeight;
        result.maximumHeight = encoding.maximumHeight;
        result.center = Cartesian3.clone(encoding.center);
        result.toScaledENU = Matrix4.clone(encoding.toScaledENU);
        result.fromScaledENU = Matrix4.clone(encoding.fromScaledENU);
        result.matrix = Matrix4.clone(encoding.matrix);
        result.hasVertexNormals = encoding.hasVertexNormals;
        result.hasWebMercatorT = encoding.hasWebMercatorT;
        return result;
    };

    return TerrainEncoding;
});

/*global define*/
define('Core/WebMercatorProjection',[
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './Math'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        CesiumMath) {
    'use strict';

    /**
     * The map projection used by Google Maps, Bing Maps, and most of ArcGIS Online, EPSG:3857.  This
     * projection use longitude and latitude expressed with the WGS84 and transforms them to Mercator using
     * the spherical (rather than ellipsoidal) equations.
     *
     * @alias WebMercatorProjection
     * @constructor
     *
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid.
     *
     * @see GeographicProjection
     */
    function WebMercatorProjection(ellipsoid) {
        this._ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        this._semimajorAxis = this._ellipsoid.maximumRadius;
        this._oneOverSemimajorAxis = 1.0 / this._semimajorAxis;
    }

    defineProperties(WebMercatorProjection.prototype, {
        /**
         * Gets the {@link Ellipsoid}.
         *
         * @memberof WebMercatorProjection.prototype
         *
         * @type {Ellipsoid}
         * @readonly
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        }
    });

    /**
     * Converts a Mercator angle, in the range -PI to PI, to a geodetic latitude
     * in the range -PI/2 to PI/2.
     *
     * @param {Number} mercatorAngle The angle to convert.
     * @returns {Number} The geodetic latitude in radians.
     */
    WebMercatorProjection.mercatorAngleToGeodeticLatitude = function(mercatorAngle) {
        return CesiumMath.PI_OVER_TWO - (2.0 * Math.atan(Math.exp(-mercatorAngle)));
    };

    /**
     * Converts a geodetic latitude in radians, in the range -PI/2 to PI/2, to a Mercator
     * angle in the range -PI to PI.
     *
     * @param {Number} latitude The geodetic latitude in radians.
     * @returns {Number} The Mercator angle.
     */
    WebMercatorProjection.geodeticLatitudeToMercatorAngle = function(latitude) {
        // Clamp the latitude coordinate to the valid Mercator bounds.
        if (latitude > WebMercatorProjection.MaximumLatitude) {
            latitude = WebMercatorProjection.MaximumLatitude;
        } else if (latitude < -WebMercatorProjection.MaximumLatitude) {
            latitude = -WebMercatorProjection.MaximumLatitude;
        }
        var sinLatitude = Math.sin(latitude);
        return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
    };

    /**
     * The maximum latitude (both North and South) supported by a Web Mercator
     * (EPSG:3857) projection.  Technically, the Mercator projection is defined
     * for any latitude up to (but not including) 90 degrees, but it makes sense
     * to cut it off sooner because it grows exponentially with increasing latitude.
     * The logic behind this particular cutoff value, which is the one used by
     * Google Maps, Bing Maps, and Esri, is that it makes the projection
     * square.  That is, the rectangle is equal in the X and Y directions.
     *
     * The constant value is computed by calling:
     *    WebMercatorProjection.mercatorAngleToGeodeticLatitude(Math.PI)
     *
     * @type {Number}
     */
    WebMercatorProjection.MaximumLatitude = WebMercatorProjection.mercatorAngleToGeodeticLatitude(Math.PI);

    /**
     * Converts geodetic ellipsoid coordinates, in radians, to the equivalent Web Mercator
     * X, Y, Z coordinates expressed in meters and returned in a {@link Cartesian3}.  The height
     * is copied unmodified to the Z coordinate.
     *
     * @param {Cartographic} cartographic The cartographic coordinates in radians.
     * @param {Cartesian3} [result] The instance to which to copy the result, or undefined if a
     *        new instance should be created.
     * @returns {Cartesian3} The equivalent web mercator X, Y, Z coordinates, in meters.
     */
    WebMercatorProjection.prototype.project = function(cartographic, result) {
        var semimajorAxis = this._semimajorAxis;
        var x = cartographic.longitude * semimajorAxis;
        var y = WebMercatorProjection.geodeticLatitudeToMercatorAngle(cartographic.latitude) * semimajorAxis;
        var z = cartographic.height;

        if (!defined(result)) {
            return new Cartesian3(x, y, z);
        }

        result.x = x;
        result.y = y;
        result.z = z;
        return result;
    };

    /**
     * Converts Web Mercator X, Y coordinates, expressed in meters, to a {@link Cartographic}
     * containing geodetic ellipsoid coordinates.  The Z coordinate is copied unmodified to the
     * height.
     *
     * @param {Cartesian3} cartesian The web mercator Cartesian position to unrproject with height (z) in meters.
     * @param {Cartographic} [result] The instance to which to copy the result, or undefined if a
     *        new instance should be created.
     * @returns {Cartographic} The equivalent cartographic coordinates.
     */
    WebMercatorProjection.prototype.unproject = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required');
        }
        
        var oneOverEarthSemimajorAxis = this._oneOverSemimajorAxis;
        var longitude = cartesian.x * oneOverEarthSemimajorAxis;
        var latitude = WebMercatorProjection.mercatorAngleToGeodeticLatitude(cartesian.y * oneOverEarthSemimajorAxis);
        var height = cartesian.z;

        if (!defined(result)) {
            return new Cartographic(longitude, latitude, height);
        }

        result.longitude = longitude;
        result.latitude = latitude;
        result.height = height;
        return result;
    };

    return WebMercatorProjection;
});

/*global define*/
define('Core/HeightmapTessellator',[
        './AxisAlignedBoundingBox',
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './EllipsoidalOccluder',
        './freezeObject',
        './Math',
        './Matrix4',
        './OrientedBoundingBox',
        './Rectangle',
        './TerrainEncoding',
        './Transforms',
        './WebMercatorProjection'
    ], function(
        AxisAlignedBoundingBox,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        EllipsoidalOccluder,
        freezeObject,
        CesiumMath,
        Matrix4,
        OrientedBoundingBox,
        Rectangle,
        TerrainEncoding,
        Transforms,
        WebMercatorProjection) {
    'use strict';

    /**
     * Contains functions to create a mesh from a heightmap image.
     *
     * @exports HeightmapTessellator
     *
     * @private
     */
    var HeightmapTessellator = {};

    /**
     * The default structure of a heightmap, as given to {@link HeightmapTessellator.computeVertices}.
     *
     * @constant
     */
    HeightmapTessellator.DEFAULT_STRUCTURE = freezeObject({
        heightScale : 1.0,
        heightOffset : 0.0,
        elementsPerHeight : 1,
        stride : 1,
        elementMultiplier : 256.0,
        isBigEndian : false
    });

    var cartesian3Scratch = new Cartesian3();
    var matrix4Scratch = new Matrix4();
    var minimumScratch = new Cartesian3();
    var maximumScratch = new Cartesian3();

    /**
     * Fills an array of vertices from a heightmap image.
     *
     * @param {Object} options Object with the following properties:
     * @param {TypedArray} options.heightmap The heightmap to tessellate.
     * @param {Number} options.width The width of the heightmap, in height samples.
     * @param {Number} options.height The height of the heightmap, in height samples.
     * @param {Number} options.skirtHeight The height of skirts to drape at the edges of the heightmap.
     * @param {Rectangle} options.nativeRectangle An rectangle in the native coordinates of the heightmap's projection.  For
     *                 a heightmap with a geographic projection, this is degrees.  For the web mercator
     *                 projection, this is meters.
     * @param {Number} [options.exaggeration=1.0] The scale used to exaggerate the terrain.
     * @param {Rectangle} [options.rectangle] The rectangle covered by the heightmap, in geodetic coordinates with north, south, east and
     *                 west properties in radians.  Either rectangle or nativeRectangle must be provided.  If both
     *                 are provided, they're assumed to be consistent.
     * @param {Boolean} [options.isGeographic=true] True if the heightmap uses a {@link GeographicProjection}, or false if it uses
     *                  a {@link WebMercatorProjection}.
     * @param {Cartesian3} [options.relativeToCenter=Cartesian3.ZERO] The positions will be computed as <code>Cartesian3.subtract(worldPosition, relativeToCenter)</code>.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to which the heightmap applies.
     * @param {Object} [options.structure] An object describing the structure of the height data.
     * @param {Number} [options.structure.heightScale=1.0] The factor by which to multiply height samples in order to obtain
     *                 the height above the heightOffset, in meters.  The heightOffset is added to the resulting
     *                 height after multiplying by the scale.
     * @param {Number} [options.structure.heightOffset=0.0] The offset to add to the scaled height to obtain the final
     *                 height in meters.  The offset is added after the height sample is multiplied by the
     *                 heightScale.
     * @param {Number} [options.structure.elementsPerHeight=1] The number of elements in the buffer that make up a single height
     *                 sample.  This is usually 1, indicating that each element is a separate height sample.  If
     *                 it is greater than 1, that number of elements together form the height sample, which is
     *                 computed according to the structure.elementMultiplier and structure.isBigEndian properties.
     * @param {Number} [options.structure.stride=1] The number of elements to skip to get from the first element of
     *                 one height to the first element of the next height.
     * @param {Number} [options.structure.elementMultiplier=256.0] The multiplier used to compute the height value when the
     *                 stride property is greater than 1.  For example, if the stride is 4 and the strideMultiplier
     *                 is 256, the height is computed as follows:
     *                 `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256`
     *                 This is assuming that the isBigEndian property is false.  If it is true, the order of the
     *                 elements is reversed.
     * @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer.  Any heights that are lower
     *                 than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value.  For example, if the height
     *                 buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers.  If this parameter is
     *                 not specified, no minimum value is enforced.
     * @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer.  Any heights that are higher
     *                 than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value.  For example, if the height
     *                 buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger
     *                 than 65535.  If this parameter is not specified, no maximum value is enforced.
     * @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the
     *                  stride property is greater than 1.  If this property is false, the first element is the
     *                  low-order element.  If it is true, the first element is the high-order element.
     *
     * @example
     * var width = 5;
     * var height = 5;
     * var statistics = Cesium.HeightmapTessellator.computeVertices({
     *     heightmap : [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
     *     width : width,
     *     height : height,
     *     skirtHeight : 0.0,
     *     nativeRectangle : {
     *         west : 10.0,
     *         east : 20.0,
     *         south : 30.0,
     *         north : 40.0
     *     }
     * });
     *
     * var encoding = statistics.encoding;
     * var position = encoding.decodePosition(statistics.vertices, index * encoding.getStride());
     */
    HeightmapTessellator.computeVertices = function(options) {
                if (!defined(options) || !defined(options.heightmap)) {
            throw new DeveloperError('options.heightmap is required.');
        }
        if (!defined(options.width) || !defined(options.height)) {
            throw new DeveloperError('options.width and options.height are required.');
        }
        if (!defined(options.nativeRectangle)) {
            throw new DeveloperError('options.nativeRectangle is required.');
        }
        if (!defined(options.skirtHeight)) {
            throw new DeveloperError('options.skirtHeight is required.');
        }
        
        // This function tends to be a performance hotspot for terrain rendering,
        // so it employs a lot of inlining and unrolling as an optimization.
        // In particular, the functionality of Ellipsoid.cartographicToCartesian
        // is inlined.

        var cos = Math.cos;
        var sin = Math.sin;
        var sqrt = Math.sqrt;
        var atan = Math.atan;
        var exp = Math.exp;
        var piOverTwo = CesiumMath.PI_OVER_TWO;
        var toRadians = CesiumMath.toRadians;

        var heightmap = options.heightmap;
        var width = options.width;
        var height = options.height;
        var skirtHeight = options.skirtHeight;

        var isGeographic = defaultValue(options.isGeographic, true);
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);

        var oneOverGlobeSemimajorAxis = 1.0 / ellipsoid.maximumRadius;

        var nativeRectangle = options.nativeRectangle;

        var geographicWest;
        var geographicSouth;
        var geographicEast;
        var geographicNorth;

        var rectangle = options.rectangle;
        if (!defined(rectangle)) {
            if (isGeographic) {
                geographicWest = toRadians(nativeRectangle.west);
                geographicSouth = toRadians(nativeRectangle.south);
                geographicEast = toRadians(nativeRectangle.east);
                geographicNorth = toRadians(nativeRectangle.north);
            } else {
                geographicWest = nativeRectangle.west * oneOverGlobeSemimajorAxis;
                geographicSouth = piOverTwo - (2.0 * atan(exp(-nativeRectangle.south * oneOverGlobeSemimajorAxis)));
                geographicEast = nativeRectangle.east * oneOverGlobeSemimajorAxis;
                geographicNorth = piOverTwo - (2.0 * atan(exp(-nativeRectangle.north * oneOverGlobeSemimajorAxis)));
            }
        } else {
            geographicWest = rectangle.west;
            geographicSouth = rectangle.south;
            geographicEast = rectangle.east;
            geographicNorth = rectangle.north;
        }

        var relativeToCenter = options.relativeToCenter;
        var hasRelativeToCenter = defined(relativeToCenter);
        relativeToCenter = hasRelativeToCenter ? relativeToCenter : Cartesian3.ZERO;
        var exaggeration = defaultValue(options.exaggeration, 1.0);
        var includeWebMercatorT = defaultValue(options.includeWebMercatorT, false);

        var structure = defaultValue(options.structure, HeightmapTessellator.DEFAULT_STRUCTURE);
        var heightScale = defaultValue(structure.heightScale, HeightmapTessellator.DEFAULT_STRUCTURE.heightScale);
        var heightOffset = defaultValue(structure.heightOffset, HeightmapTessellator.DEFAULT_STRUCTURE.heightOffset);
        var elementsPerHeight = defaultValue(structure.elementsPerHeight, HeightmapTessellator.DEFAULT_STRUCTURE.elementsPerHeight);
        var stride = defaultValue(structure.stride, HeightmapTessellator.DEFAULT_STRUCTURE.stride);
        var elementMultiplier = defaultValue(structure.elementMultiplier, HeightmapTessellator.DEFAULT_STRUCTURE.elementMultiplier);
        var isBigEndian = defaultValue(structure.isBigEndian, HeightmapTessellator.DEFAULT_STRUCTURE.isBigEndian);

        var granularityX = Rectangle.computeWidth(nativeRectangle) / (width - 1);
        var granularityY = Rectangle.computeHeight(nativeRectangle) / (height - 1);

        var radiiSquared = ellipsoid.radiiSquared;
        var radiiSquaredX = radiiSquared.x;
        var radiiSquaredY = radiiSquared.y;
        var radiiSquaredZ = radiiSquared.z;

        var minimumHeight = 65536.0;
        var maximumHeight = -65536.0;

        var fromENU = Transforms.eastNorthUpToFixedFrame(relativeToCenter, ellipsoid);
        var toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch);

        var southMercatorY;
        var oneOverMercatorHeight;
        if (includeWebMercatorT) {
            southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth);
            oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) - southMercatorY);
        }

        var minimum = minimumScratch;
        minimum.x = Number.POSITIVE_INFINITY;
        minimum.y = Number.POSITIVE_INFINITY;
        minimum.z = Number.POSITIVE_INFINITY;

        var maximum = maximumScratch;
        maximum.x = Number.NEGATIVE_INFINITY;
        maximum.y = Number.NEGATIVE_INFINITY;
        maximum.z = Number.NEGATIVE_INFINITY;

        var hMin = Number.POSITIVE_INFINITY;

        var arrayWidth = width + (skirtHeight > 0.0 ? 2.0 : 0.0);
        var arrayHeight = height + (skirtHeight > 0.0 ? 2.0 : 0.0);
        var size = arrayWidth * arrayHeight;
        var positions = new Array(size);
        var heights = new Array(size);
        var uvs = new Array(size);
        var webMercatorTs = includeWebMercatorT ? new Array(size) : [];

        var startRow = 0;
        var endRow = height;
        var startCol = 0;
        var endCol = width;

        if (skirtHeight > 0) {
            --startRow;
            ++endRow;
            --startCol;
            ++endCol;
        }

        var index = 0;

        for (var rowIndex = startRow; rowIndex < endRow; ++rowIndex) {
            var row = rowIndex;
            if (row < 0) {
                row = 0;
            }
            if (row >= height) {
                row = height - 1;
            }

            var latitude = nativeRectangle.north - granularityY * row;

            if (!isGeographic) {
                latitude = piOverTwo - (2.0 * atan(exp(-latitude * oneOverGlobeSemimajorAxis)));
            } else {
                latitude = toRadians(latitude);
            }

            var cosLatitude = cos(latitude);
            var nZ = sin(latitude);
            var kZ = radiiSquaredZ * nZ;

            var v = (latitude - geographicSouth) / (geographicNorth - geographicSouth);
            v = CesiumMath.clamp(v, 0.0, 1.0);

            var webMercatorT;
            if (includeWebMercatorT) {
                webMercatorT = (WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) - southMercatorY) * oneOverMercatorHeight;
            }

            for (var colIndex = startCol; colIndex < endCol; ++colIndex) {
                var col = colIndex;
                if (col < 0) {
                    col = 0;
                }
                if (col >= width) {
                    col = width - 1;
                }

                var longitude = nativeRectangle.west + granularityX * col;

                if (!isGeographic) {
                    longitude = longitude * oneOverGlobeSemimajorAxis;
                } else {
                    longitude = toRadians(longitude);
                }

                var terrainOffset = row * (width * stride) + col * stride;

                var heightSample;
                if (elementsPerHeight === 1) {
                    heightSample = heightmap[terrainOffset];
                } else {
                    heightSample = 0;

                    var elementOffset;
                    if (isBigEndian) {
                        for (elementOffset = 0; elementOffset < elementsPerHeight; ++elementOffset) {
                            heightSample = (heightSample * elementMultiplier) + heightmap[terrainOffset + elementOffset];
                        }
                    } else {
                        for (elementOffset = elementsPerHeight - 1; elementOffset >= 0; --elementOffset) {
                            heightSample = (heightSample * elementMultiplier) + heightmap[terrainOffset + elementOffset];
                        }
                    }
                }

                heightSample = (heightSample * heightScale + heightOffset) * exaggeration;

                maximumHeight = Math.max(maximumHeight, heightSample);
                minimumHeight = Math.min(minimumHeight, heightSample);

                if (colIndex !== col || rowIndex !== row) {
                    heightSample -= skirtHeight;
                }

                var nX = cosLatitude * cos(longitude);
                var nY = cosLatitude * sin(longitude);

                var kX = radiiSquaredX * nX;
                var kY = radiiSquaredY * nY;

                var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ));
                var oneOverGamma = 1.0 / gamma;

                var rSurfaceX = kX * oneOverGamma;
                var rSurfaceY = kY * oneOverGamma;
                var rSurfaceZ = kZ * oneOverGamma;

                var position = new Cartesian3();
                position.x = rSurfaceX + nX * heightSample;
                position.y = rSurfaceY + nY * heightSample;
                position.z = rSurfaceZ + nZ * heightSample;

                positions[index] = position;
                heights[index] = heightSample;

                var u = (longitude - geographicWest) / (geographicEast - geographicWest);
                u = CesiumMath.clamp(u, 0.0, 1.0);
                uvs[index] = new Cartesian2(u, v);

                if (includeWebMercatorT) {
                    webMercatorTs[index] = webMercatorT;
                }

                index++;

                Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch);

                Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum);
                Cartesian3.maximumByComponent(cartesian3Scratch, maximum, maximum);
                hMin = Math.min(hMin, heightSample);
            }
        }

        var boundingSphere3D = BoundingSphere.fromPoints(positions);
        var orientedBoundingBox;
        if (defined(rectangle) && rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) {
            // Here, rectangle.width < pi/2, and rectangle.height < pi
            // (though it would still work with rectangle.width up to pi)
            orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, ellipsoid);
        }

        var occludeePointInScaledSpace;
        if (hasRelativeToCenter) {
            var occluder = new EllipsoidalOccluder(ellipsoid);
            occludeePointInScaledSpace = occluder.computeHorizonCullingPoint(relativeToCenter, positions);
        }

        var aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
        var encoding = new TerrainEncoding(aaBox, hMin, maximumHeight, fromENU, false, includeWebMercatorT);
        var vertices = new Float32Array(size * encoding.getStride());

        var bufferIndex = 0;
        for (var j = 0; j < size; ++j) {
            bufferIndex = encoding.encode(vertices, bufferIndex, positions[j], uvs[j], heights[j], undefined, webMercatorTs[j]);
        }

        return {
            vertices : vertices,
            maximumHeight : maximumHeight,
            minimumHeight : minimumHeight,
            encoding : encoding,
            boundingSphere3D : boundingSphere3D,
            orientedBoundingBox : orientedBoundingBox,
            occludeePointInScaledSpace : occludeePointInScaledSpace
        };
    };

    return HeightmapTessellator;
});

/*global define*/
define('Core/destroyObject',[
        './defaultValue',
        './DeveloperError'
    ], function(
        defaultValue,
        DeveloperError) {
    'use strict';

    function returnTrue() {
        return true;
    }

    /**
     * Destroys an object.  Each of the object's functions, including functions in its prototype,
     * is replaced with a function that throws a {@link DeveloperError}, except for the object's
     * <code>isDestroyed</code> function, which is set to a function that returns <code>true</code>.
     * The object's properties are removed with <code>delete</code>.
     * <br /><br />
     * This function is used by objects that hold native resources, e.g., WebGL resources, which
     * need to be explicitly released.  Client code calls an object's <code>destroy</code> function,
     * which then releases the native resource and calls <code>destroyObject</code> to put itself
     * in a destroyed state.
     *
     * @exports destroyObject
     *
     * @param {Object} object The object to destroy.
     * @param {String} [message] The message to include in the exception that is thrown if
     *                           a destroyed object's function is called.
     *
     *
     * @example
     * // How a texture would destroy itself.
     * this.destroy = function () {
     *     _gl.deleteTexture(_texture);
     *     return Cesium.destroyObject(this);
     * };
     * 
     * @see DeveloperError
     */
    function destroyObject(object, message) {
        message = defaultValue(message, 'This object was destroyed, i.e., destroy() was called.');

        function throwOnDestroyed() {
                        throw new DeveloperError(message);
                    }

        for ( var key in object) {
            if (typeof object[key] === 'function') {
                object[key] = throwOnDestroyed;
            }
        }

        object.isDestroyed = returnTrue;

        return undefined;
    }

    return destroyObject;
});

/*global define*/
define('Core/isCrossOriginUrl',[
        './defined'
    ], function(
        defined) {
    'use strict';

    var a;

    /**
     * Given a URL, determine whether that URL is considered cross-origin to the current page.
     *
     * @private
     */
    function isCrossOriginUrl(url) {
        if (!defined(a)) {
            a = document.createElement('a');
        }

        // copy window location into the anchor to get consistent results
        // when the port is default for the protocol (e.g. 80 for HTTP)
        a.href = window.location.href;

        // host includes both hostname and port if the port is not standard
        var host = a.host;
        var protocol = a.protocol;

        a.href = url;
        a.href = a.href; // IE only absolutizes href on get, not set

        return protocol !== a.protocol || host !== a.host;
    }

    return isCrossOriginUrl;
});

/*global define*/
define('Core/TaskProcessor',[
        '../ThirdParty/when',
        './buildModuleUrl',
        './defaultValue',
        './defined',
        './destroyObject',
        './DeveloperError',
        './getAbsoluteUri',
        './isCrossOriginUrl',
        './RuntimeError',
        'require'
    ], function(
        when,
        buildModuleUrl,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        getAbsoluteUri,
        isCrossOriginUrl,
        RuntimeError,
        require) {
    'use strict';

    function canTransferArrayBuffer() {
        if (!defined(TaskProcessor._canTransferArrayBuffer)) {
            var worker = new Worker(getWorkerUrl('Workers/transferTypedArrayTest.js'));
            worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);

            var value = 99;
            var array = new Int8Array([value]);

            try {
                // postMessage might fail with a DataCloneError
                // if transferring array buffers is not supported.
                worker.postMessage({
                    array : array
                }, [array.buffer]);
            } catch (e) {
                TaskProcessor._canTransferArrayBuffer = false;
                return TaskProcessor._canTransferArrayBuffer;
            }

            var deferred = when.defer();

            worker.onmessage = function(event) {
                var array = event.data.array;

                // some versions of Firefox silently fail to transfer typed arrays.
                // https://bugzilla.mozilla.org/show_bug.cgi?id=841904
                // Check to make sure the value round-trips successfully.
                var result = defined(array) && array[0] === value;
                deferred.resolve(result);

                worker.terminate();

                TaskProcessor._canTransferArrayBuffer = result;
            };

            TaskProcessor._canTransferArrayBuffer = deferred.promise;
        }

        return TaskProcessor._canTransferArrayBuffer;
    }

    function completeTask(processor, data) {
        --processor._activeTasks;

        var id = data.id;
        if (!defined(id)) {
            // This is not one of ours.
            return;
        }

        var deferreds = processor._deferreds;
        var deferred = deferreds[id];

        if (defined(data.error)) {
            var error = data.error;
            if (error.name === 'RuntimeError') {
                error = new RuntimeError(data.error.message);
                error.stack = data.error.stack;
            } else if (error.name === 'DeveloperError') {
                error = new DeveloperError(data.error.message);
                error.stack = data.error.stack;
            }
            deferred.reject(error);
        } else {
            deferred.resolve(data.result);
        }

        delete deferreds[id];
    }

    function getWorkerUrl(moduleID) {
        var url = buildModuleUrl(moduleID);

        if (isCrossOriginUrl(url)) {
            //to load cross-origin, create a shim worker from a blob URL
            var script = 'importScripts("' + url + '");';

            var blob;
            try {
                blob = new Blob([script], {
                    type : 'application/javascript'
                });
            } catch (e) {
                var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
                var blobBuilder = new BlobBuilder();
                blobBuilder.append(script);
                blob = blobBuilder.getBlob('application/javascript');
            }

            var URL = window.URL || window.webkitURL;
            url = URL.createObjectURL(blob);
        }

        return url;
    }

    var bootstrapperUrlResult;
    function getBootstrapperUrl() {
        if (!defined(bootstrapperUrlResult)) {
            bootstrapperUrlResult = getWorkerUrl('Workers/cesiumWorkerBootstrapper.js');
        }
        return bootstrapperUrlResult;
    }

    function createWorker(processor) {
        var worker = new Worker(getBootstrapperUrl());
        worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);

        var bootstrapMessage = {
            loaderConfig : {},
            workerModule : TaskProcessor._workerModulePrefix + processor._workerName
        };

        if (defined(TaskProcessor._loaderConfig)) {
            bootstrapMessage.loaderConfig = TaskProcessor._loaderConfig;
        } else if (defined(require.toUrl)) {
            bootstrapMessage.loaderConfig.baseUrl =
                getAbsoluteUri('..', buildModuleUrl('Workers/cesiumWorkerBootstrapper.js'));
        } else {
            bootstrapMessage.loaderConfig.paths = {
                'Workers' : buildModuleUrl('Workers')
            };
        }

        worker.postMessage(bootstrapMessage);

        worker.onmessage = function(event) {
            completeTask(processor, event.data);
        };

        return worker;
    }

    /**
     * A wrapper around a web worker that allows scheduling tasks for a given worker,
     * returning results asynchronously via a promise.
     *
     * The Worker is not constructed until a task is scheduled.
     *
     * @alias TaskProcessor
     * @constructor
     *
     * @param {String} workerName The name of the worker.  This is expected to be a script
     *                            in the Workers folder.
     * @param {Number} [maximumActiveTasks=5] The maximum number of active tasks.  Once exceeded,
     *                                        scheduleTask will not queue any more tasks, allowing
     *                                        work to be rescheduled in future frames.
     */
    function TaskProcessor(workerName, maximumActiveTasks) {
        this._workerName = workerName;
        this._maximumActiveTasks = defaultValue(maximumActiveTasks, 5);
        this._activeTasks = 0;
        this._deferreds = {};
        this._nextID = 0;
    }

    var emptyTransferableObjectArray = [];

    /**
     * Schedule a task to be processed by the web worker asynchronously.  If there are currently more
     * tasks active than the maximum set by the constructor, will immediately return undefined.
     * Otherwise, returns a promise that will resolve to the result posted back by the worker when
     * finished.
     *
     * @param {*} parameters Any input data that will be posted to the worker.
     * @param {Object[]} [transferableObjects] An array of objects contained in parameters that should be
     *                                      transferred to the worker instead of copied.
     * @returns {Promise.<Object>|undefined} Either a promise that will resolve to the result when available, or undefined
     *                    if there are too many active tasks,
     *
     * @example
     * var taskProcessor = new Cesium.TaskProcessor('myWorkerName');
     * var promise = taskProcessor.scheduleTask({
     *     someParameter : true,
     *     another : 'hello'
     * });
     * if (!Cesium.defined(promise)) {
     *     // too many active tasks - try again later
     * } else {
     *     Cesium.when(promise, function(result) {
     *         // use the result of the task
     *     });
     * }
     */
    TaskProcessor.prototype.scheduleTask = function(parameters, transferableObjects) {
        if (!defined(this._worker)) {
            this._worker = createWorker(this);
        }

        if (this._activeTasks >= this._maximumActiveTasks) {
            return undefined;
        }

        ++this._activeTasks;

        var processor = this;
        return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) {
            if (!defined(transferableObjects)) {
                transferableObjects = emptyTransferableObjectArray;
            } else if (!canTransferArrayBuffer) {
                transferableObjects.length = 0;
            }

            var id = processor._nextID++;
            var deferred = when.defer();
            processor._deferreds[id] = deferred;

            processor._worker.postMessage({
                id : id,
                parameters : parameters,
                canTransferArrayBuffer : canTransferArrayBuffer
            }, transferableObjects);

            return deferred.promise;
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see TaskProcessor#destroy
     */
    TaskProcessor.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys this object.  This will immediately terminate the Worker.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {undefined}
     */
    TaskProcessor.prototype.destroy = function() {
        if (defined(this._worker)) {
            this._worker.terminate();
        }
        return destroyObject(this);
    };

    // exposed for testing purposes
    TaskProcessor._defaultWorkerModulePrefix = 'Workers/';
    TaskProcessor._workerModulePrefix = TaskProcessor._defaultWorkerModulePrefix;
    TaskProcessor._loaderConfig = undefined;
    TaskProcessor._canTransferArrayBuffer = undefined;

    return TaskProcessor;
});

/*global define*/
define('Core/TerrainMesh',[
        './defaultValue'
    ], function(
        defaultValue) {
    'use strict';

    /**
      * A mesh plus related metadata for a single tile of terrain.  Instances of this type are
      * usually created from raw {@link TerrainData}.
      *
      * @alias TerrainMesh
      * @constructor
      *
      * @param {Cartesian3} center The center of the tile.  Vertex positions are specified relative to this center.
      * @param {Float32Array} vertices The vertex data, including positions, texture coordinates, and heights.
      *                       The vertex data is in the order [X, Y, Z, H, U, V], where X, Y, and Z represent
      *                       the Cartesian position of the vertex, H is the height above the ellipsoid, and
      *                       U and V are the texture coordinates.
      * @param {Uint16Array|Uint32Array} indices The indices describing how the vertices are connected to form triangles.
      * @param {Number} minimumHeight The lowest height in the tile, in meters above the ellipsoid.
      * @param {Number} maximumHeight The highest height in the tile, in meters above the ellipsoid.
      * @param {BoundingSphere} boundingSphere3D A bounding sphere that completely contains the tile.
      * @param {Cartesian3} occludeePointInScaledSpace The occludee point of the tile, represented in ellipsoid-
      *                     scaled space, and used for horizon culling.  If this point is below the horizon,
      *                     the tile is considered to be entirely below the horizon.
      * @param {Number} [vertexStride=6] The number of components in each vertex.
      * @param {OrientedBoundingBox} [orientedBoundingBox] A bounding box that completely contains the tile.
      * @param {TerrainEncoding} encoding Information used to decode the mesh.
      * @param {Number} exaggeration The amount that this mesh was exaggerated.
      *
      * @private
      */
    function TerrainMesh(center, vertices, indices, minimumHeight, maximumHeight, boundingSphere3D, occludeePointInScaledSpace, vertexStride, orientedBoundingBox, encoding, exaggeration) {
        /**
         * The center of the tile.  Vertex positions are specified relative to this center.
         * @type {Cartesian3}
         */
        this.center = center;

        /**
         * The vertex data, including positions, texture coordinates, and heights.
         * The vertex data is in the order [X, Y, Z, H, U, V], where X, Y, and Z represent
         * the Cartesian position of the vertex, H is the height above the ellipsoid, and
         * U and V are the texture coordinates.  The vertex data may have additional attributes after those
         * mentioned above when the {@link TerrainMesh#stride} is greater than 6.
         * @type {Float32Array}
         */
        this.vertices = vertices;

        /**
         * The number of components in each vertex.  Typically this is 6 for the 6 components
         * [X, Y, Z, H, U, V], but if each vertex has additional data (such as a vertex normal), this value
         * may be higher.
         * @type {Number}
         */
        this.stride = defaultValue(vertexStride, 6);

        /**
         * The indices describing how the vertices are connected to form triangles.
         * @type {Uint16Array|Uint32Array}
         */
        this.indices = indices;

        /**
         * The lowest height in the tile, in meters above the ellipsoid.
         * @type {Number}
         */
        this.minimumHeight = minimumHeight;

        /**
         * The highest height in the tile, in meters above the ellipsoid.
         * @type {Number}
         */
        this.maximumHeight = maximumHeight;

        /**
         * A bounding sphere that completely contains the tile.
         * @type {BoundingSphere}
         */
        this.boundingSphere3D = boundingSphere3D;

        /**
         * The occludee point of the tile, represented in ellipsoid-
         * scaled space, and used for horizon culling.  If this point is below the horizon,
         * the tile is considered to be entirely below the horizon.
         * @type {Cartesian3}
         */
        this.occludeePointInScaledSpace = occludeePointInScaledSpace;

        /**
         * A bounding box that completely contains the tile.
         * @type {OrientedBoundingBox}
         */
        this.orientedBoundingBox = orientedBoundingBox;

        /**
         * Information for decoding the mesh vertices.
         * @type {TerrainEncoding}
         */
        this.encoding = encoding;

        /**
         * The amount that this mesh was exaggerated.
         * @type {Number}
         */
        this.exaggeration = exaggeration;
    }

    return TerrainMesh;
});

/*global define*/
define('Core/TerrainProvider',[
        './defined',
        './defineProperties',
        './DeveloperError',
        './Math'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        CesiumMath) {
    'use strict';

    /**
     * Provides terrain or other geometry for the surface of an ellipsoid.  The surface geometry is
     * organized into a pyramid of tiles according to a {@link TilingScheme}.  This type describes an
     * interface and is not intended to be instantiated directly.
     *
     * @alias TerrainProvider
     * @constructor
     *
     * @see EllipsoidTerrainProvider
     * @see CesiumTerrainProvider
     * @see ArcGisImageServerTerrainProvider
     */
    function TerrainProvider() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(TerrainProvider.prototype, {
        /**
         * Gets an event that is raised when the terrain provider encounters an asynchronous error..  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof TerrainProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the credit to display when this terrain provider is active.  Typically this is used to credit
         * the source of the terrain. This function should
         * not be called before {@link TerrainProvider#ready} returns true.
         * @memberof TerrainProvider.prototype
         * @type {Credit}
         */
        credit : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the tiling scheme used by the provider.  This function should
         * not be called before {@link TerrainProvider#ready} returns true.
         * @memberof TerrainProvider.prototype
         * @type {TilingScheme}
         */
        tilingScheme : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof TerrainProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof TerrainProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a value indicating whether or not the provider includes a water mask.  The water mask
         * indicates which areas of the globe are water rather than land, so they can be rendered
         * as a reflective surface with animated waves.  This function should not be
         * called before {@link TerrainProvider#ready} returns true.
         * @memberof TerrainProvider.prototype
         * @type {Boolean}
         */
        hasWaterMask : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a value indicating whether or not the requested tiles include vertex normals.
         * This function should not be called before {@link TerrainProvider#ready} returns true.
         * @memberof TerrainProvider.prototype
         * @type {Boolean}
         */
        hasVertexNormals : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets an object that can be used to determine availability of terrain from this provider, such as
         * at points and in rectangles.  This function should not be called before
         * {@link TerrainProvider#ready} returns true.  This property may be undefined if availability
         * information is not available.
         * @memberof TerrainProvider.prototype
         * @type {TileAvailability}
         */
        availability : {
            get : DeveloperError.throwInstantiationError
        }
    });

    var regularGridIndexArrays = [];

    /**
     * Gets a list of indices for a triangle mesh representing a regular grid.  Calling
     * this function multiple times with the same grid width and height returns the
     * same list of indices.  The total number of vertices must be less than or equal
     * to 65536.
     *
     * @param {Number} width The number of vertices in the regular grid in the horizontal direction.
     * @param {Number} height The number of vertices in the regular grid in the vertical direction.
     * @returns {Uint16Array} The list of indices.
     */
    TerrainProvider.getRegularGridIndices = function(width, height) {
                if (width * height >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
            throw new DeveloperError('The total number of vertices (width * height) must be less than 65536.');
        }
        
        var byWidth = regularGridIndexArrays[width];
        if (!defined(byWidth)) {
            regularGridIndexArrays[width] = byWidth = [];
        }

        var indices = byWidth[height];
        if (!defined(indices)) {
            indices = byWidth[height] = new Uint16Array((width - 1) * (height - 1) * 6);

            var index = 0;
            var indicesIndex = 0;
            for (var j = 0; j < height - 1; ++j) {
                for (var i = 0; i < width - 1; ++i) {
                    var upperLeft = index;
                    var lowerLeft = upperLeft + width;
                    var lowerRight = lowerLeft + 1;
                    var upperRight = upperLeft + 1;

                    indices[indicesIndex++] = upperLeft;
                    indices[indicesIndex++] = lowerLeft;
                    indices[indicesIndex++] = upperRight;
                    indices[indicesIndex++] = upperRight;
                    indices[indicesIndex++] = lowerLeft;
                    indices[indicesIndex++] = lowerRight;

                    ++index;
                }
                ++index;
            }
        }

        return indices;
    };

    /**
     * Specifies the quality of terrain created from heightmaps.  A value of 1.0 will
     * ensure that adjacent heightmap vertices are separated by no more than
     * {@link Globe.maximumScreenSpaceError} screen pixels and will probably go very slowly.
     * A value of 0.5 will cut the estimated level zero geometric error in half, allowing twice the
     * screen pixels between adjacent heightmap vertices and thus rendering more quickly.
     * @type {Number}
     */
    TerrainProvider.heightmapTerrainQuality = 0.25;

    /**
     * Determines an appropriate geometric error estimate when the geometry comes from a heightmap.
     *
     * @param {Ellipsoid} ellipsoid The ellipsoid to which the terrain is attached.
     * @param {Number} tileImageWidth The width, in pixels, of the heightmap associated with a single tile.
     * @param {Number} numberOfTilesAtLevelZero The number of tiles in the horizontal direction at tile level zero.
     * @returns {Number} An estimated geometric error.
     */
    TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap = function(ellipsoid, tileImageWidth, numberOfTilesAtLevelZero) {
        return ellipsoid.maximumRadius * 2 * Math.PI * TerrainProvider.heightmapTerrainQuality / (tileImageWidth * numberOfTilesAtLevelZero);
    };

    /**
     * Requests the geometry for a given tile.  This function should not be called before
     * {@link TerrainProvider#ready} returns true.  The result must include terrain data and
     * may optionally include a water mask and an indication of which child tiles are available.
     * @function
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
     *                  or false if the request should be initiated regardless of the number of requests
     *                  already in progress.
     * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry.  If this method
     *          returns undefined instead of a promise, it is an indication that too many requests are already
     *          pending and the request will be retried later.
     */
    TerrainProvider.prototype.requestTileGeometry = DeveloperError.throwInstantiationError;

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.  This function should not be
     * called before {@link TerrainProvider#ready} returns true.
     * @function
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    TerrainProvider.prototype.getLevelMaximumGeometricError = DeveloperError.throwInstantiationError;

    /**
     * Determines whether data for a tile is available to be loaded.
     * @function
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @returns {Boolean} Undefined if not supported by the terrain provider, otherwise true or false.
     */
    TerrainProvider.prototype.getTileDataAvailable = DeveloperError.throwInstantiationError;

    return TerrainProvider;
});

/*global define*/
define('Core/HeightmapTerrainData',[
        '../ThirdParty/when',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './GeographicTilingScheme',
        './HeightmapTessellator',
        './Math',
        './Rectangle',
        './TaskProcessor',
        './TerrainEncoding',
        './TerrainMesh',
        './TerrainProvider'
    ], function(
        when,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        GeographicTilingScheme,
        HeightmapTessellator,
        CesiumMath,
        Rectangle,
        TaskProcessor,
        TerrainEncoding,
        TerrainMesh,
        TerrainProvider) {
    'use strict';

    /**
     * Terrain data for a single tile where the terrain data is represented as a heightmap.  A heightmap
     * is a rectangular array of heights in row-major order from south to north and west to east.
     *
     * @alias HeightmapTerrainData
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {TypedArray} options.buffer The buffer containing height data.
     * @param {Number} options.width The width (longitude direction) of the heightmap, in samples.
     * @param {Number} options.height The height (latitude direction) of the heightmap, in samples.
     * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
     *                 If a child's bit is set, geometry will be requested for that tile as well when it
     *                 is needed.  If the bit is cleared, the child tile is not requested and geometry is
     *                 instead upsampled from the parent.  The bit values are as follows:
     *                 <table>
     *                  <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
     *                  <tr><td>0</td><td>1</td><td>Southwest</td></tr>
     *                  <tr><td>1</td><td>2</td><td>Southeast</td></tr>
     *                  <tr><td>2</td><td>4</td><td>Northwest</td></tr>
     *                  <tr><td>3</td><td>8</td><td>Northeast</td></tr>
     *                 </table>
     * @param {Object} [options.structure] An object describing the structure of the height data.
     * @param {Number} [options.structure.heightScale=1.0] The factor by which to multiply height samples in order to obtain
     *                 the height above the heightOffset, in meters.  The heightOffset is added to the resulting
     *                 height after multiplying by the scale.
     * @param {Number} [options.structure.heightOffset=0.0] The offset to add to the scaled height to obtain the final
     *                 height in meters.  The offset is added after the height sample is multiplied by the
     *                 heightScale.
     * @param {Number} [options.structure.elementsPerHeight=1] The number of elements in the buffer that make up a single height
     *                 sample.  This is usually 1, indicating that each element is a separate height sample.  If
     *                 it is greater than 1, that number of elements together form the height sample, which is
     *                 computed according to the structure.elementMultiplier and structure.isBigEndian properties.
     * @param {Number} [options.structure.stride=1] The number of elements to skip to get from the first element of
     *                 one height to the first element of the next height.
     * @param {Number} [options.structure.elementMultiplier=256.0] The multiplier used to compute the height value when the
     *                 stride property is greater than 1.  For example, if the stride is 4 and the strideMultiplier
     *                 is 256, the height is computed as follows:
     *                 `height = buffer[index] + buffer[index + 1] * 256 + buffer[index + 2] * 256 * 256 + buffer[index + 3] * 256 * 256 * 256`
     *                 This is assuming that the isBigEndian property is false.  If it is true, the order of the
     *                 elements is reversed.
     * @param {Boolean} [options.structure.isBigEndian=false] Indicates endianness of the elements in the buffer when the
     *                  stride property is greater than 1.  If this property is false, the first element is the
     *                  low-order element.  If it is true, the first element is the high-order element.
     * @param {Number} [options.structure.lowestEncodedHeight] The lowest value that can be stored in the height buffer.  Any heights that are lower
     *                 than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value.  For example, if the height
     *                 buffer is a `Uint16Array`, this value should be 0 because a `Uint16Array` cannot store negative numbers.  If this parameter is
     *                 not specified, no minimum value is enforced.
     * @param {Number} [options.structure.highestEncodedHeight] The highest value that can be stored in the height buffer.  Any heights that are higher
     *                 than this value after encoding with the `heightScale` and `heightOffset` are clamped to this value.  For example, if the height
     *                 buffer is a `Uint16Array`, this value should be `256 * 256 - 1` or 65535 because a `Uint16Array` cannot store numbers larger
     *                 than 65535.  If this parameter is not specified, no maximum value is enforced.
     * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
     *                  otherwise, false.
     *
     *
     * @example
     * var buffer = ...
     * var heightBuffer = new Uint16Array(buffer, 0, that._heightmapWidth * that._heightmapWidth);
     * var childTileMask = new Uint8Array(buffer, heightBuffer.byteLength, 1)[0];
     * var waterMask = new Uint8Array(buffer, heightBuffer.byteLength + 1, buffer.byteLength - heightBuffer.byteLength - 1);
     * var terrainData = new Cesium.HeightmapTerrainData({
     *   buffer : heightBuffer,
     *   width : 65,
     *   height : 65,
     *   childTileMask : childTileMask,
     *   waterMask : waterMask
     * });
     *
     * @see TerrainData
     * @see QuantizedMeshTerrainData
     */
    function HeightmapTerrainData(options) {
                if (!defined(options) || !defined(options.buffer)) {
            throw new DeveloperError('options.buffer is required.');
        }
        if (!defined(options.width)) {
            throw new DeveloperError('options.width is required.');
        }
        if (!defined(options.height)) {
            throw new DeveloperError('options.height is required.');
        }
        
        this._buffer = options.buffer;
        this._width = options.width;
        this._height = options.height;
        this._childTileMask = defaultValue(options.childTileMask, 15);

        var defaultStructure = HeightmapTessellator.DEFAULT_STRUCTURE;
        var structure = options.structure;
        if (!defined(structure)) {
            structure = defaultStructure;
        } else if (structure !== defaultStructure) {
            structure.heightScale = defaultValue(structure.heightScale, defaultStructure.heightScale);
            structure.heightOffset = defaultValue(structure.heightOffset, defaultStructure.heightOffset);
            structure.elementsPerHeight = defaultValue(structure.elementsPerHeight, defaultStructure.elementsPerHeight);
            structure.stride = defaultValue(structure.stride, defaultStructure.stride);
            structure.elementMultiplier = defaultValue(structure.elementMultiplier, defaultStructure.elementMultiplier);
            structure.isBigEndian = defaultValue(structure.isBigEndian, defaultStructure.isBigEndian);
        }

        this._structure = structure;
        this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
        this._waterMask = options.waterMask;

        this._skirtHeight = undefined;
        this._bufferType = this._buffer.constructor;
        this._mesh = undefined;
    }

    defineProperties(HeightmapTerrainData.prototype, {
        /**
         * The water mask included in this terrain data, if any.  A water mask is a rectangular
         * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
         * Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
         * @memberof HeightmapTerrainData.prototype
         * @type {Uint8Array|Image|Canvas}
         */
        waterMask : {
            get : function() {
                return this._waterMask;
            }
        }
    });


    var taskProcessor = new TaskProcessor('createVerticesFromHeightmap');

    /**
     * Creates a {@link TerrainMesh} from this terrain data.
     *
     * @private
     *
     * @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
     * @param {Number} x The X coordinate of the tile for which to create the terrain data.
     * @param {Number} y The Y coordinate of the tile for which to create the terrain data.
     * @param {Number} level The level of the tile for which to create the terrain data.
     * @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain.
     * @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
     *          asynchronous mesh creations are already in progress and the operation should
     *          be retried later.
     */
    HeightmapTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) {
                if (!defined(tilingScheme)) {
            throw new DeveloperError('tilingScheme is required.');
        }
        if (!defined(x)) {
            throw new DeveloperError('x is required.');
        }
        if (!defined(y)) {
            throw new DeveloperError('y is required.');
        }
        if (!defined(level)) {
            throw new DeveloperError('level is required.');
        }
        
        var ellipsoid = tilingScheme.ellipsoid;
        var nativeRectangle = tilingScheme.tileXYToNativeRectangle(x, y, level);
        var rectangle = tilingScheme.tileXYToRectangle(x, y, level);
        exaggeration = defaultValue(exaggeration, 1.0);

        // Compute the center of the tile for RTC rendering.
        var center = ellipsoid.cartographicToCartesian(Rectangle.center(rectangle));

        var structure = this._structure;

        var levelZeroMaxError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, this._width, tilingScheme.getNumberOfXTilesAtLevel(0));
        var thisLevelMaxError = levelZeroMaxError / (1 << level);
        this._skirtHeight = Math.min(thisLevelMaxError * 4.0, 1000.0);

        var verticesPromise = taskProcessor.scheduleTask({
            heightmap : this._buffer,
            structure : structure,
            includeWebMercatorT : true,
            width : this._width,
            height : this._height,
            nativeRectangle : nativeRectangle,
            rectangle : rectangle,
            relativeToCenter : center,
            ellipsoid : ellipsoid,
            skirtHeight : this._skirtHeight,
            isGeographic : tilingScheme instanceof GeographicTilingScheme,
            exaggeration : exaggeration
        });

        if (!defined(verticesPromise)) {
            // Postponed
            return undefined;
        }

        var that = this;
        return when(verticesPromise, function(result) {
            that._mesh = new TerrainMesh(
                    center,
                    new Float32Array(result.vertices),
                    TerrainProvider.getRegularGridIndices(result.gridWidth, result.gridHeight),
                    result.minimumHeight,
                    result.maximumHeight,
                    result.boundingSphere3D,
                    result.occludeePointInScaledSpace,
                    result.numberOfAttributes,
                    result.orientedBoundingBox,
                    TerrainEncoding.clone(result.encoding),
                    exaggeration);

            // Free memory received from server after mesh is created.
            that._buffer = undefined;
            return that._mesh;
        });
    };

    /**
     * Computes the terrain height at a specified longitude and latitude.
     *
     * @param {Rectangle} rectangle The rectangle covered by this terrain data.
     * @param {Number} longitude The longitude in radians.
     * @param {Number} latitude The latitude in radians.
     * @returns {Number} The terrain height at the specified position.  If the position
     *          is outside the rectangle, this method will extrapolate the height, which is likely to be wildly
     *          incorrect for positions far outside the rectangle.
     */
    HeightmapTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) {
        var width = this._width;
        var height = this._height;

        var structure = this._structure;
        var stride = structure.stride;
        var elementsPerHeight = structure.elementsPerHeight;
        var elementMultiplier = structure.elementMultiplier;
        var isBigEndian = structure.isBigEndian;
        var heightOffset = structure.heightOffset;
        var heightScale = structure.heightScale;

        var heightSample;
        if (defined(this._mesh)) {
            var buffer = this._mesh.vertices;
            var encoding = this._mesh.encoding;
            var skirtHeight = this._skirtHeight;
            var exaggeration = this._mesh.exaggeration;
            heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, rectangle, width, height, longitude, latitude, exaggeration);
        } else {
            heightSample = interpolateHeight(this._buffer, elementsPerHeight, elementMultiplier, stride, isBigEndian, rectangle, width, height, longitude, latitude);
            heightSample = heightSample * heightScale + heightOffset;
        }

        return heightSample;
    };

    /**
     * Upsamples this terrain data for use by a descendant tile.  The resulting instance will contain a subset of the
     * height samples in this instance, interpolated if necessary.
     *
     * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
     * @param {Number} thisX The X coordinate of this tile in the tiling scheme.
     * @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
     * @param {Number} thisLevel The level of this tile in the tiling scheme.
     * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
     * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
     * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
     * @returns {Promise.<HeightmapTerrainData>|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
     *          or undefined if too many asynchronous upsample operations are in progress and the request has been
     *          deferred.
     */
    HeightmapTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) {
                if (!defined(tilingScheme)) {
            throw new DeveloperError('tilingScheme is required.');
        }
        if (!defined(thisX)) {
            throw new DeveloperError('thisX is required.');
        }
        if (!defined(thisY)) {
            throw new DeveloperError('thisY is required.');
        }
        if (!defined(thisLevel)) {
            throw new DeveloperError('thisLevel is required.');
        }
        if (!defined(descendantX)) {
            throw new DeveloperError('descendantX is required.');
        }
        if (!defined(descendantY)) {
            throw new DeveloperError('descendantY is required.');
        }
        if (!defined(descendantLevel)) {
            throw new DeveloperError('descendantLevel is required.');
        }
        var levelDifference = descendantLevel - thisLevel;
        if (levelDifference > 1) {
            throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.');
        }
        
        var width = this._width;
        var height = this._height;
        var structure = this._structure;
        var skirtHeight = this._skirtHeight;
        var stride = structure.stride;

        var heights = new this._bufferType(width * height * stride);
        var meshData = this._mesh;
        if (!defined(meshData)) {
            return undefined;
        }

        var buffer = meshData.vertices;
        var encoding = meshData.encoding;

        // PERFORMANCE_IDEA: don't recompute these rectangles - the caller already knows them.
        var sourceRectangle = tilingScheme.tileXYToRectangle(thisX, thisY, thisLevel);
        var destinationRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel);

        var heightOffset = structure.heightOffset;
        var heightScale = structure.heightScale;
        var exaggeration = meshData.exaggeration;

        var elementsPerHeight = structure.elementsPerHeight;
        var elementMultiplier = structure.elementMultiplier;
        var isBigEndian = structure.isBigEndian;

        var divisor = Math.pow(elementMultiplier, elementsPerHeight - 1);

        for (var j = 0; j < height; ++j) {
            var latitude = CesiumMath.lerp(destinationRectangle.north, destinationRectangle.south, j / (height - 1));
            for (var i = 0; i < width; ++i) {
                var longitude = CesiumMath.lerp(destinationRectangle.west, destinationRectangle.east, i / (width - 1));
                var heightSample = interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration);

                // Use conditionals here instead of Math.min and Math.max so that an undefined
                // lowestEncodedHeight or highestEncodedHeight has no effect.
                heightSample = heightSample < structure.lowestEncodedHeight ? structure.lowestEncodedHeight : heightSample;
                heightSample = heightSample > structure.highestEncodedHeight ? structure.highestEncodedHeight : heightSample;

                setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, j * width + i, heightSample);
            }
        }

        return new HeightmapTerrainData({
            buffer : heights,
            width : width,
            height : height,
            childTileMask : 0,
            structure : this._structure,
            createdByUpsampling : true
        });
    };

    /**
     * Determines if a given child tile is available, based on the
     * {@link HeightmapTerrainData.childTileMask}.  The given child tile coordinates are assumed
     * to be one of the four children of this tile.  If non-child tile coordinates are
     * given, the availability of the southeast child tile is returned.
     *
     * @param {Number} thisX The tile X coordinate of this (the parent) tile.
     * @param {Number} thisY The tile Y coordinate of this (the parent) tile.
     * @param {Number} childX The tile X coordinate of the child tile to check for availability.
     * @param {Number} childY The tile Y coordinate of the child tile to check for availability.
     * @returns {Boolean} True if the child tile is available; otherwise, false.
     */
    HeightmapTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) {
                if (!defined(thisX)) {
            throw new DeveloperError('thisX is required.');
        }
        if (!defined(thisY)) {
            throw new DeveloperError('thisY is required.');
        }
        if (!defined(childX)) {
            throw new DeveloperError('childX is required.');
        }
        if (!defined(childY)) {
            throw new DeveloperError('childY is required.');
        }
        
        var bitNumber = 2; // northwest child
        if (childX !== thisX * 2) {
            ++bitNumber; // east child
        }
        if (childY !== thisY * 2) {
            bitNumber -= 2; // south child
        }

        return (this._childTileMask & (1 << bitNumber)) !== 0;
    };

    /**
     * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
     * terrain data.  If this value is false, the data was obtained from some other source, such
     * as by downloading it from a remote server.  This method should return true for instances
     * returned from a call to {@link HeightmapTerrainData#upsample}.
     *
     * @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
     */
    HeightmapTerrainData.prototype.wasCreatedByUpsampling = function() {
        return this._createdByUpsampling;
    };

    function interpolateHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, sourceRectangle, width, height, longitude, latitude) {
        var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
        var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south);

        var westInteger = fromWest | 0;
        var eastInteger = westInteger + 1;
        if (eastInteger >= width) {
            eastInteger = width - 1;
            westInteger = width - 2;
        }

        var southInteger = fromSouth | 0;
        var northInteger = southInteger + 1;
        if (northInteger >= height) {
            northInteger = height - 1;
            southInteger = height - 2;
        }

        var dx = fromWest - westInteger;
        var dy = fromSouth - southInteger;

        southInteger = height - 1 - southInteger;
        northInteger = height - 1 - northInteger;

        var southwestHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, southInteger * width + westInteger);
        var southeastHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, southInteger * width + eastInteger);
        var northwestHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, northInteger * width + westInteger);
        var northeastHeight = getHeight(sourceHeights, elementsPerHeight, elementMultiplier, stride, isBigEndian, northInteger * width + eastInteger);

        return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight);
    }

    function interpolateMeshHeight(buffer, encoding, heightOffset, heightScale, skirtHeight, sourceRectangle, width, height, longitude, latitude, exaggeration) {
        // returns a height encoded according to the structure's heightScale and heightOffset.
        var fromWest = (longitude - sourceRectangle.west) * (width - 1) / (sourceRectangle.east - sourceRectangle.west);
        var fromSouth = (latitude - sourceRectangle.south) * (height - 1) / (sourceRectangle.north - sourceRectangle.south);

        if (skirtHeight > 0) {
            fromWest += 1.0;
            fromSouth += 1.0;

            width += 2;
            height += 2;
        }

        var widthEdge = (skirtHeight > 0) ? width - 1 : width;
        var westInteger = fromWest | 0;
        var eastInteger = westInteger + 1;
        if (eastInteger >= widthEdge) {
            eastInteger = width - 1;
            westInteger = width - 2;
        }

        var heightEdge = (skirtHeight > 0) ? height - 1 : height;
        var southInteger = fromSouth | 0;
        var northInteger = southInteger + 1;
        if (northInteger >= heightEdge) {
            northInteger = height - 1;
            southInteger = height - 2;
        }

        var dx = fromWest - westInteger;
        var dy = fromSouth - southInteger;

        southInteger = height - 1 - southInteger;
        northInteger = height - 1 - northInteger;

        var southwestHeight = (encoding.decodeHeight(buffer, southInteger * width + westInteger) / exaggeration - heightOffset) / heightScale;
        var southeastHeight = (encoding.decodeHeight(buffer, southInteger * width + eastInteger) / exaggeration - heightOffset) / heightScale;
        var northwestHeight = (encoding.decodeHeight(buffer, northInteger * width + westInteger) / exaggeration - heightOffset) / heightScale;
        var northeastHeight = (encoding.decodeHeight(buffer, northInteger * width + eastInteger) / exaggeration - heightOffset) / heightScale;

        return triangleInterpolateHeight(dx, dy, southwestHeight, southeastHeight, northwestHeight, northeastHeight);
    }

    function triangleInterpolateHeight(dX, dY, southwestHeight, southeastHeight, northwestHeight, northeastHeight) {
        // The HeightmapTessellator bisects the quad from southwest to northeast.
        if (dY < dX) {
            // Lower right triangle
            return southwestHeight + (dX * (southeastHeight - southwestHeight)) + (dY * (northeastHeight - southeastHeight));
        }

        // Upper left triangle
        return southwestHeight + (dX * (northeastHeight - northwestHeight)) + (dY * (northwestHeight - southwestHeight));
    }

    function getHeight(heights, elementsPerHeight, elementMultiplier, stride, isBigEndian, index) {
        index *= stride;

        var height = 0;
        var i;

        if (isBigEndian) {
            for (i = 0; i < elementsPerHeight; ++i) {
                height = (height * elementMultiplier) + heights[index + i];
            }
        } else {
            for (i = elementsPerHeight - 1; i >= 0; --i) {
                height = (height * elementMultiplier) + heights[index + i];
            }
        }

        return height;
    }

    function setHeight(heights, elementsPerHeight, elementMultiplier, divisor, stride, isBigEndian, index, height) {
        index *= stride;

        var i;
        if (isBigEndian) {
            for (i = 0; i < elementsPerHeight - 1; ++i) {
                heights[index + i] = (height / divisor) | 0;
                height -= heights[index + i] * divisor;
                divisor /= elementMultiplier;
            }
        } else {
            for (i = elementsPerHeight - 1; i > 0; --i) {
                heights[index + i] = (height / divisor) | 0;
                height -= heights[index + i] * divisor;
                divisor /= elementMultiplier;
            }
        }
        heights[index + i] = height;
    }

    return HeightmapTerrainData;
});

/*global define*/
define('Core/loadImage',[
        '../ThirdParty/when',
        './defaultValue',
        './defined',
        './DeveloperError',
        './isCrossOriginUrl',
        './TrustedServers'
    ], function(
        when,
        defaultValue,
        defined,
        DeveloperError,
        isCrossOriginUrl,
        TrustedServers) {
    'use strict';

    var dataUriRegex = /^data:/;

    /**
     * Asynchronously loads the given image URL.  Returns a promise that will resolve to
     * an {@link Image} once loaded, or reject if the image failed to load.
     *
     * @exports loadImage
     *
     * @param {String|Promise.<String>} url The source of the image, or a promise for the URL.
     * @param {Boolean} [allowCrossOrigin=true] Whether to request the image using Cross-Origin
     *        Resource Sharing (CORS).  CORS is only actually used if the image URL is actually cross-origin.
     *        Data URIs are never requested using CORS.
     * @returns {Promise.<Image>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load a single image asynchronously
     * Cesium.loadImage('some/image/url.png').then(function(image) {
     *     // use the loaded image
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     *
     * // load several images in parallel
     * when.all([loadImage('image1.png'), loadImage('image2.png')]).then(function(images) {
     *     // images is an array containing all the loaded images
     * });
     * 
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadImage(url, allowCrossOrigin) {
                if (!defined(url)) {
            throw new DeveloperError('url is required.');
        }
        
        allowCrossOrigin = defaultValue(allowCrossOrigin, true);

        return when(url, function(url) {
            var crossOrigin;

            // data URIs can't have allowCrossOrigin set.
            if (dataUriRegex.test(url) || !allowCrossOrigin) {
                crossOrigin = false;
            } else {
                crossOrigin = isCrossOriginUrl(url);
            }

            var deferred = when.defer();

            loadImage.createImage(url, crossOrigin, deferred);

            return deferred.promise;
        });
    }

    // This is broken out into a separate function so that it can be mocked for testing purposes.
    loadImage.createImage = function(url, crossOrigin, deferred) {
        var image = new Image();

        image.onload = function() {
            deferred.resolve(image);
        };

        image.onerror = function(e) {
            deferred.reject(e);
        };

        if (crossOrigin) {
            if (TrustedServers.contains(url)) {
                image.crossOrigin = 'use-credentials';
            } else {
                image.crossOrigin = '';
            }
        }

        image.src = url;
    };

    loadImage.defaultCreateImage = loadImage.createImage;

    return loadImage;
});

/*global define*/
define('Core/throttleRequestByServer',[
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        './defaultValue'
    ], function(
        Uri,
        when,
        defaultValue) {
    'use strict';

    var activeRequests = {};

    var pageUri = typeof document !== 'undefined' ? new Uri(document.location.href) : new Uri();
    function getServer(url) {
        var uri = new Uri(url).resolve(pageUri);
        uri.normalize();
        var server = uri.authority;
        if (!/:/.test(server)) {
            server = server + ':' + (uri.scheme === 'https' ? '443' : '80');
        }
        return server;
    }

    /**
     * Because browsers throttle the number of parallel requests allowed to each server,
     * this function tracks the number of active requests in progress to each server, and
     * returns undefined immediately if the request would exceed the maximum, allowing
     * the caller to retry later, instead of queueing indefinitely under the browser's control.
     *
     * @exports throttleRequestByServer
     *
     * @param {String} url The URL to request.
     * @param {throttleRequestByServer~RequestFunction} requestFunction The actual function that
     *        makes the request.
     * @returns {Promise.<Object>|undefined} Either undefined, meaning the request would exceed the maximum number of
     *          parallel requests, or a Promise for the requested data.
     *
     *
     * @example
     * // throttle requests for an image
     * var url = 'http://madeupserver.example.com/myImage.png';
     * function requestFunction(url) {
     *   // in this simple example, loadImage could be used directly as requestFunction.
     *   return Cesium.loadImage(url);
     * };
     * var promise = Cesium.throttleRequestByServer(url, requestFunction);
     * if (!Cesium.defined(promise)) {
     *   // too many active requests in progress, try again later.
     * } else {
     *   promise.then(function(image) {
     *     // handle loaded image
     *   });
     * }
     * 
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function throttleRequestByServer(url, requestFunction) {
        var server = getServer(url);

        var activeRequestsForServer = defaultValue(activeRequests[server], 0);
        if (activeRequestsForServer >= throttleRequestByServer.maximumRequestsPerServer) {
            return undefined;
        }

        activeRequests[server] = activeRequestsForServer + 1;

        return when(requestFunction(url), function(result) {
            activeRequests[server]--;
            return result;
        }).otherwise(function(error) {
            activeRequests[server]--;
            return when.reject(error);
        });
    }

    /**
     * Specifies the maximum number of requests that can be simultaneously open to a single server.  If this value is higher than
     * the number of requests per server actually allowed by the web browser, Cesium's ability to prioritize requests will be adversely
     * affected.
     * @type {Number}
     * @default 6
     */
    throttleRequestByServer.maximumRequestsPerServer = 6;

    /**
     * A function that will make a request if there are available slots to the server.
     * @callback throttleRequestByServer~RequestFunction
     *
     * @param {String} url The url to request.
     * @returns {Promise.<Object>} A promise for the requested data.
     */

    return throttleRequestByServer;
});

/*global define*/
define('Core/ArcGisImageServerTerrainProvider',[
        '../ThirdParty/when',
        './Credit',
        './defaultValue',
        './defined',
        './defineProperties',
        './deprecationWarning',
        './DeveloperError',
        './Ellipsoid',
        './Event',
        './GeographicTilingScheme',
        './getImagePixels',
        './HeightmapTerrainData',
        './loadImage',
        './Math',
        './TerrainProvider',
        './throttleRequestByServer'
    ], function(
        when,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        deprecationWarning,
        DeveloperError,
        Ellipsoid,
        Event,
        GeographicTilingScheme,
        getImagePixels,
        HeightmapTerrainData,
        loadImage,
        CesiumMath,
        TerrainProvider,
        throttleRequestByServer) {
    'use strict';

    /**
     * A {@link TerrainProvider} that produces terrain geometry by tessellating height maps
     * retrieved from an ArcGIS ImageServer.
     *
     * @alias ArcGisImageServerTerrainProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The URL of the ArcGIS ImageServer service.
     * @param {String} [options.token] The authorization token to use to connect to the service.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
     * @param {TilingScheme} [options.tilingScheme] The tiling scheme specifying how the terrain
     *                       is broken into tiles.  If this parameter is not provided, a {@link GeographicTilingScheme}
     *                       is used.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     *                    this parameter is ignored and the tiling scheme's ellipsoid is used instead.
     *                    If neither parameter is specified, the WGS84 ellipsoid is used.
     * @param {Credit|String} [options.credit] The credit, which will is displayed on the canvas.
     *
     *
     * @example
     * var terrainProvider = new Cesium.ArcGisImageServerTerrainProvider({
     *   url : 'https://elevation.arcgisonline.com/ArcGIS/rest/services/WorldElevation/DTMEllipsoidal/ImageServer',
     *   token : 'KED1aF_I4UzXOHy3BnhwyBHU4l5oY6rO6walkmHoYqGp4XyIWUd5YZUC1ZrLAzvV40pR6gBXQayh0eFA8m6vPg..',
     *   proxy : new Cesium.DefaultProxy('/terrain/')
     * });
     * viewer.terrainProvider = terrainProvider;
     *
     *  @see TerrainProvider
     *
     *  @deprecated
     */
    function ArcGisImageServerTerrainProvider(options) {
                if (!defined(options) || !defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        deprecationWarning('ArcGisImageServerTerrainProvider', 'ArcGisImageServerTerrainProvider was deprecated in Cesium 1.31.  It will be removed in 1.32.');

        this._url = options.url;
        this._token = options.token;

        this._tilingScheme = options.tilingScheme;
        if (!defined(this._tilingScheme)) {
            this._tilingScheme = new GeographicTilingScheme({
                ellipsoid : defaultValue(options.ellipsoid, Ellipsoid.WGS84)
            });
        }

        this._heightmapWidth = 65;
        this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._heightmapWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0));

        this._proxy = options.proxy;

        this._terrainDataStructure = {
            heightScale : 1.0 / 1000.0,
            heightOffset : -1000.0,
            elementsPerHeight : 3,
            stride : 4,
            elementMultiplier : 256.0,
            isBigEndian : true,
            lowestEncodedHeight : 0,
            highestEncodedHeight : 256 * 256 * 256 - 1
        };

        this._errorEvent = new Event();

        var credit = options.credit;
        if (typeof credit === 'string') {
            credit = new Credit(credit);
        }
        this._credit = credit;
        this._readyPromise = when.resolve(true);
    }

    defineProperties(ArcGisImageServerTerrainProvider.prototype, {
        /**
         * Gets an event that is raised when the terrain provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets the credit to display when this terrain provider is active.  Typically this is used to credit
         * the source of the terrain.  This function should not be called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {Credit}
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {GeographicTilingScheme}
         */
        tilingScheme : {
            get : function() {
                return this._tilingScheme;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : function() {
                return true;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        },

        /**
         * Gets a value indicating whether or not the provider includes a water mask.  The water mask
         * indicates which areas of the globe are water rather than land, so they can be rendered
         * as a reflective surface with animated waves.  This function should not be
         * called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {Boolean}
         */
        hasWaterMask : {
            get : function() {
                return false;
            }
        },

        /**
         * Gets a value indicating whether or not the requested tiles include vertex normals.
         * This function should not be called before {@link ArcGisImageServerTerrainProvider#ready} returns true.
         * @memberof ArcGisImageServerTerrainProvider.prototype
         * @type {Boolean}
         */
        hasVertexNormals : {
            get : function() {
                return false;
            }
        }
    });

    /**
     * Requests the geometry for a given tile.  This function should not be called before
     * {@link ArcGisImageServerTerrainProvider#ready} returns true.  The result includes terrain
     * data and indicates that all child tiles are available.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry.  If this method
     *          returns undefined instead of a promise, it is an indication that too many requests are already
     *          pending and the request will be retried later.
     */
    ArcGisImageServerTerrainProvider.prototype.requestTileGeometry = function(x, y, level) {
        var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level);

        // Each pixel in the heightmap represents the height at the center of that
        // pixel.  So expand the rectangle by half a sample spacing in each direction
        // so that the first height is on the edge of the rectangle we need rather than
        // half a sample spacing into the rectangle.
        var xSpacing = (rectangle.east - rectangle.west) / (this._heightmapWidth - 1);
        var ySpacing = (rectangle.north - rectangle.south) / (this._heightmapWidth - 1);

        rectangle.west -= xSpacing * 0.5;
        rectangle.east += xSpacing * 0.5;
        rectangle.south -= ySpacing * 0.5;
        rectangle.north += ySpacing * 0.5;

        var bbox = CesiumMath.toDegrees(rectangle.west) + '%2C' + CesiumMath.toDegrees(rectangle.south) + '%2C' + CesiumMath.toDegrees(rectangle.east) + '%2C' + CesiumMath.toDegrees(rectangle.north);

        var url = this._url + '/exportImage?interpolation=RSP_BilinearInterpolation&format=tiff&f=image&size=' + this._heightmapWidth + '%2C' + this._heightmapWidth + '&bboxSR=4326&imageSR=4326&bbox=' + bbox;
        if (this._token) {
            url += '&token=' + this._token;
        }

        var proxy = this._proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        var promise = throttleRequestByServer(url, loadImage);
        if (!defined(promise)) {
            return undefined;
        }

        var that = this;
        return when(promise, function(image) {
            return new HeightmapTerrainData({
                buffer : getImagePixels(image),
                width : that._heightmapWidth,
                height : that._heightmapWidth,
                childTileMask : 15, // all children present
                structure : that._terrainDataStructure
            });
        });
    };

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    ArcGisImageServerTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
        return this._levelZeroMaximumGeometricError / (1 << level);
    };

    /**
     * Determines whether data for a tile is available to be loaded.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @returns {Boolean} Undefined if not supported, otherwise true or false.
     */
    ArcGisImageServerTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
        return undefined;
    };

    return ArcGisImageServerTerrainProvider;
});

/*global define*/
define('Core/arrayRemoveDuplicates',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './Math'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        CesiumMath) {
    'use strict';

    var removeDuplicatesEpsilon = CesiumMath.EPSILON10;

    /**
     * Removes adjacent duplicate values in an array of values.
     *
     * @param {Object[]} [values] The array of values.
     * @param {Function} equalsEpsilon Function to compare values with an epsilon. Boolean equalsEpsilon(left, right, epsilon).
     * @param {Boolean} [wrapAround=false] Compare the last value in the array against the first value.
     * @returns {Object[]|undefined} A new array of values with no adjacent duplicate values or the input array if no duplicates were found.
     *
     * @example
     * // Returns [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0), (3.0, 3.0, 3.0), (1.0, 1.0, 1.0)]
     * var values = [
     *     new Cesium.Cartesian3(1.0, 1.0, 1.0),
     *     new Cesium.Cartesian3(1.0, 1.0, 1.0),
     *     new Cesium.Cartesian3(2.0, 2.0, 2.0),
     *     new Cesium.Cartesian3(3.0, 3.0, 3.0),
     *     new Cesium.Cartesian3(1.0, 1.0, 1.0)];
     * var nonDuplicatevalues = Cesium.PolylinePipeline.removeDuplicates(values, Cartesian3.equalsEpsilon);
     *
     * @example
     * // Returns [(1.0, 1.0, 1.0), (2.0, 2.0, 2.0), (3.0, 3.0, 3.0)]
     * var values = [
     *     new Cesium.Cartesian3(1.0, 1.0, 1.0),
     *     new Cesium.Cartesian3(1.0, 1.0, 1.0),
     *     new Cesium.Cartesian3(2.0, 2.0, 2.0),
     *     new Cesium.Cartesian3(3.0, 3.0, 3.0),
     *     new Cesium.Cartesian3(1.0, 1.0, 1.0)];
     * var nonDuplicatevalues = Cesium.PolylinePipeline.removeDuplicates(values, Cartesian3.equalsEpsilon, true);
     *
     * @private
     */
    function arrayRemoveDuplicates(values, equalsEpsilon, wrapAround) {
                if (!defined(equalsEpsilon)) {
            throw new DeveloperError('equalsEpsilon is required.');
        }
        
        if (!defined(values)) {
            return undefined;
        }

        wrapAround = defaultValue(wrapAround, false);

        var length = values.length;
        if (length < 2) {
            return values;
        }

        var i;
        var v0;
        var v1;

        for (i = 1; i < length; ++i) {
            v0 = values[i - 1];
            v1 = values[i];
            if (equalsEpsilon(v0, v1, removeDuplicatesEpsilon)) {
                break;
            }
        }

        if (i === length) {
            if (wrapAround && equalsEpsilon(values[0], values[values.length - 1], removeDuplicatesEpsilon)) {
                return values.slice(1);
            }
            return values;
        }

        var cleanedvalues = values.slice(0, i);
        for (; i < length; ++i) {
            // v0 is set by either the previous loop, or the previous clean point.
            v1 = values[i];
            if (!equalsEpsilon(v0, v1, removeDuplicatesEpsilon)) {
                cleanedvalues.push(v1);
                v0 = v1;
            }
        }

        if (wrapAround && cleanedvalues.length > 1 && equalsEpsilon(cleanedvalues[0], cleanedvalues[cleanedvalues.length - 1], removeDuplicatesEpsilon)) {
            cleanedvalues.shift();
        }

        return cleanedvalues;
    }

    return arrayRemoveDuplicates;
});

/*global define*/
define('Core/AssociativeArray',[
        './defined',
        './defineProperties',
        './DeveloperError'
    ], function(
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * A collection of key-value pairs that is stored as a hash for easy
     * lookup but also provides an array for fast iteration.
     * @alias AssociativeArray
     * @constructor
     */
    function AssociativeArray() {
        this._array = [];
        this._hash = {};
    }

    defineProperties(AssociativeArray.prototype, {
        /**
         * Gets the number of items in the collection.
         * @memberof AssociativeArray.prototype
         *
         * @type {Number}
         */
        length : {
            get : function() {
                return this._array.length;
            }
        },
        /**
         * Gets an unordered array of all values in the collection.
         * This is a live array that will automatically reflect the values in the collection,
         * it should not be modified directly.
         * @memberof AssociativeArray.prototype
         *
         * @type {Array}
         */
        values : {
            get : function() {
                return this._array;
            }
        }
    });

    /**
     * Determines if the provided key is in the array.
     *
     * @param {String|Number} key The key to check.
     * @returns {Boolean} <code>true</code> if the key is in the array, <code>false</code> otherwise.
     */
    AssociativeArray.prototype.contains = function(key) {
                if (typeof key !== 'string' && typeof key !== 'number') {
            throw new DeveloperError('key is required to be a string or number.');
        }
                return defined(this._hash[key]);
    };

    /**
     * Associates the provided key with the provided value.  If the key already
     * exists, it is overwritten with the new value.
     *
     * @param {String|Number} key A unique identifier.
     * @param {Object} value The value to associate with the provided key.
     */
    AssociativeArray.prototype.set = function(key, value) {
                if (typeof key !== 'string' && typeof key !== 'number') {
            throw new DeveloperError('key is required to be a string or number.');
        }
        
        var oldValue = this._hash[key];
        if (value !== oldValue) {
            this.remove(key);
            this._hash[key] = value;
            this._array.push(value);
        }
    };

    /**
     * Retrieves the value associated with the provided key.
     *
     * @param {String|Number} key The key whose value is to be retrieved.
     * @returns {Object} The associated value, or undefined if the key does not exist in the collection.
     */
    AssociativeArray.prototype.get = function(key) {
                if (typeof key !== 'string' && typeof key !== 'number') {
            throw new DeveloperError('key is required to be a string or number.');
        }
                return this._hash[key];
    };

    /**
     * Removes a key-value pair from the collection.
     *
     * @param {String|Number} key The key to be removed.
     * @returns {Boolean} True if it was removed, false if the key was not in the collection.
     */
    AssociativeArray.prototype.remove = function(key) {
                if (defined(key) && typeof key !== 'string' && typeof key !== 'number') {
            throw new DeveloperError('key is required to be a string or number.');
        }
        
        var value = this._hash[key];
        var hasValue = defined(value);
        if (hasValue) {
            var array = this._array;
            array.splice(array.indexOf(value), 1);
            delete this._hash[key];
        }
        return hasValue;
    };

    /**
     * Clears the collection.
     */
    AssociativeArray.prototype.removeAll = function() {
        var array = this._array;
        if (array.length > 0) {
            this._hash = {};
            array.length = 0;
        }
    };

    return AssociativeArray;
});

/*global define*/
define('Core/barycentricCoordinates',[
        './Cartesian2',
        './Cartesian3',
        './defined',
        './DeveloperError'
    ], function(
        Cartesian2,
        Cartesian3,
        defined,
        DeveloperError) {
    'use strict';

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();

    /**
     * Computes the barycentric coordinates for a point with respect to a triangle.
     *
     * @exports barycentricCoordinates
     *
     * @param {Cartesian2|Cartesian3} point The point to test.
     * @param {Cartesian2|Cartesian3} p0 The first point of the triangle, corresponding to the barycentric x-axis.
     * @param {Cartesian2|Cartesian3} p1 The second point of the triangle, corresponding to the barycentric y-axis.
     * @param {Cartesian2|Cartesian3} p2 The third point of the triangle, corresponding to the barycentric z-axis.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new Cartesian3 instance if one was not provided.
     *
     * @example
     * // Returns Cartesian3.UNIT_X
     * var p = new Cesium.Cartesian3(-1.0, 0.0, 0.0);
     * var b = Cesium.barycentricCoordinates(p,
     *   new Cesium.Cartesian3(-1.0, 0.0, 0.0),
     *   new Cesium.Cartesian3( 1.0, 0.0, 0.0),
     *   new Cesium.Cartesian3( 0.0, 1.0, 1.0));
     */
    function barycentricCoordinates(point, p0, p1, p2, result) {
                if (!defined(point) || !defined(p0) || !defined(p1) || !defined(p2)) {
            throw new DeveloperError('point, p0, p1, and p2 are required.');
        }
        

        if (!defined(result)) {
            result = new Cartesian3();
        }

        // Implementation based on http://www.blackpawn.com/texts/pointinpoly/default.html.
        var v0, v1, v2;
        var dot00, dot01, dot02, dot11, dot12;

        if(!defined(p0.z)) {
          v0 = Cartesian2.subtract(p1, p0, scratchCartesian1);
          v1 = Cartesian2.subtract(p2, p0, scratchCartesian2);
          v2 = Cartesian2.subtract(point, p0, scratchCartesian3);

          dot00 = Cartesian2.dot(v0, v0);
          dot01 = Cartesian2.dot(v0, v1);
          dot02 = Cartesian2.dot(v0, v2);
          dot11 = Cartesian2.dot(v1, v1);
          dot12 = Cartesian2.dot(v1, v2);
        } else {
          v0 = Cartesian3.subtract(p1, p0, scratchCartesian1);
          v1 = Cartesian3.subtract(p2, p0, scratchCartesian2);
          v2 = Cartesian3.subtract(point, p0, scratchCartesian3);

          dot00 = Cartesian3.dot(v0, v0);
          dot01 = Cartesian3.dot(v0, v1);
          dot02 = Cartesian3.dot(v0, v2);
          dot11 = Cartesian3.dot(v1, v1);
          dot12 = Cartesian3.dot(v1, v2);
        }

        var q = 1.0 / (dot00 * dot11 - dot01 * dot01);
        result.y = (dot11 * dot02 - dot01 * dot12) * q;
        result.z = (dot00 * dot12 - dot01 * dot02) * q;
        result.x = 1.0 - result.y - result.z;
        return result;
    }

    return barycentricCoordinates;
});

/*global define*/
define('Core/BingMapsApi',[
        './Credit',
        './defined'
    ], function(
        Credit,
        defined) {
    'use strict';

    /**
     * Object for setting and retrieving the default BingMaps API key.
     *
     * @exports BingMapsApi
     */
    var BingMapsApi = {
    };

    /**
     * The default Bing Maps API key to use if one is not provided to the
     * constructor of an object that uses the Bing Maps API.  If this property is undefined,
     * Cesium's default key is used, which is only suitable for use early in development.
     * Please generate your own key by visiting
     * {@link https://www.bingmapsportal.com/}
     * as soon as possible and prior to deployment.  When Cesium's default key is used,
     * a message is printed to the console the first time the Bing Maps API is used.
     *
     * @type {String}
     */
    BingMapsApi.defaultKey = undefined;

    var printedBingWarning = false;
    var errorCredit;
    var errorString = 'This application is using Cesium\'s default Bing Maps key.  Please create a new key for the application as soon as possible and prior to deployment by visiting https://www.bingmapsportal.com/, and provide your key to Cesium by setting the Cesium.BingMapsApi.defaultKey property before constructing the CesiumWidget or any other object that uses the Bing Maps API.';

    BingMapsApi.getKey = function(providedKey) {
        if (defined(providedKey)) {
            return providedKey;
        }

        if (!defined(BingMapsApi.defaultKey)) {
            if (!printedBingWarning) {
                console.log(errorString);
                printedBingWarning = true;
            }
            return 'AudaMP3YocqmIOQrFAValZ9d_tFqS4JWwiSEnvt2jwP5lizGzV_mJxLaKaI0fWRT';
        }

        return BingMapsApi.defaultKey;
    };

    BingMapsApi.getErrorCredit = function(providedKey) {
        if (defined(providedKey) || defined(BingMapsApi.defaultKey)) {
            return undefined;
        }

        if (!defined(errorCredit)) {
            errorCredit = new Credit(errorString);
        }

        return errorCredit;
    };

    return BingMapsApi;
});

/*global define*/
define('Core/combine',[
        './defaultValue',
        './defined'
    ], function(
        defaultValue,
        defined) {
    'use strict';

    /**
     * Merges two objects, copying their properties onto a new combined object. When two objects have the same
     * property, the value of the property on the first object is used.  If either object is undefined,
     * it will be treated as an empty object.
     *
     * @example
     * var object1 = {
     *     propOne : 1,
     *     propTwo : {
     *         value1 : 10
     *     }
     * }
     * var object2 = {
     *     propTwo : 2
     * }
     * var final = Cesium.combine(object1, object2);
     *
     * // final === {
     * //     propOne : 1,
     * //     propTwo : {
     * //         value1 : 10
     * //     }
     * // }
     *
     * @param {Object} [object1] The first object to merge.
     * @param {Object} [object2] The second object to merge.
     * @param {Boolean} [deep=false] Perform a recursive merge.
     * @returns {Object} The combined object containing all properties from both objects.
     *
     * @exports combine
     */
    function combine(object1, object2, deep) {
        deep = defaultValue(deep, false);

        var result = {};

        var object1Defined = defined(object1);
        var object2Defined = defined(object2);
        var property;
        var object1Value;
        var object2Value;
        if (object1Defined) {
            for (property in object1) {
                if (object1.hasOwnProperty(property)) {
                    object1Value = object1[property];
                    if (object2Defined && deep && typeof object1Value === 'object' && object2.hasOwnProperty(property)) {
                        object2Value = object2[property];
                        if (typeof object2Value === 'object') {
                            result[property] = combine(object1Value, object2Value, deep);
                        } else {
                            result[property] = object1Value;
                        }
                    } else {
                        result[property] = object1Value;
                    }
                }
            }
        }
        if (object2Defined) {
            for (property in object2) {
                if (object2.hasOwnProperty(property) && !result.hasOwnProperty(property)) {
                    object2Value = object2[property];
                    result[property] = object2Value;
                }
            }
        }
        return result;
    }

    return combine;
});

/*global define*/
define('Core/isArray',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * Tests an object to see if it is an array.
     * @exports isArray
     *
     * @param {Object} value The value to test.
     * @returns {Boolean} true if the value is an array, false otherwise.
     */
    var isArray = Array.isArray;
    if (!defined(isArray)) {
        isArray = function(value) {
            return Object.prototype.toString.call(value) === '[object Array]';
        };
    }

    return isArray;
});

/*global define*/
define('Core/objectToQuery',[
        './defined',
        './DeveloperError',
        './isArray'
    ], function(
        defined,
        DeveloperError,
        isArray) {
    'use strict';

    /**
     * Converts an object representing a set of name/value pairs into a query string,
     * with names and values encoded properly for use in a URL.  Values that are arrays
     * will produce multiple values with the same name.
     * @exports objectToQuery
     *
     * @param {Object} obj The object containing data to encode.
     * @returns {String} An encoded query string.
     *
     *
     * @example
     * var str = Cesium.objectToQuery({
     *     key1 : 'some value',
     *     key2 : 'a/b',
     *     key3 : ['x', 'y']
     * });
     * 
     * @see queryToObject
     * // str will be:
     * // 'key1=some%20value&key2=a%2Fb&key3=x&key3=y'
     */
    function objectToQuery(obj) {
                if (!defined(obj)) {
            throw new DeveloperError('obj is required.');
        }
        
        var result = '';
        for ( var propName in obj) {
            if (obj.hasOwnProperty(propName)) {
                var value = obj[propName];

                var part = encodeURIComponent(propName) + '=';
                if (isArray(value)) {
                    for (var i = 0, len = value.length; i < len; ++i) {
                        result += part + encodeURIComponent(value[i]) + '&';
                    }
                } else {
                    result += part + encodeURIComponent(value) + '&';
                }
            }
        }

        // trim last &
        result = result.slice(0, -1);

        // This function used to replace %20 with + which is more compact and readable.
        // However, some servers didn't properly handle + as a space.
        // https://github.com/AnalyticalGraphicsInc/cesium/issues/2192

        return result;
    }

    return objectToQuery;
});

/*global define*/
define('Core/queryToObject',[
        './defined',
        './DeveloperError',
        './isArray'
    ], function(
        defined,
        DeveloperError,
        isArray) {
    'use strict';

    /**
     * Parses a query string into an object, where the keys and values of the object are the
     * name/value pairs from the query string, decoded. If a name appears multiple times,
     * the value in the object will be an array of values.
     * @exports queryToObject
     *
     * @param {String} queryString The query string.
     * @returns {Object} An object containing the parameters parsed from the query string.
     *
     *
     * @example
     * var obj = Cesium.queryToObject('key1=some%20value&key2=a%2Fb&key3=x&key3=y');
     * // obj will be:
     * // {
     * //   key1 : 'some value',
     * //   key2 : 'a/b',
     * //   key3 : ['x', 'y']
     * // }
     * 
     * @see objectToQuery
     */
    function queryToObject(queryString) {
                if (!defined(queryString)) {
            throw new DeveloperError('queryString is required.');
        }
        
        var result = {};
        if (queryString === '') {
            return result;
        }
        var parts = queryString.replace(/\+/g, '%20').split('&');
        for (var i = 0, len = parts.length; i < len; ++i) {
            var subparts = parts[i].split('=');

            var name = decodeURIComponent(subparts[0]);
            var value = subparts[1];
            if (defined(value)) {
                value = decodeURIComponent(value);
            } else {
                value = '';
            }

            var resultValue = result[name];
            if (typeof resultValue === 'string') {
                // expand the single value to an array
                result[name] = [resultValue, value];
            } else if (isArray(resultValue)) {
                resultValue.push(value);
            } else {
                result[name] = value;
            }
        }
        return result;
    }

    return queryToObject;
});

/*global define*/
define('Core/loadJsonp',[
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        './combine',
        './defaultValue',
        './defined',
        './DeveloperError',
        './objectToQuery',
        './queryToObject'
    ], function(
        Uri,
        when,
        combine,
        defaultValue,
        defined,
        DeveloperError,
        objectToQuery,
        queryToObject) {
    'use strict';

    /**
     * Requests a resource using JSONP.
     *
     * @exports loadJsonp
     *
     * @param {String} url The URL to request.
     * @param {Object} [options] Object with the following properties:
     * @param {Object} [options.parameters] Any extra query parameters to append to the URL.
     * @param {String} [options.callbackParameterName='callback'] The callback parameter name that the server expects.
     * @param {Proxy} [options.proxy] A proxy to use for the request. This object is expected to have a getURL function which returns the proxied URL, if needed.
     * @returns {Promise.<Object>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load a data asynchronously
     * Cesium.loadJsonp('some/webservice').then(function(data) {
     *     // use the loaded data
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadJsonp(url, options) {
                if (!defined(url)) {
            throw new DeveloperError('url is required.');
        }
        
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        //generate a unique function name
        var functionName;
        do {
            functionName = 'loadJsonp' + Math.random().toString().substring(2, 8);
        } while (defined(window[functionName]));

        var deferred = when.defer();

        //assign a function with that name in the global scope
        window[functionName] = function(data) {
            deferred.resolve(data);

            try {
                delete window[functionName];
            } catch (e) {
                window[functionName] = undefined;
            }
        };

        var uri = new Uri(url);

        var queryOptions = queryToObject(defaultValue(uri.query, ''));

        if (defined(options.parameters)) {
            queryOptions = combine(options.parameters, queryOptions);
        }

        var callbackParameterName = defaultValue(options.callbackParameterName, 'callback');
        queryOptions[callbackParameterName] = functionName;

        uri.query = objectToQuery(queryOptions);

        url = uri.toString();

        var proxy = options.proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        loadJsonp.loadAndExecuteScript(url, functionName, deferred);

        return deferred.promise;
    }

    // This is broken out into a separate function so that it can be mocked for testing purposes.
    loadJsonp.loadAndExecuteScript = function(url, functionName, deferred) {
        var script = document.createElement('script');
        script.async = true;
        script.src = url;

        var head = document.getElementsByTagName('head')[0];
        script.onload = function() {
            script.onload = undefined;
            head.removeChild(script);
        };
        script.onerror = function(e) {
            deferred.reject(e);
        };

        head.appendChild(script);
    };

    loadJsonp.defaultLoadAndExecuteScript = loadJsonp.loadAndExecuteScript;

    return loadJsonp;
});

/*global define*/
define('Core/BingMapsGeocoderService',[
    './BingMapsApi',
    './defaultValue',
    './defined',
    './defineProperties',
    './DeveloperError',
    './loadJsonp',
    './Rectangle'
], function(
    BingMapsApi,
    defaultValue,
    defined,
    defineProperties,
    DeveloperError,
    loadJsonp,
    Rectangle) {
    'use strict';

    var url = 'https://dev.virtualearth.net/REST/v1/Locations';

    /**
     * Provides geocoding through Bing Maps.
     * @alias BingMapsGeocoderService
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.scene The scene
     * @param {String} [options.key] A key to use with the Bing Maps geocoding service
     */
    function BingMapsGeocoderService(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
                if (!defined(options.scene)) {
            throw new DeveloperError('options.scene is required.');
        }
        
        var key = options.key;
        this._key = BingMapsApi.getKey(key);

        if (defined(key)) {
            var errorCredit = BingMapsApi.getErrorCredit(key);
            if (defined(errorCredit)) {
                options.scene._frameState.creditDisplay.addDefaultCredit(errorCredit);
            }
        }
    }

    defineProperties(BingMapsGeocoderService.prototype, {
        /**
         * The URL endpoint for the Bing geocoder service
         * @type {String}
         * @memberof {BingMapsGeocoderService.prototype}
         * @readonly
         */
        url : {
            get : function () {
                return url;
            }
        },

        /**
         * The key for the Bing geocoder service
         * @type {String}
         * @memberof {BingMapsGeocoderService.prototype}
         * @readonly
         */
        key : {
            get : function () {
                return this._key;
            }
        }
    });

    /**
     * @function
     *
     * @param {String} query The query to be sent to the geocoder service
     * @returns {Promise<GeocoderResult[]>}
     */
    BingMapsGeocoderService.prototype.geocode = function(query) {
                if (!defined(query)) {
            throw new DeveloperError('query must be defined');
        }
        
        var key = this.key;
        var promise = loadJsonp(url, {
            parameters : {
                query : query,
                key : key
            },
            callbackParameterName : 'jsonp'
        });

        return promise.then(function(result) {
            if (result.resourceSets.length === 0) {
                return [];
            }

            var results = result.resourceSets[0].resources;

            return results.map(function (resource) {
                var bbox = resource.bbox;
                var south = bbox[0];
                var west = bbox[1];
                var north = bbox[2];
                var east = bbox[3];
                return {
                    displayName: resource.name,
                    destination: Rectangle.fromDegrees(west, south, east, north)
                };
            });
        });
    };

    return BingMapsGeocoderService;
});

/*global define*/
define('Core/BoundingRectangle',[
        './Cartesian2',
        './Cartographic',
        './Check',
        './defaultValue',
        './defined',
        './GeographicProjection',
        './Intersect',
        './Rectangle'
    ], function(
        Cartesian2,
        Cartographic,
        Check,
        defaultValue,
        defined,
        GeographicProjection,
        Intersect,
        Rectangle) {
    'use strict';

    /**
     * A bounding rectangle given by a corner, width and height.
     * @alias BoundingRectangle
     * @constructor
     *
     * @param {Number} [x=0.0] The x coordinate of the rectangle.
     * @param {Number} [y=0.0] The y coordinate of the rectangle.
     * @param {Number} [width=0.0] The width of the rectangle.
     * @param {Number} [height=0.0] The height of the rectangle.
     *
     * @see BoundingSphere
     * @see Packable
     */
    function BoundingRectangle(x, y, width, height) {
        /**
         * The x coordinate of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.x = defaultValue(x, 0.0);

        /**
         * The y coordinate of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.y = defaultValue(y, 0.0);

        /**
         * The width of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.width = defaultValue(width, 0.0);

        /**
         * The height of the rectangle.
         * @type {Number}
         * @default 0.0
         */
        this.height = defaultValue(height, 0.0);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    BoundingRectangle.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {BoundingRectangle} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    BoundingRectangle.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.x;
        array[startingIndex++] = value.y;
        array[startingIndex++] = value.width;
        array[startingIndex] = value.height;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {BoundingRectangle} [result] The object into which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new BoundingRectangle();
        }
        result.x = array[startingIndex++];
        result.y = array[startingIndex++];
        result.width = array[startingIndex++];
        result.height = array[startingIndex];
        return result;
    };

    /**
     * Computes a bounding rectangle enclosing the list of 2D points.
     * The rectangle is oriented with the corner at the bottom left.
     *
     * @param {Cartesian2[]} positions List of points that the bounding rectangle will enclose.  Each point must have <code>x</code> and <code>y</code> properties.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.fromPoints = function(positions, result) {
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        if (!defined(positions) || positions.length === 0) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        var length = positions.length;

        var minimumX = positions[0].x;
        var minimumY = positions[0].y;

        var maximumX = positions[0].x;
        var maximumY = positions[0].y;

        for ( var i = 1; i < length; i++) {
            var p = positions[i];
            var x = p.x;
            var y = p.y;

            minimumX = Math.min(x, minimumX);
            maximumX = Math.max(x, maximumX);
            minimumY = Math.min(y, minimumY);
            maximumY = Math.max(y, maximumY);
        }

        result.x = minimumX;
        result.y = minimumY;
        result.width = maximumX - minimumX;
        result.height = maximumY - minimumY;
        return result;
    };

    var defaultProjection = new GeographicProjection();
    var fromRectangleLowerLeft = new Cartographic();
    var fromRectangleUpperRight = new Cartographic();
    /**
     * Computes a bounding rectangle from an rectangle.
     *
     * @param {Rectangle} rectangle The valid rectangle used to create a bounding rectangle.
     * @param {Object} [projection=GeographicProjection] The projection used to project the rectangle into 2D.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.fromRectangle = function(rectangle, projection, result) {
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        if (!defined(rectangle)) {
            result.x = 0;
            result.y = 0;
            result.width = 0;
            result.height = 0;
            return result;
        }

        projection = defaultValue(projection, defaultProjection);

        var lowerLeft = projection.project(Rectangle.southwest(rectangle, fromRectangleLowerLeft));
        var upperRight = projection.project(Rectangle.northeast(rectangle, fromRectangleUpperRight));

        Cartesian2.subtract(upperRight, lowerLeft, upperRight);

        result.x = lowerLeft.x;
        result.y = lowerLeft.y;
        result.width = upperRight.x;
        result.height = upperRight.y;
        return result;
    };

    /**
     * Duplicates a BoundingRectangle instance.
     *
     * @param {BoundingRectangle} rectangle The bounding rectangle to duplicate.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided. (Returns undefined if rectangle is undefined)
     */
    BoundingRectangle.clone = function(rectangle, result) {
        if (!defined(rectangle)) {
            return undefined;
        }

        if (!defined(result)) {
            return new BoundingRectangle(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
        }

        result.x = rectangle.x;
        result.y = rectangle.y;
        result.width = rectangle.width;
        result.height = rectangle.height;
        return result;
    };

    /**
     * Computes a bounding rectangle that is the union of the left and right bounding rectangles.
     *
     * @param {BoundingRectangle} left A rectangle to enclose in bounding rectangle.
     * @param {BoundingRectangle} right A rectangle to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.union = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        var lowerLeftX = Math.min(left.x, right.x);
        var lowerLeftY = Math.min(left.y, right.y);
        var upperRightX = Math.max(left.x + left.width, right.x + right.width);
        var upperRightY = Math.max(left.y + left.height, right.y + right.height);

        result.x = lowerLeftX;
        result.y = lowerLeftY;
        result.width = upperRightX - lowerLeftX;
        result.height = upperRightY - lowerLeftY;
        return result;
    };

    /**
     * Computes a bounding rectangle by enlarging the provided rectangle until it contains the provided point.
     *
     * @param {BoundingRectangle} rectangle A rectangle to expand.
     * @param {Cartesian2} point A point to enclose in a bounding rectangle.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.expand = function(rectangle, point, result) {
                Check.typeOf.object('rectangle', rectangle);
        Check.typeOf.object('point', point);
        
        result = BoundingRectangle.clone(rectangle, result);

        var width = point.x - result.x;
        var height = point.y - result.y;

        if (width > result.width) {
            result.width = width;
        } else if (width < 0) {
            result.width -= width;
            result.x = point.x;
        }

        if (height > result.height) {
            result.height = height;
        } else if (height < 0) {
            result.height -= height;
            result.y = point.y;
        }

        return result;
    };

    /**
     * Determines if two rectangles intersect.
     *
     * @param {BoundingRectangle} left A rectangle to check for intersection.
     * @param {BoundingRectangle} right The other rectangle to check for intersection.
     * @returns {Intersect} <code>Intersect.INTESECTING</code> if the rectangles intersect, <code>Intersect.OUTSIDE</code> otherwise.
     */
    BoundingRectangle.intersect = function(left, right) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        
        var leftX = left.x;
        var leftY = left.y;
        var rightX = right.x;
        var rightY = right.y;
        if (!(leftX > rightX + right.width ||
              leftX + left.width < rightX ||
              leftY + left.height < rightY ||
              leftY > rightY + right.height)) {
            return Intersect.INTERSECTING;
        }

        return Intersect.OUTSIDE;
    };

    /**
     * Compares the provided BoundingRectangles componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {BoundingRectangle} [left] The first BoundingRectangle.
     * @param {BoundingRectangle} [right] The second BoundingRectangle.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    BoundingRectangle.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.x === right.x) &&
                (left.y === right.y) &&
                (left.width === right.width) &&
                (left.height === right.height));
    };

    /**
     * Duplicates this BoundingRectangle instance.
     *
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
     */
    BoundingRectangle.prototype.clone = function(result) {
        return BoundingRectangle.clone(this, result);
    };

    /**
     * Determines if this rectangle intersects with another.
     *
     * @param {BoundingRectangle} right A rectangle to check for intersection.
     * @returns {Intersect} <code>Intersect.INTESECTING</code> if the rectangles intersect, <code>Intersect.OUTSIDE</code> otherwise.
     */
    BoundingRectangle.prototype.intersect = function(right) {
        return BoundingRectangle.intersect(this, right);
    };

    /**
     * Compares this BoundingRectangle against the provided BoundingRectangle componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {BoundingRectangle} [right] The right hand side BoundingRectangle.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    BoundingRectangle.prototype.equals = function(right) {
        return BoundingRectangle.equals(this, right);
    };

    return BoundingRectangle;
});

/*global define*/
define('Core/GeometryType',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * @private
     */
    var GeometryType = {
        NONE : 0,
        TRIANGLES : 1,
        LINES : 2,
        POLYLINES : 3
    };

    return freezeObject(GeometryType);
});

/*global define*/
define('Core/PrimitiveType',[
        './freezeObject',
        './WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * The type of a geometric primitive, i.e., points, lines, and triangles.
     *
     * @exports PrimitiveType
     */
    var PrimitiveType = {
        /**
         * Points primitive where each vertex (or index) is a separate point.
         *
         * @type {Number}
         * @constant
         */
        POINTS : WebGLConstants.POINTS,

        /**
         * Lines primitive where each two vertices (or indices) is a line segment.  Line segments are not necessarily connected.
         *
         * @type {Number}
         * @constant
         */
        LINES : WebGLConstants.LINES,

        /**
         * Line loop primitive where each vertex (or index) after the first connects a line to
         * the previous vertex, and the last vertex implicitly connects to the first.
         *
         * @type {Number}
         * @constant
         */
        LINE_LOOP : WebGLConstants.LINE_LOOP,

        /**
         * Line strip primitive where each vertex (or index) after the first connects a line to the previous vertex.
         *
         * @type {Number}
         * @constant
         */
        LINE_STRIP : WebGLConstants.LINE_STRIP,

        /**
         * Triangles primitive where each three vertices (or indices) is a triangle.  Triangles do not necessarily share edges.
         *
         * @type {Number}
         * @constant
         */
        TRIANGLES : WebGLConstants.TRIANGLES,

        /**
         * Triangle strip primitive where each vertex (or index) after the first two connect to
         * the previous two vertices forming a triangle.  For example, this can be used to model a wall.
         *
         * @type {Number}
         * @constant
         */
        TRIANGLE_STRIP : WebGLConstants.TRIANGLE_STRIP,

        /**
         * Triangle fan primitive where each vertex (or index) after the first two connect to
         * the previous vertex and the first vertex forming a triangle.  For example, this can be used
         * to model a cone or circle.
         *
         * @type {Number}
         * @constant
         */
        TRIANGLE_FAN : WebGLConstants.TRIANGLE_FAN,

        /**
         * @private
         */
        validate : function(primitiveType) {
            return primitiveType === PrimitiveType.POINTS ||
                   primitiveType === PrimitiveType.LINES ||
                   primitiveType === PrimitiveType.LINE_LOOP ||
                   primitiveType === PrimitiveType.LINE_STRIP ||
                   primitiveType === PrimitiveType.TRIANGLES ||
                   primitiveType === PrimitiveType.TRIANGLE_STRIP ||
                   primitiveType === PrimitiveType.TRIANGLE_FAN;
        }
    };

    return freezeObject(PrimitiveType);
});

/*global define*/
define('Core/Geometry',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './GeometryType',
        './PrimitiveType'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        GeometryType,
        PrimitiveType) {
    'use strict';

    /**
     * A geometry representation with attributes forming vertices and optional index data
     * defining primitives.  Geometries and an {@link Appearance}, which describes the shading,
     * can be assigned to a {@link Primitive} for visualization.  A <code>Primitive</code> can
     * be created from many heterogeneous - in many cases - geometries for performance.
     * <p>
     * Geometries can be transformed and optimized using functions in {@link GeometryPipeline}.
     * </p>
     *
     * @alias Geometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {GeometryAttributes} options.attributes Attributes, which make up the geometry's vertices.
     * @param {PrimitiveType} [options.primitiveType=PrimitiveType.TRIANGLES] The type of primitives in the geometry.
     * @param {Uint16Array|Uint32Array} [options.indices] Optional index data that determines the primitives in the geometry.
     * @param {BoundingSphere} [options.boundingSphere] An optional bounding sphere that fully enclosed the geometry.
     *
     * @see PolygonGeometry
     * @see RectangleGeometry
     * @see EllipseGeometry
     * @see CircleGeometry
     * @see WallGeometry
     * @see SimplePolylineGeometry
     * @see BoxGeometry
     * @see EllipsoidGeometry
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Geometry%20and%20Appearances.html|Geometry and Appearances Demo}
     *
     * @example
     * // Create geometry with a position attribute and indexed lines.
     * var positions = new Float64Array([
     *   0.0, 0.0, 0.0,
     *   7500000.0, 0.0, 0.0,
     *   0.0, 7500000.0, 0.0
     * ]);
     *
     * var geometry = new Cesium.Geometry({
     *   attributes : {
     *     position : new Cesium.GeometryAttribute({
     *       componentDatatype : Cesium.ComponentDatatype.DOUBLE,
     *       componentsPerAttribute : 3,
     *       values : positions
     *     })
     *   },
     *   indices : new Uint16Array([0, 1, 1, 2, 2, 0]),
     *   primitiveType : Cesium.PrimitiveType.LINES,
     *   boundingSphere : Cesium.BoundingSphere.fromVertices(positions)
     * });
     */
    function Geometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.attributes)) {
            throw new DeveloperError('options.attributes is required.');
        }
        
        /**
         * Attributes, which make up the geometry's vertices.  Each property in this object corresponds to a
         * {@link GeometryAttribute} containing the attribute's data.
         * <p>
         * Attributes are always stored non-interleaved in a Geometry.
         * </p>
         * <p>
         * There are reserved attribute names with well-known semantics.  The following attributes
         * are created by a Geometry (depending on the provided {@link VertexFormat}.
         * <ul>
         *    <li><code>position</code> - 3D vertex position.  64-bit floating-point (for precision).  3 components per attribute.  See {@link VertexFormat#position}.</li>
         *    <li><code>normal</code> - Normal (normalized), commonly used for lighting.  32-bit floating-point.  3 components per attribute.  See {@link VertexFormat#normal}.</li>
         *    <li><code>st</code> - 2D texture coordinate.  32-bit floating-point.  2 components per attribute.  See {@link VertexFormat#st}.</li>
         *    <li><code>bitangent</code> - Bitangent (normalized), used for tangent-space effects like bump mapping.  32-bit floating-point.  3 components per attribute.  See {@link VertexFormat#bitangent}.</li>
         *    <li><code>tangent</code> - Tangent (normalized), used for tangent-space effects like bump mapping.  32-bit floating-point.  3 components per attribute.  See {@link VertexFormat#tangent}.</li>
         * </ul>
         * </p>
         * <p>
         * The following attribute names are generally not created by a Geometry, but are added
         * to a Geometry by a {@link Primitive} or {@link GeometryPipeline} functions to prepare
         * the geometry for rendering.
         * <ul>
         *    <li><code>position3DHigh</code> - High 32 bits for encoded 64-bit position computed with {@link GeometryPipeline.encodeAttribute}.  32-bit floating-point.  4 components per attribute.</li>
         *    <li><code>position3DLow</code> - Low 32 bits for encoded 64-bit position computed with {@link GeometryPipeline.encodeAttribute}.  32-bit floating-point.  4 components per attribute.</li>
         *    <li><code>position3DHigh</code> - High 32 bits for encoded 64-bit 2D (Columbus view) position computed with {@link GeometryPipeline.encodeAttribute}.  32-bit floating-point.  4 components per attribute.</li>
         *    <li><code>position2DLow</code> - Low 32 bits for encoded 64-bit 2D (Columbus view) position computed with {@link GeometryPipeline.encodeAttribute}.  32-bit floating-point.  4 components per attribute.</li>
         *    <li><code>color</code> - RGBA color (normalized) usually from {@link GeometryInstance#color}.  32-bit floating-point.  4 components per attribute.</li>
         *    <li><code>pickColor</code> - RGBA color used for picking.  32-bit floating-point.  4 components per attribute.</li>
         * </ul>
         * </p>
         *
         * @type GeometryAttributes
         *
         * @default undefined
         *
         *
         * @example
         * geometry.attributes.position = new Cesium.GeometryAttribute({
         *   componentDatatype : Cesium.ComponentDatatype.FLOAT,
         *   componentsPerAttribute : 3,
         *   values : new Float32Array(0)
         * });
         *
         * @see GeometryAttribute
         * @see VertexFormat
         */
        this.attributes = options.attributes;

        /**
         * Optional index data that - along with {@link Geometry#primitiveType} -
         * determines the primitives in the geometry.
         *
         * @type Array
         *
         * @default undefined
         */
        this.indices = options.indices;

        /**
         * The type of primitives in the geometry.  This is most often {@link PrimitiveType.TRIANGLES},
         * but can varying based on the specific geometry.
         *
         * @type PrimitiveType
         *
         * @default undefined
         */
        this.primitiveType = defaultValue(options.primitiveType, PrimitiveType.TRIANGLES);

        /**
         * An optional bounding sphere that fully encloses the geometry.  This is
         * commonly used for culling.
         *
         * @type BoundingSphere
         *
         * @default undefined
         */
        this.boundingSphere = options.boundingSphere;

        /**
         * @private
         */
        this.geometryType = defaultValue(options.geometryType, GeometryType.NONE);

        /**
         * @private
         */
        this.boundingSphereCV = options.boundingSphereCV;
    }

    /**
     * Computes the number of vertices in a geometry.  The runtime is linear with
     * respect to the number of attributes in a vertex, not the number of vertices.
     *
     * @param {Geometry} geometry The geometry.
     * @returns {Number} The number of vertices in the geometry.
     *
     * @example
     * var numVertices = Cesium.Geometry.computeNumberOfVertices(geometry);
     */
    Geometry.computeNumberOfVertices = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        var numberOfVertices = -1;
        for ( var property in geometry.attributes) {
            if (geometry.attributes.hasOwnProperty(property) &&
                    defined(geometry.attributes[property]) &&
                    defined(geometry.attributes[property].values)) {

                var attribute = geometry.attributes[property];
                var num = attribute.values.length / attribute.componentsPerAttribute;
                                if ((numberOfVertices !== num) && (numberOfVertices !== -1)) {
                    throw new DeveloperError('All attribute lists must have the same number of attributes.');
                }
                                numberOfVertices = num;
            }
        }

        return numberOfVertices;
    };

    return Geometry;
});

/*global define*/
define('Core/GeometryAttribute',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Values and type information for geometry attributes.  A {@link Geometry}
     * generally contains one or more attributes.  All attributes together form
     * the geometry's vertices.
     *
     * @alias GeometryAttribute
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {ComponentDatatype} [options.componentDatatype] The datatype of each component in the attribute, e.g., individual elements in values.
     * @param {Number} [options.componentsPerAttribute] A number between 1 and 4 that defines the number of components in an attributes.
     * @param {Boolean} [options.normalize=false] When <code>true</code> and <code>componentDatatype</code> is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering.
     * @param {TypedArray} [options.values] The values for the attributes stored in a typed array.
     *
     * @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4.
     *
     *
     * @example
     * var geometry = new Cesium.Geometry({
     *   attributes : {
     *     position : new Cesium.GeometryAttribute({
     *       componentDatatype : Cesium.ComponentDatatype.FLOAT,
     *       componentsPerAttribute : 3,
     *       values : new Float32Array([
     *         0.0, 0.0, 0.0,
     *         7500000.0, 0.0, 0.0,
     *         0.0, 7500000.0, 0.0
     *       ])
     *     })
     *   },
     *   primitiveType : Cesium.PrimitiveType.LINE_LOOP
     * });
     * 
     * @see Geometry
     */
    function GeometryAttribute(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.componentDatatype)) {
            throw new DeveloperError('options.componentDatatype is required.');
        }
        if (!defined(options.componentsPerAttribute)) {
            throw new DeveloperError('options.componentsPerAttribute is required.');
        }
        if (options.componentsPerAttribute < 1 || options.componentsPerAttribute > 4) {
            throw new DeveloperError('options.componentsPerAttribute must be between 1 and 4.');
        }
        if (!defined(options.values)) {
            throw new DeveloperError('options.values is required.');
        }
        
        /**
         * The datatype of each component in the attribute, e.g., individual elements in
         * {@link GeometryAttribute#values}.
         *
         * @type ComponentDatatype
         *
         * @default undefined
         */
        this.componentDatatype = options.componentDatatype;

        /**
         * A number between 1 and 4 that defines the number of components in an attributes.
         * For example, a position attribute with x, y, and z components would have 3 as
         * shown in the code example.
         *
         * @type Number
         *
         * @default undefined
         *
         * @example
         * attribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
         * attribute.componentsPerAttribute = 3;
         * attribute.values = new Float32Array([
         *   0.0, 0.0, 0.0,
         *   7500000.0, 0.0, 0.0,
         *   0.0, 7500000.0, 0.0
         * ]);
         */
        this.componentsPerAttribute = options.componentsPerAttribute;

        /**
         * When <code>true</code> and <code>componentDatatype</code> is an integer format,
         * indicate that the components should be mapped to the range [0, 1] (unsigned)
         * or [-1, 1] (signed) when they are accessed as floating-point for rendering.
         * <p>
         * This is commonly used when storing colors using {@link ComponentDatatype.UNSIGNED_BYTE}.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         *
         * @example
         * attribute.componentDatatype = Cesium.ComponentDatatype.UNSIGNED_BYTE;
         * attribute.componentsPerAttribute = 4;
         * attribute.normalize = true;
         * attribute.values = new Uint8Array([
         *   Cesium.Color.floatToByte(color.red),
         *   Cesium.Color.floatToByte(color.green),
         *   Cesium.Color.floatToByte(color.blue),
         *   Cesium.Color.floatToByte(color.alpha)
         * ]);
         */
        this.normalize = defaultValue(options.normalize, false);

        /**
         * The values for the attributes stored in a typed array.  In the code example,
         * every three elements in <code>values</code> defines one attributes since
         * <code>componentsPerAttribute</code> is 3.
         *
         * @type TypedArray
         *
         * @default undefined
         *
         * @example
         * attribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
         * attribute.componentsPerAttribute = 3;
         * attribute.values = new Float32Array([
         *   0.0, 0.0, 0.0,
         *   7500000.0, 0.0, 0.0,
         *   0.0, 7500000.0, 0.0
         * ]);
         */
        this.values = options.values;
    }

    return GeometryAttribute;
});

/*global define*/
define('Core/GeometryAttributes',[
        './defaultValue'
    ], function(
        defaultValue) {
    'use strict';

    /**
     * Attributes, which make up a geometry's vertices.  Each property in this object corresponds to a
     * {@link GeometryAttribute} containing the attribute's data.
     * <p>
     * Attributes are always stored non-interleaved in a Geometry.
     * </p>
     *
     * @alias GeometryAttributes
     * @constructor
     */
    function GeometryAttributes(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The 3D position attribute.
         * <p>
         * 64-bit floating-point (for precision).  3 components per attribute.
         * </p>
         *
         * @type GeometryAttribute
         *
         * @default undefined
         */
        this.position = options.position;

        /**
         * The normal attribute (normalized), which is commonly used for lighting.
         * <p>
         * 32-bit floating-point.  3 components per attribute.
         * </p>
         *
         * @type GeometryAttribute
         *
         * @default undefined
         */
        this.normal = options.normal;

        /**
         * The 2D texture coordinate attribute.
         * <p>
         * 32-bit floating-point.  2 components per attribute
         * </p>
         *
         * @type GeometryAttribute
         *
         * @default undefined
         */
        this.st = options.st;

        /**
         * The bitangent attribute (normalized), which is used for tangent-space effects like bump mapping.
         * <p>
         * 32-bit floating-point.  3 components per attribute.
         * </p>
         *
         * @type GeometryAttribute
         *
         * @default undefined
         */
        this.bitangent = options.bitangent;

        /**
         * The tangent attribute (normalized), which is used for tangent-space effects like bump mapping.
         * <p>
         * 32-bit floating-point.  3 components per attribute.
         * </p>
         *
         * @type GeometryAttribute
         *
         * @default undefined
         */
        this.tangent = options.tangent;

        /**
         * The color attribute.
         * <p>
         * 8-bit unsigned integer. 4 components per attribute.
         * </p>
         *
         * @type GeometryAttribute
         *
         * @default undefined
         */
        this.color = options.color;
    }

    return GeometryAttributes;
});

/*global define*/
define('Core/VertexFormat',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './freezeObject'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        freezeObject) {
    'use strict';

    /**
     * A vertex format defines what attributes make up a vertex.  A VertexFormat can be provided
     * to a {@link Geometry} to request that certain properties be computed, e.g., just position,
     * position and normal, etc.
     *
     * @param {Object} [options] An object with boolean properties corresponding to VertexFormat properties as shown in the code example.
     *
     * @alias VertexFormat
     * @constructor
     *
     * @example
     * // Create a vertex format with position and 2D texture coordinate attributes.
     * var format = new Cesium.VertexFormat({
     *   position : true,
     *   st : true
     * });
     *
     * @see Geometry#attributes
     * @see Packable
     */
    function VertexFormat(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * When <code>true</code>, the vertex has a 3D position attribute.
         * <p>
         * 64-bit floating-point (for precision).  3 components per attribute.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.position = defaultValue(options.position, false);

        /**
         * When <code>true</code>, the vertex has a normal attribute (normalized), which is commonly used for lighting.
         * <p>
         * 32-bit floating-point.  3 components per attribute.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.normal = defaultValue(options.normal, false);

        /**
         * When <code>true</code>, the vertex has a 2D texture coordinate attribute.
         * <p>
         * 32-bit floating-point.  2 components per attribute
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.st = defaultValue(options.st, false);

        /**
         * When <code>true</code>, the vertex has a bitangent attribute (normalized), which is used for tangent-space effects like bump mapping.
         * <p>
         * 32-bit floating-point.  3 components per attribute.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.bitangent = defaultValue(options.bitangent, false);

        /**
         * When <code>true</code>, the vertex has a tangent attribute (normalized), which is used for tangent-space effects like bump mapping.
         * <p>
         * 32-bit floating-point.  3 components per attribute.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.tangent = defaultValue(options.tangent, false);

        /**
         * When <code>true</code>, the vertex has an RGB color attribute.
         * <p>
         * 8-bit unsigned byte.  3 components per attribute.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.color = defaultValue(options.color, false);
    }

    /**
     * An immutable vertex format with only a position attribute.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     */
    VertexFormat.POSITION_ONLY = freezeObject(new VertexFormat({
        position : true
    }));

    /**
     * An immutable vertex format with position and normal attributes.
     * This is compatible with per-instance color appearances like {@link PerInstanceColorAppearance}.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     * @see VertexFormat#normal
     */
    VertexFormat.POSITION_AND_NORMAL = freezeObject(new VertexFormat({
        position : true,
        normal : true
    }));

    /**
     * An immutable vertex format with position, normal, and st attributes.
     * This is compatible with {@link MaterialAppearance} when {@link MaterialAppearance#materialSupport}
     * is <code>TEXTURED/code>.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     * @see VertexFormat#normal
     * @see VertexFormat#st
     */
    VertexFormat.POSITION_NORMAL_AND_ST = freezeObject(new VertexFormat({
        position : true,
        normal : true,
        st : true
    }));

    /**
     * An immutable vertex format with position and st attributes.
     * This is compatible with {@link EllipsoidSurfaceAppearance}.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     * @see VertexFormat#st
     */
    VertexFormat.POSITION_AND_ST = freezeObject(new VertexFormat({
        position : true,
        st : true
    }));

    /**
     * An immutable vertex format with position and color attributes.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     * @see VertexFormat#color
     */
    VertexFormat.POSITION_AND_COLOR = freezeObject(new VertexFormat({
        position : true,
        color : true
    }));

    /**
     * An immutable vertex format with well-known attributes: position, normal, st, tangent, and bitangent.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     * @see VertexFormat#normal
     * @see VertexFormat#st
     * @see VertexFormat#tangent
     * @see VertexFormat#bitangent
     */
    VertexFormat.ALL = freezeObject(new VertexFormat({
        position : true,
        normal : true,
        st : true,
        tangent  : true,
        bitangent : true
    }));

    /**
     * An immutable vertex format with position, normal, and st attributes.
     * This is compatible with most appearances and materials; however
     * normal and st attributes are not always required.  When this is
     * known in advance, another <code>VertexFormat</code> should be used.
     *
     * @type {VertexFormat}
     * @constant
     *
     * @see VertexFormat#position
     * @see VertexFormat#normal
     */
    VertexFormat.DEFAULT = VertexFormat.POSITION_NORMAL_AND_ST;

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    VertexFormat.packedLength = 6;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {VertexFormat} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    VertexFormat.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.position ? 1.0 : 0.0;
        array[startingIndex++] = value.normal ? 1.0 : 0.0;
        array[startingIndex++] = value.st ? 1.0 : 0.0;
        array[startingIndex++] = value.tangent ? 1.0 : 0.0;
        array[startingIndex++] = value.bitangent ? 1.0 : 0.0;
        array[startingIndex++] = value.color ? 1.0 : 0.0;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {VertexFormat} [result] The object into which to store the result.
     * @returns {VertexFormat} The modified result parameter or a new VertexFormat instance if one was not provided.
     */
    VertexFormat.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new VertexFormat();
        }

        result.position  = array[startingIndex++] === 1.0;
        result.normal    = array[startingIndex++] === 1.0;
        result.st        = array[startingIndex++] === 1.0;
        result.tangent   = array[startingIndex++] === 1.0;
        result.bitangent = array[startingIndex++] === 1.0;
        result.color     = array[startingIndex++] === 1.0;
        return result;
    };

    /**
     * Duplicates a VertexFormat instance.
     *
     * @param {VertexFormat} vertexFormat The vertex format to duplicate.
     * @param {VertexFormat} [result] The object onto which to store the result.
     * @returns {VertexFormat} The modified result parameter or a new VertexFormat instance if one was not provided. (Returns undefined if vertexFormat is undefined)
     */
    VertexFormat.clone = function(vertexFormat, result) {
        if (!defined(vertexFormat)) {
            return undefined;
        }
        if (!defined(result)) {
            result = new VertexFormat();
        }

        result.position = vertexFormat.position;
        result.normal = vertexFormat.normal;
        result.st = vertexFormat.st;
        result.tangent = vertexFormat.tangent;
        result.bitangent = vertexFormat.bitangent;
        result.color = vertexFormat.color;
        return result;
    };

    return VertexFormat;
});

/*global define*/
define('Core/BoxGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './PrimitiveType',
        './VertexFormat'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        PrimitiveType,
        VertexFormat) {
    'use strict';

    var diffScratch = new Cartesian3();

    /**
     * Describes a cube centered at the origin.
     *
     * @alias BoxGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box.
     * @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     *
     * @see BoxGeometry.fromDimensions
     * @see BoxGeometry.createGeometry
     * @see Packable
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo}
     *
     * @example
     * var box = new Cesium.BoxGeometry({
     *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
     *   maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0),
     *   minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0)
     * });
     * var geometry = Cesium.BoxGeometry.createGeometry(box);
     */
    function BoxGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var min = options.minimum;
        var max = options.maximum;

                if (!defined(min)) {
            throw new DeveloperError('options.minimum is required.');
        }
        if (!defined(max)) {
            throw new DeveloperError('options.maximum is required');
        }
        
        var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);

        this._minimum = Cartesian3.clone(min);
        this._maximum = Cartesian3.clone(max);
        this._vertexFormat = vertexFormat;
        this._workerName = 'createBoxGeometry';
    }

    /**
     * Creates a cube centered at the origin given its dimensions.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the <code>Cartesian3</code>, respectively.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @returns {BoxGeometry}
     *
     * @exception {DeveloperError} All dimensions components must be greater than or equal to zero.
     *
     *
     * @example
     * var box = Cesium.BoxGeometry.fromDimensions({
     *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
     *   dimensions : new Cesium.Cartesian3(500000.0, 500000.0, 500000.0)
     * });
     * var geometry = Cesium.BoxGeometry.createGeometry(box);
     *
     * @see BoxGeometry.createGeometry
     */
    BoxGeometry.fromDimensions = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var dimensions = options.dimensions;

                if (!defined(dimensions)) {
            throw new DeveloperError('options.dimensions is required.');
        }
        if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) {
            throw new DeveloperError('All dimensions components must be greater than or equal to zero.');
        }
        
        var corner = Cartesian3.multiplyByScalar(dimensions, 0.5, new Cartesian3());

        return new BoxGeometry({
            minimum : Cartesian3.negate(corner, new Cartesian3()),
            maximum : corner,
            vertexFormat : options.vertexFormat
        });
    };

    /**
     * Creates a cube from the dimensions of an AxisAlignedBoundingBox.
     *
     * @param {AxisAlignedBoundingBox} boundingBox A description of the AxisAlignedBoundingBox.
     * @returns {BoxGeometry}
     *
     *
     *
     * @example
     * var aabb = Cesium.AxisAlignedBoundingBox.fromPoints(Cesium.Cartesian3.fromDegreesArray([
     *      -72.0, 40.0,
     *      -70.0, 35.0,
     *      -75.0, 30.0,
     *      -70.0, 30.0,
     *      -68.0, 40.0
     * ]));
     * var box = Cesium.BoxGeometry.fromAxisAlignedBoundingBox(aabb);
     *
     * @see BoxGeometry.createGeometry
     */
    BoxGeometry.fromAxisAlignedBoundingBox = function (boundingBox) {
                if (!defined(boundingBox)) {
            throw new DeveloperError('boundingBox is required.');
        }
        
        return new BoxGeometry({
            minimum : boundingBox.minimum,
            maximum : boundingBox.maximum
        });
    };

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    BoxGeometry.packedLength = 2 * Cartesian3.packedLength + VertexFormat.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {BoxGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    BoxGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._minimum, array, startingIndex);
        Cartesian3.pack(value._maximum, array, startingIndex + Cartesian3.packedLength);
        VertexFormat.pack(value._vertexFormat, array, startingIndex + 2 * Cartesian3.packedLength);

        return array;
    };

    var scratchMin = new Cartesian3();
    var scratchMax = new Cartesian3();
    var scratchVertexFormat = new VertexFormat();
    var scratchOptions = {
        minimum: scratchMin,
        maximum: scratchMax,
        vertexFormat: scratchVertexFormat
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {BoxGeometry} [result] The object into which to store the result.
     * @returns {BoxGeometry} The modified result parameter or a new BoxGeometry instance if one was not provided.
     */
    BoxGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var min = Cartesian3.unpack(array, startingIndex, scratchMin);
        var max = Cartesian3.unpack(array, startingIndex + Cartesian3.packedLength, scratchMax);
        var vertexFormat = VertexFormat.unpack(array, startingIndex + 2 * Cartesian3.packedLength, scratchVertexFormat);

        if (!defined(result)) {
            return new BoxGeometry(scratchOptions);
        }

        result._minimum = Cartesian3.clone(min, result._minimum);
        result._maximum = Cartesian3.clone(max, result._maximum);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);

        return result;
    };

    /**
     * Computes the geometric representation of a box, including its vertices, indices, and a bounding sphere.
     *
     * @param {BoxGeometry} boxGeometry A description of the box.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    BoxGeometry.createGeometry = function(boxGeometry) {
        var min = boxGeometry._minimum;
        var max = boxGeometry._maximum;
        var vertexFormat = boxGeometry._vertexFormat;

        if (Cartesian3.equals(min, max)) {
            return;
        }

        var attributes = new GeometryAttributes();
        var indices;
        var positions;

        if (vertexFormat.position &&
                (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent)) {
            if (vertexFormat.position) {
                // 8 corner points.  Duplicated 3 times each for each incident edge/face.
                positions = new Float64Array(6 * 4 * 3);

                // +z face
                positions[0]  = min.x;
                positions[1]  = min.y;
                positions[2]  = max.z;
                positions[3]  = max.x;
                positions[4]  = min.y;
                positions[5]  = max.z;
                positions[6]  = max.x;
                positions[7]  = max.y;
                positions[8]  = max.z;
                positions[9]  = min.x;
                positions[10] = max.y;
                positions[11] = max.z;

                // -z face
                positions[12] = min.x;
                positions[13] = min.y;
                positions[14] = min.z;
                positions[15] = max.x;
                positions[16] = min.y;
                positions[17] = min.z;
                positions[18] = max.x;
                positions[19] = max.y;
                positions[20] = min.z;
                positions[21] = min.x;
                positions[22] = max.y;
                positions[23] = min.z;

                // +x face
                positions[24] = max.x;
                positions[25] = min.y;
                positions[26] = min.z;
                positions[27] = max.x;
                positions[28] = max.y;
                positions[29] = min.z;
                positions[30] = max.x;
                positions[31] = max.y;
                positions[32] = max.z;
                positions[33] = max.x;
                positions[34] = min.y;
                positions[35] = max.z;

                // -x face
                positions[36] = min.x;
                positions[37] = min.y;
                positions[38] = min.z;
                positions[39] = min.x;
                positions[40] = max.y;
                positions[41] = min.z;
                positions[42] = min.x;
                positions[43] = max.y;
                positions[44] = max.z;
                positions[45] = min.x;
                positions[46] = min.y;
                positions[47] = max.z;

                // +y face
                positions[48] = min.x;
                positions[49] = max.y;
                positions[50] = min.z;
                positions[51] = max.x;
                positions[52] = max.y;
                positions[53] = min.z;
                positions[54] = max.x;
                positions[55] = max.y;
                positions[56] = max.z;
                positions[57] = min.x;
                positions[58] = max.y;
                positions[59] = max.z;

                // -y face
                positions[60] = min.x;
                positions[61] = min.y;
                positions[62] = min.z;
                positions[63] = max.x;
                positions[64] = min.y;
                positions[65] = min.z;
                positions[66] = max.x;
                positions[67] = min.y;
                positions[68] = max.z;
                positions[69] = min.x;
                positions[70] = min.y;
                positions[71] = max.z;

                attributes.position = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.DOUBLE,
                    componentsPerAttribute : 3,
                    values : positions
                });
            }

            if (vertexFormat.normal) {
                var normals = new Float32Array(6 * 4 * 3);

                // +z face
                normals[0]  = 0.0;
                normals[1]  = 0.0;
                normals[2]  = 1.0;
                normals[3]  = 0.0;
                normals[4]  = 0.0;
                normals[5]  = 1.0;
                normals[6]  = 0.0;
                normals[7]  = 0.0;
                normals[8]  = 1.0;
                normals[9]  = 0.0;
                normals[10] = 0.0;
                normals[11] = 1.0;

                // -z face
                normals[12] = 0.0;
                normals[13] = 0.0;
                normals[14] = -1.0;
                normals[15] = 0.0;
                normals[16] = 0.0;
                normals[17] = -1.0;
                normals[18] = 0.0;
                normals[19] = 0.0;
                normals[20] = -1.0;
                normals[21] = 0.0;
                normals[22] = 0.0;
                normals[23] = -1.0;

                // +x face
                normals[24] = 1.0;
                normals[25] = 0.0;
                normals[26] = 0.0;
                normals[27] = 1.0;
                normals[28] = 0.0;
                normals[29] = 0.0;
                normals[30] = 1.0;
                normals[31] = 0.0;
                normals[32] = 0.0;
                normals[33] = 1.0;
                normals[34] = 0.0;
                normals[35] = 0.0;

                // -x face
                normals[36] = -1.0;
                normals[37] = 0.0;
                normals[38] = 0.0;
                normals[39] = -1.0;
                normals[40] = 0.0;
                normals[41] = 0.0;
                normals[42] = -1.0;
                normals[43] = 0.0;
                normals[44] = 0.0;
                normals[45] = -1.0;
                normals[46] = 0.0;
                normals[47] = 0.0;

                // +y face
                normals[48] = 0.0;
                normals[49] = 1.0;
                normals[50] = 0.0;
                normals[51] = 0.0;
                normals[52] = 1.0;
                normals[53] = 0.0;
                normals[54] = 0.0;
                normals[55] = 1.0;
                normals[56] = 0.0;
                normals[57] = 0.0;
                normals[58] = 1.0;
                normals[59] = 0.0;

                // -y face
                normals[60] = 0.0;
                normals[61] = -1.0;
                normals[62] = 0.0;
                normals[63] = 0.0;
                normals[64] = -1.0;
                normals[65] = 0.0;
                normals[66] = 0.0;
                normals[67] = -1.0;
                normals[68] = 0.0;
                normals[69] = 0.0;
                normals[70] = -1.0;
                normals[71] = 0.0;

                attributes.normal = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : normals
                });
            }

            if (vertexFormat.st) {
                var texCoords = new Float32Array(6 * 4 * 2);

                // +z face
                texCoords[0]  = 0.0;
                texCoords[1]  = 0.0;
                texCoords[2]  = 1.0;
                texCoords[3]  = 0.0;
                texCoords[4]  = 1.0;
                texCoords[5]  = 1.0;
                texCoords[6]  = 0.0;
                texCoords[7]  = 1.0;

                // -z face
                texCoords[8]  = 1.0;
                texCoords[9]  = 0.0;
                texCoords[10] = 0.0;
                texCoords[11] = 0.0;
                texCoords[12] = 0.0;
                texCoords[13] = 1.0;
                texCoords[14] = 1.0;
                texCoords[15] = 1.0;

                //+x face
                texCoords[16] = 0.0;
                texCoords[17] = 0.0;
                texCoords[18] = 1.0;
                texCoords[19] = 0.0;
                texCoords[20] = 1.0;
                texCoords[21] = 1.0;
                texCoords[22] = 0.0;
                texCoords[23] = 1.0;

                // -x face
                texCoords[24] = 1.0;
                texCoords[25] = 0.0;
                texCoords[26] = 0.0;
                texCoords[27] = 0.0;
                texCoords[28] = 0.0;
                texCoords[29] = 1.0;
                texCoords[30] = 1.0;
                texCoords[31] = 1.0;

                // +y face
                texCoords[32] = 1.0;
                texCoords[33] = 0.0;
                texCoords[34] = 0.0;
                texCoords[35] = 0.0;
                texCoords[36] = 0.0;
                texCoords[37] = 1.0;
                texCoords[38] = 1.0;
                texCoords[39] = 1.0;

                // -y face
                texCoords[40] = 0.0;
                texCoords[41] = 0.0;
                texCoords[42] = 1.0;
                texCoords[43] = 0.0;
                texCoords[44] = 1.0;
                texCoords[45] = 1.0;
                texCoords[46] = 0.0;
                texCoords[47] = 1.0;

                attributes.st = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 2,
                    values : texCoords
                });
            }

            if (vertexFormat.tangent) {
                var tangents = new Float32Array(6 * 4 * 3);

                // +z face
                tangents[0]  = 1.0;
                tangents[1]  = 0.0;
                tangents[2]  = 0.0;
                tangents[3]  = 1.0;
                tangents[4]  = 0.0;
                tangents[5]  = 0.0;
                tangents[6]  = 1.0;
                tangents[7]  = 0.0;
                tangents[8]  = 0.0;
                tangents[9]  = 1.0;
                tangents[10] = 0.0;
                tangents[11] = 0.0;

                // -z face
                tangents[12] = -1.0;
                tangents[13] = 0.0;
                tangents[14] = 0.0;
                tangents[15] = -1.0;
                tangents[16] = 0.0;
                tangents[17] = 0.0;
                tangents[18] = -1.0;
                tangents[19] = 0.0;
                tangents[20] = 0.0;
                tangents[21] = -1.0;
                tangents[22] = 0.0;
                tangents[23] = 0.0;

                // +x face
                tangents[24] = 0.0;
                tangents[25] = 1.0;
                tangents[26] = 0.0;
                tangents[27] = 0.0;
                tangents[28] = 1.0;
                tangents[29] = 0.0;
                tangents[30] = 0.0;
                tangents[31] = 1.0;
                tangents[32] = 0.0;
                tangents[33] = 0.0;
                tangents[34] = 1.0;
                tangents[35] = 0.0;

                // -x face
                tangents[36] = 0.0;
                tangents[37] = -1.0;
                tangents[38] = 0.0;
                tangents[39] = 0.0;
                tangents[40] = -1.0;
                tangents[41] = 0.0;
                tangents[42] = 0.0;
                tangents[43] = -1.0;
                tangents[44] = 0.0;
                tangents[45] = 0.0;
                tangents[46] = -1.0;
                tangents[47] = 0.0;

                // +y face
                tangents[48] = -1.0;
                tangents[49] = 0.0;
                tangents[50] = 0.0;
                tangents[51] = -1.0;
                tangents[52] = 0.0;
                tangents[53] = 0.0;
                tangents[54] = -1.0;
                tangents[55] = 0.0;
                tangents[56] = 0.0;
                tangents[57] = -1.0;
                tangents[58] = 0.0;
                tangents[59] = 0.0;

                // -y face
                tangents[60] = 1.0;
                tangents[61] = 0.0;
                tangents[62] = 0.0;
                tangents[63] = 1.0;
                tangents[64] = 0.0;
                tangents[65] = 0.0;
                tangents[66] = 1.0;
                tangents[67] = 0.0;
                tangents[68] = 0.0;
                tangents[69] = 1.0;
                tangents[70] = 0.0;
                tangents[71] = 0.0;

                attributes.tangent = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : tangents
                });
            }

            if (vertexFormat.bitangent) {
                var bitangents = new Float32Array(6 * 4 * 3);

                // +z face
                bitangents[0] = 0.0;
                bitangents[1] = 1.0;
                bitangents[2] = 0.0;
                bitangents[3] = 0.0;
                bitangents[4] = 1.0;
                bitangents[5] = 0.0;
                bitangents[6] = 0.0;
                bitangents[7] = 1.0;
                bitangents[8] = 0.0;
                bitangents[9] = 0.0;
                bitangents[10] = 1.0;
                bitangents[11] = 0.0;

                // -z face
                bitangents[12] = 0.0;
                bitangents[13] = 1.0;
                bitangents[14] = 0.0;
                bitangents[15] = 0.0;
                bitangents[16] = 1.0;
                bitangents[17] = 0.0;
                bitangents[18] = 0.0;
                bitangents[19] = 1.0;
                bitangents[20] = 0.0;
                bitangents[21] = 0.0;
                bitangents[22] = 1.0;
                bitangents[23] = 0.0;

                // +x face
                bitangents[24] = 0.0;
                bitangents[25] = 0.0;
                bitangents[26] = 1.0;
                bitangents[27] = 0.0;
                bitangents[28] = 0.0;
                bitangents[29] = 1.0;
                bitangents[30] = 0.0;
                bitangents[31] = 0.0;
                bitangents[32] = 1.0;
                bitangents[33] = 0.0;
                bitangents[34] = 0.0;
                bitangents[35] = 1.0;

                // -x face
                bitangents[36] = 0.0;
                bitangents[37] = 0.0;
                bitangents[38] = 1.0;
                bitangents[39] = 0.0;
                bitangents[40] = 0.0;
                bitangents[41] = 1.0;
                bitangents[42] = 0.0;
                bitangents[43] = 0.0;
                bitangents[44] = 1.0;
                bitangents[45] = 0.0;
                bitangents[46] = 0.0;
                bitangents[47] = 1.0;

                // +y face
                bitangents[48] = 0.0;
                bitangents[49] = 0.0;
                bitangents[50] = 1.0;
                bitangents[51] = 0.0;
                bitangents[52] = 0.0;
                bitangents[53] = 1.0;
                bitangents[54] = 0.0;
                bitangents[55] = 0.0;
                bitangents[56] = 1.0;
                bitangents[57] = 0.0;
                bitangents[58] = 0.0;
                bitangents[59] = 1.0;

                // -y face
                bitangents[60] = 0.0;
                bitangents[61] = 0.0;
                bitangents[62] = 1.0;
                bitangents[63] = 0.0;
                bitangents[64] = 0.0;
                bitangents[65] = 1.0;
                bitangents[66] = 0.0;
                bitangents[67] = 0.0;
                bitangents[68] = 1.0;
                bitangents[69] = 0.0;
                bitangents[70] = 0.0;
                bitangents[71] = 1.0;

                attributes.bitangent = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : bitangents
                });
            }

            // 12 triangles:  6 faces, 2 triangles each.
            indices = new Uint16Array(6 * 2 * 3);

            // +z face
            indices[0] = 0;
            indices[1] = 1;
            indices[2] = 2;
            indices[3] = 0;
            indices[4] = 2;
            indices[5] = 3;

            // -z face
            indices[6] = 4 + 2;
            indices[7] = 4 + 1;
            indices[8] = 4 + 0;
            indices[9] = 4 + 3;
            indices[10] = 4 + 2;
            indices[11] = 4 + 0;

            // +x face
            indices[12] = 8 + 0;
            indices[13] = 8 + 1;
            indices[14] = 8 + 2;
            indices[15] = 8 + 0;
            indices[16] = 8 + 2;
            indices[17] = 8 + 3;

            // -x face
            indices[18] = 12 + 2;
            indices[19] = 12 + 1;
            indices[20] = 12 + 0;
            indices[21] = 12 + 3;
            indices[22] = 12 + 2;
            indices[23] = 12 + 0;

            // +y face
            indices[24] = 16 + 2;
            indices[25] = 16 + 1;
            indices[26] = 16 + 0;
            indices[27] = 16 + 3;
            indices[28] = 16 + 2;
            indices[29] = 16 + 0;

            // -y face
            indices[30] = 20 + 0;
            indices[31] = 20 + 1;
            indices[32] = 20 + 2;
            indices[33] = 20 + 0;
            indices[34] = 20 + 2;
            indices[35] = 20 + 3;
        } else {
            // Positions only - no need to duplicate corner points
            positions = new Float64Array(8 * 3);

            positions[0] = min.x;
            positions[1] = min.y;
            positions[2] = min.z;
            positions[3] = max.x;
            positions[4] = min.y;
            positions[5] = min.z;
            positions[6] = max.x;
            positions[7] = max.y;
            positions[8] = min.z;
            positions[9] = min.x;
            positions[10] = max.y;
            positions[11] = min.z;
            positions[12] = min.x;
            positions[13] = min.y;
            positions[14] = max.z;
            positions[15] = max.x;
            positions[16] = min.y;
            positions[17] = max.z;
            positions[18] = max.x;
            positions[19] = max.y;
            positions[20] = max.z;
            positions[21] = min.x;
            positions[22] = max.y;
            positions[23] = max.z;

            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : positions
            });

            // 12 triangles:  6 faces, 2 triangles each.
            indices = new Uint16Array(6 * 2 * 3);

            // plane z = corner.Z
            indices[0] = 4;
            indices[1] = 5;
            indices[2] = 6;
            indices[3] = 4;
            indices[4] = 6;
            indices[5] = 7;

            // plane z = -corner.Z
            indices[6] = 1;
            indices[7] = 0;
            indices[8] = 3;
            indices[9] = 1;
            indices[10] = 3;
            indices[11] = 2;

            // plane x = corner.X
            indices[12] = 1;
            indices[13] = 6;
            indices[14] = 5;
            indices[15] = 1;
            indices[16] = 2;
            indices[17] = 6;

            // plane y = corner.Y
            indices[18] = 2;
            indices[19] = 3;
            indices[20] = 7;
            indices[21] = 2;
            indices[22] = 7;
            indices[23] = 6;

            // plane x = -corner.X
            indices[24] = 3;
            indices[25] = 0;
            indices[26] = 4;
            indices[27] = 3;
            indices[28] = 4;
            indices[29] = 7;

            // plane y = -corner.Y
            indices[30] = 0;
            indices[31] = 1;
            indices[32] = 5;
            indices[33] = 0;
            indices[34] = 5;
            indices[35] = 4;
        }

        var diff = Cartesian3.subtract(max, min, diffScratch);
        var radius = Cartesian3.magnitude(diff) * 0.5;

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : new BoundingSphere(Cartesian3.ZERO, radius)
        });
    };

    return BoxGeometry;
});

/*global define*/
define('Core/BoxOutlineGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './PrimitiveType'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        PrimitiveType) {
    'use strict';

    var diffScratch = new Cartesian3();

    /**
     * A description of the outline of a cube centered at the origin.
     *
     * @alias BoxOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.minimum The minimum x, y, and z coordinates of the box.
     * @param {Cartesian3} options.maximum The maximum x, y, and z coordinates of the box.
     *
     * @see BoxOutlineGeometry.fromDimensions
     * @see BoxOutlineGeometry.createGeometry
     * @see Packable
     *
     * @example
     * var box = new Cesium.BoxOutlineGeometry({
     *   maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0),
     *   minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0)
     * });
     * var geometry = Cesium.BoxOutlineGeometry.createGeometry(box);
     */
    function BoxOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var min = options.minimum;
        var max = options.maximum;

                if (!defined(min)) {
            throw new DeveloperError('options.minimum is required.');
        }
        if (!defined(max)) {
            throw new DeveloperError('options.maximum is required');
        }
        
        this._min = Cartesian3.clone(min);
        this._max = Cartesian3.clone(max);
        this._workerName = 'createBoxOutlineGeometry';
    }

    /**
     * Creates an outline of a cube centered at the origin given its dimensions.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.dimensions The width, depth, and height of the box stored in the x, y, and z coordinates of the <code>Cartesian3</code>, respectively.
     * @returns {BoxOutlineGeometry}
     *
     * @exception {DeveloperError} All dimensions components must be greater than or equal to zero.
     *
     *
     * @example
     * var box = Cesium.BoxOutlineGeometry.fromDimensions({
     *   dimensions : new Cesium.Cartesian3(500000.0, 500000.0, 500000.0)
     * });
     * var geometry = Cesium.BoxOutlineGeometry.createGeometry(box);
     *
     * @see BoxOutlineGeometry.createGeometry
     */
    BoxOutlineGeometry.fromDimensions = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var dimensions = options.dimensions;

                if (!defined(dimensions)) {
            throw new DeveloperError('options.dimensions is required.');
        }
        if (dimensions.x < 0 || dimensions.y < 0 || dimensions.z < 0) {
            throw new DeveloperError('All dimensions components must be greater than or equal to zero.');
        }
        
        var corner = Cartesian3.multiplyByScalar(dimensions, 0.5, new Cartesian3());

        return new BoxOutlineGeometry({
            minimum : Cartesian3.negate(corner, new Cartesian3()),
            maximum : corner
        });
    };

    /**
     * Creates an outline of a cube from the dimensions of an AxisAlignedBoundingBox.
     *
     * @param {AxisAlignedBoundingBox} boundingBox A description of the AxisAlignedBoundingBox.
     * @returns {BoxOutlineGeometry}
     *
     *
     *
     * @example
     * var aabb = Cesium.AxisAlignedBoundingBox.fromPoints(Cesium.Cartesian3.fromDegreesArray([
     *      -72.0, 40.0,
     *      -70.0, 35.0,
     *      -75.0, 30.0,
     *      -70.0, 30.0,
     *      -68.0, 40.0
     * ]));
     * var box = Cesium.BoxOutlineGeometry.fromAxisAlignedBoundingBox(aabb);
     *
     *  @see BoxOutlineGeometry.createGeometry
     */
    BoxOutlineGeometry.fromAxisAlignedBoundingBox = function(boundingBox) {
                if (!defined(boundingBox)) {
            throw new DeveloperError('boundingBox is required.');
        }
        
        return new BoxOutlineGeometry({
            minimum : boundingBox.minimum,
            maximum : boundingBox.maximum
        });
    };

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    BoxOutlineGeometry.packedLength = 2 * Cartesian3.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {BoxOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    BoxOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._min, array, startingIndex);
        Cartesian3.pack(value._max, array, startingIndex + Cartesian3.packedLength);
        return array;
    };

    var scratchMin = new Cartesian3();
    var scratchMax = new Cartesian3();
    var scratchOptions = {
        minimum : scratchMin,
        maximum : scratchMax
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {BoxOutlineGeometry} [result] The object into which to store the result.
     * @returns {BoxOutlineGeometry} The modified result parameter or a new BoxOutlineGeometry instance if one was not provided.
     */
    BoxOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var min = Cartesian3.unpack(array, startingIndex, scratchMin);
        var max = Cartesian3.unpack(array, startingIndex + Cartesian3.packedLength, scratchMax);

        if (!defined(result)) {
            return new BoxOutlineGeometry(scratchOptions);
        }

        result._min = Cartesian3.clone(min, result._min);
        result._max = Cartesian3.clone(max, result._max);

        return result;
    };

    /**
     * Computes the geometric representation of an outline of a box, including its vertices, indices, and a bounding sphere.
     *
     * @param {BoxOutlineGeometry} boxGeometry A description of the box outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    BoxOutlineGeometry.createGeometry = function(boxGeometry) {
        var min = boxGeometry._min;
        var max = boxGeometry._max;

        if (Cartesian3.equals(min, max)) {
            return;
        }

        var attributes = new GeometryAttributes();
        var indices = new Uint16Array(12 * 2);
        var positions = new Float64Array(8 * 3);

        positions[0] = min.x;
        positions[1] = min.y;
        positions[2] = min.z;
        positions[3] = max.x;
        positions[4] = min.y;
        positions[5] = min.z;
        positions[6] = max.x;
        positions[7] = max.y;
        positions[8] = min.z;
        positions[9] = min.x;
        positions[10] = max.y;
        positions[11] = min.z;

        positions[12] = min.x;
        positions[13] = min.y;
        positions[14] = max.z;
        positions[15] = max.x;
        positions[16] = min.y;
        positions[17] = max.z;
        positions[18] = max.x;
        positions[19] = max.y;
        positions[20] = max.z;
        positions[21] = min.x;
        positions[22] = max.y;
        positions[23] = max.z;

        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : positions
        });

        // top
        indices[0] = 4;
        indices[1] = 5;
        indices[2] = 5;
        indices[3] = 6;
        indices[4] = 6;
        indices[5] = 7;
        indices[6] = 7;
        indices[7] = 4;

        // bottom
        indices[8] = 0;
        indices[9] = 1;
        indices[10] = 1;
        indices[11] = 2;
        indices[12] = 2;
        indices[13] = 3;
        indices[14] = 3;
        indices[15] = 0;

        // left
        indices[16] = 0;
        indices[17] = 4;
        indices[18] = 1;
        indices[19] = 5;

        //right
        indices[20] = 2;
        indices[21] = 6;
        indices[22] = 3;
        indices[23] = 7;

        var diff = Cartesian3.subtract(max, min, diffScratch);
        var radius = Cartesian3.magnitude(diff) * 0.5;

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : new BoundingSphere(Cartesian3.ZERO, radius)
        });
    };

    return BoxOutlineGeometry;
});

/*global define*/
define('Core/cancelAnimationFrame',[
        './defined'
    ], function(
        defined) {
    'use strict';

    if (typeof window === 'undefined') {
        return;
    }

    var implementation = window.cancelAnimationFrame;
    (function() {
        // look for vendor prefixed function
        if (!defined(implementation)) {
            var vendors = ['webkit', 'moz', 'ms', 'o'];
            var i = 0;
            var len = vendors.length;
            while (i < len && !defined(implementation)) {
                implementation = window[vendors[i] + 'CancelAnimationFrame'];
                if (!defined(implementation)) {
                    implementation = window[vendors[i] + 'CancelRequestAnimationFrame'];
                }
                ++i;
            }
        }

        // otherwise, assume requestAnimationFrame is based on setTimeout, so use clearTimeout
        if (!defined(implementation)) {
            implementation = clearTimeout;
        }
    })();

    /**
     * A browser-independent function to cancel an animation frame requested using {@link requestAnimationFrame}.
     *
     * @exports cancelAnimationFrame
     *
     * @param {Number} requestID The value returned by {@link requestAnimationFrame}.
     *
     * @see {@link http://www.w3.org/TR/animation-timing/#the-WindowAnimationTiming-interface|The WindowAnimationTiming interface}
     */
    function cancelAnimationFrame(requestID) {
        // we need this extra wrapper function because the native cancelAnimationFrame
        // functions must be invoked on the global scope (window), which is not the case
        // if invoked as Cesium.cancelAnimationFrame(requestID)
        implementation(requestID);
    }

    return cancelAnimationFrame;
});

/*global define*/
define('Core/CartographicGeocoderService',[
    './Cartesian3',
    './defaultValue',
    './defineProperties',
    './defined',
    './DeveloperError',
    '../ThirdParty/when'
], function(
    Cartesian3,
    defaultValue,
    defineProperties,
    defined,
    DeveloperError,
    when) {
    'use strict';

    /**
     * Geocodes queries containing longitude and latitude coordinates and an optional height.
     * Query format: `longitude latitude (height)` with longitude/latitude in degrees and height in meters.
     *
     * @alias CartographicGeocoderService
     * @constructor
     */
    function CartographicGeocoderService() {
    }

    /**
     * @function
     *
     * @param {String} query The query to be sent to the geocoder service
     * @returns {Promise<GeocoderResult[]>}
     */
    CartographicGeocoderService.prototype.geocode = function(query) {
                if (!defined(query)) {
            throw new DeveloperError('query must be defined');
        }
        
        var splitQuery = query.match(/[^\s,\n]+/g);
        if ((splitQuery.length === 2) || (splitQuery.length === 3)) {
            var longitude = +splitQuery[0];
            var latitude = +splitQuery[1];
            var height = (splitQuery.length === 3) ? +splitQuery[2] : 300.0;

            if (!isNaN(longitude) && !isNaN(latitude) && !isNaN(height)) {
                var result = {
                    displayName: query,
                    destination: Cartesian3.fromDegrees(longitude, latitude, height)
                };
                return when.resolve([result]);
            }
        }
        return when.resolve([]);
    };

    return CartographicGeocoderService;
});

/*global define*/
define('Core/Spline',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Creates a curve parameterized and evaluated by time. This type describes an interface
     * and is not intended to be instantiated directly.
     *
     * @alias Spline
     * @constructor
     *
     * @see CatmullRomSpline
     * @see HermiteSpline
     * @see LinearSpline
     * @see QuaternionSpline
     */
    function Spline() {
        /**
         * An array of times for the control points.
         * @type {Number[]}
         * @default undefined
         */
        this.times = undefined;

        /**
         * An array of control points.
         * @type {Cartesian3[]|Quaternion[]}
         * @default undefined
         */
        this.points = undefined;

        DeveloperError.throwInstantiationError();
    }

    /**
     * Evaluates the curve at a given time.
     * @function
     *
     * @param {Number} time The time at which to evaluate the curve.
     * @param {Cartesian3|Quaternion} [result] The object onto which to store the result.
     * @returns {Cartesian3|Quaternion} The modified result parameter or a new instance of the point on the curve at the given time.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    Spline.prototype.evaluate = DeveloperError.throwInstantiationError;

    /**
     * Finds an index <code>i</code> in <code>times</code> such that the parameter
     * <code>time</code> is in the interval <code>[times[i], times[i + 1]]</code>.
     *
     * @param {Number} time The time.
     * @param {Number} startIndex The index from which to start the search.
     * @returns {Number} The index for the element at the start of the interval.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    Spline.prototype.findTimeInterval = function(time, startIndex) {
        var times = this.times;
        var length = times.length;

                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (time < times[0] || time > times[length - 1]) {
            throw new DeveloperError('time is out of range.');
        }
        
        // Take advantage of temporal coherence by checking current, next and previous intervals
        // for containment of time.
        startIndex = defaultValue(startIndex, 0);

        if (time >= times[startIndex]) {
            if (startIndex + 1 < length && time < times[startIndex + 1]) {
                return startIndex;
            } else if (startIndex + 2 < length && time < times[startIndex + 2]) {
                return startIndex + 1;
            }
        } else if (startIndex - 1 >= 0 && time >= times[startIndex - 1]) {
            return startIndex - 1;
        }

        // The above failed so do a linear search. For the use cases so far, the
        // length of the list is less than 10. In the future, if there is a bottle neck,
        // it might be here.

        var i;
        if (time > times[startIndex]) {
            for (i = startIndex; i < length - 1; ++i) {
                if (time >= times[i] && time < times[i + 1]) {
                    break;
                }
            }
        } else {
            for (i = startIndex - 1; i >= 0; --i) {
                if (time >= times[i] && time < times[i + 1]) {
                    break;
                }
            }
        }

        if (i === length - 1) {
            i = length - 2;
        }

        return i;
    };

    return Spline;
});

/*global define*/
define('Core/LinearSpline',[
        './Cartesian3',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Spline'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Spline) {
    'use strict';

    /**
     * A spline that uses piecewise linear interpolation to create a curve.
     *
     * @alias LinearSpline
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
     *                The values are in no way connected to the clock time. They are the parameterization for the curve.
     * @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
     *
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times.length must be equal to points.length.
     *
     *
     * @example
     * var times = [ 0.0, 1.5, 3.0, 4.5, 6.0 ];
     * var spline = new Cesium.LinearSpline({
     *     times : times,
     *     points : [
     *         new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
     *         new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
     *         new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
     *         new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
     *         new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
     *     ]
     * });
     *
     * var p0 = spline.evaluate(times[0]);
     * 
     * @see HermiteSpline
     * @see CatmullRomSpline
     * @see QuaternionSpline
     */
    function LinearSpline(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var points = options.points;
        var times = options.times;

                if (!defined(points) || !defined(times)) {
            throw new DeveloperError('points and times are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length) {
            throw new DeveloperError('times.length must be equal to points.length.');
        }
        
        this._times = times;
        this._points = points;

        this._lastTimeIndex = 0;
    }

    defineProperties(LinearSpline.prototype, {
        /**
         * An array of times for the control points.
         *
         * @memberof LinearSpline.prototype
         *
         * @type {Number[]}
         * @readonly
         */
        times : {
            get : function() {
                return this._times;
            }
        },

        /**
         * An array of {@link Cartesian3} control points.
         *
         * @memberof LinearSpline.prototype
         *
         * @type {Cartesian3[]}
         * @readonly
         */
        points : {
            get : function() {
                return this._points;
            }
        }
    });

    /**
     * Finds an index <code>i</code> in <code>times</code> such that the parameter
     * <code>time</code> is in the interval <code>[times[i], times[i + 1]]</code>.
     * @function
     *
     * @param {Number} time The time.
     * @returns {Number} The index for the element at the start of the interval.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    LinearSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;

    /**
     * Evaluates the curve at a given time.
     *
     * @param {Number} time The time at which to evaluate the curve.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    LinearSpline.prototype.evaluate = function(time, result) {
        var points = this.points;
        var times = this.times;

        var i = this._lastTimeIndex = this.findTimeInterval(time, this._lastTimeIndex);
        var u = (time - times[i]) / (times[i + 1] - times[i]);

        if (!defined(result)) {
            result = new Cartesian3();
        }

        return Cartesian3.lerp(points[i], points[i + 1], u, result);
    };

    return LinearSpline;
});

/*global define*/
define('Core/TridiagonalSystemSolver',[
        './Cartesian3',
        './defined',
        './DeveloperError'
    ], function(
        Cartesian3,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Uses the Tridiagonal Matrix Algorithm, also known as the Thomas Algorithm, to solve
     * a system of linear equations where the coefficient matrix is a tridiagonal matrix.
     *
     * @exports TridiagonalSystemSolver
     */
    var TridiagonalSystemSolver = {};

    /**
     * Solves a tridiagonal system of linear equations.
     *
     * @param {Number[]} diagonal An array with length <code>n</code> that contains the diagonal of the coefficient matrix.
     * @param {Number[]} lower An array with length <code>n - 1</code> that contains the lower diagonal of the coefficient matrix.
     * @param {Number[]} upper An array with length <code>n - 1</code> that contains the upper diagonal of the coefficient matrix.
     * @param {Cartesian3[]} right An array of Cartesians with length <code>n</code> that is the right side of the system of equations.
     *
     * @exception {DeveloperError} diagonal and right must have the same lengths.
     * @exception {DeveloperError} lower and upper must have the same lengths.
     * @exception {DeveloperError} lower and upper must be one less than the length of diagonal.
     *
     * @performance Linear time.
     *
     * @example
     * var lowerDiagonal = [1.0, 1.0, 1.0, 1.0];
     * var diagonal = [2.0, 4.0, 4.0, 4.0, 2.0];
     * var upperDiagonal = [1.0, 1.0, 1.0, 1.0];
     * var rightHandSide = [
     *     new Cesium.Cartesian3(410757.0, -1595711.0, 1375302.0),
     *     new Cesium.Cartesian3(-5986705.0, -2190640.0, 1099600.0),
     *     new Cesium.Cartesian3(-12593180.0, 288588.0, -1755549.0),
     *     new Cesium.Cartesian3(-5349898.0, 2457005.0, -2685438.0),
     *     new Cesium.Cartesian3(845820.0, 1573488.0, -1205591.0)
     * ];
     *
     * var solution = Cesium.TridiagonalSystemSolver.solve(lowerDiagonal, diagonal, upperDiagonal, rightHandSide);
     *
     * @returns {Cartesian3[]} An array of Cartesians with length <code>n</code> that is the solution to the tridiagonal system of equations.
     */
    TridiagonalSystemSolver.solve = function(lower, diagonal, upper, right) {
                if (!defined(lower) || !(lower instanceof Array)) {
            throw new DeveloperError('The array lower is required.');
        }
        if (!defined(diagonal) || !(diagonal instanceof Array)) {
            throw new DeveloperError('The array diagonal is required.');
        }
        if (!defined(upper) || !(upper instanceof Array)) {
            throw new DeveloperError('The array upper is required.');
        }
        if (!defined(right) || !(right instanceof Array)) {
            throw new DeveloperError('The array right is required.');
        }
        if (diagonal.length !== right.length) {
            throw new DeveloperError('diagonal and right must have the same lengths.');
        }
        if (lower.length !== upper.length) {
            throw new DeveloperError('lower and upper must have the same lengths.');
        } else if (lower.length !== diagonal.length - 1) {
            throw new DeveloperError('lower and upper must be one less than the length of diagonal.');
        }
        
        var c = new Array(upper.length);
        var d = new Array(right.length);
        var x = new Array(right.length);

        var i;
        for (i = 0; i < d.length; i++) {
            d[i] = new Cartesian3();
            x[i] = new Cartesian3();
        }

        c[0] = upper[0] / diagonal[0];
        d[0] = Cartesian3.multiplyByScalar(right[0], 1.0 / diagonal[0], d[0]);

        var scalar;
        for (i = 1; i < c.length; ++i) {
            scalar = 1.0 / (diagonal[i] - c[i - 1] * lower[i - 1]);
            c[i] = upper[i] * scalar;
            d[i] = Cartesian3.subtract(right[i], Cartesian3.multiplyByScalar(d[i - 1], lower[i - 1], d[i]), d[i]);
            d[i] = Cartesian3.multiplyByScalar(d[i], scalar, d[i]);
        }

        scalar = 1.0 / (diagonal[i] - c[i - 1] * lower[i - 1]);
        d[i] = Cartesian3.subtract(right[i], Cartesian3.multiplyByScalar(d[i - 1], lower[i - 1], d[i]), d[i]);
        d[i] = Cartesian3.multiplyByScalar(d[i], scalar, d[i]);

        x[x.length - 1] = d[d.length - 1];
        for (i = x.length - 2; i >= 0; --i) {
            x[i] = Cartesian3.subtract(d[i], Cartesian3.multiplyByScalar(x[i + 1], c[i], x[i]), x[i]);
        }

        return x;
    };

    return TridiagonalSystemSolver;
});

/*global define*/
define('Core/HermiteSpline',[
        './Cartesian3',
        './Cartesian4',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './LinearSpline',
        './Matrix4',
        './Spline',
        './TridiagonalSystemSolver'
    ], function(
        Cartesian3,
        Cartesian4,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        LinearSpline,
        Matrix4,
        Spline,
        TridiagonalSystemSolver) {
    'use strict';

    var scratchLower = [];
    var scratchDiagonal = [];
    var scratchUpper = [];
    var scratchRight = [];

    function generateClamped(points, firstTangent, lastTangent) {
        var l = scratchLower;
        var u = scratchUpper;
        var d = scratchDiagonal;
        var r = scratchRight;

        l.length = u.length = points.length - 1;
        d.length = r.length = points.length;

        var i;
        l[0] = d[0] = 1.0;
        u[0] = 0.0;

        var right = r[0];
        if (!defined(right)) {
            right = r[0] = new Cartesian3();
        }
        Cartesian3.clone(firstTangent, right);

        for (i = 1; i < l.length - 1; ++i) {
            l[i] = u[i] = 1.0;
            d[i] = 4.0;

            right = r[i];
            if (!defined(right)) {
                right = r[i] = new Cartesian3();
            }
            Cartesian3.subtract(points[i + 1], points[i - 1], right);
            Cartesian3.multiplyByScalar(right, 3.0, right);
        }

        l[i] = 0.0;
        u[i] = 1.0;
        d[i] = 4.0;

        right = r[i];
        if (!defined(right)) {
            right = r[i] = new Cartesian3();
        }
        Cartesian3.subtract(points[i + 1], points[i - 1], right);
        Cartesian3.multiplyByScalar(right, 3.0, right);

        d[i + 1] = 1.0;
        right = r[i + 1];
        if (!defined(right)) {
            right = r[i + 1] = new Cartesian3();
        }
        Cartesian3.clone(lastTangent, right);

        return TridiagonalSystemSolver.solve(l, d, u, r);
    }

    function generateNatural(points){
        var l = scratchLower;
        var u = scratchUpper;
        var d = scratchDiagonal;
        var r = scratchRight;

        l.length = u.length = points.length - 1;
        d.length = r.length = points.length;

        var i;
        l[0] = u[0] = 1.0;
        d[0] = 2.0;

        var right = r[0];
        if (!defined(right)) {
            right = r[0] = new Cartesian3();
        }
        Cartesian3.subtract(points[1], points[0], right);
        Cartesian3.multiplyByScalar(right, 3.0, right);

        for (i = 1; i < l.length; ++i) {
            l[i] = u[i] = 1.0;
            d[i] = 4.0;

            right = r[i];
            if (!defined(right)) {
                right = r[i] = new Cartesian3();
            }
            Cartesian3.subtract(points[i + 1], points[i - 1], right);
            Cartesian3.multiplyByScalar(right, 3.0, right);
        }

        d[i] = 2.0;

        right = r[i];
        if (!defined(right)) {
            right = r[i] = new Cartesian3();
        }
        Cartesian3.subtract(points[i], points[i - 1], right);
        Cartesian3.multiplyByScalar(right, 3.0, right);

        return TridiagonalSystemSolver.solve(l, d, u, r);
    }

    /**
     * A Hermite spline is a cubic interpolating spline. Points, incoming tangents, outgoing tangents, and times
     * must be defined for each control point. The outgoing tangents are defined for points [0, n - 2] and the incoming
     * tangents are defined for points [1, n - 1]. For example, when interpolating a segment of the curve between <code>points[i]</code> and
     * <code>points[i + 1]</code>, the tangents at the points will be <code>outTangents[i]</code> and <code>inTangents[i]</code>,
     * respectively.
     *
     * @alias HermiteSpline
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
     *                The values are in no way connected to the clock time. They are the parameterization for the curve.
     * @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
     * @param {Cartesian3[]} options.inTangents The array of {@link Cartesian3} incoming tangents at each control point.
     * @param {Cartesian3[]} options.outTangents The array of {@link Cartesian3} outgoing tangents at each control point.
     *
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times.length must be equal to points.length.
     * @exception {DeveloperError} inTangents and outTangents must have a length equal to points.length - 1.
     *
     *
     * @example
     * // Create a G<sup>1</sup> continuous Hermite spline
     * var times = [ 0.0, 1.5, 3.0, 4.5, 6.0 ];
     * var spline = new Cesium.HermiteSpline({
     *     times : times,
     *     points : [
     *         new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
     *         new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
     *         new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
     *         new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
     *         new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
     *     ],
     *     outTangents : [
     *         new Cesium.Cartesian3(1125196, -161816, 270551),
     *         new Cesium.Cartesian3(-996690.5, -365906.5, 184028.5),
     *         new Cesium.Cartesian3(-2096917, 48379.5, -292683.5),
     *         new Cesium.Cartesian3(-890902.5, 408999.5, -447115)
     *     ],
     *     inTangents : [
     *         new Cesium.Cartesian3(-1993381, -731813, 368057),
     *         new Cesium.Cartesian3(-4193834, 96759, -585367),
     *         new Cesium.Cartesian3(-1781805, 817999, -894230),
     *         new Cesium.Cartesian3(1165345, 112641, 47281)
     *     ]
     * });
     *
     * var p0 = spline.evaluate(times[0]);
     * 
     * @see CatmullRomSpline
     * @see LinearSpline
     * @see QuaternionSpline
     */
    function HermiteSpline(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var points = options.points;
        var times = options.times;
        var inTangents = options.inTangents;
        var outTangents = options.outTangents;

                if (!defined(points) || !defined(times) || !defined(inTangents) || !defined(outTangents)) {
            throw new DeveloperError('times, points, inTangents, and outTangents are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length) {
            throw new DeveloperError('times.length must be equal to points.length.');
        }
        if (inTangents.length !== outTangents.length || inTangents.length !== points.length - 1) {
            throw new DeveloperError('inTangents and outTangents must have a length equal to points.length - 1.');
        }
        
        this._times = times;
        this._points = points;
        this._inTangents = inTangents;
        this._outTangents = outTangents;

        this._lastTimeIndex = 0;
    }

    defineProperties(HermiteSpline.prototype, {
        /**
         * An array of times for the control points.
         *
         * @memberof HermiteSpline.prototype
         *
         * @type {Number[]}
         * @readonly
         */
        times : {
            get : function() {
                return this._times;
            }
        },

        /**
         * An array of {@link Cartesian3} control points.
         *
         * @memberof HermiteSpline.prototype
         *
         * @type {Cartesian3[]}
         * @readonly
         */
        points : {
            get : function() {
                return this._points;
            }
        },

        /**
         * An array of {@link Cartesian3} incoming tangents at each control point.
         *
         * @memberof HermiteSpline.prototype
         *
         * @type {Cartesian3[]}
         * @readonly
         */
        inTangents : {
            get : function() {
                return this._inTangents;
            }
        },

        /**
         * An array of {@link Cartesian3} outgoing tangents at each control point.
         *
         * @memberof HermiteSpline.prototype
         *
         * @type {Cartesian3[]}
         * @readonly
         */
        outTangents : {
            get : function() {
                return this._outTangents;
            }
        }
    });

    /**
     * Creates a spline where the tangents at each control point are the same.
     * The curves are guaranteed to be at least in the class C<sup>1</sup>.
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times The array of control point times.
     * @param {Cartesian3[]} options.points The array of control points.
     * @param {Cartesian3[]} options.tangents The array of tangents at the control points.
     * @returns {HermiteSpline} A hermite spline.
     *
     * @exception {DeveloperError} points, times and tangents are required.
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times, points and tangents must have the same length.
     *
     * @example
     * var points = [
     *     new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
     *     new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
     *     new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
     *     new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
     *     new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
     * ];
     *
     * // Add tangents
     * var tangents = new Array(points.length);
     * tangents[0] = new Cesium.Cartesian3(1125196, -161816, 270551);
     * var temp = new Cesium.Cartesian3();
     * for (var i = 1; i < tangents.length - 1; ++i) {
     *     tangents[i] = Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.subtract(points[i + 1], points[i - 1], temp), 0.5, new Cesium.Cartesian3());
     * }
     * tangents[tangents.length - 1] = new Cesium.Cartesian3(1165345, 112641, 47281);
     *
     * var spline = Cesium.HermiteSpline.createC1({
     *     times : times,
     *     points : points,
     *     tangents : tangents
     * });
     */
    HermiteSpline.createC1 = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var times = options.times;
        var points = options.points;
        var tangents = options.tangents;

                if (!defined(points) || !defined(times) || !defined(tangents)) {
            throw new DeveloperError('points, times and tangents are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length || times.length !== tangents.length) {
            throw new DeveloperError('times, points and tangents must have the same length.');
        }
        
        var outTangents = tangents.slice(0, tangents.length - 1);
        var inTangents = tangents.slice(1, tangents.length);

        return new HermiteSpline({
            times : times,
            points : points,
            inTangents : inTangents,
            outTangents : outTangents
        });
    };

    /**
     * Creates a natural cubic spline. The tangents at the control points are generated
     * to create a curve in the class C<sup>2</sup>.
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times The array of control point times.
     * @param {Cartesian3[]} options.points The array of control points.
     * @returns {HermiteSpline|LinearSpline} A hermite spline or a linear spline if less than 3 control points were given.
     *
     * @exception {DeveloperError} points and times are required.
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times.length must be equal to points.length.
     *
     * @example
     * // Create a natural cubic spline above the earth from Philadelphia to Los Angeles.
     * var spline = Cesium.HermiteSpline.createNaturalCubic({
     *     times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
     *     points : [
     *         new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
     *         new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
     *         new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
     *         new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
     *         new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
     *     ]
     * });
     */
    HermiteSpline.createNaturalCubic = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var times = options.times;
        var points = options.points;

                if (!defined(points) || !defined(times)) {
            throw new DeveloperError('points and times are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length) {
            throw new DeveloperError('times.length must be equal to points.length.');
        }
        
        if (points.length < 3) {
            return new LinearSpline({
                points : points,
                times : times
            });
        }

        var tangents = generateNatural(points);
        var outTangents = tangents.slice(0, tangents.length - 1);
        var inTangents = tangents.slice(1, tangents.length);

        return new HermiteSpline({
            times : times,
            points : points,
            inTangents : inTangents,
            outTangents : outTangents
        });
    };

    /**
     * Creates a clamped cubic spline. The tangents at the interior control points are generated
     * to create a curve in the class C<sup>2</sup>.
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times The array of control point times.
     * @param {Cartesian3[]} options.points The array of control points.
     * @param {Cartesian3} options.firstTangent The outgoing tangent of the first control point.
     * @param {Cartesian3} options.lastTangent The incoming tangent of the last control point.
     * @returns {HermiteSpline|LinearSpline} A hermite spline or a linear spline if less than 3 control points were given.
     *
     * @exception {DeveloperError} points, times, firstTangent and lastTangent are required.
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times.length must be equal to points.length.
     *
     * @example
     * // Create a clamped cubic spline above the earth from Philadelphia to Los Angeles.
     * var spline = Cesium.HermiteSpline.createClampedCubic({
     *     times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
     *     points : [
     *         new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
     *         new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
     *         new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
     *         new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
     *         new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
     *     ],
     *     firstTangent : new Cesium.Cartesian3(1125196, -161816, 270551),
     *     lastTangent : new Cesium.Cartesian3(1165345, 112641, 47281)
     * });
     */
    HermiteSpline.createClampedCubic = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var times = options.times;
        var points = options.points;
        var firstTangent = options.firstTangent;
        var lastTangent = options.lastTangent;

                if (!defined(points) || !defined(times) || !defined(firstTangent) || !defined(lastTangent)) {
            throw new DeveloperError('points, times, firstTangent and lastTangent are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length) {
            throw new DeveloperError('times.length must be equal to points.length.');
        }
        
        if (points.length < 3) {
            return new LinearSpline({
                points : points,
                times : times
            });
        }

        var tangents = generateClamped(points, firstTangent, lastTangent);
        var outTangents = tangents.slice(0, tangents.length - 1);
        var inTangents = tangents.slice(1, tangents.length);

        return new HermiteSpline({
            times : times,
            points : points,
            inTangents : inTangents,
            outTangents : outTangents
        });
    };

    HermiteSpline.hermiteCoefficientMatrix = new Matrix4(
             2.0, -3.0,  0.0,  1.0,
            -2.0,  3.0,  0.0,  0.0,
             1.0, -2.0,  1.0,  0.0,
             1.0, -1.0,  0.0,  0.0);

    /**
     * Finds an index <code>i</code> in <code>times</code> such that the parameter
     * <code>time</code> is in the interval <code>[times[i], times[i + 1]]</code>.
     * @function
     *
     * @param {Number} time The time.
     * @returns {Number} The index for the element at the start of the interval.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    HermiteSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;

    var scratchTimeVec = new Cartesian4();
    var scratchTemp = new Cartesian3();

    /**
     * Evaluates the curve at a given time.
     *
     * @param {Number} time The time at which to evaluate the curve.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    HermiteSpline.prototype.evaluate = function(time, result) {
        if (!defined(result)) {
            result = new Cartesian3();
        }
        var points = this.points;
        var times = this.times;
        var inTangents = this.inTangents;
        var outTangents = this.outTangents;

        var i = this._lastTimeIndex = this.findTimeInterval(time, this._lastTimeIndex);
        var u = (time - times[i]) / (times[i + 1] - times[i]);

        var timeVec = scratchTimeVec;
        timeVec.z = u;
        timeVec.y = u * u;
        timeVec.x = timeVec.y * u;
        timeVec.w = 1.0;

        var coefs = Matrix4.multiplyByVector(HermiteSpline.hermiteCoefficientMatrix, timeVec, timeVec);

        result = Cartesian3.multiplyByScalar(points[i], coefs.x, result);
        Cartesian3.multiplyByScalar(points[i + 1], coefs.y, scratchTemp);
        Cartesian3.add(result, scratchTemp, result);
        Cartesian3.multiplyByScalar(outTangents[i], coefs.z, scratchTemp);
        Cartesian3.add(result, scratchTemp, result);
        Cartesian3.multiplyByScalar(inTangents[i], coefs.w, scratchTemp);
        return Cartesian3.add(result, scratchTemp, result);
    };

    return HermiteSpline;
});

/*global define*/
define('Core/CatmullRomSpline',[
        './Cartesian3',
        './Cartesian4',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './HermiteSpline',
        './Matrix4',
        './Spline'
    ], function(
        Cartesian3,
        Cartesian4,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        HermiteSpline,
        Matrix4,
        Spline) {
    'use strict';

    var scratchTimeVec = new Cartesian4();
    var scratchTemp0 = new Cartesian3();
    var scratchTemp1 = new Cartesian3();

    function createEvaluateFunction(spline) {
        var points = spline.points;
        var times = spline.times;

        if (points.length < 3) {
            var t0 = times[0];
            var invSpan = 1.0 / (times[1] - t0);

            var p0 = points[0];
            var p1 = points[1];

            return function(time, result) {
                if (!defined(result)){
                    result = new Cartesian3();
                }
                var u = (time - t0) * invSpan;
                return Cartesian3.lerp(p0, p1, u, result);
            };
        }

        return function(time, result) {
            if (!defined(result)) {
                result = new Cartesian3();
            }
            var i = spline._lastTimeIndex = spline.findTimeInterval(time, spline._lastTimeIndex);
            var u = (time - times[i]) / (times[i + 1] - times[i]);

            var timeVec = scratchTimeVec;
            timeVec.z = u;
            timeVec.y = u * u;
            timeVec.x = timeVec.y * u;
            timeVec.w = 1.0;

            var p0;
            var p1;
            var p2;
            var p3;
            var coefs;

            if (i === 0) {
                p0 = points[0];
                p1 = points[1];
                p2 = spline.firstTangent;

                p3 = Cartesian3.subtract(points[2], p0, scratchTemp0);
                Cartesian3.multiplyByScalar(p3, 0.5, p3);

                coefs = Matrix4.multiplyByVector(HermiteSpline.hermiteCoefficientMatrix, timeVec, timeVec);
            } else if (i === points.length - 2) {
                p0 = points[i];
                p1 = points[i + 1];
                p3 = spline.lastTangent;

                p2 = Cartesian3.subtract(p1, points[i - 1], scratchTemp0);
                Cartesian3.multiplyByScalar(p2, 0.5, p2);

                coefs = Matrix4.multiplyByVector(HermiteSpline.hermiteCoefficientMatrix, timeVec, timeVec);
            } else {
                p0 = points[i - 1];
                p1 = points[i];
                p2 = points[i + 1];
                p3 = points[i + 2];
                coefs = Matrix4.multiplyByVector(CatmullRomSpline.catmullRomCoefficientMatrix, timeVec, timeVec);
            }
            result = Cartesian3.multiplyByScalar(p0, coefs.x, result);
            Cartesian3.multiplyByScalar(p1, coefs.y, scratchTemp1);
            Cartesian3.add(result, scratchTemp1, result);
            Cartesian3.multiplyByScalar(p2, coefs.z, scratchTemp1);
            Cartesian3.add(result, scratchTemp1, result);
            Cartesian3.multiplyByScalar(p3, coefs.w, scratchTemp1);
            return Cartesian3.add(result, scratchTemp1, result);
        };
    }

    var firstTangentScratch = new Cartesian3();
    var lastTangentScratch = new Cartesian3();

    /**
     * A Catmull-Rom spline is a cubic spline where the tangent at control points,
     * except the first and last, are computed using the previous and next control points.
     * Catmull-Rom splines are in the class C<sup>1</sup>.
     *
     * @alias CatmullRomSpline
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
     *                The values are in no way connected to the clock time. They are the parameterization for the curve.
     * @param {Cartesian3[]} options.points The array of {@link Cartesian3} control points.
     * @param {Cartesian3} [options.firstTangent] The tangent of the curve at the first control point.
     *                     If the tangent is not given, it will be estimated.
     * @param {Cartesian3} [options.lastTangent] The tangent of the curve at the last control point.
     *                     If the tangent is not given, it will be estimated.
     *
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times.length must be equal to points.length.
     *
     *
     * @example
     * // spline above the earth from Philadelphia to Los Angeles
     * var spline = new Cesium.CatmullRomSpline({
     *     times : [ 0.0, 1.5, 3.0, 4.5, 6.0 ],
     *     points : [
     *         new Cesium.Cartesian3(1235398.0, -4810983.0, 4146266.0),
     *         new Cesium.Cartesian3(1372574.0, -5345182.0, 4606657.0),
     *         new Cesium.Cartesian3(-757983.0, -5542796.0, 4514323.0),
     *         new Cesium.Cartesian3(-2821260.0, -5248423.0, 4021290.0),
     *         new Cesium.Cartesian3(-2539788.0, -4724797.0, 3620093.0)
     *     ]
     * });
     *
     * var p0 = spline.evaluate(times[i]);         // equal to positions[i]
     * var p1 = spline.evaluate(times[i] + delta); // interpolated value when delta < times[i + 1] - times[i]
     * 
     * @see HermiteSpline
     * @see LinearSpline
     * @see QuaternionSpline
     */
    function CatmullRomSpline(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var points = options.points;
        var times = options.times;
        var firstTangent = options.firstTangent;
        var lastTangent = options.lastTangent;

                if (!defined(points) || !defined(times)) {
            throw new DeveloperError('points and times are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length) {
            throw new DeveloperError('times.length must be equal to points.length.');
        }
        
        if (points.length > 2) {
            if (!defined(firstTangent)) {
                firstTangent = firstTangentScratch;
                Cartesian3.multiplyByScalar(points[1], 2.0, firstTangent);
                Cartesian3.subtract(firstTangent, points[2], firstTangent);
                Cartesian3.subtract(firstTangent, points[0], firstTangent);
                Cartesian3.multiplyByScalar(firstTangent, 0.5, firstTangent);
            }

            if (!defined(lastTangent)) {
                var n = points.length - 1;
                lastTangent = lastTangentScratch;
                Cartesian3.multiplyByScalar(points[n - 1], 2.0, lastTangent);
                Cartesian3.subtract(points[n], lastTangent, lastTangent);
                Cartesian3.add(lastTangent, points[n - 2], lastTangent);
                Cartesian3.multiplyByScalar(lastTangent, 0.5, lastTangent);
            }
        }

        this._times = times;
        this._points = points;
        this._firstTangent = Cartesian3.clone(firstTangent);
        this._lastTangent = Cartesian3.clone(lastTangent);

        this._evaluateFunction = createEvaluateFunction(this);
        this._lastTimeIndex = 0;
    }

    defineProperties(CatmullRomSpline.prototype, {
        /**
         * An array of times for the control points.
         *
         * @memberof CatmullRomSpline.prototype
         *
         * @type {Number[]}
         * @readonly
         */
        times : {
            get : function() {
                return this._times;
            }
        },

        /**
         * An array of {@link Cartesian3} control points.
         *
         * @memberof CatmullRomSpline.prototype
         *
         * @type {Cartesian3[]}
         * @readonly
         */
        points : {
            get : function() {
                return this._points;
            }
        },

        /**
         * The tangent at the first control point.
         *
         * @memberof CatmullRomSpline.prototype
         *
         * @type {Cartesian3}
         * @readonly
         */
        firstTangent : {
            get : function() {
                return this._firstTangent;
            }
        },

        /**
         * The tangent at the last control point.
         *
         * @memberof CatmullRomSpline.prototype
         *
         * @type {Cartesian3}
         * @readonly
         */
        lastTangent : {
            get : function() {
                return this._lastTangent;
            }
        }
    });

    /**
     * @private
     */
    CatmullRomSpline.catmullRomCoefficientMatrix = new Matrix4(
            -0.5,  1.0, -0.5,  0.0,
             1.5, -2.5,  0.0,  1.0,
            -1.5,  2.0,  0.5,  0.0,
             0.5, -0.5,  0.0,  0.0);

    /**
     * Finds an index <code>i</code> in <code>times</code> such that the parameter
     * <code>time</code> is in the interval <code>[times[i], times[i + 1]]</code>.
     * @function
     *
     * @param {Number} time The time.
     * @returns {Number} The index for the element at the start of the interval.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    CatmullRomSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;

    /**
     * Evaluates the curve at a given time.
     *
     * @param {Number} time The time at which to evaluate the curve.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The modified result parameter or a new instance of the point on the curve at the given time.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    CatmullRomSpline.prototype.evaluate = function(time, result) {
        return this._evaluateFunction(time, result);
    };

    return CatmullRomSpline;
});

/*global define*/
define('Core/IndexDatatype',[
        './defined',
        './DeveloperError',
        './freezeObject',
        './Math',
        './WebGLConstants'
    ], function(
        defined,
        DeveloperError,
        freezeObject,
        CesiumMath,
        WebGLConstants) {
    'use strict';

    /**
     * Constants for WebGL index datatypes.  These corresponds to the
     * <code>type</code> parameter of {@link http://www.khronos.org/opengles/sdk/docs/man/xhtml/glDrawElements.xml|drawElements}.
     *
     * @exports IndexDatatype
     */
    var IndexDatatype = {
        /**
         * 8-bit unsigned byte corresponding to <code>UNSIGNED_BYTE</code> and the type
         * of an element in <code>Uint8Array</code>.
         *
         * @type {Number}
         * @constant
         */
        UNSIGNED_BYTE : WebGLConstants.UNSIGNED_BYTE,

        /**
         * 16-bit unsigned short corresponding to <code>UNSIGNED_SHORT</code> and the type
         * of an element in <code>Uint16Array</code>.
         *
         * @type {Number}
         * @constant
         */
        UNSIGNED_SHORT : WebGLConstants.UNSIGNED_SHORT,

        /**
         * 32-bit unsigned int corresponding to <code>UNSIGNED_INT</code> and the type
         * of an element in <code>Uint32Array</code>.
         *
         * @type {Number}
         * @constant
         */
        UNSIGNED_INT : WebGLConstants.UNSIGNED_INT
    };

    /**
     * Returns the size, in bytes, of the corresponding datatype.
     *
     * @param {IndexDatatype} indexDatatype The index datatype to get the size of.
     * @returns {Number} The size in bytes.
     *
     * @example
     * // Returns 2
     * var size = Cesium.IndexDatatype.getSizeInBytes(Cesium.IndexDatatype.UNSIGNED_SHORT);
     */
    IndexDatatype.getSizeInBytes = function(indexDatatype) {
        switch(indexDatatype) {
            case IndexDatatype.UNSIGNED_BYTE:
                return Uint8Array.BYTES_PER_ELEMENT;
            case IndexDatatype.UNSIGNED_SHORT:
                return Uint16Array.BYTES_PER_ELEMENT;
            case IndexDatatype.UNSIGNED_INT:
                return Uint32Array.BYTES_PER_ELEMENT;
        }

                throw new DeveloperError('indexDatatype is required and must be a valid IndexDatatype constant.');
            };

    /**
     * Validates that the provided index datatype is a valid {@link IndexDatatype}.
     *
     * @param {IndexDatatype} indexDatatype The index datatype to validate.
     * @returns {Boolean} <code>true</code> if the provided index datatype is a valid value; otherwise, <code>false</code>.
     *
     * @example
     * if (!Cesium.IndexDatatype.validate(indexDatatype)) {
     *   throw new Cesium.DeveloperError('indexDatatype must be a valid value.');
     * }
     */
    IndexDatatype.validate = function(indexDatatype) {
        return defined(indexDatatype) &&
               (indexDatatype === IndexDatatype.UNSIGNED_BYTE ||
                indexDatatype === IndexDatatype.UNSIGNED_SHORT ||
                indexDatatype === IndexDatatype.UNSIGNED_INT);
    };

    /**
     * Creates a typed array that will store indices, using either <code><Uint16Array</code>
     * or <code>Uint32Array</code> depending on the number of vertices.
     *
     * @param {Number} numberOfVertices Number of vertices that the indices will reference.
     * @param {Any} indicesLengthOrArray Passed through to the typed array constructor.
     * @returns {Uint16Array|Uint32Array} A <code>Uint16Array</code> or <code>Uint32Array</code> constructed with <code>indicesLengthOrArray</code>.
     *
     * @example
     * this.indices = Cesium.IndexDatatype.createTypedArray(positions.length / 3, numberOfIndices);
     */
    IndexDatatype.createTypedArray = function(numberOfVertices, indicesLengthOrArray) {
                if (!defined(numberOfVertices)) {
            throw new DeveloperError('numberOfVertices is required.');
        }
        
        if (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
            return new Uint32Array(indicesLengthOrArray);
        }

        return new Uint16Array(indicesLengthOrArray);
    };

    /**
     * Creates a typed array from a source array buffer.  The resulting typed array will store indices, using either <code><Uint16Array</code>
     * or <code>Uint32Array</code> depending on the number of vertices.
     *
     * @param {Number} numberOfVertices Number of vertices that the indices will reference.
     * @param {ArrayBuffer} sourceArray Passed through to the typed array constructor.
     * @param {Number} byteOffset Passed through to the typed array constructor.
     * @param {Number} length Passed through to the typed array constructor.
     * @returns {Uint16Array|Uint32Array} A <code>Uint16Array</code> or <code>Uint32Array</code> constructed with <code>sourceArray</code>, <code>byteOffset</code>, and <code>length</code>.
     *
     */
    IndexDatatype.createTypedArrayFromArrayBuffer = function(numberOfVertices, sourceArray, byteOffset, length) {
                if (!defined(numberOfVertices)) {
            throw new DeveloperError('numberOfVertices is required.');
        }
        if (!defined(sourceArray)) {
            throw new DeveloperError('sourceArray is required.');
        }
        if (!defined(byteOffset)) {
            throw new DeveloperError('byteOffset is required.');
        }
        
        if (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
            return new Uint32Array(sourceArray, byteOffset, length);
        }

        return new Uint16Array(sourceArray, byteOffset, length);
    };

    return freezeObject(IndexDatatype);
});

/*global define*/
define('Core/loadArrayBuffer',[
        './loadWithXhr'
    ], function(
        loadWithXhr) {
    'use strict';

    /**
     * Asynchronously loads the given URL as raw binary data.  Returns a promise that will resolve to
     * an ArrayBuffer once loaded, or reject if the URL failed to load.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     *
     * @exports loadArrayBuffer
     *
     * @param {String|Promise.<String>} url The URL of the binary data, or a promise for the URL.
     * @param {Object} [headers] HTTP headers to send with the requests.
     * @returns {Promise.<ArrayBuffer>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load a single URL asynchronously
     * Cesium.loadArrayBuffer('some/url').then(function(arrayBuffer) {
     *     // use the data
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadArrayBuffer(url, headers) {
        return loadWithXhr({
            url : url,
            responseType : 'arraybuffer',
            headers : headers
        });
    }

    return loadArrayBuffer;
});

/*global define*/
define('Core/Intersections2D',[
        './Cartesian3',
        './defined',
        './DeveloperError'
    ], function(
        Cartesian3,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Contains functions for operating on 2D triangles.
     *
     * @exports Intersections2D
     */
    var Intersections2D = {};

    /**
     * Splits a 2D triangle at given axis-aligned threshold value and returns the resulting
     * polygon on a given side of the threshold.  The resulting polygon may have 0, 1, 2,
     * 3, or 4 vertices.
     *
     * @param {Number} threshold The threshold coordinate value at which to clip the triangle.
     * @param {Boolean} keepAbove true to keep the portion of the triangle above the threshold, or false
     *                            to keep the portion below.
     * @param {Number} u0 The coordinate of the first vertex in the triangle, in counter-clockwise order.
     * @param {Number} u1 The coordinate of the second vertex in the triangle, in counter-clockwise order.
     * @param {Number} u2 The coordinate of the third vertex in the triangle, in counter-clockwise order.
     * @param {Number[]} [result] The array into which to copy the result.  If this parameter is not supplied,
     *                            a new array is constructed and returned.
     * @returns {Number[]} The polygon that results after the clip, specified as a list of
     *                     vertices.  The vertices are specified in counter-clockwise order.
     *                     Each vertex is either an index from the existing list (identified as
     *                     a 0, 1, or 2) or -1 indicating a new vertex not in the original triangle.
     *                     For new vertices, the -1 is followed by three additional numbers: the
     *                     index of each of the two original vertices forming the line segment that
     *                     the new vertex lies on, and the fraction of the distance from the first
     *                     vertex to the second one.
     *
     * @example
     * var result = Cesium.Intersections2D.clipTriangleAtAxisAlignedThreshold(0.5, false, 0.2, 0.6, 0.4);
     * // result === [2, 0, -1, 1, 0, 0.25, -1, 1, 2, 0.5]
     */
    Intersections2D.clipTriangleAtAxisAlignedThreshold = function(threshold, keepAbove, u0, u1, u2, result) {
                if (!defined(threshold)) {
            throw new DeveloperError('threshold is required.');
        }
        if (!defined(keepAbove)) {
            throw new DeveloperError('keepAbove is required.');
        }
        if (!defined(u0)) {
            throw new DeveloperError('u0 is required.');
        }
        if (!defined(u1)) {
            throw new DeveloperError('u1 is required.');
        }
        if (!defined(u2)) {
            throw new DeveloperError('u2 is required.');
        }
        
        if (!defined(result)) {
            result = [];
        } else {
            result.length = 0;
        }

        var u0Behind;
        var u1Behind;
        var u2Behind;
        if (keepAbove) {
            u0Behind = u0 < threshold;
            u1Behind = u1 < threshold;
            u2Behind = u2 < threshold;
        } else {
            u0Behind = u0 > threshold;
            u1Behind = u1 > threshold;
            u2Behind = u2 > threshold;
        }

        var numBehind = u0Behind + u1Behind + u2Behind;

        var u01Ratio;
        var u02Ratio;
        var u12Ratio;
        var u10Ratio;
        var u20Ratio;
        var u21Ratio;

        if (numBehind === 1) {
            if (u0Behind) {
                u01Ratio = (threshold - u0) / (u1 - u0);
                u02Ratio = (threshold - u0) / (u2 - u0);

                result.push(1);

                result.push(2);

                if (u02Ratio !== 1.0) {
                    result.push(-1);
                    result.push(0);
                    result.push(2);
                    result.push(u02Ratio);
                }

                if (u01Ratio !== 1.0) {
                    result.push(-1);
                    result.push(0);
                    result.push(1);
                    result.push(u01Ratio);
                }
            } else if (u1Behind) {
                u12Ratio = (threshold - u1) / (u2 - u1);
                u10Ratio = (threshold - u1) / (u0 - u1);

                result.push(2);

                result.push(0);

                if (u10Ratio !== 1.0) {
                    result.push(-1);
                    result.push(1);
                    result.push(0);
                    result.push(u10Ratio);
                }

                if (u12Ratio !== 1.0) {
                    result.push(-1);
                    result.push(1);
                    result.push(2);
                    result.push(u12Ratio);
                }
            } else if (u2Behind) {
                u20Ratio = (threshold - u2) / (u0 - u2);
                u21Ratio = (threshold - u2) / (u1 - u2);

                result.push(0);

                result.push(1);

                if (u21Ratio !== 1.0) {
                    result.push(-1);
                    result.push(2);
                    result.push(1);
                    result.push(u21Ratio);
                }

                if (u20Ratio !== 1.0) {
                    result.push(-1);
                    result.push(2);
                    result.push(0);
                    result.push(u20Ratio);
                }
            }
        } else if (numBehind === 2) {
            if (!u0Behind && u0 !== threshold) {
                u10Ratio = (threshold - u1) / (u0 - u1);
                u20Ratio = (threshold - u2) / (u0 - u2);

                result.push(0);

                result.push(-1);
                result.push(1);
                result.push(0);
                result.push(u10Ratio);

                result.push(-1);
                result.push(2);
                result.push(0);
                result.push(u20Ratio);
            } else if (!u1Behind && u1 !== threshold) {
                u21Ratio = (threshold - u2) / (u1 - u2);
                u01Ratio = (threshold - u0) / (u1 - u0);

                result.push(1);

                result.push(-1);
                result.push(2);
                result.push(1);
                result.push(u21Ratio);

                result.push(-1);
                result.push(0);
                result.push(1);
                result.push(u01Ratio);
            } else if (!u2Behind && u2 !== threshold) {
                u02Ratio = (threshold - u0) / (u2 - u0);
                u12Ratio = (threshold - u1) / (u2 - u1);

                result.push(2);

                result.push(-1);
                result.push(0);
                result.push(2);
                result.push(u02Ratio);

                result.push(-1);
                result.push(1);
                result.push(2);
                result.push(u12Ratio);
            }
        } else if (numBehind !== 3) {
            // Completely in front of threshold
            result.push(0);
            result.push(1);
            result.push(2);
        }
        // else Completely behind threshold

        return result;
    };

    /**
     * Compute the barycentric coordinates of a 2D position within a 2D triangle.
     *
     * @param {Number} x The x coordinate of the position for which to find the barycentric coordinates.
     * @param {Number} y The y coordinate of the position for which to find the barycentric coordinates.
     * @param {Number} x1 The x coordinate of the triangle's first vertex.
     * @param {Number} y1 The y coordinate of the triangle's first vertex.
     * @param {Number} x2 The x coordinate of the triangle's second vertex.
     * @param {Number} y2 The y coordinate of the triangle's second vertex.
     * @param {Number} x3 The x coordinate of the triangle's third vertex.
     * @param {Number} y3 The y coordinate of the triangle's third vertex.
     * @param {Cartesian3} [result] The instance into to which to copy the result.  If this parameter
     *                     is undefined, a new instance is created and returned.
     * @returns {Cartesian3} The barycentric coordinates of the position within the triangle.
     *
     * @example
     * var result = Cesium.Intersections2D.computeBarycentricCoordinates(0.0, 0.0, 0.0, 1.0, -1, -0.5, 1, -0.5);
     * // result === new Cesium.Cartesian3(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0);
     */
    Intersections2D.computeBarycentricCoordinates = function(x, y, x1, y1, x2, y2, x3, y3, result) {
                if (!defined(x)) {
            throw new DeveloperError('x is required.');
        }
        if (!defined(y)) {
            throw new DeveloperError('y is required.');
        }
        if (!defined(x1)) {
            throw new DeveloperError('x1 is required.');
        }
        if (!defined(y1)) {
            throw new DeveloperError('y1 is required.');
        }
        if (!defined(x2)) {
            throw new DeveloperError('x2 is required.');
        }
        if (!defined(y2)) {
            throw new DeveloperError('y2 is required.');
        }
        if (!defined(x3)) {
            throw new DeveloperError('x3 is required.');
        }
        if (!defined(y3)) {
            throw new DeveloperError('y3 is required.');
        }
        
        var x1mx3 = x1 - x3;
        var x3mx2 = x3 - x2;
        var y2my3 = y2 - y3;
        var y1my3 = y1 - y3;
        var inverseDeterminant = 1.0 / (y2my3 * x1mx3 + x3mx2 * y1my3);
        var ymy3 = y - y3;
        var xmx3 = x - x3;
        var l1 = (y2my3 * xmx3 + x3mx2 * ymy3) * inverseDeterminant;
        var l2 = (-y1my3 * xmx3 + x1mx3 * ymy3) * inverseDeterminant;
        var l3 = 1.0 - l1 - l2;

        if (defined(result)) {
            result.x = l1;
            result.y = l2;
            result.z = l3;
            return result;
        } else {
            return new Cartesian3(l1, l2, l3);
        }
    };

    return Intersections2D;
});

/*global define*/
define('Core/QuantizedMeshTerrainData',[
        '../ThirdParty/when',
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './IndexDatatype',
        './Intersections2D',
        './Math',
        './OrientedBoundingBox',
        './TaskProcessor',
        './TerrainEncoding',
        './TerrainMesh'
    ], function(
        when,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        IndexDatatype,
        Intersections2D,
        CesiumMath,
        OrientedBoundingBox,
        TaskProcessor,
        TerrainEncoding,
        TerrainMesh) {
    'use strict';

    /**
     * Terrain data for a single tile where the terrain data is represented as a quantized mesh.  A quantized
     * mesh consists of three vertex attributes, longitude, latitude, and height.  All attributes are expressed
     * as 16-bit values in the range 0 to 32767.  Longitude and latitude are zero at the southwest corner
     * of the tile and 32767 at the northeast corner.  Height is zero at the minimum height in the tile
     * and 32767 at the maximum height in the tile.
     *
     * @alias QuantizedMeshTerrainData
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Uint16Array} options.quantizedVertices The buffer containing the quantized mesh.
     * @param {Uint16Array|Uint32Array} options.indices The indices specifying how the quantized vertices are linked
     *                      together into triangles.  Each three indices specifies one triangle.
     * @param {Number} options.minimumHeight The minimum terrain height within the tile, in meters above the ellipsoid.
     * @param {Number} options.maximumHeight The maximum terrain height within the tile, in meters above the ellipsoid.
     * @param {BoundingSphere} options.boundingSphere A sphere bounding all of the vertices in the mesh.
     * @param {OrientedBoundingBox} [options.orientedBoundingBox] An OrientedBoundingBox bounding all of the vertices in the mesh.
     * @param {Cartesian3} options.horizonOcclusionPoint The horizon occlusion point of the mesh.  If this point
     *                      is below the horizon, the entire tile is assumed to be below the horizon as well.
     *                      The point is expressed in ellipsoid-scaled coordinates.
     * @param {Number[]} options.westIndices The indices of the vertices on the western edge of the tile.
     * @param {Number[]} options.southIndices The indices of the vertices on the southern edge of the tile.
     * @param {Number[]} options.eastIndices The indices of the vertices on the eastern edge of the tile.
     * @param {Number[]} options.northIndices The indices of the vertices on the northern edge of the tile.
     * @param {Number} options.westSkirtHeight The height of the skirt to add on the western edge of the tile.
     * @param {Number} options.southSkirtHeight The height of the skirt to add on the southern edge of the tile.
     * @param {Number} options.eastSkirtHeight The height of the skirt to add on the eastern edge of the tile.
     * @param {Number} options.northSkirtHeight The height of the skirt to add on the northern edge of the tile.
     * @param {Number} [options.childTileMask=15] A bit mask indicating which of this tile's four children exist.
     *                 If a child's bit is set, geometry will be requested for that tile as well when it
     *                 is needed.  If the bit is cleared, the child tile is not requested and geometry is
     *                 instead upsampled from the parent.  The bit values are as follows:
     *                 <table>
     *                  <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
     *                  <tr><td>0</td><td>1</td><td>Southwest</td></tr>
     *                  <tr><td>1</td><td>2</td><td>Southeast</td></tr>
     *                  <tr><td>2</td><td>4</td><td>Northwest</td></tr>
     *                  <tr><td>3</td><td>8</td><td>Northeast</td></tr>
     *                 </table>
     * @param {Boolean} [options.createdByUpsampling=false] True if this instance was created by upsampling another instance;
     *                  otherwise, false.
     * @param {Uint8Array} [options.encodedNormals] The buffer containing per vertex normals, encoded using 'oct' encoding
     * @param {Uint8Array} [options.waterMask] The buffer containing the watermask.
     *
     *
     * @example
     * var data = new Cesium.QuantizedMeshTerrainData({
     *     minimumHeight : -100,
     *     maximumHeight : 2101,
     *     quantizedVertices : new Uint16Array([// order is SW NW SE NE
     *                                          // longitude
     *                                          0, 0, 32767, 32767,
     *                                          // latitude
     *                                          0, 32767, 0, 32767,
     *                                          // heights
     *                                          16384, 0, 32767, 16384]),
     *     indices : new Uint16Array([0, 3, 1,
     *                                0, 2, 3]),
     *     boundingSphere : new Cesium.BoundingSphere(new Cesium.Cartesian3(1.0, 2.0, 3.0), 10000),
     *     orientedBoundingBox : new Cesium.OrientedBoundingBox(new Cesium.Cartesian3(1.0, 2.0, 3.0), Cesium.Matrix3.fromRotationX(Cesium.Math.PI, new Cesium.Matrix3())),
     *     horizonOcclusionPoint : new Cesium.Cartesian3(3.0, 2.0, 1.0),
     *     westIndices : [0, 1],
     *     southIndices : [0, 1],
     *     eastIndices : [2, 3],
     *     northIndices : [1, 3],
     *     westSkirtHeight : 1.0,
     *     southSkirtHeight : 1.0,
     *     eastSkirtHeight : 1.0,
     *     northSkirtHeight : 1.0
     * });
     *
     * @see TerrainData
     * @see HeightmapTerrainData
     */
    function QuantizedMeshTerrainData(options) {
                if (!defined(options) || !defined(options.quantizedVertices)) {
            throw new DeveloperError('options.quantizedVertices is required.');
        }
        if (!defined(options.indices)) {
            throw new DeveloperError('options.indices is required.');
        }
        if (!defined(options.minimumHeight)) {
            throw new DeveloperError('options.minimumHeight is required.');
        }
        if (!defined(options.maximumHeight)) {
            throw new DeveloperError('options.maximumHeight is required.');
        }
        if (!defined(options.maximumHeight)) {
            throw new DeveloperError('options.maximumHeight is required.');
        }
        if (!defined(options.boundingSphere)) {
            throw new DeveloperError('options.boundingSphere is required.');
        }
        if (!defined(options.horizonOcclusionPoint)) {
            throw new DeveloperError('options.horizonOcclusionPoint is required.');
        }
        if (!defined(options.westIndices)) {
            throw new DeveloperError('options.westIndices is required.');
        }
        if (!defined(options.southIndices)) {
            throw new DeveloperError('options.southIndices is required.');
        }
        if (!defined(options.eastIndices)) {
            throw new DeveloperError('options.eastIndices is required.');
        }
        if (!defined(options.northIndices)) {
            throw new DeveloperError('options.northIndices is required.');
        }
        if (!defined(options.westSkirtHeight)) {
            throw new DeveloperError('options.westSkirtHeight is required.');
        }
        if (!defined(options.southSkirtHeight)) {
            throw new DeveloperError('options.southSkirtHeight is required.');
        }
        if (!defined(options.eastSkirtHeight)) {
            throw new DeveloperError('options.eastSkirtHeight is required.');
        }
        if (!defined(options.northSkirtHeight)) {
            throw new DeveloperError('options.northSkirtHeight is required.');
        }
        
        this._quantizedVertices = options.quantizedVertices;
        this._encodedNormals = options.encodedNormals;
        this._indices = options.indices;
        this._minimumHeight = options.minimumHeight;
        this._maximumHeight = options.maximumHeight;
        this._boundingSphere = options.boundingSphere;
        this._orientedBoundingBox = options.orientedBoundingBox;
        this._horizonOcclusionPoint = options.horizonOcclusionPoint;

        var vertexCount = this._quantizedVertices.length / 3;
        var uValues = this._uValues = this._quantizedVertices.subarray(0, vertexCount);
        var vValues = this._vValues = this._quantizedVertices.subarray(vertexCount, 2 * vertexCount);
        this._heightValues = this._quantizedVertices.subarray(2 * vertexCount, 3 * vertexCount);

        // We don't assume that we can count on the edge vertices being sorted by u or v.
        function sortByV(a, b) {
            return vValues[a] - vValues[b];
        }

        function sortByU(a, b) {
            return uValues[a] - uValues[b];
        }

        this._westIndices = sortIndicesIfNecessary(options.westIndices, sortByV, vertexCount);
        this._southIndices = sortIndicesIfNecessary(options.southIndices, sortByU, vertexCount);
        this._eastIndices = sortIndicesIfNecessary(options.eastIndices, sortByV, vertexCount);
        this._northIndices = sortIndicesIfNecessary(options.northIndices, sortByU, vertexCount);

        this._westSkirtHeight = options.westSkirtHeight;
        this._southSkirtHeight = options.southSkirtHeight;
        this._eastSkirtHeight = options.eastSkirtHeight;
        this._northSkirtHeight = options.northSkirtHeight;

        this._childTileMask = defaultValue(options.childTileMask, 15);

        this._createdByUpsampling = defaultValue(options.createdByUpsampling, false);
        this._waterMask = options.waterMask;

        this._mesh = undefined;
    }

    defineProperties(QuantizedMeshTerrainData.prototype, {
        /**
         * The water mask included in this terrain data, if any.  A water mask is a rectangular
         * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
         * Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
         * @memberof QuantizedMeshTerrainData.prototype
         * @type {Uint8Array|Image|Canvas}
         */
        waterMask : {
            get : function() {
                return this._waterMask;
            }
        }
    });

    var arrayScratch = [];

    function sortIndicesIfNecessary(indices, sortFunction, vertexCount) {
        arrayScratch.length = indices.length;

        var needsSort = false;
        for (var i = 0, len = indices.length; i < len; ++i) {
            arrayScratch[i] = indices[i];
            needsSort = needsSort || (i > 0 && sortFunction(indices[i - 1], indices[i]) > 0);
        }

        if (needsSort) {
            arrayScratch.sort(sortFunction);
            return IndexDatatype.createTypedArray(vertexCount, arrayScratch);
        } else {
            return indices;
        }
    }

    var createMeshTaskProcessor = new TaskProcessor('createVerticesFromQuantizedTerrainMesh');

    /**
     * Creates a {@link TerrainMesh} from this terrain data.
     *
     * @private
     *
     * @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
     * @param {Number} x The X coordinate of the tile for which to create the terrain data.
     * @param {Number} y The Y coordinate of the tile for which to create the terrain data.
     * @param {Number} level The level of the tile for which to create the terrain data.
     * @param {Number} [exaggeration=1.0] The scale used to exaggerate the terrain.
     * @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
     *          asynchronous mesh creations are already in progress and the operation should
     *          be retried later.
     */
    QuantizedMeshTerrainData.prototype.createMesh = function(tilingScheme, x, y, level, exaggeration) {
                if (!defined(tilingScheme)) {
            throw new DeveloperError('tilingScheme is required.');
        }
        if (!defined(x)) {
            throw new DeveloperError('x is required.');
        }
        if (!defined(y)) {
            throw new DeveloperError('y is required.');
        }
        if (!defined(level)) {
            throw new DeveloperError('level is required.');
        }
        
        var ellipsoid = tilingScheme.ellipsoid;
        var rectangle = tilingScheme.tileXYToRectangle(x, y, level);
        exaggeration = defaultValue(exaggeration, 1.0);

        var verticesPromise = createMeshTaskProcessor.scheduleTask({
            minimumHeight : this._minimumHeight,
            maximumHeight : this._maximumHeight,
            quantizedVertices : this._quantizedVertices,
            octEncodedNormals : this._encodedNormals,
            includeWebMercatorT : true,
            indices : this._indices,
            westIndices : this._westIndices,
            southIndices : this._southIndices,
            eastIndices : this._eastIndices,
            northIndices : this._northIndices,
            westSkirtHeight : this._westSkirtHeight,
            southSkirtHeight : this._southSkirtHeight,
            eastSkirtHeight : this._eastSkirtHeight,
            northSkirtHeight : this._northSkirtHeight,
            rectangle : rectangle,
            relativeToCenter : this._boundingSphere.center,
            ellipsoid : ellipsoid,
            exaggeration : exaggeration
        });

        if (!defined(verticesPromise)) {
            // Postponed
            return undefined;
        }

        var that = this;
        return when(verticesPromise, function(result) {
            var vertexCount = that._quantizedVertices.length / 3;
            vertexCount += that._westIndices.length + that._southIndices.length + that._eastIndices.length + that._northIndices.length;
            var indicesTypedArray = IndexDatatype.createTypedArray(vertexCount, result.indices);

            var vertices = new Float32Array(result.vertices);
            var rtc = result.center;
            var minimumHeight = result.minimumHeight;
            var maximumHeight = result.maximumHeight;
            var boundingSphere = defaultValue(result.boundingSphere, that._boundingSphere);
            var obb = defaultValue(result.orientedBoundingBox, that._orientedBoundingBox);
            var occlusionPoint = that._horizonOcclusionPoint;
            var stride = result.vertexStride;
            var terrainEncoding = TerrainEncoding.clone(result.encoding);

            that._skirtIndex = result.skirtIndex;
            that._vertexCountWithoutSkirts = that._quantizedVertices.length / 3;

            that._mesh = new TerrainMesh(
                    rtc,
                    vertices,
                    indicesTypedArray,
                    minimumHeight,
                    maximumHeight,
                    boundingSphere,
                    occlusionPoint,
                    stride,
                    obb,
                    terrainEncoding,
                    exaggeration);

            // Free memory received from server after mesh is created.
            that._quantizedVertices = undefined;
            that._encodedNormals = undefined;
            that._indices = undefined;

            that._uValues = undefined;
            that._vValues = undefined;
            that._heightValues = undefined;

            that._westIndices = undefined;
            that._southIndices = undefined;
            that._eastIndices = undefined;
            that._northIndices = undefined;

            return that._mesh;
        });
    };

    var upsampleTaskProcessor = new TaskProcessor('upsampleQuantizedTerrainMesh');

    /**
     * Upsamples this terrain data for use by a descendant tile.  The resulting instance will contain a subset of the
     * vertices in this instance, interpolated if necessary.
     *
     * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
     * @param {Number} thisX The X coordinate of this tile in the tiling scheme.
     * @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
     * @param {Number} thisLevel The level of this tile in the tiling scheme.
     * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
     * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
     * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
     * @returns {Promise.<QuantizedMeshTerrainData>|undefined} A promise for upsampled heightmap terrain data for the descendant tile,
     *          or undefined if too many asynchronous upsample operations are in progress and the request has been
     *          deferred.
     */
    QuantizedMeshTerrainData.prototype.upsample = function(tilingScheme, thisX, thisY, thisLevel, descendantX, descendantY, descendantLevel) {
                if (!defined(tilingScheme)) {
            throw new DeveloperError('tilingScheme is required.');
        }
        if (!defined(thisX)) {
            throw new DeveloperError('thisX is required.');
        }
        if (!defined(thisY)) {
            throw new DeveloperError('thisY is required.');
        }
        if (!defined(thisLevel)) {
            throw new DeveloperError('thisLevel is required.');
        }
        if (!defined(descendantX)) {
            throw new DeveloperError('descendantX is required.');
        }
        if (!defined(descendantY)) {
            throw new DeveloperError('descendantY is required.');
        }
        if (!defined(descendantLevel)) {
            throw new DeveloperError('descendantLevel is required.');
        }
        var levelDifference = descendantLevel - thisLevel;
        if (levelDifference > 1) {
            throw new DeveloperError('Upsampling through more than one level at a time is not currently supported.');
        }
        
        var mesh = this._mesh;
        if (!defined(this._mesh)) {
            return undefined;
        }

        var isEastChild = thisX * 2 !== descendantX;
        var isNorthChild = thisY * 2 === descendantY;

        var ellipsoid = tilingScheme.ellipsoid;
        var childRectangle = tilingScheme.tileXYToRectangle(descendantX, descendantY, descendantLevel);

        var upsamplePromise = upsampleTaskProcessor.scheduleTask({
            vertices : mesh.vertices,
            vertexCountWithoutSkirts : this._vertexCountWithoutSkirts,
            indices : mesh.indices,
            skirtIndex : this._skirtIndex,
            encoding : mesh.encoding,
            minimumHeight : this._minimumHeight,
            maximumHeight : this._maximumHeight,
            isEastChild : isEastChild,
            isNorthChild : isNorthChild,
            childRectangle : childRectangle,
            ellipsoid : ellipsoid,
            exaggeration : mesh.exaggeration
        });

        if (!defined(upsamplePromise)) {
            // Postponed
            return undefined;
        }

        var shortestSkirt = Math.min(this._westSkirtHeight, this._eastSkirtHeight);
        shortestSkirt = Math.min(shortestSkirt, this._southSkirtHeight);
        shortestSkirt = Math.min(shortestSkirt, this._northSkirtHeight);

        var westSkirtHeight = isEastChild ? (shortestSkirt * 0.5) : this._westSkirtHeight;
        var southSkirtHeight = isNorthChild ? (shortestSkirt * 0.5) : this._southSkirtHeight;
        var eastSkirtHeight = isEastChild ? this._eastSkirtHeight : (shortestSkirt * 0.5);
        var northSkirtHeight = isNorthChild ? this._northSkirtHeight : (shortestSkirt * 0.5);

        return when(upsamplePromise, function(result) {
            var quantizedVertices = new Uint16Array(result.vertices);
            var indicesTypedArray = IndexDatatype.createTypedArray(quantizedVertices.length / 3, result.indices);
            var encodedNormals;
            if (defined(result.encodedNormals)) {
                encodedNormals = new Uint8Array(result.encodedNormals);
            }

            return new QuantizedMeshTerrainData({
                quantizedVertices : quantizedVertices,
                indices : indicesTypedArray,
                encodedNormals : encodedNormals,
                minimumHeight : result.minimumHeight,
                maximumHeight : result.maximumHeight,
                boundingSphere : BoundingSphere.clone(result.boundingSphere),
                orientedBoundingBox : OrientedBoundingBox.clone(result.orientedBoundingBox),
                horizonOcclusionPoint : Cartesian3.clone(result.horizonOcclusionPoint),
                westIndices : result.westIndices,
                southIndices : result.southIndices,
                eastIndices : result.eastIndices,
                northIndices : result.northIndices,
                westSkirtHeight : westSkirtHeight,
                southSkirtHeight : southSkirtHeight,
                eastSkirtHeight : eastSkirtHeight,
                northSkirtHeight : northSkirtHeight,
                childTileMask : 0,
                createdByUpsampling : true
            });
        });
    };

    var maxShort = 32767;
    var barycentricCoordinateScratch = new Cartesian3();

    /**
     * Computes the terrain height at a specified longitude and latitude.
     *
     * @param {Rectangle} rectangle The rectangle covered by this terrain data.
     * @param {Number} longitude The longitude in radians.
     * @param {Number} latitude The latitude in radians.
     * @returns {Number} The terrain height at the specified position.  The position is clamped to
     *          the rectangle, so expect incorrect results for positions far outside the rectangle.
     */
    QuantizedMeshTerrainData.prototype.interpolateHeight = function(rectangle, longitude, latitude) {
        var u = CesiumMath.clamp((longitude - rectangle.west) / rectangle.width, 0.0, 1.0);
        u *= maxShort;
        var v = CesiumMath.clamp((latitude - rectangle.south) / rectangle.height, 0.0, 1.0);
        v *= maxShort;

        if (!defined(this._mesh)) {
            return interpolateHeight(this, u, v);
        }

        interpolateMeshHeight(this, u, v);
    };

    var texCoordScratch0 = new Cartesian2();
    var texCoordScratch1 = new Cartesian2();
    var texCoordScratch2 = new Cartesian2();

    function interpolateMeshHeight(terrainData, u, v) {
        var mesh = terrainData._mesh;
        var vertices = mesh.vertices;
        var encoding = mesh.encoding;
        var indices = mesh.indices;

        for (var i = 0, len = indices.length; i < len; i += 3) {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var uv0 = encoding.decodeTextureCoordinates(vertices, i0, texCoordScratch0);
            var uv1 = encoding.decodeTextureCoordinates(vertices, i1, texCoordScratch1);
            var uv2 = encoding.decodeTextureCoordinates(vertices, i2, texCoordScratch2);

            var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, uv0.x, uv0.y, uv1.x, uv1.y, uv2.x, uv2.y, barycentricCoordinateScratch);
            if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) {
                var h0 = encoding.decodeHeight(vertices, i0);
                var h1 = encoding.decodeHeight(vertices, i1);
                var h2 = encoding.decodeHeight(vertices, i2);
                return barycentric.x * h0 + barycentric.y * h1 + barycentric.z * h2;
            }
        }

        // Position does not lie in any triangle in this mesh.
        return undefined;
    }

    function interpolateHeight(terrainData, u, v) {
        var uBuffer = terrainData._uValues;
        var vBuffer = terrainData._vValues;
        var heightBuffer = terrainData._heightValues;

        var indices = terrainData._indices;
        for (var i = 0, len = indices.length; i < len; i += 3) {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var u0 = uBuffer[i0];
            var u1 = uBuffer[i1];
            var u2 = uBuffer[i2];

            var v0 = vBuffer[i0];
            var v1 = vBuffer[i1];
            var v2 = vBuffer[i2];

            var barycentric = Intersections2D.computeBarycentricCoordinates(u, v, u0, v0, u1, v1, u2, v2, barycentricCoordinateScratch);
            if (barycentric.x >= -1e-15 && barycentric.y >= -1e-15 && barycentric.z >= -1e-15) {
                var quantizedHeight = barycentric.x * heightBuffer[i0] +
                                      barycentric.y * heightBuffer[i1] +
                                      barycentric.z * heightBuffer[i2];
                return CesiumMath.lerp(terrainData._minimumHeight, terrainData._maximumHeight, quantizedHeight / maxShort);
            }
        }

        // Position does not lie in any triangle in this mesh.
        return undefined;
    }

    /**
     * Determines if a given child tile is available, based on the
     * {@link HeightmapTerrainData.childTileMask}.  The given child tile coordinates are assumed
     * to be one of the four children of this tile.  If non-child tile coordinates are
     * given, the availability of the southeast child tile is returned.
     *
     * @param {Number} thisX The tile X coordinate of this (the parent) tile.
     * @param {Number} thisY The tile Y coordinate of this (the parent) tile.
     * @param {Number} childX The tile X coordinate of the child tile to check for availability.
     * @param {Number} childY The tile Y coordinate of the child tile to check for availability.
     * @returns {Boolean} True if the child tile is available; otherwise, false.
     */
    QuantizedMeshTerrainData.prototype.isChildAvailable = function(thisX, thisY, childX, childY) {
                if (!defined(thisX)) {
            throw new DeveloperError('thisX is required.');
        }
        if (!defined(thisY)) {
            throw new DeveloperError('thisY is required.');
        }
        if (!defined(childX)) {
            throw new DeveloperError('childX is required.');
        }
        if (!defined(childY)) {
            throw new DeveloperError('childY is required.');
        }
        
        var bitNumber = 2; // northwest child
        if (childX !== thisX * 2) {
            ++bitNumber; // east child
        }
        if (childY !== thisY * 2) {
            bitNumber -= 2; // south child
        }

        return (this._childTileMask & (1 << bitNumber)) !== 0;
    };

    /**
     * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
     * terrain data.  If this value is false, the data was obtained from some other source, such
     * as by downloading it from a remote server.  This method should return true for instances
     * returned from a call to {@link HeightmapTerrainData#upsample}.
     *
     * @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
     */
    QuantizedMeshTerrainData.prototype.wasCreatedByUpsampling = function() {
        return this._createdByUpsampling;
    };

    return QuantizedMeshTerrainData;
});

/*global define*/
define('Core/TileAvailability',[
    './binarySearch',
    './Cartographic',
    './defined',
    './defineProperties',
    './DeveloperError',
    './Rectangle'
], function(
    binarySearch,
    Cartographic,
    defined,
    defineProperties,
    DeveloperError,
    Rectangle) {
    "use strict";

    /**
     * Reports the availability of tiles in a {@link TilingScheme}.
     *
     * @alias TileAvailability
     * @constructor
     *
     * @param {TilingScheme} tilingScheme The tiling scheme in which to report availability.
     * @param {Number} maximumLevel The maximum tile level that is potentially available.
     */
    function TileAvailability(tilingScheme, maximumLevel) {
        this._tilingScheme = tilingScheme;
        this._maximumLevel = maximumLevel;

        this._rootNodes = [];
        for (var y = 0; y < tilingScheme.getNumberOfYTilesAtLevel(); ++y) {
            for (var x = 0; x < tilingScheme.getNumberOfXTilesAtLevel(); ++x) {
                this._rootNodes.push(new QuadtreeNode(tilingScheme, undefined, 0, x, y));
            }
        }
    }

    var rectangleScratch = new Rectangle();

    /**
     * Marks a rectangular range of tiles in a particular level as being available.  For best performance,
     * add your ranges in order of increasing level.
     *
     * @param {Number} level The level.
     * @param {Number} startX The X coordinate of the first available tiles at the level.
     * @param {Number} startY The Y coordinate of the first available tiles at the level.
     * @param {Number} endX The X coordinate of the last available tiles at the level.
     * @param {Number} endY The Y coordinate of the last available tiles at the level.
     */
    TileAvailability.prototype.addAvailableTileRange = function(level, startX, startY, endX, endY) {
        var tilingScheme = this._tilingScheme;

        tilingScheme.tileXYToRectangle(startX, startY, level, rectangleScratch);
        var west = rectangleScratch.west;
        var north = rectangleScratch.north;

        tilingScheme.tileXYToRectangle(endX, endY, level, rectangleScratch);
        var east = rectangleScratch.east;
        var south = rectangleScratch.south;

        var rectangleWithLevel = new RectangleWithLevel(level, west, south, east, north);

        for (var i = 0; i < this._rootNodes.length; ++i) {
            var rootNode = this._rootNodes[i];
            if (rectanglesOverlap(rootNode.extent, rectangleWithLevel)) {
                putRectangleInQuadtree(this._maximumLevel, rootNode, rectangleWithLevel);
            }
        }
    };

    /**
     * Determines the level of the most detailed tile covering the position.  This function
     * usually completes in time logarithmic to the number of rectangles added with
     * {@link TileAvailability#addAvailableTileRange}.
     *
     * @param {Cartographic} position The position for which to determine the maximum available level.  The height component is ignored.
     * @return {Number} The level of the most detailed tile covering the position.
     * @throws {DeveloperError} If position is outside any tile according to the tiling scheme.
     */
    TileAvailability.prototype.computeMaximumLevelAtPosition = function(position) {
        // Find the root node that contains this position.
        var node;
        for (var nodeIndex = 0; nodeIndex < this._rootNodes.length; ++nodeIndex) {
            var rootNode = this._rootNodes[nodeIndex];
            if (rectangleContainsPosition(rootNode.extent, position)) {
                node = rootNode;
                break;
            }
        }

                if (!defined(node)) {
            throw new DeveloperError('The specified position does not exist in any root node of the tiling scheme.');
        }
        
        return findMaxLevelFromNode(undefined, node, position);
    };

    var rectanglesScratch = [];
    var remainingToCoverByLevelScratch = [];
    var westScratch = new Rectangle();
    var eastScratch = new Rectangle();

    /**
     * Finds the most detailed level that is available _everywhere_ within a given rectangle.  More detailed
     * tiles may be available in parts of the rectangle, but not the whole thing.  The return value of this
     * function may be safely passed to {@link sampleTerrain} for any position within the rectangle.  This function
     * usually completes in time logarithmic to the number of rectangles added with
     * {@link TileAvailability#addAvailableTileRange}.
     *
     * @param {Rectangle} rectangle The rectangle.
     * @return {Number} The best available level for the entire rectangle.
     */
    TileAvailability.prototype.computeBestAvailableLevelOverRectangle = function(rectangle) {
        var rectangles = rectanglesScratch;
        rectangles.length = 0;

        if (rectangle.east < rectangle.west) {
            // Rectangle crosses the IDL, make it two rectangles.
            rectangles.push(Rectangle.fromRadians(-Math.PI, rectangle.south, rectangle.east, rectangle.north, westScratch));
            rectangles.push(Rectangle.fromRadians(rectangle.west, rectangle.south, Math.PI, rectangle.north, eastScratch));
        } else {
            rectangles.push(rectangle);
        }

        var remainingToCoverByLevel = remainingToCoverByLevelScratch;
        remainingToCoverByLevel.length = 0;

        var i;
        for (i = 0; i < this._rootNodes.length; ++i) {
            updateCoverageWithNode(remainingToCoverByLevel, this._rootNodes[i], rectangles);
        }

        for (i = remainingToCoverByLevel.length - 1; i >= 0; --i) {
            if (defined(remainingToCoverByLevel[i]) && remainingToCoverByLevel[i].length === 0) {
                return i;
            }
        }

        return 0;
    };

    var cartographicScratch = new Cartographic();

    /**
     * Determines if a particular tile is available.
     * @param {Number} level The tile level to check.
     * @param {Number} x The X coordinate of the tile to check.
     * @param {Number} y The Y coordinate of the tile to check.
     * @return {Boolean} True if the tile is available; otherwise, false.
     */
    TileAvailability.prototype.isTileAvailable = function(level, x, y) {
        // Get the center of the tile and find the maximum level at that position.
        // Because availability is by tile, if the level is available at that point, it
        // is sure to be available for the whole tile.  We assume that if a tile at level n exists,
        // then all its parent tiles back to level 0 exist too.  This isn't really enforced
        // anywhere, but Cesium would never load a tile for which this is not true.
        var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch);
        Rectangle.center(rectangle, cartographicScratch);
        return this.computeMaximumLevelAtPosition(cartographicScratch) >= level;
    };

    /**
     * Computes a bit mask indicating which of a tile's four children exist.
     * If a child's bit is set, a tile is available for that child.  If it is cleared,
     * the tile is not available.  The bit values are as follows:
     * <table>
     *     <tr><th>Bit Position</th><th>Bit Value</th><th>Child Tile</th></tr>
     *     <tr><td>0</td><td>1</td><td>Southwest</td></tr>
     *     <tr><td>1</td><td>2</td><td>Southeast</td></tr>
     *     <tr><td>2</td><td>4</td><td>Northwest</td></tr>
     *     <tr><td>3</td><td>8</td><td>Northeast</td></tr>
     * </table>
     *
     * @param {Number} level The level of the parent tile.
     * @param {Number} x The X coordinate of the parent tile.
     * @param {Number} y The Y coordinate of the parent tile.
     * @return {Number} The bit mask indicating child availability.
     */
    TileAvailability.prototype.computeChildMaskForTile = function(level, x, y) {
        var childLevel = level + 1;
        if (childLevel >= this._maximumLevel) {
            return 0;
        }

        var mask = 0;

        mask |= this.isTileAvailable(childLevel, 2 * x, 2 * y + 1) ? 1 : 0;
        mask |= this.isTileAvailable(childLevel, 2 * x + 1, 2 * y + 1) ? 2 : 0;
        mask |= this.isTileAvailable(childLevel, 2 * x, 2 * y) ? 4 : 0;
        mask |= this.isTileAvailable(childLevel, 2 * x + 1, 2 * y) ? 8 : 0;

        return mask;
    };

    function QuadtreeNode(tilingScheme, parent, level, x, y) {
        this.tilingScheme = tilingScheme;
        this.parent = parent;
        this.level = level;
        this.x = x;
        this.y = y;
        this.extent = tilingScheme.tileXYToRectangle(x, y, level);

        this.rectangles = [];
        this._sw = undefined;
        this._se = undefined;
        this._nw = undefined;
        this._ne = undefined;
    }

    defineProperties(QuadtreeNode.prototype, {
        nw: {
            get: function() {
                if (!this._nw) {
                    this._nw = new QuadtreeNode(this.tilingScheme, this, this.level + 1, this.x * 2, this.y * 2);
                }
                return this._nw;
            }
        },

        ne: {
            get: function() {
                if (!this._ne) {
                    this._ne = new QuadtreeNode(this.tilingScheme, this, this.level + 1, this.x * 2 + 1, this.y * 2);
                }
                return this._ne;
            }
        },

        sw: {
            get: function() {
                if (!this._sw) {
                    this._sw = new QuadtreeNode(this.tilingScheme, this, this.level + 1, this.x * 2, this.y * 2 + 1);
                }
                return this._sw;
            }
        },

        se: {
            get: function() {
                if (!this._se) {
                    this._se = new QuadtreeNode(this.tilingScheme, this, this.level + 1, this.x * 2 + 1, this.y * 2 + 1);
                }
                return this._se;
            }
        }
    });

    function RectangleWithLevel(level, west, south, east, north) {
        this.level = level;
        this.west = west;
        this.south = south;
        this.east = east;
        this.north = north;
    }

    function rectanglesOverlap(rectangle1, rectangle2) {
        var west = Math.max(rectangle1.west, rectangle2.west);
        var south = Math.max(rectangle1.south, rectangle2.south);
        var east = Math.min(rectangle1.east, rectangle2.east);
        var north = Math.min(rectangle1.north, rectangle2.north);
        return south < north && west < east;
    }

    function putRectangleInQuadtree(maxDepth, node, rectangle) {
        while (node.level < maxDepth) {
            if (rectangleFullyContainsRectangle(node.nw.extent, rectangle)) {
                node = node.nw;
            } else if (rectangleFullyContainsRectangle(node.ne.extent, rectangle)) {
                node = node.ne;
            } else if (rectangleFullyContainsRectangle(node.sw.extent, rectangle)) {
                node = node.sw;
            } else if (rectangleFullyContainsRectangle(node.se.extent, rectangle)) {
                node = node.se;
            } else {
                break;
            }
        }

        if (node.rectangles.length === 0 || node.rectangles[node.rectangles.length - 1].level <= rectangle.level) {
            node.rectangles.push(rectangle);
        } else {
            // Maintain ordering by level when inserting.
            var index = binarySearch(node.rectangles, rectangle.level, rectangleLevelComparator);
            if (index <= 0) {
                index = ~index;
            }
            node.rectangles.splice(index, 0, rectangle);
        }
    }

    function rectangleLevelComparator(a, b) {
        return a.level - b;
    }

    function rectangleFullyContainsRectangle(potentialContainer, rectangleToTest) {
        return rectangleToTest.west >= potentialContainer.west &&
               rectangleToTest.east <= potentialContainer.east &&
               rectangleToTest.south >= potentialContainer.south &&
               rectangleToTest.north <= potentialContainer.north;
    }

    function rectangleContainsPosition(potentialContainer, positionToTest) {
        return positionToTest.longitude >= potentialContainer.west &&
               positionToTest.longitude <= potentialContainer.east &&
               positionToTest.latitude >= potentialContainer.south &&
               positionToTest.latitude <= potentialContainer.north;
    }

    function findMaxLevelFromNode(stopNode, node, position) {
        var maxLevel = 0;

        // Find the deepest quadtree node containing this point.
        while (true) {
            var nw = node._nw && rectangleContainsPosition(node._nw.extent, position);
            var ne = node._ne && rectangleContainsPosition(node._ne.extent, position);
            var sw = node._sw && rectangleContainsPosition(node._sw.extent, position);
            var se = node._se && rectangleContainsPosition(node._se.extent, position);

            // The common scenario is that the point is in only one quadrant and we can simply
            // iterate down the tree.  But if the point is on a boundary between tiles, it is
            // in multiple tiles and we need to check all of them, so use recursion.
            if (nw + ne + sw + se > 1) {
                if (nw) {
                    maxLevel = Math.max(maxLevel, findMaxLevelFromNode(node, node._nw, position));
                }
                if (ne) {
                    maxLevel = Math.max(maxLevel, findMaxLevelFromNode(node, node._ne, position));
                }
                if (sw) {
                    maxLevel = Math.max(maxLevel, findMaxLevelFromNode(node, node._sw, position));
                }
                if (se) {
                    maxLevel = Math.max(maxLevel, findMaxLevelFromNode(node, node._se, position));
                }
                break;
            } else if (nw) {
                node = node._nw;
            } else if (ne) {
                node = node._ne;
            } else if (sw) {
                node = node._sw;
            } else if (se) {
                node = node._se;
            } else {
                break;
            }
        }

        // Work up the tree until we find a rectangle that contains this point.
        while (node !== stopNode) {
            var rectangles = node.rectangles;

            // Rectangles are sorted by level, lowest first.
            for (var i = rectangles.length - 1; i >= 0 && rectangles[i].level > maxLevel; --i) {
                var rectangle = rectangles[i];
                if (rectangleContainsPosition(rectangle, position)) {
                    maxLevel = rectangle.level;
                }
            }

            node = node.parent;
        }

        return maxLevel;
    }

    function updateCoverageWithNode(remainingToCoverByLevel, node, rectanglesToCover) {
        if (!node) {
            return;
        }

        var i;
        var anyOverlap = false;
        for (i = 0; i < rectanglesToCover.length; ++i) {
            anyOverlap = anyOverlap || rectanglesOverlap(node.extent, rectanglesToCover[i]);
        }

        if (!anyOverlap) {
            // This node is not applicable to the rectangle(s).
            return;
        }

        var rectangles = node.rectangles;
        for (i = 0; i < rectangles.length; ++i) {
            var rectangle = rectangles[i];

            if (!remainingToCoverByLevel[rectangle.level]) {
                remainingToCoverByLevel[rectangle.level] = rectanglesToCover;
            }

            remainingToCoverByLevel[rectangle.level] = subtractRectangle(remainingToCoverByLevel[rectangle.level], rectangle);
        }

        // Update with child nodes.
        updateCoverageWithNode(remainingToCoverByLevel, node._nw, rectanglesToCover);
        updateCoverageWithNode(remainingToCoverByLevel, node._ne, rectanglesToCover);
        updateCoverageWithNode(remainingToCoverByLevel, node._sw, rectanglesToCover);
        updateCoverageWithNode(remainingToCoverByLevel, node._se, rectanglesToCover);
    }

    function subtractRectangle(rectangleList, rectangleToSubtract) {
        var result = [];
        for (var i = 0; i < rectangleList.length; ++i) {
            var rectangle = rectangleList[i];
            if (!rectanglesOverlap(rectangle, rectangleToSubtract)) {
                // Disjoint rectangles.  Original rectangle is unmodified.
                result.push(rectangle);
            } else {
                // rectangleToSubtract partially or completely overlaps rectangle.
                if (rectangle.west < rectangleToSubtract.west) {
                    result.push(new Rectangle(rectangle.west, rectangle.south, rectangleToSubtract.west, rectangle.north));
                }
                if (rectangle.east > rectangleToSubtract.east) {
                    result.push(new Rectangle(rectangleToSubtract.east, rectangle.south, rectangle.east, rectangle.north));
                }
                if (rectangle.south < rectangleToSubtract.south) {
                    result.push(new Rectangle(Math.max(rectangleToSubtract.west, rectangle.west), rectangle.south, Math.min(rectangleToSubtract.east, rectangle.east), rectangleToSubtract.south));
                }
                if (rectangle.north > rectangleToSubtract.north) {
                    result.push(new Rectangle(Math.max(rectangleToSubtract.west, rectangle.west), rectangleToSubtract.north, Math.min(rectangleToSubtract.east, rectangle.east), rectangle.north));
                }
            }
        }

        return result;
    }

    return TileAvailability;
});

/*global define*/
define('Core/formatError',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * Formats an error object into a String.  If available, uses name, message, and stack
     * properties, otherwise, falls back on toString().
     *
     * @exports formatError
     *
     * @param {Object} object The item to find in the array.
     * @returns {String} A string containing the formatted error.
     */
    function formatError(object) {
        var result;

        var name = object.name;
        var message = object.message;
        if (defined(name) && defined(message)) {
            result = name + ': ' + message;
        } else {
            result = object.toString();
        }

        var stack = object.stack;
        if (defined(stack)) {
            result += '\n' + stack;
        }

        return result;
    }

    return formatError;
});

/*global define*/
define('Core/TileProviderError',[
        './defaultValue',
        './defined',
        './formatError'
    ], function(
        defaultValue,
        defined,
        formatError) {
    'use strict';

    /**
     * Provides details about an error that occurred in an {@link ImageryProvider} or a {@link TerrainProvider}.
     *
     * @alias TileProviderError
     * @constructor
     *
     * @param {ImageryProvider|TerrainProvider} provider The imagery or terrain provider that experienced the error.
     * @param {String} message A message describing the error.
     * @param {Number} [x] The X coordinate of the tile that experienced the error, or undefined if the error
     *        is not specific to a particular tile.
     * @param {Number} [y] The Y coordinate of the tile that experienced the error, or undefined if the error
     *        is not specific to a particular tile.
     * @param {Number} [level] The level of the tile that experienced the error, or undefined if the error
     *        is not specific to a particular tile.
     * @param {Number} [timesRetried=0] The number of times this operation has been retried.
     * @param {Error} [error] The error or exception that occurred, if any.
     */
    function TileProviderError(provider, message, x, y, level, timesRetried, error) {
        /**
         * The {@link ImageryProvider} or {@link TerrainProvider} that experienced the error.
         * @type {ImageryProvider|TerrainProvider}
         */
        this.provider = provider;

        /**
         * The message describing the error.
         * @type {String}
         */
        this.message = message;

        /**
         * The X coordinate of the tile that experienced the error.  If the error is not specific
         * to a particular tile, this property will be undefined.
         * @type {Number}
         */
        this.x = x;

        /**
         * The Y coordinate of the tile that experienced the error.  If the error is not specific
         * to a particular tile, this property will be undefined.
         * @type {Number}
         */
        this.y = y;

        /**
         * The level-of-detail of the tile that experienced the error.  If the error is not specific
         * to a particular tile, this property will be undefined.
         * @type {Number}
         */
        this.level = level;

        /**
         * The number of times this operation has been retried.
         * @type {Number}
         * @default 0
         */
        this.timesRetried = defaultValue(timesRetried, 0);

        /**
         * True if the failed operation should be retried; otherwise, false.  The imagery or terrain provider
         * will set the initial value of this property before raising the event, but any listeners
         * can change it.  The value after the last listener is invoked will be acted upon.
         * @type {Boolean}
         * @default false
         */
        this.retry = false;

        /**
         * The error or exception that occurred, if any.
         * @type {Error}
         */
        this.error = error;
    }

    /**
     * Handles an error in an {@link ImageryProvider} or {@link TerrainProvider} by raising an event if it has any listeners, or by
     * logging the error to the console if the event has no listeners.  This method also tracks the number
     * of times the operation has been retried and will automatically retry if requested to do so by the
     * event listeners.
     *
     * @param {TileProviderError} previousError The error instance returned by this function the last
     *        time it was called for this error, or undefined if this is the first time this error has
     *        occurred.
     * @param {ImageryProvider|TerrainProvider} provider The imagery or terrain provider that encountered the error.
     * @param {Event} event The event to raise to inform listeners of the error.
     * @param {String} message The message describing the error.
     * @param {Number} x The X coordinate of the tile that experienced the error, or undefined if the
     *        error is not specific to a particular tile.
     * @param {Number} y The Y coordinate of the tile that experienced the error, or undefined if the
     *        error is not specific to a particular tile.
     * @param {Number} level The level-of-detail of the tile that experienced the error, or undefined if the
     *        error is not specific to a particular tile.
     * @param {TileProviderError~RetryFunction} retryFunction The function to call to retry the operation.  If undefined, the
     *        operation will not be retried.
     * @param {Error} [errorDetails] The error or exception that occurred, if any.
     * @returns {TileProviderError} The error instance that was passed to the event listeners and that
     *          should be passed to this function the next time it is called for the same error in order
     *          to track retry counts.
     */
    TileProviderError.handleError = function(previousError, provider, event, message, x, y, level, retryFunction, errorDetails) {
        var error = previousError;
        if (!defined(previousError)) {
            error = new TileProviderError(provider, message, x, y, level, 0, errorDetails);
        } else {
            error.provider = provider;
            error.message = message;
            error.x = x;
            error.y = y;
            error.level = level;
            error.retry = false;
            error.error = errorDetails;
            ++error.timesRetried;
        }

        if (event.numberOfListeners > 0) {
            event.raiseEvent(error);
        } else {
            console.log('An error occurred in "' + provider.constructor.name + '": ' + formatError(message));
        }

        if (error.retry && defined(retryFunction)) {
            retryFunction();
        }

        return error;
    };

    /**
     * Handles success of an operation by resetting the retry count of a previous error, if any.  This way,
     * if the error occurs again in the future, the listeners will be informed that it has not yet been retried.
     *
     * @param {TileProviderError} previousError The previous error, or undefined if this operation has
     *        not previously resulted in an error.
     */
    TileProviderError.handleSuccess = function(previousError) {
        if (defined(previousError)) {
            previousError.timesRetried = -1;
        }
    };

    /**
     * A function that will be called to retry the operation.
     * @callback TileProviderError~RetryFunction
     */

    return TileProviderError;
});

/*global define*/
define('Core/CesiumTerrainProvider',[
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        './BoundingSphere',
        './Cartesian3',
        './Credit',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Event',
        './GeographicTilingScheme',
        './HeightmapTerrainData',
        './IndexDatatype',
        './joinUrls',
        './loadArrayBuffer',
        './loadJson',
        './Math',
        './OrientedBoundingBox',
        './QuantizedMeshTerrainData',
        './TerrainProvider',
        './throttleRequestByServer',
        './TileAvailability',
        './TileProviderError'
    ], function(
        Uri,
        when,
        BoundingSphere,
        Cartesian3,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        GeographicTilingScheme,
        HeightmapTerrainData,
        IndexDatatype,
        joinUrls,
        loadArrayBuffer,
        loadJson,
        CesiumMath,
        OrientedBoundingBox,
        QuantizedMeshTerrainData,
        TerrainProvider,
        throttleRequestByServer,
        TileAvailability,
        TileProviderError) {
    'use strict';

    /**
     * A {@link TerrainProvider} that access terrain data in a Cesium terrain format.
     * The format is described on the
     * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Cesium-Terrain-Server|Cesium wiki}.
     *
     * @alias CesiumTerrainProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The URL of the Cesium terrain server.
     * @param {Proxy} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
     * @param {Boolean} [options.requestVertexNormals=false] Flag that indicates if the client should request additional lighting information from the server, in the form of per vertex normals if available.
     * @param {Boolean} [options.requestWaterMask=false] Flag that indicates if the client should request per tile water masks from the server,  if available.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
     *
     *
     * @example
     * // Construct a terrain provider that uses per vertex normals for lighting
     * // to add shading detail to an imagery provider.
     * var terrainProvider = new Cesium.CesiumTerrainProvider({
     *     url : 'https://assets.agi.com/stk-terrain/world',
     *     requestVertexNormals : true
     * });
     *
     * // Terrain geometry near the surface of the globe is difficult to view when using NaturalEarthII imagery,
     * // unless the TerrainProvider provides additional lighting information to shade the terrain (as shown above).
     * var imageryProvider = Cesium.createTileMapServiceImageryProvider({
     *        url : 'http://localhost:8080/Source/Assets/Textures/NaturalEarthII',
     *        fileExtension : 'jpg'
     *    });
     *
     * var viewer = new Cesium.Viewer('cesiumContainer', {
     *     imageryProvider : imageryProvider,
     *     baseLayerPicker : false,
     *     terrainProvider : terrainProvider
     * });
     *
     * // The globe must enable lighting to make use of the terrain's vertex normals
     * viewer.scene.globe.enableLighting = true;
     *
     * @see TerrainProvider
     */
    function CesiumTerrainProvider(options) {
                if (!defined(options) || !defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        this._url = options.url;
        this._proxy = options.proxy;

        this._tilingScheme = new GeographicTilingScheme({
            numberOfLevelZeroTilesX : 2,
            numberOfLevelZeroTilesY : 1,
            ellipsoid : options.ellipsoid
        });

        this._heightmapWidth = 65;
        this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, this._heightmapWidth, this._tilingScheme.getNumberOfXTilesAtLevel(0));

        this._heightmapStructure = undefined;
        this._hasWaterMask = false;

        /**
         * Boolean flag that indicates if the Terrain Server can provide vertex normals.
         * @type {Boolean}
         * @default false
         * @private
         */
        this._hasVertexNormals = false;
        /**
         * Boolean flag that indicates if the client should request vertex normals from the server.
         * @type {Boolean}
         * @default false
         * @private
         */
        this._requestVertexNormals = defaultValue(options.requestVertexNormals, false);
        this._littleEndianExtensionSize = true;
        /**
         * Boolean flag that indicates if the client should request tile watermasks from the server.
         * @type {Boolean}
         * @default false
         * @private
         */
        this._requestWaterMask = defaultValue(options.requestWaterMask, false);

        this._errorEvent = new Event();
        this._availability = undefined;

        var credit = options.credit;
        if (typeof credit === 'string') {
            credit = new Credit(credit);
        }
        this._credit = credit;

        this._ready = false;
        this._readyPromise = when.defer();

        var metadataUrl = joinUrls(this._url, 'layer.json');
        if (defined(this._proxy)) {
            metadataUrl = this._proxy.getURL(metadataUrl);
        }

        var that = this;
        var metadataError;

        function metadataSuccess(data) {
            var message;

            if (!data.format) {
                message = 'The tile format is not specified in the layer.json file.';
                metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
                return;
            }

            if (!data.tiles || data.tiles.length === 0) {
                message = 'The layer.json file does not specify any tile URL templates.';
                metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
                return;
            }

            if (data.format === 'heightmap-1.0') {
                that._heightmapStructure = {
                        heightScale : 1.0 / 5.0,
                        heightOffset : -1000.0,
                        elementsPerHeight : 1,
                        stride : 1,
                        elementMultiplier : 256.0,
                        isBigEndian : false,
                        lowestEncodedHeight : 0,
                        highestEncodedHeight : 256 * 256 - 1
                    };
                that._hasWaterMask = true;
                that._requestWaterMask = true;
            } else if (data.format.indexOf('quantized-mesh-1.') !== 0) {
                message = 'The tile format "' + data.format + '" is invalid or not supported.';
                metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
                return;
            }

            that._tileUrlTemplates = data.tiles;
            for (var i = 0; i < that._tileUrlTemplates.length; ++i) {
                var template = new Uri(that._tileUrlTemplates[i]);
                var baseUri = new Uri(that._url);
                if (template.authority && !baseUri.authority) {
                    baseUri.authority = template.authority;
                    baseUri.scheme = template.scheme;
                }
                that._tileUrlTemplates[i] = joinUrls(baseUri, template).toString().replace('{version}', data.version);
            }

            var availableTiles = data.available;

            if (defined(availableTiles)) {
                that._availability = new TileAvailability(that._tilingScheme, availableTiles.length);

                for (var level = 0; level < availableTiles.length; ++level) {
                    var rangesAtLevel = availableTiles[level];
                    var yTiles = that._tilingScheme.getNumberOfYTilesAtLevel(level);

                    for (var rangeIndex = 0; rangeIndex < rangesAtLevel.length; ++rangeIndex) {
                        var range = rangesAtLevel[rangeIndex];
                        that._availability.addAvailableTileRange(level, range.startX, yTiles - range.endY - 1, range.endX, yTiles - range.startY - 1);
                    }
                }
            }

            if (!defined(that._credit) && defined(data.attribution) && data.attribution !== null) {
                that._credit = new Credit(data.attribution);
            }

            // The vertex normals defined in the 'octvertexnormals' extension is identical to the original
            // contents of the original 'vertexnormals' extension.  'vertexnormals' extension is now
            // deprecated, as the extensionLength for this extension was incorrectly using big endian.
            // We maintain backwards compatibility with the legacy 'vertexnormal' implementation
            // by setting the _littleEndianExtensionSize to false. Always prefer 'octvertexnormals'
            // over 'vertexnormals' if both extensions are supported by the server.
            if (defined(data.extensions) && data.extensions.indexOf('octvertexnormals') !== -1) {
                that._hasVertexNormals = true;
            } else if (defined(data.extensions) && data.extensions.indexOf('vertexnormals') !== -1) {
                that._hasVertexNormals = true;
                that._littleEndianExtensionSize = false;
            }
            if (defined(data.extensions) && data.extensions.indexOf('watermask') !== -1) {
                that._hasWaterMask = true;
            }

            that._ready = true;
            that._readyPromise.resolve(true);
        }

        function metadataFailure(data) {
            // If the metadata is not found, assume this is a pre-metadata heightmap tileset.
            if (defined(data) && data.statusCode === 404) {
                metadataSuccess({
                    tilejson: '2.1.0',
                    format : 'heightmap-1.0',
                    version : '1.0.0',
                    scheme : 'tms',
                    tiles : [
                        '{z}/{x}/{y}.terrain?v={version}'
                    ]
                });
                return;
            }
            var message = 'An error occurred while accessing ' + metadataUrl + '.';
            metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
        }

        function requestMetadata() {
            var metadata = loadJson(metadataUrl);
            when(metadata, metadataSuccess, metadataFailure);
        }

        requestMetadata();
    }

    /**
     * When using the Quantized-Mesh format, a tile may be returned that includes additional extensions, such as PerVertexNormals, watermask, etc.
     * This enumeration defines the unique identifiers for each type of extension data that has been appended to the standard mesh data.
     *
     * @exports QuantizedMeshExtensionIds
     * @see CesiumTerrainProvider
     * @private
     */
    var QuantizedMeshExtensionIds = {
        /**
         * Oct-Encoded Per-Vertex Normals are included as an extension to the tile mesh
         *
         * @type {Number}
         * @constant
         * @default 1
         */
        OCT_VERTEX_NORMALS: 1,
        /**
         * A watermask is included as an extension to the tile mesh
         *
         * @type {Number}
         * @constant
         * @default 2
         */
        WATER_MASK: 2
    };

    function getRequestHeader(extensionsList) {
        if (!defined(extensionsList) || extensionsList.length === 0) {
            return {
                Accept : 'application/vnd.quantized-mesh,application/octet-stream;q=0.9,*/*;q=0.01'
            };
        } else {
            var extensions = extensionsList.join('-');
            return {
                Accept : 'application/vnd.quantized-mesh;extensions=' + extensions + ',application/octet-stream;q=0.9,*/*;q=0.01'
            };
        }
    }

    function createHeightmapTerrainData(provider, buffer, level, x, y, tmsY) {
        var heightBuffer = new Uint16Array(buffer, 0, provider._heightmapWidth * provider._heightmapWidth);
        return new HeightmapTerrainData({
            buffer : heightBuffer,
            childTileMask : new Uint8Array(buffer, heightBuffer.byteLength, 1)[0],
            waterMask : new Uint8Array(buffer, heightBuffer.byteLength + 1, buffer.byteLength - heightBuffer.byteLength - 1),
            width : provider._heightmapWidth,
            height : provider._heightmapWidth,
            structure : provider._heightmapStructure
        });
    }

    function createQuantizedMeshTerrainData(provider, buffer, level, x, y, tmsY) {
        var pos = 0;
        var cartesian3Elements = 3;
        var boundingSphereElements = cartesian3Elements + 1;
        var cartesian3Length = Float64Array.BYTES_PER_ELEMENT * cartesian3Elements;
        var boundingSphereLength = Float64Array.BYTES_PER_ELEMENT * boundingSphereElements;
        var encodedVertexElements = 3;
        var encodedVertexLength = Uint16Array.BYTES_PER_ELEMENT * encodedVertexElements;
        var triangleElements = 3;
        var bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT;
        var triangleLength = bytesPerIndex * triangleElements;

        var view = new DataView(buffer);
        var center = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true));
        pos += cartesian3Length;

        var minimumHeight = view.getFloat32(pos, true);
        pos += Float32Array.BYTES_PER_ELEMENT;
        var maximumHeight = view.getFloat32(pos, true);
        pos += Float32Array.BYTES_PER_ELEMENT;

        var boundingSphere = new BoundingSphere(
                new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true)),
                view.getFloat64(pos + cartesian3Length, true));
        pos += boundingSphereLength;

        var horizonOcclusionPoint = new Cartesian3(view.getFloat64(pos, true), view.getFloat64(pos + 8, true), view.getFloat64(pos + 16, true));
        pos += cartesian3Length;

        var vertexCount = view.getUint32(pos, true);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        var encodedVertexBuffer = new Uint16Array(buffer, pos, vertexCount * 3);
        pos += vertexCount * encodedVertexLength;

        if (vertexCount > 64 * 1024) {
            // More than 64k vertices, so indices are 32-bit.
            bytesPerIndex = Uint32Array.BYTES_PER_ELEMENT;
            triangleLength = bytesPerIndex * triangleElements;
        }

        // Decode the vertex buffer.
        var uBuffer = encodedVertexBuffer.subarray(0, vertexCount);
        var vBuffer = encodedVertexBuffer.subarray(vertexCount, 2 * vertexCount);
        var heightBuffer = encodedVertexBuffer.subarray(vertexCount * 2, 3 * vertexCount);

        var i;
        var u = 0;
        var v = 0;
        var height = 0;

        function zigZagDecode(value) {
            return (value >> 1) ^ (-(value & 1));
        }

        for (i = 0; i < vertexCount; ++i) {
            u += zigZagDecode(uBuffer[i]);
            v += zigZagDecode(vBuffer[i]);
            height += zigZagDecode(heightBuffer[i]);

            uBuffer[i] = u;
            vBuffer[i] = v;
            heightBuffer[i] = height;
        }

        // skip over any additional padding that was added for 2/4 byte alignment
        if (pos % bytesPerIndex !== 0) {
            pos += (bytesPerIndex - (pos % bytesPerIndex));
        }

        var triangleCount = view.getUint32(pos, true);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        var indices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, triangleCount * triangleElements);
        pos += triangleCount * triangleLength;

        // High water mark decoding based on decompressIndices_ in webgl-loader's loader.js.
        // https://code.google.com/p/webgl-loader/source/browse/trunk/samples/loader.js?r=99#55
        // Copyright 2012 Google Inc., Apache 2.0 license.
        var highest = 0;
        for (i = 0; i < indices.length; ++i) {
            var code = indices[i];
            indices[i] = highest - code;
            if (code === 0) {
                ++highest;
            }
        }

        var westVertexCount = view.getUint32(pos, true);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        var westIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, westVertexCount);
        pos += westVertexCount * bytesPerIndex;

        var southVertexCount = view.getUint32(pos, true);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        var southIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, southVertexCount);
        pos += southVertexCount * bytesPerIndex;

        var eastVertexCount = view.getUint32(pos, true);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        var eastIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, eastVertexCount);
        pos += eastVertexCount * bytesPerIndex;

        var northVertexCount = view.getUint32(pos, true);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        var northIndices = IndexDatatype.createTypedArrayFromArrayBuffer(vertexCount, buffer, pos, northVertexCount);
        pos += northVertexCount * bytesPerIndex;

        var encodedNormalBuffer;
        var waterMaskBuffer;
        while (pos < view.byteLength) {
            var extensionId = view.getUint8(pos, true);
            pos += Uint8Array.BYTES_PER_ELEMENT;
            var extensionLength = view.getUint32(pos, provider._littleEndianExtensionSize);
            pos += Uint32Array.BYTES_PER_ELEMENT;

            if (extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS && provider._requestVertexNormals) {
                encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2);
            } else if (extensionId === QuantizedMeshExtensionIds.WATER_MASK && provider._requestWaterMask) {
                waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength);
            }
            pos += extensionLength;
        }

        var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0;

        var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level);
        var orientedBoundingBox;
        if (rectangle.width < CesiumMath.PI_OVER_TWO + CesiumMath.EPSILON5) {
            // Here, rectangle.width < pi/2, and rectangle.height < pi
            // (though it would still work with rectangle.width up to pi)

            // The skirt is not included in the OBB computation. If this ever
            // causes any rendering artifacts (cracks), they are expected to be
            // minor and in the corners of the screen. It's possible that this
            // might need to be changed - just change to `minimumHeight - skirtHeight`
            // A similar change might also be needed in `upsampleQuantizedTerrainMesh.js`.
            orientedBoundingBox = OrientedBoundingBox.fromRectangle(rectangle, minimumHeight, maximumHeight, provider._tilingScheme.ellipsoid);
        }

        return new QuantizedMeshTerrainData({
            center : center,
            minimumHeight : minimumHeight,
            maximumHeight : maximumHeight,
            boundingSphere : boundingSphere,
            orientedBoundingBox : orientedBoundingBox,
            horizonOcclusionPoint : horizonOcclusionPoint,
            quantizedVertices : encodedVertexBuffer,
            encodedNormals : encodedNormalBuffer,
            indices : indices,
            westIndices : westIndices,
            southIndices : southIndices,
            eastIndices : eastIndices,
            northIndices : northIndices,
            westSkirtHeight : skirtHeight,
            southSkirtHeight : skirtHeight,
            eastSkirtHeight : skirtHeight,
            northSkirtHeight : skirtHeight,
            childTileMask: provider.availability.computeChildMaskForTile(level, x, y),
            waterMask: waterMaskBuffer
        });
    }

    /**
     * Requests the geometry for a given tile.  This function should not be called before
     * {@link CesiumTerrainProvider#ready} returns true.  The result must include terrain data and
     * may optionally include a water mask and an indication of which child tiles are available.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
     *                  or false if the request should be initiated regardless of the number of requests
     *                  already in progress.
     * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry.  If this method
     *          returns undefined instead of a promise, it is an indication that too many requests are already
     *          pending and the request will be retried later.
     *
     * @exception {DeveloperError} This function must not be called before {@link CesiumTerrainProvider#ready}
     *            returns true.
     */
    CesiumTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
                if (!this._ready) {
            throw new DeveloperError('requestTileGeometry must not be called before the terrain provider is ready.');
        }
        
        var urlTemplates = this._tileUrlTemplates;
        if (urlTemplates.length === 0) {
            return undefined;
        }

        var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);

        var tmsY = (yTiles - y - 1);

        var url = urlTemplates[(x + tmsY + level) % urlTemplates.length].replace('{z}', level).replace('{x}', x).replace('{y}', tmsY);

        var proxy = this._proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        var promise;

        var extensionList = [];
        if (this._requestVertexNormals && this._hasVertexNormals) {
            extensionList.push(this._littleEndianExtensionSize ? 'octvertexnormals' : 'vertexnormals');
        }
        if (this._requestWaterMask && this._hasWaterMask) {
            extensionList.push('watermask');
        }

        function tileLoader(tileUrl) {
            return loadArrayBuffer(tileUrl, getRequestHeader(extensionList));
        }
        throttleRequests = defaultValue(throttleRequests, true);
        if (throttleRequests) {
            promise = throttleRequestByServer(url, tileLoader);
            if (!defined(promise)) {
                return undefined;
            }
        } else {
            promise = tileLoader(url);
        }

        var that = this;
        return when(promise, function(buffer) {
            if (defined(that._heightmapStructure)) {
                return createHeightmapTerrainData(that, buffer, level, x, y, tmsY);
            } else {
                return createQuantizedMeshTerrainData(that, buffer, level, x, y, tmsY);
            }
        });
    };

    defineProperties(CesiumTerrainProvider.prototype, {
        /**
         * Gets an event that is raised when the terrain provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets the credit to display when this terrain provider is active.  Typically this is used to credit
         * the source of the terrain.  This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Credit}
         */
        credit : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('credit must not be called before the terrain provider is ready.');
                }
                
                return this._credit;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link CesiumTerrainProvider#ready} returns true.
         * @memberof CesiumTerrainProvider.prototype
         * @type {GeographicTilingScheme}
         */
        tilingScheme : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tilingScheme must not be called before the terrain provider is ready.');
                }
                
                return this._tilingScheme;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Gets a value indicating whether or not the provider includes a water mask.  The water mask
         * indicates which areas of the globe are water rather than land, so they can be rendered
         * as a reflective surface with animated waves.  This function should not be
         * called before {@link CesiumTerrainProvider#ready} returns true.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Boolean}
         * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
         */
        hasWaterMask : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('hasWaterMask must not be called before the terrain provider is ready.');
                }
                
                return this._hasWaterMask && this._requestWaterMask;
            }
        },

        /**
         * Gets a value indicating whether or not the requested tiles include vertex normals.
         * This function should not be called before {@link CesiumTerrainProvider#ready} returns true.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Boolean}
         * @exception {DeveloperError} This property must not be called before {@link CesiumTerrainProvider#ready}
         */
        hasVertexNormals : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('hasVertexNormals must not be called before the terrain provider is ready.');
                }
                
                // returns true if we can request vertex normals from the server
                return this._hasVertexNormals && this._requestVertexNormals;
            }
        },

        /**
         * Boolean flag that indicates if the client should request vertex normals from the server.
         * Vertex normals data is appended to the standard tile mesh data only if the client requests the vertex normals and
         * if the server provides vertex normals.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Boolean}
         */
        requestVertexNormals : {
            get : function() {
                return this._requestVertexNormals;
            }
        },

        /**
         * Boolean flag that indicates if the client should request a watermask from the server.
         * Watermask data is appended to the standard tile mesh data only if the client requests the watermask and
         * if the server provides a watermask.
         * @memberof CesiumTerrainProvider.prototype
         * @type {Boolean}
         */
        requestWaterMask : {
            get : function() {
                return this._requestWaterMask;
            }
        },

        /**
         * Gets an object that can be used to determine availability of terrain from this provider, such as
         * at points and in rectangles.  This function should not be called before
         * {@link CesiumTerrainProvider#ready} returns true.  This property may be undefined if availability
         * information is not available.
         * @memberof CesiumTerrainProvider.prototype
         * @type {TileAvailability}
         */
        availability : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('availability must not be called before the terrain provider is ready.');
                }
                                return this._availability;
            }
        }
    });

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    CesiumTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
        return this._levelZeroMaximumGeometricError / (1 << level);
    };

    /**
     * Determines whether data for a tile is available to be loaded.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @returns {Boolean} Undefined if not supported, otherwise true or false.
     */
    CesiumTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
        if (!defined(this.availability)) {
            return undefined;
        }
        return this.availability.isTileAvailable(level, x, y);
    };

    return CesiumTerrainProvider;
});

/*global define*/
define('Core/EllipseGeometryLibrary',[
        './Cartesian3',
        './Math',
        './Matrix3',
        './Quaternion'
    ], function(
        Cartesian3,
        CesiumMath,
        Matrix3,
        Quaternion) {
    'use strict';

    var EllipseGeometryLibrary = {};

    var rotAxis = new Cartesian3();
    var tempVec = new Cartesian3();
    var unitQuat = new Quaternion();
    var rotMtx = new Matrix3();

    function pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, result) {
        var azimuth = theta + rotation;

        Cartesian3.multiplyByScalar(eastVec, Math.cos(azimuth), rotAxis);
        Cartesian3.multiplyByScalar(northVec, Math.sin(azimuth), tempVec);
        Cartesian3.add(rotAxis, tempVec, rotAxis);

        var cosThetaSquared = Math.cos(theta);
        cosThetaSquared = cosThetaSquared * cosThetaSquared;

        var sinThetaSquared = Math.sin(theta);
        sinThetaSquared = sinThetaSquared * sinThetaSquared;

        var radius = ab / Math.sqrt(bSqr * cosThetaSquared + aSqr * sinThetaSquared);
        var angle = radius / mag;

        // Create the quaternion to rotate the position vector to the boundary of the ellipse.
        Quaternion.fromAxisAngle(rotAxis, angle, unitQuat);
        Matrix3.fromQuaternion(unitQuat, rotMtx);

        Matrix3.multiplyByVector(rotMtx, unitPos, result);
        Cartesian3.normalize(result, result);
        Cartesian3.multiplyByScalar(result, mag, result);
        return result;
    }

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchNormal = new Cartesian3();
    /**
     * Returns the positions raised to the given heights
     * @private
     */
    EllipseGeometryLibrary.raisePositionsToHeight = function(positions, options, extrude) {
        var ellipsoid = options.ellipsoid;
        var height = options.height;
        var extrudedHeight = options.extrudedHeight;
        var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3;

        var finalPositions = new Float64Array(size * 3);

        var length = positions.length;
        var bottomOffset = (extrude) ? length : 0;
        for (var i = 0; i < length; i += 3) {
            var i1 = i + 1;
            var i2 = i + 2;

            var position = Cartesian3.fromArray(positions, i, scratchCartesian1);
            ellipsoid.scaleToGeodeticSurface(position, position);

            var extrudedPosition = Cartesian3.clone(position, scratchCartesian2);
            var normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal);
            var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian3);
            Cartesian3.add(position, scaledNormal, position);

            if (extrude) {
                Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal);
                Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition);

                finalPositions[i + bottomOffset] = extrudedPosition.x;
                finalPositions[i1 + bottomOffset] = extrudedPosition.y;
                finalPositions[i2 + bottomOffset] = extrudedPosition.z;
            }

            finalPositions[i] = position.x;
            finalPositions[i1] = position.y;
            finalPositions[i2] = position.z;
        }

        return finalPositions;
    };

    var unitPosScratch = new Cartesian3();
    var eastVecScratch = new Cartesian3();
    var northVecScratch = new Cartesian3();
    /**
     * Returns an array of positions that make up the ellipse.
     * @private
     */
    EllipseGeometryLibrary.computeEllipsePositions = function(options, addFillPositions, addEdgePositions) {
        var semiMinorAxis = options.semiMinorAxis;
        var semiMajorAxis = options.semiMajorAxis;
        var rotation = options.rotation;
        var center = options.center;

        // Computing the arc-length of the ellipse is too expensive to be practical. Estimating it using the
        // arc length of the sphere is too inaccurate and creates sharp edges when either the semi-major or
        // semi-minor axis is much bigger than the other. Instead, scale the angle delta to make
        // the distance along the ellipse boundary more closely match the granularity.
        var granularity = options.granularity * 8.0;

        var aSqr = semiMinorAxis * semiMinorAxis;
        var bSqr = semiMajorAxis * semiMajorAxis;
        var ab = semiMajorAxis * semiMinorAxis;

        var mag = Cartesian3.magnitude(center);

        var unitPos = Cartesian3.normalize(center, unitPosScratch);
        var eastVec = Cartesian3.cross(Cartesian3.UNIT_Z, center, eastVecScratch);
        eastVec = Cartesian3.normalize(eastVec, eastVec);
        var northVec = Cartesian3.cross(unitPos, eastVec, northVecScratch);

        // The number of points in the first quadrant
        var numPts = 1 + Math.ceil(CesiumMath.PI_OVER_TWO / granularity);

        var deltaTheta = CesiumMath.PI_OVER_TWO / (numPts - 1);
        var theta = CesiumMath.PI_OVER_TWO - numPts * deltaTheta;
        if (theta < 0.0) {
            numPts -= Math.ceil(Math.abs(theta) / deltaTheta);
        }

        // If the number of points were three, the ellipse
        // would be tessellated like below:
        //
        //         *---*
        //       / | \ | \
        //     *---*---*---*
        //   / | \ | \ | \ | \
        //  / .*---*---*---*. \
        // * ` | \ | \ | \ | `*
        //  \`.*---*---*---*.`/
        //   \ | \ | \ | \ | /
        //     *---*---*---*
        //       \ | \ | /
        //         *---*
        // The first and last column have one position and fan to connect to the adjacent column.
        // Each other vertical column contains an even number of positions.
        var size = 2 * (numPts * (numPts + 2));
        var positions = (addFillPositions) ? new Array(size * 3) : undefined;
        var positionIndex = 0;
        var position = scratchCartesian1;
        var reflectedPosition = scratchCartesian2;

        var outerPositionsLength = (numPts * 4) * 3;
        var outerRightIndex = outerPositionsLength - 1;
        var outerLeftIndex = 0;
        var outerPositions = (addEdgePositions) ? new Array(outerPositionsLength) : undefined;

        var i;
        var j;
        var numInterior;
        var t;
        var interiorPosition;

        // Compute points in the 'eastern' half of the ellipse
        theta = CesiumMath.PI_OVER_TWO;
        position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
        if (addFillPositions) {
            positions[positionIndex++] = position.x;
            positions[positionIndex++] = position.y;
            positions[positionIndex++] = position.z;
        }
        if (addEdgePositions) {
            outerPositions[outerRightIndex--] = position.z;
            outerPositions[outerRightIndex--] = position.y;
            outerPositions[outerRightIndex--] = position.x;
        }
        theta = CesiumMath.PI_OVER_TWO -  deltaTheta;
        for (i = 1; i < numPts + 1; ++i) {
            position = pointOnEllipsoid(theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
            reflectedPosition = pointOnEllipsoid(Math.PI - theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition);

            if (addFillPositions) {
                positions[positionIndex++] = position.x;
                positions[positionIndex++] = position.y;
                positions[positionIndex++] = position.z;

                numInterior = 2 * i + 2;
                for (j = 1; j < numInterior - 1; ++j) {
                    t = j / (numInterior - 1);
                    interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3);
                    positions[positionIndex++] = interiorPosition.x;
                    positions[positionIndex++] = interiorPosition.y;
                    positions[positionIndex++] = interiorPosition.z;
                }

                positions[positionIndex++] = reflectedPosition.x;
                positions[positionIndex++] = reflectedPosition.y;
                positions[positionIndex++] = reflectedPosition.z;
            }

            if (addEdgePositions) {
                outerPositions[outerRightIndex--] = position.z;
                outerPositions[outerRightIndex--] = position.y;
                outerPositions[outerRightIndex--] = position.x;
                outerPositions[outerLeftIndex++] = reflectedPosition.x;
                outerPositions[outerLeftIndex++] = reflectedPosition.y;
                outerPositions[outerLeftIndex++] = reflectedPosition.z;
            }

            theta = CesiumMath.PI_OVER_TWO - (i + 1) * deltaTheta;
        }

        // Compute points in the 'western' half of the ellipse
        for (i = numPts; i > 1; --i) {
            theta = CesiumMath.PI_OVER_TWO - (i - 1) * deltaTheta;

            position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);
            reflectedPosition = pointOnEllipsoid(theta + Math.PI, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, reflectedPosition);

            if (addFillPositions) {
                positions[positionIndex++] = position.x;
                positions[positionIndex++] = position.y;
                positions[positionIndex++] = position.z;

                numInterior = 2 * (i - 1) + 2;
                for (j = 1; j < numInterior - 1; ++j) {
                    t = j / (numInterior - 1);
                    interiorPosition = Cartesian3.lerp(position, reflectedPosition, t, scratchCartesian3);
                    positions[positionIndex++] = interiorPosition.x;
                    positions[positionIndex++] = interiorPosition.y;
                    positions[positionIndex++] = interiorPosition.z;
                }

                positions[positionIndex++] = reflectedPosition.x;
                positions[positionIndex++] = reflectedPosition.y;
                positions[positionIndex++] = reflectedPosition.z;
            }

            if (addEdgePositions) {
                outerPositions[outerRightIndex--] = position.z;
                outerPositions[outerRightIndex--] = position.y;
                outerPositions[outerRightIndex--] = position.x;
                outerPositions[outerLeftIndex++] = reflectedPosition.x;
                outerPositions[outerLeftIndex++] = reflectedPosition.y;
                outerPositions[outerLeftIndex++] = reflectedPosition.z;
            }
        }

        theta = CesiumMath.PI_OVER_TWO;
        position = pointOnEllipsoid(-theta, rotation, northVec, eastVec, aSqr, ab, bSqr, mag, unitPos, position);

        var r = {};
        if (addFillPositions) {
            positions[positionIndex++] = position.x;
            positions[positionIndex++] = position.y;
            positions[positionIndex++] = position.z;
            r.positions = positions;
            r.numPts = numPts;
        }
        if (addEdgePositions) {
            outerPositions[outerRightIndex--] = position.z;
            outerPositions[outerRightIndex--] = position.y;
            outerPositions[outerRightIndex--] = position.x;
            r.outerPositions = outerPositions;
        }

        return r;
    };

    return EllipseGeometryLibrary;
});

/*global define*/
define('Core/GeometryInstance',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './Matrix4'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        Matrix4) {
    'use strict';

    /**
     * Geometry instancing allows one {@link Geometry} object to be positions in several
     * different locations and colored uniquely.  For example, one {@link BoxGeometry} can
     * be instanced several times, each with a different <code>modelMatrix</code> to change
     * its position, rotation, and scale.
     *
     * @alias GeometryInstance
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Geometry} options.geometry The geometry to instance.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix that transforms to transform the geometry from model to world coordinates.
     * @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick} or get/set per-instance attributes with {@link Primitive#getGeometryInstanceAttributes}.
     * @param {Object} [options.attributes] Per-instance attributes like a show or color attribute shown in the example below.
     *
     *
     * @example
     * // Create geometry for a box, and two instances that refer to it.
     * // One instance positions the box on the bottom and colored aqua.
     * // The other instance positions the box on the top and color white.
     * var geometry = Cesium.BoxGeometry.fromDimensions({
     *   vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL,
     *   dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 500000.0)
     * });
     * var instanceBottom = new Cesium.GeometryInstance({
     *   geometry : geometry,
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
     *   attributes : {
     *     color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
     *   },
     *   id : 'bottom'
     * });
     * var instanceTop = new Cesium.GeometryInstance({
     *   geometry : geometry,
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 3000000.0), new Cesium.Matrix4()),
     *   attributes : {
     *     color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
     *   },
     *   id : 'top'
     * });
     * 
     * @see Geometry
     */
    function GeometryInstance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.geometry)) {
            throw new DeveloperError('options.geometry is required.');
        }
        
        /**
         * The geometry being instanced.
         *
         * @type Geometry
         *
         * @default undefined
         */
        this.geometry = options.geometry;

        /**
         * The 4x4 transformation matrix that transforms the geometry from model to world coordinates.
         * When this is the identity matrix, the geometry is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type Matrix4
         *
         * @default Matrix4.IDENTITY
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));

        /**
         * User-defined object returned when the instance is picked or used to get/set per-instance attributes.
         *
         * @type Object
         *
         * @default undefined
         *
         * @see Scene#pick
         * @see Primitive#getGeometryInstanceAttributes
         */
        this.id = options.id;

        /**
         * Used for picking primitives that wrap geometry instances.
         *
         * @private
         */
        this.pickPrimitive = options.pickPrimitive;

        /**
         * Per-instance attributes like {@link ColorGeometryInstanceAttribute} or {@link ShowGeometryInstanceAttribute}.
         * {@link Geometry} attributes varying per vertex; these attributes are constant for the entire instance.
         *
         * @type Object
         *
         * @default undefined
         */
        this.attributes = defaultValue(options.attributes, {});

        /**
         * @private
         */
        this.westHemisphereGeometry = undefined;
        /**
         * @private
         */
        this.eastHemisphereGeometry = undefined;
    }

    return GeometryInstance;
});

/*global define*/
define('Core/EncodedCartesian3',[
        './Cartesian3',
        './defined',
        './DeveloperError'
    ], function(
        Cartesian3,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * A fixed-point encoding of a {@link Cartesian3} with 64-bit floating-point components, as two {@link Cartesian3}
     * values that, when converted to 32-bit floating-point and added, approximate the original input.
     * <p>
     * This is used to encode positions in vertex buffers for rendering without jittering artifacts
     * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
     * </p>
     *
     * @alias EncodedCartesian3
     * @constructor
     *
     * @private
     */
    function EncodedCartesian3() {
        /**
         * The high bits for each component.  Bits 0 to 22 store the whole value.  Bits 23 to 31 are not used.
         *
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.high = Cartesian3.clone(Cartesian3.ZERO);

        /**
         * The low bits for each component.  Bits 7 to 22 store the whole value, and bits 0 to 6 store the fraction.  Bits 23 to 31 are not used.
         *
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         */
        this.low = Cartesian3.clone(Cartesian3.ZERO);
    }

    /**
     * Encodes a 64-bit floating-point value as two floating-point values that, when converted to
     * 32-bit floating-point and added, approximate the original input.  The returned object
     * has <code>high</code> and <code>low</code> properties for the high and low bits, respectively.
     * <p>
     * The fixed-point encoding follows {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
     * </p>
     *
     * @param {Number} value The floating-point value to encode.
     * @param {Object} [result] The object onto which to store the result.
     * @returns {Object} The modified result parameter or a new instance if one was not provided.
     *
     * @example
     * var value = 1234567.1234567;
     * var splitValue = Cesium.EncodedCartesian3.encode(value);
     */
    EncodedCartesian3.encode = function(value, result) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        
        if (!defined(result)) {
            result = {
                high : 0.0,
                low : 0.0
            };
        }

        var doubleHigh;
        if (value >= 0.0) {
            doubleHigh = Math.floor(value / 65536.0) * 65536.0;
            result.high = doubleHigh;
            result.low = value - doubleHigh;
        } else {
            doubleHigh = Math.floor(-value / 65536.0) * 65536.0;
            result.high = -doubleHigh;
            result.low = value + doubleHigh;
        }

        return result;
    };

    var scratchEncode = {
        high : 0.0,
        low : 0.0
    };

    /**
     * Encodes a {@link Cartesian3} with 64-bit floating-point components as two {@link Cartesian3}
     * values that, when converted to 32-bit floating-point and added, approximate the original input.
     * <p>
     * The fixed-point encoding follows {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
     * </p>
     *
     * @param {Cartesian3} cartesian The cartesian to encode.
     * @param {EncodedCartesian3} [result] The object onto which to store the result.
     * @returns {EncodedCartesian3} The modified result parameter or a new EncodedCartesian3 instance if one was not provided.
     *
     * @example
     * var cart = new Cesium.Cartesian3(-10000000.0, 0.0, 10000000.0);
     * var encoded = Cesium.EncodedCartesian3.fromCartesian(cart);
     */
    EncodedCartesian3.fromCartesian = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required');
        }
        
        if (!defined(result)) {
            result = new EncodedCartesian3();
        }

        var high = result.high;
        var low = result.low;

        EncodedCartesian3.encode(cartesian.x, scratchEncode);
        high.x = scratchEncode.high;
        low.x = scratchEncode.low;

        EncodedCartesian3.encode(cartesian.y, scratchEncode);
        high.y = scratchEncode.high;
        low.y = scratchEncode.low;

        EncodedCartesian3.encode(cartesian.z, scratchEncode);
        high.z = scratchEncode.high;
        low.z = scratchEncode.low;

        return result;
    };

    var encodedP = new EncodedCartesian3();

    /**
     * Encodes the provided <code>cartesian</code>, and writes it to an array with <code>high</code>
     * components followed by <code>low</code> components, i.e. <code>[high.x, high.y, high.z, low.x, low.y, low.z]</code>.
     * <p>
     * This is used to create interleaved high-precision position vertex attributes.
     * </p>
     *
     * @param {Cartesian3} cartesian The cartesian to encode.
     * @param {Number[]} cartesianArray The array to write to.
     * @param {Number} index The index into the array to start writing.  Six elements will be written.
     *
     * @exception {DeveloperError} index must be a number greater than or equal to 0.
     *
     * @example
     * var positions = [
     *    new Cesium.Cartesian3(),
     *    // ...
     * ];
     * var encodedPositions = new Float32Array(2 * 3 * positions.length);
     * var j = 0;
     * for (var i = 0; i < positions.length; ++i) {
     *   Cesium.EncodedCartesian3.writeElement(positions[i], encodedPositions, j);
     *   j += 6;
     * }
     */
    EncodedCartesian3.writeElements = function(cartesian, cartesianArray, index) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required');
        }
        if (!defined(cartesianArray)) {
            throw new DeveloperError('cartesianArray is required');
        }
        if (typeof index !== 'number' || index < 0) {
            throw new DeveloperError('index must be a number greater than or equal to 0.');
        }
        
        EncodedCartesian3.fromCartesian(cartesian, encodedP);
        var high = encodedP.high;
        var low = encodedP.low;

        cartesianArray[index] = high.x;
        cartesianArray[index + 1] = high.y;
        cartesianArray[index + 2] = high.z;
        cartesianArray[index + 3] = low.x;
        cartesianArray[index + 4] = low.y;
        cartesianArray[index + 5] = low.z;
    };

    return EncodedCartesian3;
});

/*global define*/
define('Core/Tipsify',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Encapsulates an algorithm to optimize triangles for the post
     * vertex-shader cache.  This is based on the 2007 SIGGRAPH paper
     * 'Fast Triangle Reordering for Vertex Locality and Reduced Overdraw.'
     * The runtime is linear but several passes are made.
     *
     * @exports Tipsify
     *
     * @see <a href='http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf'>
     * Fast Triangle Reordering for Vertex Locality and Reduced Overdraw</a>
     * by Sander, Nehab, and Barczak
     *
     * @private
     */
    var Tipsify = {};

    /**
     * Calculates the average cache miss ratio (ACMR) for a given set of indices.
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
     *                        in the vertex buffer that define the geometry's triangles.
     * @param {Number} [options.maximumIndex] The maximum value of the elements in <code>args.indices</code>.
     *                                     If not supplied, this value will be computed.
     * @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
     * @returns {Number} The average cache miss ratio (ACMR).
     *
     * @exception {DeveloperError} indices length must be a multiple of three.
     * @exception {DeveloperError} cacheSize must be greater than two.
     *
     * @example
     * var indices = [0, 1, 2, 3, 4, 5];
     * var maxIndex = 5;
     * var cacheSize = 3;
     * var acmr = Cesium.Tipsify.calculateACMR({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
     */
    Tipsify.calculateACMR = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var indices = options.indices;
        var maximumIndex = options.maximumIndex;
        var cacheSize = defaultValue(options.cacheSize, 24);

                if (!defined(indices)) {
            throw new DeveloperError('indices is required.');
        }
        
        var numIndices = indices.length;

                if (numIndices < 3 || numIndices % 3 !== 0) {
            throw new DeveloperError('indices length must be a multiple of three.');
        }
        if (maximumIndex <= 0) {
            throw new DeveloperError('maximumIndex must be greater than zero.');
        }
        if (cacheSize < 3) {
            throw new DeveloperError('cacheSize must be greater than two.');
        }
        
        // Compute the maximumIndex if not given
        if (!defined(maximumIndex)) {
            maximumIndex = 0;
            var currentIndex = 0;
            var intoIndices = indices[currentIndex];
            while (currentIndex < numIndices) {
                if (intoIndices > maximumIndex) {
                    maximumIndex = intoIndices;
                }
                ++currentIndex;
                intoIndices = indices[currentIndex];
            }
        }

        // Vertex time stamps
        var vertexTimeStamps = [];
        for ( var i = 0; i < maximumIndex + 1; i++) {
            vertexTimeStamps[i] = 0;
        }

        // Cache processing
        var s = cacheSize + 1;
        for ( var j = 0; j < numIndices; ++j) {
            if ((s - vertexTimeStamps[indices[j]]) > cacheSize) {
                vertexTimeStamps[indices[j]] = s;
                ++s;
            }
        }

        return (s - cacheSize + 1) / (numIndices / 3);
    };

    /**
     * Optimizes triangles for the post-vertex shader cache.
     *
     * @param {Number[]} options.indices Lists triads of numbers corresponding to the indices of the vertices
     *                        in the vertex buffer that define the geometry's triangles.
     * @param {Number} [options.maximumIndex] The maximum value of the elements in <code>args.indices</code>.
     *                                     If not supplied, this value will be computed.
     * @param {Number} [options.cacheSize=24] The number of vertices that can be stored in the cache at any one time.
     * @returns {Number[]} A list of the input indices in an optimized order.
     *
     * @exception {DeveloperError} indices length must be a multiple of three.
     * @exception {DeveloperError} cacheSize must be greater than two.
     *
     * @example
     * var indices = [0, 1, 2, 3, 4, 5];
     * var maxIndex = 5;
     * var cacheSize = 3;
     * var reorderedIndices = Cesium.Tipsify.tipsify({indices : indices, maxIndex : maxIndex, cacheSize : cacheSize});
     */
    Tipsify.tipsify = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var indices = options.indices;
        var maximumIndex = options.maximumIndex;
        var cacheSize = defaultValue(options.cacheSize, 24);

        var cursor;

        function skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne) {
            while (deadEnd.length >= 1) {
                // while the stack is not empty
                var d = deadEnd[deadEnd.length - 1]; // top of the stack
                deadEnd.splice(deadEnd.length - 1, 1); // pop the stack

                if (vertices[d].numLiveTriangles > 0) {
                    return d;
                }
            }

            while (cursor < maximumIndexPlusOne) {
                if (vertices[cursor].numLiveTriangles > 0) {
                    ++cursor;
                    return cursor - 1;
                }
                ++cursor;
            }
            return -1;
        }

        function getNextVertex(indices, cacheSize, oneRing, vertices, s, deadEnd, maximumIndexPlusOne) {
            var n = -1;
            var p;
            var m = -1;
            var itOneRing = 0;
            while (itOneRing < oneRing.length) {
                var index = oneRing[itOneRing];
                if (vertices[index].numLiveTriangles) {
                    p = 0;
                    if ((s - vertices[index].timeStamp + (2 * vertices[index].numLiveTriangles)) <= cacheSize) {
                        p = s - vertices[index].timeStamp;
                    }
                    if ((p > m) || (m === -1)) {
                        m = p;
                        n = index;
                    }
                }
                ++itOneRing;
            }
            if (n === -1) {
                return skipDeadEnd(vertices, deadEnd, indices, maximumIndexPlusOne);
            }
            return n;
        }

                if (!defined(indices)) {
            throw new DeveloperError('indices is required.');
        }
        
        var numIndices = indices.length;

                if (numIndices < 3 || numIndices % 3 !== 0) {
            throw new DeveloperError('indices length must be a multiple of three.');
        }
        if (maximumIndex <= 0) {
            throw new DeveloperError('maximumIndex must be greater than zero.');
        }
        if (cacheSize < 3) {
            throw new DeveloperError('cacheSize must be greater than two.');
        }
        
        // Determine maximum index
        var maximumIndexPlusOne = 0;
        var currentIndex = 0;
        var intoIndices = indices[currentIndex];
        var endIndex = numIndices;
        if (defined(maximumIndex)) {
            maximumIndexPlusOne = maximumIndex + 1;
        } else {
            while (currentIndex < endIndex) {
                if (intoIndices > maximumIndexPlusOne) {
                    maximumIndexPlusOne = intoIndices;
                }
                ++currentIndex;
                intoIndices = indices[currentIndex];
            }
            if (maximumIndexPlusOne === -1) {
                return 0;
            }
            ++maximumIndexPlusOne;
        }

        // Vertices
        var vertices = [];
        for ( var i = 0; i < maximumIndexPlusOne; i++) {
            vertices[i] = {
                numLiveTriangles : 0,
                timeStamp : 0,
                vertexTriangles : []
            };
        }
        currentIndex = 0;
        var triangle = 0;
        while (currentIndex < endIndex) {
            vertices[indices[currentIndex]].vertexTriangles.push(triangle);
            ++(vertices[indices[currentIndex]]).numLiveTriangles;
            vertices[indices[currentIndex + 1]].vertexTriangles.push(triangle);
            ++(vertices[indices[currentIndex + 1]]).numLiveTriangles;
            vertices[indices[currentIndex + 2]].vertexTriangles.push(triangle);
            ++(vertices[indices[currentIndex + 2]]).numLiveTriangles;
            ++triangle;
            currentIndex += 3;
        }

        // Starting index
        var f = 0;

        // Time Stamp
        var s = cacheSize + 1;
        cursor = 1;

        // Process
        var oneRing = [];
        var deadEnd = []; //Stack
        var vertex;
        var intoVertices;
        var currentOutputIndex = 0;
        var outputIndices = [];
        var numTriangles = numIndices / 3;
        var triangleEmitted = [];
        for (i = 0; i < numTriangles; i++) {
            triangleEmitted[i] = false;
        }
        var index;
        var limit;
        while (f !== -1) {
            oneRing = [];
            intoVertices = vertices[f];
            limit = intoVertices.vertexTriangles.length;
            for ( var k = 0; k < limit; ++k) {
                triangle = intoVertices.vertexTriangles[k];
                if (!triangleEmitted[triangle]) {
                    triangleEmitted[triangle] = true;
                    currentIndex = triangle + triangle + triangle;
                    for ( var j = 0; j < 3; ++j) {
                        // Set this index as a possible next index
                        index = indices[currentIndex];
                        oneRing.push(index);
                        deadEnd.push(index);

                        // Output index
                        outputIndices[currentOutputIndex] = index;
                        ++currentOutputIndex;

                        // Cache processing
                        vertex = vertices[index];
                        --vertex.numLiveTriangles;
                        if ((s - vertex.timeStamp) > cacheSize) {
                            vertex.timeStamp = s;
                            ++s;
                        }
                        ++currentIndex;
                    }
                }
            }
            f = getNextVertex(indices, cacheSize, oneRing, vertices, s, deadEnd, maximumIndexPlusOne);
        }

        return outputIndices;
    };

    return Tipsify;
});

/*global define*/
define('Core/GeometryPipeline',[
        './AttributeCompression',
        './barycentricCoordinates',
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './Cartesian4',
        './Cartographic',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './EncodedCartesian3',
        './GeographicProjection',
        './Geometry',
        './GeometryAttribute',
        './GeometryType',
        './IndexDatatype',
        './Intersect',
        './IntersectionTests',
        './Math',
        './Matrix3',
        './Matrix4',
        './Plane',
        './PrimitiveType',
        './Tipsify'
    ], function(
        AttributeCompression,
        barycentricCoordinates,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        EncodedCartesian3,
        GeographicProjection,
        Geometry,
        GeometryAttribute,
        GeometryType,
        IndexDatatype,
        Intersect,
        IntersectionTests,
        CesiumMath,
        Matrix3,
        Matrix4,
        Plane,
        PrimitiveType,
        Tipsify) {
    'use strict';

    /**
     * Content pipeline functions for geometries.
     *
     * @exports GeometryPipeline
     *
     * @see Geometry
     */
    var GeometryPipeline = {};

    function addTriangle(lines, index, i0, i1, i2) {
        lines[index++] = i0;
        lines[index++] = i1;

        lines[index++] = i1;
        lines[index++] = i2;

        lines[index++] = i2;
        lines[index] = i0;
    }

    function trianglesToLines(triangles) {
        var count = triangles.length;
        var size = (count / 3) * 6;
        var lines = IndexDatatype.createTypedArray(count, size);

        var index = 0;
        for ( var i = 0; i < count; i += 3, index += 6) {
            addTriangle(lines, index, triangles[i], triangles[i + 1], triangles[i + 2]);
        }

        return lines;
    }

    function triangleStripToLines(triangles) {
        var count = triangles.length;
        if (count >= 3) {
            var size = (count - 2) * 6;
            var lines = IndexDatatype.createTypedArray(count, size);

            addTriangle(lines, 0, triangles[0], triangles[1], triangles[2]);
            var index = 6;

            for ( var i = 3; i < count; ++i, index += 6) {
                addTriangle(lines, index, triangles[i - 1], triangles[i], triangles[i - 2]);
            }

            return lines;
        }

        return new Uint16Array();
    }

    function triangleFanToLines(triangles) {
        if (triangles.length > 0) {
            var count = triangles.length - 1;
            var size = (count - 1) * 6;
            var lines = IndexDatatype.createTypedArray(count, size);

            var base = triangles[0];
            var index = 0;
            for ( var i = 1; i < count; ++i, index += 6) {
                addTriangle(lines, index, base, triangles[i], triangles[i + 1]);
            }

            return lines;
        }

        return new Uint16Array();
    }

    /**
     * Converts a geometry's triangle indices to line indices.  If the geometry has an <code>indices</code>
     * and its <code>primitiveType</code> is <code>TRIANGLES</code>, <code>TRIANGLE_STRIP</code>,
     * <code>TRIANGLE_FAN</code>, it is converted to <code>LINES</code>; otherwise, the geometry is not changed.
     * <p>
     * This is commonly used to create a wireframe geometry for visual debugging.
     * </p>
     *
     * @param {Geometry} geometry The geometry to modify.
     * @returns {Geometry} The modified <code>geometry</code> argument, with its triangle indices converted to lines.
     *
     * @exception {DeveloperError} geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN.
     *
     * @example
     * geometry = Cesium.GeometryPipeline.toWireframe(geometry);
     */
    GeometryPipeline.toWireframe = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        var indices = geometry.indices;
        if (defined(indices)) {
            switch (geometry.primitiveType) {
                case PrimitiveType.TRIANGLES:
                    geometry.indices = trianglesToLines(indices);
                    break;
                case PrimitiveType.TRIANGLE_STRIP:
                    geometry.indices = triangleStripToLines(indices);
                    break;
                case PrimitiveType.TRIANGLE_FAN:
                    geometry.indices = triangleFanToLines(indices);
                    break;
                                default:
                    throw new DeveloperError('geometry.primitiveType must be TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN.');
                            }

            geometry.primitiveType = PrimitiveType.LINES;
        }

        return geometry;
    };

    /**
     * Creates a new {@link Geometry} with <code>LINES</code> representing the provided
     * attribute (<code>attributeName</code>) for the provided geometry.  This is used to
     * visualize vector attributes like normals, tangents, and bitangents.
     *
     * @param {Geometry} geometry The <code>Geometry</code> instance with the attribute.
     * @param {String} [attributeName='normal'] The name of the attribute.
     * @param {Number} [length=10000.0] The length of each line segment in meters.  This can be negative to point the vector in the opposite direction.
     * @returns {Geometry} A new <code>Geometry</code> instance with line segments for the vector.
     *
     * @exception {DeveloperError} geometry.attributes must have an attribute with the same name as the attributeName parameter.
     *
     * @example
     * var geometry = Cesium.GeometryPipeline.createLineSegmentsForVectors(instance.geometry, 'bitangent', 100000.0);
     */
    GeometryPipeline.createLineSegmentsForVectors = function(geometry, attributeName, length) {
        attributeName = defaultValue(attributeName, 'normal');

                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        if (!defined(geometry.attributes.position)) {
            throw new DeveloperError('geometry.attributes.position is required.');
        }
        if (!defined(geometry.attributes[attributeName])) {
            throw new DeveloperError('geometry.attributes must have an attribute with the same name as the attributeName parameter, ' + attributeName + '.');
        }
        
        length = defaultValue(length, 10000.0);

        var positions = geometry.attributes.position.values;
        var vectors = geometry.attributes[attributeName].values;
        var positionsLength = positions.length;

        var newPositions = new Float64Array(2 * positionsLength);

        var j = 0;
        for (var i = 0; i < positionsLength; i += 3) {
            newPositions[j++] = positions[i];
            newPositions[j++] = positions[i + 1];
            newPositions[j++] = positions[i + 2];

            newPositions[j++] = positions[i] + (vectors[i] * length);
            newPositions[j++] = positions[i + 1] + (vectors[i + 1] * length);
            newPositions[j++] = positions[i + 2] + (vectors[i + 2] * length);
        }

        var newBoundingSphere;
        var bs = geometry.boundingSphere;
        if (defined(bs)) {
            newBoundingSphere = new BoundingSphere(bs.center, bs.radius + length);
        }

        return new Geometry({
            attributes : {
                position : new GeometryAttribute({
                    componentDatatype : ComponentDatatype.DOUBLE,
                    componentsPerAttribute : 3,
                    values : newPositions
                })
            },
            primitiveType : PrimitiveType.LINES,
            boundingSphere : newBoundingSphere
        });
    };

    /**
     * Creates an object that maps attribute names to unique locations (indices)
     * for matching vertex attributes and shader programs.
     *
     * @param {Geometry} geometry The geometry, which is not modified, to create the object for.
     * @returns {Object} An object with attribute name / index pairs.
     *
     * @example
     * var attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry);
     * // Example output
     * // {
     * //   'position' : 0,
     * //   'normal' : 1
     * // }
     */
    GeometryPipeline.createAttributeLocations = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        // There can be a WebGL performance hit when attribute 0 is disabled, so
        // assign attribute locations to well-known attributes.
        var semantics = [
            'position',
            'positionHigh',
            'positionLow',

            // From VertexFormat.position - after 2D projection and high-precision encoding
            'position3DHigh',
            'position3DLow',
            'position2DHigh',
            'position2DLow',

            // From Primitive
            'pickColor',

            // From VertexFormat
            'normal',
            'st',
            'tangent',
            'bitangent',

            // For shadow volumes
            'extrudeDirection',

            // From compressing texture coordinates and normals
            'compressedAttributes'
        ];

        var attributes = geometry.attributes;
        var indices = {};
        var j = 0;
        var i;
        var len = semantics.length;

        // Attribute locations for well-known attributes
        for (i = 0; i < len; ++i) {
            var semantic = semantics[i];

            if (defined(attributes[semantic])) {
                indices[semantic] = j++;
            }
        }

        // Locations for custom attributes
        for (var name in attributes) {
            if (attributes.hasOwnProperty(name) && (!defined(indices[name]))) {
                indices[name] = j++;
            }
        }

        return indices;
    };

    /**
     * Reorders a geometry's attributes and <code>indices</code> to achieve better performance from the GPU's pre-vertex-shader cache.
     *
     * @param {Geometry} geometry The geometry to modify.
     * @returns {Geometry} The modified <code>geometry</code> argument, with its attributes and indices reordered for the GPU's pre-vertex-shader cache.
     *
     * @exception {DeveloperError} Each attribute array in geometry.attributes must have the same number of attributes.
     *
     *
     * @example
     * geometry = Cesium.GeometryPipeline.reorderForPreVertexCache(geometry);
     *
     * @see GeometryPipeline.reorderForPostVertexCache
     */
    GeometryPipeline.reorderForPreVertexCache = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        var numVertices = Geometry.computeNumberOfVertices(geometry);

        var indices = geometry.indices;
        if (defined(indices)) {
            var indexCrossReferenceOldToNew = new Int32Array(numVertices);
            for ( var i = 0; i < numVertices; i++) {
                indexCrossReferenceOldToNew[i] = -1;
            }

            // Construct cross reference and reorder indices
            var indicesIn = indices;
            var numIndices = indicesIn.length;
            var indicesOut = IndexDatatype.createTypedArray(numVertices, numIndices);

            var intoIndicesIn = 0;
            var intoIndicesOut = 0;
            var nextIndex = 0;
            var tempIndex;
            while (intoIndicesIn < numIndices) {
                tempIndex = indexCrossReferenceOldToNew[indicesIn[intoIndicesIn]];
                if (tempIndex !== -1) {
                    indicesOut[intoIndicesOut] = tempIndex;
                } else {
                    tempIndex = indicesIn[intoIndicesIn];
                    indexCrossReferenceOldToNew[tempIndex] = nextIndex;

                    indicesOut[intoIndicesOut] = nextIndex;
                    ++nextIndex;
                }
                ++intoIndicesIn;
                ++intoIndicesOut;
            }
            geometry.indices = indicesOut;

            // Reorder attributes
            var attributes = geometry.attributes;
            for ( var property in attributes) {
                if (attributes.hasOwnProperty(property) &&
                        defined(attributes[property]) &&
                        defined(attributes[property].values)) {

                    var attribute = attributes[property];
                    var elementsIn = attribute.values;
                    var intoElementsIn = 0;
                    var numComponents = attribute.componentsPerAttribute;
                    var elementsOut = ComponentDatatype.createTypedArray(attribute.componentDatatype, nextIndex * numComponents);
                    while (intoElementsIn < numVertices) {
                        var temp = indexCrossReferenceOldToNew[intoElementsIn];
                        if (temp !== -1) {
                            for (i = 0; i < numComponents; i++) {
                                elementsOut[numComponents * temp + i] = elementsIn[numComponents * intoElementsIn + i];
                            }
                        }
                        ++intoElementsIn;
                    }
                    attribute.values = elementsOut;
                }
            }
        }

        return geometry;
    };

    /**
     * Reorders a geometry's <code>indices</code> to achieve better performance from the GPU's
     * post vertex-shader cache by using the Tipsify algorithm.  If the geometry <code>primitiveType</code>
     * is not <code>TRIANGLES</code> or the geometry does not have an <code>indices</code>, this function has no effect.
     *
     * @param {Geometry} geometry The geometry to modify.
     * @param {Number} [cacheCapacity=24] The number of vertices that can be held in the GPU's vertex cache.
     * @returns {Geometry} The modified <code>geometry</code> argument, with its indices reordered for the post-vertex-shader cache.
     *
     * @exception {DeveloperError} cacheCapacity must be greater than two.
     *
     *
     * @example
     * geometry = Cesium.GeometryPipeline.reorderForPostVertexCache(geometry);
     *
     * @see GeometryPipeline.reorderForPreVertexCache
     * @see {@link http://gfx.cs.princ0eton.edu/pubs/Sander_2007_%3ETR/tipsy.pdf|Fast Triangle Reordering for Vertex Locality and Reduced Overdraw}
     * by Sander, Nehab, and Barczak
     */
    GeometryPipeline.reorderForPostVertexCache = function(geometry, cacheCapacity) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        var indices = geometry.indices;
        if ((geometry.primitiveType === PrimitiveType.TRIANGLES) && (defined(indices))) {
            var numIndices = indices.length;
            var maximumIndex = 0;
            for ( var j = 0; j < numIndices; j++) {
                if (indices[j] > maximumIndex) {
                    maximumIndex = indices[j];
                }
            }
            geometry.indices = Tipsify.tipsify({
                indices : indices,
                maximumIndex : maximumIndex,
                cacheSize : cacheCapacity
            });
        }

        return geometry;
    };

    function copyAttributesDescriptions(attributes) {
        var newAttributes = {};

        for ( var attribute in attributes) {
            if (attributes.hasOwnProperty(attribute) &&
                    defined(attributes[attribute]) &&
                    defined(attributes[attribute].values)) {

                var attr = attributes[attribute];
                newAttributes[attribute] = new GeometryAttribute({
                    componentDatatype : attr.componentDatatype,
                    componentsPerAttribute : attr.componentsPerAttribute,
                    normalize : attr.normalize,
                    values : []
                });
            }
        }

        return newAttributes;
    }

    function copyVertex(destinationAttributes, sourceAttributes, index) {
        for ( var attribute in sourceAttributes) {
            if (sourceAttributes.hasOwnProperty(attribute) &&
                    defined(sourceAttributes[attribute]) &&
                    defined(sourceAttributes[attribute].values)) {

                var attr = sourceAttributes[attribute];

                for ( var k = 0; k < attr.componentsPerAttribute; ++k) {
                    destinationAttributes[attribute].values.push(attr.values[(index * attr.componentsPerAttribute) + k]);
                }
            }
        }
    }

    /**
     * Splits a geometry into multiple geometries, if necessary, to ensure that indices in the
     * <code>indices</code> fit into unsigned shorts.  This is used to meet the WebGL requirements
     * when unsigned int indices are not supported.
     * <p>
     * If the geometry does not have any <code>indices</code>, this function has no effect.
     * </p>
     *
     * @param {Geometry} geometry The geometry to be split into multiple geometries.
     * @returns {Geometry[]} An array of geometries, each with indices that fit into unsigned shorts.
     *
     * @exception {DeveloperError} geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS
     * @exception {DeveloperError} All geometry attribute lists must have the same number of attributes.
     *
     * @example
     * var geometries = Cesium.GeometryPipeline.fitToUnsignedShortIndices(geometry);
     */
    GeometryPipeline.fitToUnsignedShortIndices = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        if ((defined(geometry.indices)) &&
            ((geometry.primitiveType !== PrimitiveType.TRIANGLES) &&
             (geometry.primitiveType !== PrimitiveType.LINES) &&
             (geometry.primitiveType !== PrimitiveType.POINTS))) {
            throw new DeveloperError('geometry.primitiveType must equal to PrimitiveType.TRIANGLES, PrimitiveType.LINES, or PrimitiveType.POINTS.');
        }
        
        var geometries = [];

        // If there's an index list and more than 64K attributes, it is possible that
        // some indices are outside the range of unsigned short [0, 64K - 1]
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
        if (defined(geometry.indices) && (numberOfVertices >= CesiumMath.SIXTY_FOUR_KILOBYTES)) {
            var oldToNewIndex = [];
            var newIndices = [];
            var currentIndex = 0;
            var newAttributes = copyAttributesDescriptions(geometry.attributes);

            var originalIndices = geometry.indices;
            var numberOfIndices = originalIndices.length;

            var indicesPerPrimitive;

            if (geometry.primitiveType === PrimitiveType.TRIANGLES) {
                indicesPerPrimitive = 3;
            } else if (geometry.primitiveType === PrimitiveType.LINES) {
                indicesPerPrimitive = 2;
            } else if (geometry.primitiveType === PrimitiveType.POINTS) {
                indicesPerPrimitive = 1;
            }

            for ( var j = 0; j < numberOfIndices; j += indicesPerPrimitive) {
                for (var k = 0; k < indicesPerPrimitive; ++k) {
                    var x = originalIndices[j + k];
                    var i = oldToNewIndex[x];
                    if (!defined(i)) {
                        i = currentIndex++;
                        oldToNewIndex[x] = i;
                        copyVertex(newAttributes, geometry.attributes, x);
                    }
                    newIndices.push(i);
                }

                if (currentIndex + indicesPerPrimitive >= CesiumMath.SIXTY_FOUR_KILOBYTES) {
                    geometries.push(new Geometry({
                        attributes : newAttributes,
                        indices : newIndices,
                        primitiveType : geometry.primitiveType,
                        boundingSphere : geometry.boundingSphere,
                        boundingSphereCV : geometry.boundingSphereCV
                    }));

                    // Reset for next vertex-array
                    oldToNewIndex = [];
                    newIndices = [];
                    currentIndex = 0;
                    newAttributes = copyAttributesDescriptions(geometry.attributes);
                }
            }

            if (newIndices.length !== 0) {
                geometries.push(new Geometry({
                    attributes : newAttributes,
                    indices : newIndices,
                    primitiveType : geometry.primitiveType,
                    boundingSphere : geometry.boundingSphere,
                    boundingSphereCV : geometry.boundingSphereCV
                }));
            }
        } else {
            // No need to split into multiple geometries
            geometries.push(geometry);
        }

        return geometries;
    };

    var scratchProjectTo2DCartesian3 = new Cartesian3();
    var scratchProjectTo2DCartographic = new Cartographic();

    /**
     * Projects a geometry's 3D <code>position</code> attribute to 2D, replacing the <code>position</code>
     * attribute with separate <code>position3D</code> and <code>position2D</code> attributes.
     * <p>
     * If the geometry does not have a <code>position</code>, this function has no effect.
     * </p>
     *
     * @param {Geometry} geometry The geometry to modify.
     * @param {String} attributeName The name of the attribute.
     * @param {String} attributeName3D The name of the attribute in 3D.
     * @param {String} attributeName2D The name of the attribute in 2D.
     * @param {Object} [projection=new GeographicProjection()] The projection to use.
     * @returns {Geometry} The modified <code>geometry</code> argument with <code>position3D</code> and <code>position2D</code> attributes.
     *
     * @exception {DeveloperError} geometry must have attribute matching the attributeName argument.
     * @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE.
     * @exception {DeveloperError} Could not project a point to 2D.
     *
     * @example
     * geometry = Cesium.GeometryPipeline.projectTo2D(geometry, 'position', 'position3D', 'position2D');
     */
    GeometryPipeline.projectTo2D = function(geometry, attributeName, attributeName3D, attributeName2D, projection) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        if (!defined(attributeName)) {
            throw new DeveloperError('attributeName is required.');
        }
        if (!defined(attributeName3D)) {
            throw new DeveloperError('attributeName3D is required.');
        }
        if (!defined(attributeName2D)) {
            throw new DeveloperError('attributeName2D is required.');
        }
        if (!defined(geometry.attributes[attributeName])) {
            throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.');
        }
        if (geometry.attributes[attributeName].componentDatatype !== ComponentDatatype.DOUBLE) {
            throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.');
        }
        
        var attribute = geometry.attributes[attributeName];
        projection = (defined(projection)) ? projection : new GeographicProjection();
        var ellipsoid = projection.ellipsoid;

        // Project original values to 2D.
        var values3D = attribute.values;
        var projectedValues = new Float64Array(values3D.length);
        var index = 0;

        for ( var i = 0; i < values3D.length; i += 3) {
            var value = Cartesian3.fromArray(values3D, i, scratchProjectTo2DCartesian3);

            var lonLat = ellipsoid.cartesianToCartographic(value, scratchProjectTo2DCartographic);
                        if (!defined(lonLat)) {
                throw new DeveloperError('Could not project point (' + value.x + ', ' + value.y + ', ' + value.z + ') to 2D.');
            }
            
            var projectedLonLat = projection.project(lonLat, scratchProjectTo2DCartesian3);

            projectedValues[index++] = projectedLonLat.x;
            projectedValues[index++] = projectedLonLat.y;
            projectedValues[index++] = projectedLonLat.z;
        }

        // Rename original cartesians to WGS84 cartesians.
        geometry.attributes[attributeName3D] = attribute;

        // Replace original cartesians with 2D projected cartesians
        geometry.attributes[attributeName2D] = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : projectedValues
        });
        delete geometry.attributes[attributeName];

        return geometry;
    };

    var encodedResult = {
        high : 0.0,
        low : 0.0
    };

    /**
     * Encodes floating-point geometry attribute values as two separate attributes to improve
     * rendering precision.
     * <p>
     * This is commonly used to create high-precision position vertex attributes.
     * </p>
     *
     * @param {Geometry} geometry The geometry to modify.
     * @param {String} attributeName The name of the attribute.
     * @param {String} attributeHighName The name of the attribute for the encoded high bits.
     * @param {String} attributeLowName The name of the attribute for the encoded low bits.
     * @returns {Geometry} The modified <code>geometry</code> argument, with its encoded attribute.
     *
     * @exception {DeveloperError} geometry must have attribute matching the attributeName argument.
     * @exception {DeveloperError} The attribute componentDatatype must be ComponentDatatype.DOUBLE.
     *
     * @example
     * geometry = Cesium.GeometryPipeline.encodeAttribute(geometry, 'position3D', 'position3DHigh', 'position3DLow');
     */
    GeometryPipeline.encodeAttribute = function(geometry, attributeName, attributeHighName, attributeLowName) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        if (!defined(attributeName)) {
            throw new DeveloperError('attributeName is required.');
        }
        if (!defined(attributeHighName)) {
            throw new DeveloperError('attributeHighName is required.');
        }
        if (!defined(attributeLowName)) {
            throw new DeveloperError('attributeLowName is required.');
        }
        if (!defined(geometry.attributes[attributeName])) {
            throw new DeveloperError('geometry must have attribute matching the attributeName argument: ' + attributeName + '.');
        }
        if (geometry.attributes[attributeName].componentDatatype !== ComponentDatatype.DOUBLE) {
            throw new DeveloperError('The attribute componentDatatype must be ComponentDatatype.DOUBLE.');
        }
        
        var attribute = geometry.attributes[attributeName];
        var values = attribute.values;
        var length = values.length;
        var highValues = new Float32Array(length);
        var lowValues = new Float32Array(length);

        for (var i = 0; i < length; ++i) {
            EncodedCartesian3.encode(values[i], encodedResult);
            highValues[i] = encodedResult.high;
            lowValues[i] = encodedResult.low;
        }

        var componentsPerAttribute = attribute.componentsPerAttribute;

        geometry.attributes[attributeHighName] = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : componentsPerAttribute,
            values : highValues
        });
        geometry.attributes[attributeLowName] = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : componentsPerAttribute,
            values : lowValues
        });
        delete geometry.attributes[attributeName];

        return geometry;
    };

    var scratchCartesian3 = new Cartesian3();

    function transformPoint(matrix, attribute) {
        if (defined(attribute)) {
            var values = attribute.values;
            var length = values.length;
            for (var i = 0; i < length; i += 3) {
                Cartesian3.unpack(values, i, scratchCartesian3);
                Matrix4.multiplyByPoint(matrix, scratchCartesian3, scratchCartesian3);
                Cartesian3.pack(scratchCartesian3, values, i);
            }
        }
    }

    function transformVector(matrix, attribute) {
        if (defined(attribute)) {
            var values = attribute.values;
            var length = values.length;
            for (var i = 0; i < length; i += 3) {
                Cartesian3.unpack(values, i, scratchCartesian3);
                Matrix3.multiplyByVector(matrix, scratchCartesian3, scratchCartesian3);
                scratchCartesian3 = Cartesian3.normalize(scratchCartesian3, scratchCartesian3);
                Cartesian3.pack(scratchCartesian3, values, i);
            }
        }
    }

    var inverseTranspose = new Matrix4();
    var normalMatrix = new Matrix3();

    /**
     * Transforms a geometry instance to world coordinates.  This changes
     * the instance's <code>modelMatrix</code> to {@link Matrix4.IDENTITY} and transforms the
     * following attributes if they are present: <code>position</code>, <code>normal</code>,
     * <code>tangent</code>, and <code>bitangent</code>.
     *
     * @param {GeometryInstance} instance The geometry instance to modify.
     * @returns {GeometryInstance} The modified <code>instance</code> argument, with its attributes transforms to world coordinates.
     *
     * @example
     * Cesium.GeometryPipeline.transformToWorldCoordinates(instance);
     */
    GeometryPipeline.transformToWorldCoordinates = function(instance) {
                if (!defined(instance)) {
            throw new DeveloperError('instance is required.');
        }
        
        var modelMatrix = instance.modelMatrix;

        if (Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) {
            // Already in world coordinates
            return instance;
        }

        var attributes = instance.geometry.attributes;

        // Transform attributes in known vertex formats
        transformPoint(modelMatrix, attributes.position);
        transformPoint(modelMatrix, attributes.prevPosition);
        transformPoint(modelMatrix, attributes.nextPosition);

        if ((defined(attributes.normal)) ||
            (defined(attributes.tangent)) ||
            (defined(attributes.bitangent))) {

            Matrix4.inverse(modelMatrix, inverseTranspose);
            Matrix4.transpose(inverseTranspose, inverseTranspose);
            Matrix4.getRotation(inverseTranspose, normalMatrix);

            transformVector(normalMatrix, attributes.normal);
            transformVector(normalMatrix, attributes.tangent);
            transformVector(normalMatrix, attributes.bitangent);
        }

        var boundingSphere = instance.geometry.boundingSphere;

        if (defined(boundingSphere)) {
            instance.geometry.boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, boundingSphere);
        }

        instance.modelMatrix = Matrix4.clone(Matrix4.IDENTITY);

        return instance;
    };

    function findAttributesInAllGeometries(instances, propertyName) {
        var length = instances.length;

        var attributesInAllGeometries = {};

        var attributes0 = instances[0][propertyName].attributes;
        var name;

        for (name in attributes0) {
            if (attributes0.hasOwnProperty(name) &&
                    defined(attributes0[name]) &&
                    defined(attributes0[name].values)) {

                var attribute = attributes0[name];
                var numberOfComponents = attribute.values.length;
                var inAllGeometries = true;

                // Does this same attribute exist in all geometries?
                for (var i = 1; i < length; ++i) {
                    var otherAttribute = instances[i][propertyName].attributes[name];

                    if ((!defined(otherAttribute)) ||
                        (attribute.componentDatatype !== otherAttribute.componentDatatype) ||
                        (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) ||
                        (attribute.normalize !== otherAttribute.normalize)) {

                        inAllGeometries = false;
                        break;
                    }

                    numberOfComponents += otherAttribute.values.length;
                }

                if (inAllGeometries) {
                    attributesInAllGeometries[name] = new GeometryAttribute({
                        componentDatatype : attribute.componentDatatype,
                        componentsPerAttribute : attribute.componentsPerAttribute,
                        normalize : attribute.normalize,
                        values : ComponentDatatype.createTypedArray(attribute.componentDatatype, numberOfComponents)
                    });
                }
            }
        }

        return attributesInAllGeometries;
    }

    var tempScratch = new Cartesian3();

    function combineGeometries(instances, propertyName) {
        var length = instances.length;

        var name;
        var i;
        var j;
        var k;

        var m = instances[0].modelMatrix;
        var haveIndices = (defined(instances[0][propertyName].indices));
        var primitiveType = instances[0][propertyName].primitiveType;

                for (i = 1; i < length; ++i) {
            if (!Matrix4.equals(instances[i].modelMatrix, m)) {
                throw new DeveloperError('All instances must have the same modelMatrix.');
            }
            if ((defined(instances[i][propertyName].indices)) !== haveIndices) {
                throw new DeveloperError('All instance geometries must have an indices or not have one.');
            }
            if (instances[i][propertyName].primitiveType !== primitiveType) {
                throw new DeveloperError('All instance geometries must have the same primitiveType.');
            }
        }
        
        // Find subset of attributes in all geometries
        var attributes = findAttributesInAllGeometries(instances, propertyName);
        var values;
        var sourceValues;
        var sourceValuesLength;

        // Combine attributes from each geometry into a single typed array
        for (name in attributes) {
            if (attributes.hasOwnProperty(name)) {
                values = attributes[name].values;

                k = 0;
                for (i = 0; i < length; ++i) {
                    sourceValues = instances[i][propertyName].attributes[name].values;
                    sourceValuesLength = sourceValues.length;

                    for (j = 0; j < sourceValuesLength; ++j) {
                        values[k++] = sourceValues[j];
                    }
                }
            }
        }

        // Combine index lists
        var indices;

        if (haveIndices) {
            var numberOfIndices = 0;
            for (i = 0; i < length; ++i) {
                numberOfIndices += instances[i][propertyName].indices.length;
            }

            var numberOfVertices = Geometry.computeNumberOfVertices(new Geometry({
                attributes : attributes,
                primitiveType : PrimitiveType.POINTS
            }));
            var destIndices = IndexDatatype.createTypedArray(numberOfVertices, numberOfIndices);

            var destOffset = 0;
            var offset = 0;

            for (i = 0; i < length; ++i) {
                var sourceIndices = instances[i][propertyName].indices;
                var sourceIndicesLen = sourceIndices.length;

                for (k = 0; k < sourceIndicesLen; ++k) {
                    destIndices[destOffset++] = offset + sourceIndices[k];
                }

                offset += Geometry.computeNumberOfVertices(instances[i][propertyName]);
            }

            indices = destIndices;
        }

        // Create bounding sphere that includes all instances
        var center = new Cartesian3();
        var radius = 0.0;
        var bs;

        for (i = 0; i < length; ++i) {
            bs = instances[i][propertyName].boundingSphere;
            if (!defined(bs)) {
                // If any geometries have an undefined bounding sphere, then so does the combined geometry
                center = undefined;
                break;
            }

            Cartesian3.add(bs.center, center, center);
        }

        if (defined(center)) {
            Cartesian3.divideByScalar(center, length, center);

            for (i = 0; i < length; ++i) {
                bs = instances[i][propertyName].boundingSphere;
                var tempRadius = Cartesian3.magnitude(Cartesian3.subtract(bs.center, center, tempScratch)) + bs.radius;

                if (tempRadius > radius) {
                    radius = tempRadius;
                }
            }
        }

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : primitiveType,
            boundingSphere : (defined(center)) ? new BoundingSphere(center, radius) : undefined
        });
    }

    /**
     * Combines geometry from several {@link GeometryInstance} objects into one geometry.
     * This concatenates the attributes, concatenates and adjusts the indices, and creates
     * a bounding sphere encompassing all instances.
     * <p>
     * If the instances do not have the same attributes, a subset of attributes common
     * to all instances is used, and the others are ignored.
     * </p>
     * <p>
     * This is used by {@link Primitive} to efficiently render a large amount of static data.
     * </p>
     *
     * @private
     *
     * @param {GeometryInstance[]} [instances] The array of {@link GeometryInstance} objects whose geometry will be combined.
     * @returns {Geometry} A single geometry created from the provided geometry instances.
     *
     * @exception {DeveloperError} All instances must have the same modelMatrix.
     * @exception {DeveloperError} All instance geometries must have an indices or not have one.
     * @exception {DeveloperError} All instance geometries must have the same primitiveType.
     *
     *
     * @example
     * for (var i = 0; i < instances.length; ++i) {
     *   Cesium.GeometryPipeline.transformToWorldCoordinates(instances[i]);
     * }
     * var geometries = Cesium.GeometryPipeline.combineInstances(instances);
     *
     * @see GeometryPipeline.transformToWorldCoordinates
     */
    GeometryPipeline.combineInstances = function(instances) {
                if ((!defined(instances)) || (instances.length < 1)) {
            throw new DeveloperError('instances is required and must have length greater than zero.');
        }
        
        var instanceGeometry = [];
        var instanceSplitGeometry = [];
        var length = instances.length;
        for (var i = 0; i < length; ++i) {
            var instance = instances[i];
            if (defined(instance.geometry)) {
                instanceGeometry.push(instance);
            } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) {
                instanceSplitGeometry.push(instance);
            }
        }

        var geometries = [];
        if (instanceGeometry.length > 0) {
            geometries.push(combineGeometries(instanceGeometry, 'geometry'));
        }

        if (instanceSplitGeometry.length > 0) {
            geometries.push(combineGeometries(instanceSplitGeometry, 'westHemisphereGeometry'));
            geometries.push(combineGeometries(instanceSplitGeometry, 'eastHemisphereGeometry'));
        }

        return geometries;
    };

    var normal = new Cartesian3();
    var v0 = new Cartesian3();
    var v1 = new Cartesian3();
    var v2 = new Cartesian3();

    /**
     * Computes per-vertex normals for a geometry containing <code>TRIANGLES</code> by averaging the normals of
     * all triangles incident to the vertex.  The result is a new <code>normal</code> attribute added to the geometry.
     * This assumes a counter-clockwise winding order.
     *
     * @param {Geometry} geometry The geometry to modify.
     * @returns {Geometry} The modified <code>geometry</code> argument with the computed <code>normal</code> attribute.
     *
     * @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3.
     * @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}.
     *
     * @example
     * Cesium.GeometryPipeline.computeNormal(geometry);
     */
    GeometryPipeline.computeNormal = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        if (!defined(geometry.attributes.position) || !defined(geometry.attributes.position.values)) {
            throw new DeveloperError('geometry.attributes.position.values is required.');
        }
        if (!defined(geometry.indices)) {
            throw new DeveloperError('geometry.indices is required.');
        }
        if (geometry.indices.length < 2 || geometry.indices.length % 3 !== 0) {
            throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.');
        }
        if (geometry.primitiveType !== PrimitiveType.TRIANGLES) {
            throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.');
        }
        
        var indices = geometry.indices;
        var attributes = geometry.attributes;
        var vertices = attributes.position.values;
        var numVertices = attributes.position.values.length / 3;
        var numIndices = indices.length;
        var normalsPerVertex = new Array(numVertices);
        var normalsPerTriangle = new Array(numIndices / 3);
        var normalIndices = new Array(numIndices);

        for ( var i = 0; i < numVertices; i++) {
            normalsPerVertex[i] = {
                indexOffset : 0,
                count : 0,
                currentCount : 0
            };
        }

        var j = 0;
        for (i = 0; i < numIndices; i += 3) {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];
            var i03 = i0 * 3;
            var i13 = i1 * 3;
            var i23 = i2 * 3;

            v0.x = vertices[i03];
            v0.y = vertices[i03 + 1];
            v0.z = vertices[i03 + 2];
            v1.x = vertices[i13];
            v1.y = vertices[i13 + 1];
            v1.z = vertices[i13 + 2];
            v2.x = vertices[i23];
            v2.y = vertices[i23 + 1];
            v2.z = vertices[i23 + 2];

            normalsPerVertex[i0].count++;
            normalsPerVertex[i1].count++;
            normalsPerVertex[i2].count++;

            Cartesian3.subtract(v1, v0, v1);
            Cartesian3.subtract(v2, v0, v2);
            normalsPerTriangle[j] = Cartesian3.cross(v1, v2, new Cartesian3());
            j++;
        }

        var indexOffset = 0;
        for (i = 0; i < numVertices; i++) {
            normalsPerVertex[i].indexOffset += indexOffset;
            indexOffset += normalsPerVertex[i].count;
        }

        j = 0;
        var vertexNormalData;
        for (i = 0; i < numIndices; i += 3) {
            vertexNormalData = normalsPerVertex[indices[i]];
            var index = vertexNormalData.indexOffset + vertexNormalData.currentCount;
            normalIndices[index] = j;
            vertexNormalData.currentCount++;

            vertexNormalData = normalsPerVertex[indices[i + 1]];
            index = vertexNormalData.indexOffset + vertexNormalData.currentCount;
            normalIndices[index] = j;
            vertexNormalData.currentCount++;

            vertexNormalData = normalsPerVertex[indices[i + 2]];
            index = vertexNormalData.indexOffset + vertexNormalData.currentCount;
            normalIndices[index] = j;
            vertexNormalData.currentCount++;

            j++;
        }

        var normalValues = new Float32Array(numVertices * 3);
        for (i = 0; i < numVertices; i++) {
            var i3 = i * 3;
            vertexNormalData = normalsPerVertex[i];
            Cartesian3.clone(Cartesian3.ZERO, normal);
            if (vertexNormalData.count > 0) {
                for (j = 0; j < vertexNormalData.count; j++) {
                    Cartesian3.add(normal, normalsPerTriangle[normalIndices[vertexNormalData.indexOffset + j]], normal);
                }

                // We can run into an issue where a vertex is used with 2 primitives that have opposite winding order.
                if (Cartesian3.equalsEpsilon(Cartesian3.ZERO, normal, CesiumMath.EPSILON10)) {
                    Cartesian3.clone(normalsPerTriangle[normalIndices[vertexNormalData.indexOffset]], normal);
                }
            }

            // We end up with a zero vector probably because of a degenerate triangle
            if (Cartesian3.equalsEpsilon(Cartesian3.ZERO, normal, CesiumMath.EPSILON10)) {
                // Default to (0,0,1)
                normal.z = 1.0;
            }

            Cartesian3.normalize(normal, normal);
            normalValues[i3] = normal.x;
            normalValues[i3 + 1] = normal.y;
            normalValues[i3 + 2] = normal.z;
        }

        geometry.attributes.normal = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 3,
            values : normalValues
        });

        return geometry;
    };

    var normalScratch = new Cartesian3();
    var normalScale = new Cartesian3();
    var tScratch = new Cartesian3();

    /**
     * Computes per-vertex tangents and bitangents for a geometry containing <code>TRIANGLES</code>.
     * The result is new <code>tangent</code> and <code>bitangent</code> attributes added to the geometry.
     * This assumes a counter-clockwise winding order.
     * <p>
     * Based on <a href="http://www.terathon.com/code/tangent.html">Computing Tangent Space Basis Vectors
     * for an Arbitrary Mesh</a> by Eric Lengyel.
     * </p>
     *
     * @param {Geometry} geometry The geometry to modify.
     * @returns {Geometry} The modified <code>geometry</code> argument with the computed <code>tangent</code> and <code>bitangent</code> attributes.
     *
     * @exception {DeveloperError} geometry.indices length must be greater than 0 and be a multiple of 3.
     * @exception {DeveloperError} geometry.primitiveType must be {@link PrimitiveType.TRIANGLES}.
     *
     * @example
     * Cesium.GeometryPipeline.computeTangentAndBiTangent(geometry);
     */
    GeometryPipeline.computeTangentAndBitangent = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        var attributes = geometry.attributes;
        var indices = geometry.indices;

                if (!defined(attributes.position) || !defined(attributes.position.values)) {
            throw new DeveloperError('geometry.attributes.position.values is required.');
        }
        if (!defined(attributes.normal) || !defined(attributes.normal.values)) {
            throw new DeveloperError('geometry.attributes.normal.values is required.');
        }
        if (!defined(attributes.st) || !defined(attributes.st.values)) {
            throw new DeveloperError('geometry.attributes.st.values is required.');
        }
        if (!defined(indices)) {
            throw new DeveloperError('geometry.indices is required.');
        }
        if (indices.length < 2 || indices.length % 3 !== 0) {
            throw new DeveloperError('geometry.indices length must be greater than 0 and be a multiple of 3.');
        }
        if (geometry.primitiveType !== PrimitiveType.TRIANGLES) {
            throw new DeveloperError('geometry.primitiveType must be PrimitiveType.TRIANGLES.');
        }
        
        var vertices = geometry.attributes.position.values;
        var normals = geometry.attributes.normal.values;
        var st = geometry.attributes.st.values;

        var numVertices = geometry.attributes.position.values.length / 3;
        var numIndices = indices.length;
        var tan1 = new Array(numVertices * 3);

        for ( var i = 0; i < tan1.length; i++) {
            tan1[i] = 0;
        }

        var i03;
        var i13;
        var i23;
        for (i = 0; i < numIndices; i += 3) {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];
            i03 = i0 * 3;
            i13 = i1 * 3;
            i23 = i2 * 3;
            var i02 = i0 * 2;
            var i12 = i1 * 2;
            var i22 = i2 * 2;

            var ux = vertices[i03];
            var uy = vertices[i03 + 1];
            var uz = vertices[i03 + 2];

            var wx = st[i02];
            var wy = st[i02 + 1];
            var t1 = st[i12 + 1] - wy;
            var t2 = st[i22 + 1] - wy;

            var r = 1.0 / ((st[i12] - wx) * t2 - (st[i22] - wx) * t1);
            var sdirx = (t2 * (vertices[i13] - ux) - t1 * (vertices[i23] - ux)) * r;
            var sdiry = (t2 * (vertices[i13 + 1] - uy) - t1 * (vertices[i23 + 1] - uy)) * r;
            var sdirz = (t2 * (vertices[i13 + 2] - uz) - t1 * (vertices[i23 + 2] - uz)) * r;

            tan1[i03] += sdirx;
            tan1[i03 + 1] += sdiry;
            tan1[i03 + 2] += sdirz;

            tan1[i13] += sdirx;
            tan1[i13 + 1] += sdiry;
            tan1[i13 + 2] += sdirz;

            tan1[i23] += sdirx;
            tan1[i23 + 1] += sdiry;
            tan1[i23 + 2] += sdirz;
        }

        var tangentValues = new Float32Array(numVertices * 3);
        var bitangentValues = new Float32Array(numVertices * 3);

        for (i = 0; i < numVertices; i++) {
            i03 = i * 3;
            i13 = i03 + 1;
            i23 = i03 + 2;

            var n = Cartesian3.fromArray(normals, i03, normalScratch);
            var t = Cartesian3.fromArray(tan1, i03, tScratch);
            var scalar = Cartesian3.dot(n, t);
            Cartesian3.multiplyByScalar(n, scalar, normalScale);
            Cartesian3.normalize(Cartesian3.subtract(t, normalScale, t), t);

            tangentValues[i03] = t.x;
            tangentValues[i13] = t.y;
            tangentValues[i23] = t.z;

            Cartesian3.normalize(Cartesian3.cross(n, t, t), t);

            bitangentValues[i03] = t.x;
            bitangentValues[i13] = t.y;
            bitangentValues[i23] = t.z;
        }

        geometry.attributes.tangent = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 3,
            values : tangentValues
        });

        geometry.attributes.bitangent = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 3,
            values : bitangentValues
        });

        return geometry;
    };

    var scratchCartesian2 = new Cartesian2();
    var toEncode1 = new Cartesian3();
    var toEncode2 = new Cartesian3();
    var toEncode3 = new Cartesian3();
    var encodeResult2 = new Cartesian2();
    /**
     * Compresses and packs geometry normal attribute values to save memory.
     *
     * @param {Geometry} geometry The geometry to modify.
     * @returns {Geometry} The modified <code>geometry</code> argument, with its normals compressed and packed.
     *
     * @example
     * geometry = Cesium.GeometryPipeline.compressVertices(geometry);
     */
    GeometryPipeline.compressVertices = function(geometry) {
                if (!defined(geometry)) {
            throw new DeveloperError('geometry is required.');
        }
        
        var extrudeAttribute = geometry.attributes.extrudeDirection;
        var i;
        var numVertices;
        if (defined(extrudeAttribute)) {
            //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes
            var extrudeDirections = extrudeAttribute.values;
            numVertices = extrudeDirections.length / 3.0;
            var compressedDirections = new Float32Array(numVertices * 2);

            var i2 = 0;
            for (i = 0; i < numVertices; ++i) {
                Cartesian3.fromArray(extrudeDirections, i * 3.0, toEncode1);
                if (Cartesian3.equals(toEncode1, Cartesian3.ZERO)) {
                    i2 += 2;
                    continue;
                }
                encodeResult2 = AttributeCompression.octEncodeInRange(toEncode1, 65535, encodeResult2);
                compressedDirections[i2++] = encodeResult2.x;
                compressedDirections[i2++] = encodeResult2.y;
            }

            geometry.attributes.compressedAttributes = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : compressedDirections
            });
            delete geometry.attributes.extrudeDirection;
            return geometry;
        }

        var normalAttribute = geometry.attributes.normal;
        var stAttribute = geometry.attributes.st;

        var hasNormal = defined(normalAttribute);
        var hasSt = defined(stAttribute);
        if (!hasNormal && !hasSt) {
            return geometry;
        }

        var tangentAttribute = geometry.attributes.tangent;
        var bitangentAttribute = geometry.attributes.bitangent;

        var hasTangent = defined(tangentAttribute);
        var hasBitangent = defined(bitangentAttribute);

        var normals;
        var st;
        var tangents;
        var bitangents;

        if (hasNormal) {
            normals = normalAttribute.values;
        }
        if (hasSt) {
            st = stAttribute.values;
        }
        if (hasTangent) {
            tangents = tangentAttribute.values;
        }
        if (hasBitangent) {
            bitangents = bitangentAttribute.values;
        }

        var length = hasNormal ? normals.length : st.length;
        var numComponents = hasNormal ? 3.0 : 2.0;
        numVertices = length / numComponents;

        var compressedLength = numVertices;
        var numCompressedComponents = hasSt && hasNormal ? 2.0 : 1.0;
        numCompressedComponents += hasTangent || hasBitangent ? 1.0 : 0.0;
        compressedLength *= numCompressedComponents;

        var compressedAttributes = new Float32Array(compressedLength);

        var normalIndex = 0;
        for (i = 0; i < numVertices; ++i) {
            if (hasSt) {
                Cartesian2.fromArray(st, i * 2.0, scratchCartesian2);
                compressedAttributes[normalIndex++] = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
            }

            var index = i * 3.0;
            if (hasNormal && defined(tangents) && defined(bitangents)) {
                Cartesian3.fromArray(normals, index, toEncode1);
                Cartesian3.fromArray(tangents, index, toEncode2);
                Cartesian3.fromArray(bitangents, index, toEncode3);

                AttributeCompression.octPack(toEncode1, toEncode2, toEncode3, scratchCartesian2);
                compressedAttributes[normalIndex++] = scratchCartesian2.x;
                compressedAttributes[normalIndex++] = scratchCartesian2.y;
            } else {
                if (hasNormal) {
                    Cartesian3.fromArray(normals, index, toEncode1);
                    compressedAttributes[normalIndex++] = AttributeCompression.octEncodeFloat(toEncode1);
                }

                if (hasTangent) {
                    Cartesian3.fromArray(tangents, index, toEncode1);
                    compressedAttributes[normalIndex++] = AttributeCompression.octEncodeFloat(toEncode1);
                }

                if (hasBitangent) {
                    Cartesian3.fromArray(bitangents, index, toEncode1);
                    compressedAttributes[normalIndex++] = AttributeCompression.octEncodeFloat(toEncode1);
                }
            }
        }

        geometry.attributes.compressedAttributes = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : numCompressedComponents,
            values : compressedAttributes
        });

        if (hasNormal) {
            delete geometry.attributes.normal;
        }
        if (hasSt) {
            delete geometry.attributes.st;
        }
        if (hasBitangent) {
            delete geometry.attributes.bitangent;
        }
        if (hasTangent) {
            delete geometry.attributes.tangent;
        }

        return geometry;
    };

    function indexTriangles(geometry) {
        if (defined(geometry.indices)) {
            return geometry;
        }
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);

                if (numberOfVertices < 3) {
            throw new DeveloperError('The number of vertices must be at least three.');
        }
        if (numberOfVertices % 3 !== 0) {
            throw new DeveloperError('The number of vertices must be a multiple of three.');
        }
        
        var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices);
        for (var i = 0; i < numberOfVertices; ++i) {
            indices[i] = i;
        }

        geometry.indices = indices;
        return geometry;
    }

    function indexTriangleFan(geometry) {
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);

                if (numberOfVertices < 3) {
            throw new DeveloperError('The number of vertices must be at least three.');
        }
        
        var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 2) * 3);
        indices[0] = 1;
        indices[1] = 0;
        indices[2] = 2;

        var indicesIndex = 3;
        for (var i = 3; i < numberOfVertices; ++i) {
            indices[indicesIndex++] = i - 1;
            indices[indicesIndex++] = 0;
            indices[indicesIndex++] = i;
        }

        geometry.indices = indices;
        geometry.primitiveType = PrimitiveType.TRIANGLES;
        return geometry;
    }

    function indexTriangleStrip(geometry) {
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);

                if (numberOfVertices < 3) {
            throw new DeveloperError('The number of vertices must be at least 3.');
        }
        
        var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 2) * 3);
        indices[0] = 0;
        indices[1] = 1;
        indices[2] = 2;

        if (numberOfVertices > 3) {
            indices[3] = 0;
            indices[4] = 2;
            indices[5] = 3;
        }

        var indicesIndex = 6;
        for (var i = 3; i < numberOfVertices - 1; i += 2) {
            indices[indicesIndex++] = i;
            indices[indicesIndex++] = i - 1;
            indices[indicesIndex++] = i + 1;

            if (i + 2 < numberOfVertices) {
                indices[indicesIndex++] = i;
                indices[indicesIndex++] = i + 1;
                indices[indicesIndex++] = i + 2;
            }
        }

        geometry.indices = indices;
        geometry.primitiveType = PrimitiveType.TRIANGLES;
        return geometry;
    }

    function indexLines(geometry) {
        if (defined(geometry.indices)) {
            return geometry;
        }
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);

                if (numberOfVertices < 2) {
            throw new DeveloperError('The number of vertices must be at least two.');
        }
        if (numberOfVertices % 2 !== 0) {
            throw new DeveloperError('The number of vertices must be a multiple of 2.');
        }
        
        var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices);
        for (var i = 0; i < numberOfVertices; ++i) {
            indices[i] = i;
        }

        geometry.indices = indices;
        return geometry;
    }

    function indexLineStrip(geometry) {
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);

                if (numberOfVertices < 2) {
            throw new DeveloperError('The number of vertices must be at least two.');
        }
        
        var indices = IndexDatatype.createTypedArray(numberOfVertices, (numberOfVertices - 1) * 2);
        indices[0] = 0;
        indices[1] = 1;
        var indicesIndex = 2;
        for (var i = 2; i < numberOfVertices; ++i) {
            indices[indicesIndex++] = i - 1;
            indices[indicesIndex++] = i;
        }

        geometry.indices = indices;
        geometry.primitiveType = PrimitiveType.LINES;
        return geometry;
    }

    function indexLineLoop(geometry) {
        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);

                if (numberOfVertices < 2) {
            throw new DeveloperError('The number of vertices must be at least two.');
        }
        
        var indices = IndexDatatype.createTypedArray(numberOfVertices, numberOfVertices * 2);

        indices[0] = 0;
        indices[1] = 1;

        var indicesIndex = 2;
        for (var i = 2; i < numberOfVertices; ++i) {
            indices[indicesIndex++] = i - 1;
            indices[indicesIndex++] = i;
        }

        indices[indicesIndex++] = numberOfVertices - 1;
        indices[indicesIndex] = 0;

        geometry.indices = indices;
        geometry.primitiveType = PrimitiveType.LINES;
        return geometry;
    }

    function indexPrimitive(geometry) {
        switch (geometry.primitiveType) {
        case PrimitiveType.TRIANGLE_FAN:
            return indexTriangleFan(geometry);
        case PrimitiveType.TRIANGLE_STRIP:
            return indexTriangleStrip(geometry);
        case PrimitiveType.TRIANGLES:
            return indexTriangles(geometry);
        case PrimitiveType.LINE_STRIP:
            return indexLineStrip(geometry);
        case PrimitiveType.LINE_LOOP:
            return indexLineLoop(geometry);
        case PrimitiveType.LINES:
            return indexLines(geometry);
        }

        return geometry;
    }

    function offsetPointFromXZPlane(p, isBehind) {
        if (Math.abs(p.y) < CesiumMath.EPSILON6){
            if (isBehind) {
                p.y = -CesiumMath.EPSILON6;
            } else {
                p.y = CesiumMath.EPSILON6;
            }
        }
    }

    function offsetTriangleFromXZPlane(p0, p1, p2) {
        if (p0.y !== 0.0 && p1.y !== 0.0 && p2.y !== 0.0) {
            offsetPointFromXZPlane(p0, p0.y < 0.0);
            offsetPointFromXZPlane(p1, p1.y < 0.0);
            offsetPointFromXZPlane(p2, p2.y < 0.0);
            return;
        }

        var p0y = Math.abs(p0.y);
        var p1y = Math.abs(p1.y);
        var p2y = Math.abs(p2.y);

        var sign;
        if (p0y > p1y) {
            if (p0y > p2y) {
                sign = CesiumMath.sign(p0.y);
            } else {
                sign = CesiumMath.sign(p2.y);
            }
        } else if (p1y > p2y) {
            sign = CesiumMath.sign(p1.y);
        } else {
            sign = CesiumMath.sign(p2.y);
        }

        var isBehind = sign < 0.0;
        offsetPointFromXZPlane(p0, isBehind);
        offsetPointFromXZPlane(p1, isBehind);
        offsetPointFromXZPlane(p2, isBehind);
    }

    var c3 = new Cartesian3();
    function getXZIntersectionOffsetPoints(p, p1, u1, v1) {
        Cartesian3.add(p, Cartesian3.multiplyByScalar(Cartesian3.subtract(p1, p, c3), p.y/(p.y-p1.y), c3), u1);
        Cartesian3.clone(u1, v1);
        offsetPointFromXZPlane(u1, true);
        offsetPointFromXZPlane(v1, false);
    }

    var u1 = new Cartesian3();
    var u2 = new Cartesian3();
    var q1 = new Cartesian3();
    var q2 = new Cartesian3();

    var splitTriangleResult = {
        positions : new Array(7),
        indices : new Array(3 * 3)
    };

    function splitTriangle(p0, p1, p2) {
        // In WGS84 coordinates, for a triangle approximately on the
        // ellipsoid to cross the IDL, first it needs to be on the
        // negative side of the plane x = 0.
        if ((p0.x >= 0.0) || (p1.x >= 0.0) || (p2.x >= 0.0)) {
            return undefined;
        }

        offsetTriangleFromXZPlane(p0, p1, p2);

        var p0Behind = p0.y < 0.0;
        var p1Behind = p1.y < 0.0;
        var p2Behind = p2.y < 0.0;

        var numBehind = 0;
        numBehind += p0Behind ? 1 : 0;
        numBehind += p1Behind ? 1 : 0;
        numBehind += p2Behind ? 1 : 0;

        var indices = splitTriangleResult.indices;

        if (numBehind === 1) {
            indices[1] = 3;
            indices[2] = 4;
            indices[5] = 6;
            indices[7] = 6;
            indices[8] = 5;

            if (p0Behind) {
                getXZIntersectionOffsetPoints(p0, p1, u1, q1);
                getXZIntersectionOffsetPoints(p0, p2, u2, q2);

                indices[0] = 0;
                indices[3] = 1;
                indices[4] = 2;
                indices[6] = 1;
            } else if (p1Behind) {
                getXZIntersectionOffsetPoints(p1, p2, u1, q1);
                getXZIntersectionOffsetPoints(p1, p0, u2, q2);

                indices[0] = 1;
                indices[3] = 2;
                indices[4] = 0;
                indices[6] = 2;
            } else if (p2Behind) {
                getXZIntersectionOffsetPoints(p2, p0, u1, q1);
                getXZIntersectionOffsetPoints(p2, p1, u2, q2);

                indices[0] = 2;
                indices[3] = 0;
                indices[4] = 1;
                indices[6] = 0;
            }
        } else if (numBehind === 2) {
            indices[2] = 4;
            indices[4] = 4;
            indices[5] = 3;
            indices[7] = 5;
            indices[8] = 6;

            if (!p0Behind) {
                getXZIntersectionOffsetPoints(p0, p1, u1, q1);
                getXZIntersectionOffsetPoints(p0, p2, u2, q2);

                indices[0] = 1;
                indices[1] = 2;
                indices[3] = 1;
                indices[6] = 0;
            } else if (!p1Behind) {
                getXZIntersectionOffsetPoints(p1, p2, u1, q1);
                getXZIntersectionOffsetPoints(p1, p0, u2, q2);

                indices[0] = 2;
                indices[1] = 0;
                indices[3] = 2;
                indices[6] = 1;
            } else if (!p2Behind) {
                getXZIntersectionOffsetPoints(p2, p0, u1, q1);
                getXZIntersectionOffsetPoints(p2, p1, u2, q2);

                indices[0] = 0;
                indices[1] = 1;
                indices[3] = 0;
                indices[6] = 2;
            }
        }

        var positions = splitTriangleResult.positions;
        positions[0] = p0;
        positions[1] = p1;
        positions[2] = p2;
        positions.length = 3;

        if (numBehind === 1 || numBehind === 2) {
            positions[3] = u1;
            positions[4] = u2;
            positions[5] = q1;
            positions[6] = q2;
            positions.length = 7;
        }

        return splitTriangleResult;
    }

    function updateGeometryAfterSplit(geometry, computeBoundingSphere) {
        var attributes = geometry.attributes;

        if (attributes.position.values.length === 0) {
            return undefined;
        }

        for (var property in attributes) {
            if (attributes.hasOwnProperty(property) &&
                    defined(attributes[property]) &&
                    defined(attributes[property].values)) {

                var attribute = attributes[property];
                attribute.values = ComponentDatatype.createTypedArray(attribute.componentDatatype, attribute.values);
            }
        }

        var numberOfVertices = Geometry.computeNumberOfVertices(geometry);
        geometry.indices = IndexDatatype.createTypedArray(numberOfVertices, geometry.indices);

        if (computeBoundingSphere) {
            geometry.boundingSphere = BoundingSphere.fromVertices(attributes.position.values);
        }

        return geometry;
    }

    function copyGeometryForSplit(geometry) {
        var attributes = geometry.attributes;
        var copiedAttributes = {};

        for (var property in attributes) {
            if (attributes.hasOwnProperty(property) &&
                    defined(attributes[property]) &&
                    defined(attributes[property].values)) {

                var attribute = attributes[property];
                copiedAttributes[property] = new GeometryAttribute({
                    componentDatatype : attribute.componentDatatype,
                    componentsPerAttribute : attribute.componentsPerAttribute,
                    normalize : attribute.normalize,
                    values : []
                });
            }
        }

        return new Geometry({
            attributes : copiedAttributes,
            indices : [],
            primitiveType : geometry.primitiveType
        });
    }

    function updateInstanceAfterSplit(instance, westGeometry, eastGeometry) {
        var computeBoundingSphere = defined(instance.geometry.boundingSphere);

        westGeometry = updateGeometryAfterSplit(westGeometry, computeBoundingSphere);
        eastGeometry = updateGeometryAfterSplit(eastGeometry, computeBoundingSphere);

        if (defined(eastGeometry) && !defined(westGeometry)) {
            instance.geometry = eastGeometry;
        } else if (!defined(eastGeometry) && defined(westGeometry)) {
            instance.geometry = westGeometry;
        } else {
            instance.westHemisphereGeometry = westGeometry;
            instance.eastHemisphereGeometry = eastGeometry;
            instance.geometry = undefined;
        }
    }

    var p0Scratch = new Cartesian3();
    var p1Scratch = new Cartesian3();
    var p2Scratch = new Cartesian3();
    var barycentricScratch = new Cartesian3();
    var s0Scratch = new Cartesian2();
    var s1Scratch = new Cartesian2();
    var s2Scratch = new Cartesian2();

    function computeTriangleAttributes(i0, i1, i2, point, positions, normals, tangents, bitangents, texCoords, extrudeDirections, currentAttributes, insertedIndex) {
        if (!defined(normals) && !defined(tangents) && !defined(bitangents) && !defined(texCoords) && !defined(extrudeDirections)) {
            return;
        }

        var p0 = Cartesian3.fromArray(positions, i0 * 3, p0Scratch);
        var p1 = Cartesian3.fromArray(positions, i1 * 3, p1Scratch);
        var p2 = Cartesian3.fromArray(positions, i2 * 3, p2Scratch);
        var coords = barycentricCoordinates(point, p0, p1, p2, barycentricScratch);

        if (defined(normals)) {
            var n0 = Cartesian3.fromArray(normals, i0 * 3, p0Scratch);
            var n1 = Cartesian3.fromArray(normals, i1 * 3, p1Scratch);
            var n2 = Cartesian3.fromArray(normals, i2 * 3, p2Scratch);

            Cartesian3.multiplyByScalar(n0, coords.x, n0);
            Cartesian3.multiplyByScalar(n1, coords.y, n1);
            Cartesian3.multiplyByScalar(n2, coords.z, n2);

            var normal = Cartesian3.add(n0, n1, n0);
            Cartesian3.add(normal, n2, normal);
            Cartesian3.normalize(normal, normal);

            Cartesian3.pack(normal, currentAttributes.normal.values, insertedIndex * 3);
        }

        if (defined(extrudeDirections)) {
            var d0 = Cartesian3.fromArray(extrudeDirections, i0 * 3, p0Scratch);
            var d1 = Cartesian3.fromArray(extrudeDirections, i1 * 3, p1Scratch);
            var d2 = Cartesian3.fromArray(extrudeDirections, i2 * 3, p2Scratch);

            Cartesian3.multiplyByScalar(d0, coords.x, d0);
            Cartesian3.multiplyByScalar(d1, coords.y, d1);
            Cartesian3.multiplyByScalar(d2, coords.z, d2);

            var direction;
            if (!Cartesian3.equals(d0, Cartesian3.ZERO) || !Cartesian3.equals(d1, Cartesian3.ZERO) || !Cartesian3.equals(d2, Cartesian3.ZERO)) {
                direction = Cartesian3.add(d0, d1, d0);
                Cartesian3.add(direction, d2, direction);
                Cartesian3.normalize(direction, direction);
            } else {
                direction = p0Scratch;
                direction.x = 0;
                direction.y = 0;
                direction.z = 0;
            }
            Cartesian3.pack(direction, currentAttributes.extrudeDirection.values, insertedIndex * 3);
        }

        if (defined(tangents)) {
            var t0 = Cartesian3.fromArray(tangents, i0 * 3, p0Scratch);
            var t1 = Cartesian3.fromArray(tangents, i1 * 3, p1Scratch);
            var t2 = Cartesian3.fromArray(tangents, i2 * 3, p2Scratch);

            Cartesian3.multiplyByScalar(t0, coords.x, t0);
            Cartesian3.multiplyByScalar(t1, coords.y, t1);
            Cartesian3.multiplyByScalar(t2, coords.z, t2);

            var tangent = Cartesian3.add(t0, t1, t0);
            Cartesian3.add(tangent, t2, tangent);
            Cartesian3.normalize(tangent, tangent);

            Cartesian3.pack(tangent, currentAttributes.tangent.values, insertedIndex * 3);
        }

        if (defined(bitangents)) {
            var b0 = Cartesian3.fromArray(bitangents, i0 * 3, p0Scratch);
            var b1 = Cartesian3.fromArray(bitangents, i1 * 3, p1Scratch);
            var b2 = Cartesian3.fromArray(bitangents, i2 * 3, p2Scratch);

            Cartesian3.multiplyByScalar(b0, coords.x, b0);
            Cartesian3.multiplyByScalar(b1, coords.y, b1);
            Cartesian3.multiplyByScalar(b2, coords.z, b2);

            var bitangent = Cartesian3.add(b0, b1, b0);
            Cartesian3.add(bitangent, b2, bitangent);
            Cartesian3.normalize(bitangent, bitangent);

            Cartesian3.pack(bitangent, currentAttributes.bitangent.values, insertedIndex * 3);
        }

        if (defined(texCoords)) {
            var s0 = Cartesian2.fromArray(texCoords, i0 * 2, s0Scratch);
            var s1 = Cartesian2.fromArray(texCoords, i1 * 2, s1Scratch);
            var s2 = Cartesian2.fromArray(texCoords, i2 * 2, s2Scratch);

            Cartesian2.multiplyByScalar(s0, coords.x, s0);
            Cartesian2.multiplyByScalar(s1, coords.y, s1);
            Cartesian2.multiplyByScalar(s2, coords.z, s2);

            var texCoord = Cartesian2.add(s0, s1, s0);
            Cartesian2.add(texCoord, s2, texCoord);

            Cartesian2.pack(texCoord, currentAttributes.st.values, insertedIndex * 2);
        }
    }

    function insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, currentIndex, point) {
        var insertIndex = currentAttributes.position.values.length / 3;

        if (currentIndex !== -1) {
            var prevIndex = indices[currentIndex];
            var newIndex = currentIndexMap[prevIndex];

            if (newIndex === -1) {
                currentIndexMap[prevIndex] = insertIndex;
                currentAttributes.position.values.push(point.x, point.y, point.z);
                currentIndices.push(insertIndex);
                return insertIndex;
            }

            currentIndices.push(newIndex);
            return newIndex;
        }

        currentAttributes.position.values.push(point.x, point.y, point.z);
        currentIndices.push(insertIndex);
        return insertIndex;
    }

    function splitLongitudeTriangles(instance) {
        var geometry = instance.geometry;
        var attributes = geometry.attributes;
        var positions = attributes.position.values;
        var normals = (defined(attributes.normal)) ? attributes.normal.values : undefined;
        var bitangents = (defined(attributes.bitangent)) ? attributes.bitangent.values : undefined;
        var tangents = (defined(attributes.tangent)) ? attributes.tangent.values : undefined;
        var texCoords = (defined(attributes.st)) ? attributes.st.values : undefined;
        var extrudeDirections = (defined(attributes.extrudeDirection)) ? attributes.extrudeDirection.values : undefined;
        var indices = geometry.indices;

        var eastGeometry = copyGeometryForSplit(geometry);
        var westGeometry = copyGeometryForSplit(geometry);

        var currentAttributes;
        var currentIndices;
        var currentIndexMap;
        var insertedIndex;
        var i;

        var westGeometryIndexMap = [];
        westGeometryIndexMap.length = positions.length / 3;

        var eastGeometryIndexMap = [];
        eastGeometryIndexMap.length = positions.length / 3;

        for (i = 0; i < westGeometryIndexMap.length; ++i) {
            westGeometryIndexMap[i] = -1;
            eastGeometryIndexMap[i] = -1;
        }

        var len = indices.length;
        for (i = 0; i < len; i += 3) {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var p0 = Cartesian3.fromArray(positions, i0 * 3);
            var p1 = Cartesian3.fromArray(positions, i1 * 3);
            var p2 = Cartesian3.fromArray(positions, i2 * 3);

            var result = splitTriangle(p0, p1, p2);
            if (defined(result) && result.positions.length > 3) {
                var resultPositions = result.positions;
                var resultIndices = result.indices;
                var resultLength = resultIndices.length;

                for (var j = 0; j < resultLength; ++j) {
                    var resultIndex = resultIndices[j];
                    var point = resultPositions[resultIndex];

                    if (point.y < 0.0) {
                        currentAttributes = westGeometry.attributes;
                        currentIndices = westGeometry.indices;
                        currentIndexMap = westGeometryIndexMap;
                    } else {
                        currentAttributes = eastGeometry.attributes;
                        currentIndices = eastGeometry.indices;
                        currentIndexMap = eastGeometryIndexMap;
                    }

                    insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, resultIndex < 3 ? i + resultIndex : -1, point);
                    computeTriangleAttributes(i0, i1, i2, point, positions, normals, tangents, bitangents, texCoords, extrudeDirections, currentAttributes, insertedIndex);
                }
            } else {
                if (defined(result)) {
                    p0 = result.positions[0];
                    p1 = result.positions[1];
                    p2 = result.positions[2];
                }

                if (p0.y < 0.0) {
                    currentAttributes = westGeometry.attributes;
                    currentIndices = westGeometry.indices;
                    currentIndexMap = westGeometryIndexMap;
                } else {
                    currentAttributes = eastGeometry.attributes;
                    currentIndices = eastGeometry.indices;
                    currentIndexMap = eastGeometryIndexMap;
                }

                insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i, p0);
                computeTriangleAttributes(i0, i1, i2, p0, positions, normals, tangents, bitangents, texCoords, extrudeDirections, currentAttributes, insertedIndex);

                insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i + 1, p1);
                computeTriangleAttributes(i0, i1, i2, p1, positions, normals, tangents, bitangents, texCoords, extrudeDirections, currentAttributes, insertedIndex);

                insertedIndex = insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i + 2, p2);
                computeTriangleAttributes(i0, i1, i2, p2, positions, normals, tangents, bitangents, texCoords, extrudeDirections, currentAttributes, insertedIndex);
            }
        }

        updateInstanceAfterSplit(instance, westGeometry, eastGeometry);
    }

    var xzPlane = Plane.fromPointNormal(Cartesian3.ZERO, Cartesian3.UNIT_Y);

    var offsetScratch = new Cartesian3();
    var offsetPointScratch = new Cartesian3();

    function splitLongitudeLines(instance) {
        var geometry = instance.geometry;
        var attributes = geometry.attributes;
        var positions = attributes.position.values;
        var indices = geometry.indices;

        var eastGeometry = copyGeometryForSplit(geometry);
        var westGeometry = copyGeometryForSplit(geometry);

        var i;
        var length = indices.length;

        var westGeometryIndexMap = [];
        westGeometryIndexMap.length = positions.length / 3;

        var eastGeometryIndexMap = [];
        eastGeometryIndexMap.length = positions.length / 3;

        for (i = 0; i < westGeometryIndexMap.length; ++i) {
            westGeometryIndexMap[i] = -1;
            eastGeometryIndexMap[i] = -1;
        }

        for (i = 0; i < length; i += 2) {
            var i0 = indices[i];
            var i1 = indices[i + 1];

            var p0 = Cartesian3.fromArray(positions, i0 * 3, p0Scratch);
            var p1 = Cartesian3.fromArray(positions, i1 * 3, p1Scratch);

            if (Math.abs(p0.y) < CesiumMath.EPSILON6){
                if (p0.y < 0.0) {
                    p0.y = -CesiumMath.EPSILON6;
                } else {
                    p0.y = CesiumMath.EPSILON6;
                }
            }

            if (Math.abs(p1.y) < CesiumMath.EPSILON6){
                if (p1.y < 0.0) {
                    p1.y = -CesiumMath.EPSILON6;
                } else {
                    p1.y = CesiumMath.EPSILON6;
                }
            }

            var p0Attributes = eastGeometry.attributes;
            var p0Indices = eastGeometry.indices;
            var p0IndexMap = eastGeometryIndexMap;
            var p1Attributes = westGeometry.attributes;
            var p1Indices = westGeometry.indices;
            var p1IndexMap = westGeometryIndexMap;

            var intersection = IntersectionTests.lineSegmentPlane(p0, p1, xzPlane, p2Scratch);
            if (defined(intersection)) {
                // move point on the xz-plane slightly away from the plane
                var offset = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Y, 5.0 * CesiumMath.EPSILON9, offsetScratch);
                if (p0.y < 0.0) {
                    Cartesian3.negate(offset, offset);

                    p0Attributes = westGeometry.attributes;
                    p0Indices = westGeometry.indices;
                    p0IndexMap = westGeometryIndexMap;
                    p1Attributes = eastGeometry.attributes;
                    p1Indices = eastGeometry.indices;
                    p1IndexMap = eastGeometryIndexMap;
                }

                var offsetPoint = Cartesian3.add(intersection, offset, offsetPointScratch);
                insertSplitPoint(p0Attributes, p0Indices, p0IndexMap, indices, i, p0);
                insertSplitPoint(p0Attributes, p0Indices, p0IndexMap, indices, -1, offsetPoint);

                Cartesian3.negate(offset, offset);
                Cartesian3.add(intersection, offset, offsetPoint);
                insertSplitPoint(p1Attributes, p1Indices, p1IndexMap, indices, -1, offsetPoint);
                insertSplitPoint(p1Attributes, p1Indices, p1IndexMap, indices, i + 1, p1);
            } else {
                var currentAttributes;
                var currentIndices;
                var currentIndexMap;

                if (p0.y < 0.0) {
                    currentAttributes = westGeometry.attributes;
                    currentIndices = westGeometry.indices;
                    currentIndexMap = westGeometryIndexMap;
                } else {
                    currentAttributes = eastGeometry.attributes;
                    currentIndices = eastGeometry.indices;
                    currentIndexMap = eastGeometryIndexMap;
                }

                insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i, p0);
                insertSplitPoint(currentAttributes, currentIndices, currentIndexMap, indices, i + 1, p1);
            }
        }

        updateInstanceAfterSplit(instance, westGeometry, eastGeometry);
    }

    var cartesian2Scratch0 = new Cartesian2();
    var cartesian2Scratch1 = new Cartesian2();

    var cartesian3Scratch0 = new Cartesian3();
    var cartesian3Scratch2 = new Cartesian3();
    var cartesian3Scratch3 = new Cartesian3();
    var cartesian3Scratch4 = new Cartesian3();
    var cartesian3Scratch5 = new Cartesian3();
    var cartesian3Scratch6 = new Cartesian3();

    var cartesian4Scratch0 = new Cartesian4();

    function updateAdjacencyAfterSplit(geometry) {
        var attributes = geometry.attributes;
        var positions = attributes.position.values;
        var prevPositions = attributes.prevPosition.values;
        var nextPositions = attributes.nextPosition.values;

        var length = positions.length;
        for (var j = 0; j < length; j += 3) {
            var position = Cartesian3.unpack(positions, j, cartesian3Scratch0);
            if (position.x > 0.0) {
                continue;
            }

            var prevPosition = Cartesian3.unpack(prevPositions, j, cartesian3Scratch2);
            if ((position.y < 0.0 && prevPosition.y > 0.0) || (position.y > 0.0 && prevPosition.y < 0.0)) {
                if (j - 3 > 0) {
                    prevPositions[j] = positions[j - 3];
                    prevPositions[j + 1] = positions[j - 2];
                    prevPositions[j + 2] = positions[j - 1];
                } else {
                    Cartesian3.pack(position, prevPositions, j);
                }
            }

            var nextPosition = Cartesian3.unpack(nextPositions, j, cartesian3Scratch3);
            if ((position.y < 0.0 && nextPosition.y > 0.0) || (position.y > 0.0 && nextPosition.y < 0.0)) {
                if (j + 3 < length) {
                    nextPositions[j] = positions[j + 3];
                    nextPositions[j + 1] = positions[j + 4];
                    nextPositions[j + 2] = positions[j + 5];
                } else {
                    Cartesian3.pack(position, nextPositions, j);
                }
            }
        }
    }

    var offsetScalar = 5.0 * CesiumMath.EPSILON9;
    var coplanarOffset = CesiumMath.EPSILON6;

    function splitLongitudePolyline(instance) {
        var geometry = instance.geometry;
        var attributes = geometry.attributes;
        var positions = attributes.position.values;
        var prevPositions = attributes.prevPosition.values;
        var nextPositions = attributes.nextPosition.values;
        var expandAndWidths = attributes.expandAndWidth.values;

        var texCoords = (defined(attributes.st)) ? attributes.st.values : undefined;
        var colors = (defined(attributes.color)) ? attributes.color.values : undefined;

        var eastGeometry = copyGeometryForSplit(geometry);
        var westGeometry = copyGeometryForSplit(geometry);

        var i;
        var j;
        var index;

        var intersectionFound = false;

        var length = positions.length / 3;
        for (i = 0; i < length; i += 4) {
            var i0 = i;
            var i2 = i + 2;

            var p0 = Cartesian3.fromArray(positions, i0 * 3, cartesian3Scratch0);
            var p2 = Cartesian3.fromArray(positions, i2 * 3, cartesian3Scratch2);

            // Offset points that are close to the 180 longitude and change the previous/next point
            // to be the same offset point so it can be projected to 2D. There is special handling in the
            // shader for when position == prevPosition || position == nextPosition.
            if (Math.abs(p0.y) < coplanarOffset) {
                p0.y = coplanarOffset * (p2.y < 0.0 ? -1.0 : 1.0);
                positions[i * 3 + 1] = p0.y;
                positions[(i + 1) * 3 + 1] = p0.y;

                for (j = i0 * 3; j < i0 * 3 + 4 * 3; j += 3) {
                    prevPositions[j] = positions[i * 3];
                    prevPositions[j + 1] = positions[i * 3 + 1];
                    prevPositions[j + 2] = positions[i * 3 + 2];
                }
            }

            // Do the same but for when the line crosses 180 longitude in the opposite direction.
            if (Math.abs(p2.y) < coplanarOffset) {
                p2.y = coplanarOffset * (p0.y < 0.0 ? -1.0 : 1.0);
                positions[(i + 2) * 3 + 1] = p2.y;
                positions[(i + 3) * 3 + 1] = p2.y;

                for (j = i0 * 3; j < i0 * 3 + 4 * 3; j += 3) {
                    nextPositions[j] = positions[(i + 2) * 3];
                    nextPositions[j + 1] = positions[(i + 2) * 3 + 1];
                    nextPositions[j + 2] = positions[(i + 2) * 3 + 2];
                }
            }

            var p0Attributes = eastGeometry.attributes;
            var p0Indices = eastGeometry.indices;
            var p2Attributes = westGeometry.attributes;
            var p2Indices = westGeometry.indices;

            var intersection = IntersectionTests.lineSegmentPlane(p0, p2, xzPlane, cartesian3Scratch4);
            if (defined(intersection)) {
                intersectionFound = true;

                // move point on the xz-plane slightly away from the plane
                var offset = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Y, offsetScalar, cartesian3Scratch5);
                if (p0.y < 0.0) {
                    Cartesian3.negate(offset, offset);
                    p0Attributes = westGeometry.attributes;
                    p0Indices = westGeometry.indices;
                    p2Attributes = eastGeometry.attributes;
                    p2Indices = eastGeometry.indices;
                }

                var offsetPoint = Cartesian3.add(intersection, offset, cartesian3Scratch6);
                p0Attributes.position.values.push(p0.x, p0.y, p0.z, p0.x, p0.y, p0.z);
                p0Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p0Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);

                p0Attributes.prevPosition.values.push(prevPositions[i0 * 3], prevPositions[i0 * 3 + 1], prevPositions[i0 * 3 + 2]);
                p0Attributes.prevPosition.values.push(prevPositions[i0 * 3 + 3], prevPositions[i0 * 3 + 4], prevPositions[i0 * 3 + 5]);
                p0Attributes.prevPosition.values.push(p0.x, p0.y, p0.z, p0.x, p0.y, p0.z);

                p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p0Attributes.nextPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);

                Cartesian3.negate(offset, offset);
                Cartesian3.add(intersection, offset, offsetPoint);
                p2Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p2Attributes.position.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p2Attributes.position.values.push(p2.x, p2.y, p2.z, p2.x, p2.y, p2.z);

                p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);
                p2Attributes.prevPosition.values.push(offsetPoint.x, offsetPoint.y, offsetPoint.z);

                p2Attributes.nextPosition.values.push(p2.x, p2.y, p2.z, p2.x, p2.y, p2.z);
                p2Attributes.nextPosition.values.push(nextPositions[i2 * 3], nextPositions[i2 * 3 + 1], nextPositions[i2 * 3 + 2]);
                p2Attributes.nextPosition.values.push(nextPositions[i2 * 3 + 3], nextPositions[i2 * 3 + 4], nextPositions[i2 * 3 + 5]);

                var ew0 = Cartesian2.fromArray(expandAndWidths, i0 * 2, cartesian2Scratch0);
                var width = Math.abs(ew0.y);

                p0Attributes.expandAndWidth.values.push(-1,  width, 1,  width);
                p0Attributes.expandAndWidth.values.push(-1, -width, 1, -width);
                p2Attributes.expandAndWidth.values.push(-1,  width, 1,  width);
                p2Attributes.expandAndWidth.values.push(-1, -width, 1, -width);

                var t = Cartesian3.magnitudeSquared(Cartesian3.subtract(intersection, p0, cartesian3Scratch3));
                t /= Cartesian3.magnitudeSquared(Cartesian3.subtract(p2, p0, cartesian3Scratch3));

                if (defined(colors)) {
                    var c0 = Cartesian4.fromArray(colors, i0 * 4, cartesian4Scratch0);
                    var c2 = Cartesian4.fromArray(colors, i2 * 4, cartesian4Scratch0);

                    var r = CesiumMath.lerp(c0.x, c2.x, t);
                    var g = CesiumMath.lerp(c0.y, c2.y, t);
                    var b = CesiumMath.lerp(c0.z, c2.z, t);
                    var a = CesiumMath.lerp(c0.w, c2.w, t);

                    for (j = i0 * 4; j < i0 * 4 + 2 * 4; ++j) {
                        p0Attributes.color.values.push(colors[j]);
                    }
                    p0Attributes.color.values.push(r, g, b, a);
                    p0Attributes.color.values.push(r, g, b, a);
                    p2Attributes.color.values.push(r, g, b, a);
                    p2Attributes.color.values.push(r, g, b, a);
                    for (j = i2 * 4; j < i2 * 4 + 2 * 4; ++j) {
                        p2Attributes.color.values.push(colors[j]);
                    }
                }

                if (defined(texCoords)) {
                    var s0 = Cartesian2.fromArray(texCoords, i0 * 2, cartesian2Scratch0);
                    var s3 = Cartesian2.fromArray(texCoords, (i + 3) * 2, cartesian2Scratch1);

                    var sx = CesiumMath.lerp(s0.x, s3.x, t);

                    for (j = i0 * 2; j < i0 * 2 + 2 * 2; ++j) {
                        p0Attributes.st.values.push(texCoords[j]);
                    }
                    p0Attributes.st.values.push(sx, s0.y);
                    p0Attributes.st.values.push(sx, s3.y);
                    p2Attributes.st.values.push(sx, s0.y);
                    p2Attributes.st.values.push(sx, s3.y);
                    for (j = i2 * 2; j < i2 * 2 + 2 * 2; ++j) {
                        p2Attributes.st.values.push(texCoords[j]);
                    }
                }

                index = p0Attributes.position.values.length / 3 - 4;
                p0Indices.push(index, index + 2, index + 1);
                p0Indices.push(index + 1, index + 2, index + 3);

                index = p2Attributes.position.values.length / 3 - 4;
                p2Indices.push(index, index + 2, index + 1);
                p2Indices.push(index + 1, index + 2, index + 3);
            } else {
                var currentAttributes;
                var currentIndices;

                if (p0.y < 0.0) {
                    currentAttributes = westGeometry.attributes;
                    currentIndices = westGeometry.indices;
                } else {
                    currentAttributes = eastGeometry.attributes;
                    currentIndices = eastGeometry.indices;
                }

                currentAttributes.position.values.push(p0.x, p0.y, p0.z);
                currentAttributes.position.values.push(p0.x, p0.y, p0.z);
                currentAttributes.position.values.push(p2.x, p2.y, p2.z);
                currentAttributes.position.values.push(p2.x, p2.y, p2.z);

                for (j = i * 3; j < i * 3 + 4 * 3; ++j) {
                    currentAttributes.prevPosition.values.push(prevPositions[j]);
                    currentAttributes.nextPosition.values.push(nextPositions[j]);
                }

                for (j = i * 2; j < i * 2 + 4 * 2; ++j) {
                    currentAttributes.expandAndWidth.values.push(expandAndWidths[j]);
                    if (defined(texCoords)) {
                        currentAttributes.st.values.push(texCoords[j]);
                    }
                }

                if (defined(colors)) {
                    for (j = i * 4; j < i * 4 + 4 * 4; ++j) {
                        currentAttributes.color.values.push(colors[j]);
                    }
                }

                index = currentAttributes.position.values.length / 3 - 4;
                currentIndices.push(index, index + 2, index + 1);
                currentIndices.push(index + 1, index + 2, index + 3);
            }
        }

        if (intersectionFound) {
            updateAdjacencyAfterSplit(westGeometry);
            updateAdjacencyAfterSplit(eastGeometry);
        }

        updateInstanceAfterSplit(instance, westGeometry, eastGeometry);
    }

    /**
     * Splits the instances's geometry, by introducing new vertices and indices,that
     * intersect the International Date Line and Prime Meridian so that no primitives cross longitude
     * -180/180 degrees.  This is not required for 3D drawing, but is required for
     * correcting drawing in 2D and Columbus view.
     *
     * @private
     *
     * @param {GeometryInstance} instance The instance to modify.
     * @returns {GeometryInstance} The modified <code>instance</code> argument, with it's geometry split at the International Date Line.
     *
     * @example
     * instance = Cesium.GeometryPipeline.splitLongitude(instance);
     */
    GeometryPipeline.splitLongitude = function(instance) {
                if (!defined(instance)) {
            throw new DeveloperError('instance is required.');
        }
        
        var geometry = instance.geometry;
        var boundingSphere = geometry.boundingSphere;
        if (defined(boundingSphere)) {
            var minX = boundingSphere.center.x - boundingSphere.radius;
            if (minX > 0 || BoundingSphere.intersectPlane(boundingSphere, Plane.ORIGIN_ZX_PLANE) !== Intersect.INTERSECTING) {
                return instance;
            }
        }

        if (geometry.geometryType !== GeometryType.NONE) {
            switch (geometry.geometryType) {
            case GeometryType.POLYLINES:
                splitLongitudePolyline(instance);
                break;
            case GeometryType.TRIANGLES:
                splitLongitudeTriangles(instance);
                break;
            case GeometryType.LINES:
                splitLongitudeLines(instance);
                break;
            }
        } else {
            indexPrimitive(geometry);
            if (geometry.primitiveType === PrimitiveType.TRIANGLES) {
                splitLongitudeTriangles(instance);
            } else if (geometry.primitiveType === PrimitiveType.LINES) {
                splitLongitudeLines(instance);
            }
        }

        return instance;
    };

    return GeometryPipeline;
});

/*global define*/
define('Core/EllipseGeometry',[
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './Cartographic',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './EllipseGeometryLibrary',
        './Ellipsoid',
        './GeographicProjection',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './GeometryInstance',
        './GeometryPipeline',
        './IndexDatatype',
        './Math',
        './Matrix3',
        './Matrix4',
        './PrimitiveType',
        './Quaternion',
        './Rectangle',
        './Transforms',
        './VertexFormat'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartographic,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        EllipseGeometryLibrary,
        Ellipsoid,
        GeographicProjection,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        GeometryInstance,
        GeometryPipeline,
        IndexDatatype,
        CesiumMath,
        Matrix3,
        Matrix4,
        PrimitiveType,
        Quaternion,
        Rectangle,
        Transforms,
        VertexFormat) {
    'use strict';

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchCartesian4 = new Cartesian3();
    var texCoordScratch = new Cartesian2();
    var textureMatrixScratch = new Matrix3();
    var quaternionScratch = new Quaternion();

    var scratchNormal = new Cartesian3();
    var scratchTangent = new Cartesian3();
    var scratchBitangent = new Cartesian3();

    var scratchCartographic = new Cartographic();
    var projectedCenterScratch = new Cartesian3();

    var scratchMinTexCoord = new Cartesian2();
    var scratchMaxTexCoord = new Cartesian2();

    function computeTopBottomAttributes(positions, options, extrude) {
        var vertexFormat = options.vertexFormat;
        var center = options.center;
        var semiMajorAxis = options.semiMajorAxis;
        var semiMinorAxis = options.semiMinorAxis;
        var ellipsoid = options.ellipsoid;
        var stRotation = options.stRotation;
        var size = (extrude) ? positions.length / 3 * 2 : positions.length / 3;
        var shadowVolume = options.shadowVolume;

        var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined;
        var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(size * 3) : undefined;

        var extrudeNormals = (shadowVolume) ? new Float32Array(size * 3) : undefined;

        var textureCoordIndex = 0;

        // Raise positions to a height above the ellipsoid and compute the
        // texture coordinates, normals, tangents, and bitangents.
        var normal = scratchNormal;
        var tangent = scratchTangent;
        var bitangent = scratchBitangent;

        var projection = new GeographicProjection(ellipsoid);
        var projectedCenter = projection.project(ellipsoid.cartesianToCartographic(center, scratchCartographic), projectedCenterScratch);

        var geodeticNormal = ellipsoid.scaleToGeodeticSurface(center, scratchCartesian1);
        ellipsoid.geodeticSurfaceNormal(geodeticNormal, geodeticNormal);
        var rotation = Quaternion.fromAxisAngle(geodeticNormal, stRotation, quaternionScratch);
        var textureMatrix = Matrix3.fromQuaternion(rotation, textureMatrixScratch);

        var minTexCoord = Cartesian2.fromElements(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, scratchMinTexCoord);
        var maxTexCoord = Cartesian2.fromElements(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, scratchMaxTexCoord);

        var length = positions.length;
        var bottomOffset = (extrude) ? length : 0;
        var stOffset = bottomOffset / 3 * 2;
        for (var i = 0; i < length; i += 3) {
            var i1 = i + 1;
            var i2 = i + 2;
            var position = Cartesian3.fromArray(positions, i, scratchCartesian1);

            if (vertexFormat.st) {
                var rotatedPoint = Matrix3.multiplyByVector(textureMatrix, position, scratchCartesian2);
                var projectedPoint = projection.project(ellipsoid.cartesianToCartographic(rotatedPoint, scratchCartographic), scratchCartesian3);
                Cartesian3.subtract(projectedPoint, projectedCenter, projectedPoint);

                texCoordScratch.x = (projectedPoint.x + semiMajorAxis) / (2.0 * semiMajorAxis);
                texCoordScratch.y = (projectedPoint.y + semiMinorAxis) / (2.0 * semiMinorAxis);

                minTexCoord.x = Math.min(texCoordScratch.x, minTexCoord.x);
                minTexCoord.y = Math.min(texCoordScratch.y, minTexCoord.y);
                maxTexCoord.x = Math.max(texCoordScratch.x, maxTexCoord.x);
                maxTexCoord.y = Math.max(texCoordScratch.y, maxTexCoord.y);

                if (extrude) {
                    textureCoordinates[textureCoordIndex + stOffset] = texCoordScratch.x;
                    textureCoordinates[textureCoordIndex + 1 + stOffset] = texCoordScratch.y;
                }

                textureCoordinates[textureCoordIndex++] = texCoordScratch.x;
                textureCoordinates[textureCoordIndex++] = texCoordScratch.y;
            }

            if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent || shadowVolume) {
                normal = ellipsoid.geodeticSurfaceNormal(position, normal);

                if (shadowVolume) {
                    extrudeNormals[i + bottomOffset] = -normal.x;
                    extrudeNormals[i1 + bottomOffset] = -normal.y;
                    extrudeNormals[i2 + bottomOffset] = -normal.z;
                }

                if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
                    if (vertexFormat.tangent || vertexFormat.bitangent) {
                        tangent = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent), tangent);
                        Matrix3.multiplyByVector(textureMatrix, tangent, tangent);
                    }
                    if (vertexFormat.normal) {
                        normals[i] = normal.x;
                        normals[i1] = normal.y;
                        normals[i2] = normal.z;
                        if (extrude) {
                            normals[i + bottomOffset] = -normal.x;
                            normals[i1 + bottomOffset] = -normal.y;
                            normals[i2 + bottomOffset] = -normal.z;
                        }
                    }

                    if (vertexFormat.tangent) {
                        tangents[i] = tangent.x;
                        tangents[i1] = tangent.y;
                        tangents[i2] = tangent.z;
                        if (extrude) {
                            tangents[i + bottomOffset] = -tangent.x;
                            tangents[i1 + bottomOffset] = -tangent.y;
                            tangents[i2 + bottomOffset] = -tangent.z;
                        }
                    }

                    if (vertexFormat.bitangent) {
                        bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent);
                        bitangents[i ] = bitangent.x;
                        bitangents[i1] = bitangent.y;
                        bitangents[i2] = bitangent.z;
                        if (extrude) {
                            bitangents[i + bottomOffset] = bitangent.x;
                            bitangents[i1 + bottomOffset] = bitangent.y;
                            bitangents[i2 + bottomOffset] = bitangent.z;
                        }
                    }
                }
            }
        }

        if (vertexFormat.st) {
            length = textureCoordinates.length;
            for (var k = 0; k < length; k += 2) {
                textureCoordinates[k] = (textureCoordinates[k] - minTexCoord.x) / (maxTexCoord.x - minTexCoord.x);
                textureCoordinates[k + 1] = (textureCoordinates[k + 1] - minTexCoord.y) / (maxTexCoord.y - minTexCoord.y);
            }
        }

        var attributes = new GeometryAttributes();

        if (vertexFormat.position) {
            var finalPositions = EllipseGeometryLibrary.raisePositionsToHeight(positions, options, extrude);
            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : finalPositions
            });
        }

        if (vertexFormat.st) {
            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : textureCoordinates
            });
        }

        if (vertexFormat.normal) {
            attributes.normal = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : normals
            });
        }

        if (vertexFormat.tangent) {
            attributes.tangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : tangents
            });
        }

        if (vertexFormat.bitangent) {
            attributes.bitangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : bitangents
            });
        }

        if (shadowVolume) {
            attributes.extrudeDirection = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : extrudeNormals
            });
        }

        return attributes;
    }

    function topIndices(numPts) {
        // numTriangles in half = 3 + 8 + 12 + ... = -1 + 4 + (4 + 4) + (4 + 4 + 4) + ... = -1 + 4 * (1 + 2 + 3 + ...)
        //              = -1 + 4 * ((n * ( n + 1)) / 2)
        // total triangles = 2 * numTrangles in half
        // indices = total triangles * 3;
        // Substitute numPts for n above

        var indices = new Array(12 * (numPts * ( numPts + 1)) - 6);
        var indicesIndex = 0;
        var prevIndex;
        var numInterior;
        var positionIndex;
        var i;
        var j;
        // Indices triangles to the 'right' of the north vector

        prevIndex = 0;
        positionIndex = 1;
        for (i = 0; i < 3; i++) {
            indices[indicesIndex++] = positionIndex++;
            indices[indicesIndex++] = prevIndex;
            indices[indicesIndex++] = positionIndex;
        }

        for (i = 2; i < numPts + 1; ++i) {
            positionIndex = i * (i + 1) - 1;
            prevIndex = (i - 1) * i - 1;

            indices[indicesIndex++] = positionIndex++;
            indices[indicesIndex++] = prevIndex;
            indices[indicesIndex++] = positionIndex;

            numInterior = 2 * i;
            for (j = 0; j < numInterior - 1; ++j) {

                indices[indicesIndex++] = positionIndex;
                indices[indicesIndex++] = prevIndex++;
                indices[indicesIndex++] = prevIndex;

                indices[indicesIndex++] = positionIndex++;
                indices[indicesIndex++] = prevIndex;
                indices[indicesIndex++] = positionIndex;
            }

            indices[indicesIndex++] = positionIndex++;
            indices[indicesIndex++] = prevIndex;
            indices[indicesIndex++] = positionIndex;
        }

        // Indices for center column of triangles
        numInterior = numPts * 2;
        ++positionIndex;
        ++prevIndex;
        for (i = 0; i < numInterior - 1; ++i) {
            indices[indicesIndex++] = positionIndex;
            indices[indicesIndex++] = prevIndex++;
            indices[indicesIndex++] = prevIndex;

            indices[indicesIndex++] = positionIndex++;
            indices[indicesIndex++] = prevIndex;
            indices[indicesIndex++] = positionIndex;
        }

        indices[indicesIndex++] = positionIndex;
        indices[indicesIndex++] = prevIndex++;
        indices[indicesIndex++] = prevIndex;

        indices[indicesIndex++] = positionIndex++;
        indices[indicesIndex++] = prevIndex++;
        indices[indicesIndex++] = prevIndex;

        // Reverse the process creating indices to the 'left' of the north vector
        ++prevIndex;
        for (i = numPts - 1; i > 1; --i) {
            indices[indicesIndex++] = prevIndex++;
            indices[indicesIndex++] = prevIndex;
            indices[indicesIndex++] = positionIndex;

            numInterior = 2 * i;
            for (j = 0; j < numInterior - 1; ++j) {
                indices[indicesIndex++] = positionIndex;
                indices[indicesIndex++] = prevIndex++;
                indices[indicesIndex++] = prevIndex;

                indices[indicesIndex++] = positionIndex++;
                indices[indicesIndex++] = prevIndex;
                indices[indicesIndex++] = positionIndex;
            }

            indices[indicesIndex++] = prevIndex++;
            indices[indicesIndex++] = prevIndex++;
            indices[indicesIndex++] = positionIndex++;
        }

        for (i = 0; i < 3; i++) {
            indices[indicesIndex++] = prevIndex++;
            indices[indicesIndex++] = prevIndex;
            indices[indicesIndex++] = positionIndex;
        }
        return indices;
    }

    var boundingSphereCenter = new Cartesian3();

    function computeEllipse(options) {
        var center = options.center;
        boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter);
        boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter);
        var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis);
        var cep = EllipseGeometryLibrary.computeEllipsePositions(options, true, false);
        var positions = cep.positions;
        var numPts = cep.numPts;
        var attributes = computeTopBottomAttributes(positions, options, false);
        var indices = topIndices(numPts);
        indices = IndexDatatype.createTypedArray(positions.length / 3, indices);
        return {
            boundingSphere : boundingSphere,
            attributes : attributes,
            indices : indices
        };
    }

    function computeWallAttributes(positions, options) {
        var vertexFormat = options.vertexFormat;
        var center = options.center;
        var semiMajorAxis = options.semiMajorAxis;
        var semiMinorAxis = options.semiMinorAxis;
        var ellipsoid = options.ellipsoid;
        var height = options.height;
        var extrudedHeight = options.extrudedHeight;
        var stRotation = options.stRotation;
        var size = positions.length / 3 * 2;

        var finalPositions = new Float64Array(size * 3);
        var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined;
        var normals = (vertexFormat.normal) ? new Float32Array(size * 3) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(size * 3) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(size * 3) : undefined;

        var shadowVolume = options.shadowVolume;
        var extrudeNormals = (shadowVolume) ? new Float32Array(size * 3) : undefined;

        var textureCoordIndex = 0;

        // Raise positions to a height above the ellipsoid and compute the
        // texture coordinates, normals, tangents, and bitangents.
        var normal = scratchNormal;
        var tangent = scratchTangent;
        var bitangent = scratchBitangent;

        var projection = new GeographicProjection(ellipsoid);
        var projectedCenter = projection.project(ellipsoid.cartesianToCartographic(center, scratchCartographic), projectedCenterScratch);

        var geodeticNormal = ellipsoid.scaleToGeodeticSurface(center, scratchCartesian1);
        ellipsoid.geodeticSurfaceNormal(geodeticNormal, geodeticNormal);
        var rotation = Quaternion.fromAxisAngle(geodeticNormal, stRotation, quaternionScratch);
        var textureMatrix = Matrix3.fromQuaternion(rotation, textureMatrixScratch);

        var minTexCoord = Cartesian2.fromElements(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, scratchMinTexCoord);
        var maxTexCoord = Cartesian2.fromElements(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, scratchMaxTexCoord);

        var length = positions.length;
        var stOffset = length / 3 * 2;
        for (var i = 0; i < length; i += 3) {
            var i1 = i + 1;
            var i2 = i + 2;
            var position = Cartesian3.fromArray(positions, i, scratchCartesian1);
            var extrudedPosition;

            if (vertexFormat.st) {
                var rotatedPoint = Matrix3.multiplyByVector(textureMatrix, position, scratchCartesian2);
                var projectedPoint = projection.project(ellipsoid.cartesianToCartographic(rotatedPoint, scratchCartographic), scratchCartesian3);
                Cartesian3.subtract(projectedPoint, projectedCenter, projectedPoint);

                texCoordScratch.x = (projectedPoint.x + semiMajorAxis) / (2.0 * semiMajorAxis);
                texCoordScratch.y = (projectedPoint.y + semiMinorAxis) / (2.0 * semiMinorAxis);

                minTexCoord.x = Math.min(texCoordScratch.x, minTexCoord.x);
                minTexCoord.y = Math.min(texCoordScratch.y, minTexCoord.y);
                maxTexCoord.x = Math.max(texCoordScratch.x, maxTexCoord.x);
                maxTexCoord.y = Math.max(texCoordScratch.y, maxTexCoord.y);

                textureCoordinates[textureCoordIndex + stOffset] = texCoordScratch.x;
                textureCoordinates[textureCoordIndex + 1 + stOffset] = texCoordScratch.y;

                textureCoordinates[textureCoordIndex++] = texCoordScratch.x;
                textureCoordinates[textureCoordIndex++] = texCoordScratch.y;
            }

            position = ellipsoid.scaleToGeodeticSurface(position, position);
            extrudedPosition = Cartesian3.clone(position, scratchCartesian2);
            normal = ellipsoid.geodeticSurfaceNormal(position, normal);

            if (shadowVolume) {
                extrudeNormals[i + length] = -normal.x;
                extrudeNormals[i1 + length] = -normal.y;
                extrudeNormals[i2 + length] = -normal.z;
            }

            var scaledNormal = Cartesian3.multiplyByScalar(normal, height, scratchCartesian4);
            position = Cartesian3.add(position, scaledNormal, position);
            scaledNormal = Cartesian3.multiplyByScalar(normal, extrudedHeight, scaledNormal);
            extrudedPosition = Cartesian3.add(extrudedPosition, scaledNormal, extrudedPosition);

            if (vertexFormat.position) {
                finalPositions[i + length] = extrudedPosition.x;
                finalPositions[i1 + length] = extrudedPosition.y;
                finalPositions[i2 + length] = extrudedPosition.z;

                finalPositions[i] = position.x;
                finalPositions[i1] = position.y;
                finalPositions[i2] = position.z;
            }

            if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {

                bitangent = Cartesian3.clone(normal, bitangent);
                var next = Cartesian3.fromArray(positions, (i + 3) % length, scratchCartesian4);
                Cartesian3.subtract(next, position, next);
                var bottom = Cartesian3.subtract(extrudedPosition, position, scratchCartesian3);

                normal = Cartesian3.normalize(Cartesian3.cross(bottom, next, normal), normal);

                if (vertexFormat.normal) {
                    normals[i] = normal.x;
                    normals[i1] = normal.y;
                    normals[i2] = normal.z;

                    normals[i + length] = normal.x;
                    normals[i1 + length] = normal.y;
                    normals[i2 + length] = normal.z;
                }

                if (vertexFormat.tangent) {
                    tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent);
                    tangents[i] = tangent.x;
                    tangents[i1] = tangent.y;
                    tangents[i2] = tangent.z;

                    tangents[i + length] = tangent.x;
                    tangents[i + 1 + length] = tangent.y;
                    tangents[i + 2 + length] = tangent.z;
                }

                if (vertexFormat.bitangent) {
                    bitangents[i ] = bitangent.x;
                    bitangents[i1] = bitangent.y;
                    bitangents[i2] = bitangent.z;

                    bitangents[i + length] = bitangent.x;
                    bitangents[i1 + length] = bitangent.y;
                    bitangents[i2 + length] = bitangent.z;
                }
            }
        }

        if (vertexFormat.st) {
            length = textureCoordinates.length;
            for (var k = 0; k < length; k += 2) {
                textureCoordinates[k] = (textureCoordinates[k] - minTexCoord.x) / (maxTexCoord.x - minTexCoord.x);
                textureCoordinates[k + 1] = (textureCoordinates[k + 1] - minTexCoord.y) / (maxTexCoord.y - minTexCoord.y);
            }
        }

        var attributes = new GeometryAttributes();

        if (vertexFormat.position) {
            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : finalPositions
            });
        }

        if (vertexFormat.st) {
            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : textureCoordinates
            });
        }

        if (vertexFormat.normal) {
            attributes.normal = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : normals
            });
        }

        if (vertexFormat.tangent) {
            attributes.tangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : tangents
            });
        }

        if (vertexFormat.bitangent) {
            attributes.bitangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : bitangents
            });
        }

        if (shadowVolume) {
            attributes.extrudeDirection = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : extrudeNormals
            });
        }

        return attributes;
    }

    function computeWallIndices(positions) {
        var length = positions.length / 3;
        var indices = IndexDatatype.createTypedArray(length, length * 6);
        var index = 0;
        for (var i = 0; i < length; i++) {
            var UL = i;
            var LL = i + length;
            var UR = (UL + 1) % length;
            var LR = UR + length;
            indices[index++] = UL;
            indices[index++] = LL;
            indices[index++] = UR;
            indices[index++] = UR;
            indices[index++] = LL;
            indices[index++] = LR;
        }

        return indices;
    }

    var topBoundingSphere = new BoundingSphere();
    var bottomBoundingSphere = new BoundingSphere();

    function computeExtrudedEllipse(options) {
        var center = options.center;
        var ellipsoid = options.ellipsoid;
        var semiMajorAxis = options.semiMajorAxis;
        var scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scratchCartesian1), options.height, scratchCartesian1);
        topBoundingSphere.center = Cartesian3.add(center, scaledNormal, topBoundingSphere.center);
        topBoundingSphere.radius = semiMajorAxis;

        scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scaledNormal), options.extrudedHeight, scaledNormal);
        bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center);
        bottomBoundingSphere.radius = semiMajorAxis;

        var cep = EllipseGeometryLibrary.computeEllipsePositions(options, true, true);
        var positions = cep.positions;
        var numPts = cep.numPts;
        var outerPositions = cep.outerPositions;
        var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere);
        var topBottomAttributes = computeTopBottomAttributes(positions, options, true);
        var indices = topIndices(numPts);
        var length = indices.length;
        indices.length = length * 2;
        var posLength = positions.length / 3;
        for (var i = 0; i < length; i += 3) {
            indices[i + length] = indices[i + 2] + posLength;
            indices[i + 1 + length] = indices[i + 1] + posLength;
            indices[i + 2 + length] = indices[i] + posLength;
        }

        var topBottomIndices = IndexDatatype.createTypedArray(posLength * 2 / 3, indices);

        var topBottomGeo = new Geometry({
            attributes : topBottomAttributes,
            indices : topBottomIndices,
            primitiveType : PrimitiveType.TRIANGLES
        });

        var wallAttributes = computeWallAttributes(outerPositions, options);
        indices = computeWallIndices(outerPositions);
        var wallIndices = IndexDatatype.createTypedArray(outerPositions.length * 2 / 3, indices);

        var wallGeo = new Geometry({
            attributes : wallAttributes,
            indices : wallIndices,
            primitiveType : PrimitiveType.TRIANGLES
        });

        var geo = GeometryPipeline.combineInstances([
            new GeometryInstance({
                geometry : topBottomGeo
            }),
            new GeometryInstance({
                geometry : wallGeo
            })
        ]);

        return {
            boundingSphere : boundingSphere,
            attributes : geo[0].attributes,
            indices : geo[0].indices
        };
    }

    var scratchEnuToFixedMatrix = new Matrix4();
    var scratchFixedToEnuMatrix = new Matrix4();
    var scratchRotationMatrix = new Matrix3();
    var scratchRectanglePoints = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
    var scratchCartographicPoints = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];

    function computeRectangle(center, ellipsoid, semiMajorAxis, semiMinorAxis, rotation) {
        Transforms.eastNorthUpToFixedFrame(center, ellipsoid, scratchEnuToFixedMatrix);
        Matrix4.inverseTransformation(scratchEnuToFixedMatrix, scratchFixedToEnuMatrix);

        // Find the 4 extreme points of the ellipse in ENU
        for (var i = 0; i < 4; ++i) {
            Cartesian3.clone(Cartesian3.ZERO, scratchRectanglePoints[i]);
        }
        scratchRectanglePoints[0].x += semiMajorAxis;
        scratchRectanglePoints[1].x -= semiMajorAxis;
        scratchRectanglePoints[2].y += semiMinorAxis;
        scratchRectanglePoints[3].y -= semiMinorAxis;

        Matrix3.fromRotationZ(rotation, scratchRotationMatrix);
        for (i = 0; i < 4; ++i) {
            // Apply the rotation
            Matrix3.multiplyByVector(scratchRotationMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]);

            // Convert back to fixed and then to cartographic
            Matrix4.multiplyByPoint(scratchEnuToFixedMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]);
            ellipsoid.cartesianToCartographic(scratchRectanglePoints[i], scratchCartographicPoints[i]);
        }

        return Rectangle.fromCartographicArray(scratchCartographicPoints);
    }

    /**
     * A description of an ellipse on an ellipsoid. Ellipse geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
     *
     * @alias EllipseGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.center The ellipse's center point in the fixed frame.
     * @param {Number} options.semiMajorAxis The length of the ellipse's semi-major axis in meters.
     * @param {Number} options.semiMinorAxis The length of the ellipse's semi-minor axis in meters.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the ellipse will be on.
     * @param {Number} [options.height=0.0] The distance in meters between the ellipse and the ellipsoid surface.
     * @param {Number} [options.extrudedHeight] The distance in meters between the ellipse's extruded face and the ellipsoid surface.
     * @param {Number} [options.rotation=0.0] The angle of rotation counter-clockwise from north.
     * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates counter-clockwise from north.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The angular distance between points on the ellipse in radians.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     *
     * @exception {DeveloperError} semiMajorAxis and semiMinorAxis must be greater than zero.
     * @exception {DeveloperError} semiMajorAxis must be greater than or equal to the semiMinorAxis.
     * @exception {DeveloperError} granularity must be greater than zero.
     *
     *
     * @example
     * // Create an ellipse.
     * var ellipse = new Cesium.EllipseGeometry({
     *   center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
     *   semiMajorAxis : 500000.0,
     *   semiMinorAxis : 300000.0,
     *   rotation : Cesium.Math.toRadians(60.0)
     * });
     * var geometry = Cesium.EllipseGeometry.createGeometry(ellipse);
     *
     * @see EllipseGeometry.createGeometry
     */
    function EllipseGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var center = options.center;
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        var semiMajorAxis = options.semiMajorAxis;
        var semiMinorAxis = options.semiMinorAxis;
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var height = defaultValue(options.height, 0.0);
        var extrudedHeight = options.extrudedHeight;
        var extrude = (defined(extrudedHeight) && Math.abs(height - extrudedHeight) > 1.0);
        var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);

                if (!defined(center)) {
            throw new DeveloperError('center is required.');
        }
        if (!defined(semiMajorAxis)) {
            throw new DeveloperError('semiMajorAxis is required.');
        }
        if (!defined(semiMinorAxis)) {
            throw new DeveloperError('semiMinorAxis is required.');
        }
        if (semiMajorAxis < semiMinorAxis) {
            throw new DeveloperError('semiMajorAxis must be greater than or equal to the semiMinorAxis.');
        }
        if (granularity <= 0.0) {
            throw new DeveloperError('granularity must be greater than zero.');
        }
        
        this._center = Cartesian3.clone(center);
        this._semiMajorAxis = semiMajorAxis;
        this._semiMinorAxis = semiMinorAxis;
        this._ellipsoid = Ellipsoid.clone(ellipsoid);
        this._rotation = defaultValue(options.rotation, 0.0);
        this._stRotation = defaultValue(options.stRotation, 0.0);
        this._height = height;
        this._granularity = granularity;
        this._vertexFormat = VertexFormat.clone(vertexFormat);
        this._extrudedHeight = defaultValue(extrudedHeight, height);
        this._extrude = extrude;
        this._shadowVolume = defaultValue(options.shadowVolume, false);
        this._workerName = 'createEllipseGeometry';

        this._rectangle = computeRectangle(this._center, this._ellipsoid, semiMajorAxis, semiMinorAxis, this._rotation);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    EllipseGeometry.packedLength = Cartesian3.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 9;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {EllipseGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    EllipseGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._center, array, startingIndex);
        startingIndex += Cartesian3.packedLength;

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        Rectangle.pack(value._rectangle, array, startingIndex);
        startingIndex += Rectangle.packedLength;

        array[startingIndex++] = value._semiMajorAxis;
        array[startingIndex++] = value._semiMinorAxis;
        array[startingIndex++] = value._rotation;
        array[startingIndex++] = value._stRotation;
        array[startingIndex++] = value._height;
        array[startingIndex++] = value._granularity;
        array[startingIndex++] = value._extrudedHeight;
        array[startingIndex++] = value._extrude ? 1.0 : 0.0;
        array[startingIndex] = value._shadowVolume ? 1.0 : 0.0;

        return array;
    };

    var scratchCenter = new Cartesian3();
    var scratchEllipsoid = new Ellipsoid();
    var scratchVertexFormat = new VertexFormat();
    var scratchRectangle = new Rectangle();
    var scratchOptions = {
        center : scratchCenter,
        ellipsoid : scratchEllipsoid,
        vertexFormat : scratchVertexFormat,
        semiMajorAxis : undefined,
        semiMinorAxis : undefined,
        rotation : undefined,
        stRotation : undefined,
        height : undefined,
        granularity : undefined,
        extrudedHeight : undefined,
        shadowVolume: undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {EllipseGeometry} [result] The object into which to store the result.
     * @returns {EllipseGeometry} The modified result parameter or a new EllipseGeometry instance if one was not provided.
     */
    EllipseGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var center = Cartesian3.unpack(array, startingIndex, scratchCenter);
        startingIndex += Cartesian3.packedLength;

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
        startingIndex += Rectangle.packedLength;

        var semiMajorAxis = array[startingIndex++];
        var semiMinorAxis = array[startingIndex++];
        var rotation = array[startingIndex++];
        var stRotation = array[startingIndex++];
        var height = array[startingIndex++];
        var granularity = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var extrude = array[startingIndex++] === 1.0;
        var shadowVolume = array[startingIndex] === 1.0;

        if (!defined(result)) {
            scratchOptions.height = height;
            scratchOptions.extrudedHeight = extrudedHeight;
            scratchOptions.granularity = granularity;
            scratchOptions.stRotation = stRotation;
            scratchOptions.rotation = rotation;
            scratchOptions.semiMajorAxis = semiMajorAxis;
            scratchOptions.semiMinorAxis = semiMinorAxis;
            scratchOptions.shadowVolume = shadowVolume;
            return new EllipseGeometry(scratchOptions);
        }

        result._center = Cartesian3.clone(center, result._center);
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._semiMajorAxis = semiMajorAxis;
        result._semiMinorAxis = semiMinorAxis;
        result._rotation = rotation;
        result._stRotation = stRotation;
        result._height = height;
        result._granularity = granularity;
        result._extrudedHeight = extrudedHeight;
        result._extrude = extrude;
        result._shadowVolume = shadowVolume;
        result._rectangle = Rectangle.clone(rectangle);

        return result;
    };

    /**
     * Computes the geometric representation of a ellipse on an ellipsoid, including its vertices, indices, and a bounding sphere.
     *
     * @param {EllipseGeometry} ellipseGeometry A description of the ellipse.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    EllipseGeometry.createGeometry = function(ellipseGeometry) {
        if ((ellipseGeometry._semiMajorAxis <= 0.0) || (ellipseGeometry._semiMinorAxis <= 0.0)) {
            return;
        }

        ellipseGeometry._center = ellipseGeometry._ellipsoid.scaleToGeodeticSurface(ellipseGeometry._center, ellipseGeometry._center);
        var options = {
            center : ellipseGeometry._center,
            semiMajorAxis : ellipseGeometry._semiMajorAxis,
            semiMinorAxis : ellipseGeometry._semiMinorAxis,
            ellipsoid : ellipseGeometry._ellipsoid,
            rotation : ellipseGeometry._rotation,
            height : ellipseGeometry._height,
            extrudedHeight : ellipseGeometry._extrudedHeight,
            granularity : ellipseGeometry._granularity,
            vertexFormat : ellipseGeometry._vertexFormat,
            stRotation : ellipseGeometry._stRotation
        };
        var geometry;
        if (ellipseGeometry._extrude) {
            options.extrudedHeight = Math.min(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
            options.height = Math.max(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
            options.shadowVolume = ellipseGeometry._shadowVolume;
            geometry = computeExtrudedEllipse(options);
        } else {
            geometry = computeEllipse(options);
        }

        return new Geometry({
            attributes : geometry.attributes,
            indices : geometry.indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : geometry.boundingSphere
        });
    };

    /**
     * @private
     */
    EllipseGeometry.createShadowVolume = function(ellipseGeometry, minHeightFunc, maxHeightFunc) {
        var granularity = ellipseGeometry._granularity;
        var ellipsoid = ellipseGeometry._ellipsoid;

        var minHeight = minHeightFunc(granularity, ellipsoid);
        var maxHeight = maxHeightFunc(granularity, ellipsoid);

        return new EllipseGeometry({
            center : ellipseGeometry._center,
            semiMajorAxis : ellipseGeometry._semiMajorAxis,
            semiMinorAxis : ellipseGeometry._semiMinorAxis,
            ellipsoid : ellipsoid,
            rotation : ellipseGeometry._rotation,
            stRotation : ellipseGeometry._stRotation,
            granularity : granularity,
            extrudedHeight : minHeight,
            height : maxHeight,
            vertexFormat : VertexFormat.POSITION_ONLY,
            shadowVolume: true
        });
    };

    defineProperties(EllipseGeometry.prototype, {
        /**
         * @private
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        }
    });

    return EllipseGeometry;
});

/*global define*/
define('Core/CircleGeometry',[
        './Cartesian3',
        './Check',
        './defaultValue',
        './defined',
        './defineProperties',
        './EllipseGeometry',
        './Ellipsoid',
        './VertexFormat'
    ], function(
        Cartesian3,
        Check,
        defaultValue,
        defined,
        defineProperties,
        EllipseGeometry,
        Ellipsoid,
        VertexFormat) {
    'use strict';

    /**
     * A description of a circle on the ellipsoid. Circle geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
     *
     * @alias CircleGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.center The circle's center point in the fixed frame.
     * @param {Number} options.radius The radius in meters.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the circle will be on.
     * @param {Number} [options.height=0.0] The distance in meters between the circle and the ellipsoid surface.
     * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {Number} [options.extrudedHeight=0.0] The distance in meters between the circle's extruded face and the ellipsoid surface.
     * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
     *
     * @exception {DeveloperError} radius must be greater than zero.
     * @exception {DeveloperError} granularity must be greater than zero.
     *
     * @see CircleGeometry.createGeometry
     * @see Packable
     *
     * @example
     * // Create a circle.
     * var circle = new Cesium.CircleGeometry({
     *   center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
     *   radius : 100000.0
     * });
     * var geometry = Cesium.CircleGeometry.createGeometry(circle);
     */
    function CircleGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var radius = options.radius;

                Check.typeOf.number('radius', radius);
        
        var ellipseGeometryOptions = {
            center : options.center,
            semiMajorAxis : radius,
            semiMinorAxis : radius,
            ellipsoid : options.ellipsoid,
            height : options.height,
            extrudedHeight : options.extrudedHeight,
            granularity : options.granularity,
            vertexFormat : options.vertexFormat,
            stRotation : options.stRotation,
            shadowVolume: options.shadowVolume
        };
        this._ellipseGeometry = new EllipseGeometry(ellipseGeometryOptions);
        this._workerName = 'createCircleGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    CircleGeometry.packedLength = EllipseGeometry.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {CircleGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    CircleGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
                return EllipseGeometry.pack(value._ellipseGeometry, array, startingIndex);
    };

    var scratchEllipseGeometry = new EllipseGeometry({
        center : new Cartesian3(),
        semiMajorAxis : 1.0,
        semiMinorAxis : 1.0
    });
    var scratchOptions = {
        center : new Cartesian3(),
        radius : undefined,
        ellipsoid : Ellipsoid.clone(Ellipsoid.UNIT_SPHERE),
        height : undefined,
        extrudedHeight : undefined,
        granularity : undefined,
        vertexFormat : new VertexFormat(),
        stRotation : undefined,
        semiMajorAxis : undefined,
        semiMinorAxis : undefined,
        shadowVolume: undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {CircleGeometry} [result] The object into which to store the result.
     * @returns {CircleGeometry} The modified result parameter or a new CircleGeometry instance if one was not provided.
     */
    CircleGeometry.unpack = function(array, startingIndex, result) {
        var ellipseGeometry = EllipseGeometry.unpack(array, startingIndex, scratchEllipseGeometry);
        scratchOptions.center = Cartesian3.clone(ellipseGeometry._center, scratchOptions.center);
        scratchOptions.ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid, scratchOptions.ellipsoid);
        scratchOptions.height = ellipseGeometry._height;
        scratchOptions.extrudedHeight = ellipseGeometry._extrudedHeight;
        scratchOptions.granularity = ellipseGeometry._granularity;
        scratchOptions.vertexFormat = VertexFormat.clone(ellipseGeometry._vertexFormat, scratchOptions.vertexFormat);
        scratchOptions.stRotation = ellipseGeometry._stRotation;
        scratchOptions.shadowVolume = ellipseGeometry._shadowVolume;

        if (!defined(result)) {
            scratchOptions.radius = ellipseGeometry._semiMajorAxis;
            return new CircleGeometry(scratchOptions);
        }

        scratchOptions.semiMajorAxis = ellipseGeometry._semiMajorAxis;
        scratchOptions.semiMinorAxis = ellipseGeometry._semiMinorAxis;
        result._ellipseGeometry = new EllipseGeometry(scratchOptions);
        return result;
    };

    /**
     * Computes the geometric representation of a circle on an ellipsoid, including its vertices, indices, and a bounding sphere.
     *
     * @param {CircleGeometry} circleGeometry A description of the circle.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    CircleGeometry.createGeometry = function(circleGeometry) {
        return EllipseGeometry.createGeometry(circleGeometry._ellipseGeometry);
    };

    /**
     * @private
     */
    CircleGeometry.createShadowVolume = function(circleGeometry, minHeightFunc, maxHeightFunc) {
        var granularity = circleGeometry._ellipseGeometry._granularity;
        var ellipsoid = circleGeometry._ellipseGeometry._ellipsoid;

        var minHeight = minHeightFunc(granularity, ellipsoid);
        var maxHeight = maxHeightFunc(granularity, ellipsoid);

        return new CircleGeometry({
            center : circleGeometry._ellipseGeometry._center,
            radius : circleGeometry._ellipseGeometry._semiMajorAxis,
            ellipsoid : ellipsoid,
            stRotation : circleGeometry._ellipseGeometry._stRotation,
            granularity : granularity,
            extrudedHeight : minHeight,
            height : maxHeight,
            vertexFormat : VertexFormat.POSITION_ONLY,
            shadowVolume: true
        });
    };

    defineProperties(CircleGeometry.prototype, {
        /**
         * @private
         */
        rectangle : {
            get : function() {
                return this._ellipseGeometry.rectangle;
            }
        }
    });

    return CircleGeometry;
});

/*global define*/
define('Core/EllipseOutlineGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './EllipseGeometryLibrary',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PrimitiveType'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        EllipseGeometryLibrary,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PrimitiveType) {
    'use strict';

    var scratchCartesian1 = new Cartesian3();
    var boundingSphereCenter = new Cartesian3();

    function computeEllipse(options) {
        var center = options.center;
        boundingSphereCenter = Cartesian3.multiplyByScalar(options.ellipsoid.geodeticSurfaceNormal(center, boundingSphereCenter), options.height, boundingSphereCenter);
        boundingSphereCenter = Cartesian3.add(center, boundingSphereCenter, boundingSphereCenter);
        var boundingSphere = new BoundingSphere(boundingSphereCenter, options.semiMajorAxis);
        var positions = EllipseGeometryLibrary.computeEllipsePositions(options, false, true).outerPositions;

        var attributes = new GeometryAttributes({
            position: new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : EllipseGeometryLibrary.raisePositionsToHeight(positions, options, false)
            })
        });

        var length = positions.length / 3;
        var indices = IndexDatatype.createTypedArray(length, length * 2);
        var index = 0;
        for ( var i = 0; i < length; ++i) {
            indices[index++] = i;
            indices[index++] = (i + 1) % length;
        }

        return {
            boundingSphere : boundingSphere,
            attributes : attributes,
            indices : indices
        };
    }

    var topBoundingSphere = new BoundingSphere();
    var bottomBoundingSphere = new BoundingSphere();
    function computeExtrudedEllipse(options) {
        var center = options.center;
        var ellipsoid = options.ellipsoid;
        var semiMajorAxis = options.semiMajorAxis;
        var scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scratchCartesian1), options.height, scratchCartesian1);
        topBoundingSphere.center = Cartesian3.add(center, scaledNormal, topBoundingSphere.center);
        topBoundingSphere.radius = semiMajorAxis;

        scaledNormal = Cartesian3.multiplyByScalar(ellipsoid.geodeticSurfaceNormal(center, scaledNormal), options.extrudedHeight, scaledNormal);
        bottomBoundingSphere.center = Cartesian3.add(center, scaledNormal, bottomBoundingSphere.center);
        bottomBoundingSphere.radius = semiMajorAxis;

        var positions = EllipseGeometryLibrary.computeEllipsePositions(options, false, true).outerPositions;
        var attributes = new GeometryAttributes({
            position: new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : EllipseGeometryLibrary.raisePositionsToHeight(positions, options, true)
            })
        });

        positions = attributes.position.values;
        var boundingSphere = BoundingSphere.union(topBoundingSphere, bottomBoundingSphere);
        var length = positions.length/3;
        var numberOfVerticalLines = defaultValue(options.numberOfVerticalLines, 16);
        numberOfVerticalLines = CesiumMath.clamp(numberOfVerticalLines, 0, length/2);

        var indices = IndexDatatype.createTypedArray(length, length * 2 + numberOfVerticalLines * 2);

        length /= 2;
        var index = 0;
        var i;
        for (i = 0; i < length; ++i) {
            indices[index++] = i;
            indices[index++] = (i + 1) % length;
            indices[index++] = i + length;
            indices[index++] = ((i + 1) % length) + length;
        }

        var numSide;
        if (numberOfVerticalLines > 0) {
            var numSideLines = Math.min(numberOfVerticalLines, length);
            numSide = Math.round(length / numSideLines);

            var maxI = Math.min(numSide * numberOfVerticalLines, length);
            for (i = 0; i < maxI; i += numSide) {
                indices[index++] = i;
                indices[index++] = i + length;
            }
        }

        return {
            boundingSphere : boundingSphere,
            attributes : attributes,
            indices : indices
        };
    }

    /**
     * A description of the outline of an ellipse on an ellipsoid.
     *
     * @alias EllipseOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.center The ellipse's center point in the fixed frame.
     * @param {Number} options.semiMajorAxis The length of the ellipse's semi-major axis in meters.
     * @param {Number} options.semiMinorAxis The length of the ellipse's semi-minor axis in meters.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the ellipse will be on.
     * @param {Number} [options.height=0.0] The distance in meters between the ellipse and the ellipsoid surface.
     * @param {Number} [options.extrudedHeight] The distance in meters between the ellipse's extruded face and the ellipsoid surface.
     * @param {Number} [options.rotation=0.0] The angle from north (counter-clockwise) in radians.
     * @param {Number} [options.granularity=0.02] The angular distance between points on the ellipse in radians.
     * @param {Number} [options.numberOfVerticalLines=16] Number of lines to draw between the top and bottom surface of an extruded ellipse.
     *
     * @exception {DeveloperError} semiMajorAxis and semiMinorAxis must be greater than zero.
     * @exception {DeveloperError} semiMajorAxis must be greater than or equal to the semiMinorAxis.
     * @exception {DeveloperError} granularity must be greater than zero.
     *
     * @see EllipseOutlineGeometry.createGeometry
     *
     * @example
     * var ellipse = new Cesium.EllipseOutlineGeometry({
     *   center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
     *   semiMajorAxis : 500000.0,
     *   semiMinorAxis : 300000.0,
     *   rotation : Cesium.Math.toRadians(60.0)
     * });
     * var geometry = Cesium.EllipseOutlineGeometry.createGeometry(ellipse);
     */
    function EllipseOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var center = options.center;
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        var semiMajorAxis = options.semiMajorAxis;
        var semiMinorAxis = options.semiMinorAxis;
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var height = defaultValue(options.height, 0.0);
        var extrudedHeight = options.extrudedHeight;
        var extrude = (defined(extrudedHeight) && Math.abs(height - extrudedHeight) > 1.0);

                if (!defined(center)) {
            throw new DeveloperError('center is required.');
        }
        if (!defined(semiMajorAxis)) {
            throw new DeveloperError('semiMajorAxis is required.');
        }
        if (!defined(semiMinorAxis)) {
            throw new DeveloperError('semiMinorAxis is required.');
        }
        if (semiMajorAxis < semiMinorAxis) {
            throw new DeveloperError('semiMajorAxis must be greater than or equal to the semiMinorAxis.');
        }
        if (granularity <= 0.0) {
            throw new DeveloperError('granularity must be greater than zero.');
        }
        
        this._center = Cartesian3.clone(center);
        this._semiMajorAxis = semiMajorAxis;
        this._semiMinorAxis = semiMinorAxis;
        this._ellipsoid = Ellipsoid.clone(ellipsoid);
        this._rotation = defaultValue(options.rotation, 0.0);
        this._height = height;
        this._granularity = granularity;
        this._extrudedHeight = extrudedHeight;
        this._extrude = extrude;
        this._numberOfVerticalLines = Math.max(defaultValue(options.numberOfVerticalLines, 16), 0);
        this._workerName = 'createEllipseOutlineGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    EllipseOutlineGeometry.packedLength = Cartesian3.packedLength + Ellipsoid.packedLength + 9;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {EllipseOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    EllipseOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._center, array, startingIndex);
        startingIndex += Cartesian3.packedLength;

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex++] = value._semiMajorAxis;
        array[startingIndex++] = value._semiMinorAxis;
        array[startingIndex++] = value._rotation;
        array[startingIndex++] = value._height;
        array[startingIndex++] = value._granularity;
        array[startingIndex++] = defined(value._extrudedHeight) ? 1.0 : 0.0;
        array[startingIndex++] = defaultValue(value._extrudedHeight, 0.0);
        array[startingIndex++] = value._extrude ? 1.0 : 0.0;
        array[startingIndex]   = value._numberOfVerticalLines;

        return array;
    };

    var scratchCenter = new Cartesian3();
    var scratchEllipsoid = new Ellipsoid();
    var scratchOptions = {
        center : scratchCenter,
        ellipsoid : scratchEllipsoid,
        semiMajorAxis : undefined,
        semiMinorAxis : undefined,
        rotation : undefined,
        height : undefined,
        granularity : undefined,
        extrudedHeight : undefined,
        numberOfVerticalLines : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {EllipseOutlineGeometry} [result] The object into which to store the result.
     * @returns {EllipseOutlineGeometry} The modified result parameter or a new EllipseOutlineGeometry instance if one was not provided.
     */
    EllipseOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var center = Cartesian3.unpack(array, startingIndex, scratchCenter);
        startingIndex += Cartesian3.packedLength;

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var semiMajorAxis = array[startingIndex++];
        var semiMinorAxis = array[startingIndex++];
        var rotation = array[startingIndex++];
        var height = array[startingIndex++];
        var granularity = array[startingIndex++];
        var hasExtrudedHeight = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var extrude = array[startingIndex++] === 1.0;
        var numberOfVerticalLines = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.height = height;
            scratchOptions.extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
            scratchOptions.granularity = granularity;
            scratchOptions.rotation = rotation;
            scratchOptions.semiMajorAxis = semiMajorAxis;
            scratchOptions.semiMinorAxis = semiMinorAxis;
            scratchOptions.numberOfVerticalLines = numberOfVerticalLines;
            return new EllipseOutlineGeometry(scratchOptions);
        }

        result._center = Cartesian3.clone(center, result._center);
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._semiMajorAxis = semiMajorAxis;
        result._semiMinorAxis = semiMinorAxis;
        result._rotation = rotation;
        result._height = height;
        result._granularity = granularity;
        result._extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
        result._extrude = extrude;
        result._numberOfVerticalLines = numberOfVerticalLines;

        return result;
    };

    /**
     * Computes the geometric representation of an outline of an ellipse on an ellipsoid, including its vertices, indices, and a bounding sphere.
     *
     * @param {EllipseOutlineGeometry} ellipseGeometry A description of the ellipse.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    EllipseOutlineGeometry.createGeometry = function(ellipseGeometry) {
        if ((ellipseGeometry._semiMajorAxis <= 0.0) || (ellipseGeometry._semiMinorAxis <= 0.0)) {
            return;
        }

        ellipseGeometry._center = ellipseGeometry._ellipsoid.scaleToGeodeticSurface(ellipseGeometry._center, ellipseGeometry._center);
        var options = {
            center : ellipseGeometry._center,
            semiMajorAxis : ellipseGeometry._semiMajorAxis,
            semiMinorAxis : ellipseGeometry._semiMinorAxis,
            ellipsoid : ellipseGeometry._ellipsoid,
            rotation : ellipseGeometry._rotation,
            height : ellipseGeometry._height,
            extrudedHeight : ellipseGeometry._extrudedHeight,
            granularity : ellipseGeometry._granularity,
            numberOfVerticalLines : ellipseGeometry._numberOfVerticalLines
        };
        var geometry;
        if (ellipseGeometry._extrude) {
            options.extrudedHeight = Math.min(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
            options.height = Math.max(ellipseGeometry._extrudedHeight, ellipseGeometry._height);
            geometry = computeExtrudedEllipse(options);
        } else {
            geometry = computeEllipse(options);
        }

        return new Geometry({
            attributes : geometry.attributes,
            indices : geometry.indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : geometry.boundingSphere
        });
    };

    return EllipseOutlineGeometry;
});

/*global define*/
define('Core/CircleOutlineGeometry',[
        './Cartesian3',
        './Check',
        './defaultValue',
        './defined',
        './EllipseOutlineGeometry',
        './Ellipsoid'
    ], function(
        Cartesian3,
        Check,
        defaultValue,
        defined,
        EllipseOutlineGeometry,
        Ellipsoid) {
    'use strict';

    /**
     * A description of the outline of a circle on the ellipsoid.
     *
     * @alias CircleOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3} options.center The circle's center point in the fixed frame.
     * @param {Number} options.radius The radius in meters.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid the circle will be on.
     * @param {Number} [options.height=0.0] The distance in meters between the circle and the ellipsoid surface.
     * @param {Number} [options.granularity=0.02] The angular distance between points on the circle in radians.
     * @param {Number} [options.extrudedHeight=0.0] The distance in meters between the circle's extruded face and the ellipsoid surface.
     * @param {Number} [options.numberOfVerticalLines=16] Number of lines to draw between the top and bottom of an extruded circle.
     *
     * @exception {DeveloperError} radius must be greater than zero.
     * @exception {DeveloperError} granularity must be greater than zero.
     *
     * @see CircleOutlineGeometry.createGeometry
     * @see Packable
     *
     * @example
     * // Create a circle.
     * var circle = new Cesium.CircleOutlineGeometry({
     *   center : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
     *   radius : 100000.0
     * });
     * var geometry = Cesium.CircleOutlineGeometry.createGeometry(circle);
     */
    function CircleOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var radius = options.radius;

                Check.typeOf.number('radius', radius);
        
        var ellipseGeometryOptions = {
            center : options.center,
            semiMajorAxis : radius,
            semiMinorAxis : radius,
            ellipsoid : options.ellipsoid,
            height : options.height,
            extrudedHeight : options.extrudedHeight,
            granularity : options.granularity,
            numberOfVerticalLines : options.numberOfVerticalLines
        };
        this._ellipseGeometry = new EllipseOutlineGeometry(ellipseGeometryOptions);
        this._workerName = 'createCircleOutlineGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    CircleOutlineGeometry.packedLength = EllipseOutlineGeometry.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {CircleOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    CircleOutlineGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
                return EllipseOutlineGeometry.pack(value._ellipseGeometry, array, startingIndex);
    };

    var scratchEllipseGeometry = new EllipseOutlineGeometry({
        center : new Cartesian3(),
        semiMajorAxis : 1.0,
        semiMinorAxis : 1.0
    });
    var scratchOptions = {
        center : new Cartesian3(),
        radius : undefined,
        ellipsoid : Ellipsoid.clone(Ellipsoid.UNIT_SPHERE),
        height : undefined,
        extrudedHeight : undefined,
        granularity : undefined,
        numberOfVerticalLines : undefined,
        semiMajorAxis : undefined,
        semiMinorAxis : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {CircleOutlineGeometry} [result] The object into which to store the result.
     * @returns {CircleOutlineGeometry} The modified result parameter or a new CircleOutlineGeometry instance if one was not provided.
     */
    CircleOutlineGeometry.unpack = function(array, startingIndex, result) {
        var ellipseGeometry = EllipseOutlineGeometry.unpack(array, startingIndex, scratchEllipseGeometry);
        scratchOptions.center = Cartesian3.clone(ellipseGeometry._center, scratchOptions.center);
        scratchOptions.ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid, scratchOptions.ellipsoid);
        scratchOptions.height = ellipseGeometry._height;
        scratchOptions.extrudedHeight = ellipseGeometry._extrudedHeight;
        scratchOptions.granularity = ellipseGeometry._granularity;
        scratchOptions.numberOfVerticalLines = ellipseGeometry._numberOfVerticalLines;

        if (!defined(result)) {
            scratchOptions.radius = ellipseGeometry._semiMajorAxis;
            return new CircleOutlineGeometry(scratchOptions);
        }

        scratchOptions.semiMajorAxis = ellipseGeometry._semiMajorAxis;
        scratchOptions.semiMinorAxis = ellipseGeometry._semiMinorAxis;
        result._ellipseGeometry = new EllipseOutlineGeometry(scratchOptions);
        return result;
    };

    /**
     * Computes the geometric representation of an outline of a circle on an ellipsoid, including its vertices, indices, and a bounding sphere.
     *
     * @param {CircleOutlineGeometry} circleGeometry A description of the circle.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    CircleOutlineGeometry.createGeometry = function(circleGeometry) {
        return EllipseOutlineGeometry.createGeometry(circleGeometry._ellipseGeometry);
    };

    return CircleOutlineGeometry;
});

/*global define*/
define('Core/ClockRange',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Constants used by {@link Clock#tick} to determine behavior
     * when {@link Clock#startTime} or {@link Clock#stopTime} is reached.
     *
     * @exports ClockRange
     *
     * @see Clock
     * @see ClockStep
     */
    var ClockRange = {
        /**
         * {@link Clock#tick} will always advances the clock in its current direction.
         *
         * @type {Number}
         * @constant
         */
        UNBOUNDED : 0,

        /**
         * When {@link Clock#startTime} or {@link Clock#stopTime} is reached,
         * {@link Clock#tick} will not advance {@link Clock#currentTime} any further.
         *
         * @type {Number}
         * @constant
         */
        CLAMPED : 1,

        /**
         * When {@link Clock#stopTime} is reached, {@link Clock#tick} will advance
         * {@link Clock#currentTime} to the opposite end of the interval.  When
         * time is moving backwards, {@link Clock#tick} will not advance past
         * {@link Clock#startTime}
         *
         * @type {Number}
         * @constant
         */
        LOOP_STOP : 2
    };

    return freezeObject(ClockRange);
});

/*global define*/
define('Core/ClockStep',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Constants to determine how much time advances with each call
     * to {@link Clock#tick}.
     *
     * @exports ClockStep
     *
     * @see Clock
     * @see ClockRange
     */
    var ClockStep = {
        /**
         * {@link Clock#tick} advances the current time by a fixed step,
         * which is the number of seconds specified by {@link Clock#multiplier}.
         *
         * @type {Number}
         * @constant
         */
        TICK_DEPENDENT : 0,

        /**
         * {@link Clock#tick} advances the current time by the amount of system
         * time elapsed since the previous call multiplied by {@link Clock#multiplier}.
         *
         * @type {Number}
         * @constant
         */
        SYSTEM_CLOCK_MULTIPLIER : 1,

        /**
         * {@link Clock#tick} sets the clock to the current system time;
         * ignoring all other settings.
         *
         * @type {Number}
         * @constant
         */
        SYSTEM_CLOCK : 2
    };

    return freezeObject(ClockStep);
});

/*global define*/
define('Core/getTimestamp',[
        './defined'
    ], function(
        defined) {
    'use strict';
    /*global performance*/

    /**
     * Gets a timestamp that can be used in measuring the time between events.  Timestamps
     * are expressed in milliseconds, but it is not specified what the milliseconds are
     * measured from.  This function uses performance.now() if it is available, or Date.now()
     * otherwise.
     *
     * @exports getTimestamp
     *
     * @returns {Number} The timestamp in milliseconds since some unspecified reference time.
     */
    var getTimestamp;

    if (typeof performance !== 'undefined' && defined(performance.now)) {
        getTimestamp = function() {
            return performance.now();
        };
    } else {
        getTimestamp = function() {
            return Date.now();
        };
    }

    return getTimestamp;
});

/*global define*/
define('Core/Clock',[
        './ClockRange',
        './ClockStep',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Event',
        './getTimestamp',
        './JulianDate'
    ], function(
        ClockRange,
        ClockStep,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        getTimestamp,
        JulianDate) {
    'use strict';

    /**
     * A simple clock for keeping track of simulated time.
     *
     * @alias Clock
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {JulianDate} [options.startTime] The start time of the clock.
     * @param {JulianDate} [options.stopTime] The stop time of the clock.
     * @param {JulianDate} [options.currentTime] The current time.
     * @param {Number} [options.multiplier=1.0] Determines how much time advances when {@link Clock#tick} is called, negative values allow for advancing backwards.
     * @param {ClockStep} [options.clockStep=ClockStep.SYSTEM_CLOCK_MULTIPLIER] Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent.
     * @param {ClockRange} [options.clockRange=ClockRange.UNBOUNDED] Determines how the clock should behave when {@link Clock#startTime} or {@link Clock#stopTime} is reached.
     * @param {Boolean} [options.canAnimate=true] Indicates whether {@link Clock#tick} can advance time.  This could be false if data is being buffered, for example.  The clock will only tick when both {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
     * @param {Boolean} [options.shouldAnimate=true] Indicates whether {@link Clock#tick} should attempt to advance time.  The clock will only tick when both {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
     *
     * @exception {DeveloperError} startTime must come before stopTime.
     *
     *
     * @example
     * // Create a clock that loops on Christmas day 2013 and runs in real-time.
     * var clock = new Cesium.Clock({
     *    startTime : Cesium.JulianDate.fromIso8601("2013-12-25"),
     *    currentTime : Cesium.JulianDate.fromIso8601("2013-12-25"),
     *    stopTime : Cesium.JulianDate.fromIso8601("2013-12-26"),
     *    clockRange : Cesium.ClockRange.LOOP_STOP,
     *    clockStep : Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER
     * });
     *
     * @see ClockStep
     * @see ClockRange
     * @see JulianDate
     */
    function Clock(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var currentTime = options.currentTime;
        var startTime = options.startTime;
        var stopTime = options.stopTime;

        if (!defined(currentTime)) {
            // if not specified, current time is the start time,
            // or if that is not specified, 1 day before the stop time,
            // or if that is not specified, then now.
            if (defined(startTime)) {
                currentTime = JulianDate.clone(startTime);
            } else if (defined(stopTime)) {
                currentTime = JulianDate.addDays(stopTime, -1.0, new JulianDate());
            } else {
                currentTime = JulianDate.now();
            }
        } else {
            currentTime = JulianDate.clone(currentTime);
        }

        if (!defined(startTime)) {
            // if not specified, start time is the current time
            // (as determined above)
            startTime = JulianDate.clone(currentTime);
        } else {
            startTime = JulianDate.clone(startTime);
        }

        if (!defined(stopTime)) {
            // if not specified, stop time is 1 day after the start time
            // (as determined above)
            stopTime = JulianDate.addDays(startTime, 1.0, new JulianDate());
        } else {
            stopTime = JulianDate.clone(stopTime);
        }

                if (JulianDate.greaterThan(startTime, stopTime)) {
            throw new DeveloperError('startTime must come before stopTime.');
        }
        
        /**
         * The start time of the clock.
         * @type {JulianDate}
         */
        this.startTime = startTime;

        /**
         * The stop time of the clock.
         * @type {JulianDate}
         */
        this.stopTime = stopTime;

        /**
         * Determines how the clock should behave when
         * {@link Clock#startTime} or {@link Clock#stopTime}
         * is reached.
         * @type {ClockRange}
         * @default {@link ClockRange.UNBOUNDED}
         */
        this.clockRange = defaultValue(options.clockRange, ClockRange.UNBOUNDED);

        /**
         * Indicates whether {@link Clock#tick} can advance time.  This could be false if data is being buffered,
         * for example.  The clock will only advance time when both
         * {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
         * @type {Boolean}
         * @default true
         */
        this.canAnimate = defaultValue(options.canAnimate, true);

        /**
         * An {@link Event} that is fired whenever {@link Clock#tick} is called.
         * @type {Event}
         */
        this.onTick = new Event();

        this._currentTime = undefined;
        this._multiplier = undefined;
        this._clockStep = undefined;
        this._shouldAnimate = undefined;
        this._lastSystemTime = getTimestamp();

        // set values using the property setters to
        // make values consistent.

        this.currentTime = currentTime;
        this.multiplier = defaultValue(options.multiplier, 1.0);
        this.clockStep = defaultValue(options.clockStep, ClockStep.SYSTEM_CLOCK_MULTIPLIER);
        this.shouldAnimate = defaultValue(options.shouldAnimate, true);
    }

    defineProperties(Clock.prototype, {
        /**
         * The current time.
         * Changing this property will change
         * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
         * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
         * @memberof Clock.prototype
         * @type {JulianDate}
         */
        currentTime : {
            get : function() {
                return this._currentTime;
            },
            set : function(value) {
                if (JulianDate.equals(this._currentTime, value)) {
                    return;
                }

                if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
                    this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
                }

                this._currentTime = value;
            }
        },

        /**
         * Gets or sets how much time advances when {@link Clock#tick} is called. Negative values allow for advancing backwards.
         * If {@link Clock#clockStep} is set to {@link ClockStep.TICK_DEPENDENT}, this is the number of seconds to advance.
         * If {@link Clock#clockStep} is set to {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}, this value is multiplied by the
         * elapsed system time since the last call to {@link Clock#tick}.
         * Changing this property will change
         * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
         * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
         * @memberof Clock.prototype
         * @type {Number}
         * @default 1.0
         */
        multiplier : {
            get : function() {
                return this._multiplier;
            },
            set : function(value) {
                if (this._multiplier === value) {
                    return;
                }

                if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
                    this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
                }

                this._multiplier = value;
            }
        },

        /**
         * Determines if calls to {@link Clock#tick} are frame dependent or system clock dependent.
         * Changing this property to {@link ClockStep.SYSTEM_CLOCK} will set
         * {@link Clock#multiplier} to 1.0, {@link Clock#shouldAnimate} to true, and
         * {@link Clock#currentTime} to the current system clock time.
         * @memberof Clock.prototype
         * @type ClockStep
         * @default {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}
         */
        clockStep : {
            get : function() {
                return this._clockStep;
            },
            set : function(value) {
                if (value === ClockStep.SYSTEM_CLOCK) {
                    this._multiplier = 1.0;
                    this._shouldAnimate = true;
                    this._currentTime = JulianDate.now();
                }

                this._clockStep = value;
            }
        },


        /**
         * Indicates whether {@link Clock#tick} should attempt to advance time.
         * The clock will only advance time when both
         * {@link Clock#canAnimate} and {@link Clock#shouldAnimate} are true.
         * Changing this property will change
         * {@link Clock#clockStep} from {@link ClockStep.SYSTEM_CLOCK} to
         * {@link ClockStep.SYSTEM_CLOCK_MULTIPLIER}.
         * @memberof Clock.prototype
         * @type {Boolean}
         * @default true
         */
        shouldAnimate : {
            get : function() {
                return this._shouldAnimate;
            },
            set : function(value) {
                if (this._shouldAnimate === value) {
                    return;
                }

                if (this._clockStep === ClockStep.SYSTEM_CLOCK) {
                    this._clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
                }

                this._shouldAnimate = value;
            }
        }
    });

    /**
     * Advances the clock from the current time based on the current configuration options.
     * tick should be called every frame, regardless of whether animation is taking place
     * or not.  To control animation, use the {@link Clock#shouldAnimate} property.
     *
     * @returns {JulianDate} The new value of the {@link Clock#currentTime} property.
     */
    Clock.prototype.tick = function() {
        var currentSystemTime = getTimestamp();
        var currentTime = JulianDate.clone(this._currentTime);

        if (this.canAnimate && this._shouldAnimate) {
            var clockStep = this._clockStep;
            if (clockStep === ClockStep.SYSTEM_CLOCK) {
                currentTime = JulianDate.now(currentTime);
            } else {
                var multiplier = this._multiplier;

                if (clockStep === ClockStep.TICK_DEPENDENT) {
                    currentTime = JulianDate.addSeconds(currentTime, multiplier, currentTime);
                } else {
                    var milliseconds = currentSystemTime - this._lastSystemTime;
                    currentTime = JulianDate.addSeconds(currentTime, multiplier * (milliseconds / 1000.0), currentTime);
                }

                var clockRange = this.clockRange;
                var startTime = this.startTime;
                var stopTime = this.stopTime;

                if (clockRange === ClockRange.CLAMPED) {
                    if (JulianDate.lessThan(currentTime, startTime)) {
                        currentTime = JulianDate.clone(startTime, currentTime);
                    } else if (JulianDate.greaterThan(currentTime, stopTime)) {
                        currentTime = JulianDate.clone(stopTime, currentTime);
                    }
                } else if (clockRange === ClockRange.LOOP_STOP) {
                    if (JulianDate.lessThan(currentTime, startTime)) {
                        currentTime = JulianDate.clone(startTime, currentTime);
                    }
                    while (JulianDate.greaterThan(currentTime, stopTime)) {
                        currentTime = JulianDate.addSeconds(startTime, JulianDate.secondsDifference(currentTime, stopTime), currentTime);
                    }
                }
            }
        }

        this._currentTime = currentTime;
        this._lastSystemTime = currentSystemTime;
        this.onTick.raiseEvent(this);
        return currentTime;
    };

    return Clock;
});

/*global define*/
define('Core/Color',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './FeatureDetection',
        './freezeObject',
        './Math'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        FeatureDetection,
        freezeObject,
        CesiumMath) {
    'use strict';

    function hue2rgb(m1, m2, h) {
        if (h < 0) {
            h += 1;
        }
        if (h > 1) {
            h -= 1;
        }
        if (h * 6 < 1) {
            return m1 + (m2 - m1) * 6 * h;
        }
        if (h * 2 < 1) {
            return m2;
        }
        if (h * 3 < 2) {
            return m1 + (m2 - m1) * (2 / 3 - h) * 6;
        }
        return m1;
    }

    /**
     * A color, specified using red, green, blue, and alpha values,
     * which range from <code>0</code> (no intensity) to <code>1.0</code> (full intensity).
     * @param {Number} [red=1.0] The red component.
     * @param {Number} [green=1.0] The green component.
     * @param {Number} [blue=1.0] The blue component.
     * @param {Number} [alpha=1.0] The alpha component.
     *
     * @constructor
     * @alias Color
     *
     * @see Packable
     */
    function Color(red, green, blue, alpha) {
        /**
         * The red component.
         * @type {Number}
         * @default 1.0
         */
        this.red = defaultValue(red, 1.0);
        /**
         * The green component.
         * @type {Number}
         * @default 1.0
         */
        this.green = defaultValue(green, 1.0);
        /**
         * The blue component.
         * @type {Number}
         * @default 1.0
         */
        this.blue = defaultValue(blue, 1.0);
        /**
         * The alpha component.
         * @type {Number}
         * @default 1.0
         */
        this.alpha = defaultValue(alpha, 1.0);
    }

    /**
     * Creates a Color instance from a {@link Cartesian4}. <code>x</code>, <code>y</code>, <code>z</code>,
     * and <code>w</code> map to <code>red</code>, <code>green</code>, <code>blue</code>, and <code>alpha</code>, respectively.
     *
     * @param {Cartesian4} cartesian The source cartesian.
     * @param {Color} [result] The object onto which to store the result.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     */
    Color.fromCartesian4 = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required');
        }
        
        if (!defined(result)) {
            return new Color(cartesian.x, cartesian.y, cartesian.z, cartesian.w);
        }

        result.red = cartesian.x;
        result.green = cartesian.y;
        result.blue = cartesian.z;
        result.alpha = cartesian.w;
        return result;
    };

    /**
     * Creates a new Color specified using red, green, blue, and alpha values
     * that are in the range of 0 to 255, converting them internally to a range of 0.0 to 1.0.
     *
     * @param {Number} [red=255] The red component.
     * @param {Number} [green=255] The green component.
     * @param {Number} [blue=255] The blue component.
     * @param {Number} [alpha=255] The alpha component.
     * @param {Color} [result] The object onto which to store the result.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     */
    Color.fromBytes = function(red, green, blue, alpha, result) {
        red = Color.byteToFloat(defaultValue(red, 255.0));
        green = Color.byteToFloat(defaultValue(green, 255.0));
        blue = Color.byteToFloat(defaultValue(blue, 255.0));
        alpha = Color.byteToFloat(defaultValue(alpha, 255.0));

        if (!defined(result)) {
            return new Color(red, green, blue, alpha);
        }

        result.red = red;
        result.green = green;
        result.blue = blue;
        result.alpha = alpha;
        return result;
    };

    /**
     * Creates a new Color that has the same red, green, and blue components
     * of the specified color, but with the specified alpha value.
     *
     * @param {Color} color The base color
     * @param {Number} alpha The new alpha component.
     * @param {Color} [result] The object onto which to store the result.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     *
     * @example var translucentRed = Cesium.Color.fromAlpha(Cesium.Color.RED, 0.9);
     */
    Color.fromAlpha = function(color, alpha, result) {
                if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(alpha)) {
            throw new DeveloperError('alpha is required');
        }
        
        if (!defined(result)) {
            return new Color(color.red, color.green, color.blue, alpha);
        }

        result.red = color.red;
        result.green = color.green;
        result.blue = color.blue;
        result.alpha = alpha;
        return result;
    };

    var scratchArrayBuffer;
    var scratchUint32Array;
    var scratchUint8Array;
    if (FeatureDetection.supportsTypedArrays()) {
        scratchArrayBuffer = new ArrayBuffer(4);
        scratchUint32Array = new Uint32Array(scratchArrayBuffer);
        scratchUint8Array = new Uint8Array(scratchArrayBuffer);
    }

    /**
     * Creates a new Color from a single numeric unsigned 32-bit RGBA value, using the endianness
     * of the system.
     *
     * @param {Number} rgba A single numeric unsigned 32-bit RGBA value.
     * @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
     * @returns {Color} The color object.
     *
     * @example
     * var color = Cesium.Color.fromRgba(0x67ADDFFF);
     *
     * @see Color#toRgba
     */
    Color.fromRgba = function(rgba, result) {
        // scratchUint32Array and scratchUint8Array share an underlying array buffer
        scratchUint32Array[0] = rgba;
        return Color.fromBytes(scratchUint8Array[0], scratchUint8Array[1], scratchUint8Array[2], scratchUint8Array[3], result);
    };

    /**
     * Creates a Color instance from hue, saturation, and lightness.
     *
     * @param {Number} [hue=0] The hue angle 0...1
     * @param {Number} [saturation=0] The saturation value 0...1
     * @param {Number} [lightness=0] The lightness value 0...1
     * @param {Number} [alpha=1.0] The alpha component 0...1
     * @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
     * @returns {Color} The color object.
     *
     * @see {@link http://www.w3.org/TR/css3-color/#hsl-color|CSS color values}
     */
    Color.fromHsl = function(hue, saturation, lightness, alpha, result) {
        hue = defaultValue(hue, 0.0) % 1.0;
        saturation = defaultValue(saturation, 0.0);
        lightness = defaultValue(lightness, 0.0);
        alpha = defaultValue(alpha, 1.0);

        var red = lightness;
        var green = lightness;
        var blue = lightness;

        if (saturation !== 0) {
            var m2;
            if (lightness < 0.5) {
                m2 = lightness * (1 + saturation);
            } else {
                m2 = lightness + saturation - lightness * saturation;
            }

            var m1 = 2.0 * lightness - m2;
            red = hue2rgb(m1, m2, hue + 1 / 3);
            green = hue2rgb(m1, m2, hue);
            blue = hue2rgb(m1, m2, hue - 1 / 3);
        }

        if (!defined(result)) {
            return new Color(red, green, blue, alpha);
        }

        result.red = red;
        result.green = green;
        result.blue = blue;
        result.alpha = alpha;
        return result;
    };

    /**
     * Creates a random color using the provided options. For reproducible random colors, you should
     * call {@link CesiumMath#setRandomNumberSeed} once at the beginning of your application.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Number} [options.red] If specified, the red component to use instead of a randomized value.
     * @param {Number} [options.minimumRed=0.0] The maximum red value to generate if none was specified.
     * @param {Number} [options.maximumRed=1.0] The minimum red value to generate if none was specified.
     * @param {Number} [options.green] If specified, the green component to use instead of a randomized value.
     * @param {Number} [options.minimumGreen=0.0] The maximum green value to generate if none was specified.
     * @param {Number} [options.maximumGreen=1.0] The minimum green value to generate if none was specified.
     * @param {Number} [options.blue] If specified, the blue component to use instead of a randomized value.
     * @param {Number} [options.minimumBlue=0.0] The maximum blue value to generate if none was specified.
     * @param {Number} [options.maximumBlue=1.0] The minimum blue value to generate if none was specified.
     * @param {Number} [options.alpha] If specified, the alpha component to use instead of a randomized value.
     * @param {Number} [options.minimumAlpha=0.0] The maximum alpha value to generate if none was specified.
     * @param {Number} [options.maximumAlpha=1.0] The minimum alpha value to generate if none was specified.
     * @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
     * @returns {Color} The modified result parameter or a new instance if result was undefined.
     *
     * @exception {DeveloperError} minimumRed must be less than or equal to maximumRed.
     * @exception {DeveloperError} minimumGreen must be less than or equal to maximumGreen.
     * @exception {DeveloperError} minimumBlue must be less than or equal to maximumBlue.
     * @exception {DeveloperError} minimumAlpha must be less than or equal to maximumAlpha.
     *
     * @example
     * //Create a completely random color
     * var color = Cesium.Color.fromRandom();
     *
     * //Create a random shade of yellow.
     * var color = Cesium.Color.fromRandom({
     *     red : 1.0,
     *     green : 1.0,
     *     alpha : 1.0
     * });
     *
     * //Create a random bright color.
     * var color = Cesium.Color.fromRandom({
     *     minimumRed : 0.75,
     *     minimumGreen : 0.75,
     *     minimumBlue : 0.75,
     *     alpha : 1.0
     * });
     */
    Color.fromRandom = function(options, result) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var red = options.red;
        if (!defined(red)) {
            var minimumRed = defaultValue(options.minimumRed, 0);
            var maximumRed = defaultValue(options.maximumRed, 1.0);

                        if (minimumRed > maximumRed) {
                throw new DeveloperError("minimumRed must be less than or equal to maximumRed");
            }
            
            red = minimumRed + (CesiumMath.nextRandomNumber() * (maximumRed - minimumRed));
        }

        var green = options.green;
        if (!defined(green)) {
            var minimumGreen = defaultValue(options.minimumGreen, 0);
            var maximumGreen = defaultValue(options.maximumGreen, 1.0);

                        if (minimumGreen > maximumGreen) {
                throw new DeveloperError("minimumGreen must be less than or equal to maximumGreen");
            }
            
            green = minimumGreen + (CesiumMath.nextRandomNumber() * (maximumGreen - minimumGreen));
        }

        var blue = options.blue;
        if (!defined(blue)) {
            var minimumBlue = defaultValue(options.minimumBlue, 0);
            var maximumBlue = defaultValue(options.maximumBlue, 1.0);

                        if (minimumBlue > maximumBlue) {
                throw new DeveloperError("minimumBlue must be less than or equal to maximumBlue");
            }
            
            blue = minimumBlue + (CesiumMath.nextRandomNumber() * (maximumBlue - minimumBlue));
        }

        var alpha = options.alpha;
        if (!defined(alpha)) {
            var minimumAlpha = defaultValue(options.minimumAlpha, 0);
            var maximumAlpha = defaultValue(options.maximumAlpha, 1.0);

                        if (minimumAlpha > maximumAlpha) {
                throw new DeveloperError("minimumAlpha must be less than or equal to maximumAlpha");
            }
            
            alpha = minimumAlpha + (CesiumMath.nextRandomNumber() * (maximumAlpha - minimumAlpha));
        }

        if (!defined(result)) {
            return new Color(red, green, blue, alpha);
        }

        result.red = red;
        result.green = green;
        result.blue = blue;
        result.alpha = alpha;
        return result;
    };

    //#rgb
    var rgbMatcher = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i;
    //#rrggbb
    var rrggbbMatcher = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
    //rgb(), rgba(), or rgb%()
    var rgbParenthesesMatcher = /^rgba?\(\s*([0-9.]+%?)\s*,\s*([0-9.]+%?)\s*,\s*([0-9.]+%?)(?:\s*,\s*([0-9.]+))?\s*\)$/i;
    //hsl(), hsla(), or hsl%()
    var hslParenthesesMatcher = /^hsla?\(\s*([0-9.]+)\s*,\s*([0-9.]+%)\s*,\s*([0-9.]+%)(?:\s*,\s*([0-9.]+))?\s*\)$/i;

    /**
     * Creates a Color instance from a CSS color value.
     *
     * @param {String} color The CSS color value in #rgb, #rrggbb, rgb(), rgba(), hsl(), or hsla() format.
     * @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
     * @returns {Color} The color object, or undefined if the string was not a valid CSS color.
     *
     *
     * @example
     * var cesiumBlue = Cesium.Color.fromCssColorString('#67ADDF');
     * var green = Cesium.Color.fromCssColorString('green');
     *
     * @see {@link http://www.w3.org/TR/css3-color|CSS color values}
     */
    Color.fromCssColorString = function(color, result) {
                if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        
        if (!defined(result)) {
            result = new Color();
        }

        var namedColor = Color[color.toUpperCase()];
        if (defined(namedColor)) {
            Color.clone(namedColor, result);
            return result;
        }

        var matches = rgbMatcher.exec(color);
        if (matches !== null) {
            result.red = parseInt(matches[1], 16) / 15;
            result.green = parseInt(matches[2], 16) / 15.0;
            result.blue = parseInt(matches[3], 16) / 15.0;
            result.alpha = 1.0;
            return result;
        }

        matches = rrggbbMatcher.exec(color);
        if (matches !== null) {
            result.red = parseInt(matches[1], 16) / 255.0;
            result.green = parseInt(matches[2], 16) / 255.0;
            result.blue = parseInt(matches[3], 16) / 255.0;
            result.alpha = 1.0;
            return result;
        }

        matches = rgbParenthesesMatcher.exec(color);
        if (matches !== null) {
            result.red = parseFloat(matches[1]) / ('%' === matches[1].substr(-1) ? 100.0 : 255.0);
            result.green = parseFloat(matches[2]) / ('%' === matches[2].substr(-1) ? 100.0 : 255.0);
            result.blue = parseFloat(matches[3]) / ('%' === matches[3].substr(-1) ? 100.0 : 255.0);
            result.alpha = parseFloat(defaultValue(matches[4], '1.0'));
            return result;
        }

        matches = hslParenthesesMatcher.exec(color);
        if (matches !== null) {
            return Color.fromHsl(parseFloat(matches[1]) / 360.0,
                                 parseFloat(matches[2]) / 100.0,
                                 parseFloat(matches[3]) / 100.0,
                                 parseFloat(defaultValue(matches[4], '1.0')), result);
        }

        result = undefined;
        return result;
    };

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Color.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Color} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Color.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);
        array[startingIndex++] = value.red;
        array[startingIndex++] = value.green;
        array[startingIndex++] = value.blue;
        array[startingIndex] = value.alpha;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Color} [result] The object into which to store the result.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     */
    Color.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);
        if (!defined(result)) {
            result = new Color();
        }
        result.red = array[startingIndex++];
        result.green = array[startingIndex++];
        result.blue = array[startingIndex++];
        result.alpha = array[startingIndex];
        return result;
    };

    /**
     * Converts a 'byte' color component in the range of 0 to 255 into
     * a 'float' color component in the range of 0 to 1.0.
     *
     * @param {Number} number The number to be converted.
     * @returns {Number} The converted number.
     */
    Color.byteToFloat = function(number) {
        return number / 255.0;
    };

    /**
     * Converts a 'float' color component in the range of 0 to 1.0 into
     * a 'byte' color component in the range of 0 to 255.
     *
     * @param {Number} number The number to be converted.
     * @returns {Number} The converted number.
     */
    Color.floatToByte = function(number) {
        return number === 1.0 ? 255.0 : (number * 256.0) | 0;
    };

    /**
     * Duplicates a Color.
     *
     * @param {Color} color The Color to duplicate.
     * @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
     * @returns {Color} The modified result parameter or a new instance if result was undefined. (Returns undefined if color is undefined)
     */
    Color.clone = function(color, result) {
        if (!defined(color)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Color(color.red, color.green, color.blue, color.alpha);
        }
        result.red = color.red;
        result.green = color.green;
        result.blue = color.blue;
        result.alpha = color.alpha;
        return result;
    };

    /**
     * Returns true if the first Color equals the second color.
     *
     * @param {Color} left The first Color to compare for equality.
     * @param {Color} right The second Color to compare for equality.
     * @returns {Boolean} <code>true</code> if the Colors are equal; otherwise, <code>false</code>.
     */
    Color.equals = function(left, right) {
        return (left === right) || //
               (defined(left) && //
                defined(right) && //
                left.red === right.red && //
                left.green === right.green && //
                left.blue === right.blue && //
                left.alpha === right.alpha);
    };

    /**
     * @private
     */
    Color.equalsArray = function(color, array, offset) {
        return color.red === array[offset] &&
               color.green === array[offset + 1] &&
               color.blue === array[offset + 2] &&
               color.alpha === array[offset + 3];
    };

    /**
     * Returns a duplicate of a Color instance.
     *
     * @param {Color} [result] The object to store the result in, if undefined a new instance will be created.
     * @returns {Color} The modified result parameter or a new instance if result was undefined.
     */
    Color.prototype.clone = function(result) {
        return Color.clone(this, result);
    };

    /**
     * Returns true if this Color equals other.
     *
     * @param {Color} other The Color to compare for equality.
     * @returns {Boolean} <code>true</code> if the Colors are equal; otherwise, <code>false</code>.
     */
    Color.prototype.equals = function(other) {
        return Color.equals(this, other);
    };

    /**
     * Returns <code>true</code> if this Color equals other componentwise within the specified epsilon.
     *
     * @param {Color} other The Color to compare for equality.
     * @param {Number} [epsilon=0.0] The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if the Colors are equal within the specified epsilon; otherwise, <code>false</code>.
     */
    Color.prototype.equalsEpsilon = function(other, epsilon) {
        return (this === other) || //
               ((defined(other)) && //
                (Math.abs(this.red - other.red) <= epsilon) && //
                (Math.abs(this.green - other.green) <= epsilon) && //
                (Math.abs(this.blue - other.blue) <= epsilon) && //
                (Math.abs(this.alpha - other.alpha) <= epsilon));
    };

    /**
     * Creates a string representing this Color in the format '(red, green, blue, alpha)'.
     *
     * @returns {String} A string representing this Color in the format '(red, green, blue, alpha)'.
     */
    Color.prototype.toString = function() {
        return '(' + this.red + ', ' + this.green + ', ' + this.blue + ', ' + this.alpha + ')';
    };

    /**
     * Creates a string containing the CSS color value for this color.
     *
     * @returns {String} The CSS equivalent of this color.
     *
     * @see {@link http://www.w3.org/TR/css3-color/#rgba-color|CSS RGB or RGBA color values}
     */
    Color.prototype.toCssColorString = function() {
        var red = Color.floatToByte(this.red);
        var green = Color.floatToByte(this.green);
        var blue = Color.floatToByte(this.blue);
        if (this.alpha === 1) {
            return 'rgb(' + red + ',' + green + ',' + blue + ')';
        }
        return 'rgba(' + red + ',' + green + ',' + blue + ',' + this.alpha + ')';
    };

    /**
     * Converts this color to an array of red, green, blue, and alpha values
     * that are in the range of 0 to 255.
     *
     * @param {Number[]} [result] The array to store the result in, if undefined a new instance will be created.
     * @returns {Number[]} The modified result parameter or a new instance if result was undefined.
     */
    Color.prototype.toBytes = function(result) {
        var red = Color.floatToByte(this.red);
        var green = Color.floatToByte(this.green);
        var blue = Color.floatToByte(this.blue);
        var alpha = Color.floatToByte(this.alpha);

        if (!defined(result)) {
            return [red, green, blue, alpha];
        }
        result[0] = red;
        result[1] = green;
        result[2] = blue;
        result[3] = alpha;
        return result;
    };

    /**
     * Converts this color to a single numeric unsigned 32-bit RGBA value, using the endianness
     * of the system.
     *
     * @returns {Number} A single numeric unsigned 32-bit RGBA value.
     *
     *
     * @example
     * var rgba = Cesium.Color.BLUE.toRgba();
     *
     * @see Color.fromRgba
     */
    Color.prototype.toRgba = function() {
        // scratchUint32Array and scratchUint8Array share an underlying array buffer
        scratchUint8Array[0] = Color.floatToByte(this.red);
        scratchUint8Array[1] = Color.floatToByte(this.green);
        scratchUint8Array[2] = Color.floatToByte(this.blue);
        scratchUint8Array[3] = Color.floatToByte(this.alpha);
        return scratchUint32Array[0];
    };

    /**
     * Brightens this color by the provided magnitude.
     *
     * @param {Number} magnitude A positive number indicating the amount to brighten.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     *
     * @example
     * var brightBlue = Cesium.Color.BLUE.brighten(0.5, new Cesium.Color());
     */
    Color.prototype.brighten = function(magnitude, result) {
                if (!defined(magnitude)) {
            throw new DeveloperError('magnitude is required.');
        }
        if (magnitude < 0.0) {
            throw new DeveloperError('magnitude must be positive.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        magnitude = (1.0 - magnitude);
        result.red = 1.0 - ((1.0 - this.red) * magnitude);
        result.green = 1.0 - ((1.0 - this.green) * magnitude);
        result.blue = 1.0 - ((1.0 - this.blue) * magnitude);
        result.alpha = this.alpha;
        return result;
    };

    /**
     * Darkens this color by the provided magnitude.
     *
     * @param {Number} magnitude A positive number indicating the amount to darken.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     *
     * @example
     * var darkBlue = Cesium.Color.BLUE.darken(0.5, new Cesium.Color());
     */
    Color.prototype.darken = function(magnitude, result) {
                if (!defined(magnitude)) {
            throw new DeveloperError('magnitude is required.');
        }
        if (magnitude < 0.0) {
            throw new DeveloperError('magnitude must be positive.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        magnitude = (1.0 - magnitude);
        result.red = this.red * magnitude;
        result.green = this.green * magnitude;
        result.blue = this.blue * magnitude;
        result.alpha = this.alpha;
        return result;
    };

    /**
     * Creates a new Color that has the same red, green, and blue components
     * as this Color, but with the specified alpha value.
     *
     * @param {Number} alpha The new alpha component.
     * @param {Color} [result] The object onto which to store the result.
     * @returns {Color} The modified result parameter or a new Color instance if one was not provided.
     *
     * @example var translucentRed = Cesium.Color.RED.withAlpha(0.9);
     */
    Color.prototype.withAlpha = function(alpha, result) {
        return Color.fromAlpha(this, alpha, result);
    };

    /**
     * Computes the componentwise sum of two Colors.
     *
     * @param {Color} left The first Color.
     * @param {Color} right The second Color.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.add = function(left, right, result) {
                if (!defined(left)) {
            throw new DeveloperError('left is required');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = left.red + right.red;
        result.green = left.green + right.green;
        result.blue = left.blue + right.blue;
        result.alpha = left.alpha + right.alpha;
        return result;
    };

    /**
     * Computes the componentwise difference of two Colors.
     *
     * @param {Color} left The first Color.
     * @param {Color} right The second Color.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.subtract = function(left, right, result) {
                if (!defined(left)) {
            throw new DeveloperError('left is required');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = left.red - right.red;
        result.green = left.green - right.green;
        result.blue = left.blue - right.blue;
        result.alpha = left.alpha - right.alpha;
        return result;
    };

    /**
     * Computes the componentwise product of two Colors.
     *
     * @param {Color} left The first Color.
     * @param {Color} right The second Color.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.multiply = function(left, right, result) {
                if (!defined(left)) {
            throw new DeveloperError('left is required');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = left.red * right.red;
        result.green = left.green * right.green;
        result.blue = left.blue * right.blue;
        result.alpha = left.alpha * right.alpha;
        return result;
    };

    /**
     * Computes the componentwise quotient of two Colors.
     *
     * @param {Color} left The first Color.
     * @param {Color} right The second Color.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.divide = function(left, right, result) {
                if (!defined(left)) {
            throw new DeveloperError('left is required');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = left.red / right.red;
        result.green = left.green / right.green;
        result.blue = left.blue / right.blue;
        result.alpha = left.alpha / right.alpha;
        return result;
    };

    /**
     * Computes the componentwise modulus of two Colors.
     *
     * @param {Color} left The first Color.
     * @param {Color} right The second Color.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.mod = function(left, right, result) {
                if (!defined(left)) {
            throw new DeveloperError('left is required');
        }
        if (!defined(right)) {
            throw new DeveloperError('right is required');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = left.red % right.red;
        result.green = left.green % right.green;
        result.blue = left.blue % right.blue;
        result.alpha = left.alpha % right.alpha;
        return result;
    };

    /**
     * Multiplies the provided Color componentwise by the provided scalar.
     *
     * @param {Color} color The Color to be scaled.
     * @param {Number} scalar The scalar to multiply with.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.multiplyByScalar = function(color, scalar, result) {
                if (!defined(color)) {
            throw new DeveloperError('cartesian is required');
        }
        if (typeof scalar !== 'number') {
            throw new DeveloperError('scalar is required and must be a number.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = color.red * scalar;
        result.green = color.green * scalar;
        result.blue = color.blue * scalar;
        result.alpha = color.alpha * scalar;
        return result;
    };

    /**
     * Divides the provided Color componentwise by the provided scalar.
     *
     * @param {Color} color The Color to be divided.
     * @param {Number} scalar The scalar to divide with.
     * @param {Color} result The object onto which to store the result.
     * @returns {Color} The modified result parameter.
     */
    Color.divideByScalar = function(color, scalar, result) {
                if (!defined(color)) {
            throw new DeveloperError('cartesian is required');
        }
        if (typeof scalar !== 'number') {
            throw new DeveloperError('scalar is required and must be a number.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required');
        }
        
        result.red = color.red / scalar;
        result.green = color.green / scalar;
        result.blue = color.blue / scalar;
        result.alpha = color.alpha / scalar;
        return result;
    };

    /**
     * An immutable Color instance initialized to CSS color #F0F8FF
     * <span class="colorSwath" style="background: #F0F8FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ALICEBLUE = freezeObject(Color.fromCssColorString('#F0F8FF'));

    /**
     * An immutable Color instance initialized to CSS color #FAEBD7
     * <span class="colorSwath" style="background: #FAEBD7;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ANTIQUEWHITE = freezeObject(Color.fromCssColorString('#FAEBD7'));

    /**
     * An immutable Color instance initialized to CSS color #00FFFF
     * <span class="colorSwath" style="background: #00FFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.AQUA = freezeObject(Color.fromCssColorString('#00FFFF'));

    /**
     * An immutable Color instance initialized to CSS color #7FFFD4
     * <span class="colorSwath" style="background: #7FFFD4;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.AQUAMARINE = freezeObject(Color.fromCssColorString('#7FFFD4'));

    /**
     * An immutable Color instance initialized to CSS color #F0FFFF
     * <span class="colorSwath" style="background: #F0FFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.AZURE = freezeObject(Color.fromCssColorString('#F0FFFF'));

    /**
     * An immutable Color instance initialized to CSS color #F5F5DC
     * <span class="colorSwath" style="background: #F5F5DC;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BEIGE = freezeObject(Color.fromCssColorString('#F5F5DC'));

    /**
     * An immutable Color instance initialized to CSS color #FFE4C4
     * <span class="colorSwath" style="background: #FFE4C4;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BISQUE = freezeObject(Color.fromCssColorString('#FFE4C4'));

    /**
     * An immutable Color instance initialized to CSS color #000000
     * <span class="colorSwath" style="background: #000000;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BLACK = freezeObject(Color.fromCssColorString('#000000'));

    /**
     * An immutable Color instance initialized to CSS color #FFEBCD
     * <span class="colorSwath" style="background: #FFEBCD;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BLANCHEDALMOND = freezeObject(Color.fromCssColorString('#FFEBCD'));

    /**
     * An immutable Color instance initialized to CSS color #0000FF
     * <span class="colorSwath" style="background: #0000FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BLUE = freezeObject(Color.fromCssColorString('#0000FF'));

    /**
     * An immutable Color instance initialized to CSS color #8A2BE2
     * <span class="colorSwath" style="background: #8A2BE2;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BLUEVIOLET = freezeObject(Color.fromCssColorString('#8A2BE2'));

    /**
     * An immutable Color instance initialized to CSS color #A52A2A
     * <span class="colorSwath" style="background: #A52A2A;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BROWN = freezeObject(Color.fromCssColorString('#A52A2A'));

    /**
     * An immutable Color instance initialized to CSS color #DEB887
     * <span class="colorSwath" style="background: #DEB887;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.BURLYWOOD = freezeObject(Color.fromCssColorString('#DEB887'));

    /**
     * An immutable Color instance initialized to CSS color #5F9EA0
     * <span class="colorSwath" style="background: #5F9EA0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CADETBLUE = freezeObject(Color.fromCssColorString('#5F9EA0'));
    /**
     * An immutable Color instance initialized to CSS color #7FFF00
     * <span class="colorSwath" style="background: #7FFF00;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CHARTREUSE = freezeObject(Color.fromCssColorString('#7FFF00'));

    /**
     * An immutable Color instance initialized to CSS color #D2691E
     * <span class="colorSwath" style="background: #D2691E;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CHOCOLATE = freezeObject(Color.fromCssColorString('#D2691E'));

    /**
     * An immutable Color instance initialized to CSS color #FF7F50
     * <span class="colorSwath" style="background: #FF7F50;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CORAL = freezeObject(Color.fromCssColorString('#FF7F50'));

    /**
     * An immutable Color instance initialized to CSS color #6495ED
     * <span class="colorSwath" style="background: #6495ED;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CORNFLOWERBLUE = freezeObject(Color.fromCssColorString('#6495ED'));

    /**
     * An immutable Color instance initialized to CSS color #FFF8DC
     * <span class="colorSwath" style="background: #FFF8DC;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CORNSILK = freezeObject(Color.fromCssColorString('#FFF8DC'));

    /**
     * An immutable Color instance initialized to CSS color #DC143C
     * <span class="colorSwath" style="background: #DC143C;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CRIMSON = freezeObject(Color.fromCssColorString('#DC143C'));

    /**
     * An immutable Color instance initialized to CSS color #00FFFF
     * <span class="colorSwath" style="background: #00FFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.CYAN = freezeObject(Color.fromCssColorString('#00FFFF'));

    /**
     * An immutable Color instance initialized to CSS color #00008B
     * <span class="colorSwath" style="background: #00008B;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKBLUE = freezeObject(Color.fromCssColorString('#00008B'));

    /**
     * An immutable Color instance initialized to CSS color #008B8B
     * <span class="colorSwath" style="background: #008B8B;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKCYAN = freezeObject(Color.fromCssColorString('#008B8B'));

    /**
     * An immutable Color instance initialized to CSS color #B8860B
     * <span class="colorSwath" style="background: #B8860B;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKGOLDENROD = freezeObject(Color.fromCssColorString('#B8860B'));

    /**
     * An immutable Color instance initialized to CSS color #A9A9A9
     * <span class="colorSwath" style="background: #A9A9A9;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKGRAY = freezeObject(Color.fromCssColorString('#A9A9A9'));

    /**
     * An immutable Color instance initialized to CSS color #006400
     * <span class="colorSwath" style="background: #006400;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKGREEN = freezeObject(Color.fromCssColorString('#006400'));

    /**
     * An immutable Color instance initialized to CSS color #A9A9A9
     * <span class="colorSwath" style="background: #A9A9A9;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKGREY = Color.DARKGRAY;

    /**
     * An immutable Color instance initialized to CSS color #BDB76B
     * <span class="colorSwath" style="background: #BDB76B;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKKHAKI = freezeObject(Color.fromCssColorString('#BDB76B'));

    /**
     * An immutable Color instance initialized to CSS color #8B008B
     * <span class="colorSwath" style="background: #8B008B;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKMAGENTA = freezeObject(Color.fromCssColorString('#8B008B'));

    /**
     * An immutable Color instance initialized to CSS color #556B2F
     * <span class="colorSwath" style="background: #556B2F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKOLIVEGREEN = freezeObject(Color.fromCssColorString('#556B2F'));

    /**
     * An immutable Color instance initialized to CSS color #FF8C00
     * <span class="colorSwath" style="background: #FF8C00;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKORANGE = freezeObject(Color.fromCssColorString('#FF8C00'));

    /**
     * An immutable Color instance initialized to CSS color #9932CC
     * <span class="colorSwath" style="background: #9932CC;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKORCHID = freezeObject(Color.fromCssColorString('#9932CC'));

    /**
     * An immutable Color instance initialized to CSS color #8B0000
     * <span class="colorSwath" style="background: #8B0000;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKRED = freezeObject(Color.fromCssColorString('#8B0000'));

    /**
     * An immutable Color instance initialized to CSS color #E9967A
     * <span class="colorSwath" style="background: #E9967A;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKSALMON = freezeObject(Color.fromCssColorString('#E9967A'));

    /**
     * An immutable Color instance initialized to CSS color #8FBC8F
     * <span class="colorSwath" style="background: #8FBC8F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKSEAGREEN = freezeObject(Color.fromCssColorString('#8FBC8F'));

    /**
     * An immutable Color instance initialized to CSS color #483D8B
     * <span class="colorSwath" style="background: #483D8B;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKSLATEBLUE = freezeObject(Color.fromCssColorString('#483D8B'));

    /**
     * An immutable Color instance initialized to CSS color #2F4F4F
     * <span class="colorSwath" style="background: #2F4F4F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKSLATEGRAY = freezeObject(Color.fromCssColorString('#2F4F4F'));

    /**
     * An immutable Color instance initialized to CSS color #2F4F4F
     * <span class="colorSwath" style="background: #2F4F4F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKSLATEGREY = Color.DARKSLATEGRAY;

    /**
     * An immutable Color instance initialized to CSS color #00CED1
     * <span class="colorSwath" style="background: #00CED1;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKTURQUOISE = freezeObject(Color.fromCssColorString('#00CED1'));

    /**
     * An immutable Color instance initialized to CSS color #9400D3
     * <span class="colorSwath" style="background: #9400D3;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DARKVIOLET = freezeObject(Color.fromCssColorString('#9400D3'));

    /**
     * An immutable Color instance initialized to CSS color #FF1493
     * <span class="colorSwath" style="background: #FF1493;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DEEPPINK = freezeObject(Color.fromCssColorString('#FF1493'));

    /**
     * An immutable Color instance initialized to CSS color #00BFFF
     * <span class="colorSwath" style="background: #00BFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DEEPSKYBLUE = freezeObject(Color.fromCssColorString('#00BFFF'));

    /**
     * An immutable Color instance initialized to CSS color #696969
     * <span class="colorSwath" style="background: #696969;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DIMGRAY = freezeObject(Color.fromCssColorString('#696969'));

    /**
     * An immutable Color instance initialized to CSS color #696969
     * <span class="colorSwath" style="background: #696969;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DIMGREY = Color.DIMGRAY;

    /**
     * An immutable Color instance initialized to CSS color #1E90FF
     * <span class="colorSwath" style="background: #1E90FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.DODGERBLUE = freezeObject(Color.fromCssColorString('#1E90FF'));

    /**
     * An immutable Color instance initialized to CSS color #B22222
     * <span class="colorSwath" style="background: #B22222;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.FIREBRICK = freezeObject(Color.fromCssColorString('#B22222'));

    /**
     * An immutable Color instance initialized to CSS color #FFFAF0
     * <span class="colorSwath" style="background: #FFFAF0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.FLORALWHITE = freezeObject(Color.fromCssColorString('#FFFAF0'));

    /**
     * An immutable Color instance initialized to CSS color #228B22
     * <span class="colorSwath" style="background: #228B22;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.FORESTGREEN = freezeObject(Color.fromCssColorString('#228B22'));

    /**
     * An immutable Color instance initialized to CSS color #FF00FF
     * <span class="colorSwath" style="background: #FF00FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.FUCHSIA = freezeObject(Color.fromCssColorString('#FF00FF'));

    /**
     * An immutable Color instance initialized to CSS color #DCDCDC
     * <span class="colorSwath" style="background: #DCDCDC;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GAINSBORO = freezeObject(Color.fromCssColorString('#DCDCDC'));

    /**
     * An immutable Color instance initialized to CSS color #F8F8FF
     * <span class="colorSwath" style="background: #F8F8FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GHOSTWHITE = freezeObject(Color.fromCssColorString('#F8F8FF'));

    /**
     * An immutable Color instance initialized to CSS color #FFD700
     * <span class="colorSwath" style="background: #FFD700;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GOLD = freezeObject(Color.fromCssColorString('#FFD700'));

    /**
     * An immutable Color instance initialized to CSS color #DAA520
     * <span class="colorSwath" style="background: #DAA520;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GOLDENROD = freezeObject(Color.fromCssColorString('#DAA520'));

    /**
     * An immutable Color instance initialized to CSS color #808080
     * <span class="colorSwath" style="background: #808080;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GRAY = freezeObject(Color.fromCssColorString('#808080'));

    /**
     * An immutable Color instance initialized to CSS color #008000
     * <span class="colorSwath" style="background: #008000;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GREEN = freezeObject(Color.fromCssColorString('#008000'));

    /**
     * An immutable Color instance initialized to CSS color #ADFF2F
     * <span class="colorSwath" style="background: #ADFF2F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GREENYELLOW = freezeObject(Color.fromCssColorString('#ADFF2F'));

    /**
     * An immutable Color instance initialized to CSS color #808080
     * <span class="colorSwath" style="background: #808080;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.GREY = Color.GRAY;

    /**
     * An immutable Color instance initialized to CSS color #F0FFF0
     * <span class="colorSwath" style="background: #F0FFF0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.HONEYDEW = freezeObject(Color.fromCssColorString('#F0FFF0'));

    /**
     * An immutable Color instance initialized to CSS color #FF69B4
     * <span class="colorSwath" style="background: #FF69B4;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.HOTPINK = freezeObject(Color.fromCssColorString('#FF69B4'));

    /**
     * An immutable Color instance initialized to CSS color #CD5C5C
     * <span class="colorSwath" style="background: #CD5C5C;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.INDIANRED = freezeObject(Color.fromCssColorString('#CD5C5C'));

    /**
     * An immutable Color instance initialized to CSS color #4B0082
     * <span class="colorSwath" style="background: #4B0082;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.INDIGO = freezeObject(Color.fromCssColorString('#4B0082'));

    /**
     * An immutable Color instance initialized to CSS color #FFFFF0
     * <span class="colorSwath" style="background: #FFFFF0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.IVORY = freezeObject(Color.fromCssColorString('#FFFFF0'));

    /**
     * An immutable Color instance initialized to CSS color #F0E68C
     * <span class="colorSwath" style="background: #F0E68C;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.KHAKI = freezeObject(Color.fromCssColorString('#F0E68C'));

    /**
     * An immutable Color instance initialized to CSS color #E6E6FA
     * <span class="colorSwath" style="background: #E6E6FA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LAVENDER = freezeObject(Color.fromCssColorString('#E6E6FA'));

    /**
     * An immutable Color instance initialized to CSS color #FFF0F5
     * <span class="colorSwath" style="background: #FFF0F5;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LAVENDAR_BLUSH = freezeObject(Color.fromCssColorString('#FFF0F5'));

    /**
     * An immutable Color instance initialized to CSS color #7CFC00
     * <span class="colorSwath" style="background: #7CFC00;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LAWNGREEN = freezeObject(Color.fromCssColorString('#7CFC00'));

    /**
     * An immutable Color instance initialized to CSS color #FFFACD
     * <span class="colorSwath" style="background: #FFFACD;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LEMONCHIFFON = freezeObject(Color.fromCssColorString('#FFFACD'));

    /**
     * An immutable Color instance initialized to CSS color #ADD8E6
     * <span class="colorSwath" style="background: #ADD8E6;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTBLUE = freezeObject(Color.fromCssColorString('#ADD8E6'));

    /**
     * An immutable Color instance initialized to CSS color #F08080
     * <span class="colorSwath" style="background: #F08080;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTCORAL = freezeObject(Color.fromCssColorString('#F08080'));

    /**
     * An immutable Color instance initialized to CSS color #E0FFFF
     * <span class="colorSwath" style="background: #E0FFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTCYAN = freezeObject(Color.fromCssColorString('#E0FFFF'));

    /**
     * An immutable Color instance initialized to CSS color #FAFAD2
     * <span class="colorSwath" style="background: #FAFAD2;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTGOLDENRODYELLOW = freezeObject(Color.fromCssColorString('#FAFAD2'));

    /**
     * An immutable Color instance initialized to CSS color #D3D3D3
     * <span class="colorSwath" style="background: #D3D3D3;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTGRAY = freezeObject(Color.fromCssColorString('#D3D3D3'));

    /**
     * An immutable Color instance initialized to CSS color #90EE90
     * <span class="colorSwath" style="background: #90EE90;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTGREEN = freezeObject(Color.fromCssColorString('#90EE90'));

    /**
     * An immutable Color instance initialized to CSS color #D3D3D3
     * <span class="colorSwath" style="background: #D3D3D3;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTGREY = Color.LIGHTGRAY;

    /**
     * An immutable Color instance initialized to CSS color #FFB6C1
     * <span class="colorSwath" style="background: #FFB6C1;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTPINK = freezeObject(Color.fromCssColorString('#FFB6C1'));

    /**
     * An immutable Color instance initialized to CSS color #20B2AA
     * <span class="colorSwath" style="background: #20B2AA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTSEAGREEN = freezeObject(Color.fromCssColorString('#20B2AA'));

    /**
     * An immutable Color instance initialized to CSS color #87CEFA
     * <span class="colorSwath" style="background: #87CEFA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTSKYBLUE = freezeObject(Color.fromCssColorString('#87CEFA'));

    /**
     * An immutable Color instance initialized to CSS color #778899
     * <span class="colorSwath" style="background: #778899;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTSLATEGRAY = freezeObject(Color.fromCssColorString('#778899'));

    /**
     * An immutable Color instance initialized to CSS color #778899
     * <span class="colorSwath" style="background: #778899;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTSLATEGREY = Color.LIGHTSLATEGRAY;

    /**
     * An immutable Color instance initialized to CSS color #B0C4DE
     * <span class="colorSwath" style="background: #B0C4DE;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTSTEELBLUE = freezeObject(Color.fromCssColorString('#B0C4DE'));

    /**
     * An immutable Color instance initialized to CSS color #FFFFE0
     * <span class="colorSwath" style="background: #FFFFE0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIGHTYELLOW = freezeObject(Color.fromCssColorString('#FFFFE0'));

    /**
     * An immutable Color instance initialized to CSS color #00FF00
     * <span class="colorSwath" style="background: #00FF00;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIME = freezeObject(Color.fromCssColorString('#00FF00'));

    /**
     * An immutable Color instance initialized to CSS color #32CD32
     * <span class="colorSwath" style="background: #32CD32;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LIMEGREEN = freezeObject(Color.fromCssColorString('#32CD32'));

    /**
     * An immutable Color instance initialized to CSS color #FAF0E6
     * <span class="colorSwath" style="background: #FAF0E6;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.LINEN = freezeObject(Color.fromCssColorString('#FAF0E6'));

    /**
     * An immutable Color instance initialized to CSS color #FF00FF
     * <span class="colorSwath" style="background: #FF00FF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MAGENTA = freezeObject(Color.fromCssColorString('#FF00FF'));

    /**
     * An immutable Color instance initialized to CSS color #800000
     * <span class="colorSwath" style="background: #800000;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MAROON = freezeObject(Color.fromCssColorString('#800000'));

    /**
     * An immutable Color instance initialized to CSS color #66CDAA
     * <span class="colorSwath" style="background: #66CDAA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMAQUAMARINE = freezeObject(Color.fromCssColorString('#66CDAA'));

    /**
     * An immutable Color instance initialized to CSS color #0000CD
     * <span class="colorSwath" style="background: #0000CD;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMBLUE = freezeObject(Color.fromCssColorString('#0000CD'));

    /**
     * An immutable Color instance initialized to CSS color #BA55D3
     * <span class="colorSwath" style="background: #BA55D3;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMORCHID = freezeObject(Color.fromCssColorString('#BA55D3'));

    /**
     * An immutable Color instance initialized to CSS color #9370DB
     * <span class="colorSwath" style="background: #9370DB;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMPURPLE = freezeObject(Color.fromCssColorString('#9370DB'));

    /**
     * An immutable Color instance initialized to CSS color #3CB371
     * <span class="colorSwath" style="background: #3CB371;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMSEAGREEN = freezeObject(Color.fromCssColorString('#3CB371'));

    /**
     * An immutable Color instance initialized to CSS color #7B68EE
     * <span class="colorSwath" style="background: #7B68EE;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMSLATEBLUE = freezeObject(Color.fromCssColorString('#7B68EE'));

    /**
     * An immutable Color instance initialized to CSS color #00FA9A
     * <span class="colorSwath" style="background: #00FA9A;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMSPRINGGREEN = freezeObject(Color.fromCssColorString('#00FA9A'));

    /**
     * An immutable Color instance initialized to CSS color #48D1CC
     * <span class="colorSwath" style="background: #48D1CC;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMTURQUOISE = freezeObject(Color.fromCssColorString('#48D1CC'));

    /**
     * An immutable Color instance initialized to CSS color #C71585
     * <span class="colorSwath" style="background: #C71585;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MEDIUMVIOLETRED = freezeObject(Color.fromCssColorString('#C71585'));

    /**
     * An immutable Color instance initialized to CSS color #191970
     * <span class="colorSwath" style="background: #191970;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MIDNIGHTBLUE = freezeObject(Color.fromCssColorString('#191970'));

    /**
     * An immutable Color instance initialized to CSS color #F5FFFA
     * <span class="colorSwath" style="background: #F5FFFA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MINTCREAM = freezeObject(Color.fromCssColorString('#F5FFFA'));

    /**
     * An immutable Color instance initialized to CSS color #FFE4E1
     * <span class="colorSwath" style="background: #FFE4E1;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MISTYROSE = freezeObject(Color.fromCssColorString('#FFE4E1'));

    /**
     * An immutable Color instance initialized to CSS color #FFE4B5
     * <span class="colorSwath" style="background: #FFE4B5;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.MOCCASIN = freezeObject(Color.fromCssColorString('#FFE4B5'));

    /**
     * An immutable Color instance initialized to CSS color #FFDEAD
     * <span class="colorSwath" style="background: #FFDEAD;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.NAVAJOWHITE = freezeObject(Color.fromCssColorString('#FFDEAD'));

    /**
     * An immutable Color instance initialized to CSS color #000080
     * <span class="colorSwath" style="background: #000080;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.NAVY = freezeObject(Color.fromCssColorString('#000080'));

    /**
     * An immutable Color instance initialized to CSS color #FDF5E6
     * <span class="colorSwath" style="background: #FDF5E6;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.OLDLACE = freezeObject(Color.fromCssColorString('#FDF5E6'));

    /**
     * An immutable Color instance initialized to CSS color #808000
     * <span class="colorSwath" style="background: #808000;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.OLIVE = freezeObject(Color.fromCssColorString('#808000'));

    /**
     * An immutable Color instance initialized to CSS color #6B8E23
     * <span class="colorSwath" style="background: #6B8E23;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.OLIVEDRAB = freezeObject(Color.fromCssColorString('#6B8E23'));

    /**
     * An immutable Color instance initialized to CSS color #FFA500
     * <span class="colorSwath" style="background: #FFA500;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ORANGE = freezeObject(Color.fromCssColorString('#FFA500'));

    /**
     * An immutable Color instance initialized to CSS color #FF4500
     * <span class="colorSwath" style="background: #FF4500;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ORANGERED = freezeObject(Color.fromCssColorString('#FF4500'));

    /**
     * An immutable Color instance initialized to CSS color #DA70D6
     * <span class="colorSwath" style="background: #DA70D6;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ORCHID = freezeObject(Color.fromCssColorString('#DA70D6'));

    /**
     * An immutable Color instance initialized to CSS color #EEE8AA
     * <span class="colorSwath" style="background: #EEE8AA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PALEGOLDENROD = freezeObject(Color.fromCssColorString('#EEE8AA'));

    /**
     * An immutable Color instance initialized to CSS color #98FB98
     * <span class="colorSwath" style="background: #98FB98;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PALEGREEN = freezeObject(Color.fromCssColorString('#98FB98'));

    /**
     * An immutable Color instance initialized to CSS color #AFEEEE
     * <span class="colorSwath" style="background: #AFEEEE;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PALETURQUOISE = freezeObject(Color.fromCssColorString('#AFEEEE'));

    /**
     * An immutable Color instance initialized to CSS color #DB7093
     * <span class="colorSwath" style="background: #DB7093;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PALEVIOLETRED = freezeObject(Color.fromCssColorString('#DB7093'));

    /**
     * An immutable Color instance initialized to CSS color #FFEFD5
     * <span class="colorSwath" style="background: #FFEFD5;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PAPAYAWHIP = freezeObject(Color.fromCssColorString('#FFEFD5'));

    /**
     * An immutable Color instance initialized to CSS color #FFDAB9
     * <span class="colorSwath" style="background: #FFDAB9;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PEACHPUFF = freezeObject(Color.fromCssColorString('#FFDAB9'));

    /**
     * An immutable Color instance initialized to CSS color #CD853F
     * <span class="colorSwath" style="background: #CD853F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PERU = freezeObject(Color.fromCssColorString('#CD853F'));

    /**
     * An immutable Color instance initialized to CSS color #FFC0CB
     * <span class="colorSwath" style="background: #FFC0CB;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PINK = freezeObject(Color.fromCssColorString('#FFC0CB'));

    /**
     * An immutable Color instance initialized to CSS color #DDA0DD
     * <span class="colorSwath" style="background: #DDA0DD;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PLUM = freezeObject(Color.fromCssColorString('#DDA0DD'));

    /**
     * An immutable Color instance initialized to CSS color #B0E0E6
     * <span class="colorSwath" style="background: #B0E0E6;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.POWDERBLUE = freezeObject(Color.fromCssColorString('#B0E0E6'));

    /**
     * An immutable Color instance initialized to CSS color #800080
     * <span class="colorSwath" style="background: #800080;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.PURPLE = freezeObject(Color.fromCssColorString('#800080'));

    /**
     * An immutable Color instance initialized to CSS color #FF0000
     * <span class="colorSwath" style="background: #FF0000;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.RED = freezeObject(Color.fromCssColorString('#FF0000'));

    /**
     * An immutable Color instance initialized to CSS color #BC8F8F
     * <span class="colorSwath" style="background: #BC8F8F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ROSYBROWN = freezeObject(Color.fromCssColorString('#BC8F8F'));

    /**
     * An immutable Color instance initialized to CSS color #4169E1
     * <span class="colorSwath" style="background: #4169E1;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.ROYALBLUE = freezeObject(Color.fromCssColorString('#4169E1'));

    /**
     * An immutable Color instance initialized to CSS color #8B4513
     * <span class="colorSwath" style="background: #8B4513;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SADDLEBROWN = freezeObject(Color.fromCssColorString('#8B4513'));

    /**
     * An immutable Color instance initialized to CSS color #FA8072
     * <span class="colorSwath" style="background: #FA8072;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SALMON = freezeObject(Color.fromCssColorString('#FA8072'));

    /**
     * An immutable Color instance initialized to CSS color #F4A460
     * <span class="colorSwath" style="background: #F4A460;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SANDYBROWN = freezeObject(Color.fromCssColorString('#F4A460'));

    /**
     * An immutable Color instance initialized to CSS color #2E8B57
     * <span class="colorSwath" style="background: #2E8B57;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SEAGREEN = freezeObject(Color.fromCssColorString('#2E8B57'));

    /**
     * An immutable Color instance initialized to CSS color #FFF5EE
     * <span class="colorSwath" style="background: #FFF5EE;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SEASHELL = freezeObject(Color.fromCssColorString('#FFF5EE'));

    /**
     * An immutable Color instance initialized to CSS color #A0522D
     * <span class="colorSwath" style="background: #A0522D;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SIENNA = freezeObject(Color.fromCssColorString('#A0522D'));

    /**
     * An immutable Color instance initialized to CSS color #C0C0C0
     * <span class="colorSwath" style="background: #C0C0C0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SILVER = freezeObject(Color.fromCssColorString('#C0C0C0'));

    /**
     * An immutable Color instance initialized to CSS color #87CEEB
     * <span class="colorSwath" style="background: #87CEEB;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SKYBLUE = freezeObject(Color.fromCssColorString('#87CEEB'));

    /**
     * An immutable Color instance initialized to CSS color #6A5ACD
     * <span class="colorSwath" style="background: #6A5ACD;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SLATEBLUE = freezeObject(Color.fromCssColorString('#6A5ACD'));

    /**
     * An immutable Color instance initialized to CSS color #708090
     * <span class="colorSwath" style="background: #708090;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SLATEGRAY = freezeObject(Color.fromCssColorString('#708090'));

    /**
     * An immutable Color instance initialized to CSS color #708090
     * <span class="colorSwath" style="background: #708090;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SLATEGREY = Color.SLATEGRAY;

    /**
     * An immutable Color instance initialized to CSS color #FFFAFA
     * <span class="colorSwath" style="background: #FFFAFA;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SNOW = freezeObject(Color.fromCssColorString('#FFFAFA'));

    /**
     * An immutable Color instance initialized to CSS color #00FF7F
     * <span class="colorSwath" style="background: #00FF7F;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.SPRINGGREEN = freezeObject(Color.fromCssColorString('#00FF7F'));

    /**
     * An immutable Color instance initialized to CSS color #4682B4
     * <span class="colorSwath" style="background: #4682B4;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.STEELBLUE = freezeObject(Color.fromCssColorString('#4682B4'));

    /**
     * An immutable Color instance initialized to CSS color #D2B48C
     * <span class="colorSwath" style="background: #D2B48C;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.TAN = freezeObject(Color.fromCssColorString('#D2B48C'));

    /**
     * An immutable Color instance initialized to CSS color #008080
     * <span class="colorSwath" style="background: #008080;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.TEAL = freezeObject(Color.fromCssColorString('#008080'));

    /**
     * An immutable Color instance initialized to CSS color #D8BFD8
     * <span class="colorSwath" style="background: #D8BFD8;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.THISTLE = freezeObject(Color.fromCssColorString('#D8BFD8'));

    /**
     * An immutable Color instance initialized to CSS color #FF6347
     * <span class="colorSwath" style="background: #FF6347;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.TOMATO = freezeObject(Color.fromCssColorString('#FF6347'));

    /**
     * An immutable Color instance initialized to CSS color #40E0D0
     * <span class="colorSwath" style="background: #40E0D0;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.TURQUOISE = freezeObject(Color.fromCssColorString('#40E0D0'));

    /**
     * An immutable Color instance initialized to CSS color #EE82EE
     * <span class="colorSwath" style="background: #EE82EE;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.VIOLET = freezeObject(Color.fromCssColorString('#EE82EE'));

    /**
     * An immutable Color instance initialized to CSS color #F5DEB3
     * <span class="colorSwath" style="background: #F5DEB3;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.WHEAT = freezeObject(Color.fromCssColorString('#F5DEB3'));

    /**
     * An immutable Color instance initialized to CSS color #FFFFFF
     * <span class="colorSwath" style="background: #FFFFFF;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.WHITE = freezeObject(Color.fromCssColorString('#FFFFFF'));

    /**
     * An immutable Color instance initialized to CSS color #F5F5F5
     * <span class="colorSwath" style="background: #F5F5F5;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.WHITESMOKE = freezeObject(Color.fromCssColorString('#F5F5F5'));

    /**
     * An immutable Color instance initialized to CSS color #FFFF00
     * <span class="colorSwath" style="background: #FFFF00;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.YELLOW = freezeObject(Color.fromCssColorString('#FFFF00'));

    /**
     * An immutable Color instance initialized to CSS color #9ACD32
     * <span class="colorSwath" style="background: #9ACD32;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.YELLOWGREEN = freezeObject(Color.fromCssColorString('#9ACD32'));

    /**
     * An immutable Color instance initialized to CSS transparent.
     * <span class="colorSwath" style="background: transparent;"></span>
     *
     * @constant
     * @type {Color}
     */
    Color.TRANSPARENT = freezeObject(new Color(0, 0, 0, 0));

    return Color;
});

/*global define*/
define('Core/ColorGeometryInstanceAttribute',[
        './Color',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError'
    ], function(
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Value and type information for per-instance geometry color.
     *
     * @alias ColorGeometryInstanceAttribute
     * @constructor
     *
     * @param {Number} [red=1.0] The red component.
     * @param {Number} [green=1.0] The green component.
     * @param {Number} [blue=1.0] The blue component.
     * @param {Number} [alpha=1.0] The alpha component.
     *
     *
     * @example
     * var instance = new Cesium.GeometryInstance({
     *   geometry : Cesium.BoxGeometry.fromDimensions({
     *     dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 500000.0)
     *   }),
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(0.0, 0.0)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
     *   id : 'box',
     *   attributes : {
     *     color : new Cesium.ColorGeometryInstanceAttribute(red, green, blue, alpha)
     *   }
     * });
     * 
     * @see GeometryInstance
     * @see GeometryInstanceAttribute
     */
    function ColorGeometryInstanceAttribute(red, green, blue, alpha) {
        red = defaultValue(red, 1.0);
        green = defaultValue(green, 1.0);
        blue = defaultValue(blue, 1.0);
        alpha = defaultValue(alpha, 1.0);

        /**
         * The values for the attributes stored in a typed array.
         *
         * @type Uint8Array
         *
         * @default [255, 255, 255, 255]
         */
        this.value = new Uint8Array([
            Color.floatToByte(red),
            Color.floatToByte(green),
            Color.floatToByte(blue),
            Color.floatToByte(alpha)
        ]);
    }

    defineProperties(ColorGeometryInstanceAttribute.prototype, {
        /**
         * The datatype of each component in the attribute, e.g., individual elements in
         * {@link ColorGeometryInstanceAttribute#value}.
         *
         * @memberof ColorGeometryInstanceAttribute.prototype
         *
         * @type {ComponentDatatype}
         * @readonly
         *
         * @default {@link ComponentDatatype.UNSIGNED_BYTE}
         */
        componentDatatype : {
            get : function() {
                return ComponentDatatype.UNSIGNED_BYTE;
            }
        },

        /**
         * The number of components in the attributes, i.e., {@link ColorGeometryInstanceAttribute#value}.
         *
         * @memberof ColorGeometryInstanceAttribute.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @default 4
         */
        componentsPerAttribute : {
            get : function() {
                return 4;
            }
        },

        /**
         * When <code>true</code> and <code>componentDatatype</code> is an integer format,
         * indicate that the components should be mapped to the range [0, 1] (unsigned)
         * or [-1, 1] (signed) when they are accessed as floating-point for rendering.
         *
         * @memberof ColorGeometryInstanceAttribute.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        normalize : {
            get : function() {
                return true;
            }
        }
    });

    /**
     * Creates a new {@link ColorGeometryInstanceAttribute} instance given the provided {@link Color}.
     *
     * @param {Color} color The color.
     * @returns {ColorGeometryInstanceAttribute} The new {@link ColorGeometryInstanceAttribute} instance.
     *
     * @example
     * var instance = new Cesium.GeometryInstance({
     *   geometry : geometry,
     *   attributes : {
     *     color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.CORNFLOWERBLUE),
     *   }
     * });
     */
    ColorGeometryInstanceAttribute.fromColor = function(color) {
                if (!defined(color)) {
            throw new DeveloperError('color is required.');
        }
        
        return new ColorGeometryInstanceAttribute(color.red, color.green, color.blue, color.alpha);
    };

    /**
     * Converts a color to a typed array that can be used to assign a color attribute.
     *
     * @param {Color} color The color.
     * @param {Uint8Array} [result] The array to store the result in, if undefined a new instance will be created.
     *
     * @returns {Uint8Array} The modified result parameter or a new instance if result was undefined.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA, attributes.color);
     */
    ColorGeometryInstanceAttribute.toValue = function(color, result) {
                if (!defined(color)) {
            throw new DeveloperError('color is required.');
        }
        
        if (!defined(result)) {
            return new Uint8Array(color.toBytes());
        }
        return color.toBytes(result);
    };

    /**
     * Compares the provided ColorGeometryInstanceAttributes and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {ColorGeometryInstanceAttribute} [left] The first ColorGeometryInstanceAttribute.
     * @param {ColorGeometryInstanceAttribute} [right] The second ColorGeometryInstanceAttribute.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    ColorGeometryInstanceAttribute.equals = function(left, right) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                left.value[0] === right.value[0] &&
                left.value[1] === right.value[1] &&
                left.value[2] === right.value[2] &&
                left.value[3] === right.value[3]);
    };

    return ColorGeometryInstanceAttribute;
});

/*global define*/
define('Core/CompressedTextureBuffer',[
        './defined',
        './defineProperties'
    ], function(
        defined,
        defineProperties
    ) {
    'use strict';

    /**
     * Describes a compressed texture and contains a compressed texture buffer.
     *
     * @param {PixelFormat} internalFormat The pixel format of the compressed texture.
     * @param {Number} width The width of the texture.
     * @param {Number} height The height of the texture.
     * @param {Uint8Array} buffer The compressed texture buffer.
     */
    function CompressedTextureBuffer(internalFormat, width, height, buffer) {
        this._format = internalFormat;
        this._width = width;
        this._height = height;
        this._buffer =  buffer;
    }

    defineProperties(CompressedTextureBuffer.prototype, {
        /**
         * The format of the compressed texture.
         * @type PixelFormat
         * @readonly
         */
        internalFormat : {
            get : function() {
                return this._format;
            }
        },
        /**
         * The width of the texture.
         * @type Number
         * @readonly
         */
        width : {
            get : function() {
                return this._width;
            }
        },
        /**
         * The height of the texture.
         * @type Number
         * @readonly
         */
        height : {
            get : function() {
                return this._height;
            }
        },
        /**
         * The compressed texture buffer.
         * @type Uint8Array
         * @readonly
         */
        bufferView : {
            get : function() {
                return this._buffer;
            }
        }
    });

    /**
     * Creates a shallow clone of a compressed texture buffer.
     *
     * @param {CompressedTextureBuffer} object The compressed texture buffer to be cloned.
     * @return {CompressedTextureBuffer} A shallow clone of the compressed texture buffer.
     */
    CompressedTextureBuffer.clone = function(object) {
        if (!defined(object)) {
            return undefined;
        }

        return new CompressedTextureBuffer(object._format, object._width, object._height, object._buffer);
    };

    /**
     * Creates a shallow clone of this compressed texture buffer.
     *
     * @return {CompressedTextureBuffer} A shallow clone of the compressed texture buffer.
     */
    CompressedTextureBuffer.prototype.clone = function() {
        return CompressedTextureBuffer.clone(this);
    };

    return CompressedTextureBuffer;
});
/*global define*/
define('Core/CornerType',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Style options for corners.
     *
     * @demo The {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html&label=Geometries|Corridor Demo}
     * demonstrates the three corner types, as used by {@link CorridorGraphics}.
     *
     * @exports CornerType
     */
    var CornerType = {
        /**
         * <img src="images/CornerTypeRounded.png" style="vertical-align: middle;" width="186" height="189" />
         *
         * Corner has a smooth edge.
         * @type {Number}
         * @constant
         */
        ROUNDED : 0,

        /**
         * <img src="images/CornerTypeMitered.png" style="vertical-align: middle;" width="186" height="189" />
         *
         * Corner point is the intersection of adjacent edges.
         * @type {Number}
         * @constant
         */
        MITERED : 1,

        /**
         * <img src="images/CornerTypeBeveled.png" style="vertical-align: middle;" width="186" height="189" />
         *
         * Corner is clipped.
         * @type {Number}
         * @constant
         */
        BEVELED : 2
    };

    return freezeObject(CornerType);
});

/*global define*/
define('Core/EllipsoidGeodesic',[
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './Math'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        CesiumMath) {
    'use strict';

    function setConstants(ellipsoidGeodesic) {
        var uSquared = ellipsoidGeodesic._uSquared;
        var a = ellipsoidGeodesic._ellipsoid.maximumRadius;
        var b = ellipsoidGeodesic._ellipsoid.minimumRadius;
        var f = (a - b) / a;

        var cosineHeading = Math.cos(ellipsoidGeodesic._startHeading);
        var sineHeading = Math.sin(ellipsoidGeodesic._startHeading);

        var tanU = (1 - f) * Math.tan(ellipsoidGeodesic._start.latitude);

        var cosineU = 1.0 / Math.sqrt(1.0 + tanU * tanU);
        var sineU = cosineU * tanU;

        var sigma = Math.atan2(tanU, cosineHeading);

        var sineAlpha = cosineU * sineHeading;
        var sineSquaredAlpha = sineAlpha * sineAlpha;

        var cosineSquaredAlpha = 1.0 - sineSquaredAlpha;
        var cosineAlpha = Math.sqrt(cosineSquaredAlpha);

        var u2Over4 = uSquared / 4.0;
        var u4Over16 = u2Over4 * u2Over4;
        var u6Over64 = u4Over16 * u2Over4;
        var u8Over256 = u4Over16 * u4Over16;

        var a0 = (1.0 + u2Over4 - 3.0 * u4Over16 / 4.0 + 5.0 * u6Over64 / 4.0 - 175.0 * u8Over256 / 64.0);
        var a1 = (1.0 - u2Over4 + 15.0 * u4Over16 / 8.0 - 35.0 * u6Over64 / 8.0);
        var a2 = (1.0 - 3.0 * u2Over4 + 35.0 * u4Over16 / 4.0);
        var a3 = (1.0 - 5.0 * u2Over4);

        var distanceRatio = a0 * sigma - a1 * Math.sin(2.0 * sigma) * u2Over4 / 2.0 - a2 * Math.sin(4.0 * sigma) * u4Over16 / 16.0 -
                            a3 * Math.sin(6.0 * sigma) * u6Over64 / 48.0 - Math.sin(8.0 * sigma) * 5.0 * u8Over256 / 512;

        var constants = ellipsoidGeodesic._constants;

        constants.a = a;
        constants.b = b;
        constants.f = f;
        constants.cosineHeading = cosineHeading;
        constants.sineHeading = sineHeading;
        constants.tanU = tanU;
        constants.cosineU = cosineU;
        constants.sineU = sineU;
        constants.sigma = sigma;
        constants.sineAlpha = sineAlpha;
        constants.sineSquaredAlpha = sineSquaredAlpha;
        constants.cosineSquaredAlpha = cosineSquaredAlpha;
        constants.cosineAlpha = cosineAlpha;
        constants.u2Over4 = u2Over4;
        constants.u4Over16 = u4Over16;
        constants.u6Over64 = u6Over64;
        constants.u8Over256 = u8Over256;
        constants.a0 = a0;
        constants.a1 = a1;
        constants.a2 = a2;
        constants.a3 = a3;
        constants.distanceRatio = distanceRatio;
    }

    function computeC(f, cosineSquaredAlpha) {
        return f * cosineSquaredAlpha * (4.0 + f * (4.0 - 3.0 * cosineSquaredAlpha)) / 16.0;
    }

    function computeDeltaLambda(f, sineAlpha, cosineSquaredAlpha, sigma, sineSigma, cosineSigma, cosineTwiceSigmaMidpoint) {
        var C = computeC(f, cosineSquaredAlpha);

        return (1.0 - C) * f * sineAlpha * (sigma + C * sineSigma * (cosineTwiceSigmaMidpoint +
                C * cosineSigma * (2.0 * cosineTwiceSigmaMidpoint * cosineTwiceSigmaMidpoint - 1.0)));
    }

    function vincentyInverseFormula(ellipsoidGeodesic, major, minor, firstLongitude, firstLatitude, secondLongitude, secondLatitude) {
        var eff = (major - minor) / major;
        var l = secondLongitude - firstLongitude;

        var u1 = Math.atan((1 - eff) * Math.tan(firstLatitude));
        var u2 = Math.atan((1 - eff) * Math.tan(secondLatitude));

        var cosineU1 = Math.cos(u1);
        var sineU1 = Math.sin(u1);
        var cosineU2 = Math.cos(u2);
        var sineU2 = Math.sin(u2);

        var cc = cosineU1 * cosineU2;
        var cs = cosineU1 * sineU2;
        var ss = sineU1 * sineU2;
        var sc = sineU1 * cosineU2;

        var lambda = l;
        var lambdaDot = CesiumMath.TWO_PI;

        var cosineLambda = Math.cos(lambda);
        var sineLambda = Math.sin(lambda);

        var sigma;
        var cosineSigma;
        var sineSigma;
        var cosineSquaredAlpha;
        var cosineTwiceSigmaMidpoint;

        do {
            cosineLambda = Math.cos(lambda);
            sineLambda = Math.sin(lambda);

            var temp = cs - sc * cosineLambda;
            sineSigma = Math.sqrt(cosineU2 * cosineU2 * sineLambda * sineLambda + temp * temp);
            cosineSigma = ss + cc * cosineLambda;

            sigma = Math.atan2(sineSigma, cosineSigma);

            var sineAlpha;

            if (sineSigma === 0.0) {
                sineAlpha = 0.0;
                cosineSquaredAlpha = 1.0;
            } else {
                sineAlpha = cc * sineLambda / sineSigma;
                cosineSquaredAlpha = 1.0 - sineAlpha * sineAlpha;
            }

            lambdaDot = lambda;

            cosineTwiceSigmaMidpoint = cosineSigma - 2.0 * ss / cosineSquaredAlpha;

            if (isNaN(cosineTwiceSigmaMidpoint)) {
                cosineTwiceSigmaMidpoint = 0.0;
            }

            lambda = l + computeDeltaLambda(eff, sineAlpha, cosineSquaredAlpha,
                                            sigma, sineSigma, cosineSigma, cosineTwiceSigmaMidpoint);
        } while (Math.abs(lambda - lambdaDot) > CesiumMath.EPSILON12);

        var uSquared = cosineSquaredAlpha * (major * major - minor * minor) / (minor * minor);
        var A = 1.0 + uSquared * (4096.0 + uSquared * (uSquared * (320.0 - 175.0 * uSquared) - 768.0)) / 16384.0;
        var B = uSquared * (256.0 + uSquared * (uSquared * (74.0 - 47.0 * uSquared) - 128.0)) / 1024.0;

        var cosineSquaredTwiceSigmaMidpoint = cosineTwiceSigmaMidpoint * cosineTwiceSigmaMidpoint;
        var deltaSigma = B * sineSigma * (cosineTwiceSigmaMidpoint + B * (cosineSigma *
                (2.0 * cosineSquaredTwiceSigmaMidpoint - 1.0) - B * cosineTwiceSigmaMidpoint *
                (4.0 * sineSigma * sineSigma - 3.0) * (4.0 * cosineSquaredTwiceSigmaMidpoint - 3.0) / 6.0) / 4.0);

        var distance = minor * A * (sigma - deltaSigma);

        var startHeading = Math.atan2(cosineU2 * sineLambda, cs - sc * cosineLambda);
        var endHeading = Math.atan2(cosineU1 * sineLambda, cs * cosineLambda - sc);

        ellipsoidGeodesic._distance = distance;
        ellipsoidGeodesic._startHeading = startHeading;
        ellipsoidGeodesic._endHeading = endHeading;
        ellipsoidGeodesic._uSquared = uSquared;
    }

    function computeProperties(ellipsoidGeodesic, start, end, ellipsoid) {
        var firstCartesian = Cartesian3.normalize(ellipsoid.cartographicToCartesian(start, scratchCart2), scratchCart1);
        var lastCartesian = Cartesian3.normalize(ellipsoid.cartographicToCartesian(end, scratchCart2), scratchCart2);

                if (Math.abs(Math.abs(Cartesian3.angleBetween(firstCartesian, lastCartesian)) - Math.PI) < 0.0125) {
            throw new DeveloperError('geodesic position is not unique');
        }
        
        vincentyInverseFormula(ellipsoidGeodesic, ellipsoid.maximumRadius, ellipsoid.minimumRadius,
                               start.longitude, start.latitude, end.longitude, end.latitude);

        ellipsoidGeodesic._start = Cartographic.clone(start, ellipsoidGeodesic._start);
        ellipsoidGeodesic._end = Cartographic.clone(end, ellipsoidGeodesic._end);
        ellipsoidGeodesic._start.height = 0;
        ellipsoidGeodesic._end.height = 0;

        setConstants(ellipsoidGeodesic);
    }

    var scratchCart1 = new Cartesian3();
    var scratchCart2 = new Cartesian3();
    /**
     * Initializes a geodesic on the ellipsoid connecting the two provided planetodetic points.
     *
     * @alias EllipsoidGeodesic
     * @constructor
     *
     * @param {Cartographic} [start] The initial planetodetic point on the path.
     * @param {Cartographic} [end] The final planetodetic point on the path.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the geodesic lies.
     */
    function EllipsoidGeodesic(start, end, ellipsoid) {
        var e = defaultValue(ellipsoid, Ellipsoid.WGS84);
        this._ellipsoid = e;
        this._start = new Cartographic();
        this._end = new Cartographic();

        this._constants = {};
        this._startHeading = undefined;
        this._endHeading = undefined;
        this._distance = undefined;
        this._uSquared = undefined;

        if (defined(start) && defined(end)) {
            computeProperties(this, start, end, e);
        }
    }

    defineProperties(EllipsoidGeodesic.prototype, {
        /**
         * Gets the ellipsoid.
         * @memberof EllipsoidGeodesic.prototype
         * @type {Ellipsoid}
         * @readonly
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        },

        /**
         * Gets the surface distance between the start and end point
         * @memberof EllipsoidGeodesic.prototype
         * @type {Number}
         * @readonly
         */
        surfaceDistance : {
            get : function() {
                                if (!defined(this._distance)) {
                    throw new DeveloperError('set end positions before getting surfaceDistance');
                }
                
                return this._distance;
            }
        },

        /**
         * Gets the initial planetodetic point on the path.
         * @memberof EllipsoidGeodesic.prototype
         * @type {Cartographic}
         * @readonly
         */
        start : {
            get : function() {
                return this._start;
            }
        },

        /**
         * Gets the final planetodetic point on the path.
         * @memberof EllipsoidGeodesic.prototype
         * @type {Cartographic}
         * @readonly
         */
        end : {
            get : function() {
                return this._end;
            }
        },

        /**
         * Gets the heading at the initial point.
         * @memberof EllipsoidGeodesic.prototype
         * @type {Number}
         * @readonly
         */
        startHeading : {
            get : function() {
                                if (!defined(this._distance)) {
                    throw new DeveloperError('set end positions before getting startHeading');
                }
                
                return this._startHeading;
            }
        },

        /**
         * Gets the heading at the final point.
         * @memberof EllipsoidGeodesic.prototype
         * @type {Number}
         * @readonly
         */
        endHeading : {
            get : function() {
                                if (!defined(this._distance)) {
                    throw new DeveloperError('set end positions before getting endHeading');
                }
                
                return this._endHeading;
            }
        }
    });

    /**
     * Sets the start and end points of the geodesic
     *
     * @param {Cartographic} start The initial planetodetic point on the path.
     * @param {Cartographic} end The final planetodetic point on the path.
     */
    EllipsoidGeodesic.prototype.setEndPoints = function(start, end) {
                if (!defined(start)) {
            throw new DeveloperError('start cartographic position is required');
        }
        if (!defined(end)) {
            throw new DeveloperError('end cartgraphic position is required');
        }
        
        computeProperties(this, start, end, this._ellipsoid);
    };

    /**
     * Provides the location of a point at the indicated portion along the geodesic.
     *
     * @param {Number} fraction The portion of the distance between the initial and final points.
     * @param {Cartographic} result The object in which to store the result.
     * @returns {Cartographic} The location of the point along the geodesic.
     */
    EllipsoidGeodesic.prototype.interpolateUsingFraction = function(fraction, result) {
        return this.interpolateUsingSurfaceDistance(this._distance * fraction, result);
    };

    /**
     * Provides the location of a point at the indicated distance along the geodesic.
     *
     * @param {Number} distance The distance from the inital point to the point of interest along the geodesic
     * @param {Cartographic} result The object in which to store the result.
     * @returns {Cartographic} The location of the point along the geodesic.
     *
     * @exception {DeveloperError} start and end must be set before calling function interpolateUsingSurfaceDistance
     */
    EllipsoidGeodesic.prototype.interpolateUsingSurfaceDistance = function(distance, result) {
                if (!defined(this._distance)) {
            throw new DeveloperError('start and end must be set before calling function interpolateUsingSurfaceDistance');
        }
        
        var constants = this._constants;

        var s = constants.distanceRatio + distance / constants.b;

        var cosine2S = Math.cos(2.0 * s);
        var cosine4S = Math.cos(4.0 * s);
        var cosine6S = Math.cos(6.0 * s);
        var sine2S = Math.sin(2.0 * s);
        var sine4S = Math.sin(4.0 * s);
        var sine6S = Math.sin(6.0 * s);
        var sine8S = Math.sin(8.0 * s);

        var s2 = s * s;
        var s3 = s * s2;

        var u8Over256 = constants.u8Over256;
        var u2Over4 = constants.u2Over4;
        var u6Over64 = constants.u6Over64;
        var u4Over16 = constants.u4Over16;
        var sigma = 2.0 * s3 * u8Over256 * cosine2S / 3.0 +
            s * (1.0 - u2Over4 + 7.0 * u4Over16 / 4.0 - 15.0 * u6Over64 / 4.0 + 579.0 * u8Over256 / 64.0 -
            (u4Over16 - 15.0 * u6Over64 / 4.0 + 187.0 * u8Over256 / 16.0) * cosine2S -
            (5.0 * u6Over64 / 4.0 - 115.0 * u8Over256 / 16.0) * cosine4S -
            29.0 * u8Over256 * cosine6S / 16.0) +
            (u2Over4 / 2.0 - u4Over16 + 71.0 * u6Over64 / 32.0 - 85.0 * u8Over256 / 16.0) * sine2S +
            (5.0 * u4Over16 / 16.0 - 5.0 * u6Over64 / 4.0 + 383.0 * u8Over256 / 96.0) * sine4S -
            s2 * ((u6Over64 - 11.0 * u8Over256 / 2.0) * sine2S + 5.0 * u8Over256 * sine4S / 2.0) +
            (29.0 * u6Over64 / 96.0 - 29.0 * u8Over256 / 16.0) * sine6S +
            539.0 * u8Over256 * sine8S / 1536.0;

        var theta = Math.asin(Math.sin(sigma) * constants.cosineAlpha);
        var latitude = Math.atan(constants.a / constants.b * Math.tan(theta));

        // Redefine in terms of relative argument of latitude.
        sigma = sigma - constants.sigma;

        var cosineTwiceSigmaMidpoint = Math.cos(2.0 * constants.sigma + sigma);

        var sineSigma = Math.sin(sigma);
        var cosineSigma = Math.cos(sigma);

        var cc = constants.cosineU * cosineSigma;
        var ss = constants.sineU * sineSigma;

        var lambda = Math.atan2(sineSigma * constants.sineHeading, cc - ss * constants.cosineHeading);

        var l = lambda - computeDeltaLambda(constants.f, constants.sineAlpha, constants.cosineSquaredAlpha,
                                            sigma, sineSigma, cosineSigma, cosineTwiceSigmaMidpoint);

        if (defined(result)) {
            result.longitude = this._start.longitude + l;
            result.latitude = latitude;
            result.height = 0.0;
            return result;
        }

        return new Cartographic(this._start.longitude + l, latitude, 0.0);
    };

    return EllipsoidGeodesic;
});

/*global define*/
define('Core/PolylinePipeline',[
        './Cartesian3',
        './Cartographic',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './EllipsoidGeodesic',
        './IntersectionTests',
        './isArray',
        './Math',
        './Matrix4',
        './Plane'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        EllipsoidGeodesic,
        IntersectionTests,
        isArray,
        CesiumMath,
        Matrix4,
        Plane) {
    'use strict';

    /**
     * @private
     */
    var PolylinePipeline = {};

    PolylinePipeline.numberOfPoints = function(p0, p1, minDistance) {
        var distance = Cartesian3.distance(p0, p1);
        return Math.ceil(distance / minDistance);
    };

    var cartoScratch = new Cartographic();
    PolylinePipeline.extractHeights = function(positions, ellipsoid) {
        var length = positions.length;
        var heights = new Array(length);
        for (var i = 0; i < length; i++) {
            var p = positions[i];
            heights[i] = ellipsoid.cartesianToCartographic(p, cartoScratch).height;
        }
        return heights;
    };

    var wrapLongitudeInversMatrix = new Matrix4();
    var wrapLongitudeOrigin = new Cartesian3();
    var wrapLongitudeXZNormal = new Cartesian3();
    var wrapLongitudeXZPlane = new Plane(Cartesian3.ZERO, 0.0);
    var wrapLongitudeYZNormal = new Cartesian3();
    var wrapLongitudeYZPlane = new Plane(Cartesian3.ZERO, 0.0);
    var wrapLongitudeIntersection = new Cartesian3();
    var wrapLongitudeOffset = new Cartesian3();

    var subdivideHeightsScratchArray = [];

    function subdivideHeights(numPoints, h0, h1) {
        var heights = subdivideHeightsScratchArray;
        heights.length = numPoints;

        var i;
        if (h0 === h1) {
            for (i = 0; i < numPoints; i++) {
                heights[i] = h0;
            }
            return heights;
        }

        var dHeight = h1 - h0;
        var heightPerVertex = dHeight / numPoints;

        for (i = 0; i < numPoints; i++) {
            var h = h0 + i*heightPerVertex;
            heights[i] = h;
        }

        return heights;
    }

    var carto1 = new Cartographic();
    var carto2 = new Cartographic();
    var cartesian = new Cartesian3();
    var scaleFirst = new Cartesian3();
    var scaleLast = new Cartesian3();
    var ellipsoidGeodesic = new EllipsoidGeodesic();

    //Returns subdivided line scaled to ellipsoid surface starting at p1 and ending at p2.
    //Result includes p1, but not include p2.  This function is called for a sequence of line segments,
    //and this prevents duplication of end point.
    function generateCartesianArc(p0, p1, minDistance, ellipsoid, h0, h1, array, offset) {
        var first = ellipsoid.scaleToGeodeticSurface(p0, scaleFirst);
        var last = ellipsoid.scaleToGeodeticSurface(p1, scaleLast);
        var numPoints = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
        var start = ellipsoid.cartesianToCartographic(first, carto1);
        var end = ellipsoid.cartesianToCartographic(last, carto2);
        var heights = subdivideHeights(numPoints, h0, h1);

        ellipsoidGeodesic.setEndPoints(start, end);
        var surfaceDistanceBetweenPoints = ellipsoidGeodesic.surfaceDistance / numPoints;

        var index = offset;
        start.height = h0;
        var cart = ellipsoid.cartographicToCartesian(start, cartesian);
        Cartesian3.pack(cart, array, index);
        index += 3;

        for (var i = 1; i < numPoints; i++) {
            var carto = ellipsoidGeodesic.interpolateUsingSurfaceDistance(i * surfaceDistanceBetweenPoints, carto2);
            carto.height = heights[i];
            cart = ellipsoid.cartographicToCartesian(carto, cartesian);
            Cartesian3.pack(cart, array, index);
            index += 3;
        }

        return index;
    }

    /**
     * Breaks a {@link Polyline} into segments such that it does not cross the &plusmn;180 degree meridian of an ellipsoid.
     *
     * @param {Cartesian3[]} positions The polyline's Cartesian positions.
     * @param {Matrix4} [modelMatrix=Matrix4.IDENTITY] The polyline's model matrix. Assumed to be an affine
     * transformation matrix, where the upper left 3x3 elements are a rotation matrix, and
     * the upper three elements in the fourth column are the translation.  The bottom row is assumed to be [0, 0, 0, 1].
     * The matrix is not verified to be in the proper form.
     * @returns {Object} An object with a <code>positions</code> property that is an array of positions and a
     * <code>segments</code> property.
     *
     *
     * @example
     * var polylines = new Cesium.PolylineCollection();
     * var polyline = polylines.add(...);
     * var positions = polyline.positions;
     * var modelMatrix = polylines.modelMatrix;
     * var segments = Cesium.PolylinePipeline.wrapLongitude(positions, modelMatrix);
     *
     * @see PolygonPipeline.wrapLongitude
     * @see Polyline
     * @see PolylineCollection
     */
    PolylinePipeline.wrapLongitude = function(positions, modelMatrix) {
        var cartesians = [];
        var segments = [];

        if (defined(positions) && positions.length > 0) {
            modelMatrix = defaultValue(modelMatrix, Matrix4.IDENTITY);
            var inverseModelMatrix = Matrix4.inverseTransformation(modelMatrix, wrapLongitudeInversMatrix);

            var origin = Matrix4.multiplyByPoint(inverseModelMatrix, Cartesian3.ZERO, wrapLongitudeOrigin);
            var xzNormal = Matrix4.multiplyByPointAsVector(inverseModelMatrix, Cartesian3.UNIT_Y, wrapLongitudeXZNormal);
            var xzPlane = Plane.fromPointNormal(origin, xzNormal, wrapLongitudeXZPlane);
            var yzNormal = Matrix4.multiplyByPointAsVector(inverseModelMatrix, Cartesian3.UNIT_X, wrapLongitudeYZNormal);
            var yzPlane = Plane.fromPointNormal(origin, yzNormal, wrapLongitudeYZPlane);

            var count = 1;
            cartesians.push(Cartesian3.clone(positions[0]));
            var prev = cartesians[0];

            var length = positions.length;
            for (var i = 1; i < length; ++i) {
                var cur = positions[i];

                // intersects the IDL if either endpoint is on the negative side of the yz-plane
                if (Plane.getPointDistance(yzPlane, prev) < 0.0 || Plane.getPointDistance(yzPlane, cur) < 0.0) {
                    // and intersects the xz-plane
                    var intersection = IntersectionTests.lineSegmentPlane(prev, cur, xzPlane, wrapLongitudeIntersection);
                    if (defined(intersection)) {
                        // move point on the xz-plane slightly away from the plane
                        var offset = Cartesian3.multiplyByScalar(xzNormal, 5.0e-9, wrapLongitudeOffset);
                        if (Plane.getPointDistance(xzPlane, prev) < 0.0) {
                            Cartesian3.negate(offset, offset);
                        }

                        cartesians.push(Cartesian3.add(intersection, offset, new Cartesian3()));
                        segments.push(count + 1);

                        Cartesian3.negate(offset, offset);
                        cartesians.push(Cartesian3.add(intersection, offset, new Cartesian3()));
                        count = 1;
                    }
                }

                cartesians.push(Cartesian3.clone(positions[i]));
                count++;

                prev = cur;
            }

            segments.push(count);
        }

        return {
            positions : cartesians,
            lengths : segments
        };
    };

    /**
     * Subdivides polyline and raises all points to the specified height.  Returns an array of numbers to represent the positions.
     * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
     * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
     * @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
     * @returns {Number[]} A new array of positions of type {Number} that have been subdivided and raised to the surface of the ellipsoid.
     *
     * @example
     * var positions = Cesium.Cartesian3.fromDegreesArray([
     *   -105.0, 40.0,
     *   -100.0, 38.0,
     *   -105.0, 35.0,
     *   -100.0, 32.0
     * ]);
     * var surfacePositions = Cesium.PolylinePipeline.generateArc({
     *   positons: positions
     * });
     */
    PolylinePipeline.generateArc = function(options) {
        if (!defined(options)) {
            options = {};
        }
        var positions = options.positions;
                if (!defined(positions)) {
            throw new DeveloperError('options.positions is required.');
        }
        
        var length = positions.length;
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        var height = defaultValue(options.height, 0);
        var hasHeightArray = isArray(height);

        if (length < 1) {
            return [];
        } else if (length === 1) {
            var p = ellipsoid.scaleToGeodeticSurface(positions[0], scaleFirst);
            height = hasHeightArray ? height[0] : height;
            if (height !== 0) {
                var n = ellipsoid.geodeticSurfaceNormal(p, cartesian);
                Cartesian3.multiplyByScalar(n, height, n);
                Cartesian3.add(p, n, p);
            }

            return [p.x, p.y, p.z];
        }

        var minDistance = options.minDistance;
        if (!defined(minDistance)) {
            var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
            minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
        }

        var numPoints = 0;
        var i;

        for (i = 0; i < length -1; i++) {
            numPoints += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance);
        }

        var arrayLength = (numPoints + 1) * 3;
        var newPositions = new Array(arrayLength);
        var offset = 0;

        for (i = 0; i < length - 1; i++) {
            var p0 = positions[i];
            var p1 = positions[i + 1];

            var h0 = hasHeightArray ? height[i] : height;
            var h1 = hasHeightArray ? height[i + 1] : height;

            offset = generateCartesianArc(p0, p1, minDistance, ellipsoid, h0, h1, newPositions, offset);
        }

        subdivideHeightsScratchArray.length = 0;

        var lastPoint = positions[length - 1];
        var carto = ellipsoid.cartesianToCartographic(lastPoint, carto1);
        carto.height = hasHeightArray ? height[length - 1] : height;
        var cart = ellipsoid.cartographicToCartesian(carto, cartesian);
        Cartesian3.pack(cart, newPositions, arrayLength - 3);

        return newPositions;
    };

    /**
     * Subdivides polyline and raises all points to the specified height. Returns an array of new {Cartesian3} positions.
     * @param {Cartesian3[]} options.positions The array of type {Cartesian3} representing positions.
     * @param {Number|Number[]} [options.height=0.0] A number or array of numbers representing the heights of each position.
     * @param {Number} [options.granularity = CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
     * @returns {Cartesian3[]} A new array of cartesian3 positions that have been subdivided and raised to the surface of the ellipsoid.
     *
     * @example
     * var positions = Cesium.Cartesian3.fromDegreesArray([
     *   -105.0, 40.0,
     *   -100.0, 38.0,
     *   -105.0, 35.0,
     *   -100.0, 32.0
     * ]);
     * var surfacePositions = Cesium.PolylinePipeline.generateCartesianArc({
     *   positons: positions
     * });
     */
    PolylinePipeline.generateCartesianArc = function(options) {
        var numberArray = PolylinePipeline.generateArc(options);
        var size = numberArray.length/3;
        var newPositions = new Array(size);
        for (var i = 0; i < size; i++) {
            newPositions[i] = Cartesian3.unpack(numberArray, i*3);
        }
        return newPositions;
    };

    return PolylinePipeline;
});

/*global define*/
define('Core/PolylineVolumeGeometryLibrary',[
        './Cartesian2',
        './Cartesian3',
        './Cartesian4',
        './Cartographic',
        './CornerType',
        './EllipsoidTangentPlane',
        './Math',
        './Matrix3',
        './Matrix4',
        './PolylinePipeline',
        './Quaternion',
        './Transforms'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        CornerType,
        EllipsoidTangentPlane,
        CesiumMath,
        Matrix3,
        Matrix4,
        PolylinePipeline,
        Quaternion,
        Transforms) {
    'use strict';

    var scratch2Array = [new Cartesian3(), new Cartesian3()];
    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchCartesian4 = new Cartesian3();
    var scratchCartesian5 = new Cartesian3();
    var scratchCartesian6 = new Cartesian3();
    var scratchCartesian7 = new Cartesian3();
    var scratchCartesian8 = new Cartesian3();
    var scratchCartesian9 = new Cartesian3();

    var scratch1 = new Cartesian3();
    var scratch2 = new Cartesian3();

    /**
     * @private
     */
    var PolylineVolumeGeometryLibrary = {};

    var cartographic = new Cartographic();
    function scaleToSurface(positions, ellipsoid) {
        var heights = new Array(positions.length);
        for (var i = 0; i < positions.length; i++) {
            var pos = positions[i];
            cartographic = ellipsoid.cartesianToCartographic(pos, cartographic);
            heights[i] = cartographic.height;
            positions[i] = ellipsoid.scaleToGeodeticSurface(pos, pos);
        }
        return heights;
    }

    function subdivideHeights(points, h0, h1, granularity) {
        var p0 = points[0];
        var p1 = points[1];
        var angleBetween = Cartesian3.angleBetween(p0, p1);
        var numPoints = Math.ceil(angleBetween / granularity);
        var heights = new Array(numPoints);
        var i;
        if (h0 === h1) {
            for (i = 0; i < numPoints; i++) {
                heights[i] = h0;
            }
            heights.push(h1);
            return heights;
        }

        var dHeight = h1 - h0;
        var heightPerVertex = dHeight / (numPoints);

        for (i = 1; i < numPoints; i++) {
            var h = h0 + i * heightPerVertex;
            heights[i] = h;
        }

        heights[0] = h0;
        heights.push(h1);
        return heights;
    }

    function computeRotationAngle(start, end, position, ellipsoid) {
        var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid);
        var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, start, nextScratch), nextScratch);
        var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, end, prevScratch), prevScratch);
        var angle = Cartesian2.angleBetween(next, prev);

        return (prev.x * next.y - prev.y * next.x >= 0.0) ? -angle : angle;
    }

    var negativeX = new Cartesian3(-1, 0, 0);
    var transform = new Matrix4();
    var translation = new Matrix4();
    var rotationZ = new Matrix3();
    var scaleMatrix = Matrix3.IDENTITY.clone();
    var westScratch = new Cartesian3();
    var finalPosScratch = new Cartesian4();
    var heightCartesian = new Cartesian3();
    function addPosition(center, left, shape, finalPositions, ellipsoid, height, xScalar, repeat) {
        var west = westScratch;
        var finalPosition = finalPosScratch;
        transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, transform);

        west = Matrix4.multiplyByPointAsVector(transform, negativeX, west);
        west = Cartesian3.normalize(west, west);
        var angle = computeRotationAngle(west, left, center, ellipsoid);
        rotationZ = Matrix3.fromRotationZ(angle, rotationZ);

        heightCartesian.z = height;
        transform = Matrix4.multiplyTransformation(transform, Matrix4.fromRotationTranslation(rotationZ, heightCartesian, translation), transform);
        var scale = scaleMatrix;
        scale[0] = xScalar;

        for (var j = 0; j < repeat; j++) {
            for (var i = 0; i < shape.length; i += 3) {
                finalPosition = Cartesian3.fromArray(shape, i, finalPosition);
                finalPosition = Matrix3.multiplyByVector(scale, finalPosition, finalPosition);
                finalPosition = Matrix4.multiplyByPoint(transform, finalPosition, finalPosition);
                finalPositions.push(finalPosition.x, finalPosition.y, finalPosition.z);
            }
        }

        return finalPositions;
    }

    var centerScratch = new Cartesian3();
    function addPositions(centers, left, shape, finalPositions, ellipsoid, heights, xScalar) {
        for (var i = 0; i < centers.length; i += 3) {
            var center = Cartesian3.fromArray(centers, i, centerScratch);
            finalPositions = addPosition(center, left, shape, finalPositions, ellipsoid, heights[i / 3], xScalar, 1);
        }
        return finalPositions;
    }

    function convertShapeTo3DDuplicate(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0), duplicate points
        var length = shape2D.length;
        var shape = new Array(length * 6);
        var index = 0;
        var xOffset = boundingRectangle.x + boundingRectangle.width / 2;
        var yOffset = boundingRectangle.y + boundingRectangle.height / 2;

        var point = shape2D[0];
        shape[index++] = point.x - xOffset;
        shape[index++] = 0.0;
        shape[index++] = point.y - yOffset;
        for (var i = 1; i < length; i++) {
            point = shape2D[i];
            var x = point.x - xOffset;
            var z = point.y - yOffset;
            shape[index++] = x;
            shape[index++] = 0.0;
            shape[index++] = z;

            shape[index++] = x;
            shape[index++] = 0.0;
            shape[index++] = z;
        }
        point = shape2D[0];
        shape[index++] = point.x - xOffset;
        shape[index++] = 0.0;
        shape[index++] = point.y - yOffset;

        return shape;
    }

    function convertShapeTo3D(shape2D, boundingRectangle) { //orientate 2D shape to XZ plane center at (0, 0, 0)
        var length = shape2D.length;
        var shape = new Array(length * 3);
        var index = 0;
        var xOffset = boundingRectangle.x + boundingRectangle.width / 2;
        var yOffset = boundingRectangle.y + boundingRectangle.height / 2;

        for (var i = 0; i < length; i++) {
            shape[index++] = shape2D[i].x - xOffset;
            shape[index++] = 0;
            shape[index++] = shape2D[i].y - yOffset;
        }

        return shape;
    }

    var quaterion = new Quaternion();
    var startPointScratch = new Cartesian3();
    var rotMatrix = new Matrix3();
    function computeRoundCorner(pivot, startPoint, endPoint, cornerType, leftIsOutside, ellipsoid, finalPositions, shape, height, duplicatePoints) {
        var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, pivot, scratch1), Cartesian3.subtract(endPoint, pivot, scratch2));
        var granularity = (cornerType === CornerType.BEVELED) ? 0 : Math.ceil(angle / CesiumMath.toRadians(5));

        var m;
        if (leftIsOutside) {
            m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(pivot, scratch1), angle / (granularity + 1), quaterion), rotMatrix);
        } else {
            m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(pivot, angle / (granularity + 1), quaterion), rotMatrix);
        }

        var left;
        var surfacePoint;
        startPoint = Cartesian3.clone(startPoint, startPointScratch);
        if (granularity > 0) {
            var repeat = duplicatePoints ? 2 : 1;
            for (var i = 0; i < granularity; i++) {
                startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint);
                left = Cartesian3.subtract(startPoint, pivot, scratch1);
                left = Cartesian3.normalize(left, left);
                if (!leftIsOutside) {
                    left = Cartesian3.negate(left, left);
                }
                surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2);
                finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, repeat);
            }
        } else {
            left = Cartesian3.subtract(startPoint, pivot, scratch1);
            left = Cartesian3.normalize(left, left);
            if (!leftIsOutside) {
                left = Cartesian3.negate(left, left);
            }
            surfacePoint = ellipsoid.scaleToGeodeticSurface(startPoint, scratch2);
            finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1);

            endPoint = Cartesian3.clone(endPoint, startPointScratch);
            left = Cartesian3.subtract(endPoint, pivot, scratch1);
            left = Cartesian3.normalize(left, left);
            if (!leftIsOutside) {
                left = Cartesian3.negate(left, left);
            }
            surfacePoint = ellipsoid.scaleToGeodeticSurface(endPoint, scratch2);
            finalPositions = addPosition(surfacePoint, left, shape, finalPositions, ellipsoid, height, 1, 1);
        }

        return finalPositions;
    }

    PolylineVolumeGeometryLibrary.removeDuplicatesFromShape = function(shapePositions) {
        var length = shapePositions.length;
        var cleanedPositions = [];
        for (var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) {
            var v0 = shapePositions[i0];
            var v1 = shapePositions[i1];

            if (!Cartesian2.equals(v0, v1)) {
                cleanedPositions.push(v1); // Shallow copy!
            }
        }

        return cleanedPositions;
    };

    var nextScratch = new Cartesian3();
    var prevScratch = new Cartesian3();
    PolylineVolumeGeometryLibrary.angleIsGreaterThanPi = function(forward, backward, position, ellipsoid) {
        var tangentPlane = new EllipsoidTangentPlane(position, ellipsoid);
        var next = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, forward, nextScratch), nextScratch);
        var prev = tangentPlane.projectPointOntoPlane(Cartesian3.add(position, backward, prevScratch), prevScratch);

        return ((prev.x * next.y) - (prev.y * next.x)) >= 0.0;
    };

    var scratchForwardProjection = new Cartesian3();
    var scratchBackwardProjection = new Cartesian3();

    PolylineVolumeGeometryLibrary.computePositions = function(positions, shape2D, boundingRectangle, geometry, duplicatePoints) {
        var ellipsoid = geometry._ellipsoid;
        var heights = scaleToSurface(positions, ellipsoid);
        var granularity = geometry._granularity;
        var cornerType = geometry._cornerType;
        var shapeForSides = duplicatePoints ? convertShapeTo3DDuplicate(shape2D, boundingRectangle) : convertShapeTo3D(shape2D, boundingRectangle);
        var shapeForEnds = duplicatePoints ? convertShapeTo3D(shape2D, boundingRectangle) : undefined;
        var heightOffset = boundingRectangle.height / 2;
        var width = boundingRectangle.width / 2;
        var length = positions.length;
        var finalPositions = [];
        var ends = duplicatePoints ? [] : undefined;

        var forward = scratchCartesian1;
        var backward = scratchCartesian2;
        var cornerDirection = scratchCartesian3;
        var surfaceNormal = scratchCartesian4;
        var pivot = scratchCartesian5;
        var start = scratchCartesian6;
        var end = scratchCartesian7;
        var left = scratchCartesian8;
        var previousPosition = scratchCartesian9;

        var position = positions[0];
        var nextPosition = positions[1];
        surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal);
        forward = Cartesian3.subtract(nextPosition, position, forward);
        forward = Cartesian3.normalize(forward, forward);
        left = Cartesian3.cross(surfaceNormal, forward, left);
        left = Cartesian3.normalize(left, left);
        var h0 = heights[0];
        var h1 = heights[1];
        if (duplicatePoints) {
            ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h0 + heightOffset, 1, 1);
        }
        previousPosition = Cartesian3.clone(position, previousPosition);
        position = nextPosition;
        backward = Cartesian3.negate(forward, backward);
        var subdividedHeights;
        var subdividedPositions;
        for (var i = 1; i < length - 1; i++) {
            var repeat = duplicatePoints ? 2 : 1;
            nextPosition = positions[i + 1];
            forward = Cartesian3.subtract(nextPosition, position, forward);
            forward = Cartesian3.normalize(forward, forward);
            cornerDirection = Cartesian3.add(forward, backward, cornerDirection);
            cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection);
            surfaceNormal = ellipsoid.geodeticSurfaceNormal(position, surfaceNormal);

            var forwardProjection = Cartesian3.multiplyByScalar(surfaceNormal, Cartesian3.dot(forward, surfaceNormal), scratchForwardProjection);
            Cartesian3.subtract(forward, forwardProjection, forwardProjection);
            Cartesian3.normalize(forwardProjection, forwardProjection);

            var backwardProjection = Cartesian3.multiplyByScalar(surfaceNormal, Cartesian3.dot(backward, surfaceNormal), scratchBackwardProjection);
            Cartesian3.subtract(backward, backwardProjection, backwardProjection);
            Cartesian3.normalize(backwardProjection, backwardProjection);

            var doCorner = !CesiumMath.equalsEpsilon(Math.abs(Cartesian3.dot(forwardProjection, backwardProjection)), 1.0, CesiumMath.EPSILON7);

            if (doCorner) {
                cornerDirection = Cartesian3.cross(cornerDirection, surfaceNormal, cornerDirection);
                cornerDirection = Cartesian3.cross(surfaceNormal, cornerDirection, cornerDirection);
                cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection);
                var scalar = 1 / Math.max(0.25, (Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1))));
                var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid);
                if (leftIsOutside) {
                    pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot);
                    start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, start), start);
                    scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]);
                    scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]);
                    subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity);
                    subdividedPositions = PolylinePipeline.generateArc({
                        positions: scratch2Array,
                        granularity: granularity,
                        ellipsoid: ellipsoid
                    });
                    finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1);
                    left = Cartesian3.cross(surfaceNormal, forward, left);
                    left = Cartesian3.normalize(left, left);
                    end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, width, end), end);
                    if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
                        computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints);
                    } else {
                        cornerDirection = Cartesian3.negate(cornerDirection, cornerDirection);
                        finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat);
                    }
                    previousPosition = Cartesian3.clone(end, previousPosition);
                } else {
                    pivot = Cartesian3.add(position, Cartesian3.multiplyByScalar(cornerDirection, scalar * width, cornerDirection), pivot);
                    start = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, start), start);
                    scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]);
                    scratch2Array[1] = Cartesian3.clone(start, scratch2Array[1]);
                    subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity);
                    subdividedPositions = PolylinePipeline.generateArc({
                        positions: scratch2Array,
                        granularity: granularity,
                        ellipsoid: ellipsoid
                    });
                    finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1);
                    left = Cartesian3.cross(surfaceNormal, forward, left);
                    left = Cartesian3.normalize(left, left);
                    end = Cartesian3.add(pivot, Cartesian3.multiplyByScalar(left, -width, end), end);
                    if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
                        computeRoundCorner(pivot, start, end, cornerType, leftIsOutside, ellipsoid, finalPositions, shapeForSides, h1 + heightOffset, duplicatePoints);
                    } else {
                        finalPositions = addPosition(position, cornerDirection, shapeForSides, finalPositions, ellipsoid, h1 + heightOffset, scalar, repeat);
                    }
                    previousPosition = Cartesian3.clone(end, previousPosition);
                }
                backward = Cartesian3.negate(forward, backward);
            } else {
                finalPositions = addPosition(previousPosition, left, shapeForSides, finalPositions, ellipsoid, h0 + heightOffset, 1, 1);
                previousPosition = position;
            }
            h0 = h1;
            h1 = heights[i + 1];
            position = nextPosition;
        }

        scratch2Array[0] = Cartesian3.clone(previousPosition, scratch2Array[0]);
        scratch2Array[1] = Cartesian3.clone(position, scratch2Array[1]);
        subdividedHeights = subdivideHeights(scratch2Array, h0 + heightOffset, h1 + heightOffset, granularity);
        subdividedPositions = PolylinePipeline.generateArc({
            positions: scratch2Array,
            granularity: granularity,
            ellipsoid: ellipsoid
        });
        finalPositions = addPositions(subdividedPositions, left, shapeForSides, finalPositions, ellipsoid, subdividedHeights, 1);
        if (duplicatePoints) {
            ends = addPosition(position, left, shapeForEnds, ends, ellipsoid, h1 + heightOffset, 1, 1);
        }

        length = finalPositions.length;
        var posLength = duplicatePoints ? length + ends.length : length;
        var combinedPositions = new Float64Array(posLength);
        combinedPositions.set(finalPositions);
        if (duplicatePoints) {
            combinedPositions.set(ends, length);
        }

        return combinedPositions;
    };

    return PolylineVolumeGeometryLibrary;
});

/*global define*/
define('Core/CorridorGeometryLibrary',[
        './Cartesian3',
        './CornerType',
        './defined',
        './Math',
        './Matrix3',
        './PolylinePipeline',
        './PolylineVolumeGeometryLibrary',
        './Quaternion'
    ], function(
        Cartesian3,
        CornerType,
        defined,
        CesiumMath,
        Matrix3,
        PolylinePipeline,
        PolylineVolumeGeometryLibrary,
        Quaternion) {
    'use strict';

    /**
     * @private
     */
    var CorridorGeometryLibrary = {};

    var scratch1 = new Cartesian3();
    var scratch2 = new Cartesian3();
    var scratch3 = new Cartesian3();
    var scratch4 = new Cartesian3();

    var scaleArray2 = [new Cartesian3(), new Cartesian3()];

    var cartesian1 = new Cartesian3();
    var cartesian2 = new Cartesian3();
    var cartesian3 = new Cartesian3();
    var cartesian4 = new Cartesian3();
    var cartesian5 = new Cartesian3();
    var cartesian6 = new Cartesian3();
    var cartesian7 = new Cartesian3();
    var cartesian8 = new Cartesian3();
    var cartesian9 = new Cartesian3();
    var cartesian10 = new Cartesian3();

    var quaterion = new Quaternion();
    var rotMatrix = new Matrix3();
    function computeRoundCorner(cornerPoint, startPoint, endPoint, cornerType, leftIsOutside) {
        var angle = Cartesian3.angleBetween(Cartesian3.subtract(startPoint, cornerPoint, scratch1), Cartesian3.subtract(endPoint, cornerPoint, scratch2));
        var granularity = (cornerType === CornerType.BEVELED) ? 1 : Math.ceil(angle / CesiumMath.toRadians(5)) + 1;

        var size = granularity * 3;
        var array = new Array(size);

        array[size - 3] = endPoint.x;
        array[size - 2] = endPoint.y;
        array[size - 1] = endPoint.z;

        var m;
        if (leftIsOutside) {
            m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(Cartesian3.negate(cornerPoint, scratch1), angle / granularity, quaterion), rotMatrix);
        } else {
            m = Matrix3.fromQuaternion(Quaternion.fromAxisAngle(cornerPoint, angle / granularity, quaterion), rotMatrix);
        }

        var index = 0;
        startPoint = Cartesian3.clone(startPoint, scratch1);
        for (var i = 0; i < granularity; i++) {
            startPoint = Matrix3.multiplyByVector(m, startPoint, startPoint);
            array[index++] = startPoint.x;
            array[index++] = startPoint.y;
            array[index++] = startPoint.z;
        }

        return array;
    }

    function addEndCaps(calculatedPositions) {
        var cornerPoint = cartesian1;
        var startPoint = cartesian2;
        var endPoint = cartesian3;

        var leftEdge = calculatedPositions[1];
        startPoint = Cartesian3.fromArray(calculatedPositions[1], leftEdge.length - 3, startPoint);
        endPoint = Cartesian3.fromArray(calculatedPositions[0], 0, endPoint);
        cornerPoint = Cartesian3.multiplyByScalar(Cartesian3.add(startPoint, endPoint, cornerPoint), 0.5, cornerPoint);
        var firstEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false);

        var length = calculatedPositions.length - 1;
        var rightEdge = calculatedPositions[length - 1];
        leftEdge = calculatedPositions[length];
        startPoint = Cartesian3.fromArray(rightEdge, rightEdge.length - 3, startPoint);
        endPoint = Cartesian3.fromArray(leftEdge, 0, endPoint);
        cornerPoint = Cartesian3.multiplyByScalar(Cartesian3.add(startPoint, endPoint, cornerPoint), 0.5, cornerPoint);
        var lastEndCap = computeRoundCorner(cornerPoint, startPoint, endPoint, CornerType.ROUNDED, false);

        return [firstEndCap, lastEndCap];
    }

    function computeMiteredCorner(position, leftCornerDirection, lastPoint, leftIsOutside) {
        var cornerPoint = scratch1;
        if (leftIsOutside) {
            cornerPoint = Cartesian3.add(position, leftCornerDirection, cornerPoint);
        } else {
            leftCornerDirection = Cartesian3.negate(leftCornerDirection, leftCornerDirection);
            cornerPoint = Cartesian3.add(position, leftCornerDirection, cornerPoint);
        }
        return [cornerPoint.x, cornerPoint.y, cornerPoint.z, lastPoint.x, lastPoint.y, lastPoint.z];
    }

    function addShiftedPositions(positions, left, scalar, calculatedPositions) {
        var rightPositions = new Array(positions.length);
        var leftPositions = new Array(positions.length);
        var scaledLeft = Cartesian3.multiplyByScalar(left, scalar, scratch1);
        var scaledRight = Cartesian3.negate(scaledLeft, scratch2);
        var rightIndex = 0;
        var leftIndex = positions.length - 1;

        for (var i = 0; i < positions.length; i += 3) {
            var pos = Cartesian3.fromArray(positions, i, scratch3);
            var rightPos = Cartesian3.add(pos, scaledRight, scratch4);
            rightPositions[rightIndex++] = rightPos.x;
            rightPositions[rightIndex++] = rightPos.y;
            rightPositions[rightIndex++] = rightPos.z;

            var leftPos = Cartesian3.add(pos, scaledLeft, scratch4);
            leftPositions[leftIndex--] = leftPos.z;
            leftPositions[leftIndex--] = leftPos.y;
            leftPositions[leftIndex--] = leftPos.x;
        }
        calculatedPositions.push(rightPositions, leftPositions);

        return calculatedPositions;
    }

    /**
     * @private
     */
    CorridorGeometryLibrary.addAttribute = function(attribute, value, front, back) {
        var x = value.x;
        var y = value.y;
        var z = value.z;
        if (defined(front)) {
            attribute[front] = x;
            attribute[front + 1] = y;
            attribute[front + 2] = z;
        }
        if (defined(back)) {
            attribute[back] = z;
            attribute[back - 1] = y;
            attribute[back - 2] = x;
        }
    };

    function scaleToSurface(positions, ellipsoid) {
        for (var i = 0; i < positions.length; i++) {
            positions[i] = ellipsoid.scaleToGeodeticSurface(positions[i], positions[i]);
        }
        return positions;
    }

    var scratchForwardProjection = new Cartesian3();
    var scratchBackwardProjection = new Cartesian3();

    /**
     * @private
     */
    CorridorGeometryLibrary.computePositions = function(params) {
        var granularity = params.granularity;
        var positions = params.positions;
        var ellipsoid = params.ellipsoid;
        positions = scaleToSurface(positions, ellipsoid);
        var width = params.width / 2;
        var cornerType = params.cornerType;
        var saveAttributes = params.saveAttributes;
        var normal = cartesian1;
        var forward = cartesian2;
        var backward = cartesian3;
        var left = cartesian4;
        var cornerDirection = cartesian5;
        var startPoint = cartesian6;
        var previousPos = cartesian7;
        var rightPos = cartesian8;
        var leftPos = cartesian9;
        var center = cartesian10;
        var calculatedPositions = [];
        var calculatedLefts = (saveAttributes) ? [] : undefined;
        var calculatedNormals = (saveAttributes) ? [] : undefined;
        var position = positions[0]; //add first point
        var nextPosition = positions[1];

        forward = Cartesian3.normalize(Cartesian3.subtract(nextPosition, position, forward), forward);
        normal = ellipsoid.geodeticSurfaceNormal(position, normal);
        left = Cartesian3.normalize(Cartesian3.cross(normal, forward, left), left);
        if (saveAttributes) {
            calculatedLefts.push(left.x, left.y, left.z);
            calculatedNormals.push(normal.x, normal.y, normal.z);
        }
        previousPos = Cartesian3.clone(position, previousPos);
        position = nextPosition;
        backward = Cartesian3.negate(forward, backward);

        var subdividedPositions;
        var corners = [];
        var i;
        var length = positions.length;
        for (i = 1; i < length - 1; i++) { // add middle points and corners
            normal = ellipsoid.geodeticSurfaceNormal(position, normal);
            nextPosition = positions[i + 1];
            forward = Cartesian3.normalize(Cartesian3.subtract(nextPosition, position, forward), forward);
            cornerDirection = Cartesian3.normalize(Cartesian3.add(forward, backward, cornerDirection), cornerDirection);

            var forwardProjection = Cartesian3.multiplyByScalar(normal, Cartesian3.dot(forward, normal), scratchForwardProjection);
            Cartesian3.subtract(forward, forwardProjection, forwardProjection);
            Cartesian3.normalize(forwardProjection, forwardProjection);

            var backwardProjection = Cartesian3.multiplyByScalar(normal, Cartesian3.dot(backward, normal), scratchBackwardProjection);
            Cartesian3.subtract(backward, backwardProjection, backwardProjection);
            Cartesian3.normalize(backwardProjection, backwardProjection);

            var doCorner = !CesiumMath.equalsEpsilon(Math.abs(Cartesian3.dot(forwardProjection, backwardProjection)), 1.0, CesiumMath.EPSILON7);

            if (doCorner) {
                cornerDirection = Cartesian3.cross(cornerDirection, normal, cornerDirection);
                cornerDirection = Cartesian3.cross(normal, cornerDirection, cornerDirection);
                cornerDirection = Cartesian3.normalize(cornerDirection, cornerDirection);
                var scalar = width / Math.max(0.25, Cartesian3.magnitude(Cartesian3.cross(cornerDirection, backward, scratch1)));
                var leftIsOutside = PolylineVolumeGeometryLibrary.angleIsGreaterThanPi(forward, backward, position, ellipsoid);
                cornerDirection = Cartesian3.multiplyByScalar(cornerDirection, scalar, cornerDirection);
                if (leftIsOutside) {
                    rightPos = Cartesian3.add(position, cornerDirection, rightPos);
                    center = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width, center), center);
                    leftPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width * 2, leftPos), leftPos);
                    scaleArray2[0] = Cartesian3.clone(previousPos, scaleArray2[0]);
                    scaleArray2[1] = Cartesian3.clone(center, scaleArray2[1]);
                    subdividedPositions = PolylinePipeline.generateArc({
                        positions: scaleArray2,
                        granularity: granularity,
                        ellipsoid: ellipsoid
                    });
                    calculatedPositions = addShiftedPositions(subdividedPositions, left, width, calculatedPositions);
                    if (saveAttributes) {
                        calculatedLefts.push(left.x, left.y, left.z);
                        calculatedNormals.push(normal.x, normal.y, normal.z);
                    }
                    startPoint = Cartesian3.clone(leftPos, startPoint);
                    left = Cartesian3.normalize(Cartesian3.cross(normal, forward, left), left);
                    leftPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width * 2, leftPos), leftPos);
                    previousPos = Cartesian3.add(rightPos, Cartesian3.multiplyByScalar(left, width, previousPos), previousPos);
                    if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
                        corners.push({
                            leftPositions : computeRoundCorner(rightPos, startPoint, leftPos, cornerType, leftIsOutside)
                        });
                    } else {
                        corners.push({
                            leftPositions : computeMiteredCorner(position, Cartesian3.negate(cornerDirection, cornerDirection), leftPos, leftIsOutside)
                        });
                    }
                } else {
                    leftPos = Cartesian3.add(position, cornerDirection, leftPos);
                    center = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width, center), center), center);
                    rightPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width * 2, rightPos), rightPos), rightPos);
                    scaleArray2[0] = Cartesian3.clone(previousPos, scaleArray2[0]);
                    scaleArray2[1] = Cartesian3.clone(center, scaleArray2[1]);
                    subdividedPositions = PolylinePipeline.generateArc({
                        positions: scaleArray2,
                        granularity: granularity,
                        ellipsoid: ellipsoid
                    });
                    calculatedPositions = addShiftedPositions(subdividedPositions, left, width, calculatedPositions);
                    if (saveAttributes) {
                        calculatedLefts.push(left.x, left.y, left.z);
                        calculatedNormals.push(normal.x, normal.y, normal.z);
                    }
                    startPoint = Cartesian3.clone(rightPos, startPoint);
                    left = Cartesian3.normalize(Cartesian3.cross(normal, forward, left), left);
                    rightPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width * 2, rightPos), rightPos), rightPos);
                    previousPos = Cartesian3.add(leftPos, Cartesian3.negate(Cartesian3.multiplyByScalar(left, width, previousPos), previousPos), previousPos);
                    if (cornerType === CornerType.ROUNDED || cornerType === CornerType.BEVELED) {
                        corners.push({
                            rightPositions : computeRoundCorner(leftPos, startPoint, rightPos, cornerType, leftIsOutside)
                        });
                    } else {
                        corners.push({
                            rightPositions : computeMiteredCorner(position, cornerDirection, rightPos, leftIsOutside)
                        });
                    }
                }
                backward = Cartesian3.negate(forward, backward);
            }
            position = nextPosition;
        }

        normal = ellipsoid.geodeticSurfaceNormal(position, normal);
        scaleArray2[0] = Cartesian3.clone(previousPos, scaleArray2[0]);
        scaleArray2[1] = Cartesian3.clone(position, scaleArray2[1]);
        subdividedPositions = PolylinePipeline.generateArc({
            positions: scaleArray2,
            granularity: granularity,
            ellipsoid: ellipsoid
        });
        calculatedPositions = addShiftedPositions(subdividedPositions, left, width, calculatedPositions);
        if (saveAttributes) {
            calculatedLefts.push(left.x, left.y, left.z);
            calculatedNormals.push(normal.x, normal.y, normal.z);
        }

        var endPositions;
        if (cornerType === CornerType.ROUNDED) {
            endPositions = addEndCaps(calculatedPositions);
        }

        return {
            positions : calculatedPositions,
            corners : corners,
            lefts : calculatedLefts,
            normals : calculatedNormals,
            endPositions : endPositions
        };
    };

    return CorridorGeometryLibrary;
});

/*global define*/
define('ThirdParty/earcut-2.1.1',[], function() {
    'use strict';

function earcut(data, holeIndices, dim) {

    dim = dim || 2;

    var hasHoles = holeIndices && holeIndices.length,
        outerLen = hasHoles ? holeIndices[0] * dim : data.length,
        outerNode = linkedList(data, 0, outerLen, dim, true),
        triangles = [];

    if (!outerNode) return triangles;

    var minX, minY, maxX, maxY, x, y, size;

    if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);

    // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
    if (data.length > 80 * dim) {
        minX = maxX = data[0];
        minY = maxY = data[1];

        for (var i = dim; i < outerLen; i += dim) {
            x = data[i];
            y = data[i + 1];
            if (x < minX) minX = x;
            if (y < minY) minY = y;
            if (x > maxX) maxX = x;
            if (y > maxY) maxY = y;
        }

        // minX, minY and size are later used to transform coords into integers for z-order calculation
        size = Math.max(maxX - minX, maxY - minY);
    }

    earcutLinked(outerNode, triangles, dim, minX, minY, size);

    return triangles;
}

// create a circular doubly linked list from polygon points in the specified winding order
function linkedList(data, start, end, dim, clockwise) {
    var i, last;

    if (clockwise === (signedArea(data, start, end, dim) > 0)) {
        for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
    } else {
        for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
    }

    if (last && equals(last, last.next)) {
        removeNode(last);
        last = last.next;
    }

    return last;
}

// eliminate colinear or duplicate points
function filterPoints(start, end) {
    if (!start) return start;
    if (!end) end = start;

    var p = start,
        again;
    do {
        again = false;

        if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
            removeNode(p);
            p = end = p.prev;
            if (p === p.next) return null;
            again = true;

        } else {
            p = p.next;
        }
    } while (again || p !== end);

    return end;
}

// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
    if (!ear) return;

    // interlink polygon nodes in z-order
    if (!pass && size) indexCurve(ear, minX, minY, size);

    var stop = ear,
        prev, next;

    // iterate through ears, slicing them one by one
    while (ear.prev !== ear.next) {
        prev = ear.prev;
        next = ear.next;

        if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) {
            // cut off the triangle
            triangles.push(prev.i / dim);
            triangles.push(ear.i / dim);
            triangles.push(next.i / dim);

            removeNode(ear);

            // skipping the next vertice leads to less sliver triangles
            ear = next.next;
            stop = next.next;

            continue;
        }

        ear = next;

        // if we looped through the whole remaining polygon and can't find any more ears
        if (ear === stop) {
            // try filtering points and slicing again
            if (!pass) {
                earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1);

            // if this didn't work, try curing all small self-intersections locally
            } else if (pass === 1) {
                ear = cureLocalIntersections(ear, triangles, dim);
                earcutLinked(ear, triangles, dim, minX, minY, size, 2);

            // as a last resort, try splitting the remaining polygon into two
            } else if (pass === 2) {
                splitEarcut(ear, triangles, dim, minX, minY, size);
            }

            break;
        }
    }
}

// check whether a polygon node forms a valid ear with adjacent nodes
function isEar(ear) {
    var a = ear.prev,
        b = ear,
        c = ear.next;

    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear

    // now make sure we don't have other points inside the potential ear
    var p = ear.next.next;

    while (p !== ear.prev) {
        if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
            area(p.prev, p, p.next) >= 0) return false;
        p = p.next;
    }

    return true;
}

function isEarHashed(ear, minX, minY, size) {
    var a = ear.prev,
        b = ear,
        c = ear.next;

    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear

    // triangle bbox; min & max are calculated like this for speed
    var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
        minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
        maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
        maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);

    // z-order range for the current triangle bbox;
    var minZ = zOrder(minTX, minTY, minX, minY, size),
        maxZ = zOrder(maxTX, maxTY, minX, minY, size);

    // first look for points inside the triangle in increasing z-order
    var p = ear.nextZ;

    while (p && p.z <= maxZ) {
        if (p !== ear.prev && p !== ear.next &&
            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
            area(p.prev, p, p.next) >= 0) return false;
        p = p.nextZ;
    }

    // then look for points in decreasing z-order
    p = ear.prevZ;

    while (p && p.z >= minZ) {
        if (p !== ear.prev && p !== ear.next &&
            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
            area(p.prev, p, p.next) >= 0) return false;
        p = p.prevZ;
    }

    return true;
}

// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections(start, triangles, dim) {
    var p = start;
    do {
        var a = p.prev,
            b = p.next.next;

        if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {

            triangles.push(a.i / dim);
            triangles.push(p.i / dim);
            triangles.push(b.i / dim);

            // remove two nodes involved
            removeNode(p);
            removeNode(p.next);

            p = start = b;
        }
        p = p.next;
    } while (p !== start);

    return p;
}

// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, size) {
    // look for a valid diagonal that divides the polygon into two
    var a = start;
    do {
        var b = a.next.next;
        while (b !== a.prev) {
            if (a.i !== b.i && isValidDiagonal(a, b)) {
                // split the polygon in two by the diagonal
                var c = splitPolygon(a, b);

                // filter colinear points around the cuts
                a = filterPoints(a, a.next);
                c = filterPoints(c, c.next);

                // run earcut on each half
                earcutLinked(a, triangles, dim, minX, minY, size);
                earcutLinked(c, triangles, dim, minX, minY, size);
                return;
            }
            b = b.next;
        }
        a = a.next;
    } while (a !== start);
}

// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
    var queue = [],
        i, len, start, end, list;

    for (i = 0, len = holeIndices.length; i < len; i++) {
        start = holeIndices[i] * dim;
        end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
        list = linkedList(data, start, end, dim, false);
        if (list === list.next) list.steiner = true;
        queue.push(getLeftmost(list));
    }

    queue.sort(compareX);

    // process holes from left to right
    for (i = 0; i < queue.length; i++) {
        eliminateHole(queue[i], outerNode);
        outerNode = filterPoints(outerNode, outerNode.next);
    }

    return outerNode;
}

function compareX(a, b) {
    return a.x - b.x;
}

// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole(hole, outerNode) {
    outerNode = findHoleBridge(hole, outerNode);
    if (outerNode) {
        var b = splitPolygon(outerNode, hole);
        filterPoints(b, b.next);
    }
}

// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge(hole, outerNode) {
    var p = outerNode,
        hx = hole.x,
        hy = hole.y,
        qx = -Infinity,
        m;

    // find a segment intersected by a ray from the hole's leftmost point to the left;
    // segment's endpoint with lesser x will be potential connection point
    do {
        if (hy <= p.y && hy >= p.next.y) {
            var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
            if (x <= hx && x > qx) {
                qx = x;
                if (x === hx) {
                    if (hy === p.y) return p;
                    if (hy === p.next.y) return p.next;
                }
                m = p.x < p.next.x ? p : p.next;
            }
        }
        p = p.next;
    } while (p !== outerNode);

    if (!m) return null;

    if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint

    // look for points inside the triangle of hole point, segment intersection and endpoint;
    // if there are no points found, we have a valid connection;
    // otherwise choose the point of the minimum angle with the ray as connection point

    var stop = m,
        mx = m.x,
        my = m.y,
        tanMin = Infinity,
        tan;

    p = m.next;

    while (p !== stop) {
        if (hx >= p.x && p.x >= mx &&
                pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {

            tan = Math.abs(hy - p.y) / (hx - p.x); // tangential

            if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
                m = p;
                tanMin = tan;
            }
        }

        p = p.next;
    }

    return m;
}

// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, size) {
    var p = start;
    do {
        if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size);
        p.prevZ = p.prev;
        p.nextZ = p.next;
        p = p.next;
    } while (p !== start);

    p.prevZ.nextZ = null;
    p.prevZ = null;

    sortLinked(p);
}

// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked(list) {
    var i, p, q, e, tail, numMerges, pSize, qSize,
        inSize = 1;

    do {
        p = list;
        list = null;
        tail = null;
        numMerges = 0;

        while (p) {
            numMerges++;
            q = p;
            pSize = 0;
            for (i = 0; i < inSize; i++) {
                pSize++;
                q = q.nextZ;
                if (!q) break;
            }

            qSize = inSize;

            while (pSize > 0 || (qSize > 0 && q)) {

                if (pSize === 0) {
                    e = q;
                    q = q.nextZ;
                    qSize--;
                } else if (qSize === 0 || !q) {
                    e = p;
                    p = p.nextZ;
                    pSize--;
                } else if (p.z <= q.z) {
                    e = p;
                    p = p.nextZ;
                    pSize--;
                } else {
                    e = q;
                    q = q.nextZ;
                    qSize--;
                }

                if (tail) tail.nextZ = e;
                else list = e;

                e.prevZ = tail;
                tail = e;
            }

            p = q;
        }

        tail.nextZ = null;
        inSize *= 2;

    } while (numMerges > 1);

    return list;
}

// z-order of a point given coords and size of the data bounding box
function zOrder(x, y, minX, minY, size) {
    // coords are transformed into non-negative 15-bit integer range
    x = 32767 * (x - minX) / size;
    y = 32767 * (y - minY) / size;

    x = (x | (x << 8)) & 0x00FF00FF;
    x = (x | (x << 4)) & 0x0F0F0F0F;
    x = (x | (x << 2)) & 0x33333333;
    x = (x | (x << 1)) & 0x55555555;

    y = (y | (y << 8)) & 0x00FF00FF;
    y = (y | (y << 4)) & 0x0F0F0F0F;
    y = (y | (y << 2)) & 0x33333333;
    y = (y | (y << 1)) & 0x55555555;

    return x | (y << 1);
}

// find the leftmost node of a polygon ring
function getLeftmost(start) {
    var p = start,
        leftmost = start;
    do {
        if (p.x < leftmost.x) leftmost = p;
        p = p.next;
    } while (p !== start);

    return leftmost;
}

// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
    return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
           (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
           (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}

// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
    return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
           locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
}

// signed area of a triangle
function area(p, q, r) {
    return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}

// check if two points are equal
function equals(p1, p2) {
    return p1.x === p2.x && p1.y === p2.y;
}

// check if two segments intersect
function intersects(p1, q1, p2, q2) {
    if ((equals(p1, q1) && equals(p2, q2)) ||
        (equals(p1, q2) && equals(p2, q1))) return true;
    return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
           area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
}

// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
    var p = a;
    do {
        if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
                intersects(p, p.next, a, b)) return true;
        p = p.next;
    } while (p !== a);

    return false;
}

// check if a polygon diagonal is locally inside the polygon
function locallyInside(a, b) {
    return area(a.prev, a, a.next) < 0 ?
        area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
        area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}

// check if the middle point of a polygon diagonal is inside the polygon
function middleInside(a, b) {
    var p = a,
        inside = false,
        px = (a.x + b.x) / 2,
        py = (a.y + b.y) / 2;
    do {
        if (((p.y > py) !== (p.next.y > py)) && (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
            inside = !inside;
        p = p.next;
    } while (p !== a);

    return inside;
}

// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon(a, b) {
    var a2 = new Node(a.i, a.x, a.y),
        b2 = new Node(b.i, b.x, b.y),
        an = a.next,
        bp = b.prev;

    a.next = b;
    b.prev = a;

    a2.next = an;
    an.prev = a2;

    b2.next = a2;
    a2.prev = b2;

    bp.next = b2;
    b2.prev = bp;

    return b2;
}

// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode(i, x, y, last) {
    var p = new Node(i, x, y);

    if (!last) {
        p.prev = p;
        p.next = p;

    } else {
        p.next = last.next;
        p.prev = last;
        last.next.prev = p;
        last.next = p;
    }
    return p;
}

function removeNode(p) {
    p.next.prev = p.prev;
    p.prev.next = p.next;

    if (p.prevZ) p.prevZ.nextZ = p.nextZ;
    if (p.nextZ) p.nextZ.prevZ = p.prevZ;
}

function Node(i, x, y) {
    // vertice index in coordinates array
    this.i = i;

    // vertex coordinates
    this.x = x;
    this.y = y;

    // previous and next vertice nodes in a polygon ring
    this.prev = null;
    this.next = null;

    // z-order curve value
    this.z = null;

    // previous and next nodes in z-order
    this.prevZ = null;
    this.nextZ = null;

    // indicates whether this is a steiner point
    this.steiner = false;
}

// return a percentage difference between the polygon area and its triangulation area;
// used to verify correctness of triangulation
earcut.deviation = function (data, holeIndices, dim, triangles) {
    var hasHoles = holeIndices && holeIndices.length;
    var outerLen = hasHoles ? holeIndices[0] * dim : data.length;

    var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
    if (hasHoles) {
        for (var i = 0, len = holeIndices.length; i < len; i++) {
            var start = holeIndices[i] * dim;
            var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
            polygonArea -= Math.abs(signedArea(data, start, end, dim));
        }
    }

    var trianglesArea = 0;
    for (i = 0; i < triangles.length; i += 3) {
        var a = triangles[i] * dim;
        var b = triangles[i + 1] * dim;
        var c = triangles[i + 2] * dim;
        trianglesArea += Math.abs(
            (data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
            (data[a] - data[b]) * (data[c + 1] - data[a + 1]));
    }

    return polygonArea === 0 && trianglesArea === 0 ? 0 :
        Math.abs((trianglesArea - polygonArea) / polygonArea);
};

function signedArea(data, start, end, dim) {
    var sum = 0;
    for (var i = start, j = end - dim; i < end; i += dim) {
        sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
        j = i;
    }
    return sum;
}

// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
earcut.flatten = function (data) {
    var dim = data[0][0].length,
        result = {vertices: [], holes: [], dimensions: dim},
        holeIndex = 0;

    for (var i = 0; i < data.length; i++) {
        for (var j = 0; j < data[i].length; j++) {
            for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
        }
        if (i > 0) {
            holeIndex += data[i - 1].length;
            result.holes.push(holeIndex);
        }
    }
    return result;
};

return earcut;
});

/*global define*/
define('Core/WindingOrder',[
        './freezeObject',
        './WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Winding order defines the order of vertices for a triangle to be considered front-facing.
     *
     * @exports WindingOrder
     */
    var WindingOrder = {
        /**
         * Vertices are in clockwise order.
         *
         * @type {Number}
         * @constant
         */
        CLOCKWISE : WebGLConstants.CW,

        /**
         * Vertices are in counter-clockwise order.
         *
         * @type {Number}
         * @constant
         */
        COUNTER_CLOCKWISE : WebGLConstants.CCW,

        /**
         * @private
         */
        validate : function(windingOrder) {
            return windingOrder === WindingOrder.CLOCKWISE ||
                   windingOrder === WindingOrder.COUNTER_CLOCKWISE;
        }
    };

    return freezeObject(WindingOrder);
});

/*global define*/
define('Core/PolygonPipeline',[
        '../ThirdParty/earcut-2.1.1',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './Math',
        './PrimitiveType',
        './WindingOrder'
    ], function(
        earcut,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        CesiumMath,
        PrimitiveType,
        WindingOrder) {
    'use strict';

    var scaleToGeodeticHeightN = new Cartesian3();
    var scaleToGeodeticHeightP = new Cartesian3();

    /**
     * @private
     */
    var PolygonPipeline = {};

    /**
     * @exception {DeveloperError} At least three positions are required.
     */
    PolygonPipeline.computeArea2D = function(positions) {
                if (!defined(positions)) {
            throw new DeveloperError('positions is required.');
        }
        if (positions.length < 3) {
            throw new DeveloperError('At least three positions are required.');
        }
        
        var length = positions.length;
        var area = 0.0;

        for ( var i0 = length - 1, i1 = 0; i1 < length; i0 = i1++) {
            var v0 = positions[i0];
            var v1 = positions[i1];

            area += (v0.x * v1.y) - (v1.x * v0.y);
        }

        return area * 0.5;
    };

    /**
     * @returns {WindingOrder} The winding order.
     *
     * @exception {DeveloperError} At least three positions are required.
     */
    PolygonPipeline.computeWindingOrder2D = function(positions) {
        var area = PolygonPipeline.computeArea2D(positions);
        return (area > 0.0) ? WindingOrder.COUNTER_CLOCKWISE : WindingOrder.CLOCKWISE;
    };

    /**
     * Triangulate a polygon.
     *
     * @param {Cartesian2[]} positions Cartesian2 array containing the vertices of the polygon
     * @param {Number[]} [holes] An array of the staring indices of the holes.
     * @returns {Number[]} Index array representing triangles that fill the polygon
     */
    PolygonPipeline.triangulate = function(positions, holes) {
                if (!defined(positions)) {
            throw new DeveloperError('positions is required.');
        }
        
        var flattenedPositions = Cartesian2.packArray(positions);
        return earcut(flattenedPositions, holes, 2);
    };

    var subdivisionV0Scratch = new Cartesian3();
    var subdivisionV1Scratch = new Cartesian3();
    var subdivisionV2Scratch = new Cartesian3();
    var subdivisionS0Scratch = new Cartesian3();
    var subdivisionS1Scratch = new Cartesian3();
    var subdivisionS2Scratch = new Cartesian3();
    var subdivisionMidScratch = new Cartesian3();

    /**
     * Subdivides positions and raises points to the surface of the ellipsoid.
     *
     * @param {Ellipsoid} ellipsoid The ellipsoid the polygon in on.
     * @param {Cartesian3[]} positions An array of {@link Cartesian3} positions of the polygon.
     * @param {Number[]} indices An array of indices that determines the triangles in the polygon.
     * @param {Number} [granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     *
     * @exception {DeveloperError} At least three indices are required.
     * @exception {DeveloperError} The number of indices must be divisable by three.
     * @exception {DeveloperError} Granularity must be greater than zero.
     */
    PolygonPipeline.computeSubdivision = function(ellipsoid, positions, indices, granularity) {
        granularity = defaultValue(granularity, CesiumMath.RADIANS_PER_DEGREE);

                if (!defined(ellipsoid)) {
            throw new DeveloperError('ellipsoid is required.');
        }
        if (!defined(positions)) {
            throw new DeveloperError('positions is required.');
        }
        if (!defined(indices)) {
            throw new DeveloperError('indices is required.');
        }
        if (indices.length < 3) {
            throw new DeveloperError('At least three indices are required.');
        }
        if (indices.length % 3 !== 0) {
            throw new DeveloperError('The number of indices must be divisable by three.');
        }
        if (granularity <= 0.0) {
            throw new DeveloperError('granularity must be greater than zero.');
        }
        
        // triangles that need (or might need) to be subdivided.
        var triangles = indices.slice(0);

        // New positions due to edge splits are appended to the positions list.
        var i;
        var length = positions.length;
        var subdividedPositions = new Array(length * 3);
        var q = 0;
        for (i = 0; i < length; i++) {
            var item = positions[i];
            subdividedPositions[q++] = item.x;
            subdividedPositions[q++] = item.y;
            subdividedPositions[q++] = item.z;
        }

        var subdividedIndices = [];

        // Used to make sure shared edges are not split more than once.
        var edges = {};

        var radius = ellipsoid.maximumRadius;
        var minDistance = CesiumMath.chordLength(granularity, radius);
        var minDistanceSqrd = minDistance * minDistance;

        while (triangles.length > 0) {
            var i2 = triangles.pop();
            var i1 = triangles.pop();
            var i0 = triangles.pop();

            var v0 = Cartesian3.fromArray(subdividedPositions, i0 * 3, subdivisionV0Scratch);
            var v1 = Cartesian3.fromArray(subdividedPositions, i1 * 3, subdivisionV1Scratch);
            var v2 = Cartesian3.fromArray(subdividedPositions, i2 * 3, subdivisionV2Scratch);

            var s0 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v0, subdivisionS0Scratch), radius, subdivisionS0Scratch);
            var s1 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v1, subdivisionS1Scratch), radius, subdivisionS1Scratch);
            var s2 = Cartesian3.multiplyByScalar(Cartesian3.normalize(v2, subdivisionS2Scratch), radius, subdivisionS2Scratch);

            var g0 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s0, s1, subdivisionMidScratch));
            var g1 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s1, s2, subdivisionMidScratch));
            var g2 = Cartesian3.magnitudeSquared(Cartesian3.subtract(s2, s0, subdivisionMidScratch));

            var max = Math.max(g0, g1, g2);
            var edge;
            var mid;

            // if the max length squared of a triangle edge is greater than the chord length of squared
            // of the granularity, subdivide the triangle
            if (max > minDistanceSqrd) {
                if (g0 === max) {
                    edge = Math.min(i0, i1) + ' ' + Math.max(i0, i1);

                    i = edges[edge];
                    if (!defined(i)) {
                        mid = Cartesian3.add(v0, v1, subdivisionMidScratch);
                        Cartesian3.multiplyByScalar(mid, 0.5, mid);
                        subdividedPositions.push(mid.x, mid.y, mid.z);
                        i = subdividedPositions.length / 3 - 1;
                        edges[edge] = i;
                    }

                    triangles.push(i0, i, i2);
                    triangles.push(i, i1, i2);
                } else if (g1 === max) {
                    edge = Math.min(i1, i2) + ' ' + Math.max(i1, i2);

                    i = edges[edge];
                    if (!defined(i)) {
                        mid = Cartesian3.add(v1, v2, subdivisionMidScratch);
                        Cartesian3.multiplyByScalar(mid, 0.5, mid);
                        subdividedPositions.push(mid.x, mid.y, mid.z);
                        i = subdividedPositions.length / 3 - 1;
                        edges[edge] = i;
                    }

                    triangles.push(i1, i, i0);
                    triangles.push(i, i2, i0);
                } else if (g2 === max) {
                    edge = Math.min(i2, i0) + ' ' + Math.max(i2, i0);

                    i = edges[edge];
                    if (!defined(i)) {
                        mid = Cartesian3.add(v2, v0, subdivisionMidScratch);
                        Cartesian3.multiplyByScalar(mid, 0.5, mid);
                        subdividedPositions.push(mid.x, mid.y, mid.z);
                        i = subdividedPositions.length / 3 - 1;
                        edges[edge] = i;
                    }

                    triangles.push(i2, i, i1);
                    triangles.push(i, i0, i1);
                }
            } else {
                subdividedIndices.push(i0);
                subdividedIndices.push(i1);
                subdividedIndices.push(i2);
            }
        }

        return new Geometry({
            attributes : {
                position : new GeometryAttribute({
                    componentDatatype : ComponentDatatype.DOUBLE,
                    componentsPerAttribute : 3,
                    values : subdividedPositions
                })
            },
            indices : subdividedIndices,
            primitiveType : PrimitiveType.TRIANGLES
        });
    };

    /**
     * Scales each position of a geometry's position attribute to a height, in place.
     *
     * @param {Number[]} positions The array of numbers representing the positions to be scaled
     * @param {Number} [height=0.0] The desired height to add to the positions
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the positions lie.
     * @param {Boolean} [scaleToSurface=true] <code>true</code> if the positions need to be scaled to the surface before the height is added.
     * @returns {Number[]} The input array of positions, scaled to height
     */
    PolygonPipeline.scaleToGeodeticHeight = function(positions, height, ellipsoid, scaleToSurface) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        var n = scaleToGeodeticHeightN;
        var p = scaleToGeodeticHeightP;

        height = defaultValue(height, 0.0);
        scaleToSurface = defaultValue(scaleToSurface, true);

        if (defined(positions)) {
            var length = positions.length;

            for ( var i = 0; i < length; i += 3) {
                Cartesian3.fromArray(positions, i, p);

                if (scaleToSurface) {
                    p = ellipsoid.scaleToGeodeticSurface(p, p);
                }

                if (height !== 0) {
                    n = ellipsoid.geodeticSurfaceNormal(p, n);

                    Cartesian3.multiplyByScalar(n, height, n);
                    Cartesian3.add(p, n, p);
                }

                positions[i] = p.x;
                positions[i + 1] = p.y;
                positions[i + 2] = p.z;
            }
        }

        return positions;
    };

    return PolygonPipeline;
});

/*global define*/
define('Core/CorridorGeometry',[
        './arrayRemoveDuplicates',
        './BoundingSphere',
        './Cartesian3',
        './Cartographic',
        './ComponentDatatype',
        './CornerType',
        './CorridorGeometryLibrary',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PolygonPipeline',
        './PrimitiveType',
        './Rectangle',
        './VertexFormat'
    ], function(
        arrayRemoveDuplicates,
        BoundingSphere,
        Cartesian3,
        Cartographic,
        ComponentDatatype,
        CornerType,
        CorridorGeometryLibrary,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PolygonPipeline,
        PrimitiveType,
        Rectangle,
        VertexFormat) {
    'use strict';

    var cartesian1 = new Cartesian3();
    var cartesian2 = new Cartesian3();
    var cartesian3 = new Cartesian3();
    var cartesian4 = new Cartesian3();
    var cartesian5 = new Cartesian3();
    var cartesian6 = new Cartesian3();

    var scratch1 = new Cartesian3();
    var scratch2 = new Cartesian3();

    function addNormals(attr, normal, left, front, back, vertexFormat) {
        var normals = attr.normals;
        var tangents = attr.tangents;
        var bitangents = attr.bitangents;
        var forward = Cartesian3.normalize(Cartesian3.cross(left, normal, scratch1), scratch1);
        if (vertexFormat.normal) {
            CorridorGeometryLibrary.addAttribute(normals, normal, front, back);
        }
        if (vertexFormat.tangent) {
            CorridorGeometryLibrary.addAttribute(tangents, forward, front, back);
        }
        if (vertexFormat.bitangent) {
            CorridorGeometryLibrary.addAttribute(bitangents, left, front, back);
        }
    }

    function combine(computedPositions, vertexFormat, ellipsoid) {
        var positions = computedPositions.positions;
        var corners = computedPositions.corners;
        var endPositions = computedPositions.endPositions;
        var computedLefts = computedPositions.lefts;
        var computedNormals = computedPositions.normals;
        var attributes = new GeometryAttributes();
        var corner;
        var leftCount = 0;
        var rightCount = 0;
        var i;
        var indicesLength = 0;
        var length;
        for (i = 0; i < positions.length; i += 2) {
            length = positions[i].length - 3;
            leftCount += length; //subtracting 3 to account for duplicate points at corners
            indicesLength += length*2;
            rightCount += positions[i + 1].length - 3;
        }
        leftCount += 3; //add back count for end positions
        rightCount += 3;
        for (i = 0; i < corners.length; i++) {
            corner = corners[i];
            var leftSide = corners[i].leftPositions;
            if (defined(leftSide)) {
                length = leftSide.length;
                leftCount += length;
                indicesLength += length;
            } else {
                length = corners[i].rightPositions.length;
                rightCount += length;
                indicesLength += length;
            }
        }

        var addEndPositions = defined(endPositions);
        var endPositionLength;
        if (addEndPositions) {
            endPositionLength = endPositions[0].length - 3;
            leftCount += endPositionLength;
            rightCount += endPositionLength;
            endPositionLength /= 3;
            indicesLength += endPositionLength * 6;
        }
        var size = leftCount + rightCount;
        var finalPositions = new Float64Array(size);
        var normals = (vertexFormat.normal) ? new Float32Array(size) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(size) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(size) : undefined;
        var attr = {
            normals : normals,
            tangents : tangents,
            bitangents : bitangents
        };
        var front = 0;
        var back = size - 1;
        var UL, LL, UR, LR;
        var normal = cartesian1;
        var left = cartesian2;
        var rightPos, leftPos;
        var halfLength = endPositionLength / 2;

        var indices = IndexDatatype.createTypedArray(size / 3, indicesLength);
        var index = 0;
        if (addEndPositions) { // add rounded end
            leftPos = cartesian3;
            rightPos = cartesian4;
            var firstEndPositions = endPositions[0];
            normal = Cartesian3.fromArray(computedNormals, 0, normal);
            left = Cartesian3.fromArray(computedLefts, 0, left);
            for (i = 0; i < halfLength; i++) {
                leftPos = Cartesian3.fromArray(firstEndPositions, (halfLength - 1 - i) * 3, leftPos);
                rightPos = Cartesian3.fromArray(firstEndPositions, (halfLength + i) * 3, rightPos);
                CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
                CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
                addNormals(attr, normal, left, front, back, vertexFormat);

                LL = front / 3;
                LR = LL + 1;
                UL = (back - 2) / 3;
                UR = UL - 1;
                indices[index++] = UL;
                indices[index++] = LL;
                indices[index++] = UR;
                indices[index++] = UR;
                indices[index++] = LL;
                indices[index++] = LR;

                front += 3;
                back -= 3;
            }
        }

        var posIndex = 0;
        var compIndex = 0;
        var rightEdge = positions[posIndex++]; //add first two edges
        var leftEdge = positions[posIndex++];
        finalPositions.set(rightEdge, front);
        finalPositions.set(leftEdge, back - leftEdge.length + 1);

        left = Cartesian3.fromArray(computedLefts, compIndex, left);
        var rightNormal;
        var leftNormal;
        length = leftEdge.length - 3;
        for (i = 0; i < length; i += 3) {
            rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, i, scratch1), scratch1);
            leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length - i, scratch2), scratch2);
            normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
            addNormals(attr, normal, left, front, back, vertexFormat);

            LL = front / 3;
            LR = LL + 1;
            UL = (back - 2) / 3;
            UR = UL - 1;
            indices[index++] = UL;
            indices[index++] = LL;
            indices[index++] = UR;
            indices[index++] = UR;
            indices[index++] = LL;
            indices[index++] = LR;

            front += 3;
            back -= 3;
        }

        rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, length, scratch1), scratch1);
        leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length, scratch2), scratch2);
        normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
        compIndex += 3;
        for (i = 0; i < corners.length; i++) {
            var j;
            corner = corners[i];
            var l = corner.leftPositions;
            var r = corner.rightPositions;
            var pivot;
            var start;
            var outsidePoint = cartesian6;
            var previousPoint = cartesian3;
            var nextPoint = cartesian4;
            normal = Cartesian3.fromArray(computedNormals, compIndex, normal);
            if (defined(l)) {
                addNormals(attr, normal, left, undefined, back, vertexFormat);
                back -= 3;
                pivot = LR;
                start = UR;
                for (j = 0; j < l.length / 3; j++) {
                    outsidePoint = Cartesian3.fromArray(l, j * 3, outsidePoint);
                    indices[index++] = pivot;
                    indices[index++] = start - j - 1;
                    indices[index++] = start - j;
                    CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, undefined, back);
                    previousPoint = Cartesian3.fromArray(finalPositions, (start - j - 1) * 3, previousPoint);
                    nextPoint = Cartesian3.fromArray(finalPositions, pivot * 3, nextPoint);
                    left = Cartesian3.normalize(Cartesian3.subtract(previousPoint, nextPoint, left), left);
                    addNormals(attr, normal, left, undefined, back, vertexFormat);
                    back -= 3;
                }
                outsidePoint = Cartesian3.fromArray(finalPositions, pivot * 3, outsidePoint);
                previousPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start) * 3, previousPoint), outsidePoint, previousPoint);
                nextPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start - j) * 3, nextPoint), outsidePoint, nextPoint);
                left = Cartesian3.normalize(Cartesian3.add(previousPoint, nextPoint, left), left);
                addNormals(attr, normal, left, front, undefined, vertexFormat);
                front += 3;
            } else {
                addNormals(attr, normal, left, front, undefined, vertexFormat);
                front += 3;
                pivot = UR;
                start = LR;
                for (j = 0; j < r.length / 3; j++) {
                    outsidePoint = Cartesian3.fromArray(r, j * 3, outsidePoint);
                    indices[index++] = pivot;
                    indices[index++] = start + j;
                    indices[index++] = start + j + 1;
                    CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, front);
                    previousPoint = Cartesian3.fromArray(finalPositions, pivot * 3, previousPoint);
                    nextPoint = Cartesian3.fromArray(finalPositions, (start + j) * 3, nextPoint);
                    left = Cartesian3.normalize(Cartesian3.subtract(previousPoint, nextPoint, left), left);
                    addNormals(attr, normal, left, front, undefined, vertexFormat);
                    front += 3;
                }
                outsidePoint = Cartesian3.fromArray(finalPositions, pivot * 3, outsidePoint);
                previousPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, (start + j) * 3, previousPoint), outsidePoint, previousPoint);
                nextPoint = Cartesian3.subtract(Cartesian3.fromArray(finalPositions, start * 3, nextPoint), outsidePoint, nextPoint);
                left = Cartesian3.normalize(Cartesian3.negate(Cartesian3.add(nextPoint, previousPoint, left), left), left);
                addNormals(attr, normal, left, undefined, back, vertexFormat);
                back -= 3;
            }
            rightEdge = positions[posIndex++];
            leftEdge = positions[posIndex++];
            rightEdge.splice(0, 3); //remove duplicate points added by corner
            leftEdge.splice(leftEdge.length - 3, 3);
            finalPositions.set(rightEdge, front);
            finalPositions.set(leftEdge, back - leftEdge.length + 1);
            length = leftEdge.length - 3;

            compIndex += 3;
            left = Cartesian3.fromArray(computedLefts, compIndex, left);
            for (j = 0; j < leftEdge.length; j += 3) {
                rightNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(rightEdge, j, scratch1), scratch1);
                leftNormal = ellipsoid.geodeticSurfaceNormal(Cartesian3.fromArray(leftEdge, length - j, scratch2), scratch2);
                normal = Cartesian3.normalize(Cartesian3.add(rightNormal, leftNormal, normal), normal);
                addNormals(attr, normal, left, front, back, vertexFormat);

                LR = front / 3;
                LL = LR - 1;
                UR = (back - 2) / 3;
                UL = UR + 1;
                indices[index++] = UL;
                indices[index++] = LL;
                indices[index++] = UR;
                indices[index++] = UR;
                indices[index++] = LL;
                indices[index++] = LR;

                front += 3;
                back -= 3;
            }
            front -= 3;
            back += 3;
        }
        normal = Cartesian3.fromArray(computedNormals, computedNormals.length - 3, normal);
        addNormals(attr, normal, left, front, back, vertexFormat);

        if (addEndPositions) { // add rounded end
            front += 3;
            back -= 3;
            leftPos = cartesian3;
            rightPos = cartesian4;
            var lastEndPositions = endPositions[1];
            for (i = 0; i < halfLength; i++) {
                leftPos = Cartesian3.fromArray(lastEndPositions, (endPositionLength - i - 1) * 3, leftPos);
                rightPos = Cartesian3.fromArray(lastEndPositions, i * 3, rightPos);
                CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
                CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
                addNormals(attr, normal, left, front, back, vertexFormat);

                LR = front / 3;
                LL = LR - 1;
                UR = (back - 2) / 3;
                UL = UR + 1;
                indices[index++] = UL;
                indices[index++] = LL;
                indices[index++] = UR;
                indices[index++] = UR;
                indices[index++] = LL;
                indices[index++] = LR;

                front += 3;
                back -= 3;
            }
        }

        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : finalPositions
        });

        if (vertexFormat.st) {
            var st = new Float32Array(size / 3 * 2);
            var rightSt;
            var leftSt;
            var stIndex = 0;
            if (addEndPositions) {
                leftCount /= 3;
                rightCount /= 3;
                var theta = Math.PI / (endPositionLength + 1);
                leftSt = 1 / (leftCount - endPositionLength + 1);
                rightSt = 1 / (rightCount - endPositionLength + 1);
                var a;
                var halfEndPos = endPositionLength / 2;
                for (i = halfEndPos + 1; i < endPositionLength + 1; i++) { // lower left rounded end
                    a = CesiumMath.PI_OVER_TWO + theta * i;
                    st[stIndex++] = rightSt * (1 + Math.cos(a));
                    st[stIndex++] = 0.5 * (1 + Math.sin(a));
                }
                for (i = 1; i < rightCount - endPositionLength + 1; i++) { // bottom edge
                    st[stIndex++] = i * rightSt;
                    st[stIndex++] = 0;
                }
                for (i = endPositionLength; i > halfEndPos; i--) { // lower right rounded end
                    a = CesiumMath.PI_OVER_TWO - i * theta;
                    st[stIndex++] = 1 - rightSt * (1 + Math.cos(a));
                    st[stIndex++] = 0.5 * (1 + Math.sin(a));
                }
                for (i = halfEndPos; i > 0; i--) { // upper right rounded end
                    a = CesiumMath.PI_OVER_TWO - theta * i;
                    st[stIndex++] = 1 - leftSt * (1 + Math.cos(a));
                    st[stIndex++] = 0.5 * (1 + Math.sin(a));
                }
                for (i = leftCount - endPositionLength; i > 0; i--) { // top edge
                    st[stIndex++] = i * leftSt;
                    st[stIndex++] = 1;
                }
                for (i = 1; i < halfEndPos + 1; i++) { // upper left rounded end
                    a = CesiumMath.PI_OVER_TWO + theta * i;
                    st[stIndex++] = leftSt * (1 + Math.cos(a));
                    st[stIndex++] = 0.5 * (1 + Math.sin(a));
                }
            } else {
                leftCount /= 3;
                rightCount /= 3;
                leftSt = 1 / (leftCount - 1);
                rightSt = 1 / (rightCount - 1);
                for (i = 0; i < rightCount; i++) { // bottom edge
                    st[stIndex++] = i * rightSt;
                    st[stIndex++] = 0;
                }
                for (i = leftCount; i > 0; i--) { // top edge
                    st[stIndex++] = (i - 1) * leftSt;
                    st[stIndex++] = 1;
                }
            }

            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : st
            });
        }

        if (vertexFormat.normal) {
            attributes.normal = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : attr.normals
            });
        }

        if (vertexFormat.tangent) {
            attributes.tangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : attr.tangents
            });
        }

        if (vertexFormat.bitangent) {
            attributes.bitangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : attr.bitangents
            });
        }

        return {
            attributes : attributes,
            indices : indices
        };
    }

    function extrudedAttributes(attributes, vertexFormat) {
        if (!vertexFormat.normal && !vertexFormat.tangent && !vertexFormat.bitangent && !vertexFormat.st) {
            return attributes;
        }
        var positions = attributes.position.values;
        var topNormals;
        var topBitangents;
        if (vertexFormat.normal || vertexFormat.bitangent) {
            topNormals = attributes.normal.values;
            topBitangents = attributes.bitangent.values;
        }
        var size = attributes.position.values.length / 18;
        var threeSize = size * 3;
        var twoSize = size * 2;
        var sixSize = threeSize * 2;
        var i;
        if (vertexFormat.normal || vertexFormat.bitangent || vertexFormat.tangent) {
            var normals = (vertexFormat.normal) ? new Float32Array(threeSize * 6) : undefined;
            var tangents = (vertexFormat.tangent) ? new Float32Array(threeSize * 6) : undefined;
            var bitangents = (vertexFormat.bitangent) ? new Float32Array(threeSize * 6) : undefined;
            var topPosition = cartesian1;
            var bottomPosition = cartesian2;
            var previousPosition = cartesian3;
            var normal = cartesian4;
            var tangent = cartesian5;
            var bitangent = cartesian6;
            var attrIndex = sixSize;
            for (i = 0; i < threeSize; i += 3) {
                var attrIndexOffset = attrIndex + sixSize;
                topPosition      = Cartesian3.fromArray(positions, i, topPosition);
                bottomPosition   = Cartesian3.fromArray(positions, i + threeSize, bottomPosition);
                previousPosition = Cartesian3.fromArray(positions, (i + 3) % threeSize, previousPosition);
                bottomPosition   = Cartesian3.subtract(bottomPosition,   topPosition, bottomPosition);
                previousPosition = Cartesian3.subtract(previousPosition, topPosition, previousPosition);
                normal = Cartesian3.normalize(Cartesian3.cross(bottomPosition, previousPosition, normal), normal);
                if (vertexFormat.normal) {
                    CorridorGeometryLibrary.addAttribute(normals, normal, attrIndexOffset);
                    CorridorGeometryLibrary.addAttribute(normals, normal, attrIndexOffset + 3);
                    CorridorGeometryLibrary.addAttribute(normals, normal, attrIndex);
                    CorridorGeometryLibrary.addAttribute(normals, normal, attrIndex + 3);
                }
                if (vertexFormat.tangent || vertexFormat.bitangent) {
                    bitangent = Cartesian3.fromArray(topNormals, i, bitangent);
                    if (vertexFormat.bitangent) {
                        CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndexOffset);
                        CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndexOffset + 3);
                        CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndex);
                        CorridorGeometryLibrary.addAttribute(bitangents, bitangent, attrIndex + 3);
                    }

                    if (vertexFormat.tangent) {
                        tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent);
                        CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndexOffset);
                        CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndexOffset + 3);
                        CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndex);
                        CorridorGeometryLibrary.addAttribute(tangents, tangent, attrIndex + 3);
                    }
                }
                attrIndex += 6;
            }

            if (vertexFormat.normal) {
                normals.set(topNormals); //top
                for (i = 0; i < threeSize; i += 3) { //bottom normals
                    normals[i + threeSize] = -topNormals[i];
                    normals[i + threeSize + 1] = -topNormals[i + 1];
                    normals[i + threeSize + 2] = -topNormals[i + 2];
                }
                attributes.normal.values = normals;
            } else {
                attributes.normal = undefined;
            }

            if (vertexFormat.bitangent) {
                bitangents.set(topBitangents); //top
                bitangents.set(topBitangents, threeSize); //bottom
                attributes.bitangent.values = bitangents;
            } else {
                attributes.bitangent = undefined;
            }

            if (vertexFormat.tangent) {
                var topTangents = attributes.tangent.values;
                tangents.set(topTangents); //top
                tangents.set(topTangents, threeSize); //bottom
                attributes.tangent.values = tangents;
            }
        }
        if (vertexFormat.st) {
            var topSt = attributes.st.values;
            var st = new Float32Array(twoSize * 6);
            st.set(topSt); //top
            st.set(topSt, twoSize); //bottom
            var index = twoSize * 2;

            for ( var j = 0; j < 2; j++) {
                st[index++] = topSt[0];
                st[index++] = topSt[1];
                for (i = 2; i < twoSize; i += 2) {
                    var s = topSt[i];
                    var t = topSt[i + 1];
                    st[index++] = s;
                    st[index++] = t;
                    st[index++] = s;
                    st[index++] = t;
                }
                st[index++] = topSt[0];
                st[index++] = topSt[1];
            }
            attributes.st.values = st;
        }

        return attributes;
    }

    function addWallPositions(positions, index, wallPositions) {
        wallPositions[index++] = positions[0];
        wallPositions[index++] = positions[1];
        wallPositions[index++] = positions[2];
        for ( var i = 3; i < positions.length; i += 3) {
            var x = positions[i];
            var y = positions[i + 1];
            var z = positions[i + 2];
            wallPositions[index++] = x;
            wallPositions[index++] = y;
            wallPositions[index++] = z;
            wallPositions[index++] = x;
            wallPositions[index++] = y;
            wallPositions[index++] = z;
        }
        wallPositions[index++] = positions[0];
        wallPositions[index++] = positions[1];
        wallPositions[index++] = positions[2];

        return wallPositions;
    }

    function computePositionsExtruded(params, vertexFormat) {
        var topVertexFormat = new VertexFormat({
            position : vertexFormat.position,
            normal : (vertexFormat.normal || vertexFormat.bitangent || params.shadowVolume),
            tangent : vertexFormat.tangent,
            bitangent : (vertexFormat.normal || vertexFormat.bitangent),
            st : vertexFormat.st
        });
        var ellipsoid = params.ellipsoid;
        var computedPositions = CorridorGeometryLibrary.computePositions(params);
        var attr = combine(computedPositions, topVertexFormat, ellipsoid);
        var height = params.height;
        var extrudedHeight = params.extrudedHeight;
        var attributes = attr.attributes;
        var indices = attr.indices;
        var positions = attributes.position.values;
        var length = positions.length;
        var newPositions = new Float64Array(length * 6);
        var extrudedPositions = new Float64Array(length);
        extrudedPositions.set(positions);
        var wallPositions = new Float64Array(length * 4);

        positions = PolygonPipeline.scaleToGeodeticHeight(positions, height, ellipsoid);
        wallPositions = addWallPositions(positions, 0, wallPositions);
        extrudedPositions = PolygonPipeline.scaleToGeodeticHeight(extrudedPositions, extrudedHeight, ellipsoid);
        wallPositions = addWallPositions(extrudedPositions, length * 2, wallPositions);
        newPositions.set(positions);
        newPositions.set(extrudedPositions, length);
        newPositions.set(wallPositions, length * 2);
        attributes.position.values = newPositions;

        attributes = extrudedAttributes(attributes, vertexFormat);
        var size = length / 3;
        if (params.shadowVolume) {
            var topNormals = attributes.normal.values;
            length = topNormals.length;

            var extrudeNormals = new Float32Array(length * 6);
            for (i = 0; i < length; i ++) {
                topNormals[i] = -topNormals[i];
            }
            //only get normals for bottom layer that's going to be pushed down
            extrudeNormals.set(topNormals, length); //bottom face
            extrudeNormals = addWallPositions(topNormals, length*4, extrudeNormals); //bottom wall
            attributes.extrudeDirection = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : extrudeNormals
            });
            if (!vertexFormat.normal) {
                attributes.normal = undefined;
            }
        }

        var i;
        var iLength = indices.length;
        var twoSize = size + size;
        var newIndices = IndexDatatype.createTypedArray(newPositions.length / 3, iLength * 2 + twoSize * 3);
        newIndices.set(indices);
        var index = iLength;
        for (i = 0; i < iLength; i += 3) { // bottom indices
            var v0 = indices[i];
            var v1 = indices[i + 1];
            var v2 = indices[i + 2];
            newIndices[index++] = v2 + size;
            newIndices[index++] = v1 + size;
            newIndices[index++] = v0 + size;
        }

        var UL, LL, UR, LR;

        for (i = 0; i < twoSize; i += 2) { //wall indices
            UL = i + twoSize;
            LL = UL + twoSize;
            UR = UL + 1;
            LR = LL + 1;
            newIndices[index++] = UL;
            newIndices[index++] = LL;
            newIndices[index++] = UR;
            newIndices[index++] = UR;
            newIndices[index++] = LL;
            newIndices[index++] = LR;
        }

        return {
            attributes : attributes,
            indices : newIndices
        };
    }

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartographic = new Cartographic();

    function computeOffsetPoints(position1, position2, ellipsoid, halfWidth, min, max) {
        // Compute direction of offset the point
        var direction = Cartesian3.subtract(position2, position1, scratchCartesian1);
        Cartesian3.normalize(direction, direction);
        var normal = ellipsoid.geodeticSurfaceNormal(position1, scratchCartesian2);
        var offsetDirection = Cartesian3.cross(direction, normal, scratchCartesian1);
        Cartesian3.multiplyByScalar(offsetDirection, halfWidth, offsetDirection);

        var minLat = min.latitude;
        var minLon = min.longitude;
        var maxLat = max.latitude;
        var maxLon = max.longitude;

        // Compute 2 offset points
        Cartesian3.add(position1, offsetDirection, scratchCartesian2);
        ellipsoid.cartesianToCartographic(scratchCartesian2, scratchCartographic);

        var lat = scratchCartographic.latitude;
        var lon = scratchCartographic.longitude;
        minLat = Math.min(minLat, lat);
        minLon = Math.min(minLon, lon);
        maxLat = Math.max(maxLat, lat);
        maxLon = Math.max(maxLon, lon);

        Cartesian3.subtract(position1, offsetDirection, scratchCartesian2);
        ellipsoid.cartesianToCartographic(scratchCartesian2, scratchCartographic);

        lat = scratchCartographic.latitude;
        lon = scratchCartographic.longitude;
        minLat = Math.min(minLat, lat);
        minLon = Math.min(minLon, lon);
        maxLat = Math.max(maxLat, lat);
        maxLon = Math.max(maxLon, lon);

        min.latitude = minLat;
        min.longitude = minLon;
        max.latitude = maxLat;
        max.longitude = maxLon;
    }

    var scratchCartesianOffset = new Cartesian3();
    var scratchCartesianEnds = new Cartesian3();
    var scratchCartographicMin = new Cartographic();
    var scratchCartographicMax = new Cartographic();

    function computeRectangle(positions, ellipsoid, width, cornerType) {
        var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
        var length = cleanPositions.length;
        if (length < 2 || width <= 0) {
            return new Rectangle();
        }
        var halfWidth = width * 0.5;

        scratchCartographicMin.latitude = Number.POSITIVE_INFINITY;
        scratchCartographicMin.longitude = Number.POSITIVE_INFINITY;
        scratchCartographicMax.latitude = Number.NEGATIVE_INFINITY;
        scratchCartographicMax.longitude = Number.NEGATIVE_INFINITY;

        var lat, lon;
        if (cornerType === CornerType.ROUNDED) {
            // Compute start cap
            var first = cleanPositions[0];
            Cartesian3.subtract(first, cleanPositions[1], scratchCartesianOffset);
            Cartesian3.normalize(scratchCartesianOffset, scratchCartesianOffset);
            Cartesian3.multiplyByScalar(scratchCartesianOffset, halfWidth, scratchCartesianOffset);
            Cartesian3.add(first, scratchCartesianOffset, scratchCartesianEnds);

            ellipsoid.cartesianToCartographic(scratchCartesianEnds, scratchCartographic);
            lat = scratchCartographic.latitude;
            lon = scratchCartographic.longitude;
            scratchCartographicMin.latitude = Math.min(scratchCartographicMin.latitude, lat);
            scratchCartographicMin.longitude = Math.min(scratchCartographicMin.longitude, lon);
            scratchCartographicMax.latitude = Math.max(scratchCartographicMax.latitude, lat);
            scratchCartographicMax.longitude = Math.max(scratchCartographicMax.longitude, lon);
        }

        // Compute the rest
        for (var i = 0; i < length-1; ++i) {
            computeOffsetPoints(cleanPositions[i], cleanPositions[i+1], ellipsoid, halfWidth,
                scratchCartographicMin, scratchCartographicMax);
        }

        // Compute ending point
        var last = cleanPositions[length-1];
        Cartesian3.subtract(last, cleanPositions[length-2], scratchCartesianOffset);
        Cartesian3.normalize(scratchCartesianOffset, scratchCartesianOffset);
        Cartesian3.multiplyByScalar(scratchCartesianOffset, halfWidth, scratchCartesianOffset);
        Cartesian3.add(last, scratchCartesianOffset, scratchCartesianEnds);
        computeOffsetPoints(last, scratchCartesianEnds, ellipsoid, halfWidth,
            scratchCartographicMin, scratchCartographicMax);

        if (cornerType === CornerType.ROUNDED) {
            // Compute end cap
            ellipsoid.cartesianToCartographic(scratchCartesianEnds, scratchCartographic);
            lat = scratchCartographic.latitude;
            lon = scratchCartographic.longitude;
            scratchCartographicMin.latitude = Math.min(scratchCartographicMin.latitude, lat);
            scratchCartographicMin.longitude = Math.min(scratchCartographicMin.longitude, lon);
            scratchCartographicMax.latitude = Math.max(scratchCartographicMax.latitude, lat);
            scratchCartographicMax.longitude = Math.max(scratchCartographicMax.longitude, lon);
        }

        var rectangle = new Rectangle();
        rectangle.north = scratchCartographicMax.latitude;
        rectangle.south = scratchCartographicMin.latitude;
        rectangle.east = scratchCartographicMax.longitude;
        rectangle.west = scratchCartographicMin.longitude;

        return rectangle;
    }

    /**
     * A description of a corridor. Corridor geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
     *
     * @alias CorridorGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of positions that define the center of the corridor.
     * @param {Number} options.width The distance between the edges of the corridor in meters.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Number} [options.height=0] The distance in meters between the ellipsoid surface and the positions.
     * @param {Number} [options.extrudedHeight] The distance in meters between the ellipsoid surface and the extruded face.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
     *
     * @see CorridorGeometry.createGeometry
     * @see Packable
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo}
     *
     * @example
     * var corridor = new Cesium.CorridorGeometry({
     *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
     *   positions : Cesium.Cartesian3.fromDegreesArray([-72.0, 40.0, -70.0, 35.0]),
     *   width : 100000
     * });
     */
    function CorridorGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.positions;
        var width = options.width;

                if (!defined(positions)) {
            throw new DeveloperError('options.positions is required.');
        }
        if (!defined(width)) {
            throw new DeveloperError('options.width is required.');
        }
        
        this._positions = positions;
        this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
        this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
        this._width = width;
        this._height = defaultValue(options.height, 0);
        this._extrudedHeight = defaultValue(options.extrudedHeight, this._height);
        this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._shadowVolume = defaultValue(options.shadowVolume, false);
        this._workerName = 'createCorridorGeometry';
        this._rectangle = computeRectangle(positions, this._ellipsoid, width, this._cornerType);

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = 1 + positions.length * Cartesian3.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 6;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {CorridorGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    CorridorGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        Rectangle.pack(value._rectangle, array, startingIndex);
        startingIndex += Rectangle.packedLength;

        array[startingIndex++] = value._width;
        array[startingIndex++] = value._height;
        array[startingIndex++] = value._extrudedHeight;
        array[startingIndex++] = value._cornerType;
        array[startingIndex++] = value._granularity;
        array[startingIndex] = value._shadowVolume ? 1.0 : 0.0;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchVertexFormat = new VertexFormat();
    var scratchRectangle = new Rectangle();
    var scratchOptions = {
        positions : undefined,
        ellipsoid : scratchEllipsoid,
        vertexFormat : scratchVertexFormat,
        width : undefined,
        height : undefined,
        extrudedHeight : undefined,
        cornerType : undefined,
        granularity : undefined,
        shadowVolume: undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {CorridorGeometry} [result] The object into which to store the result.
     * @returns {CorridorGeometry} The modified result parameter or a new CorridorGeometry instance if one was not provided.
     */
    CorridorGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
        startingIndex += Rectangle.packedLength;

        var width = array[startingIndex++];
        var height = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var cornerType = array[startingIndex++];
        var granularity = array[startingIndex++];
        var shadowVolume = array[startingIndex] === 1.0;

        if (!defined(result)) {
            scratchOptions.positions = positions;
            scratchOptions.width = width;
            scratchOptions.height = height;
            scratchOptions.extrudedHeight = extrudedHeight;
            scratchOptions.cornerType = cornerType;
            scratchOptions.granularity = granularity;
            scratchOptions.shadowVolume = shadowVolume;
            return new CorridorGeometry(scratchOptions);
        }

        result._positions = positions;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._width = width;
        result._height = height;
        result._extrudedHeight = extrudedHeight;
        result._cornerType = cornerType;
        result._granularity = granularity;
        result._rectangle = Rectangle.clone(rectangle);
        result._shadowVolume = shadowVolume;

        return result;
    };

    /**
     * Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere.
     *
     * @param {CorridorGeometry} corridorGeometry A description of the corridor.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    CorridorGeometry.createGeometry = function(corridorGeometry) {
        var positions = corridorGeometry._positions;
        var height = corridorGeometry._height;
        var width = corridorGeometry._width;
        var extrudedHeight = corridorGeometry._extrudedHeight;
        var extrude = (height !== extrudedHeight);

        var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);

        if ((cleanPositions.length < 2) || (width <= 0)) {
            return;
        }

        var ellipsoid = corridorGeometry._ellipsoid;
        var vertexFormat = corridorGeometry._vertexFormat;
        var params = {
            ellipsoid : ellipsoid,
            positions : cleanPositions,
            width : width,
            cornerType : corridorGeometry._cornerType,
            granularity : corridorGeometry._granularity,
            saveAttributes: true
        };
        var attr;
        if (extrude) {
            var h = Math.max(height, extrudedHeight);
            extrudedHeight = Math.min(height, extrudedHeight);
            height = h;
            params.height = height;
            params.extrudedHeight = extrudedHeight;
            params.shadowVolume = corridorGeometry._shadowVolume;
            attr = computePositionsExtruded(params, vertexFormat);
        } else {
            var computedPositions = CorridorGeometryLibrary.computePositions(params);
            attr = combine(computedPositions, vertexFormat, ellipsoid);
            attr.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(attr.attributes.position.values, height, ellipsoid);
        }
        var attributes = attr.attributes;
        var boundingSphere = BoundingSphere.fromVertices(attributes.position.values, undefined, 3);
        if (!vertexFormat.position) {
            attr.attributes.position.values = undefined;
        }

        return new Geometry({
            attributes : attributes,
            indices : attr.indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : boundingSphere
        });
    };

    /**
     * @private
     */
    CorridorGeometry.createShadowVolume = function(corridorGeometry, minHeightFunc, maxHeightFunc) {
        var granularity = corridorGeometry._granularity;
        var ellipsoid = corridorGeometry._ellipsoid;

        var minHeight = minHeightFunc(granularity, ellipsoid);
        var maxHeight = maxHeightFunc(granularity, ellipsoid);

        return new CorridorGeometry({
            positions : corridorGeometry._positions,
            width : corridorGeometry._width,
            cornerType : corridorGeometry._cornerType,
            ellipsoid : ellipsoid,
            granularity : granularity,
            extrudedHeight : minHeight,
            height : maxHeight,
            vertexFormat : VertexFormat.POSITION_ONLY,
            shadowVolume: true
        });
    };

    defineProperties(CorridorGeometry.prototype, {
        /**
         * @private
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        }
    });

    return CorridorGeometry;
});

/*global define*/
define('Core/CorridorOutlineGeometry',[
        './arrayRemoveDuplicates',
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './CornerType',
        './CorridorGeometryLibrary',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PolygonPipeline',
        './PrimitiveType'
    ], function(
        arrayRemoveDuplicates,
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        CornerType,
        CorridorGeometryLibrary,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PolygonPipeline,
        PrimitiveType) {
    'use strict';

    var cartesian1 = new Cartesian3();
    var cartesian2 = new Cartesian3();
    var cartesian3 = new Cartesian3();

    function combine(computedPositions, cornerType) {
        var wallIndices = [];
        var positions = computedPositions.positions;
        var corners = computedPositions.corners;
        var endPositions = computedPositions.endPositions;
        var attributes = new GeometryAttributes();
        var corner;
        var leftCount = 0;
        var rightCount = 0;
        var i;
        var indicesLength = 0;
        var length;
        for (i = 0; i < positions.length; i += 2) {
            length = positions[i].length - 3;
            leftCount += length; //subtracting 3 to account for duplicate points at corners
            indicesLength += length / 3 * 4;
            rightCount += positions[i + 1].length - 3;
        }
        leftCount += 3; //add back count for end positions
        rightCount += 3;
        for (i = 0; i < corners.length; i++) {
            corner = corners[i];
            var leftSide = corners[i].leftPositions;
            if (defined(leftSide)) {
                length = leftSide.length;
                leftCount += length;
                indicesLength += length / 3 * 2;
            } else {
                length = corners[i].rightPositions.length;
                rightCount += length;
                indicesLength += length / 3 * 2;
            }
        }

        var addEndPositions = defined(endPositions);
        var endPositionLength;
        if (addEndPositions) {
            endPositionLength = endPositions[0].length - 3;
            leftCount += endPositionLength;
            rightCount += endPositionLength;
            endPositionLength /= 3;
            indicesLength += endPositionLength * 4;
        }
        var size = leftCount + rightCount;
        var finalPositions = new Float64Array(size);
        var front = 0;
        var back = size - 1;
        var UL, LL, UR, LR;
        var rightPos, leftPos;
        var halfLength = endPositionLength / 2;

        var indices = IndexDatatype.createTypedArray(size / 3, indicesLength + 4);
        var index = 0;

        indices[index++] = front / 3;
        indices[index++] = (back - 2) / 3;
        if (addEndPositions) { // add rounded end
            wallIndices.push(front / 3);
            leftPos = cartesian1;
            rightPos = cartesian2;
            var firstEndPositions = endPositions[0];
            for (i = 0; i < halfLength; i++) {
                leftPos = Cartesian3.fromArray(firstEndPositions, (halfLength - 1 - i) * 3, leftPos);
                rightPos = Cartesian3.fromArray(firstEndPositions, (halfLength + i) * 3, rightPos);
                CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);
                CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);

                LL = front / 3;
                LR = LL + 1;
                UL = (back - 2) / 3;
                UR = UL - 1;
                indices[index++] = UL;
                indices[index++] = UR;
                indices[index++] = LL;
                indices[index++] = LR;

                front += 3;
                back -= 3;
            }
        }

        var posIndex = 0;
        var rightEdge = positions[posIndex++]; //add first two edges
        var leftEdge = positions[posIndex++];
        finalPositions.set(rightEdge, front);
        finalPositions.set(leftEdge, back - leftEdge.length + 1);

        length = leftEdge.length - 3;
        wallIndices.push(front / 3, (back - 2) / 3);
        for (i = 0; i < length; i += 3) {
            LL = front / 3;
            LR = LL + 1;
            UL = (back - 2) / 3;
            UR = UL - 1;
            indices[index++] = UL;
            indices[index++] = UR;
            indices[index++] = LL;
            indices[index++] = LR;

            front += 3;
            back -= 3;
        }

        for (i = 0; i < corners.length; i++) {
            var j;
            corner = corners[i];
            var l = corner.leftPositions;
            var r = corner.rightPositions;
            var start;
            var outsidePoint = cartesian3;
            if (defined(l)) {
                back -= 3;
                start = UR;
                wallIndices.push(LR);
                for (j = 0; j < l.length / 3; j++) {
                    outsidePoint = Cartesian3.fromArray(l, j * 3, outsidePoint);
                    indices[index++] = start - j - 1;
                    indices[index++] = start - j;
                    CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, undefined, back);
                    back -= 3;
                }
                wallIndices.push(start - Math.floor(l.length / 6));
                if (cornerType === CornerType.BEVELED) {
                    wallIndices.push((back - 2) / 3 + 1);
                }
                front += 3;
            } else {
                front += 3;
                start = LR;
                wallIndices.push(UR);
                for (j = 0; j < r.length / 3; j++) {
                    outsidePoint = Cartesian3.fromArray(r, j * 3, outsidePoint);
                    indices[index++] = start + j;
                    indices[index++] = start + j + 1;
                    CorridorGeometryLibrary.addAttribute(finalPositions, outsidePoint, front);
                    front += 3;
                }
                wallIndices.push(start + Math.floor(r.length / 6));
                if (cornerType === CornerType.BEVELED) {
                    wallIndices.push(front / 3 - 1);
                }
                back -= 3;
            }
            rightEdge = positions[posIndex++];
            leftEdge = positions[posIndex++];
            rightEdge.splice(0, 3); //remove duplicate points added by corner
            leftEdge.splice(leftEdge.length - 3, 3);
            finalPositions.set(rightEdge, front);
            finalPositions.set(leftEdge, back - leftEdge.length + 1);
            length = leftEdge.length - 3;

            for (j = 0; j < leftEdge.length; j += 3) {
                LR = front / 3;
                LL = LR - 1;
                UR = (back - 2) / 3;
                UL = UR + 1;
                indices[index++] = UL;
                indices[index++] = UR;
                indices[index++] = LL;
                indices[index++] = LR;
                front += 3;
                back -= 3;
            }
            front -= 3;
            back += 3;
            wallIndices.push(front / 3, (back - 2) / 3);
        }

        if (addEndPositions) { // add rounded end
            front += 3;
            back -= 3;
            leftPos = cartesian1;
            rightPos = cartesian2;
            var lastEndPositions = endPositions[1];
            for (i = 0; i < halfLength; i++) {
                leftPos = Cartesian3.fromArray(lastEndPositions, (endPositionLength - i - 1) * 3, leftPos);
                rightPos = Cartesian3.fromArray(lastEndPositions, i * 3, rightPos);
                CorridorGeometryLibrary.addAttribute(finalPositions, leftPos, undefined, back);
                CorridorGeometryLibrary.addAttribute(finalPositions, rightPos, front);

                LR = front / 3;
                LL = LR - 1;
                UR = (back - 2) / 3;
                UL = UR + 1;
                indices[index++] = UL;
                indices[index++] = UR;
                indices[index++] = LL;
                indices[index++] = LR;

                front += 3;
                back -= 3;
            }

            wallIndices.push(front / 3);
        } else {
            wallIndices.push(front / 3, (back - 2) / 3);
        }
        indices[index++] = front / 3;
        indices[index++] = (back - 2) / 3;

        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : finalPositions
        });

        return {
            attributes : attributes,
            indices : indices,
            wallIndices : wallIndices
        };
    }

    function computePositionsExtruded(params) {
        var ellipsoid = params.ellipsoid;
        var computedPositions = CorridorGeometryLibrary.computePositions(params);
        var attr = combine(computedPositions, params.cornerType);
        var wallIndices = attr.wallIndices;
        var height = params.height;
        var extrudedHeight = params.extrudedHeight;
        var attributes = attr.attributes;
        var indices = attr.indices;
        var positions = attributes.position.values;
        var length = positions.length;
        var extrudedPositions = new Float64Array(length);
        extrudedPositions.set(positions);
        var newPositions = new Float64Array(length * 2);

        positions = PolygonPipeline.scaleToGeodeticHeight(positions, height, ellipsoid);
        extrudedPositions = PolygonPipeline.scaleToGeodeticHeight(extrudedPositions, extrudedHeight, ellipsoid);
        newPositions.set(positions);
        newPositions.set(extrudedPositions, length);
        attributes.position.values = newPositions;

        length /= 3;
        var i;
        var iLength = indices.length;
        var newIndices = IndexDatatype.createTypedArray(newPositions.length / 3, (iLength + wallIndices.length) * 2);
        newIndices.set(indices);
        var index = iLength;
        for (i = 0; i < iLength; i += 2) { // bottom indices
            var v0 = indices[i];
            var v1 = indices[i + 1];
            newIndices[index++] = v0 + length;
            newIndices[index++] = v1 + length;
        }

        var UL, LL;
        for (i = 0; i < wallIndices.length; i++) { //wall indices
            UL = wallIndices[i];
            LL = UL + length;
            newIndices[index++] = UL;
            newIndices[index++] = LL;
        }

        return {
            attributes : attributes,
            indices : newIndices
        };
    }

    /**
     * A description of a corridor outline.
     *
     * @alias CorridorOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of positions that define the center of the corridor outline.
     * @param {Number} options.width The distance between the edges of the corridor outline.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Number} [options.height=0] The distance in meters between the positions and the ellipsoid surface.
     * @param {Number} [options.extrudedHeight] The distance in meters between the extruded face and the ellipsoid surface.
     * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
     *
     * @see CorridorOutlineGeometry.createGeometry
     *
     * @example
     * var corridor = new Cesium.CorridorOutlineGeometry({
     *   positions : Cesium.Cartesian3.fromDegreesArray([-72.0, 40.0, -70.0, 35.0]),
     *   width : 100000
     * });
     */
    function CorridorOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.positions;
        var width = options.width;

                if (!defined(positions)) {
            throw new DeveloperError('options.positions is required.');
        }
        if (!defined(width)) {
            throw new DeveloperError('options.width is required.');
        }
        
        this._positions = positions;
        this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
        this._width = width;
        this._height = defaultValue(options.height, 0);
        this._extrudedHeight = defaultValue(options.extrudedHeight, this._height);
        this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._workerName = 'createCorridorOutlineGeometry';

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = 1 + positions.length * Cartesian3.packedLength + Ellipsoid.packedLength + 5;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {CorridorOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    CorridorOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex++] = value._width;
        array[startingIndex++] = value._height;
        array[startingIndex++] = value._extrudedHeight;
        array[startingIndex++] = value._cornerType;
        array[startingIndex]   = value._granularity;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchOptions = {
        positions : undefined,
        ellipsoid : scratchEllipsoid,
        width : undefined,
        height : undefined,
        extrudedHeight : undefined,
        cornerType : undefined,
        granularity : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {CorridorOutlineGeometry} [result] The object into which to store the result.
     * @returns {CorridorOutlineGeometry} The modified result parameter or a new CorridorOutlineGeometry instance if one was not provided.
     */
    CorridorOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (var i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var width = array[startingIndex++];
        var height = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var cornerType = array[startingIndex++];
        var granularity = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.positions = positions;
            scratchOptions.width = width;
            scratchOptions.height = height;
            scratchOptions.extrudedHeight = extrudedHeight;
            scratchOptions.cornerType = cornerType;
            scratchOptions.granularity = granularity;
            return new CorridorOutlineGeometry(scratchOptions);
        }

        result._positions = positions;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._width = width;
        result._height = height;
        result._extrudedHeight = extrudedHeight;
        result._cornerType = cornerType;
        result._granularity = granularity;

        return result;
    };

    /**
     * Computes the geometric representation of a corridor, including its vertices, indices, and a bounding sphere.
     *
     * @param {CorridorOutlineGeometry} corridorOutlineGeometry A description of the corridor.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    CorridorOutlineGeometry.createGeometry = function(corridorOutlineGeometry) {
        var positions = corridorOutlineGeometry._positions;
        var height = corridorOutlineGeometry._height;
        var width = corridorOutlineGeometry._width;
        var extrudedHeight = corridorOutlineGeometry._extrudedHeight;
        var extrude = (height !== extrudedHeight);

        var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);

        if ((cleanPositions.length < 2) || (width <= 0)) {
            return;
        }

        var ellipsoid = corridorOutlineGeometry._ellipsoid;
        var params = {
            ellipsoid : ellipsoid,
            positions : cleanPositions,
            width : width,
            cornerType : corridorOutlineGeometry._cornerType,
            granularity : corridorOutlineGeometry._granularity,
            saveAttributes : false
        };
        var attr;
        if (extrude) {
            var h = Math.max(height, extrudedHeight);
            extrudedHeight = Math.min(height, extrudedHeight);
            height = h;
            params.height = height;
            params.extrudedHeight = extrudedHeight;
            attr = computePositionsExtruded(params);
        } else {
            var computedPositions = CorridorGeometryLibrary.computePositions(params);
            attr = combine(computedPositions, params.cornerType);
            attr.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(attr.attributes.position.values, height, ellipsoid);
        }
        var attributes = attr.attributes;
        var boundingSphere = BoundingSphere.fromVertices(attributes.position.values, undefined, 3);

        return new Geometry({
            attributes : attributes,
            indices : attr.indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : boundingSphere
        });
    };

    return CorridorOutlineGeometry;
});

/*global define*/
define('Core/createGuid',[],function() {
    'use strict';

    /**
     * Creates a Globally unique identifier (GUID) string.  A GUID is 128 bits long, and can guarantee uniqueness across space and time.
     *
     * @exports createGuid
     *
     * @returns {String}
     *
     *
     * @example
     * this.guid = Cesium.createGuid();
     * 
     * @see {@link http://www.ietf.org/rfc/rfc4122.txt|RFC 4122 A Universally Unique IDentifier (UUID) URN Namespace}
     */
    function createGuid() {
        // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = Math.random() * 16 | 0;
            var v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }

    return createGuid;
});

/*global define*/
define('Core/CylinderGeometryLibrary',[
        './Math'
    ], function(
        CesiumMath) {
    'use strict';

    /**
     * @private
     */
    var CylinderGeometryLibrary = {};

    /**
     * @private
     */
    CylinderGeometryLibrary.computePositions = function(length, topRadius, bottomRadius, slices, fill){
        var topZ = length * 0.5;
        var bottomZ = -topZ;

        var twoSlice = slices + slices;
        var size = (fill) ? 2 * twoSlice : twoSlice;
        var positions = new Float64Array(size*3);
        var i;
        var index = 0;
        var tbIndex = 0;
        var bottomOffset = (fill) ? twoSlice*3 : 0;
        var topOffset = (fill) ? (twoSlice + slices)*3 : slices*3;

        for (i = 0; i < slices; i++) {
            var angle = i / slices * CesiumMath.TWO_PI;
            var x = Math.cos(angle);
            var y = Math.sin(angle);
            var bottomX = x * bottomRadius;
            var bottomY = y * bottomRadius;
            var topX = x * topRadius;
            var topY = y * topRadius;

            positions[tbIndex + bottomOffset] = bottomX;
            positions[tbIndex + bottomOffset + 1] = bottomY;
            positions[tbIndex + bottomOffset + 2] = bottomZ;

            positions[tbIndex + topOffset] = topX;
            positions[tbIndex + topOffset + 1] = topY;
            positions[tbIndex + topOffset + 2] = topZ;
            tbIndex += 3;
            if (fill) {
                positions[index++] = bottomX;
                positions[index++] = bottomY;
                positions[index++] = bottomZ;
                positions[index++] = topX;
                positions[index++] = topY;
                positions[index++] = topZ;
            }
        }

        return positions;
    };

    return CylinderGeometryLibrary;
});

/*global define*/
define('Core/CylinderGeometry',[
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './CylinderGeometryLibrary',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PrimitiveType',
        './VertexFormat'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        CylinderGeometryLibrary,
        defaultValue,
        defined,
        DeveloperError,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PrimitiveType,
        VertexFormat) {
    'use strict';

    var radiusScratch = new Cartesian2();
    var normalScratch = new Cartesian3();
    var bitangentScratch = new Cartesian3();
    var tangentScratch = new Cartesian3();
    var positionScratch = new Cartesian3();


    /**
     * A description of a cylinder.
     *
     * @alias CylinderGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Number} options.length The length of the cylinder.
     * @param {Number} options.topRadius The radius of the top of the cylinder.
     * @param {Number} options.bottomRadius The radius of the bottom of the cylinder.
     * @param {Number} [options.slices=128] The number of edges around the perimeter of the cylinder.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     *
     * @exception {DeveloperError} options.length must be greater than 0.
     * @exception {DeveloperError} options.topRadius must be greater than 0.
     * @exception {DeveloperError} options.bottomRadius must be greater than 0.
     * @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0.
     * @exception {DeveloperError} options.slices must be greater than or equal to 3.
     *
     * @see CylinderGeometry.createGeometry
     *
     * @example
     * // create cylinder geometry
     * var cylinder = new Cesium.CylinderGeometry({
     *     length: 200000,
     *     topRadius: 80000,
     *     bottomRadius: 200000,
     * });
     * var geometry = Cesium.CylinderGeometry.createGeometry(cylinder);
     */
    function CylinderGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var length = options.length;
        var topRadius = options.topRadius;
        var bottomRadius = options.bottomRadius;
        var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
        var slices = defaultValue(options.slices, 128);

                if (!defined(length)) {
            throw new DeveloperError('options.length must be defined.');
        }
        if (!defined(topRadius)) {
            throw new DeveloperError('options.topRadius must be defined.');
        }
        if (!defined(bottomRadius)) {
            throw new DeveloperError('options.bottomRadius must be defined.');
        }
        if (slices < 3) {
            throw new DeveloperError('options.slices must be greater than or equal to 3.');
        }
        
        this._length = length;
        this._topRadius = topRadius;
        this._bottomRadius = bottomRadius;
        this._vertexFormat = VertexFormat.clone(vertexFormat);
        this._slices = slices;
        this._workerName = 'createCylinderGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    CylinderGeometry.packedLength = VertexFormat.packedLength + 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {CylinderGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    CylinderGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        array[startingIndex++] = value._length;
        array[startingIndex++] = value._topRadius;
        array[startingIndex++] = value._bottomRadius;
        array[startingIndex]   = value._slices;

        return array;
    };

    var scratchVertexFormat = new VertexFormat();
    var scratchOptions = {
        vertexFormat : scratchVertexFormat,
        length : undefined,
        topRadius : undefined,
        bottomRadius : undefined,
        slices : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {CylinderGeometry} [result] The object into which to store the result.
     * @returns {CylinderGeometry} The modified result parameter or a new CylinderGeometry instance if one was not provided.
     */
    CylinderGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var length = array[startingIndex++];
        var topRadius = array[startingIndex++];
        var bottomRadius = array[startingIndex++];
        var slices = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.length = length;
            scratchOptions.topRadius = topRadius;
            scratchOptions.bottomRadius = bottomRadius;
            scratchOptions.slices = slices;
            return new CylinderGeometry(scratchOptions);
        }

        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._length = length;
        result._topRadius = topRadius;
        result._bottomRadius = bottomRadius;
        result._slices = slices;

        return result;
    };

    /**
     * Computes the geometric representation of a cylinder, including its vertices, indices, and a bounding sphere.
     *
     * @param {CylinderGeometry} cylinderGeometry A description of the cylinder.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    CylinderGeometry.createGeometry = function(cylinderGeometry) {
        var length = cylinderGeometry._length;
        var topRadius = cylinderGeometry._topRadius;
        var bottomRadius = cylinderGeometry._bottomRadius;
        var vertexFormat = cylinderGeometry._vertexFormat;
        var slices = cylinderGeometry._slices;

        if ((length <= 0) || (topRadius < 0) || (bottomRadius < 0) || ((topRadius === 0) && (bottomRadius === 0))) {
            return;
        }

        var twoSlices = slices + slices;
        var threeSlices = slices + twoSlices;
        var numVertices = twoSlices + twoSlices;

        var positions = CylinderGeometryLibrary.computePositions(length, topRadius, bottomRadius, slices, true);

        var st = (vertexFormat.st) ? new Float32Array(numVertices * 2) : undefined;
        var normals = (vertexFormat.normal) ? new Float32Array(numVertices * 3) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(numVertices * 3) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(numVertices * 3) : undefined;

        var i;
        var computeNormal = (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent);

        if (computeNormal) {
            var computeTangent = (vertexFormat.tangent || vertexFormat.bitangent);

            var normalIndex = 0;
            var tangentIndex = 0;
            var bitangentIndex = 0;

            var normal = normalScratch;
            normal.z = 0;
            var tangent = tangentScratch;
            var bitangent = bitangentScratch;

            for (i = 0; i < slices; i++) {
                var angle = i / slices * CesiumMath.TWO_PI;
                var x = Math.cos(angle);
                var y = Math.sin(angle);
                if (computeNormal) {
                    normal.x = x;
                    normal.y = y;

                    if (computeTangent) {
                        tangent = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent), tangent);
                    }

                    if (vertexFormat.normal) {
                        normals[normalIndex++] = x;
                        normals[normalIndex++] = y;
                        normals[normalIndex++] = 0;
                        normals[normalIndex++] = x;
                        normals[normalIndex++] = y;
                        normals[normalIndex++] = 0;
                    }

                    if (vertexFormat.tangent) {
                        tangents[tangentIndex++] = tangent.x;
                        tangents[tangentIndex++] = tangent.y;
                        tangents[tangentIndex++] = tangent.z;
                        tangents[tangentIndex++] = tangent.x;
                        tangents[tangentIndex++] = tangent.y;
                        tangents[tangentIndex++] = tangent.z;
                    }

                    if (vertexFormat.bitangent) {
                        bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent);
                        bitangents[bitangentIndex++] = bitangent.x;
                        bitangents[bitangentIndex++] = bitangent.y;
                        bitangents[bitangentIndex++] = bitangent.z;
                        bitangents[bitangentIndex++] = bitangent.x;
                        bitangents[bitangentIndex++] = bitangent.y;
                        bitangents[bitangentIndex++] = bitangent.z;
                    }
                }
            }

            for (i = 0; i < slices; i++) {
                if (vertexFormat.normal) {
                    normals[normalIndex++] = 0;
                    normals[normalIndex++] = 0;
                    normals[normalIndex++] = -1;
                }
                if (vertexFormat.tangent) {
                    tangents[tangentIndex++] = 1;
                    tangents[tangentIndex++] = 0;
                    tangents[tangentIndex++] = 0;
                }
                if (vertexFormat.bitangent) {
                    bitangents[bitangentIndex++] = 0;
                    bitangents[bitangentIndex++] = -1;
                    bitangents[bitangentIndex++] = 0;
                }
            }

            for (i = 0; i < slices; i++) {
                if (vertexFormat.normal) {
                    normals[normalIndex++] = 0;
                    normals[normalIndex++] = 0;
                    normals[normalIndex++] = 1;
                }
                if (vertexFormat.tangent) {
                    tangents[tangentIndex++] = 1;
                    tangents[tangentIndex++] = 0;
                    tangents[tangentIndex++] = 0;
                }
                if (vertexFormat.bitangent) {
                    bitangents[bitangentIndex++] = 0;
                    bitangents[bitangentIndex++] = 1;
                    bitangents[bitangentIndex++] = 0;
                }
            }
        }

        var numIndices = 12 * slices - 12;
        var indices = IndexDatatype.createTypedArray(numVertices, numIndices);
        var index = 0;
        var j = 0;
        for (i = 0; i < slices - 1; i++) {
            indices[index++] = j;
            indices[index++] = j + 2;
            indices[index++] = j + 3;

            indices[index++] = j;
            indices[index++] = j + 3;
            indices[index++] = j + 1;

            j += 2;
        }

        indices[index++] = twoSlices - 2;
        indices[index++] = 0;
        indices[index++] = 1;
        indices[index++] = twoSlices - 2;
        indices[index++] = 1;
        indices[index++] = twoSlices - 1;

        for (i = 1; i < slices - 1; i++) {
            indices[index++] = twoSlices + i + 1;
            indices[index++] = twoSlices + i;
            indices[index++] = twoSlices;
        }

        for (i = 1; i < slices - 1; i++) {
            indices[index++] = threeSlices;
            indices[index++] = threeSlices + i;
            indices[index++] = threeSlices + i + 1;
        }

        var textureCoordIndex = 0;
        if (vertexFormat.st) {
            var rad = Math.max(topRadius, bottomRadius);
            for (i = 0; i < numVertices; i++) {
                var position = Cartesian3.fromArray(positions, i * 3, positionScratch);
                st[textureCoordIndex++] = (position.x + rad) / (2.0 * rad);
                st[textureCoordIndex++] = (position.y + rad) / (2.0 * rad);
            }
        }

        var attributes = new GeometryAttributes();
        if (vertexFormat.position) {
            attributes.position = new GeometryAttribute({
                componentDatatype: ComponentDatatype.DOUBLE,
                componentsPerAttribute: 3,
                values: positions
            });
        }

        if (vertexFormat.normal) {
            attributes.normal = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : normals
            });
        }

        if (vertexFormat.tangent) {
            attributes.tangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : tangents
            });
        }

        if (vertexFormat.bitangent) {
            attributes.bitangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : bitangents
            });
        }

        if (vertexFormat.st) {
            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : st
            });
        }

        radiusScratch.x = length * 0.5;
        radiusScratch.y = Math.max(bottomRadius, topRadius);

        var boundingSphere = new BoundingSphere(Cartesian3.ZERO, Cartesian2.magnitude(radiusScratch));

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : boundingSphere
        });
    };

    return CylinderGeometry;
});


/*global define*/
define('Core/CylinderOutlineGeometry',[
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './CylinderGeometryLibrary',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './PrimitiveType'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        CylinderGeometryLibrary,
        defaultValue,
        defined,
        DeveloperError,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        PrimitiveType) {
    'use strict';

    var radiusScratch = new Cartesian2();

    /**
     * A description of the outline of a cylinder.
     *
     * @alias CylinderOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Number} options.length The length of the cylinder.
     * @param {Number} options.topRadius The radius of the top of the cylinder.
     * @param {Number} options.bottomRadius The radius of the bottom of the cylinder.
     * @param {Number} [options.slices=128] The number of edges around the perimeter of the cylinder.
     * @param {Number} [options.numberOfVerticalLines=16] Number of lines to draw between the top and bottom surfaces of the cylinder.
     *
     * @exception {DeveloperError} options.length must be greater than 0.
     * @exception {DeveloperError} options.topRadius must be greater than 0.
     * @exception {DeveloperError} options.bottomRadius must be greater than 0.
     * @exception {DeveloperError} bottomRadius and topRadius cannot both equal 0.
     * @exception {DeveloperError} options.slices must be greater than or equal to 3.
     *
     * @see CylinderOutlineGeometry.createGeometry
     *
     * @example
     * // create cylinder geometry
     * var cylinder = new Cesium.CylinderOutlineGeometry({
     *     length: 200000,
     *     topRadius: 80000,
     *     bottomRadius: 200000,
     * });
     * var geometry = Cesium.CylinderOutlineGeometry.createGeometry(cylinder);
     */
    function CylinderOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var length = options.length;
        var topRadius = options.topRadius;
        var bottomRadius = options.bottomRadius;
        var slices = defaultValue(options.slices, 128);
        var numberOfVerticalLines = Math.max(defaultValue(options.numberOfVerticalLines, 16), 0);

                if (!defined(length)) {
            throw new DeveloperError('options.length must be defined.');
        }
        if (!defined(topRadius)) {
            throw new DeveloperError('options.topRadius must be defined.');
        }
        if (!defined(bottomRadius)) {
            throw new DeveloperError('options.bottomRadius must be defined.');
        }
        if (slices < 3) {
            throw new DeveloperError('options.slices must be greater than or equal to 3.');
        }
        
        this._length = length;
        this._topRadius = topRadius;
        this._bottomRadius = bottomRadius;
        this._slices = slices;
        this._numberOfVerticalLines = numberOfVerticalLines;
        this._workerName = 'createCylinderOutlineGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    CylinderOutlineGeometry.packedLength = 5;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {CylinderOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    CylinderOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value._length;
        array[startingIndex++] = value._topRadius;
        array[startingIndex++] = value._bottomRadius;
        array[startingIndex++] = value._slices;
        array[startingIndex]   = value._numberOfVerticalLines;

        return array;
    };

    var scratchOptions = {
        length : undefined,
        topRadius : undefined,
        bottomRadius : undefined,
        slices : undefined,
        numberOfVerticalLines : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {CylinderOutlineGeometry} [result] The object into which to store the result.
     * @returns {CylinderOutlineGeometry} The modified result parameter or a new CylinderOutlineGeometry instance if one was not provided.
     */
    CylinderOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var length = array[startingIndex++];
        var topRadius = array[startingIndex++];
        var bottomRadius = array[startingIndex++];
        var slices = array[startingIndex++];
        var numberOfVerticalLines = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.length = length;
            scratchOptions.topRadius = topRadius;
            scratchOptions.bottomRadius = bottomRadius;
            scratchOptions.slices = slices;
            scratchOptions.numberOfVerticalLines = numberOfVerticalLines;
            return new CylinderOutlineGeometry(scratchOptions);
        }

        result._length = length;
        result._topRadius = topRadius;
        result._bottomRadius = bottomRadius;
        result._slices = slices;
        result._numberOfVerticalLines = numberOfVerticalLines;

        return result;
    };

    /**
     * Computes the geometric representation of an outline of a cylinder, including its vertices, indices, and a bounding sphere.
     *
     * @param {CylinderOutlineGeometry} cylinderGeometry A description of the cylinder outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    CylinderOutlineGeometry.createGeometry = function(cylinderGeometry) {
        var length = cylinderGeometry._length;
        var topRadius = cylinderGeometry._topRadius;
        var bottomRadius = cylinderGeometry._bottomRadius;
        var slices = cylinderGeometry._slices;
        var numberOfVerticalLines = cylinderGeometry._numberOfVerticalLines;

        if ((length <= 0) || (topRadius < 0) || (bottomRadius < 0) || ((topRadius === 0) && (bottomRadius === 0))) {
            return;
        }

        var numVertices = slices * 2;

        var positions = CylinderGeometryLibrary.computePositions(length, topRadius, bottomRadius, slices, false);
        var numIndices = slices * 2;
        var numSide;
        if (numberOfVerticalLines > 0) {
            var numSideLines = Math.min(numberOfVerticalLines, slices);
            numSide = Math.round(slices / numSideLines);
            numIndices += numSideLines;
        }

        var indices = IndexDatatype.createTypedArray(numVertices, numIndices * 2);
        var index = 0;
        for (var i = 0; i < slices - 1; i++) {
            indices[index++] = i;
            indices[index++] = i + 1;
            indices[index++] = i + slices;
            indices[index++] = i + 1 + slices;
        }

        indices[index++] = slices - 1;
        indices[index++] = 0;
        indices[index++] = slices + slices - 1;
        indices[index++] = slices;

        if (numberOfVerticalLines > 0) {
            for (i = 0; i < slices; i += numSide) {
                indices[index++] = i;
                indices[index++] = i + slices;
            }
        }

        var attributes = new GeometryAttributes();
        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : positions
        });

        radiusScratch.x = length * 0.5;
        radiusScratch.y = Math.max(bottomRadius, topRadius);

        var boundingSphere = new BoundingSphere(Cartesian3.ZERO, Cartesian2.magnitude(radiusScratch));

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : boundingSphere
        });
    };

    return CylinderOutlineGeometry;
});

/*global define*/
define('Core/DefaultProxy',[],function() {
    'use strict';

    /**
     * A simple proxy that appends the desired resource as the sole query parameter
     * to the given proxy URL.
     *
     * @alias DefaultProxy
     * @constructor
     *
     * @param {String} proxy The proxy URL that will be used to requests all resources.
     */
    function DefaultProxy(proxy) {
        this.proxy = proxy;
    }

    /**
     * Get the final URL to use to request a given resource.
     *
     * @param {String} resource The resource to request.
     * @returns {String} proxied resource
     */
    DefaultProxy.prototype.getURL = function(resource) {
        var prefix = this.proxy.indexOf('?') === -1 ? '?' : '';
        return this.proxy + prefix + encodeURIComponent(resource);
    };

    return DefaultProxy;
});

/*global define*/
define('Core/DistanceDisplayCondition',[
        './defaultValue',
        './defined',
        './defineProperties'
    ], function(
        defaultValue,
        defined,
        defineProperties) {
    'use strict';

    /**
     * Determines visibility based on the distance to the camera.
     *
     * @alias DistanceDisplayCondition
     * @constructor
     *
     * @param {Number} [near=0.0] The smallest distance in the interval where the object is visible.
     * @param {Number} [far=Number.MAX_VALUE] The largest distance in the interval where the object is visible.
     *
     * @example
     * // Make a billboard that is only visible when the distance to the camera is between 10 and 20 meters.
     * billboard.distanceDisplayCondition = new DistanceDisplayCondition(10.0 20.0);
     */
    function DistanceDisplayCondition(near, far) {
        near = defaultValue(near, 0.0);
        this._near = near;

        far = defaultValue(far, Number.MAX_VALUE);
        this._far = far;
    }

    defineProperties(DistanceDisplayCondition.prototype, {
        /**
         * The smallest distance in the interval where the object is visible.
         * @memberof DistanceDisplayCondition.prototype
         * @type {Number}
         * @default 0.0
         */
        near : {
            get : function() {
                return this._near;
            },
            set : function(value) {
                this._near = value;
            }
        },
        /**
         * The largest distance in the interval where the object is visible.
         * @memberof DistanceDisplayCondition.prototype
         * @type {Number}
         * @default Number.MAX_VALUE
         */
        far : {
            get : function() {
                return this._far;
            },
            set : function(value) {
                this._far = value;
            }
        }
    });

    /**
     * Determines if two distance display conditions are equal.
     *
     * @param {DistanceDisplayCondition} left A distance display condition.
     * @param {DistanceDisplayCondition} right Another distance display condition.
     * @return {Boolean} Whether the two distance display conditions are equal.
     */
    DistanceDisplayCondition.equals = function(left, right) {
        return left === right ||
               (defined(left) &&
                defined(right) &&
                left.near === right.near &&
                left.far === right.far);
    };

    /**
     * Duplicates a distance display condition instance.
     *
     * @param {DistanceDisplayCondition} [value] The distance display condition to duplicate.
     * @param {DistanceDisplayCondition} [result] The result onto which to store the result.
     * @return {DistanceDisplayCondition} The duplicated instance.
     */
    DistanceDisplayCondition.clone = function(value, result) {
        if (!defined(value)) {
            return undefined;
        }

        if (!defined(result)) {
            result = new DistanceDisplayCondition();
        }

        result.near = value.near;
        result.far = value.far;
        return result;
    };

    /**
     * Duplicates this instance.
     *
     * @param {DistanceDisplayCondition} [result] The result onto which to store the result.
     * @return {DistanceDisplayCondition} The duplicated instance.
     */
    DistanceDisplayCondition.prototype.clone = function(result) {
        return DistanceDisplayCondition.clone(this, result);
    };

    /**
     * Determines if this distance display condition is equal to another.
     *
     * @param {DistanceDisplayCondition} other Another distance display condition.
     * @return {Boolean} Whether this distance display condition is equal to the other.
     */
    DistanceDisplayCondition.prototype.equals = function(other) {
        return DistanceDisplayCondition.equals(this, other);
    };

    return DistanceDisplayCondition;
});

/*global define*/
define('Core/DistanceDisplayConditionGeometryInstanceAttribute',[
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError'
    ], function(
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Value and type information for per-instance geometry attribute that determines if the geometry instance has a distance display condition.
     *
     * @alias DistanceDisplayConditionGeometryInstanceAttribute
     * @constructor
     *
     * @param {Number} [near=0.0] The near distance.
     * @param {Number} [far=Number.MAX_VALUE] The far distance.
     *
     * @exception {DeveloperError} far must be greater than near.
     *
     * @example
     * var instance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.BoxGeometry({
     *     vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL,
     *     minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0),
     *     maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0)
     *   }),
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
     *   id : 'box',
     *   attributes : {
     *     show : new Cesium.DistanceDisplayConditionGeometryInstanceAttribute(100.0, 10000.0)
     *   }
     * });
     *
     * @see GeometryInstance
     * @see GeometryInstanceAttribute
     */
    function DistanceDisplayConditionGeometryInstanceAttribute(near, far) {
        near = defaultValue(near, 0.0);
        far = defaultValue(far, Number.MAX_VALUE);

                if (far <= near) {
            throw new DeveloperError('far distance must be greater than near distance.');
        }
        
        /**
         * The values for the attributes stored in a typed array.
         *
         * @type Float32Array
         *
         * @default [0.0, 0.0, Number.MAX_VALUE]
         */
        this.value = new Float32Array([near, far]);
    }

    defineProperties(DistanceDisplayConditionGeometryInstanceAttribute.prototype, {
        /**
         * The datatype of each component in the attribute, e.g., individual elements in
         * {@link DistanceDisplayConditionGeometryInstanceAttribute#value}.
         *
         * @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype
         *
         * @type {ComponentDatatype}
         * @readonly
         *
         * @default {@link ComponentDatatype.FLOAT}
         */
        componentDatatype : {
            get : function() {
                return ComponentDatatype.FLOAT;
            }
        },

        /**
         * The number of components in the attributes, i.e., {@link DistanceDisplayConditionGeometryInstanceAttribute#value}.
         *
         * @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @default 3
         */
        componentsPerAttribute : {
            get : function() {
                return 2;
            }
        },

        /**
         * When <code>true</code> and <code>componentDatatype</code> is an integer format,
         * indicate that the components should be mapped to the range [0, 1] (unsigned)
         * or [-1, 1] (signed) when they are accessed as floating-point for rendering.
         *
         * @memberof DistanceDisplayConditionGeometryInstanceAttribute.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        normalize : {
            get : function() {
                return false;
            }
        }
    });

    /**
     * Creates a new {@link DistanceDisplayConditionGeometryInstanceAttribute} instance given the provided an enabled flag and {@link DistanceDisplayCondition}.
     *
     * @param {DistanceDisplayCondition} distanceDisplayCondition The distance display condition.
     * @returns {DistanceDisplayConditionGeometryInstanceAttribute} The new {@link DistanceDisplayConditionGeometryInstanceAttribute} instance.
     *
     * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
     *
     * @example
     * var instance = new Cesium.GeometryInstance({
     *   geometry : geometry,
     *   attributes : {
     *     color : Cesium.DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition),
     *   }
     * });
     */
    DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition = function(distanceDisplayCondition) {
                if (!defined(distanceDisplayCondition)) {
            throw new DeveloperError('distanceDisplayCondition is required.');
        }
        if (distanceDisplayCondition.far <= distanceDisplayCondition.near) {
            throw new DeveloperError('distanceDisplayCondition.far distance must be greater than distanceDisplayCondition.near distance.');
        }
        
        return new DistanceDisplayConditionGeometryInstanceAttribute(distanceDisplayCondition.near, distanceDisplayCondition.far);
    };

    /**
     * Converts a distance display condition to a typed array that can be used to assign a distance display condition attribute.
     *
     * @param {DistanceDisplayCondition} distanceDisplayCondition The distance display condition value.
     * @param {Float32Array} [result] The array to store the result in, if undefined a new instance will be created.
     * @returns {Float32Array} The modified result parameter or a new instance if result was undefined.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
     */
    DistanceDisplayConditionGeometryInstanceAttribute.toValue = function(distanceDisplayCondition, result) {
                if (!defined(distanceDisplayCondition)) {
            throw new DeveloperError('distanceDisplayCondition is required.');
        }
        
        if (!defined(result)) {
            return new Float32Array([distanceDisplayCondition.near, distanceDisplayCondition.far]);
        }
        result[0] = distanceDisplayCondition.near;
        result[1] = distanceDisplayCondition.far;
        return result;
    };

    return DistanceDisplayConditionGeometryInstanceAttribute;
});

/**
@license
tween.js - https://github.com/sole/tween.js

Copyright (c) 2010-2012 Tween.js authors.

Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * @author sole / http://soledadpenades.com
 * @author mrdoob / http://mrdoob.com
 * @author Robert Eisele / http://www.xarg.org
 * @author Philippe / http://philippe.elsass.me
 * @author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html
 * @author Paul Lewis / http://www.aerotwist.com/
 * @author lechecacharro
 * @author Josh Faul / http://jocafa.com/
 * @author egraether / http://egraether.com/
 * @author endel / http://endel.me
 * @author Ben Delarre / http://delarre.net
 */

/*global define*/
define('ThirdParty/Tween',[],function() {

    // Date.now shim for (ahem) Internet Explo(d|r)er
    if ( Date.now === undefined ) {

        Date.now = function () {

            return new Date().valueOf();

        };

    }

    var TWEEN = TWEEN || ( function () {

        var _tweens = [];

        return {

            REVISION: '13',

            getAll: function () {

                return _tweens;

            },

            removeAll: function () {

                _tweens = [];

            },

            add: function ( tween ) {

                _tweens.push( tween );

            },

            remove: function ( tween ) {

                var i = _tweens.indexOf( tween );

                if ( i !== -1 ) {

                    _tweens.splice( i, 1 );

                }

            },

            update: function ( time ) {

                if ( _tweens.length === 0 ) return false;

                var i = 0;

                time = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );

                while ( i < _tweens.length ) {

                    if ( _tweens[ i ].update( time ) ) {

                        i++;

                    } else {

                        _tweens.splice( i, 1 );

                    }

                }

                return true;

            }
        };

    } )();

    TWEEN.Tween = function ( object ) {

        var _object = object;
        var _valuesStart = {};
        var _valuesEnd = {};
        var _valuesStartRepeat = {};
        var _duration = 1000;
        var _repeat = 0;
        var _yoyo = false;
        var _isPlaying = false;
        var _reversed = false;
        var _delayTime = 0;
        var _startTime = null;
        var _easingFunction = TWEEN.Easing.Linear.None;
        var _interpolationFunction = TWEEN.Interpolation.Linear;
        var _chainedTweens = [];
        var _onStartCallback = null;
        var _onStartCallbackFired = false;
        var _onUpdateCallback = null;
        var _onCompleteCallback = null;
        var _onStopCallback = null;

        // Set all starting values present on the target object
        for ( var field in object ) {

            _valuesStart[ field ] = parseFloat(object[field], 10);

        }

        this.to = function ( properties, duration ) {

            if ( duration !== undefined ) {

                _duration = duration;

            }

            _valuesEnd = properties;

            return this;

        };

        this.start = function ( time ) {

            TWEEN.add( this );

            _isPlaying = true;

            _onStartCallbackFired = false;

            _startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
            _startTime += _delayTime;

            for ( var property in _valuesEnd ) {

                // check if an Array was provided as property value
                if ( _valuesEnd[ property ] instanceof Array ) {

                    if ( _valuesEnd[ property ].length === 0 ) {

                        continue;

                    }

                    // create a local copy of the Array with the start value at the front
                    _valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] );

                }

                _valuesStart[ property ] = _object[ property ];

                if( ( _valuesStart[ property ] instanceof Array ) === false ) {
                    _valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings
                }

                _valuesStartRepeat[ property ] = _valuesStart[ property ] || 0;

            }

            return this;

        };

        this.stop = function () {

            if ( !_isPlaying ) {
                return this;
            }

            TWEEN.remove( this );
            _isPlaying = false;

            if ( _onStopCallback !== null ) {

                _onStopCallback.call( _object );

            }

            this.stopChainedTweens();
            return this;

        };

        this.stopChainedTweens = function () {

            for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) {

                _chainedTweens[ i ].stop();

            }

        };

        this.delay = function ( amount ) {

            _delayTime = amount;
            return this;

        };

        this.repeat = function ( times ) {

            _repeat = times;
            return this;

        };

        this.yoyo = function( yoyo ) {

            _yoyo = yoyo;
            return this;

        };


        this.easing = function ( easing ) {

            _easingFunction = easing;
            return this;

        };

        this.interpolation = function ( interpolation ) {

            _interpolationFunction = interpolation;
            return this;

        };

        this.chain = function () {

            _chainedTweens = arguments;
            return this;

        };

        this.onStart = function ( callback ) {

            _onStartCallback = callback;
            return this;

        };

        this.onUpdate = function ( callback ) {

            _onUpdateCallback = callback;
            return this;

        };

        this.onComplete = function ( callback ) {

            _onCompleteCallback = callback;
            return this;

        };

        this.onStop = function ( callback ) {

            _onStopCallback = callback;
            return this;

        };

        this.update = function ( time ) {

            var property;

            if ( time < _startTime ) {

                return true;

            }

            if ( _onStartCallbackFired === false ) {

                if ( _onStartCallback !== null ) {

                    _onStartCallback.call( _object );

                }

                _onStartCallbackFired = true;

            }

            var elapsed = ( time - _startTime ) / _duration;
            elapsed = elapsed > 1 ? 1 : elapsed;

            var value = _easingFunction( elapsed );

            for ( property in _valuesEnd ) {

                var start = _valuesStart[ property ] || 0;
                var end = _valuesEnd[ property ];

                if ( end instanceof Array ) {

                    _object[ property ] = _interpolationFunction( end, value );

                } else {

                    // Parses relative end values with start as base (e.g.: +10, -3)
                    if ( typeof(end) === "string" ) {
                        end = start + parseFloat(end, 10);
                    }

                    // protect against non numeric properties.
                    if ( typeof(end) === "number" ) {
                        _object[ property ] = start + ( end - start ) * value;
                    }

                }

            }

            if ( _onUpdateCallback !== null ) {

                _onUpdateCallback.call( _object, value );

            }

            if ( elapsed == 1 ) {

                if ( _repeat > 0 ) {

                    if( isFinite( _repeat ) ) {
                        _repeat--;
                    }

                    // reassign starting values, restart by making startTime = now
                    for( property in _valuesStartRepeat ) {

                        if ( typeof( _valuesEnd[ property ] ) === "string" ) {
                            _valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10);
                        }

                        if (_yoyo) {
                            var tmp = _valuesStartRepeat[ property ];
                            _valuesStartRepeat[ property ] = _valuesEnd[ property ];
                            _valuesEnd[ property ] = tmp;
                        }

                        _valuesStart[ property ] = _valuesStartRepeat[ property ];

                    }

                    if (_yoyo) {
                        _reversed = !_reversed;
                    }

                    _startTime = time + _delayTime;

                    return true;

                } else {

                    if ( _onCompleteCallback !== null ) {

                        _onCompleteCallback.call( _object );

                    }

                    for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) {

                        _chainedTweens[ i ].start( time );

                    }

                    return false;

                }

            }

            return true;

        };

    };


    TWEEN.Easing = {

        Linear: {

            None: function ( k ) {

                return k;

            }

        },

        Quadratic: {

            In: function ( k ) {

                return k * k;

            },

            Out: function ( k ) {

                return k * ( 2 - k );

            },

            InOut: function ( k ) {

                if ( ( k *= 2 ) < 1 ) return 0.5 * k * k;
                return - 0.5 * ( --k * ( k - 2 ) - 1 );

            }

        },

        Cubic: {

            In: function ( k ) {

                return k * k * k;

            },

            Out: function ( k ) {

                return --k * k * k + 1;

            },

            InOut: function ( k ) {

                if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k;
                return 0.5 * ( ( k -= 2 ) * k * k + 2 );

            }

        },

        Quartic: {

            In: function ( k ) {

                return k * k * k * k;

            },

            Out: function ( k ) {

                return 1 - ( --k * k * k * k );

            },

            InOut: function ( k ) {

                if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k;
                return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 );

            }

        },

        Quintic: {

            In: function ( k ) {

                return k * k * k * k * k;

            },

            Out: function ( k ) {

                return --k * k * k * k * k + 1;

            },

            InOut: function ( k ) {

                if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k;
                return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 );

            }

        },

        Sinusoidal: {

            In: function ( k ) {

                return 1 - Math.cos( k * Math.PI / 2 );

            },

            Out: function ( k ) {

                return Math.sin( k * Math.PI / 2 );

            },

            InOut: function ( k ) {

                return 0.5 * ( 1 - Math.cos( Math.PI * k ) );

            }

        },

        Exponential: {

            In: function ( k ) {

                return k === 0 ? 0 : Math.pow( 1024, k - 1 );

            },

            Out: function ( k ) {

                return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k );

            },

            InOut: function ( k ) {

                if ( k === 0 ) return 0;
                if ( k === 1 ) return 1;
                if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 );
                return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 );

            }

        },

        Circular: {

            In: function ( k ) {

                return 1 - Math.sqrt( 1 - k * k );

            },

            Out: function ( k ) {

                return Math.sqrt( 1 - ( --k * k ) );

            },

            InOut: function ( k ) {

                if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1);
                return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1);

            }

        },

        Elastic: {

            In: function ( k ) {

                var s, a = 0.1, p = 0.4;
                if ( k === 0 ) return 0;
                if ( k === 1 ) return 1;
                if ( !a || a < 1 ) { a = 1; s = p / 4; }
                else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
                return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );

            },

            Out: function ( k ) {

                var s, a = 0.1, p = 0.4;
                if ( k === 0 ) return 0;
                if ( k === 1 ) return 1;
                if ( !a || a < 1 ) { a = 1; s = p / 4; }
                else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
                return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );

            },

            InOut: function ( k ) {

                var s, a = 0.1, p = 0.4;
                if ( k === 0 ) return 0;
                if ( k === 1 ) return 1;
                if ( !a || a < 1 ) { a = 1; s = p / 4; }
                else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
                if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
                return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1;

            }

        },

        Back: {

            In: function ( k ) {

                var s = 1.70158;
                return k * k * ( ( s + 1 ) * k - s );

            },

            Out: function ( k ) {

                var s = 1.70158;
                return --k * k * ( ( s + 1 ) * k + s ) + 1;

            },

            InOut: function ( k ) {

                var s = 1.70158 * 1.525;
                if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) );
                return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 );

            }

        },

        Bounce: {

            In: function ( k ) {

                return 1 - TWEEN.Easing.Bounce.Out( 1 - k );

            },

            Out: function ( k ) {

                if ( k < ( 1 / 2.75 ) ) {

                    return 7.5625 * k * k;

                } else if ( k < ( 2 / 2.75 ) ) {

                    return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;

                } else if ( k < ( 2.5 / 2.75 ) ) {

                    return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;

                } else {

                    return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;

                }

            },

            InOut: function ( k ) {

                if ( k < 0.5 ) return TWEEN.Easing.Bounce.In( k * 2 ) * 0.5;
                return TWEEN.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5;

            }

        }

    };

    TWEEN.Interpolation = {

        Linear: function ( v, k ) {

            var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.Linear;

            if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f );
            if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f );

            return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i );

        },

        Bezier: function ( v, k ) {

            var b = 0, n = v.length - 1, pw = Math.pow, bn = TWEEN.Interpolation.Utils.Bernstein, i;

            for ( i = 0; i <= n; i++ ) {
                b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i );
            }

            return b;

        },

        CatmullRom: function ( v, k ) {

            var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.CatmullRom;

            if ( v[ 0 ] === v[ m ] ) {

                if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) );

                return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i );

            } else {

                if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] );
                if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] );

                return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i );

            }

        },

        Utils: {

            Linear: function ( p0, p1, t ) {

                return ( p1 - p0 ) * t + p0;

            },

            Bernstein: function ( n , i ) {

                var fc = TWEEN.Interpolation.Utils.Factorial;
                return fc( n ) / fc( i ) / fc( n - i );

            },

            Factorial: ( function () {

                var a = [ 1 ];

                return function ( n ) {

                    var s = 1, i;
                    if ( a[ n ] ) return a[ n ];
                    for ( i = n; i > 1; i-- ) s *= i;
                    return a[ n ] = s;

                };

            } )(),

            CatmullRom: function ( p0, p1, p2, p3, t ) {

                var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2;
                return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;

            }

        }

    };

return TWEEN;
});

/*global define*/
define('Core/EasingFunction',[
        '../ThirdParty/Tween',
        './freezeObject'
    ], function(
        Tween,
        freezeObject) {
    'use strict';

    /**
     * Easing functions for use with {@link TweenCollection}.  These function are from
     * {@link https://github.com/sole/tween.js/|Tween.js} and Robert Penner.  See the
     * {@link http://sole.github.io/tween.js/examples/03_graphs.html|Tween.js graphs for each function}.
     *
     * @exports EasingFunction
     */
    var EasingFunction = {
        /**
         * Linear easing.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        LINEAR_NONE : Tween.Easing.Linear.None,

        /**
         * Quadratic in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUADRACTIC_IN : Tween.Easing.Quadratic.In,
        /**
         * Quadratic out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUADRACTIC_OUT : Tween.Easing.Quadratic.Out,
        /**
         * Quadratic in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUADRACTIC_IN_OUT : Tween.Easing.Quadratic.InOut,

        /**
         * Cubic in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        CUBIC_IN : Tween.Easing.Cubic.In,
        /**
         * Cubic out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        CUBIC_OUT : Tween.Easing.Cubic.Out,
        /**
         * Cubic in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        CUBIC_IN_OUT : Tween.Easing.Cubic.InOut,

        /**
         * Quartic in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUARTIC_IN : Tween.Easing.Quartic.In,
        /**
         * Quartic out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUARTIC_OUT : Tween.Easing.Quartic.Out,
        /**
         * Quartic in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUARTIC_IN_OUT : Tween.Easing.Quartic.InOut,

        /**
         * Quintic in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUINTIC_IN : Tween.Easing.Quintic.In,
        /**
         * Quintic out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUINTIC_OUT : Tween.Easing.Quintic.Out,
        /**
         * Quintic in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        QUINTIC_IN_OUT : Tween.Easing.Quintic.InOut,

        /**
         * Sinusoidal in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        SINUSOIDAL_IN : Tween.Easing.Sinusoidal.In,
        /**
         * Sinusoidal out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        SINUSOIDAL_OUT : Tween.Easing.Sinusoidal.Out,
        /**
         * Sinusoidal in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        SINUSOIDAL_IN_OUT : Tween.Easing.Sinusoidal.InOut,

        /**
         * Exponential in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        EXPONENTIAL_IN : Tween.Easing.Exponential.In,
        /**
         * Exponential out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        EXPONENTIAL_OUT : Tween.Easing.Exponential.Out,
        /**
         * Exponential in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        EXPONENTIAL_IN_OUT : Tween.Easing.Exponential.InOut,

        /**
         * Circular in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        CIRCULAR_IN : Tween.Easing.Circular.In,
        /**
         * Circular out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        CIRCULAR_OUT : Tween.Easing.Circular.Out,
        /**
         * Circular in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        CIRCULAR_IN_OUT : Tween.Easing.Circular.InOut,

        /**
         * Elastic in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        ELASTIC_IN : Tween.Easing.Elastic.In,
        /**
         * Elastic out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        ELASTIC_OUT : Tween.Easing.Elastic.Out,
        /**
         * Elastic in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        ELASTIC_IN_OUT : Tween.Easing.Elastic.InOut,

        /**
         * Back in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        BACK_IN : Tween.Easing.Back.In,
        /**
         * Back out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        BACK_OUT : Tween.Easing.Back.Out,
        /**
         * Back in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        BACK_IN_OUT : Tween.Easing.Back.InOut,

        /**
         * Bounce in.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        BOUNCE_IN : Tween.Easing.Bounce.In,
        /**
         * Bounce out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        BOUNCE_OUT : Tween.Easing.Bounce.Out,
        /**
         * Bounce in then out.
         *
         * @type {EasingFunction~Callback}
         * @constant
         */
        BOUNCE_IN_OUT : Tween.Easing.Bounce.InOut
    };

    /**
     * Function interface for implementing a custom easing function.
     * @callback EasingFunction~Callback
     * @param {Number} time The time in the range <code>[0, 1]</code>.
     * @returns {Number} The value of the function at the given time.
     *
     * @example
     * function quadraticIn(time) {
     *     return time * time;
     * }
     *
     * @example
     * function quadraticOut(time) {
     *     return time * (2.0 - time);
     * }
     */

    return freezeObject(EasingFunction);
});

/*global define*/
define('Core/EllipsoidGeometry',[
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PrimitiveType',
        './VertexFormat'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PrimitiveType,
        VertexFormat) {
    'use strict';

    var scratchPosition = new Cartesian3();
    var scratchNormal = new Cartesian3();
    var scratchTangent = new Cartesian3();
    var scratchBitangent = new Cartesian3();
    var scratchNormalST = new Cartesian3();
    var defaultRadii = new Cartesian3(1.0, 1.0, 1.0);

    var cos = Math.cos;
    var sin = Math.sin;

    /**
     * A description of an ellipsoid centered at the origin.
     *
     * @alias EllipsoidGeometry
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions.
     * @param {Number} [options.stackPartitions=64] The number of times to partition the ellipsoid into stacks.
     * @param {Number} [options.slicePartitions=64] The number of times to partition the ellipsoid into radial slices.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     *
     * @exception {DeveloperError} options.slicePartitions cannot be less than three.
     * @exception {DeveloperError} options.stackPartitions cannot be less than three.
     *
     * @see EllipsoidGeometry#createGeometry
     *
     * @example
     * var ellipsoid = new Cesium.EllipsoidGeometry({
     *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
     *   radii : new Cesium.Cartesian3(1000000.0, 500000.0, 500000.0)
     * });
     * var geometry = Cesium.EllipsoidGeometry.createGeometry(ellipsoid);
     */
    function EllipsoidGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var radii = defaultValue(options.radii, defaultRadii);
        var stackPartitions = defaultValue(options.stackPartitions, 64);
        var slicePartitions = defaultValue(options.slicePartitions, 64);
        var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);

                if (slicePartitions < 3) {
            throw new DeveloperError ('options.slicePartitions cannot be less than three.');
        }
        if (stackPartitions < 3) {
            throw new DeveloperError('options.stackPartitions cannot be less than three.');
        }
        
        this._radii = Cartesian3.clone(radii);
        this._stackPartitions = stackPartitions;
        this._slicePartitions = slicePartitions;
        this._vertexFormat = VertexFormat.clone(vertexFormat);
        this._workerName = 'createEllipsoidGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    EllipsoidGeometry.packedLength = Cartesian3.packedLength + VertexFormat.packedLength + 2;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {EllipsoidGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    EllipsoidGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._radii, array, startingIndex);
        startingIndex += Cartesian3.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        array[startingIndex++] = value._stackPartitions;
        array[startingIndex]   = value._slicePartitions;

        return array;
    };

    var scratchRadii = new Cartesian3();
    var scratchVertexFormat = new VertexFormat();
    var scratchOptions = {
        radii : scratchRadii,
        vertexFormat : scratchVertexFormat,
        stackPartitions : undefined,
        slicePartitions : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {EllipsoidGeometry} [result] The object into which to store the result.
     * @returns {EllipsoidGeometry} The modified result parameter or a new EllipsoidGeometry instance if one was not provided.
     */
    EllipsoidGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var radii = Cartesian3.unpack(array, startingIndex, scratchRadii);
        startingIndex += Cartesian3.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var stackPartitions = array[startingIndex++];
        var slicePartitions = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.stackPartitions = stackPartitions;
            scratchOptions.slicePartitions = slicePartitions;
            return new EllipsoidGeometry(scratchOptions);
        }

        result._radii = Cartesian3.clone(radii, result._radii);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._stackPartitions = stackPartitions;
        result._slicePartitions = slicePartitions;

        return result;
    };

    /**
     * Computes the geometric representation of an ellipsoid, including its vertices, indices, and a bounding sphere.
     *
     * @param {EllipsoidGeometry} ellipsoidGeometry A description of the ellipsoid.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    EllipsoidGeometry.createGeometry = function(ellipsoidGeometry) {
        var radii = ellipsoidGeometry._radii;

        if ((radii.x <= 0) || (radii.y <= 0) || (radii.z <= 0)) {
            return;
        }

        var ellipsoid = Ellipsoid.fromCartesian3(radii);
        var vertexFormat = ellipsoidGeometry._vertexFormat;

        // The extra slice and stack are for duplicating points at the x axis and poles.
        // We need the texture coordinates to interpolate from (2 * pi - delta) to 2 * pi instead of
        // (2 * pi - delta) to 0.
        var slicePartitions = ellipsoidGeometry._slicePartitions + 1;
        var stackPartitions = ellipsoidGeometry._stackPartitions + 1;

        var vertexCount = stackPartitions * slicePartitions;
        var positions = new Float64Array(vertexCount * 3);

        var numIndices = 6 * (slicePartitions - 1) * (stackPartitions - 2);
        var indices = IndexDatatype.createTypedArray(vertexCount, numIndices);

        var normals = (vertexFormat.normal) ? new Float32Array(vertexCount * 3) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(vertexCount * 3) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(vertexCount * 3) : undefined;
        var st = (vertexFormat.st) ? new Float32Array(vertexCount * 2) : undefined;

        var cosTheta = new Array(slicePartitions);
        var sinTheta = new Array(slicePartitions);

        var i;
        var j;
        var index = 0;

        for (i = 0; i < slicePartitions; i++) {
            var theta = CesiumMath.TWO_PI * i / (slicePartitions - 1);
            cosTheta[i] = cos(theta);
            sinTheta[i] = sin(theta);

            // duplicate first point for correct
            // texture coordinates at the north pole.
            positions[index++] = 0.0;
            positions[index++] = 0.0;
            positions[index++] = radii.z;
        }

        for (i = 1; i < stackPartitions - 1; i++) {
            var phi = Math.PI * i / (stackPartitions - 1);
            var sinPhi = sin(phi);

            var xSinPhi = radii.x * sinPhi;
            var ySinPhi = radii.y * sinPhi;
            var zCosPhi = radii.z * cos(phi);

            for (j = 0; j < slicePartitions; j++) {
                positions[index++] = cosTheta[j] * xSinPhi;
                positions[index++] = sinTheta[j] * ySinPhi;
                positions[index++] = zCosPhi;
            }
        }

        for (i = 0; i < slicePartitions; i++) {
            // duplicate first point for correct
            // texture coordinates at the south pole.
            positions[index++] = 0.0;
            positions[index++] = 0.0;
            positions[index++] = -radii.z;
        }

        var attributes = new GeometryAttributes();

        if (vertexFormat.position) {
            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : positions
            });
        }

        var stIndex = 0;
        var normalIndex = 0;
        var tangentIndex = 0;
        var bitangentIndex = 0;

        if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
            for( i = 0; i < vertexCount; i++) {
                var position = Cartesian3.fromArray(positions, i * 3, scratchPosition);
                var normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal);

                if (vertexFormat.st) {
                    var normalST = Cartesian2.negate(normal, scratchNormalST);

                    // if the point is at or close to the pole, find a point along the same longitude
                    // close to the xy-plane for the s coordinate.
                    if (Cartesian2.magnitude(normalST) < CesiumMath.EPSILON6) {
                        index = (i + slicePartitions * Math.floor(stackPartitions * 0.5)) * 3;
                        if (index > positions.length) {
                            index = (i - slicePartitions * Math.floor(stackPartitions * 0.5)) * 3;
                        }
                        Cartesian3.fromArray(positions, index, normalST);
                        ellipsoid.geodeticSurfaceNormal(normalST, normalST);
                        Cartesian2.negate(normalST, normalST);
                    }

                    st[stIndex++] = (Math.atan2(normalST.y, normalST.x) / CesiumMath.TWO_PI) + 0.5;
                    st[stIndex++] = (Math.asin(normal.z) / Math.PI) + 0.5;
                }

                if (vertexFormat.normal) {
                    normals[normalIndex++] = normal.x;
                    normals[normalIndex++] = normal.y;
                    normals[normalIndex++] = normal.z;
                }

                if (vertexFormat.tangent || vertexFormat.bitangent) {
                    var tangent = scratchTangent;
                    if (i < slicePartitions || i > vertexCount - slicePartitions - 1) {
                        Cartesian3.cross(Cartesian3.UNIT_X, normal, tangent);
                        Cartesian3.normalize(tangent, tangent);
                    } else {
                        Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent);
                        Cartesian3.normalize(tangent, tangent);
                    }

                    if (vertexFormat.tangent) {
                        tangents[tangentIndex++] = tangent.x;
                        tangents[tangentIndex++] = tangent.y;
                        tangents[tangentIndex++] = tangent.z;
                    }

                    if (vertexFormat.bitangent) {
                        var bitangent = Cartesian3.cross(normal, tangent, scratchBitangent);
                        Cartesian3.normalize(bitangent, bitangent);

                        bitangents[bitangentIndex++] = bitangent.x;
                        bitangents[bitangentIndex++] = bitangent.y;
                        bitangents[bitangentIndex++] = bitangent.z;
                    }
                }
            }

            if (vertexFormat.st) {
                attributes.st = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 2,
                    values : st
                });
            }

            if (vertexFormat.normal) {
                attributes.normal = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : normals
                });
            }

            if (vertexFormat.tangent) {
                attributes.tangent = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : tangents
                });
            }

            if (vertexFormat.bitangent) {
                attributes.bitangent = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : bitangents
                });
            }
        }

        index = 0;
        for (j = 0; j < slicePartitions - 1; j++) {
            indices[index++] = slicePartitions + j;
            indices[index++] = slicePartitions + j + 1;
            indices[index++] = j + 1;
        }

        var topOffset;
        var bottomOffset;
        for (i = 1; i < stackPartitions - 2; i++) {
            topOffset = i * slicePartitions;
            bottomOffset = (i + 1) * slicePartitions;

            for (j = 0; j < slicePartitions - 1; j++) {
                indices[index++] = bottomOffset + j;
                indices[index++] = bottomOffset + j + 1;
                indices[index++] = topOffset + j + 1;

                indices[index++] = bottomOffset + j;
                indices[index++] = topOffset + j + 1;
                indices[index++] = topOffset + j;
            }
        }

        i = stackPartitions - 2;
        topOffset = i * slicePartitions;
        bottomOffset = (i + 1) * slicePartitions;

        for (j = 0; j < slicePartitions - 1; j++) {
            indices[index++] = bottomOffset + j;
            indices[index++] = topOffset + j + 1;
            indices[index++] = topOffset + j;
        }

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : BoundingSphere.fromEllipsoid(ellipsoid)
        });
    };

    return EllipsoidGeometry;
});

/*global define*/
define('Core/EllipsoidOutlineGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PrimitiveType'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PrimitiveType) {
    'use strict';

    var defaultRadii = new Cartesian3(1.0, 1.0, 1.0);
    var cos = Math.cos;
    var sin = Math.sin;

    /**
     * A description of the outline of an ellipsoid centered at the origin.
     *
     * @alias EllipsoidOutlineGeometry
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Cartesian3} [options.radii=Cartesian3(1.0, 1.0, 1.0)] The radii of the ellipsoid in the x, y, and z directions.
     * @param {Number} [options.stackPartitions=10] The count of stacks for the ellipsoid (1 greater than the number of parallel lines).
     * @param {Number} [options.slicePartitions=8] The count of slices for the ellipsoid (Equal to the number of radial lines).
     * @param {Number} [options.subdivisions=128] The number of points per line, determining the granularity of the curvature.
     *
     * @exception {DeveloperError} options.stackPartitions must be greater than or equal to one.
     * @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero.
     * @exception {DeveloperError} options.subdivisions must be greater than or equal to zero.
     *
     * @example
     * var ellipsoid = new Cesium.EllipsoidOutlineGeometry({
     *   radii : new Cesium.Cartesian3(1000000.0, 500000.0, 500000.0),
     *   stackPartitions: 6,
     *   slicePartitions: 5
     * });
     * var geometry = Cesium.EllipsoidOutlineGeometry.createGeometry(ellipsoid);
     */
    function EllipsoidOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var radii = defaultValue(options.radii, defaultRadii);
        var stackPartitions = defaultValue(options.stackPartitions, 10);
        var slicePartitions = defaultValue(options.slicePartitions, 8);
        var subdivisions = defaultValue(options.subdivisions, 128);

                if (stackPartitions < 1) {
            throw new DeveloperError('options.stackPartitions cannot be less than 1');
        }
        if (slicePartitions < 0) {
            throw new DeveloperError('options.slicePartitions cannot be less than 0');
        }
        if (subdivisions < 0) {
            throw new DeveloperError('options.subdivisions must be greater than or equal to zero.');
        }
        
        this._radii = Cartesian3.clone(radii);
        this._stackPartitions = stackPartitions;
        this._slicePartitions = slicePartitions;
        this._subdivisions = subdivisions;
        this._workerName = 'createEllipsoidOutlineGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    EllipsoidOutlineGeometry.packedLength = Cartesian3.packedLength + 3;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {EllipsoidOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    EllipsoidOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Cartesian3.pack(value._radii, array, startingIndex);
        startingIndex += Cartesian3.packedLength;

        array[startingIndex++] = value._stackPartitions;
        array[startingIndex++] = value._slicePartitions;
        array[startingIndex]   = value._subdivisions;

        return array;
    };

    var scratchRadii = new Cartesian3();
    var scratchOptions = {
        radii : scratchRadii,
        stackPartitions : undefined,
        slicePartitions : undefined,
        subdivisions : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {EllipsoidOutlineGeometry} [result] The object into which to store the result.
     * @returns {EllipsoidOutlineGeometry} The modified result parameter or a new EllipsoidOutlineGeometry instance if one was not provided.
     */
    EllipsoidOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var radii = Cartesian3.unpack(array, startingIndex, scratchRadii);
        startingIndex += Cartesian3.packedLength;

        var stackPartitions = array[startingIndex++];
        var slicePartitions = array[startingIndex++];
        var subdivisions = array[startingIndex++];

        if (!defined(result)) {
            scratchOptions.stackPartitions = stackPartitions;
            scratchOptions.slicePartitions = slicePartitions;
            scratchOptions.subdivisions = subdivisions;
            return new EllipsoidOutlineGeometry(scratchOptions);
        }

        result._radii = Cartesian3.clone(radii, result._radii);
        result._stackPartitions = stackPartitions;
        result._slicePartitions = slicePartitions;
        result._subdivisions = subdivisions;

        return result;
    };

    /**
     * Computes the geometric representation of an outline of an ellipsoid, including its vertices, indices, and a bounding sphere.
     *
     * @param {EllipsoidOutlineGeometry} ellipsoidGeometry A description of the ellipsoid outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    EllipsoidOutlineGeometry.createGeometry = function(ellipsoidGeometry) {
        var radii = ellipsoidGeometry._radii;

        if ((radii.x <= 0) || (radii.y <= 0) || (radii.z <= 0)) {
            return;
        }

        var ellipsoid = Ellipsoid.fromCartesian3(radii);
        var stackPartitions = ellipsoidGeometry._stackPartitions;
        var slicePartitions = ellipsoidGeometry._slicePartitions;
        var subdivisions = ellipsoidGeometry._subdivisions;

        var indicesSize = subdivisions * (stackPartitions + slicePartitions - 1);
        var positionSize = indicesSize - slicePartitions + 2;
        var positions = new Float64Array(positionSize * 3);
        var indices = IndexDatatype.createTypedArray(positionSize, indicesSize * 2);

        var i;
        var j;
        var theta;
        var phi;
        var cosPhi;
        var sinPhi;
        var index = 0;

        var cosTheta = new Array(subdivisions);
        var sinTheta = new Array(subdivisions);
        for (i = 0; i < subdivisions; i++) {
            theta = CesiumMath.TWO_PI * i / subdivisions;
            cosTheta[i] = cos(theta);
            sinTheta[i] = sin(theta);
        }

        for (i = 1; i < stackPartitions; i++) {
            phi = Math.PI * i / stackPartitions;
            cosPhi = cos(phi);
            sinPhi = sin(phi);

            for (j = 0; j < subdivisions; j++) {
                positions[index++] = radii.x * cosTheta[j] * sinPhi;
                positions[index++] = radii.y * sinTheta[j] * sinPhi;
                positions[index++] = radii.z * cosPhi;
            }
        }

        cosTheta.length = slicePartitions;
        sinTheta.length = slicePartitions;
        for (i = 0; i < slicePartitions; i++) {
            theta = CesiumMath.TWO_PI * i / slicePartitions;
            cosTheta[i] = cos(theta);
            sinTheta[i] = sin(theta);
        }

        positions[index++] = 0;
        positions[index++] = 0;
        positions[index++] = radii.z;

        for (i = 1; i < subdivisions; i++) {
            phi = Math.PI * i / subdivisions;
            cosPhi = cos(phi);
            sinPhi = sin(phi);

            for (j = 0; j < slicePartitions; j++) {
                positions[index++] = radii.x * cosTheta[j] * sinPhi;
                positions[index++] = radii.y * sinTheta[j] * sinPhi;
                positions[index++] = radii.z * cosPhi;
            }
        }

        positions[index++] = 0;
        positions[index++] = 0;
        positions[index++] = -radii.z;

        index = 0;
        for (i = 0; i < stackPartitions - 1; ++i) {
            var topRowOffset = (i * subdivisions);
            for (j = 0; j < subdivisions - 1; ++j) {
                indices[index++] = topRowOffset + j;
                indices[index++] = topRowOffset + j + 1;
            }

            indices[index++] = topRowOffset + subdivisions - 1;
            indices[index++] = topRowOffset;
        }

        var sliceOffset = subdivisions * (stackPartitions - 1);
        for (j = 1; j < slicePartitions + 1; ++j) {
            indices[index++] = sliceOffset;
            indices[index++] = sliceOffset + j;
        }

        for (i = 0; i < subdivisions - 2; ++i) {
            var topOffset = (i * slicePartitions) + 1 + sliceOffset;
            var bottomOffset = ((i + 1) * slicePartitions) + 1 + sliceOffset;

            for (j = 0; j < slicePartitions - 1; ++j) {
                indices[index++] = bottomOffset + j;
                indices[index++] = topOffset + j;
            }

            indices[index++] = bottomOffset + slicePartitions - 1;
            indices[index++] = topOffset + slicePartitions - 1;
        }

        var lastPosition = positions.length / 3 - 1;
        for (j = lastPosition - 1; j > lastPosition - slicePartitions - 1; --j) {
            indices[index++] = lastPosition;
            indices[index++] = j;
        }

        var attributes = new GeometryAttributes({
            position: new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : positions
            })
        });

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : BoundingSphere.fromEllipsoid(ellipsoid)
        });
    };

    return EllipsoidOutlineGeometry;
});

/*global define*/
define('Core/EllipsoidTerrainProvider',[
        '../ThirdParty/when',
        './defaultValue',
        './defined',
        './defineProperties',
        './Ellipsoid',
        './Event',
        './GeographicTilingScheme',
        './HeightmapTerrainData',
        './TerrainProvider'
    ], function(
        when,
        defaultValue,
        defined,
        defineProperties,
        Ellipsoid,
        Event,
        GeographicTilingScheme,
        HeightmapTerrainData,
        TerrainProvider) {
    'use strict';

    /**
     * A very simple {@link TerrainProvider} that produces geometry by tessellating an ellipsoidal
     * surface.
     *
     * @alias EllipsoidTerrainProvider
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {TilingScheme} [options.tilingScheme] The tiling scheme specifying how the ellipsoidal
     * surface is broken into tiles.  If this parameter is not provided, a {@link GeographicTilingScheme}
     * is used.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     * parameter is specified, the WGS84 ellipsoid is used.
     *
     * @see TerrainProvider
     */
    function EllipsoidTerrainProvider(options) {
        options = defaultValue(options, {});

        this._tilingScheme = options.tilingScheme;
        if (!defined(this._tilingScheme)) {
            this._tilingScheme = new GeographicTilingScheme({
                ellipsoid : defaultValue(options.ellipsoid, Ellipsoid.WGS84)
            });
        }

        // Note: the 64 below does NOT need to match the actual vertex dimensions, because
        // the ellipsoid is significantly smoother than actual terrain.
        this._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(this._tilingScheme.ellipsoid, 64, this._tilingScheme.getNumberOfXTilesAtLevel(0));

        this._errorEvent = new Event();
        this._readyPromise = when.resolve(true);
    }

    defineProperties(EllipsoidTerrainProvider.prototype, {
        /**
         * Gets an event that is raised when the terrain provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets the credit to display when this terrain provider is active.  Typically this is used to credit
         * the source of the terrain.  This function should not be called before {@link EllipsoidTerrainProvider#ready} returns true.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {Credit}
         */
        credit : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link EllipsoidTerrainProvider#ready} returns true.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {GeographicTilingScheme}
         */
        tilingScheme : {
            get : function() {
                return this._tilingScheme;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : function() {
                return true;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        },

        /**
         * Gets a value indicating whether or not the provider includes a water mask.  The water mask
         * indicates which areas of the globe are water rather than land, so they can be rendered
         * as a reflective surface with animated waves.  This function should not be
         * called before {@link EllipsoidTerrainProvider#ready} returns true.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {Boolean}
         */
        hasWaterMask : {
            get : function() {
                return false;
            }
        },

        /**
         * Gets a value indicating whether or not the requested tiles include vertex normals.
         * This function should not be called before {@link EllipsoidTerrainProvider#ready} returns true.
         * @memberof EllipsoidTerrainProvider.prototype
         * @type {Boolean}
         */
        hasVertexNormals : {
            get : function() {
                return false;
            }
        }
    });

    /**
     * Requests the geometry for a given tile.  This function should not be called before
     * {@link TerrainProvider#ready} returns true.  The result includes terrain
     * data and indicates that all child tiles are available.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
     *                  or false if the request should be initiated regardless of the number of requests
     *                  already in progress.
     * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry.  If this method
     *          returns undefined instead of a promise, it is an indication that too many requests are already
     *          pending and the request will be retried later.
     */
    EllipsoidTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
        var width = 16;
        var height = 16;
        return new HeightmapTerrainData({
            buffer : new Uint8Array(width * height),
            width : width,
            height : height
        });
    };

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    EllipsoidTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
        return this._levelZeroMaximumGeometricError / (1 << level);
    };

    /**
     * Determines whether data for a tile is available to be loaded.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @returns {Boolean} Undefined if not supported, otherwise true or false.
     */
    EllipsoidTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
        return undefined;
    };

    return EllipsoidTerrainProvider;
});

/*global define*/
define('Core/EventHelper',[
        './defined',
        './DeveloperError'
    ], function(
        defined,
        DeveloperError) {
    'use strict';

    /**
     * A convenience object that simplifies the common pattern of attaching event listeners
     * to several events, then removing all those listeners at once later, for example, in
     * a destroy method.
     *
     * @alias EventHelper
     * @constructor
     *
     *
     * @example
     * var helper = new Cesium.EventHelper();
     *
     * helper.add(someObject.event, listener1, this);
     * helper.add(otherObject.event, listener2, this);
     *
     * // later...
     * helper.removeAll();
     * 
     * @see Event
     */
    function EventHelper() {
        this._removalFunctions = [];
    }

    /**
     * Adds a listener to an event, and records the registration to be cleaned up later.
     *
     * @param {Event} event The event to attach to.
     * @param {Function} listener The function to be executed when the event is raised.
     * @param {Object} [scope] An optional object scope to serve as the <code>this</code>
     *        pointer in which the listener function will execute.
     * @returns {EventHelper~RemoveCallback} A function that will remove this event listener when invoked.
     *
     * @see Event#addEventListener
     */
    EventHelper.prototype.add = function(event, listener, scope) {
                if (!defined(event)) {
            throw new DeveloperError('event is required');
        }
        
        var removalFunction = event.addEventListener(listener, scope);
        this._removalFunctions.push(removalFunction);

        var that = this;
        return function() {
            removalFunction();
            var removalFunctions = that._removalFunctions;
            removalFunctions.splice(removalFunctions.indexOf(removalFunction), 1);
        };
    };

    /**
     * Unregisters all previously added listeners.
     *
     * @see Event#removeEventListener
     */
    EventHelper.prototype.removeAll = function() {
        var removalFunctions = this._removalFunctions;
        for (var i = 0, len = removalFunctions.length; i < len; ++i) {
            removalFunctions[i]();
        }
        removalFunctions.length = 0;
    };

    /**
     * A function that removes a listener.
     * @callback EventHelper~RemoveCallback
     */

    return EventHelper;
});

/*global define*/
define('Core/ExtrapolationType',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Constants to determine how an interpolated value is extrapolated
     * when querying outside the bounds of available data.
     * 
     * @exports ExtrapolationType
     *
     * @see SampledProperty
     */
    var ExtrapolationType = {
        /**
         * No extrapolation occurs.
         *
         * @type {Number}
         * @constant
         */
        NONE : 0,

        /**
         * The first or last value is used when outside the range of sample data.
         *
         * @type {Number}
         * @constant
         */
        HOLD : 1,

        /**
         * The value is extrapolated.
         *
         * @type {Number}
         * @constant
         */
        EXTRAPOLATE : 2
    };

    return freezeObject(ExtrapolationType);
});

/*global define*/
define('Core/GeocoderService',[
    './DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * @typedef {Object} GeocoderResult
     * @property {String} displayName The display name for a location
     * @property {Rectangle|Cartesian3} destination The bounding box for a location
     */

    /**
     * Provides geocoding through an external service. This type describes an interface and
     * is not intended to be used.
     * @alias GeocoderService
     * @constructor
     *
     * @see BingMapsGeocoderService
     */
    function GeocoderService() {
    }

    /**
     * @function
     *
     * @param {String} query The query to be sent to the geocoder service
     * @returns {Promise<GeocoderResult[]>}
     */
    GeocoderService.prototype.geocode = DeveloperError.throwInstantiationError;

    return GeocoderService;
});

/*global define*/
define('Core/GeometryInstanceAttribute',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Values and type information for per-instance geometry attributes.
     *
     * @alias GeometryInstanceAttribute
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {ComponentDatatype} [options.componentDatatype] The datatype of each component in the attribute, e.g., individual elements in values.
     * @param {Number} [options.componentsPerAttribute] A number between 1 and 4 that defines the number of components in an attributes.
     * @param {Boolean} [options.normalize=false] When <code>true</code> and <code>componentDatatype</code> is an integer format, indicate that the components should be mapped to the range [0, 1] (unsigned) or [-1, 1] (signed) when they are accessed as floating-point for rendering.
     * @param {Number[]} [options.value] The value for the attribute.
     *
     * @exception {DeveloperError} options.componentsPerAttribute must be between 1 and 4.
     *
     *
     * @example
     * var instance = new Cesium.GeometryInstance({
     *   geometry : Cesium.BoxGeometry.fromDimensions({
     *     dimensions : new Cesium.Cartesian3(1000000.0, 1000000.0, 500000.0)
     *   }),
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(0.0, 0.0)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
     *   id : 'box',
     *   attributes : {
     *     color : new Cesium.GeometryInstanceAttribute({
     *       componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE,
     *       componentsPerAttribute : 4,
     *       normalize : true,
     *       value : [255, 255, 0, 255]
     *     })
     *   }
     * });
     * 
     * @see ColorGeometryInstanceAttribute
     * @see ShowGeometryInstanceAttribute
     * @see DistanceDisplayConditionGeometryInstanceAttribute
     */
    function GeometryInstanceAttribute(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.componentDatatype)) {
            throw new DeveloperError('options.componentDatatype is required.');
        }
        if (!defined(options.componentsPerAttribute)) {
            throw new DeveloperError('options.componentsPerAttribute is required.');
        }
        if (options.componentsPerAttribute < 1 || options.componentsPerAttribute > 4) {
            throw new DeveloperError('options.componentsPerAttribute must be between 1 and 4.');
        }
        if (!defined(options.value)) {
            throw new DeveloperError('options.value is required.');
        }
        
        /**
         * The datatype of each component in the attribute, e.g., individual elements in
         * {@link GeometryInstanceAttribute#value}.
         *
         * @type ComponentDatatype
         *
         * @default undefined
         */
        this.componentDatatype = options.componentDatatype;

        /**
         * A number between 1 and 4 that defines the number of components in an attributes.
         * For example, a position attribute with x, y, and z components would have 3 as
         * shown in the code example.
         *
         * @type Number
         *
         * @default undefined
         *
         * @example
         * show : new Cesium.GeometryInstanceAttribute({
         *   componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE,
         *   componentsPerAttribute : 1,
         *   normalize : true,
         *   value : [1.0]
         * })
         */
        this.componentsPerAttribute = options.componentsPerAttribute;

        /**
         * When <code>true</code> and <code>componentDatatype</code> is an integer format,
         * indicate that the components should be mapped to the range [0, 1] (unsigned)
         * or [-1, 1] (signed) when they are accessed as floating-point for rendering.
         * <p>
         * This is commonly used when storing colors using {@link ComponentDatatype.UNSIGNED_BYTE}.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         *
         * @example
         * attribute.componentDatatype = Cesium.ComponentDatatype.UNSIGNED_BYTE;
         * attribute.componentsPerAttribute = 4;
         * attribute.normalize = true;
         * attribute.value = [
         *   Cesium.Color.floatToByte(color.red),
         *   Cesium.Color.floatToByte(color.green),
         *   Cesium.Color.floatToByte(color.blue),
         *   Cesium.Color.floatToByte(color.alpha)
         * ];
         */
        this.normalize = defaultValue(options.normalize, false);

        /**
         * The values for the attributes stored in a typed array.  In the code example,
         * every three elements in <code>values</code> defines one attributes since
         * <code>componentsPerAttribute</code> is 3.
         *
         * @type {Number[]}
         *
         * @default undefined
         *
         * @example
         * show : new Cesium.GeometryInstanceAttribute({
         *   componentDatatype : Cesium.ComponentDatatype.UNSIGNED_BYTE,
         *   componentsPerAttribute : 1,
         *   normalize : true,
         *   value : [1.0]
         * })
         */
        this.value = options.value;
    }

    return GeometryInstanceAttribute;
});

/*global define*/
define('Core/getBaseUri',[
        '../ThirdParty/Uri',
        './defined',
        './DeveloperError'
    ], function(
        Uri,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Given a URI, returns the base path of the URI.
     * @exports getBaseUri
     *
     * @param {String} uri The Uri.
     * @param {Boolean} [includeQuery = false] Whether or not to include the query string and fragment form the uri
     * @returns {String} The base path of the Uri.
     *
     * @example
     * // basePath will be "/Gallery/";
     * var basePath = Cesium.getBaseUri('/Gallery/simple.czml?value=true&example=false');
     *
     * // basePath will be "/Gallery/?value=true&example=false";
     * var basePath = Cesium.getBaseUri('/Gallery/simple.czml?value=true&example=false', true);
     */
    function getBaseUri(uri, includeQuery) {
                if (!defined(uri)) {
            throw new DeveloperError('uri is required.');
        }
        
        var basePath = '';
        var i = uri.lastIndexOf('/');
        if (i !== -1) {
            basePath = uri.substring(0, i + 1);
        }

        if (!includeQuery) {
            return basePath;
        }

        uri = new Uri(uri);
        if (defined(uri.query)) {
            basePath += '?' + uri.query;
        }
        if (defined(uri.fragment)){
            basePath += '#' + uri.fragment;
        }

        return basePath;
    }

    return getBaseUri;
});

/*global define*/
define('Core/getExtensionFromUri',[
        '../ThirdParty/Uri',
        './defined',
        './DeveloperError'
    ], function(
        Uri,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Given a URI, returns the extension of the URI.
     * @exports getExtensionFromUri
     *
     * @param {String} uri The Uri.
     * @returns {String} The extension of the Uri.
     *
     * @example
     * //extension will be "czml";
     * var extension = Cesium.getExtensionFromUri('/Gallery/simple.czml?value=true&example=false');
     */
    function getExtensionFromUri(uri) {
                if (!defined(uri)) {
            throw new DeveloperError('uri is required.');
        }
        
        var uriObject = new Uri(uri);
        uriObject.normalize();
        var path = uriObject.path;
        var index = path.lastIndexOf('/');
        if (index !== -1) {
            path = path.substr(index + 1);
        }
        index = path.lastIndexOf('.');
        if (index === -1) {
            path = '';
        } else {
            path = path.substr(index + 1);
        }
        return path;
    }

    return getExtensionFromUri;
});

/*global define*/
define('Core/getFilenameFromUri',[
        '../ThirdParty/Uri',
        './defined',
        './DeveloperError'
    ], function(
        Uri,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Given a URI, returns the last segment of the URI, removing any path or query information.
     * @exports getFilenameFromUri
     *
     * @param {String} uri The Uri.
     * @returns {String} The last segment of the Uri.
     *
     * @example
     * //fileName will be"simple.czml";
     * var fileName = Cesium.getFilenameFromUri('/Gallery/simple.czml?value=true&example=false');
     */
    function getFilenameFromUri(uri) {
                if (!defined(uri)) {
            throw new DeveloperError('uri is required.');
        }
        
        var uriObject = new Uri(uri);
        uriObject.normalize();
        var path = uriObject.path;
        var index = path.lastIndexOf('/');
        if (index !== -1) {
            path = path.substr(index + 1);
        }
        return path;
    }

    return getFilenameFromUri;
});

/*global define*/
define('Core/getStringFromTypedArray',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';
    /*global TextDecoder*/

    /**
     * @private
     */
    function getStringFromTypedArray(uint8Array, byteOffset, byteLength) {
                if (!defined(uint8Array)) {
            throw new DeveloperError('uint8Array is required.');
        }
        if (byteOffset < 0) {
            throw new DeveloperError('byteOffset cannot be negative.');
        }
        if (byteLength < 0) {
            throw new DeveloperError('byteLength cannot be negative.');
        }
        if ((byteOffset + byteLength) > uint8Array.byteLength) {
            throw new DeveloperError('sub-region exceeds array bounds.');
        }
        
        byteOffset = defaultValue(byteOffset, 0);
        byteLength = defaultValue(byteLength, uint8Array.byteLength - byteOffset);

        uint8Array = uint8Array.subarray(byteOffset, byteOffset + byteLength);

        return getStringFromTypedArray.decode(uint8Array);
    }

    // Exposed functions for testing
    getStringFromTypedArray.decodeWithTextDecoder = function(view) {
        var decoder = new TextDecoder('utf-8');
        return decoder.decode(view);
    };

    getStringFromTypedArray.decodeWithFromCharCode = function(view) {
        var result = '';
        var length = view.length;

        // Convert one character at a time to avoid stack overflow on iPad.
        //
        // fromCharCode will not handle all legal Unicode values (up to 21 bits).  See
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode
        for (var i = 0; i < length; ++i) {
            result += String.fromCharCode(view[i]);
        }
        return result;
    };

    if (typeof TextDecoder !== 'undefined') {
        getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithTextDecoder;
    } else {
        getStringFromTypedArray.decode = getStringFromTypedArray.decodeWithFromCharCode;
    }

    return getStringFromTypedArray;
});

/*global define*/
define('Core/getMagic',[
        './defaultValue',
        './getStringFromTypedArray'
    ], function(
        defaultValue,
        getStringFromTypedArray) {
    'use strict';

    /**
     * @private
     */
    function getMagic(uint8Array, byteOffset) {
        byteOffset = defaultValue(byteOffset, 0);
        return getStringFromTypedArray(uint8Array, byteOffset, Math.min(4, uint8Array.length));
    }

    return getMagic;
});

/*global define*/
define('Core/HeadingPitchRange',[
        './defaultValue',
        './defined'
    ], function(
        defaultValue,
        defined) {
    'use strict';

    /**
     * Defines a heading angle, pitch angle, and range in a local frame.
     * Heading is the rotation from the local north direction where a positive angle is increasing eastward.
     * Pitch is the rotation from the local xy-plane. Positive pitch angles are above the plane. Negative pitch
     * angles are below the plane. Range is the distance from the center of the frame.
     * @alias HeadingPitchRange
     * @constructor
     *
     * @param {Number} [heading=0.0] The heading angle in radians.
     * @param {Number} [pitch=0.0] The pitch angle in radians.
     * @param {Number} [range=0.0] The distance from the center in meters.
     */
    function HeadingPitchRange(heading, pitch, range) {
        /**
         * Heading is the rotation from the local north direction where a positive angle is increasing eastward.
         * @type {Number}
         */
        this.heading = defaultValue(heading, 0.0);

        /**
         * Pitch is the rotation from the local xy-plane. Positive pitch angles
         * are above the plane. Negative pitch angles are below the plane.
         * @type {Number}
         */
        this.pitch = defaultValue(pitch, 0.0);

        /**
         * Range is the distance from the center of the local frame.
         * @type {Number}
         */
        this.range = defaultValue(range, 0.0);
    }

    /**
     * Duplicates a HeadingPitchRange instance.
     *
     * @param {HeadingPitchRange} hpr The HeadingPitchRange to duplicate.
     * @param {HeadingPitchRange} [result] The object onto which to store the result.
     * @returns {HeadingPitchRange} The modified result parameter or a new HeadingPitchRange instance if one was not provided. (Returns undefined if hpr is undefined)
     */
    HeadingPitchRange.clone = function(hpr, result) {
        if (!defined(hpr)) {
            return undefined;
        }
        if (!defined(result)) {
            result = new HeadingPitchRange();
        }

        result.heading = hpr.heading;
        result.pitch = hpr.pitch;
        result.range = hpr.range;
        return result;
    };

    return HeadingPitchRange;
});

/*global define*/
define('Core/HermitePolynomialApproximation',[
        './defaultValue',
        './defined',
        './DeveloperError',
        './Math'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        CesiumMath) {
    'use strict';

    var factorial = CesiumMath.factorial;

    function calculateCoefficientTerm(x, zIndices, xTable, derivOrder, termOrder, reservedIndices) {
        var result = 0;
        var reserved;
        var i;
        var j;

        if (derivOrder > 0) {
            for (i = 0; i < termOrder; i++) {
                reserved = false;
                for (j = 0; j < reservedIndices.length && !reserved; j++) {
                    if (i === reservedIndices[j]) {
                        reserved = true;
                    }
                }

                if (!reserved) {
                    reservedIndices.push(i);
                    result += calculateCoefficientTerm(x, zIndices, xTable, derivOrder - 1, termOrder, reservedIndices);
                    reservedIndices.splice(reservedIndices.length - 1, 1);
                }
            }

            return result;
        }

        result = 1;
        for (i = 0; i < termOrder; i++) {
            reserved = false;
            for (j = 0; j < reservedIndices.length && !reserved; j++) {
                if (i === reservedIndices[j]) {
                    reserved = true;
                }
            }

            if (!reserved) {
                result *= x - xTable[zIndices[i]];
            }
        }

        return result;
    }

    /**
     * An {@link InterpolationAlgorithm} for performing Hermite interpolation.
     *
     * @exports HermitePolynomialApproximation
     */
    var HermitePolynomialApproximation = {
        type : 'Hermite'
    };

    /**
     * Given the desired degree, returns the number of data points required for interpolation.
     *
     * @param {Number} degree The desired degree of interpolation.
     * @param {Number} [inputOrder=0]  The order of the inputs (0 means just the data, 1 means the data and its derivative, etc).
     * @returns {Number} The number of required data points needed for the desired degree of interpolation.
     * @exception {DeveloperError} degree must be 0 or greater.
     * @exception {DeveloperError} inputOrder must be 0 or greater.
     */
    HermitePolynomialApproximation.getRequiredDataPoints = function(degree, inputOrder) {
        inputOrder = defaultValue(inputOrder, 0);

                if (!defined(degree)) {
            throw new DeveloperError('degree is required.');
        }
        if (degree < 0) {
            throw new DeveloperError('degree must be 0 or greater.');
        }
        if (inputOrder < 0) {
            throw new DeveloperError('inputOrder must be 0 or greater.');
        }
        
        return Math.max(Math.floor((degree + 1) / (inputOrder + 1)), 2);
    };

    /**
     * Interpolates values using Hermite Polynomial Approximation.
     *
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number[]} [result] An existing array into which to store the result.
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    HermitePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
        if (!defined(result)) {
            result = new Array(yStride);
        }

        var i;
        var j;
        var d;
        var s;
        var len;
        var index;
        var length = xTable.length;
        var coefficients = new Array(yStride);

        for (i = 0; i < yStride; i++) {
            result[i] = 0;

            var l = new Array(length);
            coefficients[i] = l;
            for (j = 0; j < length; j++) {
                l[j] = [];
            }
        }

        var zIndicesLength = length, zIndices = new Array(zIndicesLength);

        for (i = 0; i < zIndicesLength; i++) {
            zIndices[i] = i;
        }

        var highestNonZeroCoef = length - 1;
        for (s = 0; s < yStride; s++) {
            for (j = 0; j < zIndicesLength; j++) {
                index = zIndices[j] * yStride + s;
                coefficients[s][0].push(yTable[index]);
            }

            for (i = 1; i < zIndicesLength; i++) {
                var nonZeroCoefficients = false;
                for (j = 0; j < zIndicesLength - i; j++) {
                    var zj = xTable[zIndices[j]];
                    var zn = xTable[zIndices[j + i]];

                    var numerator;
                    if (zn - zj <= 0) {
                        index = zIndices[j] * yStride + yStride * i + s;
                        numerator = yTable[index];
                        coefficients[s][i].push(numerator / factorial(i));
                    } else {
                        numerator = (coefficients[s][i - 1][j + 1] - coefficients[s][i - 1][j]);
                        coefficients[s][i].push(numerator / (zn - zj));
                    }
                    nonZeroCoefficients = nonZeroCoefficients || (numerator !== 0);
                }

                if (!nonZeroCoefficients) {
                    highestNonZeroCoef = i - 1;
                }
            }
        }

        for (d = 0, len = 0; d <= len; d++) {
            for (i = d; i <= highestNonZeroCoef; i++) {
                var tempTerm = calculateCoefficientTerm(x, zIndices, xTable, d, i, []);
                for (s = 0; s < yStride; s++) {
                    var coeff = coefficients[s][i][0];
                    result[s + d * yStride] += coeff * tempTerm;
                }
            }
        }

        return result;
    };

    var arrayScratch = [];

    /**
     * Interpolates values using Hermite Polynomial Approximation.
     *
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number} inputOrder The number of derivatives supplied for input.
     * @param {Number} outputOrder The number of derivatives desired for output.
     * @param {Number[]} [result] An existing array into which to store the result.
     *
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    HermitePolynomialApproximation.interpolate = function(x, xTable, yTable, yStride, inputOrder, outputOrder, result) {
        var resultLength = yStride * (outputOrder + 1);
        if (!defined(result)) {
            result = new Array(resultLength);
        }
        for (var r = 0; r < resultLength; r++) {
            result[r] = 0;
        }

        var length = xTable.length;
        // The zIndices array holds copies of the addresses of the xTable values
        // in the range we're looking at. Even though this just holds information already
        // available in xTable this is a much more convenient format.
        var zIndices = new Array(length * (inputOrder + 1));
        for (var i = 0; i < length; i++) {
            for (var j = 0; j < (inputOrder + 1); j++) {
                zIndices[i * (inputOrder + 1) + j] = i;
            }
        }

        var zIndiceslength = zIndices.length;
        var coefficients = arrayScratch;
        var highestNonZeroCoef = fillCoefficientList(coefficients, zIndices, xTable, yTable, yStride, inputOrder);
        var reservedIndices = [];

        var tmp = zIndiceslength * (zIndiceslength + 1) / 2;
        var loopStop = Math.min(highestNonZeroCoef, outputOrder);
        for (var d = 0; d <= loopStop; d++) {
            for (i = d; i <= highestNonZeroCoef; i++) {
                reservedIndices.length = 0;
                var tempTerm = calculateCoefficientTerm(x, zIndices, xTable, d, i, reservedIndices);
                var dimTwo = Math.floor(i * (1 - i) / 2) + (zIndiceslength * i);

                for (var s = 0; s < yStride; s++) {
                    var dimOne = Math.floor(s * tmp);
                    var coef = coefficients[dimOne + dimTwo];
                    result[s + d * yStride] += coef * tempTerm;
                }
            }
        }

        return result;
    };

    function fillCoefficientList(coefficients, zIndices, xTable, yTable, yStride, inputOrder) {
        var j;
        var index;
        var highestNonZero = -1;
        var zIndiceslength = zIndices.length;
        var tmp = zIndiceslength * (zIndiceslength + 1) / 2;

        for (var s = 0; s < yStride; s++) {
            var dimOne = Math.floor(s * tmp);

            for (j = 0; j < zIndiceslength; j++) {
                index = zIndices[j] * yStride * (inputOrder + 1) + s;
                coefficients[dimOne + j] = yTable[index];
            }

            for (var i = 1; i < zIndiceslength; i++) {
                var coefIndex = 0;
                var dimTwo = Math.floor(i * (1 - i) / 2) + (zIndiceslength * i);
                var nonZeroCoefficients = false;

                for (j = 0; j < zIndiceslength - i; j++) {
                    var zj = xTable[zIndices[j]];
                    var zn = xTable[zIndices[j + i]];

                    var numerator;
                    var coefficient;
                    if (zn - zj <= 0) {
                        index = zIndices[j] * yStride * (inputOrder + 1) + yStride * i + s;
                        numerator = yTable[index];
                        coefficient = (numerator / CesiumMath.factorial(i));
                        coefficients[dimOne + dimTwo + coefIndex] = coefficient;
                        coefIndex++;
                    } else {
                        var dimTwoMinusOne = Math.floor((i - 1) * (2 - i) / 2) + (zIndiceslength * (i - 1));
                        numerator = coefficients[dimOne + dimTwoMinusOne + j + 1] - coefficients[dimOne + dimTwoMinusOne + j];
                        coefficient = (numerator / (zn - zj));
                        coefficients[dimOne + dimTwo + coefIndex] = coefficient;
                        coefIndex++;
                    }
                    nonZeroCoefficients = nonZeroCoefficients || (numerator !== 0.0);
                }

                if (nonZeroCoefficients) {
                    highestNonZero = Math.max(highestNonZero, i);
                }
            }
        }

        return highestNonZero;
    }

    return HermitePolynomialApproximation;
});

/*global define*/
define('Core/IauOrientationParameters',[],function() {
    'use strict';

    /**
     * A structure containing the orientation data computed at a particular time. The data
     * represents the direction of the pole of rotation and the rotation about that pole.
     * <p>
     * These parameters correspond to the parameters in the Report from the IAU/IAG Working Group
     * except that they are expressed in radians.
     * </p>
     *
     * @exports IauOrientationParameters
     *
     * @private
     */
    function IauOrientationParameters(rightAscension, declination, rotation, rotationRate) {
        /**
         * The right ascension of the north pole of the body with respect to
         * the International Celestial Reference Frame, in radians.
         * @type {Number}
         *
         * @private
         */
        this.rightAscension = rightAscension;

        /**
         * The declination of the north pole of the body with respect to
         * the International Celestial Reference Frame, in radians.
         * @type {Number}
         *
         * @private
         */
        this.declination = declination;

        /**
         * The rotation about the north pole used to align a set of axes with
         * the meridian defined by the IAU report, in radians.
         * @type {Number}
         *
         * @private
         */
        this.rotation = rotation;

        /**
         * The instantaneous rotation rate about the north pole, in radians per second.
         * @type {Number}
         *
         * @private
         */
        this.rotationRate = rotationRate;
    }

    return IauOrientationParameters;
});

/*global define*/
define('Core/Iau2000Orientation',[
        './defined',
        './IauOrientationParameters',
        './JulianDate',
        './Math',
        './TimeConstants'
    ], function(
        defined,
        IauOrientationParameters,
        JulianDate,
        CesiumMath,
        TimeConstants) {
    'use strict';

    /**
     * This is a collection of the orientation information available for central bodies.
     * The data comes from the Report of the IAU/IAG Working Group on Cartographic
     * Coordinates and Rotational Elements: 2000.
     *
     * @exports Iau2000Orientation
     *
     * @private
     */
    var Iau2000Orientation = {};

    var TdtMinusTai = 32.184;
    var J2000d = 2451545.0;

    var c1 = -0.0529921;
    var c2 = -0.1059842;
    var c3 = 13.0120009;
    var c4 = 13.3407154;
    var c5 = 0.9856003;
    var c6 = 26.4057084;
    var c7 = 13.0649930;
    var c8 = 0.3287146;
    var c9 = 1.7484877;
    var c10 = -0.1589763;
    var c11 = 0.0036096;
    var c12 = 0.1643573;
    var c13 = 12.9590088;
    var dateTT = new JulianDate();

    /**
     * Compute the orientation parameters for the Moon.
     *
     * @param {JulianDate} [date=JulianDate.now()] The date to evaluate the parameters.
     * @param {IauOrientationParameters} [result] The object onto which to store the result.
     * @returns {IauOrientationParameters} The modified result parameter or a new instance representing the orientation of the Earth's Moon.
     */
    Iau2000Orientation.ComputeMoon = function(date, result) {
        if (!defined(date)) {
            date = JulianDate.now();
        }

        dateTT = JulianDate.addSeconds(date, TdtMinusTai, dateTT);
        var d = JulianDate.totalDays(dateTT) - J2000d;
        var T = d / TimeConstants.DAYS_PER_JULIAN_CENTURY;

        var E1 = (125.045 + c1 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E2 = (250.089 + c2 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E3 = (260.008 + c3 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E4 = (176.625 + c4 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E5 = (357.529 + c5 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E6 = (311.589 + c6 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E7 = (134.963 + c7 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E8 = (276.617 + c8 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E9 = (34.226 + c9 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E10 = (15.134 + c10 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E11 = (119.743 + c11 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E12 = (239.961 + c12 * d) * CesiumMath.RADIANS_PER_DEGREE;
        var E13 = (25.053 + c13 * d) * CesiumMath.RADIANS_PER_DEGREE;

        var sinE1 = Math.sin(E1);
        var sinE2 = Math.sin(E2);
        var sinE3 = Math.sin(E3);
        var sinE4 = Math.sin(E4);
        var sinE5 = Math.sin(E5);
        var sinE6 = Math.sin(E6);
        var sinE7 = Math.sin(E7);
        var sinE8 = Math.sin(E8);
        var sinE9 = Math.sin(E9);
        var sinE10 = Math.sin(E10);
        var sinE11 = Math.sin(E11);
        var sinE12 = Math.sin(E12);
        var sinE13 = Math.sin(E13);

        var cosE1 = Math.cos(E1);
        var cosE2 = Math.cos(E2);
        var cosE3 = Math.cos(E3);
        var cosE4 = Math.cos(E4);
        var cosE5 = Math.cos(E5);
        var cosE6 = Math.cos(E6);
        var cosE7 = Math.cos(E7);
        var cosE8 = Math.cos(E8);
        var cosE9 = Math.cos(E9);
        var cosE10 = Math.cos(E10);
        var cosE11 = Math.cos(E11);
        var cosE12 = Math.cos(E12);
        var cosE13 = Math.cos(E13);

        var rightAscension = (269.9949 + 0.0031 * T - 3.8787 * sinE1 - 0.1204 * sinE2 +
            0.0700 * sinE3 - 0.0172 * sinE4 + 0.0072 * sinE6 -
            0.0052 * sinE10 + 0.0043 * sinE13) *
            CesiumMath.RADIANS_PER_DEGREE;
        var declination = (66.5392 + 0.013 * T + 1.5419 * cosE1 + 0.0239 * cosE2 -
            0.0278 * cosE3 + 0.0068 * cosE4 - 0.0029 * cosE6 +
            0.0009 * cosE7 + 0.0008 * cosE10 - 0.0009 * cosE13) *
            CesiumMath.RADIANS_PER_DEGREE;
        var rotation = (38.3213 + 13.17635815 * d - 1.4e-12 * d * d + 3.5610 * sinE1 +
            0.1208 * sinE2 - 0.0642 * sinE3 + 0.0158 * sinE4 +
            0.0252 * sinE5 - 0.0066 * sinE6 - 0.0047 * sinE7 -
            0.0046 * sinE8 + 0.0028 * sinE9 + 0.0052 * sinE10 +
            0.004 * sinE11 + 0.0019 * sinE12 - 0.0044 * sinE13) *
            CesiumMath.RADIANS_PER_DEGREE;

        var rotationRate = ((13.17635815 - 1.4e-12 * (2.0 * d)) +
            3.5610 * cosE1 * c1 +
            0.1208 * cosE2*c2 - 0.0642 * cosE3*c3 + 0.0158 * cosE4*c4 +
            0.0252 * cosE5*c5 - 0.0066 * cosE6*c6 - 0.0047 * cosE7*c7 -
            0.0046 * cosE8*c8 + 0.0028 * cosE9*c9 + 0.0052 * cosE10*c10 +
            0.004 * cosE11*c11 + 0.0019 * cosE12*c12 - 0.0044 * cosE13*c13) /
            86400.0 * CesiumMath.RADIANS_PER_DEGREE;

        if (!defined(result)) {
            result = new IauOrientationParameters();
        }

        result.rightAscension = rightAscension;
        result.declination = declination;
        result.rotation = rotation;
        result.rotationRate = rotationRate;

        return result;
    };

    return Iau2000Orientation;
});

/*global define*/
define('Core/IauOrientationAxes',[
        './Cartesian3',
        './defined',
        './Iau2000Orientation',
        './JulianDate',
        './Math',
        './Matrix3',
        './Quaternion'
    ], function(
        Cartesian3,
        defined,
        Iau2000Orientation,
        JulianDate,
        CesiumMath,
        Matrix3,
        Quaternion) {
    'use strict';

    /**
     * The Axes representing the orientation of a Globe as represented by the data
     * from the IAU/IAG Working Group reports on rotational elements.
     * @alias IauOrientationAxes
     * @constructor
     *
     * @param {IauOrientationAxes~ComputeFunction} [computeFunction] The function that computes the {@link IauOrientationParameters} given a {@link JulianDate}.
     *
     * @see Iau2000Orientation
     *
     * @private
     */
    function IauOrientationAxes(computeFunction) {
        if (!defined(computeFunction) || typeof computeFunction !== 'function') {
            computeFunction = Iau2000Orientation.ComputeMoon;
        }

        this._computeFunction = computeFunction;
    }

    var xAxisScratch = new Cartesian3();
    var yAxisScratch = new Cartesian3();
    var zAxisScratch = new Cartesian3();

    function computeRotationMatrix(alpha, delta, result) {
        var xAxis = xAxisScratch;
        xAxis.x = Math.cos(alpha + CesiumMath.PI_OVER_TWO);
        xAxis.y = Math.sin(alpha + CesiumMath.PI_OVER_TWO);
        xAxis.z = 0.0;

        var cosDec = Math.cos(delta);

        var zAxis = zAxisScratch;
        zAxis.x = cosDec * Math.cos(alpha);
        zAxis.y = cosDec * Math.sin(alpha);
        zAxis.z = Math.sin(delta);

        var yAxis = Cartesian3.cross(zAxis, xAxis, yAxisScratch);

        if (!defined(result)) {
            result = new Matrix3();
        }

        result[0] = xAxis.x;
        result[1] = yAxis.x;
        result[2] = zAxis.x;
        result[3] = xAxis.y;
        result[4] = yAxis.y;
        result[5] = zAxis.y;
        result[6] = xAxis.z;
        result[7] = yAxis.z;
        result[8] = zAxis.z;

        return result;
    }

    var rotMtxScratch = new Matrix3();
    var quatScratch = new Quaternion();

    /**
     * Computes a rotation from ICRF to a Globe's Fixed axes.
     *
     * @param {JulianDate} date The date to evaluate the matrix.
     * @param {Matrix3} result The object onto which to store the result.
     * @returns {Matrix} The modified result parameter or a new instance of the rotation from ICRF to Fixed.
     */
    IauOrientationAxes.prototype.evaluate = function(date, result) {
        if (!defined(date)) {
            date = JulianDate.now();
        }

        var alphaDeltaW = this._computeFunction(date);
        var precMtx = computeRotationMatrix(alphaDeltaW.rightAscension, alphaDeltaW.declination, result);

        var rot = CesiumMath.zeroToTwoPi(alphaDeltaW.rotation);
        var quat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, rot, quatScratch);
        var rotMtx = Matrix3.fromQuaternion(Quaternion.conjugate(quat, quat), rotMtxScratch);

        var cbi2cbf = Matrix3.multiply(rotMtx, precMtx, precMtx);
        return cbi2cbf;
    };

    /**
     * A function that computes the {@link IauOrientationParameters} for a {@link JulianDate}.
     * @callback IauOrientationAxes~ComputeFunction
     * @param {JulianDate} date The date to evaluate the parameters.
     * @returns {IauOrientationParameters} The orientation parameters.
     */

    return IauOrientationAxes;
});

/*global define*/
define('Core/InterpolationAlgorithm',[
        './DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * The interface for interpolation algorithms.
     *
     * @exports InterpolationAlgorithm
     *
     * @see LagrangePolynomialApproximation
     * @see LinearApproximation
     * @see HermitePolynomialApproximation
     */
    var InterpolationAlgorithm = {};

    /**
     * Gets the name of this interpolation algorithm.
     * @type {String}
     */
    InterpolationAlgorithm.type = undefined;

    /**
     * Given the desired degree, returns the number of data points required for interpolation.
     * @function
     *
     * @param {Number} degree The desired degree of interpolation.
     * @returns {Number} The number of required data points needed for the desired degree of interpolation.
     */
    InterpolationAlgorithm.getRequiredDataPoints = DeveloperError.throwInstantiationError;

    /**
     * Performs zero order interpolation.
     * @function
     *
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number[]} [result] An existing array into which to store the result.
     *
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    InterpolationAlgorithm.interpolateOrderZero = DeveloperError.throwInstantiationError;

    /**
     * Performs higher order interpolation.  Not all interpolators need to support high-order interpolation,
     * if this function remains undefined on implementing objects, interpolateOrderZero will be used instead.
     * @function
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number} inputOrder The number of derivatives supplied for input.
     * @param {Number} outputOrder The number of derivatives desired for output.
     * @param {Number[]} [result] An existing array into which to store the result.
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    InterpolationAlgorithm.interpolate = DeveloperError.throwInstantiationError;

    return InterpolationAlgorithm;
});

/*global define*/
define('Core/TimeInterval',[
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './freezeObject',
        './JulianDate'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        freezeObject,
        JulianDate) {
    'use strict';

    /**
     * An interval defined by a start and a stop time; optionally including those times as part of the interval.
     * Arbitrary data can optionally be associated with each instance for used with {@link TimeIntervalCollection}.
     *
     * @alias TimeInterval
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {JulianDate} [options.start=new JulianDate()] The start time of the interval.
     * @param {JulianDate} [options.stop=new JulianDate()] The stop time of the interval.
     * @param {Boolean} [options.isStartIncluded=true] <code>true</code> if <code>options.start</code> is included in the interval, <code>false</code> otherwise.
     * @param {Boolean} [options.isStopIncluded=true] <code>true</code> if <code>options.stop</code> is included in the interval, <code>false</code> otherwise.
     * @param {Object} [options.data] Arbitrary data associated with this interval.
     *
     * @example
     * // Create an instance that spans August 1st, 1980 and is associated
     * // with a Cartesian position.
     * var timeInterval = new Cesium.TimeInterval({
     *     start : Cesium.JulianDate.fromIso8601('1980-08-01T00:00:00Z'),
     *     stop : Cesium.JulianDate.fromIso8601('1980-08-02T00:00:00Z'),
     *     isStartIncluded : true,
     *     isStopIncluded : false,
     *     data : Cesium.Cartesian3.fromDegrees(39.921037, -75.170082)
     * });
     *
     * @example
     * // Create two instances from ISO 8601 intervals with associated numeric data
     * // then compute their intersection, summing the data they contain.
     * var left = Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2000/2010',
     *     data : 2
     * });
     *
     * var right = Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '1995/2005',
     *     data : 3
     * });
     *
     * //The result of the below intersection will be an interval equivalent to
     * //var intersection = Cesium.TimeInterval.fromIso8601({
     * //  iso8601 : '2000/2005',
     * //  data : 5
     * //});
     * var intersection = new Cesium.TimeInterval();
     * Cesium.TimeInterval.intersect(left, right, intersection, function(leftData, rightData) {
     *     return leftData + rightData;
     * });
     *
     * @example
     * // Check if an interval contains a specific time.
     * var dateToCheck = Cesium.JulianDate.fromIso8601('1982-09-08T11:30:00Z');
     * var containsDate = Cesium.TimeInterval.contains(timeInterval, dateToCheck);
     */
    function TimeInterval(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        /**
         * Gets or sets the start time of this interval.
         * @type {JulianDate}
         */
        this.start = defined(options.start) ? JulianDate.clone(options.start) : new JulianDate();

        /**
         * Gets or sets the stop time of this interval.
         * @type {JulianDate}
         */
        this.stop = defined(options.stop) ? JulianDate.clone(options.stop) : new JulianDate();

        /**
         * Gets or sets the data associated with this interval.
         * @type {Object}
         */
        this.data = options.data;

        /**
         * Gets or sets whether or not the start time is included in this interval.
         * @type {Boolean}
         * @default true
         */
        this.isStartIncluded = defaultValue(options.isStartIncluded, true);

        /**
         * Gets or sets whether or not the stop time is included in this interval.
         * @type {Boolean}
         * @default true
         */
        this.isStopIncluded = defaultValue(options.isStopIncluded, true);
    }

    defineProperties(TimeInterval.prototype, {
        /**
         * Gets whether or not this interval is empty.
         * @memberof TimeInterval.prototype
         * @type {Boolean}
         * @readonly
         */
        isEmpty : {
            get : function() {
                var stopComparedToStart = JulianDate.compare(this.stop, this.start);
                return stopComparedToStart < 0 || (stopComparedToStart === 0 && (!this.isStartIncluded || !this.isStopIncluded));
            }
        }
    });

    var scratchInterval = {
        start : undefined,
        stop : undefined,
        isStartIncluded : undefined,
        isStopIncluded : undefined,
        data : undefined
    };

    /**
     * Creates a new instance from an {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601} interval.
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.iso8601 An ISO 8601 interval.
     * @param {Boolean} [options.isStartIncluded=true] <code>true</code> if <code>options.start</code> is included in the interval, <code>false</code> otherwise.
     * @param {Boolean} [options.isStopIncluded=true] <code>true</code> if <code>options.stop</code> is included in the interval, <code>false</code> otherwise.
     * @param {Object} [options.data] Arbitrary data associated with this interval.
     * @param {TimeInterval} [result] An existing instance to use for the result.
     * @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
     */
    TimeInterval.fromIso8601 = function(options, result) {
                if (!defined(options)) {
            throw new DeveloperError('options is required.');
        }
        if (!defined(options.iso8601)) {
            throw new DeveloperError('options.iso8601 is required.');
        }
        
        var dates = options.iso8601.split('/');
        var start = JulianDate.fromIso8601(dates[0]);
        var stop = JulianDate.fromIso8601(dates[1]);
        var isStartIncluded = defaultValue(options.isStartIncluded, true);
        var isStopIncluded = defaultValue(options.isStopIncluded, true);
        var data = options.data;

        if (!defined(result)) {
            scratchInterval.start = start;
            scratchInterval.stop = stop;
            scratchInterval.isStartIncluded = isStartIncluded;
            scratchInterval.isStopIncluded = isStopIncluded;
            scratchInterval.data = data;
            return new TimeInterval(scratchInterval);
        }

        result.start = start;
        result.stop = stop;
        result.isStartIncluded = isStartIncluded;
        result.isStopIncluded = isStopIncluded;
        result.data = data;
        return result;
    };

    /**
     * Creates an ISO8601 representation of the provided interval.
     *
     * @param {TimeInterval} timeInterval The interval to be converted.
     * @param {Number} [precision] The number of fractional digits used to represent the seconds component.  By default, the most precise representation is used.
     * @returns {String} The ISO8601 representation of the provided interval.
     */
    TimeInterval.toIso8601 = function(timeInterval, precision) {
                if (!defined(timeInterval)) {
            throw new DeveloperError('timeInterval is required.');
        }
        
        return JulianDate.toIso8601(timeInterval.start, precision) + '/' + JulianDate.toIso8601(timeInterval.stop, precision);
    };

    /**
     * Duplicates the provided instance.
     *
     * @param {TimeInterval} [timeInterval] The instance to clone.
     * @param {TimeInterval} [result] An existing instance to use for the result.
     * @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
     */
    TimeInterval.clone = function(timeInterval, result) {
        if (!defined(timeInterval)) {
            return undefined;
        }
        if (!defined(result)) {
            return new TimeInterval(timeInterval);
        }
        result.start = timeInterval.start;
        result.stop = timeInterval.stop;
        result.isStartIncluded = timeInterval.isStartIncluded;
        result.isStopIncluded = timeInterval.isStopIncluded;
        result.data = timeInterval.data;
        return result;
    };

    /**
     * Compares two instances and returns <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {TimeInterval} [left] The first instance.
     * @param {TimeInterval} [right] The second instance.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     * @returns {Boolean} <code>true</code> if the dates are equal; otherwise, <code>false</code>.
     */
    TimeInterval.equals = function(left, right, dataComparer) {
        return left === right ||
               defined(left) && defined(right) &&
               (left.isEmpty && right.isEmpty ||
                left.isStartIncluded === right.isStartIncluded &&
                left.isStopIncluded === right.isStopIncluded &&
                JulianDate.equals(left.start, right.start) &&
                JulianDate.equals(left.stop, right.stop) &&
                (left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data))));
    };

    /**
     * Compares two instances and returns <code>true</code> if they are within <code>epsilon</code> seconds of
     * each other.  That is, in order for the dates to be considered equal (and for
     * this function to return <code>true</code>), the absolute value of the difference between them, in
     * seconds, must be less than <code>epsilon</code>.
     *
     * @param {TimeInterval} [left] The first instance.
     * @param {TimeInterval} [right] The second instance.
     * @param {Number} epsilon The maximum number of seconds that should separate the two instances.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     * @returns {Boolean} <code>true</code> if the two dates are within <code>epsilon</code> seconds of each other; otherwise <code>false</code>.
     */
    TimeInterval.equalsEpsilon = function(left, right, epsilon, dataComparer) {
                if (typeof epsilon !== 'number') {
            throw new DeveloperError('epsilon is required and must be a number.');
        }
        
        return left === right ||
               defined(left) && defined(right) &&
               (left.isEmpty && right.isEmpty ||
                left.isStartIncluded === right.isStartIncluded &&
                left.isStopIncluded === right.isStopIncluded &&
                JulianDate.equalsEpsilon(left.start, right.start, epsilon) &&
                JulianDate.equalsEpsilon(left.stop, right.stop, epsilon) &&
                (left.data === right.data || (defined(dataComparer) && dataComparer(left.data, right.data))));
    };

    /**
     * Computes the intersection of two intervals, optionally merging their data.
     *
     * @param {TimeInterval} left The first interval.
     * @param {TimeInterval} [right] The second interval.
     * @param {TimeInterval} result An existing instance to use for the result.
     * @param {TimeInterval~MergeCallback} [mergeCallback] A function which merges the data of the two intervals. If omitted, the data from the left interval will be used.
     * @returns {TimeInterval} The modified result parameter.
     */
    TimeInterval.intersect = function(left, right, result, mergeCallback) {
                if (!defined(left)) {
            throw new DeveloperError('left is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        if (!defined(right)) {
            return TimeInterval.clone(TimeInterval.EMPTY, result);
        }

        var leftStart = left.start;
        var leftStop = left.stop;

        var rightStart = right.start;
        var rightStop = right.stop;

        var intersectsStartRight = JulianDate.greaterThanOrEquals(rightStart, leftStart) && JulianDate.greaterThanOrEquals(leftStop, rightStart);
        var intersectsStartLeft = !intersectsStartRight && JulianDate.lessThanOrEquals(rightStart, leftStart) && JulianDate.lessThanOrEquals(leftStart, rightStop);

        if (!intersectsStartRight && !intersectsStartLeft) {
            return TimeInterval.clone(TimeInterval.EMPTY, result);
        }

        var leftIsStartIncluded = left.isStartIncluded;
        var leftIsStopIncluded = left.isStopIncluded;
        var rightIsStartIncluded = right.isStartIncluded;
        var rightIsStopIncluded = right.isStopIncluded;
        var leftLessThanRight = JulianDate.lessThan(leftStop, rightStop);

        result.start = intersectsStartRight ? rightStart : leftStart;
        result.isStartIncluded = (leftIsStartIncluded && rightIsStartIncluded) || (!JulianDate.equals(rightStart, leftStart) && ((intersectsStartRight && rightIsStartIncluded) || (intersectsStartLeft && leftIsStartIncluded)));
        result.stop = leftLessThanRight ? leftStop : rightStop;
        result.isStopIncluded = leftLessThanRight ? leftIsStopIncluded : (leftIsStopIncluded && rightIsStopIncluded) || (!JulianDate.equals(rightStop, leftStop) && rightIsStopIncluded);
        result.data = defined(mergeCallback) ? mergeCallback(left.data, right.data) : left.data;
        return result;
    };

    /**
     * Checks if the specified date is inside the provided interval.
     *
     * @param {TimeInterval} timeInterval The interval.
     * @param {JulianDate} julianDate The date to check.
     * @returns {Boolean} <code>true</code> if the interval contains the specified date, <code>false</code> otherwise.
     */
    TimeInterval.contains = function(timeInterval, julianDate) {
                if (!defined(timeInterval)) {
            throw new DeveloperError('timeInterval is required.');
        }
        if (!defined(julianDate)) {
            throw new DeveloperError('julianDate is required.');
        }
        
        if (timeInterval.isEmpty) {
            return false;
        }

        var startComparedToDate = JulianDate.compare(timeInterval.start, julianDate);
        if (startComparedToDate === 0) {
            return timeInterval.isStartIncluded;
        }

        var dateComparedToStop = JulianDate.compare(julianDate, timeInterval.stop);
        if (dateComparedToStop === 0) {
            return timeInterval.isStopIncluded;
        }

        return startComparedToDate < 0 && dateComparedToStop < 0;
    };

    /**
     * Duplicates this instance.
     *
     * @param {TimeInterval} [result] An existing instance to use for the result.
     * @returns {TimeInterval} The modified result parameter or a new instance if none was provided.
     */
    TimeInterval.prototype.clone = function(result) {
        return TimeInterval.clone(this, result);
    };

    /**
     * Compares this instance against the provided instance componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {TimeInterval} [right] The right hand side interval.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    TimeInterval.prototype.equals = function(right, dataComparer) {
        return TimeInterval.equals(this, right, dataComparer);
    };

    /**
     * Compares this instance against the provided instance componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {TimeInterval} [right] The right hand side interval.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    TimeInterval.prototype.equalsEpsilon = function(right, epsilon, dataComparer) {
        return TimeInterval.equalsEpsilon(this, right, epsilon, dataComparer);
    };

    /**
     * Creates a string representing this TimeInterval in ISO8601 format.
     *
     * @returns {String} A string representing this TimeInterval in ISO8601 format.
     */
    TimeInterval.prototype.toString = function() {
        return TimeInterval.toIso8601(this);
    };

    /**
     * An immutable empty interval.
     *
     * @type {TimeInterval}
     * @constant
     */
    TimeInterval.EMPTY = freezeObject(new TimeInterval({
        start : new JulianDate(),
        stop : new JulianDate(),
        isStartIncluded : false,
        isStopIncluded : false
    }));

    /**
     * Function interface for merging interval data.
     * @callback TimeInterval~MergeCallback
     *
     * @param {Object} leftData The first data instance.
     * @param {Object} rightData The second data instance.
     * @returns {Object} The result of merging the two data instances.
     */

    /**
     * Function interface for comparing interval data.
     * @callback TimeInterval~DataComparer
     * @param {Object} leftData The first data instance.
     * @param {Object} rightData The second data instance.
     * @returns {Boolean} <code>true</code> if the provided instances are equal, <code>false</code> otherwise.
     */

    return TimeInterval;
});

/*global define*/
define('Core/Iso8601',[
        './freezeObject',
        './JulianDate',
        './TimeInterval'
    ], function(
        freezeObject,
        JulianDate,
        TimeInterval) {
    'use strict';

    var MINIMUM_VALUE = freezeObject(JulianDate.fromIso8601('0000-01-01T00:00:00Z'));
    var MAXIMUM_VALUE = freezeObject(JulianDate.fromIso8601('9999-12-31T24:00:00Z'));
    var MAXIMUM_INTERVAL = freezeObject(new TimeInterval({
        start : MINIMUM_VALUE,
        stop : MAXIMUM_VALUE
    }));

    /**
     * Constants related to ISO8601 support.
     *
     * @exports Iso8601
     *
     * @see {@link http://en.wikipedia.org/wiki/ISO_8601|ISO 8601 on Wikipedia}
     * @see JulianDate
     * @see TimeInterval
     */
    var Iso8601 = {
        /**
         * A {@link JulianDate} representing the earliest time representable by an ISO8601 date.
         * This is equivalent to the date string '0000-01-01T00:00:00Z'
         */
        MINIMUM_VALUE : MINIMUM_VALUE,

        /**
         * A {@link JulianDate} representing the latest time representable by an ISO8601 date.
         * This is equivalent to the date string '9999-12-31T24:00:00Z'
         */
        MAXIMUM_VALUE : MAXIMUM_VALUE,

        /**
         * A {@link TimeInterval} representing the largest interval representable by an ISO8601 interval.
         * This is equivalent to the interval string '0000-01-01T00:00:00Z/9999-12-31T24:00:00Z'
         */
        MAXIMUM_INTERVAL : MAXIMUM_INTERVAL
    };

    return Iso8601;
});

/*global define*/
define('Core/KeyboardEventModifier',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * This enumerated type is for representing keyboard modifiers. These are keys
     * that are held down in addition to other event types.
     *
     * @exports KeyboardEventModifier
     */
    var KeyboardEventModifier = {
        /**
         * Represents the shift key being held down.
         *
         * @type {Number}
         * @constant
         */
        SHIFT : 0,

        /**
         * Represents the control key being held down.
         *
         * @type {Number}
         * @constant
         */
        CTRL : 1,

        /**
         * Represents the alt key being held down.
         *
         * @type {Number}
         * @constant
         */
        ALT : 2
    };

    return freezeObject(KeyboardEventModifier);
});

/*global define*/
define('Core/LagrangePolynomialApproximation',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * An {@link InterpolationAlgorithm} for performing Lagrange interpolation.
     *
     * @exports LagrangePolynomialApproximation
     */
    var LagrangePolynomialApproximation = {
        type : 'Lagrange'
    };

    /**
     * Given the desired degree, returns the number of data points required for interpolation.
     *
     * @param {Number} degree The desired degree of interpolation.
     * @returns {Number} The number of required data points needed for the desired degree of interpolation.
     */
    LagrangePolynomialApproximation.getRequiredDataPoints = function(degree) {
        return Math.max(degree + 1.0, 2);
    };

    /**
     * Interpolates values using Lagrange Polynomial Approximation.
     *
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number[]} [result] An existing array into which to store the result.
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    LagrangePolynomialApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
        if (!defined(result)) {
            result = new Array(yStride);
        }

        var i;
        var j;
        var length = xTable.length;

        for (i = 0; i < yStride; i++) {
            result[i] = 0;
        }

        for (i = 0; i < length; i++) {
            var coefficient = 1;

            for (j = 0; j < length; j++) {
                if (j !== i) {
                    var diffX = xTable[i] - xTable[j];
                    coefficient *= (x - xTable[j]) / diffX;
                }
            }

            for (j = 0; j < yStride; j++) {
                result[j] += coefficient * yTable[i * yStride + j];
            }
        }

        return result;
    };

    return LagrangePolynomialApproximation;
});

/*global define*/
define('Core/LinearApproximation',[
        './defined',
        './DeveloperError'
    ], function(
        defined,
        DeveloperError) {
    'use strict';

    /**
     * An {@link InterpolationAlgorithm} for performing linear interpolation.
     *
     * @exports LinearApproximation
     */
    var LinearApproximation = {
        type : 'Linear'
    };

    /**
     * Given the desired degree, returns the number of data points required for interpolation.
     * Since linear interpolation can only generate a first degree polynomial, this function
     * always returns 2.
     * @param {Number} degree The desired degree of interpolation.
     * @returns {Number} This function always returns 2.
     *
     */
    LinearApproximation.getRequiredDataPoints = function(degree) {
        return 2;
    };

    /**
     * Interpolates values using linear approximation.
     *
     * @param {Number} x The independent variable for which the dependent variables will be interpolated.
     * @param {Number[]} xTable The array of independent variables to use to interpolate.  The values
     * in this array must be in increasing order and the same value must not occur twice in the array.
     * @param {Number[]} yTable The array of dependent variables to use to interpolate.  For a set of three
     * dependent values (p,q,w) at time 1 and time 2 this should be as follows: {p1, q1, w1, p2, q2, w2}.
     * @param {Number} yStride The number of dependent variable values in yTable corresponding to
     * each independent variable value in xTable.
     * @param {Number[]} [result] An existing array into which to store the result.
     * @returns {Number[]} The array of interpolated values, or the result parameter if one was provided.
     */
    LinearApproximation.interpolateOrderZero = function(x, xTable, yTable, yStride, result) {
                if (xTable.length !== 2) {
            throw new DeveloperError('The xTable provided to the linear interpolator must have exactly two elements.');
        } else if (yStride <= 0) {
            throw new DeveloperError('There must be at least 1 dependent variable for each independent variable.');
        }
        
        if (!defined(result)) {
            result = new Array(yStride);
        }

        var i;
        var y0;
        var y1;
        var x0 = xTable[0];
        var x1 = xTable[1];

                if (x0 === x1) {
            throw new DeveloperError('Divide by zero error: xTable[0] and xTable[1] are equal');
        }
        
        for (i = 0; i < yStride; i++) {
            y0 = yTable[i];
            y1 = yTable[i + yStride];
            result[i] = (((y1 - y0) * x) + (x1 * y0) - (x0 * y1)) / (x1 - x0);
        }

        return result;
    };

    return LinearApproximation;
});

/*global define*/
define('Core/loadBlob',[
        './loadWithXhr'
    ], function(
        loadWithXhr) {
    'use strict';

    /**
     * Asynchronously loads the given URL as a blob.  Returns a promise that will resolve to
     * a Blob once loaded, or reject if the URL failed to load.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     *
     * @exports loadBlob
     *
     * @param {String|Promise.<String>} url The URL of the data, or a promise for the URL.
     * @param {Object} [headers] HTTP headers to send with the requests.
     * @returns {Promise.<Blob>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load a single URL asynchronously
     * Cesium.loadBlob('some/url').then(function(blob) {
     *     // use the data
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadBlob(url, headers) {
        return loadWithXhr({
            url : url,
            responseType : 'blob',
            headers : headers
        });
    }

    return loadBlob;
});

/*global define*/
define('Core/loadCRN',[
    './CompressedTextureBuffer',
    './defined',
    './DeveloperError',
    './loadArrayBuffer',
    './TaskProcessor',
    '../ThirdParty/when'
], function(
    CompressedTextureBuffer,
    defined,
    DeveloperError,
    loadArrayBuffer,
    TaskProcessor,
    when) {
    'use strict';

    var transcodeTaskProcessor = new TaskProcessor('transcodeCRNToDXT', Number.POSITIVE_INFINITY);

    /**
     * Asynchronously loads and parses the given URL to a CRN file or parses the raw binary data of a CRN file.
     * Returns a promise that will resolve to an object containing the image buffer, width, height and format once loaded,
     * or reject if the URL failed to load or failed to parse the data.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     *
     * @exports loadCRN
     *
     * @param {String|Promise.<String>|ArrayBuffer} urlOrBuffer The URL of the binary data, a promise for the URL, or an ArrayBuffer.
     * @param {Object} [headers] HTTP headers to send with the requests.
     * @returns {Promise.<CompressedTextureBuffer>} A promise that will resolve to the requested data when loaded.
     *
     * @exception {RuntimeError} Unsupported compressed format.
     *
     * @example
     * // load a single URL asynchronously
     * Cesium.loadCRN('some/url').then(function(textureData) {
     *     var width = textureData.width;
     *     var height = textureData.height;
     *     var format = textureData.internalFormat;
     *     var arrayBufferView = textureData.bufferView;
     *     // use the data to create a texture
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     *
     * @see {@link https://github.com/BinomialLLC/crunch|crunch DXTc texture compression and transcoding library}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadCRN(urlOrBuffer, headers) {
                if (!defined(urlOrBuffer)) {
            throw new DeveloperError('urlOrBuffer is required.');
        }
        
        var loadPromise;
        if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) {
            loadPromise = when.resolve(urlOrBuffer);
        } else {
            loadPromise = loadArrayBuffer(urlOrBuffer, headers);
        }

        return loadPromise.then(function(data) {
            var transferrableObjects = [];
            if (data instanceof ArrayBuffer) {
                transferrableObjects.push(data);
            } else if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
                transferrableObjects.push(data.buffer);
            } else {
                // data is a view of an array buffer. need to copy so it is transferrable to web worker
                data = data.slice(0, data.length);
                transferrableObjects.push(data.buffer);
            }

            return transcodeTaskProcessor.scheduleTask(data, transferrableObjects);
        }).then(function(compressedTextureBuffer) {
            return CompressedTextureBuffer.clone(compressedTextureBuffer);
        });
    }

    return loadCRN;
});
/*global define*/
define('Core/loadImageFromTypedArray',[
        '../ThirdParty/when',
        './defined',
        './DeveloperError',
        './loadImage'
    ], function(
        when,
        defined,
        DeveloperError,
        loadImage) {
    'use strict';

    /**
     * @private
     */
    function loadImageFromTypedArray(uint8Array, format) {
                if (!defined(uint8Array)) {
            throw new DeveloperError('uint8Array is required.');
        }

        if (!defined(format)) {
            throw new DeveloperError('format is required.');
        }
        
        var blob = new Blob([uint8Array], {
            type : format
        });

        var blobUrl = window.URL.createObjectURL(blob);
        return loadImage(blobUrl, false).then(function(image) {
            window.URL.revokeObjectURL(blobUrl);
            return image;
        }, function(error) {
            window.URL.revokeObjectURL(blobUrl);
            return when.reject(error);
        });
    }

    return loadImageFromTypedArray;
});

/*global define*/
define('Core/loadImageViaBlob',[
        '../ThirdParty/when',
        './loadBlob',
        './loadImage'
    ], function(
        when,
        loadBlob,
        loadImage) {
    'use strict';

    var dataUriRegex = /^data:/;

    /**
     * Asynchronously loads the given image URL by first downloading it as a blob using
     * XMLHttpRequest and then loading the image from the buffer via a blob URL.
     * This allows access to more information that is not accessible via normal
     * Image-based downloading, such as the size of the response.  This function
     * returns a promise that will resolve to
     * an {@link Image} once loaded, or reject if the image failed to load.  The
     * returned image will have a "blob" property with the Blob itself.  If the browser
     * does not support an XMLHttpRequests with a responseType of 'blob', or if the
     * provided URI is a data URI, this function is equivalent to calling {@link loadImage},
     * and the extra blob property will not be present.
     *
     * @exports loadImageViaBlob
     *
     * @param {String|Promise.<String>} url The source of the image, or a promise for the URL.
     * @returns {Promise.<Image>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load a single image asynchronously
     * Cesium.loadImageViaBlob('some/image/url.png').then(function(image) {
     *     var blob = image.blob;
     *     // use the loaded image or XHR
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     *
     * // load several images in parallel
     * when.all([loadImageViaBlob('image1.png'), loadImageViaBlob('image2.png')]).then(function(images) {
     *     // images is an array containing all the loaded images
     * });
     * 
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadImageViaBlob(url) {
        if (dataUriRegex.test(url)) {
            return loadImage(url);
        }

        return loadBlob(url).then(function(blob) {
            var blobUrl = window.URL.createObjectURL(blob);

            return loadImage(blobUrl, false).then(function(image) {
                image.blob = blob;
                window.URL.revokeObjectURL(blobUrl);
                return image;
            }, function(error) {
                window.URL.revokeObjectURL(blobUrl);
                return when.reject(error);
            });
        });
    }

    var xhrBlobSupported = (function() {
        try {
            var xhr = new XMLHttpRequest();
            xhr.open('GET', '#', true);
            xhr.responseType = 'blob';
            return xhr.responseType === 'blob';
        } catch (e) {
            return false;
        }
    })();

    return xhrBlobSupported ? loadImageViaBlob : loadImage;
});

/*global define*/
define('Core/PixelFormat',[
        './freezeObject',
        './WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * The format of a pixel, i.e., the number of components it has and what they represent.
     *
     * @exports PixelFormat
     */
    var PixelFormat = {
        /**
         * A pixel format containing a depth value.
         *
         * @type {Number}
         * @constant
         */
        DEPTH_COMPONENT : WebGLConstants.DEPTH_COMPONENT,

        /**
         * A pixel format containing a depth and stencil value, most often used with {@link PixelDatatype.UNSIGNED_INT_24_8}.
         *
         * @type {Number}
         * @constant
         */
        DEPTH_STENCIL : WebGLConstants.DEPTH_STENCIL,

        /**
         * A pixel format containing an alpha channel.
         *
         * @type {Number}
         * @constant
         */
        ALPHA : WebGLConstants.ALPHA,

        /**
         * A pixel format containing red, green, and blue channels.
         *
         * @type {Number}
         * @constant
         */
        RGB : WebGLConstants.RGB,

        /**
         * A pixel format containing red, green, blue, and alpha channels.
         *
         * @type {Number}
         * @constant
         */
        RGBA : WebGLConstants.RGBA,

        /**
         * A pixel format containing a luminance (intensity) channel.
         *
         * @type {Number}
         * @constant
         */
        LUMINANCE : WebGLConstants.LUMINANCE,

        /**
         * A pixel format containing luminance (intensity) and alpha channels.
         *
         * @type {Number}
         * @constant
         */
        LUMINANCE_ALPHA : WebGLConstants.LUMINANCE_ALPHA,

        /**
         * A pixel format containing red, green, and blue channels that is DXT1 compressed.
         *
         * @type {Number}
         * @constant
         */
        RGB_DXT1 : WebGLConstants.COMPRESSED_RGB_S3TC_DXT1_EXT,

        /**
         * A pixel format containing red, green, blue, and alpha channels that is DXT1 compressed.
         *
         * @type {Number}
         * @constant
         */
        RGBA_DXT1 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT1_EXT,

        /**
         * A pixel format containing red, green, blue, and alpha channels that is DXT3 compressed.
         *
         * @type {Number}
         * @constant
         */
        RGBA_DXT3 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT3_EXT,

        /**
         * A pixel format containing red, green, blue, and alpha channels that is DXT5 compressed.
         *
         * @type {Number}
         * @constant
         */
        RGBA_DXT5 : WebGLConstants.COMPRESSED_RGBA_S3TC_DXT5_EXT,

        /**
         * A pixel format containing red, green, and blue channels that is PVR 4bpp compressed.
         *
         * @type {Number}
         * @constant
         */
        RGB_PVRTC_4BPPV1 : WebGLConstants.COMPRESSED_RGB_PVRTC_4BPPV1_IMG,

        /**
         * A pixel format containing red, green, and blue channels that is PVR 2bpp compressed.
         *
         * @type {Number}
         * @constant
         */
        RGB_PVRTC_2BPPV1 : WebGLConstants.COMPRESSED_RGB_PVRTC_2BPPV1_IMG,

        /**
         * A pixel format containing red, green, blue, and alpha channels that is PVR 4bpp compressed.
         *
         * @type {Number}
         * @constant
         */
        RGBA_PVRTC_4BPPV1 : WebGLConstants.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,

        /**
         * A pixel format containing red, green, blue, and alpha channels that is PVR 2bpp compressed.
         *
         * @type {Number}
         * @constant
         */
        RGBA_PVRTC_2BPPV1 : WebGLConstants.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG,

        /**
         * A pixel format containing red, green, and blue channels that is ETC1 compressed.
         *
         * @type {Number}
         * @constant
         */
        RGB_ETC1 : WebGLConstants.COMPRESSED_RGB_ETC1_WEBGL,

        /**
         * @private
         */
        validate : function(pixelFormat) {
            return pixelFormat === PixelFormat.DEPTH_COMPONENT ||
                   pixelFormat === PixelFormat.DEPTH_STENCIL ||
                   pixelFormat === PixelFormat.ALPHA ||
                   pixelFormat === PixelFormat.RGB ||
                   pixelFormat === PixelFormat.RGBA ||
                   pixelFormat === PixelFormat.LUMINANCE ||
                   pixelFormat === PixelFormat.LUMINANCE_ALPHA ||
                   pixelFormat === PixelFormat.RGB_DXT1 ||
                   pixelFormat === PixelFormat.RGBA_DXT1 ||
                   pixelFormat === PixelFormat.RGBA_DXT3 ||
                   pixelFormat === PixelFormat.RGBA_DXT5 ||
                   pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 ||
                   pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 ||
                   pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 ||
                   pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1 ||
                   pixelFormat === PixelFormat.RGB_ETC1;
        },

        /**
         * @private
         */
        isColorFormat : function(pixelFormat) {
            return pixelFormat === PixelFormat.ALPHA ||
                   pixelFormat === PixelFormat.RGB ||
                   pixelFormat === PixelFormat.RGBA ||
                   pixelFormat === PixelFormat.LUMINANCE ||
                   pixelFormat === PixelFormat.LUMINANCE_ALPHA;
        },

        /**
         * @private
         */
        isDepthFormat : function(pixelFormat) {
            return pixelFormat === PixelFormat.DEPTH_COMPONENT ||
                   pixelFormat === PixelFormat.DEPTH_STENCIL;
        },

        /**
         * @private
         */
        isCompressedFormat : function(pixelFormat) {
            return pixelFormat === PixelFormat.RGB_DXT1 ||
                   pixelFormat === PixelFormat.RGBA_DXT1 ||
                   pixelFormat === PixelFormat.RGBA_DXT3 ||
                   pixelFormat === PixelFormat.RGBA_DXT5 ||
                   pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 ||
                   pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 ||
                   pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 ||
                   pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1 ||
                   pixelFormat === PixelFormat.RGB_ETC1;
        },

        /**
         * @private
         */
        isDXTFormat : function(pixelFormat) {
            return pixelFormat === PixelFormat.RGB_DXT1 ||
                   pixelFormat === PixelFormat.RGBA_DXT1 ||
                   pixelFormat === PixelFormat.RGBA_DXT3 ||
                   pixelFormat === PixelFormat.RGBA_DXT5;
        },

        /**
         * @private
         */
        isPVRTCFormat : function(pixelFormat) {
            return pixelFormat === PixelFormat.RGB_PVRTC_4BPPV1 ||
                   pixelFormat === PixelFormat.RGB_PVRTC_2BPPV1 ||
                   pixelFormat === PixelFormat.RGBA_PVRTC_4BPPV1 ||
                   pixelFormat === PixelFormat.RGBA_PVRTC_2BPPV1;
        },

        /**
         * @private
         */
        isETC1Format : function(pixelFormat) {
            return pixelFormat === PixelFormat.RGB_ETC1;
        },

        /**
         * @private
         */
        compressedTextureSize : function(pixelFormat, width, height) {
            switch (pixelFormat) {
                case PixelFormat.RGB_DXT1:
                case PixelFormat.RGBA_DXT1:
                case PixelFormat.RGB_ETC1:
                    return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 8;

                case PixelFormat.RGBA_DXT3:
                case PixelFormat.RGBA_DXT5:
                    return Math.floor((width + 3) / 4) * Math.floor((height + 3) / 4) * 16;

                case PixelFormat.RGB_PVRTC_4BPPV1:
                case PixelFormat.RGBA_PVRTC_4BPPV1:
                    return Math.floor((Math.max(width, 8) * Math.max(height, 8) * 4 + 7) / 8);

                case PixelFormat.RGB_PVRTC_2BPPV1:
                case PixelFormat.RGBA_PVRTC_2BPPV1:
                    return Math.floor((Math.max(width, 16) * Math.max(height, 8) * 2 + 7) / 8);

                default:
                    return 0;
            }
        }
    };

    return freezeObject(PixelFormat);
});

/*global define*/
define('Core/loadKTX',[
        '../ThirdParty/when',
        './CompressedTextureBuffer',
        './defined',
        './DeveloperError',
        './loadArrayBuffer',
        './PixelFormat',
        './RuntimeError'
    ], function(
        when,
        CompressedTextureBuffer,
        defined,
        DeveloperError,
        loadArrayBuffer,
        PixelFormat,
        RuntimeError) {
    'use strict';

    /**
     * Asynchronously loads and parses the given URL to a KTX file or parses the raw binary data of a KTX file.
     * Returns a promise that will resolve to an object containing the image buffer, width, height and format once loaded,
     * or reject if the URL failed to load or failed to parse the data.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     * <p>
     * The following are part of the KTX format specification but are not supported:
     * <ul>
     *     <li>Big-endian files</li>
     *     <li>Metadata</li>
     *     <li>3D textures</li>
     *     <li>Texture Arrays</li>
     *     <li>Cubemaps</li>
     *     <li>Mipmaps</li>
     * </ul>
     * </p>
     *
     * @exports loadKTX
     *
     * @param {String|Promise.<String>|ArrayBuffer} urlOrBuffer The URL of the binary data, a promise for the URL, or an ArrayBuffer.
     * @param {Object} [headers] HTTP headers to send with the requests.
     * @returns {Promise.<CompressedTextureBuffer>} A promise that will resolve to the requested data when loaded.
     *
     * @exception {RuntimeError} Invalid KTX file.
     * @exception {RuntimeError} File is the wrong endianness.
     * @exception {RuntimeError} glInternalFormat is not a valid format.
     * @exception {RuntimeError} glType must be zero when the texture is compressed.
     * @exception {RuntimeError} The type size for compressed textures must be 1.
     * @exception {RuntimeError} glFormat must be zero when the texture is compressed.
     * @exception {RuntimeError} Generating mipmaps for a compressed texture is unsupported.
     * @exception {RuntimeError} The base internal format must be the same as the format for uncompressed textures.
     * @exception {RuntimeError} 3D textures are not supported.
     * @exception {RuntimeError} Texture arrays are not supported.
     * @exception {RuntimeError} Cubemaps are not supported.
     *
     * @example
     * // load a single URL asynchronously
     * Cesium.loadKTX('some/url').then(function(ktxData) {
     *     var width = ktxData.width;
     *     var height = ktxData.height;
     *     var format = ktxData.internalFormat;
     *     var arrayBufferView = ktxData.bufferView;
     *     // use the data to create a texture
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     *
     * @see {@link https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/|KTX file format}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadKTX(urlOrBuffer, headers) {
                if (!defined(urlOrBuffer)) {
            throw new DeveloperError('urlOrBuffer is required.');
        }
        
        var loadPromise;
        if (urlOrBuffer instanceof ArrayBuffer || ArrayBuffer.isView(urlOrBuffer)) {
            loadPromise = when.resolve(urlOrBuffer);
        } else {
            loadPromise = loadArrayBuffer(urlOrBuffer, headers);
        }

        return loadPromise.then(function(data) {
            return parseKTX(data);
        });
    }

    var fileIdentifier = [0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A];
    var endiannessTest = 0x04030201;

    var sizeOfUint32 = 4;

    function parseKTX(data) {
        var byteBuffer = new Uint8Array(data);

        var isKTX = true;
        for (var i = 0; i < fileIdentifier.length; ++i) {
            if (fileIdentifier[i] !== byteBuffer[i]) {
                isKTX = false;
                break;
            }
        }

        if (!isKTX) {
            throw new RuntimeError('Invalid KTX file.');
        }

        var view;
        var byteOffset;

        if (defined(data.buffer)) {
            view = new DataView(data.buffer);
            byteOffset = data.byteOffset;
        } else {
            view = new DataView(data);
            byteOffset = 0;
        }

        byteOffset += 12; // skip identifier

        var endianness = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        if (endianness !== endiannessTest) {
            throw new RuntimeError('File is the wrong endianness.');
        }

        var glType = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var glTypeSize = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var glFormat = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var glInternalFormat = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var glBaseInternalFormat = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var pixelWidth = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var pixelHeight = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var pixelDepth = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var numberOfArrayElements = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var numberOfFaces = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var numberOfMipmapLevels = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;
        var bytesOfKeyValueByteSize = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;

        // skip metadata
        byteOffset += bytesOfKeyValueByteSize;

        var imageSize = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32;

        var texture;
        if (defined(data.buffer)) {
            texture = new Uint8Array(data.buffer, byteOffset, imageSize);
        } else {
            texture = new Uint8Array(data, byteOffset, imageSize);
        }

        // Some tools use a sized internal format.
        // See table 2: https://www.opengl.org/sdk/docs/man/html/glTexImage2D.xhtml
        if (glInternalFormat === 0x8051) {         // GL_RGB8
            glInternalFormat = PixelFormat.RGB;
        } else if (glInternalFormat === 0x8058) {  // GL_RGBA8
            glInternalFormat = PixelFormat.RGBA;
        }

        if (!PixelFormat.validate(glInternalFormat)) {
            throw new RuntimeError('glInternalFormat is not a valid format.');
        }

        if (PixelFormat.isCompressedFormat(glInternalFormat)) {
            if (glType !== 0) {
                throw new RuntimeError('glType must be zero when the texture is compressed.');
            }
            if (glTypeSize !== 1) {
                throw new RuntimeError('The type size for compressed textures must be 1.');
            }
            if (glFormat !== 0) {
                throw new RuntimeError('glFormat must be zero when the texture is compressed.');
            }
            if (numberOfMipmapLevels === 0) {
                throw new RuntimeError('Generating mipmaps for a compressed texture is unsupported.');
            }
        } else {
            if (glBaseInternalFormat !== glFormat) {
                throw new RuntimeError('The base internal format must be the same as the format for uncompressed textures.');
            }
        }

        if (pixelDepth !== 0) {
            throw new RuntimeError('3D textures are unsupported.');
        }

        if (numberOfArrayElements !== 0) {
            throw new RuntimeError('Texture arrays are unsupported.');
        }
        if (numberOfFaces !== 1) {
            throw new RuntimeError('Cubemaps are unsupported.');
        }

        // Only use the level 0 mipmap
        if (PixelFormat.isCompressedFormat(glInternalFormat) && numberOfMipmapLevels > 1) {
            var levelSize = PixelFormat.compressedTextureSize(glInternalFormat, pixelWidth, pixelHeight);
            texture = texture.slice(0, levelSize);
        }

        return new CompressedTextureBuffer(glInternalFormat, pixelWidth, pixelHeight, texture);
    }

    return loadKTX;
});

/*global define*/
define('Core/loadXML',[
        './loadWithXhr'
    ], function(
        loadWithXhr) {
    'use strict';

    /**
     * Asynchronously loads the given URL as XML.  Returns a promise that will resolve to
     * an XML Document once loaded, or reject if the URL failed to load.  The data is loaded
     * using XMLHttpRequest, which means that in order to make requests to another origin,
     * the server must have Cross-Origin Resource Sharing (CORS) headers enabled.
     *
     * @exports loadXML
     *
     * @param {String|Promise.<String>} url The URL to request, or a promise for the URL.
     * @param {Object} [headers] HTTP headers to send with the request.
     * @returns {Promise.<XMLDocument>} a promise that will resolve to the requested data when loaded.
     *
     *
     * @example
     * // load XML from a URL, setting a custom header
     * Cesium.loadXML('http://someUrl.com/someXML.xml', {
     *   'X-Custom-Header' : 'some value'
     * }).then(function(document) {
     *     // Do something with the document
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest|XMLHttpRequest}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     */
    function loadXML(url, headers) {
        return loadWithXhr({
            url : url,
            responseType : 'document',
            headers : headers,
            overrideMimeType : 'text/xml'
        });
    }

    return loadXML;
});

/*global define*/
define('Core/MapboxApi',[
    './defined',
    './Credit'
], function(
    defined,
    Credit) {
    'use strict';

    var MapboxApi = {
    };

    /**
     * The default Mapbox API access token to use if one is not provided to the
     * constructor of an object that uses the Mapbox API.  If this property is undefined,
     * Cesium's default access token is used, which is only suitable for use early in development.
     * Please supply your own access token as soon as possible and prior to deployment.
     * Visit {@link https://www.mapbox.com/help/create-api-access-token/} for details.
     * When Cesium's default access token is used, a message is printed to the console the first
     * time the Mapbox API is used.
     *
     * @type {String}
     */
    MapboxApi.defaultAccessToken = undefined;

    var printedMapboxWarning = false;
    var errorCredit;
    var errorString = 'This application is using Cesium\'s default Mapbox access token.  Please create a new access token for the application as soon as possible and prior to deployment by visiting https://www.mapbox.com/account/apps/, and provide your token to Cesium by setting the Cesium.MapboxApi.defaultAccessToken property before constructing the CesiumWidget or any other object that uses the Mapbox API.';


    MapboxApi.getAccessToken = function(providedToken) {
        if (defined(providedToken)) {
            return providedToken;
        }

        if (!defined(MapboxApi.defaultAccessToken)) {
            if (!printedMapboxWarning) {
                console.log(errorString);
                printedMapboxWarning = true;
            }
            return 'pk.eyJ1IjoiYW5hbHl0aWNhbGdyYXBoaWNzIiwiYSI6ImNpd204Zm4wejAwNzYyeW5uNjYyZmFwdWEifQ.7i-VIZZWX8pd1bTfxIVj9g';
        }

        return MapboxApi.defaultAccessToken;
    };

    MapboxApi.getErrorCredit = function(providedToken) {
        if (defined(providedToken) || defined(MapboxApi.defaultAccessToken)) {
            return undefined;
        }

        if (!defined(errorCredit)) {
            errorCredit = new Credit(errorString);
        }

        return errorCredit;
    };

    return MapboxApi;
});

/*global define*/
define('Core/MapProjection',[
        './defineProperties',
        './DeveloperError'
    ], function(
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Defines how geodetic ellipsoid coordinates ({@link Cartographic}) project to a
     * flat map like Cesium's 2D and Columbus View modes.
     *
     * @alias MapProjection
     * @constructor
     *
     * @see GeographicProjection
     * @see WebMercatorProjection
     */
    function MapProjection() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(MapProjection.prototype, {
        /**
         * Gets the {@link Ellipsoid}.
         *
         * @memberof MapProjection.prototype
         *
         * @type {Ellipsoid}
         * @readonly
         */
        ellipsoid : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Projects {@link Cartographic} coordinates, in radians, to projection-specific map coordinates, in meters.
     *
     * @memberof MapProjection
     * @function
     *
     * @param {Cartographic} cartographic The coordinates to project.
     * @param {Cartesian3} [result] An instance into which to copy the result.  If this parameter is
     *        undefined, a new instance is created and returned.
     * @returns {Cartesian3} The projected coordinates.  If the result parameter is not undefined, the
     *          coordinates are copied there and that instance is returned.  Otherwise, a new instance is
     *          created and returned.
     */
    MapProjection.prototype.project = DeveloperError.throwInstantiationError;

    /**
     * Unprojects projection-specific map {@link Cartesian3} coordinates, in meters, to {@link Cartographic}
     * coordinates, in radians.
     *
     * @memberof MapProjection
     * @function
     *
     * @param {Cartesian3} cartesian The Cartesian position to unproject with height (z) in meters.
     * @param {Cartographic} [result] An instance into which to copy the result.  If this parameter is
     *        undefined, a new instance is created and returned.
     * @returns {Cartographic} The unprojected coordinates.  If the result parameter is not undefined, the
     *          coordinates are copied there and that instance is returned.  Otherwise, a new instance is
     *          created and returned.
     */
    MapProjection.prototype.unproject = DeveloperError.throwInstantiationError;

    return MapProjection;
});

/*global define*/
define('Core/Matrix2',[
        './Cartesian2',
        './Check',
        './defaultValue',
        './defined',
        './defineProperties',
        './freezeObject'
    ], function(
        Cartesian2,
        Check,
        defaultValue,
        defined,
        defineProperties,
        freezeObject) {
    'use strict';

    /**
     * A 2x2 matrix, indexable as a column-major order array.
     * Constructor parameters are in row-major order for code readability.
     * @alias Matrix2
     * @constructor
     *
     * @param {Number} [column0Row0=0.0] The value for column 0, row 0.
     * @param {Number} [column1Row0=0.0] The value for column 1, row 0.
     * @param {Number} [column0Row1=0.0] The value for column 0, row 1.
     * @param {Number} [column1Row1=0.0] The value for column 1, row 1.
     *
     * @see Matrix2.fromColumnMajorArray
     * @see Matrix2.fromRowMajorArray
     * @see Matrix2.fromScale
     * @see Matrix2.fromUniformScale
     * @see Matrix3
     * @see Matrix4
     */
    function Matrix2(column0Row0, column1Row0, column0Row1, column1Row1) {
        this[0] = defaultValue(column0Row0, 0.0);
        this[1] = defaultValue(column0Row1, 0.0);
        this[2] = defaultValue(column1Row0, 0.0);
        this[3] = defaultValue(column1Row1, 0.0);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    Matrix2.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {Matrix2} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    Matrix2.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value[0];
        array[startingIndex++] = value[1];
        array[startingIndex++] = value[2];
        array[startingIndex++] = value[3];

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {Matrix2} [result] The object into which to store the result.
     * @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided.
     */
    Matrix2.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Matrix2();
        }

        result[0] = array[startingIndex++];
        result[1] = array[startingIndex++];
        result[2] = array[startingIndex++];
        result[3] = array[startingIndex++];
        return result;
    };

    /**
     * Duplicates a Matrix2 instance.
     *
     * @param {Matrix2} matrix The matrix to duplicate.
     * @param {Matrix2} [result] The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided. (Returns undefined if matrix is undefined)
     */
    Matrix2.clone = function(matrix, result) {
        if (!defined(matrix)) {
            return undefined;
        }
        if (!defined(result)) {
            return new Matrix2(matrix[0], matrix[2],
                               matrix[1], matrix[3]);
        }
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        return result;
    };

    /**
     * Creates a Matrix2 from 4 consecutive elements in an array.
     *
     * @param {Number[]} array The array whose 4 consecutive elements correspond to the positions of the matrix.  Assumes column-major order.
     * @param {Number} [startingIndex=0] The offset into the array of the first element, which corresponds to first column first row position in the matrix.
     * @param {Matrix2} [result] The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided.
     *
     * @example
     * // Create the Matrix2:
     * // [1.0, 2.0]
     * // [1.0, 2.0]
     *
     * var v = [1.0, 1.0, 2.0, 2.0];
     * var m = Cesium.Matrix2.fromArray(v);
     *
     * // Create same Matrix2 with using an offset into an array
     * var v2 = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];
     * var m2 = Cesium.Matrix2.fromArray(v2, 2);
     */
    Matrix2.fromArray = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new Matrix2();
        }

        result[0] = array[startingIndex];
        result[1] = array[startingIndex + 1];
        result[2] = array[startingIndex + 2];
        result[3] = array[startingIndex + 3];
        return result;
    };

    /**
     * Creates a Matrix2 instance from a column-major order array.
     *
     * @param {Number[]} values The column-major order array.
     * @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
     */
    Matrix2.fromColumnMajorArray = function(values, result) {
                Check.defined('values', values);
        
        return Matrix2.clone(values, result);
    };

    /**
     * Creates a Matrix2 instance from a row-major order array.
     * The resulting matrix will be in column-major order.
     *
     * @param {Number[]} values The row-major order array.
     * @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
     */
    Matrix2.fromRowMajorArray = function(values, result) {
                Check.defined('values', values);
        
        if (!defined(result)) {
            return new Matrix2(values[0], values[1],
                               values[2], values[3]);
        }
        result[0] = values[0];
        result[1] = values[2];
        result[2] = values[1];
        result[3] = values[3];
        return result;
    };

    /**
     * Computes a Matrix2 instance representing a non-uniform scale.
     *
     * @param {Cartesian2} scale The x and y scale factors.
     * @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [7.0, 0.0]
     * //   [0.0, 8.0]
     * var m = Cesium.Matrix2.fromScale(new Cesium.Cartesian2(7.0, 8.0));
     */
    Matrix2.fromScale = function(scale, result) {
                Check.typeOf.object('scale', scale);
        
        if (!defined(result)) {
            return new Matrix2(
                scale.x, 0.0,
                0.0,     scale.y);
        }

        result[0] = scale.x;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = scale.y;
        return result;
    };

    /**
     * Computes a Matrix2 instance representing a uniform scale.
     *
     * @param {Number} scale The uniform scale factor.
     * @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
     *
     * @example
     * // Creates
     * //   [2.0, 0.0]
     * //   [0.0, 2.0]
     * var m = Cesium.Matrix2.fromUniformScale(2.0);
     */
    Matrix2.fromUniformScale = function(scale, result) {
                Check.typeOf.number('scale', scale);
        
        if (!defined(result)) {
            return new Matrix2(
                scale, 0.0,
                0.0,   scale);
        }

        result[0] = scale;
        result[1] = 0.0;
        result[2] = 0.0;
        result[3] = scale;
        return result;
    };

    /**
     * Creates a rotation matrix.
     *
     * @param {Number} angle The angle, in radians, of the rotation.  Positive angles are counterclockwise.
     * @param {Matrix2} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Matrix2} The modified result parameter, or a new Matrix2 instance if one was not provided.
     *
     * @example
     * // Rotate a point 45 degrees counterclockwise.
     * var p = new Cesium.Cartesian2(5, 6);
     * var m = Cesium.Matrix2.fromRotation(Cesium.Math.toRadians(45.0));
     * var rotated = Cesium.Matrix2.multiplyByVector(m, p, new Cesium.Cartesian2());
     */
    Matrix2.fromRotation = function(angle, result) {
                Check.typeOf.number('angle', angle);
        
        var cosAngle = Math.cos(angle);
        var sinAngle = Math.sin(angle);

        if (!defined(result)) {
            return new Matrix2(
                cosAngle, -sinAngle,
                sinAngle, cosAngle);
        }
        result[0] = cosAngle;
        result[1] = sinAngle;
        result[2] = -sinAngle;
        result[3] = cosAngle;
        return result;
    };

    /**
     * Creates an Array from the provided Matrix2 instance.
     * The array will be in column-major order.
     *
     * @param {Matrix2} matrix The matrix to use..
     * @param {Number[]} [result] The Array onto which to store the result.
     * @returns {Number[]} The modified Array parameter or a new Array instance if one was not provided.
     */
    Matrix2.toArray = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        
        if (!defined(result)) {
            return [matrix[0], matrix[1], matrix[2], matrix[3]];
        }
        result[0] = matrix[0];
        result[1] = matrix[1];
        result[2] = matrix[2];
        result[3] = matrix[3];
        return result;
    };

    /**
     * Computes the array index of the element at the provided row and column.
     *
     * @param {Number} row The zero-based index of the row.
     * @param {Number} column The zero-based index of the column.
     * @returns {Number} The index of the element at the provided row and column.
     *
     * @exception {DeveloperError} row must be 0 or 1.
     * @exception {DeveloperError} column must be 0 or 1.
     *
     * @example
     * var myMatrix = new Cesium.Matrix2();
     * var column1Row0Index = Cesium.Matrix2.getElementIndex(1, 0);
     * var column1Row0 = myMatrix[column1Row0Index]
     * myMatrix[column1Row0Index] = 10.0;
     */
    Matrix2.getElementIndex = function(column, row) {
                Check.typeOf.number.greaterThanOrEquals('row', row, 0);
        Check.typeOf.number.lessThanOrEquals('row', row, 1);

        Check.typeOf.number.greaterThanOrEquals('column', column, 0);
        Check.typeOf.number.lessThanOrEquals('column', column, 1);
        
        return column * 2 + row;
    };

    /**
     * Retrieves a copy of the matrix column at the provided index as a Cartesian2 instance.
     *
     * @param {Matrix2} matrix The matrix to use.
     * @param {Number} index The zero-based index of the column to retrieve.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0 or 1.
     */
    Matrix2.getColumn = function(matrix, index, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 1);

        Check.typeOf.object('result', result);
        
        var startIndex = index * 2;
        var x = matrix[startIndex];
        var y = matrix[startIndex + 1];

        result.x = x;
        result.y = y;
        return result;
    };

    /**
     * Computes a new matrix that replaces the specified column in the provided matrix with the provided Cartesian2 instance.
     *
     * @param {Matrix2} matrix The matrix to use.
     * @param {Number} index The zero-based index of the column to set.
     * @param {Cartesian2} cartesian The Cartesian whose values will be assigned to the specified column.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0 or 1.
     */
    Matrix2.setColumn = function(matrix, index, cartesian, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 1);

        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result = Matrix2.clone(matrix, result);
        var startIndex = index * 2;
        result[startIndex] = cartesian.x;
        result[startIndex + 1] = cartesian.y;
        return result;
    };

    /**
     * Retrieves a copy of the matrix row at the provided index as a Cartesian2 instance.
     *
     * @param {Matrix2} matrix The matrix to use.
     * @param {Number} index The zero-based index of the row to retrieve.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0 or 1.
     */
    Matrix2.getRow = function(matrix, index, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 1);

        Check.typeOf.object('result', result);
        
        var x = matrix[index];
        var y = matrix[index + 2];

        result.x = x;
        result.y = y;
        return result;
    };

    /**
     * Computes a new matrix that replaces the specified row in the provided matrix with the provided Cartesian2 instance.
     *
     * @param {Matrix2} matrix The matrix to use.
     * @param {Number} index The zero-based index of the row to set.
     * @param {Cartesian2} cartesian The Cartesian whose values will be assigned to the specified row.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     *
     * @exception {DeveloperError} index must be 0 or 1.
     */
    Matrix2.setRow = function(matrix, index, cartesian, result) {
                Check.typeOf.object('matrix', matrix);

        Check.typeOf.number.greaterThanOrEquals('index', index, 0);
        Check.typeOf.number.lessThanOrEquals('index', index, 1);

        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        result = Matrix2.clone(matrix, result);
        result[index] = cartesian.x;
        result[index + 2] = cartesian.y;
        return result;
    };

    var scratchColumn = new Cartesian2();

    /**
     * Extracts the non-uniform scale assuming the matrix is an affine transformation.
     *
     * @param {Matrix2} matrix The matrix.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Matrix2.getScale = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result.x = Cartesian2.magnitude(Cartesian2.fromElements(matrix[0], matrix[1], scratchColumn));
        result.y = Cartesian2.magnitude(Cartesian2.fromElements(matrix[2], matrix[3], scratchColumn));
        return result;
    };

    var scratchScale = new Cartesian2();

    /**
     * Computes the maximum scale assuming the matrix is an affine transformation.
     * The maximum scale is the maximum length of the column vectors.
     *
     * @param {Matrix2} matrix The matrix.
     * @returns {Number} The maximum scale.
     */
    Matrix2.getMaximumScale = function(matrix) {
        Matrix2.getScale(matrix, scratchScale);
        return Cartesian2.maximumComponent(scratchScale);
    };

    /**
     * Computes the product of two matrices.
     *
     * @param {Matrix2} left The first matrix.
     * @param {Matrix2} right The second matrix.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.multiply = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        var column0Row0 = left[0] * right[0] + left[2] * right[1];
        var column1Row0 = left[0] * right[2] + left[2] * right[3];
        var column0Row1 = left[1] * right[0] + left[3] * right[1];
        var column1Row1 = left[1] * right[2] + left[3] * right[3];

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column1Row0;
        result[3] = column1Row1;
        return result;
    };

    /**
     * Computes the sum of two matrices.
     *
     * @param {Matrix2} left The first matrix.
     * @param {Matrix2} right The second matrix.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.add = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = left[0] + right[0];
        result[1] = left[1] + right[1];
        result[2] = left[2] + right[2];
        result[3] = left[3] + right[3];
        return result;
    };

    /**
     * Computes the difference of two matrices.
     *
     * @param {Matrix2} left The first matrix.
     * @param {Matrix2} right The second matrix.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.subtract = function(left, right, result) {
                Check.typeOf.object('left', left);
        Check.typeOf.object('right', right);
        Check.typeOf.object('result', result);
        
        result[0] = left[0] - right[0];
        result[1] = left[1] - right[1];
        result[2] = left[2] - right[2];
        result[3] = left[3] - right[3];
        return result;
    };

    /**
     * Computes the product of a matrix and a column vector.
     *
     * @param {Matrix2} matrix The matrix.
     * @param {Cartesian2} cartesian The column.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter.
     */
    Matrix2.multiplyByVector = function(matrix, cartesian, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('cartesian', cartesian);
        Check.typeOf.object('result', result);
        
        var x = matrix[0] * cartesian.x + matrix[2] * cartesian.y;
        var y = matrix[1] * cartesian.x + matrix[3] * cartesian.y;

        result.x = x;
        result.y = y;
        return result;
    };

    /**
     * Computes the product of a matrix and a scalar.
     *
     * @param {Matrix2} matrix The matrix.
     * @param {Number} scalar The number to multiply by.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.multiplyByScalar = function(matrix, scalar, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.number('scalar', scalar);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0] * scalar;
        result[1] = matrix[1] * scalar;
        result[2] = matrix[2] * scalar;
        result[3] = matrix[3] * scalar;
        return result;
    };

    /**
     * Computes the product of a matrix times a (non-uniform) scale, as if the scale were a scale matrix.
     *
     * @param {Matrix2} matrix The matrix on the left-hand side.
     * @param {Cartesian2} scale The non-uniform scale on the right-hand side.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     *
     *
     * @example
     * // Instead of Cesium.Matrix2.multiply(m, Cesium.Matrix2.fromScale(scale), m);
     * Cesium.Matrix2.multiplyByScale(m, scale, m);
     *
     * @see Matrix2.fromScale
     * @see Matrix2.multiplyByUniformScale
     */
    Matrix2.multiplyByScale = function(matrix, scale, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('scale', scale);
        Check.typeOf.object('result', result);
        
        result[0] = matrix[0] * scale.x;
        result[1] = matrix[1] * scale.x;
        result[2] = matrix[2] * scale.y;
        result[3] = matrix[3] * scale.y;
        return result;
    };

    /**
     * Creates a negated copy of the provided matrix.
     *
     * @param {Matrix2} matrix The matrix to negate.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.negate = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = -matrix[0];
        result[1] = -matrix[1];
        result[2] = -matrix[2];
        result[3] = -matrix[3];
        return result;
    };

    /**
     * Computes the transpose of the provided matrix.
     *
     * @param {Matrix2} matrix The matrix to transpose.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.transpose = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        var column0Row0 = matrix[0];
        var column0Row1 = matrix[2];
        var column1Row0 = matrix[1];
        var column1Row1 = matrix[3];

        result[0] = column0Row0;
        result[1] = column0Row1;
        result[2] = column1Row0;
        result[3] = column1Row1;
        return result;
    };

    /**
     * Computes a matrix, which contains the absolute (unsigned) values of the provided matrix's elements.
     *
     * @param {Matrix2} matrix The matrix with signed elements.
     * @param {Matrix2} result The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter.
     */
    Matrix2.abs = function(matrix, result) {
                Check.typeOf.object('matrix', matrix);
        Check.typeOf.object('result', result);
        
        result[0] = Math.abs(matrix[0]);
        result[1] = Math.abs(matrix[1]);
        result[2] = Math.abs(matrix[2]);
        result[3] = Math.abs(matrix[3]);

        return result;
    };

    /**
     * Compares the provided matrices componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Matrix2} [left] The first matrix.
     * @param {Matrix2} [right] The second matrix.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Matrix2.equals = function(left, right) {
        return (left === right) ||
               (defined(left) &&
                defined(right) &&
                left[0] === right[0] &&
                left[1] === right[1] &&
                left[2] === right[2] &&
                left[3] === right[3]);
    };

    /**
     * @private
     */
    Matrix2.equalsArray = function(matrix, array, offset) {
        return matrix[0] === array[offset] &&
               matrix[1] === array[offset + 1] &&
               matrix[2] === array[offset + 2] &&
               matrix[3] === array[offset + 3];
    };

    /**
     * Compares the provided matrices componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Matrix2} [left] The first matrix.
     * @param {Matrix2} [right] The second matrix.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
     */
    Matrix2.equalsEpsilon = function(left, right, epsilon) {
                Check.typeOf.number('epsilon', epsilon);
        
        return (left === right) ||
                (defined(left) &&
                defined(right) &&
                Math.abs(left[0] - right[0]) <= epsilon &&
                Math.abs(left[1] - right[1]) <= epsilon &&
                Math.abs(left[2] - right[2]) <= epsilon &&
                Math.abs(left[3] - right[3]) <= epsilon);
    };

    /**
     * An immutable Matrix2 instance initialized to the identity matrix.
     *
     * @type {Matrix2}
     * @constant
     */
    Matrix2.IDENTITY = freezeObject(new Matrix2(1.0, 0.0,
                                                0.0, 1.0));

    /**
     * An immutable Matrix2 instance initialized to the zero matrix.
     *
     * @type {Matrix2}
     * @constant
     */
    Matrix2.ZERO = freezeObject(new Matrix2(0.0, 0.0,
                                            0.0, 0.0));

    /**
     * The index into Matrix2 for column 0, row 0.
     *
     * @type {Number}
     * @constant
     *
     * @example
     * var matrix = new Cesium.Matrix2();
     * matrix[Cesium.Matrix2.COLUMN0ROW0] = 5.0; // set column 0, row 0 to 5.0
     */
    Matrix2.COLUMN0ROW0 = 0;

    /**
     * The index into Matrix2 for column 0, row 1.
     *
     * @type {Number}
     * @constant
     *
     * @example
     * var matrix = new Cesium.Matrix2();
     * matrix[Cesium.Matrix2.COLUMN0ROW1] = 5.0; // set column 0, row 1 to 5.0
     */
    Matrix2.COLUMN0ROW1 = 1;

    /**
     * The index into Matrix2 for column 1, row 0.
     *
     * @type {Number}
     * @constant
     *
     * @example
     * var matrix = new Cesium.Matrix2();
     * matrix[Cesium.Matrix2.COLUMN1ROW0] = 5.0; // set column 1, row 0 to 5.0
     */
    Matrix2.COLUMN1ROW0 = 2;

    /**
     * The index into Matrix2 for column 1, row 1.
     *
     * @type {Number}
     * @constant
     *
     * @example
     * var matrix = new Cesium.Matrix2();
     * matrix[Cesium.Matrix2.COLUMN1ROW1] = 5.0; // set column 1, row 1 to 5.0
     */
    Matrix2.COLUMN1ROW1 = 3;

    defineProperties(Matrix2.prototype, {
        /**
         * Gets the number of items in the collection.
         * @memberof Matrix2.prototype
         *
         * @type {Number}
         */
        length : {
            get : function() {
                return Matrix2.packedLength;
            }
        }
    });

    /**
     * Duplicates the provided Matrix2 instance.
     *
     * @param {Matrix2} [result] The object onto which to store the result.
     * @returns {Matrix2} The modified result parameter or a new Matrix2 instance if one was not provided.
     */
    Matrix2.prototype.clone = function(result) {
        return Matrix2.clone(this, result);
    };

    /**
     * Compares this matrix to the provided matrix componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Matrix2} [right] The right hand side matrix.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    Matrix2.prototype.equals = function(right) {
        return Matrix2.equals(this, right);
    };

    /**
     * Compares this matrix to the provided matrix componentwise and returns
     * <code>true</code> if they are within the provided epsilon,
     * <code>false</code> otherwise.
     *
     * @param {Matrix2} [right] The right hand side matrix.
     * @param {Number} epsilon The epsilon to use for equality testing.
     * @returns {Boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
     */
    Matrix2.prototype.equalsEpsilon = function(right, epsilon) {
        return Matrix2.equalsEpsilon(this, right, epsilon);
    };

    /**
     * Creates a string representing this Matrix with each row being
     * on a separate line and in the format '(column0, column1)'.
     *
     * @returns {String} A string representing the provided Matrix with each row being on a separate line and in the format '(column0, column1)'.
     */
    Matrix2.prototype.toString = function() {
        return '(' + this[0] + ', ' + this[2] + ')\n' +
               '(' + this[1] + ', ' + this[3] + ')';
    };

    return Matrix2;
});

/*global define*/
define('Core/mergeSort',[
        './defined',
        './DeveloperError'
    ], function(
        defined,
        DeveloperError) {
    'use strict';

    var leftScratchArray = [];
    var rightScratchArray = [];

    function merge(array, compare, userDefinedObject, start, middle, end) {
        var leftLength = middle - start + 1;
        var rightLength = end - middle;

        var left = leftScratchArray;
        var right = rightScratchArray;

        var i;
        var j;

        for (i = 0; i < leftLength; ++i) {
            left[i] = array[start + i];
        }

        for (j = 0; j < rightLength; ++j) {
            right[j] = array[middle + j + 1];
        }

        i = 0;
        j = 0;
        for (var k = start; k <= end; ++k) {
            var leftElement = left[i];
            var rightElement = right[j];
            if (i < leftLength && (j >= rightLength || compare(leftElement, rightElement, userDefinedObject) <= 0)) {
                array[k] = leftElement;
                ++i;
            } else if (j < rightLength) {
                array[k] = rightElement;
                ++j;
            }
        }
    }

    function sort(array, compare, userDefinedObject, start, end) {
        if (start >= end) {
            return;
        }

        var middle = Math.floor((start + end) * 0.5);
        sort(array, compare, userDefinedObject, start, middle);
        sort(array, compare, userDefinedObject, middle + 1, end);
        merge(array, compare, userDefinedObject, start, middle, end);
    }

    /**
     * A stable merge sort.
     *
     * @exports mergeSort
     *
     * @param {Array} array The array to sort.
     * @param {mergeSort~Comparator} comparator The function to use to compare elements in the array.
     * @param {Object} [userDefinedObject] An object to pass as the third parameter to <code>comparator</code>.
     *
     * @example
     * // Assume array contains BoundingSpheres in world coordinates.
     * // Sort them in ascending order of distance from the camera.
     * var position = camera.positionWC;
     * Cesium.mergeSort(array, function(a, b, position) {
     *     return Cesium.BoundingSphere.distanceSquaredTo(b, position) - Cesium.BoundingSphere.distanceSquaredTo(a, position);
     * }, position);
     */
    function mergeSort(array, comparator, userDefinedObject) {
                if (!defined(array)) {
            throw new DeveloperError('array is required.');
        }
        if (!defined(comparator)) {
            throw new DeveloperError('comparator is required.');
        }
        
        var length = array.length;
        var scratchLength = Math.ceil(length * 0.5);

        // preallocate space in scratch arrays
        leftScratchArray.length = scratchLength;
        rightScratchArray.length = scratchLength;

        sort(array, comparator, userDefinedObject, 0, length - 1);

        // trim scratch arrays
        leftScratchArray.length = 0;
        rightScratchArray.length = 0;
    }

    /**
     * A function used to compare two items while performing a merge sort.
     * @callback mergeSort~Comparator
     *
     * @param {Object} a An item in the array.
     * @param {Object} b An item in the array.
     * @param {Object} [userDefinedObject] An object that was passed to {@link mergeSort}.
     * @returns {Number} Returns a negative value if <code>a</code> is less than <code>b</code>,
     *          a positive value if <code>a</code> is greater than <code>b</code>, or
     *          0 if <code>a</code> is equal to <code>b</code>.
     *
     * @example
     * function compareNumbers(a, b, userDefinedObject) {
     *     return a - b;
     * }
     */

    return mergeSort;
});

/*global define*/
define('Core/NearFarScalar',[
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Represents a scalar value's lower and upper bound at a near distance and far distance in eye space.
     * @alias NearFarScalar
     * @constructor
     *
     * @param {Number} [near=0.0] The lower bound of the camera range.
     * @param {Number} [nearValue=0.0] The value at the lower bound of the camera range.
     * @param {Number} [far=1.0] The upper bound of the camera range.
     * @param {Number} [farValue=0.0] The value at the upper bound of the camera range.
     *
     * @see Packable
     */
    function NearFarScalar(near, nearValue, far, farValue) {
        /**
         * The lower bound of the camera range.
         * @type {Number}
         * @default 0.0
         */
        this.near = defaultValue(near, 0.0);
        /**
         * The value at the lower bound of the camera range.
         * @type {Number}
         * @default 0.0
         */
        this.nearValue = defaultValue(nearValue, 0.0);
        /**
         * The upper bound of the camera range.
         * @type {Number}
         * @default 1.0
         */
        this.far = defaultValue(far, 1.0);
        /**
         * The value at the upper bound of the camera range.
         * @type {Number}
         * @default 0.0
         */
        this.farValue = defaultValue(farValue, 0.0);
    }

    /**
     * Duplicates a NearFarScalar instance.
     *
     * @param {NearFarScalar} nearFarScalar The NearFarScalar to duplicate.
     * @param {NearFarScalar} [result] The object onto which to store the result.
     * @returns {NearFarScalar} The modified result parameter or a new NearFarScalar instance if one was not provided. (Returns undefined if nearFarScalar is undefined)
     */
    NearFarScalar.clone = function(nearFarScalar, result) {
        if (!defined(nearFarScalar)) {
            return undefined;
        }

        if (!defined(result)) {
            return new NearFarScalar(nearFarScalar.near, nearFarScalar.nearValue, nearFarScalar.far, nearFarScalar.farValue);
        }

        result.near = nearFarScalar.near;
        result.nearValue = nearFarScalar.nearValue;
        result.far = nearFarScalar.far;
        result.farValue = nearFarScalar.farValue;
        return result;
    };


    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    NearFarScalar.packedLength = 4;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {NearFarScalar} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    NearFarScalar.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        array[startingIndex++] = value.near;
        array[startingIndex++] = value.nearValue;
        array[startingIndex++] = value.far;
        array[startingIndex] = value.farValue;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {NearFarScalar} [result] The object into which to store the result.
     * @returns {NearFarScalar} The modified result parameter or a new NearFarScalar instance if one was not provided.
     */
    NearFarScalar.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        if (!defined(result)) {
            result = new NearFarScalar();
        }
        result.near = array[startingIndex++];
        result.nearValue = array[startingIndex++];
        result.far = array[startingIndex++];
        result.farValue = array[startingIndex];
        return result;
    };

    /**
     * Compares the provided NearFarScalar and returns <code>true</code> if they are equal,
     * <code>false</code> otherwise.
     *
     * @param {NearFarScalar} [left] The first NearFarScalar.
     * @param {NearFarScalar} [right] The second NearFarScalar.
     * @returns {Boolean} <code>true</code> if left and right are equal; otherwise <code>false</code>.
     */
    NearFarScalar.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.near === right.near) &&
                (left.nearValue === right.nearValue) &&
                (left.far === right.far) &&
                (left.farValue === right.farValue));
    };

    /**
     * Duplicates this instance.
     *
     * @param {NearFarScalar} [result] The object onto which to store the result.
     * @returns {NearFarScalar} The modified result parameter or a new NearFarScalar instance if one was not provided.
     */
    NearFarScalar.prototype.clone = function(result) {
        return NearFarScalar.clone(this, result);
    };

    /**
     * Compares this instance to the provided NearFarScalar and returns <code>true</code> if they are equal,
     * <code>false</code> otherwise.
     *
     * @param {NearFarScalar} [right] The right hand side NearFarScalar.
     * @returns {Boolean} <code>true</code> if left and right are equal; otherwise <code>false</code>.
     */
    NearFarScalar.prototype.equals = function(right) {
        return NearFarScalar.equals(this, right);
    };

    return NearFarScalar;
});

/*global define*/
define('Core/Visibility',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * This enumerated type is used in determining to what extent an object, the occludee,
     * is visible during horizon culling. An occluder may fully block an occludee, in which case
     * it has no visibility, may partially block an occludee from view, or may not block it at all,
     * leading to full visibility.
     *
     * @exports Visibility
     */
    var Visibility = {
        /**
         * Represents that no part of an object is visible.
         *
         * @type {Number}
         * @constant
         */
        NONE : -1,

        /**
        * Represents that part, but not all, of an object is visible
        *
        * @type {Number}
        * @constant
        */
        PARTIAL : 0,

        /**
        * Represents that an object is visible in its entirety.
        *
        * @type {Number}
        * @constant
        */
        FULL : 1
    };

    return freezeObject(Visibility);
});

/*global define*/
define('Core/Occluder',[
        './BoundingSphere',
        './Cartesian3',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './Math',
        './Rectangle',
        './Visibility'
    ], function(
        BoundingSphere,
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        CesiumMath,
        Rectangle,
        Visibility) {
    'use strict';

    /**
     * Creates an Occluder derived from an object's position and radius, as well as the camera position.
     * The occluder can be used to determine whether or not other objects are visible or hidden behind the
     * visible horizon defined by the occluder and camera position.
     *
     * @alias Occluder
     *
     * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
     * @param {Cartesian3} cameraPosition The coordinate of the viewer/camera.
     *
     * @constructor
     *
     * @example
     * // Construct an occluder one unit away from the origin with a radius of one.
     * var cameraPosition = Cesium.Cartesian3.ZERO;
     * var occluderBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 1);
     * var occluder = new Cesium.Occluder(occluderBoundingSphere, cameraPosition);
     */
    function Occluder(occluderBoundingSphere, cameraPosition) {
                if (!defined(occluderBoundingSphere)) {
            throw new DeveloperError('occluderBoundingSphere is required.');
        }
        if (!defined(cameraPosition)) {
            throw new DeveloperError('camera position is required.');
        }
        
        this._occluderPosition = Cartesian3.clone(occluderBoundingSphere.center);
        this._occluderRadius = occluderBoundingSphere.radius;

        this._horizonDistance = 0.0;
        this._horizonPlaneNormal = undefined;
        this._horizonPlanePosition = undefined;
        this._cameraPosition = undefined;

        // cameraPosition fills in the above values
        this.cameraPosition = cameraPosition;
    }

    var scratchCartesian3 = new Cartesian3();

    defineProperties(Occluder.prototype, {
        /**
         * The position of the occluder.
         * @memberof Occluder.prototype
         * @type {Cartesian3}
         */
        position: {
            get: function() {
                return this._occluderPosition;
            }
        },

        /**
         * The radius of the occluder.
         * @memberof Occluder.prototype
         * @type {Number}
         */
        radius: {
            get: function() {
                return this._occluderRadius;
            }
        },

        /**
         * The position of the camera.
         * @memberof Occluder.prototype
         * @type {Cartesian3}
         */
        cameraPosition: {
            set: function(cameraPosition) {
                                if (!defined(cameraPosition)) {
                    throw new DeveloperError('cameraPosition is required.');
                }
                
                cameraPosition = Cartesian3.clone(cameraPosition, this._cameraPosition);

                var cameraToOccluderVec = Cartesian3.subtract(this._occluderPosition, cameraPosition, scratchCartesian3);
                var invCameraToOccluderDistance = Cartesian3.magnitudeSquared(cameraToOccluderVec);
                var occluderRadiusSqrd = this._occluderRadius * this._occluderRadius;

                var horizonDistance;
                var horizonPlaneNormal;
                var horizonPlanePosition;
                if (invCameraToOccluderDistance > occluderRadiusSqrd) {
                    horizonDistance = Math.sqrt(invCameraToOccluderDistance - occluderRadiusSqrd);
                    invCameraToOccluderDistance = 1.0 / Math.sqrt(invCameraToOccluderDistance);
                    horizonPlaneNormal = Cartesian3.multiplyByScalar(cameraToOccluderVec, invCameraToOccluderDistance, scratchCartesian3);
                    var nearPlaneDistance = horizonDistance * horizonDistance * invCameraToOccluderDistance;
                    horizonPlanePosition = Cartesian3.add(cameraPosition, Cartesian3.multiplyByScalar(horizonPlaneNormal, nearPlaneDistance, scratchCartesian3), scratchCartesian3);
                } else {
                    horizonDistance = Number.MAX_VALUE;
                }

                this._horizonDistance = horizonDistance;
                this._horizonPlaneNormal = horizonPlaneNormal;
                this._horizonPlanePosition = horizonPlanePosition;
                this._cameraPosition = cameraPosition;
            }
        }
    });

    /**
     * Creates an occluder from a bounding sphere and the camera position.
     *
     * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
     * @param {Cartesian3} cameraPosition The coordinate of the viewer/camera.
     * @param {Occluder} [result] The object onto which to store the result.
     * @returns {Occluder} The occluder derived from an object's position and radius, as well as the camera position.
     */
    Occluder.fromBoundingSphere = function(occluderBoundingSphere, cameraPosition, result) {
                if (!defined(occluderBoundingSphere)) {
            throw new DeveloperError('occluderBoundingSphere is required.');
        }

        if (!defined(cameraPosition)) {
            throw new DeveloperError('camera position is required.');
        }
        
        if (!defined(result)) {
            return new Occluder(occluderBoundingSphere, cameraPosition);
        }

        Cartesian3.clone(occluderBoundingSphere.center, result._occluderPosition);
        result._occluderRadius = occluderBoundingSphere.radius;
        result.cameraPosition = cameraPosition;

        return result;
    };


    var tempVecScratch = new Cartesian3();

    /**
     * Determines whether or not a point, the <code>occludee</code>, is hidden from view by the occluder.
     *
     * @param {Cartesian3} occludee The point surrounding the occludee object.
     * @returns {Boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
     *
     *
     * @example
     * var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
     * var littleSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 0.25);
     * var occluder = new Cesium.Occluder(littleSphere, cameraPosition);
     * var point = new Cesium.Cartesian3(0, 0, -3);
     * occluder.isPointVisible(point); //returns true
     * 
     * @see Occluder#computeVisibility
     */
    Occluder.prototype.isPointVisible = function(occludee) {
        if (this._horizonDistance !== Number.MAX_VALUE) {
            var tempVec = Cartesian3.subtract(occludee, this._occluderPosition, tempVecScratch);
            var temp = this._occluderRadius;
            temp = Cartesian3.magnitudeSquared(tempVec) - (temp * temp);
            if (temp > 0.0) {
                temp = Math.sqrt(temp) + this._horizonDistance;
                tempVec = Cartesian3.subtract(occludee, this._cameraPosition, tempVec);
                return temp * temp > Cartesian3.magnitudeSquared(tempVec);
            }
        }
        return false;
    };

    var occludeePositionScratch = new Cartesian3();

    /**
    * Determines whether or not a sphere, the <code>occludee</code>, is hidden from view by the occluder.
    *
    * @param {BoundingSphere} occludee The bounding sphere surrounding the occludee object.
    * @returns {Boolean} <code>true</code> if the occludee is visible; otherwise <code>false</code>.
    *
    *
    * @example
    * var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
    * var littleSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1), 0.25);
    * var occluder = new Cesium.Occluder(littleSphere, cameraPosition);
    * var bigSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -3), 1);
    * occluder.isBoundingSphereVisible(bigSphere); //returns true
    * 
    * @see Occluder#computeVisibility
    */
    Occluder.prototype.isBoundingSphereVisible = function(occludee) {
        var occludeePosition = Cartesian3.clone(occludee.center, occludeePositionScratch);
        var occludeeRadius = occludee.radius;

        if (this._horizonDistance !== Number.MAX_VALUE) {
            var tempVec = Cartesian3.subtract(occludeePosition, this._occluderPosition, tempVecScratch);
            var temp = this._occluderRadius - occludeeRadius;
            temp = Cartesian3.magnitudeSquared(tempVec) - (temp * temp);
            if (occludeeRadius < this._occluderRadius) {
                if (temp > 0.0) {
                    temp = Math.sqrt(temp) + this._horizonDistance;
                    tempVec = Cartesian3.subtract(occludeePosition, this._cameraPosition, tempVec);
                    return ((temp * temp) + (occludeeRadius * occludeeRadius)) > Cartesian3.magnitudeSquared(tempVec);
                }
                return false;
            }

            // Prevent against the case where the occludee radius is larger than the occluder's; since this is
            // an uncommon case, the following code should rarely execute.
            if (temp > 0.0) {
                tempVec = Cartesian3.subtract(occludeePosition, this._cameraPosition, tempVec);
                var tempVecMagnitudeSquared = Cartesian3.magnitudeSquared(tempVec);
                var occluderRadiusSquared = this._occluderRadius * this._occluderRadius;
                var occludeeRadiusSquared = occludeeRadius * occludeeRadius;
                if ((((this._horizonDistance * this._horizonDistance) + occluderRadiusSquared) * occludeeRadiusSquared) >
                    (tempVecMagnitudeSquared * occluderRadiusSquared)) {
                    // The occludee is close enough that the occluder cannot possible occlude the occludee
                    return true;
                }
                temp = Math.sqrt(temp) + this._horizonDistance;
                return ((temp * temp) + occludeeRadiusSquared) > tempVecMagnitudeSquared;
            }

            // The occludee completely encompasses the occluder
            return true;
        }

        return false;
    };

    var tempScratch = new Cartesian3();
    /**
     * Determine to what extent an occludee is visible (not visible, partially visible,  or fully visible).
     *
     * @param {BoundingSphere} occludeeBS The bounding sphere of the occludee.
     * @returns {Number} Visibility.NONE if the occludee is not visible,
     *                       Visibility.PARTIAL if the occludee is partially visible, or
     *                       Visibility.FULL if the occludee is fully visible.
     *
     *
     * @example
     * var sphere1 = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -1.5), 0.5);
     * var sphere2 = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -2.5), 0.5);
     * var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
     * var occluder = new Cesium.Occluder(sphere1, cameraPosition);
     * occluder.computeVisibility(sphere2); //returns Visibility.NONE
     * 
     * @see Occluder#isVisible
     */
    Occluder.prototype.computeVisibility = function(occludeeBS) {
                if (!defined(occludeeBS)) {
            throw new DeveloperError('occludeeBS is required.');
        }
        
        // If the occludee radius is larger than the occluders, this will return that
        // the entire ocludee is visible, even though that may not be the case, though this should
        // not occur too often.
        var occludeePosition = Cartesian3.clone(occludeeBS.center);
        var occludeeRadius = occludeeBS.radius;

        if (occludeeRadius > this._occluderRadius) {
            return Visibility.FULL;
        }

        if (this._horizonDistance !== Number.MAX_VALUE) {
            // The camera is outside the occluder
            var tempVec = Cartesian3.subtract(occludeePosition, this._occluderPosition, tempScratch);
            var temp = this._occluderRadius - occludeeRadius;
            var occluderToOccludeeDistSqrd = Cartesian3.magnitudeSquared(tempVec);
            temp = occluderToOccludeeDistSqrd - (temp * temp);
            if (temp > 0.0) {
                // The occludee is not completely inside the occluder
                // Check to see if the occluder completely hides the occludee
                temp = Math.sqrt(temp) + this._horizonDistance;
                tempVec = Cartesian3.subtract(occludeePosition, this._cameraPosition, tempVec);
                var cameraToOccludeeDistSqrd = Cartesian3.magnitudeSquared(tempVec);
                if (((temp * temp) + (occludeeRadius * occludeeRadius)) < cameraToOccludeeDistSqrd) {
                    return Visibility.NONE;
                }

                // Check to see whether the occluder is fully or partially visible
                // when the occludee does not intersect the occluder
                temp = this._occluderRadius + occludeeRadius;
                temp = occluderToOccludeeDistSqrd - (temp * temp);
                if (temp > 0.0) {
                    // The occludee does not intersect the occluder.
                    temp = Math.sqrt(temp) + this._horizonDistance;
                    return (cameraToOccludeeDistSqrd < ((temp * temp)) + (occludeeRadius * occludeeRadius)) ? Visibility.FULL : Visibility.PARTIAL;
                }

                //Check to see if the occluder is fully or partially visible when the occludee DOES
                //intersect the occluder
                tempVec = Cartesian3.subtract(occludeePosition, this._horizonPlanePosition, tempVec);
                return (Cartesian3.dot(tempVec, this._horizonPlaneNormal) > -occludeeRadius) ? Visibility.PARTIAL : Visibility.FULL;
            }
        }
        return Visibility.NONE;
    };

    var occludeePointScratch = new Cartesian3();
    /**
     * Computes a point that can be used as the occludee position to the visibility functions.
     * Use a radius of zero for the occludee radius.  Typically, a user computes a bounding sphere around
     * an object that is used for visibility; however it is also possible to compute a point that if
     * seen/not seen would also indicate if an object is visible/not visible.  This function is better
     * called for objects that do not move relative to the occluder and is large, such as a chunk of
     * terrain.  You are better off not calling this and using the object's bounding sphere for objects
     * such as a satellite or ground vehicle.
     *
     * @param {BoundingSphere} occluderBoundingSphere The bounding sphere surrounding the occluder.
     * @param {Cartesian3} occludeePosition The point where the occludee (bounding sphere of radius 0) is located.
     * @param {Cartesian3[]} positions List of altitude points on the horizon near the surface of the occluder.
     * @returns {Object} An object containing two attributes: <code>occludeePoint</code> and <code>valid</code>
     * which is a boolean value.
     *
     * @exception {DeveloperError} <code>positions</code> must contain at least one element.
     * @exception {DeveloperError} <code>occludeePosition</code> must have a value other than <code>occluderBoundingSphere.center</code>.
     *
     * @example
     * var cameraPosition = new Cesium.Cartesian3(0, 0, 0);
     * var occluderBoundingSphere = new Cesium.BoundingSphere(new Cesium.Cartesian3(0, 0, -8), 2);
     * var occluder = new Cesium.Occluder(occluderBoundingSphere, cameraPosition);
     * var positions = [new Cesium.Cartesian3(-0.25, 0, -5.3), new Cesium.Cartesian3(0.25, 0, -5.3)];
     * var tileOccluderSphere = Cesium.BoundingSphere.fromPoints(positions);
     * var occludeePosition = tileOccluderSphere.center;
     * var occludeePt = Cesium.Occluder.computeOccludeePoint(occluderBoundingSphere, occludeePosition, positions);
     */
    Occluder.computeOccludeePoint = function(occluderBoundingSphere, occludeePosition, positions) {
                if (!defined(occluderBoundingSphere)) {
            throw new DeveloperError('occluderBoundingSphere is required.');
        }
        if (!defined(positions)) {
            throw new DeveloperError('positions is required.');
        }
        if (positions.length === 0) {
            throw new DeveloperError('positions must contain at least one element');
        }
        
        var occludeePos = Cartesian3.clone(occludeePosition);
        var occluderPosition = Cartesian3.clone(occluderBoundingSphere.center);
        var occluderRadius = occluderBoundingSphere.radius;
        var numPositions = positions.length;

                if (Cartesian3.equals(occluderPosition, occludeePosition)) {
            throw new DeveloperError('occludeePosition must be different than occluderBoundingSphere.center');
        }
        
        // Compute a plane with a normal from the occluder to the occludee position.
        var occluderPlaneNormal = Cartesian3.normalize(Cartesian3.subtract(occludeePos, occluderPosition, occludeePointScratch), occludeePointScratch);
        var occluderPlaneD = -(Cartesian3.dot(occluderPlaneNormal, occluderPosition));

        //For each position, determine the horizon intersection. Choose the position and intersection
        //that results in the greatest angle with the occcluder plane.
        var aRotationVector = Occluder._anyRotationVector(occluderPosition, occluderPlaneNormal, occluderPlaneD);
        var dot = Occluder._horizonToPlaneNormalDotProduct(occluderBoundingSphere, occluderPlaneNormal, occluderPlaneD, aRotationVector, positions[0]);
        if (!dot) {
            //The position is inside the mimimum radius, which is invalid
            return undefined;
        }
        var tempDot;
        for ( var i = 1; i < numPositions; ++i) {
            tempDot = Occluder._horizonToPlaneNormalDotProduct(occluderBoundingSphere, occluderPlaneNormal, occluderPlaneD, aRotationVector, positions[i]);
            if (!tempDot) {
                //The position is inside the minimum radius, which is invalid
                return undefined;
            }
            if (tempDot < dot) {
                dot = tempDot;
            }
        }
        //Verify that the dot is not near 90 degress
        if (dot < 0.00174532836589830883577820272085) {
            return undefined;
        }

        var distance = occluderRadius / dot;
        return Cartesian3.add(occluderPosition, Cartesian3.multiplyByScalar(occluderPlaneNormal, distance, occludeePointScratch), occludeePointScratch);
    };

    var computeOccludeePointFromRectangleScratch = [];
    /**
     * Computes a point that can be used as the occludee position to the visibility functions from an rectangle.
     *
     * @param {Rectangle} rectangle The rectangle used to create a bounding sphere.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine positions of the rectangle.
     * @returns {Object} An object containing two attributes: <code>occludeePoint</code> and <code>valid</code>
     * which is a boolean value.
     */
    Occluder.computeOccludeePointFromRectangle = function(rectangle, ellipsoid) {
                if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required.');
        }
        
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        var positions = Rectangle.subsample(rectangle, ellipsoid, 0.0, computeOccludeePointFromRectangleScratch);
        var bs = BoundingSphere.fromPoints(positions);

        // TODO: get correct ellipsoid center
        var ellipsoidCenter = Cartesian3.ZERO;
        if (!Cartesian3.equals(ellipsoidCenter, bs.center)) {
            return Occluder.computeOccludeePoint(new BoundingSphere(ellipsoidCenter, ellipsoid.minimumRadius), bs.center, positions);
        }

        return undefined;
    };

    var tempVec0Scratch = new Cartesian3();
    Occluder._anyRotationVector = function(occluderPosition, occluderPlaneNormal, occluderPlaneD) {
        var tempVec0 = Cartesian3.abs(occluderPlaneNormal, tempVec0Scratch);
        var majorAxis = tempVec0.x > tempVec0.y ? 0 : 1;
        if (((majorAxis === 0) && (tempVec0.z > tempVec0.x)) || ((majorAxis === 1) && (tempVec0.z > tempVec0.y))) {
            majorAxis = 2;
        }
        var tempVec = new Cartesian3();
        var tempVec1;
        if (majorAxis === 0) {
            tempVec0.x = occluderPosition.x;
            tempVec0.y = occluderPosition.y + 1.0;
            tempVec0.z = occluderPosition.z + 1.0;
            tempVec1 = Cartesian3.UNIT_X;
        } else if (majorAxis === 1) {
            tempVec0.x = occluderPosition.x + 1.0;
            tempVec0.y = occluderPosition.y;
            tempVec0.z = occluderPosition.z + 1.0;
            tempVec1 = Cartesian3.UNIT_Y;
        } else {
            tempVec0.x = occluderPosition.x + 1.0;
            tempVec0.y = occluderPosition.y + 1.0;
            tempVec0.z = occluderPosition.z;
            tempVec1 = Cartesian3.UNIT_Z;
        }
        var u = (Cartesian3.dot(occluderPlaneNormal, tempVec0) + occluderPlaneD) / -(Cartesian3.dot(occluderPlaneNormal, tempVec1));
        return Cartesian3.normalize(Cartesian3.subtract(Cartesian3.add(tempVec0, Cartesian3.multiplyByScalar(tempVec1, u, tempVec), tempVec0), occluderPosition, tempVec0), tempVec0);
    };

    var posDirectionScratch = new Cartesian3();
    Occluder._rotationVector = function(occluderPosition, occluderPlaneNormal, occluderPlaneD, position, anyRotationVector) {
        //Determine the angle between the occluder plane normal and the position direction
        var positionDirection = Cartesian3.subtract(position, occluderPosition, posDirectionScratch);
        positionDirection = Cartesian3.normalize(positionDirection, positionDirection);
        if (Cartesian3.dot(occluderPlaneNormal, positionDirection) < 0.99999998476912904932780850903444) {
            var crossProduct = Cartesian3.cross(occluderPlaneNormal, positionDirection, positionDirection);
            var length = Cartesian3.magnitude(crossProduct);
            if (length > CesiumMath.EPSILON13) {
                return Cartesian3.normalize(crossProduct, new Cartesian3());
            }
        }
        //The occluder plane normal and the position direction are colinear. Use any
        //vector in the occluder plane as the rotation vector
        return anyRotationVector;
    };

    var posScratch1 = new Cartesian3();
    var occluerPosScratch = new Cartesian3();
    var posScratch2 = new Cartesian3();
    var horizonPlanePosScratch = new Cartesian3();
    Occluder._horizonToPlaneNormalDotProduct = function(occluderBS, occluderPlaneNormal, occluderPlaneD, anyRotationVector, position) {
        var pos = Cartesian3.clone(position, posScratch1);
        var occluderPosition = Cartesian3.clone(occluderBS.center, occluerPosScratch);
        var occluderRadius = occluderBS.radius;

        //Verify that the position is outside the occluder
        var positionToOccluder = Cartesian3.subtract(occluderPosition, pos, posScratch2);
        var occluderToPositionDistanceSquared = Cartesian3.magnitudeSquared(positionToOccluder);
        var occluderRadiusSquared = occluderRadius * occluderRadius;
        if (occluderToPositionDistanceSquared < occluderRadiusSquared) {
            return false;
        }

        //Horizon parameters
        var horizonDistanceSquared = occluderToPositionDistanceSquared - occluderRadiusSquared;
        var horizonDistance = Math.sqrt(horizonDistanceSquared);
        var occluderToPositionDistance = Math.sqrt(occluderToPositionDistanceSquared);
        var invOccluderToPositionDistance = 1.0 / occluderToPositionDistance;
        var cosTheta = horizonDistance * invOccluderToPositionDistance;
        var horizonPlaneDistance = cosTheta * horizonDistance;
        positionToOccluder = Cartesian3.normalize(positionToOccluder, positionToOccluder);
        var horizonPlanePosition = Cartesian3.add(pos, Cartesian3.multiplyByScalar(positionToOccluder, horizonPlaneDistance, horizonPlanePosScratch), horizonPlanePosScratch);
        var horizonCrossDistance = Math.sqrt(horizonDistanceSquared - (horizonPlaneDistance * horizonPlaneDistance));

        //Rotate the position to occluder vector 90 degrees
        var tempVec = this._rotationVector(occluderPosition, occluderPlaneNormal, occluderPlaneD, pos, anyRotationVector);
        var horizonCrossDirection = Cartesian3.fromElements(
                (tempVec.x * tempVec.x * positionToOccluder.x) + ((tempVec.x * tempVec.y - tempVec.z) * positionToOccluder.y) + ((tempVec.x * tempVec.z + tempVec.y) * positionToOccluder.z),
                ((tempVec.x * tempVec.y + tempVec.z) * positionToOccluder.x) + (tempVec.y * tempVec.y * positionToOccluder.y) + ((tempVec.y * tempVec.z - tempVec.x) * positionToOccluder.z),
                ((tempVec.x * tempVec.z - tempVec.y) * positionToOccluder.x) + ((tempVec.y * tempVec.z + tempVec.x) * positionToOccluder.y) + (tempVec.z * tempVec.z * positionToOccluder.z),
                posScratch1);
        horizonCrossDirection = Cartesian3.normalize(horizonCrossDirection, horizonCrossDirection);

        //Horizon positions
        var offset = Cartesian3.multiplyByScalar(horizonCrossDirection, horizonCrossDistance, posScratch1);
        tempVec = Cartesian3.normalize(Cartesian3.subtract(Cartesian3.add(horizonPlanePosition, offset, posScratch2), occluderPosition, posScratch2), posScratch2);
        var dot0 = Cartesian3.dot(occluderPlaneNormal, tempVec);
        tempVec = Cartesian3.normalize(Cartesian3.subtract(Cartesian3.subtract(horizonPlanePosition, offset, tempVec), occluderPosition, tempVec), tempVec);
        var dot1 = Cartesian3.dot(occluderPlaneNormal, tempVec);
        return (dot0 < dot1) ? dot0 : dot1;
    };

    return Occluder;
});

/*global define*/
define('Core/Packable',[
        './DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * Static interface for types which can store their values as packed
     * elements in an array.  These methods and properties are expected to be
     * defined on a constructor function.
     *
     * @exports Packable
     *
     * @see PackableForInterpolation
     */
    var Packable = {
        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        packedLength : undefined,

        /**
         * Stores the provided instance into the provided array.
         * @function
         *
         * @param {Object} value The value to pack.
         * @param {Number[]} array The array to pack into.
         * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
         */
        pack : DeveloperError.throwInstantiationError,

        /**
         * Retrieves an instance from a packed array.
         * @function
         *
         * @param {Number[]} array The packed array.
         * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
         * @param {Object} [result] The object into which to store the result.
         * @returns {Object} The modified result parameter or a new Object instance if one was not provided.
         */
        unpack : DeveloperError.throwInstantiationError
    };

    return Packable;
});

/*global define*/
define('Core/PackableForInterpolation',[
        './DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * Static interface for {@link Packable} types which are interpolated in a
     * different representation than their packed value.  These methods and
     * properties are expected to be defined on a constructor function.
     *
     * @exports PackableForInterpolation
     *
     * @see Packable
     */
    var PackableForInterpolation = {
        /**
         * The number of elements used to store the object into an array in its interpolatable form.
         * @type {Number}
         */
        packedInterpolationLength : undefined,

        /**
         * Converts a packed array into a form suitable for interpolation.
         * @function
         *
         * @param {Number[]} packedArray The packed array.
         * @param {Number} [startingIndex=0] The index of the first element to be converted.
         * @param {Number} [lastIndex=packedArray.length] The index of the last element to be converted.
         * @param {Number[]} result The object into which to store the result.
         */
        convertPackedArrayForInterpolation : DeveloperError.throwInstantiationError,

        /**
         * Retrieves an instance from a packed array converted with {@link PackableForInterpolation.convertPackedArrayForInterpolation}.
         * @function
         *
         * @param {Number[]} array The array previously packed for interpolation.
         * @param {Number[]} sourceArray The original packed array.
         * @param {Number} [startingIndex=0] The startingIndex used to convert the array.
         * @param {Number} [lastIndex=packedArray.length] The lastIndex used to convert the array.
         * @param {Object} [result] The object into which to store the result.
         * @returns {Object} The modified result parameter or a new Object instance if one was not provided.
         */
        unpackInterpolationResult : DeveloperError.throwInstantiationError
    };

    return PackableForInterpolation;
});

/*
  This library rewrites the Canvas2D "measureText" function
  so that it returns a more complete metrics object.

** -----------------------------------------------------------------------------

  CHANGELOG:

    2012-01-21 - Whitespace handling added by Joe Turner
                 (https://github.com/oampo)

** -----------------------------------------------------------------------------
*/
/**
  @license
  fontmetrics.js - https://github.com/Pomax/fontmetrics.js

  Copyright (C) 2011 by Mike "Pomax" Kamermans

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
**/
/*global define*/
define('ThirdParty/measureText',[],function() {
    /*jshint strict:false*/
/*
  var NAME = "FontMetrics Library"
  var VERSION = "1-2012.0121.1300";

  // if there is no getComputedStyle, this library won't work.
  if(!document.defaultView.getComputedStyle) {
    throw("ERROR: 'document.defaultView.getComputedStyle' not found. This library only works in browsers that can report computed CSS values.");
  }

  // store the old text metrics function on the Canvas2D prototype
  CanvasRenderingContext2D.prototype.measureTextWidth = CanvasRenderingContext2D.prototype.measureText;
*/
  /**
   *  shortcut function for getting computed CSS values
   */
  var getCSSValue = function(element, property) {
    return document.defaultView.getComputedStyle(element,null).getPropertyValue(property);
  };
/*
  // debug function
  var show = function(canvas, ctx, xstart, w, h, metrics)
  {
    document.body.appendChild(canvas);
    ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';

    ctx.beginPath();
    ctx.moveTo(xstart,0);
    ctx.lineTo(xstart,h);
    ctx.closePath();
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(xstart+metrics.bounds.maxx,0);
    ctx.lineTo(xstart+metrics.bounds.maxx,h);
    ctx.closePath();
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(0,h/2-metrics.ascent);
    ctx.lineTo(w,h/2-metrics.ascent);
    ctx.closePath();
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(0,h/2+metrics.descent);
    ctx.lineTo(w,h/2+metrics.descent);
    ctx.closePath();
    ctx.stroke();
  }
*/
  /**
   * The new text metrics function
   */
  var measureText = function(context2D, textstring, stroke, fill) {
    var metrics = context2D.measureText(textstring),
        fontFamily = getCSSValue(context2D.canvas,"font-family"),
        fontSize = getCSSValue(context2D.canvas,"font-size").replace("px",""),
        fontStyle = getCSSValue(context2D.canvas,"font-style"),
        fontWeight = getCSSValue(context2D.canvas,"font-weight"),
        isSpace = !(/\S/.test(textstring));
        metrics.fontsize = fontSize;

    // for text lead values, we meaure a multiline text container.
    var leadDiv = document.createElement("div");
    leadDiv.style.position = "absolute";
    leadDiv.style.opacity = 0;
    leadDiv.style.font = fontStyle + " " + fontWeight + " " + fontSize + "px " + fontFamily;
    leadDiv.innerHTML = textstring + "<br/>" + textstring;
    document.body.appendChild(leadDiv);

    // make some initial guess at the text leading (using the standard TeX ratio)
    metrics.leading = 1.2 * fontSize;

    // then we try to get the real value from the browser
    var leadDivHeight = getCSSValue(leadDiv,"height");
    leadDivHeight = leadDivHeight.replace("px","");
    if (leadDivHeight >= fontSize * 2) { metrics.leading = (leadDivHeight/2) | 0; }
    document.body.removeChild(leadDiv);

    // if we're not dealing with white space, we can compute metrics
    if (!isSpace) {
        // Have characters, so measure the text
        var canvas = document.createElement("canvas");
        var padding = 100;
        canvas.width = metrics.width + padding;
        canvas.height = 3*fontSize;
        canvas.style.opacity = 1;
        canvas.style.fontFamily = fontFamily;
        canvas.style.fontSize = fontSize;
        canvas.style.fontStyle = fontStyle;
        canvas.style.fontWeight = fontWeight;
        var ctx = canvas.getContext("2d");
        ctx.font = fontStyle + " " + fontWeight + " " + fontSize + "px " + fontFamily;

        var w = canvas.width,
            h = canvas.height,
            baseline = h/2;

        // Set all canvas pixeldata values to 255, with all the content
        // data being 0. This lets us scan for data[i] != 255.
        ctx.fillStyle = "white";
        ctx.fillRect(-1, -1, w + 2, h + 2);

        if (stroke) {
            ctx.strokeStyle = "black";
            ctx.lineWidth = context2D.lineWidth;
            ctx.strokeText(textstring, (padding / 2), baseline);
        }

        if (fill) {
            ctx.fillStyle = "black";
            ctx.fillText(textstring, padding / 2, baseline);
        }

        var pixelData = ctx.getImageData(0, 0, w, h).data;

        // canvas pixel data is w*4 by h*4, because R, G, B and A are separate,
        // consecutive values in the array, rather than stored as 32 bit ints.
        var i = 0,
            w4 = w * 4,
            len = pixelData.length;

        // Finding the ascent uses a normal, forward scanline
        while (++i < len && pixelData[i] === 255) {}
        var ascent = (i/w4)|0;

        // Finding the descent uses a reverse scanline
        i = len - 1;
        while (--i > 0 && pixelData[i] === 255) {}
        var descent = (i/w4)|0;

        // find the min-x coordinate
        for(i = 0; i<len && pixelData[i] === 255; ) {
          i += w4;
          if(i>=len) { i = (i-len) + 4; }}
        var minx = ((i%w4)/4) | 0;

        // find the max-x coordinate
        var step = 1;
        for(i = len-3; i>=0 && pixelData[i] === 255; ) {
          i -= w4;
          if(i<0) { i = (len - 3) - (step++)*4; }}
        var maxx = ((i%w4)/4) + 1 | 0;

        // set font metrics
        metrics.ascent = (baseline - ascent);
        metrics.descent = (descent - baseline);
        metrics.bounds = { minx: minx - (padding/2),
                           maxx: maxx - (padding/2),
                           miny: 0,
                           maxy: descent-ascent };
        metrics.height = 1+(descent - ascent);
    }

    // if we ARE dealing with whitespace, most values will just be zero.
    else {
        // Only whitespace, so we can't measure the text
        metrics.ascent = 0;
        metrics.descent = 0;
        metrics.bounds = { minx: 0,
                           maxx: metrics.width, // Best guess
                           miny: 0,
                           maxy: 0 };
        metrics.height = 0;
    }
    return metrics;
  };

  return measureText;
});

/*global define*/
define('Core/writeTextToCanvas',[
        '../ThirdParty/measureText',
        './Color',
        './defaultValue',
        './defined',
        './DeveloperError'
    ], function(
        measureText,
        Color,
        defaultValue,
        defined,
        DeveloperError) {
    'use strict';

    var imageSmoothingEnabledName;

    /**
     * Writes the given text into a new canvas.  The canvas will be sized to fit the text.
     * If text is blank, returns undefined.
     *
     * @param {String} text The text to write.
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.font='10px sans-serif'] The CSS font to use.
     * @param {String} [options.textBaseline='bottom'] The baseline of the text.
     * @param {Boolean} [options.fill=true] Whether to fill the text.
     * @param {Boolean} [options.stroke=false] Whether to stroke the text.
     * @param {Color} [options.fillColor=Color.WHITE] The fill color.
     * @param {Color} [options.strokeColor=Color.BLACK] The stroke color.
     * @param {Number} [options.strokeWidth=1] The stroke width.
     * @param {Color} [options.backgroundColor=Color.TRANSPARENT] The background color of the canvas.
     * @param {Number} [options.padding=0] The pixel size of the padding to add around the text.
     * @returns {Canvas} A new canvas with the given text drawn into it.  The dimensions object
     *                   from measureText will also be added to the returned canvas. If text is
     *                   blank, returns undefined.
     */
    function writeTextToCanvas(text, options) {
                if (!defined(text)) {
            throw new DeveloperError('text is required.');
        }
                if (text === '') {
            return undefined;
        }

        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var font = defaultValue(options.font, '10px sans-serif');
        var stroke = defaultValue(options.stroke, false);
        var fill = defaultValue(options.fill, true);
        var strokeWidth = defaultValue(options.strokeWidth, 1);
        var backgroundColor = defaultValue(options.backgroundColor, Color.TRANSPARENT);
        var padding = defaultValue(options.padding, 0);
        var doublePadding = padding * 2.0;

        var canvas = document.createElement('canvas');
        canvas.width = 1;
        canvas.height = 1;
        canvas.style.font = font;

        var context2D = canvas.getContext('2d');

        if (!defined(imageSmoothingEnabledName)) {
            if (defined(context2D.imageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'imageSmoothingEnabled';
            } else if (defined(context2D.mozImageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'mozImageSmoothingEnabled';
            } else if (defined(context2D.webkitImageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'webkitImageSmoothingEnabled';
            } else if (defined(context2D.msImageSmoothingEnabled)) {
                imageSmoothingEnabledName = 'msImageSmoothingEnabled';
            }
        }

        context2D.font = font;
        context2D.lineJoin = 'round';
        context2D.lineWidth = strokeWidth;
        context2D[imageSmoothingEnabledName] = false;

        // textBaseline needs to be set before the measureText call. It won't work otherwise.
        // It's magic.
        context2D.textBaseline = defaultValue(options.textBaseline, 'bottom');

        // in order for measureText to calculate style, the canvas has to be
        // (temporarily) added to the DOM.
        canvas.style.visibility = 'hidden';
        document.body.appendChild(canvas);

        var dimensions = measureText(context2D, text, stroke, fill);
        canvas.dimensions = dimensions;

        document.body.removeChild(canvas);
        canvas.style.visibility = '';

        //Some characters, such as the letter j, have a non-zero starting position.
        //This value is used for kerning later, but we need to take it into account
        //now in order to draw the text completely on the canvas
        var x = -dimensions.bounds.minx;

        //Expand the width to include the starting position.
        var width = Math.ceil(dimensions.width) + x + doublePadding;

        //While the height of the letter is correct, we need to adjust
        //where we start drawing it so that letters like j and y properly dip
        //below the line.
        var height = dimensions.height + doublePadding;
        var baseline = height - dimensions.ascent + doublePadding;
        var y = height - baseline + doublePadding;

        canvas.width = width;
        canvas.height = height;

        // Properties must be explicitly set again after changing width and height
        context2D.font = font;
        context2D.lineJoin = 'round';
        context2D.lineWidth = strokeWidth;
        context2D[imageSmoothingEnabledName] = false;

        // Draw background
        if (backgroundColor !== Color.TRANSPARENT) {
            context2D.fillStyle = backgroundColor.toCssColorString();
            context2D.fillRect(0, 0, canvas.width, canvas.height);
        }

        if (stroke) {
            var strokeColor = defaultValue(options.strokeColor, Color.BLACK);
            context2D.strokeStyle = strokeColor.toCssColorString();
            context2D.strokeText(text, x + padding, y);
        }

        if (fill) {
            var fillColor = defaultValue(options.fillColor, Color.WHITE);
            context2D.fillStyle = fillColor.toCssColorString();
            context2D.fillText(text, x + padding, y);
        }

        return canvas;
    }

    return writeTextToCanvas;
});

/*global define*/
define('Core/PinBuilder',[
        './buildModuleUrl',
        './Color',
        './defined',
        './DeveloperError',
        './loadImage',
        './writeTextToCanvas'
    ], function(
        buildModuleUrl,
        Color,
        defined,
        DeveloperError,
        loadImage,
        writeTextToCanvas) {
    'use strict';

    /**
     * A utility class for generating custom map pins as canvas elements.
     * <br /><br />
     * <div align='center'>
     * <img src='images/PinBuilder.png' width='500'/><br />
     * Example pins generated using both the maki icon set, which ships with Cesium, and single character text.
     * </div>
     *
     * @alias PinBuilder
     * @constructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Map%20Pins.html|Cesium Sandcastle PinBuilder Demo}
     */
    function PinBuilder() {
        this._cache = {};
    }

    /**
     * Creates an empty pin of the specified color and size.
     *
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas} The canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromColor = function(color, size) {
                if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
                return createPin(undefined, undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified icon, color, and size.
     *
     * @param {String} url The url of the image to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas|Promise.<Canvas>} The canvas element or a Promise to the canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromUrl = function(url, color, size) {
                if (!defined(url)) {
            throw new DeveloperError('url is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
                return createPin(url, undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified {@link https://www.mapbox.com/maki/|maki} icon identifier, color, and size.
     *
     * @param {String} id The id of the maki icon to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas|Promise.<Canvas>} The canvas element or a Promise to the canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromMakiIconId = function(id, color, size) {
                if (!defined(id)) {
            throw new DeveloperError('id is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
                return createPin(buildModuleUrl('Assets/Textures/maki/' + encodeURIComponent(id) + '.png'), undefined, color, size, this._cache);
    };

    /**
     * Creates a pin with the specified text, color, and size.  The text will be sized to be as large as possible
     * while still being contained completely within the pin.
     *
     * @param {String} text The text to be stamped onto the pin.
     * @param {Color} color The color of the pin.
     * @param {Number} size The size of the pin, in pixels.
     * @returns {Canvas} The canvas element that represents the generated pin.
     */
    PinBuilder.prototype.fromText = function(text, color, size) {
                if (!defined(text)) {
            throw new DeveloperError('text is required');
        }
        if (!defined(color)) {
            throw new DeveloperError('color is required');
        }
        if (!defined(size)) {
            throw new DeveloperError('size is required');
        }
        
        return createPin(undefined, text, color, size, this._cache);
    };

    var colorScratch = new Color();

    //This function (except for the 3 commented lines) was auto-generated from an online tool,
    //http://www.professorcloud.com/svg-to-canvas/, using Assets/Textures/pin.svg as input.
    //The reason we simply can't load and draw the SVG directly to the canvas is because
    //it taints the canvas in Internet Explorer (and possibly some other browsers); making
    //it impossible to create a WebGL texture from the result.
    function drawPin(context2D, color, size) {
        context2D.save();
        context2D.scale(size / 24, size / 24); //Added to auto-generated code to scale up to desired size.
        context2D.fillStyle = color.toCssColorString(); //Modified from auto-generated code.
        context2D.strokeStyle = color.brighten(0.6, colorScratch).toCssColorString(); //Modified from auto-generated code.
        context2D.lineWidth = 0.846;
        context2D.beginPath();
        context2D.moveTo(6.72, 0.422);
        context2D.lineTo(17.28, 0.422);
        context2D.bezierCurveTo(18.553, 0.422, 19.577, 1.758, 19.577, 3.415);
        context2D.lineTo(19.577, 10.973);
        context2D.bezierCurveTo(19.577, 12.63, 18.553, 13.966, 17.282, 13.966);
        context2D.lineTo(14.386, 14.008);
        context2D.lineTo(11.826, 23.578);
        context2D.lineTo(9.614, 14.008);
        context2D.lineTo(6.719, 13.965);
        context2D.bezierCurveTo(5.446, 13.983, 4.422, 12.629, 4.422, 10.972);
        context2D.lineTo(4.422, 3.416);
        context2D.bezierCurveTo(4.423, 1.76, 5.447, 0.423, 6.718, 0.423);
        context2D.closePath();
        context2D.fill();
        context2D.stroke();
        context2D.restore();
    }

    //This function takes an image or canvas and uses it as a template
    //to "stamp" the pin with a white image outlined in black.  The color
    //values of the input image are ignored completely and only the alpha
    //values are used.
    function drawIcon(context2D, image, size) {
        //Size is the largest image that looks good inside of pin box.
        var imageSize = size / 2.5;
        var sizeX = imageSize;
        var sizeY = imageSize;

        if (image.width > image.height) {
            sizeY = imageSize * (image.height / image.width);
        } else if (image.width < image.height) {
            sizeX = imageSize * (image.width / image.height);
        }

        //x and y are the center of the pin box
        var x = (size - sizeX) / 2;
        var y = ((7 / 24) * size) - (sizeY / 2);

        context2D.globalCompositeOperation = 'destination-out';
        context2D.drawImage(image, x - 1, y, sizeX, sizeY);
        context2D.drawImage(image, x, y - 1, sizeX, sizeY);
        context2D.drawImage(image, x + 1, y, sizeX, sizeY);
        context2D.drawImage(image, x, y + 1, sizeX, sizeY);

        context2D.globalCompositeOperation = 'destination-over';
        context2D.fillStyle = Color.BLACK.toCssColorString();
        context2D.fillRect(x - 1, y - 1, sizeX + 1, sizeY + 1);

        context2D.globalCompositeOperation = 'destination-out';
        context2D.drawImage(image, x, y, sizeX, sizeY);

        context2D.globalCompositeOperation = 'destination-over';
        context2D.fillStyle = Color.WHITE.toCssColorString();
        context2D.fillRect(x, y, sizeX, sizeY);
    }

    var stringifyScratch = new Array(4);
    function createPin(url, label, color, size, cache) {
        //Use the parameters as a unique ID for caching.
        stringifyScratch[0] = url;
        stringifyScratch[1] = label;
        stringifyScratch[2] = color;
        stringifyScratch[3] = size;
        var id = JSON.stringify(stringifyScratch);

        var item = cache[id];
        if (defined(item)) {
            return item;
        }

        var canvas = document.createElement('canvas');
        canvas.width = size;
        canvas.height = size;

        var context2D = canvas.getContext("2d");
        drawPin(context2D, color, size);

        if (defined(url)) {
            //If we have an image url, load it and then stamp the pin.
            var promise = loadImage(url).then(function(image) {
                drawIcon(context2D, image, size);
                cache[id] = canvas;
                return canvas;
            });
            cache[id] = promise;
            return promise;
        } else if (defined(label)) {
            //If we have a label, write it to a canvas and then stamp the pin.
            var image = writeTextToCanvas(label, {
                font : 'bold ' + size + 'px sans-serif'
            });
            drawIcon(context2D, image, size);
        }

        cache[id] = canvas;
        return canvas;
    }

    return PinBuilder;
});

/*global define*/
define('Core/PointGeometry',[
        './BoundingSphere',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './PrimitiveType'
    ], function(
        BoundingSphere,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        PrimitiveType) {
    'use strict';

    /**
     * Describes a collection of points made up of positions and colors.
     *
     * @alias PointGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {TypedArray} options.positionsTypedArray The position values of the points stored in a typed array. Positions are stored as packed (x, y, z) floats.
     * @param {TypedArray} options.colorsTypedArray The color values of the points stored in a typed array. Colors are stored as packed (r, g, b) unsigned bytes.
     * @param {BoundingSphere} [options.boundingSphere] Optional precomputed bounding sphere to save computation time.
     *
     * @example
     * // Create a PointGeometry with two points
     * var points = new Cesium.PointGeometry({
     *   positionsTypedArray : new Float32Array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]),
     *   colorsTypedArray : new Uint8Array([255, 0, 0, 127, 127, 127]),
     *   boundingSphere : boundingSphere
     * });
     * var geometry = Cesium.PointGeometry.createGeometry(points);
     *
     * @private
     */
    function PointGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.positionsTypedArray)) {
            throw new DeveloperError('options.positionsTypedArray is required.');
        }
        if (!defined(options.colorsTypedArray)) {
            throw new DeveloperError('options.colorsTypedArray is required');
        }
        
        this._positionsTypedArray = options.positionsTypedArray;
        this._colorsTypedArray = options.colorsTypedArray;
        this._boundingSphere = BoundingSphere.clone(options.boundingSphere);

        this._workerName = 'createPointGeometry';
    }

    /**
     * Computes the geometric representation a point collection, including its vertices and a bounding sphere.
     *
     * @param {PointGeometry} pointGeometry A description of the points.
     * @returns {Geometry} The computed vertices.
     */
    PointGeometry.createGeometry = function(pointGeometry) {
        var positions = pointGeometry._positionsTypedArray;
        var componentByteLength = positions.byteLength / positions.length;
        var componentDatatype = componentByteLength === 4 ? ComponentDatatype.FLOAT : ComponentDatatype.DOUBLE;

        var attributes = new GeometryAttributes();
        attributes.position = new GeometryAttribute({
            componentDatatype : componentDatatype,
            componentsPerAttribute : 3,
            values : positions
        });

        attributes.color = new GeometryAttribute({
            componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
            componentsPerAttribute : 3,
            values : pointGeometry._colorsTypedArray,
            normalize : true
        });

        // User provided bounding sphere to save computation time.
        var boundingSphere = pointGeometry._boundingSphere;
        if (!defined(boundingSphere)) {
            boundingSphere = BoundingSphere.fromVertices(positions);
        }

        return new Geometry({
            attributes : attributes,
            primitiveType : PrimitiveType.POINTS,
            boundingSphere : boundingSphere
        });
    };

    return PointGeometry;
});

/*global define*/
define('Core/pointInsideTriangle',[
        './barycentricCoordinates',
        './Cartesian3'
    ], function(
        barycentricCoordinates,
        Cartesian3) {
    'use strict';

    var coords = new Cartesian3();

    /**
     * Determines if a point is inside a triangle.
     *
     * @exports pointInsideTriangle
     *
     * @param {Cartesian2|Cartesian3} point The point to test.
     * @param {Cartesian2|Cartesian3} p0 The first point of the triangle.
     * @param {Cartesian2|Cartesian3} p1 The second point of the triangle.
     * @param {Cartesian2|Cartesian3} p2 The third point of the triangle.
     * @returns {Boolean} <code>true</code> if the point is inside the triangle; otherwise, <code>false</code>.
     *
     * @example
     * // Returns true
     * var p = new Cesium.Cartesian2(0.25, 0.25);
     * var b = Cesium.pointInsideTriangle(p,
     *   new Cesium.Cartesian2(0.0, 0.0),
     *   new Cesium.Cartesian2(1.0, 0.0),
     *   new Cesium.Cartesian2(0.0, 1.0));
     */
    function pointInsideTriangle(point, p0, p1, p2) {
        barycentricCoordinates(point, p0, p1, p2, coords);
        return (coords.x > 0.0) && (coords.y > 0.0) && (coords.z > 0);
    }

    return pointInsideTriangle;
});

/*global define*/
define('Core/Queue',[
        './defineProperties'
    ], function(
        defineProperties) {
    'use strict';

    /**
     * A queue that can enqueue items at the end, and dequeue items from the front.
     *
     * @alias Queue
     * @constructor
     */
    function Queue() {
        this._array = [];
        this._offset = 0;
        this._length = 0;
    }

    defineProperties(Queue.prototype, {
        /**
         * The length of the queue.
         *
         * @memberof Queue.prototype
         *
         * @type {Number}
         * @readonly
         */
        length : {
            get : function() {
                return this._length;
            }
        }
    });

    /**
     * Enqueues the specified item.
     *
     * @param {Object} item The item to enqueue.
     */
    Queue.prototype.enqueue = function(item) {
        this._array.push(item);
        this._length++;
    };

    /**
     * Dequeues an item.  Returns undefined if the queue is empty.
     *
     * @returns {Object} The the dequeued item.
     */
    Queue.prototype.dequeue = function() {
        if (this._length === 0) {
            return undefined;
        }

        var array = this._array;
        var offset = this._offset;
        var item = array[offset];
        array[offset] = undefined;

        offset++;
        if ((offset > 10) && (offset * 2 > array.length)) {
            //compact array
            this._array = array.slice(offset);
            offset = 0;
        }

        this._offset = offset;
        this._length--;

        return item;
    };

    /**
     * Returns the item at the front of the queue.  Returns undefined if the queue is empty.
     *
     * @returns {Object} The item at the front of the queue.
     */
    Queue.prototype.peek = function() {
        if (this._length === 0) {
            return undefined;
        }

        return this._array[this._offset];
    };

    /**
     * Check whether this queue contains the specified item.
     *
     * @param {Object} item The item to search for.
     */
    Queue.prototype.contains = function(item) {
        return this._array.indexOf(item) !== -1;
    };

    /**
     * Remove all items from the queue.
     */
    Queue.prototype.clear = function() {
        this._array.length = this._offset = this._length = 0;
    };

    /**
     * Sort the items in the queue in-place.
     *
     * @param {Queue~Comparator} compareFunction A function that defines the sort order.
     */
    Queue.prototype.sort = function(compareFunction) {
        if (this._offset > 0) {
            //compact array
            this._array = this._array.slice(this._offset);
            this._offset = 0;
        }

        this._array.sort(compareFunction);
    };

    /**
     * A function used to compare two items while sorting a queue.
     * @callback Queue~Comparator
     *
     * @param {Object} a An item in the array.
     * @param {Object} b An item in the array.
     * @returns {Number} Returns a negative value if <code>a</code> is less than <code>b</code>,
     *          a positive value if <code>a</code> is greater than <code>b</code>, or
     *          0 if <code>a</code> is equal to <code>b</code>.
     *
     * @example
     * function compareNumbers(a, b) {
     *     return a - b;
     * }
     */

    return Queue;
});

/*global define*/
define('Core/PolygonGeometryLibrary',[
        './arrayRemoveDuplicates',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './GeometryPipeline',
        './IndexDatatype',
        './Math',
        './PolygonPipeline',
        './PrimitiveType',
        './Queue',
        './WindingOrder'
    ], function(
        arrayRemoveDuplicates,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        GeometryPipeline,
        IndexDatatype,
        CesiumMath,
        PolygonPipeline,
        PrimitiveType,
        Queue,
        WindingOrder) {
    'use strict';

    /**
     * @private
     */
    var PolygonGeometryLibrary = {};

    PolygonGeometryLibrary.computeHierarchyPackedLength = function(polygonHierarchy) {
        var numComponents = 0;
        var stack = [polygonHierarchy];
        while (stack.length > 0) {
            var hierarchy = stack.pop();
            if (!defined(hierarchy)) {
                continue;
            }

            numComponents += 2;

            var positions = hierarchy.positions;
            var holes = hierarchy.holes;

            if (defined(positions)) {
                numComponents += positions.length * Cartesian3.packedLength;
            }

            if (defined(holes)) {
                var length = holes.length;
                for (var i = 0; i < length; ++i) {
                    stack.push(holes[i]);
                }
            }
        }

        return numComponents;
    };

    PolygonGeometryLibrary.packPolygonHierarchy = function(polygonHierarchy, array, startingIndex) {
        var stack = [polygonHierarchy];
        while (stack.length > 0) {
            var hierarchy = stack.pop();
            if (!defined(hierarchy)) {
                continue;
            }

            var positions = hierarchy.positions;
            var holes = hierarchy.holes;

            array[startingIndex++] = defined(positions) ? positions.length : 0;
            array[startingIndex++] = defined(holes) ? holes.length : 0;

            if (defined(positions)) {
                var positionsLength = positions.length;
                for (var i = 0; i < positionsLength; ++i, startingIndex += 3) {
                    Cartesian3.pack(positions[i], array, startingIndex);
                }
            }

            if (defined(holes)) {
                var holesLength = holes.length;
                for (var j = 0; j < holesLength; ++j) {
                    stack.push(holes[j]);
                }
            }
        }

        return startingIndex;
    };

    PolygonGeometryLibrary.unpackPolygonHierarchy = function(array, startingIndex) {
        var positionsLength = array[startingIndex++];
        var holesLength = array[startingIndex++];

        var positions = new Array(positionsLength);
        var holes = holesLength > 0 ? new Array(holesLength) : undefined;

        for (var i = 0; i < positionsLength; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        for (var j = 0; j < holesLength; ++j) {
            holes[j] = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex);
            startingIndex = holes[j].startingIndex;
            delete holes[j].startingIndex;
        }

        return {
            positions : positions,
            holes : holes,
            startingIndex : startingIndex
        };
    };

    var distanceScratch = new Cartesian3();
    function getPointAtDistance(p0, p1, distance, length) {
        Cartesian3.subtract(p1, p0, distanceScratch);
        Cartesian3.multiplyByScalar(distanceScratch, distance / length, distanceScratch);
        Cartesian3.add(p0, distanceScratch, distanceScratch);
        return [distanceScratch.x, distanceScratch.y, distanceScratch.z];
    }

    PolygonGeometryLibrary.subdivideLineCount = function(p0, p1, minDistance) {
        var distance = Cartesian3.distance(p0, p1);
        var n = distance / minDistance;
        var countDivide = Math.max(0, Math.ceil(Math.log(n) / Math.log(2)));
        return Math.pow(2, countDivide);
    };

    PolygonGeometryLibrary.subdivideLine = function(p0, p1, minDistance, result) {
        var numVertices = PolygonGeometryLibrary.subdivideLineCount(p0, p1, minDistance);
        var length = Cartesian3.distance(p0, p1);
        var distanceBetweenVertices = length / numVertices;

        if (!defined(result)) {
            result = [];
        }

        var positions = result;
        positions.length = numVertices * 3;

        var index = 0;
        for ( var i = 0; i < numVertices; i++) {
            var p = getPointAtDistance(p0, p1, i * distanceBetweenVertices, length);
            positions[index++] = p[0];
            positions[index++] = p[1];
            positions[index++] = p[2];
        }

        return positions;
    };

    var scaleToGeodeticHeightN1 = new Cartesian3();
    var scaleToGeodeticHeightN2 = new Cartesian3();
    var scaleToGeodeticHeightP1 = new Cartesian3();
    var scaleToGeodeticHeightP2 = new Cartesian3();

    PolygonGeometryLibrary.scaleToGeodeticHeightExtruded = function(geometry, maxHeight, minHeight, ellipsoid, perPositionHeight) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        var n1 = scaleToGeodeticHeightN1;
        var n2 = scaleToGeodeticHeightN2;
        var p = scaleToGeodeticHeightP1;
        var p2 = scaleToGeodeticHeightP2;

        if (defined(geometry) && defined(geometry.attributes) && defined(geometry.attributes.position)) {
            var positions = geometry.attributes.position.values;
            var length = positions.length / 2;

            for ( var i = 0; i < length; i += 3) {
                Cartesian3.fromArray(positions, i, p);

                ellipsoid.geodeticSurfaceNormal(p, n1);
                p2 = ellipsoid.scaleToGeodeticSurface(p, p2);
                n2 = Cartesian3.multiplyByScalar(n1, minHeight, n2);
                n2 = Cartesian3.add(p2, n2, n2);
                positions[i + length] = n2.x;
                positions[i + 1 + length] = n2.y;
                positions[i + 2 + length] = n2.z;

                if (perPositionHeight) {
                    p2 = Cartesian3.clone(p, p2);
                }
                n2 = Cartesian3.multiplyByScalar(n1, maxHeight, n2);
                n2 = Cartesian3.add(p2, n2, n2);
                positions[i] = n2.x;
                positions[i + 1] = n2.y;
                positions[i + 2] = n2.z;
            }
        }
        return geometry;
    };

    PolygonGeometryLibrary.polygonsFromHierarchy = function(polygonHierarchy, perPositionHeight, tangentPlane, ellipsoid) {
        // create from a polygon hierarchy
        // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
        var hierarchy = [];
        var polygons = [];

        var queue = new Queue();
        queue.enqueue(polygonHierarchy);

        while (queue.length !== 0) {
            var outerNode = queue.dequeue();
            var outerRing = outerNode.positions;
            var holes = outerNode.holes;

            outerRing = arrayRemoveDuplicates(outerRing, Cartesian3.equalsEpsilon, true);
            if (outerRing.length < 3) {
                continue;
            }

            var positions2D = tangentPlane.projectPointsOntoPlane(outerRing);
            var holeIndices = [];

            var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
            if (originalWindingOrder === WindingOrder.CLOCKWISE) {
                positions2D.reverse();
                outerRing = outerRing.slice().reverse();
            }

            var positions = outerRing.slice();
            var numChildren = defined(holes) ? holes.length : 0;
            var polygonHoles = [];
            var i;
            var j;

            for (i = 0; i < numChildren; i++) {
                var hole = holes[i];
                var holePositions = arrayRemoveDuplicates(hole.positions, Cartesian3.equalsEpsilon, true);
                if (holePositions.length < 3) {
                    continue;
                }

                var holePositions2D = tangentPlane.projectPointsOntoPlane(holePositions);

                originalWindingOrder = PolygonPipeline.computeWindingOrder2D(holePositions2D);
                if (originalWindingOrder === WindingOrder.CLOCKWISE) {
                    holePositions2D.reverse();
                    holePositions = holePositions.slice().reverse();
                }

                polygonHoles.push(holePositions);
                holeIndices.push(positions.length);
                positions = positions.concat(holePositions);
                positions2D = positions2D.concat(holePositions2D);

                var numGrandchildren = 0;
                if (defined(hole.holes)) {
                    numGrandchildren = hole.holes.length;
                }

                for (j = 0; j < numGrandchildren; j++) {
                    queue.enqueue(hole.holes[j]);
                }
            }

            if (!perPositionHeight) {
                for (i = 0; i < outerRing.length; i++) {
                    ellipsoid.scaleToGeodeticSurface(outerRing[i], outerRing[i]);
                }
                for (i = 0; i < polygonHoles.length; i++) {
                    var polygonHole = polygonHoles[i];
                    for (j = 0; j < polygonHole.length; ++j) {
                        ellipsoid.scaleToGeodeticSurface(polygonHole[j], polygonHole[j]);
                    }
                }
            }

            hierarchy.push({
                outerRing : outerRing,
                holes : polygonHoles
            });
            polygons.push({
                positions : positions,
                positions2D : positions2D,
                holes : holeIndices
            });
        }

        return {
            hierarchy : hierarchy,
            polygons : polygons
        };
    };

    PolygonGeometryLibrary.createGeometryFromPositions = function(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat) {
        var indices = PolygonPipeline.triangulate(polygon.positions2D, polygon.holes);

        /* If polygon is completely unrenderable, just use the first three vertices */
        if (indices.length < 3) {
            indices = [0, 1, 2];
        }

        var positions = polygon.positions;

        if (perPositionHeight) {
            var length = positions.length;
            var flattenedPositions = new Array(length * 3);
            var index = 0;
            for ( var i = 0; i < length; i++) {
                var p = positions[i];
                flattenedPositions[index++] = p.x;
                flattenedPositions[index++] = p.y;
                flattenedPositions[index++] = p.z;
            }
            var geometry = new Geometry({
                attributes : {
                    position : new GeometryAttribute({
                        componentDatatype : ComponentDatatype.DOUBLE,
                        componentsPerAttribute : 3,
                        values : flattenedPositions
                    })
                },
                indices : indices,
                primitiveType : PrimitiveType.TRIANGLES
            });

            if (vertexFormat.normal) {
                return GeometryPipeline.computeNormal(geometry);
            }

            return geometry;
        }

        return PolygonPipeline.computeSubdivision(ellipsoid, positions, indices, granularity);
    };

    var computeWallIndicesSubdivided = [];
    var p1Scratch = new Cartesian3();
    var p2Scratch = new Cartesian3();

    PolygonGeometryLibrary.computeWallGeometry = function(positions, ellipsoid, granularity, perPositionHeight) {
        var edgePositions;
        var topEdgeLength;
        var i;
        var p1;
        var p2;

        var length = positions.length;
        var index = 0;

        if (!perPositionHeight) {
            var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);

            var numVertices = 0;
            for (i = 0; i < length; i++) {
                numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
            }

            topEdgeLength = (numVertices + length) * 3;
            edgePositions = new Array(topEdgeLength * 2);
            for (i = 0; i < length; i++) {
                p1 = positions[i];
                p2 = positions[(i + 1) % length];

                var tempPositions = PolygonGeometryLibrary.subdivideLine(p1, p2, minDistance, computeWallIndicesSubdivided);
                var tempPositionsLength = tempPositions.length;
                for (var j = 0; j < tempPositionsLength; ++j, ++index) {
                    edgePositions[index] = tempPositions[j];
                    edgePositions[index + topEdgeLength] = tempPositions[j];
                }

                edgePositions[index] = p2.x;
                edgePositions[index + topEdgeLength] = p2.x;
                ++index;

                edgePositions[index] = p2.y;
                edgePositions[index + topEdgeLength] = p2.y;
                ++index;

                edgePositions[index] = p2.z;
                edgePositions[index + topEdgeLength] = p2.z;
                ++index;
            }
        } else {
            topEdgeLength = length * 3 * 2;
            edgePositions = new Array(topEdgeLength * 2);
            for (i = 0; i < length; i++) {
                p1 = positions[i];
                p2 = positions[(i + 1) % length];
                edgePositions[index] = edgePositions[index + topEdgeLength] = p1.x;
                ++index;
                edgePositions[index] = edgePositions[index + topEdgeLength] = p1.y;
                ++index;
                edgePositions[index] = edgePositions[index + topEdgeLength] = p1.z;
                ++index;
                edgePositions[index] = edgePositions[index + topEdgeLength] = p2.x;
                ++index;
                edgePositions[index] = edgePositions[index + topEdgeLength] = p2.y;
                ++index;
                edgePositions[index] = edgePositions[index + topEdgeLength] = p2.z;
                ++index;
            }
        }

        length = edgePositions.length;
        var indices = IndexDatatype.createTypedArray(length / 3, length - positions.length * 6);
        var edgeIndex = 0;
        length /= 6;

        for (i = 0; i < length; i++) {
            var UL = i;
            var UR = UL + 1;
            var LL = UL + length;
            var LR = LL + 1;

            p1 = Cartesian3.fromArray(edgePositions, UL * 3, p1Scratch);
            p2 = Cartesian3.fromArray(edgePositions, UR * 3, p2Scratch);
            if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON14)) {
                continue;
            }

            indices[edgeIndex++] = UL;
            indices[edgeIndex++] = LL;
            indices[edgeIndex++] = UR;
            indices[edgeIndex++] = UR;
            indices[edgeIndex++] = LL;
            indices[edgeIndex++] = LR;
        }

        return new Geometry({
            attributes : new GeometryAttributes({
                position : new GeometryAttribute({
                    componentDatatype : ComponentDatatype.DOUBLE,
                    componentsPerAttribute : 3,
                    values : edgePositions
                })
            }),
            indices : indices,
            primitiveType : PrimitiveType.TRIANGLES
        });
    };

    return PolygonGeometryLibrary;
});

/*global define*/
define('Core/PolygonGeometry',[
        './BoundingRectangle',
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './Cartographic',
        './Check',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './EllipsoidTangentPlane',
        './Geometry',
        './GeometryAttribute',
        './GeometryInstance',
        './GeometryPipeline',
        './IndexDatatype',
        './Math',
        './Matrix3',
        './PolygonGeometryLibrary',
        './PolygonPipeline',
        './Quaternion',
        './Rectangle',
        './VertexFormat',
        './WindingOrder'
    ], function(
        BoundingRectangle,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartographic,
        Check,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        EllipsoidTangentPlane,
        Geometry,
        GeometryAttribute,
        GeometryInstance,
        GeometryPipeline,
        IndexDatatype,
        CesiumMath,
        Matrix3,
        PolygonGeometryLibrary,
        PolygonPipeline,
        Quaternion,
        Rectangle,
        VertexFormat,
        WindingOrder) {
    'use strict';

    var computeBoundingRectangleCartesian2 = new Cartesian2();
    var computeBoundingRectangleCartesian3 = new Cartesian3();
    var computeBoundingRectangleQuaternion = new Quaternion();
    var computeBoundingRectangleMatrix3 = new Matrix3();

    function computeBoundingRectangle(tangentPlane, positions, angle, result) {
        var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, angle, computeBoundingRectangleQuaternion);
        var textureMatrix = Matrix3.fromQuaternion(rotation, computeBoundingRectangleMatrix3);

        var minX = Number.POSITIVE_INFINITY;
        var maxX = Number.NEGATIVE_INFINITY;
        var minY = Number.POSITIVE_INFINITY;
        var maxY = Number.NEGATIVE_INFINITY;

        var length = positions.length;
        for ( var i = 0; i < length; ++i) {
            var p = Cartesian3.clone(positions[i], computeBoundingRectangleCartesian3);
            Matrix3.multiplyByVector(textureMatrix, p, p);
            var st = tangentPlane.projectPointOntoPlane(p, computeBoundingRectangleCartesian2);

            if (defined(st)) {
                minX = Math.min(minX, st.x);
                maxX = Math.max(maxX, st.x);

                minY = Math.min(minY, st.y);
                maxY = Math.max(maxY, st.y);
            }
        }

        result.x = minX;
        result.y = minY;
        result.width = maxX - minX;
        result.height = maxY - minY;
        return result;
    }

    var scratchCarto1 = new Cartographic();
    var scratchCarto2 = new Cartographic();
    function adjustPosHeightsForNormal(position, p1, p2, ellipsoid) {
        var carto1 = ellipsoid.cartesianToCartographic(position, scratchCarto1);
        var height = carto1.height;
        var p1Carto = ellipsoid.cartesianToCartographic(p1, scratchCarto2);
        p1Carto.height = height;
        ellipsoid.cartographicToCartesian(p1Carto, p1);

        var p2Carto = ellipsoid.cartesianToCartographic(p2, scratchCarto2);
        p2Carto.height = height - 100;
        ellipsoid.cartographicToCartesian(p2Carto, p2);
    }

    var scratchBoundingRectangle = new BoundingRectangle();
    var scratchPosition = new Cartesian3();
    var scratchNormal = new Cartesian3();
    var scratchTangent = new Cartesian3();
    var scratchBitangent = new Cartesian3();
    var p1Scratch = new Cartesian3();
    var p2Scratch = new Cartesian3();
    var scratchPerPosNormal = new Cartesian3();
    var scratchPerPosTangent = new Cartesian3();
    var scratchPerPosBitangent = new Cartesian3();

    var appendTextureCoordinatesOrigin = new Cartesian2();
    var appendTextureCoordinatesCartesian2 = new Cartesian2();
    var appendTextureCoordinatesCartesian3 = new Cartesian3();
    var appendTextureCoordinatesQuaternion = new Quaternion();
    var appendTextureCoordinatesMatrix3 = new Matrix3();

    function computeAttributes(options) {
        var vertexFormat = options.vertexFormat;
        var geometry = options.geometry;
        var shadowVolume = options.shadowVolume;
        if (vertexFormat.st || vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent || shadowVolume) {
            // PERFORMANCE_IDEA: Compute before subdivision, then just interpolate during subdivision.
            // PERFORMANCE_IDEA: Compute with createGeometryFromPositions() for fast path when there's no holes.
            var boundingRectangle = options.boundingRectangle;
            var tangentPlane = options.tangentPlane;
            var ellipsoid = options.ellipsoid;
            var stRotation = options.stRotation;
            var wall = options.wall;
            var top = options.top || wall;
            var bottom = options.bottom || wall;
            var perPositionHeight = options.perPositionHeight;

            var origin = appendTextureCoordinatesOrigin;
            origin.x = boundingRectangle.x;
            origin.y = boundingRectangle.y;

            var flatPositions = geometry.attributes.position.values;
            var length = flatPositions.length;

            var textureCoordinates = vertexFormat.st ? new Float32Array(2 * (length / 3)) : undefined;
            var normals;
            if (vertexFormat.normal) {
                if (perPositionHeight && top && !wall) {
                    normals = geometry.attributes.normal.values;
                } else {
                    normals = new Float32Array(length);
                }
            }
            var tangents = vertexFormat.tangent ? new Float32Array(length) : undefined;
            var bitangents = vertexFormat.bitangent ? new Float32Array(length) : undefined;
            var extrudeNormals = shadowVolume ? new Float32Array(length) : undefined;

            var textureCoordIndex = 0;
            var attrIndex = 0;

            var normal = scratchNormal;
            var tangent = scratchTangent;
            var bitangent = scratchBitangent;
            var recomputeNormal = true;

            var rotation = Quaternion.fromAxisAngle(tangentPlane._plane.normal, stRotation, appendTextureCoordinatesQuaternion);
            var textureMatrix = Matrix3.fromQuaternion(rotation, appendTextureCoordinatesMatrix3);

            var bottomOffset = 0;
            var bottomOffset2 = 0;

            if (top && bottom) {
                bottomOffset = length / 2;
                bottomOffset2 = length / 3;

                length /= 2;
            }

            for ( var i = 0; i < length; i += 3) {
                var position = Cartesian3.fromArray(flatPositions, i, appendTextureCoordinatesCartesian3);

                if (vertexFormat.st) {
                    var p = Matrix3.multiplyByVector(textureMatrix, position, scratchPosition);
                    p = ellipsoid.scaleToGeodeticSurface(p,p);
                    var st = tangentPlane.projectPointOntoPlane(p, appendTextureCoordinatesCartesian2);
                    Cartesian2.subtract(st, origin, st);

                    var stx = CesiumMath.clamp(st.x / boundingRectangle.width, 0, 1);
                    var sty = CesiumMath.clamp(st.y / boundingRectangle.height, 0, 1);
                    if (bottom) {
                        textureCoordinates[textureCoordIndex + bottomOffset2] = stx;
                        textureCoordinates[textureCoordIndex + 1 + bottomOffset2] = sty;
                    }
                    if (top) {
                        textureCoordinates[textureCoordIndex] = stx;
                        textureCoordinates[textureCoordIndex + 1] = sty;
                    }

                    textureCoordIndex += 2;
                }

                if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent || shadowVolume) {
                    var attrIndex1 = attrIndex + 1;
                    var attrIndex2 = attrIndex + 2;

                    if (wall) {
                        if (i + 3 < length) {
                            var p1 = Cartesian3.fromArray(flatPositions, i + 3, p1Scratch);

                            if (recomputeNormal) {
                                var p2 = Cartesian3.fromArray(flatPositions, i + length, p2Scratch);
                                if (perPositionHeight) {
                                    adjustPosHeightsForNormal(position, p1, p2, ellipsoid);
                                }
                                Cartesian3.subtract(p1, position, p1);
                                Cartesian3.subtract(p2, position, p2);
                                normal = Cartesian3.normalize(Cartesian3.cross(p2, p1, normal), normal);
                                recomputeNormal = false;
                            }

                            if (Cartesian3.equalsEpsilon(p1, position, CesiumMath.EPSILON10)) { // if we've reached a corner
                                recomputeNormal = true;
                            }
                        }

                        if (vertexFormat.tangent || vertexFormat.bitangent) {
                            bitangent = ellipsoid.geodeticSurfaceNormal(position, bitangent);
                            if (vertexFormat.tangent) {
                                tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent);
                            }
                        }
                    } else {
                        normal = ellipsoid.geodeticSurfaceNormal(position, normal);
                        if (vertexFormat.tangent || vertexFormat.bitangent) {
                            if (perPositionHeight) {
                                scratchPerPosNormal = Cartesian3.fromArray(normals, attrIndex, scratchPerPosNormal);
                                scratchPerPosTangent = Cartesian3.cross(Cartesian3.UNIT_Z, scratchPerPosNormal, scratchPerPosTangent);
                                scratchPerPosTangent = Cartesian3.normalize(Matrix3.multiplyByVector(textureMatrix, scratchPerPosTangent, scratchPerPosTangent), scratchPerPosTangent);
                                if (vertexFormat.bitangent) {
                                    scratchPerPosBitangent = Cartesian3.normalize(Cartesian3.cross(scratchPerPosNormal, scratchPerPosTangent, scratchPerPosBitangent), scratchPerPosBitangent);
                                }
                            }

                            tangent = Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent);
                            tangent = Cartesian3.normalize(Matrix3.multiplyByVector(textureMatrix, tangent, tangent), tangent);
                            if (vertexFormat.bitangent) {
                                bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent);
                            }
                        }
                    }

                    if (vertexFormat.normal) {
                        if (options.wall) {
                            normals[attrIndex + bottomOffset] = normal.x;
                            normals[attrIndex1 + bottomOffset] = normal.y;
                            normals[attrIndex2 + bottomOffset] = normal.z;
                        } else if (bottom){
                            normals[attrIndex + bottomOffset] = -normal.x;
                            normals[attrIndex1 + bottomOffset] = -normal.y;
                            normals[attrIndex2 + bottomOffset] = -normal.z;
                        }

                        if ((top && !perPositionHeight) || wall) {
                            normals[attrIndex] = normal.x;
                            normals[attrIndex1] = normal.y;
                            normals[attrIndex2] = normal.z;
                        }
                    }

                    if (shadowVolume) {
                        if (wall) {
                            normal = ellipsoid.geodeticSurfaceNormal(position, normal);
                        }
                        extrudeNormals[attrIndex + bottomOffset] = -normal.x;
                        extrudeNormals[attrIndex1 + bottomOffset] = -normal.y;
                        extrudeNormals[attrIndex2 + bottomOffset] = -normal.z;
                    }

                    if (vertexFormat.tangent) {
                        if (options.wall) {
                            tangents[attrIndex + bottomOffset] = tangent.x;
                            tangents[attrIndex1 + bottomOffset] = tangent.y;
                            tangents[attrIndex2 + bottomOffset] = tangent.z;
                        } else if (bottom) {
                            tangents[attrIndex + bottomOffset] = -tangent.x;
                            tangents[attrIndex1 + bottomOffset] = -tangent.y;
                            tangents[attrIndex2 + bottomOffset] = -tangent.z;
                        }

                        if(top) {
                            if (perPositionHeight) {
                                tangents[attrIndex] = scratchPerPosTangent.x;
                                tangents[attrIndex1] = scratchPerPosTangent.y;
                                tangents[attrIndex2] = scratchPerPosTangent.z;
                            } else {
                                tangents[attrIndex] = tangent.x;
                                tangents[attrIndex1] = tangent.y;
                                tangents[attrIndex2] = tangent.z;
                            }
                        }
                    }

                    if (vertexFormat.bitangent) {
                        if (bottom) {
                            bitangents[attrIndex + bottomOffset] = bitangent.x;
                            bitangents[attrIndex1 + bottomOffset] = bitangent.y;
                            bitangents[attrIndex2 + bottomOffset] = bitangent.z;
                        }
                        if (top) {
                            if (perPositionHeight) {
                                bitangents[attrIndex] = scratchPerPosBitangent.x;
                                bitangents[attrIndex1] = scratchPerPosBitangent.y;
                                bitangents[attrIndex2] = scratchPerPosBitangent.z;
                            } else {
                                bitangents[attrIndex] = bitangent.x;
                                bitangents[attrIndex1] = bitangent.y;
                                bitangents[attrIndex2] = bitangent.z;
                            }
                        }
                    }
                    attrIndex += 3;
                }
            }

            if (vertexFormat.st) {
                geometry.attributes.st = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 2,
                    values : textureCoordinates
                });
            }

            if (vertexFormat.normal) {
                geometry.attributes.normal = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : normals
                });
            }

            if (vertexFormat.tangent) {
                geometry.attributes.tangent = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : tangents
                });
            }

            if (vertexFormat.bitangent) {
                geometry.attributes.bitangent = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : bitangents
                });
            }

            if (shadowVolume) {
                geometry.attributes.extrudeDirection = new GeometryAttribute({
                    componentDatatype : ComponentDatatype.FLOAT,
                    componentsPerAttribute : 3,
                    values : extrudeNormals
                });
            }
        }
        return geometry;
    }

    var createGeometryFromPositionsExtrudedPositions = [];

    function createGeometryFromPositionsExtruded(ellipsoid, polygon, granularity, hierarchy, perPositionHeight, closeTop, closeBottom, vertexFormat) {
        var geos = {
            walls : []
        };
        var i;

        if (closeTop || closeBottom) {
            var topGeo = PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygon, granularity, perPositionHeight, vertexFormat);

            var edgePoints = topGeo.attributes.position.values;
            var indices = topGeo.indices;
            var numPositions;
            var newIndices;

            if (closeTop && closeBottom) {
                var topBottomPositions = edgePoints.concat(edgePoints);

                numPositions = topBottomPositions.length / 3;

                newIndices = IndexDatatype.createTypedArray(numPositions, indices.length * 2);
                newIndices.set(indices);
                var ilength = indices.length;

                var length = numPositions / 2;

                for (i = 0; i < ilength; i += 3) {
                    var i0 = newIndices[i] + length;
                    var i1 = newIndices[i + 1] + length;
                    var i2 = newIndices[i + 2] + length;

                    newIndices[i + ilength] = i2;
                    newIndices[i + 1 + ilength] = i1;
                    newIndices[i + 2 + ilength] = i0;
                }

                topGeo.attributes.position.values = topBottomPositions;
                if (perPositionHeight) {
                    var normals = topGeo.attributes.normal.values;
                    topGeo.attributes.normal.values = new Float32Array(topBottomPositions.length);
                    topGeo.attributes.normal.values.set(normals);
                }
                topGeo.indices = newIndices;
            } else if (closeBottom) {
                numPositions = edgePoints.length / 3;
                newIndices = IndexDatatype.createTypedArray(numPositions, indices.length);

                for (i = 0; i < indices.length; i += 3) {
                    newIndices[i] = indices[i + 2];
                    newIndices[i + 1] = indices[i + 1];
                    newIndices[i + 2] = indices[i];
                }

                topGeo.indices = newIndices;
            }

            geos.topAndBottom = new GeometryInstance({
                geometry : topGeo
            });

        }

        var outerRing = hierarchy.outerRing;
        var tangentPlane = EllipsoidTangentPlane.fromPoints(outerRing, ellipsoid);
        var positions2D = tangentPlane.projectPointsOntoPlane(outerRing, createGeometryFromPositionsExtrudedPositions);

        var windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
        if (windingOrder === WindingOrder.CLOCKWISE) {
            outerRing = outerRing.slice().reverse();
        }

        var wallGeo = PolygonGeometryLibrary.computeWallGeometry(outerRing, ellipsoid, granularity, perPositionHeight);
        geos.walls.push(new GeometryInstance({
            geometry : wallGeo
        }));

        var holes = hierarchy.holes;
        for (i = 0; i < holes.length; i++) {
            var hole = holes[i];

            tangentPlane = EllipsoidTangentPlane.fromPoints(hole, ellipsoid);
            positions2D = tangentPlane.projectPointsOntoPlane(hole, createGeometryFromPositionsExtrudedPositions);

            windingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
            if (windingOrder === WindingOrder.COUNTER_CLOCKWISE) {
                hole = hole.slice().reverse();
            }

            wallGeo = PolygonGeometryLibrary.computeWallGeometry(hole, ellipsoid, granularity);
            geos.walls.push(new GeometryInstance({
                geometry : wallGeo
            }));
        }

        return geos;
    }

    /**
     * A description of a polygon on the ellipsoid. The polygon is defined by a polygon hierarchy. Polygon geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
     *
     * @alias PolygonGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {PolygonHierarchy} options.polygonHierarchy A polygon hierarchy that can include holes.
     * @param {Number} [options.height=0.0] The distance in meters between the polygon and the ellipsoid surface.
     * @param {Number} [options.extrudedHeight] The distance in meters between the polygon's extruded face and the ellipsoid surface.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
     * @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
     * @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
     *
     * @see PolygonGeometry#createGeometry
     * @see PolygonGeometry#fromPositions
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon Demo}
     *
     * @example
     * // 1. create a polygon from points
     * var polygon = new Cesium.PolygonGeometry({
     *   polygonHierarchy : new Cesium.PolygonHierarchy(
     *     Cesium.Cartesian3.fromDegreesArray([
     *       -72.0, 40.0,
     *       -70.0, 35.0,
     *       -75.0, 30.0,
     *       -70.0, 30.0,
     *       -68.0, 40.0
     *     ])
     *   )
     * });
     * var geometry = Cesium.PolygonGeometry.createGeometry(polygon);
     *
     * // 2. create a nested polygon with holes
     * var polygonWithHole = new Cesium.PolygonGeometry({
     *   polygonHierarchy : new Cesium.PolygonHierarchy(
     *     Cesium.Cartesian3.fromDegreesArray([
     *       -109.0, 30.0,
     *       -95.0, 30.0,
     *       -95.0, 40.0,
     *       -109.0, 40.0
     *     ]),
     *     [new Cesium.PolygonHierarchy(
     *       Cesium.Cartesian3.fromDegreesArray([
     *         -107.0, 31.0,
     *         -107.0, 39.0,
     *         -97.0, 39.0,
     *         -97.0, 31.0
     *       ]),
     *       [new Cesium.PolygonHierarchy(
     *         Cesium.Cartesian3.fromDegreesArray([
     *           -105.0, 33.0,
     *           -99.0, 33.0,
     *           -99.0, 37.0,
     *           -105.0, 37.0
     *         ]),
     *         [new Cesium.PolygonHierarchy(
     *           Cesium.Cartesian3.fromDegreesArray([
     *             -103.0, 34.0,
     *             -101.0, 34.0,
     *             -101.0, 36.0,
     *             -103.0, 36.0
     *           ])
     *         )]
     *       )]
     *     )]
     *   )
     * });
     * var geometry = Cesium.PolygonGeometry.createGeometry(polygonWithHole);
     *
     * // 3. create extruded polygon
     * var extrudedPolygon = new Cesium.PolygonGeometry({
     *   polygonHierarchy : new Cesium.PolygonHierarchy(
     *     Cesium.Cartesian3.fromDegreesArray([
     *       -72.0, 40.0,
     *       -70.0, 35.0,
     *       -75.0, 30.0,
     *       -70.0, 30.0,
     *       -68.0, 40.0
     *     ])
     *   ),
     *   extrudedHeight: 300000
     * });
     * var geometry = Cesium.PolygonGeometry.createGeometry(extrudedPolygon);
     */
    function PolygonGeometry(options) {
                Check.typeOf.object('options', options);
        Check.typeOf.object('options.polygonHierarchy', options.polygonHierarchy);
        if (defined(options.perPositionHeight) && options.perPositionHeight && defined(options.height)) {
            throw new DeveloperError('Cannot use both options.perPositionHeight and options.height');
        }
        
        var polygonHierarchy = options.polygonHierarchy;
        var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var stRotation = defaultValue(options.stRotation, 0.0);
        var height = defaultValue(options.height, 0.0);
        var perPositionHeight = defaultValue(options.perPositionHeight, false);

        var extrudedHeight = options.extrudedHeight;
        var extrude = defined(extrudedHeight);

        if (!perPositionHeight && extrude) {
            //Ignore extrudedHeight if it matches height
            if (CesiumMath.equalsEpsilon(height, extrudedHeight, CesiumMath.EPSILON10)) {
                extrudedHeight = undefined;
                extrude = false;
            } else {
                var h = extrudedHeight;
                extrudedHeight = Math.min(h, height);
                height = Math.max(h, height);
            }
        }

        this._vertexFormat = VertexFormat.clone(vertexFormat);
        this._ellipsoid = Ellipsoid.clone(ellipsoid);
        this._granularity = granularity;
        this._stRotation = stRotation;
        this._height = height;
        this._extrudedHeight = defaultValue(extrudedHeight, 0.0);
        this._extrude = extrude;
        this._closeTop = defaultValue(options.closeTop, true);
        this._closeBottom = defaultValue(options.closeBottom, true);
        this._polygonHierarchy = polygonHierarchy;
        this._perPositionHeight = perPositionHeight;
        this._shadowVolume = defaultValue(options.shadowVolume, false);
        this._workerName = 'createPolygonGeometry';

        var positions = polygonHierarchy.positions;
        if (!defined(positions) || positions.length < 3) {
            this._rectangle = new Rectangle();
        } else {
            this._rectangle = Rectangle.fromCartesianArray(positions, ellipsoid);
        }

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 10;
    }

    /**
     * A description of a polygon from an array of positions. Polygon geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of positions that defined the corner points of the polygon.
     * @param {Number} [options.height=0.0] The height of the polygon.
     * @param {Number} [options.extrudedHeight] The height of the polygon extrusion.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
     * @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
     * @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
     * @returns {PolygonGeometry}
     *
     *
     * @example
     * // create a polygon from points
     * var polygon = Cesium.PolygonGeometry.fromPositions({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     -72.0, 40.0,
     *     -70.0, 35.0,
     *     -75.0, 30.0,
     *     -70.0, 30.0,
     *     -68.0, 40.0
     *   ])
     * });
     * var geometry = Cesium.PolygonGeometry.createGeometry(polygon);
     *
     * @see PolygonGeometry#createGeometry
     */
    PolygonGeometry.fromPositions = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                Check.defined('options.positions', options.positions);
        
        var newOptions = {
            polygonHierarchy : {
                positions : options.positions
            },
            height : options.height,
            extrudedHeight : options.extrudedHeight,
            vertexFormat : options.vertexFormat,
            stRotation : options.stRotation,
            ellipsoid : options.ellipsoid,
            granularity : options.granularity,
            perPositionHeight : options.perPositionHeight,
            closeTop : options.closeTop,
            closeBottom: options.closeBottom
        };
        return new PolygonGeometry(newOptions);
    };

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {PolygonGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    PolygonGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        startingIndex = PolygonGeometryLibrary.packPolygonHierarchy(value._polygonHierarchy, array, startingIndex);

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        Rectangle.pack(value._rectangle, array, startingIndex);
        startingIndex += Rectangle.packedLength;

        array[startingIndex++] = value._height;
        array[startingIndex++] = value._extrudedHeight;
        array[startingIndex++] = value._granularity;
        array[startingIndex++] = value._stRotation;
        array[startingIndex++] = value._extrude ? 1.0 : 0.0;
        array[startingIndex++] = value._perPositionHeight ? 1.0 : 0.0;
        array[startingIndex++] = value._closeTop ? 1.0 : 0.0;
        array[startingIndex++] = value._closeBottom ? 1.0 : 0.0;
        array[startingIndex++] = value._shadowVolume ? 1.0 : 0.0;
        array[startingIndex] = value.packedLength;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchVertexFormat = new VertexFormat();
    var scratchRectangle = new Rectangle();

    //Only used to avoid inaability to default construct.
    var dummyOptions = {
        polygonHierarchy : {}
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {PolygonGeometry} [result] The object into which to store the result.
     */
    PolygonGeometry.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        var polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex);
        startingIndex = polygonHierarchy.startingIndex;
        delete polygonHierarchy.startingIndex;

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
        startingIndex += Rectangle.packedLength;

        var height = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var granularity = array[startingIndex++];
        var stRotation = array[startingIndex++];
        var extrude = array[startingIndex++] === 1.0;
        var perPositionHeight = array[startingIndex++] === 1.0;
        var closeTop = array[startingIndex++] === 1.0;
        var closeBottom = array[startingIndex++] === 1.0;
        var shadowVolume = array[startingIndex++] === 1.0;
        var packedLength = array[startingIndex];

        if (!defined(result)) {
            result = new PolygonGeometry(dummyOptions);
        }

        result._polygonHierarchy = polygonHierarchy;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._height = height;
        result._extrudedHeight = extrudedHeight;
        result._granularity = granularity;
        result._stRotation = stRotation;
        result._extrude = extrude;
        result._perPositionHeight = perPositionHeight;
        result._closeTop = closeTop;
        result._closeBottom = closeBottom;
        result._rectangle = Rectangle.clone(rectangle);
        result._shadowVolume = shadowVolume;
        result.packedLength = packedLength;
        return result;
    };

    /**
     * Computes the geometric representation of a polygon, including its vertices, indices, and a bounding sphere.
     *
     * @param {PolygonGeometry} polygonGeometry A description of the polygon.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    PolygonGeometry.createGeometry = function(polygonGeometry) {
        var vertexFormat = polygonGeometry._vertexFormat;
        var ellipsoid = polygonGeometry._ellipsoid;
        var granularity = polygonGeometry._granularity;
        var stRotation = polygonGeometry._stRotation;
        var height = polygonGeometry._height;
        var extrudedHeight = polygonGeometry._extrudedHeight;
        var extrude = polygonGeometry._extrude;
        var polygonHierarchy = polygonGeometry._polygonHierarchy;
        var perPositionHeight = polygonGeometry._perPositionHeight;
        var closeTop = polygonGeometry._closeTop;
        var closeBottom = polygonGeometry._closeBottom;

        var outerPositions = polygonHierarchy.positions;
        if (outerPositions.length < 3) {
            return;
        }

        var tangentPlane = EllipsoidTangentPlane.fromPoints(outerPositions, ellipsoid);

        var results = PolygonGeometryLibrary.polygonsFromHierarchy(polygonHierarchy, perPositionHeight, tangentPlane, ellipsoid);
        var hierarchy = results.hierarchy;
        var polygons = results.polygons;

        if (hierarchy.length === 0) {
            return;
        }

        outerPositions = hierarchy[0].outerRing;
        var boundingRectangle = computeBoundingRectangle(tangentPlane, outerPositions, stRotation, scratchBoundingRectangle);

        var geometry;
        var geometries = [];

        var options = {
            perPositionHeight: perPositionHeight,
            vertexFormat: vertexFormat,
            geometry: undefined,
            tangentPlane: tangentPlane,
            boundingRectangle: boundingRectangle,
            ellipsoid: ellipsoid,
            stRotation: stRotation,
            bottom: false,
            top: true,
            wall: false
        };

        var i;

        if (extrude) {
            options.top = closeTop;
            options.bottom = closeBottom;
            options.shadowVolume = polygonGeometry._shadowVolume;
            for (i = 0; i < polygons.length; i++) {
                geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], granularity, hierarchy[i], perPositionHeight, closeTop, closeBottom, vertexFormat);

                var topAndBottom;
                if (closeTop && closeBottom) {
                    topAndBottom = geometry.topAndBottom;
                    options.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(topAndBottom.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
                } else if (closeTop) {
                    topAndBottom = geometry.topAndBottom;
                    topAndBottom.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(topAndBottom.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
                    options.geometry = topAndBottom.geometry;
                } else if (closeBottom) {
                    topAndBottom = geometry.topAndBottom;
                    topAndBottom.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(topAndBottom.geometry.attributes.position.values, extrudedHeight, ellipsoid, true);
                    options.geometry = topAndBottom.geometry;
                }
                if (closeTop || closeBottom) {
                    options.wall = false;
                    topAndBottom.geometry = computeAttributes(options);
                    geometries.push(topAndBottom);
                }

                var walls = geometry.walls;
                options.wall = true;
                for ( var k = 0; k < walls.length; k++) {
                    var wall = walls[k];
                    options.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(wall.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
                    wall.geometry = computeAttributes(options);
                    geometries.push(wall);
                }
            }
        } else {
            for (i = 0; i < polygons.length; i++) {
                geometry = new GeometryInstance({
                    geometry : PolygonGeometryLibrary.createGeometryFromPositions(ellipsoid, polygons[i], granularity, perPositionHeight, vertexFormat)
                });
                geometry.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
                options.geometry = geometry.geometry;
                geometry.geometry = computeAttributes(options);
                geometries.push(geometry);
            }
        }

        geometry = GeometryPipeline.combineInstances(geometries)[0];
        geometry.attributes.position.values = new Float64Array(geometry.attributes.position.values);
        geometry.indices = IndexDatatype.createTypedArray(geometry.attributes.position.values.length / 3, geometry.indices);

        var attributes = geometry.attributes;
        var boundingSphere = BoundingSphere.fromVertices(attributes.position.values);

        if (!vertexFormat.position) {
            delete attributes.position;
        }

        return new Geometry({
            attributes : attributes,
            indices : geometry.indices,
            primitiveType : geometry.primitiveType,
            boundingSphere : boundingSphere
        });
    };

    /**
     * @private
     */
    PolygonGeometry.createShadowVolume = function(polygonGeometry, minHeightFunc, maxHeightFunc) {
        var granularity = polygonGeometry._granularity;
        var ellipsoid = polygonGeometry._ellipsoid;

        var minHeight = minHeightFunc(granularity, ellipsoid);
        var maxHeight = maxHeightFunc(granularity, ellipsoid);

        return new PolygonGeometry({
            polygonHierarchy : polygonGeometry._polygonHierarchy,
            ellipsoid : ellipsoid,
            stRotation : polygonGeometry._stRotation,
            granularity : granularity,
            perPositionHeight : false,
            extrudedHeight : minHeight,
            height : maxHeight,
            vertexFormat : VertexFormat.POSITION_ONLY,
            shadowVolume: true
        });
    };

    defineProperties(PolygonGeometry.prototype, {
        /**
         * @private
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        }
    });

    return PolygonGeometry;
});

/*global define*/
define('Core/PolygonHierarchy',[
        './defined'
    ], function(
        defined) {
    'use strict';

    /**
     * An hierarchy of linear rings which define a polygon and its holes.
     * The holes themselves may also have holes which nest inner polygons.
     * @alias PolygonHierarchy
     * @constructor
     *
     * @param {Cartesian3[]} [positions] A linear ring defining the outer boundary of the polygon or hole.
     * @param {PolygonHierarchy[]} [holes] An array of polygon hierarchies defining holes in the polygon.
     */
    function PolygonHierarchy(positions, holes) {
        /**
         * A linear ring defining the outer boundary of the polygon or hole.
         * @type {Cartesian3[]}
         */
        this.positions = defined(positions) ? positions : [];

        /**
         * An array of polygon hierarchies defining holes in the polygon.
         * @type {PolygonHierarchy[]}
         */
        this.holes = defined(holes) ? holes : [];
    }

    return PolygonHierarchy;
});

/*global define*/
define('Core/PolygonOutlineGeometry',[
        './arrayRemoveDuplicates',
        './BoundingSphere',
        './Cartesian3',
        './Check',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './EllipsoidTangentPlane',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './GeometryInstance',
        './GeometryPipeline',
        './IndexDatatype',
        './Math',
        './PolygonGeometryLibrary',
        './PolygonPipeline',
        './PrimitiveType',
        './Queue',
        './WindingOrder'
    ], function(
        arrayRemoveDuplicates,
        BoundingSphere,
        Cartesian3,
        Check,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        EllipsoidTangentPlane,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        GeometryInstance,
        GeometryPipeline,
        IndexDatatype,
        CesiumMath,
        PolygonGeometryLibrary,
        PolygonPipeline,
        PrimitiveType,
        Queue,
        WindingOrder) {
    'use strict';
    var createGeometryFromPositionsPositions = [];
    var createGeometryFromPositionsSubdivided = [];

    function createGeometryFromPositions(ellipsoid, positions, minDistance, perPositionHeight) {
        var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid);
        var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions);

        var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
        if (originalWindingOrder === WindingOrder.CLOCKWISE) {
            positions2D.reverse();
            positions = positions.slice().reverse();
        }

        var subdividedPositions;
        var i;

        var length = positions.length;
        var index = 0;

        if (!perPositionHeight) {
            var numVertices = 0;
            for (i = 0; i < length; i++) {
                numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
            }
            subdividedPositions = new Float64Array(numVertices * 3);
            for (i = 0; i < length; i++) {
                var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
                var tempPositionsLength = tempPositions.length;
                for (var j = 0; j < tempPositionsLength; ++j) {
                    subdividedPositions[index++] = tempPositions[j];
                }
            }
        } else {
            subdividedPositions = new Float64Array(length * 2 * 3);
            for (i = 0; i < length; i++) {
                var p0 = positions[i];
                var p1 = positions[(i + 1) % length];
                subdividedPositions[index++] = p0.x;
                subdividedPositions[index++] = p0.y;
                subdividedPositions[index++] = p0.z;
                subdividedPositions[index++] = p1.x;
                subdividedPositions[index++] = p1.y;
                subdividedPositions[index++] = p1.z;
            }
        }

        length = subdividedPositions.length / 3;
        var indicesSize = length * 2;
        var indices = IndexDatatype.createTypedArray(length, indicesSize);
        index = 0;
        for (i = 0; i < length - 1; i++) {
            indices[index++] = i;
            indices[index++] = i + 1;
        }
        indices[index++] = length - 1;
        indices[index++] = 0;

        return new GeometryInstance({
            geometry : new Geometry({
                attributes : new GeometryAttributes({
                    position : new GeometryAttribute({
                        componentDatatype : ComponentDatatype.DOUBLE,
                        componentsPerAttribute : 3,
                        values : subdividedPositions
                    })
                }),
                indices : indices,
                primitiveType : PrimitiveType.LINES
            })
        });
    }

    function createGeometryFromPositionsExtruded(ellipsoid, positions, minDistance, perPositionHeight) {
        var tangentPlane = EllipsoidTangentPlane.fromPoints(positions, ellipsoid);
        var positions2D = tangentPlane.projectPointsOntoPlane(positions, createGeometryFromPositionsPositions);

        var originalWindingOrder = PolygonPipeline.computeWindingOrder2D(positions2D);
        if (originalWindingOrder === WindingOrder.CLOCKWISE) {
            positions2D.reverse();
            positions = positions.slice().reverse();
        }

        var subdividedPositions;
        var i;

        var length = positions.length;
        var corners = new Array(length);
        var index = 0;

        if (!perPositionHeight) {
            var numVertices = 0;
            for (i = 0; i < length; i++) {
                numVertices += PolygonGeometryLibrary.subdivideLineCount(positions[i], positions[(i + 1) % length], minDistance);
            }

            subdividedPositions = new Float64Array(numVertices * 3 * 2);
            for (i = 0; i < length; ++i) {
                corners[i] = index / 3;
                var tempPositions = PolygonGeometryLibrary.subdivideLine(positions[i], positions[(i + 1) % length], minDistance, createGeometryFromPositionsSubdivided);
                var tempPositionsLength = tempPositions.length;
                for (var j = 0; j < tempPositionsLength; ++j) {
                    subdividedPositions[index++] = tempPositions[j];
                }
            }
        } else {
            subdividedPositions = new Float64Array(length * 2 * 3 * 2);
            for (i = 0; i < length; ++i) {
                corners[i] = index / 3;
                var p0 = positions[i];
                var p1 = positions[(i + 1) % length];

                subdividedPositions[index++] = p0.x;
                subdividedPositions[index++] = p0.y;
                subdividedPositions[index++] = p0.z;
                subdividedPositions[index++] = p1.x;
                subdividedPositions[index++] = p1.y;
                subdividedPositions[index++] = p1.z;
            }
        }

        length = subdividedPositions.length / (3 * 2);
        var cornersLength = corners.length;

        var indicesSize = ((length * 2) + cornersLength) * 2;
        var indices = IndexDatatype.createTypedArray(length, indicesSize);

        index = 0;
        for (i = 0; i < length; ++i) {
            indices[index++] = i;
            indices[index++] = (i + 1) % length;
            indices[index++] = i + length;
            indices[index++] = ((i + 1) % length) + length;
        }

        for (i = 0; i < cornersLength; i++) {
            var corner = corners[i];
            indices[index++] = corner;
            indices[index++] = corner + length;
        }

        return new GeometryInstance({
            geometry : new Geometry({
                attributes : new GeometryAttributes({
                    position : new GeometryAttribute({
                        componentDatatype : ComponentDatatype.DOUBLE,
                        componentsPerAttribute : 3,
                        values : subdividedPositions
                    })
                }),
                indices : indices,
                primitiveType : PrimitiveType.LINES
            })
        });
    }

    /**
     * A description of the outline of a polygon on the ellipsoid. The polygon is defined by a polygon hierarchy.
     *
     * @alias PolygonOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {PolygonHierarchy} options.polygonHierarchy A polygon hierarchy that can include holes.
     * @param {Number} [options.height=0.0] The distance in meters between the polygon and the ellipsoid surface.
     * @param {Number} [options.extrudedHeight] The distance in meters between the polygon's extruded face and the ellipsoid surface.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
     *
     * @see PolygonOutlineGeometry#createGeometry
     * @see PolygonOutlineGeometry#fromPositions
     *
     * @example
     * // 1. create a polygon outline from points
     * var polygon = new Cesium.PolygonOutlineGeometry({
     *   polygonHierarchy : new Cesium.PolygonHierarchy(
     *     Cesium.Cartesian3.fromDegreesArray([
     *       -72.0, 40.0,
     *       -70.0, 35.0,
     *       -75.0, 30.0,
     *       -70.0, 30.0,
     *       -68.0, 40.0
     *     ])
     *   )
     * });
     * var geometry = Cesium.PolygonOutlineGeometry.createGeometry(polygon);
     *
     * // 2. create a nested polygon with holes outline
     * var polygonWithHole = new Cesium.PolygonOutlineGeometry({
     *   polygonHierarchy : new Cesium.PolygonHierarchy(
     *     Cesium.Cartesian3.fromDegreesArray([
     *       -109.0, 30.0,
     *       -95.0, 30.0,
     *       -95.0, 40.0,
     *       -109.0, 40.0
     *     ]),
     *     [new Cesium.PolygonHierarchy(
     *       Cesium.Cartesian3.fromDegreesArray([
     *         -107.0, 31.0,
     *         -107.0, 39.0,
     *         -97.0, 39.0,
     *         -97.0, 31.0
     *       ]),
     *       [new Cesium.PolygonHierarchy(
     *         Cesium.Cartesian3.fromDegreesArray([
     *           -105.0, 33.0,
     *           -99.0, 33.0,
     *           -99.0, 37.0,
     *           -105.0, 37.0
     *         ]),
     *         [new Cesium.PolygonHierarchy(
     *           Cesium.Cartesian3.fromDegreesArray([
     *             -103.0, 34.0,
     *             -101.0, 34.0,
     *             -101.0, 36.0,
     *             -103.0, 36.0
     *           ])
     *         )]
     *       )]
     *     )]
     *   )
     * });
     * var geometry = Cesium.PolygonOutlineGeometry.createGeometry(polygonWithHole);
     *
     * // 3. create extruded polygon outline
     * var extrudedPolygon = new Cesium.PolygonOutlineGeometry({
     *   polygonHierarchy : new Cesium.PolygonHierarchy(
     *     Cesium.Cartesian3.fromDegreesArray([
     *       -72.0, 40.0,
     *       -70.0, 35.0,
     *       -75.0, 30.0,
     *       -70.0, 30.0,
     *       -68.0, 40.0
     *     ])
     *   ),
     *   extrudedHeight: 300000
     * });
     * var geometry = Cesium.PolygonOutlineGeometry.createGeometry(extrudedPolygon);
     */
    function PolygonOutlineGeometry(options) {
                Check.typeOf.object('options', options);
        Check.typeOf.object('options.polygonHierarchy', options.polygonHierarchy);

        if (defined(options.perPositionHeight) && options.perPositionHeight && defined(options.height)) {
            throw new DeveloperError('Cannot use both options.perPositionHeight and options.height');
        }
        
        var polygonHierarchy = options.polygonHierarchy;
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var height = defaultValue(options.height, 0.0);
        var perPositionHeight = defaultValue(options.perPositionHeight, false);

        var extrudedHeight = options.extrudedHeight;
        var extrude = defined(extrudedHeight);
        if (extrude && !perPositionHeight) {
            var h = extrudedHeight;
            extrudedHeight = Math.min(h, height);
            height = Math.max(h, height);
        }

        this._ellipsoid = Ellipsoid.clone(ellipsoid);
        this._granularity = granularity;
        this._height = height;
        this._extrudedHeight = defaultValue(extrudedHeight, 0.0);
        this._extrude = extrude;
        this._polygonHierarchy = polygonHierarchy;
        this._perPositionHeight = perPositionHeight;
        this._workerName = 'createPolygonOutlineGeometry';

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = PolygonGeometryLibrary.computeHierarchyPackedLength(polygonHierarchy) + Ellipsoid.packedLength + 6;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {PolygonOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    PolygonOutlineGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        startingIndex = PolygonGeometryLibrary.packPolygonHierarchy(value._polygonHierarchy, array, startingIndex);

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex++] = value._height;
        array[startingIndex++] = value._extrudedHeight;
        array[startingIndex++] = value._granularity;
        array[startingIndex++] = value._extrude ? 1.0 : 0.0;
        array[startingIndex++] = value._perPositionHeight ? 1.0 : 0.0;
        array[startingIndex++] = value.packedLength;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var dummyOptions = {
        polygonHierarchy : {}
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {PolygonOutlineGeometry} [result] The object into which to store the result.
     * @returns {PolygonOutlineGeometry} The modified result parameter or a new PolygonOutlineGeometry instance if one was not provided.
     */
    PolygonOutlineGeometry.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        var polygonHierarchy = PolygonGeometryLibrary.unpackPolygonHierarchy(array, startingIndex);
        startingIndex = polygonHierarchy.startingIndex;
        delete polygonHierarchy.startingIndex;

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var height = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var granularity = array[startingIndex++];
        var extrude = array[startingIndex++] === 1.0;
        var perPositionHeight = array[startingIndex++] === 1.0;
        var packedLength = array[startingIndex++];

        if (!defined(result)) {
            result = new PolygonOutlineGeometry(dummyOptions);
        }

        result._polygonHierarchy = polygonHierarchy;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._height = height;
        result._extrudedHeight = extrudedHeight;
        result._granularity = granularity;
        result._extrude = extrude;
        result._perPositionHeight = perPositionHeight;
        result.packedLength = packedLength;

        return result;
    };

    /**
     * A description of a polygon outline from an array of positions.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of positions that defined the corner points of the polygon.
     * @param {Number} [options.height=0.0] The height of the polygon.
     * @param {Number} [options.extrudedHeight] The height of the polygon extrusion.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Boolean} [options.perPositionHeight=false] Use the height of options.positions for each position instead of using options.height to determine the height.
     * @returns {PolygonOutlineGeometry}
     *
     *
     * @example
     * // create a polygon from points
     * var polygon = Cesium.PolygonOutlineGeometry.fromPositions({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     -72.0, 40.0,
     *     -70.0, 35.0,
     *     -75.0, 30.0,
     *     -70.0, 30.0,
     *     -68.0, 40.0
     *   ])
     * });
     * var geometry = Cesium.PolygonOutlineGeometry.createGeometry(polygon);
     *
     * @see PolygonOutlineGeometry#createGeometry
     */
    PolygonOutlineGeometry.fromPositions = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                Check.defined('options.positions', options.positions);
        
        var newOptions = {
            polygonHierarchy : {
                positions : options.positions
            },
            height : options.height,
            extrudedHeight : options.extrudedHeight,
            ellipsoid : options.ellipsoid,
            granularity : options.granularity,
            perPositionHeight : options.perPositionHeight
        };
        return new PolygonOutlineGeometry(newOptions);
    };

    /**
     * Computes the geometric representation of a polygon outline, including its vertices, indices, and a bounding sphere.
     *
     * @param {PolygonOutlineGeometry} polygonGeometry A description of the polygon outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    PolygonOutlineGeometry.createGeometry = function(polygonGeometry) {
        var ellipsoid = polygonGeometry._ellipsoid;
        var granularity = polygonGeometry._granularity;
        var height = polygonGeometry._height;
        var extrudedHeight = polygonGeometry._extrudedHeight;
        var extrude = polygonGeometry._extrude;
        var polygonHierarchy = polygonGeometry._polygonHierarchy;
        var perPositionHeight = polygonGeometry._perPositionHeight;

        // create from a polygon hierarchy
        // Algorithm adapted from http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
        var polygons = [];
        var queue = new Queue();
        queue.enqueue(polygonHierarchy);
        var i;
        while (queue.length !== 0) {
            var outerNode = queue.dequeue();
            var outerRing = outerNode.positions;
            outerRing = arrayRemoveDuplicates(outerRing, Cartesian3.equalsEpsilon, true);
            if (outerRing.length < 3) {
                continue;
            }

            var numChildren = outerNode.holes ? outerNode.holes.length : 0;
            // The outer polygon contains inner polygons
            for (i = 0; i < numChildren; i++) {
                var hole = outerNode.holes[i];
                hole.positions = arrayRemoveDuplicates(hole.positions, Cartesian3.equalsEpsilon, true);
                if (hole.positions.length < 3) {
                    continue;
                }
                polygons.push(hole.positions);

                var numGrandchildren = 0;
                if (defined(hole.holes)) {
                    numGrandchildren = hole.holes.length;
                }

                for ( var j = 0; j < numGrandchildren; j++) {
                    queue.enqueue(hole.holes[j]);
                }
            }

            polygons.push(outerRing);
        }

        if (polygons.length === 0) {
            return undefined;
        }

        var geometry;
        var geometries = [];
        var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);

        if (extrude) {
            for (i = 0; i < polygons.length; i++) {
                geometry = createGeometryFromPositionsExtruded(ellipsoid, polygons[i], minDistance, perPositionHeight);
                geometry.geometry = PolygonGeometryLibrary.scaleToGeodeticHeightExtruded(geometry.geometry, height, extrudedHeight, ellipsoid, perPositionHeight);
                geometries.push(geometry);
            }
        } else {
            for (i = 0; i < polygons.length; i++) {
                geometry = createGeometryFromPositions(ellipsoid, polygons[i], minDistance, perPositionHeight);
                geometry.geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.geometry.attributes.position.values, height, ellipsoid, !perPositionHeight);
                geometries.push(geometry);
            }
        }

        geometry = GeometryPipeline.combineInstances(geometries)[0];
        var boundingSphere = BoundingSphere.fromVertices(geometry.attributes.position.values);

        return new Geometry({
            attributes : geometry.attributes,
            indices : geometry.indices,
            primitiveType : geometry.primitiveType,
            boundingSphere : boundingSphere
        });
    };

    return PolygonOutlineGeometry;
});

/*global define*/
define('Core/PolylineGeometry',[
        './arrayRemoveDuplicates',
        './BoundingSphere',
        './Cartesian3',
        './Color',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './GeometryType',
        './IndexDatatype',
        './Math',
        './PolylinePipeline',
        './PrimitiveType',
        './VertexFormat'
    ], function(
        arrayRemoveDuplicates,
        BoundingSphere,
        Cartesian3,
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        GeometryType,
        IndexDatatype,
        CesiumMath,
        PolylinePipeline,
        PrimitiveType,
        VertexFormat) {
    'use strict';

    var scratchInterpolateColorsArray = [];

    function interpolateColors(p0, p1, color0, color1, numPoints) {
        var colors = scratchInterpolateColorsArray;
        colors.length = numPoints;
        var i;

        var r0 = color0.red;
        var g0 = color0.green;
        var b0 = color0.blue;
        var a0 = color0.alpha;

        var r1 = color1.red;
        var g1 = color1.green;
        var b1 = color1.blue;
        var a1 = color1.alpha;

        if (Color.equals(color0, color1)) {
            for (i = 0; i < numPoints; i++) {
                colors[i] = Color.clone(color0);
            }
            return colors;
        }

        var redPerVertex = (r1 - r0) / numPoints;
        var greenPerVertex = (g1 - g0) / numPoints;
        var bluePerVertex = (b1 - b0) / numPoints;
        var alphaPerVertex = (a1 - a0) / numPoints;

        for (i = 0; i < numPoints; i++) {
            colors[i] = new Color(r0 + i * redPerVertex, g0 + i * greenPerVertex, b0 + i * bluePerVertex, a0 + i * alphaPerVertex);
        }

        return colors;
    }

    /**
     * A description of a polyline modeled as a line strip; the first two positions define a line segment,
     * and each additional position defines a line segment from the previous position. The polyline is capable of
     * displaying with a material.
     *
     * @alias PolylineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the positions in the polyline as a line strip.
     * @param {Number} [options.width=1.0] The width in pixels.
     * @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors.
     * @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices.
     * @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     *
     * @exception {DeveloperError} At least two positions are required.
     * @exception {DeveloperError} width must be greater than or equal to one.
     * @exception {DeveloperError} colors has an invalid length.
     *
     * @see PolylineGeometry#createGeometry
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline.html|Cesium Sandcastle Polyline Demo}
     *
     * @example
     * // A polyline with two connected line segments
     * var polyline = new Cesium.PolylineGeometry({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     0.0, 0.0,
     *     5.0, 0.0,
     *     5.0, 5.0
     *   ]),
     *   width : 10.0
     * });
     * var geometry = Cesium.PolylineGeometry.createGeometry(polyline);
     */
    function PolylineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.positions;
        var colors = options.colors;
        var width = defaultValue(options.width, 1.0);
        var colorsPerVertex = defaultValue(options.colorsPerVertex, false);

                if ((!defined(positions)) || (positions.length < 2)) {
            throw new DeveloperError('At least two positions are required.');
        }
        if (typeof width !== 'number') {
            throw new DeveloperError('width must be a number');
        }
        if (defined(colors) && ((colorsPerVertex && colors.length < positions.length) || (!colorsPerVertex && colors.length < positions.length - 1))) {
            throw new DeveloperError('colors has an invalid length.');
        }
        
        this._positions = positions;
        this._colors = colors;
        this._width = width;
        this._colorsPerVertex = colorsPerVertex;
        this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
        this._followSurface = defaultValue(options.followSurface, true);
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
        this._workerName = 'createPolylineGeometry';

        var numComponents = 1 + positions.length * Cartesian3.packedLength;
        numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1;

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 4;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {PolylineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    PolylineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        var colors = value._colors;
        length = defined(colors) ? colors.length : 0.0;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
            Color.pack(colors[i], array, startingIndex);
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        array[startingIndex++] = value._width;
        array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0;
        array[startingIndex++] = value._followSurface ? 1.0 : 0.0;
        array[startingIndex]   = value._granularity;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchVertexFormat = new VertexFormat();
    var scratchOptions = {
        positions : undefined,
        colors : undefined,
        ellipsoid : scratchEllipsoid,
        vertexFormat : scratchVertexFormat,
        width : undefined,
        colorsPerVertex : undefined,
        followSurface : undefined,
        granularity : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {PolylineGeometry} [result] The object into which to store the result.
     * @returns {PolylineGeometry} The modified result parameter or a new PolylineGeometry instance if one was not provided.
     */
    PolylineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        length = array[startingIndex++];
        var colors = length > 0 ? new Array(length) : undefined;

        for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
            colors[i] = Color.unpack(array, startingIndex);
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var width = array[startingIndex++];
        var colorsPerVertex = array[startingIndex++] === 1.0;
        var followSurface = array[startingIndex++] === 1.0;
        var granularity = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.positions = positions;
            scratchOptions.colors = colors;
            scratchOptions.width = width;
            scratchOptions.colorsPerVertex = colorsPerVertex;
            scratchOptions.followSurface = followSurface;
            scratchOptions.granularity = granularity;
            return new PolylineGeometry(scratchOptions);
        }

        result._positions = positions;
        result._colors = colors;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._width = width;
        result._colorsPerVertex = colorsPerVertex;
        result._followSurface = followSurface;
        result._granularity = granularity;

        return result;
    };

    var scratchCartesian3 = new Cartesian3();
    var scratchPosition = new Cartesian3();
    var scratchPrevPosition = new Cartesian3();
    var scratchNextPosition = new Cartesian3();

    /**
     * Computes the geometric representation of a polyline, including its vertices, indices, and a bounding sphere.
     *
     * @param {PolylineGeometry} polylineGeometry A description of the polyline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    PolylineGeometry.createGeometry = function(polylineGeometry) {
        var width = polylineGeometry._width;
        var vertexFormat = polylineGeometry._vertexFormat;
        var colors = polylineGeometry._colors;
        var colorsPerVertex = polylineGeometry._colorsPerVertex;
        var followSurface = polylineGeometry._followSurface;
        var granularity = polylineGeometry._granularity;
        var ellipsoid = polylineGeometry._ellipsoid;

        var i;
        var j;
        var k;

        var positions = arrayRemoveDuplicates(polylineGeometry._positions, Cartesian3.equalsEpsilon);
        var positionsLength = positions.length;

        // A width of a pixel or less is not a valid geometry, but in order to support external data
        // that may have errors we treat this as an empty geometry.
        if (positionsLength < 2 || width <= 0.0) {
            return undefined;
        }

        if (followSurface) {
            var heights = PolylinePipeline.extractHeights(positions, ellipsoid);
            var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);

            if (defined(colors)) {
                var colorLength = 1;
                for (i = 0; i < positionsLength - 1; ++i) {
                    colorLength += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance);
                }

                var newColors = new Array(colorLength);
                var newColorIndex = 0;

                for (i = 0; i < positionsLength - 1; ++i) {
                    var p0 = positions[i];
                    var p1 = positions[i+1];
                    var c0 = colors[i];

                    var numColors = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
                    if (colorsPerVertex && i < colorLength) {
                        var c1 = colors[i+1];
                        var interpolatedColors = interpolateColors(p0, p1, c0, c1, numColors);
                        var interpolatedColorsLength = interpolatedColors.length;
                        for (j = 0; j < interpolatedColorsLength; ++j) {
                            newColors[newColorIndex++] = interpolatedColors[j];
                        }
                    } else {
                        for (j = 0; j < numColors; ++j) {
                            newColors[newColorIndex++] = Color.clone(c0);
                        }
                    }
                }

                newColors[newColorIndex] = Color.clone(colors[colors.length-1]);
                colors = newColors;

                scratchInterpolateColorsArray.length = 0;
            }

            positions = PolylinePipeline.generateCartesianArc({
                positions: positions,
                minDistance: minDistance,
                ellipsoid: ellipsoid,
                height: heights
            });
        }

        positionsLength = positions.length;
        var size = positionsLength * 4.0 - 4.0;

        var finalPositions = new Float64Array(size * 3);
        var prevPositions = new Float64Array(size * 3);
        var nextPositions = new Float64Array(size * 3);
        var expandAndWidth = new Float32Array(size * 2);
        var st = vertexFormat.st ? new Float32Array(size * 2) : undefined;
        var finalColors = defined(colors) ? new Uint8Array(size * 4) : undefined;

        var positionIndex = 0;
        var expandAndWidthIndex = 0;
        var stIndex = 0;
        var colorIndex = 0;
        var position;

        for (j = 0; j < positionsLength; ++j) {
            if (j === 0) {
                position = scratchCartesian3;
                Cartesian3.subtract(positions[0], positions[1], position);
                Cartesian3.add(positions[0], position, position);
            } else {
                position = positions[j - 1];
            }

            Cartesian3.clone(position, scratchPrevPosition);
            Cartesian3.clone(positions[j], scratchPosition);

            if (j === positionsLength - 1) {
                position = scratchCartesian3;
                Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], position);
                Cartesian3.add(positions[positionsLength - 1], position, position);
            } else {
                position = positions[j + 1];
            }

            Cartesian3.clone(position, scratchNextPosition);

            var color0, color1;
            if (defined(finalColors)) {
                if (j !== 0 && !colorsPerVertex) {
                    color0 = colors[j - 1];
                } else {
                    color0 = colors[j];
                }

                if (j !== positionsLength - 1) {
                    color1 = colors[j];
                }
            }

            var startK = j === 0 ? 2 : 0;
            var endK = j === positionsLength - 1 ? 2 : 4;

            for (k = startK; k < endK; ++k) {
                Cartesian3.pack(scratchPosition, finalPositions, positionIndex);
                Cartesian3.pack(scratchPrevPosition, prevPositions, positionIndex);
                Cartesian3.pack(scratchNextPosition, nextPositions, positionIndex);
                positionIndex += 3;

                var direction = (k - 2 < 0) ? -1.0 : 1.0;
                expandAndWidth[expandAndWidthIndex++] = 2 * (k % 2) - 1;       // expand direction
                expandAndWidth[expandAndWidthIndex++] = direction * width;

                if (vertexFormat.st) {
                    st[stIndex++] = j / (positionsLength - 1);
                    st[stIndex++] = Math.max(expandAndWidth[expandAndWidthIndex - 2], 0.0);
                }

                if (defined(finalColors)) {
                    var color = (k < 2) ? color0 : color1;

                    finalColors[colorIndex++] = Color.floatToByte(color.red);
                    finalColors[colorIndex++] = Color.floatToByte(color.green);
                    finalColors[colorIndex++] = Color.floatToByte(color.blue);
                    finalColors[colorIndex++] = Color.floatToByte(color.alpha);
                }
            }
        }

        var attributes = new GeometryAttributes();

        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : finalPositions
        });

        attributes.prevPosition = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : prevPositions
        });

        attributes.nextPosition = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : nextPositions
        });

        attributes.expandAndWidth = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 2,
            values : expandAndWidth
        });

        if (vertexFormat.st) {
            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : st
            });
        }

        if (defined(finalColors)) {
            attributes.color = new GeometryAttribute({
                componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
                componentsPerAttribute : 4,
                values : finalColors,
                normalize : true
            });
        }

        var indices = IndexDatatype.createTypedArray(size, positionsLength * 6 - 6);
        var index = 0;
        var indicesIndex = 0;
        var length = positionsLength - 1.0;
        for (j = 0; j < length; ++j) {
            indices[indicesIndex++] = index;
            indices[indicesIndex++] = index + 2;
            indices[indicesIndex++] = index + 1;

            indices[indicesIndex++] = index + 1;
            indices[indicesIndex++] = index + 2;
            indices[indicesIndex++] = index + 3;

            index += 4;
        }

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : BoundingSphere.fromPoints(positions),
            geometryType : GeometryType.POLYLINES
        });
    };

    return PolylineGeometry;
});

/*global define*/
define('Core/PolylineVolumeGeometry',[
        './arrayRemoveDuplicates',
        './BoundingRectangle',
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './CornerType',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './GeometryPipeline',
        './IndexDatatype',
        './Math',
        './oneTimeWarning',
        './PolygonPipeline',
        './PolylineVolumeGeometryLibrary',
        './PrimitiveType',
        './VertexFormat',
        './WindingOrder'
    ], function(
        arrayRemoveDuplicates,
        BoundingRectangle,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        CornerType,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        GeometryPipeline,
        IndexDatatype,
        CesiumMath,
        oneTimeWarning,
        PolygonPipeline,
        PolylineVolumeGeometryLibrary,
        PrimitiveType,
        VertexFormat,
        WindingOrder) {
    'use strict';

    function computeAttributes(combinedPositions, shape, boundingRectangle, vertexFormat) {
        var attributes = new GeometryAttributes();
        if (vertexFormat.position) {
            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : combinedPositions
            });
        }
        var shapeLength = shape.length;
        var vertexCount = combinedPositions.length / 3;
        var length = (vertexCount - shapeLength * 2) / (shapeLength * 2);
        var firstEndIndices = PolygonPipeline.triangulate(shape);

        var indicesCount = (length - 1) * (shapeLength) * 6 + firstEndIndices.length * 2;
        var indices = IndexDatatype.createTypedArray(vertexCount, indicesCount);
        var i, j;
        var ll, ul, ur, lr;
        var offset = shapeLength * 2;
        var index = 0;
        for (i = 0; i < length - 1; i++) {
            for (j = 0; j < shapeLength - 1; j++) {
                ll = j * 2 + i * shapeLength * 2;
                lr = ll + offset;
                ul = ll + 1;
                ur = ul + offset;

                indices[index++] = ul;
                indices[index++] = ll;
                indices[index++] = ur;
                indices[index++] = ur;
                indices[index++] = ll;
                indices[index++] = lr;
            }
            ll = shapeLength * 2 - 2 + i * shapeLength * 2;
            ul = ll + 1;
            ur = ul + offset;
            lr = ll + offset;

            indices[index++] = ul;
            indices[index++] = ll;
            indices[index++] = ur;
            indices[index++] = ur;
            indices[index++] = ll;
            indices[index++] = lr;
        }

        if (vertexFormat.st || vertexFormat.tangent || vertexFormat.bitangent) { // st required for tangent/bitangent calculation
            var st = new Float32Array(vertexCount * 2);
            var lengthSt = 1 / (length - 1);
            var heightSt = 1 / (boundingRectangle.height);
            var heightOffset = boundingRectangle.height / 2;
            var s, t;
            var stindex = 0;
            for (i = 0; i < length; i++) {
                s = i * lengthSt;
                t = heightSt * (shape[0].y + heightOffset);
                st[stindex++] = s;
                st[stindex++] = t;
                for (j = 1; j < shapeLength; j++) {
                    t = heightSt * (shape[j].y + heightOffset);
                    st[stindex++] = s;
                    st[stindex++] = t;
                    st[stindex++] = s;
                    st[stindex++] = t;
                }
                t = heightSt * (shape[0].y + heightOffset);
                st[stindex++] = s;
                st[stindex++] = t;
            }
            for (j = 0; j < shapeLength; j++) {
                s = 0;
                t = heightSt * (shape[j].y + heightOffset);
                st[stindex++] = s;
                st[stindex++] = t;
            }
            for (j = 0; j < shapeLength; j++) {
                s = (length - 1) * lengthSt;
                t = heightSt * (shape[j].y + heightOffset);
                st[stindex++] = s;
                st[stindex++] = t;
            }

            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : new Float32Array(st)
            });
        }

        var endOffset = vertexCount - shapeLength * 2;
        for (i = 0; i < firstEndIndices.length; i += 3) {
            var v0 = firstEndIndices[i] + endOffset;
            var v1 = firstEndIndices[i + 1] + endOffset;
            var v2 = firstEndIndices[i + 2] + endOffset;

            indices[index++] = v0;
            indices[index++] = v1;
            indices[index++] = v2;
            indices[index++] = v2 + shapeLength;
            indices[index++] = v1 + shapeLength;
            indices[index++] = v0 + shapeLength;
        }

        var geometry = new Geometry({
            attributes : attributes,
            indices : indices,
            boundingSphere : BoundingSphere.fromVertices(combinedPositions),
            primitiveType : PrimitiveType.TRIANGLES
        });

        if (vertexFormat.normal) {
            geometry = GeometryPipeline.computeNormal(geometry);
        }

        if (vertexFormat.tangent || vertexFormat.bitangent) {
            try {
                geometry = GeometryPipeline.computeTangentAndBitangent(geometry);
            } catch (e) {
                oneTimeWarning('polyline-volume-tangent-bitangent', 'Unable to compute tangents and bitangents for polyline volume geometry');
                //TODO https://github.com/AnalyticalGraphicsInc/cesium/issues/3609
            }

            if (!vertexFormat.tangent) {
                geometry.attributes.tangent = undefined;
            }
            if (!vertexFormat.bitangent) {
                geometry.attributes.bitangent = undefined;
            }
            if (!vertexFormat.st) {
                geometry.attributes.st = undefined;
            }
        }

        return geometry;
    }

    /**
     * A description of a polyline with a volume (a 2D shape extruded along a polyline).
     *
     * @alias PolylineVolumeGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.polylinePositions An array of {@link Cartesain3} positions that define the center of the polyline volume.
     * @param {Cartesian2[]} options.shapePositions An array of {@link Cartesian2} positions that define the shape to be extruded along the polyline
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
     *
     * @see PolylineVolumeGeometry#createGeometry
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo}
     *
     * @example
     * function computeCircle(radius) {
     *   var positions = [];
     *   for (var i = 0; i < 360; i++) {
     *     var radians = Cesium.Math.toRadians(i);
     *     positions.push(new Cesium.Cartesian2(radius * Math.cos(radians), radius * Math.sin(radians)));
     *   }
     *   return positions;
     * }
     *
     * var volume = new Cesium.PolylineVolumeGeometry({
     *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY,
     *   polylinePositions : Cesium.Cartesian3.fromDegreesArray([
     *     -72.0, 40.0,
     *     -70.0, 35.0
     *   ]),
     *   shapePositions : computeCircle(100000.0)
     * });
     */
    function PolylineVolumeGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.polylinePositions;
        var shape = options.shapePositions;

                if (!defined(positions)) {
            throw new DeveloperError('options.polylinePositions is required.');
        }
        if (!defined(shape)) {
            throw new DeveloperError('options.shapePositions is required.');
        }
        
        this._positions = positions;
        this._shape = shape;
        this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
        this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
        this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._workerName = 'createPolylineVolumeGeometry';

        var numComponents = 1 + positions.length * Cartesian3.packedLength;
        numComponents += 1 + shape.length * Cartesian2.packedLength;

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 2;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {PolylineVolumeGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    PolylineVolumeGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        var shape = value._shape;
        length = shape.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
            Cartesian2.pack(shape[i], array, startingIndex);
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        array[startingIndex++] = value._cornerType;
        array[startingIndex]   = value._granularity;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchVertexFormat = new VertexFormat();
    var scratchOptions = {
        polylinePositions : undefined,
        shapePositions : undefined,
        ellipsoid : scratchEllipsoid,
        vertexFormat : scratchVertexFormat,
        cornerType : undefined,
        granularity : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {PolylineVolumeGeometry} [result] The object into which to store the result.
     * @returns {PolylineVolumeGeometry} The modified result parameter or a new PolylineVolumeGeometry instance if one was not provided.
     */
    PolylineVolumeGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        length = array[startingIndex++];
        var shape = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
            shape[i] = Cartesian2.unpack(array, startingIndex);
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var cornerType = array[startingIndex++];
        var granularity = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.polylinePositions = positions;
            scratchOptions.shapePositions = shape;
            scratchOptions.cornerType = cornerType;
            scratchOptions.granularity = granularity;
            return new PolylineVolumeGeometry(scratchOptions);
        }

        result._positions = positions;
        result._shape = shape;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._cornerType = cornerType;
        result._granularity = granularity;

        return result;
    };

    var brScratch = new BoundingRectangle();

    /**
     * Computes the geometric representation of a polyline with a volume, including its vertices, indices, and a bounding sphere.
     *
     * @param {PolylineVolumeGeometry} polylineVolumeGeometry A description of the polyline volume.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    PolylineVolumeGeometry.createGeometry = function(polylineVolumeGeometry) {
        var positions = polylineVolumeGeometry._positions;
        var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
        var shape2D = polylineVolumeGeometry._shape;
        shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D);

        if (cleanPositions.length < 2 || shape2D.length < 3) {
            return undefined;
        }

        if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) {
            shape2D.reverse();
        }
        var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch);

        var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeGeometry, true);
        return computeAttributes(computedPositions, shape2D, boundingRectangle, polylineVolumeGeometry._vertexFormat);
    };

    return PolylineVolumeGeometry;
});

/*global define*/
define('Core/PolylineVolumeOutlineGeometry',[
        './arrayRemoveDuplicates',
        './BoundingRectangle',
        './BoundingSphere',
        './Cartesian2',
        './Cartesian3',
        './ComponentDatatype',
        './CornerType',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PolygonPipeline',
        './PolylineVolumeGeometryLibrary',
        './PrimitiveType',
        './WindingOrder'
    ], function(
        arrayRemoveDuplicates,
        BoundingRectangle,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        ComponentDatatype,
        CornerType,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PolygonPipeline,
        PolylineVolumeGeometryLibrary,
        PrimitiveType,
        WindingOrder) {
    'use strict';

    function computeAttributes(positions, shape) {
        var attributes = new GeometryAttributes();
        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : positions
        });

        var shapeLength = shape.length;
        var vertexCount = attributes.position.values.length / 3;
        var positionLength = positions.length / 3;
        var shapeCount = positionLength / shapeLength;
        var indices = IndexDatatype.createTypedArray(vertexCount, 2 * shapeLength * (shapeCount + 1));
        var i, j;
        var index = 0;
        i = 0;
        var offset = i * shapeLength;
        for (j = 0; j < shapeLength - 1; j++) {
            indices[index++] = j + offset;
            indices[index++] = j + offset + 1;
        }
        indices[index++] = shapeLength - 1 + offset;
        indices[index++] = offset;

        i = shapeCount - 1;
        offset = i * shapeLength;
        for (j = 0; j < shapeLength - 1; j++) {
            indices[index++] = j + offset;
            indices[index++] = j + offset + 1;
        }
        indices[index++] = shapeLength - 1 + offset;
        indices[index++] = offset;

        for (i = 0; i < shapeCount - 1; i++) {
            var firstOffset = shapeLength * i;
            var secondOffset = firstOffset + shapeLength;
            for (j = 0; j < shapeLength; j++) {
                indices[index++] = j + firstOffset;
                indices[index++] = j + secondOffset;
            }
        }

        var geometry = new Geometry({
            attributes : attributes,
            indices : IndexDatatype.createTypedArray(vertexCount, indices),
            boundingSphere : BoundingSphere.fromVertices(positions),
            primitiveType : PrimitiveType.LINES
        });

        return geometry;
    }

    /**
     * A description of a polyline with a volume (a 2D shape extruded along a polyline).
     *
     * @alias PolylineVolumeOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.polylinePositions An array of positions that define the center of the polyline volume.
     * @param {Cartesian2[]} options.shapePositions An array of positions that define the shape to be extruded along the polyline
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {CornerType} [options.cornerType=CornerType.ROUNDED] Determines the style of the corners.
     *
     * @see PolylineVolumeOutlineGeometry#createGeometry
     *
     * @example
     * function computeCircle(radius) {
     *   var positions = [];
     *   for (var i = 0; i < 360; i++) {
     *     var radians = Cesium.Math.toRadians(i);
     *     positions.push(new Cesium.Cartesian2(radius * Math.cos(radians), radius * Math.sin(radians)));
     *   }
     *   return positions;
     * }
     *
     * var volumeOutline = new Cesium.PolylineVolumeOutlineGeometry({
     *   polylinePositions : Cesium.Cartesian3.fromDegreesArray([
     *     -72.0, 40.0,
     *     -70.0, 35.0
     *   ]),
     *   shapePositions : computeCircle(100000.0)
     * });
     */
    function PolylineVolumeOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.polylinePositions;
        var shape = options.shapePositions;

                if (!defined(positions)) {
            throw new DeveloperError('options.polylinePositions is required.');
        }
        if (!defined(shape)) {
            throw new DeveloperError('options.shapePositions is required.');
        }
        
        this._positions = positions;
        this._shape = shape;
        this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
        this._cornerType = defaultValue(options.cornerType, CornerType.ROUNDED);
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._workerName = 'createPolylineVolumeOutlineGeometry';

        var numComponents = 1 + positions.length * Cartesian3.packedLength;
        numComponents += 1 + shape.length * Cartesian2.packedLength;

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = numComponents + Ellipsoid.packedLength + 2;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {PolylineVolumeOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    PolylineVolumeOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        var shape = value._shape;
        length = shape.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
            Cartesian2.pack(shape[i], array, startingIndex);
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex++] = value._cornerType;
        array[startingIndex]   = value._granularity;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchOptions = {
        polylinePositions : undefined,
        shapePositions : undefined,
        ellipsoid : scratchEllipsoid,
        height : undefined,
        cornerType : undefined,
        granularity : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {PolylineVolumeOutlineGeometry} [result] The object into which to store the result.
     * @returns {PolylineVolumeOutlineGeometry} The modified result parameter or a new PolylineVolumeOutlineGeometry instance if one was not provided.
     */
    PolylineVolumeOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        length = array[startingIndex++];
        var shape = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian2.packedLength) {
            shape[i] = Cartesian2.unpack(array, startingIndex);
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var cornerType = array[startingIndex++];
        var granularity = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.polylinePositions = positions;
            scratchOptions.shapePositions = shape;
            scratchOptions.cornerType = cornerType;
            scratchOptions.granularity = granularity;
            return new PolylineVolumeOutlineGeometry(scratchOptions);
        }

        result._positions = positions;
        result._shape = shape;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._cornerType = cornerType;
        result._granularity = granularity;

        return result;
    };

    var brScratch = new BoundingRectangle();

    /**
     * Computes the geometric representation of the outline of a polyline with a volume, including its vertices, indices, and a bounding sphere.
     *
     * @param {PolylineVolumeOutlineGeometry} polylineVolumeOutlineGeometry A description of the polyline volume outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    PolylineVolumeOutlineGeometry.createGeometry = function(polylineVolumeOutlineGeometry) {
        var positions = polylineVolumeOutlineGeometry._positions;
        var cleanPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);
        var shape2D = polylineVolumeOutlineGeometry._shape;
        shape2D = PolylineVolumeGeometryLibrary.removeDuplicatesFromShape(shape2D);

        if (cleanPositions.length < 2 || shape2D.length < 3) {
            return undefined;
        }

        if (PolygonPipeline.computeWindingOrder2D(shape2D) === WindingOrder.CLOCKWISE) {
            shape2D.reverse();
        }
        var boundingRectangle = BoundingRectangle.fromPoints(shape2D, brScratch);

        var computedPositions = PolylineVolumeGeometryLibrary.computePositions(cleanPositions, shape2D, boundingRectangle, polylineVolumeOutlineGeometry, false);
        return computeAttributes(computedPositions, shape2D);
    };

    return PolylineVolumeOutlineGeometry;
});

/*global define*/
define('Core/QuaternionSpline',[
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Quaternion',
        './Spline'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Quaternion,
        Spline) {
    'use strict';

    function computeInnerQuadrangles(points, firstInnerQuadrangle, lastInnerQuadrangle) {
        var length = points.length;
        var quads = new Array(length);

        quads[0] = defined(firstInnerQuadrangle) ? firstInnerQuadrangle : points[0];
        quads[length - 1] = defined(lastInnerQuadrangle) ? lastInnerQuadrangle : points[length - 1];

        for (var i = 1; i < length - 1; ++i) {
            quads[i] = Quaternion.computeInnerQuadrangle(points[i - 1], points[i], points[i + 1], new Quaternion());
        }

        return quads;
    }

    function createEvaluateFunction(spline) {
        var points = spline.points;
        var quads = spline.innerQuadrangles;
        var times = spline.times;

        // use slerp interpolation for 2 points
        if (points.length < 3) {
            var t0 = times[0];
            var invSpan = 1.0 / (times[1] - t0);

            var q0 = points[0];
            var q1 = points[1];

            return function(time, result) {
                if (!defined(result)){
                    result = new Quaternion();
                }
                var u = (time - t0) * invSpan;
                return Quaternion.fastSlerp(q0, q1, u, result);
            };
        }

        // use quad interpolation for more than 3 points
        return function(time, result) {
            if (!defined(result)){
                result = new Quaternion();
            }
            var i = spline._lastTimeIndex = spline.findTimeInterval(time, spline._lastTimeIndex);
            var u = (time - times[i]) / (times[i + 1] - times[i]);

            var q0 = points[i];
            var q1 = points[i + 1];
            var s0 = quads[i];
            var s1 = quads[i + 1];

            return Quaternion.fastSquad(q0, q1, s0, s1, u, result);
        };
    }

    /**
     * A spline that uses spherical quadrangle (squad) interpolation to create a quaternion curve.
     * The generated curve is in the class C<sup>1</sup>.
     *
     * @alias QuaternionSpline
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Number[]} options.times An array of strictly increasing, unit-less, floating-point times at each point.
     *                The values are in no way connected to the clock time. They are the parameterization for the curve.
     * @param {Quaternion[]} options.points The array of {@link Quaternion} control points.
     * @param {Quaternion} [options.firstInnerQuadrangle] The inner quadrangle of the curve at the first control point.
     *                     If the inner quadrangle is not given, it will be estimated.
     * @param {Quaternion} [options.lastInnerQuadrangle] The inner quadrangle of the curve at the last control point.
     *                     If the inner quadrangle is not given, it will be estimated.
     *
     * @exception {DeveloperError} points.length must be greater than or equal to 2.
     * @exception {DeveloperError} times.length must be equal to points.length.
     *
     * @see HermiteSpline
     * @see CatmullRomSpline
     * @see LinearSpline
     */
    function QuaternionSpline(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var points = options.points;
        var times = options.times;
        var firstInnerQuadrangle = options.firstInnerQuadrangle;
        var lastInnerQuadrangle = options.lastInnerQuadrangle;

                if (!defined(points) || !defined(times)) {
            throw new DeveloperError('points and times are required.');
        }
        if (points.length < 2) {
            throw new DeveloperError('points.length must be greater than or equal to 2.');
        }
        if (times.length !== points.length) {
            throw new DeveloperError('times.length must be equal to points.length.');
        }
        
        var innerQuadrangles = computeInnerQuadrangles(points, firstInnerQuadrangle, lastInnerQuadrangle);

        this._times = times;
        this._points = points;
        this._innerQuadrangles = innerQuadrangles;

        this._evaluateFunction = createEvaluateFunction(this);
        this._lastTimeIndex = 0;
    }

    defineProperties(QuaternionSpline.prototype, {
        /**
         * An array of times for the control points.
         *
         * @memberof QuaternionSpline.prototype
         *
         * @type {Number[]}
         * @readonly
         */
        times : {
            get : function() {
                return this._times;
            }
        },

        /**
         * An array of {@link Quaternion} control points.
         *
         * @memberof QuaternionSpline.prototype
         *
         * @type {Quaternion[]}
         * @readonly
         */
        points : {
            get : function() {
                return this._points;
            }
        },

        /**
         * An array of {@link Quaternion} inner quadrangles for the control points.
         *
         * @memberof QuaternionSpline.prototype
         *
         * @type {Quaternion[]}
         * @readonly
         */
        innerQuadrangles : {
            get : function() {
                return this._innerQuadrangles;
            }
        }
    });

    /**
     * Finds an index <code>i</code> in <code>times</code> such that the parameter
     * <code>time</code> is in the interval <code>[times[i], times[i + 1]]</code>.
     * @function
     *
     * @param {Number} time The time.
     * @returns {Number} The index for the element at the start of the interval.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    QuaternionSpline.prototype.findTimeInterval = Spline.prototype.findTimeInterval;

    /**
     * Evaluates the curve at a given time.
     *
     * @param {Number} time The time at which to evaluate the curve.
     * @param {Quaternion} [result] The object onto which to store the result.
     * @returns {Quaternion} The modified result parameter or a new instance of the point on the curve at the given time.
     *
     * @exception {DeveloperError} time must be in the range <code>[t<sub>0</sub>, t<sub>n</sub>]</code>, where <code>t<sub>0</sub></code>
     *                             is the first element in the array <code>times</code> and <code>t<sub>n</sub></code> is the last element
     *                             in the array <code>times</code>.
     */
    QuaternionSpline.prototype.evaluate = function(time, result) {
        return this._evaluateFunction(time, result);
    };

    return QuaternionSpline;
});

/*global define*/
define('Core/RectangleGeometryLibrary',[
    './Cartesian3',
    './Cartographic',
    './defined',
    './DeveloperError',
    './GeographicProjection',
    './Math',
    './Matrix2',
    './Rectangle'
], function(
    Cartesian3,
    Cartographic,
    defined,
    DeveloperError,
    GeographicProjection,
    CesiumMath,
    Matrix2,
    Rectangle) {
    'use strict';

    var cos = Math.cos;
    var sin = Math.sin;
    var sqrt = Math.sqrt;

    /**
     * @private
     */
    var RectangleGeometryLibrary = {};

    /**
     * @private
     */
    RectangleGeometryLibrary.computePosition = function(options, row, col, position, st) {
        var radiiSquared = options.ellipsoid.radiiSquared;
        var nwCorner = options.nwCorner;
        var rectangle = options.rectangle;

        var stLatitude = nwCorner.latitude - options.granYCos * row + col * options.granXSin;
        var cosLatitude = cos(stLatitude);
        var nZ = sin(stLatitude);
        var kZ = radiiSquared.z * nZ;

        var stLongitude = nwCorner.longitude + row * options.granYSin + col * options.granXCos;
        var nX = cosLatitude * cos(stLongitude);
        var nY = cosLatitude * sin(stLongitude);

        var kX = radiiSquared.x * nX;
        var kY = radiiSquared.y * nY;

        var gamma = sqrt((kX * nX) + (kY * nY) + (kZ * nZ));

        position.x = kX / gamma;
        position.y = kY / gamma;
        position.z = kZ / gamma;

        if (defined(options.vertexFormat) && options.vertexFormat.st) {
            var stNwCorner = options.stNwCorner;
            if (defined(stNwCorner)) {
                stLatitude = stNwCorner.latitude - options.stGranYCos * row + col * options.stGranXSin;
                stLongitude = stNwCorner.longitude + row * options.stGranYSin + col * options.stGranXCos;

                st.x = (stLongitude - options.stWest) * options.lonScalar;
                st.y = (stLatitude - options.stSouth) * options.latScalar;
            } else {
                st.x = (stLongitude - rectangle.west) * options.lonScalar;
                st.y = (stLatitude - rectangle.south) * options.latScalar;
            }
        }
    };

    var rotationMatrixScratch = new Matrix2();
    var nwCartesian = new Cartesian3();
    var centerScratch = new Cartographic();
    var centerCartesian = new Cartesian3();
    var proj = new GeographicProjection();

    function getRotationOptions(nwCorner, rotation, granularityX, granularityY, center, width, height) {
        var cosRotation = Math.cos(rotation);
        var granYCos = granularityY * cosRotation;
        var granXCos = granularityX * cosRotation;

        var sinRotation = Math.sin(rotation);
        var granYSin = granularityY * sinRotation;
        var granXSin = granularityX * sinRotation;

        nwCartesian = proj.project(nwCorner, nwCartesian);

        nwCartesian = Cartesian3.subtract(nwCartesian, centerCartesian, nwCartesian);
        var rotationMatrix = Matrix2.fromRotation(rotation, rotationMatrixScratch);
        nwCartesian = Matrix2.multiplyByVector(rotationMatrix, nwCartesian, nwCartesian);
        nwCartesian = Cartesian3.add(nwCartesian, centerCartesian, nwCartesian);
        nwCorner = proj.unproject(nwCartesian, nwCorner);

        width -= 1;
        height -= 1;

        var latitude = nwCorner.latitude;
        var latitude0 = latitude + width * granXSin;
        var latitude1 = latitude - granYCos * height;
        var latitude2 = latitude - granYCos * height + width * granXSin;

        var north = Math.max(latitude, latitude0, latitude1, latitude2);
        var south = Math.min(latitude, latitude0, latitude1, latitude2);

        var longitude = nwCorner.longitude;
        var longitude0 = longitude + width * granXCos;
        var longitude1 = longitude + height * granYSin;
        var longitude2 = longitude + height * granYSin + width * granXCos;

        var east = Math.max(longitude, longitude0, longitude1, longitude2);
        var west = Math.min(longitude, longitude0, longitude1, longitude2);

        return {
            north: north,
            south: south,
            east: east,
            west: west,
            granYCos : granYCos,
            granYSin : granYSin,
            granXCos : granXCos,
            granXSin : granXSin,
            nwCorner : nwCorner
        };
    }

    /**
     * @private
     */
    RectangleGeometryLibrary.computeOptions = function(geometry, rectangle, nwCorner, stNwCorner) {
        var granularity = geometry._granularity;
        var ellipsoid = geometry._ellipsoid;
        var surfaceHeight = geometry._surfaceHeight;
        var rotation = geometry._rotation;
        var stRotation = geometry._stRotation;
        var extrudedHeight = geometry._extrudedHeight;
        var east = rectangle.east;
        var west = rectangle.west;
        var north = rectangle.north;
        var south = rectangle.south;

        var width;
        var height;
        var granularityX;
        var granularityY;
        var dx;
        var dy = north - south;
        if (west > east) {
            dx = (CesiumMath.TWO_PI - west + east);
            width = Math.ceil(dx / granularity) + 1;
            height = Math.ceil(dy / granularity) + 1;
            granularityX = dx / (width - 1);
            granularityY = dy / (height - 1);
        } else {
            dx = east - west;
            width = Math.ceil(dx / granularity) + 1;
            height = Math.ceil(dy / granularity) + 1;
            granularityX = dx / (width - 1);
            granularityY = dy / (height - 1);
        }

        nwCorner = Rectangle.northwest(rectangle, nwCorner);
        var center = Rectangle.center(rectangle, centerScratch);
        if (rotation !== 0 || stRotation !== 0) {
            if (center.longitude < nwCorner.longitude) {
                center.longitude += CesiumMath.TWO_PI;
            }
            centerCartesian = proj.project(center, centerCartesian);
        }

        var granYCos = granularityY;
        var granXCos = granularityX;
        var granYSin = 0.0;
        var granXSin = 0.0;

        var options = {
            granYCos : granYCos,
            granYSin : granYSin,
            granXCos : granXCos,
            granXSin : granXSin,
            ellipsoid : ellipsoid,
            surfaceHeight : surfaceHeight,
            extrudedHeight : extrudedHeight,
            nwCorner : nwCorner,
            rectangle : rectangle,
            width: width,
            height: height
        };

        if (rotation !== 0) {
            var rotationOptions = getRotationOptions(nwCorner, rotation, granularityX, granularityY, center, width, height);
            north = rotationOptions.north;
            south = rotationOptions.south;
            east = rotationOptions.east;
            west = rotationOptions.west;

                        if (north < -CesiumMath.PI_OVER_TWO || north > CesiumMath.PI_OVER_TWO ||
                south < -CesiumMath.PI_OVER_TWO || south > CesiumMath.PI_OVER_TWO) {
                throw new DeveloperError('Rotated rectangle is invalid.  It crosses over either the north or south pole.');
            }
            
            options.granYCos = rotationOptions.granYCos;
            options.granYSin = rotationOptions.granYSin;
            options.granXCos = rotationOptions.granXCos;
            options.granXSin = rotationOptions.granXSin;

            rectangle.north = north;
            rectangle.south = south;
            rectangle.east = east;
            rectangle.west = west;
        }

        if (stRotation !== 0) {
            rotation = rotation - stRotation;
            stNwCorner = Rectangle.northwest(rectangle, stNwCorner);

            var stRotationOptions = getRotationOptions(stNwCorner, rotation, granularityX, granularityY, center, width, height);

            options.stGranYCos =  stRotationOptions.granYCos;
            options.stGranXCos = stRotationOptions.granXCos;
            options.stGranYSin = stRotationOptions.granYSin;
            options.stGranXSin = stRotationOptions.granXSin;
            options.stNwCorner = stNwCorner;
            options.stWest = stRotationOptions.west;
            options.stSouth = stRotationOptions.south;
        }

        return options;
    };

    return RectangleGeometryLibrary;
});

/*global define*/
define('Core/RectangleGeometry',[
    './BoundingSphere',
    './Cartesian2',
    './Cartesian3',
    './Cartographic',
    './Check',
    './ComponentDatatype',
    './defaultValue',
    './defined',
    './defineProperties',
    './DeveloperError',
    './Ellipsoid',
    './Geometry',
    './GeometryAttribute',
    './GeometryAttributes',
    './GeometryInstance',
    './GeometryPipeline',
    './IndexDatatype',
    './Math',
    './Matrix3',
    './PolygonPipeline',
    './PrimitiveType',
    './Quaternion',
    './Rectangle',
    './RectangleGeometryLibrary',
    './VertexFormat'
], function(
    BoundingSphere,
    Cartesian2,
    Cartesian3,
    Cartographic,
    Check,
    ComponentDatatype,
    defaultValue,
    defined,
    defineProperties,
    DeveloperError,
    Ellipsoid,
    Geometry,
    GeometryAttribute,
    GeometryAttributes,
    GeometryInstance,
    GeometryPipeline,
    IndexDatatype,
    CesiumMath,
    Matrix3,
    PolygonPipeline,
    PrimitiveType,
    Quaternion,
    Rectangle,
    RectangleGeometryLibrary,
    VertexFormat) {
    'use strict';

    var positionScratch = new Cartesian3();
    var normalScratch = new Cartesian3();
    var tangentScratch = new Cartesian3();
    var bitangentScratch = new Cartesian3();
    var rectangleScratch = new Rectangle();
    var stScratch = new Cartesian2();
    var bottomBoundingSphere = new BoundingSphere();
    var topBoundingSphere = new BoundingSphere();

    function createAttributes(vertexFormat, attributes) {
        var geo = new Geometry({
            attributes : new GeometryAttributes(),
            primitiveType : PrimitiveType.TRIANGLES
        });

        geo.attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : attributes.positions
        });
        if (vertexFormat.normal) {
            geo.attributes.normal = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : attributes.normals
            });
        }
        if (vertexFormat.tangent) {
            geo.attributes.tangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : attributes.tangents
            });
        }
        if (vertexFormat.bitangent) {
            geo.attributes.bitangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : attributes.bitangents
            });
        }
        return geo;
    }

    function calculateAttributes(positions, vertexFormat, ellipsoid, tangentRotationMatrix) {
        var length = positions.length;

        var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(length) : undefined;

        var attrIndex = 0;
        var bitangent = bitangentScratch;
        var tangent = tangentScratch;
        var normal = normalScratch;
        if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
            for (var i = 0; i < length; i += 3) {
                var p = Cartesian3.fromArray(positions, i, positionScratch);
                var attrIndex1 = attrIndex + 1;
                var attrIndex2 = attrIndex + 2;

                normal = ellipsoid.geodeticSurfaceNormal(p, normal);
                if (vertexFormat.tangent || vertexFormat.bitangent) {
                    Cartesian3.cross(Cartesian3.UNIT_Z, normal, tangent);
                    Matrix3.multiplyByVector(tangentRotationMatrix, tangent, tangent);
                    Cartesian3.normalize(tangent, tangent);

                    if (vertexFormat.bitangent) {
                        Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent);
                    }
                }

                if (vertexFormat.normal) {
                    normals[attrIndex] = normal.x;
                    normals[attrIndex1] = normal.y;
                    normals[attrIndex2] = normal.z;
                }
                if (vertexFormat.tangent) {
                    tangents[attrIndex] = tangent.x;
                    tangents[attrIndex1] = tangent.y;
                    tangents[attrIndex2] = tangent.z;
                }
                if (vertexFormat.bitangent) {
                    bitangents[attrIndex] = bitangent.x;
                    bitangents[attrIndex1] = bitangent.y;
                    bitangents[attrIndex2] = bitangent.z;
                }
                attrIndex += 3;
            }
        }
        return createAttributes(vertexFormat, {
            positions : positions,
            normals : normals,
            tangents : tangents,
            bitangents : bitangents
        });
    }

    var v1Scratch = new Cartesian3();
    var v2Scratch = new Cartesian3();

    function calculateAttributesWall(positions, vertexFormat, ellipsoid) {
        var length = positions.length;

        var normals = (vertexFormat.normal) ? new Float32Array(length) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(length) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(length) : undefined;

        var normalIndex = 0;
        var tangentIndex = 0;
        var bitangentIndex = 0;
        var recomputeNormal = true;

        var bitangent = bitangentScratch;
        var tangent = tangentScratch;
        var normal = normalScratch;
        if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
            for (var i = 0; i < length; i += 6) {
                var p = Cartesian3.fromArray(positions, i, positionScratch);
                var p1 = Cartesian3.fromArray(positions, (i + 6) % length, v1Scratch);
                if (recomputeNormal) {
                    var p2 = Cartesian3.fromArray(positions, (i + 3) % length, v2Scratch);
                    Cartesian3.subtract(p1, p, p1);
                    Cartesian3.subtract(p2, p, p2);
                    normal = Cartesian3.normalize(Cartesian3.cross(p2, p1, normal), normal);
                    recomputeNormal = false;
                }

                if (Cartesian3.equalsEpsilon(p1, p, CesiumMath.EPSILON10)) { // if we've reached a corner
                    recomputeNormal = true;
                }

                if (vertexFormat.tangent || vertexFormat.bitangent) {
                    bitangent = ellipsoid.geodeticSurfaceNormal(p, bitangent);
                    if (vertexFormat.tangent) {
                        tangent = Cartesian3.normalize(Cartesian3.cross(bitangent, normal, tangent), tangent);
                    }
                }

                if (vertexFormat.normal) {
                    normals[normalIndex++] = normal.x;
                    normals[normalIndex++] = normal.y;
                    normals[normalIndex++] = normal.z;
                    normals[normalIndex++] = normal.x;
                    normals[normalIndex++] = normal.y;
                    normals[normalIndex++] = normal.z;
                }

                if (vertexFormat.tangent) {
                    tangents[tangentIndex++] = tangent.x;
                    tangents[tangentIndex++] = tangent.y;
                    tangents[tangentIndex++] = tangent.z;
                    tangents[tangentIndex++] = tangent.x;
                    tangents[tangentIndex++] = tangent.y;
                    tangents[tangentIndex++] = tangent.z;
                }

                if (vertexFormat.bitangent) {
                    bitangents[bitangentIndex++] = bitangent.x;
                    bitangents[bitangentIndex++] = bitangent.y;
                    bitangents[bitangentIndex++] = bitangent.z;
                    bitangents[bitangentIndex++] = bitangent.x;
                    bitangents[bitangentIndex++] = bitangent.y;
                    bitangents[bitangentIndex++] = bitangent.z;
                }
            }
        }

        return createAttributes(vertexFormat, {
            positions : positions,
            normals : normals,
            tangents : tangents,
            bitangents : bitangents
        });
    }

    function constructRectangle(options) {
        var vertexFormat = options.vertexFormat;
        var ellipsoid = options.ellipsoid;
        var size = options.size;
        var height = options.height;
        var width = options.width;

        var positions = (vertexFormat.position) ? new Float64Array(size * 3) : undefined;
        var textureCoordinates = (vertexFormat.st) ? new Float32Array(size * 2) : undefined;

        var posIndex = 0;
        var stIndex = 0;

        var position = positionScratch;
        var st = stScratch;

        var minX = Number.MAX_VALUE;
        var minY = Number.MAX_VALUE;
        var maxX = -Number.MAX_VALUE;
        var maxY = -Number.MAX_VALUE;

        for (var row = 0; row < height; ++row) {
            for (var col = 0; col < width; ++col) {
                RectangleGeometryLibrary.computePosition(options, row, col, position, st);

                positions[posIndex++] = position.x;
                positions[posIndex++] = position.y;
                positions[posIndex++] = position.z;

                if (vertexFormat.st) {
                    textureCoordinates[stIndex++] = st.x;
                    textureCoordinates[stIndex++] = st.y;

                    minX = Math.min(minX, st.x);
                    minY = Math.min(minY, st.y);
                    maxX = Math.max(maxX, st.x);
                    maxY = Math.max(maxY, st.y);
                }
            }
        }

        if (vertexFormat.st && (minX < 0.0 || minY < 0.0 || maxX > 1.0 || maxY > 1.0)) {
            for (var k = 0; k < textureCoordinates.length; k += 2) {
                textureCoordinates[k] = (textureCoordinates[k] - minX) / (maxX - minX);
                textureCoordinates[k + 1] = (textureCoordinates[k + 1] - minY) / (maxY - minY);
            }
        }

        var geo = calculateAttributes(positions, vertexFormat, ellipsoid, options.tangentRotationMatrix);

        var indicesSize = 6 * (width - 1) * (height - 1);
        var indices = IndexDatatype.createTypedArray(size, indicesSize);
        var index = 0;
        var indicesIndex = 0;
        for (var i = 0; i < height - 1; ++i) {
            for (var j = 0; j < width - 1; ++j) {
                var upperLeft = index;
                var lowerLeft = upperLeft + width;
                var lowerRight = lowerLeft + 1;
                var upperRight = upperLeft + 1;
                indices[indicesIndex++] = upperLeft;
                indices[indicesIndex++] = lowerLeft;
                indices[indicesIndex++] = upperRight;
                indices[indicesIndex++] = upperRight;
                indices[indicesIndex++] = lowerLeft;
                indices[indicesIndex++] = lowerRight;
                ++index;
            }
            ++index;
        }

        geo.indices = indices;
        if (vertexFormat.st) {
            geo.attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : textureCoordinates
            });
        }

        return geo;
    }

    function addWallPositions(wallPositions, posIndex, i, topPositions, bottomPositions) {
        wallPositions[posIndex++] = topPositions[i];
        wallPositions[posIndex++] = topPositions[i + 1];
        wallPositions[posIndex++] = topPositions[i + 2];
        wallPositions[posIndex++] = bottomPositions[i];
        wallPositions[posIndex++] = bottomPositions[i + 1];
        wallPositions[posIndex++] = bottomPositions[i + 2];
        return wallPositions;
    }

    function addWallTextureCoordinates(wallTextures, stIndex, i, st) {
        wallTextures[stIndex++] = st[i];
        wallTextures[stIndex++] = st[i + 1];
        wallTextures[stIndex++] = st[i];
        wallTextures[stIndex++] = st[i + 1];
        return wallTextures;
    }

    var scratchVertexFormat = new VertexFormat();

    function constructExtrudedRectangle(options) {
        var shadowVolume = options.shadowVolume;
        var vertexFormat = options.vertexFormat;
        var surfaceHeight = options.surfaceHeight;
        var extrudedHeight = options.extrudedHeight;
        var minHeight = Math.min(extrudedHeight, surfaceHeight);
        var maxHeight = Math.max(extrudedHeight, surfaceHeight);

        var height = options.height;
        var width = options.width;
        var ellipsoid = options.ellipsoid;
        var i;

        if (shadowVolume) {
            options.vertexFormat = VertexFormat.clone(vertexFormat, scratchVertexFormat);
            options.vertexFormat.normal = true;
        }
        var topBottomGeo = constructRectangle(options);
        if (CesiumMath.equalsEpsilon(minHeight, maxHeight, CesiumMath.EPSILON10)) {
            return topBottomGeo;
        }

        var topPositions = PolygonPipeline.scaleToGeodeticHeight(topBottomGeo.attributes.position.values, maxHeight, ellipsoid, false);
        topPositions = new Float64Array(topPositions);
        var length = topPositions.length;
        var newLength = length * 2;
        var positions = new Float64Array(newLength);
        positions.set(topPositions);
        var bottomPositions = PolygonPipeline.scaleToGeodeticHeight(topBottomGeo.attributes.position.values, minHeight, ellipsoid);
        positions.set(bottomPositions, length);
        topBottomGeo.attributes.position.values = positions;

        var normals = (vertexFormat.normal) ? new Float32Array(newLength) : undefined;
        var tangents = (vertexFormat.tangent) ? new Float32Array(newLength) : undefined;
        var bitangents = (vertexFormat.bitangent) ? new Float32Array(newLength) : undefined;
        var textures = (vertexFormat.st) ? new Float32Array(newLength / 3 * 2) : undefined;
        var topSt;
        var topNormals;
        if (vertexFormat.normal) {
            topNormals = topBottomGeo.attributes.normal.values;
            normals.set(topNormals);
            for (i = 0; i < length; i++) {
                topNormals[i] = -topNormals[i];
            }
            normals.set(topNormals, length);
            topBottomGeo.attributes.normal.values = normals;
        }
        if (shadowVolume) {
            topNormals = topBottomGeo.attributes.normal.values;
            if (!vertexFormat.normal) {
                topBottomGeo.attributes.normal = undefined;
            }
            var extrudeNormals = new Float32Array(newLength);
            for (i = 0; i < length; i++) {
                topNormals[i] = -topNormals[i];
            }
            extrudeNormals.set(topNormals, length); //only get normals for bottom layer that's going to be pushed down
            topBottomGeo.attributes.extrudeDirection = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : extrudeNormals
            });
        }

        if (vertexFormat.tangent) {
            var topTangents = topBottomGeo.attributes.tangent.values;
            tangents.set(topTangents);
            for (i = 0; i < length; i++) {
                topTangents[i] = -topTangents[i];
            }
            tangents.set(topTangents, length);
            topBottomGeo.attributes.tangent.values = tangents;
        }
        if (vertexFormat.bitangent) {
            var topBitangents = topBottomGeo.attributes.bitangent.values;
            bitangents.set(topBitangents);
            bitangents.set(topBitangents, length);
            topBottomGeo.attributes.bitangent.values = bitangents;
        }
        if (vertexFormat.st) {
            topSt = topBottomGeo.attributes.st.values;
            textures.set(topSt);
            textures.set(topSt, length / 3 * 2);
            topBottomGeo.attributes.st.values = textures;
        }

        var indices = topBottomGeo.indices;
        var indicesLength = indices.length;
        var posLength = length / 3;
        var newIndices = IndexDatatype.createTypedArray(newLength / 3, indicesLength * 2);
        newIndices.set(indices);
        for (i = 0; i < indicesLength; i += 3) {
            newIndices[i + indicesLength] = indices[i + 2] + posLength;
            newIndices[i + 1 + indicesLength] = indices[i + 1] + posLength;
            newIndices[i + 2 + indicesLength] = indices[i] + posLength;
        }
        topBottomGeo.indices = newIndices;

        var perimeterPositions = 2 * width + 2 * height - 4;
        var wallCount = (perimeterPositions + 4) * 2;

        var wallPositions = new Float64Array(wallCount * 3);
        var wallExtrudeNormals = shadowVolume ? new Float32Array(wallCount * 3) : undefined;
        var wallTextures = (vertexFormat.st) ? new Float32Array(wallCount * 2) : undefined;

        var posIndex = 0;
        var stIndex = 0;
        var extrudeNormalIndex = 0;
        var area = width * height;
        var threeI;
        for (i = 0; i < area; i += width) {
            threeI = i * 3;
            wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions);
            posIndex += 6;
            if (vertexFormat.st) {
                wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt);
                stIndex += 4;
            }
            if (shadowVolume) {
                extrudeNormalIndex += 3;
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2];
            }
        }

        for (i = area - width; i < area; i++) {
            threeI = i * 3;
            wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions);
            posIndex += 6;
            if (vertexFormat.st) {
                wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt);
                stIndex += 4;
            }
            if (shadowVolume) {
                extrudeNormalIndex += 3;
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2];
            }
        }

        for (i = area - 1; i > 0; i -= width) {
            threeI = i * 3;
            wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions);
            posIndex += 6;
            if (vertexFormat.st) {
                wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt);
                stIndex += 4;
            }
            if (shadowVolume) {
                extrudeNormalIndex += 3;
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2];
            }
        }

        for (i = width - 1; i >= 0; i--) {
            threeI = i * 3;
            wallPositions = addWallPositions(wallPositions, posIndex, threeI, topPositions, bottomPositions);
            posIndex += 6;
            if (vertexFormat.st) {
                wallTextures = addWallTextureCoordinates(wallTextures, stIndex, i * 2, topSt);
                stIndex += 4;
            }
            if (shadowVolume) {
                extrudeNormalIndex += 3;
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 1];
                wallExtrudeNormals[extrudeNormalIndex++] = topNormals[threeI + 2];
            }
        }

        var geo = calculateAttributesWall(wallPositions, vertexFormat, ellipsoid);

        if (vertexFormat.st) {
            geo.attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : wallTextures
            });
        }
        if (shadowVolume) {
            geo.attributes.extrudeDirection = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : wallExtrudeNormals
            });
        }

        var wallIndices = IndexDatatype.createTypedArray(wallCount, perimeterPositions * 6);

        var upperLeft;
        var lowerLeft;
        var lowerRight;
        var upperRight;
        length = wallPositions.length / 3;
        var index = 0;
        for (i = 0; i < length - 1; i += 2) {
            upperLeft = i;
            upperRight = (upperLeft + 2) % length;
            var p1 = Cartesian3.fromArray(wallPositions, upperLeft * 3, v1Scratch);
            var p2 = Cartesian3.fromArray(wallPositions, upperRight * 3, v2Scratch);
            if (Cartesian3.equalsEpsilon(p1, p2, CesiumMath.EPSILON10)) {
                continue;
            }
            lowerLeft = (upperLeft + 1) % length;
            lowerRight = (lowerLeft + 2) % length;
            wallIndices[index++] = upperLeft;
            wallIndices[index++] = lowerLeft;
            wallIndices[index++] = upperRight;
            wallIndices[index++] = upperRight;
            wallIndices[index++] = lowerLeft;
            wallIndices[index++] = lowerRight;
        }

        geo.indices = wallIndices;

        geo = GeometryPipeline.combineInstances([
            new GeometryInstance({
                geometry : topBottomGeo
            }),
            new GeometryInstance({
                geometry : geo
            })
        ]);

        return geo[0];
    }

    var scratchRotationMatrix = new Matrix3();
    var scratchCartesian3 = new Cartesian3();
    var scratchQuaternion = new Quaternion();
    var scratchRectanglePoints = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];
    var scratchCartographicPoints = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];

    function computeRectangle(rectangle, ellipsoid, rotation) {
        if (rotation === 0.0) {
            return Rectangle.clone(rectangle);
        }

        Rectangle.northeast(rectangle, scratchCartographicPoints[0]);
        Rectangle.northwest(rectangle, scratchCartographicPoints[1]);
        Rectangle.southeast(rectangle, scratchCartographicPoints[2]);
        Rectangle.southwest(rectangle, scratchCartographicPoints[3]);

        ellipsoid.cartographicArrayToCartesianArray(scratchCartographicPoints, scratchRectanglePoints);

        var surfaceNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.center(rectangle, scratchCartesian3));
        Quaternion.fromAxisAngle(surfaceNormal, rotation, scratchQuaternion);

        Matrix3.fromQuaternion(scratchQuaternion, scratchRotationMatrix);
        for (var i = 0; i < 4; ++i) {
            // Apply the rotation
            Matrix3.multiplyByVector(scratchRotationMatrix, scratchRectanglePoints[i], scratchRectanglePoints[i]);
        }

        ellipsoid.cartesianArrayToCartographicArray(scratchRectanglePoints, scratchCartographicPoints);

        return Rectangle.fromCartographicArray(scratchCartographicPoints);
    }

    /**
     * A description of a cartographic rectangle on an ellipsoid centered at the origin. Rectangle geometry can be rendered with both {@link Primitive} and {@link GroundPrimitive}.
     *
     * @alias RectangleGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Rectangle} options.rectangle A cartographic rectangle with north, south, east and west properties in radians.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle lies.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Number} [options.height=0.0] The distance in meters between the rectangle and the ellipsoid surface.
     * @param {Number} [options.rotation=0.0] The rotation of the rectangle, in radians. A positive rotation is counter-clockwise.
     * @param {Number} [options.stRotation=0.0] The rotation of the texture coordinates, in radians. A positive rotation is counter-clockwise.
     * @param {Number} [options.extrudedHeight] The distance in meters between the rectangle's extruded face and the ellipsoid surface.
     * @param {Boolean} [options.closeTop=true] Specifies whether the rectangle has a top cover when extruded.
     * @param {Boolean} [options.closeBottom=true] Specifies whether the rectangle has a bottom cover when extruded.
     *
     * @exception {DeveloperError} <code>options.rectangle.north</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
     * @exception {DeveloperError} <code>options.rectangle.south</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
     * @exception {DeveloperError} <code>options.rectangle.east</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
     * @exception {DeveloperError} <code>options.rectangle.west</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
     * @exception {DeveloperError} <code>options.rectangle.north</code> must be greater than <code>options.rectangle.south</code>.
     *
     * @see RectangleGeometry#createGeometry
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo}
     *
     * @example
     * // 1. create an rectangle
     * var rectangle = new Cesium.RectangleGeometry({
     *   ellipsoid : Cesium.Ellipsoid.WGS84,
     *   rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
     *   height : 10000.0
     * });
     * var geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
     *
     * // 2. create an extruded rectangle without a top
     * var rectangle = new Cesium.RectangleGeometry({
     *   ellipsoid : Cesium.Ellipsoid.WGS84,
     *   rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
     *   height : 10000.0,
     *   extrudedHeight: 300000,
     *   closeTop: false
     * });
     * var geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
     */
    function RectangleGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var rectangle = options.rectangle;

                Check.typeOf.object('rectangle', rectangle);
        Rectangle.validate(rectangle);
        if (rectangle.north < rectangle.south) {
            throw new DeveloperError('options.rectangle.north must be greater than or equal to options.rectangle.south');
        }
        
        var rotation = defaultValue(options.rotation, 0.0);
        this._rectangle = rectangle;
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._ellipsoid = Ellipsoid.clone(defaultValue(options.ellipsoid, Ellipsoid.WGS84));
        this._surfaceHeight = defaultValue(options.height, 0.0);
        this._rotation = rotation;
        this._stRotation = defaultValue(options.stRotation, 0.0);
        this._vertexFormat = VertexFormat.clone(defaultValue(options.vertexFormat, VertexFormat.DEFAULT));
        this._extrudedHeight = defaultValue(options.extrudedHeight, 0.0);
        this._extrude = defined(options.extrudedHeight);
        this._closeTop = defaultValue(options.closeTop, true);
        this._closeBottom = defaultValue(options.closeBottom, true);
        this._shadowVolume = defaultValue(options.shadowVolume, false);
        this._workerName = 'createRectangleGeometry';
        this._rotatedRectangle = computeRectangle(this._rectangle, this._ellipsoid, rotation);
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    RectangleGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + VertexFormat.packedLength + Rectangle.packedLength + 9;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {RectangleGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    RectangleGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        Rectangle.pack(value._rectangle, array, startingIndex);
        startingIndex += Rectangle.packedLength;

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        Rectangle.pack(value._rotatedRectangle, array, startingIndex);
        startingIndex += Rectangle.packedLength;

        array[startingIndex++] = value._granularity;
        array[startingIndex++] = value._surfaceHeight;
        array[startingIndex++] = value._rotation;
        array[startingIndex++] = value._stRotation;
        array[startingIndex++] = value._extrudedHeight;
        array[startingIndex++] = value._extrude ? 1.0 : 0.0;
        array[startingIndex++] = value._closeTop ? 1.0 : 0.0;
        array[startingIndex++] = value._closeBottom ? 1.0 : 0.0;
        array[startingIndex] = value._shadowVolume ? 1.0 : 0.0;

        return array;
    };

    var scratchRectangle = new Rectangle();
    var scratchRotatedRectangle = new Rectangle();
    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchOptions = {
        rectangle : scratchRectangle,
        ellipsoid : scratchEllipsoid,
        vertexFormat : scratchVertexFormat,
        granularity : undefined,
        height : undefined,
        rotation : undefined,
        stRotation : undefined,
        extrudedHeight : undefined,
        closeTop : undefined,
        closeBottom : undefined,
        shadowVolume : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {RectangleGeometry} [result] The object into which to store the result.
     * @returns {RectangleGeometry} The modified result parameter or a new RectangleGeometry instance if one was not provided.
     */
    RectangleGeometry.unpack = function(array, startingIndex, result) {
                Check.defined('array', array);
        
        startingIndex = defaultValue(startingIndex, 0);

        var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
        startingIndex += Rectangle.packedLength;

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var rotatedRectangle = Rectangle.unpack(array, startingIndex, scratchRotatedRectangle);
        startingIndex += Rectangle.packedLength;

        var granularity = array[startingIndex++];
        var surfaceHeight = array[startingIndex++];
        var rotation = array[startingIndex++];
        var stRotation = array[startingIndex++];
        var extrudedHeight = array[startingIndex++];
        var extrude = array[startingIndex++] === 1.0;
        var closeTop = array[startingIndex++] === 1.0;
        var closeBottom = array[startingIndex++] === 1.0;
        var shadowVolume = array[startingIndex] === 1.0;

        if (!defined(result)) {
            scratchOptions.granularity = granularity;
            scratchOptions.height = surfaceHeight;
            scratchOptions.rotation = rotation;
            scratchOptions.stRotation = stRotation;
            scratchOptions.extrudedHeight = extrude ? extrudedHeight : undefined;
            scratchOptions.closeTop = closeTop;
            scratchOptions.closeBottom = closeBottom;
            scratchOptions.shadowVolume = shadowVolume;
            return new RectangleGeometry(scratchOptions);
        }

        result._rectangle = Rectangle.clone(rectangle, result._rectangle);
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._granularity = granularity;
        result._surfaceHeight = surfaceHeight;
        result._rotation = rotation;
        result._stRotation = stRotation;
        result._extrudedHeight = extrude ? extrudedHeight : undefined;
        result._extrude = extrude;
        result._closeTop = closeTop;
        result._closeBottom = closeBottom;
        result._rotatedRectangle = rotatedRectangle;
        result._shadowVolume = shadowVolume;

        return result;
    };

    var tangentRotationMatrixScratch = new Matrix3();
    var nwScratch = new Cartographic();
    var stNwScratch = new Cartographic();
    var quaternionScratch = new Quaternion();
    var centerScratch = new Cartographic();
    /**
     * Computes the geometric representation of an rectangle, including its vertices, indices, and a bounding sphere.
     *
     * @param {RectangleGeometry} rectangleGeometry A description of the rectangle.
     * @returns {Geometry|undefined} The computed vertices and indices.
     *
     * @exception {DeveloperError} Rotated rectangle is invalid.
     */
    RectangleGeometry.createGeometry = function(rectangleGeometry) {
        if ((CesiumMath.equalsEpsilon(rectangleGeometry._rectangle.north, rectangleGeometry._rectangle.south, CesiumMath.EPSILON10) ||
             (CesiumMath.equalsEpsilon(rectangleGeometry._rectangle.east, rectangleGeometry._rectangle.west, CesiumMath.EPSILON10)))) {
            return undefined;
        }

        var rectangle = Rectangle.clone(rectangleGeometry._rectangle, rectangleScratch);
        var ellipsoid = rectangleGeometry._ellipsoid;
        var surfaceHeight = rectangleGeometry._surfaceHeight;
        var extrude = rectangleGeometry._extrude;
        var extrudedHeight = rectangleGeometry._extrudedHeight;
        var rotation = rectangleGeometry._rotation;
        var stRotation = rectangleGeometry._stRotation;
        var vertexFormat = rectangleGeometry._vertexFormat;

        var options = RectangleGeometryLibrary.computeOptions(rectangleGeometry, rectangle, nwScratch, stNwScratch);

        var tangentRotationMatrix = tangentRotationMatrixScratch;
        if (stRotation !== 0 || rotation !== 0) {
            var center = Rectangle.center(rectangle, centerScratch);
            var axis = ellipsoid.geodeticSurfaceNormalCartographic(center, v1Scratch);
            Quaternion.fromAxisAngle(axis, -stRotation, quaternionScratch);
            Matrix3.fromQuaternion(quaternionScratch, tangentRotationMatrix);
        } else {
            Matrix3.clone(Matrix3.IDENTITY, tangentRotationMatrix);
        }

        options.lonScalar = 1.0 / rectangleGeometry._rectangle.width;
        options.latScalar = 1.0 / rectangleGeometry._rectangle.height;
        options.vertexFormat = vertexFormat;
        options.rotation = rotation;
        options.stRotation = stRotation;
        options.tangentRotationMatrix = tangentRotationMatrix;
        options.size = options.width * options.height;

        var geometry;
        var boundingSphere;
        rectangle = rectangleGeometry._rectangle;
        if (extrude) {
            options.shadowVolume = rectangleGeometry._shadowVolume;
            geometry = constructExtrudedRectangle(options);
            var topBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight, topBoundingSphere);
            var bottomBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, extrudedHeight, bottomBoundingSphere);
            boundingSphere = BoundingSphere.union(topBS, bottomBS);
        } else {
            geometry = constructRectangle(options);
            geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.attributes.position.values, surfaceHeight, ellipsoid, false);
            boundingSphere = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight);
        }

        if (!vertexFormat.position) {
            delete geometry.attributes.position;
        }

        return new Geometry({
            attributes : geometry.attributes,
            indices : geometry.indices,
            primitiveType : geometry.primitiveType,
            boundingSphere : boundingSphere
        });
    };

    /**
     * @private
     */
    RectangleGeometry.createShadowVolume = function(rectangleGeometry, minHeightFunc, maxHeightFunc) {
        var granularity = rectangleGeometry._granularity;
        var ellipsoid = rectangleGeometry._ellipsoid;

        var minHeight = minHeightFunc(granularity, ellipsoid);
        var maxHeight = maxHeightFunc(granularity, ellipsoid);

        // TODO: stRotation
        return new RectangleGeometry({
            rectangle : rectangleGeometry._rectangle,
            rotation : rectangleGeometry._rotation,
            ellipsoid : ellipsoid,
            stRotation : rectangleGeometry._stRotation,
            granularity : granularity,
            extrudedHeight : maxHeight,
            height : minHeight,
            closeTop : true,
            closeBottom : true,
            vertexFormat : VertexFormat.POSITION_ONLY,
            shadowVolume : true
        });
    };

    defineProperties(RectangleGeometry.prototype, {
        /**
         * @private
         */
        rectangle : {
            get : function() {
                return this._rotatedRectangle;
            }
        }
    });

    return RectangleGeometry;
});

/*global define*/
define('Core/RectangleOutlineGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './Cartographic',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PolygonPipeline',
        './PrimitiveType',
        './Rectangle',
        './RectangleGeometryLibrary'
    ], function(
        BoundingSphere,
        Cartesian3,
        Cartographic,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PolygonPipeline,
        PrimitiveType,
        Rectangle,
        RectangleGeometryLibrary) {
    'use strict';

    var bottomBoundingSphere = new BoundingSphere();
    var topBoundingSphere = new BoundingSphere();
    var positionScratch = new Cartesian3();
    var rectangleScratch = new Rectangle();

    function constructRectangle(options) {
        var size = options.size;
        var height = options.height;
        var width = options.width;
        var positions = new Float64Array(size * 3);

        var posIndex = 0;
        var row = 0;
        var col;
        var position = positionScratch;
        for (col = 0; col < width; col++) {
            RectangleGeometryLibrary.computePosition(options, row, col, position);
            positions[posIndex++] = position.x;
            positions[posIndex++] = position.y;
            positions[posIndex++] = position.z;
        }

        col = width - 1;
        for (row = 1; row < height; row++) {
            RectangleGeometryLibrary.computePosition(options, row, col, position);
            positions[posIndex++] = position.x;
            positions[posIndex++] = position.y;
            positions[posIndex++] = position.z;
        }

        row = height - 1;
        for (col = width-2; col >=0; col--){
            RectangleGeometryLibrary.computePosition(options, row, col, position);
            positions[posIndex++] = position.x;
            positions[posIndex++] = position.y;
            positions[posIndex++] = position.z;
        }

        col = 0;
        for (row = height - 2; row > 0; row--) {
            RectangleGeometryLibrary.computePosition(options, row, col, position);
            positions[posIndex++] = position.x;
            positions[posIndex++] = position.y;
            positions[posIndex++] = position.z;
        }

        var indicesSize = positions.length/3 * 2;
        var indices = IndexDatatype.createTypedArray(positions.length / 3, indicesSize);

        var index = 0;
        for(var i = 0; i < (positions.length/3)-1; i++) {
            indices[index++] = i;
            indices[index++] = i+1;
        }
        indices[index++] = (positions.length/3)-1;
        indices[index++] = 0;

        var geo = new Geometry({
            attributes : new GeometryAttributes(),
            primitiveType : PrimitiveType.LINES
        });

        geo.attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : positions
        });
        geo.indices = indices;

        return geo;
    }

    function constructExtrudedRectangle(options) {
        var surfaceHeight = options.surfaceHeight;
        var extrudedHeight = options.extrudedHeight;
        var ellipsoid = options.ellipsoid;
        var minHeight = Math.min(extrudedHeight, surfaceHeight);
        var maxHeight = Math.max(extrudedHeight, surfaceHeight);
        var geo = constructRectangle(options);
        if (CesiumMath.equalsEpsilon(minHeight, maxHeight, CesiumMath.EPSILON10)) {
            return geo;
        }
        var height = options.height;
        var width = options.width;

        var topPositions = PolygonPipeline.scaleToGeodeticHeight(geo.attributes.position.values, maxHeight, ellipsoid, false);
        var length = topPositions.length;
        var positions = new Float64Array(length*2);
        positions.set(topPositions);
        var bottomPositions = PolygonPipeline.scaleToGeodeticHeight(geo.attributes.position.values, minHeight, ellipsoid);
        positions.set(bottomPositions, length);
        geo.attributes.position.values = positions;

        var indicesSize = positions.length/3 * 2 + 8;
        var indices = IndexDatatype.createTypedArray(positions.length / 3, indicesSize);
        length = positions.length/6;
        var index = 0;
        for (var i = 0; i < length - 1; i++) {
            indices[index++] = i;
            indices[index++] =i+1;
            indices[index++] = i + length;
            indices[index++] = i + length + 1;
        }
        indices[index++] = length - 1;
        indices[index++] = 0;
        indices[index++] = length + length - 1;
        indices[index++] = length;

        indices[index++] = 0;
        indices[index++] = length;
        indices[index++] = width-1;
        indices[index++] = length + width-1;
        indices[index++] = width + height - 2;
        indices[index++] = width + height - 2 + length;
        indices[index++] =  2*width + height - 3;
        indices[index++] = 2*width + height - 3 + length;

        geo.indices = indices;

        return geo;
    }

    /**
     * A description of the outline of a a cartographic rectangle on an ellipsoid centered at the origin.
     *
     * @alias RectangleOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Rectangle} options.rectangle A cartographic rectangle with north, south, east and west properties in radians.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle lies.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Number} [options.height=0.0] The distance in meters between the rectangle and the ellipsoid surface.
     * @param {Number} [options.rotation=0.0] The rotation of the rectangle, in radians. A positive rotation is counter-clockwise.
     * @param {Number} [options.extrudedHeight] The distance in meters between the rectangle's extruded face and the ellipsoid surface.
     *
     * @exception {DeveloperError} <code>options.rectangle.north</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
     * @exception {DeveloperError} <code>options.rectangle.south</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
     * @exception {DeveloperError} <code>options.rectangle.east</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
     * @exception {DeveloperError} <code>options.rectangle.west</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
     * @exception {DeveloperError} <code>options.rectangle.north</code> must be greater than <code>rectangle.south</code>.
     *
     * @see RectangleOutlineGeometry#createGeometry
     *
     * @example
     * var rectangle = new Cesium.RectangleOutlineGeometry({
     *   ellipsoid : Cesium.Ellipsoid.WGS84,
     *   rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
     *   height : 10000.0
     * });
     * var geometry = Cesium.RectangleOutlineGeometry.createGeometry(rectangle);
     */
    function RectangleOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var rectangle = options.rectangle;
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        var surfaceHeight = defaultValue(options.height, 0.0);
        var rotation = defaultValue(options.rotation, 0.0);
        var extrudedHeight = options.extrudedHeight;

                if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required.');
        }
        Rectangle.validate(rectangle);
        if (rectangle.north < rectangle.south) {
            throw new DeveloperError('options.rectangle.north must be greater than options.rectangle.south');
        }
        
        this._rectangle = rectangle;
        this._granularity = granularity;
        this._ellipsoid = ellipsoid;
        this._surfaceHeight = surfaceHeight;
        this._rotation = rotation;
        this._extrudedHeight = extrudedHeight;
        this._workerName = 'createRectangleOutlineGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    RectangleOutlineGeometry.packedLength = Rectangle.packedLength + Ellipsoid.packedLength + 5;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {RectangleOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    RectangleOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }

        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        Rectangle.pack(value._rectangle, array, startingIndex);
        startingIndex += Rectangle.packedLength;

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex++] = value._granularity;
        array[startingIndex++] = value._surfaceHeight;
        array[startingIndex++] = value._rotation;
        array[startingIndex++] = defined(value._extrudedHeight) ? 1.0 : 0.0;
        array[startingIndex] = defaultValue(value._extrudedHeight, 0.0);

        return array;
    };

    var scratchRectangle = new Rectangle();
    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchOptions = {
        rectangle : scratchRectangle,
        ellipsoid : scratchEllipsoid,
        granularity : undefined,
        height : undefined,
        rotation : undefined,
        extrudedHeight : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {RectangleOutlineGeometry} [result] The object into which to store the result.
     * @returns {RectangleOutlineGeometry} The modified result parameter or a new Quaternion instance if one was not provided.
     */
    RectangleOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
        startingIndex += Rectangle.packedLength;

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var granularity = array[startingIndex++];
        var height = array[startingIndex++];
        var rotation = array[startingIndex++];
        var hasExtrudedHeight = array[startingIndex++];
        var extrudedHeight = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.granularity = granularity;
            scratchOptions.height = height;
            scratchOptions.rotation = rotation;
            scratchOptions.extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;
            return new RectangleOutlineGeometry(scratchOptions);
        }

        result._rectangle = Rectangle.clone(rectangle, result._rectangle);
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._surfaceHeight = height;
        result._rotation = rotation;
        result._extrudedHeight = hasExtrudedHeight ? extrudedHeight : undefined;

        return result;
    };

    var nwScratch = new Cartographic();
    /**
     * Computes the geometric representation of an outline of an rectangle, including its vertices, indices, and a bounding sphere.
     *
     * @param {RectangleOutlineGeometry} rectangleGeometry A description of the rectangle outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     *
     * @exception {DeveloperError} Rotated rectangle is invalid.
     */
    RectangleOutlineGeometry.createGeometry = function(rectangleGeometry) {
        var rectangle = Rectangle.clone(rectangleGeometry._rectangle, rectangleScratch);
        var ellipsoid = rectangleGeometry._ellipsoid;
        var surfaceHeight = rectangleGeometry._surfaceHeight;
        var extrudedHeight = rectangleGeometry._extrudedHeight;

        var options = RectangleGeometryLibrary.computeOptions(rectangleGeometry, rectangle, nwScratch);
        options.size =  2*options.width + 2*options.height - 4;

        var geometry;
        var boundingSphere;
        rectangle = rectangleGeometry._rectangle;

        if ((CesiumMath.equalsEpsilon(rectangle.north, rectangle.south, CesiumMath.EPSILON10) ||
             (CesiumMath.equalsEpsilon(rectangle.east, rectangle.west, CesiumMath.EPSILON10)))) {
            return undefined;
        }
        if (defined(extrudedHeight)) {
            geometry = constructExtrudedRectangle(options);
            var topBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight, topBoundingSphere);
            var bottomBS = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, extrudedHeight, bottomBoundingSphere);
            boundingSphere = BoundingSphere.union(topBS, bottomBS);
        } else {
            geometry = constructRectangle(options);
            geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(geometry.attributes.position.values, surfaceHeight, ellipsoid, false);
            boundingSphere = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, surfaceHeight);
        }

        return new Geometry({
            attributes : geometry.attributes,
            indices : geometry.indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : boundingSphere
        });
    };

    return RectangleOutlineGeometry;
});

/*global define*/
define('Core/ReferenceFrame',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Constants for identifying well-known reference frames.
     *
     * @exports ReferenceFrame
     */
    var ReferenceFrame = {
        /**
         * The fixed frame.
         *
         * @type {Number}
         * @constant
         */
        FIXED : 0,

        /**
         * The inertial frame.
         *
         * @type {Number}
         * @constant
         */
        INERTIAL : 1
    };

    return freezeObject(ReferenceFrame);
});

/*global define*/
define('Core/requestAnimationFrame',[
        './defined',
        './getTimestamp'
    ], function(
        defined,
        getTimestamp) {
    'use strict';

    if (typeof window === 'undefined') {
        return;
    }

    var implementation = window.requestAnimationFrame;

    (function() {
        // look for vendor prefixed function
        if (!defined(implementation)) {
            var vendors = ['webkit', 'moz', 'ms', 'o'];
            var i = 0;
            var len = vendors.length;
            while (i < len && !defined(implementation)) {
                implementation = window[vendors[i] + 'RequestAnimationFrame'];
                ++i;
            }
        }

        // build an implementation based on setTimeout
        if (!defined(implementation)) {
            var msPerFrame = 1000.0 / 60.0;
            var lastFrameTime = 0;
            implementation = function(callback) {
                var currentTime = getTimestamp();

                // schedule the callback to target 60fps, 16.7ms per frame,
                // accounting for the time taken by the callback
                var delay = Math.max(msPerFrame - (currentTime - lastFrameTime), 0);
                lastFrameTime = currentTime + delay;

                return setTimeout(function() {
                    callback(lastFrameTime);
                }, delay);
            };
        }
    })();

    /**
     * A browser-independent function to request a new animation frame.  This is used to create
     * an application's draw loop as shown in the example below.
     *
     * @exports requestAnimationFrame
     *
     * @param {requestAnimationFrame~Callback} callback The function to call when the next frame should be drawn.
     * @returns {Number} An ID that can be passed to {@link cancelAnimationFrame} to cancel the request.
     *
     *
     * @example
     * // Create a draw loop using requestAnimationFrame. The
     * // tick callback function is called for every animation frame.
     * function tick() {
     *   scene.render();
     *   Cesium.requestAnimationFrame(tick);
     * }
     * tick();
     * 
     * @see {@link http://www.w3.org/TR/animation-timing/#the-WindowAnimationTiming-interface|The WindowAnimationTiming interface}
     */
    function requestAnimationFrame(callback) {
        // we need this extra wrapper function because the native requestAnimationFrame
        // functions must be invoked on the global scope (window), which is not the case
        // if invoked as Cesium.requestAnimationFrame(callback)
        return implementation(callback);
    }

    /**
     * A function that will be called when the next frame should be drawn.
     * @callback requestAnimationFrame~Callback
     *
     * @param {Number} timestamp A timestamp for the frame, in milliseconds.
     */

    return requestAnimationFrame;
});

/*global define*/
define('Core/sampleTerrain',[
        '../ThirdParty/when',
        './defined',
        './DeveloperError'
    ], function(
        when,
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Initiates a terrain height query for an array of {@link Cartographic} positions by
     * requesting tiles from a terrain provider, sampling, and interpolating.  The interpolation
     * matches the triangles used to render the terrain at the specified level.  The query
     * happens asynchronously, so this function returns a promise that is resolved when
     * the query completes.  Each point height is modified in place.  If a height can not be
     * determined because no terrain data is available for the specified level at that location,
     * or another error occurs, the height is set to undefined.  As is typical of the
     * {@link Cartographic} type, the supplied height is a height above the reference ellipsoid
     * (such as {@link Ellipsoid.WGS84}) rather than an altitude above mean sea level.  In other
     * words, it will not necessarily be 0.0 if sampled in the ocean.
     *
     * @exports sampleTerrain
     *
     * @param {TerrainProvider} terrainProvider The terrain provider from which to query heights.
     * @param {Number} level The terrain level-of-detail from which to query terrain heights.
     * @param {Cartographic[]} positions The positions to update with terrain heights.
     * @returns {Promise.<Cartographic[]>} A promise that resolves to the provided list of positions when terrain the query has completed.
     *
     * @example
     * // Query the terrain height of two Cartographic positions
     * var terrainProvider = new Cesium.CesiumTerrainProvider({
     *     url : 'https://assets.agi.com/stk-terrain/world'
     * });
     * var positions = [
     *     Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
     *     Cesium.Cartographic.fromDegrees(87.0, 28.0)
     * ];
     * var promise = Cesium.sampleTerrain(terrainProvider, 11, positions);
     * Cesium.when(promise, function(updatedPositions) {
     *     // positions[0].height and positions[1].height have been updated.
     *     // updatedPositions is just a reference to positions.
     * });
     */
    function sampleTerrain(terrainProvider, level, positions) {
                if (!defined(terrainProvider)) {
            throw new DeveloperError('terrainProvider is required.');
        }
        if (!defined(level)) {
            throw new DeveloperError('level is required.');
        }
        if (!defined(positions)) {
            throw new DeveloperError('positions is required.');
        }
        
        var deferred = when.defer();

        function doSamplingWhenReady() {
            if (terrainProvider.ready) {
                when(doSampling(terrainProvider, level, positions), function(updatedPositions) {
                    deferred.resolve(updatedPositions);
                });
            } else {
                setTimeout(doSamplingWhenReady, 10);
            }
        }

        doSamplingWhenReady();

        return deferred.promise;
    }

    function doSampling(terrainProvider, level, positions) {
        var tilingScheme = terrainProvider.tilingScheme;

        var i;

        // Sort points into a set of tiles
        var tileRequests = []; // Result will be an Array as it's easier to work with
        var tileRequestSet = {}; // A unique set
        for (i = 0; i < positions.length; ++i) {
            var xy = tilingScheme.positionToTileXY(positions[i], level);
            var key = xy.toString();

            if (!tileRequestSet.hasOwnProperty(key)) {
                // When tile is requested for the first time
                var value = {
                    x : xy.x,
                    y : xy.y,
                    level : level,
                    tilingScheme : tilingScheme,
                    terrainProvider : terrainProvider,
                    positions : []
                };
                tileRequestSet[key] = value;
                tileRequests.push(value);
            }

            // Now append to array of points for the tile
            tileRequestSet[key].positions.push(positions[i]);
        }

        // Send request for each required tile
        var tilePromises = [];
        for (i = 0; i < tileRequests.length; ++i) {
            var tileRequest = tileRequests[i];
            var requestPromise = tileRequest.terrainProvider.requestTileGeometry(tileRequest.x, tileRequest.y, tileRequest.level, false);
            var tilePromise = when(requestPromise, createInterpolateFunction(tileRequest), createMarkFailedFunction(tileRequest));
            tilePromises.push(tilePromise);
        }

        return when.all(tilePromises, function() {
            return positions;
        });
    }

    function createInterpolateFunction(tileRequest) {
        var tilePositions = tileRequest.positions;
        var rectangle = tileRequest.tilingScheme.tileXYToRectangle(tileRequest.x, tileRequest.y, tileRequest.level);
        return function(terrainData) {
            for (var i = 0; i < tilePositions.length; ++i) {
                var position = tilePositions[i];
                position.height = terrainData.interpolateHeight(rectangle, position.longitude, position.latitude);
            }
        };
    }

    function createMarkFailedFunction(tileRequest) {
        var tilePositions = tileRequest.positions;
        return function() {
            for (var i = 0; i < tilePositions.length; ++i) {
                var position = tilePositions[i];
                position.height = undefined;
            }
        };
    }

    return sampleTerrain;
});

/*global define*/
define('Core/sampleTerrainMostDetailed',[
    '../ThirdParty/when',
    './defined',
    './sampleTerrain',
    './DeveloperError'
], function(
    when,
    defined,
    sampleTerrain,
    DeveloperError) {
    "use strict";

    /**
     * Initiates a sampleTerrain() request at the maximum available tile level for a terrain dataset.
     *
     * @exports sampleTerrainMostDetailed
     *
     * @param {TerrainProvider} terrainProvider The terrain provider from which to query heights.
     * @param {Cartographic[]} positions The positions to update with terrain heights.
     * @returns {Promise.<Cartographic[]>} A promise that resolves to the provided list of positions when terrain the query has completed.  This
     *                                     promise will reject if the terrain provider's `availability` property is undefined.
     *
     * @example
     * // Query the terrain height of two Cartographic positions
     * var terrainProvider = new Cesium.CesiumTerrainProvider({
     *     url : '//assets.agi.com/stk-terrain/world'
     * });
     * var positions = [
     *     Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
     *     Cesium.Cartographic.fromDegrees(87.0, 28.0)
     * ];
     * var promise = Cesium.sampleTerrainMostDetailed(terrainProvider, positions);
     * Cesium.when(promise, function(updatedPositions) {
     *     // positions[0].height and positions[1].height have been updated.
     *     // updatedPositions is just a reference to positions.
     * });
     */
    function sampleTerrainMostDetailed(terrainProvider, positions) {
                if (!defined(terrainProvider)) {
            throw new DeveloperError('terrainProvider is required.');
        }
        if (!defined(positions)) {
            throw new DeveloperError('positions is required.');
        }
        
        return terrainProvider.readyPromise.then(function() {
            var byLevel = [];

            var availability = terrainProvider.availability;

                        if (!defined(availability)) {
                throw new DeveloperError('sampleTerrainMostDetailed requires a terrain provider that has tile availability.');
            }
            
            for (var i = 0; i < positions.length; ++i) {
                var position = positions[i];
                var maxLevel = availability.computeMaximumLevelAtPosition(position);

                var atLevel = byLevel[maxLevel];
                if (!defined(atLevel)) {
                    byLevel[maxLevel] = atLevel = [];
                }
                atLevel.push(position);
            }

            return when.all(byLevel.map(function(positionsAtLevel, index) {
                if (defined(positionsAtLevel)) {
                    return sampleTerrain(terrainProvider, index, positionsAtLevel);
                }
            })).then(function() {
                return positions;
            });
        });
    }

    return sampleTerrainMostDetailed;
});

/*global define*/
define('Core/ScreenSpaceEventType',[
        './freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * This enumerated type is for classifying mouse events: down, up, click, double click, move and move while a button is held down.
     *
     * @exports ScreenSpaceEventType
     */
    var ScreenSpaceEventType = {
        /**
         * Represents a mouse left button down event.
         *
         * @type {Number}
         * @constant
         */
        LEFT_DOWN : 0,

        /**
         * Represents a mouse left button up event.
         *
         * @type {Number}
         * @constant
         */
        LEFT_UP : 1,

        /**
         * Represents a mouse left click event.
         *
         * @type {Number}
         * @constant
         */
        LEFT_CLICK : 2,

        /**
         * Represents a mouse left double click event.
         *
         * @type {Number}
         * @constant
         */
        LEFT_DOUBLE_CLICK : 3,

        /**
         * Represents a mouse left button down event.
         *
         * @type {Number}
         * @constant
         */
        RIGHT_DOWN : 5,

        /**
         * Represents a mouse right button up event.
         *
         * @type {Number}
         * @constant
         */
        RIGHT_UP : 6,

        /**
         * Represents a mouse right click event.
         *
         * @type {Number}
         * @constant
         */
        RIGHT_CLICK : 7,

        /**
         * Represents a mouse middle button down event.
         *
         * @type {Number}
         * @constant
         */
        MIDDLE_DOWN : 10,

        /**
         * Represents a mouse middle button up event.
         *
         * @type {Number}
         * @constant
         */
        MIDDLE_UP : 11,

        /**
         * Represents a mouse middle click event.
         *
         * @type {Number}
         * @constant
         */
        MIDDLE_CLICK : 12,

        /**
         * Represents a mouse move event.
         *
         * @type {Number}
         * @constant
         */
        MOUSE_MOVE : 15,

        /**
         * Represents a mouse wheel event.
         *
         * @type {Number}
         * @constant
         */
        WHEEL : 16,

        /**
         * Represents the start of a two-finger event on a touch surface.
         *
         * @type {Number}
         * @constant
         */
        PINCH_START : 17,

        /**
         * Represents the end of a two-finger event on a touch surface.
         *
         * @type {Number}
         * @constant
         */
        PINCH_END : 18,

        /**
         * Represents a change of a two-finger event on a touch surface.
         *
         * @type {Number}
         * @constant
         */
        PINCH_MOVE : 19
    };

    return freezeObject(ScreenSpaceEventType);
});

/*global define*/
define('Core/ScreenSpaceEventHandler',[
        './AssociativeArray',
        './Cartesian2',
        './defaultValue',
        './defined',
        './destroyObject',
        './DeveloperError',
        './FeatureDetection',
        './getTimestamp',
        './KeyboardEventModifier',
        './ScreenSpaceEventType'
    ], function(
        AssociativeArray,
        Cartesian2,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        getTimestamp,
        KeyboardEventModifier,
        ScreenSpaceEventType) {
    'use strict';

    function getPosition(screenSpaceEventHandler, event, result) {
        var element = screenSpaceEventHandler._element;
        if (element === document) {
            result.x = event.clientX;
            result.y = event.clientY;
            return result;
        }

        var rect = element.getBoundingClientRect();
        result.x = event.clientX - rect.left;
        result.y = event.clientY - rect.top;
        return result;
    }

    function getInputEventKey(type, modifier) {
        var key = type;
        if (defined(modifier)) {
            key += '+' + modifier;
        }
        return key;
    }

    function getModifier(event) {
        if (event.shiftKey) {
            return KeyboardEventModifier.SHIFT;
        } else if (event.ctrlKey) {
            return KeyboardEventModifier.CTRL;
        } else if (event.altKey) {
            return KeyboardEventModifier.ALT;
        }

        return undefined;
    }

    var MouseButton = {
        LEFT : 0,
        MIDDLE : 1,
        RIGHT : 2
    };

    function registerListener(screenSpaceEventHandler, domType, element, callback) {
        function listener(e) {
            callback(screenSpaceEventHandler, e);
        }
        element.addEventListener(domType, listener, false);

        screenSpaceEventHandler._removalFunctions.push(function() {
            element.removeEventListener(domType, listener, false);
        });
    }

    function registerListeners(screenSpaceEventHandler) {
        var element = screenSpaceEventHandler._element;

        // some listeners may be registered on the document, so we still get events even after
        // leaving the bounds of element.
        // this is affected by the existence of an undocumented disableRootEvents property on element.
        var alternateElement = !defined(element.disableRootEvents) ? document : element;

        if (FeatureDetection.supportsPointerEvents()) {
            registerListener(screenSpaceEventHandler, 'pointerdown', element, handlePointerDown);
            registerListener(screenSpaceEventHandler, 'pointerup', element, handlePointerUp);
            registerListener(screenSpaceEventHandler, 'pointermove', element, handlePointerMove);
            registerListener(screenSpaceEventHandler, 'pointercancel', element, handlePointerUp);
        } else {
            registerListener(screenSpaceEventHandler, 'mousedown', element, handleMouseDown);
            registerListener(screenSpaceEventHandler, 'mouseup', alternateElement, handleMouseUp);
            registerListener(screenSpaceEventHandler, 'mousemove', alternateElement, handleMouseMove);
            registerListener(screenSpaceEventHandler, 'touchstart', element, handleTouchStart);
            registerListener(screenSpaceEventHandler, 'touchend', alternateElement, handleTouchEnd);
            registerListener(screenSpaceEventHandler, 'touchmove', alternateElement, handleTouchMove);
            registerListener(screenSpaceEventHandler, 'touchcancel', alternateElement, handleTouchEnd);
        }

        registerListener(screenSpaceEventHandler, 'dblclick', element, handleDblClick);

        // detect available wheel event
        var wheelEvent;
        if ('onwheel' in element) {
            // spec event type
            wheelEvent = 'wheel';
        } else if (document.onmousewheel !== undefined) {
            // legacy event type
            wheelEvent = 'mousewheel';
        } else {
            // older Firefox
            wheelEvent = 'DOMMouseScroll';
        }

        registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel);
    }

    function unregisterListeners(screenSpaceEventHandler) {
        var removalFunctions = screenSpaceEventHandler._removalFunctions;
        for (var i = 0; i < removalFunctions.length; ++i) {
            removalFunctions[i]();
        }
    }

    var mouseDownEvent = {
        position : new Cartesian2()
    };

    function gotTouchEvent(screenSpaceEventHandler) {
        screenSpaceEventHandler._lastSeenTouchEvent = getTimestamp();
    }

    function canProcessMouseEvent(screenSpaceEventHandler) {
        return (getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent) > ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;
    }

    function handleMouseDown(screenSpaceEventHandler, event) {
        if (!canProcessMouseEvent(screenSpaceEventHandler)) {
            return;
        }

        var button = event.button;
        screenSpaceEventHandler._buttonDown = button;

        var screenSpaceEventType;
        if (button === MouseButton.LEFT) {
            screenSpaceEventType = ScreenSpaceEventType.LEFT_DOWN;
        } else if (button === MouseButton.MIDDLE) {
            screenSpaceEventType = ScreenSpaceEventType.MIDDLE_DOWN;
        } else if (button === MouseButton.RIGHT) {
            screenSpaceEventType = ScreenSpaceEventType.RIGHT_DOWN;
        } else {
            return;
        }

        var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition);
        Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
        Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);

        var modifier = getModifier(event);

        var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier);

        if (defined(action)) {
            Cartesian2.clone(position, mouseDownEvent.position);

            action(mouseDownEvent);

            event.preventDefault();
        }
    }

    var mouseUpEvent = {
        position : new Cartesian2()
    };
    var mouseClickEvent = {
        position : new Cartesian2()
    };

    function handleMouseUp(screenSpaceEventHandler, event) {
        if (!canProcessMouseEvent(screenSpaceEventHandler)) {
            return;
        }

        var button = event.button;
        screenSpaceEventHandler._buttonDown = undefined;

        var screenSpaceEventType;
        var clickScreenSpaceEventType;
        if (button === MouseButton.LEFT) {
            screenSpaceEventType = ScreenSpaceEventType.LEFT_UP;
            clickScreenSpaceEventType = ScreenSpaceEventType.LEFT_CLICK;
        } else if (button === MouseButton.MIDDLE) {
            screenSpaceEventType = ScreenSpaceEventType.MIDDLE_UP;
            clickScreenSpaceEventType = ScreenSpaceEventType.MIDDLE_CLICK;
        } else if (button === MouseButton.RIGHT) {
            screenSpaceEventType = ScreenSpaceEventType.RIGHT_UP;
            clickScreenSpaceEventType = ScreenSpaceEventType.RIGHT_CLICK;
        } else {
            return;
        }

        var modifier = getModifier(event);

        var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier);
        var clickAction = screenSpaceEventHandler.getInputAction(clickScreenSpaceEventType, modifier);

        if (defined(action) || defined(clickAction)) {
            var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition);

            if (defined(action)) {
                Cartesian2.clone(position, mouseUpEvent.position);

                action(mouseUpEvent);
            }

            if (defined(clickAction)) {
                var startPosition = screenSpaceEventHandler._primaryStartPosition;
                var xDiff = startPosition.x - position.x;
                var yDiff = startPosition.y - position.y;
                var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

                if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
                    Cartesian2.clone(position, mouseClickEvent.position);

                    clickAction(mouseClickEvent);
                }
            }
        }
    }

    var mouseMoveEvent = {
        startPosition : new Cartesian2(),
        endPosition : new Cartesian2()
    };

    function handleMouseMove(screenSpaceEventHandler, event) {
        if (!canProcessMouseEvent(screenSpaceEventHandler)) {
            return;
        }

        var modifier = getModifier(event);

        var position = getPosition(screenSpaceEventHandler, event, screenSpaceEventHandler._primaryPosition);
        var previousPosition = screenSpaceEventHandler._primaryPreviousPosition;

        var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier);

        if (defined(action)) {
            Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition);
            Cartesian2.clone(position, mouseMoveEvent.endPosition);

            action(mouseMoveEvent);
        }

        Cartesian2.clone(position, previousPosition);

        if (defined(screenSpaceEventHandler._buttonDown)) {
            event.preventDefault();
        }
    }

    var mouseDblClickEvent = {
        position : new Cartesian2()
    };

    function handleDblClick(screenSpaceEventHandler, event) {
        var button = event.button;

        var screenSpaceEventType;
        if (button === MouseButton.LEFT) {
            screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK;
        } else {
            return;
        }

        var modifier = getModifier(event);

        var action = screenSpaceEventHandler.getInputAction(screenSpaceEventType, modifier);

        if (defined(action)) {
            getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position);

            action(mouseDblClickEvent);
        }
    }

    function handleWheel(screenSpaceEventHandler, event) {
        // currently this event exposes the delta value in terms of
        // the obsolete mousewheel event type.  so, for now, we adapt the other
        // values to that scheme.
        var delta;

        // standard wheel event uses deltaY.  sign is opposite wheelDelta.
        // deltaMode indicates what unit it is in.
        if (defined(event.deltaY)) {
            var deltaMode = event.deltaMode;
            if (deltaMode === event.DOM_DELTA_PIXEL) {
                delta = -event.deltaY;
            } else if (deltaMode === event.DOM_DELTA_LINE) {
                delta = -event.deltaY * 40;
            } else {
                // DOM_DELTA_PAGE
                delta = -event.deltaY * 120;
            }
        } else if (event.detail > 0) {
            // old Firefox versions use event.detail to count the number of clicks. The sign
            // of the integer is the direction the wheel is scrolled.
            delta = event.detail * -120;
        } else {
            delta = event.wheelDelta;
        }

        if (!defined(delta)) {
            return;
        }

        var modifier = getModifier(event);
        var action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.WHEEL, modifier);

        if (defined(action)) {
            action(delta);

            event.preventDefault();
        }
    }

    function handleTouchStart(screenSpaceEventHandler, event) {
        gotTouchEvent(screenSpaceEventHandler);

        var changedTouches = event.changedTouches;

        var i;
        var length = changedTouches.length;
        var touch;
        var identifier;
        var positions = screenSpaceEventHandler._positions;

        for (i = 0; i < length; ++i) {
            touch = changedTouches[i];
            identifier = touch.identifier;
            positions.set(identifier, getPosition(screenSpaceEventHandler, touch, new Cartesian2()));
        }

        fireTouchEvents(screenSpaceEventHandler, event);

        var previousPositions = screenSpaceEventHandler._previousPositions;

        for (i = 0; i < length; ++i) {
            touch = changedTouches[i];
            identifier = touch.identifier;
            previousPositions.set(identifier, Cartesian2.clone(positions.get(identifier)));
        }
    }

    function handleTouchEnd(screenSpaceEventHandler, event) {
        gotTouchEvent(screenSpaceEventHandler);

        var changedTouches = event.changedTouches;

        var i;
        var length = changedTouches.length;
        var touch;
        var identifier;
        var positions = screenSpaceEventHandler._positions;

        for (i = 0; i < length; ++i) {
            touch = changedTouches[i];
            identifier = touch.identifier;
            positions.remove(identifier);
        }

        fireTouchEvents(screenSpaceEventHandler, event);

        var previousPositions = screenSpaceEventHandler._previousPositions;

        for (i = 0; i < length; ++i) {
            touch = changedTouches[i];
            identifier = touch.identifier;
            previousPositions.remove(identifier);
        }
    }

    var touchStartEvent = {
        position : new Cartesian2()
    };
    var touch2StartEvent = {
        position1 : new Cartesian2(),
        position2 : new Cartesian2()
    };
    var touchEndEvent = {
        position : new Cartesian2()
    };
    var touchClickEvent = {
        position : new Cartesian2()
    };

    function fireTouchEvents(screenSpaceEventHandler, event) {
        var modifier = getModifier(event);
        var positions = screenSpaceEventHandler._positions;
        var previousPositions = screenSpaceEventHandler._previousPositions;
        var numberOfTouches = positions.length;
        var action;
        var clickAction;

        if (numberOfTouches !== 1 && screenSpaceEventHandler._buttonDown === MouseButton.LEFT) {
            // transitioning from single touch, trigger UP and might trigger CLICK
            screenSpaceEventHandler._buttonDown = undefined;
            action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_UP, modifier);

            if (defined(action)) {
                Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchEndEvent.position);

                action(touchEndEvent);
            }

            if (numberOfTouches === 0) {
                // releasing single touch, check for CLICK
                clickAction = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_CLICK, modifier);

                if (defined(clickAction)) {
                    var startPosition = screenSpaceEventHandler._primaryStartPosition;
                    var endPosition = previousPositions.values[0];
                    var xDiff = startPosition.x - endPosition.x;
                    var yDiff = startPosition.y - endPosition.y;
                    var totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

                    if (totalPixels < screenSpaceEventHandler._clickPixelTolerance) {
                        Cartesian2.clone(screenSpaceEventHandler._primaryPosition, touchClickEvent.position);

                        clickAction(touchClickEvent);
                    }
                }
            }

            // Otherwise don't trigger CLICK, because we are adding more touches.
        }

        if (numberOfTouches !== 2 && screenSpaceEventHandler._isPinching) {
            // transitioning from pinch, trigger PINCH_END
            screenSpaceEventHandler._isPinching = false;

            action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.PINCH_END, modifier);

            if (defined(action)) {
                action();
            }
        }

        if (numberOfTouches === 1) {
            // transitioning to single touch, trigger DOWN
            var position = positions.values[0];
            Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
            Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
            Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);

            screenSpaceEventHandler._buttonDown = MouseButton.LEFT;

            action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.LEFT_DOWN, modifier);

            if (defined(action)) {
                Cartesian2.clone(position, touchStartEvent.position);

                action(touchStartEvent);
            }

            event.preventDefault();
        }

        if (numberOfTouches === 2) {
            // transitioning to pinch, trigger PINCH_START
            screenSpaceEventHandler._isPinching = true;

            action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.PINCH_START, modifier);

            if (defined(action)) {
                Cartesian2.clone(positions.values[0], touch2StartEvent.position1);
                Cartesian2.clone(positions.values[1], touch2StartEvent.position2);

                action(touch2StartEvent);

                // Touch-enabled devices, in particular iOS can have many default behaviours for
                // "pinch" events, which can still be executed unless we prevent them here.
                event.preventDefault();
            }
        }
    }

    function handleTouchMove(screenSpaceEventHandler, event) {
        gotTouchEvent(screenSpaceEventHandler);

        var changedTouches = event.changedTouches;

        var i;
        var length = changedTouches.length;
        var touch;
        var identifier;
        var positions = screenSpaceEventHandler._positions;

        for (i = 0; i < length; ++i) {
            touch = changedTouches[i];
            identifier = touch.identifier;
            var position = positions.get(identifier);
            if (defined(position)) {
                getPosition(screenSpaceEventHandler, touch, position);
            }
        }

        fireTouchMoveEvents(screenSpaceEventHandler, event);

        var previousPositions = screenSpaceEventHandler._previousPositions;

        for (i = 0; i < length; ++i) {
            touch = changedTouches[i];
            identifier = touch.identifier;
            Cartesian2.clone(positions.get(identifier), previousPositions.get(identifier));
        }
    }

    var touchMoveEvent = {
        startPosition : new Cartesian2(),
        endPosition : new Cartesian2()
    };
    var touchPinchMovementEvent = {
        distance : {
            startPosition : new Cartesian2(),
            endPosition : new Cartesian2()
        },
        angleAndHeight : {
            startPosition : new Cartesian2(),
            endPosition : new Cartesian2()
        }
    };

    function fireTouchMoveEvents(screenSpaceEventHandler, event) {
        var modifier = getModifier(event);
        var positions = screenSpaceEventHandler._positions;
        var previousPositions = screenSpaceEventHandler._previousPositions;
        var numberOfTouches = positions.length;
        var action;

        if (numberOfTouches === 1 && screenSpaceEventHandler._buttonDown === MouseButton.LEFT) {
            // moving single touch
            var position = positions.values[0];
            Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);

            var previousPosition = screenSpaceEventHandler._primaryPreviousPosition;

            action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.MOUSE_MOVE, modifier);

            if (defined(action)) {
                Cartesian2.clone(previousPosition, touchMoveEvent.startPosition);
                Cartesian2.clone(position, touchMoveEvent.endPosition);

                action(touchMoveEvent);
            }

            Cartesian2.clone(position, previousPosition);

            event.preventDefault();
        } else if (numberOfTouches === 2 && screenSpaceEventHandler._isPinching) {
            // moving pinch

            action = screenSpaceEventHandler.getInputAction(ScreenSpaceEventType.PINCH_MOVE, modifier);
            if (defined(action)) {
                var position1 = positions.values[0];
                var position2 = positions.values[1];
                var previousPosition1 = previousPositions.values[0];
                var previousPosition2 = previousPositions.values[1];

                var dX = position2.x - position1.x;
                var dY = position2.y - position1.y;
                var dist = Math.sqrt(dX * dX + dY * dY) * 0.25;

                var prevDX = previousPosition2.x - previousPosition1.x;
                var prevDY = previousPosition2.y - previousPosition1.y;
                var prevDist = Math.sqrt(prevDX * prevDX + prevDY * prevDY) * 0.25;

                var cY = (position2.y + position1.y) * 0.125;
                var prevCY = (previousPosition2.y + previousPosition1.y) * 0.125;
                var angle = Math.atan2(dY, dX);
                var prevAngle = Math.atan2(prevDY, prevDX);

                Cartesian2.fromElements(0.0, prevDist, touchPinchMovementEvent.distance.startPosition);
                Cartesian2.fromElements(0.0, dist, touchPinchMovementEvent.distance.endPosition);

                Cartesian2.fromElements(prevAngle, prevCY, touchPinchMovementEvent.angleAndHeight.startPosition);
                Cartesian2.fromElements(angle, cY, touchPinchMovementEvent.angleAndHeight.endPosition);

                action(touchPinchMovementEvent);
            }
        }
    }

    function handlePointerDown(screenSpaceEventHandler, event) {
        event.target.setPointerCapture(event.pointerId);

        if (event.pointerType === 'touch') {
            var positions = screenSpaceEventHandler._positions;

            var identifier = event.pointerId;
            positions.set(identifier, getPosition(screenSpaceEventHandler, event, new Cartesian2()));

            fireTouchEvents(screenSpaceEventHandler, event);

            var previousPositions = screenSpaceEventHandler._previousPositions;
            previousPositions.set(identifier, Cartesian2.clone(positions.get(identifier)));
        } else {
            handleMouseDown(screenSpaceEventHandler, event);
        }
    }

    function handlePointerUp(screenSpaceEventHandler, event) {
        if (event.pointerType === 'touch') {
            var positions = screenSpaceEventHandler._positions;

            var identifier = event.pointerId;
            positions.remove(identifier);

            fireTouchEvents(screenSpaceEventHandler, event);

            var previousPositions = screenSpaceEventHandler._previousPositions;
            previousPositions.remove(identifier);
        } else {
            handleMouseUp(screenSpaceEventHandler, event);
        }
    }

    function handlePointerMove(screenSpaceEventHandler, event) {
        if (event.pointerType === 'touch') {
            var positions = screenSpaceEventHandler._positions;

            var identifier = event.pointerId;
            var position = positions.get(identifier);
            if(!defined(position)){
                return;
            }

            getPosition(screenSpaceEventHandler, event, position);
            fireTouchMoveEvents(screenSpaceEventHandler, event);

            var previousPositions = screenSpaceEventHandler._previousPositions;
            Cartesian2.clone(positions.get(identifier), previousPositions.get(identifier));
        } else {
            handleMouseMove(screenSpaceEventHandler, event);
        }
    }

    /**
     * Handles user input events. Custom functions can be added to be executed on
     * when the user enters input.
     *
     * @alias ScreenSpaceEventHandler
     *
     * @param {Canvas} [element=document] The element to add events to.
     *
     * @constructor
     */
    function ScreenSpaceEventHandler(element) {
        this._inputEvents = {};
        this._buttonDown = undefined;
        this._isPinching = false;
        this._lastSeenTouchEvent = -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;

        this._primaryStartPosition = new Cartesian2();
        this._primaryPosition = new Cartesian2();
        this._primaryPreviousPosition = new Cartesian2();

        this._positions = new AssociativeArray();
        this._previousPositions = new AssociativeArray();

        this._removalFunctions = [];

        // TODO: Revisit when doing mobile development. May need to be configurable
        // or determined based on the platform?
        this._clickPixelTolerance = 5;

        this._element = defaultValue(element, document);

        registerListeners(this);
    }

    /**
     * Set a function to be executed on an input event.
     *
     * @param {Function} action Function to be executed when the input event occurs.
     * @param {Number} type The ScreenSpaceEventType of input event.
     * @param {Number} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
     * event occurs.
     *
     * @see ScreenSpaceEventHandler#getInputAction
     * @see ScreenSpaceEventHandler#removeInputAction
     */
    ScreenSpaceEventHandler.prototype.setInputAction = function(action, type, modifier) {
                if (!defined(action)) {
            throw new DeveloperError('action is required.');
        }
        if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getInputEventKey(type, modifier);
        this._inputEvents[key] = action;
    };

    /**
     * Returns the function to be executed on an input event.
     *
     * @param {Number} type The ScreenSpaceEventType of input event.
     * @param {Number} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
     * event occurs.
     *
     * @see ScreenSpaceEventHandler#setInputAction
     * @see ScreenSpaceEventHandler#removeInputAction
     */
    ScreenSpaceEventHandler.prototype.getInputAction = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getInputEventKey(type, modifier);
        return this._inputEvents[key];
    };

    /**
     * Removes the function to be executed on an input event.
     *
     * @param {Number} type The ScreenSpaceEventType of input event.
     * @param {Number} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
     * event occurs.
     *
     * @see ScreenSpaceEventHandler#getInputAction
     * @see ScreenSpaceEventHandler#setInputAction
     */
    ScreenSpaceEventHandler.prototype.removeInputAction = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getInputEventKey(type, modifier);
        delete this._inputEvents[key];
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see ScreenSpaceEventHandler#destroy
     */
    ScreenSpaceEventHandler.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes listeners held by this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * handler = handler && handler.destroy();
     *
     * @see ScreenSpaceEventHandler#isDestroyed
     */
    ScreenSpaceEventHandler.prototype.destroy = function() {
        unregisterListeners(this);

        return destroyObject(this);
    };

    /**
     * The amount of time, in milliseconds, that mouse events will be disabled after
     * receiving any touch events, such that any emulated mouse events will be ignored.
     * @default 800
     */
    ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;

    return ScreenSpaceEventHandler;
});

/*global define*/
define('Core/ShowGeometryInstanceAttribute',[
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError'
    ], function(
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Value and type information for per-instance geometry attribute that determines if the geometry instance will be shown.
     *
     * @alias ShowGeometryInstanceAttribute
     * @constructor
     *
     * @param {Boolean} [show=true] Determines if the geometry instance will be shown.
     *
     *
     * @example
     * var instance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.BoxGeometry({
     *     vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL,
     *     minimum : new Cesium.Cartesian3(-250000.0, -250000.0, -250000.0),
     *     maximum : new Cesium.Cartesian3(250000.0, 250000.0, 250000.0)
     *   }),
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 1000000.0), new Cesium.Matrix4()),
     *   id : 'box',
     *   attributes : {
     *     show : new Cesium.ShowGeometryInstanceAttribute(false)
     *   }
     * });
     * 
     * @see GeometryInstance
     * @see GeometryInstanceAttribute
     */
    function ShowGeometryInstanceAttribute(show) {
        show = defaultValue(show, true);

        /**
         * The values for the attributes stored in a typed array.
         *
         * @type Uint8Array
         *
         * @default [1.0]
         */
        this.value = ShowGeometryInstanceAttribute.toValue(show);
    }

    defineProperties(ShowGeometryInstanceAttribute.prototype, {
        /**
         * The datatype of each component in the attribute, e.g., individual elements in
         * {@link ColorGeometryInstanceAttribute#value}.
         *
         * @memberof ShowGeometryInstanceAttribute.prototype
         *
         * @type {ComponentDatatype}
         * @readonly
         *
         * @default {@link ComponentDatatype.UNSIGNED_BYTE}
         */
        componentDatatype : {
            get : function() {
                return ComponentDatatype.UNSIGNED_BYTE;
            }
        },

        /**
         * The number of components in the attributes, i.e., {@link ColorGeometryInstanceAttribute#value}.
         *
         * @memberof ShowGeometryInstanceAttribute.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @default 1
         */
        componentsPerAttribute : {
            get : function() {
                return 1;
            }
        },

        /**
         * When <code>true</code> and <code>componentDatatype</code> is an integer format,
         * indicate that the components should be mapped to the range [0, 1] (unsigned)
         * or [-1, 1] (signed) when they are accessed as floating-point for rendering.
         *
         * @memberof ShowGeometryInstanceAttribute.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        normalize : {
            get : function() {
                return false;
            }
        }
    });

    /**
     * Converts a boolean show to a typed array that can be used to assign a show attribute.
     *
     * @param {Boolean} show The show value.
     * @param {Uint8Array} [result] The array to store the result in, if undefined a new instance will be created.
     * @returns {Uint8Array} The modified result parameter or a new instance if result was undefined.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true, attributes.show);
     */
    ShowGeometryInstanceAttribute.toValue = function(show, result) {
                if (!defined(show)) {
            throw new DeveloperError('show is required.');
        }
        
        if (!defined(result)) {
            return new Uint8Array([show]);
        }
        result[0] = show;
        return result;
    };

    return ShowGeometryInstanceAttribute;
});

/*global define*/
define('Core/Simon1994PlanetaryPositions',[
        './Cartesian3',
        './defined',
        './DeveloperError',
        './JulianDate',
        './Math',
        './Matrix3',
        './TimeConstants',
        './TimeStandard'
    ], function(
        Cartesian3,
        defined,
        DeveloperError,
        JulianDate,
        CesiumMath,
        Matrix3,
        TimeConstants,
        TimeStandard) {
    'use strict';

    /**
     * Contains functions for finding the Cartesian coordinates of the sun and the moon in the
     * Earth-centered inertial frame.
     *
     * @exports Simon1994PlanetaryPositions
     */
    var Simon1994PlanetaryPositions = {};

    function computeTdbMinusTtSpice(daysSinceJ2000InTerrestrialTime) {
        /* STK Comments ------------------------------------------------------
         * This function uses constants designed to be consistent with
         * the SPICE Toolkit from JPL version N0051 (unitim.c)
         * M0 = 6.239996
         * M0Dot = 1.99096871e-7 rad/s = 0.01720197 rad/d
         * EARTH_ECC = 1.671e-2
         * TDB_AMPL = 1.657e-3 secs
         *--------------------------------------------------------------------*/

        //* Values taken as specified in STK Comments except: 0.01720197 rad/day = 1.99096871e-7 rad/sec
        //* Here we use the more precise value taken from the SPICE value 1.99096871e-7 rad/sec converted to rad/day
        //* All other constants are consistent with the SPICE implementation of the TDB conversion
        //* except where we treat the independent time parameter to be in TT instead of TDB.
        //* This is an approximation made to facilitate performance due to the higher prevalance of
        //* the TT2TDB conversion over TDB2TT in order to avoid having to iterate when converting to TDB for the JPL ephemeris.
        //* Days are used instead of seconds to provide a slight improvement in numerical precision.

        //* For more information see:
        //* http://www.cv.nrao.edu/~rfisher/Ephemerides/times.html#TDB
        //* ftp://ssd.jpl.nasa.gov/pub/eph/planets/ioms/ExplSupplChap8.pdf

        var g = 6.239996 + (0.0172019696544) * daysSinceJ2000InTerrestrialTime;
        return 1.657e-3 * Math.sin(g + 1.671e-2 * Math.sin(g));
    }

    var TdtMinusTai = 32.184;
    var J2000d = 2451545;
    function taiToTdb(date, result) {
        //Converts TAI to TT
        result = JulianDate.addSeconds(date, TdtMinusTai, result);

        //Converts TT to TDB
        var days = JulianDate.totalDays(result) - J2000d;
        result = JulianDate.addSeconds(result, computeTdbMinusTtSpice(days), result);

        return result;
    }

    var epoch = new JulianDate(2451545, 0, TimeStandard.TAI); //Actually TDB (not TAI)
    var MetersPerKilometer = 1000.0;
    var RadiansPerDegree = CesiumMath.RADIANS_PER_DEGREE;
    var RadiansPerArcSecond = CesiumMath.RADIANS_PER_ARCSECOND;
    var MetersPerAstronomicalUnit = 1.49597870e+11; // IAU 1976 value

    var perifocalToEquatorial = new Matrix3();
    function elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee, longitudeOfNode, meanLongitude, result) {
        if (inclination < 0.0) {
            inclination = -inclination;
            longitudeOfNode += CesiumMath.PI;
        }

                if (inclination < 0 || inclination > CesiumMath.PI) {
            throw new DeveloperError('The inclination is out of range. Inclination must be greater than or equal to zero and less than or equal to Pi radians.');
        }
        
        var radiusOfPeriapsis = semimajorAxis * (1.0 - eccentricity);
        var argumentOfPeriapsis = longitudeOfPerigee - longitudeOfNode;
        var rightAscensionOfAscendingNode = longitudeOfNode;
        var trueAnomaly = meanAnomalyToTrueAnomaly(meanLongitude - longitudeOfPerigee, eccentricity);
        var type = chooseOrbit(eccentricity, 0.0);

                if (type === 'Hyperbolic' && Math.abs(CesiumMath.negativePiToPi(trueAnomaly)) >= Math.acos(- 1.0 / eccentricity)) {
            throw new DeveloperError('The true anomaly of the hyperbolic orbit lies outside of the bounds of the hyperbola.');
        }
        
        perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscensionOfAscendingNode, perifocalToEquatorial);
        var semilatus = radiusOfPeriapsis * (1.0 + eccentricity);
        var costheta = Math.cos(trueAnomaly);
        var sintheta = Math.sin(trueAnomaly);

        var denom = (1.0 + eccentricity * costheta);

                if (denom <= CesiumMath.Epsilon10) {
            throw new DeveloperError('elements cannot be converted to cartesian');
        }
        
        var radius = semilatus / denom;
        if (!defined(result)) {
            result = new Cartesian3(radius * costheta, radius * sintheta, 0.0);
        } else {
            result.x = radius * costheta;
            result.y = radius * sintheta;
            result.z = 0.0;
        }

        return Matrix3.multiplyByVector(perifocalToEquatorial, result, result);
    }

    function chooseOrbit(eccentricity, tolerance) {
                if (eccentricity < 0) {
            throw new DeveloperError('eccentricity cannot be negative.');
        }
        
        if (eccentricity <= tolerance) {
            return 'Circular';
        } else if (eccentricity < 1.0 - tolerance) {
            return 'Elliptical';
        } else if (eccentricity <= 1.0 + tolerance) {
            return 'Parabolic';
        } else {
            return 'Hyperbolic';
        }
    }

    // Calculates the true anomaly given the mean anomaly and the eccentricity.
    function meanAnomalyToTrueAnomaly(meanAnomaly, eccentricity) {
                if (eccentricity < 0.0 || eccentricity >= 1.0) {
            throw new DeveloperError('eccentricity out of range.');
        }
        
        var eccentricAnomaly = meanAnomalyToEccentricAnomaly(meanAnomaly, eccentricity);
        return eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity);
    }

    var maxIterationCount = 50;
    var keplerEqConvergence = CesiumMath.EPSILON8;
    // Calculates the eccentric anomaly given the mean anomaly and the eccentricity.
    function meanAnomalyToEccentricAnomaly(meanAnomaly, eccentricity) {
                if (eccentricity < 0.0 || eccentricity >= 1.0) {
            throw new DeveloperError('eccentricity out of range.');
        }
        
        var revs = Math.floor(meanAnomaly / CesiumMath.TWO_PI);

        // Find angle in current revolution
        meanAnomaly -= revs * CesiumMath.TWO_PI;

        // calculate starting value for iteration sequence
        var iterationValue = meanAnomaly + (eccentricity * Math.sin(meanAnomaly)) /
            (1.0 - Math.sin(meanAnomaly + eccentricity) + Math.sin(meanAnomaly));

        // Perform Newton-Raphson iteration on Kepler's equation
        var eccentricAnomaly = Number.MAX_VALUE;

        var count;
        for (count = 0;
            count < maxIterationCount && Math.abs(eccentricAnomaly - iterationValue) > keplerEqConvergence;
            ++count)
        {
            eccentricAnomaly = iterationValue;
            var NRfunction = eccentricAnomaly - eccentricity * Math.sin(eccentricAnomaly) - meanAnomaly;
            var dNRfunction = 1 - eccentricity * Math.cos(eccentricAnomaly);
            iterationValue = eccentricAnomaly - NRfunction / dNRfunction;
        }

                if (count >= maxIterationCount) {
            throw new DeveloperError('Kepler equation did not converge');
            // STK Components uses a numerical method to find the eccentric anomaly in the case that Kepler's
            // equation does not converge. We don't expect that to ever be necessary for the reasonable orbits used here.
        }
        
        eccentricAnomaly = iterationValue + revs * CesiumMath.TWO_PI;
        return eccentricAnomaly;
    }

     // Calculates the true anomaly given the eccentric anomaly and the eccentricity.
    function eccentricAnomalyToTrueAnomaly(eccentricAnomaly, eccentricity) {
                if (eccentricity < 0.0 || eccentricity >= 1.0) {
            throw new DeveloperError('eccentricity out of range.');
        }
        
        // Calculate the number of previous revolutions
        var revs = Math.floor(eccentricAnomaly / CesiumMath.TWO_PI);

        // Find angle in current revolution
        eccentricAnomaly -= revs * CesiumMath.TWO_PI;

        // Calculate true anomaly from eccentric anomaly
        var trueAnomalyX = Math.cos(eccentricAnomaly) - eccentricity;
        var trueAnomalyY = Math.sin(eccentricAnomaly) * Math.sqrt(1 - eccentricity * eccentricity);

        var trueAnomaly = Math.atan2(trueAnomalyY, trueAnomalyX);

        // Ensure the correct quadrant
        trueAnomaly = CesiumMath.zeroToTwoPi(trueAnomaly);
        if (eccentricAnomaly < 0)
        {
            trueAnomaly -= CesiumMath.TWO_PI;
        }

        // Add on previous revolutions
        trueAnomaly += revs * CesiumMath.TWO_PI;

        return trueAnomaly;
    }

     // Calculates the transformation matrix to convert from the perifocal (PQW) coordinate
     // system to inertial cartesian coordinates.
    function perifocalToCartesianMatrix(argumentOfPeriapsis, inclination, rightAscension, result) {
                if (inclination < 0 || inclination > CesiumMath.PI) {
            throw new DeveloperError('inclination out of range');
        }
        
        var cosap = Math.cos(argumentOfPeriapsis);
        var sinap = Math.sin(argumentOfPeriapsis);

        var cosi = Math.cos(inclination);
        var sini = Math.sin(inclination);

        var cosraan = Math.cos(rightAscension);
        var sinraan = Math.sin(rightAscension);
        if (!defined(result)) {
            result = new Matrix3(
                    cosraan * cosap - sinraan * sinap * cosi,
                    -cosraan * sinap - sinraan * cosap * cosi,
                    sinraan * sini,

                    sinraan * cosap + cosraan * sinap * cosi,
                    -sinraan * sinap + cosraan * cosap * cosi,
                    -cosraan * sini,

                    sinap * sini,
                    cosap * sini,
                    cosi);
        } else {
            result[0] = cosraan * cosap - sinraan * sinap * cosi;
            result[1] = sinraan * cosap + cosraan * sinap * cosi;
            result[2] = sinap * sini;
            result[3] = -cosraan * sinap - sinraan * cosap * cosi;
            result[4] = -sinraan * sinap + cosraan * cosap * cosi;
            result[5] = cosap * sini;
            result[6] = sinraan * sini;
            result[7] = -cosraan * sini;
            result[8] = cosi;
        }
        return result;
    }

    // From section 5.8
    var semiMajorAxis0 = 1.0000010178 * MetersPerAstronomicalUnit;
    var meanLongitude0 = 100.46645683 * RadiansPerDegree;
    var meanLongitude1 = 1295977422.83429 * RadiansPerArcSecond;

    // From table 6
    var p1u = 16002;
    var p2u = 21863;
    var p3u = 32004;
    var p4u = 10931;
    var p5u = 14529;
    var p6u = 16368;
    var p7u = 15318;
    var p8u = 32794;

    var Ca1 = 64 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca2 = -152 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca3 = 62 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca4 = -8 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca5 = 32 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca6 = -41 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca7 = 19 * 1e-7 * MetersPerAstronomicalUnit;
    var Ca8 = -11 * 1e-7 * MetersPerAstronomicalUnit;

    var Sa1 = -150 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa2 = -46 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa3 = 68 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa4 = 54 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa5 = 14 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa6 = 24 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa7 = -28 * 1e-7 * MetersPerAstronomicalUnit;
    var Sa8 = 22 * 1e-7 * MetersPerAstronomicalUnit;

    var q1u = 10;
    var q2u = 16002;
    var q3u = 21863;
    var q4u = 10931;
    var q5u = 1473;
    var q6u = 32004;
    var q7u = 4387;
    var q8u = 73;

    var Cl1 = -325 * 1e-7;
    var Cl2 = -322 * 1e-7;
    var Cl3 = -79 * 1e-7;
    var Cl4 = 232 * 1e-7;
    var Cl5 = -52 * 1e-7;
    var Cl6 = 97 * 1e-7;
    var Cl7 = 55 * 1e-7;
    var Cl8 = -41 * 1e-7;

    var Sl1 = -105 * 1e-7;
    var Sl2 = -137 * 1e-7;
    var Sl3 = 258 * 1e-7;
    var Sl4 = 35 * 1e-7;
    var Sl5 = -116 * 1e-7;
    var Sl6 = -88 * 1e-7;
    var Sl7 = -112 * 1e-7;
    var Sl8 = -80 * 1e-7;

    var scratchDate = new JulianDate(0, 0.0, TimeStandard.TAI);
    /**
     * Gets a point describing the motion of the Earth-Moon barycenter according to the equations
     * described in section 6.
     */

    function computeSimonEarthMoonBarycenter(date, result) {

        // t is thousands of years from J2000 TDB
        taiToTdb(date, scratchDate);
        var x = (scratchDate.dayNumber - epoch.dayNumber) + ((scratchDate.secondsOfDay - epoch.secondsOfDay)/TimeConstants.SECONDS_PER_DAY);
        var t = x / (TimeConstants.DAYS_PER_JULIAN_CENTURY * 10.0);

        var u = 0.35953620 * t;
        var semimajorAxis = semiMajorAxis0 +
                            Ca1 * Math.cos(p1u * u) + Sa1 * Math.sin(p1u * u) +
                            Ca2 * Math.cos(p2u * u) + Sa2 * Math.sin(p2u * u) +
                            Ca3 * Math.cos(p3u * u) + Sa3 * Math.sin(p3u * u) +
                            Ca4 * Math.cos(p4u * u) + Sa4 * Math.sin(p4u * u) +
                            Ca5 * Math.cos(p5u * u) + Sa5 * Math.sin(p5u * u) +
                            Ca6 * Math.cos(p6u * u) + Sa6 * Math.sin(p6u * u) +
                            Ca7 * Math.cos(p7u * u) + Sa7 * Math.sin(p7u * u) +
                            Ca8 * Math.cos(p8u * u) + Sa8 * Math.sin(p8u * u);
        var meanLongitude = meanLongitude0 + meanLongitude1 * t +
                            Cl1 * Math.cos(q1u * u) + Sl1 * Math.sin(q1u * u) +
                            Cl2 * Math.cos(q2u * u) + Sl2 * Math.sin(q2u * u) +
                            Cl3 * Math.cos(q3u * u) + Sl3 * Math.sin(q3u * u) +
                            Cl4 * Math.cos(q4u * u) + Sl4 * Math.sin(q4u * u) +
                            Cl5 * Math.cos(q5u * u) + Sl5 * Math.sin(q5u * u) +
                            Cl6 * Math.cos(q6u * u) + Sl6 * Math.sin(q6u * u) +
                            Cl7 * Math.cos(q7u * u) + Sl7 * Math.sin(q7u * u) +
                            Cl8 * Math.cos(q8u * u) + Sl8 * Math.sin(q8u * u);

        // All constants in this part are from section 5.8
        var eccentricity = 0.0167086342 - 0.0004203654 * t;
        var longitudeOfPerigee = 102.93734808 * RadiansPerDegree + 11612.35290 * RadiansPerArcSecond * t;
        var inclination = 469.97289 * RadiansPerArcSecond * t;
        var longitudeOfNode = 174.87317577 * RadiansPerDegree - 8679.27034 * RadiansPerArcSecond * t;

        return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee,
                longitudeOfNode, meanLongitude, result);
    }

    /**
     * Gets a point describing the position of the moon according to the equations described in section 4.
     */
    function computeSimonMoon(date, result) {
        taiToTdb(date, scratchDate);
        var x = (scratchDate.dayNumber - epoch.dayNumber) + ((scratchDate.secondsOfDay - epoch.secondsOfDay)/TimeConstants.SECONDS_PER_DAY);
        var t = x / (TimeConstants.DAYS_PER_JULIAN_CENTURY);
        var t2 = t * t;
        var t3 = t2 * t;
        var t4 = t3 * t;

        // Terms from section 3.4 (b.1)
        var semimajorAxis = 383397.7725 + 0.0040 * t;
        var eccentricity = 0.055545526 - 0.000000016 * t;
        var inclinationConstant = 5.15668983 * RadiansPerDegree;
        var inclinationSecPart = -0.00008 * t + 0.02966 * t2 -
                                  0.000042 * t3 - 0.00000013 * t4;
        var longitudeOfPerigeeConstant = 83.35324312 * RadiansPerDegree;
        var longitudeOfPerigeeSecPart = 14643420.2669 * t - 38.2702 * t2 -
                                        0.045047 * t3 + 0.00021301 * t4;
        var longitudeOfNodeConstant = 125.04455501 * RadiansPerDegree;
        var longitudeOfNodeSecPart = -6967919.3631 * t + 6.3602 * t2 +
                                      0.007625 * t3 - 0.00003586 * t4;
        var meanLongitudeConstant = 218.31664563 * RadiansPerDegree;
        var meanLongitudeSecPart = 1732559343.48470 * t - 6.3910 * t2 +
                                   0.006588 * t3 - 0.00003169 * t4;

        // Delaunay arguments from section 3.5 b
        var D = 297.85019547 * RadiansPerDegree + RadiansPerArcSecond *
                    (1602961601.2090 * t - 6.3706 * t2 + 0.006593 * t3 - 0.00003169 * t4);
        var F = 93.27209062 * RadiansPerDegree + RadiansPerArcSecond *
                    (1739527262.8478 * t - 12.7512 * t2 - 0.001037 * t3 + 0.00000417 * t4);
        var l = 134.96340251 * RadiansPerDegree + RadiansPerArcSecond *
                    (1717915923.2178 * t + 31.8792 * t2 + 0.051635 * t3 - 0.00024470 * t4);
        var lprime = 357.52910918 * RadiansPerDegree + RadiansPerArcSecond *
                    (129596581.0481 * t - 0.5532 * t2 + 0.000136 * t3 - 0.00001149 * t4);
        var psi = 310.17137918 * RadiansPerDegree - RadiansPerArcSecond *
                    (6967051.4360 * t + 6.2068 * t2 + 0.007618 * t3 - 0.00003219 * t4);

        // Add terms from Table 4
        var twoD = 2.0 * D;
        var fourD = 4.0 * D;
        var sixD = 6.0 * D;
        var twol = 2.0 * l;
        var threel = 3.0 * l;
        var fourl = 4.0 * l;
        var twoF = 2.0 * F;
        semimajorAxis += 3400.4 * Math.cos(twoD) - 635.6 * Math.cos(twoD - l) -
                         235.6 * Math.cos(l) + 218.1 * Math.cos(twoD - lprime) +
                         181.0 * Math.cos(twoD + l);
        eccentricity += 0.014216 * Math.cos(twoD - l) + 0.008551 * Math.cos(twoD - twol) -
                        0.001383 * Math.cos(l) + 0.001356 * Math.cos(twoD + l) -
                        0.001147 * Math.cos(fourD - threel) - 0.000914 * Math.cos(fourD - twol) +
                        0.000869 * Math.cos(twoD - lprime - l) - 0.000627 * Math.cos(twoD) -
                        0.000394 * Math.cos(fourD - fourl) + 0.000282 * Math.cos(twoD - lprime - twol) -
                        0.000279 * Math.cos(D - l) - 0.000236 * Math.cos(twol) +
                        0.000231 * Math.cos(fourD) + 0.000229 * Math.cos(sixD - fourl) -
                        0.000201 * Math.cos(twol - twoF);
        inclinationSecPart += 486.26 * Math.cos(twoD - twoF) - 40.13 * Math.cos(twoD) +
                              37.51 * Math.cos(twoF) + 25.73 * Math.cos(twol - twoF) +
                              19.97 * Math.cos(twoD - lprime - twoF);
        longitudeOfPerigeeSecPart += -55609 * Math.sin(twoD - l) - 34711 * Math.sin(twoD - twol) -
                                      9792 * Math.sin(l) + 9385 * Math.sin(fourD - threel) +
                                      7505 * Math.sin(fourD - twol) + 5318 * Math.sin(twoD + l) +
                                      3484 * Math.sin(fourD - fourl) - 3417 * Math.sin(twoD - lprime - l) -
                                      2530 * Math.sin(sixD - fourl) - 2376 * Math.sin(twoD) -
                                      2075 * Math.sin(twoD - threel) - 1883 * Math.sin(twol) -
                                      1736 * Math.sin(sixD - 5.0 * l) + 1626 * Math.sin(lprime) -
                                      1370 * Math.sin(sixD - threel);
        longitudeOfNodeSecPart += -5392 * Math.sin(twoD - twoF) - 540 * Math.sin(lprime) -
                                  441 * Math.sin(twoD) + 423 * Math.sin(twoF) -
                                  288 * Math.sin(twol - twoF);
        meanLongitudeSecPart += -3332.9 * Math.sin(twoD) + 1197.4 * Math.sin(twoD - l) -
                                662.5 * Math.sin(lprime) + 396.3 * Math.sin(l) -
                                218.0 * Math.sin(twoD - lprime);

        // Add terms from Table 5
        var twoPsi = 2.0 * psi;
        var threePsi = 3.0 * psi;
        inclinationSecPart += 46.997 * Math.cos(psi) * t - 0.614 * Math.cos(twoD - twoF + psi) * t +
                              0.614 * Math.cos(twoD - twoF - psi) * t - 0.0297 * Math.cos(twoPsi) * t2 -
                              0.0335 * Math.cos(psi) * t2 + 0.0012 * Math.cos(twoD - twoF + twoPsi) * t2 -
                              0.00016 * Math.cos(psi) * t3 + 0.00004 * Math.cos(threePsi) * t3 +
                              0.00004 * Math.cos(twoPsi) * t3;
        var perigeeAndMean = 2.116 * Math.sin(psi) * t - 0.111 * Math.sin(twoD - twoF - psi) * t -
                                0.0015 * Math.sin(psi) * t2;
        longitudeOfPerigeeSecPart += perigeeAndMean;
        meanLongitudeSecPart += perigeeAndMean;
        longitudeOfNodeSecPart += -520.77 * Math.sin(psi) * t + 13.66 * Math.sin(twoD - twoF + psi) * t +
                                  1.12 * Math.sin(twoD - psi) * t - 1.06 * Math.sin(twoF - psi) * t +
                                  0.660 * Math.sin(twoPsi) * t2 + 0.371 * Math.sin(psi) * t2 -
                                  0.035 * Math.sin(twoD - twoF + twoPsi) * t2 - 0.015 * Math.sin(twoD - twoF + psi) * t2 +
                                  0.0014 * Math.sin(psi) * t3 - 0.0011 * Math.sin(threePsi) * t3 -
                                  0.0009 * Math.sin(twoPsi) * t3;

        // Add constants and convert units
        semimajorAxis *= MetersPerKilometer;
        var inclination = inclinationConstant + inclinationSecPart * RadiansPerArcSecond;
        var longitudeOfPerigee = longitudeOfPerigeeConstant + longitudeOfPerigeeSecPart * RadiansPerArcSecond;
        var meanLongitude = meanLongitudeConstant + meanLongitudeSecPart * RadiansPerArcSecond;
        var longitudeOfNode = longitudeOfNodeConstant + longitudeOfNodeSecPart * RadiansPerArcSecond;

        return elementsToCartesian(semimajorAxis, eccentricity, inclination, longitudeOfPerigee,
                                   longitudeOfNode, meanLongitude, result);
    }

    /**
     * Gets a point describing the motion of the Earth.  This point uses the Moon point and
     * the 1992 mu value (ratio between Moon and Earth masses) in Table 2 of the paper in order
     * to determine the position of the Earth relative to the Earth-Moon barycenter.
     */
    var moonEarthMassRatio = 0.012300034; // From 1992 mu value in Table 2
    var factor = moonEarthMassRatio / (moonEarthMassRatio + 1.0) * -1;
    function computeSimonEarth(date, result) {
        result = computeSimonMoon(date, result);
        return Cartesian3.multiplyByScalar(result, factor, result);
    }

    // Values for the <code>axesTransformation</code> needed for the rotation were found using the STK Components
    // GreographicTransformer on the position of the sun center of mass point and the earth J2000 frame.

    var axesTransformation = new Matrix3(1.0000000000000002, 5.619723173785822e-16, 4.690511510146299e-19,
            -5.154129427414611e-16, 0.9174820620691819, -0.39777715593191376,
             -2.23970096136568e-16, 0.39777715593191376, 0.9174820620691819);
    var translation = new Cartesian3();
    /**
     * Computes the position of the Sun in the Earth-centered inertial frame
     *
     * @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} Calculated sun position
     */
    Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame= function(julianDate, result){
        if (!defined(julianDate)) {
            julianDate = JulianDate.now();
        }

        if (!defined(result)) {
            result = new Cartesian3();
        }

        //first forward transformation
        translation = computeSimonEarthMoonBarycenter(julianDate, translation);
        result = Cartesian3.negate(translation, result);

        //second forward transformation
        computeSimonEarth(julianDate, translation);

        Cartesian3.subtract(result, translation, result);
        Matrix3.multiplyByVector(axesTransformation, result, result);

        return result;
    };

    /**
     * Computes the position of the Moon in the Earth-centered inertial frame
     *
     * @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} Calculated moon position
     */
    Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame = function(julianDate, result){
        if (!defined(julianDate)) {
            julianDate = JulianDate.now();
        }

        result = computeSimonMoon(julianDate, result);
        Matrix3.multiplyByVector(axesTransformation, result, result);

        return result;
    };

    return Simon1994PlanetaryPositions;
});

/*global define*/
define('Core/SimplePolylineGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './Color',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PolylinePipeline',
        './PrimitiveType'
    ], function(
        BoundingSphere,
        Cartesian3,
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PolylinePipeline,
        PrimitiveType) {
    'use strict';

    function interpolateColors(p0, p1, color0, color1, minDistance, array, offset) {
        var numPoints = PolylinePipeline.numberOfPoints(p0, p1, minDistance);
        var i;

        var r0 = color0.red;
        var g0 = color0.green;
        var b0 = color0.blue;
        var a0 = color0.alpha;

        var r1 = color1.red;
        var g1 = color1.green;
        var b1 = color1.blue;
        var a1 = color1.alpha;

        if (Color.equals(color0, color1)) {
            for (i = 0; i < numPoints; i++) {
                array[offset++] = Color.floatToByte(r0);
                array[offset++] = Color.floatToByte(g0);
                array[offset++] = Color.floatToByte(b0);
                array[offset++] = Color.floatToByte(a0);
            }
            return offset;
        }

        var redPerVertex = (r1 - r0) / numPoints;
        var greenPerVertex = (g1 - g0) / numPoints;
        var bluePerVertex = (b1 - b0) / numPoints;
        var alphaPerVertex = (a1 - a0) / numPoints;

        var index = offset;
        for (i = 0; i < numPoints; i++) {
            array[index++] = Color.floatToByte(r0 + i * redPerVertex);
            array[index++] = Color.floatToByte(g0 + i * greenPerVertex);
            array[index++] = Color.floatToByte(b0 + i * bluePerVertex);
            array[index++] = Color.floatToByte(a0 + i * alphaPerVertex);
        }

        return index;
    }

    /**
     * A description of a polyline modeled as a line strip; the first two positions define a line segment,
     * and each additional position defines a line segment from the previous position.
     *
     * @alias SimplePolylineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of {@link Cartesian3} defining the positions in the polyline as a line strip.
     * @param {Color[]} [options.colors] An Array of {@link Color} defining the per vertex or per segment colors.
     * @param {Boolean} [options.colorsPerVertex=false] A boolean that determines whether the colors will be flat across each segment of the line or interpolated across the vertices.
     * @param {Boolean} [options.followSurface=true] A boolean that determines whether positions will be adjusted to the surface of the ellipsoid via a great arc.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude if options.followSurface=true. Determines the number of positions in the buffer.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid to be used as a reference.
     *
     * @exception {DeveloperError} At least two positions are required.
     * @exception {DeveloperError} colors has an invalid length.
     *
     * @see SimplePolylineGeometry#createGeometry
     *
     * @example
     * // A polyline with two connected line segments
     * var polyline = new Cesium.SimplePolylineGeometry({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     0.0, 0.0,
     *     5.0, 0.0,
     *     5.0, 5.0
     *   ])
     * });
     * var geometry = Cesium.SimplePolylineGeometry.createGeometry(polyline);
     */
    function SimplePolylineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.positions;
        var colors = options.colors;
        var colorsPerVertex = defaultValue(options.colorsPerVertex, false);

                if ((!defined(positions)) || (positions.length < 2)) {
            throw new DeveloperError('At least two positions are required.');
        }
        if (defined(colors) && ((colorsPerVertex && colors.length < positions.length) || (!colorsPerVertex && colors.length < positions.length - 1))) {
            throw new DeveloperError('colors has an invalid length.');
        }
        
        this._positions = positions;
        this._colors = colors;
        this._colorsPerVertex = colorsPerVertex;
        this._followSurface = defaultValue(options.followSurface, true);
        this._granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        this._workerName = 'createSimplePolylineGeometry';

        var numComponents = 1 + positions.length * Cartesian3.packedLength;
        numComponents += defined(colors) ? 1 + colors.length * Color.packedLength : 1;

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = numComponents + Ellipsoid.packedLength + 3;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {SimplePolylineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    SimplePolylineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        var colors = value._colors;
        length = defined(colors) ? colors.length : 0.0;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
            Color.pack(colors[i], array, startingIndex);
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex++] = value._colorsPerVertex ? 1.0 : 0.0;
        array[startingIndex++] = value._followSurface ? 1.0 : 0.0;
        array[startingIndex]   = value._granularity;

        return array;
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {SimplePolylineGeometry} [result] The object into which to store the result.
     * @returns {SimplePolylineGeometry} The modified result parameter or a new SimplePolylineGeometry instance if one was not provided.
     */
    SimplePolylineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        length = array[startingIndex++];
        var colors = length > 0 ? new Array(length) : undefined;

        for (i = 0; i < length; ++i, startingIndex += Color.packedLength) {
            colors[i] = Color.unpack(array, startingIndex);
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        var colorsPerVertex = array[startingIndex++] === 1.0;
        var followSurface = array[startingIndex++] === 1.0;
        var granularity = array[startingIndex];

        if (!defined(result)) {
            return new SimplePolylineGeometry({
                positions : positions,
                colors : colors,
                ellipsoid : ellipsoid,
                colorsPerVertex : colorsPerVertex,
                followSurface : followSurface,
                granularity : granularity
            });
        }

        result._positions = positions;
        result._colors = colors;
        result._ellipsoid = ellipsoid;
        result._colorsPerVertex = colorsPerVertex;
        result._followSurface = followSurface;
        result._granularity = granularity;

        return result;
    };

    var scratchArray1 = new Array(2);
    var scratchArray2 = new Array(2);
    var generateArcOptionsScratch = {
        positions : scratchArray1,
        height: scratchArray2,
        ellipsoid: undefined,
        minDistance : undefined
    };

    /**
     * Computes the geometric representation of a simple polyline, including its vertices, indices, and a bounding sphere.
     *
     * @param {SimplePolylineGeometry} simplePolylineGeometry A description of the polyline.
     * @returns {Geometry} The computed vertices and indices.
     */
    SimplePolylineGeometry.createGeometry = function(simplePolylineGeometry) {
        var positions = simplePolylineGeometry._positions;
        var colors = simplePolylineGeometry._colors;
        var colorsPerVertex = simplePolylineGeometry._colorsPerVertex;
        var followSurface = simplePolylineGeometry._followSurface;
        var granularity = simplePolylineGeometry._granularity;
        var ellipsoid = simplePolylineGeometry._ellipsoid;

        var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);
        var perSegmentColors = defined(colors) && !colorsPerVertex;

        var i;
        var length = positions.length;

        var positionValues;
        var numberOfPositions;
        var colorValues;
        var color;
        var offset = 0;

        if (followSurface) {
            var heights = PolylinePipeline.extractHeights(positions, ellipsoid);
            var generateArcOptions = generateArcOptionsScratch;
            generateArcOptions.minDistance = minDistance;
            generateArcOptions.ellipsoid = ellipsoid;

            if (perSegmentColors) {
                var positionCount = 0;
                for (i = 0; i < length - 1; i++) {
                    positionCount += PolylinePipeline.numberOfPoints(positions[i], positions[i+1], minDistance) + 1;
                }

                positionValues = new Float64Array(positionCount * 3);
                colorValues = new Uint8Array(positionCount * 4);

                generateArcOptions.positions = scratchArray1;
                generateArcOptions.height= scratchArray2;

                var ci = 0;
                for (i = 0; i < length - 1; ++i) {
                    scratchArray1[0] = positions[i];
                    scratchArray1[1] = positions[i + 1];

                    scratchArray2[0] = heights[i];
                    scratchArray2[1] = heights[i + 1];

                    var pos = PolylinePipeline.generateArc(generateArcOptions);

                    if (defined(colors)) {
                        var segLen = pos.length / 3;
                        color = colors[i];
                        for(var k = 0; k < segLen; ++k) {
                            colorValues[ci++] = Color.floatToByte(color.red);
                            colorValues[ci++] = Color.floatToByte(color.green);
                            colorValues[ci++] = Color.floatToByte(color.blue);
                            colorValues[ci++] = Color.floatToByte(color.alpha);
                        }
                    }

                    positionValues.set(pos, offset);
                    offset += pos.length;
                }
            } else {
                generateArcOptions.positions = positions;
                generateArcOptions.height= heights;
                positionValues = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));

                if (defined(colors)) {
                    colorValues = new Uint8Array(positionValues.length / 3 * 4);

                    for (i = 0; i < length - 1; ++i) {
                        var p0 = positions[i];
                        var p1 = positions[i + 1];
                        var c0 = colors[i];
                        var c1 = colors[i + 1];
                        offset = interpolateColors(p0, p1, c0, c1, minDistance, colorValues, offset);
                    }

                    var lastColor = colors[length - 1];
                    colorValues[offset++] = Color.floatToByte(lastColor.red);
                    colorValues[offset++] = Color.floatToByte(lastColor.green);
                    colorValues[offset++] = Color.floatToByte(lastColor.blue);
                    colorValues[offset++] = Color.floatToByte(lastColor.alpha);
                }
            }
        } else {
            numberOfPositions = perSegmentColors ? length * 2 - 2 : length;
            positionValues = new Float64Array(numberOfPositions * 3);
            colorValues = defined(colors) ? new Uint8Array(numberOfPositions * 4) : undefined;

            var positionIndex = 0;
            var colorIndex = 0;

            for (i = 0; i < length; ++i) {
                var p = positions[i];

                if (perSegmentColors && i > 0) {
                    Cartesian3.pack(p, positionValues, positionIndex);
                    positionIndex += 3;

                    color = colors[i - 1];
                    colorValues[colorIndex++] = Color.floatToByte(color.red);
                    colorValues[colorIndex++] = Color.floatToByte(color.green);
                    colorValues[colorIndex++] = Color.floatToByte(color.blue);
                    colorValues[colorIndex++] = Color.floatToByte(color.alpha);
                }

                if (perSegmentColors && i === length - 1) {
                    break;
                }

                Cartesian3.pack(p, positionValues, positionIndex);
                positionIndex += 3;

                if (defined(colors)) {
                    color = colors[i];
                    colorValues[colorIndex++] = Color.floatToByte(color.red);
                    colorValues[colorIndex++] = Color.floatToByte(color.green);
                    colorValues[colorIndex++] = Color.floatToByte(color.blue);
                    colorValues[colorIndex++] = Color.floatToByte(color.alpha);
                }
            }
        }

        var attributes = new GeometryAttributes();
        attributes.position = new GeometryAttribute({
            componentDatatype : ComponentDatatype.DOUBLE,
            componentsPerAttribute : 3,
            values : positionValues
        });

        if (defined(colors)) {
            attributes.color = new GeometryAttribute({
                componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
                componentsPerAttribute : 4,
                values : colorValues,
                normalize : true
            });
        }

        numberOfPositions = positionValues.length / 3;
        var numberOfIndices = (numberOfPositions - 1) * 2;
        var indices = IndexDatatype.createTypedArray(numberOfPositions, numberOfIndices);

        var index = 0;
        for (i = 0; i < numberOfPositions - 1; ++i) {
            indices[index++] = i;
            indices[index++] = i + 1;
        }

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : BoundingSphere.fromPoints(positions)
        });
    };

    return SimplePolylineGeometry;
});

/*global define*/
define('Core/SphereGeometry',[
        './Cartesian3',
        './Check',
        './defaultValue',
        './defined',
        './EllipsoidGeometry',
        './VertexFormat'
    ], function(
        Cartesian3,
        Check,
        defaultValue,
        defined,
        EllipsoidGeometry,
        VertexFormat) {
    'use strict';

    /**
     * A description of a sphere centered at the origin.
     *
     * @alias SphereGeometry
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Number} [options.radius=1.0] The radius of the sphere.
     * @param {Number} [options.stackPartitions=64] The number of times to partition the ellipsoid into stacks.
     * @param {Number} [options.slicePartitions=64] The number of times to partition the ellipsoid into radial slices.
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     *
     * @exception {DeveloperError} options.slicePartitions cannot be less than three.
     * @exception {DeveloperError} options.stackPartitions cannot be less than three.
     *
     * @see SphereGeometry#createGeometry
     *
     * @example
     * var sphere = new Cesium.SphereGeometry({
     *   radius : 100.0,
     *   vertexFormat : Cesium.VertexFormat.POSITION_ONLY
     * });
     * var geometry = Cesium.SphereGeometry.createGeometry(sphere);
     */
    function SphereGeometry(options) {
        var radius = defaultValue(options.radius, 1.0);
        var radii = new Cartesian3(radius, radius, radius);
        var ellipsoidOptions = {
                radii: radii,
                stackPartitions: options.stackPartitions,
                slicePartitions: options.slicePartitions,
                vertexFormat: options.vertexFormat
        };

        this._ellipsoidGeometry = new EllipsoidGeometry(ellipsoidOptions);
        this._workerName = 'createSphereGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    SphereGeometry.packedLength = EllipsoidGeometry.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {SphereGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    SphereGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        
        return EllipsoidGeometry.pack(value._ellipsoidGeometry, array, startingIndex);
    };

    var scratchEllipsoidGeometry = new EllipsoidGeometry();
    var scratchOptions = {
        radius : undefined,
        radii : new Cartesian3(),
        vertexFormat : new VertexFormat(),
        stackPartitions : undefined,
        slicePartitions : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {SphereGeometry} [result] The object into which to store the result.
     * @returns {SphereGeometry} The modified result parameter or a new SphereGeometry instance if one was not provided.
     */
    SphereGeometry.unpack = function(array, startingIndex, result) {
        var ellipsoidGeometry = EllipsoidGeometry.unpack(array, startingIndex, scratchEllipsoidGeometry);
        scratchOptions.vertexFormat = VertexFormat.clone(ellipsoidGeometry._vertexFormat, scratchOptions.vertexFormat);
        scratchOptions.stackPartitions = ellipsoidGeometry._stackPartitions;
        scratchOptions.slicePartitions = ellipsoidGeometry._slicePartitions;

        if (!defined(result)) {
            scratchOptions.radius = ellipsoidGeometry._radii.x;
            return new SphereGeometry(scratchOptions);
        }

        Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii);
        result._ellipsoidGeometry = new EllipsoidGeometry(scratchOptions);
        return result;
    };

    /**
     * Computes the geometric representation of a sphere, including its vertices, indices, and a bounding sphere.
     *
     * @param {SphereGeometry} sphereGeometry A description of the sphere.
     * @returns {Geometry} The computed vertices and indices.
     */
    SphereGeometry.createGeometry = function(sphereGeometry) {
        return EllipsoidGeometry.createGeometry(sphereGeometry._ellipsoidGeometry);
    };

    return SphereGeometry;
});

/*global define*/
define('Core/SphereOutlineGeometry',[
        './Cartesian3',
        './Check',
        './defaultValue',
        './defined',
        './EllipsoidOutlineGeometry'
    ], function(
        Cartesian3,
        Check,
        defaultValue,
        defined,
        EllipsoidOutlineGeometry) {
    'use strict';

    /**
     * A description of the outline of a sphere.
     *
     * @alias SphereOutlineGeometry
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Number} [options.radius=1.0] The radius of the sphere.
     * @param {Number} [options.stackPartitions=10] The count of stacks for the sphere (1 greater than the number of parallel lines).
     * @param {Number} [options.slicePartitions=8] The count of slices for the sphere (Equal to the number of radial lines).
     * @param {Number} [options.subdivisions=200] The number of points per line, determining the granularity of the curvature .
     *
     * @exception {DeveloperError} options.stackPartitions must be greater than or equal to one.
     * @exception {DeveloperError} options.slicePartitions must be greater than or equal to zero.
     * @exception {DeveloperError} options.subdivisions must be greater than or equal to zero.
     *
     * @example
     * var sphere = new Cesium.SphereOutlineGeometry({
     *   radius : 100.0,
     *   stackPartitions : 6,
     *   slicePartitions: 5
     * });
     * var geometry = Cesium.SphereOutlineGeometry.createGeometry(sphere);
     */
    function SphereOutlineGeometry(options) {
        var radius = defaultValue(options.radius, 1.0);
        var radii = new Cartesian3(radius, radius, radius);
        var ellipsoidOptions = {
                radii: radii,
                stackPartitions: options.stackPartitions,
                slicePartitions: options.slicePartitions,
                subdivisions: options.subdivisions
        };

        this._ellipsoidGeometry = new EllipsoidOutlineGeometry(ellipsoidOptions);
        this._workerName = 'createSphereOutlineGeometry';
    }

    /**
     * The number of elements used to pack the object into an array.
     * @type {Number}
     */
    SphereOutlineGeometry.packedLength = EllipsoidOutlineGeometry.packedLength;

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {SphereOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    SphereOutlineGeometry.pack = function(value, array, startingIndex) {
                Check.typeOf.object('value', value);
        
        return EllipsoidOutlineGeometry.pack(value._ellipsoidGeometry, array, startingIndex);
    };

    var scratchEllipsoidGeometry = new EllipsoidOutlineGeometry();
    var scratchOptions = {
        radius : undefined,
        radii : new Cartesian3(),
        stackPartitions : undefined,
        slicePartitions : undefined,
        subdivisions : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {SphereOutlineGeometry} [result] The object into which to store the result.
     * @returns {SphereOutlineGeometry} The modified result parameter or a new SphereOutlineGeometry instance if one was not provided.
     */
    SphereOutlineGeometry.unpack = function(array, startingIndex, result) {
        var ellipsoidGeometry = EllipsoidOutlineGeometry.unpack(array, startingIndex, scratchEllipsoidGeometry);
        scratchOptions.stackPartitions = ellipsoidGeometry._stackPartitions;
        scratchOptions.slicePartitions = ellipsoidGeometry._slicePartitions;
        scratchOptions.subdivisions = ellipsoidGeometry._subdivisions;

        if (!defined(result)) {
            scratchOptions.radius = ellipsoidGeometry._radii.x;
            return new SphereOutlineGeometry(scratchOptions);
        }

        Cartesian3.clone(ellipsoidGeometry._radii, scratchOptions.radii);
        result._ellipsoidGeometry = new EllipsoidOutlineGeometry(scratchOptions);
        return result;
    };

    /**
     * Computes the geometric representation of an outline of a sphere, including its vertices, indices, and a bounding sphere.
     *
     * @param {SphereOutlineGeometry} sphereGeometry A description of the sphere outline.
     * @returns {Geometry} The computed vertices and indices.
     */
    SphereOutlineGeometry.createGeometry = function(sphereGeometry) {
        return EllipsoidOutlineGeometry.createGeometry(sphereGeometry._ellipsoidGeometry);
    };

    return SphereOutlineGeometry;
});

/*global define*/
define('Core/Spherical',[
        './Check',
        './defaultValue',
        './defined'
    ], function(
        Check,
        defaultValue,
        defined) {
    'use strict';

    /**
     * A set of curvilinear 3-dimensional coordinates.
     *
     * @alias Spherical
     * @constructor
     *
     * @param {Number} [clock=0.0] The angular coordinate lying in the xy-plane measured from the positive x-axis and toward the positive y-axis.
     * @param {Number} [cone=0.0] The angular coordinate measured from the positive z-axis and toward the negative z-axis.
     * @param {Number} [magnitude=1.0] The linear coordinate measured from the origin.
     */
    function Spherical(clock, cone, magnitude) {
        this.clock = defaultValue(clock, 0.0);
        this.cone = defaultValue(cone, 0.0);
        this.magnitude = defaultValue(magnitude, 1.0);
    }

    /**
     * Converts the provided Cartesian3 into Spherical coordinates.
     *
     * @param {Cartesian3} cartesian3 The Cartesian3 to be converted to Spherical.
     * @param {Spherical} [result] The object in which the result will be stored, if undefined a new instance will be created.
     * @returns {Spherical} The modified result parameter, or a new instance if one was not provided.
     */
    Spherical.fromCartesian3 = function(cartesian3, result) {
                Check.typeOf.object('cartesian3', cartesian3);
        
        var x = cartesian3.x;
        var y = cartesian3.y;
        var z = cartesian3.z;
        var radialSquared = x * x + y * y;

        if (!defined(result)) {
            result = new Spherical();
        }

        result.clock = Math.atan2(y, x);
        result.cone = Math.atan2(Math.sqrt(radialSquared), z);
        result.magnitude = Math.sqrt(radialSquared + z * z);
        return result;
    };

    /**
     * Creates a duplicate of a Spherical.
     *
     * @param {Spherical} spherical The spherical to clone.
     * @param {Spherical} [result] The object to store the result into, if undefined a new instance will be created.
     * @returns {Spherical} The modified result parameter or a new instance if result was undefined. (Returns undefined if spherical is undefined)
     */
    Spherical.clone = function(spherical, result) {
        if (!defined(spherical)) {
            return undefined;
        }

        if (!defined(result)) {
            return new Spherical(spherical.clock, spherical.cone, spherical.magnitude);
        }

        result.clock = spherical.clock;
        result.cone = spherical.cone;
        result.magnitude = spherical.magnitude;
        return result;
    };

    /**
     * Computes the normalized version of the provided spherical.
     *
     * @param {Spherical} spherical The spherical to be normalized.
     * @param {Spherical} [result] The object to store the result into, if undefined a new instance will be created.
     * @returns {Spherical} The modified result parameter or a new instance if result was undefined.
     */
    Spherical.normalize = function(spherical, result) {
              Check.typeOf.object('spherical', spherical);
        
        if (!defined(result)) {
            return new Spherical(spherical.clock, spherical.cone, 1.0);
        }

        result.clock = spherical.clock;
        result.cone = spherical.cone;
        result.magnitude = 1.0;
        return result;
    };

    /**
     * Returns true if the first spherical is equal to the second spherical, false otherwise.
     *
     * @param {Spherical} left The first Spherical to be compared.
     * @param {Spherical} right The second Spherical to be compared.
     * @returns {Boolean} true if the first spherical is equal to the second spherical, false otherwise.
     */
    Spherical.equals = function(left, right) {
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (left.clock === right.clock) &&
                (left.cone === right.cone) &&
                (left.magnitude === right.magnitude));
    };

    /**
     * Returns true if the first spherical is within the provided epsilon of the second spherical, false otherwise.
     *
     * @param {Spherical} left The first Spherical to be compared.
     * @param {Spherical} right The second Spherical to be compared.
     * @param {Number} [epsilon=0.0] The epsilon to compare against.
     * @returns {Boolean} true if the first spherical is within the provided epsilon of the second spherical, false otherwise.
     */
    Spherical.equalsEpsilon = function(left, right, epsilon) {
        epsilon = defaultValue(epsilon, 0.0);
        return (left === right) ||
               ((defined(left)) &&
                (defined(right)) &&
                (Math.abs(left.clock - right.clock) <= epsilon) &&
                (Math.abs(left.cone - right.cone) <= epsilon) &&
                (Math.abs(left.magnitude - right.magnitude) <= epsilon));
    };

    /**
     * Returns true if this spherical is equal to the provided spherical, false otherwise.
     *
     * @param {Spherical} other The Spherical to be compared.
     * @returns {Boolean} true if this spherical is equal to the provided spherical, false otherwise.
     */
    Spherical.prototype.equals = function(other) {
        return Spherical.equals(this, other);
    };

    /**
     * Creates a duplicate of this Spherical.
     *
     * @param {Spherical} [result] The object to store the result into, if undefined a new instance will be created.
     * @returns {Spherical} The modified result parameter or a new instance if result was undefined.
     */
    Spherical.prototype.clone = function(result) {
        return Spherical.clone(this, result);
    };

    /**
    * Returns true if this spherical is within the provided epsilon of the provided spherical, false otherwise.
    *
    * @param {Spherical} other The Spherical to be compared.
    * @param {Number} epsilon The epsilon to compare against.
    * @returns {Boolean} true if this spherical is within the provided epsilon of the provided spherical, false otherwise.
    */
    Spherical.prototype.equalsEpsilon = function(other, epsilon) {
        return Spherical.equalsEpsilon(this, other, epsilon);
    };

    /**
    * Returns a string representing this instance in the format (clock, cone, magnitude).
    *
    * @returns {String} A string representing this instance.
    */
    Spherical.prototype.toString = function() {
        return '(' + this.clock + ', ' + this.cone + ', ' + this.magnitude + ')';
    };

    return Spherical;
});

/*global define*/
define('Core/subdivideArray',[
        './defined',
        './DeveloperError'
    ], function(
        defined,
        DeveloperError) {
    'use strict';

    /**
     * Subdivides an array into a number of smaller, equal sized arrays.
     *
     * @exports subdivideArray
     *
     * @param {Array} array The array to divide.
     * @param {Number} numberOfArrays The number of arrays to divide the provided array into.
     *
     * @exception {DeveloperError} numberOfArrays must be greater than 0.
     */
    function subdivideArray(array, numberOfArrays) {
                if (!defined(array)) {
            throw new DeveloperError('array is required.');
        }

        if (!defined(numberOfArrays) || numberOfArrays < 1) {
            throw new DeveloperError('numberOfArrays must be greater than 0.');
        }
        
        var result = [];
        var len = array.length;
        var i = 0;
        while (i < len) {
            var size = Math.ceil((len - i) / numberOfArrays--);
            result.push(array.slice(i, i + size));
            i += size;
        }
        return result;
    }

    return subdivideArray;
});

/*global define*/
define('Core/TerrainData',[
        './defineProperties',
        './DeveloperError'
    ], function(
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Terrain data for a single tile.  This type describes an
     * interface and is not intended to be instantiated directly.
     *
     * @alias TerrainData
     * @constructor
     *
     * @see HeightmapTerrainData
     * @see QuantizedMeshTerrainData
     */
    function TerrainData() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(TerrainData.prototype, {
        /**
         * The water mask included in this terrain data, if any.  A water mask is a rectangular
         * Uint8Array or image where a value of 255 indicates water and a value of 0 indicates land.
         * Values in between 0 and 255 are allowed as well to smoothly blend between land and water.
         * @memberof TerrainData.prototype
         * @type {Uint8Array|Image|Canvas}
         */
        waterMask : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Computes the terrain height at a specified longitude and latitude.
     * @function
     *
     * @param {Rectangle} rectangle The rectangle covered by this terrain data.
     * @param {Number} longitude The longitude in radians.
     * @param {Number} latitude The latitude in radians.
     * @returns {Number} The terrain height at the specified position.  If the position
     *          is outside the rectangle, this method will extrapolate the height, which is likely to be wildly
     *          incorrect for positions far outside the rectangle.
     */
    TerrainData.prototype.interpolateHeight = DeveloperError.throwInstantiationError;

    /**
     * Determines if a given child tile is available, based on the
     * {@link TerrainData#childTileMask}.  The given child tile coordinates are assumed
     * to be one of the four children of this tile.  If non-child tile coordinates are
     * given, the availability of the southeast child tile is returned.
     * @function
     *
     * @param {Number} thisX The tile X coordinate of this (the parent) tile.
     * @param {Number} thisY The tile Y coordinate of this (the parent) tile.
     * @param {Number} childX The tile X coordinate of the child tile to check for availability.
     * @param {Number} childY The tile Y coordinate of the child tile to check for availability.
     * @returns {Boolean} True if the child tile is available; otherwise, false.
     */
    TerrainData.prototype.isChildAvailable = DeveloperError.throwInstantiationError;

    /**
     * Creates a {@link TerrainMesh} from this terrain data.
     * @function
     *
     * @private
     *
     * @param {TilingScheme} tilingScheme The tiling scheme to which this tile belongs.
     * @param {Number} x The X coordinate of the tile for which to create the terrain data.
     * @param {Number} y The Y coordinate of the tile for which to create the terrain data.
     * @param {Number} level The level of the tile for which to create the terrain data.
     * @returns {Promise.<TerrainMesh>|undefined} A promise for the terrain mesh, or undefined if too many
     *          asynchronous mesh creations are already in progress and the operation should
     *          be retried later.
     */
    TerrainData.prototype.createMesh = DeveloperError.throwInstantiationError;

    /**
     * Upsamples this terrain data for use by a descendant tile.
     * @function
     *
     * @param {TilingScheme} tilingScheme The tiling scheme of this terrain data.
     * @param {Number} thisX The X coordinate of this tile in the tiling scheme.
     * @param {Number} thisY The Y coordinate of this tile in the tiling scheme.
     * @param {Number} thisLevel The level of this tile in the tiling scheme.
     * @param {Number} descendantX The X coordinate within the tiling scheme of the descendant tile for which we are upsampling.
     * @param {Number} descendantY The Y coordinate within the tiling scheme of the descendant tile for which we are upsampling.
     * @param {Number} descendantLevel The level within the tiling scheme of the descendant tile for which we are upsampling.
     * @returns {Promise.<TerrainData>|undefined} A promise for upsampled terrain data for the descendant tile,
     *          or undefined if too many asynchronous upsample operations are in progress and the request has been
     *          deferred.
     */
    TerrainData.prototype.upsample = DeveloperError.throwInstantiationError;

    /**
     * Gets a value indicating whether or not this terrain data was created by upsampling lower resolution
     * terrain data.  If this value is false, the data was obtained from some other source, such
     * as by downloading it from a remote server.  This method should return true for instances
     * returned from a call to {@link TerrainData#upsample}.
     * @function
     *
     * @returns {Boolean} True if this instance was created by upsampling; otherwise, false.
     */
    TerrainData.prototype.wasCreatedByUpsampling = DeveloperError.throwInstantiationError;

    return TerrainData;
});

/*global define*/
define('Core/TilingScheme',[
        './defineProperties',
        './DeveloperError'
    ], function(
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * A tiling scheme for geometry or imagery on the surface of an ellipsoid.  At level-of-detail zero,
     * the coarsest, least-detailed level, the number of tiles is configurable.
     * At level of detail one, each of the level zero tiles has four children, two in each direction.
     * At level of detail two, each of the level one tiles has four children, two in each direction.
     * This continues for as many levels as are present in the geometry or imagery source.
     *
     * @alias TilingScheme
     * @constructor
     *
     * @see WebMercatorTilingScheme
     * @see GeographicTilingScheme
     */
    function TilingScheme(options) {
                throw new DeveloperError('This type should not be instantiated directly.  Instead, use WebMercatorTilingScheme or GeographicTilingScheme.');
            }

    defineProperties(TilingScheme.prototype, {
        /**
         * Gets the ellipsoid that is tiled by the tiling scheme.
         * @memberof TilingScheme.prototype
         * @type {Ellipsoid}
         */
        ellipsoid: {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the rectangle, in radians, covered by this tiling scheme.
         * @memberof TilingScheme.prototype
         * @type {Rectangle}
         */
        rectangle : {
            get : DeveloperError.throwInstantiationError
        },


        /**
         * Gets the map projection used by the tiling scheme.
         * @memberof TilingScheme.prototype
         * @type {MapProjection}
         */
        projection : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Gets the total number of tiles in the X direction at a specified level-of-detail.
     * @function
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the X direction at the given level.
     */
    TilingScheme.prototype.getNumberOfXTilesAtLevel = DeveloperError.throwInstantiationError;

    /**
     * Gets the total number of tiles in the Y direction at a specified level-of-detail.
     * @function
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the Y direction at the given level.
     */
    TilingScheme.prototype.getNumberOfYTilesAtLevel = DeveloperError.throwInstantiationError;

    /**
     * Transforms an rectangle specified in geodetic radians to the native coordinate system
     * of this tiling scheme.
     * @function
     *
     * @param {Rectangle} rectangle The rectangle to transform.
     * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
     *          is undefined.
     */
    TilingScheme.prototype.rectangleToNativeRectangle = DeveloperError.throwInstantiationError;

    /**
     * Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
     * of the tiling scheme.
     * @function
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    TilingScheme.prototype.tileXYToNativeRectangle = DeveloperError.throwInstantiationError;

    /**
     * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
     * @function
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    TilingScheme.prototype.tileXYToRectangle = DeveloperError.throwInstantiationError;

    /**
     * Calculates the tile x, y coordinates of the tile containing
     * a given cartographic position.
     * @function
     *
     * @param {Cartographic} position The position.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
     *          if 'result' is undefined.
     */
    TilingScheme.prototype.positionToTileXY = DeveloperError.throwInstantiationError;

    return TilingScheme;
});

/*global define*/
define('Core/TimeIntervalCollection',[
        './binarySearch',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Event',
        './JulianDate',
        './TimeInterval'
    ], function(
        binarySearch,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        JulianDate,
        TimeInterval) {
    'use strict';

    function compareIntervalStartTimes(left, right) {
        return JulianDate.compare(left.start, right.start);
    }

    /**
     * A non-overlapping collection of {@link TimeInterval} instances sorted by start time.
     * @alias TimeIntervalCollection
     * @constructor
     *
     * @param {TimeInterval[]} [intervals] An array of intervals to add to the collection.
     */
    function TimeIntervalCollection(intervals) {
        this._intervals = [];
        this._changedEvent = new Event();

        if (defined(intervals)) {
            var length = intervals.length;
            for (var i = 0; i < length; i++) {
                this.addInterval(intervals[i]);
            }
        }
    }

    defineProperties(TimeIntervalCollection.prototype, {
        /**
         * Gets an event that is raised whenever the collection of intervals change.
         * @memberof TimeIntervalCollection.prototype
         * @type {Event}
         * @readonly
         */
        changedEvent : {
            get : function() {
                return this._changedEvent;
            }
        },

        /**
         * Gets the start time of the collection.
         * @memberof TimeIntervalCollection.prototype
         * @type {JulianDate}
         * @readonly
         */
        start : {
            get : function() {
                var intervals = this._intervals;
                return intervals.length === 0 ? undefined : intervals[0].start;
            }
        },

        /**
         * Gets whether or not the start time is included in the collection.
         * @memberof TimeIntervalCollection.prototype
         * @type {Boolean}
         * @readonly
         */
        isStartIncluded : {
            get : function() {
                var intervals = this._intervals;
                return intervals.length === 0 ? false : intervals[0].isStartIncluded;
            }
        },

        /**
         * Gets the stop time of the collection.
         * @memberof TimeIntervalCollection.prototype
         * @type {JulianDate}
         * @readonly
         */
        stop : {
            get : function() {
                var intervals = this._intervals;
                var length = intervals.length;
                return length === 0 ? undefined : intervals[length - 1].stop;
            }
        },

        /**
         * Gets whether or not the stop time is included in the collection.
         * @memberof TimeIntervalCollection.prototype
         * @type {Boolean}
         * @readonly
         */
        isStopIncluded : {
            get : function() {
                var intervals = this._intervals;
                var length = intervals.length;
                return length === 0 ? false : intervals[length - 1].isStopIncluded;
            }
        },

        /**
         * Gets the number of intervals in the collection.
         * @memberof TimeIntervalCollection.prototype
         * @type {Number}
         * @readonly
         */
        length : {
            get : function() {
                return this._intervals.length;
            }
        },

        /**
         * Gets whether or not the collection is empty.
         * @memberof TimeIntervalCollection.prototype
         * @type {Boolean}
         * @readonly
         */
        isEmpty : {
            get : function() {
                return this._intervals.length === 0;
            }
        }
    });

    /**
     * Compares this instance against the provided instance componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {TimeIntervalCollection} [right] The right hand side collection.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    TimeIntervalCollection.prototype.equals = function(right, dataComparer) {
        if (this === right) {
            return true;
        }
        if (!(right instanceof TimeIntervalCollection)) {
            return false;
        }
        var intervals = this._intervals;
        var rightIntervals = right._intervals;
        var length = intervals.length;
        if (length !== rightIntervals.length) {
            return false;
        }
        for (var i = 0; i < length; i++) {
            if (!TimeInterval.equals(intervals[i], rightIntervals[i], dataComparer)) {
                return false;
            }
        }
        return true;
    };

    /**
     * Gets the interval at the specified index.
     *
     * @param {Number} index The index of the interval to retrieve.
     * @returns {TimeInterval} The interval at the specified index, or <code>undefined</code> if no interval exists as that index.
     */
    TimeIntervalCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._intervals[index];
    };

    /**
     * Removes all intervals from the collection.
     */
    TimeIntervalCollection.prototype.removeAll = function() {
        if (this._intervals.length > 0) {
            this._intervals.length = 0;
            this._changedEvent.raiseEvent(this);
        }
    };

    /**
     * Finds and returns the interval that contains the specified date.
     *
     * @param {JulianDate} date The date to search for.
     * @returns {TimeInterval|undefined} The interval containing the specified date, <code>undefined</code> if no such interval exists.
     */
    TimeIntervalCollection.prototype.findIntervalContainingDate = function(date) {
        var index = this.indexOf(date);
        return index >= 0 ? this._intervals[index] : undefined;
    };

    /**
     * Finds and returns the data for the interval that contains the specified date.
     *
     * @param {JulianDate} date The date to search for.
     * @returns {Object} The data for the interval containing the specified date, or <code>undefined</code> if no such interval exists.
     */
    TimeIntervalCollection.prototype.findDataForIntervalContainingDate = function(date) {
        var index = this.indexOf(date);
        return index >= 0 ? this._intervals[index].data : undefined;
    };

    /**
     * Checks if the specified date is inside this collection.
     *
     * @param {JulianDate} julianDate The date to check.
     * @returns {Boolean} <code>true</code> if the collection contains the specified date, <code>false</code> otherwise.
     */
    TimeIntervalCollection.prototype.contains = function(julianDate) {
        return this.indexOf(julianDate) >= 0;
    };

    var indexOfScratch = new TimeInterval();

    /**
     * Finds and returns the index of the interval in the collection that contains the specified date.
     *
     * @param {JulianDate} date The date to search for.
     * @returns {Number} The index of the interval that contains the specified date, if no such interval exists,
     * it returns a negative number which is the bitwise complement of the index of the next interval that
     * starts after the date, or if no interval starts after the specified date, the bitwise complement of
     * the length of the collection.
     */
    TimeIntervalCollection.prototype.indexOf = function(date) {
                if (!defined(date)) {
            throw new DeveloperError('date is required');
        }
        
        var intervals = this._intervals;
        indexOfScratch.start = date;
        indexOfScratch.stop = date;
        var index = binarySearch(intervals, indexOfScratch, compareIntervalStartTimes);
        if (index >= 0) {
            if (intervals[index].isStartIncluded) {
                return index;
            }

            if (index > 0 && intervals[index - 1].stop.equals(date) && intervals[index - 1].isStopIncluded) {
                return index - 1;
            }
            return ~index;
        }

        index = ~index;
        if (index > 0 && (index - 1) < intervals.length && TimeInterval.contains(intervals[index - 1], date)) {
            return index - 1;
        }
        return ~index;
    };

    /**
     * Returns the first interval in the collection that matches the specified parameters.
     * All parameters are optional and <code>undefined</code> parameters are treated as a don't care condition.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {JulianDate} [options.start] The start time of the interval.
     * @param {JulianDate} [options.stop] The stop time of the interval.
     * @param {Boolean} [options.isStartIncluded] <code>true</code> if <code>options.start</code> is included in the interval, <code>false</code> otherwise.
     * @param {Boolean} [options.isStopIncluded] <code>true</code> if <code>options.stop</code> is included in the interval, <code>false</code> otherwise.
     * @returns {TimeInterval} The first interval in the collection that matches the specified parameters.
     */
    TimeIntervalCollection.prototype.findInterval = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var start = options.start;
        var stop = options.stop;
        var isStartIncluded = options.isStartIncluded;
        var isStopIncluded = options.isStopIncluded;

        var intervals = this._intervals;
        for (var i = 0, len = intervals.length; i < len; i++) {
            var interval = intervals[i];
            if ((!defined(start) || interval.start.equals(start)) &&
                (!defined(stop) || interval.stop.equals(stop)) &&
                (!defined(isStartIncluded) || interval.isStartIncluded === isStartIncluded) &&
                (!defined(isStopIncluded) || interval.isStopIncluded === isStopIncluded)) {
                return intervals[i];
            }
        }
        return undefined;
    };

    /**
     * Adds an interval to the collection, merging intervals that contain the same data and
     * splitting intervals of different data as needed in order to maintain a non-overlapping collection.
     * The data in the new interval takes precedence over any existing intervals in the collection.
     *
     * @param {TimeInterval} interval The interval to add.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     */
    TimeIntervalCollection.prototype.addInterval = function(interval, dataComparer) {
                if (!defined(interval)) {
            throw new DeveloperError("interval is required");
        }
        
        if (interval.isEmpty) {
            return;
        }

        var comparison;
        var index;
        var intervals = this._intervals;

        // Handle the common case quickly: we're adding a new interval which is after all existing intervals.
        if (intervals.length === 0 || JulianDate.greaterThan(interval.start, intervals[intervals.length - 1].stop)) {
            intervals.push(interval);
            this._changedEvent.raiseEvent(this);
            return;
        }

        // Keep the list sorted by the start date
        index = binarySearch(intervals, interval, compareIntervalStartTimes);
        if (index < 0) {
            index = ~index;
        } else {
            // interval's start date exactly equals the start date of at least one interval in the collection.
            // It could actually equal the start date of two intervals if one of them does not actually
            // include the date.  In that case, the binary search could have found either.  We need to
            // look at the surrounding intervals and their IsStartIncluded properties in order to make sure
            // we're working with the correct interval.
            if (index > 0 && interval.isStartIncluded && intervals[index - 1].isStartIncluded && intervals[index - 1].start.equals(interval.start)) {
                --index;
            } else if (index < intervals.length && !interval.isStartIncluded && intervals[index].isStartIncluded && intervals[index].start.equals(interval.start)) {
                ++index;
            }
        }

        if (index > 0) {
            // Not the first thing in the list, so see if the interval before this one
            // overlaps this one.
            comparison = JulianDate.compare(intervals[index - 1].stop, interval.start);
            if (comparison > 0 || (comparison === 0 && (intervals[index - 1].isStopIncluded || interval.isStartIncluded))) {
                // There is an overlap
                if (defined(dataComparer) ? dataComparer(intervals[index - 1].data, interval.data) : (intervals[index - 1].data === interval.data)) {
                    // Overlapping intervals have the same data, so combine them
                    if (JulianDate.greaterThan(interval.stop, intervals[index - 1].stop)) {
                        interval = new TimeInterval({
                            start : intervals[index - 1].start,
                            stop : interval.stop,
                            isStartIncluded : intervals[index - 1].isStartIncluded,
                            isStopIncluded : interval.isStopIncluded,
                            data : interval.data
                        });
                    } else {
                        interval = new TimeInterval({
                            start : intervals[index - 1].start,
                            stop : intervals[index - 1].stop,
                            isStartIncluded : intervals[index - 1].isStartIncluded,
                            isStopIncluded : intervals[index - 1].isStopIncluded || (interval.stop.equals(intervals[index - 1].stop) && interval.isStopIncluded),
                            data : interval.data
                        });
                    }
                    intervals.splice(index - 1, 1);
                    --index;
                } else {
                    // Overlapping intervals have different data.  The new interval
                    // being added 'wins' so truncate the previous interval.
                    // If the existing interval extends past the end of the new one,
                    // split the existing interval into two intervals.
                    comparison = JulianDate.compare(intervals[index - 1].stop, interval.stop);
                    if (comparison > 0 || (comparison === 0 && intervals[index - 1].isStopIncluded && !interval.isStopIncluded)) {
                        intervals.splice(index - 1, 1, new TimeInterval({
                            start : intervals[index - 1].start,
                            stop : interval.start,
                            isStartIncluded : intervals[index - 1].isStartIncluded,
                            isStopIncluded : !interval.isStartIncluded,
                            data : intervals[index - 1].data
                        }), new TimeInterval({
                            start : interval.stop,
                            stop : intervals[index - 1].stop,
                            isStartIncluded : !interval.isStopIncluded,
                            isStopIncluded : intervals[index - 1].isStopIncluded,
                            data : intervals[index - 1].data
                        }));
                    } else {
                        intervals[index - 1] = new TimeInterval({
                            start : intervals[index - 1].start,
                            stop : interval.start,
                            isStartIncluded : intervals[index - 1].isStartIncluded,
                            isStopIncluded : !interval.isStartIncluded,
                            data : intervals[index - 1].data
                        });
                    }
                }
            }
        }

        while (index < intervals.length) {
            // Not the last thing in the list, so see if the intervals after this one overlap this one.
            comparison = JulianDate.compare(interval.stop, intervals[index].start);
            if (comparison > 0 || (comparison === 0 && (interval.isStopIncluded || intervals[index].isStartIncluded))) {
                // There is an overlap
                if (defined(dataComparer) ? dataComparer(intervals[index].data, interval.data) : intervals[index].data === interval.data) {
                    // Overlapping intervals have the same data, so combine them
                    interval = new TimeInterval({
                        start : interval.start,
                        stop : JulianDate.greaterThan(intervals[index].stop, interval.stop) ? intervals[index].stop : interval.stop,
                        isStartIncluded : interval.isStartIncluded,
                        isStopIncluded : JulianDate.greaterThan(intervals[index].stop, interval.stop) ? intervals[index].isStopIncluded : interval.isStopIncluded,
                        data : interval.data
                    });
                    intervals.splice(index, 1);
                } else {
                    // Overlapping intervals have different data.  The new interval
                    // being added 'wins' so truncate the next interval.
                    intervals[index] = new TimeInterval({
                        start : interval.stop,
                        stop : intervals[index].stop,
                        isStartIncluded : !interval.isStopIncluded,
                        isStopIncluded : intervals[index].isStopIncluded,
                        data : intervals[index].data
                    });
                    if (intervals[index].isEmpty) {
                        intervals.splice(index, 1);
                    } else {
                        // Found a partial span, so it is not possible for the next
                        // interval to be spanned at all.  Stop looking.
                        break;
                    }
                }
            } else {
                // Found the last one we're spanning, so stop looking.
                break;
            }
        }

        // Add the new interval
        intervals.splice(index, 0, interval);
        this._changedEvent.raiseEvent(this);
    };

    /**
     * Removes the specified interval from this interval collection, creating a hole over the specified interval.
     * The data property of the input interval is ignored.
     *
     * @param {TimeInterval} interval The interval to remove.
     * @returns <code>true</code> if the interval was removed, <code>false</code> if no part of the interval was in the collection.
     */
    TimeIntervalCollection.prototype.removeInterval = function(interval) {
                if (!defined(interval)) {
            throw new DeveloperError("interval is required");
        }
        
        if (interval.isEmpty) {
            return false;
        }

        var result = false;
        var intervals = this._intervals;

        var index = binarySearch(intervals, interval, compareIntervalStartTimes);
        if (index < 0) {
            index = ~index;
        }

        var intervalStart = interval.start;
        var intervalStop = interval.stop;
        var intervalIsStartIncluded = interval.isStartIncluded;
        var intervalIsStopIncluded = interval.isStopIncluded;

        // Check for truncation of the end of the previous interval.
        if (index > 0) {
            var indexMinus1 = intervals[index - 1];
            var indexMinus1Stop = indexMinus1.stop;
            if (JulianDate.greaterThan(indexMinus1Stop, intervalStart) ||
                (TimeInterval.equals(indexMinus1Stop, intervalStart) &&
                 indexMinus1.isStopIncluded && intervalIsStartIncluded)) {
                result = true;

                if (JulianDate.greaterThan(indexMinus1Stop, intervalStop) ||
                    (indexMinus1.isStopIncluded && !intervalIsStopIncluded && TimeInterval.equals(indexMinus1Stop, intervalStop))) {
                    // Break the existing interval into two pieces
                    intervals.splice(index, 0, new TimeInterval({
                        start : intervalStop,
                        stop : indexMinus1Stop,
                        isStartIncluded : !intervalIsStopIncluded,
                        isStopIncluded : indexMinus1.isStopIncluded,
                        data : indexMinus1.data
                    }));
                }
                intervals[index - 1] = new TimeInterval({
                    start : indexMinus1.start,
                    stop : intervalStart,
                    isStartIncluded : indexMinus1.isStartIncluded,
                    isStopIncluded : !intervalIsStartIncluded,
                    data : indexMinus1.data
                });
            }
        }

        // Check if the Start of the current interval should remain because interval.start is the same but
        // it is not included.
        var indexInterval = intervals[index];
        if (index < intervals.length &&
            !intervalIsStartIncluded &&
            indexInterval.isStartIncluded &&
            intervalStart.equals(indexInterval.start)) {
            result = true;

            intervals.splice(index, 0, new TimeInterval({
                start : indexInterval.start,
                stop : indexInterval.start,
                isStartIncluded : true,
                isStopIncluded : true,
                data : indexInterval.data
            }));
            ++index;
            indexInterval = intervals[index];
        }

        // Remove any intervals that are completely overlapped by the input interval.
        while (index < intervals.length && JulianDate.greaterThan(intervalStop, indexInterval.stop)) {
            result = true;
            intervals.splice(index, 1);
            indexInterval = intervals[index];
        }

        // Check for the case where the input interval ends on the same date
        // as an existing interval.
        if (index < intervals.length && intervalStop.equals(indexInterval.stop)) {
            result = true;

            if (!intervalIsStopIncluded && indexInterval.isStopIncluded) {
                // Last point of interval should remain because the stop date is included in
                // the existing interval but is not included in the input interval.
                if ((index + 1) < intervals.length && intervals[index + 1].start.equals(intervalStop) && indexInterval.data === intervals[index + 1].data) {
                    // Combine single point with the next interval
                    intervals.splice(index, 1);
                    indexInterval = new TimeInterval({
                        start : indexInterval.start,
                        stop : indexInterval.stop,
                        isStartIncluded : true,
                        isStopIncluded : indexInterval.isStopIncluded,
                        data : indexInterval.data
                    });
                } else {
                    indexInterval = new TimeInterval({
                        start : intervalStop,
                        stop : intervalStop,
                        isStartIncluded : true,
                        isStopIncluded : true,
                        data : indexInterval.data
                    });
                }
                intervals[index] = indexInterval;
            } else {
                // Interval is completely overlapped
                intervals.splice(index, 1);
            }
        }

        // Truncate any partially-overlapped intervals.
        if (index < intervals.length &&
            (JulianDate.greaterThan(intervalStop, indexInterval.start) ||
             (intervalStop.equals(indexInterval.start) &&
              intervalIsStopIncluded &&
              indexInterval.isStartIncluded))) {
            result = true;
            intervals[index] = new TimeInterval({
                start : intervalStop,
                stop : indexInterval.stop,
                isStartIncluded : !intervalIsStopIncluded,
                isStopIncluded : indexInterval.isStopIncluded,
                data : indexInterval.data
            });
        }

        if (result) {
            this._changedEvent.raiseEvent(this);
        }

        return result;
    };

    /**
     * Creates a new instance that is the intersection of this collection and the provided collection.
     *
     * @param {TimeIntervalCollection} other The collection to intersect with.
     * @param {TimeInterval~DataComparer} [dataComparer] A function which compares the data of the two intervals.  If omitted, reference equality is used.
     * @param {TimeInterval~MergeCallback} [mergeCallback] A function which merges the data of the two intervals. If omitted, the data from the left interval will be used.
     * @returns {TimeIntervalCollection} A new TimeIntervalCollection which is the intersection of this collection and the provided collection.
     */
    TimeIntervalCollection.prototype.intersect = function(other, dataComparer, mergeCallback) {
                if (!defined(other)) {
            throw new DeveloperError('other is required.');
        }
        
        var left = 0;
        var right = 0;
        var result = new TimeIntervalCollection();
        var intervals = this._intervals;
        var otherIntervals = other._intervals;

        while (left < intervals.length && right < otherIntervals.length) {
            var leftInterval = intervals[left];
            var rightInterval = otherIntervals[right];
            if (JulianDate.lessThan(leftInterval.stop, rightInterval.start)) {
                ++left;
            } else if (JulianDate.lessThan(rightInterval.stop, leftInterval.start)) {
                ++right;
            } else {
                // The following will return an intersection whose data is 'merged' if the callback is defined
                if (defined(mergeCallback) ||
                   ((defined(dataComparer) && dataComparer(leftInterval.data, rightInterval.data)) ||
                    (!defined(dataComparer) && rightInterval.data === leftInterval.data))) {

                    var intersection = TimeInterval.intersect(leftInterval, rightInterval, new TimeInterval(), mergeCallback);
                    if (!intersection.isEmpty) {
                        // Since we start with an empty collection for 'result', and there are no overlapping intervals in 'this' (as a rule),
                        // the 'intersection' will never overlap with a previous interval in 'result'.  So, no need to do any additional 'merging'.
                        result.addInterval(intersection, dataComparer);
                    }
                }

                if (JulianDate.lessThan(leftInterval.stop, rightInterval.stop) ||
                    (leftInterval.stop.equals(rightInterval.stop) &&
                     !leftInterval.isStopIncluded &&
                     rightInterval.isStopIncluded)) {
                    ++left;
                } else {
                    ++right;
                }
            }
        }
        return result;
    };

    return TimeIntervalCollection;
});

/*global define*/
define('Core/TranslationRotationScale',[
        './Cartesian3',
        './defaultValue',
        './defined',
        './Quaternion'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        Quaternion) {
    'use strict';

    var defaultScale = new Cartesian3(1.0, 1.0, 1.0);
    var defaultTranslation = Cartesian3.ZERO;
    var defaultRotation = Quaternion.IDENTITY;

    /**
     * An affine transformation defined by a translation, rotation, and scale.
     * @alias TranslationRotationScale
     * @constructor
     *
     * @param {Cartesian3} [translation=Cartesian3.ZERO] A {@link Cartesian3} specifying the (x, y, z) translation to apply to the node.
     * @param {Quaternion} [rotation=Quaternion.IDENTITY] A {@link Quaternion} specifying the (x, y, z, w) rotation to apply to the node.
     * @param {Cartesian3} [scale=new Cartesian3(1.0, 1.0, 1.0)] A {@link Cartesian3} specifying the (x, y, z) scaling to apply to the node.
     */
    var TranslationRotationScale = function(translation, rotation, scale) {
        /**
         * Gets or sets the (x, y, z) translation to apply to the node.
         * @type {Cartesian3}
         * @default Cartesian3.ZERO
         */
        this.translation = Cartesian3.clone(defaultValue(translation, defaultTranslation));

        /**
         * Gets or sets the (x, y, z, w) rotation to apply to the node.
         * @type {Quaternion}
         * @default Quaternion.IDENTITY
         */
        this.rotation = Quaternion.clone(defaultValue(rotation, defaultRotation));

        /**
         * Gets or sets the (x, y, z) scaling to apply to the node.
         * @type {Cartesian3}
         * @default new Cartesian3(1.0, 1.0, 1.0)
         */
        this.scale = Cartesian3.clone(defaultValue(scale, defaultScale));
    };

    /**
     * Compares this instance against the provided instance and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {TranslationRotationScale} [right] The right hand side TranslationRotationScale.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    TranslationRotationScale.prototype.equals = function(right) {
        return (this === right) ||
               (defined(right) &&
                Cartesian3.equals(this.translation, right.translation) &&
                Quaternion.equals(this.rotation, right.rotation) &&
                Cartesian3.equals(this.scale, right.scale));
    };

    return TranslationRotationScale;
});

/*global define*/
define('Core/VideoSynchronizer',[
        './defaultValue',
        './defined',
        './defineProperties',
        './destroyObject',
        './Iso8601',
        './JulianDate'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        Iso8601,
        JulianDate) {
    'use strict';

    /**
     * Synchronizes a video element with a simulation clock.
     *
     * @alias VideoSynchronizer
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Clock} [options.clock] The clock instance used to drive the video.
     * @param {HTMLVideoElement} [options.element] The video element to be synchronized.
     * @param {JulianDate} [options.epoch=Iso8601.MINIMUM_VALUE] The simulation time that marks the start of the video.
     * @param {Number} [options.tolerance=1.0] The maximum amount of time, in seconds, that the clock and video can diverge.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Video.html|Video Material Demo}
     */
    function VideoSynchronizer(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._clock = undefined;
        this._element = undefined;
        this._clockSubscription = undefined;
        this._seekFunction = undefined;

        this.clock = options.clock;
        this.element = options.element;

        /**
         * Gets or sets the simulation time that marks the start of the video.
         * @type {JulianDate}
         * @default Iso8601.MINIMUM_VALUE
         */
        this.epoch = defaultValue(options.epoch, Iso8601.MINIMUM_VALUE);

        /**
         * Gets or sets the amount of time in seconds the video's currentTime
         * and the clock's currentTime can diverge before a video seek is performed.
         * Lower values make the synchronization more accurate but video
         * performance might suffer.  Higher values provide better performance
         * but at the cost of accuracy.
         * @type {Number}
         * @default 1.0
         */
        this.tolerance = defaultValue(options.tolerance, 1.0);

        this._seeking = false;
        this._seekFunction = undefined;
        this._firstTickAfterSeek = false;
    }

    defineProperties(VideoSynchronizer.prototype, {
        /**
         * Gets or sets the clock used to drive the video element.
         *
         * @memberof VideoSynchronizer.prototype
         * @type {Clock}
         */
        clock : {
            get : function() {
                return this._clock;
            },
            set : function(value) {
                var oldValue = this._clock;

                if (oldValue === value) {
                    return;
                }

                if (defined(oldValue)) {
                    this._clockSubscription();
                    this._clockSubscription = undefined;
                }

                if (defined(value)) {
                    this._clockSubscription = value.onTick.addEventListener(VideoSynchronizer.prototype._onTick, this);
                }

                this._clock = value;
            }
        },
        /**
         * Gets or sets the video element to synchronize.
         *
         * @memberof VideoSynchronizer.prototype
         * @type {HTMLVideoElement}
         */
        element : {
            get : function() {
                return this._element;
            },
            set : function(value) {
                var oldValue = this._element;

                if (oldValue === value) {
                    return;
                }

                if (defined(oldValue)) {
                    oldValue.removeEventListener("seeked", this._seekFunction, false);
                }

                if (defined(value)) {
                    this._seeking = false;
                    this._seekFunction = createSeekFunction(this);
                    value.addEventListener("seeked", this._seekFunction, false);
                }

                this._element = value;
                this._seeking = false;
                this._firstTickAfterSeek = false;
            }
        }
    });

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    VideoSynchronizer.prototype.destroy = function() {
        this.element = undefined;
        this.clock = undefined;
        return destroyObject(this);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    VideoSynchronizer.prototype.isDestroyed = function() {
        return false;
    };

    VideoSynchronizer.prototype._onTick = function(clock) {
        var element = this._element;
        if (!defined(element) || element.readyState < 2) {
            return;
        }

        var paused = element.paused;
        var shouldAnimate = clock.shouldAnimate;
        if (shouldAnimate === paused) {
            if (shouldAnimate) {
                element.play();
            } else {
                element.pause();
            }
        }

        //We need to avoid constant seeking or the video will
        //never contain a complete frame for us to render.
        //So don't do anything if we're seeing or on the first
        //tick after a seek (the latter of which allows the frame
        //to actually be rendered.
        if (this._seeking || this._firstTickAfterSeek) {
            this._firstTickAfterSeek = false;
            return;
        }

        element.playbackRate = clock.multiplier;

        var clockTime = clock.currentTime;
        var epoch = defaultValue(this.epoch, Iso8601.MINIMUM_VALUE);
        var videoTime = JulianDate.secondsDifference(clockTime, epoch);

        var duration = element.duration;
        var desiredTime;
        var currentTime = element.currentTime;
        if (element.loop) {
            videoTime = videoTime % duration;
            if (videoTime < 0.0) {
                videoTime = duration - videoTime;
            }
            desiredTime = videoTime;
        } else if (videoTime > duration) {
            desiredTime = duration;
        } else if (videoTime < 0.0) {
            desiredTime = 0.0;
        } else {
            desiredTime = videoTime;
        }

        //If the playing video's time and the scene's clock time
        //ever drift too far apart, we want to set the video to match
        var tolerance = shouldAnimate ? defaultValue(this.tolerance, 1.0) : 0.001;
        if (Math.abs(desiredTime - currentTime) > tolerance) {
            this._seeking = true;
            element.currentTime = desiredTime;
        }
    };

    function createSeekFunction(that) {
        return function() {
            that._seeking = false;
            that._firstTickAfterSeek = true;
        };
    }

    return VideoSynchronizer;
});

/*global define*/
define('Core/VRTheWorldTerrainProvider',[
        '../ThirdParty/when',
        './Credit',
        './defaultValue',
        './defined',
        './defineProperties',
        './DeveloperError',
        './Ellipsoid',
        './Event',
        './GeographicTilingScheme',
        './getImagePixels',
        './HeightmapTerrainData',
        './loadImage',
        './loadXML',
        './Math',
        './Rectangle',
        './TerrainProvider',
        './throttleRequestByServer',
        './TileProviderError'
    ], function(
        when,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        Event,
        GeographicTilingScheme,
        getImagePixels,
        HeightmapTerrainData,
        loadImage,
        loadXML,
        CesiumMath,
        Rectangle,
        TerrainProvider,
        throttleRequestByServer,
        TileProviderError) {
    'use strict';

    function DataRectangle(rectangle, maxLevel) {
        this.rectangle = rectangle;
        this.maxLevel = maxLevel;
    }

    /**
     * A {@link TerrainProvider} that produces terrain geometry by tessellating height maps
     * retrieved from a {@link http://vr-theworld.com/|VT MÄK VR-TheWorld server}.
     *
     * @alias VRTheWorldTerrainProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The URL of the VR-TheWorld TileMap.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid.  If this parameter is not
     *                    specified, the WGS84 ellipsoid is used.
     * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
     *
     *
     * @example
     * var terrainProvider = new Cesium.VRTheWorldTerrainProvider({
     *   url : 'https://www.vr-theworld.com/vr-theworld/tiles1.0.0/73/'
     * });
     * viewer.terrainProvider = terrainProvider;
     *
     * @see TerrainProvider
     */
    function VRTheWorldTerrainProvider(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        this._url = options.url;
        if (this._url.length > 0 && this._url[this._url.length - 1] !== '/') {
            this._url += '/';
        }

        this._errorEvent = new Event();
        this._ready = false;
        this._readyPromise = when.defer();

        this._proxy = options.proxy;

        this._terrainDataStructure = {
                heightScale : 1.0 / 1000.0,
                heightOffset : -1000.0,
                elementsPerHeight : 3,
                stride : 4,
                elementMultiplier : 256.0,
                isBigEndian : true,
                lowestEncodedHeight : 0,
                highestEncodedHeight : 256 * 256 * 256 - 1
            };

        var credit = options.credit;
        if (typeof credit === 'string') {
            credit = new Credit(credit);
        }
        this._credit = credit;

        this._tilingScheme = undefined;
        this._rectangles = [];

        var that = this;
        var metadataError;
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);

        function metadataSuccess(xml) {
            var srs = xml.getElementsByTagName('SRS')[0].textContent;
            if (srs === 'EPSG:4326') {
                that._tilingScheme = new GeographicTilingScheme({ ellipsoid : ellipsoid });
            } else {
                metadataFailure('SRS ' + srs + ' is not supported.');
                return;
            }

            var tileFormat = xml.getElementsByTagName('TileFormat')[0];
            that._heightmapWidth = parseInt(tileFormat.getAttribute('width'), 10);
            that._heightmapHeight = parseInt(tileFormat.getAttribute('height'), 10);
            that._levelZeroMaximumGeometricError = TerrainProvider.getEstimatedLevelZeroGeometricErrorForAHeightmap(ellipsoid, Math.min(that._heightmapWidth, that._heightmapHeight), that._tilingScheme.getNumberOfXTilesAtLevel(0));

            var dataRectangles = xml.getElementsByTagName('DataExtent');

            for (var i = 0; i < dataRectangles.length; ++i) {
                var dataRectangle = dataRectangles[i];

                var west = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('minx')));
                var south = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('miny')));
                var east = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('maxx')));
                var north = CesiumMath.toRadians(parseFloat(dataRectangle.getAttribute('maxy')));
                var maxLevel = parseInt(dataRectangle.getAttribute('maxlevel'), 10);

                that._rectangles.push(new DataRectangle(new Rectangle(west, south, east, north), maxLevel));
            }

            that._ready = true;
            that._readyPromise.resolve(true);
        }

        function metadataFailure(e) {
            var message = defaultValue(e, 'An error occurred while accessing ' + that._url + '.');
            metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
        }

        function requestMetadata() {
            when(loadXML(that._url), metadataSuccess, metadataFailure);
        }

        requestMetadata();
    }

    defineProperties(VRTheWorldTerrainProvider.prototype, {
        /**
         * Gets an event that is raised when the terrain provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets the credit to display when this terrain provider is active.  Typically this is used to credit
         * the source of the terrain.  This function should not be called before {@link VRTheWorldTerrainProvider#ready} returns true.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {Credit}
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link VRTheWorldTerrainProvider#ready} returns true.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {GeographicTilingScheme}
         */
        tilingScheme : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
                }
                
                return this._tilingScheme;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Gets a value indicating whether or not the provider includes a water mask.  The water mask
         * indicates which areas of the globe are water rather than land, so they can be rendered
         * as a reflective surface with animated waves.  This function should not be
         * called before {@link VRTheWorldTerrainProvider#ready} returns true.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {Boolean}
         */
        hasWaterMask : {
            get : function() {
                return false;
            }
        },

        /**
         * Gets a value indicating whether or not the requested tiles include vertex normals.
         * This function should not be called before {@link VRTheWorldTerrainProvider#ready} returns true.
         * @memberof VRTheWorldTerrainProvider.prototype
         * @type {Boolean}
         */
        hasVertexNormals : {
            get : function() {
                return false;
            }
        }
    });

    /**
     * Requests the geometry for a given tile.  This function should not be called before
     * {@link VRTheWorldTerrainProvider#ready} returns true.  The result includes terrain
     * data and indicates that all child tiles are available.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @param {Boolean} [throttleRequests=true] True if the number of simultaneous requests should be limited,
     *                  or false if the request should be initiated regardless of the number of requests
     *                  already in progress.
     * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry.  If this method
     *          returns undefined instead of a promise, it is an indication that too many requests are already
     *          pending and the request will be retried later.
     */
    VRTheWorldTerrainProvider.prototype.requestTileGeometry = function(x, y, level, throttleRequests) {
                if (!this.ready) {
            throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
        }
        
        var yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);
        var url = this._url + level + '/' + x + '/' + (yTiles - y - 1) + '.tif?cesium=true';

        var proxy = this._proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        var promise;

        throttleRequests = defaultValue(throttleRequests, true);
        if (throttleRequests) {
            promise = throttleRequestByServer(url, loadImage);
            if (!defined(promise)) {
                return undefined;
            }
        } else {
            promise = loadImage(url);
        }

        var that = this;
        return when(promise, function(image) {
            return new HeightmapTerrainData({
                buffer : getImagePixels(image),
                width : that._heightmapWidth,
                height : that._heightmapHeight,
                childTileMask : getChildMask(that, x, y, level),
                structure : that._terrainDataStructure
            });
        });
    };

    /**
     * Gets the maximum geometric error allowed in a tile at a given level.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error.
     */
    VRTheWorldTerrainProvider.prototype.getLevelMaximumGeometricError = function(level) {
                if (!this.ready) {
            throw new DeveloperError('requestTileGeometry must not be called before ready returns true.');
        }
                return this._levelZeroMaximumGeometricError / (1 << level);
    };

    var rectangleScratch = new Rectangle();

    function getChildMask(provider, x, y, level) {
        var tilingScheme = provider._tilingScheme;
        var rectangles = provider._rectangles;
        var parentRectangle = tilingScheme.tileXYToRectangle(x, y, level);

        var childMask = 0;

        for (var i = 0; i < rectangles.length && childMask !== 15; ++i) {
            var rectangle = rectangles[i];
            if (rectangle.maxLevel <= level) {
                continue;
            }

            var testRectangle = rectangle.rectangle;

            var intersection = Rectangle.intersection(testRectangle, parentRectangle, rectangleScratch);
            if (defined(intersection)) {
                // Parent tile is inside this rectangle, so at least one child is, too.
                if (isTileInRectangle(tilingScheme, testRectangle, x * 2, y * 2, level + 1)) {
                    childMask |= 4; // northwest
                }
                if (isTileInRectangle(tilingScheme, testRectangle, x * 2 + 1, y * 2, level + 1)) {
                    childMask |= 8; // northeast
                }
                if (isTileInRectangle(tilingScheme, testRectangle, x * 2, y * 2 + 1, level + 1)) {
                    childMask |= 1; // southwest
                }
                if (isTileInRectangle(tilingScheme, testRectangle, x * 2 + 1, y * 2 + 1, level + 1)) {
                    childMask |= 2; // southeast
                }
            }
        }

        return childMask;
    }

    function isTileInRectangle(tilingScheme, rectangle, x, y, level) {
        var tileRectangle = tilingScheme.tileXYToRectangle(x, y, level);
        return defined(Rectangle.intersection(tileRectangle, rectangle, rectangleScratch));
    }

    /**
     * Determines whether data for a tile is available to be loaded.
     *
     * @param {Number} x The X coordinate of the tile for which to request geometry.
     * @param {Number} y The Y coordinate of the tile for which to request geometry.
     * @param {Number} level The level of the tile for which to request geometry.
     * @returns {Boolean} Undefined if not supported, otherwise true or false.
     */
    VRTheWorldTerrainProvider.prototype.getTileDataAvailable = function(x, y, level) {
        return undefined;
    };

    return VRTheWorldTerrainProvider;
});

/*global define*/
define('Core/WallGeometryLibrary',[
        './Cartographic',
        './defined',
        './EllipsoidTangentPlane',
        './Math',
        './PolygonPipeline',
        './PolylinePipeline',
        './WindingOrder'
    ], function(
        Cartographic,
        defined,
        EllipsoidTangentPlane,
        CesiumMath,
        PolygonPipeline,
        PolylinePipeline,
        WindingOrder) {
    'use strict';

    /**
     * private
     */
    var WallGeometryLibrary = {};

    function latLonEquals(c0, c1) {
        return ((CesiumMath.equalsEpsilon(c0.latitude, c1.latitude, CesiumMath.EPSILON14)) && (CesiumMath.equalsEpsilon(c0.longitude, c1.longitude, CesiumMath.EPSILON14)));
    }

    var scratchCartographic1 = new Cartographic();
    var scratchCartographic2 = new Cartographic();
    function removeDuplicates(ellipsoid, positions, topHeights, bottomHeights) {
        var length = positions.length;
        if (length < 2) {
            return;
        }

        var hasBottomHeights = defined(bottomHeights);
        var hasTopHeights = defined(topHeights);
        var hasAllZeroHeights = true;

        var cleanedPositions = new Array(length);
        var cleanedTopHeights = new Array(length);
        var cleanedBottomHeights = new Array(length);

        var v0 = positions[0];
        cleanedPositions[0] = v0;

        var c0 = ellipsoid.cartesianToCartographic(v0, scratchCartographic1);
        if (hasTopHeights) {
            c0.height = topHeights[0];
        }

        hasAllZeroHeights = hasAllZeroHeights && c0.height <= 0;

        cleanedTopHeights[0] = c0.height;

        if (hasBottomHeights) {
            cleanedBottomHeights[0] = bottomHeights[0];
        } else {
            cleanedBottomHeights[0] = 0.0;
        }

        var index = 1;
        for (var i = 1; i < length; ++i) {
            var v1 = positions[i];
            var c1 = ellipsoid.cartesianToCartographic(v1, scratchCartographic2);
            if (hasTopHeights) {
                c1.height = topHeights[i];
            }
            hasAllZeroHeights = hasAllZeroHeights && c1.height <= 0;

            if (!latLonEquals(c0, c1)) {
                cleanedPositions[index] = v1; // Shallow copy!
                cleanedTopHeights[index] = c1.height;

                if (hasBottomHeights) {
                    cleanedBottomHeights[index] = bottomHeights[i];
                } else {
                    cleanedBottomHeights[index] = 0.0;
                }

                Cartographic.clone(c1, c0);
                ++index;
            } else if (c0.height < c1.height) {
                cleanedTopHeights[index - 1] = c1.height;
            }
        }

        if (hasAllZeroHeights || index < 2) {
            return;
        }

        cleanedPositions.length = index;
        cleanedTopHeights.length = index;
        cleanedBottomHeights.length = index;

        return {
            positions: cleanedPositions,
            topHeights: cleanedTopHeights,
            bottomHeights: cleanedBottomHeights
        };
    }

    var positionsArrayScratch = new Array(2);
    var heightsArrayScratch = new Array(2);
    var generateArcOptionsScratch = {
        positions : undefined,
        height : undefined,
        granularity : undefined,
        ellipsoid : undefined
    };

    /**
     * @private
     */
    WallGeometryLibrary.computePositions = function(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, duplicateCorners) {
        var o = removeDuplicates(ellipsoid, wallPositions, maximumHeights, minimumHeights);

        if (!defined(o)) {
            return;
        }

        wallPositions = o.positions;
        maximumHeights = o.topHeights;
        minimumHeights = o.bottomHeights;

        if (wallPositions.length >= 3) {
            // Order positions counter-clockwise
            var tangentPlane = EllipsoidTangentPlane.fromPoints(wallPositions, ellipsoid);
            var positions2D = tangentPlane.projectPointsOntoPlane(wallPositions);

            if (PolygonPipeline.computeWindingOrder2D(positions2D) === WindingOrder.CLOCKWISE) {
                wallPositions.reverse();
                maximumHeights.reverse();
                minimumHeights.reverse();
            }
        }

        var length = wallPositions.length;
        var numCorners = length - 2;
        var topPositions;
        var bottomPositions;

        var minDistance = CesiumMath.chordLength(granularity, ellipsoid.maximumRadius);

        var generateArcOptions = generateArcOptionsScratch;
        generateArcOptions.minDistance = minDistance;
        generateArcOptions.ellipsoid = ellipsoid;

        if (duplicateCorners) {
            var count = 0;
            var i;

            for (i = 0; i < length - 1; i++) {
                count += PolylinePipeline.numberOfPoints(wallPositions[i], wallPositions[i+1], minDistance) + 1;
            }

            topPositions = new Float64Array(count * 3);
            bottomPositions = new Float64Array(count * 3);

            var generateArcPositions = positionsArrayScratch;
            var generateArcHeights = heightsArrayScratch;
            generateArcOptions.positions = generateArcPositions;
            generateArcOptions.height = generateArcHeights;

            var offset = 0;
            for (i = 0; i < length - 1; i++) {
                generateArcPositions[0] = wallPositions[i];
                generateArcPositions[1] = wallPositions[i + 1];

                generateArcHeights[0] = maximumHeights[i];
                generateArcHeights[1] = maximumHeights[i + 1];

                var pos = PolylinePipeline.generateArc(generateArcOptions);
                topPositions.set(pos, offset);

                generateArcHeights[0] = minimumHeights[i];
                generateArcHeights[1] = minimumHeights[i + 1];

                bottomPositions.set(PolylinePipeline.generateArc(generateArcOptions), offset);

                offset += pos.length;
            }
        } else {
            generateArcOptions.positions = wallPositions;
            generateArcOptions.height = maximumHeights;
            topPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));

            generateArcOptions.height = minimumHeights;
            bottomPositions = new Float64Array(PolylinePipeline.generateArc(generateArcOptions));
        }

        return {
            bottomPositions: bottomPositions,
            topPositions: topPositions,
            numCorners: numCorners
        };
    };

    return WallGeometryLibrary;
});

/*global define*/
define('Core/WallGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PrimitiveType',
        './VertexFormat',
        './WallGeometryLibrary'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PrimitiveType,
        VertexFormat,
        WallGeometryLibrary) {
    'use strict';

    var scratchCartesian3Position1 = new Cartesian3();
    var scratchCartesian3Position2 = new Cartesian3();
    var scratchCartesian3Position3 = new Cartesian3();
    var scratchCartesian3Position4 = new Cartesian3();
    var scratchCartesian3Position5 = new Cartesian3();
    var scratchBitangent = new Cartesian3();
    var scratchTangent = new Cartesian3();
    var scratchNormal = new Cartesian3();

    /**
     * A description of a wall, which is similar to a KML line string. A wall is defined by a series of points,
     * which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
     *
     * @alias WallGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Number[]} [options.maximumHeights] An array parallel to <code>positions</code> that give the maximum height of the
     *        wall at <code>positions</code>. If undefined, the height of each position in used.
     * @param {Number[]} [options.minimumHeights] An array parallel to <code>positions</code> that give the minimum height of the
     *        wall at <code>positions</code>. If undefined, the height at each position is 0.0.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     *
     * @exception {DeveloperError} positions length must be greater than or equal to 2.
     * @exception {DeveloperError} positions and maximumHeights must have the same length.
     * @exception {DeveloperError} positions and minimumHeights must have the same length.
     *
     * @see WallGeometry#createGeometry
     * @see WallGeometry#fromConstantHeight
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo}
     *
     * @example
     * // create a wall that spans from ground level to 10000 meters
     * var wall = new Cesium.WallGeometry({
     *   positions : Cesium.Cartesian3.fromDegreesArrayHeights([
     *     19.0, 47.0, 10000.0,
     *     19.0, 48.0, 10000.0,
     *     20.0, 48.0, 10000.0,
     *     20.0, 47.0, 10000.0,
     *     19.0, 47.0, 10000.0
     *   ])
     * });
     * var geometry = Cesium.WallGeometry.createGeometry(wall);
     */
    function WallGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var wallPositions = options.positions;
        var maximumHeights = options.maximumHeights;
        var minimumHeights = options.minimumHeights;

                if (!defined(wallPositions)) {
            throw new DeveloperError('options.positions is required.');
        }
        if (defined(maximumHeights) && maximumHeights.length !== wallPositions.length) {
            throw new DeveloperError('options.positions and options.maximumHeights must have the same length.');
        }
        if (defined(minimumHeights) && minimumHeights.length !== wallPositions.length) {
            throw new DeveloperError('options.positions and options.minimumHeights must have the same length.');
        }
        
        var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);

        this._positions = wallPositions;
        this._minimumHeights = minimumHeights;
        this._maximumHeights = maximumHeights;
        this._vertexFormat = VertexFormat.clone(vertexFormat);
        this._granularity = granularity;
        this._ellipsoid = Ellipsoid.clone(ellipsoid);
        this._workerName = 'createWallGeometry';

        var numComponents = 1 + wallPositions.length * Cartesian3.packedLength + 2;
        if (defined(minimumHeights)) {
            numComponents += minimumHeights.length;
        }
        if (defined(maximumHeights)) {
            numComponents += maximumHeights.length;
        }

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 1;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {WallGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    WallGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        var minimumHeights = value._minimumHeights;
        length = defined(minimumHeights) ? minimumHeights.length : 0;
        array[startingIndex++] = length;

        if (defined(minimumHeights)) {
            for (i = 0; i < length; ++i) {
                array[startingIndex++] = minimumHeights[i];
            }
        }

        var maximumHeights = value._maximumHeights;
        length = defined(maximumHeights) ? maximumHeights.length : 0;
        array[startingIndex++] = length;

        if (defined(maximumHeights)) {
            for (i = 0; i < length; ++i) {
                array[startingIndex++] = maximumHeights[i];
            }
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        VertexFormat.pack(value._vertexFormat, array, startingIndex);
        startingIndex += VertexFormat.packedLength;

        array[startingIndex] = value._granularity;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchVertexFormat = new VertexFormat();
    var scratchOptions = {
        positions : undefined,
        minimumHeights : undefined,
        maximumHeights : undefined,
        ellipsoid : scratchEllipsoid,
        vertexFormat : scratchVertexFormat,
        granularity : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {WallGeometry} [result] The object into which to store the result.
     * @returns {WallGeometry} The modified result parameter or a new WallGeometry instance if one was not provided.
     */
    WallGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        length = array[startingIndex++];
        var minimumHeights;

        if (length > 0) {
            minimumHeights = new Array(length);
            for (i = 0; i < length; ++i) {
                minimumHeights[i] = array[startingIndex++];
            }
        }

        length = array[startingIndex++];
        var maximumHeights;

        if (length > 0) {
            maximumHeights = new Array(length);
            for (i = 0; i < length; ++i) {
                maximumHeights[i] = array[startingIndex++];
            }
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
        startingIndex += VertexFormat.packedLength;

        var granularity = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.positions = positions;
            scratchOptions.minimumHeights = minimumHeights;
            scratchOptions.maximumHeights = maximumHeights;
            scratchOptions.granularity = granularity;
            return new WallGeometry(scratchOptions);
        }

        result._positions = positions;
        result._minimumHeights = minimumHeights;
        result._maximumHeights = maximumHeights;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
        result._granularity = granularity;

        return result;
    };

    /**
     * A description of a wall, which is similar to a KML line string. A wall is defined by a series of points,
     * which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
     * @param {Number} [options.maximumHeight] A constant that defines the maximum height of the
     *        wall at <code>positions</code>. If undefined, the height of each position in used.
     * @param {Number} [options.minimumHeight] A constant that defines the minimum height of the
     *        wall at <code>positions</code>. If undefined, the height at each position is 0.0.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
     * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
     * @returns {WallGeometry}
     *
     *
     * @example
     * // create a wall that spans from 10000 meters to 20000 meters
     * var wall = Cesium.WallGeometry.fromConstantHeights({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     19.0, 47.0,
     *     19.0, 48.0,
     *     20.0, 48.0,
     *     20.0, 47.0,
     *     19.0, 47.0,
     *   ]),
     *   minimumHeight : 20000.0,
     *   maximumHeight : 10000.0
     * });
     * var geometry = Cesium.WallGeometry.createGeometry(wall);
     *
     * @see WallGeometry#createGeometry
     */
    WallGeometry.fromConstantHeights = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.positions;

                if (!defined(positions)) {
            throw new DeveloperError('options.positions is required.');
        }
        
        var minHeights;
        var maxHeights;

        var min = options.minimumHeight;
        var max = options.maximumHeight;

        var doMin = defined(min);
        var doMax = defined(max);
        if (doMin || doMax) {
            var length = positions.length;
            minHeights = (doMin) ? new Array(length) : undefined;
            maxHeights = (doMax) ? new Array(length) : undefined;

            for (var i = 0; i < length; ++i) {
                if (doMin) {
                    minHeights[i] = min;
                }

                if (doMax) {
                    maxHeights[i] = max;
                }
            }
        }

        var newOptions = {
            positions : positions,
            maximumHeights : maxHeights,
            minimumHeights : minHeights,
            ellipsoid : options.ellipsoid,
            vertexFormat : options.vertexFormat
        };
        return new WallGeometry(newOptions);
    };

    /**
     * Computes the geometric representation of a wall, including its vertices, indices, and a bounding sphere.
     *
     * @param {WallGeometry} wallGeometry A description of the wall.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    WallGeometry.createGeometry = function(wallGeometry) {
        var wallPositions = wallGeometry._positions;
        var minimumHeights = wallGeometry._minimumHeights;
        var maximumHeights = wallGeometry._maximumHeights;
        var vertexFormat = wallGeometry._vertexFormat;
        var granularity = wallGeometry._granularity;
        var ellipsoid = wallGeometry._ellipsoid;

        var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, true);
        if (!defined(pos)) {
            return;
        }

        var bottomPositions = pos.bottomPositions;
        var topPositions = pos.topPositions;
        var numCorners = pos.numCorners;

        var length = topPositions.length;
        var size = length * 2;

        var positions = vertexFormat.position ? new Float64Array(size) : undefined;
        var normals = vertexFormat.normal ? new Float32Array(size) : undefined;
        var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined;
        var bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined;
        var textureCoordinates = vertexFormat.st ? new Float32Array(size / 3 * 2) : undefined;

        var positionIndex = 0;
        var normalIndex = 0;
        var bitangentIndex = 0;
        var tangentIndex = 0;
        var stIndex = 0;

        // add lower and upper points one after the other, lower
        // points being even and upper points being odd
        var normal = scratchNormal;
        var tangent = scratchTangent;
        var bitangent = scratchBitangent;
        var recomputeNormal = true;
        length /= 3;
        var i;
        var s = 0;
        var ds = 1/(length - wallPositions.length + 1);
        for (i = 0; i < length; ++i) {
            var i3 = i * 3;
            var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1);
            var bottomPosition = Cartesian3.fromArray(bottomPositions, i3, scratchCartesian3Position2);
            if (vertexFormat.position) {
                // insert the lower point
                positions[positionIndex++] = bottomPosition.x;
                positions[positionIndex++] = bottomPosition.y;
                positions[positionIndex++] = bottomPosition.z;

                // insert the upper point
                positions[positionIndex++] = topPosition.x;
                positions[positionIndex++] = topPosition.y;
                positions[positionIndex++] = topPosition.z;
            }

            if (vertexFormat.st) {
                textureCoordinates[stIndex++] = s;
                textureCoordinates[stIndex++] = 0.0;

                textureCoordinates[stIndex++] = s;
                textureCoordinates[stIndex++] = 1.0;
            }

            if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
                var nextPosition;
                var nextTop = Cartesian3.clone(Cartesian3.ZERO, scratchCartesian3Position5);
                var groundPosition = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position2), scratchCartesian3Position2);
                if (i + 1 < length) {
                    nextPosition = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(topPositions, i3 + 3, scratchCartesian3Position3), scratchCartesian3Position3);
                    nextTop = Cartesian3.fromArray(topPositions, i3 + 3, scratchCartesian3Position5);
                }

                if (recomputeNormal) {
                    var scalednextPosition = Cartesian3.subtract(nextTop, topPosition, scratchCartesian3Position4);
                    var scaledGroundPosition = Cartesian3.subtract(groundPosition, topPosition, scratchCartesian3Position1);
                    normal = Cartesian3.normalize(Cartesian3.cross(scaledGroundPosition, scalednextPosition, normal), normal);
                    recomputeNormal = false;
                }

                if (Cartesian3.equalsEpsilon(nextPosition, groundPosition, CesiumMath.EPSILON10)) {
                    recomputeNormal = true;
                } else {
                    s += ds;
                    if (vertexFormat.tangent) {
                        tangent = Cartesian3.normalize(Cartesian3.subtract(nextPosition, groundPosition, tangent), tangent);
                    }
                    if (vertexFormat.bitangent) {
                        bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent);
                    }
                }

                if (vertexFormat.normal) {
                    normals[normalIndex++] = normal.x;
                    normals[normalIndex++] = normal.y;
                    normals[normalIndex++] = normal.z;

                    normals[normalIndex++] = normal.x;
                    normals[normalIndex++] = normal.y;
                    normals[normalIndex++] = normal.z;
                }

                if (vertexFormat.tangent) {
                    tangents[tangentIndex++] = tangent.x;
                    tangents[tangentIndex++] = tangent.y;
                    tangents[tangentIndex++] = tangent.z;

                    tangents[tangentIndex++] = tangent.x;
                    tangents[tangentIndex++] = tangent.y;
                    tangents[tangentIndex++] = tangent.z;
                }

                if (vertexFormat.bitangent) {
                    bitangents[bitangentIndex++] = bitangent.x;
                    bitangents[bitangentIndex++] = bitangent.y;
                    bitangents[bitangentIndex++] = bitangent.z;

                    bitangents[bitangentIndex++] = bitangent.x;
                    bitangents[bitangentIndex++] = bitangent.y;
                    bitangents[bitangentIndex++] = bitangent.z;
                }
            }
        }

        var attributes = new GeometryAttributes();

        if (vertexFormat.position) {
            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : positions
            });
        }

        if (vertexFormat.normal) {
            attributes.normal = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : normals
            });
        }

        if (vertexFormat.tangent) {
            attributes.tangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : tangents
            });
        }

        if (vertexFormat.bitangent) {
            attributes.bitangent = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3,
                values : bitangents
            });
        }

        if (vertexFormat.st) {
            attributes.st = new GeometryAttribute({
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 2,
                values : textureCoordinates
            });
        }

        // prepare the side walls, two triangles for each wall
        //
        //    A (i+1)  B (i+3) E
        //    +--------+-------+
        //    |      / |      /|    triangles:  A C B
        //    |     /  |     / |                B C D
        //    |    /   |    /  |
        //    |   /    |   /   |
        //    |  /     |  /    |
        //    | /      | /     |
        //    +--------+-------+
        //    C (i)    D (i+2) F
        //

        var numVertices = size / 3;
        size -= 6 * (numCorners + 1);
        var indices = IndexDatatype.createTypedArray(numVertices, size);

        var edgeIndex = 0;
        for (i = 0; i < numVertices - 2; i += 2) {
            var LL = i;
            var LR = i + 2;
            var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1);
            var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2);
            if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON10)) {
                continue;
            }
            var UL = i + 1;
            var UR = i + 3;

            indices[edgeIndex++] = UL;
            indices[edgeIndex++] = LL;
            indices[edgeIndex++] = UR;
            indices[edgeIndex++] = UR;
            indices[edgeIndex++] = LL;
            indices[edgeIndex++] = LR;
        }

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.TRIANGLES,
            boundingSphere : new BoundingSphere.fromVertices(positions)
        });
    };

    return WallGeometry;
});

/*global define*/
define('Core/WallOutlineGeometry',[
        './BoundingSphere',
        './Cartesian3',
        './ComponentDatatype',
        './defaultValue',
        './defined',
        './DeveloperError',
        './Ellipsoid',
        './Geometry',
        './GeometryAttribute',
        './GeometryAttributes',
        './IndexDatatype',
        './Math',
        './PrimitiveType',
        './WallGeometryLibrary'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        IndexDatatype,
        CesiumMath,
        PrimitiveType,
        WallGeometryLibrary) {
    'use strict';

    var scratchCartesian3Position1 = new Cartesian3();
    var scratchCartesian3Position2 = new Cartesian3();

    /**
     * A description of a wall outline. A wall is defined by a series of points,
     * which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
     *
     * @alias WallOutlineGeometry
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
     * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
     * @param {Number[]} [options.maximumHeights] An array parallel to <code>positions</code> that give the maximum height of the
     *        wall at <code>positions</code>. If undefined, the height of each position in used.
     * @param {Number[]} [options.minimumHeights] An array parallel to <code>positions</code> that give the minimum height of the
     *        wall at <code>positions</code>. If undefined, the height at each position is 0.0.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
     *
     * @exception {DeveloperError} positions length must be greater than or equal to 2.
     * @exception {DeveloperError} positions and maximumHeights must have the same length.
     * @exception {DeveloperError} positions and minimumHeights must have the same length.
     *
     * @see WallGeometry#createGeometry
     * @see WallGeometry#fromConstantHeight
     *
     * @example
     * // create a wall outline that spans from ground level to 10000 meters
     * var wall = new Cesium.WallOutlineGeometry({
     *   positions : Cesium.Cartesian3.fromDegreesArrayHeights([
     *     19.0, 47.0, 10000.0,
     *     19.0, 48.0, 10000.0,
     *     20.0, 48.0, 10000.0,
     *     20.0, 47.0, 10000.0,
     *     19.0, 47.0, 10000.0
     *   ])
     * });
     * var geometry = Cesium.WallOutlineGeometry.createGeometry(wall);
     */
    function WallOutlineGeometry(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var wallPositions = options.positions;
        var maximumHeights = options.maximumHeights;
        var minimumHeights = options.minimumHeights;

                if (!defined(wallPositions)) {
            throw new DeveloperError('options.positions is required.');
        }
        if (defined(maximumHeights) && maximumHeights.length !== wallPositions.length) {
            throw new DeveloperError('options.positions and options.maximumHeights must have the same length.');
        }
        if (defined(minimumHeights) && minimumHeights.length !== wallPositions.length) {
            throw new DeveloperError('options.positions and options.minimumHeights must have the same length.');
        }
        
        var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);

        this._positions = wallPositions;
        this._minimumHeights = minimumHeights;
        this._maximumHeights = maximumHeights;
        this._granularity = granularity;
        this._ellipsoid = Ellipsoid.clone(ellipsoid);
        this._workerName = 'createWallOutlineGeometry';

        var numComponents = 1 + wallPositions.length * Cartesian3.packedLength + 2;
        if (defined(minimumHeights)) {
            numComponents += minimumHeights.length;
        }
        if (defined(maximumHeights)) {
            numComponents += maximumHeights.length;
        }

        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        this.packedLength = numComponents + Ellipsoid.packedLength + 1;
    }

    /**
     * Stores the provided instance into the provided array.
     *
     * @param {WallOutlineGeometry} value The value to pack.
     * @param {Number[]} array The array to pack into.
     * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
     *
     * @returns {Number[]} The array that was packed into
     */
    WallOutlineGeometry.pack = function(value, array, startingIndex) {
                if (!defined(value)) {
            throw new DeveloperError('value is required');
        }
        if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var positions = value._positions;
        var length = positions.length;
        array[startingIndex++] = length;

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            Cartesian3.pack(positions[i], array, startingIndex);
        }

        var minimumHeights = value._minimumHeights;
        length = defined(minimumHeights) ? minimumHeights.length : 0;
        array[startingIndex++] = length;

        if (defined(minimumHeights)) {
            for (i = 0; i < length; ++i) {
                array[startingIndex++] = minimumHeights[i];
            }
        }

        var maximumHeights = value._maximumHeights;
        length = defined(maximumHeights) ? maximumHeights.length : 0;
        array[startingIndex++] = length;

        if (defined(maximumHeights)) {
            for (i = 0; i < length; ++i) {
                array[startingIndex++] = maximumHeights[i];
            }
        }

        Ellipsoid.pack(value._ellipsoid, array, startingIndex);
        startingIndex += Ellipsoid.packedLength;

        array[startingIndex]   = value._granularity;

        return array;
    };

    var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
    var scratchOptions = {
        positions : undefined,
        minimumHeights : undefined,
        maximumHeights : undefined,
        ellipsoid : scratchEllipsoid,
        granularity : undefined
    };

    /**
     * Retrieves an instance from a packed array.
     *
     * @param {Number[]} array The packed array.
     * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
     * @param {WallOutlineGeometry} [result] The object into which to store the result.
     * @returns {WallOutlineGeometry} The modified result parameter or a new WallOutlineGeometry instance if one was not provided.
     */
    WallOutlineGeometry.unpack = function(array, startingIndex, result) {
                if (!defined(array)) {
            throw new DeveloperError('array is required');
        }
        
        startingIndex = defaultValue(startingIndex, 0);

        var i;

        var length = array[startingIndex++];
        var positions = new Array(length);

        for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
            positions[i] = Cartesian3.unpack(array, startingIndex);
        }

        length = array[startingIndex++];
        var minimumHeights;

        if (length > 0) {
            minimumHeights = new Array(length);
            for (i = 0; i < length; ++i) {
                minimumHeights[i] = array[startingIndex++];
            }
        }

        length = array[startingIndex++];
        var maximumHeights;

        if (length > 0) {
            maximumHeights = new Array(length);
            for (i = 0; i < length; ++i) {
                maximumHeights[i] = array[startingIndex++];
            }
        }

        var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
        startingIndex += Ellipsoid.packedLength;

        var granularity = array[startingIndex];

        if (!defined(result)) {
            scratchOptions.positions = positions;
            scratchOptions.minimumHeights = minimumHeights;
            scratchOptions.maximumHeights = maximumHeights;
            scratchOptions.granularity = granularity;
            return new WallOutlineGeometry(scratchOptions);
        }

        result._positions = positions;
        result._minimumHeights = minimumHeights;
        result._maximumHeights = maximumHeights;
        result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
        result._granularity = granularity;

        return result;
    };

    /**
     * A description of a walloutline. A wall is defined by a series of points,
     * which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
     * @param {Number} [options.maximumHeight] A constant that defines the maximum height of the
     *        wall at <code>positions</code>. If undefined, the height of each position in used.
     * @param {Number} [options.minimumHeight] A constant that defines the minimum height of the
     *        wall at <code>positions</code>. If undefined, the height at each position is 0.0.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
     * @returns {WallOutlineGeometry}
     *
     *
     * @example
     * // create a wall that spans from 10000 meters to 20000 meters
     * var wall = Cesium.WallOutlineGeometry.fromConstantHeights({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     19.0, 47.0,
     *     19.0, 48.0,
     *     20.0, 48.0,
     *     20.0, 47.0,
     *     19.0, 47.0,
     *   ]),
     *   minimumHeight : 20000.0,
     *   maximumHeight : 10000.0
     * });
     * var geometry = Cesium.WallOutlineGeometry.createGeometry(wall);
     *
     * @see WallOutlineGeometry#createGeometry
     */
    WallOutlineGeometry.fromConstantHeights = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var positions = options.positions;

                if (!defined(positions)) {
            throw new DeveloperError('options.positions is required.');
        }
        
        var minHeights;
        var maxHeights;

        var min = options.minimumHeight;
        var max = options.maximumHeight;

        var doMin = defined(min);
        var doMax = defined(max);
        if (doMin || doMax) {
            var length = positions.length;
            minHeights = (doMin) ? new Array(length) : undefined;
            maxHeights = (doMax) ? new Array(length) : undefined;

            for (var i = 0; i < length; ++i) {
                if (doMin) {
                    minHeights[i] = min;
                }

                if (doMax) {
                    maxHeights[i] = max;
                }
            }
        }

        var newOptions = {
            positions : positions,
            maximumHeights : maxHeights,
            minimumHeights : minHeights,
            ellipsoid : options.ellipsoid
        };
        return new WallOutlineGeometry(newOptions);
    };

    /**
     * Computes the geometric representation of a wall outline, including its vertices, indices, and a bounding sphere.
     *
     * @param {WallOutlineGeometry} wallGeometry A description of the wall outline.
     * @returns {Geometry|undefined} The computed vertices and indices.
     */
    WallOutlineGeometry.createGeometry = function(wallGeometry) {
        var wallPositions = wallGeometry._positions;
        var minimumHeights = wallGeometry._minimumHeights;
        var maximumHeights = wallGeometry._maximumHeights;
        var granularity = wallGeometry._granularity;
        var ellipsoid = wallGeometry._ellipsoid;

        var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, false);
        if (!defined(pos)) {
            return;
        }

        var bottomPositions = pos.bottomPositions;
        var topPositions = pos.topPositions;

        var length = topPositions.length;
        var size = length * 2;

        var positions = new Float64Array(size);
        var positionIndex = 0;

        // add lower and upper points one after the other, lower
        // points being even and upper points being odd
        length /= 3;
        var i;
        for (i = 0; i < length; ++i) {
            var i3 = i * 3;
            var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1);
            var bottomPosition = Cartesian3.fromArray(bottomPositions, i3, scratchCartesian3Position2);

            // insert the lower point
            positions[positionIndex++] = bottomPosition.x;
            positions[positionIndex++] = bottomPosition.y;
            positions[positionIndex++] = bottomPosition.z;

            // insert the upper point
            positions[positionIndex++] = topPosition.x;
            positions[positionIndex++] = topPosition.y;
            positions[positionIndex++] = topPosition.z;
        }

        var attributes = new GeometryAttributes({
            position : new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : positions
            })
        });

        var numVertices = size / 3;
        size = 2 * numVertices - 4 + numVertices;
        var indices = IndexDatatype.createTypedArray(numVertices, size);

        var edgeIndex = 0;
        for (i = 0; i < numVertices - 2; i += 2) {
            var LL = i;
            var LR = i + 2;
            var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1);
            var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2);
            if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON10)) {
                continue;
            }
            var UL = i + 1;
            var UR = i + 3;

            indices[edgeIndex++] = UL;
            indices[edgeIndex++] = LL;
            indices[edgeIndex++] = UL;
            indices[edgeIndex++] = UR;
            indices[edgeIndex++] = LL;
            indices[edgeIndex++] = LR;
        }

        indices[edgeIndex++] = numVertices - 2;
        indices[edgeIndex++] = numVertices - 1;

        return new Geometry({
            attributes : attributes,
            indices : indices,
            primitiveType : PrimitiveType.LINES,
            boundingSphere : new BoundingSphere.fromVertices(positions)
        });
    };

    return WallOutlineGeometry;
});

/*global define*/
define('Core/WebMercatorTilingScheme',[
        './Cartesian2',
        './defaultValue',
        './defined',
        './defineProperties',
        './Ellipsoid',
        './Rectangle',
        './WebMercatorProjection'
    ], function(
        Cartesian2,
        defaultValue,
        defined,
        defineProperties,
        Ellipsoid,
        Rectangle,
        WebMercatorProjection) {
    'use strict';

    /**
     * A tiling scheme for geometry referenced to a {@link WebMercatorProjection}, EPSG:3857.  This is
     * the tiling scheme used by Google Maps, Microsoft Bing Maps, and most of ESRI ArcGIS Online.
     *
     * @alias WebMercatorTilingScheme
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid whose surface is being tiled. Defaults to
     * the WGS84 ellipsoid.
     * @param {Number} [options.numberOfLevelZeroTilesX=1] The number of tiles in the X direction at level zero of
     *        the tile tree.
     * @param {Number} [options.numberOfLevelZeroTilesY=1] The number of tiles in the Y direction at level zero of
     *        the tile tree.
     * @param {Cartesian2} [options.rectangleSouthwestInMeters] The southwest corner of the rectangle covered by the
     *        tiling scheme, in meters.  If this parameter or rectangleNortheastInMeters is not specified, the entire
     *        globe is covered in the longitude direction and an equal distance is covered in the latitude
     *        direction, resulting in a square projection.
     * @param {Cartesian2} [options.rectangleNortheastInMeters] The northeast corner of the rectangle covered by the
     *        tiling scheme, in meters.  If this parameter or rectangleSouthwestInMeters is not specified, the entire
     *        globe is covered in the longitude direction and an equal distance is covered in the latitude
     *        direction, resulting in a square projection.
     */
    function WebMercatorTilingScheme(options) {
        options = defaultValue(options, {});

        this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        this._numberOfLevelZeroTilesX = defaultValue(options.numberOfLevelZeroTilesX, 1);
        this._numberOfLevelZeroTilesY = defaultValue(options.numberOfLevelZeroTilesY, 1);

        this._projection = new WebMercatorProjection(this._ellipsoid);

        if (defined(options.rectangleSouthwestInMeters) &&
            defined(options.rectangleNortheastInMeters)) {
            this._rectangleSouthwestInMeters = options.rectangleSouthwestInMeters;
            this._rectangleNortheastInMeters = options.rectangleNortheastInMeters;
        } else {
            var semimajorAxisTimesPi = this._ellipsoid.maximumRadius * Math.PI;
            this._rectangleSouthwestInMeters = new Cartesian2(-semimajorAxisTimesPi, -semimajorAxisTimesPi);
            this._rectangleNortheastInMeters = new Cartesian2(semimajorAxisTimesPi, semimajorAxisTimesPi);
        }

        var southwest = this._projection.unproject(this._rectangleSouthwestInMeters);
        var northeast = this._projection.unproject(this._rectangleNortheastInMeters);
        this._rectangle = new Rectangle(southwest.longitude, southwest.latitude,
                                  northeast.longitude, northeast.latitude);
    }

    defineProperties(WebMercatorTilingScheme.prototype, {
        /**
         * Gets the ellipsoid that is tiled by this tiling scheme.
         * @memberof WebMercatorTilingScheme.prototype
         * @type {Ellipsoid}
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        },

        /**
         * Gets the rectangle, in radians, covered by this tiling scheme.
         * @memberof WebMercatorTilingScheme.prototype
         * @type {Rectangle}
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        },

        /**
         * Gets the map projection used by this tiling scheme.
         * @memberof WebMercatorTilingScheme.prototype
         * @type {MapProjection}
         */
        projection : {
            get : function() {
                return this._projection;
            }
        }
    });

    /**
     * Gets the total number of tiles in the X direction at a specified level-of-detail.
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the X direction at the given level.
     */
    WebMercatorTilingScheme.prototype.getNumberOfXTilesAtLevel = function(level) {
        return this._numberOfLevelZeroTilesX << level;
    };

    /**
     * Gets the total number of tiles in the Y direction at a specified level-of-detail.
     *
     * @param {Number} level The level-of-detail.
     * @returns {Number} The number of tiles in the Y direction at the given level.
     */
    WebMercatorTilingScheme.prototype.getNumberOfYTilesAtLevel = function(level) {
        return this._numberOfLevelZeroTilesY << level;
    };

    /**
     * Transforms an rectangle specified in geodetic radians to the native coordinate system
     * of this tiling scheme.
     *
     * @param {Rectangle} rectangle The rectangle to transform.
     * @param {Rectangle} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the native rectangle if 'result'
     *          is undefined.
     */
    WebMercatorTilingScheme.prototype.rectangleToNativeRectangle = function(rectangle, result) {
        var projection = this._projection;
        var southwest = projection.project(Rectangle.southwest(rectangle));
        var northeast = projection.project(Rectangle.northeast(rectangle));

        if (!defined(result)) {
            return new Rectangle(southwest.x, southwest.y, northeast.x, northeast.y);
        }

        result.west = southwest.x;
        result.south = southwest.y;
        result.east = northeast.x;
        result.north = northeast.y;
        return result;
    };

    /**
     * Converts tile x, y coordinates and level to an rectangle expressed in the native coordinates
     * of the tiling scheme.
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    WebMercatorTilingScheme.prototype.tileXYToNativeRectangle = function(x, y, level, result) {
        var xTiles = this.getNumberOfXTilesAtLevel(level);
        var yTiles = this.getNumberOfYTilesAtLevel(level);

        var xTileWidth = (this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x) / xTiles;
        var west = this._rectangleSouthwestInMeters.x + x * xTileWidth;
        var east = this._rectangleSouthwestInMeters.x + (x + 1) * xTileWidth;

        var yTileHeight = (this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y) / yTiles;
        var north = this._rectangleNortheastInMeters.y - y * yTileHeight;
        var south = this._rectangleNortheastInMeters.y - (y + 1) * yTileHeight;

        if (!defined(result)) {
            return new Rectangle(west, south, east, north);
        }

        result.west = west;
        result.south = south;
        result.east = east;
        result.north = north;
        return result;
    };

    /**
     * Converts tile x, y coordinates and level to a cartographic rectangle in radians.
     *
     * @param {Number} x The integer x coordinate of the tile.
     * @param {Number} y The integer y coordinate of the tile.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Object} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Rectangle} The specified 'result', or a new object containing the rectangle
     *          if 'result' is undefined.
     */
    WebMercatorTilingScheme.prototype.tileXYToRectangle = function(x, y, level, result) {
        var nativeRectangle = this.tileXYToNativeRectangle(x, y, level, result);

        var projection = this._projection;
        var southwest = projection.unproject(new Cartesian2(nativeRectangle.west, nativeRectangle.south));
        var northeast = projection.unproject(new Cartesian2(nativeRectangle.east, nativeRectangle.north));

        nativeRectangle.west = southwest.longitude;
        nativeRectangle.south = southwest.latitude;
        nativeRectangle.east = northeast.longitude;
        nativeRectangle.north = northeast.latitude;
        return nativeRectangle;
    };

    /**
     * Calculates the tile x, y coordinates of the tile containing
     * a given cartographic position.
     *
     * @param {Cartographic} position The position.
     * @param {Number} level The tile level-of-detail.  Zero is the least detailed.
     * @param {Cartesian2} [result] The instance to which to copy the result, or undefined if a new instance
     *        should be created.
     * @returns {Cartesian2} The specified 'result', or a new object containing the tile x, y coordinates
     *          if 'result' is undefined.
     */
    WebMercatorTilingScheme.prototype.positionToTileXY = function(position, level, result) {
        var rectangle = this._rectangle;
        if (!Rectangle.contains(rectangle, position)) {
            // outside the bounds of the tiling scheme
            return undefined;
        }

        var xTiles = this.getNumberOfXTilesAtLevel(level);
        var yTiles = this.getNumberOfYTilesAtLevel(level);

        var overallWidth = this._rectangleNortheastInMeters.x - this._rectangleSouthwestInMeters.x;
        var xTileWidth = overallWidth / xTiles;
        var overallHeight = this._rectangleNortheastInMeters.y - this._rectangleSouthwestInMeters.y;
        var yTileHeight = overallHeight / yTiles;

        var projection = this._projection;

        var webMercatorPosition = projection.project(position);
        var distanceFromWest = webMercatorPosition.x - this._rectangleSouthwestInMeters.x;
        var distanceFromNorth = this._rectangleNortheastInMeters.y - webMercatorPosition.y;

        var xTileCoordinate = distanceFromWest / xTileWidth | 0;
        if (xTileCoordinate >= xTiles) {
            xTileCoordinate = xTiles - 1;
        }
        var yTileCoordinate = distanceFromNorth / yTileHeight | 0;
        if (yTileCoordinate >= yTiles) {
            yTileCoordinate = yTiles - 1;
        }

        if (!defined(result)) {
            return new Cartesian2(xTileCoordinate, yTileCoordinate);
        }

        result.x = xTileCoordinate;
        result.y = yTileCoordinate;
        return result;
    };

    return WebMercatorTilingScheme;
});

/*global define*/
define('Core/wrapFunction',[
        './DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * Wraps a function on the provided objects with another function called in the
     * object's context so that the new function is always called immediately
     * before the old one.
     *
     * @private
     */
    function wrapFunction(obj, oldFunction, newFunction) {
                if (typeof oldFunction !== 'function') {
            throw new DeveloperError("oldFunction is required to be a function.");
        }

        if (typeof newFunction !== 'function') {
            throw new DeveloperError("oldFunction is required to be a function.");
        }
        
        return function() {
            newFunction.apply(obj, arguments);
            oldFunction.apply(obj, arguments);
        };
    }

    return wrapFunction;
});

/*global define*/
define('DataSources/ConstantProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event'
    ], function(
        defined,
        defineProperties,
        Event) {
    'use strict';

    /**
     * A {@link Property} whose value does not change with respect to simulation time.
     *
     * @alias ConstantProperty
     * @constructor
     *
     * @param {Object} [value] The property value.
     *
     * @see ConstantPositionProperty
     *
     * @exception {DeveloperError} value.clone is a required function.
     * @exception {DeveloperError} value.equals is a required function.
     */
    function ConstantProperty(value) {
        this._value = undefined;
        this._hasClone = false;
        this._hasEquals = false;
        this._definitionChanged = new Event();
        this.setValue(value);
    }

    defineProperties(ConstantProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.
         * This property always returns <code>true</code>.
         * @memberof ConstantProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            value : true
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value.
         * @memberof ConstantProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        }
    });

    /**
     * Gets the value of the property.
     *
     * @param {JulianDate} [time] The time for which to retrieve the value.  This parameter is unused since the value does not change with respect to time.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ConstantProperty.prototype.getValue = function(time, result) {
        return this._hasClone ? this._value.clone(result) : this._value;
    };

    /**
     * Sets the value of the property.
     *
     * @param {Object} value The property value.
     *
     * @exception {DeveloperError} value.clone is a required function.
     * @exception {DeveloperError} value.equals is a required function.
     */
    ConstantProperty.prototype.setValue = function(value) {
        var oldValue = this._value;
        if (oldValue !== value) {
            var isDefined = defined(value);
            var hasClone = isDefined && typeof value.clone === 'function';
            var hasEquals = isDefined && typeof value.equals === 'function';

            this._hasClone = hasClone;
            this._hasEquals = hasEquals;

            var changed = !hasEquals || !value.equals(oldValue);
            if (changed) {
                this._value = !hasClone ? value : value.clone();
                this._definitionChanged.raiseEvent(this);
            }
        }
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    ConstantProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof ConstantProperty && //
                ((!this._hasEquals && (this._value === other._value)) || //
                (this._hasEquals && this._value.equals(other._value))));
    };

    return ConstantProperty;
});

/*global define*/
define('DataSources/createPropertyDescriptor',[
        '../Core/defaultValue',
        '../Core/defined',
        './ConstantProperty'
    ], function(
        defaultValue,
        defined,
        ConstantProperty) {
    'use strict';

    function createProperty(name, privateName, subscriptionName, configurable, createPropertyCallback) {
        return {
            configurable : configurable,
            get : function() {
                return this[privateName];
            },
            set : function(value) {
                var oldValue = this[privateName];
                var subscription = this[subscriptionName];
                if (defined(subscription)) {
                    subscription();
                    this[subscriptionName] = undefined;
                }

                var hasValue = defined(value);
                if (hasValue && !defined(value.getValue) && defined(createPropertyCallback)) {
                    value = createPropertyCallback(value);
                }

                if (oldValue !== value) {
                    this[privateName] = value;
                    this._definitionChanged.raiseEvent(this, name, value, oldValue);
                }

                if (defined(value) && defined(value.definitionChanged)) {
                    this[subscriptionName] = value.definitionChanged.addEventListener(function() {
                        this._definitionChanged.raiseEvent(this, name, value, value);
                    }, this);
                }
            }
        };
    }

    function createConstantProperty(value) {
        return new ConstantProperty(value);
    }

    /**
     * Used to consistently define all DataSources graphics objects.
     * This is broken into two functions because the Chrome profiler does a better
     * job of optimizing lookups if it notices that the string is constant throughout the function.
     * @private
     */
    function createPropertyDescriptor(name, configurable, createPropertyCallback) {
        //Safari 8.0.3 has a JavaScript bug that causes it to confuse two variables and treat them as the same.
        //The two extra toString calls work around the issue.
        return createProperty(name, '_' + name.toString(), '_' + name.toString() + 'Subscription', defaultValue(configurable, false), defaultValue(createPropertyCallback, createConstantProperty));
    }

    return createPropertyDescriptor;
});

/*global define*/
define('DataSources/BillboardGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a two dimensional icon located at the position of the containing {@link Entity}.
     * <p>
     * <div align='center'>
     * <img src='images/Billboard.png' width='400' height='300' /><br />
     * Example billboards
     * </div>
     * </p>
     *
     * @alias BillboardGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.image] A Property specifying the Image, URI, or Canvas to use for the billboard.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the billboard.
     * @param {Property} [options.scale=1.0] A numeric Property specifying the scale to apply to the image size.
     * @param {Property} [options.horizontalOrigin=HorizontalOrigin.CENTER] A Property specifying the {@link HorizontalOrigin}.
     * @param {Property} [options.verticalOrigin=VerticalOrigin.CENTER] A Property specifying the {@link VerticalOrigin}.
     * @param {Property} [options.eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the eye offset.
     * @param {Property} [options.pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Property specifying the pixel offset.
     * @param {Property} [options.rotation=0] A numeric Property specifying the rotation about the alignedAxis.
     * @param {Property} [options.alignedAxis=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the unit vector axis of rotation.
     * @param {Property} [options.width] A numeric Property specifying the width of the billboard in pixels, overriding the native size.
     * @param {Property} [options.height] A numeric Property specifying the height of the billboard in pixels, overriding the native size.
     * @param {Property} [options.color=Color.WHITE] A Property specifying the tint {@link Color} of the image.
     * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to scale the point based on distance from the camera.
     * @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera.
     * @param {Property} [options.pixelOffsetScaleByDistance] A {@link NearFarScalar} Property used to set pixelOffset based on distance from the camera.
     * @param {Property} [options.imageSubRegion] A Property specifying a {@link BoundingRectangle} that defines a sub-region of the image to use for the billboard, rather than the entire image, measured in pixels from the bottom-left.
     * @param {Property} [options.sizeInMeters] A boolean Property specifying whether this billboard's size should be measured in meters.
     * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this billboard will be displayed.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
     */
    function BillboardGraphics(options) {
        this._image = undefined;
        this._imageSubscription = undefined;
        this._imageSubRegion = undefined;
        this._imageSubRegionSubscription = undefined;
        this._width = undefined;
        this._widthSubscription = undefined;
        this._height = undefined;
        this._heightSubscription = undefined;
        this._scale = undefined;
        this._scaleSubscription = undefined;
        this._rotation = undefined;
        this._rotationSubscription = undefined;
        this._alignedAxis = undefined;
        this._alignedAxisSubscription = undefined;
        this._horizontalOrigin = undefined;
        this._horizontalOriginSubscription = undefined;
        this._verticalOrigin = undefined;
        this._verticalOriginSubscription = undefined;
        this._color = undefined;
        this._colorSubscription = undefined;
        this._eyeOffset = undefined;
        this._eyeOffsetSubscription = undefined;
        this._heightReference = undefined;
        this._heightReferenceSubscription = undefined;
        this._pixelOffset = undefined;
        this._pixelOffsetSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._scaleByDistance = undefined;
        this._scaleByDistanceSubscription = undefined;
        this._translucencyByDistance = undefined;
        this._translucencyByDistanceSubscription = undefined;
        this._pixelOffsetScaleByDistance = undefined;
        this._pixelOffsetScaleByDistanceSubscription = undefined;
        this._sizeInMeters = undefined;
        this._sizeInMetersSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(BillboardGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof BillboardGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the Property specifying the Image, URI, or Canvas to use for the billboard.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        image : createPropertyDescriptor('image'),

        /**
         * Gets or sets the Property specifying a {@link BoundingRectangle} that defines a
         * sub-region of the <code>image</code> to use for the billboard, rather than the entire image,
         * measured in pixels from the bottom-left.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        imageSubRegion : createPropertyDescriptor('imageSubRegion'),

        /**
         * Gets or sets the numeric Property specifying the uniform scale to apply to the image.
         * A scale greater than <code>1.0</code> enlarges the billboard while a scale less than <code>1.0</code> shrinks it.
         * <p>
         * <div align='center'>
         * <img src='images/Billboard.setScale.png' width='400' height='300' /><br/>
         * From left to right in the above image, the scales are <code>0.5</code>, <code>1.0</code>, and <code>2.0</code>.
         * </div>
         * </p>
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        scale : createPropertyDescriptor('scale'),

        /**
         * Gets or sets the numeric Property specifying the rotation of the image
         * counter clockwise from the <code>alignedAxis</code>.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default 0
         */
        rotation : createPropertyDescriptor('rotation'),

        /**
         * Gets or sets the {@link Cartesian3} Property specifying the unit vector axis of rotation
         * in the fixed frame. When set to Cartesian3.ZERO the rotation is from the top of the screen.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default Cartesian3.ZERO
         */
        alignedAxis : createPropertyDescriptor('alignedAxis'),

        /**
         * Gets or sets the Property specifying the {@link HorizontalOrigin}.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default HorizontalOrigin.CENTER
         */
        horizontalOrigin : createPropertyDescriptor('horizontalOrigin'),

        /**
         * Gets or sets the Property specifying the {@link VerticalOrigin}.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default VerticalOrigin.CENTER
         */
        verticalOrigin : createPropertyDescriptor('verticalOrigin'),

        /**
         * Gets or sets the Property specifying the {@link Color} that is multiplied with the <code>image</code>.
         * This has two common use cases.  First, the same white texture may be used by many different billboards,
         * each with a different color, to create colored billboards. Second, the color's alpha component can be
         * used to make the billboard translucent as shown below. An alpha of <code>0.0</code> makes the billboard
         * transparent, and <code>1.0</code> makes the billboard opaque.
         * <p>
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><code>default</code><br/><img src='images/Billboard.setColor.Alpha255.png' width='250' height='188' /></td>
         * <td align='center'><code>alpha : 0.5</code><br/><img src='images/Billboard.setColor.Alpha127.png' width='250' height='188' /></td>
         * </tr></table>
         * </div>
         * </p>
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color'),

        /**
         * Gets or sets the {@link Cartesian3} Property specifying the billboard's offset in eye coordinates.
         * Eye coordinates is a left-handed coordinate system, where <code>x</code> points towards the viewer's
         * right, <code>y</code> points up, and <code>z</code> points into the screen.
         * <p>
         * An eye offset is commonly used to arrange multiple billboards or objects at the same position, e.g., to
         * arrange a billboard above its corresponding 3D model.
         * </p>
         * Below, the billboard is positioned at the center of the Earth but an eye offset makes it always
         * appear on top of the Earth regardless of the viewer's or Earth's orientation.
         * <p>
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><img src='images/Billboard.setEyeOffset.one.png' width='250' height='188' /></td>
         * <td align='center'><img src='images/Billboard.setEyeOffset.two.png' width='250' height='188' /></td>
         * </tr></table>
         * <code>b.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);</code>
         * </div>
         * </p>
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default Cartesian3.ZERO
         */
        eyeOffset : createPropertyDescriptor('eyeOffset'),

        /**
         * Gets or sets the Property specifying the {@link HeightReference}.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default HeightReference.NONE
         */
        heightReference : createPropertyDescriptor('heightReference'),

        /**
         * Gets or sets the {@link Cartesian2} Property specifying the billboard's pixel offset in screen space
         * from the origin of this billboard.  This is commonly used to align multiple billboards and labels at
         * the same position, e.g., an image and text.  The screen space origin is the top, left corner of the
         * canvas; <code>x</code> increases from left to right, and <code>y</code> increases from top to bottom.
         * <p>
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><code>default</code><br/><img src='images/Billboard.setPixelOffset.default.png' width='250' height='188' /></td>
         * <td align='center'><code>b.pixeloffset = new Cartesian2(50, 25);</code><br/><img src='images/Billboard.setPixelOffset.x50y-25.png' width='250' height='188' /></td>
         * </tr></table>
         * The billboard's origin is indicated by the yellow point.
         * </div>
         * </p>
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default Cartesian2.ZERO
         */
        pixelOffset : createPropertyDescriptor('pixelOffset'),

        /**
         * Gets or sets the boolean Property specifying the visibility of the billboard.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the numeric Property specifying the billboard's width in pixels.
         * When undefined, the native width is used.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        width : createPropertyDescriptor('width'),

        /**
         * Gets or sets the numeric Property specifying the height of the billboard in pixels.
         * When undefined, the native height is used.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        height : createPropertyDescriptor('height'),

        /**
         * Gets or sets {@link NearFarScalar} Property specifying the scale of the billboard based on the distance from the camera.
         * A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the billboard's scale remains clamped to the nearest bound.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        scaleByDistance : createPropertyDescriptor('scaleByDistance'),

        /**
         * Gets or sets {@link NearFarScalar} Property specifying the translucency of the billboard based on the distance from the camera.
         * A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the billboard's translucency remains clamped to the nearest bound.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        translucencyByDistance : createPropertyDescriptor('translucencyByDistance'),

        /**
         * Gets or sets {@link NearFarScalar} Property specifying the pixel offset of the billboard based on the distance from the camera.
         * A billboard's pixel offset will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the billboard's pixel offset remains clamped to the nearest bound.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance'),

        /**
         * Gets or sets the boolean Property specifying if this billboard's size will be measured in meters.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         * @default false
         */
        sizeInMeters : createPropertyDescriptor('sizeInMeters'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this billboard will be displayed.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {BillboardGraphics} [result] The object onto which to store the result.
     * @returns {BillboardGraphics} The modified result parameter or a new instance if one was not provided.
     */
    BillboardGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new BillboardGraphics(this);
        }
        result.color = this._color;
        result.eyeOffset = this._eyeOffset;
        result.heightReference = this._heightReference;
        result.horizontalOrigin = this._horizontalOrigin;
        result.image = this._image;
        result.imageSubRegion = this._imageSubRegion;
        result.pixelOffset = this._pixelOffset;
        result.scale = this._scale;
        result.rotation = this._rotation;
        result.alignedAxis = this._alignedAxis;
        result.show = this._show;
        result.verticalOrigin = this._verticalOrigin;
        result.width = this._width;
        result.height = this._height;
        result.scaleByDistance = this._scaleByDistance;
        result.translucencyByDistance = this._translucencyByDistance;
        result.pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
        result.sizeInMeters = this._sizeInMeters;
        result.distanceDisplayCondition = this._distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {BillboardGraphics} source The object to be merged into this object.
     */
    BillboardGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.color = defaultValue(this._color, source.color);
        this.eyeOffset = defaultValue(this._eyeOffset, source.eyeOffset);
        this.heightReference = defaultValue(this._heightReference, source.heightReference);
        this.horizontalOrigin = defaultValue(this._horizontalOrigin, source.horizontalOrigin);
        this.image = defaultValue(this._image, source.image);
        this.imageSubRegion = defaultValue(this._imageSubRegion, source.imageSubRegion);
        this.pixelOffset = defaultValue(this._pixelOffset, source.pixelOffset);
        this.scale = defaultValue(this._scale, source.scale);
        this.rotation = defaultValue(this._rotation, source.rotation);
        this.alignedAxis = defaultValue(this._alignedAxis, source.alignedAxis);
        this.show = defaultValue(this._show, source.show);
        this.verticalOrigin = defaultValue(this._verticalOrigin, source.verticalOrigin);
        this.width = defaultValue(this._width, source.width);
        this.height = defaultValue(this._height, source.height);
        this.scaleByDistance = defaultValue(this._scaleByDistance, source.scaleByDistance);
        this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance);
        this.pixelOffsetScaleByDistance = defaultValue(this._pixelOffsetScaleByDistance, source.pixelOffsetScaleByDistance);
        this.sizeInMeters = defaultValue(this._sizeInMeters, source.sizeInMeters);
        this.distanceDisplayCondition = defaultValue(this._distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return BillboardGraphics;
});

/*global define*/
define('Scene/HeightReference',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Represents the position relative to the terrain.
     *
     * @exports HeightReference
     */
    var HeightReference = {
        /**
         * The position is absolute.
         * @type {Number}
         * @constant
         */
        NONE : 0,

        /**
         * The position is clamped to the terrain.
         * @type {Number}
         * @constant
         */
        CLAMP_TO_GROUND : 1,

        /**
         * The position height is the height above the terrain.
         * @type {Number}
         * @constant
         */
        RELATIVE_TO_GROUND : 2
    };

    return freezeObject(HeightReference);
});

/*global define*/
define('Scene/HorizontalOrigin',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The horizontal location of an origin relative to an object, e.g., a {@link Billboard}
     * or {@link Label}.  For example, setting the horizontal origin to <code>LEFT</code>
     * or <code>RIGHT</code> will display a billboard to the left or right (in screen space)
     * of the anchor position.
     * <br /><br />
     * <div align='center'>
     * <img src='images/Billboard.setHorizontalOrigin.png' width='648' height='196' /><br />
     * </div>
     *
     * @exports HorizontalOrigin
     *
     * @see Billboard#horizontalOrigin
     * @see Label#horizontalOrigin
     */
    var HorizontalOrigin = {
        /**
         * The origin is at the horizontal center of the object.
         *
         * @type {Number}
         * @constant
         */
        CENTER : 0,

        /**
         * The origin is on the left side of the object.
         *
         * @type {Number}
         * @constant
         */
        LEFT : 1,

        /**
         * The origin is on the right side of the object.
         *
         * @type {Number}
         * @constant
         */
        RIGHT : -1
    };

    return freezeObject(HorizontalOrigin);
});

/*global define*/
define('Scene/VerticalOrigin',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The vertical location of an origin relative to an object, e.g., a {@link Billboard}
     * or {@link Label}.  For example, setting the vertical origin to <code>TOP</code>
     * or <code>BOTTOM</code> will display a billboard above or below (in screen space)
     * the anchor position.
     * <br /><br />
     * <div align='center'>
     * <img src='images/Billboard.setVerticalOrigin.png' width='695' height='175' /><br />
     * </div>
     *
     * @exports VerticalOrigin
     *
     * @see Billboard#verticalOrigin
     * @see Label#verticalOrigin
     */
    var VerticalOrigin = {
        /**
         * The origin is at the vertical center between <code>BASELINE</code> and <code>TOP</code>.
         *
         * @type {Number}
         * @constant
         */
        CENTER : 0,

        /**
         * The origin is at the bottom of the object.
         *
         * @type {Number}
         * @constant
         */
        BOTTOM : 1,

        /**
         * If the object contains text, the origin is at the baseline of the text, else the origin is at the bottom of the object.
         *
         * @type {Number}
         * @constant
         */
        BASELINE : 2,

        /**
         * The origin is at the top of the object.
         *
         * @type {Number}
         * @constant
         */
        TOP : -1
    };

    return freezeObject(VerticalOrigin);
});

/*global define*/
define('DataSources/BoundingSphereState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The state of a BoundingSphere computation being performed by a {@link Visualizer}.
     * @exports BoundingSphereState
     * @private
     */
    var BoundingSphereState = {
        /**
         * The BoundingSphere has been computed.
         * @type BoundingSphereState
         * @constant
         */
        DONE : 0,
        /**
         * The BoundingSphere is still being computed.
         * @type BoundingSphereState
         * @constant
         */
        PENDING : 1,
        /**
         * The BoundingSphere does not exist.
         * @type BoundingSphereState
         * @constant
         */
        FAILED : 2
    };

    return freezeObject(BoundingSphereState);
});

/*global define*/
define('DataSources/Property',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * The interface for all properties, which represent a value that can optionally vary over time.
     * This type defines an interface and cannot be instantiated directly.
     *
     * @alias Property
     * @constructor
     *
     * @see CompositeProperty
     * @see ConstantProperty
     * @see SampledProperty
     * @see TimeIntervalCollectionProperty
     * @see MaterialProperty
     * @see PositionProperty
     * @see ReferenceProperty
     */
    function Property() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(Property.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof Property.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof Property.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Gets the value of the property at the provided time.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    Property.prototype.getValue = DeveloperError.throwInstantiationError;

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     * @function
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    Property.prototype.equals = DeveloperError.throwInstantiationError;

    /**
     * @private
     */
    Property.equals = function(left, right) {
        return left === right || (defined(left) && left.equals(right));
    };

    /**
     * @private
     */
    Property.arrayEquals = function(left, right) {
        if (left === right) {
            return true;
        }
        if ((!defined(left) || !defined(right)) || (left.length !== right.length)) {
            return false;
        }
        var length = left.length;
        for (var i = 0; i < length; i++) {
            if (!Property.equals(left[i], right[i])) {
                return false;
            }
        }
        return true;
    };

    /**
     * @private
     */
    Property.isConstant = function(property) {
        return !defined(property) || property.isConstant;
    };

    /**
     * @private
     */
    Property.getValueOrUndefined = function(property, time, result) {
        return defined(property) ? property.getValue(time, result) : undefined;
    };

    /**
     * @private
     */
    Property.getValueOrDefault = function(property, time, valueDefault, result) {
        return defined(property) ? defaultValue(property.getValue(time, result), valueDefault) : valueDefault;
    };

    /**
     * @private
     */
    Property.getValueOrClonedDefault = function(property, time, valueDefault, result) {
        var value;
        if (defined(property)) {
            value = property.getValue(time, result);
        }
        if (!defined(value)) {
            value = valueDefault.clone(value);
        }
        return value;
    };

    return Property;
});

/*global define*/
define('DataSources/BillboardVisualizer',[
        '../Core/AssociativeArray',
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/NearFarScalar',
        '../Scene/HeightReference',
        '../Scene/HorizontalOrigin',
        '../Scene/VerticalOrigin',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Color,
        defined,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        NearFarScalar,
        HeightReference,
        HorizontalOrigin,
        VerticalOrigin,
        BoundingSphereState,
        Property) {
    'use strict';

    var defaultColor = Color.WHITE;
    var defaultEyeOffset = Cartesian3.ZERO;
    var defaultHeightReference = HeightReference.NONE;
    var defaultPixelOffset = Cartesian2.ZERO;
    var defaultScale = 1.0;
    var defaultRotation = 0.0;
    var defaultAlignedAxis = Cartesian3.ZERO;
    var defaultHorizontalOrigin = HorizontalOrigin.CENTER;
    var defaultVerticalOrigin = VerticalOrigin.CENTER;
    var defaultSizeInMeters = false;

    var position = new Cartesian3();
    var color = new Color();
    var eyeOffset = new Cartesian3();
    var pixelOffset = new Cartesian2();
    var scaleByDistance = new NearFarScalar();
    var translucencyByDistance = new NearFarScalar();
    var pixelOffsetScaleByDistance = new NearFarScalar();
    var boundingRectangle = new BoundingRectangle();
    var distanceDisplayCondition = new DistanceDisplayCondition();

    function EntityData(entity) {
        this.entity = entity;
        this.billboard = undefined;
        this.textureValue = undefined;
    }

    /**
     * A {@link Visualizer} which maps {@link Entity#billboard} to a {@link Billboard}.
     * @alias BillboardVisualizer
     * @constructor
     *
     * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
     * @param {EntityCollection} entityCollection The entityCollection to visualize.
     */
    function BillboardVisualizer(entityCluster, entityCollection) {
                if (!defined(entityCluster)) {
            throw new DeveloperError('entityCluster is required.');
        }
        if (!defined(entityCollection)) {
            throw new DeveloperError('entityCollection is required.');
        }
        
        entityCollection.collectionChanged.addEventListener(BillboardVisualizer.prototype._onCollectionChanged, this);

        this._cluster = entityCluster;
        this._entityCollection = entityCollection;
        this._items = new AssociativeArray();
        this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
    }

    /**
     * Updates the primitives created by this visualizer to match their
     * Entity counterpart at the given time.
     *
     * @param {JulianDate} time The time to update to.
     * @returns {Boolean} This function always returns true.
     */
    BillboardVisualizer.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var items = this._items.values;
        var cluster = this._cluster;

        for (var i = 0, len = items.length; i < len; i++) {
            var item = items[i];
            var entity = item.entity;
            var billboardGraphics = entity._billboard;
            var textureValue;
            var billboard = item.billboard;
            var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(billboardGraphics._show, time, true);

            if (show) {
                position = Property.getValueOrUndefined(entity._position, time, position);
                textureValue = Property.getValueOrUndefined(billboardGraphics._image, time);
                show = defined(position) && defined(textureValue);
            }

            if (!show) {
                //don't bother creating or updating anything else
                returnPrimitive(item, entity, cluster);
                continue;
            }

            if (!Property.isConstant(entity._position)) {
                cluster._clusterDirty = true;
            }

            if (!defined(billboard)) {
                billboard = cluster.getBillboard(entity);
                billboard.id = entity;
                billboard.image = undefined;
                item.billboard = billboard;
            }

            billboard.show = show;
            if (!defined(billboard.image) || item.textureValue !== textureValue) {
                billboard.image = textureValue;
                item.textureValue = textureValue;
            }
            billboard.position = position;
            billboard.color = Property.getValueOrDefault(billboardGraphics._color, time, defaultColor, color);
            billboard.eyeOffset = Property.getValueOrDefault(billboardGraphics._eyeOffset, time, defaultEyeOffset, eyeOffset);
            billboard.heightReference = Property.getValueOrDefault(billboardGraphics._heightReference, time, defaultHeightReference);
            billboard.pixelOffset = Property.getValueOrDefault(billboardGraphics._pixelOffset, time, defaultPixelOffset, pixelOffset);
            billboard.scale = Property.getValueOrDefault(billboardGraphics._scale, time, defaultScale);
            billboard.rotation = Property.getValueOrDefault(billboardGraphics._rotation, time, defaultRotation);
            billboard.alignedAxis = Property.getValueOrDefault(billboardGraphics._alignedAxis, time, defaultAlignedAxis);
            billboard.horizontalOrigin = Property.getValueOrDefault(billboardGraphics._horizontalOrigin, time, defaultHorizontalOrigin);
            billboard.verticalOrigin = Property.getValueOrDefault(billboardGraphics._verticalOrigin, time, defaultVerticalOrigin);
            billboard.width = Property.getValueOrUndefined(billboardGraphics._width, time);
            billboard.height = Property.getValueOrUndefined(billboardGraphics._height, time);
            billboard.scaleByDistance = Property.getValueOrUndefined(billboardGraphics._scaleByDistance, time, scaleByDistance);
            billboard.translucencyByDistance = Property.getValueOrUndefined(billboardGraphics._translucencyByDistance, time, translucencyByDistance);
            billboard.pixelOffsetScaleByDistance = Property.getValueOrUndefined(billboardGraphics._pixelOffsetScaleByDistance, time, pixelOffsetScaleByDistance);
            billboard.sizeInMeters = Property.getValueOrDefault(billboardGraphics._sizeInMeters, defaultSizeInMeters);
            billboard.distanceDisplayCondition = Property.getValueOrUndefined(billboardGraphics._distanceDisplayCondition, time, distanceDisplayCondition);

            var subRegion = Property.getValueOrUndefined(billboardGraphics._imageSubRegion, time, boundingRectangle);
            if (defined(subRegion)) {
                billboard.setImageSubRegion(billboard._imageId, subRegion);
            }
        }
        return true;
    };

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    BillboardVisualizer.prototype.getBoundingSphere = function(entity, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var item = this._items.get(entity.id);
        if (!defined(item) || !defined(item.billboard)) {
            return BoundingSphereState.FAILED;
        }

        var billboard = item.billboard;
        if (billboard.heightReference === HeightReference.NONE) {
            result.center = Cartesian3.clone(billboard.position, result.center);
        } else {
            if (!defined(billboard._clampedPosition)) {
                return BoundingSphereState.PENDING;
            }
            result.center = Cartesian3.clone(billboard._clampedPosition, result.center);
        }
        result.radius = 0;
        return BoundingSphereState.DONE;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    BillboardVisualizer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes and destroys all primitives created by this instance.
     */
    BillboardVisualizer.prototype.destroy = function() {
        this._entityCollection.collectionChanged.removeEventListener(BillboardVisualizer.prototype._onCollectionChanged, this);
        var entities = this._entityCollection.values;
        for (var i = 0; i < entities.length; i++) {
            this._cluster.removeBillboard(entities[i]);
        }
        return destroyObject(this);
    };

    BillboardVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
        var i;
        var entity;
        var items = this._items;
        var cluster = this._cluster;

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            if (defined(entity._billboard) && defined(entity._position)) {
                items.set(entity.id, new EntityData(entity));
            }
        }

        for (i = changed.length - 1; i > -1; i--) {
            entity = changed[i];
            if (defined(entity._billboard) && defined(entity._position)) {
                if (!items.contains(entity.id)) {
                    items.set(entity.id, new EntityData(entity));
                }
            } else {
                returnPrimitive(items.get(entity.id), entity, cluster);
                items.remove(entity.id);
            }
        }

        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            returnPrimitive(items.get(entity.id), entity, cluster);
            items.remove(entity.id);
        }
    };

    function returnPrimitive(item, entity, cluster) {
        if (defined(item)) {
            item.billboard = undefined;
            cluster.removeBillboard(entity);
        }
    }

    return BillboardVisualizer;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/AllMaterialAppearanceFS',[],function() {
    'use strict';
    return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec3 v_tangentEC;\n\
varying vec3 v_bitangentEC;\n\
varying vec2 v_st;\n\
\n\
void main()\n\
{\n\
    vec3 positionToEyeEC = -v_positionEC;\n\
    mat3 tangentToEyeMatrix = czm_tangentToEyeSpaceMatrix(v_normalEC, v_tangentEC, v_bitangentEC);\n\
\n\
    vec3 normalEC = normalize(v_normalEC);\n\
#ifdef FACE_FORWARD\n\
    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
\n\
    czm_materialInput materialInput;\n\
    materialInput.normalEC = normalEC;\n\
    materialInput.tangentToEyeMatrix = tangentToEyeMatrix;\n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
    materialInput.st = v_st;\n\
    czm_material material = czm_getMaterial(materialInput);\n\
\n\
#ifdef FLAT\n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
    gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/AllMaterialAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec3 tangent;\n\
attribute vec3 bitangent;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec3 v_tangentEC;\n\
varying vec3 v_bitangentEC;\n\
varying vec2 v_st;\n\
\n\
void main()\n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates\n\
    v_normalEC = czm_normal * normal;                         // normal in eye coordinates\n\
    v_tangentEC = czm_normal * tangent;                       // tangent in eye coordinates\n\
    v_bitangentEC = czm_normal * bitangent;                   // bitangent in eye coordinates\n\
    v_st = st;\n\
\n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/BasicMaterialAppearanceFS',[],function() {
    'use strict';
    return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
\n\
void main()\n\
{\n\
    vec3 positionToEyeEC = -v_positionEC; \n\
\n\
    vec3 normalEC = normalize(v_normalEC);\n\
#ifdef FACE_FORWARD\n\
    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
\n\
    czm_materialInput materialInput;\n\
    materialInput.normalEC = normalEC;\n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
    czm_material material = czm_getMaterial(materialInput);\n\
    \n\
#ifdef FLAT    \n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
    gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/BasicMaterialAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute float batchId;\n\
\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
\n\
void main() \n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates\n\
    v_normalEC = czm_normal * normal;                         // normal in eye coordinates\n\
    \n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/TexturedMaterialAppearanceFS',[],function() {
    'use strict';
    return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec2 v_st;\n\
\n\
void main()\n\
{\n\
    vec3 positionToEyeEC = -v_positionEC; \n\
\n\
    vec3 normalEC = normalize(v_normalEC);;\n\
#ifdef FACE_FORWARD\n\
    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
\n\
    czm_materialInput materialInput;\n\
    materialInput.normalEC = normalEC;\n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
    materialInput.st = v_st;\n\
    czm_material material = czm_getMaterial(materialInput);\n\
    \n\
#ifdef FLAT    \n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
    gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/TexturedMaterialAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec2 v_st;\n\
\n\
void main() \n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates\n\
    v_normalEC = czm_normal * normal;                         // normal in eye coordinates\n\
    v_st = st;\n\
    \n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/BlendEquation',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Determines how two pixels' values are combined.
     *
     * @exports BlendEquation
     */
    var BlendEquation = {
        /**
         * Pixel values are added componentwise.  This is used in additive blending for translucency.
         *
         * @type {Number}
         * @constant
         */
        ADD : WebGLConstants.FUNC_ADD,

        /**
         * Pixel values are subtracted componentwise (source - destination).  This is used in alpha blending for translucency.
         *
         * @type {Number}
         * @constant
         */
        SUBTRACT : WebGLConstants.FUNC_SUBTRACT,

        /**
         * Pixel values are subtracted componentwise (destination - source).
         *
         * @type {Number}
         * @constant
         */
        REVERSE_SUBTRACT : WebGLConstants.FUNC_REVERSE_SUBTRACT

        // No min and max like in ColladaFX GLES2 profile
    };

    return freezeObject(BlendEquation);
});

/*global define*/
define('Scene/BlendFunction',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Determines how blending factors are computed.
     *
     * @exports BlendFunction
     */
    var BlendFunction = {
        /**
         * The blend factor is zero.
         *
         * @type {Number}
         * @constant
         */
        ZERO : WebGLConstants.ZERO,

        /**
         * The blend factor is one.
         *
         * @type {Number}
         * @constant
         */
        ONE : WebGLConstants.ONE,

        /**
         * The blend factor is the source color.
         *
         * @type {Number}
         * @constant
         */
        SOURCE_COLOR : WebGLConstants.SRC_COLOR,

        /**
         * The blend factor is one minus the source color.
         *
         * @type {Number}
         * @constant
         */
        ONE_MINUS_SOURCE_COLOR : WebGLConstants.ONE_MINUS_SRC_COLOR,

        /**
         * The blend factor is the destination color.
         *
         * @type {Number}
         * @constant
         */
        DESTINATION_COLOR : WebGLConstants.DST_COLOR,

        /**
         * The blend factor is one minus the destination color.
         *
         * @type {Number}
         * @constant
         */
        ONE_MINUS_DESTINATION_COLOR : WebGLConstants.ONE_MINUS_DST_COLOR,

        /**
         * The blend factor is the source alpha.
         *
         * @type {Number}
         * @constant
         */
        SOURCE_ALPHA : WebGLConstants.SRC_ALPHA,

        /**
         * The blend factor is one minus the source alpha.
         *
         * @type {Number}
         * @constant
         */
        ONE_MINUS_SOURCE_ALPHA : WebGLConstants.ONE_MINUS_SRC_ALPHA,

        /**
         * The blend factor is the destination alpha.
         *
         * @type {Number}
         * @constant
         */
        DESTINATION_ALPHA : WebGLConstants.DST_ALPHA,

        /**
         * The blend factor is one minus the destination alpha.
         *
         * @type {Number}
         * @constant
         */
        ONE_MINUS_DESTINATION_ALPHA : WebGLConstants.ONE_MINUS_DST_ALPHA,

        /**
         * The blend factor is the constant color.
         *
         * @type {Number}
         * @constant
         */
        CONSTANT_COLOR : WebGLConstants.CONSTANT_COLOR,

        /**
         * The blend factor is one minus the constant color.
         *
         * @type {Number}
         * @constant
         */
        ONE_MINUS_CONSTANT_COLOR : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA,

        /**
         * The blend factor is the constant alpha.
         *
         * @type {Number}
         * @constant
         */
        CONSTANT_ALPHA : WebGLConstants.CONSTANT_ALPHA,

        /**
         * The blend factor is one minus the constant alpha.
         *
         * @type {Number}
         * @constant
         */
        ONE_MINUS_CONSTANT_ALPHA : WebGLConstants.ONE_MINUS_CONSTANT_ALPHA,

        /**
         * The blend factor is the saturated source alpha.
         *
         * @type {Number}
         * @constant
         */
        SOURCE_ALPHA_SATURATE : WebGLConstants.SRC_ALPHA_SATURATE
    };

    return freezeObject(BlendFunction);
});

/*global define*/
define('Scene/BlendingState',[
        '../Core/freezeObject',
        './BlendEquation',
        './BlendFunction'
    ], function(
        freezeObject,
        BlendEquation,
        BlendFunction) {
    'use strict';

    /**
     * The blending state combines {@link BlendEquation} and {@link BlendFunction} and the
     * <code>enabled</code> flag to define the full blending state for combining source and
     * destination fragments when rendering.
     * <p>
     * This is a helper when using custom render states with {@link Appearance#renderState}.
     * </p>
     *
     * @exports BlendingState
     */
    var BlendingState = {
        /**
         * Blending is disabled.
         *
         * @type {Object}
         * @constant
         */
        DISABLED : freezeObject({
            enabled : false
        }),

        /**
         * Blending is enabled using alpha blending, <code>source(source.alpha) + destination(1 - source.alpha)</code>.
         *
         * @type {Object}
         * @constant
         */
        ALPHA_BLEND : freezeObject({
            enabled : true,
            equationRgb : BlendEquation.ADD,
            equationAlpha : BlendEquation.ADD,
            functionSourceRgb : BlendFunction.SOURCE_ALPHA,
            functionSourceAlpha : BlendFunction.SOURCE_ALPHA,
            functionDestinationRgb : BlendFunction.ONE_MINUS_SOURCE_ALPHA,
            functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
        }),

        /**
         * Blending is enabled using alpha blending with premultiplied alpha, <code>source + destination(1 - source.alpha)</code>.
         *
         * @type {Object}
         * @constant
         */
        PRE_MULTIPLIED_ALPHA_BLEND : freezeObject({
            enabled : true,
            equationRgb : BlendEquation.ADD,
            equationAlpha : BlendEquation.ADD,
            functionSourceRgb : BlendFunction.ONE,
            functionSourceAlpha : BlendFunction.ONE,
            functionDestinationRgb : BlendFunction.ONE_MINUS_SOURCE_ALPHA,
            functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
        }),

        /**
         * Blending is enabled using additive blending, <code>source(source.alpha) + destination</code>.
         *
         * @type {Object}
         * @constant
         */
        ADDITIVE_BLEND : freezeObject({
            enabled : true,
            equationRgb : BlendEquation.ADD,
            equationAlpha : BlendEquation.ADD,
            functionSourceRgb : BlendFunction.SOURCE_ALPHA,
            functionSourceAlpha : BlendFunction.SOURCE_ALPHA,
            functionDestinationRgb : BlendFunction.ONE,
            functionDestinationAlpha : BlendFunction.ONE
        })
    };

    return freezeObject(BlendingState);
});

/*global define*/
define('Scene/CullFace',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Determines which triangles, if any, are culled.
     *
     * @exports CullFace
     */
    var CullFace = {
        /**
         * Front-facing triangles are culled.
         *
         * @type {Number}
         * @constant
         */
        FRONT : WebGLConstants.FRONT,

        /**
         * Back-facing triangles are culled.
         *
         * @type {Number}
         * @constant
         */
        BACK : WebGLConstants.BACK,

        /**
         * Both front-facing and back-facing triangles are culled.
         *
         * @type {Number}
         * @constant
         */
        FRONT_AND_BACK : WebGLConstants.FRONT_AND_BACK
    };

    return freezeObject(CullFace);
});

/*global define*/
define('Scene/Appearance',[
        '../Core/clone',
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        './BlendingState',
        './CullFace'
    ], function(
        clone,
        combine,
        defaultValue,
        defined,
        defineProperties,
        BlendingState,
        CullFace) {
    'use strict';

    /**
     * An appearance defines the full GLSL vertex and fragment shaders and the
     * render state used to draw a {@link Primitive}.  All appearances implement
     * this base <code>Appearance</code> interface.
     *
     * @alias Appearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link Appearance#renderState} has alpha blending enabled.
     * @param {Boolean} [options.closed=false] When <code>true</code>, the geometry is expected to be closed so {@link Appearance#renderState} has backface culling enabled.
     * @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @see MaterialAppearance
     * @see EllipsoidSurfaceAppearance
     * @see PerInstanceColorAppearance
     * @see DebugAppearance
     * @see PolylineColorAppearance
     * @see PolylineMaterialAppearance
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Geometry%20and%20Appearances.html|Geometry and Appearances Demo}
     */
    function Appearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The material used to determine the fragment color.  Unlike other {@link Appearance}
         * properties, this is not read-only, so an appearance's material can change on the fly.
         *
         * @type Material
         *
         * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
         */
        this.material = options.material;

        /**
         * When <code>true</code>, the geometry is expected to appear translucent.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.translucent = defaultValue(options.translucent, true);

        this._vertexShaderSource = options.vertexShaderSource;
        this._fragmentShaderSource = options.fragmentShaderSource;
        this._renderState = options.renderState;
        this._closed = defaultValue(options.closed, false);
    }

    defineProperties(Appearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof Appearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.  The full fragment shader
         * source is built procedurally taking into account the {@link Appearance#material}.
         * Use {@link Appearance#getFragmentShaderSource} to get the full source.
         *
         * @memberof Appearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         *
         * @memberof Appearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed.
         *
         * @memberof Appearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        }
    });

    /**
     * Procedurally creates the full GLSL fragment shader source for this appearance
     * taking into account {@link Appearance#fragmentShaderSource} and {@link Appearance#material}.
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    Appearance.prototype.getFragmentShaderSource = function() {
        var parts = [];
        if (this.flat) {
            parts.push('#define FLAT');
        }
        if (this.faceForward) {
            parts.push('#define FACE_FORWARD');
        }
        if (defined(this.material)) {
            parts.push(this.material.shaderSource);
        }
        parts.push(this.fragmentShaderSource);

        return parts.join('\n');
    };

    /**
     * Determines if the geometry is translucent based on {@link Appearance#translucent} and {@link Material#isTranslucent}.
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    Appearance.prototype.isTranslucent = function() {
        return (defined(this.material) && this.material.isTranslucent()) || (!defined(this.material) && this.translucent);
    };

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @returns {Object} The render state.
     */
    Appearance.prototype.getRenderState = function() {
        var translucent = this.isTranslucent();
        var rs = clone(this.renderState, false);
        if (translucent) {
            rs.depthMask = false;
            rs.blending = BlendingState.ALPHA_BLEND;
        } else {
            rs.depthMask = true;
        }
        return rs;
    };

    /**
     * @private
     */
    Appearance.getDefaultRenderState = function(translucent, closed, existing) {
        var rs = {
            depthTest : {
                enabled : true
            }
        };

        if (translucent) {
            rs.depthMask = false;
            rs.blending = BlendingState.ALPHA_BLEND;
        }

        if (closed) {
            rs.cull = {
                enabled : true,
                face : CullFace.BACK
            };
        }

        if (defined(existing)) {
            rs = combine(existing, rs, true);
        }

        return rs;
    };

    return Appearance;
});

/*global define*/
define('Renderer/ContextLimits',[
        '../Core/defineProperties'
    ], function(
        defineProperties) {
    'use strict';

    /**
     * @private
     */
    var ContextLimits = {
        _maximumCombinedTextureImageUnits : 0,
        _maximumCubeMapSize : 0,
        _maximumFragmentUniformVectors : 0,
        _maximumTextureImageUnits : 0,
        _maximumRenderbufferSize : 0,
        _maximumTextureSize : 0,
        _maximumVaryingVectors : 0,
        _maximumVertexAttributes : 0,
        _maximumVertexTextureImageUnits : 0,
        _maximumVertexUniformVectors : 0,
        _minimumAliasedLineWidth : 0,
        _maximumAliasedLineWidth : 0,
        _minimumAliasedPointSize : 0,
        _maximumAliasedPointSize : 0,
        _maximumViewportWidth : 0,
        _maximumViewportHeight : 0,
        _maximumTextureFilterAnisotropy : 0,
        _maximumDrawBuffers : 0,
        _maximumColorAttachments : 0,
        _highpFloatSupported: false,
        _highpIntSupported: false
    };

    defineProperties(ContextLimits, {

        /**
         * The maximum number of texture units that can be used from the vertex and fragment
         * shader with this WebGL implementation.  The minimum is eight.  If both shaders access the
         * same texture unit, this counts as two texture units.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_COMBINED_TEXTURE_IMAGE_UNITS</code>.
         */
        maximumCombinedTextureImageUnits : {
            get: function () {
                return ContextLimits._maximumCombinedTextureImageUnits;
            }
        },

        /**
         * The approximate maximum cube mape width and height supported by this WebGL implementation.
         * The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_CUBE_MAP_TEXTURE_SIZE</code>.
         */
        maximumCubeMapSize : {
            get: function () {
                return ContextLimits._maximumCubeMapSize;
            }
        },

        /**
         * The maximum number of <code>vec4</code>, <code>ivec4</code>, and <code>bvec4</code>
         * uniforms that can be used by a fragment shader with this WebGL implementation.  The minimum is 16.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_FRAGMENT_UNIFORM_VECTORS</code>.
         */
        maximumFragmentUniformVectors : {
            get: function () {
                return ContextLimits._maximumFragmentUniformVectors;
            }
        },

        /**
         * The maximum number of texture units that can be used from the fragment shader with this WebGL implementation.  The minimum is eight.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_TEXTURE_IMAGE_UNITS</code>.
         */
        maximumTextureImageUnits : {
            get: function () {
                return ContextLimits._maximumTextureImageUnits;
            }
        },

        /**
         * The maximum renderbuffer width and height supported by this WebGL implementation.
         * The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_RENDERBUFFER_SIZE</code>.
         */
        maximumRenderbufferSize : {
            get: function () {
                return ContextLimits._maximumRenderbufferSize;
            }
        },

        /**
         * The approximate maximum texture width and height supported by this WebGL implementation.
         * The minimum is 64, but most desktop and laptop implementations will support much larger sizes like 8,192.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_TEXTURE_SIZE</code>.
         */
        maximumTextureSize : {
            get: function () {
                return ContextLimits._maximumTextureSize;
            }
        },

        /**
         * The maximum number of <code>vec4</code> varying variables supported by this WebGL implementation.
         * The minimum is eight.  Matrices and arrays count as multiple <code>vec4</code>s.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_VARYING_VECTORS</code>.
         */
        maximumVaryingVectors : {
            get: function () {
                return ContextLimits._maximumVaryingVectors;
            }
        },

        /**
         * The maximum number of <code>vec4</code> vertex attributes supported by this WebGL implementation.  The minimum is eight.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_VERTEX_ATTRIBS</code>.
         */
        maximumVertexAttributes : {
            get: function () {
                return ContextLimits._maximumVertexAttributes;
            }
        },

        /**
         * The maximum number of texture units that can be used from the vertex shader with this WebGL implementation.
         * The minimum is zero, which means the GL does not support vertex texture fetch.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_VERTEX_TEXTURE_IMAGE_UNITS</code>.
         */
        maximumVertexTextureImageUnits : {
            get: function () {
                return ContextLimits._maximumVertexTextureImageUnits;
            }
        },

        /**
         * The maximum number of <code>vec4</code>, <code>ivec4</code>, and <code>bvec4</code>
         * uniforms that can be used by a vertex shader with this WebGL implementation.  The minimum is 16.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_VERTEX_UNIFORM_VECTORS</code>.
         */
        maximumVertexUniformVectors : {
            get: function () {
                return ContextLimits._maximumVertexUniformVectors;
            }
        },

        /**
         * The minimum aliased line width, in pixels, supported by this WebGL implementation.  It will be at most one.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALIASED_LINE_WIDTH_RANGE</code>.
         */
        minimumAliasedLineWidth : {
            get: function () {
                return ContextLimits._minimumAliasedLineWidth;
            }
        },

        /**
         * The maximum aliased line width, in pixels, supported by this WebGL implementation.  It will be at least one.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALIASED_LINE_WIDTH_RANGE</code>.
         */
        maximumAliasedLineWidth : {
            get: function () {
                return ContextLimits._maximumAliasedLineWidth;
            }
        },

        /**
         * The minimum aliased point size, in pixels, supported by this WebGL implementation.  It will be at most one.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALIASED_POINT_SIZE_RANGE</code>.
         */
        minimumAliasedPointSize : {
            get: function () {
                return ContextLimits._minimumAliasedPointSize;
            }
        },

        /**
         * The maximum aliased point size, in pixels, supported by this WebGL implementation.  It will be at least one.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALIASED_POINT_SIZE_RANGE</code>.
         */
        maximumAliasedPointSize : {
            get: function () {
                return ContextLimits._maximumAliasedPointSize;
            }
        },

        /**
         * The maximum supported width of the viewport.  It will be at least as large as the visible width of the associated canvas.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_VIEWPORT_DIMS</code>.
         */
        maximumViewportWidth : {
            get: function () {
                return ContextLimits._maximumViewportWidth;
            }
        },

        /**
         * The maximum supported height of the viewport.  It will be at least as large as the visible height of the associated canvas.
         * @memberof ContextLimits
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>MAX_VIEWPORT_DIMS</code>.
         */
        maximumViewportHeight : {
            get: function () {
                return ContextLimits._maximumViewportHeight;
            }
        },

        /**
         * The maximum degree of anisotropy for texture filtering
         * @memberof ContextLimits
         * @type {Number}
         */
        maximumTextureFilterAnisotropy : {
            get: function () {
                return ContextLimits._maximumTextureFilterAnisotropy;
            }
        },

        /**
         * The maximum number of simultaneous outputs that may be written in a fragment shader.
         * @memberof ContextLimits
         * @type {Number}
         */
        maximumDrawBuffers : {
            get: function () {
                return ContextLimits._maximumDrawBuffers;
            }
        },

        /**
         * The maximum number of color attachments supported.
         * @memberof ContextLimits
         * @type {Number}
         */
        maximumColorAttachments : {
            get: function () {
                return ContextLimits._maximumColorAttachments;
            }
        },

        /**
         * High precision float supported (<code>highp</code>) in fragment shaders.
         * @memberof ContextLimits
         * @type {Boolean}
         */
        highpFloatSupported : {
            get: function () {
                return ContextLimits._highpFloatSupported;
            }
        },

        /**
         * High precision int supported (<code>highp</code>) in fragment shaders.
         * @memberof ContextLimits
         * @type {Boolean}
         */
        highpIntSupported : {
            get: function () {
                return ContextLimits._highpIntSupported;
            }
        }

    });

    return ContextLimits;
});

/*global define*/
define('Renderer/PixelDatatype',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var PixelDatatype = {
        UNSIGNED_BYTE : WebGLConstants.UNSIGNED_BYTE,
        UNSIGNED_SHORT : WebGLConstants.UNSIGNED_SHORT,
        UNSIGNED_INT : WebGLConstants.UNSIGNED_INT,
        FLOAT : WebGLConstants.FLOAT,
        UNSIGNED_INT_24_8 : WebGLConstants.UNSIGNED_INT_24_8,
        UNSIGNED_SHORT_4_4_4_4 : WebGLConstants.UNSIGNED_SHORT_4_4_4_4,
        UNSIGNED_SHORT_5_5_5_1 : WebGLConstants.UNSIGNED_SHORT_5_5_5_1,
        UNSIGNED_SHORT_5_6_5 : WebGLConstants.UNSIGNED_SHORT_5_6_5,

        validate : function(pixelDatatype) {
            return ((pixelDatatype === PixelDatatype.UNSIGNED_BYTE) ||
                    (pixelDatatype === PixelDatatype.UNSIGNED_SHORT) ||
                    (pixelDatatype === PixelDatatype.UNSIGNED_INT) ||
                    (pixelDatatype === PixelDatatype.FLOAT) ||
                    (pixelDatatype === PixelDatatype.UNSIGNED_INT_24_8) ||
                    (pixelDatatype === PixelDatatype.UNSIGNED_SHORT_4_4_4_4) ||
                    (pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_5_5_1) ||
                    (pixelDatatype === PixelDatatype.UNSIGNED_SHORT_5_6_5));
        }
    };

    return freezeObject(PixelDatatype);
});

/*global define*/
define('Renderer/CubeMapFace',[
        '../Core/defaultValue',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        './PixelDatatype'
    ], function(
        defaultValue,
        defineProperties,
        DeveloperError,
        PixelDatatype) {
    'use strict';

    /**
     * @private
     */
    function CubeMapFace(gl, texture, textureTarget, targetFace, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY) {
        this._gl = gl;
        this._texture = texture;
        this._textureTarget = textureTarget;
        this._targetFace = targetFace;
        this._pixelFormat = pixelFormat;
        this._pixelDatatype = pixelDatatype;
        this._size = size;
        this._preMultiplyAlpha = preMultiplyAlpha;
        this._flipY = flipY;
    }

    defineProperties(CubeMapFace.prototype, {
        pixelFormat : {
            get : function() {
                return this._pixelFormat;
            }
        },
        pixelDatatype : {
            get : function() {
                return this._pixelDatatype;
            }
        },
        _target : {
            get : function() {
                return this._targetFace;
            }
        }
    });

    /**
     * Copies texels from the source to the cubemap's face.
     *
     * @param {Object} source The source ImageData, HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, or an object with a width, height, and typed array as shown in the example.
     * @param {Number} [xOffset=0] An offset in the x direction in the cubemap where copying begins.
     * @param {Number} [yOffset=0] An offset in the y direction in the cubemap where copying begins.
     *
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
     * @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
     *
     * @example
     * // Create a cubemap with 1x1 faces, and make the +x face red.
     * var cubeMap = new CubeMap({
     *   context : context
     *   width : 1,
     *   height : 1
     * });
     * cubeMap.positiveX.copyFrom({
     *   width : 1,
     *   height : 1,
     *   arrayBufferView : new Uint8Array([255, 0, 0, 255])
     * });
     */
    CubeMapFace.prototype.copyFrom = function(source, xOffset, yOffset) {
        xOffset = defaultValue(xOffset, 0);
        yOffset = defaultValue(yOffset, 0);

                if (!source) {
            throw new DeveloperError('source is required.');
        }
        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }
        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }
        if (xOffset + source.width > this._size) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
        }
        if (yOffset + source.height > this._size) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
        }
        
        var gl = this._gl;
        var target = this._textureTarget;

        // TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this._flipY);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);

        if (source.arrayBufferView) {
            gl.texSubImage2D(this._targetFace, 0, xOffset, yOffset, source.width, source.height, this._pixelFormat, this._pixelDatatype, source.arrayBufferView);
        } else {
            gl.texSubImage2D(this._targetFace, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
        }

        gl.bindTexture(target, null);
    };

    /**
     * Copies texels from the framebuffer to the cubemap's face.
     *
     * @param {Number} [xOffset=0] An offset in the x direction in the cubemap where copying begins.
     * @param {Number} [yOffset=0] An offset in the y direction in the cubemap where copying begins.
     * @param {Number} [framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
     * @param {Number} [framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
     * @param {Number} [width=CubeMap's width] The width of the subimage to copy.
     * @param {Number} [height=CubeMap's height] The height of the subimage to copy.
     *
     * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
     * @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
     * @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
     *
     * @example
     * // Copy the framebuffer contents to the +x cube map face.
     * cubeMap.positiveX.copyFromFramebuffer();
     */
    CubeMapFace.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
        xOffset = defaultValue(xOffset, 0);
        yOffset = defaultValue(yOffset, 0);
        framebufferXOffset = defaultValue(framebufferXOffset, 0);
        framebufferYOffset = defaultValue(framebufferYOffset, 0);
        width = defaultValue(width, this._size);
        height = defaultValue(height, this._size);

                if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }
        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }
        if (framebufferXOffset < 0) {
            throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
        }
        if (framebufferYOffset < 0) {
            throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
        }
        if (xOffset + width > this._size) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
        }
        if (yOffset + height > this._size) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
        }
        if (this._pixelDatatype === PixelDatatype.FLOAT) {
            throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.');
        }
        
        var gl = this._gl;
        var target = this._textureTarget;

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.copyTexSubImage2D(this._targetFace, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
        gl.bindTexture(target, null);
    };

    return CubeMapFace;
});

/*global define*/
define('Renderer/MipmapHint',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var MipmapHint = {
        DONT_CARE : WebGLConstants.DONT_CARE,
        FASTEST : WebGLConstants.FASTEST,
        NICEST : WebGLConstants.NICEST,

        validate : function(mipmapHint) {
            return ((mipmapHint === MipmapHint.DONT_CARE) ||
                    (mipmapHint === MipmapHint.FASTEST) ||
                    (mipmapHint === MipmapHint.NICEST));
        }
    };

    return freezeObject(MipmapHint);
});

/*global define*/
define('Renderer/TextureMagnificationFilter',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var TextureMagnificationFilter = {
        NEAREST : WebGLConstants.NEAREST,
        LINEAR : WebGLConstants.LINEAR,

        validate : function(textureMagnificationFilter) {
            return ((textureMagnificationFilter === TextureMagnificationFilter.NEAREST) ||
                    (textureMagnificationFilter === TextureMagnificationFilter.LINEAR));
        }
    };

    return freezeObject(TextureMagnificationFilter);
});

/*global define*/
define('Renderer/TextureMinificationFilter',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var TextureMinificationFilter = {
        NEAREST : WebGLConstants.NEAREST,
        LINEAR : WebGLConstants.LINEAR,
        NEAREST_MIPMAP_NEAREST : WebGLConstants.NEAREST_MIPMAP_NEAREST,
        LINEAR_MIPMAP_NEAREST : WebGLConstants.LINEAR_MIPMAP_NEAREST,
        NEAREST_MIPMAP_LINEAR : WebGLConstants.NEAREST_MIPMAP_LINEAR,
        LINEAR_MIPMAP_LINEAR : WebGLConstants.LINEAR_MIPMAP_LINEAR,

        validate : function(textureMinificationFilter) {
            return ((textureMinificationFilter === TextureMinificationFilter.NEAREST) ||
                    (textureMinificationFilter === TextureMinificationFilter.LINEAR) ||
                    (textureMinificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
                    (textureMinificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
                    (textureMinificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
                    (textureMinificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR));
        }
    };

    return freezeObject(TextureMinificationFilter);
});

/*global define*/
define('Renderer/TextureWrap',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var TextureWrap = {
        CLAMP_TO_EDGE : WebGLConstants.CLAMP_TO_EDGE,
        REPEAT : WebGLConstants.REPEAT,
        MIRRORED_REPEAT : WebGLConstants.MIRRORED_REPEAT,

        validate : function(textureWrap) {
            return ((textureWrap === TextureWrap.CLAMP_TO_EDGE) ||
                    (textureWrap === TextureWrap.REPEAT) ||
                    (textureWrap === TextureWrap.MIRRORED_REPEAT));
        }
    };

    return freezeObject(TextureWrap);
});

/*global define*/
define('Renderer/Sampler',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        './TextureMagnificationFilter',
        './TextureMinificationFilter',
        './TextureWrap'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap) {
    'use strict';

    /**
     * @private
     */
    function Sampler(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var wrapS = defaultValue(options.wrapS, TextureWrap.CLAMP_TO_EDGE);
        var wrapT = defaultValue(options.wrapT, TextureWrap.CLAMP_TO_EDGE);
        var minificationFilter = defaultValue(options.minificationFilter, TextureMinificationFilter.LINEAR);
        var magnificationFilter = defaultValue(options.magnificationFilter, TextureMagnificationFilter.LINEAR);
        var maximumAnisotropy = (defined(options.maximumAnisotropy)) ? options.maximumAnisotropy : 1.0;

                if (!TextureWrap.validate(wrapS)) {
            throw new DeveloperError('Invalid sampler.wrapS.');
        }

        if (!TextureWrap.validate(wrapT)) {
            throw new DeveloperError('Invalid sampler.wrapT.');
        }

        if (!TextureMinificationFilter.validate(minificationFilter)) {
            throw new DeveloperError('Invalid sampler.minificationFilter.');
        }

        if (!TextureMagnificationFilter.validate(magnificationFilter)) {
            throw new DeveloperError('Invalid sampler.magnificationFilter.');
        }

        if (maximumAnisotropy < 1.0) {
            throw new DeveloperError('sampler.maximumAnisotropy must be greater than or equal to one.');
        }
        
        this._wrapS = wrapS;
        this._wrapT = wrapT;
        this._minificationFilter = minificationFilter;
        this._magnificationFilter = magnificationFilter;
        this._maximumAnisotropy = maximumAnisotropy;
    }

    defineProperties(Sampler.prototype, {
        wrapS : {
            get : function() {
                return this._wrapS;
            }
        },
        wrapT : {
            get : function() {
                return this._wrapT;
            }
        },
        minificationFilter : {
            get : function() {
                return this._minificationFilter;
            }
        },
        magnificationFilter : {
            get : function() {
                return this._magnificationFilter;
            }
        },
        maximumAnisotropy : {
            get : function() {
                return this._maximumAnisotropy;
            }
        }
    });

    return Sampler;
});

/*global define*/
define('Renderer/CubeMap',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Math',
        '../Core/PixelFormat',
        './ContextLimits',
        './CubeMapFace',
        './MipmapHint',
        './PixelDatatype',
        './Sampler',
        './TextureMagnificationFilter',
        './TextureMinificationFilter'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        CesiumMath,
        PixelFormat,
        ContextLimits,
        CubeMapFace,
        MipmapHint,
        PixelDatatype,
        Sampler,
        TextureMagnificationFilter,
        TextureMinificationFilter) {
    'use strict';

    function CubeMap(options) {

        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        var context = options.context;
        var source = options.source;
        var width;
        var height;

        if (defined(source)) {
            var faces = [source.positiveX, source.negativeX, source.positiveY, source.negativeY, source.positiveZ, source.negativeZ];

                        if (!faces[0] || !faces[1] || !faces[2] || !faces[3] || !faces[4] || !faces[5]) {
                throw new DeveloperError('options.source requires positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ faces.');
            }
            
            width = faces[0].width;
            height = faces[0].height;

                        for ( var i = 1; i < 6; ++i) {
                if ((Number(faces[i].width) !== width) || (Number(faces[i].height) !== height)) {
                    throw new DeveloperError('Each face in options.source must have the same width and height.');
                }
            }
                    } else {
            width = options.width;
            height = options.height;
        }

        var size = width;
        var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
        var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);

                if (!defined(width) || !defined(height)) {
            throw new DeveloperError('options requires a source field to create an initialized cube map or width and height fields to create a blank cube map.');
        }

        if (width !== height) {
            throw new DeveloperError('Width must equal height.');
        }

        if (size <= 0) {
            throw new DeveloperError('Width and height must be greater than zero.');
        }

        if (size > ContextLimits.maximumCubeMapSize) {
            throw new DeveloperError('Width and height must be less than or equal to the maximum cube map size (' + ContextLimits.maximumCubeMapSize + ').  Check maximumCubeMapSize.');
        }

        if (!PixelFormat.validate(pixelFormat)) {
            throw new DeveloperError('Invalid options.pixelFormat.');
        }

        if (PixelFormat.isDepthFormat(pixelFormat)) {
            throw new DeveloperError('options.pixelFormat cannot be DEPTH_COMPONENT or DEPTH_STENCIL.');
        }

        if (!PixelDatatype.validate(pixelDatatype)) {
            throw new DeveloperError('Invalid options.pixelDatatype.');
        }

        if ((pixelDatatype === PixelDatatype.FLOAT) && !context.floatingPointTexture) {
            throw new DeveloperError('When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension.');
        }
        
        // Use premultiplied alpha for opaque textures should perform better on Chrome:
        // http://media.tojicode.com/webglCamp4/#20
        var preMultiplyAlpha = options.preMultiplyAlpha || ((pixelFormat === PixelFormat.RGB) || (pixelFormat === PixelFormat.LUMINANCE));
        var flipY = defaultValue(options.flipY, true);

        var gl = context._gl;
        var textureTarget = gl.TEXTURE_CUBE_MAP;
        var texture = gl.createTexture();

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(textureTarget, texture);

        function createFace(target, sourceFace) {
            if (sourceFace.arrayBufferView) {
                gl.texImage2D(target, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, sourceFace.arrayBufferView);
            } else {
                gl.texImage2D(target, 0, pixelFormat, pixelFormat, pixelDatatype, sourceFace);
            }
        }

        if (defined(source)) {
            // TODO: _gl.pixelStorei(_gl._UNPACK_ALIGNMENT, 4);
            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);

            createFace(gl.TEXTURE_CUBE_MAP_POSITIVE_X, source.positiveX);
            createFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, source.negativeX);
            createFace(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, source.positiveY);
            createFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, source.negativeY);
            createFace(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, source.positiveZ);
            createFace(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, source.negativeZ);
        } else {
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
            gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, pixelFormat, size, size, 0, pixelFormat, pixelDatatype, null);
        }
        gl.bindTexture(textureTarget, null);

        this._gl = gl;
        this._textureFilterAnisotropic = context._textureFilterAnisotropic;
        this._textureTarget = textureTarget;
        this._texture = texture;
        this._pixelFormat = pixelFormat;
        this._pixelDatatype = pixelDatatype;
        this._size = size;
        this._preMultiplyAlpha = preMultiplyAlpha;
        this._flipY = flipY;
        this._sampler = undefined;

        this._positiveX = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_X, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
        this._negativeX = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
        this._positiveY = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
        this._negativeY = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
        this._positiveZ = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_POSITIVE_Z, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);
        this._negativeZ = new CubeMapFace(gl, texture, textureTarget, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, pixelFormat, pixelDatatype, size, preMultiplyAlpha, flipY);

        this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
    }

    defineProperties(CubeMap.prototype, {
        positiveX : {
            get : function() {
                return this._positiveX;
            }
        },
        negativeX : {
            get : function() {
                return this._negativeX;
            }
        },
        positiveY : {
            get : function() {
                return this._positiveY;
            }
        },
        negativeY : {
            get : function() {
                return this._negativeY;
            }
        },
        positiveZ : {
            get : function() {
                return this._positiveZ;
            }
        },
        negativeZ : {
            get : function() {
                return this._negativeZ;
            }
        },
        sampler : {
            get : function() {
                return this._sampler;
            },
            set : function(sampler) {
                var minificationFilter = sampler.minificationFilter;
                var magnificationFilter = sampler.magnificationFilter;

                var mipmap =
                    (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
                    (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
                    (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
                    (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);

                // float textures only support nearest filtering, so override the sampler's settings
                if (this._pixelDatatype === PixelDatatype.FLOAT) {
                    minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST;
                    magnificationFilter = TextureMagnificationFilter.NEAREST;
                }

                var gl = this._gl;
                var target = this._textureTarget;

                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(target, this._texture);
                gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
                gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
                gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
                gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
                if (defined(this._textureFilterAnisotropic)) {
                    gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
                }
                gl.bindTexture(target, null);

                this._sampler = sampler;
            }
        },
        pixelFormat: {
            get : function() {
                return this._pixelFormat;
            }
        },
        pixelDatatype : {
            get : function() {
                return this._pixelDatatype;
            }
        },
        width : {
            get : function() {
                return this._size;
            }
        },
        height: {
            get : function() {
                return this._size;
            }
        },
        preMultiplyAlpha : {
            get : function() {
                return this._preMultiplyAlpha;
            }
        },
        flipY : {
            get : function() {
                return this._flipY;
            }
        },

        _target : {
            get : function() {
                return this._textureTarget;
            }
        }
    });

    /**
     * Generates a complete mipmap chain for each cubemap face.
     *
     * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] A performance vs. quality hint.
     *
     * @exception {DeveloperError} hint is invalid.
     * @exception {DeveloperError} This CubeMap's width must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This CubeMap's height must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This CubeMap was destroyed, i.e., destroy() was called.
     *
     * @example
     * // Generate mipmaps, and then set the sampler so mipmaps are used for
     * // minification when the cube map is sampled.
     * cubeMap.generateMipmap();
     * cubeMap.sampler = new Sampler({
     *   minificationFilter : Cesium.TextureMinificationFilter.NEAREST_MIPMAP_LINEAR
     * });
     */
    CubeMap.prototype.generateMipmap = function(hint) {
        hint = defaultValue(hint, MipmapHint.DONT_CARE);

                if ((this._size > 1) && !CesiumMath.isPowerOfTwo(this._size)) {
            throw new DeveloperError('width and height must be a power of two to call generateMipmap().');
        }
        if (!MipmapHint.validate(hint)) {
            throw new DeveloperError('hint is invalid.');
        }
        
        var gl = this._gl;
        var target = this._textureTarget;
        gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.generateMipmap(target);
        gl.bindTexture(target, null);
    };

    CubeMap.prototype.isDestroyed = function() {
        return false;
    };

    CubeMap.prototype.destroy = function() {
        this._gl.deleteTexture(this._texture);
        this._positiveX = destroyObject(this._positiveX);
        this._negativeX = destroyObject(this._negativeX);
        this._positiveY = destroyObject(this._positiveY);
        this._negativeY = destroyObject(this._negativeY);
        this._positiveZ = destroyObject(this._positiveZ);
        this._negativeZ = destroyObject(this._negativeZ);
        return destroyObject(this);
    };

    return CubeMap;
});

/*global define*/
define('Renderer/Texture',[
        '../Core/Cartesian2',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Math',
        '../Core/PixelFormat',
        '../Core/WebGLConstants',
        './ContextLimits',
        './MipmapHint',
        './PixelDatatype',
        './Sampler',
        './TextureMagnificationFilter',
        './TextureMinificationFilter'
    ], function(
        Cartesian2,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        CesiumMath,
        PixelFormat,
        WebGLConstants,
        ContextLimits,
        MipmapHint,
        PixelDatatype,
        Sampler,
        TextureMagnificationFilter,
        TextureMinificationFilter) {
    'use strict';

    function Texture(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        var context = options.context;
        var width = options.width;
        var height = options.height;
        var source = options.source;

        if (defined(source)) {
            if (!defined(width)) {
                width = defaultValue(source.videoWidth, source.width);
            }
            if (!defined(height)) {
                height = defaultValue(source.videoHeight, source.height);
            }
        }

        var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
        var pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);
        var internalFormat = pixelFormat;

        var isCompressed = PixelFormat.isCompressedFormat(internalFormat);

        if (context.webgl2) {
            if (pixelFormat === PixelFormat.DEPTH_STENCIL) {
                internalFormat = WebGLConstants.DEPTH24_STENCIL8;
            } else if (pixelFormat === PixelFormat.DEPTH_COMPONENT) {
                if (pixelDatatype === PixelDatatype.UNSIGNED_SHORT) {
                    internalFormat = WebGLConstants.DEPTH_COMPONENT16;
                } else if (pixelDatatype === PixelDatatype.UNSIGNED_INT) {
                    internalFormat = WebGLConstants.DEPTH_COMPONENT24;
                }
            }
        }

                if (!defined(width) || !defined(height)) {
            throw new DeveloperError('options requires a source field to create an initialized texture or width and height fields to create a blank texture.');
        }

        if (width <= 0) {
            throw new DeveloperError('Width must be greater than zero.');
        }

        if (width > ContextLimits.maximumTextureSize) {
            throw new DeveloperError('Width must be less than or equal to the maximum texture size (' + ContextLimits.maximumTextureSize + ').  Check maximumTextureSize.');
        }

        if (height <= 0) {
            throw new DeveloperError('Height must be greater than zero.');
        }

        if (height > ContextLimits.maximumTextureSize) {
            throw new DeveloperError('Height must be less than or equal to the maximum texture size (' + ContextLimits.maximumTextureSize + ').  Check maximumTextureSize.');
        }

        if (!PixelFormat.validate(pixelFormat)) {
            throw new DeveloperError('Invalid options.pixelFormat.');
        }

        if (!isCompressed && !PixelDatatype.validate(pixelDatatype)) {
            throw new DeveloperError('Invalid options.pixelDatatype.');
        }

        if ((pixelFormat === PixelFormat.DEPTH_COMPONENT) &&
            ((pixelDatatype !== PixelDatatype.UNSIGNED_SHORT) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT))) {
            throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT, options.pixelDatatype must be UNSIGNED_SHORT or UNSIGNED_INT.');
        }

        if ((pixelFormat === PixelFormat.DEPTH_STENCIL) && (pixelDatatype !== PixelDatatype.UNSIGNED_INT_24_8)) {
            throw new DeveloperError('When options.pixelFormat is DEPTH_STENCIL, options.pixelDatatype must be UNSIGNED_INT_24_8.');
        }

        if ((pixelDatatype === PixelDatatype.FLOAT) && !context.floatingPointTexture) {
            throw new DeveloperError('When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension.  Check context.floatingPointTexture.');
        }

        if (PixelFormat.isDepthFormat(pixelFormat)) {
            if (defined(source)) {
                throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, source cannot be provided.');
            }

            if (!context.depthTexture) {
                throw new DeveloperError('When options.pixelFormat is DEPTH_COMPONENT or DEPTH_STENCIL, this WebGL implementation must support WEBGL_depth_texture.  Check context.depthTexture.');
            }
        }

        if (isCompressed) {
            if (!defined(source) || !defined(source.arrayBufferView)) {
                throw new DeveloperError('When options.pixelFormat is compressed, options.source.arrayBufferView must be defined.');
            }

            if (PixelFormat.isDXTFormat(internalFormat) && !context.s3tc) {
                throw new DeveloperError('When options.pixelFormat is S3TC compressed, this WebGL implementation must support the WEBGL_texture_compression_s3tc extension. Check context.s3tc.');
            } else if (PixelFormat.isPVRTCFormat(internalFormat) && !context.pvrtc) {
                throw new DeveloperError('When options.pixelFormat is PVRTC compressed, this WebGL implementation must support the WEBGL_texture_compression_pvrtc extension. Check context.pvrtc.');
            } else if (PixelFormat.isETC1Format(internalFormat) && !context.etc1) {
                throw new DeveloperError('When options.pixelFormat is ETC1 compressed, this WebGL implementation must support the WEBGL_texture_compression_etc1 extension. Check context.etc1.');
            }

            if (PixelFormat.compressedTextureSize(internalFormat, width, height) !== source.arrayBufferView.byteLength) {
                throw new DeveloperError('The byte length of the array buffer is invalid for the compressed texture with the given width and height.');
            }
        }
        
        // Use premultiplied alpha for opaque textures should perform better on Chrome:
        // http://media.tojicode.com/webglCamp4/#20
        var preMultiplyAlpha = options.preMultiplyAlpha || pixelFormat === PixelFormat.RGB || pixelFormat === PixelFormat.LUMINANCE;
        var flipY = defaultValue(options.flipY, true);

        var gl = context._gl;
        var textureTarget = gl.TEXTURE_2D;
        var texture = gl.createTexture();

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(textureTarget, texture);

        if (defined(source)) {
            // TODO: _gl.pixelStorei(_gl._UNPACK_ALIGNMENT, 4);
            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);

            if (defined(source.arrayBufferView)) {
                // Source: typed array
                if (isCompressed) {
                    gl.compressedTexImage2D(textureTarget, 0, internalFormat, width, height, 0, source.arrayBufferView);
                } else {
                    gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, source.arrayBufferView);
                }
            } else if (defined(source.framebuffer)) {
                // Source: framebuffer
                if (source.framebuffer !== context.defaultFramebuffer) {
                    source.framebuffer._bind();
                }

                gl.copyTexImage2D(textureTarget, 0, internalFormat, source.xOffset, source.yOffset, width, height, 0);

                if (source.framebuffer !== context.defaultFramebuffer) {
                    source.framebuffer._unBind();
                }
            } else {
                // Source: ImageData, HTMLImageElement, HTMLCanvasElement, or HTMLVideoElement
                gl.texImage2D(textureTarget, 0, internalFormat, pixelFormat, pixelDatatype, source);
            }
        } else {
            gl.texImage2D(textureTarget, 0, internalFormat, width, height, 0, pixelFormat, pixelDatatype, null);
        }
        gl.bindTexture(textureTarget, null);

        this._context = context;
        this._textureFilterAnisotropic = context._textureFilterAnisotropic;
        this._textureTarget = textureTarget;
        this._texture = texture;
        this._pixelFormat = pixelFormat;
        this._pixelDatatype = pixelDatatype;
        this._width = width;
        this._height = height;
        this._dimensions = new Cartesian2(width, height);
        this._preMultiplyAlpha = preMultiplyAlpha;
        this._flipY = flipY;
        this._sampler = undefined;

        this.sampler = defined(options.sampler) ? options.sampler : new Sampler();
    }

    /**
     * Creates a texture, and copies a subimage of the framebuffer to it.  When called without arguments,
     * the texture is the same width and height as the framebuffer and contains its contents.
     *
     * @param {Object} options Object with the following properties:
     * @param {Context} options.context The context in which the Texture gets created.
     * @param {PixelFormat} [options.pixelFormat=PixelFormat.RGB] The texture's internal pixel format.
     * @param {Number} [options.framebufferXOffset=0] An offset in the x direction in the framebuffer where copying begins from.
     * @param {Number} [options.framebufferYOffset=0] An offset in the y direction in the framebuffer where copying begins from.
     * @param {Number} [options.width=canvas.clientWidth] The width of the texture in texels.
     * @param {Number} [options.height=canvas.clientHeight] The height of the texture in texels.
     * @param {Framebuffer} [options.framebuffer=defaultFramebuffer] The framebuffer from which to create the texture.  If this
     *        parameter is not specified, the default framebuffer is used.
     * @returns {Texture} A texture with contents from the framebuffer.
     *
     * @exception {DeveloperError} Invalid pixelFormat.
     * @exception {DeveloperError} pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format.
     * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferXOffset + width must be less than or equal to canvas.clientWidth.
     * @exception {DeveloperError} framebufferYOffset + height must be less than or equal to canvas.clientHeight.
     *
     *
     * @example
     * // Create a texture with the contents of the framebuffer.
     * var t = Texture.fromFramebuffer({
     *     context : context
     * });
     *
     * @see Sampler
     *
     * @private
     */
    Texture.fromFramebuffer = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        var context = options.context;
        var gl = context._gl;

        var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGB);
        var framebufferXOffset = defaultValue(options.framebufferXOffset, 0);
        var framebufferYOffset = defaultValue(options.framebufferYOffset, 0);
        var width = defaultValue(options.width, gl.drawingBufferWidth);
        var height = defaultValue(options.height, gl.drawingBufferHeight);
        var framebuffer = options.framebuffer;

                if (!defined(options.context)) {
            throw new DeveloperError('context is required.');
        }
        if (!PixelFormat.validate(pixelFormat)) {
            throw new DeveloperError('Invalid pixelFormat.');
        }
        if (PixelFormat.isDepthFormat(pixelFormat) || PixelFormat.isCompressedFormat(pixelFormat)) {
            throw new DeveloperError('pixelFormat cannot be DEPTH_COMPONENT, DEPTH_STENCIL or a compressed format.');
        }
        if (framebufferXOffset < 0) {
            throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
        }
        if (framebufferYOffset < 0) {
            throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
        }
        if (framebufferXOffset + width > gl.drawingBufferWidth) {
            throw new DeveloperError('framebufferXOffset + width must be less than or equal to drawingBufferWidth');
        }
        if (framebufferYOffset + height > gl.drawingBufferHeight) {
            throw new DeveloperError('framebufferYOffset + height must be less than or equal to drawingBufferHeight.');
        }
        
        var texture = new Texture({
            context : context,
            width : width,
            height : height,
            pixelFormat : pixelFormat,
            source : {
                framebuffer : defined(framebuffer) ? framebuffer : context.defaultFramebuffer,
                xOffset : framebufferXOffset,
                yOffset : framebufferYOffset,
                width : width,
                height : height
            }
        });

        return texture;
    };

    defineProperties(Texture.prototype, {
        /**
         * The sampler to use when sampling this texture.
         * Create a sampler by calling {@link Sampler}.  If this
         * parameter is not specified, a default sampler is used.  The default sampler clamps texture
         * coordinates in both directions, uses linear filtering for both magnification and minifcation,
         * and uses a maximum anisotropy of 1.0.
         * @memberof Texture.prototype
         * @type {Object}
         */
        sampler : {
            get : function() {
                return this._sampler;
            },
            set : function(sampler) {
                var minificationFilter = sampler.minificationFilter;
                var magnificationFilter = sampler.magnificationFilter;

                var mipmap =
                    (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
                    (minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
                    (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
                    (minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);

                // float textures only support nearest filtering, so override the sampler's settings
                if (this._pixelDatatype === PixelDatatype.FLOAT) {
                    minificationFilter = mipmap ? TextureMinificationFilter.NEAREST_MIPMAP_NEAREST : TextureMinificationFilter.NEAREST;
                    magnificationFilter = TextureMagnificationFilter.NEAREST;
                }

                var gl = this._context._gl;
                var target = this._textureTarget;

                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(target, this._texture);
                gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minificationFilter);
                gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magnificationFilter);
                gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
                gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);
                if (defined(this._textureFilterAnisotropic)) {
                    gl.texParameteri(target, this._textureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maximumAnisotropy);
                }
                gl.bindTexture(target, null);

                this._sampler = sampler;
            }
        },
        pixelFormat : {
            get : function() {
                return this._pixelFormat;
            }
        },
        pixelDatatype : {
            get : function() {
                return this._pixelDatatype;
            }
        },
        dimensions : {
            get : function() {
                return this._dimensions;
            }
        },
        preMultiplyAlpha : {
            get : function() {
                return this._preMultiplyAlpha;
            }
        },
        flipY : {
            get : function() {
                return this._flipY;
            }
        },
        width : {
            get : function() {
                return this._width;
            }
        },
        height : {
            get : function() {
                return this._height;
            }
        },
        _target : {
            get : function() {
                return this._textureTarget;
            }
        }
    });

    /**
     * Copy new image data into this texture, from a source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video}.
     * or an object with width, height, and arrayBufferView properties.
     *
     * @param {Object} source The source {@link ImageData}, {@link Image}, {@link Canvas}, or {@link Video},
     *                        or an object with width, height, and arrayBufferView properties.
     * @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
     * @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
     *
     * @exception {DeveloperError} Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + source.width must be less than or equal to width.
     * @exception {DeveloperError} yOffset + source.height must be less than or equal to height.
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     *
     * @example
     * texture.copyFrom({
     *   width : 1,
     *   height : 1,
     *   arrayBufferView : new Uint8Array([255, 0, 0, 255])
     * });
     */
    Texture.prototype.copyFrom = function(source, xOffset, yOffset) {
        xOffset = defaultValue(xOffset, 0);
        yOffset = defaultValue(yOffset, 0);

                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFrom when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }
        if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFrom with a compressed texture pixel format.');
        }
        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }
        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }
        if (xOffset + source.width > this._width) {
            throw new DeveloperError('xOffset + source.width must be less than or equal to width.');
        }
        if (yOffset + source.height > this._height) {
            throw new DeveloperError('yOffset + source.height must be less than or equal to height.');
        }
        
        var gl = this._context._gl;
        var target = this._textureTarget;

        // TODO: gl.pixelStorei(gl._UNPACK_ALIGNMENT, 4);
        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._preMultiplyAlpha);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this._flipY);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);

        if (source.arrayBufferView) {
            gl.texSubImage2D(target, 0, xOffset, yOffset, source.width, source.height, this._pixelFormat, this._pixelDatatype, source.arrayBufferView);
        } else {
            gl.texSubImage2D(target, 0, xOffset, yOffset, this._pixelFormat, this._pixelDatatype, source);
        }

        gl.bindTexture(target, null);
    };

    /**
     * @param {Number} [xOffset=0] The offset in the x direction within the texture to copy into.
     * @param {Number} [yOffset=0] The offset in the y direction within the texture to copy into.
     * @param {Number} [framebufferXOffset=0] optional
     * @param {Number} [framebufferYOffset=0] optional
     * @param {Number} [width=width] optional
     * @param {Number} [height=height] optional
     *
     * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.
     * @exception {DeveloperError} Cannot call copyFrom with a compressed texture pixel format.
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     * @exception {DeveloperError} xOffset must be greater than or equal to zero.
     * @exception {DeveloperError} yOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferXOffset must be greater than or equal to zero.
     * @exception {DeveloperError} framebufferYOffset must be greater than or equal to zero.
     * @exception {DeveloperError} xOffset + width must be less than or equal to width.
     * @exception {DeveloperError} yOffset + height must be less than or equal to height.
     */
    Texture.prototype.copyFromFramebuffer = function(xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height) {
        xOffset = defaultValue(xOffset, 0);
        yOffset = defaultValue(yOffset, 0);
        framebufferXOffset = defaultValue(framebufferXOffset, 0);
        framebufferYOffset = defaultValue(framebufferYOffset, 0);
        width = defaultValue(width, this._width);
        height = defaultValue(height, this._height);

                if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }
        if (this._pixelDatatype === PixelDatatype.FLOAT) {
            throw new DeveloperError('Cannot call copyFromFramebuffer when the texture pixel data type is FLOAT.');
        }
        if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call copyFrom with a compressed texture pixel format.');
        }
        if (xOffset < 0) {
            throw new DeveloperError('xOffset must be greater than or equal to zero.');
        }
        if (yOffset < 0) {
            throw new DeveloperError('yOffset must be greater than or equal to zero.');
        }
        if (framebufferXOffset < 0) {
            throw new DeveloperError('framebufferXOffset must be greater than or equal to zero.');
        }
        if (framebufferYOffset < 0) {
            throw new DeveloperError('framebufferYOffset must be greater than or equal to zero.');
        }
        if (xOffset + width > this._width) {
            throw new DeveloperError('xOffset + width must be less than or equal to width.');
        }
        if (yOffset + height > this._height) {
            throw new DeveloperError('yOffset + height must be less than or equal to height.');
        }
        
        var gl = this._context._gl;
        var target = this._textureTarget;

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.copyTexSubImage2D(target, 0, xOffset, yOffset, framebufferXOffset, framebufferYOffset, width, height);
        gl.bindTexture(target, null);
    };

    /**
     * @param {MipmapHint} [hint=MipmapHint.DONT_CARE] optional.
     *
     * @exception {DeveloperError} Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.
     * @exception {DeveloperError} hint is invalid.
     * @exception {DeveloperError} This texture's width must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This texture's height must be a power of two to call generateMipmap().
     * @exception {DeveloperError} This texture was destroyed, i.e., destroy() was called.
     */
    Texture.prototype.generateMipmap = function(hint) {
        hint = defaultValue(hint, MipmapHint.DONT_CARE);

                if (PixelFormat.isDepthFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call generateMipmap when the texture pixel format is DEPTH_COMPONENT or DEPTH_STENCIL.');
        }
        if (PixelFormat.isCompressedFormat(this._pixelFormat)) {
            throw new DeveloperError('Cannot call generateMipmap with a compressed pixel format.');
        }
        if (this._width > 1 && !CesiumMath.isPowerOfTwo(this._width)) {
            throw new DeveloperError('width must be a power of two to call generateMipmap().');
        }
        if (this._height > 1 && !CesiumMath.isPowerOfTwo(this._height)) {
            throw new DeveloperError('height must be a power of two to call generateMipmap().');
        }
        if (!MipmapHint.validate(hint)) {
            throw new DeveloperError('hint is invalid.');
        }
        
        var gl = this._context._gl;
        var target = this._textureTarget;

        gl.hint(gl.GENERATE_MIPMAP_HINT, hint);
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(target, this._texture);
        gl.generateMipmap(target);
        gl.bindTexture(target, null);
    };

    Texture.prototype.isDestroyed = function() {
        return false;
    };

    Texture.prototype.destroy = function() {
        this._context._gl.deleteTexture(this._texture);
        return destroyObject(this);
    };

    return Texture;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/BumpMapMaterial',[],function() {
    'use strict';
    return "uniform sampler2D image;\n\
uniform float strength;\n\
uniform vec2 repeat;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    vec2 st = materialInput.st;\n\
    \n\
    vec2 centerPixel = fract(repeat * st);\n\
    float centerBump = texture2D(image, centerPixel).channel;\n\
    \n\
    float imageWidth = float(imageDimensions.x);\n\
    vec2 rightPixel = fract(repeat * (st + vec2(1.0 / imageWidth, 0.0)));\n\
    float rightBump = texture2D(image, rightPixel).channel;\n\
    \n\
    float imageHeight = float(imageDimensions.y);\n\
    vec2 leftPixel = fract(repeat * (st + vec2(0.0, 1.0 / imageHeight)));\n\
    float topBump = texture2D(image, leftPixel).channel;\n\
    \n\
    vec3 normalTangentSpace = normalize(vec3(centerBump - rightBump, centerBump - topBump, clamp(1.0 - strength, 0.1, 1.0)));\n\
    vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace;\n\
 \n\
    material.normal = normalEC;\n\
    material.diffuse = vec3(0.01);\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/CheckerboardMaterial',[],function() {
    'use strict';
    return "uniform vec4 lightColor;\n\
uniform vec4 darkColor;\n\
uniform vec2 repeat;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    vec2 st = materialInput.st;\n\
    \n\
    // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights\n\
    float b = mod(floor(repeat.s * st.s) + floor(repeat.t * st.t), 2.0);  // 0.0 or 1.0\n\
    \n\
    // Find the distance from the closest separator (region between two colors)\n\
    float scaledWidth = fract(repeat.s * st.s);\n\
    scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5));\n\
    float scaledHeight = fract(repeat.t * st.t);\n\
    scaledHeight = abs(scaledHeight - floor(scaledHeight + 0.5));\n\
    float value = min(scaledWidth, scaledHeight);\n\
    \n\
    vec4 currentColor = mix(lightColor, darkColor, b);\n\
    vec4 color = czm_antialias(lightColor, darkColor, currentColor, value, 0.03);\n\
    \n\
    material.diffuse = color.rgb;\n\
    material.alpha = color.a;\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/DotMaterial',[],function() {
    'use strict';
    return "uniform vec4 lightColor;\n\
uniform vec4 darkColor;\n\
uniform vec2 repeat;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
    \n\
    // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights\n\
    float b = smoothstep(0.3, 0.32, length(fract(repeat * materialInput.st) - 0.5));  // 0.0 or 1.0\n\
\n\
    vec4 color = mix(lightColor, darkColor, b);\n\
    material.diffuse = color.rgb;\n\
    material.alpha = color.a;\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/FadeMaterial',[],function() {
    'use strict';
    return "uniform vec4 fadeInColor;\n\
uniform vec4 fadeOutColor;\n\
uniform float maximumDistance;\n\
uniform bool repeat;\n\
uniform vec2 fadeDirection;\n\
uniform vec2 time;\n\
\n\
float getTime(float t, float coord)\n\
{\n\
    float scalar = 1.0 / maximumDistance;\n\
    float q  = distance(t, coord) * scalar;\n\
    if (repeat)\n\
    {\n\
        float r = distance(t, coord + 1.0) * scalar;\n\
        float s = distance(t, coord - 1.0) * scalar;\n\
        q = min(min(r, s), q);\n\
    }\n\
    return clamp(q, 0.0, 1.0);\n\
}\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
    \n\
    vec2 st = materialInput.st;\n\
    float s = getTime(time.x, st.s) * fadeDirection.s;\n\
    float t = getTime(time.y, st.t) * fadeDirection.t;\n\
    \n\
    float u = length(vec2(s, t));\n\
    vec4 color = mix(fadeInColor, fadeOutColor, u);\n\
    \n\
    material.emission = color.rgb;\n\
    material.alpha = color.a;\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/GridMaterial',[],function() {
    'use strict';
    return "#ifdef GL_OES_standard_derivatives\n\
    #extension GL_OES_standard_derivatives : enable\n\
#endif\n\
\n\
uniform vec4 color;\n\
uniform float cellAlpha;\n\
uniform vec2 lineCount;\n\
uniform vec2 lineThickness;\n\
uniform vec2 lineOffset;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    vec2 st = materialInput.st;\n\
\n\
    float scaledWidth = fract(lineCount.s * st.s - lineOffset.s);\n\
    scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5));\n\
    float scaledHeight = fract(lineCount.t * st.t - lineOffset.t);\n\
    scaledHeight = abs(scaledHeight - floor(scaledHeight + 0.5));\n\
\n\
    float value;\n\
#ifdef GL_OES_standard_derivatives\n\
    // Fuzz Factor - Controls blurriness of lines\n\
    const float fuzz = 1.2;\n\
    vec2 thickness = (lineThickness * czm_resolutionScale) - 1.0;\n\
\n\
    // From \"3D Engine Design for Virtual Globes\" by Cozzi and Ring, Listing 4.13.\n\
    vec2 dx = abs(dFdx(st));\n\
    vec2 dy = abs(dFdy(st));\n\
    vec2 dF = vec2(max(dx.s, dy.s), max(dx.t, dy.t)) * lineCount;\n\
    value = min(\n\
        smoothstep(dF.s * thickness.s, dF.s * (fuzz + thickness.s), scaledWidth),\n\
        smoothstep(dF.t * thickness.t, dF.t * (fuzz + thickness.t), scaledHeight));\n\
#else\n\
    // Fuzz Factor - Controls blurriness of lines\n\
    const float fuzz = 0.05;\n\
\n\
    vec2 range = 0.5 - (lineThickness * 0.05);\n\
    value = min(\n\
        1.0 - smoothstep(range.s, range.s + fuzz, scaledWidth),\n\
        1.0 - smoothstep(range.t, range.t + fuzz, scaledHeight));\n\
#endif\n\
\n\
    // Edges taken from RimLightingMaterial.glsl\n\
    // See http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html\n\
    float dRim = 1.0 - abs(dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC)));\n\
    float sRim = smoothstep(0.8, 1.0, dRim);\n\
    value *= (1.0 - sRim);\n\
\n\
    vec3 halfColor = color.rgb * 0.5;\n\
    material.diffuse = halfColor;\n\
    material.emission = halfColor;\n\
    material.alpha = color.a * (1.0 - ((1.0 - cellAlpha) * value));\n\
\n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/NormalMapMaterial',[],function() {
    'use strict';
    return "uniform sampler2D image;\n\
uniform float strength;\n\
uniform vec2 repeat;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
    \n\
    vec4 textureValue = texture2D(image, fract(repeat * materialInput.st));\n\
    vec3 normalTangentSpace = textureValue.channels;\n\
    normalTangentSpace.xy = normalTangentSpace.xy * 2.0 - 1.0;\n\
    normalTangentSpace.z = clamp(1.0 - strength, 0.1, 1.0);\n\
    normalTangentSpace = normalize(normalTangentSpace);\n\
    vec3 normalEC = materialInput.tangentToEyeMatrix * normalTangentSpace;\n\
    \n\
    material.normal = normalEC;\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/PolylineArrowMaterial',[],function() {
    'use strict';
    return "#ifdef GL_OES_standard_derivatives\n\
#extension GL_OES_standard_derivatives : enable\n\
#endif\n\
\n\
uniform vec4 color;\n\
\n\
varying float v_width;\n\
\n\
float getPointOnLine(vec2 p0, vec2 p1, float x)\n\
{\n\
    float slope = (p0.y - p1.y) / (p0.x - p1.x);\n\
    return slope * (x - p0.x) + p0.y;\n\
}\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    vec2 st = materialInput.st;\n\
\n\
#ifdef GL_OES_standard_derivatives\n\
    float base = 1.0 - abs(fwidth(st.s)) * 10.0;\n\
#else\n\
    float base = 0.99; // 1% of the line will be the arrow head\n\
#endif\n\
\n\
    vec2 center = vec2(1.0, 0.5);\n\
    float ptOnUpperLine = getPointOnLine(vec2(base, 1.0), center, st.s);\n\
    float ptOnLowerLine = getPointOnLine(vec2(base, 0.0), center, st.s);\n\
\n\
    float halfWidth = 0.15;\n\
    float s = step(0.5 - halfWidth, st.t);\n\
    s *= 1.0 - step(0.5 + halfWidth, st.t);\n\
    s *= 1.0 - step(base, st.s);\n\
\n\
    float t = step(base, materialInput.st.s);\n\
    t *= 1.0 - step(ptOnUpperLine, st.t);\n\
    t *= step(ptOnLowerLine, st.t);\n\
\n\
    // Find the distance from the closest separator (region between two colors)\n\
    float dist;\n\
    if (st.s < base)\n\
    {\n\
        float d1 = abs(st.t - (0.5 - halfWidth));\n\
        float d2 = abs(st.t - (0.5 + halfWidth));\n\
        dist = min(d1, d2);\n\
    }\n\
    else\n\
    {\n\
        float d1 = czm_infinity;\n\
        if (st.t < 0.5 - halfWidth && st.t > 0.5 + halfWidth)\n\
        {\n\
            d1 = abs(st.s - base);\n\
        }\n\
        float d2 = abs(st.t - ptOnUpperLine);\n\
        float d3 = abs(st.t - ptOnLowerLine);\n\
        dist = min(min(d1, d2), d3);\n\
    }\n\
\n\
    vec4 outsideColor = vec4(0.0);\n\
    vec4 currentColor = mix(outsideColor, color, clamp(s + t, 0.0, 1.0));\n\
    vec4 outColor = czm_antialias(outsideColor, color, currentColor, dist);\n\
\n\
    material.diffuse = outColor.rgb;\n\
    material.alpha = outColor.a;\n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/PolylineGlowMaterial',[],function() {
    'use strict';
    return "uniform vec4 color;\n\
uniform float glowPower;\n\
\n\
varying float v_width;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    vec2 st = materialInput.st;\n\
    float glow = glowPower / abs(st.t - 0.5) - (glowPower / 0.5);\n\
\n\
    material.emission = max(vec3(glow - 1.0 + color.rgb), color.rgb);\n\
    material.alpha = clamp(0.0, 1.0, glow) * color.a;\n\
\n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/PolylineOutlineMaterial',[],function() {
    'use strict';
    return "uniform vec4 color;\n\
uniform vec4 outlineColor;\n\
uniform float outlineWidth;\n\
\n\
varying float v_width;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
    \n\
    vec2 st = materialInput.st;\n\
    float halfInteriorWidth =  0.5 * (v_width - outlineWidth) / v_width;\n\
    float b = step(0.5 - halfInteriorWidth, st.t);\n\
    b *= 1.0 - step(0.5 + halfInteriorWidth, st.t);\n\
    \n\
    // Find the distance from the closest separator (region between two colors)\n\
    float d1 = abs(st.t - (0.5 - halfInteriorWidth));\n\
    float d2 = abs(st.t - (0.5 + halfInteriorWidth));\n\
    float dist = min(d1, d2);\n\
    \n\
    vec4 currentColor = mix(outlineColor, color, b);\n\
    vec4 outColor = czm_antialias(outlineColor, color, currentColor, dist);\n\
    \n\
    material.diffuse = outColor.rgb;\n\
    material.alpha = outColor.a;\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/RimLightingMaterial',[],function() {
    'use strict';
    return "uniform vec4 color;\n\
uniform vec4 rimColor;\n\
uniform float width;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    // See http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html\n\
    float d = 1.0 - dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC));\n\
    float s = smoothstep(1.0 - width, 1.0, d);\n\
\n\
    material.diffuse = color.rgb;\n\
    material.emission = rimColor.rgb * s; \n\
    material.alpha = mix(color.a, rimColor.a, s);\n\
\n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/StripeMaterial',[],function() {
    'use strict';
    return "uniform vec4 evenColor;\n\
uniform vec4 oddColor;\n\
uniform float offset;\n\
uniform float repeat;\n\
uniform bool horizontal;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    // Based on the Stripes Fragment Shader in the Orange Book (11.1.2)\n\
    float coord = mix(materialInput.st.s, materialInput.st.t, float(horizontal));\n\
    float value = fract((coord - offset) * (repeat * 0.5));\n\
    float dist = min(value, min(abs(value - 0.5), 1.0 - value));\n\
    \n\
    vec4 currentColor = mix(evenColor, oddColor, step(0.5, value));\n\
    vec4 color = czm_antialias(evenColor, oddColor, currentColor, dist);\n\
    \n\
    material.diffuse = color.rgb;\n\
    material.alpha = color.a;\n\
    \n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Materials/Water',[],function() {
    'use strict';
    return "// Thanks for the contribution Jonas\n\
// http://29a.ch/2012/7/19/webgl-terrain-rendering-water-fog\n\
\n\
uniform sampler2D specularMap;\n\
uniform sampler2D normalMap;\n\
uniform vec4 baseWaterColor;\n\
uniform vec4 blendColor;\n\
uniform float frequency;\n\
uniform float animationSpeed;\n\
uniform float amplitude;\n\
uniform float specularIntensity;\n\
uniform float fadeFactor;\n\
\n\
czm_material czm_getMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
\n\
    float time = czm_frameNumber * animationSpeed;\n\
    \n\
    // fade is a function of the distance from the fragment and the frequency of the waves\n\
    float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency * fadeFactor);\n\
            \n\
    float specularMapValue = texture2D(specularMap, materialInput.st).r;\n\
    \n\
    // note: not using directional motion at this time, just set the angle to 0.0;\n\
    vec4 noise = czm_getWaterNoise(normalMap, materialInput.st * frequency, time, 0.0);\n\
    vec3 normalTangentSpace = noise.xyz * vec3(1.0, 1.0, (1.0 / amplitude));\n\
    \n\
    // fade out the normal perturbation as we move further from the water surface\n\
    normalTangentSpace.xy /= fade;\n\
        \n\
    // attempt to fade out the normal perturbation as we approach non water areas (low specular map value)\n\
    normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);\n\
    \n\
    normalTangentSpace = normalize(normalTangentSpace);\n\
    \n\
    // get ratios for alignment of the new normal vector with a vector perpendicular to the tangent plane\n\
    float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);\n\
    \n\
    // fade out water effect as specular map value decreases\n\
    material.alpha = specularMapValue;\n\
    \n\
    // base color is a blend of the water and non-water color based on the value from the specular map\n\
    // may need a uniform blend factor to better control this\n\
    material.diffuse = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);\n\
    \n\
    // diffuse highlights are based on how perturbed the normal is\n\
    material.diffuse += (0.1 * tsPerturbationRatio);\n\
    \n\
    material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);\n\
    \n\
    material.specular = specularIntensity;\n\
    material.shininess = 10.0;\n\
    \n\
    return material;\n\
}\n\
";
});
/*global define*/
define('Scene/Material',[
        '../Core/Cartesian2',
        '../Core/clone',
        '../Core/Color',
        '../Core/combine',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/isArray',
        '../Core/loadCRN',
        '../Core/loadImage',
        '../Core/loadKTX',
        '../Core/Matrix2',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Renderer/CubeMap',
        '../Renderer/Texture',
        '../Shaders/Materials/BumpMapMaterial',
        '../Shaders/Materials/CheckerboardMaterial',
        '../Shaders/Materials/DotMaterial',
        '../Shaders/Materials/FadeMaterial',
        '../Shaders/Materials/GridMaterial',
        '../Shaders/Materials/NormalMapMaterial',
        '../Shaders/Materials/PolylineArrowMaterial',
        '../Shaders/Materials/PolylineGlowMaterial',
        '../Shaders/Materials/PolylineOutlineMaterial',
        '../Shaders/Materials/RimLightingMaterial',
        '../Shaders/Materials/StripeMaterial',
        '../Shaders/Materials/Water',
        '../ThirdParty/when'
    ], function(
        Cartesian2,
        clone,
        Color,
        combine,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        isArray,
        loadCRN,
        loadImage,
        loadKTX,
        Matrix2,
        Matrix3,
        Matrix4,
        CubeMap,
        Texture,
        BumpMapMaterial,
        CheckerboardMaterial,
        DotMaterial,
        FadeMaterial,
        GridMaterial,
        NormalMapMaterial,
        PolylineArrowMaterial,
        PolylineGlowMaterial,
        PolylineOutlineMaterial,
        RimLightingMaterial,
        StripeMaterial,
        WaterMaterial,
        when) {
    'use strict';

    /**
     * A Material defines surface appearance through a combination of diffuse, specular,
     * normal, emission, and alpha components. These values are specified using a
     * JSON schema called Fabric which gets parsed and assembled into glsl shader code
     * behind-the-scenes. Check out the {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|wiki page}
     * for more details on Fabric.
     * <br /><br />
     * <style type="text/css">
     *  #materialDescriptions code {
     *      font-weight: normal;
     *      font-family: Consolas, 'Lucida Console', Monaco, monospace;
     *      color: #A35A00;
     *  }
     *  #materialDescriptions ul, #materialDescriptions ul ul {
     *      list-style-type: none;
     *  }
     *  #materialDescriptions ul ul {
     *      margin-bottom: 10px;
     *  }
     *  #materialDescriptions ul ul li {
     *      font-weight: normal;
     *      color: #000000;
     *      text-indent: -2em;
     *      margin-left: 2em;
     *  }
     *  #materialDescriptions ul li {
     *      font-weight: bold;
     *      color: #0053CF;
     *  }
     * </style>
     *
     * Base material types and their uniforms:
     * <div id='materialDescriptions'>
     * <ul>
     *  <li>Color</li>
     *  <ul>
     *      <li><code>color</code>:  rgba color object.</li>
     *  </ul>
     *  <li>Image</li>
     *  <ul>
     *      <li><code>image</code>:  path to image.</li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of times to repeat the image.</li>
     *  </ul>
     *  <li>DiffuseMap</li>
     *  <ul>
     *      <li><code>image</code>:  path to image.</li>
     *      <li><code>channels</code>:  Three character string containing any combination of r, g, b, and a for selecting the desired image channels.</li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of times to repeat the image.</li>
     *  </ul>
     *  <li>AlphaMap</li>
     *  <ul>
     *      <li><code>image</code>:  path to image.</li>
     *      <li><code>channel</code>:  One character string containing r, g, b, or a for selecting the desired image channel. </li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of times to repeat the image.</li>
     *  </ul>
     *  <li>SpecularMap</li>
     *  <ul>
     *      <li><code>image</code>: path to image.</li>
     *      <li><code>channel</code>: One character string containing r, g, b, or a for selecting the desired image channel. </li>
     *      <li><code>repeat</code>: Object with x and y values specifying the number of times to repeat the image.</li>
     *  </ul>
     *  <li>EmissionMap</li>
     *  <ul>
     *      <li><code>image</code>:  path to image.</li>
     *      <li><code>channels</code>:  Three character string containing any combination of r, g, b, and a for selecting the desired image channels. </li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of times to repeat the image.</li>
     *  </ul>
     *  <li>BumpMap</li>
     *  <ul>
     *      <li><code>image</code>:  path to image.</li>
     *      <li><code>channel</code>:  One character string containing r, g, b, or a for selecting the desired image channel. </li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of times to repeat the image.</li>
     *      <li><code>strength</code>:  Bump strength value between 0.0 and 1.0 where 0.0 is small bumps and 1.0 is large bumps.</li>
     *  </ul>
     *  <li>NormalMap</li>
     *  <ul>
     *      <li><code>image</code>:  path to image.</li>
     *      <li><code>channels</code>:  Three character string containing any combination of r, g, b, and a for selecting the desired image channels. </li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of times to repeat the image.</li>
     *      <li><code>strength</code>:  Bump strength value between 0.0 and 1.0 where 0.0 is small bumps and 1.0 is large bumps.</li>
     *  </ul>
     *  <li>Grid</li>
     *  <ul>
     *      <li><code>color</code>:  rgba color object for the whole material.</li>
     *      <li><code>cellAlpha</code>: Alpha value for the cells between grid lines.  This will be combined with color.alpha.</li>
     *      <li><code>lineCount</code>:  Object with x and y values specifying the number of columns and rows respectively.</li>
     *      <li><code>lineThickness</code>:  Object with x and y values specifying the thickness of grid lines (in pixels where available).</li>
     *      <li><code>lineOffset</code>:  Object with x and y values specifying the offset of grid lines (range is 0 to 1).</li>
     *  </ul>
     *  <li>Stripe</li>
     *  <ul>
     *      <li><code>horizontal</code>:  Boolean that determines if the stripes are horizontal or vertical.</li>
     *      <li><code>evenColor</code>:  rgba color object for the stripe's first color.</li>
     *      <li><code>oddColor</code>:  rgba color object for the stripe's second color.</li>
     *      <li><code>offset</code>:  Number that controls at which point into the pattern to begin drawing; with 0.0 being the beginning of the even color, 1.0 the beginning of the odd color, 2.0 being the even color again, and any multiple or fractional values being in between.</li>
     *      <li><code>repeat</code>:  Number that controls the total number of stripes, half light and half dark.</li>
     *  </ul>
     *  <li>Checkerboard</li>
     *  <ul>
     *      <li><code>lightColor</code>:  rgba color object for the checkerboard's light alternating color.</li>
     *      <li><code>darkColor</code>: rgba color object for the checkerboard's dark alternating color.</li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of columns and rows respectively.</li>
     *  </ul>
     *  <li>Dot</li>
     *  <ul>
     *      <li><code>lightColor</code>:  rgba color object for the dot color.</li>
     *      <li><code>darkColor</code>:  rgba color object for the background color.</li>
     *      <li><code>repeat</code>:  Object with x and y values specifying the number of columns and rows of dots respectively.</li>
     *  </ul>
     *  <li>Water</li>
     *  <ul>
     *      <li><code>baseWaterColor</code>:  rgba color object base color of the water.</li>
     *      <li><code>blendColor</code>:  rgba color object used when blending from water to non-water areas.</li>
     *      <li><code>specularMap</code>:  Single channel texture used to indicate areas of water.</li>
     *      <li><code>normalMap</code>:  Normal map for water normal perturbation.</li>
     *      <li><code>frequency</code>:  Number that controls the number of waves.</li>
     *      <li><code>normalMap</code>:  Normal map for water normal perturbation.</li>
     *      <li><code>animationSpeed</code>:  Number that controls the animations speed of the water.</li>
     *      <li><code>amplitude</code>:  Number that controls the amplitude of water waves.</li>
     *      <li><code>specularIntensity</code>:  Number that controls the intensity of specular reflections.</li>
     *  </ul>
     *  <li>RimLighting</li>
     *  <ul>
     *      <li><code>color</code>:  diffuse color and alpha.</li>
     *      <li><code>rimColor</code>:  diffuse color and alpha of the rim.</li>
     *      <li><code>width</code>:  Number that determines the rim's width.</li>
     *  </ul>
     *  <li>Fade</li>
     *  <ul>
     *      <li><code>fadeInColor</code>: diffuse color and alpha at <code>time</code></li>
     *      <li><code>fadeOutColor</code>: diffuse color and alpha at <code>maximumDistance</code> from <code>time</code></li>
     *      <li><code>maximumDistance</code>: Number between 0.0 and 1.0 where the <code>fadeInColor</code> becomes the <code>fadeOutColor</code>. A value of 0.0 gives the entire material a color of <code>fadeOutColor</code> and a value of 1.0 gives the the entire material a color of <code>fadeInColor</code></li>
     *      <li><code>repeat</code>: true if the fade should wrap around the texture coodinates.</li>
     *      <li><code>fadeDirection</code>: Object with x and y values specifying if the fade should be in the x and y directions.</li>
     *      <li><code>time</code>: Object with x and y values between 0.0 and 1.0 of the <code>fadeInColor</code> position</li>
     *  </ul>
     *  <li>PolylineArrow</li>
     *  <ul>
     *      <li><code>color</code>: diffuse color and alpha.</li>
     *  </ul>
     *  <li>PolylineGlow</li>
     *  <ul>
     *      <li><code>color</code>: color and maximum alpha for the glow on the line.</li>
     *      <li><code>glowPower</code>: strength of the glow, as a percentage of the total line width (less than 1.0).</li>
     *  </ul>
     *  <li>PolylineOutline</li>
     *  <ul>
     *      <li><code>color</code>: diffuse color and alpha for the interior of the line.</li>
     *      <li><code>outlineColor</code>: diffuse color and alpha for the outline.</li>
     *      <li><code>outlineWidth</code>: width of the outline in pixels.</li>
     *  </ul>
     * </ul>
     * </div>
     *
     * @alias Material
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.strict=false] Throws errors for issues that would normally be ignored, including unused uniforms or materials.
     * @param {Boolean|Function} [options.translucent=true] When <code>true</code> or a function that returns <code>true</code>, the geometry
     *                           with this material is expected to appear translucent.
     * @param {Object} options.fabric The fabric JSON used to generate the material.
     *
     * @constructor
     *
     * @exception {DeveloperError} fabric: uniform has invalid type.
     * @exception {DeveloperError} fabric: uniforms and materials cannot share the same property.
     * @exception {DeveloperError} fabric: cannot have source and components in the same section.
     * @exception {DeveloperError} fabric: property name is not valid. It should be 'type', 'materials', 'uniforms', 'components', or 'source'.
     * @exception {DeveloperError} fabric: property name is not valid. It should be 'diffuse', 'specular', 'shininess', 'normal', 'emission', or 'alpha'.
     * @exception {DeveloperError} strict: shader source does not use string.
     * @exception {DeveloperError} strict: shader source does not use uniform.
     * @exception {DeveloperError} strict: shader source does not use material.
     *
     * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric wiki page} for a more detailed options of Fabric.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Materials.html|Cesium Sandcastle Materials Demo}
     *
     * @example
     * // Create a color material with fromType:
     * polygon.material = Cesium.Material.fromType('Color');
     * polygon.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
     *
     * // Create the default material:
     * polygon.material = new Cesium.Material();
     *
     * // Create a color material with full Fabric notation:
     * polygon.material = new Cesium.Material({
     *     fabric : {
     *         type : 'Color',
     *         uniforms : {
     *             color : new Cesium.Color(1.0, 1.0, 0.0, 1.0)
     *         }
     *     }
     * });
     */
    function Material(options) {
        /**
         * The material type. Can be an existing type or a new type. If no type is specified in fabric, type is a GUID.
         * @type {String}
         * @default undefined
         */
        this.type = undefined;

        /**
         * The glsl shader source for this material.
         * @type {String}
         * @default undefined
         */
        this.shaderSource = undefined;

        /**
         * Maps sub-material names to Material objects.
         * @type {Object}
         * @default undefined
         */
        this.materials = undefined;

        /**
         * Maps uniform names to their values.
         * @type {Object}
         * @default undefined
         */
        this.uniforms = undefined;
        this._uniforms = undefined;

        /**
         * When <code>true</code> or a function that returns <code>true</code>,
         * the geometry is expected to appear translucent.
         * @type {Boolean|Function}
         * @default undefined
         */
        this.translucent = undefined;

        this._strict = undefined;
        this._template = undefined;
        this._count = undefined;

        this._texturePaths = {};
        this._loadedImages = [];
        this._loadedCubeMaps = [];

        this._textures = {};

        this._updateFunctions = [];

        this._defaultTexture = undefined;

        initializeMaterial(options, this);
        defineProperties(this, {
            type : {
                value : this.type,
                writable : false
            }
        });

        if (!defined(Material._uniformList[this.type])) {
            Material._uniformList[this.type] = Object.keys(this._uniforms);
        }
    }

    // Cached list of combined uniform names indexed by type.
    // Used to get the list of uniforms in the same order.
    Material._uniformList = {};

    /**
     * Creates a new material using an existing material type.
     * <br /><br />
     * Shorthand for: new Material({fabric : {type : type}});
     *
     * @param {String} type The base material type.
     * @param {Object} [uniforms] Overrides for the default uniforms.
     * @returns {Material} New material object.
     *
     * @exception {DeveloperError} material with that type does not exist.
     *
     * @example
     * var material = Cesium.Material.fromType('Color', {
     *     color : new Cesium.Color(1.0, 0.0, 0.0, 1.0)
     * });
     */
    Material.fromType = function(type, uniforms) {
                if (!defined(Material._materialCache.getMaterial(type))) {
            throw new DeveloperError('material with type \'' + type + '\' does not exist.');
        }
        
        var material = new Material({
            fabric : {
                type : type
            }
        });

        if (defined(uniforms)) {
            for (var name in uniforms) {
                if (uniforms.hasOwnProperty(name)) {
                    material.uniforms[name] = uniforms[name];
                }
            }
        }

        return material;
    };

    /**
     * Gets whether or not this material is translucent.
     * @returns <code>true</code> if this material is translucent, <code>false</code> otherwise.
     */
    Material.prototype.isTranslucent = function() {
        if (defined(this.translucent)) {
            if (typeof this.translucent === 'function') {
                return this.translucent();
            }

            return this.translucent;
        }

        var translucent = true;
        var funcs = this._translucentFunctions;
        var length = funcs.length;
        for (var i = 0; i < length; ++i) {
            var func = funcs[i];
            if (typeof func === 'function') {
                translucent = translucent && func();
            } else {
                translucent = translucent && func;
            }

            if (!translucent) {
                break;
            }
        }
        return translucent;
    };

    /**
     * @private
     */
    Material.prototype.update = function(context) {
        var i;
        var uniformId;

        var loadedImages = this._loadedImages;
        var length = loadedImages.length;

        for (i = 0; i < length; ++i) {
            var loadedImage = loadedImages[i];
            uniformId = loadedImage.id;
            var image = loadedImage.image;

            var texture;
            if (defined(image.internalFormat)) {
                texture = new Texture({
                    context : context,
                    pixelFormat : image.internalFormat,
                    width : image.width,
                    height : image.height,
                    source : {
                        arrayBufferView : image.bufferView
                    }
                });
            } else {
                texture = new Texture({
                    context : context,
                    source : image
                });
            }

            this._textures[uniformId] = texture;

            var uniformDimensionsName = uniformId + 'Dimensions';
            if (this.uniforms.hasOwnProperty(uniformDimensionsName)) {
                var uniformDimensions = this.uniforms[uniformDimensionsName];
                uniformDimensions.x = texture._width;
                uniformDimensions.y = texture._height;
            }
        }

        loadedImages.length = 0;

        var loadedCubeMaps = this._loadedCubeMaps;
        length = loadedCubeMaps.length;

        for (i = 0; i < length; ++i) {
            var loadedCubeMap = loadedCubeMaps[i];
            uniformId = loadedCubeMap.id;
            var images = loadedCubeMap.images;

            var cubeMap = new CubeMap({
                    context : context,
                    source : {
                        positiveX : images[0],
                        negativeX : images[1],
                        positiveY : images[2],
                        negativeY : images[3],
                        positiveZ : images[4],
                        negativeZ : images[5]
                    }
                });

            this._textures[uniformId] = cubeMap;
        }

        loadedCubeMaps.length = 0;

        var updateFunctions = this._updateFunctions;
        length = updateFunctions.length;
        for (i = 0; i < length; ++i) {
            updateFunctions[i](this, context);
        }

        var subMaterials = this.materials;
        for (var name in subMaterials) {
            if (subMaterials.hasOwnProperty(name)) {
                subMaterials[name].update(context);
            }
        }
    };

    /**
    * Returns true if this object was destroyed; otherwise, false.
    * <br /><br />
    * If this object was destroyed, it should not be used; calling any function other than
    * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
    *
    * @returns {Boolean} True if this object was destroyed; otherwise, false.
    *
    * @see Material#destroy
    */
    Material.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * material = material && material.destroy();
     *
     * @see Material#isDestroyed
     */
    Material.prototype.destroy = function() {
        var textures = this._textures;
        for ( var texture in textures) {
            if (textures.hasOwnProperty(texture)) {
                var instance = textures[texture];
                if (instance !== this._defaultTexture) {
                    instance.destroy();
                }
            }
        }

        var materials = this.materials;
        for ( var material in materials) {
            if (materials.hasOwnProperty(material)) {
                materials[material].destroy();
            }
        }
        return destroyObject(this);
    };

    function initializeMaterial(options, result) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        result._strict = defaultValue(options.strict, false);
        result._count = defaultValue(options.count, 0);
        result._template = clone(defaultValue(options.fabric, defaultValue.EMPTY_OBJECT));
        result._template.uniforms = clone(defaultValue(result._template.uniforms, defaultValue.EMPTY_OBJECT));
        result._template.materials = clone(defaultValue(result._template.materials, defaultValue.EMPTY_OBJECT));

        result.type = defined(result._template.type) ? result._template.type : createGuid();

        result.shaderSource = '';
        result.materials = {};
        result.uniforms = {};
        result._uniforms = {};
        result._translucentFunctions = [];

        var translucent;

        // If the cache contains this material type, build the material template off of the stored template.
        var cachedMaterial = Material._materialCache.getMaterial(result.type);
        if (defined(cachedMaterial)) {
            var template = clone(cachedMaterial.fabric, true);
            result._template = combine(result._template, template, true);
            translucent = cachedMaterial.translucent;
        }

        // Make sure the template has no obvious errors. More error checking happens later.
        checkForTemplateErrors(result);

        // If the material has a new type, add it to the cache.
        if (!defined(cachedMaterial)) {
            Material._materialCache.addMaterial(result.type, result);
        }

        createMethodDefinition(result);
        createUniforms(result);
        createSubMaterials(result);

        var defaultTranslucent = result._translucentFunctions.length === 0 ? true : undefined;
        translucent = defaultValue(translucent, defaultTranslucent);
        translucent = defaultValue(options.translucent, translucent);

        if (defined(translucent)) {
            if (typeof translucent === 'function') {
                var wrappedTranslucent = function() {
                    return translucent(result);
                };
                result._translucentFunctions.push(wrappedTranslucent);
            } else {
                result._translucentFunctions.push(translucent);
            }

        }
    }

    function checkForValidProperties(object, properties, result, throwNotFound) {
        if (defined(object)) {
            for ( var property in object) {
                if (object.hasOwnProperty(property)) {
                    var hasProperty = properties.indexOf(property) !== -1;
                    if ((throwNotFound && !hasProperty) || (!throwNotFound && hasProperty)) {
                        result(property, properties);
                    }
                }
            }
        }
    }

    function invalidNameError(property, properties) {
                var errorString = 'fabric: property name \'' + property + '\' is not valid. It should be ';
        for ( var i = 0; i < properties.length; i++) {
            var propertyName = '\'' + properties[i] + '\'';
            errorString += (i === properties.length - 1) ? ('or ' + propertyName + '.') : (propertyName + ', ');
        }
        throw new DeveloperError(errorString);
            }

    function duplicateNameError(property, properties) {
                var errorString = 'fabric: uniforms and materials cannot share the same property \'' + property + '\'';
        throw new DeveloperError(errorString);
            }

    var templateProperties = ['type', 'materials', 'uniforms', 'components', 'source'];
    var componentProperties = ['diffuse', 'specular', 'shininess', 'normal', 'emission', 'alpha'];

    function checkForTemplateErrors(material) {
        var template = material._template;
        var uniforms = template.uniforms;
        var materials = template.materials;
        var components = template.components;

        // Make sure source and components do not exist in the same template.
                if (defined(components) && defined(template.source)) {
            throw new DeveloperError('fabric: cannot have source and components in the same template.');
        }
        
        // Make sure all template and components properties are valid.
        checkForValidProperties(template, templateProperties, invalidNameError, true);
        checkForValidProperties(components, componentProperties, invalidNameError, true);

        // Make sure uniforms and materials do not share any of the same names.
        var materialNames = [];
        for ( var property in materials) {
            if (materials.hasOwnProperty(property)) {
                materialNames.push(property);
            }
        }
        checkForValidProperties(uniforms, materialNames, duplicateNameError, false);
    }

    // Create the czm_getMaterial method body using source or components.
    function createMethodDefinition(material) {
        var components = material._template.components;
        var source = material._template.source;
        if (defined(source)) {
            material.shaderSource += source + '\n';
        } else {
            material.shaderSource += 'czm_material czm_getMaterial(czm_materialInput materialInput)\n{\n';
            material.shaderSource += 'czm_material material = czm_getDefaultMaterial(materialInput);\n';
            if (defined(components)) {
                for ( var component in components) {
                    if (components.hasOwnProperty(component)) {
                        material.shaderSource += 'material.' + component + ' = ' + components[component] + ';\n';
                    }
                }
            }
            material.shaderSource += 'return material;\n}\n';
        }
    }

    var matrixMap = {
        'mat2' : Matrix2,
        'mat3' : Matrix3,
        'mat4' : Matrix4
    };

    var ktxRegex = /\.ktx$/i;
    var crnRegex = /\.crn$/i;

    function createTexture2DUpdateFunction(uniformId) {
        var oldUniformValue;
        return function(material, context) {
            var uniforms = material.uniforms;
            var uniformValue = uniforms[uniformId];
            var uniformChanged = oldUniformValue !== uniformValue;
            oldUniformValue = uniformValue;
            var texture = material._textures[uniformId];

            var uniformDimensionsName;
            var uniformDimensions;

            if (uniformValue instanceof HTMLVideoElement) {
                // HTMLVideoElement.readyState >=2 means we have enough data for the current frame.
                // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
                if (uniformValue.readyState >= 2) {
                    if (uniformChanged && defined(texture)) {
                        if (texture !== context.defaultTexture) {
                            texture.destroy();
                        }
                        texture = undefined;
                    }

                    if (!defined(texture) || texture === context.defaultTexture) {
                        texture = new Texture({
                            context : context,
                            source : uniformValue
                        });
                        material._textures[uniformId] = texture;
                        return;
                    }

                    texture.copyFrom(uniformValue);
                } else if (!defined(texture)) {
                    material._textures[uniformId] = context.defaultTexture;
                }
                return;
            }

            if (uniformValue instanceof HTMLCanvasElement) {
                if (!uniformValue.drawing) {
                    if (uniformChanged && defined(texture)) {
                        if (texture !== context.defaultTexture) {
                            texture.destroy();
                        }
                        texture = undefined;
                    }

                    if (!defined(texture) || texture === context.defaultTexture) {
                        texture = new Texture({
                            context : context,
                            source : uniformValue
                        });
                        material._textures[uniformId] = texture;
                        return;
                    }

                    texture.copyFrom(uniformValue);
                } else if (!defined(texture)) {
                    material._textures[uniformId] = context.defaultTexture;
                }
                return;
            }

            if (uniformValue instanceof Texture && uniformValue !== texture) {
                material._texturePaths[uniformId] = undefined;
                var tmp = material._textures[uniformId];
                if (tmp !== material._defaultTexture) {
                    tmp.destroy();
                }
                material._textures[uniformId] = uniformValue;

                uniformDimensionsName = uniformId + 'Dimensions';
                if (uniforms.hasOwnProperty(uniformDimensionsName)) {
                    uniformDimensions = uniforms[uniformDimensionsName];
                    uniformDimensions.x = uniformValue._width;
                    uniformDimensions.y = uniformValue._height;
                }

                return;
            }

            if (!defined(texture)) {
                material._texturePaths[uniformId] = undefined;
                if (!defined(material._defaultTexture)) {
                    material._defaultTexture = context.defaultTexture;
                }
                texture = material._textures[uniformId] = material._defaultTexture;

                uniformDimensionsName = uniformId + 'Dimensions';
                if (uniforms.hasOwnProperty(uniformDimensionsName)) {
                    uniformDimensions = uniforms[uniformDimensionsName];
                    uniformDimensions.x = texture._width;
                    uniformDimensions.y = texture._height;
                }
            }

            if (uniformValue === Material.DefaultImageId) {
                return;
            }

            if (uniformValue !== material._texturePaths[uniformId]) {
                if (typeof uniformValue === 'string') {
                    var promise;
                    if (ktxRegex.test(uniformValue)) {
                        promise = loadKTX(uniformValue);
                    } else if (crnRegex.test(uniformValue)) {
                        promise = loadCRN(uniformValue);
                    } else {
                        promise = loadImage(uniformValue);
                    }
                    when(promise, function(image) {
                        material._loadedImages.push({
                            id : uniformId,
                            image : image
                        });
                    });
                } else if (uniformValue instanceof HTMLCanvasElement) {
                    material._loadedImages.push({
                        id : uniformId,
                        image : uniformValue
                    });
                }

                material._texturePaths[uniformId] = uniformValue;
            }
        };
    }

    function createCubeMapUpdateFunction(uniformId) {
        return function(material, context) {
            var uniformValue = material.uniforms[uniformId];

            if (uniformValue instanceof CubeMap) {
                var tmp = material._textures[uniformId];
                if (tmp !== material._defaultTexture) {
                    tmp.destroy();
                }
                material._texturePaths[uniformId] = undefined;
                material._textures[uniformId] = uniformValue;
                return;
            }

            if (!defined(material._textures[uniformId])) {
                material._texturePaths[uniformId] = undefined;
                material._textures[uniformId] = context.defaultCubeMap;
            }

            if (uniformValue === Material.DefaultCubeMapId) {
                return;
            }

            var path =
                uniformValue.positiveX + uniformValue.negativeX +
                uniformValue.positiveY + uniformValue.negativeY +
                uniformValue.positiveZ + uniformValue.negativeZ;

            if (path !== material._texturePaths[uniformId]) {
                var promises = [
                    loadImage(uniformValue.positiveX),
                    loadImage(uniformValue.negativeX),
                    loadImage(uniformValue.positiveY),
                    loadImage(uniformValue.negativeY),
                    loadImage(uniformValue.positiveZ),
                    loadImage(uniformValue.negativeZ)
                ];

                when.all(promises).then(function(images) {
                    material._loadedCubeMaps.push({
                        id : uniformId,
                        images : images
                    });
                });

                material._texturePaths[uniformId] = path;
            }
        };
    }

    function createUniforms(material) {
        var uniforms = material._template.uniforms;
        for ( var uniformId in uniforms) {
            if (uniforms.hasOwnProperty(uniformId)) {
                createUniform(material, uniformId);
            }
        }
    }

    // Writes uniform declarations to the shader file and connects uniform values with
    // corresponding material properties through the returnUniforms function.
    function createUniform(material, uniformId) {
        var strict = material._strict;
        var materialUniforms = material._template.uniforms;
        var uniformValue = materialUniforms[uniformId];
        var uniformType = getUniformType(uniformValue);

                if (!defined(uniformType)) {
            throw new DeveloperError('fabric: uniform \'' + uniformId + '\' has invalid type.');
        }
        
        var replacedTokenCount;
        if (uniformType === 'channels') {
            replacedTokenCount = replaceToken(material, uniformId, uniformValue, false);
                        if (replacedTokenCount === 0 && strict) {
                throw new DeveloperError('strict: shader source does not use channels \'' + uniformId + '\'.');
            }
                    } else {
            // Since webgl doesn't allow texture dimension queries in glsl, create a uniform to do it.
            // Check if the shader source actually uses texture dimensions before creating the uniform.
            if (uniformType === 'sampler2D') {
                var imageDimensionsUniformName = uniformId + 'Dimensions';
                if (getNumberOfTokens(material, imageDimensionsUniformName) > 0) {
                    materialUniforms[imageDimensionsUniformName] = {
                        type : 'ivec3',
                        x : 1,
                        y : 1
                    };
                    createUniform(material, imageDimensionsUniformName);
                }
            }

            // Add uniform declaration to source code.
            var uniformDeclarationRegex = new RegExp('uniform\\s+' + uniformType + '\\s+' + uniformId + '\\s*;');
            if (!uniformDeclarationRegex.test(material.shaderSource)) {
                var uniformDeclaration = 'uniform ' + uniformType + ' ' + uniformId + ';';
                material.shaderSource = uniformDeclaration + material.shaderSource;
            }

            var newUniformId = uniformId + '_' + material._count++;
            replacedTokenCount = replaceToken(material, uniformId, newUniformId);
                        if (replacedTokenCount === 1 && strict) {
                throw new DeveloperError('strict: shader source does not use uniform \'' + uniformId + '\'.');
            }
            
            // Set uniform value
            material.uniforms[uniformId] = uniformValue;

            if (uniformType === 'sampler2D') {
                material._uniforms[newUniformId] = function() {
                    return material._textures[uniformId];
                };
                material._updateFunctions.push(createTexture2DUpdateFunction(uniformId));
            } else if (uniformType === 'samplerCube') {
                material._uniforms[newUniformId] = function() {
                    return material._textures[uniformId];
                };
                material._updateFunctions.push(createCubeMapUpdateFunction(uniformId));
            } else if (uniformType.indexOf('mat') !== -1) {
                var scratchMatrix = new matrixMap[uniformType]();
                material._uniforms[newUniformId] = function() {
                    return matrixMap[uniformType].fromColumnMajorArray(material.uniforms[uniformId], scratchMatrix);
                };
            } else {
                material._uniforms[newUniformId] = function() {
                    return material.uniforms[uniformId];
                };
            }
        }
    }

    // Determines the uniform type based on the uniform in the template.
    function getUniformType(uniformValue) {
        var uniformType = uniformValue.type;
        if (!defined(uniformType)) {
            var type = typeof uniformValue;
            if (type === 'number') {
                uniformType = 'float';
            } else if (type === 'boolean') {
                uniformType = 'bool';
            } else if (type === 'string' || uniformValue instanceof HTMLCanvasElement || uniformValue instanceof HTMLVideoElement) {
                if (/^([rgba]){1,4}$/i.test(uniformValue)) {
                    uniformType = 'channels';
                } else if (uniformValue === Material.DefaultCubeMapId) {
                    uniformType = 'samplerCube';
                } else {
                    uniformType = 'sampler2D';
                }
            } else if (type === 'object') {
                if (isArray(uniformValue)) {
                    if (uniformValue.length === 4 || uniformValue.length === 9 || uniformValue.length === 16) {
                        uniformType = 'mat' + Math.sqrt(uniformValue.length);
                    }
                } else {
                    var numAttributes = 0;
                    for ( var attribute in uniformValue) {
                        if (uniformValue.hasOwnProperty(attribute)) {
                            numAttributes += 1;
                        }
                    }
                    if (numAttributes >= 2 && numAttributes <= 4) {
                        uniformType = 'vec' + numAttributes;
                    } else if (numAttributes === 6) {
                        uniformType = 'samplerCube';
                    }
                }
            }
        }
        return uniformType;
    }

    // Create all sub-materials by combining source and uniforms together.
    function createSubMaterials(material) {
        var strict = material._strict;
        var subMaterialTemplates = material._template.materials;
        for ( var subMaterialId in subMaterialTemplates) {
            if (subMaterialTemplates.hasOwnProperty(subMaterialId)) {
                // Construct the sub-material.
                var subMaterial = new Material({
                    strict : strict,
                    fabric : subMaterialTemplates[subMaterialId],
                    count : material._count
                });

                material._count = subMaterial._count;
                material._uniforms = combine(material._uniforms, subMaterial._uniforms, true);
                material.materials[subMaterialId] = subMaterial;
                material._translucentFunctions = material._translucentFunctions.concat(subMaterial._translucentFunctions);

                // Make the material's czm_getMaterial unique by appending the sub-material type.
                var originalMethodName = 'czm_getMaterial';
                var newMethodName = originalMethodName + '_' + material._count++;
                replaceToken(subMaterial, originalMethodName, newMethodName);
                material.shaderSource = subMaterial.shaderSource + material.shaderSource;

                // Replace each material id with an czm_getMaterial method call.
                var materialMethodCall = newMethodName + '(materialInput)';
                var tokensReplacedCount = replaceToken(material, subMaterialId, materialMethodCall);
                                if (tokensReplacedCount === 0 && strict) {
                    throw new DeveloperError('strict: shader source does not use material \'' + subMaterialId + '\'.');
                }
                            }
        }
    }

    // Used for searching or replacing a token in a material's shader source with something else.
    // If excludePeriod is true, do not accept tokens that are preceded by periods.
    // http://stackoverflow.com/questions/641407/javascript-negative-lookbehind-equivalent
    function replaceToken(material, token, newToken, excludePeriod) {
        excludePeriod = defaultValue(excludePeriod, true);
        var count = 0;
        var suffixChars = '([\\w])?';
        var prefixChars = '([\\w' + (excludePeriod ? '.' : '') + '])?';
        var regExp = new RegExp(prefixChars + token + suffixChars, 'g');
        material.shaderSource = material.shaderSource.replace(regExp, function($0, $1, $2) {
            if ($1 || $2) {
                return $0;
            }
            count += 1;
            return newToken;
        });
        return count;
    }

    function getNumberOfTokens(material, token, excludePeriod) {
        return replaceToken(material, token, token, excludePeriod);
    }

    Material._materialCache = {
        _materials : {},
        addMaterial : function(type, materialTemplate) {
            this._materials[type] = materialTemplate;
        },
        getMaterial : function(type) {
            return this._materials[type];
        }
    };

    /**
     * Gets or sets the default texture uniform value.
     * @type {String}
     */
    Material.DefaultImageId = 'czm_defaultImage';

    /**
     * Gets or sets the default cube map texture uniform value.
     * @type {String}
     */
    Material.DefaultCubeMapId = 'czm_defaultCubeMap';

    /**
     * Gets the name of the color material.
     * @type {String}
     * @readonly
     */
    Material.ColorType = 'Color';
    Material._materialCache.addMaterial(Material.ColorType, {
        fabric : {
            type : Material.ColorType,
            uniforms : {
                color : new Color(1.0, 0.0, 0.0, 0.5)
            },
            components : {
                diffuse : 'color.rgb',
                alpha : 'color.a'
            }
        },
        translucent : function(material) {
            return material.uniforms.color.alpha < 1.0;
        }
    });

    /**
     * Gets the name of the image material.
     * @type {String}
     * @readonly
     */
    Material.ImageType = 'Image';
    Material._materialCache.addMaterial(Material.ImageType, {
        fabric : {
            type : Material.ImageType,
            uniforms : {
                image : Material.DefaultImageId,
                repeat : new Cartesian2(1.0, 1.0),
                color: new Color(1.0, 1.0, 1.0, 1.0)
            },
            components : {
                diffuse : 'texture2D(image, fract(repeat * materialInput.st)).rgb * color.rgb',
                alpha : 'texture2D(image, fract(repeat * materialInput.st)).a * color.a'
            }
        },
        translucent : function(material) {
            return material.uniforms.color.alpha < 1.0;
        }
    });

    /**
     * Gets the name of the diffuce map material.
     * @type {String}
     * @readonly
     */
    Material.DiffuseMapType = 'DiffuseMap';
    Material._materialCache.addMaterial(Material.DiffuseMapType, {
        fabric : {
            type : Material.DiffuseMapType,
            uniforms : {
                image : Material.DefaultImageId,
                channels : 'rgb',
                repeat : new Cartesian2(1.0, 1.0)
            },
            components : {
                diffuse : 'texture2D(image, fract(repeat * materialInput.st)).channels'
            }
        },
        translucent : false
    });

    /**
     * Gets the name of the alpha map material.
     * @type {String}
     * @readonly
     */
    Material.AlphaMapType = 'AlphaMap';
    Material._materialCache.addMaterial(Material.AlphaMapType, {
        fabric : {
            type : Material.AlphaMapType,
            uniforms : {
                image : Material.DefaultImageId,
                channel : 'a',
                repeat : new Cartesian2(1.0, 1.0)
            },
            components : {
                alpha : 'texture2D(image, fract(repeat * materialInput.st)).channel'
            }
        },
        translucent : true
    });

    /**
     * Gets the name of the specular map material.
     * @type {String}
     * @readonly
     */
    Material.SpecularMapType = 'SpecularMap';
    Material._materialCache.addMaterial(Material.SpecularMapType, {
        fabric : {
            type : Material.SpecularMapType,
            uniforms : {
                image : Material.DefaultImageId,
                channel : 'r',
                repeat : new Cartesian2(1.0, 1.0)
            },
            components : {
                specular : 'texture2D(image, fract(repeat * materialInput.st)).channel'
            }
        },
        translucent : false
    });

    /**
     * Gets the name of the emmision map material.
     * @type {String}
     * @readonly
     */
    Material.EmissionMapType = 'EmissionMap';
    Material._materialCache.addMaterial(Material.EmissionMapType, {
        fabric : {
            type : Material.EmissionMapType,
            uniforms : {
                image : Material.DefaultImageId,
                channels : 'rgb',
                repeat : new Cartesian2(1.0, 1.0)
            },
            components : {
                emission : 'texture2D(image, fract(repeat * materialInput.st)).channels'
            }
        },
        translucent : false
    });

    /**
     * Gets the name of the bump map material.
     * @type {String}
     * @readonly
     */
    Material.BumpMapType = 'BumpMap';
    Material._materialCache.addMaterial(Material.BumpMapType, {
        fabric : {
            type : Material.BumpMapType,
            uniforms : {
                image : Material.DefaultImageId,
                channel : 'r',
                strength : 0.8,
                repeat : new Cartesian2(1.0, 1.0)
            },
            source : BumpMapMaterial
        },
        translucent : false
    });

    /**
     * Gets the name of the normal map material.
     * @type {String}
     * @readonly
     */
    Material.NormalMapType = 'NormalMap';
    Material._materialCache.addMaterial(Material.NormalMapType, {
        fabric : {
            type : Material.NormalMapType,
            uniforms : {
                image : Material.DefaultImageId,
                channels : 'rgb',
                strength : 0.8,
                repeat : new Cartesian2(1.0, 1.0)
            },
            source : NormalMapMaterial
        },
        translucent : false
    });

    /**
     * Gets the name of the grid material.
     * @type {String}
     * @readonly
     */
    Material.GridType = 'Grid';
    Material._materialCache.addMaterial(Material.GridType, {
        fabric : {
            type : Material.GridType,
            uniforms : {
                color : new Color(0.0, 1.0, 0.0, 1.0),
                cellAlpha : 0.1,
                lineCount : new Cartesian2(8.0, 8.0),
                lineThickness : new Cartesian2(1.0, 1.0),
                lineOffset : new Cartesian2(0.0, 0.0)
            },
            source : GridMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.color.alpha < 1.0) || (uniforms.cellAlpha < 1.0);
        }
    });

    /**
     * Gets the name of the stripe material.
     * @type {String}
     * @readonly
     */
    Material.StripeType = 'Stripe';
    Material._materialCache.addMaterial(Material.StripeType, {
        fabric : {
            type : Material.StripeType,
            uniforms : {
                horizontal : true,
                evenColor : new Color(1.0, 1.0, 1.0, 0.5),
                oddColor : new Color(0.0, 0.0, 1.0, 0.5),
                offset : 0.0,
                repeat : 5.0
            },
            source : StripeMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.evenColor.alpha < 1.0) || (uniforms.oddColor.alpha < 0.0);
        }
    });

    /**
     * Gets the name of the checkerboard material.
     * @type {String}
     * @readonly
     */
    Material.CheckerboardType = 'Checkerboard';
    Material._materialCache.addMaterial(Material.CheckerboardType, {
        fabric : {
            type : Material.CheckerboardType,
            uniforms : {
                lightColor : new Color(1.0, 1.0, 1.0, 0.5),
                darkColor : new Color(0.0, 0.0, 0.0, 0.5),
                repeat : new Cartesian2(5.0, 5.0)
            },
            source : CheckerboardMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.lightColor.alpha < 1.0) || (uniforms.darkColor.alpha < 0.0);
        }
    });

    /**
     * Gets the name of the dot material.
     * @type {String}
     * @readonly
     */
    Material.DotType = 'Dot';
    Material._materialCache.addMaterial(Material.DotType, {
        fabric : {
            type : Material.DotType,
            uniforms : {
                lightColor : new Color(1.0, 1.0, 0.0, 0.75),
                darkColor : new Color(0.0, 1.0, 1.0, 0.75),
                repeat : new Cartesian2(5.0, 5.0)
            },
            source : DotMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.lightColor.alpha < 1.0) || (uniforms.darkColor.alpha < 0.0);
        }
    });

    /**
     * Gets the name of the water material.
     * @type {String}
     * @readonly
     */
    Material.WaterType = 'Water';
    Material._materialCache.addMaterial(Material.WaterType, {
        fabric : {
            type : Material.WaterType,
            uniforms : {
                baseWaterColor : new Color(0.2, 0.3, 0.6, 1.0),
                blendColor : new Color(0.0, 1.0, 0.699, 1.0),
                specularMap : Material.DefaultImageId,
                normalMap : Material.DefaultImageId,
                frequency : 10.0,
                animationSpeed : 0.01,
                amplitude : 1.0,
                specularIntensity : 0.5,
                fadeFactor : 1.0
            },
            source : WaterMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.baseWaterColor.alpha < 1.0) || (uniforms.blendColor.alpha < 0.0);
        }
    });

    /**
     * Gets the name of the rim lighting material.
     * @type {String}
     * @readonly
     */
    Material.RimLightingType = 'RimLighting';
    Material._materialCache.addMaterial(Material.RimLightingType, {
        fabric : {
            type : Material.RimLightingType,
            uniforms : {
                color : new Color(1.0, 0.0, 0.0, 0.7),
                rimColor : new Color(1.0, 1.0, 1.0, 0.4),
                width : 0.3
            },
            source : RimLightingMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.color.alpha < 1.0) || (uniforms.rimColor.alpha < 0.0);
        }
    });

    /**
     * Gets the name of the fade material.
     * @type {String}
     * @readonly
     */
    Material.FadeType = 'Fade';
    Material._materialCache.addMaterial(Material.FadeType, {
        fabric : {
            type : Material.FadeType,
            uniforms : {
                fadeInColor : new Color(1.0, 0.0, 0.0, 1.0),
                fadeOutColor : new Color(0.0, 0.0, 0.0, 0.0),
                maximumDistance : 0.5,
                repeat : true,
                fadeDirection : {
                    x : true,
                    y : true
                },
                time : new Cartesian2(0.5, 0.5)
            },
            source : FadeMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.fadeInColor.alpha < 1.0) || (uniforms.fadeOutColor.alpha < 1.0);
        }
    });

    /**
     * Gets the name of the polyline arrow material.
     * @type {String}
     * @readonly
     */
    Material.PolylineArrowType = 'PolylineArrow';
    Material._materialCache.addMaterial(Material.PolylineArrowType, {
        fabric : {
            type : Material.PolylineArrowType,
            uniforms : {
                color : new Color(1.0, 1.0, 1.0, 1.0)
            },
            source : PolylineArrowMaterial
        },
        translucent : true
    });

    /**
     * Gets the name of the polyline glow material.
     * @type {String}
     * @readonly
     */
    Material.PolylineGlowType = 'PolylineGlow';
    Material._materialCache.addMaterial(Material.PolylineGlowType, {
        fabric : {
            type : Material.PolylineGlowType,
            uniforms : {
                color : new Color(0.0, 0.5, 1.0, 1.0),
                glowPower : 0.25
            },
            source : PolylineGlowMaterial
        },
        translucent : true
    });

    /**
     * Gets the name of the polyline outline material.
     * @type {String}
     * @readonly
     */
    Material.PolylineOutlineType = 'PolylineOutline';
    Material._materialCache.addMaterial(Material.PolylineOutlineType, {
        fabric : {
            type : Material.PolylineOutlineType,
            uniforms : {
                color : new Color(1.0, 1.0, 1.0, 1.0),
                outlineColor : new Color(1.0, 0.0, 0.0, 1.0),
                outlineWidth : 1.0
            },
            source : PolylineOutlineMaterial
        },
        translucent : function(material) {
            var uniforms = material.uniforms;
            return (uniforms.color.alpha < 1.0) || (uniforms.outlineColor.alpha < 1.0);
        }
    });

    return Material;
});

/*global define*/
define('Scene/MaterialAppearance',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/freezeObject',
        '../Core/VertexFormat',
        '../Shaders/Appearances/AllMaterialAppearanceFS',
        '../Shaders/Appearances/AllMaterialAppearanceVS',
        '../Shaders/Appearances/BasicMaterialAppearanceFS',
        '../Shaders/Appearances/BasicMaterialAppearanceVS',
        '../Shaders/Appearances/TexturedMaterialAppearanceFS',
        '../Shaders/Appearances/TexturedMaterialAppearanceVS',
        './Appearance',
        './Material'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        freezeObject,
        VertexFormat,
        AllMaterialAppearanceFS,
        AllMaterialAppearanceVS,
        BasicMaterialAppearanceFS,
        BasicMaterialAppearanceVS,
        TexturedMaterialAppearanceFS,
        TexturedMaterialAppearanceVS,
        Appearance,
        Material) {
    'use strict';

    /**
     * An appearance for arbitrary geometry (as opposed to {@link EllipsoidSurfaceAppearance}, for example)
     * that supports shading with materials.
     *
     * @alias MaterialAppearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.flat=false] When <code>true</code>, flat shading is used in the fragment shader, which means lighting is not taking into account.
     * @param {Boolean} [options.faceForward=!options.closed] When <code>true</code>, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots.  This is useful when both sides of a geometry should be shaded like {@link WallGeometry}.
     * @param {Boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link MaterialAppearance#renderState} has alpha blending enabled.
     * @param {Boolean} [options.closed=false] When <code>true</code>, the geometry is expected to be closed so {@link MaterialAppearance#renderState} has backface culling enabled.
     * @param {MaterialAppearance.MaterialSupport} [options.materialSupport=MaterialAppearance.MaterialSupport.TEXTURED] The type of materials that will be supported.
     * @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Materials.html|Cesium Sandcastle Material Appearance Demo}
     *
     * @example
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *     geometry : new Cesium.WallGeometry({
            materialSupport :  Cesium.MaterialAppearance.MaterialSupport.BASIC.vertexFormat,
     *       // ...
     *     })
     *   }),
     *   appearance : new Cesium.MaterialAppearance({
     *     material : Cesium.Material.fromType('Color'),
     *     faceForward : true
     *   })
     *
     * });
     */
    function MaterialAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var translucent = defaultValue(options.translucent, true);
        var closed = defaultValue(options.closed, false);
        var materialSupport = defaultValue(options.materialSupport, MaterialAppearance.MaterialSupport.TEXTURED);

        /**
         * The material used to determine the fragment color.  Unlike other {@link MaterialAppearance}
         * properties, this is not read-only, so an appearance's material can change on the fly.
         *
         * @type Material
         *
         * @default {@link Material.ColorType}
         *
         * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
         */
        this.material = (defined(options.material)) ? options.material : Material.fromType(Material.ColorType);

        /**
         * When <code>true</code>, the geometry is expected to appear translucent.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.translucent = translucent;

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, materialSupport.vertexShaderSource);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, materialSupport.fragmentShaderSource);
        this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
        this._closed = closed;

        // Non-derived members

        this._materialSupport = materialSupport;
        this._vertexFormat = materialSupport.vertexFormat;
        this._flat = defaultValue(options.flat, false);
        this._faceForward = defaultValue(options.faceForward, !closed);
    }

    defineProperties(MaterialAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.  The full fragment shader
         * source is built procedurally taking into account {@link MaterialAppearance#material},
         * {@link MaterialAppearance#flat}, and {@link MaterialAppearance#faceForward}.
         * Use {@link MaterialAppearance#getFragmentShaderSource} to get the full source.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         * <p>
         * The render state can be explicitly defined when constructing a {@link MaterialAppearance}
         * instance, or it is set implicitly via {@link MaterialAppearance#translucent}
         * and {@link MaterialAppearance#closed}.
         * </p>
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed so
         * {@link MaterialAppearance#renderState} has backface culling enabled.
         * If the viewer enters the geometry, it will not be visible.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        },

        /**
         * The type of materials supported by this instance.  This impacts the required
         * {@link VertexFormat} and the complexity of the vertex and fragment shaders.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {MaterialAppearance.MaterialSupport}
         * @readonly
         *
         * @default {@link MaterialAppearance.MaterialSupport.TEXTURED}
         */
        materialSupport : {
            get : function() {
                return this._materialSupport;
            }
        },

        /**
         * The {@link VertexFormat} that this appearance instance is compatible with.
         * A geometry can have more vertex attributes and still be compatible - at a
         * potential performance cost - but it can't have less.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type VertexFormat
         * @readonly
         *
         * @default {@link MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat}
         */
        vertexFormat : {
            get : function() {
                return this._vertexFormat;
            }
        },

        /**
         * When <code>true</code>, flat shading is used in the fragment shader,
         * which means lighting is not taking into account.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        flat : {
            get : function() {
                return this._flat;
            }
        },

        /**
         * When <code>true</code>, the fragment shader flips the surface normal
         * as needed to ensure that the normal faces the viewer to avoid
         * dark spots.  This is useful when both sides of a geometry should be
         * shaded like {@link WallGeometry}.
         *
         * @memberof MaterialAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        faceForward : {
            get : function() {
                return this._faceForward;
            }
        }
    });

    /**
     * Procedurally creates the full GLSL fragment shader source.  For {@link MaterialAppearance},
     * this is derived from {@link MaterialAppearance#fragmentShaderSource}, {@link MaterialAppearance#material},
     * {@link MaterialAppearance#flat}, and {@link MaterialAppearance#faceForward}.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    MaterialAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link MaterialAppearance#translucent} and {@link Material#isTranslucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    MaterialAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    MaterialAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    /**
     * Determines the type of {@link Material} that is supported by a
     * {@link MaterialAppearance} instance.  This is a trade-off between
     * flexibility (a wide array of materials) and memory/performance
     * (required vertex format and GLSL shader complexity.
     */
    MaterialAppearance.MaterialSupport = {
        /**
         * Only basic materials, which require just <code>position</code> and
         * <code>normal</code> vertex attributes, are supported.
         *
         * @constant
         */
        BASIC : freezeObject({
            vertexFormat : VertexFormat.POSITION_AND_NORMAL,
            vertexShaderSource : BasicMaterialAppearanceVS,
            fragmentShaderSource : BasicMaterialAppearanceFS
        }),
        /**
         * Materials with textures, which require <code>position</code>,
         * <code>normal</code>, and <code>st</code> vertex attributes,
         * are supported.  The vast majority of materials fall into this category.
         *
         * @constant
         */
        TEXTURED : freezeObject({
            vertexFormat : VertexFormat.POSITION_NORMAL_AND_ST,
            vertexShaderSource : TexturedMaterialAppearanceVS,
            fragmentShaderSource : TexturedMaterialAppearanceFS
        }),
        /**
         * All materials, including those that work in tangent space, are supported.
         * This requires <code>position</code>, <code>normal</code>, <code>st</code>,
         * <code>tangent</code>, and <code>bitangent</code> vertex attributes.
         *
         * @constant
         */
        ALL : freezeObject({
            vertexFormat : VertexFormat.ALL,
            vertexShaderSource : AllMaterialAppearanceVS,
            fragmentShaderSource : AllMaterialAppearanceFS
        })
    };

    return MaterialAppearance;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceColorAppearanceFS',[],function() {
    'use strict';
    return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec4 v_color;\n\
\n\
void main()\n\
{\n\
    vec3 positionToEyeEC = -v_positionEC;\n\
    \n\
    vec3 normalEC = normalize(v_normalEC);\n\
#ifdef FACE_FORWARD\n\
    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
    \n\
    czm_materialInput materialInput;\n\
    materialInput.normalEC = normalEC;\n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
    czm_material material = czm_getDefaultMaterial(materialInput);\n\
    material.diffuse = v_color.rgb;\n\
    material.alpha = v_color.a;\n\
    \n\
    gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceColorAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec4 v_color;\n\
\n\
void main() \n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates\n\
    v_normalEC = czm_normal * normal;                         // normal in eye coordinates\n\
    v_color = color;\n\
    \n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceFlatColorAppearanceFS',[],function() {
    'use strict';
    return "varying vec4 v_color;\n\
\n\
void main()\n\
{\n\
    gl_FragColor = v_color;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PerInstanceFlatColorAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
\n\
varying vec4 v_color;\n\
\n\
void main() \n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_color = color;\n\
    \n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/PerInstanceColorAppearance',[
        '../Core/defaultValue',
        '../Core/defineProperties',
        '../Core/VertexFormat',
        '../Shaders/Appearances/PerInstanceColorAppearanceFS',
        '../Shaders/Appearances/PerInstanceColorAppearanceVS',
        '../Shaders/Appearances/PerInstanceFlatColorAppearanceFS',
        '../Shaders/Appearances/PerInstanceFlatColorAppearanceVS',
        './Appearance'
    ], function(
        defaultValue,
        defineProperties,
        VertexFormat,
        PerInstanceColorAppearanceFS,
        PerInstanceColorAppearanceVS,
        PerInstanceFlatColorAppearanceFS,
        PerInstanceFlatColorAppearanceVS,
        Appearance) {
    'use strict';

    /**
     * An appearance for {@link GeometryInstance} instances with color attributes.
     * This allows several geometry instances, each with a different color, to
     * be drawn with the same {@link Primitive} as shown in the second example below.
     *
     * @alias PerInstanceColorAppearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.flat=false] When <code>true</code>, flat shading is used in the fragment shader, which means lighting is not taking into account.
     * @param {Boolean} [options.faceForward=!options.closed] When <code>true</code>, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots.  This is useful when both sides of a geometry should be shaded like {@link WallGeometry}.
     * @param {Boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link PerInstanceColorAppearance#renderState} has alpha blending enabled.
     * @param {Boolean} [options.closed=false] When <code>true</code>, the geometry is expected to be closed so {@link PerInstanceColorAppearance#renderState} has backface culling enabled.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @example
     * // A solid white line segment
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *     geometry : new Cesium.SimplePolylineGeometry({
     *       positions : Cesium.Cartesian3.fromDegreesArray([
     *         0.0, 0.0,
     *         5.0, 0.0
     *       ])
     *     }),
     *     attributes : {
     *       color : Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 1.0, 1.0, 1.0))
     *     }
     *   }),
     *   appearance : new Cesium.PerInstanceColorAppearance({
     *     flat : true,
     *     translucent : false
     *   })
     * });
     *
     * // Two rectangles in a primitive, each with a different color
     * var instance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.RectangleGeometry({
     *     rectangle : Cesium.Rectangle.fromDegrees(0.0, 20.0, 10.0, 30.0)
     *   }),
     *   attributes : {
     *     color : new Cesium.Color(1.0, 0.0, 0.0, 0.5)
     *   }
     * });
     *
     * var anotherInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.RectangleGeometry({
     *     rectangle : Cesium.Rectangle.fromDegrees(0.0, 40.0, 10.0, 50.0)
     *   }),
     *   attributes : {
     *     color : new Cesium.Color(0.0, 0.0, 1.0, 0.5)
     *   }
     * });
     *
     * var rectanglePrimitive = new Cesium.Primitive({
     *   geometryInstances : [instance, anotherInstance],
     *   appearance : new Cesium.PerInstanceColorAppearance()
     * });
     */
    function PerInstanceColorAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var translucent = defaultValue(options.translucent, true);
        var closed = defaultValue(options.closed, false);
        var flat = defaultValue(options.flat, false);
        var vs = flat ? PerInstanceFlatColorAppearanceVS : PerInstanceColorAppearanceVS;
        var fs = flat ? PerInstanceFlatColorAppearanceFS : PerInstanceColorAppearanceFS;
        var vertexFormat = flat ? PerInstanceColorAppearance.FLAT_VERTEX_FORMAT : PerInstanceColorAppearance.VERTEX_FORMAT;

        /**
         * This property is part of the {@link Appearance} interface, but is not
         * used by {@link PerInstanceColorAppearance} since a fully custom fragment shader is used.
         *
         * @type Material
         *
         * @default undefined
         */
        this.material = undefined;

        /**
         * When <code>true</code>, the geometry is expected to appear translucent so
         * {@link PerInstanceColorAppearance#renderState} has alpha blending enabled.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.translucent = translucent;

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, vs);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs);
        this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
        this._closed = closed;

        // Non-derived members

        this._vertexFormat = vertexFormat;
        this._flat = flat;
        this._faceForward = defaultValue(options.faceForward, !closed);
    }

    defineProperties(PerInstanceColorAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         * <p>
         * The render state can be explicitly defined when constructing a {@link PerInstanceColorAppearance}
         * instance, or it is set implicitly via {@link PerInstanceColorAppearance#translucent}
         * and {@link PerInstanceColorAppearance#closed}.
         * </p>
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed so
         * {@link PerInstanceColorAppearance#renderState} has backface culling enabled.
         * If the viewer enters the geometry, it will not be visible.
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        },

        /**
         * The {@link VertexFormat} that this appearance instance is compatible with.
         * A geometry can have more vertex attributes and still be compatible - at a
         * potential performance cost - but it can't have less.
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type VertexFormat
         * @readonly
         */
        vertexFormat : {
            get : function() {
                return this._vertexFormat;
            }
        },

        /**
         * When <code>true</code>, flat shading is used in the fragment shader,
         * which means lighting is not taking into account.
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        flat : {
            get : function() {
                return this._flat;
            }
        },

        /**
         * When <code>true</code>, the fragment shader flips the surface normal
         * as needed to ensure that the normal faces the viewer to avoid
         * dark spots.  This is useful when both sides of a geometry should be
         * shaded like {@link WallGeometry}.
         *
         * @memberof PerInstanceColorAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        faceForward : {
            get : function() {
                return this._faceForward;
            }
        }
    });

    /**
     * The {@link VertexFormat} that all {@link PerInstanceColorAppearance} instances
     * are compatible with.  This requires only <code>position</code> and <code>st</code>
     * attributes.
     *
     * @type VertexFormat
     *
     * @constant
     */
    PerInstanceColorAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_NORMAL;

    /**
     * The {@link VertexFormat} that all {@link PerInstanceColorAppearance} instances
     * are compatible with when {@link PerInstanceColorAppearance#flat} is <code>false</code>.
     * This requires only a <code>position</code> attribute.
     *
     * @type VertexFormat
     *
     * @constant
     */
    PerInstanceColorAppearance.FLAT_VERTEX_FORMAT = VertexFormat.POSITION_ONLY;

    /**
     * Procedurally creates the full GLSL fragment shader source.  For {@link PerInstanceColorAppearance},
     * this is derived from {@link PerInstanceColorAppearance#fragmentShaderSource}, {@link PerInstanceColorAppearance#flat},
     * and {@link PerInstanceColorAppearance#faceForward}.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    PerInstanceColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link PerInstanceColorAppearance#translucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    PerInstanceColorAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    PerInstanceColorAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    return PerInstanceColorAppearance;
});

/*global define*/
define('Renderer/BufferUsage',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var BufferUsage = {
        STREAM_DRAW : WebGLConstants.STREAM_DRAW,
        STATIC_DRAW : WebGLConstants.STATIC_DRAW,
        DYNAMIC_DRAW : WebGLConstants.DYNAMIC_DRAW,

        validate : function(bufferUsage) {
            return ((bufferUsage === BufferUsage.STREAM_DRAW) ||
                    (bufferUsage === BufferUsage.STATIC_DRAW) ||
                    (bufferUsage === BufferUsage.DYNAMIC_DRAW));
        }
    };

    return freezeObject(BufferUsage);
});

/*global define*/
define('Renderer/DrawCommand',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/PrimitiveType'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        PrimitiveType) {
    'use strict';

    /**
     * Represents a command to the renderer for drawing.
     *
     * @private
     */
    function DrawCommand(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._boundingVolume = options.boundingVolume;
        this._orientedBoundingBox = options.orientedBoundingBox;
        this._cull = defaultValue(options.cull, true);
        this._modelMatrix = options.modelMatrix;
        this._primitiveType = defaultValue(options.primitiveType, PrimitiveType.TRIANGLES);
        this._vertexArray = options.vertexArray;
        this._count = options.count;
        this._offset = defaultValue(options.offset, 0);
        this._instanceCount = defaultValue(options.instanceCount, 0);
        this._shaderProgram = options.shaderProgram;
        this._uniformMap = options.uniformMap;
        this._renderState = options.renderState;
        this._framebuffer = options.framebuffer;
        this._pass = options.pass;
        this._executeInClosestFrustum = defaultValue(options.executeInClosestFrustum, false);
        this._owner = options.owner;
        this._debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
        this._debugOverlappingFrustums = 0;
        this._castShadows = defaultValue(options.castShadows, false);
        this._receiveShadows = defaultValue(options.receiveShadows, false);

        this.dirty = true;
        this.lastDirtyTime = 0;

        /**
         * @private
         */
        this.derivedCommands = {};
    }

    defineProperties(DrawCommand.prototype, {
        /**
         * The bounding volume of the geometry in world space.  This is used for culling and frustum selection.
         * <p>
         * For best rendering performance, use the tightest possible bounding volume.  Although
         * <code>undefined</code> is allowed, always try to provide a bounding volume to
         * allow the tightest possible near and far planes to be computed for the scene, and
         * minimize the number of frustums needed.
         * </p>
         *
         * @memberof DrawCommand.prototype
         * @type {Object}
         * @default undefined
         *
         * @see DrawCommand#debugShowBoundingVolume
         */
        boundingVolume : {
            get : function() {
                return this._boundingVolume;
            },
            set : function(value) {
                if (this._boundingVolume !== value) {
                    this._boundingVolume = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The oriented bounding box of the geometry in world space. If this is defined, it is used instead of
         * {@link DrawCommand#boundingVolume} for plane intersection testing.
         *
         * @memberof DrawCommand.prototype
         * @type {OrientedBoundingBox}
         * @default undefined
         *
         * @see DrawCommand#debugShowBoundingVolume
         */
        orientedBoundingBox : {
            get : function() {
                return this._orientedBoundingBox;
            },
            set : function(value) {
                if (this._orientedBoundingBox !== value) {
                    this._orientedBoundingBox = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * When <code>true</code>, the renderer frustum and horizon culls the command based on its {@link DrawCommand#boundingVolume}.
         * If the command was already culled, set this to <code>false</code> for a performance improvement.
         *
         * @memberof DrawCommand.prototype
         * @type {Boolean}
         * @default true
         */
        cull : {
            get : function() {
                return this._cull;
            },
            set : function(value) {
                if (this._cull !== value) {
                    this._cull = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The transformation from the geometry in model space to world space.
         * <p>
         * When <code>undefined</code>, the geometry is assumed to be defined in world space.
         * </p>
         *
         * @memberof DrawCommand.prototype
         * @type {Matrix4}
         * @default undefined
         */
        modelMatrix : {
            get : function() {
                return this._modelMatrix;
            },
            set : function(value) {
                if (this._modelMatrix !== value) {
                    this._modelMatrix = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The type of geometry in the vertex array.
         *
         * @memberof DrawCommand.prototype
         * @type {PrimitiveType}
         * @default PrimitiveType.TRIANGLES
         */
        primitiveType : {
            get : function() {
                return this._primitiveType;
            },
            set : function(value) {
                if (this._primitiveType !== value) {
                    this._primitiveType = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The vertex array.
         *
         * @memberof DrawCommand.prototype
         * @type {VertexArray}
         * @default undefined
         */
        vertexArray : {
            get : function() {
                return this._vertexArray;
            },
            set : function(value) {
                if (this._vertexArray !== value) {
                    this._vertexArray = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The number of vertices to draw in the vertex array.
         *
         * @memberof DrawCommand.prototype
         * @type {Number}
         * @default undefined
         */
        count : {
            get : function() {
                return this._count;
            },
            set : function(value) {
                if (this._count !== value) {
                    this._count = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The offset to start drawing in the vertex array.
         *
         * @memberof DrawCommand.prototype
         * @type {Number}
         * @default 0
         */
        offset : {
            get : function() {
                return this._offset;
            },
            set : function(value) {
                if (this._offset !== value) {
                    this._offset = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The number of instances to draw.
         *
         * @memberof DrawCommand.prototype
         * @type {Number}
         * @default 0
         */
        instanceCount : {
            get : function() {
                return this._instanceCount;
            },
            set : function(value) {
                if (this._instanceCount !== value) {
                    this._instanceCount = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The shader program to apply.
         *
         * @memberof DrawCommand.prototype
         * @type {ShaderProgram}
         * @default undefined
         */
        shaderProgram : {
            get : function() {
                return this._shaderProgram;
            },
            set : function(value) {
                if (this._shaderProgram !== value) {
                    this._shaderProgram = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * Whether this command should cast shadows when shadowing is enabled.
         *
         * @memberof DrawCommand.prototype
         * @type {Boolean}
         * @default false
         */
        castShadows : {
            get : function() {
                return this._castShadows;
            },
            set : function(value) {
                if (this._castShadows !== value) {
                    this._castShadows = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * Whether this command should receive shadows when shadowing is enabled.
         *
         * @memberof DrawCommand.prototype
         * @type {Boolean}
         * @default false
         */
        receiveShadows : {
            get : function() {
                return this._receiveShadows;
            },
            set : function(value) {
                if (this._receiveShadows !== value) {
                    this._receiveShadows = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * An object with functions whose names match the uniforms in the shader program
         * and return values to set those uniforms.
         *
         * @memberof DrawCommand.prototype
         * @type {Object}
         * @default undefined
         */
        uniformMap : {
            get : function() {
                return this._uniformMap;
            },
            set : function(value) {
                if (this._uniformMap !== value) {
                    this._uniformMap = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The render state.
         *
         * @memberof DrawCommand.prototype
         * @type {RenderState}
         * @default undefined
         */
        renderState : {
            get : function() {
                return this._renderState;
            },
            set : function(value) {
                if (this._renderState !== value) {
                    this._renderState = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The framebuffer to draw to.
         *
         * @memberof DrawCommand.prototype
         * @type {Framebuffer}
         * @default undefined
         */
        framebuffer : {
            get : function() {
                return this._framebuffer;
            },
            set : function(value) {
                if (this._framebuffer !== value) {
                    this._framebuffer = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The pass when to render.
         *
         * @memberof DrawCommand.prototype
         * @type {Pass}
         * @default undefined
         */
        pass : {
            get : function() {
                return this._pass;
            },
            set : function(value) {
                if (this._pass !== value) {
                    this._pass = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * Specifies if this command is only to be executed in the frustum closest
         * to the eye containing the bounding volume. Defaults to <code>false</code>.
         *
         * @memberof DrawCommand.prototype
         * @type {Boolean}
         * @default false
         */
        executeInClosestFrustum : {
            get : function() {
                return this._executeInClosestFrustum;
            },
            set : function(value) {
                if (this._executeInClosestFrustum !== value) {
                    this._executeInClosestFrustum = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * The object who created this command.  This is useful for debugging command
         * execution; it allows us to see who created a command when we only have a
         * reference to the command, and can be used to selectively execute commands
         * with {@link Scene#debugCommandFilter}.
         *
         * @memberof DrawCommand.prototype
         * @type {Object}
         * @default undefined
         *
         * @see Scene#debugCommandFilter
         */
        owner : {
            get : function() {
                return this._owner;
            },
            set : function(value) {
                if (this._owner !== value) {
                    this._owner = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the {@link DrawCommand#boundingVolume} for this command, assuming it is a sphere, when the command executes.
         * </p>
         *
         * @memberof DrawCommand.prototype
         * @type {Boolean}
         * @default false
         *
         * @see DrawCommand#boundingVolume
         */
        debugShowBoundingVolume : {
            get : function() {
                return this._debugShowBoundingVolume;
            },
            set : function(value) {
                if (this._debugShowBoundingVolume !== value) {
                    this._debugShowBoundingVolume = value;
                    this.dirty = true;
                }
            }
        },

        /**
         * Used to implement Scene.debugShowFrustums.
         * @private
         */
        debugOverlappingFrustums : {
            get : function() {
                return this._debugOverlappingFrustums;
            },
            set : function(value) {
                if (this._debugOverlappingFrustums !== value) {
                    this._debugOverlappingFrustums = value;
                    this.dirty = true;
                }
            }
        }
    });

    /**
     * @private
     */
    DrawCommand.shallowClone = function(command, result) {
        if (!defined(command)) {
            return undefined;
        }
        if (!defined(result)) {
            result = new DrawCommand();
        }

        result._boundingVolume = command._boundingVolume;
        result._orientedBoundingBox = command._orientedBoundingBox;
        result._cull = command._cull;
        result._modelMatrix = command._modelMatrix;
        result._primitiveType = command._primitiveType;
        result._vertexArray = command._vertexArray;
        result._count = command._count;
        result._offset = command._offset;
        result._instanceCount = command._instanceCount;
        result._shaderProgram = command._shaderProgram;
        result._uniformMap = command._uniformMap;
        result._renderState = command._renderState;
        result._framebuffer = command._framebuffer;
        result._pass = command._pass;
        result._executeInClosestFrustum = command._executeInClosestFrustum;
        result._owner = command._owner;
        result._debugShowBoundingVolume = command._debugShowBoundingVolume;
        result._debugOverlappingFrustums = command._debugOverlappingFrustums;
        result._castShadows = command._castShadows;
        result._receiveShadows = command._receiveShadows;

        result.dirty = true;
        result.lastDirtyTime = 0;

        return result;
    };

    /**
     * Executes the draw command.
     *
     * @param {Context} context The renderer context in which to draw.
     * @param {PassState} [passState] The state for the current render pass.
     */
    DrawCommand.prototype.execute = function(context, passState) {
        context.draw(this, passState);
    };

    return DrawCommand;
});

/*global define*/
define('Renderer/Pass',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The render pass for a command.
     *
     * @private
     */
    var Pass = {
        // If you add/modify/remove Pass constants, also change the automatic GLSL constants
        // that start with 'czm_pass'
        //
        // Commands are executed in order by pass up to the translucent pass.
        // Translucent geometry needs special handling (sorting/OIT). The compute pass
        // is executed first and the overlay pass is executed last. Both are not sorted
        // by frustum.
        ENVIRONMENT : 0,
        COMPUTE : 1,
        GLOBE : 2,
        GROUND : 3,
        OPAQUE : 4,
        TRANSLUCENT : 5,
        OVERLAY : 6,
        NUMBER_OF_PASSES : 7
    };

    return freezeObject(Pass);
});

/*global define*/
define('Renderer/RenderState',[
        '../Core/BoundingRectangle',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/WebGLConstants',
        '../Core/WindingOrder',
        './ContextLimits'
    ], function(
        BoundingRectangle,
        Color,
        defaultValue,
        defined,
        DeveloperError,
        WebGLConstants,
        WindingOrder,
        ContextLimits) {
    'use strict';

    function validateBlendEquation(blendEquation) {
        return ((blendEquation === WebGLConstants.FUNC_ADD) ||
                (blendEquation === WebGLConstants.FUNC_SUBTRACT) ||
                (blendEquation === WebGLConstants.FUNC_REVERSE_SUBTRACT));
    }

    function validateBlendFunction(blendFunction) {
        return ((blendFunction === WebGLConstants.ZERO) ||
                (blendFunction === WebGLConstants.ONE) ||
                (blendFunction === WebGLConstants.SRC_COLOR) ||
                (blendFunction === WebGLConstants.ONE_MINUS_SRC_COLOR) ||
                (blendFunction === WebGLConstants.DST_COLOR) ||
                (blendFunction === WebGLConstants.ONE_MINUS_DST_COLOR) ||
                (blendFunction === WebGLConstants.SRC_ALPHA) ||
                (blendFunction === WebGLConstants.ONE_MINUS_SRC_ALPHA) ||
                (blendFunction === WebGLConstants.DST_ALPHA) ||
                (blendFunction === WebGLConstants.ONE_MINUS_DST_ALPHA) ||
                (blendFunction === WebGLConstants.CONSTANT_COLOR) ||
                (blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_COLOR) ||
                (blendFunction === WebGLConstants.CONSTANT_ALPHA) ||
                (blendFunction === WebGLConstants.ONE_MINUS_CONSTANT_ALPHA) ||
                (blendFunction === WebGLConstants.SRC_ALPHA_SATURATE));
    }

    function validateCullFace(cullFace) {
        return ((cullFace === WebGLConstants.FRONT) ||
                (cullFace === WebGLConstants.BACK) ||
                (cullFace === WebGLConstants.FRONT_AND_BACK));
    }

    function validateDepthFunction(depthFunction) {
        return ((depthFunction === WebGLConstants.NEVER) ||
                (depthFunction === WebGLConstants.LESS) ||
                (depthFunction === WebGLConstants.EQUAL) ||
                (depthFunction === WebGLConstants.LEQUAL) ||
                (depthFunction === WebGLConstants.GREATER) ||
                (depthFunction === WebGLConstants.NOTEQUAL) ||
                (depthFunction === WebGLConstants.GEQUAL) ||
                (depthFunction === WebGLConstants.ALWAYS));
    }

    function validateStencilFunction (stencilFunction) {
        return ((stencilFunction === WebGLConstants.NEVER) ||
                (stencilFunction === WebGLConstants.LESS) ||
                (stencilFunction === WebGLConstants.EQUAL) ||
                (stencilFunction === WebGLConstants.LEQUAL) ||
                (stencilFunction === WebGLConstants.GREATER) ||
                (stencilFunction === WebGLConstants.NOTEQUAL) ||
                (stencilFunction === WebGLConstants.GEQUAL) ||
                (stencilFunction === WebGLConstants.ALWAYS));
    }

    function validateStencilOperation(stencilOperation) {
        return ((stencilOperation === WebGLConstants.ZERO) ||
                (stencilOperation === WebGLConstants.KEEP) ||
                (stencilOperation === WebGLConstants.REPLACE) ||
                (stencilOperation === WebGLConstants.INCR) ||
                (stencilOperation === WebGLConstants.DECR) ||
                (stencilOperation === WebGLConstants.INVERT) ||
                (stencilOperation === WebGLConstants.INCR_WRAP) ||
                (stencilOperation === WebGLConstants.DECR_WRAP));
    }

    /**
     * @private
     */
    function RenderState(renderState) {
        var rs = defaultValue(renderState, {});
        var cull = defaultValue(rs.cull, {});
        var polygonOffset = defaultValue(rs.polygonOffset, {});
        var scissorTest = defaultValue(rs.scissorTest, {});
        var scissorTestRectangle = defaultValue(scissorTest.rectangle, {});
        var depthRange = defaultValue(rs.depthRange, {});
        var depthTest = defaultValue(rs.depthTest, {});
        var colorMask = defaultValue(rs.colorMask, {});
        var blending = defaultValue(rs.blending, {});
        var blendingColor = defaultValue(blending.color, {});
        var stencilTest = defaultValue(rs.stencilTest, {});
        var stencilTestFrontOperation = defaultValue(stencilTest.frontOperation, {});
        var stencilTestBackOperation = defaultValue(stencilTest.backOperation, {});
        var sampleCoverage = defaultValue(rs.sampleCoverage, {});
        var viewport = rs.viewport;

        this.frontFace = defaultValue(rs.frontFace, WindingOrder.COUNTER_CLOCKWISE);
        this.cull = {
            enabled : defaultValue(cull.enabled, false),
            face : defaultValue(cull.face, WebGLConstants.BACK)
        };
        this.lineWidth = defaultValue(rs.lineWidth, 1.0);
        this.polygonOffset = {
            enabled : defaultValue(polygonOffset.enabled, false),
            factor : defaultValue(polygonOffset.factor, 0),
            units : defaultValue(polygonOffset.units, 0)
        };
        this.scissorTest = {
            enabled : defaultValue(scissorTest.enabled, false),
            rectangle : BoundingRectangle.clone(scissorTestRectangle)
        };
        this.depthRange = {
            near : defaultValue(depthRange.near, 0),
            far : defaultValue(depthRange.far, 1)
        };
        this.depthTest = {
            enabled : defaultValue(depthTest.enabled, false),
            func : defaultValue(depthTest.func, WebGLConstants.LESS) // func, because function is a JavaScript keyword
        };
        this.colorMask = {
            red : defaultValue(colorMask.red, true),
            green : defaultValue(colorMask.green, true),
            blue : defaultValue(colorMask.blue, true),
            alpha : defaultValue(colorMask.alpha, true)
        };
        this.depthMask = defaultValue(rs.depthMask, true);
        this.stencilMask = defaultValue(rs.stencilMask, ~0);
        this.blending = {
            enabled : defaultValue(blending.enabled, false),
            color : new Color(
                defaultValue(blendingColor.red, 0.0),
                defaultValue(blendingColor.green, 0.0),
                defaultValue(blendingColor.blue, 0.0),
                defaultValue(blendingColor.alpha, 0.0)
            ),
            equationRgb : defaultValue(blending.equationRgb, WebGLConstants.FUNC_ADD),
            equationAlpha : defaultValue(blending.equationAlpha, WebGLConstants.FUNC_ADD),
            functionSourceRgb : defaultValue(blending.functionSourceRgb, WebGLConstants.ONE),
            functionSourceAlpha : defaultValue(blending.functionSourceAlpha, WebGLConstants.ONE),
            functionDestinationRgb : defaultValue(blending.functionDestinationRgb, WebGLConstants.ZERO),
            functionDestinationAlpha : defaultValue(blending.functionDestinationAlpha, WebGLConstants.ZERO)
        };
        this.stencilTest = {
            enabled : defaultValue(stencilTest.enabled, false),
            frontFunction : defaultValue(stencilTest.frontFunction, WebGLConstants.ALWAYS),
            backFunction : defaultValue(stencilTest.backFunction, WebGLConstants.ALWAYS),
            reference : defaultValue(stencilTest.reference, 0),
            mask : defaultValue(stencilTest.mask, ~0),
            frontOperation : {
                fail : defaultValue(stencilTestFrontOperation.fail, WebGLConstants.KEEP),
                zFail : defaultValue(stencilTestFrontOperation.zFail, WebGLConstants.KEEP),
                zPass : defaultValue(stencilTestFrontOperation.zPass, WebGLConstants.KEEP)
            },
            backOperation : {
                fail : defaultValue(stencilTestBackOperation.fail, WebGLConstants.KEEP),
                zFail : defaultValue(stencilTestBackOperation.zFail, WebGLConstants.KEEP),
                zPass : defaultValue(stencilTestBackOperation.zPass, WebGLConstants.KEEP)
            }
        };
        this.sampleCoverage = {
            enabled : defaultValue(sampleCoverage.enabled, false),
            value : defaultValue(sampleCoverage.value, 1.0),
            invert : defaultValue(sampleCoverage.invert, false)
        };
        this.viewport = (defined(viewport)) ? new BoundingRectangle(viewport.x, viewport.y, viewport.width, viewport.height) : undefined;

                if ((this.lineWidth < ContextLimits.minimumAliasedLineWidth) ||
                (this.lineWidth > ContextLimits.maximumAliasedLineWidth)) {
                throw new DeveloperError('renderState.lineWidth is out of range.  Check minimumAliasedLineWidth and maximumAliasedLineWidth.');
        }
        if (!WindingOrder.validate(this.frontFace)) {
            throw new DeveloperError('Invalid renderState.frontFace.');
        }
        if (!validateCullFace(this.cull.face)) {
            throw new DeveloperError('Invalid renderState.cull.face.');
        }
        if ((this.scissorTest.rectangle.width < 0) ||
            (this.scissorTest.rectangle.height < 0)) {
            throw new DeveloperError('renderState.scissorTest.rectangle.width and renderState.scissorTest.rectangle.height must be greater than or equal to zero.');
        }
        if (this.depthRange.near > this.depthRange.far) {
            // WebGL specific - not an error in GL ES
            throw new DeveloperError('renderState.depthRange.near can not be greater than renderState.depthRange.far.');
        }
        if (this.depthRange.near < 0) {
            // Would be clamped by GL
            throw new DeveloperError('renderState.depthRange.near must be greater than or equal to zero.');
        }
        if (this.depthRange.far > 1) {
            // Would be clamped by GL
            throw new DeveloperError('renderState.depthRange.far must be less than or equal to one.');
        }
        if (!validateDepthFunction(this.depthTest.func)) {
            throw new DeveloperError('Invalid renderState.depthTest.func.');
        }
        if ((this.blending.color.red < 0.0) || (this.blending.color.red > 1.0) ||
            (this.blending.color.green < 0.0) || (this.blending.color.green > 1.0) ||
            (this.blending.color.blue < 0.0) || (this.blending.color.blue > 1.0) ||
            (this.blending.color.alpha < 0.0) || (this.blending.color.alpha > 1.0)) {
            // Would be clamped by GL
            throw new DeveloperError('renderState.blending.color components must be greater than or equal to zero and less than or equal to one.');
        }
        if (!validateBlendEquation(this.blending.equationRgb)) {
            throw new DeveloperError('Invalid renderState.blending.equationRgb.');
        }
        if (!validateBlendEquation(this.blending.equationAlpha)) {
            throw new DeveloperError('Invalid renderState.blending.equationAlpha.');
        }
        if (!validateBlendFunction(this.blending.functionSourceRgb)) {
            throw new DeveloperError('Invalid renderState.blending.functionSourceRgb.');
        }
        if (!validateBlendFunction(this.blending.functionSourceAlpha)) {
            throw new DeveloperError('Invalid renderState.blending.functionSourceAlpha.');
        }
        if (!validateBlendFunction(this.blending.functionDestinationRgb)) {
            throw new DeveloperError('Invalid renderState.blending.functionDestinationRgb.');
        }
        if (!validateBlendFunction(this.blending.functionDestinationAlpha)) {
            throw new DeveloperError('Invalid renderState.blending.functionDestinationAlpha.');
        }
        if (!validateStencilFunction(this.stencilTest.frontFunction)) {
            throw new DeveloperError('Invalid renderState.stencilTest.frontFunction.');
        }
        if (!validateStencilFunction(this.stencilTest.backFunction)) {
            throw new DeveloperError('Invalid renderState.stencilTest.backFunction.');
        }
        if (!validateStencilOperation(this.stencilTest.frontOperation.fail)) {
            throw new DeveloperError('Invalid renderState.stencilTest.frontOperation.fail.');
        }
        if (!validateStencilOperation(this.stencilTest.frontOperation.zFail)) {
            throw new DeveloperError('Invalid renderState.stencilTest.frontOperation.zFail.');
        }
        if (!validateStencilOperation(this.stencilTest.frontOperation.zPass)) {
            throw new DeveloperError('Invalid renderState.stencilTest.frontOperation.zPass.');
        }
        if (!validateStencilOperation(this.stencilTest.backOperation.fail)) {
            throw new DeveloperError('Invalid renderState.stencilTest.backOperation.fail.');
        }
        if (!validateStencilOperation(this.stencilTest.backOperation.zFail)) {
            throw new DeveloperError('Invalid renderState.stencilTest.backOperation.zFail.');
        }
        if (!validateStencilOperation(this.stencilTest.backOperation.zPass)) {
            throw new DeveloperError('Invalid renderState.stencilTest.backOperation.zPass.');
        }

        if (defined(this.viewport)) {
            if (this.viewport.width < 0) {
                throw new DeveloperError('renderState.viewport.width must be greater than or equal to zero.');
            }
            if (this.viewport.height < 0) {
                throw new DeveloperError('renderState.viewport.height must be greater than or equal to zero.');
            }

            if (this.viewport.width > ContextLimits.maximumViewportWidth) {
                throw new DeveloperError('renderState.viewport.width must be less than or equal to the maximum viewport width (' + ContextLimits.maximumViewportWidth.toString() + ').  Check maximumViewportWidth.');
            }
            if (this.viewport.height > ContextLimits.maximumViewportHeight) {
                throw new DeveloperError('renderState.viewport.height must be less than or equal to the maximum viewport height (' + ContextLimits.maximumViewportHeight.toString() + ').  Check maximumViewportHeight.');
            }
        }
        
        this.id = 0;
        this._applyFunctions = [];
    }

    var nextRenderStateId = 0;
    var renderStateCache = {};

    /**
     * Validates and then finds or creates an immutable render state, which defines the pipeline
     * state for a {@link DrawCommand} or {@link ClearCommand}.  All inputs states are optional.  Omitted states
     * use the defaults shown in the example below.
     *
     * @param {Object} [renderState] The states defining the render state as shown in the example below.
     *
     * @exception {RuntimeError} renderState.lineWidth is out of range.
     * @exception {DeveloperError} Invalid renderState.frontFace.
     * @exception {DeveloperError} Invalid renderState.cull.face.
     * @exception {DeveloperError} scissorTest.rectangle.width and scissorTest.rectangle.height must be greater than or equal to zero.
     * @exception {DeveloperError} renderState.depthRange.near can't be greater than renderState.depthRange.far.
     * @exception {DeveloperError} renderState.depthRange.near must be greater than or equal to zero.
     * @exception {DeveloperError} renderState.depthRange.far must be less than or equal to zero.
     * @exception {DeveloperError} Invalid renderState.depthTest.func.
     * @exception {DeveloperError} renderState.blending.color components must be greater than or equal to zero and less than or equal to one
     * @exception {DeveloperError} Invalid renderState.blending.equationRgb.
     * @exception {DeveloperError} Invalid renderState.blending.equationAlpha.
     * @exception {DeveloperError} Invalid renderState.blending.functionSourceRgb.
     * @exception {DeveloperError} Invalid renderState.blending.functionSourceAlpha.
     * @exception {DeveloperError} Invalid renderState.blending.functionDestinationRgb.
     * @exception {DeveloperError} Invalid renderState.blending.functionDestinationAlpha.
     * @exception {DeveloperError} Invalid renderState.stencilTest.frontFunction.
     * @exception {DeveloperError} Invalid renderState.stencilTest.backFunction.
     * @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.fail.
     * @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zFail.
     * @exception {DeveloperError} Invalid renderState.stencilTest.frontOperation.zPass.
     * @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.fail.
     * @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zFail.
     * @exception {DeveloperError} Invalid renderState.stencilTest.backOperation.zPass.
     * @exception {DeveloperError} renderState.viewport.width must be greater than or equal to zero.
     * @exception {DeveloperError} renderState.viewport.width must be less than or equal to the maximum viewport width.
     * @exception {DeveloperError} renderState.viewport.height must be greater than or equal to zero.
     * @exception {DeveloperError} renderState.viewport.height must be less than or equal to the maximum viewport height.
     *
     *
     * @example
     * var defaults = {
     *     frontFace : WindingOrder.COUNTER_CLOCKWISE,
     *     cull : {
     *         enabled : false,
     *         face : CullFace.BACK
     *     },
     *     lineWidth : 1,
     *     polygonOffset : {
     *         enabled : false,
     *         factor : 0,
     *         units : 0
     *     },
     *     scissorTest : {
     *         enabled : false,
     *         rectangle : {
     *             x : 0,
     *             y : 0,
     *             width : 0,
     *             height : 0
     *         }
     *     },
     *     depthRange : {
     *         near : 0,
     *         far : 1
     *     },
     *     depthTest : {
     *         enabled : false,
     *         func : DepthFunction.LESS
     *      },
     *     colorMask : {
     *         red : true,
     *         green : true,
     *         blue : true,
     *         alpha : true
     *     },
     *     depthMask : true,
     *     stencilMask : ~0,
     *     blending : {
     *         enabled : false,
     *         color : {
     *             red : 0.0,
     *             green : 0.0,
     *             blue : 0.0,
     *             alpha : 0.0
     *         },
     *         equationRgb : BlendEquation.ADD,
     *         equationAlpha : BlendEquation.ADD,
     *         functionSourceRgb : BlendFunction.ONE,
     *         functionSourceAlpha : BlendFunction.ONE,
     *         functionDestinationRgb : BlendFunction.ZERO,
     *         functionDestinationAlpha : BlendFunction.ZERO
     *     },
     *     stencilTest : {
     *         enabled : false,
     *         frontFunction : StencilFunction.ALWAYS,
     *         backFunction : StencilFunction.ALWAYS,
     *         reference : 0,
     *         mask : ~0,
     *         frontOperation : {
     *             fail : StencilOperation.KEEP,
     *             zFail : StencilOperation.KEEP,
     *             zPass : StencilOperation.KEEP
     *         },
     *         backOperation : {
     *             fail : StencilOperation.KEEP,
     *             zFail : StencilOperation.KEEP,
     *             zPass : StencilOperation.KEEP
     *         }
     *     },
     *     sampleCoverage : {
     *         enabled : false,
     *         value : 1.0,
     *         invert : false
     *      }
     * };
     *
     * var rs = RenderState.fromCache(defaults);
     *
     * @see DrawCommand
     * @see ClearCommand
     *
     * @private
     */
    RenderState.fromCache = function(renderState) {
        var partialKey = JSON.stringify(renderState);
        var cachedState = renderStateCache[partialKey];
        if (defined(cachedState)) {
            ++cachedState.referenceCount;
            return cachedState.state;
        }

        // Cache miss.  Fully define render state and try again.
        var states = new RenderState(renderState);
        var fullKey = JSON.stringify(states);
        cachedState = renderStateCache[fullKey];
        if (!defined(cachedState)) {
            states.id = nextRenderStateId++;

            cachedState = {
                referenceCount : 0,
                state : states
            };

            // Cache full render state.  Multiple partially defined render states may map to this.
            renderStateCache[fullKey] = cachedState;
        }

        ++cachedState.referenceCount;

        // Cache partial render state so we can skip validation on a cache hit for a partially defined render state
        renderStateCache[partialKey] = {
            referenceCount : 1,
            state : cachedState.state
        };

        return cachedState.state;
    };

    /**
     * @private
     */
    RenderState.removeFromCache = function(renderState) {
        var states = new RenderState(renderState);
        var fullKey = JSON.stringify(states);
        var fullCachedState = renderStateCache[fullKey];

        // decrement partial key reference count
        var partialKey = JSON.stringify(renderState);
        var cachedState = renderStateCache[partialKey];
        if (defined(cachedState)) {
            --cachedState.referenceCount;

            if (cachedState.referenceCount === 0) {
                // remove partial key
                delete renderStateCache[partialKey];

                // decrement full key reference count
                if (defined(fullCachedState)) {
                    --fullCachedState.referenceCount;
                }
            }
        }

        // remove full key if reference count is zero
        if (defined(fullCachedState) && (fullCachedState.referenceCount === 0)) {
            delete renderStateCache[fullKey];
        }
    };

    /**
     * This function is for testing purposes only.
     * @private
     */
    RenderState.getCache = function() {
        return renderStateCache;
    };

    /**
     * This function is for testing purposes only.
     * @private
     */
    RenderState.clearCache = function() {
        renderStateCache = {};
    };

    function enableOrDisable(gl, glEnum, enable) {
        if (enable) {
            gl.enable(glEnum);
        } else {
            gl.disable(glEnum);
        }
    }

    function applyFrontFace(gl, renderState) {
        gl.frontFace(renderState.frontFace);
    }

    function applyCull(gl, renderState) {
        var cull = renderState.cull;
        var enabled = cull.enabled;

        enableOrDisable(gl, gl.CULL_FACE, enabled);

        if (enabled) {
            gl.cullFace(cull.face);
        }
    }

    function applyLineWidth(gl, renderState) {
        gl.lineWidth(renderState.lineWidth);
    }

    function applyPolygonOffset(gl, renderState) {
        var polygonOffset = renderState.polygonOffset;
        var enabled = polygonOffset.enabled;

        enableOrDisable(gl, gl.POLYGON_OFFSET_FILL, enabled);

        if (enabled) {
            gl.polygonOffset(polygonOffset.factor, polygonOffset.units);
        }
    }

    function applyScissorTest(gl, renderState, passState) {
        var scissorTest = renderState.scissorTest;
        var enabled = (defined(passState.scissorTest)) ? passState.scissorTest.enabled : scissorTest.enabled;

        enableOrDisable(gl, gl.SCISSOR_TEST, enabled);

        if (enabled) {
            var rectangle = (defined(passState.scissorTest)) ? passState.scissorTest.rectangle : scissorTest.rectangle;
            gl.scissor(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
        }
    }

    function applyDepthRange(gl, renderState) {
        var depthRange = renderState.depthRange;
        gl.depthRange(depthRange.near, depthRange.far);
    }

    function applyDepthTest(gl, renderState) {
        var depthTest = renderState.depthTest;
        var enabled = depthTest.enabled;

        enableOrDisable(gl, gl.DEPTH_TEST, enabled);

        if (enabled) {
            gl.depthFunc(depthTest.func);
        }
    }

    function applyColorMask(gl, renderState) {
        var colorMask = renderState.colorMask;
        gl.colorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha);
    }

    function applyDepthMask(gl, renderState) {
        gl.depthMask(renderState.depthMask);
    }

    function applyStencilMask(gl, renderState) {
        gl.stencilMask(renderState.stencilMask);
    }

    function applyBlendingColor(gl, color) {
        gl.blendColor(color.red, color.green, color.blue, color.alpha);
    }

    function applyBlending(gl, renderState, passState) {
        var blending = renderState.blending;
        var enabled = (defined(passState.blendingEnabled)) ? passState.blendingEnabled : blending.enabled;

        enableOrDisable(gl, gl.BLEND, enabled);

        if (enabled) {
            applyBlendingColor(gl, blending.color);
            gl.blendEquationSeparate(blending.equationRgb, blending.equationAlpha);
            gl.blendFuncSeparate(blending.functionSourceRgb, blending.functionDestinationRgb, blending.functionSourceAlpha, blending.functionDestinationAlpha);
        }
    }

    function applyStencilTest(gl, renderState) {
        var stencilTest = renderState.stencilTest;
        var enabled = stencilTest.enabled;

        enableOrDisable(gl, gl.STENCIL_TEST, enabled);

        if (enabled) {
            var frontFunction = stencilTest.frontFunction;
            var backFunction = stencilTest.backFunction;
            var reference = stencilTest.reference;
            var mask = stencilTest.mask;

            // Section 6.8 of the WebGL spec requires the reference and masks to be the same for
            // front- and back-face tests.  This call prevents invalid operation errors when calling
            // stencilFuncSeparate on Firefox.  Perhaps they should delay validation to avoid requiring this.
            gl.stencilFunc(frontFunction, reference, mask);
            gl.stencilFuncSeparate(gl.BACK, backFunction, reference, mask);
            gl.stencilFuncSeparate(gl.FRONT, frontFunction, reference, mask);

            var frontOperation = stencilTest.frontOperation;
            var frontOperationFail = frontOperation.fail;
            var frontOperationZFail = frontOperation.zFail;
            var frontOperationZPass = frontOperation.zPass;

            gl.stencilOpSeparate(gl.FRONT, frontOperationFail, frontOperationZFail, frontOperationZPass);

            var backOperation = stencilTest.backOperation;
            var backOperationFail = backOperation.fail;
            var backOperationZFail = backOperation.zFail;
            var backOperationZPass = backOperation.zPass;

            gl.stencilOpSeparate(gl.BACK, backOperationFail, backOperationZFail, backOperationZPass);
        }
    }

    function applySampleCoverage(gl, renderState) {
        var sampleCoverage = renderState.sampleCoverage;
        var enabled = sampleCoverage.enabled;

        enableOrDisable(gl, gl.SAMPLE_COVERAGE, enabled);

        if (enabled) {
            gl.sampleCoverage(sampleCoverage.value, sampleCoverage.invert);
        }
    }

    var scratchViewport = new BoundingRectangle();
    function applyViewport(gl, renderState, passState) {
        var viewport = defaultValue(renderState.viewport, passState.viewport);
        if (!defined(viewport)) {
            viewport = scratchViewport;
            viewport.width = passState.context.drawingBufferWidth;
            viewport.height = passState.context.drawingBufferHeight;
        }

        passState.context.uniformState.viewport = viewport;
        gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
    }

    RenderState.apply = function(gl, renderState, passState) {
        applyFrontFace(gl, renderState);
        applyCull(gl, renderState);
        applyLineWidth(gl, renderState);
        applyPolygonOffset(gl, renderState);
        applyDepthRange(gl, renderState);
        applyDepthTest(gl, renderState);
        applyColorMask(gl, renderState);
        applyDepthMask(gl, renderState);
        applyStencilMask(gl, renderState);
        applyStencilTest(gl, renderState);
        applySampleCoverage(gl, renderState);
        applyScissorTest(gl, renderState, passState);
        applyBlending(gl, renderState, passState);
        applyViewport(gl, renderState, passState);
    };

    function createFuncs(previousState, nextState) {
        var funcs = [];

        if (previousState.frontFace !== nextState.frontFace) {
            funcs.push(applyFrontFace);
        }

        if ((previousState.cull.enabled !== nextState.cull.enabled) || (previousState.cull.face !== nextState.cull.face)) {
            funcs.push(applyCull);
        }

        if (previousState.lineWidth !== nextState.lineWidth) {
            funcs.push(applyLineWidth);
        }

        if ((previousState.polygonOffset.enabled !== nextState.polygonOffset.enabled) ||
                (previousState.polygonOffset.factor !== nextState.polygonOffset.factor) ||
                (previousState.polygonOffset.units !== nextState.polygonOffset.units)) {
            funcs.push(applyPolygonOffset);
        }

        if ((previousState.depthRange.near !== nextState.depthRange.near) || (previousState.depthRange.far !== nextState.depthRange.far)) {
            funcs.push(applyDepthRange);
        }

        if ((previousState.depthTest.enabled !== nextState.depthTest.enabled) || (previousState.depthTest.func !== nextState.depthTest.func)) {
            funcs.push(applyDepthTest);
        }

        if ((previousState.colorMask.red !== nextState.colorMask.red) ||
                (previousState.colorMask.green !== nextState.colorMask.green) ||
                (previousState.colorMask.blue !== nextState.colorMask.blue) ||
                (previousState.colorMask.alpha !== nextState.colorMask.alpha)) {
            funcs.push(applyColorMask);
        }

        if (previousState.depthMask !== nextState.depthMask) {
            funcs.push(applyDepthMask);
        }

        if (previousState.stencilMask !== nextState.stencilMask) {
            funcs.push(applyStencilMask);
        }

        if ((previousState.stencilTest.enabled !== nextState.stencilTest.enabled) ||
                (previousState.stencilTest.frontFunction !== nextState.stencilTest.frontFunction) ||
                (previousState.stencilTest.backFunction !== nextState.stencilTest.backFunction) ||
                (previousState.stencilTest.reference !== nextState.stencilTest.reference) ||
                (previousState.stencilTest.mask !== nextState.stencilTest.mask) ||
                (previousState.stencilTest.frontOperation.fail !== nextState.stencilTest.frontOperation.fail) ||
                (previousState.stencilTest.frontOperation.zFail !== nextState.stencilTest.frontOperation.zFail) ||
                (previousState.stencilTest.backOperation.fail !== nextState.stencilTest.backOperation.fail) ||
                (previousState.stencilTest.backOperation.zFail !== nextState.stencilTest.backOperation.zFail) ||
                (previousState.stencilTest.backOperation.zPass !== nextState.stencilTest.backOperation.zPass)) {
            funcs.push(applyStencilTest);
        }

        if ((previousState.sampleCoverage.enabled !== nextState.sampleCoverage.enabled) ||
                (previousState.sampleCoverage.value !== nextState.sampleCoverage.value) ||
                (previousState.sampleCoverage.invert !== nextState.sampleCoverage.invert)) {
            funcs.push(applySampleCoverage);
        }

        return funcs;
    }

    RenderState.partialApply = function(gl, previousRenderState, renderState, previousPassState, passState, clear) {
        if (previousRenderState !== renderState) {
            // When a new render state is applied, instead of making WebGL calls for all the states or first
            // comparing the states one-by-one with the previous state (basically a linear search), we take
            // advantage of RenderState's immutability, and store a dynamically populated sparse data structure
            // containing functions that make the minimum number of WebGL calls when transitioning from one state
            // to the other.  In practice, this works well since state-to-state transitions generally only require a
            // few WebGL calls, especially if commands are stored by state.
            var funcs = renderState._applyFunctions[previousRenderState.id];
            if (!defined(funcs)) {
                funcs = createFuncs(previousRenderState, renderState);
                renderState._applyFunctions[previousRenderState.id] = funcs;
            }

            var len = funcs.length;
            for (var i = 0; i < len; ++i) {
                funcs[i](gl, renderState);
            }
        }

        var previousScissorTest = (defined(previousPassState.scissorTest)) ? previousPassState.scissorTest : previousRenderState.scissorTest;
        var scissorTest = (defined(passState.scissorTest)) ? passState.scissorTest : renderState.scissorTest;

        // Our scissor rectangle can get out of sync with the GL scissor rectangle on clears.
        // Seems to be a problem only on ANGLE. See https://github.com/AnalyticalGraphicsInc/cesium/issues/2994
        if ((previousScissorTest !== scissorTest) || clear) {
            applyScissorTest(gl, renderState, passState);
        }

        var previousBlendingEnabled = (defined(previousPassState.blendingEnabled)) ? previousPassState.blendingEnabled : previousRenderState.blending.enabled;
        var blendingEnabled = (defined(passState.blendingEnabled)) ? passState.blendingEnabled : renderState.blending.enabled;
        if ((previousBlendingEnabled !== blendingEnabled) ||
                (blendingEnabled && (previousRenderState.blending !== renderState.blending))) {
            applyBlending(gl, renderState, passState);
        }

        if (previousRenderState !== renderState || previousPassState !== passState || previousPassState.context !== passState.context) {
            applyViewport(gl, renderState, passState);
        }
    };

    RenderState.getState = function(renderState) {
                if (!defined(renderState)) {
            throw new DeveloperError('renderState is required.');
        }
        
        return {
            frontFace : renderState.frontFace,
            cull : {
                enabled : renderState.cull.enabled,
                face : renderState.cull.face
            },
            lineWidth : renderState.lineWidth,
            polygonOffset : {
                enabled : renderState.polygonOffset.enabled,
                factor : renderState.polygonOffset.factor,
                units : renderState.polygonOffset.units
            },
            scissorTest : {
                enabled : renderState.scissorTest.enabled,
                rectangle : BoundingRectangle.clone(renderState.scissorTest.rectangle)
            },
            depthRange : {
                near : renderState.depthRange.near,
                far : renderState.depthRange.far
            },
            depthTest : {
                enabled : renderState.depthTest.enabled,
                func : renderState.depthTest.func
            },
            colorMask : {
                red : renderState.colorMask.red,
                green : renderState.colorMask.green,
                blue : renderState.colorMask.blue,
                alpha : renderState.colorMask.alpha
            },
            depthMask : renderState.depthMask,
            stencilMask : renderState.stencilMask,
            blending : {
                enabled : renderState.blending.enabled,
                color : Color.clone(renderState.blending.color),
                equationRgb : renderState.blending.equationRgb,
                equationAlpha : renderState.blending.equationAlpha,
                functionSourceRgb : renderState.blending.functionSourceRgb,
                functionSourceAlpha : renderState.blending.functionSourceAlpha,
                functionDestinationRgb : renderState.blending.functionDestinationRgb,
                functionDestinationAlpha : renderState.blending.functionDestinationAlpha
            },
            stencilTest : {
                enabled : renderState.stencilTest.enabled,
                frontFunction : renderState.stencilTest.frontFunction,
                backFunction : renderState.stencilTest.backFunction,
                reference : renderState.stencilTest.reference,
                mask : renderState.stencilTest.mask,
                frontOperation : {
                    fail : renderState.stencilTest.frontOperation.fail,
                    zFail : renderState.stencilTest.frontOperation.zFail,
                    zPass : renderState.stencilTest.frontOperation.zPass
                },
                backOperation : {
                    fail : renderState.stencilTest.backOperation.fail,
                    zFail : renderState.stencilTest.backOperation.zFail,
                    zPass : renderState.stencilTest.backOperation.zPass
                }
            },
            sampleCoverage : {
                enabled : renderState.sampleCoverage.enabled,
                value : renderState.sampleCoverage.value,
                invert : renderState.sampleCoverage.invert
            },
            viewport : defined(renderState.viewport) ? BoundingRectangle.clone(renderState.viewport) : undefined
        };
    };

    return RenderState;
});

/*global define*/
define('Renderer/AutomaticUniforms',[
        '../Core/Cartesian3',
        '../Core/Matrix4',
        '../Core/WebGLConstants'
    ], function(
        Cartesian3,
        Matrix4,
        WebGLConstants) {
    'use strict';
    /*global WebGLRenderingContext*/

    var viewerPositionWCScratch = new Cartesian3();

    function AutomaticUniform(options) {
        this._size = options.size;
        this._datatype = options.datatype;
        this.getValue = options.getValue;
    }

    // this check must use typeof, not defined, because defined doesn't work with undeclared variables.
    if (typeof WebGLRenderingContext === 'undefined') {
        return {};
    }

    var datatypeToGlsl = {};
    datatypeToGlsl[WebGLConstants.FLOAT] = 'float';
    datatypeToGlsl[WebGLConstants.FLOAT_VEC2] = 'vec2';
    datatypeToGlsl[WebGLConstants.FLOAT_VEC3] = 'vec3';
    datatypeToGlsl[WebGLConstants.FLOAT_VEC4] = 'vec4';
    datatypeToGlsl[WebGLConstants.INT] = 'int';
    datatypeToGlsl[WebGLConstants.INT_VEC2] = 'ivec2';
    datatypeToGlsl[WebGLConstants.INT_VEC3] = 'ivec3';
    datatypeToGlsl[WebGLConstants.INT_VEC4] = 'ivec4';
    datatypeToGlsl[WebGLConstants.BOOL] = 'bool';
    datatypeToGlsl[WebGLConstants.BOOL_VEC2] = 'bvec2';
    datatypeToGlsl[WebGLConstants.BOOL_VEC3] = 'bvec3';
    datatypeToGlsl[WebGLConstants.BOOL_VEC4] = 'bvec4';
    datatypeToGlsl[WebGLConstants.FLOAT_MAT2] = 'mat2';
    datatypeToGlsl[WebGLConstants.FLOAT_MAT3] = 'mat3';
    datatypeToGlsl[WebGLConstants.FLOAT_MAT4] = 'mat4';
    datatypeToGlsl[WebGLConstants.SAMPLER_2D] = 'sampler2D';
    datatypeToGlsl[WebGLConstants.SAMPLER_CUBE] = 'samplerCube';

    AutomaticUniform.prototype.getDeclaration = function(name) {
        var declaration = 'uniform ' + datatypeToGlsl[this._datatype] + ' ' + name;

        var size = this._size;
        if (size === 1) {
            declaration += ';';
        } else {
            declaration += '[' + size.toString() + '];';
        }

        return declaration;
    };

    /**
     * @private
     */
    var AutomaticUniforms = {
        /**
         * An automatic GLSL uniform containing the viewport's <code>x</code>, <code>y</code>, <code>width</code>,
         * and <code>height</code> properties in an <code>vec4</code>'s <code>x</code>, <code>y</code>, <code>z</code>,
         * and <code>w</code> components, respectively.
         *
         * @alias czm_viewport
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec4 czm_viewport;
         *
         * // Scale the window coordinate components to [0, 1] by dividing
         * // by the viewport's width and height.
         * vec2 v = gl_FragCoord.xy / czm_viewport.zw;
         *
         * @see Context#getViewport
         */
        czm_viewport : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC4,
            getValue : function(uniformState) {
                return uniformState.viewportCartesian4;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 orthographic projection matrix that
         * transforms window coordinates to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         * <br /><br />
         * This transform is useful when a vertex shader inputs or manipulates window coordinates
         * as done by {@link BillboardCollection}.
         * <br /><br />
         * Do not confuse {@link czm_viewportTransformation} with <code>czm_viewportOrthographic</code>.
         * The former transforms from normalized device coordinates to window coordinates; the later transforms
         * from window coordinates to clip coordinates, and is often used to assign to <code>gl_Position</code>.
         *
         * @alias czm_viewportOrthographic
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_viewportOrthographic;
         *
         * // Example
         * gl_Position = czm_viewportOrthographic * vec4(windowPosition, 0.0, 1.0);
         *
         * @see UniformState#viewportOrthographic
         * @see czm_viewport
         * @see czm_viewportTransformation
         * @see BillboardCollection
         */
        czm_viewportOrthographic : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.viewportOrthographic;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 transformation matrix that
         * transforms normalized device coordinates to window coordinates.  The context's
         * full viewport is used, and the depth range is assumed to be <code>near = 0</code>
         * and <code>far = 1</code>.
         * <br /><br />
         * This transform is useful when there is a need to manipulate window coordinates
         * in a vertex shader as done by {@link BillboardCollection}.  In many cases,
         * this matrix will not be used directly; instead, {@link czm_modelToWindowCoordinates}
         * will be used to transform directly from model to window coordinates.
         * <br /><br />
         * Do not confuse <code>czm_viewportTransformation</code> with {@link czm_viewportOrthographic}.
         * The former transforms from normalized device coordinates to window coordinates; the later transforms
         * from window coordinates to clip coordinates, and is often used to assign to <code>gl_Position</code>.
         *
         * @alias czm_viewportTransformation
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_viewportTransformation;
         *
         * // Use czm_viewportTransformation as part of the
         * // transform from model to window coordinates.
         * vec4 q = czm_modelViewProjection * positionMC;               // model to clip coordinates
         * q.xyz /= q.w;                                                // clip to normalized device coordinates (ndc)
         * q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // ndc to window coordinates
         *
         * @see UniformState#viewportTransformation
         * @see czm_viewport
         * @see czm_viewportOrthographic
         * @see czm_modelToWindowCoordinates
         * @see BillboardCollection
         */
        czm_viewportTransformation : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.viewportTransformation;
            }
        }),

        /**
         * An automatic GLSL uniform representing the depth after
         * only the globe has been rendered and packed into an RGBA texture.
         *
         * @private
         *
         * @alias czm_globeDepthTexture
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform sampler2D czm_globeDepthTexture;
         *
         * // Get the depth at the current fragment
         * vec2 coords = gl_FragCoord.xy / czm_viewport.zw;
         * float depth = czm_unpackDepth(texture2D(czm_globeDepthTexture, coords));
         */
        czm_globeDepthTexture : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.SAMPLER_2D,
            getValue : function(uniformState) {
                return uniformState.globeDepthTexture;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model transformation matrix that
         * transforms model coordinates to world coordinates.
         *
         * @alias czm_model
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_model;
         *
         * // Example
         * vec4 worldPosition = czm_model * modelPosition;
         *
         * @see UniformState#model
         * @see czm_inverseModel
         * @see czm_modelView
         * @see czm_modelViewProjection
         */
        czm_model : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.model;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model transformation matrix that
         * transforms world coordinates to model coordinates.
         *
         * @alias czm_inverseModel
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseModel;
         *
         * // Example
         * vec4 modelPosition = czm_inverseModel * worldPosition;
         *
         * @see UniformState#inverseModel
         * @see czm_model
         * @see czm_inverseModelView
         */
        czm_inverseModel : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseModel;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 view transformation matrix that
         * transforms world coordinates to eye coordinates.
         *
         * @alias czm_view
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_view;
         *
         * // Example
         * vec4 eyePosition = czm_view * worldPosition;
         *
         * @see UniformState#view
         * @see czm_viewRotation
         * @see czm_modelView
         * @see czm_viewProjection
         * @see czm_modelViewProjection
         * @see czm_inverseView
         */
        czm_view : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.view;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 view transformation matrix that
         * transforms 3D world coordinates to eye coordinates.  In 3D mode, this is identical to
         * {@link czm_view}, but in 2D and Columbus View it represents the view matrix
         * as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         *
         * @alias czm_view3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_view3D;
         *
         * // Example
         * vec4 eyePosition3D = czm_view3D * worldPosition3D;
         *
         * @see UniformState#view3D
         * @see czm_view
         */
        czm_view3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.view3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 view rotation matrix that
         * transforms vectors in world coordinates to eye coordinates.
         *
         * @alias czm_viewRotation
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_viewRotation;
         *
         * // Example
         * vec3 eyeVector = czm_viewRotation * worldVector;
         *
         * @see UniformState#viewRotation
         * @see czm_view
         * @see czm_inverseView
         * @see czm_inverseViewRotation
         */
        czm_viewRotation : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.viewRotation;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 view rotation matrix that
         * transforms vectors in 3D world coordinates to eye coordinates.  In 3D mode, this is identical to
         * {@link czm_viewRotation}, but in 2D and Columbus View it represents the view matrix
         * as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         *
         * @alias czm_viewRotation3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_viewRotation3D;
         *
         * // Example
         * vec3 eyeVector = czm_viewRotation3D * worldVector;
         *
         * @see UniformState#viewRotation3D
         * @see czm_viewRotation
         */
        czm_viewRotation3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.viewRotation3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 transformation matrix that
         * transforms from eye coordinates to world coordinates.
         *
         * @alias czm_inverseView
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseView;
         *
         * // Example
         * vec4 worldPosition = czm_inverseView * eyePosition;
         *
         * @see UniformState#inverseView
         * @see czm_view
         * @see czm_inverseNormal
         */
        czm_inverseView : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseView;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 transformation matrix that
         * transforms from 3D eye coordinates to world coordinates.  In 3D mode, this is identical to
         * {@link czm_inverseView}, but in 2D and Columbus View it represents the inverse view matrix
         * as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         *
         * @alias czm_inverseView3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseView3D;
         *
         * // Example
         * vec4 worldPosition = czm_inverseView3D * eyePosition;
         *
         * @see UniformState#inverseView3D
         * @see czm_inverseView
         */
        czm_inverseView3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseView3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 rotation matrix that
         * transforms vectors from eye coordinates to world coordinates.
         *
         * @alias czm_inverseViewRotation
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_inverseViewRotation;
         *
         * // Example
         * vec4 worldVector = czm_inverseViewRotation * eyeVector;
         *
         * @see UniformState#inverseView
         * @see czm_view
         * @see czm_viewRotation
         * @see czm_inverseViewRotation
         */
        czm_inverseViewRotation : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.inverseViewRotation;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 rotation matrix that
         * transforms vectors from 3D eye coordinates to world coordinates.  In 3D mode, this is identical to
         * {@link czm_inverseViewRotation}, but in 2D and Columbus View it represents the inverse view matrix
         * as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         *
         * @alias czm_inverseViewRotation3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_inverseViewRotation3D;
         *
         * // Example
         * vec4 worldVector = czm_inverseViewRotation3D * eyeVector;
         *
         * @see UniformState#inverseView3D
         * @see czm_inverseViewRotation
         */
        czm_inverseViewRotation3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.inverseViewRotation3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 projection transformation matrix that
         * transforms eye coordinates to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         *
         * @alias czm_projection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_projection;
         *
         * // Example
         * gl_Position = czm_projection * eyePosition;
         *
         * @see UniformState#projection
         * @see czm_viewProjection
         * @see czm_modelViewProjection
         * @see czm_infiniteProjection
         */
        czm_projection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.projection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 inverse projection transformation matrix that
         * transforms from clip coordinates to eye coordinates. Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         *
         * @alias czm_inverseProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseProjection;
         *
         * // Example
         * vec4 eyePosition = czm_inverseProjection * clipPosition;
         *
         * @see UniformState#inverseProjection
         * @see czm_projection
         */
        czm_inverseProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseProjection;
            }
        }),

        /**
         * @private
         */
        czm_inverseProjectionOIT : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseProjectionOIT;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 projection transformation matrix with the far plane at infinity,
         * that transforms eye coordinates to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.  An infinite far plane is used
         * in algorithms like shadow volumes and GPU ray casting with proxy geometry to ensure that triangles
         * are not clipped by the far plane.
         *
         * @alias czm_infiniteProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_infiniteProjection;
         *
         * // Example
         * gl_Position = czm_infiniteProjection * eyePosition;
         *
         * @see UniformState#infiniteProjection
         * @see czm_projection
         * @see czm_modelViewInfiniteProjection
         */
        czm_infiniteProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.infiniteProjection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that
         * transforms model coordinates to eye coordinates.
         * <br /><br />
         * Positions should be transformed to eye coordinates using <code>czm_modelView</code> and
         * normals should be transformed using {@link czm_normal}.
         *
         * @alias czm_modelView
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_modelView;
         *
         * // Example
         * vec4 eyePosition = czm_modelView * modelPosition;
         *
         * // The above is equivalent to, but more efficient than:
         * vec4 eyePosition = czm_view * czm_model * modelPosition;
         *
         * @see UniformState#modelView
         * @see czm_model
         * @see czm_view
         * @see czm_modelViewProjection
         * @see czm_normal
         */
        czm_modelView : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.modelView;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that
         * transforms 3D model coordinates to eye coordinates.  In 3D mode, this is identical to
         * {@link czm_modelView}, but in 2D and Columbus View it represents the model-view matrix
         * as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         * <br /><br />
         * Positions should be transformed to eye coordinates using <code>czm_modelView3D</code> and
         * normals should be transformed using {@link czm_normal3D}.
         *
         * @alias czm_modelView3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_modelView3D;
         *
         * // Example
         * vec4 eyePosition = czm_modelView3D * modelPosition;
         *
         * // The above is equivalent to, but more efficient than:
         * vec4 eyePosition = czm_view3D * czm_model * modelPosition;
         *
         * @see UniformState#modelView3D
         * @see czm_modelView
         */
        czm_modelView3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.modelView3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model-view transformation matrix that
         * transforms model coordinates, relative to the eye, to eye coordinates.  This is used
         * in conjunction with {@link czm_translateRelativeToEye}.
         *
         * @alias czm_modelViewRelativeToEye
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_modelViewRelativeToEye;
         *
         * // Example
         * attribute vec3 positionHigh;
         * attribute vec3 positionLow;
         *
         * void main()
         * {
         *   vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);
         *   gl_Position = czm_projection * (czm_modelViewRelativeToEye * p);
         * }
         *
         * @see czm_modelViewProjectionRelativeToEye
         * @see czm_translateRelativeToEye
         * @see EncodedCartesian3
         */
        czm_modelViewRelativeToEye : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.modelViewRelativeToEye;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 transformation matrix that
         * transforms from eye coordinates to model coordinates.
         *
         * @alias czm_inverseModelView
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseModelView;
         *
         * // Example
         * vec4 modelPosition = czm_inverseModelView * eyePosition;
         *
         * @see UniformState#inverseModelView
         * @see czm_modelView
         */
        czm_inverseModelView : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseModelView;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 transformation matrix that
         * transforms from eye coordinates to 3D model coordinates.  In 3D mode, this is identical to
         * {@link czm_inverseModelView}, but in 2D and Columbus View it represents the inverse model-view matrix
         * as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         *
         * @alias czm_inverseModelView3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseModelView3D;
         *
         * // Example
         * vec4 modelPosition = czm_inverseModelView3D * eyePosition;
         *
         * @see UniformState#inverseModelView
         * @see czm_inverseModelView
         * @see czm_modelView3D
         */
        czm_inverseModelView3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseModelView3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that
         * transforms world coordinates to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         *
         * @alias czm_viewProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_viewProjection;
         *
         * // Example
         * vec4 gl_Position = czm_viewProjection * czm_model * modelPosition;
         *
         * // The above is equivalent to, but more efficient than:
         * gl_Position = czm_projection * czm_view * czm_model * modelPosition;
         *
         * @see UniformState#viewProjection
         * @see czm_view
         * @see czm_projection
         * @see czm_modelViewProjection
         * @see czm_inverseViewProjection
         */
        czm_viewProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.viewProjection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 view-projection transformation matrix that
         * transforms clip coordinates to world coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         *
         * @alias czm_inverseViewProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseViewProjection;
         *
         * // Example
         * vec4 worldPosition = czm_inverseViewProjection * clipPosition;
         *
         * @see UniformState#inverseViewProjection
         * @see czm_viewProjection
         */
        czm_inverseViewProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseViewProjection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that
         * transforms model coordinates to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         *
         * @alias czm_modelViewProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_modelViewProjection;
         *
         * // Example
         * vec4 gl_Position = czm_modelViewProjection * modelPosition;
         *
         * // The above is equivalent to, but more efficient than:
         * gl_Position = czm_projection * czm_view * czm_model * modelPosition;
         *
         * @see UniformState#modelViewProjection
         * @see czm_model
         * @see czm_view
         * @see czm_projection
         * @see czm_modelView
         * @see czm_viewProjection
         * @see czm_modelViewInfiniteProjection
         * @see czm_inverseModelViewProjection
         */
        czm_modelViewProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.modelViewProjection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 inverse model-view-projection transformation matrix that
         * transforms clip coordinates to model coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.
         *
         * @alias czm_inverseModelViewProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_inverseModelViewProjection;
         *
         * // Example
         * vec4 modelPosition = czm_inverseModelViewProjection * clipPosition;
         *
         * @see UniformState#modelViewProjection
         * @see czm_modelViewProjection
         */
        czm_inverseModelViewProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.inverseModelViewProjection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that
         * transforms model coordinates, relative to the eye, to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.  This is used in
         * conjunction with {@link czm_translateRelativeToEye}.
         *
         * @alias czm_modelViewProjectionRelativeToEye
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_modelViewProjectionRelativeToEye;
         *
         * // Example
         * attribute vec3 positionHigh;
         * attribute vec3 positionLow;
         *
         * void main()
         * {
         *   vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);
         *   gl_Position = czm_modelViewProjectionRelativeToEye * p;
         * }
         *
         * @see czm_modelViewRelativeToEye
         * @see czm_translateRelativeToEye
         * @see EncodedCartesian3
         */
        czm_modelViewProjectionRelativeToEye : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.modelViewProjectionRelativeToEye;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 4x4 model-view-projection transformation matrix that
         * transforms model coordinates to clip coordinates.  Clip coordinates is the
         * coordinate system for a vertex shader's <code>gl_Position</code> output.  The projection matrix places
         * the far plane at infinity.  This is useful in algorithms like shadow volumes and GPU ray casting with
         * proxy geometry to ensure that triangles are not clipped by the far plane.
         *
         * @alias czm_modelViewInfiniteProjection
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat4 czm_modelViewInfiniteProjection;
         *
         * // Example
         * vec4 gl_Position = czm_modelViewInfiniteProjection * modelPosition;
         *
         * // The above is equivalent to, but more efficient than:
         * gl_Position = czm_infiniteProjection * czm_view * czm_model * modelPosition;
         *
         * @see UniformState#modelViewInfiniteProjection
         * @see czm_model
         * @see czm_view
         * @see czm_infiniteProjection
         * @see czm_modelViewProjection
         */
        czm_modelViewInfiniteProjection : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT4,
            getValue : function(uniformState) {
                return uniformState.modelViewInfiniteProjection;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 normal transformation matrix that
         * transforms normal vectors in model coordinates to eye coordinates.
         * <br /><br />
         * Positions should be transformed to eye coordinates using {@link czm_modelView} and
         * normals should be transformed using <code>czm_normal</code>.
         *
         * @alias czm_normal
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_normal;
         *
         * // Example
         * vec3 eyeNormal = czm_normal * normal;
         *
         * @see UniformState#normal
         * @see czm_inverseNormal
         * @see czm_modelView
         */
        czm_normal : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.normal;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 normal transformation matrix that
         * transforms normal vectors in 3D model coordinates to eye coordinates.
         * In 3D mode, this is identical to
         * {@link czm_normal}, but in 2D and Columbus View it represents the normal transformation
         * matrix as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         * <br /><br />
         * Positions should be transformed to eye coordinates using {@link czm_modelView3D} and
         * normals should be transformed using <code>czm_normal3D</code>.
         *
         * @alias czm_normal3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_normal3D;
         *
         * // Example
         * vec3 eyeNormal = czm_normal3D * normal;
         *
         * @see UniformState#normal3D
         * @see czm_normal
         */
        czm_normal3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.normal3D;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 normal transformation matrix that
         * transforms normal vectors in eye coordinates to model coordinates.  This is
         * the opposite of the transform provided by {@link czm_normal}.
         *
         * @alias czm_inverseNormal
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_inverseNormal;
         *
         * // Example
         * vec3 normalMC = czm_inverseNormal * normalEC;
         *
         * @see UniformState#inverseNormal
         * @see czm_normal
         * @see czm_modelView
         * @see czm_inverseView
         */
        czm_inverseNormal : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.inverseNormal;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 normal transformation matrix that
         * transforms normal vectors in eye coordinates to 3D model coordinates.  This is
         * the opposite of the transform provided by {@link czm_normal}.
         * In 3D mode, this is identical to
         * {@link czm_inverseNormal}, but in 2D and Columbus View it represents the inverse normal transformation
         * matrix as if the camera were at an equivalent location in 3D mode.  This is useful for lighting
         * 2D and Columbus View in the same way that 3D is lit.
         *
         * @alias czm_inverseNormal3D
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_inverseNormal3D;
         *
         * // Example
         * vec3 normalMC = czm_inverseNormal3D * normalEC;
         *
         * @see UniformState#inverseNormal3D
         * @see czm_inverseNormal
         */
        czm_inverseNormal3D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.inverseNormal3D;
            }
        }),

        /**
         * An automatic GLSL uniform containing height (<code>x</code>) and height squared (<code>y</code>)
         *  of the eye (camera) in the 2D scene in meters.
         *
         * @alias czm_eyeHeight2D
         * @glslUniform
         *
         * @see UniformState#eyeHeight2D
         */
        czm_eyeHeight2D : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC2,
            getValue : function(uniformState) {
                return uniformState.eyeHeight2D;
            }
        }),

        /**
         * An automatic GLSL uniform containing the near distance (<code>x</code>) and the far distance (<code>y</code>)
         * of the frustum defined by the camera.  This is the largest possible frustum, not an individual
         * frustum used for multi-frustum rendering.
         *
         * @alias czm_entireFrustum
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec2 czm_entireFrustum;
         *
         * // Example
         * float frustumLength = czm_entireFrustum.y - czm_entireFrustum.x;
         *
         * @see UniformState#entireFrustum
         * @see czm_currentFrustum
         */
        czm_entireFrustum : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC2,
            getValue : function(uniformState) {
                return uniformState.entireFrustum;
            }
        }),

        /**
         * An automatic GLSL uniform containing the near distance (<code>x</code>) and the far distance (<code>y</code>)
         * of the frustum defined by the camera.  This is the individual
         * frustum used for multi-frustum rendering.
         *
         * @alias czm_currentFrustum
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec2 czm_currentFrustum;
         *
         * // Example
         * float frustumLength = czm_currentFrustum.y - czm_currentFrustum.x;
         *
         * @see UniformState#currentFrustum
         * @see czm_entireFrustum
         */
        czm_currentFrustum : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC2,
            getValue : function(uniformState) {
                return uniformState.currentFrustum;
            }
        }),

        /**
         * The distances to the frustum planes. The top, bottom, left and right distances are
         * the x, y, z, and w components, respectively.
         *
         * @alias czm_frustumPlanes
         * @glslUniform
         */
        czm_frustumPlanes : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC4,
            getValue : function(uniformState) {
                return uniformState.frustumPlanes;
            }
        }),

        /**
         * An automatic GLSL uniform representing the sun position in world coordinates.
         *
         * @alias czm_sunPositionWC
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_sunPositionWC;
         *
         * @see UniformState#sunPositionWC
         * @see czm_sunPositionColumbusView
         * @see czm_sunDirectionWC
         */
        czm_sunPositionWC : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.sunPositionWC;
            }
        }),

        /**
         * An automatic GLSL uniform representing the sun position in Columbus view world coordinates.
         *
         * @alias czm_sunPositionColumbusView
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_sunPositionColumbusView;
         *
         * @see UniformState#sunPositionColumbusView
         * @see czm_sunPositionWC
         */
        czm_sunPositionColumbusView : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.sunPositionColumbusView;
            }
        }),

        /**
         * An automatic GLSL uniform representing the normalized direction to the sun in eye coordinates.
         * This is commonly used for directional lighting computations.
         *
         * @alias czm_sunDirectionEC
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_sunDirectionEC;
         *
         * // Example
         * float diffuse = max(dot(czm_sunDirectionEC, normalEC), 0.0);
         *
         * @see UniformState#sunDirectionEC
         * @see czm_moonDirectionEC
         * @see czm_sunDirectionWC
         */
        czm_sunDirectionEC : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.sunDirectionEC;
            }
        }),

        /**
         * An automatic GLSL uniform representing the normalized direction to the sun in world coordinates.
         * This is commonly used for directional lighting computations.
         *
         * @alias czm_sunDirectionWC
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_sunDirectionWC;
         *
         * @see UniformState#sunDirectionWC
         * @see czm_sunPositionWC
         * @see czm_sunDirectionEC
         */
        czm_sunDirectionWC : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.sunDirectionWC;
            }
        }),

        /**
         * An automatic GLSL uniform representing the normalized direction to the moon in eye coordinates.
         * This is commonly used for directional lighting computations.
         *
         * @alias czm_moonDirectionEC
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_moonDirectionEC;
         *
         * // Example
         * float diffuse = max(dot(czm_moonDirectionEC, normalEC), 0.0);
         *
         * @see UniformState#moonDirectionEC
         * @see czm_sunDirectionEC
         */
        czm_moonDirectionEC : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.moonDirectionEC;
            }
        }),

        /**
         * An automatic GLSL uniform representing the high bits of the camera position in model
         * coordinates.  This is used for GPU RTE to eliminate jittering artifacts when rendering
         * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
         *
         * @alias czm_encodedCameraPositionMCHigh
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_encodedCameraPositionMCHigh;
         *
         * @see czm_encodedCameraPositionMCLow
         * @see czm_modelViewRelativeToEye
         * @see czm_modelViewProjectionRelativeToEye
         */
        czm_encodedCameraPositionMCHigh : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.encodedCameraPositionMCHigh;
            }
        }),

        /**
         * An automatic GLSL uniform representing the low bits of the camera position in model
         * coordinates.  This is used for GPU RTE to eliminate jittering artifacts when rendering
         * as described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.
         *
         * @alias czm_encodedCameraPositionMCLow
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_encodedCameraPositionMCLow;
         *
         * @see czm_encodedCameraPositionMCHigh
         * @see czm_modelViewRelativeToEye
         * @see czm_modelViewProjectionRelativeToEye
         */
        czm_encodedCameraPositionMCLow : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return uniformState.encodedCameraPositionMCLow;
            }
        }),

        /**
         * An automatic GLSL uniform representing the position of the viewer (camera) in world coordinates.
         *
         * @alias czm_viewerPositionWC
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform vec3 czm_viewerPositionWC;
         */
        czm_viewerPositionWC : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_VEC3,
            getValue : function(uniformState) {
                return Matrix4.getTranslation(uniformState.inverseView, viewerPositionWCScratch);
            }
        }),

        /**
         * An automatic GLSL uniform representing the frame number. This uniform is automatically incremented
         * every frame.
         *
         * @alias czm_frameNumber
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform float czm_frameNumber;
         */
        czm_frameNumber : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.frameState.frameNumber;
            }
        }),

        /**
         * An automatic GLSL uniform representing the current morph transition time between
         * 2D/Columbus View and 3D, with 0.0 being 2D or Columbus View and 1.0 being 3D.
         *
         * @alias czm_morphTime
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform float czm_morphTime;
         *
         * // Example
         * vec4 p = czm_columbusViewMorph(position2D, position3D, czm_morphTime);
         */
        czm_morphTime : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.frameState.morphTime;
            }
        }),

        /**
         * An automatic GLSL uniform representing the current {@link SceneMode}, expressed
         * as a float.
         *
         * @alias czm_sceneMode
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform float czm_sceneMode;
         *
         * // Example
         * if (czm_sceneMode == czm_sceneMode2D)
         * {
         *     eyeHeightSq = czm_eyeHeight2D.y;
         * }
         *
         * @see czm_sceneMode2D
         * @see czm_sceneModeColumbusView
         * @see czm_sceneMode3D
         * @see czm_sceneModeMorphing
         */
        czm_sceneMode : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.frameState.mode;
            }
        }),

        /**
         * An automatic GLSL uniform representing the current rendering pass.
         *
         * @alias czm_pass
         * @glslUniform
         *
         * @example
         * // GLSL declaration
         * uniform float czm_pass;
         *
         * // Example
         * if ((czm_pass == czm_passTranslucent) && isOpaque())
         * {
         *     gl_Position *= 0.0; // Cull opaque geometry in the translucent pass
         * }
         */
        czm_pass : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.pass;
            }
        }),

        /**
         * An automatic GLSL uniform representing a 3x3 rotation matrix that transforms
         * from True Equator Mean Equinox (TEME) axes to the pseudo-fixed axes at the current scene time.
         *
         * @alias czm_temeToPseudoFixed
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform mat3 czm_temeToPseudoFixed;
         *
         * // Example
         * vec3 pseudoFixed = czm_temeToPseudoFixed * teme;
         *
         * @see UniformState#temeToPseudoFixedMatrix
         * @see Transforms.computeTemeToPseudoFixedMatrix
         */
        czm_temeToPseudoFixed : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT_MAT3,
            getValue : function(uniformState) {
                return uniformState.temeToPseudoFixedMatrix;
            }
        }),

        /**
         * An automatic GLSL uniform representing the ratio of canvas coordinate space to canvas pixel space.
         *
         * @alias czm_resolutionScale
         * @glslUniform
         *
         * @example
         * uniform float czm_resolutionScale;
         */
        czm_resolutionScale : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.resolutionScale;
            }
        }),

        /**
         * An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera.
         *
         * @alias czm_fogDensity
         * @glslUniform
         *
         * @see czm_fog
         */
        czm_fogDensity : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.fogDensity;
            }
        }),

         /**
         * An automatic GLSL uniform representing the splitter position to use when rendering imagery layers with a splitter.
         * This will be in the range 0.0 to 1.0 with 0.0 being the far left of the viewport and 1.0 being the far right of the viewport.
         *
         * @alias czm_imagerySplitPosition
         * @glslUniform
         *
         *
         * @example
         * // GLSL declaration
         * uniform float czm_imagerySplitPosition;
         */
        czm_imagerySplitPosition : new AutomaticUniform({
            size : 1,
            datatype : WebGLConstants.FLOAT,
            getValue : function(uniformState) {
                return uniformState.imagerySplitPosition;
            }
        }),

        /**
         * An automatic GLSL uniform scalar representing the geometric tolerance per meter
         *
         * @alias czm_geometricToleranceOverMeter
         * @glslUniform
         */
        czm_geometricToleranceOverMeter : new AutomaticUniform({
            size: 1,
            datatype: WebGLConstants.FLOAT,
            getValue: function(uniformState) {
                return uniformState.geometricToleranceOverMeter;
            }
        })
    };

    return AutomaticUniforms;
});

/*global define*/
define('Renderer/createUniform',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Matrix2',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/RuntimeError'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Color,
        defined,
        DeveloperError,
        Matrix2,
        Matrix3,
        Matrix4,
        RuntimeError) {
    'use strict';

    /**
     * @private
     */
    function createUniform(gl, activeUniform, uniformName, location) {
        switch (activeUniform.type) {
            case gl.FLOAT:
                return new UniformFloat(gl, activeUniform, uniformName, location);
            case gl.FLOAT_VEC2:
                return new UniformFloatVec2(gl, activeUniform, uniformName, location);
            case gl.FLOAT_VEC3:
                return new UniformFloatVec3(gl, activeUniform, uniformName, location);
            case gl.FLOAT_VEC4:
                return new UniformFloatVec4(gl, activeUniform, uniformName, location);
            case gl.SAMPLER_2D:
            case gl.SAMPLER_CUBE:
                return new UniformSampler(gl, activeUniform, uniformName, location);
            case gl.INT:
            case gl.BOOL:
                return new UniformInt(gl, activeUniform, uniformName, location);
            case gl.INT_VEC2:
            case gl.BOOL_VEC2:
                return new UniformIntVec2(gl, activeUniform, uniformName, location);
            case gl.INT_VEC3:
            case gl.BOOL_VEC3:
                return new UniformIntVec3(gl, activeUniform, uniformName, location);
            case gl.INT_VEC4:
            case gl.BOOL_VEC4:
                return new UniformIntVec4(gl, activeUniform, uniformName, location);
            case gl.FLOAT_MAT2:
                return new UniformMat2(gl, activeUniform, uniformName, location);
            case gl.FLOAT_MAT3:
                return new UniformMat3(gl, activeUniform, uniformName, location);
            case gl.FLOAT_MAT4:
                return new UniformMat4(gl, activeUniform, uniformName, location);
            default:
                throw new RuntimeError('Unrecognized uniform type: ' + activeUniform.type + ' for uniform "' + uniformName + '".');
        }
    }

    function UniformFloat(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = 0.0;

        this._gl = gl;
        this._location = location;
    }

    UniformFloat.prototype.set = function() {
        if (this.value !== this._value) {
            this._value = this.value;
            this._gl.uniform1f(this._location, this.value);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformFloatVec2(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Cartesian2();

        this._gl = gl;
        this._location = location;
    }

    UniformFloatVec2.prototype.set = function() {
        var v = this.value;
        if (!Cartesian2.equals(v, this._value)) {
            Cartesian2.clone(v, this._value);
            this._gl.uniform2f(this._location, v.x, v.y);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformFloatVec3(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = undefined;

        this._gl = gl;
        this._location = location;
    }

    UniformFloatVec3.prototype.set = function() {
        var v = this.value;

        if (defined(v.red)) {
            if (!Color.equals(v, this._value)) {
                this._value = Color.clone(v, this._value);
                this._gl.uniform3f(this._location, v.red, v.green, v.blue);
            }
        } else if (defined(v.x)) {
            if (!Cartesian3.equals(v, this._value)) {
                this._value = Cartesian3.clone(v, this._value);
                this._gl.uniform3f(this._location, v.x, v.y, v.z);
            }
        } else {
                        throw new DeveloperError('Invalid vec3 value for uniform "' + this._activethis.name + '".');
                    }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformFloatVec4(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = undefined;

        this._gl = gl;
        this._location = location;
    }

    UniformFloatVec4.prototype.set = function() {
        var v = this.value;

        if (defined(v.red)) {
            if (!Color.equals(v, this._value)) {
                this._value = Color.clone(v, this._value);
                this._gl.uniform4f(this._location, v.red, v.green, v.blue, v.alpha);
            }
        } else if (defined(v.x)) {
            if (!Cartesian4.equals(v, this._value)) {
                this._value = Cartesian4.clone(v, this._value);
                this._gl.uniform4f(this._location, v.x, v.y, v.z, v.w);
            }
        } else {
                        throw new DeveloperError('Invalid vec4 value for uniform "' + this._activethis.name + '".');
                    }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformSampler(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;

        this._gl = gl;
        this._location = location;

        this.textureUnitIndex = undefined;
    }

    UniformSampler.prototype.set = function() {
        var gl = this._gl;
        gl.activeTexture(gl.TEXTURE0 + this.textureUnitIndex);

        var v = this.value;
        gl.bindTexture(v._target, v._texture);
    };

    UniformSampler.prototype._setSampler = function(textureUnitIndex) {
        this.textureUnitIndex = textureUnitIndex;
        this._gl.uniform1i(this._location, textureUnitIndex);
        return textureUnitIndex + 1;
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformInt(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = 0.0;

        this._gl = gl;
        this._location = location;
    }

    UniformInt.prototype.set = function() {
        if (this.value !== this._value) {
            this._value = this.value;
            this._gl.uniform1i(this._location, this.value);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformIntVec2(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Cartesian2();

        this._gl = gl;
        this._location = location;
    }

    UniformIntVec2.prototype.set = function() {
        var v = this.value;
        if (!Cartesian2.equals(v, this._value)) {
            Cartesian2.clone(v, this._value);
            this._gl.uniform2i(this._location, v.x, v.y);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformIntVec3(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Cartesian3();

        this._gl = gl;
        this._location = location;
    }

    UniformIntVec3.prototype.set = function() {
        var v = this.value;
        if (!Cartesian3.equals(v, this._value)) {
            Cartesian3.clone(v, this._value);
            this._gl.uniform3i(this._location, v.x, v.y, v.z);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformIntVec4(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Cartesian4();

        this._gl = gl;
        this._location = location;
    }

    UniformIntVec4.prototype.set = function() {
        var v = this.value;
        if (!Cartesian4.equals(v, this._value)) {
            Cartesian4.clone(v, this._value);
            this._gl.uniform4i(this._location, v.x, v.y, v.z, v.w);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformMat2(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Float32Array(4);

        this._gl = gl;
        this._location = location;
    }

    UniformMat2.prototype.set = function() {
        if (!Matrix2.equalsArray(this.value, this._value, 0)) {
            Matrix2.toArray(this.value, this._value);
            this._gl.uniformMatrix2fv(this._location, false, this._value);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformMat3(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Float32Array(9);

        this._gl = gl;
        this._location = location;
    }

    UniformMat3.prototype.set = function() {
        if (!Matrix3.equalsArray(this.value, this._value, 0)) {
            Matrix3.toArray(this.value, this._value);
            this._gl.uniformMatrix3fv(this._location, false, this._value);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformMat4(gl, activeUniform, uniformName, location) {
        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = undefined;
        this._value = new Float32Array(16);

        this._gl = gl;
        this._location = location;
    }

    UniformMat4.prototype.set = function() {
        if (!Matrix4.equalsArray(this.value, this._value, 0)) {
            Matrix4.toArray(this.value, this._value);
            this._gl.uniformMatrix4fv(this._location, false, this._value);
        }
    };

    return createUniform;
});

/*global define*/
define('Renderer/createUniformArray',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Matrix2',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/RuntimeError'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Color,
        defined,
        DeveloperError,
        Matrix2,
        Matrix3,
        Matrix4,
        RuntimeError) {
    'use strict';

    /**
     * @private
     */
    function createUniformArray(gl, activeUniform, uniformName, locations) {
        switch (activeUniform.type) {
            case gl.FLOAT:
                return new UniformArrayFloat(gl, activeUniform, uniformName, locations);
            case gl.FLOAT_VEC2:
                return new UniformArrayFloatVec2(gl, activeUniform, uniformName, locations);
            case gl.FLOAT_VEC3:
                return new UniformArrayFloatVec3(gl, activeUniform, uniformName, locations);
            case gl.FLOAT_VEC4:
                return new UniformArrayFloatVec4(gl, activeUniform, uniformName, locations);
            case gl.SAMPLER_2D:
            case gl.SAMPLER_CUBE:
                return new UniformArraySampler(gl, activeUniform, uniformName, locations);
            case gl.INT:
            case gl.BOOL:
                return new UniformArrayInt(gl, activeUniform, uniformName, locations);
            case gl.INT_VEC2:
            case gl.BOOL_VEC2:
                return new UniformArrayIntVec2(gl, activeUniform, uniformName, locations);
            case gl.INT_VEC3:
            case gl.BOOL_VEC3:
                return new UniformArrayIntVec3(gl, activeUniform, uniformName, locations);
            case gl.INT_VEC4:
            case gl.BOOL_VEC4:
                return new UniformArrayIntVec4(gl, activeUniform, uniformName, locations);
            case gl.FLOAT_MAT2:
                return new UniformArrayMat2(gl, activeUniform, uniformName, locations);
            case gl.FLOAT_MAT3:
                return new UniformArrayMat3(gl, activeUniform, uniformName, locations);
            case gl.FLOAT_MAT4:
                return new UniformArrayMat4(gl, activeUniform, uniformName, locations);
            default:
                throw new RuntimeError('Unrecognized uniform type: ' + activeUniform.type + ' for uniform "' + uniformName + '".');
        }
    }

    function UniformArrayFloat(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayFloat.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (v !== arraybuffer[i]) {
                arraybuffer[i] = v;
                changed = true;
            }
        }

        if (changed) {
            this._gl.uniform1fv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayFloatVec2(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length * 2);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayFloatVec2.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian2.equalsArray(v, arraybuffer, j)) {
                Cartesian2.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 2;
        }

        if (changed) {
            this._gl.uniform2fv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayFloatVec3(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length * 3);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayFloatVec3.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (defined(v.red)) {
                if ((v.red !== arraybuffer[j]) ||
                    (v.green !== arraybuffer[j + 1]) ||
                    (v.blue !== arraybuffer[j + 2])) {

                    arraybuffer[j] = v.red;
                    arraybuffer[j + 1] = v.green;
                    arraybuffer[j + 2] = v.blue;
                    changed = true;
                }
            } else if (defined(v.x)) {
                if (!Cartesian3.equalsArray(v, arraybuffer, j)) {
                    Cartesian3.pack(v, arraybuffer, j);
                    changed = true;
                }
            } else {
                                throw new DeveloperError('Invalid vec3 value.');
                            }

            j += 3;
        }

        if (changed) {
            this._gl.uniform3fv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayFloatVec4(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length * 4);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayFloatVec4.prototype.set = function() {
        // PERFORMANCE_IDEA: if it is a common case that only a few elements
        // in a uniform array change, we could use heuristics to determine
        // when it is better to call uniform4f for each element that changed
        // vs. call uniform4fv once to set the entire array.  This applies
        // to all uniform array types, not just vec4.  We might not care
        // once we have uniform buffers since that will be the fast path.

        // PERFORMANCE_IDEA: Micro-optimization (I bet it works though):
        // As soon as changed is true, break into a separate loop that
        // does the copy without the equals check.

        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (defined(v.red)) {
                if (!Color.equalsArray(v, arraybuffer, j)) {
                    Color.pack(v, arraybuffer, j);
                    changed = true;
                }
            } else if (defined(v.x)) {
                if (!Cartesian4.equalsArray(v, arraybuffer, j)) {
                    Cartesian4.pack(v, arraybuffer, j);
                    changed = true;
                }
            } else {
                                throw new DeveloperError('Invalid vec4 value.');
                            }

            j += 4;
        }

        if (changed) {
            this._gl.uniform4fv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArraySampler(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length);

        this._gl = gl;
        this._locations = locations;

        this.textureUnitIndex = undefined;
    }

    UniformArraySampler.prototype.set = function() {
        var gl = this._gl;
        var textureUnitIndex = gl.TEXTURE0 + this.textureUnitIndex;

        var value = this.value;
        var length = value.length;
        for (var i = 0; i < length; ++i) {
            var v = value[i];
            gl.activeTexture(textureUnitIndex + i);
            gl.bindTexture(v._target, v._texture);
        }
    };

    UniformArraySampler.prototype._setSampler = function(textureUnitIndex) {
        this.textureUnitIndex = textureUnitIndex;

        var locations = this._locations;
        var length = locations.length;
        for (var i = 0; i < length; ++i) {
            var index = textureUnitIndex + i;
            this._gl.uniform1i(locations[i], index);
        }

        return textureUnitIndex + length;
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayInt(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Int32Array(length);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayInt.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (v !== arraybuffer[i]) {
                arraybuffer[i] = v;
                changed = true;
            }
        }

        if (changed) {
            this._gl.uniform1iv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayIntVec2(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Int32Array(length * 2);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayIntVec2.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian2.equalsArray(v, arraybuffer, j)) {
                Cartesian2.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 2;
        }

        if (changed) {
            this._gl.uniform2iv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayIntVec3(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Int32Array(length * 3);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayIntVec3.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian3.equalsArray(v, arraybuffer, j)) {
                Cartesian3.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 3;
        }

        if (changed) {
            this._gl.uniform3iv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayIntVec4(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Int32Array(length * 4);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayIntVec4.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Cartesian4.equalsArray(v, arraybuffer, j)) {
                Cartesian4.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 4;
        }

        if (changed) {
            this._gl.uniform4iv(this._location, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayMat2(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length * 4);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayMat2.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Matrix2.equalsArray(v, arraybuffer, j)) {
                Matrix2.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 4;
        }

        if (changed) {
            this._gl.uniformMatrix2fv(this._location, false, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayMat3(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length * 9);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayMat3.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Matrix3.equalsArray(v, arraybuffer, j)) {
                Matrix3.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 9;
        }

        if (changed) {
            this._gl.uniformMatrix3fv(this._location, false, arraybuffer);
        }
    };

    ///////////////////////////////////////////////////////////////////////////

    function UniformArrayMat4(gl, activeUniform, uniformName, locations) {
        var length = locations.length;

        /**
         * @readonly
         */
        this.name = uniformName;

        this.value = new Array(length);
        this._value = new Float32Array(length * 16);

        this._gl = gl;
        this._location = locations[0];
    }

    UniformArrayMat4.prototype.set = function() {
        var value = this.value;
        var length = value.length;
        var arraybuffer = this._value;
        var changed = false;
        var j = 0;

        for (var i = 0; i < length; ++i) {
            var v = value[i];

            if (!Matrix4.equalsArray(v, arraybuffer, j)) {
                Matrix4.pack(v, arraybuffer, j);
                changed = true;
            }
            j += 16;
        }

        if (changed) {
            this._gl.uniformMatrix4fv(this._location, false, arraybuffer);
        }
    };

    return createUniformArray;
});

/*global define*/
define('Renderer/ShaderProgram',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/RuntimeError',
        './AutomaticUniforms',
        './ContextLimits',
        './createUniform',
        './createUniformArray'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        RuntimeError,
        AutomaticUniforms,
        ContextLimits,
        createUniform,
        createUniformArray) {
    'use strict';

    var nextShaderProgramId = 0;

    /**
     * @private
     */
    function ShaderProgram(options) {
        var modifiedFS = handleUniformPrecisionMismatches(options.vertexShaderText, options.fragmentShaderText);

        this._gl = options.gl;
        this._logShaderCompilation = options.logShaderCompilation;
        this._debugShaders = options.debugShaders;
        this._attributeLocations = options.attributeLocations;

        this._program = undefined;
        this._numberOfVertexAttributes = undefined;
        this._vertexAttributes = undefined;
        this._uniformsByName = undefined;
        this._uniforms = undefined;
        this._automaticUniforms = undefined;
        this._manualUniforms = undefined;
        this._duplicateUniformNames = modifiedFS.duplicateUniformNames;
        this._cachedShader = undefined; // Used by ShaderCache

        /**
         * @private
         */
        this.maximumTextureUnitIndex = undefined;

        this._vertexShaderSource = options.vertexShaderSource;
        this._vertexShaderText = options.vertexShaderText;
        this._fragmentShaderSource = options.fragmentShaderSource;
        this._fragmentShaderText = modifiedFS.fragmentShaderText;

        /**
         * @private
         */
        this.id = nextShaderProgramId++;
    }

    ShaderProgram.fromCache = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        return options.context.shaderCache.getShaderProgram(options);
    };

    ShaderProgram.replaceCache = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        return options.context.shaderCache.replaceShaderProgram(options);
    };

    defineProperties(ShaderProgram.prototype, {
        /**
         * GLSL source for the shader program's vertex shader.
         * @memberof ShaderProgram.prototype
         *
         * @type {ShaderSource}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },
        /**
         * GLSL source for the shader program's fragment shader.
         * @memberof ShaderProgram.prototype
         *
         * @type {ShaderSource}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },
        vertexAttributes : {
            get : function() {
                initialize(this);
                return this._vertexAttributes;
            }
        },
        numberOfVertexAttributes : {
            get : function() {
                initialize(this);
                return this._numberOfVertexAttributes;
            }
        },
        allUniforms : {
            get : function() {
                initialize(this);
                return this._uniformsByName;
            }
        }
    });

    function extractUniforms(shaderText) {
        var uniformNames = [];
        var uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
        if (defined(uniformLines)) {
            var len = uniformLines.length;
            for (var i = 0; i < len; i++) {
                var line = uniformLines[i].trim();
                var name = line.slice(line.lastIndexOf(' ') + 1);
                uniformNames.push(name);
            }
        }
        return uniformNames;
    }

    function handleUniformPrecisionMismatches(vertexShaderText, fragmentShaderText) {
        // If a uniform exists in both the vertex and fragment shader but with different precision qualifiers,
        // give the fragment shader uniform a different name. This fixes shader compilation errors on devices
        // that only support mediump in the fragment shader.
        var duplicateUniformNames = {};

        if (!ContextLimits.highpFloatSupported || !ContextLimits.highpIntSupported) {
            var i, j;
            var uniformName;
            var duplicateName;
            var vertexShaderUniforms = extractUniforms(vertexShaderText);
            var fragmentShaderUniforms = extractUniforms(fragmentShaderText);
            var vertexUniformsCount = vertexShaderUniforms.length;
            var fragmentUniformsCount = fragmentShaderUniforms.length;

            for (i = 0; i < vertexUniformsCount; i++) {
                for (j = 0; j < fragmentUniformsCount; j++) {
                    if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) {
                        uniformName = vertexShaderUniforms[i];
                        duplicateName = 'czm_mediump_' + uniformName;
                        // Update fragmentShaderText with renamed uniforms
                        var re = new RegExp(uniformName + '\\b', 'g');
                        fragmentShaderText = fragmentShaderText.replace(re, duplicateName);
                        duplicateUniformNames[duplicateName] = uniformName;
                    }
                }
            }
        }

        return {
            fragmentShaderText : fragmentShaderText,
            duplicateUniformNames : duplicateUniformNames
        };
    }

    var consolePrefix = '[Cesium WebGL] ';

    function createAndLinkProgram(gl, shader) {
        var vsSource = shader._vertexShaderText;
        var fsSource = shader._fragmentShaderText;

        var vertexShader = gl.createShader(gl.VERTEX_SHADER);
        gl.shaderSource(vertexShader, vsSource);
        gl.compileShader(vertexShader);

        var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
        gl.shaderSource(fragmentShader, fsSource);
        gl.compileShader(fragmentShader);

        var program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);

        gl.deleteShader(vertexShader);
        gl.deleteShader(fragmentShader);

        var attributeLocations = shader._attributeLocations;
        if (defined(attributeLocations)) {
            for ( var attribute in attributeLocations) {
                if (attributeLocations.hasOwnProperty(attribute)) {
                    gl.bindAttribLocation(program, attributeLocations[attribute], attribute);
                }
            }
        }

        gl.linkProgram(program);

        var log;
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            var debugShaders = shader._debugShaders;

            // For performance, only check compile errors if there is a linker error.
            if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
                log = gl.getShaderInfoLog(fragmentShader);
                console.error(consolePrefix + 'Fragment shader compile log: ' + log);
                if (defined(debugShaders)) {
                    var fragmentSourceTranslation = debugShaders.getTranslatedShaderSource(fragmentShader);
                    if (fragmentSourceTranslation !== '') {
                        console.error(consolePrefix + 'Translated fragment shader source:\n' + fragmentSourceTranslation);
                    } else {
                        console.error(consolePrefix + 'Fragment shader translation failed.');
                    }
                }

                gl.deleteProgram(program);
                throw new RuntimeError('Fragment shader failed to compile.  Compile log: ' + log);
            }

            if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
                log = gl.getShaderInfoLog(vertexShader);
                console.error(consolePrefix + 'Vertex shader compile log: ' + log);
                if (defined(debugShaders)) {
                    var vertexSourceTranslation = debugShaders.getTranslatedShaderSource(vertexShader);
                    if (vertexSourceTranslation !== '') {
                        console.error(consolePrefix + 'Translated vertex shader source:\n' + vertexSourceTranslation);
                    } else {
                        console.error(consolePrefix + 'Vertex shader translation failed.');
                    }
                }

                gl.deleteProgram(program);
                throw new RuntimeError('Vertex shader failed to compile.  Compile log: ' + log);
            }

            log = gl.getProgramInfoLog(program);
            console.error(consolePrefix + 'Shader program link log: ' + log);
            if (defined(debugShaders)) {
                console.error(consolePrefix + 'Translated vertex shader source:\n' + debugShaders.getTranslatedShaderSource(vertexShader));
                console.error(consolePrefix + 'Translated fragment shader source:\n' + debugShaders.getTranslatedShaderSource(fragmentShader));
            }

            gl.deleteProgram(program);
            throw new RuntimeError('Program failed to link.  Link log: ' + log);
        }

        var logShaderCompilation = shader._logShaderCompilation;

        if (logShaderCompilation) {
            log = gl.getShaderInfoLog(vertexShader);
            if (defined(log) && (log.length > 0)) {
                console.log(consolePrefix + 'Vertex shader compile log: ' + log);
            }
        }

        if (logShaderCompilation) {
            log = gl.getShaderInfoLog(fragmentShader);
            if (defined(log) && (log.length > 0)) {
                console.log(consolePrefix + 'Fragment shader compile log: ' + log);
            }
        }

        if (logShaderCompilation) {
            log = gl.getProgramInfoLog(program);
            if (defined(log) && (log.length > 0)) {
                console.log(consolePrefix + 'Shader program link log: ' + log);
            }
        }

        return program;
    }

    function findVertexAttributes(gl, program, numberOfAttributes) {
        var attributes = {};
        for (var i = 0; i < numberOfAttributes; ++i) {
            var attr = gl.getActiveAttrib(program, i);
            var location = gl.getAttribLocation(program, attr.name);

            attributes[attr.name] = {
                name : attr.name,
                type : attr.type,
                index : location
            };
        }

        return attributes;
    }

    function findUniforms(gl, program) {
        var uniformsByName = {};
        var uniforms = [];
        var samplerUniforms = [];

        var numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);

        for (var i = 0; i < numberOfUniforms; ++i) {
            var activeUniform = gl.getActiveUniform(program, i);
            var suffix = '[0]';
            var uniformName = activeUniform.name.indexOf(suffix, activeUniform.name.length - suffix.length) !== -1 ? activeUniform.name.slice(0, activeUniform.name.length - 3) : activeUniform.name;

            // Ignore GLSL built-in uniforms returned in Firefox.
            if (uniformName.indexOf('gl_') !== 0) {
                if (activeUniform.name.indexOf('[') < 0) {
                    // Single uniform
                    var location = gl.getUniformLocation(program, uniformName);

                    // IE 11.0.9 needs this check since getUniformLocation can return null
                    // if the uniform is not active (e.g., it is optimized out).  Looks like
                    // getActiveUniform() above returns uniforms that are not actually active.
                    if (location !== null) {
                        var uniform = createUniform(gl, activeUniform, uniformName, location);

                        uniformsByName[uniformName] = uniform;
                        uniforms.push(uniform);

                        if (uniform._setSampler) {
                            samplerUniforms.push(uniform);
                        }
                    }
                } else {
                    // Uniform array

                    var uniformArray;
                    var locations;
                    var value;
                    var loc;

                    // On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented
                    // as separate uniforms, one for each array element.  Check for and handle that case.
                    var indexOfBracket = uniformName.indexOf('[');
                    if (indexOfBracket >= 0) {
                        // We're assuming the array elements show up in numerical order - it seems to be true.
                        uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)];

                        // Nexus 4 with Android 4.3 needs this check, because it reports a uniform
                        // with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader.
                        if (!defined(uniformArray)) {
                            continue;
                        }

                        locations = uniformArray._locations;

                        // On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox,
                        // but the size is not 1 like it is in Firefox.  So if we push locations here,
                        // we'll end up adding too many locations.
                        if (locations.length <= 1) {
                            value = uniformArray.value;
                            loc = gl.getUniformLocation(program, uniformName);

                            // Workaround for IE 11.0.9.  See above.
                            if (loc !== null) {
                                locations.push(loc);
                                value.push(gl.getUniform(program, loc));
                            }
                        }
                    } else {
                        locations = [];
                        for (var j = 0; j < activeUniform.size; ++j) {
                            loc = gl.getUniformLocation(program, uniformName + '[' + j + ']');

                            // Workaround for IE 11.0.9.  See above.
                            if (loc !== null) {
                                locations.push(loc);
                            }
                        }
                        uniformArray = createUniformArray(gl, activeUniform, uniformName, locations);

                        uniformsByName[uniformName] = uniformArray;
                        uniforms.push(uniformArray);

                        if (uniformArray._setSampler) {
                            samplerUniforms.push(uniformArray);
                        }
                    }
                }
            }
        }

        return {
            uniformsByName : uniformsByName,
            uniforms : uniforms,
            samplerUniforms : samplerUniforms
        };
    }

    function partitionUniforms(shader, uniforms) {
        var automaticUniforms = [];
        var manualUniforms = [];

        for (var uniform in uniforms) {
            if (uniforms.hasOwnProperty(uniform)) {
                var uniformObject = uniforms[uniform];
                var uniformName = uniform;
                // if it's a duplicate uniform, use its original name so it is updated correctly
                var duplicateUniform = shader._duplicateUniformNames[uniformName];
                if (defined(duplicateUniform)) {
                    uniformObject.name = duplicateUniform;
                    uniformName = duplicateUniform;
                }
                var automaticUniform = AutomaticUniforms[uniformName];
                if (defined(automaticUniform)) {
                    automaticUniforms.push({
                        uniform : uniformObject,
                        automaticUniform : automaticUniform
                    });
                } else {
                    manualUniforms.push(uniformObject);
                }
            }
        }

        return {
            automaticUniforms : automaticUniforms,
            manualUniforms : manualUniforms
        };
    }

    function setSamplerUniforms(gl, program, samplerUniforms) {
        gl.useProgram(program);

        var textureUnitIndex = 0;
        var length = samplerUniforms.length;
        for (var i = 0; i < length; ++i) {
            textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex);
        }

        gl.useProgram(null);

        return textureUnitIndex;
    }

    function initialize(shader) {
        if (defined(shader._program)) {
            return;
        }

        var gl = shader._gl;
        var program = createAndLinkProgram(gl, shader, shader._debugShaders);
        var numberOfVertexAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
        var uniforms = findUniforms(gl, program);
        var partitionedUniforms = partitionUniforms(shader, uniforms.uniformsByName);

        shader._program = program;
        shader._numberOfVertexAttributes = numberOfVertexAttributes;
        shader._vertexAttributes = findVertexAttributes(gl, program, numberOfVertexAttributes);
        shader._uniformsByName = uniforms.uniformsByName;
        shader._uniforms = uniforms.uniforms;
        shader._automaticUniforms = partitionedUniforms.automaticUniforms;
        shader._manualUniforms = partitionedUniforms.manualUniforms;

        shader.maximumTextureUnitIndex = setSamplerUniforms(gl, program, uniforms.samplerUniforms);
    }

    ShaderProgram.prototype._bind = function() {
        initialize(this);
        this._gl.useProgram(this._program);
    };

    ShaderProgram.prototype._setUniforms = function(uniformMap, uniformState, validate) {
        var len;
        var i;

        if (defined(uniformMap)) {
            var manualUniforms = this._manualUniforms;
            len = manualUniforms.length;
            for (i = 0; i < len; ++i) {
                var mu = manualUniforms[i];
                mu.value = uniformMap[mu.name]();
            }
        }

        var automaticUniforms = this._automaticUniforms;
        len = automaticUniforms.length;
        for (i = 0; i < len; ++i) {
            var au = automaticUniforms[i];
            au.uniform.value = au.automaticUniform.getValue(uniformState);
        }

        ///////////////////////////////////////////////////////////////////

        // It appears that assigning the uniform values above and then setting them here
        // (which makes the GL calls) is faster than removing this loop and making
        // the GL calls above.  I suspect this is because each GL call pollutes the
        // L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
        var uniforms = this._uniforms;
        len = uniforms.length;
        for (i = 0; i < len; ++i) {
            uniforms[i].set();
        }

        if (validate) {
            var gl = this._gl;
            var program = this._program;

            gl.validateProgram(program);
                        if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
                throw new DeveloperError('Program validation failed.  Program info log: ' + gl.getProgramInfoLog(program));
            }
                    }
    };

    ShaderProgram.prototype.isDestroyed = function() {
        return false;
    };

    ShaderProgram.prototype.destroy = function() {
        this._cachedShader.cache.releaseShaderProgram(this);
        return undefined;
    };

    ShaderProgram.prototype.finalDestroy = function() {
        this._gl.deleteProgram(this._program);
        return destroyObject(this);
    };

    return ShaderProgram;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/degreesPerRadian',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for converting radians to degrees.\n\
 *\n\
 * @alias czm_degreesPerRadian\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.DEGREES_PER_RADIAN\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_degreesPerRadian = ...;\n\
 *\n\
 * // Example\n\
 * float deg = czm_degreesPerRadian * rad;\n\
 */\n\
const float czm_degreesPerRadian = 57.29577951308232;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/depthRange',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL vec2 constant for defining the depth range.\n\
 * This is a workaround to a bug where IE11 does not implement gl_DepthRange.\n\
 *\n\
 * @alias czm_depthRange\n\
 * @glslConstant\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * float depthRangeNear = czm_depthRange.near;\n\
 * float depthRangeFar = czm_depthRange.far;\n\
 *\n\
 */\n\
const czm_depthRangeStruct czm_depthRange = czm_depthRangeStruct(0.0, 1.0);\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon1',[],function() {
    'use strict';
    return "/**\n\
 * 0.1\n\
 *\n\
 * @name czm_epsilon1\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon1 = 0.1;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon2',[],function() {
    'use strict';
    return "/**\n\
 * 0.01\n\
 *\n\
 * @name czm_epsilon2\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon2 = 0.01;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon3',[],function() {
    'use strict';
    return "/**\n\
 * 0.001\n\
 *\n\
 * @name czm_epsilon3\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon3 = 0.001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon4',[],function() {
    'use strict';
    return "/**\n\
 * 0.0001\n\
 *\n\
 * @name czm_epsilon4\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon4 = 0.0001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon5',[],function() {
    'use strict';
    return "/**\n\
 * 0.00001\n\
 *\n\
 * @name czm_epsilon5\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon5 = 0.00001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon6',[],function() {
    'use strict';
    return "/**\n\
 * 0.000001\n\
 *\n\
 * @name czm_epsilon6\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon6 = 0.000001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/epsilon7',[],function() {
    'use strict';
    return "/**\n\
 * 0.0000001\n\
 *\n\
 * @name czm_epsilon7\n\
 * @glslConstant\n\
 */\n\
const float czm_epsilon7 = 0.0000001;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/infinity',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_infinity\n\
 * @glslConstant\n\
 */\n\
const float czm_infinity = 5906376272000.0;  // Distance from the Sun to Pluto in meters.  TODO: What is best given lowp, mediump, and highp?\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/oneOverPi',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>1/pi</code>.\n\
 *\n\
 * @alias czm_oneOverPi\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.ONE_OVER_PI\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_oneOverPi = ...;\n\
 *\n\
 * // Example\n\
 * float pi = 1.0 / czm_oneOverPi;\n\
 */\n\
const float czm_oneOverPi = 0.3183098861837907;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/oneOverTwoPi',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>1/2pi</code>.\n\
 *\n\
 * @alias czm_oneOverTwoPi\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.ONE_OVER_TWO_PI\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_oneOverTwoPi = ...;\n\
 *\n\
 * // Example\n\
 * float pi = 2.0 * czm_oneOverTwoPi;\n\
 */\n\
const float czm_oneOverTwoPi = 0.15915494309189535;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passCompute',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#COMPUTE}\n\
 *\n\
 * @name czm_passCompute\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passCompute = 1.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passEnvironment',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#ENVIRONMENT}\n\
 *\n\
 * @name czm_passEnvironment\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passEnvironment = 0.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passGlobe',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#GLOBE}\n\
 *\n\
 * @name czm_passGlobe\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passGlobe = 2.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passGround',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#GROUND}\n\
 *\n\
 * @name czm_passGround\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passGround = 3.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passOpaque',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#OPAQUE}\n\
 *\n\
 * @name czm_passOpaque\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passOpaque = 4.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passOverlay',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#OVERLAY}\n\
 *\n\
 * @name czm_passOverlay\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passOverlay = 6.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/passTranslucent',[],function() {
    'use strict';
    return "/**\n\
 * The automatic GLSL constant for {@link Pass#TRANSLUCENT}\n\
 *\n\
 * @name czm_passTranslucent\n\
 * @glslConstant\n\
 *\n\
 * @see czm_pass\n\
 */\n\
const float czm_passTranslucent = 5.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/pi',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>Math.PI</code>.\n\
 *\n\
 * @alias czm_pi\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.PI\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_pi = ...;\n\
 *\n\
 * // Example\n\
 * float twoPi = 2.0 * czm_pi;\n\
 */\n\
const float czm_pi = 3.141592653589793;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverFour',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>pi/4</code>.\n\
 *\n\
 * @alias czm_piOverFour\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.PI_OVER_FOUR\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_piOverFour = ...;\n\
 *\n\
 * // Example\n\
 * float pi = 4.0 * czm_piOverFour;\n\
 */\n\
const float czm_piOverFour = 0.7853981633974483;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverSix',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>pi/6</code>.\n\
 *\n\
 * @alias czm_piOverSix\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.PI_OVER_SIX\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_piOverSix = ...;\n\
 *\n\
 * // Example\n\
 * float pi = 6.0 * czm_piOverSix;\n\
 */\n\
const float czm_piOverSix = 0.5235987755982988;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverThree',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>pi/3</code>.\n\
 *\n\
 * @alias czm_piOverThree\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.PI_OVER_THREE\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_piOverThree = ...;\n\
 *\n\
 * // Example\n\
 * float pi = 3.0 * czm_piOverThree;\n\
 */\n\
const float czm_piOverThree = 1.0471975511965976;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/piOverTwo',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>pi/2</code>.\n\
 *\n\
 * @alias czm_piOverTwo\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.PI_OVER_TWO\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_piOverTwo = ...;\n\
 *\n\
 * // Example\n\
 * float pi = 2.0 * czm_piOverTwo;\n\
 */\n\
const float czm_piOverTwo = 1.5707963267948966;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/radiansPerDegree',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for converting degrees to radians.\n\
 *\n\
 * @alias czm_radiansPerDegree\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.RADIANS_PER_DEGREE\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_radiansPerDegree = ...;\n\
 *\n\
 * // Example\n\
 * float rad = czm_radiansPerDegree * deg;\n\
 */\n\
const float czm_radiansPerDegree = 0.017453292519943295;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneMode2D',[],function() {
    'use strict';
    return "/**\n\
 * The constant identifier for the 2D {@link SceneMode}\n\
 *\n\
 * @name czm_sceneMode2D\n\
 * @glslConstant\n\
 * @see czm_sceneMode\n\
 * @see czm_sceneModeColumbusView\n\
 * @see czm_sceneMode3D\n\
 * @see czm_sceneModeMorphing\n\
 */\n\
const float czm_sceneMode2D = 2.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneMode3D',[],function() {
    'use strict';
    return "/**\n\
 * The constant identifier for the 3D {@link SceneMode}\n\
 *\n\
 * @name czm_sceneMode3D\n\
 * @glslConstant\n\
 * @see czm_sceneMode\n\
 * @see czm_sceneMode2D\n\
 * @see czm_sceneModeColumbusView\n\
 * @see czm_sceneModeMorphing\n\
 */\n\
const float czm_sceneMode3D = 3.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneModeColumbusView',[],function() {
    'use strict';
    return "/**\n\
 * The constant identifier for the Columbus View {@link SceneMode}\n\
 *\n\
 * @name czm_sceneModeColumbusView\n\
 * @glslConstant\n\
 * @see czm_sceneMode\n\
 * @see czm_sceneMode2D\n\
 * @see czm_sceneMode3D\n\
 * @see czm_sceneModeMorphing\n\
 */\n\
const float czm_sceneModeColumbusView = 1.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/sceneModeMorphing',[],function() {
    'use strict';
    return "/**\n\
 * The constant identifier for the Morphing {@link SceneMode}\n\
 *\n\
 * @name czm_sceneModeMorphing\n\
 * @glslConstant\n\
 * @see czm_sceneMode\n\
 * @see czm_sceneMode2D\n\
 * @see czm_sceneModeColumbusView\n\
 * @see czm_sceneMode3D\n\
 */\n\
const float czm_sceneModeMorphing = 0.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/solarRadius',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for one solar radius.\n\
 *\n\
 * @alias czm_solarRadius\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.SOLAR_RADIUS\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_solarRadius = ...;\n\
 */\n\
const float czm_solarRadius = 695500000.0;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/threePiOver2',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>3pi/2</code>.\n\
 *\n\
 * @alias czm_threePiOver2\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.THREE_PI_OVER_TWO\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_threePiOver2 = ...;\n\
 *\n\
 * // Example\n\
 * float pi = (2.0 / 3.0) * czm_threePiOver2;\n\
 */\n\
const float czm_threePiOver2 = 4.71238898038469;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/twoPi',[],function() {
    'use strict';
    return "/**\n\
 * A built-in GLSL floating-point constant for <code>2pi</code>.\n\
 *\n\
 * @alias czm_twoPi\n\
 * @glslConstant\n\
 *\n\
 * @see CesiumMath.TWO_PI\n\
 *\n\
 * @example\n\
 * // GLSL declaration\n\
 * const float czm_twoPi = ...;\n\
 *\n\
 * // Example\n\
 * float pi = czm_twoPi / 2.0;\n\
 */\n\
const float czm_twoPi = 6.283185307179586;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Constants/webMercatorMaxLatitude',[],function() {
    'use strict';
    return "/**\n\
 * The maximum latitude, in radians, both North and South, supported by a Web Mercator\n\
 * (EPSG:3857) projection.  Technically, the Mercator projection is defined\n\
 * for any latitude up to (but not including) 90 degrees, but it makes sense\n\
 * to cut it off sooner because it grows exponentially with increasing latitude.\n\
 * The logic behind this particular cutoff value, which is the one used by\n\
 * Google Maps, Bing Maps, and Esri, is that it makes the projection\n\
 * square.  That is, the rectangle is equal in the X and Y directions.\n\
 *\n\
 * The constant value is computed as follows:\n\
 *   czm_pi * 0.5 - (2.0 * atan(exp(-czm_pi)))\n\
 *\n\
 * @name czm_webMercatorMaxLatitude\n\
 * @glslConstant\n\
 */\n\
const float czm_webMercatorMaxLatitude = 1.4844222297453324;\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/depthRangeStruct',[],function() {
    'use strict';
    return "/**\n\
 * @name czm_depthRangeStruct\n\
 * @glslStruct\n\
 */\n\
struct czm_depthRangeStruct\n\
{\n\
    float near;\n\
    float far;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/ellipsoid',[],function() {
    'use strict';
    return "/** DOC_TBA\n\
 *\n\
 * @name czm_ellipsoid\n\
 * @glslStruct\n\
 */\n\
struct czm_ellipsoid\n\
{\n\
    vec3 center;\n\
    vec3 radii;\n\
    vec3 inverseRadii;\n\
    vec3 inverseRadiiSquared;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/material',[],function() {
    'use strict';
    return "/**\n\
 * Holds material information that can be used for lighting. Returned by all czm_getMaterial functions.\n\
 *\n\
 * @name czm_material\n\
 * @glslStruct\n\
 *\n\
 * @property {vec3} diffuse Incoming light that scatters evenly in all directions.\n\
 * @property {float} specular Intensity of incoming light reflecting in a single direction.\n\
 * @property {float} shininess The sharpness of the specular reflection.  Higher values create a smaller, more focused specular highlight.\n\
 * @property {vec3} normal Surface's normal in eye coordinates. It is used for effects such as normal mapping. The default is the surface's unmodified normal.\n\
 * @property {vec3} emission Light emitted by the material equally in all directions. The default is vec3(0.0), which emits no light.\n\
 * @property {float} alpha Opacity of this material. 0.0 is completely transparent; 1.0 is completely opaque.\n\
 */\n\
struct czm_material\n\
{\n\
    vec3 diffuse;\n\
    float specular;\n\
    float shininess;\n\
    vec3 normal;\n\
    vec3 emission;\n\
    float alpha;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/materialInput',[],function() {
    'use strict';
    return "/**\n\
 * Used as input to every material's czm_getMaterial function.\n\
 *\n\
 * @name czm_materialInput\n\
 * @glslStruct\n\
 *\n\
 * @property {float} s 1D texture coordinates.\n\
 * @property {vec2} st 2D texture coordinates.\n\
 * @property {vec3} str 3D texture coordinates.\n\
 * @property {vec3} normalEC Unperturbed surface normal in eye coordinates.\n\
 * @property {mat3} tangentToEyeMatrix Matrix for converting a tangent space normal to eye space.\n\
 * @property {vec3} positionToEyeEC Vector from the fragment to the eye in eye coordinates.  The magnitude is the distance in meters from the fragment to the eye.\n\
 */\n\
struct czm_materialInput\n\
{\n\
    float s;\n\
    vec2 st;\n\
    vec3 str;\n\
    vec3 normalEC;\n\
    mat3 tangentToEyeMatrix;\n\
    vec3 positionToEyeEC;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/ray',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_ray\n\
 * @glslStruct\n\
 */\n\
struct czm_ray\n\
{\n\
    vec3 origin;\n\
    vec3 direction;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/raySegment',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_raySegment\n\
 * @glslStruct\n\
 */\n\
struct czm_raySegment\n\
{\n\
    float start;\n\
    float stop;\n\
};\n\
\n\
/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_emptyRaySegment\n\
 * @glslConstant \n\
 */\n\
const czm_raySegment czm_emptyRaySegment = czm_raySegment(-czm_infinity, -czm_infinity);\n\
\n\
/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_fullRaySegment\n\
 * @glslConstant \n\
 */\n\
const czm_raySegment czm_fullRaySegment = czm_raySegment(0.0, czm_infinity);\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Structs/shadowParameters',[],function() {
    'use strict';
    return "struct czm_shadowParameters\n\
{\n\
#ifdef USE_CUBE_MAP_SHADOW\n\
    vec3 texCoords;\n\
#else\n\
    vec2 texCoords;\n\
#endif\n\
\n\
    float depthBias;\n\
    float depth;\n\
    float nDotL;\n\
    vec2 texelStepSize;\n\
    float normalShadingSmooth;\n\
    float darkness;\n\
};\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/alphaWeight',[],function() {
    'use strict';
    return "/**\n\
 * @private\n\
 */\n\
float czm_alphaWeight(float a)\n\
{\n\
    float z;\n\
    if (czm_sceneMode != czm_sceneMode2D)\n\
    {\n\
        float x = 2.0 * (gl_FragCoord.x - czm_viewport.x) / czm_viewport.z - 1.0;\n\
        float y = 2.0 * (gl_FragCoord.y - czm_viewport.y) / czm_viewport.w - 1.0;\n\
        z = (gl_FragCoord.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2];\n\
        vec4 q = vec4(x, y, z, 0.0);\n\
        q /= gl_FragCoord.w;\n\
        z = (czm_inverseProjectionOIT * q).z;\n\
    }\n\
    else\n\
    {\n\
        z = gl_FragCoord.z * (czm_currentFrustum.y - czm_currentFrustum.x) + czm_currentFrustum.x;\n\
    }\n\
    \n\
    // See Weighted Blended Order-Independent Transparency for examples of different weighting functions:\n\
    // http://jcgt.org/published/0002/02/09/\n\
    return pow(a + 0.01, 4.0) + max(1e-2, min(3.0 * 1e3, 100.0 / (1e-5 + pow(abs(z) / 10.0, 3.0) + pow(abs(z) / 200.0, 6.0))));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/antialias',[],function() {
    'use strict';
    return "/**\n\
 * Procedural anti-aliasing by blurring two colors that meet at a sharp edge.\n\
 *\n\
 * @name czm_antialias\n\
 * @glslFunction\n\
 *\n\
 * @param {vec4} color1 The color on one side of the edge.\n\
 * @param {vec4} color2 The color on the other side of the edge.\n\
 * @param {vec4} currentcolor The current color, either <code>color1</code> or <code>color2</code>.\n\
 * @param {float} dist The distance to the edge in texture coordinates.\n\
 * @param {float} [fuzzFactor=0.1] Controls the blurriness between the two colors.\n\
 * @returns {vec4} The anti-aliased color.\n\
 *\n\
 * @example\n\
 * // GLSL declarations\n\
 * vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist, float fuzzFactor);\n\
 * vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist);\n\
 *\n\
 * // get the color for a material that has a sharp edge at the line y = 0.5 in texture space\n\
 * float dist = abs(textureCoordinates.t - 0.5);\n\
 * vec4 currentColor = mix(bottomColor, topColor, step(0.5, textureCoordinates.t));\n\
 * vec4 color = czm_antialias(bottomColor, topColor, currentColor, dist, 0.1);\n\
 */\n\
vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist, float fuzzFactor)\n\
{\n\
    float val1 = clamp(dist / fuzzFactor, 0.0, 1.0);\n\
    float val2 = clamp((dist - 0.5) / fuzzFactor, 0.0, 1.0);\n\
    val1 = val1 * (1.0 - val2);\n\
    val1 = val1 * val1 * (3.0 - (2.0 * val1));\n\
    val1 = pow(val1, 0.5); //makes the transition nicer\n\
    \n\
    vec4 midColor = (color1 + color2) * 0.5;\n\
    return mix(midColor, currentColor, val1);\n\
}\n\
\n\
vec4 czm_antialias(vec4 color1, vec4 color2, vec4 currentColor, float dist)\n\
{\n\
    return czm_antialias(color1, color2, currentColor, dist, 0.1);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeColor',[],function() {
    'use strict';
    return "\n\
vec4 czm_cascadeColor(vec4 weights)\n\
{\n\
    return vec4(1.0, 0.0, 0.0, 1.0) * weights.x +\n\
           vec4(0.0, 1.0, 0.0, 1.0) * weights.y +\n\
           vec4(0.0, 0.0, 1.0, 1.0) * weights.z +\n\
           vec4(1.0, 0.0, 1.0, 1.0) * weights.w;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeDistance',[],function() {
    'use strict';
    return "\n\
uniform vec4 shadowMap_cascadeDistances;\n\
\n\
float czm_cascadeDistance(vec4 weights)\n\
{\n\
    return dot(shadowMap_cascadeDistances, weights);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeMatrix',[],function() {
    'use strict';
    return "\n\
uniform mat4 shadowMap_cascadeMatrices[4];\n\
\n\
mat4 czm_cascadeMatrix(vec4 weights)\n\
{\n\
    return shadowMap_cascadeMatrices[0] * weights.x +\n\
           shadowMap_cascadeMatrices[1] * weights.y +\n\
           shadowMap_cascadeMatrices[2] * weights.z +\n\
           shadowMap_cascadeMatrices[3] * weights.w;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cascadeWeights',[],function() {
    'use strict';
    return "\n\
uniform vec4 shadowMap_cascadeSplits[2];\n\
\n\
vec4 czm_cascadeWeights(float depthEye)\n\
{\n\
    // One component is set to 1.0 and all others set to 0.0.\n\
    vec4 near = step(shadowMap_cascadeSplits[0], vec4(depthEye));\n\
    vec4 far = step(depthEye, shadowMap_cascadeSplits[1]);\n\
    return near * far;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/columbusViewMorph',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_columbusViewMorph\n\
 * @glslFunction\n\
 */\n\
vec4 czm_columbusViewMorph(vec4 position2D, vec4 position3D, float time)\n\
{\n\
    // Just linear for now.\n\
    vec3 p = mix(position2D.xyz, position3D.xyz, time);\n\
    return vec4(p, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/computePosition',[],function() {
    'use strict';
    return "/**\n\
 * Returns a position in model coordinates relative to eye taking into\n\
 * account the current scene mode: 3D, 2D, or Columbus view.\n\
 * <p>\n\
 * This uses standard position attributes, <code>position3DHigh</code>, \n\
 * <code>position3DLow</code>, <code>position2DHigh</code>, and <code>position2DLow</code>, \n\
 * and should be used when writing a vertex shader for an {@link Appearance}.\n\
 * </p>\n\
 *\n\
 * @name czm_computePosition\n\
 * @glslFunction\n\
 *\n\
 * @returns {vec4} The position relative to eye.\n\
 *\n\
 * @example\n\
 * vec4 p = czm_computePosition();\n\
 * v_positionEC = (czm_modelViewRelativeToEye * p).xyz;\n\
 * gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
 *\n\
 * @see czm_translateRelativeToEye\n\
 */\n\
vec4 czm_computePosition();\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/cosineAndSine',[],function() {
    'use strict';
    return "/**\n\
 * @private\n\
 */\n\
vec2 cordic(float angle)\n\
{\n\
// Scale the vector by the appropriate factor for the 24 iterations to follow.\n\
    vec2 vector = vec2(6.0725293500888267e-1, 0.0);\n\
// Iteration 1\n\
    float sense = (angle < 0.0) ? -1.0 : 1.0;\n\
 //   float factor = sense * 1.0;  // 2^-0\n\
    mat2 rotation = mat2(1.0, sense, -sense, 1.0);\n\
    vector = rotation * vector;\n\
    angle -= sense * 7.8539816339744828e-1;  // atan(2^-0)\n\
// Iteration 2\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    float factor = sense * 5.0e-1;  // 2^-1\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 4.6364760900080609e-1;  // atan(2^-1)\n\
// Iteration 3\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 2.5e-1;  // 2^-2\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 2.4497866312686414e-1;  // atan(2^-2)\n\
// Iteration 4\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.25e-1;  // 2^-3\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 1.2435499454676144e-1;  // atan(2^-3)\n\
// Iteration 5\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 6.25e-2;  // 2^-4\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 6.2418809995957350e-2;  // atan(2^-4)\n\
// Iteration 6\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 3.125e-2;  // 2^-5\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 3.1239833430268277e-2;  // atan(2^-5)\n\
// Iteration 7\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.5625e-2;  // 2^-6\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 1.5623728620476831e-2;  // atan(2^-6)\n\
// Iteration 8\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 7.8125e-3;  // 2^-7\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 7.8123410601011111e-3;  // atan(2^-7)\n\
// Iteration 9\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 3.90625e-3;  // 2^-8\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 3.9062301319669718e-3;  // atan(2^-8)\n\
// Iteration 10\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.953125e-3;  // 2^-9\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 1.9531225164788188e-3;  // atan(2^-9)\n\
// Iteration 11\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 9.765625e-4;  // 2^-10\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 9.7656218955931946e-4;  // atan(2^-10)\n\
// Iteration 12\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 4.8828125e-4;  // 2^-11\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 4.8828121119489829e-4;  // atan(2^-11)\n\
// Iteration 13\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 2.44140625e-4;  // 2^-12\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 2.4414062014936177e-4;  // atan(2^-12)\n\
// Iteration 14\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.220703125e-4;  // 2^-13\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 1.2207031189367021e-4;  // atan(2^-13)\n\
// Iteration 15\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 6.103515625e-5;  // 2^-14\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 6.1035156174208773e-5;  // atan(2^-14)\n\
// Iteration 16\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 3.0517578125e-5;  // 2^-15\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 3.0517578115526096e-5;  // atan(2^-15)\n\
// Iteration 17\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.52587890625e-5;  // 2^-16\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 1.5258789061315762e-5;  // atan(2^-16)\n\
// Iteration 18\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 7.62939453125e-6;  // 2^-17\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 7.6293945311019700e-6;  // atan(2^-17)\n\
// Iteration 19\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 3.814697265625e-6;  // 2^-18\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 3.8146972656064961e-6;  // atan(2^-18)\n\
// Iteration 20\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.9073486328125e-6;  // 2^-19\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 1.9073486328101870e-6;  // atan(2^-19)\n\
// Iteration 21\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 9.5367431640625e-7;  // 2^-20\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 9.5367431640596084e-7;  // atan(2^-20)\n\
// Iteration 22\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 4.76837158203125e-7;  // 2^-21\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 4.7683715820308884e-7;  // atan(2^-21)\n\
// Iteration 23\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 2.384185791015625e-7;  // 2^-22\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
    angle -= sense * 2.3841857910155797e-7;  // atan(2^-22)\n\
// Iteration 24\n\
    sense = (angle < 0.0) ? -1.0 : 1.0;\n\
    factor = sense * 1.1920928955078125e-7;  // 2^-23\n\
    rotation[0][1] = factor;\n\
    rotation[1][0] = -factor;\n\
    vector = rotation * vector;\n\
//    angle -= sense * 1.1920928955078068e-7;  // atan(2^-23)\n\
\n\
    return vector;\n\
}\n\
\n\
/**\n\
 * Computes the cosine and sine of the provided angle using the CORDIC algorithm.\n\
 *\n\
 * @name czm_cosineAndSine\n\
 * @glslFunction\n\
 *\n\
 * @param {float} angle The angle in radians.\n\
 *\n\
 * @returns {vec2} The resulting cosine of the angle (as the x coordinate) and sine of the angle (as the y coordinate).\n\
 *\n\
 * @example\n\
 * vec2 v = czm_cosineAndSine(czm_piOverSix);\n\
 * float cosine = v.x;\n\
 * float sine = v.y;\n\
 */\n\
vec2 czm_cosineAndSine(float angle)\n\
{\n\
    if (angle < -czm_piOverTwo || angle > czm_piOverTwo)\n\
    {\n\
        if (angle < 0.0)\n\
        {\n\
            return -cordic(angle + czm_pi);\n\
        }\n\
        else\n\
        {\n\
            return -cordic(angle - czm_pi);\n\
        }\n\
    }\n\
    else\n\
    {\n\
        return cordic(angle);\n\
    }\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/decompressTextureCoordinates',[],function() {
    'use strict';
    return "/**\n\
 * Decompresses texture coordinates that were packed into a single float.\n\
 *\n\
 * @name czm_decompressTextureCoordinates\n\
 * @glslFunction\n\
 *\n\
 * @param {float} encoded The compressed texture coordinates.\n\
 * @returns {vec2} The decompressed texture coordinates.\n\
 */\n\
 vec2 czm_decompressTextureCoordinates(float encoded)\n\
 {\n\
    float temp = encoded / 4096.0;\n\
    float xZeroTo4095 = floor(temp);\n\
    float stx = xZeroTo4095 / 4095.0;\n\
    float sty = (encoded - xZeroTo4095 * 4096.0) / 4095.0;\n\
    return vec2(stx, sty);\n\
 }\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/eastNorthUpToEyeCoordinates',[],function() {
    'use strict';
    return "/**\n\
 * Computes a 3x3 rotation matrix that transforms vectors from an ellipsoid's east-north-up coordinate system \n\
 * to eye coordinates.  In east-north-up coordinates, x points east, y points north, and z points along the \n\
 * surface normal.  East-north-up can be used as an ellipsoid's tangent space for operations such as bump mapping.\n\
 * <br /><br />\n\
 * The ellipsoid is assumed to be centered at the model coordinate's origin.\n\
 *\n\
 * @name czm_eastNorthUpToEyeCoordinates\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} positionMC The position on the ellipsoid in model coordinates.\n\
 * @param {vec3} normalEC The normalized ellipsoid surface normal, at <code>positionMC</code>, in eye coordinates.\n\
 *\n\
 * @returns {mat3} A 3x3 rotation matrix that transforms vectors from the east-north-up coordinate system to eye coordinates.\n\
 *\n\
 * @example\n\
 * // Transform a vector defined in the east-north-up coordinate \n\
 * // system, (0, 0, 1) which is the surface normal, to eye \n\
 * // coordinates.\n\
 * mat3 m = czm_eastNorthUpToEyeCoordinates(positionMC, normalEC);\n\
 * vec3 normalEC = m * vec3(0.0, 0.0, 1.0);\n\
 */\n\
mat3 czm_eastNorthUpToEyeCoordinates(vec3 positionMC, vec3 normalEC)\n\
{\n\
    vec3 tangentMC = normalize(vec3(-positionMC.y, positionMC.x, 0.0));  // normalized surface tangent in model coordinates\n\
    vec3 tangentEC = normalize(czm_normal3D * tangentMC);                // normalized surface tangent in eye coordiantes\n\
    vec3 bitangentEC = normalize(cross(normalEC, tangentEC));            // normalized surface bitangent in eye coordinates\n\
\n\
    return mat3(\n\
        tangentEC.x,   tangentEC.y,   tangentEC.z,\n\
        bitangentEC.x, bitangentEC.y, bitangentEC.z,\n\
        normalEC.x,    normalEC.y,    normalEC.z);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/ellipsoidContainsPoint',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_ellipsoidContainsPoint\n\
 * @glslFunction\n\
 *\n\
 */\n\
bool czm_ellipsoidContainsPoint(czm_ellipsoid ellipsoid, vec3 point)\n\
{\n\
    vec3 scaled = ellipsoid.inverseRadii * (czm_inverseModelView * vec4(point, 1.0)).xyz;\n\
    return (dot(scaled, scaled) <= 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/ellipsoidNew',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_ellipsoidNew\n\
 * @glslFunction\n\
 *\n\
 */\n\
czm_ellipsoid czm_ellipsoidNew(vec3 center, vec3 radii)\n\
{\n\
    vec3 inverseRadii = vec3(1.0 / radii.x, 1.0 / radii.y, 1.0 / radii.z);\n\
    vec3 inverseRadiiSquared = inverseRadii * inverseRadii;\n\
    czm_ellipsoid temp = czm_ellipsoid(center, radii, inverseRadii, inverseRadiiSquared);\n\
    return temp;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/ellipsoidWgs84TextureCoordinates',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_ellipsoidWgs84TextureCoordinates\n\
 * @glslFunction\n\
 */\n\
vec2 czm_ellipsoidWgs84TextureCoordinates(vec3 normal)\n\
{\n\
    return vec2(atan(normal.y, normal.x) * czm_oneOverTwoPi + 0.5, asin(normal.z) * czm_oneOverPi + 0.5);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/equalsEpsilon',[],function() {
    'use strict';
    return "/**\n\
 * Compares <code>left</code> and <code>right</code> componentwise. Returns <code>true</code>\n\
 * if they are within <code>epsilon</code> and <code>false</code> otherwise. The inputs\n\
 * <code>left</code> and <code>right</code> can be <code>float</code>s, <code>vec2</code>s,\n\
 * <code>vec3</code>s, or <code>vec4</code>s.\n\
 *\n\
 * @name czm_equalsEpsilon\n\
 * @glslFunction\n\
 *\n\
 * @param {} left The first vector.\n\
 * @param {} right The second vector.\n\
 * @param {float} epsilon The epsilon to use for equality testing.\n\
 * @returns {bool} <code>true</code> if the components are within <code>epsilon</code> and <code>false</code> otherwise.\n\
 *\n\
 * @example\n\
 * // GLSL declarations\n\
 * bool czm_equalsEpsilon(float left, float right, float epsilon);\n\
 * bool czm_equalsEpsilon(vec2 left, vec2 right, float epsilon);\n\
 * bool czm_equalsEpsilon(vec3 left, vec3 right, float epsilon);\n\
 * bool czm_equalsEpsilon(vec4 left, vec4 right, float epsilon);\n\
 */\n\
bool czm_equalsEpsilon(vec4 left, vec4 right, float epsilon) {\n\
    return all(lessThanEqual(abs(left - right), vec4(epsilon)));\n\
}\n\
\n\
bool czm_equalsEpsilon(vec3 left, vec3 right, float epsilon) {\n\
    return all(lessThanEqual(abs(left - right), vec3(epsilon)));\n\
}\n\
\n\
bool czm_equalsEpsilon(vec2 left, vec2 right, float epsilon) {\n\
    return all(lessThanEqual(abs(left - right), vec2(epsilon)));\n\
}\n\
\n\
bool czm_equalsEpsilon(float left, float right, float epsilon) {\n\
    return (abs(left - right) <= epsilon);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/eyeOffset',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_eyeOffset\n\
 * @glslFunction\n\
 *\n\
 * @param {vec4} positionEC DOC_TBA.\n\
 * @param {vec3} eyeOffset DOC_TBA.\n\
 *\n\
 * @returns {vec4} DOC_TBA.\n\
 */\n\
vec4 czm_eyeOffset(vec4 positionEC, vec3 eyeOffset)\n\
{\n\
    // This equation is approximate in x and y.\n\
    vec4 p = positionEC;\n\
    vec4 zEyeOffset = normalize(p) * eyeOffset.z;\n\
    p.xy += eyeOffset.xy + zEyeOffset.xy;\n\
    p.z += zEyeOffset.z;\n\
    return p;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/eyeToWindowCoordinates',[],function() {
    'use strict';
    return "/**\n\
 * Transforms a position from eye to window coordinates.  The transformation\n\
 * from eye to clip coordinates is done using {@link czm_projection}.\n\
 * The transform from normalized device coordinates to window coordinates is\n\
 * done using {@link czm_viewportTransformation}, which assumes a depth range\n\
 * of <code>near = 0</code> and <code>far = 1</code>.\n\
 * <br /><br />\n\
 * This transform is useful when there is a need to manipulate window coordinates\n\
 * in a vertex shader as done by {@link BillboardCollection}.\n\
 *\n\
 * @name czm_eyeToWindowCoordinates\n\
 * @glslFunction\n\
 *\n\
 * @param {vec4} position The position in eye coordinates to transform.\n\
 *\n\
 * @returns {vec4} The transformed position in window coordinates.\n\
 *\n\
 * @see czm_modelToWindowCoordinates\n\
 * @see czm_projection\n\
 * @see czm_viewportTransformation\n\
 * @see BillboardCollection\n\
 *\n\
 * @example\n\
 * vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
 */\n\
vec4 czm_eyeToWindowCoordinates(vec4 positionEC)\n\
{\n\
    vec4 q = czm_projection * positionEC;                        // clip coordinates\n\
    q.xyz /= q.w;                                                // normalized device coordinates\n\
    q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // window coordinates\n\
    return q;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/fog',[],function() {
    'use strict';
    return "/**\n\
 * Gets the color with fog at a distance from the camera.\n\
 * \n\
 * @name czm_fog\n\
 * @glslFunction\n\
 * \n\
 * @param {float} distanceToCamera The distance to the camera in meters.\n\
 * @param {vec3} color The original color.\n\
 * @param {vec3} fogColor The color of the fog.\n\
 *\n\
 * @returns {vec3} The color adjusted for fog at the distance from the camera.\n\
 */\n\
vec3 czm_fog(float distanceToCamera, vec3 color, vec3 fogColor)\n\
{\n\
    float scalar = distanceToCamera * czm_fogDensity;\n\
    float fog = 1.0 - exp(-(scalar * scalar));\n\
    \n\
    return mix(color, fogColor, fog);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/geodeticSurfaceNormal',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_geodeticSurfaceNormal\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} positionOnEllipsoid DOC_TBA\n\
 * @param {vec3} ellipsoidCenter DOC_TBA\n\
 * @param {vec3} oneOverEllipsoidRadiiSquared DOC_TBA\n\
 * \n\
 * @returns {vec3} DOC_TBA.\n\
 */\n\
vec3 czm_geodeticSurfaceNormal(vec3 positionOnEllipsoid, vec3 ellipsoidCenter, vec3 oneOverEllipsoidRadiiSquared)\n\
{\n\
    return normalize((positionOnEllipsoid - ellipsoidCenter) * oneOverEllipsoidRadiiSquared);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getDefaultMaterial',[],function() {
    'use strict';
    return "/**\n\
 * An czm_material with default values. Every material's czm_getMaterial\n\
 * should use this default material as a base for the material it returns.\n\
 * The default normal value is given by materialInput.normalEC.\n\
 *\n\
 * @name czm_getDefaultMaterial\n\
 * @glslFunction \n\
 *\n\
 * @param {czm_materialInput} input The input used to construct the default material.\n\
 * \n\
 * @returns {czm_material} The default material.\n\
 *\n\
 * @see czm_materialInput\n\
 * @see czm_material\n\
 * @see czm_getMaterial\n\
 */\n\
czm_material czm_getDefaultMaterial(czm_materialInput materialInput)\n\
{\n\
    czm_material material;\n\
    material.diffuse = vec3(0.0);\n\
    material.specular = 0.0;\n\
    material.shininess = 1.0;\n\
    material.normal = materialInput.normalEC;\n\
    material.emission = vec3(0.0);\n\
    material.alpha = 1.0;\n\
    return material;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getLambertDiffuse',[],function() {
    'use strict';
    return "/**\n\
 * Calculates the intensity of diffusely reflected light.\n\
 *\n\
 * @name czm_getLambertDiffuse\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates.\n\
 * @param {vec3} normalEC The surface normal in eye coordinates.\n\
 *\n\
 * @returns {float} The intensity of the diffuse reflection.\n\
 *\n\
 * @see czm_phong\n\
 *\n\
 * @example\n\
 * float diffuseIntensity = czm_getLambertDiffuse(lightDirectionEC, normalEC);\n\
 * float specularIntensity = czm_getSpecular(lightDirectionEC, toEyeEC, normalEC, 200);\n\
 * vec3 color = (diffuseColor * diffuseIntensity) + (specularColor * specularIntensity);\n\
 */\n\
float czm_getLambertDiffuse(vec3 lightDirectionEC, vec3 normalEC)\n\
{\n\
    return max(dot(lightDirectionEC, normalEC), 0.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getSpecular',[],function() {
    'use strict';
    return "/**\n\
 * Calculates the specular intensity of reflected light.\n\
 *\n\
 * @name czm_getSpecular\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates.\n\
 * @param {vec3} toEyeEC Unit vector pointing to the eye position in eye coordinates.\n\
 * @param {vec3} normalEC The surface normal in eye coordinates.\n\
 * @param {float} shininess The sharpness of the specular reflection.  Higher values create a smaller, more focused specular highlight.\n\
 *\n\
 * @returns {float} The intensity of the specular highlight.\n\
 *\n\
 * @see czm_phong\n\
 *\n\
 * @example\n\
 * float diffuseIntensity = czm_getLambertDiffuse(lightDirectionEC, normalEC);\n\
 * float specularIntensity = czm_getSpecular(lightDirectionEC, toEyeEC, normalEC, 200);\n\
 * vec3 color = (diffuseColor * diffuseIntensity) + (specularColor * specularIntensity);\n\
 */\n\
float czm_getSpecular(vec3 lightDirectionEC, vec3 toEyeEC, vec3 normalEC, float shininess)\n\
{\n\
    vec3 toReflectedLight = reflect(-lightDirectionEC, normalEC);\n\
    float specular = max(dot(toReflectedLight, toEyeEC), 0.0);\n\
\n\
    // pow has undefined behavior if both parameters <= 0.\n\
    // Prevent this by making sure shininess is at least czm_epsilon2.\n\
    return pow(specular, max(shininess, czm_epsilon2));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getWaterNoise',[],function() {
    'use strict';
    return "/**\n\
 * @private\n\
 */\n\
vec4 czm_getWaterNoise(sampler2D normalMap, vec2 uv, float time, float angleInRadians)\n\
{\n\
    float cosAngle = cos(angleInRadians);\n\
    float sinAngle = sin(angleInRadians);\n\
\n\
    // time dependent sampling directions\n\
    vec2 s0 = vec2(1.0/17.0, 0.0);\n\
    vec2 s1 = vec2(-1.0/29.0, 0.0);\n\
    vec2 s2 = vec2(1.0/101.0, 1.0/59.0);\n\
    vec2 s3 = vec2(-1.0/109.0, -1.0/57.0);\n\
\n\
    // rotate sampling direction by specified angle\n\
    s0 = vec2((cosAngle * s0.x) - (sinAngle * s0.y), (sinAngle * s0.x) + (cosAngle * s0.y));\n\
    s1 = vec2((cosAngle * s1.x) - (sinAngle * s1.y), (sinAngle * s1.x) + (cosAngle * s1.y));\n\
    s2 = vec2((cosAngle * s2.x) - (sinAngle * s2.y), (sinAngle * s2.x) + (cosAngle * s2.y));\n\
    s3 = vec2((cosAngle * s3.x) - (sinAngle * s3.y), (sinAngle * s3.x) + (cosAngle * s3.y));\n\
\n\
    vec2 uv0 = (uv/103.0) + (time * s0);\n\
    vec2 uv1 = uv/107.0 + (time * s1) + vec2(0.23);\n\
    vec2 uv2 = uv/vec2(897.0, 983.0) + (time * s2) + vec2(0.51);\n\
    vec2 uv3 = uv/vec2(991.0, 877.0) + (time * s3) + vec2(0.71);\n\
\n\
    uv0 = fract(uv0);\n\
    uv1 = fract(uv1);\n\
    uv2 = fract(uv2);\n\
    uv3 = fract(uv3);\n\
    vec4 noise = (texture2D(normalMap, uv0)) +\n\
                 (texture2D(normalMap, uv1)) +\n\
                 (texture2D(normalMap, uv2)) +\n\
                 (texture2D(normalMap, uv3));\n\
\n\
    // average and scale to between -1 and 1\n\
    return ((noise / 4.0) - 0.5) * 2.0;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/getWgs84EllipsoidEC',[],function() {
    'use strict';
    return "/**\n\
 * Returns the WGS84 ellipsoid, with its center at the origin of world coordinates, in eye coordinates.\n\
 *\n\
 * @name czm_getWgs84EllipsoidEC\n\
 * @glslFunction\n\
 *\n\
 * @returns {czm_ellipsoid} The WGS84 ellipsoid, with its center at the origin of world coordinates, in eye coordinates.\n\
 *\n\
 * @see Ellipsoid.WGS84\n\
 *\n\
 * @example\n\
 * czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC();\n\
 */\n\
czm_ellipsoid czm_getWgs84EllipsoidEC()\n\
{\n\
    vec3 radii = vec3(6378137.0, 6378137.0, 6356752.314245);\n\
    vec3 inverseRadii = vec3(1.0 / radii.x, 1.0 / radii.y, 1.0 / radii.z);\n\
    vec3 inverseRadiiSquared = inverseRadii * inverseRadii;\n\
    czm_ellipsoid temp = czm_ellipsoid(czm_view[3].xyz, radii, inverseRadii, inverseRadiiSquared);\n\
    return temp;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/hue',[],function() {
    'use strict';
    return "/**\n\
 * Adjusts the hue of a color.\n\
 * \n\
 * @name czm_hue\n\
 * @glslFunction\n\
 * \n\
 * @param {vec3} rgb The color.\n\
 * @param {float} adjustment The amount to adjust the hue of the color in radians.\n\
 *\n\
 * @returns {float} The color with the hue adjusted.\n\
 *\n\
 * @example\n\
 * vec3 adjustHue = czm_hue(color, czm_pi); // The same as czm_hue(color, -czm_pi)\n\
 */\n\
vec3 czm_hue(vec3 rgb, float adjustment)\n\
{\n\
    const mat3 toYIQ = mat3(0.299,     0.587,     0.114,\n\
                            0.595716, -0.274453, -0.321263,\n\
                            0.211456, -0.522591,  0.311135);\n\
    const mat3 toRGB = mat3(1.0,  0.9563,  0.6210,\n\
                            1.0, -0.2721, -0.6474,\n\
                            1.0, -1.107,   1.7046);\n\
    \n\
    vec3 yiq = toYIQ * rgb;\n\
    float hue = atan(yiq.z, yiq.y) + adjustment;\n\
    float chroma = sqrt(yiq.z * yiq.z + yiq.y * yiq.y);\n\
    \n\
    vec3 color = vec3(yiq.x, chroma * cos(hue), chroma * sin(hue));\n\
    return toRGB * color;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/isEmpty',[],function() {
    'use strict';
    return "/**\n\
 * Determines if a time interval is empty.\n\
 *\n\
 * @name czm_isEmpty\n\
 * @glslFunction \n\
 * \n\
 * @param {czm_raySegment} interval The interval to test.\n\
 * \n\
 * @returns {bool} <code>true</code> if the time interval is empty; otherwise, <code>false</code>.\n\
 *\n\
 * @example\n\
 * bool b0 = czm_isEmpty(czm_emptyRaySegment);      // true\n\
 * bool b1 = czm_isEmpty(czm_raySegment(0.0, 1.0)); // false\n\
 * bool b2 = czm_isEmpty(czm_raySegment(1.0, 1.0)); // false, contains 1.0.\n\
 */\n\
bool czm_isEmpty(czm_raySegment interval)\n\
{\n\
    return (interval.stop < 0.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/isFull',[],function() {
    'use strict';
    return "/**\n\
 * Determines if a time interval is empty.\n\
 *\n\
 * @name czm_isFull\n\
 * @glslFunction \n\
 * \n\
 * @param {czm_raySegment} interval The interval to test.\n\
 * \n\
 * @returns {bool} <code>true</code> if the time interval is empty; otherwise, <code>false</code>.\n\
 *\n\
 * @example\n\
 * bool b0 = czm_isEmpty(czm_emptyRaySegment);      // true\n\
 * bool b1 = czm_isEmpty(czm_raySegment(0.0, 1.0)); // false\n\
 * bool b2 = czm_isEmpty(czm_raySegment(1.0, 1.0)); // false, contains 1.0.\n\
 */\n\
bool czm_isFull(czm_raySegment interval)\n\
{\n\
    return (interval.start == 0.0 && interval.stop == czm_infinity);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/latitudeToWebMercatorFraction',[],function() {
    'use strict';
    return "/**\n\
 * Computes the fraction of a Web Wercator rectangle at which a given geodetic latitude is located.\n\
 *\n\
 * @name czm_latitudeToWebMercatorFraction\n\
 * @glslFunction\n\
 *\n\
 * @param {float} latitude The geodetic latitude, in radians.\n\
 * @param {float} southMercatorY The Web Mercator coordinate of the southern boundary of the rectangle.\n\
 * @param {float} oneOverMercatorHeight The total height of the rectangle in Web Mercator coordinates.\n\
 *\n\
 * @returns {float} The fraction of the rectangle at which the latitude occurs.  If the latitude is the southern\n\
 *          boundary of the rectangle, the return value will be zero.  If it is the northern boundary, the return\n\
 *          value will be 1.0.  Latitudes in between are mapped according to the Web Mercator projection.\n\
 */ \n\
float czm_latitudeToWebMercatorFraction(float latitude, float southMercatorY, float oneOverMercatorHeight)\n\
{\n\
    float sinLatitude = sin(latitude);\n\
    float mercatorY = 0.5 * log((1.0 + sinLatitude) / (1.0 - sinLatitude));\n\
    \n\
    return (mercatorY - southMercatorY) * oneOverMercatorHeight;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/luminance',[],function() {
    'use strict';
    return "/**\n\
 * Computes the luminance of a color. \n\
 *\n\
 * @name czm_luminance\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} rgb The color.\n\
 * \n\
 * @returns {float} The luminance.\n\
 *\n\
 * @example\n\
 * float light = czm_luminance(vec3(0.0)); // 0.0\n\
 * float dark = czm_luminance(vec3(1.0));  // ~1.0 \n\
 */\n\
float czm_luminance(vec3 rgb)\n\
{\n\
    // Algorithm from Chapter 10 of Graphics Shaders.\n\
    const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n\
    return dot(rgb, W);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/metersPerPixel',[],function() {
    'use strict';
    return "/**\n\
 * Computes the size of a pixel in meters at a distance from the eye.\n\
 \n\
 * @name czm_metersPerPixel\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} positionEC The position to get the meters per pixel in eye coordinates.\n\
 *\n\
 * @returns {float} The meters per pixel at positionEC.\n\
 */\n\
float czm_metersPerPixel(vec4 positionEC)\n\
{\n\
    float width = czm_viewport.z;\n\
    float height = czm_viewport.w;\n\
    float pixelWidth;\n\
    float pixelHeight;\n\
    \n\
    float top = czm_frustumPlanes.x;\n\
    float bottom = czm_frustumPlanes.y;\n\
    float left = czm_frustumPlanes.z;\n\
    float right = czm_frustumPlanes.w;\n\
    \n\
    if (czm_sceneMode == czm_sceneMode2D)\n\
    {\n\
        float frustumWidth = right - left;\n\
        float frustumHeight = top - bottom;\n\
        pixelWidth = frustumWidth / width;\n\
        pixelHeight = frustumHeight / height;\n\
    }\n\
    else\n\
    {\n\
        float distanceToPixel = -positionEC.z;\n\
        float inverseNear = 1.0 / czm_currentFrustum.x;\n\
        float tanTheta = top * inverseNear;\n\
        pixelHeight = 2.0 * distanceToPixel * tanTheta / height;\n\
        tanTheta = right * inverseNear;\n\
        pixelWidth = 2.0 * distanceToPixel * tanTheta / width;\n\
    }\n\
\n\
    return max(pixelWidth, pixelHeight);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/modelToWindowCoordinates',[],function() {
    'use strict';
    return "/**\n\
 * Transforms a position from model to window coordinates.  The transformation\n\
 * from model to clip coordinates is done using {@link czm_modelViewProjection}.\n\
 * The transform from normalized device coordinates to window coordinates is\n\
 * done using {@link czm_viewportTransformation}, which assumes a depth range\n\
 * of <code>near = 0</code> and <code>far = 1</code>.\n\
 * <br /><br />\n\
 * This transform is useful when there is a need to manipulate window coordinates\n\
 * in a vertex shader as done by {@link BillboardCollection}.\n\
 * <br /><br />\n\
 * This function should not be confused with {@link czm_viewportOrthographic},\n\
 * which is an orthographic projection matrix that transforms from window \n\
 * coordinates to clip coordinates.\n\
 *\n\
 * @name czm_modelToWindowCoordinates\n\
 * @glslFunction\n\
 *\n\
 * @param {vec4} position The position in model coordinates to transform.\n\
 *\n\
 * @returns {vec4} The transformed position in window coordinates.\n\
 *\n\
 * @see czm_eyeToWindowCoordinates\n\
 * @see czm_modelViewProjection\n\
 * @see czm_viewportTransformation\n\
 * @see czm_viewportOrthographic\n\
 * @see BillboardCollection\n\
 *\n\
 * @example\n\
 * vec4 positionWC = czm_modelToWindowCoordinates(positionMC);\n\
 */\n\
vec4 czm_modelToWindowCoordinates(vec4 position)\n\
{\n\
    vec4 q = czm_modelViewProjection * position;                // clip coordinates\n\
    q.xyz /= q.w;                                                // normalized device coordinates\n\
    q.xyz = (czm_viewportTransformation * vec4(q.xyz, 1.0)).xyz; // window coordinates\n\
    return q;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/multiplyWithColorBalance',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_multiplyWithColorBalance\n\
 * @glslFunction\n\
 */\n\
vec3 czm_multiplyWithColorBalance(vec3 left, vec3 right)\n\
{\n\
    // Algorithm from Chapter 10 of Graphics Shaders.\n\
    const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n\
    \n\
    vec3 target = left * right;\n\
    float leftLuminance = dot(left, W);\n\
    float rightLuminance = dot(right, W);\n\
    float targetLuminance = dot(target, W);\n\
    \n\
    return ((leftLuminance + rightLuminance) / (2.0 * targetLuminance)) * target;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/nearFarScalar',[],function() {
    'use strict';
    return "/**\n\
 * Computes a value that scales with distance.  The scaling is clamped at the near and\n\
 * far distances, and does not extrapolate.  This function works with the\n\
 * {@link NearFarScalar} JavaScript class.\n\
 *\n\
 * @name czm_nearFarScalar\n\
 * @glslFunction\n\
 *\n\
 * @param {vec4} nearFarScalar A vector with 4 components: Near distance (x), Near value (y), Far distance (z), Far value (w).\n\
 * @param {float} cameraDistSq The square of the current distance from the camera.\n\
 *\n\
 * @returns {float} The value at this distance.\n\
 */\n\
float czm_nearFarScalar(vec4 nearFarScalar, float cameraDistSq)\n\
{\n\
    float valueAtMin = nearFarScalar.y;\n\
    float valueAtMax = nearFarScalar.w;\n\
    float nearDistanceSq = nearFarScalar.x * nearFarScalar.x;\n\
    float farDistanceSq = nearFarScalar.z * nearFarScalar.z;\n\
\n\
    float t = (cameraDistSq - nearDistanceSq) / (farDistanceSq - nearDistanceSq);\n\
\n\
    t = pow(clamp(t, 0.0, 1.0), 0.2);\n\
\n\
    return mix(valueAtMin, valueAtMax, t);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/octDecode',[],function() {
    'use strict';
    return " /**\n\
  * Decodes a unit-length vector in 'oct' encoding to a normalized 3-component Cartesian vector.\n\
  * The 'oct' encoding is described in \"A Survey of Efficient Representations of Independent Unit Vectors\",\n\
  * Cigolle et al 2014: http://jcgt.org/published/0003/02/01/\n\
  *\n\
  * @name czm_octDecode\n\
  * @param {vec2} encoded The oct-encoded, unit-length vector\n\
  * @param {float} range The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.\n\
  * @returns {vec3} The decoded and normalized vector\n\
  */\n\
  vec3 czm_octDecode(vec2 encoded, float range)\n\
  {\n\
      if (encoded.x == 0.0 && encoded.y == 0.0) {\n\
          return vec3(0.0, 0.0, 0.0);\n\
      }\n\
\n\
     encoded = encoded / range * 2.0 - 1.0;\n\
     vec3 v = vec3(encoded.x, encoded.y, 1.0 - abs(encoded.x) - abs(encoded.y));\n\
     if (v.z < 0.0)\n\
     {\n\
         v.xy = (1.0 - abs(v.yx)) * czm_signNotZero(v.xy);\n\
     }\n\
\n\
     return normalize(v);\n\
  }\n\
\n\
/**\n\
 * Decodes a unit-length vector in 'oct' encoding to a normalized 3-component Cartesian vector.\n\
 * The 'oct' encoding is described in \"A Survey of Efficient Representations of Independent Unit Vectors\",\n\
 * Cigolle et al 2014: http://jcgt.org/published/0003/02/01/\n\
 * \n\
 * @name czm_octDecode\n\
 * @param {vec2} encoded The oct-encoded, unit-length vector\n\
 * @returns {vec3} The decoded and normalized vector\n\
 */\n\
 vec3 czm_octDecode(vec2 encoded)\n\
 {\n\
    return czm_octDecode(encoded, 255.0);\n\
 }\n\
\n\
 /**\n\
 * Decodes a unit-length vector in 'oct' encoding packed into a floating-point number to a normalized 3-component Cartesian vector.\n\
 * The 'oct' encoding is described in \"A Survey of Efficient Representations of Independent Unit Vectors\",\n\
 * Cigolle et al 2014: http://jcgt.org/published/0003/02/01/\n\
 * \n\
 * @name czm_octDecode\n\
 * @param {float} encoded The oct-encoded, unit-length vector\n\
 * @returns {vec3} The decoded and normalized vector\n\
 */\n\
 vec3 czm_octDecode(float encoded)\n\
 {\n\
    float temp = encoded / 256.0;\n\
    float x = floor(temp);\n\
    float y = (temp - x) * 256.0;\n\
    return czm_octDecode(vec2(x, y));\n\
 }\n\
 \n\
/**\n\
 * Decodes three unit-length vectors in 'oct' encoding packed into two floating-point numbers to normalized 3-component Cartesian vectors.\n\
 * The 'oct' encoding is described in \"A Survey of Efficient Representations of Independent Unit Vectors\",\n\
 * Cigolle et al 2014: http://jcgt.org/published/0003/02/01/\n\
 * \n\
 * @name czm_octDecode\n\
 * @param {vec2} encoded The packed oct-encoded, unit-length vectors.\n\
 * @param {vec3} vector1 One decoded and normalized vector.\n\
 * @param {vec3} vector2 One decoded and normalized vector.\n\
 * @param {vec3} vector3 One decoded and normalized vector.\n\
 */\n\
  void czm_octDecode(vec2 encoded, out vec3 vector1, out vec3 vector2, out vec3 vector3)\n\
 {\n\
    float temp = encoded.x / 65536.0;\n\
    float x = floor(temp);\n\
    float encodedFloat1 = (temp - x) * 65536.0;\n\
\n\
    temp = encoded.y / 65536.0;\n\
    float y = floor(temp);\n\
    float encodedFloat2 = (temp - y) * 65536.0;\n\
\n\
    vector1 = czm_octDecode(encodedFloat1);\n\
    vector2 = czm_octDecode(encodedFloat2);\n\
    vector3 = czm_octDecode(vec2(x, y));\n\
 }\n\
 \n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/packDepth',[],function() {
    'use strict';
    return "/**\n\
 * Packs a depth value into a vec3 that can be represented by unsigned bytes.\n\
 *\n\
 * @name czm_packDepth\n\
 * @glslFunction\n\
 *\n\
 * @param {float} depth The floating-point depth.\n\
 * @returns {vec3} The packed depth.\n\
 */\n\
vec4 czm_packDepth(float depth)\n\
{\n\
    // See Aras Pranckevičius' post Encoding Floats to RGBA\n\
    // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/\n\
    vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * depth;\n\
    enc = fract(enc);\n\
    enc -= enc.yzww * vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0);\n\
    return enc;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/phong',[],function() {
    'use strict';
    return "float czm_private_getLambertDiffuseOfMaterial(vec3 lightDirectionEC, czm_material material)\n\
{\n\
    return czm_getLambertDiffuse(lightDirectionEC, material.normal);\n\
}\n\
\n\
float czm_private_getSpecularOfMaterial(vec3 lightDirectionEC, vec3 toEyeEC, czm_material material)\n\
{\n\
    return czm_getSpecular(lightDirectionEC, toEyeEC, material.normal, material.shininess);\n\
}\n\
\n\
/**\n\
 * Computes a color using the Phong lighting model.\n\
 *\n\
 * @name czm_phong\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} toEye A normalized vector from the fragment to the eye in eye coordinates.\n\
 * @param {czm_material} material The fragment's material.\n\
 * \n\
 * @returns {vec4} The computed color.\n\
 * \n\
 * @example\n\
 * vec3 positionToEyeEC = // ...\n\
 * czm_material material = // ...\n\
 * gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
 *\n\
 * @see czm_getMaterial\n\
 */\n\
vec4 czm_phong(vec3 toEye, czm_material material)\n\
{\n\
    // Diffuse from directional light sources at eye (for top-down)\n\
    float diffuse = czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 0.0, 1.0), material);\n\
    if (czm_sceneMode == czm_sceneMode3D) {\n\
        // (and horizon views in 3D)\n\
        diffuse += czm_private_getLambertDiffuseOfMaterial(vec3(0.0, 1.0, 0.0), material);\n\
    }\n\
\n\
    // Specular from sun and pseudo-moon\n\
    float specular = czm_private_getSpecularOfMaterial(czm_sunDirectionEC, toEye, material) + czm_private_getSpecularOfMaterial(czm_moonDirectionEC, toEye, material);\n\
\n\
    // Temporary workaround for adding ambient.\n\
    vec3 materialDiffuse = material.diffuse * 0.5;\n\
    \n\
    vec3 ambient = materialDiffuse;\n\
    vec3 color = ambient + material.emission;\n\
    color += materialDiffuse * diffuse;\n\
    color += material.specular * specular;\n\
\n\
    return vec4(color, material.alpha);\n\
}\n\
\n\
vec4 czm_private_phong(vec3 toEye, czm_material material)\n\
{\n\
    float diffuse = czm_private_getLambertDiffuseOfMaterial(czm_sunDirectionEC, material);\n\
    float specular = czm_private_getSpecularOfMaterial(czm_sunDirectionEC, toEye, material);\n\
\n\
    vec3 ambient = vec3(0.0);\n\
    vec3 color = ambient + material.emission;\n\
    color += material.diffuse * diffuse;\n\
    color += material.specular * specular;\n\
\n\
    return vec4(color, material.alpha);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/pointAlongRay',[],function() {
    'use strict';
    return "/**\n\
 * Computes the point along a ray at the given time.  <code>time</code> can be positive, negative, or zero.\n\
 *\n\
 * @name czm_pointAlongRay\n\
 * @glslFunction\n\
 *\n\
 * @param {czm_ray} ray The ray to compute the point along.\n\
 * @param {float} time The time along the ray.\n\
 * \n\
 * @returns {vec3} The point along the ray at the given time.\n\
 * \n\
 * @example\n\
 * czm_ray ray = czm_ray(vec3(0.0), vec3(1.0, 0.0, 0.0)); // origin, direction\n\
 * vec3 v = czm_pointAlongRay(ray, 2.0); // (2.0, 0.0, 0.0)\n\
 */\n\
vec3 czm_pointAlongRay(czm_ray ray, float time)\n\
{\n\
    return ray.origin + (time * ray.direction);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/rayEllipsoidIntersectionInterval',[],function() {
    'use strict';
    return "/**\n\
 * DOC_TBA\n\
 *\n\
 * @name czm_rayEllipsoidIntersectionInterval\n\
 * @glslFunction\n\
 */\n\
czm_raySegment czm_rayEllipsoidIntersectionInterval(czm_ray ray, czm_ellipsoid ellipsoid)\n\
{\n\
   // ray and ellipsoid center in eye coordinates.  radii in model coordinates.\n\
    vec3 q = ellipsoid.inverseRadii * (czm_inverseModelView * vec4(ray.origin, 1.0)).xyz;\n\
    vec3 w = ellipsoid.inverseRadii * (czm_inverseModelView * vec4(ray.direction, 0.0)).xyz;\n\
   \n\
    q = q - ellipsoid.inverseRadii * (czm_inverseModelView * vec4(ellipsoid.center, 1.0)).xyz;\n\
    \n\
    float q2 = dot(q, q);\n\
    float qw = dot(q, w);\n\
    \n\
    if (q2 > 1.0) // Outside ellipsoid.\n\
    {\n\
        if (qw >= 0.0) // Looking outward or tangent (0 intersections).\n\
        {\n\
            return czm_emptyRaySegment;\n\
        }\n\
        else // qw < 0.0.\n\
        {\n\
            float qw2 = qw * qw;\n\
            float difference = q2 - 1.0; // Positively valued.\n\
            float w2 = dot(w, w);\n\
            float product = w2 * difference;\n\
            \n\
            if (qw2 < product) // Imaginary roots (0 intersections).\n\
            {\n\
                return czm_emptyRaySegment;     \n\
            }   \n\
            else if (qw2 > product) // Distinct roots (2 intersections).\n\
            {\n\
                float discriminant = qw * qw - product;\n\
                float temp = -qw + sqrt(discriminant); // Avoid cancellation.\n\
                float root0 = temp / w2;\n\
                float root1 = difference / temp;\n\
                if (root0 < root1)\n\
                {\n\
                    czm_raySegment i = czm_raySegment(root0, root1);\n\
                    return i;\n\
                }\n\
                else\n\
                {\n\
                    czm_raySegment i = czm_raySegment(root1, root0);\n\
                    return i;\n\
                }\n\
            }\n\
            else // qw2 == product.  Repeated roots (2 intersections).\n\
            {\n\
                float root = sqrt(difference / w2);\n\
                czm_raySegment i = czm_raySegment(root, root);\n\
                return i;\n\
            }\n\
        }\n\
    }\n\
    else if (q2 < 1.0) // Inside ellipsoid (2 intersections).\n\
    {\n\
        float difference = q2 - 1.0; // Negatively valued.\n\
        float w2 = dot(w, w);\n\
        float product = w2 * difference; // Negatively valued.\n\
        float discriminant = qw * qw - product;\n\
        float temp = -qw + sqrt(discriminant); // Positively valued.\n\
        czm_raySegment i = czm_raySegment(0.0, temp / w2);\n\
        return i;\n\
    }\n\
    else // q2 == 1.0. On ellipsoid.\n\
    {\n\
        if (qw < 0.0) // Looking inward.\n\
        {\n\
            float w2 = dot(w, w);\n\
            czm_raySegment i = czm_raySegment(0.0, -qw / w2);\n\
            return i;\n\
        }\n\
        else // qw >= 0.0.  Looking outward or tangent.\n\
        {\n\
            return czm_emptyRaySegment;\n\
        }\n\
    }\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/RGBToXYZ',[],function() {
    'use strict';
    return "/**\n\
 * Converts an RGB color to CIE Yxy.\n\
 * <p>The conversion is described in\n\
 * {@link http://content.gpwiki.org/index.php/D3DBook:High-Dynamic_Range_Rendering#Luminance_Transform|Luminance Transform}\n\
 * </p>\n\
 * \n\
 * @name czm_RGBToXYZ\n\
 * @glslFunction\n\
 * \n\
 * @param {vec3} rgb The color in RGB.\n\
 *\n\
 * @returns {vec3} The color in CIE Yxy.\n\
 *\n\
 * @example\n\
 * vec3 xyz = czm_RGBToXYZ(rgb);\n\
 * xyz.x = max(xyz.x - luminanceThreshold, 0.0);\n\
 * rgb = czm_XYZToRGB(xyz);\n\
 */\n\
vec3 czm_RGBToXYZ(vec3 rgb)\n\
{\n\
    const mat3 RGB2XYZ = mat3(0.4124, 0.2126, 0.0193,\n\
                              0.3576, 0.7152, 0.1192,\n\
                              0.1805, 0.0722, 0.9505);\n\
    vec3 xyz = RGB2XYZ * rgb;\n\
    vec3 Yxy;\n\
    Yxy.r = xyz.g;\n\
    float temp = dot(vec3(1.0), xyz);\n\
    Yxy.gb = xyz.rg / temp;\n\
    return Yxy;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/saturation',[],function() {
    'use strict';
    return "/**\n\
 * Adjusts the saturation of a color.\n\
 * \n\
 * @name czm_saturation\n\
 * @glslFunction\n\
 * \n\
 * @param {vec3} rgb The color.\n\
 * @param {float} adjustment The amount to adjust the saturation of the color.\n\
 *\n\
 * @returns {float} The color with the saturation adjusted.\n\
 *\n\
 * @example\n\
 * vec3 greyScale = czm_saturation(color, 0.0);\n\
 * vec3 doubleSaturation = czm_saturation(color, 2.0);\n\
 */\n\
vec3 czm_saturation(vec3 rgb, float adjustment)\n\
{\n\
    // Algorithm from Chapter 16 of OpenGL Shading Language\n\
    const vec3 W = vec3(0.2125, 0.7154, 0.0721);\n\
    vec3 intensity = vec3(dot(rgb, W));\n\
    return mix(intensity, rgb, adjustment);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/shadowDepthCompare',[],function() {
    'use strict';
    return "\n\
float czm_sampleShadowMap(samplerCube shadowMap, vec3 d)\n\
{\n\
    return czm_unpackDepth(textureCube(shadowMap, d));\n\
}\n\
\n\
float czm_sampleShadowMap(sampler2D shadowMap, vec2 uv)\n\
{\n\
#ifdef USE_SHADOW_DEPTH_TEXTURE\n\
    return texture2D(shadowMap, uv).r;\n\
#else\n\
    return czm_unpackDepth(texture2D(shadowMap, uv));\n\
#endif\n\
}\n\
\n\
float czm_shadowDepthCompare(samplerCube shadowMap, vec3 uv, float depth)\n\
{\n\
    return step(depth, czm_sampleShadowMap(shadowMap, uv));\n\
}\n\
\n\
float czm_shadowDepthCompare(sampler2D shadowMap, vec2 uv, float depth)\n\
{\n\
    return step(depth, czm_sampleShadowMap(shadowMap, uv));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/shadowVisibility',[],function() {
    'use strict';
    return "\n\
float czm_private_shadowVisibility(float visibility, float nDotL, float normalShadingSmooth, float darkness)\n\
{\n\
#ifdef USE_NORMAL_SHADING\n\
#ifdef USE_NORMAL_SHADING_SMOOTH\n\
    float strength = clamp(nDotL / normalShadingSmooth, 0.0, 1.0);\n\
#else\n\
    float strength = step(0.0, nDotL);\n\
#endif\n\
    visibility *= strength;\n\
#endif\n\
\n\
    visibility = max(visibility, darkness);\n\
    return visibility;\n\
}\n\
\n\
#ifdef USE_CUBE_MAP_SHADOW\n\
float czm_shadowVisibility(samplerCube shadowMap, czm_shadowParameters shadowParameters)\n\
{\n\
    float depthBias = shadowParameters.depthBias;\n\
    float depth = shadowParameters.depth;\n\
    float nDotL = shadowParameters.nDotL;\n\
    float normalShadingSmooth = shadowParameters.normalShadingSmooth;\n\
    float darkness = shadowParameters.darkness;\n\
    vec3 uvw = shadowParameters.texCoords;\n\
\n\
    depth -= depthBias;\n\
    float visibility = czm_shadowDepthCompare(shadowMap, uvw, depth);\n\
    return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);\n\
}\n\
#else\n\
float czm_shadowVisibility(sampler2D shadowMap, czm_shadowParameters shadowParameters)\n\
{\n\
    float depthBias = shadowParameters.depthBias;\n\
    float depth = shadowParameters.depth;\n\
    float nDotL = shadowParameters.nDotL;\n\
    float normalShadingSmooth = shadowParameters.normalShadingSmooth;\n\
    float darkness = shadowParameters.darkness;\n\
    vec2 uv = shadowParameters.texCoords;\n\
\n\
    depth -= depthBias;\n\
#ifdef USE_SOFT_SHADOWS\n\
    vec2 texelStepSize = shadowParameters.texelStepSize;\n\
    float radius = 1.0;\n\
    float dx0 = -texelStepSize.x * radius;\n\
    float dy0 = -texelStepSize.y * radius;\n\
    float dx1 = texelStepSize.x * radius;\n\
    float dy1 = texelStepSize.y * radius;\n\
    float visibility = (\n\
        czm_shadowDepthCompare(shadowMap, uv, depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy0), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy0), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy0), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, 0.0), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, 0.0), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(dx0, dy1), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(0.0, dy1), depth) +\n\
        czm_shadowDepthCompare(shadowMap, uv + vec2(dx1, dy1), depth)\n\
    ) * (1.0 / 9.0);\n\
#else\n\
    float visibility = czm_shadowDepthCompare(shadowMap, uv, depth);\n\
#endif\n\
\n\
    return czm_private_shadowVisibility(visibility, nDotL, normalShadingSmooth, darkness);\n\
}\n\
#endif\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/signNotZero',[],function() {
    'use strict';
    return "/**\n\
 * Returns 1.0 if the given value is positive or zero, and -1.0 if it is negative.  This is similar to the GLSL\n\
 * built-in function <code>sign</code> except that returns 1.0 instead of 0.0 when the input value is 0.0.\n\
 * \n\
 * @name czm_signNotZero\n\
 * @glslFunction\n\
 *\n\
 * @param {} value The value for which to determine the sign.\n\
 * @returns {} 1.0 if the value is positive or zero, -1.0 if the value is negative.\n\
 */\n\
float czm_signNotZero(float value)\n\
{\n\
    return value >= 0.0 ? 1.0 : -1.0;\n\
}\n\
\n\
vec2 czm_signNotZero(vec2 value)\n\
{\n\
    return vec2(czm_signNotZero(value.x), czm_signNotZero(value.y));\n\
}\n\
\n\
vec3 czm_signNotZero(vec3 value)\n\
{\n\
    return vec3(czm_signNotZero(value.x), czm_signNotZero(value.y), czm_signNotZero(value.z));\n\
}\n\
\n\
vec4 czm_signNotZero(vec4 value)\n\
{\n\
    return vec4(czm_signNotZero(value.x), czm_signNotZero(value.y), czm_signNotZero(value.z), czm_signNotZero(value.w));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/tangentToEyeSpaceMatrix',[],function() {
    'use strict';
    return "/**\n\
 * Creates a matrix that transforms vectors from tangent space to eye space.\n\
 *\n\
 * @name czm_tangentToEyeSpaceMatrix\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} normalEC The normal vector in eye coordinates.\n\
 * @param {vec3} tangentEC The tangent vector in eye coordinates.\n\
 * @param {vec3} bitangentEC The bitangent vector in eye coordinates.\n\
 *\n\
 * @returns {mat3} The matrix that transforms from tangent space to eye space.\n\
 *\n\
 * @example\n\
 * mat3 tangentToEye = czm_tangentToEyeSpaceMatrix(normalEC, tangentEC, bitangentEC);\n\
 * vec3 normal = tangentToEye * texture2D(normalMap, st).xyz;\n\
 */\n\
mat3 czm_tangentToEyeSpaceMatrix(vec3 normalEC, vec3 tangentEC, vec3 bitangentEC)\n\
{\n\
    vec3 normal = normalize(normalEC);\n\
    vec3 tangent = normalize(tangentEC);\n\
    vec3 bitangent = normalize(bitangentEC);\n\
    return mat3(tangent.x  , tangent.y  , tangent.z,\n\
                bitangent.x, bitangent.y, bitangent.z,\n\
                normal.x   , normal.y   , normal.z);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/translateRelativeToEye',[],function() {
    'use strict';
    return "/**\n\
 * Translates a position (or any <code>vec3</code>) that was encoded with {@link EncodedCartesian3},\n\
 * and then provided to the shader as separate <code>high</code> and <code>low</code> bits to\n\
 * be relative to the eye.  As shown in the example, the position can then be transformed in eye\n\
 * or clip coordinates using {@link czm_modelViewRelativeToEye} or {@link czm_modelViewProjectionRelativeToEye},\n\
 * respectively.\n\
 * <p>\n\
 * This technique, called GPU RTE, eliminates jittering artifacts when using large coordinates as\n\
 * described in {@link http://blogs.agi.com/insight3d/index.php/2008/09/03/precisions-precisions/|Precisions, Precisions}.\n\
 * </p>\n\
 *\n\
 * @name czm_translateRelativeToEye\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} high The position's high bits.\n\
 * @param {vec3} low The position's low bits.\n\
 * @returns {vec3} The position translated to be relative to the camera's position.\n\
 *\n\
 * @example\n\
 * attribute vec3 positionHigh;\n\
 * attribute vec3 positionLow;\n\
 * \n\
 * void main() \n\
 * {\n\
 *   vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\
 *   gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
 * }\n\
 *\n\
 * @see czm_modelViewRelativeToEye\n\
 * @see czm_modelViewProjectionRelativeToEye\n\
 * @see czm_computePosition\n\
 * @see EncodedCartesian3\n\
 */\n\
vec4 czm_translateRelativeToEye(vec3 high, vec3 low)\n\
{\n\
    vec3 highDifference = high - czm_encodedCameraPositionMCHigh;\n\
    vec3 lowDifference = low - czm_encodedCameraPositionMCLow;\n\
\n\
    return vec4(highDifference + lowDifference, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/translucentPhong',[],function() {
    'use strict';
    return "/**\n\
 * @private\n\
 */\n\
vec4 czm_translucentPhong(vec3 toEye, czm_material material)\n\
{\n\
    // Diffuse from directional light sources at eye (for top-down and horizon views)\n\
    float diffuse = czm_getLambertDiffuse(vec3(0.0, 0.0, 1.0), material.normal);\n\
    \n\
    if (czm_sceneMode == czm_sceneMode3D) {\n\
        // (and horizon views in 3D)\n\
        diffuse += czm_getLambertDiffuse(vec3(0.0, 1.0, 0.0), material.normal);\n\
    }\n\
    \n\
    diffuse = clamp(diffuse, 0.0, 1.0);\n\
\n\
    // Specular from sun and pseudo-moon\n\
    float specular = czm_getSpecular(czm_sunDirectionEC, toEye, material.normal, material.shininess);\n\
    specular += czm_getSpecular(czm_moonDirectionEC, toEye, material.normal, material.shininess);\n\
\n\
    // Temporary workaround for adding ambient.\n\
    vec3 materialDiffuse = material.diffuse * 0.5;\n\
\n\
    vec3 ambient = materialDiffuse;\n\
    vec3 color = ambient + material.emission;\n\
    color += materialDiffuse * diffuse;\n\
    color += material.specular * specular;\n\
\n\
    return vec4(color, material.alpha);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/transpose',[],function() {
    'use strict';
    return "/**\n\
 * Returns the transpose of the matrix.  The input <code>matrix</code> can be\n\
 * a <code>mat2</code>, <code>mat3</code>, or <code>mat4</code>.\n\
 *\n\
 * @name czm_transpose\n\
 * @glslFunction\n\
 *\n\
 * @param {} matrix The matrix to transpose.\n\
 *\n\
 * @returns {} The transposed matrix.\n\
 *\n\
 * @example\n\
 * // GLSL declarations\n\
 * mat2 czm_transpose(mat2 matrix);\n\
 * mat3 czm_transpose(mat3 matrix);\n\
 * mat4 czm_transpose(mat4 matrix);\n\
 *\n\
 * // Transpose a 3x3 rotation matrix to find its inverse.\n\
 * mat3 eastNorthUpToEye = czm_eastNorthUpToEyeCoordinates(\n\
 *     positionMC, normalEC);\n\
 * mat3 eyeToEastNorthUp = czm_transpose(eastNorthUpToEye);\n\
 */\n\
mat2 czm_transpose(mat2 matrix)\n\
{\n\
    return mat2(\n\
        matrix[0][0], matrix[1][0],\n\
        matrix[0][1], matrix[1][1]);\n\
}\n\
\n\
mat3 czm_transpose(mat3 matrix)\n\
{\n\
    return mat3(\n\
        matrix[0][0], matrix[1][0], matrix[2][0],\n\
        matrix[0][1], matrix[1][1], matrix[2][1],\n\
        matrix[0][2], matrix[1][2], matrix[2][2]);\n\
}\n\
\n\
mat4 czm_transpose(mat4 matrix)\n\
{\n\
    return mat4(\n\
        matrix[0][0], matrix[1][0], matrix[2][0], matrix[3][0],\n\
        matrix[0][1], matrix[1][1], matrix[2][1], matrix[3][1],\n\
        matrix[0][2], matrix[1][2], matrix[2][2], matrix[3][2],\n\
        matrix[0][3], matrix[1][3], matrix[2][3], matrix[3][3]);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/unpackDepth',[],function() {
    'use strict';
    return "/**\n\
 * Unpacks a vec3 depth depth value to a float.\n\
 *\n\
 * @name czm_unpackDepth\n\
 * @glslFunction\n\
 *\n\
 * @param {vec3} packedDepth The packed depth.\n\
 *\n\
 * @returns {float} The floating-point depth.\n\
 */\n\
 float czm_unpackDepth(vec4 packedDepth)\n\
 {\n\
    // See Aras Pranckevičius' post Encoding Floats to RGBA\n\
    // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/\n\
    return dot(packedDepth, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0));\n\
 }\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/windowToEyeCoordinates',[],function() {
    'use strict';
    return "/**\n\
 * Transforms a position from window to eye coordinates.\n\
 * The transform from window to normalized device coordinates is done using components\n\
 * of (@link czm_viewport} and {@link czm_viewportTransformation} instead of calculating\n\
 * the inverse of <code>czm_viewportTransformation</code>. The transformation from \n\
 * normalized device coordinates to clip coordinates is done using <code>positionWC.w</code>,\n\
 * which is expected to be the scalar used in the perspective divide. The transformation\n\
 * from clip to eye coordinates is done using {@link czm_inverseProjection}.\n\
 *\n\
 * @name czm_windowToEyeCoordinates\n\
 * @glslFunction\n\
 *\n\
 * @param {vec4} fragmentCoordinate The position in window coordinates to transform.\n\
 *\n\
 * @returns {vec4} The transformed position in eye coordinates.\n\
 *\n\
 * @see czm_modelToWindowCoordinates\n\
 * @see czm_eyeToWindowCoordinates\n\
 * @see czm_inverseProjection\n\
 * @see czm_viewport\n\
 * @see czm_viewportTransformation\n\
 *\n\
 * @example\n\
 * vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord);\n\
 */\n\
vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)\n\
{\n\
    float x = 2.0 * (fragmentCoordinate.x - czm_viewport.x) / czm_viewport.z - 1.0;\n\
    float y = 2.0 * (fragmentCoordinate.y - czm_viewport.y) / czm_viewport.w - 1.0;\n\
    float z = (fragmentCoordinate.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2];\n\
    vec4 q = vec4(x, y, z, 1.0);\n\
    q /= fragmentCoordinate.w;\n\
    q = czm_inverseProjection * q;\n\
    return q;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/Functions/XYZToRGB',[],function() {
    'use strict';
    return "/**\n\
 * Converts a CIE Yxy color to RGB.\n\
 * <p>The conversion is described in\n\
 * {@link http://content.gpwiki.org/index.php/D3DBook:High-Dynamic_Range_Rendering#Luminance_Transform|Luminance Transform}\n\
 * </p>\n\
 * \n\
 * @name czm_XYZToRGB\n\
 * @glslFunction\n\
 * \n\
 * @param {vec3} Yxy The color in CIE Yxy.\n\
 *\n\
 * @returns {vec3} The color in RGB.\n\
 *\n\
 * @example\n\
 * vec3 xyz = czm_RGBToXYZ(rgb);\n\
 * xyz.x = max(xyz.x - luminanceThreshold, 0.0);\n\
 * rgb = czm_XYZToRGB(xyz);\n\
 */\n\
vec3 czm_XYZToRGB(vec3 Yxy)\n\
{\n\
    const mat3 XYZ2RGB = mat3( 3.2405, -0.9693,  0.0556,\n\
                              -1.5371,  1.8760, -0.2040,\n\
                              -0.4985,  0.0416,  1.0572);\n\
    vec3 xyz;\n\
    xyz.r = Yxy.r * Yxy.g / Yxy.b;\n\
    xyz.g = Yxy.r;\n\
    xyz.b = Yxy.r * (1.0 - Yxy.g - Yxy.b) / Yxy.b;\n\
    \n\
    return XYZ2RGB * xyz;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Builtin/CzmBuiltins',[
        './Constants/degreesPerRadian',
        './Constants/depthRange',
        './Constants/epsilon1',
        './Constants/epsilon2',
        './Constants/epsilon3',
        './Constants/epsilon4',
        './Constants/epsilon5',
        './Constants/epsilon6',
        './Constants/epsilon7',
        './Constants/infinity',
        './Constants/oneOverPi',
        './Constants/oneOverTwoPi',
        './Constants/passCompute',
        './Constants/passEnvironment',
        './Constants/passGlobe',
        './Constants/passGround',
        './Constants/passOpaque',
        './Constants/passOverlay',
        './Constants/passTranslucent',
        './Constants/pi',
        './Constants/piOverFour',
        './Constants/piOverSix',
        './Constants/piOverThree',
        './Constants/piOverTwo',
        './Constants/radiansPerDegree',
        './Constants/sceneMode2D',
        './Constants/sceneMode3D',
        './Constants/sceneModeColumbusView',
        './Constants/sceneModeMorphing',
        './Constants/solarRadius',
        './Constants/threePiOver2',
        './Constants/twoPi',
        './Constants/webMercatorMaxLatitude',
        './Structs/depthRangeStruct',
        './Structs/ellipsoid',
        './Structs/material',
        './Structs/materialInput',
        './Structs/ray',
        './Structs/raySegment',
        './Structs/shadowParameters',
        './Functions/alphaWeight',
        './Functions/antialias',
        './Functions/cascadeColor',
        './Functions/cascadeDistance',
        './Functions/cascadeMatrix',
        './Functions/cascadeWeights',
        './Functions/columbusViewMorph',
        './Functions/computePosition',
        './Functions/cosineAndSine',
        './Functions/decompressTextureCoordinates',
        './Functions/eastNorthUpToEyeCoordinates',
        './Functions/ellipsoidContainsPoint',
        './Functions/ellipsoidNew',
        './Functions/ellipsoidWgs84TextureCoordinates',
        './Functions/equalsEpsilon',
        './Functions/eyeOffset',
        './Functions/eyeToWindowCoordinates',
        './Functions/fog',
        './Functions/geodeticSurfaceNormal',
        './Functions/getDefaultMaterial',
        './Functions/getLambertDiffuse',
        './Functions/getSpecular',
        './Functions/getWaterNoise',
        './Functions/getWgs84EllipsoidEC',
        './Functions/hue',
        './Functions/isEmpty',
        './Functions/isFull',
        './Functions/latitudeToWebMercatorFraction',
        './Functions/luminance',
        './Functions/metersPerPixel',
        './Functions/modelToWindowCoordinates',
        './Functions/multiplyWithColorBalance',
        './Functions/nearFarScalar',
        './Functions/octDecode',
        './Functions/packDepth',
        './Functions/phong',
        './Functions/pointAlongRay',
        './Functions/rayEllipsoidIntersectionInterval',
        './Functions/RGBToXYZ',
        './Functions/saturation',
        './Functions/shadowDepthCompare',
        './Functions/shadowVisibility',
        './Functions/signNotZero',
        './Functions/tangentToEyeSpaceMatrix',
        './Functions/translateRelativeToEye',
        './Functions/translucentPhong',
        './Functions/transpose',
        './Functions/unpackDepth',
        './Functions/windowToEyeCoordinates',
        './Functions/XYZToRGB'
    ], function(
        czm_degreesPerRadian,
        czm_depthRange,
        czm_epsilon1,
        czm_epsilon2,
        czm_epsilon3,
        czm_epsilon4,
        czm_epsilon5,
        czm_epsilon6,
        czm_epsilon7,
        czm_infinity,
        czm_oneOverPi,
        czm_oneOverTwoPi,
        czm_passCompute,
        czm_passEnvironment,
        czm_passGlobe,
        czm_passGround,
        czm_passOpaque,
        czm_passOverlay,
        czm_passTranslucent,
        czm_pi,
        czm_piOverFour,
        czm_piOverSix,
        czm_piOverThree,
        czm_piOverTwo,
        czm_radiansPerDegree,
        czm_sceneMode2D,
        czm_sceneMode3D,
        czm_sceneModeColumbusView,
        czm_sceneModeMorphing,
        czm_solarRadius,
        czm_threePiOver2,
        czm_twoPi,
        czm_webMercatorMaxLatitude,
        czm_depthRangeStruct,
        czm_ellipsoid,
        czm_material,
        czm_materialInput,
        czm_ray,
        czm_raySegment,
        czm_shadowParameters,
        czm_alphaWeight,
        czm_antialias,
        czm_cascadeColor,
        czm_cascadeDistance,
        czm_cascadeMatrix,
        czm_cascadeWeights,
        czm_columbusViewMorph,
        czm_computePosition,
        czm_cosineAndSine,
        czm_decompressTextureCoordinates,
        czm_eastNorthUpToEyeCoordinates,
        czm_ellipsoidContainsPoint,
        czm_ellipsoidNew,
        czm_ellipsoidWgs84TextureCoordinates,
        czm_equalsEpsilon,
        czm_eyeOffset,
        czm_eyeToWindowCoordinates,
        czm_fog,
        czm_geodeticSurfaceNormal,
        czm_getDefaultMaterial,
        czm_getLambertDiffuse,
        czm_getSpecular,
        czm_getWaterNoise,
        czm_getWgs84EllipsoidEC,
        czm_hue,
        czm_isEmpty,
        czm_isFull,
        czm_latitudeToWebMercatorFraction,
        czm_luminance,
        czm_metersPerPixel,
        czm_modelToWindowCoordinates,
        czm_multiplyWithColorBalance,
        czm_nearFarScalar,
        czm_octDecode,
        czm_packDepth,
        czm_phong,
        czm_pointAlongRay,
        czm_rayEllipsoidIntersectionInterval,
        czm_RGBToXYZ,
        czm_saturation,
        czm_shadowDepthCompare,
        czm_shadowVisibility,
        czm_signNotZero,
        czm_tangentToEyeSpaceMatrix,
        czm_translateRelativeToEye,
        czm_translucentPhong,
        czm_transpose,
        czm_unpackDepth,
        czm_windowToEyeCoordinates,
        czm_XYZToRGB) {
                           'use strict';
                           return {
        czm_degreesPerRadian : czm_degreesPerRadian,
        czm_depthRange : czm_depthRange,
        czm_epsilon1 : czm_epsilon1,
        czm_epsilon2 : czm_epsilon2,
        czm_epsilon3 : czm_epsilon3,
        czm_epsilon4 : czm_epsilon4,
        czm_epsilon5 : czm_epsilon5,
        czm_epsilon6 : czm_epsilon6,
        czm_epsilon7 : czm_epsilon7,
        czm_infinity : czm_infinity,
        czm_oneOverPi : czm_oneOverPi,
        czm_oneOverTwoPi : czm_oneOverTwoPi,
        czm_passCompute : czm_passCompute,
        czm_passEnvironment : czm_passEnvironment,
        czm_passGlobe : czm_passGlobe,
        czm_passGround : czm_passGround,
        czm_passOpaque : czm_passOpaque,
        czm_passOverlay : czm_passOverlay,
        czm_passTranslucent : czm_passTranslucent,
        czm_pi : czm_pi,
        czm_piOverFour : czm_piOverFour,
        czm_piOverSix : czm_piOverSix,
        czm_piOverThree : czm_piOverThree,
        czm_piOverTwo : czm_piOverTwo,
        czm_radiansPerDegree : czm_radiansPerDegree,
        czm_sceneMode2D : czm_sceneMode2D,
        czm_sceneMode3D : czm_sceneMode3D,
        czm_sceneModeColumbusView : czm_sceneModeColumbusView,
        czm_sceneModeMorphing : czm_sceneModeMorphing,
        czm_solarRadius : czm_solarRadius,
        czm_threePiOver2 : czm_threePiOver2,
        czm_twoPi : czm_twoPi,
        czm_webMercatorMaxLatitude : czm_webMercatorMaxLatitude,
        czm_depthRangeStruct : czm_depthRangeStruct,
        czm_ellipsoid : czm_ellipsoid,
        czm_material : czm_material,
        czm_materialInput : czm_materialInput,
        czm_ray : czm_ray,
        czm_raySegment : czm_raySegment,
        czm_shadowParameters : czm_shadowParameters,
        czm_alphaWeight : czm_alphaWeight,
        czm_antialias : czm_antialias,
        czm_cascadeColor : czm_cascadeColor,
        czm_cascadeDistance : czm_cascadeDistance,
        czm_cascadeMatrix : czm_cascadeMatrix,
        czm_cascadeWeights : czm_cascadeWeights,
        czm_columbusViewMorph : czm_columbusViewMorph,
        czm_computePosition : czm_computePosition,
        czm_cosineAndSine : czm_cosineAndSine,
        czm_decompressTextureCoordinates : czm_decompressTextureCoordinates,
        czm_eastNorthUpToEyeCoordinates : czm_eastNorthUpToEyeCoordinates,
        czm_ellipsoidContainsPoint : czm_ellipsoidContainsPoint,
        czm_ellipsoidNew : czm_ellipsoidNew,
        czm_ellipsoidWgs84TextureCoordinates : czm_ellipsoidWgs84TextureCoordinates,
        czm_equalsEpsilon : czm_equalsEpsilon,
        czm_eyeOffset : czm_eyeOffset,
        czm_eyeToWindowCoordinates : czm_eyeToWindowCoordinates,
        czm_fog : czm_fog,
        czm_geodeticSurfaceNormal : czm_geodeticSurfaceNormal,
        czm_getDefaultMaterial : czm_getDefaultMaterial,
        czm_getLambertDiffuse : czm_getLambertDiffuse,
        czm_getSpecular : czm_getSpecular,
        czm_getWaterNoise : czm_getWaterNoise,
        czm_getWgs84EllipsoidEC : czm_getWgs84EllipsoidEC,
        czm_hue : czm_hue,
        czm_isEmpty : czm_isEmpty,
        czm_isFull : czm_isFull,
        czm_latitudeToWebMercatorFraction : czm_latitudeToWebMercatorFraction,
        czm_luminance : czm_luminance,
        czm_metersPerPixel : czm_metersPerPixel,
        czm_modelToWindowCoordinates : czm_modelToWindowCoordinates,
        czm_multiplyWithColorBalance : czm_multiplyWithColorBalance,
        czm_nearFarScalar : czm_nearFarScalar,
        czm_octDecode : czm_octDecode,
        czm_packDepth : czm_packDepth,
        czm_phong : czm_phong,
        czm_pointAlongRay : czm_pointAlongRay,
        czm_rayEllipsoidIntersectionInterval : czm_rayEllipsoidIntersectionInterval,
        czm_RGBToXYZ : czm_RGBToXYZ,
        czm_saturation : czm_saturation,
        czm_shadowDepthCompare : czm_shadowDepthCompare,
        czm_shadowVisibility : czm_shadowVisibility,
        czm_signNotZero : czm_signNotZero,
        czm_tangentToEyeSpaceMatrix : czm_tangentToEyeSpaceMatrix,
        czm_translateRelativeToEye : czm_translateRelativeToEye,
        czm_translucentPhong : czm_translucentPhong,
        czm_transpose : czm_transpose,
        czm_unpackDepth : czm_unpackDepth,
        czm_windowToEyeCoordinates : czm_windowToEyeCoordinates,
        czm_XYZToRGB : czm_XYZToRGB};
});
/*global define*/
define('Renderer/ShaderSource',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Shaders/Builtin/CzmBuiltins',
        './AutomaticUniforms'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        CzmBuiltins,
        AutomaticUniforms) {
    'use strict';

    function removeComments(source) {
        // remove inline comments
        source = source.replace(/\/\/.*/g, '');
        // remove multiline comment block
        return source.replace(/\/\*\*[\s\S]*?\*\//gm, function(match) {
            // preserve the number of lines in the comment block so the line numbers will be correct when debugging shaders
            var numberOfLines = match.match(/\n/gm).length;
            var replacement = '';
            for (var lineNumber = 0; lineNumber < numberOfLines; ++lineNumber) {
                replacement += '\n';
            }
            return replacement;
        });
    }

    function getDependencyNode(name, glslSource, nodes) {
        var dependencyNode;

        // check if already loaded
        for (var i = 0; i < nodes.length; ++i) {
            if (nodes[i].name === name) {
                dependencyNode = nodes[i];
            }
        }

        if (!defined(dependencyNode)) {
            // strip doc comments so we don't accidentally try to determine a dependency for something found
            // in a comment
            glslSource = removeComments(glslSource);

            // create new node
            dependencyNode = {
                name : name,
                glslSource : glslSource,
                dependsOn : [],
                requiredBy : [],
                evaluated : false
            };
            nodes.push(dependencyNode);
        }

        return dependencyNode;
    }

    function generateDependencies(currentNode, dependencyNodes) {
        if (currentNode.evaluated) {
            return;
        }

        currentNode.evaluated = true;

        // identify all dependencies that are referenced from this glsl source code
        var czmMatches = currentNode.glslSource.match(/\bczm_[a-zA-Z0-9_]*/g);
        if (defined(czmMatches) && czmMatches !== null) {
            // remove duplicates
            czmMatches = czmMatches.filter(function(elem, pos) {
                return czmMatches.indexOf(elem) === pos;
            });

            czmMatches.forEach(function(element) {
                if (element !== currentNode.name && ShaderSource._czmBuiltinsAndUniforms.hasOwnProperty(element)) {
                    var referencedNode = getDependencyNode(element, ShaderSource._czmBuiltinsAndUniforms[element], dependencyNodes);
                    currentNode.dependsOn.push(referencedNode);
                    referencedNode.requiredBy.push(currentNode);

                    // recursive call to find any dependencies of the new node
                    generateDependencies(referencedNode, dependencyNodes);
                }
            });
        }
    }

    function sortDependencies(dependencyNodes) {
        var nodesWithoutIncomingEdges = [];
        var allNodes = [];

        while (dependencyNodes.length > 0) {
            var node = dependencyNodes.pop();
            allNodes.push(node);

            if (node.requiredBy.length === 0) {
                nodesWithoutIncomingEdges.push(node);
            }
        }

        while (nodesWithoutIncomingEdges.length > 0) {
            var currentNode = nodesWithoutIncomingEdges.shift();

            dependencyNodes.push(currentNode);

            for (var i = 0; i < currentNode.dependsOn.length; ++i) {
                // remove the edge from the graph
                var referencedNode = currentNode.dependsOn[i];
                var index = referencedNode.requiredBy.indexOf(currentNode);
                referencedNode.requiredBy.splice(index, 1);

                // if referenced node has no more incoming edges, add to list
                if (referencedNode.requiredBy.length === 0) {
                    nodesWithoutIncomingEdges.push(referencedNode);
                }
            }
        }

        // if there are any nodes left with incoming edges, then there was a circular dependency somewhere in the graph
        var badNodes = [];
        for (var j = 0; j < allNodes.length; ++j) {
            if (allNodes[j].requiredBy.length !== 0) {
                badNodes.push(allNodes[j]);
            }
        }

                if (badNodes.length !== 0) {
            var message = 'A circular dependency was found in the following built-in functions/structs/constants: \n';
            for (j = 0; j < badNodes.length; ++j) {
                message = message + badNodes[j].name + '\n';
            }
            throw new DeveloperError(message);
        }
            }

    function getBuiltinsAndAutomaticUniforms(shaderSource) {
        // generate a dependency graph for builtin functions
        var dependencyNodes = [];
        var root = getDependencyNode('main', shaderSource, dependencyNodes);
        generateDependencies(root, dependencyNodes);
        sortDependencies(dependencyNodes);

        // Concatenate the source code for the function dependencies.
        // Iterate in reverse so that dependent items are declared before they are used.
        var builtinsSource = '';
        for (var i = dependencyNodes.length - 1; i >= 0; --i) {
            builtinsSource = builtinsSource + dependencyNodes[i].glslSource + '\n';
        }

        return builtinsSource.replace(root.glslSource, '');
    }

    function combineShader(shaderSource, isFragmentShader) {
        var i;
        var length;

        // Combine shader sources, generally for pseudo-polymorphism, e.g., czm_getMaterial.
        var combinedSources = '';
        var sources = shaderSource.sources;
        if (defined(sources)) {
            for (i = 0, length = sources.length; i < length; ++i) {
                // #line needs to be on its own line.
                combinedSources += '\n#line 0\n' + sources[i];
            }
        }

        combinedSources = removeComments(combinedSources);

        // Extract existing shader version from sources
        var version;
        combinedSources = combinedSources.replace(/#version\s+(.*?)\n/gm, function(match, group1) {
                        if (defined(version) && version !== group1) {
                throw new DeveloperError('inconsistent versions found: ' + version + ' and ' + group1);
            }
            
            // Extract #version to put at the top
            version = group1;

            // Replace original #version directive with a new line so the line numbers
            // are not off by one.  There can be only one #version directive
            // and it must appear at the top of the source, only preceded by
            // whitespace and comments.
            return '\n';
        });

        // Remove precision qualifier
        combinedSources = combinedSources.replace(/precision\s(lowp|mediump|highp)\s(float|int);/, '');

        // Replace main() for picked if desired.
        var pickColorQualifier = shaderSource.pickColorQualifier;
        if (defined(pickColorQualifier)) {
            combinedSources = ShaderSource.createPickFragmentShaderSource(combinedSources, pickColorQualifier);
        }

        // combine into single string
        var result = '';

        // #version must be first
        // defaults to #version 100 if not specified
        if (defined(version)) {
            result = '#version ' + version;
        }

        if (isFragmentShader) {
            result += '\
#ifdef GL_FRAGMENT_PRECISION_HIGH\n\
    precision highp float;\n\
#else\n\
    precision mediump float;\n\
#endif\n\n';
        }

        // Prepend #defines for uber-shaders
        var defines = shaderSource.defines;
        if (defined(defines)) {
            for (i = 0, length = defines.length; i < length; ++i) {
                var define = defines[i];
                if (define.length !== 0) {
                    result += '#define ' + define + '\n';
                }
            }
        }

        // append built-ins
        if (shaderSource.includeBuiltIns) {
            result += getBuiltinsAndAutomaticUniforms(combinedSources);
        }

        // reset line number
        result += '\n#line 0\n';

        // append actual source
        result += combinedSources;

        return result;
    }

    /**
     * An object containing various inputs that will be combined to form a final GLSL shader string.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String[]} [options.sources] An array of strings to combine containing GLSL code for the shader.
     * @param {String[]} [options.defines] An array of strings containing GLSL identifiers to <code>#define</code>.
     * @param {String} [options.pickColorQualifier] The GLSL qualifier, <code>uniform</code> or <code>varying</code>, for the input <code>czm_pickColor</code>.  When defined, a pick fragment shader is generated.
     * @param {Boolean} [options.includeBuiltIns=true] If true, referenced built-in functions will be included with the combined shader.  Set to false if this shader will become a source in another shader, to avoid duplicating functions.
     *
     * @exception {DeveloperError} options.pickColorQualifier must be 'uniform' or 'varying'.
     *
     * @example
     * // 1. Prepend #defines to a shader
     * var source = new Cesium.ShaderSource({
     *   defines : ['WHITE'],
     *   sources : ['void main() { \n#ifdef WHITE\n gl_FragColor = vec4(1.0); \n#else\n gl_FragColor = vec4(0.0); \n#endif\n }']
     * });
     *
     * // 2. Modify a fragment shader for picking
     * var source = new Cesium.ShaderSource({
     *   sources : ['void main() { gl_FragColor = vec4(1.0); }'],
     *   pickColorQualifier : 'uniform'
     * });
     *
     * @private
     */
    function ShaderSource(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var pickColorQualifier = options.pickColorQualifier;

                if (defined(pickColorQualifier) && pickColorQualifier !== 'uniform' && pickColorQualifier !== 'varying') {
            throw new DeveloperError('options.pickColorQualifier must be \'uniform\' or \'varying\'.');
        }
        
        this.defines = defined(options.defines) ? options.defines.slice(0) : [];
        this.sources = defined(options.sources) ? options.sources.slice(0) : [];
        this.pickColorQualifier = pickColorQualifier;
        this.includeBuiltIns = defaultValue(options.includeBuiltIns, true);
    }
    
    ShaderSource.prototype.clone = function() {
        return new ShaderSource({
            sources : this.sources,
            defines : this.defines,
            pickColorQuantifier : this.pickColorQualifier,
            includeBuiltIns : this.includeBuiltIns
        });
    };

    ShaderSource.replaceMain = function(source, renamedMain) {
        renamedMain = 'void ' + renamedMain + '()';
        return source.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, renamedMain);
    };

    /**
     * Create a single string containing the full, combined vertex shader with all dependencies and defines.
     *
     * @returns {String} The combined shader string.
     */
    ShaderSource.prototype.createCombinedVertexShader = function() {
        return combineShader(this, false);
    };

    /**
     * Create a single string containing the full, combined fragment shader with all dependencies and defines.
     *
     * @returns {String} The combined shader string.
     */
    ShaderSource.prototype.createCombinedFragmentShader = function() {
        return combineShader(this, true);
    };

    /**
     * For ShaderProgram testing
     * @private
     */
    ShaderSource._czmBuiltinsAndUniforms = {};

    // combine automatic uniforms and Cesium built-ins
    for ( var builtinName in CzmBuiltins) {
        if (CzmBuiltins.hasOwnProperty(builtinName)) {
            ShaderSource._czmBuiltinsAndUniforms[builtinName] = CzmBuiltins[builtinName];
        }
    }
    for ( var uniformName in AutomaticUniforms) {
        if (AutomaticUniforms.hasOwnProperty(uniformName)) {
            var uniform = AutomaticUniforms[uniformName];
            if (typeof uniform.getDeclaration === 'function') {
                ShaderSource._czmBuiltinsAndUniforms[uniformName] = uniform.getDeclaration(uniformName);
            }
        }
    }

    ShaderSource.createPickVertexShaderSource = function(vertexShaderSource) {
        var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_old_main');
        var pickMain = 'attribute vec4 pickColor; \n' +
            'varying vec4 czm_pickColor; \n' +
            'void main() \n' +
            '{ \n' +
            '    czm_old_main(); \n' +
            '    czm_pickColor = pickColor; \n' +
            '}';

        return renamedVS + '\n' + pickMain;
    };

    ShaderSource.createPickFragmentShaderSource = function(fragmentShaderSource, pickColorQualifier) {
        var renamedFS = ShaderSource.replaceMain(fragmentShaderSource, 'czm_old_main');
        var pickMain = pickColorQualifier + ' vec4 czm_pickColor; \n' +
            'void main() \n' +
            '{ \n' +
            '    czm_old_main(); \n' +
            '    if (gl_FragColor.a == 0.0) { \n' +
            '       discard; \n' +
            '    } \n' +
            '    gl_FragColor = czm_pickColor; \n' +
            '}';

        return renamedFS + '\n' + pickMain;
    };

    ShaderSource.findVarying = function(shaderSource, names) {
        var sources = shaderSource.sources;

        var namesLength = names.length;
        for (var i = 0; i < namesLength; ++i) {
            var name = names[i];

            var sourcesLength = sources.length;
            for (var j = 0; j < sourcesLength; ++j) {
                if (sources[j].indexOf(name) !== -1) {
                    return name;
                }
            }
        }

        return undefined;
    };

    var normalVaryingNames = ['v_normalEC', 'v_normal'];

    ShaderSource.findNormalVarying = function(shaderSource) {
        return ShaderSource.findVarying(shaderSource, normalVaryingNames);
    };

    var positionVaryingNames = ['v_positionEC'];

    ShaderSource.findPositionVarying = function(shaderSource) {
        return ShaderSource.findVarying(shaderSource, positionVaryingNames);
    };

    return ShaderSource;
});

/*global define*/
define('Renderer/Buffer',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/IndexDatatype',
        '../Core/WebGLConstants',
        './BufferUsage'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        IndexDatatype,
        WebGLConstants,
        BufferUsage) {
    'use strict';

    /**
     * @private
     */
    function Buffer(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }

        if (!defined(options.typedArray) && !defined(options.sizeInBytes)) {
            throw new DeveloperError('Either options.sizeInBytes or options.typedArray is required.');
        }

        if (defined(options.typedArray) && defined(options.sizeInBytes)) {
            throw new DeveloperError('Cannot pass in both options.sizeInBytes and options.typedArray.');
        }

        if (defined(options.typedArray) && !(typeof options.typedArray === 'object' && typeof options.typedArray.byteLength === 'number')) {
            throw new DeveloperError('options.typedArray must be a typed array');
        }

        if (!BufferUsage.validate(options.usage)) {
            throw new DeveloperError('usage is invalid.');
        }
        
        var gl = options.context._gl;
        var bufferTarget = options.bufferTarget;
        var typedArray = options.typedArray;
        var sizeInBytes = options.sizeInBytes;
        var usage = options.usage;
        var hasArray = defined(typedArray);

        if (hasArray) {
            sizeInBytes = typedArray.byteLength;
        }

                if (sizeInBytes <= 0) {
            throw new DeveloperError('Buffer size must be greater than zero.');
        }
        
        var buffer = gl.createBuffer();
        gl.bindBuffer(bufferTarget, buffer);
        gl.bufferData(bufferTarget, hasArray ? typedArray : sizeInBytes, usage);
        gl.bindBuffer(bufferTarget, null);

        this._gl = gl;
        this._bufferTarget = bufferTarget;
        this._sizeInBytes = sizeInBytes;
        this._usage = usage;
        this._buffer = buffer;
        this.vertexArrayDestroyable = true;
    }

    /**
     * Creates a vertex buffer, which contains untyped vertex data in GPU-controlled memory.
     * <br /><br />
     * A vertex array defines the actual makeup of a vertex, e.g., positions, normals, texture coordinates,
     * etc., by interpreting the raw data in one or more vertex buffers.
     *
     * @param {Object} options An object containing the following properties:
     * @param {Context} options.context The context in which to create the buffer
     * @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
     * @param {Number} [options.sizeInBytes] A <code>Number</code> defining the size of the buffer in bytes. Required if options.typedArray is not given.
     * @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
     * @returns {VertexBuffer} The vertex buffer, ready to be attached to a vertex array.
     *
     * @exception {DeveloperError} Must specify either <options.typedArray> or <options.sizeInBytes>, but not both.
     * @exception {DeveloperError} The buffer size must be greater than zero.
     * @exception {DeveloperError} Invalid <code>usage</code>.
     *
     *
     * @example
     * // Example 1. Create a dynamic vertex buffer 16 bytes in size.
     * var buffer = Buffer.createVertexBuffer({
     *     context : context,
     *     sizeInBytes : 16,
     *     usage : BufferUsage.DYNAMIC_DRAW
     * });
     *
     * @example
     * // Example 2. Create a dynamic vertex buffer from three floating-point values.
     * // The data copied to the vertex buffer is considered raw bytes until it is
     * // interpreted as vertices using a vertex array.
     * var positionBuffer = buffer.createVertexBuffer({
     *     context : context,
     *     typedArray : new Float32Array([0, 0, 0]),
     *     usage : BufferUsage.STATIC_DRAW
     * });
     *
     * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
     * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with <code>ARRAY_BUFFER</code>
     * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with <code>ARRAY_BUFFER</code>
     */
    Buffer.createVertexBuffer = function(options) {
                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        return new Buffer({
            context: options.context,
            bufferTarget: WebGLConstants.ARRAY_BUFFER,
            typedArray: options.typedArray,
            sizeInBytes: options.sizeInBytes,
            usage: options.usage
        });
    };

    /**
     * Creates an index buffer, which contains typed indices in GPU-controlled memory.
     * <br /><br />
     * An index buffer can be attached to a vertex array to select vertices for rendering.
     * <code>Context.draw</code> can render using the entire index buffer or a subset
     * of the index buffer defined by an offset and count.
     *
     * @param {Object} options An object containing the following properties:
     * @param {Context} options.context The context in which to create the buffer
     * @param {ArrayBufferView} [options.typedArray] A typed array containing the data to copy to the buffer.
     * @param {Number} [options.sizeInBytes] A <code>Number</code> defining the size of the buffer in bytes. Required if options.typedArray is not given.
     * @param {BufferUsage} options.usage Specifies the expected usage pattern of the buffer. On some GL implementations, this can significantly affect performance. See {@link BufferUsage}.
     * @param {IndexDatatype} options.indexDatatype The datatype of indices in the buffer.
     * @returns {IndexBuffer} The index buffer, ready to be attached to a vertex array.
     *
     * @exception {DeveloperError} Must specify either <options.typedArray> or <options.sizeInBytes>, but not both.
     * @exception {DeveloperError} IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system. Check context.elementIndexUint.
     * @exception {DeveloperError} The size in bytes must be greater than zero.
     * @exception {DeveloperError} Invalid <code>usage</code>.
     * @exception {DeveloperError} Invalid <code>indexDatatype</code>.
     *
     *
     * @example
     * // Example 1. Create a stream index buffer of unsigned shorts that is
     * // 16 bytes in size.
     * var buffer = Buffer.createIndexBuffer({
     *     context : context,
     *     sizeInBytes : 16,
     *     usage : BufferUsage.STREAM_DRAW,
     *     indexDatatype : IndexDatatype.UNSIGNED_SHORT
     * });
     *
     * @example
     * // Example 2. Create a static index buffer containing three unsigned shorts.
     * var buffer = Buffer.createIndexBuffer({
     *     context : context,
     *     typedArray : new Uint16Array([0, 1, 2]),
     *     usage : BufferUsage.STATIC_DRAW,
     *     indexDatatype : IndexDatatype.UNSIGNED_SHORT
     * });
     *
     * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenBuffer.xml|glGenBuffer}
     * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml|glBindBuffer} with <code>ELEMENT_ARRAY_BUFFER</code>
     * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBufferData.xml|glBufferData} with <code>ELEMENT_ARRAY_BUFFER</code>
     */
    Buffer.createIndexBuffer = function(options) {
                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }

        if (!IndexDatatype.validate(options.indexDatatype)) {
            throw new DeveloperError('Invalid indexDatatype.');
        }

        if ((options.indexDatatype === IndexDatatype.UNSIGNED_INT) && !options.context.elementIndexUint) {
            throw new DeveloperError('IndexDatatype.UNSIGNED_INT requires OES_element_index_uint, which is not supported on this system.  Check context.elementIndexUint.');
        }
        
        var context = options.context;
        var indexDatatype = options.indexDatatype;

        var bytesPerIndex = IndexDatatype.getSizeInBytes(indexDatatype);
        var buffer = new Buffer({
            context : context,
            bufferTarget : WebGLConstants.ELEMENT_ARRAY_BUFFER,
            typedArray : options.typedArray,
            sizeInBytes : options.sizeInBytes,
            usage : options.usage
        });

        var numberOfIndices = buffer.sizeInBytes / bytesPerIndex;

        defineProperties(buffer, {
            indexDatatype: {
                get : function() {
                    return indexDatatype;
                }
            },
            bytesPerIndex : {
                get : function() {
                    return bytesPerIndex;
                }
            },
            numberOfIndices : {
                get : function() {
                    return numberOfIndices;
                }
            }
        });

        return buffer;
    };

    defineProperties(Buffer.prototype, {
        sizeInBytes : {
            get : function() {
                return this._sizeInBytes;
            }
        },

        usage: {
            get : function() {
                return this._usage;
            }
        }
    });

    Buffer.prototype._getBuffer = function() {
        return this._buffer;
    };

    Buffer.prototype.copyFromArrayView = function(arrayView, offsetInBytes) {
        offsetInBytes = defaultValue(offsetInBytes, 0);

                if (!arrayView) {
            throw new DeveloperError('arrayView is required.');
        }
        if (offsetInBytes + arrayView.byteLength > this._sizeInBytes) {
            throw new DeveloperError('This buffer is not large enough.');
        }
        
        var gl = this._gl;
        var target = this._bufferTarget;
        gl.bindBuffer(target, this._buffer);
        gl.bufferSubData(target, offsetInBytes, arrayView);
        gl.bindBuffer(target, null);
    };

    Buffer.prototype.isDestroyed = function() {
        return false;
    };

    Buffer.prototype.destroy = function() {
        this._gl.deleteBuffer(this._buffer);
        return destroyObject(this);
    };

    return Buffer;
});

/*global define*/
define('Renderer/VertexArray',[
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Geometry',
        '../Core/IndexDatatype',
        '../Core/Math',
        '../Core/RuntimeError',
        './Buffer',
        './BufferUsage',
        './ContextLimits'
    ], function(
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Geometry,
        IndexDatatype,
        CesiumMath,
        RuntimeError,
        Buffer,
        BufferUsage,
        ContextLimits) {
    'use strict';

    function addAttribute(attributes, attribute, index, context) {
        var hasVertexBuffer = defined(attribute.vertexBuffer);
        var hasValue = defined(attribute.value);
        var componentsPerAttribute = attribute.value ? attribute.value.length : attribute.componentsPerAttribute;

                if (!hasVertexBuffer && !hasValue) {
            throw new DeveloperError('attribute must have a vertexBuffer or a value.');
        }
        if (hasVertexBuffer && hasValue) {
            throw new DeveloperError('attribute cannot have both a vertexBuffer and a value.  It must have either a vertexBuffer property defining per-vertex data or a value property defining data for all vertices.');
        }
        if ((componentsPerAttribute !== 1) &&
            (componentsPerAttribute !== 2) &&
            (componentsPerAttribute !== 3) &&
            (componentsPerAttribute !== 4)) {
            if (hasValue) {
                throw new DeveloperError('attribute.value.length must be in the range [1, 4].');
            }

            throw new DeveloperError('attribute.componentsPerAttribute must be in the range [1, 4].');
        }
        if (defined(attribute.componentDatatype) && !ComponentDatatype.validate(attribute.componentDatatype)) {
            throw new DeveloperError('attribute must have a valid componentDatatype or not specify it.');
        }
        if (defined(attribute.strideInBytes) && (attribute.strideInBytes > 255)) {
            // WebGL limit.  Not in GL ES.
            throw new DeveloperError('attribute must have a strideInBytes less than or equal to 255 or not specify it.');
        }
        if (defined(attribute.instanceDivisor) && (attribute.instanceDivisor > 0) && !context.instancedArrays) {
            throw new DeveloperError('instanced arrays is not supported');
        }
        if (defined(attribute.instanceDivisor) && (attribute.instanceDivisor < 0)) {
            throw new DeveloperError('attribute must have an instanceDivisor greater than or equal to zero');
        }
        if (defined(attribute.instanceDivisor) && hasValue) {
            throw new DeveloperError('attribute cannot have have an instanceDivisor if it is not backed by a buffer');
        }
        if (defined(attribute.instanceDivisor) && (attribute.instanceDivisor > 0) && (attribute.index === 0)) {
            throw new DeveloperError('attribute zero cannot have an instanceDivisor greater than 0');
        }
        
        // Shallow copy the attribute; we do not want to copy the vertex buffer.
        var attr = {
            index : defaultValue(attribute.index, index),
            enabled : defaultValue(attribute.enabled, true),
            vertexBuffer : attribute.vertexBuffer,
            value : hasValue ? attribute.value.slice(0) : undefined,
            componentsPerAttribute : componentsPerAttribute,
            componentDatatype : defaultValue(attribute.componentDatatype, ComponentDatatype.FLOAT),
            normalize : defaultValue(attribute.normalize, false),
            offsetInBytes : defaultValue(attribute.offsetInBytes, 0),
            strideInBytes : defaultValue(attribute.strideInBytes, 0),
            instanceDivisor : defaultValue(attribute.instanceDivisor, 0)
        };

        if (hasVertexBuffer) {
            // Common case: vertex buffer for per-vertex data
            attr.vertexAttrib = function(gl) {
                var index = this.index;
                gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer._getBuffer());
                gl.vertexAttribPointer(index, this.componentsPerAttribute, this.componentDatatype, this.normalize, this.strideInBytes, this.offsetInBytes);
                gl.enableVertexAttribArray(index);
                if (this.instanceDivisor > 0) {
                    context.glVertexAttribDivisor(index, this.instanceDivisor);
                    context._vertexAttribDivisors[index] = this.instanceDivisor;
                    context._previousDrawInstanced = true;
                }
            };

            attr.disableVertexAttribArray = function(gl) {
                gl.disableVertexAttribArray(this.index);
                if (this.instanceDivisor > 0) {
                    context.glVertexAttribDivisor(index, 0);
                }
            };
        } else {
            // Less common case: value array for the same data for each vertex
            switch (attr.componentsPerAttribute) {
            case 1:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib1fv(this.index, this.value);
                };
                break;
            case 2:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib2fv(this.index, this.value);
                };
                break;
            case 3:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib3fv(this.index, this.value);
                };
                break;
            case 4:
                attr.vertexAttrib = function(gl) {
                    gl.vertexAttrib4fv(this.index, this.value);
                };
                break;
            }

            attr.disableVertexAttribArray = function(gl) {
            };
        }

        attributes.push(attr);
    }

    function bind(gl, attributes, indexBuffer) {
        for ( var i = 0; i < attributes.length; ++i) {
            var attribute = attributes[i];
            if (attribute.enabled) {
                attribute.vertexAttrib(gl);
            }
        }

        if (defined(indexBuffer)) {
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer._getBuffer());
        }
    }

    /**
     * Creates a vertex array, which defines the attributes making up a vertex, and contains an optional index buffer
     * to select vertices for rendering.  Attributes are defined using object literals as shown in Example 1 below.
     *
     * @param {Object} options Object with the following properties:
     * @param {Context} options.context The context in which the VertexArray gets created.
     * @param {Object[]} options.attributes An array of attributes.
     * @param {IndexBuffer} [options.indexBuffer] An optional index buffer.
     *
     * @returns {VertexArray} The vertex array, ready for use with drawing.
     *
     * @exception {DeveloperError} Attribute must have a <code>vertexBuffer</code>.
     * @exception {DeveloperError} Attribute must have a <code>componentsPerAttribute</code>.
     * @exception {DeveloperError} Attribute must have a valid <code>componentDatatype</code> or not specify it.
     * @exception {DeveloperError} Attribute must have a <code>strideInBytes</code> less than or equal to 255 or not specify it.
     * @exception {DeveloperError} Index n is used by more than one attribute.
     *
     *
     * @example
     * // Example 1. Create a vertex array with vertices made up of three floating point
     * // values, e.g., a position, from a single vertex buffer.  No index buffer is used.
     * var positionBuffer = Buffer.createVertexBuffer({
     *     context : context,
     *     sizeInBytes : 12,
     *     usage : BufferUsage.STATIC_DRAW
     * });
     * var attributes = [
     *     {
     *         index                  : 0,
     *         enabled                : true,
     *         vertexBuffer           : positionBuffer,
     *         componentsPerAttribute : 3,
     *         componentDatatype      : ComponentDatatype.FLOAT,
     *         normalize              : false,
     *         offsetInBytes          : 0,
     *         strideInBytes          : 0 // tightly packed
     *         instanceDivisor        : 0 // not instanced
     *     }
     * ];
     * var va = new VertexArray({
     *     context : context,
     *     attributes : attributes
     * });
     *
     * @example
     * // Example 2. Create a vertex array with vertices from two different vertex buffers.
     * // Each vertex has a three-component position and three-component normal.
     * var positionBuffer = Buffer.createVertexBuffer({
     *     context : context,
     *     sizeInBytes : 12,
     *     usage : BufferUsage.STATIC_DRAW
     * });
     * var normalBuffer = Buffer.createVertexBuffer({
     *     context : context,
     *     sizeInBytes : 12,
     *     usage : BufferUsage.STATIC_DRAW
     * });
     * var attributes = [
     *     {
     *         index                  : 0,
     *         vertexBuffer           : positionBuffer,
     *         componentsPerAttribute : 3,
     *         componentDatatype      : ComponentDatatype.FLOAT
     *     },
     *     {
     *         index                  : 1,
     *         vertexBuffer           : normalBuffer,
     *         componentsPerAttribute : 3,
     *         componentDatatype      : ComponentDatatype.FLOAT
     *     }
     * ];
     * var va = new VertexArray({
     *     context : context,
     *     attributes : attributes
     * });
     *
     * @example
     * // Example 3. Creates the same vertex layout as Example 2 using a single
     * // vertex buffer, instead of two.
     * var buffer = Buffer.createVertexBuffer({
     *     context : context,
     *     sizeInBytes : 24,
     *     usage : BufferUsage.STATIC_DRAW
     * });
     * var attributes = [
     *     {
     *         vertexBuffer           : buffer,
     *         componentsPerAttribute : 3,
     *         componentDatatype      : ComponentDatatype.FLOAT,
     *         offsetInBytes          : 0,
     *         strideInBytes          : 24
     *     },
     *     {
     *         vertexBuffer           : buffer,
     *         componentsPerAttribute : 3,
     *         componentDatatype      : ComponentDatatype.FLOAT,
     *         normalize              : true,
     *         offsetInBytes          : 12,
     *         strideInBytes          : 24
     *     }
     * ];
     * var va = new VertexArray({
     *     context : context,
     *     attributes : attributes
     * });
     *
     * @see Buffer#createVertexBuffer
     * @see Buffer#createIndexBuffer
     * @see Context#draw
     *
     * @private
     */
    function VertexArray(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }

        if (!defined(options.attributes)) {
            throw new DeveloperError('options.attributes is required.');
        }
        
        var context = options.context;
        var gl = context._gl;
        var attributes = options.attributes;
        var indexBuffer = options.indexBuffer;

        var i;
        var vaAttributes = [];
        var numberOfVertices = 1;   // if every attribute is backed by a single value
        var hasInstancedAttributes = false;
        var hasConstantAttributes = false;

        var length = attributes.length;
        for (i = 0; i < length; ++i) {
            addAttribute(vaAttributes, attributes[i], i, context);
        }

        length = vaAttributes.length;
        for (i = 0; i < length; ++i) {
            var attribute = vaAttributes[i];

            if (defined(attribute.vertexBuffer) && (attribute.instanceDivisor === 0)) {
                // This assumes that each vertex buffer in the vertex array has the same number of vertices.
                var bytes = attribute.strideInBytes || (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(attribute.componentDatatype));
                numberOfVertices = attribute.vertexBuffer.sizeInBytes / bytes;
                break;
            }
        }

        for (i = 0; i < length; ++i) {
            if (vaAttributes[i].instanceDivisor > 0) {
                hasInstancedAttributes = true;
            }
            if (defined(vaAttributes[i].value)) {
                hasConstantAttributes = true;
            }
        }

                // Verify all attribute names are unique
        var uniqueIndices = {};
        for (i = 0; i < length; ++i) {
            var index = vaAttributes[i].index;
            if (uniqueIndices[index]) {
                throw new DeveloperError('Index ' + index + ' is used by more than one attribute.');
            }
            uniqueIndices[index] = true;
        }
        
        var vao;

        // Setup VAO if supported
        if (context.vertexArrayObject) {
            vao = context.glCreateVertexArray();
            context.glBindVertexArray(vao);
            bind(gl, vaAttributes, indexBuffer);
            context.glBindVertexArray(null);
        }

        this._numberOfVertices = numberOfVertices;
        this._hasInstancedAttributes = hasInstancedAttributes;
        this._hasConstantAttributes = hasConstantAttributes;
        this._context = context;
        this._gl = gl;
        this._vao = vao;
        this._attributes = vaAttributes;
        this._indexBuffer = indexBuffer;
    }

    function computeNumberOfVertices(attribute) {
        return attribute.values.length / attribute.componentsPerAttribute;
    }

    function computeAttributeSizeInBytes(attribute) {
        return ComponentDatatype.getSizeInBytes(attribute.componentDatatype) * attribute.componentsPerAttribute;
    }

    function interleaveAttributes(attributes) {
        var j;
        var name;
        var attribute;

        // Extract attribute names.
        var names = [];
        for (name in attributes) {
            // Attribute needs to have per-vertex values; not a constant value for all vertices.
            if (attributes.hasOwnProperty(name) &&
                defined(attributes[name]) &&
                defined(attributes[name].values)) {
                names.push(name);

                if (attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
                    attributes[name].componentDatatype = ComponentDatatype.FLOAT;
                    attributes[name].values = ComponentDatatype.createTypedArray(ComponentDatatype.FLOAT, attributes[name].values);
                }
            }
        }

        // Validation.  Compute number of vertices.
        var numberOfVertices;
        var namesLength = names.length;

        if (namesLength > 0) {
            numberOfVertices = computeNumberOfVertices(attributes[names[0]]);

            for (j = 1; j < namesLength; ++j) {
                var currentNumberOfVertices = computeNumberOfVertices(attributes[names[j]]);

                if (currentNumberOfVertices !== numberOfVertices) {
                    throw new RuntimeError(
                        'Each attribute list must have the same number of vertices.  ' +
                        'Attribute ' + names[j] + ' has a different number of vertices ' +
                        '(' + currentNumberOfVertices.toString() + ')' +
                        ' than attribute ' + names[0] +
                        ' (' + numberOfVertices.toString() + ').');
                }
            }
        }

        // Sort attributes by the size of their components.  From left to right, a vertex stores floats, shorts, and then bytes.
        names.sort(function(left, right) {
            return ComponentDatatype.getSizeInBytes(attributes[right].componentDatatype) - ComponentDatatype.getSizeInBytes(attributes[left].componentDatatype);
        });

        // Compute sizes and strides.
        var vertexSizeInBytes = 0;
        var offsetsInBytes = {};

        for (j = 0; j < namesLength; ++j) {
            name = names[j];
            attribute = attributes[name];

            offsetsInBytes[name] = vertexSizeInBytes;
            vertexSizeInBytes += computeAttributeSizeInBytes(attribute);
        }

        if (vertexSizeInBytes > 0) {
            // Pad each vertex to be a multiple of the largest component datatype so each
            // attribute can be addressed using typed arrays.
            var maxComponentSizeInBytes = ComponentDatatype.getSizeInBytes(attributes[names[0]].componentDatatype); // Sorted large to small
            var remainder = vertexSizeInBytes % maxComponentSizeInBytes;
            if (remainder !== 0) {
                vertexSizeInBytes += (maxComponentSizeInBytes - remainder);
            }

            // Total vertex buffer size in bytes, including per-vertex padding.
            var vertexBufferSizeInBytes = numberOfVertices * vertexSizeInBytes;

            // Create array for interleaved vertices.  Each attribute has a different view (pointer) into the array.
            var buffer = new ArrayBuffer(vertexBufferSizeInBytes);
            var views = {};

            for (j = 0; j < namesLength; ++j) {
                name = names[j];
                var sizeInBytes = ComponentDatatype.getSizeInBytes(attributes[name].componentDatatype);

                views[name] = {
                    pointer : ComponentDatatype.createTypedArray(attributes[name].componentDatatype, buffer),
                    index : offsetsInBytes[name] / sizeInBytes, // Offset in ComponentType
                    strideInComponentType : vertexSizeInBytes / sizeInBytes
                };
            }

            // Copy attributes into one interleaved array.
            // PERFORMANCE_IDEA:  Can we optimize these loops?
            for (j = 0; j < numberOfVertices; ++j) {
                for ( var n = 0; n < namesLength; ++n) {
                    name = names[n];
                    attribute = attributes[name];
                    var values = attribute.values;
                    var view = views[name];
                    var pointer = view.pointer;

                    var numberOfComponents = attribute.componentsPerAttribute;
                    for ( var k = 0; k < numberOfComponents; ++k) {
                        pointer[view.index + k] = values[(j * numberOfComponents) + k];
                    }

                    view.index += view.strideInComponentType;
                }
            }

            return {
                buffer : buffer,
                offsetsInBytes : offsetsInBytes,
                vertexSizeInBytes : vertexSizeInBytes
            };
        }

        // No attributes to interleave.
        return undefined;
    }

    /**
     * Creates a vertex array from a geometry.  A geometry contains vertex attributes and optional index data
     * in system memory, whereas a vertex array contains vertex buffers and an optional index buffer in WebGL
     * memory for use with rendering.
     * <br /><br />
     * The <code>geometry</code> argument should use the standard layout like the geometry returned by {@link BoxGeometry}.
     * <br /><br />
     * <code>options</code> can have four properties:
     * <ul>
     *   <li><code>geometry</code>:  The source geometry containing data used to create the vertex array.</li>
     *   <li><code>attributeLocations</code>:  An object that maps geometry attribute names to vertex shader attribute locations.</li>
     *   <li><code>bufferUsage</code>:  The expected usage pattern of the vertex array's buffers.  On some WebGL implementations, this can significantly affect performance.  See {@link BufferUsage}.  Default: <code>BufferUsage.DYNAMIC_DRAW</code>.</li>
     *   <li><code>interleave</code>:  Determines if all attributes are interleaved in a single vertex buffer or if each attribute is stored in a separate vertex buffer.  Default: <code>false</code>.</li>
     * </ul>
     * <br />
     * If <code>options</code> is not specified or the <code>geometry</code> contains no data, the returned vertex array is empty.
     *
     * @param {Object} options An object defining the geometry, attribute indices, buffer usage, and vertex layout used to create the vertex array.
     *
     * @exception {RuntimeError} Each attribute list must have the same number of vertices.
     * @exception {DeveloperError} The geometry must have zero or one index lists.
     * @exception {DeveloperError} Index n is used by more than one attribute.
     *
     *
     * @example
     * // Example 1. Creates a vertex array for rendering a box.  The default dynamic draw
     * // usage is used for the created vertex and index buffer.  The attributes are not
     * // interleaved by default.
     * var geometry = new BoxGeometry();
     * var va = VertexArray.fromGeometry({
     *     context            : context,
     *     geometry           : geometry,
     *     attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
     * });
     *
     * @example
     * // Example 2. Creates a vertex array with interleaved attributes in a
     * // single vertex buffer.  The vertex and index buffer have static draw usage.
     * var va = VertexArray.fromGeometry({
     *     context            : context,
     *     geometry           : geometry,
     *     attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
     *     bufferUsage        : BufferUsage.STATIC_DRAW,
     *     interleave         : true
     * });
     *
     * @example
     * // Example 3.  When the caller destroys the vertex array, it also destroys the
     * // attached vertex buffer(s) and index buffer.
     * va = va.destroy();
     *
     * @see Buffer#createVertexBuffer
     * @see Buffer#createIndexBuffer
     * @see GeometryPipeline.createAttributeLocations
     * @see ShaderProgram
     */
    VertexArray.fromGeometry = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        var context = options.context;
        var geometry = defaultValue(options.geometry, defaultValue.EMPTY_OBJECT);

        var bufferUsage = defaultValue(options.bufferUsage, BufferUsage.DYNAMIC_DRAW);

        var attributeLocations = defaultValue(options.attributeLocations, defaultValue.EMPTY_OBJECT);
        var interleave = defaultValue(options.interleave, false);
        var createdVAAttributes = options.vertexArrayAttributes;

        var name;
        var attribute;
        var vertexBuffer;
        var vaAttributes = (defined(createdVAAttributes)) ? createdVAAttributes : [];
        var attributes = geometry.attributes;

        if (interleave) {
            // Use a single vertex buffer with interleaved vertices.
            var interleavedAttributes = interleaveAttributes(attributes);
            if (defined(interleavedAttributes)) {
                vertexBuffer = Buffer.createVertexBuffer({
                    context : context,
                    typedArray : interleavedAttributes.buffer,
                    usage : bufferUsage
                });
                var offsetsInBytes = interleavedAttributes.offsetsInBytes;
                var strideInBytes = interleavedAttributes.vertexSizeInBytes;

                for (name in attributes) {
                    if (attributes.hasOwnProperty(name) && defined(attributes[name])) {
                        attribute = attributes[name];

                        if (defined(attribute.values)) {
                            // Common case: per-vertex attributes
                            vaAttributes.push({
                                index : attributeLocations[name],
                                vertexBuffer : vertexBuffer,
                                componentDatatype : attribute.componentDatatype,
                                componentsPerAttribute : attribute.componentsPerAttribute,
                                normalize : attribute.normalize,
                                offsetInBytes : offsetsInBytes[name],
                                strideInBytes : strideInBytes
                            });
                        } else {
                            // Constant attribute for all vertices
                            vaAttributes.push({
                                index : attributeLocations[name],
                                value : attribute.value,
                                componentDatatype : attribute.componentDatatype,
                                normalize : attribute.normalize
                            });
                        }
                    }
                }
            }
        } else {
            // One vertex buffer per attribute.
            for (name in attributes) {
                if (attributes.hasOwnProperty(name) && defined(attributes[name])) {
                    attribute = attributes[name];

                    var componentDatatype = attribute.componentDatatype;
                    if (componentDatatype === ComponentDatatype.DOUBLE) {
                        componentDatatype = ComponentDatatype.FLOAT;
                    }

                    vertexBuffer = undefined;
                    if (defined(attribute.values)) {
                        vertexBuffer = Buffer.createVertexBuffer({
                            context : context,
                            typedArray : ComponentDatatype.createTypedArray(componentDatatype, attribute.values),
                            usage : bufferUsage
                        });
                    }

                    vaAttributes.push({
                        index : attributeLocations[name],
                        vertexBuffer : vertexBuffer,
                        value : attribute.value,
                        componentDatatype : componentDatatype,
                        componentsPerAttribute : attribute.componentsPerAttribute,
                        normalize : attribute.normalize
                    });
                }
            }
        }

        var indexBuffer;
        var indices = geometry.indices;
        if (defined(indices)) {
            if ((Geometry.computeNumberOfVertices(geometry) >= CesiumMath.SIXTY_FOUR_KILOBYTES) && context.elementIndexUint) {
                indexBuffer = Buffer.createIndexBuffer({
                    context : context,
                    typedArray : new Uint32Array(indices),
                    usage : bufferUsage,
                    indexDatatype : IndexDatatype.UNSIGNED_INT
                });
            } else{
                indexBuffer = Buffer.createIndexBuffer({
                    context : context,
                    typedArray : new Uint16Array(indices),
                    usage : bufferUsage,
                    indexDatatype : IndexDatatype.UNSIGNED_SHORT
                });
            }
        }

        return new VertexArray({
            context : context,
            attributes : vaAttributes,
            indexBuffer : indexBuffer
        });
    };

    defineProperties(VertexArray.prototype, {
        numberOfAttributes : {
            get : function() {
                return this._attributes.length;
            }
        },
        numberOfVertices : {
            get : function() {
                return this._numberOfVertices;
            }
        },
        indexBuffer : {
            get : function() {
                return this._indexBuffer;
            }
        }
    });

    /**
     * index is the location in the array of attributes, not the index property of an attribute.
     */
    VertexArray.prototype.getAttribute = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._attributes[index];
    };

    // Workaround for ANGLE, where the attribute divisor seems to be part of the global state instead
    // of the VAO state. This function is called when the vao is bound, and should be removed
    // once the ANGLE issue is resolved. Setting the divisor should normally happen in vertexAttrib and
    // disableVertexAttribArray.
    function setVertexAttribDivisor(vertexArray) {
        var context = vertexArray._context;
        var hasInstancedAttributes = vertexArray._hasInstancedAttributes;
        if (!hasInstancedAttributes && !context._previousDrawInstanced) {
            return;
        }
        context._previousDrawInstanced = hasInstancedAttributes;

        var divisors = context._vertexAttribDivisors;
        var attributes = vertexArray._attributes;
        var maxAttributes = ContextLimits.maximumVertexAttributes;
        var i;

        if (hasInstancedAttributes) {
            var length = attributes.length;
            for (i = 0; i < length; ++i) {
                var attribute = attributes[i];
                if (attribute.enabled) {
                    var divisor = attribute.instanceDivisor;
                    var index = attribute.index;
                    if (divisor !== divisors[index]) {
                        context.glVertexAttribDivisor(index, divisor);
                        divisors[index] = divisor;
                    }
                }
            }
        } else {
            for (i = 0; i < maxAttributes; ++i) {
                if (divisors[i] > 0) {
                    context.glVertexAttribDivisor(i, 0);
                    divisors[i] = 0;
                }
            }
        }
    }

    // Vertex attributes backed by a constant value go through vertexAttrib[1234]f[v]
    // which is part of context state rather than VAO state.
    function setConstantAttributes(vertexArray, gl) {
        var attributes = vertexArray._attributes;
        var length = attributes.length;
        for (var i = 0; i < length; ++i) {
            var attribute = attributes[i];
            if (attribute.enabled && defined(attribute.value)) {
                attribute.vertexAttrib(gl);
            }
        }
    }

    VertexArray.prototype._bind = function() {
        if (defined(this._vao)) {
            this._context.glBindVertexArray(this._vao);
            if (this._context.instancedArrays) {
                setVertexAttribDivisor(this);
            }
            if (this._hasConstantAttributes) {
                setConstantAttributes(this, this._gl);
            }
        } else {
            bind(this._gl, this._attributes, this._indexBuffer);
        }
    };

    VertexArray.prototype._unBind = function() {
        if (defined(this._vao)) {
            this._context.glBindVertexArray(null);
        } else {
            var attributes = this._attributes;
            var gl = this._gl;

            for ( var i = 0; i < attributes.length; ++i) {
                var attribute = attributes[i];
                if (attribute.enabled) {
                    attribute.disableVertexAttribArray(gl);
                }
            }
            if (this._indexBuffer) {
                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
            }
        }
    };

    VertexArray.prototype.isDestroyed = function() {
        return false;
    };

    VertexArray.prototype.destroy = function() {
        var attributes = this._attributes;
        for ( var i = 0; i < attributes.length; ++i) {
            var vertexBuffer = attributes[i].vertexBuffer;
            if (defined(vertexBuffer) && !vertexBuffer.isDestroyed() && vertexBuffer.vertexArrayDestroyable) {
                vertexBuffer.destroy();
            }
        }

        var indexBuffer = this._indexBuffer;
        if (defined(indexBuffer) && !indexBuffer.isDestroyed() && indexBuffer.vertexArrayDestroyable) {
            indexBuffer.destroy();
        }

        if (defined(this._vao)) {
            this._context.glDeleteVertexArray(this._vao);
        }

        return destroyObject(this);
    };

    return VertexArray;
});

/*global define*/
define('Scene/BatchTable',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/combine',
        '../Core/ComponentDatatype',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/FeatureDetection',
        '../Core/Math',
        '../Core/PixelFormat',
        '../Renderer/ContextLimits',
        '../Renderer/PixelDatatype',
        '../Renderer/Sampler',
        '../Renderer/Texture',
        '../Renderer/TextureMagnificationFilter',
        '../Renderer/TextureMinificationFilter'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartesian4,
        combine,
        ComponentDatatype,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        CesiumMath,
        PixelFormat,
        ContextLimits,
        PixelDatatype,
        Sampler,
        Texture,
        TextureMagnificationFilter,
        TextureMinificationFilter) {
    'use strict';

    /**
     * Creates a texture to look up per instance attributes for batched primitives. For example, store each primitive's pick color in the texture.
     *
     * @alias BatchTable
     * @constructor
     * @private
     *
     * @param {Context} context The context in which the batch table is created.
     * @param {Object[]} attributes An array of objects describing a per instance attribute. Each object contains a datatype, components per attributes, whether it is normalized and a function name
     *     to retrieve the value in the vertex shader.
     * @param {Number} numberOfInstances The number of instances in a batch table.
     *
     * @example
     * // create the batch table
     * var attributes = [{
     *     functionName : 'getShow',
     *     componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
     *     componentsPerAttribute : 1
     * }, {
     *     functionName : 'getPickColor',
     *     componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
     *     componentsPerAttribute : 4,
     *     normalize : true
     * }];
     * var batchTable = new BatchTable(context, attributes, 5);
     *
     * // when creating the draw commands, update the uniform map and the vertex shader
     * vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource);
     * var shaderProgram = ShaderProgram.fromCache({
     *    // ...
     *    vertexShaderSource : vertexShaderSource,
     * });
     *
     * drawCommand.shaderProgram = shaderProgram;
     * drawCommand.uniformMap = batchTable.getUniformMapCallback()(uniformMap);
     *
     * // use the attribute function names in the shader to retrieve the instance values
     * // ...
     * attribute float batchId;
     *
     * void main() {
     *     // ...
     *     float show = getShow(batchId);
     *     vec3 pickColor = getPickColor(batchId);
     *     // ...
     * }
     */
    function BatchTable(context, attributes, numberOfInstances) {
                if (!defined(context)) {
            throw new DeveloperError('context is required');
        }
        if (!defined(attributes)) {
            throw new DeveloperError('attributes is required');
        }
        if (!defined(numberOfInstances)) {
            throw new DeveloperError('numberOfInstances is required');
        }
        
        this._attributes = attributes;
        this._numberOfInstances = numberOfInstances;

        if (attributes.length === 0) {
            return;
        }

        // PERFORMANCE_IDEA: We may be able to arrange the attributes so they can be packing into fewer texels.
        // Right now, an attribute with one component uses an entire texel when 4 single component attributes can
        // be packed into a texel.
        //
        // Packing floats into unsigned byte textures makes the problem worse. A single component float attribute
        // will be packed into a single texel leaving 3 texels unused. 4 texels are reserved for each float attribute
        // regardless of how many components it has.
        var pixelDatatype = getDatatype(attributes);
        var textureFloatSupported = context.floatingPointTexture;
        var packFloats = pixelDatatype === PixelDatatype.FLOAT && !textureFloatSupported;
        var offsets = createOffsets(attributes, packFloats);

        var stride = getStride(offsets, attributes, packFloats);
        var maxNumberOfInstancesPerRow = Math.floor(ContextLimits.maximumTextureSize / stride);

        var instancesPerWidth = Math.min(numberOfInstances, maxNumberOfInstancesPerRow);
        var width = stride * instancesPerWidth;
        var height = Math.ceil(numberOfInstances / instancesPerWidth);

        var stepX = 1.0 / width;
        var centerX = stepX * 0.5;
        var stepY = 1.0 / height;
        var centerY = stepY * 0.5;

        this._textureDimensions = new Cartesian2(width, height);
        this._textureStep = new Cartesian4(stepX, centerX, stepY, centerY);
        this._pixelDatatype = !packFloats ? pixelDatatype : PixelDatatype.UNSIGNED_BYTE;
        this._packFloats = packFloats;
        this._offsets = offsets;
        this._stride = stride;
        this._texture = undefined;

        var batchLength = 4 * width * height;
        this._batchValues = pixelDatatype === PixelDatatype.FLOAT && !packFloats ? new Float32Array(batchLength) : new Uint8Array(batchLength);
        this._batchValuesDirty = false;
    }

    defineProperties(BatchTable.prototype, {
        /**
         * The attribute descriptions.
         * @memberOf BatchTable.prototype
         * @type {Object[]}
         * @readonly
         */
        attributes : {
            get : function() {
                return this._attributes;
            }
        },
        /**
         * The number of instances.
         * @memberOf BatchTable.prototype
         * @type {Number}
         * @readonly
         */
        numberOfInstances : {
            get : function () {
                return this._numberOfInstances;
            }
        }
    });

    function getDatatype(attributes) {
        var foundFloatDatatype = false;
        var length = attributes.length;
        for (var i = 0; i < length; ++i) {
            if (attributes[i].componentDatatype !== ComponentDatatype.UNSIGNED_BYTE) {
                foundFloatDatatype = true;
                break;
            }
        }
        return foundFloatDatatype ? PixelDatatype.FLOAT : PixelDatatype.UNSIGNED_BYTE;
    }

    function getAttributeType(attributes, attributeIndex) {
        var componentsPerAttribute = attributes[attributeIndex].componentsPerAttribute;
        if (componentsPerAttribute === 2) {
            return Cartesian2;
        } else if (componentsPerAttribute === 3) {
            return Cartesian3;
        } else if (componentsPerAttribute === 4) {
            return Cartesian4;
        }
        return Number;
    }

    function createOffsets(attributes, packFloats) {
        var offsets = new Array(attributes.length);

        var currentOffset = 0;
        var attributesLength = attributes.length;
        for (var i = 0; i < attributesLength; ++i) {
            var attribute = attributes[i];
            var componentDatatype = attribute.componentDatatype;

            offsets[i] = currentOffset;

            if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
                currentOffset += 4;
            } else {
                ++currentOffset;
            }
        }

        return offsets;
    }

    function getStride(offsets, attributes, packFloats) {
        var length = offsets.length;
        var lastOffset = offsets[length - 1];
        var lastAttribute = attributes[length - 1];
        var componentDatatype = lastAttribute.componentDatatype;

        if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) {
            return lastOffset + 4;
        }
        return lastOffset + 1;
    }

    var scratchPackedFloatCartesian4 = new Cartesian4();

    var SHIFT_LEFT_8 = 256.0;
    var SHIFT_LEFT_16 = 65536.0;
    var SHIFT_LEFT_24 = 16777216.0;

    var SHIFT_RIGHT_8 = 1.0 / SHIFT_LEFT_8;
    var SHIFT_RIGHT_16 = 1.0 / SHIFT_LEFT_16;
    var SHIFT_RIGHT_24 = 1.0 / SHIFT_LEFT_24;

    var BIAS = 38.0;

    function unpackFloat(value) {
        var temp = value.w / 2.0;
        var exponent = Math.floor(temp);
        var sign = (temp - exponent) * 2.0;
        exponent = exponent - BIAS;

        sign = sign * 2.0 - 1.0;
        sign = -sign;

        if (exponent >= BIAS) {
            return sign < 0.0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY;
        }

        var unpacked = sign * value.x * SHIFT_RIGHT_8;
        unpacked += sign * value.y * SHIFT_RIGHT_16;
        unpacked += sign * value.z * SHIFT_RIGHT_24;

        return unpacked * Math.pow(10.0, exponent);
    }

    function getPackedFloat(array, index, result) {
        var packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4);
        var x = unpackFloat(packed);

        packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4);
        var y = unpackFloat(packed);

        packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4);
        var z = unpackFloat(packed);

        packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4);
        var w = unpackFloat(packed);

        return Cartesian4.fromElements(x, y, z, w, result);
    }

    if (!FeatureDetection.supportsTypedArrays()) {
        return;
    }
    var scratchFloatArray = new Float32Array(1);

    function packFloat(value, result) {
        scratchFloatArray[0] = value;
        value = scratchFloatArray[0];

        if (value === 0.0) {
            return Cartesian4.clone(Cartesian4.ZERO, result);
        }

        var sign = value < 0.0 ? 1.0 : 0.0;
        var exponent;

        if (!isFinite(value)) {
            value = 0.1;
            exponent = BIAS;
        } else {
            value = Math.abs(value);
            exponent = Math.floor(CesiumMath.logBase(value, 10)) + 1.0;
            value = value / Math.pow(10.0, exponent);
        }

        var temp = value * SHIFT_LEFT_8;
        result.x = Math.floor(temp);
        temp = (temp - result.x) * SHIFT_LEFT_8;
        result.y = Math.floor(temp);
        temp = (temp - result.y) * SHIFT_LEFT_8;
        result.z = Math.floor(temp);
        result.w = (exponent + BIAS) * 2.0 + sign;

        return result;
    }

    function setPackedAttribute(value, array, index) {
        var packed = packFloat(value.x, scratchPackedFloatCartesian4);
        Cartesian4.pack(packed, array, index);

        packed = packFloat(value.y, packed);
        Cartesian4.pack(packed, array, index + 4);

        packed = packFloat(value.z, packed);
        Cartesian4.pack(packed, array, index + 8);

        packed = packFloat(value.w, packed);
        Cartesian4.pack(packed, array, index + 12);
    }

    var scratchGetAttributeCartesian4 = new Cartesian4();

    /**
     * Gets the value of an attribute in the table.
     *
     * @param {Number} instanceIndex The index of the instance.
     * @param {Number} attributeIndex The index of the attribute.
     * @param {undefined|Cartesian2|Cartesian3|Cartesian4} [result] The object onto which to store the result. The type is dependent on the attribute's number of components.
     * @returns {Number|Cartesian2|Cartesian3|Cartesian4} The attribute value stored for the instance.
     *
     * @exception {DeveloperError} instanceIndex is out of range.
     * @exception {DeveloperError} attributeIndex is out of range.
     */
    BatchTable.prototype.getBatchedAttribute = function(instanceIndex, attributeIndex, result) {
                if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
            throw new DeveloperError('instanceIndex is out of range.');
        }
        if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
            throw new DeveloperError('attributeIndex is out of range');
        }
        
        var attributes = this._attributes;
        var offset = this._offsets[attributeIndex];
        var stride = this._stride;

        var index = 4 * stride * instanceIndex + 4 * offset;
        var value;

        if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) {
            value = getPackedFloat(this._batchValues, index, scratchGetAttributeCartesian4);
        } else {
            value = Cartesian4.unpack(this._batchValues, index, scratchGetAttributeCartesian4);
        }

        var attributeType = getAttributeType(attributes, attributeIndex);
        if (defined(attributeType.fromCartesian4)) {
            return attributeType.fromCartesian4(value, result);
        } else if (defined(attributeType.clone)) {
            return attributeType.clone(value, result);
        }

        return value.x;
    };

    var setAttributeScratchValues = [undefined, undefined, new Cartesian2(), new Cartesian3(), new Cartesian4()];
    var setAttributeScratchCartesian4 = new Cartesian4();

    /**
     * Sets the value of an attribute in the table.
     *
     * @param {Number} instanceIndex The index of the instance.
     * @param {Number} attributeIndex The index of the attribute.
     * @param {Number|Cartesian2|Cartesian3|Cartesian4} value The value to be stored in the table. The type of value will depend on the number of components of the attribute.
     *
     * @exception {DeveloperError} instanceIndex is out of range.
     * @exception {DeveloperError} attributeIndex is out of range.
     */
    BatchTable.prototype.setBatchedAttribute = function(instanceIndex, attributeIndex, value) {
                if (instanceIndex < 0 || instanceIndex >= this._numberOfInstances) {
            throw new DeveloperError('instanceIndex is out of range.');
        }
        if (attributeIndex < 0 || attributeIndex >= this._attributes.length) {
            throw new DeveloperError('attributeIndex is out of range');
        }
        if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }
        
        var attributes = this._attributes;
        var result = setAttributeScratchValues[attributes[attributeIndex].componentsPerAttribute];
        var currentAttribute = this.getBatchedAttribute(instanceIndex, attributeIndex, result);
        var attributeType = getAttributeType(this._attributes, attributeIndex);
        var entriesEqual = defined(attributeType.equals) ? attributeType.equals(currentAttribute, value) : currentAttribute === value;
        if (entriesEqual) {
            return;
        }

        var attributeValue = setAttributeScratchCartesian4;
        attributeValue.x = defined(value.x) ? value.x : value;
        attributeValue.y = defined(value.y) ? value.y : 0.0;
        attributeValue.z = defined(value.z) ? value.z : 0.0;
        attributeValue.w = defined(value.w) ? value.w : 0.0;

        var offset = this._offsets[attributeIndex];
        var stride = this._stride;
        var index = 4 * stride * instanceIndex + 4 * offset;

        if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) {
            setPackedAttribute(attributeValue, this._batchValues, index);
        } else {
            Cartesian4.pack(attributeValue, this._batchValues, index);
        }

        this._batchValuesDirty = true;
    };

    function createTexture(batchTable, context) {
        var dimensions = batchTable._textureDimensions;
        batchTable._texture = new Texture({
            context : context,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : batchTable._pixelDatatype,
            width : dimensions.x,
            height : dimensions.y,
            sampler : new Sampler({
                minificationFilter : TextureMinificationFilter.NEAREST,
                magnificationFilter : TextureMagnificationFilter.NEAREST
            })
        });
    }

    function updateTexture(batchTable) {
        var dimensions = batchTable._textureDimensions;
        batchTable._texture.copyFrom({
            width : dimensions.x,
            height : dimensions.y,
            arrayBufferView : batchTable._batchValues
        });
    }

    /**
     * Creates/updates the batch table texture.
     * @param {FrameState} frameState The frame state.
     *
     * @exception {RuntimeError} The floating point texture extension is required but not supported.
     */
    BatchTable.prototype.update = function(frameState) {
        if ((defined(this._texture) && !this._batchValuesDirty) || this._attributes.length === 0) {
            return;
        }

        this._batchValuesDirty = false;

        if (!defined(this._texture)) {
            createTexture(this, frameState.context);
        }
        updateTexture(this);
    };

    /**
     * Gets a function that will update a uniform map to contain values for looking up values in the batch table.
     *
     * @returns {BatchTable~updateUniformMapCallback} A callback for updating uniform maps.
     */
    BatchTable.prototype.getUniformMapCallback = function() {
        var that = this;
        return function(uniformMap) {
            if (that._attributes.length === 0) {
                return uniformMap;
            }

            var batchUniformMap = {
                batchTexture : function() {
                    return that._texture;
                },
                batchTextureDimensions : function() {
                    return that._textureDimensions;
                },
                batchTextureStep : function() {
                    return that._textureStep;
                }
            };
            return combine(uniformMap, batchUniformMap);
        };
    };

    function getGlslComputeSt(batchTable) {
        var stride = batchTable._stride;

        // GLSL batchId is zero-based: [0, numberOfInstances - 1]
        if (batchTable._textureDimensions.y === 1) {
            return 'uniform vec4 batchTextureStep; \n' +
                   'vec2 computeSt(float batchId) \n' +
                   '{ \n' +
                   '    float stepX = batchTextureStep.x; \n' +
                   '    float centerX = batchTextureStep.y; \n' +
                   '    float numberOfAttributes = float('+ stride + '); \n' +
                   '    return vec2(centerX + (batchId * numberOfAttributes * stepX), 0.5); \n' +
                   '} \n';
        }

        return 'uniform vec4 batchTextureStep; \n' +
               'uniform vec2 batchTextureDimensions; \n' +
               'vec2 computeSt(float batchId) \n' +
               '{ \n' +
               '    float stepX = batchTextureStep.x; \n' +
               '    float centerX = batchTextureStep.y; \n' +
               '    float stepY = batchTextureStep.z; \n' +
               '    float centerY = batchTextureStep.w; \n' +
               '    float numberOfAttributes = float('+ stride + '); \n' +
               '    float xId = mod(batchId * numberOfAttributes, batchTextureDimensions.x); \n' +
               '    float yId = floor(batchId * numberOfAttributes / batchTextureDimensions.x); \n' +
               '    return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' +
               '} \n';
    }

    function getGlslUnpackFloat(batchTable) {
        if (!batchTable._packFloats) {
            return '';
        }

        return 'float unpackFloat(vec4 value) \n' +
               '{ \n' +
               '    value *= 255.0; \n' +
               '    float temp = value.w / 2.0; \n' +
               '    float exponent = floor(temp); \n' +
               '    float sign = (temp - exponent) * 2.0; \n' +
               '    exponent = exponent - float(' + BIAS + '); \n' +
               '    sign = sign * 2.0 - 1.0; \n' +
               '    sign = -sign; \n' +
               '    float unpacked = sign * value.x * float(' + SHIFT_RIGHT_8 + '); \n' +
               '    unpacked += sign * value.y * float(' + SHIFT_RIGHT_16 + '); \n' +
               '    unpacked += sign * value.z * float(' + SHIFT_RIGHT_24 + '); \n' +
               '    return unpacked * pow(10.0, exponent); \n' +
               '} \n';
    }

    function getComponentType(componentsPerAttribute) {
        if (componentsPerAttribute === 1) {
            return 'float';
        }
        return 'vec' + componentsPerAttribute;
    }

    function getComponentSwizzle(componentsPerAttribute) {
        if (componentsPerAttribute === 1) {
            return '.x';
        } else if (componentsPerAttribute === 2) {
            return '.xy';
        } else if (componentsPerAttribute === 3) {
            return '.xyz';
        }
        return '';
    }

    function getGlslAttributeFunction(batchTable, attributeIndex) {
        var attributes = batchTable._attributes;
        var attribute = attributes[attributeIndex];
        var componentsPerAttribute = attribute.componentsPerAttribute;
        var functionName = attribute.functionName;
        var functionReturnType = getComponentType(componentsPerAttribute);
        var functionReturnValue = getComponentSwizzle(componentsPerAttribute);

        var offset = batchTable._offsets[attributeIndex];

        var glslFunction =
            functionReturnType + ' ' + functionName + '(float batchId) \n' +
            '{ \n' +
            '    vec2 st = computeSt(batchId); \n' +
            '    st.x += batchTextureStep.x * float(' + offset + '); \n';

        if (batchTable._packFloats && attribute.componentDatatype !== PixelDatatype.UNSIGNED_BYTE) {
            glslFunction += 'vec4 textureValue; \n' +
                            'textureValue.x = unpackFloat(texture2D(batchTexture, st)); \n' +
                            'textureValue.y = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x, 0.0))); \n' +
                            'textureValue.z = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x * 2.0, 0.0))); \n' +
                            'textureValue.w = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x * 3.0, 0.0))); \n';

        } else {
            glslFunction += '    vec4 textureValue = texture2D(batchTexture, st); \n';
        }

        glslFunction += '    ' + functionReturnType + ' value = textureValue' + functionReturnValue + '; \n';

        if (batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && !attribute.normalize) {
            glslFunction += 'value *= 255.0; \n';
        } else if (batchTable._pixelDatatype === PixelDatatype.FLOAT && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && attribute.normalize) {
            glslFunction += 'value /= 255.0; \n';
        }

        glslFunction +=
            '    return value; \n' +
            '} \n';
        return glslFunction;
    }

    /**
     * Gets a function that will update a vertex shader to contain functions for looking up values in the batch table.
     *
     * @returns {BatchTable~updateVertexShaderSourceCallback} A callback for updating a vertex shader source.
     */
    BatchTable.prototype.getVertexShaderCallback = function() {
        var attributes = this._attributes;
        if (attributes.length === 0) {
            return function(source) {
                return source;
            };
        }

        var batchTableShader = 'uniform sampler2D batchTexture; \n';
        batchTableShader += getGlslComputeSt(this) + '\n';
        batchTableShader += getGlslUnpackFloat(this) + '\n';

        var length = attributes.length;
        for (var i = 0; i < length; ++i) {
            batchTableShader += getGlslAttributeFunction(this, i);
        }

        return function(source) {
            var mainIndex = source.indexOf('void main');
            var beforeMain = source.substring(0, mainIndex);
            var afterMain = source.substring(mainIndex);
            return beforeMain + '\n' + batchTableShader + '\n' + afterMain;
        };
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see BatchTable#destroy
     */
    BatchTable.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see BatchTable#isDestroyed
     */
    BatchTable.prototype.destroy = function() {
        this._texture = this._texture && this._texture.destroy();
        return destroyObject(this);
    };

    /**
     * A callback for updating uniform maps.
     * @callback BatchTable~updateUniformMapCallback
     *
     * @param {Object} uniformMap The uniform map.
     * @returns {Object} The new uniform map with properties for retrieving values from the batch table.
     */

    /**
     * A callback for updating a vertex shader source.
     * @callback BatchTable~updateVertexShaderSourceCallback
     *
     * @param {String} vertexShaderSource The vertex shader source.
     * @returns {String} The new vertex shader source with the functions for retrieving batch table values injected.
     */

    return BatchTable;
});

/*global define*/
define('Scene/PrimitivePipeline',[
        '../Core/BoundingSphere',
        '../Core/ComponentDatatype',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/FeatureDetection',
        '../Core/GeographicProjection',
        '../Core/Geometry',
        '../Core/GeometryAttribute',
        '../Core/GeometryAttributes',
        '../Core/GeometryPipeline',
        '../Core/IndexDatatype',
        '../Core/Matrix4',
        '../Core/WebMercatorProjection'
    ], function(
        BoundingSphere,
        ComponentDatatype,
        defined,
        DeveloperError,
        Ellipsoid,
        FeatureDetection,
        GeographicProjection,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        GeometryPipeline,
        IndexDatatype,
        Matrix4,
        WebMercatorProjection) {
    'use strict';

    // Bail out if the browser doesn't support typed arrays, to prevent the setup function
    // from failing, since we won't be able to create a WebGL context anyway.
    if (!FeatureDetection.supportsTypedArrays()) {
        return {};
    }

    function transformToWorldCoordinates(instances, primitiveModelMatrix, scene3DOnly) {
        var toWorld = !scene3DOnly;
        var length = instances.length;
        var i;

        if (!toWorld && (length > 1)) {
            var modelMatrix = instances[0].modelMatrix;

            for (i = 1; i < length; ++i) {
                if (!Matrix4.equals(modelMatrix, instances[i].modelMatrix)) {
                    toWorld = true;
                    break;
                }
            }
        }

        if (toWorld) {
            for (i = 0; i < length; ++i) {
                if (defined(instances[i].geometry)) {
                    GeometryPipeline.transformToWorldCoordinates(instances[i]);
                }
            }
        } else {
            // Leave geometry in local coordinate system; auto update model-matrix.
            Matrix4.multiplyTransformation(primitiveModelMatrix, instances[0].modelMatrix, primitiveModelMatrix);
        }
    }

    function addGeometryBatchId(geometry, batchId) {
        var attributes = geometry.attributes;
        var positionAttr = attributes.position;
        var numberOfComponents = positionAttr.values.length / positionAttr.componentsPerAttribute;

        attributes.batchId = new GeometryAttribute({
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 1,
            values : new Float32Array(numberOfComponents)
        });

        var values = attributes.batchId.values;
        for (var j = 0; j < numberOfComponents; ++j) {
            values[j] = batchId;
        }
    }

    function addBatchIds(instances) {
        var length = instances.length;

        for (var i = 0; i < length; ++i) {
            var instance = instances[i];
            if (defined(instance.geometry)) {
                addGeometryBatchId(instance.geometry, i);
            } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) {
                addGeometryBatchId(instance.westHemisphereGeometry, i);
                addGeometryBatchId(instance.eastHemisphereGeometry, i);
            }
        }
    }

    function geometryPipeline(parameters) {
        var instances = parameters.instances;
        var projection = parameters.projection;
        var uintIndexSupport = parameters.elementIndexUintSupported;
        var scene3DOnly = parameters.scene3DOnly;
        var vertexCacheOptimize = parameters.vertexCacheOptimize;
        var compressVertices = parameters.compressVertices;
        var modelMatrix = parameters.modelMatrix;

        var i;
        var geometry;
        var primitiveType;
        var length = instances.length;

        for (i = 0 ; i < length; ++i) {
            if (defined(instances[i].geometry)) {
                primitiveType = instances[i].geometry.primitiveType;
                break;
            }
        }

                for (i = 1; i < length; ++i) {
            if (defined(instances[i].geometry) && instances[i].geometry.primitiveType !== primitiveType) {
                throw new DeveloperError('All instance geometries must have the same primitiveType.');
            }
        }
        
        // Unify to world coordinates before combining.
        transformToWorldCoordinates(instances, modelMatrix, scene3DOnly);

        // Clip to IDL
        if (!scene3DOnly) {
            for (i = 0; i < length; ++i) {
                if (defined(instances[i].geometry)) {
                    GeometryPipeline.splitLongitude(instances[i]);
                }
            }
        }

        addBatchIds(instances);

        // Optimize for vertex shader caches
        if (vertexCacheOptimize) {
            for (i = 0; i < length; ++i) {
                var instance = instances[i];
                if (defined(instance.geometry)) {
                    GeometryPipeline.reorderForPostVertexCache(instance.geometry);
                    GeometryPipeline.reorderForPreVertexCache(instance.geometry);
                } else if (defined(instance.westHemisphereGeometry) && defined(instance.eastHemisphereGeometry)) {
                    GeometryPipeline.reorderForPostVertexCache(instance.westHemisphereGeometry);
                    GeometryPipeline.reorderForPreVertexCache(instance.westHemisphereGeometry);

                    GeometryPipeline.reorderForPostVertexCache(instance.eastHemisphereGeometry);
                    GeometryPipeline.reorderForPreVertexCache(instance.eastHemisphereGeometry);
                }
            }
        }

        // Combine into single geometry for better rendering performance.
        var geometries = GeometryPipeline.combineInstances(instances);

        length = geometries.length;
        for (i = 0; i < length; ++i) {
            geometry = geometries[i];

            // Split positions for GPU RTE
            var attributes = geometry.attributes;
            var name;
            if (!scene3DOnly) {
                for (name in attributes) {
                    if (attributes.hasOwnProperty(name) && attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
                        var name3D = name + '3D';
                        var name2D = name + '2D';

                        // Compute 2D positions
                        GeometryPipeline.projectTo2D(geometry, name, name3D, name2D, projection);
                        if (defined(geometry.boundingSphere) && name === 'position') {
                            geometry.boundingSphereCV = BoundingSphere.fromVertices(geometry.attributes.position2D.values);
                        }

                        GeometryPipeline.encodeAttribute(geometry, name3D, name3D + 'High', name3D + 'Low');
                        GeometryPipeline.encodeAttribute(geometry, name2D, name2D + 'High', name2D + 'Low');
                    }
                }
            } else {
                for (name in attributes) {
                    if (attributes.hasOwnProperty(name) && attributes[name].componentDatatype === ComponentDatatype.DOUBLE) {
                        GeometryPipeline.encodeAttribute(geometry, name, name + '3DHigh', name + '3DLow');
                    }
                }
            }

            // oct encode and pack normals, compress texture coordinates
            if (compressVertices) {
                GeometryPipeline.compressVertices(geometry);
            }
        }

        if (!uintIndexSupport) {
            // Break into multiple geometries to fit within unsigned short indices if needed
            var splitGeometries = [];
            length = geometries.length;
            for (i = 0; i < length; ++i) {
                geometry = geometries[i];
                splitGeometries = splitGeometries.concat(GeometryPipeline.fitToUnsignedShortIndices(geometry));
            }

            geometries = splitGeometries;
        }

        return geometries;
    }

    function createPickOffsets(instances, geometryName, geometries, pickOffsets) {
        var offset;
        var indexCount;
        var geometryIndex;

        var offsetIndex = pickOffsets.length - 1;
        if (offsetIndex >= 0) {
            var pickOffset = pickOffsets[offsetIndex];
            offset = pickOffset.offset + pickOffset.count;
            geometryIndex = pickOffset.index;
            indexCount = geometries[geometryIndex].indices.length;
        } else {
            offset = 0;
            geometryIndex = 0;
            indexCount = geometries[geometryIndex].indices.length;
        }

        var length = instances.length;
        for (var i = 0; i < length; ++i) {
            var instance = instances[i];
            var geometry = instance[geometryName];
            if (!defined(geometry)) {
                continue;
            }

            var count = geometry.indices.length;

            if (offset + count > indexCount) {
                offset = 0;
                indexCount = geometries[++geometryIndex].indices.length;
            }

            pickOffsets.push({
                index : geometryIndex,
                offset : offset,
                count : count
            });
            offset += count;
        }
    }

    function createInstancePickOffsets(instances, geometries) {
        var pickOffsets = [];
        createPickOffsets(instances, 'geometry', geometries, pickOffsets);
        createPickOffsets(instances, 'westHemisphereGeometry', geometries, pickOffsets);
        createPickOffsets(instances, 'eastHemisphereGeometry', geometries, pickOffsets);
        return pickOffsets;
    }

    /**
     * @private
     */
    var PrimitivePipeline = {};

    /**
     * @private
     */
    PrimitivePipeline.combineGeometry = function(parameters) {
        var geometries;
        var attributeLocations;
        var instances = parameters.instances;
        var length = instances.length;

        if (length > 0) {
            geometries = geometryPipeline(parameters);
            if (geometries.length > 0) {
                attributeLocations = GeometryPipeline.createAttributeLocations(geometries[0]);
            }
        }

        var pickOffsets;
        if (parameters.createPickOffsets && geometries.length > 0) {
            pickOffsets = createInstancePickOffsets(instances, geometries);
        }

        var boundingSpheres = new Array(length);
        var boundingSpheresCV = new Array(length);
        for (var i = 0; i < length; ++i) {
            var instance = instances[i];
            var geometry = instance.geometry;
            if (defined(geometry)) {
                boundingSpheres[i] = geometry.boundingSphere;
                boundingSpheresCV[i] = geometry.boundingSphereCV;
            }

            var eastHemisphereGeometry = instance.eastHemisphereGeometry;
            var westHemisphereGeometry = instance.westHemisphereGeometry;
            if (defined(eastHemisphereGeometry) && defined(westHemisphereGeometry)) {
                if (defined(eastHemisphereGeometry.boundingSphere) && defined(westHemisphereGeometry.boundingSphere)) {
                    boundingSpheres[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphere, westHemisphereGeometry.boundingSphere);
                }
                if (defined(eastHemisphereGeometry.boundingSphereCV) && defined(westHemisphereGeometry.boundingSphereCV)) {
                    boundingSpheresCV[i] = BoundingSphere.union(eastHemisphereGeometry.boundingSphereCV, westHemisphereGeometry.boundingSphereCV);
                }
            }
        }

        return {
            geometries : geometries,
            modelMatrix : parameters.modelMatrix,
            attributeLocations : attributeLocations,
            pickOffsets : pickOffsets,
            boundingSpheres : boundingSpheres,
            boundingSpheresCV : boundingSpheresCV
        };
    };

    function transferGeometry(geometry, transferableObjects) {
        var attributes = geometry.attributes;
        for ( var name in attributes) {
            if (attributes.hasOwnProperty(name)) {
                var attribute = attributes[name];

                if (defined(attribute) && defined(attribute.values)) {
                    transferableObjects.push(attribute.values.buffer);
                }
            }
        }

        if (defined(geometry.indices)) {
            transferableObjects.push(geometry.indices.buffer);
        }
    }

    function transferGeometries(geometries, transferableObjects) {
        var length = geometries.length;
        for (var i = 0; i < length; ++i) {
            transferGeometry(geometries[i], transferableObjects);
        }
    }

    // This function was created by simplifying packCreateGeometryResults into a count-only operation.
    function countCreateGeometryResults(items) {
        var count = 1;
        var length = items.length;
        for (var i = 0; i < length; i++) {
            var geometry = items[i];
            ++count;

            if (!defined(geometry)) {
                continue;
            }

            var attributes = geometry.attributes;

            count += 6 + 2 * BoundingSphere.packedLength + (defined(geometry.indices) ? geometry.indices.length : 0);

            for ( var property in attributes) {
                if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
                    var attribute = attributes[property];
                    count += 5 + attribute.values.length;
                }
            }
        }

        return count;
    }

    /**
     * @private
     */
    PrimitivePipeline.packCreateGeometryResults = function(items, transferableObjects) {
        var packedData = new Float64Array(countCreateGeometryResults(items));
        var stringTable = [];
        var stringHash = {};

        var length = items.length;
        var count = 0;
        packedData[count++] = length;
        for (var i = 0; i < length; i++) {
            var geometry = items[i];

            var validGeometry = defined(geometry);
            packedData[count++] = validGeometry ? 1.0 : 0.0;

            if (!validGeometry) {
                continue;
            }

            packedData[count++] = geometry.primitiveType;
            packedData[count++] = geometry.geometryType;

            var validBoundingSphere = defined(geometry.boundingSphere) ? 1.0 : 0.0;
            packedData[count++] = validBoundingSphere;
            if (validBoundingSphere) {
                BoundingSphere.pack(geometry.boundingSphere, packedData, count);
            }

            count += BoundingSphere.packedLength;

            var validBoundingSphereCV = defined(geometry.boundingSphereCV) ? 1.0 : 0.0;
            packedData[count++] = validBoundingSphereCV;
            if (validBoundingSphereCV) {
                BoundingSphere.pack(geometry.boundingSphereCV, packedData, count);
            }

            count += BoundingSphere.packedLength;

            var attributes = geometry.attributes;
            var attributesToWrite = [];
            for ( var property in attributes) {
                if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
                    attributesToWrite.push(property);
                    if (!defined(stringHash[property])) {
                        stringHash[property] = stringTable.length;
                        stringTable.push(property);
                    }
                }
            }

            packedData[count++] = attributesToWrite.length;
            for (var q = 0; q < attributesToWrite.length; q++) {
                var name = attributesToWrite[q];
                var attribute = attributes[name];
                packedData[count++] = stringHash[name];
                packedData[count++] = attribute.componentDatatype;
                packedData[count++] = attribute.componentsPerAttribute;
                packedData[count++] = attribute.normalize ? 1 : 0;
                packedData[count++] = attribute.values.length;
                packedData.set(attribute.values, count);
                count += attribute.values.length;
            }

            var indicesLength = defined(geometry.indices) ? geometry.indices.length : 0;
            packedData[count++] = indicesLength;

            if (indicesLength > 0) {
                packedData.set(geometry.indices, count);
                count += indicesLength;
            }
        }

        transferableObjects.push(packedData.buffer);

        return {
            stringTable : stringTable,
            packedData : packedData
        };
    };

    /**
     * @private
     */
    PrimitivePipeline.unpackCreateGeometryResults = function(createGeometryResult) {
        var stringTable = createGeometryResult.stringTable;
        var packedGeometry = createGeometryResult.packedData;

        var i;
        var result = new Array(packedGeometry[0]);
        var resultIndex = 0;

        var packedGeometryIndex = 1;
        while (packedGeometryIndex < packedGeometry.length) {
            var valid = packedGeometry[packedGeometryIndex++] === 1.0;
            if (!valid) {
                result[resultIndex++] = undefined;
                continue;
            }

            var primitiveType = packedGeometry[packedGeometryIndex++];
            var geometryType = packedGeometry[packedGeometryIndex++];

            var boundingSphere;
            var boundingSphereCV;

            var validBoundingSphere = packedGeometry[packedGeometryIndex++] === 1.0;
            if (validBoundingSphere) {
                boundingSphere = BoundingSphere.unpack(packedGeometry, packedGeometryIndex);
            }

            packedGeometryIndex += BoundingSphere.packedLength;

            var validBoundingSphereCV = packedGeometry[packedGeometryIndex++] === 1.0;
            if (validBoundingSphereCV) {
                boundingSphereCV = BoundingSphere.unpack(packedGeometry, packedGeometryIndex);
            }

            packedGeometryIndex += BoundingSphere.packedLength;

            var length;
            var values;
            var componentsPerAttribute;
            var attributes = new GeometryAttributes();
            var numAttributes = packedGeometry[packedGeometryIndex++];
            for (i = 0; i < numAttributes; i++) {
                var name = stringTable[packedGeometry[packedGeometryIndex++]];
                var componentDatatype = packedGeometry[packedGeometryIndex++];
                componentsPerAttribute = packedGeometry[packedGeometryIndex++];
                var normalize = packedGeometry[packedGeometryIndex++] !== 0;

                length = packedGeometry[packedGeometryIndex++];
                values = ComponentDatatype.createTypedArray(componentDatatype, length);
                for (var valuesIndex = 0; valuesIndex < length; valuesIndex++) {
                    values[valuesIndex] = packedGeometry[packedGeometryIndex++];
                }

                attributes[name] = new GeometryAttribute({
                    componentDatatype : componentDatatype,
                    componentsPerAttribute : componentsPerAttribute,
                    normalize : normalize,
                    values : values
                });
            }

            var indices;
            length = packedGeometry[packedGeometryIndex++];

            if (length > 0) {
                var numberOfVertices = values.length / componentsPerAttribute;
                indices = IndexDatatype.createTypedArray(numberOfVertices, length);
                for (i = 0; i < length; i++) {
                    indices[i] = packedGeometry[packedGeometryIndex++];
                }
            }

            result[resultIndex++] = new Geometry({
                primitiveType : primitiveType,
                geometryType : geometryType,
                boundingSphere : boundingSphere,
                boundingSphereCV : boundingSphereCV,
                indices : indices,
                attributes : attributes
            });
        }

        return result;
    };

    function packInstancesForCombine(instances, transferableObjects) {
        var length = instances.length;
        var packedData = new Float64Array(1 + (length * 16));
        var count = 0;
        packedData[count++] = length;
        for (var i = 0; i < length; i++) {
            var instance = instances[i];

            Matrix4.pack(instance.modelMatrix, packedData, count);
            count += Matrix4.packedLength;
        }
        transferableObjects.push(packedData.buffer);

        return packedData;
    }

    function unpackInstancesForCombine(data) {
        var packedInstances = data;
        var result = new Array(packedInstances[0]);
        var count = 0;

        var i = 1;
        while (i < packedInstances.length) {
            var modelMatrix = Matrix4.unpack(packedInstances, i);
            i += Matrix4.packedLength;

            result[count++] = {
                modelMatrix : modelMatrix
            };
        }

        return result;
    }

    /**
     * @private
     */
    PrimitivePipeline.packCombineGeometryParameters = function(parameters, transferableObjects) {
        var createGeometryResults = parameters.createGeometryResults;
        var length = createGeometryResults.length;

        for (var i = 0; i < length; i++) {
            transferableObjects.push(createGeometryResults[i].packedData.buffer);
        }

        return {
            createGeometryResults : parameters.createGeometryResults,
            packedInstances : packInstancesForCombine(parameters.instances, transferableObjects),
            ellipsoid : parameters.ellipsoid,
            isGeographic : parameters.projection instanceof GeographicProjection,
            elementIndexUintSupported : parameters.elementIndexUintSupported,
            scene3DOnly : parameters.scene3DOnly,
            vertexCacheOptimize : parameters.vertexCacheOptimize,
            compressVertices : parameters.compressVertices,
            modelMatrix : parameters.modelMatrix,
            createPickOffsets : parameters.createPickOffsets
        };
    };

    /**
     * @private
     */
    PrimitivePipeline.unpackCombineGeometryParameters = function(packedParameters) {
        var instances = unpackInstancesForCombine(packedParameters.packedInstances);
        var createGeometryResults = packedParameters.createGeometryResults;
        var length = createGeometryResults.length;
        var instanceIndex = 0;

        for (var resultIndex = 0; resultIndex < length; resultIndex++) {
            var geometries = PrimitivePipeline.unpackCreateGeometryResults(createGeometryResults[resultIndex]);
            var geometriesLength = geometries.length;
            for (var geometryIndex = 0; geometryIndex < geometriesLength; geometryIndex++) {
                var geometry = geometries[geometryIndex];
                var instance = instances[instanceIndex];
                instance.geometry = geometry;
                ++instanceIndex;
            }
        }

        var ellipsoid = Ellipsoid.clone(packedParameters.ellipsoid);
        var projection = packedParameters.isGeographic ? new GeographicProjection(ellipsoid) : new WebMercatorProjection(ellipsoid);

        return {
            instances : instances,
            ellipsoid : ellipsoid,
            projection : projection,
            elementIndexUintSupported : packedParameters.elementIndexUintSupported,
            scene3DOnly : packedParameters.scene3DOnly,
            vertexCacheOptimize : packedParameters.vertexCacheOptimize,
            compressVertices : packedParameters.compressVertices,
            modelMatrix : Matrix4.clone(packedParameters.modelMatrix),
            createPickOffsets : packedParameters.createPickOffsets
        };
    };

    function packBoundingSpheres(boundingSpheres) {
        var length = boundingSpheres.length;
        var bufferLength = 1 + (BoundingSphere.packedLength + 1) * length;
        var buffer = new Float32Array(bufferLength);

        var bufferIndex = 0;
        buffer[bufferIndex++] = length;

        for (var i = 0; i < length; ++i) {
            var bs = boundingSpheres[i];
            if (!defined(bs)) {
                buffer[bufferIndex++] = 0.0;
            } else {
                buffer[bufferIndex++] = 1.0;
                BoundingSphere.pack(boundingSpheres[i], buffer, bufferIndex);
            }
            bufferIndex += BoundingSphere.packedLength;
        }

        return buffer;
    }

    function unpackBoundingSpheres(buffer) {
        var result = new Array(buffer[0]);
        var count = 0;

        var i = 1;
        while (i < buffer.length) {
            if (buffer[i++] === 1.0) {
                result[count] = BoundingSphere.unpack(buffer, i);
            }
            ++count;
            i += BoundingSphere.packedLength;
        }

        return result;
    }

    /**
     * @private
     */
    PrimitivePipeline.packCombineGeometryResults = function(results, transferableObjects) {
        if (defined(results.geometries)) {
            transferGeometries(results.geometries, transferableObjects);
        }

        var packedBoundingSpheres = packBoundingSpheres(results.boundingSpheres);
        var packedBoundingSpheresCV = packBoundingSpheres(results.boundingSpheresCV);
        transferableObjects.push(packedBoundingSpheres.buffer, packedBoundingSpheresCV.buffer);

        return {
            geometries : results.geometries,
            attributeLocations : results.attributeLocations,
            modelMatrix : results.modelMatrix,
            pickOffsets : results.pickOffsets,
            boundingSpheres : packedBoundingSpheres,
            boundingSpheresCV : packedBoundingSpheresCV
        };
    };

    /**
     * @private
     */
    PrimitivePipeline.unpackCombineGeometryResults = function(packedResult) {
        return {
            geometries : packedResult.geometries,
            attributeLocations : packedResult.attributeLocations,
            modelMatrix : packedResult.modelMatrix,
            pickOffsets : packedResult.pickOffsets,
            boundingSpheres : unpackBoundingSpheres(packedResult.boundingSpheres),
            boundingSpheresCV : unpackBoundingSpheres(packedResult.boundingSpheresCV)
        };
    };

    return PrimitivePipeline;
});

/*global define*/
define('Scene/PrimitiveState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * @private
     */
    var PrimitiveState = {
        READY : 0,
        CREATING : 1,
        CREATED : 2,
        COMBINING : 3,
        COMBINED : 4,
        COMPLETE : 5,
        FAILED : 6
    };

    return freezeObject(PrimitiveState);
});

/*global define*/
define('Scene/SceneMode',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Indicates if the scene is viewed in 3D, 2D, or 2.5D Columbus view.
     *
     * @exports SceneMode
     *
     * @see Scene#mode
     */
    var SceneMode = {
        /**
         * Morphing between mode, e.g., 3D to 2D.
         *
         * @type {Number}
         * @constant
         */
        MORPHING : 0,

        /**
         * Columbus View mode.  A 2.5D perspective view where the map is laid out
         * flat and objects with non-zero height are drawn above it.
         *
         * @type {Number}
         * @constant
         */
        COLUMBUS_VIEW : 1,

        /**
         * 2D mode.  The map is viewed top-down with an orthographic projection.
         *
         * @type {Number}
         * @constant
         */
        SCENE2D : 2,

        /**
         * 3D mode.  A traditional 3D perspective view of the globe.
         *
         * @type {Number}
         * @constant
         */
        SCENE3D : 3
    };

    /**
     * Returns the morph time for the given scene mode.
     *
     * @param {SceneMode} value The scene mode
     * @returns {Number} The morph time
     */
    SceneMode.getMorphTime = function(value) {
        if (value === SceneMode.SCENE3D) {
            return 1.0;
        } else if (value === SceneMode.MORPHING) {
            return undefined;
        }
        return 0.0;
    };

    return freezeObject(SceneMode);
});

/*global define*/
define('Scene/ShadowMode',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Specifies whether the object casts or receives shadows from each light source when
     * shadows are enabled.
     *
     * @exports ShadowMode
     */
    var ShadowMode = {
        /**
         * The object does not cast or receive shadows.
         *
         * @type {Number}
         * @constant
         */
        DISABLED : 0,

        /**
         * The object casts and receives shadows.
         *
         * @type {Number}
         * @constant
         */
        ENABLED : 1,

        /**
         * The object casts shadows only.
         *
         * @type {Number}
         * @constant
         */
        CAST_ONLY : 2,

        /**
         * The object receives shadows only.
         *
         * @type {Number}
         * @constant
         */
        RECEIVE_ONLY : 3,

        /**
         * @private
         */
        NUMBER_OF_SHADOW_MODES : 4
    };

    /**
     * @private
     */
    ShadowMode.castShadows = function(shadowMode) {
        return (shadowMode === ShadowMode.ENABLED) || (shadowMode === ShadowMode.CAST_ONLY);
    };

    /**
     * @private
     */
    ShadowMode.receiveShadows = function(shadowMode) {
        return (shadowMode === ShadowMode.ENABLED) || (shadowMode === ShadowMode.RECEIVE_ONLY);
    };

    /**
     * @private
     */
    ShadowMode.fromCastReceive = function(castShadows, receiveShadows) {
        if (castShadows && receiveShadows) {
            return ShadowMode.ENABLED;
        } else if (castShadows) {
            return ShadowMode.CAST_ONLY;
        } else if (receiveShadows) {
            return ShadowMode.RECEIVE_ONLY;
        } else {
            return ShadowMode.DISABLED;
        }
    };

    return freezeObject(ShadowMode);
});

/*global define*/
define('Scene/Primitive',[
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/clone',
        '../Core/Color',
        '../Core/combine',
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EncodedCartesian3',
        '../Core/FeatureDetection',
        '../Core/Geometry',
        '../Core/GeometryAttribute',
        '../Core/GeometryAttributes',
        '../Core/isArray',
        '../Core/Matrix4',
        '../Core/RuntimeError',
        '../Core/subdivideArray',
        '../Core/TaskProcessor',
        '../Renderer/BufferUsage',
        '../Renderer/ContextLimits',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArray',
        '../ThirdParty/when',
        './BatchTable',
        './CullFace',
        './PrimitivePipeline',
        './PrimitiveState',
        './SceneMode',
        './ShadowMode'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        clone,
        Color,
        combine,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EncodedCartesian3,
        FeatureDetection,
        Geometry,
        GeometryAttribute,
        GeometryAttributes,
        isArray,
        Matrix4,
        RuntimeError,
        subdivideArray,
        TaskProcessor,
        BufferUsage,
        ContextLimits,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArray,
        when,
        BatchTable,
        CullFace,
        PrimitivePipeline,
        PrimitiveState,
        SceneMode,
        ShadowMode) {
    'use strict';

    /**
     * A primitive represents geometry in the {@link Scene}.  The geometry can be from a single {@link GeometryInstance}
     * as shown in example 1 below, or from an array of instances, even if the geometry is from different
     * geometry types, e.g., an {@link RectangleGeometry} and an {@link EllipsoidGeometry} as shown in Code Example 2.
     * <p>
     * A primitive combines geometry instances with an {@link Appearance} that describes the full shading, including
     * {@link Material} and {@link RenderState}.  Roughly, the geometry instance defines the structure and placement,
     * and the appearance defines the visual characteristics.  Decoupling geometry and appearance allows us to mix
     * and match most of them and add a new geometry or appearance independently of each other.
     * </p>
     * <p>
     * Combining multiple instances into one primitive is called batching, and significantly improves performance for static data.
     * Instances can be individually picked; {@link Scene#pick} returns their {@link GeometryInstance#id}.  Using
     * per-instance appearances like {@link PerInstanceColorAppearance}, each instance can also have a unique color.
     * </p>
     * <p>
     * {@link Geometry} can either be created and batched on a web worker or the main thread. The first two examples
     * show geometry that will be created on a web worker by using the descriptions of the geometry. The third example
     * shows how to create the geometry on the main thread by explicitly calling the <code>createGeometry</code> method.
     * </p>
     *
     * @alias Primitive
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {GeometryInstance[]|GeometryInstance} [options.geometryInstances] The geometry instances - or a single geometry instance - to render.
     * @param {Appearance} [options.appearance] The appearance used to render the primitive.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates.
     * @param {Boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
     * @param {Boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
     * @param {Boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory.
     * @param {Boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
     * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
     * @param {Boolean} [options.cull=true] When <code>true</code>, the renderer frustum culls and horizon culls the primitive's commands based on their bounding volume.  Set this to <code>false</code> for a small performance gain if you are manually culling the primitive.
     * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {ShadowMode} [options.shadows=ShadowMode.DISABLED] Determines whether this primitive casts or receives shadows from each light source.
     *
     * @example
     * // 1. Draw a translucent ellipse on the surface with a checkerboard pattern
     * var instance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.EllipseGeometry({
     *       center : Cesium.Cartesian3.fromDegrees(-100.0, 20.0),
     *       semiMinorAxis : 500000.0,
     *       semiMajorAxis : 1000000.0,
     *       rotation : Cesium.Math.PI_OVER_FOUR,
     *       vertexFormat : Cesium.VertexFormat.POSITION_AND_ST
     *   }),
     *   id : 'object returned when this instance is picked and to get/set per-instance attributes'
     * });
     * scene.primitives.add(new Cesium.Primitive({
     *   geometryInstances : instance,
     *   appearance : new Cesium.EllipsoidSurfaceAppearance({
     *     material : Cesium.Material.fromType('Checkerboard')
     *   })
     * }));
     *
     * @example
     * // 2. Draw different instances each with a unique color
     * var rectangleInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.RectangleGeometry({
     *     rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0),
     *     vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
     *   }),
     *   id : 'rectangle',
     *   attributes : {
     *     color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
     *   }
     * });
     * var ellipsoidInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.EllipsoidGeometry({
     *     radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0),
     *     vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL
     *   }),
     *   modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *     Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()),
     *   id : 'ellipsoid',
     *   attributes : {
     *     color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
     *   }
     * });
     * scene.primitives.add(new Cesium.Primitive({
     *   geometryInstances : [rectangleInstance, ellipsoidInstance],
     *   appearance : new Cesium.PerInstanceColorAppearance()
     * }));
     *
     * @example
     * // 3. Create the geometry on the main thread.
     * scene.primitives.add(new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *       geometry : Cesium.EllipsoidGeometry.createGeometry(new Cesium.EllipsoidGeometry({
     *         radii : new Cesium.Cartesian3(500000.0, 500000.0, 1000000.0),
     *         vertexFormat : Cesium.VertexFormat.POSITION_AND_NORMAL
     *       })),
     *       modelMatrix : Cesium.Matrix4.multiplyByTranslation(Cesium.Transforms.eastNorthUpToFixedFrame(
     *         Cesium.Cartesian3.fromDegrees(-95.59777, 40.03883)), new Cesium.Cartesian3(0.0, 0.0, 500000.0), new Cesium.Matrix4()),
     *       id : 'ellipsoid',
     *       attributes : {
     *         color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.AQUA)
     *       }
     *   }),
     *   appearance : new Cesium.PerInstanceColorAppearance()
     * }));
     *
     * @see GeometryInstance
     * @see Appearance
     */
    function Primitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The geometry instances rendered with this primitive.  This may
         * be <code>undefined</code> if <code>options.releaseGeometryInstances</code>
         * is <code>true</code> when the primitive is constructed.
         * <p>
         * Changing this property after the primitive is rendered has no effect.
         * </p>
         *
         * @type GeometryInstance[]|GeometryInstance
         *
         * @default undefined
         */
        this.geometryInstances = options.geometryInstances;

        /**
         * The {@link Appearance} used to shade this primitive.  Each geometry
         * instance is shaded with the same appearance.  Some appearances, like
         * {@link PerInstanceColorAppearance} allow giving each instance unique
         * properties.
         *
         * @type Appearance
         *
         * @default undefined
         */
        this.appearance = options.appearance;
        this._appearance = undefined;
        this._material = undefined;

        /**
         * The 4x4 transformation matrix that transforms the primitive (all geometry instances) from model to world coordinates.
         * When this is the identity matrix, the primitive is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * <p>
         * This property is only supported in 3D mode.
         * </p>
         *
         * @type Matrix4
         *
         * @default Matrix4.IDENTITY
         *
         * @example
         * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
         * p.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = new Matrix4();

        /**
         * Determines if the primitive will be shown.  This affects all geometry
         * instances in the primitive.
         *
         * @type Boolean
         *
         * @default true
         */
        this.show = defaultValue(options.show, true);

        this._vertexCacheOptimize = defaultValue(options.vertexCacheOptimize, false);
        this._interleave = defaultValue(options.interleave, false);
        this._releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true);
        this._allowPicking = defaultValue(options.allowPicking, true);
        this._asynchronous = defaultValue(options.asynchronous, true);
        this._compressVertices = defaultValue(options.compressVertices, true);

        /**
         * When <code>true</code>, the renderer frustum culls and horizon culls the primitive's commands
         * based on their bounding volume.  Set this to <code>false</code> for a small performance gain
         * if you are manually culling the primitive.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.cull = defaultValue(options.cull, true);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * @private
         */
        this.rtcCenter = options.rtcCenter;

                if (defined(this.rtcCenter) && (!defined(this.geometryInstances) || (isArray(this.geometryInstances) && this.geometryInstances !== 1))) {
            throw new DeveloperError('Relative-to-center rendering only supports one geometry instance.');
        }
        
        /**
         * Determines whether this primitive casts or receives shadows from each light source.
         *
         * @type {ShadowMode}
         *
         * @default ShadowMode.DISABLED
         */
        this.shadows = defaultValue(options.shadows, ShadowMode.DISABLED);

        this._translucent = undefined;

        this._state = PrimitiveState.READY;
        this._geometries = [];
        this._error = undefined;
        this._numberOfInstances = 0;

        this._boundingSpheres = [];
        this._boundingSphereWC = [];
        this._boundingSphereCV = [];
        this._boundingSphere2D = [];
        this._boundingSphereMorph = [];
        this._perInstanceAttributeCache = [];
        this._instanceIds = [];
        this._lastPerInstanceAttributeIndex = 0;

        this._va = [];
        this._attributeLocations = undefined;
        this._primitiveType = undefined;

        this._frontFaceRS = undefined;
        this._backFaceRS = undefined;
        this._sp = undefined;

        this._pickRS = undefined;
        this._pickSP = undefined;
        this._pickIds = [];

        this._colorCommands = [];
        this._pickCommands = [];

        this._readOnlyInstanceAttributes = options._readOnlyInstanceAttributes;

        this._createBoundingVolumeFunction = options._createBoundingVolumeFunction;
        this._createRenderStatesFunction = options._createRenderStatesFunction;
        this._createShaderProgramFunction = options._createShaderProgramFunction;
        this._createCommandsFunction = options._createCommandsFunction;
        this._updateAndQueueCommandsFunction = options._updateAndQueueCommandsFunction;

        this._createPickOffsets = options._createPickOffsets;
        this._pickOffsets = undefined;

        this._createGeometryResults = undefined;
        this._ready = false;
        this._readyPromise = when.defer();

        this._batchTable = undefined;
        this._batchTableAttributeIndices = undefined;
        this._instanceBoundingSpheres = undefined;
        this._instanceBoundingSpheresCV = undefined;
        this._batchTableBoundingSpheresUpdated = false;
        this._batchTableBoundingSphereAttributeIndices = undefined;
    }

    defineProperties(Primitive.prototype, {
        /**
         * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        vertexCacheOptimize : {
            get : function() {
                return this._vertexCacheOptimize;
            }
        },

        /**
         * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        interleave : {
            get : function() {
                return this._interleave;
            }
        },

        /**
         * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        releaseGeometryInstances : {
            get : function() {
                return this._releaseGeometryInstances;
            }
        },

        /**
         * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.         *
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        allowPicking : {
            get : function() {
                return this._allowPicking;
            }
        },

        /**
         * Determines if the geometry instances will be created and batched on a web worker.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        asynchronous : {
            get : function() {
                return this._asynchronous;
            }
        },

        /**
         * When <code>true</code>, geometry vertices are compressed, which will save memory.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        compressVertices : {
            get : function() {
                return this._compressVertices;
            }
        },

        /**
         * Determines if the primitive is complete and ready to render.  If this property is
         * true, the primitive will be rendered the next time that {@link Primitive#update}
         * is called.
         *
         * @memberof Primitive.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves when the primitive is ready to render.
         * @memberof Primitive.prototype
         * @type {Promise.<Primitive>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        }
    });

    function getCommonPerInstanceAttributeNames(instances) {
        var length = instances.length;

        var attributesInAllInstances = [];
        var attributes0 = instances[0].attributes;
        var name;

        for (name in attributes0) {
            if (attributes0.hasOwnProperty(name)) {
                var attribute = attributes0[name];
                var inAllInstances = true;

                // Does this same attribute exist in all instances?
                for (var i = 1; i < length; ++i) {
                    var otherAttribute = instances[i].attributes[name];

                    if (!defined(otherAttribute) ||
                        (attribute.componentDatatype !== otherAttribute.componentDatatype) ||
                        (attribute.componentsPerAttribute !== otherAttribute.componentsPerAttribute) ||
                        (attribute.normalize !== otherAttribute.normalize)) {

                        inAllInstances = false;
                        break;
                    }
                }

                if (inAllInstances) {
                    attributesInAllInstances.push(name);
                }
            }
        }

        return attributesInAllInstances;
    }

    var scratchGetAttributeCartesian2 = new Cartesian2();
    var scratchGetAttributeCartesian3 = new Cartesian3();
    var scratchGetAttributeCartesian4 = new Cartesian4();

    function getAttributeValue(value) {
        var componentsPerAttribute = value.length;
        if (componentsPerAttribute === 1) {
            return value[0];
        } else if (componentsPerAttribute === 2) {
            return Cartesian2.unpack(value, 0, scratchGetAttributeCartesian2);
        } else if (componentsPerAttribute === 3) {
            return Cartesian3.unpack(value, 0, scratchGetAttributeCartesian3);
        } else if (componentsPerAttribute === 4) {
            return Cartesian4.unpack(value, 0, scratchGetAttributeCartesian4);
        }
    }

    function createBatchTable(primitive, context) {
        var geometryInstances = primitive.geometryInstances;
        var instances = (isArray(geometryInstances)) ? geometryInstances : [geometryInstances];
        var numberOfInstances = instances.length;
        if (numberOfInstances === 0) {
            return;
        }

        var names = getCommonPerInstanceAttributeNames(instances);
        var length = names.length;

        var allowPicking = primitive.allowPicking;
        var attributes = [];
        var attributeIndices = {};
        var boundingSphereAttributeIndices = {};

        var firstInstance = instances[0];
        var instanceAttributes = firstInstance.attributes;

        var i;
        var name;
        var attribute;

        for (i = 0; i < length; ++i) {
            name = names[i];
            attribute = instanceAttributes[name];

            attributeIndices[name] = i;
            attributes.push({
                functionName : 'czm_batchTable_' + name,
                componentDatatype : attribute.componentDatatype,
                componentsPerAttribute : attribute.componentsPerAttribute,
                normalize : attribute.normalize
            });
        }

        if (names.indexOf('distanceDisplayCondition') !== -1) {
            attributes.push({
                functionName : 'czm_batchTable_boundingSphereCenter3DHigh',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            },{
                functionName : 'czm_batchTable_boundingSphereCenter3DLow',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            },{
                functionName : 'czm_batchTable_boundingSphereCenter2DHigh',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            },{
                functionName : 'czm_batchTable_boundingSphereCenter2DLow',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 3
            },{
                functionName : 'czm_batchTable_boundingSphereRadius',
                componentDatatype : ComponentDatatype.FLOAT,
                componentsPerAttribute : 1
            });
            boundingSphereAttributeIndices.center3DHigh = attributes.length - 5;
            boundingSphereAttributeIndices.center3DLow = attributes.length - 4;
            boundingSphereAttributeIndices.center2DHigh = attributes.length - 3;
            boundingSphereAttributeIndices.center2DLow = attributes.length - 2;
            boundingSphereAttributeIndices.radius = attributes.length - 1;
        }

        if (allowPicking) {
            attributes.push({
                functionName : 'czm_batchTable_pickColor',
                componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
                componentsPerAttribute : 4,
                normalize : true
            });
        }

        var attributesLength = attributes.length;
        var batchTable = new BatchTable(context, attributes, numberOfInstances);

        for (i = 0; i < numberOfInstances; ++i) {
            var instance = instances[i];
            instanceAttributes = instance.attributes;

            for (var j = 0; j < length; ++j) {
                name = names[j];
                attribute = instanceAttributes[name];
                var value = getAttributeValue(attribute.value);
                var attributeIndex = attributeIndices[name];
                batchTable.setBatchedAttribute(i, attributeIndex, value);
            }

            if (allowPicking) {
                var pickObject = {
                    primitive : defaultValue(instance.pickPrimitive, primitive)
                };

                if (defined(instance.id)) {
                    pickObject.id = instance.id;
                }

                var pickId = context.createPickId(pickObject);
                primitive._pickIds.push(pickId);

                var pickColor = pickId.color;
                var color = scratchGetAttributeCartesian4;
                color.x = Color.floatToByte(pickColor.red);
                color.y = Color.floatToByte(pickColor.green);
                color.z = Color.floatToByte(pickColor.blue);
                color.w = Color.floatToByte(pickColor.alpha);

                batchTable.setBatchedAttribute(i, attributesLength - 1, color);
            }
        }

        primitive._batchTable = batchTable;
        primitive._batchTableAttributeIndices = attributeIndices;
        primitive._batchTableBoundingSphereAttributeIndices = boundingSphereAttributeIndices;
    }

    function cloneAttribute(attribute) {
        var clonedValues;
        if (isArray(attribute.values)) {
            clonedValues = attribute.values.slice(0);
        } else {
            clonedValues = new attribute.values.constructor(attribute.values);
        }
        return new GeometryAttribute({
            componentDatatype : attribute.componentDatatype,
            componentsPerAttribute : attribute.componentsPerAttribute,
            normalize : attribute.normalize,
            values : clonedValues
        });
    }

    function cloneGeometry(geometry) {
        var attributes = geometry.attributes;
        var newAttributes = new GeometryAttributes();
        for (var property in attributes) {
            if (attributes.hasOwnProperty(property) && defined(attributes[property])) {
                newAttributes[property] = cloneAttribute(attributes[property]);
            }
        }

        var indices;
        if (defined(geometry.indices)) {
            var sourceValues = geometry.indices;
            if (isArray(sourceValues)) {
                indices = sourceValues.slice(0);
            } else {
                indices = new sourceValues.constructor(sourceValues);
            }
        }

        return new Geometry({
            attributes : newAttributes,
            indices : indices,
            primitiveType : geometry.primitiveType,
            boundingSphere : BoundingSphere.clone(geometry.boundingSphere)
        });
    }

    function cloneInstance(instance, geometry) {
        return {
            geometry : geometry,
            modelMatrix : Matrix4.clone(instance.modelMatrix),
            pickPrimitive : instance.pickPrimitive,
            id : instance.id
        };
    }

    var positionRegex = /attribute\s+vec(?:3|4)\s+(.*)3DHigh;/g;

    Primitive._modifyShaderPosition = function(primitive, vertexShaderSource, scene3DOnly) {
        var match;

        var forwardDecl = '';
        var attributes = '';
        var computeFunctions = '';

        while ((match = positionRegex.exec(vertexShaderSource)) !== null) {
            var name = match[1];

            var functionName = 'vec4 czm_compute' + name[0].toUpperCase() + name.substr(1) + '()';

            // Don't forward-declare czm_computePosition because computePosition.glsl already does.
            if (functionName !== 'vec4 czm_computePosition()') {
                forwardDecl += functionName + ';\n';
            }

            if (!defined(primitive.rtcCenter)) {
                // Use GPU RTE
                if (!scene3DOnly) {
                    attributes +=
                        'attribute vec3 ' + name + '2DHigh;\n' +
                        'attribute vec3 ' + name + '2DLow;\n';

                    computeFunctions +=
                        functionName + '\n' +
                        '{\n' +
                        '    vec4 p;\n' +
                        '    if (czm_morphTime == 1.0)\n' +
                        '    {\n' +
                        '        p = czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' +
                        '    }\n' +
                        '    else if (czm_morphTime == 0.0)\n' +
                        '    {\n' +
                        '        p = czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy);\n' +
                        '    }\n' +
                        '    else\n' +
                        '    {\n' +
                        '        p = czm_columbusViewMorph(\n' +
                        '                czm_translateRelativeToEye(' + name + '2DHigh.zxy, ' + name + '2DLow.zxy),\n' +
                        '                czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow),\n' +
                        '                czm_morphTime);\n' +
                        '    }\n' +
                        '    return p;\n' +
                        '}\n\n';
                } else {
                    computeFunctions +=
                        functionName + '\n' +
                        '{\n' +
                        '    return czm_translateRelativeToEye(' + name + '3DHigh, ' + name + '3DLow);\n' +
                        '}\n\n';
                }
            } else {
                // Use RTC
                vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DHigh;/g, '');
                vertexShaderSource = vertexShaderSource.replace(/attribute\s+vec(?:3|4)\s+position3DLow;/g, '');

                forwardDecl += 'uniform mat4 u_modifiedModelView;\n';
                attributes += 'attribute vec4 position;\n';

                computeFunctions +=
                    functionName + '\n' +
                    '{\n' +
                    '    return u_modifiedModelView * position;\n' +
                    '}\n\n';


                vertexShaderSource = vertexShaderSource.replace(/czm_modelViewRelativeToEye\s+\*\s+/g, '');
                vertexShaderSource = vertexShaderSource.replace(/czm_modelViewProjectionRelativeToEye/g, 'czm_projection');
            }
        }

        return [forwardDecl, attributes, vertexShaderSource, computeFunctions].join('\n');
    };

    Primitive._appendShowToShader = function(primitive, vertexShaderSource) {
        if (!defined(primitive._batchTableAttributeIndices.show)) {
            return vertexShaderSource;
        }

        var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_show_main');
        var showMain =
            'void main() \n' +
            '{ \n' +
            '    czm_non_show_main(); \n' +
            '    gl_Position *= czm_batchTable_show(batchId); \n' +
            '}';

        return renamedVS + '\n' + showMain;
    };

    Primitive._updateColorAttribute = function(primitive, vertexShaderSource) {
        // some appearances have a color attribute for per vertex color.
        // only remove if color is a per instance attribute.
        if (!defined(primitive._batchTableAttributeIndices.color)) {
            return vertexShaderSource;
        }

        if (vertexShaderSource.search(/attribute\s+vec4\s+color;/g) === -1) {
            return vertexShaderSource;
        }

        var modifiedVS = vertexShaderSource;
        modifiedVS = modifiedVS.replace(/attribute\s+vec4\s+color;/g, '');
        modifiedVS = modifiedVS.replace(/(\b)color(\b)/g, '$1czm_batchTable_color(batchId)$2');
        return modifiedVS;
    };

    Primitive._updatePickColorAttribute = function(source) {
        var vsPick = source.replace(/attribute\s+vec4\s+pickColor;/g, '');
        vsPick = vsPick.replace(/(\b)pickColor(\b)/g, '$1czm_batchTable_pickColor(batchId)$2');
        return vsPick;
    };

    Primitive._appendDistanceDisplayConditionToShader = function(primitive, vertexShaderSource, scene3DOnly) {
        if (!defined(primitive._batchTableAttributeIndices.distanceDisplayCondition)) {
            return vertexShaderSource;
        }

        var renamedVS = ShaderSource.replaceMain(vertexShaderSource, 'czm_non_distanceDisplayCondition_main');
        var distanceDisplayConditionMain =
            'void main() \n' +
            '{ \n' +
            '    czm_non_distanceDisplayCondition_main(); \n' +
            '    vec2 distanceDisplayCondition = czm_batchTable_distanceDisplayCondition(batchId);\n' +
            '    vec3 boundingSphereCenter3DHigh = czm_batchTable_boundingSphereCenter3DHigh(batchId);\n' +
            '    vec3 boundingSphereCenter3DLow = czm_batchTable_boundingSphereCenter3DLow(batchId);\n' +
            '    vec3 boundingSphereCenter2DHigh = czm_batchTable_boundingSphereCenter2DHigh(batchId);\n' +
            '    vec3 boundingSphereCenter2DLow = czm_batchTable_boundingSphereCenter2DLow(batchId);\n' +
            '    float boundingSphereRadius = czm_batchTable_boundingSphereRadius(batchId);\n';

        if (!scene3DOnly) {
            distanceDisplayConditionMain +=
                '    vec4 centerRTE;\n' +
                '    if (czm_morphTime == 1.0)\n' +
                '    {\n' +
                '        centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n' +
                '    }\n' +
                '    else if (czm_morphTime == 0.0)\n' +
                '    {\n' +
                '        centerRTE = czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy);\n' +
                '    }\n' +
                '    else\n' +
                '    {\n' +
                '        centerRTE = czm_columbusViewMorph(\n' +
                '                czm_translateRelativeToEye(boundingSphereCenter2DHigh.zxy, boundingSphereCenter2DLow.zxy),\n' +
                '                czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow),\n' +
                '                czm_morphTime);\n' +
                '    }\n';
        } else {
            distanceDisplayConditionMain +=
                '    vec4 centerRTE = czm_translateRelativeToEye(boundingSphereCenter3DHigh, boundingSphereCenter3DLow);\n';
        }

        distanceDisplayConditionMain +=
            '    float radiusSq = boundingSphereRadius * boundingSphereRadius; \n' +
            '    float distanceSq; \n' +
            '    if (czm_sceneMode == czm_sceneMode2D) \n' +
            '    { \n' +
            '        distanceSq = czm_eyeHeight2D.y - radiusSq; \n' +
            '    } \n' +
            '    else \n' +
            '    { \n' +
            '        distanceSq = dot(centerRTE.xyz, centerRTE.xyz) - radiusSq; \n' +
            '    } \n' +
            '    distanceSq = max(distanceSq, 0.0); \n' +
            '    float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x; \n' +
            '    float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y; \n' +
            '    float show = (distanceSq >= nearSq && distanceSq <= farSq) ? 1.0 : 0.0; \n' +
            '    gl_Position *= show; \n' +
            '}';
        return renamedVS + '\n' + distanceDisplayConditionMain;
    };

    function modifyForEncodedNormals(primitive, vertexShaderSource) {
        if (!primitive.compressVertices) {
            return vertexShaderSource;
        }

        var containsNormal = vertexShaderSource.search(/attribute\s+vec3\s+normal;/g) !== -1;
        var containsSt = vertexShaderSource.search(/attribute\s+vec2\s+st;/g) !== -1;
        if (!containsNormal && !containsSt) {
            return vertexShaderSource;
        }

        var containsTangent = vertexShaderSource.search(/attribute\s+vec3\s+tangent;/g) !== -1;
        var containsBitangent = vertexShaderSource.search(/attribute\s+vec3\s+bitangent;/g) !== -1;

        var numComponents = containsSt && containsNormal ? 2.0 : 1.0;
        numComponents += containsTangent || containsBitangent ? 1 : 0;

        var type = (numComponents > 1) ? 'vec' + numComponents : 'float';

        var attributeName = 'compressedAttributes';
        var attributeDecl = 'attribute ' + type + ' ' + attributeName + ';';

        var globalDecl = '';
        var decode = '';

        if (containsSt) {
            globalDecl += 'vec2 st;\n';
            var stComponent = numComponents > 1 ? attributeName + '.x' : attributeName;
            decode += '    st = czm_decompressTextureCoordinates(' + stComponent + ');\n';
        }

        if (containsNormal && containsTangent && containsBitangent) {
            globalDecl +=
                'vec3 normal;\n' +
                'vec3 tangent;\n' +
                'vec3 bitangent;\n';
            decode += '    czm_octDecode(' + attributeName + '.' + (containsSt ? 'yz' : 'xy') + ', normal, tangent, bitangent);\n';
        } else {
            if (containsNormal) {
                globalDecl += 'vec3 normal;\n';
                decode += '    normal = czm_octDecode(' + attributeName + (numComponents > 1 ? '.' + (containsSt ? 'y' : 'x') : '') + ');\n';
            }

            if (containsTangent) {
                globalDecl += 'vec3 tangent;\n';
                decode += '    tangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n';
            }

            if (containsBitangent) {
                globalDecl += 'vec3 bitangent;\n';
                decode += '    bitangent = czm_octDecode(' + attributeName + '.' + (containsSt && containsNormal ? 'z' : 'y') + ');\n';
            }
        }

        var modifiedVS = vertexShaderSource;
        modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, '');
        modifiedVS = modifiedVS.replace(/attribute\s+vec2\s+st;/g, '');
        modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+tangent;/g, '');
        modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+bitangent;/g, '');
        modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main');
        var compressedMain =
            'void main() \n' +
            '{ \n' +
            decode +
            '    czm_non_compressed_main(); \n' +
            '}';

        return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n');
    }

    function validateShaderMatching(shaderProgram, attributeLocations) {
        // For a VAO and shader program to be compatible, the VAO must have
        // all active attribute in the shader program.  The VAO may have
        // extra attributes with the only concern being a potential
        // performance hit due to extra memory bandwidth and cache pollution.
        // The shader source could have extra attributes that are not used,
        // but there is no guarantee they will be optimized out.
        //
        // Here, we validate that the VAO has all attributes required
        // to match the shader program.
        var shaderAttributes = shaderProgram.vertexAttributes;

                for (var name in shaderAttributes) {
            if (shaderAttributes.hasOwnProperty(name)) {
                if (!defined(attributeLocations[name])) {
                    throw new DeveloperError('Appearance/Geometry mismatch.  The appearance requires vertex shader attribute input \'' + name +
                        '\', which was not computed as part of the Geometry.  Use the appearance\'s vertexFormat property when constructing the geometry.');
                }
            }
        }
            }

    function getUniformFunction(uniforms, name) {
        return function() {
            return uniforms[name];
        };
    }

    var numberOfCreationWorkers = Math.max(FeatureDetection.hardwareConcurrency - 1, 1);
    var createGeometryTaskProcessors;
    var combineGeometryTaskProcessor = new TaskProcessor('combineGeometry', Number.POSITIVE_INFINITY);

    function loadAsynchronous(primitive, frameState) {
        var instances;
        var geometry;
        var i;
        var j;

        var instanceIds = primitive._instanceIds;

        if (primitive._state === PrimitiveState.READY) {
            instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
            var length = primitive._numberOfInstances = instances.length;

            var promises = [];
            var subTasks = [];
            for (i = 0; i < length; ++i) {
                geometry = instances[i].geometry;
                instanceIds.push(instances[i].id);

                                if (!defined(geometry._workerName)) {
                    throw new DeveloperError('_workerName must be defined for asynchronous geometry.');
                }
                
                subTasks.push({
                    moduleName : geometry._workerName,
                    geometry : geometry
                });
            }

            if (!defined(createGeometryTaskProcessors)) {
                createGeometryTaskProcessors = new Array(numberOfCreationWorkers);
                for (i = 0; i < numberOfCreationWorkers; i++) {
                    createGeometryTaskProcessors[i] = new TaskProcessor('createGeometry', Number.POSITIVE_INFINITY);
                }
            }

            var subTask;
            subTasks = subdivideArray(subTasks, numberOfCreationWorkers);

            for (i = 0; i < subTasks.length; i++) {
                var packedLength = 0;
                var workerSubTasks = subTasks[i];
                var workerSubTasksLength = workerSubTasks.length;
                for (j = 0; j < workerSubTasksLength; ++j) {
                    subTask = workerSubTasks[j];
                    geometry = subTask.geometry;
                    if (defined(geometry.constructor.pack)) {
                        subTask.offset = packedLength;
                        packedLength += defaultValue(geometry.constructor.packedLength, geometry.packedLength);
                    }
                }

                var subTaskTransferableObjects;

                if (packedLength > 0) {
                    var array = new Float64Array(packedLength);
                    subTaskTransferableObjects = [array.buffer];

                    for (j = 0; j < workerSubTasksLength; ++j) {
                        subTask = workerSubTasks[j];
                        geometry = subTask.geometry;
                        if (defined(geometry.constructor.pack)) {
                            geometry.constructor.pack(geometry, array, subTask.offset);
                            subTask.geometry = array;
                        }
                    }
                }

                promises.push(createGeometryTaskProcessors[i].scheduleTask({
                    subTasks : subTasks[i]
                }, subTaskTransferableObjects));
            }

            primitive._state = PrimitiveState.CREATING;

            when.all(promises, function(results) {
                primitive._createGeometryResults = results;
                primitive._state = PrimitiveState.CREATED;
            }).otherwise(function(error) {
                setReady(primitive, frameState, PrimitiveState.FAILED, error);
            });
        } else if (primitive._state === PrimitiveState.CREATED) {
            var transferableObjects = [];
            instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];

            var scene3DOnly = frameState.scene3DOnly;
            var projection = frameState.mapProjection;

            var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({
                createGeometryResults : primitive._createGeometryResults,
                instances : instances,
                ellipsoid : projection.ellipsoid,
                projection : projection,
                elementIndexUintSupported : frameState.context.elementIndexUint,
                scene3DOnly : scene3DOnly,
                vertexCacheOptimize : primitive.vertexCacheOptimize,
                compressVertices : primitive.compressVertices,
                modelMatrix : primitive.modelMatrix,
                createPickOffsets : primitive._createPickOffsets
            }, transferableObjects), transferableObjects);

            primitive._createGeometryResults = undefined;
            primitive._state = PrimitiveState.COMBINING;

            when(promise, function(packedResult) {
                var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult);
                primitive._geometries = result.geometries;
                primitive._attributeLocations = result.attributeLocations;
                primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
                primitive._pickOffsets = result.pickOffsets;
                primitive._instanceBoundingSpheres = result.boundingSpheres;
                primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;

                if (defined(primitive._geometries) && primitive._geometries.length > 0) {
                    primitive._state = PrimitiveState.COMBINED;
                } else {
                    setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
                }
            }).otherwise(function(error) {
                setReady(primitive, frameState, PrimitiveState.FAILED, error);
            });
        }
    }

    function loadSynchronous(primitive, frameState) {
        var instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
        var length = primitive._numberOfInstances = instances.length;
        var clonedInstances = new Array(length);
        var instanceIds = primitive._instanceIds;

        var instance;
        var i;

        var geometryIndex = 0;
        for (i = 0; i < length; i++) {
            instance = instances[i];
            var geometry = instance.geometry;

            var createdGeometry;
            if (defined(geometry.attributes) && defined(geometry.primitiveType)) {
                createdGeometry = cloneGeometry(geometry);
            } else {
                createdGeometry = geometry.constructor.createGeometry(geometry);
            }

            clonedInstances[geometryIndex++] = cloneInstance(instance, createdGeometry);
            instanceIds.push(instance.id);
        }

        clonedInstances.length = geometryIndex;

        var scene3DOnly = frameState.scene3DOnly;
        var projection = frameState.mapProjection;

        var result = PrimitivePipeline.combineGeometry({
            instances : clonedInstances,
            ellipsoid : projection.ellipsoid,
            projection : projection,
            elementIndexUintSupported : frameState.context.elementIndexUint,
            scene3DOnly : scene3DOnly,
            vertexCacheOptimize : primitive.vertexCacheOptimize,
            compressVertices : primitive.compressVertices,
            modelMatrix : primitive.modelMatrix,
            createPickOffsets : primitive._createPickOffsets
        });

        primitive._geometries = result.geometries;
        primitive._attributeLocations = result.attributeLocations;
        primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
        primitive._pickOffsets = result.pickOffsets;
        primitive._instanceBoundingSpheres = result.boundingSpheres;
        primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;

        if (defined(primitive._geometries) && primitive._geometries.length > 0) {
            primitive._state = PrimitiveState.COMBINED;
        } else {
            setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
        }
    }

    var scratchBoundingSphereCenterEncoded = new EncodedCartesian3();
    var scratchBoundingSphereCartographic = new Cartographic();
    var scratchBoundingSphereCenter2D = new Cartesian3();

    function updateBatchTableBoundingSpheres(primitive, frameState) {
        var hasDistanceDisplayCondition = defined(primitive._batchTableAttributeIndices.distanceDisplayCondition);
        if (!hasDistanceDisplayCondition && primitive._batchTableBoundingSpheresUpdated) {
            return;
        }

        var indices = primitive._batchTableBoundingSphereAttributeIndices;
        var center3DHighIndex = indices.center3DHigh;
        var center3DLowIndex = indices.center3DLow;
        var center2DHighIndex = indices.center2DHigh;
        var center2DLowIndex = indices.center2DLow;
        var radiusIndex = indices.radius;

        var projection = frameState.mapProjection;
        var ellipsoid = projection.ellipsoid;

        var batchTable = primitive._batchTable;
        var boundingSpheres = primitive._instanceBoundingSpheres;
        var length = boundingSpheres.length;

        for (var i = 0; i < length; ++i) {
            var boundingSphere = boundingSpheres[i];
            if (!defined(boundingSphere)) {
                continue;
            }

            var modelMatrix = primitive.modelMatrix;
            if (defined(modelMatrix)) {
                boundingSphere = BoundingSphere.transform(boundingSphere, modelMatrix, boundingSphere);
            }

            if (hasDistanceDisplayCondition) {
                var center = boundingSphere.center;
                var radius = boundingSphere.radius;

                var encodedCenter = EncodedCartesian3.fromCartesian(center, scratchBoundingSphereCenterEncoded);
                batchTable.setBatchedAttribute(i, center3DHighIndex, encodedCenter.high);
                batchTable.setBatchedAttribute(i, center3DLowIndex, encodedCenter.low);

                var cartographic = ellipsoid.cartesianToCartographic(center, scratchBoundingSphereCartographic);
                var center2D = projection.project(cartographic, scratchBoundingSphereCenter2D);
                encodedCenter = EncodedCartesian3.fromCartesian(center2D, scratchBoundingSphereCenterEncoded);
                batchTable.setBatchedAttribute(i, center2DHighIndex, encodedCenter.high);
                batchTable.setBatchedAttribute(i, center2DLowIndex, encodedCenter.low);
                batchTable.setBatchedAttribute(i, radiusIndex, radius);
            }
        }

        primitive._batchTableBoundingSpheresUpdated = true;
    }

    function createVertexArray(primitive, frameState) {
        var attributeLocations = primitive._attributeLocations;
        var geometries = primitive._geometries;
        var scene3DOnly = frameState.scene3DOnly;
        var context = frameState.context;

        var va = [];
        var length = geometries.length;
        for (var i = 0; i < length; ++i) {
            var geometry = geometries[i];

            va.push(VertexArray.fromGeometry({
                context : context,
                geometry : geometry,
                attributeLocations : attributeLocations,
                bufferUsage : BufferUsage.STATIC_DRAW,
                interleave : primitive._interleave
            }));

            if (defined(primitive._createBoundingVolumeFunction)) {
                primitive._createBoundingVolumeFunction(frameState, geometry);
            } else {
                primitive._boundingSpheres.push(BoundingSphere.clone(geometry.boundingSphere));
                primitive._boundingSphereWC.push(new BoundingSphere());

                if (!scene3DOnly) {
                    var center = geometry.boundingSphereCV.center;
                    var x = center.x;
                    var y = center.y;
                    var z = center.z;
                    center.x = z;
                    center.y = x;
                    center.z = y;

                    primitive._boundingSphereCV.push(BoundingSphere.clone(geometry.boundingSphereCV));
                    primitive._boundingSphere2D.push(new BoundingSphere());
                    primitive._boundingSphereMorph.push(new BoundingSphere());
                }
            }
        }

        primitive._va = va;
        primitive._primitiveType = geometries[0].primitiveType;

        if (primitive.releaseGeometryInstances) {
            primitive.geometryInstances = undefined;
        }

        primitive._geometries = undefined;
        setReady(primitive, frameState, PrimitiveState.COMPLETE, undefined);
    }

    function createRenderStates(primitive, context, appearance, twoPasses) {
        var renderState = appearance.getRenderState();
        var rs;

        if (twoPasses) {
            rs = clone(renderState, false);
            rs.cull = {
                enabled : true,
                face : CullFace.BACK
            };
            primitive._frontFaceRS = RenderState.fromCache(rs);

            rs.cull.face = CullFace.FRONT;
            primitive._backFaceRS = RenderState.fromCache(rs);
        } else {
            primitive._frontFaceRS = RenderState.fromCache(renderState);
            primitive._backFaceRS = primitive._frontFaceRS;
        }

        if (primitive.allowPicking) {
            if (twoPasses) {
                rs = clone(renderState, false);
                rs.cull = {
                    enabled : false
                };
                primitive._pickRS = RenderState.fromCache(rs);
            } else {
                primitive._pickRS = primitive._frontFaceRS;
            }
        } else {
            rs = clone(renderState, false);
            rs.colorMask = {
                red : false,
                green : false,
                blue : false,
                alpha : false
            };

            if (twoPasses) {
                rs.cull = {
                    enabled : false
                };
                primitive._pickRS = RenderState.fromCache(rs);
            } else {
                primitive._pickRS = RenderState.fromCache(rs);
            }
        }
    }

    function createShaderProgram(primitive, frameState, appearance) {
        var context = frameState.context;

        var attributeLocations = primitive._attributeLocations;

        var vs = primitive._batchTable.getVertexShaderCallback()(appearance.vertexShaderSource);
        vs = Primitive._appendShowToShader(primitive, vs);
        vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs, frameState.scene3DOnly);
        vs = Primitive._updateColorAttribute(primitive, vs);
        vs = modifyForEncodedNormals(primitive, vs);
        vs = Primitive._modifyShaderPosition(primitive, vs, frameState.scene3DOnly);
        var fs = appearance.getFragmentShaderSource();

        // Create pick program
        if (primitive.allowPicking) {
            var vsPick = ShaderSource.createPickVertexShaderSource(vs);
            vsPick = Primitive._updatePickColorAttribute(vsPick);

            primitive._pickSP = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : primitive._pickSP,
                vertexShaderSource : vsPick,
                fragmentShaderSource : ShaderSource.createPickFragmentShaderSource(fs, 'varying'),
                attributeLocations : attributeLocations
            });
        } else {
            primitive._pickSP = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
        }
        validateShaderMatching(primitive._pickSP, attributeLocations);

        primitive._sp = ShaderProgram.replaceCache({
            context : context,
            shaderProgram : primitive._sp,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });
        validateShaderMatching(primitive._sp, attributeLocations);
    }

    var modifiedModelViewScratch = new Matrix4();
    var rtcScratch = new Cartesian3();

    function createCommands(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands, frameState) {
        // Create uniform map by combining uniforms from the appearance and material if either have uniforms.
        var materialUniformMap = defined(material) ? material._uniforms : undefined;
        var appearanceUniformMap = {};
        var appearanceUniforms = appearance.uniforms;
        if (defined(appearanceUniforms)) {
            // Convert to uniform map of functions for the renderer
            for (var name in appearanceUniforms) {
                if (appearanceUniforms.hasOwnProperty(name)) {
                                        if (defined(materialUniformMap) && defined(materialUniformMap[name])) {
                        // Later, we could rename uniforms behind-the-scenes if needed.
                        throw new DeveloperError('Appearance and material have a uniform with the same name: ' + name);
                    }
                    
                    appearanceUniformMap[name] = getUniformFunction(appearanceUniforms, name);
                }
            }
        }
        var uniforms = combine(appearanceUniformMap, materialUniformMap);
        uniforms = primitive._batchTable.getUniformMapCallback()(uniforms);

        if (defined(primitive.rtcCenter)) {
            uniforms.u_modifiedModelView = function() {
                var viewMatrix = frameState.context.uniformState.view;
                Matrix4.multiply(viewMatrix, primitive._modelMatrix, modifiedModelViewScratch);
                Matrix4.multiplyByPoint(modifiedModelViewScratch, primitive.rtcCenter, rtcScratch);
                Matrix4.setTranslation(modifiedModelViewScratch, rtcScratch, modifiedModelViewScratch);
                return modifiedModelViewScratch;
            };
        }

        var pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;

        colorCommands.length = primitive._va.length * (twoPasses ? 2 : 1);
        pickCommands.length = primitive._va.length;

        var length = colorCommands.length;
        var m = 0;
        var vaIndex = 0;
        for (var i = 0; i < length; ++i) {
            var colorCommand;

            if (twoPasses) {
                colorCommand = colorCommands[i];
                if (!defined(colorCommand)) {
                    colorCommand = colorCommands[i] = new DrawCommand({
                        owner : primitive,
                        primitiveType : primitive._primitiveType
                    });
                }
                colorCommand.vertexArray = primitive._va[vaIndex];
                colorCommand.renderState = primitive._backFaceRS;
                colorCommand.shaderProgram = primitive._sp;
                colorCommand.uniformMap = uniforms;
                colorCommand.pass = pass;

                ++i;
            }

            colorCommand = colorCommands[i];
            if (!defined(colorCommand)) {
                colorCommand = colorCommands[i] = new DrawCommand({
                    owner : primitive,
                    primitiveType : primitive._primitiveType
                });
            }
            colorCommand.vertexArray = primitive._va[vaIndex];
            colorCommand.renderState = primitive._frontFaceRS;
            colorCommand.shaderProgram = primitive._sp;
            colorCommand.uniformMap = uniforms;
            colorCommand.pass = pass;

            var pickCommand = pickCommands[m];
            if (!defined(pickCommand)) {
                pickCommand = pickCommands[m] = new DrawCommand({
                    owner : primitive,
                    primitiveType : primitive._primitiveType
                });
            }
            pickCommand.vertexArray = primitive._va[vaIndex];
            pickCommand.renderState = primitive._pickRS;
            pickCommand.shaderProgram = primitive._pickSP;
            pickCommand.uniformMap = uniforms;
            pickCommand.pass = pass;
            ++m;

            ++vaIndex;
        }
    }

    function updateBoundingVolumes(primitive, frameState) {
        // Update bounding volumes for primitives that are sized in pixels.
        // The pixel size in meters varies based on the distance from the camera.
        var pixelSize = primitive.appearance.pixelSize;
        if (defined(pixelSize)) {
            var length = primitive._boundingSpheres.length;
            for (var i = 0; i < length; ++i) {
                var boundingSphere = primitive._boundingSpheres[i];
                var boundingSphereWC = primitive._boundingSphereWC[i];
                var pixelSizeInMeters = frameState.camera.getPixelSize(boundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
                var sizeInMeters = pixelSizeInMeters * pixelSize;
                boundingSphereWC.radius = boundingSphere.radius + sizeInMeters;
            }
        }
    }

    function updateAndQueueCommands(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
                if (frameState.mode !== SceneMode.SCENE3D && !Matrix4.equals(modelMatrix, Matrix4.IDENTITY)) {
            throw new DeveloperError('Primitive.modelMatrix is only supported in 3D mode.');
        }
        
        updateBoundingVolumes(primitive, frameState);

        if (!Matrix4.equals(modelMatrix, primitive._modelMatrix)) {
            Matrix4.clone(modelMatrix, primitive._modelMatrix);
            var length = primitive._boundingSpheres.length;
            for (var i = 0; i < length; ++i) {
                var boundingSphere = primitive._boundingSpheres[i];
                if (defined(boundingSphere)) {
                    primitive._boundingSphereWC[i] = BoundingSphere.transform(boundingSphere, modelMatrix, primitive._boundingSphereWC[i]);
                    if (!frameState.scene3DOnly) {
                        primitive._boundingSphere2D[i] = BoundingSphere.clone(primitive._boundingSphereCV[i], primitive._boundingSphere2D[i]);
                        primitive._boundingSphere2D[i].center.x = 0.0;
                        primitive._boundingSphereMorph[i] = BoundingSphere.union(primitive._boundingSphereWC[i], primitive._boundingSphereCV[i]);
                    }
                }
            }
        }

        var boundingSpheres;
        if (frameState.mode === SceneMode.SCENE3D) {
            boundingSpheres = primitive._boundingSphereWC;
        } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
            boundingSpheres = primitive._boundingSphereCV;
        } else if (frameState.mode === SceneMode.SCENE2D && defined(primitive._boundingSphere2D)) {
            boundingSpheres = primitive._boundingSphere2D;
        } else if (defined(primitive._boundingSphereMorph)) {
            boundingSpheres = primitive._boundingSphereMorph;
        }

        var commandList = frameState.commandList;
        var passes = frameState.passes;
        if (passes.render) {
            var castShadows = ShadowMode.castShadows(primitive.shadows);
            var receiveShadows = ShadowMode.receiveShadows(primitive.shadows);
            var colorLength = colorCommands.length;
            for (var j = 0; j < colorLength; ++j) {
                var sphereIndex = twoPasses ? Math.floor(j / 2) : j;
                var colorCommand = colorCommands[j];
                colorCommand.modelMatrix = modelMatrix;
                colorCommand.boundingVolume = boundingSpheres[sphereIndex];
                colorCommand.cull = cull;
                colorCommand.debugShowBoundingVolume = debugShowBoundingVolume;
                colorCommand.castShadows = castShadows;
                colorCommand.receiveShadows = receiveShadows;

                commandList.push(colorCommand);
            }
        }

        if (passes.pick) {
            var pickLength = pickCommands.length;
            for (var k = 0; k < pickLength; ++k) {
                var pickCommand = pickCommands[k];
                pickCommand.modelMatrix = modelMatrix;
                pickCommand.boundingVolume = boundingSpheres[k];
                pickCommand.cull = cull;

                commandList.push(pickCommand);
            }
        }
    }

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} All instance geometries must have the same primitiveType.
     * @exception {DeveloperError} Appearance and material have a uniform with the same name.
     * @exception {DeveloperError} Primitive.modelMatrix is only supported in 3D mode.
     * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.
     */
    Primitive.prototype.update = function(frameState) {
        if (((!defined(this.geometryInstances)) && (this._va.length === 0)) ||
            (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length === 0) ||
            (!defined(this.appearance)) ||
            (frameState.mode !== SceneMode.SCENE3D && frameState.scene3DOnly) ||
            (!frameState.passes.render && !frameState.passes.pick)) {
            return;
        }

        if (defined(this._error)) {
            throw this._error;
        }

                if (defined(this.rtcCenter) && !frameState.scene3DOnly) {
            throw new DeveloperError('RTC rendering is only available for 3D only scenes.');
        }
        
        if (this._state === PrimitiveState.FAILED) {
            return;
        }

        var context = frameState.context;
        if (!defined(this._batchTable)) {
            createBatchTable(this, context);
        }
        if (this._batchTable.attributes.length > 0) {
            if (ContextLimits.maximumVertexTextureImageUnits === 0) {
                throw new RuntimeError('Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.');
            }
            this._batchTable.update(frameState);
        }

        if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) {
            if (this.asynchronous) {
                loadAsynchronous(this, frameState);
            } else {
                loadSynchronous(this, frameState);
            }
        }

        if (this._state === PrimitiveState.COMBINED) {
            updateBatchTableBoundingSpheres(this, frameState);
            createVertexArray(this, frameState);
        }

        if (!this.show || this._state !== PrimitiveState.COMPLETE) {
            return;
        }

        // Create or recreate render state and shader program if appearance/material changed
        var appearance = this.appearance;
        var material = appearance.material;
        var createRS = false;
        var createSP = false;

        if (this._appearance !== appearance) {
            this._appearance = appearance;
            this._material = material;
            createRS = true;
            createSP = true;
        } else if (this._material !== material ) {
            this._material = material;
            createSP = true;
        }

        var translucent = this._appearance.isTranslucent();
        if (this._translucent !== translucent) {
            this._translucent = translucent;
            createRS = true;
        }

        if (defined(this._material)) {
            this._material.update(context);
        }

        var twoPasses = appearance.closed && translucent;

        if (createRS) {
            var rsFunc = defaultValue(this._createRenderStatesFunction, createRenderStates);
            rsFunc(this, context, appearance, twoPasses);
        }

        if (createSP) {
            var spFunc = defaultValue(this._createShaderProgramFunction, createShaderProgram);
            spFunc(this, frameState, appearance);
        }

        if (createRS || createSP) {
            var commandFunc = defaultValue(this._createCommandsFunction, createCommands);
            commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState);
        }

        var updateAndQueueCommandsFunc = defaultValue(this._updateAndQueueCommandsFunction, updateAndQueueCommands);
        updateAndQueueCommandsFunc(this, frameState, this._colorCommands, this._pickCommands, this.modelMatrix, this.cull, this.debugShowBoundingVolume, twoPasses);
    };

    function createGetFunction(batchTable, instanceIndex, attributeIndex) {
        return function() {
            var attributeValue = batchTable.getBatchedAttribute(instanceIndex, attributeIndex);
            var attribute = batchTable.attributes[attributeIndex];
            var componentsPerAttribute = attribute.componentsPerAttribute;
            var value = ComponentDatatype.createTypedArray(attribute.componentDatatype, componentsPerAttribute);
            if (defined(attributeValue.constructor.pack)) {
                attributeValue.constructor.pack(attributeValue, value, 0);
            } else {
                value[0] = attributeValue;
            }
            return value;
        };
    }

    function createSetFunction(batchTable, instanceIndex, attributeIndex) {
        return function(value) {
                        if (!defined(value) || !defined(value.length) || value.length < 1 || value.length > 4) {
                throw new DeveloperError('value must be and array with length between 1 and 4.');
            }
                        var attributeValue = getAttributeValue(value);
            batchTable.setBatchedAttribute(instanceIndex, attributeIndex, attributeValue);
        };
    }

    function createBoundingSphereProperties(primitive, properties, index) {
        properties.boundingSphere = {
            get : function() {
                return primitive._instanceBoundingSpheres[index];
            }
        };
        properties.boundingSphereCV = {
            get : function() {
                return primitive._instanceBoundingSpheresCV[index];
            }
        };
    }

    /**
     * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
     *
     * @param {Object} id The id of the {@link GeometryInstance}.
     * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
     *
     * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
     * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
     * attributes.distanceDisplayCondition = Cesium.DistanceDisplayConditionGeometryInstanceAttribute.toValue(100.0, 10000.0);
     */
    Primitive.prototype.getGeometryInstanceAttributes = function(id) {
                if (!defined(id)) {
            throw new DeveloperError('id is required');
        }
        if (!defined(this._batchTable)) {
            throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
        }
        
        var index = -1;
        var lastIndex = this._lastPerInstanceAttributeIndex;
        var ids = this._instanceIds;
        var length = ids.length;
        for (var i = 0; i < length; ++i) {
            var curIndex = (lastIndex + i) % length;
            if (id === ids[curIndex]) {
                index = curIndex;
                break;
            }
        }

        if (index === -1) {
            return undefined;
        }

        var attributes = this._perInstanceAttributeCache[index];
        if (defined(attributes)) {
            return attributes;
        }

        var batchTable = this._batchTable;
        var perInstanceAttributeIndices = this._batchTableAttributeIndices;
        attributes = {};
        var properties = {};

        for (var name in perInstanceAttributeIndices) {
            if (perInstanceAttributeIndices.hasOwnProperty(name)) {
                var attributeIndex = perInstanceAttributeIndices[name];
                properties[name] = {
                    get : createGetFunction(batchTable, index, attributeIndex)
                };

                var createSetter = true;
                var readOnlyAttributes = this._readOnlyInstanceAttributes;
                if (createSetter && defined(readOnlyAttributes)) {
                    length = readOnlyAttributes.length;
                    for (var k = 0; k < length; ++k) {
                        if (name === readOnlyAttributes[k]) {
                            createSetter = false;
                            break;
                        }
                    }
                }

                if (createSetter) {
                    properties[name].set = createSetFunction(batchTable, index, attributeIndex);
                }
            }
        }

        createBoundingSphereProperties(this, properties, index);
        defineProperties(attributes, properties);

        this._lastPerInstanceAttributeIndex = index;
        this._perInstanceAttributeCache[index] = attributes;
        return attributes;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Primitive#destroy
     */
    Primitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * e = e && e.destroy();
     *
     * @see Primitive#isDestroyed
     */
    Primitive.prototype.destroy = function() {
        var length;
        var i;

        this._sp = this._sp && this._sp.destroy();
        this._pickSP = this._pickSP && this._pickSP.destroy();

        var va = this._va;
        length = va.length;
        for (i = 0; i < length; ++i) {
            va[i].destroy();
        }
        this._va = undefined;

        var pickIds = this._pickIds;
        length = pickIds.length;
        for (i = 0; i < length; ++i) {
            pickIds[i].destroy();
        }
        this._pickIds = undefined;

        this._batchTable = this._batchTable && this._batchTable.destroy();

        //These objects may be fairly large and reference other large objects (like Entities)
        //We explicitly set them to undefined here so that the memory can be freed
        //even if a reference to the destroyed Primitive has been kept around.
        this._instanceIds = undefined;
        this._perInstanceAttributeCache = undefined;
        this._attributeLocations = undefined;

        return destroyObject(this);
    };

    function setReady(primitive, frameState, state, error) {
        primitive._error = error;
        primitive._state = state;
        frameState.afterRender.push(function() {
            primitive._ready = primitive._state === PrimitiveState.COMPLETE || primitive._state === PrimitiveState.FAILED;
            if (!defined(error)) {
                primitive._readyPromise.resolve(primitive);
            } else {
                primitive._readyPromise.reject(error);
            }
        });
    }

    return Primitive;
});

/*global define*/
define('DataSources/ColorMaterialProperty',[
        '../Core/Color',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Color,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    /**
     * A {@link MaterialProperty} that maps to solid color {@link Material} uniforms.
     *
     * @param {Property} [color=Color.WHITE] The {@link Color} Property to be used.
     *
     * @alias ColorMaterialProperty
     * @constructor
     */
    function ColorMaterialProperty(color) {
        this._definitionChanged = new Event();
        this._color = undefined;
        this._colorSubscription = undefined;
        this.color = color;
    }

    defineProperties(ColorMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof ColorMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._color);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof ColorMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the {@link Color} {@link Property}.
         * @memberof ColorMaterialProperty.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    ColorMaterialProperty.prototype.getType = function(time) {
        return 'Color';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ColorMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.color = Property.getValueOrClonedDefault(this._color, time, Color.WHITE, result.color);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    ColorMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof ColorMaterialProperty && //
                Property.equals(this._color, other._color));
    };

    return ColorMaterialProperty;
});

/*global define*/
define('DataSources/dynamicGeometryGetBoundingSphere',[
        '../Core/BoundingSphere',
        '../Core/defined',
        '../Core/DeveloperError',
        './BoundingSphereState'
    ], function(
        BoundingSphere,
        defined,
        DeveloperError,
        BoundingSphereState) {
    'use strict';

    /**
     * @private
     */
    function dynamicGeometryGetBoundingSphere(entity, primitive, outlinePrimitive, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var attributes;

        //Outline and Fill geometries have the same bounding sphere, so just use whichever one is defined and ready
        if (defined(primitive) && primitive.show && primitive.ready) {
            attributes = primitive.getGeometryInstanceAttributes(entity);
            if (defined(attributes) && defined(attributes.boundingSphere)) {
                BoundingSphere.clone(attributes.boundingSphere, result);
                return BoundingSphereState.DONE;
            }
        }

        if (defined(outlinePrimitive) && outlinePrimitive.show && outlinePrimitive.ready) {
            attributes = outlinePrimitive.getGeometryInstanceAttributes(entity);
            if (defined(attributes) && defined(attributes.boundingSphere)) {
                BoundingSphere.clone(attributes.boundingSphere, result);
                return BoundingSphereState.DONE;
            }
        }

        if ((defined(primitive) && !primitive.ready) || (defined(outlinePrimitive) && !outlinePrimitive.ready)) {
            return BoundingSphereState.PENDING;
        }

        return BoundingSphereState.FAILED;
    }

    return dynamicGeometryGetBoundingSphere;
});

/*global define*/
define('DataSources/MaterialProperty',[
        '../Core/Color',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Scene/Material'
    ], function(
        Color,
        defined,
        defineProperties,
        DeveloperError,
        Material) {
    'use strict';

    /**
     * The interface for all {@link Property} objects that represent {@link Material} uniforms.
     * This type defines an interface and cannot be instantiated directly.
     *
     * @alias MaterialProperty
     * @constructor
     *
     * @see ColorMaterialProperty
     * @see CompositeMaterialProperty
     * @see GridMaterialProperty
     * @see ImageMaterialProperty
     * @see PolylineGlowMaterialProperty
     * @see PolylineOutlineMaterialProperty
     * @see StripeMaterialProperty
     */
    function MaterialProperty() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(MaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof MaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof MaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Gets the {@link Material} type at the provided time.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    MaterialProperty.prototype.getType = DeveloperError.throwInstantiationError;

    /**
     * Gets the value of the property at the provided time.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    MaterialProperty.prototype.getValue = DeveloperError.throwInstantiationError;

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     * @function
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    MaterialProperty.prototype.equals = DeveloperError.throwInstantiationError;

    /**
     * @private
     */
    MaterialProperty.getValue = function(time, materialProperty, material) {
        var type;

        if (defined(materialProperty)) {
            type = materialProperty.getType(time);
            if (defined(type)) {
                if (!defined(material) || (material.type !== type)) {
                    material = Material.fromType(type);
                }
                materialProperty.getValue(time, material.uniforms);
                return material;
            }
        }

        if (!defined(material) || (material.type !== Material.ColorType)) {
            material = Material.fromType(Material.ColorType);
        }
        Color.clone(Color.WHITE, material.uniforms.color);

        return material;
    };

    return MaterialProperty;
});

/*global define*/
define('DataSources/BoxGeometryUpdater',[
        '../Core/BoxGeometry',
        '../Core/BoxOutlineGeometry',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        BoxGeometry,
        BoxOutlineGeometry,
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        ShowGeometryInstanceAttribute,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.dimensions = undefined;
    }

    /**
     * A {@link GeometryUpdater} for boxes.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias BoxGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function BoxGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(BoxGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'box', entity.box, undefined);
    }

    defineProperties(BoxGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof BoxGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof BoxGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(BoxGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof BoxGeometryUpdater.prototype
         * 
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            value : true
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof BoxGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    BoxGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    BoxGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    BoxGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : BoxGeometry.fromDimensions(this._options),
            modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    BoxGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : BoxOutlineGeometry.fromDimensions(this._options),
            modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    BoxGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    BoxGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    BoxGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'box')) {
            return;
        }

        var box = this._entity.box;

        if (!defined(box)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = box.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = box.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var dimensions = box.dimensions;
        var position = entity.position;

        var show = box.show;
        if (!defined(dimensions) || !defined(position) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(box.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(box.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(box.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(box.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(box.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var outlineWidth = box.outlineWidth;

        this._fillEnabled = fillEnabled;
        this._outlineEnabled = outlineEnabled;

        if (!position.isConstant || //
            !Property.isConstant(entity.orientation) || //
            !dimensions.isConstant || //
            !Property.isConstant(outlineWidth)) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.dimensions = dimensions.getValue(Iso8601.MINIMUM_VALUE, options.dimensions);
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    BoxGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, geometryUpdater) {
        this._primitives = primitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        this._primitive = undefined;
        this._outlinePrimitive = undefined;

        var geometryUpdater = this._geometryUpdater;
        var entity = geometryUpdater._entity;
        var box = entity.box;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(box.show, time, true)) {
            return;
        }

        var options = this._options;
        var modelMatrix = entity._getModelMatrix(time);
        var dimensions = Property.getValueOrUndefined(box.dimensions, time, options.dimensions);
        if (!defined(modelMatrix) || !defined(dimensions)) {
            return;
        }

        options.dimensions = dimensions;

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
        var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);

        if (Property.getValueOrDefault(box.fill, time, true)) {
            var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
            this._material = material;

            var appearance = new MaterialAppearance({
                material : material,
                translucent : material.isTranslucent(),
                closed : true
            });
            options.vertexFormat = appearance.vertexFormat;

            this._primitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : BoxGeometry.fromDimensions(options),
                    modelMatrix : modelMatrix,
                    attributes : {
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : appearance,
                asynchronous : false,
                shadows : shadows
            }));
        }

        if (Property.getValueOrDefault(box.outline, time, false)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(box.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(box.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : BoxOutlineGeometry.fromDimensions(options),
                    modelMatrix : modelMatrix,
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return BoxGeometryUpdater;
});

/*global define*/
define('DataSources/ImageMaterialProperty',[
        '../Core/Cartesian2',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Cartesian2,
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    var defaultRepeat = new Cartesian2(1, 1);
    var defaultTransparent = false;
    var defaultColor = Color.WHITE;

    /**
     * A {@link MaterialProperty} that maps to image {@link Material} uniforms.
     * @alias ImageMaterialProperty
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.image] A Property specifying the Image, URL, Canvas, or Video.
     * @param {Property} [options.repeat=new Cartesian2(1.0, 1.0)] A {@link Cartesian2} Property specifying the number of times the image repeats in each direction.
     * @param {Property} [options.color=Color.WHITE] The color applied to the image
     * @param {Property} [options.transparent=false] Set to true when the image has transparency (for example, when a png has transparent sections)
     */
    function ImageMaterialProperty(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();
        this._image = undefined;
        this._imageSubscription = undefined;
        this._repeat = undefined;
        this._repeatSubscription = undefined;
        this._color = undefined;
        this._colorSubscription = undefined;
        this._transparent = undefined;
        this._transparentSubscription = undefined;
        this.image = options.image;
        this.repeat = options.repeat;
        this.color = options.color;
        this.transparent = options.transparent;
    }

    defineProperties(ImageMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof ImageMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._image) && Property.isConstant(this._repeat);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof ImageMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the Property specifying Image, URL, Canvas, or Video to use.
         * @memberof ImageMaterialProperty.prototype
         * @type {Property}
         */
        image : createPropertyDescriptor('image'),
        /**
         * Gets or sets the {@link Cartesian2} Property specifying the number of times the image repeats in each direction.
         * @memberof ImageMaterialProperty.prototype
         * @type {Property}
         * @default new Cartesian2(1, 1)
         */
        repeat : createPropertyDescriptor('repeat'),
        /**
         * Gets or sets the Color Property specifying the desired color applied to the image.
         * @memberof ImageMaterialProperty.prototype
         * @type {Property}
         * @default 1.0
         */
        color : createPropertyDescriptor('color'),
        /**
         * Gets or sets the Boolean Property specifying whether the image has transparency
         * @memberof ImageMaterialProperty.prototype
         * @type {Property}
         * @default 1.0
         */
        transparent : createPropertyDescriptor('transparent')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    ImageMaterialProperty.prototype.getType = function(time) {
        return 'Image';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ImageMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }

        result.image = Property.getValueOrUndefined(this._image, time);
        result.repeat = Property.getValueOrClonedDefault(this._repeat, time, defaultRepeat, result.repeat);
        result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
        if (Property.getValueOrDefault(this._transparent, time, defaultTransparent)) {
            result.color.alpha = Math.min(0.99, result.color.alpha);
        }

        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    ImageMaterialProperty.prototype.equals = function(other) {
        return this === other ||
               (other instanceof ImageMaterialProperty &&
                Property.equals(this._image, other._image) &&
                Property.equals(this._color, other._color) &&
                Property.equals(this._transparent, other._transparent) &&
                Property.equals(this._repeat, other._repeat));
    };

    return ImageMaterialProperty;
});

/*global define*/
define('DataSources/createMaterialPropertyDescriptor',[
        '../Core/Color',
        '../Core/DeveloperError',
        './ColorMaterialProperty',
        './createPropertyDescriptor',
        './ImageMaterialProperty'
    ], function(
        Color,
        DeveloperError,
        ColorMaterialProperty,
        createPropertyDescriptor,
        ImageMaterialProperty) {
    'use strict';

    function createMaterialProperty(value) {
        if (value instanceof Color) {
            return new ColorMaterialProperty(value);
        }

        if (typeof value === 'string' || value instanceof HTMLCanvasElement || value instanceof HTMLVideoElement) {
            var result = new ImageMaterialProperty();
            result.image = value;
            return result;
        }

                throw new DeveloperError('Unable to infer material type: ' + value);
            }

    /**
     * @private
     */
    function createMaterialPropertyDescriptor(name, configurable) {
        return createPropertyDescriptor(name, configurable, createMaterialProperty);
    }

    return createMaterialPropertyDescriptor;
});

/*global define*/
define('DataSources/BoxGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a box. The center position and orientation are determined by the containing {@link Entity}.
     *
     * @alias BoxGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.dimensions] A {@link Cartesian3} Property specifying the length, width, and height of the box.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the box.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the box is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the box.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the box is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the box casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this box will be displayed.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Box.html|Cesium Sandcastle Box Demo}
     */
    function BoxGraphics(options) {
        this._dimensions = undefined;
        this._dimensionsSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(BoxGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof BoxGraphics.prototype
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the box.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets {@link Cartesian3} Property property specifying the length, width, and height of the box.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         */
        dimensions : createPropertyDescriptor('dimensions'),

        /**
         * Gets or sets the material used to fill the box.
         * @memberof BoxGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the boolean Property specifying whether the box is filled with the provided material.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the box is outlined.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Get or sets the enum Property specifying whether the box
         * casts or receives shadows from each light source.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this box will be displayed.
         * @memberof BoxGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {BoxGraphics} [result] The object onto which to store the result.
     * @returns {BoxGraphics} The modified result parameter or a new instance if one was not provided.
     */
    BoxGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new BoxGraphics(this);
        }
        result.dimensions = this.dimensions;
        result.show = this.show;
        result.material = this.material;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {BoxGraphics} source The object to be merged into this object.
     */
    BoxGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.dimensions = defaultValue(this.dimensions, source.dimensions);
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return BoxGraphics;
});

/*global define*/
define('DataSources/CallbackProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event) {
    'use strict';

    /**
     * A {@link Property} whose value is lazily evaluated by a callback function.
     *
     * @alias CallbackProperty
     * @constructor
     *
     * @param {CallbackProperty~Callback} callback The function to be called when the property is evaluated.
     * @param {Boolean} isConstant <code>true</code> when the callback function returns the same value every time, <code>false</code> if the value will change.
     */
    function CallbackProperty(callback, isConstant) {
        this._callback = undefined;
        this._isConstant = undefined;
        this._definitionChanged = new Event();
        this.setCallback(callback, isConstant);
    }

    defineProperties(CallbackProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.
         * @memberof CallbackProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._isConstant;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setCallback is called.
         * @memberof CallbackProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        }
    });

    /**
     * Gets the value of the property.
     *
     * @param {JulianDate} [time] The time for which to retrieve the value.  This parameter is unused since the value does not change with respect to time.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied or is unsupported.
     */
    CallbackProperty.prototype.getValue = function(time, result) {
        return this._callback(time, result);
    };

    /**
     * Sets the callback to be used.
     *
     * @param {CallbackProperty~Callback} callback The function to be called when the property is evaluated.
     * @param {Boolean} isConstant <code>true</code> when the callback function returns the same value every time, <code>false</code> if the value will change.
     */
    CallbackProperty.prototype.setCallback = function(callback, isConstant) {
                if (!defined(callback)) {
            throw new DeveloperError('callback is required.');
        }
        if (!defined(isConstant)) {
            throw new DeveloperError('isConstant is required.');
        }
        
        var changed = this._callback !== callback || this._isConstant !== isConstant;

        this._callback = callback;
        this._isConstant = isConstant;

        if (changed) {
            this._definitionChanged.raiseEvent(this);
        }
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    CallbackProperty.prototype.equals = function(other) {
        return this === other || (other instanceof CallbackProperty && this._callback === other._callback && this._isConstant === other._isConstant);
    };

    /**
     * A function that returns the value of the property.
     * @callback CallbackProperty~Callback
     *
     * @param {JulianDate} [time] The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied or is unsupported.
     */

    return CallbackProperty;
});

/*global define*/
define('DataSources/CheckerboardMaterialProperty',[
        '../Core/Cartesian2',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Cartesian2,
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    var defaultEvenColor = Color.WHITE;
    var defaultOddColor = Color.BLACK;
    var defaultRepeat = new Cartesian2(2.0, 2.0);

    /**
     * A {@link MaterialProperty} that maps to checkerboard {@link Material} uniforms.
     * @alias CheckerboardMaterialProperty
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.evenColor=Color.WHITE] A Property specifying the first {@link Color}.
     * @param {Property} [options.oddColor=Color.BLACK] A Property specifying the second {@link Color}.
     * @param {Property} [options.repeat=new Cartesian2(2.0, 2.0)] A {@link Cartesian2} Property specifying how many times the tiles repeat in each direction.
     */
    function CheckerboardMaterialProperty(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();

        this._evenColor = undefined;
        this._evenColorSubscription = undefined;

        this._oddColor = undefined;
        this._oddColorSubscription = undefined;

        this._repeat = undefined;
        this._repeatSubscription = undefined;

        this.evenColor = options.evenColor;
        this.oddColor = options.oddColor;
        this.repeat = options.repeat;
    }

    defineProperties(CheckerboardMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof CheckerboardMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._evenColor) && //
                       Property.isConstant(this._oddColor) && //
                       Property.isConstant(this._repeat);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof CheckerboardMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the Property specifying the first {@link Color}.
         * @memberof CheckerboardMaterialProperty.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        evenColor : createPropertyDescriptor('evenColor'),
        /**
         * Gets or sets the Property specifying the second {@link Color}.
         * @memberof CheckerboardMaterialProperty.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        oddColor : createPropertyDescriptor('oddColor'),
        /**
         * Gets or sets the {@link Cartesian2} Property specifying how many times the tiles repeat in each direction.
         * @memberof CheckerboardMaterialProperty.prototype
         * @type {Property}
         * @default new Cartesian2(2.0, 2.0)
         */
        repeat : createPropertyDescriptor('repeat')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    CheckerboardMaterialProperty.prototype.getType = function(time) {
        return 'Checkerboard';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    CheckerboardMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.lightColor = Property.getValueOrClonedDefault(this._evenColor, time, defaultEvenColor, result.lightColor);
        result.darkColor = Property.getValueOrClonedDefault(this._oddColor, time, defaultOddColor, result.darkColor);
        result.repeat = Property.getValueOrDefault(this._repeat, time, defaultRepeat);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    CheckerboardMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof CheckerboardMaterialProperty && //
                       Property.equals(this._evenColor, other._evenColor) && //
                       Property.equals(this._oddColor, other._oddColor) && //
                       Property.equals(this._repeat, other._repeat));
    };

    return CheckerboardMaterialProperty;
});

/*global define*/
define('DataSources/PositionProperty',[
        '../Core/Cartesian3',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Matrix3',
        '../Core/ReferenceFrame',
        '../Core/Transforms'
    ], function(
        Cartesian3,
        defined,
        defineProperties,
        DeveloperError,
        Matrix3,
        ReferenceFrame,
        Transforms) {
    'use strict';

    /**
     * The interface for all {@link Property} objects that define a world
     * location as a {@link Cartesian3} with an associated {@link ReferenceFrame}.
     * This type defines an interface and cannot be instantiated directly.
     *
     * @alias PositionProperty
     * @constructor
     *
     * @see CompositePositionProperty
     * @see ConstantPositionProperty
     * @see SampledPositionProperty
     * @see TimeIntervalCollectionPositionProperty
     */
    function PositionProperty() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(PositionProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof PositionProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof PositionProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the reference frame that the position is defined in.
         * @memberof PositionProperty.prototype
         * @type {ReferenceFrame}
         */
        referenceFrame : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Gets the value of the property at the provided time in the fixed frame.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PositionProperty.prototype.getValue = DeveloperError.throwInstantiationError;

    /**
     * Gets the value of the property at the provided time and in the provided reference frame.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PositionProperty.prototype.getValueInReferenceFrame = DeveloperError.throwInstantiationError;

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     * @function
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PositionProperty.prototype.equals = DeveloperError.throwInstantiationError;

    var scratchMatrix3 = new Matrix3();

    /**
     * @private
     */
    PositionProperty.convertToReferenceFrame = function(time, value, inputFrame, outputFrame, result) {
        if (!defined(value)) {
            return value;
        }
        if (!defined(result)){
            result = new Cartesian3();
        }

        if (inputFrame === outputFrame) {
            return Cartesian3.clone(value, result);
        }

        var icrfToFixed = Transforms.computeIcrfToFixedMatrix(time, scratchMatrix3);
        if (!defined(icrfToFixed)) {
            icrfToFixed = Transforms.computeTemeToPseudoFixedMatrix(time, scratchMatrix3);
        }
        if (inputFrame === ReferenceFrame.INERTIAL) {
            return Matrix3.multiplyByVector(icrfToFixed, value, result);
        }
        if (inputFrame === ReferenceFrame.FIXED) {
            return Matrix3.multiplyByVector(Matrix3.transpose(icrfToFixed, scratchMatrix3), value, result);
        }
    };

    return PositionProperty;
});

/*global define*/
define('DataSources/ConstantPositionProperty',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/ReferenceFrame',
        './PositionProperty'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        ReferenceFrame,
        PositionProperty) {
    'use strict';

    /**
     * A {@link PositionProperty} whose value does not change in respect to the
     * {@link ReferenceFrame} in which is it defined.
     *
     * @alias ConstantPositionProperty
     * @constructor
     *
     * @param {Cartesian3} [value] The property value.
     * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
     */
    function ConstantPositionProperty(value, referenceFrame) {
        this._definitionChanged = new Event();
        this._value = Cartesian3.clone(value);
        this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
    }

    defineProperties(ConstantPositionProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof ConstantPositionProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return !defined(this._value) || this._referenceFrame === ReferenceFrame.FIXED;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof ConstantPositionProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the reference frame in which the position is defined.
         * @memberof ConstantPositionProperty.prototype
         * @type {ReferenceFrame}
         * @default ReferenceFrame.FIXED;
         */
        referenceFrame : {
            get : function() {
                return this._referenceFrame;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time in the fixed frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ConstantPositionProperty.prototype.getValue = function(time, result) {
        return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
    };

    /**
     * Sets the value of the property.
     *
     * @param {Cartesian3} value The property value.
     * @param {ReferenceFrame} [referenceFrame=this.referenceFrame] The reference frame in which the position is defined.
     */
    ConstantPositionProperty.prototype.setValue = function(value, referenceFrame) {
        var definitionChanged = false;
        if (!Cartesian3.equals(this._value, value)) {
            definitionChanged = true;
            this._value = Cartesian3.clone(value);
        }
        if (defined(referenceFrame) && this._referenceFrame !== referenceFrame) {
            definitionChanged = true;
            this._referenceFrame = referenceFrame;
        }
        if (definitionChanged) {
            this._definitionChanged.raiseEvent(this);
        }
    };

    /**
     * Gets the value of the property at the provided time and in the provided reference frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ConstantPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(referenceFrame)) {
            throw new DeveloperError('referenceFrame is required.');
        }
        
        return PositionProperty.convertToReferenceFrame(time, this._value, this._referenceFrame, referenceFrame, result);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    ConstantPositionProperty.prototype.equals = function(other) {
        return this === other ||
               (other instanceof ConstantPositionProperty &&
                Cartesian3.equals(this._value, other._value) &&
                this._referenceFrame === other._referenceFrame);
    };

    return ConstantPositionProperty;
});

/*global define*/
define('DataSources/CorridorGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a corridor, which is a shape defined by a centerline and width that
     * conforms to the curvature of the globe. It can be placed on the surface or at altitude
     * and can optionally be extruded into a volume.
     *
     * @alias CorridorGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the centerline of the corridor.
     * @param {Property} [options.width] A numeric Property specifying the distance between the edges of the corridor.
     * @param {Property} [options.cornerType=CornerType.ROUNDED] A {@link CornerType} Property specifying the style of the corners.
     * @param {Property} [options.height=0] A numeric Property specifying the altitude of the corridor relative to the ellipsoid surface.
     * @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the corridor's extruded face relative to the ellipsoid surface.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the corridor.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the corridor is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the corridor.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the corridor is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the distance between each latitude and longitude.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the corridor casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this corridor will be displayed.
     *
     * @see Entity
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Corridor.html|Cesium Sandcastle Corridor Demo}
     */
    function CorridorGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._positions = undefined;
        this._positionsSubscription = undefined;
        this._height = undefined;
        this._heightSubscription = undefined;
        this._extrudedHeight = undefined;
        this._extrudedHeightSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._width = undefined;
        this._widthSubscription = undefined;
        this._cornerType = undefined;
        this._cornerTypeSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(CorridorGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof CorridorGraphics.prototype
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the corridor.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to fill the corridor.
         * @memberof CorridorGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets a Property specifying the array of {@link Cartesian3} positions that define the centerline of the corridor.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         */
        positions : createPropertyDescriptor('positions'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the corridor.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default 0.0
         */
        height : createPropertyDescriptor('height'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the corridor extrusion.
         * Setting this property creates a corridor shaped volume starting at height and ending
         * at this altitude.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         */
        extrudedHeight : createPropertyDescriptor('extrudedHeight'),

        /**
         * Gets or sets the numeric Property specifying the sampling distance between each latitude and longitude point.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default {CesiumMath.RADIANS_PER_DEGREE}
         */
        granularity : createPropertyDescriptor('granularity'),

        /**
         * Gets or sets the numeric Property specifying the width of the corridor.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         */
        width : createPropertyDescriptor('width'),

        /**
         * Gets or sets the boolean Property specifying whether the corridor is filled with the provided material.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the corridor is outlined.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the {@link CornerType} Property specifying how corners are styled.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default CornerType.ROUNDED
         */
        cornerType : createPropertyDescriptor('cornerType'),
        
        /**
         * Get or sets the enum Property specifying whether the corridor
         * casts or receives shadows from each light source.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this corridor will be displayed.
         * @memberof CorridorGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {CorridorGraphics} [result] The object onto which to store the result.
     * @returns {CorridorGraphics} The modified result parameter or a new instance if one was not provided.
     */
    CorridorGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new CorridorGraphics(this);
        }
        result.show = this.show;
        result.material = this.material;
        result.positions = this.positions;
        result.height = this.height;
        result.extrudedHeight = this.extrudedHeight;
        result.granularity = this.granularity;
        result.width = this.width;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.cornerType = this.cornerType;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {CorridorGraphics} source The object to be merged into this object.
     */
    CorridorGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.positions = defaultValue(this.positions, source.positions);
        this.height = defaultValue(this.height, source.height);
        this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.width = defaultValue(this.width, source.width);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.cornerType = defaultValue(this.cornerType, source.cornerType);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return CorridorGraphics;
});

/*global define*/
define('DataSources/createRawPropertyDescriptor',[
        './createPropertyDescriptor'
    ], function(
        createPropertyDescriptor) {
    'use strict';

    function createRawProperty(value) {
        return value;
    }

    /**
     * @private
     */
    function createRawPropertyDescriptor(name, configurable) {
        return createPropertyDescriptor(name, configurable, createRawProperty);
    }

    return createRawPropertyDescriptor;
});

/*global define*/
define('DataSources/CylinderGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a cylinder, truncated cone, or cone defined by a length, top radius, and bottom radius.
     * The center position and orientation are determined by the containing {@link Entity}.
     *
     * @alias CylinderGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.length] A numeric Property specifying the length of the cylinder.
     * @param {Property} [options.topRadius] A numeric Property specifying the radius of the top of the cylinder.
     * @param {Property} [options.bottomRadius] A numeric Property specifying the radius of the bottom of the cylinder.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the cylinder.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the cylinder is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the cylinder.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the cylinder is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.numberOfVerticalLines=16] A numeric Property specifying the number of vertical lines to draw along the perimeter for the outline.
     * @param {Property} [options.slices=128] The number of edges around the perimeter of the cylinder.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the cylinder casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this cylinder will be displayed.
     */
    function CylinderGraphics(options) {
        this._length = undefined;
        this._lengthSubscription = undefined;
        this._topRadius = undefined;
        this._topRadiusSubscription = undefined;
        this._bottomRadius = undefined;
        this._bottomRadiusSubscription = undefined;
        this._numberOfVerticalLines = undefined;
        this._numberOfVerticalLinesSubscription = undefined;
        this._slices = undefined;
        this._slicesSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(CylinderGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof CylinderGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the numeric Property specifying the length of the cylinder.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         */
        length : createPropertyDescriptor('length'),

        /**
         * Gets or sets the numeric Property specifying the radius of the top of the cylinder.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         */
        topRadius : createPropertyDescriptor('topRadius'),

        /**
         * Gets or sets the numeric Property specifying the radius of the bottom of the cylinder.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         */
        bottomRadius : createPropertyDescriptor('bottomRadius'),

        /**
         * Gets or sets the Property specifying the number of vertical lines to draw along the perimeter for the outline.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default 16
         */
        numberOfVerticalLines : createPropertyDescriptor('numberOfVerticalLines'),

        /**
         * Gets or sets the Property specifying the number of edges around the perimeter of the cylinder.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default 16
         */
        slices : createPropertyDescriptor('slices'),

        /**
         * Gets or sets the boolean Property specifying the visibility of the cylinder.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to fill the cylinder.
         * @memberof CylinderGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the boolean Property specifying whether the cylinder is filled with the provided material.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the boolean Property specifying whether the cylinder is outlined.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Get or sets the enum Property specifying whether the cylinder
         * casts or receives shadows from each light source.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this cylinder will be displayed.
         * @memberof CylinderGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {CylinderGraphics} [result] The object onto which to store the result.
     * @returns {CylinderGraphics} The modified result parameter or a new instance if one was not provided.
     */
    CylinderGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new CylinderGraphics(this);
        }
        result.bottomRadius = this.bottomRadius;
        result.length = this.length;
        result.topRadius = this.topRadius;
        result.show = this.show;
        result.material = this.material;
        result.numberOfVerticalLines = this.numberOfVerticalLines;
        result.slices = this.slices;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {CylinderGraphics} source The object to be merged into this object.
     */
    CylinderGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.bottomRadius = defaultValue(this.bottomRadius, source.bottomRadius);
        this.length = defaultValue(this.length, source.length);
        this.topRadius = defaultValue(this.topRadius, source.topRadius);
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.numberOfVerticalLines = defaultValue(this.numberOfVerticalLines, source.numberOfVerticalLines);
        this.slices = defaultValue(this.slices, source.slices);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return CylinderGraphics;
});

/*global define*/
define('DataSources/EllipseGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes an ellipse defined by a center point and semi-major and semi-minor axes.
     * The ellipse conforms to the curvature of the globe and can be placed on the surface or
     * at altitude and can optionally be extruded into a volume.
     * The center point is determined by the containing {@link Entity}.
     *
     * @alias EllipseGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.semiMajorAxis] The numeric Property specifying the semi-major axis.
     * @param {Property} [options.semiMinorAxis] The numeric Property specifying the semi-minor axis.
     * @param {Property} [options.height=0] A numeric Property specifying the altitude of the ellipse relative to the ellipsoid surface.
     * @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the ellipse's extruded face relative to the ellipsoid surface.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the ellipse.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the ellipse is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the ellipse.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the ellipse is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.numberOfVerticalLines=16] A numeric Property specifying the number of vertical lines to draw along the perimeter for the outline.
     * @param {Property} [options.rotation=0.0] A numeric property specifying the rotation of the ellipse counter-clockwise from north.
     * @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the ellipse texture counter-clockwise from north.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between points on the ellipse.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the ellipse casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this ellipse will be displayed.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Circles and Ellipses.html|Cesium Sandcastle Circles and Ellipses Demo}
     */
    function EllipseGraphics(options) {
        this._semiMajorAxis = undefined;
        this._semiMajorAxisSubscription = undefined;
        this._semiMinorAxis = undefined;
        this._semiMinorAxisSubscription = undefined;
        this._rotation = undefined;
        this._rotationSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._height = undefined;
        this._heightSubscription = undefined;
        this._extrudedHeight = undefined;
        this._extrudedHeightSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._stRotation = undefined;
        this._stRotationSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._numberOfVerticalLines = undefined;
        this._numberOfVerticalLinesSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(EllipseGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof EllipseGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the numeric Property specifying the semi-major axis.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         */
        semiMajorAxis : createPropertyDescriptor('semiMajorAxis'),

        /**
         * Gets or sets the numeric Property specifying the semi-minor axis.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         */
        semiMinorAxis : createPropertyDescriptor('semiMinorAxis'),

        /**
         * Gets or sets the numeric property specifying the rotation of the ellipse clockwise from north.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default 0
         */
        rotation : createPropertyDescriptor('rotation'),

        /**
         * Gets or sets the boolean Property specifying the visibility of the ellipse.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to fill the ellipse.
         * @memberof EllipseGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the ellipse.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default 0.0
         */
        height : createPropertyDescriptor('height'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the ellipse extrusion.
         * Setting this property creates volume starting at height and ending at this altitude.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         */
        extrudedHeight : createPropertyDescriptor('extrudedHeight'),

        /**
         * Gets or sets the numeric Property specifying the angular distance between points on the ellipse.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default {CesiumMath.RADIANS_PER_DEGREE}
         */
        granularity : createPropertyDescriptor('granularity'),

        /**
         * Gets or sets the numeric property specifying the rotation of the ellipse texture counter-clockwise from north.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default 0
         */
        stRotation : createPropertyDescriptor('stRotation'),

        /**
         * Gets or sets the boolean Property specifying whether the ellipse is filled with the provided material.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the ellipse is outlined.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the numeric Property specifying the number of vertical lines to draw along the perimeter for the outline.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default 16
         */
        numberOfVerticalLines : createPropertyDescriptor('numberOfVerticalLines'),
        
        /**
         * Get or sets the enum Property specifying whether the ellipse
         * casts or receives shadows from each light source.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this ellipse will be displayed.
         * @memberof EllipseGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {EllipseGraphics} [result] The object onto which to store the result.
     * @returns {EllipseGraphics} The modified result parameter or a new instance if one was not provided.
     */
    EllipseGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new EllipseGraphics(this);
        }
        result.rotation = this.rotation;
        result.semiMajorAxis = this.semiMajorAxis;
        result.semiMinorAxis = this.semiMinorAxis;
        result.show = this.show;
        result.material = this.material;
        result.height = this.height;
        result.extrudedHeight = this.extrudedHeight;
        result.granularity = this.granularity;
        result.stRotation = this.stRotation;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.numberOfVerticalLines = this.numberOfVerticalLines;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {EllipseGraphics} source The object to be merged into this object.
     */
    EllipseGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.rotation = defaultValue(this.rotation, source.rotation);
        this.semiMajorAxis = defaultValue(this.semiMajorAxis, source.semiMajorAxis);
        this.semiMinorAxis = defaultValue(this.semiMinorAxis, source.semiMinorAxis);
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.height = defaultValue(this.height, source.height);
        this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.stRotation = defaultValue(this.stRotation, source.stRotation);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.numberOfVerticalLines = defaultValue(this.numberOfVerticalLines, source.numberOfVerticalLines);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return EllipseGraphics;
});

/*global define*/
define('DataSources/EllipsoidGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describe an ellipsoid or sphere.  The center position and orientation are determined by the containing {@link Entity}.
     *
     * @alias EllipsoidGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.radii] A {@link Cartesian3} Property specifying the radii of the ellipsoid.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the ellipsoid.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the ellipsoid is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the ellipsoid.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the ellipsoid is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.subdivisions=128] A Property specifying the number of samples per outline ring, determining the granularity of the curvature.
     * @param {Property} [options.stackPartitions=64] A Property specifying the number of stacks.
     * @param {Property} [options.slicePartitions=64] A Property specifying the number of radial slices.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the ellipsoid casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this ellipsoid will be displayed.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Spheres%20and%20Ellipsoids.html|Cesium Sandcastle Spheres and Ellipsoids Demo}
     */
    function EllipsoidGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._radii = undefined;
        this._radiiSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._stackPartitions = undefined;
        this._stackPartitionsSubscription = undefined;
        this._slicePartitions = undefined;
        this._slicePartitionsSubscription = undefined;
        this._subdivisions = undefined;
        this._subdivisionsSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(EllipsoidGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof EllipsoidGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the ellipsoid.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the {@link Cartesian3} {@link Property} specifying the radii of the ellipsoid.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         */
        radii : createPropertyDescriptor('radii'),

        /**
         * Gets or sets the Property specifying the material used to fill the ellipsoid.
         * @memberof EllipsoidGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the boolean Property specifying whether the ellipsoid is filled with the provided material.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the ellipsoid is outlined.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the Property specifying the number of stacks.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default 64
         */
        stackPartitions : createPropertyDescriptor('stackPartitions'),

        /**
         * Gets or sets the Property specifying the number of radial slices.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default 64
         */
        slicePartitions : createPropertyDescriptor('slicePartitions'),

        /**
         * Gets or sets the Property specifying the number of samples per outline ring, determining the granularity of the curvature.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default 128
         */
        subdivisions : createPropertyDescriptor('subdivisions'),

        /**
         * Get or sets the enum Property specifying whether the ellipsoid
         * casts or receives shadows from each light source.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this ellipsoid will be displayed.
         * @memberof EllipsoidGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {EllipsoidGraphics} [result] The object onto which to store the result.
     * @returns {EllipsoidGraphics} The modified result parameter or a new instance if one was not provided.
     */
    EllipsoidGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new EllipsoidGraphics(this);
        }
        result.show = this.show;
        result.radii = this.radii;
        result.material = this.material;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.stackPartitions = this.stackPartitions;
        result.slicePartitions = this.slicePartitions;
        result.subdivisions = this.subdivisions;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;

        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {EllipsoidGraphics} source The object to be merged into this object.
     */
    EllipsoidGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.radii = defaultValue(this.radii, source.radii);
        this.material = defaultValue(this.material, source.material);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.stackPartitions = defaultValue(this.stackPartitions, source.stackPartitions);
        this.slicePartitions = defaultValue(this.slicePartitions, source.slicePartitions);
        this.subdivisions = defaultValue(this.subdivisions, source.subdivisions);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return EllipsoidGraphics;
});

/*global define*/
define('DataSources/LabelGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a two dimensional label located at the position of the containing {@link Entity}.
     * <p>
     * <div align='center'>
     * <img src='images/Label.png' width='400' height='300' /><br />
     * Example labels
     * </div>
     * </p>
     *
     * @alias LabelGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.text] A Property specifying the text. Explicit newlines '\n' are supported.
     * @param {Property} [options.font='10px sans-serif'] A Property specifying the CSS font.
     * @param {Property} [options.style=LabelStyle.FILL] A Property specifying the {@link LabelStyle}.
     * @param {Property} [options.fillColor=Color.WHITE] A Property specifying the fill {@link Color}.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the outline {@link Color}.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the outline width.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the label.
     * @param {Property} [options.showBackground=false] A boolean Property specifying the visibility of the background behind the label.
     * @param {Property} [options.backgroundColor=new Color(0.165, 0.165, 0.165, 0.8)] A Property specifying the background {@link Color}.
     * @param {Property} [options.backgroundPadding=new Cartesian2(7, 5)] A {@link Cartesian2} Property specifying the horizontal and vertical background padding in pixels.
     * @param {Property} [options.scale=1.0] A numeric Property specifying the scale to apply to the text.
     * @param {Property} [options.horizontalOrigin=HorizontalOrigin.CENTER] A Property specifying the {@link HorizontalOrigin}.
     * @param {Property} [options.verticalOrigin=VerticalOrigin.CENTER] A Property specifying the {@link VerticalOrigin}.
     * @param {Property} [options.eyeOffset=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the eye offset.
     * @param {Property} [options.pixelOffset=Cartesian2.ZERO] A {@link Cartesian2} Property specifying the pixel offset.
     * @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera.
     * @param {Property} [options.pixelOffsetScaleByDistance] A {@link NearFarScalar} Property used to set pixelOffset based on distance from the camera.
     * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to set scale based on distance from the camera.
     * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this label will be displayed.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
     */
    function LabelGraphics(options) {
        this._text = undefined;
        this._textSubscription = undefined;
        this._font = undefined;
        this._fontSubscription = undefined;
        this._style = undefined;
        this._styleSubscription = undefined;
        this._fillColor = undefined;
        this._fillColorSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._horizontalOrigin = undefined;
        this._horizontalOriginSubscription = undefined;
        this._verticalOrigin = undefined;
        this._verticalOriginSubscription = undefined;
        this._eyeOffset = undefined;
        this._eyeOffsetSubscription = undefined;
        this._heightReference = undefined;
        this._heightReferenceSubscription = undefined;
        this._pixelOffset = undefined;
        this._pixelOffsetSubscription = undefined;
        this._scale = undefined;
        this._scaleSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._showBackground = undefined;
        this._showBackgroundSubscription = undefined;
        this._backgroundColor = undefined;
        this._backgroundColorSubscription = undefined;
        this._backgroundPadding = undefined;
        this._backgroundPaddingSubscription = undefined;
        this._translucencyByDistance = undefined;
        this._translucencyByDistanceSubscription = undefined;
        this._pixelOffsetScaleByDistance = undefined;
        this._pixelOffsetScaleByDistanceSubscription = undefined;
        this._scaleByDistance = undefined;
        this._scaleByDistanceSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(LabelGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof LabelGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the string Property specifying the text of the label.
         * Explicit newlines '\n' are supported.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        text : createPropertyDescriptor('text'),

        /**
         * Gets or sets the string Property specifying the font in CSS syntax.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/font|CSS font on MDN}
         */
        font : createPropertyDescriptor('font'),

        /**
         * Gets or sets the Property specifying the {@link LabelStyle}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        style : createPropertyDescriptor('style'),

        /**
         * Gets or sets the Property specifying the fill {@link Color}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        fillColor : createPropertyDescriptor('fillColor'),

        /**
         * Gets or sets the Property specifying the outline {@link Color}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the outline width.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the Property specifying the {@link HorizontalOrigin}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        horizontalOrigin : createPropertyDescriptor('horizontalOrigin'),

        /**
         * Gets or sets the Property specifying the {@link VerticalOrigin}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        verticalOrigin : createPropertyDescriptor('verticalOrigin'),

        /**
         * Gets or sets the {@link Cartesian3} Property specifying the label's offset in eye coordinates.
         * Eye coordinates is a left-handed coordinate system, where <code>x</code> points towards the viewer's
         * right, <code>y</code> points up, and <code>z</code> points into the screen.
         * <p>
         * An eye offset is commonly used to arrange multiple labels or objects at the same position, e.g., to
         * arrange a label above its corresponding 3D model.
         * </p>
         * Below, the label is positioned at the center of the Earth but an eye offset makes it always
         * appear on top of the Earth regardless of the viewer's or Earth's orientation.
         * <p>
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><img src='images/Billboard.setEyeOffset.one.png' width='250' height='188' /></td>
         * <td align='center'><img src='images/Billboard.setEyeOffset.two.png' width='250' height='188' /></td>
         * </tr></table>
         * <code>l.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);</code><br /><br />
         * </div>
         * </p>
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default Cartesian3.ZERO
         */
        eyeOffset : createPropertyDescriptor('eyeOffset'),

        /**
         * Gets or sets the Property specifying the {@link HeightReference}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default HeightReference.NONE
         */
        heightReference : createPropertyDescriptor('heightReference'),

        /**
         * Gets or sets the {@link Cartesian2} Property specifying the label's pixel offset in screen space
         * from the origin of this label.  This is commonly used to align multiple labels and labels at
         * the same position, e.g., an image and text.  The screen space origin is the top, left corner of the
         * canvas; <code>x</code> increases from left to right, and <code>y</code> increases from top to bottom.
         * <p>
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><code>default</code><br/><img src='images/Label.setPixelOffset.default.png' width='250' height='188' /></td>
         * <td align='center'><code>l.pixeloffset = new Cartesian2(25, 75);</code><br/><img src='images/Label.setPixelOffset.x50y-25.png' width='250' height='188' /></td>
         * </tr></table>
         * The label's origin is indicated by the yellow point.
         * </div>
         * </p>
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default Cartesian2.ZERO
         */
        pixelOffset : createPropertyDescriptor('pixelOffset'),

        /**
         * Gets or sets the numeric Property specifying the uniform scale to apply to the image.
         * A scale greater than <code>1.0</code> enlarges the label while a scale less than <code>1.0</code> shrinks it.
         * <p>
         * <div align='center'>
         * <img src='images/Label.setScale.png' width='400' height='300' /><br/>
         * From left to right in the above image, the scales are <code>0.5</code>, <code>1.0</code>,
         * and <code>2.0</code>.
         * </div>
         * </p>
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        scale : createPropertyDescriptor('scale'),

        /**
         * Gets or sets the boolean Property specifying the visibility of the label.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the boolean Property specifying the visibility of the background behind the label.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default false
         */
        showBackground : createPropertyDescriptor('showBackground'),

        /**
         * Gets or sets the Property specifying the background {@link Color}.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default new Color(0.165, 0.165, 0.165, 0.8)
         */
        backgroundColor : createPropertyDescriptor('backgroundColor'),

        /**
         * Gets or sets the {@link Cartesian2} Property specifying the label's horizontal and vertical
         * background padding in pixels.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         * @default new Cartesian2(7, 5)
         */
        backgroundPadding : createPropertyDescriptor('backgroundPadding'),

        /**
         * Gets or sets {@link NearFarScalar} Property specifying the translucency of the label based on the distance from the camera.
         * A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the label's translucency remains clamped to the nearest bound.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        translucencyByDistance : createPropertyDescriptor('translucencyByDistance'),

        /**
         * Gets or sets {@link NearFarScalar} Property specifying the pixel offset of the label based on the distance from the camera.
         * A label's pixel offset will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the label's pixel offset remains clamped to the nearest bound.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        pixelOffsetScaleByDistance : createPropertyDescriptor('pixelOffsetScaleByDistance'),

        /**
         * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera.
         * A label's scale will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the label's scale remains clamped to the nearest bound.  If undefined,
         * scaleByDistance will be disabled.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        scaleByDistance : createPropertyDescriptor('scaleByDistance'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this label will be displayed.
         * @memberof LabelGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {LabelGraphics} [result] The object onto which to store the result.
     * @returns {LabelGraphics} The modified result parameter or a new instance if one was not provided.
     */
    LabelGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new LabelGraphics(this);
        }
        result.text = this.text;
        result.font = this.font;
        result.show = this.show;
        result.style = this.style;
        result.fillColor = this.fillColor;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.showBackground = this.showBackground;
        result.backgroundColor = this.backgroundColor;
        result.backgroundPadding = this.backgroundPadding;
        result.scale = this.scale;
        result.horizontalOrigin = this.horizontalOrigin;
        result.verticalOrigin = this.verticalOrigin;
        result.eyeOffset = this.eyeOffset;
        result.heightReference = this.heightReference;
        result.pixelOffset = this.pixelOffset;
        result.translucencyByDistance = this.translucencyByDistance;
        result.pixelOffsetScaleByDistance = this.pixelOffsetScaleByDistance;
        result.scaleByDistance = this.scaleByDistance;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {LabelGraphics} source The object to be merged into this object.
     */
    LabelGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.text = defaultValue(this.text, source.text);
        this.font = defaultValue(this.font, source.font);
        this.show = defaultValue(this.show, source.show);
        this.style = defaultValue(this.style, source.style);
        this.fillColor = defaultValue(this.fillColor, source.fillColor);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.showBackground = defaultValue(this.showBackground, source.showBackground);
        this.backgroundColor = defaultValue(this.backgroundColor, source.backgroundColor);
        this.backgroundPadding = defaultValue(this.backgroundPadding, source.backgroundPadding);
        this.scale = defaultValue(this.scale, source.scale);
        this.horizontalOrigin = defaultValue(this.horizontalOrigin, source.horizontalOrigin);
        this.verticalOrigin = defaultValue(this.verticalOrigin, source.verticalOrigin);
        this.eyeOffset = defaultValue(this.eyeOffset, source.eyeOffset);
        this.heightReference = defaultValue(this.heightReference, source.heightReference);
        this.pixelOffset = defaultValue(this.pixelOffset, source.pixelOffset);
        this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance);
        this.pixelOffsetScaleByDistance = defaultValue(this._pixelOffsetScaleByDistance, source.pixelOffsetScaleByDistance);
        this.scaleByDistance = defaultValue(this._scaleByDistance, source.scaleByDistance);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return LabelGraphics;
});

/*global define*/
define('DataSources/NodeTransformationProperty',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        '../Core/TranslationRotationScale',
        './createPropertyDescriptor',
        './Property'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        Event,
        TranslationRotationScale,
        createPropertyDescriptor,
        Property) {
    'use strict';

    var defaultNodeTransformation = new TranslationRotationScale();

    /**
     * A {@link Property} that produces {@link TranslationRotationScale} data.
     * @alias NodeTransformationProperty
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.translation=Cartesian3.ZERO] A {@link Cartesian3} Property specifying the (x, y, z) translation to apply to the node.
     * @param {Property} [options.rotation=Quaternion.IDENTITY] A {@link Quaternion} Property specifying the (x, y, z, w) rotation to apply to the node.
     * @param {Property} [options.scale=new Cartesian3(1.0, 1.0, 1.0)] A {@link Cartesian3} Property specifying the (x, y, z) scaling to apply to the node.
     */
    var NodeTransformationProperty = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();
        this._translation = undefined;
        this._translationSubscription = undefined;
        this._rotation = undefined;
        this._rotationSubscription = undefined;
        this._scale = undefined;
        this._scaleSubscription = undefined;

        this.translation = options.translation;
        this.rotation = options.rotation;
        this.scale = options.scale;
    };

    defineProperties(NodeTransformationProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof NodeTransformationProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._translation) &&
                       Property.isConstant(this._rotation) &&
                       Property.isConstant(this._scale);
            }
        },

        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof NodeTransformationProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the {@link Cartesian3} Property specifying the (x, y, z) translation to apply to the node.
         * @memberof NodeTransformationProperty.prototype
         * @type {Property}
         * @default Cartesian3.ZERO
         */
        translation : createPropertyDescriptor('translation'),

        /**
         * Gets or sets the {@link Quaternion} Property specifying the (x, y, z, w) rotation to apply to the node.
         * @memberof NodeTransformationProperty.prototype
         * @type {Property}
         * @default Quaternion.IDENTITY
         */
        rotation : createPropertyDescriptor('rotation'),

        /**
         * Gets or sets the {@link Cartesian3} Property specifying the (x, y, z) scaling to apply to the node.
         * @memberof NodeTransformationProperty.prototype
         * @type {Property}
         * @default new Cartesian3(1.0, 1.0, 1.0)
         */
        scale : createPropertyDescriptor('scale')
    });

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {TranslationRotationScale} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {TranslationRotationScale} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    NodeTransformationProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = new TranslationRotationScale();
        }

        result.translation = Property.getValueOrClonedDefault(this._translation, time, defaultNodeTransformation.translation, result.translation);
        result.rotation = Property.getValueOrClonedDefault(this._rotation, time, defaultNodeTransformation.rotation, result.rotation);
        result.scale = Property.getValueOrClonedDefault(this._scale, time, defaultNodeTransformation.scale, result.scale);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    NodeTransformationProperty.prototype.equals = function(other) {
        return this === other ||
               (other instanceof NodeTransformationProperty &&
                Property.equals(this._translation, other._translation) &&
                Property.equals(this._rotation, other._rotation) &&
                Property.equals(this._scale, other._scale));
    };

    return NodeTransformationProperty;
});

/*global define*/
define('DataSources/PropertyBag',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './ConstantProperty',
        './createPropertyDescriptor',
        './Property'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        ConstantProperty,
        createPropertyDescriptor,
        Property) {
    'use strict';

    /**
     * A {@link Property} whose value is a key-value mapping of property names to the computed value of other properties.
     *
     * @alias PropertyBag
     * @constructor
     *
     * @param {Object} [value] An object, containing key-value mapping of property names to properties.
     * @param {Function} [createPropertyCallback] A function that will be called when the value of any of the properties in value are not a Property.
     */
    var PropertyBag = function(value, createPropertyCallback) {
        this._propertyNames = [];
        this._definitionChanged = new Event();

        if (defined(value)) {
            this.merge(value, createPropertyCallback);
        }
    };

    defineProperties(PropertyBag.prototype, {
        /**
         * Gets the names of all properties registered on this instance.
         * @memberof PropertyBag.prototype
         * @type {Array}
         */
        propertyNames : {
            get : function() {
                return this._propertyNames;
            }
        },
        /**
         * Gets a value indicating if this property is constant.  This property
         * is considered constant if all property items in this object are constant.
         * @memberof PropertyBag.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                var propertyNames = this._propertyNames;
                for (var i = 0, len = propertyNames.length; i < len; i++) {
                    if (!Property.isConstant(this[propertyNames[i]])) {
                        return false;
                    }
                }
                return true;
            }
        },
        /**
         * Gets the event that is raised whenever the set of properties contained in this
         * object changes, or one of the properties itself changes.
         *
         * @memberof PropertyBag.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        }
    });

    /**
     * Determines if this object has defined a property with the given name.
     *
     * @param {String} propertyName The name of the property to check for.
     *
     * @returns {Boolean} True if this object has defined a property with the given name, false otherwise.
     */
    PropertyBag.prototype.hasProperty = function(propertyName) {
        return this._propertyNames.indexOf(propertyName) !== -1;
    };

    function createConstantProperty(value) {
        return new ConstantProperty(value);
    }

    /**
     * Adds a property to this object.
     *
     * @param {String} propertyName The name of the property to add.
     * @param {Any} [value] The value of the new property, if provided.
     * @param {Function} [createPropertyCallback] A function that will be called when the value of this new property is set to a value that is not a Property.
     *
     * @exception {DeveloperError} "propertyName" is already a registered property.
     */
    PropertyBag.prototype.addProperty = function(propertyName, value, createPropertyCallback) {
        var propertyNames = this._propertyNames;

                if (!defined(propertyName)) {
            throw new DeveloperError('propertyName is required.');
        }
        if (propertyNames.indexOf(propertyName) !== -1) {
            throw new DeveloperError(propertyName + ' is already a registered property.');
        }
        
        propertyNames.push(propertyName);
        Object.defineProperty(this, propertyName, createPropertyDescriptor(propertyName, true, defaultValue(createPropertyCallback, createConstantProperty)));

        if (defined(value)) {
            this[propertyName] = value;
        }

        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Removed a property previously added with addProperty.
     *
     * @param {String} propertyName The name of the property to remove.
     *
     * @exception {DeveloperError} "propertyName" is not a registered property.
     */
    PropertyBag.prototype.removeProperty = function(propertyName) {
        var propertyNames = this._propertyNames;
        var index = propertyNames.indexOf(propertyName);

                if (!defined(propertyName)) {
            throw new DeveloperError('propertyName is required.');
        }
        if (index === -1) {
            throw new DeveloperError(propertyName + ' is not a registered property.');
        }
        
        this._propertyNames.splice(index, 1);
        delete this[propertyName];

        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Gets the value of this property.  Each contained property will be evaluated at the given time, and the overall
     * result will be an object, mapping property names to those values.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * Note that any properties in result which are not part of this PropertyBag will be left as-is.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PropertyBag.prototype.getValue = function(time, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        if (!defined(result)) {
            result = {};
        }

        var propertyNames = this._propertyNames;
        for (var i = 0, len = propertyNames.length; i < len; i++) {
            var propertyName = propertyNames[i];
            result[propertyName] = Property.getValueOrUndefined(this[propertyName], time, result[propertyName]);
        }
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {Object} source The object to be merged into this object.
     * @param {Function} [createPropertyCallback] A function that will be called when the value of any of the properties in value are not a Property.
     */
    PropertyBag.prototype.merge = function(source, createPropertyCallback) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        var propertyNames = this._propertyNames;
        var sourcePropertyNames = defined(source._propertyNames) ? source._propertyNames : Object.keys(source);
        for (var i = 0, len = sourcePropertyNames.length; i < len; i++) {
            var name = sourcePropertyNames[i];

            var targetProperty = this[name];
            var sourceProperty = source[name];

            //Custom properties that are registered on the source must also be added to this.
            if (!defined(targetProperty) && propertyNames.indexOf(name) === -1) {
                this.addProperty(name, undefined, createPropertyCallback);
            }

            if (defined(sourceProperty)) {
                if (defined(targetProperty)) {
                    if (defined(targetProperty.merge)) {
                        targetProperty.merge(sourceProperty);
                    }
                } else if (defined(sourceProperty.merge) && defined(sourceProperty.clone)) {
                    this[name] = sourceProperty.clone();
                } else {
                    this[name] = sourceProperty;
                }
            }
        }
    };

    function propertiesEqual(a, b) {
        var aPropertyNames = a._propertyNames;
        var bPropertyNames = b._propertyNames;

        var len = aPropertyNames.length;
        if (len !== bPropertyNames.length) {
            return false;
        }

        for (var aIndex = 0; aIndex < len; ++aIndex) {
            var name = aPropertyNames[aIndex];
            var bIndex = bPropertyNames.indexOf(name);
            if (bIndex === -1) {
                return false;
            }
            if (!Property.equals(a[name], b[name])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PropertyBag.prototype.equals = function(other) {
        return this === other || //
               (other instanceof PropertyBag && //
                propertiesEqual(this, other));
    };

    return PropertyBag;
});

/*global define*/
define('DataSources/ModelGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createPropertyDescriptor',
        './NodeTransformationProperty',
        './PropertyBag'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createPropertyDescriptor,
        NodeTransformationProperty,
        PropertyBag) {
    'use strict';

    function createNodeTransformationProperty(value) {
        return new NodeTransformationProperty(value);
    }

    function createNodeTransformationPropertyBag(value) {
        return new PropertyBag(value, createNodeTransformationProperty);
    }

    /**
     * A 3D model based on {@link https://github.com/KhronosGroup/glTF|glTF}, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
     * The position and orientation of the model is determined by the containing {@link Entity}.
     * <p>
     * Cesium includes support for glTF geometry, materials, animations, and skinning.
     * Cameras and lights are not currently supported.
     * </p>
     *
     * @alias ModelGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.uri] A string Property specifying the URI of the glTF asset.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the model.
     * @param {Property} [options.scale=1.0] A numeric Property specifying a uniform linear scale.
     * @param {Property} [options.minimumPixelSize=0.0] A numeric Property specifying the approximate minimum pixel size of the model regardless of zoom.
     * @param {Property} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
     * @param {Property} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
     * @param {Property} [options.runAnimations=true] A boolean Property specifying if glTF animations specified in the model should be started.
     * @param {Property} [options.nodeTransformations] An object, where keys are names of nodes, and values are {@link TranslationRotationScale} Properties describing the transformation to apply to that node.
     * @param {Property} [options.shadows=ShadowMode.ENABLED] An enum Property specifying whether the model casts or receives shadows from each light source.
     * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this model will be displayed.
     * @param {Property} [options.silhouetteColor=Color.RED] A Property specifying the {@link Color} of the silhouette.
     * @param {Property} [options.silhouetteSize=0.0] A numeric Property specifying the size of the silhouette in pixels.
     * @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} that blends with the model's rendered color.
     * @param {Property} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] An enum Property specifying how the color blends with the model.
     * @param {Property} [options.colorBlendAmount=0.5] A numeric Property specifying the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
     *
     * @see {@link http://cesiumjs.org/2014/03/03/Cesium-3D-Models-Tutorial/|3D Models Tutorial}
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle 3D Models Demo}
     */
    function ModelGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._scale = undefined;
        this._scaleSubscription = undefined;
        this._minimumPixelSize = undefined;
        this._minimumPixelSizeSubscription = undefined;
        this._maximumScale = undefined;
        this._maximumScaleSubscription = undefined;
        this._incrementallyLoadTextures = undefined;
        this._incrementallyLoadTexturesSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._uri = undefined;
        this._uriSubscription = undefined;
        this._runAnimations = undefined;
        this._runAnimationsSubscription = undefined;
        this._nodeTransformations = undefined;
        this._nodeTransformationsSubscription = undefined;
        this._heightReference = undefined;
        this._heightReferenceSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._silhouetteColor = undefined;
        this._silhouetteColorSubscription = undefined;
        this._silhouetteSize = undefined;
        this._silhouetteSizeSubscription = undefined;
        this._color = undefined;
        this._colorSubscription = undefined;
        this._colorBlendMode = undefined;
        this._colorBlendModeSubscription = undefined;
        this._colorBlendAmount = undefined;
        this._colorBlendAmountSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(ModelGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof ModelGraphics.prototype
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the model.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the numeric Property specifying a uniform linear scale
         * for this model. Values greater than 1.0 increase the size of the model while
         * values less than 1.0 decrease it.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        scale : createPropertyDescriptor('scale'),

        /**
         * Gets or sets the numeric Property specifying the approximate minimum
         * pixel size of the model regardless of zoom. This can be used to ensure that
         * a model is visible even when the viewer zooms out.  When <code>0.0</code>,
         * no minimum size is enforced.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default 0.0
         */
        minimumPixelSize : createPropertyDescriptor('minimumPixelSize'),

        /**
         * Gets or sets the numeric Property specifying the maximum scale
         * size of a model. This property is used as an upper limit for
         * {@link ModelGraphics#minimumPixelSize}.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         */
        maximumScale : createPropertyDescriptor('maximumScale'),

        /**
         * Get or sets the boolean Property specifying whether textures
         * may continue to stream in after the model is loaded.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         */
        incrementallyLoadTextures : createPropertyDescriptor('incrementallyLoadTextures'),

        /**
         * Get or sets the enum Property specifying whether the model
         * casts or receives shadows from each light source.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default ShadowMode.ENABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the string Property specifying the URI of the glTF asset.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         */
        uri : createPropertyDescriptor('uri'),

        /**
         * Gets or sets the boolean Property specifying if glTF animations should be run.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default true
         */
        runAnimations : createPropertyDescriptor('runAnimations'),

        /**
         * Gets or sets the set of node transformations to apply to this model.  This is represented as an {@link PropertyBag}, where keys are
         * names of nodes, and values are {@link TranslationRotationScale} Properties describing the transformation to apply to that node.
         * @memberof ModelGraphics.prototype
         * @type {PropertyBag}
         */
        nodeTransformations : createPropertyDescriptor('nodeTransformations', undefined, createNodeTransformationPropertyBag),

        /**
         * Gets or sets the Property specifying the {@link HeightReference}.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default HeightReference.NONE
         */
        heightReference : createPropertyDescriptor('heightReference'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this model will be displayed.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the silhouette.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default Color.RED
         */
        silhouetteColor: createPropertyDescriptor('silhouetteColor'),

        /**
         * Gets or sets the numeric Property specifying the size of the silhouette in pixels.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default 0.0
         */
        silhouetteSize : createPropertyDescriptor('silhouetteSize'),

        /**
         * Gets or sets the Property specifying the {@link Color} that blends with the model's rendered color.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color'),

        /**
         * Gets or sets the enum Property specifying how the color blends with the model.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default ColorBlendMode.HIGHLIGHT
         */
        colorBlendMode : createPropertyDescriptor('colorBlendMode'),

        /**
         * A numeric Property specifying the color strength when the <code>colorBlendMode</code> is MIX.
         * A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with
         * any value in-between resulting in a mix of the two.
         * @memberof ModelGraphics.prototype
         * @type {Property}
         * @default 0.5
         */
        colorBlendAmount : createPropertyDescriptor('colorBlendAmount')
    });

    /**
     * Duplicates this instance.
     *
     * @param {ModelGraphics} [result] The object onto which to store the result.
     * @returns {ModelGraphics} The modified result parameter or a new instance if one was not provided.
     */
    ModelGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new ModelGraphics(this);
        }
        result.show = this.show;
        result.scale = this.scale;
        result.minimumPixelSize = this.minimumPixelSize;
        result.maximumScale = this.maximumScale;
        result.incrementallyLoadTextures = this.incrementallyLoadTextures;
        result.shadows = this.shadows;
        result.uri = this.uri;
        result.runAnimations = this.runAnimations;
        result.nodeTransformations = this.nodeTransformations;
        result.heightReference = this._heightReference;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        result.silhouetteColor = this.silhouetteColor;
        result.silhouetteSize = this.silhouetteSize;
        result.color = this.color;
        result.colorBlendMode = this.colorBlendMode;
        result.colorBlendAmount = this.colorBlendAmount;

        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {ModelGraphics} source The object to be merged into this object.
     */
    ModelGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.scale = defaultValue(this.scale, source.scale);
        this.minimumPixelSize = defaultValue(this.minimumPixelSize, source.minimumPixelSize);
        this.maximumScale = defaultValue(this.maximumScale, source.maximumScale);
        this.incrementallyLoadTextures = defaultValue(this.incrementallyLoadTextures, source.incrementallyLoadTextures);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.uri = defaultValue(this.uri, source.uri);
        this.runAnimations = defaultValue(this.runAnimations, source.runAnimations);
        this.heightReference = defaultValue(this.heightReference, source.heightReference);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
        this.silhouetteColor = defaultValue(this.silhouetteColor, source.silhouetteColor);
        this.silhouetteSize = defaultValue(this.silhouetteSize, source.silhouetteSize);
        this.color = defaultValue(this.color, source.color);
        this.colorBlendMode = defaultValue(this.colorBlendMode, source.colorBlendMode);
        this.colorBlendAmount = defaultValue(this.colorBlendAmount, source.colorBlendAmount);

        var sourceNodeTransformations = source.nodeTransformations;
        if (defined(sourceNodeTransformations)) {
            var targetNodeTransformations = this.nodeTransformations;
            if (defined(targetNodeTransformations)) {
                targetNodeTransformations.merge(sourceNodeTransformations);
            } else {
                this.nodeTransformations = new PropertyBag(sourceNodeTransformations, createNodeTransformationProperty);
            }
        }
    };

    return ModelGraphics;
});

/*global define*/
define('DataSources/PathGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a polyline defined as the path made by an {@link Entity} as it moves over time.
     *
     * @alias PathGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.leadTime] A Property specifying the number of seconds behind the object to show.
     * @param {Property} [options.trailTime] A Property specifying the number of seconds in front of the object to show.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the path.
     * @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the path.
     * @param {Property} [options.resolution=60] A numeric Property specifying the maximum number of seconds to step when sampling the position.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this path will be displayed.
     */
    function PathGraphics(options) {
        this._material = undefined;
        this._materialSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._width = undefined;
        this._widthSubscription = undefined;
        this._resolution = undefined;
        this._resolutionSubscription = undefined;
        this._leadTime = undefined;
        this._leadTimeSubscription = undefined;
        this._trailTime = undefined;
        this._trailTimeSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(PathGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof PathGraphics.prototype
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the path.
         * @memberof PathGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to draw the path.
         * @memberof PathGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the numeric Property specifying the width in pixels.
         * @memberof PathGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        width : createPropertyDescriptor('width'),

        /**
         * Gets or sets the Property specifying the maximum number of seconds to step when sampling the position.
         * @memberof PathGraphics.prototype
         * @type {Property}
         * @default 60
         */
        resolution : createPropertyDescriptor('resolution'),

        /**
         * Gets or sets the Property specifying the number of seconds in front of the object to show.
         * @memberof PathGraphics.prototype
         * @type {Property}
         */
        leadTime : createPropertyDescriptor('leadTime'),

        /**
         * Gets or sets the Property specifying the number of seconds behind the object to show.
         * @memberof PathGraphics.prototype
         * @type {Property}
         */
        trailTime : createPropertyDescriptor('trailTime'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this path will be displayed.
         * @memberof PathGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {PathGraphics} [result] The object onto which to store the result.
     * @returns {PathGraphics} The modified result parameter or a new instance if one was not provided.
     */
    PathGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new PathGraphics(this);
        }
        result.material = this.material;
        result.width = this.width;
        result.resolution = this.resolution;
        result.show = this.show;
        result.leadTime = this.leadTime;
        result.trailTime = this.trailTime;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {PathGraphics} source The object to be merged into this object.
     */
    PathGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.material = defaultValue(this.material, source.material);
        this.width = defaultValue(this.width, source.width);
        this.resolution = defaultValue(this.resolution, source.resolution);
        this.show = defaultValue(this.show, source.show);
        this.leadTime = defaultValue(this.leadTime, source.leadTime);
        this.trailTime = defaultValue(this.trailTime, source.trailTime);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return PathGraphics;
});

/*global define*/
define('DataSources/PointGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a graphical point located at the position of the containing {@link Entity}.
     *
     * @alias PointGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the point.
     * @param {Property} [options.pixelSize=1] A numeric Property specifying the size in pixels.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=0] A numeric Property specifying the the outline width in pixels.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the point.
     * @param {Property} [options.scaleByDistance] A {@link NearFarScalar} Property used to scale the point based on distance.
     * @param {Property} [options.translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera.
     * @param {Property} [options.heightReference=HeightReference.NONE] A Property specifying what the height is relative to.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this point will be displayed.
     */
    function PointGraphics(options) {
        this._color = undefined;
        this._colorSubscription = undefined;
        this._pixelSize = undefined;
        this._pixelSizeSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._show = undefined;
        this._showSubscription = undefined;
        this._scaleByDistance = undefined;
        this._scaleByDistanceSubscription = undefined;
        this._translucencyByDistance = undefined;
        this._translucencyByDistanceSubscription = undefined;
        this._heightReference = undefined;
        this._heightReferenceSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(PointGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof PointGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the Property specifying the {@link Color} of the point.
         * @memberof PointGraphics.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color'),

        /**
         * Gets or sets the numeric Property specifying the size in pixels.
         * @memberof PointGraphics.prototype
         * @type {Property}
         * @default 1
         */
        pixelSize : createPropertyDescriptor('pixelSize'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof PointGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the the outline width in pixels.
         * @memberof PointGraphics.prototype
         * @type {Property}
         * @default 0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the boolean Property specifying the visibility of the point.
         * @memberof PointGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the {@link NearFarScalar} Property used to scale the point based on distance.
         * If undefined, a constant size is used.
         * @memberof PointGraphics.prototype
         * @type {Property}
         */
        scaleByDistance : createPropertyDescriptor('scaleByDistance'),

        /**
         * Gets or sets {@link NearFarScalar} Property specifying the translucency of the point based on the distance from the camera.
         * A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the points's translucency remains clamped to the nearest bound.
         * @memberof PointGraphics.prototype
         * @type {Property}
         */
        translucencyByDistance : createPropertyDescriptor('translucencyByDistance'),

        /**
         * Gets or sets the Property specifying the {@link HeightReference}.
         * @memberof PointGraphics.prototype
         * @type {Property}
         * @default HeightReference.NONE
         */
        heightReference : createPropertyDescriptor('heightReference'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this point will be displayed.
         * @memberof PointGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {PointGraphics} [result] The object onto which to store the result.
     * @returns {PointGraphics} The modified result parameter or a new instance if one was not provided.
     */
    PointGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new PointGraphics(this);
        }
        result.color = this.color;
        result.pixelSize = this.pixelSize;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.show = this.show;
        result.scaleByDistance = this.scaleByDistance;
        result.translucencyByDistance = this._translucencyByDistance;
        result.heightReference = this.heightReference;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {PointGraphics} source The object to be merged into this object.
     */
    PointGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.color = defaultValue(this.color, source.color);
        this.pixelSize = defaultValue(this.pixelSize, source.pixelSize);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.show = defaultValue(this.show, source.show);
        this.scaleByDistance = defaultValue(this.scaleByDistance, source.scaleByDistance);
        this.translucencyByDistance = defaultValue(this._translucencyByDistance, source.translucencyByDistance);
        this.heightReference = defaultValue(this.heightReference, source.heightReference);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return PointGraphics;
});

/*global define*/
define('DataSources/PolygonGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a polygon defined by an hierarchy of linear rings which make up the outer shape and any nested holes.
     * The polygon conforms to the curvature of the globe and can be placed on the surface or
     * at altitude and can optionally be extruded into a volume.
     *
     * @alias PolygonGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.hierarchy] A Property specifying the {@link PolygonHierarchy}.
     * @param {Property} [options.height=0] A numeric Property specifying the altitude of the polygon relative to the ellipsoid surface.
     * @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the polygon's extruded face relative to the ellipsoid surface.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the polygon.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the polygon is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the polygon.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the polygon is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the polygon texture counter-clockwise from north.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point.
     * @param {Property} [options.perPositionHeight=false] A boolean specifying whether or not the the height of each position is used.
     * @param {Boolean} [options.closeTop=true] When false, leaves off the top of an extruded polygon open.
     * @param {Boolean} [options.closeBottom=true] When false, leaves off the bottom of an extruded polygon open.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polygon casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polygon will be displayed.
     *
     * @see Entity
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polygon.html|Cesium Sandcastle Polygon Demo}
     */
    function PolygonGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._hierarchy = undefined;
        this._hierarchySubscription = undefined;
        this._height = undefined;
        this._heightSubscription = undefined;
        this._extrudedHeight = undefined;
        this._extrudedHeightSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._stRotation = undefined;
        this._stRotationSubscription = undefined;
        this._perPositionHeight = undefined;
        this._perPositionHeightSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._definitionChanged = new Event();
        this._closeTop = undefined;
        this._closeTopSubscription = undefined;
        this._closeBottom = undefined;
        this._closeBottomSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(PolygonGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof PolygonGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the polygon.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to fill the polygon.
         * @memberof PolygonGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the Property specifying the {@link PolygonHierarchy}.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         */
        hierarchy : createPropertyDescriptor('hierarchy'),

        /**
         * Gets or sets the numeric Property specifying the constant altitude of the polygon.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default 0.0
         */
        height : createPropertyDescriptor('height'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the polygon extrusion.
         * If {@link PolygonGraphics#perPositionHeight} is false, the volume starts at {@link PolygonGraphics#height} and ends at this altitude.
         * If {@link PolygonGraphics#perPositionHeight} is true, the volume starts at the height of each {@link PolygonGraphics#hierarchy} position and ends at this altitude.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         */
        extrudedHeight : createPropertyDescriptor('extrudedHeight'),

        /**
         * Gets or sets the numeric Property specifying the angular distance between points on the polygon.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default {CesiumMath.RADIANS_PER_DEGREE}
         */
        granularity : createPropertyDescriptor('granularity'),

        /**
         * Gets or sets the numeric property specifying the rotation of the polygon texture counter-clockwise from north.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default 0
         */
        stRotation : createPropertyDescriptor('stRotation'),

        /**
         * Gets or sets the boolean Property specifying whether the polygon is filled with the provided material.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the polygon is outlined.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the boolean specifying whether or not the the height of each position is used.
         * If true, the shape will have non-uniform altitude defined by the height of each {@link PolygonGraphics#hierarchy} position.
         * If false, the shape will have a constant altitude as specified by {@link PolygonGraphics#height}.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         */
        perPositionHeight : createPropertyDescriptor('perPositionHeight'),

        /**
         * Gets or sets a boolean specifying whether or not the top of an extruded polygon is included.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         */
        closeTop : createPropertyDescriptor('closeTop'),

        /**
         * Gets or sets a boolean specifying whether or not the bottom of an extruded polygon is included.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         */
        closeBottom : createPropertyDescriptor('closeBottom'),

        /**
         * Get or sets the enum Property specifying whether the polygon
         * casts or receives shadows from each light source.
         * @memberof PolygonGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polygon will be displayed.
         * @memberof BillboardGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {PolygonGraphics} [result] The object onto which to store the result.
     * @returns {PolygonGraphics} The modified result parameter or a new instance if one was not provided.
     */
    PolygonGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new PolygonGraphics(this);
        }
        result.show = this.show;
        result.material = this.material;
        result.hierarchy = this.hierarchy;
        result.height = this.height;
        result.extrudedHeight = this.extrudedHeight;
        result.granularity = this.granularity;
        result.stRotation = this.stRotation;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.perPositionHeight = this.perPositionHeight;
        result.closeTop = this.closeTop;
        result.closeBottom = this.closeBottom;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {PolygonGraphics} source The object to be merged into this object.
     */
    PolygonGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.hierarchy = defaultValue(this.hierarchy, source.hierarchy);
        this.height = defaultValue(this.height, source.height);
        this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.stRotation = defaultValue(this.stRotation, source.stRotation);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.perPositionHeight = defaultValue(this.perPositionHeight, source.perPositionHeight);
        this.closeTop = defaultValue(this.closeTop, source.closeTop);
        this.closeBottom = defaultValue(this.closeBottom, source.closeBottom);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return PolygonGraphics;
});

/*global define*/
define('DataSources/PolylineGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a polyline defined as a line strip. The first two positions define a line segment,
     * and each additional position defines a line segment from the previous position. The segments
     * can be linear connected points or great arcs.
     *
     * @alias PolylineGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions that define the line strip.
     * @param {Property} [options.followSurface=true] A boolean Property specifying whether the line segments should be great arcs or linearly connected.
     * @param {Property} [options.width=1.0] A numeric Property specifying the width in pixels.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the polyline.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to draw the polyline.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude if followSurface is true.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the polyline casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this polyline will be displayed.
     *
     * @see Entity
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline.html|Cesium Sandcastle Polyline Demo}
     */
    function PolylineGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._positions = undefined;
        this._positionsSubscription = undefined;
        this._followSurface = undefined;
        this._followSurfaceSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._widthSubscription = undefined;
        this._width = undefined;
        this._widthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(PolylineGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof PolylineGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the polyline.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to draw the polyline.
         * @memberof PolylineGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the Property specifying the array of {@link Cartesian3}
         * positions that define the line strip.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         */
        positions : createPropertyDescriptor('positions'),

        /**
         * Gets or sets the numeric Property specifying the width in pixels.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        width : createPropertyDescriptor('width'),

        /**
         * Gets or sets the boolean Property specifying whether the line segments
         * should be great arcs or linearly connected.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         * @default true
         */
        followSurface : createPropertyDescriptor('followSurface'),

        /**
         * Gets or sets the numeric Property specifying the angular distance between each latitude and longitude if followSurface is true.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         * @default Cesium.Math.RADIANS_PER_DEGREE
         */
        granularity : createPropertyDescriptor('granularity'),
        
        /**
         * Get or sets the enum Property specifying whether the polyline
         * casts or receives shadows from each light source.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this polyline will be displayed.
         * @memberof PolylineGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {PolylineGraphics} [result] The object onto which to store the result.
     * @returns {PolylineGraphics} The modified result parameter or a new instance if one was not provided.
     */
    PolylineGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new PolylineGraphics(this);
        }
        result.show = this.show;
        result.material = this.material;
        result.positions = this.positions;
        result.width = this.width;
        result.followSurface = this.followSurface;
        result.granularity = this.granularity;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {PolylineGraphics} source The object to be merged into this object.
     */
    PolylineGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.positions = defaultValue(this.positions, source.positions);
        this.width = defaultValue(this.width, source.width);
        this.followSurface = defaultValue(this.followSurface, source.followSurface);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return PolylineGraphics;
});

/*global define*/
define('DataSources/PolylineVolumeGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a polyline volume defined as a line strip and corresponding two dimensional shape which is extruded along it.
     * The resulting volume conforms to the curvature of the globe.
     *
     * @alias PolylineVolumeGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions which define the line strip.
     * @param {Property} [options.shape] A Property specifying the array of {@link Cartesian2} positions which define the shape to be extruded.
     * @param {Property} [options.cornerType=CornerType.ROUNDED] A {@link CornerType} Property specifying the style of the corners.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the volume.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the volume is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the volume.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the volume is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the volume casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this volume will be displayed.
     *
     * @see Entity
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Polyline%20Volume.html|Cesium Sandcastle Polyline Volume Demo}
     */
    function PolylineVolumeGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._positions = undefined;
        this._positionsSubscription = undefined;
        this._shape = undefined;
        this._shapeSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._cornerType = undefined;
        this._cornerTypeSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubsription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(PolylineVolumeGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof PolylineVolumeGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the volume.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to fill the volume.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the Property specifying the array of {@link Cartesian3} positions which define the line strip.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         */
        positions : createPropertyDescriptor('positions'),

        /**
         * Gets or sets the Property specifying the array of {@link Cartesian2} positions which define the shape to be extruded.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         */
        shape : createPropertyDescriptor('shape'),

        /**
         * Gets or sets the numeric Property specifying the angular distance between points on the volume.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default {CesiumMath.RADIANS_PER_DEGREE}
         */
        granularity : createPropertyDescriptor('granularity'),

        /**
         * Gets or sets the boolean Property specifying whether the volume is filled with the provided material.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the volume is outlined.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the {@link CornerType} Property specifying the style of the corners.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default CornerType.ROUNDED
         */
        cornerType : createPropertyDescriptor('cornerType'),

        /**
         * Get or sets the enum Property specifying whether the volume
         * casts or receives shadows from each light source.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this volume will be displayed.
         * @memberof PolylineVolumeGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {PolylineVolumeGraphics} [result] The object onto which to store the result.
     * @returns {PolylineVolumeGraphics} The modified result parameter or a new instance if one was not provided.
     */
    PolylineVolumeGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new PolylineVolumeGraphics(this);
        }
        result.show = this.show;
        result.material = this.material;
        result.positions = this.positions;
        result.shape = this.shape;
        result.granularity = this.granularity;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.cornerType = this.cornerType;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {PolylineVolumeGraphics} source The object to be merged into this object.
     */
    PolylineVolumeGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.positions = defaultValue(this.positions, source.positions);
        this.shape = defaultValue(this.shape, source.shape);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.cornerType = defaultValue(this.cornerType, source.cornerType);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return PolylineVolumeGraphics;
});

/*global define*/
define('DataSources/RectangleGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes graphics for a {@link Rectangle}.
     * The rectangle conforms to the curvature of the globe and can be placed on the surface or
     * at altitude and can optionally be extruded into a volume.
     *
     * @alias RectangleGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.coordinates] The Property specifying the {@link Rectangle}.
     * @param {Property} [options.height=0] A numeric Property specifying the altitude of the rectangle relative to the ellipsoid surface.
     * @param {Property} [options.extrudedHeight] A numeric Property specifying the altitude of the rectangle's extruded face relative to the ellipsoid surface.
     * @param {Property} [options.closeTop=true] A boolean Property specifying whether the rectangle has a top cover when extruded
     * @param {Property} [options.closeBottom=true] A boolean Property specifying whether the rectangle has a bottom cover when extruded.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the rectangle.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the rectangle is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the rectangle.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the rectangle is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.rotation=0.0] A numeric property specifying the rotation of the rectangle clockwise from north.
     * @param {Property} [options.stRotation=0.0] A numeric property specifying the rotation of the rectangle texture counter-clockwise from north.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between points on the rectangle.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the rectangle casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this rectangle will be displayed.
     *
     * @see Entity
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Rectangle.html|Cesium Sandcastle Rectangle Demo}
     */
    function RectangleGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._coordinates = undefined;
        this._coordinatesSubscription = undefined;
        this._height = undefined;
        this._heightSubscription = undefined;
        this._extrudedHeight = undefined;
        this._extrudedHeightSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._stRotation = undefined;
        this._stRotationSubscription = undefined;
        this._rotation = undefined;
        this._rotationSubscription = undefined;
        this._closeTop = undefined;
        this._closeTopSubscription = undefined;
        this._closeBottom = undefined;
        this._closeBottomSubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distancedisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(RectangleGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof RectangleGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the rectangle.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the {@link Rectangle}.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         */
        coordinates : createPropertyDescriptor('coordinates'),

        /**
         * Gets or sets the Property specifying the material used to fill the rectangle.
         * @memberof RectangleGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the rectangle.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default 0.0
         */
        height : createPropertyDescriptor('height'),

        /**
         * Gets or sets the numeric Property specifying the altitude of the rectangle extrusion.
         * Setting this property creates volume starting at height and ending at this altitude.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         */
        extrudedHeight : createPropertyDescriptor('extrudedHeight'),

        /**
         * Gets or sets the numeric Property specifying the angular distance between points on the rectangle.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default {CesiumMath.RADIANS_PER_DEGREE}
         */
        granularity : createPropertyDescriptor('granularity'),

        /**
         * Gets or sets the numeric property specifying the rotation of the rectangle texture counter-clockwise from north.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default 0
         */
        stRotation : createPropertyDescriptor('stRotation'),

        /**
         * Gets or sets the numeric property specifying the rotation of the rectangle clockwise from north.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default 0
         */
        rotation : createPropertyDescriptor('rotation'),

        /**
         * Gets or sets the boolean Property specifying whether the rectangle is filled with the provided material.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the rectangle is outlined.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),

        /**
         * Gets or sets the boolean Property specifying whether the rectangle has a top cover when extruded.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default true
         */
        closeTop : createPropertyDescriptor('closeTop'),

        /**
         * Gets or sets the boolean Property specifying whether the rectangle has a bottom cover when extruded.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default true
         */
        closeBottom : createPropertyDescriptor('closeBottom'),

        /**
         * Get or sets the enum Property specifying whether the rectangle
         * casts or receives shadows from each light source.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this rectangle will be displayed.
         * @memberof RectangleGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {RectangleGraphics} [result] The object onto which to store the result.
     * @returns {RectangleGraphics} The modified result parameter or a new instance if one was not provided.
     */
    RectangleGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new RectangleGraphics(this);
        }
        result.show = this.show;
        result.coordinates = this.coordinates;
        result.material = this.material;
        result.height = this.height;
        result.extrudedHeight = this.extrudedHeight;
        result.granularity = this.granularity;
        result.stRotation = this.stRotation;
        result.rotation = this.rotation;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.closeTop = this.closeTop;
        result.closeBottom = this.closeBottom;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {RectangleGraphics} source The object to be merged into this object.
     */
    RectangleGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.coordinates = defaultValue(this.coordinates, source.coordinates);
        this.material = defaultValue(this.material, source.material);
        this.height = defaultValue(this.height, source.height);
        this.extrudedHeight = defaultValue(this.extrudedHeight, source.extrudedHeight);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.stRotation = defaultValue(this.stRotation, source.stRotation);
        this.rotation = defaultValue(this.rotation, source.rotation);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.closeTop = defaultValue(this.closeTop, source.closeTop);
        this.closeBottom = defaultValue(this.closeBottom, source.closeBottom);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return RectangleGraphics;
});

/*global define*/
define('DataSources/WallGraphics',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './createMaterialPropertyDescriptor',
        './createPropertyDescriptor'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        createMaterialPropertyDescriptor,
        createPropertyDescriptor) {
    'use strict';

    /**
     * Describes a two dimensional wall defined as a line strip and optional maximum and minimum heights.
     * The wall conforms to the curvature of the globe and can be placed along the surface or at altitude.
     *
     * @alias WallGraphics
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.positions] A Property specifying the array of {@link Cartesian3} positions which define the top of the wall.
     * @param {Property} [options.maximumHeights] A Property specifying an array of heights to be used for the top of the wall instead of the height of each position.
     * @param {Property} [options.minimumHeights] A Property specifying an array of heights to be used for the bottom of the wall instead of the globe surface.
     * @param {Property} [options.show=true] A boolean Property specifying the visibility of the wall.
     * @param {Property} [options.fill=true] A boolean Property specifying whether the wall is filled with the provided material.
     * @param {MaterialProperty} [options.material=Color.WHITE] A Property specifying the material used to fill the wall.
     * @param {Property} [options.outline=false] A boolean Property specifying whether the wall is outlined.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline.
     * @param {Property} [options.granularity=Cesium.Math.RADIANS_PER_DEGREE] A numeric Property specifying the angular distance between each latitude and longitude point.
     * @param {Property} [options.shadows=ShadowMode.DISABLED] An enum Property specifying whether the wall casts or receives shadows from each light source.
     * @param {Property} [options.distanceDisplayCondition] A Property specifying at what distance from the camera that this wall will be displayed.
     *
     * @see Entity
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Wall.html|Cesium Sandcastle Wall Demo}
     */
    function WallGraphics(options) {
        this._show = undefined;
        this._showSubscription = undefined;
        this._material = undefined;
        this._materialSubscription = undefined;
        this._positions = undefined;
        this._positionsSubscription = undefined;
        this._minimumHeights = undefined;
        this._minimumHeightsSubscription = undefined;
        this._maximumHeights = undefined;
        this._maximumHeightsSubscription = undefined;
        this._granularity = undefined;
        this._granularitySubscription = undefined;
        this._fill = undefined;
        this._fillSubscription = undefined;
        this._outline = undefined;
        this._outlineSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;
        this._shadows = undefined;
        this._shadowsSubscription = undefined;
        this._distanceDisplayCondition = undefined;
        this._distanceDisplayConditionSubscription = undefined;
        this._definitionChanged = new Event();

        this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT));
    }

    defineProperties(WallGraphics.prototype, {
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof WallGraphics.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the boolean Property specifying the visibility of the wall.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default true
         */
        show : createPropertyDescriptor('show'),

        /**
         * Gets or sets the Property specifying the material used to fill the wall.
         * @memberof WallGraphics.prototype
         * @type {MaterialProperty}
         * @default Color.WHITE
         */
        material : createMaterialPropertyDescriptor('material'),

        /**
         * Gets or sets the Property specifying the array of {@link Cartesian3} positions which define the top of the wall.
         * @memberof WallGraphics.prototype
         * @type {Property}
         */
        positions : createPropertyDescriptor('positions'),

        /**
         * Gets or sets the Property specifying an array of heights to be used for the bottom of the wall instead of the surface of the globe.
         * If defined, the array must be the same length as {@link Wall#positions}.
         * @memberof WallGraphics.prototype
         * @type {Property}
         */
        minimumHeights : createPropertyDescriptor('minimumHeights'),

        /**
         * Gets or sets the Property specifying an array of heights to be used for the top of the wall instead of the height of each position.
         * If defined, the array must be the same length as {@link Wall#positions}.
         * @memberof WallGraphics.prototype
         * @type {Property}
         */
        maximumHeights : createPropertyDescriptor('maximumHeights'),

        /**
         * Gets or sets the numeric Property specifying the angular distance between points on the wall.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default {CesiumMath.RADIANS_PER_DEGREE}
         */
        granularity : createPropertyDescriptor('granularity'),

        /**
         * Gets or sets the boolean Property specifying whether the wall is filled with the provided material.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default true
         */
        fill : createPropertyDescriptor('fill'),

        /**
         * Gets or sets the Property specifying whether the wall is outlined.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default false
         */
        outline : createPropertyDescriptor('outline'),

        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),

        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth'),
        
        /**
         * Get or sets the enum Property specifying whether the wall
         * casts or receives shadows from each light source.
         * @memberof WallGraphics.prototype
         * @type {Property}
         * @default ShadowMode.DISABLED
         */
        shadows : createPropertyDescriptor('shadows'),

        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this wall will be displayed.
         * @memberof WallGraphics.prototype
         * @type {Property}
         */
        distanceDisplayCondition : createPropertyDescriptor('distanceDisplayCondition')
    });

    /**
     * Duplicates this instance.
     *
     * @param {WallGraphics} [result] The object onto which to store the result.
     * @returns {WallGraphics} The modified result parameter or a new instance if one was not provided.
     */
    WallGraphics.prototype.clone = function(result) {
        if (!defined(result)) {
            return new WallGraphics(this);
        }
        result.show = this.show;
        result.material = this.material;
        result.positions = this.positions;
        result.minimumHeights = this.minimumHeights;
        result.maximumHeights = this.maximumHeights;
        result.granularity = this.granularity;
        result.fill = this.fill;
        result.outline = this.outline;
        result.outlineColor = this.outlineColor;
        result.outlineWidth = this.outlineWidth;
        result.shadows = this.shadows;
        result.distanceDisplayCondition = this.distanceDisplayCondition;
        return result;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {WallGraphics} source The object to be merged into this object.
     */
    WallGraphics.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.show = defaultValue(this.show, source.show);
        this.material = defaultValue(this.material, source.material);
        this.positions = defaultValue(this.positions, source.positions);
        this.minimumHeights = defaultValue(this.minimumHeights, source.minimumHeights);
        this.maximumHeights = defaultValue(this.maximumHeights, source.maximumHeights);
        this.granularity = defaultValue(this.granularity, source.granularity);
        this.fill = defaultValue(this.fill, source.fill);
        this.outline = defaultValue(this.outline, source.outline);
        this.outlineColor = defaultValue(this.outlineColor, source.outlineColor);
        this.outlineWidth = defaultValue(this.outlineWidth, source.outlineWidth);
        this.shadows = defaultValue(this.shadows, source.shadows);
        this.distanceDisplayCondition = defaultValue(this.distanceDisplayCondition, source.distanceDisplayCondition);
    };

    return WallGraphics;
});

/*global define*/
define('DataSources/Entity',[
        '../Core/Cartesian3',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Quaternion',
        '../Core/Transforms',
        './BillboardGraphics',
        './BoxGraphics',
        './ConstantPositionProperty',
        './CorridorGraphics',
        './createPropertyDescriptor',
        './createRawPropertyDescriptor',
        './CylinderGraphics',
        './EllipseGraphics',
        './EllipsoidGraphics',
        './LabelGraphics',
        './ModelGraphics',
        './PathGraphics',
        './PointGraphics',
        './PolygonGraphics',
        './PolylineGraphics',
        './PolylineVolumeGraphics',
        './Property',
        './RectangleGraphics',
        './WallGraphics'
    ], function(
        Cartesian3,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        Matrix3,
        Matrix4,
        Quaternion,
        Transforms,
        BillboardGraphics,
        BoxGraphics,
        ConstantPositionProperty,
        CorridorGraphics,
        createPropertyDescriptor,
        createRawPropertyDescriptor,
        CylinderGraphics,
        EllipseGraphics,
        EllipsoidGraphics,
        LabelGraphics,
        ModelGraphics,
        PathGraphics,
        PointGraphics,
        PolygonGraphics,
        PolylineGraphics,
        PolylineVolumeGraphics,
        Property,
        RectangleGraphics,
        WallGraphics) {
    'use strict';

    function createConstantPositionProperty(value) {
        return new ConstantPositionProperty(value);
    }

    function createPositionPropertyDescriptor(name) {
        return createPropertyDescriptor(name, undefined, createConstantPositionProperty);
    }

    function createPropertyTypeDescriptor(name, Type) {
        return createPropertyDescriptor(name, undefined, function(value) {
            if (value instanceof Type) {
                return value;
            }
            return new Type(value);
        });
    }

    /**
     * Entity instances aggregate multiple forms of visualization into a single high-level object.
     * They can be created manually and added to {@link Viewer#entities} or be produced by
     * data sources, such as {@link CzmlDataSource} and {@link GeoJsonDataSource}.
     * @alias Entity
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.id] A unique identifier for this object. If none is provided, a GUID is generated.
     * @param {String} [options.name] A human readable name to display to users. It does not have to be unique.
     * @param {TimeIntervalCollection} [options.availability] The availability, if any, associated with this object.
     * @param {Boolean} [options.show] A boolean value indicating if the entity and its children are displayed.
     * @param {Property} [options.description] A string Property specifying an HTML description for this entity.
     * @param {PositionProperty} [options.position] A Property specifying the entity position.
     * @param {Property} [options.orientation] A Property specifying the entity orientation.
     * @param {Property} [options.viewFrom] A suggested initial offset for viewing this object.
     * @param {Entity} [options.parent] A parent entity to associate with this entity.
     * @param {BillboardGraphics} [options.billboard] A billboard to associate with this entity.
     * @param {BoxGraphics} [options.box] A box to associate with this entity.
     * @param {CorridorGraphics} [options.corridor] A corridor to associate with this entity.
     * @param {CylinderGraphics} [options.cylinder] A cylinder to associate with this entity.
     * @param {EllipseGraphics} [options.ellipse] A ellipse to associate with this entity.
     * @param {EllipsoidGraphics} [options.ellipsoid] A ellipsoid to associate with this entity.
     * @param {LabelGraphics} [options.label] A options.label to associate with this entity.
     * @param {ModelGraphics} [options.model] A model to associate with this entity.
     * @param {PathGraphics} [options.path] A path to associate with this entity.
     * @param {PointGraphics} [options.point] A point to associate with this entity.
     * @param {PolygonGraphics} [options.polygon] A polygon to associate with this entity.
     * @param {PolylineGraphics} [options.polyline] A polyline to associate with this entity.
     * @param {PolylineVolumeGraphics} [options.polylineVolume] A polylineVolume to associate with this entity.
     * @param {RectangleGraphics} [options.rectangle] A rectangle to associate with this entity.
     * @param {WallGraphics} [options.wall] A wall to associate with this entity.
     *
     * @see {@link http://cesiumjs.org/2015/02/02/Visualizing-Spatial-Data/|Visualizing Spatial Data}
     */
    function Entity(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var id = options.id;
        if (!defined(id)) {
            id = createGuid();
        }

        this._availability = undefined;
        this._id = id;
        this._definitionChanged = new Event();
        this._name = options.name;
        this._show = defaultValue(options.show, true);
        this._parent = undefined;
        this._propertyNames = ['billboard', 'box', 'corridor', 'cylinder', 'description', 'ellipse', //
                               'ellipsoid', 'label', 'model', 'orientation', 'path', 'point', 'polygon', //
                               'polyline', 'polylineVolume', 'position', 'rectangle', 'viewFrom', 'wall'];

        this._billboard = undefined;
        this._billboardSubscription = undefined;
        this._box = undefined;
        this._boxSubscription = undefined;
        this._corridor = undefined;
        this._corridorSubscription = undefined;
        this._cylinder = undefined;
        this._cylinderSubscription = undefined;
        this._description = undefined;
        this._descriptionSubscription = undefined;
        this._ellipse = undefined;
        this._ellipseSubscription = undefined;
        this._ellipsoid = undefined;
        this._ellipsoidSubscription = undefined;
        this._label = undefined;
        this._labelSubscription = undefined;
        this._model = undefined;
        this._modelSubscription = undefined;
        this._orientation = undefined;
        this._orientationSubscription = undefined;
        this._path = undefined;
        this._pathSubscription = undefined;
        this._point = undefined;
        this._pointSubscription = undefined;
        this._polygon = undefined;
        this._polygonSubscription = undefined;
        this._polyline = undefined;
        this._polylineSubscription = undefined;
        this._polylineVolume = undefined;
        this._polylineVolumeSubscription = undefined;
        this._position = undefined;
        this._positionSubscription = undefined;
        this._rectangle = undefined;
        this._rectangleSubscription = undefined;
        this._viewFrom = undefined;
        this._viewFromSubscription = undefined;
        this._wall = undefined;
        this._wallSubscription = undefined;
        this._children = [];

        /**
         * Gets or sets the entity collection that this entity belongs to.
         * @type {EntityCollection}
         */
        this.entityCollection = undefined;

        this.parent = options.parent;
        this.merge(options);
    }

    function updateShow(entity, children, isShowing) {
        var length = children.length;
        for (var i = 0; i < length; i++) {
            var child = children[i];
            var childShow = child._show;
            var oldValue = !isShowing && childShow;
            var newValue = isShowing && childShow;
            if (oldValue !== newValue) {
                updateShow(child, child._children, isShowing);
            }
        }
        entity._definitionChanged.raiseEvent(entity, 'isShowing', isShowing, !isShowing);
    }

    defineProperties(Entity.prototype, {
        /**
         * The availability, if any, associated with this object.
         * If availability is undefined, it is assumed that this object's
         * other properties will return valid data for any provided time.
         * If availability exists, the objects other properties will only
         * provide valid data if queried within the given interval.
         * @memberof Entity.prototype
         * @type {TimeIntervalCollection}
         */
        availability : createRawPropertyDescriptor('availability'),
        /**
         * Gets the unique ID associated with this object.
         * @memberof Entity.prototype
         * @type {String}
         */
        id : {
            get : function() {
                return this._id;
            }
        },
        /**
         * Gets the event that is raised whenever a property or sub-property is changed or modified.
         * @memberof Entity.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the name of the object.  The name is intended for end-user
         * consumption and does not need to be unique.
         * @memberof Entity.prototype
         * @type {String}
         */
        name : createRawPropertyDescriptor('name'),
        /**
         * Gets or sets whether this entity should be displayed. When set to true,
         * the entity is only displayed if the parent entity's show property is also true.
         * @memberof Entity.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._show;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (value === this._show) {
                    return;
                }

                var wasShowing = this.isShowing;
                this._show = value;
                var isShowing = this.isShowing;

                if (wasShowing !== isShowing) {
                    updateShow(this, this._children, isShowing);
                }

                this._definitionChanged.raiseEvent(this, 'show', value, !value);
            }
        },
        /**
         * Gets whether this entity is being displayed, taking into account
         * the visibility of any ancestor entities.
         * @memberof Entity.prototype
         * @type {Boolean}
         */
        isShowing : {
            get : function() {
                return this._show && (!defined(this.entityCollection) || this.entityCollection.show) && (!defined(this._parent) || this._parent.isShowing);
            }
        },
        /**
         * Gets or sets the parent object.
         * @memberof Entity.prototype
         * @type {Entity}
         */
        parent : {
            get : function() {
                return this._parent;
            },
            set : function(value) {
                var oldValue = this._parent;

                if (oldValue === value) {
                    return;
                }

                var wasShowing = this.isShowing;
                if (defined(oldValue)) {
                    var index = oldValue._children.indexOf(this);
                    oldValue._children.splice(index, 1);
                }

                this._parent = value;
                if (defined(value)) {
                    value._children.push(this);
                }

                var isShowing = this.isShowing;

                if (wasShowing !== isShowing) {
                    updateShow(this, this._children, isShowing);
                }

                this._definitionChanged.raiseEvent(this, 'parent', value, oldValue);
            }
        },
        /**
         * Gets the names of all properties registered on this instance.
         * @memberof Entity.prototype
         * @type {Array}
         */
        propertyNames : {
            get : function() {
                return this._propertyNames;
            }
        },
        /**
         * Gets or sets the billboard.
         * @memberof Entity.prototype
         * @type {BillboardGraphics}
         */
        billboard : createPropertyTypeDescriptor('billboard', BillboardGraphics),
        /**
         * Gets or sets the box.
         * @memberof Entity.prototype
         * @type {BoxGraphics}
         */
        box : createPropertyTypeDescriptor('box', BoxGraphics),
        /**
         * Gets or sets the corridor.
         * @memberof Entity.prototype
         * @type {CorridorGraphics}
         */
        corridor : createPropertyTypeDescriptor('corridor', CorridorGraphics),
        /**
         * Gets or sets the cylinder.
         * @memberof Entity.prototype
         * @type {CylinderGraphics}
         */
        cylinder : createPropertyTypeDescriptor('cylinder', CylinderGraphics),
        /**
         * Gets or sets the description.
         * @memberof Entity.prototype
         * @type {Property}
         */
        description : createPropertyDescriptor('description'),
        /**
         * Gets or sets the ellipse.
         * @memberof Entity.prototype
         * @type {EllipseGraphics}
         */
        ellipse : createPropertyTypeDescriptor('ellipse', EllipseGraphics),
        /**
         * Gets or sets the ellipsoid.
         * @memberof Entity.prototype
         * @type {EllipsoidGraphics}
         */
        ellipsoid : createPropertyTypeDescriptor('ellipsoid', EllipsoidGraphics),
        /**
         * Gets or sets the label.
         * @memberof Entity.prototype
         * @type {LabelGraphics}
         */
        label : createPropertyTypeDescriptor('label', LabelGraphics),
        /**
         * Gets or sets the model.
         * @memberof Entity.prototype
         * @type {ModelGraphics}
         */
        model : createPropertyTypeDescriptor('model', ModelGraphics),
        /**
         * Gets or sets the orientation.
         * @memberof Entity.prototype
         * @type {Property}
         */
        orientation : createPropertyDescriptor('orientation'),
        /**
         * Gets or sets the path.
         * @memberof Entity.prototype
         * @type {PathGraphics}
         */
        path : createPropertyTypeDescriptor('path', PathGraphics),
        /**
         * Gets or sets the point graphic.
         * @memberof Entity.prototype
         * @type {PointGraphics}
         */
        point : createPropertyTypeDescriptor('point', PointGraphics),
        /**
         * Gets or sets the polygon.
         * @memberof Entity.prototype
         * @type {PolygonGraphics}
         */
        polygon : createPropertyTypeDescriptor('polygon', PolygonGraphics),
        /**
         * Gets or sets the polyline.
         * @memberof Entity.prototype
         * @type {PolylineGraphics}
         */
        polyline : createPropertyTypeDescriptor('polyline', PolylineGraphics),
        /**
         * Gets or sets the polyline volume.
         * @memberof Entity.prototype
         * @type {PolylineVolumeGraphics}
         */
        polylineVolume : createPropertyTypeDescriptor('polylineVolume', PolylineVolumeGraphics),
        /**
         * Gets or sets the position.
         * @memberof Entity.prototype
         * @type {PositionProperty}
         */
        position : createPositionPropertyDescriptor('position'),
        /**
         * Gets or sets the rectangle.
         * @memberof Entity.prototype
         * @type {RectangleGraphics}
         */
        rectangle : createPropertyTypeDescriptor('rectangle', RectangleGraphics),
        /**
         * Gets or sets the suggested initial offset for viewing this object
         * with the camera.  The offset is defined in the east-north-up reference frame.
         * @memberof Entity.prototype
         * @type {Property}
         */
        viewFrom : createPropertyDescriptor('viewFrom'),
        /**
         * Gets or sets the wall.
         * @memberof Entity.prototype
         * @type {WallGraphics}
         */
        wall : createPropertyTypeDescriptor('wall', WallGraphics)
    });

    /**
     * Given a time, returns true if this object should have data during that time.
     *
     * @param {JulianDate} time The time to check availability for.
     * @returns {Boolean} true if the object should have data during the provided time, false otherwise.
     */
    Entity.prototype.isAvailable = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var availability = this._availability;
        return !defined(availability) || availability.contains(time);
    };

    /**
     * Adds a property to this object.  Once a property is added, it can be
     * observed with {@link Entity#definitionChanged} and composited
     * with {@link CompositeEntityCollection}
     *
     * @param {String} propertyName The name of the property to add.
     *
     * @exception {DeveloperError} "propertyName" is a reserved property name.
     * @exception {DeveloperError} "propertyName" is already a registered property.
     */
    Entity.prototype.addProperty = function(propertyName) {
        var propertyNames = this._propertyNames;

                if (!defined(propertyName)) {
            throw new DeveloperError('propertyName is required.');
        }
        if (propertyNames.indexOf(propertyName) !== -1) {
            throw new DeveloperError(propertyName + ' is already a registered property.');
        }
        if (propertyName in this) {
            throw new DeveloperError(propertyName + ' is a reserved property name.');
        }
        
        propertyNames.push(propertyName);
        Object.defineProperty(this, propertyName, createRawPropertyDescriptor(propertyName, true));
    };

    /**
     * Removed a property previously added with addProperty.
     *
     * @param {String} propertyName The name of the property to remove.
     *
     * @exception {DeveloperError} "propertyName" is a reserved property name.
     * @exception {DeveloperError} "propertyName" is not a registered property.
     */
    Entity.prototype.removeProperty = function(propertyName) {
        var propertyNames = this._propertyNames;
        var index = propertyNames.indexOf(propertyName);

                if (!defined(propertyName)) {
            throw new DeveloperError('propertyName is required.');
        }
        if (index === -1) {
            throw new DeveloperError(propertyName + ' is not a registered property.');
        }
        
        this._propertyNames.splice(index, 1);
        delete this[propertyName];
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {Entity} source The object to be merged into this object.
     */
    Entity.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        //Name, show, and availability are not Property objects and are currently handled differently.
        //source.show is intentionally ignored because this.show always has a value.
        this.name = defaultValue(this.name, source.name);
        this.availability = defaultValue(source.availability, this.availability);

        var propertyNames = this._propertyNames;
        var sourcePropertyNames = defined(source._propertyNames) ? source._propertyNames : Object.keys(source);
        var propertyNamesLength = sourcePropertyNames.length;
        for (var i = 0; i < propertyNamesLength; i++) {
            var name = sourcePropertyNames[i];

            //Ignore parent when merging, this only happens at construction time.
            if (name === 'parent') {
                continue;
            }

            var targetProperty = this[name];
            var sourceProperty = source[name];

            //Custom properties that are registered on the source entity must also
            //get registered on this entity.
            if (!defined(targetProperty) && propertyNames.indexOf(name) === -1) {
                this.addProperty(name);
            }

            if (defined(sourceProperty)) {
                if (defined(targetProperty)) {
                    if (defined(targetProperty.merge)) {
                        targetProperty.merge(sourceProperty);
                    }
                } else if (defined(sourceProperty.merge) && defined(sourceProperty.clone)) {
                    this[name] = sourceProperty.clone();
                } else {
                    this[name] = sourceProperty;
                }
            }
        }
    };

    var matrix3Scratch = new Matrix3();
    var positionScratch = new Cartesian3();
    var orientationScratch = new Quaternion();

    /**
     * @private
     */
    Entity.prototype._getModelMatrix = function(time, result) {
        var position = Property.getValueOrUndefined(this._position, time, positionScratch);
        if (!defined(position)) {
            return undefined;
        }
        var orientation = Property.getValueOrUndefined(this._orientation, time, orientationScratch);
        if (!defined(orientation)) {
            result = Transforms.eastNorthUpToFixedFrame(position, undefined, result);
        } else {
            result = Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, result);
        }
        return result;
    };

    return Entity;
});

/*global define*/
define('DataSources/EntityCollection',[
        '../Core/AssociativeArray',
        '../Core/createGuid',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/Iso8601',
        '../Core/JulianDate',
        '../Core/RuntimeError',
        '../Core/TimeInterval',
        './Entity'
    ], function(
        AssociativeArray,
        createGuid,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        Iso8601,
        JulianDate,
        RuntimeError,
        TimeInterval,
        Entity) {
    'use strict';

    var entityOptionsScratch = {
        id : undefined
    };

    function fireChangedEvent(collection) {
        if (collection._firing) {
            collection._refire = true;
            return;
        }

        if (collection._suspendCount === 0) {
            var added = collection._addedEntities;
            var removed = collection._removedEntities;
            var changed = collection._changedEntities;
            if (changed.length !== 0 || added.length !== 0 || removed.length !== 0) {
                collection._firing = true;
                do {
                    collection._refire = false;
                    var addedArray = added.values.slice(0);
                    var removedArray = removed.values.slice(0);
                    var changedArray = changed.values.slice(0);

                    added.removeAll();
                    removed.removeAll();
                    changed.removeAll();
                    collection._collectionChanged.raiseEvent(collection, addedArray, removedArray, changedArray);
                } while (collection._refire);
                collection._firing = false;
            }
        }
    }

    /**
     * An observable collection of {@link Entity} instances where each entity has a unique id.
     * @alias EntityCollection
     * @constructor
     *
     * @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
     */
    function EntityCollection(owner) {
        this._owner = owner;
        this._entities = new AssociativeArray();
        this._addedEntities = new AssociativeArray();
        this._removedEntities = new AssociativeArray();
        this._changedEntities = new AssociativeArray();
        this._suspendCount = 0;
        this._collectionChanged = new Event();
        this._id = createGuid();
        this._show = true;
        this._firing = false;
        this._refire  = false;
    }

    /**
     * Prevents {@link EntityCollection#collectionChanged} events from being raised
     * until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
     * point a single event will be raised that covers all suspended operations.
     * This allows for many items to be added and removed efficiently.
     * This function can be safely called multiple times as long as there
     * are corresponding calls to {@link EntityCollection#resumeEvents}.
     */
    EntityCollection.prototype.suspendEvents = function() {
        this._suspendCount++;
    };

    /**
     * Resumes raising {@link EntityCollection#collectionChanged} events immediately
     * when an item is added or removed.  Any modifications made while while events were suspended
     * will be triggered as a single event when this function is called.
     * This function is reference counted and can safely be called multiple times as long as there
     * are corresponding calls to {@link EntityCollection#resumeEvents}.
     *
     * @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
     */
    EntityCollection.prototype.resumeEvents = function() {
                if (this._suspendCount === 0) {
            throw new DeveloperError('resumeEvents can not be called before suspendEvents.');
        }
        
        this._suspendCount--;
        fireChangedEvent(this);
    };

    /**
     * The signature of the event generated by {@link EntityCollection#collectionChanged}.
     * @function
     *
     * @param {EntityCollection} collection The collection that triggered the event.
     * @param {Entity[]} added The array of {@link Entity} instances that have been added to the collection.
     * @param {Entity[]} removed The array of {@link Entity} instances that have been removed from the collection.
     * @param {Entity[]} changed The array of {@link Entity} instances that have been modified.
     */
    EntityCollection.collectionChangedEventCallback = undefined;

    defineProperties(EntityCollection.prototype, {
        /**
         * Gets the event that is fired when entities are added or removed from the collection.
         * The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
         * @memberof EntityCollection.prototype
         * @readonly
         * @type {Event}
         */
        collectionChanged : {
            get : function() {
                return this._collectionChanged;
            }
        },
        /**
         * Gets a globally unique identifier for this collection.
         * @memberof EntityCollection.prototype
         * @readonly
         * @type {String}
         */
        id : {
            get : function() {
                return this._id;
            }
        },
        /**
         * Gets the array of Entity instances in the collection.
         * This array should not be modified directly.
         * @memberof EntityCollection.prototype
         * @readonly
         * @type {Entity[]}
         */
        values : {
            get : function() {
                return this._entities.values;
            }
        },
        /**
         * Gets whether or not this entity collection should be
         * displayed.  When true, each entity is only displayed if
         * its own show property is also true.
         * @memberof EntityCollection.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._show;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (value === this._show) {
                    return;
                }

                //Since entity.isShowing includes the EntityCollection.show state
                //in its calculation, we need to loop over the entities array
                //twice, once to get the old showing value and a second time
                //to raise the changed event.
                this.suspendEvents();

                var i;
                var oldShows = [];
                var entities = this._entities.values;
                var entitiesLength = entities.length;

                for (i = 0; i < entitiesLength; i++) {
                    oldShows.push(entities[i].isShowing);
                }

                this._show = value;

                for (i = 0; i < entitiesLength; i++) {
                    var oldShow = oldShows[i];
                    var entity = entities[i];
                    if (oldShow !== entity.isShowing) {
                        entity.definitionChanged.raiseEvent(entity, 'isShowing', entity.isShowing, oldShow);
                    }
                }

                this.resumeEvents();
            }
        },
        /**
         * Gets the owner of this entity collection, ie. the data source or composite entity collection which created it.
         * @memberof EntityCollection.prototype
         * @readonly
         * @type {DataSource|CompositeEntityCollection}
         */
        owner : {
            get : function() {
                return this._owner;
            }
        }
    });

    /**
     * Computes the maximum availability of the entities in the collection.
     * If the collection contains a mix of infinitely available data and non-infinite data,
     * it will return the interval pertaining to the non-infinite data only.  If all
     * data is infinite, an infinite interval will be returned.
     *
     * @returns {TimeInterval} The availability of entities in the collection.
     */
    EntityCollection.prototype.computeAvailability = function() {
        var startTime = Iso8601.MAXIMUM_VALUE;
        var stopTime = Iso8601.MINIMUM_VALUE;
        var entities = this._entities.values;
        for (var i = 0, len = entities.length; i < len; i++) {
            var entity = entities[i];
            var availability = entity.availability;
            if (defined(availability)) {
                var start = availability.start;
                var stop = availability.stop;
                if (JulianDate.lessThan(start, startTime) && !start.equals(Iso8601.MINIMUM_VALUE)) {
                    startTime = start;
                }
                if (JulianDate.greaterThan(stop, stopTime) && !stop.equals(Iso8601.MAXIMUM_VALUE)) {
                    stopTime = stop;
                }
            }
        }

        if (Iso8601.MAXIMUM_VALUE.equals(startTime)) {
            startTime = Iso8601.MINIMUM_VALUE;
        }
        if (Iso8601.MINIMUM_VALUE.equals(stopTime)) {
            stopTime = Iso8601.MAXIMUM_VALUE;
        }
        return new TimeInterval({
            start : startTime,
            stop : stopTime
        });
    };

    /**
     * Add an entity to the collection.
     *
     * @param {Entity} entity The entity to be added.
     * @returns {Entity} The entity that was added.
     * @exception {DeveloperError} An entity with <entity.id> already exists in this collection.
     */
    EntityCollection.prototype.add = function(entity) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        
        if (!(entity instanceof Entity)) {
            entity = new Entity(entity);
        }

        var id = entity.id;
        var entities = this._entities;
        if (entities.contains(id)) {
            throw new RuntimeError('An entity with id ' + id + ' already exists in this collection.');
        }

        entity.entityCollection = this;
        entities.set(id, entity);

        if (!this._removedEntities.remove(id)) {
            this._addedEntities.set(id, entity);
        }
        entity.definitionChanged.addEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);

        fireChangedEvent(this);
        return entity;
    };

    /**
     * Removes an entity from the collection.
     *
     * @param {Entity} entity The entity to be removed.
     * @returns {Boolean} true if the item was removed, false if it did not exist in the collection.
     */
    EntityCollection.prototype.remove = function(entity) {
        if (!defined(entity)) {
            return false;
        }
        return this.removeById(entity.id);
    };

    /**
     * Returns true if the provided entity is in this collection, false otherwise.
     *
     * @param {Entity} entity The entity.
     * @returns {Boolean} true if the provided entity is in this collection, false otherwise.
     */
    EntityCollection.prototype.contains = function(entity) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
                return this._entities.get(entity.id) === entity;
    };

    /**
     * Removes an entity with the provided id from the collection.
     *
     * @param {Object} id The id of the entity to remove.
     * @returns {Boolean} true if the item was removed, false if no item with the provided id existed in the collection.
     */
    EntityCollection.prototype.removeById = function(id) {
        if (!defined(id)) {
            return false;
        }

        var entities = this._entities;
        var entity = entities.get(id);
        if (!this._entities.remove(id)) {
            return false;
        }

        if (!this._addedEntities.remove(id)) {
            this._removedEntities.set(id, entity);
            this._changedEntities.remove(id);
        }
        this._entities.remove(id);
        entity.definitionChanged.removeEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
        fireChangedEvent(this);

        return true;
    };

    /**
     * Removes all Entities from the collection.
     */
    EntityCollection.prototype.removeAll = function() {
        //The event should only contain items added before events were suspended
        //and the contents of the collection.
        var entities = this._entities;
        var entitiesLength = entities.length;
        var array = entities.values;

        var addedEntities = this._addedEntities;
        var removed = this._removedEntities;

        for (var i = 0; i < entitiesLength; i++) {
            var existingItem = array[i];
            var existingItemId = existingItem.id;
            var addedItem = addedEntities.get(existingItemId);
            if (!defined(addedItem)) {
                existingItem.definitionChanged.removeEventListener(EntityCollection.prototype._onEntityDefinitionChanged, this);
                removed.set(existingItemId, existingItem);
            }
        }

        entities.removeAll();
        addedEntities.removeAll();
        this._changedEntities.removeAll();
        fireChangedEvent(this);
    };

    /**
     * Gets an entity with the specified id.
     *
     * @param {Object} id The id of the entity to retrieve.
     * @returns {Entity} The entity with the provided id or undefined if the id did not exist in the collection.
     */
    EntityCollection.prototype.getById = function(id) {
                if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        
        return this._entities.get(id);
    };

    /**
     * Gets an entity with the specified id or creates it and adds it to the collection if it does not exist.
     *
     * @param {Object} id The id of the entity to retrieve or create.
     * @returns {Entity} The new or existing object.
     */
    EntityCollection.prototype.getOrCreateEntity = function(id) {
                if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        
        var entity = this._entities.get(id);
        if (!defined(entity)) {
            entityOptionsScratch.id = id;
            entity = new Entity(entityOptionsScratch);
            this.add(entity);
        }
        return entity;
    };

    EntityCollection.prototype._onEntityDefinitionChanged = function(entity) {
        var id = entity.id;
        if (!this._addedEntities.contains(id)) {
            this._changedEntities.set(id, entity);
        }
        fireChangedEvent(this);
    };

    return EntityCollection;
});

/*global define*/
define('DataSources/CompositeEntityCollection',[
        '../Core/createGuid',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Math',
        './Entity',
        './EntityCollection'
    ], function(
        createGuid,
        defined,
        defineProperties,
        DeveloperError,
        CesiumMath,
        Entity,
        EntityCollection) {
    'use strict';

    var entityOptionsScratch = {
        id : undefined
    };
    var entityIdScratch = new Array(2);

    function clean(entity) {
        var propertyNames = entity.propertyNames;
        var propertyNamesLength = propertyNames.length;
        for (var i = 0; i < propertyNamesLength; i++) {
            entity[propertyNames[i]] = undefined;
        }
    }

    function subscribeToEntity(that, eventHash, collectionId, entity) {
        entityIdScratch[0] = collectionId;
        entityIdScratch[1] = entity.id;
        eventHash[JSON.stringify(entityIdScratch)] = entity.definitionChanged.addEventListener(CompositeEntityCollection.prototype._onDefinitionChanged, that);
    }

    function unsubscribeFromEntity(that, eventHash, collectionId, entity) {
        entityIdScratch[0] = collectionId;
        entityIdScratch[1] = entity.id;
        var id = JSON.stringify(entityIdScratch);
        eventHash[id]();
        eventHash[id] = undefined;
    }

    function recomposite(that) {
        that._shouldRecomposite = true;
        if (that._suspendCount !== 0) {
            return;
        }

        var collections = that._collections;
        var collectionsLength = collections.length;

        var collectionsCopy = that._collectionsCopy;
        var collectionsCopyLength = collectionsCopy.length;

        var i;
        var entity;
        var entities;
        var iEntities;
        var collection;
        var composite = that._composite;
        var newEntities = new EntityCollection(that);
        var eventHash = that._eventHash;
        var collectionId;

        for (i = 0; i < collectionsCopyLength; i++) {
            collection = collectionsCopy[i];
            collection.collectionChanged.removeEventListener(CompositeEntityCollection.prototype._onCollectionChanged, that);
            entities = collection.values;
            collectionId = collection.id;
            for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {
                entity = entities[iEntities];
                unsubscribeFromEntity(that, eventHash, collectionId, entity);
            }
        }

        for (i = collectionsLength - 1; i >= 0; i--) {
            collection = collections[i];
            collection.collectionChanged.addEventListener(CompositeEntityCollection.prototype._onCollectionChanged, that);

            //Merge all of the existing entities.
            entities = collection.values;
            collectionId = collection.id;
            for (iEntities = entities.length - 1; iEntities > -1; iEntities--) {
                entity = entities[iEntities];
                subscribeToEntity(that, eventHash, collectionId, entity);

                var compositeEntity = newEntities.getById(entity.id);
                if (!defined(compositeEntity)) {
                    compositeEntity = composite.getById(entity.id);
                    if (!defined(compositeEntity)) {
                        entityOptionsScratch.id = entity.id;
                        compositeEntity = new Entity(entityOptionsScratch);
                    } else {
                        clean(compositeEntity);
                    }
                    newEntities.add(compositeEntity);
                }
                compositeEntity.merge(entity);
            }
        }
        that._collectionsCopy = collections.slice(0);

        composite.suspendEvents();
        composite.removeAll();
        var newEntitiesArray = newEntities.values;
        for (i = 0; i < newEntitiesArray.length; i++) {
            composite.add(newEntitiesArray[i]);
        }
        composite.resumeEvents();
    }

    /**
     * Non-destructively composites multiple {@link EntityCollection} instances into a single collection.
     * If a Entity with the same ID exists in multiple collections, it is non-destructively
     * merged into a single new entity instance.  If an entity has the same property in multiple
     * collections, the property of the Entity in the last collection of the list it
     * belongs to is used.  CompositeEntityCollection can be used almost anywhere that a
     * EntityCollection is used.
     *
     * @alias CompositeEntityCollection
     * @constructor
     *
     * @param {EntityCollection[]} [collections] The initial list of EntityCollection instances to merge.
     * @param {DataSource|CompositeEntityCollection} [owner] The data source (or composite entity collection) which created this collection.
     */
    function CompositeEntityCollection(collections, owner) {
        this._owner = owner;
        this._composite = new EntityCollection(this);
        this._suspendCount = 0;
        this._collections = defined(collections) ? collections.slice() : [];
        this._collectionsCopy = [];
        this._id = createGuid();
        this._eventHash = {};
        recomposite(this);
        this._shouldRecomposite = false;
    }

    defineProperties(CompositeEntityCollection.prototype, {
        /**
         * Gets the event that is fired when entities are added or removed from the collection.
         * The generated event is a {@link EntityCollection.collectionChangedEventCallback}.
         * @memberof CompositeEntityCollection.prototype
         * @readonly
         * @type {Event}
         */
        collectionChanged : {
            get : function() {
                return this._composite._collectionChanged;
            }
        },
        /**
         * Gets a globally unique identifier for this collection.
         * @memberof CompositeEntityCollection.prototype
         * @readonly
         * @type {String}
         */
        id : {
            get : function() {
                return this._id;
            }
        },
        /**
         * Gets the array of Entity instances in the collection.
         * This array should not be modified directly.
         * @memberof CompositeEntityCollection.prototype
         * @readonly
         * @type {Entity[]}
         */
        values : {
            get : function() {
                return this._composite.values;
            }
        },
        /**
         * Gets the owner of this composite entity collection, ie. the data source or composite entity collection which created it.
         * @memberof CompositeEntityCollection.prototype
         * @readonly
         * @type {DataSource|CompositeEntityCollection}
         */
        owner : {
            get : function() {
                return this._owner;
            }
        }
    });

    /**
     * Adds a collection to the composite.
     *
     * @param {EntityCollection} collection the collection to add.
     * @param {Number} [index] the index to add the collection at.  If omitted, the collection will
     *                         added on top of all existing collections.
     *
     * @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of collections.
     */
    CompositeEntityCollection.prototype.addCollection = function(collection, index) {
        var hasIndex = defined(index);
                if (!defined(collection)) {
            throw new DeveloperError('collection is required.');
        }
        if (hasIndex) {
            if (index < 0) {
                throw new DeveloperError('index must be greater than or equal to zero.');
            } else if (index > this._collections.length) {
                throw new DeveloperError('index must be less than or equal to the number of collections.');
            }
        }
        
        if (!hasIndex) {
            index = this._collections.length;
            this._collections.push(collection);
        } else {
            this._collections.splice(index, 0, collection);
        }

        recomposite(this);
    };

    /**
     * Removes a collection from this composite, if present.
     *
     * @param {EntityCollection} collection The collection to remove.
     * @returns {Boolean} true if the collection was in the composite and was removed,
     *                    false if the collection was not in the composite.
     */
    CompositeEntityCollection.prototype.removeCollection = function(collection) {
        var index = this._collections.indexOf(collection);
        if (index !== -1) {
            this._collections.splice(index, 1);
            recomposite(this);
            return true;
        }
        return false;
    };

    /**
     * Removes all collections from this composite.
     */
    CompositeEntityCollection.prototype.removeAllCollections = function() {
        this._collections.length = 0;
        recomposite(this);
    };

    /**
     * Checks to see if the composite contains a given collection.
     *
     * @param {EntityCollection} collection the collection to check for.
     * @returns {Boolean} true if the composite contains the collection, false otherwise.
     */
    CompositeEntityCollection.prototype.containsCollection = function(collection) {
        return this._collections.indexOf(collection) !== -1;
    };

    /**
     * Returns true if the provided entity is in this collection, false otherwise.
     *
     * @param {Entity} entity The entity.
     * @returns {Boolean} true if the provided entity is in this collection, false otherwise.
     */
    CompositeEntityCollection.prototype.contains = function(entity) {
        return this._composite.contains(entity);
    };

    /**
     * Determines the index of a given collection in the composite.
     *
     * @param {EntityCollection} collection The collection to find the index of.
     * @returns {Number} The index of the collection in the composite, or -1 if the collection does not exist in the composite.
     */
    CompositeEntityCollection.prototype.indexOfCollection = function(collection) {
        return this._collections.indexOf(collection);
    };

    /**
     * Gets a collection by index from the composite.
     *
     * @param {Number} index the index to retrieve.
     */
    CompositeEntityCollection.prototype.getCollection = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.', 'index');
        }
        
        return this._collections[index];
    };

    /**
     * Gets the number of collections in this composite.
     */
    CompositeEntityCollection.prototype.getCollectionsLength = function() {
        return this._collections.length;
    };

    function getCollectionIndex(collections, collection) {
                if (!defined(collection)) {
            throw new DeveloperError('collection is required.');
        }
        
        var index = collections.indexOf(collection);

                if (index === -1) {
            throw new DeveloperError('collection is not in this composite.');
        }
        
        return index;
    }

    function swapCollections(composite, i, j) {
        var arr = composite._collections;
        i = CesiumMath.clamp(i, 0, arr.length - 1);
        j = CesiumMath.clamp(j, 0, arr.length - 1);

        if (i === j) {
            return;
        }

        var temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;

        recomposite(composite);
    }

    /**
     * Raises a collection up one position in the composite.
     *
     * @param {EntityCollection} collection the collection to move.
     *
     * @exception {DeveloperError} collection is not in this composite.
     */
    CompositeEntityCollection.prototype.raiseCollection = function(collection) {
        var index = getCollectionIndex(this._collections, collection);
        swapCollections(this, index, index + 1);
    };

    /**
     * Lowers a collection down one position in the composite.
     *
     * @param {EntityCollection} collection the collection to move.
     *
     * @exception {DeveloperError} collection is not in this composite.
     */
    CompositeEntityCollection.prototype.lowerCollection = function(collection) {
        var index = getCollectionIndex(this._collections, collection);
        swapCollections(this, index, index - 1);
    };

    /**
     * Raises a collection to the top of the composite.
     *
     * @param {EntityCollection} collection the collection to move.
     *
     * @exception {DeveloperError} collection is not in this composite.
     */
    CompositeEntityCollection.prototype.raiseCollectionToTop = function(collection) {
        var index = getCollectionIndex(this._collections, collection);
        if (index === this._collections.length - 1) {
            return;
        }
        this._collections.splice(index, 1);
        this._collections.push(collection);

        recomposite(this);
    };

    /**
     * Lowers a collection to the bottom of the composite.
     *
     * @param {EntityCollection} collection the collection to move.
     *
     * @exception {DeveloperError} collection is not in this composite.
     */
    CompositeEntityCollection.prototype.lowerCollectionToBottom = function(collection) {
        var index = getCollectionIndex(this._collections, collection);
        if (index === 0) {
            return;
        }
        this._collections.splice(index, 1);
        this._collections.splice(0, 0, collection);

        recomposite(this);
    };

    /**
     * Prevents {@link EntityCollection#collectionChanged} events from being raised
     * until a corresponding call is made to {@link EntityCollection#resumeEvents}, at which
     * point a single event will be raised that covers all suspended operations.
     * This allows for many items to be added and removed efficiently.
     * While events are suspended, recompositing of the collections will
     * also be suspended, as this can be a costly operation.
     * This function can be safely called multiple times as long as there
     * are corresponding calls to {@link EntityCollection#resumeEvents}.
     */
    CompositeEntityCollection.prototype.suspendEvents = function() {
        this._suspendCount++;
        this._composite.suspendEvents();
    };

    /**
     * Resumes raising {@link EntityCollection#collectionChanged} events immediately
     * when an item is added or removed.  Any modifications made while while events were suspended
     * will be triggered as a single event when this function is called.  This function also ensures
     * the collection is recomposited if events are also resumed.
     * This function is reference counted and can safely be called multiple times as long as there
     * are corresponding calls to {@link EntityCollection#resumeEvents}.
     *
     * @exception {DeveloperError} resumeEvents can not be called before suspendEvents.
     */
    CompositeEntityCollection.prototype.resumeEvents = function() {
                if (this._suspendCount === 0) {
            throw new DeveloperError('resumeEvents can not be called before suspendEvents.');
        }
        
        this._suspendCount--;
        // recomposite before triggering events (but only if required for performance) that might depend on a composited collection
        if (this._shouldRecomposite && this._suspendCount === 0) {
            recomposite(this);
            this._shouldRecomposite = false;
        }

        this._composite.resumeEvents();
    };

    /**
     * Computes the maximum availability of the entities in the collection.
     * If the collection contains a mix of infinitely available data and non-infinite data,
     * It will return the interval pertaining to the non-infinite data only.  If all
     * data is infinite, an infinite interval will be returned.
     *
     * @returns {TimeInterval} The availability of entities in the collection.
     */
    CompositeEntityCollection.prototype.computeAvailability = function() {
        return this._composite.computeAvailability();
    };

    /**
     * Gets an entity with the specified id.
     *
     * @param {Object} id The id of the entity to retrieve.
     * @returns {Entity} The entity with the provided id or undefined if the id did not exist in the collection.
     */
    CompositeEntityCollection.prototype.getById = function(id) {
        return this._composite.getById(id);
    };

    CompositeEntityCollection.prototype._onCollectionChanged = function(collection, added, removed) {
        var collections = this._collectionsCopy;
        var collectionsLength = collections.length;
        var composite = this._composite;
        composite.suspendEvents();

        var i;
        var q;
        var entity;
        var compositeEntity;
        var removedLength = removed.length;
        var eventHash = this._eventHash;
        var collectionId = collection.id;
        for (i = 0; i < removedLength; i++) {
            var removedEntity = removed[i];
            unsubscribeFromEntity(this, eventHash, collectionId, removedEntity);

            var removedId = removedEntity.id;
            //Check if the removed entity exists in any of the remaining collections
            //If so, we clean and remerge it.
            for (q = collectionsLength - 1; q >= 0; q--) {
                entity = collections[q].getById(removedId);
                if (defined(entity)) {
                    if (!defined(compositeEntity)) {
                        compositeEntity = composite.getById(removedId);
                        clean(compositeEntity);
                    }
                    compositeEntity.merge(entity);
                }
            }
            //We never retrieved the compositeEntity, which means it no longer
            //exists in any of the collections, remove it from the composite.
            if (!defined(compositeEntity)) {
                composite.removeById(removedId);
            }
            compositeEntity = undefined;
        }

        var addedLength = added.length;
        for (i = 0; i < addedLength; i++) {
            var addedEntity = added[i];
            subscribeToEntity(this, eventHash, collectionId, addedEntity);

            var addedId = addedEntity.id;
            //We know the added entity exists in at least one collection,
            //but we need to check all collections and re-merge in order
            //to maintain the priority of properties.
            for (q = collectionsLength - 1; q >= 0; q--) {
                entity = collections[q].getById(addedId);
                if (defined(entity)) {
                    if (!defined(compositeEntity)) {
                        compositeEntity = composite.getById(addedId);
                        if (!defined(compositeEntity)) {
                            entityOptionsScratch.id = addedId;
                            compositeEntity = new Entity(entityOptionsScratch);
                            composite.add(compositeEntity);
                        } else {
                            clean(compositeEntity);
                        }
                    }
                    compositeEntity.merge(entity);
                }
            }
            compositeEntity = undefined;
        }

        composite.resumeEvents();
    };

    CompositeEntityCollection.prototype._onDefinitionChanged = function(entity, propertyName, newValue, oldValue) {
        var collections = this._collections;
        var composite = this._composite;

        var collectionsLength = collections.length;
        var id = entity.id;
        var compositeEntity = composite.getById(id);
        var compositeProperty = compositeEntity[propertyName];
        var newProperty = !defined(compositeProperty);

        var firstTime = true;
        for (var q = collectionsLength - 1; q >= 0; q--) {
            var innerEntity = collections[q].getById(entity.id);
            if (defined(innerEntity)) {
                var property = innerEntity[propertyName];
                if (defined(property)) {
                    if (firstTime) {
                        firstTime = false;
                        //We only want to clone if the property is also mergeable.
                        //This ensures that leaf properties are referenced and not copied,
                        //which is the entire point of compositing.
                        if (defined(property.merge) && defined(property.clone)) {
                            compositeProperty = property.clone(compositeProperty);
                        } else {
                            compositeProperty = property;
                            break;
                        }
                    }
                    compositeProperty.merge(property);
                }
            }
        }

        if (newProperty && compositeEntity.propertyNames.indexOf(propertyName) === -1) {
            compositeEntity.addProperty(propertyName);
        }

        compositeEntity[propertyName] = compositeProperty;
    };

    return CompositeEntityCollection;
});

/*global define*/
define('DataSources/CompositeProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/EventHelper',
        '../Core/TimeIntervalCollection',
        './Property'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event,
        EventHelper,
        TimeIntervalCollection,
        Property) {
    'use strict';

    function subscribeAll(property, eventHelper, definitionChanged, intervals) {
        function callback() {
            definitionChanged.raiseEvent(property);
        }
        var items = [];
        eventHelper.removeAll();
        var length = intervals.length;
        for (var i = 0; i < length; i++) {
            var interval = intervals.get(i);
            if (defined(interval.data) && items.indexOf(interval.data) === -1) {
                eventHelper.add(interval.data.definitionChanged, callback);
            }
        }
    }

    /**
     * A {@link Property} which is defined by a {@link TimeIntervalCollection}, where the
     * data property of each {@link TimeInterval} is another Property instance which is
     * evaluated at the provided time.
     *
     * @alias CompositeProperty
     * @constructor
     *
     *
     * @example
     * var constantProperty = ...;
     * var sampledProperty = ...;
     *
     * //Create a composite property from two previously defined properties
     * //where the property is valid on August 1st, 2012 and uses a constant
     * //property for the first half of the day and a sampled property for the
     * //remaining half.
     * var composite = new Cesium.CompositeProperty();
     * composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2012-08-01T00:00:00.00Z/2012-08-01T12:00:00.00Z',
     *     data : constantProperty
     * }));
     * composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2012-08-01T12:00:00.00Z/2012-08-02T00:00:00.00Z',
     *     isStartIncluded : false,
     *     isStopIncluded : false,
     *     data : sampledProperty
     * }));
     * 
     * @see CompositeMaterialProperty
     * @see CompositePositionProperty
     */
    function CompositeProperty() {
        this._eventHelper = new EventHelper();
        this._definitionChanged = new Event();
        this._intervals = new TimeIntervalCollection();
        this._intervals.changedEvent.addEventListener(CompositeProperty.prototype._intervalsChanged, this);
    }

    defineProperties(CompositeProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof CompositeProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._intervals.isEmpty;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value.
         * @memberof CompositeProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the interval collection.
         * @memberof CompositeProperty.prototype
         *
         * @type {TimeIntervalCollection}
         */
        intervals : {
            get : function() {
                return this._intervals;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    CompositeProperty.prototype.getValue = function(time, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required');
        }
        
        var innerProperty = this._intervals.findDataForIntervalContainingDate(time);
        if (defined(innerProperty)) {
            return innerProperty.getValue(time, result);
        }
        return undefined;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    CompositeProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof CompositeProperty && //
                this._intervals.equals(other._intervals, Property.equals));
    };

    /**
     * @private
     */
    CompositeProperty.prototype._intervalsChanged = function() {
        subscribeAll(this, this._eventHelper, this._definitionChanged, this._intervals);
        this._definitionChanged.raiseEvent(this);
    };

    return CompositeProperty;
});

/*global define*/
define('DataSources/CompositeMaterialProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './CompositeProperty',
        './Property'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event,
        CompositeProperty,
        Property) {
    'use strict';

    /**
     * A {@link CompositeProperty} which is also a {@link MaterialProperty}.
     *
     * @alias CompositeMaterialProperty
     * @constructor
     */
    function CompositeMaterialProperty() {
        this._definitionChanged = new Event();
        this._composite = new CompositeProperty();
        this._composite.definitionChanged.addEventListener(CompositeMaterialProperty.prototype._raiseDefinitionChanged, this);
    }

    defineProperties(CompositeMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof CompositeMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._composite.isConstant;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value.
         * @memberof CompositeMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the interval collection.
         * @memberof CompositeMaterialProperty.prototype
         *
         * @type {TimeIntervalCollection}
         */
        intervals : {
            get : function() {
                return this._composite._intervals;
            }
        }
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    CompositeMaterialProperty.prototype.getType = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required');
        }
        
        var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time);
        if (defined(innerProperty)) {
            return innerProperty.getType(time);
        }
        return undefined;
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    CompositeMaterialProperty.prototype.getValue = function(time, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required');
        }
        
        var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time);
        if (defined(innerProperty)) {
            return innerProperty.getValue(time, result);
        }
        return undefined;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    CompositeMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof CompositeMaterialProperty && //
                this._composite.equals(other._composite, Property.equals));
    };

    /**
     * @private
     */
    CompositeMaterialProperty.prototype._raiseDefinitionChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return CompositeMaterialProperty;
});

/*global define*/
define('DataSources/CompositePositionProperty',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/ReferenceFrame',
        './CompositeProperty',
        './Property'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        ReferenceFrame,
        CompositeProperty,
        Property) {
    'use strict';

    /**
     * A {@link CompositeProperty} which is also a {@link PositionProperty}.
     *
     * @alias CompositePositionProperty
     * @constructor
     *
     * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
     */
    function CompositePositionProperty(referenceFrame) {
        this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
        this._definitionChanged = new Event();
        this._composite = new CompositeProperty();
        this._composite.definitionChanged.addEventListener(CompositePositionProperty.prototype._raiseDefinitionChanged, this);
    }

    defineProperties(CompositePositionProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof CompositePositionProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._composite.isConstant;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value.
         * @memberof CompositePositionProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the interval collection.
         * @memberof CompositePositionProperty.prototype
         *
         * @type {TimeIntervalCollection}
         */
        intervals : {
            get : function() {
                return this._composite.intervals;
            }
        },
        /**
         * Gets or sets the reference frame which this position presents itself as.
         * Each PositionProperty making up this object has it's own reference frame,
         * so this property merely exposes a "preferred" reference frame for clients
         * to use.
         * @memberof CompositePositionProperty.prototype
         *
         * @type {ReferenceFrame}
         */
        referenceFrame : {
            get : function() {
                return this._referenceFrame;
            },
            set : function(value) {
                this._referenceFrame = value;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time in the fixed frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    CompositePositionProperty.prototype.getValue = function(time, result) {
        return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
    };

    /**
     * Gets the value of the property at the provided time and in the provided reference frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    CompositePositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(referenceFrame)) {
            throw new DeveloperError('referenceFrame is required.');
        }
        
        var innerProperty = this._composite._intervals.findDataForIntervalContainingDate(time);
        if (defined(innerProperty)) {
            return innerProperty.getValueInReferenceFrame(time, referenceFrame, result);
        }
        return undefined;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    CompositePositionProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof CompositePositionProperty && //
                this._referenceFrame === other._referenceFrame && //
                this._composite.equals(other._composite, Property.equals));
    };

    /**
     * @private
     */
    CompositePositionProperty.prototype._raiseDefinitionChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return CompositePositionProperty;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ShadowVolumeFS',[],function() {
    'use strict';
    return "#ifdef GL_EXT_frag_depth\n\
#extension GL_EXT_frag_depth : enable\n\
#endif\n\
\n\
// emulated noperspective\n\
varying float v_WindowZ;\n\
varying vec4 v_color;\n\
\n\
void writeDepthClampedToFarPlane()\n\
{\n\
#ifdef GL_EXT_frag_depth\n\
    gl_FragDepthEXT = min(v_WindowZ * gl_FragCoord.w, 1.0);\n\
#endif\n\
}\n\
\n\
void main(void)\n\
{\n\
    gl_FragColor = v_color;\n\
    writeDepthClampedToFarPlane();\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ShadowVolumeVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 extrudeDirection;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
\n\
uniform float u_globeMinimumAltitude;\n\
\n\
// emulated noperspective\n\
varying float v_WindowZ;\n\
varying vec4 v_color;\n\
\n\
vec4 depthClampFarPlane(vec4 vertexInClipCoordinates)\n\
{\n\
    v_WindowZ = (0.5 * (vertexInClipCoordinates.z / vertexInClipCoordinates.w) + 0.5) * vertexInClipCoordinates.w;\n\
    vertexInClipCoordinates.z = min(vertexInClipCoordinates.z, vertexInClipCoordinates.w);\n\
    return vertexInClipCoordinates;\n\
}\n\
\n\
void main()\n\
{\n\
    v_color = color;\n\
\n\
    vec4 position = czm_computePosition();\n\
    float delta = min(u_globeMinimumAltitude, czm_geometricToleranceOverMeter * length(position.xyz));\n\
\n\
    //extrudeDirection is zero for the top layer\n\
    position = position + vec4(extrudeDirection * delta, 0.0);\n\
    gl_Position = depthClampFarPlane(czm_modelViewProjectionRelativeToEye * position);\n\
}\n\
";
});
/*global define*/
define('Scene/DepthFunction',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Determines the function used to compare two depths for the depth test.
     *
     * @exports DepthFunction
     */
    var DepthFunction = {
        /**
         * The depth test never passes.
         *
         * @type {Number}
         * @constant
         */
        NEVER : WebGLConstants.NEVER,

        /**
         * The depth test passes if the incoming depth is less than the stored depth.
         *
         * @type {Number}
         * @constant
         */
        LESS : WebGLConstants.LESS,

        /**
         * The depth test passes if the incoming depth is equal to the stored depth.
         *
         * @type {Number}
         * @constant
         */
        EQUAL : WebGLConstants.EQUAL,

        /**
         * The depth test passes if the incoming depth is less than or equal to the stored depth.
         *
         * @type {Number}
         * @constant
         */
        LESS_OR_EQUAL : WebGLConstants.LEQUAL,

        /**
         * The depth test passes if the incoming depth is greater than the stored depth.
         *
         * @type {Number}
         * @constant
         */
        GREATER : WebGLConstants.GREATER,

        /**
         * The depth test passes if the incoming depth is not equal to the stored depth.
         *
         * @type {Number}
         * @constant
         */
        NOT_EQUAL : WebGLConstants.NOTEQUAL,

        /**
         * The depth test passes if the incoming depth is greater than or equal to the stored depth.
         *
         * @type {Number}
         * @constant
         */
        GREATER_OR_EQUAL : WebGLConstants.GEQUAL,

        /**
         * The depth test always passes.
         *
         * @type {Number}
         * @constant
         */
        ALWAYS : WebGLConstants.ALWAYS
    };

    return freezeObject(DepthFunction);
});

/*global define*/
define('Scene/StencilFunction',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Determines the function used to compare stencil values for the stencil test.
     *
     * @exports StencilFunction
     */
    var StencilFunction = {
        /**
         * The stencil test never passes.
         *
         * @type {Number}
         * @constant
         */
        NEVER : WebGLConstants.NEVER,

        /**
         * The stencil test passes when the masked reference value is less than the masked stencil value.
         *
         * @type {Number}
         * @constant
         */
        LESS : WebGLConstants.LESS,

        /**
         * The stencil test passes when the masked reference value is equal to the masked stencil value.
         *
         * @type {Number}
         * @constant
         */
        EQUAL : WebGLConstants.EQUAL,

        /**
         * The stencil test passes when the masked reference value is less than or equal to the masked stencil value.
         *
         * @type {Number}
         * @constant
         */
        LESS_OR_EQUAL : WebGLConstants.LEQUAL,

        /**
         * The stencil test passes when the masked reference value is greater than the masked stencil value.
         *
         * @type {Number}
         * @constant
         */
        GREATER : WebGLConstants.GREATER,

        /**
         * The stencil test passes when the masked reference value is not equal to the masked stencil value.
         *
         * @type {Number}
         * @constant
         */
        NOT_EQUAL : WebGLConstants.NOTEQUAL,

        /**
         * The stencil test passes when the masked reference value is greater than or equal to the masked stencil value.
         *
         * @type {Number}
         * @constant
         */
        GREATER_OR_EQUAL : WebGLConstants.GEQUAL,

        /**
         * The stencil test always passes.
         *
         * @type {Number}
         * @constant
         */
        ALWAYS : WebGLConstants.ALWAYS
    };

    return freezeObject(StencilFunction);
});

/*global define*/
define('Scene/StencilOperation',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * Determines the action taken based on the result of the stencil test.
     *
     * @exports StencilOperation
     */
    var StencilOperation = {
        /**
         * Sets the stencil buffer value to zero.
         *
         * @type {Number}
         * @constant
         */
        ZERO : WebGLConstants.ZERO,

        /**
         * Does not change the stencil buffer.
         *
         * @type {Number}
         * @constant
         */
        KEEP : WebGLConstants.KEEP,

        /**
         * Replaces the stencil buffer value with the reference value.
         *
         * @type {Number}
         * @constant
         */
        REPLACE : WebGLConstants.REPLACE,

        /**
         * Increments the stencil buffer value, clamping to unsigned byte.
         *
         * @type {Number}
         * @constant
         */
        INCREMENT : WebGLConstants.INCR,

        /**
         * Decrements the stencil buffer value, clamping to zero.
         *
         * @type {Number}
         * @constant
         */
        DECREMENT : WebGLConstants.DECR,

        /**
         * Bitwise inverts the existing stencil buffer value.
         *
         * @type {Number}
         * @constant
         */
        INVERT : WebGLConstants.INVERT,

        /**
         * Increments the stencil buffer value, wrapping to zero when exceeding the unsigned byte range.
         *
         * @type {Number}
         * @constant
         */
        INCREMENT_WRAP : WebGLConstants.INCR_WRAP,

        /**
         * Decrements the stencil buffer value, wrapping to the maximum unsigned byte instead of going below zero.
         *
         * @type {Number}
         * @constant
         */
        DECREMENT_WRAP : WebGLConstants.DECR_WRAP
    };

    return freezeObject(StencilOperation);
});

/*global define*/
define('Scene/GroundPrimitive',[
        '../Core/BoundingSphere',
        '../Core/buildModuleUrl',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/GeographicTilingScheme',
        '../Core/GeometryInstance',
        '../Core/isArray',
        '../Core/loadJson',
        '../Core/Math',
        '../Core/OrientedBoundingBox',
        '../Core/Rectangle',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Shaders/ShadowVolumeFS',
        '../Shaders/ShadowVolumeVS',
        '../ThirdParty/when',
        './BlendingState',
        './DepthFunction',
        './PerInstanceColorAppearance',
        './Primitive',
        './SceneMode',
        './StencilFunction',
        './StencilOperation'
    ], function(
        BoundingSphere,
        buildModuleUrl,
        Cartesian2,
        Cartesian3,
        Cartographic,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        GeographicTilingScheme,
        GeometryInstance,
        isArray,
        loadJson,
        CesiumMath,
        OrientedBoundingBox,
        Rectangle,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        ShadowVolumeFS,
        ShadowVolumeVS,
        when,
        BlendingState,
        DepthFunction,
        PerInstanceColorAppearance,
        Primitive,
        SceneMode,
        StencilFunction,
        StencilOperation) {
    'use strict';

    /**
     * A ground primitive represents geometry draped over the terrain in the {@link Scene}.  The geometry must be from a single {@link GeometryInstance}.
     * Batching multiple geometries is not yet supported.
     * <p>
     * A primitive combines the geometry instance with an {@link Appearance} that describes the full shading, including
     * {@link Material} and {@link RenderState}.  Roughly, the geometry instance defines the structure and placement,
     * and the appearance defines the visual characteristics.  Decoupling geometry and appearance allows us to mix
     * and match most of them and add a new geometry or appearance independently of each other. Only the {@link PerInstanceColorAppearance}
     * is supported at this time.
     * </p>
     * <p>
     * For correct rendering, this feature requires the EXT_frag_depth WebGL extension. For hardware that do not support this extension, there
     * will be rendering artifacts for some viewing angles.
     * </p>
     * <p>
     * Valid geometries are {@link CircleGeometry}, {@link CorridorGeometry}, {@link EllipseGeometry}, {@link PolygonGeometry}, and {@link RectangleGeometry}.
     * </p>
     *
     * @alias GroundPrimitive
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Array|GeometryInstance} [options.geometryInstances] The geometry instances to render.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
     * @param {Boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
     * @param {Boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory.
     * @param {Boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
     * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
     * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be <code>true</code> on
     *                  creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be <code>false</code>.
     *
     * @example
     * // Example 1: Create primitive with a single instance
     * var rectangleInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.RectangleGeometry({
     *     rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
     *   }),
     *   id : 'rectangle',
     *   attributes : {
     *     color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
     *   }
     * });
     * scene.primitives.add(new Cesium.GroundPrimitive({
     *   geometryInstances : rectangleInstance
     * }));
     *
     * // Example 2: Batch instances
     * var color = new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5); // Both instances must have the same color.
     * var rectangleInstance = new Cesium.GeometryInstance({
     *   geometry : new Cesium.RectangleGeometry({
     *     rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
     *   }),
     *   id : 'rectangle',
     *   attributes : {
     *     color : color
     *   }
     * });
     * var ellipseInstance = new Cesium.GeometryInstance({
     *     geometry : new Cesium.EllipseGeometry({
     *         center : Cesium.Cartesian3.fromDegrees(-105.0, 40.0),
     *         semiMinorAxis : 300000.0,
     *         semiMajorAxis : 400000.0
     *     }),
     *     id : 'ellipse',
     *     attributes : {
     *         color : color
     *     }
     * });
     * scene.primitives.add(new Cesium.GroundPrimitive({
     *   geometryInstances : [rectangleInstance, ellipseInstance]
     * }));
     *
     * @see Primitive
     * @see GeometryInstance
     * @see Appearance
     */
    function GroundPrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The geometry instance rendered with this primitive.  This may
         * be <code>undefined</code> if <code>options.releaseGeometryInstances</code>
         * is <code>true</code> when the primitive is constructed.
         * <p>
         * Changing this property after the primitive is rendered has no effect.
         * </p>
         * <p>
         * Because of the rendering technique used, all geometry instances must be the same color.
         * If there is an instance with a differing color, a <code>DeveloperError</code> will be thrown
         * on the first attempt to render.
         * </p>
         *
         * @type {Array|GeometryInstance}
         *
         * @default undefined
         */
        this.geometryInstances = options.geometryInstances;
        /**
         * Determines if the primitive will be shown.  This affects all geometry
         * instances in the primitive.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.show = defaultValue(options.show, true);
        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the shadow volume for each geometry in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false);
        this._debugShowShadowVolume = false;

        this._sp = undefined;
        this._spPick = undefined;

        this._rsStencilPreloadPass = undefined;
        this._rsStencilDepthPass = undefined;
        this._rsColorPass = undefined;
        this._rsPickPass = undefined;

        this._uniformMap = {
            u_globeMinimumAltitude: function() {
                return 55000.0;
            }
        };

        this._boundingVolumes = [];
        this._boundingVolumes2D = [];

        this._ready = false;
        this._readyPromise = when.defer();

        this._primitive = undefined;

        this._maxHeight = undefined;
        this._minHeight = undefined;

        this._maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
        this._minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;

        this._boundingSpheresKeys = [];
        this._boundingSpheres = [];

        var appearance = new PerInstanceColorAppearance({
            flat : true
        });

        var readOnlyAttributes;
        if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) {
            readOnlyAttributes = readOnlyInstanceAttributesScratch;
        }

        this._primitiveOptions = {
            geometryInstances : undefined,
            appearance : appearance,
            vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false),
            interleave : defaultValue(options.interleave, false),
            releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true),
            allowPicking : defaultValue(options.allowPicking, true),
            asynchronous : defaultValue(options.asynchronous, true),
            compressVertices : defaultValue(options.compressVertices, true),
            _readOnlyInstanceAttributes : readOnlyAttributes,
            _createRenderStatesFunction : undefined,
            _createShaderProgramFunction : undefined,
            _createCommandsFunction : undefined,
            _createPickOffsets : true
        };
    }

    var readOnlyInstanceAttributesScratch = ['color'];

    defineProperties(GroundPrimitive.prototype, {
        /**
         * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        vertexCacheOptimize : {
            get : function() {
                return this._primitiveOptions.vertexCacheOptimize;
            }
        },

        /**
         * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        interleave : {
            get : function() {
                return this._primitiveOptions.interleave;
            }
        },

        /**
         * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        releaseGeometryInstances : {
            get : function() {
                return this._primitiveOptions.releaseGeometryInstances;
            }
        },

        /**
         * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        allowPicking : {
            get : function() {
                return this._primitiveOptions.allowPicking;
            }
        },

        /**
         * Determines if the geometry instances will be created and batched on a web worker.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        asynchronous : {
            get : function() {
                return this._primitiveOptions.asynchronous;
            }
        },

        /**
         * When <code>true</code>, geometry vertices are compressed, which will save memory.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        compressVertices : {
            get : function() {
                return this._primitiveOptions.compressVertices;
            }
        },

        /**
         * Determines if the primitive is complete and ready to render.  If this property is
         * true, the primitive will be rendered the next time that {@link GroundPrimitive#update}
         * is called.
         *
         * @memberof GroundPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves when the primitive is ready to render.
         * @memberof GroundPrimitive.prototype
         * @type {Promise.<GroundPrimitive>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        }
    });

    /**
     * Determines if GroundPrimitive rendering is supported.
     *
     * @param {Scene} scene The scene.
     * @returns {Boolean} <code>true</code> if GroundPrimitives are supported; otherwise, returns <code>false</code>
     */
    GroundPrimitive.isSupported = function(scene) {
        return scene.context.stencilBuffer;
    };

    GroundPrimitive._defaultMaxTerrainHeight = 9000.0;
    GroundPrimitive._defaultMinTerrainHeight = -100000.0;

    GroundPrimitive._terrainHeights = undefined;
    GroundPrimitive._terrainHeightsMaxLevel = 6;

    function getComputeMaximumHeightFunction(primitive) {
        return function(granularity, ellipsoid) {
            var r = ellipsoid.maximumRadius;
            var delta = (r / Math.cos(granularity * 0.5)) - r;
            return primitive._maxHeight + delta;
        };
    }

    function getComputeMinimumHeightFunction(primitive) {
        return function(granularity, ellipsoid) {
            return primitive._minHeight;
        };
    }

    function getStencilPreloadRenderState(enableStencil) {
        return {
            colorMask : {
                red : false,
                green : false,
                blue : false,
                alpha : false
            },
            stencilTest : {
                enabled : enableStencil,
                frontFunction : StencilFunction.ALWAYS,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.DECREMENT_WRAP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                backFunction : StencilFunction.ALWAYS,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.INCREMENT_WRAP,
                    zPass : StencilOperation.INCREMENT_WRAP
                },
                reference : 0,
                mask : ~0
            },
            depthTest : {
                enabled : false
            },
            depthMask : false
        };
    }

    function getStencilDepthRenderState(enableStencil) {
        return {
            colorMask : {
                red : false,
                green : false,
                blue : false,
                alpha : false
            },
            stencilTest : {
                enabled : enableStencil,
                frontFunction : StencilFunction.ALWAYS,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.INCREMENT_WRAP
                },
                backFunction : StencilFunction.ALWAYS,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                reference : 0,
                mask : ~0
            },
            depthTest : {
                enabled : true,
                func : DepthFunction.LESS_OR_EQUAL
            },
            depthMask : false
        };
    }


    function getColorRenderState(enableStencil) {
        return {
            stencilTest : {
                enabled : enableStencil,
                frontFunction : StencilFunction.NOT_EQUAL,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                backFunction : StencilFunction.NOT_EQUAL,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                reference : 0,
                mask : ~0
            },
            depthTest : {
                enabled : false
            },
            depthMask : false,
            blending : BlendingState.ALPHA_BLEND
        };
    }

    var pickRenderState = {
        stencilTest : {
            enabled : true,
            frontFunction : StencilFunction.NOT_EQUAL,
            frontOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            backFunction : StencilFunction.NOT_EQUAL,
            backOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            reference : 0,
            mask : ~0
        },
        depthTest : {
            enabled : false
        },
        depthMask : false
    };

    var scratchBVCartesianHigh = new Cartesian3();
    var scratchBVCartesianLow = new Cartesian3();
    var scratchBVCartesian = new Cartesian3();
    var scratchBVCartographic = new Cartographic();
    var scratchBVRectangle = new Rectangle();
    var tilingScheme = new GeographicTilingScheme();
    var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
    var scratchTileXY = new Cartesian2();

    function getRectangle(frameState, geometry) {
        var ellipsoid = frameState.mapProjection.ellipsoid;

        if (!defined(geometry.attributes) || !defined(geometry.attributes.position3DHigh)) {
            if (defined(geometry.rectangle)) {
                return geometry.rectangle;
            }

            return undefined;
        }

        var highPositions = geometry.attributes.position3DHigh.values;
        var lowPositions = geometry.attributes.position3DLow.values;
        var length = highPositions.length;

        var minLat = Number.POSITIVE_INFINITY;
        var minLon = Number.POSITIVE_INFINITY;
        var maxLat = Number.NEGATIVE_INFINITY;
        var maxLon = Number.NEGATIVE_INFINITY;

        for (var i = 0; i < length; i +=3) {
            var highPosition = Cartesian3.unpack(highPositions, i, scratchBVCartesianHigh);
            var lowPosition = Cartesian3.unpack(lowPositions, i, scratchBVCartesianLow);

            var position = Cartesian3.add(highPosition, lowPosition, scratchBVCartesian);
            var cartographic = ellipsoid.cartesianToCartographic(position, scratchBVCartographic);

            var latitude = cartographic.latitude;
            var longitude = cartographic.longitude;

            minLat = Math.min(minLat, latitude);
            minLon = Math.min(minLon, longitude);
            maxLat = Math.max(maxLat, latitude);
            maxLon = Math.max(maxLon, longitude);
        }

        var rectangle = scratchBVRectangle;
        rectangle.north = maxLat;
        rectangle.south = minLat;
        rectangle.east = maxLon;
        rectangle.west = minLon;

        return rectangle;
    }

    var scratchDiagonalCartesianNE = new Cartesian3();
    var scratchDiagonalCartesianSW = new Cartesian3();
    var scratchDiagonalCartographic = new Cartographic();
    var scratchCenterCartesian = new Cartesian3();
    var scratchSurfaceCartesian = new Cartesian3();

    function getTileXYLevel(rectangle) {
        Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]);
        Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]);
        Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]);
        Cartographic.fromRadians(rectangle.west, rectangle.south, 0.0, scratchCorners[3]);

        // Determine which tile the bounding rectangle is in
        var lastLevelX = 0, lastLevelY = 0;
        var currentX = 0, currentY = 0;
        var maxLevel = GroundPrimitive._terrainHeightsMaxLevel;
        for(var i = 0; i <= maxLevel; ++i) {
            var failed = false;
            for(var j = 0; j < 4; ++j) {
                var corner = scratchCorners[j];
                tilingScheme.positionToTileXY(corner, i, scratchTileXY);
                if (j === 0) {
                    currentX = scratchTileXY.x;
                    currentY = scratchTileXY.y;
                } else if(currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
                    failed = true;
                    break;
                }
            }

            if (failed) {
                break;
            }

            lastLevelX = currentX;
            lastLevelY = currentY;
        }

        if (i === 0) {
            return undefined;
        }

        return {
            x : lastLevelX,
            y : lastLevelY,
            level : (i > maxLevel) ? maxLevel : (i - 1)
        };
    }

    function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) {
        var xyLevel = getTileXYLevel(rectangle);

        // Get the terrain min/max for that tile
        var minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
        var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
        if (defined(xyLevel)) {
            var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
            var heights = GroundPrimitive._terrainHeights[key];
            if (defined(heights)) {
                minTerrainHeight = heights[0];
                maxTerrainHeight = heights[1];
            }

            // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
            ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle, scratchDiagonalCartographic),
                scratchDiagonalCartesianNE);
            ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle, scratchDiagonalCartographic),
                scratchDiagonalCartesianSW);

            Cartesian3.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian);
            Cartesian3.add(scratchDiagonalCartesianNE,
                Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian);
            var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian);
            if (defined(surfacePosition)) {
                var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition);
                minTerrainHeight = Math.min(minTerrainHeight, -distance);
            } else {
                minTerrainHeight = GroundPrimitive._defaultMinTerrainHeight;
            }
        }

        primitive._minTerrainHeight = Math.max(GroundPrimitive._defaultMinTerrainHeight, minTerrainHeight);
        primitive._maxTerrainHeight = maxTerrainHeight;
    }

    var scratchBoundingSphere = new BoundingSphere();
    function getInstanceBoundingSphere(rectangle, ellipsoid) {
        var xyLevel = getTileXYLevel(rectangle);

        // Get the terrain max for that tile
        var maxTerrainHeight = GroundPrimitive._defaultMaxTerrainHeight;
        if (defined(xyLevel)) {
            var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
            var heights = GroundPrimitive._terrainHeights[key];
            if (defined(heights)) {
                maxTerrainHeight = heights[1];
            }
        }

        var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
        BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere);

        return BoundingSphere.union(result, scratchBoundingSphere, result);
    }

    function createBoundingVolume(groundPrimitive, frameState, geometry) {
        var ellipsoid = frameState.mapProjection.ellipsoid;
        var rectangle = getRectangle(frameState, geometry);

        // Use an oriented bounding box by default, but switch to a bounding sphere if bounding box creation would fail.
        if (rectangle.width < CesiumMath.PI) {
            var obb = OrientedBoundingBox.fromRectangle(rectangle, groundPrimitive._maxHeight, groundPrimitive._minHeight, ellipsoid);
            groundPrimitive._boundingVolumes.push(obb);
        } else {
            var highPositions = geometry.attributes.position3DHigh.values;
            var lowPositions = geometry.attributes.position3DLow.values;
            groundPrimitive._boundingVolumes.push(BoundingSphere.fromEncodedCartesianVertices(highPositions, lowPositions));
        }

        if (!frameState.scene3DOnly) {
            var projection = frameState.mapProjection;
            var boundingVolume = BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, groundPrimitive._maxHeight, groundPrimitive._minHeight);
            Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);

            groundPrimitive._boundingVolumes2D.push(boundingVolume);
        }
    }

    function createRenderStates(groundPrimitive, context, appearance, twoPasses) {
        if (defined(groundPrimitive._rsStencilPreloadPass)) {
            return;
        }
        var stencilEnabled = !groundPrimitive.debugShowShadowVolume;

        groundPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled));
        groundPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled));
        groundPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled));
        groundPrimitive._rsPickPass = RenderState.fromCache(pickRenderState);
    }

    function modifyForEncodedNormals(primitive, vertexShaderSource) {
        if (!primitive.compressVertices) {
            return vertexShaderSource;
        }

        if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) {
            var attributeName = 'compressedAttributes';

            //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes
            var attributeDecl = 'attribute vec2 ' + attributeName + ';';

            var globalDecl = 'vec3 extrudeDirection;\n';
            var decode = '    extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n';

            var modifiedVS = vertexShaderSource;
            modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, '');
            modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main');
            var compressedMain =
                'void main() \n' +
                '{ \n' +
                decode +
                '    czm_non_compressed_main(); \n' +
                '}';

            return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n');
        }
    }

    function createShaderProgram(groundPrimitive, frameState, appearance) {
        if (defined(groundPrimitive._sp)) {
            return;
        }

        var context = frameState.context;
        var primitive = groundPrimitive._primitive;
        var vs = ShadowVolumeVS;
        vs = groundPrimitive._primitive._batchTable.getVertexShaderCallback()(vs);
        vs = Primitive._appendShowToShader(primitive, vs);
        vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs);
        vs = Primitive._modifyShaderPosition(groundPrimitive, vs, frameState.scene3DOnly);
        vs = Primitive._updateColorAttribute(primitive, vs);
        vs = modifyForEncodedNormals(primitive, vs);

        var fs = ShadowVolumeFS;
        var attributeLocations = groundPrimitive._primitive._attributeLocations;

        groundPrimitive._sp = ShaderProgram.replaceCache({
            context : context,
            shaderProgram : groundPrimitive._sp,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });

        if (groundPrimitive._primitive.allowPicking) {
            var vsPick = ShaderSource.createPickVertexShaderSource(vs);
            vsPick = Primitive._updatePickColorAttribute(vsPick);

            var pickFS = new ShaderSource({
                sources : [fs],
                pickColorQualifier : 'varying'
            });
            groundPrimitive._spPick = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : groundPrimitive._spPick,
                vertexShaderSource : vsPick,
                fragmentShaderSource : pickFS,
                attributeLocations : attributeLocations
            });
        } else {
            groundPrimitive._spPick = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
        }
    }

    function createColorCommands(groundPrimitive, colorCommands) {
        var primitive = groundPrimitive._primitive;
        var length = primitive._va.length * 3;
        colorCommands.length = length;

        var vaIndex = 0;
        var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap);

        for (var i = 0; i < length; i += 3) {
            var vertexArray = primitive._va[vaIndex++];

            // stencil preload command
            var command = colorCommands[i];
            if (!defined(command)) {
                command = colorCommands[i] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.renderState = groundPrimitive._rsStencilPreloadPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // stencil depth command
            command = colorCommands[i + 1];
            if (!defined(command)) {
                command = colorCommands[i + 1] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.renderState = groundPrimitive._rsStencilDepthPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // color command
            command = colorCommands[i + 2];
            if (!defined(command)) {
                command = colorCommands[i + 2] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.renderState = groundPrimitive._rsColorPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;
        }
    }

    function createPickCommands(groundPrimitive, pickCommands) {
        var primitive = groundPrimitive._primitive;
        var pickOffsets = primitive._pickOffsets;
        var length = pickOffsets.length * 3;
        pickCommands.length = length;

        var pickIndex = 0;
        var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap);

        for (var j = 0; j < length; j += 3) {
            var pickOffset = pickOffsets[pickIndex++];

            var offset = pickOffset.offset;
            var count = pickOffset.count;
            var vertexArray = primitive._va[pickOffset.index];

            // stencil preload command
            var command = pickCommands[j];
            if (!defined(command)) {
                command = pickCommands[j] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.offset = offset;
            command.count = count;
            command.renderState = groundPrimitive._rsStencilPreloadPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // stencil depth command
            command = pickCommands[j + 1];
            if (!defined(command)) {
                command = pickCommands[j + 1] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.offset = offset;
            command.count = count;
            command.renderState = groundPrimitive._rsStencilDepthPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // color command
            command = pickCommands[j + 2];
            if (!defined(command)) {
                command = pickCommands[j + 2] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.offset = offset;
            command.count = count;
            command.renderState = groundPrimitive._rsPickPass;
            command.shaderProgram = groundPrimitive._spPick;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;
        }
    }

    function createCommands(groundPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
        createColorCommands(groundPrimitive, colorCommands);
        createPickCommands(groundPrimitive, pickCommands);
    }

    function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
        var boundingVolumes;
        if (frameState.mode === SceneMode.SCENE3D) {
            boundingVolumes = groundPrimitive._boundingVolumes;
        } else if (frameState.mode !== SceneMode.SCENE3D && defined(groundPrimitive._boundingVolumes2D)) {
            boundingVolumes = groundPrimitive._boundingVolumes2D;
        }

        var commandList = frameState.commandList;
        var passes = frameState.passes;
        if (passes.render) {
            var colorLength = colorCommands.length;
            for (var j = 0; j < colorLength; ++j) {
                colorCommands[j].modelMatrix = modelMatrix;
                colorCommands[j].boundingVolume = boundingVolumes[Math.floor(j / 3)];
                colorCommands[j].cull = cull;
                colorCommands[j].debugShowBoundingVolume = debugShowBoundingVolume;

                commandList.push(colorCommands[j]);
            }
        }

        if (passes.pick) {
            var primitive = groundPrimitive._primitive;
            var pickOffsets = primitive._pickOffsets;
            var length = pickOffsets.length * 3;
            pickCommands.length = length;

            var pickIndex = 0;
            for (var k = 0; k < length; k += 3) {
                var pickOffset = pickOffsets[pickIndex++];
                var bv = boundingVolumes[pickOffset.index];

                pickCommands[k].modelMatrix = modelMatrix;
                pickCommands[k].boundingVolume = bv;
                pickCommands[k].cull = cull;

                pickCommands[k + 1].modelMatrix = modelMatrix;
                pickCommands[k + 1].boundingVolume = bv;
                pickCommands[k + 1].cull = cull;

                pickCommands[k + 2].modelMatrix = modelMatrix;
                pickCommands[k + 2].boundingVolume = bv;
                pickCommands[k + 2].cull = cull;

                commandList.push(pickCommands[k], pickCommands[k + 1], pickCommands[k + 2]);
            }
        }
    }

    GroundPrimitive._initialized = false;
    GroundPrimitive._initPromise = undefined;

    /**
     * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the
     * GroundPrimitive asynchronously.
     *
     * @returns {Promise} A promise that will resolve once the terrain heights have been loaded.
     *
     */
    GroundPrimitive.initializeTerrainHeights = function() {
        var initPromise = GroundPrimitive._initPromise;
        if (defined(initPromise)) {
            return initPromise;
        }

        GroundPrimitive._initPromise = loadJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) {
            GroundPrimitive._initialized = true;
            GroundPrimitive._terrainHeights = json;
        });

        return GroundPrimitive._initPromise;
    };

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} All instance geometries must have the same primitiveType.
     * @exception {DeveloperError} Appearance and material have a uniform with the same name.
     * @exception {DeveloperError} Not all of the geometry instances have the same color attribute.
     */
    GroundPrimitive.prototype.update = function(frameState) {
        if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) {
            return;
        }

        if (!GroundPrimitive._initialized) {
                        if (!this.asynchronous) {
                throw new DeveloperError('For synchronous GroundPrimitives, you must call GroundPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.');
            }
            
            GroundPrimitive.initializeTerrainHeights();
            return;
        }

        var that = this;
        var primitiveOptions = this._primitiveOptions;

        if (!defined(this._primitive)) {
            var ellipsoid = frameState.mapProjection.ellipsoid;

            var instance;
            var geometry;
            var instanceType;

            var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances];
            var length = instances.length;
            var groundInstances = new Array(length);

            var i;
            var color;
            var rectangle;
            for (i = 0; i < length; ++i) {
                instance = instances[i];
                geometry = instance.geometry;
                var instanceRectangle = getRectangle(frameState, geometry);
                if (!defined(rectangle)) {
                    rectangle = instanceRectangle;
                } else {
                    if (defined(instanceRectangle)) {
                        Rectangle.union(rectangle, instanceRectangle, rectangle);
                    }
                }

                var id = instance.id;
                if (defined(id) && defined(instanceRectangle)) {
                    var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid);
                    this._boundingSpheresKeys.push(id);
                    this._boundingSpheres.push(boundingSphere);
                }

                instanceType = geometry.constructor;
                if (defined(instanceType) && defined(instanceType.createShadowVolume)) {
                    var attributes = instance.attributes;

                                        if (!defined(attributes) || !defined(attributes.color)) {
                        throw new DeveloperError('Not all of the geometry instances have the same color attribute.');
                    } else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) {
                        throw new DeveloperError('Not all of the geometry instances have the same color attribute.');
                    } else if (!defined(color)) {
                        color = attributes.color;
                    }
                                    } else {
                                        throw new DeveloperError('Not all of the geometry instances have GroundPrimitive support.');
                                    }
            }

            // Now compute the min/max heights for the primitive
            setMinMaxTerrainHeights(this, rectangle, frameState.mapProjection.ellipsoid);
            var exaggeration = frameState.terrainExaggeration;
            this._minHeight = this._minTerrainHeight * exaggeration;
            this._maxHeight = this._maxTerrainHeight * exaggeration;

            for (i = 0; i < length; ++i) {
                instance = instances[i];
                geometry = instance.geometry;
                instanceType = geometry.constructor;
                groundInstances[i] = new GeometryInstance({
                    geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this),
                        getComputeMaximumHeightFunction(this)),
                    attributes : instance.attributes,
                    id : instance.id,
                    pickPrimitive : this
                });
            }

            primitiveOptions.geometryInstances = groundInstances;

            primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) {
                createBoundingVolume(that, frameState, geometry);
            };
            primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) {
                createRenderStates(that, context);
            };
            primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) {
                createShaderProgram(that, frameState);
            };
            primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
                createCommands(that, undefined, undefined, true, false, colorCommands, pickCommands);
            };
            primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
                updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses);
            };

            this._primitive = new Primitive(primitiveOptions);
            this._primitive.readyPromise.then(function(primitive) {
                that._ready = true;

                if (that.releaseGeometryInstances) {
                    that.geometryInstances = undefined;
                }

                var error = primitive._error;
                if (!defined(error)) {
                    that._readyPromise.resolve(that);
                } else {
                    that._readyPromise.reject(error);
                }
            });
        }

        if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) {
            this._debugShowShadowVolume = true;
            this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false));
            this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false));
            this._rsColorPass = RenderState.fromCache(getColorRenderState(false));
        } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) {
            this._debugShowShadowVolume = false;
            this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true));
            this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true));
            this._rsColorPass = RenderState.fromCache(getColorRenderState(true));
        }

        this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
        this._primitive.update(frameState);
    };

    /**
     * @private
     */
    GroundPrimitive.prototype.getBoundingSphere = function(id) {
        var index = this._boundingSpheresKeys.indexOf(id);
        if (index !== -1) {
            return this._boundingSpheres[index];
        }

        return undefined;
    };

    /**
     * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
     *
     * @param {Object} id The id of the {@link GeometryInstance}.
     * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
     *
     * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
     * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
     */
    GroundPrimitive.prototype.getGeometryInstanceAttributes = function(id) {
                if (!defined(this._primitive)) {
            throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
        }
                return this._primitive.getGeometryInstanceAttributes(id);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see GroundPrimitive#destroy
     */
    GroundPrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * e = e && e.destroy();
     *
     * @see GroundPrimitive#isDestroyed
     */
    GroundPrimitive.prototype.destroy = function() {
        this._primitive = this._primitive && this._primitive.destroy();
        this._sp = this._sp && this._sp.destroy();
        this._spPick = this._spPick && this._spPick.destroy();
        return destroyObject(this);
    };

    return GroundPrimitive;
});

/*global define*/
define('DataSources/CorridorGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/CorridorGeometry',
        '../Core/CorridorOutlineGeometry',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/oneTimeWarning',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/GroundPrimitive',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        CorridorGeometry,
        CorridorOutlineGeometry,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        oneTimeWarning,
        ShowGeometryInstanceAttribute,
        GroundPrimitive,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.positions = undefined;
        this.width = undefined;
        this.cornerType = undefined;
        this.height = undefined;
        this.extrudedHeight = undefined;
        this.granularity = undefined;
    }

    /**
     * A {@link GeometryUpdater} for corridors.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias CorridorGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function CorridorGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(CorridorGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._isClosed = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._onTerrain = false;
        this._options = new GeometryOptions(entity);

        this._onEntityPropertyChanged(entity, 'corridor', entity.corridor, undefined);
    }

    defineProperties(CorridorGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof CorridorGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof CorridorGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(CorridorGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayCondition;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            get : function() {
                return this._isClosed;
            }
        },
        /**
         * Gets a value indicating if the geometry should be drawn on terrain.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        onTerrain : {
            get : function() {
                return this._onTerrain;
            }
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof CorridorGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    CorridorGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    CorridorGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    CorridorGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(this._distanceDisplayCondition.getValue(time));
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayCondition,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayCondition
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new CorridorGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    CorridorGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);

        return new GeometryInstance({
            id : entity,
            geometry : new CorridorOutlineGeometry(this._options),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(this._distanceDisplayCondition.getValue(time))
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    CorridorGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    CorridorGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    CorridorGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'corridor')) {
            return;
        }

        var corridor = this._entity.corridor;

        if (!defined(corridor)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = corridor.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = corridor.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var positions = corridor.positions;

        var show = corridor.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(positions))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(corridor.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(corridor.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(corridor.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(corridor.shadows, defaultShadows);
        this._distanceDisplayCondition = defaultValue(corridor.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var height = corridor.height;
        var extrudedHeight = corridor.extrudedHeight;
        var granularity = corridor.granularity;
        var width = corridor.width;
        var outlineWidth = corridor.outlineWidth;
        var cornerType = corridor.cornerType;
        var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) &&
                        isColorMaterial && GroundPrimitive.isSupported(this._scene);

        if (outlineEnabled && onTerrain) {
            oneTimeWarning(oneTimeWarning.geometryOutlines);
            outlineEnabled = false;
        }

        this._fillEnabled = fillEnabled;
        this._onTerrain = onTerrain;
        this._isClosed = defined(extrudedHeight) || onTerrain;
        this._outlineEnabled = outlineEnabled;

        if (!positions.isConstant || //
            !Property.isConstant(height) || //
            !Property.isConstant(extrudedHeight) || //
            !Property.isConstant(granularity) || //
            !Property.isConstant(width) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(cornerType) || //
            (onTerrain && !Property.isConstant(material))) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.positions = positions.getValue(Iso8601.MINIMUM_VALUE, options.positions);
            options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.cornerType = defined(cornerType) ? cornerType.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @param {PrimitiveCollection} groundPrimitives The ground primitives collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    CorridorGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
        this._primitives = primitives;
        this._groundPrimitives = groundPrimitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var geometryUpdater = this._geometryUpdater;
        var onTerrain = geometryUpdater._onTerrain;

        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
            primitives.removeAndDestroy(this._outlinePrimitive);
            this._outlinePrimitive = undefined;
        }
        this._primitive = undefined;

        var entity = geometryUpdater._entity;
        var corridor = entity.corridor;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(corridor.show, time, true)) {
            return;
        }

        var options = this._options;
        var positions = Property.getValueOrUndefined(corridor.positions, time, options.positions);
        var width = Property.getValueOrUndefined(corridor.width, time);
        if (!defined(positions) || !defined(width)) {
            return;
        }

        options.positions = positions;
        options.width = width;
        options.height = Property.getValueOrUndefined(corridor.height, time);
        options.extrudedHeight = Property.getValueOrUndefined(corridor.extrudedHeight, time);
        options.granularity = Property.getValueOrUndefined(corridor.granularity, time);
        options.cornerType = Property.getValueOrUndefined(corridor.cornerType, time);

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);
        var distanceDisplayCondition = this._geometryUpdater.distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);

        if (!defined(corridor.fill) || corridor.fill.getValue(time)) {
            var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
            var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
            this._material = material;

            if (onTerrain) {
                var currentColor = Color.WHITE;
                if (defined(fillMaterialProperty.color)) {
                    currentColor = fillMaterialProperty.color.getValue(time);
                }

                this._primitive = groundPrimitives.add(new GroundPrimitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new CorridorGeometry(options),
                        attributes: {
                            color: ColorGeometryInstanceAttribute.fromColor(currentColor),
                            distanceDisplayCondition : distanceDisplayConditionAttribute
                        }
                    }),
                    asynchronous : false,
                    shadows : shadows
                }));
            } else {
                var appearance = new MaterialAppearance({
                    material : material,
                    translucent : material.isTranslucent(),
                    closed : defined(options.extrudedHeight)
                });
                options.vertexFormat = appearance.vertexFormat;

                this._primitive = primitives.add(new Primitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new CorridorGeometry(options),
                        attributes : {
                            distanceDisplayCondition : distanceDisplayConditionAttribute
                        }
                    }),
                    appearance : appearance,
                    asynchronous : false,
                    shadows : shadows
                }));
            }
        }

        if (!onTerrain && defined(corridor.outline) && corridor.outline.getValue(time)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(corridor.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(corridor.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new CorridorOutlineGeometry(options),
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (this._geometryUpdater._onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
        }
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return CorridorGeometryUpdater;
});

/*global define*/
define('DataSources/DataSource',[
        '../Core/defineProperties',
        '../Core/DeveloperError'
    ], function(
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Defines the interface for data sources, which turn arbitrary data into a
     * {@link EntityCollection} for generic consumption. This object is an interface
     * for documentation purposes and is not intended to be instantiated directly.
     * @alias DataSource
     * @constructor
     *
     * @see Entity
     * @see DataSourceDisplay
     */
    function DataSource() {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(DataSource.prototype, {
        /**
         * Gets a human-readable name for this instance.
         * @memberof DataSource.prototype
         * @type {String}
         */
        name : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the preferred clock settings for this data source.
         * @memberof DataSource.prototype
         * @type {DataSourceClock}
         */
        clock : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the collection of {@link Entity} instances.
         * @memberof DataSource.prototype
         * @type {EntityCollection}
         */
        entities : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if the data source is currently loading data.
         * @memberof DataSource.prototype
         * @type {Boolean}
         */
        isLoading : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets an event that will be raised when the underlying data changes.
         * @memberof DataSource.prototype
         * @type {Event}
         */
        changedEvent : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets an event that will be raised if an error is encountered during processing.
         * @memberof DataSource.prototype
         * @type {Event}
         */
        errorEvent : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets an event that will be raised when the value of isLoading changes.
         * @memberof DataSource.prototype
         * @type {Event}
         */
        loadingEvent : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets whether or not this data source should be displayed.
         * @memberof DataSource.prototype
         * @type {Boolean}
         */
        show : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
         *
         * @memberof DataSource.prototype
         * @type {EntityCluster}
         */
        clustering : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Updates the data source to the provided time.  This function is optional and
     * is not required to be implemented.  It is provided for data sources which
     * retrieve data based on the current animation time or scene state.
     * If implemented, update will be called by {@link DataSourceDisplay} once a frame.
     * @function
     *
     * @param {JulianDate} time The simulation time.
     * @returns {Boolean} True if this data source is ready to be displayed at the provided time, false otherwise.
     */
    DataSource.prototype.update = DeveloperError.throwInstantiationError;

    /**
     * @private
     */
    DataSource.setLoading = function(dataSource, isLoading) {
        if (dataSource._isLoading !== isLoading) {
            if (isLoading) {
                dataSource._entityCollection.suspendEvents();
            } else {
                dataSource._entityCollection.resumeEvents();
            }
            dataSource._isLoading = isLoading;
            dataSource._loading.raiseEvent(dataSource, isLoading);
        }
    };

    return DataSource;
});

/*global define*/
define('Scene/SceneTransforms',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/Transforms',
        './SceneMode'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        defined,
        DeveloperError,
        CesiumMath,
        Matrix4,
        Transforms,
        SceneMode) {
    'use strict';

    /**
     * Functions that do scene-dependent transforms between rendering-related coordinate systems.
     *
     * @exports SceneTransforms
     */
    var SceneTransforms = {};

    var actualPositionScratch = new Cartesian4(0, 0, 0, 1);
    var positionCC = new Cartesian4();
    var scratchViewport = new BoundingRectangle();

    var scratchWindowCoord0 = new Cartesian2();
    var scratchWindowCoord1 = new Cartesian2();

    /**
     * Transforms a position in WGS84 coordinates to window coordinates.  This is commonly used to place an
     * HTML element at the same screen position as an object in the scene.
     *
     * @param {Scene} scene The scene.
     * @param {Cartesian3} position The position in WGS84 (world) coordinates.
     * @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.  This may be <code>undefined</code> if the input position is near the center of the ellipsoid.
     *
     * @example
     * // Output the window position of longitude/latitude (0, 0) every time the mouse moves.
     * var scene = widget.scene;
     * var ellipsoid = scene.globe.ellipsoid;
     * var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
     * handler.setInputAction(function(movement) {
     *     console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position));
     * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
     */
    SceneTransforms.wgs84ToWindowCoordinates = function(scene, position, result) {
        return SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(scene, position, Cartesian3.ZERO, result);
    };

    var scratchCartesian4 = new Cartesian4();
    var scratchEyeOffset = new Cartesian3();

    function worldToClip(position, eyeOffset, camera, result) {
        var viewMatrix = camera.viewMatrix;

        var positionEC = Matrix4.multiplyByVector(viewMatrix, Cartesian4.fromElements(position.x, position.y, position.z, 1, scratchCartesian4), scratchCartesian4);

        var zEyeOffset = Cartesian3.multiplyComponents(eyeOffset, Cartesian3.normalize(positionEC, scratchEyeOffset), scratchEyeOffset);
        positionEC.x += eyeOffset.x + zEyeOffset.x;
        positionEC.y += eyeOffset.y + zEyeOffset.y;
        positionEC.z += zEyeOffset.z;

        return Matrix4.multiplyByVector(camera.frustum.projectionMatrix, positionEC, result);
    }

    var scratchMaxCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
    var scratchProjectedCartesian = new Cartesian3();
    var scratchCameraPosition = new Cartesian3();

    /**
     * @private
     */
    SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates = function(scene, position, eyeOffset, result) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(position)) {
            throw new DeveloperError('position is required.');
        }
        
        // Transform for 3D, 2D, or Columbus view
        var frameState = scene.frameState;
        var actualPosition = SceneTransforms.computeActualWgs84Position(frameState, position, actualPositionScratch);

        if (!defined(actualPosition)) {
            return undefined;
        }

        // Assuming viewport takes up the entire canvas...
        var canvas = scene.canvas;
        var viewport = scratchViewport;
        viewport.x = 0;
        viewport.y = 0;
        viewport.width = canvas.clientWidth;
        viewport.height = canvas.clientHeight;

        var camera = scene.camera;
        var cameraCentered = false;

        if (frameState.mode === SceneMode.SCENE2D) {
            var projection = scene.mapProjection;
            var maxCartographic = scratchMaxCartographic;
            var maxCoord = projection.project(maxCartographic, scratchProjectedCartesian);

            var cameraPosition = Cartesian3.clone(camera.position, scratchCameraPosition);
            var frustum = camera.frustum.clone();

            var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, new Matrix4());
            var projectionMatrix = camera.frustum.projectionMatrix;

            var x = camera.positionWC.y;
            var eyePoint = Cartesian3.fromElements(CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x);
            var windowCoordinates = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, eyePoint);

            if (x === 0.0 || windowCoordinates.x <= 0.0 || windowCoordinates.x >= canvas.clientWidth) {
                cameraCentered = true;
            } else {
                if (windowCoordinates.x > canvas.clientWidth * 0.5) {
                    viewport.width = windowCoordinates.x;

                    camera.frustum.right = maxCoord.x - x;

                    positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
                    SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord0);

                    viewport.x += windowCoordinates.x;

                    camera.position.x = -camera.position.x;

                    var right = camera.frustum.right;
                    camera.frustum.right = -camera.frustum.left;
                    camera.frustum.left = -right;

                    positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
                    SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord1);
                } else {
                    viewport.x += windowCoordinates.x;
                    viewport.width -= windowCoordinates.x;

                    camera.frustum.left = -maxCoord.x - x;

                    positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
                    SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord0);

                    viewport.x = viewport.x - viewport.width;

                    camera.position.x = -camera.position.x;

                    var left = camera.frustum.left;
                    camera.frustum.left = -camera.frustum.right;
                    camera.frustum.right = -left;

                    positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
                    SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, scratchWindowCoord1);
                }

                Cartesian3.clone(cameraPosition, camera.position);
                camera.frustum = frustum.clone();

                result = Cartesian2.clone(scratchWindowCoord0, result);
                if (result.x < 0.0 || result.x > canvas.clientWidth) {
                    result.x = scratchWindowCoord1.x;
                }
            }
        }

        if (frameState.mode !== SceneMode.SCENE2D || cameraCentered) {
            // View-projection matrix to transform from world coordinates to clip coordinates
            positionCC = worldToClip(actualPosition, eyeOffset, camera, positionCC);
            if (positionCC.z < 0 && frameState.mode !== SceneMode.SCENE2D) {
                return undefined;
            }

            result = SceneTransforms.clipToGLWindowCoordinates(viewport, positionCC, result);
        }

        result.y = canvas.clientHeight - result.y;
        return result;
    };

    /**
     * Transforms a position in WGS84 coordinates to drawing buffer coordinates.  This may produce different
     * results from SceneTransforms.wgs84ToWindowCoordinates when the browser zoom is not 100%, or on high-DPI displays.
     *
     * @param {Scene} scene The scene.
     * @param {Cartesian3} position The position in WGS84 (world) coordinates.
     * @param {Cartesian2} [result] An optional object to return the input position transformed to window coordinates.
     * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.  This may be <code>undefined</code> if the input position is near the center of the ellipsoid.
     *
     * @example
     * // Output the window position of longitude/latitude (0, 0) every time the mouse moves.
     * var scene = widget.scene;
     * var ellipsoid = scene.globe.ellipsoid;
     * var position = Cesium.Cartesian3.fromDegrees(0.0, 0.0);
     * var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
     * handler.setInputAction(function(movement) {
     *     console.log(Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position));
     * }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
     */
    SceneTransforms.wgs84ToDrawingBufferCoordinates = function(scene, position, result) {
        result = SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
        if (!defined(result)) {
            return undefined;
        }

        return SceneTransforms.transformWindowToDrawingBuffer(scene, result, result);
    };

    var projectedPosition = new Cartesian3();
    var positionInCartographic = new Cartographic();

    /**
     * @private
     */
    SceneTransforms.computeActualWgs84Position = function(frameState, position, result) {
        var mode = frameState.mode;

        if (mode === SceneMode.SCENE3D) {
            return Cartesian3.clone(position, result);
        }

        var projection = frameState.mapProjection;
        var cartographic = projection.ellipsoid.cartesianToCartographic(position, positionInCartographic);
        if (!defined(cartographic)) {
            return undefined;
        }

        projection.project(cartographic, projectedPosition);

        if (mode === SceneMode.COLUMBUS_VIEW) {
            return Cartesian3.fromElements(projectedPosition.z, projectedPosition.x, projectedPosition.y, result);
        }

        if (mode === SceneMode.SCENE2D) {
            return Cartesian3.fromElements(0.0, projectedPosition.x, projectedPosition.y, result);
        }

        // mode === SceneMode.MORPHING
        var morphTime = frameState.morphTime;
        return Cartesian3.fromElements(
            CesiumMath.lerp(projectedPosition.z, position.x, morphTime),
            CesiumMath.lerp(projectedPosition.x, position.y, morphTime),
            CesiumMath.lerp(projectedPosition.y, position.z, morphTime),
            result);
    };

    var positionNDC = new Cartesian3();
    var positionWC = new Cartesian3();
    var viewportTransform = new Matrix4();

    /**
     * @private
     */
    SceneTransforms.clipToGLWindowCoordinates = function(viewport, position, result) {
        // Perspective divide to transform from clip coordinates to normalized device coordinates
        Cartesian3.divideByScalar(position, position.w, positionNDC);

        // Viewport transform to transform from clip coordinates to window coordinates
        Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform);
        Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC);

        return Cartesian2.fromCartesian3(positionWC, result);
    };

    /**
     * @private
     */
    SceneTransforms.clipToDrawingBufferCoordinates = function(viewport, position, result) {
        // Perspective divide to transform from clip coordinates to normalized device coordinates
        Cartesian3.divideByScalar(position, position.w, positionNDC);

        // Viewport transform to transform from clip coordinates to drawing buffer coordinates
        Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, viewportTransform);
        Matrix4.multiplyByPoint(viewportTransform, positionNDC, positionWC);

        return Cartesian2.fromCartesian3(positionWC, result);
    };

    /**
     * @private
     */
    SceneTransforms.transformWindowToDrawingBuffer = function(scene, windowPosition, result) {
        var canvas = scene.canvas;
        var xScale = scene.drawingBufferWidth / canvas.clientWidth;
        var yScale = scene.drawingBufferHeight / canvas.clientHeight;
        return Cartesian2.fromElements(windowPosition.x * xScale, windowPosition.y * yScale, result);
    };

    var scratchNDC = new Cartesian4();
    var scratchWorldCoords = new Cartesian4();

    /**
     * @private
     */
    SceneTransforms.drawingBufferToWgs84Coordinates = function(scene, drawingBufferPosition, depth, result) {
        var context = scene.context;
        var uniformState = context.uniformState;

        var viewport = scene._passState.viewport;
        var ndc = Cartesian4.clone(Cartesian4.UNIT_W, scratchNDC);
        ndc.x = (drawingBufferPosition.x - viewport.x) / viewport.width * 2.0 - 1.0;
        ndc.y = (drawingBufferPosition.y - viewport.y) / viewport.height * 2.0 - 1.0;
        ndc.z = (depth * 2.0) - 1.0;
        ndc.w = 1.0;

        var worldCoords;
        var frustum = scene.camera.frustum;
        if (!defined(frustum.fovy)) {
            var currentFrustum = uniformState.currentFrustum;
            worldCoords = scratchWorldCoords;
            worldCoords.x = (ndc.x * (frustum.right - frustum.left) + frustum.left + frustum.right) * 0.5;
            worldCoords.y = (ndc.y * (frustum.top - frustum.bottom) + frustum.bottom + frustum.top) * 0.5;
            worldCoords.z = (ndc.z * (currentFrustum.x - currentFrustum.y) - currentFrustum.x - currentFrustum.y) * 0.5;
            worldCoords.w = 1.0;

            worldCoords = Matrix4.multiplyByVector(uniformState.inverseView, worldCoords, worldCoords);
        } else {
            worldCoords = Matrix4.multiplyByVector(uniformState.inverseViewProjection, ndc, scratchWorldCoords);

            // Reverse perspective divide
            var w = 1.0 / worldCoords.w;
            Cartesian3.multiplyByScalar(worldCoords, w, worldCoords);
        }

        return Cartesian3.fromCartesian4(worldCoords, result);
    };

    return SceneTransforms;
});

/*global define*/
define('Scene/Billboard',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/Color',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/Matrix4',
        '../Core/NearFarScalar',
        './HeightReference',
        './HorizontalOrigin',
        './SceneMode',
        './SceneTransforms',
        './VerticalOrigin'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        Color,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        DistanceDisplayCondition,
        Matrix4,
        NearFarScalar,
        HeightReference,
        HorizontalOrigin,
        SceneMode,
        SceneTransforms,
        VerticalOrigin) {
    'use strict';

    /**
     * A viewport-aligned image positioned in the 3D scene, that is created
     * and rendered using a {@link BillboardCollection}.  A billboard is created and its initial
     * properties are set by calling {@link BillboardCollection#add}.
     * <br /><br />
     * <div align='center'>
     * <img src='images/Billboard.png' width='400' height='300' /><br />
     * Example billboards
     * </div>
     *
     * @alias Billboard
     *
     * @performance Reading a property, e.g., {@link Billboard#show}, is constant time.
     * Assigning to a property is constant time but results in
     * CPU to GPU traffic when {@link BillboardCollection#update} is called.  The per-billboard traffic is
     * the same regardless of how many properties were updated.  If most billboards in a collection need to be
     * updated, it may be more efficient to clear the collection with {@link BillboardCollection#removeAll}
     * and add new billboards instead of modifying each one.
     *
     * @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near
     * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
     * @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near
     * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
     *
     * @see BillboardCollection
     * @see BillboardCollection#add
     * @see Label
     *
     * @internalConstructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
     */
    function Billboard(options, billboardCollection) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) {
            throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.');
        }
        if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) {
            throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.');
        }
        if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) {
            throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.');
        }
        if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) {
            throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near');
        }
        
        this._show = defaultValue(options.show, true);
        this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
        this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D
        this._pixelOffset = Cartesian2.clone(defaultValue(options.pixelOffset, Cartesian2.ZERO));
        this._translate = new Cartesian2(0.0, 0.0); // used by labels for glyph vertex translation
        this._eyeOffset = Cartesian3.clone(defaultValue(options.eyeOffset, Cartesian3.ZERO));
        this._heightReference = defaultValue(options.heightReference, HeightReference.NONE);
        this._verticalOrigin = defaultValue(options.verticalOrigin, VerticalOrigin.CENTER);
        this._horizontalOrigin = defaultValue(options.horizontalOrigin, HorizontalOrigin.CENTER);
        this._scale = defaultValue(options.scale, 1.0);
        this._color = Color.clone(defaultValue(options.color, Color.WHITE));
        this._rotation = defaultValue(options.rotation, 0.0);
        this._alignedAxis = Cartesian3.clone(defaultValue(options.alignedAxis, Cartesian3.ZERO));
        this._width = options.width;
        this._height = options.height;
        this._scaleByDistance = options.scaleByDistance;
        this._translucencyByDistance = options.translucencyByDistance;
        this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance;
        this._sizeInMeters = defaultValue(options.sizeInMeters, false);
        this._distanceDisplayCondition = options.distanceDisplayCondition;
        this._id = options.id;
        this._collection = defaultValue(options.collection, billboardCollection);

        this._pickId = undefined;
        this._pickPrimitive = defaultValue(options._pickPrimitive, this);
        this._billboardCollection = billboardCollection;
        this._dirty = false;
        this._index = -1; //Used only by BillboardCollection

        this._imageIndex = -1;
        this._imageIndexPromise = undefined;
        this._imageId = undefined;
        this._image = undefined;
        this._imageSubRegion = undefined;
        this._imageWidth = undefined;
        this._imageHeight = undefined;

        var image = options.image;
        var imageId = options.imageId;
        if (defined(image)) {
            if (!defined(imageId)) {
                if (typeof image === 'string') {
                    imageId = image;
                } else if (defined(image.src)) {
                    imageId = image.src;
                } else {
                    imageId = createGuid();
                }
            }

            this._imageId = imageId;
            this._image = image;
        }

        if (defined(options.imageSubRegion)) {
            this._imageId = imageId;
            this._imageSubRegion = options.imageSubRegion;
        }

        if (defined(this._billboardCollection._textureAtlas)) {
            this._loadImage();
        }

        this._actualClampedPosition = undefined;
        this._removeCallbackFunc = undefined;
        this._mode = SceneMode.SCENE3D;

        this._clusterShow = true;

        this._updateClamping();
    }

    var SHOW_INDEX = Billboard.SHOW_INDEX = 0;
    var POSITION_INDEX = Billboard.POSITION_INDEX = 1;
    var PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX = 2;
    var EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX = 3;
    var HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX = 4;
    var VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX = 5;
    var SCALE_INDEX = Billboard.SCALE_INDEX = 6;
    var IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX = 7;
    var COLOR_INDEX = Billboard.COLOR_INDEX = 8;
    var ROTATION_INDEX = Billboard.ROTATION_INDEX = 9;
    var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX = 10;
    var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX = 11;
    var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX = 12;
    var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = 13;
    var DISTANCE_DISPLAY_CONDITION = Billboard.DISTANCE_DISPLAY_CONDITION = 14;
    Billboard.NUMBER_OF_PROPERTIES = 15;

    function makeDirty(billboard, propertyChanged) {
        var billboardCollection = billboard._billboardCollection;
        if (defined(billboardCollection)) {
            billboardCollection._updateBillboard(billboard, propertyChanged);
            billboard._dirty = true;
        }
    }

    defineProperties(Billboard.prototype, {
        /**
         * Determines if this billboard will be shown.  Use this to hide or show a billboard, instead
         * of removing it and re-adding it to the collection.
         * @memberof Billboard.prototype
         * @type {Boolean}
         * @default true
         */
        show : {
            get : function() {
                return this._show;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._show !== value) {
                    this._show = value;
                    makeDirty(this, SHOW_INDEX);
                }
            }
        },

        /**
        * Gets or sets the Cartesian position of this billboard.
        * @memberof Billboard.prototype
        * @type {Cartesian3}
        */
        position : {
            get : function() {
                return this._position;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var position = this._position;
                if (!Cartesian3.equals(position, value)) {
                    Cartesian3.clone(value, position);
                    Cartesian3.clone(value, this._actualPosition);

                    this._updateClamping();
                    makeDirty(this, POSITION_INDEX);
                }
            }
        },

        /**
         * Gets or sets the height reference of this billboard.
         * @memberof Billboard.prototype
         * @type {HeightReference}
         * @default HeightReference.NONE
         */
        heightReference : {
            get : function() {
                return this._heightReference;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var heightReference = this._heightReference;
                if (value !== heightReference) {
                    this._heightReference = value;
                    this._updateClamping();
                    makeDirty(this, POSITION_INDEX);
                }
            }
        },

        /**
         * Gets or sets the pixel offset in screen space from the origin of this billboard.  This is commonly used
         * to align multiple billboards and labels at the same position, e.g., an image and text.  The
         * screen space origin is the top, left corner of the canvas; <code>x</code> increases from
         * left to right, and <code>y</code> increases from top to bottom.
         * <br /><br />
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><code>default</code><br/><img src='images/Billboard.setPixelOffset.default.png' width='250' height='188' /></td>
         * <td align='center'><code>b.pixeloffset = new Cartesian2(50, 25);</code><br/><img src='images/Billboard.setPixelOffset.x50y-25.png' width='250' height='188' /></td>
         * </tr></table>
         * The billboard's origin is indicated by the yellow point.
         * </div>
         * @memberof Billboard.prototype
         * @type {Cartesian2}
         */
        pixelOffset : {
            get : function() {
                return this._pixelOffset;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var pixelOffset = this._pixelOffset;
                if (!Cartesian2.equals(pixelOffset, value)) {
                    Cartesian2.clone(value, pixelOffset);
                    makeDirty(this, PIXEL_OFFSET_INDEX);
                }
            }
        },

        /**
         * Gets or sets near and far scaling properties of a Billboard based on the billboard's distance from the camera.
         * A billboard's scale will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the billboard's scale remains clamped to the nearest bound.  If undefined,
         * scaleByDistance will be disabled.
         * @memberof Billboard.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a billboard's scaleByDistance to scale by 1.5 when the
         * // camera is 1500 meters from the billboard and disappear as
         * // the camera distance approaches 8.0e6 meters.
         * b.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0);
         *
         * @example
         * // Example 2.
         * // disable scaling by distance
         * b.scaleByDistance = undefined;
         */
        scaleByDistance : {
            get : function() {
                return this._scaleByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var scaleByDistance = this._scaleByDistance;
                if (!NearFarScalar.equals(scaleByDistance, value)) {
                    this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);
                    makeDirty(this, SCALE_BY_DISTANCE_INDEX);
                }
            }
        },

        /**
         * Gets or sets near and far translucency properties of a Billboard based on the billboard's distance from the camera.
         * A billboard's translucency will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the billboard's translucency remains clamped to the nearest bound.  If undefined,
         * translucencyByDistance will be disabled.
         * @memberof Billboard.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a billboard's translucency to 1.0 when the
         * // camera is 1500 meters from the billboard and disappear as
         * // the camera distance approaches 8.0e6 meters.
         * b.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
         *
         * @example
         * // Example 2.
         * // disable translucency by distance
         * b.translucencyByDistance = undefined;
         */
        translucencyByDistance : {
            get : function() {
                return this._translucencyByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var translucencyByDistance = this._translucencyByDistance;
                if (!NearFarScalar.equals(translucencyByDistance, value)) {
                    this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance);
                    makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX);
                }
            }
        },

        /**
         * Gets or sets near and far pixel offset scaling properties of a Billboard based on the billboard's distance from the camera.
         * A billboard's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the billboard's pixel offset scale remains clamped to the nearest bound.  If undefined,
         * pixelOffsetScaleByDistance will be disabled.
         * @memberof Billboard.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a billboard's pixel offset scale to 0.0 when the
         * // camera is 1500 meters from the billboard and scale pixel offset to 10.0 pixels
         * // in the y direction the camera distance approaches 8.0e6 meters.
         * b.pixelOffset = new Cesium.Cartesian2(0.0, 1.0);
         * b.pixelOffsetScaleByDistance = new Cesium.NearFarScalar(1.5e2, 0.0, 8.0e6, 10.0);
         *
         * @example
         * // Example 2.
         * // disable pixel offset by distance
         * b.pixelOffsetScaleByDistance = undefined;
         */
        pixelOffsetScaleByDistance : {
            get : function() {
                return this._pixelOffsetScaleByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
                if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) {
                    this._pixelOffsetScaleByDistance = NearFarScalar.clone(value, pixelOffsetScaleByDistance);
                    makeDirty(this, PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX);
                }
            }
        },

        /**
         * Gets or sets the 3D Cartesian offset applied to this billboard in eye coordinates.  Eye coordinates is a left-handed
         * coordinate system, where <code>x</code> points towards the viewer's right, <code>y</code> points up, and
         * <code>z</code> points into the screen.  Eye coordinates use the same scale as world and model coordinates,
         * which is typically meters.
         * <br /><br />
         * An eye offset is commonly used to arrange multiple billboards or objects at the same position, e.g., to
         * arrange a billboard above its corresponding 3D model.
         * <br /><br />
         * Below, the billboard is positioned at the center of the Earth but an eye offset makes it always
         * appear on top of the Earth regardless of the viewer's or Earth's orientation.
         * <br /><br />
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><img src='images/Billboard.setEyeOffset.one.png' width='250' height='188' /></td>
         * <td align='center'><img src='images/Billboard.setEyeOffset.two.png' width='250' height='188' /></td>
         * </tr></table>
         * <code>b.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);</code><br /><br />
         * </div>
         * @memberof Billboard.prototype
         * @type {Cartesian3}
         */
        eyeOffset : {
            get : function() {
                return this._eyeOffset;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var eyeOffset = this._eyeOffset;
                if (!Cartesian3.equals(eyeOffset, value)) {
                    Cartesian3.clone(value, eyeOffset);
                    makeDirty(this, EYE_OFFSET_INDEX);
                }
            }
        },

        /**
         * Gets or sets the horizontal origin of this billboard, which determines if the billboard is
         * to the left, center, or right of its anchor position.
         * <br /><br />
         * <div align='center'>
         * <img src='images/Billboard.setHorizontalOrigin.png' width='648' height='196' /><br />
         * </div>
         * @memberof Billboard.prototype
         * @type {HorizontalOrigin}
         * @example
         * // Use a bottom, left origin
         * b.horizontalOrigin = Cesium.HorizontalOrigin.LEFT;
         * b.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
         */
        horizontalOrigin : {
            get : function() {
                return this._horizontalOrigin;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._horizontalOrigin !== value) {
                    this._horizontalOrigin = value;
                    makeDirty(this, HORIZONTAL_ORIGIN_INDEX);
                }
            }
        },

        /**
         * Gets or sets the vertical origin of this billboard, which determines if the billboard is
         * to the above, below, or at the center of its anchor position.
         * <br /><br />
         * <div align='center'>
         * <img src='images/Billboard.setVerticalOrigin.png' width='695' height='175' /><br />
         * </div>
         * @memberof Billboard.prototype
         * @type {VerticalOrigin}
         * @example
         * // Use a bottom, left origin
         * b.horizontalOrigin = Cesium.HorizontalOrigin.LEFT;
         * b.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
         */
        verticalOrigin : {
            get : function() {
                return this._verticalOrigin;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._verticalOrigin !== value) {
                    this._verticalOrigin = value;
                    makeDirty(this, VERTICAL_ORIGIN_INDEX);
                }
            }
        },

        /**
         * Gets or sets the uniform scale that is multiplied with the billboard's image size in pixels.
         * A scale of <code>1.0</code> does not change the size of the billboard; a scale greater than
         * <code>1.0</code> enlarges the billboard; a positive scale less than <code>1.0</code> shrinks
         * the billboard.
         * <br /><br />
         * <div align='center'>
         * <img src='images/Billboard.setScale.png' width='400' height='300' /><br/>
         * From left to right in the above image, the scales are <code>0.5</code>, <code>1.0</code>,
         * and <code>2.0</code>.
         * </div>
         * @memberof Billboard.prototype
         * @type {Number}
         */
        scale : {
            get : function() {
                return this._scale;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._scale !== value) {
                    this._scale = value;
                    makeDirty(this, SCALE_INDEX);
                }
            }
        },

        /**
         * Gets or sets the color that is multiplied with the billboard's texture.  This has two common use cases.  First,
         * the same white texture may be used by many different billboards, each with a different color, to create
         * colored billboards.  Second, the color's alpha component can be used to make the billboard translucent as shown below.
         * An alpha of <code>0.0</code> makes the billboard transparent, and <code>1.0</code> makes the billboard opaque.
         * <br /><br />
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><code>default</code><br/><img src='images/Billboard.setColor.Alpha255.png' width='250' height='188' /></td>
         * <td align='center'><code>alpha : 0.5</code><br/><img src='images/Billboard.setColor.Alpha127.png' width='250' height='188' /></td>
         * </tr></table>
         * </div>
         * <br />
         * The red, green, blue, and alpha values are indicated by <code>value</code>'s <code>red</code>, <code>green</code>,
         * <code>blue</code>, and <code>alpha</code> properties as shown in Example 1.  These components range from <code>0.0</code>
         * (no intensity) to <code>1.0</code> (full intensity).
         * @memberof Billboard.prototype
         * @type {Color}
         *
         * @example
         * // Example 1. Assign yellow.
         * b.color = Cesium.Color.YELLOW;
         *
         * @example
         * // Example 2. Make a billboard 50% translucent.
         * b.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5);
         */
        color : {
            get : function() {
                return this._color;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var color = this._color;
                if (!Color.equals(color, value)) {
                    Color.clone(value, color);
                    makeDirty(this, COLOR_INDEX);
                }
            }
        },

        /**
         * Gets or sets the rotation angle in radians.
         * @memberof Billboard.prototype
         * @type {Number}
         */
        rotation : {
            get : function() {
                return this._rotation;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._rotation !== value) {
                    this._rotation = value;
                    makeDirty(this, ROTATION_INDEX);
                }
            }
        },

        /**
         * Gets or sets the aligned axis in world space. The aligned axis is the unit vector that the billboard up vector points towards.
         * The default is the zero vector, which means the billboard is aligned to the screen up vector.
         * @memberof Billboard.prototype
         * @type {Cartesian3}
         * @example
         * // Example 1.
         * // Have the billboard up vector point north
         * billboard.alignedAxis = Cesium.Cartesian3.UNIT_Z;
         *
         * @example
         * // Example 2.
         * // Have the billboard point east.
         * billboard.alignedAxis = Cesium.Cartesian3.UNIT_Z;
         * billboard.rotation = -Cesium.Math.PI_OVER_TWO;
         *
         * @example
         * // Example 3.
         * // Reset the aligned axis
         * billboard.alignedAxis = Cesium.Cartesian3.ZERO;
         */
        alignedAxis : {
            get : function() {
                return this._alignedAxis;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var alignedAxis = this._alignedAxis;
                if (!Cartesian3.equals(alignedAxis, value)) {
                    Cartesian3.clone(value, alignedAxis);
                    makeDirty(this, ALIGNED_AXIS_INDEX);
                }
            }
        },

        /**
         * Gets or sets a width for the billboard. If undefined, the image width will be used.
         * @memberof Billboard.prototype
         * @type {Number}
         */
        width : {
            get : function() {
                return defaultValue(this._width, this._imageWidth);
            },
            set : function(value) {
                if (this._width !== value) {
                    this._width = value;
                    makeDirty(this, IMAGE_INDEX_INDEX);
                }
            }
        },

        /**
         * Gets or sets a height for the billboard. If undefined, the image height will be used.
         * @memberof Billboard.prototype
         * @type {Number}
         */
        height : {
            get : function() {
                return defaultValue(this._height, this._imageHeight);
            },
            set : function(value) {
                if (this._height !== value) {
                    this._height = value;
                    makeDirty(this, IMAGE_INDEX_INDEX);
                }
            }
        },

        /**
         * Gets or sets if the billboard size is in meters or pixels. <code>true</code> to size the billboard in meters;
         * otherwise, the size is in pixels.
         * @memberof Billboard.prototype
         * @type {Boolean}
         * @default false
         */
        sizeInMeters : {
            get : function() {
                return this._sizeInMeters;
            },
            set : function(value) {
                if (this._sizeInMeters !== value) {
                    this._sizeInMeters = value;
                    makeDirty(this, COLOR_INDEX);
                }
            }
        },

        /**
         * Gets or sets the condition specifying at what distance from the camera that this billboard will be displayed.
         * @memberof Billboard.prototype
         * @type {DistanceDisplayCondition}
         * @default undefined
         */
        distanceDisplayCondition : {
            get : function() {
                return this._distanceDisplayCondition;
            },
            set : function(value) {
                if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
                                        if (defined(value) && value.far <= value.near) {
                        throw new DeveloperError('far distance must be greater than near distance.');
                    }
                                        this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
                    makeDirty(this, DISTANCE_DISPLAY_CONDITION);
                }
            }
        },

        /**
         * Gets or sets the user-defined object returned when the billboard is picked.
         * @memberof Billboard.prototype
         * @type {Object}
         */
        id : {
            get : function() {
                return this._id;
            },
            set : function(value) {
                this._id = value;
                if (defined(this._pickId)) {
                    this._pickId.object.id = value;
                }
            }
        },

        /**
         * The primitive to return when picking this billboard.
         * @memberof Billboard.prototype
         * @private
         */
        pickPrimitive : {
            get : function() {
                return this._pickPrimitive;
            },
            set : function(value) {
                this._pickPrimitive = value;
                if (defined(this._pickId)) {
                    this._pickId.object.primitive = value;
                }
            }
        },

        /**
         * <p>
         * Gets or sets the image to be used for this billboard.  If a texture has already been created for the
         * given image, the existing texture is used.
         * </p>
         * <p>
         * This property can be set to a loaded Image, a URL which will be loaded as an Image automatically,
         * a canvas, or another billboard's image property (from the same billboard collection).
         * </p>
         *
         * @memberof Billboard.prototype
         * @type {String}
         * @example
         * // load an image from a URL
         * b.image = 'some/image/url.png';
         *
         * // assuming b1 and b2 are billboards in the same billboard collection,
         * // use the same image for both billboards.
         * b2.image = b1.image;
         */
        image : {
            get : function() {
                return this._imageId;
            },
            set : function(value) {
                if (!defined(value)) {
                    this._imageIndex = -1;
                    this._imageSubRegion = undefined;
                    this._imageId = undefined;
                    this._image = undefined;
                    this._imageIndexPromise = undefined;
                    makeDirty(this, IMAGE_INDEX_INDEX);
                } else if (typeof value === 'string') {
                    this.setImage(value, value);
                } else if (defined(value.src)) {
                    this.setImage(value.src, value);
                } else {
                    this.setImage(createGuid(), value);
                }
            }
        },

        /**
         * When <code>true</code>, this billboard is ready to render, i.e., the image
         * has been downloaded and the WebGL resources are created.
         *
         * @memberof Billboard.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        ready : {
            get : function() {
                return this._imageIndex !== -1;
            }
        },

        /**
         * Keeps track of the position of the billboard based on the height reference.
         * @memberof Billboard.prototype
         * @type {Cartesian3}
         * @private
         */
        _clampedPosition : {
            get : function() {
                return this._actualClampedPosition;
            },
            set : function(value) {
                this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition);
                makeDirty(this, POSITION_INDEX);
            }
        },

        /**
         * Determines whether or not this billboard will be shown or hidden because it was clustered.
         * @memberof Billboard.prototype
         * @type {Boolean}
         * @private
         */
        clusterShow : {
            get : function() {
                return this._clusterShow;
            },
            set : function(value) {
                if (this._clusterShow !== value) {
                    this._clusterShow = value;
                    makeDirty(this, SHOW_INDEX);
                }
            }
        }
    });

    Billboard.prototype.getPickId = function(context) {
        if (!defined(this._pickId)) {
            this._pickId = context.createPickId({
                primitive : this._pickPrimitive,
                collection : this._collection,
                id : this._id
            });
        }

        return this._pickId;
    };

    Billboard.prototype._updateClamping = function() {
        Billboard._updateClamping(this._billboardCollection, this);
    };

    var scratchCartographic = new Cartographic();
    var scratchPosition = new Cartesian3();

    Billboard._updateClamping = function(collection, owner) {
        var scene = collection._scene;
        if (!defined(scene)) {
                        if (owner._heightReference !== HeightReference.NONE) {
                throw new DeveloperError('Height reference is not supported without a scene.');
            }
                        return;
        }

        var globe = scene.globe;
        var ellipsoid = globe.ellipsoid;
        var surface = globe._surface;

        var mode = scene.frameState.mode;

        var modeChanged = mode !== owner._mode;
        owner._mode = mode;

        if ((owner._heightReference === HeightReference.NONE || modeChanged) && defined(owner._removeCallbackFunc)) {
            owner._removeCallbackFunc();
            owner._removeCallbackFunc = undefined;
            owner._clampedPosition = undefined;
        }

        if (owner._heightReference === HeightReference.NONE || !defined(owner._position)) {
            return;
        }

        var position = ellipsoid.cartesianToCartographic(owner._position);
        if (!defined(position)) {
            return;
        }

        if (defined(owner._removeCallbackFunc)) {
            owner._removeCallbackFunc();
        }

        function updateFunction(clampedPosition) {
            if (owner._heightReference === HeightReference.RELATIVE_TO_GROUND) {
                if (owner._mode === SceneMode.SCENE3D) {
                    var clampedCart = ellipsoid.cartesianToCartographic(clampedPosition, scratchCartographic);
                    clampedCart.height += position.height;
                    ellipsoid.cartographicToCartesian(clampedCart, clampedPosition);
                } else {
                    clampedPosition.x += position.height;
                }
            }
            owner._clampedPosition = Cartesian3.clone(clampedPosition, owner._clampedPosition);
        }
        owner._removeCallbackFunc = surface.updateHeight(position, updateFunction);

        Cartographic.clone(position, scratchCartographic);
        var height = globe.getHeight(position);
        if (defined(height)) {
            scratchCartographic.height = height;
        }

        ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition);

        updateFunction(scratchPosition);
    };

    Billboard.prototype._loadImage = function() {
        var atlas = this._billboardCollection._textureAtlas;

        var imageId = this._imageId;
        var image = this._image;
        var imageSubRegion = this._imageSubRegion;
        var imageIndexPromise;

        if (defined(image)) {
            imageIndexPromise = atlas.addImage(imageId, image);
        }
        if (defined(imageSubRegion)) {
            imageIndexPromise = atlas.addSubRegion(imageId, imageSubRegion);
        }

        this._imageIndexPromise = imageIndexPromise;

        if (!defined(imageIndexPromise)) {
            return;
        }

        var that = this;
        imageIndexPromise.then(function(index) {
            if (that._imageId !== imageId || that._image !== image || !BoundingRectangle.equals(that._imageSubRegion, imageSubRegion)) {
                // another load occurred before this one finished, ignore the index
                return;
            }

            // fill in imageWidth and imageHeight
            var textureCoordinates = atlas.textureCoordinates[index];
            that._imageWidth = atlas.texture.width * textureCoordinates.width;
            that._imageHeight = atlas.texture.height * textureCoordinates.height;

            that._imageIndex = index;
            that._ready = true;
            that._image = undefined;
            that._imageIndexPromise = undefined;
            makeDirty(that, IMAGE_INDEX_INDEX);
        }).otherwise(function(error) {
            console.error('Error loading image for billboard: ' + error);
            that._imageIndexPromise = undefined;
        });
    };

    /**
     * <p>
     * Sets the image to be used for this billboard.  If a texture has already been created for the
     * given id, the existing texture is used.
     * </p>
     * <p>
     * This function is useful for dynamically creating textures that are shared across many billboards.
     * Only the first billboard will actually call the function and create the texture, while subsequent
     * billboards created with the same id will simply re-use the existing texture.
     * </p>
     * <p>
     * To load an image from a URL, setting the {@link Billboard#image} property is more convenient.
     * </p>
     *
     * @param {String} id The id of the image.  This can be any string that uniquely identifies the image.
     * @param {Image|Canvas|String|Billboard~CreateImageCallback} image The image to load.  This parameter
     *        can either be a loaded Image or Canvas, a URL which will be loaded as an Image automatically,
     *        or a function which will be called to create the image if it hasn't been loaded already.
     * @example
     * // create a billboard image dynamically
     * function drawImage(id) {
     *   // create and draw an image using a canvas
     *   var canvas = document.createElement('canvas');
     *   var context2D = canvas.getContext('2d');
     *   // ... draw image
     *   return canvas;
     * }
     * // drawImage will be called to create the texture
     * b.setImage('myImage', drawImage);
     *
     * // subsequent billboards created in the same collection using the same id will use the existing
     * // texture, without the need to create the canvas or draw the image
     * b2.setImage('myImage', drawImage);
     */
    Billboard.prototype.setImage = function(id, image) {
                if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        if (!defined(image)) {
            throw new DeveloperError('image is required.');
        }
        
        if (this._imageId === id) {
            return;
        }

        this._imageIndex = -1;
        this._imageSubRegion = undefined;
        this._imageId = id;
        this._image = image;

        if (defined(this._billboardCollection._textureAtlas)) {
            this._loadImage();
        }
    };

    /**
     * Uses a sub-region of the image with the given id as the image for this billboard,
     * measured in pixels from the bottom-left.
     *
     * @param {String} id The id of the image to use.
     * @param {BoundingRectangle} subRegion The sub-region of the image.
     *
     * @exception {RuntimeError} image with id must be in the atlas
     */
    Billboard.prototype.setImageSubRegion = function(id, subRegion) {
                if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        if (!defined(subRegion)) {
            throw new DeveloperError('subRegion is required.');
        }
        
        if (this._imageId === id && BoundingRectangle.equals(this._imageSubRegion, subRegion)) {
            return;
        }

        this._imageIndex = -1;
        this._imageId = id;
        this._imageSubRegion = BoundingRectangle.clone(subRegion);

        if (defined(this._billboardCollection._textureAtlas)) {
            this._loadImage();
        }
    };

    Billboard.prototype._setTranslate = function(value) {
                if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }
        
        var translate = this._translate;
        if (!Cartesian2.equals(translate, value)) {
            Cartesian2.clone(value, translate);
            makeDirty(this, PIXEL_OFFSET_INDEX);
        }
    };

    Billboard.prototype._getActualPosition = function() {
        return defined(this._clampedPosition) ? this._clampedPosition : this._actualPosition;
    };

    Billboard.prototype._setActualPosition = function(value) {
        if (!(defined(this._clampedPosition))) {
            Cartesian3.clone(value, this._actualPosition);
        }
        makeDirty(this, POSITION_INDEX);
    };

    var tempCartesian3 = new Cartesian4();
    Billboard._computeActualPosition = function(billboard, position, frameState, modelMatrix) {
        if (defined(billboard._clampedPosition)) {
            if (frameState.mode !== billboard._mode) {
                billboard._updateClamping();
            }
            return billboard._clampedPosition;
        } else if (frameState.mode === SceneMode.SCENE3D) {
            return position;
        }

        Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3);
        return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3);
    };

    var scratchCartesian3 = new Cartesian3();

    // This function is basically a stripped-down JavaScript version of BillboardCollectionVS.glsl
    Billboard._computeScreenSpacePosition = function(modelMatrix, position, eyeOffset, pixelOffset, scene, result) {
        // Model to world coordinates
        var positionWorld = Matrix4.multiplyByPoint(modelMatrix, position, scratchCartesian3);

        // World to window coordinates
        var positionWC = SceneTransforms.wgs84WithEyeOffsetToWindowCoordinates(scene, positionWorld, eyeOffset, result);
        if (!defined(positionWC)) {
            return undefined;
        }

        // Apply pixel offset
        Cartesian2.add(positionWC, pixelOffset, positionWC);

        return positionWC;
    };

    var scratchPixelOffset = new Cartesian2(0.0, 0.0);

    /**
     * Computes the screen-space position of the billboard's origin, taking into account eye and pixel offsets.
     * The screen space origin is the top, left corner of the canvas; <code>x</code> increases from
     * left to right, and <code>y</code> increases from top to bottom.
     *
     * @param {Scene} scene The scene.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The screen-space position of the billboard.
     *
     * @exception {DeveloperError} Billboard must be in a collection.
     *
     * @example
     * console.log(b.computeScreenSpacePosition(scene).toString());
     *
     * @see Billboard#eyeOffset
     * @see Billboard#pixelOffset
     */
    Billboard.prototype.computeScreenSpacePosition = function(scene, result) {
        var billboardCollection = this._billboardCollection;
        if (!defined(result)) {
            result = new Cartesian2();
        }

                if (!defined(billboardCollection)) {
            throw new DeveloperError('Billboard must be in a collection.  Was it removed?');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        // pixel offset for screenspace computation is the pixelOffset + screenspace translate
        Cartesian2.clone(this._pixelOffset, scratchPixelOffset);
        Cartesian2.add(scratchPixelOffset, this._translate, scratchPixelOffset);

        var modelMatrix = billboardCollection.modelMatrix;
        var actualPosition = this._getActualPosition();

        var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, actualPosition,
                this._eyeOffset, scratchPixelOffset, scene, result);
        return windowCoordinates;
    };

    /**
     * Gets a billboard's screen space bounding box centered around screenSpacePosition.
     * @param {Billboard} billboard The billboard to get the screen space bounding box for.
     * @param {Cartesian2} screenSpacePosition The screen space center of the label.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The screen space bounding box.
     *
     * @private
     */
    Billboard.getScreenSpaceBoundingBox = function(billboard, screenSpacePosition, result) {
        var width = billboard.width;
        var height = billboard.height;

        var scale = billboard.scale;
        width *= scale;
        height *= scale;

        var x = screenSpacePosition.x;
        if (billboard.horizontalOrigin === HorizontalOrigin.RIGHT) {
            x -= width;
        } else if (billboard.horizontalOrigin === HorizontalOrigin.CENTER) {
            x -= width * 0.5;
        }

        var y = screenSpacePosition.y;
        if (billboard.verticalOrigin === VerticalOrigin.BOTTOM || billboard.verticalOrigin === VerticalOrigin.BASELINE) {
            y -= height;
        } else if (billboard.verticalOrigin === VerticalOrigin.CENTER) {
            y -= height * 0.5;
        }

        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        result.x = x;
        result.y = y;
        result.width = width;
        result.height = height;

        return result;
    };

    /**
     * Determines if this billboard equals another billboard.  Billboards are equal if all their properties
     * are equal.  Billboards in different collections can be equal.
     *
     * @param {Billboard} other The billboard to compare for equality.
     * @returns {Boolean} <code>true</code> if the billboards are equal; otherwise, <code>false</code>.
     */
    Billboard.prototype.equals = function(other) {
        return this === other ||
               defined(other) &&
               this._id === other._id &&
               Cartesian3.equals(this._position, other._position) &&
               this._imageId === other._imageId &&
               this._show === other._show &&
               this._scale === other._scale &&
               this._verticalOrigin === other._verticalOrigin &&
               this._horizontalOrigin === other._horizontalOrigin &&
               this._heightReference === other._heightReference &&
               BoundingRectangle.equals(this._imageSubRegion, other._imageSubRegion) &&
               Color.equals(this._color, other._color) &&
               Cartesian2.equals(this._pixelOffset, other._pixelOffset) &&
               Cartesian2.equals(this._translate, other._translate) &&
               Cartesian3.equals(this._eyeOffset, other._eyeOffset) &&
               NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
               NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) &&
               NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance) &&
               DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition);
    };

    Billboard.prototype._destroy = function() {
        if (defined(this._customData)) {
            this._billboardCollection._scene.globe._surface.removeTileCustomData(this._customData);
            this._customData = undefined;
        }

        if (defined(this._removeCallbackFunc)) {
            this._removeCallbackFunc();
            this._removeCallbackFunc = undefined;
        }

        this.image = undefined;
        this._pickId = this._pickId && this._pickId.destroy();
        this._billboardCollection = undefined;
    };

    /**
     * A function that creates an image.
     * @callback Billboard~CreateImageCallback
     * @param {String} id The identifier of the image to load.
     * @returns {Image|Canvas|Promise<Image|Canvas>} The image, or a promise that will resolve to an image.
     */

    return Billboard;
});

/*global define*/
define('Renderer/VertexArrayFacade',[
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Math',
        './Buffer',
        './BufferUsage',
        './VertexArray'
    ], function(
        ComponentDatatype,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        CesiumMath,
        Buffer,
        BufferUsage,
        VertexArray) {
    'use strict';

    /**
     * @private
     */
    function VertexArrayFacade(context, attributes, sizeInVertices, instanced) {
                if (!context) {
            throw new DeveloperError('context is required.');
        }
        if (!attributes || (attributes.length === 0)) {
            throw new DeveloperError('At least one attribute is required.');
        }
        
        var attrs = VertexArrayFacade._verifyAttributes(attributes);
        sizeInVertices = defaultValue(sizeInVertices, 0);
        var precreatedAttributes = [];
        var attributesByUsage = {};
        var attributesForUsage;
        var usage;

        // Bucket the attributes by usage.
        var length = attrs.length;
        for (var i = 0; i < length; ++i) {
            var attribute = attrs[i];

            // If the attribute already has a vertex buffer, we do not need
            // to manage a vertex buffer or typed array for it.
            if (attribute.vertexBuffer) {
                precreatedAttributes.push(attribute);
                continue;
            }

            usage = attribute.usage;
            attributesForUsage = attributesByUsage[usage];
            if (!defined(attributesForUsage)) {
                attributesForUsage = attributesByUsage[usage] = [];
            }

            attributesForUsage.push(attribute);
        }

        // A function to sort attributes by the size of their components.  From left to right, a vertex
        // stores floats, shorts, and then bytes.
        function compare(left, right) {
            return ComponentDatatype.getSizeInBytes(right.componentDatatype) - ComponentDatatype.getSizeInBytes(left.componentDatatype);
        }

        this._allBuffers = [];

        for (usage in attributesByUsage) {
            if (attributesByUsage.hasOwnProperty(usage)) {
                attributesForUsage = attributesByUsage[usage];

                attributesForUsage.sort(compare);
                var vertexSizeInBytes = VertexArrayFacade._vertexSizeInBytes(attributesForUsage);

                var bufferUsage = attributesForUsage[0].usage;

                var buffer = {
                    vertexSizeInBytes : vertexSizeInBytes,
                    vertexBuffer : undefined,
                    usage : bufferUsage,
                    needsCommit : false,
                    arrayBuffer : undefined,
                    arrayViews : VertexArrayFacade._createArrayViews(attributesForUsage, vertexSizeInBytes)
                };

                this._allBuffers.push(buffer);
            }
        }

        this._size = 0;
        this._instanced = defaultValue(instanced, false);

        this._precreated = precreatedAttributes;
        this._context = context;

        this.writers = undefined;
        this.va = undefined;

        this.resize(sizeInVertices);
    }
    VertexArrayFacade._verifyAttributes = function(attributes) {
        var attrs = [];

        for ( var i = 0; i < attributes.length; ++i) {
            var attribute = attributes[i];

            var attr = {
                index : defaultValue(attribute.index, i),
                enabled : defaultValue(attribute.enabled, true),
                componentsPerAttribute : attribute.componentsPerAttribute,
                componentDatatype : defaultValue(attribute.componentDatatype, ComponentDatatype.FLOAT),
                normalize : defaultValue(attribute.normalize, false),

                // There will be either a vertexBuffer or an [optional] usage.
                vertexBuffer : attribute.vertexBuffer,
                usage : defaultValue(attribute.usage, BufferUsage.STATIC_DRAW)
            };
            attrs.push(attr);

                        if ((attr.componentsPerAttribute !== 1) && (attr.componentsPerAttribute !== 2) && (attr.componentsPerAttribute !== 3) && (attr.componentsPerAttribute !== 4)) {
                throw new DeveloperError('attribute.componentsPerAttribute must be in the range [1, 4].');
            }

            var datatype = attr.componentDatatype;
            if (!ComponentDatatype.validate(datatype)) {
                throw new DeveloperError('Attribute must have a valid componentDatatype or not specify it.');
            }

            if (!BufferUsage.validate(attr.usage)) {
                throw new DeveloperError('Attribute must have a valid usage or not specify it.');
            }
                    }

        // Verify all attribute names are unique.
        var uniqueIndices = new Array(attrs.length);
        for ( var j = 0; j < attrs.length; ++j) {
            var currentAttr = attrs[j];
            var index = currentAttr.index;
                        if (uniqueIndices[index]) {
                throw new DeveloperError('Index ' + index + ' is used by more than one attribute.');
            }
                        uniqueIndices[index] = true;
        }

        return attrs;
    };

    VertexArrayFacade._vertexSizeInBytes = function(attributes) {
        var sizeInBytes = 0;

        var length = attributes.length;
        for ( var i = 0; i < length; ++i) {
            var attribute = attributes[i];
            sizeInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(attribute.componentDatatype));
        }

        var maxComponentSizeInBytes = (length > 0) ? ComponentDatatype.getSizeInBytes(attributes[0].componentDatatype) : 0; // Sorted by size
        var remainder = (maxComponentSizeInBytes > 0) ? (sizeInBytes % maxComponentSizeInBytes) : 0;
        var padding = (remainder === 0) ? 0 : (maxComponentSizeInBytes - remainder);
        sizeInBytes += padding;

        return sizeInBytes;
    };

    VertexArrayFacade._createArrayViews = function(attributes, vertexSizeInBytes) {
        var views = [];
        var offsetInBytes = 0;

        var length = attributes.length;
        for ( var i = 0; i < length; ++i) {
            var attribute = attributes[i];
            var componentDatatype = attribute.componentDatatype;

            views.push({
                index : attribute.index,
                enabled : attribute.enabled,
                componentsPerAttribute : attribute.componentsPerAttribute,
                componentDatatype : componentDatatype,
                normalize : attribute.normalize,

                offsetInBytes : offsetInBytes,
                vertexSizeInComponentType : vertexSizeInBytes / ComponentDatatype.getSizeInBytes(componentDatatype),

                view : undefined
            });

            offsetInBytes += (attribute.componentsPerAttribute * ComponentDatatype.getSizeInBytes(componentDatatype));
        }

        return views;
    };

    /**
     * Invalidates writers.  Can't render again until commit is called.
     */
    VertexArrayFacade.prototype.resize = function(sizeInVertices) {
        this._size = sizeInVertices;

        var allBuffers = this._allBuffers;
        this.writers = [];

        for (var i = 0, len = allBuffers.length; i < len; ++i) {
            var buffer = allBuffers[i];

            VertexArrayFacade._resize(buffer, this._size);

            // Reserving invalidates the writers, so if client's cache them, they need to invalidate their cache.
            VertexArrayFacade._appendWriters(this.writers, buffer);
        }

        // VAs are recreated next time commit is called.
        destroyVA(this);
    };

    VertexArrayFacade._resize = function(buffer, size) {
        if (buffer.vertexSizeInBytes > 0) {
            // Create larger array buffer
            var arrayBuffer = new ArrayBuffer(size * buffer.vertexSizeInBytes);

            // Copy contents from previous array buffer
            if (defined(buffer.arrayBuffer)) {
                var destView = new Uint8Array(arrayBuffer);
                var sourceView = new Uint8Array(buffer.arrayBuffer);
                var sourceLength = sourceView.length;
                for ( var j = 0; j < sourceLength; ++j) {
                    destView[j] = sourceView[j];
                }
            }

            // Create typed views into the new array buffer
            var views = buffer.arrayViews;
            var length = views.length;
            for ( var i = 0; i < length; ++i) {
                var view = views[i];
                view.view = ComponentDatatype.createArrayBufferView(view.componentDatatype, arrayBuffer, view.offsetInBytes);
            }

            buffer.arrayBuffer = arrayBuffer;
        }
    };

    var createWriters = [
    // 1 component per attribute
    function(buffer, view, vertexSizeInComponentType) {
        return function(index, attribute) {
            view[index * vertexSizeInComponentType] = attribute;
            buffer.needsCommit = true;
        };
    },

    // 2 component per attribute
    function(buffer, view, vertexSizeInComponentType) {
        return function(index, component0, component1) {
            var i = index * vertexSizeInComponentType;
            view[i] = component0;
            view[i + 1] = component1;
            buffer.needsCommit = true;
        };
    },

    // 3 component per attribute
    function(buffer, view, vertexSizeInComponentType) {
        return function(index, component0, component1, component2) {
            var i = index * vertexSizeInComponentType;
            view[i] = component0;
            view[i + 1] = component1;
            view[i + 2] = component2;
            buffer.needsCommit = true;
        };
    },

    // 4 component per attribute
    function(buffer, view, vertexSizeInComponentType) {
        return function(index, component0, component1, component2, component3) {
            var i = index * vertexSizeInComponentType;
            view[i] = component0;
            view[i + 1] = component1;
            view[i + 2] = component2;
            view[i + 3] = component3;
            buffer.needsCommit = true;
        };
    }];

    VertexArrayFacade._appendWriters = function(writers, buffer) {
        var arrayViews = buffer.arrayViews;
        var length = arrayViews.length;
        for ( var i = 0; i < length; ++i) {
            var arrayView = arrayViews[i];
            writers[arrayView.index] = createWriters[arrayView.componentsPerAttribute - 1](buffer, arrayView.view, arrayView.vertexSizeInComponentType);
        }
    };

    VertexArrayFacade.prototype.commit = function(indexBuffer) {
        var recreateVA = false;

        var allBuffers = this._allBuffers;
        var buffer;
        var i;
        var length;

        for (i = 0, length = allBuffers.length; i < length; ++i) {
            buffer = allBuffers[i];
            recreateVA = commit(this, buffer) || recreateVA;
        }

        ///////////////////////////////////////////////////////////////////////

        if (recreateVA || !defined(this.va)) {
            destroyVA(this);
            var va = this.va = [];

            var numberOfVertexArrays = defined(indexBuffer) ? Math.ceil(this._size / (CesiumMath.SIXTY_FOUR_KILOBYTES - 1)) : 1;
            for ( var k = 0; k < numberOfVertexArrays; ++k) {
                var attributes = [];
                for (i = 0, length = allBuffers.length; i < length; ++i) {
                    buffer = allBuffers[i];
                    var offset = k * (buffer.vertexSizeInBytes * (CesiumMath.SIXTY_FOUR_KILOBYTES - 1));
                    VertexArrayFacade._appendAttributes(attributes, buffer, offset, this._instanced);
                }

                attributes = attributes.concat(this._precreated);

                va.push({
                    va : new VertexArray({
                        context : this._context,
                        attributes : attributes,
                        indexBuffer : indexBuffer
                    }),
                    indicesCount : 1.5 * ((k !== (numberOfVertexArrays - 1)) ? (CesiumMath.SIXTY_FOUR_KILOBYTES - 1) : (this._size % (CesiumMath.SIXTY_FOUR_KILOBYTES - 1)))
                // TODO: not hardcode 1.5, this assumes 6 indices per 4 vertices (as for Billboard quads).
                });
            }
        }
    };

    function commit(vertexArrayFacade, buffer) {
        if (buffer.needsCommit && (buffer.vertexSizeInBytes > 0)) {
            buffer.needsCommit = false;

            var vertexBuffer = buffer.vertexBuffer;
            var vertexBufferSizeInBytes = vertexArrayFacade._size * buffer.vertexSizeInBytes;
            var vertexBufferDefined = defined(vertexBuffer);
            if (!vertexBufferDefined || (vertexBuffer.sizeInBytes < vertexBufferSizeInBytes)) {
                if (vertexBufferDefined) {
                    vertexBuffer.destroy();
                }
                buffer.vertexBuffer = Buffer.createVertexBuffer({
                    context : vertexArrayFacade._context,
                    typedArray : buffer.arrayBuffer,
                    usage : buffer.usage
                });
                buffer.vertexBuffer.vertexArrayDestroyable = false;

                return true; // Created new vertex buffer
            }

            buffer.vertexBuffer.copyFromArrayView(buffer.arrayBuffer);
        }

        return false; // Did not create new vertex buffer
    }

    VertexArrayFacade._appendAttributes = function(attributes, buffer, vertexBufferOffset, instanced) {
        var arrayViews = buffer.arrayViews;
        var length = arrayViews.length;
        for ( var i = 0; i < length; ++i) {
            var view = arrayViews[i];

            attributes.push({
                index : view.index,
                enabled : view.enabled,
                componentsPerAttribute : view.componentsPerAttribute,
                componentDatatype : view.componentDatatype,
                normalize : view.normalize,
                vertexBuffer : buffer.vertexBuffer,
                offsetInBytes : vertexBufferOffset + view.offsetInBytes,
                strideInBytes : buffer.vertexSizeInBytes,
                instanceDivisor : instanced ? 1 : 0
            });
        }
    };

    VertexArrayFacade.prototype.subCommit = function(offsetInVertices, lengthInVertices) {
                if (offsetInVertices < 0 || offsetInVertices >= this._size) {
            throw new DeveloperError('offsetInVertices must be greater than or equal to zero and less than the vertex array size.');
        }
        if (offsetInVertices + lengthInVertices > this._size) {
            throw new DeveloperError('offsetInVertices + lengthInVertices cannot exceed the vertex array size.');
        }
        
        var allBuffers = this._allBuffers;
        for (var i = 0, len = allBuffers.length; i < len; ++i) {
            subCommit(allBuffers[i], offsetInVertices, lengthInVertices);
        }
    };

    function subCommit(buffer, offsetInVertices, lengthInVertices) {
        if (buffer.needsCommit && (buffer.vertexSizeInBytes > 0)) {
            var byteOffset = buffer.vertexSizeInBytes * offsetInVertices;
            var byteLength = buffer.vertexSizeInBytes * lengthInVertices;

            // PERFORMANCE_IDEA: If we want to get really crazy, we could consider updating
            // individual attributes instead of the entire (sub-)vertex.
            //
            // PERFORMANCE_IDEA: Does creating the typed view add too much GC overhead?
            buffer.vertexBuffer.copyFromArrayView(new Uint8Array(buffer.arrayBuffer, byteOffset, byteLength), byteOffset);
        }
    }

    VertexArrayFacade.prototype.endSubCommits = function() {
        var allBuffers = this._allBuffers;

        for (var i = 0, len = allBuffers.length; i < len; ++i) {
            allBuffers[i].needsCommit = false;
        }
    };

    function destroyVA(vertexArrayFacade) {
        var va = vertexArrayFacade.va;
        if (!defined(va)) {
            return;
        }

        var length = va.length;
        for (var i = 0; i < length; ++i) {
            va[i].va.destroy();
        }

        vertexArrayFacade.va = undefined;
    }

    VertexArrayFacade.prototype.isDestroyed = function() {
        return false;
    };

    VertexArrayFacade.prototype.destroy = function() {
        var allBuffers = this._allBuffers;
        for (var i = 0, len = allBuffers.length; i < len; ++i) {
            var buffer = allBuffers[i];
            buffer.vertexBuffer = buffer.vertexBuffer && buffer.vertexBuffer.destroy();
        }

        destroyVA(this);

        return destroyObject(this);
    };

    return VertexArrayFacade;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/BillboardCollectionFS',[],function() {
    'use strict';
    return "uniform sampler2D u_atlas;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#else\n\
varying vec4 v_color;\n\
#endif\n\
\n\
void main()\n\
{\n\
#ifdef RENDER_FOR_PICK\n\
    vec4 vertexColor = vec4(1.0, 1.0, 1.0, 1.0);\n\
#else\n\
    vec4 vertexColor = v_color;\n\
#endif\n\
\n\
    vec4 color = texture2D(u_atlas, v_textureCoordinates) * vertexColor;\n\
\n\
// Fully transparent parts of the billboard are not pickable.\n\
#if defined(RENDER_FOR_PICK) || (!defined(OPAQUE) && !defined(TRANSLUCENT))\n\
    if (color.a < 0.005)   // matches 0/255 and 1/255\n\
    {\n\
        discard;\n\
    }\n\
#else\n\
// The billboard is rendered twice. The opaque pass discards translucent fragments\n\
// and the translucent pass discards opaque fragments.\n\
#ifdef OPAQUE\n\
    if (color.a < 0.995)   // matches < 254/255\n\
    {\n\
        discard;\n\
    }\n\
#else\n\
    if (color.a >= 0.995)  // matches 254/255 and 255/255\n\
    {\n\
        discard;\n\
    }\n\
#endif\n\
#endif\n\
\n\
#ifdef RENDER_FOR_PICK\n\
    gl_FragColor = v_pickColor;\n\
#else\n\
    gl_FragColor = color;\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/BillboardCollectionVS',[],function() {
    'use strict';
    return "#ifdef INSTANCED\n\
attribute vec2 direction;\n\
#endif\n\
attribute vec4 positionHighAndScale;\n\
attribute vec4 positionLowAndRotation;\n\
attribute vec4 compressedAttribute0;        // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates (texture offset)\n\
attribute vec4 compressedAttribute1;        // aligned axis, translucency by distance, image width\n\
attribute vec4 compressedAttribute2;        // image height, color, pick color, size in meters, valid aligned axis, 13 bits free\n\
attribute vec4 eyeOffset;                   // eye offset in meters, 4 bytes free (texture range)\n\
attribute vec4 scaleByDistance;             // near, nearScale, far, farScale\n\
attribute vec4 pixelOffsetScaleByDistance;  // near, nearScale, far, farScale\n\
attribute vec2 distanceDisplayCondition;    // near, far\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#else\n\
varying vec4 v_color;\n\
#endif\n\
\n\
const float UPPER_BOUND = 32768.0;\n\
\n\
const float SHIFT_LEFT16 = 65536.0;\n\
const float SHIFT_LEFT8 = 256.0;\n\
const float SHIFT_LEFT7 = 128.0;\n\
const float SHIFT_LEFT5 = 32.0;\n\
const float SHIFT_LEFT3 = 8.0;\n\
const float SHIFT_LEFT2 = 4.0;\n\
const float SHIFT_LEFT1 = 2.0;\n\
\n\
const float SHIFT_RIGHT8 = 1.0 / 256.0;\n\
const float SHIFT_RIGHT7 = 1.0 / 128.0;\n\
const float SHIFT_RIGHT5 = 1.0 / 32.0;\n\
const float SHIFT_RIGHT3 = 1.0 / 8.0;\n\
const float SHIFT_RIGHT2 = 1.0 / 4.0;\n\
const float SHIFT_RIGHT1 = 1.0 / 2.0;\n\
\n\
vec4 computePositionWindowCoordinates(vec4 positionEC, vec2 imageSize, float scale, vec2 direction, vec2 origin, vec2 translate, vec2 pixelOffset, vec3 alignedAxis, bool validAlignedAxis, float rotation, bool sizeInMeters)\n\
{\n\
    // Note the halfSize cannot be computed in JavaScript because it is sent via\n\
    // compressed vertex attributes that coerce it to an integer.\n\
    vec2 halfSize = imageSize * scale * czm_resolutionScale * 0.5;\n\
    halfSize *= ((direction * 2.0) - 1.0);\n\
\n\
    vec2 originTranslate = origin * abs(halfSize);\n\
\n\
#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\
    if (validAlignedAxis || rotation != 0.0)\n\
    {\n\
        float angle = rotation;\n\
        if (validAlignedAxis)\n\
        {\n\
            vec3 pos = positionEC.xyz + czm_encodedCameraPositionMCHigh + czm_encodedCameraPositionMCLow;\n\
            vec3 normal = normalize(cross(alignedAxis, pos));\n\
            vec4 tangent = vec4(normalize(cross(pos, normal)), 0.0);\n\
            tangent = czm_modelViewProjection * tangent;\n\
            angle += sign(-tangent.x) * acos(tangent.y / length(tangent.xy));\n\
        }\n\
\n\
        float cosTheta = cos(angle);\n\
        float sinTheta = sin(angle);\n\
        mat2 rotationMatrix = mat2(cosTheta, sinTheta, -sinTheta, cosTheta);\n\
        halfSize = rotationMatrix * halfSize;\n\
    }\n\
#endif\n\
\n\
    if (sizeInMeters)\n\
    {\n\
        positionEC.xy += halfSize;\n\
    }\n\
\n\
    vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
\n\
    if (sizeInMeters)\n\
    {\n\
        originTranslate += originTranslate / czm_metersPerPixel(positionEC);\n\
    }\n\
\n\
    positionWC.xy += originTranslate;\n\
    if (!sizeInMeters)\n\
    {\n\
        positionWC.xy += halfSize;\n\
    }\n\
\n\
    positionWC.xy += translate;\n\
    positionWC.xy += (pixelOffset * czm_resolutionScale);\n\
\n\
    return positionWC;\n\
}\n\
\n\
void main()\n\
{\n\
    // Modifying this shader may also require modifications to Billboard._computeScreenSpacePosition\n\
\n\
    // unpack attributes\n\
    vec3 positionHigh = positionHighAndScale.xyz;\n\
    vec3 positionLow = positionLowAndRotation.xyz;\n\
    float scale = positionHighAndScale.w;\n\
\n\
#if defined(ROTATION) || defined(ALIGNED_AXIS)\n\
    float rotation = positionLowAndRotation.w;\n\
#else\n\
    float rotation = 0.0;\n\
#endif\n\
\n\
    float compressed = compressedAttribute0.x;\n\
\n\
    vec2 pixelOffset;\n\
    pixelOffset.x = floor(compressed * SHIFT_RIGHT7);\n\
    compressed -= pixelOffset.x * SHIFT_LEFT7;\n\
    pixelOffset.x -= UPPER_BOUND;\n\
\n\
    vec2 origin;\n\
    origin.x = floor(compressed * SHIFT_RIGHT5);\n\
    compressed -= origin.x * SHIFT_LEFT5;\n\
\n\
    origin.y = floor(compressed * SHIFT_RIGHT3);\n\
    compressed -= origin.y * SHIFT_LEFT3;\n\
\n\
    origin -= vec2(1.0);\n\
\n\
    float show = floor(compressed * SHIFT_RIGHT2);\n\
    compressed -= show * SHIFT_LEFT2;\n\
\n\
#ifdef INSTANCED\n\
    vec2 textureCoordinatesBottomLeft = czm_decompressTextureCoordinates(compressedAttribute0.w);\n\
    vec2 textureCoordinatesRange = czm_decompressTextureCoordinates(eyeOffset.w);\n\
    vec2 textureCoordinates = textureCoordinatesBottomLeft + direction * textureCoordinatesRange;\n\
#else\n\
    vec2 direction;\n\
    direction.x = floor(compressed * SHIFT_RIGHT1);\n\
    direction.y = compressed - direction.x * SHIFT_LEFT1;\n\
\n\
    vec2 textureCoordinates = czm_decompressTextureCoordinates(compressedAttribute0.w);\n\
#endif\n\
\n\
    float temp = compressedAttribute0.y  * SHIFT_RIGHT8;\n\
    pixelOffset.y = -(floor(temp) - UPPER_BOUND);\n\
\n\
    vec2 translate;\n\
    translate.y = (temp - floor(temp)) * SHIFT_LEFT16;\n\
\n\
    temp = compressedAttribute0.z * SHIFT_RIGHT8;\n\
    translate.x = floor(temp) - UPPER_BOUND;\n\
\n\
    translate.y += (temp - floor(temp)) * SHIFT_LEFT8;\n\
    translate.y -= UPPER_BOUND;\n\
\n\
    temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\
\n\
    vec2 imageSize = vec2(floor(temp), compressedAttribute2.w);\n\
\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
    vec4 translucencyByDistance;\n\
    translucencyByDistance.x = compressedAttribute1.z;\n\
    translucencyByDistance.z = compressedAttribute1.w;\n\
\n\
    translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
\n\
    temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\
    translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
#endif\n\
\n\
#ifdef ALIGNED_AXIS\n\
    vec3 alignedAxis = czm_octDecode(floor(compressedAttribute1.y * SHIFT_RIGHT8));\n\
    temp = compressedAttribute2.z * SHIFT_RIGHT5;\n\
    bool validAlignedAxis = (temp - floor(temp)) * SHIFT_LEFT1 > 0.0;\n\
#else\n\
    vec3 alignedAxis = vec3(0.0);\n\
    bool validAlignedAxis = false;\n\
#endif\n\
\n\
#ifdef RENDER_FOR_PICK\n\
    temp = compressedAttribute2.y;\n\
#else\n\
    temp = compressedAttribute2.x;\n\
#endif\n\
\n\
    vec4 color;\n\
    temp = temp * SHIFT_RIGHT8;\n\
    color.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    temp = floor(temp) * SHIFT_RIGHT8;\n\
    color.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    color.r = floor(temp);\n\
\n\
    temp = compressedAttribute2.z * SHIFT_RIGHT8;\n\
    bool sizeInMeters = floor((temp - floor(temp)) * SHIFT_LEFT7) > 0.0;\n\
    temp = floor(temp) * SHIFT_RIGHT8;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
    color.a = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    vec4 pickColor = color / 255.0;\n\
#else\n\
    color.a = floor(temp);\n\
    color /= 255.0;\n\
#endif\n\
\n\
    ///////////////////////////////////////////////////////////////////////////\n\
\n\
    vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\
    vec4 positionEC = czm_modelViewRelativeToEye * p;\n\
    positionEC = czm_eyeOffset(positionEC, eyeOffset.xyz);\n\
    positionEC.xyz *= show;\n\
\n\
    ///////////////////////////////////////////////////////////////////////////\n\
\n\
#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(EYE_DISTANCE_PIXEL_OFFSET) || defined(DISTANCE_DISPLAY_CONDITION)\n\
    float lengthSq;\n\
    if (czm_sceneMode == czm_sceneMode2D)\n\
    {\n\
        // 2D camera distance is a special case\n\
        // treat all billboards as flattened to the z=0.0 plane\n\
        lengthSq = czm_eyeHeight2D.y;\n\
    }\n\
    else\n\
    {\n\
        lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\
    }\n\
#endif\n\
\n\
#ifdef EYE_DISTANCE_SCALING\n\
    float distanceScale = czm_nearFarScalar(scaleByDistance, lengthSq);\n\
    scale *= distanceScale;\n\
    translate *= distanceScale;\n\
    // push vertex behind near plane for clipping\n\
    if (scale == 0.0)\n\
    {\n\
        positionEC.xyz = vec3(0.0);\n\
    }\n\
#endif\n\
\n\
    float translucency = 1.0;\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
    translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\
    // push vertex behind near plane for clipping\n\
    if (translucency == 0.0)\n\
    {\n\
        positionEC.xyz = vec3(0.0);\n\
    }\n\
#endif\n\
\n\
#ifdef EYE_DISTANCE_PIXEL_OFFSET\n\
    float pixelOffsetScale = czm_nearFarScalar(pixelOffsetScaleByDistance, lengthSq);\n\
    pixelOffset *= pixelOffsetScale;\n\
#endif\n\
\n\
#ifdef DISTANCE_DISPLAY_CONDITION\n\
    float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x;\n\
    float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y;\n\
    if (lengthSq < nearSq || lengthSq > farSq)\n\
    {\n\
        positionEC.xyz = vec3(0.0);\n\
    }\n\
#endif\n\
\n\
    vec4 positionWC = computePositionWindowCoordinates(positionEC, imageSize, scale, direction, origin, translate, pixelOffset, alignedAxis, validAlignedAxis, rotation, sizeInMeters);\n\
    gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0);\n\
    v_textureCoordinates = textureCoordinates;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
    v_pickColor = pickColor;\n\
#else\n\
    v_color = color;\n\
    v_color.a *= translucency;\n\
#endif\n\
}\n\
";
});
/*global define*/
define('Scene/BlendOption',[
    '../Core/freezeObject'
], function(
    freezeObject) {
    'use strict';

    /**
     * Determines how opaque and translucent parts of billboards, points, and labels are blended with the scene.
     *
     * @exports BlendOption
     */
    var BlendOption = {
        /**
         * The billboards, points, or labels in the collection are completely opaque.
         * @type {Number}
         * @constant
         */
        OPAQUE : 0,

        /**
         * The billboards, points, or labels in the collection are completely translucent.
         * @type {Number}
         * @constant
         */
        TRANSLUCENT : 1,

        /**
         * The billboards, points, or labels in the collection are both opaque and translucent.
         * @type {Number}
         * @constant
         */
        OPAQUE_AND_TRANSLUCENT : 2
    };

    return freezeObject(BlendOption);
});

/*global define*/
define('Renderer/Framebuffer',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/PixelFormat',
        './ContextLimits'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        PixelFormat,
        ContextLimits) {
    'use strict';

    function attachTexture(framebuffer, attachment, texture) {
        var gl = framebuffer._gl;
        gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, texture._target, texture._texture, 0);
    }

    function attachRenderbuffer(framebuffer, attachment, renderbuffer) {
        var gl = framebuffer._gl;
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer._getRenderbuffer());
    }

    /**
     * Creates a framebuffer with optional initial color, depth, and stencil attachments.
     * Framebuffers are used for render-to-texture effects; they allow us to render to
     * textures in one pass, and read from it in a later pass.
     *
     * @param {Object} options The initial framebuffer attachments as shown in the example below. <code>context</code> is required. The possible properties are <code>colorTextures</code>, <code>colorRenderbuffers</code>, <code>depthTexture</code>, <code>depthRenderbuffer</code>, <code>stencilRenderbuffer</code>, <code>depthStencilTexture</code>, and <code>depthStencilRenderbuffer</code>.
     *
     * @exception {DeveloperError} Cannot have both color texture and color renderbuffer attachments.
     * @exception {DeveloperError} Cannot have both a depth texture and depth renderbuffer attachment.
     * @exception {DeveloperError} Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.
     * @exception {DeveloperError} Cannot have both a depth and depth-stencil renderbuffer.
     * @exception {DeveloperError} Cannot have both a stencil and depth-stencil renderbuffer.
     * @exception {DeveloperError} Cannot have both a depth and stencil renderbuffer.
     * @exception {DeveloperError} The color-texture pixel-format must be a color format.
     * @exception {DeveloperError} The depth-texture pixel-format must be DEPTH_COMPONENT.
     * @exception {DeveloperError} The depth-stencil-texture pixel-format must be DEPTH_STENCIL.
     * @exception {DeveloperError} The number of color attachments exceeds the number supported.
     *
     * @example
     * // Create a framebuffer with color and depth texture attachments.
     * var width = context.canvas.clientWidth;
     * var height = context.canvas.clientHeight;
     * var framebuffer = new Framebuffer({
     *   context : context,
     *   colorTextures : [new Texture({
     *     context : context,
     *     width : width,
     *     height : height,
     *     pixelFormat : PixelFormat.RGBA
     *   })],
     *   depthTexture : new Texture({
     *     context : context,
     *     width : width,
     *     height : height,
     *     pixelFormat : PixelFormat.DEPTH_COMPONENT,
     *     pixelDatatype : PixelDatatype.UNSIGNED_SHORT
     *   })
     * });
     *
     * @private
     */
    function Framebuffer(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        var gl = options.context._gl;
        var maximumColorAttachments = ContextLimits.maximumColorAttachments;

        this._gl = gl;
        this._framebuffer = gl.createFramebuffer();

        this._colorTextures = [];
        this._colorRenderbuffers = [];
        this._activeColorAttachments = [];

        this._depthTexture = undefined;
        this._depthRenderbuffer = undefined;
        this._stencilRenderbuffer = undefined;
        this._depthStencilTexture = undefined;
        this._depthStencilRenderbuffer = undefined;

        /**
         * When true, the framebuffer owns its attachments so they will be destroyed when
         * {@link Framebuffer#destroy} is called or when a new attachment is assigned
         * to an attachment point.
         *
         * @type {Boolean}
         * @default true
         *
         * @see Framebuffer#destroy
         */
        this.destroyAttachments = defaultValue(options.destroyAttachments, true);

        // Throw if a texture and renderbuffer are attached to the same point.  This won't
        // cause a WebGL error (because only one will be attached), but is likely a developer error.

                if (defined(options.colorTextures) && defined(options.colorRenderbuffers)) {
            throw new DeveloperError('Cannot have both color texture and color renderbuffer attachments.');
        }
        if (defined(options.depthTexture) && defined(options.depthRenderbuffer)) {
            throw new DeveloperError('Cannot have both a depth texture and depth renderbuffer attachment.');
        }
        if (defined(options.depthStencilTexture) && defined(options.depthStencilRenderbuffer)) {
            throw new DeveloperError('Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.');
        }
        
        // Avoid errors defined in Section 6.5 of the WebGL spec
        var depthAttachment = (defined(options.depthTexture) || defined(options.depthRenderbuffer));
        var depthStencilAttachment = (defined(options.depthStencilTexture) || defined(options.depthStencilRenderbuffer));

                if (depthAttachment && depthStencilAttachment) {
            throw new DeveloperError('Cannot have both a depth and depth-stencil attachment.');
        }
        if (defined(options.stencilRenderbuffer) && depthStencilAttachment) {
            throw new DeveloperError('Cannot have both a stencil and depth-stencil attachment.');
        }
        if (depthAttachment && defined(options.stencilRenderbuffer)) {
            throw new DeveloperError('Cannot have both a depth and stencil attachment.');
        }
        
        ///////////////////////////////////////////////////////////////////

        this._bind();

        var texture;
        var renderbuffer;
        var i;
        var length;
        var attachmentEnum;

        if (defined(options.colorTextures)) {
            var textures = options.colorTextures;
            length = this._colorTextures.length = this._activeColorAttachments.length = textures.length;

                        if (length > maximumColorAttachments) {
                throw new DeveloperError('The number of color attachments exceeds the number supported.');
            }
            
            for (i = 0; i < length; ++i) {
                texture = textures[i];

                                if (!PixelFormat.isColorFormat(texture.pixelFormat)) {
                    throw new DeveloperError('The color-texture pixel-format must be a color format.');
                }
                
                attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
                attachTexture(this, attachmentEnum, texture);
                this._activeColorAttachments[i] = attachmentEnum;
                this._colorTextures[i] = texture;
            }
        }

        if (defined(options.colorRenderbuffers)) {
            var renderbuffers = options.colorRenderbuffers;
            length = this._colorRenderbuffers.length = this._activeColorAttachments.length = renderbuffers.length;

                        if (length > maximumColorAttachments) {
                throw new DeveloperError('The number of color attachments exceeds the number supported.');
            }
            
            for (i = 0; i < length; ++i) {
                renderbuffer = renderbuffers[i];
                attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
                attachRenderbuffer(this, attachmentEnum, renderbuffer);
                this._activeColorAttachments[i] = attachmentEnum;
                this._colorRenderbuffers[i] = renderbuffer;
            }
        }

        if (defined(options.depthTexture)) {
            texture = options.depthTexture;

                        if (texture.pixelFormat !== PixelFormat.DEPTH_COMPONENT) {
                throw new DeveloperError('The depth-texture pixel-format must be DEPTH_COMPONENT.');
            }
            
            attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture);
            this._depthTexture = texture;
        }

        if (defined(options.depthRenderbuffer)) {
            renderbuffer = options.depthRenderbuffer;
            attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer);
            this._depthRenderbuffer = renderbuffer;
        }

        if (defined(options.stencilRenderbuffer)) {
            renderbuffer = options.stencilRenderbuffer;
            attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer);
            this._stencilRenderbuffer = renderbuffer;
        }

        if (defined(options.depthStencilTexture)) {
            texture = options.depthStencilTexture;

                        if (texture.pixelFormat !== PixelFormat.DEPTH_STENCIL) {
                throw new DeveloperError('The depth-stencil pixel-format must be DEPTH_STENCIL.');
            }
            
            attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture);
            this._depthStencilTexture = texture;
        }

        if (defined(options.depthStencilRenderbuffer)) {
            renderbuffer = options.depthStencilRenderbuffer;
            attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer);
            this._depthStencilRenderbuffer = renderbuffer;
        }

        this._unBind();
    }

    defineProperties(Framebuffer.prototype, {
        /**
         * The status of the framebuffer. If the status is not WebGLConstants.FRAMEBUFFER_COMPLETE,
         * a {@link DeveloperError} will be thrown when attempting to render to the framebuffer.
         * @memberof Framebuffer.prototype
         * @type {Number}
         */
        status : {
            get : function() {
                this._bind();
                var status = this._gl.checkFramebufferStatus(this._gl.FRAMEBUFFER);
                this._unBind();
                return status;
            }
        },
        numberOfColorAttachments : {
            get : function() {
                return this._activeColorAttachments.length;
            }
        },
        depthTexture: {
            get : function() {
                return this._depthTexture;
            }
        },
        depthRenderbuffer: {
            get : function() {
                return this._depthRenderbuffer;
            }
        },
        stencilRenderbuffer : {
            get : function() {
                return this._stencilRenderbuffer;
            }
        },
        depthStencilTexture : {
            get : function() {
                return this._depthStencilTexture;
            }
        },
        depthStencilRenderbuffer : {
            get : function() {
                return this._depthStencilRenderbuffer;
            }
        },

        /**
         * True if the framebuffer has a depth attachment.  Depth attachments include
         * depth and depth-stencil textures, and depth and depth-stencil renderbuffers.  When
         * rendering to a framebuffer, a depth attachment is required for the depth test to have effect.
         * @memberof Framebuffer.prototype
         * @type {Boolean}
         */
        hasDepthAttachment : {
            get : function() {
                return !!(this.depthTexture || this.depthRenderbuffer || this.depthStencilTexture || this.depthStencilRenderbuffer);
            }
        }
    });

    Framebuffer.prototype._bind = function() {
        var gl = this._gl;
        gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer);
    };

    Framebuffer.prototype._unBind = function() {
        var gl = this._gl;
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    };

    Framebuffer.prototype._getActiveColorAttachments = function() {
        return this._activeColorAttachments;
    };

    Framebuffer.prototype.getColorTexture = function(index) {
                if (!defined(index) || index < 0 || index >= this._colorTextures.length) {
            throw new DeveloperError('index is required, must be greater than or equal to zero and must be less than the number of color attachments.');
        }
        
        return this._colorTextures[index];
    };

    Framebuffer.prototype.getColorRenderbuffer = function(index) {
                if (!defined(index) || index < 0 || index >= this._colorRenderbuffers.length) {
            throw new DeveloperError('index is required, must be greater than or equal to zero and must be less than the number of color attachments.');
        }
        
        return this._colorRenderbuffers[index];
    };

    Framebuffer.prototype.isDestroyed = function() {
        return false;
    };

    Framebuffer.prototype.destroy = function() {
        if (this.destroyAttachments) {
            // If the color texture is a cube map face, it is owned by the cube map, and will not be destroyed.
            var i = 0;
            var textures = this._colorTextures;
            var length = textures.length;
            for (; i < length; ++i) {
                var texture = textures[i];
                if (defined(texture)) {
                    texture.destroy();
                }
            }

            var renderbuffers = this._colorRenderbuffers;
            length = renderbuffers.length;
            for (i = 0; i < length; ++i) {
                var renderbuffer = renderbuffers[i];
                if (defined(renderbuffer)) {
                    renderbuffer.destroy();
                }
            }

            this._depthTexture = this._depthTexture && this._depthTexture.destroy();
            this._depthRenderbuffer = this._depthRenderbuffer && this._depthRenderbuffer.destroy();
            this._stencilRenderbuffer = this._stencilRenderbuffer && this._stencilRenderbuffer.destroy();
            this._depthStencilTexture = this._depthStencilTexture && this._depthStencilTexture.destroy();
            this._depthStencilRenderbuffer = this._depthStencilRenderbuffer && this._depthStencilRenderbuffer.destroy();
        }

        this._gl.deleteFramebuffer(this._framebuffer);
        return destroyObject(this);
    };

    return Framebuffer;
});

/*global define*/
define('Scene/TextureAtlas',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/loadImage',
        '../Core/PixelFormat',
        '../Core/RuntimeError',
        '../Renderer/Framebuffer',
        '../Renderer/Texture',
        '../ThirdParty/when'
    ], function(
        BoundingRectangle,
        Cartesian2,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        loadImage,
        PixelFormat,
        RuntimeError,
        Framebuffer,
        Texture,
        when) {
    'use strict';

    // The atlas is made up of regions of space called nodes that contain images or child nodes.
    function TextureAtlasNode(bottomLeft, topRight, childNode1, childNode2, imageIndex) {
        this.bottomLeft = defaultValue(bottomLeft, Cartesian2.ZERO);
        this.topRight = defaultValue(topRight, Cartesian2.ZERO);
        this.childNode1 = childNode1;
        this.childNode2 = childNode2;
        this.imageIndex = imageIndex;
    }

    var defaultInitialSize = new Cartesian2(16.0, 16.0);

    /**
     * A TextureAtlas stores multiple images in one square texture and keeps
     * track of the texture coordinates for each image. TextureAtlas is dynamic,
     * meaning new images can be added at any point in time.
     * Texture coordinates are subject to change if the texture atlas resizes, so it is
     * important to check {@link TextureAtlas#getGUID} before using old values.
     *
     * @alias TextureAtlas
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Scene} options.context The context in which the texture gets created.
     * @param {PixelFormat} [options.pixelFormat=PixelFormat.RGBA] The pixel format of the texture.
     * @param {Number} [options.borderWidthInPixels=1] The amount of spacing between adjacent images in pixels.
     * @param {Cartesian2} [options.initialSize=new Cartesian2(16.0, 16.0)] The initial side lengths of the texture.
     *
     * @exception {DeveloperError} borderWidthInPixels must be greater than or equal to zero.
     * @exception {DeveloperError} initialSize must be greater than zero.
     *
     * @private
     */
    function TextureAtlas(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var borderWidthInPixels = defaultValue(options.borderWidthInPixels, 1.0);
        var initialSize = defaultValue(options.initialSize, defaultInitialSize);

                if (!defined(options.context)) {
            throw new DeveloperError('context is required.');
        }
        if (borderWidthInPixels < 0) {
            throw new DeveloperError('borderWidthInPixels must be greater than or equal to zero.');
        }
        if (initialSize.x < 1 || initialSize.y < 1) {
            throw new DeveloperError('initialSize must be greater than zero.');
        }
        
        this._context = options.context;
        this._pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
        this._borderWidthInPixels = borderWidthInPixels;
        this._textureCoordinates = [];
        this._guid = createGuid();
        this._idHash = {};
        this._initialSize = initialSize;

        this._root = undefined;
    }

    defineProperties(TextureAtlas.prototype, {
        /**
         * The amount of spacing between adjacent images in pixels.
         * @memberof TextureAtlas.prototype
         * @type {Number}
         */
        borderWidthInPixels : {
            get : function() {
                return this._borderWidthInPixels;
            }
        },

        /**
         * An array of {@link BoundingRectangle} texture coordinate regions for all the images in the texture atlas.
         * The x and y values of the rectangle correspond to the bottom-left corner of the texture coordinate.
         * The coordinates are in the order that the corresponding images were added to the atlas.
         * @memberof TextureAtlas.prototype
         * @type {BoundingRectangle[]}
         */
        textureCoordinates : {
            get : function() {
                return this._textureCoordinates;
            }
        },

        /**
         * The texture that all of the images are being written to.
         * @memberof TextureAtlas.prototype
         * @type {Texture}
         */
        texture : {
            get : function() {
                if(!defined(this._texture)) {
                    this._texture = new Texture({
                        context : this._context,
                        width : this._initialSize.x,
                        height : this._initialSize.y,
                        pixelFormat : this._pixelFormat
                    });
                }
                return this._texture;
            }
        },

        /**
         * The number of images in the texture atlas. This value increases
         * every time addImage or addImages is called.
         * Texture coordinates are subject to change if the texture atlas resizes, so it is
         * important to check {@link TextureAtlas#getGUID} before using old values.
         * @memberof TextureAtlas.prototype
         * @type {Number}
         */
        numberOfImages : {
            get : function() {
                return this._textureCoordinates.length;
            }
        },

        /**
         * The atlas' globally unique identifier (GUID).
         * The GUID changes whenever the texture atlas is modified.
         * Classes that use a texture atlas should check if the GUID
         * has changed before processing the atlas data.
         * @memberof TextureAtlas.prototype
         * @type {String}
         */
        guid : {
            get : function() {
                return this._guid;
            }
        }
    });

    // Builds a larger texture and copies the old texture into the new one.
    function resizeAtlas(textureAtlas, image) {
        var context = textureAtlas._context;
        var numImages = textureAtlas.numberOfImages;
        var scalingFactor = 2.0;
        var borderWidthInPixels = textureAtlas._borderWidthInPixels;
        if (numImages > 0) {
            var oldAtlasWidth = textureAtlas._texture.width;
            var oldAtlasHeight = textureAtlas._texture.height;
            var atlasWidth = scalingFactor * (oldAtlasWidth + image.width + borderWidthInPixels);
            var atlasHeight = scalingFactor * (oldAtlasHeight + image.height + borderWidthInPixels);
            var widthRatio = oldAtlasWidth / atlasWidth;
            var heightRatio = oldAtlasHeight / atlasHeight;

            // Create new node structure, putting the old root node in the bottom left.
            var nodeBottomRight = new TextureAtlasNode(new Cartesian2(oldAtlasWidth + borderWidthInPixels, borderWidthInPixels), new Cartesian2(atlasWidth, oldAtlasHeight));
            var nodeBottomHalf = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, oldAtlasHeight), textureAtlas._root, nodeBottomRight);
            var nodeTopHalf = new TextureAtlasNode(new Cartesian2(borderWidthInPixels, oldAtlasHeight + borderWidthInPixels), new Cartesian2(atlasWidth, atlasHeight));
            var nodeMain = new TextureAtlasNode(new Cartesian2(), new Cartesian2(atlasWidth, atlasHeight), nodeBottomHalf, nodeTopHalf);

            // Resize texture coordinates.
            for (var i = 0; i < textureAtlas._textureCoordinates.length; i++) {
                var texCoord = textureAtlas._textureCoordinates[i];
                if (defined(texCoord)) {
                    texCoord.x *= widthRatio;
                    texCoord.y *= heightRatio;
                    texCoord.width *= widthRatio;
                    texCoord.height *= heightRatio;
                }
            }

            // Copy larger texture.
            var newTexture = new Texture({
                context : textureAtlas._context,
                width : atlasWidth,
                height : atlasHeight,
                pixelFormat : textureAtlas._pixelFormat
            });

            var framebuffer = new Framebuffer({
                context : context,
                colorTextures : [textureAtlas._texture],
                destroyAttachments : false
            });

            framebuffer._bind();
            newTexture.copyFromFramebuffer(0, 0, 0, 0, atlasWidth, atlasHeight);
            framebuffer._unBind();
            framebuffer.destroy();
            textureAtlas._texture = textureAtlas._texture && textureAtlas._texture.destroy();
            textureAtlas._texture = newTexture;
            textureAtlas._root = nodeMain;
        } else {
            // First image exceeds initialSize
            var initialWidth = scalingFactor * (image.width + 2 * borderWidthInPixels);
            var initialHeight = scalingFactor * (image.height + 2 * borderWidthInPixels);
            if(initialWidth < textureAtlas._initialSize.x) {
                initialWidth = textureAtlas._initialSize.x;
            }
            if(initialHeight < textureAtlas._initialSize.y) {
                initialHeight = textureAtlas._initialSize.y;
            }
            textureAtlas._texture = textureAtlas._texture && textureAtlas._texture.destroy();
            textureAtlas._texture = new Texture({
                context : textureAtlas._context,
                width : initialWidth,
                height : initialHeight,
                pixelFormat : textureAtlas._pixelFormat
            });
            textureAtlas._root = new TextureAtlasNode(new Cartesian2(borderWidthInPixels, borderWidthInPixels),
                new Cartesian2(initialWidth, initialHeight));
        }
    }

    // A recursive function that finds the best place to insert
    // a new image based on existing image 'nodes'.
    // Inspired by: http://blackpawn.com/texts/lightmaps/default.html
    function findNode(textureAtlas, node, image) {
        if (!defined(node)) {
            return undefined;
        }

        // If a leaf node
        if (!defined(node.childNode1) &&
            !defined(node.childNode2)) {

            // Node already contains an image, don't add to it.
            if (defined(node.imageIndex)) {
                return undefined;
            }

            var nodeWidth = node.topRight.x - node.bottomLeft.x;
            var nodeHeight = node.topRight.y - node.bottomLeft.y;
            var widthDifference = nodeWidth - image.width;
            var heightDifference = nodeHeight - image.height;

            // Node is smaller than the image.
            if (widthDifference < 0 || heightDifference < 0) {
                return undefined;
            }

            // If the node is the same size as the image, return the node
            if (widthDifference === 0 && heightDifference === 0) {
                return node;
            }

            // Vertical split (childNode1 = left half, childNode2 = right half).
            if (widthDifference > heightDifference) {
                node.childNode1 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, node.bottomLeft.y), new Cartesian2(node.bottomLeft.x + image.width, node.topRight.y));
                // Only make a second child if the border gives enough space.
                var childNode2BottomLeftX = node.bottomLeft.x + image.width + textureAtlas._borderWidthInPixels;
                if (childNode2BottomLeftX < node.topRight.x) {
                    node.childNode2 = new TextureAtlasNode(new Cartesian2(childNode2BottomLeftX, node.bottomLeft.y), new Cartesian2(node.topRight.x, node.topRight.y));
                }
            }
            // Horizontal split (childNode1 = bottom half, childNode2 = top half).
            else {
                node.childNode1 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, node.bottomLeft.y), new Cartesian2(node.topRight.x, node.bottomLeft.y + image.height));
                // Only make a second child if the border gives enough space.
                var childNode2BottomLeftY = node.bottomLeft.y + image.height + textureAtlas._borderWidthInPixels;
                if (childNode2BottomLeftY < node.topRight.y) {
                    node.childNode2 = new TextureAtlasNode(new Cartesian2(node.bottomLeft.x, childNode2BottomLeftY), new Cartesian2(node.topRight.x, node.topRight.y));
                }
            }
            return findNode(textureAtlas, node.childNode1, image);
        }

        // If not a leaf node
        return findNode(textureAtlas, node.childNode1, image) ||
            findNode(textureAtlas, node.childNode2, image);
    }

    // Adds image of given index to the texture atlas. Called from addImage and addImages.
    function addImage(textureAtlas, image, index) {
        var node = findNode(textureAtlas, textureAtlas._root, image);
        if (defined(node)) {
            // Found a node that can hold the image.
            node.imageIndex = index;

            // Add texture coordinate and write to texture
            var atlasWidth = textureAtlas._texture.width;
            var atlasHeight = textureAtlas._texture.height;
            var nodeWidth = node.topRight.x - node.bottomLeft.x;
            var nodeHeight = node.topRight.y - node.bottomLeft.y;
            var x = node.bottomLeft.x / atlasWidth;
            var y = node.bottomLeft.y / atlasHeight;
            var w = nodeWidth / atlasWidth;
            var h = nodeHeight / atlasHeight;
            textureAtlas._textureCoordinates[index] = new BoundingRectangle(x, y, w, h);
            textureAtlas._texture.copyFrom(image, node.bottomLeft.x, node.bottomLeft.y);
        } else {
            // No node found, must resize the texture atlas.
            resizeAtlas(textureAtlas, image);
            addImage(textureAtlas, image, index);
        }

        textureAtlas._guid = createGuid();
    }

    /**
     * Adds an image to the atlas.  If the image is already in the atlas, the atlas is unchanged and
     * the existing index is used.
     *
     * @param {String} id An identifier to detect whether the image already exists in the atlas.
     * @param {Image|Canvas|String|Promise|TextureAtlas~CreateImageCallback} image An image or canvas to add to the texture atlas,
     *        or a URL to an Image, or a Promise for an image, or a function that creates an image.
     * @returns {Promise.<Number>} A Promise for the image index.
     */
    TextureAtlas.prototype.addImage = function(id, image) {
                if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        if (!defined(image)) {
            throw new DeveloperError('image is required.');
        }
        
        var indexPromise = this._idHash[id];
        if (defined(indexPromise)) {
            // we're already aware of this source
            return indexPromise;
        }

        // not in atlas, create the promise for the index

        if (typeof image === 'function') {
            // if image is a function, call it
            image = image(id);
                        if (!defined(image)) {
                throw new DeveloperError('image is required.');
            }
                    } else if (typeof image === 'string') {
            // if image is a string, load it as an image
            image = loadImage(image);
        }

        var that = this;

        indexPromise = when(image, function(image) {
            if (that.isDestroyed()) {
                return -1;
            }

            var index = that.numberOfImages;

            addImage(that, image, index);

            return index;
        });

        // store the promise
        this._idHash[id] = indexPromise;

        return indexPromise;
    };

    /**
     * Add a sub-region of an existing atlas image as additional image indices.
     *
     * @param {String} id The identifier of the existing image.
     * @param {BoundingRectangle} subRegion An {@link BoundingRectangle} sub-region measured in pixels from the bottom-left.
     *
     * @returns {Promise.<Number>} A Promise for the image index.
     */
    TextureAtlas.prototype.addSubRegion = function(id, subRegion) {
                if (!defined(id)) {
            throw new DeveloperError('id is required.');
        }
        if (!defined(subRegion)) {
            throw new DeveloperError('subRegion is required.');
        }
        
        var indexPromise = this._idHash[id];
        if (!defined(indexPromise)) {
            throw new RuntimeError('image with id "' + id + '" not found in the atlas.');
        }

        var that = this;
        return when(indexPromise, function(index) {
            if (index === -1) {
                // the atlas is destroyed
                return -1;
            }
            var atlasWidth = that._texture.width;
            var atlasHeight = that._texture.height;
            var numImages = that.numberOfImages;

            var baseRegion = that._textureCoordinates[index];
            var x = baseRegion.x + (subRegion.x / atlasWidth);
            var y = baseRegion.y + (subRegion.y / atlasHeight);
            var w = subRegion.width / atlasWidth;
            var h = subRegion.height / atlasHeight;
            that._textureCoordinates.push(new BoundingRectangle(x, y, w, h));
            that._guid = createGuid();

            return numImages;
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see TextureAtlas#destroy
     */
    TextureAtlas.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * atlas = atlas && atlas.destroy();
     *
     * @see TextureAtlas#isDestroyed
     */
    TextureAtlas.prototype.destroy = function() {
        this._texture = this._texture && this._texture.destroy();
        return destroyObject(this);
    };

    /**
     * A function that creates an image.
     * @callback TextureAtlas~CreateImageCallback
     * @param {String} id The identifier of the image to load.
     * @returns {Image|Promise} The image, or a promise that will resolve to an image.
     */

    return TextureAtlas;
});

/*global define*/
define('Scene/BillboardCollection',[
        '../Core/AttributeCompression',
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EncodedCartesian3',
        '../Core/IndexDatatype',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/WebGLConstants',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArrayFacade',
        '../Shaders/BillboardCollectionFS',
        '../Shaders/BillboardCollectionVS',
        './Billboard',
        './BlendingState',
        './BlendOption',
        './HeightReference',
        './HorizontalOrigin',
        './SceneMode',
        './TextureAtlas',
        './VerticalOrigin'
    ], function(
        AttributeCompression,
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EncodedCartesian3,
        IndexDatatype,
        CesiumMath,
        Matrix4,
        WebGLConstants,
        Buffer,
        BufferUsage,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArrayFacade,
        BillboardCollectionFS,
        BillboardCollectionVS,
        Billboard,
        BlendingState,
        BlendOption,
        HeightReference,
        HorizontalOrigin,
        SceneMode,
        TextureAtlas,
        VerticalOrigin) {
    'use strict';

    var SHOW_INDEX = Billboard.SHOW_INDEX;
    var POSITION_INDEX = Billboard.POSITION_INDEX;
    var PIXEL_OFFSET_INDEX = Billboard.PIXEL_OFFSET_INDEX;
    var EYE_OFFSET_INDEX = Billboard.EYE_OFFSET_INDEX;
    var HORIZONTAL_ORIGIN_INDEX = Billboard.HORIZONTAL_ORIGIN_INDEX;
    var VERTICAL_ORIGIN_INDEX = Billboard.VERTICAL_ORIGIN_INDEX;
    var SCALE_INDEX = Billboard.SCALE_INDEX;
    var IMAGE_INDEX_INDEX = Billboard.IMAGE_INDEX_INDEX;
    var COLOR_INDEX = Billboard.COLOR_INDEX;
    var ROTATION_INDEX = Billboard.ROTATION_INDEX;
    var ALIGNED_AXIS_INDEX = Billboard.ALIGNED_AXIS_INDEX;
    var SCALE_BY_DISTANCE_INDEX = Billboard.SCALE_BY_DISTANCE_INDEX;
    var TRANSLUCENCY_BY_DISTANCE_INDEX = Billboard.TRANSLUCENCY_BY_DISTANCE_INDEX;
    var PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX = Billboard.PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX;
    var DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION_INDEX;
    var NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;

    var attributeLocations;

    var attributeLocationsBatched = {
        positionHighAndScale : 0,
        positionLowAndRotation : 1,
        compressedAttribute0 : 2,        // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates
        compressedAttribute1 : 3,        // aligned axis, translucency by distance, image width
        compressedAttribute2 : 4,        // image height, color, pick color, size in meters, valid aligned axis, 13 bits free
        eyeOffset : 5,                   // 4 bytes free
        scaleByDistance : 6,
        pixelOffsetScaleByDistance : 7,
        distanceDisplayCondition : 8
    };

    var attributeLocationsInstanced = {
        direction : 0,
        positionHighAndScale : 1,
        positionLowAndRotation : 2,     // texture offset in w
        compressedAttribute0 : 3,
        compressedAttribute1 : 4,
        compressedAttribute2 : 5,
        eyeOffset : 6,                  // texture range in w
        scaleByDistance : 7,
        pixelOffsetScaleByDistance : 8,
        distanceDisplayCondition : 9
    };

    /**
     * A renderable collection of billboards.  Billboards are viewport-aligned
     * images positioned in the 3D scene.
     * <br /><br />
     * <div align='center'>
     * <img src='images/Billboard.png' width='400' height='300' /><br />
     * Example billboards
     * </div>
     * <br /><br />
     * Billboards are added and removed from the collection using {@link BillboardCollection#add}
     * and {@link BillboardCollection#remove}.  Billboards in a collection automatically share textures
     * for images with the same identifier.
     *
     * @alias BillboardCollection
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each billboard from model to world coordinates.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {Scene} [options.scene] Must be passed in for billboards that use the height reference property or will be depth tested against the globe.
     * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The billboard blending option. The default
     * is used for rendering both opaque and translucent billboards. However, if either all of the billboards are completely opaque or all are completely translucent,
     * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x.
     *
     * @performance For best performance, prefer a few collections, each with many billboards, to
     * many collections with only a few billboards each.  Organize collections so that billboards
     * with the same update frequency are in the same collection, i.e., billboards that do not
     * change should be in one collection; billboards that change every frame should be in another
     * collection; and so on.
     *
     * @see BillboardCollection#add
     * @see BillboardCollection#remove
     * @see Billboard
     * @see LabelCollection
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Billboards.html|Cesium Sandcastle Billboard Demo}
     *
     * @example
     * // Create a billboard collection with two billboards
     * var billboards = scene.primitives.add(new Cesium.BillboardCollection());
     * billboards.add({
     *   position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
     *   image : 'url/to/image'
     * });
     * billboards.add({
     *   position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
     *   image : 'url/to/another/image'
     * });
     */
    function BillboardCollection(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._scene = options.scene;

        this._textureAtlas = undefined;
        this._textureAtlasGUID = undefined;
        this._destroyTextureAtlas = true;
        this._sp = undefined;
        this._spTranslucent = undefined;
        this._spPick = undefined;
        this._rsOpaque = undefined;
        this._rsTranslucent = undefined;
        this._vaf = undefined;

        this._billboards = [];
        this._billboardsToUpdate = [];
        this._billboardsToUpdateIndex = 0;
        this._billboardsRemoved = false;
        this._createVertexArray = false;

        this._shaderRotation = false;
        this._compiledShaderRotation = false;
        this._compiledShaderRotationPick = false;

        this._shaderAlignedAxis = false;
        this._compiledShaderAlignedAxis = false;
        this._compiledShaderAlignedAxisPick = false;

        this._shaderScaleByDistance = false;
        this._compiledShaderScaleByDistance = false;
        this._compiledShaderScaleByDistancePick = false;

        this._shaderTranslucencyByDistance = false;
        this._compiledShaderTranslucencyByDistance = false;
        this._compiledShaderTranslucencyByDistancePick = false;

        this._shaderPixelOffsetScaleByDistance = false;
        this._compiledShaderPixelOffsetScaleByDistance = false;
        this._compiledShaderPixelOffsetScaleByDistancePick = false;

        this._shaderDistanceDisplayCondition = false;
        this._compiledShaderDistanceDisplayCondition = false;
        this._compiledShaderDistanceDisplayConditionPick = false;

        this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);

        this._maxSize = 0.0;
        this._maxEyeOffset = 0.0;
        this._maxScale = 1.0;
        this._maxPixelOffset = 0.0;
        this._allHorizontalCenter = true;
        this._allVerticalCenter = true;
        this._allSizedInMeters = true;

        this._baseVolume = new BoundingSphere();
        this._baseVolumeWC = new BoundingSphere();
        this._baseVolume2D = new BoundingSphere();
        this._boundingVolume = new BoundingSphere();
        this._boundingVolumeDirty = false;

        this._colorCommands = [];
        this._pickCommands = [];

        /**
         * The 4x4 transformation matrix that transforms each billboard in this collection from model to world coordinates.
         * When this is the identity matrix, the billboards are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type {Matrix4}
         * @default {@link Matrix4.IDENTITY}
         *
         *
         * @example
         * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
         * billboards.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
         * billboards.add({
         *   image : 'url/to/image',
         *   position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
         * });
         * billboards.add({
         *   image : 'url/to/image',
         *   position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
         * });
         * billboards.add({
         *   image : 'url/to/image',
         *   position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
         * });
         * billboards.add({
         *   image : 'url/to/image',
         *   position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
         * });
         *
         * @see Transforms.eastNorthUpToFixedFrame
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * The billboard blending option. The default is used for rendering both opaque and translucent billboards.
         * However, if either all of the billboards are completely opaque or all are completely translucent,
         * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve
         * performance by up to 2x.
         * @type {BlendOption}
         * @default BlendOption.OPAQUE_AND_TRANSLUCENT
         */
        this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT);
        this._blendOption = undefined;

        this._mode = SceneMode.SCENE3D;

        // The buffer usage for each attribute is determined based on the usage of the attribute over time.
        this._buffersUsage = [
                              BufferUsage.STATIC_DRAW, // SHOW_INDEX
                              BufferUsage.STATIC_DRAW, // POSITION_INDEX
                              BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_INDEX
                              BufferUsage.STATIC_DRAW, // EYE_OFFSET_INDEX
                              BufferUsage.STATIC_DRAW, // HORIZONTAL_ORIGIN_INDEX
                              BufferUsage.STATIC_DRAW, // VERTICAL_ORIGIN_INDEX
                              BufferUsage.STATIC_DRAW, // SCALE_INDEX
                              BufferUsage.STATIC_DRAW, // IMAGE_INDEX_INDEX
                              BufferUsage.STATIC_DRAW, // COLOR_INDEX
                              BufferUsage.STATIC_DRAW, // ROTATION_INDEX
                              BufferUsage.STATIC_DRAW, // ALIGNED_AXIS_INDEX
                              BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
                              BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
                              BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX
                              BufferUsage.STATIC_DRAW  // DISTANCE_DISPLAY_CONDITION_INDEX
                          ];

        var that = this;
        this._uniforms = {
            u_atlas : function() {
                return that._textureAtlas.texture;
            }
        };

        var scene = this._scene;
        if (defined(scene)) {
            this._removeCallbackFunc = scene.terrainProviderChanged.addEventListener(function() {
                var billboards = this._billboards;
                var length = billboards.length;
                for (var i=0;i<length;++i) {
                    billboards[i]._updateClamping();
                }
            }, this);
        }
    }

    defineProperties(BillboardCollection.prototype, {
        /**
         * Returns the number of billboards in this collection.  This is commonly used with
         * {@link BillboardCollection#get} to iterate over all the billboards
         * in the collection.
         * @memberof BillboardCollection.prototype
         * @type {Number}
         */
        length : {
            get : function() {
                removeBillboards(this);
                return this._billboards.length;
            }
        },

        /**
         * Gets or sets the textureAtlas.
         * @memberof BillboardCollection.prototype
         * @type {TextureAtlas}
         * @private
         */
        textureAtlas : {
            get : function() {
                return this._textureAtlas;
            },
            set : function(value) {
                if (this._textureAtlas !== value) {
                    this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy();
                    this._textureAtlas = value;
                    this._createVertexArray = true; // New per-billboard texture coordinates
                }
            }
        },

        /**
         * Gets or sets a value which determines if the texture atlas is
         * destroyed when the collection is destroyed.
         *
         * If the texture atlas is used by more than one collection, set this to <code>false</code>,
         * and explicitly destroy the atlas to avoid attempting to destroy it multiple times.
         *
         * @memberof BillboardCollection.prototype
         * @type {Boolean}
         * @private
         *
         * @example
         * // Set destroyTextureAtlas
         * // Destroy a billboard collection but not its texture atlas.
         *
         * var atlas = new TextureAtlas({
         *   scene : scene,
         *   images : images
         * });
         * billboards.textureAtlas = atlas;
         * billboards.destroyTextureAtlas = false;
         * billboards = billboards.destroy();
         * console.log(atlas.isDestroyed()); // False
         */
        destroyTextureAtlas : {
            get : function() {
                return this._destroyTextureAtlas;
            },
            set : function(value) {
                this._destroyTextureAtlas = value;
            }
        }
    });

    function destroyBillboards(billboards) {
        var length = billboards.length;
        for (var i = 0; i < length; ++i) {
            if (billboards[i]) {
                billboards[i]._destroy();
            }
        }
    }

    /**
     * Creates and adds a billboard with the specified initial properties to the collection.
     * The added billboard is returned so it can be modified or removed from the collection later.
     *
     * @param {Object}[billboard] A template describing the billboard's properties as shown in Example 1.
     * @returns {Billboard} The billboard that was added to the collection.
     *
     * @performance Calling <code>add</code> is expected constant time.  However, the collection's vertex buffer
     * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.  For
     * best performance, add as many billboards as possible before calling <code>update</code>.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Example 1:  Add a billboard, specifying all the default values.
     * var b = billboards.add({
     *   show : true,
     *   position : Cesium.Cartesian3.ZERO,
     *   pixelOffset : Cesium.Cartesian2.ZERO,
     *   eyeOffset : Cesium.Cartesian3.ZERO,
     *   heightReference : Cesium.HeightReference.NONE,
     *   horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
     *   verticalOrigin : Cesium.VerticalOrigin.CENTER,
     *   scale : 1.0,
     *   image : 'url/to/image',
     *   imageSubRegion : undefined,
     *   color : Cesium.Color.WHITE,
     *   id : undefined,
     *   rotation : 0.0,
     *   alignedAxis : Cesium.Cartesian3.ZERO,
     *   width : undefined,
     *   height : undefined,
     *   scaleByDistance : undefined,
     *   translucencyByDistance : undefined,
     *   pixelOffsetScaleByDistance : undefined,
     *   sizeInMeters : false,
     *   distanceDisplayCondition : undefined
     * });
     *
     * @example
     * // Example 2:  Specify only the billboard's cartographic position.
     * var b = billboards.add({
     *   position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
     * });
     *
     * @see BillboardCollection#remove
     * @see BillboardCollection#removeAll
     */
    BillboardCollection.prototype.add = function(billboard) {
        var b = new Billboard(billboard, this);
        b._index = this._billboards.length;

        this._billboards.push(b);
        this._createVertexArray = true;

        return b;
    };

    /**
     * Removes a billboard from the collection.
     *
     * @param {Billboard} billboard The billboard to remove.
     * @returns {Boolean} <code>true</code> if the billboard was removed; <code>false</code> if the billboard was not found in the collection.
     *
     * @performance Calling <code>remove</code> is expected constant time.  However, the collection's vertex buffer
     * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.  For
     * best performance, remove as many billboards as possible before calling <code>update</code>.
     * If you intend to temporarily hide a billboard, it is usually more efficient to call
     * {@link Billboard#show} instead of removing and re-adding the billboard.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * var b = billboards.add(...);
     * billboards.remove(b);  // Returns true
     *
     * @see BillboardCollection#add
     * @see BillboardCollection#removeAll
     * @see Billboard#show
     */
    BillboardCollection.prototype.remove = function(billboard) {
        if (this.contains(billboard)) {
            this._billboards[billboard._index] = null; // Removed later
            this._billboardsRemoved = true;
            this._createVertexArray = true;
            billboard._destroy();
            return true;
        }

        return false;
    };

    /**
     * Removes all billboards from the collection.
     *
     * @performance <code>O(n)</code>.  It is more efficient to remove all the billboards
     * from a collection and then add new ones than to create a new collection entirely.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * billboards.add(...);
     * billboards.add(...);
     * billboards.removeAll();
     *
     * @see BillboardCollection#add
     * @see BillboardCollection#remove
     */
    BillboardCollection.prototype.removeAll = function() {
        destroyBillboards(this._billboards);
        this._billboards = [];
        this._billboardsToUpdate = [];
        this._billboardsToUpdateIndex = 0;
        this._billboardsRemoved = false;

        this._createVertexArray = true;
    };

    function removeBillboards(billboardCollection) {
        if (billboardCollection._billboardsRemoved) {
            billboardCollection._billboardsRemoved = false;

            var newBillboards = [];
            var billboards = billboardCollection._billboards;
            var length = billboards.length;
            for (var i = 0, j = 0; i < length; ++i) {
                var billboard = billboards[i];
                if (billboard) {
                    billboard._index = j++;
                    newBillboards.push(billboard);
                }
            }

            billboardCollection._billboards = newBillboards;
        }
    }

    BillboardCollection.prototype._updateBillboard = function(billboard, propertyChanged) {
        if (!billboard._dirty) {
            this._billboardsToUpdate[this._billboardsToUpdateIndex++] = billboard;
        }

        ++this._propertiesChanged[propertyChanged];
    };

    /**
     * Check whether this collection contains a given billboard.
     *
     * @param {Billboard} [billboard] The billboard to check for.
     * @returns {Boolean} true if this collection contains the billboard, false otherwise.
     *
     * @see BillboardCollection#get
     */
    BillboardCollection.prototype.contains = function(billboard) {
        return defined(billboard) && billboard._billboardCollection === this;
    };

    /**
     * Returns the billboard in the collection at the specified index.  Indices are zero-based
     * and increase as billboards are added.  Removing a billboard shifts all billboards after
     * it to the left, changing their indices.  This function is commonly used with
     * {@link BillboardCollection#length} to iterate over all the billboards
     * in the collection.
     *
     * @param {Number} index The zero-based index of the billboard.
     * @returns {Billboard} The billboard at the specified index.
     *
     * @performance Expected constant time.  If billboards were removed from the collection and
     * {@link BillboardCollection#update} was not called, an implicit <code>O(n)</code>
     * operation is performed.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Toggle the show property of every billboard in the collection
     * var len = billboards.length;
     * for (var i = 0; i < len; ++i) {
     *   var b = billboards.get(i);
     *   b.show = !b.show;
     * }
     *
     * @see BillboardCollection#length
     */
    BillboardCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        removeBillboards(this);
        return this._billboards[index];
    };

    var getIndexBuffer;

    function getIndexBufferBatched(context) {
        var sixteenK = 16 * 1024;

        var indexBuffer = context.cache.billboardCollection_indexBufferBatched;
        if (defined(indexBuffer)) {
            return indexBuffer;
        }

        // Subtract 6 because the last index is reserverd for primitive restart.
        // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.18
        var length = sixteenK * 6 - 6;
        var indices = new Uint16Array(length);
        for (var i = 0, j = 0; i < length; i += 6, j += 4) {
            indices[i] = j;
            indices[i + 1] = j + 1;
            indices[i + 2] = j + 2;

            indices[i + 3] = j + 0;
            indices[i + 4] = j + 2;
            indices[i + 5] = j + 3;
        }

        // PERFORMANCE_IDEA:  Should we reference count billboard collections, and eventually delete this?
        // Is this too much memory to allocate up front?  Should we dynamically grow it?
        indexBuffer = Buffer.createIndexBuffer({
            context : context,
            typedArray : indices,
            usage : BufferUsage.STATIC_DRAW,
            indexDatatype : IndexDatatype.UNSIGNED_SHORT
        });
        indexBuffer.vertexArrayDestroyable = false;
        context.cache.billboardCollection_indexBufferBatched = indexBuffer;
        return indexBuffer;
    }

    function getIndexBufferInstanced(context) {
        var indexBuffer = context.cache.billboardCollection_indexBufferInstanced;
        if (defined(indexBuffer)) {
            return indexBuffer;
        }

        indexBuffer = Buffer.createIndexBuffer({
            context : context,
            typedArray : new Uint16Array([0, 1, 2, 0, 2, 3]),
            usage : BufferUsage.STATIC_DRAW,
            indexDatatype : IndexDatatype.UNSIGNED_SHORT
        });

        indexBuffer.vertexArrayDestroyable = false;
        context.cache.billboardCollection_indexBufferInstanced = indexBuffer;
        return indexBuffer;
    }

    function getVertexBufferInstanced(context) {
        var vertexBuffer = context.cache.billboardCollection_vertexBufferInstanced;
        if (defined(vertexBuffer)) {
            return vertexBuffer;
        }

        vertexBuffer = Buffer.createVertexBuffer({
            context : context,
            typedArray : new Float32Array([0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0]),
            usage : BufferUsage.STATIC_DRAW
        });

        vertexBuffer.vertexArrayDestroyable = false;
        context.cache.billboardCollection_vertexBufferInstanced = vertexBuffer;
        return vertexBuffer;
    }

    BillboardCollection.prototype.computeNewBuffersUsage = function() {
        var buffersUsage = this._buffersUsage;
        var usageChanged = false;

        var properties = this._propertiesChanged;
        for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
            var newUsage = (properties[k] === 0) ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
            usageChanged = usageChanged || (buffersUsage[k] !== newUsage);
            buffersUsage[k] = newUsage;
        }

        return usageChanged;
    };

    function createVAF(context, numberOfBillboards, buffersUsage, instanced) {
        var attributes = [{
            index : attributeLocations.positionHighAndScale,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[POSITION_INDEX]
        }, {
            index : attributeLocations.positionLowAndRotation,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[POSITION_INDEX]
        }, {
            index : attributeLocations.compressedAttribute0,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[PIXEL_OFFSET_INDEX]
        }, {
            index : attributeLocations.compressedAttribute1,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX]
        }, {
            index : attributeLocations.compressedAttribute2,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[COLOR_INDEX]
        }, {
            index : attributeLocations.eyeOffset,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[EYE_OFFSET_INDEX]
        }, {
            index : attributeLocations.scaleByDistance,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
        }, {
            index : attributeLocations.pixelOffsetScaleByDistance,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]
        }, {
            index : attributeLocations.distanceDisplayCondition,
            componentsPerAttribute : 2,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX]
        }];

        // Instancing requires one non-instanced attribute.
        if (instanced) {
            attributes.push({
                index : attributeLocations.direction,
                componentsPerAttribute : 2,
                componentDatatype : ComponentDatatype.FLOAT,
                vertexBuffer : getVertexBufferInstanced(context)
            });
        }

        // When instancing is enabled, only one vertex is needed for each billboard.
        var sizeInVertices = instanced ? numberOfBillboards : 4 * numberOfBillboards;
        return new VertexArrayFacade(context, attributes, sizeInVertices, instanced);
    }

    ///////////////////////////////////////////////////////////////////////////

    // Four vertices per billboard.  Each has the same position, etc., but a different screen-space direction vector.

    // PERFORMANCE_IDEA:  Save memory if a property is the same for all billboards, use a latched attribute state,
    // instead of storing it in a vertex buffer.

    var writePositionScratch = new EncodedCartesian3();

    function writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var positionHighWriter = vafWriters[attributeLocations.positionHighAndScale];
        var positionLowWriter = vafWriters[attributeLocations.positionLowAndRotation];
        var position = billboard._getActualPosition();

        if (billboardCollection._mode === SceneMode.SCENE3D) {
            BoundingSphere.expand(billboardCollection._baseVolume, position, billboardCollection._baseVolume);
            billboardCollection._boundingVolumeDirty = true;
        }

        EncodedCartesian3.fromCartesian(position, writePositionScratch);
        var scale = billboard.scale;
        var rotation = billboard.rotation;

        if (rotation !== 0.0) {
            billboardCollection._shaderRotation = true;
        }

        billboardCollection._maxScale = Math.max(billboardCollection._maxScale, scale);

        var high = writePositionScratch.high;
        var low = writePositionScratch.low;

        if (billboardCollection._instanced) {
            i = billboard._index;
            positionHighWriter(i, high.x, high.y, high.z, scale);
            positionLowWriter(i, low.x, low.y, low.z, rotation);
        } else {
            i = billboard._index * 4;
            positionHighWriter(i + 0, high.x, high.y, high.z, scale);
            positionHighWriter(i + 1, high.x, high.y, high.z, scale);
            positionHighWriter(i + 2, high.x, high.y, high.z, scale);
            positionHighWriter(i + 3, high.x, high.y, high.z, scale);

            positionLowWriter(i + 0, low.x, low.y, low.z, rotation);
            positionLowWriter(i + 1, low.x, low.y, low.z, rotation);
            positionLowWriter(i + 2, low.x, low.y, low.z, rotation);
            positionLowWriter(i + 3, low.x, low.y, low.z, rotation);
        }
    }

    var scratchCartesian2 = new Cartesian2();

    var UPPER_BOUND = 32768.0;  // 2^15

    var LEFT_SHIFT16 = 65536.0; // 2^16
    var LEFT_SHIFT8 = 256.0;    // 2^8
    var LEFT_SHIFT7 = 128.0;
    var LEFT_SHIFT5 = 32.0;
    var LEFT_SHIFT3 = 8.0;
    var LEFT_SHIFT2 = 4.0;

    var RIGHT_SHIFT8 = 1.0 / 256.0;

    var LOWER_LEFT = 0.0;
    var LOWER_RIGHT = 2.0;
    var UPPER_RIGHT = 3.0;
    var UPPER_LEFT = 1.0;

    function writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.compressedAttribute0];
        var pixelOffset = billboard.pixelOffset;
        var pixelOffsetX = pixelOffset.x;
        var pixelOffsetY = pixelOffset.y;

        var translate = billboard._translate;
        var translateX = translate.x;
        var translateY = translate.y;

        billboardCollection._maxPixelOffset = Math.max(billboardCollection._maxPixelOffset, Math.abs(pixelOffsetX + translateX), Math.abs(-pixelOffsetY + translateY));

        var horizontalOrigin = billboard.horizontalOrigin;
        var verticalOrigin = billboard._verticalOrigin;
        var show = billboard.show && billboard.clusterShow;

        // If the color alpha is zero, do not show this billboard.  This lets us avoid providing
        // color during the pick pass and also eliminates a discard in the fragment shader.
        if (billboard.color.alpha === 0.0) {
            show = false;
        }

        // Raw billboards don't distinguish between BASELINE and BOTTOM, only LabelCollection does that.
        if (verticalOrigin === VerticalOrigin.BASELINE) {
            verticalOrigin = VerticalOrigin.BOTTOM;
        }

        billboardCollection._allHorizontalCenter = billboardCollection._allHorizontalCenter && horizontalOrigin === HorizontalOrigin.CENTER;
        billboardCollection._allVerticalCenter = billboardCollection._allVerticalCenter && verticalOrigin === VerticalOrigin.CENTER;

        var bottomLeftX = 0;
        var bottomLeftY = 0;
        var width = 0;
        var height = 0;
        var index = billboard._imageIndex;
        if (index !== -1) {
            var imageRectangle = textureAtlasCoordinates[index];

                        if (!defined(imageRectangle)) {
                throw new DeveloperError('Invalid billboard image index: ' + index);
            }
            
            bottomLeftX = imageRectangle.x;
            bottomLeftY = imageRectangle.y;
            width = imageRectangle.width;
            height = imageRectangle.height;
        }
        var topRightX = bottomLeftX + width;
        var topRightY = bottomLeftY + height;

        var compressed0 = Math.floor(CesiumMath.clamp(pixelOffsetX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT7;
        compressed0 += (horizontalOrigin + 1.0) * LEFT_SHIFT5;
        compressed0 += (verticalOrigin + 1.0) * LEFT_SHIFT3;
        compressed0 += (show ? 1.0 : 0.0) * LEFT_SHIFT2;

        var compressed1 = Math.floor(CesiumMath.clamp(pixelOffsetY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;
        var compressed2 = Math.floor(CesiumMath.clamp(translateX, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * LEFT_SHIFT8;

        var tempTanslateY = (CesiumMath.clamp(translateY, -UPPER_BOUND, UPPER_BOUND) + UPPER_BOUND) * RIGHT_SHIFT8;
        var upperTranslateY = Math.floor(tempTanslateY);
        var lowerTranslateY = Math.floor((tempTanslateY - upperTranslateY) * LEFT_SHIFT8);

        compressed1 += upperTranslateY;
        compressed2 += lowerTranslateY;

        scratchCartesian2.x = bottomLeftX;
        scratchCartesian2.y = bottomLeftY;
        var compressedTexCoordsLL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
        scratchCartesian2.x = topRightX;
        var compressedTexCoordsLR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
        scratchCartesian2.y = topRightY;
        var compressedTexCoordsUR = AttributeCompression.compressTextureCoordinates(scratchCartesian2);
        scratchCartesian2.x = bottomLeftX;
        var compressedTexCoordsUL = AttributeCompression.compressTextureCoordinates(scratchCartesian2);

        if (billboardCollection._instanced) {
            i = billboard._index;
            writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL);
        } else {
            i = billboard._index * 4;
            writer(i + 0, compressed0 + LOWER_LEFT, compressed1, compressed2, compressedTexCoordsLL);
            writer(i + 1, compressed0 + LOWER_RIGHT, compressed1, compressed2, compressedTexCoordsLR);
            writer(i + 2, compressed0 + UPPER_RIGHT, compressed1, compressed2, compressedTexCoordsUR);
            writer(i + 3, compressed0 + UPPER_LEFT, compressed1, compressed2, compressedTexCoordsUL);
        }
    }

    function writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.compressedAttribute1];
        var alignedAxis = billboard.alignedAxis;
        if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) {
            billboardCollection._shaderAlignedAxis = true;
        }

        var near = 0.0;
        var nearValue = 1.0;
        var far = 1.0;
        var farValue = 1.0;

        var translucency = billboard.translucencyByDistance;
        if (defined(translucency)) {
            near = translucency.near;
            nearValue = translucency.nearValue;
            far = translucency.far;
            farValue = translucency.farValue;

            if (nearValue !== 1.0 || farValue !== 1.0) {
                // translucency by distance calculation in shader need not be enabled
                // until a billboard with near and far !== 1.0 is found
                billboardCollection._shaderTranslucencyByDistance = true;
            }
        }

        var width = 0;
        var index = billboard._imageIndex;
        if (index !== -1) {
            var imageRectangle = textureAtlasCoordinates[index];

                        if (!defined(imageRectangle)) {
                throw new DeveloperError('Invalid billboard image index: ' + index);
            }
            
            width = imageRectangle.width;
        }

        var textureWidth = billboardCollection._textureAtlas.texture.width;
        var imageWidth = Math.round(defaultValue(billboard.width, textureWidth * width));
        billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageWidth);

        var compressed0 = CesiumMath.clamp(imageWidth, 0.0, LEFT_SHIFT16);
        var compressed1 = 0.0;

        if (Math.abs(Cartesian3.magnitudeSquared(alignedAxis) - 1.0) < CesiumMath.EPSILON6) {
            compressed1 = AttributeCompression.octEncodeFloat(alignedAxis);
        }

        nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
        nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
        compressed0 = compressed0 * LEFT_SHIFT8 + nearValue;

        farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
        farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
        compressed1 = compressed1 * LEFT_SHIFT8 + farValue;

        if (billboardCollection._instanced) {
            i = billboard._index;
            writer(i, compressed0, compressed1, near, far);
        } else {
            i = billboard._index * 4;
            writer(i + 0, compressed0, compressed1, near, far);
            writer(i + 1, compressed0, compressed1, near, far);
            writer(i + 2, compressed0, compressed1, near, far);
            writer(i + 3, compressed0, compressed1, near, far);
        }
    }

    function writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.compressedAttribute2];
        var color = billboard.color;
        var pickColor = billboard.getPickId(context).color;
        var sizeInMeters = billboard.sizeInMeters ? 1.0 : 0.0;
        var validAlignedAxis = Math.abs(Cartesian3.magnitudeSquared(billboard.alignedAxis) - 1.0) < CesiumMath.EPSILON6 ? 1.0 : 0.0;

        billboardCollection._allSizedInMeters = billboardCollection._allSizedInMeters && sizeInMeters === 1.0;

        var height = 0;
        var index = billboard._imageIndex;
        if (index !== -1) {
            var imageRectangle = textureAtlasCoordinates[index];

                        if (!defined(imageRectangle)) {
                throw new DeveloperError('Invalid billboard image index: ' + index);
            }
            
            height = imageRectangle.height;
        }

        var dimensions = billboardCollection._textureAtlas.texture.dimensions;
        var imageHeight = Math.round(defaultValue(billboard.height, dimensions.y * height));
        billboardCollection._maxSize = Math.max(billboardCollection._maxSize, imageHeight);

        var red = Color.floatToByte(color.red);
        var green = Color.floatToByte(color.green);
        var blue = Color.floatToByte(color.blue);
        var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;

        red = Color.floatToByte(pickColor.red);
        green = Color.floatToByte(pickColor.green);
        blue = Color.floatToByte(pickColor.blue);
        var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;

        var compressed2 = Color.floatToByte(color.alpha) * LEFT_SHIFT16 + Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8;
        compressed2 += sizeInMeters * 2.0 + validAlignedAxis;

        if (billboardCollection._instanced) {
            i = billboard._index;
            writer(i, compressed0, compressed1, compressed2, imageHeight);
        } else {
            i = billboard._index * 4;
            writer(i + 0, compressed0, compressed1, compressed2, imageHeight);
            writer(i + 1, compressed0, compressed1, compressed2, imageHeight);
            writer(i + 2, compressed0, compressed1, compressed2, imageHeight);
            writer(i + 3, compressed0, compressed1, compressed2, imageHeight);
        }
    }

    function writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.eyeOffset];
        var eyeOffset = billboard.eyeOffset;

        // For billboards that are clamped to ground, move it slightly closer to the camera
        var eyeOffsetZ = eyeOffset.z;
        if (billboard._heightReference !== HeightReference.NONE) {
            eyeOffsetZ *= 1.005;
        }
        billboardCollection._maxEyeOffset = Math.max(billboardCollection._maxEyeOffset, Math.abs(eyeOffset.x), Math.abs(eyeOffset.y), Math.abs(eyeOffsetZ));

        if (billboardCollection._instanced) {
            var width = 0;
            var height = 0;
            var index = billboard._imageIndex;
            if (index !== -1) {
                var imageRectangle = textureAtlasCoordinates[index];

                                if (!defined(imageRectangle)) {
                    throw new DeveloperError('Invalid billboard image index: ' + index);
                }
                
                width = imageRectangle.width;
                height = imageRectangle.height;
            }

            scratchCartesian2.x = width;
            scratchCartesian2.y = height;
            var compressedTexCoordsRange = AttributeCompression.compressTextureCoordinates(scratchCartesian2);

            i = billboard._index;
            writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange);
        } else {
            i = billboard._index * 4;
            writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
            writer(i + 1, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
            writer(i + 2, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
            writer(i + 3, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
        }
    }

    function writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.scaleByDistance];
        var near = 0.0;
        var nearValue = 1.0;
        var far = 1.0;
        var farValue = 1.0;

        var scale = billboard.scaleByDistance;
        if (defined(scale)) {
            near = scale.near;
            nearValue = scale.nearValue;
            far = scale.far;
            farValue = scale.farValue;

            if (nearValue !== 1.0 || farValue !== 1.0) {
                // scale by distance calculation in shader need not be enabled
                // until a billboard with near and far !== 1.0 is found
                billboardCollection._shaderScaleByDistance = true;
            }
        }

        if (billboardCollection._instanced) {
            i = billboard._index;
            writer(i, near, nearValue, far, farValue);
        } else {
            i = billboard._index * 4;
            writer(i + 0, near, nearValue, far, farValue);
            writer(i + 1, near, nearValue, far, farValue);
            writer(i + 2, near, nearValue, far, farValue);
            writer(i + 3, near, nearValue, far, farValue);
        }
    }

    function writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance];
        var near = 0.0;
        var nearValue = 1.0;
        var far = 1.0;
        var farValue = 1.0;

        var pixelOffsetScale = billboard.pixelOffsetScaleByDistance;
        if (defined(pixelOffsetScale)) {
            near = pixelOffsetScale.near;
            nearValue = pixelOffsetScale.nearValue;
            far = pixelOffsetScale.far;
            farValue = pixelOffsetScale.farValue;

            if (nearValue !== 1.0 || farValue !== 1.0) {
                // pixelOffsetScale by distance calculation in shader need not be enabled
                // until a billboard with near and far !== 1.0 is found
                billboardCollection._shaderPixelOffsetScaleByDistance = true;
            }
        }

        if (billboardCollection._instanced) {
            i = billboard._index;
            writer(i, near, nearValue, far, farValue);
        } else {
            i = billboard._index * 4;
            writer(i + 0, near, nearValue, far, farValue);
            writer(i + 1, near, nearValue, far, farValue);
            writer(i + 2, near, nearValue, far, farValue);
            writer(i + 3, near, nearValue, far, farValue);
        }
    }

    function writeDistanceDisplayCondition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        var i;
        var writer = vafWriters[attributeLocations.distanceDisplayCondition];
        var near = 0.0;
        var far = Number.MAX_VALUE;

        var distanceDisplayCondition = billboard.distanceDisplayCondition;
        if (defined(distanceDisplayCondition)) {
            near = distanceDisplayCondition.near;
            far = distanceDisplayCondition.far;

            billboardCollection._shaderDistanceDisplayCondition = true;
        }

        if (billboardCollection._instanced) {
            i = billboard._index;
            writer(i, near, far);
        } else {
            i = billboard._index * 4;
            writer(i + 0, near, far);
            writer(i + 1, near, far);
            writer(i + 2, near, far);
            writer(i + 3, near, far);
        }
    }

    function writeBillboard(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard) {
        writePositionScaleAndRotation(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writeCompressedAttrib0(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writeCompressedAttrib1(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writeCompressedAttrib2(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writeEyeOffset(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writeScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writePixelOffsetScaleByDistance(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
        writeDistanceDisplayCondition(billboardCollection, context, textureAtlasCoordinates, vafWriters, billboard);
    }

    function recomputeActualPositions(billboardCollection, billboards, length, frameState, modelMatrix, recomputeBoundingVolume) {
        var boundingVolume;
        if (frameState.mode === SceneMode.SCENE3D) {
            boundingVolume = billboardCollection._baseVolume;
            billboardCollection._boundingVolumeDirty = true;
        } else {
            boundingVolume = billboardCollection._baseVolume2D;
        }

        var positions = [];
        for ( var i = 0; i < length; ++i) {
            var billboard = billboards[i];
            var position = billboard.position;
            var actualPosition = Billboard._computeActualPosition(billboard, position, frameState, modelMatrix);
            if (defined(actualPosition)) {
                billboard._setActualPosition(actualPosition);

                if (recomputeBoundingVolume) {
                    positions.push(actualPosition);
                } else {
                    BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume);
                }
            }
        }

        if (recomputeBoundingVolume) {
            BoundingSphere.fromPoints(positions, boundingVolume);
        }
    }

    function updateMode(billboardCollection, frameState) {
        var mode = frameState.mode;

        var billboards = billboardCollection._billboards;
        var billboardsToUpdate = billboardCollection._billboardsToUpdate;
        var modelMatrix = billboardCollection._modelMatrix;

        if (billboardCollection._createVertexArray ||
            billboardCollection._mode !== mode ||
            mode !== SceneMode.SCENE3D &&
            !Matrix4.equals(modelMatrix, billboardCollection.modelMatrix)) {

            billboardCollection._mode = mode;
            Matrix4.clone(billboardCollection.modelMatrix, modelMatrix);
            billboardCollection._createVertexArray = true;

            if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
                recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true);
            }
        } else if (mode === SceneMode.MORPHING) {
            recomputeActualPositions(billboardCollection, billboards, billboards.length, frameState, modelMatrix, true);
        } else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
            recomputeActualPositions(billboardCollection, billboardsToUpdate, billboardCollection._billboardsToUpdateIndex, frameState, modelMatrix, false);
        }
    }

    function updateBoundingVolume(collection, frameState, boundingVolume) {
        var pixelScale = 1.0;
        if (!collection._allSizedInMeters || collection._maxPixelOffset !== 0.0) {
            pixelScale = frameState.camera.getPixelSize(boundingVolume, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
        }

        var size = pixelScale * collection._maxScale * collection._maxSize * 2.0;
        if (collection._allHorizontalCenter && collection._allVerticalCenter ) {
            size *= 0.5;
        }

        var offset = pixelScale * collection._maxPixelOffset + collection._maxEyeOffset;
        boundingVolume.radius += size + offset;
    }

    var scratchWriterArray = [];

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {RuntimeError} image with id must be in the atlas.
     */
    BillboardCollection.prototype.update = function(frameState) {
        removeBillboards(this);
        var billboards = this._billboards;
        var billboardsLength = billboards.length;

        var context = frameState.context;
        this._instanced = context.instancedArrays;
        attributeLocations = this._instanced ? attributeLocationsInstanced : attributeLocationsBatched;
        getIndexBuffer = this._instanced ? getIndexBufferInstanced : getIndexBufferBatched;

        var textureAtlas = this._textureAtlas;
        if (!defined(textureAtlas)) {
            textureAtlas = this._textureAtlas = new TextureAtlas({
                context : context
            });

            for (var ii = 0; ii < billboardsLength; ++ii) {
                billboards[ii]._loadImage();
            }
        }

        var textureAtlasCoordinates = textureAtlas.textureCoordinates;
        if (textureAtlasCoordinates.length === 0) {
            // Can't write billboard vertices until we have texture coordinates
            // provided by a texture atlas
            return;
        }

        updateMode(this, frameState);

        billboards = this._billboards;
        billboardsLength = billboards.length;
        var billboardsToUpdate = this._billboardsToUpdate;
        var billboardsToUpdateLength = this._billboardsToUpdateIndex;

        var properties = this._propertiesChanged;

        var textureAtlasGUID = textureAtlas.guid;
        var createVertexArray = this._createVertexArray || this._textureAtlasGUID !== textureAtlasGUID;
        this._textureAtlasGUID = textureAtlasGUID;

        var vafWriters;
        var pass = frameState.passes;
        var picking = pass.pick;

        // PERFORMANCE_IDEA: Round robin multiple buffers.
        if (createVertexArray || (!picking && this.computeNewBuffersUsage())) {
            this._createVertexArray = false;

            for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
                properties[k] = 0;
            }

            this._vaf = this._vaf && this._vaf.destroy();

            if (billboardsLength > 0) {
                // PERFORMANCE_IDEA:  Instead of creating a new one, resize like std::vector.
                this._vaf = createVAF(context, billboardsLength, this._buffersUsage, this._instanced);
                vafWriters = this._vaf.writers;

                // Rewrite entire buffer if billboards were added or removed.
                for (var i = 0; i < billboardsLength; ++i) {
                    var billboard = this._billboards[i];
                    billboard._dirty = false; // In case it needed an update.
                    writeBillboard(this, context, textureAtlasCoordinates, vafWriters, billboard);
                }

                // Different billboard collections share the same index buffer.
                this._vaf.commit(getIndexBuffer(context));
            }

            this._billboardsToUpdateIndex = 0;
        } else {
            // Billboards were modified, but none were added or removed.
            if (billboardsToUpdateLength > 0) {
                var writers = scratchWriterArray;
                writers.length = 0;

                if (properties[POSITION_INDEX] || properties[ROTATION_INDEX] || properties[SCALE_INDEX]) {
                    writers.push(writePositionScaleAndRotation);
                }

                if (properties[IMAGE_INDEX_INDEX] || properties[PIXEL_OFFSET_INDEX] || properties[HORIZONTAL_ORIGIN_INDEX] || properties[VERTICAL_ORIGIN_INDEX] || properties[SHOW_INDEX]) {
                    writers.push(writeCompressedAttrib0);
                    if (this._instanced) {
                        writers.push(writeEyeOffset);
                    }
                }

                if (properties[IMAGE_INDEX_INDEX] || properties[ALIGNED_AXIS_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) {
                    writers.push(writeCompressedAttrib1);
                    writers.push(writeCompressedAttrib2);
                }

                if (properties[IMAGE_INDEX_INDEX] || properties[COLOR_INDEX]) {
                    writers.push(writeCompressedAttrib2);
                }

                if (properties[EYE_OFFSET_INDEX]) {
                    writers.push(writeEyeOffset);
                }

                if (properties[SCALE_BY_DISTANCE_INDEX]) {
                    writers.push(writeScaleByDistance);
                }

                if (properties[PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX]) {
                    writers.push(writePixelOffsetScaleByDistance);
                }

                if (properties[DISTANCE_DISPLAY_CONDITION_INDEX]) {
                    writers.push(writeDistanceDisplayCondition);
                }

                var numWriters = writers.length;
                vafWriters = this._vaf.writers;

                if ((billboardsToUpdateLength / billboardsLength) > 0.1) {
                    // If more than 10% of billboard change, rewrite the entire buffer.

                    // PERFORMANCE_IDEA:  I totally made up 10% :).

                    for (var m = 0; m < billboardsToUpdateLength; ++m) {
                        var b = billboardsToUpdate[m];
                        b._dirty = false;

                        for ( var n = 0; n < numWriters; ++n) {
                            writers[n](this, context, textureAtlasCoordinates, vafWriters, b);
                        }
                    }
                    this._vaf.commit(getIndexBuffer(context));
                } else {
                    for (var h = 0; h < billboardsToUpdateLength; ++h) {
                        var bb = billboardsToUpdate[h];
                        bb._dirty = false;

                        for ( var o = 0; o < numWriters; ++o) {
                            writers[o](this, context, textureAtlasCoordinates, vafWriters, bb);
                        }

                        if (this._instanced) {
                            this._vaf.subCommit(bb._index, 1);
                        } else {
                            this._vaf.subCommit(bb._index * 4, 4);
                        }
                    }
                    this._vaf.endSubCommits();
                }

                this._billboardsToUpdateIndex = 0;
            }
        }

        // If the number of total billboards ever shrinks considerably
        // Truncate billboardsToUpdate so that we free memory that we're
        // not going to be using.
        if (billboardsToUpdateLength > billboardsLength * 1.5) {
            billboardsToUpdate.length = billboardsLength;
        }

        if (!defined(this._vaf) || !defined(this._vaf.va)) {
            return;
        }

        if (this._boundingVolumeDirty) {
            this._boundingVolumeDirty = false;
            BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC);
        }

        var boundingVolume;
        var modelMatrix = Matrix4.IDENTITY;
        if (frameState.mode === SceneMode.SCENE3D) {
            modelMatrix = this.modelMatrix;
            boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume);
        } else {
            boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume);
        }
        updateBoundingVolume(this, frameState, boundingVolume);

        var blendOptionChanged = this._blendOption !== this.blendOption;
        this._blendOption = this.blendOption;

        if (blendOptionChanged) {
            if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
                this._rsOpaque = RenderState.fromCache({
                    depthTest : {
                        enabled : true,
                        func : WebGLConstants.LEQUAL  // Allows label glyphs and billboards to overlap.
                    },
                    depthMask : true
                });
            } else {
                this._rsOpaque = undefined;
            }

            if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
                this._rsTranslucent = RenderState.fromCache({
                    depthTest : {
                        enabled : true,
                        func : WebGLConstants.LEQUAL  // Allows label glyphs and billboards to overlap.
                    },
                    depthMask : this._blendOption === BlendOption.TRANSLUCENT,
                    blending : BlendingState.ALPHA_BLEND
                });
            } else {
                this._rsTranslucent = undefined;
            }
        }

        if (blendOptionChanged ||
            (this._shaderRotation !== this._compiledShaderRotation) ||
            (this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) ||
            (this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) ||
            (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) ||
            (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) ||
            (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition)) {

            vs = new ShaderSource({
                sources : [BillboardCollectionVS]
            });
            if (this._instanced) {
                vs.defines.push('INSTANCED');
            }
            if (this._shaderRotation) {
                vs.defines.push('ROTATION');
            }
            if (this._shaderAlignedAxis) {
                vs.defines.push('ALIGNED_AXIS');
            }
            if (this._shaderScaleByDistance) {
                vs.defines.push('EYE_DISTANCE_SCALING');
            }
            if (this._shaderTranslucencyByDistance) {
                vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
            }
            if (this._shaderPixelOffsetScaleByDistance) {
                vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET');
            }
            if (this._shaderDistanceDisplayCondition) {
                vs.defines.push('DISTANCE_DISPLAY_CONDITION');
            }

            if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
                fs = new ShaderSource({
                    defines : ['OPAQUE'],
                    sources : [BillboardCollectionFS]
                });
                this._sp = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._sp,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });

                fs = new ShaderSource({
                    defines : ['TRANSLUCENT'],
                    sources : [BillboardCollectionFS]
                });
                this._spTranslucent = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._spTranslucent,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });
            }

            if (this._blendOption === BlendOption.OPAQUE) {
                fs = new ShaderSource({
                    sources : [BillboardCollectionFS]
                });
                this._sp = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._sp,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });
            }

            if (this._blendOption === BlendOption.TRANSLUCENT) {
                fs = new ShaderSource({
                    sources : [BillboardCollectionFS]
                });
                this._spTranslucent = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._spTranslucent,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });
            }

            this._compiledShaderRotation = this._shaderRotation;
            this._compiledShaderAlignedAxis = this._shaderAlignedAxis;
            this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
            this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance;
            this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance;
            this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition;
        }

        if (!defined(this._spPick) ||
            (this._shaderRotation !== this._compiledShaderRotationPick) ||
            (this._shaderAlignedAxis !== this._compiledShaderAlignedAxisPick) ||
            (this._shaderScaleByDistance !== this._compiledShaderScaleByDistancePick) ||
            (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistancePick) ||
            (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistancePick) ||
            (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayConditionPick)) {

            vs = new ShaderSource({
                defines : ['RENDER_FOR_PICK'],
                sources : [BillboardCollectionVS]
            });

            if(this._instanced) {
                vs.defines.push('INSTANCED');
            }
            if (this._shaderRotation) {
                vs.defines.push('ROTATION');
            }
            if (this._shaderAlignedAxis) {
                vs.defines.push('ALIGNED_AXIS');
            }
            if (this._shaderScaleByDistance) {
                vs.defines.push('EYE_DISTANCE_SCALING');
            }
            if (this._shaderTranslucencyByDistance) {
                vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
            }
            if (this._shaderPixelOffsetScaleByDistance) {
                vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET');
            }
            if (this._shaderDistanceDisplayCondition) {
                vs.defines.push('DISTANCE_DISPLAY_CONDITION');
            }

            fs = new ShaderSource({
                defines : ['RENDER_FOR_PICK'],
                sources : [BillboardCollectionFS]
            });

            this._spPick = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : this._spPick,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
            this._compiledShaderRotationPick = this._shaderRotation;
            this._compiledShaderAlignedAxisPick = this._shaderAlignedAxis;
            this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance;
            this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance;
            this._compiledShaderPixelOffsetScaleByDistancePick = this._shaderPixelOffsetScaleByDistance;
            this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition;
        }

        var va;
        var vaLength;
        var command;
        var vs;
        var fs;
        var j;

        var commandList = frameState.commandList;

        if (pass.render) {
            var colorList = this._colorCommands;

            var opaque = this._blendOption === BlendOption.OPAQUE;
            var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT;

            va = this._vaf.va;
            vaLength = va.length;

            colorList.length = vaLength;
            var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength;
            for (j = 0; j < totalLength; ++j) {
                command = colorList[j];
                if (!defined(command)) {
                    command = colorList[j] = new DrawCommand();
                }

                var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0);

                command.pass = opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT;
                command.owner = this;

                var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j;
                command.boundingVolume = boundingVolume;
                command.modelMatrix = modelMatrix;
                command.count = va[index].indicesCount;
                command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent;
                command.uniformMap = this._uniforms;
                command.vertexArray = va[index].va;
                command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent;
                command.debugShowBoundingVolume = this.debugShowBoundingVolume;

                if (this._instanced) {
                    command.count = 6;
                    command.instanceCount = billboardsLength;
                }

                commandList.push(command);
            }
        }

        if (picking) {
            var pickList = this._pickCommands;

            va = this._vaf.va;
            vaLength = va.length;

            pickList.length = vaLength;
            for (j = 0; j < vaLength; ++j) {
                command = pickList[j];
                if (!defined(command)) {
                    command = pickList[j] = new DrawCommand({
                        pass : Pass.OPAQUE,
                        owner : this
                    });
                }

                command.boundingVolume = boundingVolume;
                command.modelMatrix = modelMatrix;
                command.count = va[j].indicesCount;
                command.shaderProgram = this._spPick;
                command.uniformMap = this._uniforms;
                command.vertexArray = va[j].va;
                command.renderState = this._rsOpaque;

                if (this._instanced) {
                    command.count = 6;
                    command.instanceCount = billboardsLength;
                }

                commandList.push(command);
            }
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see BillboardCollection#destroy
     */
    BillboardCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * billboards = billboards && billboards.destroy();
     *
     * @see BillboardCollection#isDestroyed
     */
    BillboardCollection.prototype.destroy = function() {
        if (defined(this._removeCallbackFunc)) {
            this._removeCallbackFunc();
            this._removeCallbackFunc = undefined;
        }

        this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy();
        this._sp = this._sp && this._sp.destroy();
        this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy();
        this._spPick = this._spPick && this._spPick.destroy();
        this._vaf = this._vaf && this._vaf.destroy();
        destroyBillboards(this._billboards);

        return destroyObject(this);
    };

    return BillboardCollection;
});

/*global define*/
define('Scene/LabelStyle',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Describes how to draw a label.
     *
     * @exports LabelStyle
     *
     * @see Label#style
     */
    var LabelStyle = {
        /**
         * Fill the text of the label, but do not outline.
         *
         * @type {Number}
         * @constant
         */
        FILL : 0,

        /**
         * Outline the text of the label, but do not fill.
         *
         * @type {Number}
         * @constant
         */
        OUTLINE : 1,

        /**
         * Fill and outline the text of the label.
         *
         * @type {Number}
         * @constant
         */
        FILL_AND_OUTLINE : 2
    };

    return freezeObject(LabelStyle);
});

/*global define*/
define('Scene/Label',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/NearFarScalar',
        './Billboard',
        './HeightReference',
        './HorizontalOrigin',
        './LabelStyle',
        './VerticalOrigin'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Color,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        DistanceDisplayCondition,
        NearFarScalar,
        Billboard,
        HeightReference,
        HorizontalOrigin,
        LabelStyle,
        VerticalOrigin) {
    'use strict';

    function rebindAllGlyphs(label) {
        if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
            // only push label if it's not already been marked dirty
            label._labelCollection._labelsToUpdate.push(label);
        }
        label._rebindAllGlyphs = true;
    }

    function repositionAllGlyphs(label) {
        if (!label._rebindAllGlyphs && !label._repositionAllGlyphs) {
            // only push label if it's not already been marked dirty
            label._labelCollection._labelsToUpdate.push(label);
        }
        label._repositionAllGlyphs = true;
    }

    /**
     * A Label draws viewport-aligned text positioned in the 3D scene.  This constructor
     * should not be used directly, instead create labels by calling {@link LabelCollection#add}.
     *
     * @alias Label
     * @internalConstructor
     *
     * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
     * @exception {DeveloperError} pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near
     * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
     *
     * @see LabelCollection
     * @see LabelCollection#add
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
     */
    function Label(options, labelCollection) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) {
            throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.');
        }
        if (defined(options.pixelOffsetScaleByDistance) && options.pixelOffsetScaleByDistance.far <= options.pixelOffsetScaleByDistance.near) {
            throw new DeveloperError('pixelOffsetScaleByDistance.far must be greater than pixelOffsetScaleByDistance.near.');
        }
        if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) {
            throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.');
        }
        if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) {
            throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near');
        }
        
        this._text = defaultValue(options.text, '');
        this._show = defaultValue(options.show, true);
        this._font = defaultValue(options.font, '30px sans-serif');
        this._fillColor = Color.clone(defaultValue(options.fillColor, Color.WHITE));
        this._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.BLACK));
        this._outlineWidth = defaultValue(options.outlineWidth, 1.0);
        this._showBackground = defaultValue(options.showBackground, false);
        this._backgroundColor = defaultValue(options.backgroundColor, new Color(0.165, 0.165, 0.165, 0.8));
        this._backgroundPadding = defaultValue(options.backgroundPadding, new Cartesian2(7, 5));
        this._style = defaultValue(options.style, LabelStyle.FILL);
        this._verticalOrigin = defaultValue(options.verticalOrigin, VerticalOrigin.BASELINE);
        this._horizontalOrigin = defaultValue(options.horizontalOrigin, HorizontalOrigin.LEFT);
        this._pixelOffset = Cartesian2.clone(defaultValue(options.pixelOffset, Cartesian2.ZERO));
        this._eyeOffset = Cartesian3.clone(defaultValue(options.eyeOffset, Cartesian3.ZERO));
        this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
        this._scale = defaultValue(options.scale, 1.0);
        this._id = options.id;
        this._translucencyByDistance = options.translucencyByDistance;
        this._pixelOffsetScaleByDistance = options.pixelOffsetScaleByDistance;
        this._scaleByDistance = options.scaleByDistance;
        this._heightReference = defaultValue(options.heightReference, HeightReference.NONE);
        this._distanceDisplayCondition = options.distanceDisplayCondition;

        this._labelCollection = labelCollection;
        this._glyphs = [];
        this._backgroundBillboard = undefined;

        this._rebindAllGlyphs = true;
        this._repositionAllGlyphs = true;

        this._actualClampedPosition = undefined;
        this._removeCallbackFunc = undefined;
        this._mode = undefined;

        this._clusterShow = true;

        this._updateClamping();
    }

    defineProperties(Label.prototype, {
        /**
         * Determines if this label will be shown.  Use this to hide or show a label, instead
         * of removing it and re-adding it to the collection.
         * @memberof Label.prototype
         * @type {Boolean}
         * @default true
         */
        show : {
            get : function() {
                return this._show;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._show !== value) {
                    this._show = value;

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var billboard = glyphs[i].billboard;
                        if (defined(billboard)) {
                            billboard.show = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.show = value;
                    }
                }
            }
        },

        /**
         * Gets or sets the Cartesian position of this label.
         * @memberof Label.prototype
         * @type {Cartesian3}
         */
        position : {
            get : function() {
                return this._position;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var position = this._position;
                if (!Cartesian3.equals(position, value)) {
                    Cartesian3.clone(value, position);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var billboard = glyphs[i].billboard;
                        if (defined(billboard)) {
                            billboard.position = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.position = value;
                    }

                    if (this._heightReference !== HeightReference.NONE) {
                        this._updateClamping();
                    }
                }
            }
        },

        /**
         * Gets or sets the height reference of this billboard.
         * @memberof Label.prototype
         * @type {HeightReference}
         * @default HeightReference.NONE
         */
        heightReference : {
            get : function() {
                return this._heightReference;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (value !== this._heightReference) {
                    this._heightReference = value;

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var billboard = glyphs[i].billboard;
                        if (defined(billboard)) {
                            billboard.heightReference = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.heightReference = value;
                    }

                    repositionAllGlyphs(this);

                    this._updateClamping();
                }
            }
        },

        /**
         * Gets or sets the text of this label.
         * @memberof Label.prototype
         * @type {String}
         */
        text : {
            get : function() {
                return this._text;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._text !== value) {
                    this._text = value;
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the font used to draw this label. Fonts are specified using the same syntax as the CSS 'font' property.
         * @memberof Label.prototype
         * @type {String}
         * @default '30px sans-serif'
         * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles|HTML canvas 2D context text styles}
         */
        font : {
            get : function() {
                return this._font;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._font !== value) {
                    this._font = value;
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the fill color of this label.
         * @memberof Label.prototype
         * @type {Color}
         * @default Color.WHITE
         * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
         */
        fillColor : {
            get : function() {
                return this._fillColor;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var fillColor = this._fillColor;
                if (!Color.equals(fillColor, value)) {
                    Color.clone(value, fillColor);
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the outline color of this label.
         * @memberof Label.prototype
         * @type {Color}
         * @default Color.BLACK
         * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
         */
        outlineColor : {
            get : function() {
                return this._outlineColor;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var outlineColor = this._outlineColor;
                if (!Color.equals(outlineColor, value)) {
                    Color.clone(value, outlineColor);
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the outline width of this label.
         * @memberof Label.prototype
         * @type {Number}
         * @default 1.0
         * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#fill-and-stroke-styles|HTML canvas 2D context fill and stroke styles}
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._outlineWidth !== value) {
                    this._outlineWidth = value;
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Determines if a background behind this label will be shown.
         * @memberof Label.prototype
         * @default false
         * @type {Boolean}
         */
        showBackground : {
            get : function() {
                return this._showBackground;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._showBackground !== value) {
                    this._showBackground = value;
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the background color of this label.
         * @memberof Label.prototype
         * @type {Color}
         * @default new Color(0.165, 0.165, 0.165, 0.8)
         */
        backgroundColor : {
            get : function() {
                return this._backgroundColor;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var backgroundColor = this._backgroundColor;
                if (!Color.equals(backgroundColor, value)) {
                    Color.clone(value, backgroundColor);

                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.color = backgroundColor;
                    }
                }
            }
        },

        /**
         * Gets or sets the background padding, in pixels, of this label.  The <code>x</code> value
         * controls horizontal padding, and the <code>y</code> value controls vertical padding.
         * @memberof Label.prototype
         * @type {Cartesian2}
         * @default new Cartesian2(7, 5)
         */
        backgroundPadding : {
            get : function() {
                return this._backgroundPadding;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var backgroundPadding = this._backgroundPadding;
                if (!Cartesian2.equals(backgroundPadding, value)) {
                    Cartesian2.clone(value, backgroundPadding);
                    repositionAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the style of this label.
         * @memberof Label.prototype
         * @type {LabelStyle}
         * @default LabelStyle.FILL
         */
        style : {
            get : function() {
                return this._style;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._style !== value) {
                    this._style = value;
                    rebindAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the pixel offset in screen space from the origin of this label.  This is commonly used
         * to align multiple labels and billboards at the same position, e.g., an image and text.  The
         * screen space origin is the top, left corner of the canvas; <code>x</code> increases from
         * left to right, and <code>y</code> increases from top to bottom.
         * <br /><br />
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><code>default</code><br/><img src='images/Label.setPixelOffset.default.png' width='250' height='188' /></td>
         * <td align='center'><code>l.pixeloffset = new Cartesian2(25, 75);</code><br/><img src='images/Label.setPixelOffset.x50y-25.png' width='250' height='188' /></td>
         * </tr></table>
         * The label's origin is indicated by the yellow point.
         * </div>
         * @memberof Label.prototype
         * @type {Cartesian2}
         * @default Cartesian2.ZERO
         */
        pixelOffset : {
            get : function() {
                return this._pixelOffset;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var pixelOffset = this._pixelOffset;
                if (!Cartesian2.equals(pixelOffset, value)) {
                    Cartesian2.clone(value, pixelOffset);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.pixelOffset = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.pixelOffset = value;
                    }
                }
            }
        },

        /**
         * Gets or sets near and far translucency properties of a Label based on the Label's distance from the camera.
         * A label's translucency will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the label's translucency remains clamped to the nearest bound.  If undefined,
         * translucencyByDistance will be disabled.
         * @memberof Label.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a label's translucencyByDistance to 1.0 when the
         * // camera is 1500 meters from the label and disappear as
         * // the camera distance approaches 8.0e6 meters.
         * text.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
         *
         * @example
         * // Example 2.
         * // disable translucency by distance
         * text.translucencyByDistance = undefined;
         */
        translucencyByDistance : {
            get : function() {
                return this._translucencyByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var translucencyByDistance = this._translucencyByDistance;
                if (!NearFarScalar.equals(translucencyByDistance, value)) {
                    this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.translucencyByDistance = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.translucencyByDistance = value;
                    }
                }
            }
        },

        /**
         * Gets or sets near and far pixel offset scaling properties of a Label based on the Label's distance from the camera.
         * A label's pixel offset will be scaled between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the label's pixel offset scaling remains clamped to the nearest bound.  If undefined,
         * pixelOffsetScaleByDistance will be disabled.
         * @memberof Label.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a label's pixel offset scale to 0.0 when the
         * // camera is 1500 meters from the label and scale pixel offset to 10.0 pixels
         * // in the y direction the camera distance approaches 8.0e6 meters.
         * text.pixelOffset = new Cesium.Cartesian2(0.0, 1.0);
         * text.pixelOffsetScaleByDistance = new Cesium.NearFarScalar(1.5e2, 0.0, 8.0e6, 10.0);
         *
         * @example
         * // Example 2.
         * // disable pixel offset by distance
         * text.pixelOffsetScaleByDistance = undefined;
         */
        pixelOffsetScaleByDistance : {
            get : function() {
                return this._pixelOffsetScaleByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var pixelOffsetScaleByDistance = this._pixelOffsetScaleByDistance;
                if (!NearFarScalar.equals(pixelOffsetScaleByDistance, value)) {
                    this._pixelOffsetScaleByDistance = NearFarScalar.clone(value, pixelOffsetScaleByDistance);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.pixelOffsetScaleByDistance = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.pixelOffsetScaleByDistance = value;
                    }
                }
            }
        },

        /**
         * Gets or sets near and far scaling properties of a Label based on the label's distance from the camera.
         * A label's scale will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the label's scale remains clamped to the nearest bound.  If undefined,
         * scaleByDistance will be disabled.
         * @memberof Label.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a label's scaleByDistance to scale by 1.5 when the
         * // camera is 1500 meters from the label and disappear as
         * // the camera distance approaches 8.0e6 meters.
         * label.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 1.5, 8.0e6, 0.0);
         *
         * @example
         * // Example 2.
         * // disable scaling by distance
         * label.scaleByDistance = undefined;
         */
        scaleByDistance : {
            get : function() {
                return this._scaleByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var scaleByDistance = this._scaleByDistance;
                if (!NearFarScalar.equals(scaleByDistance, value)) {
                    this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.scaleByDistance = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.scaleByDistance = value;
                    }
                }
            }
        },

        /**
         * Gets and sets the 3D Cartesian offset applied to this label in eye coordinates.  Eye coordinates is a left-handed
         * coordinate system, where <code>x</code> points towards the viewer's right, <code>y</code> points up, and
         * <code>z</code> points into the screen.  Eye coordinates use the same scale as world and model coordinates,
         * which is typically meters.
         * <br /><br />
         * An eye offset is commonly used to arrange multiple label or objects at the same position, e.g., to
         * arrange a label above its corresponding 3D model.
         * <br /><br />
         * Below, the label is positioned at the center of the Earth but an eye offset makes it always
         * appear on top of the Earth regardless of the viewer's or Earth's orientation.
         * <br /><br />
         * <div align='center'>
         * <table border='0' cellpadding='5'><tr>
         * <td align='center'><img src='images/Billboard.setEyeOffset.one.png' width='250' height='188' /></td>
         * <td align='center'><img src='images/Billboard.setEyeOffset.two.png' width='250' height='188' /></td>
         * </tr></table>
         * <code>l.eyeOffset = new Cartesian3(0.0, 8000000.0, 0.0);</code><br /><br />
         * </div>
         * @memberof Label.prototype
         * @type {Cartesian3}
         * @default Cartesian3.ZERO
         */
        eyeOffset : {
            get : function() {
                return this._eyeOffset;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var eyeOffset = this._eyeOffset;
                if (!Cartesian3.equals(eyeOffset, value)) {
                    Cartesian3.clone(value, eyeOffset);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.eyeOffset = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.eyeOffset = value;
                    }
                }
            }
        },

        /**
         * Gets or sets the horizontal origin of this label, which determines if the label is drawn
         * to the left, center, or right of its anchor position.
         * <br /><br />
         * <div align='center'>
         * <img src='images/Billboard.setHorizontalOrigin.png' width='648' height='196' /><br />
         * </div>
         * @memberof Label.prototype
         * @type {HorizontalOrigin}
         * @default HorizontalOrigin.LEFT
         * @example
         * // Use a top, right origin
         * l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT;
         * l.verticalOrigin = Cesium.VerticalOrigin.TOP;
         */
        horizontalOrigin : {
            get : function() {
                return this._horizontalOrigin;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._horizontalOrigin !== value) {
                    this._horizontalOrigin = value;
                    repositionAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the vertical origin of this label, which determines if the label is
         * to the above, below, or at the center of its anchor position.
         * <br /><br />
         * <div align='center'>
         * <img src='images/Billboard.setVerticalOrigin.png' width='695' height='175' /><br />
         * </div>
         * @memberof Label.prototype
         * @type {VerticalOrigin}
         * @default VerticalOrigin.BASELINE
         * @example
         * // Use a top, right origin
         * l.horizontalOrigin = Cesium.HorizontalOrigin.RIGHT;
         * l.verticalOrigin = Cesium.VerticalOrigin.TOP;
         */
        verticalOrigin : {
            get : function() {
                return this._verticalOrigin;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._verticalOrigin !== value) {
                    this._verticalOrigin = value;

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.verticalOrigin = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.verticalOrigin = value;
                    }

                    repositionAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the uniform scale that is multiplied with the label's size in pixels.
         * A scale of <code>1.0</code> does not change the size of the label; a scale greater than
         * <code>1.0</code> enlarges the label; a positive scale less than <code>1.0</code> shrinks
         * the label.
         * <br /><br />
         * Applying a large scale value may pixelate the label.  To make text larger without pixelation,
         * use a larger font size when calling {@link Label#font} instead.
         * <br /><br />
         * <div align='center'>
         * <img src='images/Label.setScale.png' width='400' height='300' /><br/>
         * From left to right in the above image, the scales are <code>0.5</code>, <code>1.0</code>,
         * and <code>2.0</code>.
         * </div>
         * @memberof Label.prototype
         * @type {Number}
         * @default 1.0
         */
        scale : {
            get : function() {
                return this._scale;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._scale !== value) {
                    this._scale = value;

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.scale = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.scale = value;
                    }

                    repositionAllGlyphs(this);
                }
            }
        },

        /**
         * Gets or sets the condition specifying at what distance from the camera that this label will be displayed.
         * @memberof Label.prototype
         * @type {DistanceDisplayCondition}
         * @default undefined
         */
        distanceDisplayCondition : {
            get : function() {
                return this._distanceDisplayCondition;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far must be greater than near');
                }
                                if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
                    this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.distanceDisplayCondition = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.distanceDisplayCondition = value;
                    }
                }
            }
        },

        /**
         * Gets or sets the user-defined object returned when the label is picked.
         * @memberof Label.prototype
         * @type {Object}
         */
        id : {
            get : function() {
                return this._id;
            },
            set : function(value) {
                if (this._id !== value) {
                    this._id = value;

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.id = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.id = value;
                    }
                }
            }
        },

        /**
         * Keeps track of the position of the label based on the height reference.
         * @memberof Label.prototype
         * @type {Cartesian3}
         * @private
         */
        _clampedPosition : {
            get : function() {
                return this._actualClampedPosition;
            },
            set : function(value) {
                this._actualClampedPosition = Cartesian3.clone(value, this._actualClampedPosition);

                var glyphs = this._glyphs;
                value = defaultValue(value, this._position);
                for (var i = 0, len = glyphs.length; i < len; i++) {
                    var glyph = glyphs[i];
                    if (defined(glyph.billboard)) {
                        // Set all the private values here, because we already clamped to ground
                        //  so we don't want to do it again for every glyph
                        glyph.billboard._clampedPosition = value;
                        Cartesian3.clone(value, glyph.billboard._position);
                        Cartesian3.clone(value, glyph.billboard._actualPosition);
                    }
                }
                var backgroundBillboard = this._backgroundBillboard;
                if (defined(backgroundBillboard)) {
                    backgroundBillboard._clampedPosition = value;
                    Cartesian3.clone(value, backgroundBillboard._position);
                    Cartesian3.clone(value, backgroundBillboard._actualPosition);
                }
            }
        },

        /**
         * Determines whether or not this label will be shown or hidden because it was clustered.
         * @memberof Label.prototype
         * @type {Boolean}
         * @default true
         * @private
         */
        clusterShow : {
            get : function() {
                return this._clusterShow;
            },
            set : function(value) {
                if (this._clusterShow !== value) {
                    this._clusterShow = value;

                    var glyphs = this._glyphs;
                    for (var i = 0, len = glyphs.length; i < len; i++) {
                        var glyph = glyphs[i];
                        if (defined(glyph.billboard)) {
                            glyph.billboard.clusterShow = value;
                        }
                    }
                    var backgroundBillboard = this._backgroundBillboard;
                    if (defined(backgroundBillboard)) {
                        backgroundBillboard.clusterShow = value;
                    }
                }
            }
        }
    });

    Label.prototype._updateClamping = function() {
        Billboard._updateClamping(this._labelCollection, this);
    };

    /**
     * Computes the screen-space position of the label's origin, taking into account eye and pixel offsets.
     * The screen space origin is the top, left corner of the canvas; <code>x</code> increases from
     * left to right, and <code>y</code> increases from top to bottom.
     *
     * @param {Scene} scene The scene the label is in.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The screen-space position of the label.
     *
     *
     * @example
     * console.log(l.computeScreenSpacePosition(scene).toString());
     *
     * @see Label#eyeOffset
     * @see Label#pixelOffset
     */
    Label.prototype.computeScreenSpacePosition = function(scene, result) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian2();
        }

        var labelCollection = this._labelCollection;
        var modelMatrix = labelCollection.modelMatrix;
        var actualPosition = defined(this._actualClampedPosition) ? this._actualClampedPosition : this._position;

        var windowCoordinates = Billboard._computeScreenSpacePosition(modelMatrix, actualPosition,
                this._eyeOffset, this._pixelOffset, scene, result);
        return windowCoordinates;
    };

    /**
     * Gets a label's screen space bounding box centered around screenSpacePosition.
     * @param {Label} label The label to get the screen space bounding box for.
     * @param {Cartesian2} screenSpacePosition The screen space center of the label.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The screen space bounding box.
     *
     * @private
     */
    Label.getScreenSpaceBoundingBox = function(label, screenSpacePosition, result) {
        var x = 0;
        var y = 0;
        var width = 0;
        var height = 0;
        var scale = label.scale;
        var resolutionScale = label._labelCollection._resolutionScale;

        var backgroundBillboard = label._backgroundBillboard;
        if (defined(backgroundBillboard)) {
            x = screenSpacePosition.x + (backgroundBillboard._translate.x / resolutionScale);
            y = screenSpacePosition.y - (backgroundBillboard._translate.y / resolutionScale);
            width = backgroundBillboard.width * scale;
            height = backgroundBillboard.height * scale;

            if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) {
                y -= height;
            } else if (label.verticalOrigin === VerticalOrigin.CENTER) {
                y -= height * 0.5;
            }
        } else {
            x = Number.POSITIVE_INFINITY;
            y = Number.POSITIVE_INFINITY;
            var maxX = 0;
            var maxY = 0;
            var glyphs = label._glyphs;
            var length = glyphs.length;
            for (var i = 0; i < length; ++i) {
                var glyph = glyphs[i];
                var billboard = glyph.billboard;
                if (!defined(billboard)) {
                    continue;
                }

                var glyphX = screenSpacePosition.x + (billboard._translate.x / resolutionScale);
                var glyphY = screenSpacePosition.y - (billboard._translate.y / resolutionScale);
                var glyphWidth = billboard.width * scale;
                var glyphHeight = billboard.height * scale;

                if (label.verticalOrigin === VerticalOrigin.BOTTOM || label.verticalOrigin === VerticalOrigin.BASELINE) {
                    glyphY -= glyphHeight;
                } else if (label.verticalOrigin === VerticalOrigin.CENTER) {
                    glyphY -= glyphHeight * 0.5;
                }

                x = Math.min(x, glyphX);
                y = Math.min(y, glyphY);
                maxX = Math.max(maxX, glyphX + glyphWidth);
                maxY = Math.max(maxY, glyphY + glyphHeight);
            }

            width = maxX - x;
            height = maxY - y;
        }

        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        result.x = x;
        result.y = y;
        result.width = width;
        result.height = height;

        return result;
    };

    /**
     * Determines if this label equals another label.  Labels are equal if all their properties
     * are equal.  Labels in different collections can be equal.
     *
     * @param {Label} other The label to compare for equality.
     * @returns {Boolean} <code>true</code> if the labels are equal; otherwise, <code>false</code>.
     */
    Label.prototype.equals = function(other) {
        return this === other ||
               defined(other) &&
               this._show === other._show &&
               this._scale === other._scale &&
               this._outlineWidth === other._outlineWidth &&
               this._showBackground === other._showBackground &&
               this._style === other._style &&
               this._verticalOrigin === other._verticalOrigin &&
               this._horizontalOrigin === other._horizontalOrigin &&
               this._heightReference === other._heightReference &&
               this._text === other._text &&
               this._font === other._font &&
               Cartesian3.equals(this._position, other._position) &&
               Color.equals(this._fillColor, other._fillColor) &&
               Color.equals(this._outlineColor, other._outlineColor) &&
               Color.equals(this._backgroundColor, other._backgroundColor) &&
               Cartesian2.equals(this._backgroundPadding, other._backgroundPadding) &&
               Cartesian2.equals(this._pixelOffset, other._pixelOffset) &&
               Cartesian3.equals(this._eyeOffset, other._eyeOffset) &&
               NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) &&
               NearFarScalar.equals(this._pixelOffsetScaleByDistance, other._pixelOffsetScaleByDistance) &&
               NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
               DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition) &&
               this._id === other._id;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    Label.prototype.isDestroyed = function() {
        return false;
    };

    return Label;
});

/*global define*/
define('Scene/LabelCollection',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Matrix4',
        '../Core/writeTextToCanvas',
        './BillboardCollection',
        './BlendOption',
        './HorizontalOrigin',
        './Label',
        './LabelStyle',
        './TextureAtlas',
        './VerticalOrigin'
    ], function(
        BoundingRectangle,
        Cartesian2,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Matrix4,
        writeTextToCanvas,
        BillboardCollection,
        BlendOption,
        HorizontalOrigin,
        Label,
        LabelStyle,
        TextureAtlas,
        VerticalOrigin) {
    'use strict';

    // A glyph represents a single character in a particular label.  It may or may
    // not have a billboard, depending on whether the texture info has an index into
    // the the label collection's texture atlas.  Invisible characters have no texture, and
    // no billboard.  However, it always has a valid dimensions object.
    function Glyph() {
        this.textureInfo = undefined;
        this.dimensions = undefined;
        this.billboard = undefined;
    }

    // GlyphTextureInfo represents a single character, drawn in a particular style,
    // shared and reference counted across all labels.  It may or may not have an
    // index into the label collection's texture atlas, depending on whether the character
    // has both width and height, but it always has a valid dimensions object.
    function GlyphTextureInfo(labelCollection, index, dimensions) {
        this.labelCollection = labelCollection;
        this.index = index;
        this.dimensions = dimensions;
    }

    // Traditionally, leading is %20 of the font size.
    var defaultLineSpacingPercent = 1.2;

    var whitePixelCanvasId = 'ID_WHITE_PIXEL';
    var whitePixelSize = new Cartesian2(4, 4);
    var whitePixelBoundingRegion = new BoundingRectangle(1, 1, 1, 1);

    function addWhitePixelCanvas(textureAtlas, labelCollection) {
        var canvas = document.createElement('canvas');
        canvas.width = whitePixelSize.x;
        canvas.height = whitePixelSize.y;

        var context2D = canvas.getContext('2d');
        context2D.fillStyle = '#fff';
        context2D.fillRect(0, 0, canvas.width, canvas.height);

        textureAtlas.addImage(whitePixelCanvasId, canvas).then(function(index) {
            labelCollection._whitePixelIndex = index;
        });
    }

    // reusable object for calling writeTextToCanvas
    var writeTextToCanvasParameters = {};
    function createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin) {
        writeTextToCanvasParameters.font = font;
        writeTextToCanvasParameters.fillColor = fillColor;
        writeTextToCanvasParameters.strokeColor = outlineColor;
        writeTextToCanvasParameters.strokeWidth = outlineWidth;

        if (verticalOrigin === VerticalOrigin.CENTER) {
            writeTextToCanvasParameters.textBaseline = 'middle';
        } else if (verticalOrigin === VerticalOrigin.TOP) {
            writeTextToCanvasParameters.textBaseline = 'top';
        } else {
            // VerticalOrigin.BOTTOM and VerticalOrigin.BASELINE
            writeTextToCanvasParameters.textBaseline = 'bottom';
        }

        writeTextToCanvasParameters.fill = style === LabelStyle.FILL || style === LabelStyle.FILL_AND_OUTLINE;
        writeTextToCanvasParameters.stroke = style === LabelStyle.OUTLINE || style === LabelStyle.FILL_AND_OUTLINE;

        return writeTextToCanvas(character, writeTextToCanvasParameters);
    }

    function unbindGlyph(labelCollection, glyph) {
        glyph.textureInfo = undefined;
        glyph.dimensions = undefined;

        var billboard = glyph.billboard;
        if (defined(billboard)) {
            billboard.show = false;
            billboard.image = undefined;
            labelCollection._spareBillboards.push(billboard);
            glyph.billboard = undefined;
        }
    }

    function addGlyphToTextureAtlas(textureAtlas, id, canvas, glyphTextureInfo) {
        textureAtlas.addImage(id, canvas).then(function(index, id) {
            glyphTextureInfo.index = index;
        });
    }

    function rebindAllGlyphs(labelCollection, label) {
        var text = label._text;
        var textLength = text.length;
        var glyphs = label._glyphs;
        var glyphsLength = glyphs.length;

        var glyph;
        var glyphIndex;
        var textIndex;

        // if we have more glyphs than needed, unbind the extras.
        if (textLength < glyphsLength) {
            for (glyphIndex = textLength; glyphIndex < glyphsLength; ++glyphIndex) {
                unbindGlyph(labelCollection, glyphs[glyphIndex]);
            }
        }

        // presize glyphs to match the new text length
        glyphs.length = textLength;

        var showBackground = label._showBackground && (text.split('\n').join('').length > 0);
        var backgroundBillboard = label._backgroundBillboard;
        var backgroundBillboardCollection = labelCollection._backgroundBillboardCollection;
        if (!showBackground) {
            if (defined(backgroundBillboard)) {
                backgroundBillboardCollection.remove(backgroundBillboard);
                label._backgroundBillboard = backgroundBillboard = undefined;
            }
        } else {
            if (!defined(backgroundBillboard)) {
                backgroundBillboard = backgroundBillboardCollection.add({
                    collection : labelCollection,
                    image : whitePixelCanvasId,
                    imageSubRegion : whitePixelBoundingRegion
                });
                label._backgroundBillboard = backgroundBillboard;
            }

            backgroundBillboard.color = label._backgroundColor;
            backgroundBillboard.show = label._show;
            backgroundBillboard.position = label._position;
            backgroundBillboard.eyeOffset = label._eyeOffset;
            backgroundBillboard.pixelOffset = label._pixelOffset;
            backgroundBillboard.horizontalOrigin = HorizontalOrigin.LEFT;
            backgroundBillboard.verticalOrigin = label._verticalOrigin;
            backgroundBillboard.heightReference = label._heightReference;
            backgroundBillboard.scale = label._scale;
            backgroundBillboard.pickPrimitive = label;
            backgroundBillboard.id = label._id;
            backgroundBillboard.translucencyByDistance = label._translucencyByDistance;
            backgroundBillboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
            backgroundBillboard.scaleByDistance = label._scaleByDistance;
            backgroundBillboard.distanceDisplayCondition = label._distanceDisplayCondition;
        }

        var glyphTextureCache = labelCollection._glyphTextureCache;

        // walk the text looking for new characters (creating new glyphs for each)
        // or changed characters (rebinding existing glyphs)
        for (textIndex = 0; textIndex < textLength; ++textIndex) {
            var character = text.charAt(textIndex);
            var font = label._font;
            var fillColor = label._fillColor;
            var outlineColor = label._outlineColor;
            var outlineWidth = label._outlineWidth;
            var style = label._style;
            var verticalOrigin = label._verticalOrigin;

            // retrieve glyph dimensions and texture index (if the canvas has area)
            // from the glyph texture cache, or create and add if not present.
            var id = JSON.stringify([
                                     character,
                                     font,
                                     fillColor.toRgba(),
                                     outlineColor.toRgba(),
                                     outlineWidth,
                                     +style,
                                     +verticalOrigin
                                    ]);

            var glyphTextureInfo = glyphTextureCache[id];
            if (!defined(glyphTextureInfo)) {
                var canvas = createGlyphCanvas(character, font, fillColor, outlineColor, outlineWidth, style, verticalOrigin);

                glyphTextureInfo = new GlyphTextureInfo(labelCollection, -1, canvas.dimensions);
                glyphTextureCache[id] = glyphTextureInfo;

                if (canvas.width > 0 && canvas.height > 0) {
                    addGlyphToTextureAtlas(labelCollection._textureAtlas, id, canvas, glyphTextureInfo);
                }
            }

            glyph = glyphs[textIndex];

            if (defined(glyph)) {
                // clean up leftover information from the previous glyph
                if (glyphTextureInfo.index === -1) {
                    // no texture, and therefore no billboard, for this glyph.
                    // so, completely unbind glyph.
                    unbindGlyph(labelCollection, glyph);
                } else {
                    // we have a texture and billboard.  If we had one before, release
                    // our reference to that texture info, but reuse the billboard.
                    if (defined(glyph.textureInfo)) {
                        glyph.textureInfo = undefined;
                    }
                }
            } else {
                // create a glyph object
                glyph = new Glyph();
                glyphs[textIndex] = glyph;
            }

            glyph.textureInfo = glyphTextureInfo;
            glyph.dimensions = glyphTextureInfo.dimensions;

            // if we have a texture, configure the existing billboard, or obtain one
            if (glyphTextureInfo.index !== -1) {
                var billboard = glyph.billboard;
                var spareBillboards = labelCollection._spareBillboards;
                if (!defined(billboard)) {
                    if (spareBillboards.length > 0) {
                        billboard = spareBillboards.pop();
                    } else {
                        billboard = labelCollection._billboardCollection.add({
                            collection : labelCollection
                        });
                    }
                    glyph.billboard = billboard;
                }

                billboard.show = label._show;
                billboard.position = label._position;
                billboard.eyeOffset = label._eyeOffset;
                billboard.pixelOffset = label._pixelOffset;
                billboard.horizontalOrigin = HorizontalOrigin.LEFT;
                billboard.verticalOrigin = label._verticalOrigin;
                billboard.heightReference = label._heightReference;
                billboard.scale = label._scale;
                billboard.pickPrimitive = label;
                billboard.id = label._id;
                billboard.image = id;
                billboard.translucencyByDistance = label._translucencyByDistance;
                billboard.pixelOffsetScaleByDistance = label._pixelOffsetScaleByDistance;
                billboard.scaleByDistance = label._scaleByDistance;
                billboard.distanceDisplayCondition = label._distanceDisplayCondition;
            }
        }

        // changing glyphs will cause the position of the
        // glyphs to change, since different characters have different widths
        label._repositionAllGlyphs = true;
    }

    function calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding) {
        if (horizontalOrigin === HorizontalOrigin.CENTER) {
            return -lineWidth / 2;
        } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
            return -(lineWidth + backgroundPadding.x);
        }
        return backgroundPadding.x;
    }

    // reusable Cartesian2 instances
    var glyphPixelOffset = new Cartesian2();
    var scratchBackgroundPadding = new Cartesian2();

    function repositionAllGlyphs(label, resolutionScale) {
        var glyphs = label._glyphs;
        var text = label._text;
        var glyph;
        var dimensions;
        var lastLineWidth = 0;
        var maxLineWidth = 0;
        var lineWidths = [];
        var maxGlyphDescent = Number.NEGATIVE_INFINITY;
        var maxGlyphY = 0;
        var numberOfLines = 1;
        var glyphIndex = 0;
        var glyphLength = glyphs.length;

        var backgroundBillboard = label._backgroundBillboard;
        var backgroundPadding = scratchBackgroundPadding;
        Cartesian2.clone(
            (defined(backgroundBillboard) ? label._backgroundPadding : Cartesian2.ZERO),
            backgroundPadding);

        for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
            if (text.charAt(glyphIndex) === '\n') {
                lineWidths.push(lastLineWidth);
                ++numberOfLines;
                lastLineWidth = 0;
            } else {
                glyph = glyphs[glyphIndex];
                dimensions = glyph.dimensions;
                maxGlyphY = Math.max(maxGlyphY, dimensions.height - dimensions.descent);
                maxGlyphDescent = Math.max(maxGlyphDescent, dimensions.descent);

                //Computing the line width must also account for the kerning that occurs between letters.
                lastLineWidth += dimensions.width - dimensions.bounds.minx;
                if (glyphIndex < glyphLength - 1) {
                    lastLineWidth += glyphs[glyphIndex + 1].dimensions.bounds.minx;
                }
                maxLineWidth = Math.max(maxLineWidth, lastLineWidth);
            }
        }
        lineWidths.push(lastLineWidth);
        var maxLineHeight = maxGlyphY + maxGlyphDescent;

        var scale = label._scale;
        var horizontalOrigin = label._horizontalOrigin;
        var verticalOrigin = label._verticalOrigin;
        var lineIndex = 0;
        var lineWidth = lineWidths[lineIndex];
        var widthOffset = calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding);
        var lineSpacing = defaultLineSpacingPercent * maxLineHeight;
        var otherLinesHeight = lineSpacing * (numberOfLines - 1);

        glyphPixelOffset.x = widthOffset * scale * resolutionScale;
        glyphPixelOffset.y = 0;

        var lineOffsetY = 0;
        for (glyphIndex = 0; glyphIndex < glyphLength; ++glyphIndex) {
            if (text.charAt(glyphIndex) === '\n') {
                ++lineIndex;
                lineOffsetY += lineSpacing;
                lineWidth = lineWidths[lineIndex];
                widthOffset = calculateWidthOffset(lineWidth, horizontalOrigin, backgroundPadding);
                glyphPixelOffset.x = widthOffset * scale * resolutionScale;
            } else {
                glyph = glyphs[glyphIndex];
                dimensions = glyph.dimensions;

                if (verticalOrigin === VerticalOrigin.TOP) {
                    glyphPixelOffset.y = dimensions.height - maxGlyphY - backgroundPadding.y;
                } else if (verticalOrigin === VerticalOrigin.CENTER) {
                    glyphPixelOffset.y = (otherLinesHeight + dimensions.height - maxGlyphY) / 2;
                } else if (verticalOrigin === VerticalOrigin.BASELINE) {
                    glyphPixelOffset.y = otherLinesHeight;
                } else {
                    // VerticalOrigin.BOTTOM
                    glyphPixelOffset.y = otherLinesHeight + maxGlyphDescent + backgroundPadding.y;
                }
                glyphPixelOffset.y = (glyphPixelOffset.y - dimensions.descent - lineOffsetY) * scale * resolutionScale;

                if (defined(glyph.billboard)) {
                    glyph.billboard._setTranslate(glyphPixelOffset);
                }

                //Compute the next x offset taking into acocunt the kerning performed
                //on both the current letter as well as the next letter to be drawn
                //as well as any applied scale.
                if (glyphIndex < glyphLength - 1) {
                    var nextGlyph = glyphs[glyphIndex + 1];
                    glyphPixelOffset.x += ((dimensions.width - dimensions.bounds.minx) + nextGlyph.dimensions.bounds.minx) * scale * resolutionScale;
                }
            }
        }

        if (defined(backgroundBillboard) && (text.split('\n').join('').length > 0)) {
            if (horizontalOrigin === HorizontalOrigin.CENTER) {
                widthOffset = -maxLineWidth / 2 - backgroundPadding.x;
            } else if (horizontalOrigin === HorizontalOrigin.RIGHT) {
                widthOffset = -(maxLineWidth + backgroundPadding.x * 2);
            } else {
                widthOffset = 0;
            }
            glyphPixelOffset.x = widthOffset * scale * resolutionScale;

            if (verticalOrigin === VerticalOrigin.TOP) {
                glyphPixelOffset.y = maxLineHeight - maxGlyphY - maxGlyphDescent;
            } else if (verticalOrigin === VerticalOrigin.CENTER) {
                glyphPixelOffset.y = (maxLineHeight - maxGlyphY) / 2 - maxGlyphDescent;
            } else if (verticalOrigin === VerticalOrigin.BASELINE) {
                glyphPixelOffset.y = -backgroundPadding.y - maxGlyphDescent;
            } else {
                // VerticalOrigin.BOTTOM
                glyphPixelOffset.y = 0;
            }
            glyphPixelOffset.y = glyphPixelOffset.y * scale * resolutionScale;

            backgroundBillboard.width = maxLineWidth + (backgroundPadding.x * 2);
            backgroundBillboard.height = maxLineHeight + otherLinesHeight + (backgroundPadding.y * 2);
            backgroundBillboard._setTranslate(glyphPixelOffset);
        }
    }

    function destroyLabel(labelCollection, label) {
        var glyphs = label._glyphs;
        for (var i = 0, len = glyphs.length; i < len; ++i) {
            unbindGlyph(labelCollection, glyphs[i]);
        }
        if (defined(label._backgroundBillboard)) {
            labelCollection._backgroundBillboardCollection.remove(label._backgroundBillboard);
            label._backgroundBillboard = undefined;
        }
        label._labelCollection = undefined;

        if (defined(label._removeCallbackFunc)) {
            label._removeCallbackFunc();
        }

        destroyObject(label);
    }

    /**
     * A renderable collection of labels.  Labels are viewport-aligned text positioned in the 3D scene.
     * Each label can have a different font, color, scale, etc.
     * <br /><br />
     * <div align='center'>
     * <img src='images/Label.png' width='400' height='300' /><br />
     * Example labels
     * </div>
     * <br /><br />
     * Labels are added and removed from the collection using {@link LabelCollection#add}
     * and {@link LabelCollection#remove}.
     *
     * @alias LabelCollection
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each label from model to world coordinates.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {Scene} [options.scene] Must be passed in for labels that use the height reference property or will be depth tested against the globe.
     * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The label blending option. The default
     * is used for rendering both opaque and translucent labels. However, if either all of the labels are completely opaque or all are completely translucent,
     * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x.
     *
     * @performance For best performance, prefer a few collections, each with many labels, to
     * many collections with only a few labels each.  Avoid having collections where some
     * labels change every frame and others do not; instead, create one or more collections
     * for static labels, and one or more collections for dynamic labels.
     *
     * @see LabelCollection#add
     * @see LabelCollection#remove
     * @see Label
     * @see BillboardCollection
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Labels.html|Cesium Sandcastle Labels Demo}
     *
     * @example
     * // Create a label collection with two labels
     * var labels = scene.primitives.add(new Cesium.LabelCollection());
     * labels.add({
     *   position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
     *   text : 'A label'
     * });
     * labels.add({
     *   position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
     *   text : 'Another label'
     * });
     */
    function LabelCollection(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._scene = options.scene;

        this._textureAtlas = undefined;
        this._backgroundTextureAtlas = undefined;
        this._whitePixelIndex = undefined;

        this._backgroundBillboardCollection = new BillboardCollection({
            scene : this._scene
        });
        this._backgroundBillboardCollection.destroyTextureAtlas = false;

        this._billboardCollection = new BillboardCollection({
            scene : this._scene
        });
        this._billboardCollection.destroyTextureAtlas = false;

        this._spareBillboards = [];
        this._glyphTextureCache = {};
        this._labels = [];
        this._labelsToUpdate = [];
        this._totalGlyphCount = 0;
        this._resolutionScale = undefined;

        /**
         * The 4x4 transformation matrix that transforms each label in this collection from model to world coordinates.
         * When this is the identity matrix, the labels are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type Matrix4
         * @default {@link Matrix4.IDENTITY}
         *
         * @example
         * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
         * labels.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
         * labels.add({
         *   position : new Cesium.Cartesian3(0.0, 0.0, 0.0),
         *   text     : 'Center'
         * });
         * labels.add({
         *   position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0),
         *   text     : 'East'
         * });
         * labels.add({
         *   position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0),
         *   text     : 'North'
         * });
         * labels.add({
         *   position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0),
         *   text     : 'Up'
         * });
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * The label blending option. The default is used for rendering both opaque and translucent labels.
         * However, if either all of the labels are completely opaque or all are completely translucent,
         * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve
         * performance by up to 2x.
         * @type {BlendOption}
         * @default BlendOption.OPAQUE_AND_TRANSLUCENT
         */
        this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT);
    }

    defineProperties(LabelCollection.prototype, {
        /**
         * Returns the number of labels in this collection.  This is commonly used with
         * {@link LabelCollection#get} to iterate over all the labels
         * in the collection.
         * @memberof LabelCollection.prototype
         * @type {Number}
         */
        length : {
            get : function() {
                return this._labels.length;
            }
        }
    });

    /**
     * Creates and adds a label with the specified initial properties to the collection.
     * The added label is returned so it can be modified or removed from the collection later.
     *
     * @param {Object}[options] A template describing the label's properties as shown in Example 1.
     * @returns {Label} The label that was added to the collection.
     *
     * @performance Calling <code>add</code> is expected constant time.  However, the collection's vertex buffer
     * is rewritten; this operations is <code>O(n)</code> and also incurs
     * CPU to GPU overhead.  For best performance, add as many billboards as possible before
     * calling <code>update</code>.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Example 1:  Add a label, specifying all the default values.
     * var l = labels.add({
     *   show : true,
     *   position : Cesium.Cartesian3.ZERO,
     *   text : '',
     *   font : '30px sans-serif',
     *   fillColor : Cesium.Color.WHITE,
     *   outlineColor : Cesium.Color.BLACK,
     *   outlineWidth : 1.0,
     *   showBackground : false,
     *   backgroundColor : new Cesium.Color(0.165, 0.165, 0.165, 0.8),
     *   backgroundPadding : new Cesium.Cartesian2(7, 5),
     *   style : Cesium.LabelStyle.FILL,
     *   pixelOffset : Cesium.Cartesian2.ZERO,
     *   eyeOffset : Cesium.Cartesian3.ZERO,
     *   horizontalOrigin : Cesium.HorizontalOrigin.LEFT,
     *   verticalOrigin : Cesium.VerticalOrigin.BASELINE,
     *   scale : 1.0,
     *   translucencyByDistance : undefined,
     *   pixelOffsetScaleByDistance : undefined,
     *   heightReference : HeightReference.NONE,
     *   distanceDisplayCondition : undefined
     * });
     *
     * @example
     * // Example 2:  Specify only the label's cartographic position,
     * // text, and font.
     * var l = labels.add({
     *   position : Cesium.Cartesian3.fromRadians(longitude, latitude, height),
     *   text : 'Hello World',
     *   font : '24px Helvetica',
     * });
     *
     * @see LabelCollection#remove
     * @see LabelCollection#removeAll
     */
    LabelCollection.prototype.add = function(options) {
        var label = new Label(options, this);

        this._labels.push(label);
        this._labelsToUpdate.push(label);

        return label;
    };

    /**
     * Removes a label from the collection.  Once removed, a label is no longer usable.
     *
     * @param {Label} label The label to remove.
     * @returns {Boolean} <code>true</code> if the label was removed; <code>false</code> if the label was not found in the collection.
     *
     * @performance Calling <code>remove</code> is expected constant time.  However, the collection's vertex buffer
     * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.  For
     * best performance, remove as many labels as possible before calling <code>update</code>.
     * If you intend to temporarily hide a label, it is usually more efficient to call
     * {@link Label#show} instead of removing and re-adding the label.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * var l = labels.add(...);
     * labels.remove(l);  // Returns true
     *
     * @see LabelCollection#add
     * @see LabelCollection#removeAll
     * @see Label#show
     */
    LabelCollection.prototype.remove = function(label) {
        if (defined(label) && label._labelCollection === this) {
            var index = this._labels.indexOf(label);
            if (index !== -1) {
                this._labels.splice(index, 1);
                destroyLabel(this, label);
                return true;
            }
        }
        return false;
    };

    /**
     * Removes all labels from the collection.
     *
     * @performance <code>O(n)</code>.  It is more efficient to remove all the labels
     * from a collection and then add new ones than to create a new collection entirely.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * labels.add(...);
     * labels.add(...);
     * labels.removeAll();
     *
     * @see LabelCollection#add
     * @see LabelCollection#remove
     */
    LabelCollection.prototype.removeAll = function() {
        var labels = this._labels;

        for (var i = 0, len = labels.length; i < len; ++i) {
            destroyLabel(this, labels[i]);
        }

        labels.length = 0;
    };

    /**
     * Check whether this collection contains a given label.
     *
     * @param {Label} label The label to check for.
     * @returns {Boolean} true if this collection contains the label, false otherwise.
     *
     * @see LabelCollection#get
     */
    LabelCollection.prototype.contains = function(label) {
        return defined(label) && label._labelCollection === this;
    };

    /**
     * Returns the label in the collection at the specified index.  Indices are zero-based
     * and increase as labels are added.  Removing a label shifts all labels after
     * it to the left, changing their indices.  This function is commonly used with
     * {@link LabelCollection#length} to iterate over all the labels
     * in the collection.
     *
     * @param {Number} index The zero-based index of the billboard.
     *
     * @returns {Label} The label at the specified index.
     *
     * @performance Expected constant time.  If labels were removed from the collection and
     * {@link Scene#render} was not called, an implicit <code>O(n)</code>
     * operation is performed.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Toggle the show property of every label in the collection
     * var len = labels.length;
     * for (var i = 0; i < len; ++i) {
     *   var l = billboards.get(i);
     *   l.show = !l.show;
     * }
     *
     * @see LabelCollection#length
     */
    LabelCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._labels[index];
    };

    /**
     * @private
     */
    LabelCollection.prototype.update = function(frameState) {
        var billboardCollection = this._billboardCollection;
        var backgroundBillboardCollection = this._backgroundBillboardCollection;

        billboardCollection.modelMatrix = this.modelMatrix;
        billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;
        backgroundBillboardCollection.modelMatrix = this.modelMatrix;
        backgroundBillboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume;

        var context = frameState.context;

        if (!defined(this._textureAtlas)) {
            this._textureAtlas = new TextureAtlas({
                context : context
            });
            billboardCollection.textureAtlas = this._textureAtlas;
        }

        if (!defined(this._backgroundTextureAtlas)) {
            this._backgroundTextureAtlas = new TextureAtlas({
                context : context,
                initialSize : whitePixelSize
            });
            backgroundBillboardCollection.textureAtlas = this._backgroundTextureAtlas;
            addWhitePixelCanvas(this._backgroundTextureAtlas, this);
        }

        var uniformState = context.uniformState;
        var resolutionScale = uniformState.resolutionScale;
        var resolutionChanged = this._resolutionScale !== resolutionScale;
        this._resolutionScale = resolutionScale;

        var labelsToUpdate;
        if (resolutionChanged) {
            labelsToUpdate = this._labels;
        } else {
            labelsToUpdate = this._labelsToUpdate;
        }

        var len = labelsToUpdate.length;
        for (var i = 0; i < len; ++i) {
            var label = labelsToUpdate[i];
            if (label.isDestroyed()) {
                continue;
            }

            var preUpdateGlyphCount = label._glyphs.length;

            if (label._rebindAllGlyphs) {
                rebindAllGlyphs(this, label);
                label._rebindAllGlyphs = false;
            }

            if (resolutionChanged || label._repositionAllGlyphs) {
                repositionAllGlyphs(label, resolutionScale);
                label._repositionAllGlyphs = false;
            }

            var glyphCountDifference = label._glyphs.length - preUpdateGlyphCount;
            this._totalGlyphCount += glyphCountDifference;
        }

        var blendOption = backgroundBillboardCollection.length > 0 ? BlendOption.TRANSLUCENT : this.blendOption;
        billboardCollection.blendOption = blendOption;
        backgroundBillboardCollection.blendOption = blendOption;

        this._labelsToUpdate.length = 0;
        backgroundBillboardCollection.update(frameState);
        billboardCollection.update(frameState);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see LabelCollection#destroy
     */
    LabelCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * labels = labels && labels.destroy();
     *
     * @see LabelCollection#isDestroyed
     */
    LabelCollection.prototype.destroy = function() {
        this.removeAll();
        this._billboardCollection = this._billboardCollection.destroy();
        this._textureAtlas = this._textureAtlas && this._textureAtlas.destroy();
        this._backgroundBillboardCollection = this._backgroundBillboardCollection.destroy();
        this._backgroundTextureAtlas = this._backgroundTextureAtlas && this._backgroundTextureAtlas.destroy();

        return destroyObject(this);
    };

    return LabelCollection;
});

/*global define*/
define('Scene/PointPrimitive',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/Matrix4',
        '../Core/NearFarScalar',
        './SceneMode',
        './SceneTransforms'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Color,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        DistanceDisplayCondition,
        Matrix4,
        NearFarScalar,
        SceneMode,
        SceneTransforms) {
    'use strict';

    /**
     * A graphical point positioned in the 3D scene, that is created
     * and rendered using a {@link PointPrimitiveCollection}.  A point is created and its initial
     * properties are set by calling {@link PointPrimitiveCollection#add}.
     *
     * @alias PointPrimitive
     *
     * @performance Reading a property, e.g., {@link PointPrimitive#show}, is constant time.
     * Assigning to a property is constant time but results in
     * CPU to GPU traffic when {@link PointPrimitiveCollection#update} is called.  The per-pointPrimitive traffic is
     * the same regardless of how many properties were updated.  If most pointPrimitives in a collection need to be
     * updated, it may be more efficient to clear the collection with {@link PointPrimitiveCollection#removeAll}
     * and add new pointPrimitives instead of modifying each one.
     *
     * @exception {DeveloperError} scaleByDistance.far must be greater than scaleByDistance.near
     * @exception {DeveloperError} translucencyByDistance.far must be greater than translucencyByDistance.near
     * @exception {DeveloperError} distanceDisplayCondition.far must be greater than distanceDisplayCondition.near
     *
     * @see PointPrimitiveCollection
     * @see PointPrimitiveCollection#add
     *
     * @internalConstructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Points.html|Cesium Sandcastle Points Demo}
     */
    function PointPrimitive(options, pointPrimitiveCollection) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (defined(options.scaleByDistance) && options.scaleByDistance.far <= options.scaleByDistance.near) {
            throw new DeveloperError('scaleByDistance.far must be greater than scaleByDistance.near.');
        }
        if (defined(options.translucencyByDistance) && options.translucencyByDistance.far <= options.translucencyByDistance.near) {
            throw new DeveloperError('translucencyByDistance.far must be greater than translucencyByDistance.near.');
        }
        if (defined(options.distanceDisplayCondition) && options.distanceDisplayCondition.far <= options.distanceDisplayCondition.near) {
            throw new DeveloperError('distanceDisplayCondition.far must be greater than distanceDisplayCondition.near');
        }
        
        this._show = defaultValue(options.show, true);
        this._position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
        this._actualPosition = Cartesian3.clone(this._position); // For columbus view and 2D
        this._color = Color.clone(defaultValue(options.color, Color.WHITE));
        this._outlineColor = Color.clone(defaultValue(options.outlineColor, Color.TRANSPARENT));
        this._outlineWidth = defaultValue(options.outlineWidth, 0.0);
        this._pixelSize = defaultValue(options.pixelSize, 10.0);
        this._scaleByDistance = options.scaleByDistance;
        this._translucencyByDistance = options.translucencyByDistance;
        this._distanceDisplayCondition = options.distanceDisplayCondition;
        this._id = options.id;
        this._collection = defaultValue(options.collection, pointPrimitiveCollection);

        this._clusterShow = true;

        this._pickId = undefined;
        this._pointPrimitiveCollection = pointPrimitiveCollection;
        this._dirty = false;
        this._index = -1; //Used only by PointPrimitiveCollection
    }

    var SHOW_INDEX = PointPrimitive.SHOW_INDEX = 0;
    var POSITION_INDEX = PointPrimitive.POSITION_INDEX = 1;
    var COLOR_INDEX = PointPrimitive.COLOR_INDEX = 2;
    var OUTLINE_COLOR_INDEX = PointPrimitive.OUTLINE_COLOR_INDEX = 3;
    var OUTLINE_WIDTH_INDEX = PointPrimitive.OUTLINE_WIDTH_INDEX = 4;
    var PIXEL_SIZE_INDEX = PointPrimitive.PIXEL_SIZE_INDEX = 5;
    var SCALE_BY_DISTANCE_INDEX = PointPrimitive.SCALE_BY_DISTANCE_INDEX = 6;
    var TRANSLUCENCY_BY_DISTANCE_INDEX = PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX = 7;
    var DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION = 8;
    PointPrimitive.NUMBER_OF_PROPERTIES = 9;

    function makeDirty(pointPrimitive, propertyChanged) {
        var pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection;
        if (defined(pointPrimitiveCollection)) {
            pointPrimitiveCollection._updatePointPrimitive(pointPrimitive, propertyChanged);
            pointPrimitive._dirty = true;
        }
    }

    defineProperties(PointPrimitive.prototype, {
        /**
         * Determines if this point will be shown.  Use this to hide or show a point, instead
         * of removing it and re-adding it to the collection.
         * @memberof PointPrimitive.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._show;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._show !== value) {
                    this._show = value;
                    makeDirty(this, SHOW_INDEX);
                }
            }
        },

        /**
        * Gets or sets the Cartesian position of this point.
        * @memberof PointPrimitive.prototype
        * @type {Cartesian3}
        */
        position : {
            get : function() {
                return this._position;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var position = this._position;
                if (!Cartesian3.equals(position, value)) {
                    Cartesian3.clone(value, position);
                    Cartesian3.clone(value, this._actualPosition);

                    makeDirty(this, POSITION_INDEX);
                }
            }
        },

        /**
         * Gets or sets near and far scaling properties of a point based on the point's distance from the camera.
         * A point's scale will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the point's scale remains clamped to the nearest bound.  This scale
         * multiplies the pixelSize and outlineWidth to affect the total size of the point.  If undefined,
         * scaleByDistance will be disabled.
         * @memberof PointPrimitive.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a pointPrimitive's scaleByDistance to scale to 15 when the
         * // camera is 1500 meters from the pointPrimitive and disappear as
         * // the camera distance approaches 8.0e6 meters.
         * p.scaleByDistance = new Cesium.NearFarScalar(1.5e2, 15, 8.0e6, 0.0);
         *
         * @example
         * // Example 2.
         * // disable scaling by distance
         * p.scaleByDistance = undefined;
         */
        scaleByDistance : {
            get : function() {
                return this._scaleByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var scaleByDistance = this._scaleByDistance;
                if (!NearFarScalar.equals(scaleByDistance, value)) {
                    this._scaleByDistance = NearFarScalar.clone(value, scaleByDistance);
                    makeDirty(this, SCALE_BY_DISTANCE_INDEX);
                }
            }
        },

        /**
         * Gets or sets near and far translucency properties of a point based on the point's distance from the camera.
         * A point's translucency will interpolate between the {@link NearFarScalar#nearValue} and
         * {@link NearFarScalar#farValue} while the camera distance falls within the upper and lower bounds
         * of the specified {@link NearFarScalar#near} and {@link NearFarScalar#far}.
         * Outside of these ranges the point's translucency remains clamped to the nearest bound.  If undefined,
         * translucencyByDistance will be disabled.
         * @memberof PointPrimitive.prototype
         * @type {NearFarScalar}
         *
         * @example
         * // Example 1.
         * // Set a point's translucency to 1.0 when the
         * // camera is 1500 meters from the point and disappear as
         * // the camera distance approaches 8.0e6 meters.
         * p.translucencyByDistance = new Cesium.NearFarScalar(1.5e2, 1.0, 8.0e6, 0.0);
         *
         * @example
         * // Example 2.
         * // disable translucency by distance
         * p.translucencyByDistance = undefined;
         */
        translucencyByDistance : {
            get : function() {
                return this._translucencyByDistance;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                
                var translucencyByDistance = this._translucencyByDistance;
                if (!NearFarScalar.equals(translucencyByDistance, value)) {
                    this._translucencyByDistance = NearFarScalar.clone(value, translucencyByDistance);
                    makeDirty(this, TRANSLUCENCY_BY_DISTANCE_INDEX);
                }
            }
        },

        /**
         * Gets or sets the inner size of the point in pixels.
         * @memberof PointPrimitive.prototype
         * @type {Number}
         */
        pixelSize : {
            get : function() {
                return this._pixelSize;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._pixelSize !== value) {
                    this._pixelSize = value;
                    makeDirty(this, PIXEL_SIZE_INDEX);
                }
            }
        },

        /**
         * Gets or sets the inner color of the point.
         * The red, green, blue, and alpha values are indicated by <code>value</code>'s <code>red</code>, <code>green</code>,
         * <code>blue</code>, and <code>alpha</code> properties as shown in Example 1.  These components range from <code>0.0</code>
         * (no intensity) to <code>1.0</code> (full intensity).
         * @memberof PointPrimitive.prototype
         * @type {Color}
         *
         * @example
         * // Example 1. Assign yellow.
         * p.color = Cesium.Color.YELLOW;
         *
         * @example
         * // Example 2. Make a pointPrimitive 50% translucent.
         * p.color = new Cesium.Color(1.0, 1.0, 1.0, 0.5);
         */
        color : {
            get : function() {
                return this._color;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var color = this._color;
                if (!Color.equals(color, value)) {
                    Color.clone(value, color);
                    makeDirty(this, COLOR_INDEX);
                }
            }
        },

        /**
         * Gets or sets the outline color of the point.
         * @memberof PointPrimitive.prototype
         * @type {Color}
         */
        outlineColor : {
            get : function() {
                return this._outlineColor;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var outlineColor = this._outlineColor;
                if (!Color.equals(outlineColor, value)) {
                    Color.clone(value, outlineColor);
                    makeDirty(this, OUTLINE_COLOR_INDEX);
                }
            }
        },

        /**
         * Gets or sets the outline width in pixels.  This width adds to pixelSize,
         * increasing the total size of the point.
         * @memberof PointPrimitive.prototype
         * @type {Number}
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (this._outlineWidth !== value) {
                    this._outlineWidth = value;
                    makeDirty(this, OUTLINE_WIDTH_INDEX);
                }
            }
        },

        /**
         * Gets or sets the condition specifying at what distance from the camera that this point will be displayed.
         * @memberof PointPrimitive.prototype
         * @type {DistanceDisplayCondition}
         * @default undefined
         */
        distanceDisplayCondition : {
            get : function() {
                return this._distanceDisplayCondition;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far must be greater than near');
                }
                                if (!DistanceDisplayCondition.equals(this._distanceDisplayCondition, value)) {
                    this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
                    makeDirty(this, DISTANCE_DISPLAY_CONDITION_INDEX);
                }
            }
        },

        /**
         * Gets or sets the user-defined object returned when the point is picked.
         * @memberof PointPrimitive.prototype
         * @type {Object}
         */
        id : {
            get : function() {
                return this._id;
            },
            set : function(value) {
                this._id = value;
                if (defined(this._pickId)) {
                    this._pickId.object.id = value;
                }
            }
        },

        /**
         * Determines whether or not this point will be shown or hidden because it was clustered.
         * @memberof PointPrimitive.prototype
         * @type {Boolean}
         * @private
         */
        clusterShow : {
            get : function() {
                return this._clusterShow;
            },
            set : function(value) {
                if (this._clusterShow !== value) {
                    this._clusterShow = value;
                    makeDirty(this, SHOW_INDEX);
                }
            }
        }
    });

    PointPrimitive.prototype.getPickId = function(context) {
        if (!defined(this._pickId)) {
            this._pickId = context.createPickId({
                primitive : this,
                collection : this._collection,
                id : this._id
            });
        }

        return this._pickId;
    };

    PointPrimitive.prototype._getActualPosition = function() {
        return this._actualPosition;
    };

    PointPrimitive.prototype._setActualPosition = function(value) {
        Cartesian3.clone(value, this._actualPosition);
        makeDirty(this, POSITION_INDEX);
    };

    var tempCartesian3 = new Cartesian4();
    PointPrimitive._computeActualPosition = function(position, frameState, modelMatrix) {
        if (frameState.mode === SceneMode.SCENE3D) {
            return position;
        }

        Matrix4.multiplyByPoint(modelMatrix, position, tempCartesian3);
        return SceneTransforms.computeActualWgs84Position(frameState, tempCartesian3);
    };

    var scratchCartesian4 = new Cartesian4();

    // This function is basically a stripped-down JavaScript version of PointPrimitiveCollectionVS.glsl
    PointPrimitive._computeScreenSpacePosition = function(modelMatrix, position, scene, result) {
        // Model to world coordinates
        var positionWorld = Matrix4.multiplyByVector(modelMatrix, Cartesian4.fromElements(position.x, position.y, position.z, 1, scratchCartesian4), scratchCartesian4);
        var positionWC = SceneTransforms.wgs84ToWindowCoordinates(scene, positionWorld, result);
        return positionWC;
    };

    /**
     * Computes the screen-space position of the point's origin.
     * The screen space origin is the top, left corner of the canvas; <code>x</code> increases from
     * left to right, and <code>y</code> increases from top to bottom.
     *
     * @param {Scene} scene The scene.
     * @param {Cartesian2} [result] The object onto which to store the result.
     * @returns {Cartesian2} The screen-space position of the point.
     *
     * @exception {DeveloperError} PointPrimitive must be in a collection.
     *
     * @example
     * console.log(p.computeScreenSpacePosition(scene).toString());
     */
    PointPrimitive.prototype.computeScreenSpacePosition = function(scene, result) {
        var pointPrimitiveCollection = this._pointPrimitiveCollection;
        if (!defined(result)) {
            result = new Cartesian2();
        }

                if (!defined(pointPrimitiveCollection)) {
            throw new DeveloperError('PointPrimitive must be in a collection.');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        var modelMatrix = pointPrimitiveCollection.modelMatrix;
        var windowCoordinates = PointPrimitive._computeScreenSpacePosition(modelMatrix, this._actualPosition, scene, result);
        if (!defined(windowCoordinates)) {
            return undefined;
        }

        windowCoordinates.y = scene.canvas.clientHeight - windowCoordinates.y;
        return windowCoordinates;
    };

    /**
     * Gets a point's screen space bounding box centered around screenSpacePosition.
     * @param {PointPrimitive} point The point to get the screen space bounding box for.
     * @param {Cartesian2} screenSpacePosition The screen space center of the label.
     * @param {BoundingRectangle} [result] The object onto which to store the result.
     * @returns {BoundingRectangle} The screen space bounding box.
     *
     * @private
     */
    PointPrimitive.getScreenSpaceBoundingBox = function(point, screenSpacePosition, result) {
        var size = point.pixelSize;
        var halfSize = size * 0.5;

        var x = screenSpacePosition.x - halfSize;
        var y = screenSpacePosition.y - halfSize;
        var width = size;
        var height = size;

        if (!defined(result)) {
            result = new BoundingRectangle();
        }

        result.x = x;
        result.y = y;
        result.width = width;
        result.height = height;

        return result;
    };

    /**
     * Determines if this point equals another point.  Points are equal if all their properties
     * are equal.  Points in different collections can be equal.
     *
     * @param {PointPrimitive} other The point to compare for equality.
     * @returns {Boolean} <code>true</code> if the points are equal; otherwise, <code>false</code>.
     */
    PointPrimitive.prototype.equals = function(other) {
        return this === other ||
               defined(other) &&
               this._id === other._id &&
               Cartesian3.equals(this._position, other._position) &&
               Color.equals(this._color, other._color) &&
               this._pixelSize === other._pixelSize &&
               this._outlineWidth === other._outlineWidth &&
               this._show === other._show &&
               Color.equals(this._outlineColor, other._outlineColor) &&
               NearFarScalar.equals(this._scaleByDistance, other._scaleByDistance) &&
               NearFarScalar.equals(this._translucencyByDistance, other._translucencyByDistance) &&
               DistanceDisplayCondition.equals(this._distanceDisplayCondition, other._distanceDisplayCondition);
    };

    PointPrimitive.prototype._destroy = function() {
        this._pickId = this._pickId && this._pickId.destroy();
        this._pointPrimitiveCollection = undefined;
    };

    return PointPrimitive;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PointPrimitiveCollectionFS',[],function() {
    'use strict';
    return "varying vec4 v_color;\n\
varying vec4 v_outlineColor;\n\
varying float v_innerPercent;\n\
varying float v_pixelDistance;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#endif\n\
\n\
void main()\n\
{\n\
    // The distance in UV space from this fragment to the center of the point, at most 0.5.\n\
    float distanceToCenter = length(gl_PointCoord - vec2(0.5));\n\
    // The max distance stops one pixel shy of the edge to leave space for anti-aliasing.\n\
    float maxDistance = max(0.0, 0.5 - v_pixelDistance);\n\
    float wholeAlpha = 1.0 - smoothstep(maxDistance, 0.5, distanceToCenter);\n\
    float innerAlpha = 1.0 - smoothstep(maxDistance * v_innerPercent, 0.5 * v_innerPercent, distanceToCenter);\n\
\n\
    vec4 color = mix(v_outlineColor, v_color, innerAlpha);\n\
    color.a *= wholeAlpha;\n\
\n\
// Fully transparent parts of the billboard are not pickable.\n\
#if defined(RENDER_FOR_PICK) || (!defined(OPAQUE) && !defined(TRANSLUCENT))\n\
    if (color.a < 0.005)   // matches 0/255 and 1/255\n\
    {\n\
        discard;\n\
    }\n\
#else\n\
// The billboard is rendered twice. The opaque pass discards translucent fragments\n\
// and the translucent pass discards opaque fragments.\n\
#ifdef OPAQUE\n\
    if (color.a < 0.995)   // matches < 254/255\n\
    {\n\
        discard;\n\
    }\n\
#else\n\
    if (color.a >= 0.995)  // matches 254/255 and 255/255\n\
    {\n\
        discard;\n\
    }\n\
#endif\n\
#endif\n\
\n\
#ifdef RENDER_FOR_PICK\n\
    gl_FragColor = v_pickColor;\n\
#else\n\
    gl_FragColor = color;\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PointPrimitiveCollectionVS',[],function() {
    'use strict';
    return "uniform float u_maxTotalPointSize;\n\
\n\
attribute vec4 positionHighAndSize;\n\
attribute vec4 positionLowAndOutline;\n\
attribute vec4 compressedAttribute0;        // color, outlineColor, pick color\n\
attribute vec4 compressedAttribute1;        // show, translucency by distance, some free space\n\
attribute vec4 scaleByDistance;             // near, nearScale, far, farScale\n\
attribute vec2 distanceDisplayCondition;    // near, far\n\
\n\
varying vec4 v_color;\n\
varying vec4 v_outlineColor;\n\
varying float v_innerPercent;\n\
varying float v_pixelDistance;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
varying vec4 v_pickColor;\n\
#endif\n\
\n\
const float SHIFT_LEFT8 = 256.0;\n\
const float SHIFT_RIGHT8 = 1.0 / 256.0;\n\
\n\
void main()\n\
{\n\
    // Modifying this shader may also require modifications to PointPrimitive._computeScreenSpacePosition\n\
\n\
    // unpack attributes\n\
    vec3 positionHigh = positionHighAndSize.xyz;\n\
    vec3 positionLow = positionLowAndOutline.xyz;\n\
    float outlineWidthBothSides = 2.0 * positionLowAndOutline.w;\n\
    float totalSize = positionHighAndSize.w + outlineWidthBothSides;\n\
    float outlinePercent = outlineWidthBothSides / totalSize;\n\
    // Scale in response to browser-zoom.\n\
    totalSize *= czm_resolutionScale;\n\
    // Add padding for anti-aliasing on both sides.\n\
    totalSize += 3.0;\n\
\n\
    float temp = compressedAttribute1.x * SHIFT_RIGHT8;\n\
    float show = floor(temp);\n\
\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
    vec4 translucencyByDistance;\n\
    translucencyByDistance.x = compressedAttribute1.z;\n\
    translucencyByDistance.z = compressedAttribute1.w;\n\
\n\
    translucencyByDistance.y = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
\n\
    temp = compressedAttribute1.y * SHIFT_RIGHT8;\n\
    translucencyByDistance.w = ((temp - floor(temp)) * SHIFT_LEFT8) / 255.0;\n\
#endif\n\
\n\
    ///////////////////////////////////////////////////////////////////////////\n\
\n\
    vec4 color;\n\
    vec4 outlineColor;\n\
#ifdef RENDER_FOR_PICK\n\
    // compressedAttribute0.z => pickColor.rgb\n\
\n\
    color = vec4(0.0);\n\
    outlineColor = vec4(0.0);\n\
    vec4 pickColor;\n\
    temp = compressedAttribute0.z * SHIFT_RIGHT8;\n\
    pickColor.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    temp = floor(temp) * SHIFT_RIGHT8;\n\
    pickColor.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    pickColor.r = floor(temp);\n\
#else\n\
    // compressedAttribute0.x => color.rgb\n\
\n\
    temp = compressedAttribute0.x * SHIFT_RIGHT8;\n\
    color.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    temp = floor(temp) * SHIFT_RIGHT8;\n\
    color.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    color.r = floor(temp);\n\
\n\
    // compressedAttribute0.y => outlineColor.rgb\n\
\n\
    temp = compressedAttribute0.y * SHIFT_RIGHT8;\n\
    outlineColor.b = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    temp = floor(temp) * SHIFT_RIGHT8;\n\
    outlineColor.g = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    outlineColor.r = floor(temp);\n\
#endif\n\
\n\
    // compressedAttribute0.w => color.a, outlineColor.a, pickColor.a\n\
\n\
    temp = compressedAttribute0.w * SHIFT_RIGHT8;\n\
#ifdef RENDER_FOR_PICK\n\
    pickColor.a = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    pickColor = pickColor / 255.0;\n\
#endif\n\
    temp = floor(temp) * SHIFT_RIGHT8;\n\
    outlineColor.a = (temp - floor(temp)) * SHIFT_LEFT8;\n\
    outlineColor /= 255.0;\n\
    color.a = floor(temp);\n\
    color /= 255.0;\n\
\n\
    ///////////////////////////////////////////////////////////////////////////\n\
\n\
    vec4 p = czm_translateRelativeToEye(positionHigh, positionLow);\n\
    vec4 positionEC = czm_modelViewRelativeToEye * p;\n\
    positionEC.xyz *= show;\n\
\n\
    ///////////////////////////////////////////////////////////////////////////\n\
\n\
#if defined(EYE_DISTANCE_SCALING) || defined(EYE_DISTANCE_TRANSLUCENCY) || defined(DISTANCE_DISPLAY_CONDITION)\n\
    float lengthSq;\n\
    if (czm_sceneMode == czm_sceneMode2D)\n\
    {\n\
        // 2D camera distance is a special case\n\
        // treat all billboards as flattened to the z=0.0 plane\n\
        lengthSq = czm_eyeHeight2D.y;\n\
    }\n\
    else\n\
    {\n\
        lengthSq = dot(positionEC.xyz, positionEC.xyz);\n\
    }\n\
#endif\n\
\n\
#ifdef EYE_DISTANCE_SCALING\n\
    totalSize *= czm_nearFarScalar(scaleByDistance, lengthSq);\n\
#endif\n\
    // Clamp to max point size.\n\
    totalSize = min(totalSize, u_maxTotalPointSize);\n\
    // If size is too small, push vertex behind near plane for clipping.\n\
    // Note that context.minimumAliasedPointSize \"will be at most 1.0\".\n\
    if (totalSize < 1.0)\n\
    {\n\
        positionEC.xyz = vec3(0.0);\n\
        totalSize = 1.0;\n\
    }\n\
\n\
    float translucency = 1.0;\n\
#ifdef EYE_DISTANCE_TRANSLUCENCY\n\
    translucency = czm_nearFarScalar(translucencyByDistance, lengthSq);\n\
    // push vertex behind near plane for clipping\n\
    if (translucency < 0.004)\n\
    {\n\
        positionEC.xyz = vec3(0.0);\n\
    }\n\
#endif\n\
\n\
#ifdef DISTANCE_DISPLAY_CONDITION\n\
    float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x;\n\
    float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y;\n\
    if (lengthSq < nearSq || lengthSq > farSq) {\n\
        positionEC.xyz = vec3(0.0);\n\
    }\n\
#endif\n\
\n\
    vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
\n\
    gl_Position = czm_viewportOrthographic * vec4(positionWC.xy, -positionWC.z, 1.0);\n\
\n\
    v_color = color;\n\
    v_color.a *= translucency;\n\
    v_outlineColor = outlineColor;\n\
    v_outlineColor.a *= translucency;\n\
\n\
    v_innerPercent = 1.0 - outlinePercent;\n\
    v_pixelDistance = 2.0 / totalSize;\n\
    gl_PointSize = totalSize;\n\
\n\
#ifdef RENDER_FOR_PICK\n\
    v_pickColor = pickColor;\n\
#endif\n\
}\n\
";
});
/*global define*/
define('Scene/PointPrimitiveCollection',[
        '../Core/BoundingSphere',
        '../Core/Color',
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EncodedCartesian3',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/PrimitiveType',
        '../Core/WebGLConstants',
        '../Renderer/BufferUsage',
        '../Renderer/ContextLimits',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArrayFacade',
        '../Shaders/PointPrimitiveCollectionFS',
        '../Shaders/PointPrimitiveCollectionVS',
        './BlendingState',
        './BlendOption',
        './PointPrimitive',
        './SceneMode'
    ], function(
        BoundingSphere,
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EncodedCartesian3,
        CesiumMath,
        Matrix4,
        PrimitiveType,
        WebGLConstants,
        BufferUsage,
        ContextLimits,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArrayFacade,
        PointPrimitiveCollectionFS,
        PointPrimitiveCollectionVS,
        BlendingState,
        BlendOption,
        PointPrimitive,
        SceneMode) {
    'use strict';

    var SHOW_INDEX = PointPrimitive.SHOW_INDEX;
    var POSITION_INDEX = PointPrimitive.POSITION_INDEX;
    var COLOR_INDEX = PointPrimitive.COLOR_INDEX;
    var OUTLINE_COLOR_INDEX = PointPrimitive.OUTLINE_COLOR_INDEX;
    var OUTLINE_WIDTH_INDEX = PointPrimitive.OUTLINE_WIDTH_INDEX;
    var PIXEL_SIZE_INDEX = PointPrimitive.PIXEL_SIZE_INDEX;
    var SCALE_BY_DISTANCE_INDEX = PointPrimitive.SCALE_BY_DISTANCE_INDEX;
    var TRANSLUCENCY_BY_DISTANCE_INDEX = PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX;
    var DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX;
    var NUMBER_OF_PROPERTIES = PointPrimitive.NUMBER_OF_PROPERTIES;

    var attributeLocations = {
        positionHighAndSize : 0,
        positionLowAndOutline : 1,
        compressedAttribute0 : 2,        // color, outlineColor, pick color
        compressedAttribute1 : 3,        // show, translucency by distance, some free space
        scaleByDistance : 4,
        distanceDisplayCondition : 5
    };

    /**
     * A renderable collection of points.
     * <br /><br />
     * Points are added and removed from the collection using {@link PointPrimitiveCollection#add}
     * and {@link PointPrimitiveCollection#remove}.
     *
     * @alias PointPrimitiveCollection
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each point from model to world coordinates.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The point blending option. The default
     * is used for rendering both opaque and translucent points. However, if either all of the points are completely opaque or all are completely translucent,
     * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x.
     *
     * @performance For best performance, prefer a few collections, each with many points, to
     * many collections with only a few points each.  Organize collections so that points
     * with the same update frequency are in the same collection, i.e., points that do not
     * change should be in one collection; points that change every frame should be in another
     * collection; and so on.
     *
     *
     * @example
     * // Create a pointPrimitive collection with two points
     * var points = scene.primitives.add(new Cesium.PointPrimitiveCollection());
     * points.add({
     *   position : new Cesium.Cartesian3(1.0, 2.0, 3.0),
     *   color : Cesium.Color.YELLOW
     * });
     * points.add({
     *   position : new Cesium.Cartesian3(4.0, 5.0, 6.0),
     *   color : Cesium.Color.CYAN
     * });
     *
     * @see PointPrimitiveCollection#add
     * @see PointPrimitiveCollection#remove
     * @see PointPrimitive
     */
    function PointPrimitiveCollection(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._sp = undefined;
        this._spTranslucent = undefined;
        this._spPick = undefined;
        this._rsOpaque = undefined;
        this._rsTranslucent = undefined;
        this._vaf = undefined;

        this._pointPrimitives = [];
        this._pointPrimitivesToUpdate = [];
        this._pointPrimitivesToUpdateIndex = 0;
        this._pointPrimitivesRemoved = false;
        this._createVertexArray = false;

        this._shaderScaleByDistance = false;
        this._compiledShaderScaleByDistance = false;
        this._compiledShaderScaleByDistancePick = false;

        this._shaderTranslucencyByDistance = false;
        this._compiledShaderTranslucencyByDistance = false;
        this._compiledShaderTranslucencyByDistancePick = false;

        this._shaderDistanceDisplayCondition = false;
        this._compiledShaderDistanceDisplayCondition = false;
        this._compiledShaderDistanceDisplayConditionPick = false;

        this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);

        this._maxPixelSize = 1.0;

        this._baseVolume = new BoundingSphere();
        this._baseVolumeWC = new BoundingSphere();
        this._baseVolume2D = new BoundingSphere();
        this._boundingVolume = new BoundingSphere();
        this._boundingVolumeDirty = false;

        this._colorCommands = [];
        this._pickCommands = [];

        /**
         * The 4x4 transformation matrix that transforms each point in this collection from model to world coordinates.
         * When this is the identity matrix, the pointPrimitives are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type {Matrix4}
         * @default {@link Matrix4.IDENTITY}
         *
         *
         * @example
         * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
         * pointPrimitives.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center);
         * pointPrimitives.add({
         *   color : Cesium.Color.ORANGE,
         *   position : new Cesium.Cartesian3(0.0, 0.0, 0.0) // center
         * });
         * pointPrimitives.add({
         *   color : Cesium.Color.YELLOW,
         *   position : new Cesium.Cartesian3(1000000.0, 0.0, 0.0) // east
         * });
         * pointPrimitives.add({
         *   color : Cesium.Color.GREEN,
         *   position : new Cesium.Cartesian3(0.0, 1000000.0, 0.0) // north
         * });
         * pointPrimitives.add({
         *   color : Cesium.Color.CYAN,
         *   position : new Cesium.Cartesian3(0.0, 0.0, 1000000.0) // up
         * });
         *
         * @see Transforms.eastNorthUpToFixedFrame
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * The point blending option. The default is used for rendering both opaque and translucent points.
         * However, if either all of the points are completely opaque or all are completely translucent,
         * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve
         * performance by up to 2x.
         * @type {BlendOption}
         * @default BlendOption.OPAQUE_AND_TRANSLUCENT
         */
        this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT);
        this._blendOption = undefined;

        this._mode = SceneMode.SCENE3D;
        this._maxTotalPointSize = 1;

        // The buffer usage for each attribute is determined based on the usage of the attribute over time.
        this._buffersUsage = [
                              BufferUsage.STATIC_DRAW, // SHOW_INDEX
                              BufferUsage.STATIC_DRAW, // POSITION_INDEX
                              BufferUsage.STATIC_DRAW, // COLOR_INDEX
                              BufferUsage.STATIC_DRAW, // OUTLINE_COLOR_INDEX
                              BufferUsage.STATIC_DRAW, // OUTLINE_WIDTH_INDEX
                              BufferUsage.STATIC_DRAW, // PIXEL_SIZE_INDEX
                              BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX
                              BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX
                              BufferUsage.STATIC_DRAW  // DISTANCE_DISPLAY_CONDITION_INDEX
                          ];

        var that = this;
        this._uniforms = {
            u_maxTotalPointSize : function() {
                return that._maxTotalPointSize;
            }
        };
    }

    defineProperties(PointPrimitiveCollection.prototype, {
        /**
         * Returns the number of points in this collection.  This is commonly used with
         * {@link PointPrimitiveCollection#get} to iterate over all the points
         * in the collection.
         * @memberof PointPrimitiveCollection.prototype
         * @type {Number}
         */
        length : {
            get : function() {
                removePointPrimitives(this);
                return this._pointPrimitives.length;
            }
        }
    });

    function destroyPointPrimitives(pointPrimitives) {
        var length = pointPrimitives.length;
        for (var i = 0; i < length; ++i) {
            if (pointPrimitives[i]) {
                pointPrimitives[i]._destroy();
            }
        }
    }

    /**
     * Creates and adds a point with the specified initial properties to the collection.
     * The added point is returned so it can be modified or removed from the collection later.
     *
     * @param {Object}[pointPrimitive] A template describing the point's properties as shown in Example 1.
     * @returns {PointPrimitive} The point that was added to the collection.
     *
     * @performance Calling <code>add</code> is expected constant time.  However, the collection's vertex buffer
     * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.  For
     * best performance, add as many pointPrimitives as possible before calling <code>update</code>.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Example 1:  Add a point, specifying all the default values.
     * var p = pointPrimitives.add({
     *   show : true,
     *   position : Cesium.Cartesian3.ZERO,
     *   pixelSize : 10.0,
     *   color : Cesium.Color.WHITE,
     *   outlineColor : Cesium.Color.TRANSPARENT,
     *   outlineWidth : 0.0,
     *   id : undefined
     * });
     *
     * @example
     * // Example 2:  Specify only the point's cartographic position.
     * var p = pointPrimitives.add({
     *   position : Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
     * });
     *
     * @see PointPrimitiveCollection#remove
     * @see PointPrimitiveCollection#removeAll
     */
    PointPrimitiveCollection.prototype.add = function(pointPrimitive) {
        var p = new PointPrimitive(pointPrimitive, this);
        p._index = this._pointPrimitives.length;

        this._pointPrimitives.push(p);
        this._createVertexArray = true;

        return p;
    };

    /**
     * Removes a point from the collection.
     *
     * @param {PointPrimitive} pointPrimitive The point to remove.
     * @returns {Boolean} <code>true</code> if the point was removed; <code>false</code> if the point was not found in the collection.
     *
     * @performance Calling <code>remove</code> is expected constant time.  However, the collection's vertex buffer
     * is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.  For
     * best performance, remove as many points as possible before calling <code>update</code>.
     * If you intend to temporarily hide a point, it is usually more efficient to call
     * {@link PointPrimitive#show} instead of removing and re-adding the point.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * var p = pointPrimitives.add(...);
     * pointPrimitives.remove(p);  // Returns true
     *
     * @see PointPrimitiveCollection#add
     * @see PointPrimitiveCollection#removeAll
     * @see PointPrimitive#show
     */
    PointPrimitiveCollection.prototype.remove = function(pointPrimitive) {
        if (this.contains(pointPrimitive)) {
            this._pointPrimitives[pointPrimitive._index] = null; // Removed later
            this._pointPrimitivesRemoved = true;
            this._createVertexArray = true;
            pointPrimitive._destroy();
            return true;
        }

        return false;
    };

    /**
     * Removes all points from the collection.
     *
     * @performance <code>O(n)</code>.  It is more efficient to remove all the points
     * from a collection and then add new ones than to create a new collection entirely.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * pointPrimitives.add(...);
     * pointPrimitives.add(...);
     * pointPrimitives.removeAll();
     *
     * @see PointPrimitiveCollection#add
     * @see PointPrimitiveCollection#remove
     */
    PointPrimitiveCollection.prototype.removeAll = function() {
        destroyPointPrimitives(this._pointPrimitives);
        this._pointPrimitives = [];
        this._pointPrimitivesToUpdate = [];
        this._pointPrimitivesToUpdateIndex = 0;
        this._pointPrimitivesRemoved = false;

        this._createVertexArray = true;
    };

    function removePointPrimitives(pointPrimitiveCollection) {
        if (pointPrimitiveCollection._pointPrimitivesRemoved) {
            pointPrimitiveCollection._pointPrimitivesRemoved = false;

            var newPointPrimitives = [];
            var pointPrimitives = pointPrimitiveCollection._pointPrimitives;
            var length = pointPrimitives.length;
            for (var i = 0, j = 0; i < length; ++i) {
                var pointPrimitive = pointPrimitives[i];
                if (pointPrimitive) {
                    pointPrimitive._index = j++;
                    newPointPrimitives.push(pointPrimitive);
                }
            }

            pointPrimitiveCollection._pointPrimitives = newPointPrimitives;
        }
    }

    PointPrimitiveCollection.prototype._updatePointPrimitive = function(pointPrimitive, propertyChanged) {
        if (!pointPrimitive._dirty) {
            this._pointPrimitivesToUpdate[this._pointPrimitivesToUpdateIndex++] = pointPrimitive;
        }

        ++this._propertiesChanged[propertyChanged];
    };

    /**
     * Check whether this collection contains a given point.
     *
     * @param {PointPrimitive} [pointPrimitive] The point to check for.
     * @returns {Boolean} true if this collection contains the point, false otherwise.
     *
     * @see PointPrimitiveCollection#get
     */
    PointPrimitiveCollection.prototype.contains = function(pointPrimitive) {
        return defined(pointPrimitive) && pointPrimitive._pointPrimitiveCollection === this;
    };

    /**
     * Returns the point in the collection at the specified index.  Indices are zero-based
     * and increase as points are added.  Removing a point shifts all points after
     * it to the left, changing their indices.  This function is commonly used with
     * {@link PointPrimitiveCollection#length} to iterate over all the points
     * in the collection.
     *
     * @param {Number} index The zero-based index of the point.
     * @returns {PointPrimitive} The point at the specified index.
     *
     * @performance Expected constant time.  If points were removed from the collection and
     * {@link PointPrimitiveCollection#update} was not called, an implicit <code>O(n)</code>
     * operation is performed.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Toggle the show property of every point in the collection
     * var len = pointPrimitives.length;
     * for (var i = 0; i < len; ++i) {
     *   var p = pointPrimitives.get(i);
     *   p.show = !p.show;
     * }
     *
     * @see PointPrimitiveCollection#length
     */
    PointPrimitiveCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        removePointPrimitives(this);
        return this._pointPrimitives[index];
    };

    PointPrimitiveCollection.prototype.computeNewBuffersUsage = function() {
        var buffersUsage = this._buffersUsage;
        var usageChanged = false;

        var properties = this._propertiesChanged;
        for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
            var newUsage = (properties[k] === 0) ? BufferUsage.STATIC_DRAW : BufferUsage.STREAM_DRAW;
            usageChanged = usageChanged || (buffersUsage[k] !== newUsage);
            buffersUsage[k] = newUsage;
        }

        return usageChanged;
    };

    function createVAF(context, numberOfPointPrimitives, buffersUsage) {
        return new VertexArrayFacade(context, [{
            index : attributeLocations.positionHighAndSize,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[POSITION_INDEX]
        }, {
            index : attributeLocations.positionLowAndShow,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[POSITION_INDEX]
        }, {
            index : attributeLocations.compressedAttribute0,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[COLOR_INDEX]
        }, {
            index : attributeLocations.compressedAttribute1,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[TRANSLUCENCY_BY_DISTANCE_INDEX]
        }, {
            index : attributeLocations.scaleByDistance,
            componentsPerAttribute : 4,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[SCALE_BY_DISTANCE_INDEX]
        }, {
            index : attributeLocations.distanceDisplayCondition,
            componentsPerAttribute : 2,
            componentDatatype : ComponentDatatype.FLOAT,
            usage : buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX]
        }], numberOfPointPrimitives); // 1 vertex per pointPrimitive
    }

    ///////////////////////////////////////////////////////////////////////////

    // PERFORMANCE_IDEA:  Save memory if a property is the same for all pointPrimitives, use a latched attribute state,
    // instead of storing it in a vertex buffer.

    var writePositionScratch = new EncodedCartesian3();

    function writePositionSizeAndOutline(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
        var i = pointPrimitive._index;
        var position = pointPrimitive._getActualPosition();

        if (pointPrimitiveCollection._mode === SceneMode.SCENE3D) {
            BoundingSphere.expand(pointPrimitiveCollection._baseVolume, position, pointPrimitiveCollection._baseVolume);
            pointPrimitiveCollection._boundingVolumeDirty = true;
        }

        EncodedCartesian3.fromCartesian(position, writePositionScratch);
        var pixelSize = pointPrimitive.pixelSize;
        var outlineWidth = pointPrimitive.outlineWidth;

        pointPrimitiveCollection._maxPixelSize = Math.max(pointPrimitiveCollection._maxPixelSize, pixelSize + outlineWidth);

        var positionHighWriter = vafWriters[attributeLocations.positionHighAndSize];
        var high = writePositionScratch.high;
        positionHighWriter(i, high.x, high.y, high.z, pixelSize);

        var positionLowWriter = vafWriters[attributeLocations.positionLowAndOutline];
        var low = writePositionScratch.low;
        positionLowWriter(i, low.x, low.y, low.z, outlineWidth);
    }

    var LEFT_SHIFT16 = 65536.0; // 2^16
    var LEFT_SHIFT8 = 256.0;    // 2^8

    function writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
        var i = pointPrimitive._index;

        var color = pointPrimitive.color;
        var pickColor = pointPrimitive.getPickId(context).color;
        var outlineColor = pointPrimitive.outlineColor;

        var red = Color.floatToByte(color.red);
        var green = Color.floatToByte(color.green);
        var blue = Color.floatToByte(color.blue);
        var compressed0 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;

        red = Color.floatToByte(outlineColor.red);
        green = Color.floatToByte(outlineColor.green);
        blue = Color.floatToByte(outlineColor.blue);
        var compressed1 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;

        red = Color.floatToByte(pickColor.red);
        green = Color.floatToByte(pickColor.green);
        blue = Color.floatToByte(pickColor.blue);
        var compressed2 = red * LEFT_SHIFT16 + green * LEFT_SHIFT8 + blue;

        var compressed3 =
            Color.floatToByte(color.alpha) * LEFT_SHIFT16 +
            Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT8 +
            Color.floatToByte(pickColor.alpha);

        var writer = vafWriters[attributeLocations.compressedAttribute0];
        writer(i, compressed0, compressed1, compressed2, compressed3);
    }

    function writeCompressedAttrib1(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
        var i = pointPrimitive._index;

        var near = 0.0;
        var nearValue = 1.0;
        var far = 1.0;
        var farValue = 1.0;

        var translucency = pointPrimitive.translucencyByDistance;
        if (defined(translucency)) {
            near = translucency.near;
            nearValue = translucency.nearValue;
            far = translucency.far;
            farValue = translucency.farValue;

            if (nearValue !== 1.0 || farValue !== 1.0) {
                // translucency by distance calculation in shader need not be enabled
                // until a pointPrimitive with near and far !== 1.0 is found
                pointPrimitiveCollection._shaderTranslucencyByDistance = true;
            }
        }

        var show = pointPrimitive.show && pointPrimitive.clusterShow;

        // If the color alphas are zero, do not show this pointPrimitive.  This lets us avoid providing
        // color during the pick pass and also eliminates a discard in the fragment shader.
        if (pointPrimitive.color.alpha === 0.0 && pointPrimitive.outlineColor.alpha === 0.0) {
            show = false;
        }

        nearValue = CesiumMath.clamp(nearValue, 0.0, 1.0);
        nearValue = nearValue === 1.0 ? 255.0 : (nearValue * 255.0) | 0;
        var compressed0 = (show ? 1.0 : 0.0) * LEFT_SHIFT8 + nearValue;

        farValue = CesiumMath.clamp(farValue, 0.0, 1.0);
        farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
        var compressed1 = farValue;

        var writer = vafWriters[attributeLocations.compressedAttribute1];
        writer(i, compressed0, compressed1, near, far);
    }

    function writeScaleByDistance(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
        var i = pointPrimitive._index;
        var writer = vafWriters[attributeLocations.scaleByDistance];
        var near = 0.0;
        var nearValue = 1.0;
        var far = 1.0;
        var farValue = 1.0;

        var scale = pointPrimitive.scaleByDistance;
        if (defined(scale)) {
            near = scale.near;
            nearValue = scale.nearValue;
            far = scale.far;
            farValue = scale.farValue;

            if (nearValue !== 1.0 || farValue !== 1.0) {
                // scale by distance calculation in shader need not be enabled
                // until a pointPrimitive with near and far !== 1.0 is found
                pointPrimitiveCollection._shaderScaleByDistance = true;
            }
        }

        writer(i, near, nearValue, far, farValue);
    }

    function writeDistanceDisplayCondition(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
        var i = pointPrimitive._index;
        var writer = vafWriters[attributeLocations.distanceDisplayCondition];
        var near = 0.0;
        var far = Number.MAX_VALUE;

        var distanceDisplayCondition = pointPrimitive.distanceDisplayCondition;
        if (defined(distanceDisplayCondition)) {
            near = distanceDisplayCondition.near;
            far = distanceDisplayCondition.far;
            pointPrimitiveCollection._shaderDistanceDisplayCondition = true;
        }

        writer(i, near, far);
    }

    function writePointPrimitive(pointPrimitiveCollection, context, vafWriters, pointPrimitive) {
        writePositionSizeAndOutline(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
        writeCompressedAttrib0(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
        writeCompressedAttrib1(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
        writeScaleByDistance(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
        writeDistanceDisplayCondition(pointPrimitiveCollection, context, vafWriters, pointPrimitive);
    }

    function recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, length, frameState, modelMatrix, recomputeBoundingVolume) {
        var boundingVolume;
        if (frameState.mode === SceneMode.SCENE3D) {
            boundingVolume = pointPrimitiveCollection._baseVolume;
            pointPrimitiveCollection._boundingVolumeDirty = true;
        } else {
            boundingVolume = pointPrimitiveCollection._baseVolume2D;
        }

        var positions = [];
        for ( var i = 0; i < length; ++i) {
            var pointPrimitive = pointPrimitives[i];
            var position = pointPrimitive.position;
            var actualPosition = PointPrimitive._computeActualPosition(position, frameState, modelMatrix);
            if (defined(actualPosition)) {
                pointPrimitive._setActualPosition(actualPosition);

                if (recomputeBoundingVolume) {
                    positions.push(actualPosition);
                } else {
                    BoundingSphere.expand(boundingVolume, actualPosition, boundingVolume);
                }
            }
        }

        if (recomputeBoundingVolume) {
            BoundingSphere.fromPoints(positions, boundingVolume);
        }
    }

    function updateMode(pointPrimitiveCollection, frameState) {
        var mode = frameState.mode;

        var pointPrimitives = pointPrimitiveCollection._pointPrimitives;
        var pointPrimitivesToUpdate = pointPrimitiveCollection._pointPrimitivesToUpdate;
        var modelMatrix = pointPrimitiveCollection._modelMatrix;

        if (pointPrimitiveCollection._createVertexArray ||
            pointPrimitiveCollection._mode !== mode ||
            mode !== SceneMode.SCENE3D &&
            !Matrix4.equals(modelMatrix, pointPrimitiveCollection.modelMatrix)) {

            pointPrimitiveCollection._mode = mode;
            Matrix4.clone(pointPrimitiveCollection.modelMatrix, modelMatrix);
            pointPrimitiveCollection._createVertexArray = true;

            if (mode === SceneMode.SCENE3D || mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
                recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, pointPrimitives.length, frameState, modelMatrix, true);
            }
        } else if (mode === SceneMode.MORPHING) {
            recomputeActualPositions(pointPrimitiveCollection, pointPrimitives, pointPrimitives.length, frameState, modelMatrix, true);
        } else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
            recomputeActualPositions(pointPrimitiveCollection, pointPrimitivesToUpdate, pointPrimitiveCollection._pointPrimitivesToUpdateIndex, frameState, modelMatrix, false);
        }
    }

    function updateBoundingVolume(collection, frameState, boundingVolume) {
        var pixelSize = frameState.camera.getPixelSize(boundingVolume, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
        var size = pixelSize * collection._maxPixelSize;
        boundingVolume.radius += size;
    }

    var scratchWriterArray = [];

    /**
     * @private
     */
    PointPrimitiveCollection.prototype.update = function(frameState) {
        removePointPrimitives(this);

        this._maxTotalPointSize = ContextLimits.maximumAliasedPointSize;

        updateMode(this, frameState);

        var pointPrimitives = this._pointPrimitives;
        var pointPrimitivesLength = pointPrimitives.length;
        var pointPrimitivesToUpdate = this._pointPrimitivesToUpdate;
        var pointPrimitivesToUpdateLength = this._pointPrimitivesToUpdateIndex;

        var properties = this._propertiesChanged;

        var createVertexArray = this._createVertexArray;

        var vafWriters;
        var context = frameState.context;
        var pass = frameState.passes;
        var picking = pass.pick;

        // PERFORMANCE_IDEA: Round robin multiple buffers.
        if (createVertexArray || (!picking && this.computeNewBuffersUsage())) {
            this._createVertexArray = false;

            for (var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
                properties[k] = 0;
            }

            this._vaf = this._vaf && this._vaf.destroy();

            if (pointPrimitivesLength > 0) {
                // PERFORMANCE_IDEA:  Instead of creating a new one, resize like std::vector.
                this._vaf = createVAF(context, pointPrimitivesLength, this._buffersUsage);
                vafWriters = this._vaf.writers;

                // Rewrite entire buffer if pointPrimitives were added or removed.
                for (var i = 0; i < pointPrimitivesLength; ++i) {
                    var pointPrimitive = this._pointPrimitives[i];
                    pointPrimitive._dirty = false; // In case it needed an update.
                    writePointPrimitive(this, context, vafWriters, pointPrimitive);
                }

                this._vaf.commit();
            }

            this._pointPrimitivesToUpdateIndex = 0;
        } else {
            // PointPrimitives were modified, but none were added or removed.
            if (pointPrimitivesToUpdateLength > 0) {
                var writers = scratchWriterArray;
                writers.length = 0;

                if (properties[POSITION_INDEX] || properties[OUTLINE_WIDTH_INDEX] || properties[PIXEL_SIZE_INDEX]) {
                    writers.push(writePositionSizeAndOutline);
                }

                if (properties[COLOR_INDEX] || properties[OUTLINE_COLOR_INDEX]) {
                    writers.push(writeCompressedAttrib0);
                }

                if (properties[SHOW_INDEX] || properties[TRANSLUCENCY_BY_DISTANCE_INDEX]) {
                    writers.push(writeCompressedAttrib1);
                }

                if (properties[SCALE_BY_DISTANCE_INDEX]) {
                    writers.push(writeScaleByDistance);
                }

                if (properties[DISTANCE_DISPLAY_CONDITION_INDEX]) {
                    writers.push(writeDistanceDisplayCondition);
                }

                var numWriters = writers.length;

                vafWriters = this._vaf.writers;

                if ((pointPrimitivesToUpdateLength / pointPrimitivesLength) > 0.1) {
                    // If more than 10% of pointPrimitive change, rewrite the entire buffer.

                    // PERFORMANCE_IDEA:  I totally made up 10% :).

                    for (var m = 0; m < pointPrimitivesToUpdateLength; ++m) {
                        var b = pointPrimitivesToUpdate[m];
                        b._dirty = false;

                        for ( var n = 0; n < numWriters; ++n) {
                            writers[n](this, context, vafWriters, b);
                        }
                    }
                    this._vaf.commit();
                } else {
                    for (var h = 0; h < pointPrimitivesToUpdateLength; ++h) {
                        var bb = pointPrimitivesToUpdate[h];
                        bb._dirty = false;

                        for ( var o = 0; o < numWriters; ++o) {
                            writers[o](this, context, vafWriters, bb);
                        }
                        this._vaf.subCommit(bb._index, 1);
                    }
                    this._vaf.endSubCommits();
                }

                this._pointPrimitivesToUpdateIndex = 0;
            }
        }

        // If the number of total pointPrimitives ever shrinks considerably
        // Truncate pointPrimitivesToUpdate so that we free memory that we're
        // not going to be using.
        if (pointPrimitivesToUpdateLength > pointPrimitivesLength * 1.5) {
            pointPrimitivesToUpdate.length = pointPrimitivesLength;
        }

        if (!defined(this._vaf) || !defined(this._vaf.va)) {
            return;
        }

        if (this._boundingVolumeDirty) {
            this._boundingVolumeDirty = false;
            BoundingSphere.transform(this._baseVolume, this.modelMatrix, this._baseVolumeWC);
        }

        var boundingVolume;
        var modelMatrix = Matrix4.IDENTITY;
        if (frameState.mode === SceneMode.SCENE3D) {
            modelMatrix = this.modelMatrix;
            boundingVolume = BoundingSphere.clone(this._baseVolumeWC, this._boundingVolume);
        } else {
            boundingVolume = BoundingSphere.clone(this._baseVolume2D, this._boundingVolume);
        }
        updateBoundingVolume(this, frameState, boundingVolume);

        var blendOptionChanged = this._blendOption !== this.blendOption;
        this._blendOption = this.blendOption;

        if (blendOptionChanged) {
            if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
                this._rsOpaque = RenderState.fromCache({
                    depthTest : {
                        enabled : true,
                        func : WebGLConstants.LEQUAL
                    },
                    depthMask : true
                });
            } else {
                this._rsOpaque = undefined;
            }

            if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
                this._rsTranslucent = RenderState.fromCache({
                    depthTest : {
                        enabled : true,
                        func : WebGLConstants.LEQUAL
                    },
                    depthMask : false,
                    blending : BlendingState.ALPHA_BLEND
                });
            } else {
                this._rsTranslucent = undefined;
            }
        }

        if (blendOptionChanged ||
            (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) ||
            (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) ||
            (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition)) {

            vs = new ShaderSource({
                sources : [PointPrimitiveCollectionVS]
            });
            if (this._shaderScaleByDistance) {
                vs.defines.push('EYE_DISTANCE_SCALING');
            }
            if (this._shaderTranslucencyByDistance) {
                vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
            }
            if (this._shaderDistanceDisplayCondition) {
                vs.defines.push('DISTANCE_DISPLAY_CONDITION');
            }

            if (this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) {
                fs = new ShaderSource({
                    defines : ['OPAQUE'],
                    sources : [PointPrimitiveCollectionFS]
                });
                this._sp = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._sp,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });

                fs = new ShaderSource({
                    defines : ['TRANSLUCENT'],
                    sources : [PointPrimitiveCollectionFS]
                });
                this._spTranslucent = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._spTranslucent,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });
            }

            if (this._blendOption === BlendOption.OPAQUE) {
                fs = new ShaderSource({
                    sources : [PointPrimitiveCollectionFS]
                });
                this._sp = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._sp,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });
            }

            if (this._blendOption === BlendOption.TRANSLUCENT) {
                fs = new ShaderSource({
                    sources : [PointPrimitiveCollectionFS]
                });
                this._spTranslucent = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._spTranslucent,
                    vertexShaderSource : vs,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });
            }

            this._compiledShaderScaleByDistance = this._shaderScaleByDistance;
            this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance;
            this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition;
        }

        if (!defined(this._spPick) ||
            (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) ||
            (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) ||
            (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick)) {

            vs = new ShaderSource({
                defines : ['RENDER_FOR_PICK'],
                sources : [PointPrimitiveCollectionVS]
            });

            if (this._shaderScaleByDistance) {
                vs.defines.push('EYE_DISTANCE_SCALING');
            }
            if (this._shaderTranslucencyByDistance) {
                vs.defines.push('EYE_DISTANCE_TRANSLUCENCY');
            }
            if (this._shaderDistanceDisplayCondition) {
                vs.defines.push('DISTANCE_DISPLAY_CONDITION');
            }

            fs = new ShaderSource({
                defines : ['RENDER_FOR_PICK'],
                sources : [PointPrimitiveCollectionFS]
            });

            this._spPick = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : this._spPick,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });

            this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance;
            this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance;
            this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition;
        }

        var va;
        var vaLength;
        var command;
        var j;
        var vs;
        var fs;

        var commandList = frameState.commandList;

        if (pass.render) {
            var colorList = this._colorCommands;

            var opaque = this._blendOption === BlendOption.OPAQUE;
            var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT;

            va = this._vaf.va;
            vaLength = va.length;

            colorList.length = vaLength;
            var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength;
            for (j = 0; j < totalLength; ++j) {
                var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0);

                command = colorList[j];
                if (!defined(command)) {
                    command = colorList[j] = new DrawCommand();
                }

                command.primitiveType = PrimitiveType.POINTS;
                command.pass = opaqueCommand || !opaqueAndTranslucent ? Pass.OPAQUE : Pass.TRANSLUCENT;
                command.owner = this;

                var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j;
                command.boundingVolume = boundingVolume;
                command.modelMatrix = modelMatrix;
                command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent;
                command.uniformMap = this._uniforms;
                command.vertexArray = va[index].va;
                command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent;
                command.debugShowBoundingVolume = this.debugShowBoundingVolume;

                commandList.push(command);
            }
        }

        if (picking) {
            var pickList = this._pickCommands;

            va = this._vaf.va;
            vaLength = va.length;

            pickList.length = vaLength;
            for (j = 0; j < vaLength; ++j) {
                command = pickList[j];
                if (!defined(command)) {
                    command = pickList[j] = new DrawCommand({
                        primitiveType : PrimitiveType.POINTS,
                        pass : Pass.OPAQUE,
                        owner : this
                    });
                }

                command.boundingVolume = boundingVolume;
                command.modelMatrix = modelMatrix;
                command.shaderProgram = this._spPick;
                command.uniformMap = this._uniforms;
                command.vertexArray = va[j].va;
                command.renderState = this._rsOpaque;

                commandList.push(command);
            }
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see PointPrimitiveCollection#destroy
     */
    PointPrimitiveCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * pointPrimitives = pointPrimitives && pointPrimitives.destroy();
     *
     * @see PointPrimitiveCollection#isDestroyed
     */
    PointPrimitiveCollection.prototype.destroy = function() {
        this._sp = this._sp && this._sp.destroy();
        this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy();
        this._spPick = this._spPick && this._spPick.destroy();
        this._vaf = this._vaf && this._vaf.destroy();
        destroyPointPrimitives(this._pointPrimitives);

        return destroyObject(this);
    };

    return PointPrimitiveCollection;
});

/*global define*/
define('ThirdParty/kdbush',[], function() {
'use strict';

function kdbush(points, getX, getY, nodeSize, ArrayType) {
    return new KDBush(points, getX, getY, nodeSize, ArrayType);
}

function KDBush(points, getX, getY, nodeSize, ArrayType) {
    getX = getX || defaultGetX;
    getY = getY || defaultGetY;
    ArrayType = ArrayType || Array;

    this.nodeSize = nodeSize || 64;
    this.points = points;

    this.ids = new ArrayType(points.length);
    this.coords = new ArrayType(points.length * 2);

    for (var i = 0; i < points.length; i++) {
        this.ids[i] = i;
        this.coords[2 * i] = getX(points[i]);
        this.coords[2 * i + 1] = getY(points[i]);
    }

    sort(this.ids, this.coords, this.nodeSize, 0, this.ids.length - 1, 0);
}

KDBush.prototype = {
    range: function (minX, minY, maxX, maxY) {
        return range(this.ids, this.coords, minX, minY, maxX, maxY, this.nodeSize);
    },

    within: function (x, y, r) {
        return within(this.ids, this.coords, x, y, r, this.nodeSize);
    }
};

function defaultGetX(p) { return p[0]; }
function defaultGetY(p) { return p[1]; }

function range(ids, coords, minX, minY, maxX, maxY, nodeSize) {
    var stack = [0, ids.length - 1, 0];
    var result = [];
    var x, y;

    while (stack.length) {
        var axis = stack.pop();
        var right = stack.pop();
        var left = stack.pop();

        if (right - left <= nodeSize) {
            for (var i = left; i <= right; i++) {
                x = coords[2 * i];
                y = coords[2 * i + 1];
                if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]);
            }
            continue;
        }

        var m = Math.floor((left + right) / 2);

        x = coords[2 * m];
        y = coords[2 * m + 1];

        if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]);

        var nextAxis = (axis + 1) % 2;

        if (axis === 0 ? minX <= x : minY <= y) {
            stack.push(left);
            stack.push(m - 1);
            stack.push(nextAxis);
        }
        if (axis === 0 ? maxX >= x : maxY >= y) {
            stack.push(m + 1);
            stack.push(right);
            stack.push(nextAxis);
        }
    }

    return result;
}

function sort(ids, coords, nodeSize, left, right, depth) {
    if (right - left <= nodeSize) return;

    var m = Math.floor((left + right) / 2);

    select(ids, coords, m, left, right, depth % 2);

    sort(ids, coords, nodeSize, left, m - 1, depth + 1);
    sort(ids, coords, nodeSize, m + 1, right, depth + 1);
}

function select(ids, coords, k, left, right, inc) {

    while (right > left) {
        if (right - left > 600) {
            var n = right - left + 1;
            var m = k - left + 1;
            var z = Math.log(n);
            var s = 0.5 * Math.exp(2 * z / 3);
            var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
            var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
            var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
            select(ids, coords, k, newLeft, newRight, inc);
        }

        var t = coords[2 * k + inc];
        var i = left;
        var j = right;

        swapItem(ids, coords, left, k);
        if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right);

        while (i < j) {
            swapItem(ids, coords, i, j);
            i++;
            j--;
            while (coords[2 * i + inc] < t) i++;
            while (coords[2 * j + inc] > t) j--;
        }

        if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j);
        else {
            j++;
            swapItem(ids, coords, j, right);
        }

        if (j <= k) left = j + 1;
        if (k <= j) right = j - 1;
    }
}

function swapItem(ids, coords, i, j) {
    swap(ids, i, j);
    swap(coords, 2 * i, 2 * j);
    swap(coords, 2 * i + 1, 2 * j + 1);
}

function swap(arr, i, j) {
    var tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

function within(ids, coords, qx, qy, r, nodeSize) {
    var stack = [0, ids.length - 1, 0];
    var result = [];
    var r2 = r * r;

    while (stack.length) {
        var axis = stack.pop();
        var right = stack.pop();
        var left = stack.pop();

        if (right - left <= nodeSize) {
            for (var i = left; i <= right; i++) {
                if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]);
            }
            continue;
        }

        var m = Math.floor((left + right) / 2);

        var x = coords[2 * m];
        var y = coords[2 * m + 1];

        if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]);

        var nextAxis = (axis + 1) % 2;

        if (axis === 0 ? qx - r <= x : qy - r <= y) {
            stack.push(left);
            stack.push(m - 1);
            stack.push(nextAxis);
        }
        if (axis === 0 ? qx + r >= x : qy + r >= y) {
            stack.push(m + 1);
            stack.push(right);
            stack.push(nextAxis);
        }
    }

    return result;
}

function sqDist(ax, ay, bx, by) {
    var dx = ax - bx;
    var dy = ay - by;
    return dx * dx + dy * dy;
}

return kdbush;
});

/*global define*/
define('DataSources/EntityCluster',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/EllipsoidalOccluder',
        '../Core/Event',
        '../Core/Matrix4',
        '../Scene/Billboard',
        '../Scene/BillboardCollection',
        '../Scene/Label',
        '../Scene/LabelCollection',
        '../Scene/PointPrimitive',
        '../Scene/PointPrimitiveCollection',
        '../ThirdParty/kdbush'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        EllipsoidalOccluder,
        Event,
        Matrix4,
        Billboard,
        BillboardCollection,
        Label,
        LabelCollection,
        PointPrimitive,
        PointPrimitiveCollection,
        kdbush) {
    'use strict';

    /**
     * Defines how screen space objects (billboards, points, labels) are clustered.
     *
     * @param {Object} [options] An object with the following properties:
     * @param {Boolean} [options.enabled=false] Whether or not to enable clustering.
     * @param {Number} [options.pixelRange=80] The pixel range to extend the screen space bounding box.
     * @param {Number} [options.minimumClusterSize=2] The minimum number of screen space objects that can be clustered.
     * @param {Boolean} [options.clusterBillboards=true] Whether or not to cluster the billboards of an entity.
     * @param {Boolean} [options.clusterLabels=true] Whether or not to cluster the labels of an entity.
     * @param {Boolean} [options.clusterPoints=true] Whether or not to cluster the points of an entity.
     *
     * @alias EntityCluster
     * @constructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Clustering.html|Cesium Sandcastle Clustering Demo}
     */
    function EntityCluster(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._enabled = defaultValue(options.enabled, false);
        this._pixelRange = defaultValue(options.pixelRange, 80);
        this._minimumClusterSize = defaultValue(options.minimumClusterSize, 2);
        this._clusterBillboards = defaultValue(options.clusterBillboards, true);
        this._clusterLabels = defaultValue(options.clusterLabels, true);
        this._clusterPoints = defaultValue(options.clusterPoints, true);

        this._labelCollection = undefined;
        this._billboardCollection = undefined;
        this._pointCollection = undefined;

        this._clusterBillboardCollection = undefined;
        this._clusterLabelCollection = undefined;
        this._clusterPointCollection = undefined;

        this._collectionIndicesByEntity = {};

        this._unusedLabelIndices = [];
        this._unusedBillboardIndices = [];
        this._unusedPointIndices = [];

        this._previousClusters = [];
        this._previousHeight = undefined;

        this._enabledDirty = false;
        this._clusterDirty = false;

        this._cluster = undefined;
        this._removeEventListener = undefined;

        this._clusterEvent = new Event();
    }

    function getX(point) {
        return point.coord.x;
    }

    function getY(point) {
        return point.coord.y;
    }

    function expandBoundingBox(bbox, pixelRange) {
        bbox.x -= pixelRange;
        bbox.y -= pixelRange;
        bbox.width += pixelRange * 2.0;
        bbox.height += pixelRange * 2.0;
    }

    var labelBoundingBoxScratch = new BoundingRectangle();

    function getBoundingBox(item, coord, pixelRange, entityCluster, result) {
        if (defined(item._labelCollection) && entityCluster._clusterLabels) {
            result = Label.getScreenSpaceBoundingBox(item, coord, result);
        } else if (defined(item._billboardCollection) && entityCluster._clusterBillboards) {
            result = Billboard.getScreenSpaceBoundingBox(item, coord, result);
        } else if (defined(item._pointPrimitiveCollection) && entityCluster._clusterPoints) {
            result = PointPrimitive.getScreenSpaceBoundingBox(item, coord, result);
        }

        expandBoundingBox(result, pixelRange);

        if (entityCluster._clusterLabels && !defined(item._labelCollection) && defined(item.id) && hasLabelIndex(entityCluster, item.id) && defined(item.id._label)) {
            var labelIndex = entityCluster._collectionIndicesByEntity[item.id];
            var label = entityCluster._labelCollection.get(labelIndex);
            var labelBBox = Label.getScreenSpaceBoundingBox(label, coord, labelBoundingBoxScratch);
            expandBoundingBox(labelBBox, pixelRange);
            result = BoundingRectangle.union(result, labelBBox, result);
        }

        return result;
    }

    function addNonClusteredItem(item, entityCluster) {
        item.clusterShow = true;

        if (!defined(item._labelCollection) && defined(item.id) && hasLabelIndex(entityCluster, item.id) && defined(item.id._label)) {
            var labelIndex = entityCluster._collectionIndicesByEntity[item.id];
            var label = entityCluster._labelCollection.get(labelIndex);
            label.clusterShow = true;
        }
    }

    function addCluster(position, numPoints, ids, entityCluster) {
        var cluster = {
            billboard : entityCluster._clusterBillboardCollection.add(),
            label : entityCluster._clusterLabelCollection.add(),
            point : entityCluster._clusterPointCollection.add()
        };

        cluster.billboard.show = false;
        cluster.point.show = false;
        cluster.label.show = true;
        cluster.label.text = numPoints.toLocaleString();
        cluster.billboard.position = cluster.label.position = cluster.point.position = position;

        entityCluster._clusterEvent.raiseEvent(ids, cluster);
    }

    function hasLabelIndex(entityCluster, entityId) {
        return defined(entityCluster) && defined(entityCluster._collectionIndicesByEntity[entityId]) && defined(entityCluster._collectionIndicesByEntity[entityId].labelIndex);
    }

    function getScreenSpacePositions(collection, points, scene, occluder, entityCluster) {
        if (!defined(collection)) {
            return;
        }

        var length = collection.length;
        for (var i = 0; i < length; ++i) {
            var item = collection.get(i);
            item.clusterShow = false;

            if (!item.show || !occluder.isPointVisible(item.position)) {
                continue;
            }

            var canClusterLabels = entityCluster._clusterLabels && defined(item._labelCollection);
            var canClusterBillboards = entityCluster._clusterBillboards && defined(item.id._billboard);
            var canClusterPoints = entityCluster._clusterPoints && defined(item.id._point);
            if (canClusterLabels && (canClusterPoints || canClusterBillboards)) {
                continue;
            }

            var coord = item.computeScreenSpacePosition(scene);
            if (!defined(coord)) {
                continue;
            }

            points.push({
                index : i,
                collection : collection,
                clustered : false,
                coord : coord
            });
        }
    }

    var pointBoundinRectangleScratch = new BoundingRectangle();
    var totalBoundingRectangleScratch = new BoundingRectangle();
    var neighborBoundingRectangleScratch = new BoundingRectangle();

    function createDeclutterCallback(entityCluster) {
        return function(amount) {
            if ((defined(amount) && amount < 0.05) || !entityCluster.enabled) {
                return;
            }

            var scene = entityCluster._scene;

            var labelCollection = entityCluster._labelCollection;
            var billboardCollection = entityCluster._billboardCollection;
            var pointCollection = entityCluster._pointCollection;

            if ((!defined(labelCollection) && !defined(billboardCollection) && !defined(pointCollection)) ||
                (!entityCluster._clusterBillboards && !entityCluster._clusterLabels && !entityCluster._clusterPoints)) {
                return;
            }

            var clusteredLabelCollection = entityCluster._clusterLabelCollection;
            var clusteredBillboardCollection = entityCluster._clusterBillboardCollection;
            var clusteredPointCollection = entityCluster._clusterPointCollection;

            if (defined(clusteredLabelCollection)) {
                clusteredLabelCollection.removeAll();
            } else {
                clusteredLabelCollection = entityCluster._clusterLabelCollection = new LabelCollection({
                    scene : scene
                });
            }

            if (defined(clusteredBillboardCollection)) {
                clusteredBillboardCollection.removeAll();
            } else {
                clusteredBillboardCollection = entityCluster._clusterBillboardCollection = new BillboardCollection({
                    scene : scene
                });
            }

            if (defined(clusteredPointCollection)) {
                clusteredPointCollection.removeAll();
            } else {
                clusteredPointCollection = entityCluster._clusterPointCollection = new PointPrimitiveCollection();
            }

            var pixelRange = entityCluster._pixelRange;
            var minimumClusterSize = entityCluster._minimumClusterSize;

            var clusters = entityCluster._previousClusters;
            var newClusters = [];

            var previousHeight = entityCluster._previousHeight;
            var currentHeight = scene.camera.positionCartographic.height;

            var ellipsoid = scene.mapProjection.ellipsoid;
            var cameraPosition = scene.camera.positionWC;
            var occluder = new EllipsoidalOccluder(ellipsoid, cameraPosition);

            var points = [];
            if (entityCluster._clusterLabels) {
                getScreenSpacePositions(labelCollection, points, scene, occluder, entityCluster);
            }
            if (entityCluster._clusterBillboards) {
                getScreenSpacePositions(billboardCollection, points, scene, occluder, entityCluster);
            }
            if (entityCluster._clusterPoints) {
                getScreenSpacePositions(pointCollection, points, scene, occluder, entityCluster);
            }

            var i;
            var j;
            var length;
            var bbox;
            var neighbors;
            var neighborLength;
            var neighborIndex;
            var neighborPoint;
            var ids;
            var numPoints;

            var collection;
            var collectionIndex;

            var index = kdbush(points, getX, getY, 64, Int32Array);

            if (currentHeight < previousHeight) {
                length = clusters.length;
                for (i = 0; i < length; ++i) {
                    var cluster = clusters[i];

                    if (!occluder.isPointVisible(cluster.position)) {
                        continue;
                    }

                    var coord = Billboard._computeScreenSpacePosition(Matrix4.IDENTITY, cluster.position, Cartesian3.ZERO, Cartesian2.ZERO, scene);
                    if (!defined(coord)) {
                        continue;
                    }

                    var factor = 1.0 - currentHeight / previousHeight;
                    var width = cluster.width = cluster.width * factor;
                    var height = cluster.height = cluster.height * factor;

                    width = Math.max(width, cluster.minimumWidth);
                    height = Math.max(height, cluster.minimumHeight);

                    var minX = coord.x - width * 0.5;
                    var minY = coord.y - height * 0.5;
                    var maxX = coord.x + width;
                    var maxY = coord.y + height;

                    neighbors = index.range(minX, minY, maxX, maxY);
                    neighborLength = neighbors.length;
                    numPoints = 0;
                    ids = [];

                    for (j = 0; j < neighborLength; ++j) {
                        neighborIndex = neighbors[j];
                        neighborPoint = points[neighborIndex];
                        if (!neighborPoint.clustered) {
                            ++numPoints;

                            collection = neighborPoint.collection;
                            collectionIndex = neighborPoint.index;
                            ids.push(collection.get(collectionIndex).id);
                        }
                    }

                    if (numPoints >= minimumClusterSize) {
                        addCluster(cluster.position, numPoints, ids, entityCluster);
                        newClusters.push(cluster);

                        for (j = 0; j < neighborLength; ++j) {
                            points[neighbors[j]].clustered = true;
                        }
                    }
                }
            }

            length = points.length;
            for (i = 0; i < length; ++i) {
                var point = points[i];
                if (point.clustered) {
                    continue;
                }

                point.clustered = true;

                collection = point.collection;
                collectionIndex = point.index;

                var item = collection.get(collectionIndex);
                bbox = getBoundingBox(item, point.coord, pixelRange, entityCluster, pointBoundinRectangleScratch);
                var totalBBox = BoundingRectangle.clone(bbox, totalBoundingRectangleScratch);

                neighbors = index.range(bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height);
                neighborLength = neighbors.length;

                var clusterPosition = Cartesian3.clone(item.position);
                numPoints = 1;
                ids = [item.id];

                for (j = 0; j < neighborLength; ++j) {
                    neighborIndex = neighbors[j];
                    neighborPoint = points[neighborIndex];
                    if (!neighborPoint.clustered) {
                        var neighborItem = neighborPoint.collection.get(neighborPoint.index);
                        var neighborBBox = getBoundingBox(neighborItem, neighborPoint.coord, pixelRange, entityCluster, neighborBoundingRectangleScratch);

                        Cartesian3.add(neighborItem.position, clusterPosition, clusterPosition);

                        BoundingRectangle.union(totalBBox, neighborBBox, totalBBox);
                        ++numPoints;

                        ids.push(neighborItem.id);
                    }
                }

                if (numPoints >= minimumClusterSize) {
                    var position = Cartesian3.multiplyByScalar(clusterPosition, 1.0 / numPoints, clusterPosition);
                    addCluster(position, numPoints, ids, entityCluster);
                    newClusters.push({
                        position : position,
                        width : totalBBox.width,
                        height : totalBBox.height,
                        minimumWidth : bbox.width,
                        minimumHeight : bbox.height
                    });

                    for (j = 0; j < neighborLength; ++j) {
                        points[neighbors[j]].clustered = true;
                    }
                } else {
                    addNonClusteredItem(item, entityCluster);
                }
            }

            if (clusteredLabelCollection.length === 0) {
                clusteredLabelCollection.destroy();
                entityCluster._clusterLabelCollection = undefined;
            }

            if (clusteredBillboardCollection.length === 0) {
                clusteredBillboardCollection.destroy();
                entityCluster._clusterBillboardCollection = undefined;
            }

            if (clusteredPointCollection.length === 0) {
                clusteredPointCollection.destroy();
                entityCluster._clusterPointCollection = undefined;
            }

            entityCluster._previousClusters = newClusters;
            entityCluster._previousHeight = currentHeight;
        };
    }

    EntityCluster.prototype._initialize = function(scene) {
        this._scene = scene;

        var cluster = createDeclutterCallback(this);
        this._cluster = cluster;
        this._removeEventListener = scene.camera.changed.addEventListener(cluster);
    };

    defineProperties(EntityCluster.prototype, {
        /**
         * Gets or sets whether clustering is enabled.
         * @memberof EntityCluster.prototype
         * @type {Boolean}
         */
        enabled : {
            get : function() {
                return this._enabled;
            },
            set : function(value) {
                this._enabledDirty = value !== this._enabled;
                this._enabled = value;
            }
        },
        /**
         * Gets or sets the pixel range to extend the screen space bounding box.
         * @memberof EntityCluster.prototype
         * @type {Number}
         */
        pixelRange : {
            get : function() {
                return this._pixelRange;
            },
            set : function(value) {
                this._clusterDirty = this._clusterDirty || value !== this._pixelRange;
                this._pixelRange = value;
            }
        },
        /**
         * Gets or sets the minimum number of screen space objects that can be clustered.
         * @memberof EntityCluster.prototype
         * @type {Number}
         */
        minimumClusterSize : {
            get : function() {
                return this._minimumClusterSize;
            },
            set : function(value) {
                this._clusterDirty = this._clusterDirty || value !== this._minimumClusterSize;
                this._minimumClusterSize = value;
            }
        },
        /**
         * Gets the event that will be raised when a new cluster will be displayed. The signature of the event listener is {@link EntityCluster~newClusterCallback}.
         * @memberof EntityCluster.prototype
         * @type {Event}
         */
        clusterEvent : {
            get : function() {
                return this._clusterEvent;
            }
        },
        /**
         * Gets or sets whether clustering billboard entities is enabled.
         * @memberof EntityCluster.prototype
         * @type {Boolean}
         */
        clusterBillboards : {
            get : function() {
                return this._clusterBillboards;
            },
            set : function(value) {
                this._clusterDirty = this._clusterDirty || value !== this._clusterBillboards;
                this._clusterBillboards = value;
            }
        },
        /**
         * Gets or sets whether clustering labels entities is enabled.
         * @memberof EntityCluster.prototype
         * @type {Boolean}
         */
        clusterLabels : {
            get : function() {
                return this._clusterLabels;
            },
            set : function(value) {
                this._clusterDirty = this._clusterDirty || value !== this._clusterLabels;
                this._clusterLabels = value;
            }
        },
        /**
         * Gets or sets whether clustering point entities is enabled.
         * @memberof EntityCluster.prototype
         * @type {Boolean}
         */
        clusterPoints : {
            get : function() {
                return this._clusterPoints;
            },
            set : function(value) {
                this._clusterDirty = this._clusterDirty || value !== this._clusterPoints;
                this._clusterPoints = value;
            }
        }
    });

    function createGetEntity(collectionProperty, CollectionConstructor, unusedIndicesProperty, entityIndexProperty) {
        return function(entity) {
            var collection = this[collectionProperty];

            if (!defined(this._collectionIndicesByEntity)) {
                this._collectionIndicesByEntity = {};
            }

            var entityIndices = this._collectionIndicesByEntity[entity.id];

            if (!defined(entityIndices)) {
                entityIndices = this._collectionIndicesByEntity[entity.id] = {
                    billboardIndex: undefined,
                    labelIndex: undefined,
                    pointIndex: undefined
                };
            }

            if (defined(collection) && defined(entityIndices[entityIndexProperty])) {
                return collection.get(entityIndices[entityIndexProperty]);
            }

            if (!defined(collection)) {
                collection = this[collectionProperty] = new CollectionConstructor({
                    scene : this._scene
                });
            }

            var index;
            var entityItem;

            var unusedIndices = this[unusedIndicesProperty];
            if (unusedIndices.length > 0) {
                index = unusedIndices.pop();
                entityItem = collection.get(index);
            } else {
                entityItem = collection.add();
                index = collection.length - 1;
            }

            entityIndices[entityIndexProperty] = index;

            this._clusterDirty = true;

            return entityItem;
        };
    }

    function removeEntityIndicesIfUnused(entityCluster, entityId) {
        var indices = entityCluster._collectionIndicesByEntity[entityId];

        if (!defined(indices.billboardIndex) && !defined(indices.labelIndex) && !defined(indices.pointIndex)) {
            delete entityCluster._collectionIndicesByEntity[entityId];
        }
    }

    /**
     * Returns a new {@link Label}.
     * @param {Entity} entity The entity that will use the returned {@link Label} for visualization.
     * @returns {Label} The label that will be used to visualize an entity.
     *
     * @private
     */
    EntityCluster.prototype.getLabel = createGetEntity('_labelCollection', LabelCollection, '_unusedLabelIndices', 'labelIndex');


    /**
     * Removes the {@link Label} associated with an entity so it can be reused by another entity.
     * @param {Entity} entity The entity that will uses the returned {@link Label} for visualization.
     *
     * @private
     */
    EntityCluster.prototype.removeLabel = function(entity) {
        var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id];
        if (!defined(this._labelCollection) || !defined(entityIndices) || !defined(entityIndices.labelIndex)) {
            return;
        }

        var index = entityIndices.labelIndex;
        entityIndices.labelIndex = undefined;
        removeEntityIndicesIfUnused(this, entity.id);

        var label = this._labelCollection.get(index);
        label.show = false;
        label.text = '';
        label.id = undefined;

        this._unusedLabelIndices.push(index);

        this._clusterDirty = true;
    };

    /**
     * Returns a new {@link Billboard}.
     * @param {Entity} entity The entity that will use the returned {@link Billboard} for visualization.
     * @returns {Billboard} The label that will be used to visualize an entity.
     *
     * @private
     */
    EntityCluster.prototype.getBillboard = createGetEntity('_billboardCollection', BillboardCollection, '_unusedBillboardIndices', 'billboardIndex');


    /**
     * Removes the {@link Billboard} associated with an entity so it can be reused by another entity.
     * @param {Entity} entity The entity that will uses the returned {@link Billboard} for visualization.
     *
     * @private
     */
    EntityCluster.prototype.removeBillboard = function(entity) {
        var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id];
        if (!defined(this._billboardCollection) || !defined(entityIndices) || !defined(entityIndices.billboardIndex)) {
            return;
        }

        var index = entityIndices.billboardIndex;
        entityIndices.billboardIndex = undefined;
        removeEntityIndicesIfUnused(this, entity.id);

        var billboard = this._billboardCollection.get(index);
        billboard.id = undefined;
        billboard.show = false;
        billboard.image = undefined;

        this._unusedBillboardIndices.push(index);

        this._clusterDirty = true;
    };

    /**
     * Returns a new {@link Point}.
     * @param {Entity} entity The entity that will use the returned {@link Point} for visualization.
     * @returns {Point} The label that will be used to visualize an entity.
     *
     * @private
     */
    EntityCluster.prototype.getPoint = createGetEntity('_pointCollection', PointPrimitiveCollection, '_unusedPointIndices', 'pointIndex');

    /**
     * Removes the {@link Point} associated with an entity so it can be reused by another entity.
     * @param {Entity} entity The entity that will uses the returned {@link Point} for visualization.
     *
     * @private
     */
    EntityCluster.prototype.removePoint = function(entity) {
        var entityIndices = this._collectionIndicesByEntity && this._collectionIndicesByEntity[entity.id];
        if (!defined(this._pointCollection) || !defined(entityIndices) || !defined(entityIndices.pointIndex)) {
            return;
        }

        var index = entityIndices.pointIndex;
        entityIndices.pointIndex = undefined;
        removeEntityIndicesIfUnused(this, entity.id);

        var point = this._pointCollection.get(index);
        point.show = false;
        point.id = undefined;

        this._unusedPointIndices.push(index);

        this._clusterDirty = true;
    };

    function disableCollectionClustering(collection) {
        if (!defined(collection)) {
            return;
        }

        var length = collection.length;
        for (var i = 0; i < length; ++i) {
            collection.get(i).clusterShow = true;
        }
    }

    function updateEnable(entityCluster) {
        if (entityCluster.enabled) {
            return;
        }

        if (defined(entityCluster._clusterLabelCollection)) {
            entityCluster._clusterLabelCollection.destroy();
        }
        if (defined(entityCluster._clusterBillboardCollection)) {
            entityCluster._clusterBillboardCollection.destroy();
        }
        if (defined(entityCluster._clusterPointCollection)) {
            entityCluster._clusterPointCollection.destroy();
        }

        entityCluster._clusterLabelCollection = undefined;
        entityCluster._clusterBillboardCollection = undefined;
        entityCluster._clusterPointCollection = undefined;

        disableCollectionClustering(entityCluster._labelCollection);
        disableCollectionClustering(entityCluster._billboardCollection);
        disableCollectionClustering(entityCluster._pointCollection);
    }

    /**
     * Gets the draw commands for the clustered billboards/points/labels if enabled, otherwise,
     * queues the draw commands for billboards/points/labels created for entities.
     * @private
     */
    EntityCluster.prototype.update = function(frameState) {
        // If clustering is enabled before the label collection is updated,
        // the glyphs haven't been created so the screen space bounding boxes
        // are incorrect.
        if (defined(this._labelCollection) && this._labelCollection.length > 0 && this._labelCollection.get(0)._glyphs.length === 0) {
            var commandList = frameState.commandList;
            frameState.commandList = [];
            this._labelCollection.update(frameState);
            frameState.commandList = commandList;
        }

        if (this._enabledDirty) {
            this._enabledDirty = false;
            updateEnable(this);
            this._clusterDirty = true;
        }

        if (this._clusterDirty) {
            this._clusterDirty = false;
            this._cluster();
        }

        if (defined(this._clusterLabelCollection)) {
            this._clusterLabelCollection.update(frameState);
        }
        if (defined(this._clusterBillboardCollection)) {
            this._clusterBillboardCollection.update(frameState);
        }
        if (defined(this._clusterPointCollection)) {
            this._clusterPointCollection.update(frameState);
        }

        if (defined(this._labelCollection)) {
            this._labelCollection.update(frameState);
        }
        if (defined(this._billboardCollection)) {
            this._billboardCollection.update(frameState);
        }
        if (defined(this._pointCollection)) {
            this._pointCollection.update(frameState);
        }
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Unlike other objects that use WebGL resources, this object can be reused. For example, if a data source is removed
     * from a data source collection and added to another.
     * </p>
     *
     * @returns {undefined}
     */
    EntityCluster.prototype.destroy = function() {
        this._labelCollection = this._labelCollection && this._labelCollection.destroy();
        this._billboardCollection = this._billboardCollection && this._billboardCollection.destroy();
        this._pointCollection = this._pointCollection && this._pointCollection.destroy();

        this._clusterLabelCollection = this._clusterLabelCollection && this._clusterLabelCollection.destroy();
        this._clusterBillboardCollection = this._clusterBillboardCollection && this._clusterBillboardCollection.destroy();
        this._clusterPointCollection = this._clusterPointCollection && this._clusterPointCollection.destroy();

        if (defined(this._removeEventListener)) {
            this._removeEventListener();
            this._removeEventListener = undefined;
        }

        this._labelCollection = undefined;
        this._billboardCollection = undefined;
        this._pointCollection = undefined;

        this._clusterBillboardCollection = undefined;
        this._clusterLabelCollection = undefined;
        this._clusterPointCollection = undefined;

        this._collectionIndicesByEntity = undefined;

        this._unusedLabelIndices = [];
        this._unusedBillboardIndices = [];
        this._unusedPointIndices = [];

        this._previousClusters = [];
        this._previousHeight = undefined;

        this._enabledDirty = false;
        this._pixelRangeDirty = false;
        this._minimumClusterSizeDirty = false;

        return undefined;
    };

    /**
     * A event listener function used to style clusters.
     * @callback EntityCluster~newClusterCallback
     *
     * @param {Entity[]} clusteredEntities An array of the entities contained in the cluster.
     * @param {Object} cluster An object containing billboard, label, and point properties. The values are the same as
     * billboard, label and point entities, but must be the values of the ConstantProperty.
     *
     * @example
     * // The default cluster values.
     * dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) {
     *     cluster.label.show = true;
     *     cluster.label.text = entities.length.toLocaleString();
     * });
     */

    return EntityCluster;
});

/*global define*/
define('DataSources/CustomDataSource',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        './DataSource',
        './EntityCluster',
        './EntityCollection'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event,
        DataSource,
        EntityCluster,
        EntityCollection) {
    'use strict';

    /**
     * A {@link DataSource} implementation which can be used to manually manage a group of entities.
     *
     * @alias CustomDataSource
     * @constructor
     *
     * @param {String} [name] A human-readable name for this instance.
     *
     * @example
     * var dataSource = new Cesium.CustomDataSource('myData');
     *
     * var entity = dataSource.entities.add({
     *    position : Cesium.Cartesian3.fromDegrees(1, 2, 0),
     *    billboard : {
     *        image : 'image.png'
     *    }
     * });
     *
     * viewer.dataSources.add(dataSource);
     */
    function CustomDataSource(name) {
        this._name = name;
        this._clock = undefined;
        this._changed = new Event();
        this._error = new Event();
        this._isLoading = false;
        this._loading = new Event();
        this._entityCollection = new EntityCollection(this);
        this._entityCluster = new EntityCluster();
    }

    defineProperties(CustomDataSource.prototype, {
        /**
         * Gets or sets a human-readable name for this instance.
         * @memberof CustomDataSource.prototype
         * @type {String}
         */
        name : {
            get : function() {
                return this._name;
            },
            set : function(value) {
                if (this._name !== value) {
                    this._name = value;
                    this._changed.raiseEvent(this);
                }
            }
        },
        /**
         * Gets or sets the clock for this instance.
         * @memberof CustomDataSource.prototype
         * @type {DataSourceClock}
         */
        clock : {
            get : function() {
                return this._clock;
            },
            set : function(value) {
                if (this._clock !== value) {
                    this._clock = value;
                    this._changed.raiseEvent(this);
                }
            }
        },
        /**
         * Gets the collection of {@link Entity} instances.
         * @memberof CustomDataSource.prototype
         * @type {EntityCollection}
         */
        entities : {
            get : function() {
                return this._entityCollection;
            }
        },
        /**
         * Gets or sets whether the data source is currently loading data.
         * @memberof CustomDataSource.prototype
         * @type {Boolean}
         */
        isLoading : {
            get : function() {
                return this._isLoading;
            },
            set : function(value) {
                DataSource.setLoading(this, value);
            }
        },
        /**
         * Gets an event that will be raised when the underlying data changes.
         * @memberof CustomDataSource.prototype
         * @type {Event}
         */
        changedEvent : {
            get : function() {
                return this._changed;
            }
        },
        /**
         * Gets an event that will be raised if an error is encountered during processing.
         * @memberof CustomDataSource.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._error;
            }
        },
        /**
         * Gets an event that will be raised when the data source either starts or stops loading.
         * @memberof CustomDataSource.prototype
         * @type {Event}
         */
        loadingEvent : {
            get : function() {
                return this._loading;
            }
        },
        /**
         * Gets whether or not this data source should be displayed.
         * @memberof CustomDataSource.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._entityCollection.show;
            },
            set : function(value) {
                this._entityCollection.show = value;
            }
        },

        /**
         * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
         *
         * @memberof CustomDataSource.prototype
         * @type {EntityCluster}
         */
        clustering : {
            get : function() {
                return this._entityCluster;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value must be defined.');
                }
                                this._entityCluster = value;
            }
        }
    });

    return CustomDataSource;
});

/*global define*/
define('DataSources/CylinderGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/CylinderGeometry',
        '../Core/CylinderOutlineGeometry',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        CylinderGeometry,
        CylinderOutlineGeometry,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        ShowGeometryInstanceAttribute,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());

    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.length = undefined;
        this.topRadius = undefined;
        this.bottomRadius = undefined;
        this.slices = undefined;
        this.numberOfVerticalLines = undefined;
    }

    /**
     * A {@link GeometryUpdater} for cylinders.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias CylinderGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function CylinderGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(CylinderGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'cylinder', entity.cylinder, undefined);
    }

    defineProperties(CylinderGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof CylinderGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof CylinderGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(CylinderGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if outline visibility varies with simulation time.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof CylinderGeometryUpdater.prototype
         * 
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            value : true
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof CylinderGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    CylinderGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    CylinderGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    CylinderGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new CylinderGeometry(this._options),
            modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    CylinderGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new CylinderOutlineGeometry(this._options),
            modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    CylinderGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    CylinderGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    CylinderGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'cylinder')) {
            return;
        }

        var cylinder = entity.cylinder;

        if (!defined(cylinder)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = cylinder.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = cylinder.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var position = entity.position;
        var length = cylinder.length;
        var topRadius = cylinder.topRadius;
        var bottomRadius = cylinder.bottomRadius;

        var show = cylinder.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(position) || !defined(length) || !defined(topRadius) || !defined(bottomRadius))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(cylinder.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(cylinder.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(cylinder.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(cylinder.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(cylinder.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var slices = cylinder.slices;
        var outlineWidth = cylinder.outlineWidth;
        var numberOfVerticalLines = cylinder.numberOfVerticalLines;

        this._fillEnabled = fillEnabled;
        this._outlineEnabled = outlineEnabled;

        if (!position.isConstant || //
            !Property.isConstant(entity.orientation) || //
            !length.isConstant || //
            !topRadius.isConstant || //
            !bottomRadius.isConstant || //
            !Property.isConstant(slices) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(numberOfVerticalLines)) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.length = length.getValue(Iso8601.MINIMUM_VALUE);
            options.topRadius = topRadius.getValue(Iso8601.MINIMUM_VALUE);
            options.bottomRadius = bottomRadius.getValue(Iso8601.MINIMUM_VALUE);
            options.slices = defined(slices) ? slices.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.numberOfVerticalLines = defined(numberOfVerticalLines) ? numberOfVerticalLines.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    CylinderGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, geometryUpdater) {
        this._primitives = primitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        this._primitive = undefined;
        this._outlinePrimitive = undefined;

        var geometryUpdater = this._geometryUpdater;
        var entity = geometryUpdater._entity;
        var cylinder = entity.cylinder;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(cylinder.show, time, true)) {
            return;
        }

        var options = this._options;
        var modelMatrix = entity._getModelMatrix(time);
        var length = Property.getValueOrUndefined(cylinder.length, time);
        var topRadius = Property.getValueOrUndefined(cylinder.topRadius, time);
        var bottomRadius = Property.getValueOrUndefined(cylinder.bottomRadius, time);
        if (!defined(modelMatrix) || !defined(length) || !defined(topRadius) || !defined(bottomRadius)) {
            return;
        }

        options.length = length;
        options.topRadius = topRadius;
        options.bottomRadius = bottomRadius;
        options.slices = Property.getValueOrUndefined(cylinder.slices, time);
        options.numberOfVerticalLines = Property.getValueOrUndefined(cylinder.numberOfVerticalLines, time);

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
        var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        
        if (Property.getValueOrDefault(cylinder.fill, time, true)) {
            var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
            this._material = material;

            var appearance = new MaterialAppearance({
                material : material,
                translucent : material.isTranslucent(),
                closed : true
            });
            options.vertexFormat = appearance.vertexFormat;

            this._primitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new CylinderGeometry(options),
                    modelMatrix : modelMatrix,
                    attributes : {
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : appearance,
                asynchronous : false,
                shadows : shadows
            }));
        }

        if (Property.getValueOrDefault(cylinder.outline, time, false)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(cylinder.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(cylinder.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new CylinderOutlineGeometry(options),
                    modelMatrix : modelMatrix,
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return CylinderGeometryUpdater;
});

/*global define*/
define('Scene/ColorBlendMode',[
        '../Core/freezeObject',
        '../Core/Math'
], function(
        freezeObject,
        CesiumMath) {
    'use strict';

    /**
     * Defines different modes for blending between a target color and a primitive's source color.
     *
     * HIGHLIGHT multiplies the source color by the target color
     * REPLACE replaces the source color with the target color
     * MIX blends the source color and target color together
     *
     * @exports ColorBlendMode
     *
     * @see Model.colorBlendMode
     */
    var ColorBlendMode = {
        HIGHLIGHT : 0,
        REPLACE : 1,
        MIX : 2
    };

    /**
     * @private
     */
    ColorBlendMode.getColorBlend = function(colorBlendMode, colorBlendAmount) {
        if (colorBlendMode === ColorBlendMode.HIGHLIGHT) {
            return 0.0;
        } else if (colorBlendMode === ColorBlendMode.REPLACE) {
            return 1.0;
        } else if (colorBlendMode === ColorBlendMode.MIX) {
            // The value 0.0 is reserved for highlight, so clamp to just above 0.0.
            return CesiumMath.clamp(colorBlendAmount, CesiumMath.EPSILON4, 1.0);
        }
    };

    return freezeObject(ColorBlendMode);
});
/*global define*/
define('DataSources/DataSourceClock',[
        '../Core/Clock',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/JulianDate',
        './createRawPropertyDescriptor'
    ], function(
        Clock,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        JulianDate,
        createRawPropertyDescriptor) {
    'use strict';

    /**
     * Represents desired clock settings for a particular {@link DataSource}.  These settings may be applied
     * to the {@link Clock} when the DataSource is loaded.
     *
     * @alias DataSourceClock
     * @constructor
     */
    function DataSourceClock() {
        this._startTime = undefined;
        this._stopTime = undefined;
        this._currentTime = undefined;
        this._clockRange = undefined;
        this._clockStep = undefined;
        this._multiplier = undefined;
        this._definitionChanged = new Event();
    }

    defineProperties(DataSourceClock.prototype, {
        /**
         * Gets the event that is raised whenever a new property is assigned.
         * @memberof DataSourceClock.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },

        /**
         * Gets or sets the desired start time of the clock.
         * See {@link Clock#startTime}.
         * @memberof DataSourceClock.prototype
         * @type {JulianDate}
         */
        startTime : createRawPropertyDescriptor('startTime'),

        /**
         * Gets or sets the desired stop time of the clock.
         * See {@link Clock#stopTime}.
         * @memberof DataSourceClock.prototype
         * @type {JulianDate}
         */
        stopTime : createRawPropertyDescriptor('stopTime'),

        /**
         * Gets or sets the desired current time when this data source is loaded.
         * See {@link Clock#currentTime}.
         * @memberof DataSourceClock.prototype
         * @type {JulianDate}
         */
        currentTime : createRawPropertyDescriptor('currentTime'),

        /**
         * Gets or sets the desired clock range setting.
         * See {@link Clock#clockRange}.
         * @memberof DataSourceClock.prototype
         * @type {ClockRange}
         */
        clockRange : createRawPropertyDescriptor('clockRange'),

        /**
         * Gets or sets the desired clock step setting.
         * See {@link Clock#clockStep}.
         * @memberof DataSourceClock.prototype
         * @type {ClockStep}
         */
        clockStep : createRawPropertyDescriptor('clockStep'),

        /**
         * Gets or sets the desired clock multiplier.
         * See {@link Clock#multiplier}.
         * @memberof DataSourceClock.prototype
         * @type {Number}
         */
        multiplier : createRawPropertyDescriptor('multiplier')
    });

    /**
     * Duplicates a DataSourceClock instance.
     *
     * @param {DataSourceClock} [result] The object onto which to store the result.
     * @returns {DataSourceClock} The modified result parameter or a new instance if one was not provided.
     */
    DataSourceClock.prototype.clone = function(result) {
        if (!defined(result)) {
            result = new DataSourceClock();
        }
        result.startTime = this.startTime;
        result.stopTime = this.stopTime;
        result.currentTime = this.currentTime;
        result.clockRange = this.clockRange;
        result.clockStep = this.clockStep;
        result.multiplier = this.multiplier;
        return result;
    };

    /**
     * Returns true if this DataSourceClock is equivalent to the other
     *
     * @param {DataSourceClock} other The other DataSourceClock to compare to.
     * @returns {Boolean} <code>true</code> if the DataSourceClocks are equal; otherwise, <code>false</code>.
     */
    DataSourceClock.prototype.equals = function(other) {
        return this === other ||
               defined(other) &&
               JulianDate.equals(this.startTime, other.startTime) &&
               JulianDate.equals(this.stopTime, other.stopTime) &&
               JulianDate.equals(this.currentTime, other.currentTime) &&
               this.clockRange === other.clockRange &&
               this.clockStep === other.clockStep &&
               this.multiplier === other.multiplier;
    };

    /**
     * Assigns each unassigned property on this object to the value
     * of the same property on the provided source object.
     *
     * @param {DataSourceClock} source The object to be merged into this object.
     */
    DataSourceClock.prototype.merge = function(source) {
                if (!defined(source)) {
            throw new DeveloperError('source is required.');
        }
        
        this.startTime = defaultValue(this.startTime, source.startTime);
        this.stopTime = defaultValue(this.stopTime, source.stopTime);
        this.currentTime = defaultValue(this.currentTime, source.currentTime);
        this.clockRange = defaultValue(this.clockRange, source.clockRange);
        this.clockStep = defaultValue(this.clockStep, source.clockStep);
        this.multiplier = defaultValue(this.multiplier, source.multiplier);
    };

    /**
     * Gets the value of this clock instance as a {@link Clock} object.
     *
     * @returns {Clock} The modified result parameter or a new instance if one was not provided.
     */
    DataSourceClock.prototype.getValue = function(result) {
        if (!defined(result)) {
            result = new Clock();
        }
        result.startTime = defaultValue(this.startTime, result.startTime);
        result.stopTime = defaultValue(this.stopTime, result.stopTime);
        result.currentTime = defaultValue(this.currentTime, result.currentTime);
        result.clockRange = defaultValue(this.clockRange, result.clockRange);
        result.multiplier = defaultValue(this.multiplier, result.multiplier);
        result.clockStep = defaultValue(this.clockStep, result.clockStep);
        return result;
    };

    return DataSourceClock;
});

/*global define*/
define('DataSources/GridMaterialProperty',[
        '../Core/Cartesian2',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Cartesian2,
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    var defaultColor = Color.WHITE;
    var defaultCellAlpha = 0.1;
    var defaultLineCount = new Cartesian2(8, 8);
    var defaultLineOffset = new Cartesian2(0, 0);
    var defaultLineThickness = new Cartesian2(1, 1);

    /**
     * A {@link MaterialProperty} that maps to grid {@link Material} uniforms.
     * @alias GridMaterialProperty
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.color=Color.WHITE] A Property specifying the grid {@link Color}.
     * @param {Property} [options.cellAlpha=0.1] A numeric Property specifying cell alpha values.
     * @param {Property} [options.lineCount=new Cartesian2(8, 8)] A {@link Cartesian2} Property specifying the number of grid lines along each axis.
     * @param {Property} [options.lineThickness=new Cartesian2(1.0, 1.0)] A {@link Cartesian2} Property specifying the thickness of grid lines along each axis.
     * @param {Property} [options.lineOffset=new Cartesian2(0.0, 0.0)] A {@link Cartesian2} Property specifying starting offset of grid lines along each axis.
     *
     * @constructor
     */
    function GridMaterialProperty(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();
        this._color = undefined;
        this._colorSubscription = undefined;
        this._cellAlpha = undefined;
        this._cellAlphaSubscription = undefined;
        this._lineCount = undefined;
        this._lineCountSubscription = undefined;
        this._lineThickness = undefined;
        this._lineThicknessSubscription = undefined;
        this._lineOffset = undefined;
        this._lineOffsetSubscription = undefined;

        this.color = options.color;
        this.cellAlpha = options.cellAlpha;
        this.lineCount = options.lineCount;
        this.lineThickness = options.lineThickness;
        this.lineOffset = options.lineOffset;
    }

    defineProperties(GridMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof GridMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._color) &&
                       Property.isConstant(this._cellAlpha) &&
                       Property.isConstant(this._lineCount) &&
                       Property.isConstant(this._lineThickness) &&
                       Property.isConstant(this._lineOffset);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof GridMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the Property specifying the grid {@link Color}.
         * @memberof GridMaterialProperty.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color'),
        /**
         * Gets or sets the numeric Property specifying cell alpha values.
         * @memberof GridMaterialProperty.prototype
         * @type {Property}
         * @default 0.1
         */
        cellAlpha : createPropertyDescriptor('cellAlpha'),
        /**
         * Gets or sets the {@link Cartesian2} Property specifying the number of grid lines along each axis.
         * @memberof GridMaterialProperty.prototype
         * @type {Property}
         * @default new Cartesian2(8.0, 8.0)
         */
        lineCount : createPropertyDescriptor('lineCount'),
        /**
         * Gets or sets the {@link Cartesian2} Property specifying the thickness of grid lines along each axis.
         * @memberof GridMaterialProperty.prototype
         * @type {Property}
         * @default new Cartesian2(1.0, 1.0)
         */
        lineThickness : createPropertyDescriptor('lineThickness'),
        /**
         * Gets or sets the {@link Cartesian2} Property specifying the starting offset of grid lines along each axis.
         * @memberof GridMaterialProperty.prototype
         * @type {Property}
         * @default new Cartesian2(0.0, 0.0)
         */
        lineOffset : createPropertyDescriptor('lineOffset')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    GridMaterialProperty.prototype.getType = function(time) {
        return 'Grid';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    GridMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
        result.cellAlpha = Property.getValueOrDefault(this._cellAlpha, time, defaultCellAlpha);
        result.lineCount = Property.getValueOrClonedDefault(this._lineCount, time, defaultLineCount, result.lineCount);
        result.lineThickness = Property.getValueOrClonedDefault(this._lineThickness, time, defaultLineThickness, result.lineThickness);
        result.lineOffset = Property.getValueOrClonedDefault(this._lineOffset, time, defaultLineOffset, result.lineOffset);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    GridMaterialProperty.prototype.equals = function(other) {
        return this === other || //
        (other instanceof GridMaterialProperty && //
        Property.equals(this._color, other._color) && //
        Property.equals(this._cellAlpha, other._cellAlpha) && //
        Property.equals(this._lineCount, other._lineCount) && //
        Property.equals(this._lineThickness, other._lineThickness) && //
        Property.equals(this._lineOffset, other._lineOffset));
    };

    return GridMaterialProperty;
});

/*global define*/
define('DataSources/PolylineArrowMaterialProperty',[
        '../Core/Color',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Color,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    /**
     * A {@link MaterialProperty} that maps to PolylineArrow {@link Material} uniforms.
     *
     * @param {Property} [color=Color.WHITE] The {@link Color} Property to be used.
     *
     * @alias PolylineArrowMaterialProperty
     * @constructor
     */
    function PolylineArrowMaterialProperty(color) {
        this._definitionChanged = new Event();
        this._color = undefined;
        this._colorSubscription = undefined;
        this.color = color;
    }

    defineProperties(PolylineArrowMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof PolylineArrowMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._color);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof PolylineArrowMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the {@link Color} {@link Property}.
         * @memberof PolylineArrowMaterialProperty.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    PolylineArrowMaterialProperty.prototype.getType = function(time) {
        return 'PolylineArrow';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PolylineArrowMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.color = Property.getValueOrClonedDefault(this._color, time, Color.WHITE, result.color);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PolylineArrowMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof PolylineArrowMaterialProperty && //
                Property.equals(this._color, other._color));
    };

    return PolylineArrowMaterialProperty;
});

/*global define*/
define('DataSources/PolylineGlowMaterialProperty',[
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    var defaultColor = Color.WHITE;
    var defaultGlowPower = 0.25;

    /**
     * A {@link MaterialProperty} that maps to polyline glow {@link Material} uniforms.
     * @alias PolylineGlowMaterialProperty
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the line.
     * @param {Property} [options.glowPower=0.25] A numeric Property specifying the strength of the glow, as a percentage of the total line width.
     */
    function PolylineGlowMaterialProperty(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();
        this._color = undefined;
        this._colorSubscription = undefined;
        this._glowPower = undefined;
        this._glowPowerSubscription = undefined;

        this.color = options.color;
        this.glowPower = options.glowPower;
    }

    defineProperties(PolylineGlowMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof PolylineGlowMaterialProperty.prototype
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._color) && Property.isConstant(this._glow);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof PolylineGlowMaterialProperty.prototype
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the Property specifying the {@link Color} of the line.
         * @memberof PolylineGlowMaterialProperty.prototype
         * @type {Property}
         */
        color : createPropertyDescriptor('color'),
        /**
         * Gets or sets the numeric Property specifying the strength of the glow, as a percentage of the total line width (less than 1.0).
         * @memberof PolylineGlowMaterialProperty.prototype
         * @type {Property}
         */
        glowPower : createPropertyDescriptor('glowPower')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    PolylineGlowMaterialProperty.prototype.getType = function(time) {
        return 'PolylineGlow';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PolylineGlowMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
        result.glowPower = Property.getValueOrDefault(this._glowPower, time, defaultGlowPower, result.glowPower);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PolylineGlowMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof PolylineGlowMaterialProperty && //
                Property.equals(this._color, other._color) &&
                Property.equals(this._glowPower, other._glowPower));
    };

    return PolylineGlowMaterialProperty;
});

/*global define*/
define('DataSources/PolylineOutlineMaterialProperty',[
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property'
    ], function(
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property) {
    'use strict';

    var defaultColor = Color.WHITE;
    var defaultOutlineColor = Color.BLACK;
    var defaultOutlineWidth = 1.0;

    /**
     * A {@link MaterialProperty} that maps to polyline outline {@link Material} uniforms.
     * @alias PolylineOutlineMaterialProperty
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.color=Color.WHITE] A Property specifying the {@link Color} of the line.
     * @param {Property} [options.outlineColor=Color.BLACK] A Property specifying the {@link Color} of the outline.
     * @param {Property} [options.outlineWidth=1.0] A numeric Property specifying the width of the outline, in pixels.
     */
    function PolylineOutlineMaterialProperty(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();
        this._color = undefined;
        this._colorSubscription = undefined;
        this._outlineColor = undefined;
        this._outlineColorSubscription = undefined;
        this._outlineWidth = undefined;
        this._outlineWidthSubscription = undefined;

        this.color = options.color;
        this.outlineColor = options.outlineColor;
        this.outlineWidth = options.outlineWidth;
    }

    defineProperties(PolylineOutlineMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof PolylineOutlineMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._color) && Property.isConstant(this._outlineColor) && Property.isConstant(this._outlineWidth);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof PolylineOutlineMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the Property specifying the {@link Color} of the line.
         * @memberof PolylineOutlineMaterialProperty.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        color : createPropertyDescriptor('color'),
        /**
         * Gets or sets the Property specifying the {@link Color} of the outline.
         * @memberof PolylineOutlineMaterialProperty.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        outlineColor : createPropertyDescriptor('outlineColor'),
        /**
         * Gets or sets the numeric Property specifying the width of the outline.
         * @memberof PolylineOutlineMaterialProperty.prototype
         * @type {Property}
         * @default 1.0
         */
        outlineWidth : createPropertyDescriptor('outlineWidth')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    PolylineOutlineMaterialProperty.prototype.getType = function(time) {
        return 'PolylineOutline';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PolylineOutlineMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.color = Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
        result.outlineColor = Property.getValueOrClonedDefault(this._outlineColor, time, defaultOutlineColor, result.outlineColor);
        result.outlineWidth = Property.getValueOrDefault(this._outlineWidth, time, defaultOutlineWidth);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PolylineOutlineMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof PolylineOutlineMaterialProperty && //
                Property.equals(this._color, other._color) && //
                Property.equals(this._outlineColor, other._outlineColor) && //
                Property.equals(this._outlineWidth, other._outlineWidth));
    };

    return PolylineOutlineMaterialProperty;
});

/*global define*/
define('DataSources/PositionPropertyArray',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/EventHelper',
        '../Core/ReferenceFrame',
        './Property'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        EventHelper,
        ReferenceFrame,
        Property) {
    'use strict';

    /**
     * A {@link PositionProperty} whose value is an array whose items are the computed value
     * of other PositionProperty instances.
     *
     * @alias PositionPropertyArray
     * @constructor
     *
     * @param {Property[]} [value] An array of Property instances.
     * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
     */
    function PositionPropertyArray(value, referenceFrame) {
        this._value = undefined;
        this._definitionChanged = new Event();
        this._eventHelper = new EventHelper();
        this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
        this.setValue(value);
    }

    defineProperties(PositionPropertyArray.prototype, {
        /**
         * Gets a value indicating if this property is constant.  This property
         * is considered constant if all property items in the array are constant.
         * @memberof PositionPropertyArray.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                var value = this._value;
                if (!defined(value)) {
                    return true;
                }

                var length = value.length;
                for (var i = 0; i < length; i++) {
                    if (!Property.isConstant(value[i])) {
                        return false;
                    }
                }
                return true;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value or one of the properties in the array also changes.
         * @memberof PositionPropertyArray.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the reference frame in which the position is defined.
         * @memberof PositionPropertyArray.prototype
         * @type {ReferenceFrame}
         * @default ReferenceFrame.FIXED;
         */
        referenceFrame : {
            get : function() {
                return this._referenceFrame;
            }
        }
    });

    /**
     * Gets the value of the property.
     *
     * @param {JulianDate} [time] The time for which to retrieve the value.  This parameter is unused since the value does not change with respect to time.
     * @param {Cartesian3[]} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3[]} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PositionPropertyArray.prototype.getValue = function(time, result) {
        return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
    };

    /**
     * Gets the value of the property at the provided time and in the provided reference frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    PositionPropertyArray.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(referenceFrame)) {
            throw new DeveloperError('referenceFrame is required.');
        }
        
        var value = this._value;
        if (!defined(value)) {
            return undefined;
        }

        var length = value.length;
        if (!defined(result)) {
            result = new Array(length);
        }
        var i = 0;
        var x = 0;
        while (i < length) {
            var property = value[i];
            var itemValue = property.getValueInReferenceFrame(time, referenceFrame, result[i]);
            if (defined(itemValue)) {
                result[x] = itemValue;
                x++;
            }
            i++;
        }
        result.length = x;
        return result;
    };

    /**
     * Sets the value of the property.
     *
     * @param {Property[]} value An array of Property instances.
     */
    PositionPropertyArray.prototype.setValue = function(value) {
        var eventHelper = this._eventHelper;
        eventHelper.removeAll();

        if (defined(value)) {
            this._value = value.slice();
            var length = value.length;
            for (var i = 0; i < length; i++) {
                var property = value[i];
                if (defined(property)) {
                    eventHelper.add(property.definitionChanged, PositionPropertyArray.prototype._raiseDefinitionChanged, this);
                }
            }
        } else {
            this._value = undefined;
        }
        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PositionPropertyArray.prototype.equals = function(other) {
        return this === other || //
               (other instanceof PositionPropertyArray && //
                this._referenceFrame === other._referenceFrame && //
                Property.arrayEquals(this._value, other._value));
    };

    PositionPropertyArray.prototype._raiseDefinitionChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return PositionPropertyArray;
});

/*global define*/
define('DataSources/PropertyArray',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/EventHelper',
        './Property'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event,
        EventHelper,
        Property) {
    'use strict';

    /**
     * A {@link Property} whose value is an array whose items are the computed value
     * of other property instances.
     *
     * @alias PropertyArray
     * @constructor
     *
     * @param {Property[]} [value] An array of Property instances.
     */
    function PropertyArray(value) {
        this._value = undefined;
        this._definitionChanged = new Event();
        this._eventHelper = new EventHelper();
        this.setValue(value);
    }

    defineProperties(PropertyArray.prototype, {
        /**
         * Gets a value indicating if this property is constant.  This property
         * is considered constant if all property items in the array are constant.
         * @memberof PropertyArray.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                var value = this._value;
                if (!defined(value)) {
                    return true;
                }
                var length = value.length;
                for (var i = 0; i < length; i++) {
                    if (!Property.isConstant(value[i])) {
                        return false;
                    }
                }
                return true;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value or one of the properties in the array also changes.
         * @memberof PropertyArray.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        }
    });

    /**
     * Gets the value of the property.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object[]} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object[]} The modified result parameter, which is an array of values produced by evaluating each of the contained properties at the given time or a new instance if the result parameter was not supplied.
     */
    PropertyArray.prototype.getValue = function(time, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var value = this._value;
        if (!defined(value)) {
            return undefined;
        }

        var length = value.length;
        if (!defined(result)) {
            result = new Array(length);
        }
        var i = 0;
        var x = 0;
        while (i < length) {
            var property = this._value[i];
            var itemValue = property.getValue(time, result[i]);
            if (defined(itemValue)) {
                result[x] = itemValue;
                x++;
            }
            i++;
        }
        result.length = x;
        return result;
    };

    /**
     * Sets the value of the property.
     *
     * @param {Property[]} value An array of Property instances.
     */
    PropertyArray.prototype.setValue = function(value) {
        var eventHelper = this._eventHelper;
        eventHelper.removeAll();

        if (defined(value)) {
            this._value = value.slice();
            var length = value.length;
            for (var i = 0; i < length; i++) {
                var property = value[i];
                if (defined(property)) {
                    eventHelper.add(property.definitionChanged, PropertyArray.prototype._raiseDefinitionChanged, this);
                }
            }
        } else {
            this._value = undefined;
        }
        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    PropertyArray.prototype.equals = function(other) {
        return this === other || //
               (other instanceof PropertyArray && //
                Property.arrayEquals(this._value, other._value));
    };

    PropertyArray.prototype._raiseDefinitionChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return PropertyArray;
});

/*global define*/
define('DataSources/ReferenceProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/RuntimeError',
        './Property'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event,
        RuntimeError,
        Property) {
    'use strict';

    function resolveEntity(that) {
        var entityIsResolved = true;
        if (that._resolveEntity) {
            var targetEntity = that._targetCollection.getById(that._targetId);

            if (defined(targetEntity)) {
                targetEntity.definitionChanged.addEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, that);
                that._targetEntity = targetEntity;
                that._resolveEntity = false;
            } else {
                //The property has become detached.  It has a valid value but is not currently resolved to an entity in the collection
                targetEntity = that._targetEntity;
                entityIsResolved = false;
            }

            if (!defined(targetEntity)) {
                throw new RuntimeError('target entity "' + that._targetId + '" could not be resolved.');
            }
        }
        return entityIsResolved;
    }

    function resolve(that) {
        var targetProperty = that._targetProperty;

        if (that._resolveProperty) {
            var entityIsResolved = resolveEntity(that);

            var names = that._targetPropertyNames;
            targetProperty = that._targetEntity;
            var length = names.length;
            for (var i = 0; i < length && defined(targetProperty); i++) {
                targetProperty = targetProperty[names[i]];
            }

            if (defined(targetProperty)) {
                that._targetProperty = targetProperty;
                that._resolveProperty = !entityIsResolved;
            } else if (!defined(that._targetProperty)) {
                throw new RuntimeError('targetProperty "' + that._targetId + '.' + names.join('.') + '" could not be resolved.');
            }
        }

        return targetProperty;
    }

    /**
     * A {@link Property} which transparently links to another property on a provided object.
     *
     * @alias ReferenceProperty
     * @constructor
     *
     * @param {EntityCollection} targetCollection The entity collection which will be used to resolve the reference.
     * @param {String} targetId The id of the entity which is being referenced.
     * @param {String[]} targetPropertyNames The names of the property on the target entity which we will use.
     *
     * @example
     * var collection = new Cesium.EntityCollection();
     *
     * //Create a new entity and assign a billboard scale.
     * var object1 = new Cesium.Entity({id:'object1'});
     * object1.billboard = new Cesium.BillboardGraphics();
     * object1.billboard.scale = new Cesium.ConstantProperty(2.0);
     * collection.add(object1);
     *
     * //Create a second entity and reference the scale from the first one.
     * var object2 = new Cesium.Entity({id:'object2'});
     * object2.model = new Cesium.ModelGraphics();
     * object2.model.scale = new Cesium.ReferenceProperty(collection, 'object1', ['billboard', 'scale']);
     * collection.add(object2);
     *
     * //Create a third object, but use the fromString helper function.
     * var object3 = new Cesium.Entity({id:'object3'});
     * object3.billboard = new Cesium.BillboardGraphics();
     * object3.billboard.scale = Cesium.ReferenceProperty.fromString(collection, 'object1#billboard.scale');
     * collection.add(object3);
     *
     * //You can refer to an entity with a # or . in id and property names by escaping them.
     * var object4 = new Cesium.Entity({id:'#object.4'});
     * object4.billboard = new Cesium.BillboardGraphics();
     * object4.billboard.scale = new Cesium.ConstantProperty(2.0);
     * collection.add(object4);
     *
     * var object5 = new Cesium.Entity({id:'object5'});
     * object5.billboard = new Cesium.BillboardGraphics();
     * object5.billboard.scale = Cesium.ReferenceProperty.fromString(collection, '\\#object\\.4#billboard.scale');
     * collection.add(object5);
     */
    function ReferenceProperty(targetCollection, targetId, targetPropertyNames) {
                if (!defined(targetCollection)) {
            throw new DeveloperError('targetCollection is required.');
        }
        if (!defined(targetId) || targetId === '') {
            throw new DeveloperError('targetId is required.');
        }
        if (!defined(targetPropertyNames) || targetPropertyNames.length === 0) {
            throw new DeveloperError('targetPropertyNames is required.');
        }
        for (var i = 0; i < targetPropertyNames.length; i++) {
            var item = targetPropertyNames[i];
            if (!defined(item) || item === '') {
                throw new DeveloperError('reference contains invalid properties.');
            }
        }
        
        this._targetCollection = targetCollection;
        this._targetId = targetId;
        this._targetPropertyNames = targetPropertyNames;
        this._targetProperty = undefined;
        this._targetEntity = undefined;
        this._definitionChanged = new Event();
        this._resolveEntity = true;
        this._resolveProperty = true;

        targetCollection.collectionChanged.addEventListener(ReferenceProperty.prototype._onCollectionChanged, this);
    }

    defineProperties(ReferenceProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.
         * @memberof ReferenceProperty.prototype
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(resolve(this));
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever the referenced property's definition is changed.
         * @memberof ReferenceProperty.prototype
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the reference frame that the position is defined in.
         * This property is only valid if the referenced property is a {@link PositionProperty}.
         * @memberof ReferenceProperty.prototype
         * @type {ReferenceFrame}
         * @readonly
         */
        referenceFrame : {
            get : function() {
                return resolve(this).referenceFrame;
            }
        },
        /**
         * Gets the id of the entity being referenced.
         * @memberof ReferenceProperty.prototype
         * @type {String}
         * @readonly
         */
        targetId : {
            get : function() {
                return this._targetId;
            }
        },
        /**
         * Gets the collection containing the entity being referenced.
         * @memberof ReferenceProperty.prototype
         * @type {EntityCollection}
         * @readonly
         */
        targetCollection : {
            get : function() {
                return this._targetCollection;
            }
        },
        /**
         * Gets the array of property names used to retrieve the referenced property.
         * @memberof ReferenceProperty.prototype
         * @type {String[]}
         * @readonly
         */
        targetPropertyNames : {
            get : function() {
                return this._targetPropertyNames;
            }
        },
        /**
         * Gets the resolved instance of the underlying referenced property.
         * @memberof ReferenceProperty.prototype
         * @type {Property}
         * @readonly
         */
        resolvedProperty : {
            get : function() {
                return resolve(this);
            }
        }
    });

    /**
     * Creates a new instance given the entity collection that will
     * be used to resolve it and a string indicating the target entity id and property.
     * The format of the string is "objectId#foo.bar", where # separates the id from
     * property path and . separates sub-properties.  If the reference identifier or
     * or any sub-properties contains a # . or \ they must be escaped.
     *
     * @param {EntityCollection} targetCollection
     * @param {String} referenceString
     * @returns {ReferenceProperty} A new instance of ReferenceProperty.
     *
     * @exception {DeveloperError} invalid referenceString.
     */
    ReferenceProperty.fromString = function(targetCollection, referenceString) {
                if (!defined(targetCollection)) {
            throw new DeveloperError('targetCollection is required.');
        }
        if (!defined(referenceString)) {
            throw new DeveloperError('referenceString is required.');
        }
        
        var identifier;
        var values = [];

        var inIdentifier = true;
        var isEscaped = false;
        var token = '';
        for (var i = 0; i < referenceString.length; ++i) {
            var c = referenceString.charAt(i);

            if (isEscaped) {
                token += c;
                isEscaped = false;
            } else if (c === '\\') {
                isEscaped = true;
            } else if (inIdentifier && c === '#') {
                identifier = token;
                inIdentifier = false;
                token = '';
            } else if (!inIdentifier && c === '.') {
                values.push(token);
                token = '';
            } else {
                token += c;
            }
        }
        values.push(token);

        return new ReferenceProperty(targetCollection, identifier, values);
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ReferenceProperty.prototype.getValue = function(time, result) {
        return resolve(this).getValue(time, result);
    };

    /**
     * Gets the value of the property at the provided time and in the provided reference frame.
     * This method is only valid if the property being referenced is a {@link PositionProperty}.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    ReferenceProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
        return resolve(this).getValueInReferenceFrame(time, referenceFrame, result);
    };

    /**
     * Gets the {@link Material} type at the provided time.
     * This method is only valid if the property being referenced is a {@link MaterialProperty}.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    ReferenceProperty.prototype.getType = function(time) {
        return resolve(this).getType(time);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    ReferenceProperty.prototype.equals = function(other) {
        if (this === other) {
            return true;
        }

        var names = this._targetPropertyNames;
        var otherNames = other._targetPropertyNames;

        if (this._targetCollection !== other._targetCollection || //
            this._targetId !== other._targetId || //
            names.length !== otherNames.length) {
            return false;
        }

        var length = this._targetPropertyNames.length;
        for (var i = 0; i < length; i++) {
            if (names[i] !== otherNames[i]) {
                return false;
            }
        }

        return true;
    };

    ReferenceProperty.prototype._onTargetEntityDefinitionChanged = function(targetEntity, name, value, oldValue) {
        if (this._targetPropertyNames[0] === name) {
            this._resolveProperty = true;
            this._definitionChanged.raiseEvent(this);
        }
    };

    ReferenceProperty.prototype._onCollectionChanged = function(collection, added, removed) {
        var targetEntity = this._targetEntity;
        if (defined(targetEntity)) {
            if (removed.indexOf(targetEntity) !== -1) {
                targetEntity.definitionChanged.removeEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, this);
                this._resolveEntity = true;
                this._resolveProperty = true;
            } else if (this._resolveEntity) {
                //If targetEntity is defined but resolveEntity is true, then the entity is detached
                //and any change to the collection needs to incur an attempt to resolve in order to re-attach.
                //without this if block, a reference that becomes re-attached will not signal definitionChanged
                resolve(this);
                if (!this._resolveEntity) {
                    this._definitionChanged.raiseEvent(this);
                }
            }
        }
    };

    return ReferenceProperty;
});

/*global define*/
define('DataSources/Rotation',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Math'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        CesiumMath) {
    'use strict';

    /**
     * Represents a {@link Packable} number that always interpolates values
     * towards the shortest angle of rotation. This object is never used directly
     * but is instead passed to the constructor of {@link SampledProperty}
     * in order to represent a two-dimensional angle of rotation.
     *
     * @exports Rotation
     *
     *
     * @example
     * var time1 = Cesium.JulianDate.fromIso8601('2010-05-07T00:00:00');
     * var time2 = Cesium.JulianDate.fromIso8601('2010-05-07T00:01:00');
     * var time3 = Cesium.JulianDate.fromIso8601('2010-05-07T00:02:00');
     *
     * var property = new Cesium.SampledProperty(Cesium.Rotation);
     * property.addSample(time1, 0);
     * property.addSample(time3, Cesium.Math.toRadians(350));
     *
     * //Getting the value at time2 will equal 355 degrees instead
     * //of 175 degrees (which is what you get if you construct
     * //a SampledProperty(Number) instead.  Note, the actual
     * //return value is in radians, not degrees.
     * property.getValue(time2);
     *
     * @see PackableForInterpolation
     */
    var Rotation = {
        /**
         * The number of elements used to pack the object into an array.
         * @type {Number}
         */
        packedLength : 1,

        /**
         * Stores the provided instance into the provided array.
         *
         * @param {Rotation} value The value to pack.
         * @param {Number[]} array The array to pack into.
         * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
         *
         * @returns {Number[]} The array that was packed into
         */
        pack : function(value, array, startingIndex) {
                        if (!defined(value)) {
                throw new DeveloperError('value is required');
            }

            if (!defined(array)) {
                throw new DeveloperError('array is required');
            }
            
            startingIndex = defaultValue(startingIndex, 0);
            array[startingIndex] = value;

            return array;
        },

        /**
         * Retrieves an instance from a packed array.
         *
         * @param {Number[]} array The packed array.
         * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
         * @param {Rotation} [result] The object into which to store the result.
         * @returns {Rotation} The modified result parameter or a new Rotation instance if one was not provided.
         */
        unpack : function(array, startingIndex, result) {
                        if (!defined(array)) {
                throw new DeveloperError('array is required');
            }
            
            startingIndex = defaultValue(startingIndex, 0);
            return array[startingIndex];
        },

        /**
         * Converts a packed array into a form suitable for interpolation.
         *
         * @param {Number[]} packedArray The packed array.
         * @param {Number} [startingIndex=0] The index of the first element to be converted.
         * @param {Number} [lastIndex=packedArray.length] The index of the last element to be converted.
         * @param {Number[]} result The object into which to store the result.
         */
        convertPackedArrayForInterpolation : function(packedArray, startingIndex, lastIndex, result) {
                        if (!defined(packedArray)) {
                throw new DeveloperError('packedArray is required');
            }
            
            startingIndex = defaultValue(startingIndex, 0);
            lastIndex = defaultValue(lastIndex, packedArray.length);

            var previousValue;
            for (var i = 0, len = lastIndex - startingIndex + 1; i < len; i++) {
                var value = packedArray[startingIndex + i];
                if (i === 0 || Math.abs(previousValue - value) < Math.PI) {
                    result[i] = value;
                } else {
                    result[i] = value - CesiumMath.TWO_PI;
                }
                previousValue = value;
            }
        },

        /**
         * Retrieves an instance from a packed array converted with {@link Rotation.convertPackedArrayForInterpolation}.
         *
         * @param {Number[]} array The array previously packed for interpolation.
         * @param {Number[]} sourceArray The original packed array.
         * @param {Number} [firstIndex=0] The firstIndex used to convert the array.
         * @param {Number} [lastIndex=packedArray.length] The lastIndex used to convert the array.
         * @param {Rotation} [result] The object into which to store the result.
         * @returns {Rotation} The modified result parameter or a new Rotation instance if one was not provided.
         */
        unpackInterpolationResult : function(array, sourceArray, firstIndex, lastIndex, result) {
                        if (!defined(array)) {
                throw new DeveloperError('array is required');
            }
            if (!defined(sourceArray)) {
                throw new DeveloperError('sourceArray is required');
            }
            
            result = array[0];
            if (result < 0) {
                return result + CesiumMath.TWO_PI;
            }
            return result;
        }
    };

    return Rotation;
});

/*global define*/
define('DataSources/SampledProperty',[
        '../Core/binarySearch',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/ExtrapolationType',
        '../Core/JulianDate',
        '../Core/LinearApproximation'
    ], function(
        binarySearch,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        ExtrapolationType,
        JulianDate,
        LinearApproximation) {
    'use strict';

    var PackableNumber = {
        packedLength : 1,
        pack : function(value, array, startingIndex) {
            startingIndex = defaultValue(startingIndex, 0);
            array[startingIndex] = value;
        },
        unpack : function(array, startingIndex, result) {
            startingIndex = defaultValue(startingIndex, 0);
            return array[startingIndex];
        }
    };

    //We can't use splice for inserting new elements because function apply can't handle
    //a huge number of arguments.  See https://code.google.com/p/chromium/issues/detail?id=56588
    function arrayInsert(array, startIndex, items) {
        var i;
        var arrayLength = array.length;
        var itemsLength = items.length;
        var newLength = arrayLength + itemsLength;

        array.length = newLength;
        if (arrayLength !== startIndex) {
            var q = arrayLength - 1;
            for (i = newLength - 1; i >= startIndex; i--) {
                array[i] = array[q--];
            }
        }

        for (i = 0; i < itemsLength; i++) {
            array[startIndex++] = items[i];
        }
    }

    function convertDate(date, epoch) {
        if (date instanceof JulianDate) {
            return date;
        }
        if (typeof date === 'string') {
            return JulianDate.fromIso8601(date);
        }
        return JulianDate.addSeconds(epoch, date, new JulianDate());
    }

    var timesSpliceArgs = [];
    var valuesSpliceArgs = [];

    function mergeNewSamples(epoch, times, values, newData, packedLength) {
        var newDataIndex = 0;
        var i;
        var prevItem;
        var timesInsertionPoint;
        var valuesInsertionPoint;
        var currentTime;
        var nextTime;

        while (newDataIndex < newData.length) {
            currentTime = convertDate(newData[newDataIndex], epoch);
            timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);
            var timesSpliceArgsCount = 0;
            var valuesSpliceArgsCount = 0;

            if (timesInsertionPoint < 0) {
                //Doesn't exist, insert as many additional values as we can.
                timesInsertionPoint = ~timesInsertionPoint;

                valuesInsertionPoint = timesInsertionPoint * packedLength;
                prevItem = undefined;
                nextTime = times[timesInsertionPoint];
                while (newDataIndex < newData.length) {
                    currentTime = convertDate(newData[newDataIndex], epoch);
                    if ((defined(prevItem) && JulianDate.compare(prevItem, currentTime) >= 0) || (defined(nextTime) && JulianDate.compare(currentTime, nextTime) >= 0)) {
                        break;
                    }
                    timesSpliceArgs[timesSpliceArgsCount++] = currentTime;
                    newDataIndex = newDataIndex + 1;
                    for (i = 0; i < packedLength; i++) {
                        valuesSpliceArgs[valuesSpliceArgsCount++] = newData[newDataIndex];
                        newDataIndex = newDataIndex + 1;
                    }
                    prevItem = currentTime;
                }

                if (timesSpliceArgsCount > 0) {
                    valuesSpliceArgs.length = valuesSpliceArgsCount;
                    arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs);

                    timesSpliceArgs.length = timesSpliceArgsCount;
                    arrayInsert(times, timesInsertionPoint, timesSpliceArgs);
                }
            } else {
                //Found an exact match
                for (i = 0; i < packedLength; i++) {
                    newDataIndex++;
                    values[(timesInsertionPoint * packedLength) + i] = newData[newDataIndex];
                }
                newDataIndex++;
            }
        }
    }

    /**
     * A {@link Property} whose value is interpolated for a given time from the
     * provided set of samples and specified interpolation algorithm and degree.
     * @alias SampledProperty
     * @constructor
     *
     * @param {Number|Packable} type The type of property.
     * @param {Packable[]} [derivativeTypes] When supplied, indicates that samples will contain derivative information of the specified types.
     *
     *
     * @example
     * //Create a linearly interpolated Cartesian2
     * var property = new Cesium.SampledProperty(Cesium.Cartesian2);
     *
     * //Populate it with data
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), new Cesium.Cartesian2(0, 0));
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-02T00:00:00.00Z`), new Cesium.Cartesian2(4, 7));
     *
     * //Retrieve an interpolated value
     * var result = property.getValue(Cesium.JulianDate.fromIso8601(`2012-08-01T12:00:00.00Z`));
     *
     * @example
     * //Create a simple numeric SampledProperty that uses third degree Hermite Polynomial Approximation
     * var property = new Cesium.SampledProperty(Number);
     * property.setInterpolationOptions({
     *     interpolationDegree : 3,
     *     interpolationAlgorithm : Cesium.HermitePolynomialApproximation
     * });
     *
     * //Populate it with data
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:00:00.00Z`), 1.0);
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:01:00.00Z`), 6.0);
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:02:00.00Z`), 12.0);
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:03:30.00Z`), 5.0);
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:06:30.00Z`), 2.0);
     *
     * //Samples can be added in any order.
     * property.addSample(Cesium.JulianDate.fromIso8601(`2012-08-01T00:00:30.00Z`), 6.2);
     *
     * //Retrieve an interpolated value
     * var result = property.getValue(Cesium.JulianDate.fromIso8601(`2012-08-01T00:02:34.00Z`));
     * 
     * @see SampledPositionProperty
     */
    function SampledProperty(type, derivativeTypes) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var innerType = type;
        if (innerType === Number) {
            innerType = PackableNumber;
        }
        var packedLength = innerType.packedLength;
        var packedInterpolationLength = defaultValue(innerType.packedInterpolationLength, packedLength);

        var inputOrder = 0;
        var innerDerivativeTypes;
        if (defined(derivativeTypes)) {
            var length = derivativeTypes.length;
            innerDerivativeTypes = new Array(length);
            for (var i = 0; i < length; i++) {
                var derivativeType = derivativeTypes[i];
                if (derivativeType === Number) {
                    derivativeType = PackableNumber;
                }
                var derivativePackedLength = derivativeType.packedLength;
                packedLength += derivativePackedLength;
                packedInterpolationLength += defaultValue(derivativeType.packedInterpolationLength, derivativePackedLength);
                innerDerivativeTypes[i] = derivativeType;
            }
            inputOrder = length;
        }

        this._type = type;
        this._innerType = innerType;
        this._interpolationDegree = 1;
        this._interpolationAlgorithm = LinearApproximation;
        this._numberOfPoints = 0;
        this._times = [];
        this._values = [];
        this._xTable = [];
        this._yTable = [];
        this._packedLength = packedLength;
        this._packedInterpolationLength = packedInterpolationLength;
        this._updateTableLength = true;
        this._interpolationResult = new Array(packedInterpolationLength);
        this._definitionChanged = new Event();
        this._derivativeTypes = derivativeTypes;
        this._innerDerivativeTypes = innerDerivativeTypes;
        this._inputOrder = inputOrder;
        this._forwardExtrapolationType = ExtrapolationType.NONE;
        this._forwardExtrapolationDuration = 0;
        this._backwardExtrapolationType = ExtrapolationType.NONE;
        this._backwardExtrapolationDuration = 0;
    }

    defineProperties(SampledProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof SampledProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._values.length === 0;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof SampledProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the type of property.
         * @memberof SampledProperty.prototype
         * @type {Object}
         */
        type : {
            get : function() {
                return this._type;
            }
        },
        /**
         * Gets the derivative types used by this property.
         * @memberof SampledProperty.prototype
         * @type {Packable[]}
         */
        derivativeTypes : {
            get : function() {
                return this._derivativeTypes;
            }
        },
        /**
         * Gets the degree of interpolation to perform when retrieving a value.
         * @memberof SampledProperty.prototype
         * @type {Number}
         * @default 1
         */
        interpolationDegree : {
            get : function() {
                return this._interpolationDegree;
            }
        },
        /**
         * Gets the interpolation algorithm to use when retrieving a value.
         * @memberof SampledProperty.prototype
         * @type {InterpolationAlgorithm}
         * @default LinearApproximation
         */
        interpolationAlgorithm : {
            get : function() {
                return this._interpolationAlgorithm;
            }
        },
        /**
         * Gets or sets the type of extrapolation to perform when a value
         * is requested at a time after any available samples.
         * @memberof SampledProperty.prototype
         * @type {ExtrapolationType}
         * @default ExtrapolationType.NONE
         */
        forwardExtrapolationType : {
            get : function() {
                return this._forwardExtrapolationType;
            },
            set : function(value) {
                if (this._forwardExtrapolationType !== value) {
                    this._forwardExtrapolationType = value;
                    this._definitionChanged.raiseEvent(this);
                }
            }
        },
        /**
         * Gets or sets the amount of time to extrapolate forward before
         * the property becomes undefined.  A value of 0 will extrapolate forever.
         * @memberof SampledProperty.prototype
         * @type {Number}
         * @default 0
         */
        forwardExtrapolationDuration : {
            get : function() {
                return this._forwardExtrapolationDuration;
            },
            set : function(value) {
                if (this._forwardExtrapolationDuration !== value) {
                    this._forwardExtrapolationDuration = value;
                    this._definitionChanged.raiseEvent(this);
                }
            }
        },
        /**
         * Gets or sets the type of extrapolation to perform when a value
         * is requested at a time before any available samples.
         * @memberof SampledProperty.prototype
         * @type {ExtrapolationType}
         * @default ExtrapolationType.NONE
         */
        backwardExtrapolationType : {
            get : function() {
                return this._backwardExtrapolationType;
            },
            set : function(value) {
                if (this._backwardExtrapolationType !== value) {
                    this._backwardExtrapolationType = value;
                    this._definitionChanged.raiseEvent(this);
                }
            }
        },
        /**
         * Gets or sets the amount of time to extrapolate backward
         * before the property becomes undefined.  A value of 0 will extrapolate forever.
         * @memberof SampledProperty.prototype
         * @type {Number}
         * @default 0
         */
        backwardExtrapolationDuration : {
            get : function() {
                return this._backwardExtrapolationDuration;
            },
            set : function(value) {
                if (this._backwardExtrapolationDuration !== value) {
                    this._backwardExtrapolationDuration = value;
                    this._definitionChanged.raiseEvent(this);
                }
            }
        }
    });

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    SampledProperty.prototype.getValue = function(time, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var times = this._times;
        var timesLength = times.length;
        if (timesLength === 0) {
            return undefined;
        }

        var timeout;
        var innerType = this._innerType;
        var values = this._values;
        var index = binarySearch(times, time, JulianDate.compare);

        if (index < 0) {
            index = ~index;

            if (index === 0) {
                var startTime = times[index];
                timeout = this._backwardExtrapolationDuration;
                if (this._backwardExtrapolationType === ExtrapolationType.NONE || (timeout !== 0 && JulianDate.secondsDifference(startTime, time) > timeout)) {
                    return undefined;
                }
                if (this._backwardExtrapolationType === ExtrapolationType.HOLD) {
                    return innerType.unpack(values, 0, result);
                }
            }

            if (index >= timesLength) {
                index = timesLength - 1;
                var endTime = times[index];
                timeout = this._forwardExtrapolationDuration;
                if (this._forwardExtrapolationType === ExtrapolationType.NONE || (timeout !== 0 && JulianDate.secondsDifference(time, endTime) > timeout)) {
                    return undefined;
                }
                if (this._forwardExtrapolationType === ExtrapolationType.HOLD) {
                    index = timesLength - 1;
                    return innerType.unpack(values, index * innerType.packedLength, result);
                }
            }

            var xTable = this._xTable;
            var yTable = this._yTable;
            var interpolationAlgorithm = this._interpolationAlgorithm;
            var packedInterpolationLength = this._packedInterpolationLength;
            var inputOrder = this._inputOrder;

            if (this._updateTableLength) {
                this._updateTableLength = false;
                var numberOfPoints = Math.min(interpolationAlgorithm.getRequiredDataPoints(this._interpolationDegree, inputOrder), timesLength);
                if (numberOfPoints !== this._numberOfPoints) {
                    this._numberOfPoints = numberOfPoints;
                    xTable.length = numberOfPoints;
                    yTable.length = numberOfPoints * packedInterpolationLength;
                }
            }

            var degree = this._numberOfPoints - 1;
            if (degree < 1) {
                return undefined;
            }

            var firstIndex = 0;
            var lastIndex = timesLength - 1;
            var pointsInCollection = lastIndex - firstIndex + 1;

            if (pointsInCollection >= degree + 1) {
                var computedFirstIndex = index - ((degree / 2) | 0) - 1;
                if (computedFirstIndex < firstIndex) {
                    computedFirstIndex = firstIndex;
                }
                var computedLastIndex = computedFirstIndex + degree;
                if (computedLastIndex > lastIndex) {
                    computedLastIndex = lastIndex;
                    computedFirstIndex = computedLastIndex - degree;
                    if (computedFirstIndex < firstIndex) {
                        computedFirstIndex = firstIndex;
                    }
                }

                firstIndex = computedFirstIndex;
                lastIndex = computedLastIndex;
            }
            var length = lastIndex - firstIndex + 1;

            // Build the tables
            for (var i = 0; i < length; ++i) {
                xTable[i] = JulianDate.secondsDifference(times[firstIndex + i], times[lastIndex]);
            }

            if (!defined(innerType.convertPackedArrayForInterpolation)) {
                var destinationIndex = 0;
                var packedLength = this._packedLength;
                var sourceIndex = firstIndex * packedLength;
                var stop = (lastIndex + 1) * packedLength;

                while (sourceIndex < stop) {
                    yTable[destinationIndex] = values[sourceIndex];
                    sourceIndex++;
                    destinationIndex++;
                }
            } else {
                innerType.convertPackedArrayForInterpolation(values, firstIndex, lastIndex, yTable);
            }

            // Interpolate!
            var x = JulianDate.secondsDifference(time, times[lastIndex]);
            var interpolationResult;
            if (inputOrder === 0 || !defined(interpolationAlgorithm.interpolate)) {
                interpolationResult = interpolationAlgorithm.interpolateOrderZero(x, xTable, yTable, packedInterpolationLength, this._interpolationResult);
            } else {
                var yStride = Math.floor(packedInterpolationLength / (inputOrder + 1));
                interpolationResult = interpolationAlgorithm.interpolate(x, xTable, yTable, yStride, inputOrder, inputOrder, this._interpolationResult);
            }

            if (!defined(innerType.unpackInterpolationResult)) {
                return innerType.unpack(interpolationResult, 0, result);
            }
            return innerType.unpackInterpolationResult(interpolationResult, values, firstIndex, lastIndex, result);
        }
        return innerType.unpack(values, index * this._packedLength, result);
    };

    /**
     * Sets the algorithm and degree to use when interpolating a value.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {InterpolationAlgorithm} [options.interpolationAlgorithm] The new interpolation algorithm.  If undefined, the existing property will be unchanged.
     * @param {Number} [options.interpolationDegree] The new interpolation degree.  If undefined, the existing property will be unchanged.
     */
    SampledProperty.prototype.setInterpolationOptions = function(options) {
                if (!defined(options)) {
            throw new DeveloperError('options is required.');
        }
        
        var valuesChanged = false;

        var interpolationAlgorithm = options.interpolationAlgorithm;
        var interpolationDegree = options.interpolationDegree;

        if (this._interpolationAlgorithm !== interpolationAlgorithm) {
            this._interpolationAlgorithm = interpolationAlgorithm;
            valuesChanged = true;
        }

        if (this._interpolationDegree !== interpolationDegree) {
            this._interpolationDegree = interpolationDegree;
            valuesChanged = true;
        }

        if (valuesChanged) {
            this._updateTableLength = true;
            this._definitionChanged.raiseEvent(this);
        }
    };

    /**
     * Adds a new sample
     *
     * @param {JulianDate} time The sample time.
     * @param {Packable} value The value at the provided time.
     * @param {Packable[]} [derivatives] The array of derivatives at the provided time.
     */
    SampledProperty.prototype.addSample = function(time, value, derivatives) {
        var innerDerivativeTypes = this._innerDerivativeTypes;
        var hasDerivatives = defined(innerDerivativeTypes);

                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(value)) {
            throw new DeveloperError('value is required.');
        }
        if (hasDerivatives && !defined(derivatives)) {
            throw new DeveloperError('derivatives is required.');
        }
        
        var innerType = this._innerType;
        var data = [];
        data.push(time);
        innerType.pack(value, data, data.length);

        if (hasDerivatives) {
            var derivativesLength = innerDerivativeTypes.length;
            for (var x = 0; x < derivativesLength; x++) {
                innerDerivativeTypes[x].pack(derivatives[x], data, data.length);
            }
        }
        mergeNewSamples(undefined, this._times, this._values, data, this._packedLength);
        this._updateTableLength = true;
        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Adds an array of samples
     *
     * @param {JulianDate[]} times An array of JulianDate instances where each index is a sample time.
     * @param {Packable[]} values The array of values, where each value corresponds to the provided times index.
     * @param {Array[]} [derivativeValues] An array where each item is the array of derivatives at the equivalent time index.
     *
     * @exception {DeveloperError} times and values must be the same length.
     * @exception {DeveloperError} times and derivativeValues must be the same length.
     */
    SampledProperty.prototype.addSamples = function(times, values, derivativeValues) {
        var innerDerivativeTypes = this._innerDerivativeTypes;
        var hasDerivatives = defined(innerDerivativeTypes);

                if (!defined(times)) {
            throw new DeveloperError('times is required.');
        }
        if (!defined(values)) {
            throw new DeveloperError('values is required.');
        }
        if (times.length !== values.length) {
            throw new DeveloperError('times and values must be the same length.');
        }
        if (hasDerivatives && (!defined(derivativeValues) || derivativeValues.length !== times.length)) {
            throw new DeveloperError('times and derivativeValues must be the same length.');
        }
        
        var innerType = this._innerType;
        var length = times.length;
        var data = [];
        for (var i = 0; i < length; i++) {
            data.push(times[i]);
            innerType.pack(values[i], data, data.length);

            if (hasDerivatives) {
                var derivatives = derivativeValues[i];
                var derivativesLength = innerDerivativeTypes.length;
                for (var x = 0; x < derivativesLength; x++) {
                    innerDerivativeTypes[x].pack(derivatives[x], data, data.length);
                }
            }
        }
        mergeNewSamples(undefined, this._times, this._values, data, this._packedLength);
        this._updateTableLength = true;
        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Adds samples as a single packed array where each new sample is represented as a date,
     * followed by the packed representation of the corresponding value and derivatives.
     *
     * @param {Number[]} packedSamples The array of packed samples.
     * @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
     */
    SampledProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) {
                if (!defined(packedSamples)) {
            throw new DeveloperError('packedSamples is required.');
        }
        
        mergeNewSamples(epoch, this._times, this._values, packedSamples, this._packedLength);
        this._updateTableLength = true;
        this._definitionChanged.raiseEvent(this);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    SampledProperty.prototype.equals = function(other) {
        if (this === other) {
            return true;
        }
        if (!defined(other)) {
            return false;
        }

        if (this._type !== other._type || //
            this._interpolationDegree !== other._interpolationDegree || //
            this._interpolationAlgorithm !== other._interpolationAlgorithm) {
            return false;
        }

        var derivativeTypes = this._derivativeTypes;
        var hasDerivatives = defined(derivativeTypes);
        var otherDerivativeTypes = other._derivativeTypes;
        var otherHasDerivatives = defined(otherDerivativeTypes);
        if (hasDerivatives !== otherHasDerivatives) {
            return false;
        }

        var i;
        var length;
        if (hasDerivatives) {
            length = derivativeTypes.length;
            if (length !== otherDerivativeTypes.length) {
                return false;
            }

            for (i = 0; i < length; i++) {
                if (derivativeTypes[i] !== otherDerivativeTypes[i]) {
                    return false;
                }
            }
        }

        var times = this._times;
        var otherTimes = other._times;
        length = times.length;

        if (length !== otherTimes.length) {
            return false;
        }

        for (i = 0; i < length; i++) {
            if (!JulianDate.equals(times[i], otherTimes[i])) {
                return false;
            }
        }

        var values = this._values;
        var otherValues = other._values;
        for (i = 0; i < length; i++) {
            if (values[i] !== otherValues[i]) {
                return false;
            }
        }

        return true;
    };

    //Exposed for testing.
    SampledProperty._mergeNewSamples = mergeNewSamples;

    return SampledProperty;
});

/*global define*/
define('DataSources/SampledPositionProperty',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/ReferenceFrame',
        './PositionProperty',
        './Property',
        './SampledProperty'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        ReferenceFrame,
        PositionProperty,
        Property,
        SampledProperty) {
    'use strict';

    /**
     * A {@link SampledProperty} which is also a {@link PositionProperty}.
     *
     * @alias SampledPositionProperty
     * @constructor
     *
     * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
     * @param {Number} [numberOfDerivatives=0] The number of derivatives that accompany each position; i.e. velocity, acceleration, etc...
     */
    function SampledPositionProperty(referenceFrame, numberOfDerivatives) {
        numberOfDerivatives = defaultValue(numberOfDerivatives, 0);

        var derivativeTypes;
        if (numberOfDerivatives > 0) {
            derivativeTypes = new Array(numberOfDerivatives);
            for (var i = 0; i < numberOfDerivatives; i++) {
                derivativeTypes[i] = Cartesian3;
            }
        }

        this._numberOfDerivatives = numberOfDerivatives;
        this._property = new SampledProperty(Cartesian3, derivativeTypes);
        this._definitionChanged = new Event();
        this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);

        this._property._definitionChanged.addEventListener(function() {
            this._definitionChanged.raiseEvent(this);
        }, this);
    }

    defineProperties(SampledPositionProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof SampledPositionProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._property.isConstant;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof SampledPositionProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the reference frame in which the position is defined.
         * @memberof SampledPositionProperty.prototype
         * @type {ReferenceFrame}
         * @default ReferenceFrame.FIXED;
         */
        referenceFrame : {
            get : function() {
                return this._referenceFrame;
            }
        },
        /**
         * Gets the degree of interpolation to perform when retrieving a value.
         * @memberof SampledPositionProperty.prototype
         *
         * @type {Number}
         * @default 1
         */
        interpolationDegree : {
            get : function() {
                return this._property.interpolationDegree;
            }
        },
        /**
         * Gets the interpolation algorithm to use when retrieving a value.
         * @memberof SampledPositionProperty.prototype
         *
         * @type {InterpolationAlgorithm}
         * @default LinearApproximation
         */
        interpolationAlgorithm : {
            get : function() {
                return this._property.interpolationAlgorithm;
            }
        },
        /**
         * The number of derivatives contained by this property; i.e. 0 for just position, 1 for velocity, etc.
         * @memberof SampledPositionProperty.prototype
         *
         * @type {Boolean}
         * @default false
         */
        numberOfDerivatives : {
            get : function() {
                return this._numberOfDerivatives;
            }
        },
        /**
         * Gets or sets the type of extrapolation to perform when a value
         * is requested at a time after any available samples.
         * @memberof SampledPositionProperty.prototype
         * @type {ExtrapolationType}
         * @default ExtrapolationType.NONE
         */
        forwardExtrapolationType : {
            get : function() {
                return this._property.forwardExtrapolationType;
            },
            set : function(value) {
                this._property.forwardExtrapolationType = value;
            }
        },
        /**
         * Gets or sets the amount of time to extrapolate forward before
         * the property becomes undefined.  A value of 0 will extrapolate forever.
         * @memberof SampledPositionProperty.prototype
         * @type {Number}
         * @default 0
         */
        forwardExtrapolationDuration : {
            get : function() {
                return this._property.forwardExtrapolationDuration;
            },
            set : function(value) {
                this._property.forwardExtrapolationDuration = value;
            }
        },
        /**
         * Gets or sets the type of extrapolation to perform when a value
         * is requested at a time before any available samples.
         * @memberof SampledPositionProperty.prototype
         * @type {ExtrapolationType}
         * @default ExtrapolationType.NONE
         */
        backwardExtrapolationType : {
            get : function() {
                return this._property.backwardExtrapolationType;
            },
            set : function(value) {
                this._property.backwardExtrapolationType = value;
            }
        },
        /**
         * Gets or sets the amount of time to extrapolate backward
         * before the property becomes undefined.  A value of 0 will extrapolate forever.
         * @memberof SampledPositionProperty.prototype
         * @type {Number}
         * @default 0
         */
        backwardExtrapolationDuration : {
            get : function() {
                return this._property.backwardExtrapolationDuration;
            },
            set : function(value) {
                this._property.backwardExtrapolationDuration = value;
            }
        }
    });

    /**
     * Gets the position at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    SampledPositionProperty.prototype.getValue = function(time, result) {
        return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
    };

    /**
     * Gets the position at the provided time and in the provided reference frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    SampledPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(referenceFrame)) {
            throw new DeveloperError('referenceFrame is required.');
        }
        
        result = this._property.getValue(time, result);
        if (defined(result)) {
            return PositionProperty.convertToReferenceFrame(time, result, this._referenceFrame, referenceFrame, result);
        }
        return undefined;
    };

    /**
     * Sets the algorithm and degree to use when interpolating a position.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {InterpolationAlgorithm} [options.interpolationAlgorithm] The new interpolation algorithm.  If undefined, the existing property will be unchanged.
     * @param {Number} [options.interpolationDegree] The new interpolation degree.  If undefined, the existing property will be unchanged.
     */
    SampledPositionProperty.prototype.setInterpolationOptions = function(options) {
        this._property.setInterpolationOptions(options);
    };

    /**
     * Adds a new sample.
     *
     * @param {JulianDate} time The sample time.
     * @param {Cartesian3} position The position at the provided time.
     * @param {Cartesian3[]} [derivatives] The array of derivative values at the provided time.
     */
    SampledPositionProperty.prototype.addSample = function(time, position, derivatives) {
        var numberOfDerivatives = this._numberOfDerivatives;
                if (numberOfDerivatives > 0 && (!defined(derivatives) || derivatives.length !== numberOfDerivatives)) {
            throw new DeveloperError('derivatives length must be equal to the number of derivatives.');
        }
                this._property.addSample(time, position, derivatives);
    };

    /**
     * Adds multiple samples via parallel arrays.
     *
     * @param {JulianDate[]} times An array of JulianDate instances where each index is a sample time.
     * @param {Cartesian3[]} positions An array of Cartesian3 position instances, where each value corresponds to the provided time index.
     * @param {Array[]} [derivatives] An array where each value is another array containing derivatives for the corresponding time index.
     *
     * @exception {DeveloperError} All arrays must be the same length.
     */
    SampledPositionProperty.prototype.addSamples = function(times, positions, derivatives) {
        this._property.addSamples(times, positions, derivatives);
    };

    /**
     * Adds samples as a single packed array where each new sample is represented as a date,
     * followed by the packed representation of the corresponding value and derivatives.
     *
     * @param {Number[]} packedSamples The array of packed samples.
     * @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
     */
    SampledPositionProperty.prototype.addSamplesPackedArray = function(packedSamples, epoch) {
        this._property.addSamplesPackedArray(packedSamples, epoch);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    SampledPositionProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof SampledPositionProperty &&
                Property.equals(this._property, other._property) && //
                this._referenceFrame === other._referenceFrame);
    };

    return SampledPositionProperty;
});

/*global define*/
define('DataSources/StripeOrientation',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Defined the orientation of stripes in {@link StripeMaterialProperty}.
     *
     * @exports StripeOrientation
     */
    var StripeOrientation = {
        /**
         * Horizontal orientation.
         * @type {Number}
         */
        HORIZONTAL : 0,

        /**
         * Vertical orientation.
         * @type {Number}
         */
        VERTICAL : 1
    };

    return freezeObject(StripeOrientation);
});

/*global define*/
define('DataSources/StripeMaterialProperty',[
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        './createPropertyDescriptor',
        './Property',
        './StripeOrientation'
    ], function(
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        createPropertyDescriptor,
        Property,
        StripeOrientation) {
    'use strict';

    var defaultOrientation = StripeOrientation.HORIZONTAL;
    var defaultEvenColor = Color.WHITE;
    var defaultOddColor = Color.BLACK;
    var defaultOffset = 0;
    var defaultRepeat = 1;

    /**
     * A {@link MaterialProperty} that maps to stripe {@link Material} uniforms.
     * @alias StripeMaterialProperty
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Property} [options.evenColor=Color.WHITE] A Property specifying the first {@link Color}.
     * @param {Property} [options.oddColor=Color.BLACK] A Property specifying the second {@link Color}.
     * @param {Property} [options.repeat=1] A numeric Property specifying how many times the stripes repeat.
     * @param {Property} [options.offset=0] A numeric Property specifying how far into the pattern to start the material.
     * @param {Property} [options.orientation=StripeOrientation.HORIZONTAL] A Property specifying the {@link StripeOrientation}.
     */
    function StripeMaterialProperty(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._definitionChanged = new Event();

        this._orientation = undefined;
        this._orientationSubscription = undefined;

        this._evenColor = undefined;
        this._evenColorSubscription = undefined;

        this._oddColor = undefined;
        this._oddColorSubscription = undefined;

        this._offset = undefined;
        this._offsetSubscription = undefined;

        this._repeat = undefined;
        this._repeatSubscription = undefined;

        this.orientation = options.orientation;
        this.evenColor = options.evenColor;
        this.oddColor = options.oddColor;
        this.offset = options.offset;
        this.repeat = options.repeat;
    }

    defineProperties(StripeMaterialProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof StripeMaterialProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._orientation) && //
                       Property.isConstant(this._evenColor) && //
                       Property.isConstant(this._oddColor) && //
                       Property.isConstant(this._offset) && //
                       Property.isConstant(this._repeat);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof StripeMaterialProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the Property specifying the {@link StripeOrientation}/
         * @memberof StripeMaterialProperty.prototype
         * @type {Property}
         * @default StripeOrientation.HORIZONTAL
         */
        orientation : createPropertyDescriptor('orientation'),
        /**
         * Gets or sets the Property specifying the first {@link Color}.
         * @memberof StripeMaterialProperty.prototype
         * @type {Property}
         * @default Color.WHITE
         */
        evenColor : createPropertyDescriptor('evenColor'),
        /**
         * Gets or sets the Property specifying the second {@link Color}.
         * @memberof StripeMaterialProperty.prototype
         * @type {Property}
         * @default Color.BLACK
         */
        oddColor : createPropertyDescriptor('oddColor'),
        /**
         * Gets or sets the numeric Property specifying the point into the pattern
         * to begin drawing; with 0.0 being the beginning of the even color, 1.0 the beginning
         * of the odd color, 2.0 being the even color again, and any multiple or fractional values
         * being in between.
         * @memberof StripeMaterialProperty.prototype
         * @type {Property}
         * @default 0.0
         */
        offset : createPropertyDescriptor('offset'),
        /**
         * Gets or sets the numeric Property specifying how many times the stripes repeat.
         * @memberof StripeMaterialProperty.prototype
         * @type {Property}
         * @default 1.0
         */
        repeat : createPropertyDescriptor('repeat')
    });

    /**
     * Gets the {@link Material} type at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the type.
     * @returns {String} The type of material.
     */
    StripeMaterialProperty.prototype.getType = function(time) {
        return 'Stripe';
    };

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    StripeMaterialProperty.prototype.getValue = function(time, result) {
        if (!defined(result)) {
            result = {};
        }
        result.horizontal = Property.getValueOrDefault(this._orientation, time, defaultOrientation) === StripeOrientation.HORIZONTAL;
        result.evenColor = Property.getValueOrClonedDefault(this._evenColor, time, defaultEvenColor, result.evenColor);
        result.oddColor = Property.getValueOrClonedDefault(this._oddColor, time, defaultOddColor, result.oddColor);
        result.offset = Property.getValueOrDefault(this._offset, time, defaultOffset);
        result.repeat = Property.getValueOrDefault(this._repeat, time, defaultRepeat);
        return result;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    StripeMaterialProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof StripeMaterialProperty && //
                       Property.equals(this._orientation, other._orientation) && //
                       Property.equals(this._evenColor, other._evenColor) && //
                       Property.equals(this._oddColor, other._oddColor) && //
                       Property.equals(this._offset, other._offset) && //
                       Property.equals(this._repeat, other._repeat));
    };

    return StripeMaterialProperty;
});

/*global define*/
define('DataSources/TimeIntervalCollectionPositionProperty',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/ReferenceFrame',
        '../Core/TimeIntervalCollection',
        './PositionProperty',
        './Property'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        ReferenceFrame,
        TimeIntervalCollection,
        PositionProperty,
        Property) {
    'use strict';

    /**
     * A {@link TimeIntervalCollectionProperty} which is also a {@link PositionProperty}.
     *
     * @alias TimeIntervalCollectionPositionProperty
     * @constructor
     *
     * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.FIXED] The reference frame in which the position is defined.
     */
    function TimeIntervalCollectionPositionProperty(referenceFrame) {
        this._definitionChanged = new Event();
        this._intervals = new TimeIntervalCollection();
        this._intervals.changedEvent.addEventListener(TimeIntervalCollectionPositionProperty.prototype._intervalsChanged, this);
        this._referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
    }

    defineProperties(TimeIntervalCollectionPositionProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof TimeIntervalCollectionPositionProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._intervals.isEmpty;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is considered to have changed if a call to getValue would return
         * a different result for the same time.
         * @memberof TimeIntervalCollectionPositionProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the interval collection.
         * @memberof TimeIntervalCollectionPositionProperty.prototype
         * @type {TimeIntervalCollection}
         */
        intervals : {
            get : function() {
                return this._intervals;
            }
        },
        /**
         * Gets the reference frame in which the position is defined.
         * @memberof TimeIntervalCollectionPositionProperty.prototype
         * @type {ReferenceFrame}
         * @default ReferenceFrame.FIXED;
         */
        referenceFrame : {
            get : function() {
                return this._referenceFrame;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time in the fixed frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    TimeIntervalCollectionPositionProperty.prototype.getValue = function(time, result) {
        return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
    };

    /**
     * Gets the value of the property at the provided time and in the provided reference frame.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    TimeIntervalCollectionPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(referenceFrame)) {
            throw new DeveloperError('referenceFrame is required.');
        }
        
        var position = this._intervals.findDataForIntervalContainingDate(time);
        if (defined(position)) {
            return PositionProperty.convertToReferenceFrame(time, position, this._referenceFrame, referenceFrame, result);
        }
        return undefined;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    TimeIntervalCollectionPositionProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof TimeIntervalCollectionPositionProperty && //
                this._intervals.equals(other._intervals, Property.equals) && //
                this._referenceFrame === other._referenceFrame);
    };

    /**
     * @private
     */
    TimeIntervalCollectionPositionProperty.prototype._intervalsChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return TimeIntervalCollectionPositionProperty;
});

/*global define*/
define('DataSources/TimeIntervalCollectionProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/TimeIntervalCollection',
        './Property'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Event,
        TimeIntervalCollection,
        Property) {
    'use strict';

    /**
     * A {@link Property} which is defined by a {@link TimeIntervalCollection}, where the
     * data property of each {@link TimeInterval} represents the value at time.
     *
     * @alias TimeIntervalCollectionProperty
     * @constructor
     *
     * @example
     * //Create a Cartesian2 interval property which contains data on August 1st, 2012
     * //and uses a different value every 6 hours.
     * var composite = new Cesium.TimeIntervalCollectionProperty();
     * composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2012-08-01T00:00:00.00Z/2012-08-01T06:00:00.00Z',
     *     isStartIncluded : true,
     *     isStopIncluded : false,
     *     data : new Cesium.Cartesian2(2.0, 3.4)
     * }));
     * composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2012-08-01T06:00:00.00Z/2012-08-01T12:00:00.00Z',
     *     isStartIncluded : true,
     *     isStopIncluded : false,
     *     data : new Cesium.Cartesian2(12.0, 2.7)
     * }));
     * composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2012-08-01T12:00:00.00Z/2012-08-01T18:00:00.00Z',
     *     isStartIncluded : true,
     *     isStopIncluded : false,
     *     data : new Cesium.Cartesian2(5.0, 12.4)
     * }));
     * composite.intervals.addInterval(Cesium.TimeInterval.fromIso8601({
     *     iso8601 : '2012-08-01T18:00:00.00Z/2012-08-02T00:00:00.00Z',
     *     isStartIncluded : true,
     *     isStopIncluded : true,
     *     data : new Cesium.Cartesian2(85.0, 4.1)
     * }));
     */
    function TimeIntervalCollectionProperty() {
        this._definitionChanged = new Event();
        this._intervals = new TimeIntervalCollection();
        this._intervals.changedEvent.addEventListener(TimeIntervalCollectionProperty.prototype._intervalsChanged, this);
    }

    defineProperties(TimeIntervalCollectionProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.  A property is considered
         * constant if getValue always returns the same result for the current definition.
         * @memberof TimeIntervalCollectionProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return this._intervals.isEmpty;
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * The definition is changed whenever setValue is called with data different
         * than the current value.
         * @memberof TimeIntervalCollectionProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets the interval collection.
         * @memberof TimeIntervalCollectionProperty.prototype
         *
         * @type {TimeIntervalCollection}
         */
        intervals : {
            get : function() {
                return this._intervals;
            }
        }
    });

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve the value.
     * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    TimeIntervalCollectionProperty.prototype.getValue = function(time, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required');
        }
        
        var value = this._intervals.findDataForIntervalContainingDate(time);
        if (defined(value) && (typeof value.clone === 'function')) {
            return value.clone(result);
        }
        return value;
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    TimeIntervalCollectionProperty.prototype.equals = function(other) {
        return this === other || //
               (other instanceof TimeIntervalCollectionProperty && //
               this._intervals.equals(other._intervals, Property.equals));
    };

    /**
     * @private
     */
    TimeIntervalCollectionProperty.prototype._intervalsChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return TimeIntervalCollectionProperty;
});

/*global define*/
define('DataSources/VelocityVectorProperty',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/JulianDate',
        './Property'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        JulianDate,
        Property) {
    'use strict';

    /**
     * A {@link Property} which evaluates to a {@link Cartesian3} vector
     * based on the velocity of the provided {@link PositionProperty}.
     *
     * @alias VelocityVectorProperty
     * @constructor
     *
     * @param {Property} [position] The position property used to compute the velocity.
     * @param {Boolean} [normalize=true] Whether to normalize the computed velocity vector.
     *
     * @example
     * //Create an entity with a billboard rotated to match its velocity.
     * var position = new Cesium.SampledProperty();
     * position.addSamples(...);
     * var entity = viewer.entities.add({
     *   position : position,
     *   billboard : {
     *     image : 'image.png',
     *     alignedAxis : new Cesium.VelocityVectorProperty(position, true) // alignedAxis must be a unit vector
     *   }
     * }));
     */
    function VelocityVectorProperty(position, normalize) {
        this._position = undefined;
        this._subscription = undefined;
        this._definitionChanged = new Event();
        this._normalize = defaultValue(normalize, true);

        this.position = position;
    }

    defineProperties(VelocityVectorProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.
         * @memberof VelocityVectorProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._position);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * @memberof VelocityVectorProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the position property used to compute the velocity vector.
         * @memberof VelocityVectorProperty.prototype
         *
         * @type {Property}
         */
        position : {
            get : function() {
                return this._position;
            },
            set : function(value) {
                var oldValue = this._position;
                if (oldValue !== value) {
                    if (defined(oldValue)) {
                        this._subscription();
                    }

                    this._position = value;

                    if (defined(value)) {
                        this._subscription = value._definitionChanged.addEventListener(function() {
                            this._definitionChanged.raiseEvent(this);
                        }, this);
                    }

                    this._definitionChanged.raiseEvent(this);
                }
            }
        },
        /**
         * Gets or sets whether the vector produced by this property
         * will be normalized or not.
         * @memberof VelocityVectorProperty.prototype
         *
         * @type {Boolean}
         */
        normalize : {
            get : function() {
                return this._normalize;
            },
            set : function(value) {
                if (this._normalize === value) {
                    return;
                }

                this._normalize = value;
                this._definitionChanged.raiseEvent(this);
            }
        }
    });

    var position1Scratch = new Cartesian3();
    var position2Scratch = new Cartesian3();
    var timeScratch = new JulianDate();
    var step = 1.0 / 60.0;

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} [time] The time for which to retrieve the value.
     * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    VelocityVectorProperty.prototype.getValue = function(time, result) {
        return this._getValue(time, result);
    };

    /**
     * @private
     */
    VelocityVectorProperty.prototype._getValue = function(time, velocityResult, positionResult) {
                if (!defined(time)) {
            throw new DeveloperError('time is required');
        }
        
        if (!defined(velocityResult)) {
            velocityResult = new Cartesian3();
        }

        var property = this._position;
        if (Property.isConstant(property)) {
            return this._normalize ? undefined : Cartesian3.clone(Cartesian3.ZERO, velocityResult);
        }

        var position1 = property.getValue(time, position1Scratch);
        var position2 = property.getValue(JulianDate.addSeconds(time, step, timeScratch), position2Scratch);

        //If we don't have a position for now, return undefined.
        if (!defined(position1)) {
            return undefined;
        }

        //If we don't have a position for now + step, see if we have a position for now - step.
        if (!defined(position2)) {
            position2 = position1;
            position1 = property.getValue(JulianDate.addSeconds(time, -step, timeScratch), position2Scratch);

            if (!defined(position1)) {
                return undefined;
            }
        }

        if (Cartesian3.equals(position1, position2)) {
            return this._normalize ? undefined : Cartesian3.clone(Cartesian3.ZERO, velocityResult);
        }

        if (defined(positionResult)) {
            position1.clone(positionResult);
        }

        var velocity = Cartesian3.subtract(position2, position1, velocityResult);
        if (this._normalize) {
            return Cartesian3.normalize(velocity, velocityResult);
        } else {
            return Cartesian3.divideByScalar(velocity, step, velocityResult);
        }
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    VelocityVectorProperty.prototype.equals = function(other) {
        return this === other ||//
               (other instanceof VelocityVectorProperty &&
                Property.equals(this._position, other._position));
    };

    return VelocityVectorProperty;
});

/*global define*/
define('DataSources/CzmlDataSource',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/ClockRange',
        '../Core/ClockStep',
        '../Core/Color',
        '../Core/CornerType',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/Event',
        '../Core/ExtrapolationType',
        '../Core/getAbsoluteUri',
        '../Core/getFilenameFromUri',
        '../Core/HermitePolynomialApproximation',
        '../Core/isArray',
        '../Core/Iso8601',
        '../Core/JulianDate',
        '../Core/LagrangePolynomialApproximation',
        '../Core/LinearApproximation',
        '../Core/loadJson',
        '../Core/Math',
        '../Core/NearFarScalar',
        '../Core/Quaternion',
        '../Core/Rectangle',
        '../Core/ReferenceFrame',
        '../Core/RuntimeError',
        '../Core/Spherical',
        '../Core/TimeInterval',
        '../Core/TimeIntervalCollection',
        '../Scene/ColorBlendMode',
        '../Scene/HeightReference',
        '../Scene/HorizontalOrigin',
        '../Scene/LabelStyle',
        '../Scene/ShadowMode',
        '../Scene/VerticalOrigin',
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        './BillboardGraphics',
        './BoxGraphics',
        './ColorMaterialProperty',
        './CompositeMaterialProperty',
        './CompositePositionProperty',
        './CompositeProperty',
        './ConstantPositionProperty',
        './ConstantProperty',
        './CorridorGraphics',
        './CylinderGraphics',
        './DataSource',
        './DataSourceClock',
        './EllipseGraphics',
        './EllipsoidGraphics',
        './EntityCluster',
        './EntityCollection',
        './GridMaterialProperty',
        './ImageMaterialProperty',
        './LabelGraphics',
        './ModelGraphics',
        './NodeTransformationProperty',
        './PathGraphics',
        './PointGraphics',
        './PolygonGraphics',
        './PolylineArrowMaterialProperty',
        './PolylineGlowMaterialProperty',
        './PolylineGraphics',
        './PolylineOutlineMaterialProperty',
        './PositionPropertyArray',
        './PropertyArray',
        './PropertyBag',
        './RectangleGraphics',
        './ReferenceProperty',
        './Rotation',
        './SampledPositionProperty',
        './SampledProperty',
        './StripeMaterialProperty',
        './StripeOrientation',
        './TimeIntervalCollectionPositionProperty',
        './TimeIntervalCollectionProperty',
        './VelocityVectorProperty',
        './WallGraphics'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Cartographic,
        ClockRange,
        ClockStep,
        Color,
        CornerType,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        Event,
        ExtrapolationType,
        getAbsoluteUri,
        getFilenameFromUri,
        HermitePolynomialApproximation,
        isArray,
        Iso8601,
        JulianDate,
        LagrangePolynomialApproximation,
        LinearApproximation,
        loadJson,
        CesiumMath,
        NearFarScalar,
        Quaternion,
        Rectangle,
        ReferenceFrame,
        RuntimeError,
        Spherical,
        TimeInterval,
        TimeIntervalCollection,
        ColorBlendMode,
        HeightReference,
        HorizontalOrigin,
        LabelStyle,
        ShadowMode,
        VerticalOrigin,
        Uri,
        when,
        BillboardGraphics,
        BoxGraphics,
        ColorMaterialProperty,
        CompositeMaterialProperty,
        CompositePositionProperty,
        CompositeProperty,
        ConstantPositionProperty,
        ConstantProperty,
        CorridorGraphics,
        CylinderGraphics,
        DataSource,
        DataSourceClock,
        EllipseGraphics,
        EllipsoidGraphics,
        EntityCluster,
        EntityCollection,
        GridMaterialProperty,
        ImageMaterialProperty,
        LabelGraphics,
        ModelGraphics,
        NodeTransformationProperty,
        PathGraphics,
        PointGraphics,
        PolygonGraphics,
        PolylineArrowMaterialProperty,
        PolylineGlowMaterialProperty,
        PolylineGraphics,
        PolylineOutlineMaterialProperty,
        PositionPropertyArray,
        PropertyArray,
        PropertyBag,
        RectangleGraphics,
        ReferenceProperty,
        Rotation,
        SampledPositionProperty,
        SampledProperty,
        StripeMaterialProperty,
        StripeOrientation,
        TimeIntervalCollectionPositionProperty,
        TimeIntervalCollectionProperty,
        VelocityVectorProperty,
        WallGraphics) {
    'use strict';

    var currentId;

    function makeReference(collection, referenceString) {
        if (referenceString[0] === '#') {
            referenceString = currentId + referenceString;
        }
        return ReferenceProperty.fromString(collection, referenceString);
    }

    var scratchCartesian = new Cartesian3();
    var scratchSpherical = new Spherical();
    var scratchCartographic = new Cartographic();
    var scratchTimeInterval = new TimeInterval();

    function unwrapColorInterval(czmlInterval) {
        var rgbaf = czmlInterval.rgbaf;
        if (defined(rgbaf)) {
            return rgbaf;
        }

        var rgba = czmlInterval.rgba;
        if (!defined(rgba)) {
            return undefined;
        }

        var length = rgba.length;
        if (length === Color.packedLength) {
            return [Color.byteToFloat(rgba[0]), Color.byteToFloat(rgba[1]), Color.byteToFloat(rgba[2]), Color.byteToFloat(rgba[3])];
        }

        rgbaf = new Array(length);
        for (var i = 0; i < length; i += 5) {
            rgbaf[i] = rgba[i];
            rgbaf[i + 1] = Color.byteToFloat(rgba[i + 1]);
            rgbaf[i + 2] = Color.byteToFloat(rgba[i + 2]);
            rgbaf[i + 3] = Color.byteToFloat(rgba[i + 3]);
            rgbaf[i + 4] = Color.byteToFloat(rgba[i + 4]);
        }
        return rgbaf;
    }

    function unwrapUriInterval(czmlInterval, sourceUri) {
        var result = defaultValue(czmlInterval.uri, czmlInterval);
        if (defined(sourceUri)) {
            result = getAbsoluteUri(result, getAbsoluteUri(sourceUri));
        }
        return result;
    }

    function unwrapRectangleInterval(czmlInterval) {
        var wsen = czmlInterval.wsen;
        if (defined(wsen)) {
            return wsen;
        }

        var wsenDegrees = czmlInterval.wsenDegrees;
        if (!defined(wsenDegrees)) {
            return undefined;
        }

        var length = wsenDegrees.length;
        if (length === Rectangle.packedLength) {
            return [CesiumMath.toRadians(wsenDegrees[0]), CesiumMath.toRadians(wsenDegrees[1]), CesiumMath.toRadians(wsenDegrees[2]), CesiumMath.toRadians(wsenDegrees[3])];
        }

        wsen = new Array(length);
        for (var i = 0; i < length; i += 5) {
            wsen[i] = wsenDegrees[i];
            wsen[i + 1] = CesiumMath.toRadians(wsenDegrees[i + 1]);
            wsen[i + 2] = CesiumMath.toRadians(wsenDegrees[i + 2]);
            wsen[i + 3] = CesiumMath.toRadians(wsenDegrees[i + 3]);
            wsen[i + 4] = CesiumMath.toRadians(wsenDegrees[i + 4]);
        }
        return wsen;
    }

    function convertUnitSphericalToCartesian(unitSpherical) {
        var length = unitSpherical.length;
        scratchSpherical.magnitude = 1.0;
        if (length === 2) {
            scratchSpherical.clock = unitSpherical[0];
            scratchSpherical.cone = unitSpherical[1];
            Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);
            return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
        } else {
            var result = new Array(length / 3 * 4);
            for (var i = 0, j = 0; i < length; i += 3, j += 4) {
                result[j] = unitSpherical[i];

                scratchSpherical.clock = unitSpherical[i + 1];
                scratchSpherical.cone = unitSpherical[i + 2];
                Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);

                result[j + 1] = scratchCartesian.x;
                result[j + 2] = scratchCartesian.y;
                result[j + 3] = scratchCartesian.z;
            }
            return result;
        }
    }

    function convertSphericalToCartesian(spherical) {
        var length = spherical.length;
        if (length === 3) {
            scratchSpherical.clock = spherical[0];
            scratchSpherical.cone = spherical[1];
            scratchSpherical.magnitude = spherical[2];
            Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);
            return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
        } else {
            var result = new Array(length);
            for (var i = 0; i < length; i += 4) {
                result[i] = spherical[i];

                scratchSpherical.clock = spherical[i + 1];
                scratchSpherical.cone = spherical[i + 2];
                scratchSpherical.magnitude = spherical[i + 3];
                Cartesian3.fromSpherical(scratchSpherical, scratchCartesian);

                result[i + 1] = scratchCartesian.x;
                result[i + 2] = scratchCartesian.y;
                result[i + 3] = scratchCartesian.z;
            }
            return result;
        }
    }

    function convertCartographicRadiansToCartesian(cartographicRadians) {
        var length = cartographicRadians.length;
        if (length === 3) {
            scratchCartographic.longitude = cartographicRadians[0];
            scratchCartographic.latitude = cartographicRadians[1];
            scratchCartographic.height = cartographicRadians[2];
            Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);
            return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
        } else {
            var result = new Array(length);
            for (var i = 0; i < length; i += 4) {
                result[i] = cartographicRadians[i];

                scratchCartographic.longitude = cartographicRadians[i + 1];
                scratchCartographic.latitude = cartographicRadians[i + 2];
                scratchCartographic.height = cartographicRadians[i + 3];
                Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);

                result[i + 1] = scratchCartesian.x;
                result[i + 2] = scratchCartesian.y;
                result[i + 3] = scratchCartesian.z;
            }
            return result;
        }
    }

    function convertCartographicDegreesToCartesian(cartographicDegrees) {
        var length = cartographicDegrees.length;
        if (length === 3) {
            scratchCartographic.longitude = CesiumMath.toRadians(cartographicDegrees[0]);
            scratchCartographic.latitude = CesiumMath.toRadians(cartographicDegrees[1]);
            scratchCartographic.height = cartographicDegrees[2];
            Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);
            return [scratchCartesian.x, scratchCartesian.y, scratchCartesian.z];
        } else {
            var result = new Array(length);
            for (var i = 0; i < length; i += 4) {
                result[i] = cartographicDegrees[i];

                scratchCartographic.longitude = CesiumMath.toRadians(cartographicDegrees[i + 1]);
                scratchCartographic.latitude = CesiumMath.toRadians(cartographicDegrees[i + 2]);
                scratchCartographic.height = cartographicDegrees[i + 3];
                Ellipsoid.WGS84.cartographicToCartesian(scratchCartographic, scratchCartesian);

                result[i + 1] = scratchCartesian.x;
                result[i + 2] = scratchCartesian.y;
                result[i + 3] = scratchCartesian.z;
            }
            return result;
        }
    }

    function unwrapCartesianInterval(czmlInterval) {
        var cartesian = czmlInterval.cartesian;
        if (defined(cartesian)) {
            return cartesian;
        }

        var cartesianVelocity = czmlInterval.cartesianVelocity;
        if (defined(cartesianVelocity)) {
            return cartesianVelocity;
        }

        var unitCartesian = czmlInterval.unitCartesian;
        if (defined(unitCartesian)) {
            return unitCartesian;
        }

        var unitSpherical = czmlInterval.unitSpherical;
        if (defined(unitSpherical)) {
            return convertUnitSphericalToCartesian(unitSpherical);
        }

        var spherical = czmlInterval.spherical;
        if (defined(spherical)) {
            return convertSphericalToCartesian(spherical);
        }

        var cartographicRadians = czmlInterval.cartographicRadians;
        if (defined(cartographicRadians)) {
            return convertCartographicRadiansToCartesian(cartographicRadians);
        }

        var cartographicDegrees = czmlInterval.cartographicDegrees;
        if (defined(cartographicDegrees)) {
            return convertCartographicDegreesToCartesian(cartographicDegrees);
        }

        throw new RuntimeError(JSON.stringify(czmlInterval) + ' is not a valid CZML interval.');
    }

    function normalizePackedQuaternionArray(array, startingIndex) {
        var x = array[startingIndex];
        var y = array[startingIndex + 1];
        var z = array[startingIndex + 2];
        var w = array[startingIndex + 3];

        var inverseMagnitude = 1.0 / Math.sqrt(x * x + y * y + z * z + w * w);
        array[startingIndex] = x * inverseMagnitude;
        array[startingIndex + 1] = y * inverseMagnitude;
        array[startingIndex + 2] = z * inverseMagnitude;
        array[startingIndex + 3] = w * inverseMagnitude;
    }

    function unwrapQuaternionInterval(czmlInterval) {
        var unitQuaternion = czmlInterval.unitQuaternion;
        if (defined(unitQuaternion)) {
            if (unitQuaternion.length === 4) {
                normalizePackedQuaternionArray(unitQuaternion, 0);
                return unitQuaternion;
            }

            for (var i = 1; i < unitQuaternion.length; i += 5) {
                normalizePackedQuaternionArray(unitQuaternion, i);
            }
        }
        return unitQuaternion;
    }

    function unwrapInterval(type, czmlInterval, sourceUri) {
        /*jshint sub:true*/
        switch (type) {
            case Array:
                return czmlInterval.array;
            case Boolean:
                return defaultValue(czmlInterval['boolean'], czmlInterval);
            case BoundingRectangle:
                return czmlInterval.boundingRectangle;
            case Cartesian2:
                return czmlInterval.cartesian2;
            case Cartesian3:
                return unwrapCartesianInterval(czmlInterval);
            case Color:
                return unwrapColorInterval(czmlInterval);
            case ColorBlendMode:
                return ColorBlendMode[defaultValue(czmlInterval.colorBlendMode, czmlInterval)];
            case CornerType:
                return CornerType[defaultValue(czmlInterval.cornerType, czmlInterval)];
            case HeightReference:
                return HeightReference[defaultValue(czmlInterval.heightReference, czmlInterval)];
            case HorizontalOrigin:
                return HorizontalOrigin[defaultValue(czmlInterval.horizontalOrigin, czmlInterval)];
            case Image:
                return unwrapUriInterval(czmlInterval, sourceUri);
            case JulianDate:
                return JulianDate.fromIso8601(defaultValue(czmlInterval.date, czmlInterval));
            case LabelStyle:
                return LabelStyle[defaultValue(czmlInterval.labelStyle, czmlInterval)];
            case Number:
                return defaultValue(czmlInterval.number, czmlInterval);
            case NearFarScalar:
                return czmlInterval.nearFarScalar;
            case Quaternion:
                return unwrapQuaternionInterval(czmlInterval);
            case Rotation:
                return defaultValue(czmlInterval.number, czmlInterval);
            case ShadowMode:
                return ShadowMode[defaultValue(czmlInterval.shadows, czmlInterval)];
            case String:
                return defaultValue(czmlInterval.string, czmlInterval);
            case StripeOrientation:
                return StripeOrientation[defaultValue(czmlInterval.stripeOrientation, czmlInterval)];
            case Rectangle:
                return unwrapRectangleInterval(czmlInterval);
            case Uri:
                return unwrapUriInterval(czmlInterval, sourceUri);
            case VerticalOrigin:
                return VerticalOrigin[defaultValue(czmlInterval.verticalOrigin, czmlInterval)];
            default:
                throw new RuntimeError(type);
        }
    }

    var interpolators = {
        HERMITE : HermitePolynomialApproximation,
        LAGRANGE : LagrangePolynomialApproximation,
        LINEAR : LinearApproximation
    };

    function updateInterpolationSettings(packetData, property) {
        var interpolationAlgorithm = packetData.interpolationAlgorithm;
        if (defined(interpolationAlgorithm) || defined(packetData.interpolationDegree)) {
            property.setInterpolationOptions({
                interpolationAlgorithm : interpolators[interpolationAlgorithm],
                interpolationDegree : packetData.interpolationDegree
            });
        }

        var forwardExtrapolationType = packetData.forwardExtrapolationType;
        if (defined(forwardExtrapolationType)) {
            property.forwardExtrapolationType = ExtrapolationType[forwardExtrapolationType];
        }

        var forwardExtrapolationDuration = packetData.forwardExtrapolationDuration;
        if (defined(forwardExtrapolationDuration)) {
            property.forwardExtrapolationDuration = forwardExtrapolationDuration;
        }

        var backwardExtrapolationType = packetData.backwardExtrapolationType;
        if (defined(backwardExtrapolationType)) {
            property.backwardExtrapolationType = ExtrapolationType[backwardExtrapolationType];
        }

        var backwardExtrapolationDuration = packetData.backwardExtrapolationDuration;
        if (defined(backwardExtrapolationDuration)) {
            property.backwardExtrapolationDuration = backwardExtrapolationDuration;
        }
    }

    function processProperty(type, object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection) {
        var combinedInterval;
        var packetInterval = packetData.interval;
        if (defined(packetInterval)) {
            iso8601Scratch.iso8601 = packetInterval;
            combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
            if (defined(constrainedInterval)) {
                combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
            }
        } else if (defined(constrainedInterval)) {
            combinedInterval = constrainedInterval;
        }

        var packedLength;
        var isSampled;
        var unwrappedInterval;
        var unwrappedIntervalLength;
        var isReference = defined(packetData.reference);
        var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL);

        if (!isReference) {
            unwrappedInterval = unwrapInterval(type, packetData, sourceUri);
            packedLength = defaultValue(type.packedLength, 1);
            unwrappedIntervalLength = defaultValue(unwrappedInterval.length, 1);
            isSampled = !defined(packetData.array) && (typeof unwrappedInterval !== 'string') && unwrappedIntervalLength > packedLength;
        }

        //Rotation is a special case because it represents a native type (Number)
        //and therefore does not need to be unpacked when loaded as a constant value.
        var needsUnpacking = typeof type.unpack === 'function' && type !== Rotation;

        //Any time a constant value is assigned, it completely blows away anything else.
        if (!isSampled && !hasInterval) {
            if (isReference) {
                object[propertyName] = makeReference(entityCollection, packetData.reference);
            } else if (needsUnpacking) {
                object[propertyName] = new ConstantProperty(type.unpack(unwrappedInterval, 0));
            } else {
                object[propertyName] = new ConstantProperty(unwrappedInterval);
            }
            return;
        }

        var property = object[propertyName];

        var epoch;
        var packetEpoch = packetData.epoch;
        if (defined(packetEpoch)) {
            epoch = JulianDate.fromIso8601(packetEpoch);
        }

        //Without an interval, any sampled value is infinite, meaning it completely
        //replaces any non-sampled property that may exist.
        if (isSampled && !hasInterval) {
            if (!(property instanceof SampledProperty)) {
                property = new SampledProperty(type);
                object[propertyName] = property;
            }
            property.addSamplesPackedArray(unwrappedInterval, epoch);
            updateInterpolationSettings(packetData, property);
            return;
        }

        var interval;

        //A constant value with an interval is normally part of a TimeIntervalCollection,
        //However, if the current property is not a time-interval collection, we need
        //to turn it into a Composite, preserving the old data with the new interval.
        if (!isSampled && hasInterval) {
            //Create a new interval for the constant value.
            combinedInterval = combinedInterval.clone();
            if (isReference) {
                combinedInterval.data = makeReference(entityCollection, packetData.reference);
            } else if (needsUnpacking) {
                combinedInterval.data = type.unpack(unwrappedInterval, 0);
            } else {
                combinedInterval.data = unwrappedInterval;
            }

            //If no property exists, simply use a new interval collection
            if (!defined(property)) {
                if (isReference) {
                    property = new CompositeProperty();
                } else {
                    property = new TimeIntervalCollectionProperty();
                }
                object[propertyName] = property;
            }

            if (!isReference && property instanceof TimeIntervalCollectionProperty) {
                //If we create a collection, or it already existed, use it.
                property.intervals.addInterval(combinedInterval);
            } else if (property instanceof CompositeProperty) {
                //If the collection was already a CompositeProperty, use it.
                combinedInterval.data = isReference ? combinedInterval.data : new ConstantProperty(combinedInterval.data);
                property.intervals.addInterval(combinedInterval);
            } else {
                //Otherwise, create a CompositeProperty but preserve the existing data.

                //Put the old property in an infinite interval.
                interval = Iso8601.MAXIMUM_INTERVAL.clone();
                interval.data = property;

                //Create the composite.
                property = new CompositeProperty();
                object[propertyName] = property;

                //add the old property interval
                property.intervals.addInterval(interval);

                //Change the new data to a ConstantProperty and add it.
                combinedInterval.data = isReference ? combinedInterval.data : new ConstantProperty(combinedInterval.data);
                property.intervals.addInterval(combinedInterval);
            }

            return;
        }

        //isSampled && hasInterval
        if (!defined(property)) {
            property = new CompositeProperty();
            object[propertyName] = property;
        }

        //create a CompositeProperty but preserve the existing data.
        if (!(property instanceof CompositeProperty)) {
            //Put the old property in an infinite interval.
            interval = Iso8601.MAXIMUM_INTERVAL.clone();
            interval.data = property;

            //Create the composite.
            property = new CompositeProperty();
            object[propertyName] = property;

            //add the old property interval
            property.intervals.addInterval(interval);
        }

        //Check if the interval already exists in the composite
        var intervals = property.intervals;
        interval = intervals.findInterval(combinedInterval);
        if (!defined(interval) || !(interval.data instanceof SampledProperty)) {
            //If not, create a SampledProperty for it.
            interval = combinedInterval.clone();
            interval.data = new SampledProperty(type);
            intervals.addInterval(interval);
        }
        interval.data.addSamplesPackedArray(unwrappedInterval, epoch);
        updateInterpolationSettings(packetData, interval.data);
    }

    function processPacketData(type, object, propertyName, packetData, interval, sourceUri, entityCollection) {
        if (!defined(packetData)) {
            return;
        }

        if (isArray(packetData)) {
            for (var i = 0, len = packetData.length; i < len; i++) {
                processProperty(type, object, propertyName, packetData[i], interval, sourceUri, entityCollection);
            }
        } else {
            processProperty(type, object, propertyName, packetData, interval, sourceUri, entityCollection);
        }
    }

    function processPositionProperty(object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection) {
        var combinedInterval;
        var packetInterval = packetData.interval;
        if (defined(packetInterval)) {
            iso8601Scratch.iso8601 = packetInterval;
            combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
            if (defined(constrainedInterval)) {
                combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
            }
        } else if (defined(constrainedInterval)) {
            combinedInterval = constrainedInterval;
        }

        var referenceFrame;
        var unwrappedInterval;
        var isSampled = false;
        var unwrappedIntervalLength;
        var numberOfDerivatives = defined(packetData.cartesianVelocity) ? 1 : 0;
        var packedLength = Cartesian3.packedLength * (numberOfDerivatives + 1);
        var isReference = defined(packetData.reference);
        var hasInterval = defined(combinedInterval) && !combinedInterval.equals(Iso8601.MAXIMUM_INTERVAL);

        if (!isReference) {
            if (defined(packetData.referenceFrame)) {
                referenceFrame = ReferenceFrame[packetData.referenceFrame];
            }
            referenceFrame = defaultValue(referenceFrame, ReferenceFrame.FIXED);
            unwrappedInterval = unwrapCartesianInterval(packetData);
            unwrappedIntervalLength = defaultValue(unwrappedInterval.length, 1);
            isSampled = unwrappedIntervalLength > packedLength;
        }

        //Any time a constant value is assigned, it completely blows away anything else.
        if (!isSampled && !hasInterval) {
            if (isReference) {
                object[propertyName] = makeReference(entityCollection, packetData.reference);
            } else {
                object[propertyName] = new ConstantPositionProperty(Cartesian3.unpack(unwrappedInterval), referenceFrame);
            }
            return;
        }

        var property = object[propertyName];

        var epoch;
        var packetEpoch = packetData.epoch;
        if (defined(packetEpoch)) {
            epoch = JulianDate.fromIso8601(packetEpoch);
        }

        //Without an interval, any sampled value is infinite, meaning it completely
        //replaces any non-sampled property that may exist.
        if (isSampled && !hasInterval) {
            if (!(property instanceof SampledPositionProperty) || (defined(referenceFrame) && property.referenceFrame !== referenceFrame)) {
                property = new SampledPositionProperty(referenceFrame, numberOfDerivatives);
                object[propertyName] = property;
            }
            property.addSamplesPackedArray(unwrappedInterval, epoch);
            updateInterpolationSettings(packetData, property);
            return;
        }

        var interval;

        //A constant value with an interval is normally part of a TimeIntervalCollection,
        //However, if the current property is not a time-interval collection, we need
        //to turn it into a Composite, preserving the old data with the new interval.
        if (!isSampled && hasInterval) {
            //Create a new interval for the constant value.
            combinedInterval = combinedInterval.clone();
            if (isReference) {
                combinedInterval.data = makeReference(entityCollection, packetData.reference);
            } else {
                combinedInterval.data = Cartesian3.unpack(unwrappedInterval);
            }

            //If no property exists, simply use a new interval collection
            if (!defined(property)) {
                if (isReference) {
                    property = new CompositePositionProperty(referenceFrame);
                } else {
                    property = new TimeIntervalCollectionPositionProperty(referenceFrame);
                }
                object[propertyName] = property;
            }

            if (!isReference && property instanceof TimeIntervalCollectionPositionProperty && (defined(referenceFrame) && property.referenceFrame === referenceFrame)) {
                //If we create a collection, or it already existed, use it.
                property.intervals.addInterval(combinedInterval);
            } else if (property instanceof CompositePositionProperty) {
                //If the collection was already a CompositePositionProperty, use it.
                combinedInterval.data = isReference ? combinedInterval.data : new ConstantPositionProperty(combinedInterval.data, referenceFrame);
                property.intervals.addInterval(combinedInterval);
            } else {
                //Otherwise, create a CompositePositionProperty but preserve the existing data.

                //Put the old property in an infinite interval.
                interval = Iso8601.MAXIMUM_INTERVAL.clone();
                interval.data = property;

                //Create the composite.
                property = new CompositePositionProperty(property.referenceFrame);
                object[propertyName] = property;

                //add the old property interval
                property.intervals.addInterval(interval);

                //Change the new data to a ConstantPositionProperty and add it.
                combinedInterval.data = isReference ? combinedInterval.data : new ConstantPositionProperty(combinedInterval.data, referenceFrame);
                property.intervals.addInterval(combinedInterval);
            }

            return;
        }

        //isSampled && hasInterval
        if (!defined(property)) {
            property = new CompositePositionProperty(referenceFrame);
            object[propertyName] = property;
        } else if (!(property instanceof CompositePositionProperty)) {
            //create a CompositeProperty but preserve the existing data.
            //Put the old property in an infinite interval.
            interval = Iso8601.MAXIMUM_INTERVAL.clone();
            interval.data = property;

            //Create the composite.
            property = new CompositePositionProperty(property.referenceFrame);
            object[propertyName] = property;

            //add the old property interval
            property.intervals.addInterval(interval);
        }

        //Check if the interval already exists in the composite
        var intervals = property.intervals;
        interval = intervals.findInterval(combinedInterval);
        if (!defined(interval) || !(interval.data instanceof SampledPositionProperty) || (defined(referenceFrame) && interval.data.referenceFrame !== referenceFrame)) {
            //If not, create a SampledPositionProperty for it.
            interval = combinedInterval.clone();
            interval.data = new SampledPositionProperty(referenceFrame, numberOfDerivatives);
            intervals.addInterval(interval);
        }
        interval.data.addSamplesPackedArray(unwrappedInterval, epoch);
        updateInterpolationSettings(packetData, interval.data);
    }

    function processPositionPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection) {
        if (!defined(packetData)) {
            return;
        }

        if (isArray(packetData)) {
            for (var i = 0, len = packetData.length; i < len; i++) {
                processPositionProperty(object, propertyName, packetData[i], interval, sourceUri, entityCollection);
            }
        } else {
            processPositionProperty(object, propertyName, packetData, interval, sourceUri, entityCollection);
        }
    }

    function processMaterialProperty(object, propertyName, packetData, constrainedInterval, sourceUri, entityCollection) {
        var combinedInterval;
        var packetInterval = packetData.interval;
        if (defined(packetInterval)) {
            iso8601Scratch.iso8601 = packetInterval;
            combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
            if (defined(constrainedInterval)) {
                combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
            }
        } else if (defined(constrainedInterval)) {
            combinedInterval = constrainedInterval;
        }

        var property = object[propertyName];
        var existingMaterial;
        var existingInterval;

        if (defined(combinedInterval)) {
            if (!(property instanceof CompositeMaterialProperty)) {
                property = new CompositeMaterialProperty();
                object[propertyName] = property;
            }
            //See if we already have data at that interval.
            var thisIntervals = property.intervals;
            existingInterval = thisIntervals.findInterval({
                start : combinedInterval.start,
                stop : combinedInterval.stop
            });
            if (defined(existingInterval)) {
                //We have an interval, but we need to make sure the
                //new data is the same type of material as the old data.
                existingMaterial = existingInterval.data;
            } else {
                //If not, create it.
                existingInterval = combinedInterval.clone();
                thisIntervals.addInterval(existingInterval);
            }
        } else {
            existingMaterial = property;
        }

        var materialData;
        if (defined(packetData.solidColor)) {
            if (!(existingMaterial instanceof ColorMaterialProperty)) {
                existingMaterial = new ColorMaterialProperty();
            }
            materialData = packetData.solidColor;
            processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection);
        } else if (defined(packetData.grid)) {
            if (!(existingMaterial instanceof GridMaterialProperty)) {
                existingMaterial = new GridMaterialProperty();
            }
            materialData = packetData.grid;
            processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
            processPacketData(Number, existingMaterial, 'cellAlpha', materialData.cellAlpha, undefined, sourceUri, entityCollection);
            processPacketData(Cartesian2, existingMaterial, 'lineCount', materialData.lineCount, undefined, sourceUri, entityCollection);
            processPacketData(Cartesian2, existingMaterial, 'lineThickness', materialData.lineThickness, undefined, sourceUri, entityCollection);
            processPacketData(Cartesian2, existingMaterial, 'lineOffset', materialData.lineOffset, undefined, sourceUri, entityCollection);
        } else if (defined(packetData.image)) {
            if (!(existingMaterial instanceof ImageMaterialProperty)) {
                existingMaterial = new ImageMaterialProperty();
            }
            materialData = packetData.image;
            processPacketData(Image, existingMaterial, 'image', materialData.image, undefined, sourceUri, entityCollection);
            processPacketData(Cartesian2, existingMaterial, 'repeat', materialData.repeat, undefined, sourceUri, entityCollection);
            processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
            processPacketData(Boolean, existingMaterial, 'transparent', materialData.transparent, undefined, sourceUri, entityCollection);
        } else if (defined(packetData.stripe)) {
            if (!(existingMaterial instanceof StripeMaterialProperty)) {
                existingMaterial = new StripeMaterialProperty();
            }
            materialData = packetData.stripe;
            processPacketData(StripeOrientation, existingMaterial, 'orientation', materialData.orientation, undefined, sourceUri, entityCollection);
            processPacketData(Color, existingMaterial, 'evenColor', materialData.evenColor, undefined, sourceUri, entityCollection);
            processPacketData(Color, existingMaterial, 'oddColor', materialData.oddColor, undefined, sourceUri, entityCollection);
            processPacketData(Number, existingMaterial, 'offset', materialData.offset, undefined, sourceUri, entityCollection);
            processPacketData(Number, existingMaterial, 'repeat', materialData.repeat, undefined, sourceUri, entityCollection);
        } else if (defined(packetData.polylineOutline)) {
            if (!(existingMaterial instanceof PolylineOutlineMaterialProperty)) {
                existingMaterial = new PolylineOutlineMaterialProperty();
            }
            materialData = packetData.polylineOutline;
            processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
            processPacketData(Color, existingMaterial, 'outlineColor', materialData.outlineColor, undefined, sourceUri, entityCollection);
            processPacketData(Number, existingMaterial, 'outlineWidth', materialData.outlineWidth, undefined, sourceUri, entityCollection);
        } else if (defined(packetData.polylineGlow)) {
            if (!(existingMaterial instanceof PolylineGlowMaterialProperty)) {
                existingMaterial = new PolylineGlowMaterialProperty();
            }
            materialData = packetData.polylineGlow;
            processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, sourceUri, entityCollection);
            processPacketData(Number, existingMaterial, 'glowPower', materialData.glowPower, undefined, sourceUri, entityCollection);
        } else if (defined(packetData.polylineArrow)) {
            if (!(existingMaterial instanceof PolylineArrowMaterialProperty)) {
                existingMaterial = new PolylineArrowMaterialProperty();
            }
            materialData = packetData.polylineArrow;
            processPacketData(Color, existingMaterial, 'color', materialData.color, undefined, undefined, entityCollection);
        }

        if (defined(existingInterval)) {
            existingInterval.data = existingMaterial;
        } else {
            object[propertyName] = existingMaterial;
        }
    }

    function processMaterialPacketData(object, propertyName, packetData, interval, sourceUri, entityCollection) {
        if (!defined(packetData)) {
            return;
        }

        if (isArray(packetData)) {
            for (var i = 0, len = packetData.length; i < len; i++) {
                processMaterialProperty(object, propertyName, packetData[i], interval, sourceUri, entityCollection);
            }
        } else {
            processMaterialProperty(object, propertyName, packetData, interval, sourceUri, entityCollection);
        }
    }

    function processName(entity, packet, entityCollection, sourceUri) {
        entity.name = defaultValue(packet.name, entity.name);
    }

    function processDescription(entity, packet, entityCollection, sourceUri) {
        var descriptionData = packet.description;
        if (defined(descriptionData)) {
            processPacketData(String, entity, 'description', descriptionData, undefined, sourceUri, entityCollection);
        }
    }

    function processPosition(entity, packet, entityCollection, sourceUri) {
        var positionData = packet.position;
        if (defined(positionData)) {
            processPositionPacketData(entity, 'position', positionData, undefined, sourceUri, entityCollection);
        }
    }

    function processViewFrom(entity, packet, entityCollection, sourceUri) {
        var viewFromData = packet.viewFrom;
        if (defined(viewFromData)) {
            processPacketData(Cartesian3, entity, 'viewFrom', viewFromData, undefined, sourceUri, entityCollection);
        }
    }

    function processOrientation(entity, packet, entityCollection, sourceUri) {
        var orientationData = packet.orientation;
        if (defined(orientationData)) {
            processPacketData(Quaternion, entity, 'orientation', orientationData, undefined, sourceUri, entityCollection);
        }
    }

    function processArrayPacketData(object, propertyName, packetData, entityCollection) {
        var references = packetData.references;
        if (defined(references)) {
            var properties = references.map(function(reference) {
                return makeReference(entityCollection, reference);
            });

            var iso8601Interval = packetData.interval;
            if (defined(iso8601Interval)) {
                iso8601Interval = TimeInterval.fromIso8601(iso8601Interval);
                if (!(object[propertyName] instanceof CompositePositionProperty)) {
                    iso8601Interval.data = new PropertyArray(properties);
                    var property = new CompositeProperty();
                    property.intervals.addInterval(iso8601Interval);
                    object[propertyName] = property;
                }
            } else {
                object[propertyName] = new PropertyArray(properties);
            }
        } else {
            processPacketData(Array, object, propertyName, packetData, undefined, undefined, entityCollection);
        }
    }

    function processArray(object, propertyName, packetData, entityCollection) {
        if (!defined(packetData)) {
            return;
        }

        if (isArray(packetData)) {
            for (var i = 0, length = packetData.length; i < length; ++i) {
                processArrayPacketData(object, propertyName, packetData[i], entityCollection);
            }
        } else {
            processArrayPacketData(object, propertyName, packetData, entityCollection);
        }
    }

    function processPositionsPacketData(object, propertyName, positionsData, entityCollection) {
        if (defined(positionsData.references)) {
            var properties = positionsData.references.map(function(reference) {
                return makeReference(entityCollection, reference);
            });

            var iso8601Interval = positionsData.interval;
            if (defined(iso8601Interval)) {
                iso8601Interval = TimeInterval.fromIso8601(iso8601Interval);
                if (!(object[propertyName] instanceof CompositePositionProperty)) {
                    iso8601Interval.data = new PositionPropertyArray(properties);
                    var property = new CompositePositionProperty();
                    property.intervals.addInterval(iso8601Interval);
                    object[propertyName] = property;
                }
            } else {
                object[propertyName] = new PositionPropertyArray(properties);
            }
        } else {
            if (defined(positionsData.cartesian)) {
                positionsData.array = Cartesian3.unpackArray(positionsData.cartesian);
            } else if (defined(positionsData.cartographicRadians)) {
                positionsData.array = Cartesian3.fromRadiansArrayHeights(positionsData.cartographicRadians);
            } else if (defined(positionsData.cartographicDegrees)) {
                positionsData.array = Cartesian3.fromDegreesArrayHeights(positionsData.cartographicDegrees);
            }

            if (defined(positionsData.array)) {
                processPacketData(Array, object, propertyName, positionsData, undefined, undefined, entityCollection);
            }
        }
    }

    function processPositions(object, propertyName, positionsData, entityCollection) {
        if (!defined(positionsData)) {
            return;
        }

        if (isArray(positionsData)) {
            for (var i = 0, length = positionsData.length; i < length; i++) {
                processPositionsPacketData(object, propertyName, positionsData[i], entityCollection);
            }
        } else {
            processPositionsPacketData(object, propertyName, positionsData, entityCollection);
        }
    }

    function processAvailability(entity, packet, entityCollection, sourceUri) {
        var interval;
        var packetData = packet.availability;
        if (!defined(packetData)) {
            return;
        }

        var intervals;
        if (isArray(packetData)) {
            var length = packetData.length;
            for (var i = 0; i < length; i++) {
                if (!defined(intervals)) {
                    intervals = new TimeIntervalCollection();
                }
                iso8601Scratch.iso8601 = packetData[i];
                interval = TimeInterval.fromIso8601(iso8601Scratch);
                intervals.addInterval(interval);
            }
        } else {
            iso8601Scratch.iso8601 = packetData;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
            intervals = new TimeIntervalCollection();
            intervals.addInterval(interval);
        }
        entity.availability = intervals;
    }

    var iso8601Scratch = {
        iso8601 : undefined
    };

    function processAlignedAxis(billboard, packetData, interval, sourceUri, entityCollection) {
        if (!defined(packetData)) {
            return;
        }

        if (defined(packetData.velocityReference)) {
            billboard.alignedAxis = new VelocityVectorProperty(makeReference(entityCollection, packetData.velocityReference), true);
        } else {
            processPacketData(Cartesian3, billboard, 'alignedAxis', packetData, interval, sourceUri, entityCollection);
        }
    }

    function processBillboard(entity, packet, entityCollection, sourceUri) {
        var billboardData = packet.billboard;
        if (!defined(billboardData)) {
            return;
        }

        var interval;
        var intervalString = billboardData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var billboard = entity.billboard;
        if (!defined(billboard)) {
            entity.billboard = billboard = new BillboardGraphics();
        }

        processPacketData(Boolean, billboard, 'show', billboardData.show, interval, sourceUri, entityCollection);
        processPacketData(Image, billboard, 'image', billboardData.image, interval, sourceUri, entityCollection);
        processPacketData(Number, billboard, 'scale', billboardData.scale, interval, sourceUri, entityCollection);
        processPacketData(Cartesian2, billboard, 'pixelOffset', billboardData.pixelOffset, interval, sourceUri, entityCollection);
        processPacketData(Cartesian3, billboard, 'eyeOffset', billboardData.eyeOffset, interval, sourceUri, entityCollection);
        processPacketData(HorizontalOrigin, billboard, 'horizontalOrigin', billboardData.horizontalOrigin, interval, sourceUri, entityCollection);
        processPacketData(VerticalOrigin, billboard, 'verticalOrigin', billboardData.verticalOrigin, interval, sourceUri, entityCollection);
        processPacketData(HeightReference, billboard, 'heightReference', billboardData.heightReference, interval, sourceUri, entityCollection);
        processPacketData(Color, billboard, 'color', billboardData.color, interval, sourceUri, entityCollection);
        processPacketData(Rotation, billboard, 'rotation', billboardData.rotation, interval, sourceUri, entityCollection);
        processAlignedAxis(billboard, billboardData.alignedAxis, interval, sourceUri, entityCollection);
        processPacketData(Boolean, billboard, 'sizeInMeters', billboardData.sizeInMeters, interval, sourceUri, entityCollection);
        processPacketData(Number, billboard, 'width', billboardData.width, interval, sourceUri, entityCollection);
        processPacketData(Number, billboard, 'height', billboardData.height, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, billboard, 'scaleByDistance', billboardData.scaleByDistance, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, billboard, 'translucencyByDistance', billboardData.translucencyByDistance, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, billboard, 'pixelOffsetScaleByDistance', billboardData.pixelOffsetScaleByDistance, interval, sourceUri, entityCollection);
        processPacketData(BoundingRectangle, billboard, 'imageSubRegion', billboardData.imageSubRegion, interval, sourceUri, entityCollection);
    }

    function processBox(entity, packet, entityCollection, sourceUri) {
        var boxData = packet.box;
        if (!defined(boxData)) {
            return;
        }

        var interval;
        var intervalString = boxData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var box = entity.box;
        if (!defined(box)) {
            entity.box = box = new BoxGraphics();
        }

        processPacketData(Boolean, box, 'show', boxData.show, interval, sourceUri, entityCollection);
        processPacketData(Cartesian3, box, 'dimensions', boxData.dimensions, interval, sourceUri, entityCollection);
        processPacketData(Boolean, box, 'fill', boxData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(box, 'material', boxData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, box, 'outline', boxData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, box, 'outlineColor', boxData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, box, 'outlineWidth', boxData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, box, 'shadows', boxData.shadows, interval, sourceUri, entityCollection);
    }

    function processCorridor(entity, packet, entityCollection, sourceUri) {
        var corridorData = packet.corridor;
        if (!defined(corridorData)) {
            return;
        }

        var interval;
        var intervalString = corridorData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var corridor = entity.corridor;
        if (!defined(corridor)) {
            entity.corridor = corridor = new CorridorGraphics();
        }

        processPacketData(Boolean, corridor, 'show', corridorData.show, interval, sourceUri, entityCollection);
        processPositions(corridor, 'positions', corridorData.positions, entityCollection);
        processPacketData(Number, corridor, 'width', corridorData.width, interval, sourceUri, entityCollection);
        processPacketData(Number, corridor, 'height', corridorData.height, interval, sourceUri, entityCollection);
        processPacketData(Number, corridor, 'extrudedHeight', corridorData.extrudedHeight, interval, sourceUri, entityCollection);
        processPacketData(CornerType, corridor, 'cornerType', corridorData.cornerType, interval, sourceUri, entityCollection);
        processPacketData(Number, corridor, 'granularity', corridorData.granularity, interval, sourceUri, entityCollection);
        processPacketData(Boolean, corridor, 'fill', corridorData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(corridor, 'material', corridorData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, corridor, 'outline', corridorData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, corridor, 'outlineColor', corridorData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, corridor, 'outlineWidth', corridorData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, corridor, 'shadows', corridorData.shadows, interval, sourceUri, entityCollection);
    }

    function processCylinder(entity, packet, entityCollection, sourceUri) {
        var cylinderData = packet.cylinder;
        if (!defined(cylinderData)) {
            return;
        }

        var interval;
        var intervalString = cylinderData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var cylinder = entity.cylinder;
        if (!defined(cylinder)) {
            entity.cylinder = cylinder = new CylinderGraphics();
        }

        processPacketData(Boolean, cylinder, 'show', cylinderData.show, interval, sourceUri, entityCollection);
        processPacketData(Number, cylinder, 'length', cylinderData.length, interval, sourceUri, entityCollection);
        processPacketData(Number, cylinder, 'topRadius', cylinderData.topRadius, interval, sourceUri, entityCollection);
        processPacketData(Number, cylinder, 'bottomRadius', cylinderData.bottomRadius, interval, sourceUri, entityCollection);
        processPacketData(Boolean, cylinder, 'fill', cylinderData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(cylinder, 'material', cylinderData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, cylinder, 'outline', cylinderData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, cylinder, 'outlineColor', cylinderData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, cylinder, 'outlineWidth', cylinderData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(Number, cylinder, 'numberOfVerticalLines', cylinderData.numberOfVerticalLines, interval, sourceUri, entityCollection);
        processPacketData(Number, cylinder, 'slices', cylinderData.slices, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, cylinder, 'shadows', cylinderData.shadows, interval, sourceUri, entityCollection);
    }

    function processDocument(packet, dataSource) {
        var version = packet.version;
        if (defined(version)) {
            if (typeof version === 'string') {
                var tokens = version.split('.');
                if (tokens.length === 2) {
                    if (tokens[0] !== '1') {
                        throw new RuntimeError('Cesium only supports CZML version 1.');
                    }
                    dataSource._version = version;
                }
            }
        }

        if (!defined(dataSource._version)) {
            throw new RuntimeError('CZML version information invalid.  It is expected to be a property on the document object in the <Major>.<Minor> version format.');
        }

        var documentPacket = dataSource._documentPacket;

        if (defined(packet.name)) {
            documentPacket.name = packet.name;
        }

        var clockPacket = packet.clock;
        if (defined(clockPacket)) {
            var clock = documentPacket.clock;
            if (!defined(clock)) {
                documentPacket.clock = {
                    interval : clockPacket.interval,
                    currentTime : clockPacket.currentTime,
                    range : clockPacket.range,
                    step : clockPacket.step,
                    multiplier : clockPacket.multiplier
                };
            } else {
                clock.interval = defaultValue(clockPacket.interval, clock.interval);
                clock.currentTime = defaultValue(clockPacket.currentTime, clock.currentTime);
                clock.range = defaultValue(clockPacket.range, clock.range);
                clock.step = defaultValue(clockPacket.step, clock.step);
                clock.multiplier = defaultValue(clockPacket.multiplier, clock.multiplier);
            }
        }
    }

    function processEllipse(entity, packet, entityCollection, sourceUri) {
        var ellipseData = packet.ellipse;
        if (!defined(ellipseData)) {
            return;
        }

        var interval;
        var intervalString = ellipseData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var ellipse = entity.ellipse;
        if (!defined(ellipse)) {
            entity.ellipse = ellipse = new EllipseGraphics();
        }

        processPacketData(Boolean, ellipse, 'show', ellipseData.show, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'semiMajorAxis', ellipseData.semiMajorAxis, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'semiMinorAxis', ellipseData.semiMinorAxis, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'height', ellipseData.height, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'extrudedHeight', ellipseData.extrudedHeight, interval, sourceUri, entityCollection);
        processPacketData(Rotation, ellipse, 'rotation', ellipseData.rotation, interval, sourceUri, entityCollection);
        processPacketData(Rotation, ellipse, 'stRotation', ellipseData.stRotation, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'granularity', ellipseData.granularity, interval, sourceUri, entityCollection);
        processPacketData(Boolean, ellipse, 'fill', ellipseData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(ellipse, 'material', ellipseData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, ellipse, 'outline', ellipseData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, ellipse, 'outlineColor', ellipseData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'outlineWidth', ellipseData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipse, 'numberOfVerticalLines', ellipseData.numberOfVerticalLines, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, ellipse, 'shadows', ellipseData.shadows, interval, sourceUri, entityCollection);
    }

    function processEllipsoid(entity, packet, entityCollection, sourceUri) {
        var ellipsoidData = packet.ellipsoid;
        if (!defined(ellipsoidData)) {
            return;
        }

        var interval;
        var intervalString = ellipsoidData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var ellipsoid = entity.ellipsoid;
        if (!defined(ellipsoid)) {
            entity.ellipsoid = ellipsoid = new EllipsoidGraphics();
        }

        processPacketData(Boolean, ellipsoid, 'show', ellipsoidData.show, interval, sourceUri, entityCollection);
        processPacketData(Cartesian3, ellipsoid, 'radii', ellipsoidData.radii, interval, sourceUri, entityCollection);
        processPacketData(Boolean, ellipsoid, 'fill', ellipsoidData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(ellipsoid, 'material', ellipsoidData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, ellipsoid, 'outline', ellipsoidData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, ellipsoid, 'outlineColor', ellipsoidData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipsoid, 'outlineWidth', ellipsoidData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipsoid, 'stackPartitions', ellipsoidData.stackPartitions, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipsoid, 'slicePartitions', ellipsoidData.slicePartitions, interval, sourceUri, entityCollection);
        processPacketData(Number, ellipsoid, 'subdivisions', ellipsoidData.subdivisions, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, ellipsoid, 'shadows', ellipsoidData.shadows, interval, sourceUri, entityCollection);
    }

    function processLabel(entity, packet, entityCollection, sourceUri) {
        var labelData = packet.label;
        if (!defined(labelData)) {
            return;
        }

        var interval;
        var intervalString = labelData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var label = entity.label;
        if (!defined(label)) {
            entity.label = label = new LabelGraphics();
        }

        processPacketData(Boolean, label, 'show', labelData.show, interval, sourceUri, entityCollection);
        processPacketData(String, label, 'text', labelData.text, interval, sourceUri, entityCollection);
        processPacketData(String, label, 'font', labelData.font, interval, sourceUri, entityCollection);
        processPacketData(LabelStyle, label, 'style', labelData.style, interval, sourceUri, entityCollection);
        processPacketData(Number, label, 'scale', labelData.scale, interval, sourceUri, entityCollection);
        processPacketData(Boolean, label, 'showBackground', labelData.showBackground, interval, sourceUri, entityCollection);
        processPacketData(Color, label, 'backgroundColor', labelData.backgroundColor, interval, sourceUri, entityCollection);
        processPacketData(Cartesian2, label, 'backgroundPadding', labelData.backgroundPadding, interval, sourceUri, entityCollection);
        processPacketData(Cartesian2, label, 'pixelOffset', labelData.pixelOffset, interval, sourceUri, entityCollection);
        processPacketData(Cartesian3, label, 'eyeOffset', labelData.eyeOffset, interval, sourceUri, entityCollection);
        processPacketData(HorizontalOrigin, label, 'horizontalOrigin', labelData.horizontalOrigin, interval, sourceUri, entityCollection);
        processPacketData(VerticalOrigin, label, 'verticalOrigin', labelData.verticalOrigin, interval, sourceUri, entityCollection);
        processPacketData(HeightReference, label, 'heightReference', labelData.heightReference, interval, sourceUri, entityCollection);
        processPacketData(Color, label, 'fillColor', labelData.fillColor, interval, sourceUri, entityCollection);
        processPacketData(Color, label, 'outlineColor', labelData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, label, 'outlineWidth', labelData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, label, 'translucencyByDistance', labelData.translucencyByDistance, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, label, 'pixelOffsetScaleByDistance', labelData.pixelOffsetScaleByDistance, interval, sourceUri, entityCollection);
    }

    function processModel(entity, packet, entityCollection, sourceUri) {
        var modelData = packet.model;
        if (!defined(modelData)) {
            return;
        }

        var interval;
        var intervalString = modelData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var model = entity.model;
        if (!defined(model)) {
            entity.model = model = new ModelGraphics();
        }

        processPacketData(Boolean, model, 'show', modelData.show, interval, sourceUri, entityCollection);
        processPacketData(Uri, model, 'uri', modelData.gltf, interval, sourceUri, entityCollection);
        processPacketData(Number, model, 'scale', modelData.scale, interval, sourceUri, entityCollection);
        processPacketData(Number, model, 'minimumPixelSize', modelData.minimumPixelSize, interval, sourceUri, entityCollection);
        processPacketData(Number, model, 'maximumScale', modelData.maximumScale, interval, sourceUri, entityCollection);
        processPacketData(Boolean, model, 'incrementallyLoadTextures', modelData.incrementallyLoadTextures, interval, sourceUri, entityCollection);
        processPacketData(Boolean, model, 'runAnimations', modelData.runAnimations, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, model, 'shadows', modelData.shadows, interval, sourceUri, entityCollection);
        processPacketData(HeightReference, model, 'heightReference', modelData.heightReference, interval, sourceUri, entityCollection);
        processPacketData(Color, model, 'silhouetteColor', modelData.silhouetteColor, interval, sourceUri, entityCollection);
        processPacketData(Number, model, 'silhouetteSize', modelData.silhouetteSize, interval, sourceUri, entityCollection);
        processPacketData(Color, model, 'color', modelData.color, interval, sourceUri, entityCollection);
        processPacketData(ColorBlendMode, model, 'colorBlendMode', modelData.colorBlendMode, interval, sourceUri, entityCollection);
        processPacketData(Number, model, 'colorBlendAmount', modelData.colorBlendAmount, interval, sourceUri, entityCollection);

        var nodeTransformationsData = modelData.nodeTransformations;
        if (defined(nodeTransformationsData)) {
            if (isArray(nodeTransformationsData)) {
                for (var i = 0, len = nodeTransformationsData.length; i < len; i++) {
                    processNodeTransformations(model, nodeTransformationsData[i], interval, sourceUri, entityCollection);
                }
            } else {
                processNodeTransformations(model, nodeTransformationsData, interval, sourceUri, entityCollection);
            }
        }
    }

    function processNodeTransformations(model, nodeTransformationsData, constrainedInterval, sourceUri, entityCollection) {
        var combinedInterval;
        var packetInterval = nodeTransformationsData.interval;
        if (defined(packetInterval)) {
            iso8601Scratch.iso8601 = packetInterval;
            combinedInterval = TimeInterval.fromIso8601(iso8601Scratch);
            if (defined(constrainedInterval)) {
                combinedInterval = TimeInterval.intersect(combinedInterval, constrainedInterval, scratchTimeInterval);
            }
        } else if (defined(constrainedInterval)) {
            combinedInterval = constrainedInterval;
        }

        var nodeTransformations = model.nodeTransformations;
        var nodeNames = Object.keys(nodeTransformationsData);
        for (var i = 0, len = nodeNames.length; i < len; ++i) {
            var nodeName = nodeNames[i];

            if (nodeName === 'interval') {
                continue;
            }

            var nodeTransformationData = nodeTransformationsData[nodeName];

            if (!defined(nodeTransformationData)) {
                continue;
            }

            if (!defined(nodeTransformations)) {
                model.nodeTransformations = nodeTransformations = new PropertyBag();
            }

            if (!nodeTransformations.hasProperty(nodeName)) {
                nodeTransformations.addProperty(nodeName);
            }

            var nodeTransformation = nodeTransformations[nodeName];
            if (!defined(nodeTransformation)) {
                nodeTransformations[nodeName] = nodeTransformation = new NodeTransformationProperty();
            }

            processPacketData(Cartesian3, nodeTransformation, 'translation', nodeTransformationData.translation, combinedInterval, sourceUri, entityCollection);
            processPacketData(Quaternion, nodeTransformation, 'rotation', nodeTransformationData.rotation, combinedInterval, sourceUri, entityCollection);
            processPacketData(Cartesian3, nodeTransformation, 'scale', nodeTransformationData.scale, combinedInterval, sourceUri, entityCollection);
        }
    }

    function processPath(entity, packet, entityCollection, sourceUri) {
        var pathData = packet.path;
        if (!defined(pathData)) {
            return;
        }

        var interval;
        var intervalString = pathData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var path = entity.path;
        if (!defined(path)) {
            entity.path = path = new PathGraphics();
        }

        processPacketData(Boolean, path, 'show', pathData.show, interval, sourceUri, entityCollection);
        processPacketData(Number, path, 'width', pathData.width, interval, sourceUri, entityCollection);
        processPacketData(Number, path, 'resolution', pathData.resolution, interval, sourceUri, entityCollection);
        processPacketData(Number, path, 'leadTime', pathData.leadTime, interval, sourceUri, entityCollection);
        processPacketData(Number, path, 'trailTime', pathData.trailTime, interval, sourceUri, entityCollection);
        processMaterialPacketData(path, 'material', pathData.material, interval, sourceUri, entityCollection);
    }

    function processPoint(entity, packet, entityCollection, sourceUri) {
        var pointData = packet.point;
        if (!defined(pointData)) {
            return;
        }

        var interval;
        var intervalString = pointData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var point = entity.point;
        if (!defined(point)) {
            entity.point = point = new PointGraphics();
        }

        processPacketData(Boolean, point, 'show', pointData.show, interval, sourceUri, entityCollection);
        processPacketData(Number, point, 'pixelSize', pointData.pixelSize, interval, sourceUri, entityCollection);
        processPacketData(HeightReference, point, 'heightReference', pointData.heightReference, interval, sourceUri, entityCollection);
        processPacketData(Color, point, 'color', pointData.color, interval, sourceUri, entityCollection);
        processPacketData(Color, point, 'outlineColor', pointData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, point, 'outlineWidth', pointData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, point, 'scaleByDistance', pointData.scaleByDistance, interval, sourceUri, entityCollection);
        processPacketData(NearFarScalar, point, 'translucencyByDistance', pointData.translucencyByDistance, interval, sourceUri, entityCollection);
    }

    function processPolygon(entity, packet, entityCollection, sourceUri) {
        var polygonData = packet.polygon;
        if (!defined(polygonData)) {
            return;
        }

        var interval;
        var intervalString = polygonData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var polygon = entity.polygon;
        if (!defined(polygon)) {
            entity.polygon = polygon = new PolygonGraphics();
        }

        processPacketData(Boolean, polygon, 'show', polygonData.show, interval, sourceUri, entityCollection);
        processPositions(polygon, 'hierarchy', polygonData.positions, entityCollection);
        processPacketData(Number, polygon, 'height', polygonData.height, interval, sourceUri, entityCollection);
        processPacketData(Number, polygon, 'extrudedHeight', polygonData.extrudedHeight, interval, sourceUri, entityCollection);
        processPacketData(Rotation, polygon, 'stRotation', polygonData.stRotation, interval, sourceUri, entityCollection);
        processPacketData(Number, polygon, 'granularity', polygonData.granularity, interval, sourceUri, entityCollection);
        processPacketData(Boolean, polygon, 'fill', polygonData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(polygon, 'material', polygonData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, polygon, 'outline', polygonData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, polygon, 'outlineColor', polygonData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, polygon, 'outlineWidth', polygonData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(Boolean, polygon, 'perPositionHeight', polygonData.perPositionHeight, interval, sourceUri, entityCollection);
        processPacketData(Boolean, polygon, 'closeTop', polygonData.closeTop, interval, sourceUri, entityCollection);
        processPacketData(Boolean, polygon, 'closeBottom', polygonData.closeBottom, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, polygon, 'shadows', polygonData.shadows, interval, sourceUri, entityCollection);
    }

    function processPolyline(entity, packet, entityCollection, sourceUri) {
        var polylineData = packet.polyline;
        if (!defined(polylineData)) {
            return;
        }

        var interval;
        var intervalString = polylineData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var polyline = entity.polyline;
        if (!defined(polyline)) {
            entity.polyline = polyline = new PolylineGraphics();
        }

        processPacketData(Boolean, polyline, 'show', polylineData.show, interval, sourceUri, entityCollection);
        processPositions(polyline, 'positions', polylineData.positions, entityCollection);
        processPacketData(Number, polyline, 'width', polylineData.width, interval, sourceUri, entityCollection);
        processPacketData(Number, polyline, 'granularity', polylineData.granularity, interval, sourceUri, entityCollection);
        processMaterialPacketData(polyline, 'material', polylineData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, polyline, 'followSurface', polylineData.followSurface, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, polyline, 'shadows', polylineData.shadows, interval, sourceUri, entityCollection);
    }

    function processRectangle(entity, packet, entityCollection, sourceUri) {
        var rectangleData = packet.rectangle;
        if (!defined(rectangleData)) {
            return;
        }

        var interval;
        var intervalString = rectangleData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var rectangle = entity.rectangle;
        if (!defined(rectangle)) {
            entity.rectangle = rectangle = new RectangleGraphics();
        }

        processPacketData(Boolean, rectangle, 'show', rectangleData.show, interval, sourceUri, entityCollection);
        processPacketData(Rectangle, rectangle, 'coordinates', rectangleData.coordinates, interval, sourceUri, entityCollection);
        processPacketData(Number, rectangle, 'height', rectangleData.height, interval, sourceUri, entityCollection);
        processPacketData(Number, rectangle, 'extrudedHeight', rectangleData.extrudedHeight, interval, sourceUri, entityCollection);
        processPacketData(Rotation, rectangle, 'rotation', rectangleData.rotation, interval, sourceUri, entityCollection);
        processPacketData(Rotation, rectangle, 'stRotation', rectangleData.stRotation, interval, sourceUri, entityCollection);
        processPacketData(Number, rectangle, 'granularity', rectangleData.granularity, interval, sourceUri, entityCollection);
        processPacketData(Boolean, rectangle, 'fill', rectangleData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(rectangle, 'material', rectangleData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, rectangle, 'outline', rectangleData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, rectangle, 'outlineColor', rectangleData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, rectangle, 'outlineWidth', rectangleData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(Boolean, rectangle, 'closeTop', rectangleData.closeTop, interval, sourceUri, entityCollection);
        processPacketData(Boolean, rectangle, 'closeBottom', rectangleData.closeBottom, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, rectangle, 'shadows', rectangleData.shadows, interval, sourceUri, entityCollection);
    }

    function processWall(entity, packet, entityCollection, sourceUri) {
        var wallData = packet.wall;
        if (!defined(wallData)) {
            return;
        }

        var interval;
        var intervalString = wallData.interval;
        if (defined(intervalString)) {
            iso8601Scratch.iso8601 = intervalString;
            interval = TimeInterval.fromIso8601(iso8601Scratch);
        }

        var wall = entity.wall;
        if (!defined(wall)) {
            entity.wall = wall = new WallGraphics();
        }

        processPacketData(Boolean, wall, 'show', wallData.show, interval, sourceUri, entityCollection);
        processPositions(wall, 'positions', wallData.positions, entityCollection);
        processArray(wall, 'minimumHeights', wallData.minimumHeights, entityCollection);
        processArray(wall, 'maximumHeights', wallData.maximumHeights, entityCollection);
        processPacketData(Number, wall, 'granularity', wallData.granularity, interval, sourceUri, entityCollection);
        processPacketData(Boolean, wall, 'fill', wallData.fill, interval, sourceUri, entityCollection);
        processMaterialPacketData(wall, 'material', wallData.material, interval, sourceUri, entityCollection);
        processPacketData(Boolean, wall, 'outline', wallData.outline, interval, sourceUri, entityCollection);
        processPacketData(Color, wall, 'outlineColor', wallData.outlineColor, interval, sourceUri, entityCollection);
        processPacketData(Number, wall, 'outlineWidth', wallData.outlineWidth, interval, sourceUri, entityCollection);
        processPacketData(ShadowMode, wall, 'shadows', wallData.shadows, interval, sourceUri, entityCollection);
    }

    function processCzmlPacket(packet, entityCollection, updaterFunctions, sourceUri, dataSource) {
        var objectId = packet.id;
        if (!defined(objectId)) {
            objectId = createGuid();
        }

        currentId = objectId;

        if (!defined(dataSource._version) && objectId !== 'document') {
            throw new RuntimeError('The first CZML packet is required to be the document object.');
        }

        if (packet['delete'] === true) {
            entityCollection.removeById(objectId);
        } else if (objectId === 'document') {
            processDocument(packet, dataSource);
        } else {
            var entity = entityCollection.getOrCreateEntity(objectId);

            var parentId = packet.parent;
            if (defined(parentId)) {
                entity.parent = entityCollection.getOrCreateEntity(parentId);
            }

            for (var i = updaterFunctions.length - 1; i > -1; i--) {
                updaterFunctions[i](entity, packet, entityCollection, sourceUri);
            }
        }

        currentId = undefined;
    }

    function updateClock(dataSource) {
        var clock;
        var clockPacket = dataSource._documentPacket.clock;
        if (!defined(clockPacket)) {
            if (!defined(dataSource._clock)) {
                var availability = dataSource._entityCollection.computeAvailability();
                if (!availability.start.equals(Iso8601.MINIMUM_VALUE)) {
                    var startTime = availability.start;
                    var stopTime = availability.stop;
                    var totalSeconds = JulianDate.secondsDifference(stopTime, startTime);
                    var multiplier = Math.round(totalSeconds / 120.0);

                    clock = new DataSourceClock();
                    clock.startTime = JulianDate.clone(startTime);
                    clock.stopTime = JulianDate.clone(stopTime);
                    clock.clockRange = ClockRange.LOOP_STOP;
                    clock.multiplier = multiplier;
                    clock.currentTime = JulianDate.clone(startTime);
                    clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
                    dataSource._clock = clock;
                    return true;
                }
            }
            return false;
        }

        if (defined(dataSource._clock)) {
            clock = dataSource._clock.clone();
        } else {
            clock = new DataSourceClock();
            clock.startTime = Iso8601.MINIMUM_VALUE.clone();
            clock.stopTime = Iso8601.MAXIMUM_VALUE.clone();
            clock.currentTime = Iso8601.MINIMUM_VALUE.clone();
            clock.clockRange = ClockRange.LOOP_STOP;
            clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
            clock.multiplier = 1.0;
        }
        if (defined(clockPacket.interval)) {
            iso8601Scratch.iso8601 = clockPacket.interval;
            var interval = TimeInterval.fromIso8601(iso8601Scratch);
            clock.startTime = interval.start;
            clock.stopTime = interval.stop;
        }
        if (defined(clockPacket.currentTime)) {
            clock.currentTime = JulianDate.fromIso8601(clockPacket.currentTime);
        }
        if (defined(clockPacket.range)) {
            clock.clockRange = defaultValue(ClockRange[clockPacket.range], ClockRange.LOOP_STOP);
        }
        if (defined(clockPacket.step)) {
            clock.clockStep = defaultValue(ClockStep[clockPacket.step], ClockStep.SYSTEM_CLOCK_MULTIPLIER);
        }
        if (defined(clockPacket.multiplier)) {
            clock.multiplier = clockPacket.multiplier;
        }

        if (!clock.equals(dataSource._clock)) {
            dataSource._clock = clock.clone(dataSource._clock);
            return true;
        }

        return false;
    }

    function load(dataSource, czml, options, clear) {
                if (!defined(czml)) {
            throw new DeveloperError('czml is required.');
        }
        
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var promise = czml;
        var sourceUri = options.sourceUri;
        if (typeof czml === 'string') {
            promise = loadJson(czml);
            sourceUri = defaultValue(sourceUri, czml);
        }

        DataSource.setLoading(dataSource, true);

        return when(promise, function(czml) {
            return loadCzml(dataSource, czml, sourceUri, clear);
        }).otherwise(function(error) {
            DataSource.setLoading(dataSource, false);
            dataSource._error.raiseEvent(dataSource, error);
            console.log(error);
            return when.reject(error);
        });
    }

    function loadCzml(dataSource, czml, sourceUri, clear) {
        DataSource.setLoading(dataSource, true);
        var entityCollection = dataSource._entityCollection;

        if (clear) {
            dataSource._version = undefined;
            dataSource._documentPacket = new DocumentPacket();
            entityCollection.removeAll();
        }

        CzmlDataSource._processCzml(czml, entityCollection, sourceUri, undefined, dataSource);

        var raiseChangedEvent = updateClock(dataSource);

        var documentPacket = dataSource._documentPacket;
        if (defined(documentPacket.name) && dataSource._name !== documentPacket.name) {
            dataSource._name = documentPacket.name;
            raiseChangedEvent = true;
        } else if (!defined(dataSource._name) && defined(sourceUri)) {
            dataSource._name = getFilenameFromUri(sourceUri);
            raiseChangedEvent = true;
        }

        DataSource.setLoading(dataSource, false);
        if (raiseChangedEvent) {
            dataSource._changed.raiseEvent(dataSource);
        }

        return dataSource;
    }

    function DocumentPacket() {
        this.name = undefined;
        this.clock = undefined;
    }

    /**
     * A {@link DataSource} which processes {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/CZML-Guide|CZML}.
     * @alias CzmlDataSource
     * @constructor
     *
     * @param {String} [name] An optional name for the data source.  This value will be overwritten if a loaded document contains a name.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=CZML.html|Cesium Sandcastle CZML Demo}
     */
    function CzmlDataSource(name) {
        this._name = name;
        this._changed = new Event();
        this._error = new Event();
        this._isLoading = false;
        this._loading = new Event();
        this._clock = undefined;
        this._documentPacket = new DocumentPacket();
        this._version = undefined;
        this._entityCollection = new EntityCollection(this);
        this._entityCluster = new EntityCluster();
    }

    /**
     * Creates a Promise to a new instance loaded with the provided CZML data.
     *
     * @param {String|Object} czml A url or CZML object to be processed.
     * @param {Object} [options] An object with the following properties:
     * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
     * @returns {Promise.<CzmlDataSource>} A promise that resolves to the new instance once the data is processed.
     */
    CzmlDataSource.load = function(czml, options) {
        return new CzmlDataSource().load(czml, options);
    };

    defineProperties(CzmlDataSource.prototype, {
        /**
         * Gets a human-readable name for this instance.
         * @memberof CzmlDataSource.prototype
         * @type {String}
         */
        name : {
            get : function() {
                return this._name;
            }
        },
        /**
         * Gets the clock settings defined by the loaded CZML.  If no clock is explicitly
         * defined in the CZML, the combined availability of all objects is returned.  If
         * only static data exists, this value is undefined.
         * @memberof CzmlDataSource.prototype
         * @type {DataSourceClock}
         */
        clock : {
            get : function() {
                return this._clock;
            }
        },
        /**
         * Gets the collection of {@link Entity} instances.
         * @memberof CzmlDataSource.prototype
         * @type {EntityCollection}
         */
        entities : {
            get : function() {
                return this._entityCollection;
            }
        },
        /**
         * Gets a value indicating if the data source is currently loading data.
         * @memberof CzmlDataSource.prototype
         * @type {Boolean}
         */
        isLoading : {
            get : function() {
                return this._isLoading;
            }
        },
        /**
         * Gets an event that will be raised when the underlying data changes.
         * @memberof CzmlDataSource.prototype
         * @type {Event}
         */
        changedEvent : {
            get : function() {
                return this._changed;
            }
        },
        /**
         * Gets an event that will be raised if an error is encountered during processing.
         * @memberof CzmlDataSource.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._error;
            }
        },
        /**
         * Gets an event that will be raised when the data source either starts or stops loading.
         * @memberof CzmlDataSource.prototype
         * @type {Event}
         */
        loadingEvent : {
            get : function() {
                return this._loading;
            }
        },
        /**
         * Gets whether or not this data source should be displayed.
         * @memberof CzmlDataSource.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._entityCollection.show;
            },
            set : function(value) {
                this._entityCollection.show = value;
            }
        },

        /**
         * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
         *
         * @memberof CzmlDataSource.prototype
         * @type {EntityCluster}
         */
        clustering : {
            get : function() {
                return this._entityCluster;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value must be defined.');
                }
                                this._entityCluster = value;
            }
        }
    });

    /**
     * Gets the array of CZML processing functions.
     * @memberof CzmlDataSource
     * @type Array
     */
    CzmlDataSource.updaters = [
        processBillboard, //
        processBox, //
        processCorridor, //
        processCylinder, //
        processEllipse, //
        processEllipsoid, //
        processLabel, //
        processModel, //
        processName, //
        processDescription, //
        processPath, //
        processPoint, //
        processPolygon, //
        processPolyline, //
        processRectangle, //
        processPosition, //
        processViewFrom, //
        processWall, //
        processOrientation, //
        processAvailability];

    /**
     * Processes the provided url or CZML object without clearing any existing data.
     *
     * @param {String|Object} czml A url or CZML object to be processed.
     * @param {Object} [options] An object with the following properties:
     * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
     * @returns {Promise.<CzmlDataSource>} A promise that resolves to this instances once the data is processed.
     */
    CzmlDataSource.prototype.process = function(czml, options) {
        return load(this, czml, options, false);
    };

    /**
     * Loads the provided url or CZML object, replacing any existing data.
     *
     * @param {String|Object} czml A url or CZML object to be processed.
     * @param {Object} [options] An object with the following properties:
     * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
     * @returns {Promise.<CzmlDataSource>} A promise that resolves to this instances once the data is processed.
     */
    CzmlDataSource.prototype.load = function(czml, options) {
        return load(this, czml, options, true);
    };

    /**
     * A helper function used by custom CZML updater functions
     * which creates or updates a {@link Property} from a CZML packet.
     * @function
     *
     * @param {Function} type The constructor function for the property being processed.
     * @param {Object} object The object on which the property will be added or updated.
     * @param {String} propertyName The name of the property on the object.
     * @param {Object} packetData The CZML packet being processed.
     * @param {TimeInterval} interval A constraining interval for which the data is valid.
     * @param {String} sourceUri The originating uri of the data being processed.
     * @param {EntityCollection} entityCollection The collection being processsed.
     */
    CzmlDataSource.processPacketData = processPacketData;

    /**
     * A helper function used by custom CZML updater functions
     * which creates or updates a {@link PositionProperty} from a CZML packet.
     * @function
     *
     * @param {Object} object The object on which the property will be added or updated.
     * @param {String} propertyName The name of the property on the object.
     * @param {Object} packetData The CZML packet being processed.
     * @param {TimeInterval} interval A constraining interval for which the data is valid.
     * @param {String} sourceUri The originating uri of the data being processed.
     * @param {EntityCollection} entityCollection The collection being processsed.
     */
    CzmlDataSource.processPositionPacketData = processPositionPacketData;

    /**
     * A helper function used by custom CZML updater functions
     * which creates or updates a {@link MaterialProperty} from a CZML packet.
     * @function
     *
     * @param {Object} object The object on which the property will be added or updated.
     * @param {String} propertyName The name of the property on the object.
     * @param {Object} packetData The CZML packet being processed.
     * @param {TimeInterval} interval A constraining interval for which the data is valid.
     * @param {String} sourceUri The originating uri of the data being processed.
     * @param {EntityCollection} entityCollection The collection being processsed.
     */
    CzmlDataSource.processMaterialPacketData = processMaterialPacketData;

    CzmlDataSource._processCzml = function(czml, entityCollection, sourceUri, updaterFunctions, dataSource) {
        updaterFunctions = defined(updaterFunctions) ? updaterFunctions : CzmlDataSource.updaters;

        if (isArray(czml)) {
            for (var i = 0, len = czml.length; i < len; i++) {
                processCzmlPacket(czml[i], entityCollection, updaterFunctions, sourceUri, dataSource);
            }
        } else {
            processCzmlPacket(czml, entityCollection, updaterFunctions, sourceUri, dataSource);
        }
    };

    return CzmlDataSource;
});

/*global define*/
define('DataSources/DataSourceCollection',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Event',
        '../ThirdParty/when'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Event,
        when) {
    'use strict';

    /**
     * A collection of {@link DataSource} instances.
     * @alias DataSourceCollection
     * @constructor
     */
    function DataSourceCollection() {
        this._dataSources = [];
        this._dataSourceAdded = new Event();
        this._dataSourceRemoved = new Event();
    }

    defineProperties(DataSourceCollection.prototype, {
        /**
         * Gets the number of data sources in this collection.
         * @memberof DataSourceCollection.prototype
         * @type {Number}
         * @readonly
         */
        length : {
            get : function() {
                return this._dataSources.length;
            }
        },

        /**
         * An event that is raised when a data source is added to the collection.
         * Event handlers are passed the data source that was added.
         * @memberof DataSourceCollection.prototype
         * @type {Event}
         * @readonly
         */
        dataSourceAdded : {
            get : function() {
                return this._dataSourceAdded;
            }
        },

        /**
         * An event that is raised when a data source is removed from the collection.
         * Event handlers are passed the data source that was removed.
         * @memberof DataSourceCollection.prototype
         * @type {Event}
         * @readonly
         */
        dataSourceRemoved : {
            get : function() {
                return this._dataSourceRemoved;
            }
        }
    });

    /**
     * Adds a data source to the collection.
     *
     * @param {DataSource|Promise.<DataSource>} dataSource A data source or a promise to a data source to add to the collection.
     *                                        When passing a promise, the data source will not actually be added
     *                                        to the collection until the promise resolves successfully.
     * @returns {Promise.<DataSource>} A Promise that resolves once the data source has been added to the collection.
     */
    DataSourceCollection.prototype.add = function(dataSource) {
                if (!defined(dataSource)) {
            throw new DeveloperError('dataSource is required.');
        }
        
        var that = this;
        var dataSources = this._dataSources;
        return when(dataSource, function(value) {
            //Only add the data source if removeAll has not been called
            //Since it was added.
            if (dataSources === that._dataSources) {
                that._dataSources.push(value);
                that._dataSourceAdded.raiseEvent(that, value);
            }
            return value;
        });
    };

    /**
     * Removes a data source from this collection, if present.
     *
     * @param {DataSource} dataSource The data source to remove.
     * @param {Boolean} [destroy=false] Whether to destroy the data source in addition to removing it.
     * @returns {Boolean} true if the data source was in the collection and was removed,
     *                    false if the data source was not in the collection.
     */
    DataSourceCollection.prototype.remove = function(dataSource, destroy) {
        destroy = defaultValue(destroy, false);

        var index = this._dataSources.indexOf(dataSource);
        if (index !== -1) {
            this._dataSources.splice(index, 1);
            this._dataSourceRemoved.raiseEvent(this, dataSource);

            if (destroy && typeof dataSource.destroy === 'function') {
                dataSource.destroy();
            }

            return true;
        }

        return false;
    };

    /**
     * Removes all data sources from this collection.
     *
     * @param {Boolean} [destroy=false] whether to destroy the data sources in addition to removing them.
     */
    DataSourceCollection.prototype.removeAll = function(destroy) {
        destroy = defaultValue(destroy, false);

        var dataSources = this._dataSources;
        for (var i = 0, len = dataSources.length; i < len; ++i) {
            var dataSource = dataSources[i];
            this._dataSourceRemoved.raiseEvent(this, dataSource);

            if (destroy && typeof dataSource.destroy === 'function') {
                dataSource.destroy();
            }
        }
        this._dataSources = [];
    };

    /**
     * Checks to see if the collection contains a given data source.
     *
     * @param {DataSource} dataSource The data source to check for.
     * @returns {Boolean} true if the collection contains the data source, false otherwise.
     */
    DataSourceCollection.prototype.contains = function(dataSource) {
        return this.indexOf(dataSource) !== -1;
    };

    /**
     * Determines the index of a given data source in the collection.
     *
     * @param {DataSource} dataSource The data source to find the index of.
     * @returns {Number} The index of the data source in the collection, or -1 if the data source does not exist in the collection.
     */
    DataSourceCollection.prototype.indexOf = function(dataSource) {
        return this._dataSources.indexOf(dataSource);
    };

    /**
     * Gets a data source by index from the collection.
     *
     * @param {Number} index the index to retrieve.
     * @returns {DataSource} The data source at the specified index.
     */
    DataSourceCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._dataSources[index];
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} true if this object was destroyed; otherwise, false.
     *
     * @see DataSourceCollection#destroy
     */
    DataSourceCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the resources held by all data sources in this collection.  Explicitly destroying this
     * object allows for deterministic release of WebGL resources, instead of relying on the garbage
     * collector. Once this object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * dataSourceCollection = dataSourceCollection && dataSourceCollection.destroy();
     * 
     * @see DataSourceCollection#isDestroyed
     */
    DataSourceCollection.prototype.destroy = function() {
        this.removeAll(true);
        return destroyObject(this);
    };

    return DataSourceCollection;
});

/*global define*/
define('DataSources/EllipseGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/EllipseGeometry',
        '../Core/EllipseOutlineGeometry',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/oneTimeWarning',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/GroundPrimitive',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        EllipseGeometry,
        EllipseOutlineGeometry,
        Event,
        GeometryInstance,
        Iso8601,
        oneTimeWarning,
        ShowGeometryInstanceAttribute,
        GroundPrimitive,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.center = undefined;
        this.semiMajorAxis = undefined;
        this.semiMinorAxis = undefined;
        this.rotation = undefined;
        this.height = undefined;
        this.extrudedHeight = undefined;
        this.granularity = undefined;
        this.stRotation = undefined;
        this.numberOfVerticalLines = undefined;
    }

    /**
     * A {@link GeometryUpdater} for ellipses.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias EllipseGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function EllipseGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(EllipseGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._isClosed = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._onTerrain = false;
        this._options = new GeometryOptions(entity);

        this._onEntityPropertyChanged(entity, 'ellipse', entity.ellipse, undefined);
    }

    defineProperties(EllipseGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof EllipseGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof EllipseGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(EllipseGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if outline visibility varies with simulation time.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            get : function() {
                return this._isClosed;
            }
        },
        /**
         * Gets a value indicating if the geometry should be drawn on terrain.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        onTerrain : {
            get : function() {
                return this._onTerrain;
            }
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof EllipseGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    EllipseGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    EllipseGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    EllipseGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new EllipseGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    EllipseGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new EllipseOutlineGeometry(this._options),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    EllipseGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    EllipseGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    EllipseGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'ellipse')) {
            return;
        }

        var ellipse = this._entity.ellipse;

        if (!defined(ellipse)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = ellipse.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = ellipse.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var position = this._entity.position;
        var semiMajorAxis = ellipse.semiMajorAxis;
        var semiMinorAxis = ellipse.semiMinorAxis;

        var show = ellipse.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(position) || !defined(semiMajorAxis) || !defined(semiMinorAxis))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(ellipse.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(ellipse.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(ellipse.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(ellipse.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(ellipse.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var rotation = ellipse.rotation;
        var height = ellipse.height;
        var extrudedHeight = ellipse.extrudedHeight;
        var granularity = ellipse.granularity;
        var stRotation = ellipse.stRotation;
        var outlineWidth = ellipse.outlineWidth;
        var numberOfVerticalLines = ellipse.numberOfVerticalLines;
        var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) &&
                        isColorMaterial && GroundPrimitive.isSupported(this._scene);

        if (outlineEnabled && onTerrain) {
            oneTimeWarning(oneTimeWarning.geometryOutlines);
            outlineEnabled = false;
        }

        this._fillEnabled = fillEnabled;
        this._onTerrain = onTerrain;
        this._isClosed = defined(extrudedHeight) || onTerrain;
        this._outlineEnabled = outlineEnabled;

        if (!position.isConstant || //
            !semiMajorAxis.isConstant || //
            !semiMinorAxis.isConstant || //
            !Property.isConstant(rotation) || //
            !Property.isConstant(height) || //
            !Property.isConstant(extrudedHeight) || //
            !Property.isConstant(granularity) || //
            !Property.isConstant(stRotation) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(numberOfVerticalLines) || //
            (onTerrain && !Property.isConstant(material))) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.center = position.getValue(Iso8601.MINIMUM_VALUE, options.center);
            options.semiMajorAxis = semiMajorAxis.getValue(Iso8601.MINIMUM_VALUE, options.semiMajorAxis);
            options.semiMinorAxis = semiMinorAxis.getValue(Iso8601.MINIMUM_VALUE, options.semiMinorAxis);
            options.rotation = defined(rotation) ? rotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.stRotation = defined(stRotation) ? stRotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.numberOfVerticalLines = defined(numberOfVerticalLines) ? numberOfVerticalLines.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @param {PrimitiveCollection} groundPrimitives The ground primitives collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    EllipseGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
        this._primitives = primitives;
        this._groundPrimitives = groundPrimitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var geometryUpdater = this._geometryUpdater;
        var onTerrain = geometryUpdater._onTerrain;

        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
            primitives.removeAndDestroy(this._outlinePrimitive);
            this._outlinePrimitive = undefined;
        }
        this._primitive = undefined;


        var entity = geometryUpdater._entity;
        var ellipse = entity.ellipse;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipse.show, time, true)) {
            return;
        }

        var options = this._options;
        var center = Property.getValueOrUndefined(entity.position, time, options.center);
        var semiMajorAxis = Property.getValueOrUndefined(ellipse.semiMajorAxis, time);
        var semiMinorAxis = Property.getValueOrUndefined(ellipse.semiMinorAxis, time);
        if (!defined(center) || !defined(semiMajorAxis) || !defined(semiMinorAxis)) {
            return;
        }

        options.center = center;
        options.semiMajorAxis = semiMajorAxis;
        options.semiMinorAxis = semiMinorAxis;
        options.rotation = Property.getValueOrUndefined(ellipse.rotation, time);
        options.height = Property.getValueOrUndefined(ellipse.height, time);
        options.extrudedHeight = Property.getValueOrUndefined(ellipse.extrudedHeight, time);
        options.granularity = Property.getValueOrUndefined(ellipse.granularity, time);
        options.stRotation = Property.getValueOrUndefined(ellipse.stRotation, time);
        options.numberOfVerticalLines = Property.getValueOrUndefined(ellipse.numberOfVerticalLines, time);

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
        var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);

        if (Property.getValueOrDefault(ellipse.fill, time, true)) {
            var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
            var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
            this._material = material;

            if (onTerrain) {
                var currentColor = Color.WHITE;
                if (defined(fillMaterialProperty.color)) {
                    currentColor = fillMaterialProperty.color.getValue(time);
                }

                this._primitive = groundPrimitives.add(new GroundPrimitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new EllipseGeometry(options),
                        attributes: {
                            color: ColorGeometryInstanceAttribute.fromColor(currentColor),
                            distanceDisplayCondition : distanceDisplayConditionAttribute
                        }
                    }),
                    asynchronous : false,
                    shadows : shadows
                }));
            } else {
                var appearance = new MaterialAppearance({
                    material : material,
                    translucent : material.isTranslucent(),
                    closed : defined(options.extrudedHeight)
                });
                options.vertexFormat = appearance.vertexFormat;

                this._primitive = primitives.add(new Primitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new EllipseGeometry(options)
                    }),
                    attributes : {
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    },
                    appearance : appearance,
                    asynchronous : false,
                    shadows : shadows
                }));
            }
        }

        if (!onTerrain && Property.getValueOrDefault(ellipse.outline, time, false)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(ellipse.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(ellipse.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new EllipseOutlineGeometry(options),
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (this._geometryUpdater._onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
        }
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return EllipseGeometryUpdater;
});

/*global define*/
define('DataSources/EllipsoidGeometryUpdater',[
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/EllipsoidGeometry',
        '../Core/EllipsoidOutlineGeometry',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/Matrix4',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/SceneMode',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Cartesian3,
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        EllipsoidGeometry,
        EllipsoidOutlineGeometry,
        Event,
        GeometryInstance,
        Iso8601,
        Matrix4,
        ShowGeometryInstanceAttribute,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        SceneMode,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());

    var radiiScratch = new Cartesian3();
    var scratchColor = new Color();
    var unitSphere = new Cartesian3(1, 1, 1);

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.radii = undefined;
        this.stackPartitions = undefined;
        this.slicePartitions = undefined;
        this.subdivisions = undefined;
    }

    /**
     * A {@link GeometryUpdater} for ellipsoids.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias EllipsoidGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function EllipsoidGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._scene = scene;
        this._entity = entity;
        this._entitySubscription = entity.definitionChanged.addEventListener(EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'ellipsoid', entity.ellipsoid, undefined);
    }

    defineProperties(EllipsoidGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof EllipsoidGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof EllipsoidGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(EllipsoidGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if outline visibility varies with simulation time.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof EllipsoidGeometryUpdater.prototype
         * 
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            value : true
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof EllipsoidGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    EllipsoidGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    EllipsoidGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    EllipsoidGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new EllipsoidGeometry(this._options),
            modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    EllipsoidGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new EllipsoidOutlineGeometry(this._options),
            modelMatrix : entity._getModelMatrix(Iso8601.MINIMUM_VALUE),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    EllipsoidGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    EllipsoidGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    EllipsoidGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'position' || propertyName === 'orientation' || propertyName === 'ellipsoid')) {
            return;
        }

        var ellipsoid = entity.ellipsoid;

        if (!defined(ellipsoid)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = ellipsoid.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = ellipsoid.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var position = entity.position;
        var radii = ellipsoid.radii;

        var show = ellipsoid.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(position) || !defined(radii))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(ellipsoid.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(ellipsoid.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(ellipsoid.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(ellipsoid.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(ellipsoid.distanceDisplayCondition, defaultDistanceDisplayCondition);
        
        this._fillEnabled = fillEnabled;
        this._outlineEnabled = outlineEnabled;

        var stackPartitions = ellipsoid.stackPartitions;
        var slicePartitions = ellipsoid.slicePartitions;
        var outlineWidth = ellipsoid.outlineWidth;
        var subdivisions = ellipsoid.subdivisions;

        if (!position.isConstant || //
            !Property.isConstant(entity.orientation) || //
            !radii.isConstant || //
            !Property.isConstant(stackPartitions) || //
            !Property.isConstant(slicePartitions) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(subdivisions)) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.radii = radii.getValue(Iso8601.MINIMUM_VALUE, options.radii);
            options.stackPartitions = defined(stackPartitions) ? stackPartitions.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.slicePartitions = defined(slicePartitions) ? slicePartitions.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.subdivisions = defined(subdivisions) ? subdivisions.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    EllipsoidGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, geometryUpdater) {
        this._entity = geometryUpdater._entity;
        this._scene = geometryUpdater._scene;
        this._primitives = primitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
        this._modelMatrix = new Matrix4();
        this._material = undefined;
        this._attributes = undefined;
        this._outlineAttributes = undefined;
        this._lastSceneMode = undefined;
        this._lastShow = undefined;
        this._lastOutlineShow = undefined;
        this._lastOutlineWidth = undefined;
        this._lastOutlineColor = undefined;
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var entity = this._entity;
        var ellipsoid = entity.ellipsoid;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(ellipsoid.show, time, true)) {
            if (defined(this._primitive)) {
                this._primitive.show = false;
            }

            if (defined(this._outlinePrimitive)) {
                this._outlinePrimitive.show = false;
            }
            return;
        }

        var radii = Property.getValueOrUndefined(ellipsoid.radii, time, radiiScratch);
        var modelMatrix = entity._getModelMatrix(time, this._modelMatrix);
        if (!defined(modelMatrix) || !defined(radii)) {
            if (defined(this._primitive)) {
                this._primitive.show = false;
            }

            if (defined(this._outlinePrimitive)) {
                this._outlinePrimitive.show = false;
            }
            return;
        }

        //Compute attributes and material.
        var appearance;
        var showFill = Property.getValueOrDefault(ellipsoid.fill, time, true);
        var showOutline = Property.getValueOrDefault(ellipsoid.outline, time, false);
        var outlineColor = Property.getValueOrClonedDefault(ellipsoid.outlineColor, time, Color.BLACK, scratchColor);
        var material = MaterialProperty.getValue(time, defaultValue(ellipsoid.material, defaultMaterial), this._material);
        this._material = material;

        // Check properties that could trigger a primitive rebuild.
        var stackPartitions = Property.getValueOrUndefined(ellipsoid.stackPartitions, time);
        var slicePartitions = Property.getValueOrUndefined(ellipsoid.slicePartitions, time);
        var subdivisions = Property.getValueOrUndefined(ellipsoid.subdivisions, time);
        var outlineWidth = Property.getValueOrDefault(ellipsoid.outlineWidth, time, 1.0);

        //In 3D we use a fast path by modifying Primitive.modelMatrix instead of regenerating the primitive every frame.
        var sceneMode = this._scene.mode;
        var in3D = sceneMode === SceneMode.SCENE3D;

        var options = this._options;
        
        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        var distanceDisplayConditionProperty = this._geometryUpdater.distanceDisplayConditionProperty;
        var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        
        //We only rebuild the primitive if something other than the radii has changed
        //For the radii, we use unit sphere and then deform it with a scale matrix.
        var rebuildPrimitives = !in3D || this._lastSceneMode !== sceneMode || !defined(this._primitive) || //
                                options.stackPartitions !== stackPartitions || options.slicePartitions !== slicePartitions || //
                                options.subdivisions !== subdivisions || this._lastOutlineWidth !== outlineWidth;

        if (rebuildPrimitives) {
            var primitives = this._primitives;
            primitives.removeAndDestroy(this._primitive);
            primitives.removeAndDestroy(this._outlinePrimitive);
            this._primitive = undefined;
            this._outlinePrimitive = undefined;
            this._lastSceneMode = sceneMode;
            this._lastOutlineWidth = outlineWidth;

            options.stackPartitions = stackPartitions;
            options.slicePartitions = slicePartitions;
            options.subdivisions = subdivisions;
            options.radii = in3D ? unitSphere : radii;

            appearance = new MaterialAppearance({
                material : material,
                translucent : material.isTranslucent(),
                closed : true
            });
            options.vertexFormat = appearance.vertexFormat;

            this._primitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new EllipsoidGeometry(options),
                    modelMatrix : !in3D ? modelMatrix : undefined,
                    attributes : {
                        show : new ShowGeometryInstanceAttribute(showFill),
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : appearance,
                asynchronous : false,
                shadows : shadows
            }));

            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new EllipsoidOutlineGeometry(options),
                    modelMatrix : !in3D ? modelMatrix : undefined,
                    attributes : {
                        show : new ShowGeometryInstanceAttribute(showOutline),
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                        distanceDisplayCondition : distanceDisplayConditionAttribute
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : outlineColor.alpha !== 1.0,
                    renderState : {
                        lineWidth : this._geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));

            this._lastShow = showFill;
            this._lastOutlineShow = showOutline;
            this._lastOutlineColor = Color.clone(outlineColor, this._lastOutlineColor);
            this._lastDistanceDisplayCondition = distanceDisplayCondition;
        } else if (this._primitive.ready) {
            //Update attributes only.
            var primitive = this._primitive;
            var outlinePrimitive = this._outlinePrimitive;

            primitive.show = true;
            outlinePrimitive.show = true;

            appearance = primitive.appearance;
            appearance.material = material;

            var attributes = this._attributes;
            if (!defined(attributes)) {
                attributes = primitive.getGeometryInstanceAttributes(entity);
                this._attributes = attributes;
            }
            if (showFill !== this._lastShow) {
                attributes.show = ShowGeometryInstanceAttribute.toValue(showFill, attributes.show);
                this._lastShow = showFill;
            }

            var outlineAttributes = this._outlineAttributes;

            if (!defined(outlineAttributes)) {
                outlineAttributes = outlinePrimitive.getGeometryInstanceAttributes(entity);
                this._outlineAttributes = outlineAttributes;
            }

            if (showOutline !== this._lastOutlineShow) {
                outlineAttributes.show = ShowGeometryInstanceAttribute.toValue(showOutline, outlineAttributes.show);
                this._lastOutlineShow = showOutline;
            }

            if (!Color.equals(outlineColor, this._lastOutlineColor)) {
                outlineAttributes.color = ColorGeometryInstanceAttribute.toValue(outlineColor, outlineAttributes.color);
                Color.clone(outlineColor, this._lastOutlineColor);
            }

            if (!DistanceDisplayCondition.equals(distanceDisplayCondition, this._lastDistanceDisplayCondition)) {
                attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
                outlineAttributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, outlineAttributes.distanceDisplayCondition);
                DistanceDisplayCondition.clone(distanceDisplayCondition, this._lastDistanceDisplayCondition);
            }
        }

        if (in3D) {
            //Since we are scaling a unit sphere, we can't let any of the values go to zero.
            //Instead we clamp them to a small value.  To the naked eye, this produces the same results
            //that you get passing EllipsoidGeometry a radii with a zero component.
            radii.x = Math.max(radii.x, 0.001);
            radii.y = Math.max(radii.y, 0.001);
            radii.z = Math.max(radii.z, 0.001);

            modelMatrix = Matrix4.multiplyByScale(modelMatrix, radii, modelMatrix);
            this._primitive.modelMatrix = modelMatrix;
            this._outlinePrimitive.modelMatrix = modelMatrix;
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return EllipsoidGeometryUpdater;
});

/*global define*/
define('DataSources/StaticGeometryColorBatch',[
        '../Core/AssociativeArray',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defined',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/Primitive',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        Color,
        ColorGeometryInstanceAttribute,
        defined,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        ShowGeometryInstanceAttribute,
        Primitive,
        BoundingSphereState,
        Property) {
    'use strict';

    var colorScratch = new Color();
    var distanceDisplayConditionScratch = new DistanceDisplayCondition();

    function Batch(primitives, translucent, appearanceType, closed, shadows) {
        this.translucent = translucent;
        this.appearanceType = appearanceType;
        this.closed = closed;
        this.shadows = shadows;
        this.primitives = primitives;
        this.createPrimitive = false;
        this.waitingOnCreate = false;
        this.primitive = undefined;
        this.oldPrimitive = undefined;
        this.geometry = new AssociativeArray();
        this.updaters = new AssociativeArray();
        this.updatersWithAttributes = new AssociativeArray();
        this.attributes = new AssociativeArray();
        this.subscriptions = new AssociativeArray();
        this.showsUpdated = new AssociativeArray();
        this.itemsToRemove = [];
    }

    Batch.prototype.add = function(updater, instance) {
        var id = updater.entity.id;
        this.createPrimitive = true;
        this.geometry.set(id, instance);
        this.updaters.set(id, updater);
        if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
            this.updatersWithAttributes.set(id, updater);
        } else {
            var that = this;
            this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
                if (propertyName === 'isShowing') {
                    that.showsUpdated.set(entity.id, updater);
                }
            }));
        }
    };

    Batch.prototype.remove = function(updater) {
        var id = updater.entity.id;
        this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
        if (this.updaters.remove(id)) {
            this.updatersWithAttributes.remove(id);
            var unsubscribe = this.subscriptions.get(id);
            if (defined(unsubscribe)) {
                unsubscribe();
                this.subscriptions.remove(id);
            }
        }
    };

    Batch.prototype.update = function(time) {
        var isUpdated = true;
        var removedCount = 0;
        var primitive = this.primitive;
        var primitives = this.primitives;
        var attributes;
        var i;

        if (this.createPrimitive) {
            var geometries = this.geometry.values;
            var geometriesLength = geometries.length;
            if (geometriesLength > 0) {
                if (defined(primitive)) {
                    if (!defined(this.oldPrimitive)) {
                        this.oldPrimitive = primitive;
                    } else {
                        primitives.remove(primitive);
                    }
                }

                for (i = 0; i < geometriesLength; i++) {
                    var geometryItem = geometries[i];
                    var originalAttributes = geometryItem.attributes;
                    attributes = this.attributes.get(geometryItem.id.id);

                    if (defined(attributes)) {
                        if (defined(originalAttributes.show)) {
                            originalAttributes.show.value = attributes.show;
                        }
                        if (defined(originalAttributes.color)) {
                            originalAttributes.color.value = attributes.color;
                        }
                    }
                }

                primitive = new Primitive({
                    asynchronous : true,
                    geometryInstances : geometries,
                    appearance : new this.appearanceType({
                        translucent : this.translucent,
                        closed : this.closed
                    }),
                    shadows : this.shadows
                });
                primitives.add(primitive);
                isUpdated = false;
            } else {
                if (defined(primitive)) {
                    primitives.remove(primitive);
                    primitive = undefined;
                }
                var oldPrimitive = this.oldPrimitive;
                if (defined(oldPrimitive)) {
                    primitives.remove(oldPrimitive);
                    this.oldPrimitive = undefined;
                }
            }

            this.attributes.removeAll();
            this.primitive = primitive;
            this.createPrimitive = false;
            this.waitingOnCreate = true;
        } else if (defined(primitive) && primitive.ready) {
            if (defined(this.oldPrimitive)) {
                primitives.remove(this.oldPrimitive);
                this.oldPrimitive = undefined;
            }
            var updatersWithAttributes = this.updatersWithAttributes.values;
            var length = updatersWithAttributes.length;
            var waitingOnCreate = this.waitingOnCreate;
            for (i = 0; i < length; i++) {
                var updater = updatersWithAttributes[i];
                var instance = this.geometry.get(updater.entity.id);

                attributes = this.attributes.get(instance.id.id);
                if (!defined(attributes)) {
                    attributes = primitive.getGeometryInstanceAttributes(instance.id);
                    this.attributes.set(instance.id.id, attributes);
                }

                if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) {
                    var colorProperty = updater.fillMaterialProperty.color;
                    colorProperty.getValue(time, colorScratch);
                    if (!Color.equals(attributes._lastColor, colorScratch)) {
                        attributes._lastColor = Color.clone(colorScratch, attributes._lastColor);
                        attributes.color = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.color);
                        if ((this.translucent && attributes.color[3] === 255) || (!this.translucent && attributes.color[3] !== 255)) {
                            this.itemsToRemove[removedCount++] = updater;
                        }
                    }
                }

                var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time));
                var currentShow = attributes.show[0] === 1;
                if (show !== currentShow) {
                    attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
                }

                var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
                if (!Property.isConstant(distanceDisplayConditionProperty)) {
                    var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
                    if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
                        attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
                        attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
                    }
                }
            }

            this.updateShows(primitive);
            this.waitingOnCreate = false;
        } else if (defined(primitive) && !primitive.ready) {
            isUpdated = false;
        }
        this.itemsToRemove.length = removedCount;
        return isUpdated;
    };

    Batch.prototype.updateShows = function(primitive) {
        var showsUpdated = this.showsUpdated.values;
        var length = showsUpdated.length;
        for (var i = 0; i < length; i++) {
            var updater = showsUpdated[i];
            var instance = this.geometry.get(updater.entity.id);

            var attributes = this.attributes.get(instance.id.id);
            if (!defined(attributes)) {
                attributes = primitive.getGeometryInstanceAttributes(instance.id);
                this.attributes.set(instance.id.id, attributes);
            }

            var show = updater.entity.isShowing;
            var currentShow = attributes.show[0] === 1;
            if (show !== currentShow) {
                attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
            }
        }
        this.showsUpdated.removeAll();
    };

    Batch.prototype.contains = function(entity) {
        return this.updaters.contains(entity.id);
    };

    Batch.prototype.getBoundingSphere = function(entity, result) {
        var primitive = this.primitive;
        if (!primitive.ready) {
            return BoundingSphereState.PENDING;
        }
        var attributes = primitive.getGeometryInstanceAttributes(entity);
        if (!defined(attributes) || !defined(attributes.boundingSphere) ||//
            (defined(attributes.show) && attributes.show[0] === 0)) {
            return BoundingSphereState.FAILED;
        }
        attributes.boundingSphere.clone(result);
        return BoundingSphereState.DONE;
    };

    Batch.prototype.removeAllPrimitives = function() {
        var primitives = this.primitives;

        var primitive = this.primitive;
        if (defined(primitive)) {
            primitives.remove(primitive);
            this.primitive = undefined;
            this.geometry.removeAll();
            this.updaters.removeAll();
        }

        var oldPrimitive = this.oldPrimitive;
        if (defined(oldPrimitive)) {
            primitives.remove(oldPrimitive);
            this.oldPrimitive = undefined;
        }
    };

    /**
     * @private
     */
    function StaticGeometryColorBatch(primitives, appearanceType, closed, shadows) {
        this._solidBatch = new Batch(primitives, false, appearanceType, closed, shadows);
        this._translucentBatch = new Batch(primitives, true, appearanceType, closed, shadows);
    }

    StaticGeometryColorBatch.prototype.add = function(time, updater) {
        var instance = updater.createFillGeometryInstance(time);
        if (instance.attributes.color.value[3] === 255) {
            this._solidBatch.add(updater, instance);
        } else {
            this._translucentBatch.add(updater, instance);
        }
    };

    StaticGeometryColorBatch.prototype.remove = function(updater) {
        if (!this._solidBatch.remove(updater)) {
            this._translucentBatch.remove(updater);
        }
    };

    StaticGeometryColorBatch.prototype.update = function(time) {
        var i;
        var updater;

        //Perform initial update
        var isUpdated = this._solidBatch.update(time);
        isUpdated = this._translucentBatch.update(time) && isUpdated;

        //If any items swapped between solid/translucent, we need to
        //move them between batches
        var itemsToRemove = this._solidBatch.itemsToRemove;
        var solidsToMoveLength = itemsToRemove.length;
        if (solidsToMoveLength > 0) {
            for (i = 0; i < solidsToMoveLength; i++) {
                updater = itemsToRemove[i];
                this._solidBatch.remove(updater);
                this._translucentBatch.add(updater, updater.createFillGeometryInstance(time));
            }
        }

        itemsToRemove = this._translucentBatch.itemsToRemove;
        var translucentToMoveLength = itemsToRemove.length;
        if (translucentToMoveLength > 0) {
            for (i = 0; i < translucentToMoveLength; i++) {
                updater = itemsToRemove[i];
                this._translucentBatch.remove(updater);
                this._solidBatch.add(updater, updater.createFillGeometryInstance(time));
            }
        }

        //If we moved anything around, we need to re-build the primitive
        if (solidsToMoveLength > 0 || translucentToMoveLength > 0) {
            isUpdated = this._solidBatch.update(time) && isUpdated;
            isUpdated = this._translucentBatch.update(time) && isUpdated;
        }

        return isUpdated;
    };

    StaticGeometryColorBatch.prototype.getBoundingSphere = function(entity, result) {
        if (this._solidBatch.contains(entity)) {
            return this._solidBatch.getBoundingSphere(entity, result);
        } else if (this._translucentBatch.contains(entity)) {
            return this._translucentBatch.getBoundingSphere(entity, result);
        }
        return BoundingSphereState.FAILED;
    };

    StaticGeometryColorBatch.prototype.removeAllPrimitives = function() {
        this._solidBatch.removeAllPrimitives();
        this._translucentBatch.removeAllPrimitives();
    };

    return StaticGeometryColorBatch;
});

/*global define*/
define('DataSources/StaticGeometryPerMaterialBatch',[
        '../Core/AssociativeArray',
        '../Core/defined',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/Primitive',
        './BoundingSphereState',
        './MaterialProperty',
        './Property'
    ], function(
        AssociativeArray,
        defined,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        ShowGeometryInstanceAttribute,
        Primitive,
        BoundingSphereState,
        MaterialProperty,
        Property) {
    'use strict';

    var distanceDisplayConditionScratch = new DistanceDisplayCondition();

    function Batch(primitives, appearanceType, materialProperty, closed, shadows) {
        this.primitives = primitives;
        this.appearanceType = appearanceType;
        this.materialProperty = materialProperty;
        this.closed = closed;
        this.shadows = shadows;
        this.updaters = new AssociativeArray();
        this.createPrimitive = true;
        this.primitive = undefined;
        this.oldPrimitive = undefined;
        this.geometry = new AssociativeArray();
        this.material = undefined;
        this.updatersWithAttributes = new AssociativeArray();
        this.attributes = new AssociativeArray();
        this.invalidated = false;
        this.removeMaterialSubscription = materialProperty.definitionChanged.addEventListener(Batch.prototype.onMaterialChanged, this);
        this.subscriptions = new AssociativeArray();
        this.showsUpdated = new AssociativeArray();
    }
    Batch.prototype.onMaterialChanged = function() {
        this.invalidated = true;
    };

    Batch.prototype.isMaterial = function(updater) {
        var material = this.materialProperty;
        var updaterMaterial = updater.fillMaterialProperty;
        if (updaterMaterial === material) {
            return true;
        }
        if (defined(material)) {
            return material.equals(updaterMaterial);
        }
        return false;
    };

    Batch.prototype.add = function(time, updater) {
        var id = updater.entity.id;
        this.updaters.set(id, updater);
        this.geometry.set(id, updater.createFillGeometryInstance(time));
        if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
            this.updatersWithAttributes.set(id, updater);
        } else {
            var that = this;
            this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
                if (propertyName === 'isShowing') {
                    that.showsUpdated.set(entity.id, updater);
                }
            }));
        }
        this.createPrimitive = true;
    };

    Batch.prototype.remove = function(updater) {
        var id = updater.entity.id;
        this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
        if (this.updaters.remove(id)) {
            this.updatersWithAttributes.remove(id);
            var unsubscribe = this.subscriptions.get(id);
            if (defined(unsubscribe)) {
                unsubscribe();
                this.subscriptions.remove(id);
            }
        }
        return this.createPrimitive;
    };

    Batch.prototype.update = function(time) {
        var isUpdated = true;
        var primitive = this.primitive;
        var primitives = this.primitives;
        var geometries = this.geometry.values;
        var attributes;
        var i;

        if (this.createPrimitive) {
            var geometriesLength = geometries.length;
            if (geometriesLength > 0) {
                if (defined(primitive)) {
                    if (!defined(this.oldPrimitive)) {
                        this.oldPrimitive = primitive;
                    } else {
                        primitives.remove(primitive);
                    }
                }

                for (i = 0; i < geometriesLength; i++) {
                    var geometry = geometries[i];
                    var originalAttributes = geometry.attributes;
                    attributes = this.attributes.get(geometry.id.id);

                    if (defined(attributes)) {
                        if (defined(originalAttributes.show)) {
                            originalAttributes.show.value = attributes.show;
                        }
                        if (defined(originalAttributes.color)) {
                            originalAttributes.color.value = attributes.color;
                        }
                    }
                }

                this.material = MaterialProperty.getValue(time, this.materialProperty, this.material);
                primitive = new Primitive({
                    asynchronous : true,
                    geometryInstances : geometries,
                    appearance : new this.appearanceType({
                        material : this.material,
                        translucent : this.material.isTranslucent(),
                        closed : this.closed
                    }),
                    shadows : this.shadows
                });

                primitives.add(primitive);
                isUpdated = false;
            } else {
                if (defined(primitive)) {
                    primitives.remove(primitive);
                    primitive = undefined;
                }
                var oldPrimitive = this.oldPrimitive;
                if (defined(oldPrimitive)) {
                    primitives.remove(oldPrimitive);
                    this.oldPrimitive = undefined;
                }
            }

            this.attributes.removeAll();
            this.primitive = primitive;
            this.createPrimitive = false;
        } else if (defined(primitive) && primitive.ready) {
            if (defined(this.oldPrimitive)) {
                primitives.remove(this.oldPrimitive);
                this.oldPrimitive = undefined;
            }

            this.material = MaterialProperty.getValue(time, this.materialProperty, this.material);
            this.primitive.appearance.material = this.material;

            var updatersWithAttributes = this.updatersWithAttributes.values;
            var length = updatersWithAttributes.length;
            for (i = 0; i < length; i++) {
                var updater = updatersWithAttributes[i];
                var entity = updater.entity;
                var instance = this.geometry.get(entity.id);

                attributes = this.attributes.get(instance.id.id);
                if (!defined(attributes)) {
                    attributes = primitive.getGeometryInstanceAttributes(instance.id);
                    this.attributes.set(instance.id.id, attributes);
                }

                var show = entity.isShowing && (updater.hasConstantFill || updater.isFilled(time));
                var currentShow = attributes.show[0] === 1;
                if (show !== currentShow) {
                    attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
                }

                var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
                if (!Property.isConstant(distanceDisplayConditionProperty)) {
                    var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
                    if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
                        attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
                        attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
                    }
                }
            }

            this.updateShows(primitive);
        } else if (defined(primitive) && !primitive.ready) {
            isUpdated = false;
        }
        return isUpdated;
    };

    Batch.prototype.updateShows = function(primitive) {
        var showsUpdated = this.showsUpdated.values;
        var length = showsUpdated.length;
        for (var i = 0; i < length; i++) {
            var updater = showsUpdated[i];
            var entity = updater.entity;
            var instance = this.geometry.get(entity.id);

            var attributes = this.attributes.get(instance.id.id);
            if (!defined(attributes)) {
                attributes = primitive.getGeometryInstanceAttributes(instance.id);
                this.attributes.set(instance.id.id, attributes);
            }

            var show = entity.isShowing;
            var currentShow = attributes.show[0] === 1;
            if (show !== currentShow) {
                attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
            }
        }
        this.showsUpdated.removeAll();
    };

    Batch.prototype.contains = function(entity) {
        return this.updaters.contains(entity.id);
    };

    Batch.prototype.getBoundingSphere = function(entity, result) {
        var primitive = this.primitive;
        if (!primitive.ready) {
            return BoundingSphereState.PENDING;
        }
        var attributes = primitive.getGeometryInstanceAttributes(entity);
        if (!defined(attributes) || !defined(attributes.boundingSphere) ||
            (defined(attributes.show) && attributes.show[0] === 0)) {
            return BoundingSphereState.FAILED;
        }
        attributes.boundingSphere.clone(result);
        return BoundingSphereState.DONE;
    };

    Batch.prototype.destroy = function(time) {
        var primitive = this.primitive;
        var primitives = this.primitives;
        if (defined(primitive)) {
            primitives.remove(primitive);
        }
        var oldPrimitive = this.oldPrimitive;
        if (defined(oldPrimitive)) {
            primitives.remove(oldPrimitive);
        }
        this.removeMaterialSubscription();
    };

    /**
     * @private
     */
    function StaticGeometryPerMaterialBatch(primitives, appearanceType, closed, shadows) {
        this._items = [];
        this._primitives = primitives;
        this._appearanceType = appearanceType;
        this._closed = closed;
        this._shadows = shadows;
    }
    StaticGeometryPerMaterialBatch.prototype.add = function(time, updater) {
        var items = this._items;
        var length = items.length;
        for (var i = 0; i < length; i++) {
            var item = items[i];
            if (item.isMaterial(updater)) {
                item.add(time, updater);
                return;
            }
        }
        var batch = new Batch(this._primitives, this._appearanceType, updater.fillMaterialProperty, this._closed, this._shadows);
        batch.add(time, updater);
        items.push(batch);
    };

    StaticGeometryPerMaterialBatch.prototype.remove = function(updater) {
        var items = this._items;
        var length = items.length;
        for (var i = length - 1; i >= 0; i--) {
            var item = items[i];
            if (item.remove(updater)) {
                if (item.updaters.length === 0) {
                    items.splice(i, 1);
                    item.destroy();
                }
                break;
            }
        }
    };

    StaticGeometryPerMaterialBatch.prototype.update = function(time) {
        var i;
        var items = this._items;
        var length = items.length;

        for (i = length - 1; i >= 0; i--) {
            var item = items[i];
            if (item.invalidated) {
                items.splice(i, 1);
                var updaters = item.updaters.values;
                var updatersLength = updaters.length;
                for (var h = 0; h < updatersLength; h++) {
                    this.add(time, updaters[h]);
                }
                item.destroy();
            }
        }

        var isUpdated = true;
        for (i = 0; i < length; i++) {
            isUpdated = items[i].update(time) && isUpdated;
        }
        return isUpdated;
    };

    StaticGeometryPerMaterialBatch.prototype.getBoundingSphere = function(entity, result) {
        var items = this._items;
        var length = items.length;
        for (var i = 0; i < length; i++) {
            var item = items[i];
            if(item.contains(entity)){
                return item.getBoundingSphere(entity, result);
            }
        }
        return BoundingSphereState.FAILED;
    };

    StaticGeometryPerMaterialBatch.prototype.removeAllPrimitives = function() {
        var items = this._items;
        var length = items.length;
        for (var i = 0; i < length; i++) {
            items[i].destroy();
        }
        this._items.length = 0;
    };

    return StaticGeometryPerMaterialBatch;
});

/*global define*/
define('DataSources/StaticGroundGeometryColorBatch',[
        '../Core/AssociativeArray',
        '../Core/Color',
        '../Core/defined',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/GroundPrimitive',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        Color,
        defined,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        ShowGeometryInstanceAttribute,
        GroundPrimitive,
        BoundingSphereState,
        Property) {
    "use strict";

    var colorScratch = new Color();
    var distanceDisplayConditionScratch = new DistanceDisplayCondition();

    function Batch(primitives, color, key) {
        this.primitives = primitives;
        this.color = color;
        this.key = key;
        this.createPrimitive = false;
        this.waitingOnCreate = false;
        this.primitive = undefined;
        this.oldPrimitive = undefined;
        this.geometry = new AssociativeArray();
        this.updaters = new AssociativeArray();
        this.updatersWithAttributes = new AssociativeArray();
        this.attributes = new AssociativeArray();
        this.subscriptions = new AssociativeArray();
        this.showsUpdated = new AssociativeArray();
        this.itemsToRemove = [];
        this.isDirty = false;
    }

    Batch.prototype.add = function(updater, instance) {
        var id = updater.entity.id;
        this.createPrimitive = true;
        this.geometry.set(id, instance);
        this.updaters.set(id, updater);
        if (!updater.hasConstantFill || !updater.fillMaterialProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
            this.updatersWithAttributes.set(id, updater);
        } else {
            var that = this;
            this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
                if (propertyName === 'isShowing') {
                    that.showsUpdated.set(entity.id, updater);
                }
            }));
        }
    };

    Batch.prototype.remove = function(updater) {
        var id = updater.entity.id;
        this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
        if (this.updaters.remove(id)) {
            this.updatersWithAttributes.remove(id);
            var unsubscribe = this.subscriptions.get(id);
            if (defined(unsubscribe)) {
                unsubscribe();
                this.subscriptions.remove(id);
            }
        }
    };

    var scratchArray = new Array(4);

    Batch.prototype.update = function(time) {
        var isUpdated = true;
        var removedCount = 0;
        var primitive = this.primitive;
        var primitives = this.primitives;
        var attributes;
        var i;

        if (this.createPrimitive) {
            var geometries = this.geometry.values;
            var geometriesLength = geometries.length;
            if (geometriesLength > 0) {
                if (defined(primitive)) {
                    if (!defined(this.oldPrimitive)) {
                        this.oldPrimitive = primitive;
                    } else {
                        primitives.remove(primitive);
                    }
                }

                for (i = 0; i < geometriesLength; i++) {
                    var geometryItem = geometries[i];
                    var originalAttributes = geometryItem.attributes;
                    attributes = this.attributes.get(geometryItem.id.id);

                    if (defined(attributes)) {
                        if (defined(originalAttributes.show)) {
                            originalAttributes.show.value = attributes.show;
                        }
                        if (defined(originalAttributes.color)) {
                            originalAttributes.color.value = attributes.color;
                        }
                    }
                }

                primitive = new GroundPrimitive({
                    asynchronous : true,
                    geometryInstances : geometries
                });
                primitives.add(primitive);
                isUpdated = false;
            } else {
                if (defined(primitive)) {
                    primitives.remove(primitive);
                    primitive = undefined;
                }
                var oldPrimitive = this.oldPrimitive;
                if (defined(oldPrimitive)) {
                    primitives.remove(oldPrimitive);
                    this.oldPrimitive = undefined;
                }
            }

            this.attributes.removeAll();
            this.primitive = primitive;
            this.createPrimitive = false;
            this.waitingOnCreate = true;
        } else if (defined(primitive) && primitive.ready) {
            if (defined(this.oldPrimitive)) {
                primitives.remove(this.oldPrimitive);
                this.oldPrimitive = undefined;
            }
            var updatersWithAttributes = this.updatersWithAttributes.values;
            var length = updatersWithAttributes.length;
            var waitingOnCreate = this.waitingOnCreate;
            for (i = 0; i < length; i++) {
                var updater = updatersWithAttributes[i];
                var instance = this.geometry.get(updater.entity.id);

                attributes = this.attributes.get(instance.id.id);
                if (!defined(attributes)) {
                    attributes = primitive.getGeometryInstanceAttributes(instance.id);
                    this.attributes.set(instance.id.id, attributes);
                }

                if (!updater.fillMaterialProperty.isConstant || waitingOnCreate) {
                    var colorProperty = updater.fillMaterialProperty.color;
                    colorProperty.getValue(time, colorScratch);

                    if (!Color.equals(attributes._lastColor, colorScratch)) {
                        attributes._lastColor = Color.clone(colorScratch, attributes._lastColor);
                        var color = this.color;
                        var newColor = colorScratch.toBytes(scratchArray);
                        if (color[0] !== newColor[0] || color[1] !== newColor[1] ||
                            color[2] !== newColor[2] || color[3] !== newColor[3]) {
                           this.itemsToRemove[removedCount++] = updater;
                        }
                    }
                }

                var show = updater.entity.isShowing && (updater.hasConstantFill || updater.isFilled(time));
                var currentShow = attributes.show[0] === 1;
                if (show !== currentShow) {
                    attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
                }

                var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
                if (!Property.isConstant(distanceDisplayConditionProperty)) {
                    var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
                    if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
                        attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
                        attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
                    }
                }
            }

            this.updateShows(primitive);
            this.waitingOnCreate = false;
        } else if (defined(primitive) && !primitive.ready) {
            isUpdated = false;
        }
        this.itemsToRemove.length = removedCount;
        return isUpdated;
    };

    Batch.prototype.updateShows = function(primitive) {
        var showsUpdated = this.showsUpdated.values;
        var length = showsUpdated.length;
        for (var i = 0; i < length; i++) {
            var updater = showsUpdated[i];
            var instance = this.geometry.get(updater.entity.id);

            var attributes = this.attributes.get(instance.id.id);
            if (!defined(attributes)) {
                attributes = primitive.getGeometryInstanceAttributes(instance.id);
                this.attributes.set(instance.id.id, attributes);
            }

            var show = updater.entity.isShowing;
            var currentShow = attributes.show[0] === 1;
            if (show !== currentShow) {
                attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
            }
        }
        this.showsUpdated.removeAll();
    };

    Batch.prototype.contains = function(entity) {
        return this.updaters.contains(entity.id);
    };

    Batch.prototype.getBoundingSphere = function(entity, result) {
        var primitive = this.primitive;
        if (!primitive.ready) {
            return BoundingSphereState.PENDING;
        }
        
        var bs = primitive.getBoundingSphere(entity);
        if (!defined(bs)) {
            return BoundingSphereState.FAILED;
        }

        bs.clone(result);
        return BoundingSphereState.DONE;
    };

    Batch.prototype.removeAllPrimitives = function() {
        var primitives = this.primitives;

        var primitive = this.primitive;
        if (defined(primitive)) {
            primitives.remove(primitive);
            this.primitive = undefined;
            this.geometry.removeAll();
            this.updaters.removeAll();
        }

        var oldPrimitive = this.oldPrimitive;
        if (defined(oldPrimitive)) {
            primitives.remove(oldPrimitive);
            this.oldPrimitive = undefined;
        }
    };

    /**
     * @private
     */
    function StaticGroundGeometryColorBatch(primitives) {
        this._batches = new AssociativeArray();
        this._primitives = primitives;
    }

    StaticGroundGeometryColorBatch.prototype.add = function(time, updater) {
        var instance = updater.createFillGeometryInstance(time);
        var batches = this._batches;
        // instance.attributes.color.value is a Uint8Array, so just read it as a Uint32 and make that the key
        var batchKey = new Uint32Array(instance.attributes.color.value.buffer)[0];
        var batch;
        if (batches.contains(batchKey)) {
            batch = batches.get(batchKey);
        } else {
            batch = new Batch(this._primitives, instance.attributes.color.value, batchKey);
            batches.set(batchKey, batch);
        }
        batch.add(updater, instance);
        return batch;
    };

    StaticGroundGeometryColorBatch.prototype.remove = function(updater) {
        var batchesArray = this._batches.values;
        var count = batchesArray.length;
        for (var i = 0; i < count; ++i) {
            if (batchesArray[i].remove(updater)) {
                return;
            }
        }
    };

    StaticGroundGeometryColorBatch.prototype.update = function(time) {
        var i;
        var updater;

        //Perform initial update
        var isUpdated = true;
        var batches = this._batches;
        var batchesArray = batches.values;
        var batchCount = batchesArray.length;
        for (i = 0; i < batchCount; ++i) {
            isUpdated = batchesArray[i].update(time) && isUpdated;
        }

        //If any items swapped between batches we need to move them
        for (i = 0; i < batchCount; ++i) {
            var oldBatch = batchesArray[i];
            var itemsToRemove = oldBatch.itemsToRemove;
            var itemsToMoveLength = itemsToRemove.length;
            for (var j = 0; j < itemsToMoveLength; j++) {
                updater = itemsToRemove[j];
                oldBatch.remove(updater);
                var newBatch = this.add(time, updater);
                oldBatch.isDirty = true;
                newBatch.isDirty = true;
            }
        }

        //If we moved anything around, we need to re-build the primitive and remove empty batches
        var batchesArrayCopy = batchesArray.slice();
        var batchesCopyCount = batchesArrayCopy.length;
        for (i = 0; i < batchesCopyCount; ++i) {
            var batch = batchesArrayCopy[i];
            if (batch.isDirty) {
                isUpdated = batchesArrayCopy[i].update(time) && isUpdated;
                batch.isDirty = false;
            }
            if (batch.geometry.length === 0) {
                batches.remove(batch.key);
            }
        }

        return isUpdated;
    };

    StaticGroundGeometryColorBatch.prototype.getBoundingSphere = function(entity, result) {
        var batchesArray = this._batches.values;
        var batchCount = batchesArray.length;
        for (var i = 0; i < batchCount; ++i) {
            var batch = batchesArray[i];
            if (batch.contains(entity)) {
                return batch.getBoundingSphere(entity, result);
            }
        }

        return BoundingSphereState.FAILED;
    };

    StaticGroundGeometryColorBatch.prototype.removeAllPrimitives = function() {
        var batchesArray = this._batches.values;
        var batchCount = batchesArray.length;
        for (var i = 0; i < batchCount; ++i) {
            batchesArray[i].removeAllPrimitives();
        }
    };

    return StaticGroundGeometryColorBatch;
});

/*global define*/
define('DataSources/StaticOutlineGeometryBatch',[
        '../Core/AssociativeArray',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defined',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        Color,
        ColorGeometryInstanceAttribute,
        defined,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        ShowGeometryInstanceAttribute,
        PerInstanceColorAppearance,
        Primitive,
        BoundingSphereState,
        Property) {
    'use strict';

    function Batch(primitives, translucent, width, shadows) {
        this.translucent = translucent;
        this.width = width;
        this.shadows = shadows;
        this.primitives = primitives;
        this.createPrimitive = false;
        this.waitingOnCreate = false;
        this.primitive = undefined;
        this.oldPrimitive = undefined;
        this.geometry = new AssociativeArray();
        this.updaters = new AssociativeArray();
        this.updatersWithAttributes = new AssociativeArray();
        this.attributes = new AssociativeArray();
        this.itemsToRemove = [];
        this.subscriptions = new AssociativeArray();
        this.showsUpdated = new AssociativeArray();
    }
    Batch.prototype.add = function(updater, instance) {
        var id = updater.entity.id;
        this.createPrimitive = true;
        this.geometry.set(id, instance);
        this.updaters.set(id, updater);
        if (!updater.hasConstantOutline || !updater.outlineColorProperty.isConstant || !Property.isConstant(updater.distanceDisplayConditionProperty)) {
            this.updatersWithAttributes.set(id, updater);
        } else {
            var that = this;
            this.subscriptions.set(id, updater.entity.definitionChanged.addEventListener(function(entity, propertyName, newValue, oldValue) {
                if (propertyName === 'isShowing') {
                    that.showsUpdated.set(entity.id, updater);
                }
            }));
        }
    };

    Batch.prototype.remove = function(updater) {
        var id = updater.entity.id;
        this.createPrimitive = this.geometry.remove(id) || this.createPrimitive;
        if (this.updaters.remove(id)) {
            this.updatersWithAttributes.remove(id);
            var unsubscribe = this.subscriptions.get(id);
            if (defined(unsubscribe)) {
                unsubscribe();
                this.subscriptions.remove(id);
            }
        }
    };

    var colorScratch = new Color();
    var distanceDisplayConditionScratch = new DistanceDisplayCondition();

    Batch.prototype.update = function(time) {
        var isUpdated = true;
        var removedCount = 0;
        var primitive = this.primitive;
        var primitives = this.primitives;
        var attributes;
        var i;

        if (this.createPrimitive) {
            var geometries = this.geometry.values;
            var geometriesLength = geometries.length;
            if (geometriesLength > 0) {
                if (defined(primitive)) {
                    if (!defined(this.oldPrimitive)) {
                        this.oldPrimitive = primitive;
                    } else {
                        primitives.remove(primitive);
                    }
                }

                for (i = 0; i < geometriesLength; i++) {
                    var geometryItem = geometries[i];
                    var originalAttributes = geometryItem.attributes;
                    attributes = this.attributes.get(geometryItem.id.id);

                    if (defined(attributes)) {
                        if (defined(originalAttributes.show)) {
                            originalAttributes.show.value = attributes.show;
                        }
                        if (defined(originalAttributes.color)) {
                            originalAttributes.color.value = attributes.color;
                        }
                    }
                }

                primitive = new Primitive({
                    asynchronous : true,
                    geometryInstances : geometries,
                    appearance : new PerInstanceColorAppearance({
                        flat : true,
                        translucent : this.translucent,
                        renderState : {
                            lineWidth : this.width
                        }
                    }),
                    shadows : this.shadows
                });

                primitives.add(primitive);
                isUpdated = false;
            } else {
                if (defined(primitive)) {
                    primitives.remove(primitive);
                    primitive = undefined;
                }
                var oldPrimitive = this.oldPrimitive;
                if (defined(oldPrimitive)) {
                    primitives.remove(oldPrimitive);
                    this.oldPrimitive = undefined;
                }
            }

            this.attributes.removeAll();
            this.primitive = primitive;
            this.createPrimitive = false;
            this.waitingOnCreate = true;
        } else if (defined(primitive) && primitive.ready) {
            if (defined(this.oldPrimitive)) {
                primitives.remove(this.oldPrimitive);
                this.oldPrimitive = undefined;
            }

            var updatersWithAttributes = this.updatersWithAttributes.values;
            var length = updatersWithAttributes.length;
            var waitingOnCreate = this.waitingOnCreate;
            for (i = 0; i < length; i++) {
                var updater = updatersWithAttributes[i];
                var instance = this.geometry.get(updater.entity.id);

                attributes = this.attributes.get(instance.id.id);
                if (!defined(attributes)) {
                    attributes = primitive.getGeometryInstanceAttributes(instance.id);
                    this.attributes.set(instance.id.id, attributes);
                }

                if (!updater.outlineColorProperty.isConstant || waitingOnCreate) {
                    var outlineColorProperty = updater.outlineColorProperty;
                    outlineColorProperty.getValue(time, colorScratch);
                    if (!Color.equals(attributes._lastColor, colorScratch)) {
                        attributes._lastColor = Color.clone(colorScratch, attributes._lastColor);
                        attributes.color = ColorGeometryInstanceAttribute.toValue(colorScratch, attributes.color);
                        if ((this.translucent && attributes.color[3] === 255) || (!this.translucent && attributes.color[3] !== 255)) {
                            this.itemsToRemove[removedCount++] = updater;
                        }
                    }
                }

                var show = updater.entity.isShowing && (updater.hasConstantOutline || updater.isOutlineVisible(time));
                var currentShow = attributes.show[0] === 1;
                if (show !== currentShow) {
                    attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
                }

                var distanceDisplayConditionProperty = updater.distanceDisplayConditionProperty;
                if (!Property.isConstant(distanceDisplayConditionProperty)) {
                    var distanceDisplayCondition = distanceDisplayConditionProperty.getValue(time, distanceDisplayConditionScratch);
                    if (!DistanceDisplayCondition.equals(distanceDisplayCondition, attributes._lastDistanceDisplayCondition)) {
                        attributes._lastDistanceDisplayCondition = DistanceDisplayCondition.clone(distanceDisplayCondition, attributes._lastDistanceDisplayCondition);
                        attributes.distanceDisplayCondition = DistanceDisplayConditionGeometryInstanceAttribute.toValue(distanceDisplayCondition, attributes.distanceDisplayCondition);
                    }
                }
            }

            this.updateShows(primitive);
            this.waitingOnCreate = false;
        } else if (defined(primitive) && !primitive.ready) {
            isUpdated = false;
        }

        this.itemsToRemove.length = removedCount;
        return isUpdated;
    };

    Batch.prototype.updateShows = function(primitive) {
        var showsUpdated = this.showsUpdated.values;
        var length = showsUpdated.length;
        for (var i = 0; i < length; i++) {
            var updater = showsUpdated[i];
            var instance = this.geometry.get(updater.entity.id);

            var attributes = this.attributes.get(instance.id.id);
            if (!defined(attributes)) {
                attributes = primitive.getGeometryInstanceAttributes(instance.id);
                this.attributes.set(instance.id.id, attributes);
            }

            var show = updater.entity.isShowing;
            var currentShow = attributes.show[0] === 1;
            if (show !== currentShow) {
                attributes.show = ShowGeometryInstanceAttribute.toValue(show, attributes.show);
            }
        }
        this.showsUpdated.removeAll();
    };

    Batch.prototype.contains = function(entity) {
        return this.updaters.contains(entity.id);
    };

    Batch.prototype.getBoundingSphere = function(entity, result) {
        var primitive = this.primitive;
        if (!primitive.ready) {
            return BoundingSphereState.PENDING;
        }
        var attributes = primitive.getGeometryInstanceAttributes(entity);
        if (!defined(attributes) || !defined(attributes.boundingSphere) ||//
            (defined(attributes.show) && attributes.show[0] === 0)) {
            return BoundingSphereState.FAILED;
        }
        attributes.boundingSphere.clone(result);
        return BoundingSphereState.DONE;
    };

    Batch.prototype.removeAllPrimitives = function() {
        var primitives = this.primitives;

        var primitive = this.primitive;
        if (defined(primitive)) {
            primitives.remove(primitive);
            this.primitive = undefined;
            this.geometry.removeAll();
            this.updaters.removeAll();
        }

        var oldPrimitive = this.oldPrimitive;
        if (defined(oldPrimitive)) {
            primitives.remove(oldPrimitive);
            this.oldPrimitive = undefined;
        }
    };

    /**
     * @private
     */
    function StaticOutlineGeometryBatch(primitives, scene, shadows) {
        this._primitives = primitives;
        this._scene = scene;
        this._shadows = shadows;
        this._solidBatches = new AssociativeArray();
        this._translucentBatches = new AssociativeArray();
    }
    StaticOutlineGeometryBatch.prototype.add = function(time, updater) {
        var instance = updater.createOutlineGeometryInstance(time);
        var width = this._scene.clampLineWidth(updater.outlineWidth);
        var batches;
        var batch;
        if (instance.attributes.color.value[3] === 255) {
            batches = this._solidBatches;
            batch = batches.get(width);
            if (!defined(batch)) {
                batch = new Batch(this._primitives, false, width, this._shadows);
                batches.set(width, batch);
            }
            batch.add(updater, instance);
        } else {
            batches = this._translucentBatches;
            batch = batches.get(width);
            if (!defined(batch)) {
                batch = new Batch(this._primitives, true, width, this._shadows);
                batches.set(width, batch);
            }
            batch.add(updater, instance);
        }
    };

    StaticOutlineGeometryBatch.prototype.remove = function(updater) {
        var i;

        var solidBatches = this._solidBatches.values;
        var solidBatchesLength = solidBatches.length;
        for (i = 0; i < solidBatchesLength; i++) {
            if (solidBatches[i].remove(updater)) {
                return;
            }
        }

        var translucentBatches = this._translucentBatches.values;
        var translucentBatchesLength = translucentBatches.length;
        for (i = 0; i < translucentBatchesLength; i++) {
            if (translucentBatches[i].remove(updater)) {
                return;
            }
        }
    };

    StaticOutlineGeometryBatch.prototype.update = function(time) {
        var i;
        var x;
        var updater;
        var batch;
        var solidBatches = this._solidBatches.values;
        var solidBatchesLength = solidBatches.length;
        var translucentBatches = this._translucentBatches.values;
        var translucentBatchesLength = translucentBatches.length;
        var itemsToRemove;
        var isUpdated = true;
        var needUpdate = false;

        do {
            needUpdate = false;
            for (x = 0; x < solidBatchesLength; x++) {
                batch = solidBatches[x];
                //Perform initial update
                isUpdated = batch.update(time);

                //If any items swapped between solid/translucent, we need to
                //move them between batches
                itemsToRemove = batch.itemsToRemove;
                var solidsToMoveLength = itemsToRemove.length;
                if (solidsToMoveLength > 0) {
                    needUpdate = true;
                    for (i = 0; i < solidsToMoveLength; i++) {
                        updater = itemsToRemove[i];
                        batch.remove(updater);
                        this.add(time, updater);
                    }
                }
            }
            for (x = 0; x < translucentBatchesLength; x++) {
                batch = translucentBatches[x];
                //Perform initial update
                isUpdated = batch.update(time);

                //If any items swapped between solid/translucent, we need to
                //move them between batches
                itemsToRemove = batch.itemsToRemove;
                var translucentToMoveLength = itemsToRemove.length;
                if (translucentToMoveLength > 0) {
                    needUpdate = true;
                    for (i = 0; i < translucentToMoveLength; i++) {
                        updater = itemsToRemove[i];
                        batch.remove(updater);
                        this.add(time, updater);
                    }
                }
            }
        } while (needUpdate);

        return isUpdated;
    };

    StaticOutlineGeometryBatch.prototype.getBoundingSphere = function(entity, result) {
        var i;

        var solidBatches = this._solidBatches.values;
        var solidBatchesLength = solidBatches.length;
        for (i = 0; i < solidBatchesLength; i++) {
            var solidBatch = solidBatches[i];
            if(solidBatch.contains(entity)){
                return solidBatch.getBoundingSphere(entity, result);
            }
        }

        var translucentBatches = this._translucentBatches.values;
        var translucentBatchesLength = translucentBatches.length;
        for (i = 0; i < translucentBatchesLength; i++) {
            var translucentBatch = translucentBatches[i];
            if(translucentBatch.contains(entity)){
                return translucentBatch.getBoundingSphere(entity, result);
            }
        }

        return BoundingSphereState.FAILED;
    };

    StaticOutlineGeometryBatch.prototype.removeAllPrimitives = function() {
        var i;

        var solidBatches = this._solidBatches.values;
        var solidBatchesLength = solidBatches.length;
        for (i = 0; i < solidBatchesLength; i++) {
            solidBatches[i].removeAllPrimitives();
        }

        var translucentBatches = this._translucentBatches.values;
        var translucentBatchesLength = translucentBatches.length;
        for (i = 0; i < translucentBatchesLength; i++) {
            translucentBatches[i].removeAllPrimitives();
        }
    };

    return StaticOutlineGeometryBatch;
});

/*global define*/
define('DataSources/GeometryVisualizer',[
        '../Core/AssociativeArray',
        '../Core/BoundingSphere',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Scene/ShadowMode',
        './BoundingSphereState',
        './ColorMaterialProperty',
        './StaticGeometryColorBatch',
        './StaticGeometryPerMaterialBatch',
        './StaticGroundGeometryColorBatch',
        './StaticOutlineGeometryBatch'
    ], function(
        AssociativeArray,
        BoundingSphere,
        defined,
        destroyObject,
        DeveloperError,
        ShadowMode,
        BoundingSphereState,
        ColorMaterialProperty,
        StaticGeometryColorBatch,
        StaticGeometryPerMaterialBatch,
        StaticGroundGeometryColorBatch,
        StaticOutlineGeometryBatch) {
    'use strict';

    var emptyArray = [];

    function DynamicGeometryBatch(primitives, groundPrimitives) {
        this._primitives = primitives;
        this._groundPrimitives = groundPrimitives;
        this._dynamicUpdaters = new AssociativeArray();
    }
    DynamicGeometryBatch.prototype.add = function(time, updater) {
        this._dynamicUpdaters.set(updater.entity.id, updater.createDynamicUpdater(this._primitives, this._groundPrimitives));
    };

    DynamicGeometryBatch.prototype.remove = function(updater) {
        var id = updater.entity.id;
        var dynamicUpdater = this._dynamicUpdaters.get(id);
        if (defined(dynamicUpdater)) {
            this._dynamicUpdaters.remove(id);
            dynamicUpdater.destroy();
        }
    };

    DynamicGeometryBatch.prototype.update = function(time) {
        var geometries = this._dynamicUpdaters.values;
        for (var i = 0, len = geometries.length; i < len; i++) {
            geometries[i].update(time);
        }
        return true;
    };

    DynamicGeometryBatch.prototype.removeAllPrimitives = function() {
        var geometries = this._dynamicUpdaters.values;
        for (var i = 0, len = geometries.length; i < len; i++) {
            geometries[i].destroy();
        }
        this._dynamicUpdaters.removeAll();
    };

    DynamicGeometryBatch.prototype.getBoundingSphere = function(entity, result) {
        var updater = this._dynamicUpdaters.get(entity.id);
        if (defined(updater) && defined(updater.getBoundingSphere)) {
            return updater.getBoundingSphere(entity, result);
        }
        return BoundingSphereState.FAILED;
    };

    function removeUpdater(that, updater) {
        //We don't keep track of which batch an updater is in, so just remove it from all of them.
        var batches = that._batches;
        var length = batches.length;
        for (var i = 0; i < length; i++) {
            batches[i].remove(updater);
        }
    }

    function insertUpdaterIntoBatch(that, time, updater) {
        if (updater.isDynamic) {
            that._dynamicBatch.add(time, updater);
            return;
        }

        var shadows;
        if (updater.outlineEnabled || updater.fillEnabled) {
            shadows = updater.shadowsProperty.getValue(time);
        }

        if (updater.outlineEnabled) {
            that._outlineBatches[shadows].add(time, updater);
        }

        if (updater.fillEnabled) {
            if (updater.onTerrain) {
                that._groundColorBatch.add(time, updater);
            } else {
                if (updater.isClosed) {
                    if (updater.fillMaterialProperty instanceof ColorMaterialProperty) {
                        that._closedColorBatches[shadows].add(time, updater);
                    } else {
                        that._closedMaterialBatches[shadows].add(time, updater);
                    }
                } else {
                    if (updater.fillMaterialProperty instanceof ColorMaterialProperty) {
                        that._openColorBatches[shadows].add(time, updater);
                    } else {
                        that._openMaterialBatches[shadows].add(time, updater);
                    }
                }
            }
        }
    }

    /**
     * A general purpose visualizer for geometry represented by {@link Primitive} instances.
     * @alias GeometryVisualizer
     * @constructor
     *
     * @param {GeometryUpdater} type The updater to be used for creating the geometry.
     * @param {Scene} scene The scene the primitives will be rendered in.
     * @param {EntityCollection} entityCollection The entityCollection to visualize.
     */
    function GeometryVisualizer(type, scene, entityCollection) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(entityCollection)) {
            throw new DeveloperError('entityCollection is required.');
        }
        
        this._type = type;

        var primitives = scene.primitives;
        var groundPrimitives = scene.groundPrimitives;
        this._scene = scene;
        this._primitives = primitives;
        this._groundPrimitives = groundPrimitives;
        this._entityCollection = undefined;
        this._addedObjects = new AssociativeArray();
        this._removedObjects = new AssociativeArray();
        this._changedObjects = new AssociativeArray();

        var numberOfShadowModes = ShadowMode.NUMBER_OF_SHADOW_MODES;
        this._outlineBatches = new Array(numberOfShadowModes);
        this._closedColorBatches = new Array(numberOfShadowModes);
        this._closedMaterialBatches = new Array(numberOfShadowModes);
        this._openColorBatches = new Array(numberOfShadowModes);
        this._openMaterialBatches = new Array(numberOfShadowModes);

        for (var i = 0; i < numberOfShadowModes; ++i) {
            this._outlineBatches[i] = new StaticOutlineGeometryBatch(primitives, scene, i);
            this._closedColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, true, i);
            this._closedMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, true, i);
            this._openColorBatches[i] = new StaticGeometryColorBatch(primitives, type.perInstanceColorAppearanceType, false, i);
            this._openMaterialBatches[i] = new StaticGeometryPerMaterialBatch(primitives, type.materialAppearanceType, false, i);
        }

        this._groundColorBatch = new StaticGroundGeometryColorBatch(groundPrimitives);
        this._dynamicBatch = new DynamicGeometryBatch(primitives, groundPrimitives);

        this._batches = this._outlineBatches.concat(this._closedColorBatches, this._closedMaterialBatches, this._openColorBatches, this._openMaterialBatches, this._groundColorBatch, this._dynamicBatch);

        this._subscriptions = new AssociativeArray();
        this._updaters = new AssociativeArray();

        this._entityCollection = entityCollection;
        entityCollection.collectionChanged.addEventListener(GeometryVisualizer.prototype._onCollectionChanged, this);
        this._onCollectionChanged(entityCollection, entityCollection.values, emptyArray);
    }

    /**
     * Updates all of the primitives created by this visualizer to match their
     * Entity counterpart at the given time.
     *
     * @param {JulianDate} time The time to update to.
     * @returns {Boolean} True if the visualizer successfully updated to the provided time,
     * false if the visualizer is waiting for asynchronous primitives to be created.
     */
    GeometryVisualizer.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var addedObjects = this._addedObjects;
        var added = addedObjects.values;
        var removedObjects = this._removedObjects;
        var removed = removedObjects.values;
        var changedObjects = this._changedObjects;
        var changed = changedObjects.values;

        var i;
        var entity;
        var id;
        var updater;

        for (i = changed.length - 1; i > -1; i--) {
            entity = changed[i];
            id = entity.id;
            updater = this._updaters.get(id);

            //If in a single update, an entity gets removed and a new instance
            //re-added with the same id, the updater no longer tracks the
            //correct entity, we need to both remove the old one and
            //add the new one, which is done by pushing the entity
            //onto the removed/added lists.
            if (updater.entity === entity) {
                removeUpdater(this, updater);
                insertUpdaterIntoBatch(this, time, updater);
            } else {
                removed.push(entity);
                added.push(entity);
            }
        }

        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            id = entity.id;
            updater = this._updaters.get(id);
            removeUpdater(this, updater);
            updater.destroy();
            this._updaters.remove(id);
            this._subscriptions.get(id)();
            this._subscriptions.remove(id);
        }

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            id = entity.id;
            updater = new this._type(entity, this._scene);
            this._updaters.set(id, updater);
            insertUpdaterIntoBatch(this, time, updater);
            this._subscriptions.set(id, updater.geometryChanged.addEventListener(GeometryVisualizer._onGeometryChanged, this));
        }

        addedObjects.removeAll();
        removedObjects.removeAll();
        changedObjects.removeAll();

        var isUpdated = true;
        var batches = this._batches;
        var length = batches.length;
        for (i = 0; i < length; i++) {
            isUpdated = batches[i].update(time) && isUpdated;
        }

        return isUpdated;
    };

    var getBoundingSphereArrayScratch = [];
    var getBoundingSphereBoundingSphereScratch = new BoundingSphere();

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    GeometryVisualizer.prototype.getBoundingSphere = function(entity, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var boundingSpheres = getBoundingSphereArrayScratch;
        var tmp = getBoundingSphereBoundingSphereScratch;

        var count = 0;
        var state = BoundingSphereState.DONE;
        var batches = this._batches;
        var batchesLength = batches.length;

        for (var i = 0; i < batchesLength; i++) {
            state = batches[i].getBoundingSphere(entity, tmp);
            if (state === BoundingSphereState.PENDING) {
                return BoundingSphereState.PENDING;
            } else if (state === BoundingSphereState.DONE) {
                boundingSpheres[count] = BoundingSphere.clone(tmp, boundingSpheres[count]);
                count++;
            }
        }

        if (count === 0) {
            return BoundingSphereState.FAILED;
        }

        boundingSpheres.length = count;
        BoundingSphere.fromBoundingSpheres(boundingSpheres, result);
        return BoundingSphereState.DONE;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    GeometryVisualizer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes and destroys all primitives created by this instance.
     */
    GeometryVisualizer.prototype.destroy = function() {
        this._entityCollection.collectionChanged.removeEventListener(GeometryVisualizer.prototype._onCollectionChanged, this);
        this._addedObjects.removeAll();
        this._removedObjects.removeAll();

        var i;
        var batches = this._batches;
        var length = batches.length;
        for (i = 0; i < length; i++) {
            batches[i].removeAllPrimitives();
        }

        var subscriptions = this._subscriptions.values;
        length = subscriptions.length;
        for (i = 0; i < length; i++) {
            subscriptions[i]();
        }
        this._subscriptions.removeAll();
        return destroyObject(this);
    };

    /**
     * @private
     */
    GeometryVisualizer._onGeometryChanged = function(updater) {
        var removedObjects = this._removedObjects;
        var changedObjects = this._changedObjects;

        var entity = updater.entity;
        var id = entity.id;

        if (!defined(removedObjects.get(id)) && !defined(changedObjects.get(id))) {
            changedObjects.set(id, entity);
        }
    };

    /**
     * @private
     */
    GeometryVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed) {
        var addedObjects = this._addedObjects;
        var removedObjects = this._removedObjects;
        var changedObjects = this._changedObjects;

        var i;
        var id;
        var entity;
        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            id = entity.id;
            if (!addedObjects.remove(id)) {
                removedObjects.set(id, entity);
                changedObjects.remove(id);
            }
        }

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            id = entity.id;
            if (removedObjects.remove(id)) {
                changedObjects.set(id, entity);
            } else {
                addedObjects.set(id, entity);
            }
        }
    };

    return GeometryVisualizer;
});

/*global define*/
define('DataSources/LabelVisualizer',[
        '../Core/AssociativeArray',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/NearFarScalar',
        '../Scene/HeightReference',
        '../Scene/HorizontalOrigin',
        '../Scene/LabelStyle',
        '../Scene/VerticalOrigin',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        Cartesian2,
        Cartesian3,
        Color,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        NearFarScalar,
        HeightReference,
        HorizontalOrigin,
        LabelStyle,
        VerticalOrigin,
        BoundingSphereState,
        Property) {
    'use strict';

    var defaultScale = 1.0;
    var defaultFont = '30px sans-serif';
    var defaultStyle = LabelStyle.FILL;
    var defaultFillColor = Color.WHITE;
    var defaultOutlineColor = Color.BLACK;
    var defaultOutlineWidth = 1.0;
    var defaultShowBackground = false;
    var defaultBackgroundColor = new Color(0.165, 0.165, 0.165, 0.8);
    var defaultBackgroundPadding = new Cartesian2(7, 5);
    var defaultPixelOffset = Cartesian2.ZERO;
    var defaultEyeOffset = Cartesian3.ZERO;
    var defaultHeightReference = HeightReference.NONE;
    var defaultHorizontalOrigin = HorizontalOrigin.CENTER;
    var defaultVerticalOrigin = VerticalOrigin.CENTER;

    var position = new Cartesian3();
    var fillColor = new Color();
    var outlineColor = new Color();
    var backgroundColor = new Color();
    var backgroundPadding = new Cartesian2();
    var eyeOffset = new Cartesian3();
    var pixelOffset = new Cartesian2();
    var translucencyByDistance = new NearFarScalar();
    var pixelOffsetScaleByDistance = new NearFarScalar();
    var scaleByDistance = new NearFarScalar();
    var distanceDisplayCondition = new DistanceDisplayCondition();

    function EntityData(entity) {
        this.entity = entity;
        this.label = undefined;
        this.index = undefined;
    }

    /**
     * A {@link Visualizer} which maps the {@link LabelGraphics} instance
     * in {@link Entity#label} to a {@link Label}.
     * @alias LabelVisualizer
     * @constructor
     *
     * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
     * @param {EntityCollection} entityCollection The entityCollection to visualize.
     */
    function LabelVisualizer(entityCluster, entityCollection) {
                if (!defined(entityCluster)) {
            throw new DeveloperError('entityCluster is required.');
        }
        if (!defined(entityCollection)) {
            throw new DeveloperError('entityCollection is required.');
        }
        
        entityCollection.collectionChanged.addEventListener(LabelVisualizer.prototype._onCollectionChanged, this);

        this._cluster = entityCluster;
        this._entityCollection = entityCollection;
        this._items = new AssociativeArray();

        this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
    }

    /**
     * Updates the primitives created by this visualizer to match their
     * Entity counterpart at the given time.
     *
     * @param {JulianDate} time The time to update to.
     * @returns {Boolean} This function always returns true.
     */
    LabelVisualizer.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var items = this._items.values;
        var cluster = this._cluster;

        for (var i = 0, len = items.length; i < len; i++) {
            var item = items[i];
            var entity = item.entity;
            var labelGraphics = entity._label;
            var text;
            var label = item.label;
            var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(labelGraphics._show, time, true);

            if (show) {
                position = Property.getValueOrUndefined(entity._position, time, position);
                text = Property.getValueOrUndefined(labelGraphics._text, time);
                show = defined(position) && defined(text);
            }

            if (!show) {
                //don't bother creating or updating anything else
                returnPrimitive(item, entity, cluster);
                continue;
            }

            if (!Property.isConstant(entity._position)) {
                cluster._clusterDirty = true;
            }

            if (!defined(label)) {
                label = cluster.getLabel(entity);
                label.id = entity;
                item.label = label;
            }

            label.show = true;
            label.position = position;
            label.text = text;
            label.scale = Property.getValueOrDefault(labelGraphics._scale, time, defaultScale);
            label.font = Property.getValueOrDefault(labelGraphics._font, time, defaultFont);
            label.style = Property.getValueOrDefault(labelGraphics._style, time, defaultStyle);
            label.fillColor = Property.getValueOrDefault(labelGraphics._fillColor, time, defaultFillColor, fillColor);
            label.outlineColor = Property.getValueOrDefault(labelGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
            label.outlineWidth = Property.getValueOrDefault(labelGraphics._outlineWidth, time, defaultOutlineWidth);
            label.showBackground = Property.getValueOrDefault(labelGraphics._showBackground, time, defaultShowBackground);
            label.backgroundColor = Property.getValueOrDefault(labelGraphics._backgroundColor, time, defaultBackgroundColor, backgroundColor);
            label.backgroundPadding = Property.getValueOrDefault(labelGraphics._backgroundPadding, time, defaultBackgroundPadding, backgroundPadding);
            label.pixelOffset = Property.getValueOrDefault(labelGraphics._pixelOffset, time, defaultPixelOffset, pixelOffset);
            label.eyeOffset = Property.getValueOrDefault(labelGraphics._eyeOffset, time, defaultEyeOffset, eyeOffset);
            label.heightReference = Property.getValueOrDefault(labelGraphics._heightReference, time, defaultHeightReference);
            label.horizontalOrigin = Property.getValueOrDefault(labelGraphics._horizontalOrigin, time, defaultHorizontalOrigin);
            label.verticalOrigin = Property.getValueOrDefault(labelGraphics._verticalOrigin, time, defaultVerticalOrigin);
            label.translucencyByDistance = Property.getValueOrUndefined(labelGraphics._translucencyByDistance, time, translucencyByDistance);
            label.pixelOffsetScaleByDistance = Property.getValueOrUndefined(labelGraphics._pixelOffsetScaleByDistance, time, pixelOffsetScaleByDistance);
            label.scaleByDistance = Property.getValueOrUndefined(labelGraphics._scaleByDistance, time, scaleByDistance);
            label.distanceDisplayCondition = Property.getValueOrUndefined(labelGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
        }
        return true;
    };

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    LabelVisualizer.prototype.getBoundingSphere = function(entity, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var item = this._items.get(entity.id);
        if (!defined(item) || !defined(item.label)) {
            return BoundingSphereState.FAILED;
        }

        var label = item.label;
        result.center = Cartesian3.clone(defaultValue(label._clampedPosition, label.position), result.center);
        result.radius = 0;
        return BoundingSphereState.DONE;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    LabelVisualizer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes and destroys all primitives created by this instance.
     */
    LabelVisualizer.prototype.destroy = function() {
        this._entityCollection.collectionChanged.removeEventListener(LabelVisualizer.prototype._onCollectionChanged, this);
        var entities = this._entityCollection.values;
        for (var i = 0; i < entities.length; i++) {
            this._cluster.removeLabel(entities[i]);
        }
        return destroyObject(this);
    };

    LabelVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
        var i;
        var entity;
        var items = this._items;
        var cluster = this._cluster;

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            if (defined(entity._label) && defined(entity._position)) {
                items.set(entity.id, new EntityData(entity));
            }
        }

        for (i = changed.length - 1; i > -1; i--) {
            entity = changed[i];
            if (defined(entity._label) && defined(entity._position)) {
                if (!items.contains(entity.id)) {
                    items.set(entity.id, new EntityData(entity));
                }
            } else {
                returnPrimitive(items.get(entity.id), entity, cluster);
                items.remove(entity.id);
            }
        }

        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            returnPrimitive(items.get(entity.id), entity, cluster);
            items.remove(entity.id);
        }
    };

    function returnPrimitive(item, entity, cluster) {
        if (defined(item)) {
            item.label = undefined;
            cluster.removeLabel(entity);
        }
    }

    return LabelVisualizer;
});

/*global define*/
define('ThirdParty/gltfDefaults',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/Quaternion',
        '../Core/WebGLConstants'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        Quaternion,
        WebGLConstants) {
    "use strict";

    function accessorDefaults(gltf) {
        if (!defined(gltf.accessors)) {
            gltf.accessors = {};
        }
        var accessors = gltf.accessors;

        for (var name in accessors) {
            if (accessors.hasOwnProperty(name)) {
                var accessor = accessors[name];
                accessor.byteStride = defaultValue(accessor.byteStride, 0);
            }
        }
    }

    function animationDefaults(gltf) {
        if (!defined(gltf.animations)) {
            gltf.animations = {};
        }
        var animations = gltf.animations;

        for (var name in animations) {
            if (animations.hasOwnProperty(name)) {
                var animation = animations[name];

                if (!defined(animation.channels)) {
                    animation.channels = [];
                }

                if (!defined(animation.parameters)) {
                    animation.parameters = {};
                }

                if (!defined(animation.samplers)) {
                    animation.samplers = {};
                }

                var samplers = animations.samplers;

                for (var samplerName in samplers) {
                    if (samplers.hasOwnProperty(samplerName)) {
                        var sampler = samplers[samplerName];
                        sampler.interpolation = defaultValue(sampler.interpolation, 'LINEAR');
                    }
                }
            }
        }
    }

    function assetDefaults(gltf) {
        if (!defined(gltf.asset)) {
            gltf.asset = {};
        }
        var asset = gltf.asset;

        // Backwards compatibility for glTF 0.8. profile was a string.
        if (!defined(asset.profile) || (typeof asset.profile === 'string')) {
            asset.profile = {};
        }
        var profile = asset.profile;

        asset.premultipliedAlpha = defaultValue(asset.premultipliedAlpha, false);
        profile.api = defaultValue(profile.api, 'WebGL');
        profile.version = defaultValue(profile.version, '1.0.2');

        if (defined(gltf.version)) {
            asset.version = defaultValue(asset.version, gltf.version);
            delete gltf.version;
        }
        if (typeof asset.version === 'number') {
            asset.version = asset.version.toFixed(1).toString();
        }
    }

    function bufferDefaults(gltf) {
        if (!defined(gltf.buffers)) {
            gltf.buffers = {};
        }
        var buffers = gltf.buffers;

        for (var name in buffers) {
            if (buffers.hasOwnProperty(name)) {
                var buffer = buffers[name];
                buffer.type = defaultValue(buffer.type, 'arraybuffer');
            }
        }
    }

    function bufferViewDefaults(gltf) {
        if (!defined(gltf.bufferViews)) {
            gltf.bufferViews = {};
        }
    }

    function cameraDefaults(gltf) {
        if (!defined(gltf.cameras)) {
            gltf.cameras = {};
        }
    }

    function imageDefaults(gltf) {
        if (!defined(gltf.images)) {
            gltf.images = {};
        }
    }

    function lightDefaults(gltf) {
        if (!defined(gltf.extensions)) {
            gltf.extensions = {};
        }
        var extensions = gltf.extensions;

        if (!defined(extensions.KHR_materials_common)) {
            extensions.KHR_materials_common = {};
        }
        var khrMaterialsCommon = extensions.KHR_materials_common;

        if (defined(gltf.lights)) {
            khrMaterialsCommon.lights = gltf.lights;
            delete gltf.lights;
        }
        else if (!defined(khrMaterialsCommon.lights)) {
            khrMaterialsCommon.lights = {};
        }
        var lights = khrMaterialsCommon.lights;

        for (var name in lights) {
            if (lights.hasOwnProperty(name)) {
                var light = lights[name];
                if (light.type === 'ambient') {
                    if (!defined(light.ambient)) {
                        light.ambient = {};
                    }
                    var ambientLight = light.ambient;

                    if (!defined(ambientLight.color)) {
                        ambientLight.color = [1.0, 1.0, 1.0];
                    }
                } else if (light.type === 'directional') {
                    if (!defined(light.directional)) {
                        light.directional = {};
                    }
                    var directionalLight = light.directional;

                    if (!defined(directionalLight.color)) {
                        directionalLight.color = [1.0, 1.0, 1.0];
                    }
                } else if (light.type === 'point') {
                    if (!defined(light.point)) {
                        light.point = {};
                    }
                    var pointLight = light.point;

                    if (!defined(pointLight.color)) {
                        pointLight.color = [1.0, 1.0, 1.0];
                    }

                    pointLight.constantAttenuation = defaultValue(pointLight.constantAttenuation, 1.0);
                    pointLight.linearAttenuation = defaultValue(pointLight.linearAttenuation, 0.0);
                    pointLight.quadraticAttenuation = defaultValue(pointLight.quadraticAttenuation, 0.0);
                } else if (light.type === 'spot') {
                    if (!defined(light.spot)) {
                        light.spot = {};
                    }
                    var spotLight = light.spot;

                    if (!defined(spotLight.color)) {
                        spotLight.color = [1.0, 1.0, 1.0];
                    }

                    spotLight.constantAttenuation = defaultValue(spotLight.constantAttenuation, 1.0);
                    spotLight.fallOffAngle = defaultValue(spotLight.fallOffAngle, 3.14159265);
                    spotLight.fallOffExponent = defaultValue(spotLight.fallOffExponent, 0.0);
                    spotLight.linearAttenuation = defaultValue(spotLight.linearAttenuation, 0.0);
                    spotLight.quadraticAttenuation = defaultValue(spotLight.quadraticAttenuation, 0.0);
                }
            }
        }
    }

    function materialDefaults(gltf) {
        if (!defined(gltf.materials)) {
            gltf.materials = {};
        }
        var materials = gltf.materials;

        for (var name in materials) {
            if (materials.hasOwnProperty(name)) {
                var material = materials[name];
                var instanceTechnique = material.instanceTechnique;
                if (defined(instanceTechnique)) {
                    material.technique = instanceTechnique.technique;
                    material.values = instanceTechnique.values;

                    delete material.instanceTechnique;
                }

                if (!defined(material.extensions)) {
                    if (!defined(material.technique)) {
                        delete material.values;
                        material.extensions = {
                            KHR_materials_common : {
                                technique : 'CONSTANT',
                                transparent: false,
                                values : {
                                    emission : {
                                        type: WebGLConstants.FLOAT_VEC4,
                                        value: [
                                            0.5,
                                            0.5,
                                            0.5,
                                            1
                                        ]
                                    }
                                }
                            }
                        };

                        if (!defined(gltf.extensionsUsed)) {
                            gltf.extensionsUsed = [];
                        }
                        var extensionsUsed = gltf.extensionsUsed;
                        if (extensionsUsed.indexOf('KHR_materials_common') === -1) {
                            extensionsUsed.push('KHR_materials_common');
                        }
                    }
                    else if (!defined(material.values)) {
                        material.values = {};
                    }
                }
            }
        }
    }

    function meshDefaults(gltf) {
        if (!defined(gltf.meshes)) {
            gltf.meshes = {};
        }
        var meshes = gltf.meshes;

        for (var name in meshes) {
            if (meshes.hasOwnProperty(name)) {
                var mesh = meshes[name];

                if (!defined(mesh.primitives)) {
                    mesh.primitives = [];
                }

                var primitives = mesh.primitives.length;
                var length = primitives.length;
                for (var i = 0; i < length; ++i) {
                    var primitive = primitives[i];

                    if (!defined(primitive.attributes)) {
                        primitive.attributes = {};
                    }

                    // Backwards compatibility for glTF 0.8. primitive was renamed to mode.
                    var defaultMode = defaultValue(primitive.primitive, WebGLConstants.TRIANGLES);

                    primitive.mode = defaultValue(primitive.mode, defaultMode);
                }
            }
        }
    }

    function nodeDefaults(gltf) {
        if (!defined(gltf.nodes)) {
            gltf.nodes = {};
        }
        var nodes = gltf.nodes;
        var hasAxisAngle = (parseFloat(gltf.asset.version) < 1.0);

        var axis = new Cartesian3();
        var quat = new Quaternion();
        for (var name in nodes) {
            if (nodes.hasOwnProperty(name)) {
                var node = nodes[name];

                if (!defined(node.children)) {
                    node.children = [];
                }

                if (hasAxisAngle && defined(node.rotation)) {
                    var rotation = node.rotation;
                    Cartesian3.fromArray(rotation, 0, axis);
                    Quaternion.fromAxisAngle(axis, rotation[3], quat);
                    node.rotation = [quat.x, quat.y, quat.z, quat.w];
                }

                if (!defined(node.matrix)) {
                    // Add default identity matrix if there is no matrix property and no TRS properties
                    if (!defined(node.translation) && !defined(node.rotation) && !defined(node.scale)) {
                        node.matrix = [
                            1.0, 0.0, 0.0, 0.0,
                            0.0, 1.0, 0.0, 0.0,
                            0.0, 0.0, 1.0, 0.0,
                            0.0, 0.0, 0.0, 1.0
                        ];
                    } else {
                        if (!defined(node.translation)) {
                            node.translation = [0.0, 0.0, 0.0];
                        }

                        if (!defined(node.rotation)) {
                            node.rotation = [0.0, 0.0, 0.0, 1.0];
                        }

                        if (!defined(node.scale)) {
                            node.scale = [1.0, 1.0, 1.0];
                        }
                    }
                }

                var instanceSkin = node.instanceSkin;
                if (defined(instanceSkin)) {
                    node.skeletons = instanceSkin.skeletons;
                    node.skin = instanceSkin.skin;
                    node.meshes = instanceSkin.meshes;
                    delete node.instanceSkin;
                }
            }
        }
    }

    function programDefaults(gltf) {
        if (!defined(gltf.programs)) {
            gltf.programs = {};
        }
        var programs = gltf.programs;

        for (var name in programs) {
            if (programs.hasOwnProperty(name)) {
                var program = programs[name];
                if (!defined(program.attributes)) {
                    program.attributes = [];
                }
            }
        }
    }

    function samplerDefaults(gltf) {
        if (!defined(gltf.samplers)) {
            gltf.samplers = {};
        }
        var samplers = gltf.samplers;

        for (var name in samplers) {
            if (samplers.hasOwnProperty(name)) {
                var sampler = samplers[name];
                sampler.magFilter = defaultValue(sampler.magFilter, WebGLConstants.LINEAR);
                sampler.minFilter = defaultValue(sampler.minFilter, WebGLConstants.NEAREST_MIPMAP_LINEAR);
                sampler.wrapS = defaultValue(sampler.wrapS, WebGLConstants.REPEAT);
                sampler.wrapT = defaultValue(sampler.wrapT, WebGLConstants.REPEAT);
            }
        }
    }

    function sceneDefaults(gltf) {
        if (!defined(gltf.scenes)) {
            gltf.scenes = {};
        }
        var scenes = gltf.scenes;

        for (var name in scenes) {
            if (scenes.hasOwnProperty(name)) {
                var scene = scenes[name];
                if (!defined(scene.node)) {
                    scene.node = [];
                }
            }
        }
    }

    function shaderDefaults(gltf) {
        if (!defined(gltf.shaders)) {
            gltf.shaders = {};
        }
    }

    function skinDefaults(gltf) {
        if (!defined(gltf.skins)) {
            gltf.skins = {};
        }
        var skins = gltf.skins;

        for (var name in skins) {
            if (skins.hasOwnProperty(name)) {
                var skin = skins[name];
                if (!defined(skin.bindShapeMatrix)) {
                    skin.bindShapeMatrix = [
                        1.0, 0.0, 0.0, 0.0,
                        0.0, 1.0, 0.0, 0.0,
                        0.0, 0.0, 1.0, 0.0,
                        0.0, 0.0, 0.0, 1.0
                    ];
                }
            }
        }
    }

    function statesDefaults(states) {
        if (!defined(states.enable)) {
            states.enable = [];
        }

        if (!defined(states.disable)) {
            states.disable = [];
        }
    }

    function techniqueDefaults(gltf) {
        if (!defined(gltf.techniques)) {
            gltf.techniques = {};
        }
        var techniques = gltf.techniques;

        for (var name in techniques) {
            if (techniques.hasOwnProperty(name)) {
                var technique = techniques[name];
                if (!defined(technique.parameters)) {
                    technique.parameters = {};
                }
                var parameters = technique.parameters;
                for (var parameterName in parameters) {
                    var parameter = parameters[parameterName];
                    parameter.node = defaultValue(parameter.node, parameter.source);
                    parameter.source = undefined;
                }

                var passes = technique.passes;
                if (defined(passes)) {
                    var passName = defaultValue(technique.pass, 'defaultPass');
                    if (passes.hasOwnProperty(passName)) {
                        var pass = passes[passName];
                        var instanceProgram = pass.instanceProgram;

                        technique.attributes = defaultValue(technique.attributes, instanceProgram.attributes);
                        technique.program = defaultValue(technique.program, instanceProgram.program);
                        technique.uniforms = defaultValue(technique.uniforms, instanceProgram.uniforms);

                        technique.states = defaultValue(technique.states, pass.states);
                    }

                    technique.passes = undefined;
                    technique.pass = undefined;
                }

                if (!defined(technique.attributes)) {
                    technique.attributes = {};
                }

                if (!defined(technique.uniforms)) {
                    technique.uniforms = {};
                }

                if (!defined(technique.states)) {
                    technique.states = {};
                }
                statesDefaults(technique.states);
            }
        }
    }

    function textureDefaults(gltf) {
        if (!defined(gltf.textures)) {
            gltf.textures = {};
        }
        var textures = gltf.textures;

        for (var name in textures) {
            if (textures.hasOwnProperty(name)) {
                var texture = textures[name];
                texture.format = defaultValue(texture.format, WebGLConstants.RGBA);
                texture.internalFormat = defaultValue(texture.internalFormat, texture.format);
                texture.target = defaultValue(texture.target, WebGLConstants.TEXTURE_2D);
                texture.type = defaultValue(texture.type, WebGLConstants.UNSIGNED_BYTE);
            }
        }
    }

    /**
     * Modifies gltf in place.
     *
     * @private
     */
    var gltfDefaults = function(gltf) {
        if (!defined(gltf)) {
            return undefined;
        }

        if (defined(gltf.allExtensions)) {
            gltf.extensionsUsed = gltf.allExtensions;
            gltf.allExtensions = undefined;
        }
        gltf.extensionsUsed = defaultValue(gltf.extensionsUsed, []);

        accessorDefaults(gltf);
        animationDefaults(gltf);
        assetDefaults(gltf);
        bufferDefaults(gltf);
        bufferViewDefaults(gltf);
        cameraDefaults(gltf);
        imageDefaults(gltf);
        lightDefaults(gltf);
        materialDefaults(gltf);
        meshDefaults(gltf);
        nodeDefaults(gltf);
        programDefaults(gltf);
        samplerDefaults(gltf);
        sceneDefaults(gltf);
        shaderDefaults(gltf);
        skinDefaults(gltf);
        techniqueDefaults(gltf);
        textureDefaults(gltf);

        return gltf;
    };

    return gltfDefaults;
});

/*global define*/
define('Scene/Axis',[
        '../Core/Check',
        '../Core/freezeObject',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4'
], function(
        Check,
        freezeObject,
        CesiumMath,
        Matrix3,
        Matrix4) {
    'use strict';

    /**
     * An enum describing the x, y, and z axes and helper conversion functions.
     *
     * @exports Axis
     * @private
     */
    var Axis = {
        /**
         * Denotes the x-axis.
         *
         * @type {Number}
         * @constant
         */
        X : 0,

        /**
         * Denotes the y-axis.
         *
         * @type {Number}
         * @constant
         */
        Y : 1,

        /**
         * Denotes the z-axis.
         *
         * @type {Number}
         * @constant
         */
        Z : 2,

        /**
         * Matrix used to convert from y-up to z-up
         *
         * @type {Matrix4}
         * @constant
         */
        Y_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(CesiumMath.PI_OVER_TWO)),

        /**
         * Matrix used to convert from z-up to y-up
         *
         * @type {Matrix4}
         * @constant
         */
        Z_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationX(-CesiumMath.PI_OVER_TWO)),

        /**
         * Matrix used to convert from x-up to z-up
         *
         * @type {Matrix4}
         * @constant
         */
        X_UP_TO_Z_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(-CesiumMath.PI_OVER_TWO)),

        /**
         * Matrix used to convert from z-up to x-up
         *
         * @type {Matrix4}
         * @constant
         */
        Z_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationY(CesiumMath.PI_OVER_TWO)),

        /**
         * Matrix used to convert from x-up to y-up
         *
         * @type {Matrix4}
         * @constant
         */
        X_UP_TO_Y_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(CesiumMath.PI_OVER_TWO)),

        /**
         * Matrix used to convert from y-up to x-up
         *
         * @type {Matrix4}
         * @constant
         */
        Y_UP_TO_X_UP : Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(-CesiumMath.PI_OVER_TWO)),

        /**
         * Gets the axis by name
         *
         * @param {String} name The name of the axis.
         * @returns {Number} The axis enum.
         */
        fromName : function(name) {
                        Check.typeOf.string('name', name);
            
            return Axis[name];
        }
    };

    return freezeObject(Axis);
});

/*global define*/
define('Scene/getAttributeOrUniformBySemantic',[], function() {
    'use strict';

    /**
     * Return the uniform or attribute that has the given semantic.
     *
     * @private
     */
    function getAttributeOrUniformBySemantic(gltf, semantic) {
        var techniques = gltf.techniques;
        for (var techniqueName in techniques) {
            if (techniques.hasOwnProperty(techniqueName)) {
                var technique = techniques[techniqueName];
                var parameters = technique.parameters;
                var attributes = technique.attributes;
                var uniforms = technique.uniforms;
                for (var attributeName in attributes) {
                    if (attributes.hasOwnProperty(attributeName)) {
                        if (parameters[attributes[attributeName]].semantic === semantic) {
                            return attributeName;
                        }
                    }
                }
                for (var uniformName in uniforms) {
                    if (uniforms.hasOwnProperty(uniformName)) {
                        if (parameters[uniforms[uniformName]].semantic === semantic) {
                            return uniformName;
                        }
                    }
                }
            }
        }
        return undefined;
    }

    return getAttributeOrUniformBySemantic;
});

/*global define*/
define('Scene/getBinaryAccessor',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/ComponentDatatype',
        '../Core/Matrix2',
        '../Core/Matrix3',
        '../Core/Matrix4'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartesian4,
        ComponentDatatype,
        Matrix2,
        Matrix3,
        Matrix4) {
    'use strict';

    var ComponentsPerAttribute = {
        SCALAR : 1,
        VEC2 : 2,
        VEC3 : 3,
        VEC4 : 4,
        MAT2 : 4,
        MAT3 : 9,
        MAT4 : 16
    };

    var ClassPerType = {
        SCALAR : undefined,
        VEC2 : Cartesian2,
        VEC3 : Cartesian3,
        VEC4 : Cartesian4,
        MAT2 : Matrix2,
        MAT3 : Matrix3,
        MAT4 : Matrix4
    };

    /**
     * @private
     */
    function getBinaryAccessor(accessor) {
        var componentType = accessor.componentType;
        var componentDatatype;
        if (typeof componentType === 'string') {
            componentDatatype = ComponentDatatype.fromName(componentType);
        } else {
            componentDatatype = componentType;
        }

        var componentsPerAttribute = ComponentsPerAttribute[accessor.type];
        var classType = ClassPerType[accessor.type];
        return {
            componentsPerAttribute : componentsPerAttribute,
            classType : classType,
            createArrayBufferView : function(buffer, byteOffset, length) {
                return ComponentDatatype.createArrayBufferView(componentDatatype, buffer, byteOffset, componentsPerAttribute * length);
            }
        };
    }

    return getBinaryAccessor;
});

/*global define*/
define('Scene/ModelAnimationCache',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/LinearSpline',
        '../Core/Matrix4',
        '../Core/Quaternion',
        '../Core/QuaternionSpline',
        '../Core/WebGLConstants',
        './getBinaryAccessor'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        LinearSpline,
        Matrix4,
        Quaternion,
        QuaternionSpline,
        WebGLConstants,
        getBinaryAccessor) {
    'use strict';

    /**
     * @private
     */
    function ModelAnimationCache() {
    }

    function getAccessorKey(model, accessor) {
        var gltf = model.gltf;
        var buffers = gltf.buffers;
        var bufferViews = gltf.bufferViews;

        var bufferView = bufferViews[accessor.bufferView];
        var buffer = buffers[bufferView.buffer];

        var byteOffset = bufferView.byteOffset + accessor.byteOffset;
        var byteLength = accessor.count * getBinaryAccessor(accessor).componentsPerAttribute;

        // buffer.path will be undefined when animations are embedded.
        return model.cacheKey + '//' + defaultValue(buffer.path, '') + '/' + byteOffset + '/' + byteLength;
    }

    var cachedAnimationParameters = {
    };

    var axisScratch = new Cartesian3();

    ModelAnimationCache.getAnimationParameterValues = function(model, accessor) {
        var key = getAccessorKey(model, accessor);
        var values = cachedAnimationParameters[key];

        if (!defined(values)) {
            // Cache miss
            var loadResources = model._loadResources;
            var gltf = model.gltf;
            var hasAxisAngle = (parseFloat(gltf.asset.version) < 1.0);

            var bufferViews = gltf.bufferViews;

            var bufferView = bufferViews[accessor.bufferView];

            var componentType = accessor.componentType;
            var type = accessor.type;
            var count = accessor.count;

            // Convert typed array to Cesium types
            var buffer = loadResources.getBuffer(bufferView);
            var typedArray = getBinaryAccessor(accessor).createArrayBufferView(buffer.buffer, buffer.byteOffset + accessor.byteOffset, count);
            var i;

            if ((componentType === WebGLConstants.FLOAT) && (type === 'SCALAR')) {
                values = typedArray;
            }
            else if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC3')) {
                values = new Array(count);
                for (i = 0; i < count; ++i) {
                    values[i] = Cartesian3.fromArray(typedArray, 3 * i);
                }
            } else if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC4')) {
                values = new Array(count);
                for (i = 0; i < count; ++i) {
                    var byteOffset = 4 * i;
                    if (hasAxisAngle) {
                        values[i] = Quaternion.fromAxisAngle(Cartesian3.fromArray(typedArray, byteOffset, axisScratch), typedArray[byteOffset + 3]);
                    }
                    else {
                        values[i] = Quaternion.unpack(typedArray, byteOffset);
                    }
                }
            }
            // GLTF_SPEC: Support more parameter types when glTF supports targeting materials. https://github.com/KhronosGroup/glTF/issues/142

            if (defined(model.cacheKey)) {
                // Only cache when we can create a unique id
                cachedAnimationParameters[key] = values;
            }
        }

        return values;
    };

    var cachedAnimationSplines = {
    };

    function getAnimationSplineKey(model, animationName, samplerName) {
        return model.cacheKey + '//' + animationName + '/' + samplerName;
    }

 // GLTF_SPEC: https://github.com/KhronosGroup/glTF/issues/185
    function ConstantSpline(value) {
        this._value = value;
    }
    ConstantSpline.prototype.evaluate = function(time, result) {
        return this._value;
    };
 // END GLTF_SPEC

    ModelAnimationCache.getAnimationSpline = function(model, animationName, animation, samplerName, sampler, parameterValues) {
        var key = getAnimationSplineKey(model, animationName, samplerName);
        var spline = cachedAnimationSplines[key];

        if (!defined(spline)) {
            var times = parameterValues[sampler.input];
            var accessor = model.gltf.accessors[animation.parameters[sampler.output]];
            var controlPoints = parameterValues[sampler.output];

// GLTF_SPEC: https://github.com/KhronosGroup/glTF/issues/185
            if ((times.length === 1) && (controlPoints.length === 1)) {
                spline = new ConstantSpline(controlPoints[0]);
            } else {
// END GLTF_SPEC
                var componentType = accessor.componentType;
                var type = accessor.type;

                if (sampler.interpolation === 'LINEAR') {
                    if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC3')) {
                        spline = new LinearSpline({
                            times : times,
                            points : controlPoints
                        });
                    } else if ((componentType === WebGLConstants.FLOAT) && (type === 'VEC4')) {
                        spline = new QuaternionSpline({
                            times : times,
                            points : controlPoints
                        });
                    }
                    // GLTF_SPEC: Support more parameter types when glTF supports targeting materials. https://github.com/KhronosGroup/glTF/issues/142
                }
                // GLTF_SPEC: Support new interpolators. https://github.com/KhronosGroup/glTF/issues/156
            }

            if (defined(model.cacheKey)) {
                // Only cache when we can create a unique id
                cachedAnimationSplines[key] = spline;
            }
        }

        return spline;
    };

    var cachedSkinInverseBindMatrices = {
    };

    ModelAnimationCache.getSkinInverseBindMatrices = function(model, accessor) {
        var key = getAccessorKey(model, accessor);
        var matrices = cachedSkinInverseBindMatrices[key];

        if (!defined(matrices)) {
            // Cache miss

            var loadResources = model._loadResources;
            var gltf = model.gltf;
            var bufferViews = gltf.bufferViews;

            var bufferView = bufferViews[accessor.bufferView];

            var componentType = accessor.componentType;
            var type = accessor.type;
            var count = accessor.count;
            var buffer = loadResources.getBuffer(bufferView);
            var typedArray = getBinaryAccessor(accessor).createArrayBufferView(buffer.buffer, buffer.byteOffset + accessor.byteOffset, count);
            matrices =  new Array(count);

            if ((componentType === WebGLConstants.FLOAT) && (type === 'MAT4')) {
                for (var i = 0; i < count; ++i) {
                    matrices[i] = Matrix4.fromArray(typedArray, 16 * i);
                }
            }

            cachedSkinInverseBindMatrices[key] = matrices;
        }

        return matrices;
    };

    return ModelAnimationCache;
});

/*global define*/
define('Scene/ModelAnimationLoop',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Determines if and how a glTF animation is looped.
     *
     * @exports ModelAnimationLoop
     *
     * @see ModelAnimationCollection#add
     */
    var ModelAnimationLoop = {
        /**
         * Play the animation once; do not loop it.
         *
         * @type {Number}
         * @constant
         */
        NONE : 0,

        /**
         * Loop the animation playing it from the start immediately after it stops.
         *
         * @type {Number}
         * @constant
         */
        REPEAT : 1,

        /**
         * Loop the animation.  First, playing it forward, then in reverse, then forward, and so on.
         *
         * @type {Number}
         * @constant
         */
        MIRRORED_REPEAT : 2
    };

    return freezeObject(ModelAnimationLoop);
});

/*global define*/
define('Scene/ModelAnimationState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * @private
     */
    return freezeObject({
        STOPPED : 0,
        ANIMATING : 1
    });
});

/*global define*/
define('Scene/ModelAnimation',[
        '../Core/defaultValue',
        '../Core/defineProperties',
        '../Core/Event',
        '../Core/JulianDate',
        './ModelAnimationLoop',
        './ModelAnimationState'
    ], function(
        defaultValue,
        defineProperties,
        Event,
        JulianDate,
        ModelAnimationLoop,
        ModelAnimationState) {
    'use strict';

    /**
     * An active glTF animation.  A glTF asset can contain animations.  An active animation
     * is an animation that is currently playing or scheduled to be played because it was
     * added to a model's {@link ModelAnimationCollection}.  An active animation is an
     * instance of an animation; for example, there can be multiple active animations
     * for the same glTF animation, each with a different start time.
     * <p>
     * Create this by calling {@link ModelAnimationCollection#add}.
     * </p>
     *
     * @alias ModelAnimation
     * @internalConstructor
     *
     * @see ModelAnimationCollection#add
     */
    function ModelAnimation(options, model, runtimeAnimation) {
        this._name = options.name;
        this._startTime = JulianDate.clone(options.startTime);
        this._delay = defaultValue(options.delay, 0.0); // in seconds
        this._stopTime = options.stopTime;

        /**
         * When <code>true</code>, the animation is removed after it stops playing.
         * This is slightly more efficient that not removing it, but if, for example,
         * time is reversed, the animation is not played again.
         *
         * @type {Boolean}
         * @default false
         */
        this.removeOnStop = defaultValue(options.removeOnStop, false);

        this._speedup = defaultValue(options.speedup, 1.0);
        this._reverse = defaultValue(options.reverse, false);
        this._loop = defaultValue(options.loop, ModelAnimationLoop.NONE);

        /**
         * The event fired when this animation is started.  This can be used, for
         * example, to play a sound or start a particle system, when the animation starts.
         * <p>
         * This event is fired at the end of the frame after the scene is rendered.
         * </p>
         *
         * @type {Event}
         * @default new Event()
         *
         * @example
         * animation.start.addEventListener(function(model, animation) {
         *   console.log('Animation started: ' + animation.name);
         * });
         */
        this.start = new Event();

        /**
         * The event fired when on each frame when this animation is updated.  The
         * current time of the animation, relative to the glTF animation time span, is
         * passed to the event, which allows, for example, starting new animations at a
         * specific time relative to a playing animation.
         * <p>
         * This event is fired at the end of the frame after the scene is rendered.
         * </p>
         *
         * @type {Event}
         * @default new Event()
         *
         * @example
         * animation.update.addEventListener(function(model, animation, time) {
         *   console.log('Animation updated: ' + animation.name + '. glTF animation time: ' + time);
         * });
         */
        this.update = new Event();

        /**
         * The event fired when this animation is stopped.  This can be used, for
         * example, to play a sound or start a particle system, when the animation stops.
         * <p>
         * This event is fired at the end of the frame after the scene is rendered.
         * </p>
         *
         * @type {Event}
         * @default new Event()
         *
         * @example
         * animation.stop.addEventListener(function(model, animation) {
         *   console.log('Animation stopped: ' + animation.name);
         * });
         */
        this.stop = new Event();

        this._state = ModelAnimationState.STOPPED;
        this._runtimeAnimation = runtimeAnimation;

        // Set during animation update
        this._computedStartTime = undefined;
        this._duration = undefined;

        // To avoid allocations in ModelAnimationCollection.update
        var that = this;
        this._raiseStartEvent = function() {
            that.start.raiseEvent(model, that);
        };
        this._updateEventTime = 0.0;
        this._raiseUpdateEvent = function() {
            that.update.raiseEvent(model, that, that._updateEventTime);
        };
        this._raiseStopEvent = function() {
            that.stop.raiseEvent(model, that);
        };
    }

    defineProperties(ModelAnimation.prototype, {
        /**
         * The glTF animation name that identifies this animation.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {String}
         * @readonly
         */
        name : {
            get : function() {
                return this._name;
            }
        },

        /**
         * The scene time to start playing this animation.  When this is <code>undefined</code>,
         * the animation starts at the next frame.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {JulianDate}
         * @readonly
         *
         * @default undefined
         */
        startTime : {
            get : function() {
                return this._startTime;
            }
        },

        /**
         * The delay, in seconds, from {@link ModelAnimation#startTime} to start playing.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @default undefined
         */
        delay : {
            get : function() {
                return this._delay;
            }
        },

        /**
         * The scene time to stop playing this animation.  When this is <code>undefined</code>,
         * the animation is played for its full duration and perhaps repeated depending on
         * {@link ModelAnimation#loop}.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {JulianDate}
         * @readonly
         *
         * @default undefined
         */
        stopTime : {
            get : function() {
                return this._stopTime;
            }
        },

        /**
         * Values greater than <code>1.0</code> increase the speed that the animation is played relative
         * to the scene clock speed; values less than <code>1.0</code> decrease the speed.  A value of
         * <code>1.0</code> plays the animation at the speed in the glTF animation mapped to the scene
         * clock speed.  For example, if the scene is played at 2x real-time, a two-second glTF animation
         * will play in one second even if <code>speedup</code> is <code>1.0</code>.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @default 1.0
         */
        speedup : {
            get : function() {
                return this._speedup;
            }
        },

        /**
         * When <code>true</code>, the animation is played in reverse.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        reverse : {
            get : function() {
                return this._reverse;
            }
        },

        /**
         * Determines if and how the animation is looped.
         *
         * @memberof ModelAnimation.prototype
         *
         * @type {ModelAnimationLoop}
         * @readonly
         *
         * @default {@link ModelAnimationLoop.NONE}
         */
        loop : {
            get : function() {
                return this._loop;
            }
        }
    });

    return ModelAnimation;
});

/*global define*/
define('Scene/ModelAnimationCollection',[
        '../Core/clone',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/JulianDate',
        '../Core/Math',
        './ModelAnimation',
        './ModelAnimationLoop',
        './ModelAnimationState'
    ], function(
        clone,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        JulianDate,
        CesiumMath,
        ModelAnimation,
        ModelAnimationLoop,
        ModelAnimationState) {
    'use strict';

    /**
     * A collection of active model animations.  Access this using {@link Model#activeAnimations}.
     *
     * @alias ModelAnimationCollection
     * @internalConstructor
     *
     * @see Model#activeAnimations
     */
    function ModelAnimationCollection(model) {
        /**
         * The event fired when an animation is added to the collection.  This can be used, for
         * example, to keep a UI in sync.
         *
         * @type {Event}
         * @default new Event()
         *
         * @example
         * model.activeAnimations.animationAdded.addEventListener(function(model, animation) {
         *   console.log('Animation added: ' + animation.name);
         * });
         */
        this.animationAdded = new Event();

        /**
         * The event fired when an animation is removed from the collection.  This can be used, for
         * example, to keep a UI in sync.
         *
         * @type {Event}
         * @default new Event()
         *
         * @example
         * model.activeAnimations.animationRemoved.addEventListener(function(model, animation) {
         *   console.log('Animation removed: ' + animation.name);
         * });
         */
        this.animationRemoved = new Event();

        this._model = model;
        this._scheduledAnimations = [];
        this._previousTime = undefined;
    }

    defineProperties(ModelAnimationCollection.prototype, {
        /**
         * The number of animations in the collection.
         *
         * @memberof ModelAnimationCollection.prototype
         *
         * @type {Number}
         * @readonly
         */
        length : {
            get : function() {
                return this._scheduledAnimations.length;
            }
        }
    });

    /**
     * Creates and adds an animation with the specified initial properties to the collection.
     * <p>
     * This raises the {@link ModelAnimationCollection#animationAdded} event so, for example, a UI can stay in sync.
     * </p>
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.name The glTF animation name that identifies the animation.
     * @param {JulianDate} [options.startTime] The scene time to start playing the animation.  When this is <code>undefined</code>, the animation starts at the next frame.
     * @param {Number} [options.delay=0.0] The delay, in seconds, from <code>startTime</code> to start playing.
     * @param {JulianDate} [options.stopTime] The scene time to stop playing the animation.  When this is <code>undefined</code>, the animation is played for its full duration.
     * @param {Boolean} [options.removeOnStop=false] When <code>true</code>, the animation is removed after it stops playing.
     * @param {Number} [options.speedup=1.0] Values greater than <code>1.0</code> increase the speed that the animation is played relative to the scene clock speed; values less than <code>1.0</code> decrease the speed.
     * @param {Boolean} [options.reverse=false] When <code>true</code>, the animation is played in reverse.
     * @param {ModelAnimationLoop} [options.loop=ModelAnimationLoop.NONE] Determines if and how the animation is looped.
     * @returns {ModelAnimation} The animation that was added to the collection.
     *
     * @exception {DeveloperError} Animations are not loaded.  Wait for the {@link Model#readyPromise} to resolve.
     * @exception {DeveloperError} options.name must be a valid animation name.
     * @exception {DeveloperError} options.speedup must be greater than zero.
     *
     * @example
     * // Example 1. Add an animation
     * model.activeAnimations.add({
     *   name : 'animation name'
     * });
     *
     * @example
     * // Example 2. Add an animation and provide all properties and events
     * var startTime = Cesium.JulianDate.now();
     *
     * var animation = model.activeAnimations.add({
     *   name : 'another animation name',
     *   startTime : startTime,
     *   delay : 0.0,                          // Play at startTime (default)
     *   stopTime : Cesium.JulianDate.addSeconds(startTime, 4.0, new Cesium.JulianDate()),
     *   removeOnStop : false,                 // Do not remove when animation stops (default)
     *   speedup : 2.0,                        // Play at double speed
     *   reverse : true,                       // Play in reverse
     *   loop : Cesium.ModelAnimationLoop.REPEAT      // Loop the animation
     * });
     *
     * animation.start.addEventListener(function(model, animation) {
     *   console.log('Animation started: ' + animation.name);
     * });
     * animation.update.addEventListener(function(model, animation, time) {
     *   console.log('Animation updated: ' + animation.name + '. glTF animation time: ' + time);
     * });
     * animation.stop.addEventListener(function(model, animation) {
     *   console.log('Animation stopped: ' + animation.name);
     * });
     */
    ModelAnimationCollection.prototype.add = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var model = this._model;
        var animations = model._runtime.animations;

                if (!defined(animations)) {
            throw new DeveloperError('Animations are not loaded.  Wait for Model.readyPromise to resolve.');
        }
        
        var animation = animations[options.name];

                if (!defined(animation)) {
            throw new DeveloperError('options.name must be a valid animation name.');
        }

        if (defined(options.speedup) && (options.speedup <= 0.0)) {
            throw new DeveloperError('options.speedup must be greater than zero.');
        }
        
        var scheduledAnimation = new ModelAnimation(options, model, animation);
        this._scheduledAnimations.push(scheduledAnimation);
        this.animationAdded.raiseEvent(model, scheduledAnimation);
        return scheduledAnimation;
    };

    /**
     * Creates and adds an animation with the specified initial properties to the collection
     * for each animation in the model.
     * <p>
     * This raises the {@link ModelAnimationCollection#animationAdded} event for each model so, for example, a UI can stay in sync.
     * </p>
     *
     * @param {Object} [options] Object with the following properties:
     * @param {JulianDate} [options.startTime] The scene time to start playing the animations.  When this is <code>undefined</code>, the animations starts at the next frame.
     * @param {Number} [options.delay=0.0] The delay, in seconds, from <code>startTime</code> to start playing.
     * @param {JulianDate} [options.stopTime] The scene time to stop playing the animations.  When this is <code>undefined</code>, the animations are played for its full duration.
     * @param {Boolean} [options.removeOnStop=false] When <code>true</code>, the animations are removed after they stop playing.
     * @param {Number} [options.speedup=1.0] Values greater than <code>1.0</code> increase the speed that the animations play relative to the scene clock speed; values less than <code>1.0</code> decrease the speed.
     * @param {Boolean} [options.reverse=false] When <code>true</code>, the animations are played in reverse.
     * @param {ModelAnimationLoop} [options.loop=ModelAnimationLoop.NONE] Determines if and how the animations are looped.
     * @returns {ModelAnimation[]} An array of {@link ModelAnimation} objects, one for each animation added to the collection.  If there are no glTF animations, the array is empty.
     *
     * @exception {DeveloperError} Animations are not loaded.  Wait for the {@link Model#readyPromise} to resolve.
     * @exception {DeveloperError} options.speedup must be greater than zero.
     *
     * @example
     * model.activeAnimations.addAll({
     *   speedup : 0.5,                        // Play at half-speed
     *   loop : Cesium.ModelAnimationLoop.REPEAT      // Loop the animations
     * });
     */
    ModelAnimationCollection.prototype.addAll = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(this._model._runtime.animations)) {
            throw new DeveloperError('Animations are not loaded.  Wait for Model.readyPromise to resolve.');
        }

        if (defined(options.speedup) && (options.speedup <= 0.0)) {
            throw new DeveloperError('options.speedup must be greater than zero.');
        }
        
        options = clone(options);

        var scheduledAnimations = [];
        var animationIds = this._model._animationIds;
        var length = animationIds.length;
        for (var i = 0; i < length; ++i) {
            options.name = animationIds[i];
            scheduledAnimations.push(this.add(options));
        }

        return scheduledAnimations;
    };

    /**
     * Removes an animation from the collection.
     * <p>
     * This raises the {@link ModelAnimationCollection#animationRemoved} event so, for example, a UI can stay in sync.
     * </p>
     * <p>
     * An animation can also be implicitly removed from the collection by setting {@link ModelAnimation#removeOnStop} to
     * <code>true</code>.  The {@link ModelAnimationCollection#animationRemoved} event is still fired when the animation is removed.
     * </p>
     *
     * @param {ModelAnimation} animation The animation to remove.
     * @returns {Boolean} <code>true</code> if the animation was removed; <code>false</code> if the animation was not found in the collection.
     *
     * @example
     * var a = model.activeAnimations.add({
     *   name : 'animation name'
     * });
     * model.activeAnimations.remove(a); // Returns true
     */
    ModelAnimationCollection.prototype.remove = function(animation) {
        if (defined(animation)) {
            var animations = this._scheduledAnimations;
            var i = animations.indexOf(animation);
            if (i !== -1) {
                animations.splice(i, 1);
                this.animationRemoved.raiseEvent(this._model, animation);
                return true;
            }
        }

        return false;
    };

    /**
     * Removes all animations from the collection.
     * <p>
     * This raises the {@link ModelAnimationCollection#animationRemoved} event for each
     * animation so, for example, a UI can stay in sync.
     * </p>
     */
    ModelAnimationCollection.prototype.removeAll = function() {
        var model = this._model;
        var animations = this._scheduledAnimations;
        var length = animations.length;

        this._scheduledAnimations = [];

        for (var i = 0; i < length; ++i) {
            this.animationRemoved.raiseEvent(model, animations[i]);
        }
    };

    /**
     * Determines whether this collection contains a given animation.
     *
     * @param {ModelAnimation} animation The animation to check for.
     * @returns {Boolean} <code>true</code> if this collection contains the animation, <code>false</code> otherwise.
     */
    ModelAnimationCollection.prototype.contains = function(animation) {
        if (defined(animation)) {
            return (this._scheduledAnimations.indexOf(animation) !== -1);
        }

        return false;
    };

    /**
     * Returns the animation in the collection at the specified index.  Indices are zero-based
     * and increase as animations are added.  Removing an animation shifts all animations after
     * it to the left, changing their indices.  This function is commonly used to iterate over
     * all the animations in the collection.
     *
     * @param {Number} index The zero-based index of the animation.
     * @returns {ModelAnimation} The animation at the specified index.
     *
     * @example
     * // Output the names of all the animations in the collection.
     * var animations = model.activeAnimations;
     * var length = animations.length;
     * for (var i = 0; i < length; ++i) {
     *   console.log(animations.get(i).name);
     * }
     */
    ModelAnimationCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._scheduledAnimations[index];
    };

    function animateChannels(runtimeAnimation, localAnimationTime) {
        var channelEvaluators = runtimeAnimation.channelEvaluators;
        var length = channelEvaluators.length;
        for (var i = 0; i < length; ++i) {
            channelEvaluators[i](localAnimationTime);
        }
    }

    var animationsToRemove = [];

    function createAnimationRemovedFunction(modelAnimationCollection, model, animation) {
        return function() {
            modelAnimationCollection.animationRemoved.raiseEvent(model, animation);
        };
    }

    /**
     * @private
     */
    ModelAnimationCollection.prototype.update = function(frameState) {
        var scheduledAnimations = this._scheduledAnimations;
        var length = scheduledAnimations.length;

        if (length === 0) {
            // No animations - quick return for performance
            this._previousTime = undefined;
            return false;
        }

        if (JulianDate.equals(frameState.time, this._previousTime)) {
            // Animations are currently only time-dependent so do not animate when paused or picking
            return false;
        }
        this._previousTime = JulianDate.clone(frameState.time, this._previousTime);

        var animationOccured = false;
        var sceneTime = frameState.time;
        var model = this._model;

        for (var i = 0; i < length; ++i) {
            var scheduledAnimation = scheduledAnimations[i];
            var runtimeAnimation = scheduledAnimation._runtimeAnimation;

            if (!defined(scheduledAnimation._computedStartTime)) {
                scheduledAnimation._computedStartTime = JulianDate.addSeconds(defaultValue(scheduledAnimation.startTime, sceneTime), scheduledAnimation.delay, new JulianDate());
            }

            if (!defined(scheduledAnimation._duration)) {
                scheduledAnimation._duration = runtimeAnimation.stopTime * (1.0 / scheduledAnimation.speedup);
            }

            var startTime = scheduledAnimation._computedStartTime;
            var duration = scheduledAnimation._duration;
            var stopTime = scheduledAnimation.stopTime;

            // [0.0, 1.0] normalized local animation time
            var delta = (duration !== 0.0) ? (JulianDate.secondsDifference(sceneTime, startTime) / duration) : 0.0;
            var pastStartTime = (delta >= 0.0);

            // Play animation if
            // * we are after the start time or the animation is being repeated, and
            // * before the end of the animation's duration or the animation is being repeated, and
            // * we did not reach a user-provided stop time.

            var repeat = ((scheduledAnimation.loop === ModelAnimationLoop.REPEAT) ||
                          (scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT));

            var play = (pastStartTime || (repeat && !defined(scheduledAnimation.startTime))) &&
                       ((delta <= 1.0) || repeat) &&
                       (!defined(stopTime) || JulianDate.lessThanOrEquals(sceneTime, stopTime));

            if (play) {
                // STOPPED -> ANIMATING state transition?
                if (scheduledAnimation._state === ModelAnimationState.STOPPED) {
                    scheduledAnimation._state = ModelAnimationState.ANIMATING;
                    if (scheduledAnimation.start.numberOfListeners > 0) {
                        frameState.afterRender.push(scheduledAnimation._raiseStartEvent);
                    }
                }

                // Truncate to [0.0, 1.0] for repeating animations
                if (scheduledAnimation.loop === ModelAnimationLoop.REPEAT) {
                    delta = delta - Math.floor(delta);
                } else if (scheduledAnimation.loop === ModelAnimationLoop.MIRRORED_REPEAT) {
                    var floor = Math.floor(delta);
                    var fract = delta - floor;
                    // When even use (1.0 - fract) to mirror repeat
                    delta = (floor % 2 === 1.0) ? (1.0 - fract) : fract;
                }

                if (scheduledAnimation.reverse) {
                    delta = 1.0 - delta;
                }

                var localAnimationTime = delta * duration * scheduledAnimation.speedup;
                // Clamp in case floating-point roundoff goes outside the animation's first or last keyframe
                localAnimationTime = CesiumMath.clamp(localAnimationTime, runtimeAnimation.startTime, runtimeAnimation.stopTime);

                animateChannels(runtimeAnimation, localAnimationTime);

                if (scheduledAnimation.update.numberOfListeners > 0) {
                    scheduledAnimation._updateEventTime = localAnimationTime;
                    frameState.afterRender.push(scheduledAnimation._raiseUpdateEvent);
                }
                animationOccured = true;
            } else {
                // ANIMATING -> STOPPED state transition?
                if (pastStartTime && (scheduledAnimation._state === ModelAnimationState.ANIMATING)) {
                    scheduledAnimation._state = ModelAnimationState.STOPPED;
                    if (scheduledAnimation.stop.numberOfListeners > 0) {
                        frameState.afterRender.push(scheduledAnimation._raiseStopEvent);
                    }

                    if (scheduledAnimation.removeOnStop) {
                        animationsToRemove.push(scheduledAnimation);
                    }
                }
            }
        }

        // Remove animations that stopped
        length = animationsToRemove.length;
        for (var j = 0; j < length; ++j) {
            var animationToRemove = animationsToRemove[j];
            scheduledAnimations.splice(scheduledAnimations.indexOf(animationToRemove), 1);
            frameState.afterRender.push(createAnimationRemovedFunction(this, model, animationToRemove));
        }
        animationsToRemove.length = 0;

        return animationOccured;
    };

    return ModelAnimationCollection;
});

/*global define*/
define('Scene/ModelMaterial',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError'
    ], function(
        defined,
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * A model's material with modifiable parameters.  A glTF material
     * contains parameters defined by the material's technique with values
     * defined by the technique and potentially overridden by the material.
     * This class allows changing these values at runtime.
     * <p>
     * Use {@link Model#getMaterial} to create an instance.
     * </p>
     *
     * @alias ModelMaterial
     * @internalConstructor
     *
     * @see Model#getMaterial
     */
    function ModelMaterial(model, material, id) {
        this._name = material.name;
        this._id = id;
        this._uniformMap = model._uniformMaps[id];
    }

    defineProperties(ModelMaterial.prototype, {
        /**
         * The value of the <code>name</code> property of this material.  This is the
         * name assigned by the artist when the asset is created.  This can be
         * different than the name of the material property ({@link ModelMaterial#id}),
         * which is internal to glTF.
         *
         * @memberof ModelMaterial.prototype
         *
         * @type {String}
         * @readonly
         */
        name : {
            get : function() {
                return this._name;
            }
        },

        /**
         * The name of the glTF JSON property for this material.  This is guaranteed
         * to be unique among all materials.  It may not match the material's <code>
         * name</code> property (@link ModelMaterial#name), which is assigned by
         * the artist when the asset is created.
         *
         * @memberof ModelMaterial.prototype
         *
         * @type {String}
         * @readonly
         */
        id : {
            get : function() {
                return this._id;
            }
        }
    });

    /**
     * Assigns a value to a material parameter.  The type for <code>value</code>
     * depends on the glTF type of the parameter.  It will be a floating-point
     * number, Cartesian, or matrix.
     *
     * @param {String} name The name of the parameter.
     * @param {Object} [value] The value to assign to the parameter.
     *
     * @exception {DeveloperError} name must match a parameter name in the material's technique that is targetable and not optimized out.
     *
     * @example
     * material.setValue('diffuse', new Cesium.Cartesian4(1.0, 0.0, 0.0, 1.0));  // vec4
     * material.setValue('shininess', 256.0);                             // scalar
     */
    ModelMaterial.prototype.setValue = function(name, value) {
                if (!defined(name)) {
            throw new DeveloperError('name is required.');
        }
        
        var v = this._uniformMap.values[name];

                if (!defined(v)) {
            throw new DeveloperError('name must match a parameter name in the material\'s technique that is targetable and not optimized out.');
        }
        
        v.value = v.clone(value, v.value);
    };

    /**
     * Returns the value of the parameter with the given <code>name</code>.  The type of the
     * returned object depends on the glTF type of the parameter.  It will be a floating-point
     * number, Cartesian, or matrix.
     *
     * @param {String} name The name of the parameter.
     * @returns {Object} The value of the parameter or <code>undefined</code> if the parameter does not exist.
     */
    ModelMaterial.prototype.getValue = function(name) {
                if (!defined(name)) {
            throw new DeveloperError('name is required.');
        }
        
        var v = this._uniformMap.values[name];

        if (!defined(v)) {
            return undefined;
        }

        return v.value;
    };

    return ModelMaterial;
});

/*global define*/
define('Scene/modelMaterialsCommon',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/WebGLConstants'
    ], function(
        defaultValue,
        defined,
        WebGLConstants) {
    'use strict';

    function webGLConstantToGlslType(webGLValue) {
        switch(webGLValue) {
            case WebGLConstants.FLOAT:
                return 'float';
            case WebGLConstants.FLOAT_VEC2:
                return 'vec2';
            case WebGLConstants.FLOAT_VEC3:
                return 'vec3';
            case WebGLConstants.FLOAT_VEC4:
                return 'vec4';
            case WebGLConstants.FLOAT_MAT2:
                return 'mat2';
            case WebGLConstants.FLOAT_MAT3:
                return 'mat3';
            case WebGLConstants.FLOAT_MAT4:
                return 'mat4';
            case WebGLConstants.SAMPLER_2D:
                return 'sampler2D';
        }
    }

    function generateLightParameters(gltf) {
        var result = {};

        var lights;
        if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) {
            lights = gltf.extensions.KHR_materials_common.lights;
        }

        if (defined(lights)) {
            // Figure out which node references the light
            var nodes = gltf.nodes;
            for (var nodeName in nodes) {
                if (nodes.hasOwnProperty(nodeName)) {
                    var node = nodes[nodeName];
                    if (defined(node.extensions) && defined(node.extensions.KHR_materials_common)) {
                        var nodeLightId = node.extensions.KHR_materials_common.light;
                        if (defined(nodeLightId) && defined(lights[nodeLightId])) {
                            lights[nodeLightId].node = nodeName;
                        }
                        delete node.extensions.KHR_materials_common;
                    }
                }
            }

            // Add light parameters to result
            var lightCount = 0;
            for(var lightName in lights) {
                if (lights.hasOwnProperty(lightName)) {
                    var light = lights[lightName];
                    var lightType = light.type;
                    if ((lightType !== 'ambient') && !defined(light.node)) {
                        delete lights[lightName];
                        continue;
                    }
                    var lightBaseName = 'light' + lightCount.toString();
                    light.baseName = lightBaseName;
                    switch(lightType) {
                        case 'ambient':
                            var ambient = light.ambient;
                            result[lightBaseName + 'Color'] = {
                                type: WebGLConstants.FLOAT_VEC3,
                                value: ambient.color
                            };
                            break;
                        case 'directional':
                            var directional = light.directional;
                            result[lightBaseName + 'Color'] =
                            {
                                type: WebGLConstants.FLOAT_VEC3,
                                value: directional.color
                            };
                            if (defined(light.node)) {
                                result[lightBaseName + 'Transform'] =
                                {
                                    node: light.node,
                                    semantic: 'MODELVIEW',
                                    type: WebGLConstants.FLOAT_MAT4
                                };
                            }
                            break;
                        case 'point':
                            var point = light.point;
                            result[lightBaseName + 'Color'] =
                            {
                                type: WebGLConstants.FLOAT_VEC3,
                                value: point.color
                            };
                            if (defined(light.node)) {
                                result[lightBaseName + 'Transform'] =
                                {
                                    node: light.node,
                                    semantic: 'MODELVIEW',
                                    type: WebGLConstants.FLOAT_MAT4
                                };
                            }
                            result[lightBaseName + 'Attenuation'] =
                            {
                                type: WebGLConstants.FLOAT_VEC3,
                                value: [point.constantAttenuation, point.linearAttenuation, point.quadraticAttenuation]
                            };
                            break;
                        case 'spot':
                            var spot = light.spot;
                            result[lightBaseName + 'Color'] =
                            {
                                type: WebGLConstants.FLOAT_VEC3,
                                value: spot.color
                            };
                            if (defined(light.node)) {
                                result[lightBaseName + 'Transform'] =
                                {
                                    node: light.node,
                                    semantic: 'MODELVIEW',
                                    type: WebGLConstants.FLOAT_MAT4
                                };
                                result[lightBaseName + 'InverseTransform'] = {
                                    node: light.node,
                                    semantic: 'MODELVIEWINVERSE',
                                    type: WebGLConstants.FLOAT_MAT4,
                                    useInFragment: true
                                };
                            }
                            result[lightBaseName + 'Attenuation'] =
                            {
                                type: WebGLConstants.FLOAT_VEC3,
                                value: [spot.constantAttenuation, spot.linearAttenuation, spot.quadraticAttenuation]
                            };

                            result[lightBaseName + 'FallOff'] =
                            {
                                type: WebGLConstants.FLOAT_VEC2,
                                value: [spot.fallOffAngle, spot.fallOffExponent]
                            };
                            break;
                    }
                    ++lightCount;
                }
            }
        }

        return result;
    }

    function getNextId(dictionary, baseName, startingCount) {
        var count = defaultValue(startingCount, 0);
        var nextId;
        do {
            nextId = baseName + (count++).toString();
        } while(defined(dictionary[nextId]));

        return nextId;
    }

    var techniqueCount = 0;
    var vertexShaderCount = 0;
    var fragmentShaderCount = 0;
    var programCount = 0;
    function generateTechnique(gltf, khrMaterialsCommon, lightParameters) {
        var techniques = gltf.techniques;
        var shaders = gltf.shaders;
        var programs = gltf.programs;
        var lightingModel = khrMaterialsCommon.technique.toUpperCase();
        var lights;
        if (defined(gltf.extensions) && defined(gltf.extensions.KHR_materials_common)) {
            lights = gltf.extensions.KHR_materials_common.lights;
        }
        var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0);
        var hasSkinning = (jointCount > 0);
        var parameterValues = khrMaterialsCommon.values;

        var vertexShader = 'precision highp float;\n';
        var fragmentShader = 'precision highp float;\n';

        // Generate IDs for our new objects
        var techniqueId = getNextId(techniques, 'technique', techniqueCount);
        var vertexShaderId = getNextId(shaders, 'vertexShader', vertexShaderCount);
        var fragmentShaderId = getNextId(shaders, 'fragmentShader', fragmentShaderCount);
        var programId = getNextId(programs, 'program', programCount);

        var hasNormals = (lightingModel !== 'CONSTANT');

        // Add techniques
        var techniqueParameters = {
            // Add matrices
            modelViewMatrix: {
                semantic: 'MODELVIEW',
                type: WebGLConstants.FLOAT_MAT4
            },
            projectionMatrix: {
                semantic: 'PROJECTION',
                type: WebGLConstants.FLOAT_MAT4
            }
        };

        if (hasNormals) {
            techniqueParameters.normalMatrix = {
                semantic: 'MODELVIEWINVERSETRANSPOSE',
                type: WebGLConstants.FLOAT_MAT3
            };
        }

        if (hasSkinning) {
            techniqueParameters.jointMatrix = {
                count: jointCount,
                semantic: 'JOINTMATRIX',
                type: WebGLConstants.FLOAT_MAT4
            };
        }

        // Add material parameters
        var lowerCase;
        var hasTexCoords = false;
        for(var name in parameterValues) {
            //generate shader parameters for KHR_materials_common attributes
            //(including a check, because some boolean flags should not be used as shader parameters)
            if (parameterValues.hasOwnProperty(name) && (name !== 'transparent') && (name !== 'doubleSided')) {
                var valType = getKHRMaterialsCommonValueType(name, parameterValues[name]);
                lowerCase = name.toLowerCase();
                if (!hasTexCoords && (valType === WebGLConstants.SAMPLER_2D)) {
                    hasTexCoords = true;
                }
                techniqueParameters[lowerCase] = {
                    type: valType
                };
            }
        }

        // Copy light parameters into technique parameters
        if (defined(lightParameters)) {
            for (var lightParamName in lightParameters) {
                if (lightParameters.hasOwnProperty(lightParamName)) {
                    techniqueParameters[lightParamName] = lightParameters[lightParamName];
                }
            }
        }

        // Generate uniforms object before attributes are added
        var techniqueUniforms = {};
        for (var paramName in techniqueParameters) {
            if (techniqueParameters.hasOwnProperty(paramName)) {
                var param = techniqueParameters[paramName];
                techniqueUniforms['u_' + paramName] = paramName;
                var arraySize = defined(param.count) ? '['+param.count+']' : '';
                if (((param.type !== WebGLConstants.FLOAT_MAT3) && (param.type !== WebGLConstants.FLOAT_MAT4)) ||
                    param.useInFragment) {
                    fragmentShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n';
                    delete param.useInFragment;
                }
                else {
                    vertexShader += 'uniform ' + webGLConstantToGlslType(param.type) + ' u_' + paramName + arraySize + ';\n';
                }
            }
        }

        // Add attributes with semantics
        var vertexShaderMain = '';
        if (hasSkinning) {
            vertexShaderMain += '  mat4 skinMat = a_weight.x * u_jointMatrix[int(a_joint.x)];\n';
            vertexShaderMain += '  skinMat += a_weight.y * u_jointMatrix[int(a_joint.y)];\n';
            vertexShaderMain += '  skinMat += a_weight.z * u_jointMatrix[int(a_joint.z)];\n';
            vertexShaderMain += '  skinMat += a_weight.w * u_jointMatrix[int(a_joint.w)];\n';
        }

        // Add position always
        var techniqueAttributes = {
            a_position: 'position'
        };
        techniqueParameters.position = {
            semantic: 'POSITION',
            type: WebGLConstants.FLOAT_VEC3
        };
        vertexShader += 'attribute vec3 a_position;\n';
        vertexShader += 'varying vec3 v_positionEC;\n';
        if (hasSkinning) {
            vertexShaderMain += '  vec4 pos = u_modelViewMatrix * skinMat * vec4(a_position,1.0);\n';
        }
        else {
            vertexShaderMain += '  vec4 pos = u_modelViewMatrix * vec4(a_position,1.0);\n';
        }
        vertexShaderMain += '  v_positionEC = pos.xyz;\n';
        vertexShaderMain += '  gl_Position = u_projectionMatrix * pos;\n';
        fragmentShader += 'varying vec3 v_positionEC;\n';

        // Add normal if we don't have constant lighting
        if (hasNormals) {
            techniqueAttributes.a_normal = 'normal';
            techniqueParameters.normal = {
                semantic: 'NORMAL',
                type: WebGLConstants.FLOAT_VEC3
            };
            vertexShader += 'attribute vec3 a_normal;\n';
            vertexShader += 'varying vec3 v_normal;\n';
            if (hasSkinning) {
                vertexShaderMain += '  v_normal = u_normalMatrix * mat3(skinMat) * a_normal;\n';
            }
            else {
                vertexShaderMain += '  v_normal = u_normalMatrix * a_normal;\n';
            }

            fragmentShader += 'varying vec3 v_normal;\n';
        }

        // Add texture coordinates if the material uses them
        var v_texcoord;
        if (hasTexCoords) {
            techniqueAttributes.a_texcoord_0 = 'texcoord_0';
            techniqueParameters.texcoord_0 = {
                semantic: 'TEXCOORD_0',
                type: WebGLConstants.FLOAT_VEC2
            };

            v_texcoord = 'v_texcoord_0';
            vertexShader += 'attribute vec2 a_texcoord_0;\n';
            vertexShader += 'varying vec2 ' + v_texcoord + ';\n';
            vertexShaderMain += '  ' + v_texcoord + ' = a_texcoord_0;\n';

            fragmentShader += 'varying vec2 ' + v_texcoord + ';\n';
        }

        if (hasSkinning) {
            techniqueAttributes.a_joint = 'joint';
            techniqueParameters.joint = {
                semantic: 'JOINT',
                type: WebGLConstants.FLOAT_VEC4
            };
            techniqueAttributes.a_weight = 'weight';
            techniqueParameters.weight = {
                semantic: 'WEIGHT',
                type: WebGLConstants.FLOAT_VEC4
            };

            vertexShader += 'attribute vec4 a_joint;\n';
            vertexShader += 'attribute vec4 a_weight;\n';
        }

        var hasSpecular = hasNormals && ((lightingModel === 'BLINN') || (lightingModel === 'PHONG')) &&
                          defined(techniqueParameters.specular) && defined(techniqueParameters.shininess);

        // Generate lighting code blocks
        var hasNonAmbientLights = false;
        var hasAmbientLights = false;
        var fragmentLightingBlock = '';
        for (var lightName in lights) {
            if (lights.hasOwnProperty(lightName)) {
                var light = lights[lightName];
                var lightType = light.type.toLowerCase();
                var lightBaseName = light.baseName;
                fragmentLightingBlock += '  {\n';
                var lightColorName = 'u_' + lightBaseName + 'Color';
                var varyingDirectionName;
                var varyingPositionName;
                if(lightType === 'ambient') {
                    hasAmbientLights = true;
                    fragmentLightingBlock += '    ambientLight += ' + lightColorName + ';\n';
                }
                else if (hasNormals) {
                    hasNonAmbientLights = true;
                    varyingDirectionName = 'v_' + lightBaseName + 'Direction';
                    varyingPositionName = 'v_' + lightBaseName + 'Position';

                    if (lightType !== 'point') {
                        vertexShader += 'varying vec3 ' + varyingDirectionName + ';\n';
                        fragmentShader += 'varying vec3 ' + varyingDirectionName + ';\n';

                        vertexShaderMain += '  ' + varyingDirectionName + ' = mat3(u_' + lightBaseName + 'Transform) * vec3(0.,0.,1.);\n';
                        if (lightType === 'directional') {
                            fragmentLightingBlock += '    vec3 l = normalize(' + varyingDirectionName + ');\n';
                        }
                    }

                    if (lightType !== 'directional') {
                        vertexShader += 'varying vec3 ' + varyingPositionName + ';\n';
                        fragmentShader += 'varying vec3 ' + varyingPositionName + ';\n';

                        vertexShaderMain += '  ' + varyingPositionName + ' = u_' + lightBaseName + 'Transform[3].xyz;\n';
                        fragmentLightingBlock += '    vec3 VP = ' + varyingPositionName + ' - v_positionEC;\n';
                        fragmentLightingBlock += '    vec3 l = normalize(VP);\n';
                        fragmentLightingBlock += '    float range = length(VP);\n';
                        fragmentLightingBlock += '    float attenuation = 1.0 / (u_' + lightBaseName + 'Attenuation.x + ';
                        fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.y * range) + ';
                        fragmentLightingBlock += '(u_' + lightBaseName + 'Attenuation.z * range * range));\n';
                    }
                    else {
                        fragmentLightingBlock += '    float attenuation = 1.0;\n';
                    }

                    if (lightType === 'spot') {
                        fragmentLightingBlock += '    float spotDot = dot(l, normalize(' + varyingDirectionName + '));\n';
                        fragmentLightingBlock += '    if (spotDot < cos(u_' + lightBaseName + 'FallOff.x * 0.5))\n';
                        fragmentLightingBlock += '    {\n';
                        fragmentLightingBlock += '      attenuation = 0.0;\n';
                        fragmentLightingBlock += '    }\n';
                        fragmentLightingBlock += '    else\n';
                        fragmentLightingBlock += '    {\n';
                        fragmentLightingBlock += '        attenuation *= max(0.0, pow(spotDot, u_' + lightBaseName + 'FallOff.y));\n';
                        fragmentLightingBlock += '    }\n';
                    }

                    fragmentLightingBlock += '    diffuseLight += ' + lightColorName + '* max(dot(normal,l), 0.) * attenuation;\n';

                    if (hasSpecular) {
                        if (lightingModel === 'BLINN') {
                            fragmentLightingBlock += '    vec3 h = normalize(l + viewDir);\n';
                            fragmentLightingBlock += '    float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess)) * attenuation;\n';
                        }
                        else { // PHONG
                            fragmentLightingBlock += '    vec3 reflectDir = reflect(-l, normal);\n';
                            fragmentLightingBlock += '    float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess)) * attenuation;\n';
                        }
                        fragmentLightingBlock += '    specularLight += ' + lightColorName + ' * specularIntensity;\n';
                    }
                }
                fragmentLightingBlock += '  }\n';
            }
        }

        if (!hasAmbientLights) {
            // Add an ambient light if we don't have one
            fragmentLightingBlock += '  ambientLight += vec3(0.2, 0.2, 0.2);\n';
        }

        if (!hasNonAmbientLights && (lightingModel !== 'CONSTANT')) {
            fragmentLightingBlock += '  vec3 l = normalize(czm_sunDirectionEC);\n';
            fragmentLightingBlock += '  diffuseLight += vec3(1.0, 1.0, 1.0) * max(dot(normal,l), 0.);\n';

            if (hasSpecular) {
                if (lightingModel === 'BLINN') {
                    fragmentLightingBlock += '  vec3 h = normalize(l + viewDir);\n';
                    fragmentLightingBlock += '  float specularIntensity = max(0., pow(max(dot(normal, h), 0.), u_shininess));\n';
                }
                else { // PHONG
                    fragmentLightingBlock += '  vec3 reflectDir = reflect(-l, normal);\n';
                    fragmentLightingBlock += '  float specularIntensity = max(0., pow(max(dot(reflectDir, viewDir), 0.), u_shininess));\n';
                }

                fragmentLightingBlock += '  specularLight += vec3(1.0, 1.0, 1.0) * specularIntensity;\n';
            }
        }

        vertexShader += 'void main(void) {\n';
        vertexShader += vertexShaderMain;
        vertexShader += '}\n';

        fragmentShader += 'void main(void) {\n';
        var colorCreationBlock = '  vec3 color = vec3(0.0, 0.0, 0.0);\n';
        if (hasNormals) {
            fragmentShader += '  vec3 normal = normalize(v_normal);\n';
            if (khrMaterialsCommon.doubleSided) {
                fragmentShader += '  if (gl_FrontFacing == false)\n';
                fragmentShader += '  {\n';
                fragmentShader += '    normal = -normal;\n';
                fragmentShader += '  }\n';
            }
        }

        var finalColorComputation;
        if (lightingModel !== 'CONSTANT') {
            if (defined(techniqueParameters.diffuse)) {
                if (techniqueParameters.diffuse.type === WebGLConstants.SAMPLER_2D) {
                    fragmentShader += '  vec4 diffuse = texture2D(u_diffuse, ' + v_texcoord + ');\n';
                }
                else {
                    fragmentShader += '  vec4 diffuse = u_diffuse;\n';
                }
                fragmentShader += '  vec3 diffuseLight = vec3(0.0, 0.0, 0.0);\n';
                colorCreationBlock += '  color += diffuse.rgb * diffuseLight;\n';
            }

            if (hasSpecular) {
                if (techniqueParameters.specular.type === WebGLConstants.SAMPLER_2D) {
                    fragmentShader += '  vec3 specular = texture2D(u_specular, ' + v_texcoord + ').rgb;\n';
                }
                else {
                    fragmentShader += '  vec3 specular = u_specular.rgb;\n';
                }
                fragmentShader += '  vec3 specularLight = vec3(0.0, 0.0, 0.0);\n';
                colorCreationBlock += '  color += specular * specularLight;\n';
            }

            if (defined(techniqueParameters.transparency)) {
                finalColorComputation = '  gl_FragColor = vec4(color * diffuse.a, diffuse.a * u_transparency);\n';
            }
            else {
                finalColorComputation = '  gl_FragColor = vec4(color * diffuse.a, diffuse.a);\n';
            }
        }
        else {
            if (defined(techniqueParameters.transparency)) {
                finalColorComputation = '  gl_FragColor = vec4(color, u_transparency);\n';
            }
            else {
                finalColorComputation = '  gl_FragColor = vec4(color, 1.0);\n';
            }
        }

        if (defined(techniqueParameters.emission)) {
            if (techniqueParameters.emission.type === WebGLConstants.SAMPLER_2D) {
                fragmentShader += '  vec3 emission = texture2D(u_emission, ' + v_texcoord + ').rgb;\n';
            }
            else {
                fragmentShader += '  vec3 emission = u_emission.rgb;\n';
            }
            colorCreationBlock += '  color += emission;\n';
        }

        if (defined(techniqueParameters.ambient) || (lightingModel !== 'CONSTANT')) {
            if (defined(techniqueParameters.ambient)) {
                if (techniqueParameters.ambient.type === WebGLConstants.SAMPLER_2D) {
                    fragmentShader += '  vec3 ambient = texture2D(u_ambient, ' + v_texcoord + ').rgb;\n';
                }
                else {
                    fragmentShader += '  vec3 ambient = u_ambient.rgb;\n';
                }
            }
            else {
                fragmentShader += '  vec3 ambient = diffuse.rgb;\n';
            }
            colorCreationBlock += '  color += ambient * ambientLight;\n';
        }
        fragmentShader += '  vec3 viewDir = -normalize(v_positionEC);\n';
        fragmentShader += '  vec3 ambientLight = vec3(0.0, 0.0, 0.0);\n';

        // Add in light computations
        fragmentShader += fragmentLightingBlock;

        fragmentShader += colorCreationBlock;
        fragmentShader += finalColorComputation;
        fragmentShader += '}\n';

        var techniqueStates;
        if (khrMaterialsCommon.transparent) {
            techniqueStates = {
                enable: [
                    WebGLConstants.DEPTH_TEST,
                    WebGLConstants.BLEND
                ],
                depthMask: false,
                functions: {
                    blendEquationSeparate: [
                        WebGLConstants.FUNC_ADD,
                        WebGLConstants.FUNC_ADD
                    ],
                    blendFuncSeparate: [
                        WebGLConstants.ONE,
                        WebGLConstants.ONE_MINUS_SRC_ALPHA,
                        WebGLConstants.ONE,
                        WebGLConstants.ONE_MINUS_SRC_ALPHA
                    ]
                }
            };
        }
        else if (khrMaterialsCommon.doubleSided) {
            techniqueStates = {
                enable: [
                    WebGLConstants.DEPTH_TEST
                ]
            };
        }
        else { // Not transparent or double sided
            techniqueStates = {
                enable: [
                    WebGLConstants.CULL_FACE,
                    WebGLConstants.DEPTH_TEST
                ]
            };
        }
        techniques[techniqueId] = {
            attributes: techniqueAttributes,
            parameters: techniqueParameters,
            program: programId,
            states: techniqueStates,
            uniforms: techniqueUniforms
        };

        // Add shaders
        shaders[vertexShaderId] = {
            type: WebGLConstants.VERTEX_SHADER,
            uri: '',
            extras: {
                source: vertexShader
            }
        };
        shaders[fragmentShaderId] = {
            type: WebGLConstants.FRAGMENT_SHADER,
            uri: '',
            extras: {
                source: fragmentShader
            }
        };

        // Add program
        var programAttributes = Object.keys(techniqueAttributes);
        programs[programId] = {
            attributes: programAttributes,
            fragmentShader: fragmentShaderId,
            vertexShader: vertexShaderId
        };

        return techniqueId;
    }

    function getKHRMaterialsCommonValueType(paramName, paramValue)
    {
        var value;

        // Backwards compatibility for COLLADA2GLTF v1.0-draft when it encoding
        // materials using KHR_materials_common with explicit type/value members
        if (defined(paramValue.value)) {
            value = paramValue.value;
        } else {
            value = paramValue;
        }

        switch (paramName)  {
            case 'ambient':
                return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
            case 'diffuse':
                return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
            case 'emission':
                return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
            case 'specular':
                return (value instanceof String || typeof value === 'string') ? WebGLConstants.SAMPLER_2D : WebGLConstants.FLOAT_VEC4;
            case 'shininess':
                return WebGLConstants.FLOAT;
            case 'transparency':
                return WebGLConstants.FLOAT;

            // these two are usually not used directly within shaders,
            // they are just added here for completeness
            case 'transparent':
                return WebGLConstants.BOOL;
            case 'doubleSided':
                return WebGLConstants.BOOL;
        }
    }

    function getTechniqueKey(khrMaterialsCommon) {
        var techniqueKey = '';
        techniqueKey += 'technique:' + khrMaterialsCommon.technique + ';';

        var values = khrMaterialsCommon.values;
        var keys = Object.keys(values).sort();
        var keysCount = keys.length;
        for (var i=0;i<keysCount;++i) {
            var name = keys[i];
            //generate first part of key using shader parameters for KHR_materials_common attributes
            //(including a check, because some boolean flags should not be used as shader parameters)
            if (values.hasOwnProperty(name) && (name !== 'transparent') && (name !== 'doubleSided')) {
                techniqueKey += name + ':' + getKHRMaterialsCommonValueType(name, values[name]);
                techniqueKey += ';';
            }
        }

        var doubleSided = defaultValue(khrMaterialsCommon.doubleSided, false);
        techniqueKey += doubleSided.toString() + ';';
        var transparent = defaultValue(khrMaterialsCommon.transparent, false);
        techniqueKey += transparent.toString() + ';';
        var jointCount = defaultValue(khrMaterialsCommon.jointCount, 0);
        techniqueKey += jointCount.toString() + ';';

        return techniqueKey;
    }

    /**
     * Modifies gltf in place.
     *
     * @private
     */
    function modelMaterialsCommon(gltf) {
        if (!defined(gltf)) {
            return undefined;
        }

        var hasExtension = false;
        var extensionsUsed = gltf.extensionsUsed;
        if (defined(extensionsUsed)) {
            var extensionsUsedCount = extensionsUsed.length;
            for(var i=0;i<extensionsUsedCount;++i) {
                if (extensionsUsed[i] === 'KHR_materials_common') {
                    hasExtension = true;
                    extensionsUsed.splice(i, 1);
                    break;
                }
            }
        }

        if (hasExtension) {
            if (!defined(gltf.programs)) {
                gltf.programs = {};
            }
            if (!defined(gltf.shaders)) {
                gltf.shaders = {};
            }
            if (!defined(gltf.techniques)) {
                gltf.techniques = {};
            }

            var lightParameters = generateLightParameters(gltf);

            var techniques = {};
            var materials = gltf.materials;
            for (var name in materials) {
                if (materials.hasOwnProperty(name)) {
                    var material = materials[name];
                    if (defined(material.extensions) && defined(material.extensions.KHR_materials_common)) {
                        var khrMaterialsCommon = material.extensions.KHR_materials_common;
                        var techniqueKey = getTechniqueKey(khrMaterialsCommon);
                        var technique = techniques[techniqueKey];
                        if (!defined(technique)) {
                            technique = generateTechnique(gltf, khrMaterialsCommon, lightParameters);
                            techniques[techniqueKey] = technique;
                        }

                        // Take advantage of the fact that we generate techniques that use the
                        // same parameter names as the extension values.
                        material.values = {};
                        var values = khrMaterialsCommon.values;
                        for (var valueName in values) {
                            if (values.hasOwnProperty(valueName)) {
                                var value = values[valueName];

                                // Backwards compatibility for COLLADA2GLTF v1.0-draft when it encoding
                                // materials using KHR_materials_common with explicit type/value members
                                if (defined(value.value)) {
                                    material.values[valueName] = value.value;
                                } else {
                                    material.values[valueName] = value;
                                }
                            }
                        }

                        material.technique = technique;

                        delete material.extensions.KHR_materials_common;
                    }
                }
            }

            if (defined(gltf.extensions)) {
                delete gltf.extensions.KHR_materials_common;
            }
        }

        return gltf;
    }

    return modelMaterialsCommon;
});

/*global define*/
define('Scene/ModelMesh',[
        '../Core/defineProperties'
    ], function(
        defineProperties) {
    'use strict';

    /**
     * A model's mesh and its materials.
     * <p>
     * Use {@link Model#getMesh} to create an instance.
     * </p>
     *
     * @alias ModelMesh
     * @internalConstructor
     *
     * @see Model#getMesh
     */
    function ModelMesh(mesh, runtimeMaterialsById, id) {
        var materials = [];
        var primitives = mesh.primitives;
        var length = primitives.length;
        for (var i = 0; i < length; ++i) {
            var p = primitives[i];
            materials[i] = runtimeMaterialsById[p.material];
        }

        this._name = mesh.name;
        this._materials = materials;
        this._id = id;
    }

    defineProperties(ModelMesh.prototype, {
        /**
         * The value of the <code>name</code> property of this mesh.  This is the
         * name assigned by the artist when the asset is created.  This can be
         * different than the name of the mesh property ({@link ModelMesh#id}),
         * which is internal to glTF.
         *
         * @memberof ModelMesh.prototype
         *
         * @type {String}
         * @readonly
         */
        name : {
            get : function() {
                return this._name;
            }
        },

        /**
         * The name of the glTF JSON property for this mesh.  This is guaranteed
         * to be unique among all meshes.  It may not match the mesh's <code>
         * name</code> property (@link ModelMesh#name), which is assigned by
         * the artist when the asset is created.
         *
         * @memberof ModelMesh.prototype
         *
         * @type {String}
         * @readonly
         */
        id : {
            get : function() {
                return this._id;
            }
        },

        /**
         * An array of {@link ModelMaterial} instances indexed by the mesh's
         * primitive indices.
         *
         * @memberof ModelMesh.prototype
         *
         * @type {ModelMaterial[]}
         * @readonly
         */
        materials : {
            get : function() {
                return this._materials;
            }
        }
    });

    return ModelMesh;
});

/*global define*/
define('Scene/ModelNode',[
        '../Core/defineProperties',
        '../Core/Matrix4'
    ], function(
        defineProperties,
        Matrix4) {
    'use strict';

    /**
     * A model node with a transform for user-defined animations.  A glTF asset can
     * contain animations that target a node's transform.  This class allows
     * changing a node's transform externally so animation can be driven by another
     * source, not just an animation in the glTF asset.
     * <p>
     * Use {@link Model#getNode} to create an instance.
     * </p>
     *
     * @alias ModelNode
     * @internalConstructor
     *
     *
     * @example
     * var node = model.getNode('LOD3sp');
     * node.matrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(5.0, 1.0, 1.0), node.matrix);
     *
     * @see Model#getNode
     */
    function ModelNode(model, node, runtimeNode, id, matrix) {
        this._model = model;
        this._runtimeNode = runtimeNode;
        this._name = node.name;
        this._id = id;

        /**
         * @private
         */
        this.useMatrix = false;

        this._show = true;
        this._matrix = Matrix4.clone(matrix);
    }

    defineProperties(ModelNode.prototype, {
        /**
         * The value of the <code>name</code> property of this node.  This is the
         * name assigned by the artist when the asset is created.  This can be
         * different than the name of the node property ({@link ModelNode#id}),
         * which is internal to glTF.
         *
         * @memberof ModelNode.prototype
         *
         * @type {String}
         * @readonly
         */
        name : {
            get : function() {
                return this._name;
            }
        },

        /**
         * The name of the glTF JSON property for this node.  This is guaranteed
         * to be unique among all nodes.  It may not match the node's <code>
         * name</code> property (@link ModelNode#name), which is assigned by
         * the artist when the asset is created.
         *
         * @memberof ModelNode.prototype
         *
         * @type {String}
         * @readonly
         */
        id : {
            get : function() {
                return this._id;
            }
        },

        /**
         * Determines if this node and its children will be shown.
         *
         * @memberof ModelNode.prototype
         * @type {Boolean}
         *
         * @default true
         */
        show : {
            get : function() {
                return this._show;
            },
            set : function(value) {
                if (this._show !== value) {
                    this._show = value;
                    this._model._perNodeShowDirty = true;
                }
            }
        },

        /**
         * The node's 4x4 matrix transform from its local coordinates to
         * its parent's.
         * <p>
         * For changes to take effect, this property must be assigned to;
         * setting individual elements of the matrix will not work.
         * </p>
         *
         * @memberof ModelNode.prototype
         * @type {Matrix4}
         */
        matrix : {
            get : function() {
                return this._matrix;
            },
            set : function(value) {
                this._matrix = Matrix4.clone(value, this._matrix);
                this.useMatrix = true;

                var model = this._model;
                model._cesiumAnimationsDirty = true;
                this._runtimeNode.dirtyNumber = model._maxDirtyNumber;
            }
        }
    });

    /**
     * @private
     */
    ModelNode.prototype.setMatrix = function(matrix) {
        // Update matrix but do not set the dirty flag since this is used internally
        // to keep the matrix in-sync during a glTF animation.
        Matrix4.clone(matrix, this._matrix);
    };

    return ModelNode;
});

/*global define*/
define('Scene/Model',[
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/clone',
        '../Core/Color',
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/FeatureDetection',
        '../Core/getAbsoluteUri',
        '../Core/getBaseUri',
        '../Core/getMagic',
        '../Core/getStringFromTypedArray',
        '../Core/IndexDatatype',
        '../Core/loadArrayBuffer',
        '../Core/loadCRN',
        '../Core/loadImage',
        '../Core/loadImageFromTypedArray',
        '../Core/loadKTX',
        '../Core/loadText',
        '../Core/Math',
        '../Core/Matrix2',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/PixelFormat',
        '../Core/PrimitiveType',
        '../Core/Quaternion',
        '../Core/Queue',
        '../Core/RuntimeError',
        '../Core/Transforms',
        '../Core/WebGLConstants',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/Sampler',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/Texture',
        '../Renderer/TextureMinificationFilter',
        '../Renderer/TextureWrap',
        '../Renderer/VertexArray',
        '../ThirdParty/gltfDefaults',
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        './Axis',
        './BlendingState',
        './ColorBlendMode',
        './getAttributeOrUniformBySemantic',
        './getBinaryAccessor',
        './HeightReference',
        './ModelAnimationCache',
        './ModelAnimationCollection',
        './ModelMaterial',
        './modelMaterialsCommon',
        './ModelMesh',
        './ModelNode',
        './SceneMode',
        './ShadowMode'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        clone,
        Color,
        combine,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        FeatureDetection,
        getAbsoluteUri,
        getBaseUri,
        getMagic,
        getStringFromTypedArray,
        IndexDatatype,
        loadArrayBuffer,
        loadCRN,
        loadImage,
        loadImageFromTypedArray,
        loadKTX,
        loadText,
        CesiumMath,
        Matrix2,
        Matrix3,
        Matrix4,
        PixelFormat,
        PrimitiveType,
        Quaternion,
        Queue,
        RuntimeError,
        Transforms,
        WebGLConstants,
        Buffer,
        BufferUsage,
        DrawCommand,
        Pass,
        RenderState,
        Sampler,
        ShaderProgram,
        ShaderSource,
        Texture,
        TextureMinificationFilter,
        TextureWrap,
        VertexArray,
        gltfDefaults,
        Uri,
        when,
        Axis,
        BlendingState,
        ColorBlendMode,
        getAttributeOrUniformBySemantic,
        getBinaryAccessor,
        HeightReference,
        ModelAnimationCache,
        ModelAnimationCollection,
        ModelMaterial,
        modelMaterialsCommon,
        ModelMesh,
        ModelNode,
        SceneMode,
        ShadowMode) {
    'use strict';

    // Bail out if the browser doesn't support typed arrays, to prevent the setup function
    // from failing, since we won't be able to create a WebGL context anyway.
    if (!FeatureDetection.supportsTypedArrays()) {
        return {};
    }

    var boundingSphereCartesian3Scratch = new Cartesian3();

    var ModelState = {
        NEEDS_LOAD : 0,
        LOADING : 1,
        LOADED : 2,  // Renderable, but textures can still be pending when incrementallyLoadTextures is true.
        FAILED : 3
    };

    // GLTF_SPEC: Figure out correct mime types (https://github.com/KhronosGroup/glTF/issues/412)
    var defaultModelAccept = 'model/vnd.gltf.binary,model/vnd.gltf+json,model/gltf.binary,model/gltf+json;q=0.8,application/json;q=0.2,*/*;q=0.01';

    function LoadResources() {
        this.buffersToCreate = new Queue();
        this.buffers = {};
        this.pendingBufferLoads = 0;

        this.programsToCreate = new Queue();
        this.shaders = {};
        this.pendingShaderLoads = 0;

        this.texturesToCreate = new Queue();
        this.pendingTextureLoads = 0;

        this.texturesToCreateFromBufferView = new Queue();
        this.pendingBufferViewToImage = 0;

        this.createSamplers = true;
        this.createSkins = true;
        this.createRuntimeAnimations = true;
        this.createVertexArrays = true;
        this.createRenderStates = true;
        this.createUniformMaps = true;
        this.createRuntimeNodes = true;

        this.skinnedNodesIds = [];
    }

    LoadResources.prototype.getBuffer = function(bufferView) {
        return getSubarray(this.buffers[bufferView.buffer], bufferView.byteOffset, bufferView.byteLength);
    };

    LoadResources.prototype.finishedPendingBufferLoads = function() {
        return (this.pendingBufferLoads === 0);
    };

    LoadResources.prototype.finishedBuffersCreation = function() {
        return ((this.pendingBufferLoads === 0) && (this.buffersToCreate.length === 0));
    };

    LoadResources.prototype.finishedProgramCreation = function() {
        return ((this.pendingShaderLoads === 0) && (this.programsToCreate.length === 0));
    };

    LoadResources.prototype.finishedTextureCreation = function() {
        var finishedPendingLoads = (this.pendingTextureLoads === 0);
        var finishedResourceCreation =
            (this.texturesToCreate.length === 0) &&
            (this.texturesToCreateFromBufferView.length === 0);

        return finishedPendingLoads && finishedResourceCreation;
    };

    LoadResources.prototype.finishedEverythingButTextureCreation = function() {
        var finishedPendingLoads =
            (this.pendingBufferLoads === 0) &&
            (this.pendingShaderLoads === 0);
        var finishedResourceCreation =
            (this.buffersToCreate.length === 0) &&
            (this.programsToCreate.length === 0) &&
            (this.pendingBufferViewToImage === 0);

        return finishedPendingLoads && finishedResourceCreation;
    };

    LoadResources.prototype.finished = function() {
        return this.finishedTextureCreation() && this.finishedEverythingButTextureCreation();
    };

    ///////////////////////////////////////////////////////////////////////////

    function setCachedGltf(model, cachedGltf) {
        model._cachedGltf = cachedGltf;
        model._animationIds = getAnimationIds(cachedGltf);
    }

    // glTF JSON can be big given embedded geometry, textures, and animations, so we
    // cache it across all models using the same url/cache-key.  This also reduces the
    // slight overhead in assigning defaults to missing values.
    //
    // Note that this is a global cache, compared to renderer resources, which
    // are cached per context.
    function CachedGltf(options) {
        this._gltf = modelMaterialsCommon(gltfDefaults(options.gltf));
        this._bgltf = options.bgltf;
        this.ready = options.ready;
        this.modelsToLoad = [];
        this.count = 0;
    }

    defineProperties(CachedGltf.prototype, {
        gltf : {
            set : function(value) {
                this._gltf = modelMaterialsCommon(gltfDefaults(value));
            },

            get : function() {
                return this._gltf;
            }
        },

        bgltf : {
            get : function() {
                return this._bgltf;
            }
        }
    });

    CachedGltf.prototype.makeReady = function(gltfJson, bgltf) {
        this.gltf = gltfJson;
        this._bgltf = bgltf;

        var models = this.modelsToLoad;
        var length = models.length;
        for (var i = 0; i < length; ++i) {
            var m = models[i];
            if (!m.isDestroyed()) {
                setCachedGltf(m, this);
            }
        }
        this.modelsToLoad = undefined;
        this.ready = true;
    };

    function getAnimationIds(cachedGltf) {
        var animationIds = [];
        if (defined(cachedGltf) && defined(cachedGltf.gltf)) {
            var animations = cachedGltf.gltf.animations;
            for (var id in animations) {
                if (animations.hasOwnProperty(id)) {
                    animationIds.push(id);
                }
            }
        }

        return animationIds;
    }

    var gltfCache = {};

    ///////////////////////////////////////////////////////////////////////////

    /**
     * A 3D model based on glTF, the runtime asset format for WebGL, OpenGL ES, and OpenGL.
     * <p>
     * Cesium includes support for geometry and materials, glTF animations, and glTF skinning.
     * In addition, individual glTF nodes are pickable with {@link Scene#pick} and animatable
     * with {@link Model#getNode}.  glTF cameras and lights are not currently supported.
     * </p>
     * <p>
     * An external glTF asset is created with {@link Model.fromGltf}.  glTF JSON can also be
     * created at runtime and passed to this constructor function.  In either case, the
     * {@link Model#readyPromise} is resolved when the model is ready to render, i.e.,
     * when the external binary, image, and shader files are downloaded and the WebGL
     * resources are created.
     * </p>
     * <p>
     * For high-precision rendering, Cesium supports the CESIUM_RTC extension, which introduces the
     * CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated
     * relative to a local origin.
     * </p>
     *
     * @alias Model
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] The object for the glTF JSON or an arraybuffer of Binary glTF defined by the KHR_binary_glTF extension.
     * @param {String} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
     * @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
     * @param {Number} [options.scale=1.0] A uniform scale applied to this model.
     * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
     * @param {Number} [options.maximumScale] The maximum scale size of a model. An upper limit for minimumPixelSize.
     * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
     * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}.
     * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
     * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
     * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from each light source.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
     * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
     * @param {HeightReference} [options.heightReference] Determines how the model is drawn relative to terrain.
     * @param {Scene} [options.scene] Must be passed in for models that use the height reference property.
     * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this model will be displayed.
     * @param {Color} [options.color=Color.WHITE] A color that blends with the model's rendered color.
     * @param {ColorBlendMode} [options.colorBlendMode=ColorBlendMode.HIGHLIGHT] Defines how the color blends with the model.
     * @param {Number} [options.colorBlendAmount=0.5] Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>. A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with any value in-between resulting in a mix of the two.
     * @param {Color} [options.silhouetteColor=Color.RED] The silhouette color. If more than 256 models have silhouettes enabled, there is a small chance that overlapping models will have minor artifacts.
     * @param {Number} [options.silhouetteSize=0.0] The size of the silhouette in pixels.
     *
     * @exception {DeveloperError} bgltf is not a valid Binary glTF file.
     * @exception {DeveloperError} Only glTF Binary version 1 is supported.
     *
     * @see Model.fromGltf
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html|Cesium Sandcastle Models Demo}
     */
    function Model(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var cacheKey = options.cacheKey;
        this._cacheKey = cacheKey;
        this._cachedGltf = undefined;
        this._releaseGltfJson = defaultValue(options.releaseGltfJson, false);
        this._animationIds = undefined;

        var cachedGltf;
        if (defined(cacheKey) && defined(gltfCache[cacheKey]) && gltfCache[cacheKey].ready) {
            // glTF JSON is in cache and ready
            cachedGltf = gltfCache[cacheKey];
            ++cachedGltf.count;
        } else {
            // glTF was explicitly provided, e.g., when a user uses the Model constructor directly
            var gltf = options.gltf;

            if (defined(gltf)) {
                if (gltf instanceof ArrayBuffer) {
                    gltf = new Uint8Array(gltf);
                }

                if (gltf instanceof Uint8Array) {
                    // Binary glTF
                    var result = parseBinaryGltfHeader(gltf);

                    // KHR_binary_glTF is from the beginning of the binary section
                    if (result.binaryOffset !== 0) {
                        gltf = gltf.subarray(result.binaryOffset);
                    }

                    cachedGltf = new CachedGltf({
                        gltf : result.glTF,
                        bgltf : gltf,
                        ready : true
                    });
                } else {
                    // Normal glTF (JSON)
                    cachedGltf = new CachedGltf({
                        gltf : options.gltf,
                        ready : true
                    });
                }

                cachedGltf.count = 1;

                if (defined(cacheKey)) {
                    gltfCache[cacheKey] = cachedGltf;
                }
            }
        }
        setCachedGltf(this, cachedGltf);

        this._basePath = defaultValue(options.basePath, '');

        var docUri = new Uri(document.location.href);
        var modelUri = new Uri(this._basePath);
        this._baseUri = modelUri.resolve(docUri);

        /**
         * Determines if the model primitive will be shown.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.show = defaultValue(options.show, true);

        /**
         * The silhouette color.
         *
         * @type {Color}
         *
         * @default Color.RED
         */
        this.silhouetteColor = defaultValue(options.silhouetteColor, Color.RED);
        this._silhouetteColor = new Color();
        this._silhouetteColorPreviousAlpha = 1.0;
        this._normalAttributeName = undefined;

        /**
         * The size of the silhouette in pixels.
         *
         * @type {Number}
         *
         * @default 0.0
         */
        this.silhouetteSize = defaultValue(options.silhouetteSize, 0.0);

        /**
         * The 4x4 transformation matrix that transforms the model from model to world coordinates.
         * When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type {Matrix4}
         *
         * @default {@link Matrix4.IDENTITY}
         *
         * @example
         * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
         * m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = Matrix4.clone(this.modelMatrix);
        this._clampedModelMatrix = undefined;

        /**
         * A uniform scale applied to this model before the {@link Model#modelMatrix}.
         * Values greater than <code>1.0</code> increase the size of the model; values
         * less than <code>1.0</code> decrease.
         *
         * @type {Number}
         *
         * @default 1.0
         */
        this.scale = defaultValue(options.scale, 1.0);
        this._scale = this.scale;

        /**
         * The approximate minimum pixel size of the model regardless of zoom.
         * This can be used to ensure that a model is visible even when the viewer
         * zooms out.  When <code>0.0</code>, no minimum size is enforced.
         *
         * @type {Number}
         *
         * @default 0.0
         */
        this.minimumPixelSize = defaultValue(options.minimumPixelSize, 0.0);
        this._minimumPixelSize = this.minimumPixelSize;

        /**
         * The maximum scale size for a model. This can be used to give
         * an upper limit to the {@link Model#minimumPixelSize}, ensuring that the model
         * is never an unreasonable scale.
         *
         * @type {Number}
         */
        this.maximumScale = options.maximumScale;
        this._maximumScale = this.maximumScale;

        /**
         * User-defined object returned when the model is picked.
         *
         * @type Object
         *
         * @default undefined
         *
         * @see Scene#pick
         */
        this.id = options.id;
        this._id = options.id;

        /**
         * Returns the height reference of the model
         *
         * @memberof Model.prototype
         *
         * @type {HeightReference}
         *
         * @default HeightReference.NONE
         */
        this.heightReference = defaultValue(options.heightReference, HeightReference.NONE);
        this._heightReference = this.heightReference;
        this._heightChanged = false;
        this._removeUpdateHeightCallback = undefined;
        var scene = options.scene;
        this._scene = scene;
        if (defined(scene)) {
            scene.terrainProviderChanged.addEventListener(function() {
                this._heightChanged = true;
            }, this);
        }

        /**
         * Used for picking primitives that wrap a model.
         *
         * @private
         */
        this.pickPrimitive = options.pickPrimitive;

        this._allowPicking = defaultValue(options.allowPicking, true);

        this._ready = false;
        this._readyPromise = when.defer();

        /**
         * The currently playing glTF animations.
         *
         * @type {ModelAnimationCollection}
         */
        this.activeAnimations = new ModelAnimationCollection(this);

        this._defaultTexture = undefined;
        this._incrementallyLoadTextures = defaultValue(options.incrementallyLoadTextures, true);
        this._asynchronous = defaultValue(options.asynchronous, true);

        /**
         * Determines whether the model casts or receives shadows from each light source.
         *
         * @type {ShadowMode}
         *
         * @default ShadowMode.ENABLED
         */
        this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
        this._shadows = this.shadows;

        /**
         * A color that blends with the model's rendered color.
         *
         * @type {Color}
         *
         * @default Color.WHITE
         */
        this.color = defaultValue(options.color, Color.WHITE);
        this._color = new Color();
        this._colorPreviousAlpha = 1.0;

        /**
         * Defines how the color blends with the model.
         *
         * @type {ColorBlendMode}
         *
         * @default ColorBlendMode.HIGHLIGHT
         */
        this.colorBlendMode = defaultValue(options.colorBlendMode, ColorBlendMode.HIGHLIGHT);

        /**
         * Value used to determine the color strength when the <code>colorBlendMode</code> is <code>MIX</code>.
         * A value of 0.0 results in the model's rendered color while a value of 1.0 results in a solid color, with
         * any value in-between resulting in a mix of the two.
         *
         * @type {Number}
         *
         * @default 0.5
         */
        this.colorBlendAmount = defaultValue(options.colorBlendAmount, 0.5);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the model.  A glTF primitive corresponds
         * to one draw command.  A glTF mesh has an array of primitives, often of length one.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
        this._debugShowBoundingVolume = false;

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the model in wireframe.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugWireframe = defaultValue(options.debugWireframe, false);
        this._debugWireframe = false;

        this._distanceDisplayCondition = options.distanceDisplayCondition;

        // Undocumented options
        this._precreatedAttributes = options.precreatedAttributes;
        this._vertexShaderLoaded = options.vertexShaderLoaded;
        this._fragmentShaderLoaded = options.fragmentShaderLoaded;
        this._uniformMapLoaded = options.uniformMapLoaded;
        this._pickVertexShaderLoaded = options.pickVertexShaderLoaded;
        this._pickFragmentShaderLoaded = options.pickFragmentShaderLoaded;
        this._pickUniformMapLoaded = options.pickUniformMapLoaded;
        this._ignoreCommands = defaultValue(options.ignoreCommands, false);
        this._upAxis = defaultValue(options.upAxis, Axis.Y);

        /**
         * @private
         * @readonly
         */
        this.cull = defaultValue(options.cull, true);

        this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and scale
        this._initialRadius = undefined;           // Radius without model's scale property, model-matrix scale, animations, or skins
        this._boundingSphere = undefined;
        this._scaledBoundingSphere = new BoundingSphere();
        this._state = ModelState.NEEDS_LOAD;
        this._loadResources = undefined;

        this._mode = undefined;

        this._perNodeShowDirty = false;            // true when the Cesium API was used to change a node's show property
        this._cesiumAnimationsDirty = false;       // true when the Cesium API, not a glTF animation, changed a node transform
        this._dirty = false;                       // true when the model was transformed this frame
        this._maxDirtyNumber = 0;                  // Used in place of a dirty boolean flag to avoid an extra graph traversal

        this._runtime = {
            animations : undefined,
            rootNodes : undefined,
            nodes : undefined,            // Indexed with the node property's name, i.e., glTF id
            nodesByName : undefined,      // Indexed with name property in the node
            skinnedNodes : undefined,
            meshesByName : undefined,     // Indexed with the name property in the mesh
            materialsByName : undefined,  // Indexed with the name property in the material
            materialsById : undefined     // Indexed with the material's property name
        };

        this._uniformMaps = {};           // Not cached since it can be targeted by glTF animation
        this._extensionsUsed = undefined; // Cached used extensions in a hash-map so we don't have to search the gltf array
        this._quantizedUniforms = {};     // Quantized uniforms for each program for WEB3D_quantized_attributes
        this._programPrimitives = {};
        this._rendererResources = {       // Cached between models with the same url/cache-key
            buffers : {},
            vertexArrays : {},
            programs : {},
            pickPrograms : {},
            silhouettePrograms: {},
            textures : {},

            samplers : {},
            renderStates : {}
        };
        this._cachedRendererResources = undefined;
        this._loadRendererResourcesFromCache = false;

        this._nodeCommands = [];
        this._pickIds = [];

        // CESIUM_RTC extension
        this._rtcCenter = undefined;    // reference to either 3D or 2D
        this._rtcCenterEye = undefined; // in eye coordinates
        this._rtcCenter3D = undefined;  // in world coordinates
        this._rtcCenter2D = undefined;  // in projected world coordinates
    }

    defineProperties(Model.prototype, {
        /**
         * The object for the glTF JSON, including properties with default values omitted
         * from the JSON provided to this model.
         *
         * @memberof Model.prototype
         *
         * @type {Object}
         * @readonly
         *
         * @default undefined
         */
        gltf : {
            get : function() {
                return defined(this._cachedGltf) ? this._cachedGltf.gltf : undefined;
            }
        },

        /**
         * When <code>true</code>, the glTF JSON is not stored with the model once the model is
         * loaded (when {@link Model#ready} is <code>true</code>).  This saves memory when
         * geometry, textures, and animations are embedded in the .gltf file, which is the
         * default for the {@link http://cesiumjs.org/convertmodel.html|Cesium model converter}.
         * This is especially useful for cases like 3D buildings, where each .gltf model is unique
         * and caching the glTF JSON is not effective.
         *
         * @memberof Model.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         *
         * @private
         */
        releaseGltfJson : {
            get : function() {
                return this._releaseGltfJson;
            }
        },

        /**
         * The key identifying this model in the model cache for glTF JSON, renderer resources, and animations.
         * Caching saves memory and improves loading speed when several models with the same url are created.
         * <p>
         * This key is automatically generated when the model is created with {@link Model.fromGltf}.  If the model
         * is created directly from glTF JSON using the {@link Model} constructor, this key can be manually
         * provided; otherwise, the model will not be changed.
         * </p>
         *
         * @memberof Model.prototype
         *
         * @type {String}
         * @readonly
         *
         * @private
         */
        cacheKey : {
            get : function() {
                return this._cacheKey;
            }
        },

        /**
         * The base path that paths in the glTF JSON are relative to.  The base
         * path is the same path as the path containing the .gltf file
         * minus the .gltf file, when binary, image, and shader files are
         * in the same directory as the .gltf.  When this is <code>''</code>,
         * the app's base path is used.
         *
         * @memberof Model.prototype
         *
         * @type {String}
         * @readonly
         *
         * @default ''
         */
        basePath : {
            get : function() {
                return this._basePath;
            }
        },

        /**
         * The model's bounding sphere in its local coordinate system.  This does not take into
         * account glTF animations and skins nor does it take into account {@link Model#minimumPixelSize}.
         *
         * @memberof Model.prototype
         *
         * @type {BoundingSphere}
         * @readonly
         *
         * @default undefined
         *
         * @exception {DeveloperError} The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.
         *
         * @example
         * // Center in WGS84 coordinates
         * var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
         */
        boundingSphere : {
            get : function() {
                                if (this._state !== ModelState.LOADED) {
                    throw new DeveloperError('The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.');
                }
                
                var modelMatrix = this.modelMatrix;
                if ((this.heightReference !== HeightReference.NONE) && this._clampedModelMatrix) {
                    modelMatrix = this._clampedModelMatrix;
                }

                var nonUniformScale = Matrix4.getScale(modelMatrix, boundingSphereCartesian3Scratch);
                var scale = defined(this.maximumScale) ? Math.min(this.maximumScale, this.scale) : this.scale;
                Cartesian3.multiplyByScalar(nonUniformScale, scale, nonUniformScale);

                var scaledBoundingSphere = this._scaledBoundingSphere;
                scaledBoundingSphere.center = Cartesian3.multiplyComponents(this._boundingSphere.center, nonUniformScale, scaledBoundingSphere.center);
                scaledBoundingSphere.radius = Cartesian3.maximumComponent(nonUniformScale) * this._initialRadius;

                if (defined(this._rtcCenter)) {
                    Cartesian3.add(this._rtcCenter, scaledBoundingSphere.center, scaledBoundingSphere.center);
                }

                return scaledBoundingSphere;
            }
        },

        /**
         * When <code>true</code>, this model is ready to render, i.e., the external binary, image,
         * and shader files were downloaded and the WebGL resources were created.  This is set to
         * <code>true</code> right before {@link Model#readyPromise} is resolved.
         *
         * @memberof Model.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets the promise that will be resolved when this model is ready to render, i.e., when the external binary, image,
         * and shader files were downloaded and the WebGL resources were created.
         * <p>
         * This promise is resolved at the end of the frame before the first frame the model is rendered in.
         * </p>
         *
         * @memberof Model.prototype
         * @type {Promise.<Model>}
         * @readonly
         *
         * @example
         * // Play all animations at half-speed when the model is ready to render
         * Cesium.when(model.readyPromise).then(function(model) {
         *   model.activeAnimations.addAll({
         *     speedup : 0.5
         *   });
         * }).otherwise(function(error){
         *   window.alert(error);
         * });
         *
         * @see Model#ready
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Determines if model WebGL resource creation will be spread out over several frames or
         * block until completion once all glTF files are loaded.
         *
         * @memberof Model.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        asynchronous : {
            get : function() {
                return this._asynchronous;
            }
        },

        /**
         * When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
         *
         * @memberof Model.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        allowPicking : {
            get : function() {
                return this._allowPicking;
            }
        },

        /**
         * Determine if textures may continue to stream in after the model is loaded.
         *
         * @memberof Model.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        incrementallyLoadTextures : {
            get : function() {
                return this._incrementallyLoadTextures;
            }
        },

        /**
         * Return the number of pending texture loads.
         *
         * @memberof Model.prototype
         *
         * @type {Number}
         * @readonly
         */
        pendingTextureLoads : {
            get : function() {
                return defined(this._loadResources) ? this._loadResources.pendingTextureLoads : 0;
            }
        },

        /**
         * Returns true if the model was transformed this frame
         *
         * @memberof Model.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @private
         */
        dirty : {
            get : function() {
                return this._dirty;
            }
        },

        /**
         * Gets or sets the condition specifying at what distance from the camera that this model will be displayed.
         * @memberof Model.prototype
         * @type {DistanceDisplayCondition}
         * @default undefined
         */
        distanceDisplayCondition : {
            get : function() {
                return this._distanceDisplayCondition;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far must be greater than near');
                }
                                this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
            }
        },

        /**
         * Gets the model's up-axis.
         * By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up.
         *
         * @memberof Model.prototype
         *
         * @type {Number}
         * @default Axis.Y
         * @readonly
         *
         * @private
         */
        upAxis : {
            get : function() {
                return this._upAxis;
            }
        }
    });

    var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;

    function silhouetteSupported(context) {
        return context.stencilBuffer;
    }

    /**
     * Determines if silhouettes are supported.
     *
     * @param {Scene} scene The scene.
     * @returns {Boolean} <code>true</code> if silhouettes are supported; otherwise, returns <code>false</code>
     */
    Model.silhouetteSupported = function(scene) {
        return silhouetteSupported(scene.context);
    };

    /**
     * This function differs from the normal subarray function
     * because it takes offset and length, rather than begin and end.
     */
    function getSubarray(array, offset, length) {
        return array.subarray(offset, offset + length);
    }

    function containsGltfMagic(uint8Array) {
        var magic = getMagic(uint8Array);
        return magic === 'glTF';
    }

    function parseBinaryGltfHeader(uint8Array) {
                if (!containsGltfMagic(uint8Array)) {
            throw new DeveloperError('bgltf is not a valid Binary glTF file.');
        }
        
        var view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
        var byteOffset = 0;

        byteOffset += sizeOfUint32; // Skip magic number

                var version = view.getUint32(byteOffset, true);
        if (version !== 1) {
            throw new DeveloperError('Only Binary glTF version 1 is supported.  Version ' + version + ' is not.');
        }
                byteOffset += sizeOfUint32;

        byteOffset += sizeOfUint32; // Skip length

        var sceneLength = view.getUint32(byteOffset, true);
        byteOffset += sizeOfUint32 + sizeOfUint32; // Skip sceneFormat

        var sceneOffset = byteOffset;
        var binOffset = sceneOffset + sceneLength;

        var json = getStringFromTypedArray(uint8Array, sceneOffset, sceneLength);
        return {
            glTF: JSON.parse(json),
            binaryOffset: binOffset
        };
    }

    /**
     * <p>
     * Creates a model from a glTF asset.  When the model is ready to render, i.e., when the external binary, image,
     * and shader files are downloaded and the WebGL resources are created, the {@link Model#readyPromise} is resolved.
     * </p>
     * <p>
     * The model can be a traditional glTF asset with a .gltf extension or a Binary glTF using the
     * KHR_binary_glTF extension with a .glb extension.
     * </p>
     * <p>
     * For high-precision rendering, Cesium supports the CESIUM_RTC extension, which introduces the
     * CESIUM_RTC_MODELVIEW parameter semantic that says the node is in WGS84 coordinates translated
     * relative to a local origin.
     * </p>
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The url to the .gltf file.
     * @param {Object} [options.headers] HTTP headers to send with the request.
     * @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
     * @param {Number} [options.scale=1.0] A uniform scale applied to this model.
     * @param {Number} [options.minimumPixelSize=0.0] The approximate minimum pixel size of the model regardless of zoom.
     * @param {Number} [options.maximumScale] The maximum scale for the model.
     * @param {Object} [options.id] A user-defined object to return when the model is picked with {@link Scene#pick}.
     * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each glTF mesh and primitive is pickable with {@link Scene#pick}.
     * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
     * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
     * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the model casts or receives shadows from each light source.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each {@link DrawCommand} in the model.
     * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
     *
     * @returns {Model} The newly created model.
     *
     * @exception {DeveloperError} bgltf is not a valid Binary glTF file.
     * @exception {DeveloperError} Only glTF Binary version 1 is supported.
     *
     * @example
     * // Example 1. Create a model from a glTF asset
     * var model = scene.primitives.add(Cesium.Model.fromGltf({
     *   url : './duck/duck.gltf'
     * }));
     *
     * @example
     * // Example 2. Create model and provide all properties and events
     * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
     * var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
     *
     * var model = scene.primitives.add(Cesium.Model.fromGltf({
     *   url : './duck/duck.gltf',
     *   show : true,                     // default
     *   modelMatrix : modelMatrix,
     *   scale : 2.0,                     // double size
     *   minimumPixelSize : 128,          // never smaller than 128 pixels
     *   maximumScale: 20000,             // never larger than 20000 * model size (overrides minimumPixelSize)
     *   allowPicking : false,            // not pickable
     *   debugShowBoundingVolume : false, // default
     *   debugWireframe : false
     * }));
     *
     * model.readyPromise.then(function(model) {
     *   // Play all animations when the model is ready to render
     *   model.activeAnimations.addAll();
     * });
     */
    Model.fromGltf = function(options) {
                if (!defined(options) || !defined(options.url)) {
            throw new DeveloperError('options.url is required');
        }
        
        var url = options.url;
        // If no cache key is provided, use the absolute URL, since two URLs with
        // different relative paths could point to the same model.
        var cacheKey = defaultValue(options.cacheKey, getAbsoluteUri(url));

        options = clone(options);
        options.basePath = getBaseUri(url);
        options.cacheKey = cacheKey;
        var model = new Model(options);

        options.headers = defined(options.headers) ? clone(options.headers) : {};
        if (!defined(options.headers.Accept)) {
            options.headers.Accept = defaultModelAccept;
        }

        var cachedGltf = gltfCache[cacheKey];
        if (!defined(cachedGltf)) {
            cachedGltf = new CachedGltf({
                ready : false
            });
            cachedGltf.count = 1;
            cachedGltf.modelsToLoad.push(model);
            setCachedGltf(model, cachedGltf);
            gltfCache[cacheKey] = cachedGltf;

            loadArrayBuffer(url, options.headers).then(function(arrayBuffer) {
                var array = new Uint8Array(arrayBuffer);
                if (containsGltfMagic(array)) {
                    // Load binary glTF
                    var result = parseBinaryGltfHeader(array);
                    // KHR_binary_glTF is from the beginning of the binary section
                    if (result.binaryOffset !== 0) {
                        array = array.subarray(result.binaryOffset);
                    }
                    cachedGltf.makeReady(result.glTF, array);
                } else {
                    // Load text (JSON) glTF
                    var json = getStringFromTypedArray(array);
                    cachedGltf.makeReady(JSON.parse(json));
                }
            }).otherwise(getFailedLoadFunction(model, 'model', url));
        } else if (!cachedGltf.ready) {
            // Cache hit but the loadArrayBuffer() or loadText() request is still pending
            ++cachedGltf.count;
            cachedGltf.modelsToLoad.push(model);
        }
        // else if the cached glTF is defined and ready, the
        // model constructor will pick it up using the cache key.

        return model;
    };

    /**
     * For the unit tests to verify model caching.
     *
     * @private
     */
    Model._gltfCache = gltfCache;

    function getRuntime(model, runtimeName, name) {
                if (model._state !== ModelState.LOADED) {
            throw new DeveloperError('The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.');
        }

        if (!defined(name)) {
            throw new DeveloperError('name is required.');
        }
        
        return (model._runtime[runtimeName])[name];
    }

    /**
     * Returns the glTF node with the given <code>name</code> property.  This is used to
     * modify a node's transform for animation outside of glTF animations.
     *
     * @param {String} name The glTF name of the node.
     * @returns {ModelNode} The node or <code>undefined</code> if no node with <code>name</code> exists.
     *
     * @exception {DeveloperError} The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.
     *
     * @example
     * // Apply non-uniform scale to node LOD3sp
     * var node = model.getNode('LOD3sp');
     * node.matrix = Cesium.Matrix4.fromScale(new Cesium.Cartesian3(5.0, 1.0, 1.0), node.matrix);
     */
    Model.prototype.getNode = function(name) {
        var node = getRuntime(this, 'nodesByName', name);
        return defined(node) ? node.publicNode : undefined;
    };

    /**
     * Returns the glTF mesh with the given <code>name</code> property.
     *
     * @param {String} name The glTF name of the mesh.
     *
     * @returns {ModelMesh} The mesh or <code>undefined</code> if no mesh with <code>name</code> exists.
     *
     * @exception {DeveloperError} The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.
     */
    Model.prototype.getMesh = function(name) {
        return getRuntime(this, 'meshesByName', name);
    };

    /**
     * Returns the glTF material with the given <code>name</code> property.
     *
     * @param {String} name The glTF name of the material.
     * @returns {ModelMaterial} The material or <code>undefined</code> if no material with <code>name</code> exists.
     *
     * @exception {DeveloperError} The model is not loaded.  Use Model.readyPromise or wait for Model.ready to be true.
     */
    Model.prototype.getMaterial = function(name) {
        return getRuntime(this, 'materialsByName', name);
    };

    var aMinScratch = new Cartesian3();
    var aMaxScratch = new Cartesian3();

    function getAccessorMinMax(gltf, accessorId) {
        var accessor = gltf.accessors[accessorId];
        var extensions = accessor.extensions;
        var accessorMin = accessor.min;
        var accessorMax = accessor.max;
        // If this accessor is quantized, we should use the decoded min and max
        if (defined(extensions)) {
            var quantizedAttributes = extensions.WEB3D_quantized_attributes;
            if (defined(quantizedAttributes)) {
                accessorMin = quantizedAttributes.decodedMin;
                accessorMax = quantizedAttributes.decodedMax;
            }
        }
        return {
            min : accessorMin,
            max : accessorMax
        };
    }

    function computeBoundingSphere(model, gltf) {
        var gltfNodes = gltf.nodes;
        var gltfMeshes = gltf.meshes;
        var rootNodes = gltf.scenes[gltf.scene].nodes;
        var rootNodesLength = rootNodes.length;

        var nodeStack = [];

        var min = new Cartesian3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
        var max = new Cartesian3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);

        for (var i = 0; i < rootNodesLength; ++i) {
            var n = gltfNodes[rootNodes[i]];
            n._transformToRoot = getTransform(n);
            nodeStack.push(n);

            while (nodeStack.length > 0) {
                n = nodeStack.pop();
                var transformToRoot = n._transformToRoot;

                var meshes = n.meshes;
                if (defined(meshes)) {
                    var meshesLength = meshes.length;
                    for (var j = 0; j < meshesLength; ++j) {
                        var primitives = gltfMeshes[meshes[j]].primitives;
                        var primitivesLength = primitives.length;
                        for (var m = 0; m < primitivesLength; ++m) {
                            var positionAccessor = primitives[m].attributes.POSITION;
                            if (defined(positionAccessor)) {
                                var minMax = getAccessorMinMax(gltf, positionAccessor);
                                var aMin = Cartesian3.fromArray(minMax.min, 0, aMinScratch);
                                var aMax = Cartesian3.fromArray(minMax.max, 0, aMaxScratch);
                                if (defined(min) && defined(max)) {
                                    Matrix4.multiplyByPoint(transformToRoot, aMin, aMin);
                                    Matrix4.multiplyByPoint(transformToRoot, aMax, aMax);
                                    Cartesian3.minimumByComponent(min, aMin, min);
                                    Cartesian3.maximumByComponent(max, aMax, max);
                                }
                            }
                        }
                    }
                }

                var children = n.children;
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    var child = gltfNodes[children[k]];
                    child._transformToRoot = getTransform(child);
                    Matrix4.multiplyTransformation(transformToRoot, child._transformToRoot, child._transformToRoot);
                    nodeStack.push(child);
                }
                delete n._transformToRoot;
            }
        }

        var boundingSphere = BoundingSphere.fromCornerPoints(min, max);
        if (model._upAxis === Axis.Y) {
            BoundingSphere.transformWithoutScale(boundingSphere, Axis.Y_UP_TO_Z_UP, boundingSphere);
        } else if (model._upAxis === Axis.X) {
            BoundingSphere.transformWithoutScale(boundingSphere, Axis.X_UP_TO_Z_UP, boundingSphere);
        }
        return boundingSphere;
    }

    ///////////////////////////////////////////////////////////////////////////

    function getFailedLoadFunction(model, type, path) {
        return function() {
            model._state = ModelState.FAILED;
            model._readyPromise.reject(new RuntimeError('Failed to load ' + type + ': ' + path));
        };
    }

    function bufferLoad(model, id) {
        return function(arrayBuffer) {
            var loadResources = model._loadResources;
            loadResources.buffers[id] = new Uint8Array(arrayBuffer);
            --loadResources.pendingBufferLoads;
        };
    }

    function parseBuffers(model) {
        var buffers = model.gltf.buffers;
        for (var id in buffers) {
            if (buffers.hasOwnProperty(id)) {
                var buffer = buffers[id];

                // The extension 'KHR_binary_glTF' uses a special buffer entitled just 'binary_glTF'.
                // The 'KHR_binary_glTF' check is for backwards compatibility for the Cesium model converter
                // circa Cesium 1.15-1.20 when the converter incorrectly used the buffer name 'KHR_binary_glTF'.
                if ((id === 'binary_glTF') || (id === 'KHR_binary_glTF')) {
                    // Buffer is the binary glTF file itself that is already loaded
                    var loadResources = model._loadResources;
                    loadResources.buffers[id] = model._cachedGltf.bgltf;
                }
                else if (buffer.type === 'arraybuffer') {
                    ++model._loadResources.pendingBufferLoads;
                    var uri = new Uri(buffer.uri);
                    var bufferPath = uri.resolve(model._baseUri).toString();
                    loadArrayBuffer(bufferPath).then(bufferLoad(model, id)).otherwise(getFailedLoadFunction(model, 'buffer', bufferPath));
                }
            }
        }
    }

    function parseBufferViews(model) {
        var bufferViews = model.gltf.bufferViews;
        for (var id in bufferViews) {
            if (bufferViews.hasOwnProperty(id)) {
                if (bufferViews[id].target === WebGLConstants.ARRAY_BUFFER) {
                    model._loadResources.buffersToCreate.enqueue(id);
                }
            }
        }
    }

    function shaderLoad(model, id) {
        return function(source) {
            var loadResources = model._loadResources;
            loadResources.shaders[id] = {
                source : source,
                bufferView : undefined
            };
            --loadResources.pendingShaderLoads;
        };
    }

    function parseShaders(model) {
        var shaders = model.gltf.shaders;
        for (var id in shaders) {
            if (shaders.hasOwnProperty(id)) {
                var shader = shaders[id];

                // Shader references either uri (external or base64-encoded) or bufferView
                if (defined(shader.extras) && defined(shader.extras.source)) {
                    model._loadResources.shaders[id] = {
                        source : shader.extras.source,
                        bufferView : undefined
                    };
                }
                else if (defined(shader.extensions) && defined(shader.extensions.KHR_binary_glTF)) {
                    var binary = shader.extensions.KHR_binary_glTF;
                    model._loadResources.shaders[id] = {
                        source : undefined,
                        bufferView : binary.bufferView
                    };
                } else {
                    ++model._loadResources.pendingShaderLoads;
                    var uri = new Uri(shader.uri);
                    var shaderPath = uri.resolve(model._baseUri).toString();
                    loadText(shaderPath).then(shaderLoad(model, id)).otherwise(getFailedLoadFunction(model, 'shader', shaderPath));
                }
            }
        }
    }

    function parsePrograms(model) {
        var programs = model.gltf.programs;
        for (var id in programs) {
            if (programs.hasOwnProperty(id)) {
                model._loadResources.programsToCreate.enqueue(id);
            }
        }
    }

    function imageLoad(model, id) {
        return function(image) {
            var loadResources = model._loadResources;
            --loadResources.pendingTextureLoads;
            loadResources.texturesToCreate.enqueue({
                id : id,
                image : image,
                bufferView : image.bufferView,
                width : image.width,
                height : image.height,
                internalFormat : image.internalFormat
            });
        };
    }

    var ktxRegex = /(^data:image\/ktx)|(\.ktx$)/i;
    var crnRegex = /(^data:image\/crn)|(\.crn$)/i;

    function parseTextures(model, context) {
        var images = model.gltf.images;
        var textures = model.gltf.textures;
        for (var id in textures) {
            if (textures.hasOwnProperty(id)) {
                var gltfImage = images[textures[id].source];
                var extras = gltfImage.extras;

                var binary = undefined;
                var uri = undefined;

                // First check for a compressed texture
                if (defined(extras) && defined(extras.compressedImage3DTiles)) {
                    var crunch = extras.compressedImage3DTiles.crunch;
                    var s3tc = extras.compressedImage3DTiles.s3tc;
                    var pvrtc = extras.compressedImage3DTiles.pvrtc1;
                    var etc1 = extras.compressedImage3DTiles.etc1;

                    if (context.s3tc && defined(crunch)) {
                        if (defined(crunch.extensions)&& defined(crunch.extensions.KHR_binary_glTF)) {
                            binary = crunch.extensions.KHR_binary_glTF;
                        } else {
                            uri = crunch.uri;
                        }
                    } else if (context.s3tc && defined(s3tc)) {
                        if (defined(s3tc.extensions)&& defined(s3tc.extensions.KHR_binary_glTF)) {
                            binary = s3tc.extensions.KHR_binary_glTF;
                        } else {
                            uri = s3tc.uri;
                        }
                    } else if (context.pvrtc && defined(pvrtc)) {
                        if (defined(pvrtc.extensions)&& defined(pvrtc.extensions.KHR_binary_glTF)) {
                            binary = pvrtc.extensions.KHR_binary_glTF;
                        } else {
                            uri = pvrtc.uri;
                        }
                    } else if (context.etc1 && defined(etc1)) {
                        if (defined(etc1.extensions)&& defined(etc1.extensions.KHR_binary_glTF)) {
                            binary = etc1.extensions.KHR_binary_glTF;
                        } else {
                            uri = etc1.uri;
                        }
                    }
                }

                // No compressed texture, so image references either uri (external or base64-encoded) or bufferView
                if (!defined(binary) && !defined(uri)) {
                    if (defined(gltfImage.extensions) && defined(gltfImage.extensions.KHR_binary_glTF)) {
                        binary = gltfImage.extensions.KHR_binary_glTF;
                    } else {
                        uri = new Uri(gltfImage.uri);
                    }
                }

                // Image references either uri (external or base64-encoded) or bufferView
                if (defined(binary)) {
                    model._loadResources.texturesToCreateFromBufferView.enqueue({
                        id : id,
                        image : undefined,
                        bufferView : binary.bufferView,
                        mimeType : binary.mimeType
                    });
                } else {
                    ++model._loadResources.pendingTextureLoads;
                    uri = new Uri(uri);
                    var imagePath = uri.resolve(model._baseUri).toString();

                    if (ktxRegex.test(imagePath)) {
                        loadKTX(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
                    } else if (crnRegex.test(imagePath)) {
                        loadCRN(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
                    } else {
                        loadImage(imagePath).then(imageLoad(model, id)).otherwise(getFailedLoadFunction(model, 'image', imagePath));
                    }
                }
            }
        }
    }

    var nodeTranslationScratch = new Cartesian3();
    var nodeQuaternionScratch = new Quaternion();
    var nodeScaleScratch = new Cartesian3();

    function getTransform(node) {
        if (defined(node.matrix)) {
            return Matrix4.fromArray(node.matrix);
        }

        return Matrix4.fromTranslationQuaternionRotationScale(
            Cartesian3.fromArray(node.translation, 0, nodeTranslationScratch),
            Quaternion.unpack(node.rotation, 0, nodeQuaternionScratch),
            Cartesian3.fromArray(node.scale, 0 , nodeScaleScratch));
    }

    function parseNodes(model) {
        var runtimeNodes = {};
        var runtimeNodesByName = {};
        var skinnedNodes = [];

        var skinnedNodesIds = model._loadResources.skinnedNodesIds;
        var nodes = model.gltf.nodes;

        for (var id in nodes) {
            if (nodes.hasOwnProperty(id)) {
                var node = nodes[id];

                var runtimeNode = {
                    // Animation targets
                    matrix : undefined,
                    translation : undefined,
                    rotation : undefined,
                    scale : undefined,

                    // Per-node show inherited from parent
                    computedShow : true,

                    // Computed transforms
                    transformToRoot : new Matrix4(),
                    computedMatrix : new Matrix4(),
                    dirtyNumber : 0,                    // The frame this node was made dirty by an animation; for graph traversal

                    // Rendering
                    commands : [],                      // empty for transform, light, and camera nodes

                    // Skinned node
                    inverseBindMatrices : undefined,    // undefined when node is not skinned
                    bindShapeMatrix : undefined,        // undefined when node is not skinned or identity
                    joints : [],                        // empty when node is not skinned
                    computedJointMatrices : [],         // empty when node is not skinned

                    // Joint node
                    jointName : node.jointName,         // undefined when node is not a joint

                    // Graph pointers
                    children : [],                      // empty for leaf nodes
                    parents : [],                       // empty for root nodes

                    // Publicly-accessible ModelNode instance to modify animation targets
                    publicNode : undefined
                };
                runtimeNode.publicNode = new ModelNode(model, node, runtimeNode, id, getTransform(node));

                runtimeNodes[id] = runtimeNode;
                runtimeNodesByName[node.name] = runtimeNode;

                if (defined(node.skin)) {
                    skinnedNodesIds.push(id);
                    skinnedNodes.push(runtimeNode);
                }
            }
        }

        model._runtime.nodes = runtimeNodes;
        model._runtime.nodesByName = runtimeNodesByName;
        model._runtime.skinnedNodes = skinnedNodes;
    }

    function parseMaterials(model) {
        var runtimeMaterialsByName = {};
        var runtimeMaterialsById = {};
        var materials = model.gltf.materials;
        var uniformMaps = model._uniformMaps;

        for (var id in materials) {
            if (materials.hasOwnProperty(id)) {
                // Allocated now so ModelMaterial can keep a reference to it.
                uniformMaps[id] = {
                    uniformMap : undefined,
                    values : undefined,
                    jointMatrixUniformName : undefined
                };

                var material = materials[id];
                var modelMaterial = new ModelMaterial(model, material, id);
                runtimeMaterialsByName[material.name] = modelMaterial;
                runtimeMaterialsById[id] = modelMaterial;
            }
        }

        model._runtime.materialsByName = runtimeMaterialsByName;
        model._runtime.materialsById = runtimeMaterialsById;
    }

    function parseMeshes(model) {
        var runtimeMeshesByName = {};
        var runtimeMaterialsById = model._runtime.materialsById;
        var meshes = model.gltf.meshes;
        var usesQuantizedAttributes = usesExtension(model, 'WEB3D_quantized_attributes');

        for (var id in meshes) {
            if (meshes.hasOwnProperty(id)) {
                var mesh = meshes[id];
                runtimeMeshesByName[mesh.name] = new ModelMesh(mesh, runtimeMaterialsById, id);
                if (usesQuantizedAttributes) {
                    // Cache primitives according to their program
                    var primitives = mesh.primitives;
                    var primitivesLength = primitives.length;
                    for (var i = 0; i < primitivesLength; i++) {
                        var primitive = primitives[i];
                        var programId = getProgramForPrimitive(model, primitive);
                        var programPrimitives = model._programPrimitives[programId];
                        if (!defined(programPrimitives)) {
                            programPrimitives = [];
                            model._programPrimitives[programId] = programPrimitives;
                        }
                        programPrimitives.push(primitive);
                    }
                }
            }
        }

        model._runtime.meshesByName = runtimeMeshesByName;
    }

    function parse(model, context) {
        if (!model._loadRendererResourcesFromCache) {
            parseBuffers(model);
            parseBufferViews(model);
            parseShaders(model);
            parsePrograms(model);
            parseTextures(model, context);
        }
        parseMaterials(model);
        parseMeshes(model);
        parseNodes(model);
    }

    function usesExtension(model, extension) {
        var cachedExtensionsUsed = model._extensionsUsed;
        if (!defined(cachedExtensionsUsed)) {
            var extensionsUsed = model.gltf.extensionsUsed;
            cachedExtensionsUsed = {};
            var extensionsLength = extensionsUsed.length;
            for (var i = 0; i < extensionsLength; i++) {
                cachedExtensionsUsed[extensionsUsed[i]] = true;
            }
        }
        return defined(cachedExtensionsUsed[extension]);
    }

    ///////////////////////////////////////////////////////////////////////////

    function createBuffers(model, context) {
        var loadResources = model._loadResources;

        if (loadResources.pendingBufferLoads !== 0) {
            return;
        }

        var bufferView;
        var bufferViews = model.gltf.bufferViews;
        var rendererBuffers = model._rendererResources.buffers;

        while (loadResources.buffersToCreate.length > 0) {
            var bufferViewId = loadResources.buffersToCreate.dequeue();
            bufferView = bufferViews[bufferViewId];

            // Only ARRAY_BUFFER here.  ELEMENT_ARRAY_BUFFER created below.
            var vertexBuffer = Buffer.createVertexBuffer({
                context : context,
                typedArray : loadResources.getBuffer(bufferView),
                usage : BufferUsage.STATIC_DRAW
            });
            vertexBuffer.vertexArrayDestroyable = false;
            rendererBuffers[bufferViewId] = vertexBuffer;
        }

        // The Cesium Renderer requires knowing the datatype for an index buffer
        // at creation type, which is not part of the glTF bufferview so loop
        // through glTF accessors to create the bufferview's index buffer.
        var accessors = model.gltf.accessors;
        for (var id in accessors) {
            if (accessors.hasOwnProperty(id)) {
                var accessor = accessors[id];
                bufferView = bufferViews[accessor.bufferView];

                if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(rendererBuffers[accessor.bufferView])) {
                    var indexBuffer = Buffer.createIndexBuffer({
                        context : context,
                        typedArray : loadResources.getBuffer(bufferView),
                        usage : BufferUsage.STATIC_DRAW,
                        indexDatatype : accessor.componentType
                    });
                    indexBuffer.vertexArrayDestroyable = false;
                    rendererBuffers[accessor.bufferView] = indexBuffer;
                    // In theory, several glTF accessors with different componentTypes could
                    // point to the same glTF bufferView, which would break this.
                    // In practice, it is unlikely as it will be UNSIGNED_SHORT.
                }
            }
        }
    }

    function createAttributeLocations(model, attributes) {
        var attributeLocations = {};
        var length = attributes.length;
        var i;

        // Set the position attribute to the 0th index. In some WebGL implementations the shader
        // will not work correctly if the 0th attribute is not active. For example, some glTF models
        // list the normal attribute first but derived shaders like the cast-shadows shader do not use
        // the normal attribute.
        for (i = 1; i < length; ++i) {
            var attribute = attributes[i];
            if (/position/i.test(attribute)) {
                attributes[i] = attributes[0];
                attributes[0] = attribute;
                break;
            }
        }

        for (i = 0; i < length; ++i) {
            attributeLocations[attributes[i]] = i;
        }

        return attributeLocations;
    }

    function getShaderSource(model, shader) {
        if (defined(shader.source)) {
            return shader.source;
        }

        var loadResources = model._loadResources;
        var gltf = model.gltf;
        var bufferView = gltf.bufferViews[shader.bufferView];

        return getStringFromTypedArray(loadResources.getBuffer(bufferView));
    }

    function replaceAllButFirstInString(string, find, replace) {
        var index = string.indexOf(find);
        return string.replace(new RegExp(find, 'g'), function(match, offset, all) {
            return index === offset ? match : replace;
        });
    }

    function getProgramForPrimitive(model, primitive) {
        var gltf = model.gltf;
        var materialId = primitive.material;
        var material = gltf.materials[materialId];
        var techniqueId = material.technique;
        var technique = gltf.techniques[techniqueId];
        return technique.program;
    }

    function getQuantizedAttributes(model, accessorId) {
        var gltf = model.gltf;
        var accessor = gltf.accessors[accessorId];
        var extensions = accessor.extensions;
        if (defined(extensions)) {
            return extensions.WEB3D_quantized_attributes;
        }
        return undefined;
    }

    function getAttributeVariableName(model, primitive, attributeSemantic) {
        var gltf = model.gltf;
        var materialId = primitive.material;
        var material = gltf.materials[materialId];
        var techniqueId = material.technique;
        var technique = gltf.techniques[techniqueId];
        for (var parameter in technique.parameters) {
            if (technique.parameters.hasOwnProperty(parameter)) {
                var semantic = technique.parameters[parameter].semantic;
                if (semantic === attributeSemantic) {
                    var attributes = technique.attributes;
                    for (var attributeVarName in attributes) {
                        if (attributes.hasOwnProperty(attributeVarName)) {
                            var name = attributes[attributeVarName];
                            if (name === parameter) {
                                return attributeVarName;
                            }
                        }
                    }
                }
            }
        }
        return undefined;
    }

    function modifyShaderForQuantizedAttributes(shader, programName, model, context) {
        var quantizedUniforms = {};
        model._quantizedUniforms[programName] = quantizedUniforms;

        var primitives = model._programPrimitives[programName];
        for (var i = 0; i < primitives.length; i++) {
            var primitive = primitives[i];
            if (getProgramForPrimitive(model, primitive) === programName) {
                for (var attributeSemantic in primitive.attributes) {
                    if (primitive.attributes.hasOwnProperty(attributeSemantic)) {
                        var decodeUniformVarName = 'gltf_u_dec_' + attributeSemantic.toLowerCase();
                        var decodeUniformVarNameScale = decodeUniformVarName + '_scale';
                        var decodeUniformVarNameTranslate = decodeUniformVarName + '_translate';
                        if (!defined(quantizedUniforms[decodeUniformVarName]) && !defined(quantizedUniforms[decodeUniformVarNameScale])) {
                            var accessorId = primitive.attributes[attributeSemantic];
                            var quantizedAttributes = getQuantizedAttributes(model, accessorId);
                            if (defined(quantizedAttributes)) {
                                var attributeVarName = getAttributeVariableName(model, primitive, attributeSemantic);
                                var decodeMatrix = quantizedAttributes.decodeMatrix;
                                var newMain = 'gltf_decoded_' + attributeSemantic;
                                var decodedAttributeVarName = attributeVarName.replace('a_', 'gltf_a_dec_');
                                var size = Math.floor(Math.sqrt(decodeMatrix.length));

                                // replace usages of the original attribute with the decoded version, but not the declaration
                                shader = replaceAllButFirstInString(shader, attributeVarName, decodedAttributeVarName);
                                // declare decoded attribute
                                var variableType;
                                if (size > 2) {
                                    variableType = 'vec' + (size - 1);
                                } else {
                                    variableType = 'float';
                                }
                                shader = variableType + ' ' + decodedAttributeVarName + ';\n' + shader;
                                // splice decode function into the shader - attributes are pre-multiplied with the decode matrix
                                // uniform in the shader (32-bit floating point)
                                var decode = '';
                                if (size === 5) {
                                    // separate scale and translate since glsl doesn't have mat5
                                    shader = 'uniform mat4 ' + decodeUniformVarNameScale + ';\n' + shader;
                                    shader = 'uniform vec4 ' + decodeUniformVarNameTranslate + ';\n' + shader;
                                    decode = '\n' +
                                             'void main() {\n' +
                                             '    ' + decodedAttributeVarName + ' = ' + decodeUniformVarNameScale + ' * ' + attributeVarName + ' + ' + decodeUniformVarNameTranslate + ';\n' +
                                             '    ' + newMain + '();\n' +
                                             '}\n';

                                    quantizedUniforms[decodeUniformVarNameScale] = {mat : 4};
                                    quantizedUniforms[decodeUniformVarNameTranslate] = {vec : 4};
                                }
                                else {
                                    shader = 'uniform mat' + size + ' ' + decodeUniformVarName + ';\n' + shader;
                                    decode = '\n' +
                                             'void main() {\n' +
                                             '    ' + decodedAttributeVarName + ' = ' + variableType + '(' + decodeUniformVarName + ' * vec' + size + '(' + attributeVarName + ',1.0));\n' +
                                             '    ' + newMain + '();\n' +
                                             '}\n';

                                    quantizedUniforms[decodeUniformVarName] = {mat : size};
                                }
                                shader = ShaderSource.replaceMain(shader, newMain);
                                shader += decode;
                            }
                        }
                    }
                }
            }
        }
        // This is not needed after the program is processed, free the memory
        model._programPrimitives[programName] = undefined;
        return shader;
    }

    function hasPremultipliedAlpha(model) {
        var gltf = model.gltf;
        return defined(gltf.asset) ? defaultValue(gltf.asset.premultipliedAlpha, false) : false;
    }

    function modifyShaderForColor(shader, premultipliedAlpha) {
        shader = ShaderSource.replaceMain(shader, 'gltf_blend_main');
        shader +=
            'uniform vec4 gltf_color; \n' +
            'uniform float gltf_colorBlend; \n' +
            'void main() \n' +
            '{ \n' +
            '    gltf_blend_main(); \n';

        // Un-premultiply the alpha so that blending is correct.

        // Avoid divide-by-zero. The code below is equivalent to:
        // if (gl_FragColor.a > 0.0)
        // {
        //     gl_FragColor.rgb /= gl_FragColor.a;
        // }

        if (premultipliedAlpha) {
            shader +=
                '    float alpha = 1.0 - ceil(gl_FragColor.a) + gl_FragColor.a; \n' +
                '    gl_FragColor.rgb /= alpha; \n';
        }

        shader +=
            '    gl_FragColor.rgb = mix(gl_FragColor.rgb, gltf_color.rgb, gltf_colorBlend); \n' +
            '    float highlight = ceil(gltf_colorBlend); \n' +
            '    gl_FragColor.rgb *= mix(gltf_color.rgb, vec3(1.0), highlight); \n' +
            '    gl_FragColor.a *= gltf_color.a; \n' +
            '} \n';

        return shader;
    }

    function modifyShader(shader, programName, callback) {
        if (defined(callback)) {
            shader = callback(shader, programName);
        }
        return shader;
    }

    function createProgram(id, model, context) {
        var programs = model.gltf.programs;
        var shaders = model._loadResources.shaders;
        var program = programs[id];

        var attributeLocations = createAttributeLocations(model, program.attributes);
        var vs = getShaderSource(model, shaders[program.vertexShader]);
        var fs = getShaderSource(model, shaders[program.fragmentShader]);

        // Add pre-created attributes to attributeLocations
        var attributesLength = program.attributes.length;
        var precreatedAttributes = model._precreatedAttributes;
        if (defined(precreatedAttributes)) {
            for (var attrName in precreatedAttributes) {
                if (precreatedAttributes.hasOwnProperty(attrName)) {
                    attributeLocations[attrName] = attributesLength++;
                }
            }
        }

        if (usesExtension(model, 'WEB3D_quantized_attributes')) {
            vs = modifyShaderForQuantizedAttributes(vs, id, model, context);
        }

        var premultipliedAlpha = hasPremultipliedAlpha(model);
        var blendFS = modifyShaderForColor(fs, premultipliedAlpha);

        var drawVS = modifyShader(vs, id, model._vertexShaderLoaded);
        var drawFS = modifyShader(blendFS, id, model._fragmentShaderLoaded);

        model._rendererResources.programs[id] = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : drawVS,
            fragmentShaderSource : drawFS,
            attributeLocations : attributeLocations
        });

        if (model.allowPicking) {
            // PERFORMANCE_IDEA: Can optimize this shader with a glTF hint. https://github.com/KhronosGroup/glTF/issues/181
            var pickVS = modifyShader(vs, id, model._pickVertexShaderLoaded);
            var pickFS = modifyShader(fs, id, model._pickFragmentShaderLoaded);

            if (!model._pickFragmentShaderLoaded) {
                pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform');
            }

            model._rendererResources.pickPrograms[id] = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : pickVS,
                fragmentShaderSource : pickFS,
                attributeLocations : attributeLocations
            });
        }
    }

    function createPrograms(model, context) {
        var loadResources = model._loadResources;
        var id;

        if (loadResources.pendingShaderLoads !== 0) {
            return;
        }

        // PERFORMANCE_IDEA: this could be more fine-grained by looking
        // at the shader's bufferView's to determine the buffer dependencies.
        if (loadResources.pendingBufferLoads !== 0) {
            return;
        }

        if (model.asynchronous) {
            // Create one program per frame
            if (loadResources.programsToCreate.length > 0) {
                id = loadResources.programsToCreate.dequeue();
                createProgram(id, model, context);
            }
        } else {
            // Create all loaded programs this frame
            while (loadResources.programsToCreate.length > 0) {
                id = loadResources.programsToCreate.dequeue();
                createProgram(id, model, context);
            }
        }
    }

    function getOnImageCreatedFromTypedArray(loadResources, gltfTexture) {
        return function(image) {
            loadResources.texturesToCreate.enqueue({
                id : gltfTexture.id,
                image : image,
                bufferView : undefined
            });

            --loadResources.pendingBufferViewToImage;
        };
    }

    function loadTexturesFromBufferViews(model) {
        var loadResources = model._loadResources;

        if (loadResources.pendingBufferLoads !== 0) {
            return;
        }

        while (loadResources.texturesToCreateFromBufferView.length > 0) {
            var gltfTexture = loadResources.texturesToCreateFromBufferView.dequeue();

            var gltf = model.gltf;
            var bufferView = gltf.bufferViews[gltfTexture.bufferView];

            var onerror = getFailedLoadFunction(model, 'image', 'id: ' + gltfTexture.id + ', bufferView: ' + gltfTexture.bufferView);

            if (gltfTexture.mimeType === 'image/ktx') {
                loadKTX(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id)).otherwise(onerror);
                ++model._loadResources.pendingTextureLoads;
            } else if (gltfTexture.mimeType === 'image/crn') {
                loadCRN(loadResources.getBuffer(bufferView)).then(imageLoad(model, gltfTexture.id)).otherwise(onerror);
                ++model._loadResources.pendingTextureLoads;
            } else {
                var onload = getOnImageCreatedFromTypedArray(loadResources, gltfTexture);
                loadImageFromTypedArray(loadResources.getBuffer(bufferView), gltfTexture.mimeType)
                    .then(onload).otherwise(onerror);
                ++loadResources.pendingBufferViewToImage;
            }
        }
    }

    function createSamplers(model, context) {
        var loadResources = model._loadResources;

        if (loadResources.createSamplers) {
            loadResources.createSamplers = false;

            var rendererSamplers = model._rendererResources.samplers;
            var samplers = model.gltf.samplers;
            for (var id in samplers) {
                if (samplers.hasOwnProperty(id)) {
                    var sampler = samplers[id];

                    rendererSamplers[id] = new Sampler({
                        wrapS : sampler.wrapS,
                        wrapT : sampler.wrapT,
                        minificationFilter : sampler.minFilter,
                        magnificationFilter : sampler.magFilter
                    });
                }
            }
        }
    }

    function createTexture(gltfTexture, model, context) {
        var textures = model.gltf.textures;
        var texture = textures[gltfTexture.id];

        var rendererSamplers = model._rendererResources.samplers;
        var sampler = rendererSamplers[texture.sampler];

        var internalFormat = gltfTexture.internalFormat;

        var mipmap =
            (!(defined(internalFormat) && PixelFormat.isCompressedFormat(internalFormat))) &&
            ((sampler.minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_NEAREST) ||
             (sampler.minificationFilter === TextureMinificationFilter.NEAREST_MIPMAP_LINEAR) ||
             (sampler.minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_NEAREST) ||
             (sampler.minificationFilter === TextureMinificationFilter.LINEAR_MIPMAP_LINEAR));
        var requiresNpot = mipmap ||
            (sampler.wrapS === TextureWrap.REPEAT) ||
            (sampler.wrapS === TextureWrap.MIRRORED_REPEAT) ||
            (sampler.wrapT === TextureWrap.REPEAT) ||
            (sampler.wrapT === TextureWrap.MIRRORED_REPEAT);

        var tx;
        var source = gltfTexture.image;

        if (defined(internalFormat) && texture.target === WebGLConstants.TEXTURE_2D) {
            tx = new Texture({
                context : context,
                source : {
                    arrayBufferView : gltfTexture.bufferView
                },
                width : gltfTexture.width,
                height : gltfTexture.height,
                pixelFormat : internalFormat,
                sampler : sampler
            });
        } else if (defined(source)) {
            var npot = !CesiumMath.isPowerOfTwo(source.width) || !CesiumMath.isPowerOfTwo(source.height);

            if (requiresNpot && npot) {
                // WebGL requires power-of-two texture dimensions for mipmapping and REPEAT/MIRRORED_REPEAT wrap modes.
                var canvas = document.createElement('canvas');
                canvas.width = CesiumMath.nextPowerOfTwo(source.width);
                canvas.height = CesiumMath.nextPowerOfTwo(source.height);
                var canvasContext = canvas.getContext('2d');
                canvasContext.drawImage(source, 0, 0, source.width, source.height, 0, 0, canvas.width, canvas.height);
                source = canvas;
            }

            if (texture.target === WebGLConstants.TEXTURE_2D) {
                tx = new Texture({
                    context : context,
                    source : source,
                    pixelFormat : texture.internalFormat,
                    pixelDatatype : texture.type,
                    sampler : sampler,
                    flipY : false
                });
            }
            // GLTF_SPEC: Support TEXTURE_CUBE_MAP.  https://github.com/KhronosGroup/glTF/issues/40

            if (mipmap) {
                tx.generateMipmap();
            }
        }

        model._rendererResources.textures[gltfTexture.id] = tx;
    }

    function createTextures(model, context) {
        var loadResources = model._loadResources;
        var gltfTexture;

        if (model.asynchronous) {
            // Create one texture per frame
            if (loadResources.texturesToCreate.length > 0) {
                gltfTexture = loadResources.texturesToCreate.dequeue();
                createTexture(gltfTexture, model, context);
            }
        } else {
            // Create all loaded textures this frame
            while (loadResources.texturesToCreate.length > 0) {
                gltfTexture = loadResources.texturesToCreate.dequeue();
                createTexture(gltfTexture, model, context);
            }
        }
    }

    function getAttributeLocations(model, primitive) {
        var gltf = model.gltf;
        var techniques = gltf.techniques;
        var materials = gltf.materials;

        // Retrieve the compiled shader program to assign index values to attributes
        var attributeLocations = {};

        var technique = techniques[materials[primitive.material].technique];
        var parameters = technique.parameters;
        var attributes = technique.attributes;
        var programAttributeLocations = model._rendererResources.programs[technique.program].vertexAttributes;

        // Note: WebGL shader compiler may have optimized and removed some attributes from programAttributeLocations
        for (var location in programAttributeLocations){
            if (programAttributeLocations.hasOwnProperty(location)) {
                var attribute = attributes[location];
                var index = programAttributeLocations[location].index;
                if (defined(attribute)) {
                    var parameter = parameters[attribute];
                    attributeLocations[parameter.semantic] = index;
                } else {
                    // Pre-created attributes
                    attributeLocations[location] = index;
                }
            }
        }

        return attributeLocations;
    }

    function searchForest(forest, jointName, nodes) {
        var length = forest.length;
        for (var i = 0; i < length; ++i) {
            var stack = [forest[i]]; // Push root node of tree

            while (stack.length > 0) {
                var id = stack.pop();
                var n = nodes[id];

                if (n.jointName === jointName) {
                    return id;
                }

                var children = n.children;
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    stack.push(children[k]);
                }
            }
        }

        // This should never happen; the skeleton should have a node for all joints in the skin.
        return undefined;
    }

    function createJoints(model, runtimeSkins) {
        var gltf = model.gltf;
        var skins = gltf.skins;
        var nodes = gltf.nodes;
        var runtimeNodes = model._runtime.nodes;

        var skinnedNodesIds = model._loadResources.skinnedNodesIds;
        var length = skinnedNodesIds.length;
        for (var j = 0; j < length; ++j) {
            var id = skinnedNodesIds[j];
            var skinnedNode = runtimeNodes[id];
            var node = nodes[id];

            var runtimeSkin = runtimeSkins[node.skin];
            skinnedNode.inverseBindMatrices = runtimeSkin.inverseBindMatrices;
            skinnedNode.bindShapeMatrix = runtimeSkin.bindShapeMatrix;

            // 1. Find nodes with the names in node.skeletons (the node's skeletons)
            // 2. These nodes form the root nodes of the forest to search for each joint in skin.jointNames.  This search uses jointName, not the node's name.
            // 3. Search for the joint name among the gltf node hierarchy instead of the runtime node hierarchy. Child links aren't set up yet for runtime nodes.
            var forest = [];
            var gltfSkeletons = node.skeletons;
            var skeletonsLength = gltfSkeletons.length;
            for (var k = 0; k < skeletonsLength; ++k) {
                forest.push(gltfSkeletons[k]);
            }

            var gltfJointNames = skins[node.skin].jointNames;
            var jointNamesLength = gltfJointNames.length;
            for (var i = 0; i < jointNamesLength; ++i) {
                var jointName = gltfJointNames[i];
                var jointNode = runtimeNodes[searchForest(forest, jointName, nodes)];
                skinnedNode.joints.push(jointNode);
            }
        }
    }

    function createSkins(model) {
        var loadResources = model._loadResources;

        if (loadResources.pendingBufferLoads !== 0) {
            return;
        }

        if (!loadResources.createSkins) {
            return;
        }
        loadResources.createSkins = false;

        var gltf = model.gltf;
        var accessors = gltf.accessors;
        var skins = gltf.skins;
        var runtimeSkins = {};

        for (var id in skins) {
            if (skins.hasOwnProperty(id)) {
                var skin = skins[id];
                var accessor = accessors[skin.inverseBindMatrices];

                var bindShapeMatrix;
                if (!Matrix4.equals(skin.bindShapeMatrix, Matrix4.IDENTITY)) {
                    bindShapeMatrix = Matrix4.clone(skin.bindShapeMatrix);
                }

                runtimeSkins[id] = {
                    inverseBindMatrices : ModelAnimationCache.getSkinInverseBindMatrices(model, accessor),
                    bindShapeMatrix : bindShapeMatrix // not used when undefined
                };
            }
        }

        createJoints(model, runtimeSkins);
    }

    function getChannelEvaluator(model, runtimeNode, targetPath, spline) {
        return function(localAnimationTime) {
            //  Workaround for https://github.com/KhronosGroup/glTF/issues/219

            //if (targetPath === 'translation') {
            //    return;
            //}
            runtimeNode[targetPath] = spline.evaluate(localAnimationTime, runtimeNode[targetPath]);
            runtimeNode.dirtyNumber = model._maxDirtyNumber;
        };
    }

    function createRuntimeAnimations(model) {
        var loadResources = model._loadResources;

        if (!loadResources.finishedPendingBufferLoads()) {
            return;
        }

        if (!loadResources.createRuntimeAnimations) {
            return;
        }
        loadResources.createRuntimeAnimations = false;

        model._runtime.animations = {
        };

        var runtimeNodes = model._runtime.nodes;
        var animations = model.gltf.animations;
        var accessors = model.gltf.accessors;

        for (var animationId in animations) {
            if (animations.hasOwnProperty(animationId)) {
                var animation = animations[animationId];
                var channels = animation.channels;
                var parameters = animation.parameters;
                var samplers = animation.samplers;

                var parameterValues = {};

                for (var name in parameters) {
                    if (parameters.hasOwnProperty(name)) {
                        parameterValues[name] = ModelAnimationCache.getAnimationParameterValues(model, accessors[parameters[name]]);
                    }
                }

                // Find start and stop time for the entire animation
                var startTime = Number.MAX_VALUE;
                var stopTime = -Number.MAX_VALUE;

                var length = channels.length;
                var channelEvaluators = new Array(length);

                for (var i = 0; i < length; ++i) {
                    var channel = channels[i];
                    var target = channel.target;
                    var sampler = samplers[channel.sampler];
                    var times = parameterValues[sampler.input];

                    startTime = Math.min(startTime, times[0]);
                    stopTime = Math.max(stopTime, times[times.length - 1]);

                    var spline = ModelAnimationCache.getAnimationSpline(model, animationId, animation, channel.sampler, sampler, parameterValues);
                    // GLTF_SPEC: Support more targets like materials. https://github.com/KhronosGroup/glTF/issues/142
                    channelEvaluators[i] = getChannelEvaluator(model, runtimeNodes[target.id], target.path, spline);
                }

                model._runtime.animations[animationId] = {
                    startTime : startTime,
                    stopTime : stopTime,
                    channelEvaluators : channelEvaluators
                };
            }
        }
    }

    function createVertexArrays(model, context) {
        var loadResources = model._loadResources;

        if (!loadResources.finishedBuffersCreation() || !loadResources.finishedProgramCreation()) {
            return;
        }

        if (!loadResources.createVertexArrays) {
            return;
        }
        loadResources.createVertexArrays = false;

        var rendererBuffers = model._rendererResources.buffers;
        var rendererVertexArrays = model._rendererResources.vertexArrays;
        var gltf = model.gltf;
        var accessors = gltf.accessors;
        var meshes = gltf.meshes;

        for (var meshId in meshes) {
            if (meshes.hasOwnProperty(meshId)) {
                var primitives = meshes[meshId].primitives;
                var primitivesLength = primitives.length;

                for (var i = 0; i < primitivesLength; ++i) {
                    var primitive = primitives[i];

                    // GLTF_SPEC: This does not take into account attribute arrays,
                    // indicated by when an attribute points to a parameter with a
                    // count property.
                    //
                    // https://github.com/KhronosGroup/glTF/issues/258

                    var attributeLocations = getAttributeLocations(model, primitive);
                    var attributeName;
                    var attributeLocation;
                    var attribute;
                    var attributes = [];
                    var primitiveAttributes = primitive.attributes;
                    for (attributeName in primitiveAttributes) {
                        if (primitiveAttributes.hasOwnProperty(attributeName)) {
                            attributeLocation = attributeLocations[attributeName];
                            // Skip if the attribute is not used by the material, e.g., because the asset was exported
                            // with an attribute that wasn't used and the asset wasn't optimized.
                            if (defined(attributeLocation)) {
                                var a = accessors[primitiveAttributes[attributeName]];
                                attributes.push({
                                    index : attributeLocation,
                                    vertexBuffer : rendererBuffers[a.bufferView],
                                    componentsPerAttribute : getBinaryAccessor(a).componentsPerAttribute,
                                    componentDatatype : a.componentType,
                                    normalize : false,
                                    offsetInBytes : a.byteOffset,
                                    strideInBytes : a.byteStride
                                });
                            }
                        }
                    }

                    // Add pre-created attributes
                    var precreatedAttributes = model._precreatedAttributes;
                    if (defined(precreatedAttributes)) {
                        for (attributeName in precreatedAttributes) {
                            if (precreatedAttributes.hasOwnProperty(attributeName)) {
                                attributeLocation = attributeLocations[attributeName];
                                if (defined(attributeLocation)) {
                                    attribute = precreatedAttributes[attributeName];
                                    attribute.index = attributeLocation;
                                    attributes.push(attribute);
                                }
                            }
                        }
                    }

                    var indexBuffer;
                    if (defined(primitive.indices)) {
                        var accessor = accessors[primitive.indices];
                        indexBuffer = rendererBuffers[accessor.bufferView];
                    }
                    rendererVertexArrays[meshId + '.primitive.' + i] = new VertexArray({
                        context : context,
                        attributes : attributes,
                        indexBuffer : indexBuffer
                    });
                }
            }
        }
    }

    function getBooleanStates(states) {
        // GLTF_SPEC: SAMPLE_ALPHA_TO_COVERAGE not used by Cesium
        var booleanStates = {};
        booleanStates[WebGLConstants.BLEND] = false;
        booleanStates[WebGLConstants.CULL_FACE] = false;
        booleanStates[WebGLConstants.DEPTH_TEST] = false;
        booleanStates[WebGLConstants.POLYGON_OFFSET_FILL] = false;
        booleanStates[WebGLConstants.SCISSOR_TEST] = false;

        var enable = states.enable;
        var length = enable.length;
        var i;
        for (i = 0; i < length; ++i) {
            booleanStates[enable[i]] = true;
        }

        return booleanStates;
    }

    function createRenderStates(model, context) {
        var loadResources = model._loadResources;
        var techniques = model.gltf.techniques;

        if (loadResources.createRenderStates) {
            loadResources.createRenderStates = false;
            for (var id in techniques) {
                if (techniques.hasOwnProperty(id)) {
                    createRenderStateForTechnique(model, id, context);
                }
            }
        }
    }

    function createRenderStateForTechnique(model, id, context) {
        var rendererRenderStates = model._rendererResources.renderStates;
        var techniques = model.gltf.techniques;
        var technique = techniques[id];
        var states = technique.states;

        var booleanStates = getBooleanStates(states);
        var statesFunctions = defaultValue(states.functions, defaultValue.EMPTY_OBJECT);
        var blendColor = defaultValue(statesFunctions.blendColor, [0.0, 0.0, 0.0, 0.0]);
        var blendEquationSeparate = defaultValue(statesFunctions.blendEquationSeparate, [
            WebGLConstants.FUNC_ADD,
            WebGLConstants.FUNC_ADD]);
        var blendFuncSeparate = defaultValue(statesFunctions.blendFuncSeparate, [
            WebGLConstants.ONE,
            WebGLConstants.ZERO,
            WebGLConstants.ONE,
            WebGLConstants.ZERO]);
        var colorMask = defaultValue(statesFunctions.colorMask, [true, true, true, true]);
        var depthRange = defaultValue(statesFunctions.depthRange, [0.0, 1.0]);
        var polygonOffset = defaultValue(statesFunctions.polygonOffset, [0.0, 0.0]);
        var scissor = defaultValue(statesFunctions.scissor, [0.0, 0.0, 0.0, 0.0]);

        // Change the render state to use traditional alpha blending instead of premultiplied alpha blending
        if (booleanStates[WebGLConstants.BLEND] && hasPremultipliedAlpha(model)) {
            if ((blendFuncSeparate[0] === WebGLConstants.ONE) && (blendFuncSeparate[1] === WebGLConstants.ONE_MINUS_SRC_ALPHA)) {
                blendFuncSeparate[0] = WebGLConstants.SRC_ALPHA;
                blendFuncSeparate[1] = WebGLConstants.ONE_MINUS_SRC_ALPHA;
                blendFuncSeparate[2] = WebGLConstants.SRC_ALPHA;
                blendFuncSeparate[3] = WebGLConstants.ONE_MINUS_SRC_ALPHA;
            }
        }

        rendererRenderStates[id] = RenderState.fromCache({
            frontFace : defined(statesFunctions.frontFace) ? statesFunctions.frontFace[0] : WebGLConstants.CCW,
            cull : {
                enabled : booleanStates[WebGLConstants.CULL_FACE],
                face : defined(statesFunctions.cullFace) ? statesFunctions.cullFace[0] : WebGLConstants.BACK
            },
            lineWidth : defined(statesFunctions.lineWidth) ? statesFunctions.lineWidth[0] : 1.0,
            polygonOffset : {
                enabled : booleanStates[WebGLConstants.POLYGON_OFFSET_FILL],
                factor : polygonOffset[0],
                units : polygonOffset[1]
            },
            scissorTest : {
                enabled : booleanStates[WebGLConstants.SCISSOR_TEST],
                rectangle : {
                    x : scissor[0],
                    y : scissor[1],
                    width : scissor[2],
                    height : scissor[3]
                }
            },
            depthRange : {
                near : depthRange[0],
                far : depthRange[1]
            },
            depthTest : {
                enabled : booleanStates[WebGLConstants.DEPTH_TEST],
                func : defined(statesFunctions.depthFunc) ? statesFunctions.depthFunc[0] : WebGLConstants.LESS
            },
            colorMask : {
                red : colorMask[0],
                green : colorMask[1],
                blue : colorMask[2],
                alpha : colorMask[3]
            },
            depthMask : defined(statesFunctions.depthMask) ? statesFunctions.depthMask[0] : true,
            blending : {
                enabled : booleanStates[WebGLConstants.BLEND],
                color : {
                    red : blendColor[0],
                    green : blendColor[1],
                    blue : blendColor[2],
                    alpha : blendColor[3]
                },
                equationRgb : blendEquationSeparate[0],
                equationAlpha : blendEquationSeparate[1],
                functionSourceRgb : blendFuncSeparate[0],
                functionDestinationRgb : blendFuncSeparate[1],
                functionSourceAlpha : blendFuncSeparate[2],
                functionDestinationAlpha : blendFuncSeparate[3]
            }
        });
    }

    // This doesn't support LOCAL, which we could add if it is ever used.
    var scratchTranslationRtc = new Cartesian3();
    var gltfSemanticUniforms = {
        MODEL : function(uniformState, model) {
            return function() {
                return uniformState.model;
            };
        },
        VIEW : function(uniformState, model) {
            return function() {
                return uniformState.view;
            };
        },
        PROJECTION : function(uniformState, model) {
            return function() {
                return uniformState.projection;
            };
        },
        MODELVIEW : function(uniformState, model) {
            return function() {
                return uniformState.modelView;
            };
        },
        CESIUM_RTC_MODELVIEW : function(uniformState, model) {
            // CESIUM_RTC extension
            var mvRtc = new Matrix4();
            return function() {
                if (defined(model._rtcCenter)) {
                    Matrix4.getTranslation(uniformState.model, scratchTranslationRtc);
                    Cartesian3.add(scratchTranslationRtc, model._rtcCenter, scratchTranslationRtc);
                    Matrix4.multiplyByPoint(uniformState.view, scratchTranslationRtc, scratchTranslationRtc);
                    return Matrix4.setTranslation(uniformState.modelView, scratchTranslationRtc, mvRtc);
                }
                return uniformState.modelView;
            };
        },
        MODELVIEWPROJECTION : function(uniformState, model) {
            return function() {
                return uniformState.modelViewProjection;
            };
        },
        MODELINVERSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseModel;
            };
        },
        VIEWINVERSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseView;
            };
        },
        PROJECTIONINVERSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseProjection;
            };
        },
        MODELVIEWINVERSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseModelView;
            };
        },
        MODELVIEWPROJECTIONINVERSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseModelViewProjection;
            };
        },
        MODELINVERSETRANSPOSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseTransposeModel;
            };
        },
        MODELVIEWINVERSETRANSPOSE : function(uniformState, model) {
            return function() {
                return uniformState.normal;
            };
        },
        VIEWPORT : function(uniformState, model) {
            return function() {
                return uniformState.viewportCartesian4;
            };
        }
        // JOINTMATRIX created in createCommand()
    };

    ///////////////////////////////////////////////////////////////////////////

    function getScalarUniformFunction(value, model) {
        var that = {
            value : value,
            clone : function(source, result) {
                return source;
            },
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    function getVec2UniformFunction(value, model) {
        var that = {
            value : Cartesian2.fromArray(value),
            clone : Cartesian2.clone,
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    function getVec3UniformFunction(value, model) {
        var that = {
            value : Cartesian3.fromArray(value),
            clone : Cartesian3.clone,
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    function getVec4UniformFunction(value, model) {
        var that = {
            value : Cartesian4.fromArray(value),
            clone : Cartesian4.clone,
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    function getMat2UniformFunction(value, model) {
        var that = {
            value : Matrix2.fromColumnMajorArray(value),
            clone : Matrix2.clone,
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    function getMat3UniformFunction(value, model) {
        var that = {
            value : Matrix3.fromColumnMajorArray(value),
            clone : Matrix3.clone,
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    function getMat4UniformFunction(value, model) {
        var that = {
            value : Matrix4.fromColumnMajorArray(value),
            clone : Matrix4.clone,
            func : function() {
                return that.value;
            }
        };
        return that;
    }

    ///////////////////////////////////////////////////////////////////////////

    function DelayLoadedTextureUniform(value, model) {
        this._value = undefined;
        this._textureId = value;
        this._model = model;
    }

    defineProperties(DelayLoadedTextureUniform.prototype, {
        value : {
            get : function() {
                // Use the default texture (1x1 white) until the model's texture is loaded
                if (!defined(this._value)) {
                    var texture = this._model._rendererResources.textures[this._textureId];
                    if (defined(texture)) {
                        this._value = texture;
                    } else {
                        return this._model._defaultTexture;
                    }
                }

                return this._value;
            },
            set : function(value) {
                this._value = value;
            }
        }
    });

    DelayLoadedTextureUniform.prototype.clone = function(source, result) {
        return source;
    };

    DelayLoadedTextureUniform.prototype.func = undefined;

    ///////////////////////////////////////////////////////////////////////////

    function getTextureUniformFunction(value, model) {
        var uniform = new DelayLoadedTextureUniform(value, model);
        // Define function here to access closure since 'this' can't be
        // used when the Renderer sets uniforms.
        uniform.func = function() {
            return uniform.value;
        };
        return uniform;
    }

    var gltfUniformFunctions = {};
    gltfUniformFunctions[WebGLConstants.FLOAT] = getScalarUniformFunction;
    gltfUniformFunctions[WebGLConstants.FLOAT_VEC2] = getVec2UniformFunction;
    gltfUniformFunctions[WebGLConstants.FLOAT_VEC3] = getVec3UniformFunction;
    gltfUniformFunctions[WebGLConstants.FLOAT_VEC4] = getVec4UniformFunction;
    gltfUniformFunctions[WebGLConstants.INT] = getScalarUniformFunction;
    gltfUniformFunctions[WebGLConstants.INT_VEC2] = getVec2UniformFunction;
    gltfUniformFunctions[WebGLConstants.INT_VEC3] = getVec3UniformFunction;
    gltfUniformFunctions[WebGLConstants.INT_VEC4] = getVec4UniformFunction;
    gltfUniformFunctions[WebGLConstants.BOOL] = getScalarUniformFunction;
    gltfUniformFunctions[WebGLConstants.BOOL_VEC2] = getVec2UniformFunction;
    gltfUniformFunctions[WebGLConstants.BOOL_VEC3] = getVec3UniformFunction;
    gltfUniformFunctions[WebGLConstants.BOOL_VEC4] = getVec4UniformFunction;
    gltfUniformFunctions[WebGLConstants.FLOAT_MAT2] = getMat2UniformFunction;
    gltfUniformFunctions[WebGLConstants.FLOAT_MAT3] = getMat3UniformFunction;
    gltfUniformFunctions[WebGLConstants.FLOAT_MAT4] = getMat4UniformFunction;
    gltfUniformFunctions[WebGLConstants.SAMPLER_2D] = getTextureUniformFunction;
    // GLTF_SPEC: Support SAMPLER_CUBE. https://github.com/KhronosGroup/glTF/issues/40

    var gltfUniformsFromNode = {
        MODEL : function(uniformState, model, runtimeNode) {
            return function() {
                return runtimeNode.computedMatrix;
            };
        },
        VIEW : function(uniformState, model, runtimeNode) {
            return function() {
                return uniformState.view;
            };
        },
        PROJECTION : function(uniformState, model, runtimeNode) {
            return function() {
                return uniformState.projection;
            };
        },
        MODELVIEW : function(uniformState, model, runtimeNode) {
            var mv = new Matrix4();
            return function() {
                return Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
            };
        },
        CESIUM_RTC_MODELVIEW : function(uniformState, model, runtimeNode) {
            // CESIUM_RTC extension
            var mvRtc = new Matrix4();
            return function() {
                Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvRtc);
                return Matrix4.setTranslation(mvRtc, model._rtcCenterEye, mvRtc);
            };
        },
        MODELVIEWPROJECTION : function(uniformState, model, runtimeNode) {
            var mvp = new Matrix4();
            return function() {
                Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvp);
                return Matrix4.multiply(uniformState._projection, mvp, mvp);
            };
        },
        MODELINVERSE : function(uniformState, model, runtimeNode) {
            var mInverse = new Matrix4();
            return function() {
                return Matrix4.inverse(runtimeNode.computedMatrix, mInverse);
            };
        },
        VIEWINVERSE : function(uniformState, model) {
            return function() {
                return uniformState.inverseView;
            };
        },
        PROJECTIONINVERSE : function(uniformState, model, runtimeNode) {
            return function() {
                return uniformState.inverseProjection;
            };
        },
        MODELVIEWINVERSE : function(uniformState, model, runtimeNode) {
            var mv = new Matrix4();
            var mvInverse = new Matrix4();
            return function() {
                Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
                return Matrix4.inverse(mv, mvInverse);
            };
        },
        MODELVIEWPROJECTIONINVERSE : function(uniformState, model, runtimeNode) {
            var mvp = new Matrix4();
            var mvpInverse = new Matrix4();
            return function() {
                Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mvp);
                Matrix4.multiply(uniformState._projection, mvp, mvp);
                return Matrix4.inverse(mvp, mvpInverse);
            };
        },
        MODELINVERSETRANSPOSE : function(uniformState, model, runtimeNode) {
            var mInverse = new Matrix4();
            var mInverseTranspose = new Matrix3();
            return function() {
                Matrix4.inverse(runtimeNode.computedMatrix, mInverse);
                Matrix4.getRotation(mInverse, mInverseTranspose);
                return Matrix3.transpose(mInverseTranspose, mInverseTranspose);
            };
        },
        MODELVIEWINVERSETRANSPOSE : function(uniformState, model, runtimeNode) {
            var mv = new Matrix4();
            var mvInverse = new Matrix4();
            var mvInverseTranspose = new Matrix3();
            return function() {
                Matrix4.multiplyTransformation(uniformState.view, runtimeNode.computedMatrix, mv);
                Matrix4.inverse(mv, mvInverse);
                Matrix4.getRotation(mvInverse, mvInverseTranspose);
                return Matrix3.transpose(mvInverseTranspose, mvInverseTranspose);
            };
        },
        VIEWPORT : function(uniformState, model, runtimeNode) {
            return function() {
                return uniformState.viewportCartesian4;
            };
        }
    };

    function getUniformFunctionFromSource(source, model, semantic, uniformState) {
        var runtimeNode = model._runtime.nodes[source];
        return gltfUniformsFromNode[semantic](uniformState, model, runtimeNode);
    }

    function createUniformMaps(model, context) {
        var loadResources = model._loadResources;

        if (!loadResources.finishedProgramCreation()) {
            return;
        }

        if (!loadResources.createUniformMaps) {
            return;
        }
        loadResources.createUniformMaps = false;

        var gltf = model.gltf;
        var materials = gltf.materials;
        var techniques = gltf.techniques;
        var uniformMaps = model._uniformMaps;

        for (var materialId in materials) {
            if (materials.hasOwnProperty(materialId)) {
                var material = materials[materialId];
                var instanceParameters = material.values;
                var technique = techniques[material.technique];
                var parameters = technique.parameters;
                var uniforms = technique.uniforms;

                var uniformMap = {};
                var uniformValues = {};
                var jointMatrixUniformName;

                // Uniform parameters
                for (var name in uniforms) {
                    if (uniforms.hasOwnProperty(name)) {
                        var parameterName = uniforms[name];
                        var parameter = parameters[parameterName];

                        // GLTF_SPEC: This does not take into account uniform arrays,
                        // indicated by parameters with a count property.
                        //
                        // https://github.com/KhronosGroup/glTF/issues/258

                        // GLTF_SPEC: In this implementation, material parameters with a
                        // semantic or targeted via a source (for animation) are not
                        // targetable for material animations.  Is this too strict?
                        //
                        // https://github.com/KhronosGroup/glTF/issues/142

                        if (defined(instanceParameters[parameterName])) {
                            // Parameter overrides by the instance technique
                            var uv = gltfUniformFunctions[parameter.type](instanceParameters[parameterName], model);
                            uniformMap[name] = uv.func;
                            uniformValues[parameterName] = uv;
                        } else if (defined(parameter.node)) {
                            uniformMap[name] = getUniformFunctionFromSource(parameter.node, model, parameter.semantic, context.uniformState);
                        } else if (defined(parameter.semantic)) {
                            if (parameter.semantic !== 'JOINTMATRIX') {
                                // Map glTF semantic to Cesium automatic uniform
                                uniformMap[name] = gltfSemanticUniforms[parameter.semantic](context.uniformState, model);
                            } else {
                                jointMatrixUniformName = name;
                            }
                        } else if (defined(parameter.value)) {
                            // Technique value that isn't overridden by a material
                            var uv2 = gltfUniformFunctions[parameter.type](parameter.value, model);
                            uniformMap[name] = uv2.func;
                            uniformValues[parameterName] = uv2;
                        }
                    }
                }

                var u = uniformMaps[materialId];
                u.uniformMap = uniformMap;                          // uniform name -> function for the renderer
                u.values = uniformValues;                           // material parameter name -> ModelMaterial for modifying the parameter at runtime
                u.jointMatrixUniformName = jointMatrixUniformName;
            }
        }
    }

    function scaleFromMatrix5Array(matrix) {
        return [matrix[0], matrix[1], matrix[2], matrix[3],
                matrix[5], matrix[6], matrix[7], matrix[8],
                matrix[10], matrix[11], matrix[12], matrix[13],
                matrix[15], matrix[16], matrix[17], matrix[18]];
    }

    function translateFromMatrix5Array(matrix) {
        return [matrix[20], matrix[21], matrix[22], matrix[23]];
    }

    function createUniformsForQuantizedAttributes(model, primitive, context) {
        var gltf = model.gltf;
        var accessors = gltf.accessors;
        var programId = getProgramForPrimitive(model, primitive);
        var quantizedUniforms = model._quantizedUniforms[programId];
        var setUniforms = {};
        var uniformMap = {};

        for (var attribute in primitive.attributes) {
            if (primitive.attributes.hasOwnProperty(attribute)) {
                var accessorId = primitive.attributes[attribute];
                var a = accessors[accessorId];
                var extensions = a.extensions;

                if (defined(extensions)) {
                    var quantizedAttributes = extensions.WEB3D_quantized_attributes;
                    if (defined(quantizedAttributes)) {
                        var decodeMatrix = quantizedAttributes.decodeMatrix;
                        var uniformVariable = 'gltf_u_dec_' + attribute.toLowerCase();

                        switch (a.type) {
                            case 'SCALAR':
                                uniformMap[uniformVariable] = getMat2UniformFunction(decodeMatrix, model).func;
                                setUniforms[uniformVariable] = true;
                                break;
                            case 'VEC2':
                                uniformMap[uniformVariable] = getMat3UniformFunction(decodeMatrix, model).func;
                                setUniforms[uniformVariable] = true;
                                break;
                            case 'VEC3':
                                uniformMap[uniformVariable] = getMat4UniformFunction(decodeMatrix, model).func;
                                setUniforms[uniformVariable] = true;
                                break;
                            case 'VEC4':
                                // VEC4 attributes are split into scale and translate because there is no mat5 in GLSL
                                var uniformVariableScale = uniformVariable + '_scale';
                                var uniformVariableTranslate = uniformVariable + '_translate';
                                uniformMap[uniformVariableScale] = getMat4UniformFunction(scaleFromMatrix5Array(decodeMatrix), model).func;
                                uniformMap[uniformVariableTranslate] = getVec4UniformFunction(translateFromMatrix5Array(decodeMatrix), model).func;
                                setUniforms[uniformVariableScale] = true;
                                setUniforms[uniformVariableTranslate] = true;
                                break;
                        }
                    }
                }
            }
        }

        // If there are any unset quantized uniforms in this program, they should be set to the identity
        for (var quantizedUniform in quantizedUniforms) {
            if (quantizedUniforms.hasOwnProperty(quantizedUniform)) {
                if (!setUniforms[quantizedUniform]) {
                    var properties = quantizedUniforms[quantizedUniform];
                    if (defined(properties.mat)) {
                        if (properties.mat === 2) {
                            uniformMap[quantizedUniform] = getMat2UniformFunction(Matrix2.IDENTITY, model).func;
                        } else if (properties.mat === 3) {
                            uniformMap[quantizedUniform] = getMat3UniformFunction(Matrix3.IDENTITY, model).func;
                        } else if (properties.mat === 4) {
                            uniformMap[quantizedUniform] = getMat4UniformFunction(Matrix4.IDENTITY, model).func;
                        }
                    }
                    if (defined(properties.vec)) {
                        if (properties.vec === 4) {
                            uniformMap[quantizedUniform] = getVec4UniformFunction([0, 0, 0, 0], model).func;
                        }
                    }
                }
            }
        }
        return uniformMap;
    }

    function createPickColorFunction(color) {
        return function() {
            return color;
        };
    }

    function createJointMatricesFunction(runtimeNode) {
        return function() {
            return runtimeNode.computedJointMatrices;
        };
    }

    function createSilhouetteColorFunction(model) {
        return function() {
            return model.silhouetteColor;
        };
    }

    function createSilhouetteSizeFunction(model) {
        return function() {
            return model.silhouetteSize;
        };
    }

    function createColorFunction(model) {
        return function() {
            return model.color;
        };
    }

    function createColorBlendFunction(model) {
        return function() {
            return ColorBlendMode.getColorBlend(model.colorBlendMode, model.colorBlendAmount);
        };
    }

    function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) {
        var nodeCommands = model._nodeCommands;
        var pickIds = model._pickIds;
        var allowPicking = model.allowPicking;
        var runtimeMeshesByName = model._runtime.meshesByName;

        var resources = model._rendererResources;
        var rendererVertexArrays = resources.vertexArrays;
        var rendererPrograms = resources.programs;
        var rendererPickPrograms = resources.pickPrograms;
        var rendererRenderStates = resources.renderStates;
        var uniformMaps = model._uniformMaps;

        var gltf = model.gltf;
        var accessors = gltf.accessors;
        var gltfMeshes = gltf.meshes;
        var techniques = gltf.techniques;
        var materials = gltf.materials;

        var meshes = gltfNode.meshes;
        var meshesLength = meshes.length;

        for (var j = 0; j < meshesLength; ++j) {
            var id = meshes[j];
            var mesh = gltfMeshes[id];
            var primitives = mesh.primitives;
            var length = primitives.length;

            // The glTF node hierarchy is a DAG so a node can have more than one
            // parent, so a node may already have commands.  If so, append more
            // since they will have a different model matrix.

            for (var i = 0; i < length; ++i) {
                var primitive = primitives[i];
                var ix = accessors[primitive.indices];
                var material = materials[primitive.material];
                var technique = techniques[material.technique];
                var programId = technique.program;

                var boundingSphere;
                var positionAccessor = primitive.attributes.POSITION;
                if (defined(positionAccessor)) {
                    var minMax = getAccessorMinMax(gltf, positionAccessor);
                    boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(minMax.min), Cartesian3.fromArray(minMax.max));
                }

                var vertexArray = rendererVertexArrays[id + '.primitive.' + i];
                var offset;
                var count;
                if (defined(ix)) {
                    count = ix.count;
                    offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType));  // glTF has offset in bytes.  Cesium has offsets in indices
                }
                else {
                    var positions = accessors[primitive.attributes.POSITION];
                    count = positions.count;
                    offset = 0;
                }

                var um = uniformMaps[primitive.material];
                var uniformMap = um.uniformMap;
                if (defined(um.jointMatrixUniformName)) {
                    var jointUniformMap = {};
                    jointUniformMap[um.jointMatrixUniformName] = createJointMatricesFunction(runtimeNode);

                    uniformMap = combine(uniformMap, jointUniformMap);
                }

                uniformMap = combine(uniformMap, {
                    gltf_color : createColorFunction(model),
                    gltf_colorBlend : createColorBlendFunction(model)
                });

                // Allow callback to modify the uniformMap
                if (defined(model._uniformMapLoaded)) {
                    uniformMap = model._uniformMapLoaded(uniformMap, programId, runtimeNode);
                }

                // Add uniforms for decoding quantized attributes if used
                if (usesExtension(model, 'WEB3D_quantized_attributes')) {
                    var quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive, context);
                    uniformMap = combine(uniformMap, quantizedUniformMap);
                }

                var rs = rendererRenderStates[material.technique];

                // GLTF_SPEC: Offical means to determine translucency. https://github.com/KhronosGroup/glTF/issues/105
                var isTranslucent = rs.blending.enabled;
                var owner = {
                    primitive : defaultValue(model.pickPrimitive, model),
                    id : model.id,
                    node : runtimeNode.publicNode,
                    mesh : runtimeMeshesByName[mesh.name]
                };

                var castShadows = ShadowMode.castShadows(model._shadows);
                var receiveShadows = ShadowMode.receiveShadows(model._shadows);

                var command = new DrawCommand({
                    boundingVolume : new BoundingSphere(), // updated in update()
                    cull : model.cull,
                    modelMatrix : new Matrix4(),           // computed in update()
                    primitiveType : primitive.mode,
                    vertexArray : vertexArray,
                    count : count,
                    offset : offset,
                    shaderProgram : rendererPrograms[technique.program],
                    castShadows : castShadows,
                    receiveShadows : receiveShadows,
                    uniformMap : uniformMap,
                    renderState : rs,
                    owner : owner,
                    pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE
                });

                var pickCommand;

                if (allowPicking) {
                    var pickUniformMap;

                    // Callback to override default model picking
                    if (defined(model._pickFragmentShaderLoaded)) {
                        if (defined(model._pickUniformMapLoaded)) {
                            pickUniformMap = model._pickUniformMapLoaded(uniformMap);
                        } else {
                            // This is unlikely, but could happen if the override shader does not
                            // need new uniforms since, for example, its pick ids are coming from
                            // a vertex attribute or are baked into the shader source.
                            pickUniformMap = combine(uniformMap);
                        }
                    } else {
                        var pickId = context.createPickId(owner);
                        pickIds.push(pickId);
                        var pickUniforms = {
                            czm_pickColor : createPickColorFunction(pickId.color)
                        };
                        pickUniformMap = combine(uniformMap, pickUniforms);
                    }

                    pickCommand = new DrawCommand({
                        boundingVolume : new BoundingSphere(), // updated in update()
                        cull : model.cull,
                        modelMatrix : new Matrix4(),           // computed in update()
                        primitiveType : primitive.mode,
                        vertexArray : vertexArray,
                        count : count,
                        offset : offset,
                        shaderProgram : rendererPickPrograms[technique.program],
                        uniformMap : pickUniformMap,
                        renderState : rs,
                        owner : owner,
                        pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE
                    });
                }

                var command2D;
                var pickCommand2D;
                if (!scene3DOnly) {
                    command2D = DrawCommand.shallowClone(command);
                    command2D.boundingVolume = new BoundingSphere(); // updated in update()
                    command2D.modelMatrix = new Matrix4();           // updated in update()

                    if (allowPicking) {
                        pickCommand2D = DrawCommand.shallowClone(pickCommand);
                        pickCommand2D.boundingVolume = new BoundingSphere(); // updated in update()
                        pickCommand2D.modelMatrix = new Matrix4();           // updated in update()
                    }
                }

                var nodeCommand = {
                    show : true,
                    boundingSphere : boundingSphere,
                    command : command,
                    pickCommand : pickCommand,
                    command2D : command2D,
                    pickCommand2D : pickCommand2D,
                    // Generated on demand when silhouette size is greater than 0.0 and silhouette alpha is greater than 0.0
                    silhouetteModelCommand : undefined,
                    silhouetteModelCommand2D : undefined,
                    silhouetteColorCommand : undefined,
                    silhouetteColorCommand2D : undefined,
                    // Generated on demand when color alpha is less than 1.0
                    translucentCommand : undefined,
                    translucentCommand2D : undefined
                };
                runtimeNode.commands.push(nodeCommand);
                nodeCommands.push(nodeCommand);
            }
        }
    }

    function createRuntimeNodes(model, context, scene3DOnly) {
        var loadResources = model._loadResources;

        if (!loadResources.finishedEverythingButTextureCreation()) {
            return;
        }

        if (!loadResources.createRuntimeNodes) {
            return;
        }
        loadResources.createRuntimeNodes = false;

        var rootNodes = [];
        var runtimeNodes = model._runtime.nodes;

        var gltf = model.gltf;
        var nodes = gltf.nodes;

        var scene = gltf.scenes[gltf.scene];
        var sceneNodes = scene.nodes;
        var length = sceneNodes.length;

        var stack = [];

        for (var i = 0; i < length; ++i) {
            stack.push({
                parentRuntimeNode : undefined,
                gltfNode : nodes[sceneNodes[i]],
                id : sceneNodes[i]
            });

            while (stack.length > 0) {
                var n = stack.pop();
                var parentRuntimeNode = n.parentRuntimeNode;
                var gltfNode = n.gltfNode;

                // Node hierarchy is a DAG so a node can have more than one parent so it may already exist
                var runtimeNode = runtimeNodes[n.id];
                if (runtimeNode.parents.length === 0) {
                    if (defined(gltfNode.matrix)) {
                        runtimeNode.matrix = Matrix4.fromColumnMajorArray(gltfNode.matrix);
                    } else {
                        // TRS converted to Cesium types
                        var rotation = gltfNode.rotation;
                        runtimeNode.translation = Cartesian3.fromArray(gltfNode.translation);
                        runtimeNode.rotation = Quaternion.unpack(rotation);
                        runtimeNode.scale = Cartesian3.fromArray(gltfNode.scale);
                    }
                }

                if (defined(parentRuntimeNode)) {
                    parentRuntimeNode.children.push(runtimeNode);
                    runtimeNode.parents.push(parentRuntimeNode);
                } else {
                    rootNodes.push(runtimeNode);
                }

                if (defined(gltfNode.meshes)) {
                    createCommand(model, gltfNode, runtimeNode, context, scene3DOnly);
                }

                var children = gltfNode.children;
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    stack.push({
                        parentRuntimeNode : runtimeNode,
                        gltfNode : nodes[children[k]],
                        id : children[k]
                    });
                }
            }
        }

        model._runtime.rootNodes = rootNodes;
        model._runtime.nodes = runtimeNodes;
    }

    function createResources(model, frameState) {
        var context = frameState.context;
        var scene3DOnly = frameState.scene3DOnly;

        if (model._loadRendererResourcesFromCache) {
            var resources = model._rendererResources;
            var cachedResources = model._cachedRendererResources;

            resources.buffers = cachedResources.buffers;
            resources.vertexArrays = cachedResources.vertexArrays;
            resources.programs = cachedResources.programs;
            resources.pickPrograms = cachedResources.pickPrograms;
            resources.silhouettePrograms = cachedResources.silhouettePrograms;
            resources.textures = cachedResources.textures;
            resources.samplers = cachedResources.samplers;
            resources.renderStates = cachedResources.renderStates;

            // Vertex arrays are unique to this model, create instead of using the cache.
            if (defined(model._precreatedAttributes)) {
                createVertexArrays(model, context);
            }
        } else {
            createBuffers(model, context); // using glTF bufferViews
            createPrograms(model, context);
            createSamplers(model, context);
            loadTexturesFromBufferViews(model);
            createTextures(model, context);
        }

        createSkins(model);
        createRuntimeAnimations(model);

        if (!model._loadRendererResourcesFromCache) {
            createVertexArrays(model, context); // using glTF meshes
            createRenderStates(model, context); // using glTF materials/techniques/states
            // Long-term, we might not cache render states if they could change
            // due to an animation, e.g., a uniform going from opaque to transparent.
            // Could use copy-on-write if it is worth it.  Probably overkill.
        }

        createUniformMaps(model, context);               // using glTF materials/techniques
        createRuntimeNodes(model, context, scene3DOnly); // using glTF scene
    }

    ///////////////////////////////////////////////////////////////////////////

    function getNodeMatrix(node, result) {
        var publicNode = node.publicNode;
        var publicMatrix = publicNode.matrix;

        if (publicNode.useMatrix && defined(publicMatrix)) {
            // Public matrix overrides orginial glTF matrix and glTF animations
            Matrix4.clone(publicMatrix, result);
        } else if (defined(node.matrix)) {
            Matrix4.clone(node.matrix, result);
        } else {
            Matrix4.fromTranslationQuaternionRotationScale(node.translation, node.rotation, node.scale, result);
            // Keep matrix returned by the node in-sync if the node is targeted by an animation.  Only TRS nodes can be targeted.
            publicNode.setMatrix(result);
        }
    }

    var scratchNodeStack = [];
    var scratchComputedTranslation = new Cartesian4();
    var scratchComputedMatrixIn2D = new Matrix4();

    function updateNodeHierarchyModelMatrix(model, modelTransformChanged, justLoaded, projection) {
        var maxDirtyNumber = model._maxDirtyNumber;
        var allowPicking = model.allowPicking;

        var rootNodes = model._runtime.rootNodes;
        var length = rootNodes.length;

        var nodeStack = scratchNodeStack;
        var computedModelMatrix = model._computedModelMatrix;

        if (model._mode !== SceneMode.SCENE3D) {
            var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation);
            if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) {
                computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D);
                model._rtcCenter = model._rtcCenter3D;
            } else {
                var center = model.boundingSphere.center;
                var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D);
                computedModelMatrix = Matrix4.multiply(to2D, computedModelMatrix, scratchComputedMatrixIn2D);

                if (defined(model._rtcCenter)) {
                    Matrix4.setTranslation(computedModelMatrix, Cartesian4.UNIT_W, computedModelMatrix);
                    model._rtcCenter = model._rtcCenter2D;
                }
            }
        }

        for (var i = 0; i < length; ++i) {
            var n = rootNodes[i];

            getNodeMatrix(n, n.transformToRoot);
            nodeStack.push(n);

            while (nodeStack.length > 0) {
                n = nodeStack.pop();
                var transformToRoot = n.transformToRoot;
                var commands = n.commands;

                if ((n.dirtyNumber === maxDirtyNumber) || modelTransformChanged || justLoaded) {
                    var nodeMatrix = Matrix4.multiplyTransformation(computedModelMatrix, transformToRoot, n.computedMatrix);
                    var commandsLength = commands.length;
                    if (commandsLength > 0) {
                        // Node has meshes, which has primitives.  Update their commands.
                        for (var j = 0 ; j < commandsLength; ++j) {
                            var primitiveCommand = commands[j];
                            var command = primitiveCommand.command;
                            Matrix4.clone(nodeMatrix, command.modelMatrix);

                            // PERFORMANCE_IDEA: Can use transformWithoutScale if no node up to the root has scale (including animation)
                            BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume);

                            if (defined(model._rtcCenter)) {
                                Cartesian3.add(model._rtcCenter, command.boundingVolume.center, command.boundingVolume.center);
                            }

                            if (allowPicking) {
                                var pickCommand = primitiveCommand.pickCommand;
                                Matrix4.clone(command.modelMatrix, pickCommand.modelMatrix);
                                BoundingSphere.clone(command.boundingVolume, pickCommand.boundingVolume);
                            }

                            // If the model crosses the IDL in 2D, it will be drawn in one viewport, but part of it
                            // will be clipped by the viewport. We create a second command that translates the model
                            // model matrix to the opposite side of the map so the part that was clipped in one viewport
                            // is drawn in the other.
                            command = primitiveCommand.command2D;
                            if (defined(command) && model._mode === SceneMode.SCENE2D) {
                                Matrix4.clone(nodeMatrix, command.modelMatrix);
                                command.modelMatrix[13] -= CesiumMath.sign(command.modelMatrix[13]) * 2.0 * CesiumMath.PI * projection.ellipsoid.maximumRadius;
                                BoundingSphere.transform(primitiveCommand.boundingSphere, command.modelMatrix, command.boundingVolume);

                                if (allowPicking) {
                                    var pickCommand2D = primitiveCommand.pickCommand2D;
                                    Matrix4.clone(command.modelMatrix, pickCommand2D.modelMatrix);
                                    BoundingSphere.clone(command.boundingVolume, pickCommand2D.boundingVolume);
                                }
                            }
                        }
                    }
                }

                var children = n.children;
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    var child = children[k];

                    // A node's transform needs to be updated if
                    // - It was targeted for animation this frame, or
                    // - Any of its ancestors were targeted for animation this frame

                    // PERFORMANCE_IDEA: if a child has multiple parents and only one of the parents
                    // is dirty, all the subtrees for each child instance will be dirty; we probably
                    // won't see this in the wild often.
                    child.dirtyNumber = Math.max(child.dirtyNumber, n.dirtyNumber);

                    if ((child.dirtyNumber === maxDirtyNumber) || justLoaded) {
                        // Don't check for modelTransformChanged since if only the model's model matrix changed,
                        // we do not need to rebuild the local transform-to-root, only the final
                        // [model's-model-matrix][transform-to-root] above.
                        getNodeMatrix(child, child.transformToRoot);
                        Matrix4.multiplyTransformation(transformToRoot, child.transformToRoot, child.transformToRoot);
                    }

                    nodeStack.push(child);
                }
            }
        }

        ++model._maxDirtyNumber;
    }

    var scratchObjectSpace = new Matrix4();

    function applySkins(model) {
        var skinnedNodes = model._runtime.skinnedNodes;
        var length = skinnedNodes.length;

        for (var i = 0; i < length; ++i) {
            var node = skinnedNodes[i];

            scratchObjectSpace = Matrix4.inverseTransformation(node.transformToRoot, scratchObjectSpace);

            var computedJointMatrices = node.computedJointMatrices;
            var joints = node.joints;
            var bindShapeMatrix = node.bindShapeMatrix;
            var inverseBindMatrices = node.inverseBindMatrices;
            var inverseBindMatricesLength = inverseBindMatrices.length;

            for (var m = 0; m < inverseBindMatricesLength; ++m) {
                // [joint-matrix] = [node-to-root^-1][joint-to-root][inverse-bind][bind-shape]
                if (!defined(computedJointMatrices[m])) {
                    computedJointMatrices[m] = new Matrix4();
                }
                computedJointMatrices[m] = Matrix4.multiplyTransformation(scratchObjectSpace, joints[m].transformToRoot, computedJointMatrices[m]);
                computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], inverseBindMatrices[m], computedJointMatrices[m]);
                if (defined(bindShapeMatrix)) {
                    // Optimization for when bind shape matrix is the identity.
                    computedJointMatrices[m] = Matrix4.multiplyTransformation(computedJointMatrices[m], bindShapeMatrix, computedJointMatrices[m]);
                }
            }
        }
    }


    function updatePerNodeShow(model) {
        // Totally not worth it, but we could optimize this:
        // http://blogs.agi.com/insight3d/index.php/2008/02/13/deletion-in-bounding-volume-hierarchies/

        var rootNodes = model._runtime.rootNodes;
        var length = rootNodes.length;

        var nodeStack = scratchNodeStack;

        for (var i = 0; i < length; ++i) {
            var n = rootNodes[i];
            n.computedShow = n.publicNode.show;
            nodeStack.push(n);

            while (nodeStack.length > 0) {
                n = nodeStack.pop();
                var show = n.computedShow;

                var nodeCommands = n.commands;
                var nodeCommandsLength = nodeCommands.length;
                for (var j = 0 ; j < nodeCommandsLength; ++j) {
                    nodeCommands[j].show = show;
                }
                // if commandsLength is zero, the node has a light or camera

                var children = n.children;
                var childrenLength = children.length;
                for (var k = 0; k < childrenLength; ++k) {
                    var child = children[k];
                    // Parent needs to be shown for child to be shown.
                    child.computedShow = show && child.publicNode.show;
                    nodeStack.push(child);
                }
            }
        }
    }

    function updatePickIds(model, context) {
        var id = model.id;
        if (model._id !== id) {
            model._id = id;

            var pickIds = model._pickIds;
            var length = pickIds.length;
            for (var i = 0; i < length; ++i) {
                pickIds[i].object.id = id;
            }
        }
    }

    function updateWireframe(model) {
        if (model._debugWireframe !== model.debugWireframe) {
            model._debugWireframe = model.debugWireframe;

            // This assumes the original primitive was TRIANGLES and that the triangles
            // are connected for the wireframe to look perfect.
            var primitiveType = model.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES;
            var nodeCommands = model._nodeCommands;
            var length = nodeCommands.length;

            for (var i = 0; i < length; ++i) {
                nodeCommands[i].command.primitiveType = primitiveType;
            }
        }
    }

    function updateShowBoundingVolume(model) {
        if (model.debugShowBoundingVolume !== model._debugShowBoundingVolume) {
            model._debugShowBoundingVolume = model.debugShowBoundingVolume;

            var debugShowBoundingVolume = model.debugShowBoundingVolume;
            var nodeCommands = model._nodeCommands;
            var length = nodeCommands.length;

            for (var i = 0; i < length; ++i) {
                nodeCommands[i].command.debugShowBoundingVolume = debugShowBoundingVolume;
            }
        }
    }

    function updateShadows(model) {
        if (model.shadows !== model._shadows) {
            model._shadows = model.shadows;

            var castShadows = ShadowMode.castShadows(model.shadows);
            var receiveShadows = ShadowMode.receiveShadows(model.shadows);
            var nodeCommands = model._nodeCommands;
            var length = nodeCommands.length;

            for (var i = 0; i < length; i++) {
                var nodeCommand = nodeCommands[i];
                nodeCommand.command.castShadows = castShadows;
                nodeCommand.command.receiveShadows = receiveShadows;
            }
        }
    }

    function getTranslucentRenderState(renderState) {
        var rs = clone(renderState, true);
        rs.cull.enabled = false;
        rs.depthTest.enabled = true;
        rs.depthMask = false;
        rs.blending = BlendingState.ALPHA_BLEND;

        return RenderState.fromCache(rs);
    }

    function deriveTranslucentCommand(command) {
        var translucentCommand = DrawCommand.shallowClone(command);
        translucentCommand.pass = Pass.TRANSLUCENT;
        translucentCommand.renderState = getTranslucentRenderState(command.renderState);
        return translucentCommand;
    }

    function updateColor(model, frameState) {
        // Generate translucent commands when the blend color has an alpha in the range (0.0, 1.0) exclusive
        var scene3DOnly = frameState.scene3DOnly;
        var alpha = model.color.alpha;
        if ((alpha > 0.0) && (alpha < 1.0)) {
            var nodeCommands = model._nodeCommands;
            var length = nodeCommands.length;
            if (!defined(nodeCommands[0].translucentCommand)) {
                for (var i = 0; i < length; ++i) {
                    var nodeCommand = nodeCommands[i];
                    var command = nodeCommand.command;
                    nodeCommand.translucentCommand = deriveTranslucentCommand(command);
                    if (!scene3DOnly) {
                        var command2D = nodeCommand.command2D;
                        nodeCommand.translucentCommand2D = deriveTranslucentCommand(command2D);
                    }
                }
            }
        }
    }

    function getProgramId(model, program) {
        var programs = model._rendererResources.programs;
        for (var id in programs) {
            if (programs.hasOwnProperty(id)) {
                if (programs[id] === program) {
                    return id;
                }
            }
        }
    }

    function createSilhouetteProgram(model, program, frameState) {
        var vs = program.vertexShaderSource.sources[0];
        var attributeLocations = program._attributeLocations;
        var normalAttributeName = model._normalAttributeName;

        // Modified from http://forum.unity3d.com/threads/toon-outline-but-with-diffuse-surface.24668/
        vs = ShaderSource.replaceMain(vs, 'gltf_silhouette_main');
        vs +=
            'uniform float gltf_silhouetteSize; \n' +
            'void main() \n' +
            '{ \n' +
            '    gltf_silhouette_main(); \n' +
            '    vec3 n = normalize(czm_normal3D * ' + normalAttributeName + '); \n' +
            '    n.x *= czm_projection[0][0]; \n' +
            '    n.y *= czm_projection[1][1]; \n' +
            '    vec4 clip = gl_Position; \n' +
            '    clip.xy += n.xy * clip.w * gltf_silhouetteSize / czm_viewport.z; \n' +
            '    gl_Position = clip; \n' +
            '}';

        var fs =
            'uniform vec4 gltf_silhouetteColor; \n' +
            'void main() \n' +
            '{ \n' +
            '    gl_FragColor = gltf_silhouetteColor; \n' +
            '}';

        return ShaderProgram.fromCache({
            context : frameState.context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });
    }

    function hasSilhouette(model, frameState) {
        return silhouetteSupported(frameState.context) && (model.silhouetteSize > 0.0) && (model.silhouetteColor.alpha > 0.0) && defined(model._normalAttributeName);
    }

    function hasTranslucentCommands(model) {
        var nodeCommands = model._nodeCommands;
        var length = nodeCommands.length;
        for (var i = 0; i < length; ++i) {
            var nodeCommand = nodeCommands[i];
            var command = nodeCommand.command;
            if (command.pass === Pass.TRANSLUCENT) {
                return true;
            }
        }
        return false;
    }

    function isTranslucent(model) {
        return (model.color.alpha > 0.0) && (model.color.alpha < 1.0);
    }

    function isInvisible(model) {
        return (model.color.alpha === 0.0);
    }

    function alphaDirty(currAlpha, prevAlpha) {
        // Returns whether the alpha state has changed between invisible, translucent, or opaque
        return (Math.floor(currAlpha) !== Math.floor(prevAlpha)) || (Math.ceil(currAlpha) !== Math.ceil(prevAlpha));
    }

    var silhouettesLength = 0;

    function createSilhouetteCommands(model, frameState) {
        // Wrap around after exceeding the 8-bit stencil limit.
        // The reference is unique to each model until this point.
        var stencilReference = (++silhouettesLength) % 255;

        // If the model is translucent the silhouette needs to be in the translucent pass.
        // Otherwise the silhouette would be rendered before the model.
        var silhouetteTranslucent = hasTranslucentCommands(model) || isTranslucent(model) || (model.silhouetteColor.alpha < 1.0);
        var silhouettePrograms = model._rendererResources.silhouettePrograms;
        var scene3DOnly = frameState.scene3DOnly;
        var nodeCommands = model._nodeCommands;
        var length = nodeCommands.length;
        for (var i = 0; i < length; ++i) {
            var nodeCommand = nodeCommands[i];
            var command = nodeCommand.command;

            // Create model command
            var modelCommand = isTranslucent(model) ? nodeCommand.translucentCommand : command;
            var silhouetteModelCommand = DrawCommand.shallowClone(modelCommand);
            var renderState = clone(modelCommand.renderState);

            // Write the reference value into the stencil buffer.
            renderState.stencilTest = {
                enabled : true,
                frontFunction : WebGLConstants.ALWAYS,
                backFunction : WebGLConstants.ALWAYS,
                reference : stencilReference,
                mask : ~0,
                frontOperation : {
                    fail : WebGLConstants.KEEP,
                    zFail : WebGLConstants.KEEP,
                    zPass : WebGLConstants.REPLACE
                },
                backOperation : {
                    fail : WebGLConstants.KEEP,
                    zFail : WebGLConstants.KEEP,
                    zPass : WebGLConstants.REPLACE
                }
            };

            if (isInvisible(model)) {
                // When the model is invisible disable color and depth writes but still write into the stencil buffer
                renderState.colorMask = {
                    red : false,
                    green : false,
                    blue : false,
                    alpha : false
                };
                renderState.depthMask = false;
            }
            renderState = RenderState.fromCache(renderState);
            silhouetteModelCommand.renderState = renderState;
            nodeCommand.silhouetteModelCommand = silhouetteModelCommand;

            // Create color command
            var silhouetteColorCommand = DrawCommand.shallowClone(command);
            renderState = clone(command.renderState, true);
            renderState.depthTest.enabled = true;
            renderState.cull.enabled = false;
            if (silhouetteTranslucent) {
                silhouetteColorCommand.pass = Pass.TRANSLUCENT;
                renderState.depthMask = false;
                renderState.blending = BlendingState.ALPHA_BLEND;
            }

            // Only render silhouette if the value in the stencil buffer equals the reference
            renderState.stencilTest = {
                enabled : true,
                frontFunction : WebGLConstants.NOTEQUAL,
                backFunction : WebGLConstants.NOTEQUAL,
                reference : stencilReference,
                mask : ~0,
                frontOperation : {
                    fail : WebGLConstants.KEEP,
                    zFail : WebGLConstants.KEEP,
                    zPass : WebGLConstants.KEEP
                },
                backOperation : {
                    fail : WebGLConstants.KEEP,
                    zFail : WebGLConstants.KEEP,
                    zPass : WebGLConstants.KEEP
                }
            };
            renderState = RenderState.fromCache(renderState);

            // If the silhouette program has already been cached use it
            var program = command.shaderProgram;
            var id = getProgramId(model, program);
            var silhouetteProgram = silhouettePrograms[id];
            if (!defined(silhouetteProgram)) {
                silhouetteProgram = createSilhouetteProgram(model, program, frameState);
                silhouettePrograms[id] = silhouetteProgram;
            }

            var silhouetteUniformMap = combine(command.uniformMap, {
                gltf_silhouetteColor : createSilhouetteColorFunction(model),
                gltf_silhouetteSize : createSilhouetteSizeFunction(model)
            });

            silhouetteColorCommand.renderState = renderState;
            silhouetteColorCommand.shaderProgram = silhouetteProgram;
            silhouetteColorCommand.uniformMap = silhouetteUniformMap;
            silhouetteColorCommand.castShadows = false;
            silhouetteColorCommand.receiveShadows = false;
            nodeCommand.silhouetteColorCommand = silhouetteColorCommand;

            if (!scene3DOnly) {
                var command2D = nodeCommand.command2D;
                var silhouetteModelCommand2D = DrawCommand.shallowClone(silhouetteModelCommand);
                silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume;
                silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix;
                nodeCommand.silhouetteModelCommand2D = silhouetteModelCommand2D;

                var silhouetteColorCommand2D = DrawCommand.shallowClone(silhouetteColorCommand);
                silhouetteModelCommand2D.boundingVolume = command2D.boundingVolume;
                silhouetteModelCommand2D.modelMatrix = command2D.modelMatrix;
                nodeCommand.silhouetteColorCommand2D = silhouetteColorCommand2D;
            }
        }
    }

    function updateSilhouette(model, frameState) {
        // Generate silhouette commands when the silhouette size is greater than 0.0 and the alpha is greater than 0.0
        // There are two silhouette commands:
        //     1. silhouetteModelCommand : render model normally while enabling stencil mask
        //     2. silhouetteColorCommand : render enlarged model with a solid color while enabling stencil tests
        if (!hasSilhouette(model, frameState)) {
            return;
        }

        var nodeCommands = model._nodeCommands;
        var dirty = alphaDirty(model.color.alpha, model._colorPreviousAlpha) ||
                    alphaDirty(model.silhouetteColor.alpha, model._silhouetteColorPreviousAlpha) ||
                    !defined(nodeCommands[0].silhouetteModelCommand);

        model._colorPreviousAlpha = model.color.alpha;
        model._silhouetteColorPreviousAlpha = model.silhouetteColor.alpha;

        if (dirty) {
            createSilhouetteCommands(model, frameState);
        }
    }

    var scratchBoundingSphere = new BoundingSphere();

    function scaleInPixels(positionWC, radius, frameState) {
        scratchBoundingSphere.center = positionWC;
        scratchBoundingSphere.radius = radius;
        return frameState.camera.getPixelSize(scratchBoundingSphere, frameState.context.drawingBufferWidth, frameState.context.drawingBufferHeight);
    }

    var scratchPosition = new Cartesian3();
    var scratchCartographic = new Cartographic();

    function getScale(model, frameState) {
        var scale = model.scale;

        if (model.minimumPixelSize !== 0.0) {
            // Compute size of bounding sphere in pixels
            var context = frameState.context;
            var maxPixelSize = Math.max(context.drawingBufferWidth, context.drawingBufferHeight);
            var m = defined(model._clampedModelMatrix) ? model._clampedModelMatrix : model.modelMatrix;
            scratchPosition.x = m[12];
            scratchPosition.y = m[13];
            scratchPosition.z = m[14];

            if (defined(model._rtcCenter)) {
                Cartesian3.add(model._rtcCenter, scratchPosition, scratchPosition);
            }

            if (model._mode !== SceneMode.SCENE3D) {
                var projection = frameState.mapProjection;
                var cartographic = projection.ellipsoid.cartesianToCartographic(scratchPosition, scratchCartographic);
                projection.project(cartographic, scratchPosition);
                Cartesian3.fromElements(scratchPosition.z, scratchPosition.x, scratchPosition.y, scratchPosition);
            }

            var radius = model.boundingSphere.radius;
            var metersPerPixel = scaleInPixels(scratchPosition, radius, frameState);

            // metersPerPixel is always > 0.0
            var pixelsPerMeter = 1.0 / metersPerPixel;
            var diameterInPixels = Math.min(pixelsPerMeter * (2.0 * radius), maxPixelSize);

            // Maintain model's minimum pixel size
            if (diameterInPixels < model.minimumPixelSize) {
                scale = (model.minimumPixelSize * metersPerPixel) / (2.0 * model._initialRadius);
            }
        }

        return defined(model.maximumScale) ? Math.min(model.maximumScale, scale) : scale;
    }

    function releaseCachedGltf(model) {
        if (defined(model._cacheKey) && defined(model._cachedGltf) && (--model._cachedGltf.count === 0)) {
            delete gltfCache[model._cacheKey];
        }
        model._cachedGltf = undefined;
    }

    function checkSupportedExtensions(model) {
        var extensionsUsed = model.gltf.extensionsUsed;
        if (defined(extensionsUsed)) {
            var extensionsUsedCount = extensionsUsed.length;
            for (var index=0;index<extensionsUsedCount;++index) {
                var extension = extensionsUsed[index];

                if (extension !== 'CESIUM_RTC' && extension !== 'KHR_binary_glTF' &&
                    extension !== 'KHR_materials_common' && extension !== 'WEB3D_quantized_attributes') {
                    throw new RuntimeError('Unsupported glTF Extension: ' + extension);
                }
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////

    function CachedRendererResources(context, cacheKey) {
        this.buffers = undefined;
        this.vertexArrays = undefined;
        this.programs = undefined;
        this.pickPrograms = undefined;
        this.silhouettePrograms = undefined;
        this.textures = undefined;
        this.samplers = undefined;
        this.renderStates = undefined;
        this.ready = false;

        this.context = context;
        this.cacheKey = cacheKey;
        this.count = 0;
    }

    function destroy(property) {
        for (var name in property) {
            if (property.hasOwnProperty(name)) {
                property[name].destroy();
            }
        }
    }

    function destroyCachedRendererResources(resources) {
        destroy(resources.buffers);
        destroy(resources.vertexArrays);
        destroy(resources.programs);
        destroy(resources.pickPrograms);
        destroy(resources.silhouettePrograms);
        destroy(resources.textures);
    }

    CachedRendererResources.prototype.release = function() {
        if (--this.count === 0) {
            if (defined(this.cacheKey)) {
                // Remove if this was cached
                delete this.context.cache.modelRendererResourceCache[this.cacheKey];
            }
            destroyCachedRendererResources(this);
            return destroyObject(this);
        }

        return undefined;
    };

    ///////////////////////////////////////////////////////////////////////////

    function getUpdateHeightCallback(model, ellipsoid, cartoPosition) {
        return function (clampedPosition) {
            if (model.heightReference === HeightReference.RELATIVE_TO_GROUND) {
                var clampedCart = ellipsoid.cartesianToCartographic(clampedPosition, scratchCartographic);
                clampedCart.height += cartoPosition.height;
                ellipsoid.cartographicToCartesian(clampedCart, clampedPosition);
            }

            var clampedModelMatrix = model._clampedModelMatrix;

            // Modify clamped model matrix to use new height
            Matrix4.clone(model.modelMatrix, clampedModelMatrix);
            clampedModelMatrix[12] = clampedPosition.x;
            clampedModelMatrix[13] = clampedPosition.y;
            clampedModelMatrix[14] = clampedPosition.z;

            model._heightChanged = true;
        };
    }

    function updateClamping(model) {
        if (defined(model._removeUpdateHeightCallback)) {
            model._removeUpdateHeightCallback();
            model._removeUpdateHeightCallback = undefined;
        }

        var scene = model._scene;
        if (!defined(scene) || (model.heightReference === HeightReference.NONE)) {
                        if (model.heightReference !== HeightReference.NONE) {
                throw new DeveloperError('Height reference is not supported without a scene.');
            }
                        model._clampedModelMatrix = undefined;
            return;
        }

        var globe = scene.globe;
        var ellipsoid = globe.ellipsoid;

        // Compute cartographic position so we don't recompute every update
        var modelMatrix = model.modelMatrix;
        scratchPosition.x = modelMatrix[12];
        scratchPosition.y = modelMatrix[13];
        scratchPosition.z = modelMatrix[14];
        var cartoPosition = ellipsoid.cartesianToCartographic(scratchPosition);

        if (!defined(model._clampedModelMatrix)) {
            model._clampedModelMatrix = Matrix4.clone(modelMatrix, new Matrix4());
        }

        // Install callback to handle updating of terrain tiles
        var surface = globe._surface;
        model._removeUpdateHeightCallback = surface.updateHeight(cartoPosition, getUpdateHeightCallback(model, ellipsoid, cartoPosition));

        // Set the correct height now
        var height = globe.getHeight(cartoPosition);
        if (defined(height)) {
            // Get callback with cartoPosition being the non-clamped position
            var cb = getUpdateHeightCallback(model, ellipsoid, cartoPosition);

            // Compute the clamped cartesian and call updateHeight callback
            Cartographic.clone(cartoPosition, scratchCartographic);
            scratchCartographic.height = height;
            ellipsoid.cartographicToCartesian(scratchCartographic, scratchPosition);
            cb(scratchPosition);
        }
    }

    var scratchDisplayConditionCartesian = new Cartesian3();
    var scratchDistanceDisplayConditionCartographic = new Cartographic();

    function distanceDisplayConditionVisible(model, frameState) {
        var distance2;
        var ddc = model.distanceDisplayCondition;
        var nearSquared = ddc.near * ddc.near;
        var farSquared = ddc.far * ddc.far;

        if (frameState.mode === SceneMode.SCENE2D) {
            var frustum2DWidth = frameState.camera.frustum.right - frameState.camera.frustum.left;
            distance2 = frustum2DWidth * 0.5;
            distance2 = distance2 * distance2;
        } else {
            // Distance to center of primitive's reference frame
            var position = Matrix4.getTranslation(model.modelMatrix, scratchDisplayConditionCartesian);
            if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
                var projection = frameState.mapProjection;
                var ellipsoid = projection.ellipsoid;
                var cartographic = ellipsoid.cartesianToCartographic(position, scratchDistanceDisplayConditionCartographic);
                position = projection.project(cartographic, position);
                Cartesian3.fromElements(position.z, position.x, position.y, position);
            }
            distance2 = Cartesian3.distanceSquared(position, frameState.camera.positionWC);
        }

        return (distance2 >= nearSquared) && (distance2 <= farSquared);
    }

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {RuntimeError} Failed to load external reference.
     */
    Model.prototype.update = function(frameState) {
        if (frameState.mode === SceneMode.MORPHING) {
            return;
        }

        var context = frameState.context;
        this._defaultTexture = context.defaultTexture;

        if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) {
            // Use renderer resources from cache instead of loading/creating them?
            var cachedRendererResources;
            var cacheKey = this.cacheKey;
            if (defined(cacheKey)) {
                context.cache.modelRendererResourceCache = defaultValue(context.cache.modelRendererResourceCache, {});
                var modelCaches = context.cache.modelRendererResourceCache;

                cachedRendererResources = modelCaches[this.cacheKey];
                if (defined(cachedRendererResources)) {
                    if (!cachedRendererResources.ready) {
                        // Cached resources for the model are not loaded yet.  We'll
                        // try again every frame until they are.
                        return;
                    }

                    ++cachedRendererResources.count;
                    this._loadRendererResourcesFromCache = true;
                } else {
                    cachedRendererResources = new CachedRendererResources(context, cacheKey);
                    cachedRendererResources.count = 1;
                    modelCaches[this.cacheKey] = cachedRendererResources;
                }
                this._cachedRendererResources = cachedRendererResources;
            } else {
                cachedRendererResources = new CachedRendererResources(context);
                cachedRendererResources.count = 1;
                this._cachedRendererResources = cachedRendererResources;
            }

            this._state = ModelState.LOADING;

            this._boundingSphere = computeBoundingSphere(this, this.gltf);
            this._initialRadius = this._boundingSphere.radius;

            checkSupportedExtensions(this);
            if (this._state !== ModelState.FAILED) {
                var extensions = this.gltf.extensions;
                if (defined(extensions) && defined(extensions.CESIUM_RTC)) {
                    var center = Cartesian3.fromArray(extensions.CESIUM_RTC.center);
                    if (!Cartesian3.equals(center, Cartesian3.ZERO)) {
                        this._rtcCenter3D = center;

                        var projection = frameState.mapProjection;
                        var ellipsoid = projection.ellipsoid;
                        var cartographic = ellipsoid.cartesianToCartographic(this._rtcCenter3D);
                        var projectedCart = projection.project(cartographic);
                        Cartesian3.fromElements(projectedCart.z, projectedCart.x, projectedCart.y, projectedCart);
                        this._rtcCenter2D = projectedCart;

                        this._rtcCenterEye = new Cartesian3();
                        this._rtcCenter = this._rtcCenter3D;
                    }
                }

                this._loadResources = new LoadResources();
                parse(this, context);
            }
        }

        var loadResources = this._loadResources;
        var incrementallyLoadTextures = this._incrementallyLoadTextures;
        var justLoaded = false;

        if (this._state === ModelState.LOADING) {
            // Create WebGL resources as buffers/shaders/textures are downloaded
            createResources(this, frameState);

            // Transition from LOADING -> LOADED once resources are downloaded and created.
            // Textures may continue to stream in while in the LOADED state.
            if (loadResources.finished() ||
                    (incrementallyLoadTextures && loadResources.finishedEverythingButTextureCreation())) {
                this._state = ModelState.LOADED;
                justLoaded = true;
            }
        }

        // Incrementally stream textures.
        if (defined(loadResources) && (this._state === ModelState.LOADED)) {
            // Also check justLoaded so we don't process twice during the transition frame
            if (incrementallyLoadTextures && !justLoaded) {
                createResources(this, frameState);
            }

            if (loadResources.finished()) {
                this._loadResources = undefined;  // Clear CPU memory since WebGL resources were created.

                var resources = this._rendererResources;
                var cachedResources = this._cachedRendererResources;

                cachedResources.buffers = resources.buffers;
                cachedResources.vertexArrays = resources.vertexArrays;
                cachedResources.programs = resources.programs;
                cachedResources.pickPrograms = resources.pickPrograms;
                cachedResources.silhouettePrograms = resources.silhouettePrograms;
                cachedResources.textures = resources.textures;
                cachedResources.samplers = resources.samplers;
                cachedResources.renderStates = resources.renderStates;
                cachedResources.ready = true;

                // The normal attribute name is required for silhouettes, so get it before the gltf JSON is released
                this._normalAttributeName = getAttributeOrUniformBySemantic(this.gltf, 'NORMAL');

                // Vertex arrays are unique to this model, do not store in cache.
                if (defined(this._precreatedAttributes)) {
                    cachedResources.vertexArrays = {};
                }

                if (this.releaseGltfJson) {
                    releaseCachedGltf(this);
                }
            }
        }

        var silhouette = hasSilhouette(this, frameState);
        var translucent = isTranslucent(this);
        var invisible = isInvisible(this);
        var displayConditionPassed = defined(this.distanceDisplayCondition) ? distanceDisplayConditionVisible(this, frameState) : true;
        var show = this.show && displayConditionPassed && (this.scale !== 0.0) && (!invisible || silhouette);

        if ((show && this._state === ModelState.LOADED) || justLoaded) {
            var animated = this.activeAnimations.update(frameState) || this._cesiumAnimationsDirty;
            this._cesiumAnimationsDirty = false;
            this._dirty = false;
            var modelMatrix = this.modelMatrix;

            var modeChanged = frameState.mode !== this._mode;
            this._mode = frameState.mode;

            // Model's model matrix needs to be updated
            var modelTransformChanged = !Matrix4.equals(this._modelMatrix, modelMatrix) ||
                (this._scale !== this.scale) ||
                (this._minimumPixelSize !== this.minimumPixelSize) || (this.minimumPixelSize !== 0.0) || // Minimum pixel size changed or is enabled
                (this._maximumScale !== this.maximumScale) ||
                (this._heightReference !== this.heightReference) || this._heightChanged ||
                modeChanged;

            if (modelTransformChanged || justLoaded) {
                Matrix4.clone(modelMatrix, this._modelMatrix);

                updateClamping(this);

                if (defined(this._clampedModelMatrix)) {
                    modelMatrix = this._clampedModelMatrix;
                }

                this._scale = this.scale;
                this._minimumPixelSize = this.minimumPixelSize;
                this._maximumScale = this.maximumScale;
                this._heightReference = this.heightReference;
                this._heightChanged = false;

                var scale = getScale(this, frameState);
                var computedModelMatrix = this._computedModelMatrix;
                Matrix4.multiplyByUniformScale(modelMatrix, scale, computedModelMatrix);
                if (this._upAxis === Axis.Y) {
                    Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix);
                } else if (this._upAxis === Axis.X) {
                    Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix);
                }
            }

            // Update modelMatrix throughout the graph as needed
            if (animated || modelTransformChanged || justLoaded) {
                updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection);
                this._dirty = true;

                if (animated || justLoaded) {
                    // Apply skins if animation changed any node transforms
                    applySkins(this);
                }
            }

            if (this._perNodeShowDirty) {
                this._perNodeShowDirty = false;
                updatePerNodeShow(this);
            }
            updatePickIds(this, context);
            updateWireframe(this);
            updateShowBoundingVolume(this);
            updateShadows(this);
            updateColor(this, frameState);
            updateSilhouette(this, frameState);
        }

        if (justLoaded) {
            // Called after modelMatrix update.
            var model = this;
            frameState.afterRender.push(function() {
                model._ready = true;
                model._readyPromise.resolve(model);
            });
            return;
        }

        // We don't check show at the top of the function since we
        // want to be able to progressively load models when they are not shown,
        // and then have them visible immediately when show is set to true.
        if (show && !this._ignoreCommands) {
            // PERFORMANCE_IDEA: This is terrible
            var commandList = frameState.commandList;
            var passes = frameState.passes;
            var nodeCommands = this._nodeCommands;
            var length = nodeCommands.length;
            var i;
            var nc;

            var idl2D = frameState.mapProjection.ellipsoid.maximumRadius * CesiumMath.PI;
            var boundingVolume;

            if (passes.render) {
                for (i = 0; i < length; ++i) {
                    nc = nodeCommands[i];
                    if (nc.show) {
                        var command = translucent ? nc.translucentCommand : nc.command;
                        command = silhouette ? nc.silhouetteModelCommand : command;
                        commandList.push(command);
                        boundingVolume = nc.command.boundingVolume;
                        if (frameState.mode === SceneMode.SCENE2D &&
                            (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
                            var command2D = translucent ? nc.translucentCommand2D : nc.command2D;
                            command2D = silhouette ? nc.silhouetteModelCommand2D : command2D;
                            commandList.push(command2D);
                        }
                    }
                }

                if (silhouette) {
                    // Render second silhouette pass
                    for (i = 0; i < length; ++i) {
                        nc = nodeCommands[i];
                        if (nc.show) {
                            commandList.push(nc.silhouetteColorCommand);
                            boundingVolume = nc.command.boundingVolume;
                            if (frameState.mode === SceneMode.SCENE2D &&
                                (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
                                commandList.push(nc.silhouetteColorCommand2D);
                            }
                        }
                    }
                }
            }

            if (passes.pick && this.allowPicking) {
                for (i = 0; i < length; ++i) {
                    nc = nodeCommands[i];
                    if (nc.show) {
                        var pickCommand = nc.pickCommand;
                        commandList.push(pickCommand);

                        boundingVolume = pickCommand.boundingVolume;
                        if (frameState.mode === SceneMode.SCENE2D &&
                            (boundingVolume.center.y + boundingVolume.radius > idl2D || boundingVolume.center.y - boundingVolume.radius < idl2D)) {
                            commandList.push(nc.pickCommand2D);
                        }
                    }
                }
            }
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Model#destroy
     */
    Model.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * model = model && model.destroy();
     *
     * @see Model#isDestroyed
     */
    Model.prototype.destroy = function() {
        // Vertex arrays are unique to this model, destroy here.
        if (defined(this._precreatedAttributes)) {
            destroy(this._rendererResources.vertexArrays);
        }

        this._rendererResources = undefined;
        this._cachedRendererResources = this._cachedRendererResources && this._cachedRendererResources.release();

        var pickIds = this._pickIds;
        var length = pickIds.length;
        for (var i = 0; i < length; ++i) {
            pickIds[i].destroy();
        }

        releaseCachedGltf(this);

        return destroyObject(this);
    };

    return Model;
});

/*global define*/
define('DataSources/ModelVisualizer',[
        '../Core/AssociativeArray',
        '../Core/BoundingSphere',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Matrix4',
        '../Scene/ColorBlendMode',
        '../Scene/HeightReference',
        '../Scene/Model',
        '../Scene/ModelAnimationLoop',
        '../Scene/ShadowMode',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        BoundingSphere,
        Color,
        defined,
        destroyObject,
        DeveloperError,
        Matrix4,
        ColorBlendMode,
        HeightReference,
        Model,
        ModelAnimationLoop,
        ShadowMode,
        BoundingSphereState,
        Property) {
    'use strict';

    var defaultScale = 1.0;
    var defaultMinimumPixelSize = 0.0;
    var defaultIncrementallyLoadTextures = true;
    var defaultShadows = ShadowMode.ENABLED;
    var defaultHeightReference = HeightReference.NONE;
    var defaultSilhouetteColor = Color.RED;
    var defaultSilhouetteSize = 0.0;
    var defaultColor = Color.WHITE;
    var defaultColorBlendMode = ColorBlendMode.HIGHLIGHT;
    var defaultColorBlendAmount = 0.5;

    var modelMatrixScratch = new Matrix4();
    var nodeMatrixScratch = new Matrix4();

    /**
     * A {@link Visualizer} which maps {@link Entity#model} to a {@link Model}.
     * @alias ModelVisualizer
     * @constructor
     *
     * @param {Scene} scene The scene the primitives will be rendered in.
     * @param {EntityCollection} entityCollection The entityCollection to visualize.
     */
    function ModelVisualizer(scene, entityCollection) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(entityCollection)) {
            throw new DeveloperError('entityCollection is required.');
        }
        
        entityCollection.collectionChanged.addEventListener(ModelVisualizer.prototype._onCollectionChanged, this);

        this._scene = scene;
        this._primitives = scene.primitives;
        this._entityCollection = entityCollection;
        this._modelHash = {};
        this._entitiesToVisualize = new AssociativeArray();
        this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
    }

    /**
     * Updates models created this visualizer to match their
     * Entity counterpart at the given time.
     *
     * @param {JulianDate} time The time to update to.
     * @returns {Boolean} This function always returns true.
     */
    ModelVisualizer.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var entities = this._entitiesToVisualize.values;
        var modelHash = this._modelHash;
        var primitives = this._primitives;

        for (var i = 0, len = entities.length; i < len; i++) {
            var entity = entities[i];
            var modelGraphics = entity._model;

            var uri;
            var modelData = modelHash[entity.id];
            var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(modelGraphics._show, time, true);

            var modelMatrix;
            if (show) {
                modelMatrix = entity._getModelMatrix(time, modelMatrixScratch);
                uri = Property.getValueOrUndefined(modelGraphics._uri, time);
                show = defined(modelMatrix) && defined(uri);
            }

            if (!show) {
                if (defined(modelData)) {
                    modelData.modelPrimitive.show = false;
                }
                continue;
            }

            var model = defined(modelData) ? modelData.modelPrimitive : undefined;
            if (!defined(model) || uri !== modelData.uri) {
                if (defined(model)) {
                    primitives.removeAndDestroy(model);
                    delete modelHash[entity.id];
                }
                model = Model.fromGltf({
                    url : uri,
                    incrementallyLoadTextures : Property.getValueOrDefault(modelGraphics._incrementallyLoadTextures, time, defaultIncrementallyLoadTextures),
                    scene : this._scene
                });

                model.readyPromise.otherwise(onModelError);

                model.id = entity;
                primitives.add(model);

                modelData = {
                    modelPrimitive : model,
                    uri : uri,
                    animationsRunning : false,
                    nodeTransformationsScratch : {},
                    originalNodeMatrixHash : {}
                };
                modelHash[entity.id] = modelData;
            }

            model.show = true;
            model.scale = Property.getValueOrDefault(modelGraphics._scale, time, defaultScale);
            model.minimumPixelSize = Property.getValueOrDefault(modelGraphics._minimumPixelSize, time, defaultMinimumPixelSize);
            model.maximumScale = Property.getValueOrUndefined(modelGraphics._maximumScale, time);
            model.modelMatrix = Matrix4.clone(modelMatrix, model.modelMatrix);
            model.shadows = Property.getValueOrDefault(modelGraphics._shadows, time, defaultShadows);
            model.heightReference = Property.getValueOrDefault(modelGraphics._heightReference, time, defaultHeightReference);
            model.distanceDisplayCondition = Property.getValueOrUndefined(modelGraphics._distanceDisplayCondition, time);
            model.silhouetteColor = Property.getValueOrDefault(modelGraphics._silhouetteColor, time, defaultSilhouetteColor, model._silhouetteColor);
            model.silhouetteSize = Property.getValueOrDefault(modelGraphics._silhouetteSize, time, defaultSilhouetteSize);
            model.color = Property.getValueOrDefault(modelGraphics._color, time, defaultColor, model._color);
            model.colorBlendMode = Property.getValueOrDefault(modelGraphics._colorBlendMode, time, defaultColorBlendMode);
            model.colorBlendAmount = Property.getValueOrDefault(modelGraphics._colorBlendAmount, time, defaultColorBlendAmount);

            if (model.ready) {
                var runAnimations = Property.getValueOrDefault(modelGraphics._runAnimations, time, true);
                if (modelData.animationsRunning !== runAnimations) {
                    if (runAnimations) {
                        model.activeAnimations.addAll({
                            loop : ModelAnimationLoop.REPEAT
                        });
                    } else {
                        model.activeAnimations.removeAll();
                    }
                    modelData.animationsRunning = runAnimations;
                }

                // Apply node transformations
                var nodeTransformations = Property.getValueOrUndefined(modelGraphics._nodeTransformations, time, modelData.nodeTransformationsScratch);
                if (defined(nodeTransformations)) {
                    var originalNodeMatrixHash = modelData.originalNodeMatrixHash;
                    var nodeNames = Object.keys(nodeTransformations);
                    for (var nodeIndex = 0, nodeLength = nodeNames.length; nodeIndex < nodeLength; ++nodeIndex) {
                        var nodeName = nodeNames[nodeIndex];

                        var nodeTransformation = nodeTransformations[nodeName];
                        if (!defined(nodeTransformation)) {
                            continue;
                        }

                        var modelNode = model.getNode(nodeName);
                        if (!defined(modelNode)) {
                            continue;
                        }

                        var originalNodeMatrix = originalNodeMatrixHash[nodeName];
                        if (!defined(originalNodeMatrix)) {
                            originalNodeMatrix = modelNode.matrix.clone();
                            originalNodeMatrixHash[nodeName] = originalNodeMatrix;
                        }

                        var transformationMatrix = Matrix4.fromTranslationRotationScale(nodeTransformation, nodeMatrixScratch);
                        modelNode.matrix = Matrix4.multiply(originalNodeMatrix, transformationMatrix, transformationMatrix);
                    }
                }
            }
        }

        return true;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    ModelVisualizer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes and destroys all primitives created by this instance.
     */
    ModelVisualizer.prototype.destroy = function() {
        this._entityCollection.collectionChanged.removeEventListener(ModelVisualizer.prototype._onCollectionChanged, this);
        var entities = this._entitiesToVisualize.values;
        var modelHash = this._modelHash;
        var primitives = this._primitives;
        for (var i = entities.length - 1; i > -1; i--) {
            removeModel(this, entities[i], modelHash, primitives);
        }
        return destroyObject(this);
    };

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    ModelVisualizer.prototype.getBoundingSphere = function(entity, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var modelData = this._modelHash[entity.id];
        if (!defined(modelData)) {
            return BoundingSphereState.FAILED;
        }

        var model = modelData.modelPrimitive;
        if (!defined(model) || !model.show) {
            return BoundingSphereState.FAILED;
        }

        if (!model.ready) {
            return BoundingSphereState.PENDING;
        }

        if (model.heightReference === HeightReference.NONE) {
            BoundingSphere.transform(model.boundingSphere, model.modelMatrix, result);
        } else {
            if (!defined(model._clampedModelMatrix)) {
                return BoundingSphereState.PENDING;
            }
            BoundingSphere.transform(model.boundingSphere, model._clampedModelMatrix, result);
        }
        return BoundingSphereState.DONE;
    };

    /**
     * @private
     */
    ModelVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
        var i;
        var entity;
        var entities = this._entitiesToVisualize;
        var modelHash = this._modelHash;
        var primitives = this._primitives;

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            if (defined(entity._model) && defined(entity._position)) {
                entities.set(entity.id, entity);
            }
        }

        for (i = changed.length - 1; i > -1; i--) {
            entity = changed[i];
            if (defined(entity._model) && defined(entity._position)) {
                clearNodeTransformationsScratch(entity, modelHash);
                entities.set(entity.id, entity);
            } else {
                removeModel(this, entity, modelHash, primitives);
                entities.remove(entity.id);
            }
        }

        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            removeModel(this, entity, modelHash, primitives);
            entities.remove(entity.id);
        }
    };

    function removeModel(visualizer, entity, modelHash, primitives) {
        var modelData = modelHash[entity.id];
        if (defined(modelData)) {
            primitives.removeAndDestroy(modelData.modelPrimitive);
            delete modelHash[entity.id];
        }
    }

    function clearNodeTransformationsScratch(entity, modelHash) {
        var modelData = modelHash[entity.id];
        if (defined(modelData)) {
            modelData.nodeTransformationsScratch = {};
        }
    }

    function onModelError(error) {
        console.error(error);
    }

    return ModelVisualizer;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PolylineCommon',[],function() {
    'use strict';
    return "void clipLineSegmentToNearPlane(\n\
    vec3 p0,\n\
    vec3 p1,\n\
    out vec4 positionWC,\n\
    out bool clipped,\n\
    out bool culledByNearPlane)\n\
{\n\
    culledByNearPlane = false;\n\
    clipped = false;\n\
    \n\
    vec3 p1ToP0 = p1 - p0;\n\
    float magnitude = length(p1ToP0);\n\
    vec3 direction = normalize(p1ToP0);\n\
    float endPoint0Distance =  -(czm_currentFrustum.x + p0.z);\n\
    float denominator = -direction.z;\n\
    \n\
    if (endPoint0Distance < 0.0 && abs(denominator) < czm_epsilon7)\n\
    {\n\
        culledByNearPlane = true;\n\
    }\n\
    else if (endPoint0Distance < 0.0 && abs(denominator) > czm_epsilon7)\n\
    {\n\
        // t = (-plane distance - dot(plane normal, ray origin)) / dot(plane normal, ray direction)\n\
        float t = (czm_currentFrustum.x + p0.z) / denominator;\n\
        if (t < 0.0 || t > magnitude)\n\
        {\n\
            culledByNearPlane = true;\n\
        }\n\
        else\n\
        {\n\
            p0 = p0 + t * direction;\n\
            clipped = true;\n\
        }\n\
    }\n\
    \n\
    positionWC = czm_eyeToWindowCoordinates(vec4(p0, 1.0));\n\
}\n\
\n\
vec4 getPolylineWindowCoordinates(vec4 position, vec4 previous, vec4 next, float expandDirection, float width, bool usePrevious) {\n\
    vec4 endPointWC, p0, p1;\n\
    bool culledByNearPlane, clipped;\n\
    \n\
    vec4 positionEC = czm_modelViewRelativeToEye * position;\n\
    vec4 prevEC = czm_modelViewRelativeToEye * previous;\n\
    vec4 nextEC = czm_modelViewRelativeToEye * next;\n\
    \n\
    clipLineSegmentToNearPlane(prevEC.xyz, positionEC.xyz, p0, clipped, culledByNearPlane);\n\
    clipLineSegmentToNearPlane(nextEC.xyz, positionEC.xyz, p1, clipped, culledByNearPlane);\n\
    clipLineSegmentToNearPlane(positionEC.xyz, usePrevious ? prevEC.xyz : nextEC.xyz, endPointWC, clipped, culledByNearPlane);\n\
    \n\
    if (culledByNearPlane)\n\
    {\n\
        return vec4(0.0, 0.0, 0.0, 1.0);\n\
    }\n\
    \n\
    vec2 prevWC = normalize(p0.xy - endPointWC.xy);\n\
    vec2 nextWC = normalize(p1.xy - endPointWC.xy);\n\
    \n\
    float expandWidth = width * 0.5;\n\
    vec2 direction;\n\
\n\
    if (czm_equalsEpsilon(previous.xyz - position.xyz, vec3(0.0), czm_epsilon1) || czm_equalsEpsilon(prevWC, -nextWC, czm_epsilon1))\n\
    {\n\
        direction = vec2(-nextWC.y, nextWC.x);\n\
    }\n\
    else if (czm_equalsEpsilon(next.xyz - position.xyz, vec3(0.0), czm_epsilon1) || clipped)\n\
    {\n\
        direction = vec2(prevWC.y, -prevWC.x);\n\
    }\n\
    else\n\
    {\n\
        vec2 normal = vec2(-nextWC.y, nextWC.x);\n\
        direction = normalize((nextWC + prevWC) * 0.5);\n\
        if (dot(direction, normal) < 0.0)\n\
        {\n\
            direction = -direction;\n\
        }\n\
\n\
        // The sine of the angle between the two vectors is given by the formula\n\
        //         |a x b| = |a||b|sin(theta)\n\
        // which is\n\
        //     float sinAngle = length(cross(vec3(direction, 0.0), vec3(nextWC, 0.0)));\n\
        // Because the z components of both vectors are zero, the x and y coordinate will be zero.\n\
        // Therefore, the sine of the angle is just the z component of the cross product.\n\
        float sinAngle = abs(direction.x * nextWC.y - direction.y * nextWC.x);\n\
        expandWidth = clamp(expandWidth / sinAngle, 0.0, width * 2.0);\n\
    }\n\
\n\
    vec2 offset = direction * expandDirection * expandWidth * czm_resolutionScale;\n\
    return vec4(endPointWC.xy + offset, -endPointWC.z, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PolylineFS',[],function() {
    'use strict';
    return "varying vec2 v_st;\n\
\n\
void main()\n\
{\n\
    czm_materialInput materialInput;\n\
    \n\
    materialInput.s = v_st.s;\n\
    materialInput.st = v_st;\n\
    materialInput.str = vec3(v_st, 0.0);\n\
    \n\
    czm_material material = czm_getMaterial(materialInput);\n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PolylineVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 position2DHigh;\n\
attribute vec3 position2DLow;\n\
attribute vec3 prevPosition3DHigh;\n\
attribute vec3 prevPosition3DLow;\n\
attribute vec3 prevPosition2DHigh;\n\
attribute vec3 prevPosition2DLow;\n\
attribute vec3 nextPosition3DHigh;\n\
attribute vec3 nextPosition3DLow;\n\
attribute vec3 nextPosition2DHigh;\n\
attribute vec3 nextPosition2DLow;\n\
attribute vec4 texCoordExpandAndBatchIndex;\n\
\n\
varying vec2  v_st;\n\
varying float v_width;\n\
varying vec4  czm_pickColor;\n\
\n\
void main() \n\
{\n\
    float texCoord = texCoordExpandAndBatchIndex.x;\n\
    float expandDir = texCoordExpandAndBatchIndex.y;\n\
    bool usePrev = texCoordExpandAndBatchIndex.z < 0.0;\n\
    float batchTableIndex = texCoordExpandAndBatchIndex.w;\n\
\n\
    vec2 widthAndShow = batchTable_getWidthAndShow(batchTableIndex);\n\
    float width = widthAndShow.x + 0.5;\n\
    float show = widthAndShow.y;\n\
\n\
    if (width < 1.0)\n\
    {\n\
        show = 0.0;\n\
    }\n\
\n\
    vec4 pickColor = batchTable_getPickColor(batchTableIndex);\n\
    \n\
    vec4 p, prev, next;\n\
    if (czm_morphTime == 1.0)\n\
    {\n\
        p = czm_translateRelativeToEye(position3DHigh.xyz, position3DLow.xyz);\n\
        prev = czm_translateRelativeToEye(prevPosition3DHigh.xyz, prevPosition3DLow.xyz);\n\
        next = czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz);\n\
    }\n\
    else if (czm_morphTime == 0.0)\n\
    {\n\
        p = czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy);\n\
        prev = czm_translateRelativeToEye(prevPosition2DHigh.zxy, prevPosition2DLow.zxy);\n\
        next = czm_translateRelativeToEye(nextPosition2DHigh.zxy, nextPosition2DLow.zxy);\n\
    }\n\
    else\n\
    {\n\
        p = czm_columbusViewMorph(\n\
                czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy),\n\
                czm_translateRelativeToEye(position3DHigh.xyz, position3DLow.xyz),\n\
                czm_morphTime);\n\
        prev = czm_columbusViewMorph(\n\
                czm_translateRelativeToEye(prevPosition2DHigh.zxy, prevPosition2DLow.zxy),\n\
                czm_translateRelativeToEye(prevPosition3DHigh.xyz, prevPosition3DLow.xyz),\n\
                czm_morphTime);\n\
        next = czm_columbusViewMorph(\n\
                czm_translateRelativeToEye(nextPosition2DHigh.zxy, nextPosition2DLow.zxy),\n\
                czm_translateRelativeToEye(nextPosition3DHigh.xyz, nextPosition3DLow.xyz),\n\
                czm_morphTime);\n\
    }\n\
\n\
    #ifdef DISTANCE_DISPLAY_CONDITION\n\
        vec3 centerHigh = batchTable_getCenterHigh(batchTableIndex);\n\
        vec4 centerLowAndRadius = batchTable_getCenterLowAndRadius(batchTableIndex);\n\
        vec3 centerLow = centerLowAndRadius.xyz;\n\
        float radius = centerLowAndRadius.w;\n\
        vec2 distanceDisplayCondition = batchTable_getDistanceDisplayCondition(batchTableIndex);\n\
\n\
        float lengthSq;\n\
        if (czm_sceneMode == czm_sceneMode2D)\n\
        {\n\
            lengthSq = czm_eyeHeight2D.y;\n\
        }\n\
        else\n\
        {\n\
            vec4 center = czm_translateRelativeToEye(centerHigh.xyz, centerLow.xyz);\n\
            lengthSq = max(0.0, dot(center.xyz, center.xyz) - radius * radius);\n\
        }\n\
\n\
        float nearSq = distanceDisplayCondition.x * distanceDisplayCondition.x;\n\
        float farSq = distanceDisplayCondition.y * distanceDisplayCondition.y;\n\
        if (lengthSq < nearSq || lengthSq > farSq)\n\
        {\n\
            show = 0.0;\n\
        }\n\
    #endif\n\
    \n\
    vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev);\n\
    gl_Position = czm_viewportOrthographic * positionWC * show;\n\
    \n\
    v_st = vec2(texCoord, clamp(expandDir, 0.0, 1.0));\n\
    v_width = width;\n\
    czm_pickColor = pickColor;\n\
}\n\
";
});
/*global define*/
define('Scene/Polyline',[
        '../Core/arrayRemoveDuplicates',
        '../Core/BoundingSphere',
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/Matrix4',
        '../Core/PolylinePipeline',
        './Material'
    ], function(
        arrayRemoveDuplicates,
        BoundingSphere,
        Cartesian3,
        Color,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        DistanceDisplayCondition,
        Matrix4,
        PolylinePipeline,
        Material) {
    'use strict';

    /**
     * A renderable polyline. Create this by calling {@link PolylineCollection#add}
     *
     * @alias Polyline
     * @internalConstructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.show=true] <code>true</code> if this polyline will be shown; otherwise, <code>false</code>.
     * @param {Number} [options.width=1.0] The width of the polyline in pixels.
     * @param {Boolean} [options.loop=false] Whether a line segment will be added between the last and first line positions to make this line a loop.
     * @param {Material} [options.material=Material.ColorType] The material.
     * @param {Cartesian3[]} [options.positions] The positions.
     * @param {Object} [options.id] The user-defined object to be returned when this polyline is picked.
     * @param {DistanceDisplayCondition} [options.distanceDisplayCondition] The condition specifying at what distance from the camera that this polyline will be displayed.
     * @param {PolylineCollection} polylineCollection The renderable polyline collection.
     *
     * @see PolylineCollection
     *
     */
    function Polyline(options, polylineCollection) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._show = defaultValue(options.show, true);
        this._width = defaultValue(options.width, 1.0);
        this._loop = defaultValue(options.loop, false);
        this._distanceDisplayCondition = options.distanceDisplayCondition;

        this._material = options.material;
        if (!defined(this._material)) {
            this._material = Material.fromType(Material.ColorType, {
                color : new Color(1.0, 1.0, 1.0, 1.0)
            });
        }

        var positions = options.positions;
        if (!defined(positions)) {
            positions = [];
        }

        this._positions = positions;
        this._actualPositions = arrayRemoveDuplicates(positions, Cartesian3.equalsEpsilon);

        if (this._loop && this._actualPositions.length > 2) {
            if (this._actualPositions === this._positions) {
                this._actualPositions = positions.slice();
            }
            this._actualPositions.push(Cartesian3.clone(this._actualPositions[0]));
        }

        this._length = this._actualPositions.length;
        this._id = options.id;

        var modelMatrix;
        if (defined(polylineCollection)) {
            modelMatrix = Matrix4.clone(polylineCollection.modelMatrix);
        }

        this._modelMatrix = modelMatrix;
        this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix);

        this._actualLength = undefined;

        this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
        this._polylineCollection = polylineCollection;
        this._dirty = false;
        this._pickId = undefined;
        this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions);
        this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, this._modelMatrix);
        this._boundingVolume2D = new BoundingSphere(); // modified in PolylineCollection
    }

    var POSITION_INDEX = Polyline.POSITION_INDEX = 0;
    var SHOW_INDEX = Polyline.SHOW_INDEX = 1;
    var WIDTH_INDEX = Polyline.WIDTH_INDEX = 2;
    var MATERIAL_INDEX = Polyline.MATERIAL_INDEX = 3;
    var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX = 4;
    var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION = 5;
    var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES = 6;

    function makeDirty(polyline, propertyChanged) {
        ++polyline._propertiesChanged[propertyChanged];
        var polylineCollection = polyline._polylineCollection;
        if (defined(polylineCollection)) {
            polylineCollection._updatePolyline(polyline, propertyChanged);
            polyline._dirty = true;
        }
    }

    defineProperties(Polyline.prototype, {

        /**
         * Determines if this polyline will be shown.  Use this to hide or show a polyline, instead
         * of removing it and re-adding it to the collection.
         * @memberof Polyline.prototype
         * @type {Boolean}
         */
        show: {
            get: function() {
                return this._show;
            },
            set: function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (value !== this._show) {
                    this._show = value;
                    makeDirty(this, SHOW_INDEX);
                }
            }
        },

        /**
         * Gets or sets the positions of the polyline.
         * @memberof Polyline.prototype
         * @type {Cartesian3[]}
         * @example
         * polyline.positions = Cesium.Cartesian3.fromDegreesArray([
         *     0.0, 0.0,
         *     10.0, 0.0,
         *     0.0, 20.0
         * ]);
         */
        positions : {
            get: function() {
                return this._positions;
            },
            set: function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var positions = arrayRemoveDuplicates(value, Cartesian3.equalsEpsilon);

                if (this._loop && positions.length > 2) {
                    if (positions === value) {
                        positions = value.slice();
                    }
                    positions.push(Cartesian3.clone(positions[0]));
                }

                if (this._actualPositions.length !== positions.length || this._actualPositions.length !== this._length) {
                    makeDirty(this, POSITION_SIZE_INDEX);
                }

                this._positions = value;
                this._actualPositions = positions;
                this._length = positions.length;
                this._boundingVolume = BoundingSphere.fromPoints(this._actualPositions, this._boundingVolume);
                this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, this._modelMatrix, this._boundingVolumeWC);
                makeDirty(this, POSITION_INDEX);

                this.update();
            }
        },

        /**
         * Gets or sets the surface appearance of the polyline.  This can be one of several built-in {@link Material} objects or a custom material, scripted with
         * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
         * @memberof Polyline.prototype
         * @type {Material}
         */
        material: {
            get: function() {
                return this._material;
            },
            set: function(material) {
                                if (!defined(material)) {
                    throw new DeveloperError('material is required.');
                }
                
                if (this._material !== material) {
                    this._material = material;
                    makeDirty(this, MATERIAL_INDEX);
                }
            }
        },

        /**
         * Gets or sets the width of the polyline.
         * @memberof Polyline.prototype
         * @type {Number}
         */
        width: {
            get: function() {
                return this._width;
            },
            set: function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                var width = this._width;
                if (value !== width) {
                    this._width = value;
                    makeDirty(this, WIDTH_INDEX);
                }
            }
        },

        /**
         * Gets or sets whether a line segment will be added between the first and last polyline positions.
         * @memberof Polyline.prototype
         * @type {Boolean}
         */
        loop: {
            get: function() {
                return this._loop;
            },
            set: function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                if (value !== this._loop) {
                    var positions = this._actualPositions;
                    if (value) {
                        if (positions.length > 2 && !Cartesian3.equals(positions[0], positions[positions.length - 1])) {
                            if (positions.length === this._positions.length) {
                                this._actualPositions = positions = this._positions.slice();
                            }
                            positions.push(Cartesian3.clone(positions[0]));
                        }
                    } else {
                        if (positions.length > 2 && Cartesian3.equals(positions[0], positions[positions.length - 1])) {
                            if (positions.length - 1 === this._positions.length) {
                                this._actualPositions = this._positions;
                            } else {
                                positions.pop();
                            }
                        }
                    }

                    this._loop = value;
                    makeDirty(this, POSITION_SIZE_INDEX);
                }
            }
        },

        /**
         * Gets or sets the user-defined object returned when the polyline is picked.
         * @memberof Polyline.prototype
         * @type {Object}
         */
        id : {
            get : function() {
                return this._id;
            },
            set : function(value) {
                this._id = value;
                if (defined(this._pickId)) {
                    this._pickId.object.id = value;
                }
            }
        },

        /**
         * Gets or sets the condition specifying at what distance from the camera that this polyline will be displayed.
         * @memberof Polyline.prototype
         * @type {DistanceDisplayCondition}
         * @default undefined
         */
        distanceDisplayCondition : {
            get : function() {
                return this._distanceDisplayCondition;
            },
            set : function(value) {
                                if (defined(value) && value.far <= value.near) {
                    throw new DeveloperError('far distance must be greater than near distance.');
                }
                                if (!DistanceDisplayCondition.equals(value, this._distanceDisplayCondition)) {
                    this._distanceDisplayCondition = DistanceDisplayCondition.clone(value, this._distanceDisplayCondition);
                    makeDirty(this, DISTANCE_DISPLAY_CONDITION);
                }
            }
        }
    });

    /**
     * @private
     */
    Polyline.prototype.update = function() {
        var modelMatrix = Matrix4.IDENTITY;
        if (defined(this._polylineCollection)) {
            modelMatrix = this._polylineCollection.modelMatrix;
        }

        var segmentPositionsLength = this._segments.positions.length;
        var segmentLengths = this._segments.lengths;

        var positionsChanged = this._propertiesChanged[POSITION_INDEX] > 0 || this._propertiesChanged[POSITION_SIZE_INDEX] > 0;
        if (!Matrix4.equals(modelMatrix, this._modelMatrix) || positionsChanged) {
            this._segments = PolylinePipeline.wrapLongitude(this._actualPositions, modelMatrix);
            this._boundingVolumeWC = BoundingSphere.transform(this._boundingVolume, modelMatrix, this._boundingVolumeWC);
        }

        this._modelMatrix = modelMatrix;

        if (this._segments.positions.length !== segmentPositionsLength) {
            // number of positions changed
            makeDirty(this, POSITION_SIZE_INDEX);
        } else {
            var length = segmentLengths.length;
            for (var i = 0; i < length; ++i) {
                if (segmentLengths[i] !== this._segments.lengths[i]) {
                    // indices changed
                    makeDirty(this, POSITION_SIZE_INDEX);
                    break;
                }
            }
        }
    };

    /**
     * @private
     */
    Polyline.prototype.getPickId = function(context) {
        if (!defined(this._pickId)) {
            this._pickId = context.createPickId({
                primitive : this,
                collection : this._polylineCollection,
                id : this._id
            });
        }
        return this._pickId;
    };

    Polyline.prototype._clean = function() {
        this._dirty = false;
        var properties = this._propertiesChanged;
        for ( var k = 0; k < NUMBER_OF_PROPERTIES - 1; ++k) {
            properties[k] = 0;
        }
    };

    Polyline.prototype._destroy = function() {
        this._pickId = this._pickId && this._pickId.destroy();
        this._material = this._material && this._material.destroy();
        this._polylineCollection = undefined;
    };

    return Polyline;
});

/*global define*/
define('Scene/PolylineCollection',[
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/Color',
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EncodedCartesian3',
        '../Core/IndexDatatype',
        '../Core/Intersect',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/Plane',
        '../Core/RuntimeError',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/ContextLimits',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArray',
        '../Shaders/PolylineCommon',
        '../Shaders/PolylineFS',
        '../Shaders/PolylineVS',
        './BatchTable',
        './BlendingState',
        './Material',
        './Polyline',
        './SceneMode'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        Color,
        ComponentDatatype,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EncodedCartesian3,
        IndexDatatype,
        Intersect,
        CesiumMath,
        Matrix4,
        Plane,
        RuntimeError,
        Buffer,
        BufferUsage,
        ContextLimits,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArray,
        PolylineCommon,
        PolylineFS,
        PolylineVS,
        BatchTable,
        BlendingState,
        Material,
        Polyline,
        SceneMode) {
    'use strict';

    var SHOW_INDEX = Polyline.SHOW_INDEX;
    var WIDTH_INDEX = Polyline.WIDTH_INDEX;
    var POSITION_INDEX = Polyline.POSITION_INDEX;
    var MATERIAL_INDEX = Polyline.MATERIAL_INDEX;
    //POSITION_SIZE_INDEX is needed for when the polyline's position array changes size.
    //When it does, we need to recreate the indicesBuffer.
    var POSITION_SIZE_INDEX = Polyline.POSITION_SIZE_INDEX;
    var DISTANCE_DISPLAY_CONDITION = Polyline.DISTANCE_DISPLAY_CONDITION;
    var NUMBER_OF_PROPERTIES = Polyline.NUMBER_OF_PROPERTIES;

    var attributeLocations = {
        texCoordExpandAndBatchIndex : 0,
        position3DHigh : 1,
        position3DLow : 2,
        position2DHigh : 3,
        position2DLow : 4,
        prevPosition3DHigh : 5,
        prevPosition3DLow : 6,
        prevPosition2DHigh : 7,
        prevPosition2DLow : 8,
        nextPosition3DHigh : 9,
        nextPosition3DLow : 10,
        nextPosition2DHigh : 11,
        nextPosition2DLow : 12
    };

    /**
     * A renderable collection of polylines.
     * <br /><br />
     * <div align="center">
     * <img src="images/Polyline.png" width="400" height="300" /><br />
     * Example polylines
     * </div>
     * <br /><br />
     * Polylines are added and removed from the collection using {@link PolylineCollection#add}
     * and {@link PolylineCollection#remove}.
     *
     * @alias PolylineCollection
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each polyline from model to world coordinates.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     *
     * @performance For best performance, prefer a few collections, each with many polylines, to
     * many collections with only a few polylines each.  Organize collections so that polylines
     * with the same update frequency are in the same collection, i.e., polylines that do not
     * change should be in one collection; polylines that change every frame should be in another
     * collection; and so on.
     *
     * @see PolylineCollection#add
     * @see PolylineCollection#remove
     * @see Polyline
     * @see LabelCollection
     *
     * @example
     * // Create a polyline collection with two polylines
     * var polylines = new Cesium.PolylineCollection();
     * polylines.add({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     -75.10, 39.57,
     *     -77.02, 38.53,
     *     -80.50, 35.14,
     *     -80.12, 25.46]),
     *   width : 2
     * });
     *
     * polylines.add({
     *   positions : Cesium.Cartesian3.fromDegreesArray([
     *     -73.10, 37.57,
     *     -75.02, 36.53,
     *     -78.50, 33.14,
     *     -78.12, 23.46]),
     *   width : 4
     * });
     */
    function PolylineCollection(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The 4x4 transformation matrix that transforms each polyline in this collection from model to world coordinates.
         * When this is the identity matrix, the polylines are drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type {Matrix4}
         * @default {@link Matrix4.IDENTITY}
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = Matrix4.clone(Matrix4.IDENTITY);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        this._opaqueRS = undefined;
        this._translucentRS = undefined;

        this._colorCommands = [];
        this._pickCommands = [];

        this._polylinesUpdated = false;
        this._polylinesRemoved = false;
        this._createVertexArray = false;
        this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
        this._polylines = [];
        this._polylineBuckets = {};

        // The buffer usage is determined based on the usage of the attribute over time.
        this._positionBufferUsage = { bufferUsage : BufferUsage.STATIC_DRAW, frameCount : 0 };

        this._mode = undefined;

        this._polylinesToUpdate = [];
        this._vertexArrays = [];
        this._positionBuffer = undefined;
        this._texCoordExpandAndBatchIndexBuffer = undefined;

        this._batchTable = undefined;
        this._createBatchTable = false;
    }

    defineProperties(PolylineCollection.prototype, {
        /**
         * Returns the number of polylines in this collection.  This is commonly used with
         * {@link PolylineCollection#get} to iterate over all the polylines
         * in the collection.
         * @memberof PolylineCollection.prototype
         * @type {Number}
         */
        length : {
            get : function() {
                removePolylines(this);
                return this._polylines.length;
            }
        }
    });

    /**
     * Creates and adds a polyline with the specified initial properties to the collection.
     * The added polyline is returned so it can be modified or removed from the collection later.
     *
     * @param {Object}[polyline] A template describing the polyline's properties as shown in Example 1.
     * @returns {Polyline} The polyline that was added to the collection.
     *
     * @performance After calling <code>add</code>, {@link PolylineCollection#update} is called and
     * the collection's vertex buffer is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.
     * For best performance, add as many polylines as possible before calling <code>update</code>.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Example 1:  Add a polyline, specifying all the default values.
     * var p = polylines.add({
     *   show : true,
     *   positions : ellipsoid.cartographicArrayToCartesianArray([
           Cesium.Cartographic.fromDegrees(-75.10, 39.57),
           Cesium.Cartographic.fromDegrees(-77.02, 38.53)]),
     *   width : 1
     * });
     * 
     * @see PolylineCollection#remove
     * @see PolylineCollection#removeAll
     * @see PolylineCollection#update
     */
    PolylineCollection.prototype.add = function(polyline) {
        var p = new Polyline(polyline, this);
        p._index = this._polylines.length;
        this._polylines.push(p);
        this._createVertexArray = true;
        this._createBatchTable = true;
        return p;
    };

    /**
     * Removes a polyline from the collection.
     *
     * @param {Polyline} polyline The polyline to remove.
     * @returns {Boolean} <code>true</code> if the polyline was removed; <code>false</code> if the polyline was not found in the collection.
     *
     * @performance After calling <code>remove</code>, {@link PolylineCollection#update} is called and
     * the collection's vertex buffer is rewritten - an <code>O(n)</code> operation that also incurs CPU to GPU overhead.
     * For best performance, remove as many polylines as possible before calling <code>update</code>.
     * If you intend to temporarily hide a polyline, it is usually more efficient to call
     * {@link Polyline#show} instead of removing and re-adding the polyline.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * var p = polylines.add(...);
     * polylines.remove(p);  // Returns true
     * 
     * @see PolylineCollection#add
     * @see PolylineCollection#removeAll
     * @see PolylineCollection#update
     * @see Polyline#show
     */
    PolylineCollection.prototype.remove = function(polyline) {
        if (this.contains(polyline)) {
            this._polylines[polyline._index] = undefined; // Removed later
            this._polylinesRemoved = true;
            this._createVertexArray = true;
            this._createBatchTable = true;
            if (defined(polyline._bucket)) {
                var bucket = polyline._bucket;
                bucket.shaderProgram = bucket.shaderProgram && bucket.shaderProgram.destroy();
                bucket.pickShaderProgram = bucket.pickShaderProgram && bucket.pickShaderProgram.destroy();
            }
            polyline._destroy();
            return true;
        }

        return false;
    };

    /**
     * Removes all polylines from the collection.
     *
     * @performance <code>O(n)</code>.  It is more efficient to remove all the polylines
     * from a collection and then add new ones than to create a new collection entirely.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * polylines.add(...);
     * polylines.add(...);
     * polylines.removeAll();
     * 
     * @see PolylineCollection#add
     * @see PolylineCollection#remove
     * @see PolylineCollection#update
     */
    PolylineCollection.prototype.removeAll = function() {
        releaseShaders(this);
        destroyPolylines(this);
        this._polylineBuckets = {};
        this._polylinesRemoved = false;
        this._polylines.length = 0;
        this._polylinesToUpdate.length = 0;
        this._createVertexArray = true;
    };

    /**
     * Determines if this collection contains the specified polyline.
     *
     * @param {Polyline} polyline The polyline to check for.
     * @returns {Boolean} true if this collection contains the polyline, false otherwise.
     *
     * @see PolylineCollection#get
     */
    PolylineCollection.prototype.contains = function(polyline) {
        return defined(polyline) && polyline._polylineCollection === this;
    };

    /**
     * Returns the polyline in the collection at the specified index.  Indices are zero-based
     * and increase as polylines are added.  Removing a polyline shifts all polylines after
     * it to the left, changing their indices.  This function is commonly used with
     * {@link PolylineCollection#length} to iterate over all the polylines
     * in the collection.
     *
     * @param {Number} index The zero-based index of the polyline.
     * @returns {Polyline} The polyline at the specified index.
     *
     * @performance If polylines were removed from the collection and
     * {@link PolylineCollection#update} was not called, an implicit <code>O(n)</code>
     * operation is performed.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * // Toggle the show property of every polyline in the collection
     * var len = polylines.length;
     * for (var i = 0; i < len; ++i) {
     *   var p = polylines.get(i);
     *   p.show = !p.show;
     * }
     *
     * @see PolylineCollection#length
     */
    PolylineCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        removePolylines(this);
        return this._polylines[index];
    };

    function createBatchTable(collection, context) {
        if (defined(collection._batchTable)) {
            collection._batchTable.destroy();
        }

        var attributes = [{
            functionName : 'batchTable_getWidthAndShow',
            componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
            componentsPerAttribute : 2
        }, {
            functionName : 'batchTable_getPickColor',
            componentDatatype : ComponentDatatype.UNSIGNED_BYTE,
            componentsPerAttribute : 4,
            normalize : true
        }, {
            functionName : 'batchTable_getCenterHigh',
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 3
        }, {
            functionName : 'batchTable_getCenterLowAndRadius',
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 4
        }, {
            functionName : 'batchTable_getDistanceDisplayCondition',
            componentDatatype : ComponentDatatype.FLOAT,
            componentsPerAttribute : 2
        }];

        collection._batchTable = new BatchTable(context, attributes, collection._polylines.length);
    }

    var scratchUpdatePolylineEncodedCartesian = new EncodedCartesian3();
    var scratchUpdatePolylineCartesian4 = new Cartesian4();
    var scratchNearFarCartesian2 = new Cartesian2();

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {RuntimeError} Vertex texture fetch support is required to render primitives with per-instance attributes. The maximum number of vertex texture image units must be greater than zero.
     */
    PolylineCollection.prototype.update = function(frameState) {
        removePolylines(this);

        if (this._polylines.length === 0) {
            return;
        }

        updateMode(this, frameState);

        var context = frameState.context;
        var projection = frameState.mapProjection;
        var polyline;
        var properties = this._propertiesChanged;

        if (this._createBatchTable) {
            if (ContextLimits.maximumVertexTextureImageUnits === 0) {
                throw new RuntimeError('Vertex texture fetch support is required to render polylines. The maximum number of vertex texture image units must be greater than zero.');
            }
            createBatchTable(this, context);
            this._createBatchTable = false;
        }

        if (this._createVertexArray || computeNewBuffersUsage(this)) {
            createVertexArrays(this, context, projection);
        } else if (this._polylinesUpdated) {
            // Polylines were modified, but no polylines were added or removed.
            var polylinesToUpdate = this._polylinesToUpdate;
            if (this._mode !== SceneMode.SCENE3D) {
                var updateLength = polylinesToUpdate.length;
                for ( var i = 0; i < updateLength; ++i) {
                    polyline = polylinesToUpdate[i];
                    polyline.update();
                }
            }

            // if a polyline's positions size changes, we need to recreate the vertex arrays and vertex buffers because the indices will be different.
            // if a polyline's material changes, we need to recreate the VAOs and VBOs because they will be batched differently.
            if (properties[POSITION_SIZE_INDEX] || properties[MATERIAL_INDEX]) {
                createVertexArrays(this, context, projection);
            } else {
                var length = polylinesToUpdate.length;
                var polylineBuckets = this._polylineBuckets;
                for ( var ii = 0; ii < length; ++ii) {
                    polyline = polylinesToUpdate[ii];
                    properties = polyline._propertiesChanged;
                    var bucket = polyline._bucket;
                    var index = 0;
                    for (var x in polylineBuckets) {
                        if (polylineBuckets.hasOwnProperty(x)) {
                            if (polylineBuckets[x] === bucket) {
                                if (properties[POSITION_INDEX]) {
                                    bucket.writeUpdate(index, polyline, this._positionBuffer, projection);
                                }
                                break;
                            }
                            index += polylineBuckets[x].lengthOfPositions;
                        }
                    }

                    if (properties[SHOW_INDEX] || properties[WIDTH_INDEX]) {
                        this._batchTable.setBatchedAttribute(polyline._index, 0, new Cartesian2(polyline._width, polyline._show));
                    }

                    if (this._batchTable.attributes.length > 2) {
                        if (properties[POSITION_INDEX] || properties[POSITION_SIZE_INDEX]) {
                            var boundingSphere = frameState.mode === SceneMode.SCENE2D ? polyline._boundingVolume2D : polyline._boundingVolumeWC;
                            var encodedCenter = EncodedCartesian3.fromCartesian(boundingSphere.center, scratchUpdatePolylineEncodedCartesian);
                            var low = Cartesian4.fromElements(encodedCenter.low.x, encodedCenter.low.y, encodedCenter.low.z, boundingSphere.radius, scratchUpdatePolylineCartesian4);
                            this._batchTable.setBatchedAttribute(polyline._index, 2, encodedCenter.high);
                            this._batchTable.setBatchedAttribute(polyline._index, 3, low);
                        }

                        if (properties[DISTANCE_DISPLAY_CONDITION]) {
                            var nearFarCartesian = scratchNearFarCartesian2;
                            nearFarCartesian.x = 0.0;
                            nearFarCartesian.y = Number.MAX_VALUE;

                            var distanceDisplayCondition = polyline.distanceDisplayCondition;
                            if (defined(distanceDisplayCondition)) {
                                nearFarCartesian.x = distanceDisplayCondition.near;
                                nearFarCartesian.x = distanceDisplayCondition.far;
                            }

                            this._batchTable.setBatchedAttribute(polyline._index, 4, nearFarCartesian);
                        }
                    }

                    polyline._clean();
                }
            }
            polylinesToUpdate.length = 0;
            this._polylinesUpdated = false;
        }

        properties = this._propertiesChanged;
        for ( var k = 0; k < NUMBER_OF_PROPERTIES; ++k) {
            properties[k] = 0;
        }

        var modelMatrix = Matrix4.IDENTITY;
        if (frameState.mode === SceneMode.SCENE3D) {
            modelMatrix = this.modelMatrix;
        }

        var pass = frameState.passes;
        var useDepthTest = (frameState.morphTime !== 0.0);

        if (!defined(this._opaqueRS) || this._opaqueRS.depthTest.enabled !== useDepthTest) {
            this._opaqueRS = RenderState.fromCache({
                depthMask : useDepthTest,
                depthTest : {
                    enabled : useDepthTest
                }
            });
        }

        if (!defined(this._translucentRS) || this._translucentRS.depthTest.enabled !== useDepthTest) {
            this._translucentRS = RenderState.fromCache({
                blending : BlendingState.ALPHA_BLEND,
                depthMask : !useDepthTest,
                depthTest : {
                    enabled : useDepthTest
                }
            });
        }

        this._batchTable.update(frameState);

        if (pass.render) {
            var colorList = this._colorCommands;
            createCommandLists(this, frameState, colorList, modelMatrix, true);
        }

        if (pass.pick) {
            var pickList = this._pickCommands;
            createCommandLists(this, frameState, pickList, modelMatrix, false);
        }
    };

    var boundingSphereScratch = new BoundingSphere();
    var boundingSphereScratch2 = new BoundingSphere();

    function createCommandLists(polylineCollection, frameState, commands, modelMatrix, renderPass) {
        var context = frameState.context;
        var commandList = frameState.commandList;

        var commandsLength = commands.length;
        var commandIndex = 0;
        var cloneBoundingSphere = true;

        var vertexArrays = polylineCollection._vertexArrays;
        var debugShowBoundingVolume = polylineCollection.debugShowBoundingVolume;

        var batchTable = polylineCollection._batchTable;
        var uniformCallback = batchTable.getUniformMapCallback();

        var length = vertexArrays.length;
        for ( var m = 0; m < length; ++m) {
            var va = vertexArrays[m];
            var buckets = va.buckets;
            var bucketLength = buckets.length;

            for ( var n = 0; n < bucketLength; ++n) {
                var bucketLocator = buckets[n];

                var offset = bucketLocator.offset;
                var sp = renderPass ? bucketLocator.bucket.shaderProgram : bucketLocator.bucket.pickShaderProgram;

                var polylines = bucketLocator.bucket.polylines;
                var polylineLength = polylines.length;
                var currentId;
                var currentMaterial;
                var count = 0;
                var command;

                for (var s = 0; s < polylineLength; ++s) {
                    var polyline = polylines[s];
                    var mId = createMaterialId(polyline._material);
                    if (mId !== currentId) {
                        if (defined(currentId) && count > 0) {
                            var translucent = currentMaterial.isTranslucent();

                            if (commandIndex >= commandsLength) {
                                command = new DrawCommand({
                                    owner : polylineCollection
                                });
                                commands.push(command);
                            } else {
                                command = commands[commandIndex];
                            }

                            ++commandIndex;

                            command.boundingVolume = BoundingSphere.clone(boundingSphereScratch, command.boundingVolume);
                            command.modelMatrix = modelMatrix;
                            command.shaderProgram = sp;
                            command.vertexArray = va.va;
                            command.renderState = translucent ? polylineCollection._translucentRS : polylineCollection._opaqueRS;
                            command.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;
                            command.debugShowBoundingVolume = renderPass ? debugShowBoundingVolume : false;

                            command.uniformMap = uniformCallback(currentMaterial._uniforms);
                            command.count = count;
                            command.offset = offset;

                            offset += count;
                            count = 0;
                            cloneBoundingSphere = true;

                            commandList.push(command);
                        }

                        currentMaterial = polyline._material;
                        currentMaterial.update(context);
                        currentId = mId;
                    }

                    var locators = polyline._locatorBuckets;
                    var locatorLength = locators.length;
                    for (var t = 0; t < locatorLength; ++t) {
                        var locator = locators[t];
                        if (locator.locator === bucketLocator) {
                            count += locator.count;
                        }
                    }

                    var boundingVolume;
                    if (frameState.mode === SceneMode.SCENE3D) {
                        boundingVolume = polyline._boundingVolumeWC;
                    } else if (frameState.mode === SceneMode.COLUMBUS_VIEW) {
                        boundingVolume = polyline._boundingVolume2D;
                    } else if (frameState.mode === SceneMode.SCENE2D) {
                        if (defined(polyline._boundingVolume2D)) {
                            boundingVolume = BoundingSphere.clone(polyline._boundingVolume2D, boundingSphereScratch2);
                            boundingVolume.center.x = 0.0;
                        }
                    } else if (defined(polyline._boundingVolumeWC) && defined(polyline._boundingVolume2D)) {
                        boundingVolume = BoundingSphere.union(polyline._boundingVolumeWC, polyline._boundingVolume2D, boundingSphereScratch2);
                    }

                    if (cloneBoundingSphere) {
                        cloneBoundingSphere = false;
                        BoundingSphere.clone(boundingVolume, boundingSphereScratch);
                    } else {
                        BoundingSphere.union(boundingVolume, boundingSphereScratch, boundingSphereScratch);
                    }
                }

                if (defined(currentId) && count > 0) {
                    if (commandIndex >= commandsLength) {
                        command = new DrawCommand({
                            owner : polylineCollection
                        });
                        commands.push(command);
                    } else {
                        command = commands[commandIndex];
                    }

                    ++commandIndex;

                    command.boundingVolume = BoundingSphere.clone(boundingSphereScratch, command.boundingVolume);
                    command.modelMatrix = modelMatrix;
                    command.shaderProgram = sp;
                    command.vertexArray = va.va;
                    command.renderState = currentMaterial.isTranslucent() ? polylineCollection._translucentRS : polylineCollection._opaqueRS;
                    command.pass = currentMaterial.isTranslucent() ? Pass.TRANSLUCENT : Pass.OPAQUE;
                    command.debugShowBoundingVolume = renderPass ? debugShowBoundingVolume : false;

                    command.uniformMap = uniformCallback(currentMaterial._uniforms);
                    command.count = count;
                    command.offset = offset;

                    cloneBoundingSphere = true;

                    commandList.push(command);
                }

                currentId = undefined;
            }
        }

        commands.length = commandIndex;
    }

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see PolylineCollection#destroy
     */
    PolylineCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * polylines = polylines && polylines.destroy();
     * 
     * @see PolylineCollection#isDestroyed
     */
    PolylineCollection.prototype.destroy = function() {
        destroyVertexArrays(this);
        releaseShaders(this);
        destroyPolylines(this);
        this._batchTable = this._batchTable && this._batchTable.destroy();
        return destroyObject(this);
    };

    function computeNewBuffersUsage(collection) {
        var usageChanged = false;
        var properties = collection._propertiesChanged;
        var bufferUsage = collection._positionBufferUsage;
        if (properties[POSITION_INDEX]) {
            if (bufferUsage.bufferUsage !== BufferUsage.STREAM_DRAW) {
                usageChanged = true;
                bufferUsage.bufferUsage = BufferUsage.STREAM_DRAW;
                bufferUsage.frameCount = 100;
            } else {
                bufferUsage.frameCount = 100;
            }
        } else {
            if (bufferUsage.bufferUsage !== BufferUsage.STATIC_DRAW) {
                if (bufferUsage.frameCount === 0) {
                    usageChanged = true;
                    bufferUsage.bufferUsage = BufferUsage.STATIC_DRAW;
                } else {
                    bufferUsage.frameCount--;
                }
            }
        }

        return usageChanged;
    }

    var emptyVertexBuffer = [0.0, 0.0, 0.0];

    function createVertexArrays(collection, context, projection) {
        collection._createVertexArray = false;
        releaseShaders(collection);
        destroyVertexArrays(collection);
        sortPolylinesIntoBuckets(collection);

        //stores all of the individual indices arrays.
        var totalIndices = [[]];
        var indices = totalIndices[0];

        var batchTable = collection._batchTable;

        //used to determine the vertexBuffer offset if the indicesArray goes over 64k.
        //if it's the same polyline while it goes over 64k, the offset needs to backtrack componentsPerAttribute * componentDatatype bytes
        //so that the polyline looks contiguous.
        //if the polyline ends at the 64k mark, then the offset is just 64k * componentsPerAttribute * componentDatatype
        var vertexBufferOffset = [0];
        var offset = 0;
        var vertexArrayBuckets = [[]];
        var totalLength = 0;
        var polylineBuckets = collection._polylineBuckets;
        var x;
        var bucket;
        for (x in polylineBuckets) {
            if (polylineBuckets.hasOwnProperty(x)) {
                bucket = polylineBuckets[x];
                bucket.updateShader(context, batchTable);
                totalLength += bucket.lengthOfPositions;
            }
        }

        if (totalLength > 0) {
            var mode = collection._mode;

            var positionArray = new Float32Array(6 * totalLength * 3);
            var texCoordExpandAndBatchIndexArray = new Float32Array(totalLength * 4);
            var position3DArray;

            var positionIndex = 0;
            var colorIndex = 0;
            var texCoordExpandAndBatchIndexIndex = 0;
            for (x in polylineBuckets) {
                if (polylineBuckets.hasOwnProperty(x)) {
                    bucket = polylineBuckets[x];
                    bucket.write(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection);

                    if (mode === SceneMode.MORPHING) {
                        if (!defined(position3DArray)) {
                            position3DArray = new Float32Array(6 * totalLength * 3);
                        }
                        bucket.writeForMorph(position3DArray, positionIndex);
                    }

                    var bucketLength = bucket.lengthOfPositions;
                    positionIndex += 6 * bucketLength * 3;
                    colorIndex += bucketLength * 4;
                    texCoordExpandAndBatchIndexIndex += bucketLength * 4;
                    offset = bucket.updateIndices(totalIndices, vertexBufferOffset, vertexArrayBuckets, offset);
                }
            }

            var positionBufferUsage = collection._positionBufferUsage.bufferUsage;
            var texCoordExpandAndBatchIndexBufferUsage = BufferUsage.STATIC_DRAW;

            collection._positionBuffer = Buffer.createVertexBuffer({
                context : context,
                typedArray : positionArray,
                usage : positionBufferUsage
            });
            var position3DBuffer;
            if (defined(position3DArray)) {
                position3DBuffer = Buffer.createVertexBuffer({
                    context : context,
                    typedArray : position3DArray,
                    usage : positionBufferUsage
                });
            }
            collection._texCoordExpandAndBatchIndexBuffer = Buffer.createVertexBuffer({
                context : context,
                typedArray : texCoordExpandAndBatchIndexArray,
                usage : texCoordExpandAndBatchIndexBufferUsage
            });

            var positionSizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT;
            var texCoordExpandAndBatchIndexSizeInBytes = 4 * Float32Array.BYTES_PER_ELEMENT;

            var vbo = 0;
            var numberOfIndicesArrays = totalIndices.length;
            for ( var k = 0; k < numberOfIndicesArrays; ++k) {
                indices = totalIndices[k];

                if (indices.length > 0) {
                    var indicesArray = new Uint16Array(indices);
                    var indexBuffer = Buffer.createIndexBuffer({
                        context : context,
                        typedArray : indicesArray,
                        usage : BufferUsage.STATIC_DRAW,
                        indexDatatype : IndexDatatype.UNSIGNED_SHORT
                    });

                    vbo += vertexBufferOffset[k];

                    var positionHighOffset = 6 * (k * (positionSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * positionSizeInBytes);//componentsPerAttribute(3) * componentDatatype(4)
                    var positionLowOffset = positionSizeInBytes + positionHighOffset;
                    var prevPositionHighOffset =  positionSizeInBytes + positionLowOffset;
                    var prevPositionLowOffset = positionSizeInBytes + prevPositionHighOffset;
                    var nextPositionHighOffset = positionSizeInBytes + prevPositionLowOffset;
                    var nextPositionLowOffset = positionSizeInBytes + nextPositionHighOffset;
                    var vertexTexCoordExpandAndBatchIndexBufferOffset = k * (texCoordExpandAndBatchIndexSizeInBytes * CesiumMath.SIXTY_FOUR_KILOBYTES) - vbo * texCoordExpandAndBatchIndexSizeInBytes;

                    var attributes = [{
                        index : attributeLocations.position3DHigh,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : positionHighOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.position3DLow,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : positionLowOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.position2DHigh,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : positionHighOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.position2DLow,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : positionLowOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.prevPosition3DHigh,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : prevPositionHighOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.prevPosition3DLow,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : prevPositionLowOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.prevPosition2DHigh,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : prevPositionHighOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.prevPosition2DLow,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : prevPositionLowOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.nextPosition3DHigh,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : nextPositionHighOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.nextPosition3DLow,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : nextPositionLowOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.nextPosition2DHigh,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : nextPositionHighOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.nextPosition2DLow,
                        componentsPerAttribute : 3,
                        componentDatatype : ComponentDatatype.FLOAT,
                        offsetInBytes : nextPositionLowOffset,
                        strideInBytes : 6 * positionSizeInBytes
                    }, {
                        index : attributeLocations.texCoordExpandAndBatchIndex,
                        componentsPerAttribute : 4,
                        componentDatatype : ComponentDatatype.FLOAT,
                        vertexBuffer : collection._texCoordExpandAndBatchIndexBuffer,
                        offsetInBytes : vertexTexCoordExpandAndBatchIndexBufferOffset
                    }];

                    var buffer3D;
                    var bufferProperty3D;
                    var buffer2D;
                    var bufferProperty2D;

                    if (mode === SceneMode.SCENE3D) {
                        buffer3D = collection._positionBuffer;
                        bufferProperty3D = 'vertexBuffer';
                        buffer2D = emptyVertexBuffer;
                        bufferProperty2D = 'value';
                    } else if (mode === SceneMode.SCENE2D || mode === SceneMode.COLUMBUS_VIEW) {
                        buffer3D = emptyVertexBuffer;
                        bufferProperty3D = 'value';
                        buffer2D = collection._positionBuffer;
                        bufferProperty2D = 'vertexBuffer';
                    } else {
                        buffer3D = position3DBuffer;
                        bufferProperty3D = 'vertexBuffer';
                        buffer2D = collection._positionBuffer;
                        bufferProperty2D = 'vertexBuffer';
                    }

                    attributes[0][bufferProperty3D] = buffer3D;
                    attributes[1][bufferProperty3D] = buffer3D;
                    attributes[2][bufferProperty2D] = buffer2D;
                    attributes[3][bufferProperty2D] = buffer2D;
                    attributes[4][bufferProperty3D] = buffer3D;
                    attributes[5][bufferProperty3D] = buffer3D;
                    attributes[6][bufferProperty2D] = buffer2D;
                    attributes[7][bufferProperty2D] = buffer2D;
                    attributes[8][bufferProperty3D] = buffer3D;
                    attributes[9][bufferProperty3D] = buffer3D;
                    attributes[10][bufferProperty2D] = buffer2D;
                    attributes[11][bufferProperty2D] = buffer2D;

                    var va = new VertexArray({
                        context : context,
                        attributes : attributes,
                        indexBuffer : indexBuffer
                    });
                    collection._vertexArrays.push({
                        va : va,
                        buckets : vertexArrayBuckets[k]
                    });
                }
            }
        }
    }

    var scratchUniformArray = [];
    function createMaterialId(material) {
        var uniforms = Material._uniformList[material.type];
        var length = uniforms.length;
        scratchUniformArray.length = 2.0 * length;

        var index = 0;
        for (var i = 0; i < length; ++i) {
            var uniform = uniforms[i];
            scratchUniformArray[index] = uniform;
            scratchUniformArray[index + 1] = material._uniforms[uniform]();
            index += 2;
        }

        return material.type + ':' + JSON.stringify(scratchUniformArray);
    }

    function sortPolylinesIntoBuckets(collection) {
        var mode = collection._mode;
        var modelMatrix = collection._modelMatrix;

        var polylineBuckets = collection._polylineBuckets = {};
        var polylines = collection._polylines;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {
            var p = polylines[i];
            if (p._actualPositions.length > 1) {
                p.update();
                var material = p.material;
                var value = polylineBuckets[material.type];
                if (!defined(value)) {
                    value = polylineBuckets[material.type] = new PolylineBucket(material, mode, modelMatrix);
                }
                value.addPolyline(p);
            }
        }
    }

    function updateMode(collection, frameState) {
        var mode = frameState.mode;

        if (collection._mode !== mode || (!Matrix4.equals(collection._modelMatrix, collection.modelMatrix))) {
            collection._mode = mode;
            collection._modelMatrix = Matrix4.clone(collection.modelMatrix);
            collection._createVertexArray = true;
        }
    }

    function removePolylines(collection) {
        if (collection._polylinesRemoved) {
            collection._polylinesRemoved = false;

            var polylines = [];

            var length = collection._polylines.length;
            for ( var i = 0, j = 0; i < length; ++i) {
                var polyline = collection._polylines[i];
                if (defined(polyline)) {
                    polyline._index = j++;
                    polylines.push(polyline);
                }
            }

            collection._polylines = polylines;
        }
    }

    function releaseShaders(collection) {
        var polylines = collection._polylines;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {
            if (defined(polylines[i])) {
                var bucket = polylines[i]._bucket;
                if (defined(bucket)) {
                    bucket.shaderProgram = bucket.shaderProgram && bucket.shaderProgram.destroy();
                }
            }
        }
    }

    function destroyVertexArrays(collection) {
        var length = collection._vertexArrays.length;
        for ( var t = 0; t < length; ++t) {
            collection._vertexArrays[t].va.destroy();
        }
        collection._vertexArrays.length = 0;
    }

    PolylineCollection.prototype._updatePolyline = function(polyline, propertyChanged) {
        this._polylinesUpdated = true;
        this._polylinesToUpdate.push(polyline);
        ++this._propertiesChanged[propertyChanged];
    };

    function destroyPolylines(collection) {
        var polylines = collection._polylines;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {
            if (defined(polylines[i])) {
                polylines[i]._destroy();
            }
        }
    }

    function VertexArrayBucketLocator(count, offset, bucket) {
        this.count = count;
        this.offset = offset;
        this.bucket = bucket;
    }

    function PolylineBucket(material, mode, modelMatrix) {
        this.polylines = [];
        this.lengthOfPositions = 0;
        this.material = material;
        this.shaderProgram = undefined;
        this.pickShaderProgram = undefined;
        this.mode = mode;
        this.modelMatrix = modelMatrix;
    }

    PolylineBucket.prototype.addPolyline = function(p) {
        var polylines = this.polylines;
        polylines.push(p);
        p._actualLength = this.getPolylinePositionsLength(p);
        this.lengthOfPositions += p._actualLength;
        p._bucket = this;
    };

    PolylineBucket.prototype.updateShader = function(context, batchTable) {
        if (defined(this.shaderProgram)) {
            return;
        }

        var defines = ['DISTANCE_DISPLAY_CONDITION'];
        var vsSource = batchTable.getVertexShaderCallback()(PolylineVS);
        var vs = new ShaderSource({
            defines : defines,
            sources : [PolylineCommon, vsSource]
        });
        var fs = new ShaderSource({
            sources : [this.material.shaderSource, PolylineFS]
        });
        var fsPick = new ShaderSource({
            sources : fs.sources,
            pickColorQualifier : 'varying'
        });

        this.shaderProgram = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });

        this.pickShaderProgram = ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : vs,
            fragmentShaderSource : fsPick,
            attributeLocations : attributeLocations
        });
    };

    function intersectsIDL(polyline) {
        return Cartesian3.dot(Cartesian3.UNIT_X, polyline._boundingVolume.center) < 0 ||
            polyline._boundingVolume.intersectPlane(Plane.ORIGIN_ZX_PLANE) === Intersect.INTERSECTING;
    }

    PolylineBucket.prototype.getPolylinePositionsLength = function(polyline) {
        var length;
        if (this.mode === SceneMode.SCENE3D || !intersectsIDL(polyline)) {
            length = polyline._actualPositions.length;
            return length * 4.0 - 4.0;
        }

        var count = 0;
        var segmentLengths = polyline._segments.lengths;
        length = segmentLengths.length;
        for (var i = 0; i < length; ++i) {
            count += segmentLengths[i] * 4.0 - 4.0;
        }

        return count;
    };

    var scratchWritePosition = new Cartesian3();
    var scratchWritePrevPosition = new Cartesian3();
    var scratchWriteNextPosition = new Cartesian3();
    var scratchWriteVector = new Cartesian3();
    var scratchPickColorCartesian = new Cartesian4();
    var scratchWidthShowCartesian = new Cartesian2();

    PolylineBucket.prototype.write = function(positionArray, texCoordExpandAndBatchIndexArray, positionIndex, colorIndex, texCoordExpandAndBatchIndexIndex, batchTable, context, projection) {
        var mode = this.mode;
        var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI;

        var polylines = this.polylines;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {
            var polyline = polylines[i];
            var width = polyline.width;
            var show = polyline.show && width > 0.0;
            var polylineBatchIndex = polyline._index;
            var segments = this.getSegments(polyline, projection);
            var positions = segments.positions;
            var lengths = segments.lengths;
            var positionsLength = positions.length;

            var pickColor = polyline.getPickId(context).color;

            var segmentIndex = 0;
            var count = 0;
            var position;

            for ( var j = 0; j < positionsLength; ++j) {
                if (j === 0) {
                    if (polyline._loop) {
                        position = positions[positionsLength - 2];
                    } else {
                        position = scratchWriteVector;
                        Cartesian3.subtract(positions[0], positions[1], position);
                        Cartesian3.add(positions[0], position, position);
                    }
                } else {
                    position = positions[j - 1];
                }

                Cartesian3.clone(position, scratchWritePrevPosition);
                Cartesian3.clone(positions[j], scratchWritePosition);

                if (j === positionsLength - 1) {
                    if (polyline._loop) {
                        position = positions[1];
                    } else {
                        position = scratchWriteVector;
                        Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], position);
                        Cartesian3.add(positions[positionsLength - 1], position, position);
                    }
                } else {
                    position = positions[j + 1];
                }

                Cartesian3.clone(position, scratchWriteNextPosition);

                var segmentLength = lengths[segmentIndex];
                if (j === count + segmentLength) {
                    count += segmentLength;
                    ++segmentIndex;
                }

                var segmentStart = j - count === 0;
                var segmentEnd = j === count + lengths[segmentIndex] - 1;

                if (mode === SceneMode.SCENE2D) {
                    scratchWritePrevPosition.z = 0.0;
                    scratchWritePosition.z = 0.0;
                    scratchWriteNextPosition.z = 0.0;
                }

                if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
                    if ((segmentStart || segmentEnd) && maxLon - Math.abs(scratchWritePosition.x) < 1.0) {
                        if ((scratchWritePosition.x < 0.0 && scratchWritePrevPosition.x > 0.0) ||
                            (scratchWritePosition.x > 0.0 && scratchWritePrevPosition.x < 0.0)) {
                            Cartesian3.clone(scratchWritePosition, scratchWritePrevPosition);
                        }

                        if ((scratchWritePosition.x < 0.0 && scratchWriteNextPosition.x > 0.0) ||
                            (scratchWritePosition.x > 0.0 && scratchWriteNextPosition.x < 0.0)) {
                            Cartesian3.clone(scratchWritePosition, scratchWriteNextPosition);
                        }
                    }
                }

                var startK = (segmentStart) ? 2 : 0;
                var endK = (segmentEnd) ? 2 : 4;

                for (var k = startK; k < endK; ++k) {
                    EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex);
                    EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6);
                    EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12);

                    var direction = (k - 2 < 0) ? -1.0 : 1.0;
                    texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex] = j / (positionsLength - 1); // s tex coord
                    texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 1] = 2 * (k % 2) - 1;       // expand direction
                    texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 2] = direction;
                    texCoordExpandAndBatchIndexArray[texCoordExpandAndBatchIndexIndex + 3] = polylineBatchIndex;

                    positionIndex += 6 * 3;
                    texCoordExpandAndBatchIndexIndex += 4;
                }
            }

            var colorCartesian = scratchPickColorCartesian;
            colorCartesian.x = Color.floatToByte(pickColor.red);
            colorCartesian.y = Color.floatToByte(pickColor.green);
            colorCartesian.z = Color.floatToByte(pickColor.blue);
            colorCartesian.w = Color.floatToByte(pickColor.alpha);

            var widthShowCartesian = scratchWidthShowCartesian;
            widthShowCartesian.x = width;
            widthShowCartesian.y = show ? 1.0 : 0.0;

            var boundingSphere = mode === SceneMode.SCENE2D ? polyline._boundingVolume2D : polyline._boundingVolumeWC;
            var encodedCenter = EncodedCartesian3.fromCartesian(boundingSphere.center, scratchUpdatePolylineEncodedCartesian);
            var high = encodedCenter.high;
            var low = Cartesian4.fromElements(encodedCenter.low.x, encodedCenter.low.y, encodedCenter.low.z, boundingSphere.radius, scratchUpdatePolylineCartesian4);

            var nearFarCartesian = scratchNearFarCartesian2;
            nearFarCartesian.x = 0.0;
            nearFarCartesian.y = Number.MAX_VALUE;

            var distanceDisplayCondition = polyline.distanceDisplayCondition;
            if (defined(distanceDisplayCondition)) {
                nearFarCartesian.x = distanceDisplayCondition.near;
                nearFarCartesian.y = distanceDisplayCondition.far;
            }

            batchTable.setBatchedAttribute(polylineBatchIndex, 0, widthShowCartesian);
            batchTable.setBatchedAttribute(polylineBatchIndex, 1, colorCartesian);

            if (batchTable.attributes.length > 2) {
                batchTable.setBatchedAttribute(polylineBatchIndex, 2, high);
                batchTable.setBatchedAttribute(polylineBatchIndex, 3, low);
                batchTable.setBatchedAttribute(polylineBatchIndex, 4, nearFarCartesian);
            }
        }
    };

    var morphPositionScratch = new Cartesian3();
    var morphPrevPositionScratch = new Cartesian3();
    var morphNextPositionScratch = new Cartesian3();
    var morphVectorScratch = new Cartesian3();

    PolylineBucket.prototype.writeForMorph = function(positionArray, positionIndex) {
        var modelMatrix = this.modelMatrix;
        var polylines = this.polylines;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {
            var polyline = polylines[i];
            var positions = polyline._segments.positions;
            var lengths = polyline._segments.lengths;
            var positionsLength = positions.length;

            var segmentIndex = 0;
            var count = 0;

            for ( var j = 0; j < positionsLength; ++j) {
                var prevPosition;
                if (j === 0) {
                    if (polyline._loop) {
                        prevPosition = positions[positionsLength - 2];
                    } else {
                        prevPosition = morphVectorScratch;
                        Cartesian3.subtract(positions[0], positions[1], prevPosition);
                        Cartesian3.add(positions[0], prevPosition, prevPosition);
                    }
                } else {
                    prevPosition = positions[j - 1];
                }

                prevPosition = Matrix4.multiplyByPoint(modelMatrix, prevPosition, morphPrevPositionScratch);

                var position = Matrix4.multiplyByPoint(modelMatrix, positions[j], morphPositionScratch);

                var nextPosition;
                if (j === positionsLength - 1) {
                    if (polyline._loop) {
                        nextPosition = positions[1];
                    } else {
                        nextPosition = morphVectorScratch;
                        Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], nextPosition);
                        Cartesian3.add(positions[positionsLength - 1], nextPosition, nextPosition);
                    }
                } else {
                    nextPosition = positions[j + 1];
                }

                nextPosition = Matrix4.multiplyByPoint(modelMatrix, nextPosition, morphNextPositionScratch);

                var segmentLength = lengths[segmentIndex];
                if (j === count + segmentLength) {
                    count += segmentLength;
                    ++segmentIndex;
                }

                var segmentStart = j - count === 0;
                var segmentEnd = j === count + lengths[segmentIndex] - 1;

                var startK = (segmentStart) ? 2 : 0;
                var endK = (segmentEnd) ? 2 : 4;

                for (var k = startK; k < endK; ++k) {
                    EncodedCartesian3.writeElements(position, positionArray, positionIndex);
                    EncodedCartesian3.writeElements(prevPosition, positionArray, positionIndex + 6);
                    EncodedCartesian3.writeElements(nextPosition, positionArray, positionIndex + 12);

                    positionIndex += 6 * 3;
                }
            }
        }
    };

    var scratchSegmentLengths = new Array(1);

    PolylineBucket.prototype.updateIndices = function(totalIndices, vertexBufferOffset, vertexArrayBuckets, offset) {
        var vaCount = vertexArrayBuckets.length - 1;
        var bucketLocator = new VertexArrayBucketLocator(0, offset, this);
        vertexArrayBuckets[vaCount].push(bucketLocator);
        var count = 0;
        var indices = totalIndices[totalIndices.length - 1];
        var indicesCount = 0;
        if (indices.length > 0) {
            indicesCount = indices[indices.length - 1] + 1;
        }
        var polylines = this.polylines;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {

            var polyline = polylines[i];
            polyline._locatorBuckets = [];

            var segments;
            if (this.mode === SceneMode.SCENE3D) {
                segments = scratchSegmentLengths;
                var positionsLength = polyline._actualPositions.length;
                if (positionsLength > 0) {
                    segments[0] = positionsLength;
                } else {
                    continue;
                }
            } else {
                segments = polyline._segments.lengths;
            }

            var numberOfSegments = segments.length;
            if (numberOfSegments > 0) {
                var segmentIndexCount = 0;
                for ( var j = 0; j < numberOfSegments; ++j) {
                    var segmentLength = segments[j] - 1.0;
                    for ( var k = 0; k < segmentLength; ++k) {
                        if (indicesCount + 4 >= CesiumMath.SIXTY_FOUR_KILOBYTES - 2) {
                            polyline._locatorBuckets.push({
                                locator : bucketLocator,
                                count : segmentIndexCount
                            });
                            segmentIndexCount = 0;
                            vertexBufferOffset.push(4);
                            indices = [];
                            totalIndices.push(indices);
                            indicesCount = 0;
                            bucketLocator.count = count;
                            count = 0;
                            offset = 0;
                            bucketLocator = new VertexArrayBucketLocator(0, 0, this);
                            vertexArrayBuckets[++vaCount] = [bucketLocator];
                        }

                        indices.push(indicesCount, indicesCount + 2, indicesCount + 1);
                        indices.push(indicesCount + 1, indicesCount + 2, indicesCount + 3);

                        segmentIndexCount += 6;
                        count += 6;
                        offset += 6;
                        indicesCount += 4;
                    }
                }

                polyline._locatorBuckets.push({
                    locator : bucketLocator,
                    count : segmentIndexCount
                });

                if (indicesCount + 4 >= CesiumMath.SIXTY_FOUR_KILOBYTES - 2) {
                    vertexBufferOffset.push(0);
                    indices = [];
                    totalIndices.push(indices);
                    indicesCount = 0;
                    bucketLocator.count = count;
                    offset = 0;
                    count = 0;
                    bucketLocator = new VertexArrayBucketLocator(0, 0, this);
                    vertexArrayBuckets[++vaCount] = [bucketLocator];
                }
            }
            polyline._clean();
        }
        bucketLocator.count = count;
        return offset;
    };

    PolylineBucket.prototype.getPolylineStartIndex = function(polyline) {
        var polylines = this.polylines;
        var positionIndex = 0;
        var length = polylines.length;
        for ( var i = 0; i < length; ++i) {
            var p = polylines[i];
            if (p === polyline) {
                break;
            }
            positionIndex += p._actualLength;
        }
        return positionIndex;
    };

    var scratchSegments = {
        positions : undefined,
        lengths : undefined
    };
    var scratchLengths = new Array(1);
    var pscratch = new Cartesian3();
    var scratchCartographic = new Cartographic();

    PolylineBucket.prototype.getSegments = function(polyline, projection) {
        var positions = polyline._actualPositions;

        if (this.mode === SceneMode.SCENE3D) {
            scratchLengths[0] = positions.length;
            scratchSegments.positions = positions;
            scratchSegments.lengths = scratchLengths;
            return scratchSegments;
        }

        if (intersectsIDL(polyline)) {
            positions = polyline._segments.positions;
        }

        var ellipsoid = projection.ellipsoid;
        var newPositions = [];
        var modelMatrix = this.modelMatrix;
        var length = positions.length;
        var position;
        var p = pscratch;

        for ( var n = 0; n < length; ++n) {
            position = positions[n];
            p = Matrix4.multiplyByPoint(modelMatrix, position, p);
            newPositions.push(projection.project(ellipsoid.cartesianToCartographic(p, scratchCartographic)));
        }

        if (newPositions.length > 0) {
            polyline._boundingVolume2D = BoundingSphere.fromPoints(newPositions, polyline._boundingVolume2D);
            var center2D = polyline._boundingVolume2D.center;
            polyline._boundingVolume2D.center = new Cartesian3(center2D.z, center2D.x, center2D.y);
        }

        scratchSegments.positions = newPositions;
        scratchSegments.lengths = polyline._segments.lengths;
        return scratchSegments;
    };

    var scratchPositionsArray;

    PolylineBucket.prototype.writeUpdate = function(index, polyline, positionBuffer, projection) {
        var mode = this.mode;
        var maxLon = projection.ellipsoid.maximumRadius * CesiumMath.PI;

        var positionsLength = polyline._actualLength;
        if (positionsLength) {
            index += this.getPolylineStartIndex(polyline);

            var positionArray = scratchPositionsArray;
            var positionsArrayLength = 6 * positionsLength * 3;

            if (!defined(positionArray) || positionArray.length < positionsArrayLength) {
                positionArray = scratchPositionsArray = new Float32Array(positionsArrayLength);
            } else if (positionArray.length > positionsArrayLength) {
                positionArray = new Float32Array(positionArray.buffer, 0, positionsArrayLength);
            }

            var segments = this.getSegments(polyline, projection);
            var positions = segments.positions;
            var lengths = segments.lengths;

            var positionIndex = 0;
            var segmentIndex = 0;
            var count = 0;
            var position;

            positionsLength = positions.length;
            for ( var i = 0; i < positionsLength; ++i) {
                if (i === 0) {
                    if (polyline._loop) {
                        position = positions[positionsLength - 2];
                    } else {
                        position = scratchWriteVector;
                        Cartesian3.subtract(positions[0], positions[1], position);
                        Cartesian3.add(positions[0], position, position);
                    }
                } else {
                    position = positions[i - 1];
                }

                Cartesian3.clone(position, scratchWritePrevPosition);
                Cartesian3.clone(positions[i], scratchWritePosition);

                if (i === positionsLength - 1) {
                    if (polyline._loop) {
                        position = positions[1];
                    } else {
                        position = scratchWriteVector;
                        Cartesian3.subtract(positions[positionsLength - 1], positions[positionsLength - 2], position);
                        Cartesian3.add(positions[positionsLength - 1], position, position);
                    }
                } else {
                    position = positions[i + 1];
                }

                Cartesian3.clone(position, scratchWriteNextPosition);

                var segmentLength = lengths[segmentIndex];
                if (i === count + segmentLength) {
                    count += segmentLength;
                    ++segmentIndex;
                }

                var segmentStart = i - count === 0;
                var segmentEnd = i === count + lengths[segmentIndex] - 1;

                if (mode === SceneMode.SCENE2D) {
                    scratchWritePrevPosition.z = 0.0;
                    scratchWritePosition.z = 0.0;
                    scratchWriteNextPosition.z = 0.0;
                }

                if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
                    if ((segmentStart || segmentEnd) && maxLon - Math.abs(scratchWritePosition.x) < 1.0) {
                        if ((scratchWritePosition.x < 0.0 && scratchWritePrevPosition.x > 0.0) ||
                            (scratchWritePosition.x > 0.0 && scratchWritePrevPosition.x < 0.0)) {
                            Cartesian3.clone(scratchWritePosition, scratchWritePrevPosition);
                        }

                        if ((scratchWritePosition.x < 0.0 && scratchWriteNextPosition.x > 0.0) ||
                            (scratchWritePosition.x > 0.0 && scratchWriteNextPosition.x < 0.0)) {
                            Cartesian3.clone(scratchWritePosition, scratchWriteNextPosition);
                        }
                    }
                }

                var startJ = (segmentStart) ? 2 : 0;
                var endJ = (segmentEnd) ? 2 : 4;

                for (var j = startJ; j < endJ; ++j) {
                    EncodedCartesian3.writeElements(scratchWritePosition, positionArray, positionIndex);
                    EncodedCartesian3.writeElements(scratchWritePrevPosition, positionArray, positionIndex + 6);
                    EncodedCartesian3.writeElements(scratchWriteNextPosition, positionArray, positionIndex + 12);
                    positionIndex += 6 * 3;
                }
            }

            positionBuffer.copyFromArrayView(positionArray, 6 * 3 * Float32Array.BYTES_PER_ELEMENT * index);
        }
    };

    return PolylineCollection;
});

/*global define*/
define('DataSources/ScaledPositionProperty',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/Event',
        '../Core/ReferenceFrame',
        './Property'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        Event,
        ReferenceFrame,
        Property) {
    'use strict';

    /**
     * This is a temporary class for scaling position properties to the WGS84 surface.
     * It will go away or be refactored to support data with arbitrary height references.
     * @private
     */
    function ScaledPositionProperty(value) {
        this._definitionChanged = new Event();
        this._value = undefined;
        this._removeSubscription = undefined;
        this.setValue(value);
    }

    defineProperties(ScaledPositionProperty.prototype, {
        isConstant : {
            get : function() {
                return Property.isConstant(this._value);
            }
        },
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        referenceFrame : {
            get : function() {
                return defined(this._value) ? this._value.referenceFrame : ReferenceFrame.FIXED;
            }
        }
    });

    ScaledPositionProperty.prototype.getValue = function(time, result) {
        return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result);
    };

    ScaledPositionProperty.prototype.setValue = function(value) {
        if (this._value !== value) {
            this._value = value;

            if (defined(this._removeSubscription)) {
                this._removeSubscription();
                this._removeSubscription = undefined;
            }

            if (defined(value)) {
                this._removeSubscription = value.definitionChanged.addEventListener(this._raiseDefinitionChanged, this);
            }
            this._definitionChanged.raiseEvent(this);
        }
    };

    ScaledPositionProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(referenceFrame)) {
            throw new DeveloperError('referenceFrame is required.');
        }
        
        if (!defined(this._value)) {
            return undefined;
        }

        result = this._value.getValueInReferenceFrame(time, referenceFrame, result);
        return defined(result) ? Ellipsoid.WGS84.scaleToGeodeticSurface(result, result) : undefined;
    };

    ScaledPositionProperty.prototype.equals = function(other) {
        return this === other || (other instanceof ScaledPositionProperty && this._value === other._value);
    };

    ScaledPositionProperty.prototype._raiseDefinitionChanged = function() {
        this._definitionChanged.raiseEvent(this);
    };

    return ScaledPositionProperty;
});

/*global define*/
define('DataSources/PathVisualizer',[
        '../Core/AssociativeArray',
        '../Core/Cartesian3',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/JulianDate',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/ReferenceFrame',
        '../Core/TimeInterval',
        '../Core/Transforms',
        '../Scene/PolylineCollection',
        '../Scene/SceneMode',
        './CompositePositionProperty',
        './ConstantPositionProperty',
        './MaterialProperty',
        './Property',
        './ReferenceProperty',
        './SampledPositionProperty',
        './ScaledPositionProperty',
        './TimeIntervalCollectionPositionProperty'
    ], function(
        AssociativeArray,
        Cartesian3,
        defined,
        destroyObject,
        DeveloperError,
        JulianDate,
        Matrix3,
        Matrix4,
        ReferenceFrame,
        TimeInterval,
        Transforms,
        PolylineCollection,
        SceneMode,
        CompositePositionProperty,
        ConstantPositionProperty,
        MaterialProperty,
        Property,
        ReferenceProperty,
        SampledPositionProperty,
        ScaledPositionProperty,
        TimeIntervalCollectionPositionProperty) {
    'use strict';

    var defaultResolution = 60.0;
    var defaultWidth = 1.0;

    var scratchTimeInterval = new TimeInterval();
    var subSampleCompositePropertyScratch = new TimeInterval();
    var subSampleIntervalPropertyScratch = new TimeInterval();

    function EntityData(entity) {
        this.entity = entity;
        this.polyline = undefined;
        this.index = undefined;
        this.updater = undefined;
    }

    function subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, startingIndex, result) {
        var r = startingIndex;
        //Always step exactly on start (but only use it if it exists.)
        var tmp;
        tmp = property.getValueInReferenceFrame(start, referenceFrame, result[r]);
        if (defined(tmp)) {
            result[r++] = tmp;
        }

        var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);

        //Iterate over all interval times and add the ones that fall in our
        //time range.  Note that times can contain data outside of
        //the intervals range.  This is by design for use with interpolation.
        var t = 0;
        var len = times.length;
        var current = times[t];
        var loopStop = stop;
        var sampling = false;
        var sampleStepsToTake;
        var sampleStepsTaken;
        var sampleStepSize;

        while (t < len) {
            if (!steppedOnNow && JulianDate.greaterThanOrEquals(current, updateTime)) {
                tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[r]);
                if (defined(tmp)) {
                    result[r++] = tmp;
                }
                steppedOnNow = true;
            }
            if (JulianDate.greaterThan(current, start) && JulianDate.lessThan(current, loopStop) && !current.equals(updateTime)) {
                tmp = property.getValueInReferenceFrame(current, referenceFrame, result[r]);
                if (defined(tmp)) {
                    result[r++] = tmp;
                }
            }

            if (t < (len - 1)) {
                if (maximumStep > 0 && !sampling) {
                    var next = times[t + 1];
                    var secondsUntilNext = JulianDate.secondsDifference(next, current);
                    sampling = secondsUntilNext > maximumStep;

                    if (sampling) {
                        sampleStepsToTake = Math.ceil(secondsUntilNext / maximumStep);
                        sampleStepsTaken = 0;
                        sampleStepSize = secondsUntilNext / Math.max(sampleStepsToTake, 2);
                        sampleStepsToTake = Math.max(sampleStepsToTake - 1, 1);
                    }
                }

                if (sampling && sampleStepsTaken < sampleStepsToTake) {
                    current = JulianDate.addSeconds(current, sampleStepSize, new JulianDate());
                    sampleStepsTaken++;
                    continue;
                }
            }
            sampling = false;
            t++;
            current = times[t];
        }

        //Always step exactly on stop (but only use it if it exists.)
        tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[r]);
        if (defined(tmp)) {
            result[r++] = tmp;
        }

        return r;
    }

    function subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
        var tmp;
        var i = 0;
        var index = startingIndex;
        var time = start;
        var stepSize = Math.max(maximumStep, 60);
        var steppedOnNow = !defined(updateTime) || JulianDate.lessThanOrEquals(updateTime, start) || JulianDate.greaterThanOrEquals(updateTime, stop);
        while (JulianDate.lessThan(time, stop)) {
            if (!steppedOnNow && JulianDate.greaterThanOrEquals(time, updateTime)) {
                steppedOnNow = true;
                tmp = property.getValueInReferenceFrame(updateTime, referenceFrame, result[index]);
                if (defined(tmp)) {
                    result[index] = tmp;
                    index++;
                }
            }
            tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
            if (defined(tmp)) {
                result[index] = tmp;
                index++;
            }
            i++;
            time = JulianDate.addSeconds(start, stepSize * i, new JulianDate());
        }
        //Always sample stop.
        tmp = property.getValueInReferenceFrame(stop, referenceFrame, result[index]);
        if (defined(tmp)) {
            result[index] = tmp;
            index++;
        }
        return index;
    }

    function subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
        subSampleIntervalPropertyScratch.start = start;
        subSampleIntervalPropertyScratch.stop = stop;

        var index = startingIndex;
        var intervals = property.intervals;
        for (var i = 0; i < intervals.length; i++) {
            var interval = intervals.get(i);
            if (!TimeInterval.intersect(interval, subSampleIntervalPropertyScratch, scratchTimeInterval).isEmpty) {
                var time = interval.start;
                if (!interval.isStartIncluded) {
                    if (interval.isStopIncluded) {
                        time = interval.stop;
                    } else {
                        time = JulianDate.addSeconds(interval.start, JulianDate.secondsDifference(interval.stop, interval.start) / 2, new JulianDate());
                    }
                }
                var tmp = property.getValueInReferenceFrame(time, referenceFrame, result[index]);
                if (defined(tmp)) {
                    result[index] = tmp;
                    index++;
                }
            }
        }
        return index;
    }

    function subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
        var tmp = property.getValueInReferenceFrame(start, referenceFrame, result[startingIndex]);
        if (defined(tmp)) {
            result[startingIndex++] = tmp;
        }
        return startingIndex;
    }

    function subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, startingIndex, result) {
        subSampleCompositePropertyScratch.start = start;
        subSampleCompositePropertyScratch.stop = stop;

        var index = startingIndex;
        var intervals = property.intervals;
        for (var i = 0; i < intervals.length; i++) {
            var interval = intervals.get(i);
            if (!TimeInterval.intersect(interval, subSampleCompositePropertyScratch, scratchTimeInterval).isEmpty) {
                var intervalStart = interval.start;
                var intervalStop = interval.stop;

                var sampleStart = start;
                if (JulianDate.greaterThan(intervalStart, sampleStart)) {
                    sampleStart = intervalStart;
                }

                var sampleStop = stop;
                if (JulianDate.lessThan(intervalStop, sampleStop)) {
                    sampleStop = intervalStop;
                }

                index = reallySubSample(interval.data, sampleStart, sampleStop, updateTime, referenceFrame, maximumStep, index, result);
            }
        }
        return index;
    }

    function reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, index, result) {
        //Unwrap any references until we have the actual property.
        while (property instanceof ReferenceProperty) {
            property = property.resolvedProperty;
        }

        if (property instanceof SampledPositionProperty) {
            var times = property._property._times;
            index = subSampleSampledProperty(property, start, stop, times, updateTime, referenceFrame, maximumStep, index, result);
        } else if (property instanceof CompositePositionProperty) {
            index = subSampleCompositeProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
        } else if (property instanceof TimeIntervalCollectionPositionProperty) {
            index = subSampleIntervalProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
        } else if (property instanceof ConstantPositionProperty ||
                   (property instanceof ScaledPositionProperty && Property.isConstant(property))) {
            index = subSampleConstantProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
        } else {
            //Fallback to generic sampling.
            index = subSampleGenericProperty(property, start, stop, updateTime, referenceFrame, maximumStep, index, result);
        }
        return index;
    }

    function subSample(property, start, stop, updateTime, referenceFrame, maximumStep, result) {
        if (!defined(result)) {
            result = [];
        }

        var length = reallySubSample(property, start, stop, updateTime, referenceFrame, maximumStep, 0, result);
        result.length = length;
        return result;
    }

    var toFixedScratch = new Matrix3();
    function PolylineUpdater(scene, referenceFrame) {
        this._unusedIndexes = [];
        this._polylineCollection = new PolylineCollection();
        this._scene = scene;
        this._referenceFrame = referenceFrame;
        scene.primitives.add(this._polylineCollection);
    }

    PolylineUpdater.prototype.update = function(time) {
        if (this._referenceFrame === ReferenceFrame.INERTIAL) {
            var toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch);
            if (!defined(toFixed)) {
                toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch);
            }
            Matrix4.fromRotationTranslation(toFixed, Cartesian3.ZERO, this._polylineCollection.modelMatrix);
        }
    };

    PolylineUpdater.prototype.updateObject = function(time, item) {
        var entity = item.entity;
        var pathGraphics = entity._path;
        var positionProperty = entity._position;

        var sampleStart;
        var sampleStop;
        var showProperty = pathGraphics._show;
        var polyline = item.polyline;
        var show = entity.isShowing && (!defined(showProperty) || showProperty.getValue(time));

        //While we want to show the path, there may not actually be anything to show
        //depending on lead/trail settings.  Compute the interval of the path to
        //show and check against actual availability.
        if (show) {
            var leadTime = Property.getValueOrUndefined(pathGraphics._leadTime, time);
            var trailTime = Property.getValueOrUndefined(pathGraphics._trailTime, time);
            var availability = entity._availability;
            var hasAvailability = defined(availability);
            var hasLeadTime = defined(leadTime);
            var hasTrailTime = defined(trailTime);

            //Objects need to have either defined availability or both a lead and trail time in order to
            //draw a path (since we can't draw "infinite" paths.
            show = hasAvailability || (hasLeadTime && hasTrailTime);

            //The final step is to compute the actual start/stop times of the path to show.
            //If current time is outside of the availability interval, there's a chance that
            //we won't have to draw anything anyway.
            if (show) {
                if (hasTrailTime) {
                    sampleStart = JulianDate.addSeconds(time, -trailTime, new JulianDate());
                }
                if (hasLeadTime) {
                    sampleStop = JulianDate.addSeconds(time, leadTime, new JulianDate());
                }

                if (hasAvailability) {
                    var start = availability.start;
                    var stop = availability.stop;

                    if (!hasTrailTime || JulianDate.greaterThan(start, sampleStart)) {
                        sampleStart = start;
                    }

                    if (!hasLeadTime || JulianDate.lessThan(stop, sampleStop)) {
                        sampleStop = stop;
                    }
                }
                show = JulianDate.lessThan(sampleStart, sampleStop);
            }
        }

        if (!show) {
            //don't bother creating or updating anything else
            if (defined(polyline)) {
                this._unusedIndexes.push(item.index);
                item.polyline = undefined;
                polyline.show = false;
                item.index = undefined;
            }
            return;
        }

        if (!defined(polyline)) {
            var unusedIndexes = this._unusedIndexes;
            var length = unusedIndexes.length;
            if (length > 0) {
                var index = unusedIndexes.pop();
                polyline = this._polylineCollection.get(index);
                item.index = index;
            } else {
                item.index = this._polylineCollection.length;
                polyline = this._polylineCollection.add();
            }
            polyline.id = entity;
            item.polyline = polyline;
        }

        var resolution = Property.getValueOrDefault(pathGraphics._resolution, time, defaultResolution);

        polyline.show = true;
        polyline.positions = subSample(positionProperty, sampleStart, sampleStop, time, this._referenceFrame, resolution, polyline.positions.slice());
        polyline.material = MaterialProperty.getValue(time, pathGraphics._material, polyline.material);
        polyline.width = Property.getValueOrDefault(pathGraphics._width, time, defaultWidth);
        polyline.distanceDisplayCondition = Property.getValueOrUndefined(pathGraphics._distanceDisplayCondition, time, polyline.distanceDisplayCondition);
    };

    PolylineUpdater.prototype.removeObject = function(item) {
        var polyline = item.polyline;
        if (defined(polyline)) {
            this._unusedIndexes.push(item.index);
            item.polyline = undefined;
            polyline.show = false;
            polyline.id = undefined;
            item.index = undefined;
        }
    };

    PolylineUpdater.prototype.destroy = function() {
        this._scene.primitives.remove(this._polylineCollection);
        return destroyObject(this);
    };

    /**
     * A {@link Visualizer} which maps {@link Entity#path} to a {@link Polyline}.
     * @alias PathVisualizer
     * @constructor
     *
     * @param {Scene} scene The scene the primitives will be rendered in.
     * @param {EntityCollection} entityCollection The entityCollection to visualize.
     */
    function PathVisualizer(scene, entityCollection) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(entityCollection)) {
            throw new DeveloperError('entityCollection is required.');
        }
        
        entityCollection.collectionChanged.addEventListener(PathVisualizer.prototype._onCollectionChanged, this);

        this._scene = scene;
        this._updaters = {};
        this._entityCollection = entityCollection;
        this._items = new AssociativeArray();

        this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
    }

    /**
     * Updates all of the primitives created by this visualizer to match their
     * Entity counterpart at the given time.
     *
     * @param {JulianDate} time The time to update to.
     * @returns {Boolean} This function always returns true.
     */
    PathVisualizer.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var updaters = this._updaters;
        for ( var key in updaters) {
            if (updaters.hasOwnProperty(key)) {
                updaters[key].update(time);
            }
        }

        var items = this._items.values;
        for (var i = 0, len = items.length; i < len; i++) {
            var item = items[i];
            var entity = item.entity;
            var positionProperty = entity._position;

            var lastUpdater = item.updater;

            var frameToVisualize = ReferenceFrame.FIXED;
            if (this._scene.mode === SceneMode.SCENE3D) {
                frameToVisualize = positionProperty.referenceFrame;
            }

            var currentUpdater = this._updaters[frameToVisualize];

            if ((lastUpdater === currentUpdater) && (defined(currentUpdater))) {
                currentUpdater.updateObject(time, item);
                continue;
            }

            if (defined(lastUpdater)) {
                lastUpdater.removeObject(item);
            }

            if (!defined(currentUpdater)) {
                currentUpdater = new PolylineUpdater(this._scene, frameToVisualize);
                currentUpdater.update(time);
                this._updaters[frameToVisualize] = currentUpdater;
            }

            item.updater = currentUpdater;
            if (defined(currentUpdater)) {
                currentUpdater.updateObject(time, item);
            }
        }
        return true;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    PathVisualizer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes and destroys all primitives created by this instance.
     */
    PathVisualizer.prototype.destroy = function() {
        this._entityCollection.collectionChanged.removeEventListener(PathVisualizer.prototype._onCollectionChanged, this);

        var updaters = this._updaters;
        for ( var key in updaters) {
            if (updaters.hasOwnProperty(key)) {
                updaters[key].destroy();
            }
        }

        return destroyObject(this);
    };

    PathVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
        var i;
        var entity;
        var item;
        var items = this._items;

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            if (defined(entity._path) && defined(entity._position)) {
                items.set(entity.id, new EntityData(entity));
            }
        }

        for (i = changed.length - 1; i > -1; i--) {
            entity = changed[i];
            if (defined(entity._path) && defined(entity._position)) {
                if (!items.contains(entity.id)) {
                    items.set(entity.id, new EntityData(entity));
                }
            } else {
                item = items.get(entity.id);
                if (defined(item)) {
                    item.updater.removeObject(item);
                    items.remove(entity.id);
                }
            }
        }

        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            item = items.get(entity.id);
            if (defined(item)) {
                if (defined(item.updater)) {
                    item.updater.removeObject(item);
                }
                items.remove(entity.id);
            }
        }
    };

    //for testing
    PathVisualizer._subSample = subSample;

    return PathVisualizer;
});

/*global define*/
define('DataSources/PointVisualizer',[
        '../Core/AssociativeArray',
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/NearFarScalar',
        '../Scene/HeightReference',
        './BoundingSphereState',
        './Property'
    ], function(
        AssociativeArray,
        Cartesian3,
        Color,
        defined,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        NearFarScalar,
        HeightReference,
        BoundingSphereState,
        Property) {
    'use strict';

    var defaultColor = Color.WHITE;
    var defaultOutlineColor = Color.BLACK;
    var defaultOutlineWidth = 0.0;
    var defaultPixelSize = 1.0;

    var color = new Color();
    var position = new Cartesian3();
    var outlineColor = new Color();
    var scaleByDistance = new NearFarScalar();
    var translucencyByDistance = new NearFarScalar();
    var distanceDisplayCondition = new DistanceDisplayCondition();

    function EntityData(entity) {
        this.entity = entity;
        this.pointPrimitive = undefined;
        this.billboard = undefined;
        this.color = undefined;
        this.outlineColor = undefined;
        this.pixelSize = undefined;
        this.outlineWidth = undefined;
    }

    /**
     * A {@link Visualizer} which maps {@link Entity#point} to a {@link PointPrimitive}.
     * @alias PointVisualizer
     * @constructor
     *
     * @param {EntityCluster} entityCluster The entity cluster to manage the collection of billboards and optionally cluster with other entities.
     * @param {EntityCollection} entityCollection The entityCollection to visualize.
     */
    function PointVisualizer(entityCluster, entityCollection) {
                if (!defined(entityCluster)) {
            throw new DeveloperError('entityCluster is required.');
        }
        if (!defined(entityCollection)) {
            throw new DeveloperError('entityCollection is required.');
        }
        
        entityCollection.collectionChanged.addEventListener(PointVisualizer.prototype._onCollectionChanged, this);

        this._cluster = entityCluster;
        this._entityCollection = entityCollection;
        this._items = new AssociativeArray();
        this._onCollectionChanged(entityCollection, entityCollection.values, [], []);
    }

    /**
     * Updates the primitives created by this visualizer to match their
     * Entity counterpart at the given time.
     *
     * @param {JulianDate} time The time to update to.
     * @returns {Boolean} This function always returns true.
     */
    PointVisualizer.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var items = this._items.values;
        var cluster = this._cluster;
        for (var i = 0, len = items.length; i < len; i++) {
            var item = items[i];
            var entity = item.entity;
            var pointGraphics = entity._point;
            var pointPrimitive = item.pointPrimitive;
            var billboard = item.billboard;
            var heightReference = Property.getValueOrDefault(pointGraphics._heightReference, time, HeightReference.NONE);
            var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(pointGraphics._show, time, true);
            if (show) {
                position = Property.getValueOrUndefined(entity._position, time, position);
                show = defined(position);
            }
            if (!show) {
                returnPrimitive(item, entity, cluster);
                continue;
            }

            if (!Property.isConstant(entity._position)) {
                cluster._clusterDirty = true;
            }

            var needsRedraw = false;
            if ((heightReference !== HeightReference.NONE) && !defined(billboard)) {
                if (defined(pointPrimitive)) {
                    returnPrimitive(item, entity, cluster);
                    pointPrimitive = undefined;
                }

                billboard = cluster.getBillboard(entity);
                billboard.id = entity;
                billboard.image = undefined;
                item.billboard = billboard;
                needsRedraw = true;
            } else if ((heightReference === HeightReference.NONE) && !defined(pointPrimitive)) {
                if (defined(billboard)) {
                    returnPrimitive(item, entity, cluster);
                    billboard = undefined;
                }

                pointPrimitive = cluster.getPoint(entity);
                pointPrimitive.id = entity;
                item.pointPrimitive = pointPrimitive;
            }

            if (defined(pointPrimitive)) {
                pointPrimitive.show = true;
                pointPrimitive.position = position;
                pointPrimitive.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance);
                pointPrimitive.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance);
                pointPrimitive.color = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color);
                pointPrimitive.outlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
                pointPrimitive.outlineWidth = Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth);
                pointPrimitive.pixelSize = Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize);
                pointPrimitive.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
            } else { // billboard
                billboard.show = true;
                billboard.position = position;
                billboard.scaleByDistance = Property.getValueOrUndefined(pointGraphics._scaleByDistance, time, scaleByDistance);
                billboard.translucencyByDistance = Property.getValueOrUndefined(pointGraphics._translucencyByDistance, time, translucencyByDistance);
                billboard.distanceDisplayCondition = Property.getValueOrUndefined(pointGraphics._distanceDisplayCondition, time, distanceDisplayCondition);
                billboard.heightReference = heightReference;

                var newColor = Property.getValueOrDefault(pointGraphics._color, time, defaultColor, color);
                var newOutlineColor = Property.getValueOrDefault(pointGraphics._outlineColor, time, defaultOutlineColor, outlineColor);
                var newOutlineWidth = Math.round(Property.getValueOrDefault(pointGraphics._outlineWidth, time, defaultOutlineWidth));
                var newPixelSize = Math.max(1, Math.round(Property.getValueOrDefault(pointGraphics._pixelSize, time, defaultPixelSize)));

                if (newOutlineWidth > 0) {
                    billboard.scale = 1.0;
                    needsRedraw = needsRedraw || //
                                 newOutlineWidth !== item.outlineWidth || //
                                 newPixelSize !== item.pixelSize || //
                                 !Color.equals(newColor, item.color) || //
                                 !Color.equals(newOutlineColor, item.outlineColor);
                } else {
                    billboard.scale = newPixelSize / 50.0;
                    newPixelSize = 50.0;
                    needsRedraw = needsRedraw || //
                                 newOutlineWidth !== item.outlineWidth || //
                                 !Color.equals(newColor, item.color) || //
                                 !Color.equals(newOutlineColor, item.outlineColor);
                }

                if (needsRedraw) {
                    item.color = Color.clone(newColor, item.color);
                    item.outlineColor = Color.clone(newOutlineColor, item.outlineColor);
                    item.pixelSize = newPixelSize;
                    item.outlineWidth = newOutlineWidth;

                    var centerAlpha = newColor.alpha;
                    var cssColor = newColor.toCssColorString();
                    var cssOutlineColor = newOutlineColor.toCssColorString();
                    var textureId = JSON.stringify([cssColor, newPixelSize, cssOutlineColor, newOutlineWidth]);

                    billboard.setImage(textureId, createCallback(centerAlpha, cssColor, cssOutlineColor, newOutlineWidth, newPixelSize));
                }
            }
        }
        return true;
    };

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    PointVisualizer.prototype.getBoundingSphere = function(entity, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var item = this._items.get(entity.id);
        if (!defined(item) || !(defined(item.pointPrimitive) || defined(item.billboard))) {
            return BoundingSphereState.FAILED;
        }

        if (defined(item.pointPrimitive)) {
            result.center = Cartesian3.clone(item.pointPrimitive.position, result.center);
        } else {
            var billboard = item.billboard;
            if (!defined(billboard._clampedPosition)) {
                return BoundingSphereState.PENDING;
            }
            result.center = Cartesian3.clone(billboard._clampedPosition, result.center);
        }

        result.radius = 0;
        return BoundingSphereState.DONE;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    PointVisualizer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes and destroys all primitives created by this instance.
     */
    PointVisualizer.prototype.destroy = function() {
        this._entityCollection.collectionChanged.removeEventListener(PointVisualizer.prototype._onCollectionChanged, this);
        var entities = this._entityCollection.values;
        for (var i = 0; i < entities.length; i++) {
            this._cluster.removePoint(entities[i]);
        }
        return destroyObject(this);
    };

    PointVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) {
        var i;
        var entity;
        var items = this._items;
        var cluster = this._cluster;

        for (i = added.length - 1; i > -1; i--) {
            entity = added[i];
            if (defined(entity._point) && defined(entity._position)) {
                items.set(entity.id, new EntityData(entity));
            }
        }

        for (i = changed.length - 1; i > -1; i--) {
            entity = changed[i];
            if (defined(entity._point) && defined(entity._position)) {
                if (!items.contains(entity.id)) {
                    items.set(entity.id, new EntityData(entity));
                }
            } else {
                returnPrimitive(items.get(entity.id), entity, cluster);
                items.remove(entity.id);
            }
        }

        for (i = removed.length - 1; i > -1; i--) {
            entity = removed[i];
            returnPrimitive(items.get(entity.id), entity, cluster);
            items.remove(entity.id);
        }
    };

    function returnPrimitive(item, entity, cluster) {
        if (defined(item)) {
            var pointPrimitive = item.pointPrimitive;
            if (defined(pointPrimitive)) {
                item.pointPrimitive = undefined;
                cluster.removePoint(entity);
                return;
            }
            var billboard = item.billboard;
            if (defined(billboard)) {
                item.billboard = undefined;
                cluster.removeBillboard(entity);
            }
        }
    }

    function createCallback(centerAlpha, cssColor, cssOutlineColor, cssOutlineWidth, newPixelSize) {
        return function(id) {
            var canvas = document.createElement('canvas');

            var length = newPixelSize + (2 * cssOutlineWidth);
            canvas.height = canvas.width = length;

            var context2D = canvas.getContext('2d');
            context2D.clearRect(0, 0, length, length);

            if (cssOutlineWidth !== 0) {
                context2D.beginPath();
                context2D.arc(length / 2, length / 2, length / 2, 0, 2 * Math.PI, true);
                context2D.closePath();
                context2D.fillStyle = cssOutlineColor;
                context2D.fill();
                // Punch a hole in the center if needed.
                if (centerAlpha < 1.0) {
                    context2D.save();
                    context2D.globalCompositeOperation = 'destination-out';
                    context2D.beginPath();
                    context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true);
                    context2D.closePath();
                    context2D.fillStyle = 'black';
                    context2D.fill();
                    context2D.restore();
                }
            }

            context2D.beginPath();
            context2D.arc(length / 2, length / 2, newPixelSize / 2, 0, 2 * Math.PI, true);
            context2D.closePath();
            context2D.fillStyle = cssColor;
            context2D.fill();

            return canvas;
        };
    }


    return PointVisualizer;
});

/*global define*/
define('DataSources/PolygonGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/isArray',
        '../Core/Iso8601',
        '../Core/oneTimeWarning',
        '../Core/PolygonGeometry',
        '../Core/PolygonHierarchy',
        '../Core/PolygonOutlineGeometry',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/GroundPrimitive',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        isArray,
        Iso8601,
        oneTimeWarning,
        PolygonGeometry,
        PolygonHierarchy,
        PolygonOutlineGeometry,
        ShowGeometryInstanceAttribute,
        GroundPrimitive,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.polygonHierarchy = undefined;
        this.perPositionHeight = undefined;
        this.closeTop = undefined;
        this.closeBottom = undefined;
        this.height = undefined;
        this.extrudedHeight = undefined;
        this.granularity = undefined;
        this.stRotation = undefined;
    }

    /**
     * A {@link GeometryUpdater} for polygons.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias PolygonGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function PolygonGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(PolygonGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._isClosed = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._onTerrain = false;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'polygon', entity.polygon, undefined);
    }

    defineProperties(PolygonGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof PolygonGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof PolygonGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(PolygonGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            get : function() {
                return this._isClosed;
            }
        },
        /**
         * Gets a value indicating if the geometry should be drawn on terrain.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        onTerrain : {
            get : function() {
                return this._onTerrain;
            }
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof PolygonGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    PolygonGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    PolygonGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    PolygonGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new PolygonGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    PolygonGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new PolygonOutlineGeometry(this._options),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    PolygonGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    PolygonGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    PolygonGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'polygon')) {
            return;
        }

        var polygon = this._entity.polygon;

        if (!defined(polygon)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = polygon.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var perPositionHeightProperty = polygon.perPositionHeight;
        var perPositionHeightEnabled = defined(perPositionHeightProperty) && (perPositionHeightProperty.isConstant ? perPositionHeightProperty.getValue(Iso8601.MINIMUM_VALUE) : true);

        var outlineProperty = polygon.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var hierarchy = polygon.hierarchy;

        var show = polygon.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(hierarchy))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(polygon.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(polygon.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(polygon.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(polygon.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(polygon.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var height = polygon.height;
        var extrudedHeight = polygon.extrudedHeight;
        var granularity = polygon.granularity;
        var stRotation = polygon.stRotation;
        var outlineWidth = polygon.outlineWidth;
        var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) && isColorMaterial &&
                        !perPositionHeightEnabled && GroundPrimitive.isSupported(this._scene);

        if (outlineEnabled && onTerrain) {
            oneTimeWarning(oneTimeWarning.geometryOutlines);
            outlineEnabled = false;
        }

        var perPositionHeight = polygon.perPositionHeight;
        var closeTop = polygon.closeTop;
        var closeBottom = polygon.closeBottom;

        this._fillEnabled = fillEnabled;
        this._onTerrain = onTerrain;
        this._outlineEnabled = outlineEnabled;

        if (!hierarchy.isConstant || //
            !Property.isConstant(height) || //
            !Property.isConstant(extrudedHeight) || //
            !Property.isConstant(granularity) || //
            !Property.isConstant(stRotation) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(perPositionHeightProperty) || //
            !Property.isConstant(perPositionHeight) || //
            !Property.isConstant(closeTop) || //
            !Property.isConstant(closeBottom) || //
            (onTerrain && !Property.isConstant(material))) {

            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;

            var hierarchyValue = hierarchy.getValue(Iso8601.MINIMUM_VALUE);
            if (isArray(hierarchyValue)) {
                hierarchyValue = new PolygonHierarchy(hierarchyValue);
            }

            var heightValue = Property.getValueOrUndefined(height, Iso8601.MINIMUM_VALUE);
            var closeTopValue = Property.getValueOrDefault(closeTop, Iso8601.MINIMUM_VALUE, true);
            var closeBottomValue = Property.getValueOrDefault(closeBottom, Iso8601.MINIMUM_VALUE, true);
            var extrudedHeightValue = Property.getValueOrUndefined(extrudedHeight, Iso8601.MINIMUM_VALUE);

            options.polygonHierarchy = hierarchyValue;
            options.height = heightValue;
            options.extrudedHeight = extrudedHeightValue;
            options.granularity = Property.getValueOrUndefined(granularity, Iso8601.MINIMUM_VALUE);
            options.stRotation = Property.getValueOrUndefined(stRotation, Iso8601.MINIMUM_VALUE);
            options.perPositionHeight = Property.getValueOrUndefined(perPositionHeight, Iso8601.MINIMUM_VALUE);
            options.closeTop = closeTopValue;
            options.closeBottom = closeBottomValue;
            this._outlineWidth = Property.getValueOrDefault(outlineWidth, Iso8601.MINIMUM_VALUE, 1.0);
            this._isClosed = defined(extrudedHeightValue) && extrudedHeightValue !== heightValue && closeTopValue && closeBottomValue;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @param {PrimitiveCollection} groundPrimitives The ground primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    PolygonGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
        this._primitives = primitives;
        this._groundPrimitives = groundPrimitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }

    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var geometryUpdater = this._geometryUpdater;
        var onTerrain = geometryUpdater._onTerrain;

        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
            primitives.removeAndDestroy(this._outlinePrimitive);
            this._outlinePrimitive = undefined;
        }
        this._primitive = undefined;

        var entity = geometryUpdater._entity;
        var polygon = entity.polygon;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polygon.show, time, true)) {
            return;
        }

        var options = this._options;
        var hierarchy = Property.getValueOrUndefined(polygon.hierarchy, time);
        if (!defined(hierarchy)) {
            return;
        }

        if (isArray(hierarchy)) {
            options.polygonHierarchy = new PolygonHierarchy(hierarchy);
        } else {
            options.polygonHierarchy = hierarchy;
        }

        var closeTopValue = Property.getValueOrDefault(polygon.closeTop, time, true);
        var closeBottomValue = Property.getValueOrDefault(polygon.closeBottom, time, true);

        options.height = Property.getValueOrUndefined(polygon.height, time);
        options.extrudedHeight = Property.getValueOrUndefined(polygon.extrudedHeight, time);
        options.granularity = Property.getValueOrUndefined(polygon.granularity, time);
        options.stRotation = Property.getValueOrUndefined(polygon.stRotation, time);
        options.perPositionHeight = Property.getValueOrUndefined(polygon.perPositionHeight, time);
        options.closeTop = closeTopValue;
        options.closeBottom = closeBottomValue;

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        if (Property.getValueOrDefault(polygon.fill, time, true)) {
            var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
            var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
            this._material = material;

            if (onTerrain) {
                var currentColor = Color.WHITE;
                if (defined(fillMaterialProperty.color)) {
                    currentColor = fillMaterialProperty.color.getValue(time);
                }

                this._primitive = groundPrimitives.add(new GroundPrimitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new PolygonGeometry(options),
                        attributes: {
                            color: ColorGeometryInstanceAttribute.fromColor(currentColor)
                        }
                    }),
                    asynchronous : false,
                    shadows : shadows
                }));
            } else {
                var appearance = new MaterialAppearance({
                    material : material,
                    translucent : material.isTranslucent(),
                    closed : defined(options.extrudedHeight) && options.extrudedHeight !== options.height && closeTopValue && closeBottomValue
                });
                options.vertexFormat = appearance.vertexFormat;

                this._primitive = primitives.add(new Primitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new PolygonGeometry(options)
                    }),
                    appearance : appearance,
                    asynchronous : false,
                    shadows : shadows
                }));
            }
        }

        if (!onTerrain && Property.getValueOrDefault(polygon.outline, time, false)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(polygon.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(polygon.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new PolygonOutlineGeometry(options),
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (this._geometryUpdater._onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
        }
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return PolygonGeometryUpdater;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PolylineColorAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 prevPosition3DHigh;\n\
attribute vec3 prevPosition3DLow;\n\
attribute vec3 nextPosition3DHigh;\n\
attribute vec3 nextPosition3DLow;\n\
attribute vec2 expandAndWidth;\n\
attribute vec4 color;\n\
attribute float batchId;\n\
\n\
varying vec4 v_color;\n\
\n\
void main() \n\
{\n\
    float expandDir = expandAndWidth.x;\n\
    float width = abs(expandAndWidth.y) + 0.5;\n\
    bool usePrev = expandAndWidth.y < 0.0;\n\
    \n\
    vec4 p = czm_computePosition();\n\
    vec4 prev = czm_computePrevPosition();\n\
    vec4 next = czm_computeNextPosition();\n\
    \n\
    v_color = color;\n\
    \n\
    vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev);\n\
    gl_Position = czm_viewportOrthographic * positionWC;\n\
}\n\
";
});
/*global define*/
define('Scene/PolylineColorAppearance',[
        '../Core/defaultValue',
        '../Core/defineProperties',
        '../Core/VertexFormat',
        '../Shaders/Appearances/PerInstanceFlatColorAppearanceFS',
        '../Shaders/Appearances/PolylineColorAppearanceVS',
        '../Shaders/PolylineCommon',
        './Appearance'
    ], function(
        defaultValue,
        defineProperties,
        VertexFormat,
        PerInstanceFlatColorAppearanceFS,
        PolylineColorAppearanceVS,
        PolylineCommon,
        Appearance) {
    'use strict';

    var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineColorAppearanceVS;
    var defaultFragmentShaderSource = PerInstanceFlatColorAppearanceFS;

    /**
     * An appearance for {@link GeometryInstance} instances with color attributes and {@link PolylineGeometry}.
     * This allows several geometry instances, each with a different color, to
     * be drawn with the same {@link Primitive}.
     *
     * @alias PolylineColorAppearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link PolylineColorAppearance#renderState} has alpha blending enabled.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @example
     * // A solid white line segment
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *     geometry : new Cesium.PolylineGeometry({
     *       positions : Cesium.Cartesian3.fromDegreesArray([
     *         0.0, 0.0,
     *         5.0, 0.0
     *       ]),
     *       width : 10.0,
     *       vertexFormat : Cesium.PolylineColorAppearance.VERTEX_FORMAT
     *     }),
     *     attributes : {
     *       color : Cesium.ColorGeometryInstanceAttribute.fromColor(new Cesium.Color(1.0, 1.0, 1.0, 1.0))
     *     }
     *   }),
     *   appearance : new Cesium.PolylineColorAppearance({
     *     translucent : false
     *   })
     * });
     */
    function PolylineColorAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var translucent = defaultValue(options.translucent, true);
        var closed = false;
        var vertexFormat = PolylineColorAppearance.VERTEX_FORMAT;

        /**
         * This property is part of the {@link Appearance} interface, but is not
         * used by {@link PolylineColorAppearance} since a fully custom fragment shader is used.
         *
         * @type Material
         *
         * @default undefined
         */
        this.material = undefined;

        /**
         * When <code>true</code>, the geometry is expected to appear translucent so
         * {@link PolylineColorAppearance#renderState} has alpha blending enabled.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.translucent = translucent;

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource);
        this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
        this._closed = closed;

        // Non-derived members

        this._vertexFormat = vertexFormat;
    }

    defineProperties(PolylineColorAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof PolylineColorAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.
         *
         * @memberof PolylineColorAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         * <p>
         * The render state can be explicitly defined when constructing a {@link PolylineColorAppearance}
         * instance, or it is set implicitly via {@link PolylineColorAppearance#translucent}.
         * </p>
         *
         * @memberof PolylineColorAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed so
         * {@link PolylineColorAppearance#renderState} has backface culling enabled.
         * This is always <code>false</code> for <code>PolylineColorAppearance</code>.
         *
         * @memberof PolylineColorAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        },

        /**
         * The {@link VertexFormat} that this appearance instance is compatible with.
         * A geometry can have more vertex attributes and still be compatible - at a
         * potential performance cost - but it can't have less.
         *
         * @memberof PolylineColorAppearance.prototype
         *
         * @type VertexFormat
         * @readonly
         *
         * @default {@link PolylineColorAppearance.VERTEX_FORMAT}
         */
        vertexFormat : {
            get : function() {
                return this._vertexFormat;
            }
        }
    });

    /**
     * The {@link VertexFormat} that all {@link PolylineColorAppearance} instances
     * are compatible with. This requires only a <code>position</code> attribute.
     *
     * @type VertexFormat
     *
     * @constant
     */
    PolylineColorAppearance.VERTEX_FORMAT = VertexFormat.POSITION_ONLY;

    /**
     * Procedurally creates the full GLSL fragment shader source.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    PolylineColorAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link PolylineColorAppearance#translucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    PolylineColorAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    PolylineColorAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    return PolylineColorAppearance;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PolylineMaterialAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 prevPosition3DHigh;\n\
attribute vec3 prevPosition3DLow;\n\
attribute vec3 nextPosition3DHigh;\n\
attribute vec3 nextPosition3DLow;\n\
attribute vec2 expandAndWidth;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
\n\
varying float v_width;\n\
varying vec2 v_st;\n\
\n\
void main() \n\
{\n\
    float expandDir = expandAndWidth.x;\n\
    float width = abs(expandAndWidth.y) + 0.5;\n\
    bool usePrev = expandAndWidth.y < 0.0;\n\
    \n\
    vec4 p = czm_computePosition();\n\
    vec4 prev = czm_computePrevPosition();\n\
    vec4 next = czm_computeNextPosition();\n\
    \n\
    v_width = width;\n\
    v_st = st;\n\
    \n\
    vec4 positionWC = getPolylineWindowCoordinates(p, prev, next, expandDir, width, usePrev);\n\
    gl_Position = czm_viewportOrthographic * positionWC;\n\
}\n\
";
});
/*global define*/
define('Scene/PolylineMaterialAppearance',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/VertexFormat',
        '../Shaders/Appearances/PolylineMaterialAppearanceVS',
        '../Shaders/PolylineCommon',
        '../Shaders/PolylineFS',
        './Appearance',
        './Material'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        VertexFormat,
        PolylineMaterialAppearanceVS,
        PolylineCommon,
        PolylineFS,
        Appearance,
        Material) {
    'use strict';

    var defaultVertexShaderSource = PolylineCommon + '\n' + PolylineMaterialAppearanceVS;
    var defaultFragmentShaderSource = PolylineFS;

    /**
     * An appearance for {@link PolylineGeometry} that supports shading with materials.
     *
     * @alias PolylineMaterialAppearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link PolylineMaterialAppearance#renderState} has alpha blending enabled.
     * @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
     *
     * @example
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *     geometry : new Cesium.PolylineGeometry({
     *       positions : Cesium.Cartesian3.fromDegreesArray([
     *         0.0, 0.0,
     *         5.0, 0.0
     *       ]),
     *       width : 10.0,
     *       vertexFormat : Cesium.PolylineMaterialAppearance.VERTEX_FORMAT
     *     })
     *   }),
     *   appearance : new Cesium.PolylineMaterialAppearance({
     *     material : Cesium.Material.fromType('Color')
     *   })
     * });
     */
    function PolylineMaterialAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var translucent = defaultValue(options.translucent, true);
        var closed = false;
        var vertexFormat = PolylineMaterialAppearance.VERTEX_FORMAT;

        /**
         * The material used to determine the fragment color.  Unlike other {@link PolylineMaterialAppearance}
         * properties, this is not read-only, so an appearance's material can change on the fly.
         *
         * @type Material
         *
         * @default {@link Material.ColorType}
         *
         * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
         */
        this.material = defined(options.material) ? options.material : Material.fromType(Material.ColorType);

        /**
         * When <code>true</code>, the geometry is expected to appear translucent so
         * {@link PolylineMaterialAppearance#renderState} has alpha blending enabled.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.translucent = translucent;

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, defaultVertexShaderSource);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, defaultFragmentShaderSource);
        this._renderState = Appearance.getDefaultRenderState(translucent, closed, options.renderState);
        this._closed = closed;

        // Non-derived members

        this._vertexFormat = vertexFormat;
    }

    defineProperties(PolylineMaterialAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof PolylineMaterialAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.
         *
         * @memberof PolylineMaterialAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         * <p>
         * The render state can be explicitly defined when constructing a {@link PolylineMaterialAppearance}
         * instance, or it is set implicitly via {@link PolylineMaterialAppearance#translucent}
         * and {@link PolylineMaterialAppearance#closed}.
         * </p>
         *
         * @memberof PolylineMaterialAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed so
         * {@link PolylineMaterialAppearance#renderState} has backface culling enabled.
         * This is always <code>false</code> for <code>PolylineMaterialAppearance</code>.
         *
         * @memberof PolylineMaterialAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        },

        /**
         * The {@link VertexFormat} that this appearance instance is compatible with.
         * A geometry can have more vertex attributes and still be compatible - at a
         * potential performance cost - but it can't have less.
         *
         * @memberof PolylineMaterialAppearance.prototype
         *
         * @type VertexFormat
         * @readonly
         *
         * @default {@link PolylineMaterialAppearance.VERTEX_FORMAT}
         */
        vertexFormat : {
            get : function() {
                return this._vertexFormat;
            }
        }
    });

    /**
     * The {@link VertexFormat} that all {@link PolylineMaterialAppearance} instances
     * are compatible with. This requires <code>position</code> and <code>st</code> attributes.
     *
     * @type VertexFormat
     *
     * @constant
     */
    PolylineMaterialAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_ST;

    /**
     * Procedurally creates the full GLSL fragment shader source.  For {@link PolylineMaterialAppearance},
     * this is derived from {@link PolylineMaterialAppearance#fragmentShaderSource} and {@link PolylineMaterialAppearance#material}.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    PolylineMaterialAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link PolylineMaterialAppearance#translucent} and {@link Material#isTranslucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    PolylineMaterialAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    PolylineMaterialAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    return PolylineMaterialAppearance;
});

/*global define*/
define('DataSources/PolylineGeometryUpdater',[
        '../Core/BoundingSphere',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/PolylineGeometry',
        '../Core/PolylinePipeline',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/PolylineCollection',
        '../Scene/PolylineColorAppearance',
        '../Scene/PolylineMaterialAppearance',
        '../Scene/ShadowMode',
        './BoundingSphereState',
        './ColorMaterialProperty',
        './ConstantProperty',
        './MaterialProperty',
        './Property'
    ], function(
        BoundingSphere,
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        PolylineGeometry,
        PolylinePipeline,
        ShowGeometryInstanceAttribute,
        PolylineCollection,
        PolylineColorAppearance,
        PolylineMaterialAppearance,
        ShadowMode,
        BoundingSphereState,
        ColorMaterialProperty,
        ConstantProperty,
        MaterialProperty,
        Property) {
    'use strict';

    //We use this object to create one polyline collection per-scene.
    var polylineCollections = {};

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.positions = undefined;
        this.width = undefined;
        this.followSurface = undefined;
        this.granularity = undefined;
    }

    /**
     * A {@link GeometryUpdater} for polylines.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias PolylineGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function PolylineGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(PolylineGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._dynamic = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'polyline', entity.polyline, undefined);
    }

    defineProperties(PolylineGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof PolylineGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PolylineColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof PolylineGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : PolylineMaterialAppearance
        }
    });

    defineProperties(PolylineGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled || (!defined(this._entity.availability) && Property.isConstant(this._showProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            value : false
        },
        /**
         * Gets a value indicating if outline visibility varies with simulation time.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            value : true
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            value : undefined
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof PolylineGeometryUpdater.prototype
         * 
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            value : false
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof PolylineGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    PolylineGeometryUpdater.prototype.isOutlineVisible = function(time) {
        return false;
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    PolylineGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    PolylineGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var color;
        var attributes;
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);

        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new PolylineGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    PolylineGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                throw new DeveloperError('This instance does not represent an outlined geometry.');
            };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    PolylineGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    PolylineGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    PolylineGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'polyline')) {
            return;
        }

        var polyline = this._entity.polyline;

        if (!defined(polyline)) {
            if (this._fillEnabled) {
                this._fillEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var positionsProperty = polyline.positions;

        var show = polyline.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(positionsProperty))) {
            if (this._fillEnabled) {
                this._fillEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(polyline.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._showProperty = defaultValue(show, defaultShow);
        this._shadowsProperty = defaultValue(polyline.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(polyline.distanceDisplayCondition, defaultDistanceDisplayCondition);
        this._fillEnabled = true;

        var width = polyline.width;
        var followSurface = polyline.followSurface;
        var granularity = polyline.granularity;

        if (!positionsProperty.isConstant || !Property.isConstant(width) ||
            !Property.isConstant(followSurface) || !Property.isConstant(granularity)) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            var positions = positionsProperty.getValue(Iso8601.MINIMUM_VALUE, options.positions);

            //Because of the way we currently handle reference properties,
            //we can't automatically assume the positions are  always valid.
            if (!defined(positions) || positions.length < 2) {
                if (this._fillEnabled) {
                    this._fillEnabled = false;
                    this._geometryChanged.raiseEvent(this);
                }
                return;
            }

            options.vertexFormat = isColorMaterial ? PolylineColorAppearance.VERTEX_FORMAT : PolylineMaterialAppearance.VERTEX_FORMAT;
            options.positions = positions;
            options.width = defined(width) ? width.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.followSurface = defined(followSurface) ? followSurface.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    PolylineGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, this);
    };

    /**
     * @private
     */
    var generateCartesianArcOptions = {
        positions : undefined,
        granularity : undefined,
        height : undefined,
        ellipsoid : undefined
    };

    function DynamicGeometryUpdater(primitives, geometryUpdater) {
        var sceneId = geometryUpdater._scene.id;

        var polylineCollection = polylineCollections[sceneId];
        if (!defined(polylineCollection) || polylineCollection.isDestroyed()) {
            polylineCollection = new PolylineCollection();
            polylineCollections[sceneId] = polylineCollection;
            primitives.add(polylineCollection);
        } else if (!primitives.contains(polylineCollection)) {
            primitives.add(polylineCollection);
        }

        var line = polylineCollection.add();
        line.id = geometryUpdater._entity;

        this._line = line;
        this._primitives = primitives;
        this._geometryUpdater = geometryUpdater;
        this._positions = [];

        generateCartesianArcOptions.ellipsoid = geometryUpdater._scene.globe.ellipsoid;
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
        var geometryUpdater = this._geometryUpdater;
        var entity = geometryUpdater._entity;
        var polyline = entity.polyline;
        var line = this._line;

        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polyline._show, time, true)) {
            line.show = false;
            return;
        }

        var positionsProperty = polyline.positions;
        var positions = Property.getValueOrUndefined(positionsProperty, time, this._positions);
        if (!defined(positions) || positions.length < 2) {
            line.show = false;
            return;
        }

        var followSurface = Property.getValueOrDefault(polyline._followSurface, time, true);
        if (followSurface) {
            generateCartesianArcOptions.positions = positions;
            generateCartesianArcOptions.granularity = Property.getValueOrUndefined(polyline._granularity, time);
            generateCartesianArcOptions.height = PolylinePipeline.extractHeights(positions, this._geometryUpdater._scene.globe.ellipsoid);
            positions = PolylinePipeline.generateCartesianArc(generateCartesianArcOptions);
        }

        line.show = true;
        line.positions = positions.slice();
        line.material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, line.material);
        line.width = Property.getValueOrDefault(polyline._width, time, 1);
        line.distanceDisplayCondition = Property.getValueOrUndefined(polyline._distanceDisplayCondition, time, line.distanceDisplayCondition);
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        var line = this._line;
        if (line.show && line.positions.length > 0) {
            BoundingSphere.fromPoints(line.positions, result);
            return BoundingSphereState.DONE;
        }
        return BoundingSphereState.FAILED;
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var geometryUpdater = this._geometryUpdater;
        var sceneId = geometryUpdater._scene.id;
        var polylineCollection = polylineCollections[sceneId];
        polylineCollection.remove(this._line);
        if (polylineCollection.length === 0) {
            this._primitives.removeAndDestroy(polylineCollection);
            delete polylineCollections[sceneId];
        }
        destroyObject(this);
    };

    return PolylineGeometryUpdater;
});

/*global define*/
define('DataSources/PolylineVolumeGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/PolylineVolumeGeometry',
        '../Core/PolylineVolumeOutlineGeometry',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        PolylineVolumeGeometry,
        PolylineVolumeOutlineGeometry,
        ShowGeometryInstanceAttribute,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.polylinePositions = undefined;
        this.shapePositions = undefined;
        this.cornerType = undefined;
        this.granularity = undefined;
    }

    /**
     * A {@link GeometryUpdater} for polyline volumes.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias PolylineVolumeGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function PolylineVolumeGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(PolylineVolumeGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'polylineVolume', entity.polylineVolume, undefined);
    }

    defineProperties(PolylineVolumeGeometryUpdater, {
        /**
         * Gets the type of appearance to use for simple color-based geometry.
         * @memberof PolylineVolumeGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of appearance to use for material-based geometry.
         * @memberof PolylineVolumeGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(PolylineVolumeGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            value : true
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof PolylineVolumeGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    PolylineVolumeGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    PolylineVolumeGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    PolylineVolumeGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new PolylineVolumeGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    PolylineVolumeGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new PolylineVolumeOutlineGeometry(this._options),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    PolylineVolumeGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    PolylineVolumeGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    PolylineVolumeGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'polylineVolume')) {
            return;
        }

        var polylineVolume = this._entity.polylineVolume;

        if (!defined(polylineVolume)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = polylineVolume.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = polylineVolume.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var positions = polylineVolume.positions;
        var shape = polylineVolume.shape;

        var show = polylineVolume.show;
        if (!defined(positions) || !defined(shape) || (defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(polylineVolume.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(polylineVolume.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(polylineVolume.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(polylineVolume.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(polylineVolume.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var granularity = polylineVolume.granularity;
        var outlineWidth = polylineVolume.outlineWidth;
        var cornerType = polylineVolume.cornerType;

        this._fillEnabled = fillEnabled;
        this._outlineEnabled = outlineEnabled;

        if (!positions.isConstant || //
            !shape.isConstant || //
            !Property.isConstant(granularity) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(cornerType)) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.polylinePositions = positions.getValue(Iso8601.MINIMUM_VALUE, options.polylinePositions);
            options.shapePositions = shape.getValue(Iso8601.MINIMUM_VALUE, options.shape);
            options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.cornerType = defined(cornerType) ? cornerType.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    PolylineVolumeGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, geometryUpdater) {
        this._primitives = primitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        this._primitive = undefined;
        this._outlinePrimitive = undefined;

        var geometryUpdater = this._geometryUpdater;
        var entity = geometryUpdater._entity;
        var polylineVolume = entity.polylineVolume;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(polylineVolume.show, time, true)) {
            return;
        }

        var options = this._options;
        var positions = Property.getValueOrUndefined(polylineVolume.positions, time, options.polylinePositions);
        var shape = Property.getValueOrUndefined(polylineVolume.shape, time);
        if (!defined(positions) || !defined(shape)) {
            return;
        }

        options.polylinePositions = positions;
        options.shapePositions = shape;
        options.granularity = Property.getValueOrUndefined(polylineVolume.granularity, time);
        options.cornerType = Property.getValueOrUndefined(polylineVolume.cornerType, time);

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        if (!defined(polylineVolume.fill) || polylineVolume.fill.getValue(time)) {
            var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
            this._material = material;

            var appearance = new MaterialAppearance({
                material : material,
                translucent : material.isTranslucent(),
                closed : true
            });
            options.vertexFormat = appearance.vertexFormat;

            this._primitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new PolylineVolumeGeometry(options)
                }),
                appearance : appearance,
                asynchronous : false,
                shadows : shadows
            }));
        }

        if (defined(polylineVolume.outline) && polylineVolume.outline.getValue(time)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(polylineVolume.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(polylineVolume.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new PolylineVolumeOutlineGeometry(options),
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return PolylineVolumeGeometryUpdater;
});

/*global define*/
define('DataSources/RectangleGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/oneTimeWarning',
        '../Core/RectangleGeometry',
        '../Core/RectangleOutlineGeometry',
        '../Core/ShowGeometryInstanceAttribute',
        '../Scene/GroundPrimitive',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        oneTimeWarning,
        RectangleGeometry,
        RectangleOutlineGeometry,
        ShowGeometryInstanceAttribute,
        GroundPrimitive,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.rectangle = undefined;
        this.closeBottom = undefined;
        this.closeTop = undefined;
        this.height = undefined;
        this.extrudedHeight = undefined;
        this.granularity = undefined;
        this.stRotation = undefined;
        this.rotation = undefined;
    }

    /**
     * A {@link GeometryUpdater} for rectangles.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias RectangleGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function RectangleGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(RectangleGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._isClosed = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._onTerrain = false;
        this._options = new GeometryOptions(entity);

        this._onEntityPropertyChanged(entity, 'rectangle', entity.rectangle, undefined);
    }

    defineProperties(RectangleGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof RectangleGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof RectangleGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(RectangleGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof RectangleGeometryUpdater.prototype
         * 
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            get : function() {
                return this._isClosed;
            }
        },
        /**
         * Gets a value indicating if the geometry should be drawn on terrain.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        onTerrain : {
            get : function() {
                return this._onTerrain;
            }
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof RectangleGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    RectangleGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    RectangleGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    RectangleGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new RectangleGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    RectangleGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new RectangleOutlineGeometry(this._options),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    RectangleGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    RectangleGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    RectangleGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'rectangle')) {
            return;
        }

        var rectangle = this._entity.rectangle;

        if (!defined(rectangle)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = rectangle.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = rectangle.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var coordinates = rectangle.coordinates;

        var show = rectangle.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(coordinates))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(rectangle.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(rectangle.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(rectangle.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(rectangle.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(rectangle.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var height = rectangle.height;
        var extrudedHeight = rectangle.extrudedHeight;
        var granularity = rectangle.granularity;
        var stRotation = rectangle.stRotation;
        var rotation = rectangle.rotation;
        var outlineWidth = rectangle.outlineWidth;
        var closeBottom = rectangle.closeBottom;
        var closeTop = rectangle.closeTop;
        var onTerrain = fillEnabled && !defined(height) && !defined(extrudedHeight) &&
                        isColorMaterial && GroundPrimitive.isSupported(this._scene);

        if (outlineEnabled && onTerrain) {
            oneTimeWarning(oneTimeWarning.geometryOutlines);
            outlineEnabled = false;
        }

        this._fillEnabled = fillEnabled;
        this._onTerrain = onTerrain;
        this._outlineEnabled = outlineEnabled;

        if (!coordinates.isConstant || //
            !Property.isConstant(height) || //
            !Property.isConstant(extrudedHeight) || //
            !Property.isConstant(granularity) || //
            !Property.isConstant(stRotation) || //
            !Property.isConstant(rotation) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(closeBottom) || //
            !Property.isConstant(closeTop) || //
            (onTerrain && !Property.isConstant(material))) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.rectangle = coordinates.getValue(Iso8601.MINIMUM_VALUE, options.rectangle);
            options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.stRotation = defined(stRotation) ? stRotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.rotation = defined(rotation) ? rotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.closeBottom = defined(closeBottom) ? closeBottom.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            options.closeTop = defined(closeTop) ? closeTop.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._isClosed = defined(extrudedHeight) && defined(options.closeTop) && defined(options.closeBottom) && options.closeTop && options.closeBottom;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @param {PrimitiveCollection} groundPrimitives The primitive collection to use for GroundPrimitives.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    RectangleGeometryUpdater.prototype.createDynamicUpdater = function(primitives, groundPrimitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, groundPrimitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, groundPrimitives, geometryUpdater) {
        this._primitives = primitives;
        this._groundPrimitives = groundPrimitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var geometryUpdater = this._geometryUpdater;
        var onTerrain = geometryUpdater._onTerrain;

        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
            primitives.removeAndDestroy(this._outlinePrimitive);
            this._outlinePrimitive = undefined;
        }
        this._primitive = undefined;

        var entity = geometryUpdater._entity;
        var rectangle = entity.rectangle;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(rectangle.show, time, true)) {
            return;
        }

        var options = this._options;
        var coordinates = Property.getValueOrUndefined(rectangle.coordinates, time, options.rectangle);
        if (!defined(coordinates)) {
            return;
        }

        options.rectangle = coordinates;
        options.height = Property.getValueOrUndefined(rectangle.height, time);
        options.extrudedHeight = Property.getValueOrUndefined(rectangle.extrudedHeight, time);
        options.granularity = Property.getValueOrUndefined(rectangle.granularity, time);
        options.stRotation = Property.getValueOrUndefined(rectangle.stRotation, time);
        options.rotation = Property.getValueOrUndefined(rectangle.rotation, time);
        options.closeBottom = Property.getValueOrUndefined(rectangle.closeBottom, time);
        options.closeTop = Property.getValueOrUndefined(rectangle.closeTop, time);

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        if (Property.getValueOrDefault(rectangle.fill, time, true)) {
            var fillMaterialProperty = geometryUpdater.fillMaterialProperty;
            var material = MaterialProperty.getValue(time, fillMaterialProperty, this._material);
            this._material = material;

            if (onTerrain) {
                var currentColor = Color.WHITE;
                if (defined(fillMaterialProperty.color)) {
                    currentColor = fillMaterialProperty.color.getValue(time);
                }

                this._primitive = groundPrimitives.add(new GroundPrimitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new RectangleGeometry(options),
                        attributes: {
                            color: ColorGeometryInstanceAttribute.fromColor(currentColor)
                        }
                    }),
                    asynchronous : false,
                    shadows : shadows
                }));
            } else {
                var appearance = new MaterialAppearance({
                    material : material,
                    translucent : material.isTranslucent(),
                    closed : defined(options.extrudedHeight)
                });

                options.vertexFormat = appearance.vertexFormat;

                this._primitive = primitives.add(new Primitive({
                    geometryInstances : new GeometryInstance({
                        id : entity,
                        geometry : new RectangleGeometry(options)
                    }),
                    appearance : appearance,
                    asynchronous : false,
                    shadows : shadows
                }));
            }
        }

        if (!onTerrain && Property.getValueOrDefault(rectangle.outline, time, false)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(rectangle.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(rectangle.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new RectangleOutlineGeometry(options),
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        var groundPrimitives = this._groundPrimitives;
        if (this._geometryUpdater._onTerrain) {
            groundPrimitives.removeAndDestroy(this._primitive);
        } else {
            primitives.removeAndDestroy(this._primitive);
        }
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return RectangleGeometryUpdater;
});

/*global define*/
define('DataSources/WallGeometryUpdater',[
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/DistanceDisplayCondition',
        '../Core/DistanceDisplayConditionGeometryInstanceAttribute',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/Iso8601',
        '../Core/ShowGeometryInstanceAttribute',
        '../Core/WallGeometry',
        '../Core/WallOutlineGeometry',
        '../Scene/MaterialAppearance',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        '../Scene/ShadowMode',
        './ColorMaterialProperty',
        './ConstantProperty',
        './dynamicGeometryGetBoundingSphere',
        './MaterialProperty',
        './Property'
    ], function(
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        DistanceDisplayCondition,
        DistanceDisplayConditionGeometryInstanceAttribute,
        Event,
        GeometryInstance,
        Iso8601,
        ShowGeometryInstanceAttribute,
        WallGeometry,
        WallOutlineGeometry,
        MaterialAppearance,
        PerInstanceColorAppearance,
        Primitive,
        ShadowMode,
        ColorMaterialProperty,
        ConstantProperty,
        dynamicGeometryGetBoundingSphere,
        MaterialProperty,
        Property) {
    'use strict';

    var defaultMaterial = new ColorMaterialProperty(Color.WHITE);
    var defaultShow = new ConstantProperty(true);
    var defaultFill = new ConstantProperty(true);
    var defaultOutline = new ConstantProperty(false);
    var defaultOutlineColor = new ConstantProperty(Color.BLACK);
    var defaultShadows = new ConstantProperty(ShadowMode.DISABLED);
    var defaultDistanceDisplayCondition = new ConstantProperty(new DistanceDisplayCondition());
    var scratchColor = new Color();

    function GeometryOptions(entity) {
        this.id = entity;
        this.vertexFormat = undefined;
        this.positions = undefined;
        this.minimumHeights = undefined;
        this.maximumHeights = undefined;
        this.granularity = undefined;
    }

    /**
     * A {@link GeometryUpdater} for walls.
     * Clients do not normally create this class directly, but instead rely on {@link DataSourceDisplay}.
     * @alias WallGeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     */
    function WallGeometryUpdater(entity, scene) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        this._entity = entity;
        this._scene = scene;
        this._entitySubscription = entity.definitionChanged.addEventListener(WallGeometryUpdater.prototype._onEntityPropertyChanged, this);
        this._fillEnabled = false;
        this._dynamic = false;
        this._outlineEnabled = false;
        this._geometryChanged = new Event();
        this._showProperty = undefined;
        this._materialProperty = undefined;
        this._hasConstantOutline = true;
        this._showOutlineProperty = undefined;
        this._outlineColorProperty = undefined;
        this._outlineWidth = 1.0;
        this._shadowsProperty = undefined;
        this._distanceDisplayConditionProperty = undefined;
        this._options = new GeometryOptions(entity);
        this._onEntityPropertyChanged(entity, 'wall', entity.wall, undefined);
    }

    defineProperties(WallGeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof WallGeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            value : PerInstanceColorAppearance
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof WallGeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            value : MaterialAppearance
        }
    });

    defineProperties(WallGeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : function() {
                return this._entity;
            }
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : function() {
                return this._fillEnabled;
            }
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : function() {
                return !this._fillEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._fillProperty));
            }
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : function() {
                return this._materialProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : function() {
                return this._outlineEnabled;
            }
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : function() {
                return !this._outlineEnabled ||
                       (!defined(this._entity.availability) &&
                        Property.isConstant(this._showProperty) &&
                        Property.isConstant(this._showOutlineProperty));
            }
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : function() {
                return this._outlineColorProperty;
            }
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : function() {
                return this._outlineWidth;
            }
        },
        /**
         * Gets the property specifying whether the geometry
         * casts or receives shadows from each light source.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        shadowsProperty : {
            get : function() {
                return this._shadowsProperty;
            }
        },
        /**
         * Gets or sets the {@link DistanceDisplayCondition} Property specifying at what distance from the camera that this geometry will be displayed.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        distanceDisplayConditionProperty : {
            get : function() {
                return this._distanceDisplayConditionProperty;
            }
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : function() {
                return this._dynamic;
            }
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            get : function() {
                return false;
            }
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof WallGeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : function() {
                return this._geometryChanged;
            }
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    WallGeometryUpdater.prototype.isOutlineVisible = function(time) {
        var entity = this._entity;
        return this._outlineEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time);
    };

    /**
     * Checks if the geometry is filled at the provided time.
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    WallGeometryUpdater.prototype.isFilled = function(time) {
        var entity = this._entity;
        return this._fillEnabled && entity.isAvailable(time) && this._showProperty.getValue(time) && this._fillProperty.getValue(time);
    };

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    WallGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._fillEnabled) {
            throw new DeveloperError('This instance does not represent a filled geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);

        var attributes;

        var color;
        var show = new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._fillProperty.getValue(time));
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);
        var distanceDisplayConditionAttribute = DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition);
        if (this._materialProperty instanceof ColorMaterialProperty) {
            var currentColor = Color.WHITE;
            if (defined(this._materialProperty.color) && (this._materialProperty.color.isConstant || isAvailable)) {
                currentColor = this._materialProperty.color.getValue(time);
            }
            color = ColorGeometryInstanceAttribute.fromColor(currentColor);
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute,
                color : color
            };
        } else {
            attributes = {
                show : show,
                distanceDisplayCondition : distanceDisplayConditionAttribute
            };
        }

        return new GeometryInstance({
            id : entity,
            geometry : new WallGeometry(this._options),
            attributes : attributes
        });
    };

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    WallGeometryUpdater.prototype.createOutlineGeometryInstance = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }

        if (!this._outlineEnabled) {
            throw new DeveloperError('This instance does not represent an outlined geometry.');
        }
        
        var entity = this._entity;
        var isAvailable = entity.isAvailable(time);
        var outlineColor = Property.getValueOrDefault(this._outlineColorProperty, time, Color.BLACK);
        var distanceDisplayCondition = this._distanceDisplayConditionProperty.getValue(time);

        return new GeometryInstance({
            id : entity,
            geometry : new WallOutlineGeometry(this._options),
            attributes : {
                show : new ShowGeometryInstanceAttribute(isAvailable && entity.isShowing && this._showProperty.getValue(time) && this._showOutlineProperty.getValue(time)),
                color : ColorGeometryInstanceAttribute.fromColor(outlineColor),
                distanceDisplayCondition : DistanceDisplayConditionGeometryInstanceAttribute.fromDistanceDisplayCondition(distanceDisplayCondition)
            }
        });
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    WallGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    WallGeometryUpdater.prototype.destroy = function() {
        this._entitySubscription();
        destroyObject(this);
    };

    WallGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
        if (!(propertyName === 'availability' || propertyName === 'wall')) {
            return;
        }

        var wall = this._entity.wall;

        if (!defined(wall)) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var fillProperty = wall.fill;
        var fillEnabled = defined(fillProperty) && fillProperty.isConstant ? fillProperty.getValue(Iso8601.MINIMUM_VALUE) : true;

        var outlineProperty = wall.outline;
        var outlineEnabled = defined(outlineProperty);
        if (outlineEnabled && outlineProperty.isConstant) {
            outlineEnabled = outlineProperty.getValue(Iso8601.MINIMUM_VALUE);
        }

        if (!fillEnabled && !outlineEnabled) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var positions = wall.positions;

        var show = wall.show;
        if ((defined(show) && show.isConstant && !show.getValue(Iso8601.MINIMUM_VALUE)) || //
            (!defined(positions))) {
            if (this._fillEnabled || this._outlineEnabled) {
                this._fillEnabled = false;
                this._outlineEnabled = false;
                this._geometryChanged.raiseEvent(this);
            }
            return;
        }

        var material = defaultValue(wall.material, defaultMaterial);
        var isColorMaterial = material instanceof ColorMaterialProperty;
        this._materialProperty = material;
        this._fillProperty = defaultValue(fillProperty, defaultFill);
        this._showProperty = defaultValue(show, defaultShow);
        this._showOutlineProperty = defaultValue(wall.outline, defaultOutline);
        this._outlineColorProperty = outlineEnabled ? defaultValue(wall.outlineColor, defaultOutlineColor) : undefined;
        this._shadowsProperty = defaultValue(wall.shadows, defaultShadows);
        this._distanceDisplayConditionProperty = defaultValue(wall.distanceDisplayCondition, defaultDistanceDisplayCondition);

        var minimumHeights = wall.minimumHeights;
        var maximumHeights = wall.maximumHeights;
        var outlineWidth = wall.outlineWidth;
        var granularity = wall.granularity;

        this._fillEnabled = fillEnabled;
        this._outlineEnabled = outlineEnabled;

        if (!positions.isConstant || //
            !Property.isConstant(minimumHeights) || //
            !Property.isConstant(maximumHeights) || //
            !Property.isConstant(outlineWidth) || //
            !Property.isConstant(granularity)) {
            if (!this._dynamic) {
                this._dynamic = true;
                this._geometryChanged.raiseEvent(this);
            }
        } else {
            var options = this._options;
            options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
            options.positions = positions.getValue(Iso8601.MINIMUM_VALUE, options.positions);
            options.minimumHeights = defined(minimumHeights) ? minimumHeights.getValue(Iso8601.MINIMUM_VALUE, options.minimumHeights) : undefined;
            options.maximumHeights = defined(maximumHeights) ? maximumHeights.getValue(Iso8601.MINIMUM_VALUE, options.maximumHeights) : undefined;
            options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
            this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
            this._dynamic = false;
            this._geometryChanged.raiseEvent(this);
        }
    };

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    WallGeometryUpdater.prototype.createDynamicUpdater = function(primitives) {
                if (!this._dynamic) {
            throw new DeveloperError('This instance does not represent dynamic geometry.');
        }

        if (!defined(primitives)) {
            throw new DeveloperError('primitives is required.');
        }
        
        return new DynamicGeometryUpdater(primitives, this);
    };

    /**
     * @private
     */
    function DynamicGeometryUpdater(primitives, geometryUpdater) {
        this._primitives = primitives;
        this._primitive = undefined;
        this._outlinePrimitive = undefined;
        this._geometryUpdater = geometryUpdater;
        this._options = new GeometryOptions(geometryUpdater._entity);
    }
    DynamicGeometryUpdater.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        this._primitive = undefined;
        this._outlinePrimitive = undefined;

        var geometryUpdater = this._geometryUpdater;
        var entity = geometryUpdater._entity;
        var wall = entity.wall;
        if (!entity.isShowing || !entity.isAvailable(time) || !Property.getValueOrDefault(wall.show, time, true)) {
            return;
        }

        var options = this._options;
        var positions = Property.getValueOrUndefined(wall.positions, time, options.positions);
        if (!defined(positions)) {
            return;
        }

        options.positions = positions;
        options.minimumHeights = Property.getValueOrUndefined(wall.minimumHeights, time, options.minimumHeights);
        options.maximumHeights = Property.getValueOrUndefined(wall.maximumHeights, time, options.maximumHeights);
        options.granularity = Property.getValueOrUndefined(wall.granularity, time);

        var shadows = this._geometryUpdater.shadowsProperty.getValue(time);

        if (Property.getValueOrDefault(wall.fill, time, true)) {
            var material = MaterialProperty.getValue(time, geometryUpdater.fillMaterialProperty, this._material);
            this._material = material;

            var appearance = new MaterialAppearance({
                material : material,
                translucent : material.isTranslucent(),
                closed : defined(options.extrudedHeight)
            });
            options.vertexFormat = appearance.vertexFormat;

            this._primitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new WallGeometry(options)
                }),
                appearance : appearance,
                asynchronous : false,
                shadows : shadows
            }));
        }

        if (Property.getValueOrDefault(wall.outline, time, false)) {
            options.vertexFormat = PerInstanceColorAppearance.VERTEX_FORMAT;

            var outlineColor = Property.getValueOrClonedDefault(wall.outlineColor, time, Color.BLACK, scratchColor);
            var outlineWidth = Property.getValueOrDefault(wall.outlineWidth, time, 1.0);
            var translucent = outlineColor.alpha !== 1.0;

            this._outlinePrimitive = primitives.add(new Primitive({
                geometryInstances : new GeometryInstance({
                    id : entity,
                    geometry : new WallOutlineGeometry(options),
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(outlineColor)
                    }
                }),
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : translucent,
                    renderState : {
                        lineWidth : geometryUpdater._scene.clampLineWidth(outlineWidth)
                    }
                }),
                asynchronous : false,
                shadows : shadows
            }));
        }
    };

    DynamicGeometryUpdater.prototype.getBoundingSphere = function(entity, result) {
        return dynamicGeometryGetBoundingSphere(entity, this._primitive, this._outlinePrimitive, result);
    };

    DynamicGeometryUpdater.prototype.isDestroyed = function() {
        return false;
    };

    DynamicGeometryUpdater.prototype.destroy = function() {
        var primitives = this._primitives;
        primitives.removeAndDestroy(this._primitive);
        primitives.removeAndDestroy(this._outlinePrimitive);
        destroyObject(this);
    };

    return WallGeometryUpdater;
});

/*global define*/
define('DataSources/DataSourceDisplay',[
        '../Core/BoundingSphere',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EventHelper',
        '../Scene/GroundPrimitive',
        './BillboardVisualizer',
        './BoundingSphereState',
        './BoxGeometryUpdater',
        './CorridorGeometryUpdater',
        './CustomDataSource',
        './CylinderGeometryUpdater',
        './EllipseGeometryUpdater',
        './EllipsoidGeometryUpdater',
        './GeometryVisualizer',
        './LabelVisualizer',
        './ModelVisualizer',
        './PathVisualizer',
        './PointVisualizer',
        './PolygonGeometryUpdater',
        './PolylineGeometryUpdater',
        './PolylineVolumeGeometryUpdater',
        './RectangleGeometryUpdater',
        './WallGeometryUpdater'
    ], function(
        BoundingSphere,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EventHelper,
        GroundPrimitive,
        BillboardVisualizer,
        BoundingSphereState,
        BoxGeometryUpdater,
        CorridorGeometryUpdater,
        CustomDataSource,
        CylinderGeometryUpdater,
        EllipseGeometryUpdater,
        EllipsoidGeometryUpdater,
        GeometryVisualizer,
        LabelVisualizer,
        ModelVisualizer,
        PathVisualizer,
        PointVisualizer,
        PolygonGeometryUpdater,
        PolylineGeometryUpdater,
        PolylineVolumeGeometryUpdater,
        RectangleGeometryUpdater,
        WallGeometryUpdater) {
    'use strict';

    /**
     * Visualizes a collection of {@link DataSource} instances.
     * @alias DataSourceDisplay
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Scene} options.scene The scene in which to display the data.
     * @param {DataSourceCollection} options.dataSourceCollection The data sources to display.
     * @param {DataSourceDisplay~VisualizersCallback} [options.visualizersCallback=DataSourceDisplay.defaultVisualizersCallback]
     *        A function which creates an array of visualizers used for visualization.
     *        If undefined, all standard visualizers are used.
     */
    function DataSourceDisplay(options) {
                if (!defined(options)) {
            throw new DeveloperError('options is required.');
        }
        if (!defined(options.scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(options.dataSourceCollection)) {
            throw new DeveloperError('dataSourceCollection is required.');
        }
                
        GroundPrimitive.initializeTerrainHeights();

        var scene = options.scene;
        var dataSourceCollection = options.dataSourceCollection;

        this._eventHelper = new EventHelper();
        this._eventHelper.add(dataSourceCollection.dataSourceAdded, this._onDataSourceAdded, this);
        this._eventHelper.add(dataSourceCollection.dataSourceRemoved, this._onDataSourceRemoved, this);

        this._dataSourceCollection = dataSourceCollection;
        this._scene = scene;
        this._visualizersCallback = defaultValue(options.visualizersCallback, DataSourceDisplay.defaultVisualizersCallback);

        for (var i = 0, len = dataSourceCollection.length; i < len; i++) {
            this._onDataSourceAdded(dataSourceCollection, dataSourceCollection.get(i));
        }

        var defaultDataSource = new CustomDataSource();
        this._onDataSourceAdded(undefined, defaultDataSource);
        this._defaultDataSource = defaultDataSource;

        this._ready = false;
    }

    /**
     * Gets or sets the default function which creates an array of visualizers used for visualization.
     * By default, this function uses all standard visualizers.
     *
     * @member
     * @type {DataSourceDisplay~VisualizersCallback}
     */
    DataSourceDisplay.defaultVisualizersCallback = function(scene, entityCluster, dataSource) {
        var entities = dataSource.entities;
        return [new BillboardVisualizer(entityCluster, entities),
                new GeometryVisualizer(BoxGeometryUpdater, scene, entities),
                new GeometryVisualizer(CylinderGeometryUpdater, scene, entities),
                new GeometryVisualizer(CorridorGeometryUpdater, scene, entities),
                new GeometryVisualizer(EllipseGeometryUpdater, scene, entities),
                new GeometryVisualizer(EllipsoidGeometryUpdater, scene, entities),
                new GeometryVisualizer(PolygonGeometryUpdater, scene, entities),
                new GeometryVisualizer(PolylineGeometryUpdater, scene, entities),
                new GeometryVisualizer(PolylineVolumeGeometryUpdater, scene, entities),
                new GeometryVisualizer(RectangleGeometryUpdater, scene, entities),
                new GeometryVisualizer(WallGeometryUpdater, scene, entities),
                new LabelVisualizer(entityCluster, entities),
                new ModelVisualizer(scene, entities),
                new PointVisualizer(entityCluster, entities),
                new PathVisualizer(scene, entities)];
    };

    defineProperties(DataSourceDisplay.prototype, {
        /**
         * Gets the scene associated with this display.
         * @memberof DataSourceDisplay.prototype
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },
        /**
         * Gets the collection of data sources to display.
         * @memberof DataSourceDisplay.prototype
         * @type {DataSourceCollection}
         */
        dataSources : {
            get : function() {
                return this._dataSourceCollection;
            }
        },
        /**
         * Gets the default data source instance which can be used to
         * manually create and visualize entities not tied to
         * a specific data source. This instance is always available
         * and does not appear in the list dataSources collection.
         * @memberof DataSourceDisplay.prototype
         * @type {CustomDataSource}
         */
        defaultDataSource : {
            get : function() {
                return this._defaultDataSource;
            }
        },

        /**
         * Gets a value indicating whether or not all entities in the data source are ready
         * @memberof DataSourceDisplay.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        }
    });

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see DataSourceDisplay#destroy
     */
    DataSourceDisplay.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * dataSourceDisplay = dataSourceDisplay.destroy();
     *
     * @see DataSourceDisplay#isDestroyed
     */
    DataSourceDisplay.prototype.destroy = function() {
        this._eventHelper.removeAll();

        var dataSourceCollection = this._dataSourceCollection;
        for (var i = 0, length = dataSourceCollection.length; i < length; ++i) {
            this._onDataSourceRemoved(this._dataSourceCollection, dataSourceCollection.get(i));
        }
        this._onDataSourceRemoved(undefined, this._defaultDataSource);

        return destroyObject(this);
    };

    /**
     * Updates the display to the provided time.
     *
     * @param {JulianDate} time The simulation time.
     * @returns {Boolean} True if all data sources are ready to be displayed, false otherwise.
     */
    DataSourceDisplay.prototype.update = function(time) {
                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        
        if (!GroundPrimitive._initialized) {
            this._ready = false;
            return false;
        }
        
        var result = true;

        var i;
        var x;
        var visualizers;
        var vLength;
        var dataSources = this._dataSourceCollection;
        var length = dataSources.length;
        for (i = 0; i < length; i++) {
            var dataSource = dataSources.get(i);
            if (defined(dataSource.update)) {
                result = dataSource.update(time) && result;
            }

            visualizers = dataSource._visualizers;
            vLength = visualizers.length;
            for (x = 0; x < vLength; x++) {
                result = visualizers[x].update(time) && result;
            }
        }

        visualizers = this._defaultDataSource._visualizers;
        vLength = visualizers.length;
        for (x = 0; x < vLength; x++) {
            result = visualizers[x].update(time) && result;
        }

        this._ready = result;

        return result;
    };

    var getBoundingSphereArrayScratch = [];
    var getBoundingSphereBoundingSphereScratch = new BoundingSphere();

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {Boolean} allowPartial If true, pending bounding spheres are ignored and an answer will be returned from the currently available data.
     *                               If false, the the function will halt and return pending if any of the bounding spheres are pending.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    DataSourceDisplay.prototype.getBoundingSphere = function(entity, allowPartial, result) {
                if (!defined(entity)) {
            throw new DeveloperError('entity is required.');
        }
        if (!defined(allowPartial)) {
            throw new DeveloperError('allowPartial is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('result is required.');
        }
        
        if (!this._ready) {
            return BoundingSphereState.PENDING;
        }

        var i;
        var length;
        var dataSource = this._defaultDataSource;
        if (!dataSource.entities.contains(entity)) {
            dataSource = undefined;

            var dataSources = this._dataSourceCollection;
            length = dataSources.length;
            for (i = 0; i < length; i++) {
                var d = dataSources.get(i);
                if (d.entities.contains(entity)) {
                    dataSource = d;
                    break;
                }
            }
        }

        if (!defined(dataSource)) {
            return BoundingSphereState.FAILED;
        }

        var boundingSpheres = getBoundingSphereArrayScratch;
        var tmp = getBoundingSphereBoundingSphereScratch;

        var count = 0;
        var state = BoundingSphereState.DONE;
        var visualizers = dataSource._visualizers;
        var visualizersLength = visualizers.length;

        for (i = 0; i < visualizersLength; i++) {
            var visualizer = visualizers[i];
            if (defined(visualizer.getBoundingSphere)) {
                state = visualizers[i].getBoundingSphere(entity, tmp);
                if (!allowPartial && state === BoundingSphereState.PENDING) {
                    return BoundingSphereState.PENDING;
                } else if (state === BoundingSphereState.DONE) {
                    boundingSpheres[count] = BoundingSphere.clone(tmp, boundingSpheres[count]);
                    count++;
                }
            }
        }

        if (count === 0) {
            return BoundingSphereState.FAILED;
        }

        boundingSpheres.length = count;
        BoundingSphere.fromBoundingSpheres(boundingSpheres, result);
        return BoundingSphereState.DONE;
    };

    DataSourceDisplay.prototype._onDataSourceAdded = function(dataSourceCollection, dataSource) {
        var scene = this._scene;

        var entityCluster = dataSource.clustering;
        entityCluster._initialize(scene);

        scene.primitives.add(entityCluster);

        dataSource._visualizers = this._visualizersCallback(scene, entityCluster, dataSource);
    };

    DataSourceDisplay.prototype._onDataSourceRemoved = function(dataSourceCollection, dataSource) {
        var scene = this._scene;
        var entityCluster = dataSource.clustering;
        scene.primitives.remove(entityCluster);

        var visualizers = dataSource._visualizers;
        var length = visualizers.length;
        for (var i = 0; i < length; i++) {
            visualizers[i].destroy();
        }

        dataSource._visualizers = undefined;
    };

    /**
     * A function which creates an array of visualizers used for visualization.
     * @callback DataSourceDisplay~VisualizersCallback
     *
     * @param {Scene} scene The scene to create visualizers for.
     * @param {DataSource} dataSource The data source to create visualizers for.
     * @returns {Visualizer[]} An array of visualizers used for visualization.
     *
     * @example
     * function createVisualizers(scene, dataSource) {
     *     return [new Cesium.BillboardVisualizer(scene, dataSource.entities)];
     * }
     */

    return DataSourceDisplay;
});

/*global define*/
define('DataSources/DynamicGeometryUpdater',[
        '../Core/DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * Defines the interface for a dynamic geometry updater.  A DynamicGeometryUpdater
     * is responsible for handling visualization of a specific type of geometry
     * that needs to be recomputed based on simulation time.
     * This object is never used directly by client code, but is instead created by
     * {@link GeometryUpdater} implementations which contain dynamic geometry.
     *
     * This type defines an interface and cannot be instantiated directly.
     *
     * @alias DynamicGeometryUpdater
     * @constructor
     */
    function DynamicGeometryUpdater() {
        DeveloperError.throwInstantiationError();
    }

    /**
     * Updates the geometry to the specified time.
     * @memberof DynamicGeometryUpdater
     * @function
     *
     * @param {JulianDate} time The current time.
     */
    DynamicGeometryUpdater.prototype.update = DeveloperError.throwInstantiationError;

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     * @function
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    DynamicGeometryUpdater.prototype.getBoundingSphere = DeveloperError.throwInstantiationError;

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * @memberof DynamicGeometryUpdater
     * @function
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    DynamicGeometryUpdater.prototype.isDestroyed = DeveloperError.throwInstantiationError;

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     * @memberof DynamicGeometryUpdater
     * @function
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    DynamicGeometryUpdater.prototype.destroy = DeveloperError.throwInstantiationError;

    return DynamicGeometryUpdater;
});

/*global define*/
define('DataSources/EntityView',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/HeadingPitchRange',
        '../Core/JulianDate',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Transforms',
        '../Scene/SceneMode'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        HeadingPitchRange,
        JulianDate,
        CesiumMath,
        Matrix3,
        Matrix4,
        Transforms,
        SceneMode) {
    'use strict';

    var updateTransformMatrix3Scratch1 = new Matrix3();
    var updateTransformMatrix3Scratch2 = new Matrix3();
    var updateTransformMatrix3Scratch3 = new Matrix3();
    var updateTransformMatrix4Scratch = new Matrix4();
    var updateTransformCartesian3Scratch1 = new Cartesian3();
    var updateTransformCartesian3Scratch2 = new Cartesian3();
    var updateTransformCartesian3Scratch3 = new Cartesian3();
    var updateTransformCartesian3Scratch4 = new Cartesian3();
    var updateTransformCartesian3Scratch5 = new Cartesian3();
    var updateTransformCartesian3Scratch6 = new Cartesian3();
    var deltaTime = new JulianDate();
    var northUpAxisFactor = 1.25;  // times ellipsoid's maximum radius

    function updateTransform(that, camera, updateLookAt, saveCamera, positionProperty, time, ellipsoid) {
        var mode = that.scene.mode;
        var cartesian = positionProperty.getValue(time, that._lastCartesian);
        if (defined(cartesian)) {
            var hasBasis = false;
            var xBasis;
            var yBasis;
            var zBasis;

            if (mode === SceneMode.SCENE3D) {
                // The time delta was determined based on how fast satellites move compared to vehicles near the surface.
                // Slower moving vehicles will most likely default to east-north-up, while faster ones will be VVLH.
                deltaTime = JulianDate.addSeconds(time, 0.001, deltaTime);
                var deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1);
                if (defined(deltaCartesian)) {
                    var toInertial = Transforms.computeFixedToIcrfMatrix(time, updateTransformMatrix3Scratch1);
                    var toInertialDelta = Transforms.computeFixedToIcrfMatrix(deltaTime, updateTransformMatrix3Scratch2);
                    var toFixed;

                    if (!defined(toInertial) || !defined(toInertialDelta)) {
                        toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, updateTransformMatrix3Scratch3);
                        toInertial = Matrix3.transpose(toFixed, updateTransformMatrix3Scratch1);
                        toInertialDelta = Transforms.computeTemeToPseudoFixedMatrix(deltaTime, updateTransformMatrix3Scratch2);
                        Matrix3.transpose(toInertialDelta, toInertialDelta);
                    } else {
                        toFixed = Matrix3.transpose(toInertial, updateTransformMatrix3Scratch3);
                    }

                    var inertialCartesian = Matrix3.multiplyByVector(toInertial, cartesian, updateTransformCartesian3Scratch5);
                    var inertialDeltaCartesian = Matrix3.multiplyByVector(toInertialDelta, deltaCartesian, updateTransformCartesian3Scratch6);

                    Cartesian3.subtract(inertialCartesian, inertialDeltaCartesian, updateTransformCartesian3Scratch4);
                    var inertialVelocity = Cartesian3.magnitude(updateTransformCartesian3Scratch4) * 1000.0; // meters/sec

                    // http://en.wikipedia.org/wiki/Standard_gravitational_parameter
                    // Consider adding this to Cesium.Ellipsoid?
                    var mu = 3.986004418e14; // m^3 / sec^2

                    var semiMajorAxis = -mu / (inertialVelocity * inertialVelocity - (2 * mu / Cartesian3.magnitude(inertialCartesian)));

                    if (semiMajorAxis < 0 || semiMajorAxis > northUpAxisFactor * ellipsoid.maximumRadius) {
                        // North-up viewing from deep space.

                        // X along the nadir
                        xBasis = updateTransformCartesian3Scratch2;
                        Cartesian3.normalize(cartesian, xBasis);
                        Cartesian3.negate(xBasis, xBasis);

                        // Z is North
                        zBasis = Cartesian3.clone(Cartesian3.UNIT_Z, updateTransformCartesian3Scratch3);

                        // Y is along the cross of z and x (right handed basis / in the direction of motion)
                        yBasis = Cartesian3.cross(zBasis, xBasis, updateTransformCartesian3Scratch1);
                        if (Cartesian3.magnitude(yBasis) > CesiumMath.EPSILON7) {
                            Cartesian3.normalize(xBasis, xBasis);
                            Cartesian3.normalize(yBasis, yBasis);

                            zBasis = Cartesian3.cross(xBasis, yBasis, updateTransformCartesian3Scratch3);
                            Cartesian3.normalize(zBasis, zBasis);

                            hasBasis = true;
                        }
                    } else if (!Cartesian3.equalsEpsilon(cartesian, deltaCartesian, CesiumMath.EPSILON7)) {
                        // Approximation of VVLH (Vehicle Velocity Local Horizontal) with the Z-axis flipped.

                        // Z along the position
                        zBasis = updateTransformCartesian3Scratch2;
                        Cartesian3.normalize(inertialCartesian, zBasis);
                        Cartesian3.normalize(inertialDeltaCartesian, inertialDeltaCartesian);

                        // Y is along the angular momentum vector (e.g. "orbit normal")
                        yBasis = Cartesian3.cross(zBasis, inertialDeltaCartesian, updateTransformCartesian3Scratch3);
                        if (!Cartesian3.equalsEpsilon(yBasis, Cartesian3.ZERO, CesiumMath.EPSILON7)) {
                            // X is along the cross of y and z (right handed basis / in the direction of motion)
                            xBasis = Cartesian3.cross(yBasis, zBasis, updateTransformCartesian3Scratch1);

                            Matrix3.multiplyByVector(toFixed, xBasis, xBasis);
                            Matrix3.multiplyByVector(toFixed, yBasis, yBasis);
                            Matrix3.multiplyByVector(toFixed, zBasis, zBasis);

                            Cartesian3.normalize(xBasis, xBasis);
                            Cartesian3.normalize(yBasis, yBasis);
                            Cartesian3.normalize(zBasis, zBasis);

                            hasBasis = true;
                        }
                    }
                }
            }

            if (defined(that.boundingSphere)) {
                cartesian = that.boundingSphere.center;
            }

            var position;
            var direction;
            var up;

            if (saveCamera) {
                position = Cartesian3.clone(camera.position, updateTransformCartesian3Scratch4);
                direction = Cartesian3.clone(camera.direction, updateTransformCartesian3Scratch5);
                up = Cartesian3.clone(camera.up, updateTransformCartesian3Scratch6);
            }

            var transform = updateTransformMatrix4Scratch;
            if (hasBasis) {
                transform[0]  = xBasis.x;
                transform[1]  = xBasis.y;
                transform[2]  = xBasis.z;
                transform[3]  = 0.0;
                transform[4]  = yBasis.x;
                transform[5]  = yBasis.y;
                transform[6]  = yBasis.z;
                transform[7]  = 0.0;
                transform[8]  = zBasis.x;
                transform[9]  = zBasis.y;
                transform[10] = zBasis.z;
                transform[11] = 0.0;
                transform[12]  = cartesian.x;
                transform[13]  = cartesian.y;
                transform[14] = cartesian.z;
                transform[15] = 0.0;
            } else {
                // Stationary or slow-moving, low-altitude objects use East-North-Up.
                Transforms.eastNorthUpToFixedFrame(cartesian, ellipsoid, transform);
            }

            camera._setTransform(transform);

            if (saveCamera) {
                Cartesian3.clone(position, camera.position);
                Cartesian3.clone(direction, camera.direction);
                Cartesian3.clone(up, camera.up);
                Cartesian3.cross(direction, up, camera.right);
            }
        }

        if (updateLookAt) {
            var offset = (mode === SceneMode.SCENE2D || Cartesian3.equals(that._offset3D, Cartesian3.ZERO)) ? undefined : that._offset3D;
            camera.lookAtTransform(camera.transform, offset);
        }
    }

    /**
     * A utility object for tracking an entity with the camera.
     * @alias EntityView
     * @constructor
     *
     * @param {Entity} entity The entity to track with the camera.
     * @param {Scene} scene The scene to use.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use for orienting the camera.
     */
    function EntityView(entity, scene, ellipsoid) {

        /**
         * The entity to track with the camera.
         * @type {Entity}
         */
        this.entity = entity;

        /**
         * The scene in which to track the object.
         * @type {Scene}
         */
        this.scene = scene;

        /**
         * The ellipsoid to use for orienting the camera.
         * @type {Ellipsoid}
         */
        this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        /**
         * The bounding sphere of the object.
         * @type {BoundingSphere}
         */
        this.boundingSphere = undefined;

        //Shadow copies of the objects so we can detect changes.
        this._lastEntity = undefined;
        this._mode = undefined;

        this._lastCartesian = new Cartesian3();
        this._defaultOffset3D = undefined;

        this._offset3D = new Cartesian3();
    }

    // STATIC properties defined here, not per-instance.
    defineProperties(EntityView, {
        /**
         * Gets or sets a camera offset that will be used to
         * initialize subsequent EntityViews.
         * @memberof EntityView
         * @type {Cartesian3}
         */
        defaultOffset3D : {
            get : function() {
                return this._defaultOffset3D;
            },
            set : function(vector) {
                this._defaultOffset3D = Cartesian3.clone(vector, new Cartesian3());
            }
        }
    });

    // Initialize the static property.
    EntityView.defaultOffset3D = new Cartesian3(-14000, 3500, 3500);

    var scratchHeadingPitchRange = new HeadingPitchRange();
    var scratchCartesian = new Cartesian3();

    /**
    * Should be called each animation frame to update the camera
    * to the latest settings.
    * @param {JulianDate} time The current animation time.
    * @param {BoundingSphere} boundingSphere bounding sphere of the object.
    *
    */
    EntityView.prototype.update = function(time, boundingSphere) {
        var scene = this.scene;
        var entity = this.entity;
        var ellipsoid = this.ellipsoid;

                if (!defined(time)) {
            throw new DeveloperError('time is required.');
        }
        if (!defined(scene)) {
            throw new DeveloperError('EntityView.scene is required.');
        }
        if (!defined(entity)) {
            throw new DeveloperError('EntityView.entity is required.');
        }
        if (!defined(ellipsoid)) {
            throw new DeveloperError('EntityView.ellipsoid is required.');
        }
        if (!defined(entity.position)) {
            throw new DeveloperError('entity.position is required.');
        }
        
        var sceneMode = scene.mode;
        if (sceneMode === SceneMode.MORPHING) {
            return;
        }

        var positionProperty = entity.position;
        var objectChanged = entity !== this._lastEntity;
        var sceneModeChanged = sceneMode !== this._mode;

        var offset3D = this._offset3D;
        var camera = scene.camera;

        var updateLookAt = objectChanged || sceneModeChanged;
        var saveCamera = true;

        if (objectChanged) {
            var viewFromProperty = entity.viewFrom;
            var hasViewFrom = defined(viewFromProperty);

            if (!hasViewFrom && defined(boundingSphere)) {
                var controller = scene.screenSpaceCameraController;
                controller.minimumZoomDistance = Math.min(controller.minimumZoomDistance, boundingSphere.radius * 0.5);

                //The default HPR is not ideal for high altitude objects so
                //we scale the pitch as we get further from the earth for a more
                //downward view.
                scratchHeadingPitchRange.pitch = -CesiumMath.PI_OVER_FOUR;
                scratchHeadingPitchRange.range = 0;
                var position = positionProperty.getValue(time, scratchCartesian);
                if (defined(position)) {
                    var factor = 2 - 1 / Math.max(1, Cartesian3.magnitude(position) / ellipsoid.maximumRadius);
                    scratchHeadingPitchRange.pitch *= factor;
                }

                camera.viewBoundingSphere(boundingSphere, scratchHeadingPitchRange);
                this.boundingSphere = boundingSphere;
                updateLookAt = false;
                saveCamera = false;
            } else if (!hasViewFrom || !defined(viewFromProperty.getValue(time, offset3D))) {
                Cartesian3.clone(EntityView._defaultOffset3D, offset3D);
            }
        } else if (!sceneModeChanged && scene.mode !== SceneMode.MORPHING && this._mode !== SceneMode.SCENE2D) {
            Cartesian3.clone(camera.position, offset3D);
        }

        this._lastEntity = entity;
        this._mode = scene.mode !== SceneMode.MORPHING ? scene.mode : this._mode;

        if (scene.mode !== SceneMode.MORPHING) {
            updateTransform(this, camera, updateLookAt, saveCamera, positionProperty, time, ellipsoid);
        }
    };

    return EntityView;
});

/**
@license
topojson - https://github.com/mbostock/topojson

Copyright (c) 2012, Michael Bostock
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* The name Michael Bostock may not be used to endorse or promote products
  derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

!function() {
  var topojson = {
    version: "1.6.18",
    mesh: function(topology) { return object(topology, meshArcs.apply(this, arguments)); },
    meshArcs: meshArcs,
    merge: function(topology) { return object(topology, mergeArcs.apply(this, arguments)); },
    mergeArcs: mergeArcs,
    feature: featureOrCollection,
    neighbors: neighbors,
    presimplify: presimplify
  };

  function stitchArcs(topology, arcs) {
    var stitchedArcs = {},
        fragmentByStart = {},
        fragmentByEnd = {},
        fragments = [],
        emptyIndex = -1;

    // Stitch empty arcs first, since they may be subsumed by other arcs.
    arcs.forEach(function(i, j) {
      var arc = topology.arcs[i < 0 ? ~i : i], t;
      if (arc.length < 3 && !arc[1][0] && !arc[1][1]) {
        t = arcs[++emptyIndex], arcs[emptyIndex] = i, arcs[j] = t;
      }
    });

    arcs.forEach(function(i) {
      var e = ends(i),
          start = e[0],
          end = e[1],
          f, g;

      if (f = fragmentByEnd[start]) {
        delete fragmentByEnd[f.end];
        f.push(i);
        f.end = end;
        if (g = fragmentByStart[end]) {
          delete fragmentByStart[g.start];
          var fg = g === f ? f : f.concat(g);
          fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg;
        } else {
          fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
        }
      } else if (f = fragmentByStart[end]) {
        delete fragmentByStart[f.start];
        f.unshift(i);
        f.start = start;
        if (g = fragmentByEnd[start]) {
          delete fragmentByEnd[g.end];
          var gf = g === f ? f : g.concat(f);
          fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf;
        } else {
          fragmentByStart[f.start] = fragmentByEnd[f.end] = f;
        }
      } else {
        f = [i];
        fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f;
      }
    });

    function ends(i) {
      var arc = topology.arcs[i < 0 ? ~i : i], p0 = arc[0], p1;
      if (topology.transform) p1 = [0, 0], arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; });
      else p1 = arc[arc.length - 1];
      return i < 0 ? [p1, p0] : [p0, p1];
    }

    function flush(fragmentByEnd, fragmentByStart) {
      for (var k in fragmentByEnd) {
        var f = fragmentByEnd[k];
        delete fragmentByStart[f.start];
        delete f.start;
        delete f.end;
        f.forEach(function(i) { stitchedArcs[i < 0 ? ~i : i] = 1; });
        fragments.push(f);
      }
    }

    flush(fragmentByEnd, fragmentByStart);
    flush(fragmentByStart, fragmentByEnd);
    arcs.forEach(function(i) { if (!stitchedArcs[i < 0 ? ~i : i]) fragments.push([i]); });

    return fragments;
  }

  function meshArcs(topology, o, filter) {
    var arcs = [];

    if (arguments.length > 1) {
      var geomsByArc = [],
          geom;

      function arc(i) {
        var j = i < 0 ? ~i : i;
        (geomsByArc[j] || (geomsByArc[j] = [])).push({i: i, g: geom});
      }

      function line(arcs) {
        arcs.forEach(arc);
      }

      function polygon(arcs) {
        arcs.forEach(line);
      }

      function geometry(o) {
        if (o.type === "GeometryCollection") o.geometries.forEach(geometry);
        else if (o.type in geometryType) geom = o, geometryType[o.type](o.arcs);
      }

      var geometryType = {
        LineString: line,
        MultiLineString: polygon,
        Polygon: polygon,
        MultiPolygon: function(arcs) { arcs.forEach(polygon); }
      };

      geometry(o);

      geomsByArc.forEach(arguments.length < 3
          ? function(geoms) { arcs.push(geoms[0].i); }
          : function(geoms) { if (filter(geoms[0].g, geoms[geoms.length - 1].g)) arcs.push(geoms[0].i); });
    } else {
      for (var i = 0, n = topology.arcs.length; i < n; ++i) arcs.push(i);
    }

    return {type: "MultiLineString", arcs: stitchArcs(topology, arcs)};
  }

  function mergeArcs(topology, objects) {
    var polygonsByArc = {},
        polygons = [],
        components = [];

    objects.forEach(function(o) {
      if (o.type === "Polygon") register(o.arcs);
      else if (o.type === "MultiPolygon") o.arcs.forEach(register);
    });

    function register(polygon) {
      polygon.forEach(function(ring) {
        ring.forEach(function(arc) {
          (polygonsByArc[arc = arc < 0 ? ~arc : arc] || (polygonsByArc[arc] = [])).push(polygon);
        });
      });
      polygons.push(polygon);
    }

    function exterior(ring) {
      return cartesianRingArea(object(topology, {type: "Polygon", arcs: [ring]}).coordinates[0]) > 0; // TODO allow spherical?
    }

    polygons.forEach(function(polygon) {
      if (!polygon._) {
        var component = [],
            neighbors = [polygon];
        polygon._ = 1;
        components.push(component);
        while (polygon = neighbors.pop()) {
          component.push(polygon);
          polygon.forEach(function(ring) {
            ring.forEach(function(arc) {
              polygonsByArc[arc < 0 ? ~arc : arc].forEach(function(polygon) {
                if (!polygon._) {
                  polygon._ = 1;
                  neighbors.push(polygon);
                }
              });
            });
          });
        }
      }
    });

    polygons.forEach(function(polygon) {
      delete polygon._;
    });

    return {
      type: "MultiPolygon",
      arcs: components.map(function(polygons) {
        var arcs = [];

        // Extract the exterior (unique) arcs.
        polygons.forEach(function(polygon) {
          polygon.forEach(function(ring) {
            ring.forEach(function(arc) {
              if (polygonsByArc[arc < 0 ? ~arc : arc].length < 2) {
                arcs.push(arc);
              }
            });
          });
        });

        // Stitch the arcs into one or more rings.
        arcs = stitchArcs(topology, arcs);

        // If more than one ring is returned,
        // at most one of these rings can be the exterior;
        // this exterior ring has the same winding order
        // as any exterior ring in the original polygons.
        if ((n = arcs.length) > 1) {
          var sgn = exterior(polygons[0][0]);
          for (var i = 0, t; i < n; ++i) {
            if (sgn === exterior(arcs[i])) {
              t = arcs[0], arcs[0] = arcs[i], arcs[i] = t;
              break;
            }
          }
        }

        return arcs;
      })
    };
  }

  function featureOrCollection(topology, o) {
    return o.type === "GeometryCollection" ? {
      type: "FeatureCollection",
      features: o.geometries.map(function(o) { return feature(topology, o); })
    } : feature(topology, o);
  }

  function feature(topology, o) {
    var f = {
      type: "Feature",
      id: o.id,
      properties: o.properties || {},
      geometry: object(topology, o)
    };
    if (o.id == null) delete f.id;
    return f;
  }

  function object(topology, o) {
    var absolute = transformAbsolute(topology.transform),
        arcs = topology.arcs;

    function arc(i, points) {
      if (points.length) points.pop();
      for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length, p; k < n; ++k) {
        points.push(p = a[k].slice());
        absolute(p, k);
      }
      if (i < 0) reverse(points, n);
    }

    function point(p) {
      p = p.slice();
      absolute(p, 0);
      return p;
    }

    function line(arcs) {
      var points = [];
      for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points);
      if (points.length < 2) points.push(points[0].slice());
      return points;
    }

    function ring(arcs) {
      var points = line(arcs);
      while (points.length < 4) points.push(points[0].slice());
      return points;
    }

    function polygon(arcs) {
      return arcs.map(ring);
    }

    function geometry(o) {
      var t = o.type;
      return t === "GeometryCollection" ? {type: t, geometries: o.geometries.map(geometry)}
          : t in geometryType ? {type: t, coordinates: geometryType[t](o)}
          : null;
    }

    var geometryType = {
      Point: function(o) { return point(o.coordinates); },
      MultiPoint: function(o) { return o.coordinates.map(point); },
      LineString: function(o) { return line(o.arcs); },
      MultiLineString: function(o) { return o.arcs.map(line); },
      Polygon: function(o) { return polygon(o.arcs); },
      MultiPolygon: function(o) { return o.arcs.map(polygon); }
    };

    return geometry(o);
  }

  function reverse(array, n) {
    var t, j = array.length, i = j - n; while (i < --j) t = array[i], array[i++] = array[j], array[j] = t;
  }

  function bisect(a, x) {
    var lo = 0, hi = a.length;
    while (lo < hi) {
      var mid = lo + hi >>> 1;
      if (a[mid] < x) lo = mid + 1;
      else hi = mid;
    }
    return lo;
  }

  function neighbors(objects) {
    var indexesByArc = {}, // arc index -> array of object indexes
        neighbors = objects.map(function() { return []; });

    function line(arcs, i) {
      arcs.forEach(function(a) {
        if (a < 0) a = ~a;
        var o = indexesByArc[a];
        if (o) o.push(i);
        else indexesByArc[a] = [i];
      });
    }

    function polygon(arcs, i) {
      arcs.forEach(function(arc) { line(arc, i); });
    }

    function geometry(o, i) {
      if (o.type === "GeometryCollection") o.geometries.forEach(function(o) { geometry(o, i); });
      else if (o.type in geometryType) geometryType[o.type](o.arcs, i);
    }

    var geometryType = {
      LineString: line,
      MultiLineString: polygon,
      Polygon: polygon,
      MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); }
    };

    objects.forEach(geometry);

    for (var i in indexesByArc) {
      for (var indexes = indexesByArc[i], m = indexes.length, j = 0; j < m; ++j) {
        for (var k = j + 1; k < m; ++k) {
          var ij = indexes[j], ik = indexes[k], n;
          if ((n = neighbors[ij])[i = bisect(n, ik)] !== ik) n.splice(i, 0, ik);
          if ((n = neighbors[ik])[i = bisect(n, ij)] !== ij) n.splice(i, 0, ij);
        }
      }
    }

    return neighbors;
  }

  function presimplify(topology, triangleArea) {
    var absolute = transformAbsolute(topology.transform),
        relative = transformRelative(topology.transform),
        heap = minAreaHeap();

    if (!triangleArea) triangleArea = cartesianTriangleArea;

    topology.arcs.forEach(function(arc) {
      var triangles = [],
          maxArea = 0,
          triangle;

      // To store each point’s effective area, we create a new array rather than
      // extending the passed-in point to workaround a Chrome/V8 bug (getting
      // stuck in smi mode). For midpoints, the initial effective area of
      // Infinity will be computed in the next step.
      for (var i = 0, n = arc.length, p; i < n; ++i) {
        p = arc[i];
        absolute(arc[i] = [p[0], p[1], Infinity], i);
      }

      for (var i = 1, n = arc.length - 1; i < n; ++i) {
        triangle = arc.slice(i - 1, i + 2);
        triangle[1][2] = triangleArea(triangle);
        triangles.push(triangle);
        heap.push(triangle);
      }

      for (var i = 0, n = triangles.length; i < n; ++i) {
        triangle = triangles[i];
        triangle.previous = triangles[i - 1];
        triangle.next = triangles[i + 1];
      }

      while (triangle = heap.pop()) {
        var previous = triangle.previous,
            next = triangle.next;

        // If the area of the current point is less than that of the previous point
        // to be eliminated, use the latter's area instead. This ensures that the
        // current point cannot be eliminated without eliminating previously-
        // eliminated points.
        if (triangle[1][2] < maxArea) triangle[1][2] = maxArea;
        else maxArea = triangle[1][2];

        if (previous) {
          previous.next = next;
          previous[2] = triangle[2];
          update(previous);
        }

        if (next) {
          next.previous = previous;
          next[0] = triangle[0];
          update(next);
        }
      }

      arc.forEach(relative);
    });

    function update(triangle) {
      heap.remove(triangle);
      triangle[1][2] = triangleArea(triangle);
      heap.push(triangle);
    }

    return topology;
  }
  function cartesianRingArea(ring) {
    var i = -1,
        n = ring.length,
        a,
        b = ring[n - 1],
        area = 0;

    while (++i < n) {
      a = b;
      b = ring[i];
      area += a[0] * b[1] - a[1] * b[0];
    }

    return area * .5;
  }

  function cartesianTriangleArea(triangle) {
    var a = triangle[0], b = triangle[1], c = triangle[2];
    return Math.abs((a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1]));
  }

  function compareArea(a, b) {
    return a[1][2] - b[1][2];
  }

  function minAreaHeap() {
    var heap = {},
        array = [],
        size = 0;

    heap.push = function(object) {
      up(array[object._ = size] = object, size++);
      return size;
    };

    heap.pop = function() {
      if (size <= 0) return;
      var removed = array[0], object;
      if (--size > 0) object = array[size], down(array[object._ = 0] = object, 0);
      return removed;
    };

    heap.remove = function(removed) {
      var i = removed._, object;
      if (array[i] !== removed) return; // invalid request
      if (i !== --size) object = array[size], (compareArea(object, removed) < 0 ? up : down)(array[object._ = i] = object, i);
      return i;
    };

    function up(object, i) {
      while (i > 0) {
        var j = ((i + 1) >> 1) - 1,
            parent = array[j];
        if (compareArea(object, parent) >= 0) break;
        array[parent._ = i] = parent;
        array[object._ = i = j] = object;
      }
    }

    function down(object, i) {
      while (true) {
        var r = (i + 1) << 1,
            l = r - 1,
            j = i,
            child = array[j];
        if (l < size && compareArea(array[l], child) < 0) child = array[j = l];
        if (r < size && compareArea(array[r], child) < 0) child = array[j = r];
        if (j === i) break;
        array[child._ = i] = child;
        array[object._ = i = j] = object;
      }
    }

    return heap;
  }

  function transformAbsolute(transform) {
    if (!transform) return noop;
    var x0,
        y0,
        kx = transform.scale[0],
        ky = transform.scale[1],
        dx = transform.translate[0],
        dy = transform.translate[1];
    return function(point, i) {
      if (!i) x0 = y0 = 0;
      point[0] = (x0 += point[0]) * kx + dx;
      point[1] = (y0 += point[1]) * ky + dy;
    };
  }

  function transformRelative(transform) {
    if (!transform) return noop;
    var x0,
        y0,
        kx = transform.scale[0],
        ky = transform.scale[1],
        dx = transform.translate[0],
        dy = transform.translate[1];
    return function(point, i) {
      if (!i) x0 = y0 = 0;
      var x1 = (point[0] - dx) / kx | 0,
          y1 = (point[1] - dy) / ky | 0;
      point[0] = x1 - x0;
      point[1] = y1 - y0;
      x0 = x1;
      y0 = y1;
    };
  }

  function noop() {}

  if (typeof define === "function" && define.amd) define('ThirdParty/topojson',topojson);
  else if (typeof module === "object" && module.exports) module.exports = topojson;
  else this.topojson = topojson;
}();

/*global define*/
define('DataSources/GeoJsonDataSource',[
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/getFilenameFromUri',
        '../Core/loadJson',
        '../Core/PinBuilder',
        '../Core/PolygonHierarchy',
        '../Core/RuntimeError',
        '../Scene/HeightReference',
        '../Scene/VerticalOrigin',
        '../ThirdParty/topojson',
        '../ThirdParty/when',
        './BillboardGraphics',
        './CallbackProperty',
        './ColorMaterialProperty',
        './ConstantPositionProperty',
        './ConstantProperty',
        './CorridorGraphics',
        './DataSource',
        './EntityCluster',
        './EntityCollection',
        './PolygonGraphics',
        './PolylineGraphics'
    ], function(
        Cartesian3,
        Color,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        getFilenameFromUri,
        loadJson,
        PinBuilder,
        PolygonHierarchy,
        RuntimeError,
        HeightReference,
        VerticalOrigin,
        topojson,
        when,
        BillboardGraphics,
        CallbackProperty,
        ColorMaterialProperty,
        ConstantPositionProperty,
        ConstantProperty,
        CorridorGraphics,
        DataSource,
        EntityCluster,
        EntityCollection,
        PolygonGraphics,
        PolylineGraphics) {
    'use strict';

    function defaultCrsFunction(coordinates) {
        return Cartesian3.fromDegrees(coordinates[0], coordinates[1], coordinates[2]);
    }

    var crsNames = {
        'urn:ogc:def:crs:OGC:1.3:CRS84' : defaultCrsFunction,
        'EPSG:4326' : defaultCrsFunction,
        'urn:ogc:def:crs:EPSG::4326' : defaultCrsFunction
    };

    var crsLinkHrefs = {};
    var crsLinkTypes = {};
    var defaultMarkerSize = 48;
    var defaultMarkerSymbol;
    var defaultMarkerColor = Color.ROYALBLUE;
    var defaultStroke = Color.YELLOW;
    var defaultStrokeWidth = 2;
    var defaultFill = Color.fromBytes(255, 255, 0, 100);
    var defaultClampToGround = false;

    var defaultStrokeWidthProperty = new ConstantProperty(defaultStrokeWidth);
    var defaultStrokeMaterialProperty = new ColorMaterialProperty(defaultStroke);
    var defaultFillMaterialProperty = new ColorMaterialProperty(defaultFill);

    var sizes = {
        small : 24,
        medium : 48,
        large : 64
    };

    var simpleStyleIdentifiers = ['title', 'description', //
    'marker-size', 'marker-symbol', 'marker-color', 'stroke', //
    'stroke-opacity', 'stroke-width', 'fill', 'fill-opacity'];

    function defaultDescribe(properties, nameProperty) {
        var html = '';
        for ( var key in properties) {
            if (properties.hasOwnProperty(key)) {
                if (key === nameProperty || simpleStyleIdentifiers.indexOf(key) !== -1) {
                    continue;
                }
                var value = properties[key];
                if (defined(value)) {
                    if (typeof value === 'object') {
                        html += '<tr><th>' + key + '</th><td>' + defaultDescribe(value) + '</td></tr>';
                    } else {
                        html += '<tr><th>' + key + '</th><td>' + value + '</td></tr>';
                    }
                }
            }
        }

        if (html.length > 0) {
            html = '<table class="cesium-infoBox-defaultTable"><tbody>' + html + '</tbody></table>';
        }

        return html;
    }

    function createDescriptionCallback(describe, properties, nameProperty) {
        var description;
        return function(time, result) {
            if (!defined(description)) {
                description = describe(properties, nameProperty);
            }
            return description;
        };
    }

    function defaultDescribeProperty(properties, nameProperty) {
        return new CallbackProperty(createDescriptionCallback(defaultDescribe, properties, nameProperty), true);
    }

    //GeoJSON specifies only the Feature object has a usable id property
    //But since "multi" geometries create multiple entity,
    //we can't use it for them either.
    function createObject(geoJson, entityCollection, describe) {
        var id = geoJson.id;
        if (!defined(id) || geoJson.type !== 'Feature') {
            id = createGuid();
        } else {
            var i = 2;
            var finalId = id;
            while (defined(entityCollection.getById(finalId))) {
                finalId = id + "_" + i;
                i++;
            }
            id = finalId;
        }

        var entity = entityCollection.getOrCreateEntity(id);
        var properties = geoJson.properties;
        if (defined(properties)) {
            entity.addProperty('properties');
            entity.properties = properties;

            var nameProperty;

            //Check for the simplestyle specified name first.
            var name = properties.title;
            if (defined(name)) {
                entity.name = name;
                nameProperty = 'title';
            } else {
                //Else, find the name by selecting an appropriate property.
                //The name will be obtained based on this order:
                //1) The first case-insensitive property with the name 'title',
                //2) The first case-insensitive property with the name 'name',
                //3) The first property containing the word 'title'.
                //4) The first property containing the word 'name',
                var namePropertyPrecedence = Number.MAX_VALUE;
                for ( var key in properties) {
                    if (properties.hasOwnProperty(key) && properties[key]) {
                        var lowerKey = key.toLowerCase();

                        if (namePropertyPrecedence > 1 && lowerKey === 'title') {
                            namePropertyPrecedence = 1;
                            nameProperty = key;
                            break;
                        } else if (namePropertyPrecedence > 2 && lowerKey === 'name') {
                            namePropertyPrecedence = 2;
                            nameProperty = key;
                        } else if (namePropertyPrecedence > 3 && /title/i.test(key)) {
                            namePropertyPrecedence = 3;
                            nameProperty = key;
                        } else if (namePropertyPrecedence > 4 && /name/i.test(key)) {
                            namePropertyPrecedence = 4;
                            nameProperty = key;
                        }
                    }
                }
                if (defined(nameProperty)) {
                    entity.name = properties[nameProperty];
                }
            }

            var description = properties.description;
            if (description !== null) {
                entity.description = !defined(description) ? describe(properties, nameProperty) : new ConstantProperty(description);
            }
        }
        return entity;
    }

    function coordinatesArrayToCartesianArray(coordinates, crsFunction) {
        var positions = new Array(coordinates.length);
        for (var i = 0; i < coordinates.length; i++) {
            positions[i] = crsFunction(coordinates[i]);
        }
        return positions;
    }

    // GeoJSON processing functions
    function processFeature(dataSource, feature, notUsed, crsFunction, options) {
        if (feature.geometry === null) {
            //Null geometry is allowed, so just create an empty entity instance for it.
            createObject(feature, dataSource._entityCollection, options.describe);
            return;
        }

        if (!defined(feature.geometry)) {
            throw new RuntimeError('feature.geometry is required.');
        }

        var geometryType = feature.geometry.type;
        var geometryHandler = geometryTypes[geometryType];
        if (!defined(geometryHandler)) {
            throw new RuntimeError('Unknown geometry type: ' + geometryType);
        }
        geometryHandler(dataSource, feature, feature.geometry, crsFunction, options);
    }

    function processFeatureCollection(dataSource, featureCollection, notUsed, crsFunction, options) {
        var features = featureCollection.features;
        for (var i = 0, len = features.length; i < len; i++) {
            processFeature(dataSource, features[i], undefined, crsFunction, options);
        }
    }

    function processGeometryCollection(dataSource, geoJson, geometryCollection, crsFunction, options) {
        var geometries = geometryCollection.geometries;
        for (var i = 0, len = geometries.length; i < len; i++) {
            var geometry = geometries[i];
            var geometryType = geometry.type;
            var geometryHandler = geometryTypes[geometryType];
            if (!defined(geometryHandler)) {
                throw new RuntimeError('Unknown geometry type: ' + geometryType);
            }
            geometryHandler(dataSource, geoJson, geometry, crsFunction, options);
        }
    }

    function createPoint(dataSource, geoJson, crsFunction, coordinates, options) {
        var symbol = options.markerSymbol;
        var color = options.markerColor;
        var size = options.markerSize;

        var properties = geoJson.properties;
        if (defined(properties)) {
            var cssColor = properties['marker-color'];
            if (defined(cssColor)) {
                color = Color.fromCssColorString(cssColor);
            }

            size = defaultValue(sizes[properties['marker-size']], size);
            var markerSymbol = properties['marker-symbol'];
            if (defined(markerSymbol)) {
                symbol = markerSymbol;
            }
        }

        var canvasOrPromise;
        if (defined(symbol)) {
            if (symbol.length === 1) {
                canvasOrPromise = dataSource._pinBuilder.fromText(symbol.toUpperCase(), color, size);
            } else {
                canvasOrPromise = dataSource._pinBuilder.fromMakiIconId(symbol, color, size);
            }
        } else {
            canvasOrPromise = dataSource._pinBuilder.fromColor(color, size);
        }

        var billboard = new BillboardGraphics();
        billboard.verticalOrigin = new ConstantProperty(VerticalOrigin.BOTTOM);

        // Clamp to ground if there isn't a height specified
        if (coordinates.length === 2 && options.clampToGround) {
            billboard.heightReference = HeightReference.CLAMP_TO_GROUND;
        }

        var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
        entity.billboard = billboard;
        entity.position = new ConstantPositionProperty(crsFunction(coordinates));

        var promise = when(canvasOrPromise).then(function(image) {
            billboard.image = new ConstantProperty(image);
        }).otherwise(function() {
            billboard.image = new ConstantProperty(dataSource._pinBuilder.fromColor(color, size));
        });

        dataSource._promises.push(promise);
    }

    function processPoint(dataSource, geoJson, geometry, crsFunction, options) {
        createPoint(dataSource, geoJson, crsFunction, geometry.coordinates, options);
    }

    function processMultiPoint(dataSource, geoJson, geometry, crsFunction, options) {
        var coordinates = geometry.coordinates;
        for (var i = 0; i < coordinates.length; i++) {
            createPoint(dataSource, geoJson, crsFunction, coordinates[i], options);
        }
    }

    function createLineString(dataSource, geoJson, crsFunction, coordinates, options) {
        var material = options.strokeMaterialProperty;
        var widthProperty = options.strokeWidthProperty;

        var properties = geoJson.properties;
        if (defined(properties)) {
            var width = properties['stroke-width'];
            if (defined(width)) {
                widthProperty = new ConstantProperty(width);
            }

            var color;
            var stroke = properties.stroke;
            if (defined(stroke)) {
                color = Color.fromCssColorString(stroke);
            }
            var opacity = properties['stroke-opacity'];
            if (defined(opacity) && opacity !== 1.0) {
                if (!defined(color)) {
                    color = material.color.clone();
                }
                color.alpha = opacity;
            }
            if (defined(color)) {
                material = new ColorMaterialProperty(color);
            }
        }

        var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
        var graphics;
        if (options.clampToGround) {
            graphics = new CorridorGraphics();
            entity.corridor = graphics;
        } else {
            graphics = new PolylineGraphics();
            entity.polyline = graphics;
        }

        graphics.material = material;
        graphics.width = widthProperty;
        graphics.positions = new ConstantProperty(coordinatesArrayToCartesianArray(coordinates, crsFunction));
    }

    function processLineString(dataSource, geoJson, geometry, crsFunction, options) {
        createLineString(dataSource, geoJson, crsFunction, geometry.coordinates, options);
    }

    function processMultiLineString(dataSource, geoJson, geometry, crsFunction, options) {
        var lineStrings = geometry.coordinates;
        for (var i = 0; i < lineStrings.length; i++) {
            createLineString(dataSource, geoJson, crsFunction, lineStrings[i], options);
        }
    }

    function createPolygon(dataSource, geoJson, crsFunction, coordinates, options) {
        if (coordinates.length === 0 || coordinates[0].length === 0) {
            return;
        }

        var outlineColorProperty = options.strokeMaterialProperty.color;
        var material = options.fillMaterialProperty;
        var widthProperty = options.strokeWidthProperty;

        var properties = geoJson.properties;
        if (defined(properties)) {
            var width = properties['stroke-width'];
            if (defined(width)) {
                widthProperty = new ConstantProperty(width);
            }

            var color;
            var stroke = properties.stroke;
            if (defined(stroke)) {
                color = Color.fromCssColorString(stroke);
            }
            var opacity = properties['stroke-opacity'];
            if (defined(opacity) && opacity !== 1.0) {
                if (!defined(color)) {
                    color = options.strokeMaterialProperty.color.clone();
                }
                color.alpha = opacity;
            }

            if (defined(color)) {
                outlineColorProperty = new ConstantProperty(color);
            }

            var fillColor;
            var fill = properties.fill;
            if (defined(fill)) {
                fillColor = Color.fromCssColorString(fill);
                fillColor.alpha = material.color.alpha;
            }
            opacity = properties['fill-opacity'];
            if (defined(opacity) && opacity !== material.color.alpha) {
                if (!defined(fillColor)) {
                    fillColor = material.color.clone();
                }
                fillColor.alpha = opacity;
            }
            if (defined(fillColor)) {
                material = new ColorMaterialProperty(fillColor);
            }
        }

        var polygon = new PolygonGraphics();
        polygon.outline = new ConstantProperty(true);
        polygon.outlineColor = outlineColorProperty;
        polygon.outlineWidth = widthProperty;
        polygon.material = material;

        var holes = [];
        for (var i = 1, len = coordinates.length; i < len; i++) {
            holes.push(new PolygonHierarchy(coordinatesArrayToCartesianArray(coordinates[i], crsFunction)));
        }

        var positions = coordinates[0];
        polygon.hierarchy = new ConstantProperty(new PolygonHierarchy(coordinatesArrayToCartesianArray(positions, crsFunction), holes));
        if (positions[0].length > 2) {
            polygon.perPositionHeight = new ConstantProperty(true);
        } else if (!options.clampToGround) {
            polygon.height = 0;
        }

        var entity = createObject(geoJson, dataSource._entityCollection, options.describe);
        entity.polygon = polygon;
    }

    function processPolygon(dataSource, geoJson, geometry, crsFunction, options) {
        createPolygon(dataSource, geoJson, crsFunction, geometry.coordinates, options);
    }

    function processMultiPolygon(dataSource, geoJson, geometry, crsFunction, options) {
        var polygons = geometry.coordinates;
        for (var i = 0; i < polygons.length; i++) {
            createPolygon(dataSource, geoJson, crsFunction, polygons[i], options);
        }
    }

    function processTopology(dataSource, geoJson, geometry, crsFunction, options) {
        for ( var property in geometry.objects) {
            if (geometry.objects.hasOwnProperty(property)) {
                var feature = topojson.feature(geometry, geometry.objects[property]);
                var typeHandler = geoJsonObjectTypes[feature.type];
                typeHandler(dataSource, feature, feature, crsFunction, options);
            }
        }
    }

    var geoJsonObjectTypes = {
        Feature : processFeature,
        FeatureCollection : processFeatureCollection,
        GeometryCollection : processGeometryCollection,
        LineString : processLineString,
        MultiLineString : processMultiLineString,
        MultiPoint : processMultiPoint,
        MultiPolygon : processMultiPolygon,
        Point : processPoint,
        Polygon : processPolygon,
        Topology : processTopology
    };

    var geometryTypes = {
        GeometryCollection : processGeometryCollection,
        LineString : processLineString,
        MultiLineString : processMultiLineString,
        MultiPoint : processMultiPoint,
        MultiPolygon : processMultiPolygon,
        Point : processPoint,
        Polygon : processPolygon,
        Topology : processTopology
    };

    /**
     * A {@link DataSource} which processes both
     * {@link http://www.geojson.org/|GeoJSON} and {@link https://github.com/mbostock/topojson|TopoJSON} data.
     * {@link https://github.com/mapbox/simplestyle-spec|simplestyle-spec} properties will also be used if they
     * are present.
     *
     * @alias GeoJsonDataSource
     * @constructor
     *
     * @param {String} [name] The name of this data source.  If undefined, a name will be taken from
     *                        the name of the GeoJSON file.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20and%20TopoJSON.html|Cesium Sandcastle GeoJSON and TopoJSON Demo}
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20simplestyle.html|Cesium Sandcastle GeoJSON simplestyle Demo}
     *
     * @example
     * var viewer = new Cesium.Viewer('cesiumContainer');
     * viewer.dataSources.add(Cesium.GeoJsonDataSource.load('../../SampleData/ne_10m_us_states.topojson', {
     *   stroke: Cesium.Color.HOTPINK,
     *   fill: Cesium.Color.PINK,
     *   strokeWidth: 3,
     *   markerSymbol: '?'
     * }));
     */
    function GeoJsonDataSource(name) {
        this._name = name;
        this._changed = new Event();
        this._error = new Event();
        this._isLoading = false;
        this._loading = new Event();
        this._entityCollection = new EntityCollection(this);
        this._promises = [];
        this._pinBuilder = new PinBuilder();
        this._entityCluster = new EntityCluster();
    }

    /**
     * Creates a Promise to a new instance loaded with the provided GeoJSON or TopoJSON data.
     *
     * @param {String|Object} data A url, GeoJSON object, or TopoJSON object to be loaded.
     * @param {Object} [options] An object with the following properties:
     * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
     * @param {Number} [options.markerSize=GeoJsonDataSource.markerSize] The default size of the map pin created for each point, in pixels.
     * @param {String} [options.markerSymbol=GeoJsonDataSource.markerSymbol] The default symbol of the map pin created for each point.
     * @param {Color} [options.markerColor=GeoJsonDataSource.markerColor] The default color of the map pin created for each point.
     * @param {Color} [options.stroke=GeoJsonDataSource.stroke] The default color of polylines and polygon outlines.
     * @param {Number} [options.strokeWidth=GeoJsonDataSource.strokeWidth] The default width of polylines and polygon outlines.
     * @param {Color} [options.fill=GeoJsonDataSource.fill] The default color for polygon interiors.
     * @param {Boolean} [options.clampToGround=GeoJsonDataSource.clampToGround] true if we want the geometry features (polygons or linestrings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
     *
     * @returns {Promise.<GeoJsonDataSource>} A promise that will resolve when the data is loaded.
     */
    GeoJsonDataSource.load = function(data, options) {
        return new GeoJsonDataSource().load(data, options);
    };

    defineProperties(GeoJsonDataSource, {
        /**
         * Gets or sets the default size of the map pin created for each point, in pixels.
         * @memberof GeoJsonDataSource
         * @type {Number}
         * @default 48
         */
        markerSize : {
            get : function() {
                return defaultMarkerSize;
            },
            set : function(value) {
                defaultMarkerSize = value;
            }
        },
        /**
         * Gets or sets the default symbol of the map pin created for each point.
         * This can be any valid {@link http://mapbox.com/maki/|Maki} identifier, any single character,
         * or blank if no symbol is to be used.
         * @memberof GeoJsonDataSource
         * @type {String}
         */
        markerSymbol : {
            get : function() {
                return defaultMarkerSymbol;
            },
            set : function(value) {
                defaultMarkerSymbol = value;
            }
        },
        /**
         * Gets or sets the default color of the map pin created for each point.
         * @memberof GeoJsonDataSource
         * @type {Color}
         * @default Color.ROYALBLUE
         */
        markerColor : {
            get : function() {
                return defaultMarkerColor;
            },
            set : function(value) {
                defaultMarkerColor = value;
            }
        },
        /**
         * Gets or sets the default color of polylines and polygon outlines.
         * @memberof GeoJsonDataSource
         * @type {Color}
         * @default Color.BLACK
         */
        stroke : {
            get : function() {
                return defaultStroke;
            },
            set : function(value) {
                defaultStroke = value;
                defaultStrokeMaterialProperty.color.setValue(value);
            }
        },
        /**
         * Gets or sets the default width of polylines and polygon outlines.
         * @memberof GeoJsonDataSource
         * @type {Number}
         * @default 2.0
         */
        strokeWidth : {
            get : function() {
                return defaultStrokeWidth;
            },
            set : function(value) {
                defaultStrokeWidth = value;
                defaultStrokeWidthProperty.setValue(value);
            }
        },
        /**
         * Gets or sets default color for polygon interiors.
         * @memberof GeoJsonDataSource
         * @type {Color}
         * @default Color.YELLOW
         */
        fill : {
            get : function() {
                return defaultFill;
            },
            set : function(value) {
                defaultFill = value;
                defaultFillMaterialProperty = new ColorMaterialProperty(defaultFill);
            }
        },
        /**
         * Gets or sets default of whether to clamp to the ground.
         * @memberof GeoJsonDataSource
         * @type {Boolean}
         * @default false
         */
        clampToGround : {
            get : function() {
                return defaultClampToGround;
            },
            set : function(value) {
                defaultClampToGround = value;
            }
        },

        /**
         * Gets an object that maps the name of a crs to a callback function which takes a GeoJSON coordinate
         * and transforms it into a WGS84 Earth-fixed Cartesian.  Older versions of GeoJSON which
         * supported the EPSG type can be added to this list as well, by specifying the complete EPSG name,
         * for example 'EPSG:4326'.
         * @memberof GeoJsonDataSource
         * @type {Object}
         */
        crsNames : {
            get : function() {
                return crsNames;
            }
        },

        /**
         * Gets an object that maps the href property of a crs link to a callback function
         * which takes the crs properties object and returns a Promise that resolves
         * to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian.
         * Items in this object take precedence over those defined in <code>crsLinkHrefs</code>, assuming
         * the link has a type specified.
         * @memberof GeoJsonDataSource
         * @type {Object}
         */
        crsLinkHrefs : {
            get : function() {
                return crsLinkHrefs;
            }
        },

        /**
         * Gets an object that maps the type property of a crs link to a callback function
         * which takes the crs properties object and returns a Promise that resolves
         * to a function that takes a GeoJSON coordinate and transforms it into a WGS84 Earth-fixed Cartesian.
         * Items in <code>crsLinkHrefs</code> take precedence over this object.
         * @memberof GeoJsonDataSource
         * @type {Object}
         */
        crsLinkTypes : {
            get : function() {
                return crsLinkTypes;
            }
        }
    });

    defineProperties(GeoJsonDataSource.prototype, {
        /**
         * Gets a human-readable name for this instance.
         * @memberof GeoJsonDataSource.prototype
         * @type {String}
         */
        name : {
            get : function() {
                return this._name;
            }
        },
        /**
         * This DataSource only defines static data, therefore this property is always undefined.
         * @memberof GeoJsonDataSource.prototype
         * @type {DataSourceClock}
         */
        clock : {
            value : undefined,
            writable : false
        },
        /**
         * Gets the collection of {@link Entity} instances.
         * @memberof GeoJsonDataSource.prototype
         * @type {EntityCollection}
         */
        entities : {
            get : function() {
                return this._entityCollection;
            }
        },
        /**
         * Gets a value indicating if the data source is currently loading data.
         * @memberof GeoJsonDataSource.prototype
         * @type {Boolean}
         */
        isLoading : {
            get : function() {
                return this._isLoading;
            }
        },
        /**
         * Gets an event that will be raised when the underlying data changes.
         * @memberof GeoJsonDataSource.prototype
         * @type {Event}
         */
        changedEvent : {
            get : function() {
                return this._changed;
            }
        },
        /**
         * Gets an event that will be raised if an error is encountered during processing.
         * @memberof GeoJsonDataSource.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._error;
            }
        },
        /**
         * Gets an event that will be raised when the data source either starts or stops loading.
         * @memberof GeoJsonDataSource.prototype
         * @type {Event}
         */
        loadingEvent : {
            get : function() {
                return this._loading;
            }
        },
        /**
         * Gets whether or not this data source should be displayed.
         * @memberof GeoJsonDataSource.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._entityCollection.show;
            },
            set : function(value) {
                this._entityCollection.show = value;
            }
        },

        /**
         * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
         *
         * @memberof GeoJsonDataSource.prototype
         * @type {EntityCluster}
         */
        clustering : {
            get : function() {
                return this._entityCluster;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value must be defined.');
                }
                                this._entityCluster = value;
            }
        }
    });

    /**
     * Asynchronously loads the provided GeoJSON or TopoJSON data, replacing any existing data.
     *
     * @param {String|Object} data A url, GeoJSON object, or TopoJSON object to be loaded.
     * @param {Object} [options] An object with the following properties:
     * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links.
     * @param {GeoJsonDataSource~describe} [options.describe=GeoJsonDataSource.defaultDescribeProperty] A function which returns a Property object (or just a string),
     *                                                                                which converts the properties into an html description.
     * @param {Number} [options.markerSize=GeoJsonDataSource.markerSize] The default size of the map pin created for each point, in pixels.
     * @param {String} [options.markerSymbol=GeoJsonDataSource.markerSymbol] The default symbol of the map pin created for each point.
     * @param {Color} [options.markerColor=GeoJsonDataSource.markerColor] The default color of the map pin created for each point.
     * @param {Color} [options.stroke=GeoJsonDataSource.stroke] The default color of polylines and polygon outlines.
     * @param {Number} [options.strokeWidth=GeoJsonDataSource.strokeWidth] The default width of polylines and polygon outlines.
     * @param {Color} [options.fill=GeoJsonDataSource.fill] The default color for polygon interiors.
     * @param {Boolean} [options.clampToGround=GeoJsonDataSource.clampToGround] true if we want the features clamped to the ground.
     *
     * @returns {Promise.<GeoJsonDataSource>} a promise that will resolve when the GeoJSON is loaded.
     */
    GeoJsonDataSource.prototype.load = function(data, options) {
                if (!defined(data)) {
            throw new DeveloperError('data is required.');
        }
        
        DataSource.setLoading(this, true);

        var promise = data;
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var sourceUri = options.sourceUri;
        if (typeof data === 'string') {
            if (!defined(sourceUri)) {
                sourceUri = data;
            }
            promise = loadJson(data);
        }

        options = {
            describe: defaultValue(options.describe, defaultDescribeProperty),
            markerSize : defaultValue(options.markerSize, defaultMarkerSize),
            markerSymbol : defaultValue(options.markerSymbol, defaultMarkerSymbol),
            markerColor : defaultValue(options.markerColor, defaultMarkerColor),
            strokeWidthProperty : new ConstantProperty(defaultValue(options.strokeWidth, defaultStrokeWidth)),
            strokeMaterialProperty : new ColorMaterialProperty(defaultValue(options.stroke, defaultStroke)),
            fillMaterialProperty : new ColorMaterialProperty(defaultValue(options.fill, defaultFill)),
            clampToGround : defaultValue(options.clampToGround, defaultClampToGround)
        };

        var that = this;
        return when(promise, function(geoJson) {
            return load(that, geoJson, options, sourceUri);
        }).otherwise(function(error) {
            DataSource.setLoading(that, false);
            that._error.raiseEvent(that, error);
            console.log(error);
            return when.reject(error);
        });
    };

    function load(that, geoJson, options, sourceUri) {
        var name;
        if (defined(sourceUri)) {
            name = getFilenameFromUri(sourceUri);
        }

        if (defined(name) && that._name !== name) {
            that._name = name;
            that._changed.raiseEvent(that);
        }

        var typeHandler = geoJsonObjectTypes[geoJson.type];
        if (!defined(typeHandler)) {
            throw new RuntimeError('Unsupported GeoJSON object type: ' + geoJson.type);
        }

        //Check for a Coordinate Reference System.
        var crs = geoJson.crs;
        var crsFunction = crs !== null ? defaultCrsFunction : null;

        if (defined(crs)) {
            if (!defined(crs.properties)) {
                throw new RuntimeError('crs.properties is undefined.');
            }

            var properties = crs.properties;
            if (crs.type === 'name') {
                crsFunction = crsNames[properties.name];
                if (!defined(crsFunction)) {
                    throw new RuntimeError('Unknown crs name: ' + properties.name);
                }
            } else if (crs.type === 'link') {
                var handler = crsLinkHrefs[properties.href];
                if (!defined(handler)) {
                    handler = crsLinkTypes[properties.type];
                }

                if (!defined(handler)) {
                    throw new RuntimeError('Unable to resolve crs link: ' + JSON.stringify(properties));
                }

                crsFunction = handler(properties);
            } else if (crs.type === 'EPSG') {
                crsFunction = crsNames['EPSG:' + properties.code];
                if (!defined(crsFunction)) {
                    throw new RuntimeError('Unknown crs EPSG code: ' + properties.code);
                }
            } else {
                throw new RuntimeError('Unknown crs type: ' + crs.type);
            }
        }

        return when(crsFunction, function(crsFunction) {
            that._entityCollection.removeAll();

            // null is a valid value for the crs, but means the entire load process becomes a no-op
            // because we can't assume anything about the coordinates.
            if (crsFunction !== null) {
                typeHandler(that, geoJson, geoJson, crsFunction, options);
            }

            return when.all(that._promises, function() {
                that._promises.length = 0;
                DataSource.setLoading(that, false);
                return that;
            });
        });
    }

    /**
     * This callback is displayed as part of the GeoJsonDataSource class.
     * @callback GeoJsonDataSource~describe
     * @param {Object} properties The properties of the feature.
     * @param {String} nameProperty The property key that Cesium estimates to have the name of the feature.
     */

    return GeoJsonDataSource;
});

/*global define*/
define('DataSources/GeometryUpdater',[
        '../Core/defineProperties',
        '../Core/DeveloperError'
    ], function(
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Defines the interface for a geometry updater.  A GeometryUpdater maps
     * geometry defined as part of a {@link Entity} into {@link Geometry}
     * instances.  These instances are then visualized by {@link GeometryVisualizer}.
     *
     * This type defines an interface and cannot be instantiated directly.
     *
     * @alias GeometryUpdater
     * @constructor
     *
     * @param {Entity} entity The entity containing the geometry to be visualized.
     * @param {Scene} scene The scene where visualization is taking place.
     *
     * @see EllipseGeometryUpdater
     * @see EllipsoidGeometryUpdater
     * @see PolygonGeometryUpdater
     * @see PolylineGeometryUpdater
     * @see RectangleGeometryUpdater
     * @see WallGeometryUpdater
     */
    function GeometryUpdater(entity, scene) {
        DeveloperError.throwInstantiationError();
    }

    defineProperties(GeometryUpdater, {
        /**
         * Gets the type of Appearance to use for simple color-based geometry.
         * @memberof GeometryUpdater
         * @type {Appearance}
         */
        perInstanceColorAppearanceType : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the type of Appearance to use for material-based geometry.
         * @memberof GeometryUpdater
         * @type {Appearance}
         */
        materialAppearanceType : {
            get : DeveloperError.throwInstantiationError
        }
    });

    defineProperties(GeometryUpdater.prototype, {
        /**
         * Gets the entity associated with this geometry.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Entity}
         * @readonly
         */
        entity : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if the geometry has a fill component.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        fillEnabled : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if fill visibility varies with simulation time.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantFill : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the material property used to fill the geometry.
         * @memberof GeometryUpdater.prototype
         *
         * @type {MaterialProperty}
         * @readonly
         */
        fillMaterialProperty : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if the geometry has an outline component.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        outlineEnabled : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if outline visibility varies with simulation time.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        hasConstantOutline : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the {@link Color} property for the geometry outline.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Property}
         * @readonly
         */
        outlineColorProperty : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets the constant with of the geometry outline, in pixels.
         * This value is only valid if isDynamic is false.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Number}
         * @readonly
         */
        outlineWidth : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if the geometry is time-varying.
         * If true, all visualization is delegated to the {@link DynamicGeometryUpdater}
         * returned by GeometryUpdater#createDynamicUpdater.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isDynamic : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets a value indicating if the geometry is closed.
         * This property is only valid for static geometry.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isClosed : {
            get : DeveloperError.throwInstantiationError
        },
        /**
         * Gets an event that is raised whenever the public properties
         * of this updater change.
         * @memberof GeometryUpdater.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        geometryChanged : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Checks if the geometry is outlined at the provided time.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is outlined at the provided time, false otherwise.
     */
    GeometryUpdater.prototype.isOutlineVisible = DeveloperError.throwInstantiationError;

    /**
     * Checks if the geometry is filled at the provided time.
     * @function
     *
     * @param {JulianDate} time The time for which to retrieve visibility.
     * @returns {Boolean} true if geometry is filled at the provided time, false otherwise.
     */
    GeometryUpdater.prototype.isFilled = DeveloperError.throwInstantiationError;

    /**
     * Creates the geometry instance which represents the fill of the geometry.
     * @function
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the filled portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent a filled geometry.
     */
    GeometryUpdater.prototype.createFillGeometryInstance = DeveloperError.throwInstantiationError;

    /**
     * Creates the geometry instance which represents the outline of the geometry.
     * @function
     *
     * @param {JulianDate} time The time to use when retrieving initial attribute values.
     * @returns {GeometryInstance} The geometry instance representing the outline portion of the geometry.
     *
     * @exception {DeveloperError} This instance does not represent an outlined geometry.
     */
    GeometryUpdater.prototype.createOutlineGeometryInstance = DeveloperError.throwInstantiationError;

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * @function
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    GeometryUpdater.prototype.isDestroyed = DeveloperError.throwInstantiationError;

    /**
     * Destroys and resources used by the object.  Once an object is destroyed, it should not be used.
     * @function
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    GeometryUpdater.prototype.destroy = DeveloperError.throwInstantiationError;

    /**
     * Creates the dynamic updater to be used when GeometryUpdater#isDynamic is true.
     * @function
     *
     * @param {PrimitiveCollection} primitives The primitive collection to use.
     * @returns {DynamicGeometryUpdater} The dynamic updater used to update the geometry each frame.
     *
     * @exception {DeveloperError} This instance does not represent dynamic geometry.
     */
    GeometryUpdater.prototype.createDynamicUpdater = DeveloperError.throwInstantiationError;

    return GeometryUpdater;
});

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module unless amdModuleId is set
    define('ThirdParty/Autolinker',[], function () {
      return (root['Autolinker'] = factory());
    });
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory();
  } else {
    root['Autolinker'] = factory();
  }
}(this, function () {

/*!
 * Autolinker.js
 * 0.17.1
 *
 * Copyright(c) 2015 Gregory Jacobs <greg@greg-jacobs.com>
 * MIT Licensed. http://www.opensource.org/licenses/mit-license.php
 *
 * https://github.com/gregjacobs/Autolinker.js
 */
/**
 * @class Autolinker
 * @extends Object
 *
 * Utility class used to process a given string of text, and wrap the matches in
 * the appropriate anchor (&lt;a&gt;) tags to turn them into links.
 *
 * Any of the configuration options may be provided in an Object (map) provided
 * to the Autolinker constructor, which will configure how the {@link #link link()}
 * method will process the links.
 *
 * For example:
 *
 *     var autolinker = new Autolinker( {
 *         newWindow : false,
 *         truncate  : 30
 *     } );
 *
 *     var html = autolinker.link( "Joe went to www.yahoo.com" );
 *     // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
 *
 *
 * The {@link #static-link static link()} method may also be used to inline options into a single call, which may
 * be more convenient for one-off uses. For example:
 *
 *     var html = Autolinker.link( "Joe went to www.yahoo.com", {
 *         newWindow : false,
 *         truncate  : 30
 *     } );
 *     // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
 *
 *
 * ## Custom Replacements of Links
 *
 * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
 * may be provided to fully customize the output of Autolinker. This function is
 * called once for each URL/Email/Phone#/Twitter Handle/Hashtag match that is
 * encountered.
 *
 * For example:
 *
 *     var input = "...";  // string with URLs, Email Addresses, Phone #s, Twitter Handles, and Hashtags
 *
 *     var linkedText = Autolinker.link( input, {
 *         replaceFn : function( autolinker, match ) {
 *             console.log( "href = ", match.getAnchorHref() );
 *             console.log( "text = ", match.getAnchorText() );
 *
 *             switch( match.getType() ) {
 *                 case 'url' :
 *                     console.log( "url: ", match.getUrl() );
 *
 *                     if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
 *                         var tag = autolinker.getTagBuilder().build( match );  // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
 *                         tag.setAttr( 'rel', 'nofollow' );
 *                         tag.addClass( 'external-link' );
 *
 *                         return tag;
 *
 *                     } else {
 *                         return true;  // let Autolinker perform its normal anchor tag replacement
 *                     }
 *
 *                 case 'email' :
 *                     var email = match.getEmail();
 *                     console.log( "email: ", email );
 *
 *                     if( email === "my@own.address" ) {
 *                         return false;  // don't auto-link this particular email address; leave as-is
 *                     } else {
 *                         return;  // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
 *                     }
 *
 *                 case 'phone' :
 *                     var phoneNumber = match.getPhoneNumber();
 *                     console.log( phoneNumber );
 *
 *                     return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
 *
 *                 case 'twitter' :
 *                     var twitterHandle = match.getTwitterHandle();
 *                     console.log( twitterHandle );
 *
 *                     return '<a href="http://newplace.to.link.twitter.handles.to/">' + twitterHandle + '</a>';
 *
 *                 case 'hashtag' :
 *                     var hashtag = match.getHashtag();
 *                     console.log( hashtag );
 *
 *                     return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
 *             }
 *         }
 *     } );
 *
 *
 * The function may return the following values:
 *
 * - `true` (Boolean): Allow Autolinker to replace the match as it normally would.
 * - `false` (Boolean): Do not replace the current match at all - leave as-is.
 * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for
 *   the match.
 * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text.
 *
 * @constructor
 * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map).
 */
var Autolinker = function( cfg ) {
	Autolinker.Util.assign( this, cfg );  // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs.

	// Validate the value of the `hashtag` cfg.
	var hashtag = this.hashtag;
	if( hashtag !== false && hashtag !== 'twitter' && hashtag !== 'facebook' ) {
		throw new Error( "invalid `hashtag` cfg - see docs" );
	}
};

Autolinker.prototype = {
	constructor : Autolinker,  // fix constructor property

	/**
	 * @cfg {Boolean} urls
	 *
	 * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be.
	 */
	urls : true,

	/**
	 * @cfg {Boolean} email
	 *
	 * `true` if email addresses should be automatically linked, `false` if they should not be.
	 */
	email : true,

	/**
	 * @cfg {Boolean} twitter
	 *
	 * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be.
	 */
	twitter : true,

	/**
	 * @cfg {Boolean} phone
	 *
	 * `true` if Phone numbers ("(555)555-5555") should be automatically linked, `false` if they should not be.
	 */
	phone: true,

	/**
	 * @cfg {Boolean/String} hashtag
	 *
	 * A string for the service name to have hashtags (ex: "#myHashtag")
	 * auto-linked to. The currently-supported values are:
	 *
	 * - 'twitter'
	 * - 'facebook'
	 *
	 * Pass `false` to skip auto-linking of hashtags.
	 */
	hashtag : false,

	/**
	 * @cfg {Boolean} newWindow
	 *
	 * `true` if the links should open in a new window, `false` otherwise.
	 */
	newWindow : true,

	/**
	 * @cfg {Boolean} stripPrefix
	 *
	 * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped
	 * from the beginning of URL links' text, `false` otherwise.
	 */
	stripPrefix : true,

	/**
	 * @cfg {Number} truncate
	 *
	 * A number for how many characters long matched text should be truncated to inside the text of
	 * a link. If the matched text is over this number of characters, it will be truncated to this length by
	 * adding a two period ellipsis ('..') to the end of the string.
	 *
	 * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look
	 * something like this: 'yahoo.com/some/long/pat..'
	 */
	truncate : undefined,

	/**
	 * @cfg {String} className
	 *
	 * A CSS class name to add to the generated links. This class will be added to all links, as well as this class
	 * plus match suffixes for styling url/email/phone/twitter/hashtag links differently.
	 *
	 * For example, if this config is provided as "myLink", then:
	 *
	 * - URL links will have the CSS classes: "myLink myLink-url"
	 * - Email links will have the CSS classes: "myLink myLink-email", and
	 * - Twitter links will have the CSS classes: "myLink myLink-twitter"
	 * - Phone links will have the CSS classes: "myLink myLink-phone"
	 * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
	 */
	className : "",

	/**
	 * @cfg {Function} replaceFn
	 *
	 * A function to individually process each match found in the input string.
	 *
	 * See the class's description for usage.
	 *
	 * This function is called with the following parameters:
	 *
	 * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such
	 *   as the instance's {@link #getTagBuilder tag builder}).
	 * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the
	 *   match that the `replaceFn` is currently processing. See {@link Autolinker.match.Match} subclasses for details.
	 */


	/**
	 * @private
	 * @property {Autolinker.htmlParser.HtmlParser} htmlParser
	 *
	 * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated
	 * in the {@link #getHtmlParser} method.
	 */
	htmlParser : undefined,

	/**
	 * @private
	 * @property {Autolinker.matchParser.MatchParser} matchParser
	 *
	 * The MatchParser instance used to find matches in the text nodes of an input string passed to
	 * {@link #link}. This is lazily instantiated in the {@link #getMatchParser} method.
	 */
	matchParser : undefined,

	/**
	 * @private
	 * @property {Autolinker.AnchorTagBuilder} tagBuilder
	 *
	 * The AnchorTagBuilder instance used to build match replacement anchor tags. Note: this is lazily instantiated
	 * in the {@link #getTagBuilder} method.
	 */
	tagBuilder : undefined,

	/**
	 * Automatically links URLs, Email addresses, Phone numbers, Twitter
	 * handles, and Hashtags found in the given chunk of HTML. Does not link
	 * URLs found within HTML tags.
	 *
	 * For instance, if given the text: `You should go to http://www.yahoo.com`,
	 * then the result will be `You should go to
	 * &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
	 *
	 * This method finds the text around any HTML elements in the input
	 * `textOrHtml`, which will be the text that is processed. Any original HTML
	 * elements will be left as-is, as well as the text that is already wrapped
	 * in anchor (&lt;a&gt;) tags.
	 *
	 * @param {String} textOrHtml The HTML or text to autolink matches within
	 *   (depending on if the {@link #urls}, {@link #email}, {@link #phone},
	 *   {@link #twitter}, and {@link #hashtag} options are enabled).
	 * @return {String} The HTML, with matches automatically linked.
	 */
	link : function( textOrHtml ) {
		var htmlParser = this.getHtmlParser(),
		    htmlNodes = htmlParser.parse( textOrHtml ),
		    anchorTagStackCount = 0,  // used to only process text around anchor tags, and any inner text/html they may have
		    resultHtml = [];

		for( var i = 0, len = htmlNodes.length; i < len; i++ ) {
			var node = htmlNodes[ i ],
			    nodeType = node.getType(),
			    nodeText = node.getText();

			if( nodeType === 'element' ) {
				// Process HTML nodes in the input `textOrHtml`
				if( node.getTagName() === 'a' ) {
					if( !node.isClosing() ) {  // it's the start <a> tag
						anchorTagStackCount++;
					} else {   // it's the end </a> tag
						anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 );  // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
					}
				}
				resultHtml.push( nodeText );  // now add the text of the tag itself verbatim

			} else if( nodeType === 'entity' || nodeType === 'comment' ) {
				resultHtml.push( nodeText );  // append HTML entity nodes (such as '&nbsp;') or HTML comments (such as '<!-- Comment -->') verbatim

			} else {
				// Process text nodes in the input `textOrHtml`
				if( anchorTagStackCount === 0 ) {
					// If we're not within an <a> tag, process the text node to linkify
					var linkifiedStr = this.linkifyStr( nodeText );
					resultHtml.push( linkifiedStr );

				} else {
					// `text` is within an <a> tag, simply append the text - we do not want to autolink anything
					// already within an <a>...</a> tag
					resultHtml.push( nodeText );
				}
			}
		}

		return resultHtml.join( "" );
	},

	/**
	 * Process the text that lies in between HTML tags, performing the anchor
	 * tag replacements for the matches, and returns the string with the
	 * replacements made.
	 *
	 * This method does the actual wrapping of matches with anchor tags.
	 *
	 * @private
	 * @param {String} str The string of text to auto-link.
	 * @return {String} The text with anchor tags auto-filled.
	 */
	linkifyStr : function( str ) {
		return this.getMatchParser().replace( str, this.createMatchReturnVal, this );
	},


	/**
	 * Creates the return string value for a given match in the input string,
	 * for the {@link #linkifyStr} method.
	 *
	 * This method handles the {@link #replaceFn}, if one was provided.
	 *
	 * @private
	 * @param {Autolinker.match.Match} match The Match object that represents the match.
	 * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but
	 *   may be the `matchStr` itself if the match is not to be replaced.
	 */
	createMatchReturnVal : function( match ) {
		// Handle a custom `replaceFn` being provided
		var replaceFnResult;
		if( this.replaceFn ) {
			replaceFnResult = this.replaceFn.call( this, this, match );  // Autolinker instance is the context, and the first arg
		}

		if( typeof replaceFnResult === 'string' ) {
			return replaceFnResult;  // `replaceFn` returned a string, use that

		} else if( replaceFnResult === false ) {
			return match.getMatchedText();  // no replacement for the match

		} else if( replaceFnResult instanceof Autolinker.HtmlTag ) {
			return replaceFnResult.toAnchorString();

		} else {  // replaceFnResult === true, or no/unknown return value from function
			// Perform Autolinker's default anchor tag generation
			var tagBuilder = this.getTagBuilder(),
			    anchorTag = tagBuilder.build( match );  // returns an Autolinker.HtmlTag instance

			return anchorTag.toAnchorString();
		}
	},


	/**
	 * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance.
	 *
	 * @protected
	 * @return {Autolinker.htmlParser.HtmlParser}
	 */
	getHtmlParser : function() {
		var htmlParser = this.htmlParser;

		if( !htmlParser ) {
			htmlParser = this.htmlParser = new Autolinker.htmlParser.HtmlParser();
		}

		return htmlParser;
	},


	/**
	 * Lazily instantiates and returns the {@link #matchParser} instance for this Autolinker instance.
	 *
	 * @protected
	 * @return {Autolinker.matchParser.MatchParser}
	 */
	getMatchParser : function() {
		var matchParser = this.matchParser;

		if( !matchParser ) {
			matchParser = this.matchParser = new Autolinker.matchParser.MatchParser( {
				urls        : this.urls,
				email       : this.email,
				twitter     : this.twitter,
				phone       : this.phone,
				hashtag     : this.hashtag,
				stripPrefix : this.stripPrefix
			} );
		}

		return matchParser;
	},


	/**
	 * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it
	 * if it does not yet exist.
	 *
	 * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that
	 * Autolinker would normally generate, and then allow for modifications before returning it. For example:
	 *
	 *     var html = Autolinker.link( "Test google.com", {
	 *         replaceFn : function( autolinker, match ) {
	 *             var tag = autolinker.getTagBuilder().build( match );  // returns an {@link Autolinker.HtmlTag} instance
	 *             tag.setAttr( 'rel', 'nofollow' );
	 *
	 *             return tag;
	 *         }
	 *     } );
	 *
	 *     // generated html:
	 *     //   Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
	 *
	 * @return {Autolinker.AnchorTagBuilder}
	 */
	getTagBuilder : function() {
		var tagBuilder = this.tagBuilder;

		if( !tagBuilder ) {
			tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( {
				newWindow   : this.newWindow,
				truncate    : this.truncate,
				className   : this.className
			} );
		}

		return tagBuilder;
	}

};


/**
 * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
 * and Hashtags found in the given chunk of HTML. Does not link URLs found
 * within HTML tags.
 *
 * For instance, if given the text: `You should go to http://www.yahoo.com`,
 * then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
 *
 * Example:
 *
 *     var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
 *     // Produces: "Go to <a href="http://google.com">google.com</a>"
 *
 * @static
 * @param {String} textOrHtml The HTML or text to find matches within (depending
 *   on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #twitter},
 *   and {@link #hashtag} options are enabled).
 * @param {Object} [options] Any of the configuration options for the Autolinker
 *   class, specified in an Object (map). See the class description for an
 *   example call.
 * @return {String} The HTML text, with matches automatically linked.
 */
Autolinker.link = function( textOrHtml, options ) {
	var autolinker = new Autolinker( options );
	return autolinker.link( textOrHtml );
};


// Autolinker Namespaces
Autolinker.match = {};
Autolinker.htmlParser = {};
Autolinker.matchParser = {};

/*global Autolinker */
/*jshint eqnull:true, boss:true */
/**
 * @class Autolinker.Util
 * @singleton
 *
 * A few utility methods for Autolinker.
 */
Autolinker.Util = {

	/**
	 * @property {Function} abstractMethod
	 *
	 * A function object which represents an abstract method.
	 */
	abstractMethod : function() { throw "abstract"; },


	/**
	 * @private
	 * @property {RegExp} trimRegex
	 *
	 * The regular expression used to trim the leading and trailing whitespace
	 * from a string.
	 */
	trimRegex : /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,


	/**
	 * Assigns (shallow copies) the properties of `src` onto `dest`.
	 *
	 * @param {Object} dest The destination object.
	 * @param {Object} src The source object.
	 * @return {Object} The destination object (`dest`)
	 */
	assign : function( dest, src ) {
		for( var prop in src ) {
			if( src.hasOwnProperty( prop ) ) {
				dest[ prop ] = src[ prop ];
			}
		}

		return dest;
	},


	/**
	 * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype.
	 *
	 * @param {Function} superclass The constructor function for the superclass.
	 * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the
	 *   special property `constructor`, which will be used as the new subclass's constructor function.
	 * @return {Function} The new subclass function.
	 */
	extend : function( superclass, protoProps ) {
		var superclassProto = superclass.prototype;

		var F = function() {};
		F.prototype = superclassProto;

		var subclass;
		if( protoProps.hasOwnProperty( 'constructor' ) ) {
			subclass = protoProps.constructor;
		} else {
			subclass = function() { superclassProto.constructor.apply( this, arguments ); };
		}

		var subclassProto = subclass.prototype = new F();  // set up prototype chain
		subclassProto.constructor = subclass;  // fix constructor property
		subclassProto.superclass = superclassProto;

		delete protoProps.constructor;  // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there
		Autolinker.Util.assign( subclassProto, protoProps );

		return subclass;
	},


	/**
	 * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the
	 * end of the string (by default, two periods: '..'). If the `str` length does not exceed
	 * `len`, the string will be returned unchanged.
	 *
	 * @param {String} str The string to truncate and add an ellipsis to.
	 * @param {Number} truncateLen The length to truncate the string at.
	 * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str`
	 *   when truncated. Defaults to '..'
	 */
	ellipsis : function( str, truncateLen, ellipsisChars ) {
		if( str.length > truncateLen ) {
			ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars;
			str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars;
		}
		return str;
	},


	/**
	 * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below).
	 *
	 * @param {Array} arr The array to find an element of.
	 * @param {*} element The element to find in the array, and return the index of.
	 * @return {Number} The index of the `element`, or -1 if it was not found.
	 */
	indexOf : function( arr, element ) {
		if( Array.prototype.indexOf ) {
			return arr.indexOf( element );

		} else {
			for( var i = 0, len = arr.length; i < len; i++ ) {
				if( arr[ i ] === element ) return i;
			}
			return -1;
		}
	},



	/**
	 * Performs the functionality of what modern browsers do when `String.prototype.split()` is called
	 * with a regular expression that contains capturing parenthesis.
	 *
	 * For example:
	 *
	 *     // Modern browsers:
	 *     "a,b,c".split( /(,)/ );  // --> [ 'a', ',', 'b', ',', 'c' ]
	 *
	 *     // Old IE (including IE8):
	 *     "a,b,c".split( /(,)/ );  // --> [ 'a', 'b', 'c' ]
	 *
	 * This method emulates the functionality of modern browsers for the old IE case.
	 *
	 * @param {String} str The string to split.
	 * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting
	 *   character(s) will be spliced into the array, as in the "modern browsers" example in the
	 *   description of this method.
	 *   Note #1: the supplied regular expression **must** have the 'g' flag specified.
	 *   Note #2: for simplicity's sake, the regular expression does not need
	 *   to contain capturing parenthesis - it will be assumed that any match has them.
	 * @return {String[]} The split array of strings, with the splitting character(s) included.
	 */
	splitAndCapture : function( str, splitRegex ) {
		if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" );

		var result = [],
		    lastIdx = 0,
		    match;

		while( match = splitRegex.exec( str ) ) {
			result.push( str.substring( lastIdx, match.index ) );
			result.push( match[ 0 ] );  // push the splitting char(s)

			lastIdx = match.index + match[ 0 ].length;
		}
		result.push( str.substring( lastIdx ) );

		return result;
	},


	/**
	 * Trims the leading and trailing whitespace from a string.
	 *
	 * @param {String} str The string to trim.
	 * @return {String}
	 */
	trim : function( str ) {
		return str.replace( this.trimRegex, '' );
	}

};
/*global Autolinker */
/*jshint boss:true */
/**
 * @class Autolinker.HtmlTag
 * @extends Object
 *
 * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.
 *
 * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use
 * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.
 *
 * ## Examples
 *
 * Example instantiation:
 *
 *     var tag = new Autolinker.HtmlTag( {
 *         tagName : 'a',
 *         attrs   : { 'href': 'http://google.com', 'class': 'external-link' },
 *         innerHtml : 'Google'
 *     } );
 *
 *     tag.toAnchorString();  // <a href="http://google.com" class="external-link">Google</a>
 *
 *     // Individual accessor methods
 *     tag.getTagName();                 // 'a'
 *     tag.getAttr( 'href' );            // 'http://google.com'
 *     tag.hasClass( 'external-link' );  // true
 *
 *
 * Using mutator methods (which may be used in combination with instantiation config properties):
 *
 *     var tag = new Autolinker.HtmlTag();
 *     tag.setTagName( 'a' );
 *     tag.setAttr( 'href', 'http://google.com' );
 *     tag.addClass( 'external-link' );
 *     tag.setInnerHtml( 'Google' );
 *
 *     tag.getTagName();                 // 'a'
 *     tag.getAttr( 'href' );            // 'http://google.com'
 *     tag.hasClass( 'external-link' );  // true
 *
 *     tag.toAnchorString();  // <a href="http://google.com" class="external-link">Google</a>
 *
 *
 * ## Example use within a {@link Autolinker#replaceFn replaceFn}
 *
 *     var html = Autolinker.link( "Test google.com", {
 *         replaceFn : function( autolinker, match ) {
 *             var tag = autolinker.getTagBuilder().build( match );  // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text
 *             tag.setAttr( 'rel', 'nofollow' );
 *
 *             return tag;
 *         }
 *     } );
 *
 *     // generated html:
 *     //   Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
 *
 *
 * ## Example use with a new tag for the replacement
 *
 *     var html = Autolinker.link( "Test google.com", {
 *         replaceFn : function( autolinker, match ) {
 *             var tag = new Autolinker.HtmlTag( {
 *                 tagName : 'button',
 *                 attrs   : { 'title': 'Load URL: ' + match.getAnchorHref() },
 *                 innerHtml : 'Load URL: ' + match.getAnchorText()
 *             } );
 *
 *             return tag;
 *         }
 *     } );
 *
 *     // generated html:
 *     //   Test <button title="Load URL: http://google.com">Load URL: google.com</button>
 */
Autolinker.HtmlTag = Autolinker.Util.extend( Object, {

	/**
	 * @cfg {String} tagName
	 *
	 * The tag name. Ex: 'a', 'button', etc.
	 *
	 * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString}
	 * is executed.
	 */

	/**
	 * @cfg {Object.<String, String>} attrs
	 *
	 * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the
	 * values are the attribute values.
	 */

	/**
	 * @cfg {String} innerHtml
	 *
	 * The inner HTML for the tag.
	 *
	 * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym
	 * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML}
	 * if you prefer, but this one is recommended.
	 */

	/**
	 * @cfg {String} innerHTML
	 *
	 * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version
	 * for acronym names.
	 */


	/**
	 * @protected
	 * @property {RegExp} whitespaceRegex
	 *
	 * Regular expression used to match whitespace in a string of CSS classes.
	 */
	whitespaceRegex : /\s+/,


	/**
	 * @constructor
	 * @param {Object} [cfg] The configuration properties for this class, in an Object (map)
	 */
	constructor : function( cfg ) {
		Autolinker.Util.assign( this, cfg );

		this.innerHtml = this.innerHtml || this.innerHTML;  // accept either the camelCased form or the fully capitalized acronym
	},


	/**
	 * Sets the tag name that will be used to generate the tag with.
	 *
	 * @param {String} tagName
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	setTagName : function( tagName ) {
		this.tagName = tagName;
		return this;
	},


	/**
	 * Retrieves the tag name.
	 *
	 * @return {String}
	 */
	getTagName : function() {
		return this.tagName || "";
	},


	/**
	 * Sets an attribute on the HtmlTag.
	 *
	 * @param {String} attrName The attribute name to set.
	 * @param {String} attrValue The attribute value to set.
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	setAttr : function( attrName, attrValue ) {
		var tagAttrs = this.getAttrs();
		tagAttrs[ attrName ] = attrValue;

		return this;
	},


	/**
	 * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.
	 *
	 * @param {String} name The attribute name to retrieve.
	 * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
	 */
	getAttr : function( attrName ) {
		return this.getAttrs()[ attrName ];
	},


	/**
	 * Sets one or more attributes on the HtmlTag.
	 *
	 * @param {Object.<String, String>} attrs A key/value Object (map) of the attributes to set.
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	setAttrs : function( attrs ) {
		var tagAttrs = this.getAttrs();
		Autolinker.Util.assign( tagAttrs, attrs );

		return this;
	},


	/**
	 * Retrieves the attributes Object (map) for the HtmlTag.
	 *
	 * @return {Object.<String, String>} A key/value object of the attributes for the HtmlTag.
	 */
	getAttrs : function() {
		return this.attrs || ( this.attrs = {} );
	},


	/**
	 * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.
	 *
	 * @param {String} cssClass One or more space-separated CSS classes to set (overwrite).
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	setClass : function( cssClass ) {
		return this.setAttr( 'class', cssClass );
	},


	/**
	 * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.
	 *
	 * @param {String} cssClass One or more space-separated CSS classes to add.
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	addClass : function( cssClass ) {
		var classAttr = this.getClass(),
		    whitespaceRegex = this.whitespaceRegex,
		    indexOf = Autolinker.Util.indexOf,  // to support IE8 and below
		    classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),
		    newClasses = cssClass.split( whitespaceRegex ),
		    newClass;

		while( newClass = newClasses.shift() ) {
			if( indexOf( classes, newClass ) === -1 ) {
				classes.push( newClass );
			}
		}

		this.getAttrs()[ 'class' ] = classes.join( " " );
		return this;
	},


	/**
	 * Convenience method to remove one or more CSS classes from the HtmlTag.
	 *
	 * @param {String} cssClass One or more space-separated CSS classes to remove.
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	removeClass : function( cssClass ) {
		var classAttr = this.getClass(),
		    whitespaceRegex = this.whitespaceRegex,
		    indexOf = Autolinker.Util.indexOf,  // to support IE8 and below
		    classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ),
		    removeClasses = cssClass.split( whitespaceRegex ),
		    removeClass;

		while( classes.length && ( removeClass = removeClasses.shift() ) ) {
			var idx = indexOf( classes, removeClass );
			if( idx !== -1 ) {
				classes.splice( idx, 1 );
			}
		}

		this.getAttrs()[ 'class' ] = classes.join( " " );
		return this;
	},


	/**
	 * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when
	 * there are multiple.
	 *
	 * @return {String}
	 */
	getClass : function() {
		return this.getAttrs()[ 'class' ] || "";
	},


	/**
	 * Convenience method to check if the tag has a CSS class or not.
	 *
	 * @param {String} cssClass The CSS class to check for.
	 * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.
	 */
	hasClass : function( cssClass ) {
		return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1;
	},


	/**
	 * Sets the inner HTML for the tag.
	 *
	 * @param {String} html The inner HTML to set.
	 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
	 */
	setInnerHtml : function( html ) {
		this.innerHtml = html;

		return this;
	},


	/**
	 * Retrieves the inner HTML for the tag.
	 *
	 * @return {String}
	 */
	getInnerHtml : function() {
		return this.innerHtml || "";
	},


	/**
	 * Override of superclass method used to generate the HTML string for the tag.
	 *
	 * @return {String}
	 */
	toAnchorString : function() {
		var tagName = this.getTagName(),
		    attrsStr = this.buildAttrsStr();

		attrsStr = ( attrsStr ) ? ' ' + attrsStr : '';  // prepend a space if there are actually attributes

		return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '</', tagName, '>' ].join( "" );
	},


	/**
	 * Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate
	 * the stringified HtmlTag.
	 *
	 * @protected
	 * @return {String} Example return: `attr1="value1" attr2="value2"`
	 */
	buildAttrsStr : function() {
		if( !this.attrs ) return "";  // no `attrs` Object (map) has been set, return empty string

		var attrs = this.getAttrs(),
		    attrsArr = [];

		for( var prop in attrs ) {
			if( attrs.hasOwnProperty( prop ) ) {
				attrsArr.push( prop + '="' + attrs[ prop ] + '"' );
			}
		}
		return attrsArr.join( " " );
	}

} );

/*global Autolinker */
/*jshint sub:true */
/**
 * @protected
 * @class Autolinker.AnchorTagBuilder
 * @extends Object
 *
 * Builds anchor (&lt;a&gt;) tags for the Autolinker utility when a match is found.
 *
 * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may
 * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances
 * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example:
 *
 *     var html = Autolinker.link( "Test google.com", {
 *         replaceFn : function( autolinker, match ) {
 *             var tag = autolinker.getTagBuilder().build( match );  // returns an {@link Autolinker.HtmlTag} instance
 *             tag.setAttr( 'rel', 'nofollow' );
 *
 *             return tag;
 *         }
 *     } );
 *
 *     // generated html:
 *     //   Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
 */
Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, {

	/**
	 * @cfg {Boolean} newWindow
	 * @inheritdoc Autolinker#newWindow
	 */

	/**
	 * @cfg {Number} truncate
	 * @inheritdoc Autolinker#truncate
	 */

	/**
	 * @cfg {String} className
	 * @inheritdoc Autolinker#className
	 */


	/**
	 * @constructor
	 * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
	 */
	constructor : function( cfg ) {
		Autolinker.Util.assign( this, cfg );
	},


	/**
	 * Generates the actual anchor (&lt;a&gt;) tag to use in place of the
	 * matched text, via its `match` object.
	 *
	 * @param {Autolinker.match.Match} match The Match instance to generate an
	 *   anchor tag from.
	 * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag.
	 */
	build : function( match ) {
		var tag = new Autolinker.HtmlTag( {
			tagName   : 'a',
			attrs     : this.createAttrs( match.getType(), match.getAnchorHref() ),
			innerHtml : this.processAnchorText( match.getAnchorText() )
		} );

		return tag;
	},


	/**
	 * Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
	 *   tag being generated.
	 *
	 * @protected
	 * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of
	 *   match that an anchor tag is being generated for.
	 * @param {String} href The href for the anchor tag.
	 * @return {Object} A key/value Object (map) of the anchor tag's attributes.
	 */
	createAttrs : function( matchType, anchorHref ) {
		var attrs = {
			'href' : anchorHref  // we'll always have the `href` attribute
		};

		var cssClass = this.createCssClass( matchType );
		if( cssClass ) {
			attrs[ 'class' ] = cssClass;
		}
		if( this.newWindow ) {
			attrs[ 'target' ] = "_blank";
		}

		return attrs;
	},


	/**
	 * Creates the CSS class that will be used for a given anchor tag, based on
	 * the `matchType` and the {@link #className} config.
	 *
	 * @private
	 * @param {"url"/"email"/"phone"/"twitter"/"hashtag"} matchType The type of
	 *   match that an anchor tag is being generated for.
	 * @return {String} The CSS class string for the link. Example return:
	 *   "myLink myLink-url". If no {@link #className} was configured, returns
	 *   an empty string.
	 */
	createCssClass : function( matchType ) {
		var className = this.className;

		if( !className )
			return "";
		else
			return className + " " + className + "-" + matchType;  // ex: "myLink myLink-url", "myLink myLink-email", "myLink myLink-phone", "myLink myLink-twitter", or "myLink myLink-hashtag"
	},


	/**
	 * Processes the `anchorText` by truncating the text according to the
	 * {@link #truncate} config.
	 *
	 * @private
	 * @param {String} anchorText The anchor tag's text (i.e. what will be
	 *   displayed).
	 * @return {String} The processed `anchorText`.
	 */
	processAnchorText : function( anchorText ) {
		anchorText = this.doTruncate( anchorText );

		return anchorText;
	},


	/**
	 * Performs the truncation of the `anchorText`, if the `anchorText` is
	 * longer than the {@link #truncate} option. Truncates the text to 2
	 * characters fewer than the {@link #truncate} option, and adds ".." to the
	 * end.
	 *
	 * @private
	 * @param {String} text The anchor tag's text (i.e. what will be displayed).
	 * @return {String} The truncated anchor text.
	 */
	doTruncate : function( anchorText ) {
		return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY );
	}

} );
/*global Autolinker */
/**
 * @private
 * @class Autolinker.htmlParser.HtmlParser
 * @extends Object
 *
 * An HTML parser implementation which simply walks an HTML string and returns an array of
 * {@link Autolinker.htmlParser.HtmlNode HtmlNodes} that represent the basic HTML structure of the input string.
 *
 * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, effectively ignoring / "walking
 * around" HTML tags.
 */
Autolinker.htmlParser.HtmlParser = Autolinker.Util.extend( Object, {

	/**
	 * @private
	 * @property {RegExp} htmlRegex
	 *
	 * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and
	 * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html.
	 *
	 * Capturing groups:
	 *
	 * 1. The "!DOCTYPE" tag name, if a tag is a &lt;!DOCTYPE&gt; tag.
	 * 2. If it is an end tag, this group will have the '/'.
	 * 3. If it is a comment tag, this group will hold the comment text (i.e.
	 *    the text inside the `&lt;!--` and `--&gt;`.
	 * 4. The tag name for all tags (other than the &lt;!DOCTYPE&gt; tag)
	 */
	htmlRegex : (function() {
		var commentTagRegex = /!--([\s\S]+?)--/,
		    tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/,
		    attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/,   // the unicode range accounts for excluding control chars, and the delete char
		    attrValueRegex = /(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values
		    nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?';  // optional '=[value]'

		return new RegExp( [
			// for <!DOCTYPE> tag. Ex: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">)
			'(?:',
				'<(!DOCTYPE)',  // *** Capturing Group 1 - If it's a doctype tag

					// Zero or more attributes following the tag name
					'(?:',
						'\\s+',  // one or more whitespace chars before an attribute

						// Either:
						// A. attr="value", or
						// B. "value" alone (To cover example doctype tag: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">)
						'(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')',
					')*',
				'>',
			')',

			'|',

			// All other HTML tags (i.e. tags that are not <!DOCTYPE>)
			'(?:',
				'<(/)?',  // Beginning of a tag or comment. Either '<' for a start tag, or '</' for an end tag.
				          // *** Capturing Group 2: The slash or an empty string. Slash ('/') for end tag, empty string for start or self-closing tag.

					'(?:',
						commentTagRegex.source,  // *** Capturing Group 3 - A Comment Tag's Text

						'|',

						'(?:',

							// *** Capturing Group 4 - The tag name
							'(' + tagNameRegex.source + ')',

							// Zero or more attributes following the tag name
							'(?:',
								'\\s+',                // one or more whitespace chars before an attribute
								nameEqualsValueRegex,  // attr="value" (with optional ="value" part)
							')*',

							'\\s*/?',  // any trailing spaces and optional '/' before the closing '>'

						')',
					')',
				'>',
			')'
		].join( "" ), 'gi' );
	} )(),

	/**
	 * @private
	 * @property {RegExp} htmlCharacterEntitiesRegex
	 *
	 * The regular expression that matches common HTML character entities.
	 *
	 * Ignoring &amp; as it could be part of a query string -- handling it separately.
	 */
	htmlCharacterEntitiesRegex: /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;|&quot;|&#34;|&#39;)/gi,


	/**
	 * Parses an HTML string and returns a simple array of {@link Autolinker.htmlParser.HtmlNode HtmlNodes}
	 * to represent the HTML structure of the input string.
	 *
	 * @param {String} html The HTML to parse.
	 * @return {Autolinker.htmlParser.HtmlNode[]}
	 */
	parse : function( html ) {
		var htmlRegex = this.htmlRegex,
		    currentResult,
		    lastIndex = 0,
		    textAndEntityNodes,
		    nodes = [];  // will be the result of the method

		while( ( currentResult = htmlRegex.exec( html ) ) !== null ) {
			var tagText = currentResult[ 0 ],
			    commentText = currentResult[ 3 ], // if we've matched a comment
			    tagName = currentResult[ 1 ] || currentResult[ 4 ],  // The <!DOCTYPE> tag (ex: "!DOCTYPE"), or another tag (ex: "a" or "img")
			    isClosingTag = !!currentResult[ 2 ],
			    inBetweenTagsText = html.substring( lastIndex, currentResult.index );

			// Push TextNodes and EntityNodes for any text found between tags
			if( inBetweenTagsText ) {
				textAndEntityNodes = this.parseTextAndEntityNodes( inBetweenTagsText );
				nodes.push.apply( nodes, textAndEntityNodes );
			}

			// Push the CommentNode or ElementNode
			if( commentText ) {
				nodes.push( this.createCommentNode( tagText, commentText ) );
			} else {
				nodes.push( this.createElementNode( tagText, tagName, isClosingTag ) );
			}

			lastIndex = currentResult.index + tagText.length;
		}

		// Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements.
		if( lastIndex < html.length ) {
			var text = html.substring( lastIndex );

			// Push TextNodes and EntityNodes for any text found between tags
			if( text ) {
				textAndEntityNodes = this.parseTextAndEntityNodes( text );
				nodes.push.apply( nodes, textAndEntityNodes );
			}
		}

		return nodes;
	},


	/**
	 * Parses text and HTML entity nodes from a given string. The input string
	 * should not have any HTML tags (elements) within it.
	 *
	 * @private
	 * @param {String} text The text to parse.
	 * @return {Autolinker.htmlParser.HtmlNode[]} An array of HtmlNodes to
	 *   represent the {@link Autolinker.htmlParser.TextNode TextNodes} and
	 *   {@link Autolinker.htmlParser.EntityNode EntityNodes} found.
	 */
	parseTextAndEntityNodes : function( text ) {
		var nodes = [],
		    textAndEntityTokens = Autolinker.Util.splitAndCapture( text, this.htmlCharacterEntitiesRegex );  // split at HTML entities, but include the HTML entities in the results array

		// Every even numbered token is a TextNode, and every odd numbered token is an EntityNode
		// For example: an input `text` of "Test &quot;this&quot; today" would turn into the
		//   `textAndEntityTokens`: [ 'Test ', '&quot;', 'this', '&quot;', ' today' ]
		for( var i = 0, len = textAndEntityTokens.length; i < len; i += 2 ) {
			var textToken = textAndEntityTokens[ i ],
			    entityToken = textAndEntityTokens[ i + 1 ];

			if( textToken ) nodes.push( this.createTextNode( textToken ) );
			if( entityToken ) nodes.push( this.createEntityNode( entityToken ) );
		}
		return nodes;
	},


	/**
	 * Factory method to create an {@link Autolinker.htmlParser.CommentNode CommentNode}.
	 *
	 * @private
	 * @param {String} tagText The full text of the tag (comment) that was
	 *   matched, including its &lt;!-- and --&gt;.
	 * @param {String} comment The full text of the comment that was matched.
	 */
	createCommentNode : function( tagText, commentText ) {
		return new Autolinker.htmlParser.CommentNode( {
			text: tagText,
			comment: Autolinker.Util.trim( commentText )
		} );
	},


	/**
	 * Factory method to create an {@link Autolinker.htmlParser.ElementNode ElementNode}.
	 *
	 * @private
	 * @param {String} tagText The full text of the tag (element) that was
	 *   matched, including its attributes.
	 * @param {String} tagName The name of the tag. Ex: An &lt;img&gt; tag would
	 *   be passed to this method as "img".
	 * @param {Boolean} isClosingTag `true` if it's a closing tag, false
	 *   otherwise.
	 * @return {Autolinker.htmlParser.ElementNode}
	 */
	createElementNode : function( tagText, tagName, isClosingTag ) {
		return new Autolinker.htmlParser.ElementNode( {
			text    : tagText,
			tagName : tagName.toLowerCase(),
			closing : isClosingTag
		} );
	},


	/**
	 * Factory method to create a {@link Autolinker.htmlParser.EntityNode EntityNode}.
	 *
	 * @private
	 * @param {String} text The text that was matched for the HTML entity (such
	 *   as '&amp;nbsp;').
	 * @return {Autolinker.htmlParser.EntityNode}
	 */
	createEntityNode : function( text ) {
		return new Autolinker.htmlParser.EntityNode( { text: text } );
	},


	/**
	 * Factory method to create a {@link Autolinker.htmlParser.TextNode TextNode}.
	 *
	 * @private
	 * @param {String} text The text that was matched.
	 * @return {Autolinker.htmlParser.TextNode}
	 */
	createTextNode : function( text ) {
		return new Autolinker.htmlParser.TextNode( { text: text } );
	}

} );
/*global Autolinker */
/**
 * @abstract
 * @class Autolinker.htmlParser.HtmlNode
 * 
 * Represents an HTML node found in an input string. An HTML node is one of the following:
 * 
 * 1. An {@link Autolinker.htmlParser.ElementNode ElementNode}, which represents HTML tags.
 * 2. A {@link Autolinker.htmlParser.TextNode TextNode}, which represents text outside or within HTML tags.
 * 3. A {@link Autolinker.htmlParser.EntityNode EntityNode}, which represents one of the known HTML
 *    entities that Autolinker looks for. This includes common ones such as &amp;quot; and &amp;nbsp;
 */
Autolinker.htmlParser.HtmlNode = Autolinker.Util.extend( Object, {
	
	/**
	 * @cfg {String} text (required)
	 * 
	 * The original text that was matched for the HtmlNode. 
	 * 
	 * - In the case of an {@link Autolinker.htmlParser.ElementNode ElementNode}, this will be the tag's
	 *   text.
	 * - In the case of a {@link Autolinker.htmlParser.TextNode TextNode}, this will be the text itself.
	 * - In the case of a {@link Autolinker.htmlParser.EntityNode EntityNode}, this will be the text of
	 *   the HTML entity.
	 */
	text : "",
	
	
	/**
	 * @constructor
	 * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map).
	 */
	constructor : function( cfg ) {
		Autolinker.Util.assign( this, cfg );
	},

	
	/**
	 * Returns a string name for the type of node that this class represents.
	 * 
	 * @abstract
	 * @return {String}
	 */
	getType : Autolinker.Util.abstractMethod,
	
	
	/**
	 * Retrieves the {@link #text} for the HtmlNode.
	 * 
	 * @return {String}
	 */
	getText : function() {
		return this.text;
	}

} );
/*global Autolinker */
/**
 * @class Autolinker.htmlParser.CommentNode
 * @extends Autolinker.htmlParser.HtmlNode
 *
 * Represents an HTML comment node that has been parsed by the
 * {@link Autolinker.htmlParser.HtmlParser}.
 *
 * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more
 * details.
 */
Autolinker.htmlParser.CommentNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {

	/**
	 * @cfg {String} comment (required)
	 *
	 * The text inside the comment tag. This text is stripped of any leading or
	 * trailing whitespace.
	 */
	comment : '',


	/**
	 * Returns a string name for the type of node that this class represents.
	 *
	 * @return {String}
	 */
	getType : function() {
		return 'comment';
	},


	/**
	 * Returns the comment inside the comment tag.
	 *
	 * @return {String}
	 */
	getComment : function() {
		return this.comment;
	}

} );
/*global Autolinker */
/**
 * @class Autolinker.htmlParser.ElementNode
 * @extends Autolinker.htmlParser.HtmlNode
 * 
 * Represents an HTML element node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}.
 * 
 * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details.
 */
Autolinker.htmlParser.ElementNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
	
	/**
	 * @cfg {String} tagName (required)
	 * 
	 * The name of the tag that was matched.
	 */
	tagName : '',
	
	/**
	 * @cfg {Boolean} closing (required)
	 * 
	 * `true` if the element (tag) is a closing tag, `false` if its an opening tag.
	 */
	closing : false,

	
	/**
	 * Returns a string name for the type of node that this class represents.
	 * 
	 * @return {String}
	 */
	getType : function() {
		return 'element';
	},
	

	/**
	 * Returns the HTML element's (tag's) name. Ex: for an &lt;img&gt; tag, returns "img".
	 * 
	 * @return {String}
	 */
	getTagName : function() {
		return this.tagName;
	},
	
	
	/**
	 * Determines if the HTML element (tag) is a closing tag. Ex: &lt;div&gt; returns
	 * `false`, while &lt;/div&gt; returns `true`.
	 * 
	 * @return {Boolean}
	 */
	isClosing : function() {
		return this.closing;
	}
	
} );
/*global Autolinker */
/**
 * @class Autolinker.htmlParser.EntityNode
 * @extends Autolinker.htmlParser.HtmlNode
 * 
 * Represents a known HTML entity node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}.
 * Ex: '&amp;nbsp;', or '&amp#160;' (which will be retrievable from the {@link #getText} method.
 * 
 * Note that this class will only be returned from the HtmlParser for the set of checked HTML entity nodes 
 * defined by the {@link Autolinker.htmlParser.HtmlParser#htmlCharacterEntitiesRegex}.
 * 
 * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details.
 */
Autolinker.htmlParser.EntityNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
	
	/**
	 * Returns a string name for the type of node that this class represents.
	 * 
	 * @return {String}
	 */
	getType : function() {
		return 'entity';
	}
	
} );
/*global Autolinker */
/**
 * @class Autolinker.htmlParser.TextNode
 * @extends Autolinker.htmlParser.HtmlNode
 * 
 * Represents a text node that has been parsed by the {@link Autolinker.htmlParser.HtmlParser}.
 * 
 * See this class's superclass ({@link Autolinker.htmlParser.HtmlNode}) for more details.
 */
Autolinker.htmlParser.TextNode = Autolinker.Util.extend( Autolinker.htmlParser.HtmlNode, {
	
	/**
	 * Returns a string name for the type of node that this class represents.
	 * 
	 * @return {String}
	 */
	getType : function() {
		return 'text';
	}
	
} );
/*global Autolinker */
/**
 * @private
 * @class Autolinker.matchParser.MatchParser
 * @extends Object
 *
 * Used by Autolinker to parse potential matches, given an input string of text.
 *
 * The MatchParser is fed a non-HTML string in order to search for matches.
 * Autolinker first uses the {@link Autolinker.htmlParser.HtmlParser} to "walk
 * around" HTML tags, and then the text around the HTML tags is passed into the
 * MatchParser in order to find the actual matches.
 */
Autolinker.matchParser.MatchParser = Autolinker.Util.extend( Object, {

	/**
	 * @cfg {Boolean} urls
	 * @inheritdoc Autolinker#urls
	 */
	urls : true,

	/**
	 * @cfg {Boolean} email
	 * @inheritdoc Autolinker#email
	 */
	email : true,

	/**
	 * @cfg {Boolean} twitter
	 * @inheritdoc Autolinker#twitter
	 */
	twitter : true,

	/**
	 * @cfg {Boolean} phone
	 * @inheritdoc Autolinker#phone
	 */
	phone: true,

	/**
	 * @cfg {Boolean/String} hashtag
	 * @inheritdoc Autolinker#hashtag
	 */
	hashtag : false,

	/**
	 * @cfg {Boolean} stripPrefix
	 * @inheritdoc Autolinker#stripPrefix
	 */
	stripPrefix : true,


	/**
	 * @private
	 * @property {RegExp} matcherRegex
	 *
	 * The regular expression that matches URLs, email addresses, phone #s,
	 * Twitter handles, and Hashtags.
	 *
	 * This regular expression has the following capturing groups:
	 *
	 * 1.  Group that is used to determine if there is a Twitter handle match
	 *     (i.e. \@someTwitterUser). Simply check for its existence to determine
	 *     if there is a Twitter handle match. The next couple of capturing
	 *     groups give information about the Twitter handle match.
	 * 2.  The whitespace character before the \@sign in a Twitter handle. This
	 *     is needed because there are no lookbehinds in JS regular expressions,
	 *     and can be used to reconstruct the original string in a replace().
	 * 3.  The Twitter handle itself in a Twitter match. If the match is
	 *     '@someTwitterUser', the handle is 'someTwitterUser'.
	 * 4.  Group that matches an email address. Used to determine if the match
	 *     is an email address, as well as holding the full address. Ex:
	 *     'me@my.com'
	 * 5.  Group that matches a URL in the input text. Ex: 'http://google.com',
	 *     'www.google.com', or just 'google.com'. This also includes a path,
	 *     url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor
	 * 6.  Group that matches a protocol URL (i.e. 'http://google.com'). This is
	 *     used to match protocol URLs with just a single word, like 'http://localhost',
	 *     where we won't double check that the domain name has at least one '.'
	 *     in it.
	 * 7.  A protocol-relative ('//') match for the case of a 'www.' prefixed
	 *     URL. Will be an empty string if it is not a protocol-relative match.
	 *     We need to know the character before the '//' in order to determine
	 *     if it is a valid match or the // was in a string we don't want to
	 *     auto-link.
	 * 8.  A protocol-relative ('//') match for the case of a known TLD prefixed
	 *     URL. Will be an empty string if it is not a protocol-relative match.
	 *     See #6 for more info.
	 * 9.  Group that is used to determine if there is a phone number match. The
	 *     next 3 groups give segments of the phone number.
	 * 10. Group that is used to determine if there is a Hashtag match
	 *     (i.e. \#someHashtag). Simply check for its existence to determine if
	 *     there is a Hashtag match. The next couple of capturing groups give
	 *     information about the Hashtag match.
	 * 11. The whitespace character before the #sign in a Hashtag handle. This
	 *     is needed because there are no look-behinds in JS regular
	 *     expressions, and can be used to reconstruct the original string in a
	 *     replace().
	 * 12. The Hashtag itself in a Hashtag match. If the match is
	 *     '#someHashtag', the hashtag is 'someHashtag'.
	 */
	matcherRegex : (function() {
		var twitterRegex = /(^|[^\w])@(\w{1,15})/,              // For matching a twitter handle. Ex: @gregory_jacobs

		    hashtagRegex = /(^|[^\w])#(\w{1,15})/,              // For matching a Hashtag. Ex: #games

		    emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/,             // something@ for email addresses (a.k.a. local-part)
		    phoneRegex = /(?:\+?\d{1,3}[-\s.])?\(?\d{3}\)?[-\s.]?\d{3}[-\s.]\d{4}/,  // ex: (123) 456-7890, 123 456 7890, 123-456-7890, etc.
		    protocolRegex = /(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\/\/)(?!\d+\/?)(?:\/\/)?)/,  // match protocol, allow in format "http://" or "mailto:". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match "link:"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex)
		    wwwRegex = /(?:www\.)/,                             // starting with 'www.'
		    domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/,  // anything looking at all like a domain, non-unicode domains, not ending in a period
		    tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/,   // match our known top level domains (TLDs)

		    // Allow optional path, query string, and hash anchor, not ending in the following characters: "?!:,.;"
		    // http://blog.codinghorror.com/the-problem-with-urls/
		    urlSuffixRegex = /[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]?!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]]/;

		return new RegExp( [
			'(',  // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace()
				// *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and
				// *** Capturing group $3, which matches the actual twitter handle
				twitterRegex.source,
			')',

			'|',

			'(',  // *** Capturing group $4, which is used to determine an email match
				emailRegex.source,
				domainNameRegex.source,
				tldRegex.source,
			')',

			'|',

			'(',  // *** Capturing group $5, which is used to match a URL
				'(?:', // parens to cover match for protocol (optional), and domain
					'(',  // *** Capturing group $6, for a protocol-prefixed url (ex: http://google.com)
						protocolRegex.source,
						domainNameRegex.source,
					')',

					'|',

					'(?:',  // non-capturing paren for a 'www.' prefixed url (ex: www.google.com)
						'(.?//)?',  // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character
						wwwRegex.source,
						domainNameRegex.source,
					')',

					'|',

					'(?:',  // non-capturing paren for known a TLD url (ex: google.com)
						'(.?//)?',  // *** Capturing group $8 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character
						domainNameRegex.source,
						tldRegex.source,
					')',
				')',

				'(?:' + urlSuffixRegex.source + ')?',  // match for path, query string, and/or hash anchor - optional
			')',

			'|',

			// this setup does not scale well for open extension :( Need to rethink design of autolinker...
			// ***  Capturing group $9, which matches a (USA for now) phone number
			'(',
				phoneRegex.source,
			')',

			'|',

			'(',  // *** Capturing group $10, which can be used to check for a Hashtag match. Use group $12 for the actual Hashtag though. $11 may be used to reconstruct the original string in a replace()
				// *** Capturing group $11, which matches the whitespace character before the '#' sign (needed because of no lookbehinds), and
				// *** Capturing group $12, which matches the actual Hashtag
				hashtagRegex.source,
			')'
		].join( "" ), 'gi' );
	} )(),

	/**
	 * @private
	 * @property {RegExp} charBeforeProtocolRelMatchRegex
	 *
	 * The regular expression used to retrieve the character before a
	 * protocol-relative URL match.
	 *
	 * This is used in conjunction with the {@link #matcherRegex}, which needs
	 * to grab the character before a protocol-relative '//' due to the lack of
	 * a negative look-behind in JavaScript regular expressions. The character
	 * before the match is stripped from the URL.
	 */
	charBeforeProtocolRelMatchRegex : /^(.)?\/\//,

	/**
	 * @private
	 * @property {Autolinker.MatchValidator} matchValidator
	 *
	 * The MatchValidator object, used to filter out any false positives from
	 * the {@link #matcherRegex}. See {@link Autolinker.MatchValidator} for details.
	 */


	/**
	 * @constructor
	 * @param {Object} [cfg] The configuration options for the AnchorTagBuilder
	 * instance, specified in an Object (map).
	 */
	constructor : function( cfg ) {
		Autolinker.Util.assign( this, cfg );

		this.matchValidator = new Autolinker.MatchValidator();
	},


	/**
	 * Parses the input `text` to search for matches, and calls the `replaceFn`
	 * to allow replacements of the matches. Returns the `text` with matches
	 * replaced.
	 *
	 * @param {String} text The text to search and repace matches in.
	 * @param {Function} replaceFn The iterator function to handle the
	 *   replacements. The function takes a single argument, a {@link Autolinker.match.Match}
	 *   object, and should return the text that should make the replacement.
	 * @param {Object} [contextObj=window] The context object ("scope") to run
	 *   the `replaceFn` in.
	 * @return {String}
	 */
	replace : function( text, replaceFn, contextObj ) {
		var me = this;  // for closure

		return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 ) {
			var matchDescObj = me.processCandidateMatch( matchStr, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 );  // "match description" object

			// Return out with no changes for match types that are disabled (url,
			// email, phone, etc.), or for matches that are invalid (false
			// positives from the matcherRegex, which can't use look-behinds
			// since they are unavailable in JS).
			if( !matchDescObj ) {
				return matchStr;

			} else {
				// Generate replacement text for the match from the `replaceFn`
				var replaceStr = replaceFn.call( contextObj, matchDescObj.match );
				return matchDescObj.prefixStr + replaceStr + matchDescObj.suffixStr;
			}
		} );
	},


	/**
	 * Processes a candidate match from the {@link #matcherRegex}.
	 *
	 * Not all matches found by the regex are actual URL/Email/Phone/Twitter/Hashtag
	 * matches, as determined by the {@link #matchValidator}. In this case, the
	 * method returns `null`. Otherwise, a valid Object with `prefixStr`,
	 * `match`, and `suffixStr` is returned.
	 *
	 * @private
	 * @param {String} matchStr The full match that was found by the
	 *   {@link #matcherRegex}.
	 * @param {String} twitterMatch The matched text of a Twitter handle, if the
	 *   match is a Twitter match.
	 * @param {String} twitterHandlePrefixWhitespaceChar The whitespace char
	 *   before the @ sign in a Twitter handle match. This is needed because of
	 *   no lookbehinds in JS regexes, and is need to re-include the character
	 *   for the anchor tag replacement.
	 * @param {String} twitterHandle The actual Twitter user (i.e the word after
	 *   the @ sign in a Twitter match).
	 * @param {String} emailAddressMatch The matched email address for an email
	 *   address match.
	 * @param {String} urlMatch The matched URL string for a URL match.
	 * @param {String} protocolUrlMatch The match URL string for a protocol
	 *   match. Ex: 'http://yahoo.com'. This is used to match something like
	 *   'http://localhost', where we won't double check that the domain name
	 *   has at least one '.' in it.
	 * @param {String} wwwProtocolRelativeMatch The '//' for a protocol-relative
	 *   match from a 'www' url, with the character that comes before the '//'.
	 * @param {String} tldProtocolRelativeMatch The '//' for a protocol-relative
	 *   match from a TLD (top level domain) match, with the character that
	 *   comes before the '//'.
	 * @param {String} phoneMatch The matched text of a phone number
	 * @param {String} hashtagMatch The matched text of a Twitter
	 *   Hashtag, if the match is a Hashtag match.
	 * @param {String} hashtagPrefixWhitespaceChar The whitespace char
	 *   before the # sign in a Hashtag match. This is needed because of no
	 *   lookbehinds in JS regexes, and is need to re-include the character for
	 *   the anchor tag replacement.
	 * @param {String} hashtag The actual Hashtag (i.e the word
	 *   after the # sign in a Hashtag match).
	 *
	 * @return {Object} A "match description object". This will be `null` if the
	 *   match was invalid, or if a match type is disabled. Otherwise, this will
	 *   be an Object (map) with the following properties:
	 * @return {String} return.prefixStr The char(s) that should be prepended to
	 *   the replacement string. These are char(s) that were needed to be
	 *   included from the regex match that were ignored by processing code, and
	 *   should be re-inserted into the replacement stream.
	 * @return {String} return.suffixStr The char(s) that should be appended to
	 *   the replacement string. These are char(s) that were needed to be
	 *   included from the regex match that were ignored by processing code, and
	 *   should be re-inserted into the replacement stream.
	 * @return {Autolinker.match.Match} return.match The Match object that
	 *   represents the match that was found.
	 */
	processCandidateMatch : function(
		matchStr, twitterMatch, twitterHandlePrefixWhitespaceChar, twitterHandle,
		emailAddressMatch, urlMatch, protocolUrlMatch, wwwProtocolRelativeMatch,
		tldProtocolRelativeMatch, phoneMatch, hashtagMatch,
		hashtagPrefixWhitespaceChar, hashtag
	) {
		// Note: The `matchStr` variable wil be fixed up to remove characters that are no longer needed (which will
		// be added to `prefixStr` and `suffixStr`).

		var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch,
		    match,  // Will be an Autolinker.match.Match object

		    prefixStr = "",  // A string to use to prefix the anchor tag that is created. This is needed for the Twitter and Hashtag matches.
		    suffixStr = "";  // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked.

		// Return out with `null` for match types that are disabled (url, email,
		// twitter, hashtag), or for matches that are invalid (false positives
		// from the matcherRegex, which can't use look-behinds since they are
		// unavailable in JS).
		if(
			( urlMatch && !this.urls ) ||
			( emailAddressMatch && !this.email ) ||
			( phoneMatch && !this.phone ) ||
			( twitterMatch && !this.twitter ) ||
			( hashtagMatch && !this.hashtag ) ||
			!this.matchValidator.isValidMatch( urlMatch, protocolUrlMatch, protocolRelativeMatch )
		) {
			return null;
		}

		// Handle a closing parenthesis at the end of the match, and exclude it
		// if there is not a matching open parenthesis
		// in the match itself.
		if( this.matchHasUnbalancedClosingParen( matchStr ) ) {
			matchStr = matchStr.substr( 0, matchStr.length - 1 );  // remove the trailing ")"
			suffixStr = ")";  // this will be added after the generated <a> tag
		}

		if( emailAddressMatch ) {
			match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } );

		} else if( twitterMatch ) {
			// fix up the `matchStr` if there was a preceding whitespace char,
			// which was needed to determine the match itself (since there are
			// no look-behinds in JS regexes)
			if( twitterHandlePrefixWhitespaceChar ) {
				prefixStr = twitterHandlePrefixWhitespaceChar;
				matchStr = matchStr.slice( 1 );  // remove the prefixed whitespace char from the match
			}
			match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } );

		} else if( phoneMatch ) {
			// remove non-numeric values from phone number string
			var cleanNumber = matchStr.replace( /\D/g, '' );
 			match = new Autolinker.match.Phone( { matchedText: matchStr, number: cleanNumber } );

		} else if( hashtagMatch ) {
			// fix up the `matchStr` if there was a preceding whitespace char,
			// which was needed to determine the match itself (since there are
			// no look-behinds in JS regexes)
			if( hashtagPrefixWhitespaceChar ) {
				prefixStr = hashtagPrefixWhitespaceChar;
				matchStr = matchStr.slice( 1 );  // remove the prefixed whitespace char from the match
			}
			match = new Autolinker.match.Hashtag( { matchedText: matchStr, serviceName: this.hashtag, hashtag: hashtag } );

		} else {  // url match
			// If it's a protocol-relative '//' match, remove the character
			// before the '//' (which the matcherRegex needed to match due to
			// the lack of a negative look-behind in JavaScript regular
			// expressions)
			if( protocolRelativeMatch ) {
				var charBeforeMatch = protocolRelativeMatch.match( this.charBeforeProtocolRelMatchRegex )[ 1 ] || "";

				if( charBeforeMatch ) {  // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes)
					prefixStr = charBeforeMatch;
					matchStr = matchStr.slice( 1 );  // remove the prefixed char from the match
				}
			}

			match = new Autolinker.match.Url( {
				matchedText : matchStr,
				url : matchStr,
				protocolUrlMatch : !!protocolUrlMatch,
				protocolRelativeMatch : !!protocolRelativeMatch,
				stripPrefix : this.stripPrefix
			} );
		}

		return {
			prefixStr : prefixStr,
			suffixStr : suffixStr,
			match     : match
		};
	},


	/**
	 * Determines if a match found has an unmatched closing parenthesis. If so,
	 * this parenthesis will be removed from the match itself, and appended
	 * after the generated anchor tag in {@link #processCandidateMatch}.
	 *
	 * A match may have an extra closing parenthesis at the end of the match
	 * because the regular expression must include parenthesis for URLs such as
	 * "wikipedia.com/something_(disambiguation)", which should be auto-linked.
	 *
	 * However, an extra parenthesis *will* be included when the URL itself is
	 * wrapped in parenthesis, such as in the case of "(wikipedia.com/something_(disambiguation))".
	 * In this case, the last closing parenthesis should *not* be part of the
	 * URL itself, and this method will return `true`.
	 *
	 * @private
	 * @param {String} matchStr The full match string from the {@link #matcherRegex}.
	 * @return {Boolean} `true` if there is an unbalanced closing parenthesis at
	 *   the end of the `matchStr`, `false` otherwise.
	 */
	matchHasUnbalancedClosingParen : function( matchStr ) {
		var lastChar = matchStr.charAt( matchStr.length - 1 );

		if( lastChar === ')' ) {
			var openParensMatch = matchStr.match( /\(/g ),
			    closeParensMatch = matchStr.match( /\)/g ),
			    numOpenParens = ( openParensMatch && openParensMatch.length ) || 0,
			    numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0;

			if( numOpenParens < numCloseParens ) {
				return true;
			}
		}

		return false;
	}

} );
/*global Autolinker */
/*jshint scripturl:true */
/**
 * @private
 * @class Autolinker.MatchValidator
 * @extends Object
 *
 * Used by Autolinker to filter out false positives from the
 * {@link Autolinker.matchParser.MatchParser#matcherRegex}.
 *
 * Due to the limitations of regular expressions (including the missing feature
 * of look-behinds in JS regular expressions), we cannot always determine the
 * validity of a given match. This class applies a bit of additional logic to
 * filter out any false positives that have been matched by the
 * {@link Autolinker.matchParser.MatchParser#matcherRegex}.
 */
Autolinker.MatchValidator = Autolinker.Util.extend( Object, {

	/**
	 * @private
	 * @property {RegExp} invalidProtocolRelMatchRegex
	 *
	 * The regular expression used to check a potential protocol-relative URL
	 * match, coming from the {@link Autolinker.matchParser.MatchParser#matcherRegex}.
	 * A protocol-relative URL is, for example, "//yahoo.com"
	 *
	 * This regular expression checks to see if there is a word character before
	 * the '//' match in order to determine if we should actually autolink a
	 * protocol-relative URL. This is needed because there is no negative
	 * look-behind in JavaScript regular expressions.
	 *
	 * For instance, we want to autolink something like "Go to: //google.com",
	 * but we don't want to autolink something like "abc//google.com"
	 */
	invalidProtocolRelMatchRegex : /^[\w]\/\//,

	/**
	 * Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://'
	 *
	 * @private
	 * @property {RegExp} hasFullProtocolRegex
	 */
	hasFullProtocolRegex : /^[A-Za-z][-.+A-Za-z0-9]+:\/\//,

	/**
	 * Regex to find the URI scheme, such as 'mailto:'.
	 *
	 * This is used to filter out 'javascript:' and 'vbscript:' schemes.
	 *
	 * @private
	 * @property {RegExp} uriSchemeRegex
	 */
	uriSchemeRegex : /^[A-Za-z][-.+A-Za-z0-9]+:/,

	/**
	 * Regex to determine if at least one word char exists after the protocol (i.e. after the ':')
	 *
	 * @private
	 * @property {RegExp} hasWordCharAfterProtocolRegex
	 */
	hasWordCharAfterProtocolRegex : /:[^\s]*?[A-Za-z]/,


	/**
	 * Determines if a given match found by the {@link Autolinker.matchParser.MatchParser}
	 * is valid. Will return `false` for:
	 *
	 * 1) URL matches which do not have at least have one period ('.') in the
	 *    domain name (effectively skipping over matches like "abc:def").
	 *    However, URL matches with a protocol will be allowed (ex: 'http://localhost')
	 * 2) URL matches which do not have at least one word character in the
	 *    domain name (effectively skipping over matches like "git:1.0").
	 * 3) A protocol-relative url match (a URL beginning with '//') whose
	 *    previous character is a word character (effectively skipping over
	 *    strings like "abc//google.com")
	 *
	 * Otherwise, returns `true`.
	 *
	 * @param {String} urlMatch The matched URL, if there was one. Will be an
	 *   empty string if the match is not a URL match.
	 * @param {String} protocolUrlMatch The match URL string for a protocol
	 *   match. Ex: 'http://yahoo.com'. This is used to match something like
	 *   'http://localhost', where we won't double check that the domain name
	 *   has at least one '.' in it.
	 * @param {String} protocolRelativeMatch The protocol-relative string for a
	 *   URL match (i.e. '//'), possibly with a preceding character (ex, a
	 *   space, such as: ' //', or a letter, such as: 'a//'). The match is
	 *   invalid if there is a word character preceding the '//'.
	 * @return {Boolean} `true` if the match given is valid and should be
	 *   processed, or `false` if the match is invalid and/or should just not be
	 *   processed.
	 */
	isValidMatch : function( urlMatch, protocolUrlMatch, protocolRelativeMatch ) {
		if(
			( protocolUrlMatch && !this.isValidUriScheme( protocolUrlMatch ) ) ||
			this.urlMatchDoesNotHaveProtocolOrDot( urlMatch, protocolUrlMatch ) ||       // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost')
			this.urlMatchDoesNotHaveAtLeastOneWordChar( urlMatch, protocolUrlMatch ) ||  // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0"
			this.isInvalidProtocolRelativeMatch( protocolRelativeMatch )                 // A protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com")
		) {
			return false;
		}

		return true;
	},


	/**
	 * Determines if the URI scheme is a valid scheme to be autolinked. Returns
	 * `false` if the scheme is 'javascript:' or 'vbscript:'
	 *
	 * @private
	 * @param {String} uriSchemeMatch The match URL string for a full URI scheme
	 *   match. Ex: 'http://yahoo.com' or 'mailto:a@a.com'.
	 * @return {Boolean} `true` if the scheme is a valid one, `false` otherwise.
	 */
	isValidUriScheme : function( uriSchemeMatch ) {
		var uriScheme = uriSchemeMatch.match( this.uriSchemeRegex )[ 0 ].toLowerCase();

		return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' );
	},


	/**
	 * Determines if a URL match does not have either:
	 *
	 * a) a full protocol (i.e. 'http://'), or
	 * b) at least one dot ('.') in the domain name (for a non-full-protocol
	 *    match).
	 *
	 * Either situation is considered an invalid URL (ex: 'git:d' does not have
	 * either the '://' part, or at least one dot in the domain name. If the
	 * match was 'git:abc.com', we would consider this valid.)
	 *
	 * @private
	 * @param {String} urlMatch The matched URL, if there was one. Will be an
	 *   empty string if the match is not a URL match.
	 * @param {String} protocolUrlMatch The match URL string for a protocol
	 *   match. Ex: 'http://yahoo.com'. This is used to match something like
	 *   'http://localhost', where we won't double check that the domain name
	 *   has at least one '.' in it.
	 * @return {Boolean} `true` if the URL match does not have a full protocol,
	 *   or at least one dot ('.') in a non-full-protocol match.
	 */
	urlMatchDoesNotHaveProtocolOrDot : function( urlMatch, protocolUrlMatch ) {
		return ( !!urlMatch && ( !protocolUrlMatch || !this.hasFullProtocolRegex.test( protocolUrlMatch ) ) && urlMatch.indexOf( '.' ) === -1 );
	},


	/**
	 * Determines if a URL match does not have at least one word character after
	 * the protocol (i.e. in the domain name).
	 *
	 * At least one letter character must exist in the domain name after a
	 * protocol match. Ex: skip over something like "git:1.0"
	 *
	 * @private
	 * @param {String} urlMatch The matched URL, if there was one. Will be an
	 *   empty string if the match is not a URL match.
	 * @param {String} protocolUrlMatch The match URL string for a protocol
	 *   match. Ex: 'http://yahoo.com'. This is used to know whether or not we
	 *   have a protocol in the URL string, in order to check for a word
	 *   character after the protocol separator (':').
	 * @return {Boolean} `true` if the URL match does not have at least one word
	 *   character in it after the protocol, `false` otherwise.
	 */
	urlMatchDoesNotHaveAtLeastOneWordChar : function( urlMatch, protocolUrlMatch ) {
		if( urlMatch && protocolUrlMatch ) {
			return !this.hasWordCharAfterProtocolRegex.test( urlMatch );
		} else {
			return false;
		}
	},


	/**
	 * Determines if a protocol-relative match is an invalid one. This method
	 * returns `true` if there is a `protocolRelativeMatch`, and that match
	 * contains a word character before the '//' (i.e. it must contain
	 * whitespace or nothing before the '//' in order to be considered valid).
	 *
	 * @private
	 * @param {String} protocolRelativeMatch The protocol-relative string for a
	 *   URL match (i.e. '//'), possibly with a preceding character (ex, a
	 *   space, such as: ' //', or a letter, such as: 'a//'). The match is
	 *   invalid if there is a word character preceding the '//'.
	 * @return {Boolean} `true` if it is an invalid protocol-relative match,
	 *   `false` otherwise.
	 */
	isInvalidProtocolRelativeMatch : function( protocolRelativeMatch ) {
		return ( !!protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) );
	}

} );
/*global Autolinker */
/**
 * @abstract
 * @class Autolinker.match.Match
 * 
 * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a 
 * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.
 * 
 * For example:
 * 
 *     var input = "...";  // string with URLs, Email Addresses, and Twitter Handles
 *     
 *     var linkedText = Autolinker.link( input, {
 *         replaceFn : function( autolinker, match ) {
 *             console.log( "href = ", match.getAnchorHref() );
 *             console.log( "text = ", match.getAnchorText() );
 *         
 *             switch( match.getType() ) {
 *                 case 'url' : 
 *                     console.log( "url: ", match.getUrl() );
 *                     
 *                 case 'email' :
 *                     console.log( "email: ", match.getEmail() );
 *                     
 *                 case 'twitter' :
 *                     console.log( "twitter: ", match.getTwitterHandle() );
 *             }
 *         }
 *     } );
 *     
 * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.
 */
Autolinker.match.Match = Autolinker.Util.extend( Object, {
	
	/**
	 * @cfg {String} matchedText (required)
	 * 
	 * The original text that was matched.
	 */
	
	
	/**
	 * @constructor
	 * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map).
	 */
	constructor : function( cfg ) {
		Autolinker.Util.assign( this, cfg );
	},

	
	/**
	 * Returns a string name for the type of match that this class represents.
	 * 
	 * @abstract
	 * @return {String}
	 */
	getType : Autolinker.Util.abstractMethod,
	
	
	/**
	 * Returns the original text that was matched.
	 * 
	 * @return {String}
	 */
	getMatchedText : function() {
		return this.matchedText;
	},
	

	/**
	 * Returns the anchor href that should be generated for the match.
	 * 
	 * @abstract
	 * @return {String}
	 */
	getAnchorHref : Autolinker.Util.abstractMethod,
	
	
	/**
	 * Returns the anchor text that should be generated for the match.
	 * 
	 * @abstract
	 * @return {String}
	 */
	getAnchorText : Autolinker.Util.abstractMethod

} );
/*global Autolinker */
/**
 * @class Autolinker.match.Email
 * @extends Autolinker.match.Match
 * 
 * Represents a Email match found in an input string which should be Autolinked.
 * 
 * See this class's superclass ({@link Autolinker.match.Match}) for more details.
 */
Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, {
	
	/**
	 * @cfg {String} email (required)
	 * 
	 * The email address that was matched.
	 */
	

	/**
	 * Returns a string name for the type of match that this class represents.
	 * 
	 * @return {String}
	 */
	getType : function() {
		return 'email';
	},
	
	
	/**
	 * Returns the email address that was matched.
	 * 
	 * @return {String}
	 */
	getEmail : function() {
		return this.email;
	},
	

	/**
	 * Returns the anchor href that should be generated for the match.
	 * 
	 * @return {String}
	 */
	getAnchorHref : function() {
		return 'mailto:' + this.email;
	},
	
	
	/**
	 * Returns the anchor text that should be generated for the match.
	 * 
	 * @return {String}
	 */
	getAnchorText : function() {
		return this.email;
	}
	
} );
/*global Autolinker */
/**
 * @class Autolinker.match.Hashtag
 * @extends Autolinker.match.Match
 *
 * Represents a Hashtag match found in an input string which should be
 * Autolinked.
 *
 * See this class's superclass ({@link Autolinker.match.Match}) for more
 * details.
 */
Autolinker.match.Hashtag = Autolinker.Util.extend( Autolinker.match.Match, {

	/**
	 * @cfg {String} serviceName (required)
	 *
	 * The service to point hashtag matches to. See {@link Autolinker#hashtag}
	 * for available values.
	 */

	/**
	 * @cfg {String} hashtag (required)
	 *
	 * The Hashtag that was matched, without the '#'.
	 */


	/**
	 * Returns the type of match that this class represents.
	 *
	 * @return {String}
	 */
	getType : function() {
		return 'hashtag';
	},


	/**
	 * Returns the matched hashtag.
	 *
	 * @return {String}
	 */
	getHashtag : function() {
		return this.hashtag;
	},


	/**
	 * Returns the anchor href that should be generated for the match.
	 *
	 * @return {String}
	 */
	getAnchorHref : function() {
		var serviceName = this.serviceName,
		    hashtag = this.hashtag;

		switch( serviceName ) {
			case 'twitter' :
				return 'https://twitter.com/hashtag/' + hashtag;
			case 'facebook' :
				return 'https://www.facebook.com/hashtag/' + hashtag;

			default :  // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case.
				throw new Error( 'Unknown service name to point hashtag to: ', serviceName );
		}
	},


	/**
	 * Returns the anchor text that should be generated for the match.
	 *
	 * @return {String}
	 */
	getAnchorText : function() {
		return '#' + this.hashtag;
	}

} );
/*global Autolinker */
/**
 * @class Autolinker.match.Phone
 * @extends Autolinker.match.Match
 *
 * Represents a Phone number match found in an input string which should be
 * Autolinked.
 *
 * See this class's superclass ({@link Autolinker.match.Match}) for more
 * details.
 */
Autolinker.match.Phone = Autolinker.Util.extend( Autolinker.match.Match, {

	/**
	 * @cfg {String} number (required)
	 *
	 * The phone number that was matched.
	 */


	/**
	 * Returns a string name for the type of match that this class represents.
	 *
	 * @return {String}
	 */
	getType : function() {
		return 'phone';
	},


	/**
	 * Returns the phone number that was matched.
	 *
	 * @return {String}
	 */
	getNumber: function() {
		return this.number;
	},


	/**
	 * Returns the anchor href that should be generated for the match.
	 *
	 * @return {String}
	 */
	getAnchorHref : function() {
		return 'tel:' + this.number;
	},


	/**
	 * Returns the anchor text that should be generated for the match.
	 *
	 * @return {String}
	 */
	getAnchorText : function() {
		return this.matchedText;
	}

} );

/*global Autolinker */
/**
 * @class Autolinker.match.Twitter
 * @extends Autolinker.match.Match
 * 
 * Represents a Twitter match found in an input string which should be Autolinked.
 * 
 * See this class's superclass ({@link Autolinker.match.Match}) for more details.
 */
Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, {
	
	/**
	 * @cfg {String} twitterHandle (required)
	 * 
	 * The Twitter handle that was matched.
	 */
	

	/**
	 * Returns the type of match that this class represents.
	 * 
	 * @return {String}
	 */
	getType : function() {
		return 'twitter';
	},
	
	
	/**
	 * Returns a string name for the type of match that this class represents.
	 * 
	 * @return {String}
	 */
	getTwitterHandle : function() {
		return this.twitterHandle;
	},
	

	/**
	 * Returns the anchor href that should be generated for the match.
	 * 
	 * @return {String}
	 */
	getAnchorHref : function() {
		return 'https://twitter.com/' + this.twitterHandle;
	},
	
	
	/**
	 * Returns the anchor text that should be generated for the match.
	 * 
	 * @return {String}
	 */
	getAnchorText : function() {
		return '@' + this.twitterHandle;
	}
	
} );
/*global Autolinker */
/**
 * @class Autolinker.match.Url
 * @extends Autolinker.match.Match
 * 
 * Represents a Url match found in an input string which should be Autolinked.
 * 
 * See this class's superclass ({@link Autolinker.match.Match}) for more details.
 */
Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, {
	
	/**
	 * @cfg {String} url (required)
	 * 
	 * The url that was matched.
	 */
	
	/**
	 * @cfg {Boolean} protocolUrlMatch (required)
	 * 
	 * `true` if the URL is a match which already has a protocol (i.e. 'http://'), `false` if the match was from a 'www' or
	 * known TLD match.
	 */
	
	/**
	 * @cfg {Boolean} protocolRelativeMatch (required)
	 * 
	 * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//',
	 * and will be either http:// or https:// based on the protocol that the site is loaded under.
	 */
	
	/**
	 * @cfg {Boolean} stripPrefix (required)
	 * @inheritdoc Autolinker#stripPrefix
	 */
	

	/**
	 * @private
	 * @property {RegExp} urlPrefixRegex
	 * 
	 * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs.
	 */
	urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i,
	
	/**
	 * @private
	 * @property {RegExp} protocolRelativeRegex
	 * 
	 * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes
	 * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com"
	 */
	protocolRelativeRegex : /^\/\//,
	
	/**
	 * @private
	 * @property {Boolean} protocolPrepended
	 * 
	 * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the
	 * {@link #url} did not have a protocol)
	 */
	protocolPrepended : false,
	

	/**
	 * Returns a string name for the type of match that this class represents.
	 * 
	 * @return {String}
	 */
	getType : function() {
		return 'url';
	},
	
	
	/**
	 * Returns the url that was matched, assuming the protocol to be 'http://' if the original
	 * match was missing a protocol.
	 * 
	 * @return {String}
	 */
	getUrl : function() {
		var url = this.url;
		
		// if the url string doesn't begin with a protocol, assume 'http://'
		if( !this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended ) {
			url = this.url = 'http://' + url;
			
			this.protocolPrepended = true;
		}
		
		return url;
	},
	

	/**
	 * Returns the anchor href that should be generated for the match.
	 * 
	 * @return {String}
	 */
	getAnchorHref : function() {
		var url = this.getUrl();
		
		return url.replace( /&amp;/g, '&' );  // any &amp;'s in the URL should be converted back to '&' if they were displayed as &amp; in the source html 
	},
	
	
	/**
	 * Returns the anchor text that should be generated for the match.
	 * 
	 * @return {String}
	 */
	getAnchorText : function() {
		var anchorText = this.getUrl();
		
		if( this.protocolRelativeMatch ) {
			// Strip off any protocol-relative '//' from the anchor text
			anchorText = this.stripProtocolRelativePrefix( anchorText );
		}
		if( this.stripPrefix ) {
			anchorText = this.stripUrlPrefix( anchorText );
		}
		anchorText = this.removeTrailingSlash( anchorText );  // remove trailing slash, if there is one
		
		return anchorText;
	},
	
	
	// ---------------------------------------
	
	// Utility Functionality
	
	/**
	 * Strips the URL prefix (such as "http://" or "https://") from the given text.
	 * 
	 * @private
	 * @param {String} text The text of the anchor that is being generated, for which to strip off the
	 *   url prefix (such as stripping off "http://")
	 * @return {String} The `anchorText`, with the prefix stripped.
	 */
	stripUrlPrefix : function( text ) {
		return text.replace( this.urlPrefixRegex, '' );
	},
	
	
	/**
	 * Strips any protocol-relative '//' from the anchor text.
	 * 
	 * @private
	 * @param {String} text The text of the anchor that is being generated, for which to strip off the
	 *   protocol-relative prefix (such as stripping off "//")
	 * @return {String} The `anchorText`, with the protocol-relative prefix stripped.
	 */
	stripProtocolRelativePrefix : function( text ) {
		return text.replace( this.protocolRelativeRegex, '' );
	},
	
	
	/**
	 * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.
	 * 
	 * @private
	 * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing
	 *   slash ('/') that may exist.
	 * @return {String} The `anchorText`, with the trailing slash removed.
	 */
	removeTrailingSlash : function( anchorText ) {
		if( anchorText.charAt( anchorText.length - 1 ) === '/' ) {
			anchorText = anchorText.slice( 0, -1 );
		}
		return anchorText;
	}
	
} );
return Autolinker;

}));

/**
@license
 Copyright (c) 2013 Gildas Lormeau. All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:

 1. Redistributions of source code must retain the above copyright notice,
 this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in
 the documentation and/or other materials provided with the distribution.

 3. The names of the authors may not be used to endorse or promote products
 derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
 INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

define('ThirdParty/zip',[
        '../Core/buildModuleUrl',
        '../Core/defineProperties'
    ], function(
        buildModuleUrl,
        defineProperties) {
    var tmp = {};

(function(obj) {

	var ERR_BAD_FORMAT = "File format is not recognized.";
	var ERR_ENCRYPTED = "File contains encrypted entry.";
	var ERR_ZIP64 = "File is using Zip64 (4gb+ file size).";
	var ERR_READ = "Error while reading zip file.";
	var ERR_WRITE = "Error while writing zip file.";
	var ERR_WRITE_DATA = "Error while writing file data.";
	var ERR_READ_DATA = "Error while reading file data.";
	var ERR_DUPLICATED_NAME = "File already exists.";
	var CHUNK_SIZE = 512 * 1024;

	var INFLATE_JS = "inflate.js";
	var DEFLATE_JS = "deflate.js";

	var TEXT_PLAIN = "text/plain";

	var MESSAGE_EVENT = "message";

	var appendABViewSupported;
	try {
		appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0;
	} catch (e) {
	}

	function Crc32() {
		var crc = -1, that = this;
		that.append = function(data) {
			var offset, table = that.table;
			for (offset = 0; offset < data.length; offset++)
				crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
		};
		that.get = function() {
			return ~crc;
		};
	}
	Crc32.prototype.table = (function() {
		var i, j, t, table = [];
		for (i = 0; i < 256; i++) {
			t = i;
			for (j = 0; j < 8; j++)
				if (t & 1)
					t = (t >>> 1) ^ 0xEDB88320;
				else
					t = t >>> 1;
			table[i] = t;
		}
		return table;
	})();

	function blobSlice(blob, index, length) {
		if (blob.slice)
			return blob.slice(index, index + length);
		else if (blob.webkitSlice)
			return blob.webkitSlice(index, index + length);
		else if (blob.mozSlice)
			return blob.mozSlice(index, index + length);
		else if (blob.msSlice)
			return blob.msSlice(index, index + length);
	}

	function getDataHelper(byteLength, bytes) {
		var dataBuffer, dataArray;
		dataBuffer = new ArrayBuffer(byteLength);
		dataArray = new Uint8Array(dataBuffer);
		if (bytes)
			dataArray.set(bytes, 0);
		return {
			buffer : dataBuffer,
			array : dataArray,
			view : new DataView(dataBuffer)
		};
	}

	// Readers
	function Reader() {
	}

	function TextReader(text) {
		var that = this, blobReader;

		function init(callback, onerror) {
			var blob = new Blob([ text ], {
				type : TEXT_PLAIN
			});
			blobReader = new BlobReader(blob);
			blobReader.init(function() {
				that.size = blobReader.size;
				callback();
			}, onerror);
		}

		function readUint8Array(index, length, callback, onerror) {
			blobReader.readUint8Array(index, length, callback, onerror);
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	TextReader.prototype = new Reader();
	TextReader.prototype.constructor = TextReader;

	function Data64URIReader(dataURI) {
		var that = this, dataStart;

		function init(callback) {
			var dataEnd = dataURI.length;
			while (dataURI.charAt(dataEnd - 1) == "=")
				dataEnd--;
			dataStart = dataURI.indexOf(",") + 1;
			that.size = Math.floor((dataEnd - dataStart) * 0.75);
			callback();
		}

		function readUint8Array(index, length, callback) {
			var i, data = getDataHelper(length);
			var start = Math.floor(index / 3) * 4;
			var end = Math.ceil((index + length) / 3) * 4;
			var bytes = window.atob(dataURI.substring(start + dataStart, end + dataStart));
			var delta = index - Math.floor(start / 4) * 3;
			for (i = delta; i < delta + length; i++)
				data.array[i - delta] = bytes.charCodeAt(i);
			callback(data.array);
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	Data64URIReader.prototype = new Reader();
	Data64URIReader.prototype.constructor = Data64URIReader;

	function BlobReader(blob) {
		var that = this;

		function init(callback) {
			this.size = blob.size;
			callback();
		}

		function readUint8Array(index, length, callback, onerror) {
			var reader = new FileReader();
			reader.onload = function(e) {
				callback(new Uint8Array(e.target.result));
			};
			reader.onerror = onerror;
			reader.readAsArrayBuffer(blobSlice(blob, index, length));
		}

		that.size = 0;
		that.init = init;
		that.readUint8Array = readUint8Array;
	}
	BlobReader.prototype = new Reader();
	BlobReader.prototype.constructor = BlobReader;

	// Writers

	function Writer() {
	}
	Writer.prototype.getData = function(callback) {
		callback(this.data);
	};

	function TextWriter(encoding) {
		var that = this, blob;

		function init(callback) {
			blob = new Blob([], {
				type : TEXT_PLAIN
			});
			callback();
		}

		function writeUint8Array(array, callback) {
			blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
				type : TEXT_PLAIN
			});
			callback();
		}

		function getData(callback, onerror) {
			var reader = new FileReader();
			reader.onload = function(e) {
				callback(e.target.result);
			};
			reader.onerror = onerror;
			reader.readAsText(blob, encoding);
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	TextWriter.prototype = new Writer();
	TextWriter.prototype.constructor = TextWriter;

	function Data64URIWriter(contentType) {
		var that = this, data = "", pending = "";

		function init(callback) {
			data += "data:" + (contentType || "") + ";base64,";
			callback();
		}

		function writeUint8Array(array, callback) {
			var i, delta = pending.length, dataString = pending;
			pending = "";
			for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++)
				dataString += String.fromCharCode(array[i]);
			for (; i < array.length; i++)
				pending += String.fromCharCode(array[i]);
			if (dataString.length > 2)
				data += window.btoa(dataString);
			else
				pending = dataString;
			callback();
		}

		function getData(callback) {
			callback(data + window.btoa(pending));
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	Data64URIWriter.prototype = new Writer();
	Data64URIWriter.prototype.constructor = Data64URIWriter;

	function BlobWriter(contentType) {
		var blob, that = this;

		function init(callback) {
			blob = new Blob([], {
				type : contentType
			});
			callback();
		}

		function writeUint8Array(array, callback) {
			blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
				type : contentType
			});
			callback();
		}

		function getData(callback) {
			callback(blob);
		}

		that.init = init;
		that.writeUint8Array = writeUint8Array;
		that.getData = getData;
	}
	BlobWriter.prototype = new Writer();
	BlobWriter.prototype.constructor = BlobWriter;

	// inflate/deflate core functions

	function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
		var chunkIndex = 0, index, outputSize;

		function onflush() {
			worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
			onend(outputSize);
		}

		function onmessage(event) {
			var message = event.data, data = message.data;

			if (message.onappend) {
				outputSize += data.length;
				writer.writeUint8Array(data, function() {
					onappend(false, data);
					step();
				}, onwriteerror);
			}
			if (message.onflush)
				if (data) {
					outputSize += data.length;
					writer.writeUint8Array(data, function() {
						onappend(false, data);
						onflush();
					}, onwriteerror);
				} else
					onflush();
			if (message.progress && onprogress)
				onprogress(index + message.current, size);
		}

		function step() {
			index = chunkIndex * CHUNK_SIZE;
			if (index < size)
				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
					worker.postMessage({
						append : true,
						data : array
					});
					chunkIndex++;
					if (onprogress)
						onprogress(index, size);
					onappend(true, array);
				}, onreaderror);
			else
				worker.postMessage({
					flush : true
				});
		}

		outputSize = 0;
		worker.addEventListener(MESSAGE_EVENT, onmessage, false);
		step();
	}

	function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
		var chunkIndex = 0, index, outputSize = 0;

		function step() {
			var outputData;
			index = chunkIndex * CHUNK_SIZE;
			if (index < size)
				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) {
					var outputData = process.append(inputData, function() {
						if (onprogress)
							onprogress(offset + index, size);
					});
					outputSize += outputData.length;
					onappend(true, inputData);
					writer.writeUint8Array(outputData, function() {
						onappend(false, outputData);
						chunkIndex++;
						setTimeout(step, 1);
					}, onwriteerror);
					if (onprogress)
						onprogress(index, size);
				}, onreaderror);
			else {
				outputData = process.flush();
				if (outputData) {
					outputSize += outputData.length;
					writer.writeUint8Array(outputData, function() {
						onappend(false, outputData);
						onend(outputSize);
					}, onwriteerror);
				} else
					onend(outputSize);
			}
		}

		step();
	}

	function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
		var worker, crc32 = new Crc32();

		function oninflateappend(sending, array) {
			if (computeCrc32 && !sending)
				crc32.append(array);
		}

		function oninflateend(outputSize) {
			onend(outputSize, crc32.get());
		}

		if (obj.zip.useWebWorkers) {
			worker = new Worker(obj.zip.workerScriptsPath + INFLATE_JS);
			launchWorkerProcess(worker, reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
		} else
			launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
		return worker;
	}

	function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) {
		var worker, crc32 = new Crc32();

		function ondeflateappend(sending, array) {
			if (sending)
				crc32.append(array);
		}

		function ondeflateend(outputSize) {
			onend(outputSize, crc32.get());
		}

		function onmessage() {
			worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
			launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
		}

		if (obj.zip.useWebWorkers) {
			worker = new Worker(obj.zip.workerScriptsPath + DEFLATE_JS);
			worker.addEventListener(MESSAGE_EVENT, onmessage, false);
			worker.postMessage({
				init : true,
				level : level
			});
		} else
			launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
		return worker;
	}

	function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
		var chunkIndex = 0, crc32 = new Crc32();

		function step() {
			var index = chunkIndex * CHUNK_SIZE;
			if (index < size)
				reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) {
					if (computeCrc32)
						crc32.append(array);
					if (onprogress)
						onprogress(index, size, array);
					writer.writeUint8Array(array, function() {
						chunkIndex++;
						step();
					}, onwriteerror);
				}, onreaderror);
			else
				onend(size, crc32.get());
		}

		step();
	}

	// ZipReader

	function decodeASCII(str) {
		var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB',
				'\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9',
				'\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1',
				'\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6',
				'\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3',
				'\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE',
				'\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE',
				'\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7',
				'\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ];
		for (i = 0; i < str.length; i++) {
			charCode = str.charCodeAt(i) & 0xFF;
			if (charCode > 127)
				out += extendedASCII[charCode - 128];
			else
				out += String.fromCharCode(charCode);
		}
		return out;
	}

	function decodeUTF8(string) {
		return decodeURIComponent(escape(string));
	}

	function getString(bytes) {
		var i, str = "";
		for (i = 0; i < bytes.length; i++)
			str += String.fromCharCode(bytes[i]);
		return str;
	}

	function getDate(timeRaw) {
		var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
		try {
			return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5,
					(time & 0x001F) * 2, 0);
		} catch (e) {
		}
	}

	function readCommonHeader(entry, data, index, centralDirectory, onerror) {
		entry.version = data.view.getUint16(index, true);
		entry.bitFlag = data.view.getUint16(index + 2, true);
		entry.compressionMethod = data.view.getUint16(index + 4, true);
		entry.lastModDateRaw = data.view.getUint32(index + 6, true);
		entry.lastModDate = getDate(entry.lastModDateRaw);
		if ((entry.bitFlag & 0x01) === 0x01) {
			onerror(ERR_ENCRYPTED);
			return;
		}
		if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) {
			entry.crc32 = data.view.getUint32(index + 10, true);
			entry.compressedSize = data.view.getUint32(index + 14, true);
			entry.uncompressedSize = data.view.getUint32(index + 18, true);
		}
		if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) {
			onerror(ERR_ZIP64);
			return;
		}
		entry.filenameLength = data.view.getUint16(index + 22, true);
		entry.extraFieldLength = data.view.getUint16(index + 24, true);
	}

	function createZipReader(reader, onerror) {
		function Entry() {
		}

		Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) {
			var that = this, worker;

			function terminate(callback, param) {
				if (worker)
					worker.terminate();
				worker = null;
				if (callback)
					callback(param);
			}

			function testCrc32(crc32) {
				var dataCrc32 = getDataHelper(4);
				dataCrc32.view.setUint32(0, crc32);
				return that.crc32 == dataCrc32.view.getUint32(0);
			}

			function getWriterData(uncompressedSize, crc32) {
				if (checkCrc32 && !testCrc32(crc32))
					onreaderror();
				else
					writer.getData(function(data) {
						terminate(onend, data);
					});
			}

			function onreaderror() {
				terminate(onerror, ERR_READ_DATA);
			}

			function onwriteerror() {
				terminate(onerror, ERR_WRITE_DATA);
			}

			reader.readUint8Array(that.offset, 30, function(bytes) {
				var data = getDataHelper(bytes.length, bytes), dataOffset;
				if (data.view.getUint32(0) != 0x504b0304) {
					onerror(ERR_BAD_FORMAT);
					return;
				}
				readCommonHeader(that, data, 4, false, onerror);
				dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength;
				writer.init(function() {
					if (that.compressionMethod === 0)
						copy(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
					else
						worker = inflate(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
				}, onwriteerror);
			}, onreaderror);
		};

		function seekEOCDR(offset, entriesCallback) {
			reader.readUint8Array(reader.size - offset, offset, function(bytes) {
				var dataView = getDataHelper(bytes.length, bytes).view;
				if (dataView.getUint32(0) != 0x504b0506) {
					seekEOCDR(offset + 1, entriesCallback);
				} else {
					entriesCallback(dataView);
				}
			}, function() {
				onerror(ERR_READ);
			});
		}

		return {
			getEntries : function(callback) {
				if (reader.size < 22) {
					onerror(ERR_BAD_FORMAT);
					return;
				}
				// look for End of central directory record
				seekEOCDR(22, function(dataView) {
					var datalength, fileslength;
					datalength = dataView.getUint32(16, true);
					fileslength = dataView.getUint16(8, true);
					reader.readUint8Array(datalength, reader.size - datalength, function(bytes) {
						var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes);
						for (i = 0; i < fileslength; i++) {
							entry = new Entry();
							if (data.view.getUint32(index) != 0x504b0102) {
								onerror(ERR_BAD_FORMAT);
								return;
							}
							readCommonHeader(entry, data, index + 6, true, onerror);
							entry.commentLength = data.view.getUint16(index + 32, true);
							entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10);
							entry.offset = data.view.getUint32(index + 42, true);
							filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength));
							entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename);
							if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/")
								entry.directory = true;
							comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46
									+ entry.filenameLength + entry.extraFieldLength + entry.commentLength));
							entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment);
							entries.push(entry);
							index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength;
						}
						callback(entries);
					}, function() {
						onerror(ERR_READ);
					});
				});
			},
			close : function(callback) {
				if (callback)
					callback();
			}
		};
	}

	// ZipWriter

	function encodeUTF8(string) {
		return unescape(encodeURIComponent(string));
	}

	function getBytes(str) {
		var i, array = [];
		for (i = 0; i < str.length; i++)
			array.push(str.charCodeAt(i));
		return array;
	}

	function createZipWriter(writer, onerror, dontDeflate) {
		var worker, files = {}, filenames = [], datalength = 0;

		function terminate(callback, message) {
			if (worker)
				worker.terminate();
			worker = null;
			if (callback)
				callback(message);
		}

		function onwriteerror() {
			terminate(onerror, ERR_WRITE);
		}

		function onreaderror() {
			terminate(onerror, ERR_READ_DATA);
		}

		return {
			add : function(name, reader, onend, onprogress, options) {
				var header, filename, date;

				function writeHeader(callback) {
					var data;
					date = options.lastModDate || new Date();
					header = getDataHelper(26);
					files[name] = {
						headerArray : header.array,
						directory : options.directory,
						filename : filename,
						offset : datalength,
						comment : getBytes(encodeUTF8(options.comment || ""))
					};
					header.view.setUint32(0, 0x14000808);
					if (options.version)
						header.view.setUint8(0, options.version);
					if (!dontDeflate && options.level !== 0 && !options.directory)
						header.view.setUint16(4, 0x0800);
					header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
					header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
					header.view.setUint16(22, filename.length, true);
					data = getDataHelper(30 + filename.length);
					data.view.setUint32(0, 0x504b0304);
					data.array.set(header.array, 4);
					data.array.set(filename, 30);
					datalength += data.array.length;
					writer.writeUint8Array(data.array, callback, onwriteerror);
				}

				function writeFooter(compressedLength, crc32) {
					var footer = getDataHelper(16);
					datalength += compressedLength || 0;
					footer.view.setUint32(0, 0x504b0708);
					if (typeof crc32 != "undefined") {
						header.view.setUint32(10, crc32, true);
						footer.view.setUint32(4, crc32, true);
					}
					if (reader) {
						footer.view.setUint32(8, compressedLength, true);
						header.view.setUint32(14, compressedLength, true);
						footer.view.setUint32(12, reader.size, true);
						header.view.setUint32(18, reader.size, true);
					}
					writer.writeUint8Array(footer.array, function() {
						datalength += 16;
						terminate(onend);
					}, onwriteerror);
				}

				function writeFile() {
					options = options || {};
					name = name.trim();
					if (options.directory && name.charAt(name.length - 1) != "/")
						name += "/";
					if (files.hasOwnProperty(name)) {
						onerror(ERR_DUPLICATED_NAME);
						return;
					}
					filename = getBytes(encodeUTF8(name));
					filenames.push(name);
					writeHeader(function() {
						if (reader)
							if (dontDeflate || options.level === 0)
								copy(reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
							else
								worker = deflate(reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror);
						else
							writeFooter();
					}, onwriteerror);
				}

				if (reader)
					reader.init(writeFile, onreaderror);
				else
					writeFile();
			},
			close : function(callback) {
				var data, length = 0, index = 0, indexFilename, file;
				for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
					file = files[filenames[indexFilename]];
					length += 46 + file.filename.length + file.comment.length;
				}
				data = getDataHelper(length + 22);
				for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
					file = files[filenames[indexFilename]];
					data.view.setUint32(index, 0x504b0102);
					data.view.setUint16(index + 4, 0x1400);
					data.array.set(file.headerArray, index + 6);
					data.view.setUint16(index + 32, file.comment.length, true);
					if (file.directory)
						data.view.setUint8(index + 38, 0x10);
					data.view.setUint32(index + 42, file.offset, true);
					data.array.set(file.filename, index + 46);
					data.array.set(file.comment, index + 46 + file.filename.length);
					index += 46 + file.filename.length + file.comment.length;
				}
				data.view.setUint32(index, 0x504b0506);
				data.view.setUint16(index + 8, filenames.length, true);
				data.view.setUint16(index + 10, filenames.length, true);
				data.view.setUint32(index + 12, length, true);
				data.view.setUint32(index + 16, datalength, true);
				writer.writeUint8Array(data.array, function() {
					terminate(function() {
						writer.getData(callback);
					});
				}, onwriteerror);
			}
		};
	}

	obj.zip = {
		Reader : Reader,
		Writer : Writer,
		BlobReader : BlobReader,
		Data64URIReader : Data64URIReader,
		TextReader : TextReader,
		BlobWriter : BlobWriter,
		Data64URIWriter : Data64URIWriter,
		TextWriter : TextWriter,
		createReader : function(reader, callback, onerror) {
			reader.init(function() {
				callback(createZipReader(reader, onerror));
			}, onerror);
		},
		createWriter : function(writer, callback, onerror, dontDeflate) {
			writer.init(function() {
				callback(createZipWriter(writer, onerror, dontDeflate));
			}, onerror);
		},
		useWebWorkers : true
	};

	var workerScriptsPath;

	defineProperties(obj.zip, {
        'workerScriptsPath' : {
            get : function() {
                if (typeof workerScriptsPath === 'undefined') {
                    workerScriptsPath = buildModuleUrl('ThirdParty/Workers/');
                }
                return workerScriptsPath;
            }
        }
    });

})(tmp);

    return tmp.zip;
});

/*global define*/
define('DataSources/KmlDataSource',[
        '../Core/AssociativeArray',
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/ClockRange',
        '../Core/ClockStep',
        '../Core/Color',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/Event',
        '../Core/getAbsoluteUri',
        '../Core/getExtensionFromUri',
        '../Core/getFilenameFromUri',
        '../Core/Iso8601',
        '../Core/joinUrls',
        '../Core/JulianDate',
        '../Core/loadBlob',
        '../Core/loadXML',
        '../Core/Math',
        '../Core/NearFarScalar',
        '../Core/PinBuilder',
        '../Core/PolygonHierarchy',
        '../Core/Rectangle',
        '../Core/RuntimeError',
        '../Core/TimeInterval',
        '../Core/TimeIntervalCollection',
        '../Scene/HeightReference',
        '../Scene/HorizontalOrigin',
        '../Scene/LabelStyle',
        '../Scene/SceneMode',
        '../ThirdParty/Autolinker',
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        '../ThirdParty/zip',
        './BillboardGraphics',
        './CompositePositionProperty',
        './CorridorGraphics',
        './DataSource',
        './DataSourceClock',
        './Entity',
        './EntityCluster',
        './EntityCollection',
        './LabelGraphics',
        './PathGraphics',
        './PolygonGraphics',
        './PolylineGraphics',
        './PositionPropertyArray',
        './RectangleGraphics',
        './ReferenceProperty',
        './SampledPositionProperty',
        './ScaledPositionProperty',
        './TimeIntervalCollectionProperty',
        './WallGraphics'
    ], function(
        AssociativeArray,
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Cartographic,
        ClockRange,
        ClockStep,
        Color,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Ellipsoid,
        Event,
        getAbsoluteUri,
        getExtensionFromUri,
        getFilenameFromUri,
        Iso8601,
        joinUrls,
        JulianDate,
        loadBlob,
        loadXML,
        CesiumMath,
        NearFarScalar,
        PinBuilder,
        PolygonHierarchy,
        Rectangle,
        RuntimeError,
        TimeInterval,
        TimeIntervalCollection,
        HeightReference,
        HorizontalOrigin,
        LabelStyle,
        SceneMode,
        Autolinker,
        Uri,
        when,
        zip,
        BillboardGraphics,
        CompositePositionProperty,
        CorridorGraphics,
        DataSource,
        DataSourceClock,
        Entity,
        EntityCluster,
        EntityCollection,
        LabelGraphics,
        PathGraphics,
        PolygonGraphics,
        PolylineGraphics,
        PositionPropertyArray,
        RectangleGraphics,
        ReferenceProperty,
        SampledPositionProperty,
        ScaledPositionProperty,
        TimeIntervalCollectionProperty,
        WallGraphics) {
    'use strict';

    // IE 8 doesn't have a DOM parser and can't run Cesium anyway, so just bail.
    if (typeof DOMParser === 'undefined') {
        return {};
    }

    //This is by no means an exhaustive list of MIME types.
    //The purpose of this list is to be able to accurately identify content embedded
    //in KMZ files. Eventually, we can make this configurable by the end user so they can add
    //there own content types if they have KMZ files that require it.
    var MimeTypes = {
        avi : "video/x-msvideo",
        bmp : "image/bmp",
        bz2 : "application/x-bzip2",
        chm : "application/vnd.ms-htmlhelp",
        css : "text/css",
        csv : "text/csv",
        doc : "application/msword",
        dvi : "application/x-dvi",
        eps : "application/postscript",
        flv : "video/x-flv",
        gif : "image/gif",
        gz : "application/x-gzip",
        htm : "text/html",
        html : "text/html",
        ico : "image/vnd.microsoft.icon",
        jnlp : "application/x-java-jnlp-file",
        jpeg : "image/jpeg",
        jpg : "image/jpeg",
        m3u : "audio/x-mpegurl",
        m4v : "video/mp4",
        mathml : "application/mathml+xml",
        mid : "audio/midi",
        midi : "audio/midi",
        mov : "video/quicktime",
        mp3 : "audio/mpeg",
        mp4 : "video/mp4",
        mp4v : "video/mp4",
        mpeg : "video/mpeg",
        mpg : "video/mpeg",
        odp : "application/vnd.oasis.opendocument.presentation",
        ods : "application/vnd.oasis.opendocument.spreadsheet",
        odt : "application/vnd.oasis.opendocument.text",
        ogg : "application/ogg",
        pdf : "application/pdf",
        png : "image/png",
        pps : "application/vnd.ms-powerpoint",
        ppt : "application/vnd.ms-powerpoint",
        ps : "application/postscript",
        qt : "video/quicktime",
        rdf : "application/rdf+xml",
        rss : "application/rss+xml",
        rtf : "application/rtf",
        svg : "image/svg+xml",
        swf : "application/x-shockwave-flash",
        text : "text/plain",
        tif : "image/tiff",
        tiff : "image/tiff",
        txt : "text/plain",
        wav : "audio/x-wav",
        wma : "audio/x-ms-wma",
        wmv : "video/x-ms-wmv",
        xml : "application/xml",
        zip : "application/zip",

        detectFromFilename : function(filename) {
            var ext = filename.toLowerCase();
            ext = getExtensionFromUri(ext);
            return MimeTypes[ext];
        }
    };

    var parser = new DOMParser();
    var autolinker = new Autolinker({
        stripPrefix : false,
        twitter : false,
        email : false,
        replaceFn : function(linker, match) {
            if (!match.protocolUrlMatch) {
                //Prevent matching of non-explicit urls.
                //i.e. foo.id won't match but http://foo.id will
                return false;
            }
        }
    });

    var BILLBOARD_SIZE = 32;

    var BILLBOARD_NEAR_DISTANCE = 2414016;
    var BILLBOARD_NEAR_RATIO = 1.0;
    var BILLBOARD_FAR_DISTANCE = 1.6093e+7;
    var BILLBOARD_FAR_RATIO = 0.1;

    function isZipFile(blob) {
        var magicBlob = blob.slice(0, Math.min(4, blob.size));
        var deferred = when.defer();
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            deferred.resolve(new DataView(reader.result).getUint32(0, false) === 0x504b0304);
        });
        reader.addEventListener('error', function() {
            deferred.reject(reader.error);
        });
        reader.readAsArrayBuffer(magicBlob);
        return deferred.promise;
    }

    function readBlobAsText(blob) {
        var deferred = when.defer();
        var reader = new FileReader();
        reader.addEventListener('load', function() {
            deferred.resolve(reader.result);
        });
        reader.addEventListener('error', function() {
            deferred.reject(reader.error);
        });
        reader.readAsText(blob);
        return deferred.promise;
    }

    function loadXmlFromZip(reader, entry, uriResolver, deferred) {
        entry.getData(new zip.TextWriter(), function(text) {
            uriResolver.kml = parser.parseFromString(text, 'application/xml');
            deferred.resolve();
        });
    }

    function loadDataUriFromZip(reader, entry, uriResolver, deferred) {
        var mimeType = defaultValue(MimeTypes.detectFromFilename(entry.filename), 'application/octet-stream');
        entry.getData(new zip.Data64URIWriter(mimeType), function(dataUri) {
            uriResolver[entry.filename] = dataUri;
            deferred.resolve();
        });
    }

    function replaceAttributes(div, elementType, attributeName, uriResolver) {
        var keys = uriResolver.keys;
        var baseUri = new Uri('.');
        var elements = div.querySelectorAll(elementType);
        for (var i = 0; i < elements.length; i++) {
            var element = elements[i];
            var value = element.getAttribute(attributeName);
            var uri = new Uri(value).resolve(baseUri).toString();
            var index = keys.indexOf(uri);
            if (index !== -1) {
                var key = keys[index];
                element.setAttribute(attributeName, uriResolver[key]);
                if (elementType === 'a' && element.getAttribute('download') === null) {
                    element.setAttribute('download', key);
                }
            }
        }
    }

    function proxyUrl(url, proxy) {
        if (defined(proxy)) {
            if (new Uri(url).isAbsolute()) {
                url = proxy.getURL(url);
            }
        }
        return url;
    }

    // an optional context is passed to allow for some malformed kmls (those with multiple geometries with same ids) to still parse
    // correctly, as they do in Google Earth.
    function createEntity(node, entityCollection, context) {
        var id = queryStringAttribute(node, 'id');
        id = defined(id) && id.length !== 0 ? id : createGuid();
        if(defined(context)){
            id = context + id;
        }

        // If we have a duplicate ID just generate one.
        // This isn't valid KML but Google Earth handles this case.
        var entity = entityCollection.getById(id);
        if (defined(entity)) {
            id = createGuid();
            if(defined(context)){
                id = context + id;
            }
        }

        entity = entityCollection.add(new Entity({id : id}));
        if (!defined(entity.kml)) {
            entity.addProperty('kml');
            entity.kml = new KmlFeatureData();
        }
        return entity;
    }

    function isExtrudable(altitudeMode, gxAltitudeMode) {
        return altitudeMode === 'absolute' || altitudeMode === 'relativeToGround' || gxAltitudeMode === 'relativeToSeaFloor';
    }

    function readCoordinate(value) {
        //Google Earth treats empty or missing coordinates as 0.
        if (!defined(value)) {
            return Cartesian3.fromDegrees(0, 0, 0);
        }

        var digits = value.match(/[^\s,\n]+/g);
        if (!defined(digits)) {
            return Cartesian3.fromDegrees(0, 0, 0);
        }

        var longitude = parseFloat(digits[0]);
        var latitude = parseFloat(digits[1]);
        var height = parseFloat(digits[2]);

        longitude = isNaN(longitude) ? 0.0 : longitude;
        latitude = isNaN(latitude) ? 0.0 : latitude;
        height = isNaN(height) ? 0.0 : height;

        return Cartesian3.fromDegrees(longitude, latitude, height);
    }

    function readCoordinates(element) {
        if (!defined(element)) {
            return undefined;
        }

        var tuples = element.textContent.match(/[^\s\n]+/g);
        if (!defined(tuples)) {
            return undefined;
        }

        var length = tuples.length;
        var result = new Array(length);
        var resultIndex = 0;
        for (var i = 0; i < length; i++) {
            result[resultIndex++] = readCoordinate(tuples[i]);
        }
        return result;
    }

    var kmlNamespaces = [null, undefined, 'http://www.opengis.net/kml/2.2', 'http://earth.google.com/kml/2.2', 'http://earth.google.com/kml/2.1', 'http://earth.google.com/kml/2.0'];
    var gxNamespaces = ['http://www.google.com/kml/ext/2.2'];
    var atomNamespaces = ['http://www.w3.org/2005/Atom'];
    var namespaces = {
        kml : kmlNamespaces,
        gx : gxNamespaces,
        atom : atomNamespaces,
        kmlgx : kmlNamespaces.concat(gxNamespaces)
    };

    function queryNumericAttribute(node, attributeName) {
        if (!defined(node)) {
            return undefined;
        }

        var value = node.getAttribute(attributeName);
        if (value !== null) {
            var result = parseFloat(value);
            return !isNaN(result) ? result : undefined;
        }
        return undefined;
    }

    function queryStringAttribute(node, attributeName) {
        if (!defined(node)) {
            return undefined;
        }
        var value = node.getAttribute(attributeName);
        return value !== null ? value : undefined;
    }

    function queryFirstNode(node, tagName, namespace) {
        if (!defined(node)) {
            return undefined;
        }
        var childNodes = node.childNodes;
        var length = childNodes.length;
        for (var q = 0; q < length; q++) {
            var child = childNodes[q];
            if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
                return child;
            }
        }
        return undefined;
    }

    function queryNodes(node, tagName, namespace) {
        if (!defined(node)) {
            return undefined;
        }
        var result = [];
        var childNodes = node.getElementsByTagNameNS('*', tagName);
        var length = childNodes.length;
        for (var q = 0; q < length; q++) {
            var child = childNodes[q];
            if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
                result.push(child);
            }
        }
        return result;
    }

    function queryChildNodes(node, tagName, namespace) {
        if (!defined(node)) {
            return [];
        }
        var result = [];
        var childNodes = node.childNodes;
        var length = childNodes.length;
        for (var q = 0; q < length; q++) {
            var child = childNodes[q];
            if (child.localName === tagName && namespace.indexOf(child.namespaceURI) !== -1) {
                result.push(child);
            }
        }
        return result;
    }

    function queryNumericValue(node, tagName, namespace) {
        var resultNode = queryFirstNode(node, tagName, namespace);
        if (defined(resultNode)) {
            var result = parseFloat(resultNode.textContent);
            return !isNaN(result) ? result : undefined;
        }
        return undefined;
    }

    function queryStringValue(node, tagName, namespace) {
        var result = queryFirstNode(node, tagName, namespace);
        if (defined(result)) {
            return result.textContent.trim();
        }
        return undefined;
    }

    function queryBooleanValue(node, tagName, namespace) {
        var result = queryFirstNode(node, tagName, namespace);
        if (defined(result)) {
            var value = result.textContent.trim();
            return value === '1' || /^true$/i.test(value);
        }
        return undefined;
    }

    function resolveHref(href, proxy, sourceUri, uriResolver) {
        if (!defined(href)) {
            return undefined;
        }
        var hrefResolved = false;
        if (defined(uriResolver)) {
            var blob = uriResolver[href];
            if (defined(blob)) {
                hrefResolved = true;
                href = blob;
            } else {
                // Needed for multiple levels of KML files in a KMZ
                var tmpHref = getAbsoluteUri(href, sourceUri);
                blob = uriResolver[tmpHref];
                if (defined(blob)) {
                    hrefResolved = true;
                    href = blob;
                }
            }
        }
        if (!hrefResolved && defined(sourceUri)) {
            href = getAbsoluteUri(href, getAbsoluteUri(sourceUri));
            href = proxyUrl(href, proxy);
        }
        return href;
    }

    var colorOptions = {};

    function parseColorString(value, isRandom) {
        if (!defined(value) || /^\s*$/gm.test(value)) {
            return undefined;
        }

        if (value[0] === '#') {
            value = value.substring(1);
        }

        var alpha = parseInt(value.substring(0, 2), 16) / 255.0;
        var blue = parseInt(value.substring(2, 4), 16) / 255.0;
        var green = parseInt(value.substring(4, 6), 16) / 255.0;
        var red = parseInt(value.substring(6, 8), 16) / 255.0;

        if (!isRandom) {
            return new Color(red, green, blue, alpha);
        }

        if (red > 0) {
            colorOptions.maximumRed = red;
        } else {
            colorOptions.red = 0;
        }
        if (green > 0) {
            colorOptions.maximumGreen = green;
        } else {
            colorOptions.green = 0;
        }
        if (blue > 0) {
            colorOptions.maximumBlue = blue;
        } else {
            colorOptions.blue = 0;
        }
        colorOptions.alpha = alpha;
        return Color.fromRandom(colorOptions);
    }

    function queryColorValue(node, tagName, namespace) {
        var value = queryStringValue(node, tagName, namespace);
        if (!defined(value)) {
            return undefined;
        }
        return parseColorString(value, queryStringValue(node, 'colorMode', namespace) === 'random');
    }

    function processTimeStamp(featureNode) {
        var node = queryFirstNode(featureNode, 'TimeStamp', namespaces.kmlgx);
        var whenString = queryStringValue(node, 'when', namespaces.kmlgx);

        if (!defined(node) || !defined(whenString) || whenString.length === 0) {
            return undefined;
        }

        //According to the KML spec, a TimeStamp represents a "single moment in time"
        //However, since Cesium animates much differently than Google Earth, that doesn't
        //Make much sense here.  Instead, we use the TimeStamp as the moment the feature
        //comes into existence.  This works much better and gives a similar feel to
        //GE's experience.
        var when = JulianDate.fromIso8601(whenString);
        var result = new TimeIntervalCollection();
        result.addInterval(new TimeInterval({
            start : when,
            stop : Iso8601.MAXIMUM_VALUE
        }));
        return result;
    }

    function processTimeSpan(featureNode) {
        var node = queryFirstNode(featureNode, 'TimeSpan', namespaces.kmlgx);
        if (!defined(node)) {
            return undefined;
        }
        var result;

        var beginNode = queryFirstNode(node, 'begin', namespaces.kmlgx);
        var beginDate = defined(beginNode) ? JulianDate.fromIso8601(beginNode.textContent) : undefined;

        var endNode = queryFirstNode(node, 'end', namespaces.kmlgx);
        var endDate = defined(endNode) ? JulianDate.fromIso8601(endNode.textContent) : undefined;

        if (defined(beginDate) && defined(endDate)) {
            if (JulianDate.lessThan(endDate, beginDate)) {
                var tmp = beginDate;
                beginDate = endDate;
                endDate = tmp;
            }
            result = new TimeIntervalCollection();
            result.addInterval(new TimeInterval({
                start : beginDate,
                stop : endDate
            }));
        } else if (defined(beginDate)) {
            result = new TimeIntervalCollection();
            result.addInterval(new TimeInterval({
                start : beginDate,
                stop : Iso8601.MAXIMUM_VALUE
            }));
        } else if (defined(endDate)) {
            result = new TimeIntervalCollection();
            result.addInterval(new TimeInterval({
                start : Iso8601.MINIMUM_VALUE,
                stop : endDate
            }));
        }

        return result;
    }

    function createDefaultBillboard() {
        var billboard = new BillboardGraphics();
        billboard.width = BILLBOARD_SIZE;
        billboard.height = BILLBOARD_SIZE;
        billboard.scaleByDistance = new NearFarScalar(BILLBOARD_NEAR_DISTANCE, BILLBOARD_NEAR_RATIO, BILLBOARD_FAR_DISTANCE, BILLBOARD_FAR_RATIO);
        billboard.pixelOffsetScaleByDistance = new NearFarScalar(BILLBOARD_NEAR_DISTANCE, BILLBOARD_NEAR_RATIO, BILLBOARD_FAR_DISTANCE, BILLBOARD_FAR_RATIO);
        return billboard;
    }

    function createDefaultPolygon() {
        var polygon = new PolygonGraphics();
        polygon.outline = true;
        polygon.outlineColor = Color.WHITE;
        return polygon;
    }

    function createDefaultLabel() {
        var label = new LabelGraphics();
        label.translucencyByDistance = new NearFarScalar(3000000, 1.0, 5000000, 0.0);
        label.pixelOffset = new Cartesian2(17, 0);
        label.horizontalOrigin = HorizontalOrigin.LEFT;
        label.font = '16px sans-serif';
        label.style = LabelStyle.FILL_AND_OUTLINE;
        return label;
    }

    function getIconHref(iconNode, dataSource, sourceUri, uriResolver, canRefresh) {
        var href = queryStringValue(iconNode, 'href', namespaces.kml);
        if (!defined(href) || (href.length === 0)) {
            return undefined;
        }

        if (href.indexOf('root://icons/palette-') === 0) {
            var palette = href.charAt(21);

            // Get the icon number
            var x = defaultValue(queryNumericValue(iconNode, 'x', namespaces.gx), 0);
            var y = defaultValue(queryNumericValue(iconNode, 'y', namespaces.gx), 0);
            x = Math.min(x / 32, 7);
            y = 7 - Math.min(y / 32, 7);
            var iconNum = (8 * y) + x;

            href = 'https://maps.google.com/mapfiles/kml/pal' + palette + '/icon' + iconNum + '.png';
        }

        href = resolveHref(href, dataSource._proxy, sourceUri, uriResolver);

        if (canRefresh) {
            var refreshMode = queryStringValue(iconNode, 'refreshMode', namespaces.kml);
            var viewRefreshMode = queryStringValue(iconNode, 'viewRefreshMode', namespaces.kml);
            if (refreshMode === 'onInterval' || refreshMode === 'onExpire') {
                console.log('KML - Unsupported Icon refreshMode: ' + refreshMode);
            } else if (viewRefreshMode === 'onStop' || viewRefreshMode === 'onRegion') {
                console.log('KML - Unsupported Icon viewRefreshMode: ' + viewRefreshMode);
            }

            var viewBoundScale = defaultValue(queryStringValue(iconNode, 'viewBoundScale', namespaces.kml), 1.0);
            var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : '';
            var viewFormat = defaultValue(queryStringValue(iconNode, 'viewFormat', namespaces.kml), defaultViewFormat);
            var httpQuery = queryStringValue(iconNode, 'httpQuery', namespaces.kml);
            var queryString = makeQueryString(viewFormat, httpQuery);

            var icon = joinUrls(href, queryString, false);
            return processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, icon, viewBoundScale, dataSource._lastCameraView.bbox);
        }

        return href;
    }

    function processBillboardIcon(dataSource, node, targetEntity, sourceUri, uriResolver) {
        var scale = queryNumericValue(node, 'scale', namespaces.kml);
        var heading = queryNumericValue(node, 'heading', namespaces.kml);
        var color = queryColorValue(node, 'color', namespaces.kml);

        var iconNode = queryFirstNode(node, 'Icon', namespaces.kml);
        var icon = getIconHref(iconNode, dataSource, sourceUri, uriResolver, false);
        var x = queryNumericValue(iconNode, 'x', namespaces.gx);
        var y = queryNumericValue(iconNode, 'y', namespaces.gx);
        var w = queryNumericValue(iconNode, 'w', namespaces.gx);
        var h = queryNumericValue(iconNode, 'h', namespaces.gx);

        var hotSpotNode = queryFirstNode(node, 'hotSpot', namespaces.kml);
        var hotSpotX = queryNumericAttribute(hotSpotNode, 'x');
        var hotSpotY = queryNumericAttribute(hotSpotNode, 'y');
        var hotSpotXUnit = queryStringAttribute(hotSpotNode, 'xunits');
        var hotSpotYUnit = queryStringAttribute(hotSpotNode, 'yunits');

        var billboard = targetEntity.billboard;
        if (!defined(billboard)) {
            billboard = createDefaultBillboard();
            targetEntity.billboard = billboard;
        }

        billboard.image = icon;
        billboard.scale = scale;
        billboard.color = color;

        if (defined(x) || defined(y) || defined(w) || defined(h)) {
            billboard.imageSubRegion = new BoundingRectangle(x, y, w, h);
        }

        //GE treats a heading of zero as no heading
        //You can still point north using a 360 degree angle (or any multiple of 360)
        if (defined(heading) && heading !== 0) {
            billboard.rotation = CesiumMath.toRadians(-heading);
            billboard.alignedAxis = Cartesian3.UNIT_Z;
        }

        //Hotpot is the KML equivalent of pixel offset
        //The hotspot origin is the lower left, but we leave
        //our billboard origin at the center and simply
        //modify the pixel offset to take this into account
        scale = defaultValue(scale, 1.0);

        var xOffset;
        var yOffset;
        if (defined(hotSpotX)) {
            if (hotSpotXUnit === 'pixels') {
                xOffset = -hotSpotX * scale;
            } else if (hotSpotXUnit === 'insetPixels') {
                xOffset = (hotSpotX - BILLBOARD_SIZE) * scale;
            } else if (hotSpotXUnit === 'fraction') {
                xOffset = -hotSpotX * BILLBOARD_SIZE * scale;
            }
            xOffset += BILLBOARD_SIZE * 0.5 * scale;
        }

        if (defined(hotSpotY)) {
            if (hotSpotYUnit === 'pixels') {
                yOffset = hotSpotY * scale;
            } else if (hotSpotYUnit === 'insetPixels') {
                yOffset = (-hotSpotY + BILLBOARD_SIZE) * scale;
            } else if (hotSpotYUnit === 'fraction') {
                yOffset = hotSpotY * BILLBOARD_SIZE * scale;
            }

            yOffset -= BILLBOARD_SIZE * 0.5 * scale;
        }

        if (defined(xOffset) || defined(yOffset)) {
            billboard.pixelOffset = new Cartesian2(xOffset, yOffset);
        }
    }

    function applyStyle(dataSource, styleNode, targetEntity, sourceUri, uriResolver) {
        for (var i = 0, len = styleNode.childNodes.length; i < len; i++) {
            var node = styleNode.childNodes.item(i);
            if (node.localName === 'IconStyle') {
                processBillboardIcon(dataSource, node, targetEntity, sourceUri, uriResolver);
            } else if (node.localName === 'LabelStyle') {
                var label = targetEntity.label;
                if (!defined(label)) {
                    label = createDefaultLabel();
                    targetEntity.label = label;
                }
                label.scale = defaultValue(queryNumericValue(node, 'scale', namespaces.kml), label.scale);
                label.fillColor = defaultValue(queryColorValue(node, 'color', namespaces.kml), label.fillColor);
                label.text = targetEntity.name;
            } else if (node.localName === 'LineStyle') {
                var polyline = targetEntity.polyline;
                if (!defined(polyline)) {
                    polyline = new PolylineGraphics();
                    targetEntity.polyline = polyline;
                }
                polyline.width = queryNumericValue(node, 'width', namespaces.kml);
                polyline.material = queryColorValue(node, 'color', namespaces.kml);
                if (defined(queryColorValue(node, 'outerColor', namespaces.gx))) {
                    console.log('KML - gx:outerColor is not supported in a LineStyle');
                }
                if (defined(queryNumericValue(node, 'outerWidth', namespaces.gx))) {
                    console.log('KML - gx:outerWidth is not supported in a LineStyle');
                }
                if (defined(queryNumericValue(node, 'physicalWidth', namespaces.gx))) {
                    console.log('KML - gx:physicalWidth is not supported in a LineStyle');
                }
                if (defined(queryBooleanValue(node, 'labelVisibility', namespaces.gx))) {
                    console.log('KML - gx:labelVisibility is not supported in a LineStyle');
                }
            } else if (node.localName === 'PolyStyle') {
                var polygon = targetEntity.polygon;
                if (!defined(polygon)) {
                    polygon = createDefaultPolygon();
                    targetEntity.polygon = polygon;
                }
                polygon.material = defaultValue(queryColorValue(node, 'color', namespaces.kml), polygon.material);
                polygon.fill = defaultValue(queryBooleanValue(node, 'fill', namespaces.kml), polygon.fill);
                polygon.outline = defaultValue(queryBooleanValue(node, 'outline', namespaces.kml), polygon.outline);
            } else if (node.localName === 'BalloonStyle') {
                var bgColor = defaultValue(parseColorString(queryStringValue(node, 'bgColor', namespaces.kml)), Color.WHITE);
                var textColor = defaultValue(parseColorString(queryStringValue(node, 'textColor', namespaces.kml)), Color.BLACK);
                var text = queryStringValue(node, 'text', namespaces.kml);

                //This is purely an internal property used in style processing,
                //it never ends up on the final entity.
                targetEntity.addProperty('balloonStyle');
                targetEntity.balloonStyle = {
                    bgColor : bgColor,
                    textColor : textColor,
                    text : text
                };
            } else if (node.localName === 'ListStyle') {
                var listItemType = queryStringValue(node, 'listItemType', namespaces.kml);
                if (listItemType === 'radioFolder' || listItemType === 'checkOffOnly') {
                    console.log('KML - Unsupported ListStyle with listItemType: ' + listItemType);
                }
            }
        }
    }

    //Processes and merges any inline styles for the provided node into the provided entity.
    function computeFinalStyle(entity, dataSource, placeMark, styleCollection, sourceUri, uriResolver) {
        var result = new Entity();
        var styleEntity;

        //Google earth seems to always use the last inline Style/StyleMap only
        var styleIndex = -1;
        var childNodes = placeMark.childNodes;
        var length = childNodes.length;
        for (var q = 0; q < length; q++) {
            var child = childNodes[q];
            if (child.localName === 'Style' || child.localName === 'StyleMap') {
                styleIndex = q;
            }
        }

        if (styleIndex !== -1) {
            var inlineStyleNode = childNodes[styleIndex];
            if (inlineStyleNode.localName === 'Style') {
                applyStyle(dataSource, inlineStyleNode, result, sourceUri, uriResolver);
            } else { // StyleMap
                var pairs = queryChildNodes(inlineStyleNode, 'Pair', namespaces.kml);
                for (var p = 0; p < pairs.length; p++) {
                    var pair = pairs[p];
                    var key = queryStringValue(pair, 'key', namespaces.kml);
                    if (key === 'normal') {
                        var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml);
                        if (defined(styleUrl)) {
                            styleEntity = styleCollection.getById(styleUrl);
                            if (!defined(styleEntity)) {
                                styleEntity = styleCollection.getById('#' + styleUrl);
                            }
                            if (defined(styleEntity)) {
                                result.merge(styleEntity);
                            }
                        } else {
                            var node = queryFirstNode(pair, 'Style', namespaces.kml);
                            applyStyle(dataSource, node, result, sourceUri, uriResolver);
                        }
                    } else {
                        console.log('KML - Unsupported StyleMap key: ' + key);
                    }
                }
            }
        }

        //Google earth seems to always use the first external style only.
        var externalStyle = queryStringValue(placeMark, 'styleUrl', namespaces.kml);
        if (defined(externalStyle)) {
            var id = externalStyle;
            if (externalStyle[0] !== '#' && externalStyle.indexOf('#') !== -1) {
                var tokens = externalStyle.split('#');
                var uri = tokens[0];
                if (defined(sourceUri)) {
                    uri = getAbsoluteUri(uri, getAbsoluteUri(sourceUri));
                }
                id = uri + '#' + tokens[1];
            }

            styleEntity = styleCollection.getById(id);
            if (!defined(styleEntity)) {
                styleEntity = styleCollection.getById('#' + id);
            }
            if (defined(styleEntity)) {
                result.merge(styleEntity);
            }
        }

        return result;
    }

    //Asynchronously processes an external style file.
    function processExternalStyles(dataSource, uri, styleCollection) {
        return loadXML(proxyUrl(uri, dataSource._proxy)).then(function(styleKml) {
            return processStyles(dataSource, styleKml, styleCollection, uri, true);
        });
    }

    //Processes all shared and external styles and stores
    //their id into the provided styleCollection.
    //Returns an array of promises that will resolve when
    //each style is loaded.
    function processStyles(dataSource, kml, styleCollection, sourceUri, isExternal, uriResolver) {
        var i;
        var id;
        var styleEntity;

        var node;
        var styleNodes = queryNodes(kml, 'Style', namespaces.kml);
        if (defined(styleNodes)) {
            var styleNodesLength = styleNodes.length;
            for (i = 0; i < styleNodesLength; i++) {
                node = styleNodes[i];
                id = queryStringAttribute(node, 'id');
                if (defined(id)) {
                    id = '#' + id;
                    if (isExternal && defined(sourceUri)) {
                        id = sourceUri + id;
                    }
                    if (!defined(styleCollection.getById(id))) {
                        styleEntity = new Entity({
                            id : id
                        });
                        styleCollection.add(styleEntity);
                        applyStyle(dataSource, node, styleEntity, sourceUri, uriResolver);
                    }
                }
            }
        }

        var styleMaps = queryNodes(kml, 'StyleMap', namespaces.kml);
        if (defined(styleMaps)) {
            var styleMapsLength = styleMaps.length;
            for (i = 0; i < styleMapsLength; i++) {
                var styleMap = styleMaps[i];
                id = queryStringAttribute(styleMap, 'id');
                if (defined(id)) {
                    var pairs = queryChildNodes(styleMap, 'Pair', namespaces.kml);
                    for (var p = 0; p < pairs.length; p++) {
                        var pair = pairs[p];
                        var key = queryStringValue(pair, 'key', namespaces.kml);
                        if (key === 'normal') {
                            id = '#' + id;
                            if (isExternal && defined(sourceUri)) {
                                id = sourceUri + id;
                            }
                            if (!defined(styleCollection.getById(id))) {
                                styleEntity = styleCollection.getOrCreateEntity(id);

                                var styleUrl = queryStringValue(pair, 'styleUrl', namespaces.kml);
                                if (defined(styleUrl)) {
                                    if (styleUrl[0] !== '#') {
                                        styleUrl = '#' + styleUrl;
                                    }

                                    if (isExternal && defined(sourceUri)) {
                                        styleUrl = sourceUri + styleUrl;
                                    }
                                    var base = styleCollection.getById(styleUrl);

                                    if (defined(base)) {
                                        styleEntity.merge(base);
                                    }
                                } else {
                                    node = queryFirstNode(pair, 'Style', namespaces.kml);
                                    applyStyle(dataSource, node, styleEntity, sourceUri, uriResolver);
                                }
                            }
                        } else {
                            console.log('KML - Unsupported StyleMap key: ' + key);
                        }
                    }
                }
            }
        }

        var externalStyleHash = {};
        var promises = [];
        var styleUrlNodes = kml.getElementsByTagName('styleUrl');
        var styleUrlNodesLength = styleUrlNodes.length;
        for (i = 0; i < styleUrlNodesLength; i++) {
            var styleReference = styleUrlNodes[i].textContent;
            if (styleReference[0] !== '#') {
                //According to the spec, all local styles should start with a #
                //and everything else is an external style that has a # seperating
                //the URL of the document and the style.  However, Google Earth
                //also accepts styleUrls without a # as meaning a local style.
                var tokens = styleReference.split('#');
                if (tokens.length === 2) {
                    var uri = tokens[0];
                    if (!defined(externalStyleHash[uri])) {
                        if (defined(sourceUri)) {
                            uri = getAbsoluteUri(uri, getAbsoluteUri(sourceUri));
                        }
                        promises.push(processExternalStyles(dataSource, uri, styleCollection, sourceUri));
                    }
                }
            }
        }

        return promises;
    }

    function createDropLine(entityCollection, entity, styleEntity) {
        var entityPosition = new ReferenceProperty(entityCollection, entity.id, ['position']);
        var surfacePosition = new ScaledPositionProperty(entity.position);
        entity.polyline = defined(styleEntity.polyline) ? styleEntity.polyline.clone() : new PolylineGraphics();
        entity.polyline.positions = new PositionPropertyArray([entityPosition, surfacePosition]);
    }

    function heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode) {
        if (!defined(altitudeMode) && !defined(gxAltitudeMode) || altitudeMode === 'clampToGround') {
            return HeightReference.CLAMP_TO_GROUND;
        }

        if (altitudeMode === 'relativeToGround') {
            return HeightReference.RELATIVE_TO_GROUND;
        }

        if (altitudeMode === 'absolute') {
            return HeightReference.NONE;
        }

        if (gxAltitudeMode === 'clampToSeaFloor') {
            console.log('KML - <gx:altitudeMode>:clampToSeaFloor is currently not supported, using <kml:altitudeMode>:clampToGround.');
            return HeightReference.CLAMP_TO_GROUND;
        }

        if (gxAltitudeMode === 'relativeToSeaFloor') {
            console.log('KML - <gx:altitudeMode>:relativeToSeaFloor is currently not supported, using <kml:altitudeMode>:relativeToGround.');
            return HeightReference.RELATIVE_TO_GROUND;
        }

        if (defined(altitudeMode)) {
            console.log('KML - Unknown <kml:altitudeMode>:' + altitudeMode + ', using <kml:altitudeMode>:CLAMP_TO_GROUND.');
        } else {
            console.log('KML - Unknown <gx:altitudeMode>:' + gxAltitudeMode + ', using <kml:altitudeMode>:CLAMP_TO_GROUND.');
        }

        // Clamp to ground is the default
        return HeightReference.CLAMP_TO_GROUND;
    }

    function createPositionPropertyFromAltitudeMode(property, altitudeMode, gxAltitudeMode) {
        if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') {
            //Just return the ellipsoid referenced property until we support MSL
            return property;
        }

        if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || //
            (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) {
            console.log('KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode));
        }

        // Clamp to ground is the default
        return new ScaledPositionProperty(property);
    }

    function createPositionPropertyArrayFromAltitudeMode(properties, altitudeMode, gxAltitudeMode) {
        if (!defined(properties)) {
            return undefined;
        }

        if (gxAltitudeMode === 'relativeToSeaFloor' || altitudeMode === 'absolute' || altitudeMode === 'relativeToGround') {
            //Just return the ellipsoid referenced property until we support MSL
            return properties;
        }

        if ((defined(altitudeMode) && altitudeMode !== 'clampToGround') || //
            (defined(gxAltitudeMode) && gxAltitudeMode !== 'clampToSeaFloor')) {
            console.log('KML - Unknown altitudeMode: ' + defaultValue(altitudeMode, gxAltitudeMode));
        }

        // Clamp to ground is the default
        var propertiesLength = properties.length;
        for (var i = 0; i < propertiesLength; i++) {
            var property = properties[i];
            Ellipsoid.WGS84.scaleToGeodeticSurface(property, property);
        }
        return properties;
    }

    function processPositionGraphics(dataSource, entity, styleEntity, heightReference) {
        var label = entity.label;
        if (!defined(label)) {
            label = defined(styleEntity.label) ? styleEntity.label.clone() : createDefaultLabel();
            entity.label = label;
        }
        label.text = entity.name;

        var billboard = entity.billboard;
        if (!defined(billboard)) {
            billboard = defined(styleEntity.billboard) ? styleEntity.billboard.clone() : createDefaultBillboard();
            entity.billboard = billboard;
        }

        if (!defined(billboard.image)) {
            billboard.image = dataSource._pinBuilder.fromColor(Color.YELLOW, 64);
        }

        var scale = 1.0;
        if (defined(billboard.scale)) {
            scale = billboard.scale.getValue();
            if (scale !== 0) {
                label.pixelOffset = new Cartesian2((scale * 16) + 1, 0);
            } else {
                //Minor tweaks to better match Google Earth.
                label.pixelOffset = undefined;
                label.horizontalOrigin = undefined;
            }
        }

        if (defined(heightReference) && dataSource._clampToGround) {
            billboard.heightReference = heightReference;
            label.heightReference = heightReference;
        }
    }

    function processPathGraphics(dataSource, entity, styleEntity) {
        var path = entity.path;
        if (!defined(path)) {
            path = new PathGraphics();
            path.leadTime = 0;
            entity.path = path;
        }

        var polyline = styleEntity.polyline;
        if (defined(polyline)) {
            path.material = polyline.material;
            path.width = polyline.width;
        }
    }

    function processPoint(dataSource, entityCollection, geometryNode, entity, styleEntity) {
        var coordinatesString = queryStringValue(geometryNode, 'coordinates', namespaces.kml);
        var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
        var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
        var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);

        var position = readCoordinate(coordinatesString);

        entity.position = position;
        processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode));

        if (extrude && isExtrudable(altitudeMode, gxAltitudeMode)) {
            createDropLine(entityCollection, entity, styleEntity);
        }

        return true;
    }

    function processLineStringOrLinearRing(dataSource, entityCollection, geometryNode, entity, styleEntity) {
        var coordinatesNode = queryFirstNode(geometryNode, 'coordinates', namespaces.kml);
        var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
        var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
        var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
        var tessellate = queryBooleanValue(geometryNode, 'tessellate', namespaces.kml);
        var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);

        if (defined(queryNumericValue(geometryNode, 'drawOrder', namespaces.gx))) {
            console.log('KML - gx:drawOrder is not supported in LineStrings');
        }

        var coordinates = readCoordinates(coordinatesNode);
        var polyline = styleEntity.polyline;
        if (canExtrude && extrude) {
            var wall = new WallGraphics();
            entity.wall = wall;
            wall.positions = coordinates;
            var polygon = styleEntity.polygon;

            if (defined(polygon)) {
                wall.fill = polygon.fill;
                wall.material = polygon.material;
            }

            //Always outline walls so they show up in 2D.
            wall.outline = true;
            if (defined(polyline)) {
                wall.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE;
                wall.outlineWidth = polyline.width;
            } else if (defined(polygon)) {
                wall.outlineColor = defined(polygon.material) ? polygon.material.color : Color.WHITE;
            }
        } else {
            if (dataSource._clampToGround && !canExtrude && tessellate) {
                var corridor = new CorridorGraphics();
                entity.corridor = corridor;
                corridor.positions = coordinates;
                if (defined(polyline)) {
                    corridor.material = defined(polyline.material) ? polyline.material.color.getValue(Iso8601.MINIMUM_VALUE) : Color.WHITE;
                    corridor.width = defaultValue(polyline.width, 1.0);
                } else {
                    corridor.material = Color.WHITE;
                    corridor.width = 1.0;
                }
            } else {
                polyline = defined(polyline) ? polyline.clone() : new PolylineGraphics();
                entity.polyline = polyline;
                polyline.positions = createPositionPropertyArrayFromAltitudeMode(coordinates, altitudeMode, gxAltitudeMode);
                if (!tessellate || canExtrude) {
                    polyline.followSurface = false;
                }
            }
        }

        return true;
    }

    function processPolygon(dataSource, entityCollection, geometryNode, entity, styleEntity) {
        var outerBoundaryIsNode = queryFirstNode(geometryNode, 'outerBoundaryIs', namespaces.kml);
        var linearRingNode = queryFirstNode(outerBoundaryIsNode, 'LinearRing', namespaces.kml);
        var coordinatesNode = queryFirstNode(linearRingNode, 'coordinates', namespaces.kml);
        var coordinates = readCoordinates(coordinatesNode);
        var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
        var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
        var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
        var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);

        var polygon = defined(styleEntity.polygon) ? styleEntity.polygon.clone() : createDefaultPolygon();

        var polyline = styleEntity.polyline;
        if (defined(polyline)) {
            polygon.outlineColor = defined(polyline.material) ? polyline.material.color : Color.WHITE;
            polygon.outlineWidth = polyline.width;
        }
        entity.polygon = polygon;

        if (canExtrude) {
            polygon.perPositionHeight = true;
            polygon.extrudedHeight = extrude ? 0 : undefined;
        } else if (!dataSource._clampToGround) {
            polygon.height = 0;
        }

        if (defined(coordinates)) {
            var hierarchy = new PolygonHierarchy(coordinates);
            var innerBoundaryIsNodes = queryChildNodes(geometryNode, 'innerBoundaryIs', namespaces.kml);
            for (var j = 0; j < innerBoundaryIsNodes.length; j++) {
                linearRingNode = queryChildNodes(innerBoundaryIsNodes[j], 'LinearRing', namespaces.kml);
                for (var k = 0; k < linearRingNode.length; k++) {
                    coordinatesNode = queryFirstNode(linearRingNode[k], 'coordinates', namespaces.kml);
                    coordinates = readCoordinates(coordinatesNode);
                    if (defined(coordinates)) {
                        hierarchy.holes.push(new PolygonHierarchy(coordinates));
                    }
                }
            }
            polygon.hierarchy = hierarchy;
        }

        return true;
    }

    function processTrack(dataSource, entityCollection, geometryNode, entity, styleEntity) {
        var altitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.kml);
        var gxAltitudeMode = queryStringValue(geometryNode, 'altitudeMode', namespaces.gx);
        var coordNodes = queryChildNodes(geometryNode, 'coord', namespaces.gx);
        var angleNodes = queryChildNodes(geometryNode, 'angles', namespaces.gx);
        var timeNodes = queryChildNodes(geometryNode, 'when', namespaces.kml);
        var extrude = queryBooleanValue(geometryNode, 'extrude', namespaces.kml);
        var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);

        if (angleNodes.length > 0) {
            console.log('KML - gx:angles are not supported in gx:Tracks');
        }

        var length = Math.min(coordNodes.length, timeNodes.length);
        var coordinates = [];
        var times = [];
        for (var i = 0; i < length; i++) {
            var position = readCoordinate(coordNodes[i].textContent);
            coordinates.push(position);
            times.push(JulianDate.fromIso8601(timeNodes[i].textContent));
        }
        var property = new SampledPositionProperty();
        property.addSamples(times, coordinates);
        entity.position = property;
        processPositionGraphics(dataSource, entity, styleEntity, heightReferenceFromAltitudeMode(altitudeMode, gxAltitudeMode));
        processPathGraphics(dataSource, entity, styleEntity);

        entity.availability = new TimeIntervalCollection();

        if (timeNodes.length > 0) {
            entity.availability.addInterval(new TimeInterval({
                start : times[0],
                stop : times[times.length - 1]
            }));
        }

        if (canExtrude && extrude) {
            createDropLine(entityCollection, entity, styleEntity);
        }

        return true;
    }

    function addToMultiTrack(times, positions, composite, availability, dropShowProperty, extrude, altitudeMode, gxAltitudeMode, includeEndPoints) {
        var start = times[0];
        var stop = times[times.length - 1];

        var data = new SampledPositionProperty();
        data.addSamples(times, positions);

        composite.intervals.addInterval(new TimeInterval({
            start : start,
            stop : stop,
            isStartIncluded : includeEndPoints,
            isStopIncluded : includeEndPoints,
            data : createPositionPropertyFromAltitudeMode(data, altitudeMode, gxAltitudeMode)
        }));
        availability.addInterval(new TimeInterval({
            start : start,
            stop : stop,
            isStartIncluded : includeEndPoints,
            isStopIncluded : includeEndPoints
        }));
        dropShowProperty.intervals.addInterval(new TimeInterval({
            start : start,
            stop : stop,
            isStartIncluded : includeEndPoints,
            isStopIncluded : includeEndPoints,
            data : extrude
        }));
    }

    function processMultiTrack(dataSource, entityCollection, geometryNode, entity, styleEntity) {
        // Multitrack options do not work in GE as detailed in the spec,
        // rather than altitudeMode being at the MultiTrack level,
        // GE just defers all settings to the underlying track.

        var interpolate = queryBooleanValue(geometryNode, 'interpolate', namespaces.gx);
        var trackNodes = queryChildNodes(geometryNode, 'Track', namespaces.gx);

        var times;
        var lastStop;
        var lastStopPosition;
        var needDropLine = false;
        var dropShowProperty = new TimeIntervalCollectionProperty();
        var availability = new TimeIntervalCollection();
        var composite = new CompositePositionProperty();
        for (var i = 0, len = trackNodes.length; i < len; i++) {
            var trackNode = trackNodes[i];
            var timeNodes = queryChildNodes(trackNode, 'when', namespaces.kml);
            var coordNodes = queryChildNodes(trackNode, 'coord', namespaces.gx);
            var altitudeMode = queryStringValue(trackNode, 'altitudeMode', namespaces.kml);
            var gxAltitudeMode = queryStringValue(trackNode, 'altitudeMode', namespaces.gx);
            var canExtrude = isExtrudable(altitudeMode, gxAltitudeMode);
            var extrude = queryBooleanValue(trackNode, 'extrude', namespaces.kml);

            var length = Math.min(coordNodes.length, timeNodes.length);

            var positions = [];
            times = [];
            for (var x = 0; x < length; x++) {
                var position = readCoordinate(coordNodes[x].textContent);
                positions.push(position);
                times.push(JulianDate.fromIso8601(timeNodes[x].textContent));
            }

            if (interpolate) {
                //If we are interpolating, then we need to fill in the end of
                //the last track and the beginning of this one with a sampled
                //property.  From testing in Google Earth, this property
                //is never extruded and always absolute.
                if (defined(lastStop)) {
                    addToMultiTrack([lastStop, times[0]], [lastStopPosition, positions[0]], composite, availability, dropShowProperty, false, 'absolute', undefined, false);
                }
                lastStop = times[length - 1];
                lastStopPosition = positions[positions.length - 1];
            }

            addToMultiTrack(times, positions, composite, availability, dropShowProperty, canExtrude && extrude, altitudeMode, gxAltitudeMode, true);
            needDropLine = needDropLine || (canExtrude && extrude);
        }

        entity.availability = availability;
        entity.position = composite;
        processPositionGraphics(dataSource, entity, styleEntity);
        processPathGraphics(dataSource, entity, styleEntity);
        if (needDropLine) {
            createDropLine(entityCollection, entity, styleEntity);
            entity.polyline.show = dropShowProperty;
        }

        return true;
    }

    function processMultiGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity, context) {
        var childNodes = geometryNode.childNodes;
        var hasGeometry = false;
        for (var i = 0, len = childNodes.length; i < len; i++) {
            var childNode = childNodes.item(i);
            var geometryProcessor = geometryTypes[childNode.localName];
            if (defined(geometryProcessor)) {
                var childEntity = createEntity(childNode, entityCollection, context);
                childEntity.parent = entity;
                childEntity.name = entity.name;
                childEntity.availability = entity.availability;
                childEntity.description = entity.description;
                childEntity.kml = entity.kml;
                if (geometryProcessor(dataSource, entityCollection, childNode, childEntity, styleEntity)) {
                    hasGeometry = true;
                }
            }
        }

        return hasGeometry;
    }
    function processUnsupportedGeometry(dataSource, entityCollection, geometryNode, entity, styleEntity) {
        console.log('KML - Unsupported geometry: ' + geometryNode.localName);
        return false;
    }

    function processExtendedData(node, entity) {
        var extendedDataNode = queryFirstNode(node, 'ExtendedData', namespaces.kml);

        if (!defined(extendedDataNode)) {
            return undefined;
        }

        if (defined(queryFirstNode(extendedDataNode, 'SchemaData', namespaces.kml))) {
            console.log('KML - SchemaData is unsupported');
        }
        if (defined(queryStringAttribute(extendedDataNode, 'xmlns:prefix'))) {
            console.log('KML - ExtendedData with xmlns:prefix is unsupported');
        }

        var result = {};
        var dataNodes = queryChildNodes(extendedDataNode, 'Data', namespaces.kml);
        if (defined(dataNodes)) {
            var length = dataNodes.length;
            for (var i = 0; i < length; i++) {
                var dataNode = dataNodes[i];
                var name = queryStringAttribute(dataNode, 'name');
                if (defined(name)) {
                    result[name] = {
                        displayName : queryStringValue(dataNode, 'displayName', namespaces.kml),
                        value : queryStringValue(dataNode, 'value', namespaces.kml)
                    };
                }
            }
        }
        entity.kml.extendedData = result;
    }

    var scratchDiv = document.createElement('div');

    function processDescription(node, entity, styleEntity, uriResolver) {
        var i;
        var key;
        var keys;

        var kmlData = entity.kml;
        var extendedData = kmlData.extendedData;
        var description = queryStringValue(node, 'description', namespaces.kml);

        var balloonStyle = defaultValue(entity.balloonStyle, styleEntity.balloonStyle);

        var background = Color.WHITE;
        var foreground = Color.BLACK;
        var text = description;

        if (defined(balloonStyle)) {
            background = defaultValue(balloonStyle.bgColor, Color.WHITE);
            foreground = defaultValue(balloonStyle.textColor, Color.BLACK);
            text = defaultValue(balloonStyle.text, description);
        }

        var value;
        if (defined(text)) {
            text = text.replace('$[name]', defaultValue(entity.name, ''));
            text = text.replace('$[description]', defaultValue(description, ''));
            text = text.replace('$[address]', defaultValue(kmlData.address, ''));
            text = text.replace('$[Snippet]', defaultValue(kmlData.snippet, ''));
            text = text.replace('$[id]', entity.id);

            //While not explicitly defined by the OGC spec, in Google Earth
            //The appearance of geDirections adds the directions to/from links
            //We simply replace this string with nothing.
            text = text.replace('$[geDirections]', '');

            if (defined(extendedData)) {
                var matches = text.match(/\$\[.+?\]/g);
                if (matches !== null) {
                    for (i = 0; i < matches.length; i++) {
                        var token = matches[i];
                        var propertyName = token.substr(2, token.length - 3);
                        var isDisplayName = /\/displayName$/.test(propertyName);
                        propertyName = propertyName.replace(/\/displayName$/, '');

                        value = extendedData[propertyName];
                        if (defined(value)) {
                            value = isDisplayName ? value.displayName : value.value;
                        }
                        if (defined(value)) {
                            text = text.replace(token, defaultValue(value, ''));
                        }
                    }
                }
            }
        } else if (defined(extendedData)) {
            //If no description exists, build a table out of the extended data
            keys = Object.keys(extendedData);
            if (keys.length > 0) {
                text = '<table class="cesium-infoBox-defaultTable cesium-infoBox-defaultTable-lighter"><tbody>';
                for (i = 0; i < keys.length; i++) {
                    key = keys[i];
                    value = extendedData[key];
                    text += '<tr><th>' + defaultValue(value.displayName, key) + '</th><td>' + defaultValue(value.value, '') + '</td></tr>';
                }
                text += '</tbody></table>';
            }
        }

        if (!defined(text)) {
            //No description
            return;
        }

        //Turns non-explicit links into clickable links.
        text = autolinker.link(text);

        //Use a temporary div to manipulate the links
        //so that they open in a new window.
        scratchDiv.innerHTML = text;
        var links = scratchDiv.querySelectorAll('a');
        for (i = 0; i < links.length; i++) {
            links[i].setAttribute('target', '_blank');
        }

        //Rewrite any KMZ embedded urls
        if (defined(uriResolver) && uriResolver.keys.length > 1) {
            replaceAttributes(scratchDiv, 'a', 'href', uriResolver);
            replaceAttributes(scratchDiv, 'img', 'src', uriResolver);
        }

        var tmp = '<div class="cesium-infoBox-description-lighter" style="';
        tmp += 'overflow:auto;';
        tmp += 'word-wrap:break-word;';
        tmp += 'background-color:' + background.toCssColorString() + ';';
        tmp += 'color:' + foreground.toCssColorString() + ';';
        tmp += '">';
        tmp += scratchDiv.innerHTML + '</div>';
        scratchDiv.innerHTML = '';

        //Set the final HTML as the description.
        entity.description = tmp;
    }

    function processFeature(dataSource, parent, featureNode, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var entity = createEntity(featureNode, entityCollection, context);
        var kmlData = entity.kml;
        var styleEntity = computeFinalStyle(entity, dataSource, featureNode, styleCollection, sourceUri, uriResolver);

        var name = queryStringValue(featureNode, 'name', namespaces.kml);
        entity.name = name;
        entity.parent = parent;

        var availability = processTimeSpan(featureNode);
        if (!defined(availability)) {
            availability = processTimeStamp(featureNode);
        }
        entity.availability = availability;

        mergeAvailabilityWithParent(entity);

        // Per KML spec "A Feature is visible only if it and all its ancestors are visible."
        function ancestryIsVisible(parentEntity) {
            if (!parentEntity) {
              return true;
            }
            return parentEntity.show && ancestryIsVisible(parentEntity.parent);
        }

        var visibility = queryBooleanValue(featureNode, 'visibility', namespaces.kml);
        entity.show = ancestryIsVisible(parent) && defaultValue(visibility, true);
        //var open = queryBooleanValue(featureNode, 'open', namespaces.kml);

        var authorNode = queryFirstNode(featureNode, 'author', namespaces.atom);
        var author = kmlData.author;
        author.name = queryStringValue(authorNode, 'name', namespaces.atom);
        author.uri = queryStringValue(authorNode, 'uri', namespaces.atom);
        author.email = queryStringValue(authorNode, 'email', namespaces.atom);

        var linkNode = queryFirstNode(featureNode, 'link', namespaces.atom);
        var link = kmlData.link;
        link.href = queryStringAttribute(linkNode, 'href');
        link.hreflang = queryStringAttribute(linkNode, 'hreflang');
        link.rel = queryStringAttribute(linkNode, 'rel');
        link.type = queryStringAttribute(linkNode, 'type');
        link.title = queryStringAttribute(linkNode, 'title');
        link.length = queryStringAttribute(linkNode, 'length');

        kmlData.address = queryStringValue(featureNode, 'address', namespaces.kml);
        kmlData.phoneNumber = queryStringValue(featureNode, 'phoneNumber', namespaces.kml);
        kmlData.snippet = queryStringValue(featureNode, 'Snippet', namespaces.kml);

        processExtendedData(featureNode, entity);
        processDescription(featureNode, entity, styleEntity, uriResolver);

        if (defined(queryFirstNode(featureNode, 'Camera', namespaces.kml))) {
            console.log('KML - Unsupported view: Camera');
        }
        if (defined(queryFirstNode(featureNode, 'LookAt', namespaces.kml))) {
            console.log('KML - Unsupported view: LookAt');
        }
        if (defined(queryFirstNode(featureNode, 'Region', namespaces.kml))) {
            console.log('KML - Placemark Regions are unsupported');
        }

        return {
            entity : entity,
            styleEntity : styleEntity
        };
    }

    var geometryTypes = {
        Point : processPoint,
        LineString : processLineStringOrLinearRing,
        LinearRing : processLineStringOrLinearRing,
        Polygon : processPolygon,
        Track : processTrack,
        MultiTrack : processMultiTrack,
        MultiGeometry : processMultiGeometry,
        Model : processUnsupportedGeometry
    };

    function processDocument(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var featureTypeNames = Object.keys(featureTypes);
        var featureTypeNamesLength = featureTypeNames.length;

        for (var i = 0; i < featureTypeNamesLength; i++) {
            var featureName = featureTypeNames[i];
            var processFeatureNode = featureTypes[featureName];

            var childNodes = node.childNodes;
            var length = childNodes.length;
            for (var q = 0; q < length; q++) {
                var child = childNodes[q];
                if (child.localName === featureName &&
                    ((namespaces.kml.indexOf(child.namespaceURI) !== -1) || (namespaces.gx.indexOf(child.namespaceURI) !== -1))) {
                    processFeatureNode(dataSource, parent, child, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
                }
            }
        }
    }

    function processFolder(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
        processDocument(dataSource, r.entity, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
    }

    function processPlacemark(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var r = processFeature(dataSource, parent, placemark, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
        var entity = r.entity;
        var styleEntity = r.styleEntity;

        var hasGeometry = false;
        var childNodes = placemark.childNodes;
        for (var i = 0, len = childNodes.length; i < len && !hasGeometry; i++) {
            var childNode = childNodes.item(i);
            var geometryProcessor = geometryTypes[childNode.localName];
            if (defined(geometryProcessor)) {
                // pass the placemark entity id as a context for case of defining multiple child entities together to handle case
                // where some malformed kmls reuse the same id across placemarks, which works in GE, but is not technically to spec.
                geometryProcessor(dataSource, entityCollection, childNode, entity, styleEntity, entity.id);
                hasGeometry = true;
            }
        }

        if (!hasGeometry) {
            entity.merge(styleEntity);
            processPositionGraphics(dataSource, entity, styleEntity);
        }
    }

    function processGroundOverlay(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var r = processFeature(dataSource, parent, groundOverlay, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
        var entity = r.entity;

        var geometry;
        var isLatLonQuad = false;

        var positions = readCoordinates(queryFirstNode(groundOverlay, 'LatLonQuad', namespaces.gx));
        if (defined(positions)) {
            geometry = createDefaultPolygon();
            geometry.hierarchy = new PolygonHierarchy(positions);
            entity.polygon = geometry;
            isLatLonQuad = true;
        } else {
            geometry = new RectangleGraphics();
            entity.rectangle = geometry;

            var latLonBox = queryFirstNode(groundOverlay, 'LatLonBox', namespaces.kml);
            if (defined(latLonBox)) {
                var west = queryNumericValue(latLonBox, 'west', namespaces.kml);
                var south = queryNumericValue(latLonBox, 'south', namespaces.kml);
                var east = queryNumericValue(latLonBox, 'east', namespaces.kml);
                var north = queryNumericValue(latLonBox, 'north', namespaces.kml);

                if (defined(west)) {
                    west = CesiumMath.negativePiToPi(CesiumMath.toRadians(west));
                }
                if (defined(south)) {
                    south = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(south));
                }
                if (defined(east)) {
                    east = CesiumMath.negativePiToPi(CesiumMath.toRadians(east));
                }
                if (defined(north)) {
                    north = CesiumMath.clampToLatitudeRange(CesiumMath.toRadians(north));
                }
                geometry.coordinates = new Rectangle(west, south, east, north);

                var rotation = queryNumericValue(latLonBox, 'rotation', namespaces.kml);
                if (defined(rotation)) {
                    geometry.rotation = CesiumMath.toRadians(rotation);
                }
            }
        }

        var iconNode = queryFirstNode(groundOverlay, 'Icon', namespaces.kml);
        var href = getIconHref(iconNode, dataSource, sourceUri, uriResolver, true);
        if (defined(href)) {
            if (isLatLonQuad) {
                console.log('KML - gx:LatLonQuad Icon does not support texture projection.');
            }
            var x = queryNumericValue(iconNode, 'x', namespaces.gx);
            var y = queryNumericValue(iconNode, 'y', namespaces.gx);
            var w = queryNumericValue(iconNode, 'w', namespaces.gx);
            var h = queryNumericValue(iconNode, 'h', namespaces.gx);

            if (defined(x) || defined(y) || defined(w) || defined(h)) {
                console.log('KML - gx:x, gx:y, gx:w, gx:h aren\'t supported for GroundOverlays');
            }

            geometry.material = href;
            geometry.material.color = queryColorValue(groundOverlay, 'color', namespaces.kml);
            geometry.material.transparent = true;
        } else {
            geometry.material = queryColorValue(groundOverlay, 'color', namespaces.kml);
        }

        var altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.kml);

        if (defined(altitudeMode)) {
            if (altitudeMode === 'absolute') {
                //Use height above ellipsoid until we support MSL.
                geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml);
            } else if (altitudeMode !== 'clampToGround'){
                console.log('KML - Unknown altitudeMode: ' + altitudeMode);
            }
            // else just use the default of 0 until we support 'clampToGround'
        } else {
            altitudeMode = queryStringValue(groundOverlay, 'altitudeMode', namespaces.gx);
            if (altitudeMode === 'relativeToSeaFloor') {
                console.log('KML - altitudeMode relativeToSeaFloor is currently not supported, treating as absolute.');
                geometry.height = queryNumericValue(groundOverlay, 'altitude', namespaces.kml);
            } else if (altitudeMode === 'clampToSeaFloor') {
                console.log('KML - altitudeMode clampToSeaFloor is currently not supported, treating as clampToGround.');
            } else if (defined(altitudeMode)) {
                console.log('KML - Unknown altitudeMode: ' + altitudeMode);
            }
        }
    }

    function processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        dataSource._unsupportedNode.raiseEvent(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver);
        console.log('KML - Unsupported feature: ' + node.localName);
    }

    var RefreshMode = {
        INTERVAL : 0,
        EXPIRE : 1,
        STOP : 2
    };


    function cleanupString(s) {
        if (!defined(s) || s.length === 0) {
            return '';
        }

        var sFirst = s[0];
        if (sFirst === '&') {
            s.splice(0, 1);
        }

        if(sFirst !== '?') {
            s = '?' + s;
        }

        return s;
    }

    function makeQueryString(string1, string2) {
        var result = '';
        if ((defined(string1) && string1.length > 0) || (defined(string2) && string2.length > 0)) {
            result += joinUrls(cleanupString(string1), cleanupString(string2), false);
        }

        return result;
    }

    var zeroRectangle = new Rectangle();
    var scratchCartographic = new Cartographic();
    var scratchCartesian2 = new Cartesian2();
    var scratchCartesian3 = new Cartesian3();
    function processNetworkLinkQueryString(camera, canvas, queryString, viewBoundScale, bbox) {
        function fixLatitude(value) {
            if (value < -CesiumMath.PI_OVER_TWO) {
                return -CesiumMath.PI_OVER_TWO;
            } else if (value > CesiumMath.PI_OVER_TWO) {
                return CesiumMath.PI_OVER_TWO;
            }
            return value;
        }

        function fixLongitude(value) {
            if (value > CesiumMath.PI) {
                return value - CesiumMath.TWO_PI;
            } else if (value < -CesiumMath.PI) {
                return value + CesiumMath.TWO_PI;
            }

            return value;
        }

        if (defined(camera) && camera._mode !== SceneMode.MORPHING) {
            var wgs84 = Ellipsoid.WGS84;
            var centerCartesian;
            var centerCartographic;

            bbox = defaultValue(bbox, zeroRectangle);
            if (defined(canvas)) {
                scratchCartesian2.x = canvas.clientWidth * 0.5;
                scratchCartesian2.y = canvas.clientHeight * 0.5;
                centerCartesian = camera.pickEllipsoid(scratchCartesian2, wgs84, scratchCartesian3);
            }

            if (defined(centerCartesian)) {
                centerCartographic = wgs84.cartesianToCartographic(centerCartesian, scratchCartographic);
            } else {
                centerCartographic = Rectangle.center(bbox, scratchCartographic);
                centerCartesian = wgs84.cartographicToCartesian(centerCartographic);
            }


            if (defined(viewBoundScale) && !CesiumMath.equalsEpsilon(viewBoundScale, 1.0, CesiumMath.EPSILON9)) {
                var newHalfWidth = bbox.width * viewBoundScale * 0.5;
                var newHalfHeight = bbox.height * viewBoundScale * 0.5;
                bbox = new Rectangle(fixLongitude(centerCartographic.longitude - newHalfWidth),
                    fixLatitude(centerCartographic.latitude - newHalfHeight),
                    fixLongitude(centerCartographic.longitude + newHalfWidth),
                    fixLatitude(centerCartographic.latitude + newHalfHeight)
                );
            }

            queryString = queryString.replace('[bboxWest]', CesiumMath.toDegrees(bbox.west).toString());
            queryString = queryString.replace('[bboxSouth]', CesiumMath.toDegrees(bbox.south).toString());
            queryString = queryString.replace('[bboxEast]', CesiumMath.toDegrees(bbox.east).toString());
            queryString = queryString.replace('[bboxNorth]', CesiumMath.toDegrees(bbox.north).toString());

            var lon = CesiumMath.toDegrees(centerCartographic.longitude).toString();
            var lat = CesiumMath.toDegrees(centerCartographic.latitude).toString();
            queryString = queryString.replace('[lookatLon]', lon);
            queryString = queryString.replace('[lookatLat]', lat);
            queryString = queryString.replace('[lookatTilt]', CesiumMath.toDegrees(camera.pitch).toString());
            queryString = queryString.replace('[lookatHeading]', CesiumMath.toDegrees(camera.heading).toString());
            queryString = queryString.replace('[lookatRange]', Cartesian3.distance(camera.positionWC, centerCartesian));
            queryString = queryString.replace('[lookatTerrainLon]', lon);
            queryString = queryString.replace('[lookatTerrainLat]', lat);
            queryString = queryString.replace('[lookatTerrainAlt]', centerCartographic.height.toString());

            wgs84.cartesianToCartographic(camera.positionWC, scratchCartographic);
            queryString = queryString.replace('[cameraLon]', CesiumMath.toDegrees(scratchCartographic.longitude).toString());
            queryString = queryString.replace('[cameraLat]', CesiumMath.toDegrees(scratchCartographic.latitude).toString());
            queryString = queryString.replace('[cameraAlt]', CesiumMath.toDegrees(scratchCartographic.height).toString());

            var frustum = camera.frustum;
            var aspectRatio = frustum.aspectRatio;
            var horizFov = '';
            var vertFov = '';
            if (defined(aspectRatio)) {
                var fov = CesiumMath.toDegrees(frustum.fov);
                if (aspectRatio > 1.0) {
                    horizFov = fov;
                    vertFov = fov / aspectRatio;
                } else {
                    vertFov = fov;
                    horizFov = fov * aspectRatio;
                }
            }
            queryString = queryString.replace('[horizFov]', horizFov.toString());
            queryString = queryString.replace('[vertFov]', vertFov.toString());
        } else {
            queryString = queryString.replace('[bboxWest]', '-180');
            queryString = queryString.replace('[bboxSouth]', '-90');
            queryString = queryString.replace('[bboxEast]', '180');
            queryString = queryString.replace('[bboxNorth]', '90');

            queryString = queryString.replace('[lookatLon]', '');
            queryString = queryString.replace('[lookatLat]', '');
            queryString = queryString.replace('[lookatRange]', '');
            queryString = queryString.replace('[lookatTilt]', '');
            queryString = queryString.replace('[lookatHeading]', '');
            queryString = queryString.replace('[lookatTerrainLon]', '');
            queryString = queryString.replace('[lookatTerrainLat]', '');
            queryString = queryString.replace('[lookatTerrainAlt]', '');

            queryString = queryString.replace('[cameraLon]', '');
            queryString = queryString.replace('[cameraLat]', '');
            queryString = queryString.replace('[cameraAlt]', '');
            queryString = queryString.replace('[horizFov]', '');
            queryString = queryString.replace('[vertFov]', '');
        }

        if (defined(canvas)) {
            queryString = queryString.replace('[horizPixels]', canvas.clientWidth);
            queryString = queryString.replace('[vertPixels]', canvas.clientHeight);
        } else {
            queryString = queryString.replace('[horizPixels]', '');
            queryString = queryString.replace('[vertPixels]', '');
        }

        queryString = queryString.replace('[terrainEnabled]', '1');
        queryString = queryString.replace('[clientVersion]', '1');
        queryString = queryString.replace('[kmlVersion]', '2.2');
        queryString = queryString.replace('[clientName]', 'Cesium');
        queryString = queryString.replace('[language]', 'English');

        return queryString;
    }

    function processNetworkLink(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var r = processFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
        var networkEntity = r.entity;

        var link = queryFirstNode(node, 'Link', namespaces.kml);

        if(!defined(link)){
            link = queryFirstNode(node, 'Url', namespaces.kml);
        }
        if (defined(link)) {
            var href = queryStringValue(link, 'href', namespaces.kml);
            if (defined(href)) {
                var newSourceUri = href;
                href = resolveHref(href, undefined, sourceUri, uriResolver);
                var linkUrl;

                // We need to pass in the original path if resolveHref returns a data uri because the network link
                //  references a document in a KMZ archive
                if (/^data:/.test(href)) {
                    // No need to build a query string for a data uri, just use as is
                    linkUrl = href;

                    // So if sourceUri isn't the kmz file, then its another kml in the archive, so resolve it
                    if (!/\.kmz/i.test(sourceUri)) {
                        newSourceUri = getAbsoluteUri(newSourceUri, sourceUri);
                    }
                } else {
                    newSourceUri = href; // Not a data uri so use the fully qualified uri
                    var viewRefreshMode = queryStringValue(link, 'viewRefreshMode', namespaces.kml);
                    var viewBoundScale = defaultValue(queryStringValue(link, 'viewBoundScale', namespaces.kml), 1.0);
                    var defaultViewFormat = (viewRefreshMode === 'onStop') ? 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]' : '';
                    var viewFormat = defaultValue(queryStringValue(link, 'viewFormat', namespaces.kml), defaultViewFormat);
                    var httpQuery = queryStringValue(link, 'httpQuery', namespaces.kml);
                    var queryString = makeQueryString(viewFormat, httpQuery);

                    linkUrl = processNetworkLinkQueryString(dataSource._camera, dataSource._canvas, joinUrls(href, queryString, false),
                        viewBoundScale, dataSource._lastCameraView.bbox);
                }

                var options = {
                    sourceUri : newSourceUri,
                    uriResolver : uriResolver,
                    context : networkEntity.id
                };
                var networkLinkCollection = new EntityCollection();
                var promise = load(dataSource, networkLinkCollection, linkUrl, options).then(function(rootElement) {
                    var entities = dataSource._entityCollection;
                    var newEntities = networkLinkCollection.values;
                    entities.suspendEvents();
                    for (var i = 0; i < newEntities.length; i++) {
                        var newEntity = newEntities[i];
                        if (!defined(newEntity.parent)) {
                            newEntity.parent = networkEntity;
                            mergeAvailabilityWithParent(newEntity);
                        }

                        entities.add(newEntity);
                    }
                    entities.resumeEvents();

                    // Add network links to a list if we need they will need to be updated
                    var refreshMode = queryStringValue(link, 'refreshMode', namespaces.kml);
                    var refreshInterval = defaultValue(queryNumericValue(link, 'refreshInterval', namespaces.kml), 0);
                    if ((refreshMode === 'onInterval' && refreshInterval > 0 ) || (refreshMode === 'onExpire') || (viewRefreshMode === 'onStop')) {
                        var networkLinkControl = queryFirstNode(rootElement, 'NetworkLinkControl', namespaces.kml);
                        var hasNetworkLinkControl = defined(networkLinkControl);

                        var now = JulianDate.now();
                        var networkLinkInfo = {
                            id : createGuid(),
                            href : href,
                            cookie : '',
                            queryString : queryString,
                            lastUpdated : now,
                            updating : false,
                            entity : networkEntity,
                            viewBoundScale : viewBoundScale,
                            needsUpdate : false,
                            cameraUpdateTime : now
                        };

                        var minRefreshPeriod = 0;
                        if (hasNetworkLinkControl) {
                            networkLinkInfo.cookie = defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), '');
                            minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0);
                        }

                        if (refreshMode === 'onInterval') {
                            if (hasNetworkLinkControl) {
                                refreshInterval = Math.max(minRefreshPeriod, refreshInterval);
                            }
                            networkLinkInfo.refreshMode = RefreshMode.INTERVAL;
                            networkLinkInfo.time = refreshInterval;
                        } else if (refreshMode === 'onExpire') {
                            var expires;
                            if (hasNetworkLinkControl) {
                                expires = queryStringValue(networkLinkControl, 'expires', namespaces.kml);
                            }
                            if (defined(expires)) {
                                try {
                                    var date = JulianDate.fromIso8601(expires);
                                    var diff = JulianDate.secondsDifference(date, now);
                                    if (diff > 0 && diff < minRefreshPeriod) {
                                        JulianDate.addSeconds(now, minRefreshPeriod, date);
                                    }
                                    networkLinkInfo.refreshMode = RefreshMode.EXPIRE;
                                    networkLinkInfo.time = date;
                                } catch (e) {
                                    console.log('KML - NetworkLinkControl expires is not a valid date');
                                }
                            } else {
                                console.log('KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element');
                            }
                        } else {
                            if (dataSource._camera) { // Only allow onStop refreshes if we have a camera
                                networkLinkInfo.refreshMode = RefreshMode.STOP;
                                networkLinkInfo.time = defaultValue(queryNumericValue(link, 'viewRefreshTime', namespaces.kml), 0);
                            } else {
                                console.log('A NetworkLink with viewRefreshMode=onStop requires a camera be passed in when creating the KmlDataSource');
                            }
                        }

                        if (defined(networkLinkInfo.refreshMode)) {
                            dataSource._networkLinks.set(networkLinkInfo.id, networkLinkInfo);
                        }
                    } else if (viewRefreshMode === 'onRegion'){
                        console.log('KML - Unsupported viewRefreshMode: onRegion');
                    }
                });

                promises.push(promise);
            }
        }
    }

    // Ensure Specs/Data/KML/unsupported.kml is kept up to date with these supported types
    var featureTypes = {
        Document : processDocument,
        Folder : processFolder,
        Placemark : processPlacemark,
        NetworkLink : processNetworkLink,
        GroundOverlay : processGroundOverlay,
        PhotoOverlay : processUnsupportedFeature,
        ScreenOverlay : processUnsupportedFeature,
        Tour : processUnsupportedFeature
    };

    function processFeatureNode(dataSource, node, parent, entityCollection, styleCollection, sourceUri, uriResolver, promises, context) {
        var featureProcessor = featureTypes[node.localName];
        if (defined(featureProcessor)) {
            featureProcessor(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
        } else {
            processUnsupportedFeature(dataSource, parent, node, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
        }
    }

    function loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context) {
        entityCollection.removeAll();

        var promises = [];
        var documentElement = kml.documentElement;
        var document = documentElement.localName === 'Document' ? documentElement : queryFirstNode(documentElement, 'Document', namespaces.kml);
        var name = queryStringValue(document, 'name', namespaces.kml);
        if (!defined(name) && defined(sourceUri)) {
            name = getFilenameFromUri(sourceUri);
        }

        // Only set the name from the root document
        if (!defined(dataSource._name)) {
            dataSource._name = name;
        }

        var styleCollection = new EntityCollection(dataSource);
        return when.all(processStyles(dataSource, kml, styleCollection, sourceUri, false, uriResolver, context)).then(function() {
            var element = kml.documentElement;
            if (element.localName === 'kml') {
                var childNodes = element.childNodes;
                for (var i = 0; i < childNodes.length; i++) {
                    var tmp = childNodes[i];
                    if (defined(featureTypes[tmp.localName])) {
                        element = tmp;
                        break;
                    }
                }
            }
            entityCollection.suspendEvents();
            processFeatureNode(dataSource, element, undefined, entityCollection, styleCollection, sourceUri, uriResolver, promises, context);
            entityCollection.resumeEvents();

            return when.all(promises).then(function() {
                return kml.documentElement;
            });
        });
    }

    function loadKmz(dataSource, entityCollection, blob, sourceUri) {
        var deferred = when.defer();
        zip.createReader(new zip.BlobReader(blob), function(reader) {
            reader.getEntries(function(entries) {
                var promises = [];
                var uriResolver = {};
                var docEntry;
                var docDefer;
                for (var i = 0; i < entries.length; i++) {
                    var entry = entries[i];
                    if (!entry.directory) {
                        var innerDefer = when.defer();
                        promises.push(innerDefer.promise);
                        if (/\.kml$/i.test(entry.filename)) {
                            // We use the first KML document we come across
                            //  https://developers.google.com/kml/documentation/kmzarchives
                            // Unless we come across a .kml file at the root of the archive because GE does this
                            if (!defined(docEntry) || !/\//i.test(entry.filename)) {
                                if (defined(docEntry)) {
                                    // We found one at the root so load the initial kml as a data uri
                                    loadDataUriFromZip(reader, docEntry, uriResolver, docDefer);
                                }
                                docEntry = entry;
                                docDefer = innerDefer;
                            } else {
                                // Wasn't the first kml and wasn't at the root
                                loadDataUriFromZip(reader, entry, uriResolver, innerDefer);
                            }
                        } else {
                            loadDataUriFromZip(reader, entry, uriResolver, innerDefer);
                        }
                    }
                }

                // Now load the root KML document
                if (defined(docEntry)) {
                    loadXmlFromZip(reader, docEntry, uriResolver, docDefer);
                }
                when.all(promises).then(function() {
                    reader.close();
                    if (!defined(uriResolver.kml)) {
                        deferred.reject(new RuntimeError('KMZ file does not contain a KML document.'));
                        return;
                    }
                    uriResolver.keys = Object.keys(uriResolver);
                    return loadKml(dataSource, entityCollection, uriResolver.kml, sourceUri, uriResolver);
                }).then(deferred.resolve).otherwise(deferred.reject);
            });
        }, function(e) {
            deferred.reject(e);
        });

        return deferred.promise;
    }

    function load(dataSource, entityCollection, data, options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var sourceUri = options.sourceUri;
        var uriResolver = options.uriResolver;
        var context = options.context;

        var promise = data;
        if (typeof data === 'string') {
            promise = loadBlob(proxyUrl(data, dataSource._proxy));
            sourceUri = defaultValue(sourceUri, data);
        }

        return when(promise)
            .then(function(dataToLoad) {
                if (dataToLoad instanceof Blob) {
                    return isZipFile(dataToLoad).then(function(isZip) {
                        if (isZip) {
                            return loadKmz(dataSource, entityCollection, dataToLoad, sourceUri);
                        }
                        return readBlobAsText(dataToLoad).then(function(text) {
                            //There's no official way to validate if a parse was successful.
                            //The following check detects the error on various browsers.

                            //IE raises an exception
                            var kml;
                            var error;
                            try {
                                kml = parser.parseFromString(text, 'application/xml');
                            } catch (e) {
                                error = e.toString();
                            }

                            //The parse succeeds on Chrome and Firefox, but the error
                            //handling is different in each.
                            if (defined(error) || kml.body || kml.documentElement.tagName === 'parsererror') {
                                //Firefox has error information as the firstChild nodeValue.
                                var msg = defined(error) ? error : kml.documentElement.firstChild.nodeValue;

                                //Chrome has it in the body text.
                                if (!msg) {
                                    msg = kml.body.innerText;
                                }

                                //Return the error
                                throw new RuntimeError(msg);
                            }
                            return loadKml(dataSource, entityCollection, kml, sourceUri, uriResolver, context);
                        });
                    });
                } else {
                    return loadKml(dataSource, entityCollection, dataToLoad, sourceUri, uriResolver, context);
                }
            })
            .otherwise(function(error) {
                dataSource._error.raiseEvent(dataSource, error);
                console.log(error);
                return when.reject(error);
            });
    }

    /**
     * A {@link DataSource} which processes Keyhole Markup Language 2.2 (KML).
     * <p>
     * KML support in Cesium is incomplete, but a large amount of the standard,
     * as well as Google's <code>gx</code> extension namespace, is supported. See Github issue
     * {@link https://github.com/AnalyticalGraphicsInc/cesium/issues/873|#873} for a
     * detailed list of what is and isn't support. Cesium will also write information to the
     * console when it encounters most unsupported features.
     * </p>
     * <p>
     * Non visual feature data, such as <code>atom:author</code> and <code>ExtendedData</code>
     * is exposed via an instance of {@link KmlFeatureData}, which is added to each {@link Entity}
     * under the <code>kml</code> property.
     * </p>
     *
     * @alias KmlDataSource
     * @constructor
     *
     * @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
     * @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
     * @param {DefaultProxy} [options.proxy] A proxy to be used for loading external data.
     *
     * @see {@link http://www.opengeospatial.org/standards/kml/|Open Geospatial Consortium KML Standard}
     * @see {@link https://developers.google.com/kml/|Google KML Documentation}
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=KML.html|Cesium Sandcastle KML Demo}
     *
     * @example
     * var viewer = new Cesium.Viewer('cesiumContainer');
     * viewer.dataSources.add(Cesium.KmlDataSource.load('../../SampleData/facilities.kmz',
     *      {
     *           camera: viewer.scene.camera,
     *           canvas: viewer.scene.canvas
     *      })
     * );
     */
    function KmlDataSource(options) {
        options = defaultValue(options, {});
        var camera = options.camera;
        var canvas = options.canvas;

                if (!defined(camera)) {
            throw new DeveloperError('options.camera is required.');
        }
        if (!defined(canvas)) {
            throw new DeveloperError('options.canvas is required.');
        }
        
        this._changed = new Event();
        this._error = new Event();
        this._loading = new Event();
        this._refresh = new Event();
        this._unsupportedNode = new Event();

        this._clock = undefined;
        this._entityCollection = new EntityCollection(this);
        this._name = undefined;
        this._isLoading = false;
        this._proxy = options.proxy;
        this._pinBuilder = new PinBuilder();
        this._networkLinks = new AssociativeArray();
        this._entityCluster = new EntityCluster();

        this._canvas = canvas;
        this._camera = camera;
        this._lastCameraView = {
            position : defined(camera) ? Cartesian3.clone(camera.positionWC) : undefined,
            direction : defined(camera) ? Cartesian3.clone(camera.directionWC) : undefined,
            up : defined(camera) ? Cartesian3.clone(camera.upWC) : undefined,
            bbox : defined(camera) ? camera.computeViewRectangle() : Rectangle.clone(Rectangle.MAX_VALUE)
        };
    }

    /**
     * Creates a Promise to a new instance loaded with the provided KML data.
     *
     * @param {String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
     * @param {Object} [options] An object with the following properties:
     * @param {Camera} options.camera The camera that is used for viewRefreshModes and sending camera properties to network links.
     * @param {Canvas} options.canvas The canvas that is used for sending viewer properties to network links.
     * @param {DefaultProxy} [options.proxy] A proxy to be used for loading external data.
     * @param {String} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
     * @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
     *
     * @returns {Promise.<KmlDataSource>} A promise that will resolve to a new KmlDataSource instance once the KML is loaded.
     */
    KmlDataSource.load = function(data, options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var dataSource = new KmlDataSource(options);
        return dataSource.load(data, options);
    };

    defineProperties(KmlDataSource.prototype, {
        /**
         * Gets a human-readable name for this instance.
         * This will be automatically be set to the KML document name on load.
         * @memberof KmlDataSource.prototype
         * @type {String}
         */
        name : {
            get : function() {
                return this._name;
            }
        },
        /**
         * Gets the clock settings defined by the loaded KML. This represents the total
         * availability interval for all time-dynamic data. If the KML does not contain
         * time-dynamic data, this value is undefined.
         * @memberof KmlDataSource.prototype
         * @type {DataSourceClock}
         */
        clock : {
            get : function() {
                return this._clock;
            }
        },
        /**
         * Gets the collection of {@link Entity} instances.
         * @memberof KmlDataSource.prototype
         * @type {EntityCollection}
         */
        entities : {
            get : function() {
                return this._entityCollection;
            }
        },
        /**
         * Gets a value indicating if the data source is currently loading data.
         * @memberof KmlDataSource.prototype
         * @type {Boolean}
         */
        isLoading : {
            get : function() {
                return this._isLoading;
            }
        },
        /**
         * Gets an event that will be raised when the underlying data changes.
         * @memberof KmlDataSource.prototype
         * @type {Event}
         */
        changedEvent : {
            get : function() {
                return this._changed;
            }
        },
        /**
         * Gets an event that will be raised if an error is encountered during processing.
         * @memberof KmlDataSource.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._error;
            }
        },
        /**
         * Gets an event that will be raised when the data source either starts or stops loading.
         * @memberof KmlDataSource.prototype
         * @type {Event}
         */
        loadingEvent : {
            get : function() {
                return this._loading;
            }
        },
        /**
         * Gets an event that will be raised when the data source refreshes a network link.
         * @memberof KmlDataSource.prototype
         * @type {Event}
         */
        refreshEvent : {
            get : function() {
                return this._refresh;
            }
        },
        /**
         * Gets an event that will be raised when the data source finds an unsupported node type.
         * @memberof KmlDataSource.prototype
         * @type {Event}
         */
        unsupportedNodeEvent : {
            get : function() {
                return this._unsupportedNode;
            }
        },
        /**
         * Gets whether or not this data source should be displayed.
         * @memberof KmlDataSource.prototype
         * @type {Boolean}
         */
        show : {
            get : function() {
                return this._entityCollection.show;
            },
            set : function(value) {
                this._entityCollection.show = value;
            }
        },

        /**
         * Gets or sets the clustering options for this data source. This object can be shared between multiple data sources.
         *
         * @memberof KmlDataSource.prototype
         * @type {EntityCluster}
         */
        clustering : {
            get : function() {
                return this._entityCluster;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value must be defined.');
                }
                                this._entityCluster = value;
            }
        }
    });

    /**
     * Asynchronously loads the provided KML data, replacing any existing data.
     *
     * @param {String|Document|Blob} data A url, parsed KML document, or Blob containing binary KMZ data or a parsed KML document.
     * @param {Object} [options] An object with the following properties:
     * @param {Number} [options.sourceUri] Overrides the url to use for resolving relative links and other KML network features.
     * @returns {Promise.<KmlDataSource>} A promise that will resolve to this instances once the KML is loaded.
     * @param {Boolean} [options.clampToGround=false] true if we want the geometry features (Polygons, LineStrings and LinearRings) clamped to the ground. If true, lines will use corridors so use Entity.corridor instead of Entity.polyline.
     */
    KmlDataSource.prototype.load = function(data, options) {
                if (!defined(data)) {
            throw new DeveloperError('data is required.');
        }
        
        options = defaultValue(options, {});
        DataSource.setLoading(this, true);

        var oldName = this._name;
        this._name = undefined;
        this._clampToGround = defaultValue(options.clampToGround, false);

        var that = this;
        return load(this, this._entityCollection, data, options).then(function() {
            var clock;

            var availability = that._entityCollection.computeAvailability();

            var start = availability.start;
            var stop = availability.stop;
            var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
            var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
            if (!isMinStart || !isMaxStop) {
                var date;

                //If start is min time just start at midnight this morning, local time
                if (isMinStart) {
                    date = new Date();
                    date.setHours(0, 0, 0, 0);
                    start = JulianDate.fromDate(date);
                }

                //If stop is max value just stop at midnight tonight, local time
                if (isMaxStop) {
                    date = new Date();
                    date.setHours(24, 0, 0, 0);
                    stop = JulianDate.fromDate(date);
                }

                clock = new DataSourceClock();
                clock.startTime = start;
                clock.stopTime = stop;
                clock.currentTime = JulianDate.clone(start);
                clock.clockRange = ClockRange.LOOP_STOP;
                clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;
                clock.multiplier = Math.round(Math.min(Math.max(JulianDate.secondsDifference(stop, start) / 60, 1), 3.15569e7));
            }

            var changed = false;
            if (clock !== that._clock) {
                that._clock = clock;
                changed = true;
            }

            if (oldName !== that._name) {
                changed = true;
            }

            if (changed) {
                that._changed.raiseEvent(that);
            }

            DataSource.setLoading(that, false);

            return that;
        }).otherwise(function(error) {
            DataSource.setLoading(that, false);
            that._error.raiseEvent(that, error);
            console.log(error);
            return when.reject(error);
        });
    };

    function mergeAvailabilityWithParent(child) {
        var parent = child.parent;
        if (defined(parent)) {
            var parentAvailability = parent.availability;
            if (defined(parentAvailability)) {
                var childAvailability = child.availability;
                if (defined(childAvailability)) {
                    childAvailability.intersect(parentAvailability);
                } else {
                    child.availability = parentAvailability;
                }
            }
        }
    }

    function getNetworkLinkUpdateCallback(dataSource, networkLink, newEntityCollection, networkLinks, processedHref) {
        return function(rootElement) {
            if (!networkLinks.contains(networkLink.id)) {
                // Got into the odd case where a parent network link was updated while a child
                //  network link update was in flight, so just throw it away.
                return;
            }
            var remove = false;
            var networkLinkControl = queryFirstNode(rootElement, 'NetworkLinkControl', namespaces.kml);
            var hasNetworkLinkControl = defined(networkLinkControl);

            var minRefreshPeriod = 0;
            if (hasNetworkLinkControl) {
                if (defined(queryFirstNode(networkLinkControl, 'Update', namespaces.kml))) {
                    console.log('KML - NetworkLinkControl updates aren\'t supported.');
                    networkLink.updating = false;
                    networkLinks.remove(networkLink.id);
                    return;
                }
                networkLink.cookie = defaultValue(queryStringValue(networkLinkControl, 'cookie', namespaces.kml), '');
                minRefreshPeriod = defaultValue(queryNumericValue(networkLinkControl, 'minRefreshPeriod', namespaces.kml), 0);
            }

            var now = JulianDate.now();
            var refreshMode = networkLink.refreshMode;
            if (refreshMode === RefreshMode.INTERVAL) {
                if (defined(networkLinkControl)) {
                    networkLink.time = Math.max(minRefreshPeriod, networkLink.time);
                }
            } else if (refreshMode === RefreshMode.EXPIRE) {
                var expires;
                if (defined(networkLinkControl)) {
                    expires = queryStringValue(networkLinkControl, 'expires', namespaces.kml);
                }
                if (defined(expires)) {
                    try {
                        var date = JulianDate.fromIso8601(expires);
                        var diff = JulianDate.secondsDifference(date, now);
                        if (diff > 0 && diff < minRefreshPeriod) {
                            JulianDate.addSeconds(now, minRefreshPeriod, date);
                        }
                        networkLink.time = date;
                    } catch (e) {
                        console.log('KML - NetworkLinkControl expires is not a valid date');
                        remove = true;
                    }
                } else {
                    console.log('KML - refreshMode of onExpire requires the NetworkLinkControl to have an expires element');
                    remove = true;
                }
            }

            var networkLinkEntity = networkLink.entity;
            var entityCollection = dataSource._entityCollection;
            var newEntities = newEntityCollection.values;

            function removeChildren(entity) {
                entityCollection.remove(entity);
                var children = entity._children;
                var count = children.length;
                for(var i=0;i<count;++i) {
                    removeChildren(children[i]);
                }
            }

            // Remove old entities
            entityCollection.suspendEvents();
            var entitiesCopy = entityCollection.values.slice();
            for (var i=0;i<entitiesCopy.length;++i) {
                var entityToRemove = entitiesCopy[i];
                if (entityToRemove.parent === networkLinkEntity) {
                    entityToRemove.parent = undefined;
                    removeChildren(entityToRemove);
                }
            }
            entityCollection.resumeEvents();

            // Add new entities
            entityCollection.suspendEvents();
            for (i = 0; i < newEntities.length; i++) {
                var newEntity = newEntities[i];
                if (!defined(newEntity.parent)) {
                    newEntity.parent = networkLinkEntity;
                    mergeAvailabilityWithParent(newEntity);
                }
                entityCollection.add(newEntity);
            }
            entityCollection.resumeEvents();

            // No refresh information remove it, otherwise update lastUpdate time
            if (remove) {
                networkLinks.remove(networkLink.id);
            } else {
                networkLink.lastUpdated = now;
            }

            var availability = entityCollection.computeAvailability();

            var start = availability.start;
            var stop = availability.stop;
            var isMinStart = JulianDate.equals(start, Iso8601.MINIMUM_VALUE);
            var isMaxStop = JulianDate.equals(stop, Iso8601.MAXIMUM_VALUE);
            if (!isMinStart || !isMaxStop) {
                var clock = dataSource._clock;

                if (clock.startTime !== start ||  clock.stopTime !== stop) {
                    clock.startTime = start;
                    clock.stopTime = stop;
                    dataSource._changed.raiseEvent(dataSource);
                }
            }

            networkLink.updating = false;
            networkLink.needsUpdate = false;
            dataSource._refresh.raiseEvent(dataSource, processedHref);
        };
    }

    var entitiesToIgnore = new AssociativeArray();

    /**
     * Updates any NetworkLink that require updating
     * @function
     *
     * @param {JulianDate} time The simulation time.
     * @returns {Boolean} True if this data source is ready to be displayed at the provided time, false otherwise.
     */
    KmlDataSource.prototype.update = function(time) {
        var networkLinks = this._networkLinks;
        if (networkLinks.length === 0) {
            return true;
        }

        var now = JulianDate.now();
        var that = this;

        entitiesToIgnore.removeAll();
        function recurseIgnoreEntities(entity) {
            var children = entity._children;
            var count = children.length;
            for (var i=0;i<count;++i) {
                var child = children[i];
                entitiesToIgnore.set(child.id, child);
                recurseIgnoreEntities(child);
            }
        }

        var cameraViewUpdate = false;
        var lastCameraView = this._lastCameraView;
        var camera = this._camera;
        if (defined(camera) &&
            !(camera.positionWC.equalsEpsilon(lastCameraView.position, CesiumMath.EPSILON7) &&
              camera.directionWC.equalsEpsilon(lastCameraView.direction, CesiumMath.EPSILON7) &&
              camera.upWC.equalsEpsilon(lastCameraView.up, CesiumMath.EPSILON7))) {

            // Camera has changed so update the last view
            lastCameraView.position = Cartesian3.clone(camera.positionWC);
            lastCameraView.direction = Cartesian3.clone(camera.directionWC);
            lastCameraView.up = Cartesian3.clone(camera.upWC);
            lastCameraView.bbox = camera.computeViewRectangle();
            cameraViewUpdate = true;
        }

        var newNetworkLinks = new AssociativeArray();
        var changed = false;
        networkLinks.values.forEach(function(networkLink) {
            var entity = networkLink.entity;
            if (entitiesToIgnore.contains(entity.id)) {
                return;
            }

            if (!networkLink.updating) {
                var doUpdate = false;
                if (networkLink.refreshMode === RefreshMode.INTERVAL) {
                    if (JulianDate.secondsDifference(now, networkLink.lastUpdated) > networkLink.time) {
                        doUpdate = true;
                    }
                }
                else if (networkLink.refreshMode === RefreshMode.EXPIRE) {
                    if (JulianDate.greaterThan(now, networkLink.time)) {
                        doUpdate = true;
                    }

                } else if (networkLink.refreshMode === RefreshMode.STOP) {
                    if (cameraViewUpdate) {
                        networkLink.needsUpdate = true;
                        networkLink.cameraUpdateTime = now;
                    }

                    if (networkLink.needsUpdate && JulianDate.secondsDifference(now, networkLink.cameraUpdateTime) >= networkLink.time) {
                        doUpdate = true;
                    }
                }

                if (doUpdate) {
                    recurseIgnoreEntities(entity);
                    networkLink.updating = true;
                    var newEntityCollection = new EntityCollection();
                    var href = joinUrls(networkLink.href, makeQueryString(networkLink.cookie, networkLink.queryString), false);
                    href = processNetworkLinkQueryString(that._camera, that._canvas, href, networkLink.viewBoundScale, lastCameraView.bbox);
                    load(that, newEntityCollection, href, {context: entity.id})
                        .then(getNetworkLinkUpdateCallback(that, networkLink, newEntityCollection, newNetworkLinks, href))
                        .otherwise(function(error) {
                            var msg = 'NetworkLink ' + networkLink.href + ' refresh failed: ' + error;
                            console.log(msg);
                            that._error.raiseEvent(that, msg);
                        });
                    changed = true;
                }
            }
            newNetworkLinks.set(networkLink.id, networkLink);
        });

        if (changed) {
            this._networkLinks = newNetworkLinks;
            this._changed.raiseEvent(this);
        }

        return true;
    };

    /**
     * Contains KML Feature data loaded into the <code>Entity.kml</code> property by {@link KmlDataSource}.
     * @alias KmlFeatureData
     * @constructor
     */
    function KmlFeatureData() {
        /**
         * Gets the atom syndication format author field.
         * @type Object
         */
        this.author = {
            /**
             * Gets the name.
             * @type String
             * @alias author.name
             * @memberof! KmlFeatureData#
             * @property author.name
             */
            name : undefined,
            /**
             * Gets the URI.
             * @type String
             * @alias author.uri
             * @memberof! KmlFeatureData#
             * @property author.uri
             */
            uri : undefined,
            /**
             * Gets the email.
             * @type String
             * @alias author.email
             * @memberof! KmlFeatureData#
             * @property author.email
             */
            email : undefined
        };

        /**
         * Gets the link.
         * @type Object
         */
        this.link = {
            /**
             * Gets the href.
             * @type String
             * @alias link.href
             * @memberof! KmlFeatureData#
             * @property link.href
             */
            href : undefined,
            /**
             * Gets the language of the linked resource.
             * @type String
             * @alias link.hreflang
             * @memberof! KmlFeatureData#
             * @property link.hreflang
             */
            hreflang : undefined,
            /**
             * Gets the link relation.
             * @type String
             * @alias link.rel
             * @memberof! KmlFeatureData#
             * @property link.rel
             */
            rel : undefined,
            /**
             * Gets the link type.
             * @type String
             * @alias link.type
             * @memberof! KmlFeatureData#
             * @property link.type
             */
            type : undefined,
            /**
             * Gets the link title.
             * @type String
             * @alias link.title
             * @memberof! KmlFeatureData#
             * @property link.title
             */
            title : undefined,
            /**
             * Gets the link length.
             * @type String
             * @alias link.length
             * @memberof! KmlFeatureData#
             * @property link.length
             */
            length : undefined
        };

        /**
         * Gets the unstructured address field.
         * @type String
         */
        this.address = undefined;
        /**
         * Gets the phone number.
         * @type String
         */
        this.phoneNumber = undefined;
        /**
         * Gets the snippet.
         * @type String
         */
        this.snippet = undefined;
        /**
         * Gets the extended data, parsed into a JSON object.
         * Currently only the <code>Data</code> property is supported.
         * <code>SchemaData</code> and custom data are ignored.
         * @type String
         */
        this.extendedData = undefined;
    }

    return KmlDataSource;
});

/*global define*/
define('DataSources/VelocityOrientationProperty',[
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Ellipsoid',
        '../Core/Event',
        '../Core/Matrix3',
        '../Core/Quaternion',
        '../Core/Transforms',
        './Property',
        './VelocityVectorProperty'
    ], function(
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        Ellipsoid,
        Event,
        Matrix3,
        Quaternion,
        Transforms,
        Property,
        VelocityVectorProperty) {
    'use strict';

    /**
     * A {@link Property} which evaluates to a {@link Quaternion} rotation
     * based on the velocity of the provided {@link PositionProperty}.
     *
     * @alias VelocityOrientationProperty
     * @constructor
     *
     * @param {Property} [position] The position property used to compute the orientation.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid used to determine which way is up.
     *
     * @example
     * //Create an entity with position and orientation.
     * var position = new Cesium.SampledProperty();
     * position.addSamples(...);
     * var entity = viewer.entities.add({
     *   position : position,
     *   orientation : new Cesium.VelocityOrientationProperty(position)
     * }));
     */
    function VelocityOrientationProperty(position, ellipsoid) {
        this._velocityVectorProperty = new VelocityVectorProperty(position, true);
        this._subscription = undefined;
        this._ellipsoid = undefined;
        this._definitionChanged = new Event();

        this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        var that = this;
        this._velocityVectorProperty.definitionChanged.addEventListener(function() {
            that._definitionChanged.raiseEvent(that);
        });
    }

    defineProperties(VelocityOrientationProperty.prototype, {
        /**
         * Gets a value indicating if this property is constant.
         * @memberof VelocityOrientationProperty.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        isConstant : {
            get : function() {
                return Property.isConstant(this._velocityVectorProperty);
            }
        },
        /**
         * Gets the event that is raised whenever the definition of this property changes.
         * @memberof VelocityOrientationProperty.prototype
         *
         * @type {Event}
         * @readonly
         */
        definitionChanged : {
            get : function() {
                return this._definitionChanged;
            }
        },
        /**
         * Gets or sets the position property used to compute orientation.
         * @memberof VelocityOrientationProperty.prototype
         *
         * @type {Property}
         */
        position : {
            get : function() {
                return this._velocityVectorProperty.position;
            },
            set : function(value) {
                this._velocityVectorProperty.position = value;
            }
        },
        /**
         * Gets or sets the ellipsoid used to determine which way is up.
         * @memberof VelocityOrientationProperty.prototype
         *
         * @type {Property}
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            },
            set : function(value) {
                var oldValue = this._ellipsoid;
                if (oldValue !== value) {
                    this._ellipsoid = value;
                    this._definitionChanged.raiseEvent(this);
                }
            }
        }
    });

    var positionScratch = new Cartesian3();
    var velocityScratch = new Cartesian3();
    var rotationScratch = new Matrix3();

    /**
     * Gets the value of the property at the provided time.
     *
     * @param {JulianDate} [time] The time for which to retrieve the value.
     * @param {Quaternion} [result] The object to store the value into, if omitted, a new instance is created and returned.
     * @returns {Quaternion} The modified result parameter or a new instance if the result parameter was not supplied.
     */
    VelocityOrientationProperty.prototype.getValue = function(time, result) {
        var velocity = this._velocityVectorProperty._getValue(time, velocityScratch, positionScratch);

        if (!defined(velocity)) {
            return undefined;
        }

        Transforms.rotationMatrixFromPositionVelocity(positionScratch, velocity, this._ellipsoid, rotationScratch);
        return Quaternion.fromRotationMatrix(rotationScratch, result);
    };

    /**
     * Compares this property to the provided property and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {Property} [other] The other property.
     * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
     */
    VelocityOrientationProperty.prototype.equals = function(other) {
        return this === other ||//
               (other instanceof VelocityOrientationProperty &&
                Property.equals(this._velocityVectorProperty, other._velocityVectorProperty) &&
                (this._ellipsoid === other._ellipsoid ||
                 this._ellipsoid.equals(other._ellipsoid)));
    };

    return VelocityOrientationProperty;
});

/*global define*/
define('DataSources/Visualizer',[
        '../Core/DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * Defines the interface for visualizers. Visualizers are plug-ins to
     * {@link DataSourceDisplay} that render data associated with
     * {@link DataSource} instances.
     * This object is an interface for documentation purposes and is not intended
     * to be instantiated directly.
     * @alias Visualizer
     * @constructor
     *
     * @see BillboardVisualizer
     * @see LabelVisualizer
     * @see ModelVisualizer
     * @see PathVisualizer
     * @see PointVisualizer
     * @see GeometryVisualizer
     */
    function Visualizer() {
        DeveloperError.throwInstantiationError();
    }

    /**
     * Updates the visualization to the provided time.
     * @function
     *
     * @param {JulianDate} time The time.
     *
     * @returns {Boolean} True if the display was updated to the provided time,
     * false if the visualizer is waiting for an asynchronous operation to
     * complete before data can be updated.
     */
    Visualizer.prototype.update = DeveloperError.throwInstantiationError;

    /**
     * Computes a bounding sphere which encloses the visualization produced for the specified entity.
     * The bounding sphere is in the fixed frame of the scene's globe.
     *
     * @param {Entity} entity The entity whose bounding sphere to compute.
     * @param {BoundingSphere} result The bounding sphere onto which to store the result.
     * @returns {BoundingSphereState} BoundingSphereState.DONE if the result contains the bounding sphere,
     *                       BoundingSphereState.PENDING if the result is still being computed, or
     *                       BoundingSphereState.FAILED if the entity has no visualization in the current scene.
     * @private
     */
    Visualizer.prototype.getBoundingSphere = DeveloperError.throwInstantiationError;

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * @function
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     */
    Visualizer.prototype.isDestroyed = DeveloperError.throwInstantiationError;

    /**
     * Removes all visualization and cleans up any resources associated with this instance.
     * @function
     */
    Visualizer.prototype.destroy = DeveloperError.throwInstantiationError;

    return Visualizer;
});

/*global define*/
define('Renderer/ClearCommand',[
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/freezeObject'
    ], function(
        Color,
        defaultValue,
        freezeObject) {
    'use strict';

    /**
     * Represents a command to the renderer for clearing a framebuffer.
     *
     * @private
     */
    function ClearCommand(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The value to clear the color buffer to.  When <code>undefined</code>, the color buffer is not cleared.
         *
         * @type {Color}
         *
         * @default undefined
         */
        this.color = options.color;

        /**
         * The value to clear the depth buffer to.  When <code>undefined</code>, the depth buffer is not cleared.
         *
         * @type {Number}
         *
         * @default undefined
         */
        this.depth = options.depth;

        /**
         * The value to clear the stencil buffer to.  When <code>undefined</code>, the stencil buffer is not cleared.
         *
         * @type {Number}
         *
         * @default undefined
         */
        this.stencil = options.stencil;

        /**
         * The render state to apply when executing the clear command.  The following states affect clearing:
         * scissor test, color mask, depth mask, and stencil mask.  When the render state is
         * <code>undefined</code>, the default render state is used.
         *
         * @type {RenderState}
         *
         * @default undefined
         */
        this.renderState = options.renderState;

        /**
         * The framebuffer to clear.
         *
         * @type {Framebuffer}
         *
         * @default undefined
         */
        this.framebuffer = options.framebuffer;

        /**
         * The object who created this command.  This is useful for debugging command
         * execution; it allows you to see who created a command when you only have a
         * reference to the command, and can be used to selectively execute commands
         * with {@link Scene#debugCommandFilter}.
         *
         * @type {Object}
         *
         * @default undefined
         *
         * @see Scene#debugCommandFilter
         */
        this.owner = options.owner;
    }

    /**
     * Clears color to (0.0, 0.0, 0.0, 0.0); depth to 1.0; and stencil to 0.
     *
     * @type {ClearCommand}
     *
     * @constant
     */
    ClearCommand.ALL = freezeObject(new ClearCommand({
        color : new Color(0.0, 0.0, 0.0, 0.0),
        depth : 1.0,
        stencil : 0.0
    }));

    ClearCommand.prototype.execute = function(context, passState) {
        context.clear(this, passState);
    };

    return ClearCommand;
});

/*global define*/
define('Renderer/ComputeCommand',[
        '../Core/defaultValue',
        './Pass'
    ], function(
        defaultValue,
        Pass) {
    'use strict';

    /**
     * Represents a command to the renderer for GPU Compute (using old-school GPGPU).
     *
     * @private
     */
    function ComputeCommand(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The vertex array. If none is provided, a viewport quad will be used.
         *
         * @type {VertexArray}
         * @default undefined
         */
        this.vertexArray = options.vertexArray;

        /**
         * The fragment shader source. The default vertex shader is ViewportQuadVS.
         *
         * @type {ShaderSource}
         * @default undefined
         */
        this.fragmentShaderSource = options.fragmentShaderSource;

        /**
         * The shader program to apply.
         *
         * @type {ShaderProgram}
         * @default undefined
         */
        this.shaderProgram = options.shaderProgram;

        /**
         * An object with functions whose names match the uniforms in the shader program
         * and return values to set those uniforms.
         *
         * @type {Object}
         * @default undefined
         */
        this.uniformMap = options.uniformMap;

        /**
         * Texture to use for offscreen rendering.
         *
         * @type {Texture}
         * @default undefined
         */
        this.outputTexture = options.outputTexture;

        /**
         * Function that is called immediately before the ComputeCommand is executed. Used to
         * update any renderer resources. Takes the ComputeCommand as its single argument.
         *
         * @type {Function}
         * @default undefined
         */
        this.preExecute = options.preExecute;

        /**
         * Function that is called after the ComputeCommand is executed. Takes the output
         * texture as its single argument.
         *
         * @type {Function}
         * @default undefined
         */
        this.postExecute = options.postExecute;

        /**
         * Whether the renderer resources will persist beyond this call. If not, they
         * will be destroyed after completion.
         *
         * @type {Boolean}
         * @default false
         */
        this.persists = defaultValue(options.persists, false);

        /**
         * The pass when to render. Always compute pass.
         *
         * @type {Pass}
         * @default Pass.COMPUTE;
         */
        this.pass = Pass.COMPUTE;

        /**
         * The object who created this command.  This is useful for debugging command
         * execution; it allows us to see who created a command when we only have a
         * reference to the command, and can be used to selectively execute commands
         * with {@link Scene#debugCommandFilter}.
         *
         * @type {Object}
         * @default undefined
         *
         * @see Scene#debugCommandFilter
         */
        this.owner = options.owner;
    }

    /**
     * Executes the compute command.
     *
     * @param {Context} computeEngine The context that processes the compute command.
     */
    ComputeCommand.prototype.execute = function(computeEngine) {
        computeEngine.execute(this);
    };

    return ComputeCommand;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ViewportQuadVS',[],function() {
    'use strict';
    return "attribute vec4 position;\n\
attribute vec2 textureCoordinates;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main() \n\
{\n\
    gl_Position = position;\n\
    v_textureCoordinates = textureCoordinates;\n\
}\n\
";
});
/*global define*/
define('Renderer/ComputeEngine',[
        '../Core/BoundingRectangle',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/PrimitiveType',
        '../Shaders/ViewportQuadVS',
        './ClearCommand',
        './DrawCommand',
        './Framebuffer',
        './RenderState',
        './ShaderProgram'
    ], function(
        BoundingRectangle,
        Color,
        defined,
        destroyObject,
        DeveloperError,
        PrimitiveType,
        ViewportQuadVS,
        ClearCommand,
        DrawCommand,
        Framebuffer,
        RenderState,
        ShaderProgram) {
    'use strict';

    /**
     * @private
     */
    function ComputeEngine(context) {
        this._context = context;
    }

    var renderStateScratch;
    var drawCommandScratch = new DrawCommand({
        primitiveType : PrimitiveType.TRIANGLES
    });
    var clearCommandScratch = new ClearCommand({
        color : new Color(0.0, 0.0, 0.0, 0.0)
    });

    function createFramebuffer(context, outputTexture) {
        return new Framebuffer({
            context : context,
            colorTextures : [outputTexture],
            destroyAttachments : false
        });
    }

    function createViewportQuadShader(context, fragmentShaderSource) {
        return ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : ViewportQuadVS,
            fragmentShaderSource : fragmentShaderSource,
            attributeLocations : {
                position : 0,
                textureCoordinates : 1
            }
        });
    }

    function createRenderState(width, height) {
        if ((!defined(renderStateScratch)) ||
            (renderStateScratch.viewport.width !== width) ||
            (renderStateScratch.viewport.height !== height)) {

            renderStateScratch = RenderState.fromCache({
                viewport : new BoundingRectangle(0, 0, width, height)
            });
        }
        return renderStateScratch;
    }

    ComputeEngine.prototype.execute = function(computeCommand) {
                if (!defined(computeCommand)) {
            throw new DeveloperError('computeCommand is required.');
        }
        
        // This may modify the command's resources, so do error checking afterwards
        if (defined(computeCommand.preExecute)) {
            computeCommand.preExecute(computeCommand);
        }

                if (!defined(computeCommand.fragmentShaderSource) && !defined(computeCommand.shaderProgram)) {
            throw new DeveloperError('computeCommand.fragmentShaderSource or computeCommand.shaderProgram is required.');
        }

        if (!defined(computeCommand.outputTexture)) {
            throw new DeveloperError('computeCommand.outputTexture is required.');
        }
        
        var outputTexture = computeCommand.outputTexture;
        var width = outputTexture.width;
        var height = outputTexture.height;

        var context = this._context;
        var vertexArray = defined(computeCommand.vertexArray) ? computeCommand.vertexArray : context.getViewportQuadVertexArray();
        var shaderProgram = defined(computeCommand.shaderProgram) ? computeCommand.shaderProgram : createViewportQuadShader(context, computeCommand.fragmentShaderSource);
        var framebuffer = createFramebuffer(context, outputTexture);
        var renderState = createRenderState(width, height);
        var uniformMap = computeCommand.uniformMap;

        var clearCommand = clearCommandScratch;
        clearCommand.framebuffer = framebuffer;
        clearCommand.renderState = renderState;
        clearCommand.execute(context);

        var drawCommand = drawCommandScratch;
        drawCommand.vertexArray = vertexArray;
        drawCommand.renderState = renderState;
        drawCommand.shaderProgram = shaderProgram;
        drawCommand.uniformMap = uniformMap;
        drawCommand.framebuffer = framebuffer;
        drawCommand.execute(context);

        framebuffer.destroy();

        if (!computeCommand.persists) {
            shaderProgram.destroy();
            if (defined(computeCommand.vertexArray)) {
                vertexArray.destroy();
            }
        }

        if (defined(computeCommand.postExecute)) {
            computeCommand.postExecute(outputTexture);
        }
    };

    ComputeEngine.prototype.isDestroyed = function() {
        return false;
    };

    ComputeEngine.prototype.destroy = function() {
        return destroyObject(this);
    };

    return ComputeEngine;
});

/*global define*/
define('Renderer/PassState',[], function() {
    'use strict';

    /**
     * The state for a particular rendering pass.  This is used to supplement the state
     * in a command being executed.
     *
     * @private
     */
    function PassState(context) {
        /**
         * The context used to execute commands for this pass.
         *
         * @type {Context}
         */
        this.context = context;

        /**
         * The framebuffer to render to.  This framebuffer is used unless a {@link DrawCommand}
         * or {@link ClearCommand} explicitly define a framebuffer, which is used for off-screen
         * rendering.
         *
         * @type {Framebuffer}
         * @default undefined
         */
        this.framebuffer = undefined;

        /**
         * When defined, this overrides the blending property of a {@link DrawCommand}'s render state.
         * This is used to, for example, to allow the renderer to turn off blending during the picking pass.
         * <p>
         * When this is <code>undefined</code>, the {@link DrawCommand}'s property is used.
         * </p>
         *
         * @type {Boolean}
         * @default undefined
         */
        this.blendingEnabled = undefined;

        /**
         * When defined, this overrides the scissor test property of a {@link DrawCommand}'s render state.
         * This is used to, for example, to allow the renderer to scissor out the pick region during the picking pass.
         * <p>
         * When this is <code>undefined</code>, the {@link DrawCommand}'s property is used.
         * </p>
         *
         * @type {Object}
         * @default undefined
         */
        this.scissorTest = undefined;

        /**
         * The viewport used when one is not defined by a {@link DrawCommand}'s render state.
         * @type {BoundingRectangle}
         * @default undefined
         */
        this.viewport = undefined;
    }

    return PassState;
});

/*global define*/
define('Renderer/RenderbufferFormat',[
        '../Core/freezeObject',
        '../Core/WebGLConstants'
    ], function(
        freezeObject,
        WebGLConstants) {
    'use strict';

    /**
     * @private
     */
    var RenderbufferFormat = {
        RGBA4 : WebGLConstants.RGBA4,
        RGB5_A1 : WebGLConstants.RGB5_A1,
        RGB565 : WebGLConstants.RGB565,
        DEPTH_COMPONENT16 : WebGLConstants.DEPTH_COMPONENT16,
        STENCIL_INDEX8 : WebGLConstants.STENCIL_INDEX8,
        DEPTH_STENCIL : WebGLConstants.DEPTH_STENCIL,

        validate : function(renderbufferFormat) {
            return ((renderbufferFormat === RenderbufferFormat.RGBA4) ||
                    (renderbufferFormat === RenderbufferFormat.RGB5_A1) ||
                    (renderbufferFormat === RenderbufferFormat.RGB565) ||
                    (renderbufferFormat === RenderbufferFormat.DEPTH_COMPONENT16) ||
                    (renderbufferFormat === RenderbufferFormat.STENCIL_INDEX8) ||
                    (renderbufferFormat === RenderbufferFormat.DEPTH_STENCIL));
        }
    };

    return freezeObject(RenderbufferFormat);
});

/*global define*/
define('Renderer/Renderbuffer',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        './ContextLimits',
        './RenderbufferFormat'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        ContextLimits,
        RenderbufferFormat) {
    'use strict';

    /**
     * @private
     */
    function Renderbuffer(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.context)) {
            throw new DeveloperError('options.context is required.');
        }
        
        var context = options.context;
        var gl = context._gl;
        var maximumRenderbufferSize = ContextLimits.maximumRenderbufferSize;

        var format = defaultValue(options.format, RenderbufferFormat.RGBA4);
        var width = defined(options.width) ? options.width : gl.drawingBufferWidth;
        var height = defined(options.height) ? options.height : gl.drawingBufferHeight;

                if (!RenderbufferFormat.validate(format)) {
            throw new DeveloperError('Invalid format.');
        }

        if (width <= 0) {
            throw new DeveloperError('Width must be greater than zero.');
        }

        if (width > maximumRenderbufferSize) {
            throw new DeveloperError('Width must be less than or equal to the maximum renderbuffer size (' + maximumRenderbufferSize + ').  Check maximumRenderbufferSize.');
        }

        if (height <= 0) {
            throw new DeveloperError('Height must be greater than zero.');
        }

        if (height > maximumRenderbufferSize) {
            throw new DeveloperError('Height must be less than or equal to the maximum renderbuffer size (' + maximumRenderbufferSize + ').  Check maximumRenderbufferSize.');
        }
        
        this._gl = gl;
        this._format = format;
        this._width = width;
        this._height = height;
        this._renderbuffer = this._gl.createRenderbuffer();

        gl.bindRenderbuffer(gl.RENDERBUFFER, this._renderbuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, format, width, height);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    }

    defineProperties(Renderbuffer.prototype, {
        format: {
            get : function() {
                return this._format;
            }
        },
        width: {
            get : function() {
                return this._width;
            }
        },
        height: {
            get : function() {
                return this._height;
            }
        }
    });

    Renderbuffer.prototype._getRenderbuffer = function() {
        return this._renderbuffer;
    };

    Renderbuffer.prototype.isDestroyed = function() {
        return false;
    };

    Renderbuffer.prototype.destroy = function() {
        this._gl.deleteRenderbuffer(this._renderbuffer);
        return destroyObject(this);
    };

    return Renderbuffer;
});

/*global define*/
define('Renderer/PickFramebuffer',[
        '../Core/BoundingRectangle',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        './Framebuffer',
        './PassState',
        './Renderbuffer',
        './RenderbufferFormat',
        './Texture'
    ], function(
        BoundingRectangle,
        Color,
        defaultValue,
        defined,
        destroyObject,
        Framebuffer,
        PassState,
        Renderbuffer,
        RenderbufferFormat,
        Texture) {
    'use strict';

    /**
     * @private
     */
    function PickFramebuffer(context) {
        // Override per-command states
        var passState = new PassState(context);
        passState.blendingEnabled = false;
        passState.scissorTest = {
            enabled : true,
            rectangle : new BoundingRectangle()
        };
        passState.viewport = new BoundingRectangle();

        this._context = context;
        this._fb = undefined;
        this._passState = passState;
        this._width = 0;
        this._height = 0;
    }
    PickFramebuffer.prototype.begin = function(screenSpaceRectangle) {
        var context = this._context;
        var width = context.drawingBufferWidth;
        var height = context.drawingBufferHeight;

        BoundingRectangle.clone(screenSpaceRectangle, this._passState.scissorTest.rectangle);

        // Initially create or recreate renderbuffers and framebuffer used for picking
        if ((!defined(this._fb)) || (this._width !== width) || (this._height !== height)) {
            this._width = width;
            this._height = height;

            this._fb = this._fb && this._fb.destroy();
            this._fb = new Framebuffer({
                context : context,
                colorTextures : [new Texture({
                    context : context,
                    width : width,
                    height : height
                })],
                depthStencilRenderbuffer : new Renderbuffer({
                    context : context,
                    format : RenderbufferFormat.DEPTH_STENCIL
                })
            });
            this._passState.framebuffer = this._fb;
        }

        this._passState.viewport.width = width;
        this._passState.viewport.height = height;

        return this._passState;
    };

    var colorScratch = new Color();

    PickFramebuffer.prototype.end = function(screenSpaceRectangle) {
        var width = defaultValue(screenSpaceRectangle.width, 1.0);
        var height = defaultValue(screenSpaceRectangle.height, 1.0);

        var context = this._context;
        var pixels = context.readPixels({
            x : screenSpaceRectangle.x,
            y : screenSpaceRectangle.y,
            width : width,
            height : height,
            framebuffer : this._fb
        });

        var max = Math.max(width, height);
        var length = max * max;
        var halfWidth = Math.floor(width * 0.5);
        var halfHeight = Math.floor(height * 0.5);

        var x = 0;
        var y = 0;
        var dx = 0;
        var dy = -1;

        // Spiral around the center pixel, this is a workaround until
        // we can access the depth buffer on all browsers.

        // The region does not have to square and the dimensions do not have to be odd, but
        // loop iterations would be wasted. Prefer square regions where the size is odd.
        for (var i = 0; i < length; ++i) {
            if (-halfWidth <= x && x <= halfWidth && -halfHeight <= y && y <= halfHeight) {
                var index = 4 * ((halfHeight - y) * width + x + halfWidth);

                colorScratch.red = Color.byteToFloat(pixels[index]);
                colorScratch.green = Color.byteToFloat(pixels[index + 1]);
                colorScratch.blue = Color.byteToFloat(pixels[index + 2]);
                colorScratch.alpha = Color.byteToFloat(pixels[index + 3]);

                var object = context.getObjectByPickColor(colorScratch);
                if (defined(object)) {
                    return object;
                }
            }

            // if (top right || bottom left corners) || (top left corner) || (bottom right corner + (1, 0))
            // change spiral direction
            if (x === y || (x < 0 && -x === y) || (x > 0 && x === 1 - y)) {
                var temp = dx;
                dx = -dy;
                dy = temp;
            }

            x += dx;
            y += dy;
        }

        return undefined;
    };

    PickFramebuffer.prototype.isDestroyed = function() {
        return false;
    };

    PickFramebuffer.prototype.destroy = function() {
        this._fb = this._fb && this._fb.destroy();
        return destroyObject(this);
    };

    return PickFramebuffer;
});

/*global define*/
define('Renderer/ShaderCache',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        './ShaderProgram',
        './ShaderSource'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        ShaderProgram,
        ShaderSource) {
    'use strict';

    /**
     * @private
     */
    function ShaderCache(context) {
        this._context = context;
        this._shaders = {};
        this._numberOfShaders = 0;
        this._shadersToRelease = {};
    }

    defineProperties(ShaderCache.prototype, {
        numberOfShaders : {
            get : function() {
                return this._numberOfShaders;
            }
        }
    });

    /**
     * Returns a shader program from the cache, or creates and caches a new shader program,
     * given the GLSL vertex and fragment shader source and attribute locations.
     * <p>
     * The difference between this and {@link ShaderCache#getShaderProgram}, is this is used to
     * replace an existing reference to a shader program, which is passed as the first argument.
     * </p>
     *
     * @param {Object} options Object with the following properties:
     * @param {ShaderProgram} [options.shaderProgram] The shader program that is being reassigned.
     * @param {String|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
     * @param {String|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
     * @param {Object} options.attributeLocations Indices for the attribute inputs to the vertex shader.

     * @returns {ShaderProgram} The cached or newly created shader program.
     *
     *
     * @example
     * this._shaderProgram = context.shaderCache.replaceShaderProgram({
     *     shaderProgram : this._shaderProgram,
     *     vertexShaderSource : vs,
     *     fragmentShaderSource : fs,
     *     attributeLocations : attributeLocations
     * });
     *
     * @see ShaderCache#getShaderProgram
     */
    ShaderCache.prototype.replaceShaderProgram = function(options) {
        if (defined(options.shaderProgram)) {
            options.shaderProgram.destroy();
        }

        return this.getShaderProgram(options);
    };

    /**
     * Returns a shader program from the cache, or creates and caches a new shader program,
     * given the GLSL vertex and fragment shader source and attribute locations.
     *
     * @param {Object} options Object with the following properties:
     * @param {String|ShaderSource} options.vertexShaderSource The GLSL source for the vertex shader.
     * @param {String|ShaderSource} options.fragmentShaderSource The GLSL source for the fragment shader.
     * @param {Object} options.attributeLocations Indices for the attribute inputs to the vertex shader.
     *
     * @returns {ShaderProgram} The cached or newly created shader program.
     */
    ShaderCache.prototype.getShaderProgram = function(options) {
        // convert shaders which are provided as strings into ShaderSource objects
        // because ShaderSource handles all the automatic including of built-in functions, etc.

        var vertexShaderSource = options.vertexShaderSource;
        var fragmentShaderSource = options.fragmentShaderSource;
        var attributeLocations = options.attributeLocations;

        if (typeof vertexShaderSource === 'string') {
            vertexShaderSource = new ShaderSource({
                sources : [vertexShaderSource]
            });
        }

        if (typeof fragmentShaderSource === 'string') {
            fragmentShaderSource = new ShaderSource({
                sources : [fragmentShaderSource]
            });
        }

        var vertexShaderText = vertexShaderSource.createCombinedVertexShader();
        var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader();

        var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations);
        var cachedShader;

        if (defined(this._shaders[keyword])) {
            cachedShader = this._shaders[keyword];

            // No longer want to release this if it was previously released.
            delete this._shadersToRelease[keyword];
        } else {
            var context = this._context;
            var shaderProgram = new ShaderProgram({
                gl : context._gl,
                logShaderCompilation : context.logShaderCompilation,
                debugShaders : context.debugShaders,
                vertexShaderSource : vertexShaderSource,
                vertexShaderText : vertexShaderText,
                fragmentShaderSource : fragmentShaderSource,
                fragmentShaderText : fragmentShaderText,
                attributeLocations : attributeLocations
            });

            cachedShader = {
                cache : this,
                shaderProgram : shaderProgram,
                keyword : keyword,
                derivedKeywords : [],
                count : 0
            };

            // A shader can't be in more than one cache.
            shaderProgram._cachedShader = cachedShader;
            this._shaders[keyword] = cachedShader;
            ++this._numberOfShaders;
        }

        ++cachedShader.count;
        return cachedShader.shaderProgram;
    };

    ShaderCache.prototype.getDerivedShaderProgram = function(shaderProgram, keyword) {
        var cachedShader = shaderProgram._cachedShader;
        var derivedKeyword = keyword + cachedShader.keyword;
        var cachedDerivedShader = this._shaders[derivedKeyword];
        if (!defined(cachedDerivedShader)) {
            return undefined;
        }

        return cachedDerivedShader.shaderProgram;
    };

    ShaderCache.prototype.createDerivedShaderProgram = function(shaderProgram, keyword, options) {
        var cachedShader = shaderProgram._cachedShader;
        var derivedKeyword = keyword + cachedShader.keyword;

        var vertexShaderSource = options.vertexShaderSource;
        var fragmentShaderSource = options.fragmentShaderSource;
        var attributeLocations = options.attributeLocations;

        if (typeof vertexShaderSource === 'string') {
            vertexShaderSource = new ShaderSource({
                sources : [vertexShaderSource]
            });
        }

        if (typeof fragmentShaderSource === 'string') {
            fragmentShaderSource = new ShaderSource({
                sources : [fragmentShaderSource]
            });
        }

        var vertexShaderText = vertexShaderSource.createCombinedVertexShader();
        var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader();

        var context = this._context;
        var derivedShaderProgram = new ShaderProgram({
            gl : context._gl,
            logShaderCompilation : context.logShaderCompilation,
            debugShaders : context.debugShaders,
            vertexShaderSource : vertexShaderSource,
            vertexShaderText : vertexShaderText,
            fragmentShaderSource : fragmentShaderSource,
            fragmentShaderText : fragmentShaderText,
            attributeLocations : attributeLocations
        });

        var derivedCachedShader = {
            cache : this,
            shaderProgram : derivedShaderProgram,
            keyword : derivedKeyword,
            derivedKeywords : [],
            count : 0
        };

        cachedShader.derivedKeywords.push(keyword);
        derivedShaderProgram._cachedShader = derivedCachedShader;
        this._shaders[derivedKeyword] = derivedCachedShader;
        return derivedShaderProgram;
    };

    function destroyShader(cache, cachedShader) {
        var derivedKeywords = cachedShader.derivedKeywords;
        var length = derivedKeywords.length;
        for (var i = 0; i < length; ++i) {
            var keyword = derivedKeywords[i] + cachedShader.keyword;
            var derivedCachedShader = cache._shaders[keyword];
            destroyShader(cache, derivedCachedShader);
        }

        delete cache._shaders[cachedShader.keyword];
        cachedShader.shaderProgram.finalDestroy();
    }

    ShaderCache.prototype.destroyReleasedShaderPrograms = function() {
        var shadersToRelease = this._shadersToRelease;

        for ( var keyword in shadersToRelease) {
            if (shadersToRelease.hasOwnProperty(keyword)) {
                var cachedShader = shadersToRelease[keyword];
                destroyShader(this, cachedShader);
                --this._numberOfShaders;
            }
        }

        this._shadersToRelease = {};
    };

    ShaderCache.prototype.releaseShaderProgram = function(shaderProgram) {
        if (defined(shaderProgram)) {
            var cachedShader = shaderProgram._cachedShader;
            if (cachedShader && (--cachedShader.count === 0)) {
                this._shadersToRelease[cachedShader.keyword] = cachedShader;
            }
        }
    };

    ShaderCache.prototype.isDestroyed = function() {
        return false;
    };

    ShaderCache.prototype.destroy = function() {
        var shaders = this._shaders;
        for (var keyword in shaders) {
            if (shaders.hasOwnProperty(keyword)) {
                shaders[keyword].shaderProgram.finalDestroy();
            }
        }
        return destroyObject(this);
    };

    return ShaderCache;
});

/*global define*/
define('Renderer/UniformState',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/EncodedCartesian3',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Simon1994PlanetaryPositions',
        '../Core/Transforms',
        '../Scene/SceneMode'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        defined,
        defineProperties,
        EncodedCartesian3,
        CesiumMath,
        Matrix3,
        Matrix4,
        Simon1994PlanetaryPositions,
        Transforms,
        SceneMode) {
    'use strict';

    /**
     * @private
     */
    function UniformState() {
        /**
         * @type {Texture}
         */
        this.globeDepthTexture = undefined;

        this._viewport = new BoundingRectangle();
        this._viewportCartesian4 = new Cartesian4();
        this._viewportDirty = false;
        this._viewportOrthographicMatrix = Matrix4.clone(Matrix4.IDENTITY);
        this._viewportTransformation = Matrix4.clone(Matrix4.IDENTITY);

        this._model = Matrix4.clone(Matrix4.IDENTITY);
        this._view = Matrix4.clone(Matrix4.IDENTITY);
        this._inverseView = Matrix4.clone(Matrix4.IDENTITY);
        this._projection = Matrix4.clone(Matrix4.IDENTITY);
        this._infiniteProjection = Matrix4.clone(Matrix4.IDENTITY);
        this._entireFrustum = new Cartesian2();
        this._currentFrustum = new Cartesian2();
        this._frustumPlanes = new Cartesian4();

        this._frameState = undefined;
        this._temeToPseudoFixed = Matrix3.clone(Matrix4.IDENTITY);

        // Derived members
        this._view3DDirty = true;
        this._view3D = new Matrix4();

        this._inverseView3DDirty = true;
        this._inverseView3D = new Matrix4();

        this._inverseModelDirty = true;
        this._inverseModel = new Matrix4();

        this._inverseTransposeModelDirty = true;
        this._inverseTransposeModel = new Matrix3();

        this._viewRotation = new Matrix3();
        this._inverseViewRotation = new Matrix3();

        this._viewRotation3D = new Matrix3();
        this._inverseViewRotation3D = new Matrix3();

        this._inverseProjectionDirty = true;
        this._inverseProjection = new Matrix4();

        this._inverseProjectionOITDirty = true;
        this._inverseProjectionOIT = new Matrix4();

        this._modelViewDirty = true;
        this._modelView = new Matrix4();

        this._modelView3DDirty = true;
        this._modelView3D = new Matrix4();

        this._modelViewRelativeToEyeDirty = true;
        this._modelViewRelativeToEye = new Matrix4();

        this._inverseModelViewDirty = true;
        this._inverseModelView = new Matrix4();

        this._inverseModelView3DDirty = true;
        this._inverseModelView3D = new Matrix4();

        this._viewProjectionDirty = true;
        this._viewProjection = new Matrix4();

        this._inverseViewProjectionDirty = true;
        this._inverseViewProjection = new Matrix4();

        this._modelViewProjectionDirty = true;
        this._modelViewProjection = new Matrix4();

        this._inverseModelViewProjectionDirty = true;
        this._inverseModelViewProjection = new Matrix4();

        this._modelViewProjectionRelativeToEyeDirty = true;
        this._modelViewProjectionRelativeToEye = new Matrix4();

        this._modelViewInfiniteProjectionDirty = true;
        this._modelViewInfiniteProjection = new Matrix4();

        this._normalDirty = true;
        this._normal = new Matrix3();

        this._normal3DDirty = true;
        this._normal3D = new Matrix3();

        this._inverseNormalDirty = true;
        this._inverseNormal = new Matrix3();

        this._inverseNormal3DDirty = true;
        this._inverseNormal3D = new Matrix3();

        this._encodedCameraPositionMCDirty = true;
        this._encodedCameraPositionMC = new EncodedCartesian3();
        this._cameraPosition = new Cartesian3();

        this._sunPositionWC = new Cartesian3();
        this._sunPositionColumbusView = new Cartesian3();
        this._sunDirectionWC = new Cartesian3();
        this._sunDirectionEC = new Cartesian3();
        this._moonDirectionEC = new Cartesian3();

        this._pass = undefined;
        this._mode = undefined;
        this._mapProjection = undefined;
        this._cameraDirection = new Cartesian3();
        this._cameraRight = new Cartesian3();
        this._cameraUp = new Cartesian3();
        this._frustum2DWidth = 0.0;
        this._eyeHeight2D = new Cartesian2();
        this._resolutionScale = 1.0;

        this._fogDensity = undefined;

        this._imagerySplitPosition = 0.0;
        this._pixelSizePerMeter = undefined;
        this._geometricToleranceOverMeter = undefined;
    }

    defineProperties(UniformState.prototype, {
        /**
         * @memberof UniformState.prototype
         * @type {FrameState}
         * @readonly
         */
        frameState : {
            get : function() {
                return this._frameState;
            }
        },
        /**
         * @memberof UniformState.prototype
         * @type {BoundingRectangle}
         */
        viewport : {
            get : function() {
                return this._viewport;
            },
            set : function(viewport) {
                if (!BoundingRectangle.equals(viewport, this._viewport)) {
                    BoundingRectangle.clone(viewport, this._viewport);

                    var v = this._viewport;
                    var vc = this._viewportCartesian4;
                    vc.x = v.x;
                    vc.y = v.y;
                    vc.z = v.width;
                    vc.w = v.height;

                    this._viewportDirty = true;
                }
            }
        },

        /**
         * @memberof UniformState.prototype
         * @private
         */
        viewportCartesian4 : {
            get : function() {
                return this._viewportCartesian4;
            }
        },

        viewportOrthographic : {
            get : function() {
                cleanViewport(this);
                return this._viewportOrthographicMatrix;
            }
        },

        viewportTransformation : {
            get : function() {
                cleanViewport(this);
                return this._viewportTransformation;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        model : {
            get : function() {
                return this._model;
            },
            set : function(matrix) {
                Matrix4.clone(matrix, this._model);

                this._modelView3DDirty = true;
                this._inverseModelView3DDirty = true;
                this._inverseModelDirty = true;
                this._inverseTransposeModelDirty = true;
                this._modelViewDirty = true;
                this._inverseModelViewDirty = true;
                this._modelViewRelativeToEyeDirty = true;
                this._inverseModelViewDirty = true;
                this._modelViewProjectionDirty = true;
                this._inverseModelViewProjectionDirty = true;
                this._modelViewProjectionRelativeToEyeDirty = true;
                this._modelViewInfiniteProjectionDirty = true;
                this._normalDirty = true;
                this._inverseNormalDirty = true;
                this._normal3DDirty = true;
                this._inverseNormal3DDirty = true;
                this._encodedCameraPositionMCDirty = true;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseModel : {
            get : function() {
                if (this._inverseModelDirty) {
                    this._inverseModelDirty = false;

                    Matrix4.inverse(this._model, this._inverseModel);
                }

                return this._inverseModel;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @private
         */
        inverseTransposeModel : {
            get : function() {
                var m = this._inverseTransposeModel;
                if (this._inverseTransposeModelDirty) {
                    this._inverseTransposeModelDirty = false;

                    Matrix4.getRotation(this.inverseModel, m);
                    Matrix3.transpose(m, m);
                }

                return m;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        view : {
            get : function() {
                return this._view;
            }
        },

        /**
         * The 3D view matrix.  In 3D mode, this is identical to {@link UniformState#view},
         * but in 2D and Columbus View it is a synthetic matrix based on the equivalent position
         * of the camera in the 3D world.
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        view3D : {
            get : function() {
                updateView3D(this);
                return this._view3D;
            }
        },

        /**
         * The 3x3 rotation matrix of the current view matrix ({@link UniformState#view}).
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        viewRotation : {
            get : function() {
                updateView3D(this);
                return this._viewRotation;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        viewRotation3D : {
            get : function() {
                updateView3D(this);
                return this._viewRotation3D;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseView : {
            get : function() {
                return this._inverseView;
            }
        },

        /**
         * the 4x4 inverse-view matrix that transforms from eye to 3D world coordinates.  In 3D mode, this is
         * identical to {@link UniformState#inverseView}, but in 2D and Columbus View it is a synthetic matrix
         * based on the equivalent position of the camera in the 3D world.
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseView3D : {
            get : function() {
                updateInverseView3D(this);
                return this._inverseView3D;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        inverseViewRotation : {
            get : function() {
                return this._inverseViewRotation;
            }
        },

        /**
         * The 3x3 rotation matrix of the current 3D inverse-view matrix ({@link UniformState#inverseView3D}).
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        inverseViewRotation3D : {
            get : function() {
                updateInverseView3D(this);
                return this._inverseViewRotation3D;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        projection : {
            get : function() {
                return this._projection;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseProjection : {
            get : function() {
                cleanInverseProjection(this);
                return this._inverseProjection;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @private
         */
        inverseProjectionOIT : {
            get : function() {
                cleanInverseProjectionOIT(this);
                return this._inverseProjectionOIT;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        infiniteProjection : {
            get : function() {
                return this._infiniteProjection;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        modelView : {
            get : function() {
                cleanModelView(this);
                return this._modelView;
            }
        },

        /**
         * The 3D model-view matrix.  In 3D mode, this is equivalent to {@link UniformState#modelView}.  In 2D and
         * Columbus View, however, it is a synthetic matrix based on the equivalent position of the camera in the 3D world.
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        modelView3D : {
            get : function() {
                cleanModelView3D(this);
                return this._modelView3D;
            }
        },

        /**
         * Model-view relative to eye matrix.
         *
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        modelViewRelativeToEye : {
            get : function() {
                cleanModelViewRelativeToEye(this);
                return this._modelViewRelativeToEye;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseModelView : {
            get : function() {
                cleanInverseModelView(this);
                return this._inverseModelView;
            }
        },

        /**
         * The inverse of the 3D model-view matrix.  In 3D mode, this is equivalent to {@link UniformState#inverseModelView}.
         * In 2D and Columbus View, however, it is a synthetic matrix based on the equivalent position of the camera in the 3D world.
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseModelView3D : {
            get : function() {
                cleanInverseModelView3D(this);
                return this._inverseModelView3D;

            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        viewProjection : {
            get : function() {
                cleanViewProjection(this);
                return this._viewProjection;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseViewProjection : {
            get : function() {
                cleanInverseViewProjection(this);
                return this._inverseViewProjection;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        modelViewProjection : {
            get : function() {
                cleanModelViewProjection(this);
                return this._modelViewProjection;

            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        inverseModelViewProjection : {
            get : function() {
                cleanInverseModelViewProjection(this);
                return this._inverseModelViewProjection;

            }
        },

        /**
         * Model-view-projection relative to eye matrix.
         *
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        modelViewProjectionRelativeToEye : {
            get : function() {
                cleanModelViewProjectionRelativeToEye(this);
                return this._modelViewProjectionRelativeToEye;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Matrix4}
         */
        modelViewInfiniteProjection : {
            get : function() {
                cleanModelViewInfiniteProjection(this);
                return this._modelViewInfiniteProjection;
            }
        },

        /**
         * A 3x3 normal transformation matrix that transforms normal vectors in model coordinates to
         * eye coordinates.
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        normal : {
            get : function() {
                cleanNormal(this);
                return this._normal;
            }
        },

        /**
         * A 3x3 normal transformation matrix that transforms normal vectors in 3D model
         * coordinates to eye coordinates.  In 3D mode, this is identical to
         * {@link UniformState#normal}, but in 2D and Columbus View it represents the normal transformation
         * matrix as if the camera were at an equivalent location in 3D mode.
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        normal3D : {
            get : function() {
                cleanNormal3D(this);
                return this._normal3D;

            }
        },

        /**
         * An inverse 3x3 normal transformation matrix that transforms normal vectors in model coordinates
         * to eye coordinates.
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        inverseNormal : {
            get : function() {
                cleanInverseNormal(this);
                return this._inverseNormal;
            }
        },

        /**
         * An inverse 3x3 normal transformation matrix that transforms normal vectors in eye coordinates
         * to 3D model coordinates.  In 3D mode, this is identical to
         * {@link UniformState#inverseNormal}, but in 2D and Columbus View it represents the normal transformation
         * matrix as if the camera were at an equivalent location in 3D mode.
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        inverseNormal3D : {
            get : function() {
                cleanInverseNormal3D(this);
                return this._inverseNormal3D;
            }
        },

        /**
         * The near distance (<code>x</code>) and the far distance (<code>y</code>) of the frustum defined by the camera.
         * This is the largest possible frustum, not an individual frustum used for multi-frustum rendering.
         * @memberof UniformState.prototype
         * @type {Cartesian2}
         */
        entireFrustum : {
            get : function() {
                return this._entireFrustum;
            }
        },

        /**
         * The near distance (<code>x</code>) and the far distance (<code>y</code>) of the frustum defined by the camera.
         * This is the individual frustum used for multi-frustum rendering.
         * @memberof UniformState.prototype
         * @type {Cartesian2}
         */
        currentFrustum : {
            get : function() {
                return this._currentFrustum;
            }
        },

        /**
         * The distances to the frustum planes. The top, bottom, left and right distances are
         * the x, y, z, and w components, respectively.
         * @memberof UniformState.prototype
         * @type {Cartesian4}
         */
        frustumPlanes : {
            get : function() {
                return this._frustumPlanes;
            }
        },

        /**
         * The the height (<code>x</code>) and the height squared (<code>y</code>)
         * in meters of the camera above the 2D world plane. This uniform is only valid
         * when the {@link SceneMode} equal to <code>SCENE2D</code>.
         * @memberof UniformState.prototype
         * @type {Cartesian2}
         */
        eyeHeight2D : {
            get : function() {
                return this._eyeHeight2D;
            }
        },

        /**
         * The sun position in 3D world coordinates at the current scene time.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        sunPositionWC : {
            get : function() {
                return this._sunPositionWC;
            }
        },

        /**
         * The sun position in 2D world coordinates at the current scene time.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        sunPositionColumbusView : {
            get : function(){
                return this._sunPositionColumbusView;
            }
        },

        /**
         * A normalized vector to the sun in 3D world coordinates at the current scene time.  Even in 2D or
         * Columbus View mode, this returns the position of the sun in the 3D scene.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        sunDirectionWC : {
            get : function() {
                return this._sunDirectionWC;
            }
        },

        /**
         * A normalized vector to the sun in eye coordinates at the current scene time.  In 3D mode, this
         * returns the actual vector from the camera position to the sun position.  In 2D and Columbus View, it returns
         * the vector from the equivalent 3D camera position to the position of the sun in the 3D scene.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        sunDirectionEC : {
            get : function() {
                return this._sunDirectionEC;
            }
        },

        /**
         * A normalized vector to the moon in eye coordinates at the current scene time.  In 3D mode, this
         * returns the actual vector from the camera position to the moon position.  In 2D and Columbus View, it returns
         * the vector from the equivalent 3D camera position to the position of the moon in the 3D scene.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        moonDirectionEC : {
            get : function() {
                return this._moonDirectionEC;
            }
        },

        /**
         * The high bits of the camera position.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        encodedCameraPositionMCHigh : {
            get : function() {
                cleanEncodedCameraPositionMC(this);
                return this._encodedCameraPositionMC.high;
            }
        },

        /**
         * The low bits of the camera position.
         * @memberof UniformState.prototype
         * @type {Cartesian3}
         */
        encodedCameraPositionMCLow : {
            get : function() {
                cleanEncodedCameraPositionMC(this);
                return this._encodedCameraPositionMC.low;
            }
        },

        /**
         * A 3x3 matrix that transforms from True Equator Mean Equinox (TEME) axes to the
         * pseudo-fixed axes at the Scene's current time.
         * @memberof UniformState.prototype
         * @type {Matrix3}
         */
        temeToPseudoFixedMatrix : {
            get : function() {
                return this._temeToPseudoFixed;
            }
        },

        /**
         * Gets the scaling factor for transforming from the canvas
         * pixel space to canvas coordinate space.
         * @memberof UniformState.prototype
         * @type {Number}
         */
        resolutionScale : {
            get : function() {
                return this._resolutionScale;
            }
        },

        /**
         * A scalar used to mix a color with the fog color based on the distance to the camera.
         * @memberof UniformState.prototype
         * @type {Number}
         */
        fogDensity : {
            get : function() {
                return this._fogDensity;
            }
        },

        /**
         * A scalar that represents the geometric tolerance per meter
         * @memberof UniformStat.prototype
         * @type {Number}
         */
        geometricToleranceOverMeter: {
            get: function() {
                return this._geometricToleranceOverMeter;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Pass}
         */
        pass : {
            get : function() {
                return this._pass;
            }
        },

        /**
         * @memberof UniformState.prototype
         * @type {Number}
         */
        imagerySplitPosition : {
            get : function() {
                return this._imagerySplitPosition;
            }
        }
    });

    function setView(uniformState, matrix) {
        Matrix4.clone(matrix, uniformState._view);
        Matrix4.getRotation(matrix, uniformState._viewRotation);

        uniformState._view3DDirty = true;
        uniformState._inverseView3DDirty = true;
        uniformState._modelViewDirty = true;
        uniformState._modelView3DDirty = true;
        uniformState._modelViewRelativeToEyeDirty = true;
        uniformState._inverseModelViewDirty = true;
        uniformState._inverseModelView3DDirty = true;
        uniformState._viewProjectionDirty = true;
        uniformState._inverseViewProjectionDirty = true;
        uniformState._modelViewProjectionDirty = true;
        uniformState._modelViewProjectionRelativeToEyeDirty = true;
        uniformState._modelViewInfiniteProjectionDirty = true;
        uniformState._normalDirty = true;
        uniformState._inverseNormalDirty = true;
        uniformState._normal3DDirty = true;
        uniformState._inverseNormal3DDirty = true;
    }

    function setInverseView(uniformState, matrix) {
        Matrix4.clone(matrix, uniformState._inverseView);
        Matrix4.getRotation(matrix, uniformState._inverseViewRotation);
    }

    function setProjection(uniformState, matrix) {
        Matrix4.clone(matrix, uniformState._projection);

        uniformState._inverseProjectionDirty = true;
        uniformState._inverseProjectionOITDirty = true;
        uniformState._viewProjectionDirty = true;
        uniformState._inverseViewProjectionDirty = true;
        uniformState._modelViewProjectionDirty = true;
        uniformState._modelViewProjectionRelativeToEyeDirty = true;
    }

    function setInfiniteProjection(uniformState, matrix) {
        Matrix4.clone(matrix, uniformState._infiniteProjection);

        uniformState._modelViewInfiniteProjectionDirty = true;
    }

    function setCamera(uniformState, camera) {
        Cartesian3.clone(camera.positionWC, uniformState._cameraPosition);
        Cartesian3.clone(camera.directionWC, uniformState._cameraDirection);
        Cartesian3.clone(camera.rightWC, uniformState._cameraRight);
        Cartesian3.clone(camera.upWC, uniformState._cameraUp);
        uniformState._encodedCameraPositionMCDirty = true;
    }

    var transformMatrix = new Matrix3();
    var sunCartographicScratch = new Cartographic();
    function setSunAndMoonDirections(uniformState, frameState) {
        if (!defined(Transforms.computeIcrfToFixedMatrix(frameState.time, transformMatrix))) {
            transformMatrix = Transforms.computeTemeToPseudoFixedMatrix(frameState.time, transformMatrix);
        }

        var position = Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(frameState.time, uniformState._sunPositionWC);
        Matrix3.multiplyByVector(transformMatrix, position, position);

        Cartesian3.normalize(position, uniformState._sunDirectionWC);

        position = Matrix3.multiplyByVector(uniformState.viewRotation3D, position, uniformState._sunDirectionEC);
        Cartesian3.normalize(position, position);

        position = Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame(frameState.time, uniformState._moonDirectionEC);
        Matrix3.multiplyByVector(transformMatrix, position, position);
        Matrix3.multiplyByVector(uniformState.viewRotation3D, position, position);
        Cartesian3.normalize(position, position);

        var projection = frameState.mapProjection;
        var ellipsoid = projection.ellipsoid;
        var sunCartographic = ellipsoid.cartesianToCartographic(uniformState._sunPositionWC, sunCartographicScratch);
        projection.project(sunCartographic, uniformState._sunPositionColumbusView);
    }

    /**
     * Synchronizes the frustum's state with the camera state.  This is called
     * by the {@link Scene} when rendering to ensure that automatic GLSL uniforms
     * are set to the right value.
     *
     * @param {Object} camera The camera to synchronize with.
     */
    UniformState.prototype.updateCamera = function(camera) {
        setView(this, camera.viewMatrix);
        setInverseView(this, camera.inverseViewMatrix);
        setCamera(this, camera);

        this._entireFrustum.x = camera.frustum.near;
        this._entireFrustum.y = camera.frustum.far;
        this.updateFrustum(camera.frustum);
    };

    /**
     * Synchronizes the frustum's state with the uniform state.  This is called
     * by the {@link Scene} when rendering to ensure that automatic GLSL uniforms
     * are set to the right value.
     *
     * @param {Object} frustum The frustum to synchronize with.
     */
    UniformState.prototype.updateFrustum = function(frustum) {
        setProjection(this, frustum.projectionMatrix);
        if (defined(frustum.infiniteProjectionMatrix)) {
            setInfiniteProjection(this, frustum.infiniteProjectionMatrix);
        }
        this._currentFrustum.x = frustum.near;
        this._currentFrustum.y = frustum.far;

        if (!defined(frustum.top)) {
            frustum = frustum._offCenterFrustum;
        }

        this._frustumPlanes.x = frustum.top;
        this._frustumPlanes.y = frustum.bottom;
        this._frustumPlanes.z = frustum.left;
        this._frustumPlanes.w = frustum.right;
    };

    UniformState.prototype.updatePass = function(pass) {
        this._pass = pass;
    };

    /**
     * Synchronizes frame state with the uniform state.  This is called
     * by the {@link Scene} when rendering to ensure that automatic GLSL uniforms
     * are set to the right value.
     *
     * @param {FrameState} frameState The frameState to synchronize with.
     */
    UniformState.prototype.update = function(frameState) {
        this._mode = frameState.mode;
        this._mapProjection = frameState.mapProjection;

        var canvas = frameState.context._canvas;
        this._resolutionScale = canvas.width / canvas.clientWidth;

        var camera = frameState.camera;
        this.updateCamera(camera);

        if (frameState.mode === SceneMode.SCENE2D) {
            this._frustum2DWidth = camera.frustum.right - camera.frustum.left;
            this._eyeHeight2D.x = this._frustum2DWidth * 0.5;
            this._eyeHeight2D.y = this._eyeHeight2D.x * this._eyeHeight2D.x;
        } else {
            this._frustum2DWidth = 0.0;
            this._eyeHeight2D.x = 0.0;
            this._eyeHeight2D.y = 0.0;
        }

        setSunAndMoonDirections(this, frameState);

        this._fogDensity = frameState.fog.density;

        this._frameState = frameState;
        this._temeToPseudoFixed = Transforms.computeTemeToPseudoFixedMatrix(frameState.time, this._temeToPseudoFixed);

        this._imagerySplitPosition = frameState.imagerySplitPosition;
        var fov = camera.frustum.fov;
        var viewport = this._viewport;
        var pixelSizePerMeter;
        if (viewport.height > viewport.width) {
            pixelSizePerMeter = Math.tan(0.5 * fov) * 2.0 / viewport.height;
        } else {
            pixelSizePerMeter = Math.tan(0.5 * fov) * 2.0 / viewport.width;
        }

        this._geometricToleranceOverMeter = pixelSizePerMeter * frameState.maximumScreenSpaceError;
    };

    function cleanViewport(uniformState) {
        if (uniformState._viewportDirty) {
            var v = uniformState._viewport;
            Matrix4.computeOrthographicOffCenter(v.x, v.x + v.width, v.y, v.y + v.height, 0.0, 1.0, uniformState._viewportOrthographicMatrix);
            Matrix4.computeViewportTransformation(v, 0.0, 1.0, uniformState._viewportTransformation);
            uniformState._viewportDirty = false;
        }
    }

    function cleanInverseProjection(uniformState) {
        if (uniformState._inverseProjectionDirty) {
            uniformState._inverseProjectionDirty = false;

            Matrix4.inverse(uniformState._projection, uniformState._inverseProjection);
        }
    }

    function cleanInverseProjectionOIT(uniformState) {
        if (uniformState._inverseProjectionOITDirty) {
            uniformState._inverseProjectionOITDirty = false;

            if (uniformState._mode !== SceneMode.SCENE2D && uniformState._mode !== SceneMode.MORPHING) {
                Matrix4.inverse(uniformState._projection, uniformState._inverseProjectionOIT);
            } else {
                Matrix4.clone(Matrix4.IDENTITY, uniformState._inverseProjectionOIT);
            }
        }
    }

    // Derived
    function cleanModelView(uniformState) {
        if (uniformState._modelViewDirty) {
            uniformState._modelViewDirty = false;

            Matrix4.multiplyTransformation(uniformState._view, uniformState._model, uniformState._modelView);
        }
    }

    function cleanModelView3D(uniformState) {
        if (uniformState._modelView3DDirty) {
            uniformState._modelView3DDirty = false;

            Matrix4.multiplyTransformation(uniformState.view3D, uniformState._model, uniformState._modelView3D);
        }
    }

    function cleanInverseModelView(uniformState) {
        if (uniformState._inverseModelViewDirty) {
            uniformState._inverseModelViewDirty = false;

            Matrix4.inverse(uniformState.modelView, uniformState._inverseModelView);
        }
    }

    function cleanInverseModelView3D(uniformState) {
        if (uniformState._inverseModelView3DDirty) {
            uniformState._inverseModelView3DDirty = false;

            Matrix4.inverse(uniformState.modelView3D, uniformState._inverseModelView3D);
        }
    }

    function cleanViewProjection(uniformState) {
        if (uniformState._viewProjectionDirty) {
            uniformState._viewProjectionDirty = false;

            Matrix4.multiply(uniformState._projection, uniformState._view, uniformState._viewProjection);
        }
    }

    function cleanInverseViewProjection(uniformState) {
        if (uniformState._inverseViewProjectionDirty) {
            uniformState._inverseViewProjectionDirty = false;

            Matrix4.inverse(uniformState.viewProjection, uniformState._inverseViewProjection);
        }
    }

    function cleanModelViewProjection(uniformState) {
        if (uniformState._modelViewProjectionDirty) {
            uniformState._modelViewProjectionDirty = false;

            Matrix4.multiply(uniformState._projection, uniformState.modelView, uniformState._modelViewProjection);
        }
    }

    function cleanModelViewRelativeToEye(uniformState) {
        if (uniformState._modelViewRelativeToEyeDirty) {
            uniformState._modelViewRelativeToEyeDirty = false;

            var mv = uniformState.modelView;
            var mvRte = uniformState._modelViewRelativeToEye;
            mvRte[0] = mv[0];
            mvRte[1] = mv[1];
            mvRte[2] = mv[2];
            mvRte[3] = mv[3];
            mvRte[4] = mv[4];
            mvRte[5] = mv[5];
            mvRte[6] = mv[6];
            mvRte[7] = mv[7];
            mvRte[8] = mv[8];
            mvRte[9] = mv[9];
            mvRte[10] = mv[10];
            mvRte[11] = mv[11];
            mvRte[12] = 0.0;
            mvRte[13] = 0.0;
            mvRte[14] = 0.0;
            mvRte[15] = mv[15];
        }
    }

    function cleanInverseModelViewProjection(uniformState) {
        if (uniformState._inverseModelViewProjectionDirty) {
            uniformState._inverseModelViewProjectionDirty = false;

            Matrix4.inverse(uniformState.modelViewProjection, uniformState._inverseModelViewProjection);
        }
    }

    function cleanModelViewProjectionRelativeToEye(uniformState) {
        if (uniformState._modelViewProjectionRelativeToEyeDirty) {
            uniformState._modelViewProjectionRelativeToEyeDirty = false;

            Matrix4.multiply(uniformState._projection, uniformState.modelViewRelativeToEye, uniformState._modelViewProjectionRelativeToEye);
        }
    }

    function cleanModelViewInfiniteProjection(uniformState) {
        if (uniformState._modelViewInfiniteProjectionDirty) {
            uniformState._modelViewInfiniteProjectionDirty = false;

            Matrix4.multiply(uniformState._infiniteProjection, uniformState.modelView, uniformState._modelViewInfiniteProjection);
        }
    }

    function cleanNormal(uniformState) {
        if (uniformState._normalDirty) {
            uniformState._normalDirty = false;

            var m = uniformState._normal;
            Matrix4.getRotation(uniformState.inverseModelView, m);
            Matrix3.transpose(m, m);
        }
    }

    function cleanNormal3D(uniformState) {
        if (uniformState._normal3DDirty) {
            uniformState._normal3DDirty = false;

            var m = uniformState._normal3D;
            Matrix4.getRotation(uniformState.inverseModelView3D, m);
            Matrix3.transpose(m, m);
        }
    }

    function cleanInverseNormal(uniformState) {
        if (uniformState._inverseNormalDirty) {
            uniformState._inverseNormalDirty = false;

            Matrix4.getRotation(uniformState.inverseModelView, uniformState._inverseNormal);
        }
    }

    function cleanInverseNormal3D(uniformState) {
        if (uniformState._inverseNormal3DDirty) {
            uniformState._inverseNormal3DDirty = false;

            Matrix4.getRotation(uniformState.inverseModelView3D, uniformState._inverseNormal3D);
        }
    }

    var cameraPositionMC = new Cartesian3();

    function cleanEncodedCameraPositionMC(uniformState) {
        if (uniformState._encodedCameraPositionMCDirty) {
            uniformState._encodedCameraPositionMCDirty = false;

            Matrix4.multiplyByPoint(uniformState.inverseModel, uniformState._cameraPosition, cameraPositionMC);
            EncodedCartesian3.fromCartesian(cameraPositionMC, uniformState._encodedCameraPositionMC);
        }
    }

    var view2Dto3DPScratch = new Cartesian3();
    var view2Dto3DRScratch = new Cartesian3();
    var view2Dto3DUScratch = new Cartesian3();
    var view2Dto3DDScratch = new Cartesian3();
    var view2Dto3DCartographicScratch = new Cartographic();
    var view2Dto3DCartesian3Scratch = new Cartesian3();
    var view2Dto3DMatrix4Scratch = new Matrix4();

    function view2Dto3D(position2D, direction2D, right2D, up2D, frustum2DWidth, mode, projection, result) {
        // The camera position and directions are expressed in the 2D coordinate system where the Y axis is to the East,
        // the Z axis is to the North, and the X axis is out of the map.  Express them instead in the ENU axes where
        // X is to the East, Y is to the North, and Z is out of the local horizontal plane.
        var p = view2Dto3DPScratch;
        p.x = position2D.y;
        p.y = position2D.z;
        p.z = position2D.x;

        var r = view2Dto3DRScratch;
        r.x = right2D.y;
        r.y = right2D.z;
        r.z = right2D.x;

        var u = view2Dto3DUScratch;
        u.x = up2D.y;
        u.y = up2D.z;
        u.z = up2D.x;

        var d = view2Dto3DDScratch;
        d.x = direction2D.y;
        d.y = direction2D.z;
        d.z = direction2D.x;

        // In 2D, the camera height is always 12.7 million meters.
        // The apparent height is equal to half the frustum width.
        if (mode === SceneMode.SCENE2D) {
            p.z = frustum2DWidth * 0.5;
        }

        // Compute the equivalent camera position in the real (3D) world.
        // In 2D and Columbus View, the camera can travel outside the projection, and when it does so
        // there's not really any corresponding location in the real world.  So clamp the unprojected
        // longitude and latitude to their valid ranges.
        var cartographic = projection.unproject(p, view2Dto3DCartographicScratch);
        cartographic.longitude = CesiumMath.clamp(cartographic.longitude, -Math.PI, Math.PI);
        cartographic.latitude = CesiumMath.clamp(cartographic.latitude, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO);
        var ellipsoid = projection.ellipsoid;
        var position3D = ellipsoid.cartographicToCartesian(cartographic, view2Dto3DCartesian3Scratch);

        // Compute the rotation from the local ENU at the real world camera position to the fixed axes.
        var enuToFixed = Transforms.eastNorthUpToFixedFrame(position3D, ellipsoid, view2Dto3DMatrix4Scratch);

        // Transform each camera direction to the fixed axes.
        Matrix4.multiplyByPointAsVector(enuToFixed, r, r);
        Matrix4.multiplyByPointAsVector(enuToFixed, u, u);
        Matrix4.multiplyByPointAsVector(enuToFixed, d, d);

        // Compute the view matrix based on the new fixed-frame camera position and directions.
        if (!defined(result)) {
            result = new Matrix4();
        }

        result[0] = r.x;
        result[1] = u.x;
        result[2] = -d.x;
        result[3] = 0.0;
        result[4] = r.y;
        result[5] = u.y;
        result[6] = -d.y;
        result[7] = 0.0;
        result[8] = r.z;
        result[9] = u.z;
        result[10] = -d.z;
        result[11] = 0.0;
        result[12] = -Cartesian3.dot(r, position3D);
        result[13] = -Cartesian3.dot(u, position3D);
        result[14] = Cartesian3.dot(d, position3D);
        result[15] = 1.0;

        return result;
    }

    function updateView3D(that) {
        if (that._view3DDirty) {
            if (that._mode === SceneMode.SCENE3D) {
                Matrix4.clone(that._view, that._view3D);
            } else {
                view2Dto3D(that._cameraPosition, that._cameraDirection, that._cameraRight, that._cameraUp, that._frustum2DWidth, that._mode, that._mapProjection, that._view3D);
            }
            Matrix4.getRotation(that._view3D, that._viewRotation3D);
            that._view3DDirty = false;
        }
    }

    function updateInverseView3D(that){
        if (that._inverseView3DDirty) {
            Matrix4.inverseTransformation(that.view3D, that._inverseView3D);
            Matrix4.getRotation(that._inverseView3D, that._inverseViewRotation3D);
            that._inverseView3DDirty = false;
        }
    }

    return UniformState;
});

/*global define*/
define('Renderer/Context',[
        '../Core/clone',
        '../Core/Color',
        '../Core/ComponentDatatype',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Geometry',
        '../Core/GeometryAttribute',
        '../Core/Matrix4',
        '../Core/PrimitiveType',
        '../Core/RuntimeError',
        '../Core/WebGLConstants',
        '../Shaders/ViewportQuadVS',
        './BufferUsage',
        './ClearCommand',
        './ContextLimits',
        './CubeMap',
        './DrawCommand',
        './PassState',
        './PickFramebuffer',
        './RenderState',
        './ShaderCache',
        './ShaderProgram',
        './Texture',
        './UniformState',
        './VertexArray'
    ], function(
        clone,
        Color,
        ComponentDatatype,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Geometry,
        GeometryAttribute,
        Matrix4,
        PrimitiveType,
        RuntimeError,
        WebGLConstants,
        ViewportQuadVS,
        BufferUsage,
        ClearCommand,
        ContextLimits,
        CubeMap,
        DrawCommand,
        PassState,
        PickFramebuffer,
        RenderState,
        ShaderCache,
        ShaderProgram,
        Texture,
        UniformState,
        VertexArray) {
    'use strict';
    /*global WebGLRenderingContext*/
    /*global WebGL2RenderingContext*/

    function errorToString(gl, error) {
        var message = 'WebGL Error:  ';
        switch (error) {
        case gl.INVALID_ENUM:
            message += 'INVALID_ENUM';
            break;
        case gl.INVALID_VALUE:
            message += 'INVALID_VALUE';
            break;
        case gl.INVALID_OPERATION:
            message += 'INVALID_OPERATION';
            break;
        case gl.OUT_OF_MEMORY:
            message += 'OUT_OF_MEMORY';
            break;
        case gl.CONTEXT_LOST_WEBGL:
            message += 'CONTEXT_LOST_WEBGL lost';
            break;
        default:
            message += 'Unknown (' + error + ')';
        }

        return message;
    }

    function createErrorMessage(gl, glFunc, glFuncArguments, error) {
        var message = errorToString(gl, error) + ': ' + glFunc.name + '(';

        for ( var i = 0; i < glFuncArguments.length; ++i) {
            if (i !== 0) {
                message += ', ';
            }
            message += glFuncArguments[i];
        }
        message += ');';

        return message;
    }

    function throwOnError(gl, glFunc, glFuncArguments) {
        var error = gl.getError();
        if (error !== gl.NO_ERROR) {
            throw new RuntimeError(createErrorMessage(gl, glFunc, glFuncArguments, error));
        }
    }

    function makeGetterSetter(gl, propertyName, logFunction) {
        return {
            get : function() {
                var value = gl[propertyName];
                logFunction(gl, 'get: ' + propertyName, value);
                return gl[propertyName];
            },
            set : function(value) {
                gl[propertyName] = value;
                logFunction(gl, 'set: ' + propertyName, value);
            }
        };
    }

    function wrapGL(gl, logFunction) {
        if (!defined(logFunction)) {
            return gl;
        }

        function wrapFunction(property) {
            return function() {
                var result = property.apply(gl, arguments);
                logFunction(gl, property, arguments);
                return result;
            };
        }

        var glWrapper = {};

        /*jslint forin: true*/
        /*jshint forin: false*/
        // JSLint normally demands that a for..in loop must directly contain an if,
        // but in our loop below, we actually intend to iterate all properties, including
        // those in the prototype.
        for (var propertyName in gl) {
            var property = gl[propertyName];

            // wrap any functions we encounter, otherwise just copy the property to the wrapper.
            if (property instanceof Function) {
                glWrapper[propertyName] = wrapFunction(property);
            } else {
                Object.defineProperty(glWrapper, propertyName, makeGetterSetter(gl, propertyName, logFunction));
            }
        }

        return glWrapper;
    }

    function getExtension(gl, names) {
        var length = names.length;
        for (var i = 0; i < length; ++i) {
            var extension = gl.getExtension(names[i]);
            if (extension) {
                return extension;
            }
        }

        return undefined;
    }

    /**
     * @private
     */
    function Context(canvas, options) {
        // this check must use typeof, not defined, because defined doesn't work with undeclared variables.
        if (typeof WebGLRenderingContext === 'undefined') {
            throw new RuntimeError('The browser does not support WebGL.  Visit http://get.webgl.org.');
        }

                if (!defined(canvas)) {
            throw new DeveloperError('canvas is required.');
        }
        
        this._canvas = canvas;

        options = clone(options, true);
        options = defaultValue(options, {});
        options.allowTextureFilterAnisotropic = defaultValue(options.allowTextureFilterAnisotropic, true);
        var webglOptions = defaultValue(options.webgl, {});

        // Override select WebGL defaults
        webglOptions.alpha = defaultValue(webglOptions.alpha, false); // WebGL default is true
        webglOptions.stencil = defaultValue(webglOptions.stencil, true); // WebGL default is false

        var defaultToWebgl2 = false;
        var requestWebgl2 = defaultToWebgl2 && (typeof WebGL2RenderingContext !== 'undefined');
        var webgl2 = false;

        var glContext;
        var getWebGLStub = options.getWebGLStub;

        if (!defined(getWebGLStub)) {
            if (requestWebgl2) {
                glContext = canvas.getContext('webgl2', webglOptions) || canvas.getContext('experimental-webgl2', webglOptions) || undefined;
                if (defined(glContext)) {
                    webgl2 = true;
                }
            }
            if (!defined(glContext)) {
                glContext = canvas.getContext('webgl', webglOptions) || canvas.getContext('experimental-webgl', webglOptions) || undefined;
            }
            if (!defined(glContext)) {
                throw new RuntimeError('The browser supports WebGL, but initialization failed.');
            }
        } else {
            // Use WebGL stub when requested for unit tests
            glContext = getWebGLStub(canvas, webglOptions);
        }

        this._originalGLContext = glContext;
        this._gl = glContext;
        this._webgl2 = webgl2;
        this._id = createGuid();

        // Validation and logging disabled by default for speed.
        this.validateFramebuffer = false;
        this.validateShaderProgram = false;
        this.logShaderCompilation = false;

        this._throwOnWebGLError = false;

        this._shaderCache = new ShaderCache(this);

        var gl = glContext;

        this._stencilBits = gl.getParameter(gl.STENCIL_BITS);

        ContextLimits._maximumCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS); // min: 8
        ContextLimits._maximumCubeMapSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE); // min: 16
        ContextLimits._maximumFragmentUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); // min: 16
        ContextLimits._maximumTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); // min: 8
        ContextLimits._maximumRenderbufferSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE); // min: 1
        ContextLimits._maximumTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); // min: 64
        ContextLimits._maximumVaryingVectors = gl.getParameter(gl.MAX_VARYING_VECTORS); // min: 8
        ContextLimits._maximumVertexAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); // min: 8
        ContextLimits._maximumVertexTextureImageUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS); // min: 0
        ContextLimits._maximumVertexUniformVectors = gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS); // min: 128

        var aliasedLineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); // must include 1
        ContextLimits._minimumAliasedLineWidth = aliasedLineWidthRange[0];
        ContextLimits._maximumAliasedLineWidth = aliasedLineWidthRange[1];

        var aliasedPointSizeRange = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE); // must include 1
        ContextLimits._minimumAliasedPointSize = aliasedPointSizeRange[0];
        ContextLimits._maximumAliasedPointSize = aliasedPointSizeRange[1];

        var maximumViewportDimensions = gl.getParameter(gl.MAX_VIEWPORT_DIMS);
        ContextLimits._maximumViewportWidth = maximumViewportDimensions[0];
        ContextLimits._maximumViewportHeight = maximumViewportDimensions[1];

        var highpFloat = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
        ContextLimits._highpFloatSupported = highpFloat.precision !== 0;
        var highpInt = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_INT);
        ContextLimits._highpIntSupported = highpInt.rangeMax !== 0;

        this._antialias = gl.getContextAttributes().antialias;

        // Query and initialize extensions
        this._standardDerivatives = !!getExtension(gl, ['OES_standard_derivatives']);
        this._elementIndexUint = !!getExtension(gl, ['OES_element_index_uint']);
        this._depthTexture = !!getExtension(gl, ['WEBGL_depth_texture', 'WEBKIT_WEBGL_depth_texture']);
        this._textureFloat = !!getExtension(gl, ['OES_texture_float']);
        this._fragDepth = !!getExtension(gl, ['EXT_frag_depth']);
        this._debugShaders = getExtension(gl, ['WEBGL_debug_shaders']);

        this._s3tc = !!getExtension(gl, ['WEBGL_compressed_texture_s3tc', 'MOZ_WEBGL_compressed_texture_s3tc', 'WEBKIT_WEBGL_compressed_texture_s3tc']);
        this._pvrtc = !!getExtension(gl, ['WEBGL_compressed_texture_pvrtc', 'WEBKIT_WEBGL_compressed_texture_pvrtc']);
        this._etc1 = !!getExtension(gl, ['WEBGL_compressed_texture_etc1']);

        var textureFilterAnisotropic = options.allowTextureFilterAnisotropic ? getExtension(gl, ['EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic']) : undefined;
        this._textureFilterAnisotropic = textureFilterAnisotropic;
        ContextLimits._maximumTextureFilterAnisotropy = defined(textureFilterAnisotropic) ? gl.getParameter(textureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : 1.0;

        var glCreateVertexArray;
        var glBindVertexArray;
        var glDeleteVertexArray;

        var glDrawElementsInstanced;
        var glDrawArraysInstanced;
        var glVertexAttribDivisor;

        var glDrawBuffers;

        var vertexArrayObject;
        var instancedArrays;
        var drawBuffers;

        if (webgl2) {
            var that = this;

            glCreateVertexArray = function () { return that._gl.createVertexArray(); };
            glBindVertexArray = function(vao) { that._gl.bindVertexArray(vao); };
            glDeleteVertexArray = function(vao) { that._gl.deleteVertexArray(vao); };

            glDrawElementsInstanced = function(mode, count, type, offset, instanceCount) { gl.drawElementsInstanced(mode, count, type, offset, instanceCount); };
            glDrawArraysInstanced = function(mode, first, count, instanceCount) { gl.drawArraysInstanced(mode, first, count, instanceCount); };
            glVertexAttribDivisor = function(index, divisor) { gl.vertexAttribDivisor(index, divisor); };

            glDrawBuffers = function(buffers) { gl.drawBuffers(buffers); };
        } else {
            vertexArrayObject = getExtension(gl, ['OES_vertex_array_object']);
            if (defined(vertexArrayObject)) {
                glCreateVertexArray = function() { return vertexArrayObject.createVertexArrayOES(); };
                glBindVertexArray = function(vertexArray) { vertexArrayObject.bindVertexArrayOES(vertexArray); };
                glDeleteVertexArray = function(vertexArray) { vertexArrayObject.deleteVertexArrayOES(vertexArray); };
            }

            instancedArrays = getExtension(gl, ['ANGLE_instanced_arrays']);
            if (defined(instancedArrays)) {
                glDrawElementsInstanced = function(mode, count, type, offset, instanceCount) { instancedArrays.drawElementsInstancedANGLE(mode, count, type, offset, instanceCount); };
                glDrawArraysInstanced = function(mode, first, count, instanceCount) { instancedArrays.drawArraysInstancedANGLE(mode, first, count, instanceCount); };
                glVertexAttribDivisor = function(index, divisor) { instancedArrays.vertexAttribDivisorANGLE(index, divisor); };
            }

            drawBuffers = getExtension(gl, ['WEBGL_draw_buffers']);
            if (defined(drawBuffers)) {
                glDrawBuffers = function(buffers) { drawBuffers.drawBuffersWEBGL(buffers); };
            }
        }

        this.glCreateVertexArray = glCreateVertexArray;
        this.glBindVertexArray = glBindVertexArray;
        this.glDeleteVertexArray = glDeleteVertexArray;

        this.glDrawElementsInstanced = glDrawElementsInstanced;
        this.glDrawArraysInstanced = glDrawArraysInstanced;
        this.glVertexAttribDivisor = glVertexAttribDivisor;

        this.glDrawBuffers = glDrawBuffers;

        this._vertexArrayObject = !!vertexArrayObject;
        this._instancedArrays = !!instancedArrays;
        this._drawBuffers = !!drawBuffers;

        ContextLimits._maximumDrawBuffers = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_DRAW_BUFFERS) : 1;
        ContextLimits._maximumColorAttachments = this.drawBuffers ? gl.getParameter(WebGLConstants.MAX_COLOR_ATTACHMENTS) : 1;

        this._clearColor = new Color(0.0, 0.0, 0.0, 0.0);
        this._clearDepth = 1.0;
        this._clearStencil = 0;

        var us = new UniformState();
        var ps = new PassState(this);
        var rs = RenderState.fromCache();

        this._defaultPassState = ps;
        this._defaultRenderState = rs;
        this._defaultTexture = undefined;
        this._defaultCubeMap = undefined;

        this._us = us;
        this._currentRenderState = rs;
        this._currentPassState = ps;
        this._currentFramebuffer = undefined;
        this._maxFrameTextureUnitIndex = 0;

        // Vertex attribute divisor state cache. Workaround for ANGLE (also look at VertexArray.setVertexAttribDivisor)
        this._vertexAttribDivisors = [];
        this._previousDrawInstanced = false;
        for (var i = 0; i < ContextLimits._maximumVertexAttributes; i++) {
            this._vertexAttribDivisors.push(0);
        }

        this._pickObjects = {};
        this._nextPickColor = new Uint32Array(1);

        /**
         * @example
         * {
         *   webgl : {
         *     alpha : false,
         *     depth : true,
         *     stencil : false,
         *     antialias : true,
         *     premultipliedAlpha : true,
         *     preserveDrawingBuffer : false,
         *     failIfMajorPerformanceCaveat : true
         *   },
         *   allowTextureFilterAnisotropic : true
         * }
         */
        this.options = options;

        /**
         * A cache of objects tied to this context.  Just before the Context is destroyed,
         * <code>destroy</code> will be invoked on each object in this object literal that has
         * such a method.  This is useful for caching any objects that might otherwise
         * be stored globally, except they're tied to a particular context, and to manage
         * their lifetime.
         *
         * @type {Object}
         */
        this.cache = {};

        RenderState.apply(gl, rs, ps);
    }

    var defaultFramebufferMarker = {};

    defineProperties(Context.prototype, {
        id : {
            get : function() {
                return this._id;
            }
        },
        webgl2 : {
            get : function() {
                return this._webgl2;
            }
        },
        canvas : {
            get : function() {
                return this._canvas;
            }
        },
        shaderCache : {
            get : function() {
                return this._shaderCache;
            }
        },
        uniformState : {
            get : function() {
                return this._us;
            }
        },

        /**
         * The number of stencil bits per pixel in the default bound framebuffer.  The minimum is eight bits.
         * @memberof Context.prototype
         * @type {Number}
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>STENCIL_BITS</code>.
         */
        stencilBits : {
            get : function() {
                return this._stencilBits;
            }
        },

        /**
         * <code>true</code> if the WebGL context supports stencil buffers.
         * Stencil buffers are not supported by all systems.
         * @memberof Context.prototype
         * @type {Boolean}
         */
        stencilBuffer : {
            get : function() {
                return this._stencilBits >= 8;
            }
        },

        /**
         * <code>true</code> if the WebGL context supports antialiasing.  By default
         * antialiasing is requested, but it is not supported by all systems.
         * @memberof Context.prototype
         * @type {Boolean}
         */
        antialias : {
            get : function() {
                return this._antialias;
            }
        },

        /**
         * <code>true</code> if the OES_standard_derivatives extension is supported.  This
         * extension provides access to <code>dFdx</code>, <code>dFdy</code>, and <code>fwidth</code>
         * functions from GLSL.  A shader using these functions still needs to explicitly enable the
         * extension with <code>#extension GL_OES_standard_derivatives : enable</code>.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/gles/extensions/OES/OES_standard_derivatives.txt|OES_standard_derivatives}
         */
        standardDerivatives : {
            get : function() {
                return this._standardDerivatives;
            }
        },

        /**
         * <code>true</code> if the OES_element_index_uint extension is supported.  This
         * extension allows the use of unsigned int indices, which can improve performance by
         * eliminating batch breaking caused by unsigned short indices.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/webgl/extensions/OES_element_index_uint/|OES_element_index_uint}
         */
        elementIndexUint : {
            get : function() {
                return this._elementIndexUint || this._webgl2;
            }
        },

        /**
         * <code>true</code> if WEBGL_depth_texture is supported.  This extension provides
         * access to depth textures that, for example, can be attached to framebuffers for shadow mapping.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/|WEBGL_depth_texture}
         */
        depthTexture : {
            get : function() {
                return this._depthTexture;
            }
        },

        /**
         * <code>true</code> if OES_texture_float is supported.  This extension provides
         * access to floating point textures that, for example, can be attached to framebuffers for high dynamic range.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/gles/extensions/OES/OES_texture_float.txt|OES_texture_float}
         */
        floatingPointTexture : {
            get : function() {
                return this._textureFloat;
            }
        },

        textureFilterAnisotropic : {
            get : function() {
                return !!this._textureFilterAnisotropic;
            }
        },

        /**
         * <code>true</code> if WEBGL_texture_compression_s3tc is supported.  This extension provides
         * access to DXT compressed textures.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/}
         */
        s3tc : {
            get : function() {
                return this._s3tc;
            }
        },

        /**
         * <code>true</code> if WEBGL_texture_compression_pvrtc is supported.  This extension provides
         * access to PVR compressed textures.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/}
         */
        pvrtc : {
            get : function() {
                return this._pvrtc;
            }
        },

        /**
         * <code>true</code> if WEBGL_texture_compression_etc1 is supported.  This extension provides
         * access to ETC1 compressed textures.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/}
         */
        etc1 : {
            get : function() {
                return this._etc1;
            }
        },

        /**
         * <code>true</code> if the OES_vertex_array_object extension is supported.  This
         * extension can improve performance by reducing the overhead of switching vertex arrays.
         * When enabled, this extension is automatically used by {@link VertexArray}.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/webgl/extensions/OES_vertex_array_object/|OES_vertex_array_object}
         */
        vertexArrayObject : {
            get : function() {
                return this._vertexArrayObject || this._webgl2;
            }
        },

        /**
         * <code>true</code> if the EXT_frag_depth extension is supported.  This
         * extension provides access to the <code>gl_FragDepthEXT</code> built-in output variable
         * from GLSL fragment shaders.  A shader using these functions still needs to explicitly enable the
         * extension with <code>#extension GL_EXT_frag_depth : enable</code>.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/webgl/extensions/EXT_frag_depth/|EXT_frag_depth}
         */
        fragmentDepth : {
            get : function() {
                return this._fragDepth;
            }
        },

        /**
         * <code>true</code> if the ANGLE_instanced_arrays extension is supported.  This
         * extension provides access to instanced rendering.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays}
         */
        instancedArrays : {
            get : function() {
                return this._instancedArrays || this._webgl2;
            }
        },

        /**
         * <code>true</code> if the WEBGL_draw_buffers extension is supported. This
         * extensions provides support for multiple render targets. The framebuffer object can have mutiple
         * color attachments and the GLSL fragment shader can write to the built-in output array <code>gl_FragData</code>.
         * A shader using this feature needs to explicitly enable the extension with
         * <code>#extension GL_EXT_draw_buffers : enable</code>.
         * @memberof Context.prototype
         * @type {Boolean}
         * @see {@link http://www.khronos.org/registry/webgl/extensions/WEBGL_draw_buffers/|WEBGL_draw_buffers}
         */
        drawBuffers : {
            get : function() {
                return this._drawBuffers || this._webgl2;
            }
        },

        debugShaders : {
            get : function() {
                return this._debugShaders;
            }
        },

        throwOnWebGLError : {
            get : function() {
                return this._throwOnWebGLError;
            },
            set : function(value) {
                this._throwOnWebGLError = value;
                this._gl = wrapGL(this._originalGLContext, value ? throwOnError : undefined);
            }
        },

        /**
         * A 1x1 RGBA texture initialized to [255, 255, 255, 255].  This can
         * be used as a placeholder texture while other textures are downloaded.
         * @memberof Context.prototype
         * @type {Texture}
         */
        defaultTexture : {
            get : function() {
                if (this._defaultTexture === undefined) {
                    this._defaultTexture = new Texture({
                        context : this,
                        source : {
                            width : 1,
                            height : 1,
                            arrayBufferView : new Uint8Array([255, 255, 255, 255])
                        }
                    });
                }

                return this._defaultTexture;
            }
        },

        /**
         * A cube map, where each face is a 1x1 RGBA texture initialized to
         * [255, 255, 255, 255].  This can be used as a placeholder cube map while
         * other cube maps are downloaded.
         * @memberof Context.prototype
         * @type {CubeMap}
         */
        defaultCubeMap : {
            get : function() {
                if (this._defaultCubeMap === undefined) {
                    var face = {
                        width : 1,
                        height : 1,
                        arrayBufferView : new Uint8Array([255, 255, 255, 255])
                    };

                    this._defaultCubeMap = new CubeMap({
                        context : this,
                        source : {
                            positiveX : face,
                            negativeX : face,
                            positiveY : face,
                            negativeY : face,
                            positiveZ : face,
                            negativeZ : face
                        }
                    });
                }

                return this._defaultCubeMap;

            }
        },

        /**
         * The drawingBufferHeight of the underlying GL context.
         * @memberof Context.prototype
         * @type {Number}
         * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
         */
        drawingBufferHeight : {
            get : function() {
                return this._gl.drawingBufferHeight;
            }
        },

        /**
         * The drawingBufferWidth of the underlying GL context.
         * @memberof Context.prototype
         * @type {Number}
         * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferWidth|drawingBufferWidth}
         */
        drawingBufferWidth : {
            get : function() {
                return this._gl.drawingBufferWidth;
            }
        },

        /**
         * Gets an object representing the currently bound framebuffer.  While this instance is not an actual
         * {@link Framebuffer}, it is used to represent the default framebuffer in calls to
         * {@link Texture.FromFramebuffer}.
         * @memberof Context.prototype
         * @type {Object}
         */
        defaultFramebuffer : {
            get : function() {
                return defaultFramebufferMarker;
            }
        }
    });

    /**
     * Validates a framebuffer.
     * Available in debug builds only.
     * @private
     */
    function validateFramebuffer(context) {
                if (context.validateFramebuffer) {
            var gl = context._gl;
            var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

            if (status !== gl.FRAMEBUFFER_COMPLETE) {
                var message;

                switch (status) {
                case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
                    message = 'Framebuffer is not complete.  Incomplete attachment: at least one attachment point with a renderbuffer or texture attached has its attached object no longer in existence or has an attached image with a width or height of zero, or the color attachment point has a non-color-renderable image attached, or the depth attachment point has a non-depth-renderable image attached, or the stencil attachment point has a non-stencil-renderable image attached.  Color-renderable formats include GL_RGBA4, GL_RGB5_A1, and GL_RGB565. GL_DEPTH_COMPONENT16 is the only depth-renderable format. GL_STENCIL_INDEX8 is the only stencil-renderable format.';
                    break;
                case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
                    message = 'Framebuffer is not complete.  Incomplete dimensions: not all attached images have the same width and height.';
                    break;
                case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
                    message = 'Framebuffer is not complete.  Missing attachment: no images are attached to the framebuffer.';
                    break;
                case gl.FRAMEBUFFER_UNSUPPORTED:
                    message = 'Framebuffer is not complete.  Unsupported: the combination of internal formats of the attached images violates an implementation-dependent set of restrictions.';
                    break;
                }

                throw new DeveloperError(message);
            }
        }
            }

    function applyRenderState(context, renderState, passState, clear) {
        var previousRenderState = context._currentRenderState;
        var previousPassState = context._currentPassState;
        context._currentRenderState = renderState;
        context._currentPassState = passState;
        RenderState.partialApply(context._gl, previousRenderState, renderState, previousPassState, passState, clear);
    }

    var scratchBackBufferArray;
    // this check must use typeof, not defined, because defined doesn't work with undeclared variables.
    if (typeof WebGLRenderingContext !== 'undefined') {
        scratchBackBufferArray = [WebGLConstants.BACK];
    }

    function bindFramebuffer(context, framebuffer) {
        if (framebuffer !== context._currentFramebuffer) {
            context._currentFramebuffer = framebuffer;
            var buffers = scratchBackBufferArray;

            if (defined(framebuffer)) {
                framebuffer._bind();
                validateFramebuffer(context);

                // TODO: Need a way for a command to give what draw buffers are active.
                buffers = framebuffer._getActiveColorAttachments();
            } else {
                var gl = context._gl;
                gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            }

            if (context.drawBuffers) {
                context.glDrawBuffers(buffers);
            }
        }
    }

    var defaultClearCommand = new ClearCommand();

    Context.prototype.clear = function(clearCommand, passState) {
        clearCommand = defaultValue(clearCommand, defaultClearCommand);
        passState = defaultValue(passState, this._defaultPassState);

        var gl = this._gl;
        var bitmask = 0;

        var c = clearCommand.color;
        var d = clearCommand.depth;
        var s = clearCommand.stencil;

        if (defined(c)) {
            if (!Color.equals(this._clearColor, c)) {
                Color.clone(c, this._clearColor);
                gl.clearColor(c.red, c.green, c.blue, c.alpha);
            }
            bitmask |= gl.COLOR_BUFFER_BIT;
        }

        if (defined(d)) {
            if (d !== this._clearDepth) {
                this._clearDepth = d;
                gl.clearDepth(d);
            }
            bitmask |= gl.DEPTH_BUFFER_BIT;
        }

        if (defined(s)) {
            if (s !== this._clearStencil) {
                this._clearStencil = s;
                gl.clearStencil(s);
            }
            bitmask |= gl.STENCIL_BUFFER_BIT;
        }

        var rs = defaultValue(clearCommand.renderState, this._defaultRenderState);
        applyRenderState(this, rs, passState, true);

        // The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
        var framebuffer = defaultValue(clearCommand.framebuffer, passState.framebuffer);
        bindFramebuffer(this, framebuffer);

        gl.clear(bitmask);
    };

    function beginDraw(context, framebuffer, drawCommand, passState) {
        var rs = defaultValue(drawCommand._renderState, context._defaultRenderState);

                if (defined(framebuffer) && rs.depthTest) {
            if (rs.depthTest.enabled && !framebuffer.hasDepthAttachment) {
                throw new DeveloperError('The depth test can not be enabled (drawCommand.renderState.depthTest.enabled) because the framebuffer (drawCommand.framebuffer) does not have a depth or depth-stencil renderbuffer.');
            }
        }
        
        bindFramebuffer(context, framebuffer);

        applyRenderState(context, rs, passState, false);

        var sp = drawCommand._shaderProgram;
        sp._bind();
        context._maxFrameTextureUnitIndex = Math.max(context._maxFrameTextureUnitIndex, sp.maximumTextureUnitIndex);
    }

    function continueDraw(context, drawCommand) {
        var primitiveType = drawCommand._primitiveType;
        var va = drawCommand._vertexArray;
        var offset = drawCommand._offset;
        var count = drawCommand._count;
        var instanceCount = drawCommand.instanceCount;

                if (!PrimitiveType.validate(primitiveType)) {
            throw new DeveloperError('drawCommand.primitiveType is required and must be valid.');
        }

        if (!defined(va)) {
            throw new DeveloperError('drawCommand.vertexArray is required.');
        }

        if (offset < 0) {
            throw new DeveloperError('drawCommand.offset must be greater than or equal to zero.');
        }

        if (count < 0) {
            throw new DeveloperError('drawCommand.count must be greater than or equal to zero.');
        }

        if (instanceCount < 0) {
            throw new DeveloperError('drawCommand.instanceCount must be greater than or equal to zero.');
        }

        if (instanceCount > 0 && !context.instancedArrays) {
            throw new DeveloperError('Instanced arrays extension is not supported');
        }
        
        context._us.model = defaultValue(drawCommand._modelMatrix, Matrix4.IDENTITY);
        drawCommand._shaderProgram._setUniforms(drawCommand._uniformMap, context._us, context.validateShaderProgram);

        va._bind();
        var indexBuffer = va.indexBuffer;

        if (defined(indexBuffer)) {
            offset = offset * indexBuffer.bytesPerIndex; // offset in vertices to offset in bytes
            count = defaultValue(count, indexBuffer.numberOfIndices);
            if (instanceCount === 0) {
                context._gl.drawElements(primitiveType, count, indexBuffer.indexDatatype, offset);
            } else {
                context.glDrawElementsInstanced(primitiveType, count, indexBuffer.indexDatatype, offset, instanceCount);
            }
        } else {
            count = defaultValue(count, va.numberOfVertices);
            if (instanceCount === 0) {
                context._gl.drawArrays(primitiveType, offset, count);
            } else {
                context.glDrawArraysInstanced(primitiveType, offset, count, instanceCount);
            }
        }

        va._unBind();
    }

    Context.prototype.draw = function(drawCommand, passState) {
                if (!defined(drawCommand)) {
            throw new DeveloperError('drawCommand is required.');
        }

        if (!defined(drawCommand._shaderProgram)) {
            throw new DeveloperError('drawCommand.shaderProgram is required.');
        }
        
        passState = defaultValue(passState, this._defaultPassState);
        // The command's framebuffer takes presidence over the pass' framebuffer, e.g., for off-screen rendering.
        var framebuffer = defaultValue(drawCommand._framebuffer, passState.framebuffer);

        beginDraw(this, framebuffer, drawCommand, passState);
        continueDraw(this, drawCommand);
    };

    Context.prototype.endFrame = function() {
        var gl = this._gl;
        gl.useProgram(null);

        this._currentFramebuffer = undefined;
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        var buffers = scratchBackBufferArray;
        if (this.drawBuffers) {
            this.glDrawBuffers(buffers);
        }

        var length = this._maxFrameTextureUnitIndex;
        this._maxFrameTextureUnitIndex = 0;

        for (var i = 0; i < length; ++i) {
            gl.activeTexture(gl.TEXTURE0 + i);
            gl.bindTexture(gl.TEXTURE_2D, null);
            gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
        }
    };

    Context.prototype.readPixels = function(readState) {
        var gl = this._gl;

        readState = readState || {};
        var x = Math.max(readState.x || 0, 0);
        var y = Math.max(readState.y || 0, 0);
        var width = readState.width || gl.drawingBufferWidth;
        var height = readState.height || gl.drawingBufferHeight;
        var framebuffer = readState.framebuffer;

                if (width <= 0) {
            throw new DeveloperError('readState.width must be greater than zero.');
        }

        if (height <= 0) {
            throw new DeveloperError('readState.height must be greater than zero.');
        }
        
        var pixels = new Uint8Array(4 * width * height);

        bindFramebuffer(this, framebuffer);

        gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

        return pixels;
    };

    var viewportQuadAttributeLocations = {
        position : 0,
        textureCoordinates : 1
    };

    Context.prototype.getViewportQuadVertexArray = function() {
        // Per-context cache for viewport quads
        var vertexArray = this.cache.viewportQuad_vertexArray;

        if (!defined(vertexArray)) {
            var geometry = new Geometry({
                attributes : {
                    position : new GeometryAttribute({
                        componentDatatype : ComponentDatatype.FLOAT,
                        componentsPerAttribute : 2,
                        values : [
                           -1.0, -1.0,
                            1.0, -1.0,
                            1.0,  1.0,
                           -1.0,  1.0
                        ]
                    }),

                    textureCoordinates : new GeometryAttribute({
                        componentDatatype : ComponentDatatype.FLOAT,
                        componentsPerAttribute : 2,
                        values : [
                            0.0, 0.0,
                            1.0, 0.0,
                            1.0, 1.0,
                            0.0, 1.0
                        ]
                    })
                },
                // Workaround Internet Explorer 11.0.8 lack of TRIANGLE_FAN
                indices : new Uint16Array([0, 1, 2, 0, 2, 3]),
                primitiveType : PrimitiveType.TRIANGLES
            });

            vertexArray = VertexArray.fromGeometry({
                context : this,
                geometry : geometry,
                attributeLocations : viewportQuadAttributeLocations,
                bufferUsage : BufferUsage.STATIC_DRAW,
                interleave : true
            });

            this.cache.viewportQuad_vertexArray = vertexArray;
        }

        return vertexArray;
    };

    Context.prototype.createViewportQuadCommand = function(fragmentShaderSource, overrides) {
        overrides = defaultValue(overrides, defaultValue.EMPTY_OBJECT);

        return new DrawCommand({
            vertexArray : this.getViewportQuadVertexArray(),
            primitiveType : PrimitiveType.TRIANGLES,
            renderState : overrides.renderState,
            shaderProgram : ShaderProgram.fromCache({
                context : this,
                vertexShaderSource : ViewportQuadVS,
                fragmentShaderSource : fragmentShaderSource,
                attributeLocations : viewportQuadAttributeLocations
            }),
            uniformMap : overrides.uniformMap,
            owner : overrides.owner,
            framebuffer : overrides.framebuffer
        });
    };

    Context.prototype.createPickFramebuffer = function() {
        return new PickFramebuffer(this);
    };

    /**
     * Gets the object associated with a pick color.
     *
     * @param {Color} pickColor The pick color.
     * @returns {Object} The object associated with the pick color, or undefined if no object is associated with that color.
     *
     * @example
     * var object = context.getObjectByPickColor(pickColor);
     *
     * @see Context#createPickId
     */
    Context.prototype.getObjectByPickColor = function(pickColor) {
                if (!defined(pickColor)) {
            throw new DeveloperError('pickColor is required.');
        }
        
        return this._pickObjects[pickColor.toRgba()];
    };

    function PickId(pickObjects, key, color) {
        this._pickObjects = pickObjects;
        this.key = key;
        this.color = color;
    }

    defineProperties(PickId.prototype, {
        object : {
            get : function() {
                return this._pickObjects[this.key];
            },
            set : function(value) {
                this._pickObjects[this.key] = value;
            }
        }
    });

    PickId.prototype.destroy = function() {
        delete this._pickObjects[this.key];
        return undefined;
    };

    /**
     * Creates a unique ID associated with the input object for use with color-buffer picking.
     * The ID has an RGBA color value unique to this context.  You must call destroy()
     * on the pick ID when destroying the input object.
     *
     * @param {Object} object The object to associate with the pick ID.
     * @returns {Object} A PickId object with a <code>color</code> property.
     *
     * @exception {RuntimeError} Out of unique Pick IDs.
     *
     *
     * @example
     * this._pickId = context.createPickId({
     *   primitive : this,
     *   id : this.id
     * });
     *
     * @see Context#getObjectByPickColor
     */
    Context.prototype.createPickId = function(object) {
                if (!defined(object)) {
            throw new DeveloperError('object is required.');
        }
        
        // the increment and assignment have to be separate statements to
        // actually detect overflow in the Uint32 value
        ++this._nextPickColor[0];
        var key = this._nextPickColor[0];
        if (key === 0) {
            // In case of overflow
            throw new RuntimeError('Out of unique Pick IDs.');
        }

        this._pickObjects[key] = object;
        return new PickId(this._pickObjects, key, Color.fromRgba(key));
    };

    Context.prototype.isDestroyed = function() {
        return false;
    };

    Context.prototype.destroy = function() {
        // Destroy all objects in the cache that have a destroy method.
        var cache = this.cache;
        for (var property in cache) {
            if (cache.hasOwnProperty(property)) {
                var propertyValue = cache[property];
                if (defined(propertyValue.destroy)) {
                    propertyValue.destroy();
                }
            }
        }

        this._shaderCache = this._shaderCache.destroy();
        this._defaultTexture = this._defaultTexture && this._defaultTexture.destroy();
        this._defaultCubeMap = this._defaultCubeMap && this._defaultCubeMap.destroy();

        return destroyObject(this);
    };

    return Context;
});

/*global define*/
define('Renderer/loadCubeMap',[
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/loadImage',
        '../ThirdParty/when',
        './CubeMap'
    ], function(
        defined,
        DeveloperError,
        loadImage,
        when,
        CubeMap) {
    'use strict';

    /**
     * Asynchronously loads six images and creates a cube map.  Returns a promise that
     * will resolve to a {@link CubeMap} once loaded, or reject if any image fails to load.
     *
     * @exports loadCubeMap
     *
     * @param {Context} context The context to use to create the cube map.
     * @param {Object} urls The source of each image, or a promise for each URL.  See the example below.
     * @param {Boolean} [allowCrossOrigin=true] Whether to request the image using Cross-Origin
     *        Resource Sharing (CORS).  CORS is only actually used if the image URL is actually cross-origin.
     *        Data URIs are never requested using CORS.
     * @returns {Promise.<CubeMap>} a promise that will resolve to the requested {@link CubeMap} when loaded.
     *
     * @exception {DeveloperError} context is required.
     * @exception {DeveloperError} urls is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.
     *
     *
     * @example
     * Cesium.loadCubeMap(context, {
     *     positiveX : 'skybox_px.png',
     *     negativeX : 'skybox_nx.png',
     *     positiveY : 'skybox_py.png',
     *     negativeY : 'skybox_ny.png',
     *     positiveZ : 'skybox_pz.png',
     *     negativeZ : 'skybox_nz.png'
     * }).then(function(cubeMap) {
     *     // use the cubemap
     * }).otherwise(function(error) {
     *     // an error occurred
     * });
     * 
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     * @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
     *
     * @private
     */
    function loadCubeMap(context, urls, allowCrossOrigin) {
                if (!defined(context)) {
            throw new DeveloperError('context is required.');
        }
        if ((!defined(urls)) ||
            (!defined(urls.positiveX)) ||
            (!defined(urls.negativeX)) ||
            (!defined(urls.positiveY)) ||
            (!defined(urls.negativeY)) ||
            (!defined(urls.positiveZ)) ||
            (!defined(urls.negativeZ))) {
            throw new DeveloperError('urls is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.');
        }
        
        // PERFORMANCE_IDEA: Given the size of some cube maps, we should consider tiling them, which
        // would prevent hiccups when uploading, for example, six 4096x4096 textures to the GPU.
        //
        // Also, it is perhaps acceptable to use the context here in the callbacks, but
        // ideally, we would do it in the primitive's update function.

        var facePromises = [
            loadImage(urls.positiveX, allowCrossOrigin),
            loadImage(urls.negativeX, allowCrossOrigin),
            loadImage(urls.positiveY, allowCrossOrigin),
            loadImage(urls.negativeY, allowCrossOrigin),
            loadImage(urls.positiveZ, allowCrossOrigin),
            loadImage(urls.negativeZ, allowCrossOrigin)
        ];

        return when.all(facePromises, function(images) {
            return new CubeMap({
                context : context,
                source : {
                    positiveX : images[0],
                    negativeX : images[1],
                    positiveY : images[2],
                    negativeY : images[3],
                    positiveZ : images[4],
                    negativeZ : images[5]
                }
            });
        });
    }

    return loadCubeMap;
});

/*global define*/
define('Scene/DiscardMissingTileImagePolicy',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/getImagePixels',
        '../Core/loadImageViaBlob',
        '../ThirdParty/when'
    ], function(
        defaultValue,
        defined,
        DeveloperError,
        getImagePixels,
        loadImageViaBlob,
        when) {
    'use strict';

    /**
     * A policy for discarding tile images that match a known image containing a
     * "missing" image.
     *
     * @alias DiscardMissingTileImagePolicy
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.missingImageUrl The URL of the known missing image.
     * @param {Cartesian2[]} options.pixelsToCheck An array of {@link Cartesian2} pixel positions to
     *        compare against the missing image.
     * @param {Boolean} [options.disableCheckIfAllPixelsAreTransparent=false] If true, the discard check will be disabled
     *                  if all of the pixelsToCheck in the missingImageUrl have an alpha value of 0.  If false, the
     *                  discard check will proceed no matter the values of the pixelsToCheck.
     */
    function DiscardMissingTileImagePolicy(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.missingImageUrl)) {
            throw new DeveloperError('options.missingImageUrl is required.');
        }

        if (!defined(options.pixelsToCheck)) {
            throw new DeveloperError('options.pixelsToCheck is required.');
        }
        
        this._pixelsToCheck = options.pixelsToCheck;
        this._missingImagePixels = undefined;
        this._missingImageByteLength = undefined;
        this._isReady = false;

        var that = this;

        function success(image) {
            if (defined(image.blob)) {
                that._missingImageByteLength = image.blob.size;
            }

            var pixels = getImagePixels(image);

            if (options.disableCheckIfAllPixelsAreTransparent) {
                var allAreTransparent = true;
                var width = image.width;

                var pixelsToCheck = options.pixelsToCheck;
                for (var i = 0, len = pixelsToCheck.length; allAreTransparent && i < len; ++i) {
                    var pos = pixelsToCheck[i];
                    var index = pos.x * 4 + pos.y * width;
                    var alpha = pixels[index + 3];

                    if (alpha > 0) {
                        allAreTransparent = false;
                    }
                }

                if (allAreTransparent) {
                    pixels = undefined;
                }
            }

            that._missingImagePixels = pixels;
            that._isReady = true;
        }

        function failure() {
            // Failed to download "missing" image, so assume that any truly missing tiles
            // will also fail to download and disable the discard check.
            that._missingImagePixels = undefined;
            that._isReady = true;
        }

        when(loadImageViaBlob(options.missingImageUrl), success, failure);
    }

    /**
     * Determines if the discard policy is ready to process images.
     * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
     */
    DiscardMissingTileImagePolicy.prototype.isReady = function() {
        return this._isReady;
    };

    /**
     * Given a tile image, decide whether to discard that image.
     *
     * @param {Image} image An image to test.
     * @returns {Boolean} True if the image should be discarded; otherwise, false.
     *
     * @exception {DeveloperError} <code>shouldDiscardImage</code> must not be called before the discard policy is ready.
     */
    DiscardMissingTileImagePolicy.prototype.shouldDiscardImage = function(image) {
                if (!this._isReady) {
            throw new DeveloperError('shouldDiscardImage must not be called before the discard policy is ready.');
        }
        
        var pixelsToCheck = this._pixelsToCheck;
        var missingImagePixels = this._missingImagePixels;

        // If missingImagePixels is undefined, it indicates that the discard check has been disabled.
        if (!defined(missingImagePixels)) {
            return false;
        }

        if (defined(image.blob) && image.blob.size !== this._missingImageByteLength) {
            return false;
        }

        var pixels = getImagePixels(image);
        var width = image.width;

        for (var i = 0, len = pixelsToCheck.length; i < len; ++i) {
            var pos = pixelsToCheck[i];
            var index = pos.x * 4 + pos.y * width;
            for (var offset = 0; offset < 4; ++offset) {
                var pixel = index + offset;
                if (pixels[pixel] !== missingImagePixels[pixel]) {
                    return false;
                }
            }
        }
        return true;
    };

    return DiscardMissingTileImagePolicy;
});

/*global define*/
define('Scene/ImageryLayerFeatureInfo',[
        '../Core/defined'
    ], function(
        defined) {
    'use strict';

    /**
     * Describes a rasterized feature, such as a point, polygon, polyline, etc., in an imagery layer.
     *
     * @alias ImageryLayerFeatureInfo
     * @constructor
     */
    function ImageryLayerFeatureInfo() {
        /**
         * Gets or sets the name of the feature.
         * @type {String}
         */
        this.name = undefined;

        /**
         * Gets or sets an HTML description of the feature.  The HTML is not trusted and should
         * be sanitized before display to the user.
         * @type {String}
         */
        this.description = undefined;

        /**
         * Gets or sets the position of the feature, or undefined if the position is not known.
         *
         * @type {Cartographic}
         */
        this.position = undefined;

        /**
         * Gets or sets the raw data describing the feature.  The raw data may be in any
         * number of formats, such as GeoJSON, KML, etc.
         * @type {Object}
         */
        this.data = undefined;

        /**
         * Gets or sets the image layer of the feature.
         * @type {Object}
         */
        this.imageryLayer = undefined;
    }

    /**
     * Configures the name of this feature by selecting an appropriate property.  The name will be obtained from
     * one of the following sources, in this order: 1) the property with the name 'name', 2) the property with the name 'title',
     * 3) the first property containing the word 'name', 4) the first property containing the word 'title'.  If
     * the name cannot be obtained from any of these sources, the existing name will be left unchanged.
     *
     * @param {Object} properties An object literal containing the properties of the feature.
     */
    ImageryLayerFeatureInfo.prototype.configureNameFromProperties = function(properties) {
        var namePropertyPrecedence = 10;
        var nameProperty;

        for (var key in properties) {
            if (properties.hasOwnProperty(key) && properties[key]) {
                var lowerKey = key.toLowerCase();

                if (namePropertyPrecedence > 1 && lowerKey === 'name') {
                    namePropertyPrecedence = 1;
                    nameProperty = key;
                } else if (namePropertyPrecedence > 2 && lowerKey === 'title') {
                    namePropertyPrecedence = 2;
                    nameProperty = key;
                } else if (namePropertyPrecedence > 3 && /name/i.test(key)) {
                    namePropertyPrecedence = 3;
                    nameProperty = key;
                } else if (namePropertyPrecedence > 4 && /title/i.test(key)) {
                    namePropertyPrecedence = 4;
                    nameProperty = key;
                }
            }
        }

        if (defined(nameProperty)) {
            this.name = properties[nameProperty];
        }
    };

    /**
     * Configures the description of this feature by creating an HTML table of properties and their values.
     *
     * @param {Object} properties An object literal containing the properties of the feature.
     */
    ImageryLayerFeatureInfo.prototype.configureDescriptionFromProperties = function(properties) {
        function describe(properties) {
            var html = '<table class="cesium-infoBox-defaultTable">';
            for (var key in properties) {
                if (properties.hasOwnProperty(key)) {
                    var value = properties[key];
                    if (defined(value)) {
                        if (typeof value === 'object') {
                            html += '<tr><td>' + key + '</td><td>' + describe(value) + '</td></tr>';
                        } else {
                            html += '<tr><td>' + key + '</td><td>' + value + '</td></tr>';
                        }
                    }
                }
            }
            html += '</table>';

            return html;
        }

        this.description = describe(properties);
    };

    return ImageryLayerFeatureInfo;
});

/*global define*/
define('Scene/ImageryProvider',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/loadCRN',
        '../Core/loadImage',
        '../Core/loadImageViaBlob',
        '../Core/loadKTX',
        '../Core/throttleRequestByServer'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        loadCRN,
        loadImage,
        loadImageViaBlob,
        loadKTX,
        throttleRequestByServer) {
    'use strict';

    /**
     * Provides imagery to be displayed on the surface of an ellipsoid.  This type describes an
     * interface and is not intended to be instantiated directly.
     *
     * @alias ImageryProvider
     * @constructor
     *
     * @see ArcGisMapServerImageryProvider
     * @see SingleTileImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see MapboxImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see WebMapServiceImageryProvider
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Layers.html|Cesium Sandcastle Imagery Layers Demo}
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Layers%20Manipulation.html|Cesium Sandcastle Imagery Manipulation Demo}
     */
    function ImageryProvider() {
        /**
         * The default alpha blending value of this provider, with 0.0 representing fully transparent and
         * 1.0 representing fully opaque.
         *
         * @type {Number}
         * @default undefined
         */
        this.defaultAlpha = undefined;

        /**
         * The default brightness of this provider.  1.0 uses the unmodified imagery color.  Less than 1.0
         * makes the imagery darker while greater than 1.0 makes it brighter.
         *
         * @type {Number}
         * @default undefined
         */
        this.defaultBrightness = undefined;

        /**
         * The default contrast of this provider.  1.0 uses the unmodified imagery color.  Less than 1.0 reduces
         * the contrast while greater than 1.0 increases it.
         *
         * @type {Number}
         * @default undefined
         */
        this.defaultContrast = undefined;

        /**
         * The default hue of this provider in radians. 0.0 uses the unmodified imagery color.
         *
         * @type {Number}
         * @default undefined
         */
        this.defaultHue = undefined;

        /**
         * The default saturation of this provider. 1.0 uses the unmodified imagery color. Less than 1.0 reduces the
         * saturation while greater than 1.0 increases it.
         *
         * @type {Number}
         * @default undefined
         */
        this.defaultSaturation = undefined;

        /**
         * The default gamma correction to apply to this provider.  1.0 uses the unmodified imagery color.
         *
         * @type {Number}
         * @default undefined
         */
        this.defaultGamma = undefined;

        DeveloperError.throwInstantiationError();
    }

    defineProperties(ImageryProvider.prototype, {
        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof ImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof ImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by the instance.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle: {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the width of each tile, in pixels.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link ImageryProvider#ready} returns true. Generally,
         * a minimum level should only be used when the rectangle of the imagery is small
         * enough that the number of tiles at the minimum level is small.  An imagery
         * provider with more than a few tiles at the minimum level will lead to
         * rendering problems.
         * @memberof ImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the tiling scheme used by the provider.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error..  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof ImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery. This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof ImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof ImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof ImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     * @function
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    ImageryProvider.prototype.getTileCredits = DeveloperError.throwInstantiationError;

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link ImageryProvider#ready} returns true.
     * @function
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    ImageryProvider.prototype.requestImage = DeveloperError.throwInstantiationError;

    /**
     * Asynchronously determines what features, if any, are located at a given longitude and latitude within
     * a tile.  This function should not be called before {@link ImageryProvider#ready} returns true.
     * This function is optional, so it may not exist on all ImageryProviders.
     *
     * @function
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     *
     * @exception {DeveloperError} <code>pickFeatures</code> must not be called before the imagery provider is ready.
     */
    ImageryProvider.prototype.pickFeatures = DeveloperError.throwInstantiationError;

    var ktxRegex = /\.ktx$/i;
    var crnRegex = /\.crn$/i;

    /**
     * Loads an image from a given URL.  If the server referenced by the URL already has
     * too many requests pending, this function will instead return undefined, indicating
     * that the request should be retried later.
     *
     * @param {ImageryProvider} imageryProvider The imagery provider for the URL.
     * @param {String} url The URL of the image.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     */
    ImageryProvider.loadImage = function(imageryProvider, url) {
        if (ktxRegex.test(url)) {
            return throttleRequestByServer(url, loadKTX);
        } else if (crnRegex.test(url)) {
            return throttleRequestByServer(url, loadCRN);
        } else if (defined(imageryProvider.tileDiscardPolicy)) {
            return throttleRequestByServer(url, loadImageViaBlob);
        }
        return throttleRequestByServer(url, loadImage);
    };

    return ImageryProvider;
});

/*global define*/
define('Scene/ArcGisMapServerImageryProvider',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/GeographicTilingScheme',
        '../Core/loadJson',
        '../Core/loadJsonp',
        '../Core/Math',
        '../Core/Rectangle',
        '../Core/RuntimeError',
        '../Core/TileProviderError',
        '../Core/WebMercatorProjection',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/when',
        './DiscardMissingTileImagePolicy',
        './ImageryLayerFeatureInfo',
        './ImageryProvider'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartographic,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        GeographicTilingScheme,
        loadJson,
        loadJsonp,
        CesiumMath,
        Rectangle,
        RuntimeError,
        TileProviderError,
        WebMercatorProjection,
        WebMercatorTilingScheme,
        when,
        DiscardMissingTileImagePolicy,
        ImageryLayerFeatureInfo,
        ImageryProvider) {
    'use strict';

    /**
     * Provides tiled imagery hosted by an ArcGIS MapServer.  By default, the server's pre-cached tiles are
     * used, if available.
     *
     * @alias ArcGisMapServerImageryProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The URL of the ArcGIS MapServer service.
     * @param {String} [options.token] The ArcGIS token used to authenticate with the ArcGIS MapServer service.
     * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
     *        is invalid and should be discarded.  If this value is not specified, a default
     *        {@link DiscardMissingTileImagePolicy} is used for tiled map servers, and a
     *        {@link NeverTileDiscardPolicy} is used for non-tiled map servers.  In the former case,
     *        we request tile 0,0 at the maximum tile level and check pixels (0,0), (200,20), (20,200),
     *        (80,110), and (160, 130).  If all of these pixels are transparent, the discard check is
     *        disabled and no tiles are discarded.  If any of them have a non-transparent color, any
     *        tile that has the same values in these pixel locations is discarded.  The end result of
     *        these defaults should be correct tile discarding for a standard ArcGIS Server.  To ensure
     *        that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this
     *        parameter.
     * @param {Proxy} [options.proxy] A proxy to use for requests. This object is
     *        expected to have a getURL function which returns the proxied URL, if needed.
     * @param {Boolean} [options.usePreCachedTilesIfAvailable=true] If true, the server's pre-cached
     *        tiles are used if they are available.  If false, any pre-cached tiles are ignored and the
     *        'export' service is used.
     * @param {String} [options.layers] A comma-separated list of the layers to show, or undefined if all layers should be shown.
     * @param {Boolean} [options.enablePickFeatures=true] If true, {@link ArcGisMapServerImageryProvider#pickFeatures} will invoke
     *        the Identify service on the MapServer and return the features included in the response.  If false,
     *        {@link ArcGisMapServerImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
     *        without communicating with the server.  Set this property to false if you don't want this provider's features to
     *        be pickable. Can be overridden by setting the {@link ArcGisMapServerImageryProvider#enablePickFeatures} property on the object.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer.  This parameter is ignored when accessing
     *                    a tiled layer.
     * @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme to use to divide the world into tiles.
     *                       This parameter is ignored when accessing a tiled server.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified and used,
     *                    this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     *                    parameter is specified, the WGS84 ellipsoid is used.
     * @param {Number} [options.tileWidth=256] The width of each tile in pixels.  This parameter is ignored when accessing a tiled server.
     * @param {Number} [options.tileHeight=256] The height of each tile in pixels.  This parameter is ignored when accessing a tiled server.
     * @param {Number} [options.maximumLevel] The maximum tile level to request, or undefined if there is no maximum.  This parameter is ignored when accessing
     *                                        a tiled server.
     *
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     *
     *
     * @example
     * var esri = new Cesium.ArcGisMapServerImageryProvider({
     *     url : 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
     * });
     *
     * @see {@link http://resources.esri.com/help/9.3/arcgisserver/apis/rest/|ArcGIS Server REST API}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     */
    function ArcGisMapServerImageryProvider(options) {
        options = defaultValue(options, {});

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        this._url = options.url;
        this._token = options.token;
        this._tileDiscardPolicy = options.tileDiscardPolicy;
        this._proxy = options.proxy;

        this._tileWidth = defaultValue(options.tileWidth, 256);
        this._tileHeight = defaultValue(options.tileHeight, 256);
        this._maximumLevel = options.maximumLevel;
        this._tilingScheme = defaultValue(options.tilingScheme, new GeographicTilingScheme({ ellipsoid : options.ellipsoid }));
        this._credit = undefined;
        this._useTiles = defaultValue(options.usePreCachedTilesIfAvailable, true);
        this._rectangle = defaultValue(options.rectangle, this._tilingScheme.rectangle);
        this._layers = options.layers;

        /**
         * Gets or sets a value indicating whether feature picking is enabled.  If true, {@link ArcGisMapServerImageryProvider#pickFeatures} will
         * invoke the "identify" operation on the ArcGIS server and return the features included in the response.  If false,
         * {@link ArcGisMapServerImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
         * without communicating with the server.
         * @type {Boolean}
         * @default true
         */
        this.enablePickFeatures = defaultValue(options.enablePickFeatures, true);

        this._errorEvent = new Event();

        this._ready = false;
        this._readyPromise = when.defer();

        // Grab the details of this MapServer.
        var that = this;
        var metadataError;

        function metadataSuccess(data) {
            var tileInfo = data.tileInfo;
            if (!defined(tileInfo)) {
                that._useTiles = false;
            } else {
                that._tileWidth = tileInfo.rows;
                that._tileHeight = tileInfo.cols;

                if (tileInfo.spatialReference.wkid === 102100 ||
                    tileInfo.spatialReference.wkid === 102113) {
                    that._tilingScheme = new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
                } else if (data.tileInfo.spatialReference.wkid === 4326) {
                    that._tilingScheme = new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
                } else {
                    var message = 'Tile spatial reference WKID ' + data.tileInfo.spatialReference.wkid + ' is not supported.';
                    metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
                    return;
                }
                that._maximumLevel = data.tileInfo.lods.length - 1;

                if (defined(data.fullExtent)) {
                    if (defined(data.fullExtent.spatialReference) && defined(data.fullExtent.spatialReference.wkid)) {
                        if (data.fullExtent.spatialReference.wkid === 102100 ||
                            data.fullExtent.spatialReference.wkid === 102113) {

                            var projection = new WebMercatorProjection();
                            var extent = data.fullExtent;
                            var sw = projection.unproject(new Cartesian3(Math.max(extent.xmin, -that._tilingScheme.ellipsoid.maximumRadius * Math.PI), Math.max(extent.ymin, -that._tilingScheme.ellipsoid.maximumRadius * Math.PI), 0.0));
                            var ne = projection.unproject(new Cartesian3(Math.min(extent.xmax, that._tilingScheme.ellipsoid.maximumRadius * Math.PI), Math.min(extent.ymax, that._tilingScheme.ellipsoid.maximumRadius * Math.PI), 0.0));
                            that._rectangle = new Rectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude);
                        } else if (data.fullExtent.spatialReference.wkid === 4326) {
                            that._rectangle = Rectangle.fromDegrees(data.fullExtent.xmin, data.fullExtent.ymin, data.fullExtent.xmax, data.fullExtent.ymax);
                        } else {
                            var extentMessage = 'fullExtent.spatialReference WKID ' + data.fullExtent.spatialReference.wkid + ' is not supported.';
                            metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, extentMessage, undefined, undefined, undefined, requestMetadata);
                            return;
                        }
                    }
                } else {
                    that._rectangle = that._tilingScheme.rectangle;
                }

                // Install the default tile discard policy if none has been supplied.
                if (!defined(that._tileDiscardPolicy)) {
                    that._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
                        missingImageUrl : buildImageUrl(that, 0, 0, that._maximumLevel),
                        pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(200, 20), new Cartesian2(20, 200), new Cartesian2(80, 110), new Cartesian2(160, 130)],
                        disableCheckIfAllPixelsAreTransparent : true
                    });
                }

                that._useTiles = true;
            }

            if (defined(data.copyrightText) && data.copyrightText.length > 0) {
                that._credit = new Credit(data.copyrightText);
            }

            that._ready = true;
            that._readyPromise.resolve(true);
            TileProviderError.handleSuccess(metadataError);
        }

        function metadataFailure(e) {
            var message = 'An error occurred while accessing ' + that._url + '.';
            metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
            that._readyPromise.reject(new RuntimeError(message));
        }

        function requestMetadata() {
            var parameters = {
                f: 'json'
            };

            if (defined(that._token)) {
                parameters.token = that._token;
            }

            var metadata = loadJsonp(that._url, {
                parameters : parameters,
                proxy : that._proxy
            });
            when(metadata, metadataSuccess, metadataFailure);
        }

        if (this._useTiles) {
            requestMetadata();
        } else {
            this._ready = true;
            this._readyPromise.resolve(true);
        }
    }

    function buildImageUrl(imageryProvider, x, y, level) {
        var url;
        if (imageryProvider._useTiles) {
            url = imageryProvider._url + '/tile/' + level + '/' + y + '/' + x;
        } else {
            var nativeRectangle = imageryProvider._tilingScheme.tileXYToNativeRectangle(x, y, level);
            var bbox = nativeRectangle.west + '%2C' + nativeRectangle.south + '%2C' + nativeRectangle.east + '%2C' + nativeRectangle.north;

            url = imageryProvider._url + '/export?';
            url += 'bbox=' + bbox;
            if (imageryProvider._tilingScheme instanceof GeographicTilingScheme) {
                url += '&bboxSR=4326&imageSR=4326';
            } else {
                url += '&bboxSR=3857&imageSR=3857';
            }
            url += '&size=' + imageryProvider._tileWidth + '%2C' + imageryProvider._tileHeight;
            url += '&format=png&transparent=true&f=image';

            if (imageryProvider.layers) {
                url += '&layers=show:' + imageryProvider.layers;
            }
        }

        var token = imageryProvider._token;
        if (defined(token)) {
            if (url.indexOf('?') === -1) {
                url += '?';
            }
            if (url[url.length - 1] !== '?'){
                url += '&';
            }
            url += 'token=' + token;
        }

        var proxy = imageryProvider._proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        return url;
    }

    defineProperties(ArcGisMapServerImageryProvider.prototype, {
        /**
         * Gets the URL of the ArcGIS MapServer.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the ArcGIS token used to authenticate with the ArcGis MapServer service.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        token : {
            get : function() {
                return this._token;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._proxy;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
                }
                
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight: {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
                }
                
                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
                }
                
                return this._maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
                }
                
                return 0;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
                }
                
                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
                }
                
                return this._rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
                }
                
                return this._tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets a value indicating whether this imagery provider is using pre-cached tiles from the
         * ArcGIS MapServer.  If the imagery provider is not yet ready ({@link ArcGisMapServerImageryProvider#ready}), this function
         * will return the value of `options.usePreCachedTilesIfAvailable`, even if the MapServer does
         * not have pre-cached tiles.
         * @memberof ArcGisMapServerImageryProvider.prototype
         *
         * @type {Boolean}
         * @readonly
         * @default true
         */
        usingPrecachedTiles : {
            get : function() {
                return this._useTiles;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof ArcGisMapServerImageryProvider.prototype
         *
         * @type {Boolean}
         * @readonly
         * @default true
         */
        hasAlphaChannel : {
            get : function() {
                return true;
            }
        },

        /**
         * Gets the comma-separated list of layer IDs to show.
         * @memberof ArcGisMapServerImageryProvider.prototype
         * 
         * @type {String}
         */
        layers : {
            get : function() {
                return this._layers;
            }
        }
    });


    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    ArcGisMapServerImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link ArcGisMapServerImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    ArcGisMapServerImageryProvider.prototype.requestImage = function(x, y, level) {
                if (!this._ready) {
            throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
        }
        
        var url = buildImageUrl(this, x, y, level);
        return ImageryProvider.loadImage(this, url);
    };

    /**
    /**
     * Asynchronously determines what features, if any, are located at a given longitude and latitude within
     * a tile.  This function should not be called before {@link ImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *
     * @exception {DeveloperError} <code>pickFeatures</code> must not be called before the imagery provider is ready.
     */
    ArcGisMapServerImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
                if (!this._ready) {
            throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.');
        }
        
        if (!this.enablePickFeatures) {
            return undefined;
        }

        var rectangle = this._tilingScheme.tileXYToNativeRectangle(x, y, level);

        var horizontal;
        var vertical;
        var sr;
        if (this._tilingScheme instanceof GeographicTilingScheme) {
            horizontal = CesiumMath.toDegrees(longitude);
            vertical = CesiumMath.toDegrees(latitude);
            sr = '4326';
        } else {
            var projected = this._tilingScheme.projection.project(new Cartographic(longitude, latitude, 0.0));
            horizontal = projected.x;
            vertical = projected.y;
            sr = '3857';
        }

        var url = this._url + '/identify?f=json&tolerance=2&geometryType=esriGeometryPoint';
        url += '&geometry=' + horizontal + ',' + vertical;
        url += '&mapExtent=' + rectangle.west + ',' + rectangle.south + ',' + rectangle.east + ',' + rectangle.north;
        url += '&imageDisplay=' + this._tileWidth + ',' + this._tileHeight + ',96';
        url += '&sr=' + sr;

        url += '&layers=visible';
        if (defined(this._layers)) {
            url += ':' + this._layers;
        }

        if (defined(this._token)) {
            url += '&token=' + this._token;
        }

        if (defined(this._proxy)) {
            url = this._proxy.getURL(url);
        }

        return loadJson(url).then(function(json) {
            var result = [];

            var features = json.results;
            if (!defined(features)) {
                return result;
            }

            for (var i = 0; i < features.length; ++i) {
                var feature = features[i];

                var featureInfo = new ImageryLayerFeatureInfo();
                featureInfo.data = feature;
                featureInfo.name = feature.value;
                featureInfo.properties = feature.attributes;
                featureInfo.configureDescriptionFromProperties(feature.attributes);

                // If this is a point feature, use the coordinates of the point.
                if (feature.geometryType === 'esriGeometryPoint' && feature.geometry) {
                    var wkid = feature.geometry.spatialReference && feature.geometry.spatialReference.wkid ? feature.geometry.spatialReference.wkid : 4326;
                    if (wkid === 4326 || wkid === 4283) {
                        featureInfo.position = Cartographic.fromDegrees(feature.geometry.x, feature.geometry.y, feature.geometry.z);
                    } else if (wkid === 102100 || wkid === 900913 || wkid === 3857) {
                        var projection = new WebMercatorProjection();
                        featureInfo.position = projection.unproject(new Cartesian3(feature.geometry.x, feature.geometry.y, feature.geometry.z));
                    }
                }

                result.push(featureInfo);
            }

            return result;
        });
    };

    return ArcGisMapServerImageryProvider;
});

/*global define*/
define('Scene/BingMapsStyle',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The types of imagery provided by Bing Maps.
     *
     * @exports BingMapsStyle
     *
     * @see BingMapsImageryProvider
     */
    var BingMapsStyle = {
        /**
         * Aerial imagery.
         *
         * @type {String}
         * @constant
         */
        AERIAL : 'Aerial',

        /**
         * Aerial imagery with a road overlay.
         *
         * @type {String}
         * @constant
         */
        AERIAL_WITH_LABELS : 'AerialWithLabels',

        /**
         * Roads without additional imagery.
         *
         * @type {String}
         * @constant
         */
        ROAD : 'Road',

        /**
         * Ordnance Survey imagery
         *
         * @type {String}
         * @constant
         */
        ORDNANCE_SURVEY : 'OrdnanceSurvey',

        /**
         * Collins Bart imagery.
         *
         * @type {String}
         * @constant
         */
        COLLINS_BART : 'CollinsBart'
    };

    return freezeObject(BingMapsStyle);
});

/*global define*/
define('Scene/BingMapsImageryProvider',[
        '../Core/BingMapsApi',
        '../Core/Cartesian2',
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/loadJsonp',
        '../Core/Math',
        '../Core/Rectangle',
        '../Core/RuntimeError',
        '../Core/TileProviderError',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/when',
        './BingMapsStyle',
        './DiscardMissingTileImagePolicy',
        './ImageryProvider'
    ], function(
        BingMapsApi,
        Cartesian2,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        loadJsonp,
        CesiumMath,
        Rectangle,
        RuntimeError,
        TileProviderError,
        WebMercatorTilingScheme,
        when,
        BingMapsStyle,
        DiscardMissingTileImagePolicy,
        ImageryProvider) {
    'use strict';

    /**
     * Provides tiled imagery using the Bing Maps Imagery REST API.
     *
     * @alias BingMapsImageryProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The url of the Bing Maps server hosting the imagery.
     * @param {String} [options.key] The Bing Maps key for your application, which can be
     *        created at {@link https://www.bingmapsportal.com/}.
     *        If this parameter is not provided, {@link BingMapsApi.defaultKey} is used.
     *        If {@link BingMapsApi.defaultKey} is undefined as well, a message is
     *        written to the console reminding you that you must create and supply a Bing Maps
     *        key as soon as possible.  Please do not deploy an application that uses
     *        Bing Maps imagery without creating a separate key for your application.
     * @param {String} [options.tileProtocol] The protocol to use when loading tiles, e.g. 'http:' or 'https:'.
     *        By default, tiles are loaded using the same protocol as the page.
     * @param {String} [options.mapStyle=BingMapsStyle.AERIAL] The type of Bing Maps
     *        imagery to load.
     * @param {String} [options.culture=''] The culture to use when requesting Bing Maps imagery. Not
     *        all cultures are supported. See {@link http://msdn.microsoft.com/en-us/library/hh441729.aspx}
     *        for information on the supported cultures.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
     *        is invalid and should be discarded.  If this value is not specified, a default
     *        {@link DiscardMissingTileImagePolicy} is used which requests
     *        tile 0,0 at the maximum tile level and checks pixels (0,0), (120,140), (130,160),
     *        (200,50), and (200,200).  If all of these pixels are transparent, the discard check is
     *        disabled and no tiles are discarded.  If any of them have a non-transparent color, any
     *        tile that has the same values in these pixel locations is discarded.  The end result of
     *        these defaults should be correct tile discarding for a standard Bing Maps server.  To ensure
     *        that no tiles are discarded, construct and pass a {@link NeverTileDiscardPolicy} for this
     *        parameter.
     * @param {Proxy} [options.proxy] A proxy to use for requests. This object is
     *        expected to have a getURL function which returns the proxied URL, if needed.
     *
     * @see ArcGisMapServerImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     *
     *
     * @example
     * var bing = new Cesium.BingMapsImageryProvider({
     *     url : 'https://dev.virtualearth.net',
     *     key : 'get-yours-at-https://www.bingmapsportal.com/',
     *     mapStyle : Cesium.BingMapsStyle.AERIAL
     * });
     *
     * @see {@link http://msdn.microsoft.com/en-us/library/ff701713.aspx|Bing Maps REST Services}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     */
    function BingMapsImageryProvider(options) {
        options = defaultValue(options, {});

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        this._key = BingMapsApi.getKey(options.key);
        this._keyErrorCredit = BingMapsApi.getErrorCredit(options.key);

        this._url = options.url;
        this._tileProtocol = options.tileProtocol;
        this._mapStyle = defaultValue(options.mapStyle, BingMapsStyle.AERIAL);
        this._culture = defaultValue(options.culture, '');
        this._tileDiscardPolicy = options.tileDiscardPolicy;
        this._proxy = options.proxy;
        this._credit = new Credit('Bing Imagery', BingMapsImageryProvider._logoData, 'http://www.bing.com');

        /**
         * The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider.
         * Changing this value after creating an {@link ImageryLayer} for this provider will have
         * no effect.  Instead, set the layer's {@link ImageryLayer#gamma} property.
         *
         * @type {Number}
         * @default 1.0
         */
        this.defaultGamma = 1.0;

        this._tilingScheme = new WebMercatorTilingScheme({
            numberOfLevelZeroTilesX : 2,
            numberOfLevelZeroTilesY : 2,
            ellipsoid : options.ellipsoid
        });

        this._tileWidth = undefined;
        this._tileHeight = undefined;
        this._maximumLevel = undefined;
        this._imageUrlTemplate = undefined;
        this._imageUrlSubdomains = undefined;

        this._errorEvent = new Event();

        this._ready = false;
        this._readyPromise = when.defer();

        var metadataUrl = this._url + '/REST/v1/Imagery/Metadata/' + this._mapStyle + '?incl=ImageryProviders&key=' + this._key;
        var that = this;
        var metadataError;

        function metadataSuccess(data) {
            var resource = data.resourceSets[0].resources[0];

            that._tileWidth = resource.imageWidth;
            that._tileHeight = resource.imageHeight;
            that._maximumLevel = resource.zoomMax - 1;
            that._imageUrlSubdomains = resource.imageUrlSubdomains;
            that._imageUrlTemplate = resource.imageUrl.replace('{culture}', that._culture);

            var tileProtocol = that._tileProtocol;
            if (!defined(tileProtocol)) {
                // use the document's protocol, unless it's not http or https
                var documentProtocol = document.location.protocol;
                tileProtocol = /^http/.test(documentProtocol) ? documentProtocol : 'http:';
            }

            that._imageUrlTemplate = that._imageUrlTemplate.replace(/^http:/, tileProtocol);

            // Install the default tile discard policy if none has been supplied.
            if (!defined(that._tileDiscardPolicy)) {
                that._tileDiscardPolicy = new DiscardMissingTileImagePolicy({
                    missingImageUrl : buildImageUrl(that, 0, 0, that._maximumLevel),
                    pixelsToCheck : [new Cartesian2(0, 0), new Cartesian2(120, 140), new Cartesian2(130, 160), new Cartesian2(200, 50), new Cartesian2(200, 200)],
                    disableCheckIfAllPixelsAreTransparent : true
                });
            }

            var attributionList = that._attributionList = resource.imageryProviders;
            if (!attributionList) {
                attributionList = that._attributionList = [];
            }

            for (var attributionIndex = 0, attributionLength = attributionList.length; attributionIndex < attributionLength; ++attributionIndex) {
                var attribution = attributionList[attributionIndex];

                attribution.credit = new Credit(attribution.attribution);

                var coverageAreas = attribution.coverageAreas;

                for (var areaIndex = 0, areaLength = attribution.coverageAreas.length; areaIndex < areaLength; ++areaIndex) {
                    var area = coverageAreas[areaIndex];
                    var bbox = area.bbox;
                    area.bbox = new Rectangle(
                            CesiumMath.toRadians(bbox[1]),
                            CesiumMath.toRadians(bbox[0]),
                            CesiumMath.toRadians(bbox[3]),
                            CesiumMath.toRadians(bbox[2]));
                }
            }

            that._ready = true;
            that._readyPromise.resolve(true);
            TileProviderError.handleSuccess(metadataError);
        }

        function metadataFailure(e) {
            var message = 'An error occurred while accessing ' + metadataUrl + '.';
            metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
            that._readyPromise.reject(new RuntimeError(message));
        }

        function requestMetadata() {
            var metadata = loadJsonp(metadataUrl, {
                callbackParameterName : 'jsonp',
                proxy : that._proxy
            });
            when(metadata, metadataSuccess, metadataFailure);
        }

        requestMetadata();
    }

    defineProperties(BingMapsImageryProvider.prototype, {
        /**
         * Gets the name of the BingMaps server url hosting the imagery.
         * @memberof BingMapsImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._proxy;
            }
        },


        /**
         * Gets the Bing Maps key.
         * @memberof BingMapsImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        key : {
            get : function() {
                return this._key;
            }
        },

        /**
         * Gets the type of Bing Maps imagery to load.
         * @memberof BingMapsImageryProvider.prototype
         * @type {BingMapsStyle}
         * @readonly
         */
        mapStyle : {
            get : function() {
                return this._mapStyle;
            }
        },

        /**
         * The culture to use when requesting Bing Maps imagery. Not
         * all cultures are supported. See {@link http://msdn.microsoft.com/en-us/library/hh441729.aspx}
         * for information on the supported cultures.
         * @memberof BingMapsImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        culture : {
            get : function() {
                return this._culture;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
                }
                
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight: {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
                }
                
                return this._tileHeight;
            }
        },


        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
                }
                
                return this._maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
                }
                
                return 0;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
                }
                
                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
                }
                
                return this._tilingScheme.rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
                }
                
                return this._tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link BingMapsImageryProvider#ready} returns true.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  Setting this property to false reduces memory usage
         * and texture upload time.
         * @memberof BingMapsImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return false;
            }
        }
    });

    var rectangleScratch = new Rectangle();

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    BingMapsImageryProvider.prototype.getTileCredits = function(x, y, level) {
                if (!this._ready) {
            throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.');
        }
        
        var rectangle = this._tilingScheme.tileXYToRectangle(x, y, level, rectangleScratch);
        var result = getRectangleAttribution(this._attributionList, level, rectangle);

        if (defined(this._keyErrorCredit)) {
            result.push(this._keyErrorCredit);
        }

        return result;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link BingMapsImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    BingMapsImageryProvider.prototype.requestImage = function(x, y, level) {
                if (!this._ready) {
            throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
        }
        
        var url = buildImageUrl(this, x, y, level);
        return ImageryProvider.loadImage(this, url);
    };

    /**
     * Picking features is not currently supported by this imagery provider, so this function simply returns
     * undefined.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    BingMapsImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return undefined;
    };

    BingMapsImageryProvider._logoData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD0AAAAaCAYAAAAEy1RnAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3gIDEgcPTMnXOQAAClZJREFUWMPdWGtsFNcV/u689uH1+sXaONhlWQzBENtxiUFBpBSLd60IpXHSNig4URtSYQUkRJNSi0igViVVVBJBaBsiAgKRQJSG8AgEHCCWU4iBCprY2MSgXfOI16y9D3s9Mzsztz9yB12WNU2i9Ecy0tHOzN4793zn3POdcy7BnRfJ8I7iB3SRDPeEExswLz8Y0DZIAYDIRGAgLQAm+7Xle31J3L3Anp1MZPY+BUBjorN332vgYhpgV1FRUd6TTz45ubq6OtDV1SXpuu5g//Oept9wNwlMyAi8IXDjyF245TsDTdivDMATCATGNDU1/WbhwoWPTZs2bWx1dXWhx+Oxrl+/PqTrus5t9W8KWEzjinTAYhro/xuBStwiIgBnJBLxKIoy1u/3V/r9/krDMMz3339/Z3t7e38ikUgCMDLEt8W+Q0cAI3McYTDDmZxh7DESG5Ni43jg9Gsa+X+OsxWxPSJTSj3JZFK5ZRVJErOzs8e6XC4fgGwALhbzDgAKU1hK28KEA6PMmTMn56233qpevnz5PQDcbJ7EzVUAuMrLy3MBeABkcWOEDELSyFe4y7iMoHkriZZlKYZh8ASHZDKpJJPJHAC5APIA5APIAeBlCjo5TwlpXnbOmTPHP3fu3KZVq1atZKBcDJQ9x7V48WJfc3Pzhp6enj+tXLnyR8w4MjdG4gyVDk7KICMClzKlLUrpbQMNw5AkScppbGz8cWdn57WjR4/2caw+DEBlYjO8wX1foZQWuN3uKZIklQD4G+fhlG0Yl8uVm5WVVW6app6dne0D0G8vnxbjJntHubCUOK/badZICyWanrJuAaeUknTQpmlKkUhEWbx48U8LCwtHhUKha+fPn+85fPhwV0tLyzUACSZx9jvMFhIByNFoVDEMw/qKB5HPvJfkUqBr9+7deklJyZ/j8bi5ffv2OAslieMLsG+m2DybT2QuzEQOsF5SUqJfvXo1yc2l6Xn6rgSRSCSEc+fOhVeuXLmwoqJixvTp0wcWLFgQ7unpudHR0dF97ty5z/fu3XseQJh5adjeerquy5ZlCalUivh8Pt8HH3ywzOPxyD09PZ81NjZ+2NnZaQEQx40b54vFYqaqquEVK1b4a2tr/WvWrDn18ssv144fP36SqqoD69ev371nz57rDLwAwHHkyJGfjRs3rtowDOv06dOnu7q6rs6bN2/s7Nmz9zIjDKenWoFZKg/AlMLCwl82Nzf/m3LX22+/fXb06NF/ALC8u7u7m6ZdkUhksL29/UpLS0vzunXrVgAoBzAaQBGAiY2NjUui0ei1RCLRFwwG/9PX19cVi8WCqqoOdHd3HysrK6sDMCccDl8IBoOtiqIsOnbs2D+i0eiV3t7ez8Ph8GeRSKRT07TB/v7+i1OnTp0HYBqABzs7O/+paVo0Fot1RyKRi/F4/Gp/f39XIpHoZnoUMn6wU+ZtRDaymwmxZFk2AWjvvvvuJ/F4PMn/n5+fn1VeXu6fOXNmbU1NzUOM4Bz8QqIoyg6HwxuLxfq3bdu2a+vWrW/09/dfKy0tffDVV199BEC20+n0ud3uQgBup9Pp83g8JYqieE+ePPnxxo0bt33xxRen8/Ly7n3hhRcWASh47bXX5pWVldWFw+GuXbt27XjzzTd3BoPBDq/XG1AUZRRHmAKPVfqaoKkgCCkA+oYNG84Eg0FHTU1N5ezZs8eWlJQ4CSF8/LvZYhJPQoQQpFKpwcrKyo1su9HBwUF99erVv588eXINgOOmacIwDEopdaZSKUIpxYkTJz6sr68/BMBav379RcMwZk2aNOl+AP+qq6t7xDTNVEVFxR+j0WgSAJk4ceKlTz/9tNzpdHpZvIvpjVW6pykhhBJCbkvwgiAQQogEQL558ybdtGlTsLm5OWJZdxZmlmWll5OUEEJN0zSGhob6GcOrALSzZ8/2apqWcLlc2axGACNRkRAimqaph0Kh68xIwwB0y7IMSZKcABz5+fkl8Xj8y2g0apOb5na7rYGBgS/JV54Q0qpAAoBKaS0jBWClg1ZVFeFw2AlgVF1dXeDpp5+eWVFRUVpcXOzgvQwAbrcbDJhdudlGpKZpGtx6JCcnRxIEQbQsS2PjbjM+AMvlchnMSBaXkr7ymCCIhmEYfMoVRVESBEHI0CaTTNubssUsQRBuubCtra33pZdeCk6YMCGwZs2aipqaGn9paWmuJEl3JP0bN258eeTIkRMABrm0YomiaImiKGVlZeWxLecAgBkzZvgdDkfWjRs3ggA0bpfpoiiahBCqKEqKAy2yULMA6MlkMp6Xl3cP1x2SWCwmFhQU+CmlFhfHNFOevpX4LcvSJUkyAeDQoUOh119//fpTTz01Zf78+UWBQCBHUZQ7yE/TNGPfvn0n33vvvSP79+//BECMeZsCMGRZNgRBgNPpHHXx4sVVDQ0Nf1+wYMGYJ554YikAevDgwUMA4oIgQJZlSggZdDqdBiGEZGdn6ww0tQlJURTT4/EMHz9+/MCjjz7622AwuHbZsmVbiouLvWvXrm1wOp3ZqVRqaKQTIInf1gAMl8ulU0q1CxcuBGOxmL5u3bryQCDgycrKEjORXGtra8eOHTsOHz169OyVK1cuA+hlRYrGlNRkWR7UNO2mYRiaz+cb3dLS8gYhhOi6Hj116tSOVatWHQNALcsaME0zLghClBDSZ9+zQsZ2SoJS2udwOKLPPffcvsrKyrJAIPDQ/v37txiGofX19V3r7e29UlBQMHqEVpjwnrYA6PF4PK6q6s2qqqqpZWVlitvtljOB7enpiWzbtu3wgQMHTre1tV0E0MeKkkGuIhMAqHv37u30er3Px+NxlyiKygMPPOAnhFiXLl0Kbd68uYPNsXbu3Lk6mUwaqqr2btmyZUdtbe3hd955pwvAEFNcO3jw4K/b2tqiqqpGIpGI4/HHH/9rQ0PDCa/XOyoSidDLly8PNTU1PcZ4QuNK1ju6NYHFRAGASXPnzv1Fa2vrxzTDpapqateuXR/Nnz+/SVGUhwFMBzCBFSLZLF75DsrJGpXRAH4EIABgPIBxAEoBFAPwARjFif1sNzZ25+VlOhaxufcCqAFQC+BhAPVLliz5XSqVUkOhUAuAKWnFyR3dlsw+fg+A+8eMGfPzTZs2bY9GozEb8JkzZ9qXLl36l+Li4l8B+AmAyQDGsGrOzfXNPGPawG2l85jksmcPm+vihH+2W1iF3bvZPN+sWbPuGx4eDrW3t+85fvz41o6OjmZN04Y0TYvV19cvYIbN5QqUjG2mwj5YAqDK4XDMe+aZZ55vbW09+sorr2yuqqpqYFatAuBn3uB7XzJCY297XeaUd2RoGzOJmHb6IjFj5D777LP3DQwMfDw8PBxSVbUvkUj0hEKhj1588cXH2O7zMSPdplumoxveMx5Zlj3jx4/39vb26gMDA4MsvgYZo+p8Pr7LqQX5Ds/U7d0jFxUVZS1atKg4Nzc317Isp67rZldXV6y5ufkmI78hFtcmrx8ZweMit6XsUs4+6kmlgbW+peLf9gyMZNCR374G0y/FxEzX8b/8+bkXEBxKFwAAAABJRU5ErkJggg==';

    /**
     * Converts a tiles (x, y, level) position into a quadkey used to request an image
     * from a Bing Maps server.
     *
     * @param {Number} x The tile's x coordinate.
     * @param {Number} y The tile's y coordinate.
     * @param {Number} level The tile's zoom level.
     *
     * @see {@link http://msdn.microsoft.com/en-us/library/bb259689.aspx|Bing Maps Tile System}
     * @see BingMapsImageryProvider#quadKeyToTileXY
     */
    BingMapsImageryProvider.tileXYToQuadKey = function(x, y, level) {
        var quadkey = '';
        for ( var i = level; i >= 0; --i) {
            var bitmask = 1 << i;
            var digit = 0;

            if ((x & bitmask) !== 0) {
                digit |= 1;
            }

            if ((y & bitmask) !== 0) {
                digit |= 2;
            }

            quadkey += digit;
        }
        return quadkey;
    };

    /**
     * Converts a tile's quadkey used to request an image from a Bing Maps server into the
     * (x, y, level) position.
     *
     * @param {String} quadkey The tile's quad key
     *
     * @see {@link http://msdn.microsoft.com/en-us/library/bb259689.aspx|Bing Maps Tile System}
     * @see BingMapsImageryProvider#tileXYToQuadKey
     */
    BingMapsImageryProvider.quadKeyToTileXY = function(quadkey) {
        var x = 0;
        var y = 0;
        var level = quadkey.length - 1;
        for ( var i = level; i >= 0; --i) {
            var bitmask = 1 << i;
            var digit = +quadkey[level - i];

            if ((digit & 1) !== 0) {
                x |= bitmask;
            }

            if ((digit & 2) !== 0) {
                y |= bitmask;
            }
        }
        return {
            x : x,
            y : y,
            level : level
        };
    };

    function buildImageUrl(imageryProvider, x, y, level) {
        var imageUrl = imageryProvider._imageUrlTemplate;

        var quadkey = BingMapsImageryProvider.tileXYToQuadKey(x, y, level);
        imageUrl = imageUrl.replace('{quadkey}', quadkey);

        var subdomains = imageryProvider._imageUrlSubdomains;
        var subdomainIndex = (x + y + level) % subdomains.length;
        imageUrl = imageUrl.replace('{subdomain}', subdomains[subdomainIndex]);

        var proxy = imageryProvider._proxy;
        if (defined(proxy)) {
            imageUrl = proxy.getURL(imageUrl);
        }

        return imageUrl;
    }

    var intersectionScratch = new Rectangle();

    function getRectangleAttribution(attributionList, level, rectangle) {
        // Bing levels start at 1, while ours start at 0.
        ++level;

        var result = [];

        for (var attributionIndex = 0, attributionLength = attributionList.length; attributionIndex < attributionLength; ++attributionIndex) {
            var attribution = attributionList[attributionIndex];
            var coverageAreas = attribution.coverageAreas;

            var included = false;

            for (var areaIndex = 0, areaLength = attribution.coverageAreas.length; !included && areaIndex < areaLength; ++areaIndex) {
                var area = coverageAreas[areaIndex];
                if (level >= area.zoomMin && level <= area.zoomMax) {
                    var intersection = Rectangle.intersection(rectangle, area.bbox, intersectionScratch);
                    if (defined(intersection)) {
                        included = true;
                    }
                }
            }

            if (included) {
                result.push(attribution.credit);
            }
        }

        return result;
    }

    return BingMapsImageryProvider;
});

/*global define*/
define('Scene/CullingVolume',[
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Intersect',
        '../Core/Plane'
    ], function(
        Cartesian3,
        Cartesian4,
        defaultValue,
        defined,
        DeveloperError,
        Intersect,
        Plane) {
    'use strict';

    /**
     * The culling volume defined by planes.
     *
     * @alias CullingVolume
     * @constructor
     *
     * @param {Cartesian4[]} [planes] An array of clipping planes.
     */
    function CullingVolume(planes) {
        /**
         * Each plane is represented by a Cartesian4 object, where the x, y, and z components
         * define the unit vector normal to the plane, and the w component is the distance of the
         * plane from the origin.
         * @type {Cartesian4[]}
         * @default []
         */
        this.planes = defaultValue(planes, []);
    }

    var faces = [new Cartesian3(), new Cartesian3(), new Cartesian3()];
    Cartesian3.clone(Cartesian3.UNIT_X, faces[0]);
    Cartesian3.clone(Cartesian3.UNIT_Y, faces[1]);
    Cartesian3.clone(Cartesian3.UNIT_Z, faces[2]);

    var scratchPlaneCenter = new Cartesian3();
    var scratchPlaneNormal = new Cartesian3();
    var scratchPlane = new Plane(new Cartesian3(), 0.0);

    /**
     * Constructs a culling volume from a bounding sphere. Creates six planes that create a box containing the sphere.
     * The planes are aligned to the x, y, and z axes in world coordinates.
     *
     * @param {BoundingSphere} boundingSphere The bounding sphere used to create the culling volume.
     * @param {CullingVolume} [result] The object onto which to store the result.
     * @returns {CullingVolume} The culling volume created from the bounding sphere.
     */
    CullingVolume.fromBoundingSphere = function(boundingSphere, result) {
                if (!defined(boundingSphere)) {
            throw new DeveloperError('boundingSphere is required.');
        }
        
        if (!defined(result)) {
            result = new CullingVolume();
        }

        var length = faces.length;
        var planes = result.planes;
        planes.length = 2 * length;

        var center = boundingSphere.center;
        var radius = boundingSphere.radius;

        var planeIndex = 0;

        for (var i = 0; i < length; ++i) {
            var faceNormal = faces[i];

            var plane0 = planes[planeIndex];
            var plane1 = planes[planeIndex + 1];

            if (!defined(plane0)) {
                plane0 = planes[planeIndex] = new Cartesian4();
            }
            if (!defined(plane1)) {
                plane1 = planes[planeIndex + 1] = new Cartesian4();
            }

            Cartesian3.multiplyByScalar(faceNormal, -radius, scratchPlaneCenter);
            Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter);

            plane0.x = faceNormal.x;
            plane0.y = faceNormal.y;
            plane0.z = faceNormal.z;
            plane0.w = -Cartesian3.dot(faceNormal, scratchPlaneCenter);

            Cartesian3.multiplyByScalar(faceNormal, radius, scratchPlaneCenter);
            Cartesian3.add(center, scratchPlaneCenter, scratchPlaneCenter);

            plane1.x = -faceNormal.x;
            plane1.y = -faceNormal.y;
            plane1.z = -faceNormal.z;
            plane1.w = -Cartesian3.dot(Cartesian3.negate(faceNormal, scratchPlaneNormal), scratchPlaneCenter);

            planeIndex += 2;
        }

        return result;
    };

    /**
     * Determines whether a bounding volume intersects the culling volume.
     *
     * @param {Object} boundingVolume The bounding volume whose intersection with the culling volume is to be tested.
     * @returns {Intersect}  Intersect.OUTSIDE, Intersect.INTERSECTING, or Intersect.INSIDE.
     */
    CullingVolume.prototype.computeVisibility = function(boundingVolume) {
                if (!defined(boundingVolume)) {
            throw new DeveloperError('boundingVolume is required.');
        }
        
        var planes = this.planes;
        var intersecting = false;
        for (var k = 0, len = planes.length; k < len; ++k) {
            var result = boundingVolume.intersectPlane(Plane.fromCartesian4(planes[k], scratchPlane));
            if (result === Intersect.OUTSIDE) {
                return Intersect.OUTSIDE;
            } else if (result === Intersect.INTERSECTING) {
                intersecting = true;
            }
        }

        return intersecting ? Intersect.INTERSECTING : Intersect.INSIDE;
    };

    /**
     * Determines whether a bounding volume intersects the culling volume.
     *
     * @param {Object} boundingVolume The bounding volume whose intersection with the culling volume is to be tested.
     * @param {Number} parentPlaneMask A bit mask from the boundingVolume's parent's check against the same culling
     *                                 volume, such that if (planeMask & (1 << planeIndex) === 0), for k < 31, then
     *                                 the parent (and therefore this) volume is completely inside plane[planeIndex]
     *                                 and that plane check can be skipped.
     * @returns {Number} A plane mask as described above (which can be applied to this boundingVolume's children).
     *
     * @private
     */
    CullingVolume.prototype.computeVisibilityWithPlaneMask = function(boundingVolume, parentPlaneMask) {
                if (!defined(boundingVolume)) {
            throw new DeveloperError('boundingVolume is required.');
        }
        if (!defined(parentPlaneMask)) {
            throw new DeveloperError('parentPlaneMask is required.');
        }
        
        if (parentPlaneMask === CullingVolume.MASK_OUTSIDE || parentPlaneMask === CullingVolume.MASK_INSIDE) {
            // parent is completely outside or completely inside, so this child is as well.
            return parentPlaneMask;
        }

        // Start with MASK_INSIDE (all zeros) so that after the loop, the return value can be compared with MASK_INSIDE.
        // (Because if there are fewer than 31 planes, the upper bits wont be changed.)
        var mask = CullingVolume.MASK_INSIDE;

        var planes = this.planes;
        for (var k = 0, len = planes.length; k < len; ++k) {
            // For k greater than 31 (since 31 is the maximum number of INSIDE/INTERSECTING bits we can store), skip the optimization.
            var flag = (k < 31) ? (1 << k) : 0;
            if (k < 31 && (parentPlaneMask & flag) === 0) {
                // boundingVolume is known to be INSIDE this plane.
                continue;
            }

            var result = boundingVolume.intersectPlane(Plane.fromCartesian4(planes[k], scratchPlane));
            if (result === Intersect.OUTSIDE) {
                return CullingVolume.MASK_OUTSIDE;
            } else if (result === Intersect.INTERSECTING) {
                mask |= flag;
            }
        }

        return mask;
    };

    /**
     * For plane masks (as used in {@link CullingVolume#computeVisibilityWithPlaneMask}), this special value
     * represents the case where the object bounding volume is entirely outside the culling volume.
     *
     * @type {Number}
     * @private
     */
    CullingVolume.MASK_OUTSIDE = 0xffffffff;

    /**
     * For plane masks (as used in {@link CullingVolume.prototype.computeVisibilityWithPlaneMask}), this value
     * represents the case where the object bounding volume is entirely inside the culling volume.
     *
     * @type {Number}
     * @private
     */
    CullingVolume.MASK_INSIDE = 0x00000000;

    /**
     * For plane masks (as used in {@link CullingVolume.prototype.computeVisibilityWithPlaneMask}), this value
     * represents the case where the object bounding volume (may) intersect all planes of the culling volume.
     *
     * @type {Number}
     * @private
     */
    CullingVolume.MASK_INDETERMINATE = 0x7fffffff;

    return CullingVolume;
});

/*global define*/
define('Scene/PerspectiveOffCenterFrustum',[
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Matrix4',
        './CullingVolume'
    ], function(
        Cartesian3,
        Cartesian4,
        defined,
        defineProperties,
        DeveloperError,
        Matrix4,
        CullingVolume) {
    'use strict';

    /**
     * The viewing frustum is defined by 6 planes.
     * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
     * define the unit vector normal to the plane, and the w component is the distance of the
     * plane from the origin/camera position.
     *
     * @alias PerspectiveOffCenterFrustum
     * @constructor
     *
     *
     * @example
     * var frustum = new Cesium.PerspectiveOffCenterFrustum();
     * frustum.right = 1.0;
     * frustum.left = -1.0;
     * frustum.top = 1.0;
     * frustum.bottom = -1.0;
     * frustum.near = 1.0;
     * frustum.far = 2.0;
     *
     * @see PerspectiveFrustum
     */
    function PerspectiveOffCenterFrustum() {
        /**
         * Defines the left clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.left = undefined;
        this._left = undefined;

        /**
         * Defines the right clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.right = undefined;
        this._right = undefined;

        /**
         * Defines the top clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.top = undefined;
        this._top = undefined;

        /**
         * Defines the bottom clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.bottom = undefined;
        this._bottom = undefined;

        /**
         * The distance of the near plane.
         * @type {Number}
         * @default 1.0
         */
        this.near = 1.0;
        this._near = this.near;

        /**
         * The distance of the far plane.
         * @type {Number}
         * @default 500000000.0
         */
        this.far = 500000000.0;
        this._far = this.far;

        this._cullingVolume = new CullingVolume();
        this._perspectiveMatrix = new Matrix4();
        this._infinitePerspective = new Matrix4();
    }

    function update(frustum) {
                if (!defined(frustum.right) || !defined(frustum.left) ||
            !defined(frustum.top) || !defined(frustum.bottom) ||
            !defined(frustum.near) || !defined(frustum.far)) {
            throw new DeveloperError('right, left, top, bottom, near, or far parameters are not set.');
        }
        
        var t = frustum.top;
        var b = frustum.bottom;
        var r = frustum.right;
        var l = frustum.left;
        var n = frustum.near;
        var f = frustum.far;

        if (t !== frustum._top || b !== frustum._bottom ||
            l !== frustum._left || r !== frustum._right ||
            n !== frustum._near || f !== frustum._far) {

                        if (frustum.near <= 0 || frustum.near > frustum.far) {
                throw new DeveloperError('near must be greater than zero and less than far.');
            }
            
            frustum._left = l;
            frustum._right = r;
            frustum._top = t;
            frustum._bottom = b;
            frustum._near = n;
            frustum._far = f;
            frustum._perspectiveMatrix = Matrix4.computePerspectiveOffCenter(l, r, b, t, n, f, frustum._perspectiveMatrix);
            frustum._infinitePerspective = Matrix4.computeInfinitePerspectiveOffCenter(l, r, b, t, n, frustum._infinitePerspective);
        }
    }

    defineProperties(PerspectiveOffCenterFrustum.prototype, {
        /**
         * Gets the perspective projection matrix computed from the view frustum.
         * @memberof PerspectiveOffCenterFrustum.prototype
         * @type {Matrix4}
         * @readonly
         *
         * @see PerspectiveOffCenterFrustum#infiniteProjectionMatrix
         */
        projectionMatrix : {
            get : function() {
                update(this);
                return this._perspectiveMatrix;
            }
        },

        /**
         * Gets the perspective projection matrix computed from the view frustum with an infinite far plane.
         * @memberof PerspectiveOffCenterFrustum.prototype
         * @type {Matrix4}
         * @readonly
         *
         * @see PerspectiveOffCenterFrustum#projectionMatrix
         */
        infiniteProjectionMatrix : {
            get : function() {
                update(this);
                return this._infinitePerspective;
            }
        }
    });

    var getPlanesRight = new Cartesian3();
    var getPlanesNearCenter = new Cartesian3();
    var getPlanesFarCenter = new Cartesian3();
    var getPlanesNormal = new Cartesian3();
    /**
     * Creates a culling volume for this frustum.
     *
     * @param {Cartesian3} position The eye position.
     * @param {Cartesian3} direction The view direction.
     * @param {Cartesian3} up The up direction.
     * @returns {CullingVolume} A culling volume at the given position and orientation.
     *
     * @example
     * // Check if a bounding volume intersects the frustum.
     * var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
     * var intersect = cullingVolume.computeVisibility(boundingVolume);
     */
    PerspectiveOffCenterFrustum.prototype.computeCullingVolume = function(position, direction, up) {
                if (!defined(position)) {
            throw new DeveloperError('position is required.');
        }

        if (!defined(direction)) {
            throw new DeveloperError('direction is required.');
        }

        if (!defined(up)) {
            throw new DeveloperError('up is required.');
        }
        
        var planes = this._cullingVolume.planes;

        var t = this.top;
        var b = this.bottom;
        var r = this.right;
        var l = this.left;
        var n = this.near;
        var f = this.far;

        var right = Cartesian3.cross(direction, up, getPlanesRight);

        var nearCenter = getPlanesNearCenter;
        Cartesian3.multiplyByScalar(direction, n, nearCenter);
        Cartesian3.add(position, nearCenter, nearCenter);

        var farCenter = getPlanesFarCenter;
        Cartesian3.multiplyByScalar(direction, f, farCenter);
        Cartesian3.add(position, farCenter, farCenter);

        var normal = getPlanesNormal;

        //Left plane computation
        Cartesian3.multiplyByScalar(right, l, normal);
        Cartesian3.add(nearCenter, normal, normal);
        Cartesian3.subtract(normal, position, normal);
        Cartesian3.normalize(normal, normal);
        Cartesian3.cross(normal, up, normal);

        var plane = planes[0];
        if (!defined(plane)) {
            plane = planes[0] = new Cartesian4();
        }
        plane.x = normal.x;
        plane.y = normal.y;
        plane.z = normal.z;
        plane.w = -Cartesian3.dot(normal, position);

        //Right plane computation
        Cartesian3.multiplyByScalar(right, r, normal);
        Cartesian3.add(nearCenter, normal, normal);
        Cartesian3.subtract(normal, position, normal);
        Cartesian3.normalize(normal, normal);
        Cartesian3.cross(up, normal, normal);

        plane = planes[1];
        if (!defined(plane)) {
            plane = planes[1] = new Cartesian4();
        }
        plane.x = normal.x;
        plane.y = normal.y;
        plane.z = normal.z;
        plane.w = -Cartesian3.dot(normal, position);

        //Bottom plane computation
        Cartesian3.multiplyByScalar(up, b, normal);
        Cartesian3.add(nearCenter, normal, normal);
        Cartesian3.subtract(normal, position, normal);
        Cartesian3.normalize(normal, normal);
        Cartesian3.cross(right, normal, normal);

        plane = planes[2];
        if (!defined(plane)) {
            plane = planes[2] = new Cartesian4();
        }
        plane.x = normal.x;
        plane.y = normal.y;
        plane.z = normal.z;
        plane.w = -Cartesian3.dot(normal, position);

        //Top plane computation
        Cartesian3.multiplyByScalar(up, t, normal);
        Cartesian3.add(nearCenter, normal, normal);
        Cartesian3.subtract(normal, position, normal);
        Cartesian3.normalize(normal, normal);
        Cartesian3.cross(normal, right, normal);

        plane = planes[3];
        if (!defined(plane)) {
            plane = planes[3] = new Cartesian4();
        }
        plane.x = normal.x;
        plane.y = normal.y;
        plane.z = normal.z;
        plane.w = -Cartesian3.dot(normal, position);

        //Near plane computation
        plane = planes[4];
        if (!defined(plane)) {
            plane = planes[4] = new Cartesian4();
        }
        plane.x = direction.x;
        plane.y = direction.y;
        plane.z = direction.z;
        plane.w = -Cartesian3.dot(direction, nearCenter);

        //Far plane computation
        Cartesian3.negate(direction, normal);

        plane = planes[5];
        if (!defined(plane)) {
            plane = planes[5] = new Cartesian4();
        }
        plane.x = normal.x;
        plane.y = normal.y;
        plane.z = normal.z;
        plane.w = -Cartesian3.dot(normal, farCenter);

        return this._cullingVolume;
    };

    /**
     * Returns the pixel's width and height in meters.
     *
     * @param {Number} drawingBufferWidth The width of the drawing buffer.
     * @param {Number} drawingBufferHeight The height of the drawing buffer.
     * @param {Number} distance The distance to the near plane in meters.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
     *
     * @exception {DeveloperError} drawingBufferWidth must be greater than zero.
     * @exception {DeveloperError} drawingBufferHeight must be greater than zero.
     *
     * @example
     * // Example 1
     * // Get the width and height of a pixel.
     * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 1.0, new Cesium.Cartesian2());
     *
     * @example
     * // Example 2
     * // Get the width and height of a pixel if the near plane was set to 'distance'.
     * // For example, get the size of a pixel of an image on a billboard.
     * var position = camera.position;
     * var direction = camera.direction;
     * var toCenter = Cesium.Cartesian3.subtract(primitive.boundingVolume.center, position, new Cesium.Cartesian3());      // vector from camera to a primitive
     * var toCenterProj = Cesium.Cartesian3.multiplyByScalar(direction, Cesium.Cartesian3.dot(direction, toCenter), new Cesium.Cartesian3()); // project vector onto camera direction vector
     * var distance = Cesium.Cartesian3.magnitude(toCenterProj);
     * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, distance, new Cesium.Cartesian2());
     */
    PerspectiveOffCenterFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) {
        update(this);

                if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) {
            throw new DeveloperError('Both drawingBufferWidth and drawingBufferHeight are required.');
        }
        if (drawingBufferWidth <= 0) {
            throw new DeveloperError('drawingBufferWidth must be greater than zero.');
        }
        if (drawingBufferHeight <= 0) {
            throw new DeveloperError('drawingBufferHeight must be greater than zero.');
        }
        if (!defined(distance)) {
            throw new DeveloperError('distance is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('A result object is required.');
        }
        
        var inverseNear = 1.0 / this.near;
        var tanTheta = this.top * inverseNear;
        var pixelHeight = 2.0 * distance * tanTheta / drawingBufferHeight;
        tanTheta = this.right * inverseNear;
        var pixelWidth = 2.0 * distance * tanTheta / drawingBufferWidth;

        result.x = pixelWidth;
        result.y = pixelHeight;
        return result;
    };

    /**
     * Returns a duplicate of a PerspectiveOffCenterFrustum instance.
     *
     * @param {PerspectiveOffCenterFrustum} [result] The object onto which to store the result.
     * @returns {PerspectiveOffCenterFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
     */
    PerspectiveOffCenterFrustum.prototype.clone = function(result) {
        if (!defined(result)) {
            result = new PerspectiveOffCenterFrustum();
        }

        result.right = this.right;
        result.left = this.left;
        result.top = this.top;
        result.bottom = this.bottom;
        result.near = this.near;
        result.far = this.far;

        // force update of clone to compute matrices
        result._left = undefined;
        result._right = undefined;
        result._top = undefined;
        result._bottom = undefined;
        result._near = undefined;
        result._far = undefined;

        return result;
    };

    /**
     * Compares the provided PerspectiveOffCenterFrustum componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {PerspectiveOffCenterFrustum} [other] The right hand side PerspectiveOffCenterFrustum.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    PerspectiveOffCenterFrustum.prototype.equals = function(other) {
        return (defined(other) &&
                this.right === other.right &&
                this.left === other.left &&
                this.top === other.top &&
                this.bottom === other.bottom &&
                this.near === other.near &&
                this.far === other.far);
    };

    return PerspectiveOffCenterFrustum;
});

/*global define*/
define('Scene/PerspectiveFrustum',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        './PerspectiveOffCenterFrustum'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        PerspectiveOffCenterFrustum) {
    'use strict';

    /**
     * The viewing frustum is defined by 6 planes.
     * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
     * define the unit vector normal to the plane, and the w component is the distance of the
     * plane from the origin/camera position.
     *
     * @alias PerspectiveFrustum
     * @constructor
     *
     *
     * @example
     * var frustum = new Cesium.PerspectiveFrustum();
     * frustum.aspectRatio = canvas.clientWidth / canvas.clientHeight;
     * frustum.fov = Cesium.Math.PI_OVER_THREE;
     * frustum.near = 1.0;
     * frustum.far = 2.0;
     * 
     * @see PerspectiveOffCenterFrustum
     */
    function PerspectiveFrustum() {
        this._offCenterFrustum = new PerspectiveOffCenterFrustum();

        /**
         * The angle of the field of view (FOV), in radians.  This angle will be used
         * as the horizontal FOV if the width is greater than the height, otherwise
         * it will be the vertical FOV.
         * @type {Number}
         * @default undefined
         */
        this.fov = undefined;
        this._fov = undefined;
        this._fovy = undefined;

        this._sseDenominator = undefined;

        /**
         * The aspect ratio of the frustum's width to it's height.
         * @type {Number}
         * @default undefined
         */
        this.aspectRatio = undefined;
        this._aspectRatio = undefined;

        /**
         * The distance of the near plane.
         * @type {Number}
         * @default 1.0
         */
        this.near = 1.0;
        this._near = this.near;

        /**
         * The distance of the far plane.
         * @type {Number}
         * @default 500000000.0
         */
        this.far = 500000000.0;
        this._far = this.far;

        /**
         * Offsets the frustum in the x direction.
         * @type {Number}
         * @default 0.0
         */
        this.xOffset = 0.0;
        this._xOffset = this.xOffset;

        /**
         * Offsets the frustum in the y direction.
         * @type {Number}
         * @default 0.0
         */
        this.yOffset = 0.0;
        this._yOffset = this.yOffset;
    }

    function update(frustum) {
                if (!defined(frustum.fov) || !defined(frustum.aspectRatio) || !defined(frustum.near) || !defined(frustum.far)) {
            throw new DeveloperError('fov, aspectRatio, near, or far parameters are not set.');
        }
        
        var f = frustum._offCenterFrustum;

        if (frustum.fov !== frustum._fov || frustum.aspectRatio !== frustum._aspectRatio ||
                frustum.near !== frustum._near || frustum.far !== frustum._far ||
                frustum.xOffset !== frustum._xOffset || frustum.yOffset !== frustum._yOffset) {
                        if (frustum.fov < 0 || frustum.fov >= Math.PI) {
                throw new DeveloperError('fov must be in the range [0, PI).');
            }

            if (frustum.aspectRatio < 0) {
                throw new DeveloperError('aspectRatio must be positive.');
            }

            if (frustum.near < 0 || frustum.near > frustum.far) {
                throw new DeveloperError('near must be greater than zero and less than far.');
            }
            
            frustum._aspectRatio = frustum.aspectRatio;
            frustum._fov = frustum.fov;
            frustum._fovy = (frustum.aspectRatio <= 1) ? frustum.fov : Math.atan(Math.tan(frustum.fov * 0.5) / frustum.aspectRatio) * 2.0;
            frustum._near = frustum.near;
            frustum._far = frustum.far;
            frustum._sseDenominator = 2.0 * Math.tan(0.5 * frustum._fovy);
            frustum._xOffset = frustum.xOffset;
            frustum._yOffset = frustum.yOffset;

            f.top = frustum.near * Math.tan(0.5 * frustum._fovy);
            f.bottom = -f.top;
            f.right = frustum.aspectRatio * f.top;
            f.left = -f.right;
            f.near = frustum.near;
            f.far = frustum.far;

            f.right += frustum.xOffset;
            f.left += frustum.xOffset;
            f.top += frustum.yOffset;
            f.bottom += frustum.yOffset;
        }
    }

    defineProperties(PerspectiveFrustum.prototype, {
        /**
         * Gets the perspective projection matrix computed from the view frustum.
         * @memberof PerspectiveFrustum.prototype
         * @type {Matrix4}
         * @readonly
         *
         * @see PerspectiveFrustum#infiniteProjectionMatrix
         */
        projectionMatrix : {
            get : function() {
                update(this);
                return this._offCenterFrustum.projectionMatrix;
            }
        },

        /**
         * The perspective projection matrix computed from the view frustum with an infinite far plane.
         * @memberof PerspectiveFrustum.prototype
         * @type {Matrix4}
         * @readonly
         *
         * @see PerspectiveFrustum#projectionMatrix
         */
        infiniteProjectionMatrix : {
            get : function() {
                update(this);
                return this._offCenterFrustum.infiniteProjectionMatrix;
            }
        },

        /**
         * Gets the angle of the vertical field of view, in radians.
         * @memberof PerspectiveFrustum.prototype
         * @type {Number}
         * @readonly
         * @default undefined
         */
        fovy : {
            get : function() {
                update(this);
                return this._fovy;
            }
        },

        /**
         * @readonly
         * @private
         */
        sseDenominator : {
            get : function () {
                update(this);
                return this._sseDenominator;
            }
        }
    });

    /**
     * Creates a culling volume for this frustum.
     *
     * @param {Cartesian3} position The eye position.
     * @param {Cartesian3} direction The view direction.
     * @param {Cartesian3} up The up direction.
     * @returns {CullingVolume} A culling volume at the given position and orientation.
     *
     * @example
     * // Check if a bounding volume intersects the frustum.
     * var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
     * var intersect = cullingVolume.computeVisibility(boundingVolume);
     */
    PerspectiveFrustum.prototype.computeCullingVolume = function(position, direction, up) {
        update(this);
        return this._offCenterFrustum.computeCullingVolume(position, direction, up);
    };

    /**
     * Returns the pixel's width and height in meters.
     *
     * @param {Number} drawingBufferWidth The width of the drawing buffer.
     * @param {Number} drawingBufferHeight The height of the drawing buffer.
     * @param {Number} distance The distance to the near plane in meters.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
     *
     * @exception {DeveloperError} drawingBufferWidth must be greater than zero.
     * @exception {DeveloperError} drawingBufferHeight must be greater than zero.
     *
     * @example
     * // Example 1
     * // Get the width and height of a pixel.
     * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 1.0, new Cesium.Cartesian2());
     *
     * @example
     * // Example 2
     * // Get the width and height of a pixel if the near plane was set to 'distance'.
     * // For example, get the size of a pixel of an image on a billboard.
     * var position = camera.position;
     * var direction = camera.direction;
     * var toCenter = Cesium.Cartesian3.subtract(primitive.boundingVolume.center, position, new Cesium.Cartesian3());      // vector from camera to a primitive
     * var toCenterProj = Cesium.Cartesian3.multiplyByScalar(direction, Cesium.Cartesian3.dot(direction, toCenter), new Cesium.Cartesian3()); // project vector onto camera direction vector
     * var distance = Cesium.Cartesian3.magnitude(toCenterProj);
     * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, distance, new Cesium.Cartesian2());
     */
    PerspectiveFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) {
        update(this);
        return this._offCenterFrustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, result);
    };

    /**
     * Returns a duplicate of a PerspectiveFrustum instance.
     *
     * @param {PerspectiveFrustum} [result] The object onto which to store the result.
     * @returns {PerspectiveFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
     */
    PerspectiveFrustum.prototype.clone = function(result) {
        if (!defined(result)) {
            result = new PerspectiveFrustum();
        }

        result.aspectRatio = this.aspectRatio;
        result.fov = this.fov;
        result.near = this.near;
        result.far = this.far;

        // force update of clone to compute matrices
        result._aspectRatio = undefined;
        result._fov = undefined;
        result._near = undefined;
        result._far = undefined;

        this._offCenterFrustum.clone(result._offCenterFrustum);

        return result;
    };

    /**
     * Compares the provided PerspectiveFrustum componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {PerspectiveFrustum} [other] The right hand side PerspectiveFrustum.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    PerspectiveFrustum.prototype.equals = function(other) {
        if (!defined(other)) {
            return false;
        }

        update(this);
        update(other);

        return (this.fov === other.fov &&
                this.aspectRatio === other.aspectRatio &&
                this.near === other.near &&
                this.far === other.far &&
                this._offCenterFrustum.equals(other._offCenterFrustum));
    };

    return PerspectiveFrustum;
});

/*global define*/
define('Scene/CameraFlightPath',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/EasingFunction',
        '../Core/Math',
        './PerspectiveFrustum',
        './PerspectiveOffCenterFrustum',
        './SceneMode'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        EasingFunction,
        CesiumMath,
        PerspectiveFrustum,
        PerspectiveOffCenterFrustum,
        SceneMode) {
    'use strict';

    /**
     * Creates tweens for camera flights.
     * <br /><br />
     * Mouse interaction is disabled during flights.
     *
     * @private
     */
    var CameraFlightPath = {
    };

    function getAltitude(frustum, dx, dy) {
        var near;
        var top;
        var right;
        if (frustum instanceof PerspectiveFrustum) {
            var tanTheta = Math.tan(0.5 * frustum.fovy);
            near = frustum.near;
            top = frustum.near * tanTheta;
            right = frustum.aspectRatio * top;
            return Math.max(dx * near / right, dy * near / top);
        } else if (frustum instanceof PerspectiveOffCenterFrustum) {
            near = frustum.near;
            top = frustum.top;
            right = frustum.right;
            return Math.max(dx * near / right, dy * near / top);
        }

        return Math.max(dx, dy);
    }

    var scratchCart = new Cartesian3();
    var scratchCart2 = new Cartesian3();

    function createHeightFunction(camera, destination, startHeight, endHeight, optionAltitude) {
        var altitude = optionAltitude;
        var maxHeight = Math.max(startHeight, endHeight);

        if (!defined(altitude)) {
            var start = camera.position;
            var end = destination;
            var up = camera.up;
            var right = camera.right;
            var frustum = camera.frustum;

            var diff = Cartesian3.subtract(start, end, scratchCart);
            var verticalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(up, Cartesian3.dot(diff, up), scratchCart2));
            var horizontalDistance = Cartesian3.magnitude(Cartesian3.multiplyByScalar(right, Cartesian3.dot(diff, right), scratchCart2));

            altitude = Math.min(getAltitude(frustum, verticalDistance, horizontalDistance) * 0.20, 1000000000.0);
        }

        if (maxHeight < altitude) {
            var power = 8.0;
            var factor = 1000000.0;

            var s = -Math.pow((altitude - startHeight) * factor, 1.0 / power);
            var e = Math.pow((altitude - endHeight) * factor, 1.0 / power);

            return function(t) {
                var x = t * (e - s) + s;
                return -Math.pow(x, power) / factor + altitude;
            };
        }

        return function(t) {
            return CesiumMath.lerp(startHeight, endHeight, t);
        };
    }

    function adjustAngleForLERP(startAngle, endAngle) {
        if (CesiumMath.equalsEpsilon(startAngle, CesiumMath.TWO_PI, CesiumMath.EPSILON11)) {
            startAngle = 0.0;
        }

        if (endAngle > startAngle + Math.PI) {
            startAngle += CesiumMath.TWO_PI;
        } else if (endAngle < startAngle - Math.PI) {
            startAngle -= CesiumMath.TWO_PI;
        }

        return startAngle;
    }

    var scratchStart = new Cartesian3();

    function createUpdateCV(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;

        var start = Cartesian3.clone(camera.position, scratchStart);
        var startPitch = camera.pitch;
        var startHeading = adjustAngleForLERP(camera.heading, heading);
        var startRoll = adjustAngleForLERP(camera.roll, roll);

        var heightFunction = createHeightFunction(camera, destination, start.z, destination.z, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            camera.setView({
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time),
                    pitch : CesiumMath.lerp(startPitch, pitch, time),
                    roll : CesiumMath.lerp(startRoll, roll, time)
                }
            });

            Cartesian2.lerp(start, destination, time, camera.position);
            camera.position.z = heightFunction(time);
        }
        return update;
    }

    var scratchStartCart = new Cartographic();
    var scratchEndCart = new Cartographic();

    function createUpdate3D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;
        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;

        var startCart = Cartographic.clone(camera.positionCartographic, scratchStartCart);
        var startPitch = camera.pitch;
        var startHeading = adjustAngleForLERP(camera.heading, heading);
        var startRoll = adjustAngleForLERP(camera.roll, roll);

        var destCart = ellipsoid.cartesianToCartographic(destination, scratchEndCart);
        startCart.longitude = CesiumMath.zeroToTwoPi(startCart.longitude);
        destCart.longitude = CesiumMath.zeroToTwoPi(destCart.longitude);

        var diff = startCart.longitude - destCart.longitude;
        if (diff < -CesiumMath.PI) {
            startCart.longitude += CesiumMath.TWO_PI;
        } else if (diff > CesiumMath.PI) {
            destCart.longitude += CesiumMath.TWO_PI;
        }

        var heightFunction = createHeightFunction(camera, destination, startCart.height, destCart.height, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            var position = Cartesian3.fromRadians(
                CesiumMath.lerp(startCart.longitude, destCart.longitude, time),
                CesiumMath.lerp(startCart.latitude, destCart.latitude, time),
                heightFunction(time)
            );

            camera.setView({
                destination : position,
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time),
                    pitch : CesiumMath.lerp(startPitch, pitch, time),
                    roll : CesiumMath.lerp(startRoll, roll, time)
                }
            });
        }
        return update;
    }

    function createUpdate2D(scene, duration, destination, heading, pitch, roll, optionAltitude) {
        var camera = scene.camera;

        var start = Cartesian3.clone(camera.position, scratchStart);
        var startHeading = adjustAngleForLERP(camera.heading, heading);

        var startHeight = camera.frustum.right - camera.frustum.left;
        var heightFunction = createHeightFunction(camera, destination, startHeight, destination.z, optionAltitude);

        function update(value) {
            var time = value.time / duration;

            camera.setView({
                orientation: {
                    heading : CesiumMath.lerp(startHeading, heading, time)
                }
            });

            Cartesian2.lerp(start, destination, time, camera.position);

            var zoom = heightFunction(time);

            var frustum = camera.frustum;
            var ratio = frustum.top / frustum.right;

            var incrementAmount = (zoom - (frustum.right - frustum.left)) * 0.5;
            frustum.right += incrementAmount;
            frustum.left -= incrementAmount;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;
        }
        return update;
    }

    var scratchCartographic = new Cartographic();
    var scratchDestination = new Cartesian3();

    function emptyFlight(complete, cancel) {
        return {
            startObject : {},
            stopObject : {},
            duration : 0.0,
            complete : complete,
            cancel : cancel
        };
    }

    function wrapCallback(controller, cb) {
        function wrapped() {
            if (typeof cb === 'function') {
                cb();
            }

            controller.enableInputs = true;
        }
        return wrapped;
    }

    CameraFlightPath.createTween = function(scene, options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var destination = options.destination;

                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        if (!defined(destination)) {
            throw new DeveloperError('destination is required.');
        }
                var mode = scene.mode;

        if (mode === SceneMode.MORPHING) {
            return emptyFlight();
        }

        var convert = defaultValue(options.convert, true);
        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;
        var maximumHeight = options.maximumHeight;
        var easingFunction = options.easingFunction;

        if (convert && mode !== SceneMode.SCENE3D) {
            ellipsoid.cartesianToCartographic(destination, scratchCartographic);
            destination = projection.project(scratchCartographic, scratchDestination);
        }

        var camera = scene.camera;
        var transform = options.endTransform;
        if (defined(transform)) {
            camera._setTransform(transform);
        }

        var duration = options.duration;
        if (!defined(duration)) {
            duration = Math.ceil(Cartesian3.distance(camera.position, destination) / 1000000.0) + 2.0;
            duration = Math.min(duration, 3.0);
        }

        var heading = defaultValue(options.heading, 0.0);
        var pitch = defaultValue(options.pitch, -CesiumMath.PI_OVER_TWO);
        var roll = defaultValue(options.roll, 0.0);

        var controller = scene.screenSpaceCameraController;
        controller.enableInputs = false;

        var complete = wrapCallback(controller, options.complete);
        var cancel = wrapCallback(controller, options.cancel);

        var frustum = camera.frustum;

        var empty = scene.mode === SceneMode.SCENE2D;
        empty = empty && Cartesian2.equalsEpsilon(camera.position, destination, CesiumMath.EPSILON6);
        empty = empty && CesiumMath.equalsEpsilon(Math.max(frustum.right - frustum.left, frustum.top - frustum.bottom), destination.z, CesiumMath.EPSILON6);

        empty = empty || (scene.mode !== SceneMode.SCENE2D &&
            Cartesian3.equalsEpsilon(destination, camera.position, CesiumMath.EPSILON10));

        empty = empty &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(heading), CesiumMath.negativePiToPi(camera.heading), CesiumMath.EPSILON10) &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(pitch), CesiumMath.negativePiToPi(camera.pitch), CesiumMath.EPSILON10) &&
            CesiumMath.equalsEpsilon(CesiumMath.negativePiToPi(roll), CesiumMath.negativePiToPi(camera.roll), CesiumMath.EPSILON10);

        if (empty) {
            return emptyFlight(complete, cancel);
        }

        var updateFunctions = new Array(4);
        updateFunctions[SceneMode.SCENE2D] = createUpdate2D;
        updateFunctions[SceneMode.SCENE3D] = createUpdate3D;
        updateFunctions[SceneMode.COLUMBUS_VIEW] = createUpdateCV;

        if (duration <= 0.0) {
            var newOnComplete = function() {
                var update = updateFunctions[mode](scene, 1.0, destination, heading, pitch, roll, maximumHeight);
                update({ time: 1.0 });

                if (typeof complete === 'function') {
                    complete();
                }
            };
            return emptyFlight(newOnComplete, cancel);
        }

        var update = updateFunctions[mode](scene, duration, destination, heading, pitch, roll, maximumHeight);

        if (!defined(easingFunction)) {
            var startHeight = camera.positionCartographic.height;
            var endHeight = mode === SceneMode.SCENE3D ? ellipsoid.cartesianToCartographic(destination).height : destination.z;

            if (startHeight > endHeight && startHeight > 11500.0) {
                easingFunction = EasingFunction.CUBIC_OUT;
            } else {
                easingFunction = EasingFunction.QUINTIC_IN_OUT;
            }
        }

        return {
            duration : duration,
            easingFunction : easingFunction,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : duration
            },
            update : update,
            complete : complete,
            cancel: cancel
        };
    };

    return CameraFlightPath;
});

/*global define*/
define('Scene/MapMode2D',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Describes how the map will operate in 2D.
     *
     * @exports MapMode2D
     */
    var MapMode2D = {
        /**
         * The 2D map can be rotated about the z axis.
         *
         * @type {Number}
         * @constant
         */
        ROTATE : 0,

        /**
         * The 2D map can be scrolled infinitely in the horizontal direction.
         *
         * @type {Number}
         * @constant
         */
        INFINITE_SCROLL : 1
    };

    return freezeObject(MapMode2D);
});

/*global define*/
define('Scene/Camera',[
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/EasingFunction',
        '../Core/Ellipsoid',
        '../Core/EllipsoidGeodesic',
        '../Core/Event',
        '../Core/HeadingPitchRange',
        '../Core/HeadingPitchRoll',
        '../Core/Intersect',
        '../Core/IntersectionTests',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Quaternion',
        '../Core/Ray',
        '../Core/Rectangle',
        '../Core/Transforms',
        './CameraFlightPath',
        './MapMode2D',
        './PerspectiveFrustum',
        './SceneMode'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        EasingFunction,
        Ellipsoid,
        EllipsoidGeodesic,
        Event,
        HeadingPitchRange,
        HeadingPitchRoll,
        Intersect,
        IntersectionTests,
        CesiumMath,
        Matrix3,
        Matrix4,
        Quaternion,
        Ray,
        Rectangle,
        Transforms,
        CameraFlightPath,
        MapMode2D,
        PerspectiveFrustum,
        SceneMode) {
    'use strict';

    /**
     * The camera is defined by a position, orientation, and view frustum.
     * <br /><br />
     * The orientation forms an orthonormal basis with a view, up and right = view x up unit vectors.
     * <br /><br />
     * The viewing frustum is defined by 6 planes.
     * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
     * define the unit vector normal to the plane, and the w component is the distance of the
     * plane from the origin/camera position.
     *
     * @alias Camera
     *
     * @constructor
     *
     * @param {Scene} scene The scene.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Camera.html|Cesium Sandcastle Camera Demo}
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Camera%20Tutorial.html">Sandcastle Example</a> from the <a href="http://cesiumjs.org/2013/02/13/Cesium-Camera-Tutorial/|Camera Tutorial}
     *
     * @example
     * // Create a camera looking down the negative z-axis, positioned at the origin,
     * // with a field of view of 60 degrees, and 1:1 aspect ratio.
     * var camera = new Cesium.Camera(scene);
     * camera.position = new Cesium.Cartesian3();
     * camera.direction = Cesium.Cartesian3.negate(Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3());
     * camera.up = Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_Y);
     * camera.frustum.fov = Cesium.Math.PI_OVER_THREE;
     * camera.frustum.near = 1.0;
     * camera.frustum.far = 2.0;
     */
    function Camera(scene) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
                this._scene = scene;

        this._transform = Matrix4.clone(Matrix4.IDENTITY);
        this._invTransform = Matrix4.clone(Matrix4.IDENTITY);
        this._actualTransform = Matrix4.clone(Matrix4.IDENTITY);
        this._actualInvTransform = Matrix4.clone(Matrix4.IDENTITY);
        this._transformChanged = false;

        /**
         * The position of the camera.
         *
         * @type {Cartesian3}
         */
        this.position = new Cartesian3();
        this._position = new Cartesian3();
        this._positionWC = new Cartesian3();
        this._positionCartographic = new Cartographic();

        /**
         * The view direction of the camera.
         *
         * @type {Cartesian3}
         */
        this.direction = new Cartesian3();
        this._direction = new Cartesian3();
        this._directionWC = new Cartesian3();

        /**
         * The up direction of the camera.
         *
         * @type {Cartesian3}
         */
        this.up = new Cartesian3();
        this._up = new Cartesian3();
        this._upWC = new Cartesian3();

        /**
         * The right direction of the camera.
         *
         * @type {Cartesian3}
         */
        this.right = new Cartesian3();
        this._right = new Cartesian3();
        this._rightWC = new Cartesian3();

        /**
         * The region of space in view.
         *
         * @type {Frustum}
         * @default PerspectiveFrustum()
         *
         * @see PerspectiveFrustum
         * @see PerspectiveOffCenterFrustum
         * @see OrthographicFrustum
         */
        this.frustum = new PerspectiveFrustum();
        this.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
        this.frustum.fov = CesiumMath.toRadians(60.0);

        /**
         * The default amount to move the camera when an argument is not
         * provided to the move methods.
         * @type {Number}
         * @default 100000.0;
         */
        this.defaultMoveAmount = 100000.0;
        /**
         * The default amount to rotate the camera when an argument is not
         * provided to the look methods.
         * @type {Number}
         * @default Math.PI / 60.0
         */
        this.defaultLookAmount = Math.PI / 60.0;
        /**
         * The default amount to rotate the camera when an argument is not
         * provided to the rotate methods.
         * @type {Number}
         * @default Math.PI / 3600.0
         */
        this.defaultRotateAmount = Math.PI / 3600.0;
        /**
         * The default amount to move the camera when an argument is not
         * provided to the zoom methods.
         * @type {Number}
         * @default 100000.0;
         */
        this.defaultZoomAmount = 100000.0;
        /**
         * If set, the camera will not be able to rotate past this axis in either direction.
         * @type {Cartesian3}
         * @default undefined
         */
        this.constrainedAxis = undefined;
        /**
         * The factor multiplied by the the map size used to determine where to clamp the camera position
         * when zooming out from the surface. The default is 1.5. Only valid for 2D and the map is rotatable.
         * @type {Number}
         * @default 1.5
         */
        this.maximumZoomFactor = 1.5;

        this._moveStart = new Event();
        this._moveEnd = new Event();

        this._changed = new Event();
        this._changedPosition = undefined;
        this._changedDirection = undefined;
        this._changedFrustum = undefined;

        /**
         * The amount the camera has to change before the <code>changed</code> event is raised. The value is a percentage in the [0, 1] range.
         * @type {number}
         * @default 0.5
         */
        this.percentageChanged = 0.5;

        this._viewMatrix = new Matrix4();
        this._invViewMatrix = new Matrix4();
        updateViewMatrix(this);

        this._mode = SceneMode.SCENE3D;
        this._modeChanged = true;
        var projection = scene.mapProjection;
        this._projection = projection;
        this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO));
        this._max2Dfrustum = undefined;
        this._suspendTerrainAdjustment = false;

        // set default view
        rectangleCameraPosition3D(this, Camera.DEFAULT_VIEW_RECTANGLE, this.position, true);

        var mag = Cartesian3.magnitude(this.position);
        mag += mag * Camera.DEFAULT_VIEW_FACTOR;
        Cartesian3.normalize(this.position, this.position);
        Cartesian3.multiplyByScalar(this.position, mag, this.position);
    }

    /**
     * @private
     */
    Camera.TRANSFORM_2D = new Matrix4(
        0.0, 0.0, 1.0, 0.0,
        1.0, 0.0, 0.0, 0.0,
        0.0, 1.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 1.0);

    /**
     * @private
     */
    Camera.TRANSFORM_2D_INVERSE = Matrix4.inverseTransformation(Camera.TRANSFORM_2D, new Matrix4());

    /**
     * The default rectangle the camera will view on creation.
     * @type Rectangle
     */
    Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees(-95.0, -20.0, -70.0, 90.0);

    /**
     * A scalar to multiply to the camera position and add it back after setting the camera to view the rectangle.
     * A value of zero means the camera will view the entire {@link Camera#DEFAULT_VIEW_RECTANGLE}, a value greater than zero
     * will move it further away from the extent, and a value less than zero will move it close to the extent.
     * @type Number
     */
    Camera.DEFAULT_VIEW_FACTOR = 0.5;

    function updateViewMatrix(camera) {
        Matrix4.computeView(camera._position, camera._direction, camera._up, camera._right, camera._viewMatrix);
        Matrix4.multiply(camera._viewMatrix, camera._actualInvTransform, camera._viewMatrix);
        Matrix4.inverseTransformation(camera._viewMatrix, camera._invViewMatrix);
    }

    Camera.prototype._updateCameraChanged = function() {
        var camera = this;

        if (camera._changed.numberOfListeners === 0) {
            return;
        }

        var percentageChanged = camera.percentageChanged;

        if (camera._mode === SceneMode.SCENE2D) {
            if (!defined(camera._changedFrustum)) {
                camera._changedPosition = Cartesian3.clone(camera.position, camera._changedPosition);
                camera._changedFrustum = camera.frustum.clone();
                return;
            }

            var position = camera.position;
            var lastPosition = camera._changedPosition;

            var frustum = camera.frustum;
            var lastFrustum = camera._changedFrustum;

            var x0 = position.x + frustum.left;
            var x1 = position.x + frustum.right;
            var x2 = lastPosition.x + lastFrustum.left;
            var x3 = lastPosition.x + lastFrustum.right;

            var y0 = position.y + frustum.bottom;
            var y1 = position.y + frustum.top;
            var y2 = lastPosition.y + lastFrustum.bottom;
            var y3 = lastPosition.y + lastFrustum.top;

            var leftX = Math.max(x0, x2);
            var rightX = Math.min(x1, x3);
            var bottomY = Math.max(y0, y2);
            var topY = Math.min(y1, y3);

            var areaPercentage;
            if (leftX >= rightX || bottomY >= y1) {
                areaPercentage = 1.0;
            } else {
                var areaRef = lastFrustum;
                if (x0 < x2 && x1 > x3 && y0 < y2 && y1 > y3) {
                    areaRef = frustum;
                }
                areaPercentage = 1.0 - ((rightX - leftX) * (topY - bottomY)) / ((areaRef.right - areaRef.left) * (areaRef.top - areaRef.bottom));
            }

            if (areaPercentage > percentageChanged) {
                camera._changed.raiseEvent(areaPercentage);
                camera._changedPosition = Cartesian3.clone(camera.position, camera._changedPosition);
                camera._changedFrustum = camera.frustum.clone(camera._changedFrustum);
            }
            return;
        }

        if (!defined(camera._changedDirection)) {
            camera._changedPosition = Cartesian3.clone(camera.positionWC, camera._changedPosition);
            camera._changedDirection = Cartesian3.clone(camera.directionWC, camera._changedDirection);
            return;
        }

        var dirAngle = CesiumMath.acosClamped(Cartesian3.dot(camera.directionWC, camera._changedDirection));
        var dirPercentage = dirAngle / (camera.frustum.fovy * 0.5);

        var distance = Cartesian3.distance(camera.positionWC, camera._changedPosition);
        var heightPercentage = distance / camera.positionCartographic.height;

        if (dirPercentage > percentageChanged || heightPercentage > percentageChanged) {
            camera._changed.raiseEvent(Math.max(dirPercentage, heightPercentage));
            camera._changedPosition = Cartesian3.clone(camera.positionWC, camera._changedPosition);
            camera._changedDirection = Cartesian3.clone(camera.directionWC, camera._changedDirection);
        }
    };

    var scratchAdjustHeightTransform = new Matrix4();
    var scratchAdjustHeightCartographic = new Cartographic();

    Camera.prototype._adjustHeightForTerrain = function() {
        var scene = this._scene;

        var screenSpaceCameraController = scene.screenSpaceCameraController;
        var enableCollisionDetection = screenSpaceCameraController.enableCollisionDetection;
        var minimumCollisionTerrainHeight = screenSpaceCameraController.minimumCollisionTerrainHeight;
        var minimumZoomDistance = screenSpaceCameraController.minimumZoomDistance;

        if (this._suspendTerrainAdjustment || !enableCollisionDetection) {
            return;
        }

        var mode = this._mode;
        var globe = scene.globe;

        if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
            return;
        }

        var ellipsoid = globe.ellipsoid;
        var projection = scene.mapProjection;

        var transform;
        var mag;
        if (!Matrix4.equals(this.transform, Matrix4.IDENTITY)) {
            transform = Matrix4.clone(this.transform, scratchAdjustHeightTransform);
            mag = Cartesian3.magnitude(this.position);
            this._setTransform(Matrix4.IDENTITY);
        }

        var cartographic = scratchAdjustHeightCartographic;
        if (mode === SceneMode.SCENE3D) {
            ellipsoid.cartesianToCartographic(this.position, cartographic);
        } else {
            projection.unproject(this.position, cartographic);
        }

        var heightUpdated = false;
        if (cartographic.height < minimumCollisionTerrainHeight) {
            var height = globe.getHeight(cartographic);
            if (defined(height)) {
                height += minimumZoomDistance;
                if (cartographic.height < height) {
                    cartographic.height = height;
                    if (mode === SceneMode.SCENE3D) {
                        ellipsoid.cartographicToCartesian(cartographic, this.position);
                    } else {
                        projection.project(cartographic, this.position);
                    }
                    heightUpdated = true;
                }
            }
        }

        if (defined(transform)) {
            this._setTransform(transform);
            if (heightUpdated) {
                Cartesian3.normalize(this.position, this.position);
                Cartesian3.negate(this.position, this.direction);
                Cartesian3.multiplyByScalar(this.position, Math.max(mag, minimumZoomDistance), this.position);
                Cartesian3.normalize(this.direction, this.direction);
                Cartesian3.cross(this.direction, this.up, this.right);
                Cartesian3.cross(this.right, this.direction, this.up);
            }
        }
    };

    function convertTransformForColumbusView(camera) {
        Transforms.basisTo2D(camera._projection, camera._transform, camera._actualTransform);
    }

    var scratchCartographic = new Cartographic();
    var scratchCartesian3Projection = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchCartesian4Origin = new Cartesian4();
    var scratchCartesian4NewOrigin = new Cartesian4();
    var scratchCartesian4NewXAxis = new Cartesian4();
    var scratchCartesian4NewYAxis = new Cartesian4();
    var scratchCartesian4NewZAxis = new Cartesian4();

    function convertTransformFor2D(camera) {
        var projection = camera._projection;
        var ellipsoid = projection.ellipsoid;

        var origin = Matrix4.getColumn(camera._transform, 3, scratchCartesian4Origin);
        var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic);

        var projectedPosition = projection.project(cartographic, scratchCartesian3Projection);
        var newOrigin = scratchCartesian4NewOrigin;
        newOrigin.x = projectedPosition.z;
        newOrigin.y = projectedPosition.x;
        newOrigin.z = projectedPosition.y;
        newOrigin.w = 1.0;

        var newZAxis = Cartesian4.clone(Cartesian4.UNIT_X, scratchCartesian4NewZAxis);

        var xAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 0, scratchCartesian3), origin, scratchCartesian3);
        ellipsoid.cartesianToCartographic(xAxis, cartographic);

        projection.project(cartographic, projectedPosition);
        var newXAxis = scratchCartesian4NewXAxis;
        newXAxis.x = projectedPosition.z;
        newXAxis.y = projectedPosition.x;
        newXAxis.z = projectedPosition.y;
        newXAxis.w = 0.0;

        Cartesian3.subtract(newXAxis, newOrigin, newXAxis);
        newXAxis.x = 0.0;

        var newYAxis = scratchCartesian4NewYAxis;
        if (Cartesian3.magnitudeSquared(newXAxis) > CesiumMath.EPSILON10) {
            Cartesian3.cross(newZAxis, newXAxis, newYAxis);
        } else {
            var yAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 1, scratchCartesian3), origin, scratchCartesian3);
            ellipsoid.cartesianToCartographic(yAxis, cartographic);

            projection.project(cartographic, projectedPosition);
            newYAxis.x = projectedPosition.z;
            newYAxis.y = projectedPosition.x;
            newYAxis.z = projectedPosition.y;
            newYAxis.w = 0.0;

            Cartesian3.subtract(newYAxis, newOrigin, newYAxis);
            newYAxis.x = 0.0;

            if (Cartesian3.magnitudeSquared(newYAxis) < CesiumMath.EPSILON10) {
                Cartesian4.clone(Cartesian4.UNIT_Y, newXAxis);
                Cartesian4.clone(Cartesian4.UNIT_Z, newYAxis);
            }
        }

        Cartesian3.cross(newYAxis, newZAxis, newXAxis);
        Cartesian3.normalize(newXAxis, newXAxis);
        Cartesian3.cross(newZAxis, newXAxis, newYAxis);
        Cartesian3.normalize(newYAxis, newYAxis);

        Matrix4.setColumn(camera._actualTransform, 0, newXAxis, camera._actualTransform);
        Matrix4.setColumn(camera._actualTransform, 1, newYAxis, camera._actualTransform);
        Matrix4.setColumn(camera._actualTransform, 2, newZAxis, camera._actualTransform);
        Matrix4.setColumn(camera._actualTransform, 3, newOrigin, camera._actualTransform);
    }

    var scratchCartesian = new Cartesian3();

    function updateMembers(camera) {
        var mode = camera._mode;

        var heightChanged = false;
        var height = 0.0;
        if (mode === SceneMode.SCENE2D) {
            height = camera.frustum.right - camera.frustum.left;
            heightChanged = height !== camera._positionCartographic.height;
        }

        var position = camera._position;
        var positionChanged = !Cartesian3.equals(position, camera.position) || heightChanged;
        if (positionChanged) {
            position = Cartesian3.clone(camera.position, camera._position);
        }

        var direction = camera._direction;
        var directionChanged = !Cartesian3.equals(direction, camera.direction);
        if (directionChanged) {
            Cartesian3.normalize(camera.direction, camera.direction);
            direction = Cartesian3.clone(camera.direction, camera._direction);
        }

        var up = camera._up;
        var upChanged = !Cartesian3.equals(up, camera.up);
        if (upChanged) {
            Cartesian3.normalize(camera.up, camera.up);
            up = Cartesian3.clone(camera.up, camera._up);
        }

        var right = camera._right;
        var rightChanged = !Cartesian3.equals(right, camera.right);
        if (rightChanged) {
            Cartesian3.normalize(camera.right, camera.right);
            right = Cartesian3.clone(camera.right, camera._right);
        }

        var transformChanged = camera._transformChanged || camera._modeChanged;
        camera._transformChanged = false;

        if (transformChanged) {
            Matrix4.inverseTransformation(camera._transform, camera._invTransform);

            if (camera._mode === SceneMode.COLUMBUS_VIEW || camera._mode === SceneMode.SCENE2D) {
                if (Matrix4.equals(Matrix4.IDENTITY, camera._transform)) {
                    Matrix4.clone(Camera.TRANSFORM_2D, camera._actualTransform);
                } else if (camera._mode === SceneMode.COLUMBUS_VIEW) {
                    convertTransformForColumbusView(camera);
                } else {
                    convertTransformFor2D(camera);
                }
            } else {
                Matrix4.clone(camera._transform, camera._actualTransform);
            }

            Matrix4.inverseTransformation(camera._actualTransform, camera._actualInvTransform);

            camera._modeChanged = false;
        }

        var transform = camera._actualTransform;

        if (positionChanged || transformChanged) {
            camera._positionWC = Matrix4.multiplyByPoint(transform, position, camera._positionWC);

            // Compute the Cartographic position of the camera.
            if (mode === SceneMode.SCENE3D || mode === SceneMode.MORPHING) {
                camera._positionCartographic = camera._projection.ellipsoid.cartesianToCartographic(camera._positionWC, camera._positionCartographic);
            } else {
                // The camera position is expressed in the 2D coordinate system where the Y axis is to the East,
                // the Z axis is to the North, and the X axis is out of the map.  Express them instead in the ENU axes where
                // X is to the East, Y is to the North, and Z is out of the local horizontal plane.
                var positionENU = scratchCartesian;
                positionENU.x = camera._positionWC.y;
                positionENU.y = camera._positionWC.z;
                positionENU.z = camera._positionWC.x;

                // In 2D, the camera height is always 12.7 million meters.
                // The apparent height is equal to half the frustum width.
                if (mode === SceneMode.SCENE2D) {
                    positionENU.z = height;
                }

                camera._projection.unproject(positionENU, camera._positionCartographic);
            }
        }

        if (directionChanged || upChanged || rightChanged) {
            var det = Cartesian3.dot(direction, Cartesian3.cross(up, right, scratchCartesian));
            if (Math.abs(1.0 - det) > CesiumMath.EPSILON2) {
                //orthonormalize axes
                var invUpMag = 1.0 / Cartesian3.magnitudeSquared(up);
                var scalar = Cartesian3.dot(up, direction) * invUpMag;
                var w0 = Cartesian3.multiplyByScalar(direction, scalar, scratchCartesian);
                up = Cartesian3.normalize(Cartesian3.subtract(up, w0, camera._up), camera._up);
                Cartesian3.clone(up, camera.up);

                right = Cartesian3.cross(direction, up, camera._right);
                Cartesian3.clone(right, camera.right);
            }
        }

        if (directionChanged || transformChanged) {
            camera._directionWC = Matrix4.multiplyByPointAsVector(transform, direction, camera._directionWC);
        }

        if (upChanged || transformChanged) {
            camera._upWC = Matrix4.multiplyByPointAsVector(transform, up, camera._upWC);
        }

        if (rightChanged || transformChanged) {
            camera._rightWC = Matrix4.multiplyByPointAsVector(transform, right, camera._rightWC);
        }

        if (positionChanged || directionChanged || upChanged || rightChanged || transformChanged) {
            updateViewMatrix(camera);
        }
    }

    function getHeading(direction, up) {
        var heading;
        if (!CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)) {
            heading = Math.atan2(direction.y, direction.x) - CesiumMath.PI_OVER_TWO;
        } else {
            heading = Math.atan2(up.y, up.x) - CesiumMath.PI_OVER_TWO;
        }

        return CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(heading);
    }

    function getPitch(direction) {
        return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(direction.z);
    }

    function getRoll(direction, up, right) {
        var roll = 0.0;
        if (!CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)) {
            roll = Math.atan2(-right.z, up.z);
            roll = CesiumMath.zeroToTwoPi(roll + CesiumMath.TWO_PI);
        }

        return roll;
    }

    var scratchHPRMatrix1 = new Matrix4();
    var scratchHPRMatrix2 = new Matrix4();

    defineProperties(Camera.prototype, {
        /**
         * Gets the camera's reference frame. The inverse of this transformation is appended to the view matrix.
         * @memberof Camera.prototype
         *
         * @type {Matrix4}
         * @readonly
         *
         * @default {@link Matrix4.IDENTITY}
         */
        transform : {
            get : function() {
                return this._transform;
            }
        },

        /**
         * Gets the inverse camera transform.
         * @memberof Camera.prototype
         *
         * @type {Matrix4}
         * @readonly
         *
         * @default {@link Matrix4.IDENTITY}
         */
        inverseTransform : {
            get : function() {
                updateMembers(this);
                return this._invTransform;
            }
        },

        /**
         * Gets the view matrix.
         * @memberof Camera.prototype
         *
         * @type {Matrix4}
         * @readonly
         *
         * @see Camera#inverseViewMatrix
         */
        viewMatrix : {
            get : function() {
                updateMembers(this);
                return this._viewMatrix;
            }
        },

        /**
         * Gets the inverse view matrix.
         * @memberof Camera.prototype
         *
         * @type {Matrix4}
         * @readonly
         *
         * @see Camera#viewMatrix
         */
        inverseViewMatrix : {
            get : function() {
                updateMembers(this);
                return this._invViewMatrix;
            }
        },

        /**
         * Gets the {@link Cartographic} position of the camera, with longitude and latitude
         * expressed in radians and height in meters.  In 2D and Columbus View, it is possible
         * for the returned longitude and latitude to be outside the range of valid longitudes
         * and latitudes when the camera is outside the map.
         * @memberof Camera.prototype
         *
         * @type {Cartographic}
         * @readonly
         */
        positionCartographic : {
            get : function() {
                updateMembers(this);
                return this._positionCartographic;
            }
        },

        /**
         * Gets the position of the camera in world coordinates.
         * @memberof Camera.prototype
         *
         * @type {Cartesian3}
         * @readonly
         */
        positionWC : {
            get : function() {
                updateMembers(this);
                return this._positionWC;
            }
        },

        /**
         * Gets the view direction of the camera in world coordinates.
         * @memberof Camera.prototype
         *
         * @type {Cartesian3}
         * @readonly
         */
        directionWC : {
            get : function() {
                updateMembers(this);
                return this._directionWC;
            }
        },

        /**
         * Gets the up direction of the camera in world coordinates.
         * @memberof Camera.prototype
         *
         * @type {Cartesian3}
         * @readonly
         */
        upWC : {
            get : function() {
                updateMembers(this);
                return this._upWC;
            }
        },

        /**
         * Gets the right direction of the camera in world coordinates.
         * @memberof Camera.prototype
         *
         * @type {Cartesian3}
         * @readonly
         */
        rightWC : {
            get : function() {
                updateMembers(this);
                return this._rightWC;
            }
        },

        /**
         * Gets the camera heading in radians.
         * @memberof Camera.prototype
         *
         * @type {Number}
         * @readonly
         */
        heading : {
            get : function() {
                if (this._mode !== SceneMode.MORPHING) {
                    var ellipsoid = this._projection.ellipsoid;

                    var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1);
                    var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2);
                    this._setTransform(transform);

                    var heading = getHeading(this.direction, this.up);

                    this._setTransform(oldTransform);

                    return heading;
                }

                return undefined;
            }
        },

        /**
         * Gets the camera pitch in radians.
         * @memberof Camera.prototype
         *
         * @type {Number}
         * @readonly
         */
        pitch : {
            get : function() {
                if (this._mode !== SceneMode.MORPHING) {
                    var ellipsoid = this._projection.ellipsoid;

                    var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1);
                    var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2);
                    this._setTransform(transform);

                    var pitch = getPitch(this.direction);

                    this._setTransform(oldTransform);

                    return pitch;
                }

                return undefined;
            }
        },

        /**
         * Gets the camera roll in radians.
         * @memberof Camera.prototype
         *
         * @type {Number}
         * @readonly
         */
        roll : {
            get : function() {
                if (this._mode !== SceneMode.MORPHING) {
                    var ellipsoid = this._projection.ellipsoid;

                    var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1);
                    var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2);
                    this._setTransform(transform);

                    var roll = getRoll(this.direction, this.up, this.right);

                    this._setTransform(oldTransform);

                    return roll;
                }

                return undefined;
            }
        },

        /**
         * Gets the event that will be raised at when the camera starts to move.
         * @memberof Camera.prototype
         * @type {Event}
         * @readonly
         */
        moveStart : {
            get : function() {
                return this._moveStart;
            }
        },

        /**
         * Gets the event that will be raised when the camera has stopped moving.
         * @memberof Camera.prototype
         * @type {Event}
         * @readonly
         */
        moveEnd : {
            get : function() {
                return this._moveEnd;
            }
        },

        /**
         * Gets the event that will be raised when the camera has changed by <code>percentageChanged</code>.
         * @memberof Camera.prototype
         * @type {Event}
         * @readonly
         */
        changed : {
            get : function() {
                return this._changed;
            }
        }
    });

    /**
     * @private
     */
    Camera.prototype.update = function(mode) {
                if (!defined(mode)) {
            throw new DeveloperError('mode is required.');
        }
        
        var updateFrustum = false;
        if (mode !== this._mode) {
            this._mode = mode;
            this._modeChanged = mode !== SceneMode.MORPHING;
            updateFrustum = this._mode === SceneMode.SCENE2D;
        }

        if (updateFrustum) {
            var frustum = this._max2Dfrustum = this.frustum.clone();

                        if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) {
                throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.');
            }
            
            var maxZoomOut = 2.0;
            var ratio = frustum.top / frustum.right;
            frustum.right = this._maxCoord.x * maxZoomOut;
            frustum.left = -frustum.right;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;
        }

        if (this._mode === SceneMode.SCENE2D) {
            clampMove2D(this, this.position);
        }

        var globe = this._scene.globe;
        var globeFinishedUpdating = !defined(globe) || (globe._surface.tileProvider.ready && globe._surface._tileLoadQueueHigh.length === 0 && globe._surface._tileLoadQueueMedium.length === 0 && globe._surface._tileLoadQueueLow.length === 0 && globe._surface._debug.tilesWaitingForChildren === 0);
        if (this._suspendTerrainAdjustment) {
            this._suspendTerrainAdjustment = !globeFinishedUpdating;
        }
        this._adjustHeightForTerrain();
    };

    var setTransformPosition = new Cartesian3();
    var setTransformUp = new Cartesian3();
    var setTransformDirection = new Cartesian3();

    Camera.prototype._setTransform = function(transform) {
        var position = Cartesian3.clone(this.positionWC, setTransformPosition);
        var up = Cartesian3.clone(this.upWC, setTransformUp);
        var direction = Cartesian3.clone(this.directionWC, setTransformDirection);

        Matrix4.clone(transform, this._transform);
        this._transformChanged = true;
        updateMembers(this);
        var inverse = this._actualInvTransform;

        Matrix4.multiplyByPoint(inverse, position, this.position);
        Matrix4.multiplyByPointAsVector(inverse, direction, this.direction);
        Matrix4.multiplyByPointAsVector(inverse, up, this.up);
        Cartesian3.cross(this.direction, this.up, this.right);

        updateMembers(this);
    };

    var scratchSetViewCartesian = new Cartesian3();
    var scratchSetViewTransform1 = new Matrix4();
    var scratchSetViewTransform2 = new Matrix4();
    var scratchSetViewQuaternion = new Quaternion();
    var scratchSetViewMatrix3 = new Matrix3();
    var scratchSetViewCartographic = new Cartographic();

    function setView3D(camera, position, hpr) {
        var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1);
        var localTransform = Transforms.eastNorthUpToFixedFrame(position, camera._projection.ellipsoid, scratchSetViewTransform2);
        camera._setTransform(localTransform);

        Cartesian3.clone(Cartesian3.ZERO, camera.position);
        hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO;

        var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion);
        var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3);

        Matrix3.getColumn(rotMat, 0, camera.direction);
        Matrix3.getColumn(rotMat, 2, camera.up);
        Cartesian3.cross(camera.direction, camera.up, camera.right);

        camera._setTransform(currentTransform);
    }

    function setViewCV(camera, position,hpr, convert) {
        var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1);
        camera._setTransform(Matrix4.IDENTITY);

        if (!Cartesian3.equals(position, camera.positionWC)) {
            if (convert) {
                var projection = camera._projection;
                var cartographic = projection.ellipsoid.cartesianToCartographic(position, scratchSetViewCartographic);
                position = projection.project(cartographic, scratchSetViewCartesian);
            }
            Cartesian3.clone(position, camera.position);
        }
        hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO;

        var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion);
        var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3);

        Matrix3.getColumn(rotMat, 0, camera.direction);
        Matrix3.getColumn(rotMat, 2, camera.up);
        Cartesian3.cross(camera.direction, camera.up, camera.right);

        camera._setTransform(currentTransform);
    }

    function setView2D(camera, position, hpr, convert) {
        var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1);
        camera._setTransform(Matrix4.IDENTITY);

        if (!Cartesian3.equals(position, camera.positionWC)) {
            if (convert) {
                var projection = camera._projection;
                var cartographic = projection.ellipsoid.cartesianToCartographic(position, scratchSetViewCartographic);
                position = projection.project(cartographic, scratchSetViewCartesian);
            }

            Cartesian2.clone(position, camera.position);

            var newLeft = -position.z * 0.5;
            var newRight = -newLeft;

            var frustum = camera.frustum;
            if (newRight > newLeft) {
                var ratio = frustum.top / frustum.right;
                frustum.right = newRight;
                frustum.left = newLeft;
                frustum.top = frustum.right * ratio;
                frustum.bottom = -frustum.top;
            }
        }

        if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
            hpr.heading = hpr.heading  - CesiumMath.PI_OVER_TWO;
            hpr.pitch = -CesiumMath.PI_OVER_TWO;
            hpr.roll =  0.0;
            var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion);
            var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3);

            Matrix3.getColumn(rotMat, 2, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
        }

        camera._setTransform(currentTransform);
    }

    var scratchToHPRDirection = new Cartesian3();
    var scratchToHPRUp = new Cartesian3();
    var scratchToHPRRight = new Cartesian3();

    function directionUpToHeadingPitchRoll(camera, position, orientation, result) {
        var direction = Cartesian3.clone(orientation.direction, scratchToHPRDirection);
        var up = Cartesian3.clone(orientation.up, scratchToHPRUp);

        if (camera._scene.mode === SceneMode.SCENE3D) {
            var ellipsoid = camera._projection.ellipsoid;
            var transform = Transforms.eastNorthUpToFixedFrame(position, ellipsoid, scratchHPRMatrix1);
            var invTransform = Matrix4.inverseTransformation(transform, scratchHPRMatrix2);

            Matrix4.multiplyByPointAsVector(invTransform, direction, direction);
            Matrix4.multiplyByPointAsVector(invTransform, up, up);
        }

        var right = Cartesian3.cross(direction, up, scratchToHPRRight);

        result.heading = getHeading(direction, up);
        result.pitch = getPitch(direction);
        result.roll = getRoll(direction, up, right);

        return result;
    }

    var scratchSetViewOptions = {
        destination : undefined,
        orientation : {
            direction : undefined,
            up : undefined,
            heading : undefined,
            pitch : undefined,
            roll : undefined
        },
        convert : undefined,
        endTransform : undefined
    };

    var scratchHpr = new HeadingPitchRoll();
    /**
     * Sets the camera position, orientation and transform.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3|Rectangle} [options.destination] The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view.
     * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pitch and roll properties. By default, the direction will point
     * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive
     * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode.
     * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame of the camera.
     *
     * @example
     * // 1. Set position with a top-down view
     * viewer.camera.setView({
     *     destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
     * });
     *
     * // 2 Set view with heading, pitch and roll
     * viewer.camera.setView({
     *     destination : cartesianPosition,
     *     orientation: {
     *         heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north)
     *         pitch : Cesium.Math.toRadians(-90),    // default value (looking down)
     *         roll : 0.0                             // default value
     *     }
     * });
     *
     * // 3. Change heading, pitch and roll with the camera position remaining the same.
     * viewer.camera.setView({
     *     orientation: {
     *         heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north)
     *         pitch : Cesium.Math.toRadians(-90),    // default value (looking down)
     *         roll : 0.0                             // default value
     *     }
     * });
     *
     *
     * // 4. View rectangle with a top-down view
     * viewer.camera.setView({
     *     destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
     * });
     *
     * // 5. Set position with an orientation using unit vectors.
     * viewer.camera.setView({
     *     destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
     *     orientation : {
     *         direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
     *         up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
     *     }
     * });
     */
    Camera.prototype.setView = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var orientation = defaultValue(options.orientation, defaultValue.EMPTY_OBJECT);

        var mode = this._mode;
        if (mode === SceneMode.MORPHING) {
            return;
        }

        if (defined(options.endTransform)) {
            this._setTransform(options.endTransform);
        }

        var convert = defaultValue(options.convert, true);
        var destination = defaultValue(options.destination, Cartesian3.clone(this.positionWC, scratchSetViewCartesian));
        if (defined(destination) && defined(destination.west)) {
            destination = this.getRectangleCameraCoordinates(destination, scratchSetViewCartesian);
            convert = false;
        }

        if (defined(orientation.direction)) {
            orientation = directionUpToHeadingPitchRoll(this, destination, orientation, scratchSetViewOptions.orientation);
        }

        scratchHpr.heading = defaultValue(orientation.heading, 0.0);
        scratchHpr.pitch = defaultValue(orientation.pitch, -CesiumMath.PI_OVER_TWO);
        scratchHpr.roll = defaultValue(orientation.roll, 0.0);

        this._suspendTerrainAdjustment = true;

        if (mode === SceneMode.SCENE3D) {
            setView3D(this, destination, scratchHpr);
        } else if (mode === SceneMode.SCENE2D) {
            setView2D(this, destination, scratchHpr, convert);
        } else {
            setViewCV(this, destination, scratchHpr, convert);
        }
    };

    var pitchScratch = new Cartesian3();
    /**
     * Fly the camera to the home view.  Use {@link Camera#.DEFAULT_VIEW_RECTANGLE} to set
     * the default view for the 3D scene.  The home view for 2D and columbus view shows the
     * entire map.
     *
     * @param {Number} [duration] The number of seconds to complete the camera flight to home. See {@link Camera#flyTo}
     */
    Camera.prototype.flyHome = function(duration) {
        var mode = this._mode;

        if (mode === SceneMode.MORPHING) {
            this._scene.completeMorph();
        }

        if (mode === SceneMode.SCENE2D) {
            this.flyTo({
                destination : Rectangle.MAX_VALUE,
                duration : duration,
                endTransform : Matrix4.IDENTITY
            });
        } else if (mode === SceneMode.SCENE3D) {
            var destination = this.getRectangleCameraCoordinates(Camera.DEFAULT_VIEW_RECTANGLE);

            var mag = Cartesian3.magnitude(destination);
            mag += mag * Camera.DEFAULT_VIEW_FACTOR;
            Cartesian3.normalize(destination, destination);
            Cartesian3.multiplyByScalar(destination, mag, destination);

            this.flyTo({
                destination : destination,
                duration : duration,
                endTransform : Matrix4.IDENTITY
            });
        } else if (mode === SceneMode.COLUMBUS_VIEW) {
            var maxRadii = this._projection.ellipsoid.maximumRadius;
            var position = new Cartesian3(0.0, -1.0, 1.0);
            position = Cartesian3.multiplyByScalar(Cartesian3.normalize(position, position), 5.0 * maxRadii, position);
            this.flyTo({
                destination : position,
                duration : duration,
                orientation : {
                    heading : 0.0,
                    pitch : -Math.acos(Cartesian3.normalize(position, pitchScratch).z),
                    roll : 0.0
                },
                endTransform : Matrix4.IDENTITY,
                convert : false
            });
        }
    };

    /**
     * Transform a vector or point from world coordinates to the camera's reference frame.
     *
     * @param {Cartesian4} cartesian The vector or point to transform.
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4} The transformed vector or point.
     */
    Camera.prototype.worldToCameraCoordinates = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian4();
        }
        updateMembers(this);
        return Matrix4.multiplyByVector(this._actualInvTransform, cartesian, result);
    };

    /**
     * Transform a point from world coordinates to the camera's reference frame.
     *
     * @param {Cartesian3} cartesian The point to transform.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The transformed point.
     */
    Camera.prototype.worldToCameraCoordinatesPoint = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }
        updateMembers(this);
        return Matrix4.multiplyByPoint(this._actualInvTransform, cartesian, result);
    };

    /**
     * Transform a vector from world coordinates to the camera's reference frame.
     *
     * @param {Cartesian3} cartesian The vector to transform.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The transformed vector.
     */
    Camera.prototype.worldToCameraCoordinatesVector = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }
        updateMembers(this);
        return Matrix4.multiplyByPointAsVector(this._actualInvTransform, cartesian, result);
    };

    /**
     * Transform a vector or point from the camera's reference frame to world coordinates.
     *
     * @param {Cartesian4} cartesian The vector or point to transform.
     * @param {Cartesian4} [result] The object onto which to store the result.
     * @returns {Cartesian4} The transformed vector or point.
     */
    Camera.prototype.cameraToWorldCoordinates = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian4();
        }
        updateMembers(this);
        return Matrix4.multiplyByVector(this._actualTransform, cartesian, result);
    };

    /**
     * Transform a point from the camera's reference frame to world coordinates.
     *
     * @param {Cartesian3} cartesian The point to transform.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The transformed point.
     */
    Camera.prototype.cameraToWorldCoordinatesPoint = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }
        updateMembers(this);
        return Matrix4.multiplyByPoint(this._actualTransform, cartesian, result);
    };

    /**
     * Transform a vector from the camera's reference frame to world coordinates.
     *
     * @param {Cartesian3} cartesian The vector to transform.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} The transformed vector.
     */
    Camera.prototype.cameraToWorldCoordinatesVector = function(cartesian, result) {
                if (!defined(cartesian)) {
            throw new DeveloperError('cartesian is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }
        updateMembers(this);
        return Matrix4.multiplyByPointAsVector(this._actualTransform, cartesian, result);
    };

    function clampMove2D(camera, position) {
        var rotatable2D = camera._scene.mapMode2D === MapMode2D.ROTATE;
        var maxProjectedX = camera._maxCoord.x;
        var maxProjectedY = camera._maxCoord.y;

        var minX;
        var maxX;
        if (rotatable2D) {
            maxX = maxProjectedX;
            minX = -maxX;
        } else {
            maxX = position.x - maxProjectedX * 2.0;
            minX = position.x + maxProjectedX * 2.0;
        }

        if (position.x > maxProjectedX) {
            position.x = maxX;
        }
        if (position.x < -maxProjectedX) {
            position.x = minX;
        }

        if (position.y > maxProjectedY) {
            position.y = maxProjectedY;
        }
        if (position.y < -maxProjectedY) {
            position.y = -maxProjectedY;
        }
    }

    var moveScratch = new Cartesian3();
    /**
     * Translates the camera's position by <code>amount</code> along <code>direction</code>.
     *
     * @param {Cartesian3} direction The direction to move.
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveBackward
     * @see Camera#moveForward
     * @see Camera#moveLeft
     * @see Camera#moveRight
     * @see Camera#moveUp
     * @see Camera#moveDown
     */
    Camera.prototype.move = function(direction, amount) {
                if (!defined(direction)) {
            throw new DeveloperError('direction is required.');
        }
        
        var cameraPosition = this.position;
        Cartesian3.multiplyByScalar(direction, amount, moveScratch);
        Cartesian3.add(cameraPosition, moveScratch, cameraPosition);

        if (this._mode === SceneMode.SCENE2D) {
            clampMove2D(this, cameraPosition);
        }
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's view vector.
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveBackward
     */
    Camera.prototype.moveForward = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this.direction, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's view vector.
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveForward
     */
    Camera.prototype.moveBackward = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this.direction, -amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's up vector.
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveDown
     */
    Camera.prototype.moveUp = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this.up, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's up vector.
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveUp
     */
    Camera.prototype.moveDown = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this.up, -amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the camera's right vector.
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveLeft
     */
    Camera.prototype.moveRight = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this.right, amount);
    };

    /**
     * Translates the camera's position by <code>amount</code> along the opposite direction
     * of the camera's right vector.
     *
     * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>.
     *
     * @see Camera#moveRight
     */
    Camera.prototype.moveLeft = function(amount) {
        amount = defaultValue(amount, this.defaultMoveAmount);
        this.move(this.right, -amount);
    };

    /**
     * Rotates the camera around its up vector by amount, in radians, in the opposite direction
     * of its right vector.
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#lookRight
     */
    Camera.prototype.lookLeft = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this.up, -amount);
    };

    /**
     * Rotates the camera around its up vector by amount, in radians, in the direction
     * of its right vector.
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#lookLeft
     */
    Camera.prototype.lookRight = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this.up, amount);
    };

    /**
     * Rotates the camera around its right vector by amount, in radians, in the direction
     * of its up vector.
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#lookDown
     */
    Camera.prototype.lookUp = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this.right, -amount);
    };

    /**
     * Rotates the camera around its right vector by amount, in radians, in the opposite direction
     * of its up vector.
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#lookUp
     */
    Camera.prototype.lookDown = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this.right, amount);
    };

    var lookScratchQuaternion = new Quaternion();
    var lookScratchMatrix = new Matrix3();
    /**
     * Rotate each of the camera's orientation vectors around <code>axis</code> by <code>angle</code>
     *
     * @param {Cartesian3} axis The axis to rotate around.
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#lookUp
     * @see Camera#lookDown
     * @see Camera#lookLeft
     * @see Camera#lookRight
     */
    Camera.prototype.look = function(axis, angle) {
                if (!defined(axis)) {
            throw new DeveloperError('axis is required.');
        }
        
        var turnAngle = defaultValue(angle, this.defaultLookAmount);
        var quaternion = Quaternion.fromAxisAngle(axis, -turnAngle, lookScratchQuaternion);
        var rotation = Matrix3.fromQuaternion(quaternion, lookScratchMatrix);

        var direction = this.direction;
        var up = this.up;
        var right = this.right;

        Matrix3.multiplyByVector(rotation, direction, direction);
        Matrix3.multiplyByVector(rotation, up, up);
        Matrix3.multiplyByVector(rotation, right, right);
    };

    /**
     * Rotate the camera counter-clockwise around its direction vector by amount, in radians.
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#twistRight
     */
    Camera.prototype.twistLeft = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this.direction, amount);
    };

    /**
     * Rotate the camera clockwise around its direction vector by amount, in radians.
     *
     * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>.
     *
     * @see Camera#twistLeft
     */
    Camera.prototype.twistRight = function(amount) {
        amount = defaultValue(amount, this.defaultLookAmount);
        this.look(this.direction, -amount);
    };

    var rotateScratchQuaternion = new Quaternion();
    var rotateScratchMatrix = new Matrix3();
    /**
     * Rotates the camera around <code>axis</code> by <code>angle</code>. The distance
     * of the camera's position to the center of the camera's reference frame remains the same.
     *
     * @param {Cartesian3} axis The axis to rotate around given in world coordinates.
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     *
     * @see Camera#rotateUp
     * @see Camera#rotateDown
     * @see Camera#rotateLeft
     * @see Camera#rotateRight
     */
    Camera.prototype.rotate = function(axis, angle) {
                if (!defined(axis)) {
            throw new DeveloperError('axis is required.');
        }
        
        var turnAngle = defaultValue(angle, this.defaultRotateAmount);
        var quaternion = Quaternion.fromAxisAngle(axis, -turnAngle, rotateScratchQuaternion);
        var rotation = Matrix3.fromQuaternion(quaternion, rotateScratchMatrix);
        Matrix3.multiplyByVector(rotation, this.position, this.position);
        Matrix3.multiplyByVector(rotation, this.direction, this.direction);
        Matrix3.multiplyByVector(rotation, this.up, this.up);
        Cartesian3.cross(this.direction, this.up, this.right);
        Cartesian3.cross(this.right, this.direction, this.up);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle downwards.
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     *
     * @see Camera#rotateUp
     * @see Camera#rotate
     */
    Camera.prototype.rotateDown = function(angle) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateVertical(this, angle);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle upwards.
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     *
     * @see Camera#rotateDown
     * @see Camera#rotate
     */
    Camera.prototype.rotateUp = function(angle) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateVertical(this, -angle);
    };

    var rotateVertScratchP = new Cartesian3();
    var rotateVertScratchA = new Cartesian3();
    var rotateVertScratchTan = new Cartesian3();
    var rotateVertScratchNegate = new Cartesian3();
    function rotateVertical(camera, angle) {
        var position = camera.position;
        var p = Cartesian3.normalize(position, rotateVertScratchP);
        if (defined(camera.constrainedAxis)) {
            var northParallel = Cartesian3.equalsEpsilon(p, camera.constrainedAxis, CesiumMath.EPSILON2);
            var southParallel = Cartesian3.equalsEpsilon(p, Cartesian3.negate(camera.constrainedAxis, rotateVertScratchNegate), CesiumMath.EPSILON2);
            if ((!northParallel && !southParallel)) {
                var constrainedAxis = Cartesian3.normalize(camera.constrainedAxis, rotateVertScratchA);

                var dot = Cartesian3.dot(p, constrainedAxis);
                var angleToAxis = CesiumMath.acosClamped(dot);
                if (angle > 0 && angle > angleToAxis) {
                    angle = angleToAxis - CesiumMath.EPSILON4;
                }

                dot = Cartesian3.dot(p, Cartesian3.negate(constrainedAxis, rotateVertScratchNegate));
                angleToAxis = CesiumMath.acosClamped(dot);
                if (angle < 0 && -angle > angleToAxis) {
                    angle = -angleToAxis + CesiumMath.EPSILON4;
                }

                var tangent = Cartesian3.cross(constrainedAxis, p, rotateVertScratchTan);
                camera.rotate(tangent, angle);
            } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {
                camera.rotate(camera.right, angle);
            }
        } else {
            camera.rotate(camera.right, angle);
        }
    }

    /**
     * Rotates the camera around the center of the camera's reference frame by angle to the right.
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     *
     * @see Camera#rotateLeft
     * @see Camera#rotate
     */
    Camera.prototype.rotateRight = function(angle) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateHorizontal(this, -angle);
    };

    /**
     * Rotates the camera around the center of the camera's reference frame by angle to the left.
     *
     * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>.
     *
     * @see Camera#rotateRight
     * @see Camera#rotate
     */
    Camera.prototype.rotateLeft = function(angle) {
        angle = defaultValue(angle, this.defaultRotateAmount);
        rotateHorizontal(this, angle);
    };

    function rotateHorizontal(camera, angle) {
        if (defined(camera.constrainedAxis)) {
            camera.rotate(camera.constrainedAxis, angle);
        } else {
            camera.rotate(camera.up, angle);
        }
    }

    function zoom2D(camera, amount) {
        var frustum = camera.frustum;

                if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) {
            throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.');
        }
        
        amount = amount * 0.5;
        var newRight = frustum.right - amount;
        var newLeft = frustum.left + amount;

        var maxRight = camera._maxCoord.x;
        if (camera._scene.mapMode2D === MapMode2D.ROTATE) {
            maxRight *= camera.maximumZoomFactor;
        }

        if (newRight > maxRight) {
            newRight = maxRight;
            newLeft = -maxRight;
        }

        if (newRight <= newLeft) {
            newRight = 1.0;
            newLeft = -1.0;
        }

        var ratio = frustum.top / frustum.right;
        frustum.right = newRight;
        frustum.left = newLeft;
        frustum.top = frustum.right * ratio;
        frustum.bottom = -frustum.top;
    }

    function zoom3D(camera, amount) {
        camera.move(camera.direction, amount);
    }

    /**
     * Zooms <code>amount</code> along the camera's view vector.
     *
     * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
     *
     * @see Camera#zoomOut
     */
    Camera.prototype.zoomIn = function(amount) {
        amount = defaultValue(amount, this.defaultZoomAmount);
        if (this._mode === SceneMode.SCENE2D) {
            zoom2D(this, amount);
        } else {
            zoom3D(this, amount);
        }
    };

    /**
     * Zooms <code>amount</code> along the opposite direction of
     * the camera's view vector.
     *
     * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>.
     *
     * @see Camera#zoomIn
     */
    Camera.prototype.zoomOut = function(amount) {
        amount = defaultValue(amount, this.defaultZoomAmount);
        if (this._mode === SceneMode.SCENE2D) {
            zoom2D(this, -amount);
        } else {
            zoom3D(this, -amount);
        }
    };

    /**
     * Gets the magnitude of the camera position. In 3D, this is the vector magnitude. In 2D and
     * Columbus view, this is the distance to the map.
     *
     * @returns {Number} The magnitude of the position.
     */
    Camera.prototype.getMagnitude = function() {
        if (this._mode === SceneMode.SCENE3D) {
            return Cartesian3.magnitude(this.position);
        } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
            return Math.abs(this.position.z);
        } else if (this._mode === SceneMode.SCENE2D) {
            return Math.max(this.frustum.right - this.frustum.left, this.frustum.top - this.frustum.bottom);
        }
    };

    var scratchLookAtMatrix4 = new Matrix4();

    /**
     * Sets the camera position and orientation using a target and offset. The target must be given in
     * world coordinates. The offset can be either a cartesian or heading/pitch/range in the local east-north-up reference frame centered at the target.
     * If the offset is a cartesian, then it is an offset from the center of the reference frame defined by the transformation matrix. If the offset
     * is heading/pitch/range, then the heading and the pitch angles are defined in the reference frame defined by the transformation matrix.
     * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
     * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center.
     *
     * In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
     * target will be the magnitude of the offset. The heading will be determined from the offset. If the heading cannot be
     * determined from the offset, the heading will be north.
     *
     * @param {Cartesian3} target The target position in world coordinates.
     * @param {Cartesian3|HeadingPitchRange} offset The offset from the target in the local east-north-up reference frame centered at the target.
     *
     * @exception {DeveloperError} lookAt is not supported while morphing.
     *
     * @example
     * // 1. Using a cartesian offset
     * var center = Cesium.Cartesian3.fromDegrees(-98.0, 40.0);
     * viewer.camera.lookAt(center, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0));
     *
     * // 2. Using a HeadingPitchRange offset
     * var center = Cesium.Cartesian3.fromDegrees(-72.0, 40.0);
     * var heading = Cesium.Math.toRadians(50.0);
     * var pitch = Cesium.Math.toRadians(-20.0);
     * var range = 5000.0;
     * viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, range));
     */
    Camera.prototype.lookAt = function(target, offset) {
                if (!defined(target)) {
            throw new DeveloperError('target is required');
        }
        if (!defined(offset)) {
            throw new DeveloperError('offset is required');
        }
        if (this._mode === SceneMode.MORPHING) {
            throw new DeveloperError('lookAt is not supported while morphing.');
        }
        
        var transform = Transforms.eastNorthUpToFixedFrame(target, Ellipsoid.WGS84, scratchLookAtMatrix4);
        this.lookAtTransform(transform, offset);
    };

    var scratchLookAtHeadingPitchRangeOffset = new Cartesian3();
    var scratchLookAtHeadingPitchRangeQuaternion1 = new Quaternion();
    var scratchLookAtHeadingPitchRangeQuaternion2 = new Quaternion();
    var scratchHeadingPitchRangeMatrix3 = new Matrix3();

    function offsetFromHeadingPitchRange(heading, pitch, range) {
        pitch = CesiumMath.clamp(pitch, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO);
        heading = CesiumMath.zeroToTwoPi(heading) - CesiumMath.PI_OVER_TWO;

        var pitchQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -pitch, scratchLookAtHeadingPitchRangeQuaternion1);
        var headingQuat = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -heading, scratchLookAtHeadingPitchRangeQuaternion2);
        var rotQuat = Quaternion.multiply(headingQuat, pitchQuat, headingQuat);
        var rotMatrix = Matrix3.fromQuaternion(rotQuat, scratchHeadingPitchRangeMatrix3);

        var offset = Cartesian3.clone(Cartesian3.UNIT_X, scratchLookAtHeadingPitchRangeOffset);
        Matrix3.multiplyByVector(rotMatrix, offset, offset);
        Cartesian3.negate(offset, offset);
        Cartesian3.multiplyByScalar(offset, range, offset);
        return offset;
    }

    /**
     * Sets the camera position and orientation using a target and transformation matrix. The offset can be either a cartesian or heading/pitch/range.
     * If the offset is a cartesian, then it is an offset from the center of the reference frame defined by the transformation matrix. If the offset
     * is heading/pitch/range, then the heading and the pitch angles are defined in the reference frame defined by the transformation matrix.
     * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
     * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center.
     *
     * In 2D, there must be a top down view. The camera will be placed above the center of the reference frame. The height above the
     * target will be the magnitude of the offset. The heading will be determined from the offset. If the heading cannot be
     * determined from the offset, the heading will be north.
     *
     * @param {Matrix4} transform The transformation matrix defining the reference frame.
     * @param {Cartesian3|HeadingPitchRange} [offset] The offset from the target in a reference frame centered at the target.
     *
     * @exception {DeveloperError} lookAtTransform is not supported while morphing.
     *
     * @example
     * // 1. Using a cartesian offset
     * var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-98.0, 40.0));
     * viewer.camera.lookAtTransform(transform, new Cesium.Cartesian3(0.0, -4790000.0, 3930000.0));
     *
     * // 2. Using a HeadingPitchRange offset
     * var transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-72.0, 40.0));
     * var heading = Cesium.Math.toRadians(50.0);
     * var pitch = Cesium.Math.toRadians(-20.0);
     * var range = 5000.0;
     * viewer.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(heading, pitch, range));
     */
    Camera.prototype.lookAtTransform = function(transform, offset) {
                if (!defined(transform)) {
            throw new DeveloperError('transform is required');
        }
        if (this._mode === SceneMode.MORPHING) {
            throw new DeveloperError('lookAtTransform is not supported while morphing.');
        }
        
        this._setTransform(transform);
        if (!defined(offset)) {
            return;
        }

        var cartesianOffset;
        if (defined(offset.heading)) {
            cartesianOffset = offsetFromHeadingPitchRange(offset.heading, offset.pitch, offset.range);
        } else {
            cartesianOffset = offset;
        }

        if (this._mode === SceneMode.SCENE2D) {
            Cartesian2.clone(Cartesian2.ZERO, this.position);

            Cartesian3.negate(cartesianOffset, this.up);
            this.up.z = 0.0;

            if (Cartesian3.magnitudeSquared(this.up) < CesiumMath.EPSILON10) {
                Cartesian3.clone(Cartesian3.UNIT_Y, this.up);
            }

            Cartesian3.normalize(this.up, this.up);

            this._setTransform(Matrix4.IDENTITY);

            Cartesian3.negate(Cartesian3.UNIT_Z, this.direction);
            Cartesian3.cross(this.direction, this.up, this.right);
            Cartesian3.normalize(this.right, this.right);

            var frustum = this.frustum;
            var ratio = frustum.top / frustum.right;
            frustum.right = Cartesian3.magnitude(cartesianOffset) * 0.5;
            frustum.left = -frustum.right;
            frustum.top = ratio * frustum.right;
            frustum.bottom = -frustum.top;

            this._setTransform(transform);

            return;
        }

        Cartesian3.clone(cartesianOffset, this.position);
        Cartesian3.negate(this.position, this.direction);
        Cartesian3.normalize(this.direction, this.direction);
        Cartesian3.cross(this.direction, Cartesian3.UNIT_Z, this.right);

        if (Cartesian3.magnitudeSquared(this.right) < CesiumMath.EPSILON10) {
            Cartesian3.clone(Cartesian3.UNIT_X, this.right);
        }

        Cartesian3.normalize(this.right, this.right);
        Cartesian3.cross(this.right, this.direction, this.up);
        Cartesian3.normalize(this.up, this.up);
    };

    var viewRectangle3DCartographic1 = new Cartographic();
    var viewRectangle3DCartographic2 = new Cartographic();
    var viewRectangle3DNorthEast = new Cartesian3();
    var viewRectangle3DSouthWest = new Cartesian3();
    var viewRectangle3DNorthWest = new Cartesian3();
    var viewRectangle3DSouthEast = new Cartesian3();
    var viewRectangle3DNorthCenter = new Cartesian3();
    var viewRectangle3DSouthCenter = new Cartesian3();
    var viewRectangle3DCenter = new Cartesian3();
    var viewRectangle3DEquator = new Cartesian3();
    var defaultRF = {
        direction : new Cartesian3(),
        right : new Cartesian3(),
        up : new Cartesian3()
    };
    var viewRectangle3DEllipsoidGeodesic;

    function computeD(direction, upOrRight, corner, tanThetaOrPhi) {
        var opposite = Math.abs(Cartesian3.dot(upOrRight, corner));
        return opposite / tanThetaOrPhi - Cartesian3.dot(direction, corner);
    }

    function rectangleCameraPosition3D(camera, rectangle, result, updateCamera) {
        var ellipsoid = camera._projection.ellipsoid;
        var cameraRF = updateCamera ? camera : defaultRF;

        var north = rectangle.north;
        var south = rectangle.south;
        var east = rectangle.east;
        var west = rectangle.west;

        // If we go across the International Date Line
        if (west > east) {
            east += CesiumMath.TWO_PI;
        }

        // Find the midpoint latitude.
        //
        // EllipsoidGeodesic will fail if the north and south edges are very close to being on opposite sides of the ellipsoid.
        // Ideally we'd just call EllipsoidGeodesic.setEndPoints and let it throw when it detects this case, but sadly it doesn't
        // even look for this case in optimized builds, so we have to test for it here instead.
        //
        // Fortunately, this case can only happen (here) when north is very close to the north pole and south is very close to the south pole,
        // so handle it just by using 0 latitude as the center.  It's certainliy possible to use a smaller tolerance
        // than one degree here, but one degree is safe and putting the center at 0 latitude should be good enough for any
        // rectangle that spans 178+ of the 180 degrees of latitude.
        var longitude = (west + east) * 0.5;
        var latitude;
        if (south < -CesiumMath.PI_OVER_TWO + CesiumMath.RADIANS_PER_DEGREE && north > CesiumMath.PI_OVER_TWO - CesiumMath.RADIANS_PER_DEGREE) {
            latitude = 0.0;
        } else {
            var northCartographic = viewRectangle3DCartographic1;
            northCartographic.longitude = longitude;
            northCartographic.latitude = north;
            northCartographic.height = 0.0;

            var southCartographic = viewRectangle3DCartographic2;
            southCartographic.longitude = longitude;
            southCartographic.latitude = south;
            southCartographic.height = 0.0;

            var ellipsoidGeodesic = viewRectangle3DEllipsoidGeodesic;
            if (!defined(ellipsoidGeodesic) || ellipsoidGeodesic.ellipsoid !== ellipsoid) {
                viewRectangle3DEllipsoidGeodesic = ellipsoidGeodesic = new EllipsoidGeodesic(undefined, undefined, ellipsoid);
            }

            ellipsoidGeodesic.setEndPoints(northCartographic, southCartographic);
            latitude = ellipsoidGeodesic.interpolateUsingFraction(0.5, viewRectangle3DCartographic1).latitude;
        }

        var centerCartographic = viewRectangle3DCartographic1;
        centerCartographic.longitude = longitude;
        centerCartographic.latitude = latitude;
        centerCartographic.height = 0.0;

        var center = ellipsoid.cartographicToCartesian(centerCartographic, viewRectangle3DCenter);

        var cart = viewRectangle3DCartographic1;
        cart.longitude = east;
        cart.latitude = north;
        var northEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthEast);
        cart.longitude = west;
        var northWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthWest);
        cart.longitude = longitude;
        var northCenter = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthCenter);
        cart.latitude = south;
        var southCenter = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthCenter);
        cart.longitude = east;
        var southEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthEast);
        cart.longitude = west;
        var southWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthWest);

        Cartesian3.subtract(northWest, center, northWest);
        Cartesian3.subtract(southEast, center, southEast);
        Cartesian3.subtract(northEast, center, northEast);
        Cartesian3.subtract(southWest, center, southWest);
        Cartesian3.subtract(northCenter, center, northCenter);
        Cartesian3.subtract(southCenter, center, southCenter);

        var direction = ellipsoid.geodeticSurfaceNormal(center, cameraRF.direction);
        Cartesian3.negate(direction, direction);
        var right = Cartesian3.cross(direction, Cartesian3.UNIT_Z, cameraRF.right);
        Cartesian3.normalize(right, right);
        var up = Cartesian3.cross(right, direction, cameraRF.up);

        var tanPhi = Math.tan(camera.frustum.fovy * 0.5);
        var tanTheta = camera.frustum.aspectRatio * tanPhi;

        var d = Math.max(
            computeD(direction, up, northWest, tanPhi),
            computeD(direction, up, southEast, tanPhi),
            computeD(direction, up, northEast, tanPhi),
            computeD(direction, up, southWest, tanPhi),
            computeD(direction, up, northCenter, tanPhi),
            computeD(direction, up, southCenter, tanPhi),
            computeD(direction, right, northWest, tanTheta),
            computeD(direction, right, southEast, tanTheta),
            computeD(direction, right, northEast, tanTheta),
            computeD(direction, right, southWest, tanTheta),
            computeD(direction, right, northCenter, tanTheta),
            computeD(direction, right, southCenter, tanTheta));

        // If the rectangle crosses the equator, compute D at the equator, too, because that's the
        // widest part of the rectangle when projected onto the globe.
        if (south < 0 && north > 0) {
            var equatorCartographic = viewRectangle3DCartographic1;
            equatorCartographic.longitude = west;
            equatorCartographic.latitude = 0.0;
            equatorCartographic.height = 0.0;
            var equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator);
            Cartesian3.subtract(equatorPosition, center, equatorPosition);
            d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta));

            equatorCartographic.longitude = east;
            equatorPosition = ellipsoid.cartographicToCartesian(equatorCartographic, viewRectangle3DEquator);
            Cartesian3.subtract(equatorPosition, center, equatorPosition);
            d = Math.max(d, computeD(direction, up, equatorPosition, tanPhi), computeD(direction, right, equatorPosition, tanTheta));
        }

        return Cartesian3.add(center, Cartesian3.multiplyByScalar(direction, -d, viewRectangle3DEquator), result);
    }

    var viewRectangleCVCartographic = new Cartographic();
    var viewRectangleCVNorthEast = new Cartesian3();
    var viewRectangleCVSouthWest = new Cartesian3();
    function rectangleCameraPositionColumbusView(camera, rectangle, result) {
        var projection = camera._projection;
        if (rectangle.west > rectangle.east) {
            rectangle = Rectangle.MAX_VALUE;
        }
        var transform = camera._actualTransform;
        var invTransform = camera._actualInvTransform;

        var cart = viewRectangleCVCartographic;
        cart.longitude = rectangle.east;
        cart.latitude = rectangle.north;
        var northEast = projection.project(cart, viewRectangleCVNorthEast);
        Matrix4.multiplyByPoint(transform, northEast, northEast);
        Matrix4.multiplyByPoint(invTransform, northEast, northEast);

        cart.longitude = rectangle.west;
        cart.latitude = rectangle.south;
        var southWest = projection.project(cart, viewRectangleCVSouthWest);
        Matrix4.multiplyByPoint(transform, southWest, southWest);
        Matrix4.multiplyByPoint(invTransform, southWest, southWest);

        var tanPhi = Math.tan(camera.frustum.fovy * 0.5);
        var tanTheta = camera.frustum.aspectRatio * tanPhi;

        result.x = (northEast.x - southWest.x) * 0.5 + southWest.x;
        result.y = (northEast.y - southWest.y) * 0.5 + southWest.y;
        result.z = Math.max((northEast.x - southWest.x) / tanTheta, (northEast.y - southWest.y) / tanPhi) * 0.5;

        return result;
    }

    var viewRectangle2DCartographic = new Cartographic();
    var viewRectangle2DNorthEast = new Cartesian3();
    var viewRectangle2DSouthWest = new Cartesian3();
    function rectangleCameraPosition2D(camera, rectangle, result) {
        var projection = camera._projection;
        if (rectangle.west > rectangle.east) {
            rectangle = Rectangle.MAX_VALUE;
        }

        var cart = viewRectangle2DCartographic;
        cart.longitude = rectangle.east;
        cart.latitude = rectangle.north;
        var northEast = projection.project(cart, viewRectangle2DNorthEast);
        cart.longitude = rectangle.west;
        cart.latitude = rectangle.south;
        var southWest = projection.project(cart, viewRectangle2DSouthWest);

        var width = Math.abs(northEast.x - southWest.x) * 0.5;
        var height = Math.abs(northEast.y - southWest.y) * 0.5;

        var right, top;
        var ratio = camera.frustum.right / camera.frustum.top;
        var heightRatio = height * ratio;
        if (width > heightRatio) {
            right = width;
            top = right / ratio;
        } else {
            top = height;
            right = heightRatio;
        }

        height = Math.max(2.0 * right, 2.0 * top);

        result.x = (northEast.x - southWest.x) * 0.5 + southWest.x;
        result.y = (northEast.y - southWest.y) * 0.5 + southWest.y;

        cart = projection.unproject(result, cart);
        cart.height = height;
        result = projection.project(cart, result);

        return result;
    }

    /**
     * Get the camera position needed to view an rectangle on an ellipsoid or map
     *
     * @param {Rectangle} rectangle The rectangle to view.
     * @param {Cartesian3} [result] The camera position needed to view the rectangle
     * @returns {Cartesian3} The camera position needed to view the rectangle
     */
    Camera.prototype.getRectangleCameraCoordinates = function(rectangle, result) {
                if (!defined(rectangle)) {
            throw new DeveloperError('rectangle is required');
        }
                var mode = this._mode;

        if (!defined(result)) {
            result = new Cartesian3();
        }

        if (mode === SceneMode.SCENE3D) {
            return rectangleCameraPosition3D(this, rectangle, result);
        } else if (mode === SceneMode.COLUMBUS_VIEW) {
            return rectangleCameraPositionColumbusView(this, rectangle, result);
        } else if (mode === SceneMode.SCENE2D) {
            return rectangleCameraPosition2D(this, rectangle, result);
        }

        return undefined;
    };

    var pickEllipsoid3DRay = new Ray();
    function pickEllipsoid3D(camera, windowPosition, ellipsoid, result) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        var ray = camera.getPickRay(windowPosition, pickEllipsoid3DRay);
        var intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);
        if (!intersection) {
            return undefined;
        }

        var t = intersection.start > 0.0 ? intersection.start : intersection.stop;
        return Ray.getPoint(ray, t, result);
    }

    var pickEllipsoid2DRay = new Ray();
    function pickMap2D(camera, windowPosition, projection, result) {
        var ray = camera.getPickRay(windowPosition, pickEllipsoid2DRay);
        var position = ray.origin;
        position.z = 0.0;
        var cart = projection.unproject(position);

        if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO) {
            return undefined;
        }

        return projection.ellipsoid.cartographicToCartesian(cart, result);
    }

    var pickEllipsoidCVRay = new Ray();
    function pickMapColumbusView(camera, windowPosition, projection, result) {
        var ray = camera.getPickRay(windowPosition, pickEllipsoidCVRay);
        var scalar = -ray.origin.x / ray.direction.x;
        Ray.getPoint(ray, scalar, result);

        var cart = projection.unproject(new Cartesian3(result.y, result.z, 0.0));

        if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO ||
            cart.longitude < -Math.PI || cart.longitude > Math.PI) {
            return undefined;
        }

        return projection.ellipsoid.cartographicToCartesian(cart, result);
    }

    /**
     * Pick an ellipsoid or map.
     *
     * @param {Cartesian2} windowPosition The x and y coordinates of a pixel.
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to pick.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3} If the ellipsoid or map was picked, returns the point on the surface of the ellipsoid or map
     * in world coordinates. If the ellipsoid or map was not picked, returns undefined.
     */
    Camera.prototype.pickEllipsoid = function(windowPosition, ellipsoid, result) {
                if (!defined(windowPosition)) {
            throw new DeveloperError('windowPosition is required.');
        }
        
        if (!defined(result)) {
            result = new Cartesian3();
        }

        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        if (this._mode === SceneMode.SCENE3D) {
            result = pickEllipsoid3D(this, windowPosition, ellipsoid, result);
        } else if (this._mode === SceneMode.SCENE2D) {
            result = pickMap2D(this, windowPosition, this._projection, result);
        } else if (this._mode === SceneMode.COLUMBUS_VIEW) {
            result = pickMapColumbusView(this, windowPosition, this._projection, result);
        } else {
            return undefined;
        }

        return result;
    };

    var pickPerspCenter = new Cartesian3();
    var pickPerspXDir = new Cartesian3();
    var pickPerspYDir = new Cartesian3();
    function getPickRayPerspective(camera, windowPosition, result) {
        var canvas = camera._scene.canvas;
        var width = canvas.clientWidth;
        var height = canvas.clientHeight;

        var tanPhi = Math.tan(camera.frustum.fovy * 0.5);
        var tanTheta = camera.frustum.aspectRatio * tanPhi;
        var near = camera.frustum.near;

        var x = (2.0 / width) * windowPosition.x - 1.0;
        var y = (2.0 / height) * (height - windowPosition.y) - 1.0;

        var position = camera.positionWC;
        Cartesian3.clone(position, result.origin);

        var nearCenter = Cartesian3.multiplyByScalar(camera.directionWC, near, pickPerspCenter);
        Cartesian3.add(position, nearCenter, nearCenter);
        var xDir = Cartesian3.multiplyByScalar(camera.rightWC, x * near * tanTheta, pickPerspXDir);
        var yDir = Cartesian3.multiplyByScalar(camera.upWC, y * near * tanPhi, pickPerspYDir);
        var direction = Cartesian3.add(nearCenter, xDir, result.direction);
        Cartesian3.add(direction, yDir, direction);
        Cartesian3.subtract(direction, position, direction);
        Cartesian3.normalize(direction, direction);

        return result;
    }

    var scratchDirection = new Cartesian3();

    function getPickRayOrthographic(camera, windowPosition, result) {
        var canvas = camera._scene.canvas;
        var width = canvas.clientWidth;
        var height = canvas.clientHeight;

        var x = (2.0 / width) * windowPosition.x - 1.0;
        x *= (camera.frustum.right - camera.frustum.left) * 0.5;
        var y = (2.0 / height) * (height - windowPosition.y) - 1.0;
        y *= (camera.frustum.top - camera.frustum.bottom) * 0.5;

        var origin = result.origin;
        Cartesian3.clone(camera.position, origin);

        Cartesian3.multiplyByScalar(camera.right, x, scratchDirection);
        Cartesian3.add(scratchDirection, origin, origin);
        Cartesian3.multiplyByScalar(camera.up, y, scratchDirection);
        Cartesian3.add(scratchDirection, origin, origin);

        Cartesian3.clone(camera.directionWC, result.direction);

        return result;
    }

    /**
     * Create a ray from the camera position through the pixel at <code>windowPosition</code>
     * in world coordinates.
     *
     * @param {Cartesian2} windowPosition The x and y coordinates of a pixel.
     * @param {Ray} [result] The object onto which to store the result.
     * @returns {Ray} Returns the {@link Cartesian3} position and direction of the ray.
     */
    Camera.prototype.getPickRay = function(windowPosition, result) {
                if (!defined(windowPosition)) {
            throw new DeveloperError('windowPosition is required.');
        }
        
        if (!defined(result)) {
            result = new Ray();
        }

        var frustum = this.frustum;
        if (defined(frustum.aspectRatio) && defined(frustum.fov) && defined(frustum.near)) {
            return getPickRayPerspective(this, windowPosition, result);
        }

        return getPickRayOrthographic(this, windowPosition, result);
    };

    var scratchToCenter = new Cartesian3();
    var scratchProj = new Cartesian3();

    /**
     * Return the distance from the camera to the front of the bounding sphere.
     *
     * @param {BoundingSphere} boundingSphere The bounding sphere in world coordinates.
     * @returns {Number} The distance to the bounding sphere.
     */
    Camera.prototype.distanceToBoundingSphere = function(boundingSphere) {
                if (!defined(boundingSphere)) {
            throw new DeveloperError('boundingSphere is required.');
        }
        
        var toCenter = Cartesian3.subtract(this.positionWC, boundingSphere.center, scratchToCenter);
        var proj = Cartesian3.multiplyByScalar(this.directionWC, Cartesian3.dot(toCenter, this.directionWC), scratchProj);
        return Math.max(0.0, Cartesian3.magnitude(proj) - boundingSphere.radius);
    };

    var scratchPixelSize = new Cartesian2();

    /**
     * Return the pixel size in meters.
     *
     * @param {BoundingSphere} boundingSphere The bounding sphere in world coordinates.
     * @param {Number} drawingBufferWidth The drawing buffer width.
     * @param {Number} drawingBufferHeight The drawing buffer height.
     * @returns {Number} The pixel size in meters.
     */
    Camera.prototype.getPixelSize = function(boundingSphere, drawingBufferWidth, drawingBufferHeight) {
                if (!defined(boundingSphere)) {
            throw new DeveloperError('boundingSphere is required.');
        }
        if (!defined(drawingBufferWidth)) {
            throw new DeveloperError('drawingBufferWidth is required.');
        }
        if (!defined(drawingBufferHeight)) {
            throw new DeveloperError('drawingBufferHeight is required.');
        }
        
        var distance = this.distanceToBoundingSphere(boundingSphere);
        var pixelSize = this.frustum.getPixelDimensions(drawingBufferWidth, drawingBufferHeight, distance, scratchPixelSize);
        return Math.max(pixelSize.x, pixelSize.y);
    };

    function createAnimationTemplateCV(camera, position, center, maxX, maxY, duration) {
        var newPosition = Cartesian3.clone(position);

        if (center.y > maxX) {
            newPosition.y -= center.y - maxX;
        } else if (center.y < -maxX) {
            newPosition.y += -maxX - center.y;
        }

        if (center.z > maxY) {
            newPosition.z -= center.z - maxY;
        } else if (center.z < -maxY) {
            newPosition.z += -maxY - center.z;
        }

        function updateCV(value) {
            var interp = Cartesian3.lerp(position, newPosition, value.time, new Cartesian3());
            camera.worldToCameraCoordinatesPoint(interp, camera.position);
        }
        return {
            easingFunction : EasingFunction.EXPONENTIAL_OUT,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : 1.0
            },
            duration : duration,
            update : updateCV
        };
    }

    var normalScratch = new Cartesian3();
    var centerScratch = new Cartesian3();
    var posScratch = new Cartesian3();
    var scratchCartesian3Subtract = new Cartesian3();

    function createAnimationCV(camera, duration) {
        var position = camera.position;
        var direction = camera.direction;

        var normal = camera.worldToCameraCoordinatesVector(Cartesian3.UNIT_X, normalScratch);
        var scalar = -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction);
        var center = Cartesian3.add(position, Cartesian3.multiplyByScalar(direction, scalar, centerScratch), centerScratch);
        camera.cameraToWorldCoordinatesPoint(center, center);

        position = camera.cameraToWorldCoordinatesPoint(camera.position, posScratch);

        var tanPhi = Math.tan(camera.frustum.fovy * 0.5);
        var tanTheta = camera.frustum.aspectRatio * tanPhi;
        var distToC = Cartesian3.magnitude(Cartesian3.subtract(position, center, scratchCartesian3Subtract));
        var dWidth = tanTheta * distToC;
        var dHeight = tanPhi * distToC;

        var mapWidth = camera._maxCoord.x;
        var mapHeight = camera._maxCoord.y;

        var maxX = Math.max(dWidth - mapWidth, mapWidth);
        var maxY = Math.max(dHeight - mapHeight, mapHeight);

        if (position.z < -maxX || position.z > maxX || position.y < -maxY || position.y > maxY) {
            var translateX = center.y < -maxX || center.y > maxX;
            var translateY = center.z < -maxY || center.z > maxY;
            if (translateX || translateY) {
                return createAnimationTemplateCV(camera, position, center, maxX, maxY, duration);
            }
        }

        return undefined;
    }

    /**
     * Create an animation to move the map into view. This method is only valid for 2D and Columbus modes.
     *
     * @param {Number} duration The duration, in seconds, of the animation.
     * @returns {Object} The animation or undefined if the scene mode is 3D or the map is already ion view.
     *
     * @private
     */
    Camera.prototype.createCorrectPositionTween = function(duration) {
                if (!defined(duration)) {
            throw new DeveloperError('duration is required.');
        }
        
        if (this._mode === SceneMode.COLUMBUS_VIEW) {
            return createAnimationCV(this, duration);
        }

        return undefined;
    };

    var scratchFlyToDestination = new Cartesian3();
    var newOptions = {
        destination : undefined,
        heading : undefined,
        pitch : undefined,
        roll : undefined,
        duration : undefined,
        complete : undefined,
        cancel : undefined,
        endTransform : undefined,
        maximumHeight : undefined,
        easingFunction : undefined
    };

    /**
     * Cancels the current camera flight if one is in progress.
     * The camera is left at it's current location.
     */
    Camera.prototype.cancelFlight = function () {
        if (defined(this._currentFlight)) {
            this._currentFlight.cancelTween();
            this._currentFlight = undefined;
        }
    };

    /**
     * Flies the camera from its current position to a new position.
     *
     * @param {Object} options Object with the following properties:
     * @param {Cartesian3|Rectangle} options.destination The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view.
     * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pith and roll properties. By default, the direction will point
     * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive
     * y direction in Columbus view.  Orientation is not used in 2D when in infinite scrolling mode.
     * @param {Number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight.
     * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete.
     * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled.
     * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed.
     * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight.
     * @param {EasingFunction|EasingFunction~Callback} [options.easingFunction] Controls how the time is interpolated over the duration of the flight.
     *
     * @exception {DeveloperError} If either direction or up is given, then both are required.
     *
     * @example
     * // 1. Fly to a position with a top-down view
     * viewer.camera.flyTo({
     *     destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
     * });
     *
     * // 2. Fly to a Rectangle with a top-down view
     * viewer.camera.flyTo({
     *     destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
     * });
     *
     * // 3. Fly to a position with an orientation using unit vectors.
     * viewer.camera.flyTo({
     *     destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
     *     orientation : {
     *         direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734),
     *         up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339)
     *     }
     * });
     *
     * // 4. Fly to a position with an orientation using heading, pitch and roll.
     * viewer.camera.flyTo({
     *     destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),
     *     orientation : {
     *         heading : Cesium.Math.toRadians(175.0),
     *         pitch : Cesium.Math.toRadians(-35.0),
     *         roll : 0.0
     *     }
     * });
     */
    Camera.prototype.flyTo = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var destination = options.destination;
                if (!defined(destination)) {
            throw new DeveloperError('destination is required.');
        }
        
        var mode = this._mode;
        if (mode === SceneMode.MORPHING) {
            return;
        }

        this.cancelFlight();

        var orientation = defaultValue(options.orientation, defaultValue.EMPTY_OBJECT);
        if (defined(orientation.direction)) {
            orientation = directionUpToHeadingPitchRoll(this, destination, orientation, scratchSetViewOptions.orientation);
        }

        if (defined(options.duration) && options.duration <= 0.0) {
            var setViewOptions = scratchSetViewOptions;
            setViewOptions.destination = options.destination;
            setViewOptions.orientation.heading = orientation.heading;
            setViewOptions.orientation.pitch = orientation.pitch;
            setViewOptions.orientation.roll = orientation.roll;
            setViewOptions.convert = options.convert;
            setViewOptions.endTransform = options.endTransform;
            this.setView(setViewOptions);
            if (typeof options.complete === 'function') {
                options.complete();
            }
            return;
        }

        var isRectangle = defined(destination.west);
        if (isRectangle) {
            destination = this.getRectangleCameraCoordinates(destination, scratchFlyToDestination);
        }

        var that = this;
        var flightTween;

        newOptions.destination = destination;
        newOptions.heading = orientation.heading;
        newOptions.pitch = orientation.pitch;
        newOptions.roll = orientation.roll;
        newOptions.duration = options.duration;
        newOptions.complete = function () {
            if(flightTween === that._currentFlight){
                that._currentFlight = undefined;
            }
            if (defined(options.complete)) {
                options.complete();
            }
        };
        newOptions.cancel = options.cancel;
        newOptions.endTransform = options.endTransform;
        newOptions.convert = isRectangle ? false : options.convert;
        newOptions.maximumHeight = options.maximumHeight;
        newOptions.easingFunction = options.easingFunction;

        var scene = this._scene;
        flightTween = scene.tweens.add(CameraFlightPath.createTween(scene, newOptions));
        this._currentFlight = flightTween;
    };

    function distanceToBoundingSphere3D(camera, radius) {
        var frustum = camera.frustum;
        var tanPhi = Math.tan(frustum.fovy * 0.5);
        var tanTheta = frustum.aspectRatio * tanPhi;
        return Math.max(radius / tanTheta, radius / tanPhi);
    }

    function distanceToBoundingSphere2D(camera, radius) {
        var frustum = camera.frustum;

        var right, top;
        var ratio = frustum.right / frustum.top;
        var heightRatio = radius * ratio;
        if (radius > heightRatio) {
            right = radius;
            top = right / ratio;
        } else {
            top = radius;
            right = heightRatio;
        }

        return Math.max(right, top) * 1.50;
    }

    var scratchDefaultOffset = new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_FOUR, 0.0);
    var MINIMUM_ZOOM = 100.0;

    function adjustBoundingSphereOffset(camera, boundingSphere, offset) {
        if (!defined(offset)) {
            offset = HeadingPitchRange.clone(scratchDefaultOffset);
        }

        var range = offset.range;
        if (!defined(range) || range === 0.0) {
            var radius = boundingSphere.radius;
            if (radius === 0.0) {
                offset.range = MINIMUM_ZOOM;
            } else {
                offset.range = camera._mode === SceneMode.SCENE2D ? distanceToBoundingSphere2D(camera, radius) : distanceToBoundingSphere3D(camera, radius);
            }
        }

        return offset;
    }

    /**
     * Sets the camera so that the current view contains the provided bounding sphere.
     *
     * <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
     * The heading and the pitch angles are defined in the local east-north-up reference frame.
     * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
     * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. If the range is
     * zero, a range will be computed such that the whole bounding sphere is visible.</p>
     *
     * <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
     * target will be the range. The heading will be determined from the offset. If the heading cannot be
     * determined from the offset, the heading will be north.</p>
     *
     * @param {BoundingSphere} boundingSphere The bounding sphere to view, in world coordinates.
     * @param {HeadingPitchRange} [offset] The offset from the target in the local east-north-up reference frame centered at the target.
     *
     * @exception {DeveloperError} viewBoundingSphere is not supported while morphing.
     */
    Camera.prototype.viewBoundingSphere = function(boundingSphere, offset) {
                if (!defined(boundingSphere)) {
            throw new DeveloperError('boundingSphere is required.');
        }

        if (this._mode === SceneMode.MORPHING) {
            throw new DeveloperError('viewBoundingSphere is not supported while morphing.');
        }
        
        offset = adjustBoundingSphereOffset(this, boundingSphere, offset);
        this.lookAt(boundingSphere.center, offset);
    };

    var scratchflyToBoundingSphereTransform = new Matrix4();
    var scratchflyToBoundingSphereDestination = new Cartesian3();
    var scratchflyToBoundingSphereDirection = new Cartesian3();
    var scratchflyToBoundingSphereUp = new Cartesian3();
    var scratchflyToBoundingSphereRight = new Cartesian3();
    var scratchFlyToBoundingSphereCart4 = new Cartesian4();
    var scratchFlyToBoundingSphereQuaternion = new Quaternion();
    var scratchFlyToBoundingSphereMatrix3 = new Matrix3();

    /**
     * Flies the camera to a location where the current view contains the provided bounding sphere.
     *
     * <p> The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
     * The heading and the pitch angles are defined in the local east-north-up reference frame.
     * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
     * angles are below the plane. Negative pitch angles are above the plane. The range is the distance from the center. If the range is
     * zero, a range will be computed such that the whole bounding sphere is visible.</p>
     *
     * <p>In 2D and Columbus View, there must be a top down view. The camera will be placed above the target looking down. The height above the
     * target will be the range. The heading will be aligned to local north.</p>
     *
     * @param {BoundingSphere} boundingSphere The bounding sphere to view, in world coordinates.
     * @param {Object} [options] Object with the following properties:
     * @param {Number} [options.duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight.
     * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
     * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete.
     * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled.
     * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed.
     * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight.
     * @param {EasingFunction|EasingFunction~Callback} [options.easingFunction] Controls how the time is interpolated over the duration of the flight.
     */
    Camera.prototype.flyToBoundingSphere = function(boundingSphere, options) {
                if (!defined(boundingSphere)) {
            throw new DeveloperError('boundingSphere is required.');
        }
        
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var scene2D = this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW;
        this._setTransform(Matrix4.IDENTITY);
        var offset = adjustBoundingSphereOffset(this, boundingSphere, options.offset);

        var position;
        if (scene2D) {
            position = Cartesian3.multiplyByScalar(Cartesian3.UNIT_Z, offset.range, scratchflyToBoundingSphereDestination);
        } else {
            position = offsetFromHeadingPitchRange(offset.heading, offset.pitch, offset.range);
        }

        var transform = Transforms.eastNorthUpToFixedFrame(boundingSphere.center, Ellipsoid.WGS84, scratchflyToBoundingSphereTransform);
        Matrix4.multiplyByPoint(transform, position, position);

        var direction;
        var up;

        if (!scene2D) {
            direction = Cartesian3.subtract(boundingSphere.center, position, scratchflyToBoundingSphereDirection);
            Cartesian3.normalize(direction, direction);

            up = Matrix4.multiplyByPointAsVector(transform, Cartesian3.UNIT_Z, scratchflyToBoundingSphereUp);
            if (1.0 - Math.abs(Cartesian3.dot(direction, up)) < CesiumMath.EPSILON6) {
                var rotateQuat = Quaternion.fromAxisAngle(direction, offset.heading, scratchFlyToBoundingSphereQuaternion);
                var rotation = Matrix3.fromQuaternion(rotateQuat, scratchFlyToBoundingSphereMatrix3);

                Cartesian3.fromCartesian4(Matrix4.getColumn(transform, 1, scratchFlyToBoundingSphereCart4), up);
                Matrix3.multiplyByVector(rotation, up, up);
            }

            var right = Cartesian3.cross(direction, up, scratchflyToBoundingSphereRight);
            Cartesian3.cross(right, direction, up);
            Cartesian3.normalize(up, up);
        }

        this.flyTo({
            destination : position,
            orientation : {
                direction : direction,
                up : up
            },
            duration : options.duration,
            complete : options.complete,
            cancel : options.cancel,
            endTransform : options.endTransform,
            maximumHeight : options.maximumHeight,
            easingFunction : options.easingFunction
        });
    };

    var scratchCartesian3_1 = new Cartesian3();
    var scratchCartesian3_2 = new Cartesian3();
    var scratchCartesian3_3 = new Cartesian3();
    var scratchCartesian3_4 = new Cartesian3();
    var horizonPoints = [new Cartesian3(), new Cartesian3(), new Cartesian3(), new Cartesian3()];

    function computeHorizonQuad(camera, ellipsoid) {
        var radii = ellipsoid.radii;
        var p = camera.positionWC;

        // Find the corresponding position in the scaled space of the ellipsoid.
        var q = Cartesian3.multiplyComponents(ellipsoid.oneOverRadii, p, scratchCartesian3_1);

        var qMagnitude = Cartesian3.magnitude(q);
        var qUnit = Cartesian3.normalize(q, scratchCartesian3_2);

        // Determine the east and north directions at q.
        var eUnit;
        var nUnit;
        if (Cartesian3.equalsEpsilon(qUnit, Cartesian3.UNIT_Z, CesiumMath.EPSILON10)) {
            eUnit = new Cartesian3(0, 1, 0);
            nUnit = new Cartesian3(0, 0, 1);
        } else {
            eUnit = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, qUnit, scratchCartesian3_3), scratchCartesian3_3);
            nUnit = Cartesian3.normalize(Cartesian3.cross(qUnit, eUnit, scratchCartesian3_4), scratchCartesian3_4);
        }

        // Determine the radius of the 'limb' of the ellipsoid.
        var wMagnitude = Math.sqrt(Cartesian3.magnitudeSquared(q) - 1.0);

        // Compute the center and offsets.
        var center = Cartesian3.multiplyByScalar(qUnit, 1.0 / qMagnitude, scratchCartesian3_1);
        var scalar = wMagnitude / qMagnitude;
        var eastOffset = Cartesian3.multiplyByScalar(eUnit, scalar, scratchCartesian3_2);
        var northOffset = Cartesian3.multiplyByScalar(nUnit, scalar, scratchCartesian3_3);

        // A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum.
        var upperLeft = Cartesian3.add(center, northOffset, horizonPoints[0]);
        Cartesian3.subtract(upperLeft, eastOffset, upperLeft);
        Cartesian3.multiplyComponents(radii, upperLeft, upperLeft);

        var lowerLeft = Cartesian3.subtract(center, northOffset, horizonPoints[1]);
        Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft);
        Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft);

        var lowerRight = Cartesian3.subtract(center, northOffset, horizonPoints[2]);
        Cartesian3.add(lowerRight, eastOffset, lowerRight);
        Cartesian3.multiplyComponents(radii, lowerRight, lowerRight);

        var upperRight = Cartesian3.add(center, northOffset, horizonPoints[3]);
        Cartesian3.add(upperRight, eastOffset, upperRight);
        Cartesian3.multiplyComponents(radii, upperRight, upperRight);

        return horizonPoints;
    }

    var scratchPickCartesian2 = new Cartesian2();
    var scratchRectCartesian = new Cartesian3();
    var cartoArray = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
    function addToResult(x, y, index, camera, ellipsoid, computedHorizonQuad) {
        scratchPickCartesian2.x = x;
        scratchPickCartesian2.y = y;
        var r = camera.pickEllipsoid(scratchPickCartesian2, ellipsoid, scratchRectCartesian);
        if (defined(r)) {
            cartoArray[index] = ellipsoid.cartesianToCartographic(r, cartoArray[index]);
            return 1;
        }
        cartoArray[index] = ellipsoid.cartesianToCartographic(computedHorizonQuad[index], cartoArray[index]);
        return 0;
    }
    /**
     * Computes the approximate visible rectangle on the ellipsoid.
     *
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid that you want to know the visible region.
     * @param {Rectangle} [result] The rectangle in which to store the result
     *
     * @returns {Rectangle|undefined} The visible rectangle or undefined if the ellipsoid isn't visible at all.
     */
    Camera.prototype.computeViewRectangle = function(ellipsoid, result) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        var cullingVolume = this.frustum.computeCullingVolume(this.positionWC, this.directionWC, this.upWC);
        var boundingSphere = new BoundingSphere(Cartesian3.ZERO, ellipsoid.maximumRadius);
        var visibility = cullingVolume.computeVisibility(boundingSphere);
        if (visibility === Intersect.OUTSIDE) {
            return undefined;
        }

        var canvas = this._scene.canvas;
        var width = canvas.clientWidth;
        var height = canvas.clientHeight;

        var successfulPickCount = 0;

        var computedHorizonQuad = computeHorizonQuad(this, ellipsoid);

        successfulPickCount += addToResult(0, 0, 0, this, ellipsoid, computedHorizonQuad);
        successfulPickCount += addToResult(0, height, 1, this, ellipsoid, computedHorizonQuad);
        successfulPickCount += addToResult(width, height, 2, this, ellipsoid, computedHorizonQuad);
        successfulPickCount += addToResult(width, 0, 3, this, ellipsoid, computedHorizonQuad);

        if (successfulPickCount < 2) {
            // If we have space non-globe in 3 or 4 corners then return the whole globe
            return Rectangle.MAX_VALUE;
        }

        result = Rectangle.fromCartographicArray(cartoArray, result);

        // Detect if we go over the poles
        var distance = 0;
        var lastLon = cartoArray[3].longitude;
        for (var i = 0; i < 4; ++i) {
            var lon = cartoArray[i].longitude;
            var diff = Math.abs(lon - lastLon);
            if (diff > CesiumMath.PI) {
                // Crossed the dateline
                distance += CesiumMath.TWO_PI - diff;
            } else {
                distance += diff;
            }

            lastLon = lon;
        }

        // We are over one of the poles so adjust the rectangle accordingly
        if (CesiumMath.equalsEpsilon(Math.abs(distance), CesiumMath.TWO_PI, CesiumMath.EPSILON9)) {
            result.west = -CesiumMath.PI;
            result.east = CesiumMath.PI;
            if (cartoArray[0].latitude >= 0.0) {
                result.north = CesiumMath.PI_OVER_TWO;
            } else {
                result.south = -CesiumMath.PI_OVER_TWO;
            }
        }

        return result;
    };

    /**
     * @private
     */
    Camera.clone = function(camera, result) {
        if (!defined(result)) {
            result = new Camera(camera._scene);
        }

        Cartesian3.clone(camera.position, result.position);
        Cartesian3.clone(camera.direction, result.direction);
        Cartesian3.clone(camera.up, result.up);
        Cartesian3.clone(camera.right, result.right);
        Matrix4.clone(camera._transform, result.transform);
        result._transformChanged = true;

        return result;
    };

    /**
     * A function that will execute when a flight completes.
     * @callback Camera~FlightCompleteCallback
     */

    /**
     * A function that will execute when a flight is cancelled.
     * @callback Camera~FlightCancelledCallback
     */

    return Camera;
});

/*global define*/
define('Scene/CameraEventType',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * Enumerates the available input for interacting with the camera.
     *
     * @exports CameraEventType
     */
    var CameraEventType = {
        /**
         * A left mouse button press followed by moving the mouse and releasing the button.
         *
         * @type {Number}
         * @constant
         */
        LEFT_DRAG : 0,

        /**
         *  A right mouse button press followed by moving the mouse and releasing the button.
         *
         * @type {Number}
         * @constant
         */
        RIGHT_DRAG : 1,

        /**
         *  A middle mouse button press followed by moving the mouse and releasing the button.
         *
         * @type {Number}
         * @constant
         */
        MIDDLE_DRAG : 2,

        /**
         * Scrolling the middle mouse button.
         *
         * @type {Number}
         * @constant
         */
        WHEEL : 3,

        /**
         * A two-finger touch on a touch surface.
         *
         * @type {Number}
         * @constant
         */
        PINCH : 4
    };

    return freezeObject(CameraEventType);
});

/*global define*/
define('Scene/CameraEventAggregator',[
        '../Core/Cartesian2',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/KeyboardEventModifier',
        '../Core/Math',
        '../Core/ScreenSpaceEventHandler',
        '../Core/ScreenSpaceEventType',
        './CameraEventType'
    ], function(
        Cartesian2,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        KeyboardEventModifier,
        CesiumMath,
        ScreenSpaceEventHandler,
        ScreenSpaceEventType,
        CameraEventType) {
    'use strict';

    function getKey(type, modifier) {
        var key = type;
        if (defined(modifier)) {
            key += '+' + modifier;
        }
        return key;
    }

    function clonePinchMovement(pinchMovement, result) {
        Cartesian2.clone(pinchMovement.distance.startPosition, result.distance.startPosition);
        Cartesian2.clone(pinchMovement.distance.endPosition, result.distance.endPosition);

        Cartesian2.clone(pinchMovement.angleAndHeight.startPosition, result.angleAndHeight.startPosition);
        Cartesian2.clone(pinchMovement.angleAndHeight.endPosition, result.angleAndHeight.endPosition);
    }

    function listenToPinch(aggregator, modifier, canvas) {
        var key = getKey(CameraEventType.PINCH, modifier);

        var update = aggregator._update;
        var isDown = aggregator._isDown;
        var eventStartPosition = aggregator._eventStartPosition;
        var pressTime = aggregator._pressTime;
        var releaseTime = aggregator._releaseTime;

        update[key] = true;
        isDown[key] = false;
        eventStartPosition[key] = new Cartesian2();

        var movement = aggregator._movement[key];
        if (!defined(movement)) {
            movement = aggregator._movement[key] = {};
        }

        movement.distance = {
            startPosition : new Cartesian2(),
            endPosition : new Cartesian2()
        };
        movement.angleAndHeight = {
            startPosition : new Cartesian2(),
            endPosition : new Cartesian2()
        };
        movement.prevAngle = 0.0;

        aggregator._eventHandler.setInputAction(function(event) {
            aggregator._buttonsDown++;
            isDown[key] = true;
            pressTime[key] = new Date();
            // Compute center position and store as start point.
            Cartesian2.lerp(event.position1, event.position2, 0.5, eventStartPosition[key]);
        }, ScreenSpaceEventType.PINCH_START, modifier);

        aggregator._eventHandler.setInputAction(function() {
            aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
            isDown[key] = false;
            releaseTime[key] = new Date();
        }, ScreenSpaceEventType.PINCH_END, modifier);

        aggregator._eventHandler.setInputAction(function(mouseMovement) {
            if (isDown[key]) {
                // Aggregate several input events into a single animation frame.
                if (!update[key]) {
                    Cartesian2.clone(mouseMovement.distance.endPosition, movement.distance.endPosition);
                    Cartesian2.clone(mouseMovement.angleAndHeight.endPosition, movement.angleAndHeight.endPosition);
                } else {
                    clonePinchMovement(mouseMovement, movement);
                    update[key] = false;
                    movement.prevAngle = movement.angleAndHeight.startPosition.x;
                }
                // Make sure our aggregation of angles does not "flip" over 360 degrees.
                var angle = movement.angleAndHeight.endPosition.x;
                var prevAngle = movement.prevAngle;
                var TwoPI = Math.PI * 2;
                while (angle >= (prevAngle + Math.PI)) {
                    angle -= TwoPI;
                }
                while (angle < (prevAngle - Math.PI)) {
                    angle += TwoPI;
                }
                movement.angleAndHeight.endPosition.x = -angle * canvas.clientWidth / 12;
                movement.angleAndHeight.startPosition.x = -prevAngle * canvas.clientWidth / 12;
            }
        }, ScreenSpaceEventType.PINCH_MOVE, modifier);
    }

    function listenToWheel(aggregator, modifier) {
        var key = getKey(CameraEventType.WHEEL, modifier);

        var update = aggregator._update;
        update[key] = true;

        var movement = aggregator._movement[key];
        if (!defined(movement)) {
            movement = aggregator._movement[key] = {};
        }

        movement.startPosition = new Cartesian2();
        movement.endPosition = new Cartesian2();

        aggregator._eventHandler.setInputAction(function(delta) {
            // TODO: magic numbers
            var arcLength = 15.0 * CesiumMath.toRadians(delta);
            if (!update[key]) {
                movement.endPosition.y = movement.endPosition.y + arcLength;
            } else {
                Cartesian2.clone(Cartesian2.ZERO, movement.startPosition);
                movement.endPosition.x = 0.0;
                movement.endPosition.y = arcLength;
                update[key] = false;
            }
        }, ScreenSpaceEventType.WHEEL, modifier);
    }

    function listenMouseButtonDownUp(aggregator, modifier, type) {
        var key = getKey(type, modifier);

        var isDown = aggregator._isDown;
        var eventStartPosition = aggregator._eventStartPosition;
        var pressTime = aggregator._pressTime;
        var releaseTime = aggregator._releaseTime;

        isDown[key] = false;
        eventStartPosition[key] = new Cartesian2();

        var lastMovement = aggregator._lastMovement[key];
        if (!defined(lastMovement)) {
            lastMovement = aggregator._lastMovement[key] = {
                startPosition : new Cartesian2(),
                endPosition : new Cartesian2(),
                valid : false
            };
        }

        var down;
        var up;
        if (type === CameraEventType.LEFT_DRAG) {
            down = ScreenSpaceEventType.LEFT_DOWN;
            up = ScreenSpaceEventType.LEFT_UP;
        } else if (type === CameraEventType.RIGHT_DRAG) {
            down = ScreenSpaceEventType.RIGHT_DOWN;
            up = ScreenSpaceEventType.RIGHT_UP;
        } else if (type === CameraEventType.MIDDLE_DRAG) {
            down = ScreenSpaceEventType.MIDDLE_DOWN;
            up = ScreenSpaceEventType.MIDDLE_UP;
        }

        aggregator._eventHandler.setInputAction(function(event) {
            aggregator._buttonsDown++;
            lastMovement.valid = false;
            isDown[key] = true;
            pressTime[key] = new Date();
            Cartesian2.clone(event.position, eventStartPosition[key]);
        }, down, modifier);

        aggregator._eventHandler.setInputAction(function() {
            aggregator._buttonsDown = Math.max(aggregator._buttonsDown - 1, 0);
            isDown[key] = false;
            releaseTime[key] = new Date();
        }, up, modifier);
    }

    function cloneMouseMovement(mouseMovement, result) {
        Cartesian2.clone(mouseMovement.startPosition, result.startPosition);
        Cartesian2.clone(mouseMovement.endPosition, result.endPosition);
    }

    function listenMouseMove(aggregator, modifier) {
        var update = aggregator._update;
        var movement = aggregator._movement;
        var lastMovement = aggregator._lastMovement;
        var isDown = aggregator._isDown;

        for ( var typeName in CameraEventType) {
            if (CameraEventType.hasOwnProperty(typeName)) {
                var type = CameraEventType[typeName];
                if (defined(type)) {
                    var key = getKey(type, modifier);
                    update[key] = true;

                    if (!defined(aggregator._lastMovement[key])) {
                        aggregator._lastMovement[key] = {
                            startPosition : new Cartesian2(),
                            endPosition : new Cartesian2(),
                            valid : false
                        };
                    }

                    if (!defined(aggregator._movement[key])) {
                        aggregator._movement[key] = {
                            startPosition : new Cartesian2(),
                            endPosition : new Cartesian2()
                        };
                    }
                }
            }
        }

        aggregator._eventHandler.setInputAction(function(mouseMovement) {
            for ( var typeName in CameraEventType) {
                if (CameraEventType.hasOwnProperty(typeName)) {
                    var type = CameraEventType[typeName];
                    if (defined(type)) {
                        var key = getKey(type, modifier);
                        if (isDown[key]) {
                            if (!update[key]) {
                                Cartesian2.clone(mouseMovement.endPosition, movement[key].endPosition);
                            } else {
                                cloneMouseMovement(movement[key], lastMovement[key]);
                                lastMovement[key].valid = true;
                                cloneMouseMovement(mouseMovement, movement[key]);
                                update[key] = false;
                            }
                        }
                    }
                }
            }

            Cartesian2.clone(mouseMovement.endPosition, aggregator._currentMousePosition);
        }, ScreenSpaceEventType.MOUSE_MOVE, modifier);
    }

    /**
     * Aggregates input events. For example, suppose the following inputs are received between frames:
     * left mouse button down, mouse move, mouse move, left mouse button up. These events will be aggregated into
     * one event with a start and end position of the mouse.
     *
     * @alias CameraEventAggregator
     * @constructor
     *
     * @param {Canvas} [canvas=document] The element to handle events for.
     *
     * @see ScreenSpaceEventHandler
     */
    function CameraEventAggregator(canvas) {
                if (!defined(canvas)) {
            throw new DeveloperError('canvas is required.');
        }
        
        this._eventHandler = new ScreenSpaceEventHandler(canvas, true);

        this._update = {};
        this._movement = {};
        this._lastMovement = {};
        this._isDown = {};
        this._eventStartPosition = {};
        this._pressTime = {};
        this._releaseTime = {};

        this._buttonsDown = 0;

        this._currentMousePosition = new Cartesian2();

        listenToWheel(this, undefined);
        listenToPinch(this, undefined, canvas);
        listenMouseButtonDownUp(this, undefined, CameraEventType.LEFT_DRAG);
        listenMouseButtonDownUp(this, undefined, CameraEventType.RIGHT_DRAG);
        listenMouseButtonDownUp(this, undefined, CameraEventType.MIDDLE_DRAG);
        listenMouseMove(this, undefined);

        for ( var modifierName in KeyboardEventModifier) {
            if (KeyboardEventModifier.hasOwnProperty(modifierName)) {
                var modifier = KeyboardEventModifier[modifierName];
                if (defined(modifier)) {
                    listenToWheel(this, modifier);
                    listenToPinch(this, modifier, canvas);
                    listenMouseButtonDownUp(this, modifier, CameraEventType.LEFT_DRAG);
                    listenMouseButtonDownUp(this, modifier, CameraEventType.RIGHT_DRAG);
                    listenMouseButtonDownUp(this, modifier, CameraEventType.MIDDLE_DRAG);
                    listenMouseMove(this, modifier);
                }
            }
        }
    }

    defineProperties(CameraEventAggregator.prototype, {
        /**
         * Gets the current mouse position.
         * @memberof CameraEventAggregator.prototype
         * @type {Cartesian2}
         */
        currentMousePosition : {
            get : function() {
                return this._currentMousePosition;
            }
        },

        /**
         * Gets whether any mouse button is down, a touch has started, or the wheel has been moved.
         * @memberof CameraEventAggregator.prototype
         * @type {Boolean}
         */
        anyButtonDown : {
            get : function() {
                var wheelMoved = !this._update[getKey(CameraEventType.WHEEL)] ||
                                 !this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.SHIFT)] ||
                                 !this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.CTRL)] ||
                                 !this._update[getKey(CameraEventType.WHEEL, KeyboardEventModifier.ALT)];
                return this._buttonsDown > 0 || wheelMoved;
            }
        }
    });

    /**
     * Gets if a mouse button down or touch has started and has been moved.
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Boolean} Returns <code>true</code> if a mouse button down or touch has started and has been moved; otherwise, <code>false</code>
     */
    CameraEventAggregator.prototype.isMoving = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getKey(type, modifier);
        return !this._update[key];
    };

    /**
     * Gets the aggregated start and end position of the current event.
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Object} An object with two {@link Cartesian2} properties: <code>startPosition</code> and <code>endPosition</code>.
     */
    CameraEventAggregator.prototype.getMovement = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getKey(type, modifier);
        var movement = this._movement[key];
        return movement;
    };

    /**
     * Gets the start and end position of the last move event (not the aggregated event).
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Object|undefined} An object with two {@link Cartesian2} properties: <code>startPosition</code> and <code>endPosition</code> or <code>undefined</code>.
     */
    CameraEventAggregator.prototype.getLastMovement = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getKey(type, modifier);
        var lastMovement = this._lastMovement[key];
        if (lastMovement.valid) {
            return lastMovement;
        }

        return undefined;
    };

    /**
     * Gets whether the mouse button is down or a touch has started.
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Boolean} Whether the mouse button is down or a touch has started.
     */
    CameraEventAggregator.prototype.isButtonDown = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getKey(type, modifier);
        return this._isDown[key];
    };

    /**
     * Gets the mouse position that started the aggregation.
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Cartesian2} The mouse position.
     */
    CameraEventAggregator.prototype.getStartMousePosition = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        if (type === CameraEventType.WHEEL) {
            return this._currentMousePosition;
        }

        var key = getKey(type, modifier);
        return this._eventStartPosition[key];
    };

    /**
     * Gets the time the button was pressed or the touch was started.
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Date} The time the button was pressed or the touch was started.
     */
    CameraEventAggregator.prototype.getButtonPressTime = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getKey(type, modifier);
        return this._pressTime[key];
    };

    /**
     * Gets the time the button was released or the touch was ended.
     *
     * @param {CameraEventType} type The camera event type.
     * @param {KeyboardEventModifier} [modifier] The keyboard modifier.
     * @returns {Date} The time the button was released or the touch was ended.
     */
    CameraEventAggregator.prototype.getButtonReleaseTime = function(type, modifier) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        var key = getKey(type, modifier);
        return this._releaseTime[key];
    };

    /**
     * Signals that all of the events have been handled and the aggregator should be reset to handle new events.
     */
    CameraEventAggregator.prototype.reset = function() {
        for ( var name in this._update) {
            if (this._update.hasOwnProperty(name)) {
                this._update[name] = true;
            }
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see CameraEventAggregator#destroy
     */
    CameraEventAggregator.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes mouse listeners held by this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * handler = handler && handler.destroy();
     *
     * @see CameraEventAggregator#isDestroyed
     */
    CameraEventAggregator.prototype.destroy = function() {
        this._eventHandler = this._eventHandler && this._eventHandler.destroy();
        return destroyObject(this);
    };

    return CameraEventAggregator;
});

/*global define*/
define('Scene/UrlTemplateImageryProvider',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/combine',
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/GeographicTilingScheme',
        '../Core/isArray',
        '../Core/loadJson',
        '../Core/loadText',
        '../Core/loadWithXhr',
        '../Core/loadXML',
        '../Core/Math',
        '../Core/Rectangle',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/when',
        './ImageryProvider'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartographic,
        combine,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        GeographicTilingScheme,
        isArray,
        loadJson,
        loadText,
        loadWithXhr,
        loadXML,
        CesiumMath,
        Rectangle,
        WebMercatorTilingScheme,
        when,
        ImageryProvider) {
    'use strict';

    /**
     * Provides imagery by requesting tiles using a specified URL template.
     *
     * @alias UrlTemplateImageryProvider
     * @constructor
     *
     * @param {Promise.<Object>|Object} [options] Object with the following properties:
     * @param {String} options.url  The URL template to use to request tiles.  It has the following keywords:
     * <ul>
     *     <li><code>{z}</code>: The level of the tile in the tiling scheme.  Level zero is the root of the quadtree pyramid.</li>
     *     <li><code>{x}</code>: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.</li>
     *     <li><code>{y}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.</li>
     *     <li><code>{s}</code>: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.</li>
     *     <li><code>{reverseX}</code>: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.</li>
     *     <li><code>{reverseY}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.</li>
     *     <li><code>{reverseZ}</code>: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid.  In order to use reverseZ, maximumLevel must be defined.</li>
     *     <li><code>{westDegrees}</code>: The Western edge of the tile in geodetic degrees.</li>
     *     <li><code>{southDegrees}</code>: The Southern edge of the tile in geodetic degrees.</li>
     *     <li><code>{eastDegrees}</code>: The Eastern edge of the tile in geodetic degrees.</li>
     *     <li><code>{northDegrees}</code>: The Northern edge of the tile in geodetic degrees.</li>
     *     <li><code>{westProjected}</code>: The Western edge of the tile in projected coordinates of the tiling scheme.</li>
     *     <li><code>{southProjected}</code>: The Southern edge of the tile in projected coordinates of the tiling scheme.</li>
     *     <li><code>{eastProjected}</code>: The Eastern edge of the tile in projected coordinates of the tiling scheme.</li>
     *     <li><code>{northProjected}</code>: The Northern edge of the tile in projected coordinates of the tiling scheme.</li>
     *     <li><code>{width}</code>: The width of each tile in pixels.</li>
     *     <li><code>{height}</code>: The height of each tile in pixels.</li>
     * </ul>
     * @param {String} [options.pickFeaturesUrl] The URL template to use to pick features.  If this property is not specified,
     *                 {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
     *                 features picked.  The URL template supports all of the keywords supported by the <code>url</code>
     *                 parameter, plus the following:
     * <ul>
     *     <li><code>{i}</code>: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.</li>
     *     <li><code>{j}</code>: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.</li>
     *     <li><code>{reverseI}</code>: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.</li>
     *     <li><code>{reverseJ}</code>: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.</li>
     *     <li><code>{longitudeDegrees}</code>: The longitude of the picked position in degrees.</li>
     *     <li><code>{latitudeDegrees}</code>: The latitude of the picked position in degrees.</li>
     *     <li><code>{longitudeProjected}</code>: The longitude of the picked position in the projected coordinates of the tiling scheme.</li>
     *     <li><code>{latitudeProjected}</code>: The latitude of the picked position in the projected coordinates of the tiling scheme.</li>
     *     <li><code>{format}</code>: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.</li>
     * </ul>
     * @param {Object} [options.urlSchemeZeroPadding] Gets the URL scheme zero padding for each tile coordinate. The format is '000' where
     * each coordinate will be padded on the left with zeros to match the width of the passed string of zeros. e.g. Setting:
     * urlSchemeZeroPadding : { '{x}' : '0000'}
     * will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
     * It the passed object has the following keywords:
     * <ul>
     *  <li> <code>{z}</code>: The zero padding for the level of the tile in the tiling scheme.</li>
     *  <li> <code>{x}</code>: The zero padding for the tile X coordinate in the tiling scheme.</li>
     *  <li> <code>{y}</code>: The zero padding for the the tile Y coordinate in the tiling scheme.</li>
     *  <li> <code>{reverseX}</code>: The zero padding for the tile reverseX coordinate in the tiling scheme.</li>
     *  <li> <code>{reverseY}</code>: The zero padding for the tile reverseY coordinate in the tiling scheme.</li>
     *  <li> <code>{reverseZ}</code>: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.</li>
     * </ul>
     * @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the <code>{s}</code> placeholder in the URL template.
     *                          If this parameter is a single string, each character in the string is a subdomain.  If it is
     *                          an array, each element in the array is a subdomain.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
     * @param {Credit|String} [options.credit=''] A credit for the data source, which is displayed on the canvas.
     * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.  Take care when specifying
     *                 this that the number of tiles at the minimum level is small, such as four or less.  A larger number is likely
     *                 to result in rendering problems.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
     * @param {TilingScheme} [options.tilingScheme=WebMercatorTilingScheme] The tiling scheme specifying how the ellipsoidal
     * surface is broken into tiles.  If this parameter is not provided, a {@link WebMercatorTilingScheme}
     * is used.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     *                    this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     *                    parameter is specified, the WGS84 ellipsoid is used.
     * @param {Number} [options.tileWidth=256] Pixel width of image tiles.
     * @param {Number} [options.tileHeight=256] Pixel height of image tiles.
     * @param {Boolean} [options.hasAlphaChannel=true] true if the images provided by this imagery provider
     *                  include an alpha channel; otherwise, false.  If this property is false, an alpha channel, if
     *                  present, will be ignored.  If this property is true, any images without an alpha channel will
     *                  be treated as if their alpha is 1.0 everywhere.  When this property is false, memory usage
     *                  and texture upload time are potentially reduced.
     * @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats] The formats in which to get feature information at a
     *                                 specific location when {@link UrlTemplateImageryProvider#pickFeatures} is invoked.  If this
     *                                 parameter is not specified, feature picking is disabled.
     * @param {Boolean} [options.enablePickFeatures=true] If true, {@link UrlTemplateImageryProvider#pickFeatures} will
     *        request the <code>options.pickFeaturesUrl</code> and attempt to interpret the features included in the response.  If false,
     *        {@link UrlTemplateImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
     *        features) without communicating with the server.  Set this property to false if you know your data
     *        source does not support picking features or if you don't want this provider's features to be pickable. Note
     *        that this can be dynamically overridden by modifying the {@link UriTemplateImageryProvider#enablePickFeatures}
     *        property.
     *
     *
     * @example
     * // Access Natural Earth II imagery, which uses a TMS tiling scheme and Geographic (EPSG:4326) project
     * var tms = new Cesium.UrlTemplateImageryProvider({
     *     url : 'https://cesiumjs.org/tilesets/imagery/naturalearthii/{z}/{x}/{reverseY}.jpg',
     *     credit : '© Analytical Graphics, Inc.',
     *     tilingScheme : new Cesium.GeographicTilingScheme(),
     *     maximumLevel : 5
     * });
     * // Access the CartoDB Positron basemap, which uses an OpenStreetMap-like tiling scheme.
     * var positron = new Cesium.UrlTemplateImageryProvider({
     *     url : 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
     *     credit : 'Map tiles by CartoDB, under CC BY 3.0. Data by OpenStreetMap, under ODbL.'
     * });
     * // Access a Web Map Service (WMS) server.
     * var wms = new Cesium.UrlTemplateImageryProvider({
     *    url : 'https://programs.communications.gov.au/geoserver/ows?tiled=true&' +
     *          'transparent=true&format=image%2Fpng&exceptions=application%2Fvnd.ogc.se_xml&' +
     *          'styles=&service=WMS&version=1.1.1&request=GetMap&' +
     *          'layers=public%3AMyBroadband_Availability&srs=EPSG%3A3857&' +
     *          'bbox={westProjected}%2C{southProjected}%2C{eastProjected}%2C{northProjected}&' +
     *          'width=256&height=256',
     *    rectangle : Cesium.Rectangle.fromDegrees(96.799393, -43.598214999057824, 153.63925700000001, -9.2159219997013)
     * });
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     */
    function UrlTemplateImageryProvider(options) {
                if (!defined(options)) {
          throw new DeveloperError('options is required.');
        }
        if (!when.isPromise(options) && !defined(options.url)) {
          throw new DeveloperError('options is required.');
        }
        
        this._errorEvent = new Event();

        this._url = undefined;
        this._urlSchemeZeroPadding = undefined;
        this._pickFeaturesUrl = undefined;
        this._proxy = undefined;
        this._tileWidth = undefined;
        this._tileHeight = undefined;
        this._maximumLevel = undefined;
        this._minimumLevel = undefined;
        this._tilingScheme = undefined;
        this._rectangle = undefined;
        this._tileDiscardPolicy = undefined;
        this._credit = undefined;
        this._hasAlphaChannel = undefined;
        this._readyPromise = undefined;

        /**
         * Gets or sets a value indicating whether feature picking is enabled.  If true, {@link UrlTemplateImageryProvider#pickFeatures} will
         * request the <code>options.pickFeaturesUrl</code> and attempt to interpret the features included in the response.  If false,
         * {@link UrlTemplateImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
         * features) without communicating with the server.  Set this property to false if you know your data
         * source does not support picking features or if you don't want this provider's features to be pickable.
         * @type {Boolean}
         * @default true
         */
        this.enablePickFeatures = true;

        this.reinitialize(options);
    }

    defineProperties(UrlTemplateImageryProvider.prototype, {
        /**
         * Gets the URL template to use to request tiles.  It has the following keywords:
         * <ul>
         *  <li> <code>{z}</code>: The level of the tile in the tiling scheme.  Level zero is the root of the quadtree pyramid.</li>
         *  <li> <code>{x}</code>: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.</li>
         *  <li> <code>{y}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.</li>
         *  <li> <code>{s}</code>: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.</li>
         *  <li> <code>{reverseX}</code>: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.</li>
         *  <li> <code>{reverseY}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.</li>
         *  <li> <code>{reverseZ}</code>: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid.  In order to use reverseZ, maximumLevel must be defined.</li>
         *  <li> <code>{westDegrees}</code>: The Western edge of the tile in geodetic degrees.</li>
         *  <li> <code>{southDegrees}</code>: The Southern edge of the tile in geodetic degrees.</li>
         *  <li> <code>{eastDegrees}</code>: The Eastern edge of the tile in geodetic degrees.</li>
         *  <li> <code>{northDegrees}</code>: The Northern edge of the tile in geodetic degrees.</li>
         *  <li> <code>{westProjected}</code>: The Western edge of the tile in projected coordinates of the tiling scheme.</li>
         *  <li> <code>{southProjected}</code>: The Southern edge of the tile in projected coordinates of the tiling scheme.</li>
         *  <li> <code>{eastProjected}</code>: The Eastern edge of the tile in projected coordinates of the tiling scheme.</li>
         *  <li> <code>{northProjected}</code>: The Northern edge of the tile in projected coordinates of the tiling scheme.</li>
         *  <li> <code>{width}</code>: The width of each tile in pixels.</li>
         *  <li> <code>{height}</code>: The height of each tile in pixels.</li>
         * </ul>
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the URL scheme zero padding for each tile coordinate. The format is '000' where each coordinate will be padded on
         * the left with zeros to match the width of the passed string of zeros. e.g. Setting:
         * urlSchemeZeroPadding : { '{x}' : '0000'}
         * will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
         * It has the following keywords:
         * <ul>
         *  <li> <code>{z}</code>: The zero padding for the level of the tile in the tiling scheme.</li>
         *  <li> <code>{x}</code>: The zero padding for the tile X coordinate in the tiling scheme.</li>
         *  <li> <code>{y}</code>: The zero padding for the the tile Y coordinate in the tiling scheme.</li>
         *  <li> <code>{reverseX}</code>: The zero padding for the tile reverseX coordinate in the tiling scheme.</li>
         *  <li> <code>{reverseY}</code>: The zero padding for the tile reverseY coordinate in the tiling scheme.</li>
         *  <li> <code>{reverseZ}</code>: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.</li>
         * </ul>
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Object}
         * @readonly
         */
        urlSchemeZeroPadding : {
            get : function() {
                return this._urlSchemeZeroPadding;
            }
        },


        /**
         * Gets the URL template to use to use to pick features.  If this property is not specified,
         * {@link UrlTemplateImageryProvider#pickFeatures} will immediately returned undefined, indicating no
         * features picked.  The URL template supports all of the keywords supported by the
         * {@link UrlTemplateImageryProvider#url} property, plus the following:
         * <ul>
         *     <li><code>{i}</code>: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.</li>
         *     <li><code>{j}</code>: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.</li>
         *     <li><code>{reverseI}</code>: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.</li>
         *     <li><code>{reverseJ}</code>: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.</li>
         *     <li><code>{longitudeDegrees}</code>: The longitude of the picked position in degrees.</li>
         *     <li><code>{latitudeDegrees}</code>: The latitude of the picked position in degrees.</li>
         *     <li><code>{longitudeProjected}</code>: The longitude of the picked position in the projected coordinates of the tiling scheme.</li>
         *     <li><code>{latitudeProjected}</code>: The latitude of the picked position in the projected coordinates of the tiling scheme.</li>
         *     <li><code>{format}</code>: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.</li>
         * </ul>
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        pickFeaturesUrl : {
            get : function() {
                return this._pickFeaturesUrl;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         * @default undefined
         */
        proxy : {
            get : function() {
                return this._proxy;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Number}
         * @readonly
         * @default 256
         */
        tileWidth : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
                }
                                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Number}
         * @readonly
         * @default 256
         */
        tileHeight: {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
                }
                                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested, or undefined if there is no limit.
         * This function should not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Number}
         * @readonly
         * @default undefined
         */
        maximumLevel : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
                }
                                return this._maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Number}
         * @readonly
         * @default 0
         */
        minimumLevel : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
                }
                                return this._minimumLevel;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         * @default new WebMercatorTilingScheme()
         */
        tilingScheme : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
                }
                                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         * @default tilingScheme.rectangle
         */
        rectangle : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
                }
                                return this._rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         * @default undefined
         */
        tileDiscardPolicy : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
                }
                                return this._tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return defined(this._urlParts);
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link UrlTemplateImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Credit}
         * @readonly
         * @default undefined
         */
        credit : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('credit must not be called before the imagery provider is ready.');
                }
                                return this._credit;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.  This function should
         * not be called before {@link ImageryProvider#ready} returns true.
         * @memberof UrlTemplateImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         * @default true
         */
        hasAlphaChannel : {
            get : function() {
                                if (!this.ready) {
                    throw new DeveloperError('hasAlphaChannel must not be called before the imagery provider is ready.');
                }
                                return this._hasAlphaChannel;
            }
        }
    });

    /**
     * Reinitializes this instance.  Reinitializing an instance already in use is supported, but it is not
     * recommended because existing tiles provided by the imagery provider will not be updated.
     *
     * @param {Promise.<Object>|Object} options Any of the options that may be passed to the {@link UrlTemplateImageryProvider} constructor.
     */
    UrlTemplateImageryProvider.prototype.reinitialize = function(options) {
        var that = this;
        that._readyPromise = when(options).then(function(properties) {
                        if (!defined(properties)) {
                throw new DeveloperError('options is required.');
              }
            if (!defined(properties.url)) {
                throw new DeveloperError('options.url is required.');
            }
                        that.enablePickFeatures = defaultValue(properties.enablePickFeatures, that.enablePickFeatures);
            that._url = properties.url;
            that._urlSchemeZeroPadding = defaultValue(properties.urlSchemeZeroPadding, that.urlSchemeZeroPadding);
            that._pickFeaturesUrl = properties.pickFeaturesUrl;
            that._proxy = properties.proxy;
            that._tileDiscardPolicy = properties.tileDiscardPolicy;
            that._getFeatureInfoFormats = properties.getFeatureInfoFormats;

            that._subdomains = properties.subdomains;
            if (isArray(that._subdomains)) {
                that._subdomains = that._subdomains.slice();
            } else if (defined(that._subdomains) && that._subdomains.length > 0) {
                that._subdomains = that._subdomains.split('');
            } else {
                that._subdomains = ['a', 'b', 'c'];
            }

            that._tileWidth = defaultValue(properties.tileWidth, 256);
            that._tileHeight = defaultValue(properties.tileHeight, 256);
            that._minimumLevel = defaultValue(properties.minimumLevel, 0);
            that._maximumLevel = properties.maximumLevel;
            that._tilingScheme = defaultValue(properties.tilingScheme, new WebMercatorTilingScheme({ ellipsoid : properties.ellipsoid }));
            that._rectangle = defaultValue(properties.rectangle, that._tilingScheme.rectangle);
            that._rectangle = Rectangle.intersection(that._rectangle, that._tilingScheme.rectangle);
            that._hasAlphaChannel = defaultValue(properties.hasAlphaChannel, true);

            var credit = properties.credit;
            if (typeof credit === 'string') {
                credit = new Credit(credit);
            }
            that._credit = credit;

            that._urlParts = urlTemplateToParts(that._url, tags);
            that._pickFeaturesUrlParts = urlTemplateToParts(that._pickFeaturesUrl, pickFeaturesTags);
            return true;
        });
    };

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    UrlTemplateImageryProvider.prototype.getTileCredits = function(x, y, level) {
                if (!this.ready) {
            throw new DeveloperError('getTileCredits must not be called before the imagery provider is ready.');
        }
                return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link UrlTemplateImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     */
    UrlTemplateImageryProvider.prototype.requestImage = function(x, y, level) {
                if (!this.ready) {
            throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
        }
                var url = buildImageUrl(this, x, y, level);
        return ImageryProvider.loadImage(this, url);
    };

    /**
     * Asynchronously determines what features, if any, are located at a given longitude and latitude within
     * a tile.  This function should not be called before {@link ImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    UrlTemplateImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
                if (!this.ready) {
            throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.');
        }
        
        if (!this.enablePickFeatures || !defined(this._pickFeaturesUrl) || this._getFeatureInfoFormats.length === 0) {
            return undefined;
        }

        var formatIndex = 0;

        var that = this;

        function handleResponse(format, data) {
            return format.callback(data);
        }

        function doRequest() {
            if (formatIndex >= that._getFeatureInfoFormats.length) {
                // No valid formats, so no features picked.
                return when([]);
            }

            var format = that._getFeatureInfoFormats[formatIndex];
            var url = buildPickFeaturesUrl(that, x, y, level, longitude, latitude, format.format);

            ++formatIndex;

            if (format.type === 'json') {
                return loadJson(url).then(format.callback).otherwise(doRequest);
            } else if (format.type === 'xml') {
                return loadXML(url).then(format.callback).otherwise(doRequest);
            } else if (format.type === 'text' || format.type === 'html') {
                return loadText(url).then(format.callback).otherwise(doRequest);
            } else {
                return loadWithXhr({
                    url: url,
                    responseType: format.format
                }).then(handleResponse.bind(undefined, format)).otherwise(doRequest);
            }
        }

        return doRequest();
    };

    function buildImageUrl(imageryProvider, x, y, level) {
        degreesScratchComputed = false;
        projectedScratchComputed = false;

        return buildUrl(imageryProvider, imageryProvider._urlParts, function(partFunction) {
            return partFunction(imageryProvider, x, y, level);
        });
    }

    function buildPickFeaturesUrl(imageryProvider, x, y, level, longitude, latitude, format) {
        degreesScratchComputed = false;
        projectedScratchComputed = false;
        ijScratchComputed = false;
        longitudeLatitudeProjectedScratchComputed = false;

        return buildUrl(imageryProvider, imageryProvider._pickFeaturesUrlParts, function(partFunction) {
            return partFunction(imageryProvider, x, y, level, longitude, latitude, format);
        });
    }

    function buildUrl(imageryProvider, parts, partFunctionInvoker) {
        var url = '';

        for (var i = 0; i < parts.length; ++i) {
            var part = parts[i];
            if (typeof part === 'string') {
                url += part;
            } else {
                url += encodeURIComponent(partFunctionInvoker(part));
            }
        }

        var proxy = imageryProvider._proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        return url;
    }

    function urlTemplateToParts(url, tags) {
        if (!defined(url)) {
            return undefined;
        }

        var parts = [];
        var nextIndex = 0;
        var minIndex;
        var minTag;
        var tagList = Object.keys(tags);

        while (nextIndex < url.length) {
            minIndex = Number.MAX_VALUE;
            minTag = undefined;

            for (var i = 0; i < tagList.length; ++i) {
                var thisIndex = url.indexOf(tagList[i], nextIndex);
                if (thisIndex >= 0 && thisIndex < minIndex) {
                    minIndex = thisIndex;
                    minTag = tagList[i];
                }
            }

            if (!defined(minTag)) {
                parts.push(url.substring(nextIndex));
                nextIndex = url.length;
            } else {
                if (nextIndex < minIndex) {
                    parts.push(url.substring(nextIndex, minIndex));
                }
                parts.push(tags[minTag]);
                nextIndex = minIndex + minTag.length;
            }
        }

        return parts;
    }

    function padWithZerosIfNecessary(imageryProvider, key, value) {
        if (imageryProvider &&
            imageryProvider.urlSchemeZeroPadding &&
            imageryProvider.urlSchemeZeroPadding.hasOwnProperty(key) )
        {
            var paddingTemplate = imageryProvider.urlSchemeZeroPadding[key];
            if (typeof paddingTemplate === 'string') {
                var paddingTemplateWidth = paddingTemplate.length;
                if (paddingTemplateWidth > 1) {
                    value = (value.length >= paddingTemplateWidth) ? value : new Array(paddingTemplateWidth - value.toString().length + 1).join('0') + value;
                }
            }
        }
        return value;
    }

    function xTag(imageryProvider, x, y, level) {
        return padWithZerosIfNecessary(imageryProvider, '{x}', x);
    }

    function reverseXTag(imageryProvider, x, y, level) {
        var reverseX = imageryProvider.tilingScheme.getNumberOfXTilesAtLevel(level) - x - 1;
        return padWithZerosIfNecessary(imageryProvider, '{reverseX}', reverseX);
    }

    function yTag(imageryProvider, x, y, level) {
        return padWithZerosIfNecessary(imageryProvider, '{y}', y);
    }

    function reverseYTag(imageryProvider, x, y, level) {
        var reverseY = imageryProvider.tilingScheme.getNumberOfYTilesAtLevel(level) - y - 1;
        return padWithZerosIfNecessary(imageryProvider, '{reverseY}', reverseY);
    }

    function reverseZTag(imageryProvider, x, y, level) {
        var maximumLevel = imageryProvider.maximumLevel;
        var reverseZ = defined(maximumLevel) && level < maximumLevel ? maximumLevel - level - 1 : level;
        return padWithZerosIfNecessary(imageryProvider, '{reverseZ}', reverseZ);
    }

    function zTag(imageryProvider, x, y, level) {
        return padWithZerosIfNecessary(imageryProvider, '{z}', level);
    }

    function sTag(imageryProvider, x, y, level) {
        var index = (x + y + level) % imageryProvider._subdomains.length;
        return imageryProvider._subdomains[index];
    }

    var degreesScratchComputed = false;
    var degreesScratch = new Rectangle();

    function computeDegrees(imageryProvider, x, y, level) {
        if (degreesScratchComputed) {
            return;
        }

        imageryProvider.tilingScheme.tileXYToRectangle(x, y, level, degreesScratch);
        degreesScratch.west = CesiumMath.toDegrees(degreesScratch.west);
        degreesScratch.south = CesiumMath.toDegrees(degreesScratch.south);
        degreesScratch.east = CesiumMath.toDegrees(degreesScratch.east);
        degreesScratch.north = CesiumMath.toDegrees(degreesScratch.north);

        degreesScratchComputed = true;
    }

    function westDegreesTag(imageryProvider, x, y, level) {
        computeDegrees(imageryProvider, x, y, level);
        return degreesScratch.west;
    }

    function southDegreesTag(imageryProvider, x, y, level) {
        computeDegrees(imageryProvider, x, y, level);
        return degreesScratch.south;
    }

    function eastDegreesTag(imageryProvider, x, y, level) {
        computeDegrees(imageryProvider, x, y, level);
        return degreesScratch.east;
    }

    function northDegreesTag(imageryProvider, x, y, level) {
        computeDegrees(imageryProvider, x, y, level);
        return degreesScratch.north;
    }

    var projectedScratchComputed = false;
    var projectedScratch = new Rectangle();

    function computeProjected(imageryProvider, x, y, level) {
        if (projectedScratchComputed) {
            return;
        }

        imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, projectedScratch);

        projectedScratchComputed = true;
    }

    function westProjectedTag(imageryProvider, x, y, level) {
        computeProjected(imageryProvider, x, y, level);
        return projectedScratch.west;
    }

    function southProjectedTag(imageryProvider, x, y, level) {
        computeProjected(imageryProvider, x, y, level);
        return projectedScratch.south;
    }

    function eastProjectedTag(imageryProvider, x, y, level) {
        computeProjected(imageryProvider, x, y, level);
        return projectedScratch.east;
    }

    function northProjectedTag(imageryProvider, x, y, level) {
        computeProjected(imageryProvider, x, y, level);
        return projectedScratch.north;
    }

    function widthTag(imageryProvider, x, y, level) {
        return imageryProvider.tileWidth;
    }

    function heightTag(imageryProvider, x, y, level) {
        return imageryProvider.tileHeight;
    }

    var ijScratchComputed = false;
    var ijScratch = new Cartesian2();

    function iTag(imageryProvider, x, y, level, longitude, latitude, format) {
        computeIJ(imageryProvider, x, y, level, longitude, latitude);
        return ijScratch.x;
    }

    function jTag(imageryProvider, x, y, level, longitude, latitude, format) {
        computeIJ(imageryProvider, x, y, level, longitude, latitude);
        return ijScratch.y;
    }

    function reverseITag(imageryProvider, x, y, level, longitude, latitude, format) {
        computeIJ(imageryProvider, x, y, level, longitude, latitude);
        return imageryProvider.tileWidth - ijScratch.x - 1;
    }

    function reverseJTag(imageryProvider, x, y, level, longitude, latitude, format) {
        computeIJ(imageryProvider, x, y, level, longitude, latitude);
        return imageryProvider.tileHeight - ijScratch.y - 1;
    }

    var rectangleScratch = new Rectangle();

    function computeIJ(imageryProvider, x, y, level, longitude, latitude, format) {
        if (ijScratchComputed) {
            return;
        }

        computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
        var projected = longitudeLatitudeProjectedScratch;

        var rectangle = imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, rectangleScratch);
        ijScratch.x = (imageryProvider.tileWidth * (projected.x - rectangle.west) / rectangle.width) | 0;
        ijScratch.y = (imageryProvider.tileHeight * (rectangle.north - projected.y) / rectangle.height) | 0;
        ijScratchComputed = true;
    }

    function longitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
        return CesiumMath.toDegrees(longitude);
    }

    function latitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
        return CesiumMath.toDegrees(latitude);
    }

    var longitudeLatitudeProjectedScratchComputed = false;
    var longitudeLatitudeProjectedScratch = new Cartesian3();

    function longitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
        computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
        return longitudeLatitudeProjectedScratch.x;
    }

    function latitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
        computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
        return longitudeLatitudeProjectedScratch.y;
    }

    var cartographicScratch = new Cartographic();

    function computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude, format) {
        if (longitudeLatitudeProjectedScratchComputed) {
            return;
        }

        var projected;
        if (imageryProvider.tilingScheme instanceof GeographicTilingScheme) {
            longitudeLatitudeProjectedScratch.x = CesiumMath.toDegrees(longitude);
            longitudeLatitudeProjectedScratch.y = CesiumMath.toDegrees(latitude);
        } else {
            var cartographic = cartographicScratch;
            cartographic.longitude = longitude;
            cartographic.latitude = latitude;
            projected = imageryProvider.tilingScheme.projection.project(cartographic, longitudeLatitudeProjectedScratch);
        }

        longitudeLatitudeProjectedScratchComputed = true;
    }

    function formatTag(imageryProvider, x, y, level, longitude, latitude, format) {
        return format;
    }

    var tags = {
        '{x}': xTag,
        '{y}': yTag,
        '{z}': zTag,
        '{s}': sTag,
        '{reverseX}': reverseXTag,
        '{reverseY}': reverseYTag,
        '{reverseZ}': reverseZTag,
        '{westDegrees}': westDegreesTag,
        '{southDegrees}': southDegreesTag,
        '{eastDegrees}': eastDegreesTag,
        '{northDegrees}': northDegreesTag,
        '{westProjected}': westProjectedTag,
        '{southProjected}': southProjectedTag,
        '{eastProjected}': eastProjectedTag,
        '{northProjected}': northProjectedTag,
        '{width}': widthTag,
        '{height}': heightTag
    };

    var pickFeaturesTags = combine(tags, {
        '{i}' : iTag,
        '{j}' : jTag,
        '{reverseI}' : reverseITag,
        '{reverseJ}' : reverseJTag,
        '{longitudeDegrees}' : longitudeDegreesTag,
        '{latitudeDegrees}' : latitudeDegreesTag,
        '{longitudeProjected}' : longitudeProjectedTag,
        '{latitudeProjected}' : latitudeProjectedTag,
        '{format}' : formatTag
    });

    return UrlTemplateImageryProvider;
});

/*global define*/
define('Scene/createOpenStreetMapImageryProvider',[
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/DeveloperError',
        '../Core/Rectangle',
        '../Core/WebMercatorTilingScheme',
        './UrlTemplateImageryProvider'
    ], function(
        Credit,
        defaultValue,
        DeveloperError,
        Rectangle,
        WebMercatorTilingScheme,
        UrlTemplateImageryProvider) {
    'use strict';

    var trailingSlashRegex = /\/$/;
    var defaultCredit = new Credit('MapQuest, Open Street Map and contributors, CC-BY-SA');

    /**
     * Creates a {@link UrlTemplateImageryProvider} instance that provides tiled imagery hosted by OpenStreetMap
     * or another provider of Slippy tiles.  The default url connects to OpenStreetMap's volunteer-run
     * servers, so you must conform to their
     * {@link http://wiki.openstreetmap.org/wiki/Tile_usage_policy|Tile Usage Policy}.
     *
     * @exports createOpenStreetMapImageryProvider
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.url='https://a.tile.openstreetmap.org'] The OpenStreetMap server url.
     * @param {String} [options.fileExtension='png'] The file extension for images on the server.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer.
     * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {Credit|String} [options.credit='MapQuest, Open Street Map and contributors, CC-BY-SA'] A credit for the data source, which is displayed on the canvas.
     * @returns {UrlTemplateImageryProvider} The imagery provider.
     *
     * @exception {DeveloperError} The rectangle and minimumLevel indicate that there are more than four tiles at the minimum level. Imagery providers with more than four tiles at the minimum level are not supported.
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     *
     *
     * @example
     * var osm = Cesium.createOpenStreetMapImageryProvider({
     *     url : 'https://a.tile.openstreetmap.org/'
     * });
     *
     * @see {@link http://wiki.openstreetmap.org/wiki/Main_Page|OpenStreetMap Wiki}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     */
    function createOpenStreetMapImageryProvider(options) {
        options = defaultValue(options, {});

        var url = defaultValue(options.url, 'https://a.tile.openstreetmap.org/');

        if (!trailingSlashRegex.test(url)) {
            url = url + '/';
        }

        var fileExtension = defaultValue(options.fileExtension, 'png');

        var tilingScheme = new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });

        var tileWidth = 256;
        var tileHeight = 256;

        var minimumLevel = defaultValue(options.minimumLevel, 0);
        var maximumLevel = options.maximumLevel;

        var rectangle = defaultValue(options.rectangle, tilingScheme.rectangle);

        // Check the number of tiles at the minimum level.  If it's more than four,
        // throw an exception, because starting at the higher minimum
        // level will cause too many tiles to be downloaded and rendered.
        var swTile = tilingScheme.positionToTileXY(Rectangle.southwest(rectangle), minimumLevel);
        var neTile = tilingScheme.positionToTileXY(Rectangle.northeast(rectangle), minimumLevel);
        var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
                if (tileCount > 4) {
            throw new DeveloperError('The rectangle and minimumLevel indicate that there are ' + tileCount + ' tiles at the minimum level. Imagery providers with more than four tiles at the minimum level are not supported.');
        }
        
        var credit = defaultValue(options.credit, defaultCredit);
        if (typeof credit === 'string') {
            credit = new Credit(credit);
        }

        var templateUrl = url + "{z}/{x}/{y}." + fileExtension;

        return new UrlTemplateImageryProvider({
            url: templateUrl,
            proxy: options.proxy,
            credit: credit,
            tilingScheme: tilingScheme,
            tileWidth: tileWidth,
            tileHeight: tileHeight,
            minimumLevel: minimumLevel,
            maximumLevel: maximumLevel,
            rectangle: rectangle
        });
    }

    return createOpenStreetMapImageryProvider;
});

/*global define*/
define('Scene/createTangentSpaceDebugPrimitive',[
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/GeometryInstance',
        '../Core/GeometryPipeline',
        '../Core/Matrix4',
        './PerInstanceColorAppearance',
        './Primitive'
    ], function(
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        DeveloperError,
        GeometryInstance,
        GeometryPipeline,
        Matrix4,
        PerInstanceColorAppearance,
        Primitive) {
    'use strict';

    /**
     * Creates a {@link Primitive} to visualize well-known vector vertex attributes:
     * <code>normal</code>, <code>tangent</code>, and <code>bitangent</code>.  Normal
     * is red; tangent is green; and bitangent is blue.  If an attribute is not
     * present, it is not drawn.
     *
     * @exports createTangentSpaceDebugPrimitive
     *
     * @param {Object} options Object with the following properties:
     * @param {Geometry} options.geometry The <code>Geometry</code> instance with the attribute.
     * @param {Number} [options.length=10000.0] The length of each line segment in meters.  This can be negative to point the vector in the opposite direction.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix that transforms to transform the geometry from model to world coordinates.
     * @returns {Primitive} A new <code>Primitive</code> instance with geometry for the vectors.
     *
     * @example
     * scene.primitives.add(Cesium.createTangentSpaceDebugPrimitive({
     *    geometry : instance.geometry,
     *    length : 100000.0,
     *    modelMatrix : instance.modelMatrix
     * }));
     */
    function createTangentSpaceDebugPrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var instances = [];
        var geometry = options.geometry;

                if (!defined(geometry)) {
            throw new DeveloperError('options.geometry is required.');
        }
        
        if (!defined(geometry.attributes) || !defined(geometry.primitiveType)) {
            // to create the debug lines, we need the computed attributes.
            // compute them if they are undefined.
            geometry = geometry.constructor.createGeometry(geometry);
        }

        var attributes = geometry.attributes;
        var modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        var length = defaultValue(options.length, 10000.0);

        if (defined(attributes.normal)) {
            instances.push(new GeometryInstance({
              geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'normal', length),
              attributes : {
                  color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
              },
              modelMatrix : modelMatrix
            }));
        }

        if (defined(attributes.tangent)) {
            instances.push(new GeometryInstance({
              geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'tangent', length),
              attributes : {
                  color : new ColorGeometryInstanceAttribute(0.0, 1.0, 0.0, 1.0)
              },
              modelMatrix : modelMatrix
            }));
        }

        if (defined(attributes.bitangent)) {
            instances.push(new GeometryInstance({
              geometry : GeometryPipeline.createLineSegmentsForVectors(geometry, 'bitangent', length),
              attributes : {
                  color : new ColorGeometryInstanceAttribute(0.0, 0.0, 1.0, 1.0)
              },
              modelMatrix : modelMatrix
            }));
        }

        if (instances.length > 0) {
            return new Primitive({
                asynchronous : false,
                geometryInstances : instances,
                appearance : new PerInstanceColorAppearance({
                    flat : true,
                    translucent : false
                })
            });
        }

        return undefined;
    }

    return createTangentSpaceDebugPrimitive;
});

/*global define*/
define('Scene/createTileMapServiceImageryProvider',[
        '../Core/Cartesian2',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/GeographicTilingScheme',
        '../Core/joinUrls',
        '../Core/loadXML',
        '../Core/Rectangle',
        '../Core/RuntimeError',
        '../Core/TileProviderError',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/when',
        './UrlTemplateImageryProvider'
    ], function(
        Cartesian2,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        GeographicTilingScheme,
        joinUrls,
        loadXML,
        Rectangle,
        RuntimeError,
        TileProviderError,
        WebMercatorTilingScheme,
        when,
        UrlTemplateImageryProvider) {
    'use strict';

    /**
     * Creates a {@link UrlTemplateImageryProvider} instance that provides tiled imagery as generated by
     * {@link http://www.maptiler.org/'>MapTiler</a> / <a href='http://www.klokan.cz/projects/gdal2tiles/|GDDAL2Tiles} etc.
     *
     * @exports createTileMapServiceImageryProvider
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.url='.'] Path to image tiles on server.
     * @param {String} [options.fileExtension='png'] The file extension for images on the server.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
     * @param {Credit|String} [options.credit=''] A credit for the data source, which is displayed on the canvas.
     * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.  Take care when specifying
     *                 this that the number of tiles at the minimum level is small, such as four or less.  A larger number is likely
     *                 to result in rendering problems.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
     * @param {TilingScheme} [options.tilingScheme] The tiling scheme specifying how the ellipsoidal
     * surface is broken into tiles.  If this parameter is not provided, a {@link WebMercatorTilingScheme}
     * is used.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     *                    this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     *                    parameter is specified, the WGS84 ellipsoid is used.
     * @param {Number} [options.tileWidth=256] Pixel width of image tiles.
     * @param {Number} [options.tileHeight=256] Pixel height of image tiles.
     * @param {Boolean} [options.flipXY] Older versions of gdal2tiles.py flipped X and Y values in tilemapresource.xml.
     * Specifying this option will do the same, allowing for loading of these incorrect tilesets.
     * @returns {UrlTemplateImageryProvider} The imagery provider.
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     *
     * @see {@link http://www.maptiler.org/|MapTiler}
     * @see {@link http://www.klokan.cz/projects/gdal2tiles/|GDDAL2Tiles}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     *
     * @example
     * var tms = Cesium.createTileMapServiceImageryProvider({
     *    url : '../images/cesium_maptiler/Cesium_Logo_Color',
     *    fileExtension: 'png',
     *    maximumLevel: 4,
     *    rectangle: new Cesium.Rectangle(
     *        Cesium.Math.toRadians(-120.0),
     *        Cesium.Math.toRadians(20.0),
     *        Cesium.Math.toRadians(-60.0),
     *        Cesium.Math.toRadians(40.0))
     * });
     */
    function createTileMapServiceImageryProvider(options) {
        options = defaultValue(options, {});

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        
        var url = options.url;

        var deferred = when.defer();
        var imageryProvider = new UrlTemplateImageryProvider(deferred.promise);

        var metadataError;

        function metadataSuccess(xml) {
            var tileFormatRegex = /tileformat/i;
            var tileSetRegex = /tileset/i;
            var tileSetsRegex = /tilesets/i;
            var bboxRegex = /boundingbox/i;
            var srsRegex = /srs/i;
            var format, bbox, tilesets, srs;
            var tilesetsList = []; //list of TileSets

            // Allowing options properties (already copied to that) to override XML values

            // Iterate XML Document nodes for properties
            var nodeList = xml.childNodes[0].childNodes;
            for (var i = 0; i < nodeList.length; i++){
                if (tileFormatRegex.test(nodeList.item(i).nodeName)) {
                    format = nodeList.item(i);
                } else if (tileSetsRegex.test(nodeList.item(i).nodeName)) {
                    tilesets = nodeList.item(i); // Node list of TileSets
                    var tileSetNodes = nodeList.item(i).childNodes;
                    // Iterate the nodes to find all TileSets
                    for(var j = 0; j < tileSetNodes.length; j++) {
                        if (tileSetRegex.test(tileSetNodes.item(j).nodeName)) {
                            // Add them to tilesets list
                            tilesetsList.push(tileSetNodes.item(j));
                        }
                    }
                } else if (bboxRegex.test(nodeList.item(i).nodeName)) {
                    bbox = nodeList.item(i);
                } else if (srsRegex.test(nodeList.item(i).nodeName)) {
                    srs = nodeList.item(i).textContent;
                }
            }

            var fileExtension = defaultValue(options.fileExtension, format.getAttribute('extension'));
            var tileWidth = defaultValue(options.tileWidth, parseInt(format.getAttribute('width'), 10));
            var tileHeight = defaultValue(options.tileHeight, parseInt(format.getAttribute('height'), 10));
            var minimumLevel = defaultValue(options.minimumLevel, parseInt(tilesetsList[0].getAttribute('order'), 10));
            var maximumLevel = defaultValue(options.maximumLevel, parseInt(tilesetsList[tilesetsList.length - 1].getAttribute('order'), 10));
            var tilingSchemeName = tilesets.getAttribute('profile');
            var tilingScheme = options.tilingScheme;

            if (!defined(tilingScheme)) {
                if (tilingSchemeName === 'geodetic' || tilingSchemeName === 'global-geodetic') {
                    tilingScheme = new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
                } else if (tilingSchemeName === 'mercator' || tilingSchemeName === 'global-mercator') {
                    tilingScheme = new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
                } else {
                    var message = joinUrls(url, 'tilemapresource.xml') + 'specifies an unsupported profile attribute, ' + tilingSchemeName + '.';
                    metadataError = TileProviderError.handleError(metadataError, imageryProvider, imageryProvider.errorEvent, message, undefined, undefined, undefined, requestMetadata);
                    if(!metadataError.retry) {
                        deferred.reject(new RuntimeError(message));
                    }
                    return;
                }
            }

            // rectangle handling
            var rectangle = Rectangle.clone(options.rectangle);

            if (!defined(rectangle)) {
                var sw;
                var ne;
                var swXY;
                var neXY;

                // In older versions of gdal x and y values were flipped, which is why we check for an option to flip
                // the values here as well. Unfortunately there is no way to autodetect whether flipping is needed.
                var flipXY = defaultValue(options.flipXY, false);
                if (flipXY) {
                    swXY = new Cartesian2(parseFloat(bbox.getAttribute('miny')), parseFloat(bbox.getAttribute('minx')));
                    neXY = new Cartesian2(parseFloat(bbox.getAttribute('maxy')), parseFloat(bbox.getAttribute('maxx')));
                } else {
                    swXY = new Cartesian2(parseFloat(bbox.getAttribute('minx')), parseFloat(bbox.getAttribute('miny')));
                    neXY = new Cartesian2(parseFloat(bbox.getAttribute('maxx')), parseFloat(bbox.getAttribute('maxy')));
                }

                // Determine based on the profile attribute if this tileset was generated by gdal2tiles.py, which
                // uses 'mercator' and 'geodetic' profiles, or by a tool compliant with the TMS standard, which is
                // 'global-mercator' and 'global-geodetic' profiles. In the gdal2Tiles case, X and Y are always in
                // geodetic degrees.
                var isGdal2tiles = tilingSchemeName === 'geodetic' || tilingSchemeName === 'mercator';
                if (tilingScheme instanceof GeographicTilingScheme || isGdal2tiles) {
                    sw = Cartographic.fromDegrees(swXY.x, swXY.y);
                    ne = Cartographic.fromDegrees(neXY.x, neXY.y);
                } else {
                    var projection = tilingScheme.projection;
                    sw = projection.unproject(swXY);
                    ne = projection.unproject(neXY);
                }

                rectangle = new Rectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude);
            }

            // The rectangle must not be outside the bounds allowed by the tiling scheme.
            if (rectangle.west < tilingScheme.rectangle.west) {
                rectangle.west = tilingScheme.rectangle.west;
            }
            if (rectangle.east > tilingScheme.rectangle.east) {
                rectangle.east = tilingScheme.rectangle.east;
            }
            if (rectangle.south < tilingScheme.rectangle.south) {
                rectangle.south = tilingScheme.rectangle.south;
            }
            if (rectangle.north > tilingScheme.rectangle.north) {
                rectangle.north = tilingScheme.rectangle.north;
            }

            // Check the number of tiles at the minimum level.  If it's more than four,
            // try requesting the lower levels anyway, because starting at the higher minimum
            // level will cause too many tiles to be downloaded and rendered.
            var swTile = tilingScheme.positionToTileXY(Rectangle.southwest(rectangle), minimumLevel);
            var neTile = tilingScheme.positionToTileXY(Rectangle.northeast(rectangle), minimumLevel);
            var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
            if (tileCount > 4) {
                minimumLevel = 0;
            }

            var templateUrl = joinUrls(url, '{z}/{x}/{reverseY}.' + fileExtension);

            deferred.resolve({
                url : templateUrl,
                tilingScheme : tilingScheme,
                rectangle : rectangle,
                tileWidth : tileWidth,
                tileHeight : tileHeight,
                minimumLevel : minimumLevel,
                maximumLevel : maximumLevel,
                proxy : options.proxy,
                tileDiscardPolicy : options.tileDiscardPolicy,
                credit: options.credit
            });
        }

        function metadataFailure(error) {
            // Can't load XML, still allow options and defaults
            var fileExtension = defaultValue(options.fileExtension, 'png');
            var tileWidth = defaultValue(options.tileWidth, 256);
            var tileHeight = defaultValue(options.tileHeight, 256);
            var minimumLevel = defaultValue(options.minimumLevel, 0);
            var maximumLevel = options.maximumLevel;
            var tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
            var rectangle = defaultValue(options.rectangle, tilingScheme.rectangle);

            var templateUrl = joinUrls(url, '{z}/{x}/{reverseY}.' + fileExtension);

            deferred.resolve({
                url : templateUrl,
                tilingScheme : tilingScheme,
                rectangle : rectangle,
                tileWidth : tileWidth,
                tileHeight : tileHeight,
                minimumLevel : minimumLevel,
                maximumLevel : maximumLevel,
                proxy : options.proxy,
                tileDiscardPolicy : options.tileDiscardPolicy,
                credit: options.credit
            });
        }

        function requestMetadata() {
            var resourceUrl = joinUrls(url, 'tilemapresource.xml');
            var proxy = options.proxy;
            if (defined(proxy)) {
                resourceUrl = proxy.getURL(resourceUrl);
            }
            // Try to load remaining parameters from XML
            loadXML(resourceUrl).then(metadataSuccess).otherwise(metadataFailure);
        }

        requestMetadata();
        return imageryProvider;
    }

    return createTileMapServiceImageryProvider;
});

/*global define*/
define('Scene/CreditDisplay',[
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError'
    ], function(
        Credit,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError) {
    'use strict';

    function displayTextCredit(credit, container, delimiter) {
        if (!defined(credit.element)) {
            var text = credit.text;
            var link = credit.link;
            var span = document.createElement('span');
            if (credit.hasLink()) {
                var a = document.createElement('a');
                a.textContent = text;
                a.href = link;
                a.target = '_blank';
                span.appendChild(a);
            } else {
                span.textContent = text;
            }
            span.className = 'cesium-credit-text';
            credit.element = span;
        }
        if (container.hasChildNodes()) {
            var del = document.createElement('span');
            del.textContent = delimiter;
            del.className = 'cesium-credit-delimiter';
            container.appendChild(del);
        }
        container.appendChild(credit.element);
    }

    function displayImageCredit(credit, container) {
        if (!defined(credit.element)) {
            var text = credit.text;
            var link = credit.link;
            var span = document.createElement('span');
            var content = document.createElement('img');
            content.src = credit.imageUrl;
            content.style['vertical-align'] = 'bottom';
            if (defined(text)) {
                content.alt = text;
                content.title = text;
            }

            if (credit.hasLink()) {
                var a = document.createElement('a');
                a.appendChild(content);
                a.href = link;
                a.target = '_blank';
                span.appendChild(a);
            } else {
                span.appendChild(content);
            }
            span.className = 'cesium-credit-image';
            credit.element = span;
        }
        container.appendChild(credit.element);
    }

    function contains(credits, credit) {
        var len = credits.length;
        for ( var i = 0; i < len; i++) {
            var existingCredit = credits[i];
            if (Credit.equals(existingCredit, credit)) {
                return true;
            }
        }
        return false;
    }

    function removeCreditDomElement(credit) {
        var element = credit.element;
        if (defined(element)) {
            var container = element.parentNode;
            if (!credit.hasImage()) {
                var delimiter = element.previousSibling;
                if (delimiter === null) {
                    delimiter = element.nextSibling;
                }
                if (delimiter !== null) {
                    container.removeChild(delimiter);
                }
            }
            container.removeChild(element);
        }
    }

    function displayTextCredits(creditDisplay, textCredits) {
        var i;
        var index;
        var credit;
        var displayedTextCredits = creditDisplay._displayedCredits.textCredits;
        for (i = 0; i < textCredits.length; i++) {
            credit = textCredits[i];
            if (defined(credit)) {
                index = displayedTextCredits.indexOf(credit);
                if (index === -1) {
                    displayTextCredit(credit, creditDisplay._textContainer, creditDisplay._delimiter);
                } else {
                    displayedTextCredits.splice(index, 1);
                }
            }
        }
        for (i = 0; i < displayedTextCredits.length; i++) {
            credit = displayedTextCredits[i];
            if (defined(credit)) {
                removeCreditDomElement(credit);
            }
        }

    }

    function displayImageCredits(creditDisplay, imageCredits) {
        var i;
        var index;
        var credit;
        var displayedImageCredits = creditDisplay._displayedCredits.imageCredits;
        for (i = 0; i < imageCredits.length; i++) {
            credit = imageCredits[i];
            if (defined(credit)) {
                index = displayedImageCredits.indexOf(credit);
                if (index === -1) {
                    displayImageCredit(credit, creditDisplay._imageContainer);
                } else {
                    displayedImageCredits.splice(index, 1);
                }
            }
        }
        for (i = 0; i < displayedImageCredits.length; i++) {
            credit = displayedImageCredits[i];
            if (defined(credit)) {
                removeCreditDomElement(credit);
            }
        }
    }

    /**
     * The credit display is responsible for displaying credits on screen.
     *
     * @param {HTMLElement} container The HTML element where credits will be displayed
     * @param {String} [delimiter= ' • '] The string to separate text credits
     *
     * @alias CreditDisplay
     * @constructor
     *
     * @example
     * var creditDisplay = new Cesium.CreditDisplay(creditContainer);
     */
    function CreditDisplay(container, delimiter) {
                if (!defined(container)) {
            throw new DeveloperError('credit container is required');
        }
        
        var imageContainer = document.createElement('span');
        imageContainer.className = 'cesium-credit-imageContainer';
        var textContainer = document.createElement('span');
        textContainer.className = 'cesium-credit-textContainer';
        container.appendChild(imageContainer);
        container.appendChild(textContainer);

        this._delimiter = defaultValue(delimiter, ' • ');
        this._textContainer = textContainer;
        this._imageContainer = imageContainer;
        this._defaultImageCredits = [];
        this._defaultTextCredits = [];

        this._displayedCredits = {
            imageCredits : [],
            textCredits : []
        };
        this._currentFrameCredits = {
            imageCredits : [],
            textCredits : []
        };

        /**
         * The HTML element where credits will be displayed.
         * @type {HTMLElement}
         */
        this.container = container;
    }

    /**
     * Adds a credit to the list of current credits to be displayed in the credit container
     *
     * @param {Credit} credit The credit to display
     */
    CreditDisplay.prototype.addCredit = function(credit) {
                if (!defined(credit)) {
            throw new DeveloperError('credit must be defined');
        }
        
        if (credit.hasImage()) {
            var imageCredits = this._currentFrameCredits.imageCredits;
            if (!contains(this._defaultImageCredits, credit)) {
                imageCredits[credit.id] = credit;
            }
        } else {
            var textCredits = this._currentFrameCredits.textCredits;
            if (!contains(this._defaultTextCredits, credit)) {
                textCredits[credit.id] = credit;
            }
        }
    };

    /**
     * Adds credits that will persist until they are removed
     *
     * @param {Credit} credit The credit to added to defaults
     */
    CreditDisplay.prototype.addDefaultCredit = function(credit) {
                if (!defined(credit)) {
            throw new DeveloperError('credit must be defined');
        }
        
        if (credit.hasImage()) {
            var imageCredits = this._defaultImageCredits;
            if (!contains(imageCredits, credit)) {
                imageCredits.push(credit);
            }
        } else {
            var textCredits = this._defaultTextCredits;
            if (!contains(textCredits, credit)) {
                textCredits.push(credit);
            }
        }
    };

    /**
     * Removes a default credit
     *
     * @param {Credit} credit The credit to be removed from defaults
     */
    CreditDisplay.prototype.removeDefaultCredit = function(credit) {
                if (!defined(credit)) {
            throw new DeveloperError('credit must be defined');
        }
        
        var index;
        if (credit.hasImage()) {
            index = this._defaultImageCredits.indexOf(credit);
            if (index !== -1) {
                this._defaultImageCredits.splice(index, 1);
            }
        } else {
            index = this._defaultTextCredits.indexOf(credit);
            if (index !== -1) {
                this._defaultTextCredits.splice(index, 1);
            }
        }
    };

    /**
     * Resets the credit display to a beginning of frame state, clearing out current credits.
     */
    CreditDisplay.prototype.beginFrame = function() {
        this._currentFrameCredits.imageCredits.length = 0;
        this._currentFrameCredits.textCredits.length = 0;
    };

    /**
     * Sets the credit display to the end of frame state, displaying current credits in the credit container
     */
    CreditDisplay.prototype.endFrame = function() {
        var textCredits = this._defaultTextCredits.concat(this._currentFrameCredits.textCredits);
        var imageCredits = this._defaultImageCredits.concat(this._currentFrameCredits.imageCredits);

        displayTextCredits(this, textCredits);
        displayImageCredits(this, imageCredits);

        this._displayedCredits.textCredits = textCredits;
        this._displayedCredits.imageCredits = imageCredits;
    };

    /**
     * Destroys the resources held by this object.  Destroying an object allows for deterministic
     * release of resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    CreditDisplay.prototype.destroy = function() {
        this.container.removeChild(this._textContainer);
        this.container.removeChild(this._imageContainer);

        return destroyObject(this);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     */
    CreditDisplay.prototype.isDestroyed = function() {
        return false;
    };

    return CreditDisplay;
});

/*global define*/
define('Scene/DebugAppearance',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        './Appearance'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Appearance) {
    'use strict';

    /**
     * Visualizes a vertex attribute by displaying it as a color for debugging.
     * <p>
     * Components for well-known unit-length vectors, i.e., <code>normal</code>,
     * <code>tangent</code>, and <code>bitangent</code>, are scaled and biased
     * from [-1.0, 1.0] to (-1.0, 1.0).
     * </p>
     *
     * @alias DebugAppearance
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.attributeName The name of the attribute to visualize.
     * @param {Boolean} [options.perInstanceAttribute=false] Boolean that determines whether this attribute is a per-instance geometry attribute.
     * @param {String} [options.glslDatatype='vec3'] The GLSL datatype of the attribute.  Supported datatypes are <code>float</code>, <code>vec2</code>, <code>vec3</code>, and <code>vec4</code>.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @exception {DeveloperError} options.glslDatatype must be float, vec2, vec3, or vec4.
     *
     * @example
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : // ...
     *   appearance : new Cesium.DebugAppearance({
     *     attributeName : 'normal'
     *   })
     * });
     */
    function DebugAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var attributeName = options.attributeName;
        var perInstanceAttribute = options.perInstanceAttribute;

                if (!defined(attributeName)) {
            throw new DeveloperError('options.attributeName is required.');
        }
        
        if (!defined(perInstanceAttribute)) {
            perInstanceAttribute = false;
        }

        var glslDatatype = defaultValue(options.glslDatatype, 'vec3');
        var varyingName = 'v_' + attributeName;
        var getColor;

        // Well-known normalized vector attributes in VertexFormat
        if ((attributeName === 'normal') || (attributeName === 'tangent') || (attributeName === 'bitangent')) {
            getColor = 'vec4 getColor() { return vec4((' + varyingName + ' + vec3(1.0)) * 0.5, 1.0); }\n';
        } else {
            // All other attributes, both well-known and custom
            if (attributeName === 'st') {
                glslDatatype = 'vec2';
            }

            switch(glslDatatype) {
                case 'float':
                    getColor = 'vec4 getColor() { return vec4(vec3(' + varyingName + '), 1.0); }\n';
                    break;
                case 'vec2':
                    getColor = 'vec4 getColor() { return vec4(' + varyingName + ', 0.0, 1.0); }\n';
                    break;
                case 'vec3':
                    getColor = 'vec4 getColor() { return vec4(' + varyingName + ', 1.0); }\n';
                    break;
                case 'vec4':
                    getColor = 'vec4 getColor() { return ' + varyingName + '; }\n';
                    break;
                                default:
                    throw new DeveloperError('options.glslDatatype must be float, vec2, vec3, or vec4.');
                            }
        }

        var vs =
            'attribute vec3 position3DHigh;\n' +
            'attribute vec3 position3DLow;\n' +
            'attribute float batchId;\n' +
            (perInstanceAttribute ? '' : 'attribute ' + glslDatatype + ' ' + attributeName + ';\n') +
            'varying ' + glslDatatype + ' ' + varyingName + ';\n' +
            'void main()\n' +
            '{\n' +
            'vec4 p = czm_translateRelativeToEye(position3DHigh, position3DLow);\n' +
            (perInstanceAttribute ?
                varyingName + ' = czm_batchTable_' + attributeName + '(batchId);\n' :
                varyingName + ' = ' +  attributeName + ';\n') +
            'gl_Position = czm_modelViewProjectionRelativeToEye * p;\n' +
            '}';
        var fs =
            'varying ' + glslDatatype + ' ' + varyingName + ';\n' +
            getColor + '\n' +
            'void main()\n' +
            '{\n' +
            'gl_FragColor = getColor();\n' +
            '}';

        /**
         * This property is part of the {@link Appearance} interface, but is not
         * used by {@link DebugAppearance} since a fully custom fragment shader is used.
         *
         * @type Material
         *
         * @default undefined
         */
        this.material = undefined;

        /**
         * When <code>true</code>, the geometry is expected to appear translucent.
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.translucent = defaultValue(options.translucent, false);

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, vs);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, fs);
        this._renderState = Appearance.getDefaultRenderState(false, false, options.renderState);
        this._closed = defaultValue(options.closed, false);

        // Non-derived members

        this._attributeName = attributeName;
        this._glslDatatype = glslDatatype;
    }

    defineProperties(DebugAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof DebugAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.  The full fragment shader
         * source is built procedurally taking into account the {@link DebugAppearance#material}.
         * Use {@link DebugAppearance#getFragmentShaderSource} to get the full source.
         *
         * @memberof DebugAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         *
         * @memberof DebugAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed.
         *
         * @memberof DebugAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        },

        /**
         * The name of the attribute being visualized.
         *
         * @memberof DebugAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        attributeName : {
            get : function() {
                return this._attributeName;
            }
        },

        /**
         * The GLSL datatype of the attribute being visualized.
         *
         * @memberof DebugAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        glslDatatype : {
            get : function() {
                return this._glslDatatype;
            }
        }
    });

    /**
     * Returns the full GLSL fragment shader source, which for {@link DebugAppearance} is just
     * {@link DebugAppearance#fragmentShaderSource}.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    DebugAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link DebugAppearance#translucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    DebugAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    DebugAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    return DebugAppearance;
});

/*global define*/
define('Scene/DebugCameraPrimitive',[
        '../Core/BoundingSphere',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/ComponentDatatype',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/GeometryAttribute',
        '../Core/GeometryAttributes',
        '../Core/GeometryInstance',
        '../Core/Matrix4',
        '../Core/PrimitiveType',
        './PerInstanceColorAppearance',
        './Primitive'
    ], function(
        BoundingSphere,
        Cartesian3,
        Cartesian4,
        Color,
        ColorGeometryInstanceAttribute,
        ComponentDatatype,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        GeometryAttribute,
        GeometryAttributes,
        GeometryInstance,
        Matrix4,
        PrimitiveType,
        PerInstanceColorAppearance,
        Primitive) {
    'use strict';

    /**
     * Draws the outline of the camera's view frustum.
     *
     * @alias DebugCameraPrimitive
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Camera} options.camera The camera.
     * @param {Color} [options.color=Color.CYAN] The color of the debug outline.
     * @param {Boolean} [options.updateOnChange=true] Whether the primitive updates when the underlying camera changes.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}.
     *
     * @example
     * primitives.add(new Cesium.DebugCameraPrimitive({
     *   camera : camera,
     *   color : Cesium.Color.YELLOW
     * }));
     */
    function DebugCameraPrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.camera)) {
            throw new DeveloperError('options.camera is required.');
        }
        
        this._camera = options.camera;
        this._color = defaultValue(options.color, Color.CYAN);
        this._updateOnChange = defaultValue(options.updateOnChange, true);

        /**
         * Determines if this primitive will be shown.
         *
         * @type Boolean
         * @default true
         */
        this.show = defaultValue(options.show, true);

        /**
         * User-defined object returned when the primitive is picked.
         *
         * @type {Object}
         * @default undefined
         *
         * @see Scene#pick
         */
        this.id = options.id;
        this._id = undefined;

        this._outlinePrimitive = undefined;
        this._planesPrimitive = undefined;
    }

    var frustumCornersNDC = new Array(4);
    frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
    frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
    frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
    frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);

    var scratchMatrix = new Matrix4();
    var scratchFrustumCorners = new Array(4);
    for (var i = 0; i < 4; ++i) {
        scratchFrustumCorners[i] = new Cartesian4();
    }

    var scratchColor = new Color();
    var scratchSplits = [1.0, 100000.0];
    /**
     * @private
     */
    DebugCameraPrimitive.prototype.update = function(frameState) {
        if (!this.show) {
            return;
        }

        if (this._updateOnChange) {
            // Recreate the primitive every frame
            this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy();
            this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy();
        }

        if (!defined(this._outlinePrimitive)) {
            var view = this._camera.viewMatrix;
            var projection = this._camera.frustum.projectionMatrix;
            var viewProjection = Matrix4.multiply(projection, view, scratchMatrix);
            var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);

            var frustumSplits = frameState.frustumSplits;
            var numFrustums = frustumSplits.length - 1;
            if (numFrustums <= 0) {
                frustumSplits = scratchSplits; // Use near and far planes if no splits created
                frustumSplits[0] = this._camera.frustum.near;
                frustumSplits[1] = this._camera.frustum.far;
                numFrustums = 1;
            }

            var positions = new Float64Array(3 * 4 * (numFrustums + 1));
            var f;
            for (f = 0; f < numFrustums + 1; ++f) {
                for (var i = 0; i < 4; ++i) {
                    var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);

                    Matrix4.multiplyByVector(inverseViewProjection, corner, corner);
                    Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
                    Cartesian3.subtract(corner, this._camera.positionWC, corner);
                    Cartesian3.normalize(corner, corner);

                    var fac = Cartesian3.dot(this._camera.directionWC, corner);
                    Cartesian3.multiplyByScalar(corner, frustumSplits[f] / fac, corner);
                    Cartesian3.add(corner, this._camera.positionWC, corner);

                    positions[12 * f + i * 3] = corner.x;
                    positions[12 * f + i * 3 + 1] = corner.y;
                    positions[12 * f + i * 3 + 2] = corner.z;
                }
            }

            var boundingSphere = new BoundingSphere.fromVertices(positions);

            var attributes = new GeometryAttributes();
            attributes.position = new GeometryAttribute({
                componentDatatype : ComponentDatatype.DOUBLE,
                componentsPerAttribute : 3,
                values : positions
            });

            var offset, index;

            // Create the outline primitive
            var outlineIndices = new Uint16Array(8 * (2 * numFrustums + 1));
            // Build the far planes
            for (f = 0; f < numFrustums + 1; ++f) {
                offset = f * 8;
                index = f * 4;

                outlineIndices[offset] = index;
                outlineIndices[offset + 1] = index + 1;
                outlineIndices[offset + 2] = index + 1;
                outlineIndices[offset + 3] = index + 2;
                outlineIndices[offset + 4] = index + 2;
                outlineIndices[offset + 5] = index + 3;
                outlineIndices[offset + 6] = index + 3;
                outlineIndices[offset + 7] = index;
            }
            // Build the sides of the frustums
            for (f = 0; f < numFrustums; ++f) {
                offset = (numFrustums + 1 + f) * 8;
                index = f * 4;

                outlineIndices[offset] = index;
                outlineIndices[offset + 1] = index + 4;
                outlineIndices[offset + 2] = index + 1;
                outlineIndices[offset + 3] = index + 5;
                outlineIndices[offset + 4] = index + 2;
                outlineIndices[offset + 5] = index + 6;
                outlineIndices[offset + 6] = index + 3;
                outlineIndices[offset + 7] = index + 7;
            }

            this._outlinePrimitive = new Primitive({
                geometryInstances : new GeometryInstance({
                    geometry : {
                        attributes : attributes,
                        indices : outlineIndices,
                        primitiveType : PrimitiveType.LINES,
                        boundingSphere : boundingSphere
                    },
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(this._color)
                    },
                    id : this.id,
                    pickPrimitive : this
                }),
                appearance : new PerInstanceColorAppearance({
                    translucent : false,
                    flat : true
                }),
                asynchronous : false
            });

            // Create the planes primitive
            var planesIndices = new Uint16Array(6 * (5 * numFrustums + 1));
            // Build the far planes
            for (f = 0; f < numFrustums + 1; ++f) {
                offset = f * 6;
                index = f * 4;

                planesIndices[offset] = index;
                planesIndices[offset + 1] = index + 1;
                planesIndices[offset + 2] = index + 2;
                planesIndices[offset + 3] = index;
                planesIndices[offset + 4] = index + 2;
                planesIndices[offset + 5] = index + 3;
            }
            // Build the sides of the frustums
            for (f = 0; f < numFrustums; ++f) {
                offset = (numFrustums + 1 + 4 * f) * 6;
                index = f * 4;

                planesIndices[offset] = index + 4;
                planesIndices[offset + 1] = index;
                planesIndices[offset + 2] = index + 3;
                planesIndices[offset + 3] = index + 4;
                planesIndices[offset + 4] = index + 3;
                planesIndices[offset + 5] = index + 7;

                planesIndices[offset + 6] = index + 4;
                planesIndices[offset + 7] = index;
                planesIndices[offset + 8] = index + 1;
                planesIndices[offset + 9] = index + 4;
                planesIndices[offset + 10] = index + 1;
                planesIndices[offset + 11] = index + 5;

                planesIndices[offset + 12] = index + 7;
                planesIndices[offset + 13] = index + 3;
                planesIndices[offset + 14] = index + 2;
                planesIndices[offset + 15] = index + 7;
                planesIndices[offset + 16] = index + 2;
                planesIndices[offset + 17] = index + 6;

                planesIndices[offset + 18] = index + 6;
                planesIndices[offset + 19] = index + 2;
                planesIndices[offset + 20] = index + 1;
                planesIndices[offset + 21] = index + 6;
                planesIndices[offset + 22] = index + 1;
                planesIndices[offset + 23] = index + 5;
            }

            this._planesPrimitive = new Primitive({
                geometryInstances : new GeometryInstance({
                    geometry : {
                        attributes : attributes,
                        indices : planesIndices,
                        primitiveType : PrimitiveType.TRIANGLES,
                        boundingSphere : boundingSphere
                    },
                    attributes : {
                        color : ColorGeometryInstanceAttribute.fromColor(Color.fromAlpha(this._color, 0.1, scratchColor))
                    },
                    id : this.id,
                    pickPrimitive : this
                }),
                appearance : new PerInstanceColorAppearance({
                    translucent : true,
                    flat : true
                }),
                asynchronous : false
            });
        }

        this._outlinePrimitive.update(frameState);
        this._planesPrimitive.update(frameState);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see DebugCameraPrimitive#destroy
     */
    DebugCameraPrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * p = p && p.destroy();
     *
     * @see DebugCameraPrimitive#isDestroyed
     */
    DebugCameraPrimitive.prototype.destroy = function() {
        this._outlinePrimitive = this._outlinePrimitive && this._outlinePrimitive.destroy();
        this._planesPrimitive = this._planesPrimitive && this._planesPrimitive.destroy();
        return destroyObject(this);
    };

    return DebugCameraPrimitive;
});

/*global define*/
define('Scene/DebugModelMatrixPrimitive',[
        '../Core/Cartesian3',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/GeometryInstance',
        '../Core/Matrix4',
        '../Core/PolylineGeometry',
        './PolylineColorAppearance',
        './Primitive'
    ], function(
        Cartesian3,
        Color,
        defaultValue,
        defined,
        destroyObject,
        GeometryInstance,
        Matrix4,
        PolylineGeometry,
        PolylineColorAppearance,
        Primitive) {
    'use strict';

    /**
     * Draws the axes of a reference frame defined by a matrix that transforms to world
     * coordinates, i.e., Earth's WGS84 coordinates.  The most prominent example is
     * a primitives <code>modelMatrix</code>.
     * <p>
     * The X axis is red; Y is green; and Z is blue.
     * </p>
     * <p>
     * This is for debugging only; it is not optimized for production use.
     * </p>
     *
     * @alias DebugModelMatrixPrimitive
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Number} [options.length=10000000.0] The length of the axes in meters.
     * @param {Number} [options.width=2.0] The width of the axes in pixels.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 matrix that defines the reference frame, i.e., origin plus axes, to visualize.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
     *
     * @example
     * primitives.add(new Cesium.DebugModelMatrixPrimitive({
     *   modelMatrix : primitive.modelMatrix,  // primitive to debug
     *   length : 100000.0,
     *   width : 10.0
     * }));
     */
    function DebugModelMatrixPrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The length of the axes in meters.
         *
         * @type {Number}
         * @default 10000000.0
         */
        this.length = defaultValue(options.length, 10000000.0);
        this._length = undefined;

        /**
         * The width of the axes in pixels.
         *
         * @type {Number}
         * @default 2.0
         */
        this.width = defaultValue(options.width, 2.0);
        this._width = undefined;

        /**
         * Determines if this primitive will be shown.
         *
         * @type Boolean
         * @default true
         */
        this.show = defaultValue(options.show, true);

        /**
         * The 4x4 matrix that defines the reference frame, i.e., origin plus axes, to visualize.
         *
         * @type {Matrix4}
         * @default {@link Matrix4.IDENTITY}
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = new Matrix4();

        /**
         * User-defined object returned when the primitive is picked.
         *
         * @type {Object}
         * @default undefined
         *
         * @see Scene#pick
         */
        this.id = options.id;
        this._id = undefined;

        this._primitive = undefined;
    }

    /**
     * @private
     */
    DebugModelMatrixPrimitive.prototype.update = function(frameState) {
        if (!this.show) {
            return;
        }

        if (!defined(this._primitive) ||
            (!Matrix4.equals(this._modelMatrix, this.modelMatrix)) ||
            (this._length !== this.length) ||
            (this._width !== this.width) ||
            (this._id !== this.id)) {

            this._modelMatrix = Matrix4.clone(this.modelMatrix, this._modelMatrix);
            this._length = this.length;
            this._width = this.width;
            this._id = this.id;

            if (defined(this._primitive)) {
                this._primitive.destroy();
            }

            // Workaround projecting (0, 0, 0)
            if ((this.modelMatrix[12] === 0.0 && this.modelMatrix[13] === 0.0 && this.modelMatrix[14] === 0.0)) {
                this.modelMatrix[14] = 0.01;
            }

            var x = new GeometryInstance({
                geometry : new PolylineGeometry({
                    positions : [
                        Cartesian3.ZERO,
                        Cartesian3.UNIT_X
                    ],
                    width : this.width,
                    vertexFormat : PolylineColorAppearance.VERTEX_FORMAT,
                    colors : [
                        Color.RED,
                        Color.RED
                    ],
                    followSurface: false
                }),
                modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
                id : this.id,
                pickPrimitive : this
            });
            var y = new GeometryInstance({
                geometry : new PolylineGeometry({
                    positions : [
                        Cartesian3.ZERO,
                        Cartesian3.UNIT_Y
                    ],
                    width : this.width,
                    vertexFormat : PolylineColorAppearance.VERTEX_FORMAT,
                    colors : [
                        Color.GREEN,
                        Color.GREEN
                    ],
                    followSurface: false
                }),
                modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
                id : this.id,
                pickPrimitive : this
            });
            var z = new GeometryInstance({
                geometry : new PolylineGeometry({
                    positions : [
                        Cartesian3.ZERO,
                        Cartesian3.UNIT_Z
                    ],
                    width : this.width,
                    vertexFormat : PolylineColorAppearance.VERTEX_FORMAT,
                    colors : [
                        Color.BLUE,
                        Color.BLUE
                    ],
                    followSurface: false
                }),
                modelMatrix : Matrix4.multiplyByUniformScale(this.modelMatrix, this.length, new Matrix4()),
                id : this.id,
                pickPrimitive : this
            });

            this._primitive = new Primitive({
                geometryInstances : [x, y, z],
                appearance : new PolylineColorAppearance(),
                asynchronous : false
            });
        }

        this._primitive.update(frameState);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see DebugModelMatrixPrimitive#destroy
     */
    DebugModelMatrixPrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * p = p && p.destroy();
     *
     * @see DebugModelMatrixPrimitive#isDestroyed
     */
    DebugModelMatrixPrimitive.prototype.destroy = function() {
        this._primitive = this._primitive && this._primitive.destroy();
        return destroyObject(this);
    };

    return DebugModelMatrixPrimitive;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/DepthPlaneFS',[],function() {
    'use strict';
    return "varying vec4 positionEC;\n\
\n\
void main()\n\
{\n\
    // TODO: make arbitrary ellipsoid\n\
    czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC();\n\
    \n\
    vec3 direction = normalize(positionEC.xyz);\n\
    czm_ray ray = czm_ray(vec3(0.0), direction);\n\
    \n\
    czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid);\n\
    if (!czm_isEmpty(intersection))\n\
    {\n\
        gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n\
    }\n\
    else\n\
    {\n\
        discard;\n\
    }\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/DepthPlaneVS',[],function() {
    'use strict';
    return "attribute vec4 position;\n\
\n\
varying vec4 positionEC;\n\
\n\
void main()\n\
{\n\
    positionEC = czm_modelView * position;\n\
    gl_Position = czm_projection * positionEC;\n\
}\n\
";
});
/*global define*/
define('Scene/DepthPlane',[
        '../Core/BoundingSphere',
        '../Core/Cartesian3',
        '../Core/ComponentDatatype',
        '../Core/defined',
        '../Core/FeatureDetection',
        '../Core/Geometry',
        '../Core/GeometryAttribute',
        '../Core/PrimitiveType',
        '../Renderer/BufferUsage',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/VertexArray',
        '../Shaders/DepthPlaneFS',
        '../Shaders/DepthPlaneVS',
        './DepthFunction',
        './SceneMode'
    ], function(
        BoundingSphere,
        Cartesian3,
        ComponentDatatype,
        defined,
        FeatureDetection,
        Geometry,
        GeometryAttribute,
        PrimitiveType,
        BufferUsage,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        VertexArray,
        DepthPlaneFS,
        DepthPlaneVS,
        DepthFunction,
        SceneMode) {
    'use strict';

    /**
     * @private
     */
    function DepthPlane() {
        this._rs = undefined;
        this._sp = undefined;
        this._va = undefined;
        this._command = undefined;
        this._mode = undefined;
    }

    var depthQuadScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(12) : [];
    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchCartesian3 = new Cartesian3();
    var scratchCartesian4 = new Cartesian3();

    function computeDepthQuad(ellipsoid, frameState) {
        var radii = ellipsoid.radii;
        var p = frameState.camera.positionWC;

        // Find the corresponding position in the scaled space of the ellipsoid.
        var q = Cartesian3.multiplyComponents(ellipsoid.oneOverRadii, p, scratchCartesian1);

        var qMagnitude = Cartesian3.magnitude(q);
        var qUnit = Cartesian3.normalize(q, scratchCartesian2);

        // Determine the east and north directions at q.
        var eUnit = Cartesian3.normalize(Cartesian3.cross(Cartesian3.UNIT_Z, q, scratchCartesian3), scratchCartesian3);
        var nUnit = Cartesian3.normalize(Cartesian3.cross(qUnit, eUnit, scratchCartesian4), scratchCartesian4);

        // Determine the radius of the 'limb' of the ellipsoid.
        var wMagnitude = Math.sqrt(Cartesian3.magnitudeSquared(q) - 1.0);

        // Compute the center and offsets.
        var center = Cartesian3.multiplyByScalar(qUnit, 1.0 / qMagnitude, scratchCartesian1);
        var scalar = wMagnitude / qMagnitude;
        var eastOffset = Cartesian3.multiplyByScalar(eUnit, scalar, scratchCartesian2);
        var northOffset = Cartesian3.multiplyByScalar(nUnit, scalar, scratchCartesian3);

        // A conservative measure for the longitudes would be to use the min/max longitudes of the bounding frustum.
        var upperLeft = Cartesian3.add(center, northOffset, scratchCartesian4);
        Cartesian3.subtract(upperLeft, eastOffset, upperLeft);
        Cartesian3.multiplyComponents(radii, upperLeft, upperLeft);
        Cartesian3.pack(upperLeft, depthQuadScratch, 0);

        var lowerLeft = Cartesian3.subtract(center, northOffset, scratchCartesian4);
        Cartesian3.subtract(lowerLeft, eastOffset, lowerLeft);
        Cartesian3.multiplyComponents(radii, lowerLeft, lowerLeft);
        Cartesian3.pack(lowerLeft, depthQuadScratch, 3);

        var upperRight = Cartesian3.add(center, northOffset, scratchCartesian4);
        Cartesian3.add(upperRight, eastOffset, upperRight);
        Cartesian3.multiplyComponents(radii, upperRight, upperRight);
        Cartesian3.pack(upperRight, depthQuadScratch, 6);

        var lowerRight = Cartesian3.subtract(center, northOffset, scratchCartesian4);
        Cartesian3.add(lowerRight, eastOffset, lowerRight);
        Cartesian3.multiplyComponents(radii, lowerRight, lowerRight);
        Cartesian3.pack(lowerRight, depthQuadScratch, 9);

        return depthQuadScratch;
    }

    DepthPlane.prototype.update = function(frameState) {
        this._mode = frameState.mode;
        if (frameState.mode !== SceneMode.SCENE3D) {
            return;
        }

        var context = frameState.context;
        var ellipsoid = frameState.mapProjection.ellipsoid;

        if (!defined(this._command)) {
            this._rs = RenderState.fromCache({ // Write depth, not color
                cull : {
                    enabled : true
                },
                depthTest : {
                    enabled : true,
                    func : DepthFunction.ALWAYS
                },
                colorMask : {
                    red : false,
                    green : false,
                    blue : false,
                    alpha : false
                }
            });

            this._sp = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : DepthPlaneVS,
                fragmentShaderSource : DepthPlaneFS,
                attributeLocations : {
                    position : 0
                }
            });

            this._command = new DrawCommand({
                renderState : this._rs,
                shaderProgram : this._sp,
                boundingVolume : new BoundingSphere(Cartesian3.ZERO, ellipsoid.maximumRadius),
                pass : Pass.OPAQUE,
                owner : this
            });
        }

        // update depth plane
        var depthQuad = computeDepthQuad(ellipsoid, frameState);

        // depth plane
        if (!defined(this._va)) {
            var geometry = new Geometry({
                attributes : {
                    position : new GeometryAttribute({
                        componentDatatype : ComponentDatatype.FLOAT,
                        componentsPerAttribute : 3,
                        values : depthQuad
                    })
                },
                indices : [0, 1, 2, 2, 1, 3],
                primitiveType : PrimitiveType.TRIANGLES
            });

            this._va = VertexArray.fromGeometry({
                context : context,
                geometry : geometry,
                attributeLocations : {
                    position : 0
                },
                bufferUsage : BufferUsage.DYNAMIC_DRAW
            });

            this._command.vertexArray = this._va;
        } else {
            this._va.getAttribute(0).vertexBuffer.copyFromArrayView(depthQuad);
        }
    };

    DepthPlane.prototype.execute = function(context, passState) {
        if (this._mode === SceneMode.SCENE3D) {
            this._command.execute(context, passState);
        }
    };

    DepthPlane.prototype.isDestroyed = function() {
        return false;
    };

    DepthPlane.prototype.destroy = function() {
        this._sp = this._sp && this._sp.destroy();
        this._va = this._va && this._va.destroy();
    };

    return DepthPlane;
});

/*global define*/
define('Scene/DeviceOrientationCameraController',[
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Quaternion'
    ], function(
        defined,
        destroyObject,
        DeveloperError,
        CesiumMath,
        Matrix3,
        Quaternion) {
    'use strict';

    /**
     * @private
     */
    function DeviceOrientationCameraController(scene) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        this._scene = scene;

        this._lastAlpha = undefined;
        this._lastBeta = undefined;
        this._lastGamma = undefined;

        this._alpha = undefined;
        this._beta = undefined;
        this._gamma = undefined;

        var that = this;

        function callback(e) {
            var alpha = e.alpha;
            if (!defined(alpha)) {
                that._alpha = undefined;
                that._beta = undefined;
                that._gamma = undefined;
                return;
            }

            that._alpha = CesiumMath.toRadians(alpha);
            that._beta = CesiumMath.toRadians(e.beta);
            that._gamma = CesiumMath.toRadians(e.gamma);
        }

        window.addEventListener('deviceorientation', callback, false);

        this._removeListener = function() {
            window.removeEventListener('deviceorientation', callback, false);
        };
    }

    var scratchQuaternion1 = new Quaternion();
    var scratchQuaternion2 = new Quaternion();
    var scratchMatrix3 = new Matrix3();

    function rotate(camera, alpha, beta, gamma) {
        var direction = camera.direction;
        var right = camera.right;
        var up = camera.up;

        var bQuat = Quaternion.fromAxisAngle(direction, beta, scratchQuaternion2);
        var gQuat = Quaternion.fromAxisAngle(right, gamma, scratchQuaternion1);

        var rotQuat = Quaternion.multiply(gQuat, bQuat, gQuat);

        var aQuat = Quaternion.fromAxisAngle(up, alpha, scratchQuaternion2);
        Quaternion.multiply(aQuat, rotQuat, rotQuat);

        var matrix = Matrix3.fromQuaternion(rotQuat, scratchMatrix3);
        Matrix3.multiplyByVector(matrix, right, right);
        Matrix3.multiplyByVector(matrix, up, up);
        Matrix3.multiplyByVector(matrix, direction, direction);
    }

    DeviceOrientationCameraController.prototype.update = function() {
        if (!defined(this._alpha)) {
            return;
        }

        if (!defined(this._lastAlpha)) {
            this._lastAlpha = this._alpha;
            this._lastBeta = this._beta;
            this._lastGamma = this._gamma;
        }

        var a = this._lastAlpha - this._alpha;
        var b = this._lastBeta - this._beta;
        var g = this._lastGamma - this._gamma;

        rotate(this._scene.camera, -a, b, g);

        this._lastAlpha = this._alpha;
        this._lastBeta = this._beta;
        this._lastGamma = this._gamma;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     */
    DeviceOrientationCameraController.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the resources held by this object.  Destroying an object allows for deterministic
     * release of resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    DeviceOrientationCameraController.prototype.destroy = function() {
        this._removeListener();
        return destroyObject(this);
    };

    return DeviceOrientationCameraController;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/EllipsoidFS',[],function() {
    'use strict';
    return "#ifdef WRITE_DEPTH\n\
#ifdef GL_EXT_frag_depth\n\
#extension GL_EXT_frag_depth : enable\n\
#endif\n\
#endif\n\
\n\
uniform vec3 u_radii;\n\
uniform vec3 u_oneOverEllipsoidRadiiSquared;\n\
\n\
varying vec3 v_positionEC;\n\
\n\
vec4 computeEllipsoidColor(czm_ray ray, float intersection, float side)\n\
{\n\
    vec3 positionEC = czm_pointAlongRay(ray, intersection);\n\
    vec3 positionMC = (czm_inverseModelView * vec4(positionEC, 1.0)).xyz;\n\
    vec3 geodeticNormal = normalize(czm_geodeticSurfaceNormal(positionMC, vec3(0.0), u_oneOverEllipsoidRadiiSquared));\n\
    vec3 sphericalNormal = normalize(positionMC / u_radii);\n\
    vec3 normalMC = geodeticNormal * side;              // normalized surface normal (always facing the viewer) in model coordinates\n\
    vec3 normalEC = normalize(czm_normal * normalMC);   // normalized surface normal in eye coordiantes\n\
\n\
    vec2 st = czm_ellipsoidWgs84TextureCoordinates(sphericalNormal);\n\
    vec3 positionToEyeEC = -positionEC;\n\
\n\
    czm_materialInput materialInput;\n\
    materialInput.s = st.s;\n\
    materialInput.st = st;\n\
    materialInput.str = (positionMC + u_radii) / u_radii;\n\
    materialInput.normalEC = normalEC;\n\
    materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(positionMC, normalEC);\n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
    czm_material material = czm_getMaterial(materialInput);\n\
\n\
#ifdef ONLY_SUN_LIGHTING\n\
    return czm_private_phong(normalize(positionToEyeEC), material);\n\
#else\n\
    return czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
\n\
void main()\n\
{\n\
    // PERFORMANCE_TODO: When dynamic branching is available, compute ratio of maximum and minimum radii\n\
    // in the vertex shader. Only when it is larger than some constant, march along the ray.\n\
    // Otherwise perform one intersection test which will be the common case.\n\
    \n\
    // Test if the ray intersects a sphere with the ellipsoid's maximum radius.\n\
    // For very oblate ellipsoids, using the ellipsoid's radii for an intersection test\n\
    // may cause false negatives. This will discard fragments before marching the ray forward.\n\
    float maxRadius = max(u_radii.x, max(u_radii.y, u_radii.z)) * 1.5;\n\
    vec3 direction = normalize(v_positionEC);\n\
    vec3 ellipsoidCenter = czm_modelView[3].xyz;\n\
    \n\
    float t1 = -1.0;\n\
    float t2 = -1.0;\n\
    \n\
    float b = -2.0 * dot(direction, ellipsoidCenter);\n\
    float c = dot(ellipsoidCenter, ellipsoidCenter) - maxRadius * maxRadius;\n\
\n\
    float discriminant = b * b - 4.0 * c;\n\
    if (discriminant >= 0.0) {\n\
        t1 = (-b - sqrt(discriminant)) * 0.5;\n\
        t2 = (-b + sqrt(discriminant)) * 0.5;\n\
    }\n\
    \n\
    if (t1 < 0.0 && t2 < 0.0) {\n\
        discard;\n\
    }\n\
    \n\
    float t = min(t1, t2);\n\
    if (t < 0.0) {\n\
        t = 0.0;\n\
    }\n\
    \n\
    // March ray forward to intersection with larger sphere and find\n\
    // actual intersection point with ellipsoid.\n\
    czm_ellipsoid ellipsoid = czm_ellipsoidNew(ellipsoidCenter, u_radii);\n\
    czm_ray ray = czm_ray(t * direction, direction);\n\
    czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid);\n\
    \n\
    if (czm_isEmpty(intersection))\n\
    {\n\
        discard;\n\
    }\n\
\n\
    // If the viewer is outside, compute outsideFaceColor, with normals facing outward.\n\
    vec4 outsideFaceColor = (intersection.start != 0.0) ? computeEllipsoidColor(ray, intersection.start, 1.0) : vec4(0.0);\n\
\n\
    // If the viewer either is inside or can see inside, compute insideFaceColor, with normals facing inward.\n\
    vec4 insideFaceColor = (outsideFaceColor.a < 1.0) ? computeEllipsoidColor(ray, intersection.stop, -1.0) : vec4(0.0);\n\
\n\
    gl_FragColor = mix(insideFaceColor, outsideFaceColor, outsideFaceColor.a);\n\
    gl_FragColor.a = 1.0 - (1.0 - insideFaceColor.a) * (1.0 - outsideFaceColor.a);\n\
    \n\
#ifdef WRITE_DEPTH\n\
#ifdef GL_EXT_frag_depth\n\
    t = (intersection.start != 0.0) ? intersection.start : intersection.stop;\n\
    vec3 positionEC = czm_pointAlongRay(ray, t);\n\
    vec4 positionCC = czm_projection * vec4(positionEC, 1.0);\n\
    float z = positionCC.z / positionCC.w;\n\
    \n\
    float n = czm_depthRange.near;\n\
    float f = czm_depthRange.far;\n\
    \n\
    gl_FragDepthEXT = (z * (f - n) + f + n) * 0.5;\n\
#endif\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/EllipsoidVS',[],function() {
    'use strict';
    return "attribute vec3 position;\n\
\n\
uniform vec3 u_radii;\n\
\n\
varying vec3 v_positionEC;\n\
\n\
void main() \n\
{\n\
    // In the vertex data, the cube goes from (-1.0, -1.0, -1.0) to (1.0, 1.0, 1.0) in model coordinates.\n\
    // Scale to consider the radii.  We could also do this once on the CPU when using the BoxGeometry,\n\
    // but doing it here allows us to change the radii without rewriting the vertex data, and\n\
    // allows all ellipsoids to reuse the same vertex data.\n\
    vec4 p = vec4(u_radii * position, 1.0);\n\
\n\
    v_positionEC = (czm_modelView * p).xyz;     // position in eye coordinates\n\
    gl_Position = czm_modelViewProjection * p;  // position in clip coordinates\n\
\n\
    // With multi-frustum, when the ellipsoid primitive is positioned on the intersection of two frustums \n\
    // and close to terrain, the terrain (writes depth) in the closest frustum can overwrite part of the \n\
    // ellipsoid (does not write depth) that was rendered in the farther frustum.\n\
    //\n\
    // Here, we clamp the depth in the vertex shader to avoid being overwritten; however, this creates\n\
    // artifacts since some fragments can be alpha blended twice.  This is solved by only rendering\n\
    // the ellipsoid in the closest frustum to the viewer.\n\
    gl_Position.z = clamp(gl_Position.z, czm_depthRange.near, czm_depthRange.far);\n\
}\n\
";
});
/*global define*/
define('Scene/EllipsoidPrimitive',[
        '../Core/BoundingSphere',
        '../Core/BoxGeometry',
        '../Core/Cartesian3',
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Matrix4',
        '../Core/VertexFormat',
        '../Renderer/BufferUsage',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArray',
        '../Shaders/EllipsoidFS',
        '../Shaders/EllipsoidVS',
        './BlendingState',
        './CullFace',
        './Material',
        './SceneMode'
    ], function(
        BoundingSphere,
        BoxGeometry,
        Cartesian3,
        combine,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        Matrix4,
        VertexFormat,
        BufferUsage,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArray,
        EllipsoidFS,
        EllipsoidVS,
        BlendingState,
        CullFace,
        Material,
        SceneMode) {
    'use strict';

    var attributeLocations = {
        position : 0
    };

    /**
     * A renderable ellipsoid.  It can also draw spheres when the three {@link EllipsoidPrimitive#radii} components are equal.
     * <p>
     * This is only supported in 3D.  The ellipsoid is not shown in 2D or Columbus view.
     * </p>
     *
     * @alias EllipsoidPrimitive
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Cartesian3} [options.center=Cartesian3.ZERO] The center of the ellipsoid in the ellipsoid's model coordinates.
     * @param {Cartesian3} [options.radii] The radius of the ellipsoid along the <code>x</code>, <code>y</code>, and <code>z</code> axes in the ellipsoid's model coordinates.
     * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Material} [options.material=Material.ColorType] The surface appearance of the primitive.
     * @param {Object} [options.id] A user-defined object to return when the instance is picked with {@link Scene#pick}
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     *
     * @private
     */
    function EllipsoidPrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * The center of the ellipsoid in the ellipsoid's model coordinates.
         * <p>
         * The default is {@link Cartesian3.ZERO}.
         * </p>
         *
         * @type {Cartesian3}
         * @default {@link Cartesian3.ZERO}
         *
         * @see EllipsoidPrimitive#modelMatrix
         */
        this.center = Cartesian3.clone(defaultValue(options.center, Cartesian3.ZERO));
        this._center = new Cartesian3();

        /**
         * The radius of the ellipsoid along the <code>x</code>, <code>y</code>, and <code>z</code> axes in the ellipsoid's model coordinates.
         * When these are the same, the ellipsoid is a sphere.
         * <p>
         * The default is <code>undefined</code>.  The ellipsoid is not drawn until a radii is provided.
         * </p>
         *
         * @type {Cartesian3}
         * @default undefined
         *
         *
         * @example
         * // A sphere with a radius of 2.0
         * e.radii = new Cesium.Cartesian3(2.0, 2.0, 2.0);
         * 
         * @see EllipsoidPrimitive#modelMatrix
         */
        this.radii = Cartesian3.clone(options.radii);
        this._radii = new Cartesian3();

        this._oneOverEllipsoidRadiiSquared = new Cartesian3();
        this._boundingSphere = new BoundingSphere();

        /**
         * The 4x4 transformation matrix that transforms the ellipsoid from model to world coordinates.
         * When this is the identity matrix, the ellipsoid is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
         * Local reference frames can be used by providing a different transformation matrix, like that returned
         * by {@link Transforms.eastNorthUpToFixedFrame}.
         *
         * @type {Matrix4}
         * @default {@link Matrix4.IDENTITY}
         *
         * @example
         * var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
         * e.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
         */
        this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
        this._modelMatrix = new Matrix4();
        this._computedModelMatrix = new Matrix4();

        /**
         * Determines if the ellipsoid primitive will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = defaultValue(options.show, true);

        /**
         * The surface appearance of the ellipsoid.  This can be one of several built-in {@link Material} objects or a custom material, scripted with
         * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
         * <p>
         * The default material is <code>Material.ColorType</code>.
         * </p>
         *
         * @type {Material}
         * @default Material.fromType(Material.ColorType)
         *
         *
         * @example
         * // 1. Change the color of the default material to yellow
         * e.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
         *
         * // 2. Change material to horizontal stripes
         * e.material = Cesium.Material.fromType(Cesium.Material.StripeType);
         * 
         * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
         */
        this.material = defaultValue(options.material, Material.fromType(Material.ColorType));
        this._material = undefined;
        this._translucent = undefined;

        /**
         * User-defined object returned when the ellipsoid is picked.
         *
         * @type Object
         *
         * @default undefined
         *
         * @see Scene#pick
         */
        this.id = options.id;
        this._id = undefined;

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * @private
         */
        this.onlySunLighting = defaultValue(options.onlySunLighting, false);
        this._onlySunLighting = false;

        /**
         * @private
         */
        this._depthTestEnabled = defaultValue(options.depthTestEnabled, true);

        this._sp = undefined;
        this._rs = undefined;
        this._va = undefined;

        this._pickSP = undefined;
        this._pickId = undefined;

        this._colorCommand = new DrawCommand({
            owner : defaultValue(options._owner, this)
        });
        this._pickCommand = new DrawCommand({
            owner : defaultValue(options._owner, this)
        });

        var that = this;
        this._uniforms = {
            u_radii : function() {
                return that.radii;
            },
            u_oneOverEllipsoidRadiiSquared : function() {
                return that._oneOverEllipsoidRadiiSquared;
            }
        };

        this._pickUniforms = {
            czm_pickColor : function() {
                return that._pickId.color;
            }
        };
    }

    function getVertexArray(context) {
        var vertexArray = context.cache.ellipsoidPrimitive_vertexArray;

        if (defined(vertexArray)) {
            return vertexArray;
        }

        var geometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
            dimensions : new Cartesian3(2.0, 2.0, 2.0),
            vertexFormat : VertexFormat.POSITION_ONLY
        }));

        vertexArray = VertexArray.fromGeometry({
            context : context,
            geometry : geometry,
            attributeLocations : attributeLocations,
            bufferUsage : BufferUsage.STATIC_DRAW,
            interleave : true
        });

        context.cache.ellipsoidPrimitive_vertexArray = vertexArray;
        return vertexArray;
    }

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} this.material must be defined.
     */
    EllipsoidPrimitive.prototype.update = function(frameState) {
        if (!this.show ||
            (frameState.mode !== SceneMode.SCENE3D) ||
            (!defined(this.center)) ||
            (!defined(this.radii))) {
            return;
        }

                if (!defined(this.material)) {
            throw new DeveloperError('this.material must be defined.');
        }
        
        var context = frameState.context;
        var translucent = this.material.isTranslucent();
        var translucencyChanged = this._translucent !== translucent;

        if (!defined(this._rs) || translucencyChanged) {
            this._translucent = translucent;

            // If this render state is ever updated to use a non-default
            // depth range, the hard-coded values in EllipsoidVS.glsl need
            // to be updated as well.

            this._rs = RenderState.fromCache({
                // Cull front faces - not back faces - so the ellipsoid doesn't
                // disappear if the viewer enters the bounding box.
                cull : {
                    enabled : true,
                    face : CullFace.FRONT
                },
                depthTest : {
                    enabled : this._depthTestEnabled
                },
                // Only write depth when EXT_frag_depth is supported since the depth for
                // the bounding box is wrong; it is not the true depth of the ray casted ellipsoid.
                depthMask : !translucent && context.fragmentDepth,
                blending : translucent ? BlendingState.ALPHA_BLEND : undefined
            });
        }

        if (!defined(this._va)) {
            this._va = getVertexArray(context);
        }

        var boundingSphereDirty = false;

        var radii = this.radii;
        if (!Cartesian3.equals(this._radii, radii)) {
            Cartesian3.clone(radii, this._radii);

            var r = this._oneOverEllipsoidRadiiSquared;
            r.x = 1.0 / (radii.x * radii.x);
            r.y = 1.0 / (radii.y * radii.y);
            r.z = 1.0 / (radii.z * radii.z);

            boundingSphereDirty = true;
        }

        if (!Matrix4.equals(this.modelMatrix, this._modelMatrix) || !Cartesian3.equals(this.center, this._center)) {
            Matrix4.clone(this.modelMatrix, this._modelMatrix);
            Cartesian3.clone(this.center, this._center);

            // Translate model coordinates used for rendering such that the origin is the center of the ellipsoid.
            Matrix4.multiplyByTranslation(this.modelMatrix, this.center, this._computedModelMatrix);
            boundingSphereDirty = true;
        }

        if (boundingSphereDirty) {
            Cartesian3.clone(Cartesian3.ZERO, this._boundingSphere.center);
            this._boundingSphere.radius = Cartesian3.maximumComponent(radii);
            BoundingSphere.transform(this._boundingSphere, this._computedModelMatrix, this._boundingSphere);
        }

        var materialChanged = this._material !== this.material;
        this._material = this.material;
        this._material.update(context);

        var lightingChanged = this.onlySunLighting !== this._onlySunLighting;
        this._onlySunLighting = this.onlySunLighting;

        var colorCommand = this._colorCommand;
        var fs;

        // Recompile shader when material, lighting, or translucency changes
        if (materialChanged || lightingChanged || translucencyChanged) {
            fs = new ShaderSource({
                sources : [this.material.shaderSource, EllipsoidFS]
            });
            if (this.onlySunLighting) {
                fs.defines.push('ONLY_SUN_LIGHTING');
            }
            if (!translucent && context.fragmentDepth) {
                fs.defines.push('WRITE_DEPTH');
            }

            this._sp = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : this._sp,
                vertexShaderSource : EllipsoidVS,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });

            colorCommand.vertexArray = this._va;
            colorCommand.renderState = this._rs;
            colorCommand.shaderProgram = this._sp;
            colorCommand.uniformMap = combine(this._uniforms, this.material._uniforms);
            colorCommand.executeInClosestFrustum = translucent;
        }

        var commandList = frameState.commandList;
        var passes = frameState.passes;

        if (passes.render) {
            colorCommand.boundingVolume = this._boundingSphere;
            colorCommand.debugShowBoundingVolume = this.debugShowBoundingVolume;
            colorCommand.modelMatrix = this._computedModelMatrix;
            colorCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;

            commandList.push(colorCommand);
        }

        if (passes.pick) {
            var pickCommand = this._pickCommand;

            if (!defined(this._pickId) || (this._id !== this.id)) {
                this._id = this.id;
                this._pickId = this._pickId && this._pickId.destroy();
                this._pickId = context.createPickId({
                    primitive : this,
                    id : this.id
                });
            }

            // Recompile shader when material changes
            if (materialChanged || lightingChanged || !defined(this._pickSP)) {
                fs = new ShaderSource({
                    sources : [this.material.shaderSource, EllipsoidFS],
                    pickColorQualifier : 'uniform'
                });
                if (this.onlySunLighting) {
                    fs.defines.push('ONLY_SUN_LIGHTING');
                }
                if (!translucent && context.fragmentDepth) {
                    fs.defines.push('WRITE_DEPTH');
                }

                this._pickSP = ShaderProgram.replaceCache({
                    context : context,
                    shaderProgram : this._pickSP,
                    vertexShaderSource : EllipsoidVS,
                    fragmentShaderSource : fs,
                    attributeLocations : attributeLocations
                });

                pickCommand.vertexArray = this._va;
                pickCommand.renderState = this._rs;
                pickCommand.shaderProgram = this._pickSP;
                pickCommand.uniformMap = combine(combine(this._uniforms, this._pickUniforms), this.material._uniforms);
                pickCommand.executeInClosestFrustum = translucent;
            }

            pickCommand.boundingVolume = this._boundingSphere;
            pickCommand.modelMatrix = this._computedModelMatrix;
            pickCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE;

            commandList.push(pickCommand);
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see EllipsoidPrimitive#destroy
     */
    EllipsoidPrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * e = e && e.destroy();
     * 
     * @see EllipsoidPrimitive#isDestroyed
     */
    EllipsoidPrimitive.prototype.destroy = function() {
        this._sp = this._sp && this._sp.destroy();
        this._pickSP = this._pickSP && this._pickSP.destroy();
        this._pickId = this._pickId && this._pickId.destroy();
        return destroyObject(this);
    };

    return EllipsoidPrimitive;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/EllipsoidSurfaceAppearanceFS',[],function() {
    'use strict';
    return "varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec2 v_st;\n\
\n\
void main()\n\
{\n\
    czm_materialInput materialInput;\n\
    \n\
    vec3 normalEC = normalize(czm_normal3D * czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0)));\n\
#ifdef FACE_FORWARD\n\
    normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC);\n\
#endif\n\
    \n\
    materialInput.s = v_st.s;\n\
    materialInput.st = v_st;\n\
    materialInput.str = vec3(v_st, 0.0);\n\
    \n\
    // Convert tangent space material normal to eye space\n\
    materialInput.normalEC = normalEC;\n\
    materialInput.tangentToEyeMatrix = czm_eastNorthUpToEyeCoordinates(v_positionMC, materialInput.normalEC);\n\
    \n\
    // Convert view vector to world space\n\
    vec3 positionToEyeEC = -v_positionEC; \n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
\n\
    czm_material material = czm_getMaterial(materialInput);\n\
    \n\
#ifdef FLAT    \n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
#else\n\
    gl_FragColor = czm_phong(normalize(positionToEyeEC), material);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/EllipsoidSurfaceAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
\n\
varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec2 v_st;\n\
\n\
void main() \n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_positionMC = position3DHigh + position3DLow;           // position in model coordinates\n\
    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;     // position in eye coordinates\n\
    v_st = st;\n\
    \n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/EllipsoidSurfaceAppearance',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/VertexFormat',
        '../Shaders/Appearances/EllipsoidSurfaceAppearanceFS',
        '../Shaders/Appearances/EllipsoidSurfaceAppearanceVS',
        './Appearance',
        './Material'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        VertexFormat,
        EllipsoidSurfaceAppearanceFS,
        EllipsoidSurfaceAppearanceVS,
        Appearance,
        Material) {
    'use strict';

    /**
     * An appearance for geometry on the surface of the ellipsoid like {@link PolygonGeometry}
     * and {@link RectangleGeometry}, which supports all materials like {@link MaterialAppearance}
     * with {@link MaterialAppearance.MaterialSupport.ALL}.  However, this appearance requires
     * fewer vertex attributes since the fragment shader can procedurally compute <code>normal</code>,
     * <code>tangent</code>, and <code>bitangent</code>.
     *
     * @alias EllipsoidSurfaceAppearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.flat=false] When <code>true</code>, flat shading is used in the fragment shader, which means lighting is not taking into account.
     * @param {Boolean} [options.faceForward=options.aboveGround] When <code>true</code>, the fragment shader flips the surface normal as needed to ensure that the normal faces the viewer to avoid dark spots.  This is useful when both sides of a geometry should be shaded like {@link WallGeometry}.
     * @param {Boolean} [options.translucent=true] When <code>true</code>, the geometry is expected to appear translucent so {@link EllipsoidSurfaceAppearance#renderState} has alpha blending enabled.
     * @param {Boolean} [options.aboveGround=false] When <code>true</code>, the geometry is expected to be on the ellipsoid's surface - not at a constant height above it - so {@link EllipsoidSurfaceAppearance#renderState} has backface culling enabled.
     * @param {Material} [options.material=Material.ColorType] The material used to determine the fragment color.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     *
     * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
     *
     * @example
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *     geometry : new Cesium.PolygonGeometry({
     *       vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT,
     *       // ...
     *     })
     *   }),
     *   appearance : new Cesium.EllipsoidSurfaceAppearance({
     *     material : Cesium.Material.fromType('Stripe')
     *   })
     * });
     */
    function EllipsoidSurfaceAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var translucent = defaultValue(options.translucent, true);
        var aboveGround = defaultValue(options.aboveGround, false);

        /**
         * The material used to determine the fragment color.  Unlike other {@link EllipsoidSurfaceAppearance}
         * properties, this is not read-only, so an appearance's material can change on the fly.
         *
         * @type Material
         *
         * @default {@link Material.ColorType}
         *
         * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
         */
        this.material = (defined(options.material)) ? options.material : Material.fromType(Material.ColorType);

        /**
         * When <code>true</code>, the geometry is expected to appear translucent.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.translucent = defaultValue(options.translucent, true);

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, EllipsoidSurfaceAppearanceVS);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, EllipsoidSurfaceAppearanceFS);
        this._renderState = Appearance.getDefaultRenderState(translucent, !aboveGround, options.renderState);
        this._closed = false;

        // Non-derived members

        this._flat = defaultValue(options.flat, false);
        this._faceForward = defaultValue(options.faceForward, aboveGround);
        this._aboveGround = aboveGround;
    }

    defineProperties(EllipsoidSurfaceAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.  The full fragment shader
         * source is built procedurally taking into account {@link EllipsoidSurfaceAppearance#material},
         * {@link EllipsoidSurfaceAppearance#flat}, and {@link EllipsoidSurfaceAppearance#faceForward}.
         * Use {@link EllipsoidSurfaceAppearance#getFragmentShaderSource} to get the full source.
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         * <p>
         * The render state can be explicitly defined when constructing a {@link EllipsoidSurfaceAppearance}
         * instance, or it is set implicitly via {@link EllipsoidSurfaceAppearance#translucent}
         * and {@link EllipsoidSurfaceAppearance#aboveGround}.
         * </p>
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed so
         * {@link EllipsoidSurfaceAppearance#renderState} has backface culling enabled.
         * If the viewer enters the geometry, it will not be visible.
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return this._closed;
            }
        },

        /**
         * The {@link VertexFormat} that this appearance instance is compatible with.
         * A geometry can have more vertex attributes and still be compatible - at a
         * potential performance cost - but it can't have less.
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type VertexFormat
         * @readonly
         *
         * @default {@link EllipsoidSurfaceAppearance.VERTEX_FORMAT}
         */
        vertexFormat : {
            get : function() {
                return EllipsoidSurfaceAppearance.VERTEX_FORMAT;
            }
        },

        /**
         * When <code>true</code>, flat shading is used in the fragment shader,
         * which means lighting is not taking into account.
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        flat : {
            get : function() {
                return this._flat;
            }
        },

        /**
         * When <code>true</code>, the fragment shader flips the surface normal
         * as needed to ensure that the normal faces the viewer to avoid
         * dark spots.  This is useful when both sides of a geometry should be
         * shaded like {@link WallGeometry}.
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        faceForward : {
            get : function() {
                return this._faceForward;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be on the ellipsoid's
         * surface - not at a constant height above it - so {@link EllipsoidSurfaceAppearance#renderState}
         * has backface culling enabled.
         *
         *
         * @memberof EllipsoidSurfaceAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        aboveGround : {
            get : function() {
                return this._aboveGround;
            }
        }
    });

    /**
     * The {@link VertexFormat} that all {@link EllipsoidSurfaceAppearance} instances
     * are compatible with, which requires only <code>position</code> and <code>st</code>
     * attributes.  Other attributes are procedurally computed in the fragment shader.
     *
     * @type VertexFormat
     *
     * @constant
     */
    EllipsoidSurfaceAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_ST;

    /**
     * Procedurally creates the full GLSL fragment shader source.  For {@link EllipsoidSurfaceAppearance},
     * this is derived from {@link EllipsoidSurfaceAppearance#fragmentShaderSource}, {@link EllipsoidSurfaceAppearance#flat},
     * and {@link EllipsoidSurfaceAppearance#faceForward}.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    EllipsoidSurfaceAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link EllipsoidSurfaceAppearance#translucent} and {@link Material#isTranslucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    EllipsoidSurfaceAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    EllipsoidSurfaceAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    return EllipsoidSurfaceAppearance;
});

/*global define*/
define('Scene/Fog',[
        '../Core/Cartesian3',
        '../Core/defined',
        '../Core/Math',
        './SceneMode'
    ], function(
        Cartesian3,
        defined,
        CesiumMath,
        SceneMode) {
    'use strict';

    /**
     * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional
     * performance improvements by rendering less geometry and dispatching less terrain requests.
     *
     * @alias Fog
     * @constructor
     */
    function Fog() {
        /**
         * <code>true</code> if fog is enabled, <code>false</code> otherwise.
         * @type {Boolean}
         * @default true
         */
        this.enabled = true;

        /**
         * A scalar that determines the density of the fog. Terrain that is in full fog are culled.
         * The density of the fog increases as this number approaches 1.0 and becomes less dense as it approaches zero.
         * The more dense the fog is, the more aggressively the terrain is culled. For example, if the camera is a height of
         * 1000.0m above the ellipsoid, increasing the value to 3.0e-3 will cause many tiles close to the viewer be culled.
         * Decreasing the value will push the fog further from the viewer, but decrease performance as more of the terrain is rendered.
         * @type {Number}
         * @default 2.0e-4
         */
        this.density = 2.0e-4;
        /**
         * A factor used to increase the screen space error of terrain tiles when they are partially in fog. The effect is to reduce
         * the number of terrain tiles requested for rendering. If set to zero, the feature will be disabled. If the value is increased
         * for mountainous regions, less tiles will need to be requested, but the terrain meshes near the horizon may be a noticeably
         * lower resolution. If the value is increased in a relatively flat area, there will be little noticeable change on the horizon.
         * @type {Number}
         * @default 2.0
         */
        this.screenSpaceErrorFactor = 2.0;
    }

    // These values were found by sampling the density at certain views and finding at what point culled tiles impacted the view at the horizon.
    var heightsTable = [359.393, 800.749, 1275.6501, 2151.1192, 3141.7763, 4777.5198, 6281.2493, 12364.307, 15900.765, 49889.0549, 78026.8259, 99260.7344, 120036.3873, 151011.0158, 156091.1953, 203849.3112, 274866.9803, 319916.3149, 493552.0528, 628733.5874];
    var densityTable = [2.0e-5, 2.0e-4, 1.0e-4, 7.0e-5, 5.0e-5, 4.0e-5, 3.0e-5, 1.9e-5, 1.0e-5, 8.5e-6, 6.2e-6, 5.8e-6, 5.3e-6, 5.2e-6, 5.1e-6, 4.2e-6, 4.0e-6, 3.4e-6, 2.6e-6, 2.2e-6];

    // Scale densities by 1e6 to bring lowest value to ~1. Prevents divide by zero.
    for (var i = 0; i < densityTable.length; ++i) {
        densityTable[i] *= 1.0e6;
    }
    // Change range to [0, 1].
    var tableStartDensity = densityTable[1];
    var tableEndDensity = densityTable[densityTable.length - 1];
    for (var j = 0; j < densityTable.length; ++j) {
        densityTable[j] = (densityTable[j] - tableEndDensity) / (tableStartDensity - tableEndDensity);
    }

    var tableLastIndex = 0;

    function findInterval(height) {
        var heights = heightsTable;
        var length = heights.length;

        if (height < heights[0]) {
            tableLastIndex = 0;
            return tableLastIndex;
        } else if (height > heights[length - 1]) {
            tableLastIndex = length - 2;
            return tableLastIndex;
        }

        // Take advantage of temporal coherence by checking current, next and previous intervals
        // for containment of time.
        if (height >= heights[tableLastIndex]) {
            if (tableLastIndex + 1 < length && height < heights[tableLastIndex + 1]) {
                return tableLastIndex;
            } else if (tableLastIndex + 2 < length && height < heights[tableLastIndex + 2]) {
                ++tableLastIndex;
                return tableLastIndex;
            }
        } else if (tableLastIndex - 1 >= 0 && height >= heights[tableLastIndex - 1]) {
            --tableLastIndex;
            return tableLastIndex;
        }

        // The above failed so do a linear search.
        var i;
        for (i = 0; i < length - 2; ++i) {
            if (height >= heights[i] && height < heights[i + 1]) {
                break;
            }
        }

        tableLastIndex = i;
        return tableLastIndex;
    }

    var scratchPositionNormal = new Cartesian3();

    Fog.prototype.update = function(frameState) {
        var enabled = frameState.fog.enabled = this.enabled;
        if (!enabled) {
            return;
        }

        var camera = frameState.camera;
        var positionCartographic = camera.positionCartographic;

        // Turn off fog in space.
        if (!defined(positionCartographic) || positionCartographic.height > 800000.0 || frameState.mode !== SceneMode.SCENE3D) {
            frameState.fog.enabled = false;
            return;
        }

        var height = positionCartographic.height;
        var i = findInterval(height);
        var t = CesiumMath.clamp((height - heightsTable[i]) / (heightsTable[i + 1] - heightsTable[i]), 0.0, 1.0);
        var density = CesiumMath.lerp(densityTable[i], densityTable[i + 1], t);

        // Again, scale value to be in the range of densityTable (prevents divide by zero) and change to new range.
        var startDensity = this.density * 1.0e6;
        var endDensity = (startDensity / tableStartDensity) * tableEndDensity;
        density = (density * (startDensity - endDensity)) * 1.0e-6;

        // Fade fog in as the camera tilts toward the horizon.
        var positionNormal = Cartesian3.normalize(camera.positionWC, scratchPositionNormal);
        var dot = CesiumMath.clamp(Cartesian3.dot(camera.directionWC, positionNormal), 0.0, 1.0);
        density *= 1.0 - dot;

        frameState.fog.density = density;
        frameState.fog.sse = this.screenSpaceErrorFactor;
    };

    return Fog;
});

/*global define*/
define('Scene/FrameRateMonitor',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/getTimestamp',
        '../Core/TimeConstants'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Event,
        getTimestamp,
        TimeConstants) {
    'use strict';

    /**
     * Monitors the frame rate (frames per second) in a {@link Scene} and raises an event if the frame rate is
     * lower than a threshold.  Later, if the frame rate returns to the required level, a separate event is raised.
     * To avoid creating multiple FrameRateMonitors for a single {@link Scene}, use {@link FrameRateMonitor.fromScene}
     * instead of constructing an instance explicitly.
     *
     * @alias FrameRateMonitor
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Scene} options.scene The Scene instance for which to monitor performance.
     * @param {Number} [options.samplingWindow=5.0] The length of the sliding window over which to compute the average frame rate, in seconds.
     * @param {Number} [options.quietPeriod=2.0] The length of time to wait at startup and each time the page becomes visible (i.e. when the user
     *        switches back to the tab) before starting to measure performance, in seconds.
     * @param {Number} [options.warmupPeriod=5.0] The length of the warmup period, in seconds.  During the warmup period, a separate
     *        (usually lower) frame rate is required.
     * @param {Number} [options.minimumFrameRateDuringWarmup=4] The minimum frames-per-second that are required for acceptable performance during
     *        the warmup period.  If the frame rate averages less than this during any samplingWindow during the warmupPeriod, the
     *        lowFrameRate event will be raised and the page will redirect to the redirectOnLowFrameRateUrl, if any.
     * @param {Number} [options.minimumFrameRateAfterWarmup=8] The minimum frames-per-second that are required for acceptable performance after
     *        the end of the warmup period.  If the frame rate averages less than this during any samplingWindow after the warmupPeriod, the
     *        lowFrameRate event will be raised and the page will redirect to the redirectOnLowFrameRateUrl, if any.
     */
    function FrameRateMonitor(options) {
                if (!defined(options) || !defined(options.scene)) {
            throw new DeveloperError('options.scene is required.');
        }
        
        this._scene = options.scene;

        /**
         * Gets or sets the length of the sliding window over which to compute the average frame rate, in seconds.
         * @type {Number}
         */
        this.samplingWindow = defaultValue(options.samplingWindow, FrameRateMonitor.defaultSettings.samplingWindow);

        /**
         * Gets or sets the length of time to wait at startup and each time the page becomes visible (i.e. when the user
         * switches back to the tab) before starting to measure performance, in seconds.
         * @type {Number}
         */
        this.quietPeriod = defaultValue(options.quietPeriod, FrameRateMonitor.defaultSettings.quietPeriod);

        /**
         * Gets or sets the length of the warmup period, in seconds.  During the warmup period, a separate
         * (usually lower) frame rate is required.
         * @type {Number}
         */
        this.warmupPeriod = defaultValue(options.warmupPeriod, FrameRateMonitor.defaultSettings.warmupPeriod);

        /**
         * Gets or sets the minimum frames-per-second that are required for acceptable performance during
         * the warmup period.  If the frame rate averages less than this during any <code>samplingWindow</code> during the <code>warmupPeriod</code>, the
         * <code>lowFrameRate</code> event will be raised and the page will redirect to the <code>redirectOnLowFrameRateUrl</code>, if any.
         * @type {Number}
         */
        this.minimumFrameRateDuringWarmup = defaultValue(options.minimumFrameRateDuringWarmup, FrameRateMonitor.defaultSettings.minimumFrameRateDuringWarmup);

        /**
         * Gets or sets the minimum frames-per-second that are required for acceptable performance after
         * the end of the warmup period.  If the frame rate averages less than this during any <code>samplingWindow</code> after the <code>warmupPeriod</code>, the
         * <code>lowFrameRate</code> event will be raised and the page will redirect to the <code>redirectOnLowFrameRateUrl</code>, if any.
         * @type {Number}
         */
        this.minimumFrameRateAfterWarmup = defaultValue(options.minimumFrameRateAfterWarmup, FrameRateMonitor.defaultSettings.minimumFrameRateAfterWarmup);

        this._lowFrameRate = new Event();
        this._nominalFrameRate = new Event();

        this._frameTimes = [];
        this._needsQuietPeriod = true;
        this._quietPeriodEndTime = 0.0;
        this._warmupPeriodEndTime = 0.0;
        this._frameRateIsLow = false;
        this._lastFramesPerSecond = undefined;
        this._pauseCount = 0;

        var that = this;
        this._preRenderRemoveListener = this._scene.preRender.addEventListener(function(scene, time) {
            update(that, time);
        });

        this._hiddenPropertyName = (document.hidden !== undefined) ? 'hidden' :
                                   (document.mozHidden !== undefined) ? 'mozHidden' :
                                   (document.msHidden !== undefined) ? 'msHidden' :
                                   (document.webkitHidden !== undefined) ? 'webkitHidden' : undefined;

        var visibilityChangeEventName = (document.hidden !== undefined) ? 'visibilitychange' :
            (document.mozHidden !== undefined) ? 'mozvisibilitychange' :
            (document.msHidden !== undefined) ? 'msvisibilitychange' :
            (document.webkitHidden !== undefined) ? 'webkitvisibilitychange' : undefined;

        function visibilityChangeListener() {
            visibilityChanged(that);
        }

        this._visibilityChangeRemoveListener = undefined;
        if (defined(visibilityChangeEventName)) {
            document.addEventListener(visibilityChangeEventName, visibilityChangeListener, false);

            this._visibilityChangeRemoveListener = function() {
                document.removeEventListener(visibilityChangeEventName, visibilityChangeListener, false);
            };
        }
    }

    /**
     * The default frame rate monitoring settings.  These settings are used when {@link FrameRateMonitor.fromScene}
     * needs to create a new frame rate monitor, and for any settings that are not passed to the
     * {@link FrameRateMonitor} constructor.
     *
     * @memberof FrameRateMonitor
     * @type {Object}
     */
    FrameRateMonitor.defaultSettings = {
        samplingWindow : 5.0,
        quietPeriod : 2.0,
        warmupPeriod : 5.0,
        minimumFrameRateDuringWarmup : 4,
        minimumFrameRateAfterWarmup : 8
    };

    /**
     * Gets the {@link FrameRateMonitor} for a given scene.  If the scene does not yet have
     * a {@link FrameRateMonitor}, one is created with the {@link FrameRateMonitor.defaultSettings}.
     *
     * @param {Scene} scene The scene for which to get the {@link FrameRateMonitor}.
     * @returns {FrameRateMonitor} The scene's {@link FrameRateMonitor}.
     */
    FrameRateMonitor.fromScene = function(scene) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        if (!defined(scene._frameRateMonitor) || scene._frameRateMonitor.isDestroyed()) {
            scene._frameRateMonitor = new FrameRateMonitor({
                scene : scene
            });
        }

        return scene._frameRateMonitor;
    };

    defineProperties(FrameRateMonitor.prototype, {
        /**
         * Gets the {@link Scene} instance for which to monitor performance.
         * @memberof FrameRateMonitor.prototype
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets the event that is raised when a low frame rate is detected.  The function will be passed
         * the {@link Scene} instance as its first parameter and the average number of frames per second
         * over the sampling window as its second parameter.
         * @memberof FrameRateMonitor.prototype
         * @type {Event}
         */
        lowFrameRate : {
            get : function() {
                return this._lowFrameRate;
            }
        },

        /**
         * Gets the event that is raised when the frame rate returns to a normal level after having been low.
         * The function will be passed the {@link Scene} instance as its first parameter and the average
         * number of frames per second over the sampling window as its second parameter.
         * @memberof FrameRateMonitor.prototype
         * @type {Event}
         */
        nominalFrameRate : {
            get : function() {
                return this._nominalFrameRate;
            }
        },

        /**
         * Gets the most recently computed average frames-per-second over the last <code>samplingWindow</code>.
         * This property may be undefined if the frame rate has not been computed.
         * @memberof FrameRateMonitor.prototype
         * @type {Number}
         */
        lastFramesPerSecond : {
            get : function() {
                return this._lastFramesPerSecond;
            }
        }
    });

    /**
     * Pauses monitoring of the frame rate.  To resume monitoring, {@link FrameRateMonitor#unpause}
     * must be called once for each time this function is called.
     * @memberof FrameRateMonitor
     */
    FrameRateMonitor.prototype.pause = function() {
        ++this._pauseCount;
        if (this._pauseCount === 1) {
            this._frameTimes.length = 0;
            this._lastFramesPerSecond = undefined;
        }
    };

    /**
     * Resumes monitoring of the frame rate.  If {@link FrameRateMonitor#pause} was called
     * multiple times, this function must be called the same number of times in order to
     * actually resume monitoring.
     * @memberof FrameRateMonitor
     */
    FrameRateMonitor.prototype.unpause = function() {
        --this._pauseCount;
        if (this._pauseCount <= 0) {
            this._pauseCount = 0;
            this._needsQuietPeriod = true;
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @memberof FrameRateMonitor
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see FrameRateMonitor#destroy
     */
    FrameRateMonitor.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Unsubscribes this instance from all events it is listening to.
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @memberof FrameRateMonitor
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see FrameRateMonitor#isDestroyed
     */
    FrameRateMonitor.prototype.destroy = function() {
        this._preRenderRemoveListener();

        if (defined(this._visibilityChangeRemoveListener)) {
            this._visibilityChangeRemoveListener();
        }

        return destroyObject(this);
    };

    function update(monitor, time) {
        if (monitor._pauseCount > 0) {
            return;
        }

        var timeStamp = getTimestamp();

        if (monitor._needsQuietPeriod) {
            monitor._needsQuietPeriod = false;
            monitor._frameTimes.length = 0;
            monitor._quietPeriodEndTime = timeStamp + (monitor.quietPeriod / TimeConstants.SECONDS_PER_MILLISECOND);
            monitor._warmupPeriodEndTime = monitor._quietPeriodEndTime + ((monitor.warmupPeriod + monitor.samplingWindow) / TimeConstants.SECONDS_PER_MILLISECOND);
        } else if (timeStamp >= monitor._quietPeriodEndTime) {
            monitor._frameTimes.push(timeStamp);

            var beginningOfWindow = timeStamp - (monitor.samplingWindow / TimeConstants.SECONDS_PER_MILLISECOND);

            if (monitor._frameTimes.length >= 2 && monitor._frameTimes[0] <= beginningOfWindow) {
                while (monitor._frameTimes.length >= 2 && monitor._frameTimes[1] < beginningOfWindow) {
                    monitor._frameTimes.shift();
                }

                var averageTimeBetweenFrames = (timeStamp - monitor._frameTimes[0]) / (monitor._frameTimes.length - 1);

                monitor._lastFramesPerSecond = 1000.0 / averageTimeBetweenFrames;

                var maximumFrameTime = 1000.0 / (timeStamp > monitor._warmupPeriodEndTime ? monitor.minimumFrameRateAfterWarmup : monitor.minimumFrameRateDuringWarmup);
                if (averageTimeBetweenFrames > maximumFrameTime) {
                    if (!monitor._frameRateIsLow) {
                        monitor._frameRateIsLow = true;
                        monitor._needsQuietPeriod = true;
                        monitor.lowFrameRate.raiseEvent(monitor.scene, monitor._lastFramesPerSecond);
                    }
                } else if (monitor._frameRateIsLow) {
                    monitor._frameRateIsLow = false;
                    monitor._needsQuietPeriod = true;
                    monitor.nominalFrameRate.raiseEvent(monitor.scene, monitor._lastFramesPerSecond);
                }
            }
        }
    }

    function visibilityChanged(monitor) {
        if (document[monitor._hiddenPropertyName]) {
            monitor.pause();
        } else {
            monitor.unpause();
        }
    }

    return FrameRateMonitor;
});

/*global define*/
define('Scene/FrameState',[
        './SceneMode'
    ], function(
        SceneMode) {
    'use strict';

    /**
     * State information about the current frame.  An instance of this class
     * is provided to update functions.
     *
     * @param {Context} context The rendering context.
     * @param {CreditDisplay} creditDisplay Handles adding and removing credits from an HTML element
     *
     * @alias FrameState
     * @constructor
     *
     * @private
     */
    function FrameState(context, creditDisplay) {
        /**
         * The rendering context.
         * @type {Context}
         */
        this.context = context;

        /**
         * An array of rendering commands.
         * @type {DrawCommand[]}
         */
        this.commandList = [];

        /**
         * An array of shadow maps.
         * @type {ShadowMap[]}
         */
        this.shadowMaps = [];

        /**
         * The current mode of the scene.
         * @type {SceneMode}
         * @default {@link SceneMode.SCENE3D}
         */
        this.mode = SceneMode.SCENE3D;

        /**
         * The current morph transition time between 2D/Columbus View and 3D,
         * with 0.0 being 2D or Columbus View and 1.0 being 3D.
         *
         * @type {Number}
         */
        this.morphTime = SceneMode.getMorphTime(SceneMode.SCENE3D);

        /**
         * The current frame number.
         *
         * @type {Number}
         * @default 0
         */
        this.frameNumber = 0;

        /**
         * The scene's current time.
         *
         * @type {JulianDate}
         * @default undefined
         */
        this.time = undefined;

        /**
         * The map projection to use in 2D and Columbus View modes.
         *
         * @type {MapProjection}
         * @default undefined
         */
        this.mapProjection = undefined;

        /**
         * The current camera.
         * @type {Camera}
         * @default undefined
         */
        this.camera = undefined;

        /**
         * The culling volume.
         * @type {CullingVolume}
         * @default undefined
         */
        this.cullingVolume = undefined;

        /**
         * The current occluder.
         * @type {Occluder}
         * @default undefined
         */
        this.occluder = undefined;

        /**
         * The maximum screen-space error used to drive level-of-detail refinement.  Higher
         * values will provide better performance but lower visual quality.
         *
         * @type {Number}
         * @default 2
         */
        this.maximumScreenSpaceError = undefined;

        this.passes = {
            /**
             * <code>true</code> if the primitive should update for a render pass, <code>false</code> otherwise.
             * @type {Boolean}
             * @default false
             */
            render : false,
            /**
             * <code>true</code> if the primitive should update for a picking pass, <code>false</code> otherwise.
             * @type {Boolean}
             * @default false
             */
            pick : false,

            /**
             * <code>true</code> if the primitive should update for a depth only pass, <code>false</code> otherwise.
             * @type {Boolean}
             * @default false
             */
            depth : false
        };

        /**
        * The credit display.
        * @type {CreditDisplay}
        */
        this.creditDisplay = creditDisplay;

        /**
         * An array of functions to be called at the end of the frame.  This array
         * will be cleared after each frame.
         * <p>
         * This allows queueing up events in <code>update</code> functions and
         * firing them at a time when the subscribers are free to change the
         * scene state, e.g., manipulate the camera, instead of firing events
         * directly in <code>update</code> functions.
         * </p>
         *
         * @type {FrameState~AfterRenderCallback[]}
         *
         * @example
         * frameState.afterRender.push(function() {
         *   // take some action, raise an event, etc.
         * });
         */
        this.afterRender = [];

        /**
         * Gets whether or not to optimized for 3D only.
         * @type {Boolean}
         * @default false
         */
        this.scene3DOnly = false;

        this.fog = {
            /**
             * <code>true</code> if fog is enabled, <code>false</code> otherwise.
             * @type {Boolean}
             * @default false
             */
            enabled : false,
            /**
             * A positive number used to mix the color and fog color based on camera distance.
             * @type {Number}
             * @default undefined
             */
            density : undefined,
            /**
             * A scalar used to modify the screen space error of geometry partially in fog.
             * @type {Number}
             * @default undefined
             */
            sse : undefined
        };

        /**
        * A scalar used to exaggerate the terrain.
        * @type {Number}
        */
        this.terrainExaggeration = 1.0;

        this.shadowHints = {
            /**
             * Whether there are any active shadow maps this frame.
             * @type {Boolean}
             */
            shadowsEnabled : true,

            /**
             * All shadow maps that are enabled this frame.
             */
            shadowMaps : [],

            /**
             * Shadow maps that originate from light sources. Does not include shadow maps that are used for
             * analytical purposes. Only these shadow maps will be used to generate receive shadows shaders.
             */
            lightShadowMaps : [],

            /**
             * The near plane of the scene's frustum commands. Used for fitting cascaded shadow maps.
             * @type {Number}
             */
            nearPlane : 1.0,

            /**
             * The far plane of the scene's frustum commands. Used for fitting cascaded shadow maps.
             * @type {Number}
             */
            farPlane : 5000.0,

            /**
             * The size of the bounding volume that is closest to the camera. This is used to place more shadow detail near the object.
             * @type {Number}
             */
            closestObjectSize : 1000.0,

            /**
             * The time when a shadow map was last dirty
             * @type {Number}
             */
            lastDirtyTime : 0,

            /**
             * Whether the shadows maps are out of view this frame
             * @type {Boolean}
             */
            outOfView : true
        };

        /**
        * The position of the splitter to use when rendering imagery layers on either side of a splitter.
        * This value should be between 0.0 and 1.0 with 0 being the far left of the viewport and 1 being the far right of the viewport.
        * @type {Number}
        * @default 0.0
        */
        this.imagerySplitPosition = 0.0;

        /**
         * Distances to the near and far planes of the camera frustums
         * @type {Number[]}
         * @default []
         */
        this.frustumSplits = [];
    }

    /**
     * A function that will be called at the end of the frame.
     * @callback FrameState~AfterRenderCallback
     */

    return FrameState;
});

/*global define*/
define('Scene/FrustumCommands',[
        '../Core/defaultValue',
        '../Renderer/Pass'
    ], function(
        defaultValue,
        Pass) {
    'use strict';

    /**
     * Defines a list of commands whose geometry are bound by near and far distances from the camera.
     * @alias FrustumCommands
     * @constructor
     *
     * @param {Number} [near=0.0] The lower bound or closest distance from the camera.
     * @param {Number} [far=0.0] The upper bound or farthest distance from the camera.
     *
     * @private
     */
    function FrustumCommands(near, far) {
        this.near = defaultValue(near, 0.0);
        this.far = defaultValue(far, 0.0);

        var numPasses = Pass.NUMBER_OF_PASSES;
        var commands = new Array(numPasses);
        var indices = new Array(numPasses);

        for (var i = 0; i < numPasses; ++i) {
            commands[i] = [];
            indices[i] = 0;
        }

        this.commands = commands;
        this.indices = indices;
    }

    return FrustumCommands;
});

/**
 * @license
 * Copyright (c) 2011 NVIDIA Corporation. All rights reserved.
 *
 * TO  THE MAXIMUM  EXTENT PERMITTED  BY APPLICABLE  LAW, THIS SOFTWARE  IS PROVIDED
 * *AS IS*  AND NVIDIA AND  ITS SUPPLIERS DISCLAIM  ALL WARRANTIES,  EITHER  EXPRESS
 * OR IMPLIED, INCLUDING, BUT NOT LIMITED  TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL  NVIDIA 
 * OR ITS SUPPLIERS BE  LIABLE  FOR  ANY  DIRECT, SPECIAL,  INCIDENTAL,  INDIRECT,  OR  
 * CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION,  DAMAGES FOR LOSS 
 * OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY 
 * OTHER PECUNIARY LOSS) ARISING OUT OF THE  USE OF OR INABILITY  TO USE THIS SOFTWARE, 
 * EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/FXAA',[],function() {
    'use strict';
    return "/**\n\
 * @license\n\
 * Copyright (c) 2011 NVIDIA Corporation. All rights reserved.\n\
 *\n\
 * TO  THE MAXIMUM  EXTENT PERMITTED  BY APPLICABLE  LAW, THIS SOFTWARE  IS PROVIDED\n\
 * *AS IS*  AND NVIDIA AND  ITS SUPPLIERS DISCLAIM  ALL WARRANTIES,  EITHER  EXPRESS\n\
 * OR IMPLIED, INCLUDING, BUT NOT LIMITED  TO, NONINFRINGEMENT,IMPLIED WARRANTIES OF\n\
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL  NVIDIA \n\
 * OR ITS SUPPLIERS BE  LIABLE  FOR  ANY  DIRECT, SPECIAL,  INCIDENTAL,  INDIRECT,  OR  \n\
 * CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION,  DAMAGES FOR LOSS \n\
 * OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR ANY \n\
 * OTHER PECUNIARY LOSS) ARISING OUT OF THE  USE OF OR INABILITY  TO USE THIS SOFTWARE, \n\
 * EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n\
 */\n\
\n\
/*\n\
FXAA_PRESET - Choose compile-in knob preset 0-5.\n\
------------------------------------------------------------------------------\n\
FXAA_EDGE_THRESHOLD - The minimum amount of local contrast required \n\
                      to apply algorithm.\n\
                      1.0/3.0  - too little\n\
                      1.0/4.0  - good start\n\
                      1.0/8.0  - applies to more edges\n\
                      1.0/16.0 - overkill\n\
------------------------------------------------------------------------------\n\
FXAA_EDGE_THRESHOLD_MIN - Trims the algorithm from processing darks.\n\
                          Perf optimization.\n\
                          1.0/32.0 - visible limit (smaller isn't visible)\n\
                          1.0/16.0 - good compromise\n\
                          1.0/12.0 - upper limit (seeing artifacts)\n\
------------------------------------------------------------------------------\n\
FXAA_SEARCH_STEPS - Maximum number of search steps for end of span.\n\
------------------------------------------------------------------------------\n\
FXAA_SEARCH_THRESHOLD - Controls when to stop searching.\n\
                        1.0/4.0 - seems to be the best quality wise\n\
------------------------------------------------------------------------------\n\
FXAA_SUBPIX_TRIM - Controls sub-pixel aliasing removal.\n\
                   1.0/2.0 - low removal\n\
                   1.0/3.0 - medium removal\n\
                   1.0/4.0 - default removal\n\
                   1.0/8.0 - high removal\n\
                   0.0 - complete removal\n\
------------------------------------------------------------------------------\n\
FXAA_SUBPIX_CAP - Insures fine detail is not completely removed.\n\
                  This is important for the transition of sub-pixel detail,\n\
                  like fences and wires.\n\
                  3.0/4.0 - default (medium amount of filtering)\n\
                  7.0/8.0 - high amount of filtering\n\
                  1.0 - no capping of sub-pixel aliasing removal\n\
*/\n\
\n\
#ifndef FXAA_PRESET\n\
    #define FXAA_PRESET 3\n\
#endif\n\
#if (FXAA_PRESET == 3)\n\
    #define FXAA_EDGE_THRESHOLD      (1.0/8.0)\n\
    #define FXAA_EDGE_THRESHOLD_MIN  (1.0/24.0)\n\
    #define FXAA_SEARCH_STEPS        16\n\
    #define FXAA_SEARCH_THRESHOLD    (1.0/4.0)\n\
    #define FXAA_SUBPIX_CAP          (3.0/4.0)\n\
    #define FXAA_SUBPIX_TRIM         (1.0/4.0)\n\
#endif\n\
#if (FXAA_PRESET == 4)\n\
    #define FXAA_EDGE_THRESHOLD      (1.0/8.0)\n\
    #define FXAA_EDGE_THRESHOLD_MIN  (1.0/24.0)\n\
    #define FXAA_SEARCH_STEPS        24\n\
    #define FXAA_SEARCH_THRESHOLD    (1.0/4.0)\n\
    #define FXAA_SUBPIX_CAP          (3.0/4.0)\n\
    #define FXAA_SUBPIX_TRIM         (1.0/4.0)\n\
#endif\n\
#if (FXAA_PRESET == 5)\n\
    #define FXAA_EDGE_THRESHOLD      (1.0/8.0)\n\
    #define FXAA_EDGE_THRESHOLD_MIN  (1.0/24.0)\n\
    #define FXAA_SEARCH_STEPS        32\n\
    #define FXAA_SEARCH_THRESHOLD    (1.0/4.0)\n\
    #define FXAA_SUBPIX_CAP          (3.0/4.0)\n\
    #define FXAA_SUBPIX_TRIM         (1.0/4.0)\n\
#endif\n\
\n\
#define FXAA_SUBPIX_TRIM_SCALE (1.0/(1.0 - FXAA_SUBPIX_TRIM))\n\
\n\
// Return the luma, the estimation of luminance from rgb inputs.\n\
// This approximates luma using one FMA instruction,\n\
// skipping normalization and tossing out blue.\n\
// FxaaLuma() will range 0.0 to 2.963210702.\n\
float FxaaLuma(vec3 rgb) {\n\
    return rgb.y * (0.587/0.299) + rgb.x;\n\
}\n\
\n\
vec3 FxaaLerp3(vec3 a, vec3 b, float amountOfA) {\n\
    return (vec3(-amountOfA) * b) + ((a * vec3(amountOfA)) + b);\n\
}\n\
\n\
vec4 FxaaTexOff(sampler2D tex, vec2 pos, ivec2 off, vec2 rcpFrame) {\n\
    float x = pos.x + float(off.x) * rcpFrame.x;\n\
    float y = pos.y + float(off.y) * rcpFrame.y;\n\
    return texture2D(tex, vec2(x, y));\n\
}\n\
\n\
// pos is the output of FxaaVertexShader interpolated across screen.\n\
// xy -> actual texture position {0.0 to 1.0}\n\
// rcpFrame should be a uniform equal to  {1.0/frameWidth, 1.0/frameHeight}\n\
vec3 FxaaPixelShader(vec2 pos, sampler2D tex, vec2 rcpFrame)\n\
{\n\
    vec3 rgbN = FxaaTexOff(tex, pos.xy, ivec2( 0,-1), rcpFrame).xyz;\n\
    vec3 rgbW = FxaaTexOff(tex, pos.xy, ivec2(-1, 0), rcpFrame).xyz;\n\
    vec3 rgbM = FxaaTexOff(tex, pos.xy, ivec2( 0, 0), rcpFrame).xyz;\n\
    vec3 rgbE = FxaaTexOff(tex, pos.xy, ivec2( 1, 0), rcpFrame).xyz;\n\
    vec3 rgbS = FxaaTexOff(tex, pos.xy, ivec2( 0, 1), rcpFrame).xyz;\n\
    \n\
    float lumaN = FxaaLuma(rgbN);\n\
    float lumaW = FxaaLuma(rgbW);\n\
    float lumaM = FxaaLuma(rgbM);\n\
    float lumaE = FxaaLuma(rgbE);\n\
    float lumaS = FxaaLuma(rgbS);\n\
    float rangeMin = min(lumaM, min(min(lumaN, lumaW), min(lumaS, lumaE)));\n\
    float rangeMax = max(lumaM, max(max(lumaN, lumaW), max(lumaS, lumaE)));\n\
    \n\
    float range = rangeMax - rangeMin;\n\
    if(range < max(FXAA_EDGE_THRESHOLD_MIN, rangeMax * FXAA_EDGE_THRESHOLD))\n\
    {\n\
        return rgbM;\n\
    }\n\
    \n\
    vec3 rgbL = rgbN + rgbW + rgbM + rgbE + rgbS;\n\
    \n\
    float lumaL = (lumaN + lumaW + lumaE + lumaS) * 0.25;\n\
    float rangeL = abs(lumaL - lumaM);\n\
    float blendL = max(0.0, (rangeL / range) - FXAA_SUBPIX_TRIM) * FXAA_SUBPIX_TRIM_SCALE; \n\
    blendL = min(FXAA_SUBPIX_CAP, blendL);\n\
    \n\
    vec3 rgbNW = FxaaTexOff(tex, pos.xy, ivec2(-1,-1), rcpFrame).xyz;\n\
    vec3 rgbNE = FxaaTexOff(tex, pos.xy, ivec2( 1,-1), rcpFrame).xyz;\n\
    vec3 rgbSW = FxaaTexOff(tex, pos.xy, ivec2(-1, 1), rcpFrame).xyz;\n\
    vec3 rgbSE = FxaaTexOff(tex, pos.xy, ivec2( 1, 1), rcpFrame).xyz;\n\
    rgbL += (rgbNW + rgbNE + rgbSW + rgbSE);\n\
    rgbL *= vec3(1.0/9.0);\n\
    \n\
    float lumaNW = FxaaLuma(rgbNW);\n\
    float lumaNE = FxaaLuma(rgbNE);\n\
    float lumaSW = FxaaLuma(rgbSW);\n\
    float lumaSE = FxaaLuma(rgbSE);\n\
    \n\
    float edgeVert = \n\
        abs((0.25 * lumaNW) + (-0.5 * lumaN) + (0.25 * lumaNE)) +\n\
        abs((0.50 * lumaW ) + (-1.0 * lumaM) + (0.50 * lumaE )) +\n\
        abs((0.25 * lumaSW) + (-0.5 * lumaS) + (0.25 * lumaSE));\n\
    float edgeHorz = \n\
        abs((0.25 * lumaNW) + (-0.5 * lumaW) + (0.25 * lumaSW)) +\n\
        abs((0.50 * lumaN ) + (-1.0 * lumaM) + (0.50 * lumaS )) +\n\
        abs((0.25 * lumaNE) + (-0.5 * lumaE) + (0.25 * lumaSE));\n\
        \n\
    bool horzSpan = edgeHorz >= edgeVert;\n\
    float lengthSign = horzSpan ? -rcpFrame.y : -rcpFrame.x;\n\
    \n\
    if(!horzSpan)\n\
    {\n\
        lumaN = lumaW;\n\
        lumaS = lumaE;\n\
    }\n\
    \n\
    float gradientN = abs(lumaN - lumaM);\n\
    float gradientS = abs(lumaS - lumaM);\n\
    lumaN = (lumaN + lumaM) * 0.5;\n\
    lumaS = (lumaS + lumaM) * 0.5;\n\
    \n\
    if (gradientN < gradientS)\n\
    {\n\
        lumaN = lumaS;\n\
        lumaN = lumaS;\n\
        gradientN = gradientS;\n\
        lengthSign *= -1.0;\n\
    }\n\
    \n\
    vec2 posN;\n\
    posN.x = pos.x + (horzSpan ? 0.0 : lengthSign * 0.5);\n\
    posN.y = pos.y + (horzSpan ? lengthSign * 0.5 : 0.0);\n\
    \n\
    gradientN *= FXAA_SEARCH_THRESHOLD;\n\
    \n\
    vec2 posP = posN;\n\
    vec2 offNP = horzSpan ? vec2(rcpFrame.x, 0.0) : vec2(0.0, rcpFrame.y); \n\
    float lumaEndN = lumaN;\n\
    float lumaEndP = lumaN;\n\
    bool doneN = false;\n\
    bool doneP = false;\n\
    posN += offNP * vec2(-1.0, -1.0);\n\
    posP += offNP * vec2( 1.0,  1.0);\n\
    \n\
    for(int i = 0; i < FXAA_SEARCH_STEPS; i++) {\n\
        if(!doneN)\n\
        {\n\
            lumaEndN = FxaaLuma(texture2D(tex, posN.xy).xyz);\n\
        }\n\
        if(!doneP)\n\
        {\n\
            lumaEndP = FxaaLuma(texture2D(tex, posP.xy).xyz);\n\
        }\n\
        \n\
        doneN = doneN || (abs(lumaEndN - lumaN) >= gradientN);\n\
        doneP = doneP || (abs(lumaEndP - lumaN) >= gradientN);\n\
        \n\
        if(doneN && doneP)\n\
        {\n\
            break;\n\
        }\n\
        if(!doneN)\n\
        {\n\
            posN -= offNP;\n\
        }\n\
        if(!doneP)\n\
        {\n\
            posP += offNP;\n\
        }\n\
    }\n\
    \n\
    float dstN = horzSpan ? pos.x - posN.x : pos.y - posN.y;\n\
    float dstP = horzSpan ? posP.x - pos.x : posP.y - pos.y;\n\
    bool directionN = dstN < dstP;\n\
    lumaEndN = directionN ? lumaEndN : lumaEndP;\n\
    \n\
    if(((lumaM - lumaN) < 0.0) == ((lumaEndN - lumaN) < 0.0))\n\
    {\n\
        lengthSign = 0.0;\n\
    }\n\
 \n\
\n\
    float spanLength = (dstP + dstN);\n\
    dstN = directionN ? dstN : dstP;\n\
    float subPixelOffset = (0.5 + (dstN * (-1.0/spanLength))) * lengthSign;\n\
    vec3 rgbF = texture2D(tex, vec2(\n\
        pos.x + (horzSpan ? 0.0 : subPixelOffset),\n\
        pos.y + (horzSpan ? subPixelOffset : 0.0))).xyz;\n\
    return FxaaLerp3(rgbL, rgbF, blendL); \n\
}\n\
\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_step;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    gl_FragColor = vec4(FxaaPixelShader(v_textureCoordinates, u_texture, u_step), 1.0);\n\
}\n\
";
});
/*global define*/
define('Scene/FXAA',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/PixelFormat',
        '../Renderer/ClearCommand',
        '../Renderer/Framebuffer',
        '../Renderer/PixelDatatype',
        '../Renderer/Renderbuffer',
        '../Renderer/RenderbufferFormat',
        '../Renderer/RenderState',
        '../Renderer/Texture',
        '../Shaders/PostProcessFilters/FXAA'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Color,
        defined,
        destroyObject,
        PixelFormat,
        ClearCommand,
        Framebuffer,
        PixelDatatype,
        Renderbuffer,
        RenderbufferFormat,
        RenderState,
        Texture,
        FXAAFS) {
    'use strict';

    /**
     * @private
     */
    function FXAA(context) {
        this._texture = undefined;
        this._depthStencilTexture = undefined;
        this._depthStencilRenderbuffer = undefined;
        this._fbo = undefined;
        this._command = undefined;

        this._viewport = new BoundingRectangle();
        this._rs = undefined;

        var clearCommand = new ClearCommand({
            color : new Color(0.0, 0.0, 0.0, 0.0),
            depth : 1.0,
            owner : this
        });
        this._clearCommand = clearCommand;
    }

    function destroyResources(fxaa) {
        fxaa._fbo = fxaa._fbo && fxaa._fbo.destroy();
        fxaa._texture = fxaa._texture && fxaa._texture.destroy();
        fxaa._depthStencilTexture = fxaa._depthStencilTexture && fxaa._depthStencilTexture.destroy();
        fxaa._depthStencilRenderbuffer = fxaa._depthStencilRenderbuffer && fxaa._depthStencilRenderbuffer.destroy();

        fxaa._fbo = undefined;
        fxaa._texture = undefined;
        fxaa._depthStencilTexture = undefined;
        fxaa._depthStencilRenderbuffer = undefined;

        if (defined(fxaa._command)) {
            fxaa._command.shaderProgram = fxaa._command.shaderProgram && fxaa._command.shaderProgram.destroy();
            fxaa._command = undefined;
        }
    }

    FXAA.prototype.update = function(context) {
        var width = context.drawingBufferWidth;
        var height = context.drawingBufferHeight;

        var fxaaTexture = this._texture;
        var textureChanged = !defined(fxaaTexture) || fxaaTexture.width !== width || fxaaTexture.height !== height;
        if (textureChanged) {
            this._texture = this._texture && this._texture.destroy();
            this._depthStencilTexture = this._depthStencilTexture && this._depthStencilTexture.destroy();
            this._depthStencilRenderbuffer = this._depthStencilRenderbuffer && this._depthStencilRenderbuffer.destroy();

            this._texture = new Texture({
                context : context,
                width : width,
                height : height,
                pixelFormat : PixelFormat.RGBA,
                pixelDatatype : PixelDatatype.UNSIGNED_BYTE
            });

            if (context.depthTexture) {
                this._depthStencilTexture = new Texture({
                    context : context,
                    width : width,
                    height : height,
                    pixelFormat : PixelFormat.DEPTH_STENCIL,
                    pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8
                });
            } else {
                this._depthStencilRenderbuffer = new Renderbuffer({
                    context : context,
                    width : width,
                    height : height,
                    format : RenderbufferFormat.DEPTH_STENCIL
                });
            }
        }

        if (!defined(this._fbo) || textureChanged) {
            this._fbo = this._fbo && this._fbo.destroy();

            this._fbo = new Framebuffer({
                context : context,
                colorTextures : [this._texture],
                depthStencilTexture : this._depthStencilTexture,
                depthStencilRenderbuffer : this._depthStencilRenderbuffer,
                destroyAttachments : false
            });
        }

        if (!defined(this._command)) {
            this._command = context.createViewportQuadCommand(FXAAFS, {
                owner : this
            });
        }

        this._viewport.width = width;
        this._viewport.height = height;

        if (!defined(this._rs) || !BoundingRectangle.equals(this._rs.viewport, this._viewport)) {
            this._rs = RenderState.fromCache({
                viewport : this._viewport
            });
        }

        this._command.renderState = this._rs;

        if (textureChanged) {
            var that = this;
            var step = new Cartesian2(1.0 / this._texture.width, 1.0 / this._texture.height);
            this._command.uniformMap = {
                u_texture : function() {
                    return that._texture;
                },
                u_step : function() {
                    return step;
                }
            };
        }
    };

    FXAA.prototype.execute = function(context, passState) {
        this._command.execute(context, passState);
    };

    FXAA.prototype.clear = function(context, passState, clearColor) {
        var framebuffer = passState.framebuffer;

        passState.framebuffer = this._fbo;
        Color.clone(clearColor, this._clearCommand.color);
        this._clearCommand.execute(context, passState);

        passState.framebuffer = framebuffer;
    };

    FXAA.prototype.getColorFramebuffer = function() {
        return this._fbo;
    };

    FXAA.prototype.isDestroyed = function() {
        return false;
    };

    FXAA.prototype.destroy = function() {
        destroyResources(this);
        return destroyObject(this);
    };

    return FXAA;
});

/*global define*/
define('Scene/GetFeatureInfoFormat',[
        '../Core/Cartographic',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/RuntimeError',
        './ImageryLayerFeatureInfo'
    ], function(
        Cartographic,
        defined,
        DeveloperError,
        RuntimeError,
        ImageryLayerFeatureInfo) {
    'use strict';

    /**
     * Describes the format in which to request GetFeatureInfo from a Web Map Service (WMS) server.
     *
     * @alias GetFeatureInfoFormat
     * @constructor
     *
     * @param {String} type The type of response to expect from a GetFeatureInfo request.  Valid
     *        values are 'json', 'xml', 'html', or 'text'.
     * @param {String} [format] The info format to request from the WMS server.  This is usually a
     *        MIME type such as 'application/json' or text/xml'.  If this parameter is not specified, the provider will request 'json'
     *        using 'application/json', 'xml' using 'text/xml', 'html' using 'text/html', and 'text' using 'text/plain'.
     * @param {Function} [callback] A function to invoke with the GetFeatureInfo response from the WMS server
     *        in order to produce an array of picked {@link ImageryLayerFeatureInfo} instances.  If this parameter is not specified,
     *        a default function for the type of response is used.
     */
    function GetFeatureInfoFormat(type, format, callback) {
                if (!defined(type)) {
            throw new DeveloperError('type is required.');
        }
        
        this.type = type;

        if (!defined(format)) {
            if (type === 'json') {
                format = 'application/json';
            } else if (type === 'xml') {
                format = 'text/xml';
            } else if (type === 'html') {
                format = 'text/html';
            } else if (type === 'text') {
                format = 'text/plain';
            }
                        else {
                throw new DeveloperError('format is required when type is not "json", "xml", "html", or "text".');
            }
                    }

        this.format = format;

        if (!defined(callback)) {
            if (type === 'json') {
                callback = geoJsonToFeatureInfo;
            } else if (type === 'xml') {
                callback = xmlToFeatureInfo;
            } else if (type === 'html') {
                callback = textToFeatureInfo;
            } else if (type === 'text') {
                callback = textToFeatureInfo;
            }
                        else {
                throw new DeveloperError('callback is required when type is not "json", "xml", "html", or "text".');
            }
                    }

        this.callback = callback;
    }

    function geoJsonToFeatureInfo(json) {
        var result = [];

        var features = json.features;
        for (var i = 0; i < features.length; ++i) {
            var feature = features[i];

            var featureInfo = new ImageryLayerFeatureInfo();
            featureInfo.data = feature;
            featureInfo.properties = feature.properties;
            featureInfo.configureNameFromProperties(feature.properties);
            featureInfo.configureDescriptionFromProperties(feature.properties);

            // If this is a point feature, use the coordinates of the point.
            if (defined(feature.geometry) && feature.geometry.type === 'Point') {
                var longitude = feature.geometry.coordinates[0];
                var latitude = feature.geometry.coordinates[1];
                featureInfo.position = Cartographic.fromDegrees(longitude, latitude);
            }

            result.push(featureInfo);
        }

        return result;
    }

    var mapInfoMxpNamespace = 'http://www.mapinfo.com/mxp';
    var esriWmsNamespace = 'http://www.esri.com/wms';
    var wfsNamespace = 'http://www.opengis.net/wfs';
    var gmlNamespace = 'http://www.opengis.net/gml';

    function xmlToFeatureInfo(xml) {
        var documentElement = xml.documentElement;
        if (documentElement.localName === 'MultiFeatureCollection' && documentElement.namespaceURI === mapInfoMxpNamespace) {
            // This looks like a MapInfo MXP response
            return mapInfoXmlToFeatureInfo(xml);
        } else if (documentElement.localName === 'FeatureInfoResponse' && documentElement.namespaceURI === esriWmsNamespace) {
            // This looks like an Esri WMS response
            return esriXmlToFeatureInfo(xml);
        } else if (documentElement.localName === 'FeatureCollection' && documentElement.namespaceURI === wfsNamespace) {
            // This looks like a WFS/GML response.
            return gmlToFeatureInfo(xml);
        } else if (documentElement.localName === 'ServiceExceptionReport') {
            // This looks like a WMS server error, so no features picked.
            throw new RuntimeError(new XMLSerializer().serializeToString(documentElement));
        } else if (documentElement.localName === 'msGMLOutput') {
                return msGmlToFeatureInfo(xml);
        } else {
            // Unknown response type, so just dump the XML itself into the description.
            return unknownXmlToFeatureInfo(xml);
        }
    }

    function mapInfoXmlToFeatureInfo(xml) {
        var result = [];

        var multiFeatureCollection = xml.documentElement;

        var features = multiFeatureCollection.getElementsByTagNameNS(mapInfoMxpNamespace, 'Feature');
        for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) {
            var feature = features[featureIndex];

            var properties = {};

            var propertyElements = feature.getElementsByTagNameNS(mapInfoMxpNamespace, 'Val');
            for (var propertyIndex = 0; propertyIndex < propertyElements.length; ++propertyIndex) {
                var propertyElement = propertyElements[propertyIndex];
                if (propertyElement.hasAttribute('ref')) {
                    var name = propertyElement.getAttribute('ref');
                    var value = propertyElement.textContent.trim();
                    properties[name] = value;
                }
            }

            var featureInfo = new ImageryLayerFeatureInfo();
            featureInfo.data = feature;
            featureInfo.properties = properties;
            featureInfo.configureNameFromProperties(properties);
            featureInfo.configureDescriptionFromProperties(properties);
            result.push(featureInfo);
        }

        return result;
    }

    function esriXmlToFeatureInfo(xml) {
        var featureInfoResponse = xml.documentElement;
        var result = [];
        var properties;

        var features = featureInfoResponse.getElementsByTagNameNS('*', 'FIELDS');
        if (features.length > 0) {
            // Standard esri format
            for (var featureIndex = 0; featureIndex < features.length; ++featureIndex) {
                var feature = features[featureIndex];

                properties = {};

                var propertyAttributes = feature.attributes;
                for (var attributeIndex = 0; attributeIndex < propertyAttributes.length; ++attributeIndex) {
                    var attribute = propertyAttributes[attributeIndex];
                    properties[attribute.name] = attribute.value;
                }

                result.push(imageryLayerFeatureInfoFromDataAndProperties(feature, properties));
            }
        } else {
            // Thredds format -- looks like esri, but instead of containing FIELDS, contains FeatureInfo element
            var featureInfoElements = featureInfoResponse.getElementsByTagNameNS('*', 'FeatureInfo');
            for (var featureInfoElementIndex = 0; featureInfoElementIndex < featureInfoElements.length; ++featureInfoElementIndex) {
                var featureInfoElement = featureInfoElements[featureInfoElementIndex];

                properties = {};

                // node.children is not supported in IE9-11, so use childNodes and check that child.nodeType is an element
                var featureInfoChildren = featureInfoElement.childNodes;
                for (var childIndex = 0; childIndex < featureInfoChildren.length; ++childIndex) {
                    var child = featureInfoChildren[childIndex];
                    if (child.nodeType === Node.ELEMENT_NODE) {
                        properties[child.localName] = child.textContent;
                    }
                }

                result.push(imageryLayerFeatureInfoFromDataAndProperties(featureInfoElement, properties));
            }
        }

        return result;
    }

    function gmlToFeatureInfo(xml) {
        var result = [];

        var featureCollection = xml.documentElement;

        var featureMembers = featureCollection.getElementsByTagNameNS(gmlNamespace, 'featureMember');
        for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
            var featureMember = featureMembers[featureIndex];

            var properties = {};
            getGmlPropertiesRecursively(featureMember, properties);
            result.push(imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties));
        }

        return result;
    }

    // msGmlToFeatureInfo is similar to gmlToFeatureInfo, but assumes different XML structure
    // eg. <msGMLOutput> <ABC_layer> <ABC_feature> <foo>bar</foo> ... </ABC_feature> </ABC_layer> </msGMLOutput>

    function msGmlToFeatureInfo(xml) {
        var result = [];

        // Find the first child. Except for IE, this would work:
        // var layer = xml.documentElement.children[0];
        var layer;
        var children = xml.documentElement.childNodes;
        for (var i = 0; i < children.length; i++) {
            if (children[i].nodeType === Node.ELEMENT_NODE) {
                layer = children[i];
                break;
            }
        }

        var featureMembers = layer.childNodes;
        for (var featureIndex = 0; featureIndex < featureMembers.length; ++featureIndex) {
            var featureMember = featureMembers[featureIndex];
            if (featureMember.nodeType === Node.ELEMENT_NODE) {
                var properties = {};
                getGmlPropertiesRecursively(featureMember, properties);
                result.push(imageryLayerFeatureInfoFromDataAndProperties(featureMember, properties));
            }
        }

        return result;
    }

    function getGmlPropertiesRecursively(gmlNode, properties) {
        var isSingleValue = true;

        for (var i = 0; i < gmlNode.childNodes.length; ++i) {
            var child = gmlNode.childNodes[i];

            if (child.nodeType === Node.ELEMENT_NODE) {
                isSingleValue = false;
            }

            if (child.localName === 'Point' || child.localName === 'LineString' || child.localName === 'Polygon' || child.localName === 'boundedBy') {
                continue;
            }

            if (child.hasChildNodes() && getGmlPropertiesRecursively(child, properties)) {
                properties[child.localName] = child.textContent;
            }
        }

        return isSingleValue;
    }

    function imageryLayerFeatureInfoFromDataAndProperties(data, properties) {
        var featureInfo = new ImageryLayerFeatureInfo();
        featureInfo.data = data;
        featureInfo.properties = properties;
        featureInfo.configureNameFromProperties(properties);
        featureInfo.configureDescriptionFromProperties(properties);
        return featureInfo;
    }

    function unknownXmlToFeatureInfo(xml) {
        var xmlText = new XMLSerializer().serializeToString(xml);

        var element = document.createElement('div');
        var pre = document.createElement('pre');
        pre.textContent = xmlText;
        element.appendChild(pre);

        var featureInfo = new ImageryLayerFeatureInfo();
        featureInfo.data = xml;
        featureInfo.description = element.innerHTML;
        return [featureInfo];
    }

    var emptyBodyRegex= /<body>\s*<\/body>/im;
    var wmsServiceExceptionReportRegex = /<ServiceExceptionReport([\s\S]*)<\/ServiceExceptionReport>/im;
    var titleRegex = /<title>([\s\S]*)<\/title>/im;

    function textToFeatureInfo(text) {
        // If the text is HTML and it has an empty body tag, assume it means no features were found.
        if (emptyBodyRegex.test(text)) {
            return undefined;
        }

        // If this is a WMS exception report, treat it as "no features found" rather than showing
        // bogus feature info.
        if (wmsServiceExceptionReportRegex.test(text)) {
            return undefined;
        }

        // If the text has a <title> element, use it as the name.
        var name;
        var title = titleRegex.exec(text);
        if (title && title.length > 1) {
            name = title[1];
        }

        var featureInfo = new ImageryLayerFeatureInfo();
        featureInfo.name = name;
        featureInfo.description = text;
        featureInfo.data = text;
        return [featureInfo];
    }

    return GetFeatureInfoFormat;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/GlobeFS',[],function() {
    'use strict';
    return "//#define SHOW_TILE_BOUNDARIES\n\
\n\
uniform vec4 u_initialColor;\n\
\n\
#if TEXTURE_UNITS > 0\n\
uniform sampler2D u_dayTextures[TEXTURE_UNITS];\n\
uniform vec4 u_dayTextureTranslationAndScale[TEXTURE_UNITS];\n\
uniform bool u_dayTextureUseWebMercatorT[TEXTURE_UNITS];\n\
\n\
#ifdef APPLY_ALPHA\n\
uniform float u_dayTextureAlpha[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef APPLY_SPLIT\n\
uniform float u_dayTextureSplit[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef APPLY_BRIGHTNESS\n\
uniform float u_dayTextureBrightness[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef APPLY_CONTRAST\n\
uniform float u_dayTextureContrast[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef APPLY_HUE\n\
uniform float u_dayTextureHue[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef APPLY_SATURATION\n\
uniform float u_dayTextureSaturation[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef APPLY_GAMMA\n\
uniform float u_dayTextureOneOverGamma[TEXTURE_UNITS];\n\
#endif\n\
\n\
uniform vec4 u_dayTextureTexCoordsRectangle[TEXTURE_UNITS];\n\
#endif\n\
\n\
#ifdef SHOW_REFLECTIVE_OCEAN\n\
uniform sampler2D u_waterMask;\n\
uniform vec4 u_waterMaskTranslationAndScale;\n\
uniform float u_zoomedOutOceanSpecularIntensity;\n\
#endif\n\
\n\
#ifdef SHOW_OCEAN_WAVES\n\
uniform sampler2D u_oceanNormalMap;\n\
#endif\n\
\n\
#ifdef ENABLE_DAYNIGHT_SHADING\n\
uniform vec2 u_lightingFadeDistance;\n\
#endif\n\
\n\
varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
varying vec3 v_textureCoordinates;\n\
varying vec3 v_normalMC;\n\
varying vec3 v_normalEC;\n\
\n\
#ifdef FOG\n\
varying float v_distance;\n\
varying vec3 v_rayleighColor;\n\
varying vec3 v_mieColor;\n\
#endif\n\
\n\
vec4 sampleAndBlend(\n\
    vec4 previousColor,\n\
    sampler2D texture,\n\
    vec2 tileTextureCoordinates,\n\
    vec4 textureCoordinateRectangle,\n\
    vec4 textureCoordinateTranslationAndScale,\n\
    float textureAlpha,\n\
    float textureBrightness,\n\
    float textureContrast,\n\
    float textureHue,\n\
    float textureSaturation,\n\
    float textureOneOverGamma,\n\
    float split)\n\
{\n\
    // This crazy step stuff sets the alpha to 0.0 if this following condition is true:\n\
    //    tileTextureCoordinates.s < textureCoordinateRectangle.s ||\n\
    //    tileTextureCoordinates.s > textureCoordinateRectangle.p ||\n\
    //    tileTextureCoordinates.t < textureCoordinateRectangle.t ||\n\
    //    tileTextureCoordinates.t > textureCoordinateRectangle.q\n\
    // In other words, the alpha is zero if the fragment is outside the rectangle\n\
    // covered by this texture.  Would an actual 'if' yield better performance?\n\
    vec2 alphaMultiplier = step(textureCoordinateRectangle.st, tileTextureCoordinates);\n\
    textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y;\n\
\n\
    alphaMultiplier = step(vec2(0.0), textureCoordinateRectangle.pq - tileTextureCoordinates);\n\
    textureAlpha = textureAlpha * alphaMultiplier.x * alphaMultiplier.y;\n\
\n\
    vec2 translation = textureCoordinateTranslationAndScale.xy;\n\
    vec2 scale = textureCoordinateTranslationAndScale.zw;\n\
    vec2 textureCoordinates = tileTextureCoordinates * scale + translation;\n\
    vec4 value = texture2D(texture, textureCoordinates);\n\
    vec3 color = value.rgb;\n\
    float alpha = value.a;\n\
\n\
#ifdef APPLY_SPLIT\n\
    float splitPosition = czm_imagerySplitPosition * czm_viewport.z;\n\
    // Split to the left\n\
    if (split < 0.0 && gl_FragCoord.x > splitPosition) {\n\
       alpha = 0.0;\n\
    }\n\
    // Split to the right\n\
    else if (split > 0.0 && gl_FragCoord.x < splitPosition) {\n\
       alpha = 0.0;\n\
    }\n\
#endif\n\
\n\
#ifdef APPLY_BRIGHTNESS\n\
    color = mix(vec3(0.0), color, textureBrightness);\n\
#endif\n\
\n\
#ifdef APPLY_CONTRAST\n\
    color = mix(vec3(0.5), color, textureContrast);\n\
#endif\n\
\n\
#ifdef APPLY_HUE\n\
    color = czm_hue(color, textureHue);\n\
#endif\n\
\n\
#ifdef APPLY_SATURATION\n\
    color = czm_saturation(color, textureSaturation);\n\
#endif\n\
\n\
#ifdef APPLY_GAMMA\n\
    color = pow(color, vec3(textureOneOverGamma));\n\
#endif\n\
\n\
    float sourceAlpha = alpha * textureAlpha;\n\
    float outAlpha = mix(previousColor.a, 1.0, sourceAlpha);\n\
    vec3 outColor = mix(previousColor.rgb * previousColor.a, color, sourceAlpha) / outAlpha;\n\
    return vec4(outColor, outAlpha);\n\
}\n\
\n\
vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates);\n\
vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float specularMapValue);\n\
\n\
void main()\n\
{\n\
    // The clamp below works around an apparent bug in Chrome Canary v23.0.1241.0\n\
    // where the fragment shader sees textures coordinates < 0.0 and > 1.0 for the\n\
    // fragments on the edges of tiles even though the vertex shader is outputting\n\
    // coordinates strictly in the 0-1 range.\n\
    vec4 color = computeDayColor(u_initialColor, clamp(v_textureCoordinates, 0.0, 1.0));\n\
\n\
#ifdef SHOW_TILE_BOUNDARIES\n\
    if (v_textureCoordinates.x < (1.0/256.0) || v_textureCoordinates.x > (255.0/256.0) ||\n\
        v_textureCoordinates.y < (1.0/256.0) || v_textureCoordinates.y > (255.0/256.0))\n\
    {\n\
        color = vec4(1.0, 0.0, 0.0, 1.0);\n\
    }\n\
#endif\n\
\n\
#if defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING)\n\
    vec3 normalMC = czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0));   // normalized surface normal in model coordinates\n\
    vec3 normalEC = czm_normal3D * normalMC;                                         // normalized surface normal in eye coordiantes\n\
#endif\n\
\n\
#ifdef SHOW_REFLECTIVE_OCEAN\n\
    vec2 waterMaskTranslation = u_waterMaskTranslationAndScale.xy;\n\
    vec2 waterMaskScale = u_waterMaskTranslationAndScale.zw;\n\
    vec2 waterMaskTextureCoordinates = v_textureCoordinates.xy * waterMaskScale + waterMaskTranslation;\n\
\n\
    float mask = texture2D(u_waterMask, waterMaskTextureCoordinates).r;\n\
\n\
    if (mask > 0.0)\n\
    {\n\
        mat3 enuToEye = czm_eastNorthUpToEyeCoordinates(v_positionMC, normalEC);\n\
\n\
        vec2 ellipsoidTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC);\n\
        vec2 ellipsoidFlippedTextureCoordinates = czm_ellipsoidWgs84TextureCoordinates(normalMC.zyx);\n\
\n\
        vec2 textureCoordinates = mix(ellipsoidTextureCoordinates, ellipsoidFlippedTextureCoordinates, czm_morphTime * smoothstep(0.9, 0.95, normalMC.z));\n\
\n\
        color = computeWaterColor(v_positionEC, textureCoordinates, enuToEye, color, mask);\n\
    }\n\
#endif\n\
\n\
#ifdef ENABLE_VERTEX_LIGHTING\n\
    float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalize(v_normalEC)) * 0.9 + 0.3, 0.0, 1.0);\n\
    vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a);\n\
#elif defined(ENABLE_DAYNIGHT_SHADING)\n\
    float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * 5.0 + 0.3, 0.0, 1.0);\n\
    float cameraDist = length(czm_view[3]);\n\
    float fadeOutDist = u_lightingFadeDistance.x;\n\
    float fadeInDist = u_lightingFadeDistance.y;\n\
    float t = clamp((cameraDist - fadeOutDist) / (fadeInDist - fadeOutDist), 0.0, 1.0);\n\
    diffuseIntensity = mix(1.0, diffuseIntensity, t);\n\
    vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a);\n\
#else\n\
    vec4 finalColor = color;\n\
#endif\n\
\n\
\n\
#ifdef FOG\n\
    const float fExposure = 2.0;\n\
    vec3 fogColor = v_mieColor + finalColor.rgb * v_rayleighColor;\n\
    fogColor = vec3(1.0) - exp(-fExposure * fogColor);\n\
\n\
    gl_FragColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor), finalColor.a);\n\
#else\n\
    gl_FragColor = finalColor;\n\
#endif\n\
}\n\
\n\
#ifdef SHOW_REFLECTIVE_OCEAN\n\
\n\
float waveFade(float edge0, float edge1, float x)\n\
{\n\
    float y = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n\
    return pow(1.0 - y, 5.0);\n\
}\n\
\n\
float linearFade(float edge0, float edge1, float x)\n\
{\n\
    return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n\
}\n\
\n\
// Based on water rendering by Jonas Wagner:\n\
// http://29a.ch/2012/7/19/webgl-terrain-rendering-water-fog\n\
\n\
// low altitude wave settings\n\
const float oceanFrequencyLowAltitude = 825000.0;\n\
const float oceanAnimationSpeedLowAltitude = 0.004;\n\
const float oceanOneOverAmplitudeLowAltitude = 1.0 / 2.0;\n\
const float oceanSpecularIntensity = 0.5;\n\
\n\
// high altitude wave settings\n\
const float oceanFrequencyHighAltitude = 125000.0;\n\
const float oceanAnimationSpeedHighAltitude = 0.008;\n\
const float oceanOneOverAmplitudeHighAltitude = 1.0 / 2.0;\n\
\n\
vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat3 enuToEye, vec4 imageryColor, float maskValue)\n\
{\n\
    vec3 positionToEyeEC = -positionEyeCoordinates;\n\
    float positionToEyeECLength = length(positionToEyeEC);\n\
\n\
    // The double normalize below works around a bug in Firefox on Android devices.\n\
    vec3 normalizedpositionToEyeEC = normalize(normalize(positionToEyeEC));\n\
\n\
    // Fade out the waves as the camera moves far from the surface.\n\
    float waveIntensity = waveFade(70000.0, 1000000.0, positionToEyeECLength);\n\
\n\
#ifdef SHOW_OCEAN_WAVES\n\
    // high altitude waves\n\
    float time = czm_frameNumber * oceanAnimationSpeedHighAltitude;\n\
    vec4 noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyHighAltitude, time, 0.0);\n\
    vec3 normalTangentSpaceHighAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeHighAltitude);\n\
\n\
    // low altitude waves\n\
    time = czm_frameNumber * oceanAnimationSpeedLowAltitude;\n\
    noise = czm_getWaterNoise(u_oceanNormalMap, textureCoordinates * oceanFrequencyLowAltitude, time, 0.0);\n\
    vec3 normalTangentSpaceLowAltitude = vec3(noise.xy, noise.z * oceanOneOverAmplitudeLowAltitude);\n\
\n\
    // blend the 2 wave layers based on distance to surface\n\
    float highAltitudeFade = linearFade(0.0, 60000.0, positionToEyeECLength);\n\
    float lowAltitudeFade = 1.0 - linearFade(20000.0, 60000.0, positionToEyeECLength);\n\
    vec3 normalTangentSpace =\n\
        (highAltitudeFade * normalTangentSpaceHighAltitude) +\n\
        (lowAltitudeFade * normalTangentSpaceLowAltitude);\n\
    normalTangentSpace = normalize(normalTangentSpace);\n\
\n\
    // fade out the normal perturbation as we move farther from the water surface\n\
    normalTangentSpace.xy *= waveIntensity;\n\
    normalTangentSpace = normalize(normalTangentSpace);\n\
#else\n\
    vec3 normalTangentSpace = vec3(0.0, 0.0, 1.0);\n\
#endif\n\
\n\
    vec3 normalEC = enuToEye * normalTangentSpace;\n\
\n\
    const vec3 waveHighlightColor = vec3(0.3, 0.45, 0.6);\n\
\n\
    // Use diffuse light to highlight the waves\n\
    float diffuseIntensity = czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * maskValue;\n\
    vec3 diffuseHighlight = waveHighlightColor * diffuseIntensity;\n\
\n\
#ifdef SHOW_OCEAN_WAVES\n\
    // Where diffuse light is low or non-existent, use wave highlights based solely on\n\
    // the wave bumpiness and no particular light direction.\n\
    float tsPerturbationRatio = normalTangentSpace.z;\n\
    vec3 nonDiffuseHighlight = mix(waveHighlightColor * 5.0 * (1.0 - tsPerturbationRatio), vec3(0.0), diffuseIntensity);\n\
#else\n\
    vec3 nonDiffuseHighlight = vec3(0.0);\n\
#endif\n\
\n\
    // Add specular highlights in 3D, and in all modes when zoomed in.\n\
    float specularIntensity = czm_getSpecular(czm_sunDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0) + 0.25 * czm_getSpecular(czm_moonDirectionEC, normalizedpositionToEyeEC, normalEC, 10.0);\n\
    float surfaceReflectance = mix(0.0, mix(u_zoomedOutOceanSpecularIntensity, oceanSpecularIntensity, waveIntensity), maskValue);\n\
    float specular = specularIntensity * surfaceReflectance;\n\
\n\
    return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a);\n\
}\n\
\n\
#endif // #ifdef SHOW_REFLECTIVE_OCEAN\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/GlobeVS',[],function() {
    'use strict';
    return "#ifdef QUANTIZATION_BITS12\n\
attribute vec4 compressed0;\n\
attribute float compressed1;\n\
#else\n\
attribute vec4 position3DAndHeight;\n\
attribute vec4 textureCoordAndEncodedNormals;\n\
#endif\n\
\n\
uniform vec3 u_center3D;\n\
uniform mat4 u_modifiedModelView;\n\
uniform mat4 u_modifiedModelViewProjection;\n\
uniform vec4 u_tileRectangle;\n\
\n\
// Uniforms for 2D Mercator projection\n\
uniform vec2 u_southAndNorthLatitude;\n\
uniform vec2 u_southMercatorYAndOneOverHeight;\n\
\n\
varying vec3 v_positionMC;\n\
varying vec3 v_positionEC;\n\
\n\
varying vec3 v_textureCoordinates;\n\
varying vec3 v_normalMC;\n\
varying vec3 v_normalEC;\n\
\n\
#ifdef FOG\n\
varying float v_distance;\n\
varying vec3 v_mieColor;\n\
varying vec3 v_rayleighColor;\n\
#endif\n\
\n\
// These functions are generated at runtime.\n\
vec4 getPosition(vec3 position, float height, vec2 textureCoordinates);\n\
float get2DYPositionFraction(vec2 textureCoordinates);\n\
\n\
vec4 getPosition3DMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
    return u_modifiedModelViewProjection * vec4(position, 1.0);\n\
}\n\
\n\
float get2DMercatorYPositionFraction(vec2 textureCoordinates)\n\
{\n\
    // The width of a tile at level 11, in radians and assuming a single root tile, is\n\
    //   2.0 * czm_pi / pow(2.0, 11.0)\n\
    // We want to just linearly interpolate the 2D position from the texture coordinates\n\
    // when we're at this level or higher.  The constant below is the expression\n\
    // above evaluated and then rounded up at the 4th significant digit.\n\
    const float maxTileWidth = 0.003068;\n\
    float positionFraction = textureCoordinates.y;\n\
    float southLatitude = u_southAndNorthLatitude.x;\n\
    float northLatitude = u_southAndNorthLatitude.y;\n\
    if (northLatitude - southLatitude > maxTileWidth)\n\
    {\n\
        float southMercatorY = u_southMercatorYAndOneOverHeight.x;\n\
        float oneOverMercatorHeight = u_southMercatorYAndOneOverHeight.y;\n\
\n\
        float currentLatitude = mix(southLatitude, northLatitude, textureCoordinates.y);\n\
        currentLatitude = clamp(currentLatitude, -czm_webMercatorMaxLatitude, czm_webMercatorMaxLatitude);\n\
        positionFraction = czm_latitudeToWebMercatorFraction(currentLatitude, southMercatorY, oneOverMercatorHeight);\n\
    }\n\
    return positionFraction;\n\
}\n\
\n\
float get2DGeographicYPositionFraction(vec2 textureCoordinates)\n\
{\n\
    return textureCoordinates.y;\n\
}\n\
\n\
vec4 getPositionPlanarEarth(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
    float yPositionFraction = get2DYPositionFraction(textureCoordinates);\n\
    vec4 rtcPosition2D = vec4(height, mix(u_tileRectangle.st, u_tileRectangle.pq, vec2(textureCoordinates.x, yPositionFraction)), 1.0);\n\
    return u_modifiedModelViewProjection * rtcPosition2D;\n\
}\n\
\n\
vec4 getPosition2DMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
    return getPositionPlanarEarth(position, 0.0, textureCoordinates);\n\
}\n\
\n\
vec4 getPositionColumbusViewMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
    return getPositionPlanarEarth(position, height, textureCoordinates);\n\
}\n\
\n\
vec4 getPositionMorphingMode(vec3 position, float height, vec2 textureCoordinates)\n\
{\n\
    // We do not do RTC while morphing, so there is potential for jitter.\n\
    // This is unlikely to be noticeable, though.\n\
    vec3 position3DWC = position + u_center3D;\n\
    float yPositionFraction = get2DYPositionFraction(textureCoordinates);\n\
    vec4 position2DWC = vec4(height, mix(u_tileRectangle.st, u_tileRectangle.pq, vec2(textureCoordinates.x, yPositionFraction)), 1.0);\n\
    vec4 morphPosition = czm_columbusViewMorph(position2DWC, vec4(position3DWC, 1.0), czm_morphTime);\n\
    return czm_modelViewProjection * morphPosition;\n\
}\n\
\n\
#ifdef QUANTIZATION_BITS12\n\
uniform vec2 u_minMaxHeight;\n\
uniform mat4 u_scaleAndBias;\n\
#endif\n\
\n\
void main()\n\
{\n\
#ifdef QUANTIZATION_BITS12\n\
    vec2 xy = czm_decompressTextureCoordinates(compressed0.x);\n\
    vec2 zh = czm_decompressTextureCoordinates(compressed0.y);\n\
    vec3 position = vec3(xy, zh.x);\n\
    float height = zh.y;\n\
    vec2 textureCoordinates = czm_decompressTextureCoordinates(compressed0.z);\n\
\n\
    height = height * (u_minMaxHeight.y - u_minMaxHeight.x) + u_minMaxHeight.x;\n\
    position = (u_scaleAndBias * vec4(position, 1.0)).xyz;\n\
\n\
#if (defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)) && defined(INCLUDE_WEB_MERCATOR_Y)\n\
    float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;\n\
    float encodedNormal = compressed1;\n\
#elif defined(INCLUDE_WEB_MERCATOR_Y)\n\
    float webMercatorT = czm_decompressTextureCoordinates(compressed0.w).x;\n\
    float encodedNormal = 0.0;\n\
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)\n\
    float webMercatorT = textureCoordinates.y;\n\
    float encodedNormal = compressed0.w;\n\
#else\n\
    float webMercatorT = textureCoordinates.y;\n\
    float encodedNormal = 0.0;\n\
#endif\n\
\n\
#else\n\
    // A single float per element\n\
    vec3 position = position3DAndHeight.xyz;\n\
    float height = position3DAndHeight.w;\n\
    vec2 textureCoordinates = textureCoordAndEncodedNormals.xy;\n\
\n\
#if (defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)) && defined(INCLUDE_WEB_MERCATOR_Y)\n\
    float webMercatorT = textureCoordAndEncodedNormals.z;\n\
    float encodedNormal = textureCoordAndEncodedNormals.w;\n\
#elif defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)\n\
    float webMercatorT = textureCoordinates.y;\n\
    float encodedNormal = textureCoordAndEncodedNormals.z;\n\
#elif defined(INCLUDE_WEB_MERCATOR_Y)\n\
    float webMercatorT = textureCoordAndEncodedNormals.z;\n\
    float encodedNormal = 0.0;\n\
#else\n\
    float webMercatorT = textureCoordinates.y;\n\
    float encodedNormal = 0.0;\n\
#endif\n\
\n\
#endif\n\
\n\
    vec3 position3DWC = position + u_center3D;\n\
    gl_Position = getPosition(position, height, textureCoordinates);\n\
\n\
    v_textureCoordinates = vec3(textureCoordinates, webMercatorT);\n\
\n\
#if defined(ENABLE_VERTEX_LIGHTING) || defined(GENERATE_POSITION_AND_NORMAL)\n\
    v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz;\n\
    v_positionMC = position3DWC;                                 // position in model coordinates\n\
    v_normalMC = czm_octDecode(encodedNormal);\n\
    v_normalEC = czm_normal3D * v_normalMC;\n\
#elif defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) || defined(GENERATE_POSITION)\n\
    v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz;\n\
    v_positionMC = position3DWC;                                 // position in model coordinates\n\
#endif\n\
\n\
#ifdef FOG\n\
    AtmosphereColor atmosColor = computeGroundAtmosphereFromSpace(position3DWC);\n\
    v_mieColor = atmosColor.mie;\n\
    v_rayleighColor = atmosColor.rayleigh;\n\
    v_distance = length((czm_modelView3D * vec4(position3DWC, 1.0)).xyz);\n\
#endif\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/GroundAtmosphere',[],function() {
    'use strict';
    return "/*!\n\
 * Atmosphere code:\n\
 *\n\
 * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)\n\
 * All rights reserved.\n\
 * \n\
 * Redistribution and use in source and binary forms, with or without\n\
 * modification, are permitted provided that the following conditions\n\
 * are met:\n\
 * \n\
 * * Redistributions of source code must retain the above copyright notice,\n\
 *   this list of conditions and the following disclaimer.\n\
 * * Redistributions in binary form must reproduce the above copyright notice,\n\
 *   this list of conditions and the following disclaimer in the documentation\n\
 *   and/or other materials provided with the distribution.\n\
 * * Neither the name of the project nor the names of its contributors may be\n\
 *   used to endorse or promote products derived from this software without\n\
 *   specific prior written permission.\n\
 * \n\
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n\
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n\
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n\
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\n\
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n\
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n\
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n\
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n\
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\
 *\n\
 * Modifications made by Analytical Graphics, Inc.\n\
 */\n\
 \n\
 // Atmosphere:\n\
 //   Code:  http://sponeil.net/\n\
 //   GPU Gems 2 Article:  http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html\n\
\n\
const float fInnerRadius = 6378137.0;\n\
const float fOuterRadius = 6378137.0 * 1.025;\n\
const float fOuterRadius2 = fOuterRadius * fOuterRadius;\n\
\n\
const float Kr = 0.0025;\n\
const float Km = 0.0015;\n\
const float ESun = 15.0;\n\
\n\
const float fKrESun = Kr * ESun;\n\
const float fKmESun = Km * ESun;\n\
const float fKr4PI = Kr * 4.0 * czm_pi;\n\
const float fKm4PI = Km * 4.0 * czm_pi;\n\
\n\
const float fScale = 1.0 / (fOuterRadius - fInnerRadius);\n\
const float fScaleDepth = 0.25;\n\
const float fScaleOverScaleDepth = fScale / fScaleDepth;\n\
\n\
struct AtmosphereColor\n\
{\n\
    vec3 mie;\n\
    vec3 rayleigh;\n\
};\n\
\n\
const int nSamples = 2;\n\
const float fSamples = 2.0;\n\
\n\
float scale(float fCos)\n\
{\n\
    float x = 1.0 - fCos;\n\
    return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n\
}\n\
\n\
AtmosphereColor computeGroundAtmosphereFromSpace(vec3 v3Pos)\n\
{\n\
	vec3 v3InvWavelength = vec3(1.0 / pow(0.650, 4.0), 1.0 / pow(0.570, 4.0), 1.0 / pow(0.475, 4.0));\n\
\n\
    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)\n\
    vec3 v3Ray = v3Pos - czm_viewerPositionWC;\n\
    float fFar = length(v3Ray);\n\
    v3Ray /= fFar;\n\
    \n\
    float fCameraHeight = length(czm_viewerPositionWC);\n\
    float fCameraHeight2 = fCameraHeight * fCameraHeight;\n\
\n\
    // This next line is an ANGLE workaround. It is equivalent to B = 2.0 * dot(czm_viewerPositionWC, v3Ray), \n\
    // which is what it should be, but there are problems at the poles.\n\
    float B = 2.0 * length(czm_viewerPositionWC) * dot(normalize(czm_viewerPositionWC), v3Ray);\n\
    float C = fCameraHeight2 - fOuterRadius2;\n\
    float fDet = max(0.0, B*B - 4.0 * C);\n\
    float fNear = 0.5 * (-B - sqrt(fDet));\n\
\n\
    // Calculate the ray's starting position, then calculate its scattering offset\n\
    vec3 v3Start = czm_viewerPositionWC + v3Ray * fNear;\n\
    fFar -= fNear;\n\
    float fDepth = exp((fInnerRadius - fOuterRadius) / fScaleDepth);\n\
    \n\
    // The light angle based on the sun position would be:\n\
    //    dot(czm_sunDirectionWC, v3Pos) / length(v3Pos);\n\
    // We want the atmosphere to be uniform over the globe so it is set to 1.0.\n\
    float fLightAngle = 1.0;\n\
    float fCameraAngle = dot(-v3Ray, v3Pos) / length(v3Pos);\n\
    float fCameraScale = scale(fCameraAngle);\n\
    float fLightScale = scale(fLightAngle);\n\
    float fCameraOffset = fDepth*fCameraScale;\n\
    float fTemp = (fLightScale + fCameraScale);\n\
\n\
    // Initialize the scattering loop variables\n\
    float fSampleLength = fFar / fSamples;\n\
    float fScaledLength = fSampleLength * fScale;\n\
    vec3 v3SampleRay = v3Ray * fSampleLength;\n\
    vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5;\n\
\n\
    // Now loop through the sample rays\n\
    vec3 v3FrontColor = vec3(0.0);\n\
    vec3 v3Attenuate = vec3(0.0);\n\
    for(int i=0; i<nSamples; i++)\n\
    {\n\
        float fHeight = length(v3SamplePoint);\n\
        float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight));\n\
        float fScatter = fDepth*fTemp - fCameraOffset;\n\
        v3Attenuate = exp(-fScatter * (v3InvWavelength * fKr4PI + fKm4PI));\n\
        v3FrontColor += v3Attenuate * (fDepth * fScaledLength);\n\
        v3SamplePoint += v3SampleRay;\n\
    }\n\
    \n\
    AtmosphereColor color;\n\
    color.mie = v3FrontColor * (v3InvWavelength * fKrESun + fKmESun);\n\
    color.rayleigh = v3Attenuate; // Calculate the attenuation factor for the ground\n\
    \n\
    return color;\n\
}\n\
\n\
";
});
/*global define*/
define('Scene/GlobeSurfaceShaderSet',[
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/TerrainQuantization',
        '../Renderer/ShaderProgram',
        '../Scene/SceneMode'
    ], function(
        defined,
        destroyObject,
        TerrainQuantization,
        ShaderProgram,
        SceneMode) {
    'use strict';

    function GlobeSurfaceShader(numberOfDayTextures, flags, shaderProgram) {
        this.numberOfDayTextures = numberOfDayTextures;
        this.flags = flags;
        this.shaderProgram = shaderProgram;
    }

    /**
     * Manages the shaders used to shade the surface of a {@link Globe}.
     *
     * @alias GlobeSurfaceShaderSet
     * @private
     */
    function GlobeSurfaceShaderSet() {
        this.baseVertexShaderSource = undefined;
        this.baseFragmentShaderSource = undefined;

        this._shadersByTexturesFlags = [];
        this._pickShaderPrograms = [];
    }

    function getPositionMode(sceneMode) {
        var getPosition3DMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPosition3DMode(position, height, textureCoordinates); }';
        var getPositionColumbusViewAnd2DMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPositionColumbusViewMode(position, height, textureCoordinates); }';
        var getPositionMorphingMode = 'vec4 getPosition(vec3 position, float height, vec2 textureCoordinates) { return getPositionMorphingMode(position, height, textureCoordinates); }';

        var positionMode;

        switch (sceneMode) {
        case SceneMode.SCENE3D:
            positionMode = getPosition3DMode;
            break;
        case SceneMode.SCENE2D:
        case SceneMode.COLUMBUS_VIEW:
            positionMode = getPositionColumbusViewAnd2DMode;
            break;
        case SceneMode.MORPHING:
            positionMode = getPositionMorphingMode;
            break;
        }

        return positionMode;
    }

    function get2DYPositionFraction(useWebMercatorProjection) {
        var get2DYPositionFractionGeographicProjection = 'float get2DYPositionFraction(vec2 textureCoordinates) { return get2DGeographicYPositionFraction(textureCoordinates); }';
        var get2DYPositionFractionMercatorProjection = 'float get2DYPositionFraction(vec2 textureCoordinates) { return get2DMercatorYPositionFraction(textureCoordinates); }';
        return useWebMercatorProjection ? get2DYPositionFractionMercatorProjection : get2DYPositionFractionGeographicProjection;
    }

    GlobeSurfaceShaderSet.prototype.getShaderProgram = function(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog) {
        var quantization = 0;
        var quantizationDefine = '';

        var terrainEncoding = surfaceTile.pickTerrain.mesh.encoding;
        var quantizationMode = terrainEncoding.quantization;
        if (quantizationMode === TerrainQuantization.BITS12) {
            quantization = 1;
            quantizationDefine = 'QUANTIZATION_BITS12';
        }

        var sceneMode = frameState.mode;
        var flags = sceneMode |
                    (applyBrightness << 2) |
                    (applyContrast << 3) |
                    (applyHue << 4) |
                    (applySaturation << 5) |
                    (applyGamma << 6) |
                    (applyAlpha << 7) |
                    (showReflectiveOcean << 8) |
                    (showOceanWaves << 9) |
                    (enableLighting << 10) |
                    (hasVertexNormals << 11) |
                    (useWebMercatorProjection << 12) |
                    (enableFog << 13) |
                    (quantization << 14) |
                    (applySplit << 15);

        var surfaceShader = surfaceTile.surfaceShader;
        if (defined(surfaceShader) &&
            surfaceShader.numberOfDayTextures === numberOfDayTextures &&
            surfaceShader.flags === flags) {

            return surfaceShader.shaderProgram;
        }

        // New tile, or tile changed number of textures or flags.
        var shadersByFlags = this._shadersByTexturesFlags[numberOfDayTextures];
        if (!defined(shadersByFlags)) {
            shadersByFlags = this._shadersByTexturesFlags[numberOfDayTextures] = [];
        }

        surfaceShader = shadersByFlags[flags];
        if (!defined(surfaceShader)) {
            // Cache miss - we've never seen this combination of numberOfDayTextures and flags before.
            var vs = this.baseVertexShaderSource.clone();
            var fs = this.baseFragmentShaderSource.clone();

            vs.defines.push(quantizationDefine);
            fs.defines.push('TEXTURE_UNITS ' + numberOfDayTextures);

            if (applyBrightness) {
                fs.defines.push('APPLY_BRIGHTNESS');
            }
            if (applyContrast) {
                fs.defines.push('APPLY_CONTRAST');
            }
            if (applyHue) {
                fs.defines.push('APPLY_HUE');
            }
            if (applySaturation) {
                fs.defines.push('APPLY_SATURATION');
            }
            if (applyGamma) {
                fs.defines.push('APPLY_GAMMA');
            }
            if (applyAlpha) {
                fs.defines.push('APPLY_ALPHA');
            }
            if (showReflectiveOcean) {
                fs.defines.push('SHOW_REFLECTIVE_OCEAN');
                vs.defines.push('SHOW_REFLECTIVE_OCEAN');
            }
            if (showOceanWaves) {
                fs.defines.push('SHOW_OCEAN_WAVES');
            }

            if (enableLighting) {
                if (hasVertexNormals) {
                    vs.defines.push('ENABLE_VERTEX_LIGHTING');
                    fs.defines.push('ENABLE_VERTEX_LIGHTING');
                } else {
                    vs.defines.push('ENABLE_DAYNIGHT_SHADING');
                    fs.defines.push('ENABLE_DAYNIGHT_SHADING');
                }
            }

            vs.defines.push('INCLUDE_WEB_MERCATOR_Y');
            fs.defines.push('INCLUDE_WEB_MERCATOR_Y');

            if (enableFog) {
                vs.defines.push('FOG');
                fs.defines.push('FOG');
            }

            if (applySplit) {
                fs.defines.push('APPLY_SPLIT');
            }

            var computeDayColor = '\
    vec4 computeDayColor(vec4 initialColor, vec3 textureCoordinates)\n\
    {\n\
        vec4 color = initialColor;\n';

            for (var i = 0; i < numberOfDayTextures; ++i) {
                computeDayColor += '\
    color = sampleAndBlend(\n\
        color,\n\
        u_dayTextures[' + i + '],\n\
        u_dayTextureUseWebMercatorT[' + i + '] ? textureCoordinates.xz : textureCoordinates.xy,\n\
        u_dayTextureTexCoordsRectangle[' + i + '],\n\
        u_dayTextureTranslationAndScale[' + i + '],\n\
        ' + (applyAlpha ? 'u_dayTextureAlpha[' + i + ']' : '1.0') + ',\n\
        ' + (applyBrightness ? 'u_dayTextureBrightness[' + i + ']' : '0.0') + ',\n\
        ' + (applyContrast ? 'u_dayTextureContrast[' + i + ']' : '0.0') + ',\n\
        ' + (applyHue ? 'u_dayTextureHue[' + i + ']' : '0.0') + ',\n\
        ' + (applySaturation ? 'u_dayTextureSaturation[' + i + ']' : '0.0') + ',\n\
        ' + (applyGamma ? 'u_dayTextureOneOverGamma[' + i + ']' : '0.0') + ',\n\
        ' + (applySplit ? 'u_dayTextureSplit[' + i + ']' : '0.0') + '\n\
    );\n';
            }

            computeDayColor += '\
        return color;\n\
    }';

            fs.sources.push(computeDayColor);

            vs.sources.push(getPositionMode(sceneMode));
            vs.sources.push(get2DYPositionFraction(useWebMercatorProjection));

            var shader = ShaderProgram.fromCache({
                context : frameState.context,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : terrainEncoding.getAttributeLocations()
            });

            surfaceShader = shadersByFlags[flags] = new GlobeSurfaceShader(numberOfDayTextures, flags, shader);
        }

        surfaceTile.surfaceShader = surfaceShader;
        return surfaceShader.shaderProgram;
    };

    GlobeSurfaceShaderSet.prototype.getPickShaderProgram = function(frameState, surfaceTile, useWebMercatorProjection) {
        var quantization = 0;
        var quantizationDefine = '';

        var terrainEncoding = surfaceTile.pickTerrain.mesh.encoding;
        var quantizationMode = terrainEncoding.quantization;
        if (quantizationMode === TerrainQuantization.BITS12) {
            quantization = 1;
            quantizationDefine = 'QUANTIZATION_BITS12';
        }

        var sceneMode = frameState.mode;
        var flags = sceneMode | (useWebMercatorProjection << 2) | (quantization << 3);
        var pickShader = this._pickShaderPrograms[flags];

        if (!defined(pickShader)) {
            var vs = this.baseVertexShaderSource.clone();
            vs.defines.push(quantizationDefine);
            vs.sources.push(getPositionMode(sceneMode));
            vs.sources.push(get2DYPositionFraction(useWebMercatorProjection));

            // pass through fragment shader. only depth is rendered for the globe on a pick pass
            var fs =
                'void main()\n' +
                '{\n' +
                '    gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);\n' +
                '}\n';

            pickShader = this._pickShaderPrograms[flags] = ShaderProgram.fromCache({
                context : frameState.context,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : terrainEncoding.getAttributeLocations()
            });
        }

        return pickShader;
    };

    GlobeSurfaceShaderSet.prototype.destroy = function() {
        var flags;
        var shader;

        var shadersByTexturesFlags = this._shadersByTexturesFlags;
        for (var textureCount in shadersByTexturesFlags) {
            if (shadersByTexturesFlags.hasOwnProperty(textureCount)) {
                var shadersByFlags = shadersByTexturesFlags[textureCount];
                if (!defined(shadersByFlags)) {
                    continue;
                }

                for (flags in shadersByFlags) {
                    if (shadersByFlags.hasOwnProperty(flags)) {
                        shader = shadersByFlags[flags];
                        if (defined(shader)) {
                            shader.shaderProgram.destroy();
                        }
                    }
                }
            }
        }

        var pickShaderPrograms = this._pickShaderPrograms;
        for (flags in pickShaderPrograms) {
            if (pickShaderPrograms.hasOwnProperty(flags)) {
                shader = pickShaderPrograms[flags];
                shader.destroy();
            }
        }

        return destroyObject(this);
    };

    return GlobeSurfaceShaderSet;
});

/*global define*/
define('Scene/ImageryState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * @private
     */
    var ImageryState = {
        UNLOADED : 0,
        TRANSITIONING : 1,
        RECEIVED : 2,
        TEXTURE_LOADED : 3,
        READY : 4,
        FAILED : 5,
        INVALID : 6,
        PLACEHOLDER : 7
    };

    return freezeObject(ImageryState);
});

/*global define*/
define('Scene/QuadtreeTileLoadState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The state of a {@link QuadtreeTile} in the tile load pipeline.
     * @exports QuadtreeTileLoadState
     * @private
     */
    var QuadtreeTileLoadState = {
        /**
         * The tile is new and loading has not yet begun.
         * @type QuadtreeTileLoadState
         * @constant
         * @default 0
         */
        START : 0,

        /**
         * Loading is in progress.
         * @type QuadtreeTileLoadState
         * @constant
         * @default 1
         */
        LOADING : 1,

        /**
         * Loading is complete.
         * @type QuadtreeTileLoadState
         * @constant
         * @default 2
         */
        DONE : 2,

        /**
         * The tile has failed to load.
         * @type QuadtreeTileLoadState
         * @constant
         * @default 3
         */
        FAILED : 3
    };

    return freezeObject(QuadtreeTileLoadState);
});

/*global define*/
define('Scene/TerrainState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * @private
     */
    var TerrainState = {
        FAILED : 0,
        UNLOADED : 1,
        RECEIVING : 2,
        RECEIVED : 3,
        TRANSFORMING : 4,
        TRANSFORMED : 5,
        READY : 6
    };

    return freezeObject(TerrainState);
});

/*global define*/
define('Scene/TileBoundingBox',[
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/Rectangle',
        './SceneMode'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        DeveloperError,
        Ellipsoid,
        Rectangle,
        SceneMode) {
    'use strict';

    /**
     * @param {Object} options Object with the following properties:
     * @param {Rectangle} options.rectangle
     * @param {Number} [options.minimumHeight=0.0]
     * @param {Number} [options.maximumHeight=0.0]
     * @param {Ellipsoid} [options.ellipsoid=Cesium.Ellipsoid.WGS84]
     *
     * @private
     */
    var TileBoundingBox = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.rectangle)) {
            throw new DeveloperError('options.url is required.');
        }
        
        this.rectangle = Rectangle.clone(options.rectangle);
        this.minimumHeight = defaultValue(options.minimumHeight, 0.0);
        this.maximumHeight = defaultValue(options.maximumHeight, 0.0);

        /**
         * The world coordinates of the southwest corner of the tile's rectangle.
         *
         * @type {Cartesian3}
         * @default Cartesian3()
         */
        this.southwestCornerCartesian = new Cartesian3();

        /**
         * The world coordinates of the northeast corner of the tile's rectangle.
         *
         * @type {Cartesian3}
         * @default Cartesian3()
         */
        this.northeastCornerCartesian = new Cartesian3();

        /**
         * A normal that, along with southwestCornerCartesian, defines a plane at the western edge of
         * the tile.  Any position above (in the direction of the normal) this plane is outside the tile.
         *
         * @type {Cartesian3}
         * @default Cartesian3()
         */
        this.westNormal = new Cartesian3();

        /**
         * A normal that, along with southwestCornerCartesian, defines a plane at the southern edge of
         * the tile.  Any position above (in the direction of the normal) this plane is outside the tile.
         * Because points of constant latitude do not necessary lie in a plane, positions below this
         * plane are not necessarily inside the tile, but they are close.
         *
         * @type {Cartesian3}
         * @default Cartesian3()
         */
        this.southNormal = new Cartesian3();

        /**
         * A normal that, along with northeastCornerCartesian, defines a plane at the eastern edge of
         * the tile.  Any position above (in the direction of the normal) this plane is outside the tile.
         *
         * @type {Cartesian3}
         * @default Cartesian3()
         */
        this.eastNormal = new Cartesian3();

        /**
         * A normal that, along with northeastCornerCartesian, defines a plane at the eastern edge of
         * the tile.  Any position above (in the direction of the normal) this plane is outside the tile.
         * Because points of constant latitude do not necessary lie in a plane, positions below this
         * plane are not necessarily inside the tile, but they are close.
         *
         * @type {Cartesian3}
         * @default Cartesian3()
         */
        this.northNormal = new Cartesian3();

        var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
        computeBox(this, options.rectangle, ellipsoid);
    };

    var cartesian3Scratch = new Cartesian3();
    var cartesian3Scratch2 = new Cartesian3();
    var westernMidpointScratch = new Cartesian3();
    var easternMidpointScratch = new Cartesian3();
    var cartographicScratch = new Cartographic();

    function computeBox(tileBB, rectangle, ellipsoid) {
        ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle), tileBB.southwestCornerCartesian);
        ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle), tileBB.northeastCornerCartesian);

        // The middle latitude on the western edge.
        cartographicScratch.longitude = rectangle.west;
        cartographicScratch.latitude = (rectangle.south + rectangle.north) * 0.5;
        cartographicScratch.height = 0.0;
        var westernMidpointCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, westernMidpointScratch);

        // Compute the normal of the plane on the western edge of the tile.
        var westNormal = Cartesian3.cross(westernMidpointCartesian, Cartesian3.UNIT_Z, cartesian3Scratch);
        Cartesian3.normalize(westNormal, tileBB.westNormal);

        // The middle latitude on the eastern edge.
        cartographicScratch.longitude = rectangle.east;
        var easternMidpointCartesian = ellipsoid.cartographicToCartesian(cartographicScratch, easternMidpointScratch);

        // Compute the normal of the plane on the eastern edge of the tile.
        var eastNormal = Cartesian3.cross(Cartesian3.UNIT_Z, easternMidpointCartesian, cartesian3Scratch);
        Cartesian3.normalize(eastNormal, tileBB.eastNormal);

        // Compute the normal of the plane bounding the southern edge of the tile.
        var southeastCornerNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.southeast(rectangle), cartesian3Scratch2);
        var westVector = Cartesian3.subtract(westernMidpointCartesian, easternMidpointCartesian, cartesian3Scratch);
        var southNormal = Cartesian3.cross(southeastCornerNormal, westVector, cartesian3Scratch2);
        Cartesian3.normalize(southNormal, tileBB.southNormal);

        // Compute the normal of the plane bounding the northern edge of the tile.
        var northwestCornerNormal = ellipsoid.geodeticSurfaceNormalCartographic(Rectangle.northwest(rectangle), cartesian3Scratch2);
        var northNormal = Cartesian3.cross(westVector, northwestCornerNormal, cartesian3Scratch2);
        Cartesian3.normalize(northNormal, tileBB.northNormal);
    }

    var southwestCornerScratch = new Cartesian3();
    var northeastCornerScratch = new Cartesian3();
    var negativeUnitY = new Cartesian3(0.0, -1.0, 0.0);
    var negativeUnitZ = new Cartesian3(0.0, 0.0, -1.0);
    var vectorScratch = new Cartesian3();

    /**
     * Gets the distance from the camera to the closest point on the tile.  This is used for level-of-detail selection.
     *
     * @param {FrameState} frameState The state information of the current rendering frame.
     *
     * @returns {Number} The distance from the camera to the closest point on the tile, in meters.
     */
    TileBoundingBox.prototype.distanceToCamera = function(frameState) {
        var camera = frameState.camera;
        var cameraCartesianPosition = camera.positionWC;
        var cameraCartographicPosition = camera.positionCartographic;

        var result = 0.0;
        if (!Rectangle.contains(this.rectangle, cameraCartographicPosition)) {
            var southwestCornerCartesian = this.southwestCornerCartesian;
            var northeastCornerCartesian = this.northeastCornerCartesian;
            var westNormal = this.westNormal;
            var southNormal = this.southNormal;
            var eastNormal = this.eastNormal;
            var northNormal = this.northNormal;

            if (frameState.mode !== SceneMode.SCENE3D) {
                southwestCornerCartesian = frameState.mapProjection.project(Rectangle.southwest(this.rectangle), southwestCornerScratch);
                southwestCornerCartesian.z = southwestCornerCartesian.y;
                southwestCornerCartesian.y = southwestCornerCartesian.x;
                southwestCornerCartesian.x = 0.0;
                northeastCornerCartesian = frameState.mapProjection.project(Rectangle.northeast(this.rectangle), northeastCornerScratch);
                northeastCornerCartesian.z = northeastCornerCartesian.y;
                northeastCornerCartesian.y = northeastCornerCartesian.x;
                northeastCornerCartesian.x = 0.0;
                westNormal = negativeUnitY;
                eastNormal = Cartesian3.UNIT_Y;
                southNormal = negativeUnitZ;
                northNormal = Cartesian3.UNIT_Z;
            }

            var vectorFromSouthwestCorner = Cartesian3.subtract(cameraCartesianPosition, southwestCornerCartesian, vectorScratch);
            var distanceToWestPlane = Cartesian3.dot(vectorFromSouthwestCorner, westNormal);
            var distanceToSouthPlane = Cartesian3.dot(vectorFromSouthwestCorner, southNormal);

            var vectorFromNortheastCorner = Cartesian3.subtract(cameraCartesianPosition, northeastCornerCartesian, vectorScratch);
            var distanceToEastPlane = Cartesian3.dot(vectorFromNortheastCorner, eastNormal);
            var distanceToNorthPlane = Cartesian3.dot(vectorFromNortheastCorner, northNormal);

            if (distanceToWestPlane > 0.0) {
                result += distanceToWestPlane * distanceToWestPlane;
            } else if (distanceToEastPlane > 0.0) {
                result += distanceToEastPlane * distanceToEastPlane;
            }

            if (distanceToSouthPlane > 0.0) {
                result += distanceToSouthPlane * distanceToSouthPlane;
            } else if (distanceToNorthPlane > 0.0) {
                result += distanceToNorthPlane * distanceToNorthPlane;
            }
        }

        var cameraHeight;
        if (frameState.mode === SceneMode.SCENE3D) {
            cameraHeight = cameraCartographicPosition.height;
        } else {
            cameraHeight = cameraCartesianPosition.x;
        }

        var maximumHeight = frameState.mode === SceneMode.SCENE3D ? this.maximumHeight : 0.0;
        var distanceFromTop = cameraHeight - maximumHeight;
        if (distanceFromTop > 0.0) {
            result += distanceFromTop * distanceFromTop;
        }

        return Math.sqrt(result);
    };

    return TileBoundingBox;
});

/*global define*/
define('Scene/TileTerrain',[
        '../Core/BoundingSphere',
        '../Core/Cartesian3',
        '../Core/defined',
        '../Core/DeveloperError',
        '../Core/IndexDatatype',
        '../Core/OrientedBoundingBox',
        '../Core/TileProviderError',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/VertexArray',
        '../ThirdParty/when',
        './TerrainState',
        './TileBoundingBox'
    ], function(
        BoundingSphere,
        Cartesian3,
        defined,
        DeveloperError,
        IndexDatatype,
        OrientedBoundingBox,
        TileProviderError,
        Buffer,
        BufferUsage,
        VertexArray,
        when,
        TerrainState,
        TileBoundingBox) {
    'use strict';

    /**
     * Manages details of the terrain load or upsample process.
     *
     * @alias TileTerrain
     * @constructor
     * @private
     *
     * @param {TerrainData} [upsampleDetails.data] The terrain data being upsampled.
     * @param {Number} [upsampleDetails.x] The X coordinate of the tile being upsampled.
     * @param {Number} [upsampleDetails.y] The Y coordinate of the tile being upsampled.
     * @param {Number} [upsampleDetails.level] The level coordinate of the tile being upsampled.
     */
    function TileTerrain(upsampleDetails) {
        /**
         * The current state of the terrain in the terrain processing pipeline.
         * @type {TerrainState}
         * @default {@link TerrainState.UNLOADED}
         */
        this.state = TerrainState.UNLOADED;
        this.data = undefined;
        this.mesh = undefined;
        this.vertexArray = undefined;
        this.upsampleDetails = upsampleDetails;
    }

    TileTerrain.prototype.freeResources = function() {
        this.state = TerrainState.UNLOADED;
        this.data = undefined;
        this.mesh = undefined;

        if (defined(this.vertexArray)) {
            var indexBuffer = this.vertexArray.indexBuffer;

            this.vertexArray.destroy();
            this.vertexArray = undefined;

            if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
                --indexBuffer.referenceCount;
                if (indexBuffer.referenceCount === 0) {
                    indexBuffer.destroy();
                }
            }
        }
    };

    TileTerrain.prototype.publishToTile = function(tile) {
        var surfaceTile = tile.data;

        var mesh = this.mesh;
        Cartesian3.clone(mesh.center, surfaceTile.center);
        surfaceTile.minimumHeight = mesh.minimumHeight;
        surfaceTile.maximumHeight = mesh.maximumHeight;
        surfaceTile.boundingSphere3D = BoundingSphere.clone(mesh.boundingSphere3D, surfaceTile.boundingSphere3D);
        surfaceTile.orientedBoundingBox = OrientedBoundingBox.clone(mesh.orientedBoundingBox, surfaceTile.orientedBoundingBox);
        surfaceTile.tileBoundingBox = new TileBoundingBox({
            rectangle : tile.rectangle,
            minimumHeight : mesh.minimumHeight,
            maximumHeight : mesh.maximumHeight,
            ellipsoid : tile.tilingScheme.ellipsoid
        });

        tile.data.occludeePointInScaledSpace = Cartesian3.clone(mesh.occludeePointInScaledSpace, surfaceTile.occludeePointInScaledSpace);
    };

    TileTerrain.prototype.processLoadStateMachine = function(frameState, terrainProvider, x, y, level) {
        if (this.state === TerrainState.UNLOADED) {
            requestTileGeometry(this, terrainProvider, x, y, level);
        }

        if (this.state === TerrainState.RECEIVED) {
            transform(this, frameState, terrainProvider, x, y, level);
        }

        if (this.state === TerrainState.TRANSFORMED) {
            createResources(this, frameState.context, terrainProvider, x, y, level);
        }
    };

    function requestTileGeometry(tileTerrain, terrainProvider, x, y, level) {
        function success(terrainData) {
            tileTerrain.data = terrainData;
            tileTerrain.state = TerrainState.RECEIVED;
        }

        function failure() {
            // Initially assume failure.  handleError may retry, in which case the state will
            // change to RECEIVING or UNLOADED.
            tileTerrain.state = TerrainState.FAILED;

            var message = 'Failed to obtain terrain tile X: ' + x + ' Y: ' + y + ' Level: ' + level + '.';
            terrainProvider._requestError = TileProviderError.handleError(
                terrainProvider._requestError,
                terrainProvider,
                terrainProvider.errorEvent,
                message,
                x, y, level,
                doRequest);
        }

        function doRequest() {
            // Request the terrain from the terrain provider.
            tileTerrain.data = terrainProvider.requestTileGeometry(x, y, level);

            // If the request method returns undefined (instead of a promise), the request
            // has been deferred.
            if (defined(tileTerrain.data)) {
                tileTerrain.state = TerrainState.RECEIVING;

                when(tileTerrain.data, success, failure);
            } else {
                // Deferred - try again later.
                tileTerrain.state = TerrainState.UNLOADED;
            }
        }

        doRequest();
    }

    TileTerrain.prototype.processUpsampleStateMachine = function(frameState, terrainProvider, x, y, level) {
        if (this.state === TerrainState.UNLOADED) {
            var upsampleDetails = this.upsampleDetails;

                        if (!defined(upsampleDetails)) {
                throw new DeveloperError('TileTerrain cannot upsample unless provided upsampleDetails.');
            }
            
            var sourceData = upsampleDetails.data;
            var sourceX = upsampleDetails.x;
            var sourceY = upsampleDetails.y;
            var sourceLevel = upsampleDetails.level;

            this.data = sourceData.upsample(terrainProvider.tilingScheme, sourceX, sourceY, sourceLevel, x, y, level);
            if (!defined(this.data)) {
                // The upsample request has been deferred - try again later.
                return;
            }

            this.state = TerrainState.RECEIVING;

            var that = this;
            when(this.data, function(terrainData) {
                that.data = terrainData;
                that.state = TerrainState.RECEIVED;
            }, function() {
                that.state = TerrainState.FAILED;
            });
        }

        if (this.state === TerrainState.RECEIVED) {
            transform(this, frameState, terrainProvider, x, y, level);
        }

        if (this.state === TerrainState.TRANSFORMED) {
            createResources(this, frameState.context, terrainProvider, x, y, level);
        }
    };

    function transform(tileTerrain, frameState, terrainProvider, x, y, level) {
        var tilingScheme = terrainProvider.tilingScheme;

        var terrainData = tileTerrain.data;
        var meshPromise = terrainData.createMesh(tilingScheme, x, y, level, frameState.terrainExaggeration);

        if (!defined(meshPromise)) {
            // Postponed.
            return;
        }

        tileTerrain.state = TerrainState.TRANSFORMING;

        when(meshPromise, function(mesh) {
            tileTerrain.mesh = mesh;
            tileTerrain.state = TerrainState.TRANSFORMED;
        }, function() {
            tileTerrain.state = TerrainState.FAILED;
        });
    }

    function createResources(tileTerrain, context, terrainProvider, x, y, level) {
        var typedArray = tileTerrain.mesh.vertices;
        var buffer = Buffer.createVertexBuffer({
            context : context,
            typedArray : typedArray,
            usage : BufferUsage.STATIC_DRAW
        });
        var attributes = tileTerrain.mesh.encoding.getAttributes(buffer);

        var indexBuffers = tileTerrain.mesh.indices.indexBuffers || {};
        var indexBuffer = indexBuffers[context.id];
        if (!defined(indexBuffer) || indexBuffer.isDestroyed()) {
            var indices = tileTerrain.mesh.indices;
            var indexDatatype = (indices.BYTES_PER_ELEMENT === 2) ?  IndexDatatype.UNSIGNED_SHORT : IndexDatatype.UNSIGNED_INT;
            indexBuffer = Buffer.createIndexBuffer({
                context : context,
                typedArray : indices,
                usage : BufferUsage.STATIC_DRAW,
                indexDatatype : indexDatatype
            });
            indexBuffer.vertexArrayDestroyable = false;
            indexBuffer.referenceCount = 1;
            indexBuffers[context.id] = indexBuffer;
            tileTerrain.mesh.indices.indexBuffers = indexBuffers;
        } else {
            ++indexBuffer.referenceCount;
        }

        tileTerrain.vertexArray = new VertexArray({
            context : context,
            attributes : attributes,
            indexBuffer : indexBuffer
        });

        tileTerrain.state = TerrainState.READY;
    }

    return TileTerrain;
});

/*global define*/
define('Scene/GlobeSurfaceTile',[
        '../Core/BoundingSphere',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/IntersectionTests',
        '../Core/PixelFormat',
        '../Renderer/PixelDatatype',
        '../Renderer/Sampler',
        '../Renderer/Texture',
        '../Renderer/TextureMagnificationFilter',
        '../Renderer/TextureMinificationFilter',
        '../Renderer/TextureWrap',
        './ImageryState',
        './QuadtreeTileLoadState',
        './SceneMode',
        './TerrainState',
        './TileTerrain'
    ], function(
        BoundingSphere,
        Cartesian3,
        Cartesian4,
        defaultValue,
        defined,
        defineProperties,
        IntersectionTests,
        PixelFormat,
        PixelDatatype,
        Sampler,
        Texture,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap,
        ImageryState,
        QuadtreeTileLoadState,
        SceneMode,
        TerrainState,
        TileTerrain) {
    'use strict';

    /**
     * Contains additional information about a {@link QuadtreeTile} of the globe's surface, and
     * encapsulates state transition logic for loading tiles.
     *
     * @constructor
     * @alias GlobeSurfaceTile
     * @private
     */
    function GlobeSurfaceTile() {
        /**
         * The {@link TileImagery} attached to this tile.
         * @type {TileImagery[]}
         * @default []
         */
        this.imagery = [];

        this.waterMaskTexture = undefined;

        this.waterMaskTranslationAndScale = new Cartesian4(0.0, 0.0, 1.0, 1.0);

        this.terrainData = undefined;
        this.center = new Cartesian3();
        this.vertexArray = undefined;
        this.minimumHeight = 0.0;
        this.maximumHeight = 0.0;
        this.boundingSphere3D = new BoundingSphere();
        this.boundingSphere2D = new BoundingSphere();
        this.orientedBoundingBox = undefined;
        this.tileBoundingBox = undefined;
        this.occludeePointInScaledSpace = new Cartesian3();

        this.loadedTerrain = undefined;
        this.upsampledTerrain = undefined;

        this.pickBoundingSphere = new BoundingSphere();
        this.pickTerrain = undefined;

        this.surfaceShader = undefined;
    }

    defineProperties(GlobeSurfaceTile.prototype, {
        /**
         * Gets a value indicating whether or not this tile is eligible to be unloaded.
         * Typically, a tile is ineligible to be unloaded while an asynchronous operation,
         * such as a request for data, is in progress on it.  A tile will never be
         * unloaded while it is needed for rendering, regardless of the value of this
         * property.
         * @memberof GlobeSurfaceTile.prototype
         * @type {Boolean}
         */
        eligibleForUnloading : {
            get : function() {
                // Do not remove tiles that are transitioning or that have
                // imagery that is transitioning.
                var loadedTerrain = this.loadedTerrain;
                var loadingIsTransitioning = defined(loadedTerrain) &&
                                             (loadedTerrain.state === TerrainState.RECEIVING || loadedTerrain.state === TerrainState.TRANSFORMING);

                var upsampledTerrain = this.upsampledTerrain;
                var upsamplingIsTransitioning = defined(upsampledTerrain) &&
                                                (upsampledTerrain.state === TerrainState.RECEIVING || upsampledTerrain.state === TerrainState.TRANSFORMING);

                var shouldRemoveTile = !loadingIsTransitioning && !upsamplingIsTransitioning;

                var imagery = this.imagery;
                for (var i = 0, len = imagery.length; shouldRemoveTile && i < len; ++i) {
                    var tileImagery = imagery[i];
                    shouldRemoveTile = !defined(tileImagery.loadingImagery) || tileImagery.loadingImagery.state !== ImageryState.TRANSITIONING;
                }

                return shouldRemoveTile;
            }
        }
    });

    function getPosition(encoding, mode, projection, vertices, index, result) {
        encoding.decodePosition(vertices, index, result);

        if (defined(mode) && mode !== SceneMode.SCENE3D) {
            var ellipsoid = projection.ellipsoid;
            var positionCart = ellipsoid.cartesianToCartographic(result);
            projection.project(positionCart, result);
            Cartesian3.fromElements(result.z, result.x, result.y, result);
        }

        return result;
    }

    var scratchV0 = new Cartesian3();
    var scratchV1 = new Cartesian3();
    var scratchV2 = new Cartesian3();
    var scratchResult = new Cartesian3();

    GlobeSurfaceTile.prototype.pick = function(ray, mode, projection, cullBackFaces, result) {
        var terrain = this.pickTerrain;
        if (!defined(terrain)) {
            return undefined;
        }

        var mesh = terrain.mesh;
        if (!defined(mesh)) {
            return undefined;
        }

        var vertices = mesh.vertices;
        var indices = mesh.indices;
        var encoding = mesh.encoding;

        var length = indices.length;
        for (var i = 0; i < length; i += 3) {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var v0 = getPosition(encoding, mode, projection, vertices, i0, scratchV0);
            var v1 = getPosition(encoding, mode, projection, vertices, i1, scratchV1);
            var v2 = getPosition(encoding, mode, projection, vertices, i2, scratchV2);

            var intersection = IntersectionTests.rayTriangle(ray, v0, v1, v2, cullBackFaces, scratchResult);
            if (defined(intersection)) {
                return Cartesian3.clone(intersection, result);
            }
        }

        return undefined;
    };

    GlobeSurfaceTile.prototype.freeResources = function() {
        if (defined(this.waterMaskTexture)) {
            --this.waterMaskTexture.referenceCount;
            if (this.waterMaskTexture.referenceCount === 0) {
                this.waterMaskTexture.destroy();
            }
            this.waterMaskTexture = undefined;
        }

        this.terrainData = undefined;

        if (defined(this.loadedTerrain)) {
            this.loadedTerrain.freeResources();
            this.loadedTerrain = undefined;
        }

        if (defined(this.upsampledTerrain)) {
            this.upsampledTerrain.freeResources();
            this.upsampledTerrain = undefined;
        }

        if (defined(this.pickTerrain)) {
            this.pickTerrain.freeResources();
            this.pickTerrain = undefined;
        }

        var i, len;

        var imageryList = this.imagery;
        for (i = 0, len = imageryList.length; i < len; ++i) {
            imageryList[i].freeResources();
        }
        this.imagery.length = 0;

        this.freeVertexArray();
    };

    GlobeSurfaceTile.prototype.freeVertexArray = function() {
        var indexBuffer;

        if (defined(this.vertexArray)) {
            indexBuffer = this.vertexArray.indexBuffer;

            this.vertexArray = this.vertexArray.destroy();

            if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
                --indexBuffer.referenceCount;
                if (indexBuffer.referenceCount === 0) {
                    indexBuffer.destroy();
                }
            }
        }

        if (defined(this.wireframeVertexArray)) {
            indexBuffer = this.wireframeVertexArray.indexBuffer;

            this.wireframeVertexArray = this.wireframeVertexArray.destroy();

            if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
                --indexBuffer.referenceCount;
                if (indexBuffer.referenceCount === 0) {
                    indexBuffer.destroy();
                }
            }
        }
    };

    GlobeSurfaceTile.processStateMachine = function(tile, frameState, terrainProvider, imageryLayerCollection, vertexArraysToDestroy) {
        var surfaceTile = tile.data;
        if (!defined(surfaceTile)) {
            surfaceTile = tile.data = new GlobeSurfaceTile();
        }

        if (tile.state === QuadtreeTileLoadState.START) {
            prepareNewTile(tile, terrainProvider, imageryLayerCollection);
            tile.state = QuadtreeTileLoadState.LOADING;
        }

        if (tile.state === QuadtreeTileLoadState.LOADING) {
            processTerrainStateMachine(tile, frameState, terrainProvider, vertexArraysToDestroy);
        }

        // The terrain is renderable as soon as we have a valid vertex array.
        var isRenderable = defined(surfaceTile.vertexArray);

        // But it's not done loading until our two state machines are terminated.
        var isDoneLoading = !defined(surfaceTile.loadedTerrain) && !defined(surfaceTile.upsampledTerrain);

        // If this tile's terrain and imagery are just upsampled from its parent, mark the tile as
        // upsampled only.  We won't refine a tile if its four children are upsampled only.
        var isUpsampledOnly = defined(surfaceTile.terrainData) && surfaceTile.terrainData.wasCreatedByUpsampling();

        // Transition imagery states
        var tileImageryCollection = surfaceTile.imagery;
        for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
            var tileImagery = tileImageryCollection[i];
            if (!defined(tileImagery.loadingImagery)) {
                isUpsampledOnly = false;
                continue;
            }

            if (tileImagery.loadingImagery.state === ImageryState.PLACEHOLDER) {
                var imageryLayer = tileImagery.loadingImagery.imageryLayer;
                if (imageryLayer.imageryProvider.ready) {
                    // Remove the placeholder and add the actual skeletons (if any)
                    // at the same position.  Then continue the loop at the same index.
                    tileImagery.freeResources();
                    tileImageryCollection.splice(i, 1);
                    imageryLayer._createTileImagerySkeletons(tile, terrainProvider, i);
                    --i;
                    len = tileImageryCollection.length;
                    continue;
                } else {
                    isUpsampledOnly = false;
                }
            }

            var thisTileDoneLoading = tileImagery.processStateMachine(tile, frameState);
            isDoneLoading = isDoneLoading && thisTileDoneLoading;

            // The imagery is renderable as soon as we have any renderable imagery for this region.
            isRenderable = isRenderable && (thisTileDoneLoading || defined(tileImagery.readyImagery));

            isUpsampledOnly = isUpsampledOnly && defined(tileImagery.loadingImagery) &&
                              (tileImagery.loadingImagery.state === ImageryState.FAILED || tileImagery.loadingImagery.state === ImageryState.INVALID);
        }

        tile.upsampledFromParent = isUpsampledOnly;

        // The tile becomes renderable when the terrain and all imagery data are loaded.
        if (i === len) {
            if (isRenderable) {
                tile.renderable = true;
            }

            if (isDoneLoading) {
                tile.state = QuadtreeTileLoadState.DONE;
            }
        }
    };

    function prepareNewTile(tile, terrainProvider, imageryLayerCollection) {
        var surfaceTile = tile.data;

        var upsampleTileDetails = getUpsampleTileDetails(tile);
        if (defined(upsampleTileDetails)) {
            surfaceTile.upsampledTerrain = new TileTerrain(upsampleTileDetails);
        }

        if (isDataAvailable(tile, terrainProvider)) {
            surfaceTile.loadedTerrain = new TileTerrain();
        }

        // Map imagery tiles to this terrain tile
        for (var i = 0, len = imageryLayerCollection.length; i < len; ++i) {
            var layer = imageryLayerCollection.get(i);
            if (layer.show) {
                layer._createTileImagerySkeletons(tile, terrainProvider);
            }
        }
    }

    function processTerrainStateMachine(tile, frameState, terrainProvider, vertexArraysToDestroy) {
        var surfaceTile = tile.data;
        var loaded = surfaceTile.loadedTerrain;
        var upsampled = surfaceTile.upsampledTerrain;
        var suspendUpsampling = false;

        if (defined(loaded)) {
            loaded.processLoadStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level);

            // Publish the terrain data on the tile as soon as it is available.
            // We'll potentially need it to upsample child tiles.
            if (loaded.state >= TerrainState.RECEIVED) {
                if (surfaceTile.terrainData !== loaded.data) {
                    surfaceTile.terrainData = loaded.data;

                    // If there's a water mask included in the terrain data, create a
                    // texture for it.
                    createWaterMaskTextureIfNeeded(frameState.context, surfaceTile);

                    propagateNewLoadedDataToChildren(tile);
                }
                suspendUpsampling = true;
            }

            if (loaded.state === TerrainState.READY) {
                loaded.publishToTile(tile);

                if (defined(tile.data.vertexArray)) {
                    // Free the tiles existing vertex array on next render.
                    vertexArraysToDestroy.push(tile.data.vertexArray);
                }

                // Transfer ownership of the vertex array to the tile itself.
                tile.data.vertexArray = loaded.vertexArray;
                loaded.vertexArray = undefined;

                // No further loading or upsampling is necessary.
                surfaceTile.pickTerrain = defaultValue(surfaceTile.loadedTerrain, surfaceTile.upsampledTerrain);
                surfaceTile.loadedTerrain = undefined;
                surfaceTile.upsampledTerrain = undefined;
            } else if (loaded.state === TerrainState.FAILED) {
                // Loading failed for some reason, or data is simply not available,
                // so no need to continue trying to load.  Any retrying will happen before we
                // reach this point.
                surfaceTile.loadedTerrain = undefined;
            }
        }

        if (!suspendUpsampling && defined(upsampled)) {
            upsampled.processUpsampleStateMachine(frameState, terrainProvider, tile.x, tile.y, tile.level);

            // Publish the terrain data on the tile as soon as it is available.
            // We'll potentially need it to upsample child tiles.
            // It's safe to overwrite terrainData because we won't get here after
            // loaded terrain data has been received.
            if (upsampled.state >= TerrainState.RECEIVED) {
                if (surfaceTile.terrainData !== upsampled.data) {
                    surfaceTile.terrainData = upsampled.data;

                    // If the terrain provider has a water mask, "upsample" that as well
                    // by computing texture translation and scale.
                    if (terrainProvider.hasWaterMask) {
                        upsampleWaterMask(tile);
                    }

                    propagateNewUpsampledDataToChildren(tile);
                }
            }

            if (upsampled.state === TerrainState.READY) {
                upsampled.publishToTile(tile);

                if (defined(tile.data.vertexArray)) {
                    // Free the tiles existing vertex array on next render.
                    vertexArraysToDestroy.push(tile.data.vertexArray);
                }

                // Transfer ownership of the vertex array to the tile itself.
                tile.data.vertexArray = upsampled.vertexArray;
                upsampled.vertexArray = undefined;

                // No further upsampling is necessary.  We need to continue loading, though.
                surfaceTile.pickTerrain = surfaceTile.upsampledTerrain;
                surfaceTile.upsampledTerrain = undefined;
            } else if (upsampled.state === TerrainState.FAILED) {
                // Upsampling failed for some reason.  This is pretty much a catastrophic failure,
                // but maybe we'll be saved by loading.
                surfaceTile.upsampledTerrain = undefined;
            }
        }
    }

    function getUpsampleTileDetails(tile) {
        // Find the nearest ancestor with loaded terrain.
        var sourceTile = tile.parent;
        while (defined(sourceTile) && defined(sourceTile.data) && !defined(sourceTile.data.terrainData)) {
            sourceTile = sourceTile.parent;
        }

        if (!defined(sourceTile) || !defined(sourceTile.data)) {
            // No ancestors have loaded terrain - try again later.
            return undefined;
        }

        return {
            data : sourceTile.data.terrainData,
            x : sourceTile.x,
            y : sourceTile.y,
            level : sourceTile.level
        };
    }

    function propagateNewUpsampledDataToChildren(tile) {
        // Now that there's new data for this tile:
        //  - child tiles that were previously upsampled need to be re-upsampled based on the new data.

        // Generally this is only necessary when a child tile is upsampled, and then one
        // of its ancestors receives new (better) data and we want to re-upsample from the
        // new data.

        propagateNewUpsampledDataToChild(tile, tile._southwestChild);
        propagateNewUpsampledDataToChild(tile, tile._southeastChild);
        propagateNewUpsampledDataToChild(tile, tile._northwestChild);
        propagateNewUpsampledDataToChild(tile, tile._northeastChild);
    }

    function propagateNewUpsampledDataToChild(tile, childTile) {
        if (defined(childTile) && childTile.state !== QuadtreeTileLoadState.START) {
            var childSurfaceTile = childTile.data;
            if (defined(childSurfaceTile.terrainData) && !childSurfaceTile.terrainData.wasCreatedByUpsampling()) {
                // Data for the child tile has already been loaded.
                return;
            }

            // Restart the upsampling process, no matter its current state.
            // We create a new instance rather than just restarting the existing one
            // because there could be an asynchronous operation pending on the existing one.
            if (defined(childSurfaceTile.upsampledTerrain)) {
                childSurfaceTile.upsampledTerrain.freeResources();
            }
            childSurfaceTile.upsampledTerrain = new TileTerrain({
                data : tile.data.terrainData,
                x : tile.x,
                y : tile.y,
                level : tile.level
            });

            childTile.state = QuadtreeTileLoadState.LOADING;
        }
    }

    function propagateNewLoadedDataToChildren(tile) {
        var surfaceTile = tile.data;

        // Now that there's new data for this tile:
        //  - child tiles that were previously upsampled need to be re-upsampled based on the new data.
        //  - child tiles that were previously deemed unavailable may now be available.

        propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.southwestChild);
        propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.southeastChild);
        propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.northwestChild);
        propagateNewLoadedDataToChildTile(tile, surfaceTile, tile.northeastChild);
    }

    function propagateNewLoadedDataToChildTile(tile, surfaceTile, childTile) {
        if (childTile.state !== QuadtreeTileLoadState.START) {
            var childSurfaceTile = childTile.data;
            if (defined(childSurfaceTile.terrainData) && !childSurfaceTile.terrainData.wasCreatedByUpsampling()) {
                // Data for the child tile has already been loaded.
                return;
            }

            // Restart the upsampling process, no matter its current state.
            // We create a new instance rather than just restarting the existing one
            // because there could be an asynchronous operation pending on the existing one.
            if (defined(childSurfaceTile.upsampledTerrain)) {
                childSurfaceTile.upsampledTerrain.freeResources();
            }
            childSurfaceTile.upsampledTerrain = new TileTerrain({
                data : surfaceTile.terrainData,
                x : tile.x,
                y : tile.y,
                level : tile.level
            });

            if (surfaceTile.terrainData.isChildAvailable(tile.x, tile.y, childTile.x, childTile.y)) {
                // Data is available for the child now.  It might have been before, too.
                if (!defined(childSurfaceTile.loadedTerrain)) {
                    // No load process is in progress, so start one.
                    childSurfaceTile.loadedTerrain = new TileTerrain();
                }
            }

            childTile.state = QuadtreeTileLoadState.LOADING;
        }
    }

    function isDataAvailable(tile, terrainProvider) {
        var tileDataAvailable = terrainProvider.getTileDataAvailable(tile.x, tile.y, tile.level);
        if (defined(tileDataAvailable)) {
            return tileDataAvailable;
        }

        var parent = tile.parent;
        if (!defined(parent)) {
            // Data is assumed to be available for root tiles.
            return true;
        }

        if (!defined(parent.data) || !defined(parent.data.terrainData)) {
            // Parent tile data is not yet received or upsampled, so assume (for now) that this
            // child tile is not available.
            return false;
        }

        return parent.data.terrainData.isChildAvailable(parent.x, parent.y, tile.x, tile.y);
    }

    function getContextWaterMaskData(context) {
        var data = context.cache.tile_waterMaskData;

        if (!defined(data)) {
            var allWaterTexture = new Texture({
                context : context,
                pixelFormat : PixelFormat.LUMINANCE,
                pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
                source : {
                    arrayBufferView : new Uint8Array([255]),
                    width : 1,
                    height : 1
                }
            });
            allWaterTexture.referenceCount = 1;

            var sampler = new Sampler({
                wrapS : TextureWrap.CLAMP_TO_EDGE,
                wrapT : TextureWrap.CLAMP_TO_EDGE,
                minificationFilter : TextureMinificationFilter.LINEAR,
                magnificationFilter : TextureMagnificationFilter.LINEAR
            });

            data = {
                allWaterTexture : allWaterTexture,
                sampler : sampler,
                destroy : function() {
                    this.allWaterTexture.destroy();
                }
            };

            context.cache.tile_waterMaskData = data;
        }

        return data;
    }

    function createWaterMaskTextureIfNeeded(context, surfaceTile) {
        var previousTexture = surfaceTile.waterMaskTexture;
        if (defined(previousTexture)) {
            --previousTexture.referenceCount;
            if (previousTexture.referenceCount === 0) {
                previousTexture.destroy();
            }
            surfaceTile.waterMaskTexture = undefined;
        }

        var waterMask = surfaceTile.terrainData.waterMask;
        if (!defined(waterMask)) {
            return;
        }

        var waterMaskData = getContextWaterMaskData(context);
        var texture;

        var waterMaskLength = waterMask.length;
        if (waterMaskLength === 1) {
            // Length 1 means the tile is entirely land or entirely water.
            // A value of 0 indicates entirely land, a value of 1 indicates entirely water.
            if (waterMask[0] !== 0) {
                texture = waterMaskData.allWaterTexture;
            } else {
                // Leave the texture undefined if the tile is entirely land.
                return;
            }
        } else {
            var textureSize = Math.sqrt(waterMaskLength);
            texture = new Texture({
                context : context,
                pixelFormat : PixelFormat.LUMINANCE,
                pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
                source : {
                    width : textureSize,
                    height : textureSize,
                    arrayBufferView : waterMask
                },
                sampler : waterMaskData.sampler
            });

            texture.referenceCount = 0;
        }

        ++texture.referenceCount;
        surfaceTile.waterMaskTexture = texture;

        Cartesian4.fromElements(0.0, 0.0, 1.0, 1.0, surfaceTile.waterMaskTranslationAndScale);
    }

    function upsampleWaterMask(tile) {
        var surfaceTile = tile.data;

        // Find the nearest ancestor with loaded terrain.
        var sourceTile = tile.parent;
        while (defined(sourceTile) && !defined(sourceTile.data.terrainData) || sourceTile.data.terrainData.wasCreatedByUpsampling()) {
            sourceTile = sourceTile.parent;
        }

        if (!defined(sourceTile) || !defined(sourceTile.data.waterMaskTexture)) {
            // No ancestors have a water mask texture - try again later.
            return;
        }

        surfaceTile.waterMaskTexture = sourceTile.data.waterMaskTexture;
        ++surfaceTile.waterMaskTexture.referenceCount;

        // Compute the water mask translation and scale
        var sourceTileRectangle = sourceTile.rectangle;
        var tileRectangle = tile.rectangle;
        var tileWidth = tileRectangle.width;
        var tileHeight = tileRectangle.height;

        var scaleX = tileWidth / sourceTileRectangle.width;
        var scaleY = tileHeight / sourceTileRectangle.height;
        surfaceTile.waterMaskTranslationAndScale.x = scaleX * (tileRectangle.west - sourceTileRectangle.west) / tileWidth;
        surfaceTile.waterMaskTranslationAndScale.y = scaleY * (tileRectangle.south - sourceTileRectangle.south) / tileHeight;
        surfaceTile.waterMaskTranslationAndScale.z = scaleX;
        surfaceTile.waterMaskTranslationAndScale.w = scaleY;
    }

    return GlobeSurfaceTile;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ReprojectWebMercatorFS',[],function() {
    'use strict';
    return "uniform sampler2D u_texture;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ReprojectWebMercatorVS',[],function() {
    'use strict';
    return "attribute vec4 position;\n\
attribute float webMercatorT;\n\
\n\
uniform vec2 u_textureDimensions;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    v_textureCoordinates = vec2(position.x, webMercatorT);\n\
    gl_Position = czm_viewportOrthographic * (position * vec4(u_textureDimensions, 1.0, 1.0));\n\
}\n\
";
});
/*global define*/
define('Scene/Imagery',[
        '../Core/defined',
        '../Core/destroyObject',
        './ImageryState'
    ], function(
        defined,
        destroyObject,
        ImageryState) {
    'use strict';

    /**
     * Stores details about a tile of imagery.
     *
     * @alias Imagery
     * @private
     */
    function Imagery(imageryLayer, x, y, level, rectangle) {
        this.imageryLayer = imageryLayer;
        this.x = x;
        this.y = y;
        this.level = level;

        if (level !== 0) {
            var parentX = x / 2 | 0;
            var parentY = y / 2 | 0;
            var parentLevel = level - 1;
            this.parent = imageryLayer.getImageryFromCache(parentX, parentY, parentLevel);
        }

        this.state = ImageryState.UNLOADED;
        this.imageUrl = undefined;
        this.image = undefined;
        this.texture = undefined;
        this.textureWebMercator = undefined;
        this.credits = undefined;
        this.referenceCount = 0;

        if (!defined(rectangle) && imageryLayer.imageryProvider.ready) {
            var tilingScheme = imageryLayer.imageryProvider.tilingScheme;
            rectangle = tilingScheme.tileXYToRectangle(x, y, level);
        }

        this.rectangle = rectangle;
    }
    Imagery.createPlaceholder = function(imageryLayer) {
        var result = new Imagery(imageryLayer, 0, 0, 0);
        result.addReference();
        result.state = ImageryState.PLACEHOLDER;
        return result;
    };

    Imagery.prototype.addReference = function() {
        ++this.referenceCount;
    };

    Imagery.prototype.releaseReference = function() {
        --this.referenceCount;

        if (this.referenceCount === 0) {
            this.imageryLayer.removeImageryFromCache(this);

            if (defined(this.parent)) {
                this.parent.releaseReference();
            }

            if (defined(this.image) && defined(this.image.destroy)) {
                this.image.destroy();
            }

            if (defined(this.texture)) {
                this.texture.destroy();
            }

            if (defined(this.textureWebMercator) && this.texture !== this.textureWebMercator) {
                this.textureWebMercator.destroy();
            }

            destroyObject(this);

            return 0;
        }

        return this.referenceCount;
    };

    Imagery.prototype.processStateMachine = function(frameState, needGeographicProjection) {
        if (this.state === ImageryState.UNLOADED) {
            this.state = ImageryState.TRANSITIONING;
            this.imageryLayer._requestImagery(this);
        }

        if (this.state === ImageryState.RECEIVED) {
            this.state = ImageryState.TRANSITIONING;
            this.imageryLayer._createTexture(frameState.context, this);
        }

        // If the imagery is already ready, but we need a geographic version and don't have it yet,
        // we still need to do the reprojection step. This can happen if the Web Mercator version
        // is fine initially, but the geographic one is needed later.
        var needsReprojection = this.state === ImageryState.READY && needGeographicProjection && !this.texture;

        if (this.state === ImageryState.TEXTURE_LOADED || needsReprojection) {
            this.state = ImageryState.TRANSITIONING;
            this.imageryLayer._reprojectTexture(frameState, this, needGeographicProjection);
        }
    };

    return Imagery;
});

/*global define*/
define('Scene/ImagerySplitDirection',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * The direction to display an ImageryLayer relative to the {@link Scene#imagerySplitPosition}.
     *
     * @exports ImagerySplitDirection
     *
     * @see ImageryLayer#splitDirection
     */
    var ImagerySplitDirection = {
        /**
         * Display the ImageryLayer to the left of the {@link Scene#imagerySplitPosition}.
         *
         * @type {Number}
         * @constant
         */
        LEFT : -1.0,

        /**
        *  Always display the ImageryLayer.
         *
         * @type {Number}
         * @constant
         */
        NONE: 0.0,

        /**
         * Display the ImageryLayer to the right of the {@link Scene#imagerySplitPosition}.
         *
         * @type {Number}
         * @constant
         */
        RIGHT : 1.0
    };

    return freezeObject(ImagerySplitDirection);
});

/*global define*/
define('Scene/TileImagery',[
        '../Core/defined',
        './ImageryState'
    ], function(
        defined,
        ImageryState) {
    'use strict';

    /**
     * The assocation between a terrain tile and an imagery tile.
     *
     * @alias TileImagery
     * @private
     *
     * @param {Imagery} imagery The imagery tile.
     * @param {Cartesian4} textureCoordinateRectangle The texture rectangle of the tile that is covered
     *        by the imagery, where X=west, Y=south, Z=east, W=north.
     * @param {Boolean} useWebMercatorT true to use the Web Mercator texture coordinates for this imagery tile.
     */
    function TileImagery(imagery, textureCoordinateRectangle, useWebMercatorT) {
        this.readyImagery = undefined;
        this.loadingImagery = imagery;
        this.textureCoordinateRectangle = textureCoordinateRectangle;
        this.textureTranslationAndScale = undefined;
        this.useWebMercatorT = useWebMercatorT;
    }

    /**
     * Frees the resources held by this instance.
     */
    TileImagery.prototype.freeResources = function() {
        if (defined(this.readyImagery)) {
            this.readyImagery.releaseReference();
        }

        if (defined(this.loadingImagery)) {
            this.loadingImagery.releaseReference();
        }
    };

    /**
     * Processes the load state machine for this instance.
     *
     * @param {Tile} tile The tile to which this instance belongs.
     * @param {FrameState} frameState The frameState.
     * @returns {Boolean} True if this instance is done loading; otherwise, false.
     */
    TileImagery.prototype.processStateMachine = function(tile, frameState) {
        var loadingImagery = this.loadingImagery;
        var imageryLayer = loadingImagery.imageryLayer;

        loadingImagery.processStateMachine(frameState, !this.useWebMercatorT);

        if (loadingImagery.state === ImageryState.READY) {
            if (defined(this.readyImagery)) {
                this.readyImagery.releaseReference();
            }
            this.readyImagery = this.loadingImagery;
            this.loadingImagery = undefined;
            this.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, this);
            return true; // done loading
        }

        // Find some ancestor imagery we can use while this imagery is still loading.
        var ancestor = loadingImagery.parent;
        var closestAncestorThatNeedsLoading;
        while (defined(ancestor) && (ancestor.state !== ImageryState.READY || (!this.useWebMercatorT && !defined(ancestor.texture)))) {
            if (ancestor.state !== ImageryState.FAILED && ancestor.state !== ImageryState.INVALID) {
                // ancestor is still loading
                closestAncestorThatNeedsLoading = closestAncestorThatNeedsLoading || ancestor;
            }
            ancestor = ancestor.parent;
        }

        if (this.readyImagery !== ancestor) {
            if (defined(this.readyImagery)) {
                this.readyImagery.releaseReference();
            }

            this.readyImagery = ancestor;

            if (defined(ancestor)) {
                ancestor.addReference();
                this.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, this);
            }
        }

        if (loadingImagery.state === ImageryState.FAILED || loadingImagery.state === ImageryState.INVALID) {
            // The imagery tile is failed or invalid, so we'd like to use an ancestor instead.
            if (defined(closestAncestorThatNeedsLoading)) {
                // Push the ancestor's load process along a bit.  This is necessary because some ancestor imagery
                // tiles may not be attached directly to a terrain tile.  Such tiles will never load if
                // we don't do it here.
                closestAncestorThatNeedsLoading.processStateMachine(frameState, !this.useWebMercatorT);
                return false; // not done loading
            } else {
                // This imagery tile is failed or invalid, and we have the "best available" substitute.
                return true; // done loading
            }
        }

        return false; // not done loading
    };

    return TileImagery;
});

/*global define*/
define('Scene/ImageryLayer',[
        '../Core/Cartesian2',
        '../Core/Cartesian4',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/FeatureDetection',
        '../Core/GeographicTilingScheme',
        '../Core/IndexDatatype',
        '../Core/Math',
        '../Core/PixelFormat',
        '../Core/Rectangle',
        '../Core/TerrainProvider',
        '../Core/TileProviderError',
        '../Core/WebMercatorProjection',
        '../Core/WebMercatorTilingScheme',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/ComputeCommand',
        '../Renderer/ContextLimits',
        '../Renderer/MipmapHint',
        '../Renderer/Sampler',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/Texture',
        '../Renderer/TextureMagnificationFilter',
        '../Renderer/TextureMinificationFilter',
        '../Renderer/TextureWrap',
        '../Renderer/VertexArray',
        '../Shaders/ReprojectWebMercatorFS',
        '../Shaders/ReprojectWebMercatorVS',
        '../ThirdParty/when',
        './Imagery',
        './ImagerySplitDirection',
        './ImageryState',
        './TileImagery'
    ], function(
        Cartesian2,
        Cartesian4,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        FeatureDetection,
        GeographicTilingScheme,
        IndexDatatype,
        CesiumMath,
        PixelFormat,
        Rectangle,
        TerrainProvider,
        TileProviderError,
        WebMercatorProjection,
        WebMercatorTilingScheme,
        Buffer,
        BufferUsage,
        ComputeCommand,
        ContextLimits,
        MipmapHint,
        Sampler,
        ShaderProgram,
        ShaderSource,
        Texture,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap,
        VertexArray,
        ReprojectWebMercatorFS,
        ReprojectWebMercatorVS,
        when,
        Imagery,
        ImagerySplitDirection,
        ImageryState,
        TileImagery) {
    'use strict';

    /**
     * An imagery layer that displays tiled image data from a single imagery provider
     * on a {@link Globe}.
     *
     * @alias ImageryLayer
     * @constructor
     *
     * @param {ImageryProvider} imageryProvider The imagery provider to use.
     * @param {Object} [options] Object with the following properties:
     * @param {Rectangle} [options.rectangle=imageryProvider.rectangle] The rectangle of the layer.  This rectangle
     *        can limit the visible portion of the imagery provider.
     * @param {Number|Function} [options.alpha=1.0] The alpha blending value of this layer, from 0.0 to 1.0.
     *                          This can either be a simple number or a function with the signature
     *                          <code>function(frameState, layer, x, y, level)</code>.  The function is passed the
     *                          current frame state, this layer, and the x, y, and level coordinates of the
     *                          imagery tile for which the alpha is required, and it is expected to return
     *                          the alpha value to use for the tile.
     * @param {Number|Function} [options.brightness=1.0] The brightness of this layer.  1.0 uses the unmodified imagery
     *                          color.  Less than 1.0 makes the imagery darker while greater than 1.0 makes it brighter.
     *                          This can either be a simple number or a function with the signature
     *                          <code>function(frameState, layer, x, y, level)</code>.  The function is passed the
     *                          current frame state, this layer, and the x, y, and level coordinates of the
     *                          imagery tile for which the brightness is required, and it is expected to return
     *                          the brightness value to use for the tile.  The function is executed for every
     *                          frame and for every tile, so it must be fast.
     * @param {Number|Function} [options.contrast=1.0] The contrast of this layer.  1.0 uses the unmodified imagery color.
     *                          Less than 1.0 reduces the contrast while greater than 1.0 increases it.
     *                          This can either be a simple number or a function with the signature
     *                          <code>function(frameState, layer, x, y, level)</code>.  The function is passed the
     *                          current frame state, this layer, and the x, y, and level coordinates of the
     *                          imagery tile for which the contrast is required, and it is expected to return
     *                          the contrast value to use for the tile.  The function is executed for every
     *                          frame and for every tile, so it must be fast.
     * @param {Number|Function} [options.hue=0.0] The hue of this layer.  0.0 uses the unmodified imagery color.
     *                          This can either be a simple number or a function with the signature
     *                          <code>function(frameState, layer, x, y, level)</code>.  The function is passed the
     *                          current frame state, this layer, and the x, y, and level coordinates
     *                          of the imagery tile for which the hue is required, and it is expected to return
     *                          the contrast value to use for the tile.  The function is executed for every
     *                          frame and for every tile, so it must be fast.
     * @param {Number|Function} [options.saturation=1.0] The saturation of this layer.  1.0 uses the unmodified imagery color.
     *                          Less than 1.0 reduces the saturation while greater than 1.0 increases it.
     *                          This can either be a simple number or a function with the signature
     *                          <code>function(frameState, layer, x, y, level)</code>.  The function is passed the
     *                          current frame state, this layer, and the x, y, and level coordinates
     *                          of the imagery tile for which the saturation is required, and it is expected to return
     *                          the contrast value to use for the tile.  The function is executed for every
     *                          frame and for every tile, so it must be fast.
     * @param {Number|Function} [options.gamma=1.0] The gamma correction to apply to this layer.  1.0 uses the unmodified imagery color.
     *                          This can either be a simple number or a function with the signature
     *                          <code>function(frameState, layer, x, y, level)</code>.  The function is passed the
     *                          current frame state, this layer, and the x, y, and level coordinates of the
     *                          imagery tile for which the gamma is required, and it is expected to return
     *                          the gamma value to use for the tile.  The function is executed for every
     *                          frame and for every tile, so it must be fast.
     * @param {ImagerySplitDirection|Function} [options.splitDirection=ImagerySplitDirection.NONE] The {@link ImagerySplitDirection} split to apply to this layer.
     * @param {Boolean} [options.show=true] True if the layer is shown; otherwise, false.
     * @param {Number} [options.maximumAnisotropy=maximum supported] The maximum anisotropy level to use
     *        for texture filtering.  If this parameter is not specified, the maximum anisotropy supported
     *        by the WebGL stack will be used.  Larger values make the imagery look better in horizon
     *        views.
     * @param {Number} [options.minimumTerrainLevel] The minimum terrain level-of-detail at which to show this imagery layer,
     *                 or undefined to show it at all levels.  Level zero is the least-detailed level.
     * @param {Number} [options.maximumTerrainLevel] The maximum terrain level-of-detail at which to show this imagery layer,
     *                 or undefined to show it at all levels.  Level zero is the least-detailed level.
     */
    function ImageryLayer(imageryProvider, options) {
        this._imageryProvider = imageryProvider;

        options = defaultValue(options, {});

        /**
         * The alpha blending value of this layer, with 0.0 representing fully transparent and
         * 1.0 representing fully opaque.
         *
         * @type {Number}
         * @default 1.0
         */
        this.alpha = defaultValue(options.alpha, defaultValue(imageryProvider.defaultAlpha, 1.0));

        /**
         * The brightness of this layer.  1.0 uses the unmodified imagery color.  Less than 1.0
         * makes the imagery darker while greater than 1.0 makes it brighter.
         *
         * @type {Number}
         * @default {@link ImageryLayer.DEFAULT_BRIGHTNESS}
         */
        this.brightness = defaultValue(options.brightness, defaultValue(imageryProvider.defaultBrightness, ImageryLayer.DEFAULT_BRIGHTNESS));

        /**
         * The contrast of this layer.  1.0 uses the unmodified imagery color.  Less than 1.0 reduces
         * the contrast while greater than 1.0 increases it.
         *
         * @type {Number}
         * @default {@link ImageryLayer.DEFAULT_CONTRAST}
         */
        this.contrast = defaultValue(options.contrast, defaultValue(imageryProvider.defaultContrast, ImageryLayer.DEFAULT_CONTRAST));

        /**
         * The hue of this layer in radians. 0.0 uses the unmodified imagery color.
         *
         * @type {Number}
         * @default {@link ImageryLayer.DEFAULT_HUE}
         */
        this.hue = defaultValue(options.hue, defaultValue(imageryProvider.defaultHue, ImageryLayer.DEFAULT_HUE));

        /**
         * The saturation of this layer. 1.0 uses the unmodified imagery color. Less than 1.0 reduces the
         * saturation while greater than 1.0 increases it.
         *
         * @type {Number}
         * @default {@link ImageryLayer.DEFAULT_SATURATION}
         */
        this.saturation = defaultValue(options.saturation, defaultValue(imageryProvider.defaultSaturation, ImageryLayer.DEFAULT_SATURATION));

        /**
         * The gamma correction to apply to this layer.  1.0 uses the unmodified imagery color.
         *
         * @type {Number}
         * @default {@link ImageryLayer.DEFAULT_GAMMA}
         */
        this.gamma = defaultValue(options.gamma, defaultValue(imageryProvider.defaultGamma, ImageryLayer.DEFAULT_GAMMA));

        /**
         * The {@link ImagerySplitDirection} to apply to this layer.
         *
         * @type {ImagerySplitDirection}
         * @default {@link ImageryLayer.DEFAULT_SPLIT}
         */
        this.splitDirection = defaultValue(options.splitDirection, defaultValue(imageryProvider.defaultSplit, ImageryLayer.DEFAULT_SPLIT));

        /**
         * Determines if this layer is shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = defaultValue(options.show, true);

        this._minimumTerrainLevel = options.minimumTerrainLevel;
        this._maximumTerrainLevel = options.maximumTerrainLevel;

        this._rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
        this._maximumAnisotropy = options.maximumAnisotropy;

        this._imageryCache = {};

        this._skeletonPlaceholder = new TileImagery(Imagery.createPlaceholder(this));

        // The value of the show property on the last update.
        this._show = true;

        // The index of this layer in the ImageryLayerCollection.
        this._layerIndex = -1;

        // true if this is the base (lowest shown) layer.
        this._isBaseLayer = false;

        this._requestImageError = undefined;

        this._reprojectComputeCommands = [];
    }

    defineProperties(ImageryLayer.prototype, {

        /**
         * Gets the imagery provider for this layer.
         * @memberof ImageryLayer.prototype
         * @type {ImageryProvider}
         * @readonly
         */
        imageryProvider : {
            get: function() {
                return this._imageryProvider;
            }
        },

        /**
         * Gets the rectangle of this layer.  If this rectangle is smaller than the rectangle of the
         * {@link ImageryProvider}, only a portion of the imagery provider is shown.
         * @memberof ImageryLayer.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle: {
            get: function() {
                return this._rectangle;
            }
        }
    });


    /**
     * This value is used as the default brightness for the imagery layer if one is not provided during construction
     * or by the imagery provider. This value does not modify the brightness of the imagery.
     * @type {Number}
     * @default 1.0
     */
    ImageryLayer.DEFAULT_BRIGHTNESS = 1.0;
    /**
     * This value is used as the default contrast for the imagery layer if one is not provided during construction
     * or by the imagery provider. This value does not modify the contrast of the imagery.
     * @type {Number}
     * @default 1.0
     */
    ImageryLayer.DEFAULT_CONTRAST = 1.0;
    /**
     * This value is used as the default hue for the imagery layer if one is not provided during construction
     * or by the imagery provider. This value does not modify the hue of the imagery.
     * @type {Number}
     * @default 0.0
     */
    ImageryLayer.DEFAULT_HUE = 0.0;
    /**
     * This value is used as the default saturation for the imagery layer if one is not provided during construction
     * or by the imagery provider. This value does not modify the saturation of the imagery.
     * @type {Number}
     * @default 1.0
     */
    ImageryLayer.DEFAULT_SATURATION = 1.0;
    /**
     * This value is used as the default gamma for the imagery layer if one is not provided during construction
     * or by the imagery provider. This value does not modify the gamma of the imagery.
     * @type {Number}
     * @default 1.0
     */
    ImageryLayer.DEFAULT_GAMMA = 1.0;

    /**
     * This value is used as the default spliat for the imagery layer if one is not provided during construction
     * or by the imagery provider.
     * @type {ImagerySplitDirection}
     * @default ImagerySplitDirection.NONE
     */
    ImageryLayer.DEFAULT_SPLIT = ImagerySplitDirection.NONE;

    /**
     * Gets a value indicating whether this layer is the base layer in the
     * {@link ImageryLayerCollection}.  The base layer is the one that underlies all
     * others.  It is special in that it is treated as if it has global rectangle, even if
     * it actually does not, by stretching the texels at the edges over the entire
     * globe.
     *
     * @returns {Boolean} true if this is the base layer; otherwise, false.
     */
    ImageryLayer.prototype.isBaseLayer = function() {
        return this._isBaseLayer;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see ImageryLayer#destroy
     */
    ImageryLayer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * imageryLayer = imageryLayer && imageryLayer.destroy();
     *
     * @see ImageryLayer#isDestroyed
     */
    ImageryLayer.prototype.destroy = function() {
        return destroyObject(this);
    };

    var imageryBoundsScratch = new Rectangle();
    var tileImageryBoundsScratch = new Rectangle();
    var clippedRectangleScratch = new Rectangle();
    var terrainRectangleScratch = new Rectangle();

    /**
     * Computes the intersection of this layer's rectangle with the imagery provider's availability rectangle,
     * producing the overall bounds of imagery that can be produced by this layer.
     *
     * @returns {Promise.<Rectangle>} A promise to a rectangle which defines the overall bounds of imagery that can be produced by this layer.
     *
     * @example
     * // Zoom to an imagery layer.
     * imageryLayer.getViewableRectangle().then(function (rectangle) {
     *     return camera.flyTo({
     *         destination: rectangle
     *     });
     * });
     */
    ImageryLayer.prototype.getViewableRectangle = function() {
        var imageryProvider = this._imageryProvider;
        var rectangle = this._rectangle;
        return imageryProvider.readyPromise.then(function() {
            return Rectangle.intersection(imageryProvider.rectangle, rectangle);
        });
    };

    /**
     * Create skeletons for the imagery tiles that partially or completely overlap a given terrain
     * tile.
     *
     * @private
     *
     * @param {Tile} tile The terrain tile.
     * @param {TerrainProvider} terrainProvider The terrain provider associated with the terrain tile.
     * @param {Number} insertionPoint The position to insert new skeletons before in the tile's imagery list.
     * @returns {Boolean} true if this layer overlaps any portion of the terrain tile; otherwise, false.
     */
    ImageryLayer.prototype._createTileImagerySkeletons = function(tile, terrainProvider, insertionPoint) {
        var surfaceTile = tile.data;

        if (defined(this._minimumTerrainLevel) && tile.level < this._minimumTerrainLevel) {
            return false;
        }
        if (defined(this._maximumTerrainLevel) && tile.level > this._maximumTerrainLevel) {
            return false;
        }

        var imageryProvider = this._imageryProvider;

        if (!defined(insertionPoint)) {
            insertionPoint = surfaceTile.imagery.length;
        }

        if (!imageryProvider.ready) {
            // The imagery provider is not ready, so we can't create skeletons, yet.
            // Instead, add a placeholder so that we'll know to create
            // the skeletons once the provider is ready.
            this._skeletonPlaceholder.loadingImagery.addReference();
            surfaceTile.imagery.splice(insertionPoint, 0, this._skeletonPlaceholder);
            return true;
        }

        // Use Web Mercator for our texture coordinate computations if this imagery layer uses
        // that projection and the terrain tile falls entirely inside the valid bounds of the
        // projection.
        var useWebMercatorT = imageryProvider.tilingScheme instanceof WebMercatorTilingScheme &&
                              tile.rectangle.north < WebMercatorProjection.MaximumLatitude &&
                              tile.rectangle.south > -WebMercatorProjection.MaximumLatitude;

        // Compute the rectangle of the imagery from this imageryProvider that overlaps
        // the geometry tile.  The ImageryProvider and ImageryLayer both have the
        // opportunity to constrain the rectangle.  The imagery TilingScheme's rectangle
        // always fully contains the ImageryProvider's rectangle.
        var imageryBounds = Rectangle.intersection(imageryProvider.rectangle, this._rectangle, imageryBoundsScratch);
        var rectangle = Rectangle.intersection(tile.rectangle, imageryBounds, tileImageryBoundsScratch);

        if (!defined(rectangle)) {
            // There is no overlap between this terrain tile and this imagery
            // provider.  Unless this is the base layer, no skeletons need to be created.
            // We stretch texels at the edge of the base layer over the entire globe.
            if (!this.isBaseLayer()) {
                return false;
            }

            var baseImageryRectangle = imageryBounds;
            var baseTerrainRectangle = tile.rectangle;
            rectangle = tileImageryBoundsScratch;

            if (baseTerrainRectangle.south >= baseImageryRectangle.north) {
                rectangle.north = rectangle.south = baseImageryRectangle.north;
            } else if (baseTerrainRectangle.north <= baseImageryRectangle.south) {
                rectangle.north = rectangle.south = baseImageryRectangle.south;
            } else {
                rectangle.south = Math.max(baseTerrainRectangle.south, baseImageryRectangle.south);
                rectangle.north = Math.min(baseTerrainRectangle.north, baseImageryRectangle.north);
            }

            if (baseTerrainRectangle.west >= baseImageryRectangle.east) {
                rectangle.west = rectangle.east = baseImageryRectangle.east;
            } else if (baseTerrainRectangle.east <= baseImageryRectangle.west) {
                rectangle.west = rectangle.east = baseImageryRectangle.west;
            } else {
                rectangle.west = Math.max(baseTerrainRectangle.west, baseImageryRectangle.west);
                rectangle.east = Math.min(baseTerrainRectangle.east, baseImageryRectangle.east);
            }
        }

        var latitudeClosestToEquator = 0.0;
        if (rectangle.south > 0.0) {
            latitudeClosestToEquator = rectangle.south;
        } else if (rectangle.north < 0.0) {
            latitudeClosestToEquator = rectangle.north;
        }

        // Compute the required level in the imagery tiling scheme.
        // The errorRatio should really be imagerySSE / terrainSSE rather than this hard-coded value.
        // But first we need configurable imagery SSE and we need the rendering to be able to handle more
        // images attached to a terrain tile than there are available texture units.  So that's for the future.
        var errorRatio = 1.0;
        var targetGeometricError = errorRatio * terrainProvider.getLevelMaximumGeometricError(tile.level);
        var imageryLevel = getLevelWithMaximumTexelSpacing(this, targetGeometricError, latitudeClosestToEquator);
        imageryLevel = Math.max(0, imageryLevel);
        var maximumLevel = imageryProvider.maximumLevel;
        if (imageryLevel > maximumLevel) {
            imageryLevel = maximumLevel;
        }

        if (defined(imageryProvider.minimumLevel)) {
            var minimumLevel = imageryProvider.minimumLevel;
            if (imageryLevel < minimumLevel) {
                imageryLevel = minimumLevel;
            }
        }

        var imageryTilingScheme = imageryProvider.tilingScheme;
        var northwestTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.northwest(rectangle), imageryLevel);
        var southeastTileCoordinates = imageryTilingScheme.positionToTileXY(Rectangle.southeast(rectangle), imageryLevel);

        // If the southeast corner of the rectangle lies very close to the north or west side
        // of the southeast tile, we don't actually need the southernmost or easternmost
        // tiles.
        // Similarly, if the northwest corner of the rectangle lies very close to the south or east side
        // of the northwest tile, we don't actually need the northernmost or westernmost tiles.

        // We define "very close" as being within 1/512 of the width of the tile.
        var veryCloseX = tile.rectangle.width / 512.0;
        var veryCloseY = tile.rectangle.height / 512.0;

        var northwestTileRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
        if (Math.abs(northwestTileRectangle.south - tile.rectangle.north) < veryCloseY && northwestTileCoordinates.y < southeastTileCoordinates.y) {
            ++northwestTileCoordinates.y;
        }
        if (Math.abs(northwestTileRectangle.east - tile.rectangle.west) < veryCloseX && northwestTileCoordinates.x < southeastTileCoordinates.x) {
            ++northwestTileCoordinates.x;
        }

        var southeastTileRectangle = imageryTilingScheme.tileXYToRectangle(southeastTileCoordinates.x, southeastTileCoordinates.y, imageryLevel);
        if (Math.abs(southeastTileRectangle.north - tile.rectangle.south) < veryCloseY && southeastTileCoordinates.y > northwestTileCoordinates.y) {
            --southeastTileCoordinates.y;
        }
        if (Math.abs(southeastTileRectangle.west - tile.rectangle.east) < veryCloseX && southeastTileCoordinates.x > northwestTileCoordinates.x) {
            --southeastTileCoordinates.x;
        }

        // Create TileImagery instances for each imagery tile overlapping this terrain tile.
        // We need to do all texture coordinate computations in the imagery tile's tiling scheme.

        var terrainRectangle = Rectangle.clone(tile.rectangle, terrainRectangleScratch);
        var imageryRectangle = imageryTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel);
        var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);

        var imageryTileXYToRectangle;
        if (useWebMercatorT) {
            imageryTilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangle);
            imageryTilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryRectangle);
            imageryTilingScheme.rectangleToNativeRectangle(clippedImageryRectangle, clippedImageryRectangle);
            imageryTilingScheme.rectangleToNativeRectangle(imageryBounds, imageryBounds);
            imageryTileXYToRectangle = imageryTilingScheme.tileXYToNativeRectangle.bind(imageryTilingScheme);
            veryCloseX = terrainRectangle.width / 512.0;
            veryCloseY = terrainRectangle.height / 512.0;
        } else {
            imageryTileXYToRectangle = imageryTilingScheme.tileXYToRectangle.bind(imageryTilingScheme);
        }

        var minU;
        var maxU = 0.0;

        var minV = 1.0;
        var maxV;

        // If this is the northern-most or western-most tile in the imagery tiling scheme,
        // it may not start at the northern or western edge of the terrain tile.
        // Calculate where it does start.
        if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.west - terrainRectangle.west) >= veryCloseX) {
            maxU = Math.min(1.0, (clippedImageryRectangle.west - terrainRectangle.west) / terrainRectangle.width);
        }

        if (!this.isBaseLayer() && Math.abs(clippedImageryRectangle.north - terrainRectangle.north) >= veryCloseY) {
            minV = Math.max(0.0, (clippedImageryRectangle.north - terrainRectangle.south) / terrainRectangle.height);
        }

        var initialMinV = minV;

        for ( var i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) {
            minU = maxU;

            imageryRectangle = imageryTileXYToRectangle(i, northwestTileCoordinates.y, imageryLevel);
            clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch);

            if (!defined(clippedImageryRectangle)) {
                continue;
            }

            maxU = Math.min(1.0, (clippedImageryRectangle.east - terrainRectangle.west) / terrainRectangle.width);

            // If this is the eastern-most imagery tile mapped to this terrain tile,
            // and there are more imagery tiles to the east of this one, the maxU
            // should be 1.0 to make sure rounding errors don't make the last
            // image fall shy of the edge of the terrain tile.
            if (i === southeastTileCoordinates.x && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.east - terrainRectangle.east) < veryCloseX)) {
                maxU = 1.0;
            }

            minV = initialMinV;

            for ( var j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) {
                maxV = minV;

                imageryRectangle = imageryTileXYToRectangle(i, j, imageryLevel);
                clippedImageryRectangle = Rectangle.simpleIntersection(imageryRectangle, imageryBounds, clippedRectangleScratch);

                if (!defined(clippedImageryRectangle)) {
                    continue;
                }

                minV = Math.max(0.0, (clippedImageryRectangle.south - terrainRectangle.south) / terrainRectangle.height);

                // If this is the southern-most imagery tile mapped to this terrain tile,
                // and there are more imagery tiles to the south of this one, the minV
                // should be 0.0 to make sure rounding errors don't make the last
                // image fall shy of the edge of the terrain tile.
                if (j === southeastTileCoordinates.y && (this.isBaseLayer() || Math.abs(clippedImageryRectangle.south - terrainRectangle.south) < veryCloseY)) {
                    minV = 0.0;
                }

                var texCoordsRectangle = new Cartesian4(minU, minV, maxU, maxV);
                var imagery = this.getImageryFromCache(i, j, imageryLevel);
                surfaceTile.imagery.splice(insertionPoint, 0, new TileImagery(imagery, texCoordsRectangle, useWebMercatorT));
                ++insertionPoint;
            }
        }

        return true;
    };

    /**
     * Calculate the translation and scale for a particular {@link TileImagery} attached to a
     * particular terrain tile.
     *
     * @private
     *
     * @param {Tile} tile The terrain tile.
     * @param {TileImagery} tileImagery The imagery tile mapping.
     * @returns {Cartesian4} The translation and scale where X and Y are the translation and Z and W
     *          are the scale.
     */
    ImageryLayer.prototype._calculateTextureTranslationAndScale = function(tile, tileImagery) {
        var imageryRectangle = tileImagery.readyImagery.rectangle;
        var terrainRectangle = tile.rectangle;

        if (tileImagery.useWebMercatorT) {
            var tilingScheme = tileImagery.readyImagery.imageryLayer.imageryProvider.tilingScheme;
            imageryRectangle = tilingScheme.rectangleToNativeRectangle(imageryRectangle, imageryBoundsScratch);
            terrainRectangle = tilingScheme.rectangleToNativeRectangle(terrainRectangle, terrainRectangleScratch);
        }

        var terrainWidth = terrainRectangle.width;
        var terrainHeight = terrainRectangle.height;

        var scaleX = terrainWidth / imageryRectangle.width;
        var scaleY = terrainHeight / imageryRectangle.height;
        return new Cartesian4(
                scaleX * (terrainRectangle.west - imageryRectangle.west) / terrainWidth,
                scaleY * (terrainRectangle.south - imageryRectangle.south) / terrainHeight,
                scaleX,
                scaleY);
    };

    /**
     * Request a particular piece of imagery from the imagery provider.  This method handles raising an
     * error event if the request fails, and retrying the request if necessary.
     *
     * @private
     *
     * @param {Imagery} imagery The imagery to request.
     */
    ImageryLayer.prototype._requestImagery = function(imagery) {
        var imageryProvider = this._imageryProvider;

        var that = this;

        function success(image) {
            if (!defined(image)) {
                return failure();
            }

            imagery.image = image;
            imagery.state = ImageryState.RECEIVED;

            TileProviderError.handleSuccess(that._requestImageError);
        }

        function failure(e) {
            // Initially assume failure.  handleError may retry, in which case the state will
            // change to TRANSITIONING.
            imagery.state = ImageryState.FAILED;

            var message = 'Failed to obtain image tile X: ' + imagery.x + ' Y: ' + imagery.y + ' Level: ' + imagery.level + '.';
            that._requestImageError = TileProviderError.handleError(
                    that._requestImageError,
                    imageryProvider,
                    imageryProvider.errorEvent,
                    message,
                    imagery.x, imagery.y, imagery.level,
                    doRequest,
                    e);
        }

        function doRequest() {
            imagery.state = ImageryState.TRANSITIONING;
            var imagePromise = imageryProvider.requestImage(imagery.x, imagery.y, imagery.level);

            if (!defined(imagePromise)) {
                // Too many parallel requests, so postpone loading tile.
                imagery.state = ImageryState.UNLOADED;
                return;
            }

            if (defined(imageryProvider.getTileCredits)) {
                imagery.credits = imageryProvider.getTileCredits(imagery.x, imagery.y, imagery.level);
            }

            when(imagePromise, success, failure);
        }

        doRequest();
    };

    /**
     * Create a WebGL texture for a given {@link Imagery} instance.
     *
     * @private
     *
     * @param {Context} context The rendered context to use to create textures.
     * @param {Imagery} imagery The imagery for which to create a texture.
     */
    ImageryLayer.prototype._createTexture = function(context, imagery) {
        var imageryProvider = this._imageryProvider;
        var image = imagery.image;

        // If this imagery provider has a discard policy, use it to check if this
        // image should be discarded.
        if (defined(imageryProvider.tileDiscardPolicy)) {
            var discardPolicy = imageryProvider.tileDiscardPolicy;
            if (defined(discardPolicy)) {
                // If the discard policy is not ready yet, transition back to the
                // RECEIVED state and we'll try again next time.
                if (!discardPolicy.isReady()) {
                    imagery.state = ImageryState.RECEIVED;
                    return;
                }

                // Mark discarded imagery tiles invalid.  Parent imagery will be used instead.
                if (discardPolicy.shouldDiscardImage(image)) {
                    imagery.state = ImageryState.INVALID;
                    return;
                }
            }
        }

        // Imagery does not need to be discarded, so upload it to WebGL.
        var texture;
        if (defined(image.internalFormat)) {
            texture = new Texture({
                context : context,
                pixelFormat : image.internalFormat,
                width : image.width,
                height : image.height,
                source : {
                    arrayBufferView : image.bufferView
                }
            });
        } else {
            texture = new Texture({
                context : context,
                source : image,
                pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB
            });
        }

        if (imageryProvider.tilingScheme instanceof WebMercatorTilingScheme) {
            imagery.textureWebMercator = texture;
        } else {
            imagery.texture = texture;
        }
        imagery.image = undefined;
        imagery.state = ImageryState.TEXTURE_LOADED;
    };

    function finalizeReprojectTexture(imageryLayer, context, imagery, texture) {
        // Use mipmaps if this texture has power-of-two dimensions.
        if (!PixelFormat.isCompressedFormat(texture.pixelFormat) && CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) {
            var mipmapSampler = context.cache.imageryLayer_mipmapSampler;
            if (!defined(mipmapSampler)) {
                var maximumSupportedAnisotropy = ContextLimits.maximumTextureFilterAnisotropy;
                mipmapSampler = context.cache.imageryLayer_mipmapSampler = new Sampler({
                    wrapS : TextureWrap.CLAMP_TO_EDGE,
                    wrapT : TextureWrap.CLAMP_TO_EDGE,
                    minificationFilter : TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
                    magnificationFilter : TextureMagnificationFilter.LINEAR,
                    maximumAnisotropy : Math.min(maximumSupportedAnisotropy, defaultValue(imageryLayer._maximumAnisotropy, maximumSupportedAnisotropy))
                });
            }
            texture.generateMipmap(MipmapHint.NICEST);
            texture.sampler = mipmapSampler;
        } else {
            var nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler;
            if (!defined(nonMipmapSampler)) {
                nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler = new Sampler({
                    wrapS : TextureWrap.CLAMP_TO_EDGE,
                    wrapT : TextureWrap.CLAMP_TO_EDGE,
                    minificationFilter : TextureMinificationFilter.LINEAR,
                    magnificationFilter : TextureMagnificationFilter.LINEAR
                });
            }
            texture.sampler = nonMipmapSampler;
        }

        imagery.state = ImageryState.READY;
    }

    /**
     * Enqueues a command re-projecting a texture to a {@link GeographicProjection} on the next update, if necessary, and generate
     * mipmaps for the geographic texture.
     *
     * @private
     *
     * @param {FrameState} frameState The frameState.
     * @param {Imagery} imagery The imagery instance to reproject.
     * @param {Boolean} [needGeographicProjection=true] True to reproject to geographic, or false if Web Mercator is fine.
     */
    ImageryLayer.prototype._reprojectTexture = function(frameState, imagery, needGeographicProjection) {
        var texture = imagery.textureWebMercator || imagery.texture;
        var rectangle = imagery.rectangle;
        var context = frameState.context;

        needGeographicProjection = defaultValue(needGeographicProjection, true);

        // Reproject this texture if it is not already in a geographic projection and
        // the pixels are more than 1e-5 radians apart.  The pixel spacing cutoff
        // avoids precision problems in the reprojection transformation while making
        // no noticeable difference in the georeferencing of the image.
        if (needGeographicProjection &&
            !(this._imageryProvider.tilingScheme instanceof GeographicTilingScheme) &&
            rectangle.width / texture.width > 1e-5) {
                var that = this;
                imagery.addReference();
                var computeCommand = new ComputeCommand({
                    persists : true,
                    owner : this,
                    // Update render resources right before execution instead of now.
                    // This allows different ImageryLayers to share the same vao and buffers.
                    preExecute : function(command) {
                        reprojectToGeographic(command, context, texture, imagery.rectangle);
                    },
                    postExecute : function(outputTexture) {
                        imagery.texture = outputTexture;
                        finalizeReprojectTexture(that, context, imagery, outputTexture);
                        imagery.releaseReference();
                    }
                });
                this._reprojectComputeCommands.push(computeCommand);
        } else {
            if (needGeographicProjection) {
                imagery.texture = texture;
            }
            finalizeReprojectTexture(this, context, imagery, texture);
        }
    };

    /**
     * Updates frame state to execute any queued texture re-projections.
     *
     * @private
     *
     * @param {FrameState} frameState The frameState.
     */
    ImageryLayer.prototype.queueReprojectionCommands = function(frameState) {
        var computeCommands = this._reprojectComputeCommands;
        var length = computeCommands.length;
        for (var i = 0; i < length; ++i) {
            frameState.commandList.push(computeCommands[i]);
        }
        computeCommands.length = 0;
    };

    /**
     * Cancels re-projection commands queued for the next frame.
     *
     * @private
     */
    ImageryLayer.prototype.cancelReprojections = function() {
        this._reprojectComputeCommands.length = 0;
    };

    ImageryLayer.prototype.getImageryFromCache = function(x, y, level, imageryRectangle) {
        var cacheKey = getImageryCacheKey(x, y, level);
        var imagery = this._imageryCache[cacheKey];

        if (!defined(imagery)) {
            imagery = new Imagery(this, x, y, level, imageryRectangle);
            this._imageryCache[cacheKey] = imagery;
        }

        imagery.addReference();
        return imagery;
    };

    ImageryLayer.prototype.removeImageryFromCache = function(imagery) {
        var cacheKey = getImageryCacheKey(imagery.x, imagery.y, imagery.level);
        delete this._imageryCache[cacheKey];
    };

    function getImageryCacheKey(x, y, level) {
        return JSON.stringify([x, y, level]);
    }

    var uniformMap = {
        u_textureDimensions : function() {
            return this.textureDimensions;
        },
        u_texture : function() {
            return this.texture;
        },

        textureDimensions : new Cartesian2(),
        texture : undefined
    };

    var float32ArrayScratch = FeatureDetection.supportsTypedArrays() ? new Float32Array(2 * 64) : undefined;

    function reprojectToGeographic(command, context, texture, rectangle) {
        // This function has gone through a number of iterations, because GPUs are awesome.
        //
        // Originally, we had a very simple vertex shader and computed the Web Mercator texture coordinates
        // per-fragment in the fragment shader.  That worked well, except on mobile devices, because
        // fragment shaders have limited precision on many mobile devices.  The result was smearing artifacts
        // at medium zoom levels because different geographic texture coordinates would be reprojected to Web
        // Mercator as the same value.
        //
        // Our solution was to reproject to Web Mercator in the vertex shader instead of the fragment shader.
        // This required far more vertex data.  With fragment shader reprojection, we only needed a single quad.
        // But to achieve the same precision with vertex shader reprojection, we needed a vertex for each
        // output pixel.  So we used a grid of 256x256 vertices, because most of our imagery
        // tiles are 256x256.  Fortunately the grid could be created and uploaded to the GPU just once and
        // re-used for all reprojections, so the performance was virtually unchanged from our original fragment
        // shader approach.  See https://github.com/AnalyticalGraphicsInc/cesium/pull/714.
        //
        // Over a year later, we noticed (https://github.com/AnalyticalGraphicsInc/cesium/issues/2110)
        // that our reprojection code was creating a rare but severe artifact on some GPUs (Intel HD 4600
        // for one).  The problem was that the GLSL sin function on these GPUs had a discontinuity at fine scales in
        // a few places.
        //
        // We solved this by implementing a more reliable sin function based on the CORDIC algorithm
        // (https://github.com/AnalyticalGraphicsInc/cesium/pull/2111).  Even though this was a fair
        // amount of code to be executing per vertex, the performance seemed to be pretty good on most GPUs.
        // Unfortunately, on some GPUs, the performance was absolutely terrible
        // (https://github.com/AnalyticalGraphicsInc/cesium/issues/2258).
        //
        // So that brings us to our current solution, the one you see here.  Effectively, we compute the Web
        // Mercator texture coordinates on the CPU and store the T coordinate with each vertex (the S coordinate
        // is the same in Geographic and Web Mercator).  To make this faster, we reduced our reprojection mesh
        // to be only 2 vertices wide and 64 vertices high.  We should have reduced the width to 2 sooner,
        // because the extra vertices weren't buying us anything.  The height of 64 means we are technically
        // doing a slightly less accurate reprojection than we were before, but we can't see the difference
        // so it's worth the 4x speedup.

        var reproject = context.cache.imageryLayer_reproject;

        if (!defined(reproject)) {
            reproject = context.cache.imageryLayer_reproject = {
                vertexArray : undefined,
                shaderProgram : undefined,
                sampler : undefined,
                destroy : function() {
                    if (defined(this.framebuffer)) {
                        this.framebuffer.destroy();
                    }
                    if (defined(this.vertexArray)) {
                        this.vertexArray.destroy();
                    }
                    if (defined(this.shaderProgram)) {
                        this.shaderProgram.destroy();
                    }
                }
            };

            var positions = new Float32Array(2 * 64 * 2);
            var index = 0;
            for (var j = 0; j < 64; ++j) {
                var y = j / 63.0;
                positions[index++] = 0.0;
                positions[index++] = y;
                positions[index++] = 1.0;
                positions[index++] = y;
            }

            var reprojectAttributeIndices = {
                position : 0,
                webMercatorT : 1
            };

            var indices = TerrainProvider.getRegularGridIndices(2, 64);
            var indexBuffer = Buffer.createIndexBuffer({
                context : context,
                typedArray : indices,
                usage : BufferUsage.STATIC_DRAW,
                indexDatatype : IndexDatatype.UNSIGNED_SHORT
            });

            reproject.vertexArray = new VertexArray({
                context : context,
                attributes : [{
                    index : reprojectAttributeIndices.position,
                    vertexBuffer : Buffer.createVertexBuffer({
                        context : context,
                        typedArray : positions,
                        usage : BufferUsage.STATIC_DRAW
                    }),
                    componentsPerAttribute : 2
                },{
                    index : reprojectAttributeIndices.webMercatorT,
                    vertexBuffer : Buffer.createVertexBuffer({
                        context : context,
                        sizeInBytes : 64 * 2 * 4,
                        usage : BufferUsage.STREAM_DRAW
                    }),
                    componentsPerAttribute : 1
                }],
                indexBuffer : indexBuffer
            });

            var vs = new ShaderSource({
                sources : [ReprojectWebMercatorVS]
            });

            reproject.shaderProgram = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vs,
                fragmentShaderSource : ReprojectWebMercatorFS,
                attributeLocations : reprojectAttributeIndices
            });

            reproject.sampler = new Sampler({
                wrapS : TextureWrap.CLAMP_TO_EDGE,
                wrapT : TextureWrap.CLAMP_TO_EDGE,
                minificationFilter : TextureMinificationFilter.LINEAR,
                magnificationFilter : TextureMagnificationFilter.LINEAR
            });
        }

        texture.sampler = reproject.sampler;

        var width = texture.width;
        var height = texture.height;

        uniformMap.textureDimensions.x = width;
        uniformMap.textureDimensions.y = height;
        uniformMap.texture = texture;

        var sinLatitude = Math.sin(rectangle.south);
        var southMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));

        sinLatitude = Math.sin(rectangle.north);
        var northMercatorY = 0.5 * Math.log((1 + sinLatitude) / (1 - sinLatitude));
        var oneOverMercatorHeight = 1.0 / (northMercatorY - southMercatorY);

        var outputTexture = new Texture({
            context : context,
            width : width,
            height : height,
            pixelFormat : texture.pixelFormat,
            pixelDatatype : texture.pixelDatatype,
            preMultiplyAlpha : texture.preMultiplyAlpha
        });

        // Allocate memory for the mipmaps.  Failure to do this before rendering
        // to the texture via the FBO, and calling generateMipmap later,
        // will result in the texture appearing blank.  I can't pretend to
        // understand exactly why this is.
        if (CesiumMath.isPowerOfTwo(width) && CesiumMath.isPowerOfTwo(height)) {
            outputTexture.generateMipmap(MipmapHint.NICEST);
        }

        var south = rectangle.south;
        var north = rectangle.north;

        var webMercatorT = float32ArrayScratch;

        var outputIndex = 0;
        for (var webMercatorTIndex = 0; webMercatorTIndex < 64; ++webMercatorTIndex) {
            var fraction = webMercatorTIndex / 63.0;
            var latitude = CesiumMath.lerp(south, north, fraction);
            sinLatitude = Math.sin(latitude);
            var mercatorY = 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude));
            var mercatorFraction = (mercatorY - southMercatorY) * oneOverMercatorHeight;
            webMercatorT[outputIndex++] = mercatorFraction;
            webMercatorT[outputIndex++] = mercatorFraction;
        }

        reproject.vertexArray.getAttribute(1).vertexBuffer.copyFromArrayView(webMercatorT);

        command.shaderProgram = reproject.shaderProgram;
        command.outputTexture = outputTexture;
        command.uniformMap = uniformMap;
        command.vertexArray = reproject.vertexArray;
    }

    /**
     * Gets the level with the specified world coordinate spacing between texels, or less.
     *
     * @param {ImageryLayer} layer The imagery layer to use.
     * @param {Number} texelSpacing The texel spacing for which to find a corresponding level.
     * @param {Number} latitudeClosestToEquator The latitude closest to the equator that we're concerned with.
     * @returns {Number} The level with the specified texel spacing or less.
     */
    function getLevelWithMaximumTexelSpacing(layer, texelSpacing, latitudeClosestToEquator) {
        // PERFORMANCE_IDEA: factor out the stuff that doesn't change.
        var imageryProvider = layer._imageryProvider;
        var tilingScheme = imageryProvider.tilingScheme;
        var ellipsoid = tilingScheme.ellipsoid;
        var latitudeFactor = !(layer._imageryProvider.tilingScheme instanceof GeographicTilingScheme) ? Math.cos(latitudeClosestToEquator) : 1.0;
        var tilingSchemeRectangle = tilingScheme.rectangle;
        var levelZeroMaximumTexelSpacing = ellipsoid.maximumRadius * tilingSchemeRectangle.width * latitudeFactor / (imageryProvider.tileWidth * tilingScheme.getNumberOfXTilesAtLevel(0));

        var twoToTheLevelPower = levelZeroMaximumTexelSpacing / texelSpacing;
        var level = Math.log(twoToTheLevelPower) / Math.log(2);
        var rounded = Math.round(level);
        return rounded | 0;
    }

    return ImageryLayer;
});

/*global define*/
define('Scene/GlobeSurfaceTileProvider',[
        '../Core/BoundingSphere',
        '../Core/BoxOutlineGeometry',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/GeometryInstance',
        '../Core/GeometryPipeline',
        '../Core/IndexDatatype',
        '../Core/Intersect',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/OrientedBoundingBox',
        '../Core/PrimitiveType',
        '../Core/Rectangle',
        '../Core/SphereOutlineGeometry',
        '../Core/TerrainQuantization',
        '../Core/Visibility',
        '../Core/WebMercatorProjection',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/ContextLimits',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/VertexArray',
        '../Scene/BlendingState',
        '../Scene/DepthFunction',
        '../Scene/PerInstanceColorAppearance',
        '../Scene/Primitive',
        './GlobeSurfaceTile',
        './ImageryLayer',
        './QuadtreeTileLoadState',
        './SceneMode',
        './ShadowMode'
    ], function(
        BoundingSphere,
        BoxOutlineGeometry,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Color,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Event,
        GeometryInstance,
        GeometryPipeline,
        IndexDatatype,
        Intersect,
        CesiumMath,
        Matrix4,
        OrientedBoundingBox,
        PrimitiveType,
        Rectangle,
        SphereOutlineGeometry,
        TerrainQuantization,
        Visibility,
        WebMercatorProjection,
        Buffer,
        BufferUsage,
        ContextLimits,
        DrawCommand,
        Pass,
        RenderState,
        VertexArray,
        BlendingState,
        DepthFunction,
        PerInstanceColorAppearance,
        Primitive,
        GlobeSurfaceTile,
        ImageryLayer,
        QuadtreeTileLoadState,
        SceneMode,
        ShadowMode) {
    'use strict';

    /**
     * Provides quadtree tiles representing the surface of the globe.  This type is intended to be used
     * with {@link QuadtreePrimitive}.
     *
     * @alias GlobeSurfaceTileProvider
     * @constructor
     *
     * @param {TerrainProvider} options.terrainProvider The terrain provider that describes the surface geometry.
     * @param {ImageryLayerCollection} option.imageryLayers The collection of imagery layers describing the shading of the surface.
     * @param {GlobeSurfaceShaderSet} options.surfaceShaderSet The set of shaders used to render the surface.
     *
     * @private
     */
    function GlobeSurfaceTileProvider(options) {
                if (!defined(options)) {
            throw new DeveloperError('options is required.');
        }
        if (!defined(options.terrainProvider)) {
            throw new DeveloperError('options.terrainProvider is required.');
        } else if (!defined(options.imageryLayers)) {
            throw new DeveloperError('options.imageryLayers is required.');
        } else if (!defined(options.surfaceShaderSet)) {
            throw new DeveloperError('options.surfaceShaderSet is required.');
        }
        
        this.lightingFadeOutDistance = 6500000.0;
        this.lightingFadeInDistance = 9000000.0;
        this.hasWaterMask = false;
        this.oceanNormalMap = undefined;
        this.zoomedOutOceanSpecularIntensity = 0.5;
        this.enableLighting = false;
        this.shadows = ShadowMode.RECEIVE_ONLY;

        this._quadtree = undefined;
        this._terrainProvider = options.terrainProvider;
        this._imageryLayers = options.imageryLayers;
        this._surfaceShaderSet = options.surfaceShaderSet;

        this._renderState = undefined;
        this._blendRenderState = undefined;
        this._pickRenderState = undefined;

        this._errorEvent = new Event();

        this._imageryLayers.layerAdded.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerAdded, this);
        this._imageryLayers.layerRemoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerRemoved, this);
        this._imageryLayers.layerMoved.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerMoved, this);
        this._imageryLayers.layerShownOrHidden.addEventListener(GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden, this);

        this._layerOrderChanged = false;

        this._tilesToRenderByTextureCount = [];
        this._drawCommands = [];
        this._uniformMaps = [];
        this._pickCommands = [];
        this._usedDrawCommands = 0;
        this._usedPickCommands = 0;

        this._vertexArraysToDestroy = [];

        this._debug = {
            wireframe : false,
            boundingSphereTile : undefined
        };

        this._baseColor = undefined;
        this._firstPassInitialColor = undefined;
        this.baseColor = new Color(0.0, 0.0, 0.5, 1.0);
    }

    defineProperties(GlobeSurfaceTileProvider.prototype, {
        /**
         * Gets or sets the color of the globe when no imagery is available.
         * @memberof GlobeSurfaceTileProvider.prototype
         * @type {Color}
         */
        baseColor : {
            get : function() {
                return this._baseColor;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                this._baseColor = value;
                this._firstPassInitialColor = Cartesian4.fromColor(value, this._firstPassInitialColor);
            }
        },
        /**
         * Gets or sets the {@link QuadtreePrimitive} for which this provider is
         * providing tiles.  This property may be undefined if the provider is not yet associated
         * with a {@link QuadtreePrimitive}.
         * @memberof GlobeSurfaceTileProvider.prototype
         * @type {QuadtreePrimitive}
         */
        quadtree : {
            get : function() {
                return this._quadtree;
            },
            set : function(value) {
                                if (!defined(value)) {
                    throw new DeveloperError('value is required.');
                }
                
                this._quadtree = value;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof GlobeSurfaceTileProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : function() {
                return this._terrainProvider.ready && (this._imageryLayers.length === 0 || this._imageryLayers.get(0).imageryProvider.ready);
            }
        },

        /**
         * Gets the tiling scheme used by the provider.  This property should
         * not be accessed before {@link GlobeSurfaceTileProvider#ready} returns true.
         * @memberof GlobeSurfaceTileProvider.prototype
         * @type {TilingScheme}
         */
        tilingScheme : {
            get : function() {
                return this._terrainProvider.tilingScheme;
            }
        },

        /**
         * Gets an event that is raised when the geometry provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof GlobeSurfaceTileProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets or sets the terrain provider that describes the surface geometry.
         * @memberof GlobeSurfaceTileProvider.prototype
         * @type {TerrainProvider}
         */
        terrainProvider : {
            get : function() {
                return this._terrainProvider;
            },
            set : function(terrainProvider) {
                if (this._terrainProvider === terrainProvider) {
                    return;
                }

                                if (!defined(terrainProvider)) {
                    throw new DeveloperError('terrainProvider is required.');
                }
                
                this._terrainProvider = terrainProvider;

                if (defined(this._quadtree)) {
                    this._quadtree.invalidateAllTiles();
                }
            }
        }
    });

    function sortTileImageryByLayerIndex(a, b) {
        var aImagery = a.loadingImagery;
        if (!defined(aImagery)) {
            aImagery = a.readyImagery;
        }

        var bImagery = b.loadingImagery;
        if (!defined(bImagery)) {
            bImagery = b.readyImagery;
        }

        return aImagery.imageryLayer._layerIndex - bImagery.imageryLayer._layerIndex;
    }

    function freeVertexArray(vertexArray) {
        var indexBuffer = vertexArray.indexBuffer;
        vertexArray.destroy();

        if (!indexBuffer.isDestroyed() && defined(indexBuffer.referenceCount)) {
            --indexBuffer.referenceCount;
            if (indexBuffer.referenceCount === 0) {
                indexBuffer.destroy();
            }
        }
    }

    /**
     * Called at the beginning of each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
     * @param {FrameState} frameState The frame state.
     */
    GlobeSurfaceTileProvider.prototype.initialize = function(frameState) {
        var imageryLayers = this._imageryLayers;

        // update collection: imagery indices, base layers, raise layer show/hide event
        imageryLayers._update();
        // update each layer for texture reprojection.
        imageryLayers.queueReprojectionCommands(frameState);

        if (this._layerOrderChanged) {
            this._layerOrderChanged = false;

            // Sort the TileImagery instances in each tile by the layer index.
            this._quadtree.forEachLoadedTile(function(tile) {
                tile.data.imagery.sort(sortTileImageryByLayerIndex);
            });
        }

        // Add credits for terrain and imagery providers.
        var creditDisplay = frameState.creditDisplay;

        if (this._terrainProvider.ready && defined(this._terrainProvider.credit)) {
            creditDisplay.addCredit(this._terrainProvider.credit);
        }

        for (var i = 0, len = imageryLayers.length; i < len; ++i) {
            var imageryProvider = imageryLayers.get(i).imageryProvider;
            if (imageryProvider.ready && defined(imageryProvider.credit)) {
                creditDisplay.addCredit(imageryProvider.credit);
            }
        }

        var vertexArraysToDestroy = this._vertexArraysToDestroy;
        var length = vertexArraysToDestroy.length;
        for (var j = 0; j < length; ++j) {
            freeVertexArray(vertexArraysToDestroy[j]);
        }
        vertexArraysToDestroy.length = 0;
    };

    /**
     * Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
     * or any other functions.
     *
     * @param {FrameState} frameState The frame state.
     */
    GlobeSurfaceTileProvider.prototype.beginUpdate = function(frameState) {
        var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
        for (var i = 0, len = tilesToRenderByTextureCount.length; i < len; ++i) {
            var tiles = tilesToRenderByTextureCount[i];
            if (defined(tiles)) {
                tiles.length = 0;
            }
        }

        this._usedDrawCommands = 0;
    };

    /**
     * Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame}
     * and any other functions.
     *
     * @param {FrameState} frameState The frame state.
     */
    GlobeSurfaceTileProvider.prototype.endUpdate = function(frameState) {
        if (!defined(this._renderState)) {
            this._renderState = RenderState.fromCache({ // Write color and depth
                cull : {
                    enabled : true
                },
                depthTest : {
                    enabled : true,
                    func : DepthFunction.LESS
                }
            });

            this._blendRenderState = RenderState.fromCache({ // Write color and depth
                cull : {
                    enabled : true
                },
                depthTest : {
                    enabled : true,
                    func : DepthFunction.LESS_OR_EQUAL
                },
                blending : BlendingState.ALPHA_BLEND
            });
        }

        // Add the tile render commands to the command list, sorted by texture count.
        var tilesToRenderByTextureCount = this._tilesToRenderByTextureCount;
        for (var textureCountIndex = 0, textureCountLength = tilesToRenderByTextureCount.length; textureCountIndex < textureCountLength; ++textureCountIndex) {
            var tilesToRender = tilesToRenderByTextureCount[textureCountIndex];
            if (!defined(tilesToRender)) {
                continue;
            }

            for (var tileIndex = 0, tileLength = tilesToRender.length; tileIndex < tileLength; ++tileIndex) {
                addDrawCommandsForTile(this, tilesToRender[tileIndex], frameState);
            }
        }
    };

    /**
     * Adds draw commands for tiles rendered in the previous frame for a pick pass.
     *
     * @param {FrameState} frameState The frame state.
     */
    GlobeSurfaceTileProvider.prototype.updateForPick = function(frameState) {
        if (!defined(this._pickRenderState)) {
            this._pickRenderState = RenderState.fromCache({
                colorMask : {
                    red : false,
                    green : false,
                    blue : false,
                    alpha : false
                },
                depthTest : {
                    enabled : true
                }
            });
        }

        this._usedPickCommands = 0;
        var drawCommands = this._drawCommands;

        // Add the tile pick commands from the tiles drawn last frame.
        for (var i = 0, length = this._usedDrawCommands; i < length; ++i) {
            addPickCommandsForTile(this, drawCommands[i], frameState);
        }
    };

    /**
     * Cancels any imagery re-projections in the queue.
     */
    GlobeSurfaceTileProvider.prototype.cancelReprojections = function() {
        this._imageryLayers.cancelReprojections();
    };

    /**
     * Gets the maximum geometric error allowed in a tile at a given level, in meters.  This function should not be
     * called before {@link GlobeSurfaceTileProvider#ready} returns true.
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error in meters.
     */
    GlobeSurfaceTileProvider.prototype.getLevelMaximumGeometricError = function(level) {
        return this._terrainProvider.getLevelMaximumGeometricError(level);
    };

    /**
     * Loads, or continues loading, a given tile.  This function will continue to be called
     * until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}.  This function should
     * not be called before {@link GlobeSurfaceTileProvider#ready} returns true.
     *
     * @param {FrameState} frameState The frame state.
     * @param {QuadtreeTile} tile The tile to load.
     *
     * @exception {DeveloperError} <code>loadTile</code> must not be called before the tile provider is ready.
     */
    GlobeSurfaceTileProvider.prototype.loadTile = function(frameState, tile) {
        GlobeSurfaceTile.processStateMachine(tile, frameState, this._terrainProvider, this._imageryLayers, this._vertexArraysToDestroy);
    };

    var boundingSphereScratch = new BoundingSphere();

    /**
     * Determines the visibility of a given tile.  The tile may be fully visible, partially visible, or not
     * visible at all.  Tiles that are renderable and are at least partially visible will be shown by a call
     * to {@link GlobeSurfaceTileProvider#showTileThisFrame}.
     *
     * @param {QuadtreeTile} tile The tile instance.
     * @param {FrameState} frameState The state information about the current frame.
     * @param {QuadtreeOccluders} occluders The objects that may occlude this tile.
     *
     * @returns {Visibility} The visibility of the tile.
     */
    GlobeSurfaceTileProvider.prototype.computeTileVisibility = function(tile, frameState, occluders) {
        var distance = this.computeDistanceToTile(tile, frameState);
        tile._distance = distance;

        if (frameState.fog.enabled) {
            if (CesiumMath.fog(distance, frameState.fog.density) >= 1.0) {
                // Tile is completely in fog so return that it is not visible.
                return Visibility.NONE;
            }
        }

        var surfaceTile = tile.data;
        var cullingVolume = frameState.cullingVolume;
        var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D);

        if (frameState.mode !== SceneMode.SCENE3D) {
            boundingVolume = boundingSphereScratch;
            BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume);
            Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);

            if (frameState.mode === SceneMode.MORPHING) {
                boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume);
            }
        }

        var intersection = cullingVolume.computeVisibility(boundingVolume);
        if (intersection === Intersect.OUTSIDE) {
            return Visibility.NONE;
        }

        if (frameState.mode === SceneMode.SCENE3D) {
            var occludeePointInScaledSpace = surfaceTile.occludeePointInScaledSpace;
            if (!defined(occludeePointInScaledSpace)) {
                return intersection;
            }

            if (occluders.ellipsoid.isScaledSpacePointVisible(occludeePointInScaledSpace)) {
                return intersection;
            }

            return Visibility.NONE;
        }

        return intersection;
    };

    var modifiedModelViewScratch = new Matrix4();
    var modifiedModelViewProjectionScratch = new Matrix4();
    var tileRectangleScratch = new Cartesian4();
    var rtcScratch = new Cartesian3();
    var centerEyeScratch = new Cartesian3();
    var southwestScratch = new Cartesian3();
    var northeastScratch = new Cartesian3();

    /**
     * Shows a specified tile in this frame.  The provider can cause the tile to be shown by adding
     * render commands to the commandList, or use any other method as appropriate.  The tile is not
     * expected to be visible next frame as well, unless this method is called next frame, too.
     *
     * @param {Object} tile The tile instance.
     * @param {FrameState} frameState The state information of the current rendering frame.
     */
    GlobeSurfaceTileProvider.prototype.showTileThisFrame = function(tile, frameState) {
        var readyTextureCount = 0;
        var tileImageryCollection = tile.data.imagery;
        for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
            var tileImagery = tileImageryCollection[i];
            if (defined(tileImagery.readyImagery) && tileImagery.readyImagery.imageryLayer.alpha !== 0.0) {
                ++readyTextureCount;
            }
        }

        var tileSet = this._tilesToRenderByTextureCount[readyTextureCount];
        if (!defined(tileSet)) {
            tileSet = [];
            this._tilesToRenderByTextureCount[readyTextureCount] = tileSet;
        }

        tileSet.push(tile);

        var debug = this._debug;
        ++debug.tilesRendered;
        debug.texturesRendered += readyTextureCount;
    };

    /**
     * Gets the distance from the camera to the closest point on the tile.  This is used for level-of-detail selection.
     *
     * @param {QuadtreeTile} tile The tile instance.
     * @param {FrameState} frameState The state information of the current rendering frame.
     *
     * @returns {Number} The distance from the camera to the closest point on the tile, in meters.
     */
    GlobeSurfaceTileProvider.prototype.computeDistanceToTile = function(tile, frameState) {
        var surfaceTile = tile.data;
        var tileBoundingBox = surfaceTile.tileBoundingBox;
        return tileBoundingBox.distanceToCamera(frameState);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see GlobeSurfaceTileProvider#destroy
     */
    GlobeSurfaceTileProvider.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * provider = provider && provider();
     *
     * @see GlobeSurfaceTileProvider#isDestroyed
     */
    GlobeSurfaceTileProvider.prototype.destroy = function() {
        this._tileProvider = this._tileProvider && this._tileProvider.destroy();
        return destroyObject(this);
    };

    GlobeSurfaceTileProvider.prototype._onLayerAdded = function(layer, index) {
        if (layer.show) {
            var terrainProvider = this._terrainProvider;

            // create TileImagerys for this layer for all previously loaded tiles
            this._quadtree.forEachLoadedTile(function(tile) {
                if (layer._createTileImagerySkeletons(tile, terrainProvider)) {
                    tile.state = QuadtreeTileLoadState.LOADING;
                }
            });

            this._layerOrderChanged = true;
        }
    };

    GlobeSurfaceTileProvider.prototype._onLayerRemoved = function(layer, index) {
        // destroy TileImagerys for this layer for all previously loaded tiles
        this._quadtree.forEachLoadedTile(function(tile) {
            var tileImageryCollection = tile.data.imagery;

            var startIndex = -1;
            var numDestroyed = 0;
            for (var i = 0, len = tileImageryCollection.length; i < len; ++i) {
                var tileImagery = tileImageryCollection[i];
                var imagery = tileImagery.loadingImagery;
                if (!defined(imagery)) {
                    imagery = tileImagery.readyImagery;
                }
                if (imagery.imageryLayer === layer) {
                    if (startIndex === -1) {
                        startIndex = i;
                    }

                    tileImagery.freeResources();
                    ++numDestroyed;
                } else if (startIndex !== -1) {
                    // iterated past the section of TileImagerys belonging to this layer, no need to continue.
                    break;
                }
            }

            if (startIndex !== -1) {
                tileImageryCollection.splice(startIndex, numDestroyed);
            }
        });
    };

    GlobeSurfaceTileProvider.prototype._onLayerMoved = function(layer, newIndex, oldIndex) {
        this._layerOrderChanged = true;
    };

    GlobeSurfaceTileProvider.prototype._onLayerShownOrHidden = function(layer, index, show) {
        if (show) {
            this._onLayerAdded(layer, index);
        } else {
            this._onLayerRemoved(layer, index);
        }
    };

    function createTileUniformMap(frameState) {
        var uniformMap = {
            u_initialColor : function() {
                return this.properties.initialColor;
            },
            u_zoomedOutOceanSpecularIntensity : function() {
                return this.properties.zoomedOutOceanSpecularIntensity;
            },
            u_oceanNormalMap : function() {
                return this.properties.oceanNormalMap;
            },
            u_lightingFadeDistance : function() {
                return this.properties.lightingFadeDistance;
            },
            u_center3D : function() {
                return this.properties.center3D;
            },
            u_tileRectangle : function() {
                return this.properties.tileRectangle;
            },
            u_modifiedModelView : function() {
                var viewMatrix = frameState.context.uniformState.view;
                var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch);
                Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewScratch);
                return modifiedModelViewScratch;
            },
            u_modifiedModelViewProjection : function() {
                var viewMatrix = frameState.context.uniformState.view;
                var projectionMatrix = frameState.context.uniformState.projection;
                var centerEye = Matrix4.multiplyByPoint(viewMatrix, this.properties.rtc, centerEyeScratch);
                Matrix4.setTranslation(viewMatrix, centerEye, modifiedModelViewProjectionScratch);
                Matrix4.multiply(projectionMatrix, modifiedModelViewProjectionScratch, modifiedModelViewProjectionScratch);
                return modifiedModelViewProjectionScratch;
            },
            u_dayTextures : function() {
                return this.properties.dayTextures;
            },
            u_dayTextureTranslationAndScale : function() {
                return this.properties.dayTextureTranslationAndScale;
            },
            u_dayTextureTexCoordsRectangle : function() {
                return this.properties.dayTextureTexCoordsRectangle;
            },
            u_dayTextureUseWebMercatorT : function() {
                return this.properties.dayTextureUseWebMercatorT;
            },
            u_dayTextureAlpha : function() {
                return this.properties.dayTextureAlpha;
            },
            u_dayTextureBrightness : function() {
                return this.properties.dayTextureBrightness;
            },
            u_dayTextureContrast : function() {
                return this.properties.dayTextureContrast;
            },
            u_dayTextureHue : function() {
                return this.properties.dayTextureHue;
            },
            u_dayTextureSaturation : function() {
                return this.properties.dayTextureSaturation;
            },
            u_dayTextureOneOverGamma : function() {
                return this.properties.dayTextureOneOverGamma;
            },
            u_dayIntensity : function() {
                return this.properties.dayIntensity;
            },
            u_southAndNorthLatitude : function() {
                return this.properties.southAndNorthLatitude;
            },
            u_southMercatorYAndOneOverHeight : function() {
                return this.properties.southMercatorYAndOneOverHeight;
            },
            u_waterMask : function() {
                return this.properties.waterMask;
            },
            u_waterMaskTranslationAndScale : function() {
                return this.properties.waterMaskTranslationAndScale;
            },
            u_minMaxHeight : function() {
                return this.properties.minMaxHeight;
            },
            u_scaleAndBias : function() {
                return this.properties.scaleAndBias;
            },
            u_dayTextureSplit : function() {
                return this.properties.dayTextureSplit;
            },

            // make a separate object so that changes to the properties are seen on
            // derived commands that combine another uniform map with this one.
            properties : {
                initialColor : new Cartesian4(0.0, 0.0, 0.5, 1.0),
                zoomedOutOceanSpecularIntensity : 0.5,
                oceanNormalMap : undefined,
                lightingFadeDistance : new Cartesian2(6500000.0, 9000000.0),

                center3D : undefined,
                rtc : new Cartesian3(),
                modifiedModelView : new Matrix4(),
                tileRectangle : new Cartesian4(),

                dayTextures : [],
                dayTextureTranslationAndScale : [],
                dayTextureTexCoordsRectangle : [],
                dayTextureUseWebMercatorT : [],
                dayTextureAlpha : [],
                dayTextureBrightness : [],
                dayTextureContrast : [],
                dayTextureHue : [],
                dayTextureSaturation : [],
                dayTextureOneOverGamma : [],
                dayTextureSplit : [],
                dayIntensity : 0.0,

                southAndNorthLatitude : new Cartesian2(),
                southMercatorYAndOneOverHeight : new Cartesian2(),

                waterMask : undefined,
                waterMaskTranslationAndScale : new Cartesian4(),

                minMaxHeight : new Cartesian2(),
                scaleAndBias : new Matrix4()
            }
        };

        return uniformMap;
    }

    function createWireframeVertexArrayIfNecessary(context, provider, tile) {
        var surfaceTile = tile.data;

        if (defined(surfaceTile.wireframeVertexArray)) {
            return;
        }

        if (!defined(surfaceTile.terrainData) || !defined(surfaceTile.terrainData._mesh)) {
            return;
        }

        surfaceTile.wireframeVertexArray = createWireframeVertexArray(context, surfaceTile.vertexArray, surfaceTile.terrainData._mesh);
    }

    /**
     * Creates a vertex array for wireframe rendering of a terrain tile.
     *
     * @private
     *
     * @param {Context} context The context in which to create the vertex array.
     * @param {VertexArray} vertexArray The existing, non-wireframe vertex array.  The new vertex array
     *                      will share vertex buffers with this existing one.
     * @param {TerrainMesh} terrainMesh The terrain mesh containing non-wireframe indices.
     * @returns {VertexArray} The vertex array for wireframe rendering.
     */
    function createWireframeVertexArray(context, vertexArray, terrainMesh) {
        var geometry = {
            indices : terrainMesh.indices,
            primitiveType : PrimitiveType.TRIANGLES
        };

        GeometryPipeline.toWireframe(geometry);

        var wireframeIndices = geometry.indices;
        var wireframeIndexBuffer = Buffer.createIndexBuffer({
            context : context,
            typedArray : wireframeIndices,
            usage : BufferUsage.STATIC_DRAW,
            indexDatatype : IndexDatatype.UNSIGNED_SHORT
        });
        return new VertexArray({
            context : context,
            attributes : vertexArray._attributes,
            indexBuffer : wireframeIndexBuffer
        });
    }

    var getDebugOrientedBoundingBox;
    var getDebugBoundingSphere;
    var debugDestroyPrimitive;

    (function() {
        var instanceOBB = new GeometryInstance({
            geometry: BoxOutlineGeometry.fromDimensions({ dimensions: new Cartesian3(2.0, 2.0, 2.0) })
        });
        var instanceSphere = new GeometryInstance({
            geometry: new SphereOutlineGeometry({ radius: 1.0 })
        });
        var modelMatrix = new Matrix4();
        var previousVolume;
        var primitive;

        function createDebugPrimitive(instance) {
            return new Primitive({
                geometryInstances : instance,
                appearance : new PerInstanceColorAppearance({
                    translucent : false,
                    flat : true
                }),
                asynchronous : false
            });
        }

        getDebugOrientedBoundingBox = function(obb, color) {
            if (obb === previousVolume) {
                return primitive;
            }
            debugDestroyPrimitive();

            previousVolume = obb;
            modelMatrix = Matrix4.fromRotationTranslation(obb.halfAxes, obb.center, modelMatrix);

            instanceOBB.modelMatrix = modelMatrix;
            instanceOBB.attributes.color = ColorGeometryInstanceAttribute.fromColor(color);

            primitive = createDebugPrimitive(instanceOBB);
            return primitive;
        };

        getDebugBoundingSphere = function(sphere, color) {
            if (sphere === previousVolume) {
                return primitive;
            }
            debugDestroyPrimitive();

            previousVolume = sphere;
            modelMatrix = Matrix4.fromTranslation(sphere.center, modelMatrix);
            modelMatrix = Matrix4.multiplyByUniformScale(modelMatrix, sphere.radius, modelMatrix);

            instanceSphere.modelMatrix = modelMatrix;
            instanceSphere.attributes.color = ColorGeometryInstanceAttribute.fromColor(color);

            primitive = createDebugPrimitive(instanceSphere);
            return primitive;
        };

        debugDestroyPrimitive = function() {
            if (defined(primitive)) {
                primitive.destroy();
                primitive = undefined;
                previousVolume = undefined;
            }
        };
    })();

    var otherPassesInitialColor = new Cartesian4(0.0, 0.0, 0.0, 0.0);

    function addDrawCommandsForTile(tileProvider, tile, frameState) {
        var surfaceTile = tile.data;

        var maxTextures = ContextLimits.maximumTextureImageUnits;

        var waterMaskTexture = surfaceTile.waterMaskTexture;
        var showReflectiveOcean = tileProvider.hasWaterMask && defined(waterMaskTexture);
        var oceanNormalMap = tileProvider.oceanNormalMap;
        var showOceanWaves = showReflectiveOcean && defined(oceanNormalMap);
        var hasVertexNormals = tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals;
        var enableFog = frameState.fog.enabled;
        var castShadows = ShadowMode.castShadows(tileProvider.shadows);
        var receiveShadows = ShadowMode.receiveShadows(tileProvider.shadows);

        if (showReflectiveOcean) {
            --maxTextures;
        }
        if (showOceanWaves) {
            --maxTextures;
        }

        var rtc = surfaceTile.center;
        var encoding = surfaceTile.pickTerrain.mesh.encoding;

        // Not used in 3D.
        var tileRectangle = tileRectangleScratch;

        // Only used for Mercator projections.
        var southLatitude = 0.0;
        var northLatitude = 0.0;
        var southMercatorY = 0.0;
        var oneOverMercatorHeight = 0.0;

        var useWebMercatorProjection = false;

        if (frameState.mode !== SceneMode.SCENE3D) {
            var projection = frameState.mapProjection;
            var southwest = projection.project(Rectangle.southwest(tile.rectangle), southwestScratch);
            var northeast = projection.project(Rectangle.northeast(tile.rectangle), northeastScratch);

            tileRectangle.x = southwest.x;
            tileRectangle.y = southwest.y;
            tileRectangle.z = northeast.x;
            tileRectangle.w = northeast.y;

            // In 2D and Columbus View, use the center of the tile for RTC rendering.
            if (frameState.mode !== SceneMode.MORPHING) {
                rtc = rtcScratch;
                rtc.x = 0.0;
                rtc.y = (tileRectangle.z + tileRectangle.x) * 0.5;
                rtc.z = (tileRectangle.w + tileRectangle.y) * 0.5;
                tileRectangle.x -= rtc.y;
                tileRectangle.y -= rtc.z;
                tileRectangle.z -= rtc.y;
                tileRectangle.w -= rtc.z;
            }

            if (frameState.mode === SceneMode.SCENE2D && encoding.quantization === TerrainQuantization.BITS12) {
                // In 2D, the texture coordinates of the tile are interpolated over the rectangle to get the position in the vertex shader.
                // When the texture coordinates are quantized, error is introduced. This can be seen through the 1px wide cracking
                // between the quantized tiles in 2D. To compensate for the error, move the expand the rectangle in each direction by
                // half the error amount.
                var epsilon = (1.0 / (Math.pow(2.0, 12.0) - 1.0)) * 0.5;
                var widthEpsilon = (tileRectangle.z - tileRectangle.x) * epsilon;
                var heightEpsilon = (tileRectangle.w - tileRectangle.y) * epsilon;
                tileRectangle.x -= widthEpsilon;
                tileRectangle.y -= heightEpsilon;
                tileRectangle.z += widthEpsilon;
                tileRectangle.w += heightEpsilon;
            }

            if (projection instanceof WebMercatorProjection) {
                southLatitude = tile.rectangle.south;
                northLatitude = tile.rectangle.north;

                southMercatorY = WebMercatorProjection.geodeticLatitudeToMercatorAngle(southLatitude);

                oneOverMercatorHeight = 1.0 / (WebMercatorProjection.geodeticLatitudeToMercatorAngle(northLatitude) - southMercatorY);

                useWebMercatorProjection = true;
            }
        }

        var tileImageryCollection = surfaceTile.imagery;
        var imageryIndex = 0;
        var imageryLen = tileImageryCollection.length;

        var firstPassRenderState = tileProvider._renderState;
        var otherPassesRenderState = tileProvider._blendRenderState;
        var renderState = firstPassRenderState;

        var initialColor = tileProvider._firstPassInitialColor;

        var context = frameState.context;

        if (!defined(tileProvider._debug.boundingSphereTile)) {
            debugDestroyPrimitive();
        }

        do {
            var numberOfDayTextures = 0;

            var command;
            var uniformMap;

            if (tileProvider._drawCommands.length <= tileProvider._usedDrawCommands) {
                command = new DrawCommand();
                command.owner = tile;
                command.cull = false;
                command.boundingVolume = new BoundingSphere();
                command.orientedBoundingBox = undefined;

                uniformMap = createTileUniformMap(frameState);

                tileProvider._drawCommands.push(command);
                tileProvider._uniformMaps.push(uniformMap);
            } else {
                command = tileProvider._drawCommands[tileProvider._usedDrawCommands];
                uniformMap = tileProvider._uniformMaps[tileProvider._usedDrawCommands];
            }

            command.owner = tile;

            ++tileProvider._usedDrawCommands;

            if (tile === tileProvider._debug.boundingSphereTile) {
                // If a debug primitive already exists for this tile, it will not be
                // re-created, to avoid allocation every frame. If it were possible
                // to have more than one selected tile, this would have to change.
                if (defined(surfaceTile.orientedBoundingBox)) {
                    getDebugOrientedBoundingBox(surfaceTile.orientedBoundingBox, Color.RED).update(frameState);
                } else if (defined(surfaceTile.boundingSphere3D)) {
                    getDebugBoundingSphere(surfaceTile.boundingSphere3D, Color.RED).update(frameState);
                }
            }

            var uniformMapProperties = uniformMap.properties;
            Cartesian4.clone(initialColor, uniformMapProperties.initialColor);
            uniformMapProperties.oceanNormalMap = oceanNormalMap;
            uniformMapProperties.lightingFadeDistance.x = tileProvider.lightingFadeOutDistance;
            uniformMapProperties.lightingFadeDistance.y = tileProvider.lightingFadeInDistance;
            uniformMapProperties.zoomedOutOceanSpecularIntensity = tileProvider.zoomedOutOceanSpecularIntensity;

            uniformMapProperties.center3D = surfaceTile.center;
            Cartesian3.clone(rtc, uniformMapProperties.rtc);

            Cartesian4.clone(tileRectangle, uniformMapProperties.tileRectangle);
            uniformMapProperties.southAndNorthLatitude.x = southLatitude;
            uniformMapProperties.southAndNorthLatitude.y = northLatitude;
            uniformMapProperties.southMercatorYAndOneOverHeight.x = southMercatorY;
            uniformMapProperties.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight;

            // For performance, use fog in the shader only when the tile is in fog.
            var applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3;

            var applyBrightness = false;
            var applyContrast = false;
            var applyHue = false;
            var applySaturation = false;
            var applyGamma = false;
            var applyAlpha = false;
            var applySplit = false;

            while (numberOfDayTextures < maxTextures && imageryIndex < imageryLen) {
                var tileImagery = tileImageryCollection[imageryIndex];
                var imagery = tileImagery.readyImagery;
                ++imageryIndex;

                if (!defined(imagery) || imagery.imageryLayer.alpha === 0.0) {
                    continue;
                }

                var texture = tileImagery.useWebMercatorT ? imagery.textureWebMercator : imagery.texture;

                                if (!defined(texture)) {
                    // Our "ready" texture isn't actually ready.  This should never happen.
                    //
                    // Side note: It IS possible for it to not be in the READY ImageryState, though.
                    // This can happen when a single imagery tile is shared by two terrain tiles (common)
                    // and one of them (A) needs a geographic version of the tile because it is near the poles,
                    // and the other (B) does not.  B can and will transition the imagery tile to the READY state
                    // without reprojecting to geographic.  Then, later, A will deem that same tile not-ready-yet
                    // because it only has the Web Mercator texture, and flip it back to the TRANSITIONING state.
                    // The imagery tile won't be in the READY state anymore, but it's still READY enough for B's
                    // purposes.
                    throw new DeveloperError('readyImagery is not actually ready!');
                }
                
                var imageryLayer = imagery.imageryLayer;

                if (!defined(tileImagery.textureTranslationAndScale)) {
                    tileImagery.textureTranslationAndScale = imageryLayer._calculateTextureTranslationAndScale(tile, tileImagery);
                }

                uniformMapProperties.dayTextures[numberOfDayTextures] = texture;
                uniformMapProperties.dayTextureTranslationAndScale[numberOfDayTextures] = tileImagery.textureTranslationAndScale;
                uniformMapProperties.dayTextureTexCoordsRectangle[numberOfDayTextures] = tileImagery.textureCoordinateRectangle;
                uniformMapProperties.dayTextureUseWebMercatorT[numberOfDayTextures] = tileImagery.useWebMercatorT;

                uniformMapProperties.dayTextureAlpha[numberOfDayTextures] = imageryLayer.alpha;
                applyAlpha = applyAlpha || uniformMapProperties.dayTextureAlpha[numberOfDayTextures] !== 1.0;

                uniformMapProperties.dayTextureBrightness[numberOfDayTextures] = imageryLayer.brightness;
                applyBrightness = applyBrightness || uniformMapProperties.dayTextureBrightness[numberOfDayTextures] !== ImageryLayer.DEFAULT_BRIGHTNESS;

                uniformMapProperties.dayTextureContrast[numberOfDayTextures] = imageryLayer.contrast;
                applyContrast = applyContrast || uniformMapProperties.dayTextureContrast[numberOfDayTextures] !== ImageryLayer.DEFAULT_CONTRAST;

                uniformMapProperties.dayTextureHue[numberOfDayTextures] = imageryLayer.hue;
                applyHue = applyHue || uniformMapProperties.dayTextureHue[numberOfDayTextures] !== ImageryLayer.DEFAULT_HUE;

                uniformMapProperties.dayTextureSaturation[numberOfDayTextures] = imageryLayer.saturation;
                applySaturation = applySaturation || uniformMapProperties.dayTextureSaturation[numberOfDayTextures] !== ImageryLayer.DEFAULT_SATURATION;

                uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] = 1.0 / imageryLayer.gamma;
                applyGamma = applyGamma || uniformMapProperties.dayTextureOneOverGamma[numberOfDayTextures] !== 1.0 / ImageryLayer.DEFAULT_GAMMA;

                uniformMapProperties.dayTextureSplit[numberOfDayTextures] = imageryLayer.splitDirection;
                applySplit = applySplit || uniformMapProperties.dayTextureSplit[numberOfDayTextures] !== 0.0;

                if (defined(imagery.credits)) {
                    var creditDisplay = frameState.creditDisplay;
                    var credits = imagery.credits;
                    for (var creditIndex = 0, creditLength = credits.length; creditIndex < creditLength; ++creditIndex) {
                        creditDisplay.addCredit(credits[creditIndex]);
                    }
                }

                ++numberOfDayTextures;
            }

            // trim texture array to the used length so we don't end up using old textures
            // which might get destroyed eventually
            uniformMapProperties.dayTextures.length = numberOfDayTextures;
            uniformMapProperties.waterMask = waterMaskTexture;
            Cartesian4.clone(surfaceTile.waterMaskTranslationAndScale, uniformMapProperties.waterMaskTranslationAndScale);

            uniformMapProperties.minMaxHeight.x = encoding.minimumHeight;
            uniformMapProperties.minMaxHeight.y = encoding.maximumHeight;
            Matrix4.clone(encoding.matrix, uniformMapProperties.scaleAndBias);

            command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(frameState, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, applySplit, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog);
            command.castShadows = castShadows;
            command.receiveShadows = receiveShadows;
            command.renderState = renderState;
            command.primitiveType = PrimitiveType.TRIANGLES;
            command.vertexArray = surfaceTile.vertexArray;
            command.uniformMap = uniformMap;
            command.pass = Pass.GLOBE;

            if (tileProvider._debug.wireframe) {
                createWireframeVertexArrayIfNecessary(context, tileProvider, tile);
                if (defined(surfaceTile.wireframeVertexArray)) {
                    command.vertexArray = surfaceTile.wireframeVertexArray;
                    command.primitiveType = PrimitiveType.LINES;
                }
            }

            var boundingVolume = command.boundingVolume;
            var orientedBoundingBox = command.orientedBoundingBox;

            if (frameState.mode !== SceneMode.SCENE3D) {
                BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, frameState.mapProjection, surfaceTile.minimumHeight, surfaceTile.maximumHeight, boundingVolume);
                Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);

                if (frameState.mode === SceneMode.MORPHING) {
                    boundingVolume = BoundingSphere.union(surfaceTile.boundingSphere3D, boundingVolume, boundingVolume);
                }
            } else {
                command.boundingVolume = BoundingSphere.clone(surfaceTile.boundingSphere3D, boundingVolume);
                command.orientedBoundingBox = OrientedBoundingBox.clone(surfaceTile.orientedBoundingBox, orientedBoundingBox);
            }

            frameState.commandList.push(command);

            renderState = otherPassesRenderState;
            initialColor = otherPassesInitialColor;
        } while (imageryIndex < imageryLen);
    }

    function addPickCommandsForTile(tileProvider, drawCommand, frameState) {
        var pickCommand;
        if (tileProvider._pickCommands.length <= tileProvider._usedPickCommands) {
            pickCommand = new DrawCommand();
            pickCommand.cull = false;

            tileProvider._pickCommands.push(pickCommand);
        } else {
            pickCommand = tileProvider._pickCommands[tileProvider._usedPickCommands];
        }

        ++tileProvider._usedPickCommands;

        var surfaceTile = drawCommand.owner.data;
        var useWebMercatorProjection = frameState.projection instanceof WebMercatorProjection;

        pickCommand.shaderProgram = tileProvider._surfaceShaderSet.getPickShaderProgram(frameState, surfaceTile, useWebMercatorProjection);
        pickCommand.renderState = tileProvider._pickRenderState;

        pickCommand.owner = drawCommand.owner;
        pickCommand.primitiveType = drawCommand.primitiveType;
        pickCommand.vertexArray = drawCommand.vertexArray;
        pickCommand.uniformMap = drawCommand.uniformMap;
        pickCommand.boundingVolume = drawCommand.boundingVolume;
        pickCommand.orientedBoundingBox = drawCommand.orientedBoundingBox;
        pickCommand.pass = drawCommand.pass;

        frameState.commandList.push(pickCommand);
    }

    return GlobeSurfaceTileProvider;
});

/*global define*/
define('Scene/ImageryLayerCollection',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/Math',
        '../Core/Rectangle',
        '../ThirdParty/when',
        './ImageryLayer'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Event,
        CesiumMath,
        Rectangle,
        when,
        ImageryLayer) {
    'use strict';

    /**
     * An ordered collection of imagery layers.
     *
     * @alias ImageryLayerCollection
     * @constructor
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Adjustment.html|Cesium Sandcastle Imagery Adjustment Demo}
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Imagery%20Layers%20Manipulation.html|Cesium Sandcastle Imagery Manipulation Demo}
     */
    function ImageryLayerCollection() {
        this._layers = [];

        /**
         * An event that is raised when a layer is added to the collection.  Event handlers are passed the layer that
         * was added and the index at which it was added.
         * @type {Event}
         * @default Event()
         */
        this.layerAdded = new Event();

        /**
         * An event that is raised when a layer is removed from the collection.  Event handlers are passed the layer that
         * was removed and the index from which it was removed.
         * @type {Event}
         * @default Event()
         */
        this.layerRemoved = new Event();

        /**
         * An event that is raised when a layer changes position in the collection.  Event handlers are passed the layer that
         * was moved, its new index after the move, and its old index prior to the move.
         * @type {Event}
         * @default Event()
         */
        this.layerMoved = new Event();

        /**
         * An event that is raised when a layer is shown or hidden by setting the
         * {@link ImageryLayer#show} property.  Event handlers are passed a reference to this layer,
         * the index of the layer in the collection, and a flag that is true if the layer is now
         * shown or false if it is now hidden.
         *
         * @type {Event}
         * @default Event()
         */
        this.layerShownOrHidden = new Event();
    }

    defineProperties(ImageryLayerCollection.prototype, {
        /**
         * Gets the number of layers in this collection.
         * @memberof ImageryLayerCollection.prototype
         * @type {Number}
         */
        length : {
            get : function() {
                return this._layers.length;
            }
        }
    });

    /**
     * Adds a layer to the collection.
     *
     * @param {ImageryLayer} layer the layer to add.
     * @param {Number} [index] the index to add the layer at.  If omitted, the layer will
     *                         added on top of all existing layers.
     *
     * @exception {DeveloperError} index, if supplied, must be greater than or equal to zero and less than or equal to the number of the layers.
     */
    ImageryLayerCollection.prototype.add = function(layer, index) {
        var hasIndex = defined(index);

                if (!defined(layer)) {
            throw new DeveloperError('layer is required.');
        }
        if (hasIndex) {
            if (index < 0) {
                throw new DeveloperError('index must be greater than or equal to zero.');
            } else if (index > this._layers.length) {
                throw new DeveloperError('index must be less than or equal to the number of layers.');
            }
        }
        
        if (!hasIndex) {
            index = this._layers.length;
            this._layers.push(layer);
        } else {
            this._layers.splice(index, 0, layer);
        }

        this._update();
        this.layerAdded.raiseEvent(layer, index);
    };

    /**
     * Creates a new layer using the given ImageryProvider and adds it to the collection.
     *
     * @param {ImageryProvider} imageryProvider the imagery provider to create a new layer for.
     * @param {Number} [index] the index to add the layer at.  If omitted, the layer will
     *                         added on top of all existing layers.
     * @returns {ImageryLayer} The newly created layer.
     */
    ImageryLayerCollection.prototype.addImageryProvider = function(imageryProvider, index) {
                if (!defined(imageryProvider)) {
            throw new DeveloperError('imageryProvider is required.');
        }
        
        var layer = new ImageryLayer(imageryProvider);
        this.add(layer, index);
        return layer;
    };

    /**
     * Removes a layer from this collection, if present.
     *
     * @param {ImageryLayer} layer The layer to remove.
     * @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them.
     * @returns {Boolean} true if the layer was in the collection and was removed,
     *                    false if the layer was not in the collection.
     */
    ImageryLayerCollection.prototype.remove = function(layer, destroy) {
        destroy = defaultValue(destroy, true);

        var index = this._layers.indexOf(layer);
        if (index !== -1) {
            this._layers.splice(index, 1);

            this._update();

            this.layerRemoved.raiseEvent(layer, index);

            if (destroy) {
                layer.destroy();
            }

            return true;
        }

        return false;
    };

    /**
     * Removes all layers from this collection.
     *
     * @param {Boolean} [destroy=true] whether to destroy the layers in addition to removing them.
     */
    ImageryLayerCollection.prototype.removeAll = function(destroy) {
        destroy = defaultValue(destroy, true);

        var layers = this._layers;
        for (var i = 0, len = layers.length; i < len; i++) {
            var layer = layers[i];
            this.layerRemoved.raiseEvent(layer, i);

            if (destroy) {
                layer.destroy();
            }
        }

        this._layers = [];
    };

    /**
     * Checks to see if the collection contains a given layer.
     *
     * @param {ImageryLayer} layer the layer to check for.
     *
     * @returns {Boolean} true if the collection contains the layer, false otherwise.
     */
    ImageryLayerCollection.prototype.contains = function(layer) {
        return this.indexOf(layer) !== -1;
    };

    /**
     * Determines the index of a given layer in the collection.
     *
     * @param {ImageryLayer} layer The layer to find the index of.
     *
     * @returns {Number} The index of the layer in the collection, or -1 if the layer does not exist in the collection.
     */
    ImageryLayerCollection.prototype.indexOf = function(layer) {
        return this._layers.indexOf(layer);
    };

    /**
     * Gets a layer by index from the collection.
     *
     * @param {Number} index the index to retrieve.
     *
     * @returns {ImageryLayer} The imagery layer at the given index.
     */
    ImageryLayerCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.', 'index');
        }
        
        return this._layers[index];
    };

    function getLayerIndex(layers, layer) {
                if (!defined(layer)) {
            throw new DeveloperError('layer is required.');
        }
        
        var index = layers.indexOf(layer);

                if (index === -1) {
            throw new DeveloperError('layer is not in this collection.');
        }
        
        return index;
    }

    function swapLayers(collection, i, j) {
        var arr = collection._layers;
        i = CesiumMath.clamp(i, 0, arr.length - 1);
        j = CesiumMath.clamp(j, 0, arr.length - 1);

        if (i === j) {
            return;
        }

        var temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;

        collection._update();

        collection.layerMoved.raiseEvent(temp, j, i);
    }

    /**
     * Raises a layer up one position in the collection.
     *
     * @param {ImageryLayer} layer the layer to move.
     *
     * @exception {DeveloperError} layer is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    ImageryLayerCollection.prototype.raise = function(layer) {
        var index = getLayerIndex(this._layers, layer);
        swapLayers(this, index, index + 1);
    };

    /**
     * Lowers a layer down one position in the collection.
     *
     * @param {ImageryLayer} layer the layer to move.
     *
     * @exception {DeveloperError} layer is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    ImageryLayerCollection.prototype.lower = function(layer) {
        var index = getLayerIndex(this._layers, layer);
        swapLayers(this, index, index - 1);
    };

    /**
     * Raises a layer to the top of the collection.
     *
     * @param {ImageryLayer} layer the layer to move.
     *
     * @exception {DeveloperError} layer is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    ImageryLayerCollection.prototype.raiseToTop = function(layer) {
        var index = getLayerIndex(this._layers, layer);
        if (index === this._layers.length - 1) {
            return;
        }
        this._layers.splice(index, 1);
        this._layers.push(layer);

        this._update();

        this.layerMoved.raiseEvent(layer, this._layers.length - 1, index);
    };

    /**
     * Lowers a layer to the bottom of the collection.
     *
     * @param {ImageryLayer} layer the layer to move.
     *
     * @exception {DeveloperError} layer is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     */
    ImageryLayerCollection.prototype.lowerToBottom = function(layer) {
        var index = getLayerIndex(this._layers, layer);
        if (index === 0) {
            return;
        }
        this._layers.splice(index, 1);
        this._layers.splice(0, 0, layer);

        this._update();

        this.layerMoved.raiseEvent(layer, 0, index);
    };

    var applicableRectangleScratch = new Rectangle();

    /**
     * Asynchronously determines the imagery layer features that are intersected by a pick ray.  The intersected imagery
     * layer features are found by invoking {@link ImageryProvider#pickFeatures} for each imagery layer tile intersected
     * by the pick ray.  To compute a pick ray from a location on the screen, use {@link Camera.getPickRay}.
     *
     * @param {Ray} ray The ray to test for intersection.
     * @param {Scene} scene The scene.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise that resolves to an array of features intersected by the pick ray.
     *                                             If it can be quickly determined that no features are intersected (for example,
     *                                             because no active imagery providers support {@link ImageryProvider#pickFeatures}
     *                                             or because the pick ray does not intersect the surface), this function will
     *                                             return undefined.
     *
     * @example
     * var pickRay = viewer.camera.getPickRay(windowPosition);
     * var featuresPromise = viewer.imageryLayers.pickImageryLayerFeatures(pickRay, viewer.scene);
     * if (!Cesium.defined(featuresPromise)) {
     *     console.log('No features picked.');
     * } else {
     *     Cesium.when(featuresPromise, function(features) {
     *         // This function is called asynchronously when the list if picked features is available.
     *         console.log('Number of features: ' + features.length);
     *         if (features.length > 0) {
     *             console.log('First feature name: ' + features[0].name);
     *         }
     *     });
     * }
     */
    ImageryLayerCollection.prototype.pickImageryLayerFeatures = function(ray, scene) {
        // Find the picked location on the globe.
        var pickedPosition = scene.globe.pick(ray, scene);
        if (!defined(pickedPosition)) {
            return undefined;
        }

        var pickedLocation = scene.globe.ellipsoid.cartesianToCartographic(pickedPosition);

        // Find the terrain tile containing the picked location.
        var tilesToRender = scene.globe._surface._tilesToRender;
        var pickedTile;

        for (var textureIndex = 0; !defined(pickedTile) && textureIndex < tilesToRender.length; ++textureIndex) {
            var tile = tilesToRender[textureIndex];
            if (Rectangle.contains(tile.rectangle, pickedLocation)) {
                pickedTile = tile;
            }
        }

        if (!defined(pickedTile)) {
            return undefined;
        }

        // Pick against all attached imagery tiles containing the pickedLocation.
        var imageryTiles = pickedTile.data.imagery;

        var promises = [];
        var imageryLayers = [];
        for (var i = imageryTiles.length - 1; i >= 0; --i) {
            var terrainImagery = imageryTiles[i];
            var imagery = terrainImagery.readyImagery;
            if (!defined(imagery)) {
                continue;
            }
            var provider = imagery.imageryLayer.imageryProvider;
            if (!defined(provider.pickFeatures)) {
                continue;
            }

            if (!Rectangle.contains(imagery.rectangle, pickedLocation)) {
                continue;
            }

            // If this imagery came from a parent, it may not be applicable to its entire rectangle.
            // Check the textureCoordinateRectangle.
            var applicableRectangle = applicableRectangleScratch;

            var epsilon = 1 / 1024; // 1/4 of a pixel in a typical 256x256 tile.
            applicableRectangle.west = CesiumMath.lerp(pickedTile.rectangle.west, pickedTile.rectangle.east, terrainImagery.textureCoordinateRectangle.x - epsilon);
            applicableRectangle.east = CesiumMath.lerp(pickedTile.rectangle.west, pickedTile.rectangle.east, terrainImagery.textureCoordinateRectangle.z + epsilon);
            applicableRectangle.south = CesiumMath.lerp(pickedTile.rectangle.south, pickedTile.rectangle.north, terrainImagery.textureCoordinateRectangle.y - epsilon);
            applicableRectangle.north = CesiumMath.lerp(pickedTile.rectangle.south, pickedTile.rectangle.north, terrainImagery.textureCoordinateRectangle.w + epsilon);
            if (!Rectangle.contains(applicableRectangle, pickedLocation)) {
                continue;
            }

            var promise = provider.pickFeatures(imagery.x, imagery.y, imagery.level, pickedLocation.longitude, pickedLocation.latitude);
            if (!defined(promise)) {
                continue;
            }

            promises.push(promise);
            imageryLayers.push(imagery.imageryLayer);
        }

        if (promises.length === 0) {
            return undefined;
        }

        return when.all(promises, function(results) {
            var features = [];

            for (var resultIndex = 0; resultIndex < results.length; ++resultIndex) {
                var result = results[resultIndex];
                var image = imageryLayers[resultIndex];

                if (defined(result) && result.length > 0) {
                    for (var featureIndex = 0; featureIndex < result.length; ++featureIndex) {
                        var feature = result[featureIndex];
                        feature.imageryLayer = image;

                        // For features without a position, use the picked location.
                        if (!defined(feature.position)) {
                            feature.position = pickedLocation;
                        }

                        features.push(feature);
                    }
                }
            }

            return features;
        });
    };

    /**
     * Updates frame state to execute any queued texture re-projections.
     *
     * @private
     *
     * @param {FrameState} frameState The frameState.
     */
    ImageryLayerCollection.prototype.queueReprojectionCommands = function(frameState) {
        var layers = this._layers;
        for (var i = 0, len = layers.length; i < len; ++i) {
            layers[i].queueReprojectionCommands(frameState);
        }
    };

    /**
     * Cancels re-projection commands queued for the next frame.
     *
     * @private
     */
    ImageryLayerCollection.prototype.cancelReprojections = function() {
        var layers = this._layers;
        for (var i = 0, len = layers.length; i < len; ++i) {
            layers[i].cancelReprojections();
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} true if this object was destroyed; otherwise, false.
     *
     * @see ImageryLayerCollection#destroy
     */
    ImageryLayerCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by all layers in this collection.  Explicitly destroying this
     * object allows for deterministic release of WebGL resources, instead of relying on the garbage
     * collector.
     * <br /><br />
     * Once this object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * layerCollection = layerCollection && layerCollection.destroy();
     * 
     * @see ImageryLayerCollection#isDestroyed
     */
    ImageryLayerCollection.prototype.destroy = function() {
        this.removeAll(true);
        return destroyObject(this);
    };

    ImageryLayerCollection.prototype._update = function() {
        var isBaseLayer = true;
        var layers = this._layers;
        var layersShownOrHidden;
        var layer;
        for (var i = 0, len = layers.length; i < len; ++i) {
            layer = layers[i];

            layer._layerIndex = i;

            if (layer.show) {
                layer._isBaseLayer = isBaseLayer;
                isBaseLayer = false;
            } else {
                layer._isBaseLayer = false;
            }

            if (layer.show !== layer._show) {
                if (defined(layer._show)) {
                    if (!defined(layersShownOrHidden)) {
                        layersShownOrHidden = [];
                    }
                    layersShownOrHidden.push(layer);
                }
                layer._show = layer.show;
            }
        }

        if (defined(layersShownOrHidden)) {
            for (i = 0, len = layersShownOrHidden.length; i < len; ++i) {
                layer = layersShownOrHidden[i];
                this.layerShownOrHidden.raiseEvent(layer, layer._layerIndex, layer.show);
            }
        }
    };

    return ImageryLayerCollection;
});

/*global define*/
define('Scene/QuadtreeOccluders',[
        '../Core/Cartesian3',
        '../Core/defineProperties',
        '../Core/EllipsoidalOccluder'
    ], function(
        Cartesian3,
        defineProperties,
        EllipsoidalOccluder) {
    'use strict';

    /**
     * A set of occluders that can be used to test quadtree tiles for occlusion.
     *
     * @alias QuadtreeOccluders
     * @constructor
     * @private
     *
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid that potentially occludes tiles.
     */
    function QuadtreeOccluders(options) {
        this._ellipsoid = new EllipsoidalOccluder(options.ellipsoid, Cartesian3.ZERO);
    }

    defineProperties(QuadtreeOccluders.prototype, {
        /**
         * Gets the {@link EllipsoidalOccluder} that can be used to determine if a point is
         * occluded by an {@link Ellipsoid}.
         * @type {EllipsoidalOccluder}
         * @memberof QuadtreeOccluders.prototype
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        }
    });

    return QuadtreeOccluders;
});

/*global define*/
define('Scene/QuadtreeTile',[
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Rectangle',
        './QuadtreeTileLoadState'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        Rectangle,
        QuadtreeTileLoadState) {
    'use strict';

    /**
     * A single tile in a {@link QuadtreePrimitive}.
     *
     * @alias QuadtreeTile
     * @constructor
     * @private
     *
     * @param {Number} options.level The level of the tile in the quadtree.
     * @param {Number} options.x The X coordinate of the tile in the quadtree.  0 is the westernmost tile.
     * @param {Number} options.y The Y coordinate of the tile in the quadtree.  0 is the northernmost tile.
     * @param {TilingScheme} options.tilingScheme The tiling scheme in which this tile exists.
     * @param {QuadtreeTile} [options.parent] This tile's parent, or undefined if this is a root tile.
     */
    function QuadtreeTile(options) {
                if (!defined(options)) {
            throw new DeveloperError('options is required.');
        }
        if (!defined(options.x)) {
            throw new DeveloperError('options.x is required.');
        } else if (!defined(options.y)) {
            throw new DeveloperError('options.y is required.');
        } else if (options.x < 0 || options.y < 0) {
            throw new DeveloperError('options.x and options.y must be greater than or equal to zero.');
        }
        if (!defined(options.level)) {
            throw new DeveloperError('options.level is required and must be greater than or equal to zero.');
        }
        if (!defined(options.tilingScheme)) {
            throw new DeveloperError('options.tilingScheme is required.');
        }
        
        this._tilingScheme = options.tilingScheme;
        this._x = options.x;
        this._y = options.y;
        this._level = options.level;
        this._parent = options.parent;
        this._rectangle = this._tilingScheme.tileXYToRectangle(this._x, this._y, this._level);

        this._southwestChild = undefined;
        this._southeastChild = undefined;
        this._northwestChild = undefined;
        this._northeastChild = undefined;

        // QuadtreeTileReplacementQueue gets/sets these private properties.
        this._replacementPrevious = undefined;
        this._replacementNext = undefined;

        // The distance from the camera to this tile, updated when the tile is selected
        // for rendering.  We can get rid of this if we have a better way to sort by
        // distance - for example, by using the natural ordering of a quadtree.
        // QuadtreePrimitive gets/sets this private property.
        this._distance = 0.0;

        this._customData = [];
        this._frameUpdated = undefined;
        this._frameRendered = undefined;

        /**
         * Gets or sets the current state of the tile in the tile load pipeline.
         * @type {QuadtreeTileLoadState}
         * @default {@link QuadtreeTileLoadState.START}
         */
        this.state = QuadtreeTileLoadState.START;

        /**
         * Gets or sets a value indicating whether or not the tile is currently renderable.
         * @type {Boolean}
         * @default false
         */
        this.renderable = false;

        /**
         * Gets or set a value indicating whether or not the tile was entire upsampled from its
         * parent tile.  If all four children of a parent tile were upsampled from the parent,
         * we will render the parent instead of the children even if the LOD indicates that
         * the children would be preferable.
         * @type {Boolean}
         * @default false
         */
        this.upsampledFromParent = false;

        /**
         * Gets or sets the additional data associated with this tile.  The exact content is specific to the
         * {@link QuadtreeTileProvider}.
         * @type {Object}
         * @default undefined
         */
        this.data = undefined;
    }

    /**
     * Creates a rectangular set of tiles for level of detail zero, the coarsest, least detailed level.
     *
     * @memberof QuadtreeTile
     *
     * @param {TilingScheme} tilingScheme The tiling scheme for which the tiles are to be created.
     * @returns {QuadtreeTile[]} An array containing the tiles at level of detail zero, starting with the
     * tile in the northwest corner and followed by the tile (if any) to its east.
     */
    QuadtreeTile.createLevelZeroTiles = function(tilingScheme) {
                if (!defined(tilingScheme)) {
            throw new DeveloperError('tilingScheme is required.');
        }
        
        var numberOfLevelZeroTilesX = tilingScheme.getNumberOfXTilesAtLevel(0);
        var numberOfLevelZeroTilesY = tilingScheme.getNumberOfYTilesAtLevel(0);

        var result = new Array(numberOfLevelZeroTilesX * numberOfLevelZeroTilesY);

        var index = 0;
        for (var y = 0; y < numberOfLevelZeroTilesY; ++y) {
            for (var x = 0; x < numberOfLevelZeroTilesX; ++x) {
                result[index++] = new QuadtreeTile({
                    tilingScheme : tilingScheme,
                    x : x,
                    y : y,
                    level : 0
                });
            }
        }

        return result;
    };

    QuadtreeTile.prototype._updateCustomData = function(frameNumber, added, removed) {
        var customData = this.customData;

        var i;
        var data;
        var rectangle;

        if (defined(added) && defined(removed)) {
            customData = customData.filter(function(value) {
                return removed.indexOf(value) === -1;
            });
            this._customData = customData;

            rectangle = this._rectangle;
            for (i = 0; i < added.length; ++i) {
                data = added[i];
                if (Rectangle.contains(rectangle, data.positionCartographic)) {
                    customData.push(data);
                }
            }

            this._frameUpdated = frameNumber;
        } else {
            // interior or leaf tile, update from parent
            var parent = this._parent;
            if (defined(parent) && this._frameUpdated !== parent._frameUpdated) {
                customData.length = 0;

                rectangle = this._rectangle;
                var parentCustomData = parent.customData;
                for (i = 0; i < parentCustomData.length; ++i) {
                    data = parentCustomData[i];
                    if (Rectangle.contains(rectangle, data.positionCartographic)) {
                        customData.push(data);
                    }
                }

                this._frameUpdated = parent._frameUpdated;
            }
        }
    };

    defineProperties(QuadtreeTile.prototype, {
        /**
         * Gets the tiling scheme used to tile the surface.
         * @memberof QuadtreeTile.prototype
         * @type {TilingScheme}
         */
        tilingScheme : {
            get : function() {
                return this._tilingScheme;
            }
        },

        /**
         * Gets the tile X coordinate.
         * @memberof QuadtreeTile.prototype
         * @type {Number}
         */
        x : {
            get : function() {
                return this._x;
            }
        },

        /**
         * Gets the tile Y coordinate.
         * @memberof QuadtreeTile.prototype
         * @type {Number}
         */
        y : {
            get : function() {
                return this._y;
            }
        },

        /**
         * Gets the level-of-detail, where zero is the coarsest, least-detailed.
         * @memberof QuadtreeTile.prototype
         * @type {Number}
         */
        level : {
            get : function() {
                return this._level;
            }
        },

        /**
         * Gets the parent tile of this tile.
         * @memberof QuadtreeTile.prototype
         * @type {QuadtreeTile}
         */
        parent : {
            get : function() {
                return this._parent;
            }
        },

        /**
         * Gets the cartographic rectangle of the tile, with north, south, east and
         * west properties in radians.
         * @memberof QuadtreeTile.prototype
         * @type {Rectangle}
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        },

        /**
         * An array of tiles that is at the next level of the tile tree.
         * @memberof QuadtreeTile.prototype
         * @type {QuadtreeTile[]}
         */
        children : {
            get : function() {
                return [this.northwestChild, this.northeastChild, this.southwestChild, this.southeastChild];
            }
        },

        /**
         * Gets the southwest child tile.
         * @memberof QuadtreeTile.prototype
         * @type {QuadtreeTile}
         */
        southwestChild : {
            get : function() {
                if (!defined(this._southwestChild)) {
                    this._southwestChild = new QuadtreeTile({
                        tilingScheme : this.tilingScheme,
                        x : this.x * 2,
                        y : this.y * 2 + 1,
                        level : this.level + 1,
                        parent : this
                    });
                }
                return this._southwestChild;
            }
        },

        /**
         * Gets the southeast child tile.
         * @memberof QuadtreeTile.prototype
         * @type {QuadtreeTile}
         */
        southeastChild : {
            get : function() {
                if (!defined(this._southeastChild)) {
                    this._southeastChild = new QuadtreeTile({
                        tilingScheme : this.tilingScheme,
                        x : this.x * 2 + 1,
                        y : this.y * 2 + 1,
                        level : this.level + 1,
                        parent : this
                    });
                }
                return this._southeastChild;
            }
        },

        /**
         * Gets the northwest child tile.
         * @memberof QuadtreeTile.prototype
         * @type {QuadtreeTile}
         */
        northwestChild : {
            get : function() {
                if (!defined(this._northwestChild)) {
                    this._northwestChild = new QuadtreeTile({
                        tilingScheme : this.tilingScheme,
                        x : this.x * 2,
                        y : this.y * 2,
                        level : this.level + 1,
                        parent : this
                    });
                }
                return this._northwestChild;
            }
        },

        /**
         * Gets the northeast child tile.
         * @memberof QuadtreeTile.prototype
         * @type {QuadtreeTile}
         */
        northeastChild : {
            get : function() {
                if (!defined(this._northeastChild)) {
                    this._northeastChild = new QuadtreeTile({
                        tilingScheme : this.tilingScheme,
                        x : this.x * 2 + 1,
                        y : this.y * 2,
                        level : this.level + 1,
                        parent : this
                    });
                }
                return this._northeastChild;
            }
        },

        /**
         * An array of objects associated with this tile.
         * @memberof QuadtreeTile.prototype
         * @type {Array}
         */
        customData : {
            get : function() {
                return this._customData;
            }
        },

        /**
         * Gets a value indicating whether or not this tile needs further loading.
         * This property will return true if the {@link QuadtreeTile#state} is
         * <code>START</code> or <code>LOADING</code>.
         * @memberof QuadtreeTile.prototype
         * @type {Boolean}
         */
        needsLoading : {
            get : function() {
                return this.state < QuadtreeTileLoadState.DONE;
            }
        },

        /**
         * Gets a value indicating whether or not this tile is eligible to be unloaded.
         * Typically, a tile is ineligible to be unloaded while an asynchronous operation,
         * such as a request for data, is in progress on it.  A tile will never be
         * unloaded while it is needed for rendering, regardless of the value of this
         * property.  If {@link QuadtreeTile#data} is defined and has an
         * <code>eligibleForUnloading</code> property, the value of that property is returned.
         * Otherwise, this property returns true.
         * @memberof QuadtreeTile.prototype
         * @type {Boolean}
         */
        eligibleForUnloading : {
            get : function() {
                var result = true;

                if (defined(this.data)) {
                    result = this.data.eligibleForUnloading;
                    if (!defined(result)) {
                        result = true;
                    }
                }

                return result;
            }
        }
    });

    /**
     * Frees the resources associated with this tile and returns it to the <code>START</code>
     * {@link QuadtreeTileLoadState}.  If the {@link QuadtreeTile#data} property is defined and it
     * has a <code>freeResources</code> method, the method will be invoked.
     *
     * @memberof QuadtreeTile
     */
    QuadtreeTile.prototype.freeResources = function() {
        this.state = QuadtreeTileLoadState.START;
        this.renderable = false;
        this.upsampledFromParent = false;

        if (defined(this.data) && defined(this.data.freeResources)) {
            this.data.freeResources();
        }

        freeTile(this._southwestChild);
        this._southwestChild = undefined;
        freeTile(this._southeastChild);
        this._southeastChild = undefined;
        freeTile(this._northwestChild);
        this._northwestChild = undefined;
        freeTile(this._northeastChild);
        this._northeastChild = undefined;
    };

    function freeTile(tile) {
        if (defined(tile)) {
            tile.freeResources();
        }
    }

    return QuadtreeTile;
});

/*global define*/
define('Scene/TileReplacementQueue',[
        '../Core/defined'
    ], function(
        defined) {
    'use strict';

    /**
     * A priority queue of tiles to be replaced, if necessary, to make room for new tiles.  The queue
     * is implemented as a linked list.
     *
     * @alias TileReplacementQueue
     * @private
     */
    function TileReplacementQueue() {
        this.head = undefined;
        this.tail = undefined;
        this.count = 0;
        this._lastBeforeStartOfFrame = undefined;
    }

    /**
     * Marks the start of the render frame.  Tiles before (closer to the head) this tile in the
     * list were used last frame and must not be unloaded.
     */
    TileReplacementQueue.prototype.markStartOfRenderFrame = function() {
        this._lastBeforeStartOfFrame = this.head;
    };

    /**
     * Reduces the size of the queue to a specified size by unloading the least-recently used
     * tiles.  Tiles that were used last frame will not be unloaded, even if that puts the number
     * of tiles above the specified maximum.
     *
     * @param {Number} maximumTiles The maximum number of tiles in the queue.
     */
    TileReplacementQueue.prototype.trimTiles = function(maximumTiles) {
        var tileToTrim = this.tail;
        var keepTrimming = true;
        while (keepTrimming &&
               defined(this._lastBeforeStartOfFrame) &&
               this.count > maximumTiles &&
               defined(tileToTrim)) {
            // Stop trimming after we process the last tile not used in the
            // current frame.
            keepTrimming = tileToTrim !== this._lastBeforeStartOfFrame;

            var previous = tileToTrim.replacementPrevious;

            if (tileToTrim.eligibleForUnloading) {
                tileToTrim.freeResources();
                remove(this, tileToTrim);
            }

            tileToTrim = previous;
        }
    };

    function remove(tileReplacementQueue, item) {
        var previous = item.replacementPrevious;
        var next = item.replacementNext;

        if (item === tileReplacementQueue._lastBeforeStartOfFrame) {
            tileReplacementQueue._lastBeforeStartOfFrame = next;
        }

        if (item === tileReplacementQueue.head) {
            tileReplacementQueue.head = next;
        } else {
            previous.replacementNext = next;
        }

        if (item === tileReplacementQueue.tail) {
            tileReplacementQueue.tail = previous;
        } else {
            next.replacementPrevious = previous;
        }

        item.replacementPrevious = undefined;
        item.replacementNext = undefined;

        --tileReplacementQueue.count;
    }

    /**
     * Marks a tile as rendered this frame and moves it before the first tile that was not rendered
     * this frame.
     *
     * @param {TileReplacementQueue} item The tile that was rendered.
     */
    TileReplacementQueue.prototype.markTileRendered = function(item) {
        var head = this.head;
        if (head === item) {
            if (item === this._lastBeforeStartOfFrame) {
                this._lastBeforeStartOfFrame = item.replacementNext;
            }
            return;
        }

        ++this.count;

        if (!defined(head)) {
            // no other tiles in the list
            item.replacementPrevious = undefined;
            item.replacementNext = undefined;
            this.head = item;
            this.tail = item;
            return;
        }

        if (defined(item.replacementPrevious) || defined(item.replacementNext)) {
            // tile already in the list, remove from its current location
            remove(this, item);
        }

        item.replacementPrevious = undefined;
        item.replacementNext = head;
        head.replacementPrevious = item;

        this.head = item;
    };

    return TileReplacementQueue;
});

/*global define*/
define('Scene/QuadtreePrimitive',[
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/getTimestamp',
        '../Core/Math',
        '../Core/Ray',
        '../Core/Rectangle',
        '../Core/Visibility',
        './QuadtreeOccluders',
        './QuadtreeTile',
        './QuadtreeTileLoadState',
        './SceneMode',
        './TileReplacementQueue'
    ], function(
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        getTimestamp,
        CesiumMath,
        Ray,
        Rectangle,
        Visibility,
        QuadtreeOccluders,
        QuadtreeTile,
        QuadtreeTileLoadState,
        SceneMode,
        TileReplacementQueue) {
    'use strict';

    /**
     * Renders massive sets of data by utilizing level-of-detail and culling.  The globe surface is divided into
     * a quadtree of tiles with large, low-detail tiles at the root and small, high-detail tiles at the leaves.
     * The set of tiles to render is selected by projecting an estimate of the geometric error in a tile onto
     * the screen to estimate screen-space error, in pixels, which must be below a user-specified threshold.
     * The actual content of the tiles is arbitrary and is specified using a {@link QuadtreeTileProvider}.
     *
     * @alias QuadtreePrimitive
     * @constructor
     * @private
     *
     * @param {QuadtreeTileProvider} options.tileProvider The tile provider that loads, renders, and estimates
     *        the distance to individual tiles.
     * @param {Number} [options.maximumScreenSpaceError=2] The maximum screen-space error, in pixels, that is allowed.
     *        A higher maximum error will render fewer tiles and improve performance, while a lower
     *        value will improve visual quality.
     * @param {Number} [options.tileCacheSize=100] The maximum number of tiles that will be retained in the tile cache.
     *        Note that tiles will never be unloaded if they were used for rendering the last
     *        frame, so the actual number of resident tiles may be higher.  The value of
     *        this property will not affect visual quality.
     */
    function QuadtreePrimitive(options) {
                if (!defined(options) || !defined(options.tileProvider)) {
            throw new DeveloperError('options.tileProvider is required.');
        }
        if (defined(options.tileProvider.quadtree)) {
            throw new DeveloperError('A QuadtreeTileProvider can only be used with a single QuadtreePrimitive');
        }
        
        this._tileProvider = options.tileProvider;
        this._tileProvider.quadtree = this;

        this._debug = {
            enableDebugOutput : false,

            maxDepth : 0,
            tilesVisited : 0,
            tilesCulled : 0,
            tilesRendered : 0,
            tilesWaitingForChildren : 0,

            lastMaxDepth : -1,
            lastTilesVisited : -1,
            lastTilesCulled : -1,
            lastTilesRendered : -1,
            lastTilesWaitingForChildren : -1,

            suspendLodUpdate : false
        };

        var tilingScheme = this._tileProvider.tilingScheme;
        var ellipsoid = tilingScheme.ellipsoid;

        this._tilesToRender = [];
        this._tileLoadQueueHigh = []; // high priority tiles are preventing refinement
        this._tileLoadQueueMedium = []; // medium priority tiles are being rendered
        this._tileLoadQueueLow = []; // low priority tiles were refined past or are non-visible parts of quads.
        this._tileReplacementQueue = new TileReplacementQueue();
        this._levelZeroTiles = undefined;
        this._levelZeroTilesReady = false;
        this._loadQueueTimeSlice = 5.0;

        this._addHeightCallbacks = [];
        this._removeHeightCallbacks = [];

        this._tileToUpdateHeights = [];
        this._lastTileIndex = 0;
        this._updateHeightsTimeSlice = 2.0;

        /**
         * Gets or sets the maximum screen-space error, in pixels, that is allowed.
         * A higher maximum error will render fewer tiles and improve performance, while a lower
         * value will improve visual quality.
         * @type {Number}
         * @default 2
         */
        this.maximumScreenSpaceError = defaultValue(options.maximumScreenSpaceError, 2);

        /**
         * Gets or sets the maximum number of tiles that will be retained in the tile cache.
         * Note that tiles will never be unloaded if they were used for rendering the last
         * frame, so the actual number of resident tiles may be higher.  The value of
         * this property will not affect visual quality.
         * @type {Number}
         * @default 100
         */
        this.tileCacheSize = defaultValue(options.tileCacheSize, 100);

        this._occluders = new QuadtreeOccluders({
            ellipsoid : ellipsoid
        });

        this._tileLoadProgressEvent = new Event();
        this._lastTileLoadQueueLength = 0;
    }

    defineProperties(QuadtreePrimitive.prototype, {
        /**
         * Gets the provider of {@link QuadtreeTile} instances for this quadtree.
         * @type {QuadtreeTile}
         * @memberof QuadtreePrimitive.prototype
         */
        tileProvider : {
            get : function() {
                return this._tileProvider;
            }
        },
        /**
         * Gets an event that's raised when the length of the tile load queue has changed since the last render frame.  When the load queue is empty,
         * all terrain and imagery for the current view have been loaded.  The event passes the new length of the tile load queue.
         *
         * @memberof QuadtreePrimitive.prototype
         * @type {Event}
         */
        tileLoadProgressEvent : {
            get : function() {
                return this._tileLoadProgressEvent;
            }
        }
    });

    /**
     * Invalidates and frees all the tiles in the quadtree.  The tiles must be reloaded
     * before they can be displayed.
     *
     * @memberof QuadtreePrimitive
     */
    QuadtreePrimitive.prototype.invalidateAllTiles = function() {
        // Clear the replacement queue
        var replacementQueue = this._tileReplacementQueue;
        replacementQueue.head = undefined;
        replacementQueue.tail = undefined;
        replacementQueue.count = 0;

        // Free and recreate the level zero tiles.
        var levelZeroTiles = this._levelZeroTiles;
        if (defined(levelZeroTiles)) {
            for (var i = 0; i < levelZeroTiles.length; ++i) {
                var tile = levelZeroTiles[i];
                var customData = tile.customData;
                var customDataLength = customData.length;

                for (var j = 0; j < customDataLength; ++j) {
                    var data = customData[j];
                    data.level = 0;
                    this._addHeightCallbacks.push(data);
                }

                levelZeroTiles[i].freeResources();
            }
        }

        this._levelZeroTiles = undefined;

        this._tileProvider.cancelReprojections();
    };

    /**
     * Invokes a specified function for each {@link QuadtreeTile} that is partially
     * or completely loaded.
     *
     * @param {Function} tileFunction The function to invoke for each loaded tile.  The
     *        function is passed a reference to the tile as its only parameter.
     */
    QuadtreePrimitive.prototype.forEachLoadedTile = function(tileFunction) {
        var tile = this._tileReplacementQueue.head;
        while (defined(tile)) {
            if (tile.state !== QuadtreeTileLoadState.START) {
                tileFunction(tile);
            }
            tile = tile.replacementNext;
        }
    };

    /**
     * Invokes a specified function for each {@link QuadtreeTile} that was rendered
     * in the most recent frame.
     *
     * @param {Function} tileFunction The function to invoke for each rendered tile.  The
     *        function is passed a reference to the tile as its only parameter.
     */
    QuadtreePrimitive.prototype.forEachRenderedTile = function(tileFunction) {
        var tilesRendered = this._tilesToRender;
        for (var i = 0, len = tilesRendered.length; i < len; ++i) {
            tileFunction(tilesRendered[i]);
        }
    };

    /**
     * Calls the callback when a new tile is rendered that contains the given cartographic. The only parameter
     * is the cartesian position on the tile.
     *
     * @param {Cartographic} cartographic The cartographic position.
     * @param {Function} callback The function to be called when a new tile is loaded containing cartographic.
     * @returns {Function} The function to remove this callback from the quadtree.
     */
    QuadtreePrimitive.prototype.updateHeight = function(cartographic, callback) {
        var primitive = this;
        var object = {
            positionOnEllipsoidSurface : undefined,
            positionCartographic : cartographic,
            level : -1,
            callback : callback
        };

        object.removeFunc = function() {
            var addedCallbacks = primitive._addHeightCallbacks;
            var length = addedCallbacks.length;
            for (var i = 0; i < length; ++i) {
                if (addedCallbacks[i] === object) {
                    addedCallbacks.splice(i, 1);
                    break;
                }
            }
            primitive._removeHeightCallbacks.push(object);
        };

        primitive._addHeightCallbacks.push(object);
        return object.removeFunc;
    };

    /**
     * @private
     */
    QuadtreePrimitive.prototype.beginFrame = function(frameState) {
        var passes = frameState.passes;
        if (!passes.render) {
            return;
        }

        // Gets commands for any texture re-projections and updates the credit display
        this._tileProvider.initialize(frameState);

        var debug = this._debug;
        if (debug.suspendLodUpdate) {
            return;
        }

        debug.maxDepth = 0;
        debug.tilesVisited = 0;
        debug.tilesCulled = 0;
        debug.tilesRendered = 0;
        debug.tilesWaitingForChildren = 0;

        this._tileLoadQueueHigh.length = 0;
        this._tileLoadQueueMedium.length = 0;
        this._tileLoadQueueLow.length = 0;
        this._tileReplacementQueue.markStartOfRenderFrame();
    };

    /**
     * @private
     */
    QuadtreePrimitive.prototype.update = function(frameState) {
        var passes = frameState.passes;

        if (passes.render) {
            this._tileProvider.beginUpdate(frameState);

            selectTilesForRendering(this, frameState);
            createRenderCommandsForSelectedTiles(this, frameState);

            this._tileProvider.endUpdate(frameState);
        }

        if (passes.pick && this._tilesToRender.length > 0) {
            this._tileProvider.updateForPick(frameState);
        }
    };

    /**
     * @private
     */
    QuadtreePrimitive.prototype.endFrame = function(frameState) {
        var passes = frameState.passes;
        if (!passes.render || frameState.mode === SceneMode.MORPHING) {
            // Only process the load queue for a single pass.
            // Don't process the load queue or update heights during the morph flights.
            return;
        }

        // Load/create resources for terrain and imagery. Prepare texture re-projections for the next frame.
        processTileLoadQueue(this, frameState);
        updateHeights(this, frameState);

        var debug = this._debug;
        if (debug.suspendLodUpdate) {
            return;
        }

        if (debug.enableDebugOutput) {
            if (debug.tilesVisited !== debug.lastTilesVisited ||
                debug.tilesRendered !== debug.lastTilesRendered ||
                debug.tilesCulled !== debug.lastTilesCulled ||
                debug.maxDepth !== debug.lastMaxDepth ||
                debug.tilesWaitingForChildren !== debug.lastTilesWaitingForChildren) {

                console.log('Visited ' + debug.tilesVisited + ', Rendered: ' + debug.tilesRendered + ', Culled: ' + debug.tilesCulled + ', Max Depth: ' + debug.maxDepth + ', Waiting for children: ' + debug.tilesWaitingForChildren);

                debug.lastTilesVisited = debug.tilesVisited;
                debug.lastTilesRendered = debug.tilesRendered;
                debug.lastTilesCulled = debug.tilesCulled;
                debug.lastMaxDepth = debug.maxDepth;
                debug.lastTilesWaitingForChildren = debug.tilesWaitingForChildren;
            }
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @memberof QuadtreePrimitive
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see QuadtreePrimitive#destroy
     */
    QuadtreePrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @memberof QuadtreePrimitive
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * primitive = primitive && primitive.destroy();
     *
     * @see QuadtreePrimitive#isDestroyed
     */
    QuadtreePrimitive.prototype.destroy = function() {
        this._tileProvider = this._tileProvider && this._tileProvider.destroy();
    };

    var comparisonPoint;
    var centerScratch = new Cartographic();
    function compareDistanceToPoint(a, b) {
        var center = Rectangle.center(a.rectangle, centerScratch);
        var alon = center.longitude - comparisonPoint.longitude;
        var alat = center.latitude - comparisonPoint.latitude;

        center = Rectangle.center(b.rectangle, centerScratch);
        var blon = center.longitude - comparisonPoint.longitude;
        var blat = center.latitude - comparisonPoint.latitude;

        return (alon * alon + alat * alat) - (blon * blon + blat * blat);
    }

    function selectTilesForRendering(primitive, frameState) {
        var debug = primitive._debug;
        if (debug.suspendLodUpdate) {
            return;
        }

        var i;
        var len;

        // Clear the render list.
        var tilesToRender = primitive._tilesToRender;
        tilesToRender.length = 0;

        // We can't render anything before the level zero tiles exist.
        if (!defined(primitive._levelZeroTiles)) {
            if (primitive._tileProvider.ready) {
                var tilingScheme = primitive._tileProvider.tilingScheme;
                primitive._levelZeroTiles = QuadtreeTile.createLevelZeroTiles(tilingScheme);
            } else {
                // Nothing to do until the provider is ready.
                return;
            }
        }

        primitive._occluders.ellipsoid.cameraPosition = frameState.camera.positionWC;

        var tileProvider = primitive._tileProvider;
        var occluders = primitive._occluders;

        var tile;
        var levelZeroTiles = primitive._levelZeroTiles;

        // Sort the level zero tiles by the distance from the center to the camera.
        // The level zero tiles aren't necessarily a nice neat quad, so we can use the
        // quadtree ordering we use elsewhere in the tree
        comparisonPoint = frameState.camera.positionCartographic;
        levelZeroTiles.sort(compareDistanceToPoint);

        var customDataAdded = primitive._addHeightCallbacks;
        var customDataRemoved = primitive._removeHeightCallbacks;
        var frameNumber = frameState.frameNumber;

        if (customDataAdded.length > 0 || customDataRemoved.length > 0) {
            for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
                tile = levelZeroTiles[i];
                tile._updateCustomData(frameNumber, customDataAdded, customDataRemoved);
            }

            customDataAdded.length = 0;
            customDataRemoved.length = 0;
        }

        // Our goal with load ordering is to first load all of the tiles we need to
        // render the current scene at full detail.  Loading any other tiles is just
        // a form of prefetching, and we need not do it at all (other concerns aside).  This
        // simple and obvious statement gets more complicated when we realize that, because
        // we don't have bounding volumes for the entire terrain tile pyramid, we don't
        // precisely know which tiles we need to render the scene at full detail, until we do
        // some loading.
        //
        // So our load priority is (from high to low):
        // 1. Tiles that we _would_ render, except that they're not sufficiently loaded yet.
        //    Ideally this would only include tiles that we've already determined to be visible,
        //    but since we don't have reliable visibility information until a tile is loaded,
        //    and because we (currently) must have all children in a quad renderable before we
        //    can refine, this pretty much means tiles we'd like to refine to, regardless of
        //    visibility. (high)
        // 2. Tiles that we're rendering. (medium)
        // 3. All other tiles. (low)
        //
        // Within each priority group, tiles should be loaded in approximate near-to-far order,
        // but currently they're just loaded in our traversal order which makes no guarantees
        // about depth ordering.

        // Traverse in depth-first, near-to-far order.
        for (i = 0, len = levelZeroTiles.length; i < len; ++i) {
            tile = levelZeroTiles[i];
            primitive._tileReplacementQueue.markTileRendered(tile);
            if (!tile.renderable) {
                if (tile.needsLoading) {
                    primitive._tileLoadQueueHigh.push(tile);
                }
                ++debug.tilesWaitingForChildren;
            } else if (tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) {
                visitTile(primitive, frameState, tile);
            } else {
                if (tile.needsLoading) {
                    primitive._tileLoadQueueLow.push(tile);
                }
                ++debug.tilesCulled;
            }
        }

        raiseTileLoadProgressEvent(primitive);
    }

    function visitTile(primitive, frameState, tile) {
        var debug = primitive._debug;

        ++debug.tilesVisited;

        primitive._tileReplacementQueue.markTileRendered(tile);
        tile._updateCustomData(frameState.frameNumber);

        if (tile.level > debug.maxDepth) {
            debug.maxDepth = tile.level;
        }

        if (screenSpaceError(primitive, frameState, tile) < primitive.maximumScreenSpaceError) {
            // This tile meets SSE requirements, so render it.
            if (tile.needsLoading) {
                // Rendered tile meeting SSE loads with medium priority.
                primitive._tileLoadQueueMedium.push(tile);
            }
            addTileToRenderList(primitive, tile);
            return;
        }

        var southwestChild = tile.southwestChild;
        var southeastChild = tile.southeastChild;
        var northwestChild = tile.northwestChild;
        var northeastChild = tile.northeastChild;
        var allAreRenderable = southwestChild.renderable && southeastChild.renderable &&
                               northwestChild.renderable && northeastChild.renderable;
        var allAreUpsampled = southwestChild.upsampledFromParent && southeastChild.upsampledFromParent &&
                              northwestChild.upsampledFromParent && northeastChild.upsampledFromParent;

        if (allAreRenderable) {
            if (allAreUpsampled) {
                // No point in rendering the children because they're all upsampled.  Render this tile instead.
                addTileToRenderList(primitive, tile);

                // Load the children even though we're (currently) not going to render them.
                // A tile that is "upsampled only" right now might change its tune once it does more loading.
                // A tile that is upsampled now and forever should also be done loading, so no harm done.
                queueChildLoadNearToFar(primitive, frameState.camera.positionCartographic, southwestChild, southeastChild, northwestChild, northeastChild);

                if (tile.needsLoading) {
                    // Rendered tile that's not waiting on children loads with medium priority.
                    primitive._tileLoadQueueMedium.push(tile);
                }
            } else {
                // SSE is not good enough and children are loaded, so refine.
                // No need to add the children to the load queue because they'll be added (if necessary) when they're visited.
                visitVisibleChildrenNearToFar(primitive, southwestChild, southeastChild, northwestChild, northeastChild, frameState);

                if (tile.needsLoading) {
                    // Tile is not rendered, so load it with low priority.
                    primitive._tileLoadQueueLow.push(tile);
                }
            }
        } else {
            // We'd like to refine but can't because not all of our children are renderable.  Load the refinement blockers with high priority and
            // render this tile in the meantime.
            queueChildLoadNearToFar(primitive, frameState.camera.positionCartographic, southwestChild, southeastChild, northwestChild, northeastChild);
            addTileToRenderList(primitive, tile);

            if (tile.needsLoading) {
                // We will refine this tile when it's possible, so load this tile only with low priority.
                primitive._tileLoadQueueLow.push(tile);
            }
        }
    }

    function queueChildLoadNearToFar(primitive, cameraPosition, southwest, southeast, northwest, northeast) {
        if (cameraPosition.longitude < southwest.east) {
            if (cameraPosition.latitude < southwest.north) {
                // Camera in southwest quadrant
                queueChildTileLoad(primitive, southwest);
                queueChildTileLoad(primitive, southeast);
                queueChildTileLoad(primitive, northwest);
                queueChildTileLoad(primitive, northeast);
            } else {
                // Camera in northwest quadrant
                queueChildTileLoad(primitive, northwest);
                queueChildTileLoad(primitive, southwest);
                queueChildTileLoad(primitive, northeast);
                queueChildTileLoad(primitive, southeast);
            }
        } else {
            if (cameraPosition.latitude < southwest.north) {
                // Camera southeast quadrant
                queueChildTileLoad(primitive, southeast);
                queueChildTileLoad(primitive, southwest);
                queueChildTileLoad(primitive, northeast);
                queueChildTileLoad(primitive, northwest);
            } else {
                // Camera in northeast quadrant
                queueChildTileLoad(primitive, northeast);
                queueChildTileLoad(primitive, northwest);
                queueChildTileLoad(primitive, southeast);
                queueChildTileLoad(primitive, southwest);
            }
        }
    }

    function queueChildTileLoad(primitive, childTile) {
        primitive._tileReplacementQueue.markTileRendered(childTile);
        if (childTile.needsLoading) {
            if (childTile.renderable) {
                primitive._tileLoadQueueLow.push(childTile);
            } else {
                // A tile blocking refine loads with high priority
                primitive._tileLoadQueueHigh.push(childTile);
            }
        }
    }

    function visitVisibleChildrenNearToFar(primitive, southwest, southeast, northwest, northeast, frameState) {
        var cameraPosition = frameState.camera.positionCartographic;
        var tileProvider = primitive._tileProvider;
        var occluders = primitive._occluders;

        if (cameraPosition.longitude < southwest.rectangle.east) {
            if (cameraPosition.latitude < southwest.rectangle.north) {
                // Camera in southwest quadrant
                visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
                visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
                visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
                visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
            } else {
                // Camera in northwest quadrant
                visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
                visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
                visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
                visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
            }
        } else {
            if (cameraPosition.latitude < southwest.rectangle.north) {
                // Camera southeast quadrant
                visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
                visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
                visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
                visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
            } else {
                // Camera in northeast quadrant
                visitIfVisible(primitive, northeast, tileProvider, frameState, occluders);
                visitIfVisible(primitive, northwest, tileProvider, frameState, occluders);
                visitIfVisible(primitive, southeast, tileProvider, frameState, occluders);
                visitIfVisible(primitive, southwest, tileProvider, frameState, occluders);
            }
        }
    }

    function visitIfVisible(primitive, tile, tileProvider, frameState, occluders) {
        if (tileProvider.computeTileVisibility(tile, frameState, occluders) !== Visibility.NONE) {
            visitTile(primitive, frameState, tile);
        } else {
            ++primitive._debug.tilesCulled;
            primitive._tileReplacementQueue.markTileRendered(tile);
        }
    }
    /**
     * Checks if the load queue length has changed since the last time we raised a queue change event - if so, raises
     * a new one.
     */
    function raiseTileLoadProgressEvent(primitive) {
        var currentLoadQueueLength = primitive._tileLoadQueueHigh.length + primitive._tileLoadQueueMedium.length + primitive._tileLoadQueueLow.length;

        if (currentLoadQueueLength !== primitive._lastTileLoadQueueLength) {
            primitive._tileLoadProgressEvent.raiseEvent(currentLoadQueueLength);
            primitive._lastTileLoadQueueLength = currentLoadQueueLength;
        }
    }

    function screenSpaceError(primitive, frameState, tile) {
        if (frameState.mode === SceneMode.SCENE2D) {
            return screenSpaceError2D(primitive, frameState, tile);
        }

        var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);

        var distance = tile._distance;
        var height = frameState.context.drawingBufferHeight;
        var sseDenominator = frameState.camera.frustum.sseDenominator;

        var error = (maxGeometricError * height) / (distance * sseDenominator);

        if (frameState.fog.enabled) {
            error = error - CesiumMath.fog(distance, frameState.fog.density) * frameState.fog.sse;
        }

        return error;
    }

    function screenSpaceError2D(primitive, frameState, tile) {
        var camera = frameState.camera;
        var frustum = camera.frustum;

        var context = frameState.context;
        var width = context.drawingBufferWidth;
        var height = context.drawingBufferHeight;

        var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level);
        var pixelSize = Math.max(frustum.top - frustum.bottom, frustum.right - frustum.left) / Math.max(width, height);
        return maxGeometricError / pixelSize;
    }

    function addTileToRenderList(primitive, tile) {
        primitive._tilesToRender.push(tile);
        ++primitive._debug.tilesRendered;
    }

    function processTileLoadQueue(primitive, frameState) {
        var tileLoadQueueHigh = primitive._tileLoadQueueHigh;
        var tileLoadQueueMedium = primitive._tileLoadQueueMedium;
        var tileLoadQueueLow = primitive._tileLoadQueueLow;
        var tileProvider = primitive._tileProvider;

        if (tileLoadQueueHigh.length === 0 && tileLoadQueueMedium.length === 0 && tileLoadQueueLow.length === 0) {
            return;
        }

        // Remove any tiles that were not used this frame beyond the number
        // we're allowed to keep.
        primitive._tileReplacementQueue.trimTiles(primitive.tileCacheSize);

        var endTime = getTimestamp() + primitive._loadQueueTimeSlice;

        processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, tileLoadQueueHigh);
        processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, tileLoadQueueMedium);
        processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, tileLoadQueueLow);
    }

    function processSinglePriorityLoadQueue(primitive, frameState, tileProvider, endTime, loadQueue) {
        for (var i = 0, len = loadQueue.length; i < len && getTimestamp() < endTime; ++i) {
            var tile = loadQueue[i];
            primitive._tileReplacementQueue.markTileRendered(tile);
            tileProvider.loadTile(frameState, tile);
        }
    }

    var scratchRay = new Ray();
    var scratchCartographic = new Cartographic();
    var scratchPosition = new Cartesian3();

    function updateHeights(primitive, frameState) {
        var tilesToUpdateHeights = primitive._tileToUpdateHeights;
        var terrainProvider = primitive._tileProvider.terrainProvider;

        var startTime = getTimestamp();
        var timeSlice = primitive._updateHeightsTimeSlice;
        var endTime = startTime + timeSlice;

        var mode = frameState.mode;
        var projection = frameState.mapProjection;
        var ellipsoid = projection.ellipsoid;

        while (tilesToUpdateHeights.length > 0) {
            var tile = tilesToUpdateHeights[0];
            var customData = tile.customData;
            var customDataLength = customData.length;

            var timeSliceMax = false;
            for (var i = primitive._lastTileIndex; i < customDataLength; ++i) {
                var data = customData[i];

                if (tile.level > data.level) {
                    if (!defined(data.positionOnEllipsoidSurface)) {
                        // cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
                        data.positionOnEllipsoidSurface = Cartesian3.fromRadians(data.positionCartographic.longitude, data.positionCartographic.latitude, 0.0, ellipsoid);
                    }

                    if (mode === SceneMode.SCENE3D) {
                        var surfaceNormal = ellipsoid.geodeticSurfaceNormal(data.positionOnEllipsoidSurface, scratchRay.direction);

                        // compute origin point

                        // Try to find the intersection point between the surface normal and z-axis.
                        // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
                        var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(data.positionOnEllipsoidSurface, 11500.0, scratchRay.origin);

                        // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
                        if (!defined(rayOrigin)) {
                            // intersection point is outside the ellipsoid, try other value
                            // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
                            var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0);

                            // multiply by the *positive* value of the magnitude
                            var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchPosition);
                            Cartesian3.subtract(data.positionOnEllipsoidSurface, vectorToMinimumPoint, scratchRay.origin);
                        }
                    } else {
                        Cartographic.clone(data.positionCartographic, scratchCartographic);

                        // minimum height for the terrain set, need to get this information from the terrain provider
                        scratchCartographic.height = -11500.0;
                        projection.project(scratchCartographic, scratchPosition);
                        Cartesian3.fromElements(scratchPosition.z, scratchPosition.x, scratchPosition.y, scratchPosition);
                        Cartesian3.clone(scratchPosition, scratchRay.origin);
                        Cartesian3.clone(Cartesian3.UNIT_X, scratchRay.direction);
                    }

                    var position = tile.data.pick(scratchRay, mode, projection, false, scratchPosition);
                    if (defined(position)) {
                        data.callback(position);
                    }

                    data.level = tile.level;
                } else if (tile.level === data.level) {
                    var children = tile.children;
                    var childrenLength = children.length;

                    var child;
                    for (var j = 0; j < childrenLength; ++j) {
                        child = children[j];
                        if (Rectangle.contains(child.rectangle, data.positionCartographic)) {
                            break;
                        }
                    }

                    var tileDataAvailable = terrainProvider.getTileDataAvailable(child.x, child.y, child.level);
                    var parentTile = tile.parent;
                    if ((defined(tileDataAvailable) && !tileDataAvailable) ||
                        (defined(parentTile) && defined(parentTile.data) && defined(parentTile.data.terrainData) &&
                         !parentTile.data.terrainData.isChildAvailable(parentTile.x, parentTile.y, child.x, child.y))) {
                        data.removeFunc();
                    }
                }

                if (getTimestamp() >= endTime) {
                    timeSliceMax = true;
                    break;
                }
            }

            if (timeSliceMax) {
                primitive._lastTileIndex = i;
                break;
            } else {
                primitive._lastTileIndex = 0;
                tilesToUpdateHeights.shift();
            }
        }
    }

    function createRenderCommandsForSelectedTiles(primitive, frameState) {
        var tileProvider = primitive._tileProvider;
        var tilesToRender = primitive._tilesToRender;
        var tilesToUpdateHeights = primitive._tileToUpdateHeights;

        for (var i = 0, len = tilesToRender.length; i < len; ++i) {
            var tile = tilesToRender[i];
            tileProvider.showTileThisFrame(tile, frameState);

            if (tile._frameRendered !== frameState.frameNumber - 1) {
                tilesToUpdateHeights.push(tile);
            }
            tile._frameRendered = frameState.frameNumber;
        }
    }

    return QuadtreePrimitive;
});

/*global define*/
define('Scene/Globe',[
        '../Core/BoundingSphere',
        '../Core/buildModuleUrl',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/EllipsoidTerrainProvider',
        '../Core/Event',
        '../Core/IntersectionTests',
        '../Core/loadImage',
        '../Core/Ray',
        '../Core/Rectangle',
        '../Renderer/ShaderSource',
        '../Renderer/Texture',
        '../Shaders/GlobeFS',
        '../Shaders/GlobeVS',
        '../Shaders/GroundAtmosphere',
        '../ThirdParty/when',
        './GlobeSurfaceShaderSet',
        './GlobeSurfaceTileProvider',
        './ImageryLayerCollection',
        './QuadtreePrimitive',
        './SceneMode',
        './ShadowMode'
    ], function(
        BoundingSphere,
        buildModuleUrl,
        Cartesian3,
        Cartographic,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Ellipsoid,
        EllipsoidTerrainProvider,
        Event,
        IntersectionTests,
        loadImage,
        Ray,
        Rectangle,
        ShaderSource,
        Texture,
        GlobeFS,
        GlobeVS,
        GroundAtmosphere,
        when,
        GlobeSurfaceShaderSet,
        GlobeSurfaceTileProvider,
        ImageryLayerCollection,
        QuadtreePrimitive,
        SceneMode,
        ShadowMode) {
    'use strict';

    /**
     * The globe rendered in the scene, including its terrain ({@link Globe#terrainProvider})
     * and imagery layers ({@link Globe#imageryLayers}).  Access the globe using {@link Scene#globe}.
     *
     * @alias Globe
     * @constructor
     *
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] Determines the size and shape of the
     * globe.
     */
    function Globe(ellipsoid) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
        var terrainProvider = new EllipsoidTerrainProvider({
            ellipsoid : ellipsoid
        });
        var imageryLayerCollection = new ImageryLayerCollection();

        this._ellipsoid = ellipsoid;
        this._imageryLayerCollection = imageryLayerCollection;

        this._surfaceShaderSet = new GlobeSurfaceShaderSet();

        this._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({
            sources : [GroundAtmosphere, GlobeVS]
        });

        this._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({
            sources : [GlobeFS]
        });

        this._surface = new QuadtreePrimitive({
            tileProvider : new GlobeSurfaceTileProvider({
                terrainProvider : terrainProvider,
                imageryLayers : imageryLayerCollection,
                surfaceShaderSet : this._surfaceShaderSet
            })
        });

        this._terrainProvider = terrainProvider;
        this._terrainProviderChanged = new Event();

        /**
         * Determines if the globe will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = true;

        /**
         * The normal map to use for rendering waves in the ocean.  Setting this property will
         * only have an effect if the configured terrain provider includes a water mask.
         *
         * @type {String}
         * @default buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg')
         */
        this.oceanNormalMapUrl = buildModuleUrl('Assets/Textures/waterNormalsSmall.jpg');
        this._oceanNormalMapUrl = undefined;

        /**
         * The maximum screen-space error used to drive level-of-detail refinement.  Higher
         * values will provide better performance but lower visual quality.
         *
         * @type {Number}
         * @default 2
         */
        this.maximumScreenSpaceError = 2;

        /**
         * The size of the terrain tile cache, expressed as a number of tiles.  Any additional
         * tiles beyond this number will be freed, as long as they aren't needed for rendering
         * this frame.  A larger number will consume more memory but will show detail faster
         * when, for example, zooming out and then back in.
         *
         * @type {Number}
         * @default 100
         */
        this.tileCacheSize = 100;

        /**
         * Enable lighting the globe with the sun as a light source.
         *
         * @type {Boolean}
         * @default false
         */
        this.enableLighting = false;

        /**
         * The distance where everything becomes lit. This only takes effect
         * when <code>enableLighting</code> is <code>true</code>.
         *
         * @type {Number}
         * @default 6500000.0
         */
        this.lightingFadeOutDistance = 6500000.0;

        /**
         * The distance where lighting resumes. This only takes effect
         * when <code>enableLighting</code> is <code>true</code>.
         *
         * @type {Number}
         * @default 9000000.0
         */
        this.lightingFadeInDistance = 9000000.0;

        /**
         * True if an animated wave effect should be shown in areas of the globe
         * covered by water; otherwise, false.  This property is ignored if the
         * <code>terrainProvider</code> does not provide a water mask.
         *
         * @type {Boolean}
         * @default true
         */
        this.showWaterEffect = true;

        /**
         * True if primitives such as billboards, polylines, labels, etc. should be depth-tested
         * against the terrain surface, or false if such primitives should always be drawn on top
         * of terrain unless they're on the opposite side of the globe.  The disadvantage of depth
         * testing primitives against terrain is that slight numerical noise or terrain level-of-detail
         * switched can sometimes make a primitive that should be on the surface disappear underneath it.
         *
         * @type {Boolean}
         * @default false
         *
         */
        this.depthTestAgainstTerrain = false;

        /**
         * Determines whether the globe casts or receives shadows from each light source. Setting the globe
         * to cast shadows may impact performance since the terrain is rendered again from the light's perspective.
         * Currently only terrain that is in view casts shadows. By default the globe does not cast shadows.
         *
         * @type {ShadowMode}
         * @default ShadowMode.RECEIVE_ONLY
         */
        this.shadows = ShadowMode.RECEIVE_ONLY;

        this._oceanNormalMap = undefined;
        this._zoomedOutOceanSpecularIntensity = 0.5;
    }

    defineProperties(Globe.prototype, {
        /**
         * Gets an ellipsoid describing the shape of this globe.
         * @memberof Globe.prototype
         * @type {Ellipsoid}
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        },
        /**
         * Gets the collection of image layers that will be rendered on this globe.
         * @memberof Globe.prototype
         * @type {ImageryLayerCollection}
         */
        imageryLayers : {
            get : function() {
                return this._imageryLayerCollection;
            }
        },
        /**
         * Gets or sets the color of the globe when no imagery is available.
         * @memberof Globe.prototype
         * @type {Color}
         */
        baseColor : {
            get : function() {
                return this._surface.tileProvider.baseColor;
            },
            set : function(value) {
                this._surface.tileProvider.baseColor = value;
            }
        },
        /**
         * The terrain provider providing surface geometry for this globe.
         * @type {TerrainProvider}
         *
         * @memberof Globe.prototype
         * @type {TerrainProvider}
         *
         */
        terrainProvider : {
            get : function() {
                return this._terrainProvider;
            },
            set : function(value) {
                if (value !== this._terrainProvider) {
                    this._terrainProvider = value;
                    this._terrainProviderChanged.raiseEvent(value);
                }
            }
        },
        /**
         * Gets an event that's raised when the terrain provider is changed
         *
         * @memberof Globe.prototype
         * @type {Event}
         * @readonly
         */
        terrainProviderChanged : {
            get: function() {
                return this._terrainProviderChanged;
            }
        },
        /**
         * Gets an event that's raised when the length of the tile load queue has changed since the last render frame.  When the load queue is empty,
         * all terrain and imagery for the current view have been loaded.  The event passes the new length of the tile load queue.
         *
         * @memberof Globe.prototype
         * @type {Event}
         */
        tileLoadProgressEvent : {
            get: function() {
                return this._surface.tileLoadProgressEvent;
            }
        }
    });

    function createComparePickTileFunction(rayOrigin) {
        return function(a, b) {
            var aDist = BoundingSphere.distanceSquaredTo(a.pickBoundingSphere, rayOrigin);
            var bDist = BoundingSphere.distanceSquaredTo(b.pickBoundingSphere, rayOrigin);

            return aDist - bDist;
        };
    }

    var scratchArray = [];
    var scratchSphereIntersectionResult = {
        start : 0.0,
        stop : 0.0
    };

    /**
     * Find an intersection between a ray and the globe surface that was rendered. The ray must be given in world coordinates.
     *
     * @param {Ray} ray The ray to test for intersection.
     * @param {Scene} scene The scene.
     * @param {Cartesian3} [result] The object onto which to store the result.
     * @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found.
     *
     * @example
     * // find intersection of ray through a pixel and the globe
     * var ray = viewer.camera.getPickRay(windowCoordinates);
     * var intersection = globe.pick(ray, scene);
     */
    Globe.prototype.pick = function(ray, scene, result) {
                if (!defined(ray)) {
            throw new DeveloperError('ray is required');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }
        
        var mode = scene.mode;
        var projection = scene.mapProjection;

        var sphereIntersections = scratchArray;
        sphereIntersections.length = 0;

        var tilesToRender = this._surface._tilesToRender;
        var length = tilesToRender.length;

        var tile;
        var i;

        for (i = 0; i < length; ++i) {
            tile = tilesToRender[i];
            var tileData = tile.data;

            if (!defined(tileData)) {
                continue;
            }

            var boundingVolume = tileData.pickBoundingSphere;
            if (mode !== SceneMode.SCENE3D) {
                BoundingSphere.fromRectangleWithHeights2D(tile.rectangle, projection, tileData.minimumHeight, tileData.maximumHeight, boundingVolume);
                Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);
            } else {
                BoundingSphere.clone(tileData.boundingSphere3D, boundingVolume);
            }

            var boundingSphereIntersection = IntersectionTests.raySphere(ray, boundingVolume, scratchSphereIntersectionResult);
            if (defined(boundingSphereIntersection)) {
                sphereIntersections.push(tileData);
            }
        }

        sphereIntersections.sort(createComparePickTileFunction(ray.origin));

        var intersection;
        length = sphereIntersections.length;
        for (i = 0; i < length; ++i) {
            intersection = sphereIntersections[i].pick(ray, scene.mode, scene.mapProjection, true, result);
            if (defined(intersection)) {
                break;
            }
        }

        return intersection;
    };

    var scratchGetHeightCartesian = new Cartesian3();
    var scratchGetHeightIntersection = new Cartesian3();
    var scratchGetHeightCartographic = new Cartographic();
    var scratchGetHeightRay = new Ray();

    function tileIfContainsCartographic(tile, cartographic) {
        return Rectangle.contains(tile.rectangle, cartographic) ? tile : undefined;
    }

    /**
     * Get the height of the surface at a given cartographic.
     *
     * @param {Cartographic} cartographic The cartographic for which to find the height.
     * @returns {Number|undefined} The height of the cartographic or undefined if it could not be found.
     */
    Globe.prototype.getHeight = function(cartographic) {
                if (!defined(cartographic)) {
            throw new DeveloperError('cartographic is required');
        }
        
        var levelZeroTiles = this._surface._levelZeroTiles;
        if (!defined(levelZeroTiles)) {
            return;
        }

        var tile;
        var i;

        var length = levelZeroTiles.length;
        for (i = 0; i < length; ++i) {
            tile = levelZeroTiles[i];
            if (Rectangle.contains(tile.rectangle, cartographic)) {
                break;
            }
        }

        if (!defined(tile) || !Rectangle.contains(tile.rectangle, cartographic)) {
            return undefined;
        }

        while (tile.renderable) {
            tile = tileIfContainsCartographic(tile.southwestChild, cartographic) ||
                   tileIfContainsCartographic(tile.southeastChild, cartographic) ||
                   tileIfContainsCartographic(tile.northwestChild, cartographic) ||
                   tile.northeastChild;
        }

        while (defined(tile) && (!defined(tile.data) || !defined(tile.data.pickTerrain))) {
            tile = tile.parent;
        }

        if (!defined(tile)) {
            return undefined;
        }

        var ellipsoid = this._surface._tileProvider.tilingScheme.ellipsoid;

        //cartesian has to be on the ellipsoid surface for `ellipsoid.geodeticSurfaceNormal`
        var cartesian = Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0.0, ellipsoid, scratchGetHeightCartesian);

        var ray = scratchGetHeightRay;
        var surfaceNormal = ellipsoid.geodeticSurfaceNormal(cartesian, ray.direction);

        // Try to find the intersection point between the surface normal and z-axis.
        // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
        var rayOrigin = ellipsoid.getSurfaceNormalIntersectionWithZAxis(cartesian, 11500.0, ray.origin);

        // Theoretically, not with Earth datums, the intersection point can be outside the ellipsoid
        if (!defined(rayOrigin)) {
            // intersection point is outside the ellipsoid, try other value
            // minimum height (-11500.0) for the terrain set, need to get this information from the terrain provider
            var magnitude = Math.min(defaultValue(tile.data.minimumHeight, 0.0),-11500.0);

            // multiply by the *positive* value of the magnitude
            var vectorToMinimumPoint = Cartesian3.multiplyByScalar(surfaceNormal, Math.abs(magnitude) + 1, scratchGetHeightIntersection);
            Cartesian3.subtract(cartesian, vectorToMinimumPoint, ray.origin);
        }

        var intersection = tile.data.pick(ray, undefined, undefined, false, scratchGetHeightIntersection);
        if (!defined(intersection)) {
            return undefined;
        }

        return ellipsoid.cartesianToCartographic(intersection, scratchGetHeightCartographic).height;
    };

    /**
     * @private
     */
    Globe.prototype.beginFrame = function(frameState) {
        if (!this.show) {
            return;
        }

        var surface = this._surface;
        var tileProvider = surface.tileProvider;
        var terrainProvider = this.terrainProvider;
        var hasWaterMask = this.showWaterEffect && terrainProvider.ready && terrainProvider.hasWaterMask;

        if (hasWaterMask && this.oceanNormalMapUrl !== this._oceanNormalMapUrl) {
            // url changed, load new normal map asynchronously
            var oceanNormalMapUrl = this.oceanNormalMapUrl;
            this._oceanNormalMapUrl = oceanNormalMapUrl;

            if (defined(oceanNormalMapUrl)) {
                var that = this;
                when(loadImage(oceanNormalMapUrl), function(image) {
                    if (oceanNormalMapUrl !== that.oceanNormalMapUrl) {
                        // url changed while we were loading
                        return;
                    }

                    that._oceanNormalMap = that._oceanNormalMap && that._oceanNormalMap.destroy();
                    that._oceanNormalMap = new Texture({
                        context : frameState.context,
                        source : image
                    });
                });
            } else {
                this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
            }
        }

        var mode = frameState.mode;
        var pass = frameState.passes;

        if (pass.render) {
            // Don't show the ocean specular highlights when zoomed out in 2D and Columbus View.
            if (mode === SceneMode.SCENE3D) {
                this._zoomedOutOceanSpecularIntensity = 0.5;
            } else {
                this._zoomedOutOceanSpecularIntensity = 0.0;
            }

            surface.maximumScreenSpaceError = this.maximumScreenSpaceError;
            surface.tileCacheSize = this.tileCacheSize;

            tileProvider.terrainProvider = this.terrainProvider;
            tileProvider.lightingFadeOutDistance = this.lightingFadeOutDistance;
            tileProvider.lightingFadeInDistance = this.lightingFadeInDistance;
            tileProvider.zoomedOutOceanSpecularIntensity = this._zoomedOutOceanSpecularIntensity;
            tileProvider.hasWaterMask = hasWaterMask;
            tileProvider.oceanNormalMap = this._oceanNormalMap;
            tileProvider.enableLighting = this.enableLighting;
            tileProvider.shadows = this.shadows;

            surface.beginFrame(frameState);
        }
    };

    /**
     * @private
     */
    Globe.prototype.update = function(frameState) {
        if (!this.show) {
            return;
        }

        var surface = this._surface;
        var pass = frameState.passes;

        if (pass.render) {
            surface.update(frameState);
        }

        if (pass.pick) {
            surface.update(frameState);
        }
    };

    /**
     * @private
     */
    Globe.prototype.endFrame = function(frameState) {
        if (!this.show) {
            return;
        }

        if (frameState.passes.render) {
            this._surface.endFrame(frameState);
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see Globe#destroy
     */
    Globe.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * globe = globe && globe.destroy();
     *
     * @see Globe#isDestroyed
     */
    Globe.prototype.destroy = function() {
        this._surfaceShaderSet = this._surfaceShaderSet && this._surfaceShaderSet.destroy();
        this._surface = this._surface && this._surface.destroy();
        this._oceanNormalMap = this._oceanNormalMap && this._oceanNormalMap.destroy();
        return destroyObject(this);
    };

    return Globe;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/PassThrough',[],function() {
    'use strict';
    return "uniform sampler2D u_texture;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main() \n\
{\n\
    gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n\
}\n\
";
});
/*global define*/
define('Scene/GlobeDepth',[
        '../Core/BoundingRectangle',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/PixelFormat',
        '../Renderer/ClearCommand',
        '../Renderer/Framebuffer',
        '../Renderer/PixelDatatype',
        '../Renderer/RenderState',
        '../Renderer/Texture',
        '../Shaders/PostProcessFilters/PassThrough'
    ], function(
        BoundingRectangle,
        Color,
        defined,
        destroyObject,
        PixelFormat,
        ClearCommand,
        Framebuffer,
        PixelDatatype,
        RenderState,
        Texture,
        PassThrough) {
    'use strict';

    /**
     * @private
     */
    function GlobeDepth() {
        this._colorTexture = undefined;
        this._depthStencilTexture = undefined;
        this._globeDepthTexture = undefined;

        this.framebuffer = undefined;
        this._copyDepthFramebuffer = undefined;

        this._clearColorCommand = undefined;
        this._copyColorCommand = undefined;
        this._copyDepthCommand = undefined;

        this._viewport = new BoundingRectangle();
        this._rs = undefined;

        this._debugGlobeDepthViewportCommand = undefined;
    }

    function executeDebugGlobeDepth(globeDepth, context, passState) {
        if (!defined(globeDepth._debugGlobeDepthViewportCommand)) {
            var fs =
                'uniform sampler2D u_texture;\n' +
                'varying vec2 v_textureCoordinates;\n' +
                'void main()\n' +
                '{\n' +
                '    float z_window = czm_unpackDepth(texture2D(u_texture, v_textureCoordinates));\n' +
                '    float n_range = czm_depthRange.near;\n' +
                '    float f_range = czm_depthRange.far;\n' +
                '    float z_ndc = (2.0 * z_window - n_range - f_range) / (f_range - n_range);\n' +
                '    float scale = pow(z_ndc * 0.5 + 0.5, 8.0);\n' +
                '    gl_FragColor = vec4(mix(vec3(0.0), vec3(1.0), scale), 1.0);\n' +
                '}\n';

            globeDepth._debugGlobeDepthViewportCommand = context.createViewportQuadCommand(fs, {
                uniformMap : {
                    u_texture : function() {
                        return globeDepth._globeDepthTexture;
                    }
                },
                owner : globeDepth
            });
        }

        globeDepth._debugGlobeDepthViewportCommand.execute(context, passState);
    }

    function destroyTextures(globeDepth) {
        globeDepth._colorTexture = globeDepth._colorTexture && !globeDepth._colorTexture.isDestroyed() && globeDepth._colorTexture.destroy();
        globeDepth._depthStencilTexture = globeDepth._depthStencilTexture && !globeDepth._depthStencilTexture.isDestroyed() && globeDepth._depthStencilTexture.destroy();
        globeDepth._globeDepthTexture = globeDepth._globeDepthTexture && !globeDepth._globeDepthTexture.isDestroyed() && globeDepth._globeDepthTexture.destroy();
    }

    function destroyFramebuffers(globeDepth) {
        globeDepth.framebuffer = globeDepth.framebuffer && !globeDepth.framebuffer.isDestroyed() && globeDepth.framebuffer.destroy();
        globeDepth._copyDepthFramebuffer = globeDepth._copyDepthFramebuffer && !globeDepth._copyDepthFramebuffer.isDestroyed() && globeDepth._copyDepthFramebuffer.destroy();
    }

    function createTextures(globeDepth, context, width, height) {
        globeDepth._colorTexture = new Texture({
            context : context,
            width : width,
            height : height,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE
        });

        globeDepth._depthStencilTexture = new Texture({
            context : context,
            width : width,
            height : height,
            pixelFormat : PixelFormat.DEPTH_STENCIL,
            pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8
        });

        globeDepth._globeDepthTexture = new Texture({
            context : context,
            width : width,
            height : height,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE
        });
    }

    function createFramebuffers(globeDepth, context, width, height) {
        globeDepth.framebuffer = new Framebuffer({
            context : context,
            colorTextures : [globeDepth._colorTexture],
            depthStencilTexture : globeDepth._depthStencilTexture,
            destroyAttachments : false
        });

        globeDepth._copyDepthFramebuffer = new Framebuffer({
            context : context,
            colorTextures : [globeDepth._globeDepthTexture],
            destroyAttachments : false
        });
    }

    function updateFramebuffers(globeDepth, context, width, height) {
        var colorTexture = globeDepth._colorTexture;
        var textureChanged = !defined(colorTexture) || colorTexture.width !== width || colorTexture.height !== height;
        if (!defined(globeDepth.framebuffer) || textureChanged) {
            destroyTextures(globeDepth);
            destroyFramebuffers(globeDepth);
            createTextures(globeDepth, context, width, height);
            createFramebuffers(globeDepth, context, width, height);
        }
    }

    function updateCopyCommands(globeDepth, context, width, height) {
        globeDepth._viewport.width = width;
        globeDepth._viewport.height = height;

        if (!defined(globeDepth._rs) || !BoundingRectangle.equals(globeDepth._viewport, globeDepth._rs.viewport)) {
            globeDepth._rs = RenderState.fromCache({
                viewport : globeDepth._viewport
            });
        }

        if (!defined(globeDepth._copyDepthCommand)) {
            var fs =
                'uniform sampler2D u_texture;\n' +
                'varying vec2 v_textureCoordinates;\n' +
                'void main()\n' +
                '{\n' +
                '    gl_FragColor = czm_packDepth(texture2D(u_texture, v_textureCoordinates).r);\n' +
                '}\n';
            globeDepth._copyDepthCommand = context.createViewportQuadCommand(fs, {
                uniformMap : {
                    u_texture : function() {
                        return globeDepth._depthStencilTexture;
                    }
                },
                owner : globeDepth
            });
        }

        globeDepth._copyDepthCommand.framebuffer = globeDepth._copyDepthFramebuffer;

        if (!defined(globeDepth._copyColorCommand)) {
            globeDepth._copyColorCommand = context.createViewportQuadCommand(PassThrough, {
                uniformMap : {
                    u_texture : function() {
                        return globeDepth._colorTexture;
                    }
                },
                owner : globeDepth
            });
        }

        globeDepth._copyDepthCommand.renderState = globeDepth._rs;
        globeDepth._copyColorCommand.renderState = globeDepth._rs;

        if (!defined(globeDepth._clearColorCommand)) {
            globeDepth._clearColorCommand = new ClearCommand({
                color : new Color(0.0, 0.0, 0.0, 0.0),
                stencil : 0.0,
                owner : globeDepth
            });
        }

        globeDepth._clearColorCommand.framebuffer = globeDepth.framebuffer;
    }

    GlobeDepth.prototype.executeDebugGlobeDepth = function(context, passState) {
        executeDebugGlobeDepth(this, context, passState);
    };

    GlobeDepth.prototype.update = function(context) {
        var width = context.drawingBufferWidth;
        var height = context.drawingBufferHeight;

        updateFramebuffers(this, context, width, height);
        updateCopyCommands(this, context, width, height);
        context.uniformState.globeDepthTexture = undefined;
    };

    GlobeDepth.prototype.executeCopyDepth = function(context, passState) {
        if (defined(this._copyDepthCommand)) {
            this._copyDepthCommand.execute(context, passState);
            context.uniformState.globeDepthTexture = this._globeDepthTexture;
        }
    };

    GlobeDepth.prototype.executeCopyColor = function(context, passState) {
        if (defined(this._copyColorCommand)) {
            this._copyColorCommand.execute(context, passState);
        }
    };

    GlobeDepth.prototype.clear = function(context, passState, clearColor) {
        var clear = this._clearColorCommand;
        if (defined(clear)) {
            Color.clone(clearColor, clear.color);
            clear.execute(context, passState);
        }
    };

    GlobeDepth.prototype.isDestroyed = function() {
        return false;
    };

    GlobeDepth.prototype.destroy = function() {
        destroyTextures(this);
        destroyFramebuffers(this);

        if (defined(this._copyColorCommand)) {
            this._copyColorCommand.shaderProgram = this._copyColorCommand.shaderProgram.destroy();
        }

        if (defined(this._copyDepthCommand)) {
            this._copyDepthCommand.shaderProgram = this._copyDepthCommand.shaderProgram.destroy();
        }

        var command = this._debugGlobeDepthViewportCommand;
        if (defined(command)) {
            command.shaderProgram = command.shaderProgram.destroy();
        }

        return destroyObject(this);
    };

    return GlobeDepth;
});

/*global define*/
define('Scene/GoogleEarthImageryProvider',[
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/GeographicTilingScheme',
        '../Core/loadText',
        '../Core/Rectangle',
        '../Core/RuntimeError',
        '../Core/TileProviderError',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/when',
        './ImageryProvider'
    ], function(
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        GeographicTilingScheme,
        loadText,
        Rectangle,
        RuntimeError,
        TileProviderError,
        WebMercatorTilingScheme,
        when,
        ImageryProvider) {
    'use strict';

    /**
     * Provides tiled imagery using the Google Earth Imagery API.
     *
     * Notes: This imagery provider does not work with the public Google Earth servers. It works with the
     *        Google Earth Enterprise Server.
     *
     *        By default the Google Earth Enterprise server does not set the
     *        {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} headers. You can either
     *        use a proxy server which adds these headers, or in the /opt/google/gehttpd/conf/gehttpd.conf
     *        and add the 'Header set Access-Control-Allow-Origin "*"' option to the '&lt;Directory /&gt;' and
     *        '&lt;Directory "/opt/google/gehttpd/htdocs"&gt;' directives.
     *
     * @alias GoogleEarthImageryProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The url of the Google Earth server hosting the imagery.
     * @param {Number} options.channel The channel (id) to be used when requesting data from the server.
     *        The channel number can be found by looking at the json file located at:
     *        earth.localdomain/default_map/query?request=Json&vars=geeServerDefs The /default_map path may
     *        differ depending on your Google Earth Enterprise server configuration. Look for the "id" that
     *        is associated with a "ImageryMaps" requestType. There may be more than one id available.
     *        Example:
     *        {
     *          layers: [
     *            {
     *              id: 1002,
     *              requestType: "ImageryMaps"
     *            },
     *            {
     *              id: 1007,
     *              requestType: "VectorMapsRaster"
     *            }
     *          ]
     *        }
     * @param {String} [options.path="/default_map"] The path of the Google Earth server hosting the imagery.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the Google Earth
     *        Enterprise server, or undefined if there is no limit.
     * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
     *        is invalid and should be discarded. To ensure that no tiles are discarded, construct and pass
     *        a {@link NeverTileDiscardPolicy} for this parameter.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {Proxy} [options.proxy] A proxy to use for requests. This object is
     *        expected to have a getURL function which returns the proxied URL, if needed.
     *
     * @exception {RuntimeError} Could not find layer with channel (id) of <code>options.channel</code>.
     * @exception {RuntimeError} Could not find a version in channel (id) <code>options.channel</code>.
     * @exception {RuntimeError} Unsupported projection <code>data.projection</code>.
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     *
     *
     * @example
     * var google = new Cesium.GoogleEarthImageryProvider({
     *     url : 'https://earth.localdomain',
     *     channel : 1008
     * });
     *
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     */
    function GoogleEarthImageryProvider(options) {
        options = defaultValue(options, {});

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        if (!defined(options.channel)) {
            throw new DeveloperError('options.channel is required.');
        }
        
        this._url = options.url;
        this._path = defaultValue(options.path, '/default_map');
        this._tileDiscardPolicy = options.tileDiscardPolicy;
        this._proxy = options.proxy;
        this._channel = options.channel;
        this._requestType = 'ImageryMaps';
        this._credit = new Credit('Google Imagery', GoogleEarthImageryProvider._logoData, 'http://www.google.com/enterprise/mapsearth/products/earthenterprise.html');

        /**
         * The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider.
         * By default, this is set to 1.9.  Changing this value after creating an {@link ImageryLayer} for this provider will have
         * no effect.  Instead, set the layer's {@link ImageryLayer#gamma} property.
         *
         * @type {Number}
         * @default 1.9
         */
        this.defaultGamma = 1.9;

        this._tilingScheme = undefined;

        this._version = undefined;


        this._tileWidth = 256;
        this._tileHeight = 256;
        this._maximumLevel = options.maximumLevel;
        this._imageUrlTemplate = this._url + this._path + '/query?request={request}&channel={channel}&version={version}&x={x}&y={y}&z={zoom}';

        this._errorEvent = new Event();

        this._ready = false;
        this._readyPromise = when.defer();

        var metadataUrl = this._url + this._path + '/query?request=Json&vars=geeServerDefs&is2d=t';
        var that = this;
        var metadataError;

        function metadataSuccess(text) {
            var data;

            // The Google Earth server sends malformed JSON data currently...
            try {
                // First, try parsing it like normal in case a future version sends correctly formatted JSON
                data = JSON.parse(text);
            } catch(e) {
                // Quote object strings manually, then try parsing again
                data = JSON.parse(text.replace(/([\[\{,])[\n\r ]*([A-Za-z0-9]+)[\n\r ]*:/g, '$1"$2":'));
            }

            var layer;
            for(var i = 0; i < data.layers.length; i++) {
              if(data.layers[i].id === that._channel) {
                layer = data.layers[i];
                break;
              }
            }

            var message;

            if(!defined(layer)) {
              message = 'Could not find layer with channel (id) of ' + that._channel + '.';
              metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
              throw new RuntimeError(message);
            }

            if(!defined(layer.version)) {
              message = 'Could not find a version in channel (id) ' + that._channel + '.';
              metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
              throw new RuntimeError(message);
            }
            that._version = layer.version;

            if(defined(data.projection) && data.projection === 'flat') {
              that._tilingScheme = new GeographicTilingScheme({
                  numberOfLevelZeroTilesX : 2,
                  numberOfLevelZeroTilesY : 2,
                  rectangle: new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI),
                  ellipsoid : options.ellipsoid
              });
            // Default to mercator projection when projection is undefined
            } else if(!defined(data.projection) || data.projection === 'mercator') {
              that._tilingScheme = new WebMercatorTilingScheme({
                  numberOfLevelZeroTilesX : 2,
                  numberOfLevelZeroTilesY : 2,
                  ellipsoid : options.ellipsoid
              });
            } else {
              message = 'Unsupported projection ' + data.projection + '.';
              metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
              throw new RuntimeError(message);
            }

            that._imageUrlTemplate = that._imageUrlTemplate.replace('{request}', that._requestType)
              .replace('{channel}', that._channel).replace('{version}', that._version);

            that._ready = true;
            that._readyPromise.resolve(true);
            TileProviderError.handleSuccess(metadataError);
        }

        function metadataFailure(e) {
            var message = 'An error occurred while accessing ' + metadataUrl + '.';
            metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
            that._readyPromise.reject(new RuntimeError(message));
        }

        function requestMetadata() {
          var url = (!defined(that._proxy)) ? metadataUrl : that._proxy.getURL(metadataUrl);

          var metadata = loadText(url);
          when(metadata, metadataSuccess, metadataFailure);
        }

        requestMetadata();
    }

    defineProperties(GoogleEarthImageryProvider.prototype, {
        /**
         * Gets the URL of the Google Earth MapServer.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the url path of the data on the Google Earth server.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        path : {
            get : function() {
                return this._path;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._proxy;
            }
        },

        /**
         * Gets the imagery channel (id) currently being used.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        channel : {
            get : function() {
                return this._channel;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
                }
                
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight: {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
                }
                
                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
                }
                
                return this._maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
                }
                
                return 0;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
                }
                
                return this._tilingScheme;
            }
        },

        /**
         * Gets the version of the data used by this provider.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        version : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('version must not be called before the imagery provider is ready.');
                }
                
                return this._version;
            }
        },

        /**
         * Gets the type of data that is being requested from the provider.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        requestType : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('requestType must not be called before the imagery provider is ready.');
                }
                
                return this._requestType;
            }
        },
        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
                }
                
                return this._tilingScheme.rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
                }
                
                return this._tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link GoogleEarthImageryProvider#ready} returns true.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof GoogleEarthImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return true;
            }
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    GoogleEarthImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    GoogleEarthImageryProvider.prototype.requestImage = function(x, y, level) {
                if (!this._ready) {
            throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
        }
        
        var url = buildImageUrl(this, x, y, level);
        return ImageryProvider.loadImage(this, url);
    };

    /**
     * Picking features is not currently supported by this imagery provider, so this function simply returns
     * undefined.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    GoogleEarthImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return undefined;
    };

    GoogleEarthImageryProvider._logoData = 'data:image/png;base64, ';

    function buildImageUrl(imageryProvider, x, y, level) {
        var imageUrl = imageryProvider._imageUrlTemplate;

        imageUrl = imageUrl.replace('{x}', x);
        imageUrl = imageUrl.replace('{y}', y);
        // Google Earth starts with a zoom level of 1, not 0
        imageUrl = imageUrl.replace('{zoom}', (level + 1));

        var proxy = imageryProvider._proxy;
        if (defined(proxy)) {
            imageUrl = proxy.getURL(imageUrl);
        }

        return imageUrl;
    }

    return GoogleEarthImageryProvider;
});


/*global define*/
define('Scene/GridImageryProvider',[
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        '../Core/GeographicTilingScheme',
        '../ThirdParty/when'
    ], function(
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        GeographicTilingScheme,
        when) {
    'use strict';

    var defaultColor = new Color(1.0, 1.0, 1.0, 0.4);
    var defaultGlowColor = new Color(0.0, 1.0, 0.0, 0.05);
    var defaultBackgroundColor = new Color(0.0, 0.5, 0.0, 0.2);

    /**
     * An {@link ImageryProvider} that draws a wireframe grid on every tile with controllable background and glow.
     * May be useful for custom rendering effects or debugging terrain.
     *
     * @alias GridImageryProvider
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme for which to draw tiles.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     *                    this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     *                    parameter is specified, the WGS84 ellipsoid is used.
     * @param {Number} [options.cells=8] The number of grids cells.
     * @param {Color} [options.color=Color(1.0, 1.0, 1.0, 0.4)] The color to draw grid lines.
     * @param {Color} [options.glowColor=Color(0.0, 1.0, 0.0, 0.05)] The color to draw glow for grid lines.
     * @param {Number} [options.glowWidth=6] The width of lines used for rendering the line glow effect.
     * @param {Color} [options.backgroundColor=Color(0.0, 0.5, 0.0, 0.2)] Background fill color.
     * @param {Number} [options.tileWidth=256] The width of the tile for level-of-detail selection purposes.
     * @param {Number} [options.tileHeight=256] The height of the tile for level-of-detail selection purposes.
     * @param {Number} [options.canvasSize=256] The size of the canvas used for rendering.
     */
    function GridImageryProvider(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
        this._cells = defaultValue(options.cells, 8);
        this._color = defaultValue(options.color, defaultColor);
        this._glowColor = defaultValue(options.glowColor, defaultGlowColor);
        this._glowWidth = defaultValue(options.glowWidth, 6);
        this._backgroundColor = defaultValue(options.backgroundColor, defaultBackgroundColor);
        this._errorEvent = new Event();

        this._tileWidth = defaultValue(options.tileWidth, 256);
        this._tileHeight = defaultValue(options.tileHeight, 256);

        // A little larger than tile size so lines are sharper
        // Note: can't be too much difference otherwise texture blowout
        this._canvasSize = defaultValue(options.canvasSize, 256);

        // We only need a single canvas since all tiles will be the same
        this._canvas = this._createGridCanvas();

        this._readyPromise = when.resolve(true);
    }

    defineProperties(GridImageryProvider.prototype, {
        /**
         * Gets the proxy used by this provider.
         * @memberof GridImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight : {
            get : function() {
                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                return this._tilingScheme.rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof GridImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof GridImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return true;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof GridImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link GridImageryProvider#ready} returns true.
         * @memberof GridImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof GridImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return true;
            }
        }
    });

    /**
     * Draws a grid of lines into a canvas.
     */
    GridImageryProvider.prototype._drawGrid = function(context) {
        var minPixel = 0;
        var maxPixel = this._canvasSize;
        for (var x = 0; x <= this._cells; ++x) {
            var nx = x / this._cells;
            var val = 1 + nx * (maxPixel - 1);

            context.moveTo(val, minPixel);
            context.lineTo(val, maxPixel);
            context.moveTo(minPixel, val);
            context.lineTo(maxPixel, val);
        }
        context.stroke();
    };

    /**
     * Render a grid into a canvas with background and glow
     */
    GridImageryProvider.prototype._createGridCanvas = function() {
        var canvas = document.createElement('canvas');
        canvas.width = this._canvasSize;
        canvas.height = this._canvasSize;
        var minPixel = 0;
        var maxPixel = this._canvasSize;

        var context = canvas.getContext('2d');

        // Fill the background
        var cssBackgroundColor = this._backgroundColor.toCssColorString();
        context.fillStyle = cssBackgroundColor;
        context.fillRect(minPixel, minPixel, maxPixel, maxPixel);

        // Glow for grid lines
        var cssGlowColor = this._glowColor.toCssColorString();
        context.strokeStyle = cssGlowColor;
        // Wide
        context.lineWidth = this._glowWidth;
        context.strokeRect(minPixel, minPixel, maxPixel, maxPixel);
        this._drawGrid(context);
        // Narrow
        context.lineWidth = this._glowWidth * 0.5;
        context.strokeRect(minPixel, minPixel, maxPixel, maxPixel);
        this._drawGrid(context);

        // Grid lines
        var cssColor = this._color.toCssColorString();
        // Border
        context.strokeStyle = cssColor;
        context.lineWidth = 2;
        context.strokeRect(minPixel, minPixel, maxPixel, maxPixel);
        // Inner
        context.lineWidth = 1;
        this._drawGrid(context);

        return canvas;
    };

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    GridImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link GridImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     */
    GridImageryProvider.prototype.requestImage = function(x, y, level) {
        return this._canvas;
    };

    /**
     * Picking features is not currently supported by this imagery provider, so this function simply returns
     * undefined.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    GridImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return undefined;
    };

    return GridImageryProvider;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ImageDrapingFS',[],function() {
    'use strict';
    return "varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec2 v_st;\n\
\n\
\n\
vec4 windowToEye(vec4 fragCoord)\n\
{\n\
  vec2 uv = fragCoord.xy / czm_viewport.zw;\n\
  float z_window = czm_unpackDepth(texture2D(czm_globeDepthTexture, uv));\n\
  if (z_window == 1.0)\n\
    discard;\n\
  \n\
  float near = czm_depthRange.near;\n\
  float far = czm_depthRange.far;\n\
  \n\
  vec3 ndcPos;\n\
  ndcPos.x = 2.0 * (fragCoord.x - czm_viewport.x) / czm_viewport.z - 1.0;\n\
  ndcPos.y = 2.0 * (fragCoord.y - czm_viewport.y) / czm_viewport.w - 1.0;\n\
  ndcPos.z = (2.0 * z_window - near - far) / (far - near);\n\
    \n\
  vec4 clipPos;\n\
  clipPos.w = czm_projection[3][2] / (ndcPos.z - (czm_projection[2][2] / czm_projection[2][3]));\n\
  clipPos.xyz = ndcPos * clipPos.w;\n\
  \n\
  return czm_inverseProjection * clipPos;\n\
  //return vec4(ndcPos, 1.0);\n\
}\n\
\n\
\n\
// Camera model and frames are based on OpenCV conventions:\n\
// http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html\n\
// we pass in a normalized intrinsic matrix as uniform so we can compute the normalized texture coordinates directly\n\
void main()\n\
{\n\
    vec3 positionToEyeEC = -v_positionEC; \n\
        \n\
    // get fragment 3D pos in eye coordinates using depth buffer value at fragment location\n\
    vec4 v_posEC = windowToEye(gl_FragCoord);\n\
    \n\
    // translate to video cam frame\n\
    vec4 camPosEC = czm_modelViewRelativeToEye * czm_translateRelativeToEye(camPosHigh_1, camPosLow_2);    \n\
    vec4 v_posCam = v_posEC - camPosEC;\n\
    \n\
    // rotate to video cam frame\n\
    vec3 lookRay = camAtt_3*czm_inverseViewRotation3D*v_posCam.xyz;\n\
    \n\
    // discard if behind camera\n\
    if (lookRay.z < 0.1)\n\
        discard;\n\
    \n\
    // undistort\n\
    float xn = lookRay.x / lookRay.z;\n\
    float yn = lookRay.y / lookRay.z;\n\
    float k1 = camDistR_5[0];\n\
    float k2 = camDistR_5[1];\n\
    float k3 = camDistR_5[2];\n\
    float p1 = camDistT_6[0];\n\
    float p2 = camDistT_6[1];\n\
    float r2 = xn*xn+yn*yn;\n\
    float r4 = r2*r2;\n\
    float r6 = r4*r2;\n\
    float xd = xn*(1. + k1*r2 + k2*r4 + k3*r6) + 2.*p1*xn*yn + p2*(r2 + 2.*xn*xn);\n\
    float yd = yn*(1. + k1*r2 + k2*r4 + k3*r6) + 2.*p2*xn*yn + p1*(r2 + 2.*yn*yn);\n\
    \n\
    // project with pinhole model\n\
    vec3 st = camProj_4 * vec3(xd, yd, 1.);\n\
    st.y = 1.0 - st.y;    \n\
    if (st.x < 0.0 || st.x > 1.0 || st.y < 0.0 || st.y > 1.0)\n\
        discard;\n\
\n\
    // get color from material \n\
    czm_materialInput materialInput;\n\
    materialInput.positionToEyeEC = positionToEyeEC;\n\
    materialInput.st = vec2(st.x, st.y);\n\
    czm_material material = czm_getMaterial(materialInput);    \n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
\n\
    //float depth = pow(v_posEC.z * 0.5 + 0.5, 8.0);\n\
    //gl_FragColor = vec4(depth, depth, depth, 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ImageDrapingVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 normal;\n\
attribute vec2 st;\n\
attribute float batchId;\n\
\n\
varying vec3 v_positionEC;\n\
varying vec3 v_normalEC;\n\
varying vec2 v_st;\n\
\n\
void main() \n\
{\n\
    vec4 p = czm_computePosition();\n\
\n\
    v_positionEC = (czm_modelViewRelativeToEye * p).xyz;      // position in eye coordinates\n\
    v_normalEC = czm_normal * normal;                         // normal in eye coordinates\n\
    v_st = st;\n\
    \n\
    gl_Position = czm_modelViewProjectionRelativeToEye * p;\n\
}\n\
";
});
/*global define*/
define('Scene/ImageDrapingPrimitive',[
        '../Core/BoundingSphere',
        '../Core/buildModuleUrl',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/combine',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EncodedCartesian3',
        '../Core/Ellipsoid',
        '../Core/GeographicTilingScheme',
        '../Core/GeometryInstance',
        '../Core/IntersectionTests',
        '../Core/isArray',
        '../Core/loadJson',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/OrientedBoundingBox',
        '../Core/PolygonGeometry',
        '../Core/Quaternion',
        '../Core/Ray',
        '../Core/Rectangle',
        '../Renderer/DrawCommand',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Shaders/ImageDrapingFS',
        '../Shaders/ImageDrapingVS',
        '../Shaders/ShadowVolumeFS',
        '../Shaders/ShadowVolumeVS',
        '../Scene/Material',
        '../Scene/MaterialAppearance',
        '../ThirdParty/when',
        './BlendingState',
        './DepthFunction',
        './PerInstanceColorAppearance',
        './Primitive',
        './SceneMode',
        './StencilFunction',
        './StencilOperation'
    ], function(
        BoundingSphere,
        buildModuleUrl,
        Cartesian2,
        Cartesian3,
        Cartographic,
        combine,
        ColorGeometryInstanceAttribute,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EncodedCartesian3,
        Ellipsoid,
        GeographicTilingScheme,
        GeometryInstance,
        IntersectionTests,
        isArray,
        loadJson,
        CesiumMath,
        Matrix3,
        Matrix4,
        OrientedBoundingBox,
        PolygonGeometry,
        Quaternion,
        Ray,
        Rectangle,
        DrawCommand,
        Pass,
        RenderState,
        ShaderProgram,
        ShaderSource,
        ImageDrapingFS,
        ImageDrapingVS,
        ShadowVolumeFS,
        ShadowVolumeVS,
        Material,
        MaterialAppearance,
        when,
        BlendingState,
        DepthFunction,
        PerInstanceColorAppearance,
        Primitive,
        SceneMode,
        StencilFunction,
        StencilOperation) {
    'use strict';

    /**
     * An image draping primitive represents an image or video as viewed from a camera and draped over the globe primitive (ellipsoid or terrain) in the {@link Scene}.
     * <p>
     * This is a specialized primitive with a hard coded appearance. The texture is projected on terrain by computing texture coordinates directly in the fragment
     * shader w/o relying on a ray terrain-intersection computation. For this to work, the globe depth texture must be available by setting {@link Scene#scene.copyGlobeDepth}
     * and {@link Scene#_environmentState.useGlobeDepthFramebuffer} to <code>true</code>;
     * </p>
     *
     * @alias ImageDrapingPrimitive
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.imageSrc] Path to image, or canvas or video element.
     * @param {Cartesian3} [options.camPos] Position of camera in the globe coordinate system.
     * @param {Matrix3} [options.camRot] Rotation matrix giving orientation of camera in the globe coordinate system (this matrix is used to transform coordinates from Earth's fixed frame to camera frame).
     * @param {Matrix3} [options.camProj] Camera projection matrix (pinhole model intrisic parameters).
     * @param {Array<Double>} [options.camDist] Camera distortion coefficients.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     * @param {Boolean} [options.vertexCacheOptimize=false] When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
     * @param {Boolean} [options.interleave=false] When <code>true</code>, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time.
     * @param {Boolean} [options.compressVertices=true] When <code>true</code>, the geometry vertices are compressed, which will save memory.
     * @param {Boolean} [options.releaseGeometryInstances=true] When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
     * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
     * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. If false initializeTerrainHeights() must be called first.
     * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown.
     * @param {Boolean} [options.debugShowShadowVolume=false] For debugging only. Determines if the shadow volume for each geometry in the primitive is drawn. Must be <code>true</code> on
     *                  creation for the volumes to be created before the geometry is released or options.releaseGeometryInstance must be <code>false</code>.
     *
     * @example
     * scene.primitives.add(new Cesium.ImageDrapingPrimitive({
     *   imageSrc : "mypicture.png"
     *   camAtt :
     * }));
     *
     * @see Primitive
     */
    function ImageDrapingPrimitive(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        options.asynchronous = true;

        /**
         * Image and camera parameters
         */
        this._imageSrc = options.imageSrc;
        this._camPos = options.camPos;
        this._camRot = defaultValue(options.camRot, new Quaternion());
        this._camProj = defaultValue(options.camProj, new Matrix3());
        this._camDistR = defaultValue(options.camDistR, [0.0, 0.0, 0.0]);
        this._camDistT = defaultValue(options.camDistT, [0.0, 0.0]);

        /**
         * Determines if the primitive will be shown.  This affects all geometry
         * instances in the primitive.
         *
         * @type {Boolean}
         *
         * @default true
         */
        this.show = defaultValue(options.show, true);
        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the bounding sphere for each draw command in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);

        /**
         * This property is for debugging only; it is not for production use nor is it optimized.
         * <p>
         * Draws the shadow volume for each geometry in the primitive.
         * </p>
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.debugShowShadowVolume = defaultValue(options.debugShowShadowVolume, false);
        this._debugShowShadowVolume = false;

        this._sp = undefined;
        this._spPick = undefined;

        this._rsStencilPreloadPass = undefined;
        this._rsStencilDepthPass = undefined;
        this._rsColorPass = undefined;
        this._rsPickPass = undefined;

        this._uniformMap = {
            u_globeMinimumAltitude: function() {
                return 55000.0;
            }
        };

        this._boundingVolumes = [];
        this._boundingVolumes2D = [];

        this._ready = false;
        this._readyPromise = when.defer();

        this._primitive = undefined;

        this._maxHeight = undefined;
        this._minHeight = undefined;

        this._maxTerrainHeight = ImageDrapingPrimitive._defaultMaxTerrainHeight;
        this._minTerrainHeight = ImageDrapingPrimitive._defaultMinTerrainHeight;

        this._boundingSpheresKeys = [];
        this._boundingSpheres = [];

        // generate geometry instance from video footprint
        // compute ground footprint on ellipsoid
        var coords = [];
        var lookDir = new Cartesian3();
        var invCamProj = Matrix3.inverse(this._camProj, new Matrix3());

        for (var i = 0; i < 4; i++) {

            var corner;
            if (i === 0) {
                //corner = new Cartesian3(0.0, 0.0, 1.0);
                corner = new Cartesian3(-0.5, -0.5, 1.0);
            }
            else if (i === 1) {
                //corner = new Cartesian3(1.0, 0.0, 1.0);
                corner = new Cartesian3(1.5, -0.5, 1.0);
            }
            else if (i === 2) {
                //corner = new Cartesian3(1.0, 1.0, 1.0);
                corner = new Cartesian3(1.5, 1.5, 1.0);
            }
            else if (i === 3) {
                //corner = new Cartesian3(0.0, 1.0, 1.0);
                corner = new Cartesian3(-0.5, 1.5, 1.0);
            }

            // transform normalized coordinates to look direction in camera frame
            Matrix3.multiplyByVector(invCamProj, corner, lookDir);

            // transform look dir to globe frame
            Matrix3.multiplyByVector(this._camRot, lookDir, lookDir);

            // intersect ray
            var ground;
            var ray = new Ray(this._camPos, lookDir);
            var intersects = IntersectionTests.rayEllipsoid(ray, Ellipsoid.WGS84);
            if (intersects == null) {
               //ground = IntersectionTests.grazingAltitudeLocation(ray, Cesium.Ellipsoid.WGS84);
               ground = Ray.getPoint(ray, 1000.0);
            }
            else {
               ground = Ray.getPoint(ray, intersects.start);
            }
            coords[i] = ground;
        }

        var geometry = new PolygonGeometry({
            polygonHierarchy : {
              positions : coords
            }
        });

        this.geometryInstances = new GeometryInstance({
            geometry: geometry,
            /*id : 'rectangle',
            attributes : {
              color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
            }*/
        });

        // create texture appearance w/ special shader
        /*var encCamPos = EncodedCartesian3.fromCartesian(this._camPos);
        var appearance = new MaterialAppearance({
            material : new Material({
                fabric : {
                    type : 'Image',
                    uniforms : {
                        image : this._imageSrc,
                        camPosHigh : encCamPos.high,
                        camPosLow : encCamPos.low,
                        camAtt: Matrix3.toArray(Matrix3.transpose(this._camRot, new Matrix3())),
                        camProj: Matrix3.toArray(this._camProj),
                        camDistR: this._camDistR,
                        camDistT: this._camDistT
                    }
                }
            }),
            vertexShaderSource: ImageDrapingVS,
            fragmentShaderSource: ImageDrapingFS
        });*/

        /*var appearance = new MaterialAppearance({
            material : new Material({
                fabric : {
                    type : 'Image',
                    uniforms : {
                        image : this._imageSrc,
                        camPosHigh : encCamPos.high,
                        camPosLow : encCamPos.low,
                        camAtt: Matrix3.toArray(Matrix3.transpose(this._camRot, new Matrix3())),
                        camProj: Matrix3.toArray(this._camProj),
                        camDistR: this._camDistR,
                        camDistT: this._camDistT
                    }
                }
            }),
            vertexShaderSource: ImageDrapingVS,
            fragmentShaderSource: ImageDrapingFS
        });*/

        // debug appearance
        var appearance = new MaterialAppearance({
            material : new Material({
                fabric : {
                    type: 'Color',
                    uniforms : {
                        color : new Cesium.Color(1.0, 0.0, 0.0, 0.5)
                    }
                }
            })
        });

        /*var appearance = new PerInstanceColorAppearance({
            flat : true
        });*/

        var readOnlyAttributes;
        if (defined(this.geometryInstances) && isArray(this.geometryInstances) && this.geometryInstances.length > 1) {
            readOnlyAttributes = readOnlyInstanceAttributesScratch;
        }

        this._primitiveOptions = {
            geometryInstances : undefined,
            appearance : appearance,
            vertexCacheOptimize : defaultValue(options.vertexCacheOptimize, false),
            interleave : defaultValue(options.interleave, false),
            releaseGeometryInstances : defaultValue(options.releaseGeometryInstances, true),
            allowPicking : defaultValue(options.allowPicking, true),
            asynchronous : defaultValue(options.asynchronous, true),
            compressVertices : defaultValue(options.compressVertices, true),
            _readOnlyInstanceAttributes : readOnlyAttributes,
            _createRenderStatesFunction : undefined,
            _createShaderProgramFunction : undefined,
            _createCommandsFunction : undefined,
            _createPickOffsets : true
        };
    }

    var readOnlyInstanceAttributesScratch = ['color'];

    defineProperties(ImageDrapingPrimitive.prototype, {
        /**
         * When <code>true</code>, geometry vertices are optimized for the pre and post-vertex-shader caches.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        vertexCacheOptimize : {
            get : function() {
                return this._primitiveOptions.vertexCacheOptimize;
            }
        },

        /**
         * Determines if geometry vertex attributes are interleaved, which can slightly improve rendering performance.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        interleave : {
            get : function() {
                return this._primitiveOptions.interleave;
            }
        },

        /**
         * When <code>true</code>, the primitive does not keep a reference to the input <code>geometryInstances</code> to save memory.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        releaseGeometryInstances : {
            get : function() {
                return this._primitiveOptions.releaseGeometryInstances;
            }
        },

        /**
         * When <code>true</code>, each geometry instance will only be pickable with {@link Scene#pick}.  When <code>false</code>, GPU memory is saved.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        allowPicking : {
            get : function() {
                return this._primitiveOptions.allowPicking;
            }
        },

        /**
         * Determines if the geometry instances will be created and batched on a web worker.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        asynchronous : {
            get : function() {
                return this._primitiveOptions.asynchronous;
            }
        },

        /**
         * When <code>true</code>, geometry vertices are compressed, which will save memory.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default true
         */
        compressVertices : {
            get : function() {
                return this._primitiveOptions.compressVertices;
            }
        },

        /**
         * Determines if the primitive is complete and ready to render.  If this property is
         * true, the primitive will be rendered the next time that {@link ImageDrapingPrimitive#update}
         * is called.
         *
         * @memberof ImageDrapingPrimitive.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves when the primitive is ready to render.
         * @memberof ImageDrapingPrimitive.prototype
         * @type {Promise.<ImageDrapingPrimitive>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        }
    });

    /**
     * Determines if ImageDrapingPrimitive rendering is supported.
     *
     * @param {Scene} scene The scene.
     * @returns {Boolean} <code>true</code> if ImageDrapingPrimitives are supported; otherwise, returns <code>false</code>
     */
    ImageDrapingPrimitive.isSupported = function(scene) {
        return scene.context.stencilBuffer;
    };

    ImageDrapingPrimitive._defaultMaxTerrainHeight = 9000.0;
    ImageDrapingPrimitive._defaultMinTerrainHeight = -100000.0;

    ImageDrapingPrimitive._terrainHeights = undefined;
    ImageDrapingPrimitive._terrainHeightsMaxLevel = 6;

    function getComputeMaximumHeightFunction(primitive) {
        return function(granularity, ellipsoid) {
            var r = ellipsoid.maximumRadius;
            var delta = (r / Math.cos(granularity * 0.5)) - r;
            return primitive._maxHeight + delta;
        };
    }

    function getComputeMinimumHeightFunction(primitive) {
        return function(granularity, ellipsoid) {
            return primitive._minHeight;
        };
    }

    function getStencilPreloadRenderState(enableStencil) {
        return {
            colorMask : {
                red : false,
                green : false,
                blue : false,
                alpha : false
            },
            stencilTest : {
                enabled : enableStencil,
                frontFunction : StencilFunction.ALWAYS,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.DECREMENT_WRAP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                backFunction : StencilFunction.ALWAYS,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.INCREMENT_WRAP,
                    zPass : StencilOperation.INCREMENT_WRAP
                },
                reference : 0,
                mask : ~0
            },
            depthTest : {
                enabled : false
            },
            depthMask : false
        };
    }

    function getStencilDepthRenderState(enableStencil) {
        return {
            colorMask : {
                red : false,
                green : false,
                blue : false,
                alpha : false
            },
            stencilTest : {
                enabled : enableStencil,
                frontFunction : StencilFunction.ALWAYS,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.INCREMENT_WRAP
                },
                backFunction : StencilFunction.ALWAYS,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                reference : 0,
                mask : ~0
            },
            depthTest : {
                enabled : true,
                func : DepthFunction.LESS_OR_EQUAL
            },
            depthMask : false
        };
    }


    function getColorRenderState(enableStencil) {
        return {
            stencilTest : {
                enabled : enableStencil,
                frontFunction : StencilFunction.NOT_EQUAL,
                frontOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                backFunction : StencilFunction.NOT_EQUAL,
                backOperation : {
                    fail : StencilOperation.KEEP,
                    zFail : StencilOperation.KEEP,
                    zPass : StencilOperation.DECREMENT_WRAP
                },
                reference : 0,
                mask : ~0
            },
            depthTest : {
                enabled : false
            },
            depthMask : false,
            blending : BlendingState.ALPHA_BLEND
        };
    }

    var pickRenderState = {
        stencilTest : {
            enabled : true,
            frontFunction : StencilFunction.NOT_EQUAL,
            frontOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            backFunction : StencilFunction.NOT_EQUAL,
            backOperation : {
                fail : StencilOperation.KEEP,
                zFail : StencilOperation.KEEP,
                zPass : StencilOperation.DECREMENT_WRAP
            },
            reference : 0,
            mask : ~0
        },
        depthTest : {
            enabled : false
        },
        depthMask : false
    };

    var scratchBVCartesianHigh = new Cartesian3();
    var scratchBVCartesianLow = new Cartesian3();
    var scratchBVCartesian = new Cartesian3();
    var scratchBVCartographic = new Cartographic();
    var scratchBVRectangle = new Rectangle();
    var tilingScheme = new GeographicTilingScheme();
    var scratchCorners = [new Cartographic(), new Cartographic(), new Cartographic(), new Cartographic()];
    var scratchTileXY = new Cartesian2();

    function getRectangle(frameState, geometry) {
        var ellipsoid = frameState.mapProjection.ellipsoid;

        if (!defined(geometry.attributes) || !defined(geometry.attributes.position3DHigh)) {
            if (defined(geometry.rectangle)) {
                return geometry.rectangle;
            }

            return undefined;
        }

        var highPositions = geometry.attributes.position3DHigh.values;
        var lowPositions = geometry.attributes.position3DLow.values;
        var length = highPositions.length;

        var minLat = Number.POSITIVE_INFINITY;
        var minLon = Number.POSITIVE_INFINITY;
        var maxLat = Number.NEGATIVE_INFINITY;
        var maxLon = Number.NEGATIVE_INFINITY;

        for (var i = 0; i < length; i +=3) {
            var highPosition = Cartesian3.unpack(highPositions, i, scratchBVCartesianHigh);
            var lowPosition = Cartesian3.unpack(lowPositions, i, scratchBVCartesianLow);

            var position = Cartesian3.add(highPosition, lowPosition, scratchBVCartesian);
            var cartographic = ellipsoid.cartesianToCartographic(position, scratchBVCartographic);

            var latitude = cartographic.latitude;
            var longitude = cartographic.longitude;

            minLat = Math.min(minLat, latitude);
            minLon = Math.min(minLon, longitude);
            maxLat = Math.max(maxLat, latitude);
            maxLon = Math.max(maxLon, longitude);
        }

        var rectangle = scratchBVRectangle;
        rectangle.north = maxLat;
        rectangle.south = minLat;
        rectangle.east = maxLon;
        rectangle.west = minLon;

        return rectangle;
    }

    var scratchDiagonalCartesianNE = new Cartesian3();
    var scratchDiagonalCartesianSW = new Cartesian3();
    var scratchDiagonalCartographic = new Cartographic();
    var scratchCenterCartesian = new Cartesian3();
    var scratchSurfaceCartesian = new Cartesian3();

    function getTileXYLevel(rectangle) {
        Cartographic.fromRadians(rectangle.east, rectangle.north, 0.0, scratchCorners[0]);
        Cartographic.fromRadians(rectangle.west, rectangle.north, 0.0, scratchCorners[1]);
        Cartographic.fromRadians(rectangle.east, rectangle.south, 0.0, scratchCorners[2]);
        Cartographic.fromRadians(rectangle.west, rectangle.south, 0.0, scratchCorners[3]);

        // Determine which tile the bounding rectangle is in
        var lastLevelX = 0, lastLevelY = 0;
        var currentX = 0, currentY = 0;
        var maxLevel = ImageDrapingPrimitive._terrainHeightsMaxLevel;
        for(var i = 0; i <= maxLevel; ++i) {
            var failed = false;
            for(var j = 0; j < 4; ++j) {
                var corner = scratchCorners[j];
                tilingScheme.positionToTileXY(corner, i, scratchTileXY);
                if (j === 0) {
                    currentX = scratchTileXY.x;
                    currentY = scratchTileXY.y;
                } else if(currentX !== scratchTileXY.x || currentY !== scratchTileXY.y) {
                    failed = true;
                    break;
                }
            }

            if (failed) {
                break;
            }

            lastLevelX = currentX;
            lastLevelY = currentY;
        }

        if (i === 0) {
            return undefined;
        }

        return {
            x : lastLevelX,
            y : lastLevelY,
            level : (i > maxLevel) ? maxLevel : (i - 1)
        };
    }

    function setMinMaxTerrainHeights(primitive, rectangle, ellipsoid) {
        var xyLevel = getTileXYLevel(rectangle);

        // Get the terrain min/max for that tile
        var minTerrainHeight = ImageDrapingPrimitive._defaultMinTerrainHeight;
        var maxTerrainHeight = ImageDrapingPrimitive._defaultMaxTerrainHeight;
        if (defined(xyLevel)) {
            var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
            var heights = ImageDrapingPrimitive._terrainHeights[key];
            if (defined(heights)) {
                minTerrainHeight = heights[0];
                maxTerrainHeight = heights[1];
            }

            // Compute min by taking the center of the NE->SW diagonal and finding distance to the surface
            ellipsoid.cartographicToCartesian(Rectangle.northeast(rectangle, scratchDiagonalCartographic),
                scratchDiagonalCartesianNE);
            ellipsoid.cartographicToCartesian(Rectangle.southwest(rectangle, scratchDiagonalCartographic),
                scratchDiagonalCartesianSW);

            Cartesian3.subtract(scratchDiagonalCartesianSW, scratchDiagonalCartesianNE, scratchCenterCartesian);
            Cartesian3.add(scratchDiagonalCartesianNE,
                Cartesian3.multiplyByScalar(scratchCenterCartesian, 0.5, scratchCenterCartesian), scratchCenterCartesian);
            var surfacePosition = ellipsoid.scaleToGeodeticSurface(scratchCenterCartesian, scratchSurfaceCartesian);
            if (defined(surfacePosition)) {
                var distance = Cartesian3.distance(scratchCenterCartesian, surfacePosition);
                minTerrainHeight = Math.min(minTerrainHeight, -distance);
            } else {
                minTerrainHeight = ImageDrapingPrimitive._defaultMinTerrainHeight;
            }
        }

        primitive._minTerrainHeight = Math.max(ImageDrapingPrimitive._defaultMinTerrainHeight, minTerrainHeight);
        primitive._maxTerrainHeight = maxTerrainHeight;
    }

    var scratchBoundingSphere = new BoundingSphere();
    function getInstanceBoundingSphere(rectangle, ellipsoid) {
        var xyLevel = getTileXYLevel(rectangle);

        // Get the terrain max for that tile
        var maxTerrainHeight = ImageDrapingPrimitive._defaultMaxTerrainHeight;
        if (defined(xyLevel)) {
            var key = xyLevel.level + '-' + xyLevel.x + '-' + xyLevel.y;
            var heights = ImageDrapingPrimitive._terrainHeights[key];
            if (defined(heights)) {
                maxTerrainHeight = heights[1];
            }
        }

        var result = BoundingSphere.fromRectangle3D(rectangle, ellipsoid, 0.0);
        BoundingSphere.fromRectangle3D(rectangle, ellipsoid, maxTerrainHeight, scratchBoundingSphere);

        return BoundingSphere.union(result, scratchBoundingSphere, result);
    }

    function createBoundingVolume(groundPrimitive, frameState, geometry) {
        var ellipsoid = frameState.mapProjection.ellipsoid;
        var rectangle = getRectangle(frameState, geometry);

        // Use an oriented bounding box by default, but switch to a bounding sphere if bounding box creation would fail.
        if (rectangle.width < CesiumMath.PI) {
            var obb = OrientedBoundingBox.fromRectangle(rectangle, groundPrimitive._maxHeight, groundPrimitive._minHeight, ellipsoid);
            groundPrimitive._boundingVolumes.push(obb);
        } else {
            var highPositions = geometry.attributes.position3DHigh.values;
            var lowPositions = geometry.attributes.position3DLow.values;
            groundPrimitive._boundingVolumes.push(BoundingSphere.fromEncodedCartesianVertices(highPositions, lowPositions));
        }

        if (!frameState.scene3DOnly) {
            var projection = frameState.mapProjection;
            var boundingVolume = BoundingSphere.fromRectangleWithHeights2D(rectangle, projection, groundPrimitive._maxHeight, groundPrimitive._minHeight);
            Cartesian3.fromElements(boundingVolume.center.z, boundingVolume.center.x, boundingVolume.center.y, boundingVolume.center);

            groundPrimitive._boundingVolumes2D.push(boundingVolume);
        }
    }

    function createRenderStates(groundPrimitive, context, appearance, twoPasses) {
        if (defined(groundPrimitive._rsStencilPreloadPass)) {
            return;
        }
        var stencilEnabled = !groundPrimitive.debugShowShadowVolume;

        groundPrimitive._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(stencilEnabled));
        groundPrimitive._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(stencilEnabled));
        groundPrimitive._rsColorPass = RenderState.fromCache(getColorRenderState(stencilEnabled));
        groundPrimitive._rsPickPass = RenderState.fromCache(pickRenderState);
    }

    function modifyForEncodedNormals(primitive, vertexShaderSource) {
        if (!primitive.compressVertices) {
            return vertexShaderSource;
        }

        if (vertexShaderSource.search(/attribute\s+vec3\s+extrudeDirection;/g) !== -1) {
            var attributeName = 'compressedAttributes';

            //only shadow volumes use extrudeDirection, and shadow volumes use vertexFormat: POSITION_ONLY so we don't need to check other attributes
            var attributeDecl = 'attribute vec2 ' + attributeName + ';';

            var globalDecl = 'vec3 extrudeDirection;\n';
            var decode = '    extrudeDirection = czm_octDecode(' + attributeName + ', 65535.0);\n';

            var modifiedVS = vertexShaderSource;
            modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+extrudeDirection;/g, '');
            modifiedVS = ShaderSource.replaceMain(modifiedVS, 'czm_non_compressed_main');
            var compressedMain =
                'void main() \n' +
                '{ \n' +
                decode +
                '    czm_non_compressed_main(); \n' +
                '}';

            return [attributeDecl, globalDecl, modifiedVS, compressedMain].join('\n');
        }
    }

    function createShaderProgram(groundPrimitive, frameState, appearance) {
        if (defined(groundPrimitive._sp)) {
            return;
        }

        var context = frameState.context;
        var primitive = groundPrimitive._primitive;
        var vs = ShadowVolumeVS;
        vs = groundPrimitive._primitive._batchTable.getVertexShaderCallback()(vs);
        vs = Primitive._appendShowToShader(primitive, vs);
        vs = Primitive._appendDistanceDisplayConditionToShader(primitive, vs);
        vs = Primitive._modifyShaderPosition(groundPrimitive, vs, frameState.scene3DOnly);
        vs = Primitive._updateColorAttribute(primitive, vs);
        vs = modifyForEncodedNormals(primitive, vs);

        var fs = ShadowVolumeFS;
        var attributeLocations = groundPrimitive._primitive._attributeLocations;

        groundPrimitive._sp = ShaderProgram.replaceCache({
            context : context,
            shaderProgram : groundPrimitive._sp,
            vertexShaderSource : vs,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });

        if (groundPrimitive._primitive.allowPicking) {
            var vsPick = ShaderSource.createPickVertexShaderSource(vs);
            vsPick = Primitive._updatePickColorAttribute(vsPick);

            var pickFS = new ShaderSource({
                sources : [fs],
                pickColorQualifier : 'varying'
            });
            groundPrimitive._spPick = ShaderProgram.replaceCache({
                context : context,
                shaderProgram : groundPrimitive._spPick,
                vertexShaderSource : vsPick,
                fragmentShaderSource : pickFS,
                attributeLocations : attributeLocations
            });
        } else {
            groundPrimitive._spPick = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vs,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
        }
    }

    function createColorCommands(groundPrimitive, appearance, material, colorCommands) {
        var primitive = groundPrimitive._primitive;

        // Create uniform map by combining uniforms from the appearance and material if either have uniforms.
        /*var materialUniformMap = defined(material) ? material._uniforms : undefined;
        var appearanceUniformMap = {};
        var appearanceUniforms = appearance.uniforms;
        if (defined(appearanceUniforms)) {
            // Convert to uniform map of functions for the renderer
            for (var name in appearanceUniforms) {
                if (appearanceUniforms.hasOwnProperty(name)) {
                                        if (defined(materialUniformMap) && defined(materialUniformMap[name])) {
                        // Later, we could rename uniforms behind-the-scenes if needed.
                        throw new DeveloperError('Appearance and material have a uniform with the same name: ' + name);
                    }
                    
                    appearanceUniformMap[name] = getUniformFunction(appearanceUniforms, name);
                }
            }
        }
        var uniforms = combine(appearanceUniformMap, materialUniformMap);
        var uniformMap = primitive._batchTable.getUniformMapCallback()(uniforms);*/
        var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap);

        var length = primitive._va.length * 3;
        colorCommands.length = length;
        var vaIndex = 0;

        for (var i = 0; i < length; i += 3) {
            var vertexArray = primitive._va[vaIndex++];

            // stencil preload command
            var command = colorCommands[i];
            if (!defined(command)) {
                command = colorCommands[i] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.renderState = groundPrimitive._rsStencilPreloadPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // stencil depth command
            command = colorCommands[i + 1];
            if (!defined(command)) {
                command = colorCommands[i + 1] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.renderState = groundPrimitive._rsStencilDepthPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // color command
            command = colorCommands[i + 2];
            if (!defined(command)) {
                command = colorCommands[i + 2] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.renderState = groundPrimitive._rsColorPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;
        }
    }

    function createPickCommands(groundPrimitive, pickCommands) {
        var primitive = groundPrimitive._primitive;
        var pickOffsets = primitive._pickOffsets;
        var length = pickOffsets.length * 3;
        pickCommands.length = length;

        var pickIndex = 0;
        var uniformMap = primitive._batchTable.getUniformMapCallback()(groundPrimitive._uniformMap);

        for (var j = 0; j < length; j += 3) {
            var pickOffset = pickOffsets[pickIndex++];

            var offset = pickOffset.offset;
            var count = pickOffset.count;
            var vertexArray = primitive._va[pickOffset.index];

            // stencil preload command
            var command = pickCommands[j];
            if (!defined(command)) {
                command = pickCommands[j] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.offset = offset;
            command.count = count;
            command.renderState = groundPrimitive._rsStencilPreloadPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // stencil depth command
            command = pickCommands[j + 1];
            if (!defined(command)) {
                command = pickCommands[j + 1] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.offset = offset;
            command.count = count;
            command.renderState = groundPrimitive._rsStencilDepthPass;
            command.shaderProgram = groundPrimitive._sp;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;

            // color command
            command = pickCommands[j + 2];
            if (!defined(command)) {
                command = pickCommands[j + 2] = new DrawCommand({
                    owner : groundPrimitive,
                    primitiveType : primitive._primitiveType
                });
            }

            command.vertexArray = vertexArray;
            command.offset = offset;
            command.count = count;
            command.renderState = groundPrimitive._rsPickPass;
            command.shaderProgram = groundPrimitive._spPick;
            command.uniformMap = uniformMap;
            command.pass = Pass.GROUND;
        }
    }

    function createCommands(groundPrimitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
        createColorCommands(groundPrimitive, appearance, material, colorCommands);
        createPickCommands(groundPrimitive, pickCommands);
    }

    function updateAndQueueCommands(groundPrimitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
        var boundingVolumes;
        if (frameState.mode === SceneMode.SCENE3D) {
            boundingVolumes = groundPrimitive._boundingVolumes;
        } else if (frameState.mode !== SceneMode.SCENE3D && defined(groundPrimitive._boundingVolumes2D)) {
            boundingVolumes = groundPrimitive._boundingVolumes2D;
        }

        var commandList = frameState.commandList;
        var passes = frameState.passes;
        if (passes.render) {
            var colorLength = colorCommands.length;
            for (var j = 0; j < colorLength; ++j) {
                colorCommands[j].modelMatrix = modelMatrix;
                colorCommands[j].boundingVolume = boundingVolumes[Math.floor(j / 3)];
                colorCommands[j].cull = cull;
                colorCommands[j].debugShowBoundingVolume = debugShowBoundingVolume;

                commandList.push(colorCommands[j]);
            }
        }

        if (passes.pick) {
            var primitive = groundPrimitive._primitive;
            var pickOffsets = primitive._pickOffsets;
            var length = pickOffsets.length * 3;
            pickCommands.length = length;

            var pickIndex = 0;
            for (var k = 0; k < length; k += 3) {
                var pickOffset = pickOffsets[pickIndex++];
                var bv = boundingVolumes[pickOffset.index];

                pickCommands[k].modelMatrix = modelMatrix;
                pickCommands[k].boundingVolume = bv;
                pickCommands[k].cull = cull;

                pickCommands[k + 1].modelMatrix = modelMatrix;
                pickCommands[k + 1].boundingVolume = bv;
                pickCommands[k + 1].cull = cull;

                pickCommands[k + 2].modelMatrix = modelMatrix;
                pickCommands[k + 2].boundingVolume = bv;
                pickCommands[k + 2].cull = cull;

                commandList.push(pickCommands[k], pickCommands[k + 1], pickCommands[k + 2]);
            }
        }
    }

    ImageDrapingPrimitive._initialized = false;
    ImageDrapingPrimitive._initPromise = undefined;

    /**
     * Initializes the minimum and maximum terrain heights. This only needs to be called if you are creating the
     * ImageDrapingPrimitive asynchronously.
     *
     * @returns {Promise} A promise that will resolve once the terrain heights have been loaded.
     *
     */
    ImageDrapingPrimitive.initializeTerrainHeights = function() {
        var initPromise = ImageDrapingPrimitive._initPromise;
        if (defined(initPromise)) {
            return initPromise;
        }

        ImageDrapingPrimitive._initPromise = loadJson(buildModuleUrl('Assets/approximateTerrainHeights.json')).then(function(json) {
            ImageDrapingPrimitive._initialized = true;
            ImageDrapingPrimitive._terrainHeights = json;
        });

        return ImageDrapingPrimitive._initPromise;
    };

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} All instance geometries must have the same primitiveType.
     * @exception {DeveloperError} Appearance and material have a uniform with the same name.
     * @exception {DeveloperError} Not all of the geometry instances have the same color attribute.
     */
    ImageDrapingPrimitive.prototype.update = function(frameState) {
        if (!this.show || (!defined(this._primitive) && !defined(this.geometryInstances))) {
            return;
        }

        if (!ImageDrapingPrimitive._initialized) {
                        if (!this.asynchronous) {
                throw new DeveloperError('For synchronous ImageDrapingPrimitives, you must call ImageDrapingPrimitive.initializeTerrainHeights() and wait for the returned promise to resolve.');
            }
            
            ImageDrapingPrimitive.initializeTerrainHeights();
            return;
        }

        var that = this;
        var primitiveOptions = this._primitiveOptions;

        if (!defined(this._primitive)) {
            var ellipsoid = frameState.mapProjection.ellipsoid;

            var instance;
            var geometry;
            var instanceType;

            var instances = isArray(this.geometryInstances) ? this.geometryInstances : [this.geometryInstances];
            var length = instances.length;
            var groundInstances = new Array(length);

            var i;
            var color;
            var rectangle;
            for (i = 0; i < length; ++i) {
                instance = instances[i];
                geometry = instance.geometry;
                var instanceRectangle = getRectangle(frameState, geometry);
                if (!defined(rectangle)) {
                    rectangle = instanceRectangle;
                } else {
                    if (defined(instanceRectangle)) {
                        Rectangle.union(rectangle, instanceRectangle, rectangle);
                    }
                }

                var id = instance.id;
                if (defined(id) && defined(instanceRectangle)) {
                    var boundingSphere = getInstanceBoundingSphere(instanceRectangle, ellipsoid);
                    this._boundingSpheresKeys.push(id);
                    this._boundingSpheres.push(boundingSphere);
                }

                instanceType = geometry.constructor;
                if (defined(instanceType) && defined(instanceType.createShadowVolume)) {
                    var attributes = instance.attributes;

                                        /*if (!defined(attributes) || !defined(attributes.color)) {
                        throw new DeveloperError('Not all of the geometry instances have the same color attribute.');
                    } else if (defined(color) && !ColorGeometryInstanceAttribute.equals(color, attributes.color)) {
                        throw new DeveloperError('Not all of the geometry instances have the same color attribute.');
                    } else if (!defined(color)) {
                        color = attributes.color;
                    }*/
                                    } else {
                                        throw new DeveloperError('Not all of the geometry instances have ImageDrapingPrimitive support.');
                                    }
            }

            // Now compute the min/max heights for the primitive
            setMinMaxTerrainHeights(this, rectangle, frameState.mapProjection.ellipsoid);
            var exaggeration = frameState.terrainExaggeration;
            this._minHeight = this._minTerrainHeight * exaggeration;
            this._maxHeight = this._maxTerrainHeight * exaggeration;

            for (i = 0; i < length; ++i) {
                instance = instances[i];
                geometry = instance.geometry;
                instanceType = geometry.constructor;
                groundInstances[i] = new GeometryInstance({
                    geometry : instanceType.createShadowVolume(geometry, getComputeMinimumHeightFunction(this),
                        getComputeMaximumHeightFunction(this)),
                    attributes : instance.attributes,
                    id : instance.id,
                    pickPrimitive : this
                });
            }

            primitiveOptions.geometryInstances = groundInstances;

            primitiveOptions._createBoundingVolumeFunction = function(frameState, geometry) {
                createBoundingVolume(that, frameState, geometry);
            };
            primitiveOptions._createRenderStatesFunction = function(primitive, context, appearance, twoPasses) {
                createRenderStates(that, context, appearance);
            };
            primitiveOptions._createShaderProgramFunction = function(primitive, frameState, appearance) {
                createShaderProgram(that, frameState, appearance);
            };
            primitiveOptions._createCommandsFunction = function(primitive, appearance, material, translucent, twoPasses, colorCommands, pickCommands) {
                createCommands(that, appearance, material, true, false, colorCommands, pickCommands);
            };
            primitiveOptions._updateAndQueueCommandsFunction = function(primitive, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses) {
                updateAndQueueCommands(that, frameState, colorCommands, pickCommands, modelMatrix, cull, debugShowBoundingVolume, twoPasses);
            };

            this._primitive = new Primitive(primitiveOptions);
            this._primitive.readyPromise.then(function(primitive) {
                that._ready = true;

                if (that.releaseGeometryInstances) {
                    that.geometryInstances = undefined;
                }

                var error = primitive._error;
                if (!defined(error)) {
                    that._readyPromise.resolve(that);
                } else {
                    that._readyPromise.reject(error);
                }
            });
        }

        if (this.debugShowShadowVolume && !this._debugShowShadowVolume && this._ready) {
            this._debugShowShadowVolume = true;
            this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(false));
            this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(false));
            this._rsColorPass = RenderState.fromCache(getColorRenderState(false));
        } else if (!this.debugShowShadowVolume && this._debugShowShadowVolume) {
            this._debugShowShadowVolume = false;
            this._rsStencilPreloadPass = RenderState.fromCache(getStencilPreloadRenderState(true));
            this._rsStencilDepthPass = RenderState.fromCache(getStencilDepthRenderState(true));
            this._rsColorPass = RenderState.fromCache(getColorRenderState(true));
        }

        this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
        this._primitive.update(frameState);
    };

    /**
     * @private
     */
    ImageDrapingPrimitive.prototype.getBoundingSphere = function(id) {
        var index = this._boundingSpheresKeys.indexOf(id);
        if (index !== -1) {
            return this._boundingSpheres[index];
        }

        return undefined;
    };

    /**
     * Returns the modifiable per-instance attributes for a {@link GeometryInstance}.
     *
     * @param {Object} id The id of the {@link GeometryInstance}.
     * @returns {Object} The typed array in the attribute's format or undefined if the is no instance with id.
     *
     * @exception {DeveloperError} must call update before calling getGeometryInstanceAttributes.
     *
     * @example
     * var attributes = primitive.getGeometryInstanceAttributes('an id');
     * attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue(Cesium.Color.AQUA);
     * attributes.show = Cesium.ShowGeometryInstanceAttribute.toValue(true);
     */
    ImageDrapingPrimitive.prototype.getGeometryInstanceAttributes = function(id) {
                if (!defined(this._primitive)) {
            throw new DeveloperError('must call update before calling getGeometryInstanceAttributes');
        }
                return this._primitive.getGeometryInstanceAttributes(id);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <p>
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     * </p>
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see ImageDrapingPrimitive#destroy
     */
    ImageDrapingPrimitive.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <p>
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     * </p>
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * e = e && e.destroy();
     *
     * @see ImageDrapingPrimitive#isDestroyed
     */
    ImageDrapingPrimitive.prototype.destroy = function() {
        this._primitive = this._primitive && this._primitive.destroy();
        this._sp = this._sp && this._sp.destroy();
        this._spPick = this._spPick && this._spPick.destroy();
        return destroyObject(this);
    };

    return ImageDrapingPrimitive;
});

/*global define*/
define('Scene/MapboxImageryProvider',[
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/MapboxApi',
        './UrlTemplateImageryProvider'
    ], function(
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        MapboxApi,
        UrlTemplateImageryProvider) {
    'use strict';

    var trailingSlashRegex = /\/$/;
    var defaultCredit1 = new Credit('© Mapbox © OpenStreetMap', undefined, 'https://www.mapbox.com/about/maps/');
    var defaultCredit2 = [new Credit('Improve this map', undefined, 'https://www.mapbox.com/map-feedback/')];

    /**
     * Provides tiled imagery hosted by Mapbox.
     *
     * @alias MapboxImageryProvider
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {String} [options.url='https://api.mapbox.com/v4/'] The Mapbox server url.
     * @param {String} options.mapId The Mapbox Map ID.
     * @param {String} [options.accessToken] The public access token for the imagery.
     * @param {String} [options.format='png'] The format of the image request.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.  Take care when specifying
     *                 this that the number of tiles at the minimum level is small, such as four or less.  A larger number is likely
     *                 to result in rendering problems.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
     * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
     *
     *
     * @example
     * // Mapbox tile provider
     * var mapbox = new Cesium.MapboxImageryProvider({
     *     mapId: 'mapbox.streets',
     *     accessToken: 'thisIsMyAccessToken'
     * });
     *
     * @see {@link https://www.mapbox.com/developers/api/maps/#tiles}
     * @see {@link https://www.mapbox.com/developers/api/#access-tokens}
     */
    function MapboxImageryProvider(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var mapId = options.mapId;
                if (!defined(mapId)) {
            throw new DeveloperError('options.mapId is required.');
        }
        
        var url = defaultValue(options.url, 'https://api.mapbox.com/v4/');
        this._url = url;
        this._mapId = mapId;
        this._accessToken = MapboxApi.getAccessToken(options.accessToken);
        this._accessTokenErrorCredit = MapboxApi.getErrorCredit(options.key);
        var format = defaultValue(options.format, 'png');
        if (!/\./.test(format)) {
            format = '.' + format;
        }
        this._format = format;

        var templateUrl = url;
        if (!trailingSlashRegex.test(url)) {
            templateUrl += '/';
        }
        templateUrl += mapId + '/{z}/{x}/{y}' + this._format;
        if (defined(this._accessToken)) {
            templateUrl += '?access_token=' + this._accessToken;
        }

        if (defined(options.credit)) {
            var credit = options.credit;
            if (typeof credit === 'string') {
                credit = new Credit(credit);
            }
            defaultCredit1 = credit;
            defaultCredit2.length = 0;
        }

        this._imageryProvider = new UrlTemplateImageryProvider({
            url: templateUrl,
            proxy: options.proxy,
            credit: defaultCredit1,
            ellipsoid: options.ellipsoid,
            minimumLevel: options.minimumLevel,
            maximumLevel: options.maximumLevel,
            rectangle: options.rectangle
        });
    }

    defineProperties(MapboxImageryProvider.prototype, {
        /**
         * Gets the URL of the Mapbox server.
         * @memberof MapboxImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof MapboxImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._imageryProvider.ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof MapboxImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._imageryProvider.readyPromise;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by the instance.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle: {
            get : function() {
                return this._imageryProvider.rectangle;
            }
        },

        /**
         * Gets the width of each tile, in pixels.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                return this._imageryProvider.tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight : {
            get : function() {
                return this._imageryProvider.tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                return this._imageryProvider.maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true. Generally,
         * a minimum level should only be used when the rectangle of the imagery is small
         * enough that the number of tiles at the minimum level is small.  An imagery
         * provider with more than a few tiles at the minimum level will lead to
         * rendering problems.
         * @memberof MapboxImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                return this._imageryProvider.minimumLevel;
            }
        },

        /**
         * Gets the tiling scheme used by the provider.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                return this._imageryProvider.tilingScheme;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                return this._imageryProvider.tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error..  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof MapboxImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._imageryProvider.errorEvent;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery. This function should
         * not be called before {@link MapboxImageryProvider#ready} returns true.
         * @memberof MapboxImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._imageryProvider.credit;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof MapboxImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._imageryProvider.proxy;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof MapboxImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return this._imageryProvider.hasAlphaChannel;
            }
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    MapboxImageryProvider.prototype.getTileCredits = function(x, y, level) {
        var credits = defaultCredit2.slice();
        if (defined(this._accessTokenErrorCredit)) {
            credits.push(this._accessTokenErrorCredit);
        }
        return credits;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link MapboxImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    MapboxImageryProvider.prototype.requestImage = function(x, y, level) {
        return this._imageryProvider.requestImage(x, y, level);
    };

    /**
     * Asynchronously determines what features, if any, are located at a given longitude and latitude within
     * a tile.  This function should not be called before {@link MapboxImageryProvider#ready} returns true.
     * This function is optional, so it may not exist on all ImageryProviders.
     *
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     *
     * @exception {DeveloperError} <code>pickFeatures</code> must not be called before the imagery provider is ready.
     */
    MapboxImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return this._imageryProvider.pickFeatures(x, y, level, longitude, latitude);
    };

    return MapboxImageryProvider;
});

/*global define*/
define('Scene/Moon',[
        '../Core/buildModuleUrl',
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/Ellipsoid',
        '../Core/IauOrientationAxes',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Simon1994PlanetaryPositions',
        '../Core/Transforms',
        './EllipsoidPrimitive',
        './Material'
    ], function(
        buildModuleUrl,
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        Ellipsoid,
        IauOrientationAxes,
        Matrix3,
        Matrix4,
        Simon1994PlanetaryPositions,
        Transforms,
        EllipsoidPrimitive,
        Material) {
    'use strict';

    /**
     * Draws the Moon in 3D.
     * @alias Moon
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.show=true] Determines whether the moon will be rendered.
     * @param {String} [options.textureUrl=buildModuleUrl('Assets/Textures/moonSmall.jpg')] The moon texture.
     * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.MOON] The moon ellipsoid.
     * @param {Boolean} [options.onlySunLighting=true] Use the sun as the only light source.
     *
     *
     * @example
     * scene.moon = new Cesium.Moon();
     * 
     * @see Scene#moon
     */
    function Moon(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var url = options.textureUrl;
        if (!defined(url)) {
            url = buildModuleUrl('Assets/Textures/moonSmall.jpg');
        }

        /**
         * Determines if the moon will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = defaultValue(options.show, true);

        /**
         * The moon texture.
         * @type {String}
         * @default buildModuleUrl('Assets/Textures/moonSmall.jpg')
         */
        this.textureUrl = url;

        this._ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.MOON);

        /**
         * Use the sun as the only light source.
         * @type {Boolean}
         * @default true
         */
        this.onlySunLighting = defaultValue(options.onlySunLighting, true);

        this._ellipsoidPrimitive = new EllipsoidPrimitive({
            radii : this.ellipsoid.radii,
            material : Material.fromType(Material.ImageType),
            depthTestEnabled : false,
            _owner : this
        });
        this._ellipsoidPrimitive.material.translucent = false;

        this._axes = new IauOrientationAxes();
    }

    defineProperties(Moon.prototype, {
        /**
         * Get the ellipsoid that defines the shape of the moon.
         *
         * @memberof Moon.prototype
         *
         * @type {Ellipsoid}
         * @readonly
         *
         * @default {@link Ellipsoid.MOON}
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        }
    });

    var icrfToFixed = new Matrix3();
    var rotationScratch = new Matrix3();
    var translationScratch = new Cartesian3();
    var scratchCommandList = [];

    /**
     * @private
     */
    Moon.prototype.update = function(frameState) {
        if (!this.show) {
            return;
        }

        var ellipsoidPrimitive = this._ellipsoidPrimitive;
        ellipsoidPrimitive.material.uniforms.image = this.textureUrl;
        ellipsoidPrimitive.onlySunLighting = this.onlySunLighting;

        var date = frameState.time;
        if (!defined(Transforms.computeIcrfToFixedMatrix(date, icrfToFixed))) {
            Transforms.computeTemeToPseudoFixedMatrix(date, icrfToFixed);
        }

        var rotation = this._axes.evaluate(date, rotationScratch);
        Matrix3.transpose(rotation, rotation);
        Matrix3.multiply(icrfToFixed, rotation, rotation);

        var translation = Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame(date, translationScratch);
        Matrix3.multiplyByVector(icrfToFixed, translation, translation);

        Matrix4.fromRotationTranslation(rotation, translation, ellipsoidPrimitive.modelMatrix);

        var savedCommandList = frameState.commandList;
        frameState.commandList = scratchCommandList;
        scratchCommandList.length = 0;
        ellipsoidPrimitive.update(frameState);
        frameState.commandList = savedCommandList;
        return (scratchCommandList.length === 1) ? scratchCommandList[0] : undefined;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Moon#destroy
     */
    Moon.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * moon = moon && moon.destroy();
     * 
     * @see Moon#isDestroyed
     */
    Moon.prototype.destroy = function() {
        this._ellipsoidPrimitive = this._ellipsoidPrimitive && this._ellipsoidPrimitive.destroy();
        return destroyObject(this);
    };

    return Moon;
});

/*global define*/
define('Scene/NeverTileDiscardPolicy',[], function() {
    'use strict';

    /**
     * A {@link TileDiscardPolicy} specifying that tile images should never be discard.
     *
     * @alias NeverTileDiscardPolicy
     * @constructor
     *
     * @see DiscardMissingTileImagePolicy
     */
    function NeverTileDiscardPolicy(options) {
    }

    /**
     * Determines if the discard policy is ready to process images.
     * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
     */
    NeverTileDiscardPolicy.prototype.isReady = function() {
        return true;
    };

    /**
     * Given a tile image, decide whether to discard that image.
     *
     * @param {Image} image An image to test.
     * @returns {Boolean} True if the image should be discarded; otherwise, false.
     */
    NeverTileDiscardPolicy.prototype.shouldDiscardImage = function(image) {
        return false;
    };

    return NeverTileDiscardPolicy;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/AdjustTranslucentFS',[],function() {
    'use strict';
    return "#ifdef MRT\n\
#extension GL_EXT_draw_buffers : enable\n\
#endif\n\
\n\
uniform vec4 u_bgColor;\n\
uniform sampler2D u_depthTexture;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    if (texture2D(u_depthTexture, v_textureCoordinates).r < 1.0)\n\
    {\n\
#ifdef MRT\n\
        gl_FragData[0] = u_bgColor;\n\
        gl_FragData[1] = vec4(u_bgColor.a);\n\
#else\n\
        gl_FragColor = u_bgColor;\n\
#endif\n\
        return;\n\
    }\n\
    \n\
    discard;\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/CompositeOITFS',[],function() {
    'use strict';
    return "/**\n\
 * Compositing for Weighted Blended Order-Independent Transparency. See:\n\
 * - http://jcgt.org/published/0002/02/09/\n\
 * - http://casual-effects.blogspot.com/2014/03/weighted-blended-order-independent.html\n\
 */\n\
 \n\
uniform sampler2D u_opaque;\n\
uniform sampler2D u_accumulation;\n\
uniform sampler2D u_revealage;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    vec4 opaque = texture2D(u_opaque, v_textureCoordinates);\n\
    vec4 accum = texture2D(u_accumulation, v_textureCoordinates);\n\
    float r = texture2D(u_revealage, v_textureCoordinates).r;\n\
    \n\
#ifdef MRT\n\
    vec4 transparent = vec4(accum.rgb / clamp(r, 1e-4, 5e4), accum.a);\n\
#else\n\
    vec4 transparent = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);\n\
#endif\n\
    \n\
    gl_FragColor = (1.0 - transparent.a) * transparent + transparent.a * opaque;\n\
}\n\
";
});
/*global define*/
define('Scene/OIT',[
        '../Core/BoundingRectangle',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/PixelFormat',
        '../Core/WebGLConstants',
        '../Renderer/ClearCommand',
        '../Renderer/DrawCommand',
        '../Renderer/Framebuffer',
        '../Renderer/PixelDatatype',
        '../Renderer/RenderState',
        '../Renderer/ShaderSource',
        '../Renderer/Texture',
        '../Shaders/AdjustTranslucentFS',
        '../Shaders/CompositeOITFS',
        './BlendEquation',
        './BlendFunction'
    ], function(
        BoundingRectangle,
        Color,
        defined,
        destroyObject,
        PixelFormat,
        WebGLConstants,
        ClearCommand,
        DrawCommand,
        Framebuffer,
        PixelDatatype,
        RenderState,
        ShaderSource,
        Texture,
        AdjustTranslucentFS,
        CompositeOITFS,
        BlendEquation,
        BlendFunction) {
    'use strict';

    /**
     * @private
     */
    function OIT(context) {
        // We support multipass for the Chrome D3D9 backend and ES 2.0 on mobile.
        this._translucentMultipassSupport = false;
        this._translucentMRTSupport = false;

        var extensionsSupported = context.floatingPointTexture && context.depthTexture;
        this._translucentMRTSupport = context.drawBuffers && extensionsSupported;
        this._translucentMultipassSupport = !this._translucentMRTSupport && extensionsSupported;

        this._opaqueFBO = undefined;
        this._opaqueTexture = undefined;
        this._depthStencilTexture = undefined;

        this._accumulationTexture = undefined;

        this._translucentFBO = undefined;
        this._alphaFBO = undefined;

        this._adjustTranslucentFBO = undefined;
        this._adjustAlphaFBO = undefined;

        this._opaqueClearCommand = new ClearCommand({
            color : new Color(0.0, 0.0, 0.0, 0.0),
            owner : this
        });
        this._translucentMRTClearCommand = new ClearCommand({
            color : new Color(0.0, 0.0, 0.0, 1.0),
            owner : this
        });
        this._translucentMultipassClearCommand = new ClearCommand({
            color : new Color(0.0, 0.0, 0.0, 0.0),
            owner : this
        });
        this._alphaClearCommand = new ClearCommand({
            color : new Color(1.0, 1.0, 1.0, 1.0),
            owner : this
        });

        this._translucentRenderStateCache = {};
        this._alphaRenderStateCache = {};

        this._compositeCommand = undefined;
        this._adjustTranslucentCommand = undefined;
        this._adjustAlphaCommand = undefined;

        this._viewport = new BoundingRectangle();
        this._rs = undefined;
    }

    function destroyTextures(oit) {
        oit._accumulationTexture = oit._accumulationTexture && !oit._accumulationTexture.isDestroyed() && oit._accumulationTexture.destroy();
        oit._revealageTexture = oit._revealageTexture && !oit._revealageTexture.isDestroyed() && oit._revealageTexture.destroy();
    }

    function destroyFramebuffers(oit) {
        oit._translucentFBO = oit._translucentFBO && !oit._translucentFBO.isDestroyed() && oit._translucentFBO.destroy();
        oit._alphaFBO = oit._alphaFBO && !oit._alphaFBO.isDestroyed() && oit._alphaFBO.destroy();
        oit._adjustTranslucentFBO = oit._adjustTranslucentFBO && !oit._adjustTranslucentFBO.isDestroyed() && oit._adjustTranslucentFBO.destroy();
        oit._adjustAlphaFBO = oit._adjustAlphaFBO && !oit._adjustAlphaFBO.isDestroyed() && oit._adjustAlphaFBO.destroy();
    }

    function destroyResources(oit) {
        destroyTextures(oit);
        destroyFramebuffers(oit);
    }

    function updateTextures(oit, context, width, height) {
        destroyTextures(oit);

        // Use zeroed arraybuffer instead of null to initialize texture
        // to workaround Firefox 50. https://github.com/AnalyticalGraphicsInc/cesium/pull/4762
        var source = new Float32Array(width * height * 4);

        oit._accumulationTexture = new Texture({
            context : context,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.FLOAT,
            source : {
                arrayBufferView : source,
                width : width,
                height : height
            }
        });
        oit._revealageTexture = new Texture({
            context : context,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.FLOAT,
            source : {
                arrayBufferView : source,
                width : width,
                height : height
            }
        });
    }

    function updateFramebuffers(oit, context) {
        destroyFramebuffers(oit);

        var completeFBO = WebGLConstants.FRAMEBUFFER_COMPLETE;
        var supported = true;

        // if MRT is supported, attempt to make an FBO with multiple color attachments
        if (oit._translucentMRTSupport) {
            oit._translucentFBO = new Framebuffer({
                context : context,
                colorTextures : [oit._accumulationTexture, oit._revealageTexture],
                depthStencilTexture : oit._depthStencilTexture,
                destroyAttachments : false
            });
            oit._adjustTranslucentFBO = new Framebuffer({
                context : context,
                colorTextures : [oit._accumulationTexture, oit._revealageTexture],
                destroyAttachments : false
            });

            if (oit._translucentFBO.status !== completeFBO || oit._adjustTranslucentFBO.status !== completeFBO) {
                destroyFramebuffers(oit);
                oit._translucentMRTSupport = false;
            }
        }

        // either MRT isn't supported or FBO creation failed, attempt multipass
        if (!oit._translucentMRTSupport) {
            oit._translucentFBO = new Framebuffer({
                context : context,
                colorTextures : [oit._accumulationTexture],
                depthStencilTexture : oit._depthStencilTexture,
                destroyAttachments : false
            });
            oit._alphaFBO = new Framebuffer({
                context : context,
                colorTextures : [oit._revealageTexture],
                depthStencilTexture : oit._depthStencilTexture,
                destroyAttachments : false
            });
            oit._adjustTranslucentFBO = new Framebuffer({
                context : context,
                colorTextures : [oit._accumulationTexture],
                destroyAttachments : false
            });
            oit._adjustAlphaFBO = new Framebuffer({
                context : context,
                colorTextures : [oit._revealageTexture],
                destroyAttachments : false
            });

            var translucentComplete = oit._translucentFBO.status === completeFBO;
            var alphaComplete = oit._alphaFBO.status === completeFBO;
            var adjustTranslucentComplete = oit._adjustTranslucentFBO.status === completeFBO;
            var adjustAlphaComplete = oit._adjustAlphaFBO.status === completeFBO;
            if (!translucentComplete || !alphaComplete || !adjustTranslucentComplete || !adjustAlphaComplete) {
                destroyResources(oit);
                oit._translucentMultipassSupport = false;
                supported = false;
            }
        }

        return supported;
    }

    OIT.prototype.update = function(context, framebuffer) {
        if (!this.isSupported()) {
            return;
        }

        this._opaqueFBO = framebuffer;
        this._opaqueTexture = framebuffer.getColorTexture(0);
        this._depthStencilTexture = framebuffer.depthStencilTexture;

        var width = this._opaqueTexture.width;
        var height = this._opaqueTexture.height;

        var accumulationTexture = this._accumulationTexture;
        var textureChanged = !defined(accumulationTexture) || accumulationTexture.width !== width || accumulationTexture.height !== height;
        if (textureChanged) {
            updateTextures(this, context, width, height);
        }

        if (!defined(this._translucentFBO) || textureChanged) {
            if (!updateFramebuffers(this, context)) {
                // framebuffer creation failed
                return;
            }
        }

        var that = this;
        var fs;
        var uniformMap;

        if (!defined(this._compositeCommand)) {
            fs = new ShaderSource({
                sources : [CompositeOITFS]
            });
            if (this._translucentMRTSupport) {
                fs.defines.push('MRT');
            }

            uniformMap = {
                u_opaque : function() {
                    return that._opaqueTexture;
                },
                u_accumulation : function() {
                    return that._accumulationTexture;
                },
                u_revealage : function() {
                    return that._revealageTexture;
                }
            };
            this._compositeCommand = context.createViewportQuadCommand(fs, {
                uniformMap : uniformMap,
                owner : this
            });
        }

        if (!defined(this._adjustTranslucentCommand)) {
            if (this._translucentMRTSupport) {
                fs = new ShaderSource({
                    defines : ['MRT'],
                    sources : [AdjustTranslucentFS]
                });

                uniformMap = {
                    u_bgColor : function() {
                        return that._translucentMRTClearCommand.color;
                    },
                    u_depthTexture : function() {
                        return that._depthStencilTexture;
                    }
                };

                this._adjustTranslucentCommand = context.createViewportQuadCommand(fs, {
                    uniformMap : uniformMap,
                    owner : this
                });
            } else if (this._translucentMultipassSupport) {
                fs = new ShaderSource({
                    sources : [AdjustTranslucentFS]
                });

                uniformMap = {
                    u_bgColor : function() {
                        return that._translucentMultipassClearCommand.color;
                    },
                    u_depthTexture : function() {
                        return that._depthStencilTexture;
                    }
                };

                this._adjustTranslucentCommand = context.createViewportQuadCommand(fs, {
                    uniformMap : uniformMap,
                    owner : this
                });

                uniformMap = {
                    u_bgColor : function() {
                        return that._alphaClearCommand.color;
                    },
                    u_depthTexture : function() {
                        return that._depthStencilTexture;
                    }
                };

                this._adjustAlphaCommand = context.createViewportQuadCommand(fs, {
                    uniformMap : uniformMap,
                    owner : this
                });
            }
        }

        this._viewport.width = width;
        this._viewport.height = height;

        if (!defined(this._rs) || !BoundingRectangle.equals(this._viewport, this._rs.viewport)) {
            this._rs = RenderState.fromCache({
                viewport : this._viewport
            });
        }

        if (defined(this._compositeCommand)) {
            this._compositeCommand.renderState = this._rs;
        }

        if (this._adjustTranslucentCommand) {
            this._adjustTranslucentCommand.renderState = this._rs;
        }

        if (defined(this._adjustAlphaCommand)) {
            this._adjustAlphaCommand.renderState = this._rs;
        }
    };

    var translucentMRTBlend = {
        enabled : true,
        color : new Color(0.0, 0.0, 0.0, 0.0),
        equationRgb : BlendEquation.ADD,
        equationAlpha : BlendEquation.ADD,
        functionSourceRgb : BlendFunction.ONE,
        functionDestinationRgb : BlendFunction.ONE,
        functionSourceAlpha : BlendFunction.ZERO,
        functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
    };

    var translucentColorBlend = {
        enabled : true,
        color : new Color(0.0, 0.0, 0.0, 0.0),
        equationRgb : BlendEquation.ADD,
        equationAlpha : BlendEquation.ADD,
        functionSourceRgb : BlendFunction.ONE,
        functionDestinationRgb : BlendFunction.ONE,
        functionSourceAlpha : BlendFunction.ONE,
        functionDestinationAlpha : BlendFunction.ONE
    };

    var translucentAlphaBlend = {
        enabled : true,
        color : new Color(0.0, 0.0, 0.0, 0.0),
        equationRgb : BlendEquation.ADD,
        equationAlpha : BlendEquation.ADD,
        functionSourceRgb : BlendFunction.ZERO,
        functionDestinationRgb : BlendFunction.ONE_MINUS_SOURCE_ALPHA,
        functionSourceAlpha : BlendFunction.ZERO,
        functionDestinationAlpha : BlendFunction.ONE_MINUS_SOURCE_ALPHA
    };

    function getTranslucentRenderState(context, translucentBlending, cache, renderState) {
        var translucentState = cache[renderState.id];
        if (!defined(translucentState)) {
            var rs = RenderState.getState(renderState);
            rs.depthMask = false;
            rs.blending = translucentBlending;

            translucentState = RenderState.fromCache(rs);
            cache[renderState.id] = translucentState;
        }

        return translucentState;
    }

    function getTranslucentMRTRenderState(oit, context, renderState) {
        return getTranslucentRenderState(context, translucentMRTBlend, oit._translucentRenderStateCache, renderState);
    }

    function getTranslucentColorRenderState(oit, context, renderState) {
        return getTranslucentRenderState(context, translucentColorBlend, oit._translucentRenderStateCache, renderState);
    }

    function getTranslucentAlphaRenderState(oit, context, renderState) {
        return getTranslucentRenderState(context, translucentAlphaBlend, oit._alphaRenderStateCache, renderState);
    }

    var mrtShaderSource =
        '    vec3 Ci = czm_gl_FragColor.rgb * czm_gl_FragColor.a;\n' +
        '    float ai = czm_gl_FragColor.a;\n' +
        '    float wzi = czm_alphaWeight(ai);\n' +
        '    gl_FragData[0] = vec4(Ci * wzi, ai);\n' +
        '    gl_FragData[1] = vec4(ai * wzi);\n';

    var colorShaderSource =
        '    vec3 Ci = czm_gl_FragColor.rgb * czm_gl_FragColor.a;\n' +
        '    float ai = czm_gl_FragColor.a;\n' +
        '    float wzi = czm_alphaWeight(ai);\n' +
        '    gl_FragColor = vec4(Ci, ai) * wzi;\n';

    var alphaShaderSource =
        '    float ai = czm_gl_FragColor.a;\n' +
        '    gl_FragColor = vec4(ai);\n';

    function getTranslucentShaderProgram(context, shaderProgram, keyword, source) {
        var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
        if (!defined(shader)) {
            var attributeLocations = shaderProgram._attributeLocations;

            var fs = shaderProgram.fragmentShaderSource.clone();

            fs.sources = fs.sources.map(function(source) {
                source = ShaderSource.replaceMain(source, 'czm_translucent_main');
                source = source.replace(/gl_FragColor/g, 'czm_gl_FragColor');
                source = source.replace(/\bdiscard\b/g, 'czm_discard = true');
                source = source.replace(/czm_phong/g, 'czm_translucentPhong');
                return source;
            });

            // Discarding the fragment in main is a workaround for ANGLE D3D9
            // shader compilation errors.

            fs.sources.splice(0, 0,
                    (source.indexOf('gl_FragData') !== -1 ? '#extension GL_EXT_draw_buffers : enable \n' : '') +
                    'vec4 czm_gl_FragColor;\n' +
                    'bool czm_discard = false;\n');

            fs.sources.push(
                    'void main()\n' +
                    '{\n' +
                    '    czm_translucent_main();\n' +
                    '    if (czm_discard)\n' +
                    '    {\n' +
                    '        discard;\n' +
                    '    }\n' +
                    source +
                    '}\n');

            shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
                vertexShaderSource : shaderProgram.vertexShaderSource,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
        }

        return shader;
    }

    function getTranslucentMRTShaderProgram(context, shaderProgram) {
        return getTranslucentShaderProgram(context, shaderProgram, 'translucentMRT', mrtShaderSource);
    }

    function getTranslucentColorShaderProgram(context, shaderProgram) {
        return getTranslucentShaderProgram(context, shaderProgram, 'translucentMultipass', colorShaderSource);
    }

    function getTranslucentAlphaShaderProgram(context, shaderProgram) {
        return getTranslucentShaderProgram(context, shaderProgram, 'alphaMultipass', alphaShaderSource);
    }

    OIT.prototype.createDerivedCommands = function(command, context, result) {
        if (!defined(result)) {
            result = {};
        }

        if (this._translucentMRTSupport) {
            var translucentShader;
            var translucentRenderState;
            if (defined(result.translucentCommand)) {
                translucentShader = result.translucentCommand.shaderProgram;
                translucentRenderState = result.translucentCommand.renderState;
            }

            result.translucentCommand = DrawCommand.shallowClone(command, result.translucentCommand);

            if (!defined(translucentShader) || result.shaderProgramId !== command.shaderProgram.id) {
                result.translucentCommand.shaderProgram = getTranslucentMRTShaderProgram(context, command.shaderProgram);
                result.translucentCommand.renderState = getTranslucentMRTRenderState(this, context, command.renderState);
                result.shaderProgramId = command.shaderProgram.id;
            } else {
                result.translucentCommand.shaderProgram = translucentShader;
                result.translucentCommand.renderState = translucentRenderState;
            }
        } else {
            var colorShader;
            var colorRenderState;
            var alphaShader;
            var alphaRenderState;
            if (defined(result.translucentCommand)) {
                colorShader = result.translucentCommand.shaderProgram;
                colorRenderState = result.translucentCommand.renderState;
                alphaShader = result.alphaCommand.shaderProgram;
                alphaRenderState = result.alphaCommand.renderState;
            }

            result.translucentCommand = DrawCommand.shallowClone(command, result.translucentCommand);
            result.alphaCommand = DrawCommand.shallowClone(command, result.alphaCommand);

            if (!defined(colorShader) || result.shaderProgramId !== command.shaderProgram.id) {
                result.translucentCommand.shaderProgram = getTranslucentColorShaderProgram(context, command.shaderProgram);
                result.translucentCommand.renderState = getTranslucentColorRenderState(this, context, command.renderState);
                result.alphaCommand.shaderProgram = getTranslucentAlphaShaderProgram(context, command.shaderProgram);
                result.alphaCommand.renderState = getTranslucentAlphaRenderState(this, context, command.renderState);
                result.shaderProgramId = command.shaderProgram.id;
            } else {
                result.translucentCommand.shaderProgram = colorShader;
                result.translucentCommand.renderState = colorRenderState;
                result.alphaCommand.shaderProgram = alphaShader;
                result.alphaCommand.renderState = alphaRenderState;
            }
        }

        return result;
    };

    function executeTranslucentCommandsSortedMultipass(oit, scene, executeFunction, passState, commands) {
        var command;
        var derivedCommand;
        var j;

        var context = scene.context;
        var framebuffer = passState.framebuffer;
        var length = commands.length;

        var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled;

        passState.framebuffer = oit._adjustTranslucentFBO;
        oit._adjustTranslucentCommand.execute(context, passState);
        passState.framebuffer = oit._adjustAlphaFBO;
        oit._adjustAlphaCommand.execute(context, passState);

        var debugFramebuffer = oit._opaqueFBO;
        passState.framebuffer = oit._translucentFBO;

        for (j = 0; j < length; ++j) {
            command = commands[j];
            derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand;
            executeFunction(derivedCommand, scene, context, passState, debugFramebuffer);
        }

        passState.framebuffer = oit._alphaFBO;

        for (j = 0; j < length; ++j) {
            command = commands[j];
            derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand;
            executeFunction(derivedCommand, scene, context, passState, debugFramebuffer);
        }

        passState.framebuffer = framebuffer;
    }

    function executeTranslucentCommandsSortedMRT(oit, scene, executeFunction, passState, commands) {
        var context = scene.context;
        var framebuffer = passState.framebuffer;
        var length = commands.length;

        var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled;

        passState.framebuffer = oit._adjustTranslucentFBO;
        oit._adjustTranslucentCommand.execute(context, passState);

        var debugFramebuffer = oit._opaqueFBO;
        passState.framebuffer = oit._translucentFBO;

        for (var j = 0; j < length; ++j) {
            var command = commands[j];
            var derivedCommand = (shadowsEnabled && command.receiveShadows) ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand;
            executeFunction(derivedCommand, scene, context, passState, debugFramebuffer);
        }

        passState.framebuffer = framebuffer;
    }

    OIT.prototype.executeCommands = function(scene, executeFunction, passState, commands) {
        if (this._translucentMRTSupport) {
            executeTranslucentCommandsSortedMRT(this, scene, executeFunction, passState, commands);
            return;
        }

        executeTranslucentCommandsSortedMultipass(this, scene, executeFunction, passState, commands);
    };

    OIT.prototype.execute = function(context, passState) {
        this._compositeCommand.execute(context, passState);
    };

    OIT.prototype.clear = function(context, passState, clearColor) {
        var framebuffer = passState.framebuffer;

        passState.framebuffer = this._opaqueFBO;
        Color.clone(clearColor, this._opaqueClearCommand.color);
        this._opaqueClearCommand.execute(context, passState);

        passState.framebuffer = this._translucentFBO;
        var translucentClearCommand = this._translucentMRTSupport ? this._translucentMRTClearCommand : this._translucentMultipassClearCommand;
        translucentClearCommand.execute(context, passState);

        if (this._translucentMultipassSupport) {
            passState.framebuffer = this._alphaFBO;
            this._alphaClearCommand.execute(context, passState);
        }

        passState.framebuffer = framebuffer;
    };

    OIT.prototype.isSupported = function() {
        return this._translucentMRTSupport || this._translucentMultipassSupport;
    };

    OIT.prototype.isDestroyed = function() {
        return false;
    };

    OIT.prototype.destroy = function() {
        destroyResources(this);

        if (defined(this._compositeCommand)) {
            this._compositeCommand.shaderProgram = this._compositeCommand.shaderProgram && this._compositeCommand.shaderProgram.destroy();
        }

        if (defined(this._adjustTranslucentCommand)) {
            this._adjustTranslucentCommand.shaderProgram = this._adjustTranslucentCommand.shaderProgram && this._adjustTranslucentCommand.shaderProgram.destroy();
        }

        if (defined(this._adjustAlphaCommand)) {
            this._adjustAlphaCommand.shaderProgram = this._adjustAlphaCommand.shaderProgram && this._adjustAlphaCommand.shaderProgram.destroy();
        }

        return destroyObject(this);
    };

    return OIT;
});

/*global define*/
define('Scene/OrthographicFrustum',[
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Matrix4',
        './CullingVolume'
    ], function(
        Cartesian3,
        Cartesian4,
        defined,
        defineProperties,
        DeveloperError,
        Matrix4,
        CullingVolume) {
    'use strict';

    /**
     * The viewing frustum is defined by 6 planes.
     * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components
     * define the unit vector normal to the plane, and the w component is the distance of the
     * plane from the origin/camera position.
     *
     * @alias OrthographicFrustum
     * @constructor
     *
     * @example
     * var maxRadii = ellipsoid.maximumRadius;
     *
     * var frustum = new Cesium.OrthographicFrustum();
     * frustum.right = maxRadii * Cesium.Math.PI;
     * frustum.left = -c.frustum.right;
     * frustum.top = c.frustum.right * (canvas.clientHeight / canvas.clientWidth);
     * frustum.bottom = -c.frustum.top;
     * frustum.near = 0.01 * maxRadii;
     * frustum.far = 50.0 * maxRadii;
     */
    function OrthographicFrustum() {
        /**
         * The left clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.left = undefined;
        this._left = undefined;

        /**
         * The right clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.right = undefined;
        this._right = undefined;

        /**
         * The top clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.top = undefined;
        this._top = undefined;

        /**
         * The bottom clipping plane.
         * @type {Number}
         * @default undefined
         */
        this.bottom = undefined;
        this._bottom = undefined;

        /**
         * The distance of the near plane.
         * @type {Number}
         * @default 1.0
         */
        this.near = 1.0;
        this._near = this.near;

        /**
         * The distance of the far plane.
         * @type {Number}
         * @default 500000000.0;
         */
        this.far = 500000000.0;
        this._far = this.far;

        this._cullingVolume = new CullingVolume();
        this._orthographicMatrix = new Matrix4();
    }

    function update(frustum) {
                if (!defined(frustum.right) || !defined(frustum.left) ||
            !defined(frustum.top) || !defined(frustum.bottom) ||
            !defined(frustum.near) || !defined(frustum.far)) {
            throw new DeveloperError('right, left, top, bottom, near, or far parameters are not set.');
        }
        
        if (frustum.top !== frustum._top || frustum.bottom !== frustum._bottom ||
                frustum.left !== frustum._left || frustum.right !== frustum._right ||
                frustum.near !== frustum._near || frustum.far !== frustum._far) {

                        if (frustum.left > frustum.right) {
                throw new DeveloperError('right must be greater than left.');
            }
            if (frustum.bottom > frustum.top) {
                throw new DeveloperError('top must be greater than bottom.');
            }
            if (frustum.near <= 0 || frustum.near > frustum.far) {
                throw new DeveloperError('near must be greater than zero and less than far.');
            }
            
            frustum._left = frustum.left;
            frustum._right = frustum.right;
            frustum._top = frustum.top;
            frustum._bottom = frustum.bottom;
            frustum._near = frustum.near;
            frustum._far = frustum.far;
            frustum._orthographicMatrix = Matrix4.computeOrthographicOffCenter(frustum.left, frustum.right, frustum.bottom, frustum.top, frustum.near, frustum.far, frustum._orthographicMatrix);
        }
    }

    defineProperties(OrthographicFrustum.prototype, {
        /**
         * Gets the orthographic projection matrix computed from the view frustum.
         * @memberof OrthographicFrustum.prototype
         * @type {Matrix4}
         * @readonly
         */
        projectionMatrix : {
            get : function() {
                update(this);
                return this._orthographicMatrix;
            }
        }
    });

    var getPlanesRight = new Cartesian3();
    var getPlanesNearCenter = new Cartesian3();
    var getPlanesPoint = new Cartesian3();
    var negateScratch = new Cartesian3();

    /**
     * Creates a culling volume for this frustum.
     *
     * @param {Cartesian3} position The eye position.
     * @param {Cartesian3} direction The view direction.
     * @param {Cartesian3} up The up direction.
     * @returns {CullingVolume} A culling volume at the given position and orientation.
     *
     * @example
     * // Check if a bounding volume intersects the frustum.
     * var cullingVolume = frustum.computeCullingVolume(cameraPosition, cameraDirection, cameraUp);
     * var intersect = cullingVolume.computeVisibility(boundingVolume);
     */
    OrthographicFrustum.prototype.computeCullingVolume = function(position, direction, up) {
                if (!defined(position)) {
            throw new DeveloperError('position is required.');
        }
        if (!defined(direction)) {
            throw new DeveloperError('direction is required.');
        }
        if (!defined(up)) {
            throw new DeveloperError('up is required.');
        }
        
        var planes = this._cullingVolume.planes;
        var t = this.top;
        var b = this.bottom;
        var r = this.right;
        var l = this.left;
        var n = this.near;
        var f = this.far;

        var right = Cartesian3.cross(direction, up, getPlanesRight);
        var nearCenter = getPlanesNearCenter;
        Cartesian3.multiplyByScalar(direction, n, nearCenter);
        Cartesian3.add(position, nearCenter, nearCenter);

        var point = getPlanesPoint;

        // Left plane
        Cartesian3.multiplyByScalar(right, l, point);
        Cartesian3.add(nearCenter, point, point);

        var plane = planes[0];
        if (!defined(plane)) {
            plane = planes[0] = new Cartesian4();
        }
        plane.x = right.x;
        plane.y = right.y;
        plane.z = right.z;
        plane.w = -Cartesian3.dot(right, point);

        // Right plane
        Cartesian3.multiplyByScalar(right, r, point);
        Cartesian3.add(nearCenter, point, point);

        plane = planes[1];
        if (!defined(plane)) {
            plane = planes[1] = new Cartesian4();
        }
        plane.x = -right.x;
        plane.y = -right.y;
        plane.z = -right.z;
        plane.w = -Cartesian3.dot(Cartesian3.negate(right, negateScratch), point);

        // Bottom plane
        Cartesian3.multiplyByScalar(up, b, point);
        Cartesian3.add(nearCenter, point, point);

        plane = planes[2];
        if (!defined(plane)) {
            plane = planes[2] = new Cartesian4();
        }
        plane.x = up.x;
        plane.y = up.y;
        plane.z = up.z;
        plane.w = -Cartesian3.dot(up, point);

        // Top plane
        Cartesian3.multiplyByScalar(up, t, point);
        Cartesian3.add(nearCenter, point, point);

        plane = planes[3];
        if (!defined(plane)) {
            plane = planes[3] = new Cartesian4();
        }
        plane.x = -up.x;
        plane.y = -up.y;
        plane.z = -up.z;
        plane.w = -Cartesian3.dot(Cartesian3.negate(up, negateScratch), point);

        // Near plane
        plane = planes[4];
        if (!defined(plane)) {
            plane = planes[4] = new Cartesian4();
        }
        plane.x = direction.x;
        plane.y = direction.y;
        plane.z = direction.z;
        plane.w = -Cartesian3.dot(direction, nearCenter);

        // Far plane
        Cartesian3.multiplyByScalar(direction, f, point);
        Cartesian3.add(position, point, point);

        plane = planes[5];
        if (!defined(plane)) {
            plane = planes[5] = new Cartesian4();
        }
        plane.x = -direction.x;
        plane.y = -direction.y;
        plane.z = -direction.z;
        plane.w = -Cartesian3.dot(Cartesian3.negate(direction, negateScratch), point);

        return this._cullingVolume;
    };

    /**
     * Returns the pixel's width and height in meters.
     *
     * @param {Number} drawingBufferWidth The width of the drawing buffer.
     * @param {Number} drawingBufferHeight The height of the drawing buffer.
     * @param {Number} distance The distance to the near plane in meters.
     * @param {Cartesian2} result The object onto which to store the result.
     * @returns {Cartesian2} The modified result parameter or a new instance of {@link Cartesian2} with the pixel's width and height in the x and y properties, respectively.
     *
     * @exception {DeveloperError} drawingBufferWidth must be greater than zero.
     * @exception {DeveloperError} drawingBufferHeight must be greater than zero.
     *
     * @example
     * // Example 1
     * // Get the width and height of a pixel.
     * var pixelSize = camera.frustum.getPixelDimensions(scene.drawingBufferWidth, scene.drawingBufferHeight, 0.0, new Cesium.Cartesian2());
     */
    OrthographicFrustum.prototype.getPixelDimensions = function(drawingBufferWidth, drawingBufferHeight, distance, result) {
        update(this);

                if (!defined(drawingBufferWidth) || !defined(drawingBufferHeight)) {
            throw new DeveloperError('Both drawingBufferWidth and drawingBufferHeight are required.');
        }
        if (drawingBufferWidth <= 0) {
            throw new DeveloperError('drawingBufferWidth must be greater than zero.');
        }
        if (drawingBufferHeight <= 0) {
            throw new DeveloperError('drawingBufferHeight must be greater than zero.');
        }
        if (!defined(distance)) {
            throw new DeveloperError('distance is required.');
        }
        if (!defined(result)) {
            throw new DeveloperError('A result object is required.');
        }
        
        var frustumWidth = this.right - this.left;
        var frustumHeight = this.top - this.bottom;
        var pixelWidth = frustumWidth / drawingBufferWidth;
        var pixelHeight = frustumHeight / drawingBufferHeight;

        result.x = pixelWidth;
        result.y = pixelHeight;
        return result;
    };

    /**
     * Returns a duplicate of a OrthographicFrustum instance.
     *
     * @param {OrthographicFrustum} [result] The object onto which to store the result.
     * @returns {OrthographicFrustum} The modified result parameter or a new PerspectiveFrustum instance if one was not provided.
     */
    OrthographicFrustum.prototype.clone = function(result) {
        if (!defined(result)) {
            result = new OrthographicFrustum();
        }

        result.left = this.left;
        result.right = this.right;
        result.top = this.top;
        result.bottom = this.bottom;
        result.near = this.near;
        result.far = this.far;

        // force update of clone to compute matrices
        result._left = undefined;
        result._right = undefined;
        result._top = undefined;
        result._bottom = undefined;
        result._near = undefined;
        result._far = undefined;

        return result;
    };

    /**
     * Compares the provided OrthographicFrustum componentwise and returns
     * <code>true</code> if they are equal, <code>false</code> otherwise.
     *
     * @param {OrthographicFrustum} [other] The right hand side OrthographicFrustum.
     * @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
     */
    OrthographicFrustum.prototype.equals = function(other) {
        return (defined(other) &&
                this.right === other.right &&
                this.left === other.left &&
                this.top === other.top &&
                this.bottom === other.bottom &&
                this.near === other.near &&
                this.far === other.far);
    };

    return OrthographicFrustum;
});

/*global define*/
define('Widgets/getElement',[
        '../Core/DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * If element is a string, look up the element in the DOM by ID.  Otherwise return element.
     *
     * @private
     *
     * @exception {DeveloperError} Element with id "id" does not exist in the document.
     */
    function getElement(element) {
        if (typeof element === 'string') {
            var foundElement = document.getElementById(element);

                        if (foundElement === null) {
                throw new DeveloperError('Element with id "' + element + '" does not exist in the document.');
            }
            
            element = foundElement;
        }
        return element;
    }

    return getElement;
});

/*global define*/
define('Scene/PerformanceDisplay',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/getTimestamp',
        '../Widgets/getElement'
    ], function(
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        getTimestamp,
        getElement) {
    'use strict';

    /**
     * @private
     */
    function PerformanceDisplay(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var container = getElement(options.container);
                if (!defined(container)) {
            throw new DeveloperError('container is required');
        }
        
        this._container = container;

        var display = document.createElement('div');
        display.className = 'cesium-performanceDisplay';
        var fpsElement = document.createElement('div');
        fpsElement.className = 'cesium-performanceDisplay-fps';
        this._fpsText = document.createTextNode("");
        fpsElement.appendChild(this._fpsText);
        var msElement = document.createElement('div');
        msElement.className = 'cesium-performanceDisplay-ms';
        this._msText = document.createTextNode("");
        msElement.appendChild(this._msText);
        display.appendChild(msElement);
        display.appendChild(fpsElement);
        this._container.appendChild(display);

        this._lastFpsSampleTime = undefined;
        this._frameCount = 0;
        this._time = undefined;
        this._fps = 0;
        this._frameTime = 0;
    }

    /**
     * Update the display.  This function should only be called once per frame, because
     * each call records a frame in the internal buffer and redraws the display.
     */
    PerformanceDisplay.prototype.update = function() {
        if (!defined(this._time)) {
            //first update
            this._lastFpsSampleTime = getTimestamp();
            this._time = getTimestamp();
            return;
        }

        var previousTime = this._time;
        var time = getTimestamp();
        this._time = time;

        var frameTime = time - previousTime;

        this._frameCount++;
        var fps = this._fps;
        var fpsElapsedTime = time - this._lastFpsSampleTime;
        if (fpsElapsedTime > 1000) {
            fps = this._frameCount * 1000 / fpsElapsedTime | 0;

            this._lastFpsSampleTime = time;
            this._frameCount = 0;
        }

        if (fps !== this._fps) {
            this._fpsText.nodeValue = fps + ' FPS';
            this._fps = fps;
        }

        if (frameTime !== this._frameTime) {
            this._msText.nodeValue = frameTime.toFixed(2) + ' MS';
            this._frameTime = frameTime;
        }

    };

    /**
     * Destroys the WebGL resources held by this object.
     */
    PerformanceDisplay.prototype.destroy = function() {
        return destroyObject(this);
    };

    return PerformanceDisplay;
});

/*global define*/
define('Scene/PickDepth',[
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/PixelFormat',
        '../Renderer/Framebuffer',
        '../Renderer/PixelDatatype',
        '../Renderer/RenderState',
        '../Renderer/Texture'
    ], function(
        defined,
        destroyObject,
        PixelFormat,
        Framebuffer,
        PixelDatatype,
        RenderState,
        Texture) {
    'use strict';

    /**
     * @private
     */
    function PickDepth() {
        this.framebuffer = undefined;

        this._depthTexture = undefined;
        this._textureToCopy = undefined;
        this._copyDepthCommand = undefined;

        this._debugPickDepthViewportCommand = undefined;
    }

    function executeDebugPickDepth(pickDepth, context, passState) {
        if (!defined(pickDepth._debugPickDepthViewportCommand)) {
            var fs =
                'uniform sampler2D u_texture;\n' +
                'varying vec2 v_textureCoordinates;\n' +
                'void main()\n' +
                '{\n' +
                '    float z_window = czm_unpackDepth(texture2D(u_texture, v_textureCoordinates));\n' +
                '    float n_range = czm_depthRange.near;\n' +
                '    float f_range = czm_depthRange.far;\n' +
                '    float z_ndc = (2.0 * z_window - n_range - f_range) / (f_range - n_range);\n' +
                '    float scale = pow(z_ndc * 0.5 + 0.5, 8.0);\n' +
                '    gl_FragColor = vec4(mix(vec3(0.0), vec3(1.0), scale), 1.0);\n' +
                '}\n';

            pickDepth._debugPickDepthViewportCommand = context.createViewportQuadCommand(fs, {
                uniformMap : {
                    u_texture : function() {
                        return pickDepth._depthTexture;
                    }
                },
                owner : pickDepth
            });
        }

        pickDepth._debugPickDepthViewportCommand.execute(context, passState);
    }

    function destroyTextures(pickDepth) {
        pickDepth._depthTexture = pickDepth._depthTexture && !pickDepth._depthTexture.isDestroyed() && pickDepth._depthTexture.destroy();
    }

    function destroyFramebuffers(pickDepth) {
        pickDepth.framebuffer = pickDepth.framebuffer && !pickDepth.framebuffer.isDestroyed() && pickDepth.framebuffer.destroy();
    }

    function createTextures(pickDepth, context, width, height) {
        pickDepth._depthTexture = new Texture({
            context : context,
            width : width,
            height : height,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE
        });
    }

    function createFramebuffers(pickDepth, context, width, height) {
        destroyTextures(pickDepth);
        destroyFramebuffers(pickDepth);

        createTextures(pickDepth, context, width, height);

        pickDepth.framebuffer = new Framebuffer({
            context : context,
            colorTextures : [pickDepth._depthTexture],
            destroyAttachments : false
        });
    }

    function updateFramebuffers(pickDepth, context, depthTexture) {
        var width = depthTexture.width;
        var height = depthTexture.height;

        var texture = pickDepth._depthTexture;
        var textureChanged = !defined(texture) || texture.width !== width || texture.height !== height;
        if (!defined(pickDepth.framebuffer) || textureChanged) {
            createFramebuffers(pickDepth, context, width, height);
        }
    }

    function updateCopyCommands(pickDepth, context, depthTexture) {
        if (!defined(pickDepth._copyDepthCommand)) {
            var fs =
                'uniform sampler2D u_texture;\n' +
                'varying vec2 v_textureCoordinates;\n' +
                'void main()\n' +
                '{\n' +
                '    gl_FragColor = czm_packDepth(texture2D(u_texture, v_textureCoordinates).r);\n' +
                '}\n';
            pickDepth._copyDepthCommand = context.createViewportQuadCommand(fs, {
                renderState : RenderState.fromCache(),
                uniformMap : {
                    u_texture : function() {
                        return pickDepth._textureToCopy;
                    }
                },
                owner : pickDepth
            });
        }

        pickDepth._textureToCopy = depthTexture;
        pickDepth._copyDepthCommand.framebuffer = pickDepth.framebuffer;
    }

    PickDepth.prototype.executeDebugPickDepth = function(context, passState) {
        executeDebugPickDepth(this, context, passState);
    };

    PickDepth.prototype.update = function(context, depthTexture) {
        updateFramebuffers(this, context, depthTexture);
        updateCopyCommands(this, context, depthTexture);
    };

    PickDepth.prototype.executeCopyDepth = function(context, passState) {
        this._copyDepthCommand.execute(context, passState);
    };

    PickDepth.prototype.isDestroyed = function() {
        return false;
    };

    PickDepth.prototype.destroy = function() {
        destroyTextures(this);
        destroyFramebuffers(this);

        this._copyDepthCommand.shaderProgram = defined(this._copyDepthCommand.shaderProgram) && this._copyDepthCommand.shaderProgram.destroy();

        return destroyObject(this);
    };

    return PickDepth;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PointAppearanceFS',[],function() {
    'use strict';
    return "uniform vec4 highlightColor;\n\
\n\
varying vec3 v_color;\n\
\n\
void main()\n\
{\n\
    gl_FragColor = vec4(v_color * highlightColor.rgb, highlightColor.a);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/Appearances/PointAppearanceVS',[],function() {
    'use strict';
    return "attribute vec3 position3DHigh;\n\
attribute vec3 position3DLow;\n\
attribute vec3 color;\n\
attribute float batchId;\n\
\n\
uniform float pointSize;\n\
\n\
varying vec3 v_positionEC;\n\
varying vec3 v_color;\n\
\n\
void main() \n\
{\n\
    v_color = color;\n\
    gl_Position = czm_modelViewProjectionRelativeToEye * czm_computePosition();\n\
    gl_PointSize = pointSize;\n\
}\n\
";
});
/*global define*/
define('Scene/PointAppearance',[
        '../Core/Color',
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/VertexFormat',
        '../Shaders/Appearances/PointAppearanceFS',
        '../Shaders/Appearances/PointAppearanceVS',
        './Appearance'
    ], function(
        Color,
        combine,
        defaultValue,
        defined,
        defineProperties,
        VertexFormat,
        PointAppearanceFS,
        PointAppearanceVS,
        Appearance) {
    'use strict';

    /**
     * An appearance for point geometry {@link PointGeometry}
     *
     * @alias PointAppearance
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.translucent=false] When <code>true</code>, the geometry is expected to appear translucent so {@link PointAppearance#renderState} has alpha blending enabled.
     * @param {String} [options.vertexShaderSource] Optional GLSL vertex shader source to override the default vertex shader.
     * @param {String} [options.fragmentShaderSource] Optional GLSL fragment shader source to override the default fragment shader.
     * @param {RenderState} [options.renderState] Optional render state to override the default render state.
     * @param {Object} [options.uniforms] Additional uniforms that are used by the vertex and fragment shaders.
     * @param {Number} [options.pointSize] Point size in pixels.
     * @param {Color} [options.highlightColor] Color multiplier in the fragment shader. The alpha channel is used for alpha blending when translucency is enabled.
     *
     * @example
     * var primitive = new Cesium.Primitive({
     *   geometryInstances : new Cesium.GeometryInstance({
     *     geometry : new Cesium.PointGeometry({
     *       positionsTypedArray : positions,
     *       colorsTypedArray : colors,
     *       boundingSphere : boundingSphere
     *     })
     *   }),
     *   appearance : new Cesium.PointAppearance({
     *     translucent : true
     *   })
     * });
     *
     * @private
     */
    function PointAppearance(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._vertexShaderSource = defaultValue(options.vertexShaderSource, PointAppearanceVS);
        this._fragmentShaderSource = defaultValue(options.fragmentShaderSource, PointAppearanceFS);
        this._renderState = Appearance.getDefaultRenderState(false, false, options.renderState);
        this._pointSize = defaultValue(options.pointSize, 2.0);
        this._highlightColor = defined(options.highlightColor) ? options.highlightColor : new Color();

        /**
         * This property is part of the {@link Appearance} interface, but is not
         * used by {@link PointAppearance} since a fully custom fragment shader is used.
         *
         * @type Material
         *
         * @default undefined
         */
        this.material = undefined;

        /**
         * When <code>true</code>, the geometry is expected to appear translucent.
         *
         * @type {Boolean}
         *
         * @default false
         */
        this.translucent = defaultValue(options.translucent, false);

        /**
         * @private
         */
        this.uniforms = {
            highlightColor : this._highlightColor,
            pointSize : this._pointSize
        };

        // Combine default uniforms and additional uniforms
        var optionsUniforms = options.uniforms;
        this.uniforms = combine(this.uniforms, optionsUniforms, true);
    }

    defineProperties(PointAppearance.prototype, {
        /**
         * The GLSL source code for the vertex shader.
         *
         * @memberof PointAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        vertexShaderSource : {
            get : function() {
                return this._vertexShaderSource;
            }
        },

        /**
         * The GLSL source code for the fragment shader.  The full fragment shader
         * source is built procedurally taking into account the {@link PointAppearance#material}.
         * Use {@link PointAppearance#getFragmentShaderSource} to get the full source.
         *
         * @memberof PointAppearance.prototype
         *
         * @type {String}
         * @readonly
         */
        fragmentShaderSource : {
            get : function() {
                return this._fragmentShaderSource;
            }
        },

        /**
         * The WebGL fixed-function state to use when rendering the geometry.
         *
         * @memberof PointAppearance.prototype
         *
         * @type {Object}
         * @readonly
         */
        renderState : {
            get : function() {
                return this._renderState;
            }
        },

        /**
         * When <code>true</code>, the geometry is expected to be closed.
         *
         * @memberof PointAppearance.prototype
         *
         * @type {Boolean}
         * @readonly
         *
         * @default false
         */
        closed : {
            get : function() {
                return false;
            }
        },

        /**
         * The {@link VertexFormat} that this appearance instance is compatible with.
         * A geometry can have more vertex attributes and still be compatible - at a
         * potential performance cost - but it can't have less.
         *
         * @memberof PointAppearance.prototype
         *
         * @type VertexFormat
         * @readonly
         *
         * @default {@link PointAppearance.VERTEX_FORMAT}
         */
        vertexFormat : {
            get : function() {
                return PointAppearance.VERTEX_FORMAT;
            }
        },

        /**
         * The size in pixels used when rendering the primitive. This helps calculate an accurate
         * bounding volume for point rendering and other appearances that are defined in pixel sizes.
         *
         * @memberof PointAppearance.prototype
         *
         * @type {Number}
         * @readonly
         */
        pixelSize : {
            get : function() {
                return this._pointSize;
            }
        }
    });

    /**
     * The {@link VertexFormat} that all {@link PointAppearance} instances
     * are compatible with, which requires only <code>position</code> and <code>color</code>
     * attributes.  Other attributes are procedurally computed in the fragment shader.
     *
     * @type VertexFormat
     *
     * @constant
     */
    PointAppearance.VERTEX_FORMAT = VertexFormat.POSITION_AND_COLOR;

    /**
     * Returns the full GLSL fragment shader source, which for {@link PointAppearance} is just
     * {@link PointAppearance#fragmentShaderSource}.
     *
     * @function
     *
     * @returns {String} The full GLSL fragment shader source.
     */
    PointAppearance.prototype.getFragmentShaderSource = Appearance.prototype.getFragmentShaderSource;

    /**
     * Determines if the geometry is translucent based on {@link PointAppearance#translucent}.
     *
     * @function
     *
     * @returns {Boolean} <code>true</code> if the appearance is translucent.
     */
    PointAppearance.prototype.isTranslucent = Appearance.prototype.isTranslucent;

    /**
     * Creates a render state.  This is not the final render state instance; instead,
     * it can contain a subset of render state properties identical to the render state
     * created in the context.
     *
     * @function
     *
     * @returns {Object} The render state.
     */
    PointAppearance.prototype.getRenderState = Appearance.prototype.getRenderState;

    return PointAppearance;
});

/*global define*/
define('Scene/PrimitiveCollection',[
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError'
    ], function(
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError) {
    'use strict';

    /**
     * A collection of primitives.  This is most often used with {@link Scene#primitives},
     * but <code>PrimitiveCollection</code> is also a primitive itself so collections can
     * be added to collections forming a hierarchy.
     *
     * @alias PrimitiveCollection
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.show=true] Determines if the primitives in the collection will be shown.
     * @param {Boolean} [options.destroyPrimitives=true] Determines if primitives in the collection are destroyed when they are removed.
     *
     * @example
     * var billboards = new Cesium.BillboardCollection();
     * var labels = new Cesium.LabelCollection();
     *
     * var collection = new Cesium.PrimitiveCollection();
     * collection.add(billboards);
     *
     * scene.primitives.add(collection);  // Add collection
     * scene.primitives.add(labels);      // Add regular primitive
     */
    function PrimitiveCollection(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._primitives = [];
        this._guid = createGuid();

        /**
         * Determines if primitives in this collection will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = defaultValue(options.show, true);

        /**
         * Determines if primitives in the collection are destroyed when they are removed by
         * {@link PrimitiveCollection#destroy} or  {@link PrimitiveCollection#remove} or implicitly
         * by {@link PrimitiveCollection#removeAll}.
         *
         * @type {Boolean}
         * @default true
         *
         * @example
         * // Example 1. Primitives are destroyed by default.
         * var primitives = new Cesium.PrimitiveCollection();
         * var labels = primitives.add(new Cesium.LabelCollection());
         * primitives = primitives.destroy();
         * var b = labels.isDestroyed(); // true
         *
         * @example
         * // Example 2. Do not destroy primitives in a collection.
         * var primitives = new Cesium.PrimitiveCollection();
         * primitives.destroyPrimitives = false;
         * var labels = primitives.add(new Cesium.LabelCollection());
         * primitives = primitives.destroy();
         * var b = labels.isDestroyed(); // false
         * labels = labels.destroy();    // explicitly destroy
         */
        this.destroyPrimitives = defaultValue(options.destroyPrimitives, true);
    }

    defineProperties(PrimitiveCollection.prototype, {
        /**
         * Gets the number of primitives in the collection.
         *
         * @memberof PrimitiveCollection.prototype
         *
         * @type {Number}
         * @readonly
         */
        length : {
            get : function() {
                return this._primitives.length;
            }
        }
    });

    /**
     * Adds a primitive to the collection.
     *
     * @param {Object} primitive The primitive to add.
     * @returns {Object} The primitive added to the collection.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * var billboards = scene.primitives.add(new Cesium.BillboardCollection());
     */
    PrimitiveCollection.prototype.add = function(primitive) {
                if (!defined(primitive)) {
            throw new DeveloperError('primitive is required.');
        }
        
        var external = (primitive._external = primitive._external || {});
        var composites = (external._composites = external._composites || {});
        composites[this._guid] = {
            collection : this
        };

        this._primitives.push(primitive);

        return primitive;
    };

    /**
     * Removes a primitive from the collection.
     *
     * @param {Object} [primitive] The primitive to remove.
     * @returns {Boolean} <code>true</code> if the primitive was removed; <code>false</code> if the primitive is <code>undefined</code> or was not found in the collection.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * var billboards = scene.primitives.add(new Cesium.BillboardCollection());
     * scene.primitives.remove(p);  // Returns true
     * 
     * @see PrimitiveCollection#destroyPrimitives
     */
    PrimitiveCollection.prototype.remove = function(primitive) {
        // PERFORMANCE_IDEA:  We can obviously make this a lot faster.
        if (this.contains(primitive)) {
            var index = this._primitives.indexOf(primitive);
            if (index !== -1) {
                this._primitives.splice(index, 1);

                delete primitive._external._composites[this._guid];

                if (this.destroyPrimitives) {
                    primitive.destroy();
                }

                return true;
            }
            // else ... this is not possible, I swear.
        }

        return false;
    };

    /**
     * Removes and destroys a primitive, regardless of destroyPrimitives setting.
     * @private
     */
    PrimitiveCollection.prototype.removeAndDestroy = function(primitive) {
        var removed = this.remove(primitive);
        if (removed && !this.destroyPrimitives) {
            primitive.destroy();
        }
        return removed;
    };

    /**
     * Removes all primitives in the collection.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see PrimitiveCollection#destroyPrimitives
     */
    PrimitiveCollection.prototype.removeAll = function() {
        if (this.destroyPrimitives) {
            var primitives = this._primitives;
            var length = primitives.length;
            for ( var i = 0; i < length; ++i) {
                primitives[i].destroy();
            }
        }
        this._primitives = [];
    };

    /**
     * Determines if this collection contains a primitive.
     *
     * @param {Object} [primitive] The primitive to check for.
     * @returns {Boolean} <code>true</code> if the primitive is in the collection; <code>false</code> if the primitive is <code>undefined</code> or was not found in the collection.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see PrimitiveCollection#get
     */
    PrimitiveCollection.prototype.contains = function(primitive) {
        return !!(defined(primitive) &&
                  primitive._external &&
                  primitive._external._composites &&
                  primitive._external._composites[this._guid]);
    };

    function getPrimitiveIndex(compositePrimitive, primitive) {
                if (!compositePrimitive.contains(primitive)) {
            throw new DeveloperError('primitive is not in this collection.');
        }
        
        return compositePrimitive._primitives.indexOf(primitive);
    }

    /**
     * Raises a primitive "up one" in the collection.  If all primitives in the collection are drawn
     * on the globe surface, this visually moves the primitive up one.
     *
     * @param {Object} [primitive] The primitive to raise.
     *
     * @exception {DeveloperError} primitive is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see PrimitiveCollection#raiseToTop
     * @see PrimitiveCollection#lower
     * @see PrimitiveCollection#lowerToBottom
     */
    PrimitiveCollection.prototype.raise = function(primitive) {
        if (defined(primitive)) {
            var index = getPrimitiveIndex(this, primitive);
            var primitives = this._primitives;

            if (index !== primitives.length - 1) {
                var p = primitives[index];
                primitives[index] = primitives[index + 1];
                primitives[index + 1] = p;
            }
        }
    };

    /**
     * Raises a primitive to the "top" of the collection.  If all primitives in the collection are drawn
     * on the globe surface, this visually moves the primitive to the top.
     *
     * @param {Object} [primitive] The primitive to raise the top.
     *
     * @exception {DeveloperError} primitive is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see PrimitiveCollection#raise
     * @see PrimitiveCollection#lower
     * @see PrimitiveCollection#lowerToBottom
     */
    PrimitiveCollection.prototype.raiseToTop = function(primitive) {
        if (defined(primitive)) {
            var index = getPrimitiveIndex(this, primitive);
            var primitives = this._primitives;

            if (index !== primitives.length - 1) {
                // PERFORMANCE_IDEA:  Could be faster
                primitives.splice(index, 1);
                primitives.push(primitive);
            }
        }
    };

    /**
     * Lowers a primitive "down one" in the collection.  If all primitives in the collection are drawn
     * on the globe surface, this visually moves the primitive down one.
     *
     * @param {Object} [primitive] The primitive to lower.
     *
     * @exception {DeveloperError} primitive is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see PrimitiveCollection#lowerToBottom
     * @see PrimitiveCollection#raise
     * @see PrimitiveCollection#raiseToTop
     */
    PrimitiveCollection.prototype.lower = function(primitive) {
        if (defined(primitive)) {
            var index = getPrimitiveIndex(this, primitive);
            var primitives = this._primitives;

            if (index !== 0) {
                var p = primitives[index];
                primitives[index] = primitives[index - 1];
                primitives[index - 1] = p;
            }
        }
    };

    /**
     * Lowers a primitive to the "bottom" of the collection.  If all primitives in the collection are drawn
     * on the globe surface, this visually moves the primitive to the bottom.
     *
     * @param {Object} [primitive] The primitive to lower to the bottom.
     *
     * @exception {DeveloperError} primitive is not in this collection.
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @see PrimitiveCollection#lower
     * @see PrimitiveCollection#raise
     * @see PrimitiveCollection#raiseToTop
     */
    PrimitiveCollection.prototype.lowerToBottom = function(primitive) {
        if (defined(primitive)) {
            var index = getPrimitiveIndex(this, primitive);
            var primitives = this._primitives;

            if (index !== 0) {
                // PERFORMANCE_IDEA:  Could be faster
                primitives.splice(index, 1);
                primitives.unshift(primitive);
            }
        }
    };

    /**
     * Returns the primitive in the collection at the specified index.
     *
     * @param {Number} index The zero-based index of the primitive to return.
     * @returns {Object} The primitive at the <code>index</code>.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * // Toggle the show property of every primitive in the collection.
     * var primitives = scene.primitives;
     * var length = primitives.length;
     * for (var i = 0; i < length; ++i) {
     *   var p = primitives.get(i);
     *   p.show = !p.show;
     * }
     * 
     * @see PrimitiveCollection#length
     */
    PrimitiveCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._primitives[index];
    };

    /**
     * @private
     */
    PrimitiveCollection.prototype.update = function(frameState) {
        if (!this.show) {
            return;
        }

        var primitives = this._primitives;
        // Using primitives.length in the loop is a temporary workaround
        // to allow quadtree updates to add and remove primitives in
        // update().  This will be changed to manage added and removed lists.
        for (var i = 0; i < primitives.length; ++i) {
            primitives[i].update(frameState);
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see PrimitiveCollection#destroy
     */
    PrimitiveCollection.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by each primitive in this collection.  Explicitly destroying this
     * collection allows for deterministic release of WebGL resources, instead of relying on the garbage
     * collector to destroy this collection.
     * <br /><br />
     * Since destroying a collection destroys all the contained primitives, only destroy a collection
     * when you are sure no other code is still using any of the contained primitives.
     * <br /><br />
     * Once this collection is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * primitives = primitives && primitives.destroy();
     * 
     * @see PrimitiveCollection#isDestroyed
     */
    PrimitiveCollection.prototype.destroy = function() {
        this.removeAll();
        return destroyObject(this);
    };

    return PrimitiveCollection;
});

/*global define*/
define('Scene/QuadtreeTileProvider',[
        '../Core/defineProperties',
        '../Core/DeveloperError'
    ], function(
        defineProperties,
        DeveloperError) {
    'use strict';

    /**
     * Provides general quadtree tiles to be displayed on or near the surface of an ellipsoid.  It is intended to be
     * used with the {@link QuadtreePrimitive}.  This type describes an interface and is not intended to be
     * instantiated directly.
     *
     * @alias QuadtreeTileProvider
     * @constructor
     * @private
     */
    function QuadtreeTileProvider() {
        DeveloperError.throwInstantiationError();
    }

    /**
     * Computes the default geometric error for level zero of the quadtree.
     *
     * @memberof QuadtreeTileProvider
     *
     * @param {TilingScheme} tilingScheme The tiling scheme for which to compute the geometric error.
     * @returns {Number} The maximum geometric error at level zero, in meters.
     */
    QuadtreeTileProvider.computeDefaultLevelZeroMaximumGeometricError = function(tilingScheme) {
        return tilingScheme.ellipsoid.maximumRadius * 2 * Math.PI * 0.25 / (65 * tilingScheme.getNumberOfXTilesAtLevel(0));
    };

    defineProperties(QuadtreeTileProvider.prototype, {
        /**
         * Gets or sets the {@link QuadtreePrimitive} for which this provider is
         * providing tiles.
         * @memberof QuadtreeTileProvider.prototype
         * @type {QuadtreePrimitive}
         */
        quadtree : {
            get : DeveloperError.throwInstantiationError,
            set : DeveloperError.throwInstantiationError
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof QuadtreeTileProvider.prototype
         * @type {Boolean}
         */
        ready : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets the tiling scheme used by the provider.  This property should
         * not be accessed before {@link QuadtreeTileProvider#ready} returns true.
         * @memberof QuadtreeTileProvider.prototype
         * @type {TilingScheme}
         */
        tilingScheme : {
            get : DeveloperError.throwInstantiationError
        },

        /**
         * Gets an event that is raised when the geometry provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof QuadtreeTileProvider.prototype
         * @type {Event}
         */
        errorEvent : {
            get : DeveloperError.throwInstantiationError
        }
    });

    /**
     * Called at the beginning of the update cycle for each render frame, before {@link QuadtreeTileProvider#showTileThisFrame}
     * or any other functions.
     * @memberof QuadtreeTileProvider
     * @function
     *
     * @param {Context} context The rendering context.
     * @param {FrameState} frameState The frame state.
     * @param {DrawCommand[]} commandList An array of rendering commands.  This method may push
     *        commands into this array.
     */
    QuadtreeTileProvider.prototype.beginUpdate = DeveloperError.throwInstantiationError;

    /**
     * Called at the end of the update cycle for each render frame, after {@link QuadtreeTileProvider#showTileThisFrame}
     * and any other functions.
     * @memberof QuadtreeTileProvider
     * @function
     *
     * @param {Context} context The rendering context.
     * @param {FrameState} frameState The frame state.
     * @param {DrawCommand[]} commandList An array of rendering commands.  This method may push
     *        commands into this array.
     */
    QuadtreeTileProvider.prototype.endUpdate = DeveloperError.throwInstantiationError;

    /**
     * Gets the maximum geometric error allowed in a tile at a given level, in meters.  This function should not be
     * called before {@link QuadtreeTileProvider#ready} returns true.
     *
     * @see {QuadtreeTileProvider.computeDefaultLevelZeroMaximumGeometricError}
     *
     * @memberof QuadtreeTileProvider
     * @function
     *
     * @param {Number} level The tile level for which to get the maximum geometric error.
     * @returns {Number} The maximum geometric error in meters.
     */
    QuadtreeTileProvider.prototype.getLevelMaximumGeometricError = DeveloperError.throwInstantiationError;

    /**
     * Loads, or continues loading, a given tile.  This function will continue to be called
     * until {@link QuadtreeTile#state} is no longer {@link QuadtreeTileLoadState#LOADING}.  This function should
     * not be called before {@link QuadtreeTileProvider#ready} returns true.
     *
     * @memberof QuadtreeTileProvider
     * @function
     *
     * @param {Context} context The rendering context.
     * @param {FrameState} frameState The frame state.
     * @param {QuadtreeTile} tile The tile to load.
     *
     * @exception {DeveloperError} <code>loadTile</code> must not be called before the tile provider is ready.
     */
    QuadtreeTileProvider.prototype.loadTile = DeveloperError.throwInstantiationError;

    /**
     * Determines the visibility of a given tile.  The tile may be fully visible, partially visible, or not
     * visible at all.  Tiles that are renderable and are at least partially visible will be shown by a call
     * to {@link QuadtreeTileProvider#showTileThisFrame}.
     *
     * @memberof QuadtreeTileProvider
     *
     * @param {QuadtreeTile} tile The tile instance.
     * @param {FrameState} frameState The state information about the current frame.
     * @param {QuadtreeOccluders} occluders The objects that may occlude this tile.
     *
     * @returns {Visibility} The visibility of the tile.
     */
    QuadtreeTileProvider.prototype.computeTileVisibility = DeveloperError.throwInstantiationError;

    /**
     * Shows a specified tile in this frame.  The provider can cause the tile to be shown by adding
     * render commands to the commandList, or use any other method as appropriate.  The tile is not
     * expected to be visible next frame as well, unless this method is call next frame, too.
     *
     * @memberof QuadtreeTileProvider
     * @function
     *
     * @param {QuadtreeTile} tile The tile instance.
     * @param {Context} context The rendering context.
     * @param {FrameState} frameState The state information of the current rendering frame.
     * @param {DrawCommand[]} commandList The list of rendering commands.  This method may add additional commands to this list.
     */
    QuadtreeTileProvider.prototype.showTileThisFrame = DeveloperError.throwInstantiationError;

    /**
     * Gets the distance from the camera to the closest point on the tile.  This is used for level-of-detail selection.
     *
     * @memberof QuadtreeTileProvider
     * @function
     *
     * @param {QuadtreeTile} tile The tile instance.
     * @param {FrameState} frameState The state information of the current rendering frame.
     *
     * @returns {Number} The distance from the camera to the closest point on the tile, in meters.
     */
    QuadtreeTileProvider.prototype.computeDistanceToTile = DeveloperError.throwInstantiationError;

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @memberof QuadtreeTileProvider
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see QuadtreeTileProvider#destroy
     */
    QuadtreeTileProvider.prototype.isDestroyed = DeveloperError.throwInstantiationError;

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @memberof QuadtreeTileProvider
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * provider = provider && provider();
     * 
     * @see QuadtreeTileProvider#isDestroyed
     */
    QuadtreeTileProvider.prototype.destroy = DeveloperError.throwInstantiationError;

    return QuadtreeTileProvider;
});

/*global define*/
define('Scene/SceneTransitioner',[
        '../Core/Cartesian3',
        '../Core/Cartographic',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EasingFunction',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/Ray',
        '../Core/ScreenSpaceEventHandler',
        '../Core/ScreenSpaceEventType',
        '../Core/Transforms',
        './Camera',
        './OrthographicFrustum',
        './PerspectiveFrustum',
        './SceneMode'
    ], function(
        Cartesian3,
        Cartographic,
        defined,
        destroyObject,
        DeveloperError,
        EasingFunction,
        CesiumMath,
        Matrix4,
        Ray,
        ScreenSpaceEventHandler,
        ScreenSpaceEventType,
        Transforms,
        Camera,
        OrthographicFrustum,
        PerspectiveFrustum,
        SceneMode) {
    'use strict';

    /**
     * @private
     */
    function SceneTransitioner(scene) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        this._scene = scene;
        this._currentTweens = [];
        this._morphHandler = undefined;
        this._morphCancelled = false;
        this._completeMorph = undefined;
    }

    SceneTransitioner.prototype.completeMorph = function() {
        if (defined(this._completeMorph)) {
            this._completeMorph();
        }
    };

    SceneTransitioner.prototype.morphTo2D = function(duration, ellipsoid) {
        if (defined(this._completeMorph)) {
            this._completeMorph();
        }

        var scene = this._scene;
        this._previousMode = scene.mode;

        if (this._previousMode === SceneMode.SCENE2D || this._previousMode === SceneMode.MORPHING) {
            return;
        }
        this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.SCENE2D, true);

        scene._mode = SceneMode.MORPHING;
        scene.camera._setTransform(Matrix4.IDENTITY);

        if (this._previousMode === SceneMode.COLUMBUS_VIEW) {
            morphFromColumbusViewTo2D(this, duration);
        } else {
            morphFrom3DTo2D(this, duration, ellipsoid);
        }

        if (duration === 0.0 && defined(this._completeMorph)) {
            this._completeMorph();
        }
    };

    var scratchToCVPosition = new Cartesian3();
    var scratchToCVDirection = new Cartesian3();
    var scratchToCVUp = new Cartesian3();
    var scratchToCVPosition2D = new Cartesian3();
    var scratchToCVDirection2D = new Cartesian3();
    var scratchToCVUp2D = new Cartesian3();
    var scratchToCVSurfacePosition = new Cartesian3();
    var scratchToCVCartographic = new Cartographic();
    var scratchToCVToENU = new Matrix4();
    var scratchToCVFrustum = new PerspectiveFrustum();
    var scratchToCVCamera = {
        position : undefined,
        direction : undefined,
        up : undefined,
        position2D : undefined,
        direction2D : undefined,
        up2D : undefined,
        frustum : undefined
    };

    SceneTransitioner.prototype.morphToColumbusView = function(duration, ellipsoid) {
        if (defined(this._completeMorph)) {
            this._completeMorph();
        }

        var scene = this._scene;
        this._previousMode = scene.mode;

        if (this._previousMode === SceneMode.COLUMBUS_VIEW || this._previousMode === SceneMode.MORPHING) {
            return;
        }
        this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.COLUMBUS_VIEW, true);

        scene.camera._setTransform(Matrix4.IDENTITY);

        var position = scratchToCVPosition;
        var direction = scratchToCVDirection;
        var up = scratchToCVUp;

        if (duration > 0.0) {
            position.x = 0.0;
            position.y = -1.0;
            position.z = 1.0;
            position = Cartesian3.multiplyByScalar(Cartesian3.normalize(position, position), 5.0 * ellipsoid.maximumRadius, position);

            Cartesian3.negate(Cartesian3.normalize(position, direction), direction);
            Cartesian3.cross(Cartesian3.UNIT_X, direction, up);
        } else {
            var camera = scene.camera;
            if (this._previousMode === SceneMode.SCENE2D) {
                Cartesian3.clone(camera.position, position);
                position.z = camera.frustum.right - camera.frustum.left;
                Cartesian3.negate(Cartesian3.UNIT_Z, direction);
                Cartesian3.clone(Cartesian3.UNIT_Y, up);
            } else {
                Cartesian3.clone(camera.positionWC, position);
                Cartesian3.clone(camera.directionWC, direction);
                Cartesian3.clone(camera.upWC, up);

                var surfacePoint = ellipsoid.scaleToGeodeticSurface(position, scratchToCVSurfacePosition);
                var toENU = Transforms.eastNorthUpToFixedFrame(surfacePoint, ellipsoid, scratchToCVToENU);
                Matrix4.inverseTransformation(toENU, toENU);

                scene.mapProjection.project(ellipsoid.cartesianToCartographic(position, scratchToCVCartographic), position);
                Matrix4.multiplyByPointAsVector(toENU, direction, direction);
                Matrix4.multiplyByPointAsVector(toENU, up, up);
            }
        }

        var frustum = scratchToCVFrustum;
        frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
        frustum.fov = CesiumMath.toRadians(60.0);

        var cameraCV = scratchToCVCamera;
        cameraCV.position = position;
        cameraCV.direction = direction;
        cameraCV.up = up;
        cameraCV.frustum = frustum;

        var complete = completeColumbusViewCallback(cameraCV);
        createMorphHandler(this, complete);

        if (this._previousMode === SceneMode.SCENE2D) {
            morphFrom2DToColumbusView(this, duration, cameraCV, complete);
        } else {
            cameraCV.position2D = Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, position, scratchToCVPosition2D);
            cameraCV.direction2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, direction, scratchToCVDirection2D);
            cameraCV.up2D = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, up, scratchToCVUp2D);

            scene._mode = SceneMode.MORPHING;
            morphFrom3DToColumbusView(this, duration, cameraCV, complete);
        }

        if (duration === 0.0 && defined(this._completeMorph)) {
            this._completeMorph();
        }
    };

    SceneTransitioner.prototype.morphTo3D = function(duration, ellipsoid) {
        if (defined(this._completeMorph)) {
            this._completeMorph();
        }

        var scene = this._scene;
        this._previousMode = scene.mode;

        if (this._previousMode === SceneMode.SCENE3D || this._previousMode === SceneMode.MORPHING) {
            return;
        }
        this._scene.morphStart.raiseEvent(this, this._previousMode, SceneMode.SCENE3D, true);

        scene._mode = SceneMode.MORPHING;
        scene.camera._setTransform(Matrix4.IDENTITY);

        if (this._previousMode === SceneMode.SCENE2D) {
            morphFrom2DTo3D(this, duration, ellipsoid);
        } else {
            var camera3D;
            if (duration > 0.0) {
                camera3D = scratchCVTo3DCamera;
                Cartesian3.fromDegrees(0.0, 0.0, 5.0 * ellipsoid.maximumRadius, ellipsoid, camera3D.position);
                Cartesian3.negate(camera3D.position, camera3D.direction);
                Cartesian3.normalize(camera3D.direction, camera3D.direction);
                Cartesian3.clone(Cartesian3.UNIT_Z, camera3D.up);
            } else {
                camera3D = getColumbusViewTo3DCamera(this, ellipsoid);
            }
            var complete = complete3DCallback(camera3D);
            createMorphHandler(this, complete);

            morphFromColumbusViewTo3D(this, duration, camera3D, complete);
        }

        if (duration === 0.0 && defined(this._completeMorph)) {
            this._completeMorph();
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     */
    SceneTransitioner.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     * @example
     * transitioner = transitioner && transitioner.destroy();
     */
    SceneTransitioner.prototype.destroy = function() {
        destroyMorphHandler(this);
        return destroyObject(this);
    };

    function createMorphHandler(transitioner, completeMorphFunction) {
        if (transitioner._scene.completeMorphOnUserInput) {
            transitioner._morphHandler = new ScreenSpaceEventHandler(transitioner._scene.canvas, false);

            var completeMorph = function() {
                transitioner._morphCancelled = true;
                completeMorphFunction(transitioner);
            };
            transitioner._completeMorph = completeMorph;
            transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.LEFT_DOWN);
            transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.MIDDLE_DOWN);
            transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.RIGHT_DOWN);
            transitioner._morphHandler.setInputAction(completeMorph, ScreenSpaceEventType.WHEEL);
        }
    }

    function destroyMorphHandler(transitioner) {
        var tweens = transitioner._currentTweens;
        for ( var i = 0; i < tweens.length; ++i) {
            tweens[i].cancelTween();
        }
        transitioner._currentTweens.length = 0;
        transitioner._morphHandler = transitioner._morphHandler && transitioner._morphHandler.destroy();
    }

    var scratchCVTo3DCartographic = new Cartographic();
    var scratchCVTo3DSurfacePoint = new Cartesian3();
    var scratchCVTo3DFromENU = new Matrix4();
    var scratchCVTo3DCamera = {
        position : new Cartesian3(),
        direction : new Cartesian3(),
        up : new Cartesian3(),
        frustum : undefined
    };

    function getColumbusViewTo3DCamera(transitioner, ellipsoid) {
        var scene = transitioner._scene;
        var camera = scene.camera;

        var camera3D = scratchCVTo3DCamera;
        var position = camera3D.position;
        var direction = camera3D.direction;
        var up = camera3D.up;

        var positionCarto = scene.mapProjection.unproject(camera.position, scratchCVTo3DCartographic);
        ellipsoid.cartographicToCartesian(positionCarto, position);
        var surfacePoint = ellipsoid.scaleToGeodeticSurface(position, scratchCVTo3DSurfacePoint);

        var fromENU = Transforms.eastNorthUpToFixedFrame(surfacePoint, ellipsoid, scratchCVTo3DFromENU);

        Matrix4.multiplyByPointAsVector(fromENU, camera.direction, direction);
        Matrix4.multiplyByPointAsVector(fromENU, camera.up, up);

        return camera3D;
    }

    var scratchCVTo3DStartPos = new Cartesian3();
    var scratchCVTo3DStartDir = new Cartesian3();
    var scratchCVTo3DStartUp = new Cartesian3();
    var scratchCVTo3DEndPos = new Cartesian3();
    var scratchCVTo3DEndDir = new Cartesian3();
    var scratchCVTo3DEndUp = new Cartesian3();

    function morphFromColumbusViewTo3D(transitioner, duration, endCamera, complete) {
        duration *= 0.5;

        var scene = transitioner._scene;
        var camera = scene.camera;

        var startPos = Cartesian3.clone(camera.position, scratchCVTo3DStartPos);
        var startDir = Cartesian3.clone(camera.direction, scratchCVTo3DStartDir);
        var startUp = Cartesian3.clone(camera.up, scratchCVTo3DStartUp);

        var endPos = Matrix4.multiplyByPoint(Camera.TRANSFORM_2D_INVERSE, endCamera.position, scratchCVTo3DEndPos);
        var endDir = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D_INVERSE, endCamera.direction, scratchCVTo3DEndDir);
        var endUp = Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D_INVERSE, endCamera.up, scratchCVTo3DEndUp);

        function update(value) {
            columbusViewMorph(startPos, endPos, value.time, camera.position);
            columbusViewMorph(startDir, endDir, value.time, camera.direction);
            columbusViewMorph(startUp, endUp, value.time, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.normalize(camera.right, camera.right);
        }

        var tween = scene.tweens.add({
            duration : duration,
            easingFunction : EasingFunction.QUARTIC_OUT,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : 1.0
            },
            update : update,
            complete : function() {
                addMorphTimeAnimations(transitioner, scene, 0.0, 1.0, duration, complete);
            }
        });
        transitioner._currentTweens.push(tween);
    }

    var scratch2DTo3DFrustum = new PerspectiveFrustum();

    function morphFrom2DTo3D(transitioner, duration, ellipsoid) {
        duration /= 3.0;

        var scene = transitioner._scene;
        var camera = scene.camera;
        var frustum = scratch2DTo3DFrustum;
        frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight;
        frustum.fov = CesiumMath.toRadians(60.0);

        var camera3D;
        if (duration > 0.0) {
            camera3D = scratchCVTo3DCamera;
            Cartesian3.fromDegrees(0.0, 0.0, 5.0 * ellipsoid.maximumRadius, ellipsoid, camera3D.position);
            Cartesian3.negate(camera3D.position, camera3D.direction);
            Cartesian3.normalize(camera3D.direction, camera3D.direction);
            Cartesian3.clone(Cartesian3.UNIT_Z, camera3D.up);
        } else {
            camera.position.z = camera.frustum.right - camera.frustum.left;

            camera3D = getColumbusViewTo3DCamera(transitioner, ellipsoid);
        }

        camera3D.frustum = frustum;

        var complete = complete3DCallback(camera3D);
        createMorphHandler(transitioner, complete);

        var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos);
        var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir);
        var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp);

        var endPos = Cartesian3.fromElements(0.0, 0.0, 5.0 * ellipsoid.maximumRadius, scratch3DToCVEndPos);
        var endDir = Cartesian3.negate(Cartesian3.UNIT_Z, scratch3DToCVEndDir);
        var endUp = Cartesian3.clone(Cartesian3.UNIT_Y, scratch3DToCVEndUp);

        var startRight = camera.frustum.right;
        var endRight = endPos.z * 0.5;

        function update(value) {
            columbusViewMorph(startPos, endPos, value.time, camera.position);
            columbusViewMorph(startDir, endDir, value.time, camera.direction);
            columbusViewMorph(startUp, endUp, value.time, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.normalize(camera.right, camera.right);

            var frustum = camera.frustum;
            frustum.right = CesiumMath.lerp(startRight, endRight, value.time);
            frustum.left = -frustum.right;
            frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
            frustum.bottom = -frustum.top;

            camera.position.z = 2.0 * scene.mapProjection.ellipsoid.maximumRadius;
        }

        if (duration > 0.0) {
            var tween = scene.tweens.add({
                duration : duration,
                easingFunction : EasingFunction.QUARTIC_OUT,
                startObject : {
                    time : 0.0
                },
                stopObject : {
                    time : 1.0
                },
                update : update,
                complete : function() {
                    scene._mode = SceneMode.MORPHING;
                    morphOrthographicToPerspective(transitioner, duration, camera3D, function() {
                        morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete);
                    });
                }
            });
            transitioner._currentTweens.push(tween);
        } else {
            morphOrthographicToPerspective(transitioner, duration, camera3D, function() {
                morphFromColumbusViewTo3D(transitioner, duration, camera3D, complete);
            });
        }
    }

    function columbusViewMorph(startPosition, endPosition, time, result) {
        // Just linear for now.
        return Cartesian3.lerp(startPosition, endPosition, time, result);
    }

    function morphPerspectiveToOrthographic(transitioner, duration, endCamera, updateHeight, complete) {
        var scene = transitioner._scene;
        var camera = scene.camera;

        var startFOV = camera.frustum.fov;
        var endFOV = CesiumMath.RADIANS_PER_DEGREE * 0.5;
        var d = endCamera.position.z * Math.tan(startFOV * 0.5);
        camera.frustum.far = d / Math.tan(endFOV * 0.5) + 10000000.0;

        function update(value) {
            camera.frustum.fov = CesiumMath.lerp(startFOV, endFOV, value.time);
            var height = d / Math.tan(camera.frustum.fov * 0.5);
            updateHeight(camera, height);
        }
        var tween = scene.tweens.add({
            duration : duration,
            easingFunction : EasingFunction.QUARTIC_OUT,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : 1.0
            },
            update : update,
            complete : function() {
                camera.frustum = endCamera.frustum.clone();
                complete(transitioner);
            }
        });
        transitioner._currentTweens.push(tween);
    }

    var scratchCVTo2DStartPos = new Cartesian3();
    var scratchCVTo2DStartDir = new Cartesian3();
    var scratchCVTo2DStartUp = new Cartesian3();
    var scratchCVTo2DEndPos = new Cartesian3();
    var scratchCVTo2DEndDir = new Cartesian3();
    var scratchCVTo2DEndUp = new Cartesian3();
    var scratchCVTo2DFrustum = new OrthographicFrustum();
    var scratchCVTo2DRay = new Ray();
    var scratchCVTo2DPickPos = new Cartesian3();
    var scratchCVTo2DCamera = {
        position : undefined,
        direction : undefined,
        up : undefined,
        frustum : undefined
    };

    function morphFromColumbusViewTo2D(transitioner, duration) {
        duration *= 0.5;

        var scene = transitioner._scene;
        var camera = scene.camera;

        var startPos = Cartesian3.clone(camera.position, scratchCVTo2DStartPos);
        var startDir = Cartesian3.clone(camera.direction, scratchCVTo2DStartDir);
        var startUp = Cartesian3.clone(camera.up, scratchCVTo2DStartUp);

        var endDir = Cartesian3.negate(Cartesian3.UNIT_Z, scratchCVTo2DEndDir);
        var endUp = Cartesian3.clone(Cartesian3.UNIT_Y, scratchCVTo2DEndUp);

        var endPos = scratchCVTo2DEndPos;

        if (duration > 0.0) {
            Cartesian3.clone(Cartesian3.ZERO, scratchCVTo2DEndPos);
            endPos.z = 5.0 * scene.mapProjection.ellipsoid.maximumRadius;
        } else {
            Cartesian3.clone(startPos, scratchCVTo2DEndPos);

            var ray = scratchCVTo2DRay;
            Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, startPos, ray.origin);
            Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, startDir, ray.direction);

            var globe = scene.globe;
            if (defined(globe)) {
                var pickPos = globe.pick(ray, scene, scratchCVTo2DPickPos);
                if (defined(pickPos)) {
                    Matrix4.multiplyByPoint(Camera.TRANSFORM_2D_INVERSE, pickPos, endPos);
                    endPos.z += Cartesian3.distance(startPos, endPos);
                }
            }
        }

        var frustum = scratchCVTo2DFrustum;
        frustum.right = endPos.z * 0.5;
        frustum.left = -frustum.right;
        frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
        frustum.bottom = -frustum.top;

        var camera2D = scratchCVTo2DCamera;
        camera2D.position = endPos;
        camera2D.direction = endDir;
        camera2D.up = endUp;
        camera2D.frustum = frustum;

        var complete = complete2DCallback(camera2D);
        createMorphHandler(transitioner, complete);

        function updateCV(value) {
            columbusViewMorph(startPos, endPos, value.time, camera.position);
            columbusViewMorph(startDir, endDir, value.time, camera.direction);
            columbusViewMorph(startUp, endUp, value.time, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.normalize(camera.right, camera.right);
        }

        function updateHeight(camera, height) {
            camera.position.z = height;
        }

        var tween = scene.tweens.add({
            duration : duration,
            easingFunction : EasingFunction.QUARTIC_OUT,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : 1.0
            },
            update : updateCV,
            complete : function() {
                morphPerspectiveToOrthographic(transitioner, duration, camera2D, updateHeight, complete);
            }
        });
        transitioner._currentTweens.push(tween);
    }

    var scratch3DTo2DCartographic = new Cartographic();
    var scratch3DTo2DCamera = {
        position : new Cartesian3(),
        direction : new Cartesian3(),
        up : new Cartesian3(),
        position2D : new Cartesian3(),
        direction2D : new Cartesian3(),
        up2D : new Cartesian3(),
        frustum : new OrthographicFrustum()
    };
    var scratch3DTo2DEndCamera = {
        position : new Cartesian3(),
        direction : new Cartesian3(),
        up : new Cartesian3(),
        frustum : undefined
    };
    var scratch3DTo2DPickPosition = new Cartesian3();
    var scratch3DTo2DRay = new Ray();
    var scratch3DTo2DToENU = new Matrix4();
    var scratch3DTo2DSurfacePoint = new Cartesian3();

    function morphFrom3DTo2D(transitioner, duration, ellipsoid) {
        duration *= 0.5;

        var scene = transitioner._scene;
        var camera = scene.camera;
        var camera2D = scratch3DTo2DCamera;

        if (duration > 0.0) {
            Cartesian3.clone(Cartesian3.ZERO, camera2D.position);
            camera2D.position.z = 5.0 * ellipsoid.maximumRadius;
            Cartesian3.negate(Cartesian3.UNIT_Z, camera2D.direction);
            Cartesian3.clone(Cartesian3.UNIT_Y, camera2D.up);
        } else {
            ellipsoid.cartesianToCartographic(camera.positionWC, scratch3DTo2DCartographic);
            scene.mapProjection.project(scratch3DTo2DCartographic, camera2D.position);

            Cartesian3.negate(Cartesian3.UNIT_Z, camera2D.direction);
            Cartesian3.clone(Cartesian3.UNIT_Y, camera2D.up);

            var ray = scratch3DTo2DRay;
            Cartesian3.clone(camera2D.position2D, ray.origin);
            var rayDirection = Cartesian3.clone(camera.directionWC, ray.direction);
            var surfacePoint = ellipsoid.scaleToGeodeticSurface(camera.positionWC, scratch3DTo2DSurfacePoint);
            var toENU = Transforms.eastNorthUpToFixedFrame(surfacePoint, ellipsoid, scratch3DTo2DToENU);
            Matrix4.inverseTransformation(toENU, toENU);
            Matrix4.multiplyByPointAsVector(toENU, rayDirection, rayDirection);
            Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, rayDirection, rayDirection);

            var globe = scene.globe;
            if (defined(globe)) {
                var pickedPos = globe.pick(ray, scene, scratch3DTo2DPickPosition);
                if (defined(pickedPos)) {
                    var height = Cartesian3.distance(camera2D.position2D, pickedPos);
                    pickedPos.x += height;
                    Cartesian3.clone(pickedPos, camera2D.position2D);
                }
            }
        }

        function updateHeight(camera, height) {
            camera.position.x = height;
        }

        Matrix4.multiplyByPoint(Camera.TRANSFORM_2D, camera2D.position, camera2D.position2D);
        Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, camera2D.direction, camera2D.direction2D);
        Matrix4.multiplyByPointAsVector(Camera.TRANSFORM_2D, camera2D.up, camera2D.up2D);

        var frustum = camera2D.frustum;
        frustum.right = camera2D.position.z * 0.5;
        frustum.left = -frustum.right;
        frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth);
        frustum.bottom = -frustum.top;

        var endCamera = scratch3DTo2DEndCamera;
        Matrix4.multiplyByPoint(Camera.TRANSFORM_2D_INVERSE, camera2D.position2D, endCamera.position);
        Cartesian3.clone(camera2D.direction, endCamera.direction);
        Cartesian3.clone(camera2D.up, endCamera.up);
        endCamera.frustum = frustum;

        var complete = complete2DCallback(endCamera);
        createMorphHandler(transitioner, complete);

        function completeCallback() {
            morphPerspectiveToOrthographic(transitioner, duration, camera2D, updateHeight, complete);
        }
        morphFrom3DToColumbusView(transitioner, duration, camera2D, completeCallback);
    }

    function morphOrthographicToPerspective(transitioner, duration, cameraCV, complete) {
        var scene = transitioner._scene;
        var camera = scene.camera;

        var height = camera.frustum.right - camera.frustum.left;
        camera.frustum = cameraCV.frustum.clone();

        var endFOV = camera.frustum.fov;
        var startFOV = CesiumMath.RADIANS_PER_DEGREE * 0.5;
        var d = height * Math.tan(endFOV * 0.5);
        camera.frustum.far = d / Math.tan(startFOV * 0.5) + 10000000.0;
        camera.frustum.fov = startFOV;

        function update(value) {
            camera.frustum.fov = CesiumMath.lerp(startFOV, endFOV, value.time);
            camera.position.z = d / Math.tan(camera.frustum.fov * 0.5);
        }
        var tween = scene.tweens.add({
            duration : duration,
            easingFunction : EasingFunction.QUARTIC_OUT,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : 1.0
            },
            update : update,
            complete : function() {
                complete(transitioner);
            }
        });
        transitioner._currentTweens.push(tween);
    }

    function morphFrom2DToColumbusView(transitioner, duration, cameraCV, complete) {
        duration *= 0.5;

        var scene = transitioner._scene;
        var camera = scene.camera;

        var endPos = Cartesian3.clone(cameraCV.position, scratch3DToCVEndPos);
        var endDir = Cartesian3.clone(cameraCV.direction, scratch3DToCVEndDir);
        var endUp = Cartesian3.clone(cameraCV.up, scratch3DToCVEndUp);

        scene._mode = SceneMode.MORPHING;
        morphOrthographicToPerspective(transitioner, 0.0, cameraCV, function() {
            camera.frustum = cameraCV.frustum.clone();

            var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos);
            var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir);
            var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp);
            startPos.z = endPos.z;

            function update(value) {
                columbusViewMorph(startPos, endPos, value.time, camera.position);
                columbusViewMorph(startDir, endDir, value.time, camera.direction);
                columbusViewMorph(startUp, endUp, value.time, camera.up);
                Cartesian3.cross(camera.direction, camera.up, camera.right);
                Cartesian3.normalize(camera.right, camera.right);
            }
            var tween = scene.tweens.add({
                duration : duration,
                easingFunction : EasingFunction.QUARTIC_OUT,
                startObject : {
                    time : 0.0
                },
                stopObject : {
                    time : 1.0
                },
                update : update,
                complete : function() {
                    complete(transitioner);
                }
            });
            transitioner._currentTweens.push(tween);
        });
    }

    var scratch3DToCVStartPos = new Cartesian3();
    var scratch3DToCVStartDir = new Cartesian3();
    var scratch3DToCVStartUp = new Cartesian3();
    var scratch3DToCVEndPos = new Cartesian3();
    var scratch3DToCVEndDir = new Cartesian3();
    var scratch3DToCVEndUp = new Cartesian3();

    function morphFrom3DToColumbusView(transitioner, duration, endCamera, complete) {
        var scene = transitioner._scene;
        var camera = scene.camera;

        var startPos = Cartesian3.clone(camera.position, scratch3DToCVStartPos);
        var startDir = Cartesian3.clone(camera.direction, scratch3DToCVStartDir);
        var startUp = Cartesian3.clone(camera.up, scratch3DToCVStartUp);

        var endPos = Cartesian3.clone(endCamera.position2D, scratch3DToCVEndPos);
        var endDir = Cartesian3.clone(endCamera.direction2D, scratch3DToCVEndDir);
        var endUp = Cartesian3.clone(endCamera.up2D, scratch3DToCVEndUp);

        function update(value) {
            columbusViewMorph(startPos, endPos, value.time, camera.position);
            columbusViewMorph(startDir, endDir, value.time, camera.direction);
            columbusViewMorph(startUp, endUp, value.time, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.normalize(camera.right, camera.right);
        }
        var tween = scene.tweens.add({
            duration : duration,
            easingFunction : EasingFunction.QUARTIC_OUT,
            startObject : {
                time : 0.0
            },
            stopObject : {
                time : 1.0
            },
            update : update,
            complete : function() {
                addMorphTimeAnimations(transitioner, scene, 1.0, 0.0, duration, complete);
            }
        });
        transitioner._currentTweens.push(tween);
    }

    function addMorphTimeAnimations(transitioner, scene, start, stop, duration, complete) {
        // Later, this will be linear and each object will adjust, if desired, in its vertex shader.
        var options = {
            object : scene,
            property : 'morphTime',
            startValue : start,
            stopValue : stop,
            duration : duration,
            easingFunction : EasingFunction.QUARTIC_OUT
        };

        if (defined(complete)) {
            options.complete = function() {
                complete(transitioner);
            };
        }

        var tween = scene.tweens.addProperty(options);
        transitioner._currentTweens.push(tween);
    }

    function complete3DCallback(camera3D) {
        return function(transitioner) {
            var scene = transitioner._scene;
            scene._mode = SceneMode.SCENE3D;
            scene.morphTime = SceneMode.getMorphTime(SceneMode.SCENE3D);

            destroyMorphHandler(transitioner);

            if (transitioner._previousMode !== SceneMode.MORPHING || transitioner._morphCancelled) {
                transitioner._morphCancelled = false;

                var camera = scene.camera;
                Cartesian3.clone(camera3D.position, camera.position);
                Cartesian3.clone(camera3D.direction, camera.direction);
                Cartesian3.clone(camera3D.up, camera.up);
                Cartesian3.cross(camera.direction, camera.up, camera.right);
                Cartesian3.normalize(camera.right, camera.right);

                if (defined(camera3D.frustum)) {
                    camera.frustum = camera3D.frustum.clone();
                }
            }

            var wasMorphing = defined(transitioner._completeMorph);
            transitioner._completeMorph = undefined;
            scene.camera.update(scene.mode);
            transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.SCENE3D, wasMorphing);
        };
    }

    function complete2DCallback(camera2D) {
        return function(transitioner) {
            var scene = transitioner._scene;

            scene._mode = SceneMode.SCENE2D;
            scene.morphTime = SceneMode.getMorphTime(SceneMode.SCENE2D);

            destroyMorphHandler(transitioner);

            var camera = scene.camera;
            Cartesian3.clone(camera2D.position, camera.position);
            camera.position.z = scene.mapProjection.ellipsoid.maximumRadius * 2.0;
            Cartesian3.clone(camera2D.direction, camera.direction);
            Cartesian3.clone(camera2D.up, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.normalize(camera.right, camera.right);
            camera.frustum = camera2D.frustum.clone();

            var wasMorphing = defined(transitioner._completeMorph);
            transitioner._completeMorph = undefined;
            scene.camera.update(scene.mode);
            transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.SCENE2D, wasMorphing);
        };
    }

    function completeColumbusViewCallback(cameraCV) {
        return function(transitioner) {
            var scene = transitioner._scene;
            scene._mode = SceneMode.COLUMBUS_VIEW;
            scene.morphTime = SceneMode.getMorphTime(SceneMode.COLUMBUS_VIEW);

            destroyMorphHandler(transitioner);
            scene.camera.frustum = cameraCV.frustum.clone();

            if (transitioner._previousModeMode !== SceneMode.MORPHING || transitioner._morphCancelled) {
                transitioner._morphCancelled = false;

                var camera = scene.camera;
                Cartesian3.clone(cameraCV.position, camera.position);
                Cartesian3.clone(cameraCV.direction, camera.direction);
                Cartesian3.clone(cameraCV.up, camera.up);
                Cartesian3.cross(camera.direction, camera.up, camera.right);
                Cartesian3.normalize(camera.right, camera.right);
            }

            var wasMorphing = defined(transitioner._completeMorph);
            transitioner._completeMorph = undefined;
            scene.camera.update(scene.mode);
            transitioner._scene.morphComplete.raiseEvent(transitioner, transitioner._previousMode, SceneMode.COLUMBUS_VIEW, wasMorphing);
        };
    }

    return SceneTransitioner;
});

/*global define*/
define('Scene/TweenCollection',[
        '../Core/clone',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/EasingFunction',
        '../Core/getTimestamp',
        '../Core/TimeConstants',
        '../ThirdParty/Tween'
    ], function(
        clone,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        EasingFunction,
        getTimestamp,
        TimeConstants,
        TweenJS) {
    'use strict';

    /**
     * A tween is an animation that interpolates the properties of two objects using an {@link EasingFunction}.  Create
     * one using {@link Scene#tweens} and {@link TweenCollection#add} and related add functions.
     *
     * @alias Tween
     * @constructor
     *
     * @private
     */
    function Tween(tweens, tweenjs, startObject, stopObject, duration, delay, easingFunction, update, complete, cancel) {
        this._tweens = tweens;
        this._tweenjs = tweenjs;

        this._startObject = clone(startObject);
        this._stopObject = clone(stopObject);

        this._duration = duration;
        this._delay = delay;
        this._easingFunction = easingFunction;

        this._update = update;
        this._complete = complete;

        /**
         * The callback to call if the tween is canceled either because {@link Tween#cancelTween}
         * was called or because the tween was removed from the collection.
         *
         * @type {TweenCollection~TweenCancelledCallback}
         */
        this.cancel = cancel;

        /**
         * @private
         */
        this.needsStart = true;
    }

    defineProperties(Tween.prototype, {
        /**
         * An object with properties for initial values of the tween.  The properties of this object are changed during the tween's animation.
         * @memberof Tween.prototype
         *
         * @type {Object}
         * @readonly
         */
        startObject : {
            get : function() {
                return this._startObject;
            }
        },

        /**
         * An object with properties for the final values of the tween.
         * @memberof Tween.prototype
         *
         * @type {Object}
         * @readonly
         */
        stopObject : {
            get : function() {
                return this._stopObject;
            }
        },

        /**
         * The duration, in seconds, for the tween.  The tween is automatically removed from the collection when it stops.
         * @memberof Tween.prototype
         *
         * @type {Number}
         * @readonly
         */
        duration : {
            get : function() {
                return this._duration;
            }
        },

        /**
         * The delay, in seconds, before the tween starts animating.
         * @memberof Tween.prototype
         *
         * @type {Number}
         * @readonly
         */
        delay : {
            get : function() {
                return this._delay;
            }
        },

        /**
         * Determines the curve for animtion.
         * @memberof Tween.prototype
         *
         * @type {EasingFunction}
         * @readonly
         */
        easingFunction : {
            get : function() {
                return this._easingFunction;
            }
        },

        /**
         * The callback to call at each animation update (usually tied to the a rendered frame).
         * @memberof Tween.prototype
         *
         * @type {TweenCollection~TweenUpdateCallback}
         * @readonly
         */
        update : {
            get : function() {
                return this._update;
            }
        },

        /**
         * The callback to call when the tween finishes animating.
         * @memberof Tween.prototype
         *
         * @type {TweenCollection~TweenCompleteCallback}
         * @readonly
         */
        complete : {
            get : function() {
                return this._complete;
            }
        },

        /**
         * @memberof Tween.prototype
         *
         * @private
         */
        tweenjs : {
            get : function() {
                return this._tweenjs;
            }
        }
    });

    /**
     * Cancels the tween calling the {@link Tween#cancel} callback if one exists.  This
     * has no effect if the tween finished or was already canceled.
     */
    Tween.prototype.cancelTween = function() {
        this._tweens.remove(this);
    };

    /**
     * A collection of tweens for animating properties.  Commonly accessed using {@link Scene#tweens}.
     *
     * @alias TweenCollection
     * @constructor
     *
     * @private
     */
    function TweenCollection() {
        this._tweens = [];
    }

    defineProperties(TweenCollection.prototype, {
        /**
         * The number of tweens in the collection.
         * @memberof TweenCollection.prototype
         *
         * @type {Number}
         * @readonly
         */
        length : {
            get : function() {
                return this._tweens.length;
            }
        }
    });

    /**
     * Creates a tween for animating between two sets of properties.  The tween starts animating at the next call to {@link TweenCollection#update}, which
     * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Object} options.startObject An object with properties for initial values of the tween.  The properties of this object are changed during the tween's animation.
     * @param {Object} options.stopObject An object with properties for the final values of the tween.
     * @param {Number} options.duration The duration, in seconds, for the tween.  The tween is automatically removed from the collection when it stops.
     * @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
     * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
     * @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
     * @param {TweenCollection~TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating.
     * @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
     * @returns {Tween} The tween.
     *
     * @exception {DeveloperError} options.duration must be positive.
     */
    TweenCollection.prototype.add = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.startObject) || !defined(options.stopObject)) {
            throw new DeveloperError('options.startObject and options.stopObject are required.');
        }

        if (!defined(options.duration) || options.duration < 0.0) {
            throw new DeveloperError('options.duration is required and must be positive.');
        }
        
        if (options.duration === 0.0) {
            if (defined(options.complete)) {
                options.complete();
            }
            return new Tween(this);
        }

        var duration = options.duration / TimeConstants.SECONDS_PER_MILLISECOND;
        var delayInSeconds = defaultValue(options.delay, 0.0);
        var delay = delayInSeconds / TimeConstants.SECONDS_PER_MILLISECOND;
        var easingFunction = defaultValue(options.easingFunction, EasingFunction.LINEAR_NONE);

        var value = options.startObject;
        var tweenjs = new TweenJS.Tween(value);
        tweenjs.to(clone(options.stopObject), duration);
        tweenjs.delay(delay);
        tweenjs.easing(easingFunction);
        if (defined(options.update)) {
            tweenjs.onUpdate(function() {
                options.update(value);
            });
        }
        tweenjs.onComplete(defaultValue(options.complete, null));
        tweenjs.repeat(defaultValue(options._repeat, 0.0));

        var tween = new Tween(this, tweenjs, options.startObject, options.stopObject, options.duration, delayInSeconds, easingFunction, options.update, options.complete, options.cancel);
        this._tweens.push(tween);
        return tween;
    };

    /**
     * Creates a tween for animating a scalar property on the given object.  The tween starts animating at the next call to {@link TweenCollection#update}, which
     * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Object} options.object The object containing the property to animate.
     * @param {String} options.property The name of the property to animate.
     * @param {Number} options.startValue The initial value.
     * @param {Number} options.stopValue The final value.
     * @param {Number} [options.duration=3.0] The duration, in seconds, for the tween.  The tween is automatically removed from the collection when it stops.
     * @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
     * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
     * @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
     * @param {TweenCollection~TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating.
     * @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
     * @returns {Tween} The tween.
     *
     * @exception {DeveloperError} options.object must have the specified property.
     * @exception {DeveloperError} options.duration must be positive.
     */
    TweenCollection.prototype.addProperty = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var object = options.object;
        var property = options.property;
        var startValue = options.startValue;
        var stopValue = options.stopValue;

                if (!defined(object) || !defined(options.property)) {
            throw new DeveloperError('options.object and options.property are required.');
        }
        if (!defined(object[property])) {
            throw new DeveloperError('options.object must have the specified property.');
        }
        if (!defined(startValue) || !defined(stopValue)) {
            throw new DeveloperError('options.startValue and options.stopValue are required.');
        }
        
        function update(value) {
            object[property] = value.value;
        }

        return this.add({
            startObject : {
                value : startValue
            },
            stopObject : {
                value : stopValue
            },
            duration : defaultValue(options.duration, 3.0),
            delay : options.delay,
            easingFunction : options.easingFunction,
            update : update,
            complete : options.complete,
            cancel : options.cancel,
            _repeat : options._repeat
        });
    };

    /**
     * Creates a tween for animating the alpha of all color uniforms on a {@link Material}.  The tween starts animating at the next call to {@link TweenCollection#update}, which
     * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Material} options.material The material to animate.
     * @param {Number} [options.startValue=0.0] The initial alpha value.
     * @param {Number} [options.stopValue=1.0] The final alpha value.
     * @param {Number} [options.duration=3.0] The duration, in seconds, for the tween.  The tween is automatically removed from the collection when it stops.
     * @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
     * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
     * @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
     * @param {TweenCollection~TweenCompleteCallback} [options.complete] The callback to call when the tween finishes animating.
     * @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
     * @returns {Tween} The tween.
     *
     * @exception {DeveloperError} material has no properties with alpha components.
     * @exception {DeveloperError} options.duration must be positive.
     */
    TweenCollection.prototype.addAlpha = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var material = options.material;

                if (!defined(material)) {
            throw new DeveloperError('options.material is required.');
        }
        
        var properties = [];

        for (var property in material.uniforms) {
            if (material.uniforms.hasOwnProperty(property) &&
                defined(material.uniforms[property]) &&
                defined(material.uniforms[property].alpha)) {
                properties.push(property);
            }
        }

                if (properties.length === 0) {
            throw new DeveloperError('material has no properties with alpha components.');
        }
        
        function update(value) {
            var length = properties.length;
            for (var i = 0; i < length; ++i) {
                material.uniforms[properties[i]].alpha = value.alpha;
            }
        }

        return this.add({
            startObject : {
                alpha : defaultValue(options.startValue, 0.0)  // Default to fade in
            },
            stopObject : {
                alpha : defaultValue(options.stopValue, 1.0)
            },
            duration : defaultValue(options.duration, 3.0),
            delay : options.delay,
            easingFunction : options.easingFunction,
            update : update,
            complete : options.complete,
            cancel : options.cancel
        });
    };

    /**
     * Creates a tween for animating the offset uniform of a {@link Material}.  The tween starts animating at the next call to {@link TweenCollection#update}, which
     * is implicit when {@link Viewer} or {@link CesiumWidget} render the scene.
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Material} options.material The material to animate.
     * @param {Number} options.startValue The initial alpha value.
     * @param {Number} options.stopValue The final alpha value.
     * @param {Number} [options.duration=3.0] The duration, in seconds, for the tween.  The tween is automatically removed from the collection when it stops.
     * @param {Number} [options.delay=0.0] The delay, in seconds, before the tween starts animating.
     * @param {EasingFunction} [options.easingFunction=EasingFunction.LINEAR_NONE] Determines the curve for animtion.
     * @param {TweenCollection~TweenUpdateCallback} [options.update] The callback to call at each animation update (usually tied to the a rendered frame).
     * @param {TweenCollection~TweenCancelledCallback} [options.cancel] The callback to call if the tween is canceled either because {@link Tween#cancelTween} was called or because the tween was removed from the collection.
     * @returns {Tween} The tween.
     *
     * @exception {DeveloperError} material.uniforms must have an offset property.
     * @exception {DeveloperError} options.duration must be positive.
     */
    TweenCollection.prototype.addOffsetIncrement = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var material = options.material;

                if (!defined(material)) {
            throw new DeveloperError('material is required.');
        }
        if (!defined(material.uniforms.offset)) {
            throw new DeveloperError('material.uniforms must have an offset property.');
        }
        
        var uniforms = material.uniforms;
        return this.addProperty({
            object : uniforms,
            property : 'offset',
            startValue : uniforms.offset,
            stopValue :  uniforms.offset + 1,
            duration : options.duration,
            delay : options.delay,
            easingFunction : options.easingFunction,
            update : options.update,
            cancel : options.cancel,
            _repeat : Infinity
        });
    };

    /**
     * Removes a tween from the collection.
     * <p>
     * This calls the {@link Tween#cancel} callback if the tween has one.
     * </p>
     *
     * @param {Tween} tween The tween to remove.
     * @returns {Boolean} <code>true</code> if the tween was removed; <code>false</code> if the tween was not found in the collection.
     */
    TweenCollection.prototype.remove = function(tween) {
        if (!defined(tween)) {
            return false;
        }

        var index = this._tweens.indexOf(tween);
        if (index !== -1) {
            tween.tweenjs.stop();
            if (defined(tween.cancel)) {
                tween.cancel();
            }
            this._tweens.splice(index, 1);
            return true;
        }

        return false;
    };

    /**
     * Removes all tweens from the collection.
     * <p>
     * This calls the {@link Tween#cancel} callback for each tween that has one.
     * </p>
     */
    TweenCollection.prototype.removeAll = function() {
        var tweens = this._tweens;

        for (var i = 0; i < tweens.length; ++i) {
            var tween = tweens[i];
            tween.tweenjs.stop();
            if (defined(tween.cancel)) {
                tween.cancel();
            }
        }
        tweens.length = 0;
    };

    /**
     * Determines whether this collection contains a given tween.
     *
     * @param {Tween} tween The tween to check for.
     * @returns {Boolean} <code>true</code> if this collection contains the tween, <code>false</code> otherwise.
     */
    TweenCollection.prototype.contains = function(tween) {
        return defined(tween) && (this._tweens.indexOf(tween) !== -1);
    };

    /**
     * Returns the tween in the collection at the specified index.  Indices are zero-based
     * and increase as tweens are added.  Removing a tween shifts all tweens after
     * it to the left, changing their indices.  This function is commonly used to iterate over
     * all the tween in the collection.
     *
     * @param {Number} index The zero-based index of the tween.
     * @returns {Tween} The tween at the specified index.
     *
     * @example
     * // Output the duration of all the tweens in the collection.
     * var tweens = scene.tweens;
     * var length = tweens.length;
     * for (var i = 0; i < length; ++i) {
     *   console.log(tweens.get(i).duration);
     * }
     */
    TweenCollection.prototype.get = function(index) {
                if (!defined(index)) {
            throw new DeveloperError('index is required.');
        }
        
        return this._tweens[index];
    };

    /**
     * Updates the tweens in the collection to be at the provide time.  When a tween finishes, it is removed
     * from the collection.
     *
     * @param {Number} [time=getTimestamp()] The time in seconds.  By default tweens are synced to the system clock.
     */
    TweenCollection.prototype.update = function(time) {
        var tweens = this._tweens;

        var i = 0;
        time = defined(time) ? time / TimeConstants.SECONDS_PER_MILLISECOND : getTimestamp();
        while (i < tweens.length) {
            var tween = tweens[i];
            var tweenjs = tween.tweenjs;

            if (tween.needsStart) {
                tween.needsStart = false;
                tweenjs.start(time);
            } else {
                if (tweenjs.update(time)) {
                    i++;
                } else {
                    tweenjs.stop();
                    tweens.splice(i, 1);
                }
            }
        }
    };

    /**
     * A function that will execute when a tween completes.
     * @callback TweenCollection~TweenCompleteCallback
     */

    /**
     * A function that will execute when a tween updates.
     * @callback TweenCollection~TweenUpdateCallback
     */

    /**
     * A function that will execute when a tween is cancelled.
     * @callback TweenCollection~TweenCancelledCallback
     */

    return TweenCollection;
});

/*global define*/
define('Scene/ScreenSpaceCameraController',[
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/Ellipsoid',
        '../Core/IntersectionTests',
        '../Core/isArray',
        '../Core/KeyboardEventModifier',
        '../Core/Math',
        '../Core/Matrix3',
        '../Core/Matrix4',
        '../Core/Plane',
        '../Core/Quaternion',
        '../Core/Ray',
        '../Core/Transforms',
        './CameraEventAggregator',
        './CameraEventType',
        './MapMode2D',
        './SceneMode',
        './SceneTransforms',
        './TweenCollection'
    ], function(
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        Ellipsoid,
        IntersectionTests,
        isArray,
        KeyboardEventModifier,
        CesiumMath,
        Matrix3,
        Matrix4,
        Plane,
        Quaternion,
        Ray,
        Transforms,
        CameraEventAggregator,
        CameraEventType,
        MapMode2D,
        SceneMode,
        SceneTransforms,
        TweenCollection) {
    'use strict';

    /**
     * Modifies the camera position and orientation based on mouse input to a canvas.
     * @alias ScreenSpaceCameraController
     * @constructor
     *
     * @param {Scene} scene The scene.
     */
    function ScreenSpaceCameraController(scene) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        /**
         * If true, inputs are allowed conditionally with the flags enableTranslate, enableZoom,
         * enableRotate, enableTilt, and enableLook.  If false, all inputs are disabled.
         *
         * NOTE: This setting is for temporary use cases, such as camera flights and
         * drag-selection of regions (see Picking demo).  It is typically set to false at the
         * start of such events, and set true on completion.  To keep inputs disabled
         * past the end of camera flights, you must use the other booleans (enableTranslate,
         * enableZoom, enableRotate, enableTilt, and enableLook).
         * @type {Boolean}
         * @default true
         */
        this.enableInputs = true;
        /**
         * If true, allows the user to pan around the map.  If false, the camera stays locked at the current position.
         * This flag only applies in 2D and Columbus view modes.
         * @type {Boolean}
         * @default true
         */
        this.enableTranslate = true;
        /**
         * If true, allows the user to zoom in and out.  If false, the camera is locked to the current distance from the ellipsoid.
         * @type {Boolean}
         * @default true
         */
        this.enableZoom = true;
        /**
         * If true, allows the user to rotate the camera.  If false, the camera is locked to the current heading.
         * This flag only applies in 2D and 3D.
         * @type {Boolean}
         * @default true
         */
        this.enableRotate = true;
        /**
         * If true, allows the user to tilt the camera.  If false, the camera is locked to the current heading.
         * This flag only applies in 3D and Columbus view.
         * @type {Boolean}
         * @default true
         */
        this.enableTilt = true;
        /**
         * If true, allows the user to use free-look. If false, the camera view direction can only be changed through translating
         * or rotating. This flag only applies in 3D and Columbus view modes.
         * @type {Boolean}
         * @default true
         */
        this.enableLook = true;
        /**
         * A parameter in the range <code>[0, 1)</code> used to determine how long
         * the camera will continue to spin because of inertia.
         * With value of zero, the camera will have no inertia.
         * @type {Number}
         * @default 0.9
         */
        this.inertiaSpin = 0.9;
        /**
         * A parameter in the range <code>[0, 1)</code> used to determine how long
         * the camera will continue to translate because of inertia.
         * With value of zero, the camera will have no inertia.
         * @type {Number}
         * @default 0.9
         */
        this.inertiaTranslate = 0.9;
        /**
         * A parameter in the range <code>[0, 1)</code> used to determine how long
         * the camera will continue to zoom because of inertia.
         * With value of zero, the camera will have no inertia.
         * @type {Number}
         * @default 0.8
         */
        this.inertiaZoom = 0.8;
        /**
         * A parameter in the range <code>[0, 1)</code> used to limit the range
         * of various user inputs to a percentage of the window width/height per animation frame.
         * This helps keep the camera under control in low-frame-rate situations.
         * @type {Number}
         * @default 0.1
         */
        this.maximumMovementRatio = 0.1;
        /**
         * Sets the duration, in seconds, of the bounce back animations in 2D and Columbus view.
         * @type {Number}
         * @default 3.0
         */
        this.bounceAnimationTime = 3.0;
        /**
         * The minimum magnitude, in meters, of the camera position when zooming. Defaults to 1.0.
         * @type {Number}
         * @default 1.0
         */
        this.minimumZoomDistance = 1.0;
        /**
         * The maximum magnitude, in meters, of the camera position when zooming. Defaults to positive infinity.
         * @type {Number}
         * @default {@link Number.POSITIVE_INFINITY}
         */
        this.maximumZoomDistance = Number.POSITIVE_INFINITY;
        /**
         * The input that allows the user to pan around the map. This only applies in 2D and Columbus view modes.
         * <p>
         * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>
         * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},
         * or an array of any of the preceding.
         * </p>
         * @type {CameraEventType|Array|undefined}
         * @default {@link CameraEventType.LEFT_DRAG}
         */
        this.translateEventTypes = CameraEventType.LEFT_DRAG;
        /**
         * The input that allows the user to zoom in/out.
         * <p>
         * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>
         * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},
         * or an array of any of the preceding.
         * </p>
         * @type {CameraEventType|Array|undefined}
         * @default [{@link CameraEventType.RIGHT_DRAG}, {@link CameraEventType.WHEEL}, {@link CameraEventType.PINCH}]
         */
        this.zoomEventTypes = [CameraEventType.RIGHT_DRAG, CameraEventType.WHEEL, CameraEventType.PINCH];
        /**
         * The input that allows the user to rotate around the globe or another object. This only applies in 3D and Columbus view modes.
         * <p>
         * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>
         * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},
         * or an array of any of the preceding.
         * </p>
         * @type {CameraEventType|Array|undefined}
         * @default {@link CameraEventType.LEFT_DRAG}
         */
        this.rotateEventTypes = CameraEventType.LEFT_DRAG;
        /**
         * The input that allows the user to tilt in 3D and Columbus view or twist in 2D.
         * <p>
         * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>
         * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},
         * or an array of any of the preceding.
         * </p>
         * @type {CameraEventType|Array|undefined}
         * @default [{@link CameraEventType.MIDDLE_DRAG}, {@link CameraEventType.PINCH}, {
         *     eventType : {@link CameraEventType.LEFT_DRAG},
         *     modifier : {@link KeyboardEventModifier.CTRL}
         * }, {
         *     eventType : {@link CameraEventType.RIGHT_DRAG},
         *     modifier : {@link KeyboardEventModifier.CTRL}
         * }]
         */
        this.tiltEventTypes = [CameraEventType.MIDDLE_DRAG, CameraEventType.PINCH, {
            eventType : CameraEventType.LEFT_DRAG,
            modifier : KeyboardEventModifier.CTRL
        }, {
            eventType : CameraEventType.RIGHT_DRAG,
            modifier : KeyboardEventModifier.CTRL
        }];
        /**
         * The input that allows the user to change the direction the camera is viewing. This only applies in 3D and Columbus view modes.
         * <p>
         * The type came be a {@link CameraEventType}, <code>undefined</code>, an object with <code>eventType</code>
         * and <code>modifier</code> properties with types <code>CameraEventType</code> and {@link KeyboardEventModifier},
         * or an array of any of the preceding.
         * </p>
         * @type {CameraEventType|Array|undefined}
         * @default { eventType : {@link CameraEventType.LEFT_DRAG}, modifier : {@link KeyboardEventModifier.SHIFT} }
         */
        this.lookEventTypes = {
            eventType : CameraEventType.LEFT_DRAG,
            modifier : KeyboardEventModifier.SHIFT
        };
        /**
         * The minimum height the camera must be before picking the terrain instead of the ellipsoid.
         * @type {Number}
         * @default 150000.0
         */
        this.minimumPickingTerrainHeight = 150000.0;
        this._minimumPickingTerrainHeight = this.minimumPickingTerrainHeight;
        /**
         * The minimum height the camera must be before testing for collision with terrain.
         * @type {Number}
         * @default 10000.0
         */
        this.minimumCollisionTerrainHeight = 15000.0;
        this._minimumCollisionTerrainHeight = this.minimumCollisionTerrainHeight;
        /**
         * The minimum height the camera must be before switching from rotating a track ball to
         * free look when clicks originate on the sky on in space.
         * @type {Number}
         * @default 7500000.0
         */
        this.minimumTrackBallHeight = 7500000.0;
        this._minimumTrackBallHeight = this.minimumTrackBallHeight;
        /**
         * Enables or disables camera collision detection with terrain.
         * @type {Boolean}
         * @default true
         */
        this.enableCollisionDetection = true;

        this._scene = scene;
        this._globe = undefined;
        this._ellipsoid = undefined;

        this._aggregator = new CameraEventAggregator(scene.canvas);

        this._lastInertiaSpinMovement = undefined;
        this._lastInertiaZoomMovement = undefined;
        this._lastInertiaTranslateMovement = undefined;
        this._lastInertiaTiltMovement = undefined;

        this._tweens = new TweenCollection();
        this._tween = undefined;

        this._horizontalRotationAxis = undefined;

        this._tiltCenterMousePosition = new Cartesian2(-1.0, -1.0);
        this._tiltCenter = new Cartesian3();
        this._rotateMousePosition = new Cartesian2(-1.0, -1.0);
        this._rotateStartPosition = new Cartesian3();
        this._strafeStartPosition = new Cartesian3();
        this._zoomMouseStart = new Cartesian2(-1.0, -1.0);
        this._zoomWorldPosition = new Cartesian3();
        this._useZoomWorldPosition = false;
        this._tiltCVOffMap = false;
        this._looking = false;
        this._rotating = false;
        this._strafing = false;
        this._zoomingOnVector = false;
        this._rotatingZoom = false;

        var projection = scene.mapProjection;
        this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO));

        // Constants, Make any of these public?
        this._zoomFactor = 5.0;
        this._rotateFactor = undefined;
        this._rotateRateRangeAdjustment = undefined;
        this._maximumRotateRate = 1.77;
        this._minimumRotateRate = 1.0 / 5000.0;
        this._minimumZoomRate = 20.0;
        this._maximumZoomRate = 5906376272000.0;  // distance from the Sun to Pluto in meters.
    }

    function decay(time, coefficient) {
        if (time < 0) {
            return 0.0;
        }

        var tau = (1.0 - coefficient) * 25.0;
        return Math.exp(-tau * time);
    }

    function sameMousePosition(movement) {
        return Cartesian2.equalsEpsilon(movement.startPosition, movement.endPosition, CesiumMath.EPSILON14);
    }

    // If the time between mouse down and mouse up is not between
    // these thresholds, the camera will not move with inertia.
    // This value is probably dependent on the browser and/or the
    // hardware. Should be investigated further.
    var inertiaMaxClickTimeThreshold = 0.4;

    function maintainInertia(aggregator, type, modifier, decayCoef, action, object, lastMovementName) {
        var movementState = object[lastMovementName];
        if (!defined(movementState)) {
            movementState = object[lastMovementName] = {
                startPosition : new Cartesian2(),
                endPosition : new Cartesian2(),
                motion : new Cartesian2(),
                active : false
            };
        }

        var ts = aggregator.getButtonPressTime(type, modifier);
        var tr = aggregator.getButtonReleaseTime(type, modifier);

        var threshold = ts && tr && ((tr.getTime() - ts.getTime()) / 1000.0);
        var now = new Date();
        var fromNow = tr && ((now.getTime() - tr.getTime()) / 1000.0);

        if (ts && tr && threshold < inertiaMaxClickTimeThreshold) {
            var d = decay(fromNow, decayCoef);

            if (!movementState.active) {
                var lastMovement = aggregator.getLastMovement(type, modifier);
                if (!defined(lastMovement) || sameMousePosition(lastMovement)) {
                    return;
                }

                movementState.motion.x = (lastMovement.endPosition.x - lastMovement.startPosition.x) * 0.5;
                movementState.motion.y = (lastMovement.endPosition.y - lastMovement.startPosition.y) * 0.5;

                movementState.startPosition = Cartesian2.clone(lastMovement.startPosition, movementState.startPosition);

                movementState.endPosition = Cartesian2.multiplyByScalar(movementState.motion, d, movementState.endPosition);
                movementState.endPosition = Cartesian2.add(movementState.startPosition, movementState.endPosition, movementState.endPosition);

                movementState.active = true;
            } else {
                movementState.startPosition = Cartesian2.clone(movementState.endPosition, movementState.startPosition);

                movementState.endPosition = Cartesian2.multiplyByScalar(movementState.motion, d, movementState.endPosition);
                movementState.endPosition = Cartesian2.add(movementState.startPosition, movementState.endPosition, movementState.endPosition);

                movementState.motion = Cartesian2.clone(Cartesian2.ZERO, movementState.motion);
            }

            // If value from the decreasing exponential function is close to zero,
            // the end coordinates may be NaN.
            if (isNaN(movementState.endPosition.x) || isNaN(movementState.endPosition.y) || Cartesian2.distance(movementState.startPosition, movementState.endPosition) < 0.5) {
                movementState.active = false;
                return;
            }

            if (!aggregator.isButtonDown(type, modifier)) {
                var startPosition = aggregator.getStartMousePosition(type, modifier);
                action(object, startPosition, movementState);
            }
        } else {
            movementState.active = false;
        }
    }

    var scratchEventTypeArray = [];

    function reactToInput(controller, enabled, eventTypes, action, inertiaConstant, inertiaStateName) {
        if (!defined(eventTypes)) {
            return;
        }

        var aggregator = controller._aggregator;

        if (!isArray(eventTypes)) {
            scratchEventTypeArray[0] = eventTypes;
            eventTypes = scratchEventTypeArray;
        }

        var length = eventTypes.length;
        for (var i = 0; i < length; ++i) {
            var eventType = eventTypes[i];
            var type = defined(eventType.eventType) ? eventType.eventType : eventType;
            var modifier = eventType.modifier;

            var movement = aggregator.isMoving(type, modifier) && aggregator.getMovement(type, modifier);
            var startPosition = aggregator.getStartMousePosition(type, modifier);

            if (controller.enableInputs && enabled) {
                if (movement) {
                    action(controller, startPosition, movement);
                } else if (inertiaConstant < 1.0) {
                    maintainInertia(aggregator, type, modifier, inertiaConstant, action, controller, inertiaStateName);
                }
            }
        }
    }

    var scratchZoomPickRay = new Ray();
    var scratchPickCartesian = new Cartesian3();
    var scratchZoomOffset = new Cartesian2();
    var scratchZoomDirection = new Cartesian3();
    var scratchCenterPixel = new Cartesian2();
    var scratchCenterPosition = new Cartesian3();
    var scratchPositionNormal = new Cartesian3();
    var scratchPickNormal = new Cartesian3();
    var scratchZoomAxis = new Cartesian3();
    var scratchCameraPositionNormal = new Cartesian3();

    // Scratch variables used in zooming algorithm
    var scratchTargetNormal = new Cartesian3();
    var scratchCameraPosition = new Cartesian3();
    var scratchCameraUpNormal = new Cartesian3();
    var scratchCameraRightNormal = new Cartesian3();
    var scratchForwardNormal = new Cartesian3();
    var scratchPositionToTarget = new Cartesian3();
    var scratchPositionToTargetNormal = new Cartesian3();
    var scratchPan = new Cartesian3();
    var scratchCenterMovement = new Cartesian3();
    var scratchCenter = new Cartesian3();
    var scratchCartesian = new Cartesian3();
    var scratchCartesianTwo = new Cartesian3();
    var scratchCartesianThree = new Cartesian3();

    function handleZoom(object, startPosition, movement, zoomFactor, distanceMeasure, unitPositionDotDirection) {
        var percentage = 1.0;
        if (defined(unitPositionDotDirection)) {
            percentage = CesiumMath.clamp(Math.abs(unitPositionDotDirection), 0.25, 1.0);
        }

        // distanceMeasure should be the height above the ellipsoid.
        // The zoomRate slows as it approaches the surface and stops minimumZoomDistance above it.
        var minHeight = object.minimumZoomDistance * percentage;
        var maxHeight = object.maximumZoomDistance;

        var minDistance = distanceMeasure - minHeight;
        var zoomRate = zoomFactor * minDistance;
        zoomRate = CesiumMath.clamp(zoomRate, object._minimumZoomRate, object._maximumZoomRate);

        var diff = movement.endPosition.y - movement.startPosition.y;
        var rangeWindowRatio = diff / object._scene.canvas.clientHeight;
        rangeWindowRatio = Math.min(rangeWindowRatio, object.maximumMovementRatio);
        var distance = zoomRate * rangeWindowRatio;

        if (distance > 0.0 && Math.abs(distanceMeasure - minHeight) < 1.0) {
            return;
        }

        if (distance < 0.0 && Math.abs(distanceMeasure - maxHeight) < 1.0) {
            return;
        }

        if (distanceMeasure - distance < minHeight) {
            distance = distanceMeasure - minHeight - 1.0;
        } else if (distanceMeasure - distance > maxHeight) {
            distance = distanceMeasure - maxHeight;
        }

        var scene = object._scene;
        var camera = scene.camera;
        var mode = scene.mode;

        var sameStartPosition = Cartesian2.equals(startPosition, object._zoomMouseStart);
        var zoomingOnVector = object._zoomingOnVector;
        var rotatingZoom = object._rotatingZoom;
        var pickedPosition;

        if (!sameStartPosition) {
            object._zoomMouseStart = Cartesian2.clone(startPosition, object._zoomMouseStart);

            if (defined(object._globe)) {
                pickedPosition = mode !== SceneMode.SCENE2D ? pickGlobe(object, startPosition, scratchPickCartesian) : camera.getPickRay(startPosition, scratchZoomPickRay).origin;
            }
            if (defined(pickedPosition)) {
                object._useZoomWorldPosition = true;
                object._zoomWorldPosition = Cartesian3.clone(pickedPosition, object._zoomWorldPosition);
            } else {
                object._useZoomWorldPosition = false;
            }

            zoomingOnVector = object._zoomingOnVector = false;
            rotatingZoom = object._rotatingZoom = false;
        }

        if (!object._useZoomWorldPosition) {
            camera.zoomIn(distance);
            return;
        }

        var zoomOnVector = mode === SceneMode.COLUMBUS_VIEW;

        if (camera.positionCartographic.height < 2000000) {
            rotatingZoom = true;
        }

        if (!sameStartPosition || rotatingZoom) {
            if (mode === SceneMode.SCENE2D) {
                var worldPosition = object._zoomWorldPosition;
                var endPosition = camera.position;

                if (!Cartesian3.equals(worldPosition, endPosition) && camera.positionCartographic.height < object._maxCoord.x * 2.0) {
                    var savedX = camera.position.x;

                    var direction = Cartesian3.subtract(worldPosition, endPosition, scratchZoomDirection);
                    Cartesian3.normalize(direction, direction);

                    var d = Cartesian3.distance(worldPosition, endPosition) * distance / (camera.getMagnitude() * 0.5);
                    camera.move(direction, d * 0.5);

                    if ((camera.position.x < 0.0 && savedX > 0.0) || (camera.position.x > 0.0 && savedX < 0.0)) {
                        pickedPosition = camera.getPickRay(startPosition, scratchZoomPickRay).origin;
                        object._zoomWorldPosition = Cartesian3.clone(pickedPosition, object._zoomWorldPosition);
                    }
                }
            } else if (mode === SceneMode.SCENE3D) {
                var cameraPositionNormal = Cartesian3.normalize(camera.position, scratchCameraPositionNormal);
                if (camera.positionCartographic.height < 3000.0 && Math.abs(Cartesian3.dot(camera.direction, cameraPositionNormal)) < 0.6) {
                    zoomOnVector = true;
                } else {
                    var canvas = scene.canvas;

                    var centerPixel = scratchCenterPixel;
                    centerPixel.x = canvas.clientWidth / 2;
                    centerPixel.y = canvas.clientHeight / 2;
                    var centerPosition = pickGlobe(object, centerPixel, scratchCenterPosition);
                    // If centerPosition is not defined, it means the globe does not cover the center position of screen

                    if (defined(centerPosition) && camera.positionCartographic.height < 1000000) {

                        var cameraPosition = scratchCameraPosition;
                        Cartesian3.clone(camera.position, cameraPosition);
                        var target = object._zoomWorldPosition;

                        var targetNormal = scratchTargetNormal;

                        targetNormal = Cartesian3.normalize(target, targetNormal);

                        if (Cartesian3.dot(targetNormal, cameraPositionNormal) < 0.0) {
                            return;
                        }

                        var center = scratchCenter;
                        var forward = scratchForwardNormal;
                        Cartesian3.clone(camera.direction, forward);
                        Cartesian3.add(cameraPosition, Cartesian3.multiplyByScalar(forward, 1000, scratchCartesian), center);

                        var positionToTarget = scratchPositionToTarget;
                        var positionToTargetNormal = scratchPositionToTargetNormal;
                        Cartesian3.subtract(target, cameraPosition, positionToTarget);

                        Cartesian3.normalize(positionToTarget, positionToTargetNormal);

                        var alphaDot = Cartesian3.dot(cameraPositionNormal, positionToTargetNormal);
                        if (alphaDot >= 0.0) {
                            // We zoomed past the target, and this zoom is not valid anymore.
                            // This line causes the next zoom movement to pick a new starting point.
                            object._zoomMouseStart.x = -1;
                            return;
                        }
                        var alpha = Math.acos(-alphaDot);
                        var cameraDistance = Cartesian3.magnitude( cameraPosition );
                        var targetDistance = Cartesian3.magnitude( target );
                        var remainingDistance = cameraDistance - distance;
                        var positionToTargetDistance = Cartesian3.magnitude(positionToTarget);

                        var gamma = Math.asin( CesiumMath.clamp( positionToTargetDistance / targetDistance * Math.sin(alpha), -1.0, 1.0 ) );
                        var delta = Math.asin( CesiumMath.clamp( remainingDistance / targetDistance * Math.sin(alpha), -1.0, 1.0 ) );
                        var beta = gamma - delta + alpha;

                        var up = scratchCameraUpNormal;
                        Cartesian3.normalize(cameraPosition, up);
                        var right = scratchCameraRightNormal;
                        right = Cartesian3.cross(positionToTargetNormal, up, right);
                        right = Cartesian3.normalize(right, right );

                        Cartesian3.normalize( Cartesian3.cross(up, right, scratchCartesian), forward );

                        // Calculate new position to move to
                        Cartesian3.multiplyByScalar(Cartesian3.normalize(center, scratchCartesian), (Cartesian3.magnitude(center) - distance), center);
                        Cartesian3.normalize(cameraPosition, cameraPosition);
                        Cartesian3.multiplyByScalar(cameraPosition, remainingDistance, cameraPosition);

                        // Pan
                        var pMid = scratchPan;
                        Cartesian3.multiplyByScalar(Cartesian3.add(
                            Cartesian3.multiplyByScalar(up, Math.cos(beta) - 1, scratchCartesianTwo),
                            Cartesian3.multiplyByScalar(forward, Math.sin(beta), scratchCartesianThree),
                            scratchCartesian
                        ), remainingDistance, pMid);
                        Cartesian3.add(cameraPosition, pMid, cameraPosition);

                        Cartesian3.normalize(center, up);
                        Cartesian3.normalize( Cartesian3.cross(up, right, scratchCartesian), forward );

                        var cMid = scratchCenterMovement;
                        Cartesian3.multiplyByScalar(Cartesian3.add(
                            Cartesian3.multiplyByScalar(up, Math.cos(beta) - 1, scratchCartesianTwo),
                            Cartesian3.multiplyByScalar(forward, Math.sin(beta), scratchCartesianThree),
                            scratchCartesian
                        ), Cartesian3.magnitude(center), cMid);
                        Cartesian3.add(center, cMid, center);

                        // Update camera

                        // Set new position
                        Cartesian3.clone(cameraPosition, camera.position);

                        // Set new direction
                        Cartesian3.normalize(Cartesian3.subtract(center, cameraPosition, scratchCartesian), camera.direction);
                        Cartesian3.clone(camera.direction, camera.direction);

                        // Set new right & up vectors
                        Cartesian3.cross(camera.direction, camera.up, camera.right);
                        Cartesian3.cross(camera.right, camera.direction, camera.up);

                        return;
                    }

                    if (defined(centerPosition)) {
                        var positionNormal = Cartesian3.normalize(centerPosition, scratchPositionNormal);
                        var pickedNormal = Cartesian3.normalize(object._zoomWorldPosition, scratchPickNormal);
                        var dotProduct = Cartesian3.dot(pickedNormal, positionNormal);

                        if (dotProduct > 0.0 && dotProduct < 1.0) {
                            var angle = CesiumMath.acosClamped(dotProduct);
                            var axis = Cartesian3.cross(pickedNormal, positionNormal, scratchZoomAxis);

                            var denom = Math.abs(angle) > CesiumMath.toRadians(20.0) ? camera.positionCartographic.height * 0.75 : camera.positionCartographic.height - distance;
                            var scalar = distance / denom;
                            camera.rotate(axis, angle * scalar);
                        }
                    } else {
                        zoomOnVector = true;
                    }
                }
            }

            object._rotatingZoom = !zoomOnVector;
        }

        if ((!sameStartPosition && zoomOnVector) || zoomingOnVector) {
            var ray;
            var zoomMouseStart = SceneTransforms.wgs84ToWindowCoordinates(scene, object._zoomWorldPosition, scratchZoomOffset);
            if (mode !== SceneMode.COLUMBUS_VIEW && Cartesian2.equals(startPosition, object._zoomMouseStart) && defined(zoomMouseStart)) {
                ray = camera.getPickRay(zoomMouseStart, scratchZoomPickRay);
            } else {
                ray = camera.getPickRay(startPosition, scratchZoomPickRay);
            }

            var rayDirection = ray.direction;
            if (mode === SceneMode.COLUMBUS_VIEW) {
                Cartesian3.fromElements(rayDirection.y, rayDirection.z, rayDirection.x, rayDirection);
            }

            camera.move(rayDirection, distance);

            object._zoomingOnVector = true;
        } else {
            camera.zoomIn(distance);
        }
    }

    var translate2DStart = new Ray();
    var translate2DEnd = new Ray();
    var scratchTranslateP0 = new Cartesian3();

    function translate2D(controller, startPosition, movement) {
        var scene = controller._scene;
        var camera = scene.camera;
        var start = camera.getPickRay(movement.startPosition, translate2DStart).origin;
        var end = camera.getPickRay(movement.endPosition, translate2DEnd).origin;

        var direction = Cartesian3.subtract(start, end, scratchTranslateP0);
        var distance = Cartesian3.magnitude(direction);

        if (distance > 0.0) {
            Cartesian3.normalize(direction, direction);
            camera.move(direction, distance);
        }
    }

    function zoom2D(controller, startPosition, movement) {
        if (defined(movement.distance)) {
            movement = movement.distance;
        }

        var scene = controller._scene;
        var camera = scene.camera;

        handleZoom(controller, startPosition, movement, controller._zoomFactor, camera.getMagnitude());
    }

    var twist2DStart = new Cartesian2();
    var twist2DEnd = new Cartesian2();

    function twist2D(controller, startPosition, movement) {
        if (defined(movement.angleAndHeight)) {
            singleAxisTwist2D(controller, startPosition, movement.angleAndHeight);
            return;
        }

        var scene = controller._scene;
        var camera = scene.camera;
        var canvas = scene.canvas;
        var width = canvas.clientWidth;
        var height = canvas.clientHeight;

        var start = twist2DStart;
        start.x = (2.0 / width) * movement.startPosition.x - 1.0;
        start.y = (2.0 / height) * (height - movement.startPosition.y) - 1.0;
        start = Cartesian2.normalize(start, start);

        var end = twist2DEnd;
        end.x = (2.0 / width) * movement.endPosition.x - 1.0;
        end.y = (2.0 / height) * (height - movement.endPosition.y) - 1.0;
        end = Cartesian2.normalize(end, end);

        var startTheta = CesiumMath.acosClamped(start.x);
        if (start.y < 0) {
            startTheta = CesiumMath.TWO_PI - startTheta;
        }
        var endTheta = CesiumMath.acosClamped(end.x);
        if (end.y < 0) {
            endTheta = CesiumMath.TWO_PI - endTheta;
        }
        var theta = endTheta - startTheta;

        camera.twistRight(theta);
    }

    function singleAxisTwist2D(controller, startPosition, movement) {
        var rotateRate = controller._rotateFactor * controller._rotateRateRangeAdjustment;

        if (rotateRate > controller._maximumRotateRate) {
            rotateRate = controller._maximumRotateRate;
        }

        if (rotateRate < controller._minimumRotateRate) {
            rotateRate = controller._minimumRotateRate;
        }

        var scene = controller._scene;
        var camera = scene.camera;
        var canvas = scene.canvas;

        var phiWindowRatio = (movement.endPosition.x - movement.startPosition.x) / canvas.clientWidth;
        phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio);

        var deltaPhi = rotateRate * phiWindowRatio * Math.PI * 4.0;

        camera.twistRight(deltaPhi);
    }

    function update2D(controller) {
        var rotatable2D = controller._scene.mapMode2D === MapMode2D.ROTATE;
        if (!Matrix4.equals(Matrix4.IDENTITY, controller._scene.camera.transform)) {
            reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom2D, controller.inertiaZoom, '_lastInertiaZoomMovement');
            if (rotatable2D) {
                reactToInput(controller, controller.enableRotate, controller.translateEventTypes, twist2D, controller.inertiaSpin, '_lastInertiaSpinMovement');
            }
        } else {
            reactToInput(controller, controller.enableTranslate, controller.translateEventTypes, translate2D, controller.inertiaTranslate, '_lastInertiaTranslateMovement');
            reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom2D, controller.inertiaZoom, '_lastInertiaZoomMovement');
            if (rotatable2D) {
                reactToInput(controller, controller.enableRotate, controller.tiltEventTypes, twist2D, controller.inertiaSpin, '_lastInertiaTiltMovement');
            }
        }
    }

    var pickGlobeScratchRay = new Ray();
    var scratchDepthIntersection = new Cartesian3();
    var scratchRayIntersection = new Cartesian3();

    function pickGlobe(controller, mousePosition, result) {
        var scene = controller._scene;
        var globe = controller._globe;
        var camera = scene.camera;

        if (!defined(globe)) {
            return undefined;
        }

        var depthIntersection;
        if (scene.pickPositionSupported) {
            depthIntersection = scene.pickPositionWorldCoordinates(mousePosition, scratchDepthIntersection);
        }

        var ray = camera.getPickRay(mousePosition, pickGlobeScratchRay);
        var rayIntersection = globe.pick(ray, scene, scratchRayIntersection);

        var pickDistance = defined(depthIntersection) ? Cartesian3.distance(depthIntersection, camera.positionWC) : Number.POSITIVE_INFINITY;
        var rayDistance = defined(rayIntersection) ? Cartesian3.distance(rayIntersection, camera.positionWC) : Number.POSITIVE_INFINITY;

        if (pickDistance < rayDistance) {
            return Cartesian3.clone(depthIntersection, result);
        }

        return Cartesian3.clone(rayIntersection, result);
    }

    var translateCVStartRay = new Ray();
    var translateCVEndRay = new Ray();
    var translateCVStartPos = new Cartesian3();
    var translateCVEndPos = new Cartesian3();
    var translatCVDifference = new Cartesian3();
    var translateCVOrigin = new Cartesian3();
    var translateCVPlane = new Plane(Cartesian3.ZERO, 0.0);
    var translateCVStartMouse = new Cartesian2();
    var translateCVEndMouse = new Cartesian2();

    function translateCV(controller, startPosition, movement) {
        if (!Cartesian3.equals(startPosition, controller._translateMousePosition)) {
            controller._looking = false;
        }

        if (!Cartesian3.equals(startPosition, controller._strafeMousePosition)) {
            controller._strafing = false;
        }

        if (controller._looking) {
            look3D(controller, startPosition, movement);
            return;
        }

        if (controller._strafing) {
            strafe(controller, startPosition, movement);
            return;
        }

        var scene = controller._scene;
        var camera = scene.camera;
        var startMouse = Cartesian2.clone(movement.startPosition, translateCVStartMouse);
        var endMouse = Cartesian2.clone(movement.endPosition, translateCVEndMouse);
        var startRay = camera.getPickRay(startMouse, translateCVStartRay);

        var origin = Cartesian3.clone(Cartesian3.ZERO, translateCVOrigin);
        var normal = Cartesian3.UNIT_X;

        var globePos;
        if (camera.position.z < controller._minimumPickingTerrainHeight) {
            globePos = pickGlobe(controller, startMouse, translateCVStartPos);
            if (defined(globePos)) {
                origin.x = globePos.x;
            }
        }

        if (origin.x > camera.position.z && defined(globePos)) {
            Cartesian3.clone(globePos, controller._strafeStartPosition);
            controller._strafing = true;
            strafe(controller, startPosition, movement);
            controller._strafeMousePosition = Cartesian2.clone(startPosition, controller._strafeMousePosition);
            return;
        }

        var plane = Plane.fromPointNormal(origin, normal, translateCVPlane);

        startRay = camera.getPickRay(startMouse, translateCVStartRay);
        var startPlanePos = IntersectionTests.rayPlane(startRay, plane, translateCVStartPos);

        var endRay = camera.getPickRay(endMouse, translateCVEndRay);
        var endPlanePos = IntersectionTests.rayPlane(endRay, plane, translateCVEndPos);

        if (!defined(startPlanePos) || !defined(endPlanePos)) {
            controller._looking = true;
            look3D(controller, startPosition, movement);
            Cartesian2.clone(startPosition, controller._translateMousePosition);
            return;
        }

        var diff = Cartesian3.subtract(startPlanePos, endPlanePos, translatCVDifference);
        var temp = diff.x;
        diff.x = diff.y;
        diff.y = diff.z;
        diff.z = temp;
        var mag = Cartesian3.magnitude(diff);
        if (mag > CesiumMath.EPSILON6) {
            Cartesian3.normalize(diff, diff);
            camera.move(diff, mag);
        }
    }

    var rotateCVWindowPos = new Cartesian2();
    var rotateCVWindowRay = new Ray();
    var rotateCVCenter = new Cartesian3();
    var rotateCVVerticalCenter = new Cartesian3();
    var rotateCVTransform = new Matrix4();
    var rotateCVVerticalTransform = new Matrix4();
    var rotateCVOrigin = new Cartesian3();
    var rotateCVPlane = new Plane(Cartesian3.ZERO, 0.0);
    var rotateCVCartesian3 = new Cartesian3();
    var rotateCVCart = new Cartographic();
    var rotateCVOldTransform = new Matrix4();
    var rotateCVQuaternion = new Quaternion();
    var rotateCVMatrix = new Matrix3();

    function rotateCV(controller, startPosition, movement) {
        if (defined(movement.angleAndHeight)) {
            movement = movement.angleAndHeight;
        }

        if (!Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
            controller._tiltCVOffMap = false;
            controller._looking = false;
        }

        if (controller._looking) {
            look3D(controller, startPosition, movement);
            return;
        }

        var scene = controller._scene;
        var camera = scene.camera;
        var maxCoord = controller._maxCoord;
        var onMap = Math.abs(camera.position.x) - maxCoord.x < 0 && Math.abs(camera.position.y) - maxCoord.y < 0;

        if (controller._tiltCVOffMap || !onMap || camera.position.z > controller._minimumPickingTerrainHeight) {
            controller._tiltCVOffMap = true;
            rotateCVOnPlane(controller, startPosition, movement);
        } else {
            rotateCVOnTerrain(controller, startPosition, movement);
        }
    }

    function rotateCVOnPlane(controller, startPosition, movement) {
        var scene = controller._scene;
        var camera = scene.camera;
        var canvas = scene.canvas;

        var windowPosition = rotateCVWindowPos;
        windowPosition.x = canvas.clientWidth / 2;
        windowPosition.y = canvas.clientHeight / 2;
        var ray = camera.getPickRay(windowPosition, rotateCVWindowRay);
        var normal = Cartesian3.UNIT_X;

        var position = ray.origin;
        var direction = ray.direction;
        var scalar;
        var normalDotDirection = Cartesian3.dot(normal, direction);
        if (Math.abs(normalDotDirection) > CesiumMath.EPSILON6) {
            scalar = -Cartesian3.dot(normal, position) / normalDotDirection;
        }

        if (!defined(scalar) || scalar <= 0.0) {
            controller._looking = true;
            look3D(controller, startPosition, movement);
            Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
            return;
        }

        var center = Cartesian3.multiplyByScalar(direction, scalar, rotateCVCenter);
        Cartesian3.add(position, center, center);

        var projection = scene.mapProjection;
        var ellipsoid = projection.ellipsoid;

        Cartesian3.fromElements(center.y, center.z, center.x, center);
        var cart = projection.unproject(center, rotateCVCart);
        ellipsoid.cartographicToCartesian(cart, center);

        var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, rotateCVTransform);

        var oldGlobe = controller._globe;
        var oldEllipsoid = controller._ellipsoid;
        controller._globe = undefined;
        controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
        controller._rotateFactor = 1.0;
        controller._rotateRateRangeAdjustment = 1.0;

        var oldTransform = Matrix4.clone(camera.transform, rotateCVOldTransform);
        camera._setTransform(transform);

        rotate3D(controller, startPosition, movement, Cartesian3.UNIT_Z);

        camera._setTransform(oldTransform);
        controller._globe = oldGlobe;
        controller._ellipsoid = oldEllipsoid;

        var radius = oldEllipsoid.maximumRadius;
        controller._rotateFactor = 1.0 / radius;
        controller._rotateRateRangeAdjustment = radius;
    }

    function rotateCVOnTerrain(controller, startPosition, movement) {
        var scene = controller._scene;
        var camera = scene.camera;

        var center;
        var ray;
        var normal = Cartesian3.UNIT_X;

        if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
            center = Cartesian3.clone(controller._tiltCenter, rotateCVCenter);
        } else {
            if (camera.position.z < controller._minimumPickingTerrainHeight) {
                center = pickGlobe(controller, startPosition, rotateCVCenter);
            }

            if (!defined(center)) {
                ray = camera.getPickRay(startPosition, rotateCVWindowRay);
                var position = ray.origin;
                var direction = ray.direction;

                var scalar;
                var normalDotDirection = Cartesian3.dot(normal, direction);
                if (Math.abs(normalDotDirection) > CesiumMath.EPSILON6) {
                    scalar = -Cartesian3.dot(normal, position) / normalDotDirection;
                }

                if (!defined(scalar) || scalar <= 0.0) {
                    controller._looking = true;
                    look3D(controller, startPosition, movement);
                    Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
                    return;
                }

                center = Cartesian3.multiplyByScalar(direction, scalar, rotateCVCenter);
                Cartesian3.add(position, center, center);
            }

            Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
            Cartesian3.clone(center, controller._tiltCenter);
        }

        var canvas = scene.canvas;

        var windowPosition = rotateCVWindowPos;
        windowPosition.x = canvas.clientWidth / 2;
        windowPosition.y = controller._tiltCenterMousePosition.y;
        ray = camera.getPickRay(windowPosition, rotateCVWindowRay);

        var origin = Cartesian3.clone(Cartesian3.ZERO, rotateCVOrigin);
        origin.x = center.x;

        var plane = Plane.fromPointNormal(origin, normal, rotateCVPlane);
        var verticalCenter = IntersectionTests.rayPlane(ray, plane, rotateCVVerticalCenter);

        var projection = camera._projection;
        var ellipsoid = projection.ellipsoid;

        Cartesian3.fromElements(center.y, center.z, center.x, center);
        var cart = projection.unproject(center, rotateCVCart);
        ellipsoid.cartographicToCartesian(cart, center);

        var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, rotateCVTransform);

        var verticalTransform;
        if (defined(verticalCenter)) {
            Cartesian3.fromElements(verticalCenter.y, verticalCenter.z, verticalCenter.x, verticalCenter);
            cart = projection.unproject(verticalCenter, rotateCVCart);
            ellipsoid.cartographicToCartesian(cart, verticalCenter);

            verticalTransform = Transforms.eastNorthUpToFixedFrame(verticalCenter, ellipsoid, rotateCVVerticalTransform);
        } else {
            verticalTransform = transform;
        }

        var oldGlobe = controller._globe;
        var oldEllipsoid = controller._ellipsoid;
        controller._globe = undefined;
        controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
        controller._rotateFactor = 1.0;
        controller._rotateRateRangeAdjustment = 1.0;

        var constrainedAxis = Cartesian3.UNIT_Z;

        var oldTransform = Matrix4.clone(camera.transform, rotateCVOldTransform);
        camera._setTransform(transform);

        var tangent = Cartesian3.cross(Cartesian3.UNIT_Z, Cartesian3.normalize(camera.position, rotateCVCartesian3), rotateCVCartesian3);
        var dot = Cartesian3.dot(camera.right, tangent);

        rotate3D(controller, startPosition, movement, constrainedAxis, false, true);

        camera._setTransform(verticalTransform);
        if (dot < 0.0) {
            if (movement.startPosition.y > movement.endPosition.y) {
                constrainedAxis = undefined;
            }

            var oldConstrainedAxis = camera.constrainedAxis;
            camera.constrainedAxis = undefined;

            rotate3D(controller, startPosition, movement, constrainedAxis, true, false);

            camera.constrainedAxis = oldConstrainedAxis;
        } else {
            rotate3D(controller, startPosition, movement, constrainedAxis, true, false);
        }

        if (defined(camera.constrainedAxis)) {
            var right = Cartesian3.cross(camera.direction, camera.constrainedAxis, tilt3DCartesian3);
            if (!Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
                if (Cartesian3.dot(right, camera.right) < 0.0) {
                    Cartesian3.negate(right, right);
                }

                Cartesian3.cross(right, camera.direction, camera.up);
                Cartesian3.cross(camera.direction, camera.up, camera.right);

                Cartesian3.normalize(camera.up, camera.up);
                Cartesian3.normalize(camera.right, camera.right);
            }
        }

        camera._setTransform(oldTransform);
        controller._globe = oldGlobe;
        controller._ellipsoid = oldEllipsoid;

        var radius = oldEllipsoid.maximumRadius;
        controller._rotateFactor = 1.0 / radius;
        controller._rotateRateRangeAdjustment = radius;

        var originalPosition = Cartesian3.clone(camera.positionWC, rotateCVCartesian3);
        camera._adjustHeightForTerrain();

        if (!Cartesian3.equals(camera.positionWC, originalPosition)) {
            camera._setTransform(verticalTransform);
            camera.worldToCameraCoordinatesPoint(originalPosition, originalPosition);

            var magSqrd = Cartesian3.magnitudeSquared(originalPosition);
            if (Cartesian3.magnitudeSquared(camera.position) > magSqrd) {
                Cartesian3.normalize(camera.position, camera.position);
                Cartesian3.multiplyByScalar(camera.position, Math.sqrt(magSqrd), camera.position);
            }

            var angle = Cartesian3.angleBetween(originalPosition, camera.position);
            var axis = Cartesian3.cross(originalPosition, camera.position, originalPosition);
            Cartesian3.normalize(axis, axis);

            var quaternion = Quaternion.fromAxisAngle(axis, angle, rotateCVQuaternion);
            var rotation = Matrix3.fromQuaternion(quaternion, rotateCVMatrix);
            Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
            Matrix3.multiplyByVector(rotation, camera.up, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.cross(camera.right, camera.direction, camera.up);

            camera._setTransform(oldTransform);
        }
    }

    var zoomCVWindowPos = new Cartesian2();
    var zoomCVWindowRay = new Ray();
    var zoomCVIntersection = new Cartesian3();

    function zoomCV(controller, startPosition, movement) {
        if (defined(movement.distance)) {
            movement = movement.distance;
        }

        var scene = controller._scene;
        var camera = scene.camera;
        var canvas = scene.canvas;

        var windowPosition = zoomCVWindowPos;
        windowPosition.x = canvas.clientWidth / 2;
        windowPosition.y = canvas.clientHeight / 2;
        var ray = camera.getPickRay(windowPosition, zoomCVWindowRay);

        var intersection;
        if (camera.position.z < controller._minimumPickingTerrainHeight) {
            intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);
        }

        var distance;
        if (defined(intersection)) {
            distance = Cartesian3.distance(ray.origin, intersection);
        } else {
            var normal = Cartesian3.UNIT_X;
            var position = ray.origin;
            var direction = ray.direction;
            distance = -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction);
        }

        handleZoom(controller, startPosition, movement, controller._zoomFactor, distance);
    }

    function updateCV(controller) {
        var scene = controller._scene;
        var camera = scene.camera;

        if (!Matrix4.equals(Matrix4.IDENTITY, camera.transform)) {
            reactToInput(controller, controller.enableRotate, controller.rotateEventTypes, rotate3D, controller.inertiaSpin, '_lastInertiaSpinMovement');
            reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom3D, controller.inertiaZoom, '_lastInertiaZoomMovement');
        } else {
            var tweens = controller._tweens;

            if (controller._aggregator.anyButtonDown) {
                tweens.removeAll();
            }

            reactToInput(controller, controller.enableTilt, controller.tiltEventTypes, rotateCV, controller.inertiaSpin, '_lastInertiaTiltMovement');
            reactToInput(controller, controller.enableTranslate, controller.translateEventTypes, translateCV, controller.inertiaTranslate, '_lastInertiaTranslateMovement');
            reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoomCV, controller.inertiaZoom, '_lastInertiaZoomMovement');
            reactToInput(controller, controller.enableLook, controller.lookEventTypes, look3D);

            if (!controller._aggregator.anyButtonDown &&
                    (!defined(controller._lastInertiaZoomMovement) || !controller._lastInertiaZoomMovement.active) &&
                    (!defined(controller._lastInertiaTranslateMovement) || !controller._lastInertiaTranslateMovement.active) &&
                    !tweens.contains(controller._tween)) {
                var tween = camera.createCorrectPositionTween(controller.bounceAnimationTime);
                if (defined(tween)) {
                    controller._tween = tweens.add(tween);
                }
            }

            tweens.update();
        }
    }

    var scratchStrafeRay = new Ray();
    var scratchStrafePlane = new Plane(Cartesian3.ZERO, 0.0);
    var scratchStrafeIntersection = new Cartesian3();
    var scratchStrafeDirection = new Cartesian3();

    function strafe(controller, startPosition, movement) {
        var scene = controller._scene;
        var camera = scene.camera;

        var mouseStartPosition = pickGlobe(controller, movement.startPosition, scratchMousePos);
        if (!defined(mouseStartPosition)) {
            return;
        }

        var mousePosition = movement.endPosition;
        var ray = camera.getPickRay(mousePosition, scratchStrafeRay);

        var direction = Cartesian3.clone(camera.direction, scratchStrafeDirection);
        if (scene.mode === SceneMode.COLUMBUS_VIEW) {
            Cartesian3.fromElements(direction.z, direction.x, direction.y, direction);
        }

        var plane = Plane.fromPointNormal(mouseStartPosition, direction, scratchStrafePlane);
        var intersection = IntersectionTests.rayPlane(ray, plane, scratchStrafeIntersection);
        if (!defined(intersection)) {
            return;
        }

        direction = Cartesian3.subtract(mouseStartPosition, intersection, direction);
        if (scene.mode === SceneMode.COLUMBUS_VIEW) {
            Cartesian3.fromElements(direction.y, direction.z, direction.x, direction);
        }

        Cartesian3.add(camera.position, direction, camera.position);
    }

    var spin3DPick = new Cartesian3();
    var scratchCartographic = new Cartographic();
    var scratchMousePos = new Cartesian3();
    var scratchRadii = new Cartesian3();
    var scratchEllipsoid = new Ellipsoid();
    var scratchLookUp = new Cartesian3();

    function spin3D(controller, startPosition, movement) {
        var scene = controller._scene;
        var camera = scene.camera;

        if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
            rotate3D(controller, startPosition, movement);
            return;
        }

        var magnitude;
        var radii;
        var ellipsoid;

        var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, scratchLookUp);

        var height = controller._ellipsoid.cartesianToCartographic(camera.positionWC, scratchCartographic).height;
        var globe = controller._globe;

        var mousePos;
        var tangentPick = false;
        if (defined(globe) && height < controller._minimumPickingTerrainHeight) {
            mousePos = pickGlobe(controller, movement.startPosition, scratchMousePos);
            if (defined(mousePos)) {
                var ray = camera.getPickRay(movement.startPosition, pickGlobeScratchRay);
                var normal = controller._ellipsoid.geodeticSurfaceNormal(mousePos);
                tangentPick = Math.abs(Cartesian3.dot(ray.direction, normal)) < 0.05;

                if (tangentPick && !controller._looking) {
                    controller._rotating = false;
                    controller._strafing = true;
                }
            }
        }

        if (Cartesian2.equals(startPosition, controller._rotateMousePosition)) {
            if (controller._looking) {
                look3D(controller, startPosition, movement, up);
            } else if (controller._rotating) {
                rotate3D(controller, startPosition, movement);
            } else if (controller._strafing) {
                Cartesian3.clone(mousePos, controller._strafeStartPosition);
                strafe(controller, startPosition, movement);
            } else {
                magnitude = Cartesian3.magnitude(controller._rotateStartPosition);
                radii = scratchRadii;
                radii.x = radii.y = radii.z = magnitude;
                ellipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);
                pan3D(controller, startPosition, movement, ellipsoid);
            }
            return;
        } else {
            controller._looking = false;
            controller._rotating = false;
            controller._strafing = false;
        }

        if (defined(globe) && height < controller._minimumPickingTerrainHeight) {
            if (defined(mousePos)) {
                if (Cartesian3.magnitude(camera.position) < Cartesian3.magnitude(mousePos)) {
                    Cartesian3.clone(mousePos, controller._strafeStartPosition);

                    controller._strafing = true;
                    strafe(controller, startPosition, movement);
                } else {
                    magnitude = Cartesian3.magnitude(mousePos);
                    radii = scratchRadii;
                    radii.x = radii.y = radii.z = magnitude;
                    ellipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);
                    pan3D(controller, startPosition, movement, ellipsoid);

                    Cartesian3.clone(mousePos, controller._rotateStartPosition);
                }
            } else {
                controller._looking = true;
                look3D(controller, startPosition, movement, up);
            }
        } else if (defined(camera.pickEllipsoid(movement.startPosition, controller._ellipsoid, spin3DPick))) {
            pan3D(controller, startPosition, movement, controller._ellipsoid);
            Cartesian3.clone(spin3DPick, controller._rotateStartPosition);
        } else if (height > controller._minimumTrackBallHeight) {
            controller._rotating = true;
            rotate3D(controller, startPosition, movement);
        } else {
            controller._looking = true;
            look3D(controller, startPosition, movement, up);
        }

        Cartesian2.clone(startPosition, controller._rotateMousePosition);
    }

    function rotate3D(controller, startPosition, movement, constrainedAxis, rotateOnlyVertical, rotateOnlyHorizontal) {
        rotateOnlyVertical = defaultValue(rotateOnlyVertical, false);
        rotateOnlyHorizontal = defaultValue(rotateOnlyHorizontal, false);

        var scene = controller._scene;
        var camera = scene.camera;
        var canvas = scene.canvas;

        var oldAxis = camera.constrainedAxis;
        if (defined(constrainedAxis)) {
            camera.constrainedAxis = constrainedAxis;
        }

        var rho = Cartesian3.magnitude(camera.position);
        var rotateRate = controller._rotateFactor * (rho - controller._rotateRateRangeAdjustment);

        if (rotateRate > controller._maximumRotateRate) {
            rotateRate = controller._maximumRotateRate;
        }

        if (rotateRate < controller._minimumRotateRate) {
            rotateRate = controller._minimumRotateRate;
        }

        var phiWindowRatio = (movement.startPosition.x - movement.endPosition.x) / canvas.clientWidth;
        var thetaWindowRatio = (movement.startPosition.y - movement.endPosition.y) / canvas.clientHeight;
        phiWindowRatio = Math.min(phiWindowRatio, controller.maximumMovementRatio);
        thetaWindowRatio = Math.min(thetaWindowRatio, controller.maximumMovementRatio);

        var deltaPhi = rotateRate * phiWindowRatio * Math.PI * 2.0;
        var deltaTheta = rotateRate * thetaWindowRatio * Math.PI;

        if (!rotateOnlyVertical) {
            camera.rotateRight(deltaPhi);
        }

        if (!rotateOnlyHorizontal) {
            camera.rotateUp(deltaTheta);
        }

        camera.constrainedAxis = oldAxis;
    }

    var pan3DP0 = Cartesian4.clone(Cartesian4.UNIT_W);
    var pan3DP1 = Cartesian4.clone(Cartesian4.UNIT_W);
    var pan3DTemp0 = new Cartesian3();
    var pan3DTemp1 = new Cartesian3();
    var pan3DTemp2 = new Cartesian3();
    var pan3DTemp3 = new Cartesian3();
    var pan3DStartMousePosition = new Cartesian2();
    var pan3DEndMousePosition = new Cartesian2();

    function pan3D(controller, startPosition, movement, ellipsoid) {
        var scene = controller._scene;
        var camera = scene.camera;

        var startMousePosition = Cartesian2.clone(movement.startPosition, pan3DStartMousePosition);
        var endMousePosition = Cartesian2.clone(movement.endPosition, pan3DEndMousePosition);

        var p0 = camera.pickEllipsoid(startMousePosition, ellipsoid, pan3DP0);
        var p1 = camera.pickEllipsoid(endMousePosition, ellipsoid, pan3DP1);

        if (!defined(p0) || !defined(p1)) {
            controller._rotating = true;
            rotate3D(controller, startPosition, movement);
            return;
        }

        p0 = camera.worldToCameraCoordinates(p0, p0);
        p1 = camera.worldToCameraCoordinates(p1, p1);

        if (!defined(camera.constrainedAxis)) {
            Cartesian3.normalize(p0, p0);
            Cartesian3.normalize(p1, p1);
            var dot = Cartesian3.dot(p0, p1);
            var axis = Cartesian3.cross(p0, p1, pan3DTemp0);

            if (dot < 1.0 && !Cartesian3.equalsEpsilon(axis, Cartesian3.ZERO, CesiumMath.EPSILON14)) { // dot is in [0, 1]
                var angle = Math.acos(dot);
                camera.rotate(axis, angle);
            }
        } else {
            var basis0 = camera.constrainedAxis;
            var basis1 = Cartesian3.mostOrthogonalAxis(basis0, pan3DTemp0);
            Cartesian3.cross(basis1, basis0, basis1);
            Cartesian3.normalize(basis1, basis1);
            var basis2 = Cartesian3.cross(basis0, basis1, pan3DTemp1);

            var startRho = Cartesian3.magnitude(p0);
            var startDot = Cartesian3.dot(basis0, p0);
            var startTheta = Math.acos(startDot / startRho);
            var startRej = Cartesian3.multiplyByScalar(basis0, startDot, pan3DTemp2);
            Cartesian3.subtract(p0, startRej, startRej);
            Cartesian3.normalize(startRej, startRej);

            var endRho = Cartesian3.magnitude(p1);
            var endDot = Cartesian3.dot(basis0, p1);
            var endTheta = Math.acos(endDot / endRho);
            var endRej = Cartesian3.multiplyByScalar(basis0, endDot, pan3DTemp3);
            Cartesian3.subtract(p1, endRej, endRej);
            Cartesian3.normalize(endRej, endRej);

            var startPhi = Math.acos(Cartesian3.dot(startRej, basis1));
            if (Cartesian3.dot(startRej, basis2) < 0) {
                startPhi = CesiumMath.TWO_PI - startPhi;
            }

            var endPhi = Math.acos(Cartesian3.dot(endRej, basis1));
            if (Cartesian3.dot(endRej, basis2) < 0) {
                endPhi = CesiumMath.TWO_PI - endPhi;
            }

            var deltaPhi = startPhi - endPhi;

            var east;
            if (Cartesian3.equalsEpsilon(basis0, camera.position, CesiumMath.EPSILON2)) {
                east = camera.right;
            } else {
                east = Cartesian3.cross(basis0, camera.position, pan3DTemp0);
            }

            var planeNormal = Cartesian3.cross(basis0, east, pan3DTemp0);
            var side0 = Cartesian3.dot(planeNormal, Cartesian3.subtract(p0, basis0, pan3DTemp1));
            var side1 = Cartesian3.dot(planeNormal, Cartesian3.subtract(p1, basis0, pan3DTemp1));

            var deltaTheta;
            if (side0 > 0 && side1 > 0) {
                deltaTheta = endTheta - startTheta;
            } else if (side0 > 0 && side1 <= 0) {
                if (Cartesian3.dot(camera.position, basis0) > 0) {
                    deltaTheta = -startTheta - endTheta;
                } else {
                    deltaTheta = startTheta + endTheta;
                }
            } else {
                deltaTheta = startTheta - endTheta;
            }

            camera.rotateRight(deltaPhi);
            camera.rotateUp(deltaTheta);
        }
    }

    var zoom3DUnitPosition = new Cartesian3();
    var zoom3DCartographic = new Cartographic();

    function zoom3D(controller, startPosition, movement) {
        if (defined(movement.distance)) {
            movement = movement.distance;
        }

        var ellipsoid = controller._ellipsoid;
        var scene = controller._scene;
        var camera = scene.camera;
        var canvas = scene.canvas;

        var windowPosition = zoomCVWindowPos;
        windowPosition.x = canvas.clientWidth / 2;
        windowPosition.y = canvas.clientHeight / 2;
        var ray = camera.getPickRay(windowPosition, zoomCVWindowRay);

        var intersection;
        var height = ellipsoid.cartesianToCartographic(camera.position, zoom3DCartographic).height;
        if (height < controller._minimumPickingTerrainHeight) {
            intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);
        }

        var distance;
        if (defined(intersection)) {
            distance = Cartesian3.distance(ray.origin, intersection);
        } else {
            distance = height;
        }

        var unitPosition = Cartesian3.normalize(camera.position, zoom3DUnitPosition);
        handleZoom(controller, startPosition, movement, controller._zoomFactor, distance, Cartesian3.dot(unitPosition, camera.direction));
    }

    var tilt3DWindowPos = new Cartesian2();
    var tilt3DRay = new Ray();
    var tilt3DCenter = new Cartesian3();
    var tilt3DVerticalCenter = new Cartesian3();
    var tilt3DTransform = new Matrix4();
    var tilt3DVerticalTransform = new Matrix4();
    var tilt3DCartesian3 = new Cartesian3();
    var tilt3DOldTransform = new Matrix4();
    var tilt3DQuaternion = new Quaternion();
    var tilt3DMatrix = new Matrix3();
    var tilt3DCart = new Cartographic();
    var tilt3DLookUp = new Cartesian3();

    function tilt3D(controller, startPosition, movement) {
        var scene = controller._scene;
        var camera = scene.camera;

        if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
            return;
        }

        if (defined(movement.angleAndHeight)) {
            movement = movement.angleAndHeight;
        }

        if (!Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
            controller._tiltOnEllipsoid = false;
            controller._looking = false;
        }

        if (controller._looking) {
            var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, tilt3DLookUp);
            look3D(controller, startPosition, movement, up);
            return;
        }

        var ellipsoid = controller._ellipsoid;
        var cartographic = ellipsoid.cartesianToCartographic(camera.position, tilt3DCart);

        if (controller._tiltOnEllipsoid || cartographic.height > controller._minimumCollisionTerrainHeight) {
            controller._tiltOnEllipsoid = true;
            tilt3DOnEllipsoid(controller, startPosition, movement);
        } else {
            tilt3DOnTerrain(controller, startPosition, movement);
        }
    }

    var tilt3DOnEllipsoidCartographic = new Cartographic();

    function tilt3DOnEllipsoid(controller, startPosition, movement) {
        var ellipsoid = controller._ellipsoid;
        var scene = controller._scene;
        var camera = scene.camera;
        var minHeight = controller.minimumZoomDistance * 0.25;
        var height = ellipsoid.cartesianToCartographic(camera.positionWC, tilt3DOnEllipsoidCartographic).height;
        if (height - minHeight - 1.0 < CesiumMath.EPSILON3 &&
                movement.endPosition.y - movement.startPosition.y < 0) {
            return;
        }

        var canvas = scene.canvas;

        var windowPosition = tilt3DWindowPos;
        windowPosition.x = canvas.clientWidth / 2;
        windowPosition.y = canvas.clientHeight / 2;
        var ray = camera.getPickRay(windowPosition, tilt3DRay);

        var center;
        var intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);
        if (defined(intersection)) {
            center = Ray.getPoint(ray, intersection.start, tilt3DCenter);
        } else if (height > controller._minimumTrackBallHeight) {
            var grazingAltitudeLocation = IntersectionTests.grazingAltitudeLocation(ray, ellipsoid);
            if (!defined(grazingAltitudeLocation)) {
                return;
            }
            var grazingAltitudeCart = ellipsoid.cartesianToCartographic(grazingAltitudeLocation, tilt3DCart);
            grazingAltitudeCart.height = 0.0;
            center = ellipsoid.cartographicToCartesian(grazingAltitudeCart, tilt3DCenter);
        } else {
            controller._looking = true;
            var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, tilt3DLookUp);
            look3D(controller, startPosition, movement, up);
            Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
            return;
        }

        var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, tilt3DTransform);

        var oldGlobe = controller._globe;
        var oldEllipsoid = controller._ellipsoid;
        controller._globe = undefined;
        controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
        controller._rotateFactor = 1.0;
        controller._rotateRateRangeAdjustment = 1.0;

        var oldTransform = Matrix4.clone(camera.transform, tilt3DOldTransform);
        camera._setTransform(transform);

        rotate3D(controller, startPosition, movement, Cartesian3.UNIT_Z);

        camera._setTransform(oldTransform);
        controller._globe = oldGlobe;
        controller._ellipsoid = oldEllipsoid;

        var radius = oldEllipsoid.maximumRadius;
        controller._rotateFactor = 1.0 / radius;
        controller._rotateRateRangeAdjustment = radius;
    }

    function tilt3DOnTerrain(controller, startPosition, movement) {
        var ellipsoid = controller._ellipsoid;
        var scene = controller._scene;
        var camera = scene.camera;

        var center;
        var ray;
        var intersection;

        if (Cartesian2.equals(startPosition, controller._tiltCenterMousePosition)) {
            center = Cartesian3.clone(controller._tiltCenter, tilt3DCenter);
        } else {
            center = pickGlobe(controller, startPosition, tilt3DCenter);

            if (!defined(center)) {
                ray = camera.getPickRay(startPosition, tilt3DRay);
                intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid);
                if (!defined(intersection)) {
                    var cartographic = ellipsoid.cartesianToCartographic(camera.position, tilt3DCart);
                    if (cartographic.height <= controller._minimumTrackBallHeight) {
                        controller._looking = true;
                        var up = controller._ellipsoid.geodeticSurfaceNormal(camera.position, tilt3DLookUp);
                        look3D(controller, startPosition, movement, up);
                        Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
                    }
                    return;
                }
                center = Ray.getPoint(ray, intersection.start, tilt3DCenter);
            }

            Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
            Cartesian3.clone(center, controller._tiltCenter);
        }

        var canvas = scene.canvas;

        var windowPosition = tilt3DWindowPos;
        windowPosition.x = canvas.clientWidth / 2;
        windowPosition.y = controller._tiltCenterMousePosition.y;
        ray = camera.getPickRay(windowPosition, tilt3DRay);

        var mag = Cartesian3.magnitude(center);
        var radii = Cartesian3.fromElements(mag, mag, mag, scratchRadii);
        var newEllipsoid = Ellipsoid.fromCartesian3(radii, scratchEllipsoid);

        intersection = IntersectionTests.rayEllipsoid(ray, newEllipsoid);
        if (!defined(intersection)) {
            return;
        }

        var t = Cartesian3.magnitude(ray.origin) > mag ? intersection.start : intersection.stop;
        var verticalCenter = Ray.getPoint(ray, t, tilt3DVerticalCenter);

        var transform = Transforms.eastNorthUpToFixedFrame(center, ellipsoid, tilt3DTransform);
        var verticalTransform = Transforms.eastNorthUpToFixedFrame(verticalCenter, newEllipsoid, tilt3DVerticalTransform);

        var oldGlobe = controller._globe;
        var oldEllipsoid = controller._ellipsoid;
        controller._globe = undefined;
        controller._ellipsoid = Ellipsoid.UNIT_SPHERE;
        controller._rotateFactor = 1.0;
        controller._rotateRateRangeAdjustment = 1.0;

        var constrainedAxis = Cartesian3.UNIT_Z;

        var oldTransform = Matrix4.clone(camera.transform, tilt3DOldTransform);
        camera._setTransform(transform);

        var tangent = Cartesian3.cross(verticalCenter, camera.positionWC, tilt3DCartesian3);
        var dot = Cartesian3.dot(camera.rightWC, tangent);

        rotate3D(controller, startPosition, movement, constrainedAxis, false, true);

        camera._setTransform(verticalTransform);

        if (dot < 0.0) {
            if (movement.startPosition.y > movement.endPosition.y) {
                constrainedAxis = undefined;
            }

            var oldConstrainedAxis = camera.constrainedAxis;
            camera.constrainedAxis = undefined;

            rotate3D(controller, startPosition, movement, constrainedAxis, true, false);

            camera.constrainedAxis = oldConstrainedAxis;
        } else {
            rotate3D(controller, startPosition, movement, constrainedAxis, true, false);
        }

        if (defined(camera.constrainedAxis)) {
            var right = Cartesian3.cross(camera.direction, camera.constrainedAxis, tilt3DCartesian3);
            if (!Cartesian3.equalsEpsilon(right, Cartesian3.ZERO, CesiumMath.EPSILON6)) {
                if (Cartesian3.dot(right, camera.right) < 0.0) {
                    Cartesian3.negate(right, right);
                }

                Cartesian3.cross(right, camera.direction, camera.up);
                Cartesian3.cross(camera.direction, camera.up, camera.right);

                Cartesian3.normalize(camera.up, camera.up);
                Cartesian3.normalize(camera.right, camera.right);
            }
        }

        camera._setTransform(oldTransform);
        controller._globe = oldGlobe;
        controller._ellipsoid = oldEllipsoid;

        var radius = oldEllipsoid.maximumRadius;
        controller._rotateFactor = 1.0 / radius;
        controller._rotateRateRangeAdjustment = radius;

        var originalPosition = Cartesian3.clone(camera.positionWC, tilt3DCartesian3);
        camera._adjustHeightForTerrain();

        if (!Cartesian3.equals(camera.positionWC, originalPosition)) {
            camera._setTransform(verticalTransform);
            camera.worldToCameraCoordinatesPoint(originalPosition, originalPosition);

            var magSqrd = Cartesian3.magnitudeSquared(originalPosition);
            if (Cartesian3.magnitudeSquared(camera.position) > magSqrd) {
                Cartesian3.normalize(camera.position, camera.position);
                Cartesian3.multiplyByScalar(camera.position, Math.sqrt(magSqrd), camera.position);
            }

            var angle = Cartesian3.angleBetween(originalPosition, camera.position);
            var axis = Cartesian3.cross(originalPosition, camera.position, originalPosition);
            Cartesian3.normalize(axis, axis);

            var quaternion = Quaternion.fromAxisAngle(axis, angle, tilt3DQuaternion);
            var rotation = Matrix3.fromQuaternion(quaternion, tilt3DMatrix);
            Matrix3.multiplyByVector(rotation, camera.direction, camera.direction);
            Matrix3.multiplyByVector(rotation, camera.up, camera.up);
            Cartesian3.cross(camera.direction, camera.up, camera.right);
            Cartesian3.cross(camera.right, camera.direction, camera.up);

            camera._setTransform(oldTransform);
        }
    }

    var look3DStartPos = new Cartesian2();
    var look3DEndPos = new Cartesian2();
    var look3DStartRay = new Ray();
    var look3DEndRay = new Ray();
    var look3DNegativeRot = new Cartesian3();
    var look3DTan = new Cartesian3();

    function look3D(controller, startPosition, movement, rotationAxis) {
        var scene = controller._scene;
        var camera = scene.camera;

        var startPos = look3DStartPos;
        startPos.x = movement.startPosition.x;
        startPos.y = 0.0;
        var endPos = look3DEndPos;
        endPos.x = movement.endPosition.x;
        endPos.y = 0.0;
        var start = camera.getPickRay(startPos, look3DStartRay).direction;
        var end = camera.getPickRay(endPos, look3DEndRay).direction;

        var angle = 0.0;
        var dot = Cartesian3.dot(start, end);
        if (dot < 1.0) { // dot is in [0, 1]
            angle = Math.acos(dot);
        }
        angle = (movement.startPosition.x > movement.endPosition.x) ? -angle : angle;

        var horizontalRotationAxis = controller._horizontalRotationAxis;
        if (defined(rotationAxis)) {
            camera.look(rotationAxis, -angle);
        } else if (defined(horizontalRotationAxis)) {
            camera.look(horizontalRotationAxis, -angle);
        } else {
            camera.lookLeft(angle);
        }

        startPos.x = 0.0;
        startPos.y = movement.startPosition.y;
        endPos.x = 0.0;
        endPos.y = movement.endPosition.y;
        start = camera.getPickRay(startPos, look3DStartRay).direction;
        end = camera.getPickRay(endPos, look3DEndRay).direction;

        angle = 0.0;
        dot = Cartesian3.dot(start, end);
        if (dot < 1.0) { // dot is in [0, 1]
            angle = Math.acos(dot);
        }
        angle = (movement.startPosition.y > movement.endPosition.y) ? -angle : angle;

        rotationAxis = defaultValue(rotationAxis, horizontalRotationAxis);
        if (defined(rotationAxis)) {
            var direction = camera.direction;
            var negativeRotationAxis = Cartesian3.negate(rotationAxis, look3DNegativeRot);
            var northParallel = Cartesian3.equalsEpsilon(direction, rotationAxis, CesiumMath.EPSILON2);
            var southParallel = Cartesian3.equalsEpsilon(direction, negativeRotationAxis, CesiumMath.EPSILON2);
            if ((!northParallel && !southParallel)) {
                dot = Cartesian3.dot(direction, rotationAxis);
                var angleToAxis = CesiumMath.acosClamped(dot);
                if (angle > 0 && angle > angleToAxis) {
                    angle = angleToAxis - CesiumMath.EPSILON4;
                }

                dot = Cartesian3.dot(direction, negativeRotationAxis);
                angleToAxis = CesiumMath.acosClamped(dot);
                if (angle < 0 && -angle > angleToAxis) {
                    angle = -angleToAxis + CesiumMath.EPSILON4;
                }

                var tangent = Cartesian3.cross(rotationAxis, direction, look3DTan);
                camera.look(tangent, angle);
            } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) {
                camera.look(camera.right, -angle);
            }
        } else {
            camera.lookUp(angle);
        }
    }

    function update3D(controller) {
        reactToInput(controller, controller.enableRotate, controller.rotateEventTypes, spin3D, controller.inertiaSpin, '_lastInertiaSpinMovement');
        reactToInput(controller, controller.enableZoom, controller.zoomEventTypes, zoom3D, controller.inertiaZoom, '_lastInertiaZoomMovement');
        reactToInput(controller, controller.enableTilt, controller.tiltEventTypes, tilt3D, controller.inertiaSpin, '_lastInertiaTiltMovement');
        reactToInput(controller, controller.enableLook, controller.lookEventTypes, look3D);
    }

    /**
     * @private
     */
    ScreenSpaceCameraController.prototype.update = function() {
        if (!Matrix4.equals(this._scene.camera.transform, Matrix4.IDENTITY)) {
            this._globe = undefined;
            this._ellipsoid = Ellipsoid.UNIT_SPHERE;
        } else {
            this._globe = this._scene.globe;
            this._ellipsoid = defined(this._globe) ? this._globe.ellipsoid : this._scene.mapProjection.ellipsoid;
        }

        this._minimumCollisionTerrainHeight = this.minimumCollisionTerrainHeight * this._scene.terrainExaggeration;
        this._minimumPickingTerrainHeight = this.minimumPickingTerrainHeight * this._scene.terrainExaggeration;
        this._minimumTrackBallHeight = this.minimumTrackBallHeight * this._scene.terrainExaggeration;

        var radius = this._ellipsoid.maximumRadius;
        this._rotateFactor = 1.0 / radius;
        this._rotateRateRangeAdjustment = radius;

        var scene = this._scene;
        var mode = scene.mode;
        if (mode === SceneMode.SCENE2D) {
            update2D(this);
        } else if (mode === SceneMode.COLUMBUS_VIEW) {
            this._horizontalRotationAxis = Cartesian3.UNIT_Z;
            updateCV(this);
        } else if (mode === SceneMode.SCENE3D) {
            this._horizontalRotationAxis = undefined;
            update3D(this);
        }

        this._aggregator.reset();
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see ScreenSpaceCameraController#destroy
     */
    ScreenSpaceCameraController.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Removes mouse listeners held by this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * controller = controller && controller.destroy();
     *
     * @see ScreenSpaceCameraController#isDestroyed
     */
    ScreenSpaceCameraController.prototype.destroy = function() {
        this._tweens.removeAll();
        this._aggregator = this._aggregator && this._aggregator.destroy();
        return destroyObject(this);
    };

    return ScreenSpaceCameraController;
});

/*global define*/
define('Scene/ShadowMapShader',[
        '../Core/defined',
        '../Renderer/ShaderSource'
    ], function(
        defined,
        ShaderSource) {
    'use strict';

    /**
     * @private
     */
    function ShadowMapShader() {
    }

    ShadowMapShader.getShadowCastShaderKeyword = function(isPointLight, isTerrain, usesDepthTexture, isOpaque) {
        return 'castShadow ' + isPointLight + ' ' + isTerrain + ' ' + usesDepthTexture + ' ' + isOpaque;
    };

    ShadowMapShader.createShadowCastVertexShader = function(vs, isPointLight, isTerrain) {
        var defines = vs.defines.slice(0);
        var sources = vs.sources.slice(0);

        if (isTerrain) {
            defines.push('GENERATE_POSITION');
        }

        var positionVaryingName = ShaderSource.findPositionVarying(vs);
        var hasPositionVarying = defined(positionVaryingName);

        if (isPointLight && !hasPositionVarying) {
            var length = sources.length;
            for (var j = 0; j < length; ++j) {
                sources[j] = ShaderSource.replaceMain(sources[j], 'czm_shadow_cast_main');
            }

            var shadowVS =
                'varying vec3 v_positionEC; \n' +
                'void main() \n' +
                '{ \n' +
                '    czm_shadow_cast_main(); \n' +
                '    v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n' +
                '}';
            sources.push(shadowVS);
        }

        return new ShaderSource({
            defines : defines,
            sources : sources
        });
    };

    ShadowMapShader.createShadowCastFragmentShader = function(fs, isPointLight, usesDepthTexture, opaque) {
        var defines = fs.defines.slice(0);
        var sources = fs.sources.slice(0);

        var positionVaryingName = ShaderSource.findPositionVarying(fs);
        var hasPositionVarying = defined(positionVaryingName);
        if (!hasPositionVarying) {
            positionVaryingName = 'v_positionEC';
        }

        var length = sources.length;
        for (var i = 0; i < length; ++i) {
            sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_cast_main');
        }

        var fsSource = '';

        if (isPointLight) {
            if (!hasPositionVarying) {
                fsSource += 'varying vec3 v_positionEC; \n';
            }
            fsSource += 'uniform vec4 shadowMap_lightPositionEC; \n';
        }

        if (opaque) {
            fsSource +=
                'void main() \n' +
                '{ \n';
        } else {
            fsSource +=
                'void main() \n' +
                '{ \n' +
                '    czm_shadow_cast_main(); \n' +
                '    if (gl_FragColor.a == 0.0) \n' +
                '    { \n' +
                '       discard; \n' +
                '    } \n';
        }

        if (isPointLight) {
            fsSource +=
                'float distance = length(' + positionVaryingName + '); \n' +
                'distance /= shadowMap_lightPositionEC.w; // radius \n' +
                'gl_FragColor = czm_packDepth(distance); \n';
        } else if (usesDepthTexture) {
            fsSource += 'gl_FragColor = vec4(1.0); \n';
        } else {
            fsSource += 'gl_FragColor = czm_packDepth(gl_FragCoord.z); \n';
        }

        fsSource += '} \n';

        sources.push(fsSource);

        return new ShaderSource({
            defines : defines,
            sources : sources
        });
    };

    ShadowMapShader.getShadowReceiveShaderKeyword = function(shadowMap, castShadows, isTerrain, hasTerrainNormal) {
        var usesDepthTexture = shadowMap._usesDepthTexture;
        var polygonOffsetSupported = shadowMap._polygonOffsetSupported;
        var isPointLight = shadowMap._isPointLight;
        var isSpotLight = shadowMap._isSpotLight;
        var hasCascades = shadowMap._numberOfCascades > 1;
        var debugCascadeColors = shadowMap.debugCascadeColors;
        var softShadows = shadowMap.softShadows;

        return 'receiveShadow ' + usesDepthTexture + polygonOffsetSupported + isPointLight + isSpotLight +
               hasCascades + debugCascadeColors + softShadows + castShadows + isTerrain + hasTerrainNormal;
    };

    ShadowMapShader.createShadowReceiveVertexShader = function(vs, isTerrain, hasTerrainNormal) {
        var defines = vs.defines.slice(0);
        var sources = vs.sources.slice(0);

        if (isTerrain) {
            if (hasTerrainNormal) {
                defines.push('GENERATE_POSITION_AND_NORMAL');
            } else {
                defines.push('GENERATE_POSITION');
            }
        }

        return new ShaderSource({
            defines : defines,
            sources : sources
        });
    };

    ShadowMapShader.createShadowReceiveFragmentShader = function(fs, shadowMap, castShadows, isTerrain, hasTerrainNormal) {
        var normalVaryingName = ShaderSource.findNormalVarying(fs);
        var hasNormalVarying = (!isTerrain && defined(normalVaryingName)) || (isTerrain && hasTerrainNormal);

        var positionVaryingName = ShaderSource.findPositionVarying(fs);
        var hasPositionVarying = defined(positionVaryingName);

        var usesDepthTexture = shadowMap._usesDepthTexture;
        var polygonOffsetSupported = shadowMap._polygonOffsetSupported;
        var isPointLight = shadowMap._isPointLight;
        var isSpotLight = shadowMap._isSpotLight;
        var hasCascades = shadowMap._numberOfCascades > 1;
        var debugCascadeColors = shadowMap.debugCascadeColors;
        var softShadows = shadowMap.softShadows;
        var bias = isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);

        var defines = fs.defines.slice(0);
        var sources = fs.sources.slice(0);

        var length = sources.length;
        for (var i = 0; i < length; ++i) {
            sources[i] = ShaderSource.replaceMain(sources[i], 'czm_shadow_receive_main');
        }

        if (isPointLight) {
            defines.push('USE_CUBE_MAP_SHADOW');
        } else if (usesDepthTexture) {
            defines.push('USE_SHADOW_DEPTH_TEXTURE');
        }

        if (softShadows && !isPointLight) {
            defines.push('USE_SOFT_SHADOWS');
        }

        // Enable day-night shading so that the globe is dark when the light is below the horizon
        if (hasCascades && castShadows && isTerrain) {
            if (hasNormalVarying) {
                defines.push('ENABLE_VERTEX_LIGHTING');
            } else {
                defines.push('ENABLE_DAYNIGHT_SHADING');
            }
        }

        if (castShadows && bias.normalShading && hasNormalVarying) {
            defines.push('USE_NORMAL_SHADING');
            if (bias.normalShadingSmooth > 0.0) {
                defines.push('USE_NORMAL_SHADING_SMOOTH');
            }
        }

        var fsSource = '';

        if (isPointLight) {
            fsSource += 'uniform samplerCube shadowMap_textureCube; \n';
        } else {
            fsSource += 'uniform sampler2D shadowMap_texture; \n';
        }

        fsSource +=
            'uniform mat4 shadowMap_matrix; \n' +
            'uniform vec3 shadowMap_lightDirectionEC; \n' +
            'uniform vec4 shadowMap_lightPositionEC; \n' +
            'uniform vec4 shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness; \n' +
            'uniform vec4 shadowMap_texelSizeDepthBiasAndNormalShadingSmooth; \n' +
            'vec4 getPositionEC() \n' +
            '{ \n' +
            (hasPositionVarying ?
            '    return vec4(' + positionVaryingName + ', 1.0); \n' :
            '    return czm_windowToEyeCoordinates(gl_FragCoord); \n') +
            '} \n' +
            'vec3 getNormalEC() \n' +
            '{ \n' +
            (hasNormalVarying ?
            '    return normalize(' + normalVaryingName + '); \n' :
            '    return vec3(1.0); \n') +
            '} \n' +

            // Offset the shadow position in the direction of the normal for perpendicular and back faces
            'void applyNormalOffset(inout vec4 positionEC, vec3 normalEC, float nDotL) \n' +
            '{ \n' +
            (bias.normalOffset && hasNormalVarying ?
            '    float normalOffset = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.x; \n' +
            '    float normalOffsetScale = 1.0 - nDotL; \n' +
            '    vec3 offset = normalOffset * normalOffsetScale * normalEC; \n' +
            '    positionEC.xyz += offset; \n' : '') +
            '} \n';

        fsSource +=
            'void main() \n' +
            '{ \n' +
            '    czm_shadow_receive_main(); \n' +
            '    vec4 positionEC = getPositionEC(); \n' +
            '    vec3 normalEC = getNormalEC(); \n' +
            '    float depth = -positionEC.z; \n';

        fsSource +=
            '    czm_shadowParameters shadowParameters; \n' +
            '    shadowParameters.texelStepSize = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.xy; \n' +
            '    shadowParameters.depthBias = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.z; \n' +
            '    shadowParameters.normalShadingSmooth = shadowMap_texelSizeDepthBiasAndNormalShadingSmooth.w; \n' +
            '    shadowParameters.darkness = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.w; \n';

        if (isTerrain) {
            // Scale depth bias based on view distance to reduce z-fighting in distant terrain
            fsSource += '    shadowParameters.depthBias *= max(depth * 0.01, 1.0); \n';
        } else if (!polygonOffsetSupported) {
            // If polygon offset isn't supported push the depth back based on view, however this
            // causes light leaking at further away views
            fsSource += '    shadowParameters.depthBias *= mix(1.0, 100.0, depth * 0.0015); \n';
        }

        if (isPointLight) {
            fsSource +=
                '    vec3 directionEC = positionEC.xyz - shadowMap_lightPositionEC.xyz; \n' +
                '    float distance = length(directionEC); \n' +
                '    directionEC = normalize(directionEC); \n' +
                '    float radius = shadowMap_lightPositionEC.w; \n' +
                '    // Stop early if the fragment is beyond the point light radius \n' +
                '    if (distance > radius) \n' +
                '    { \n' +
                '        return; \n' +
                '    } \n' +
                '    vec3 directionWC  = czm_inverseViewRotation * directionEC; \n' +

                '    shadowParameters.depth = distance / radius; \n' +
                '    shadowParameters.nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n' +

                '    shadowParameters.texCoords = directionWC; \n' +
                '    float visibility = czm_shadowVisibility(shadowMap_textureCube, shadowParameters); \n';
        } else if (isSpotLight) {
            fsSource +=
                '    vec3 directionEC = normalize(positionEC.xyz - shadowMap_lightPositionEC.xyz); \n' +
                '    float nDotL = clamp(dot(normalEC, -directionEC), 0.0, 1.0); \n' +
                '    applyNormalOffset(positionEC, normalEC, nDotL); \n' +

                '    vec4 shadowPosition = shadowMap_matrix * positionEC; \n' +
                '    // Spot light uses a perspective projection, so perform the perspective divide \n' +
                '    shadowPosition /= shadowPosition.w; \n' +

                '    // Stop early if the fragment is not in the shadow bounds \n' +
                '    if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n' +
                '    { \n' +
                '        return; \n' +
                '    } \n' +

                '    shadowParameters.texCoords = shadowPosition.xy; \n' +
                '    shadowParameters.depth = shadowPosition.z; \n' +
                '    shadowParameters.nDotL = nDotL; \n' +

                '    float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n';
        } else if (hasCascades) {
            fsSource +=
                '    float maxDepth = shadowMap_cascadeSplits[1].w; \n' +

                '    // Stop early if the eye depth exceeds the last cascade \n' +
                '    if (depth > maxDepth) \n' +
                '    { \n' +
                '        return; \n' +
                '    } \n' +

                '    // Get the cascade based on the eye-space depth \n' +
                '    vec4 weights = czm_cascadeWeights(depth); \n' +

                '    // Apply normal offset \n' +
                '    float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n' +
                '    applyNormalOffset(positionEC, normalEC, nDotL); \n' +

                '    // Transform position into the cascade \n' +
                '    vec4 shadowPosition = czm_cascadeMatrix(weights) * positionEC; \n' +

                '    // Get visibility \n' +
                '    shadowParameters.texCoords = shadowPosition.xy; \n' +
                '    shadowParameters.depth = shadowPosition.z; \n' +
                '    shadowParameters.nDotL = nDotL; \n' +
                '    float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n' +

                '    // Fade out shadows that are far away \n' +
                '    float shadowMapMaximumDistance = shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness.z; \n' +
                '    float fade = max((depth - shadowMapMaximumDistance * 0.8) / (shadowMapMaximumDistance * 0.2), 0.0); \n' +
                '    visibility = mix(visibility, 1.0, fade); \n' +

                (debugCascadeColors ?
                '    // Draw cascade colors for debugging \n' +
                '    gl_FragColor *= czm_cascadeColor(weights); \n' : '');
        } else {
            fsSource +=
                '    float nDotL = clamp(dot(normalEC, shadowMap_lightDirectionEC), 0.0, 1.0); \n' +
                '    applyNormalOffset(positionEC, normalEC, nDotL); \n' +
                '    vec4 shadowPosition = shadowMap_matrix * positionEC; \n' +

                '    // Stop early if the fragment is not in the shadow bounds \n' +
                '    if (any(lessThan(shadowPosition.xyz, vec3(0.0))) || any(greaterThan(shadowPosition.xyz, vec3(1.0)))) \n' +
                '    { \n' +
                '        return; \n' +
                '    } \n' +

                '    shadowParameters.texCoords = shadowPosition.xy; \n' +
                '    shadowParameters.depth = shadowPosition.z; \n' +
                '    shadowParameters.nDotL = nDotL; \n' +
                '    float visibility = czm_shadowVisibility(shadowMap_texture, shadowParameters); \n';
        }

        fsSource +=
            '    gl_FragColor.rgb *= visibility; \n' +
            '} \n';

        sources.push(fsSource);

        return new ShaderSource({
            defines : defines,
            sources : sources
        });
    };

    return ShadowMapShader;
});

/*global define*/
define('Scene/ShadowMap',[
        '../Core/BoundingRectangle',
        '../Core/BoundingSphere',
        '../Core/BoxOutlineGeometry',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/clone',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/FeatureDetection',
        '../Core/GeometryInstance',
        '../Core/Intersect',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/PixelFormat',
        '../Core/Quaternion',
        '../Core/SphereOutlineGeometry',
        '../Core/WebGLConstants',
        '../Renderer/ClearCommand',
        '../Renderer/ContextLimits',
        '../Renderer/CubeMap',
        '../Renderer/DrawCommand',
        '../Renderer/Framebuffer',
        '../Renderer/Pass',
        '../Renderer/PassState',
        '../Renderer/PixelDatatype',
        '../Renderer/Renderbuffer',
        '../Renderer/RenderbufferFormat',
        '../Renderer/RenderState',
        '../Renderer/Sampler',
        '../Renderer/Texture',
        '../Renderer/TextureMagnificationFilter',
        '../Renderer/TextureMinificationFilter',
        '../Renderer/TextureWrap',
        './Camera',
        './CullFace',
        './CullingVolume',
        './DebugCameraPrimitive',
        './OrthographicFrustum',
        './PerInstanceColorAppearance',
        './PerspectiveFrustum',
        './Primitive',
        './ShadowMapShader'
    ], function(
        BoundingRectangle,
        BoundingSphere,
        BoxOutlineGeometry,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        clone,
        Color,
        ColorGeometryInstanceAttribute,
        combine,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        GeometryInstance,
        Intersect,
        CesiumMath,
        Matrix4,
        PixelFormat,
        Quaternion,
        SphereOutlineGeometry,
        WebGLConstants,
        ClearCommand,
        ContextLimits,
        CubeMap,
        DrawCommand,
        Framebuffer,
        Pass,
        PassState,
        PixelDatatype,
        Renderbuffer,
        RenderbufferFormat,
        RenderState,
        Sampler,
        Texture,
        TextureMagnificationFilter,
        TextureMinificationFilter,
        TextureWrap,
        Camera,
        CullFace,
        CullingVolume,
        DebugCameraPrimitive,
        OrthographicFrustum,
        PerInstanceColorAppearance,
        PerspectiveFrustum,
        Primitive,
        ShadowMapShader) {
    'use strict';

    /**
     * Use {@link Viewer#shadowMap} to get the scene's shadow map originating from the sun. Do not construct this directly.
     *
     * <p>
     * The normalOffset bias pushes the shadows forward slightly, and may be disabled
     * for applications that require ultra precise shadows.
     * </p>
     *
     * @alias ShadowMap
     * @internalConstructor
     *
     * @param {Object} options An object containing the following properties:
     * @param {Camera} options.lightCamera A camera representing the light source.
     * @param {Boolean} [options.enabled=true] Whether the shadow map is enabled.
     * @param {Boolean} [options.isPointLight=false] Whether the light source is a point light. Point light shadows do not use cascades.
     * @param {Boolean} [options.pointLightRadius=100.0] Radius of the point light.
     * @param {Boolean} [options.cascadesEnabled=true] Use multiple shadow maps to cover different partitions of the view frustum.
     * @param {Number} [options.numberOfCascades=4] The number of cascades to use for the shadow map. Supported values are one and four.
     * @param {Number} [options.maximumDistance=5000.0] The maximum distance used for generating cascaded shadows. Lower values improve shadow quality.
     * @param {Number} [options.size=2048] The width and height, in pixels, of each shadow map.
     * @param {Boolean} [options.softShadows=false] Whether percentage-closer-filtering is enabled for producing softer shadows.
     * @param {Number} [options.darkness=0.3] The shadow darkness.
     * @param {Boolean} [options.normalOffset=true] Whether a normal bias is applied to shadows.
     *
     * @exception {DeveloperError} Only one or four cascades are supported.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Shadows.html|Cesium Sandcastle Shadows Demo}
     */
    function ShadowMap(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        // options.context is an undocumented option
        var context = options.context;

                if (!defined(context)) {
            throw new DeveloperError('context is required.');
        }
        if (!defined(options.lightCamera)) {
            throw new DeveloperError('lightCamera is required.');
        }
        if (defined(options.numberOfCascades) && ((options.numberOfCascades !== 1) && (options.numberOfCascades !== 4))) {
            throw new DeveloperError('Only one or four cascades are supported.');
        }
        
        this._enabled = defaultValue(options.enabled, true);
        this._softShadows = defaultValue(options.softShadows, false);
        this._normalOffset = defaultValue(options.normalOffset, true);
        this.dirty = true;

        /**
         * Specifies whether the shadow map originates from a light source. Shadow maps that are used for analytical
         * purposes should set this to false so as not to affect scene rendering.
         *
         * @private
         */
        this.fromLightSource = defaultValue(options.fromLightSource, true);

        /**
         * Determines the darkness of the shadows.
         *
         * @type {Number}
         * @default 0.3
         */
        this.darkness = defaultValue(options.darkness, 0.3);
        this._darkness = this.darkness;

        /**
         * Determines the maximum distance of the shadow map. Only applicable for cascaded shadows. Larger distances may result in lower quality shadows.
         *
         * @type {Number}
         * @default 5000.0
         */
        this.maximumDistance = defaultValue(options.maximumDistance, 5000.0);

        this._outOfView = false;
        this._outOfViewPrevious = false;
        this._needsUpdate = true;

        // In IE11 and Edge polygon offset is not functional.
        // TODO : Also disabled for instances of Firefox and Chrome running ANGLE that do not support depth textures.
        // Re-enable once https://github.com/AnalyticalGraphicsInc/cesium/issues/4560 is resolved.
        var polygonOffsetSupported = true;
        if (FeatureDetection.isInternetExplorer() || FeatureDetection.isEdge() || ((FeatureDetection.isChrome() || FeatureDetection.isFirefox()) && FeatureDetection.isWindows() && !context.depthTexture)) {
            polygonOffsetSupported = false;
        }
        this._polygonOffsetSupported = polygonOffsetSupported;

        this._terrainBias = {
            polygonOffset : polygonOffsetSupported,
            polygonOffsetFactor : 1.1,
            polygonOffsetUnits : 4.0,
            normalOffset : this._normalOffset,
            normalOffsetScale : 0.5,
            normalShading : true,
            normalShadingSmooth : 0.3,
            depthBias : 0.0001
        };

        this._primitiveBias = {
            polygonOffset : polygonOffsetSupported,
            polygonOffsetFactor : 1.1,
            polygonOffsetUnits : 4.0,
            normalOffset : this._normalOffset,
            normalOffsetScale : 0.1,
            normalShading : true,
            normalShadingSmooth : 0.05,
            depthBias : 0.00002
        };

        this._pointBias = {
            polygonOffset : false,
            polygonOffsetFactor : 1.1,
            polygonOffsetUnits : 4.0,
            normalOffset : this._normalOffset,
            normalOffsetScale : 0.0,
            normalShading : true,
            normalShadingSmooth : 0.1,
            depthBias : 0.0005
        };

        // Framebuffer resources
        this._depthAttachment = undefined;
        this._colorAttachment = undefined;

        // Uniforms
        this._shadowMapMatrix = new Matrix4();
        this._shadowMapTexture = undefined;
        this._lightDirectionEC = new Cartesian3();
        this._lightPositionEC = new Cartesian4();
        this._distance = 0.0;

        this._lightCamera = options.lightCamera;
        this._shadowMapCamera = new ShadowMapCamera();
        this._shadowMapCullingVolume = undefined;
        this._sceneCamera = undefined;
        this._boundingSphere = new BoundingSphere();

        this._isPointLight = defaultValue(options.isPointLight, false);
        this._pointLightRadius = defaultValue(options.pointLightRadius, 100.0);

        this._cascadesEnabled = this._isPointLight ? false : defaultValue(options.cascadesEnabled, true);
        this._numberOfCascades = !this._cascadesEnabled ? 0 : defaultValue(options.numberOfCascades, 4);
        this._fitNearFar = true;
        this._maximumCascadeDistances = [25.0, 150.0, 700.0, Number.MAX_VALUE];

        this._textureSize = new Cartesian2();

        this._isSpotLight = false;
        if (this._cascadesEnabled) {
            // Cascaded shadows are always orthographic. The frustum dimensions are calculated on the fly.
            this._shadowMapCamera.frustum = new OrthographicFrustum();
        } else if (defined(this._lightCamera.frustum.fov)) {
            // If the light camera uses a perspective frustum, then the light source is a spot light
            this._isSpotLight = true;
        }

        // Uniforms
        this._cascadeSplits = [new Cartesian4(), new Cartesian4()];
        this._cascadeMatrices = [new Matrix4(), new Matrix4(), new Matrix4(), new Matrix4()];
        this._cascadeDistances = new Cartesian4();

        var numberOfPasses;
        if (this._isPointLight) {
            numberOfPasses = 6; // One shadow map for each direction
        } else if (!this._cascadesEnabled) {
            numberOfPasses = 1;
        } else {
            numberOfPasses = this._numberOfCascades;
        }

        this._passes = new Array(numberOfPasses);
        for (var i = 0; i < numberOfPasses; ++i) {
            this._passes[i] = new ShadowPass(context);
        }

        this.debugShow = false;
        this.debugFreezeFrame = false;
        this._debugFreezeFrame = false;
        this._debugCascadeColors = false;
        this._debugLightFrustum = undefined;
        this._debugCameraFrustum = undefined;
        this._debugCascadeFrustums = new Array(this._numberOfCascades);
        this._debugShadowViewCommand = undefined;

        this._usesDepthTexture = context.depthTexture;

        if (this._isPointLight) {
            this._usesDepthTexture = false;
        }

        // Create render states for shadow casters
        this._primitiveRenderState = undefined;
        this._terrainRenderState = undefined;
        this._pointRenderState = undefined;
        createRenderStates(this);

        // For clearing the shadow map texture every frame
        this._clearCommand = new ClearCommand({
            depth : 1.0,
            color : new Color()
        });

        this._clearPassState = new PassState(context);

        this._size = defaultValue(options.size, 2048);
        this.size = this._size;
    }

    /**
     * Global maximum shadow distance used to prevent far off receivers from extending
     * the shadow far plane. This helps set a tighter near/far when viewing objects from space.
     *
     * @private
     */
    ShadowMap.MAXIMUM_DISTANCE = 20000.0;

    function ShadowPass(context) {
        this.camera = new ShadowMapCamera();
        this.passState = new PassState(context);
        this.framebuffer = undefined;
        this.textureOffsets = undefined;
        this.commandList = [];
        this.cullingVolume = undefined;
    }

    function createRenderState(colorMask, bias) {
        return RenderState.fromCache({
            cull : {
                enabled : true,
                face : CullFace.BACK
            },
            depthTest : {
                enabled : true
            },
            colorMask : {
                red : colorMask,
                green : colorMask,
                blue : colorMask,
                alpha : colorMask
            },
            depthMask : true,
            polygonOffset : {
                enabled : bias.polygonOffset,
                factor : bias.polygonOffsetFactor,
                units : bias.polygonOffsetUnits
            }
        });
    }

    function createRenderStates(shadowMap) {
        // Enable the color mask if the shadow map is backed by a color texture, e.g. when depth textures aren't supported
        var colorMask = !shadowMap._usesDepthTexture;
        shadowMap._primitiveRenderState = createRenderState(colorMask, shadowMap._primitiveBias);
        shadowMap._terrainRenderState = createRenderState(colorMask, shadowMap._terrainBias);
        shadowMap._pointRenderState = createRenderState(colorMask, shadowMap._pointBias);
    }

    /**
     * @private
     */
    ShadowMap.prototype.debugCreateRenderStates = function() {
        createRenderStates(this);
    };

    defineProperties(ShadowMap.prototype, {
        /**
         * Determines if the shadow map will be shown.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default true
         */
        enabled : {
            get : function() {
                return this._enabled;
            },
            set : function(value) {
                this.dirty = this._enabled !== value;
                this._enabled = value;
            }
        },

        /**
         * Determines if a normal bias will be applied to shadows.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default true
         */
        normalOffset : {
            get : function() {
                return this._normalOffset;
            },
            set : function(value) {
                this.dirty = this._normalOffset !== value;
                this._normalOffset = value;
                this._terrainBias.normalOffset = value;
                this._primitiveBias.normalOffset = value;
                this._pointBias.normalOffset = value;
            }
        },

        /**
         * Determines if soft shadows are enabled. Uses pcf filtering which requires more texture reads and may hurt performance.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default false
         */
        softShadows : {
            get : function() {
                return this._softShadows;
            },
            set : function(value) {
                this.dirty = this._softShadows !== value;
                this._softShadows = value;
            }
        },

        /**
         * The width and height, in pixels, of each shadow map.
         *
         * @memberof ShadowMap.prototype
         * @type {Number}
         * @default 2048
         */
        size : {
            get : function() {
                return this._size;
            },
            set : function(value) {
                resize(this, value);
            }
        },

        /**
         * Whether the shadow map is out of view of the scene camera.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @readonly
         * @private
         */
        outOfView : {
            get : function() {
                return this._outOfView;
            }
        },

        /**
         * The culling volume of the shadow frustum.
         *
         * @memberof ShadowMap.prototype
         * @type {CullingVolume}
         * @readonly
         * @private
         */
        shadowMapCullingVolume : {
            get : function() {
                return this._shadowMapCullingVolume;
            }
        },

        /**
         * The passes used for rendering shadows. Each face of a point light or each cascade for a cascaded shadow map is a separate pass.
         *
         * @memberof ShadowMap.prototype
         * @type {ShadowPass[]}
         * @readonly
         * @private
         */
        passes : {
            get : function() {
                return this._passes;
            }
        },

        /**
         * Whether the light source is a point light.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @readonly
         * @private
         */
        isPointLight : {
            get : function() {
                return this._isPointLight;
            }
        },

        /**
         * Debug option for visualizing the cascades by color.
         *
         * @memberof ShadowMap.prototype
         * @type {Boolean}
         * @default false
         * @private
         */
        debugCascadeColors : {
            get : function() {
                return this._debugCascadeColors;
            },
            set : function(value) {
                this.dirty = this._debugCascadeColors !== value;
                this._debugCascadeColors = value;
            }
        }
    });

    function destroyFramebuffer(shadowMap) {
        var length = shadowMap._passes.length;
        for (var i = 0; i < length; ++i) {
            var pass = shadowMap._passes[i];
            var framebuffer = pass.framebuffer;
            if (defined(framebuffer) && !framebuffer.isDestroyed()) {
                framebuffer.destroy();
            }
            pass.framebuffer = undefined;
        }

        // Destroy the framebuffer attachments
        shadowMap._depthAttachment = shadowMap._depthAttachment && shadowMap._depthAttachment.destroy();
        shadowMap._colorAttachment = shadowMap._colorAttachment && shadowMap._colorAttachment.destroy();
    }

    function createSampler() {
        return new Sampler({
            wrapS : TextureWrap.CLAMP_TO_EDGE,
            wrapT : TextureWrap.CLAMP_TO_EDGE,
            minificationFilter : TextureMinificationFilter.NEAREST,
            magnificationFilter : TextureMagnificationFilter.NEAREST
        });
    }

    function createFramebufferColor(shadowMap, context) {
        var depthRenderbuffer = new Renderbuffer({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            format : RenderbufferFormat.DEPTH_COMPONENT16
        });

        var colorTexture = new Texture({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
            sampler : createSampler()
        });

        var framebuffer = new Framebuffer({
            context : context,
            depthRenderbuffer : depthRenderbuffer,
            colorTextures : [colorTexture],
            destroyAttachments : false
        });

        var length = shadowMap._passes.length;
        for (var i = 0; i < length; ++i) {
            var pass = shadowMap._passes[i];
            pass.framebuffer = framebuffer;
            pass.passState.framebuffer = framebuffer;
        }

        shadowMap._shadowMapTexture = colorTexture;
        shadowMap._depthAttachment = depthRenderbuffer;
        shadowMap._colorAttachment = colorTexture;
    }

    function createFramebufferDepth(shadowMap, context) {
        var depthStencilTexture = new Texture({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            pixelFormat : PixelFormat.DEPTH_STENCIL,
            pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8,
            sampler : createSampler()
        });

        var framebuffer = new Framebuffer({
            context : context,
            depthStencilTexture : depthStencilTexture,
            destroyAttachments : false
        });

        var length = shadowMap._passes.length;
        for (var i = 0; i < length; ++i) {
            var pass = shadowMap._passes[i];
            pass.framebuffer = framebuffer;
            pass.passState.framebuffer = framebuffer;
        }

        shadowMap._shadowMapTexture = depthStencilTexture;
        shadowMap._depthAttachment = depthStencilTexture;
    }

    function createFramebufferCube(shadowMap, context) {
        var depthRenderbuffer = new Renderbuffer({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            format : RenderbufferFormat.DEPTH_COMPONENT16
        });

        var cubeMap = new CubeMap({
            context : context,
            width : shadowMap._textureSize.x,
            height : shadowMap._textureSize.y,
            pixelFormat : PixelFormat.RGBA,
            pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
            sampler : createSampler()
        });

        var faces = [cubeMap.negativeX, cubeMap.negativeY, cubeMap.negativeZ, cubeMap.positiveX, cubeMap.positiveY, cubeMap.positiveZ];

        for (var i = 0; i < 6; ++i) {
            var framebuffer = new Framebuffer({
                context : context,
                depthRenderbuffer : depthRenderbuffer,
                colorTextures : [faces[i]],
                destroyAttachments : false
            });
            var pass = shadowMap._passes[i];
            pass.framebuffer = framebuffer;
            pass.passState.framebuffer = framebuffer;
        }

        shadowMap._shadowMapTexture = cubeMap;
        shadowMap._depthAttachment = depthRenderbuffer;
        shadowMap._colorAttachment = cubeMap;
    }

    function createFramebuffer(shadowMap, context) {
        if (shadowMap._isPointLight) {
            createFramebufferCube(shadowMap, context);
        } else if (shadowMap._usesDepthTexture) {
            createFramebufferDepth(shadowMap, context);
        } else {
            createFramebufferColor(shadowMap, context);
        }
    }

    function checkFramebuffer(shadowMap, context) {
        // Attempt to make an FBO with only a depth texture. If it fails, fallback to a color texture.
        if (shadowMap._usesDepthTexture && (shadowMap._passes[0].framebuffer.status !== WebGLConstants.FRAMEBUFFER_COMPLETE)) {
            shadowMap._usesDepthTexture = false;
            createRenderStates(shadowMap);
            destroyFramebuffer(shadowMap);
            createFramebuffer(shadowMap, context);
        }
    }

    function updateFramebuffer(shadowMap, context) {
        if (!defined(shadowMap._passes[0].framebuffer) || (shadowMap._shadowMapTexture.width !== shadowMap._textureSize.x)) {
            destroyFramebuffer(shadowMap);
            createFramebuffer(shadowMap, context);
            checkFramebuffer(shadowMap, context);
            clearFramebuffer(shadowMap, context);
        }
    }

    function clearFramebuffer(shadowMap, context, shadowPass) {
        shadowPass = defaultValue(shadowPass, 0);
        if (shadowMap._isPointLight || (shadowPass === 0)) {
            shadowMap._clearCommand.framebuffer = shadowMap._passes[shadowPass].framebuffer;
            shadowMap._clearCommand.execute(context, shadowMap._clearPassState);
        }
    }

    function resize(shadowMap, size) {
        shadowMap._size = size;
        var passes = shadowMap._passes;
        var numberOfPasses = passes.length;
        var textureSize = shadowMap._textureSize;

        if (shadowMap._isPointLight) {
            size = (ContextLimits.maximumCubeMapSize >= size) ? size : ContextLimits.maximumCubeMapSize;
            textureSize.x = size;
            textureSize.y = size;
            var faceViewport = new BoundingRectangle(0, 0, size, size);
            passes[0].passState.viewport = faceViewport;
            passes[1].passState.viewport = faceViewport;
            passes[2].passState.viewport = faceViewport;
            passes[3].passState.viewport = faceViewport;
            passes[4].passState.viewport = faceViewport;
            passes[5].passState.viewport = faceViewport;
        } else if (numberOfPasses === 1) {
            // +----+
            // |  1 |
            // +----+
            size = (ContextLimits.maximumTextureSize >= size) ? size : ContextLimits.maximumTextureSize;
            textureSize.x = size;
            textureSize.y = size;
            passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
        } else if (numberOfPasses === 4) {
            // +----+----+
            // |  3 |  4 |
            // +----+----+
            // |  1 |  2 |
            // +----+----+
            size = (ContextLimits.maximumTextureSize >= size * 2) ? size : ContextLimits.maximumTextureSize / 2;
            textureSize.x = size * 2;
            textureSize.y = size * 2;
            passes[0].passState.viewport = new BoundingRectangle(0, 0, size, size);
            passes[1].passState.viewport = new BoundingRectangle(size, 0, size, size);
            passes[2].passState.viewport = new BoundingRectangle(0, size, size, size);
            passes[3].passState.viewport = new BoundingRectangle(size, size, size, size);
        }

        // Update clear pass state
        shadowMap._clearPassState.viewport = new BoundingRectangle(0, 0, textureSize.x, textureSize.y);

        // Transforms shadow coordinates [0, 1] into the pass's region of the texture
        for (var i = 0; i < numberOfPasses; ++i) {
            var pass = passes[i];
            var viewport = pass.passState.viewport;
            var biasX = viewport.x / textureSize.x;
            var biasY = viewport.y / textureSize.y;
            var scaleX = viewport.width / textureSize.x;
            var scaleY = viewport.height / textureSize.y;
            pass.textureOffsets = new Matrix4(scaleX, 0.0, 0.0, biasX, 0.0, scaleY, 0.0, biasY, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
        }
    }

    var scratchViewport = new BoundingRectangle();

    function createDebugShadowViewCommand(shadowMap, context) {
        var fs;
        if (shadowMap._isPointLight) {
            fs = 'uniform samplerCube shadowMap_textureCube; \n' +
                 'varying vec2 v_textureCoordinates; \n' +
                 'void main() \n' +
                 '{ \n' +
                 '    vec2 uv = v_textureCoordinates; \n' +
                 '    vec3 dir; \n' +
                 ' \n' +
                 '    if (uv.y < 0.5) \n' +
                 '    { \n' +
                 '        if (uv.x < 0.333) \n' +
                 '        { \n' +
                 '            dir.x = -1.0; \n' +
                 '            dir.y = uv.x * 6.0 - 1.0; \n' +
                 '            dir.z = uv.y * 4.0 - 1.0; \n' +
                 '        } \n' +
                 '        else if (uv.x < 0.666) \n' +
                 '        { \n' +
                 '            dir.y = -1.0; \n' +
                 '            dir.x = uv.x * 6.0 - 3.0; \n' +
                 '            dir.z = uv.y * 4.0 - 1.0; \n' +
                 '        } \n' +
                 '        else \n' +
                 '        { \n' +
                 '            dir.z = -1.0; \n' +
                 '            dir.x = uv.x * 6.0 - 5.0; \n' +
                 '            dir.y = uv.y * 4.0 - 1.0; \n' +
                 '        } \n' +
                 '    } \n' +
                 '    else \n' +
                 '    { \n' +
                 '        if (uv.x < 0.333) \n' +
                 '        { \n' +
                 '            dir.x = 1.0; \n' +
                 '            dir.y = uv.x * 6.0 - 1.0; \n' +
                 '            dir.z = uv.y * 4.0 - 3.0; \n' +
                 '        } \n' +
                 '        else if (uv.x < 0.666) \n' +
                 '        { \n' +
                 '            dir.y = 1.0; \n' +
                 '            dir.x = uv.x * 6.0 - 3.0; \n' +
                 '            dir.z = uv.y * 4.0 - 3.0; \n' +
                 '        } \n' +
                 '        else \n' +
                 '        { \n' +
                 '            dir.z = 1.0; \n' +
                 '            dir.x = uv.x * 6.0 - 5.0; \n' +
                 '            dir.y = uv.y * 4.0 - 3.0; \n' +
                 '        } \n' +
                 '    } \n' +
                 ' \n' +
                 '    float shadow = czm_unpackDepth(textureCube(shadowMap_textureCube, dir)); \n' +
                 '    gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
                 '} \n';
        } else {
            fs = 'uniform sampler2D shadowMap_texture; \n' +
                 'varying vec2 v_textureCoordinates; \n' +
                 'void main() \n' +
                 '{ \n' +

                 (shadowMap._usesDepthTexture ?
                 '    float shadow = texture2D(shadowMap_texture, v_textureCoordinates).r; \n' :
                 '    float shadow = czm_unpackDepth(texture2D(shadowMap_texture, v_textureCoordinates)); \n') +

                 '    gl_FragColor = vec4(vec3(shadow), 1.0); \n' +
                 '} \n';
        }

        var drawCommand = context.createViewportQuadCommand(fs, {
            uniformMap : {
                shadowMap_texture : function() {
                    return shadowMap._shadowMapTexture;
                },
                shadowMap_textureCube : function() {
                    return shadowMap._shadowMapTexture;
                }
            }
        });
        drawCommand.pass = Pass.OVERLAY;
        return drawCommand;
    }

    function updateDebugShadowViewCommand(shadowMap, frameState) {
        // Draws the shadow map on the bottom-right corner of the screen
        var context = frameState.context;
        var screenWidth = frameState.context.drawingBufferWidth;
        var screenHeight = frameState.context.drawingBufferHeight;
        var size = Math.min(screenWidth, screenHeight) * 0.3;

        var viewport = scratchViewport;
        viewport.x = screenWidth - size;
        viewport.y = 0;
        viewport.width = size;
        viewport.height = size;

        var debugCommand = shadowMap._debugShadowViewCommand;
        if (!defined(debugCommand)) {
            debugCommand = createDebugShadowViewCommand(shadowMap, context);
            shadowMap._debugShadowViewCommand = debugCommand;
        }

        // Get a new RenderState for the updated viewport size
        if (!defined(debugCommand.renderState) || !BoundingRectangle.equals(debugCommand.renderState.viewport, viewport)) {
            debugCommand.renderState = RenderState.fromCache({
                viewport : BoundingRectangle.clone(viewport)
            });
        }

        frameState.commandList.push(shadowMap._debugShadowViewCommand);
    }

    var frustumCornersNDC = new Array(8);
    frustumCornersNDC[0] = new Cartesian4(-1.0, -1.0, -1.0, 1.0);
    frustumCornersNDC[1] = new Cartesian4(1.0, -1.0, -1.0, 1.0);
    frustumCornersNDC[2] = new Cartesian4(1.0, 1.0, -1.0, 1.0);
    frustumCornersNDC[3] = new Cartesian4(-1.0, 1.0, -1.0, 1.0);
    frustumCornersNDC[4] = new Cartesian4(-1.0, -1.0, 1.0, 1.0);
    frustumCornersNDC[5] = new Cartesian4(1.0, -1.0, 1.0, 1.0);
    frustumCornersNDC[6] = new Cartesian4(1.0, 1.0, 1.0, 1.0);
    frustumCornersNDC[7] = new Cartesian4(-1.0, 1.0, 1.0, 1.0);

    var scratchMatrix = new Matrix4();
    var scratchFrustumCorners = new Array(8);
    for (var i = 0; i < 8; ++i) {
        scratchFrustumCorners[i] = new Cartesian4();
    }

    function createDebugPointLight(modelMatrix, color) {
        var box = new GeometryInstance({
            geometry : new BoxOutlineGeometry({
                minimum : new Cartesian3(-0.5, -0.5, -0.5),
                maximum : new Cartesian3(0.5, 0.5, 0.5)
            }),
            attributes : {
                color : ColorGeometryInstanceAttribute.fromColor(color)
            }
        });

        var sphere = new GeometryInstance({
            geometry : new SphereOutlineGeometry({
                radius : 0.5
            }),
            attributes : {
                color : ColorGeometryInstanceAttribute.fromColor(color)
            }
        });

        return new Primitive({
            geometryInstances : [box, sphere],
            appearance : new PerInstanceColorAppearance({
                translucent : false,
                flat : true
            }),
            asynchronous : false,
            modelMatrix : modelMatrix
        });
    }

    var debugOutlineColors = [Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA];
    var scratchScale = new Cartesian3();

    function applyDebugSettings(shadowMap, frameState) {
        updateDebugShadowViewCommand(shadowMap, frameState);

        var enterFreezeFrame = shadowMap.debugFreezeFrame && !shadowMap._debugFreezeFrame;
        shadowMap._debugFreezeFrame = shadowMap.debugFreezeFrame;

        // Draw scene camera in freeze frame mode
        if (shadowMap.debugFreezeFrame) {
            if (enterFreezeFrame) {
                // Recreate debug camera when entering freeze frame mode
                shadowMap._debugCameraFrustum = shadowMap._debugCameraFrustum && shadowMap._debugCameraFrustum.destroy();
                shadowMap._debugCameraFrustum = new DebugCameraPrimitive({
                    camera : shadowMap._sceneCamera,
                    color : Color.CYAN,
                    updateOnChange : false
                });
            }
            shadowMap._debugCameraFrustum.update(frameState);
        }

        if (shadowMap._cascadesEnabled) {
            // Draw cascades only in freeze frame mode
            if (shadowMap.debugFreezeFrame) {
                if (enterFreezeFrame) {
                    // Recreate debug frustum when entering freeze frame mode
                    shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
                    shadowMap._debugLightFrustum = new DebugCameraPrimitive({
                        camera : shadowMap._shadowMapCamera,
                        color : Color.YELLOW,
                        updateOnChange : false
                    });
                }
                shadowMap._debugLightFrustum.update(frameState);

                for (var i = 0; i < shadowMap._numberOfCascades; ++i) {
                    if (enterFreezeFrame) {
                        // Recreate debug frustum when entering freeze frame mode
                        shadowMap._debugCascadeFrustums[i] = shadowMap._debugCascadeFrustums[i] && shadowMap._debugCascadeFrustums[i].destroy();
                        shadowMap._debugCascadeFrustums[i] = new DebugCameraPrimitive({
                            camera : shadowMap._passes[i].camera,
                            color : debugOutlineColors[i],
                            updateOnChange : false
                        });
                    }
                    shadowMap._debugCascadeFrustums[i].update(frameState);
                }
            }
        } else if (shadowMap._isPointLight) {
            if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
                var translation = shadowMap._shadowMapCamera.positionWC;
                var rotation = Quaternion.IDENTITY;
                var uniformScale = shadowMap._pointLightRadius * 2.0;
                var scale = Cartesian3.fromElements(uniformScale, uniformScale, uniformScale, scratchScale);
                var modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(translation, rotation, scale, scratchMatrix);

                shadowMap._debugLightFrustum = shadowMap._debugLightFrustum && shadowMap._debugLightFrustum.destroy();
                shadowMap._debugLightFrustum = createDebugPointLight(modelMatrix, Color.YELLOW);
            }
            shadowMap._debugLightFrustum.update(frameState);
        } else {
            if (!defined(shadowMap._debugLightFrustum) || shadowMap._needsUpdate) {
                shadowMap._debugLightFrustum = new DebugCameraPrimitive({
                    camera : shadowMap._shadowMapCamera,
                    color : Color.YELLOW,
                    updateOnChange : false
                });
            }
            shadowMap._debugLightFrustum.update(frameState);
        }
    }

    function ShadowMapCamera() {
        this.viewMatrix = new Matrix4();
        this.inverseViewMatrix = new Matrix4();
        this.frustum = undefined;
        this.positionCartographic = new Cartographic();
        this.positionWC = new Cartesian3();
        this.directionWC = Cartesian3.clone(Cartesian3.UNIT_Z);
        this.upWC = Cartesian3.clone(Cartesian3.UNIT_Y);
        this.rightWC = Cartesian3.clone(Cartesian3.UNIT_X);
        this.viewProjectionMatrix = new Matrix4();
    }

    ShadowMapCamera.prototype.clone = function(camera) {
        Matrix4.clone(camera.viewMatrix, this.viewMatrix);
        Matrix4.clone(camera.inverseViewMatrix, this.inverseViewMatrix);
        this.frustum = camera.frustum.clone(this.frustum);
        Cartographic.clone(camera.positionCartographic, this.positionCartographic);
        Cartesian3.clone(camera.positionWC, this.positionWC);
        Cartesian3.clone(camera.directionWC, this.directionWC);
        Cartesian3.clone(camera.upWC, this.upWC);
        Cartesian3.clone(camera.rightWC, this.rightWC);
    };

    // Converts from NDC space to texture space
    var scaleBiasMatrix = new Matrix4(0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0);

    ShadowMapCamera.prototype.getViewProjection = function() {
        var view = this.viewMatrix;
        var projection = this.frustum.projectionMatrix;
        Matrix4.multiply(projection, view, this.viewProjectionMatrix);
        Matrix4.multiply(scaleBiasMatrix, this.viewProjectionMatrix, this.viewProjectionMatrix);
        return this.viewProjectionMatrix;
    };

    var scratchSplits = new Array(5);
    var scratchFrustum = new PerspectiveFrustum();
    var scratchCascadeDistances = new Array(4);

    function computeCascades(shadowMap, frameState) {
        var shadowMapCamera = shadowMap._shadowMapCamera;
        var sceneCamera = shadowMap._sceneCamera;
        var cameraNear = sceneCamera.frustum.near;
        var cameraFar = sceneCamera.frustum.far;
        var numberOfCascades = shadowMap._numberOfCascades;

        // Split cascades. Use a mix of linear and log splits.
        var i;
        var range = cameraFar - cameraNear;
        var ratio = cameraFar / cameraNear;

        var lambda = 0.9;
        var clampCascadeDistances = false;

        // When the camera is close to a relatively small model, provide more detail in the closer cascades.
        // If the camera is near or inside a large model, such as the root tile of a city, then use the default values.
        // To get the most accurate cascade splits we would need to find the min and max values from the depth texture.
        if (frameState.shadowHints.closestObjectSize < 200.0) {
            clampCascadeDistances = true;
            lambda = 0.9;
        }

        var cascadeDistances = scratchCascadeDistances;
        var splits = scratchSplits;
        splits[0] = cameraNear;
        splits[numberOfCascades] = cameraFar;

        // Find initial splits
        for (i = 0; i < numberOfCascades; ++i) {
            var p = (i + 1) / numberOfCascades;
            var logScale = cameraNear * Math.pow(ratio, p);
            var uniformScale = cameraNear + range * p;
            var split = CesiumMath.lerp(uniformScale, logScale, lambda);
            splits[i + 1] = split;
            cascadeDistances[i] = split - splits[i];
        }

        if (clampCascadeDistances) {
            // Clamp each cascade to its maximum distance
            for (i = 0; i < numberOfCascades; ++i) {
                cascadeDistances[i] = Math.min(cascadeDistances[i], shadowMap._maximumCascadeDistances[i]);
            }

            // Recompute splits
            var distance = splits[0];
            for (i = 0; i < numberOfCascades - 1; ++i) {
                distance += cascadeDistances[i];
                splits[i + 1] = distance;
            }
        }

        Cartesian4.unpack(splits, 0, shadowMap._cascadeSplits[0]);
        Cartesian4.unpack(splits, 1, shadowMap._cascadeSplits[1]);
        Cartesian4.unpack(cascadeDistances, 0, shadowMap._cascadeDistances);

        var shadowFrustum = shadowMapCamera.frustum;
        var left = shadowFrustum.left;
        var right = shadowFrustum.right;
        var bottom = shadowFrustum.bottom;
        var top = shadowFrustum.top;
        var near = shadowFrustum.near;
        var far = shadowFrustum.far;

        var position = shadowMapCamera.positionWC;
        var direction = shadowMapCamera.directionWC;
        var up = shadowMapCamera.upWC;

        var cascadeSubFrustum = sceneCamera.frustum.clone(scratchFrustum);
        var shadowViewProjection = shadowMapCamera.getViewProjection();

        for (i = 0; i < numberOfCascades; ++i) {
            // Find the bounding box of the camera sub-frustum in shadow map texture space
            cascadeSubFrustum.near = splits[i];
            cascadeSubFrustum.far = splits[i + 1];
            var viewProjection = Matrix4.multiply(cascadeSubFrustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
            var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);
            var shadowMapMatrix = Matrix4.multiply(shadowViewProjection, inverseViewProjection, scratchMatrix);

            // Project each corner from camera NDC space to shadow map texture space. Min and max will be from 0 to 1.
            var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
            var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);

            for (var k = 0; k < 8; ++k) {
                var corner = Cartesian4.clone(frustumCornersNDC[k], scratchFrustumCorners[k]);
                Matrix4.multiplyByVector(shadowMapMatrix, corner, corner);
                Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
                Cartesian3.minimumByComponent(corner, min, min);
                Cartesian3.maximumByComponent(corner, max, max);
            }

            // Limit light-space coordinates to the [0, 1] range
            min.x = Math.max(min.x, 0.0);
            min.y = Math.max(min.y, 0.0);
            min.z = 0.0; // Always start cascade frustum at the top of the light frustum to capture objects in the light's path
            max.x = Math.min(max.x, 1.0);
            max.y = Math.min(max.y, 1.0);
            max.z = Math.min(max.z, 1.0);

            var pass = shadowMap._passes[i];
            var cascadeCamera = pass.camera;
            cascadeCamera.clone(shadowMapCamera); // PERFORMANCE_IDEA : could do a shallow clone for all properties except the frustum

            var frustum = cascadeCamera.frustum;
            frustum.left = left + min.x * (right - left);
            frustum.right = left + max.x * (right - left);
            frustum.bottom = bottom + min.y * (top - bottom);
            frustum.top = bottom + max.y * (top - bottom);
            frustum.near = near + min.z * (far - near);
            frustum.far = near + max.z * (far - near);

            pass.cullingVolume = cascadeCamera.frustum.computeCullingVolume(position, direction, up);

            // Transforms from eye space to the cascade's texture space
            var cascadeMatrix = shadowMap._cascadeMatrices[i];
            Matrix4.multiply(cascadeCamera.getViewProjection(), sceneCamera.inverseViewMatrix, cascadeMatrix);
            Matrix4.multiply(pass.textureOffsets, cascadeMatrix, cascadeMatrix);
        }
    }

    var scratchLightView = new Matrix4();
    var scratchRight = new Cartesian3();
    var scratchUp = new Cartesian3();
    var scratchMin = new Cartesian3();
    var scratchMax = new Cartesian3();
    var scratchTranslation = new Cartesian3();

    function fitShadowMapToScene(shadowMap, frameState) {
        var shadowMapCamera = shadowMap._shadowMapCamera;
        var sceneCamera = shadowMap._sceneCamera;

        // 1. First find a tight bounding box in light space that contains the entire camera frustum.
        var viewProjection = Matrix4.multiply(sceneCamera.frustum.projectionMatrix, sceneCamera.viewMatrix, scratchMatrix);
        var inverseViewProjection = Matrix4.inverse(viewProjection, scratchMatrix);

        // Start to construct the light view matrix. Set translation later once the bounding box is found.
        var lightDir = shadowMapCamera.directionWC;
        var lightUp = sceneCamera.directionWC; // Align shadows to the camera view.
        var lightRight = Cartesian3.cross(lightDir, lightUp, scratchRight);
        lightUp = Cartesian3.cross(lightRight, lightDir, scratchUp); // Recalculate up now that right is derived
        Cartesian3.normalize(lightUp, lightUp);
        Cartesian3.normalize(lightRight, lightRight);
        var lightPosition = Cartesian3.fromElements(0.0, 0.0, 0.0, scratchTranslation);

        var lightView = Matrix4.computeView(lightPosition, lightDir, lightUp, lightRight, scratchLightView);
        var cameraToLight = Matrix4.multiply(lightView, inverseViewProjection, scratchMatrix);

        // Project each corner from NDC space to light view space, and calculate a min and max in light view space
        var min = Cartesian3.fromElements(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE, scratchMin);
        var max = Cartesian3.fromElements(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE, scratchMax);

        for (var i = 0; i < 8; ++i) {
            var corner = Cartesian4.clone(frustumCornersNDC[i], scratchFrustumCorners[i]);
            Matrix4.multiplyByVector(cameraToLight, corner, corner);
            Cartesian3.divideByScalar(corner, corner.w, corner); // Handle the perspective divide
            Cartesian3.minimumByComponent(corner, min, min);
            Cartesian3.maximumByComponent(corner, max, max);
        }

        // 2. Set bounding box back to include objects in the light's view
        max.z += 1000.0; // Note: in light space, a positive number is behind the camera
        min.z -= 10.0; // Extend the shadow volume forward slightly to avoid problems right at the edge

        // 3. Adjust light view matrix so that it is centered on the bounding volume
        var translation = scratchTranslation;
        translation.x = -(0.5 * (min.x + max.x));
        translation.y = -(0.5 * (min.y + max.y));
        translation.z = -max.z;

        var translationMatrix = Matrix4.fromTranslation(translation, scratchMatrix);
        lightView = Matrix4.multiply(translationMatrix, lightView, lightView);

        // 4. Create an orthographic frustum that covers the bounding box extents
        var halfWidth = 0.5 * (max.x - min.x);
        var halfHeight = 0.5 * (max.y - min.y);
        var depth = max.z - min.z;

        var frustum = shadowMapCamera.frustum;
        frustum.left = -halfWidth;
        frustum.right = halfWidth;
        frustum.bottom = -halfHeight;
        frustum.top = halfHeight;
        frustum.near = 0.01;
        frustum.far = depth;

        // 5. Update the shadow map camera
        Matrix4.clone(lightView, shadowMapCamera.viewMatrix);
        Matrix4.inverse(lightView, shadowMapCamera.inverseViewMatrix);
        Matrix4.getTranslation(shadowMapCamera.inverseViewMatrix, shadowMapCamera.positionWC);
        frameState.mapProjection.ellipsoid.cartesianToCartographic(shadowMapCamera.positionWC, shadowMapCamera.positionCartographic);
        Cartesian3.clone(lightDir, shadowMapCamera.directionWC);
        Cartesian3.clone(lightUp, shadowMapCamera.upWC);
        Cartesian3.clone(lightRight, shadowMapCamera.rightWC);
    }

    var directions = [
        new Cartesian3(-1.0, 0.0, 0.0),
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, 0.0, -1.0),
        new Cartesian3(1.0, 0.0, 0.0),
        new Cartesian3(0.0, 1.0, 0.0),
        new Cartesian3(0.0, 0.0, 1.0)
    ];

    var ups = [
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, 0.0, -1.0),
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, -1.0, 0.0),
        new Cartesian3(0.0, 0.0, 1.0),
        new Cartesian3(0.0, -1.0, 0.0)
    ];

    var rights = [
        new Cartesian3(0.0, 0.0, 1.0),
        new Cartesian3(1.0, 0.0, 0.0),
        new Cartesian3(-1.0, 0.0, 0.0),
        new Cartesian3(0.0, 0.0, -1.0),
        new Cartesian3(1.0, 0.0, 0.0),
        new Cartesian3(1.0, 0.0, 0.0)
    ];

    function computeOmnidirectional(shadowMap, frameState) {
        // All sides share the same frustum
        var frustum = new PerspectiveFrustum();
        frustum.fov = CesiumMath.PI_OVER_TWO;
        frustum.near = 1.0;
        frustum.far = shadowMap._pointLightRadius;
        frustum.aspectRatio = 1.0;

        for (var i = 0; i < 6; ++i) {
            var camera = shadowMap._passes[i].camera;
            camera.positionWC = shadowMap._shadowMapCamera.positionWC;
            camera.positionCartographic = frameState.mapProjection.ellipsoid.cartesianToCartographic(camera.positionWC, camera.positionCartographic);
            camera.directionWC = directions[i];
            camera.upWC = ups[i];
            camera.rightWC = rights[i];

            Matrix4.computeView(camera.positionWC, camera.directionWC, camera.upWC, camera.rightWC, camera.viewMatrix);
            Matrix4.inverse(camera.viewMatrix, camera.inverseViewMatrix);

            camera.frustum = frustum;
        }
    }

    var scratchCartesian1 = new Cartesian3();
    var scratchCartesian2 = new Cartesian3();
    var scratchBoundingSphere = new BoundingSphere();
    var scratchCenter = scratchBoundingSphere.center;

    function checkVisibility(shadowMap, frameState) {
        var sceneCamera = shadowMap._sceneCamera;
        var shadowMapCamera = shadowMap._shadowMapCamera;

        var boundingSphere = scratchBoundingSphere;

        // Check whether the shadow map is in view and needs to be updated
        if (shadowMap._cascadesEnabled) {
            // If the nearest shadow receiver is further than the shadow map's maximum distance then the shadow map is out of view.
            if (sceneCamera.frustum.near >= shadowMap.maximumDistance) {
                shadowMap._outOfView = true;
                shadowMap._needsUpdate = false;
                return;
            }

            // If the light source is below the horizon then the shadow map is out of view
            var surfaceNormal = frameState.mapProjection.ellipsoid.geodeticSurfaceNormal(sceneCamera.positionWC, scratchCartesian1);
            var lightDirection = Cartesian3.negate(shadowMapCamera.directionWC, scratchCartesian2);
            var dot = Cartesian3.dot(surfaceNormal, lightDirection);

            // Shadows start to fade out once the light gets closer to the horizon.
            // At this point the globe uses vertex lighting alone to darken the surface.
            var darknessAmount = CesiumMath.clamp(dot / 0.1, 0.0, 1.0);
            shadowMap._darkness = CesiumMath.lerp(1.0, shadowMap.darkness, darknessAmount);

            if (dot < 0.0) {
                shadowMap._outOfView = true;
                shadowMap._needsUpdate = false;
                return;
            }

            // By default cascaded shadows need to update and are always in view
            shadowMap._needsUpdate = true;
            shadowMap._outOfView = false;
        } else if (shadowMap._isPointLight) {
            // Sphere-frustum intersection test
            boundingSphere.center = shadowMapCamera.positionWC;
            boundingSphere.radius = shadowMap._pointLightRadius;
            shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
            shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
            BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
        } else {
            // Simplify frustum-frustum intersection test as a sphere-frustum test
            var frustumRadius = shadowMapCamera.frustum.far / 2.0;
            var frustumCenter = Cartesian3.add(shadowMapCamera.positionWC, Cartesian3.multiplyByScalar(shadowMapCamera.directionWC, frustumRadius, scratchCenter), scratchCenter);
            boundingSphere.center = frustumCenter;
            boundingSphere.radius = frustumRadius;
            shadowMap._outOfView = frameState.cullingVolume.computeVisibility(boundingSphere) === Intersect.OUTSIDE;
            shadowMap._needsUpdate = !shadowMap._outOfView && !shadowMap._boundingSphere.equals(boundingSphere);
            BoundingSphere.clone(boundingSphere, shadowMap._boundingSphere);
        }
    }

    function updateCameras(shadowMap, frameState) {
        var camera = frameState.camera; // The actual camera in the scene
        var lightCamera = shadowMap._lightCamera; // The external camera representing the light source
        var sceneCamera = shadowMap._sceneCamera; // Clone of camera, with clamped near and far planes
        var shadowMapCamera = shadowMap._shadowMapCamera; // Camera representing the shadow volume, initially cloned from lightCamera

        // Clone light camera into the shadow map camera
        if (shadowMap._cascadesEnabled) {
            Cartesian3.clone(lightCamera.directionWC, shadowMapCamera.directionWC);
        } else if (shadowMap._isPointLight) {
            Cartesian3.clone(lightCamera.positionWC, shadowMapCamera.positionWC);
        } else {
            shadowMapCamera.clone(lightCamera);
        }

        // Get the light direction in eye coordinates
        var lightDirection = shadowMap._lightDirectionEC;
        Matrix4.multiplyByPointAsVector(camera.viewMatrix, shadowMapCamera.directionWC, lightDirection);
        Cartesian3.normalize(lightDirection, lightDirection);
        Cartesian3.negate(lightDirection, lightDirection);

        // Get the light position in eye coordinates
        Matrix4.multiplyByPoint(camera.viewMatrix, shadowMapCamera.positionWC, shadowMap._lightPositionEC);
        shadowMap._lightPositionEC.w = shadowMap._pointLightRadius;

        // Get the near and far of the scene camera
        var near;
        var far;
        if (shadowMap._fitNearFar) {
            // shadowFar can be very large, so limit to shadowMap.maximumDistance
            // Push the far plane slightly further than the near plane to avoid degenerate frustum
            near = Math.min(frameState.shadowHints.nearPlane, shadowMap.maximumDistance);
            far = Math.min(frameState.shadowHints.farPlane, shadowMap.maximumDistance + 1.0);
        } else {
            near = camera.frustum.near;
            far = shadowMap.maximumDistance;
        }

        shadowMap._sceneCamera = Camera.clone(camera, sceneCamera);
        camera.frustum.clone(shadowMap._sceneCamera.frustum);
        shadowMap._sceneCamera.frustum.near = near;
        shadowMap._sceneCamera.frustum.far = far;
        shadowMap._distance = far - near;

        checkVisibility(shadowMap, frameState);

        if (!shadowMap._outOfViewPrevious && shadowMap._outOfView) {
            shadowMap._needsUpdate = true;
        }
        shadowMap._outOfViewPrevious = shadowMap._outOfView;
    }

    /**
     * @private
     */
    ShadowMap.prototype.update = function(frameState) {
        updateCameras(this, frameState);

        if (this._needsUpdate) {
            updateFramebuffer(this, frameState.context);

            if (this._isPointLight) {
                computeOmnidirectional(this, frameState);
            }

            if (this._cascadesEnabled) {
                fitShadowMapToScene(this, frameState);

                if (this._numberOfCascades > 1) {
                    computeCascades(this, frameState);
                }
            }

            if (!this._isPointLight) {
                // Compute the culling volume
                var shadowMapCamera = this._shadowMapCamera;
                var position = shadowMapCamera.positionWC;
                var direction = shadowMapCamera.directionWC;
                var up = shadowMapCamera.upWC;
                this._shadowMapCullingVolume = shadowMapCamera.frustum.computeCullingVolume(position, direction, up);

                if (this._passes.length === 1) {
                    // Since there is only one pass, use the shadow map camera as the pass camera.
                    this._passes[0].camera.clone(shadowMapCamera);
                }
            } else {
                this._shadowMapCullingVolume = CullingVolume.fromBoundingSphere(this._boundingSphere);
            }
        }

        if (this._passes.length === 1) {
            // Transforms from eye space to shadow texture space.
            // Always requires an update since the scene camera constantly changes.
            var inverseView = this._sceneCamera.inverseViewMatrix;
            Matrix4.multiply(this._shadowMapCamera.getViewProjection(), inverseView, this._shadowMapMatrix);
        }

        if (this.debugShow) {
            applyDebugSettings(this, frameState);
        }
    };

    /**
     * @private
     */
    ShadowMap.prototype.updatePass = function(context, shadowPass) {
        clearFramebuffer(this, context, shadowPass);
    };

    var scratchTexelStepSize = new Cartesian2();

    function combineUniforms(shadowMap, uniforms, isTerrain) {
        var bias = shadowMap._isPointLight ? shadowMap._pointBias : (isTerrain ? shadowMap._terrainBias : shadowMap._primitiveBias);

        var mapUniforms = {
            shadowMap_texture :function() {
                return shadowMap._shadowMapTexture;
            },
            shadowMap_textureCube : function() {
                return shadowMap._shadowMapTexture;
            },
            shadowMap_matrix : function() {
                return shadowMap._shadowMapMatrix;
            },
            shadowMap_cascadeSplits : function() {
                return shadowMap._cascadeSplits;
            },
            shadowMap_cascadeMatrices : function() {
                return shadowMap._cascadeMatrices;
            },
            shadowMap_lightDirectionEC : function() {
                return shadowMap._lightDirectionEC;
            },
            shadowMap_lightPositionEC : function() {
                return shadowMap._lightPositionEC;
            },
            shadowMap_cascadeDistances : function() {
                return shadowMap._cascadeDistances;
            },
            shadowMap_texelSizeDepthBiasAndNormalShadingSmooth : function() {
                var texelStepSize = scratchTexelStepSize;
                texelStepSize.x = 1.0 / shadowMap._textureSize.x;
                texelStepSize.y = 1.0 / shadowMap._textureSize.y;

                return Cartesian4.fromElements(texelStepSize.x, texelStepSize.y, bias.depthBias, bias.normalShadingSmooth, this.combinedUniforms1);
            },
            shadowMap_normalOffsetScaleDistanceMaxDistanceAndDarkness : function() {
                return Cartesian4.fromElements(bias.normalOffsetScale, shadowMap._distance, shadowMap.maximumDistance, shadowMap._darkness, this.combinedUniforms2);
            },

            combinedUniforms1 : new Cartesian4(),
            combinedUniforms2 : new Cartesian4()
        };

        return combine(uniforms, mapUniforms, false);
    }

    function createCastDerivedCommand(shadowMap, shadowsDirty, command, context, oldShaderId, result) {
        var castShader;
        var castRenderState;
        var castUniformMap;
        if (defined(result)) {
            castShader = result.shaderProgram;
            castRenderState = result.renderState;
            castUniformMap = result.uniformMap;
        }

        result = DrawCommand.shallowClone(command, result);
        result.castShadows = true;
        result.receiveShadows = false;

        if (!defined(castShader) || oldShaderId !== command.shaderProgram.id || shadowsDirty) {
            var shaderProgram = command.shaderProgram;

            var isTerrain = command.pass === Pass.GLOBE;
            var isOpaque = command.pass !== Pass.TRANSLUCENT;
            var isPointLight = shadowMap._isPointLight;
            var usesDepthTexture= shadowMap._usesDepthTexture;

            var keyword =  ShadowMapShader.getShadowCastShaderKeyword(isPointLight, isTerrain, usesDepthTexture, isOpaque);
            castShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
            if (!defined(castShader)) {
                var vertexShaderSource = shaderProgram.vertexShaderSource;
                var fragmentShaderSource = shaderProgram.fragmentShaderSource;

                var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain);
                var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, usesDepthTexture, isOpaque);

                castShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
                    vertexShaderSource : castVS,
                    fragmentShaderSource : castFS,
                    attributeLocations : shaderProgram._attributeLocations
                });
            }

            castRenderState = shadowMap._primitiveRenderState;
            if (isPointLight) {
                castRenderState = shadowMap._pointRenderState;
            } else if (isTerrain) {
                castRenderState = shadowMap._terrainRenderState;
            }

            // Modify the render state for commands that do not use back-face culling, e.g. flat textured walls
            var cullEnabled = command.renderState.cull.enabled;
            if (!cullEnabled) {
                castRenderState = clone(castRenderState, false);
                castRenderState.cull.enabled = false;
                castRenderState = RenderState.fromCache(castRenderState);
            }

            castUniformMap = combineUniforms(shadowMap, command.uniformMap, isTerrain);
        }

        result.shaderProgram = castShader;
        result.renderState = castRenderState;
        result.uniformMap = castUniformMap;

        return result;
    }

    ShadowMap.createDerivedCommands = function(shadowMaps, lightShadowMaps, command, shadowsDirty, context, result) {
        if (!defined(result)) {
            result = {};
        }

        var lightShadowMapsEnabled = (lightShadowMaps.length > 0);
        var shaderProgram = command.shaderProgram;
        var vertexShaderSource = shaderProgram.vertexShaderSource;
        var fragmentShaderSource = shaderProgram.fragmentShaderSource;
        var isTerrain = command.pass === Pass.GLOBE;

        var hasTerrainNormal = false;
        if (isTerrain) {
            hasTerrainNormal = command.owner.data.pickTerrain.mesh.encoding.hasVertexNormals;
        }

        if (command.castShadows) {
            var castCommands = result.castCommands;
            if (!defined(castCommands)) {
                castCommands = result.castCommands = [];
            }

            var oldShaderId = result.castShaderProgramId;

            var shadowMapLength = shadowMaps.length;
            castCommands.length = shadowMapLength;

            for (var i = 0; i < shadowMapLength; ++i) {
                castCommands[i] = createCastDerivedCommand(shadowMaps[i], shadowsDirty, command, context, oldShaderId, castCommands[i]);
            }

            result.castShaderProgramId = command.shaderProgram.id;
        }

        if (command.receiveShadows && lightShadowMapsEnabled) {
            // Only generate a receiveCommand if there is a shadow map originating from a light source.
            var receiveShader;
            var receiveUniformMap;
            if (defined(result.receiveCommand)) {
                receiveShader = result.receiveCommand.shaderProgram;
                receiveUniformMap = result.receiveCommand.uniformMap;
            }

            result.receiveCommand = DrawCommand.shallowClone(command, result.receiveCommand);
            result.castShadows = false;
            result.receiveShadows = true;

            // If castShadows changed, recompile the receive shadows shader. The normal shading technique simulates
            // self-shadowing so it should be turned off if castShadows is false.
            var castShadowsDirty = result.receiveShaderCastShadows !== command.castShadows;
            var shaderDirty = result.receiveShaderProgramId !== command.shaderProgram.id;

            if (!defined(receiveShader) || shaderDirty || shadowsDirty || castShadowsDirty) {
                var keyword = ShadowMapShader.getShadowReceiveShaderKeyword(lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);
                receiveShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword);
                if (!defined(receiveShader)) {
                    var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal);
                    var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal);

                    receiveShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, {
                        vertexShaderSource : receiveVS,
                        fragmentShaderSource : receiveFS,
                        attributeLocations : shaderProgram._attributeLocations
                    });
                }

                receiveUniformMap = combineUniforms(lightShadowMaps[0], command.uniformMap, isTerrain);
            }

            result.receiveCommand.shaderProgram = receiveShader;
            result.receiveCommand.uniformMap = receiveUniformMap;
            result.receiveShaderProgramId = command.shaderProgram.id;
            result.receiveShaderCastShadows = command.castShadows;
        }

        return result;
    };

    /**
     * @private
     */
    ShadowMap.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * @private
     */
    ShadowMap.prototype.destroy = function() {
        destroyFramebuffer(this);

        this._debugLightFrustum = this._debugLightFrustum && this._debugLightFrustum.destroy();
        this._debugCameraFrustum = this._debugCameraFrustum && this._debugCameraFrustum.destroy();
        this._debugShadowViewCommand = this._debugShadowViewCommand && this._debugShadowViewCommand.shaderProgram && this._debugShadowViewCommand.shaderProgram.destroy();

        for (var i = 0; i < this._numberOfCascades; ++i) {
            this._debugCascadeFrustums[i] = this._debugCascadeFrustums[i] && this._debugCascadeFrustums[i].destroy();
        }

        return destroyObject(this);
    };

    return ShadowMap;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/AdditiveBlend',[],function() {
    'use strict';
    return "uniform sampler2D u_texture0;\n\
uniform sampler2D u_texture1;\n\
\n\
uniform vec2 u_center;\n\
uniform float u_radius;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    vec4 color0 = texture2D(u_texture0, v_textureCoordinates);\n\
    vec4 color1 = texture2D(u_texture1, v_textureCoordinates);\n\
    \n\
    float x = length(gl_FragCoord.xy - u_center) / u_radius;\n\
    float t = smoothstep(0.5, 0.8, x);\n\
    gl_FragColor = mix(color0 + color1, color0, t);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/BrightPass',[],function() {
    'use strict';
    return "uniform sampler2D u_texture;\n\
\n\
uniform float u_avgLuminance;\n\
uniform float u_threshold;\n\
uniform float u_offset;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
float key(float avg)\n\
{\n\
    float guess = 1.5 - (1.5 / (avg * 0.1 + 1.0));\n\
    return max(0.0, guess) + 0.1;\n\
}\n\
\n\
// See section 9. \"The bright-pass filter\" of Realtime HDR Rendering\n\
// http://www.cg.tuwien.ac.at/research/publications/2007/Luksch_2007_RHR/Luksch_2007_RHR-RealtimeHDR%20.pdf\n\
\n\
void main()\n\
{\n\
    vec4 color = texture2D(u_texture, v_textureCoordinates);\n\
    vec3 xyz = czm_RGBToXYZ(color.rgb);\n\
    float luminance = xyz.r;\n\
    \n\
    float scaledLum = key(u_avgLuminance) * luminance / u_avgLuminance;\n\
    float brightLum = max(scaledLum - u_threshold, 0.0);\n\
    float brightness = brightLum / (u_offset + brightLum);\n\
    \n\
    xyz.r = brightness;\n\
    gl_FragColor = vec4(czm_XYZToRGB(xyz), 1.0);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/PostProcessFilters/GaussianBlur1D',[],function() {
    'use strict';
    return "#define SAMPLES 8\n\
\n\
uniform float delta;\n\
uniform float sigma;\n\
uniform float direction; // 0.0 for x direction, 1.0 for y direction\n\
\n\
uniform sampler2D u_texture;\n\
uniform vec2 u_step;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
//  Incremental Computation of the Gaussian:\n\
//  http://http.developer.nvidia.com/GPUGems3/gpugems3_ch40.html\n\
\n\
void main()\n\
{\n\
    vec2 st = v_textureCoordinates;\n\
    \n\
    vec2 dir = vec2(1.0 - direction, direction);\n\
    \n\
    vec3 g;\n\
    g.x = 1.0 / (sqrt(czm_twoPi) * sigma);\n\
    g.y = exp((-0.5 * delta * delta) / (sigma * sigma));\n\
    g.z = g.y * g.y;\n\
    \n\
    vec4 result = texture2D(u_texture, st) * g.x;\n\
    for (int i = 1; i < SAMPLES; ++i)\n\
    {\n\
        g.xy *= g.yz;\n\
        \n\
        vec2 offset = float(i) * dir * u_step;\n\
        result += texture2D(u_texture, st - offset) * g.x;\n\
        result += texture2D(u_texture, st + offset) * g.x;\n\
    }\n\
    \n\
    gl_FragColor = result;\n\
}\n\
";
});
/*global define*/
define('Scene/SunPostProcess',[
        '../Core/BoundingRectangle',
        '../Core/Cartesian2',
        '../Core/Cartesian4',
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/PixelFormat',
        '../Core/Transforms',
        '../Renderer/ClearCommand',
        '../Renderer/Framebuffer',
        '../Renderer/PassState',
        '../Renderer/PixelDatatype',
        '../Renderer/Renderbuffer',
        '../Renderer/RenderbufferFormat',
        '../Renderer/RenderState',
        '../Renderer/Texture',
        '../Shaders/PostProcessFilters/AdditiveBlend',
        '../Shaders/PostProcessFilters/BrightPass',
        '../Shaders/PostProcessFilters/GaussianBlur1D',
        '../Shaders/PostProcessFilters/PassThrough'
    ], function(
        BoundingRectangle,
        Cartesian2,
        Cartesian4,
        Color,
        defaultValue,
        defined,
        destroyObject,
        CesiumMath,
        Matrix4,
        PixelFormat,
        Transforms,
        ClearCommand,
        Framebuffer,
        PassState,
        PixelDatatype,
        Renderbuffer,
        RenderbufferFormat,
        RenderState,
        Texture,
        AdditiveBlend,
        BrightPass,
        GaussianBlur1D,
        PassThrough) {
    'use strict';

    function SunPostProcess() {
        this._fbo = undefined;

        this._downSampleFBO1 = undefined;
        this._downSampleFBO2 = undefined;

        this._clearFBO1Command = undefined;
        this._clearFBO2Command = undefined;

        this._downSampleCommand = undefined;
        this._brightPassCommand = undefined;
        this._blurXCommand = undefined;
        this._blurYCommand = undefined;
        this._blendCommand = undefined;
        this._fullScreenCommand = undefined;

        this._downSamplePassState = new PassState();
        this._downSamplePassState.scissorTest = {
            enable : true,
            rectangle : new BoundingRectangle()
        };

        this._upSamplePassState = new PassState();
        this._upSamplePassState.scissorTest = {
            enabled : true,
            rectangle : new BoundingRectangle()
        };

        this._uCenter = new Cartesian2();
        this._uRadius = undefined;

        this._blurStep = new Cartesian2();
    }

    SunPostProcess.prototype.clear = function(context, color) {
        var clear = this._clearFBO1Command;
        Color.clone(defaultValue(color, Color.BLACK), clear.color);
        clear.execute(context);

        clear = this._clearFBO2Command;
        Color.clone(defaultValue(color, Color.BLACK), clear.color);
        clear.execute(context);
    };

    SunPostProcess.prototype.execute = function(context, framebuffer) {
        this._downSampleCommand.execute(context, this._downSamplePassState);
        this._brightPassCommand.execute(context, this._downSamplePassState);
        this._blurXCommand.execute(context, this._downSamplePassState);
        this._blurYCommand.execute(context, this._downSamplePassState);

        this._fullScreenCommand.framebuffer = framebuffer;
        this._blendCommand.framebuffer = framebuffer;

        this._fullScreenCommand.execute(context);
        this._blendCommand.execute(context, this._upSamplePassState);
    };

    var viewportBoundingRectangle  = new BoundingRectangle();
    var downSampleViewportBoundingRectangle = new BoundingRectangle();
    var sunPositionECScratch = new Cartesian4();
    var sunPositionWCScratch = new Cartesian2();
    var sizeScratch = new Cartesian2();
    var postProcessMatrix4Scratch= new Matrix4();

    SunPostProcess.prototype.update = function(passState) {
        var context = passState.context;
        var viewport = passState.viewport;
        var width = context.drawingBufferWidth;
        var height = context.drawingBufferHeight;

        var that = this;

        if (!defined(this._downSampleCommand)) {
            this._clearFBO1Command = new ClearCommand({
                color : new Color()
            });
            this._clearFBO2Command = new ClearCommand({
                color : new Color()
            });

            var uniformMap = {};

            this._downSampleCommand = context.createViewportQuadCommand(PassThrough, {
                uniformMap : uniformMap,
                owner : this
            });

            uniformMap = {
                u_avgLuminance : function() {
                    // A guess at the average luminance across the entire scene
                    return 0.5;
                },
                u_threshold : function() {
                    return 0.25;
                },
                u_offset : function() {
                    return 0.1;
                }
            };

            this._brightPassCommand = context.createViewportQuadCommand(BrightPass, {
                uniformMap : uniformMap,
                owner : this
            });

            var delta = 1.0;
            var sigma = 2.0;

            uniformMap = {
                delta : function() {
                    return delta;
                },
                sigma : function() {
                    return sigma;
                },
                direction : function() {
                    return 0.0;
                }
            };

            this._blurXCommand = context.createViewportQuadCommand(GaussianBlur1D, {
                uniformMap : uniformMap,
                owner : this
            });

            uniformMap = {
                delta : function() {
                    return delta;
                },
                sigma : function() {
                    return sigma;
                },
                direction : function() {
                    return 1.0;
                }
            };

            this._blurYCommand = context.createViewportQuadCommand(GaussianBlur1D, {
                uniformMap : uniformMap,
                owner : this
            });

            uniformMap = {
                u_center : function() {
                    return that._uCenter;
                },
                u_radius : function() {
                    return that._uRadius;
                }
            };

            this._blendCommand = context.createViewportQuadCommand(AdditiveBlend, {
                uniformMap : uniformMap,
                owner : this
            });

            uniformMap = {};

            this._fullScreenCommand = context.createViewportQuadCommand(PassThrough, {
                uniformMap : uniformMap,
                owner : this
            });
        }

        var downSampleWidth = Math.pow(2.0, Math.ceil(Math.log(width) / Math.log(2)) - 2.0);
        var downSampleHeight = Math.pow(2.0, Math.ceil(Math.log(height) / Math.log(2)) - 2.0);

        // The size computed above can be less than 1.0 if size < 4.0. This will probably
        // never happen in practice, but does in the tests. Clamp to 1.0 to prevent WebGL
        // errors in the tests.
        var downSampleSize = Math.max(1.0, downSampleWidth, downSampleHeight);

        var downSampleViewport = downSampleViewportBoundingRectangle;
        downSampleViewport.width = downSampleSize;
        downSampleViewport.height = downSampleSize;

        var fbo = this._fbo;
        var colorTexture = (defined(fbo) && fbo.getColorTexture(0)) || undefined;
        if (!defined(colorTexture) || colorTexture.width !== width || colorTexture.height !== height) {
            fbo = fbo && fbo.destroy();
            this._downSampleFBO1 = this._downSampleFBO1 && this._downSampleFBO1.destroy();
            this._downSampleFBO2 = this._downSampleFBO2 && this._downSampleFBO2.destroy();

            this._blurStep.x = this._blurStep.y = 1.0 / downSampleSize;

            var colorTextures = [new Texture({
                context : context,
                width : width,
                height : height
            })];

            if (context.depthTexture) {
                fbo = this._fbo = new Framebuffer({
                    context : context,
                    colorTextures :colorTextures,
                    depthTexture : new Texture({
                        context : context,
                        width : width,
                        height : height,
                        pixelFormat : PixelFormat.DEPTH_COMPONENT,
                        pixelDatatype : PixelDatatype.UNSIGNED_SHORT
                    })
                });
            } else {
                fbo = this._fbo = new Framebuffer({
                    context : context,
                    colorTextures : colorTextures,
                    depthRenderbuffer : new Renderbuffer({
                        context : context,
                        format : RenderbufferFormat.DEPTH_COMPONENT16
                    })
                });
            }

            this._downSampleFBO1 = new Framebuffer({
                context : context,
                colorTextures : [new Texture({
                    context : context,
                    width : downSampleSize,
                    height : downSampleSize
                })]
            });
            this._downSampleFBO2 = new Framebuffer({
                context : context,
                colorTextures : [new Texture({
                    context : context,
                    width : downSampleSize,
                    height : downSampleSize
                })]
            });

            this._clearFBO1Command.framebuffer = this._downSampleFBO1;
            this._clearFBO2Command.framebuffer = this._downSampleFBO2;
            this._downSampleCommand.framebuffer = this._downSampleFBO1;
            this._brightPassCommand.framebuffer = this._downSampleFBO2;
            this._blurXCommand.framebuffer = this._downSampleFBO1;
            this._blurYCommand.framebuffer = this._downSampleFBO2;

            var downSampleRenderState = RenderState.fromCache({
                viewport : downSampleViewport
            });

            this._downSampleCommand.uniformMap.u_texture = function() {
                return fbo.getColorTexture(0);
            };
            this._downSampleCommand.renderState = downSampleRenderState;

            this._brightPassCommand.uniformMap.u_texture = function() {
                return that._downSampleFBO1.getColorTexture(0);
            };
            this._brightPassCommand.renderState = downSampleRenderState;

            this._blurXCommand.uniformMap.u_texture = function() {
                return that._downSampleFBO2.getColorTexture(0);
            };
            this._blurXCommand.uniformMap.u_step = function() {
                return that._blurStep;
            };
            this._blurXCommand.renderState = downSampleRenderState;

            this._blurYCommand.uniformMap.u_texture = function() {
                return that._downSampleFBO1.getColorTexture(0);
            };
            this._blurYCommand.uniformMap.u_step = function() {
                return that._blurStep;
            };
            this._blurYCommand.renderState = downSampleRenderState;

            var upSampledViewport = viewportBoundingRectangle;
            upSampledViewport.width = width;
            upSampledViewport.height = height;

            var upSampleRenderState = RenderState.fromCache({ viewport : upSampledViewport });

            this._blendCommand.uniformMap.u_texture0 = function() {
                return fbo.getColorTexture(0);
            };
            this._blendCommand.uniformMap.u_texture1 = function() {
                return that._downSampleFBO2.getColorTexture(0);
            };
            this._blendCommand.renderState = upSampleRenderState;

            this._fullScreenCommand.uniformMap.u_texture = function() {
                return fbo.getColorTexture(0);
            };
            this._fullScreenCommand.renderState = upSampleRenderState;
        }

        var us = context.uniformState;
        var sunPosition = us.sunPositionWC;
        var viewMatrix = us.view;
        var viewProjectionMatrix = us.viewProjection;
        var projectionMatrix = us.projection;

        // create up sampled render state
        var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, postProcessMatrix4Scratch);
        var sunPositionEC = Matrix4.multiplyByPoint(viewMatrix, sunPosition, sunPositionECScratch);
        var sunPositionWC = Transforms.pointToGLWindowCoordinates(viewProjectionMatrix, viewportTransformation, sunPosition, sunPositionWCScratch);

        sunPositionEC.x += CesiumMath.SOLAR_RADIUS;
        var limbWC = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, sunPositionEC, sunPositionEC);
        var sunSize = Cartesian2.magnitude(Cartesian2.subtract(limbWC, sunPositionWC, limbWC)) * 30.0 * 2.0;

        var size = sizeScratch;
        size.x = sunSize;
        size.y = sunSize;

        var scissorRectangle = this._upSamplePassState.scissorTest.rectangle;
        scissorRectangle.x = Math.max(sunPositionWC.x - size.x * 0.5, 0.0);
        scissorRectangle.y = Math.max(sunPositionWC.y - size.y * 0.5, 0.0);
        scissorRectangle.width = Math.min(size.x, width);
        scissorRectangle.height = Math.min(size.y, height);

        this._uCenter = Cartesian2.clone(sunPositionWC, this._uCenter);
        this._uRadius = Math.max(size.x, size.y) * 0.5;

        // create down sampled render state
        viewportTransformation = Matrix4.computeViewportTransformation(downSampleViewport, 0.0, 1.0, postProcessMatrix4Scratch);
        sunPositionWC = Transforms.pointToGLWindowCoordinates(viewProjectionMatrix, viewportTransformation, sunPosition, sunPositionWCScratch);

        size.x *= downSampleWidth / width;
        size.y *= downSampleHeight / height;

        scissorRectangle = this._downSamplePassState.scissorTest.rectangle;
        scissorRectangle.x = Math.max(sunPositionWC.x - size.x * 0.5, 0.0);
        scissorRectangle.y = Math.max(sunPositionWC.y - size.y * 0.5, 0.0);
        scissorRectangle.width = Math.min(size.x, width);
        scissorRectangle.height = Math.min(size.y, height);

        this._downSamplePassState.context = context;
        this._upSamplePassState.context = context;

        return this._fbo;
    };

    SunPostProcess.prototype.isDestroyed = function() {
        return false;
    };

    SunPostProcess.prototype.destroy = function() {
        this._fbo = this._fbo && this._fbo.destroy();
        this._downSampleFBO1 = this._downSampleFBO1 && this._downSampleFBO1.destroy();
        this._downSampleFBO2 = this._downSampleFBO2 && this._downSampleFBO2.destroy();
        this._downSampleCommand = this._downSampleCommand && this._downSampleCommand.shaderProgram && this._downSampleCommand.shaderProgram.destroy();
        this._brightPassCommand = this._brightPassCommand && this._brightPassCommand.shaderProgram && this._brightPassCommand.shaderProgram.destroy();
        this._blurXCommand = this._blurXCommand && this._blurXCommand.shaderProgram && this._blurXCommand.shaderProgram.destroy();
        this._blurYCommand = this._blurYCommand && this._blurYCommand.shaderProgram && this._blurYCommand.shaderProgram.destroy();
        this._blendCommand = this._blendCommand && this._blendCommand.shaderProgram && this._blendCommand.shaderProgram.destroy();
        this._fullScreenCommand = this._fullScreenCommand && this._fullScreenCommand.shaderProgram && this._fullScreenCommand.shaderProgram.destroy();
        return destroyObject(this);
    };

    return SunPostProcess;
});

/*global define*/
define('Scene/Scene',[
        '../Core/BoundingRectangle',
        '../Core/BoundingSphere',
        '../Core/BoxGeometry',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/Cartographic',
        '../Core/Color',
        '../Core/ColorGeometryInstanceAttribute',
        '../Core/createGuid',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/EllipsoidGeometry',
        '../Core/Event',
        '../Core/GeographicProjection',
        '../Core/GeometryInstance',
        '../Core/GeometryPipeline',
        '../Core/getTimestamp',
        '../Core/Intersect',
        '../Core/Interval',
        '../Core/JulianDate',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/mergeSort',
        '../Core/Occluder',
        '../Core/PixelFormat',
        '../Core/ShowGeometryInstanceAttribute',
        '../Core/Transforms',
        '../Renderer/ClearCommand',
        '../Renderer/ComputeEngine',
        '../Renderer/Context',
        '../Renderer/ContextLimits',
        '../Renderer/DrawCommand',
        '../Renderer/Framebuffer',
        '../Renderer/Pass',
        '../Renderer/PassState',
        '../Renderer/PixelDatatype',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/Texture',
        './Camera',
        './CreditDisplay',
        './CullingVolume',
        './DebugCameraPrimitive',
        './DepthPlane',
        './DeviceOrientationCameraController',
        './Fog',
        './FrameState',
        './FrustumCommands',
        './FXAA',
        './GlobeDepth',
        './MapMode2D',
        './OIT',
        './OrthographicFrustum',
        './PerformanceDisplay',
        './PerInstanceColorAppearance',
        './PerspectiveFrustum',
        './PerspectiveOffCenterFrustum',
        './PickDepth',
        './Primitive',
        './PrimitiveCollection',
        './SceneMode',
        './SceneTransforms',
        './SceneTransitioner',
        './ScreenSpaceCameraController',
        './ShadowMap',
        './SunPostProcess',
        './TweenCollection'
    ], function(
        BoundingRectangle,
        BoundingSphere,
        BoxGeometry,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        Cartographic,
        Color,
        ColorGeometryInstanceAttribute,
        createGuid,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EllipsoidGeometry,
        Event,
        GeographicProjection,
        GeometryInstance,
        GeometryPipeline,
        getTimestamp,
        Intersect,
        Interval,
        JulianDate,
        CesiumMath,
        Matrix4,
        mergeSort,
        Occluder,
        PixelFormat,
        ShowGeometryInstanceAttribute,
        Transforms,
        ClearCommand,
        ComputeEngine,
        Context,
        ContextLimits,
        DrawCommand,
        Framebuffer,
        Pass,
        PassState,
        PixelDatatype,
        RenderState,
        ShaderProgram,
        ShaderSource,
        Texture,
        Camera,
        CreditDisplay,
        CullingVolume,
        DebugCameraPrimitive,
        DepthPlane,
        DeviceOrientationCameraController,
        Fog,
        FrameState,
        FrustumCommands,
        FXAA,
        GlobeDepth,
        MapMode2D,
        OIT,
        OrthographicFrustum,
        PerformanceDisplay,
        PerInstanceColorAppearance,
        PerspectiveFrustum,
        PerspectiveOffCenterFrustum,
        PickDepth,
        Primitive,
        PrimitiveCollection,
        SceneMode,
        SceneTransforms,
        SceneTransitioner,
        ScreenSpaceCameraController,
        ShadowMap,
        SunPostProcess,
        TweenCollection) {
    'use strict';

    /**
     * The container for all 3D graphical objects and state in a Cesium virtual scene.  Generally,
     * a scene is not created directly; instead, it is implicitly created by {@link CesiumWidget}.
     * <p>
     * <em><code>contextOptions</code> parameter details:</em>
     * </p>
     * <p>
     * The default values are:
     * <code>
     * {
     *   webgl : {
     *     alpha : false,
     *     depth : true,
     *     stencil : false,
     *     antialias : true,
     *     premultipliedAlpha : true,
     *     preserveDrawingBuffer : false,
     *     failIfMajorPerformanceCaveat : false
     *   },
     *   allowTextureFilterAnisotropic : true
     * }
     * </code>
     * </p>
     * <p>
     * The <code>webgl</code> property corresponds to the {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
     * object used to create the WebGL context.
     * </p>
     * <p>
     * <code>webgl.alpha</code> defaults to false, which can improve performance compared to the standard WebGL default
     * of true.  If an application needs to composite Cesium above other HTML elements using alpha-blending, set
     * <code>webgl.alpha</code> to true.
     * </p>
     * <p>
     * The other <code>webgl</code> properties match the WebGL defaults for {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}.
     * </p>
     * <p>
     * <code>allowTextureFilterAnisotropic</code> defaults to true, which enables anisotropic texture filtering when the
     * WebGL extension is supported.  Setting this to false will improve performance, but hurt visual quality, especially for horizon views.
     * </p>
     *
     * @alias Scene
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Canvas} options.canvas The HTML canvas element to create the scene for.
     * @param {Object} [options.contextOptions] Context and WebGL creation properties.  See details above.
     * @param {Element} [options.creditContainer] The HTML element in which the credits will be displayed.
     * @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
     * @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
     * @param {Boolean} [options.scene3DOnly=false] If true, optimizes memory use and performance for 3D mode but disables the ability to use 2D or Columbus View.
     * @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
     * @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
     * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
     *
     * @see CesiumWidget
     * @see {@link http://www.khronos.org/registry/webgl/specs/latest/#5.2|WebGLContextAttributes}
     *
     * @exception {DeveloperError} options and options.canvas are required.
     *
     * @example
     * // Create scene without anisotropic texture filtering
     * var scene = new Cesium.Scene({
     *   canvas : canvas,
     *   contextOptions : {
     *     allowTextureFilterAnisotropic : false
     *   }
     * });
     */
    function Scene(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);
        var canvas = options.canvas;
        var contextOptions = options.contextOptions;
        var creditContainer = options.creditContainer;

                if (!defined(canvas)) {
            throw new DeveloperError('options and options.canvas are required.');
        }
        
        var context = new Context(canvas, contextOptions);
        if (!defined(creditContainer)) {
            creditContainer = document.createElement('div');
            creditContainer.style.position = 'absolute';
            creditContainer.style.bottom = '0';
            creditContainer.style['text-shadow'] = '0 0 2px #000000';
            creditContainer.style.color = '#ffffff';
            creditContainer.style['font-size'] = '10px';
            creditContainer.style['padding-right'] = '5px';
            canvas.parentNode.appendChild(creditContainer);
        }

        this._id = createGuid();
        this._frameState = new FrameState(context, new CreditDisplay(creditContainer));
        this._frameState.scene3DOnly = defaultValue(options.scene3DOnly, false);

        var ps = new PassState(context);
        ps.viewport = new BoundingRectangle();
        ps.viewport.x = 0;
        ps.viewport.y = 0;
        ps.viewport.width = context.drawingBufferWidth;
        ps.viewport.height = context.drawingBufferHeight;
        this._passState = ps;

        this._canvas = canvas;
        this._context = context;
        this._computeEngine = new ComputeEngine(context);
        this._globe = undefined;
        this._primitives = new PrimitiveCollection();
        this._groundPrimitives = new PrimitiveCollection();

        this._tweens = new TweenCollection();

        this._shaderFrameCount = 0;

        this._sunPostProcess = undefined;

        this._computeCommandList = [];
        this._frustumCommandsList = [];
        this._overlayCommandList = [];

        this._pickFramebuffer = undefined;

        this._useOIT = defaultValue(options.orderIndependentTranslucency, true);
        this._executeOITFunction = undefined;

        var globeDepth;
        if (context.depthTexture) {
            globeDepth = new GlobeDepth();
        }

        var oit;
        if (this._useOIT && defined(globeDepth)) {
            oit = new OIT(context);
        }

        this._globeDepth = globeDepth;
        this._depthPlane = new DepthPlane();
        this._oit = oit;
        this._fxaa = new FXAA();

        this._clearColorCommand = new ClearCommand({
            color : new Color(),
            stencil : 0,
            owner : this
        });
        this._depthClearCommand = new ClearCommand({
            depth : 1.0,
            owner : this
        });
        this._stencilClearCommand = new ClearCommand({
            stencil : 0
        });

        this._pickDepths = [];
        this._debugGlobeDepths = [];

        this._pickDepthPassState = undefined;
        this._pickDepthFramebuffer = undefined;
        this._pickDepthFramebufferWidth = undefined;
        this._pickDepthFramebufferHeight = undefined;
        this._depthOnlyRenderStateCache = {};

        this._transitioner = new SceneTransitioner(this);

        this._renderError = new Event();
        this._preRender = new Event();
        this._postRender = new Event();

        this._cameraStartFired = false;
        this._cameraMovedTime = undefined;

        /**
         * Exceptions occurring in <code>render</code> are always caught in order to raise the
         * <code>renderError</code> event.  If this property is true, the error is rethrown
         * after the event is raised.  If this property is false, the <code>render</code> function
         * returns normally after raising the event.
         *
         * @type {Boolean}
         * @default false
         */
        this.rethrowRenderErrors = false;

        /**
         * Determines whether or not to instantly complete the
         * scene transition animation on user input.
         *
         * @type {Boolean}
         * @default true
         */
        this.completeMorphOnUserInput = true;

        /**
         * The event fired at the beginning of a scene transition.
         * @type {Event}
         * @default Event()
         */
        this.morphStart = new Event();

        /**
         * The event fired at the completion of a scene transition.
         * @type {Event}
         * @default Event()
         */
        this.morphComplete = new Event();

        /**
         * The {@link SkyBox} used to draw the stars.
         *
         * @type {SkyBox}
         * @default undefined
         *
         * @see Scene#backgroundColor
         */
        this.skyBox = undefined;

        /**
         * The sky atmosphere drawn around the globe.
         *
         * @type {SkyAtmosphere}
         * @default undefined
         */
        this.skyAtmosphere = undefined;

        /**
         * The {@link Sun}.
         *
         * @type {Sun}
         * @default undefined
         */
        this.sun = undefined;

        /**
         * Uses a bloom filter on the sun when enabled.
         *
         * @type {Boolean}
         * @default true
         */
        this.sunBloom = true;
        this._sunBloom = undefined;

        /**
         * The {@link Moon}
         *
         * @type Moon
         * @default undefined
         */
        this.moon = undefined;

        /**
         * The background color, which is only visible if there is no sky box, i.e., {@link Scene#skyBox} is undefined.
         *
         * @type {Color}
         * @default {@link Color.BLACK}
         *
         * @see Scene#skyBox
         */
        this.backgroundColor = Color.clone(Color.BLACK);

        this._mode = SceneMode.SCENE3D;

        this._mapProjection = defined(options.mapProjection) ? options.mapProjection : new GeographicProjection();

        /**
         * The current morph transition time between 2D/Columbus View and 3D,
         * with 0.0 being 2D or Columbus View and 1.0 being 3D.
         *
         * @type {Number}
         * @default 1.0
         */
        this.morphTime = 1.0;
        /**
         * The far-to-near ratio of the multi-frustum. The default is 1,000.0.
         *
         * @type {Number}
         * @default 1000.0
         */
        this.farToNearRatio = 1000.0;

        /**
         * Determines the uniform depth size in meters of each frustum of the multifrustum in 2D. If a primitive or model close
         * to the surface shows z-fighting, decreasing this will eliminate the artifact, but decrease performance. On the
         * other hand, increasing this will increase performance but may cause z-fighting among primitives close to thesurface.
         * @type {Number}
         * @default 1.75e6
         */
        this.nearToFarDistance2D = 1.75e6;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * A function that determines what commands are executed.  As shown in the examples below,
         * the function receives the command's <code>owner</code> as an argument, and returns a boolean indicating if the
         * command should be executed.
         * </p>
         * <p>
         * The default is <code>undefined</code>, indicating that all commands are executed.
         * </p>
         *
         * @type Function
         *
         * @default undefined
         *
         * @example
         * // Do not execute any commands.
         * scene.debugCommandFilter = function(command) {
         *     return false;
         * };
         *
         * // Execute only the billboard's commands.  That is, only draw the billboard.
         * var billboards = new Cesium.BillboardCollection();
         * scene.debugCommandFilter = function(command) {
         *     return command.owner === billboards;
         * };
         */
        this.debugCommandFilter = undefined;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When <code>true</code>, commands are randomly shaded.  This is useful
         * for performance analysis to see what parts of a scene or model are
         * command-dense and could benefit from batching.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowCommands = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When <code>true</code>, commands are shaded based on the frustums they
         * overlap.  Commands in the closest frustum are tinted red, commands in
         * the next closest are green, and commands in the farthest frustum are
         * blue.  If a command overlaps more than one frustum, the color components
         * are combined, e.g., a command overlapping the first two frustums is tinted
         * yellow.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowFrustums = false;

        this._debugFrustumStatistics = undefined;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * Displays frames per second and time between frames.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowFramesPerSecond = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * Displays depth information for the indicated frustum.
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowGlobeDepth = false;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * Indicates which frustum will have depth information displayed.
         * </p>
         *
         * @type Number
         *
         * @default 1
         */
        this.debugShowDepthFrustum = 1;

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When <code>true</code>, draws outlines to show the boundaries of the camera frustums
         * </p>
         *
         * @type Boolean
         *
         * @default false
         */
        this.debugShowFrustumPlanes = false;
        this._debugShowFrustumPlanes = false;
        this._debugFrustumPlanes = undefined;

        /**
         * When <code>true</code>, enables Fast Approximate Anti-aliasing even when order independent translucency
         * is unsupported.
         *
         * @type Boolean
         * @default true
         */
        this.fxaa = true;

        /**
         * When <code>true</code>, enables picking using the depth buffer.
         *
         * @type Boolean
         * @default true
         */
        this.useDepthPicking = true;

        /**
         * When <code>true</code>, enables picking translucent geometry using the depth buffer.
         * {@link Scene#useDepthPicking} must also be true to enable picking the depth buffer.
         * <p>
         * There is a decrease in performance when enabled. There are extra draw calls to write depth for
         * translucent geometry.
         * </p>
         * @type {Boolean}
         * @default false
         */
        this.pickTranslucentDepth = false;

        /**
         * The time in milliseconds to wait before checking if the camera has not moved and fire the cameraMoveEnd event.
         * @type {Number}
         * @default 500.0
         * @private
         */
        this.cameraEventWaitTime = 500.0;

        /**
         * Set to true to copy the depth texture after rendering the globe. Makes czm_globeDepthTexture valid.
         * @type {Boolean}
         * @default false
         * @private
         */
        this.copyGlobeDepth = false;

        /**
         * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional
         * performance improvements by rendering less geometry and dispatching less terrain requests.
         * @type {Fog}
         */
        this.fog = new Fog();

        this._sunCamera = new Camera(this);

        /**
         * The shadow map in the scene. When enabled, models, primitives, and the globe may cast and receive shadows.
         * By default the light source of the shadow map is the sun.
         * @type {ShadowMap}
         */
        this.shadowMap = new ShadowMap({
            context : context,
            lightCamera : this._sunCamera,
            enabled : defaultValue(options.shadows, false)
        });

        this._terrainExaggeration = defaultValue(options.terrainExaggeration, 1.0);

        this._performanceDisplay = undefined;
        this._debugVolume = undefined;

        var camera = new Camera(this);
        this._camera = camera;
        this._cameraClone = Camera.clone(camera);
        this._screenSpaceCameraController = new ScreenSpaceCameraController(this);
        this._mapMode2D = defaultValue(options.mapMode2D, MapMode2D.INFINITE_SCROLL);

        // Keeps track of the state of a frame. FrameState is the state across
        // the primitives of the scene. This state is for internally keeping track
        // of celestial and environment effects that need to be updated/rendered in
        // a certain order as well as updating/tracking framebuffer usage.
        this._environmentState = {
            skyBoxCommand : undefined,
            skyAtmosphereCommand : undefined,
            sunDrawCommand : undefined,
            sunComputeCommand : undefined,
            moonCommand : undefined,

            isSunVisible : false,
            isMoonVisible : false,
            isReadyForAtmosphere : false,
            isSkyAtmosphereVisible : false,

            clearGlobeDepth : false,
            useDepthPlane : false,

            originalFramebuffer : undefined,
            useGlobeDepthFramebuffer : false,
            useOIT : false,
            useFXAA : false
        };

        this._useWebVR = false;
        this._cameraVR = undefined;
        this._aspectRatioVR = undefined;

        // initial guess at frustums.
        var near = camera.frustum.near;
        var far = camera.frustum.far;
        var numFrustums = Math.ceil(Math.log(far / near) / Math.log(this.farToNearRatio));
        updateFrustums(near, far, this.farToNearRatio, numFrustums, this._frustumCommandsList, false, undefined);

        // give frameState, camera, and screen space camera controller initial state before rendering
        updateFrameState(this, 0.0, JulianDate.now());
        this.initializeFrame();
    }

    var OPAQUE_FRUSTUM_NEAR_OFFSET = 0.99;

    defineProperties(Scene.prototype, {
        /**
         * Gets the canvas element to which this scene is bound.
         * @memberof Scene.prototype
         *
         * @type {Canvas}
         * @readonly
         */
        canvas : {
            get : function() {
                return this._canvas;
            }
        },

        /**
         * The drawingBufferWidth of the underlying GL context.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferWidth|drawingBufferWidth}
         */
        drawingBufferHeight : {
            get : function() {
                return this._context.drawingBufferHeight;
            }
        },

        /**
         * The drawingBufferHeight of the underlying GL context.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/registry/webgl/specs/1.0/#DOM-WebGLRenderingContext-drawingBufferHeight|drawingBufferHeight}
         */
        drawingBufferWidth : {
            get : function() {
                return this._context.drawingBufferWidth;
            }
        },

        /**
         * The maximum aliased line width, in pixels, supported by this WebGL implementation.  It will be at least one.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>ALIASED_LINE_WIDTH_RANGE</code>.
         */
        maximumAliasedLineWidth : {
            get : function() {
                return ContextLimits.maximumAliasedLineWidth;
            }
        },

        /**
         * The maximum length in pixels of one edge of a cube map, supported by this WebGL implementation.  It will be at least 16.
         * @memberof Scene.prototype
         *
         * @type {Number}
         * @readonly
         *
         * @see {@link https://www.khronos.org/opengles/sdk/docs/man/xhtml/glGet.xml|glGet} with <code>GL_MAX_CUBE_MAP_TEXTURE_SIZE</code>.
         */
        maximumCubeMapSize : {
            get : function() {
                return ContextLimits.maximumCubeMapSize;
            }
        },

        /**
         * Returns true if the pickPosition function is supported.
         * @memberof Scene.prototype
         *
         * @type {Boolean}
         * @readonly
         */
        pickPositionSupported : {
            get : function() {
                return this._context.depthTexture;
            }
        },

        /**
         * Gets or sets the depth-test ellipsoid.
         * @memberof Scene.prototype
         *
         * @type {Globe}
         */
        globe : {
            get: function() {
                return this._globe;
            },

            set: function(globe) {
                this._globe = this._globe && this._globe.destroy();
                this._globe = globe;
            }
        },

        /**
         * Gets the collection of primitives.
         * @memberof Scene.prototype
         *
         * @type {PrimitiveCollection}
         * @readonly
         */
        primitives : {
            get : function() {
                return this._primitives;
            }
        },

        /**
         * Gets the collection of ground primitives.
         * @memberof Scene.prototype
         *
         * @type {PrimitiveCollection}
         * @readonly
         */
        groundPrimitives : {
            get : function() {
                return this._groundPrimitives;
            }
        },

        /**
         * Gets the camera.
         * @memberof Scene.prototype
         *
         * @type {Camera}
         * @readonly
         */
        camera : {
            get : function() {
                return this._camera;
            }
        },
        // TODO: setCamera

        /**
         * Gets the controller for camera input handling.
         * @memberof Scene.prototype
         *
         * @type {ScreenSpaceCameraController}
         * @readonly
         */
        screenSpaceCameraController : {
            get : function() {
                return this._screenSpaceCameraController;
            }
        },

        /**
         * Get the map projection to use in 2D and Columbus View modes.
         * @memberof Scene.prototype
         *
         * @type {MapProjection}
         * @readonly
         *
         * @default new GeographicProjection()
         */
        mapProjection : {
            get: function() {
                return this._mapProjection;
            }
        },

        /**
         * Gets state information about the current scene. If called outside of a primitive's <code>update</code>
         * function, the previous frame's state is returned.
         * @memberof Scene.prototype
         *
         * @type {FrameState}
         * @readonly
         *
         * @private
         */
        frameState : {
            get: function() {
                return this._frameState;
            }
        },

        /**
         * Gets the collection of tweens taking place in the scene.
         * @memberof Scene.prototype
         *
         * @type {TweenCollection}
         * @readonly
         *
         * @private
         */
        tweens : {
            get : function() {
                return this._tweens;
            }
        },

        /**
         * Gets the collection of image layers that will be rendered on the globe.
         * @memberof Scene.prototype
         *
         * @type {ImageryLayerCollection}
         * @readonly
         */
        imageryLayers : {
            get : function() {
                return this.globe.imageryLayers;
            }
        },

        /**
         * The terrain provider providing surface geometry for the globe.
         * @memberof Scene.prototype
         *
         * @type {TerrainProvider}
         */
        terrainProvider : {
            get : function() {
                return this.globe.terrainProvider;
            },
            set : function(terrainProvider) {
                this.globe.terrainProvider = terrainProvider;
            }
        },

        /**
         * Gets an event that's raised when the terrain provider is changed
         * @memberof Scene.prototype
         *
         * @type {Event}
         * @readonly
         */
        terrainProviderChanged : {
            get : function() {
                return this.globe.terrainProviderChanged;
            }
        },

        /**
         * Gets the event that will be raised when an error is thrown inside the <code>render</code> function.
         * The Scene instance and the thrown error are the only two parameters passed to the event handler.
         * By default, errors are not rethrown after this event is raised, but that can be changed by setting
         * the <code>rethrowRenderErrors</code> property.
         * @memberof Scene.prototype
         *
         * @type {Event}
         * @readonly
         */
        renderError : {
            get : function() {
                return this._renderError;
            }
        },

        /**
         * Gets the event that will be raised at the start of each call to <code>render</code>.  Subscribers to the event
         * receive the Scene instance as the first parameter and the current time as the second parameter.
         * @memberof Scene.prototype
         *
         * @type {Event}
         * @readonly
         */
        preRender : {
            get : function() {
                return this._preRender;
            }
        },

        /**
         * Gets the event that will be raised at the end of each call to <code>render</code>.  Subscribers to the event
         * receive the Scene instance as the first parameter and the current time as the second parameter.
         * @memberof Scene.prototype
         *
         * @type {Event}
         * @readonly
         */
        postRender : {
            get : function() {
                return this._postRender;
            }
        },

        /**
         * @memberof Scene.prototype
         * @private
         * @readonly
         */
        context : {
            get : function() {
                return this._context;
            }
        },

        /**
         * This property is for debugging only; it is not for production use.
         * <p>
         * When {@link Scene.debugShowFrustums} is <code>true</code>, this contains
         * properties with statistics about the number of command execute per frustum.
         * <code>totalCommands</code> is the total number of commands executed, ignoring
         * overlap. <code>commandsInFrustums</code> is an array with the number of times
         * commands are executed redundantly, e.g., how many commands overlap two or
         * three frustums.
         * </p>
         *
         * @memberof Scene.prototype
         *
         * @type {Object}
         * @readonly
         *
         * @default undefined
         */
        debugFrustumStatistics : {
            get : function() {
                return this._debugFrustumStatistics;
            }
        },

        /**
         * Gets whether or not the scene is optimized for 3D only viewing.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @readonly
         */
        scene3DOnly : {
            get : function() {
                return this._frameState.scene3DOnly;
            }
        },

        /**
         * Gets whether or not the scene has order independent translucency enabled.
         * Note that this only reflects the original construction option, and there are
         * other factors that could prevent OIT from functioning on a given system configuration.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @readonly
         */
        orderIndependentTranslucency : {
            get : function() {
                return defined(this._oit);
            }
        },

        /**
         * Gets the unique identifier for this scene.
         * @memberof Scene.prototype
         * @type {String}
         * @readonly
         */
        id : {
            get : function() {
                return this._id;
            }
        },

        /**
         * Gets or sets the current mode of the scene.
         * @memberof Scene.prototype
         * @type {SceneMode}
         * @default {@link SceneMode.SCENE3D}
         */
        mode : {
            get : function() {
                return this._mode;
            },
            set : function(value) {
                                if (this.scene3DOnly && value !== SceneMode.SCENE3D) {
                    throw new DeveloperError('Only SceneMode.SCENE3D is valid when scene3DOnly is true.');
                }
                                if (value === SceneMode.SCENE2D) {
                    this.morphTo2D(0);
                } else if (value === SceneMode.SCENE3D) {
                    this.morphTo3D(0);
                } else if (value === SceneMode.COLUMBUS_VIEW) {
                    this.morphToColumbusView(0);
                                    } else {
                    throw new DeveloperError('value must be a valid SceneMode enumeration.');
                                    }
                this._mode = value;
            }
        },

        /**
         * Gets the number of frustums used in the last frame.
         * @memberof Scene.prototype
         * @type {Number}
         *
         * @private
         */
        numberOfFrustums : {
            get : function() {
                return this._frustumCommandsList.length;
            }
        },

        /**
         * Gets the scalar used to exaggerate the terrain.
         * @memberof Scene.prototype
         * @type {Number}
         */
        terrainExaggeration : {
            get : function() {
                return this._terrainExaggeration;
            }
        },

        /**
         * When <code>true</code>, splits the scene into two viewports with steroscopic views for the left and right eyes.
         * Used for cardboard and WebVR.
         * @memberof Scene.prototype
         * @type {Boolean}
         * @default false
         */
        useWebVR : {
            get : function() {
                return this._useWebVR;
            },
            set : function(value) {
                this._useWebVR = value;
                if (this._useWebVR) {
                    this._frameState.creditDisplay.container.style.visibility = 'hidden';
                    this._cameraVR = new Camera(this);
                    if (!defined(this._deviceOrientationCameraController)) {
                        this._deviceOrientationCameraController = new DeviceOrientationCameraController(this);
                    }

                    this._aspectRatioVR = this._camera.frustum.aspectRatio;
                } else {
                    this._frameState.creditDisplay.container.style.visibility = 'visible';
                    this._cameraVR = undefined;
                    this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy();

                    this._camera.frustum.aspectRatio = this._aspectRatioVR;
                    this._camera.frustum.xOffset = 0.0;
                }
            }
        },

        /**
         * Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
         * @memberof Scene.prototype
         * @type {Boolean}
         */
        mapMode2D : {
            get : function() {
                return this._mapMode2D;
            }
        },

         /**
         * Gets or sets the position of the Imagery splitter within the viewport.  Valid values are between 0.0 and 1.0.
         * @memberof Scene.prototype
         *
         * @type {Number}
         */
        imagerySplitPosition : {
            get: function() {
                return this._frameState.imagerySplitPosition;
            },

            set: function(value) {
                this._frameState.imagerySplitPosition = value;
            }
        }
    });

    /**
     * Determines if a compressed texture format is supported.
     * @param {String} format The texture format. May be the name of the format or the WebGL extension name, e.g. s3tc or WEBGL_compressed_texture_s3tc.
     * @return {boolean} Whether or not the format is supported.
     */
    Scene.prototype.getCompressedTextureFormatSupported = function(format) {
        var context = this.context;
        return ((format === 'WEBGL_compressed_texture_s3tc' || format === 's3tc') && context.s3tc) ||
               ((format === 'WEBGL_compressed_texture_pvrtc' || format === 'pvrtc') && context.pvrtc) ||
               ((format === 'WEBGL_compressed_texture_etc1' || format === 'etc1') && context.etc1);
    };

    var scratchPosition0 = new Cartesian3();
    var scratchPosition1 = new Cartesian3();
    function maxComponent(a, b) {
        var x = Math.max(Math.abs(a.x), Math.abs(b.x));
        var y = Math.max(Math.abs(a.y), Math.abs(b.y));
        var z = Math.max(Math.abs(a.z), Math.abs(b.z));
        return Math.max(Math.max(x, y), z);
    }

    function cameraEqual(camera0, camera1, epsilon) {
        var scalar = 1 / Math.max(1, maxComponent(camera0.position, camera1.position));
        Cartesian3.multiplyByScalar(camera0.position, scalar, scratchPosition0);
        Cartesian3.multiplyByScalar(camera1.position, scalar, scratchPosition1);
        return Cartesian3.equalsEpsilon(scratchPosition0, scratchPosition1, epsilon) &&
            Cartesian3.equalsEpsilon(camera0.direction, camera1.direction, epsilon) &&
            Cartesian3.equalsEpsilon(camera0.up, camera1.up, epsilon) &&
            Cartesian3.equalsEpsilon(camera0.right, camera1.right, epsilon) &&
            Matrix4.equalsEpsilon(camera0.transform, camera1.transform, epsilon);
    }

    function updateDerivedCommands(scene, command) {
        var frameState = scene.frameState;
        var context = scene._context;
        var shadowsEnabled = frameState.shadowHints.shadowsEnabled;
        var shadowMaps = frameState.shadowHints.shadowMaps;
        var lightShadowMaps = frameState.shadowHints.lightShadowMaps;
        var lightShadowsEnabled = shadowsEnabled && (lightShadowMaps.length > 0);

        // Update derived commands when any shadow maps become dirty
        var shadowsDirty = false;
        var lastDirtyTime = frameState.shadowHints.lastDirtyTime;
        if (command.lastDirtyTime !== lastDirtyTime) {
            command.lastDirtyTime = lastDirtyTime;
            command.dirty = true;
            shadowsDirty = true;
        }

        var derivedCommands = command.derivedCommands;
        if (command.dirty && defined(derivedCommands)) {
            command.dirty = false;

            if (shadowsEnabled && (command.receiveShadows || command.castShadows)) {
                derivedCommands.shadows = ShadowMap.createDerivedCommands(shadowMaps, lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows);
            }

            var oit = scene._oit;
            if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) {
                if (lightShadowsEnabled && command.receiveShadows) {
                    derivedCommands.oit = defined(derivedCommands.oit) ? derivedCommands.oit : {};
                    derivedCommands.oit.shadows = oit.createDerivedCommands(command.derivedCommands.shadows.receiveCommand, context, derivedCommands.oit.shadows);
                } else {
                    derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit);
                }
            }

            derivedCommands.depth = createDepthOnlyDerivedCommand(scene, command, context, derivedCommands.depth);
        }
    }

    var scratchOccluderBoundingSphere = new BoundingSphere();
    var scratchOccluder;

    function getOccluder(scene) {
        // TODO: The occluder is the top-level globe. When we add
        //       support for multiple central bodies, this should be the closest one.
        var globe = scene.globe;
        if (scene._mode === SceneMode.SCENE3D && defined(globe)) {
            var ellipsoid = globe.ellipsoid;
            scratchOccluderBoundingSphere.radius = ellipsoid.minimumRadius;
            scratchOccluder = Occluder.fromBoundingSphere(scratchOccluderBoundingSphere, scene._camera.positionWC, scratchOccluder);
            return scratchOccluder;
        }

        return undefined;
    }

    function clearPasses(passes) {
        passes.render = false;
        passes.pick = false;
        passes.depth = false;
    }

    function updateFrameState(scene, frameNumber, time) {
        var camera = scene._camera;

        var frameState = scene._frameState;
        frameState.commandList.length = 0;
        frameState.shadowMaps.length = 0;
        frameState.mode = scene._mode;
        frameState.morphTime = scene.morphTime;
        frameState.mapProjection = scene.mapProjection;
        frameState.frameNumber = frameNumber;
        frameState.time = JulianDate.clone(time, frameState.time);
        frameState.camera = camera;
        frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
        frameState.occluder = getOccluder(scene);
        frameState.terrainExaggeration = scene._terrainExaggeration;
        if (defined(scene.globe)) {
            frameState.maximumScreenSpaceError = scene.globe.maximumScreenSpaceError;
        } else {
            frameState.maximumScreenSpaceError = 2;
        }

        clearPasses(frameState.passes);
    }

    function updateFrustums(near, far, farToNearRatio, numFrustums, frustumCommandsList, is2D, nearToFarDistance2D) {
        frustumCommandsList.length = numFrustums;
        for (var m = 0; m < numFrustums; ++m) {
            var curNear;
            var curFar;

            if (!is2D) {
                curNear = Math.max(near, Math.pow(farToNearRatio, m) * near);
                curFar = Math.min(far, farToNearRatio * curNear);
            } else {
                curNear = Math.min(far - nearToFarDistance2D, near + m * nearToFarDistance2D);
                curFar = Math.min(far, curNear + nearToFarDistance2D);
            }

            var frustumCommands = frustumCommandsList[m];
            if (!defined(frustumCommands)) {
                frustumCommands = frustumCommandsList[m] = new FrustumCommands(curNear, curFar);
            } else {
                frustumCommands.near = curNear;
                frustumCommands.far = curFar;
            }
        }
    }

    function insertIntoBin(scene, command, distance) {
        if (scene.debugShowFrustums) {
            command.debugOverlappingFrustums = 0;
        }

        if (!scene.frameState.passes.pick) {
            updateDerivedCommands(scene, command);
        }

        var frustumCommandsList = scene._frustumCommandsList;
        var length = frustumCommandsList.length;

        for (var i = 0; i < length; ++i) {
            var frustumCommands = frustumCommandsList[i];
            var curNear = frustumCommands.near;
            var curFar = frustumCommands.far;

            if (distance.start > curFar) {
                continue;
            }

            if (distance.stop < curNear) {
                break;
            }

            var pass = command instanceof ClearCommand ? Pass.OPAQUE : command.pass;
            var index = frustumCommands.indices[pass]++;
            frustumCommands.commands[pass][index] = command;

            if (scene.debugShowFrustums) {
                command.debugOverlappingFrustums |= (1 << i);
            }

            if (command.executeInClosestFrustum) {
                break;
            }
        }

        if (scene.debugShowFrustums) {
            var cf = scene._debugFrustumStatistics.commandsInFrustums;
            cf[command.debugOverlappingFrustums] = defined(cf[command.debugOverlappingFrustums]) ? cf[command.debugOverlappingFrustums] + 1 : 1;
            ++scene._debugFrustumStatistics.totalCommands;
        }
    }

    var scratchCullingVolume = new CullingVolume();
    var distances = new Interval();

    function isVisible(command, cullingVolume, occluder) {
        return ((defined(command)) &&
                ((!defined(command.boundingVolume)) ||
                 !command.cull ||
                 ((cullingVolume.computeVisibility(command.boundingVolume) !== Intersect.OUTSIDE) &&
                  (!defined(occluder) || !command.boundingVolume.isOccluded(occluder)))));
    }

    function createPotentiallyVisibleSet(scene) {
        var frameState = scene._frameState;
        var camera = frameState.camera;
        var direction = camera.directionWC;
        var position = camera.positionWC;

        var computeList = scene._computeCommandList;
        var overlayList = scene._overlayCommandList;
        var commandList = frameState.commandList;

        if (scene.debugShowFrustums) {
            scene._debugFrustumStatistics = {
                totalCommands : 0,
                commandsInFrustums : {}
            };
        }

        var frustumCommandsList = scene._frustumCommandsList;
        var numberOfFrustums = frustumCommandsList.length;
        var numberOfPasses = Pass.NUMBER_OF_PASSES;
        for (var n = 0; n < numberOfFrustums; ++n) {
            for (var p = 0; p < numberOfPasses; ++p) {
                frustumCommandsList[n].indices[p] = 0;
            }
        }

        computeList.length = 0;
        overlayList.length = 0;

        var near = Number.MAX_VALUE;
        var far = -Number.MAX_VALUE;
        var undefBV = false;

        var shadowsEnabled = frameState.shadowHints.shadowsEnabled;
        var shadowNear = Number.MAX_VALUE;
        var shadowFar = -Number.MAX_VALUE;
        var shadowClosestObjectSize = Number.MAX_VALUE;

        var occluder = (frameState.mode === SceneMode.SCENE3D) ? frameState.occluder: undefined;
        var cullingVolume = frameState.cullingVolume;

        // get user culling volume minus the far plane.
        var planes = scratchCullingVolume.planes;
        for (var k = 0; k < 5; ++k) {
            planes[k] = cullingVolume.planes[k];
        }
        cullingVolume = scratchCullingVolume;

        // Determine visibility of celestial and terrestrial environment effects.
        var environmentState = scene._environmentState;
        environmentState.isSkyAtmosphereVisible = defined(environmentState.skyAtmosphereCommand) && environmentState.isReadyForAtmosphere;
        environmentState.isSunVisible = isVisible(environmentState.sunDrawCommand, cullingVolume, occluder);
        environmentState.isMoonVisible = isVisible(environmentState.moonCommand, cullingVolume, occluder);

        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            var command = commandList[i];
            var pass = command.pass;

            if (pass === Pass.COMPUTE) {
                computeList.push(command);
            } else if (pass === Pass.OVERLAY) {
                overlayList.push(command);
            } else {
                var boundingVolume = command.boundingVolume;
                if (defined(boundingVolume)) {
                    if (!isVisible(command, cullingVolume, occluder)) {
                        continue;
                    }

                    distances = boundingVolume.computePlaneDistances(position, direction, distances);
                    near = Math.min(near, distances.start);
                    far = Math.max(far, distances.stop);

                    // Compute a tight near and far plane for commands that receive shadows. This helps compute
                    // good splits for cascaded shadow maps. Ignore commands that exceed the maximum distance.
                    // When moving the camera low LOD globe tiles begin to load, whose bounding volumes
                    // throw off the near/far fitting for the shadow map. Only update for globe tiles that the
                    // camera isn't inside.
                    if (shadowsEnabled && command.receiveShadows && (distances.start < ShadowMap.MAXIMUM_DISTANCE) &&
                        !((pass === Pass.GLOBE) && (distances.start < -100.0) && (distances.stop > 100.0))) {

                        // Get the smallest bounding volume the camera is near. This is used to place more shadow detail near the object.
                        var size = distances.stop - distances.start;
                        if ((pass !== Pass.GLOBE) && (distances.start < 100.0)) {
                            shadowClosestObjectSize = Math.min(shadowClosestObjectSize, size);
                        }
                        shadowNear = Math.min(shadowNear, distances.start);
                        shadowFar = Math.max(shadowFar, distances.stop);
                    }
                } else {
                    // Clear commands don't need a bounding volume - just add the clear to all frustums.
                    // If another command has no bounding volume, though, we need to use the camera's
                    // worst-case near and far planes to avoid clipping something important.
                    distances.start = camera.frustum.near;
                    distances.stop = camera.frustum.far;
                    undefBV = !(command instanceof ClearCommand);
                }

                insertIntoBin(scene, command, distances);
            }
        }

        if (undefBV) {
            near = camera.frustum.near;
            far = camera.frustum.far;
        } else {
            // The computed near plane must be between the user defined near and far planes.
            // The computed far plane must between the user defined far and computed near.
            // This will handle the case where the computed near plane is further than the user defined far plane.
            near = Math.min(Math.max(near, camera.frustum.near), camera.frustum.far);
            far = Math.max(Math.min(far, camera.frustum.far), near);

            if (shadowsEnabled) {
                shadowNear = Math.min(Math.max(shadowNear, camera.frustum.near), camera.frustum.far);
                shadowFar = Math.max(Math.min(shadowFar, camera.frustum.far), shadowNear);
            }
        }

        // Use the computed near and far for shadows
        if (shadowsEnabled) {
            frameState.shadowHints.nearPlane = shadowNear;
            frameState.shadowHints.farPlane = shadowFar;
            frameState.shadowHints.closestObjectSize = shadowClosestObjectSize;
        }

        // Exploit temporal coherence. If the frustums haven't changed much, use the frustums computed
        // last frame, else compute the new frustums and sort them by frustum again.
        var is2D = scene.mode === SceneMode.SCENE2D;
        var farToNearRatio = scene.farToNearRatio;

        var numFrustums;
        if (!is2D) {
            // The multifrustum for 3D/CV is non-uniformly distributed.
            numFrustums = Math.ceil(Math.log(far / near) / Math.log(farToNearRatio));
        } else {
            // The multifrustum for 2D is uniformly distributed. To avoid z-fighting in 2D,
            // the camera i smoved to just before the frustum and the frustum depth is scaled
            // to be in [1.0, nearToFarDistance2D].
            far = Math.min(far, camera.position.z + scene.nearToFarDistance2D);
            near = Math.min(near, far);
            numFrustums = Math.ceil(Math.max(1.0, far - near) / scene.nearToFarDistance2D);
        }

        if (near !== Number.MAX_VALUE && (numFrustums !== numberOfFrustums || (frustumCommandsList.length !== 0 &&
                (near < frustumCommandsList[0].near || (far > frustumCommandsList[numberOfFrustums - 1].far && !CesiumMath.equalsEpsilon(far, frustumCommandsList[numberOfFrustums - 1].far, CesiumMath.EPSILON8)))))) {
            updateFrustums(near, far, farToNearRatio, numFrustums, frustumCommandsList, is2D, scene.nearToFarDistance2D);
            createPotentiallyVisibleSet(scene);
        }

        var frustumSplits = frameState.frustumSplits;
        frustumSplits.length = numFrustums + 1;
        for (var j = 0; j < numFrustums; ++j) {
            frustumSplits[j] = frustumCommandsList[j].near;
            if (j === numFrustums - 1) {
                frustumSplits[j + 1] = frustumCommandsList[j].far;
            }
        }
    }

    function getAttributeLocations(shaderProgram) {
        var attributeLocations = {};
        var attributes = shaderProgram.vertexAttributes;
        for (var a in attributes) {
            if (attributes.hasOwnProperty(a)) {
                attributeLocations[a] = attributes[a].index;
            }
        }

        return attributeLocations;
    }

    function createDebugFragmentShaderProgram(command, scene, shaderProgram) {
        var context = scene.context;
        var sp = defaultValue(shaderProgram, command.shaderProgram);
        var fs = sp.fragmentShaderSource.clone();

        var targets = [];
        fs.sources = fs.sources.map(function(source) {
            source = ShaderSource.replaceMain(source, 'czm_Debug_main');
            var re = /gl_FragData\[(\d+)\]/g;
            var match;
            while ((match = re.exec(source)) !== null) {
                if (targets.indexOf(match[1]) === -1) {
                    targets.push(match[1]);
                }
            }
            return source;
        });
        var length = targets.length;

        var newMain =
            'void main() \n' +
            '{ \n' +
            '    czm_Debug_main(); \n';

        var i;
        if (scene.debugShowCommands) {
            if (!defined(command._debugColor)) {
                command._debugColor = Color.fromRandom();
            }
            var c = command._debugColor;
            if (length > 0) {
                for (i = 0; i < length; ++i) {
                    newMain += '    gl_FragData[' + targets[i] + '].rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
                }
            } else {
                newMain += '    ' + 'gl_FragColor' + '.rgb *= vec3(' + c.red + ', ' + c.green + ', ' + c.blue + '); \n';
            }
        }

        if (scene.debugShowFrustums) {
            // Support up to three frustums.  If a command overlaps all
            // three, it's code is not changed.
            var r = (command.debugOverlappingFrustums & (1 << 0)) ? '1.0' : '0.0';
            var g = (command.debugOverlappingFrustums & (1 << 1)) ? '1.0' : '0.0';
            var b = (command.debugOverlappingFrustums & (1 << 2)) ? '1.0' : '0.0';
            if (length > 0) {
                for (i = 0; i < length; ++i) {
                    newMain += '    gl_FragData[' + targets[i] + '].rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
                }
            } else {
                newMain += '    ' + 'gl_FragColor' + '.rgb *= vec3(' + r + ', ' + g + ', ' + b + '); \n';
            }
        }

        newMain += '}';

        fs.sources.push(newMain);

        var attributeLocations = getAttributeLocations(sp);

        return ShaderProgram.fromCache({
            context : context,
            vertexShaderSource : sp.vertexShaderSource,
            fragmentShaderSource : fs,
            attributeLocations : attributeLocations
        });
    }

    function executeDebugCommand(command, scene, passState) {
        var debugCommand = DrawCommand.shallowClone(command);
        debugCommand.shaderProgram = createDebugFragmentShaderProgram(command, scene);
        debugCommand.execute(scene.context, passState);
        debugCommand.shaderProgram.destroy();
    }

    var transformFrom2D = new Matrix4(0.0, 0.0, 1.0, 0.0,
                                      1.0, 0.0, 0.0, 0.0,
                                      0.0, 1.0, 0.0, 0.0,
                                      0.0, 0.0, 0.0, 1.0);
    transformFrom2D = Matrix4.inverseTransformation(transformFrom2D, transformFrom2D);

    function executeCommand(command, scene, context, passState, debugFramebuffer) {
        if ((defined(scene.debugCommandFilter)) && !scene.debugCommandFilter(command)) {
            return;
        }

        var shadowsEnabled = scene.frameState.shadowHints.shadowsEnabled;
        var lightShadowsEnabled = shadowsEnabled && (scene.frameState.shadowHints.lightShadowMaps.length > 0);

        if (scene.debugShowCommands || scene.debugShowFrustums) {
            executeDebugCommand(command, scene, passState);
        } else if (lightShadowsEnabled && command.receiveShadows && defined(command.derivedCommands.shadows)) {
            // If the command receives shadows, execute the derived shadows command.
            // Some commands, such as OIT derived commands, do not have derived shadow commands themselves
            // and instead shadowing is built-in. In this case execute the command regularly below.
            command.derivedCommands.shadows.receiveCommand.execute(context, passState);
        } else if (scene.frameState.passes.depth && defined(command.derivedCommands.depth)) {
            command.derivedCommands.depth.depthOnlyCommand.execute(context, passState);
        } else {
            command.execute(context, passState);
        }

        if (command.debugShowBoundingVolume && (defined(command.boundingVolume))) {
            // Debug code to draw bounding volume for command.  Not optimized!
            // Assumes bounding volume is a bounding sphere or box
            var frameState = scene._frameState;
            var boundingVolume = command.boundingVolume;

            if (defined(scene._debugVolume)) {
                scene._debugVolume.destroy();
            }

            var geometry;

            var center = Cartesian3.clone(boundingVolume.center);
            if (frameState.mode !== SceneMode.SCENE3D) {
                center = Matrix4.multiplyByPoint(transformFrom2D, center, center);
                var projection = frameState.mapProjection;
                var centerCartographic = projection.unproject(center);
                center = projection.ellipsoid.cartographicToCartesian(centerCartographic);
            }

            if (defined(boundingVolume.radius)) {
                var radius = boundingVolume.radius;

                geometry = GeometryPipeline.toWireframe(EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
                    radii : new Cartesian3(radius, radius, radius),
                    vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
                })));

                scene._debugVolume = new Primitive({
                    geometryInstances : new GeometryInstance({
                        geometry : geometry,
                        modelMatrix : Matrix4.multiplyByTranslation(Matrix4.IDENTITY, center, new Matrix4()),
                        attributes : {
                            color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
                        }
                    }),
                    appearance : new PerInstanceColorAppearance({
                        flat : true,
                        translucent : false
                    }),
                    asynchronous : false
                });
            } else {
                var halfAxes = boundingVolume.halfAxes;

                geometry = GeometryPipeline.toWireframe(BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
                    dimensions : new Cartesian3(2.0, 2.0, 2.0),
                    vertexFormat : PerInstanceColorAppearance.FLAT_VERTEX_FORMAT
                })));

                scene._debugVolume = new Primitive({
                    geometryInstances : new GeometryInstance({
                        geometry : geometry,
                        modelMatrix : Matrix4.fromRotationTranslation(halfAxes, center, new Matrix4()),
                        attributes : {
                            color : new ColorGeometryInstanceAttribute(1.0, 0.0, 0.0, 1.0)
                        }
                    }),
                    appearance : new PerInstanceColorAppearance({
                        flat : true,
                        translucent : false
                    }),
                    asynchronous : false
                });
            }

            var savedCommandList = frameState.commandList;
            var commandList = frameState.commandList = [];
            scene._debugVolume.update(frameState);

            var framebuffer;
            if (defined(debugFramebuffer)) {
                framebuffer = passState.framebuffer;
                passState.framebuffer = debugFramebuffer;
            }

            commandList[0].execute(context, passState);

            if (defined(framebuffer)) {
                passState.framebuffer = framebuffer;
            }

            frameState.commandList = savedCommandList;
        }
    }

    function translucentCompare(a, b, position) {
        return b.boundingVolume.distanceSquaredTo(position) - a.boundingVolume.distanceSquaredTo(position);
    }

    function executeTranslucentCommandsSorted(scene, executeFunction, passState, commands) {
        var context = scene.context;

        mergeSort(commands, translucentCompare, scene._camera.positionWC);

        var length = commands.length;
        for (var j = 0; j < length; ++j) {
            executeFunction(commands[j], scene, context, passState);
        }
    }

    function getDebugGlobeDepth(scene, index) {
        var globeDepth = scene._debugGlobeDepths[index];
        if (!defined(globeDepth) && scene.context.depthTexture) {
            globeDepth = new GlobeDepth();
            scene._debugGlobeDepths[index] = globeDepth;
        }
        return globeDepth;
    }

    function getPickDepth(scene, index) {
        var pickDepth = scene._pickDepths[index];
        if (!defined(pickDepth)) {
            pickDepth = new PickDepth();
            scene._pickDepths[index] = pickDepth;
        }
        return pickDepth;
    }

    var scratchPerspectiveFrustum = new PerspectiveFrustum();
    var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
    var scratchOrthographicFrustum = new OrthographicFrustum();

    function executeCommands(scene, passState) {
        var camera = scene._camera;
        var context = scene.context;
        var us = context.uniformState;

        us.updateCamera(camera);

        // Create a working frustum from the original camera frustum.
        var frustum;
        if (defined(camera.frustum.fov)) {
            frustum = camera.frustum.clone(scratchPerspectiveFrustum);
        } else if (defined(camera.frustum.infiniteProjectionMatrix)){
            frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
        } else {
            frustum = camera.frustum.clone(scratchOrthographicFrustum);
        }

        // Ideally, we would render the sky box and atmosphere last for
        // early-z, but we would have to draw it in each frustum
        frustum.near = camera.frustum.near;
        frustum.far = camera.frustum.far;
        us.updateFrustum(frustum);
        us.updatePass(Pass.ENVIRONMENT);

        var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D;
        var passes = scene._frameState.passes;
        var picking = passes.pick;
        var depthOnly = passes.depth;
        var environmentState = scene._environmentState;

        // Do not render environment primitives during a pick pass since they do not generate picking commands.
        if (!picking) {
            var skyBoxCommand = environmentState.skyBoxCommand;
            if (defined(skyBoxCommand)) {
                executeCommand(skyBoxCommand, scene, context, passState);
            }

            if (environmentState.isSkyAtmosphereVisible) {
                executeCommand(environmentState.skyAtmosphereCommand, scene, context, passState);
            }

            if (environmentState.isSunVisible) {
                environmentState.sunDrawCommand.execute(context, passState);
                if (scene.sunBloom && !useWebVR) {
                    var framebuffer;
                    if (environmentState.useGlobeDepthFramebuffer) {
                        framebuffer = scene._globeDepth.framebuffer;
                    } else if (environmentState.useFXAA) {
                        framebuffer = scene._fxaa.getColorFramebuffer();
                    } else {
                        framebuffer = environmentState.originalFramebuffer;
                    }
                    scene._sunPostProcess.execute(context, framebuffer);
                    passState.framebuffer = framebuffer;
                }
            }

            // Moon can be seen through the atmosphere, since the sun is rendered after the atmosphere.
            if (environmentState.isMoonVisible) {
                environmentState.moonCommand.execute(context, passState);
            }
        }

        // Determine how translucent surfaces will be handled.
        var executeTranslucentCommands;
        if (environmentState.useOIT) {
            if (!defined(scene._executeOITFunction)) {
                scene._executeOITFunction = function(scene, executeFunction, passState, commands) {
                    scene._oit.executeCommands(scene, executeFunction, passState, commands);
                };
            }
            executeTranslucentCommands = scene._executeOITFunction;
        } else {
            executeTranslucentCommands = executeTranslucentCommandsSorted;
        }

        var clearGlobeDepth = environmentState.clearGlobeDepth;
        var useDepthPlane = environmentState.useDepthPlane;
        var clearDepth = scene._depthClearCommand;
        var depthPlane = scene._depthPlane;

        var height2D = camera.position.z;

        // Execute commands in each frustum in back to front order
        var j;
        var frustumCommandsList = scene._frustumCommandsList;
        var numFrustums = frustumCommandsList.length;

        for (var i = 0; i < numFrustums; ++i) {
            var index = numFrustums - i - 1;
            var frustumCommands = frustumCommandsList[index];

            if (scene.mode === SceneMode.SCENE2D) {
                // To avoid z-fighting in 2D, move the camera to just before the frustum
                // and scale the frustum depth to be in [1.0, nearToFarDistance2D].
                camera.position.z = height2D - frustumCommands.near + 1.0;
                frustum.far = Math.max(1.0, frustumCommands.far - frustumCommands.near);
                frustum.near = 1.0;
                us.update(scene.frameState);
                us.updateFrustum(frustum);
            } else {
                // Avoid tearing artifacts between adjacent frustums in the opaque passes
                frustum.near = index !== 0 ? frustumCommands.near * OPAQUE_FRUSTUM_NEAR_OFFSET : frustumCommands.near;
                frustum.far = frustumCommands.far;
                us.updateFrustum(frustum);
            }

            var globeDepth = scene.debugShowGlobeDepth ? getDebugGlobeDepth(scene, index) : scene._globeDepth;

            var fb;
            if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                fb = passState.framebuffer;
                passState.framebuffer = globeDepth.framebuffer;
            }

            clearDepth.execute(context, passState);

            us.updatePass(Pass.GLOBE);
            var commands = frustumCommands.commands[Pass.GLOBE];
            var length = frustumCommands.indices[Pass.GLOBE];
            for (j = 0; j < length; ++j) {
                executeCommand(commands[j], scene, context, passState);
            }

            if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && (scene.copyGlobeDepth || scene.debugShowGlobeDepth)) {
                globeDepth.update(context);
                globeDepth.executeCopyDepth(context, passState);
            }

            if (scene.debugShowGlobeDepth && defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) {
                passState.framebuffer = fb;
            }

            us.updatePass(Pass.GROUND);
            commands = frustumCommands.commands[Pass.GROUND];
            length = frustumCommands.indices[Pass.GROUND];
            for (j = 0; j < length; ++j) {
                executeCommand(commands[j], scene, context, passState);
            }

            // Clear the stencil after the ground pass
            if (length > 0 && context.stencilBuffer) {
                scene._stencilClearCommand.execute(context, passState);
            }

            if (clearGlobeDepth) {
                clearDepth.execute(context, passState);
                if (useDepthPlane) {
                    depthPlane.execute(context, passState);
                }
            }

            // Execute commands in order by pass up to the translucent pass.
            // Translucent geometry needs special handling (sorting/OIT).
            var startPass = Pass.GROUND + 1;
            var endPass = Pass.TRANSLUCENT;
            for (var pass = startPass; pass < endPass; ++pass) {
                us.updatePass(pass);
                commands = frustumCommands.commands[pass];
                length = frustumCommands.indices[pass];
                for (j = 0; j < length; ++j) {
                    executeCommand(commands[j], scene, context, passState);
                }
            }

            if (index !== 0 && scene.mode !== SceneMode.SCENE2D) {
                // Do not overlap frustums in the translucent pass to avoid blending artifacts
                frustum.near = frustumCommands.near;
                us.updateFrustum(frustum);
            }

            us.updatePass(Pass.TRANSLUCENT);
            commands = frustumCommands.commands[Pass.TRANSLUCENT];
            commands.length = frustumCommands.indices[Pass.TRANSLUCENT];
            executeTranslucentCommands(scene, executeCommand, passState, commands);

            if (defined(globeDepth) && (environmentState.useGlobeDepthFramebuffer || depthOnly) && scene.useDepthPicking) {
                // PERFORMANCE_IDEA: Use MRT to avoid the extra copy.
                var depthStencilTexture = depthOnly ? passState.framebuffer.depthStencilTexture : globeDepth.framebuffer.depthStencilTexture;
                var pickDepth = getPickDepth(scene, index);
                pickDepth.update(context, depthStencilTexture);
                pickDepth.executeCopyDepth(context, passState);
            }
        }
    }

    function executeComputeCommands(scene) {
        var us = scene.context.uniformState;
        us.updatePass(Pass.COMPUTE);

        var sunComputeCommand = scene._environmentState.sunComputeCommand;
        if (defined(sunComputeCommand)) {
            sunComputeCommand.execute(scene._computeEngine);
        }

        var commandList = scene._computeCommandList;
        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            commandList[i].execute(scene._computeEngine);
        }
    }

    function executeOverlayCommands(scene, passState) {
        var us = scene.context.uniformState;
        us.updatePass(Pass.OVERLAY);

        var context = scene.context;
        var commandList = scene._overlayCommandList;
        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            commandList[i].execute(context, passState);
        }
    }

    function insertShadowCastCommands(scene, commandList, shadowMap) {
        var shadowVolume = shadowMap.shadowMapCullingVolume;
        var isPointLight = shadowMap.isPointLight;
        var passes = shadowMap.passes;
        var numberOfPasses = passes.length;

        var length = commandList.length;
        for (var i = 0; i < length; ++i) {
            var command = commandList[i];
            updateDerivedCommands(scene, command);

            if (command.castShadows && (command.pass === Pass.GLOBE || command.pass === Pass.OPAQUE || command.pass === Pass.TRANSLUCENT)) {
                if (isVisible(command, shadowVolume)) {
                    if (isPointLight) {
                        for (var k = 0; k < numberOfPasses; ++k) {
                            passes[k].commandList.push(command);
                        }
                    } else if (numberOfPasses === 1) {
                        passes[0].commandList.push(command);
                    } else {
                        var wasVisible = false;
                        // Loop over cascades from largest to smallest
                        for (var j = numberOfPasses - 1; j >= 0; --j) {
                            var cascadeVolume = passes[j].cullingVolume;
                            if (isVisible(command, cascadeVolume)) {
                                passes[j].commandList.push(command);
                                wasVisible = true;
                            } else if (wasVisible) {
                                // If it was visible in the previous cascade but now isn't
                                // then there is no need to check any more cascades
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    function executeShadowMapCastCommands(scene) {
        var frameState = scene.frameState;
        var shadowMaps = frameState.shadowHints.shadowMaps;
        var shadowMapLength = shadowMaps.length;

        if (!frameState.shadowHints.shadowsEnabled) {
            return;
        }

        var context = scene.context;
        var uniformState = context.uniformState;

        for (var i = 0; i < shadowMapLength; ++i) {
            var shadowMap = shadowMaps[i];
            if (shadowMap.outOfView) {
                continue;
            }

            // Reset the command lists
            var j;
            var passes = shadowMap.passes;
            var numberOfPasses = passes.length;
            for (j = 0; j < numberOfPasses; ++j) {
                passes[j].commandList.length = 0;
            }

            // Insert the primitive/model commands into the command lists
            var sceneCommands = scene.frameState.commandList;
            insertShadowCastCommands(scene, sceneCommands, shadowMap);

            for (j = 0; j < numberOfPasses; ++j) {
                var pass = shadowMap.passes[j];
                uniformState.updateCamera(pass.camera);
                shadowMap.updatePass(context, j);
                var numberOfCommands = pass.commandList.length;
                for (var k = 0; k < numberOfCommands; ++k) {
                    var command = pass.commandList[k];
                    // Set the correct pass before rendering into the shadow map because some shaders
                    // conditionally render based on whether the pass is translucent or opaque.
                    uniformState.updatePass(command.pass);
                    executeCommand(command.derivedCommands.shadows.castCommands[i], scene, context, pass.passState);
                }
            }
        }
    }

    function updateAndExecuteCommands(scene, passState, backgroundColor) {
        var context = scene._context;

        var viewport = passState.viewport;

        var frameState = scene._frameState;
        var camera = frameState.camera;
        var mode = frameState.mode;
        var depthOnly = frameState.passes.depth;

        if (scene._useWebVR && mode !== SceneMode.SCENE2D) {
            if (!depthOnly) {
                updatePrimitives(scene);
            }

            createPotentiallyVisibleSet(scene);
            updateAndClearFramebuffers(scene, passState, backgroundColor);

            if (!depthOnly) {
                executeComputeCommands(scene);
                executeShadowMapCastCommands(scene);
            }

            // Based on Calculating Stereo pairs by Paul Bourke
            // http://paulbourke.net/stereographics/stereorender/

            viewport.x = 0;
            viewport.y = 0;
            viewport.width = context.drawingBufferWidth * 0.5;
            viewport.height = context.drawingBufferHeight;

            var savedCamera = Camera.clone(camera, scene._cameraVR);

            var near = camera.frustum.near;
            var fo = near * 5.0;
            var eyeSeparation = fo / 30.0;
            var eyeTranslation = Cartesian3.multiplyByScalar(savedCamera.right, eyeSeparation * 0.5, scratchEyeTranslation);

            camera.frustum.aspectRatio = viewport.width / viewport.height;

            var offset = 0.5 * eyeSeparation * near / fo;

            Cartesian3.add(savedCamera.position, eyeTranslation, camera.position);
            camera.frustum.xOffset = offset;

            executeCommands(scene, passState);

            viewport.x = passState.viewport.width;

            Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position);
            camera.frustum.xOffset = -offset;

            executeCommands(scene, passState);

            Camera.clone(savedCamera, camera);
        } else {
            viewport.x = 0;
            viewport.y = 0;
            viewport.width = context.drawingBufferWidth;
            viewport.height = context.drawingBufferHeight;

            if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) {
                executeCommandsInViewport(true, scene, passState, backgroundColor);
            } else {
                execute2DViewportCommands(scene, passState, backgroundColor);
            }
        }
    }

    var scratch2DViewportCartographic = new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO);
    var scratch2DViewportMaxCoord = new Cartesian3();
    var scratch2DViewportSavedPosition = new Cartesian3();
    var scratch2DViewportTransform = new Matrix4();
    var scratch2DViewportCameraTransform = new Matrix4();
    var scratch2DViewportEyePoint = new Cartesian3();
    var scratch2DViewportWindowCoords = new Cartesian3();

    function execute2DViewportCommands(scene, passState, backgroundColor) {
        var context = scene.context;
        var frameState = scene.frameState;
        var camera = scene.camera;
        var viewport = passState.viewport;

        var maxCartographic = scratch2DViewportCartographic;
        var maxCoord = scratch2DViewportMaxCoord;

        var projection = scene.mapProjection;
        projection.project(maxCartographic, maxCoord);

        var position = Cartesian3.clone(camera.position, scratch2DViewportSavedPosition);
        var transform = Matrix4.clone(camera.transform, scratch2DViewportCameraTransform);
        var frustum = camera.frustum.clone();

        camera._setTransform(Matrix4.IDENTITY);

        var viewportTransformation = Matrix4.computeViewportTransformation(viewport, 0.0, 1.0, scratch2DViewportTransform);
        var projectionMatrix = camera.frustum.projectionMatrix;

        var x = camera.positionWC.y;
        var eyePoint = Cartesian3.fromElements(CesiumMath.sign(x) * maxCoord.x - x, 0.0, -camera.positionWC.x, scratch2DViewportEyePoint);
        var windowCoordinates = Transforms.pointToGLWindowCoordinates(projectionMatrix, viewportTransformation, eyePoint, scratch2DViewportWindowCoords);

        windowCoordinates.x = Math.floor(windowCoordinates.x);

        var viewportX = viewport.x;
        var viewportWidth = viewport.width;

        if (x === 0.0 || windowCoordinates.x <= 0.0 || windowCoordinates.x >= context.drawingBufferWidth) {
            executeCommandsInViewport(true, scene, passState, backgroundColor);
        } else if (Math.abs(context.drawingBufferWidth * 0.5 - windowCoordinates.x) < 1.0) {
            viewport.width = windowCoordinates.x;

            camera.position.x *= CesiumMath.sign(camera.position.x);

            camera.frustum.right = 0.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState, backgroundColor);

            viewport.x = viewport.width;

            camera.position.x = -camera.position.x;

            camera.frustum.right = -camera.frustum.left;
            camera.frustum.left = 0.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState, backgroundColor);
        } else if (windowCoordinates.x > context.drawingBufferWidth * 0.5) {
            viewport.width = windowCoordinates.x;

            var right = camera.frustum.right;
            camera.frustum.right = maxCoord.x - x;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState, backgroundColor);

            viewport.x += windowCoordinates.x;
            viewport.width = context.drawingBufferWidth - windowCoordinates.x;

            camera.position.x = -camera.position.x;

            camera.frustum.left = -camera.frustum.right;
            camera.frustum.right = right - camera.frustum.right * 2.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState, backgroundColor);
        } else {
            viewport.x = windowCoordinates.x;
            viewport.width = context.drawingBufferWidth - windowCoordinates.x;

            var left = camera.frustum.left;
            camera.frustum.left = -maxCoord.x - x;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(true, scene, passState, backgroundColor);

            viewport.x = 0;
            viewport.width = windowCoordinates.x;

            camera.position.x = -camera.position.x;

            camera.frustum.right = -camera.frustum.left;
            camera.frustum.left = left - camera.frustum.left * 2.0;

            frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
            context.uniformState.update(frameState);

            executeCommandsInViewport(false, scene, passState, backgroundColor);
        }

        camera._setTransform(transform);
        Cartesian3.clone(position, camera.position);
        camera.frustum = frustum.clone();

        viewport.x = viewportX;
        viewport.width = viewportWidth;
    }

    function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor) {
        var depthOnly = scene.frameState.passes.depth;

        if (!firstViewport && !depthOnly) {
            scene.frameState.commandList.length = 0;
        }

        if (!depthOnly) {
            updatePrimitives(scene);
        }

        createPotentiallyVisibleSet(scene);

        if (firstViewport) {
            updateAndClearFramebuffers(scene, passState, backgroundColor);
            if (!depthOnly) {
                executeComputeCommands(scene);
                executeShadowMapCastCommands(scene);
            }
        }

        executeCommands(scene, passState);
    }

    function updateEnvironment(scene) {
        var frameState = scene._frameState;

        // Update celestial and terrestrial environment effects.
        var environmentState = scene._environmentState;
        var renderPass = frameState.passes.render;
        environmentState.skyBoxCommand = (renderPass && defined(scene.skyBox)) ? scene.skyBox.update(frameState) : undefined;
        var skyAtmosphere = scene.skyAtmosphere;
        var globe = scene.globe;
        if (defined(skyAtmosphere) && defined(globe)) {
            skyAtmosphere.setDynamicAtmosphereColor(globe.enableLighting);
            environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0;
        }
        environmentState.skyAtmosphereCommand = (renderPass && defined(skyAtmosphere)) ? skyAtmosphere.update(frameState) : undefined;
        var sunCommands = (renderPass && defined(scene.sun)) ? scene.sun.update(scene) : undefined;
        environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined;
        environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined;
        environmentState.moonCommand = (renderPass && defined(scene.moon)) ? scene.moon.update(frameState) : undefined;

        var clearGlobeDepth = environmentState.clearGlobeDepth = defined(globe) && (!globe.depthTestAgainstTerrain || scene.mode === SceneMode.SCENE2D);
        var useDepthPlane = environmentState.useDepthPlane = clearGlobeDepth && scene.mode === SceneMode.SCENE3D;
        if (useDepthPlane) {
            // Update the depth plane that is rendered in 3D when the primitives are
            // not depth tested against terrain so primitives on the backface
            // of the globe are not picked.
            scene._depthPlane.update(frameState);
        }
    }

    function updateDebugFrustumPlanes(scene) {
        var frameState = scene._frameState;
        if (scene.debugShowFrustumPlanes !== scene._debugShowFrustumPlanes) {
            if (scene.debugShowFrustumPlanes) {
                scene._debugFrustumPlanes = new DebugCameraPrimitive({
                    camera: scene.camera,
                    updateOnChange: false
                });
            } else {
                scene._debugFrustumPlanes = scene._debugFrustumPlanes && scene._debugFrustumPlanes.destroy();
            }
            scene._debugShowFrustumPlanes = scene.debugShowFrustumPlanes;
        }

        if (defined(scene._debugFrustumPlanes)) {
            scene._debugFrustumPlanes.update(frameState);
        }
    }

    function updateShadowMaps(scene) {
        var frameState = scene._frameState;
        var shadowMaps = frameState.shadowMaps;
        var length = shadowMaps.length;

        var shadowsEnabled = (length > 0) && !frameState.passes.pick && (scene.mode === SceneMode.SCENE3D);
        if (shadowsEnabled !== frameState.shadowHints.shadowsEnabled) {
            // Update derived commands when shadowsEnabled changes
            ++frameState.shadowHints.lastDirtyTime;
            frameState.shadowHints.shadowsEnabled = shadowsEnabled;
        }

        if (!shadowsEnabled) {
            return;
        }

        // Check if the shadow maps are different than the shadow maps last frame.
        // If so, the derived commands need to be updated.
        for (var j = 0; j < length; ++j) {
            if (shadowMaps[j] !== frameState.shadowHints.shadowMaps[j]) {
                ++frameState.shadowHints.lastDirtyTime;
                break;
            }
        }

        frameState.shadowHints.shadowMaps.length = 0;
        frameState.shadowHints.lightShadowMaps.length = 0;

        for (var i = 0; i < length; ++i) {
            var shadowMap = shadowMaps[i];
            shadowMap.update(frameState);

            frameState.shadowHints.shadowMaps.push(shadowMap);

            if (shadowMap.fromLightSource) {
                frameState.shadowHints.lightShadowMaps.push(shadowMap);
            }

            if (shadowMap.dirty) {
                ++frameState.shadowHints.lastDirtyTime;
                shadowMap.dirty = false;
            }
        }
    }

    function updatePrimitives(scene) {
        var frameState = scene._frameState;

        scene._groundPrimitives.update(frameState);
        scene._primitives.update(frameState);

        updateDebugFrustumPlanes(scene);
        updateShadowMaps(scene);

        if (scene._globe) {
            scene._globe.update(frameState);
        }
    }

    function updateAndClearFramebuffers(scene, passState, clearColor) {
        var context = scene._context;
        var environmentState = scene._environmentState;

        var passes = scene._frameState.passes;
        var picking = passes.pick;
        var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D;

        // Preserve the reference to the original framebuffer.
        environmentState.originalFramebuffer = passState.framebuffer;

        // Manage sun bloom post-processing effect.
        if (defined(scene.sun) && scene.sunBloom !== scene._sunBloom) {
            if (scene.sunBloom && !useWebVR) {
                scene._sunPostProcess = new SunPostProcess();
            } else if(defined(scene._sunPostProcess)){
                scene._sunPostProcess = scene._sunPostProcess.destroy();
            }

            scene._sunBloom = scene.sunBloom;
        } else if (!defined(scene.sun) && defined(scene._sunPostProcess)) {
            scene._sunPostProcess = scene._sunPostProcess.destroy();
            scene._sunBloom = false;
        }

        // Clear the pass state framebuffer.
        var clear = scene._clearColorCommand;
        Color.clone(clearColor, clear.color);
        clear.execute(context, passState);

        // Update globe depth rendering based on the current context and clear the globe depth framebuffer.
        var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = !picking && defined(scene._globeDepth);
        if (useGlobeDepthFramebuffer) {
            scene._globeDepth.update(context);
            scene._globeDepth.clear(context, passState, clearColor);
        }

        // Determine if there are any translucent surfaces in any of the frustums.
        var renderTranslucentCommands = false;
        var frustumCommandsList = scene._frustumCommandsList;
        var numFrustums = frustumCommandsList.length;
        for (var i = 0; i < numFrustums; ++i) {
            if (frustumCommandsList[i].indices[Pass.TRANSLUCENT] > 0) {
                renderTranslucentCommands = true;
                break;
            }
        }

        // If supported, configure OIT to use the globe depth framebuffer and clear the OIT framebuffer.
        var useOIT = environmentState.useOIT = !picking && renderTranslucentCommands && defined(scene._oit) && scene._oit.isSupported();
        if (useOIT) {
            scene._oit.update(context, scene._globeDepth.framebuffer);
            scene._oit.clear(context, passState, clearColor);
            environmentState.useOIT = scene._oit.isSupported();
        }

        // If supported, configure FXAA to use the globe depth color texture and clear the FXAA framebuffer.
        var useFXAA = environmentState.useFXAA = !picking && scene.fxaa;
        if (useFXAA) {
            scene._fxaa.update(context);
            scene._fxaa.clear(context, passState, clearColor);
        }

        if (environmentState.isSunVisible && scene.sunBloom && !useWebVR) {
            passState.framebuffer = scene._sunPostProcess.update(passState);
        } else if (useGlobeDepthFramebuffer) {
            passState.framebuffer = scene._globeDepth.framebuffer;
        } else if (useFXAA) {
            passState.framebuffer = scene._fxaa.getColorFramebuffer();
        }

        if (defined(passState.framebuffer)) {
            clear.execute(context, passState);
        }
    }

    function resolveFramebuffers(scene, passState) {
        var context = scene._context;
        var environmentState = scene._environmentState;

        var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer;
        if (scene.debugShowGlobeDepth && useGlobeDepthFramebuffer) {
            var gd = getDebugGlobeDepth(scene, scene.debugShowDepthFrustum - 1);
            gd.executeDebugGlobeDepth(context, passState);
        }

        if (scene.debugShowPickDepth && useGlobeDepthFramebuffer) {
            var pd = getPickDepth(scene, scene.debugShowDepthFrustum - 1);
            pd.executeDebugPickDepth(context, passState);
        }

        var useOIT = environmentState.useOIT;
        var useFXAA = environmentState.useFXAA;

        if (useOIT) {
            passState.framebuffer = useFXAA ? scene._fxaa.getColorFramebuffer() : undefined;
            scene._oit.execute(context, passState);
        }

        if (useFXAA) {
            if (!useOIT && useGlobeDepthFramebuffer) {
                passState.framebuffer = scene._fxaa.getColorFramebuffer();
                scene._globeDepth.executeCopyColor(context, passState);
            }

            passState.framebuffer = environmentState.originalFramebuffer;
            scene._fxaa.execute(context, passState);
        }

        if (!useOIT && !useFXAA && useGlobeDepthFramebuffer) {
            passState.framebuffer = environmentState.originalFramebuffer;
            scene._globeDepth.executeCopyColor(context, passState);
        }
    }

    function callAfterRenderFunctions(frameState) {
        // Functions are queued up during primitive update and executed here in case
        // the function modifies scene state that should remain constant over the frame.
        var functions = frameState.afterRender;
        for (var i = 0, length = functions.length; i < length; ++i) {
            functions[i]();
        }
        functions.length = 0;
    }

    /**
     * @private
     */
    Scene.prototype.initializeFrame = function() {
        // Destroy released shaders once every 120 frames to avoid thrashing the cache
        if (this._shaderFrameCount++ === 120) {
            this._shaderFrameCount = 0;
            this._context.shaderCache.destroyReleasedShaderPrograms();
        }

        this._tweens.update();

        this._screenSpaceCameraController.update();
        if (defined(this._deviceOrientationCameraController)) {
            this._deviceOrientationCameraController.update();
        }

        this._camera.update(this._mode);
        this._camera._updateCameraChanged();
    };

    var scratchEyeTranslation = new Cartesian3();

    function render(scene, time) {
        if (!defined(time)) {
            time = JulianDate.now();
        }

        var camera = scene._camera;
        if (!cameraEqual(camera, scene._cameraClone, CesiumMath.EPSILON6)) {
            if (!scene._cameraStartFired) {
                camera.moveStart.raiseEvent();
                scene._cameraStartFired = true;
            }
            scene._cameraMovedTime = getTimestamp();
            Camera.clone(camera, scene._cameraClone);
        } else if (scene._cameraStartFired && getTimestamp() - scene._cameraMovedTime > scene.cameraEventWaitTime) {
            camera.moveEnd.raiseEvent();
            scene._cameraStartFired = false;
        }

        scene._preRender.raiseEvent(scene, time);

        var context = scene.context;
        var us = context.uniformState;
        var frameState = scene._frameState;

        var frameNumber = CesiumMath.incrementWrap(frameState.frameNumber, 15000000.0, 1.0);
        updateFrameState(scene, frameNumber, time);
        frameState.passes.render = true;
        frameState.creditDisplay.beginFrame();

        scene.fog.update(frameState);

        us.update(frameState);

        var shadowMap = scene.shadowMap;
        if (defined(shadowMap) && shadowMap.enabled) {
            // Update the sun's direction
            Cartesian3.negate(us.sunDirectionWC, scene._sunCamera.direction);
            frameState.shadowMaps.push(shadowMap);
        }

        scene._computeCommandList.length = 0;
        scene._overlayCommandList.length = 0;

        var passState = scene._passState;
        passState.framebuffer = undefined;
        passState.blendingEnabled = undefined;
        passState.scissorTest = undefined;

        if (defined(scene.globe)) {
            scene.globe.beginFrame(frameState);
        }

        updateEnvironment(scene);
        updateAndExecuteCommands(scene, passState, defaultValue(scene.backgroundColor, Color.BLACK));
        resolveFramebuffers(scene, passState);
        executeOverlayCommands(scene, passState);

        if (defined(scene.globe)) {
            scene.globe.endFrame(frameState);
        }

        frameState.creditDisplay.endFrame();

        if (scene.debugShowFramesPerSecond) {
            if (!defined(scene._performanceDisplay)) {
                var performanceContainer = document.createElement('div');
                performanceContainer.className = 'cesium-performanceDisplay-defaultContainer';
                var container = scene._canvas.parentNode;
                container.appendChild(performanceContainer);
                var performanceDisplay = new PerformanceDisplay({container: performanceContainer});
                scene._performanceDisplay = performanceDisplay;
                scene._performanceContainer = performanceContainer;
            }

            scene._performanceDisplay.update();
        } else if (defined(scene._performanceDisplay)) {
            scene._performanceDisplay = scene._performanceDisplay && scene._performanceDisplay.destroy();
            scene._performanceContainer.parentNode.removeChild(scene._performanceContainer);
        }

        context.endFrame();
        callAfterRenderFunctions(frameState);

        scene._postRender.raiseEvent(scene, time);
    }

    /**
     * @private
     */
    Scene.prototype.render = function(time) {
        try {
            render(this, time);
        } catch (error) {
            this._renderError.raiseEvent(this, error);

            if (this.rethrowRenderErrors) {
                throw error;
            }
        }
    };

    /**
     * @private
     */
    Scene.prototype.clampLineWidth = function(width) {
        return Math.max(ContextLimits.minimumAliasedLineWidth, Math.min(width, ContextLimits.maximumAliasedLineWidth));
    };

    var orthoPickingFrustum = new OrthographicFrustum();
    var scratchOrigin = new Cartesian3();
    var scratchDirection = new Cartesian3();
    var scratchPixelSize = new Cartesian2();
    var scratchPickVolumeMatrix4 = new Matrix4();

    function getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height) {
        var camera = scene._camera;
        var frustum = camera.frustum;

        var viewport = scene._passState.viewport;
        var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
        x *= (frustum.right - frustum.left) * 0.5;
        var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;
        y *= (frustum.top - frustum.bottom) * 0.5;

        var transform = Matrix4.clone(camera.transform, scratchPickVolumeMatrix4);
        camera._setTransform(Matrix4.IDENTITY);

        var origin = Cartesian3.clone(camera.position, scratchOrigin);
        Cartesian3.multiplyByScalar(camera.right, x, scratchDirection);
        Cartesian3.add(scratchDirection, origin, origin);
        Cartesian3.multiplyByScalar(camera.up, y, scratchDirection);
        Cartesian3.add(scratchDirection, origin, origin);

        camera._setTransform(transform);

        Cartesian3.fromElements(origin.z, origin.x, origin.y, origin);

        var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize);

        var ortho = orthoPickingFrustum;
        ortho.right = pixelSize.x * 0.5;
        ortho.left = -ortho.right;
        ortho.top = pixelSize.y * 0.5;
        ortho.bottom = -ortho.top;
        ortho.near = frustum.near;
        ortho.far = frustum.far;

        return ortho.computeCullingVolume(origin, camera.directionWC, camera.upWC);
    }

    var perspPickingFrustum = new PerspectiveOffCenterFrustum();

    function getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height) {
        var camera = scene._camera;
        var frustum = camera.frustum;
        var near = frustum.near;

        var tanPhi = Math.tan(frustum.fovy * 0.5);
        var tanTheta = frustum.aspectRatio * tanPhi;

        var viewport = scene._passState.viewport;
        var x = 2.0 * (drawingBufferPosition.x - viewport.x) / viewport.width - 1.0;
        var y = 2.0 * (viewport.height - drawingBufferPosition.y - viewport.y) / viewport.height - 1.0;

        var xDir = x * near * tanTheta;
        var yDir = y * near * tanPhi;

        var pixelSize = frustum.getPixelDimensions(viewport.width, viewport.height, 1.0, scratchPixelSize);
        var pickWidth = pixelSize.x * width * 0.5;
        var pickHeight = pixelSize.y * height * 0.5;

        var offCenter = perspPickingFrustum;
        offCenter.top = yDir + pickHeight;
        offCenter.bottom = yDir - pickHeight;
        offCenter.right = xDir + pickWidth;
        offCenter.left = xDir - pickWidth;
        offCenter.near = near;
        offCenter.far = frustum.far;

        return offCenter.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);
    }

    function getPickCullingVolume(scene, drawingBufferPosition, width, height) {
        if (scene._mode === SceneMode.SCENE2D) {
            return getPickOrthographicCullingVolume(scene, drawingBufferPosition, width, height);
        }

        return getPickPerspectiveCullingVolume(scene, drawingBufferPosition, width, height);
    }

    // pick rectangle width and height, assumed odd
    var rectangleWidth = 3.0;
    var rectangleHeight = 3.0;
    var scratchRectangle = new BoundingRectangle(0.0, 0.0, rectangleWidth, rectangleHeight);
    var scratchColorZero = new Color(0.0, 0.0, 0.0, 0.0);
    var scratchPosition = new Cartesian2();

    /**
     * Returns an object with a `primitive` property that contains the first (top) primitive in the scene
     * at a particular window coordinate or undefined if nothing is at the location. Other properties may
     * potentially be set depending on the type of primitive.
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @returns {Object} Object containing the picked primitive.
     *
     * @exception {DeveloperError} windowPosition is undefined.
     */
    Scene.prototype.pick = function(windowPosition) {
                if(!defined(windowPosition)) {
            throw new DeveloperError('windowPosition is undefined.');
        }
        
        var context = this._context;
        var us = context.uniformState;
        var frameState = this._frameState;

        var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition);

        if (!defined(this._pickFramebuffer)) {
            this._pickFramebuffer = context.createPickFramebuffer();
        }

        // Update with previous frame's number and time, assuming that render is called before picking.
        updateFrameState(this, frameState.frameNumber, frameState.time);
        frameState.cullingVolume = getPickCullingVolume(this, drawingBufferPosition, rectangleWidth, rectangleHeight);
        frameState.passes.pick = true;

        us.update(frameState);

        scratchRectangle.x = drawingBufferPosition.x - ((rectangleWidth - 1.0) * 0.5);
        scratchRectangle.y = (this.drawingBufferHeight - drawingBufferPosition.y) - ((rectangleHeight - 1.0) * 0.5);

        var passState = this._pickFramebuffer.begin(scratchRectangle);

        updateAndExecuteCommands(this, passState, scratchColorZero);
        resolveFramebuffers(this, passState);

        var object = this._pickFramebuffer.end(scratchRectangle);
        context.endFrame();
        callAfterRenderFunctions(frameState);
        return object;
    };

    var fragDepthRegex = /\bgl_FragDepthEXT\b/;
    var discardRegex = /\bdiscard\b/;

    function getDepthOnlyShaderProgram(context, shaderProgram) {
        var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, 'depthOnly');
        if (!defined(shader)) {
            var attributeLocations = shaderProgram._attributeLocations;
            var fs = shaderProgram.fragmentShaderSource;

            var writesDepthOrDiscards = false;
            var sources = fs.sources;
            var length = sources.length;
            for (var i = 0; i < length; ++i) {
                if (fragDepthRegex.test(sources[i]) || discardRegex.test(sources[i])) {
                    writesDepthOrDiscards = true;
                    break;
                }
            }

            if (!writesDepthOrDiscards) {
                fs = new ShaderSource({
                    sources : ['void main() { gl_FragColor = vec4(1.0); }']
                });
            }

            shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, 'depthOnly', {
                vertexShaderSource : shaderProgram.vertexShaderSource,
                fragmentShaderSource : fs,
                attributeLocations : attributeLocations
            });
        }

        return shader;
    }

    function getDepthOnlyRenderState(scene, renderState) {
        var cache = scene._depthOnlyRenderStateCache;
        var depthOnlyState = cache[renderState.id];
        if (!defined(depthOnlyState)) {
            var rs = RenderState.getState(renderState);
            rs.depthMask = true;
            rs.colorMask = {
                red : false,
                green : false,
                blue : false,
                alpha : false
            };

            depthOnlyState = RenderState.fromCache(rs);
            cache[renderState.id] = depthOnlyState;
        }

        return depthOnlyState;
    }

    function createDepthOnlyDerivedCommand(scene, command, context, result) {
        // For a depth only pass, we bind a framebuffer with only a depth attachment (no color attachments),
        // do not write color, and write depth. If the fragment shader doesn't modify the fragment depth
        // or discard, the driver can replace the fragment shader with a pass-through shader. We're unsure if this
        // actually happens so we modify the shader to use a pass-through fragment shader.

        if (!defined(result)) {
            result = {};
        }

        var shader;
        var renderState;
        if (defined(result.depthOnlyCommand)) {
            shader = result.depthOnlyCommand.shaderProgram;
            renderState = result.depthOnlyCommand.renderState;
        }

        result.depthOnlyCommand = DrawCommand.shallowClone(command, result.depthOnlyCommand);

        if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) {
            result.depthOnlyCommand.shaderProgram = getDepthOnlyShaderProgram(context, command.shaderProgram);
            result.depthOnlyCommand.renderState = getDepthOnlyRenderState(scene, command.renderState);
            result.shaderProgramId = command.shaderProgram.id;
        } else {
            result.depthOnlyCommand.shaderProgram = shader;
            result.depthOnlyCommand.renderState = renderState;
        }

        return result;
    }

    function renderTranslucentDepthForPick(scene, drawingBufferPosition) {
        // PERFORMANCE_IDEA: render translucent only and merge with the previous frame
        var context = scene._context;
        var frameState = scene._frameState;

        clearPasses(frameState.passes);
        frameState.passes.pick = true;
        frameState.passes.depth = true;
        frameState.cullingVolume = getPickCullingVolume(scene, drawingBufferPosition, 1, 1);

        var passState = scene._pickDepthPassState;
        if (!defined(passState)) {
            passState = scene._pickDepthPassState = new PassState(context);
            passState.scissorTest = {
                enabled : true,
                rectangle : new BoundingRectangle()
            };
            passState.viewport = new BoundingRectangle();
        }

        var width = context.drawingBufferWidth;
        var height = context.drawingBufferHeight;

        var framebuffer = scene._pickDepthFramebuffer;
        var pickDepthFBWidth = scene._pickDepthFramebufferWidth;
        var pickDepthFBHeight = scene._pickDepthFramebufferHeight;
        if (!defined(framebuffer) || pickDepthFBWidth !== width || pickDepthFBHeight !== height) {
            scene._pickDepthFramebuffer = scene._pickDepthFramebuffer && scene._pickDepthFramebuffer.destroy();
            framebuffer = scene._pickDepthFramebuffer = new Framebuffer({
                context : context,
                depthStencilTexture : new Texture({
                    context : context,
                    width : width,
                    height : height,
                    pixelFormat : PixelFormat.DEPTH_STENCIL,
                    pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8
                })
            });

            scene._pickDepthFramebufferWidth = width;
            scene._pickDepthFramebufferHeight = height;
        }

        passState.framebuffer = framebuffer;
        passState.viewport.width = width;
        passState.viewport.height = height;
        passState.scissorTest.rectangle.x = drawingBufferPosition.x;
        passState.scissorTest.rectangle.y = height - drawingBufferPosition.y;
        passState.scissorTest.rectangle.width = 1;
        passState.scissorTest.rectangle.height = 1;

        updateAndExecuteCommands(scene, passState, scratchColorZero);
        resolveFramebuffers(scene, passState);

        context.endFrame();
    }

    var scratchPackedDepth = new Cartesian4();
    var packedDepthScale = new Cartesian4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);

    /**
     * Returns the cartesian position reconstructed from the depth buffer and window position.
     * The returned position is in world coordinates. Used internally by camera functions to
     * prevent conversion to projected 2D coordinates and then back.
     * <p>
     * Set {@link Scene#pickTranslucentDepth} to <code>true</code> to include the depth of
     * translucent primitives; otherwise, this essentially picks through translucent primitives.
     * </p>
     *
     * @private
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Cartesian3} [result] The object on which to restore the result.
     * @returns {Cartesian3} The cartesian position in world coordinates.
     *
     * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported.
     */
    Scene.prototype.pickPositionWorldCoordinates = function(windowPosition, result) {
        if (!this.useDepthPicking) {
            return undefined;
        }

                if(!defined(windowPosition)) {
            throw new DeveloperError('windowPosition is undefined.');
        }
        if (!defined(this._globeDepth)) {
            throw new DeveloperError('Picking from the depth buffer is not supported. Check pickPositionSupported.');
        }
        
        var context = this._context;
        var uniformState = context.uniformState;

        var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition);
        if (this.pickTranslucentDepth) {
            renderTranslucentDepthForPick(this, drawingBufferPosition);
        }
        drawingBufferPosition.y = this.drawingBufferHeight - drawingBufferPosition.y;

        var camera = this._camera;

        // Create a working frustum from the original camera frustum.
        var frustum;
        if (defined(camera.frustum.fov)) {
            frustum = camera.frustum.clone(scratchPerspectiveFrustum);
        } else if (defined(camera.frustum.infiniteProjectionMatrix)){
            frustum = camera.frustum.clone(scratchPerspectiveOffCenterFrustum);
        } else {
            frustum = camera.frustum.clone(scratchOrthographicFrustum);
        }

        var numFrustums = this.numberOfFrustums;
        for (var i = 0; i < numFrustums; ++i) {
            var pickDepth = getPickDepth(this, i);
            var pixels = context.readPixels({
                x : drawingBufferPosition.x,
                y : drawingBufferPosition.y,
                width : 1,
                height : 1,
                framebuffer : pickDepth.framebuffer
            });

            var packedDepth = Cartesian4.unpack(pixels, 0, scratchPackedDepth);
            Cartesian4.divideByScalar(packedDepth, 255.0, packedDepth);
            var depth = Cartesian4.dot(packedDepth, packedDepthScale);

            if (depth > 0.0 && depth < 1.0) {
                var renderedFrustum = this._frustumCommandsList[i];
                var height2D;
                if (this.mode === SceneMode.SCENE2D) {
                    height2D = camera.position.z;
                    camera.position.z = height2D - renderedFrustum.near + 1.0;
                    frustum.far = Math.max(1.0, renderedFrustum.far - renderedFrustum.near);
                    frustum.near = 1.0;
                    uniformState.update(this.frameState);
                    uniformState.updateFrustum(frustum);
                } else {
                    frustum.near = renderedFrustum.near * (i !== 0 ? OPAQUE_FRUSTUM_NEAR_OFFSET : 1.0);
                    frustum.far = renderedFrustum.far;
                    uniformState.updateFrustum(frustum);
                }

                result = SceneTransforms.drawingBufferToWgs84Coordinates(this, drawingBufferPosition, depth, result);

                if (this.mode === SceneMode.SCENE2D) {
                    camera.position.z = height2D;
                    uniformState.update(this.frameState);
                }

                return result;
            }
        }

        return undefined;
    };

    var scratchPickPositionCartographic = new Cartographic();

    /**
     * Returns the cartesian position reconstructed from the depth buffer and window position.
     * <p>
     * The position reconstructed from the depth buffer in 2D may be slightly different from those
     * reconstructed in 3D and Columbus view. This is caused by the difference in the distribution
     * of depth values of perspective and orthographic projection.
     * </p>
     * <p>
     * Set {@link Scene#pickTranslucentDepth} to <code>true</code> to include the depth of
     * translucent primitives; otherwise, this essentially picks through translucent primitives.
     * </p>
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Cartesian3} [result] The object on which to restore the result.
     * @returns {Cartesian3} The cartesian position.
     *
     * @exception {DeveloperError} Picking from the depth buffer is not supported. Check pickPositionSupported.
     */
    Scene.prototype.pickPosition = function(windowPosition, result) {
        result = this.pickPositionWorldCoordinates(windowPosition, result);
        if (defined(result) && this.mode !== SceneMode.SCENE3D) {
            Cartesian3.fromElements(result.y, result.z, result.x, result);

            var projection = this.mapProjection;
            var ellipsoid = projection.ellipsoid;

            var cart = projection.unproject(result, scratchPickPositionCartographic);
            ellipsoid.cartographicToCartesian(cart, result);
        }
        return result;
    };

    /**
     * Returns a list of objects, each containing a `primitive` property, for all primitives at
     * a particular window coordinate position. Other properties may also be set depending on the
     * type of primitive. The primitives in the list are ordered by their visual order in the
     * scene (front to back).
     *
     * @param {Cartesian2} windowPosition Window coordinates to perform picking on.
     * @param {Number} [limit] If supplied, stop drilling after collecting this many picks.
     * @returns {Object[]} Array of objects, each containing 1 picked primitives.
     *
     * @exception {DeveloperError} windowPosition is undefined.
     *
     * @example
     * var pickedObjects = scene.drillPick(new Cesium.Cartesian2(100.0, 200.0));
     */
    Scene.prototype.drillPick = function(windowPosition, limit) {
        // PERFORMANCE_IDEA: This function calls each primitive's update for each pass. Instead
        // we could update the primitive once, and then just execute their commands for each pass,
        // and cull commands for picked primitives.  e.g., base on the command's owner.

                if (!defined(windowPosition)) {
            throw new DeveloperError('windowPosition is undefined.');
        }
        
        var i;
        var attributes;
        var result = [];
        var pickedPrimitives = [];
        var pickedAttributes = [];
        if (!defined(limit)) {
            limit = Number.MAX_VALUE;
        }

        var pickedResult = this.pick(windowPosition);
        while (defined(pickedResult) && defined(pickedResult.primitive)) {
            result.push(pickedResult);
            if (0 >= --limit) {
                break;
            }

            var primitive = pickedResult.primitive;
            var hasShowAttribute = false;

            //If the picked object has a show attribute, use it.
            if (typeof primitive.getGeometryInstanceAttributes === 'function') {
                if (defined(pickedResult.id)) {
                    attributes = primitive.getGeometryInstanceAttributes(pickedResult.id);
                    if (defined(attributes) && defined(attributes.show)) {
                        hasShowAttribute = true;
                        attributes.show = ShowGeometryInstanceAttribute.toValue(false, attributes.show);
                        pickedAttributes.push(attributes);
                    }
                }
            }

            //Otherwise, hide the entire primitive
            if (!hasShowAttribute) {
                primitive.show = false;
                pickedPrimitives.push(primitive);
            }

            pickedResult = this.pick(windowPosition);
        }

        // unhide everything we hid while drill picking
        for (i = 0; i < pickedPrimitives.length; ++i) {
            pickedPrimitives[i].show = true;
        }

        for (i = 0; i < pickedAttributes.length; ++i) {
            attributes = pickedAttributes[i];
            attributes.show = ShowGeometryInstanceAttribute.toValue(true, attributes.show);
        }

        return result;
    };

    /**
     * Instantly completes an active transition.
     */
    Scene.prototype.completeMorph = function(){
        this._transitioner.completeMorph();
    };

    /**
     * Asynchronously transitions the scene to 2D.
     * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
     */
    Scene.prototype.morphTo2D = function(duration) {
        var ellipsoid;
        var globe = this.globe;
        if (defined(globe)) {
            ellipsoid = globe.ellipsoid;
        } else {
            ellipsoid = this.mapProjection.ellipsoid;
        }
        duration = defaultValue(duration, 2.0);
        this._transitioner.morphTo2D(duration, ellipsoid);
    };

    /**
     * Asynchronously transitions the scene to Columbus View.
     * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
     */
    Scene.prototype.morphToColumbusView = function(duration) {
        var ellipsoid;
        var globe = this.globe;
        if (defined(globe)) {
            ellipsoid = globe.ellipsoid;
        } else {
            ellipsoid = this.mapProjection.ellipsoid;
        }
        duration = defaultValue(duration, 2.0);
        this._transitioner.morphToColumbusView(duration, ellipsoid);
    };

    /**
     * Asynchronously transitions the scene to 3D.
     * @param {Number} [duration=2.0] The amount of time, in seconds, for transition animations to complete.
     */
    Scene.prototype.morphTo3D = function(duration) {
        var ellipsoid;
        var globe = this.globe;
        if (defined(globe)) {
            ellipsoid = globe.ellipsoid;
        } else {
            ellipsoid = this.mapProjection.ellipsoid;
        }
        duration = defaultValue(duration, 2.0);
        this._transitioner.morphTo3D(duration, ellipsoid);
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Scene#destroy
     */
    Scene.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * scene = scene && scene.destroy();
     *
     * @see Scene#isDestroyed
     */
    Scene.prototype.destroy = function() {
        this._tweens.removeAll();
        this._computeEngine = this._computeEngine && this._computeEngine.destroy();
        this._screenSpaceCameraController = this._screenSpaceCameraController && this._screenSpaceCameraController.destroy();
        this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy();
        this._pickFramebuffer = this._pickFramebuffer && this._pickFramebuffer.destroy();
        this._pickDepthFramebuffer = this._pickDepthFramebuffer && this._pickDepthFramebuffer.destroy();
        this._primitives = this._primitives && this._primitives.destroy();
        this._groundPrimitives = this._groundPrimitives && this._groundPrimitives.destroy();
        this._globe = this._globe && this._globe.destroy();
        this.skyBox = this.skyBox && this.skyBox.destroy();
        this.skyAtmosphere = this.skyAtmosphere && this.skyAtmosphere.destroy();
        this._debugSphere = this._debugSphere && this._debugSphere.destroy();
        this.sun = this.sun && this.sun.destroy();
        this._sunPostProcess = this._sunPostProcess && this._sunPostProcess.destroy();
        this._depthPlane = this._depthPlane && this._depthPlane.destroy();
        this._transitioner.destroy();
        this._debugFrustumPlanes = this._debugFrustumPlanes && this._debugFrustumPlanes.destroy();

        if (defined(this._globeDepth)) {
            this._globeDepth.destroy();
        }

        if (defined(this._oit)) {
            this._oit.destroy();
        }
        this._fxaa.destroy();

        this._context = this._context && this._context.destroy();
        this._frameState.creditDisplay.destroy();
        if (defined(this._performanceDisplay)){
            this._performanceDisplay = this._performanceDisplay && this._performanceDisplay.destroy();
            this._performanceContainer.parentNode.removeChild(this._performanceContainer);
        }

        return destroyObject(this);
    };

    return Scene;
});

/*global define*/
define('Scene/SingleTileImageryProvider',[
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/GeographicTilingScheme',
        '../Core/loadImage',
        '../Core/Rectangle',
        '../Core/RuntimeError',
        '../Core/TileProviderError',
        '../ThirdParty/when'
    ], function(
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        GeographicTilingScheme,
        loadImage,
        Rectangle,
        RuntimeError,
        TileProviderError,
        when) {
    'use strict';

    /**
     * Provides a single, top-level imagery tile.  The single image is assumed to use a
     * {@link GeographicTilingScheme}.
     *
     * @alias SingleTileImageryProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The url for the tile.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
     * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL, if needed.
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     */
    function SingleTileImageryProvider(options) {
        options = defaultValue(options, {});
        var url = options.url;

                if (!defined(url)) {
            throw new DeveloperError('url is required.');
        }
        
        this._url = url;

        var proxy = options.proxy;
        this._proxy = proxy;

        var rectangle = defaultValue(options.rectangle, Rectangle.MAX_VALUE);
        var tilingScheme = new GeographicTilingScheme({
            rectangle : rectangle,
            numberOfLevelZeroTilesX : 1,
            numberOfLevelZeroTilesY : 1,
            ellipsoid : options.ellipsoid
        });
        this._tilingScheme = tilingScheme;

        this._image = undefined;
        this._texture = undefined;
        this._tileWidth = 0;
        this._tileHeight = 0;

        this._errorEvent = new Event();

        this._ready = false;
        this._readyPromise = when.defer();

        var imageUrl = url;
        if (defined(proxy)) {
            imageUrl = proxy.getURL(imageUrl);
        }

        var credit = options.credit;
        if (typeof credit === 'string') {
            credit = new Credit(credit);
        }
        this._credit = credit;

        var that = this;
        var error;

        function success(image) {
            that._image = image;
            that._tileWidth = image.width;
            that._tileHeight = image.height;
            that._ready = true;
            that._readyPromise.resolve(true);
            TileProviderError.handleSuccess(that._errorEvent);
        }

        function failure(e) {
            var message = 'Failed to load image ' + imageUrl + '.';
            error = TileProviderError.handleError(
                    error,
                    that,
                    that._errorEvent,
                    message,
                    0, 0, 0,
                    doRequest,
                    e);
            that._readyPromise.reject(new RuntimeError(message));
        }

        function doRequest() {
            when(loadImage(imageUrl), success, failure);
        }

        doRequest();
    }

    defineProperties(SingleTileImageryProvider.prototype, {
        /**
         * Gets the URL of the single, top-level imagery tile.
         * @memberof SingleTileImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._proxy;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
                }
                
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight: {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
                }
                
                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
                }
                
                return 0;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
                }
                
                return 0;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
                }
                
                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                return this._tilingScheme.rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                                if (!this._ready) {
                    throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
                }
                
                return undefined;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise.promise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link SingleTileImageryProvider#ready} returns true.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof SingleTileImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return true;
            }
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    SingleTileImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link SingleTileImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    SingleTileImageryProvider.prototype.requestImage = function(x, y, level) {
                if (!this._ready) {
            throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
        }
        
        return this._image;
    };

    /**
     * Picking features is not currently supported by this imagery provider, so this function simply returns
     * undefined.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    SingleTileImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return undefined;
    };

    return SingleTileImageryProvider;
});

/**
 * @license
 * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the project nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Modifications made by Analytical Graphics, Inc.
 */
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyAtmosphereFS',[],function() {
    'use strict';
    return "/**\n\
 * @license\n\
 * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)\n\
 * All rights reserved.\n\
 * \n\
 * Redistribution and use in source and binary forms, with or without\n\
 * modification, are permitted provided that the following conditions\n\
 * are met:\n\
 * \n\
 * * Redistributions of source code must retain the above copyright notice,\n\
 *   this list of conditions and the following disclaimer.\n\
 * * Redistributions in binary form must reproduce the above copyright notice,\n\
 *   this list of conditions and the following disclaimer in the documentation\n\
 *   and/or other materials provided with the distribution.\n\
 * * Neither the name of the project nor the names of its contributors may be\n\
 *   used to endorse or promote products derived from this software without\n\
 *   specific prior written permission.\n\
 * \n\
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n\
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n\
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n\
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\n\
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n\
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n\
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n\
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n\
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\
 *\n\
 * Modifications made by Analytical Graphics, Inc.\n\
 */\n\
 \n\
 // Code:  http://sponeil.net/\n\
 // GPU Gems 2 Article:  http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html\n\
 // HSV/HSB <-> RGB conversion with minimal branching: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl\n\
\n\
#ifdef COLOR_CORRECT\n\
uniform vec3 u_hsbShift; // Hue, saturation, brightness\n\
#endif\n\
\n\
const float g = -0.95;\n\
const float g2 = g * g;\n\
const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n\
const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n\
\n\
varying vec3 v_rayleighColor;\n\
varying vec3 v_mieColor;\n\
varying vec3 v_toCamera;\n\
varying vec3 v_positionEC;\n\
\n\
#ifdef COLOR_CORRECT\n\
vec3 rgb2hsb(vec3 rgbColor)\n\
{\n\
    vec4 p = mix(vec4(rgbColor.bg, K_RGB2HSB.wz), vec4(rgbColor.gb, K_RGB2HSB.xy), step(rgbColor.b, rgbColor.g));\n\
    vec4 q = mix(vec4(p.xyw, rgbColor.r), vec4(rgbColor.r, p.yzx), step(p.x, rgbColor.r));\n\
\n\
    float d = q.x - min(q.w, q.y);\n\
    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x);\n\
}\n\
\n\
vec3 hsb2rgb(vec3 hsbColor)\n\
{\n\
    vec3 p = abs(fract(hsbColor.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www);\n\
    return hsbColor.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsbColor.y);\n\
}\n\
#endif\n\
\n\
void main (void)\n\
{\n\
    // Extra normalize added for Android\n\
    float cosAngle = dot(czm_sunDirectionWC, normalize(v_toCamera)) / length(v_toCamera);\n\
    float rayleighPhase = 0.75 * (1.0 + cosAngle * cosAngle);\n\
    float miePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + cosAngle * cosAngle) / pow(1.0 + g2 - 2.0 * g * cosAngle, 1.5);\n\
    \n\
    const float exposure = 2.0;\n\
    \n\
    vec3 rgb = rayleighPhase * v_rayleighColor + miePhase * v_mieColor;\n\
    rgb = vec3(1.0) - exp(-exposure * rgb);\n\
    // Compute luminance before color correction to avoid strangely gray night skies\n\
    float l = czm_luminance(rgb);\n\
\n\
#ifdef COLOR_CORRECT\n\
    // Convert rgb color to hsb\n\
    vec3 hsb = rgb2hsb(rgb);\n\
    // Perform hsb shift\n\
    hsb.x += u_hsbShift.x; // hue\n\
    hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation\n\
    hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness\n\
    // Convert shifted hsb back to rgb\n\
    rgb = hsb2rgb(hsb);\n\
\n\
    // Check if correction decreased the luminance to 0\n\
    l = min(l, czm_luminance(rgb));\n\
#endif\n\
\n\
    gl_FragColor = vec4(rgb, min(smoothstep(0.0, 0.1, l), 1.0) * smoothstep(0.0, 1.0, czm_morphTime));\n\
}\n\
";
});
/**
 * @license
 * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the project nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Modifications made by Analytical Graphics, Inc.
 */
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyAtmosphereVS',[],function() {
    'use strict';
    return "/**\n\
 * @license\n\
 * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com)\n\
 * All rights reserved.\n\
 * \n\
 * Redistribution and use in source and binary forms, with or without\n\
 * modification, are permitted provided that the following conditions\n\
 * are met:\n\
 * \n\
 * * Redistributions of source code must retain the above copyright notice,\n\
 *   this list of conditions and the following disclaimer.\n\
 * * Redistributions in binary form must reproduce the above copyright notice,\n\
 *   this list of conditions and the following disclaimer in the documentation\n\
 *   and/or other materials provided with the distribution.\n\
 * * Neither the name of the project nor the names of its contributors may be\n\
 *   used to endorse or promote products derived from this software without\n\
 *   specific prior written permission.\n\
 * \n\
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n\
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n\
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n\
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE\n\
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n\
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n\
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n\
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\n\
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n\
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\
 *\n\
 * Modifications made by Analytical Graphics, Inc.\n\
 */\n\
 \n\
 // Code:  http://sponeil.net/\n\
 // GPU Gems 2 Article:  http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html\n\
  \n\
attribute vec4 position;\n\
\n\
uniform vec4 u_cameraAndRadiiAndDynamicAtmosphereColor; // Camera height, outer radius, inner radius, dynamic atmosphere color flag\n\
\n\
const float Kr = 0.0025;\n\
const float Kr4PI = Kr * 4.0 * czm_pi;\n\
const float Km = 0.0015;\n\
const float Km4PI = Km * 4.0 * czm_pi;\n\
const float ESun = 15.0;\n\
const float KmESun = Km * ESun;\n\
const float KrESun = Kr * ESun;\n\
const vec3 InvWavelength = vec3(\n\
    5.60204474633241,  // Red = 1.0 / Math.pow(0.650, 4.0)\n\
    9.473284437923038, // Green = 1.0 / Math.pow(0.570, 4.0)\n\
    19.643802610477206); // Blue = 1.0 / Math.pow(0.475, 4.0)\n\
const float rayleighScaleDepth = 0.25;\n\
          \n\
const int nSamples = 2;\n\
const float fSamples = 2.0;\n\
\n\
varying vec3 v_rayleighColor;\n\
varying vec3 v_mieColor;\n\
varying vec3 v_toCamera;\n\
\n\
float scale(float cosAngle)\n\
{\n\
    float x = 1.0 - cosAngle;\n\
    return rayleighScaleDepth  * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));\n\
}\n\
\n\
void main(void)\n\
{\n\
    // Unpack attributes\n\
    float cameraHeight = u_cameraAndRadiiAndDynamicAtmosphereColor.x;\n\
    float outerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.y;\n\
    float innerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.z;\n\
\n\
    // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere)\n\
    vec3 positionV3 = position.xyz;\n\
    vec3 ray = positionV3 - czm_viewerPositionWC;\n\
    float far = length(ray);\n\
    ray /= far;\n\
    float atmosphereScale = 1.0 / (outerRadius - innerRadius);\n\
\n\
#ifdef SKY_FROM_SPACE\n\
    // Calculate the closest intersection of the ray with the outer atmosphere (which is the near point of the ray passing through the atmosphere)\n\
    float B = 2.0 * dot(czm_viewerPositionWC, ray);\n\
    float C = cameraHeight * cameraHeight - outerRadius * outerRadius;\n\
    float det = max(0.0, B*B - 4.0 * C);\n\
    float near = 0.5 * (-B - sqrt(det));\n\
\n\
    // Calculate the ray's starting position, then calculate its scattering offset\n\
    vec3 start = czm_viewerPositionWC + ray * near;\n\
    far -= near;\n\
    float startAngle = dot(ray, start) / outerRadius;\n\
    float startDepth = exp(-1.0 / rayleighScaleDepth );\n\
    float startOffset = startDepth*scale(startAngle);\n\
#else // SKY_FROM_ATMOSPHERE\n\
    // Calculate the ray's starting position, then calculate its scattering offset\n\
    vec3 start = czm_viewerPositionWC;\n\
    float height = length(start);\n\
    float depth = exp((atmosphereScale / rayleighScaleDepth ) * (innerRadius - cameraHeight));\n\
    float startAngle = dot(ray, start) / height;\n\
    float startOffset = depth*scale(startAngle);\n\
#endif\n\
\n\
    // Initialize the scattering loop variables\n\
    float sampleLength = far / fSamples;\n\
    float scaledLength = sampleLength * atmosphereScale;\n\
    vec3 sampleRay = ray * sampleLength;\n\
    vec3 samplePoint = start + sampleRay * 0.5;\n\
\n\
    // Now loop through the sample rays\n\
    vec3 frontColor = vec3(0.0, 0.0, 0.0);\n\
    vec3 lightDir = (u_cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? czm_sunPositionWC - czm_viewerPositionWC : czm_viewerPositionWC;\n\
    lightDir = normalize(lightDir);\n\
\n\
    for(int i=0; i<nSamples; i++)\n\
    {\n\
        float height = length(samplePoint);\n\
        float depth = exp((atmosphereScale / rayleighScaleDepth ) * (innerRadius - height));\n\
        float fLightAngle = dot(lightDir, samplePoint) / height;\n\
        float fCameraAngle = dot(ray, samplePoint) / height;\n\
        float fScatter = (startOffset + depth*(scale(fLightAngle) - scale(fCameraAngle)));\n\
        vec3 attenuate = exp(-fScatter * (InvWavelength * Kr4PI + Km4PI));\n\
        frontColor += attenuate * (depth * scaledLength);\n\
        samplePoint += sampleRay;\n\
    }\n\
\n\
    // Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader\n\
    v_mieColor = frontColor * KmESun;\n\
    v_rayleighColor = frontColor * (InvWavelength * KrESun);\n\
    v_toCamera = czm_viewerPositionWC - positionV3;\n\
    gl_Position = czm_modelViewProjection * position;\n\
}\n\
";
});
/*global define*/
define('Scene/SkyAtmosphere',[
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/Ellipsoid',
        '../Core/EllipsoidGeometry',
        '../Core/GeometryPipeline',
        '../Core/Math',
        '../Core/VertexFormat',
        '../Renderer/BufferUsage',
        '../Renderer/DrawCommand',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/ShaderSource',
        '../Renderer/VertexArray',
        '../Shaders/SkyAtmosphereFS',
        '../Shaders/SkyAtmosphereVS',
        './BlendingState',
        './CullFace',
        './SceneMode'
    ], function(
        Cartesian3,
        Cartesian4,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        Ellipsoid,
        EllipsoidGeometry,
        GeometryPipeline,
        CesiumMath,
        VertexFormat,
        BufferUsage,
        DrawCommand,
        RenderState,
        ShaderProgram,
        ShaderSource,
        VertexArray,
        SkyAtmosphereFS,
        SkyAtmosphereVS,
        BlendingState,
        CullFace,
        SceneMode) {
    'use strict';

    /**
     * An atmosphere drawn around the limb of the provided ellipsoid.  Based on
     * {@link http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html|Accurate Atmospheric Scattering}
     * in GPU Gems 2.
     * <p>
     * This is only supported in 3D.  atmosphere is faded out when morphing to 2D or Columbus view.
     * </p>
     *
     * @alias SkyAtmosphere
     * @constructor
     *
     * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid that the atmosphere is drawn around.
     *
     * @example
     * scene.skyAtmosphere = new Cesium.SkyAtmosphere();
     *
     * @see Scene.skyAtmosphere
     */
    function SkyAtmosphere(ellipsoid) {
        ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);

        /**
         * Determines if the atmosphere is shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = true;

        this._ellipsoid = ellipsoid;
        this._command = new DrawCommand({
            owner : this
        });
        this._spSkyFromSpace = undefined;
        this._spSkyFromAtmosphere = undefined;

        this._spSkyFromSpaceColorCorrect = undefined;
        this._spSkyFromAtmosphereColorCorrect = undefined;

        /**
         * The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift).
         * A hue shift of 1.0 indicates a complete rotation of the hues available.
         * @type {Number}
         * @default 0.0
         */
        this.hueShift = 0.0;

        /**
         * The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift).
         * A saturation shift of -1.0 is monochrome.
         * @type {Number}
         * @default 0.0
         */
        this.saturationShift = 0.0;

        /**
         * The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift).
         * A brightness shift of -1.0 is complete darkness, which will let space show through.
         * @type {Number}
         * @default 0.0
         */
        this.brightnessShift = 0.0;

        this._hueSaturationBrightness = new Cartesian3();

        // camera height, outer radius, inner radius, dynamic atmosphere color flag
        var cameraAndRadiiAndDynamicAtmosphereColor = new Cartesian4();

        // Toggles whether the sun position is used. 0 treats the sun as always directly overhead.
        cameraAndRadiiAndDynamicAtmosphereColor.w = 0;
        cameraAndRadiiAndDynamicAtmosphereColor.y = Cartesian3.maximumComponent(Cartesian3.multiplyByScalar(ellipsoid.radii, 1.025, new Cartesian3()));
        cameraAndRadiiAndDynamicAtmosphereColor.z = ellipsoid.maximumRadius;

        this._cameraAndRadiiAndDynamicAtmosphereColor = cameraAndRadiiAndDynamicAtmosphereColor;

        var that = this;

        this._command.uniformMap = {
            u_cameraAndRadiiAndDynamicAtmosphereColor : function() {
                return that._cameraAndRadiiAndDynamicAtmosphereColor;
            },
            u_hsbShift : function() {
                that._hueSaturationBrightness.x = that.hueShift;
                that._hueSaturationBrightness.y = that.saturationShift;
                that._hueSaturationBrightness.z = that.brightnessShift;
                return that._hueSaturationBrightness;
            }
        };
    }

    defineProperties(SkyAtmosphere.prototype, {
        /**
         * Gets the ellipsoid the atmosphere is drawn around.
         * @memberof SkyAtmosphere.prototype
         *
         * @type {Ellipsoid}
         * @readonly
         */
        ellipsoid : {
            get : function() {
                return this._ellipsoid;
            }
        }
    });

    /**
     * @private
     */
    SkyAtmosphere.prototype.setDynamicAtmosphereColor = function(enableLighting) {
        this._cameraAndRadiiAndDynamicAtmosphereColor.w = enableLighting ? 1 : 0;
    };

    /**
     * @private
     */
    SkyAtmosphere.prototype.update = function(frameState) {
        if (!this.show) {
            return undefined;
        }

        if ((frameState.mode !== SceneMode.SCENE3D) &&
            (frameState.mode !== SceneMode.MORPHING)) {
            return undefined;
        }

        // The atmosphere is only rendered during the render pass; it is not pickable, it doesn't cast shadows, etc.
        if (!frameState.passes.render) {
            return undefined;
        }

        var command = this._command;

        if (!defined(command.vertexArray)) {
            var context = frameState.context;

            var geometry = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
                radii : Cartesian3.multiplyByScalar(this._ellipsoid.radii, 1.025, new Cartesian3()),
                slicePartitions : 256,
                stackPartitions : 256,
                vertexFormat : VertexFormat.POSITION_ONLY
            }));
            command.vertexArray = VertexArray.fromGeometry({
                context : context,
                geometry : geometry,
                attributeLocations : GeometryPipeline.createAttributeLocations(geometry),
                bufferUsage : BufferUsage.STATIC_DRAW
            });
            command.renderState = RenderState.fromCache({
                cull : {
                    enabled : true,
                    face : CullFace.FRONT
                },
                blending : BlendingState.ALPHA_BLEND
            });

            var vs = new ShaderSource({
                defines : ['SKY_FROM_SPACE'],
                sources : [SkyAtmosphereVS]
            });

            this._spSkyFromSpace = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vs,
                fragmentShaderSource : SkyAtmosphereFS
            });

            vs = new ShaderSource({
                defines : ['SKY_FROM_ATMOSPHERE'],
                sources : [SkyAtmosphereVS]
            });
            this._spSkyFromAtmosphere = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : vs,
                fragmentShaderSource : SkyAtmosphereFS
            });
        }

        // Compile the color correcting versions of the shader on demand
        var useColorCorrect = colorCorrect(this);
        if (useColorCorrect && (!defined(this._spSkyFromSpaceColorCorrect) || !defined(this._spSkyFromAtmosphereColorCorrect))) {
            var contextColorCorrect = frameState.context;

            var vsColorCorrect = new ShaderSource({
                defines : ['SKY_FROM_SPACE'],
                sources : [SkyAtmosphereVS]
            });
            var fsColorCorrect = new ShaderSource({
                defines : ['COLOR_CORRECT'],
                sources : [SkyAtmosphereFS]
            });

            this._spSkyFromSpaceColorCorrect = ShaderProgram.fromCache({
                context : contextColorCorrect,
                vertexShaderSource : vsColorCorrect,
                fragmentShaderSource : fsColorCorrect
            });
            vsColorCorrect = new ShaderSource({
                defines : ['SKY_FROM_ATMOSPHERE'],
                sources : [SkyAtmosphereVS]
            });
            this._spSkyFromAtmosphereColorCorrect = ShaderProgram.fromCache({
                context : contextColorCorrect,
                vertexShaderSource : vsColorCorrect,
                fragmentShaderSource : fsColorCorrect
            });
        }

        var cameraPosition = frameState.camera.positionWC;

        var cameraHeight = Cartesian3.magnitude(cameraPosition);
        this._cameraAndRadiiAndDynamicAtmosphereColor.x = cameraHeight;

        if (cameraHeight > this._cameraAndRadiiAndDynamicAtmosphereColor.y) {
            // Camera in space
            command.shaderProgram = useColorCorrect ? this._spSkyFromSpaceColorCorrect : this._spSkyFromSpace;
        } else {
            // Camera in atmosphere
            command.shaderProgram = useColorCorrect ? this._spSkyFromAtmosphereColorCorrect : this._spSkyFromAtmosphere;
        }

        return command;
    };

    function colorCorrect(skyAtmosphere) {
        return !(CesiumMath.equalsEpsilon(skyAtmosphere.hueShift, 0.0, CesiumMath.EPSILON7) &&
                 CesiumMath.equalsEpsilon(skyAtmosphere.saturationShift, 0.0, CesiumMath.EPSILON7) &&
                 CesiumMath.equalsEpsilon(skyAtmosphere.brightnessShift, 0.0, CesiumMath.EPSILON7));
    }

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see SkyAtmosphere#destroy
     */
    SkyAtmosphere.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * skyAtmosphere = skyAtmosphere && skyAtmosphere.destroy();
     * 
     * @see SkyAtmosphere#isDestroyed
     */
    SkyAtmosphere.prototype.destroy = function() {
        var command = this._command;
        command.vertexArray = command.vertexArray && command.vertexArray.destroy();
        this._spSkyFromSpace = this._spSkyFromSpace && this._spSkyFromSpace.destroy();
        this._spSkyFromAtmosphere = this._spSkyFromAtmosphere && this._spSkyFromAtmosphere.destroy();
        this._spSkyFromSpaceColorCorrect = this._spSkyFromSpaceColorCorrect && this._spSkyFromSpaceColorCorrect.destroy();
        this._spSkyFromAtmosphereColorCorrect = this._spSkyFromAtmosphereColorCorrect && this._spSkyFromAtmosphereColorCorrect.destroy();
        return destroyObject(this);
    };

    return SkyAtmosphere;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyBoxFS',[],function() {
    'use strict';
    return "uniform samplerCube u_cubeMap;\n\
\n\
varying vec3 v_texCoord;\n\
\n\
void main()\n\
{\n\
    vec3 rgb = textureCube(u_cubeMap, normalize(v_texCoord)).rgb;\n\
    gl_FragColor = vec4(rgb, czm_morphTime);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SkyBoxVS',[],function() {
    'use strict';
    return "attribute vec3 position;\n\
\n\
varying vec3 v_texCoord;\n\
\n\
void main()\n\
{\n\
    vec3 p = czm_viewRotation * (czm_temeToPseudoFixed * (czm_entireFrustum.y * position));\n\
    gl_Position = czm_projection * vec4(p, 1.0);\n\
    v_texCoord = position.xyz;\n\
}\n\
";
});
/*global define*/
define('Scene/SkyBox',[
        '../Core/BoxGeometry',
        '../Core/Cartesian3',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Core/GeometryPipeline',
        '../Core/Matrix4',
        '../Core/VertexFormat',
        '../Renderer/BufferUsage',
        '../Renderer/CubeMap',
        '../Renderer/DrawCommand',
        '../Renderer/loadCubeMap',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/VertexArray',
        '../Shaders/SkyBoxFS',
        '../Shaders/SkyBoxVS',
        './BlendingState',
        './SceneMode'
    ], function(
        BoxGeometry,
        Cartesian3,
        defaultValue,
        defined,
        destroyObject,
        DeveloperError,
        GeometryPipeline,
        Matrix4,
        VertexFormat,
        BufferUsage,
        CubeMap,
        DrawCommand,
        loadCubeMap,
        RenderState,
        ShaderProgram,
        VertexArray,
        SkyBoxFS,
        SkyBoxVS,
        BlendingState,
        SceneMode) {
    'use strict';

    /**
     * A sky box around the scene to draw stars.  The sky box is defined using the True Equator Mean Equinox (TEME) axes.
     * <p>
     * This is only supported in 3D.  The sky box is faded out when morphing to 2D or Columbus view.  The size of
     * the sky box must not exceed {@link Scene#maximumCubeMapSize}.
     * </p>
     *
     * @alias SkyBox
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Object} [options.sources] The source URL or <code>Image</code> object for each of the six cube map faces.  See the example below.
     * @param {Boolean} [options.show=true] Determines if this primitive will be shown.
     *
     *
     * @example
     * scene.skyBox = new Cesium.SkyBox({
     *   sources : {
     *     positiveX : 'skybox_px.png',
     *     negativeX : 'skybox_nx.png',
     *     positiveY : 'skybox_py.png',
     *     negativeY : 'skybox_ny.png',
     *     positiveZ : 'skybox_pz.png',
     *     negativeZ : 'skybox_nz.png'
     *   }
     * });
     * 
     * @see Scene#skyBox
     * @see Transforms.computeTemeToPseudoFixedMatrix
     */
    function SkyBox(options) {
        /**
         * The sources used to create the cube map faces: an object
         * with <code>positiveX</code>, <code>negativeX</code>, <code>positiveY</code>,
         * <code>negativeY</code>, <code>positiveZ</code>, and <code>negativeZ</code> properties.
         * These can be either URLs or <code>Image</code> objects.
         *
         * @type Object
         * @default undefined
         */
        this.sources = options.sources;
        this._sources = undefined;

        /**
         * Determines if the sky box will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = defaultValue(options.show, true);

        this._command = new DrawCommand({
            modelMatrix : Matrix4.clone(Matrix4.IDENTITY),
            owner : this
        });
        this._cubeMap = undefined;
    }

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} this.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.
     * @exception {DeveloperError} this.sources properties must all be the same type.
     */
    SkyBox.prototype.update = function(frameState) {
        if (!this.show) {
            return undefined;
        }

        if ((frameState.mode !== SceneMode.SCENE3D) &&
            (frameState.mode !== SceneMode.MORPHING)) {
            return undefined;
        }

        // The sky box is only rendered during the render pass; it is not pickable, it doesn't cast shadows, etc.
        if (!frameState.passes.render) {
            return undefined;
        }

        var context = frameState.context;

        if (this._sources !== this.sources) {
            this._sources = this.sources;
            var sources = this.sources;

                        if ((!defined(sources.positiveX)) ||
                (!defined(sources.negativeX)) ||
                (!defined(sources.positiveY)) ||
                (!defined(sources.negativeY)) ||
                (!defined(sources.positiveZ)) ||
                (!defined(sources.negativeZ))) {
                throw new DeveloperError('this.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties.');
            }

            if ((typeof sources.positiveX !== typeof sources.negativeX) ||
                (typeof sources.positiveX !== typeof sources.positiveY) ||
                (typeof sources.positiveX !== typeof sources.negativeY) ||
                (typeof sources.positiveX !== typeof sources.positiveZ) ||
                (typeof sources.positiveX !== typeof sources.negativeZ)) {
                throw new DeveloperError('this.sources properties must all be the same type.');
            }
            
            if (typeof sources.positiveX === 'string') {
                // Given urls for cube-map images.  Load them.
                loadCubeMap(context, this._sources).then(function(cubeMap) {
                    that._cubeMap = that._cubeMap && that._cubeMap.destroy();
                    that._cubeMap = cubeMap;
                });
            } else {
                this._cubeMap = this._cubeMap && this._cubeMap.destroy();
                this._cubeMap = new CubeMap({
                    context : context,
                    source : sources
                });
            }
        }

        var command = this._command;

        if (!defined(command.vertexArray)) {
            var that = this;

            command.uniformMap = {
                u_cubeMap: function() {
                    return that._cubeMap;
                }
            };

            var geometry = BoxGeometry.createGeometry(BoxGeometry.fromDimensions({
                dimensions : new Cartesian3(2.0, 2.0, 2.0),
                vertexFormat : VertexFormat.POSITION_ONLY
            }));
            var attributeLocations = GeometryPipeline.createAttributeLocations(geometry);

            command.vertexArray = VertexArray.fromGeometry({
                context : context,
                geometry : geometry,
                attributeLocations : attributeLocations,
                bufferUsage : BufferUsage.STATIC_DRAW
            });

            command.shaderProgram = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : SkyBoxVS,
                fragmentShaderSource : SkyBoxFS,
                attributeLocations : attributeLocations
            });

            command.renderState = RenderState.fromCache({
                blending : BlendingState.ALPHA_BLEND
            });
        }

        if (!defined(this._cubeMap)) {
            return undefined;
        }

        return command;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see SkyBox#destroy
     */
    SkyBox.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * skyBox = skyBox && skyBox.destroy();
     * 
     * @see SkyBox#isDestroyed
     */
    SkyBox.prototype.destroy = function() {
        var command = this._command;
        command.vertexArray = command.vertexArray && command.vertexArray.destroy();
        command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy();
        this._cubeMap = this._cubeMap && this._cubeMap.destroy();
        return destroyObject(this);
    };

    return SkyBox;
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SunFS',[],function() {
    'use strict';
    return "uniform sampler2D u_texture;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    gl_FragColor = texture2D(u_texture, v_textureCoordinates);\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SunTextureFS',[],function() {
    'use strict';
    return "uniform float u_glowLengthTS;\n\
uniform float u_radiusTS;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
vec2 rotate(vec2 p, vec2 direction)\n\
{\n\
    return vec2(p.x * direction.x - p.y * direction.y, p.x * direction.y + p.y * direction.x);\n\
}\n\
\n\
vec4 addBurst(vec2 position, vec2 direction)\n\
{\n\
    vec2 rotatedPosition = rotate(position, direction) * vec2(25.0, 0.75);\n\
    float radius = length(rotatedPosition);\n\
    float burst = 1.0 - smoothstep(0.0, 0.55, radius);\n\
\n\
    return vec4(burst);\n\
}\n\
\n\
void main()\n\
{\n\
    vec2 position = v_textureCoordinates - vec2(0.5);\n\
    float radius = length(position);\n\
    float surface = step(radius, u_radiusTS);\n\
    vec4 color = vec4(1.0, 1.0, surface + 0.2, surface);\n\
\n\
    float glow = 1.0 - smoothstep(0.0, 0.55, radius);\n\
    color.ba += mix(vec2(0.0), vec2(1.0), glow) * 0.75;\n\
\n\
    vec4 burst = vec4(0.0);\n\
\n\
    // The following loop has been manually unrolled for speed, to\n\
    // avoid sin() and cos().\n\
    //\n\
    //for (float i = 0.4; i < 3.2; i += 1.047) {\n\
    //    vec2 direction = vec2(sin(i), cos(i));\n\
    //    burst += 0.4 * addBurst(position, direction);\n\
    //\n\
    //    direction = vec2(sin(i - 0.08), cos(i - 0.08));\n\
    //    burst += 0.3 * addBurst(position, direction);\n\
    //}\n\
\n\
    burst += 0.4 * addBurst(position, vec2(0.38942,  0.92106));  // angle == 0.4\n\
    burst += 0.4 * addBurst(position, vec2(0.99235,  0.12348));  // angle == 0.4 + 1.047\n\
    burst += 0.4 * addBurst(position, vec2(0.60327, -0.79754));  // angle == 0.4 + 1.047 * 2.0\n\
\n\
    burst += 0.3 * addBurst(position, vec2(0.31457,  0.94924));  // angle == 0.4 - 0.08\n\
    burst += 0.3 * addBurst(position, vec2(0.97931,  0.20239));  // angle == 0.4 + 1.047 - 0.08\n\
    burst += 0.3 * addBurst(position, vec2(0.66507, -0.74678));  // angle == 0.4 + 1.047 * 2.0 - 0.08\n\
\n\
    // End of manual loop unrolling.\n\
\n\
    color += clamp(burst, vec4(0.0), vec4(1.0)) * 0.15;\n\
    \n\
    gl_FragColor = clamp(color, vec4(0.0), vec4(1.0));\n\
}\n\
";
});
//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/SunVS',[],function() {
    'use strict';
    return "attribute vec2 direction;\n\
\n\
uniform float u_size;\n\
\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main() \n\
{\n\
    vec4 position;\n\
    if (czm_morphTime == 1.0)\n\
    {\n\
        position = vec4(czm_sunPositionWC, 1.0);\n\
    }\n\
    else\n\
    {\n\
        position = vec4(czm_sunPositionColumbusView.zxy, 1.0);\n\
    }\n\
    \n\
    vec4 positionEC = czm_view * position;\n\
    vec4 positionWC = czm_eyeToWindowCoordinates(positionEC);\n\
    \n\
    vec2 halfSize = vec2(u_size * 0.5);\n\
    halfSize *= ((direction * 2.0) - 1.0);\n\
    \n\
    gl_Position = czm_viewportOrthographic * vec4(positionWC.xy + halfSize, -positionWC.z, 1.0);\n\
    \n\
    v_textureCoordinates = direction;\n\
}\n\
";
});
/*global define*/
define('Scene/Sun',[
        '../Core/BoundingSphere',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Cartesian4',
        '../Core/ComponentDatatype',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/IndexDatatype',
        '../Core/Math',
        '../Core/Matrix4',
        '../Core/PixelFormat',
        '../Core/PrimitiveType',
        '../Renderer/Buffer',
        '../Renderer/BufferUsage',
        '../Renderer/ComputeCommand',
        '../Renderer/DrawCommand',
        '../Renderer/RenderState',
        '../Renderer/ShaderProgram',
        '../Renderer/Texture',
        '../Renderer/VertexArray',
        '../Shaders/SunFS',
        '../Shaders/SunTextureFS',
        '../Shaders/SunVS',
        './BlendingState',
        './SceneMode',
        './SceneTransforms'
    ], function(
        BoundingSphere,
        Cartesian2,
        Cartesian3,
        Cartesian4,
        ComponentDatatype,
        defined,
        defineProperties,
        destroyObject,
        IndexDatatype,
        CesiumMath,
        Matrix4,
        PixelFormat,
        PrimitiveType,
        Buffer,
        BufferUsage,
        ComputeCommand,
        DrawCommand,
        RenderState,
        ShaderProgram,
        Texture,
        VertexArray,
        SunFS,
        SunTextureFS,
        SunVS,
        BlendingState,
        SceneMode,
        SceneTransforms) {
    'use strict';

    /**
     * Draws a sun billboard.
     * <p>This is only supported in 3D and Columbus view.</p>
     *
     * @alias Sun
     * @constructor
     *
     *
     * @example
     * scene.sun = new Cesium.Sun();
     * 
     * @see Scene#sun
     */
    function Sun() {
        /**
         * Determines if the sun will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = true;

        this._drawCommand = new DrawCommand({
            primitiveType : PrimitiveType.TRIANGLES,
            boundingVolume : new BoundingSphere(),
            owner : this
        });
        this._commands = {
            drawCommand : this._drawCommand,
            computeCommand : undefined
        };
        this._boundingVolume = new BoundingSphere();
        this._boundingVolume2D = new BoundingSphere();

        this._texture = undefined;
        this._drawingBufferWidth = undefined;
        this._drawingBufferHeight = undefined;
        this._radiusTS = undefined;
        this._size = undefined;

        this.glowFactor = 1.0;
        this._glowFactorDirty = false;

        var that = this;
        this._uniformMap = {
            u_texture : function() {
                return that._texture;
            },
            u_size : function() {
                return that._size;
            }
        };
    }

    defineProperties(Sun.prototype, {
        /**
         * Gets or sets a number that controls how "bright" the Sun's lens flare appears
         * to be.  Zero shows just the Sun's disc without any flare.
         * Use larger values for a more pronounced flare around the Sun.
         *
         * @memberof Sun.prototype
         * @type {Number}
         * @default 1.0
         */
        glowFactor : {
            get : function () { return this._glowFactor; },
            set : function (glowFactor) {
                glowFactor = Math.max(glowFactor, 0.0);
                this._glowFactor = glowFactor;
                this._glowFactorDirty = true;
            }
        }
    });

    var scratchPositionWC = new Cartesian2();
    var scratchLimbWC = new Cartesian2();
    var scratchPositionEC = new Cartesian4();
    var scratchCartesian4 = new Cartesian4();

    /**
     * @private
     */
    Sun.prototype.update = function(scene) {
        var passState = scene._passState;
        var frameState = scene.frameState;
        var context = scene.context;

        if (!this.show) {
            return undefined;
        }

        var mode = frameState.mode;
        if (mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
            return undefined;
        }

        if (!frameState.passes.render) {
            return undefined;
        }

        var drawingBufferWidth = passState.viewport.width;
        var drawingBufferHeight = passState.viewport.height;

        if (!defined(this._texture) ||
                drawingBufferWidth !== this._drawingBufferWidth ||
                drawingBufferHeight !== this._drawingBufferHeight ||
                this._glowFactorDirty) {
            this._texture = this._texture && this._texture.destroy();
            this._drawingBufferWidth = drawingBufferWidth;
            this._drawingBufferHeight = drawingBufferHeight;
            this._glowFactorDirty = false;

            var size = Math.max(drawingBufferWidth, drawingBufferHeight);
            size = Math.pow(2.0, Math.ceil(Math.log(size) / Math.log(2.0)) - 2.0);

            // The size computed above can be less than 1.0 if size < 4.0. This will probably
            // never happen in practice, but does in the tests. Clamp to 1.0 to prevent WebGL
            // errors in the tests.
            size = Math.max(1.0, size);

            this._texture = new Texture({
                context : context,
                width : size,
                height : size,
                pixelFormat : PixelFormat.RGBA
            });

            this._glowLengthTS = this._glowFactor * 5.0;
            this._radiusTS = (1.0 / (1.0 + 2.0 * this._glowLengthTS)) * 0.5;

            var that = this;
            var uniformMap = {
                u_glowLengthTS : function() {
                    return that._glowLengthTS;
                },
                u_radiusTS : function() {
                    return that._radiusTS;
                }
            };

            this._commands.computeCommand = new ComputeCommand({
                fragmentShaderSource : SunTextureFS,
                outputTexture  : this._texture,
                uniformMap : uniformMap,
                persists : false,
                owner : this,
                postExecute : function() {
                    that._commands.computeCommand = undefined;
                }
            });
        }

        var drawCommand = this._drawCommand;

        if (!defined(drawCommand.vertexArray)) {
            var attributeLocations = {
                direction : 0
            };

            var directions = new Uint8Array(4 * 2);
            directions[0] = 0;
            directions[1] = 0;

            directions[2] = 255;
            directions[3] = 0.0;

            directions[4] = 255;
            directions[5] = 255;

            directions[6] = 0.0;
            directions[7] = 255;

            var vertexBuffer = Buffer.createVertexBuffer({
                context : context,
                typedArray : directions,
                usage : BufferUsage.STATIC_DRAW
            });
            var attributes = [{
                index : attributeLocations.direction,
                vertexBuffer : vertexBuffer,
                componentsPerAttribute : 2,
                normalize : true,
                componentDatatype : ComponentDatatype.UNSIGNED_BYTE
            }];
            // Workaround Internet Explorer 11.0.8 lack of TRIANGLE_FAN
            var indexBuffer = Buffer.createIndexBuffer({
                context : context,
                typedArray : new Uint16Array([0, 1, 2, 0, 2, 3]),
                usage : BufferUsage.STATIC_DRAW,
                indexDatatype : IndexDatatype.UNSIGNED_SHORT
            });
            drawCommand.vertexArray = new VertexArray({
                context : context,
                attributes : attributes,
                indexBuffer : indexBuffer
            });

            drawCommand.shaderProgram = ShaderProgram.fromCache({
                context : context,
                vertexShaderSource : SunVS,
                fragmentShaderSource : SunFS,
                attributeLocations : attributeLocations
            });

            drawCommand.renderState = RenderState.fromCache({
                blending : BlendingState.ALPHA_BLEND
            });
            drawCommand.uniformMap = this._uniformMap;
        }

        var sunPosition = context.uniformState.sunPositionWC;
        var sunPositionCV = context.uniformState.sunPositionColumbusView;

        var boundingVolume = this._boundingVolume;
        var boundingVolume2D = this._boundingVolume2D;

        Cartesian3.clone(sunPosition, boundingVolume.center);
        boundingVolume2D.center.x = sunPositionCV.z;
        boundingVolume2D.center.y = sunPositionCV.x;
        boundingVolume2D.center.z = sunPositionCV.y;

        boundingVolume.radius = CesiumMath.SOLAR_RADIUS + CesiumMath.SOLAR_RADIUS * this._glowLengthTS;
        boundingVolume2D.radius = boundingVolume.radius;

        if (mode === SceneMode.SCENE3D) {
            BoundingSphere.clone(boundingVolume, drawCommand.boundingVolume);
        } else if (mode === SceneMode.COLUMBUS_VIEW) {
            BoundingSphere.clone(boundingVolume2D, drawCommand.boundingVolume);
        }

        var position = SceneTransforms.computeActualWgs84Position(frameState, sunPosition, scratchCartesian4);

        var dist = Cartesian3.magnitude(Cartesian3.subtract(position, scene.camera.position, scratchCartesian4));
        var projMatrix = context.uniformState.projection;

        var positionEC = scratchPositionEC;
        positionEC.x = 0;
        positionEC.y = 0;
        positionEC.z = -dist;
        positionEC.w = 1;

        var positionCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4);
        var positionWC = SceneTransforms.clipToDrawingBufferCoordinates(passState.viewport, positionCC, scratchPositionWC);

        positionEC.x = CesiumMath.SOLAR_RADIUS;
        var limbCC = Matrix4.multiplyByVector(projMatrix, positionEC, scratchCartesian4);
        var limbWC = SceneTransforms.clipToDrawingBufferCoordinates(passState.viewport, limbCC, scratchLimbWC);

        this._size = Math.ceil(Cartesian2.magnitude(Cartesian2.subtract(limbWC, positionWC, scratchCartesian4)));
        this._size = 2.0 * this._size * (1.0 + 2.0 * this._glowLengthTS);

        return this._commands;
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
     *
     * @see Sun#destroy
     */
    Sun.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * sun = sun && sun.destroy();
     * 
     *  @see Sun#isDestroyed
     */
    Sun.prototype.destroy = function() {
        var command = this._drawCommand;
        command.vertexArray = command.vertexArray && command.vertexArray.destroy();
        command.shaderProgram = command.shaderProgram && command.shaderProgram.destroy();

        this._texture = this._texture && this._texture.destroy();

        return destroyObject(this);
    };

    return Sun;
});

/*global define*/
define('Scene/TileCoordinatesImageryProvider',[
        '../Core/Color',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/Event',
        '../Core/GeographicTilingScheme',
        '../ThirdParty/when'
    ], function(
        Color,
        defaultValue,
        defined,
        defineProperties,
        Event,
        GeographicTilingScheme,
        when) {
    'use strict';

    /**
     * An {@link ImageryProvider} that draws a box around every rendered tile in the tiling scheme, and draws
     * a label inside it indicating the X, Y, Level coordinates of the tile.  This is mostly useful for
     * debugging terrain and imagery rendering problems.
     *
     * @alias TileCoordinatesImageryProvider
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme for which to draw tiles.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     *                    this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     *                    parameter is specified, the WGS84 ellipsoid is used.
     * @param {Color} [options.color=Color.YELLOW] The color to draw the tile box and label.
     * @param {Number} [options.tileWidth=256] The width of the tile for level-of-detail selection purposes.
     * @param {Number} [options.tileHeight=256] The height of the tile for level-of-detail selection purposes.
     */
    function TileCoordinatesImageryProvider(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new GeographicTilingScheme({ ellipsoid : options.ellipsoid });
        this._color = defaultValue(options.color, Color.YELLOW);
        this._errorEvent = new Event();
        this._tileWidth = defaultValue(options.tileWidth, 256);
        this._tileHeight = defaultValue(options.tileHeight, 256);
        this._readyPromise = when.resolve(true);
    }

    defineProperties(TileCoordinatesImageryProvider.prototype, {
        /**
         * Gets the proxy used by this provider.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight: {
            get : function() {
                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                return this._tilingScheme.rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return true;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return undefined;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  Setting this property to false reduces memory usage
         * and texture upload time.
         * @memberof TileCoordinatesImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return true;
            }
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    TileCoordinatesImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link TileCoordinatesImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     */
    TileCoordinatesImageryProvider.prototype.requestImage = function(x, y, level) {
        var canvas = document.createElement('canvas');
        canvas.width = 256;
        canvas.height = 256;
        var context = canvas.getContext('2d');

        var cssColor = this._color.toCssColorString();

        context.strokeStyle = cssColor;
        context.lineWidth = 2;
        context.strokeRect(1, 1, 255, 255);

        var label = 'L' + level + 'X' + x + 'Y' + y;
        context.font = 'bold 25px Arial';
        context.textAlign = 'center';
        context.fillStyle = 'black';
        context.fillText(label, 127, 127);
        context.fillStyle = cssColor;
        context.fillText(label, 124, 124);

        return canvas;
    };

    /**
     * Picking features is not currently supported by this imagery provider, so this function simply returns
     * undefined.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    TileCoordinatesImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return undefined;
    };

    return TileCoordinatesImageryProvider;
});

/*global define*/
define('Scene/TileDiscardPolicy',[
        '../Core/DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * A policy for discarding tile images according to some criteria.  This type describes an
     * interface and is not intended to be instantiated directly.
     *
     * @alias TileDiscardPolicy
     * @constructor
     *
     * @see DiscardMissingTileImagePolicy
     * @see NeverTileDiscardPolicy
     */
    function TileDiscardPolicy(options) {
        DeveloperError.throwInstantiationError();
    }

    /**
     * Determines if the discard policy is ready to process images.
     * @function
     *
     * @returns {Boolean} True if the discard policy is ready to process images; otherwise, false.
     */
    TileDiscardPolicy.prototype.isReady = DeveloperError.throwInstantiationError;

    /**
     * Given a tile image, decide whether to discard that image.
     * @function
     *
     * @param {Image} image An image to test.
     * @returns {Boolean} True if the image should be discarded; otherwise, false.
     */
    TileDiscardPolicy.prototype.shouldDiscardImage = DeveloperError.throwInstantiationError;

    return TileDiscardPolicy;
});

/*global define*/
define('Scene/TileState',[
        '../Core/freezeObject'
    ], function(
        freezeObject) {
    'use strict';

    /**
     * @private
     */
    var TileState = {
        START : 0,
        LOADING : 1,
        READY : 2,
        UPSAMPLED_ONLY : 3
    };

    return freezeObject(TileState);
});

//This file is automatically rebuilt by the Cesium build process.
/*global define*/
define('Shaders/ViewportQuadFS',[],function() {
    'use strict';
    return "\n\
varying vec2 v_textureCoordinates;\n\
\n\
void main()\n\
{\n\
    czm_materialInput materialInput;\n\
    \n\
    materialInput.s = v_textureCoordinates.s;\n\
    materialInput.st = v_textureCoordinates;\n\
    materialInput.str = vec3(v_textureCoordinates, 0.0);\n\
    materialInput.normalEC = vec3(0.0, 0.0, -1.0);\n\
    \n\
    czm_material material = czm_getMaterial(materialInput);\n\
\n\
    gl_FragColor = vec4(material.diffuse + material.emission, material.alpha);\n\
}\n\
";
});
/*global define*/
define('Scene/ViewportQuad',[
        '../Core/BoundingRectangle',
        '../Core/Color',
        '../Core/defined',
        '../Core/destroyObject',
        '../Core/DeveloperError',
        '../Renderer/Pass',
        '../Renderer/RenderState',
        '../Renderer/ShaderSource',
        '../Shaders/ViewportQuadFS',
        './BlendingState',
        './Material'
    ], function(
        BoundingRectangle,
        Color,
        defined,
        destroyObject,
        DeveloperError,
        Pass,
        RenderState,
        ShaderSource,
        ViewportQuadFS,
        BlendingState,
        Material) {
    'use strict';

    /**
     * A viewport aligned quad.
     *
     * @alias ViewportQuad
     * @constructor
     *
     * @param {BoundingRectangle} [rectangle] The {@link BoundingRectangle} defining the quad's position within the viewport.
     * @param {Material} [material] The {@link Material} defining the surface appearance of the viewport quad.
     *
     * @example
     * var viewportQuad = new Cesium.ViewportQuad(new Cesium.BoundingRectangle(0, 0, 80, 40));
     * viewportQuad.material.uniforms.color = new Cesium.Color(1.0, 0.0, 0.0, 1.0);
     */
    function ViewportQuad(rectangle, material) {
        /**
         * Determines if the viewport quad primitive will be shown.
         *
         * @type {Boolean}
         * @default true
         */
        this.show = true;

        if (!defined(rectangle)) {
            rectangle = new BoundingRectangle();
        }

        /**
         * The BoundingRectangle defining the quad's position within the viewport.
         *
         * @type {BoundingRectangle}
         *
         * @example
         * viewportQuad.rectangle = new Cesium.BoundingRectangle(0, 0, 80, 40);
         */
        this.rectangle = BoundingRectangle.clone(rectangle);

        if (!defined(material)) {
            material = Material.fromType(Material.ColorType, {
                color : new Color(1.0, 1.0, 1.0, 1.0)
            });
        }

        /**
         * The surface appearance of the viewport quad.  This can be one of several built-in {@link Material} objects or a custom material, scripted with
         * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}.
         * <p>
         * The default material is <code>Material.ColorType</code>.
         * </p>
         *
         * @type Material
         *
         * @example
         * // 1. Change the color of the default material to yellow
         * viewportQuad.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0);
         *
         * // 2. Change material to horizontal stripes
         * viewportQuad.material = Cesium.Material.fromType(Cesium.Material.StripeType);
         *
         * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}
         */
        this.material = material;
        this._material = undefined;

        this._overlayCommand = undefined;
        this._rs = undefined;
    }

    /**
     * Called when {@link Viewer} or {@link CesiumWidget} render the scene to
     * get the draw commands needed to render this primitive.
     * <p>
     * Do not call this function directly.  This is documented just to
     * list the exceptions that may be propagated when the scene is rendered:
     * </p>
     *
     * @exception {DeveloperError} this.material must be defined.
     * @exception {DeveloperError} this.rectangle must be defined.
     */
    ViewportQuad.prototype.update = function(frameState) {
        if (!this.show) {
            return;
        }

                if (!defined(this.material)) {
            throw new DeveloperError('this.material must be defined.');
        }
        if (!defined(this.rectangle)) {
            throw new DeveloperError('this.rectangle must be defined.');
        }
        
        var rs = this._rs;
        if ((!defined(rs)) || !BoundingRectangle.equals(rs.viewport, this.rectangle)) {
            this._rs = RenderState.fromCache({
                blending : BlendingState.ALPHA_BLEND,
                viewport : this.rectangle
            });
        }

        var pass = frameState.passes;
        if (pass.render) {
            var context = frameState.context;

            if (this._material !== this.material || !defined(this._overlayCommand)) {
                // Recompile shader when material changes
                this._material = this.material;

                if (defined(this._overlayCommand)) {
                    this._overlayCommand.shaderProgram.destroy();
                }

                var fs = new ShaderSource({
                    sources : [this._material.shaderSource, ViewportQuadFS]
                });
                this._overlayCommand = context.createViewportQuadCommand(fs, {
                    renderState : this._rs,
                    uniformMap : this._material._uniforms,
                    owner : this
                });
                this._overlayCommand.pass = Pass.OVERLAY;
            }

            this._material.update(context);

            this._overlayCommand.uniformMap = this._material._uniforms;
            frameState.commandList.push(this._overlayCommand);
        }
    };

    /**
     * Returns true if this object was destroyed; otherwise, false.
     * <br /><br />
     * If this object was destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
     *
     * @returns {Boolean} True if this object was destroyed; otherwise, false.
     *
     * @see ViewportQuad#destroy
     */
    ViewportQuad.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the WebGL resources held by this object.  Destroying an object allows for deterministic
     * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
     * <br /><br />
     * Once an object is destroyed, it should not be used; calling any function other than
     * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
     * assign the return value (<code>undefined</code>) to the object as done in the example.
     *
     * @returns {undefined}
     *
     * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
     *
     *
     * @example
     * quad = quad && quad.destroy();
     *
     * @see ViewportQuad#isDestroyed
     */
    ViewportQuad.prototype.destroy = function() {
        if (defined(this._overlayCommand)) {
            this._overlayCommand.shaderProgram = this._overlayCommand.shaderProgram && this._overlayCommand.shaderProgram.destroy();
        }
        return destroyObject(this);
    };

    return ViewportQuad;
});

/*global define*/
define('Scene/WebMapServiceImageryProvider',[
        '../Core/combine',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/freezeObject',
        '../Core/GeographicTilingScheme',
        '../Core/objectToQuery',
        '../Core/queryToObject',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/Uri',
        './GetFeatureInfoFormat',
        './UrlTemplateImageryProvider'
    ], function(
        combine,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        freezeObject,
        GeographicTilingScheme,
        objectToQuery,
        queryToObject,
        WebMercatorTilingScheme,
        Uri,
        GetFeatureInfoFormat,
        UrlTemplateImageryProvider) {
    'use strict';

    /**
     * Provides tiled imagery hosted by a Web Map Service (WMS) server.
     *
     * @alias WebMapServiceImageryProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The URL of the WMS service. The URL supports the same keywords as the {@link UrlTemplateImageryProvider}.
     * @param {String} options.layers The layers to include, separated by commas.
     * @param {Object} [options.parameters=WebMapServiceImageryProvider.DefaultParameters] Additional parameters
     *        to pass to the WMS server in the GetMap URL.
     * @param {Object} [options.getFeatureInfoParameters=WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters] Additional
     *        parameters to pass to the WMS server in the GetFeatureInfo URL.
     * @param {Boolean} [options.enablePickFeatures=true] If true, {@link WebMapServiceImageryProvider#pickFeatures} will invoke
     *        the GetFeatureInfo operation on the WMS server and return the features included in the response.  If false,
     *        {@link WebMapServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
     *        without communicating with the server.  Set this property to false if you know your WMS server does not support
     *        GetFeatureInfo or if you don't want this provider's features to be pickable. Note that this can be dynamically
     *        overridden by modifying the WebMapServiceImageryProvider#enablePickFeatures property.
     * @param {GetFeatureInfoFormat[]} [options.getFeatureInfoFormats=WebMapServiceImageryProvider.DefaultGetFeatureInfoFormats] The formats
     *        in which to try WMS GetFeatureInfo requests.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle of the layer.
     * @param {TilingScheme} [options.tilingScheme=new GeographicTilingScheme()] The tiling scheme to use to divide the world into tiles.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If the tilingScheme is specified,
     *        this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
     *        parameter is specified, the WGS84 ellipsoid is used.
     * @param {Number} [options.tileWidth=256] The width of each tile in pixels.
     * @param {Number} [options.tileHeight=256] The height of each tile in pixels.
     * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.  Take care when
     *        specifying this that the number of tiles at the minimum level is small, such as four or less.  A larger number is
     *        likely to result in rendering problems.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
     *        If not specified, there is no limit.
     * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is
     *        expected to have a getURL function which returns the proxied URL, if needed.
     * @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the <code>{s}</code> placeholder in the URL template.
     *                          If this parameter is a single string, each character in the string is a subdomain.  If it is
     *                          an array, each element in the array is a subdomain.
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapTileServiceImageryProvider
     * @see UrlTemplateImageryProvider
     *
     * @see {@link http://resources.esri.com/help/9.3/arcgisserver/apis/rest/|ArcGIS Server REST API}
     * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
     *
     * @example
     * var provider = new Cesium.WebMapServiceImageryProvider({
     *     url : 'https://sampleserver1.arcgisonline.com/ArcGIS/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/WMSServer',
     *     layers : '0',
     *     proxy: new Cesium.DefaultProxy('/proxy/')
     * });
     *
     * viewer.imageryLayers.addImageryProvider(provider);
     */
    function WebMapServiceImageryProvider(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        if (!defined(options.layers)) {
            throw new DeveloperError('options.layers is required.');
        }
        
        this._url = options.url;
        this._layers = options.layers;

        var getFeatureInfoFormats = defaultValue(options.getFeatureInfoFormats, WebMapServiceImageryProvider.DefaultGetFeatureInfoFormats);

        // Build the template URLs for tiles and pickFeatures.
        var uri = new Uri(options.url);
        var queryOptions = queryToObject(defaultValue(uri.query, ''));
        var parameters = combine(objectToLowercase(defaultValue(options.parameters, defaultValue.EMPTY_OBJECT)), WebMapServiceImageryProvider.DefaultParameters);
        queryOptions = combine(parameters, queryOptions);

        var pickFeaturesUri;
        var pickFeaturesQueryOptions;

        pickFeaturesUri = new Uri(options.url);
        pickFeaturesQueryOptions = queryToObject(defaultValue(pickFeaturesUri.query, ''));
        var pickFeaturesParameters = combine(objectToLowercase(defaultValue(options.getFeatureInfoParameters, defaultValue.EMPTY_OBJECT)), WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters);
        pickFeaturesQueryOptions = combine(pickFeaturesParameters, pickFeaturesQueryOptions);

        function setParameter(name, value) {
            if (!defined(queryOptions[name])) {
                queryOptions[name] = value;
            }

            if (defined(pickFeaturesQueryOptions) && !defined(pickFeaturesQueryOptions[name])) {
                pickFeaturesQueryOptions[name] = value;
            }
        }

        setParameter('layers', options.layers);

        // Use SRS or CRS based on the WMS version.
        if (parseFloat(parameters.version) >= 1.3) {
          // Use CRS with 1.3.0 and going forward.
          // For GeographicTilingScheme, use CRS:84 vice EPSG:4326 to specify lon, lat (x, y) ordering for
          // bbox requests.
          setParameter('crs', options.tilingScheme instanceof WebMercatorTilingScheme ? 'EPSG:3857' : 'CRS:84');
        }
        else {
          // SRS for WMS 1.1.0 or 1.1.1.
          setParameter('srs', options.tilingScheme instanceof WebMercatorTilingScheme ? 'EPSG:3857' : 'EPSG:4326');
        }

        setParameter('bbox', '{westProjected},{southProjected},{eastProjected},{northProjected}');
        setParameter('width', '{width}');
        setParameter('height', '{height}');

        uri.query = objectToQuery(queryOptions);

        // objectToQuery escapes the placeholders.  Undo that.
        var templateUrl = uri.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');

        var pickFeaturesTemplateUrl;
        if (defined(pickFeaturesQueryOptions)) {
            if (!defined(pickFeaturesQueryOptions.query_layers)) {
                pickFeaturesQueryOptions.query_layers = options.layers;
            }

            if (!defined(pickFeaturesQueryOptions.x)) {
                pickFeaturesQueryOptions.x = '{i}';
            }

            if (!defined(pickFeaturesQueryOptions.y)) {
                pickFeaturesQueryOptions.y = '{j}';
            }

            if (!defined(pickFeaturesQueryOptions.info_format)) {
                pickFeaturesQueryOptions.info_format = '{format}';
            }

            pickFeaturesUri.query = objectToQuery(pickFeaturesQueryOptions);
            pickFeaturesTemplateUrl = pickFeaturesUri.toString().replace(/%7B/g, '{').replace(/%7D/g, '}');
        }

        // Let UrlTemplateImageryProvider do the actual URL building.
        this._tileProvider = new UrlTemplateImageryProvider({
            url : templateUrl,
            pickFeaturesUrl : pickFeaturesTemplateUrl,
            tilingScheme : defaultValue(options.tilingScheme, new GeographicTilingScheme({ ellipsoid : options.ellipsoid})),
            rectangle : options.rectangle,
            tileWidth : options.tileWidth,
            tileHeight : options.tileHeight,
            minimumLevel : options.minimumLevel,
            maximumLevel : options.maximumLevel,
            proxy : options.proxy,
            subdomains: options.subdomains,
            tileDiscardPolicy : options.tileDiscardPolicy,
            credit : options.credit,
            getFeatureInfoFormats : getFeatureInfoFormats,
            enablePickFeatures: options.enablePickFeatures
        });
    }

    defineProperties(WebMapServiceImageryProvider.prototype, {
        /**
         * Gets the URL of the WMS server.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._tileProvider.proxy;
            }
        },

        /**
         * Gets the names of the WMS layers, separated by commas.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        layers : {
            get : function() {
                return this._layers;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                return this._tileProvider.tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight : {
            get : function() {
                return this._tileProvider.tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                return this._tileProvider.maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                return this._tileProvider.minimumLevel;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                return this._tileProvider.tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                return this._tileProvider.rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                return this._tileProvider.tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._tileProvider.errorEvent;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            get : function() {
                return this._tileProvider.ready;
            }
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._tileProvider.readyPromise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link WebMapServiceImageryProvider#ready} returns true.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._tileProvider.credit;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return this._tileProvider.hasAlphaChannel;
            }
        },

        /**
         * Gets or sets a value indicating whether feature picking is enabled.  If true, {@link WebMapServiceImageryProvider#pickFeatures} will
         * invoke the <code>GetFeatureInfo</code> service on the WMS server and attempt to interpret the features included in the response.  If false,
         * {@link WebMapServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
         * features) without communicating with the server.  Set this property to false if you know your data
         * source does not support picking features or if you don't want this provider's features to be pickable.
         * @memberof WebMapServiceImageryProvider.prototype
         * @type {Boolean}
         * @default true
         */
        enablePickFeatures : {
            get : function() {
                return this._tileProvider.enablePickFeatures;
            },
            set : function(enablePickFeatures)  {
                this._tileProvider.enablePickFeatures = enablePickFeatures;
            }
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    WebMapServiceImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return this._tileProvider.getTileCredits(x, y, level);
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link WebMapServiceImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    WebMapServiceImageryProvider.prototype.requestImage = function(x, y, level) {
        return this._tileProvider.requestImage(x, y, level);
    };

    /**
     * Asynchronously determines what features, if any, are located at a given longitude and latitude within
     * a tile.  This function should not be called before {@link ImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *
     * @exception {DeveloperError} <code>pickFeatures</code> must not be called before the imagery provider is ready.
     */
    WebMapServiceImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return this._tileProvider.pickFeatures(x, y, level, longitude, latitude);
    };

    /**
     * The default parameters to include in the WMS URL to obtain images.  The values are as follows:
     *    service=WMS
     *    version=1.1.1
     *    request=GetMap
     *    styles=
     *    format=image/jpeg
     *
     * @constant
     */
    WebMapServiceImageryProvider.DefaultParameters = freezeObject({
        service : 'WMS',
        version : '1.1.1',
        request : 'GetMap',
        styles : '',
        format : 'image/jpeg'
    });

    /**
     * The default parameters to include in the WMS URL to get feature information.  The values are as follows:
     *     service=WMS
     *     version=1.1.1
     *     request=GetFeatureInfo
     *
     * @constant
     */
    WebMapServiceImageryProvider.GetFeatureInfoDefaultParameters = freezeObject({
        service : 'WMS',
        version : '1.1.1',
        request : 'GetFeatureInfo'
    });

    WebMapServiceImageryProvider.DefaultGetFeatureInfoFormats = freezeObject([
        freezeObject(new GetFeatureInfoFormat('json', 'application/json')),
        freezeObject(new GetFeatureInfoFormat('xml', 'text/xml')),
        freezeObject(new GetFeatureInfoFormat('text', 'text/html'))
    ]);

    function objectToLowercase(obj) {
        var result = {};
        for ( var key in obj) {
            if (obj.hasOwnProperty(key)) {
                result[key.toLowerCase()] = obj[key];
            }
        }
        return result;
    }

    return WebMapServiceImageryProvider;
});

/*global define*/
define('Scene/WebMapTileServiceImageryProvider',[
        '../Core/combine',
        '../Core/Credit',
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../Core/freezeObject',
        '../Core/isArray',
        '../Core/objectToQuery',
        '../Core/queryToObject',
        '../Core/Rectangle',
        '../Core/WebMercatorTilingScheme',
        '../ThirdParty/Uri',
        '../ThirdParty/when',
        './ImageryProvider'
    ], function(
        combine,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        freezeObject,
        isArray,
        objectToQuery,
        queryToObject,
        Rectangle,
        WebMercatorTilingScheme,
        Uri,
        when,
        ImageryProvider) {
    'use strict';

    /**
     * Provides tiled imagery served by {@link http://www.opengeospatial.org/standards/wmts|WMTS 1.0.0} compliant servers.
     * This provider supports HTTP KVP-encoded and RESTful GetTile requests, but does not yet support the SOAP encoding.
     *
     * @alias WebMapTileServiceImageryProvider
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {String} options.url The base URL for the WMTS GetTile operation (for KVP-encoded requests) or the tile-URL template (for RESTful requests). The tile-URL template should contain the following variables: &#123;style&#125;, &#123;TileMatrixSet&#125;, &#123;TileMatrix&#125;, &#123;TileRow&#125;, &#123;TileCol&#125;. The first two are optional if actual values are hardcoded or not required by the server. The &#123;s&#125; keyword may be used to specify subdomains.
     * @param {String} [options.format='image/jpeg'] The MIME type for images to retrieve from the server.
     * @param {String} options.layer The layer name for WMTS requests.
     * @param {String} options.style The style name for WMTS requests.
     * @param {String} options.tileMatrixSetID The identifier of the TileMatrixSet to use for WMTS requests.
     * @param {Array} [options.tileMatrixLabels] A list of identifiers in the TileMatrix to use for WMTS requests, one per TileMatrix level.
     * @param {Number} [options.tileWidth=256] The tile width in pixels.
     * @param {Number} [options.tileHeight=256] The tile height in pixels.
     * @param {TilingScheme} [options.tilingScheme] The tiling scheme corresponding to the organization of the tiles in the TileMatrixSet.
     * @param {Object} [options.proxy] A proxy to use for requests. This object is expected to have a getURL function which returns the proxied URL.
     * @param {Rectangle} [options.rectangle=Rectangle.MAX_VALUE] The rectangle covered by the layer.
     * @param {Number} [options.minimumLevel=0] The minimum level-of-detail supported by the imagery provider.
     * @param {Number} [options.maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
     * @param {Ellipsoid} [options.ellipsoid] The ellipsoid.  If not specified, the WGS84 ellipsoid is used.
     * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
     * @param {String|String[]} [options.subdomains='abc'] The subdomains to use for the <code>{s}</code> placeholder in the URL template.
     *                          If this parameter is a single string, each character in the string is a subdomain.  If it is
     *                          an array, each element in the array is a subdomain.
     *
     *
     * @example
     * // Example 1. USGS shaded relief tiles (KVP)
     * var shadedRelief1 = new Cesium.WebMapTileServiceImageryProvider({
     *     url : 'http://basemap.nationalmap.gov/arcgis/rest/services/USGSShadedReliefOnly/MapServer/WMTS',
     *     layer : 'USGSShadedReliefOnly',
     *     style : 'default',
     *     format : 'image/jpeg',
     *     tileMatrixSetID : 'default028mm',
     *     // tileMatrixLabels : ['default028mm:0', 'default028mm:1', 'default028mm:2' ...],
     *     maximumLevel: 19,
     *     credit : new Cesium.Credit('U. S. Geological Survey')
     * });
     * viewer.imageryLayers.addImageryProvider(shadedRelief1);
     *
     * @example
     * // Example 2. USGS shaded relief tiles (RESTful)
     * var shadedRelief2 = new Cesium.WebMapTileServiceImageryProvider({
     *     url : 'http://basemap.nationalmap.gov/arcgis/rest/services/USGSShadedReliefOnly/MapServer/WMTS/tile/1.0.0/USGSShadedReliefOnly/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpg',
     *     layer : 'USGSShadedReliefOnly',
     *     style : 'default',
     *     format : 'image/jpeg',
     *     tileMatrixSetID : 'default028mm',
     *     maximumLevel: 19,
     *     credit : new Cesium.Credit('U. S. Geological Survey')
     * });
     * viewer.imageryLayers.addImageryProvider(shadedRelief2);
     *
     * @see ArcGisMapServerImageryProvider
     * @see BingMapsImageryProvider
     * @see GoogleEarthImageryProvider
     * @see createOpenStreetMapImageryProvider
     * @see SingleTileImageryProvider
     * @see createTileMapServiceImageryProvider
     * @see WebMapServiceImageryProvider
     * @see UrlTemplateImageryProvider
     */
    function WebMapTileServiceImageryProvider(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

                if (!defined(options.url)) {
            throw new DeveloperError('options.url is required.');
        }
        if (!defined(options.layer)) {
            throw new DeveloperError('options.layer is required.');
        }
        if (!defined(options.style)) {
            throw new DeveloperError('options.style is required.');
        }
        if (!defined(options.tileMatrixSetID)) {
            throw new DeveloperError('options.tileMatrixSetID is required.');
        }
        
        this._url = options.url;
        this._layer = options.layer;
        this._style = options.style;
        this._tileMatrixSetID = options.tileMatrixSetID;
        this._tileMatrixLabels = options.tileMatrixLabels;
        this._format = defaultValue(options.format, 'image/jpeg');
        this._proxy = options.proxy;
        this._tileDiscardPolicy = options.tileDiscardPolicy;

        this._tilingScheme = defined(options.tilingScheme) ? options.tilingScheme : new WebMercatorTilingScheme({ ellipsoid : options.ellipsoid });
        this._tileWidth = defaultValue(options.tileWidth, 256);
        this._tileHeight = defaultValue(options.tileHeight, 256);

        this._minimumLevel = defaultValue(options.minimumLevel, 0);
        this._maximumLevel = options.maximumLevel;

        this._rectangle = defaultValue(options.rectangle, this._tilingScheme.rectangle);

        this._readyPromise = when.resolve(true);

        // Check the number of tiles at the minimum level.  If it's more than four,
        // throw an exception, because starting at the higher minimum
        // level will cause too many tiles to be downloaded and rendered.
        var swTile = this._tilingScheme.positionToTileXY(Rectangle.southwest(this._rectangle), this._minimumLevel);
        var neTile = this._tilingScheme.positionToTileXY(Rectangle.northeast(this._rectangle), this._minimumLevel);
        var tileCount = (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
                if (tileCount > 4) {
            throw new DeveloperError('The imagery provider\'s rectangle and minimumLevel indicate that there are ' + tileCount + ' tiles at the minimum level. Imagery providers with more than four tiles at the minimum level are not supported.');
        }
        
        this._errorEvent = new Event();

        var credit = options.credit;
        this._credit = typeof credit === 'string' ? new Credit(credit) : credit;

        this._subdomains = options.subdomains;
        if (isArray(this._subdomains)) {
            this._subdomains = this._subdomains.slice();
        } else if (defined(this._subdomains) && this._subdomains.length > 0) {
            this._subdomains = this._subdomains.split('');
        } else {
            this._subdomains = ['a', 'b', 'c'];
        }
    }

    var defaultParameters = freezeObject({
        service : 'WMTS',
        version : '1.0.0',
        request : 'GetTile'
    });

    function buildImageUrl(imageryProvider, col, row, level) {
        var labels = imageryProvider._tileMatrixLabels;
        var tileMatrix = defined(labels) ? labels[level] : level.toString();
        var subdomains = imageryProvider._subdomains;
        var url;

        if (imageryProvider._url.indexOf('{') >= 0) {
            // resolve tile-URL template
            url = imageryProvider._url
                .replace('{style}', imageryProvider._style)
                .replace('{Style}', imageryProvider._style)
                .replace('{TileMatrixSet}', imageryProvider._tileMatrixSetID)
                .replace('{TileMatrix}', tileMatrix)
                .replace('{TileRow}', row.toString())
                .replace('{TileCol}', col.toString())
                .replace('{s}', subdomains[(col + row + level) % subdomains.length]);
        }
        else {
            // build KVP request
            var uri = new Uri(imageryProvider._url);
            var queryOptions = queryToObject(defaultValue(uri.query, ''));

            queryOptions = combine(defaultParameters, queryOptions);

            queryOptions.tilematrix = tileMatrix;
            queryOptions.layer = imageryProvider._layer;
            queryOptions.style = imageryProvider._style;
            queryOptions.tilerow = row;
            queryOptions.tilecol = col;
            queryOptions.tilematrixset = imageryProvider._tileMatrixSetID;
            queryOptions.format = imageryProvider._format;

            uri.query = objectToQuery(queryOptions);

            url = uri.toString();
        }

        var proxy = imageryProvider._proxy;
        if (defined(proxy)) {
            url = proxy.getURL(url);
        }

        return url;
    }

    defineProperties(WebMapTileServiceImageryProvider.prototype, {
        /**
         * Gets the URL of the service hosting the imagery.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        url : {
            get : function() {
                return this._url;
            }
        },

        /**
         * Gets the proxy used by this provider.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Proxy}
         * @readonly
         */
        proxy : {
            get : function() {
                return this._proxy;
            }
        },

        /**
         * Gets the width of each tile, in pixels. This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileWidth : {
            get : function() {
                return this._tileWidth;
            }
        },

        /**
         * Gets the height of each tile, in pixels.  This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        tileHeight : {
            get : function() {
                return this._tileHeight;
            }
        },

        /**
         * Gets the maximum level-of-detail that can be requested.  This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        maximumLevel : {
            get : function() {
                return this._maximumLevel;
            }
        },

        /**
         * Gets the minimum level-of-detail that can be requested.  This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Number}
         * @readonly
         */
        minimumLevel : {
            get : function() {
                return this._minimumLevel;
            }
        },

        /**
         * Gets the tiling scheme used by this provider.  This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {TilingScheme}
         * @readonly
         */
        tilingScheme : {
            get : function() {
                return this._tilingScheme;
            }
        },

        /**
         * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Rectangle}
         * @readonly
         */
        rectangle : {
            get : function() {
                return this._rectangle;
            }
        },

        /**
         * Gets the tile discard policy.  If not undefined, the discard policy is responsible
         * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
         * returns undefined, no tiles are filtered.  This function should
         * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {TileDiscardPolicy}
         * @readonly
         */
        tileDiscardPolicy : {
            get : function() {
                return this._tileDiscardPolicy;
            }
        },

        /**
         * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
         * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
         * are passed an instance of {@link TileProviderError}.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Event}
         * @readonly
         */
        errorEvent : {
            get : function() {
                return this._errorEvent;
            }
        },

        /**
         * Gets the mime type of images returned by this imagery provider.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {String}
         * @readonly
         */
        format : {
            get : function() {
                return this._format;
            }
        },

        /**
         * Gets a value indicating whether or not the provider is ready for use.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        ready : {
            value: true
        },

        /**
         * Gets a promise that resolves to true when the provider is ready for use.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Promise.<Boolean>}
         * @readonly
         */
        readyPromise : {
            get : function() {
                return this._readyPromise;
            }
        },

        /**
         * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
         * the source of the imagery.  This function should not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Credit}
         * @readonly
         */
        credit : {
            get : function() {
                return this._credit;
            }
        },

        /**
         * Gets a value indicating whether or not the images provided by this imagery provider
         * include an alpha channel.  If this property is false, an alpha channel, if present, will
         * be ignored.  If this property is true, any images without an alpha channel will be treated
         * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
         * and texture upload time are reduced.
         * @memberof WebMapTileServiceImageryProvider.prototype
         * @type {Boolean}
         * @readonly
         */
        hasAlphaChannel : {
            get : function() {
                return true;
            }
        }
    });

    /**
     * Gets the credits to be displayed when a given tile is displayed.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level;
     * @returns {Credit[]} The credits to be displayed when the tile is displayed.
     *
     * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
     */
    WebMapTileServiceImageryProvider.prototype.getTileCredits = function(x, y, level) {
        return undefined;
    };

    /**
     * Requests the image for a given tile.  This function should
     * not be called before {@link WebMapTileServiceImageryProvider#ready} returns true.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
     *          undefined if there are too many active requests to the server, and the request
     *          should be retried later.  The resolved image may be either an
     *          Image or a Canvas DOM object.
     *
     * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
     */
    WebMapTileServiceImageryProvider.prototype.requestImage = function(x, y, level) {
        var url = buildImageUrl(this, x, y, level);
        return ImageryProvider.loadImage(this, url);
    };

    /**
     * Picking features is not currently supported by this imagery provider, so this function simply returns
     * undefined.
     *
     * @param {Number} x The tile X coordinate.
     * @param {Number} y The tile Y coordinate.
     * @param {Number} level The tile level.
     * @param {Number} longitude The longitude at which to pick features.
     * @param {Number} latitude  The latitude at which to pick features.
     * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
     *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
     *                   instances.  The array may be empty if no features are found at the given location.
     *                   It may also be undefined if picking is not supported.
     */
    WebMapTileServiceImageryProvider.prototype.pickFeatures = function(x, y, level, longitude, latitude) {
        return undefined;
    };

    return WebMapTileServiceImageryProvider;
});

/**
 * @licence
 *
 * crunch/crnlib v1.04 - Advanced DXTn texture compression library
 * Copyright (C) 2010-2016 Richard Geldreich, Jr. and Binomial LLC http://binomial.info
 */

/**
 * @license
 *
 * crunch_lib.cpp
 *
 * Copyright (c) 2013, Evan Parker, Brandon Jones. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// The C++ code was compiled to Javascript with Emcripten.
// For instructions, see:
//    https://github.com/AnalyticalGraphicsInc/crunch

/*global define*/
define('ThirdParty/crunch',[], function() {

    var Module;if(!Module)Module=(typeof Module!=="undefined"?Module:null)||{};var moduleOverrides={};for(var key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){if(!Module["print"])Module["print"]=function print(x){process["stdout"].write(x+"\n")};if(!Module["printErr"])Module["printErr"]=function printErr(x){process["stderr"].write(x+"\n")};var nodeFS=require("fs");var nodePath=require("path");Module["read"]=function read(filename,binary){filename=nodePath["normalize"](filename);var ret=nodeFS["readFileSync"](filename);if(!ret&&filename!=nodePath["resolve"](filename)){filename=path.join(__dirname,"..","src",filename);ret=nodeFS["readFileSync"](filename)}if(ret&&!binary)ret=ret.toString();return ret};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};Module["load"]=function load(f){globalEval(read(f))};if(!Module["thisProgram"]){if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}else{Module["thisProgram"]="unknown-program"}}Module["arguments"]=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",(function(ex){if(!(ex instanceof ExitStatus)){throw ex}}));Module["inspect"]=(function(){return"[Emscripten Module object]"})}else if(ENVIRONMENT_IS_SHELL){if(!Module["print"])Module["print"]=print;if(typeof printErr!="undefined")Module["printErr"]=printErr;if(typeof read!="undefined"){Module["read"]=read}else{Module["read"]=function read(){throw"no read() available (jsc?)"}}Module["readBinary"]=function readBinary(f){if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}var data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof console!=="undefined"){if(!Module["print"])Module["print"]=function print(x){console.log(x)};if(!Module["printErr"])Module["printErr"]=function printErr(x){console.log(x)}}else{var TRY_USE_DUMP=false;if(!Module["print"])Module["print"]=TRY_USE_DUMP&&typeof dump!=="undefined"?(function(x){dump(x)}):(function(x){})}if(ENVIRONMENT_IS_WORKER){Module["load"]=importScripts}if(typeof Module["setWindowTitle"]==="undefined"){Module["setWindowTitle"]=(function(title){document.title=title})}}else{throw"Unknown runtime environment. Where are we?"}function globalEval(x){eval.call(null,x)}if(!Module["load"]&&Module["read"]){Module["load"]=function load(f){globalEval(Module["read"](f))}}if(!Module["print"]){Module["print"]=(function(){})}if(!Module["printErr"]){Module["printErr"]=Module["print"]}if(!Module["arguments"]){Module["arguments"]=[]}if(!Module["thisProgram"]){Module["thisProgram"]="./this.program"}Module.print=Module["print"];Module.printErr=Module["printErr"];Module["preRun"]=[];Module["postRun"]=[];for(var key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}var Runtime={setTempRet0:(function(value){tempRet0=value}),getTempRet0:(function(){return tempRet0}),stackSave:(function(){return STACKTOP}),stackRestore:(function(stackTop){STACKTOP=stackTop}),getNativeTypeSize:(function(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return Runtime.QUANTUM_SIZE}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0);return bits/8}else{return 0}}}}),getNativeFieldSize:(function(type){return Math.max(Runtime.getNativeTypeSize(type),Runtime.QUANTUM_SIZE)}),STACK_ALIGN:16,prepVararg:(function(ptr,type){if(type==="double"||type==="i64"){if(ptr&7){assert((ptr&7)===4);ptr+=4}}else{assert((ptr&3)===0)}return ptr}),getAlignSize:(function(type,size,vararg){if(!vararg&&(type=="i64"||type=="double"))return 8;if(!type)return Math.min(size,8);return Math.min(size||(type?Runtime.getNativeFieldSize(type):0),Runtime.QUANTUM_SIZE)}),dynCall:(function(sig,ptr,args){if(args&&args.length){if(!args.splice)args=Array.prototype.slice.call(args);args.splice(0,0,ptr);return Module["dynCall_"+sig].apply(null,args)}else{return Module["dynCall_"+sig].call(null,ptr)}}),functionPointers:[],addFunction:(function(func){for(var i=0;i<Runtime.functionPointers.length;i++){if(!Runtime.functionPointers[i]){Runtime.functionPointers[i]=func;return 2*(1+i)}}throw"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS."}),removeFunction:(function(index){Runtime.functionPointers[(index-2)/2]=null}),warnOnce:(function(text){if(!Runtime.warnOnce.shown)Runtime.warnOnce.shown={};if(!Runtime.warnOnce.shown[text]){Runtime.warnOnce.shown[text]=1;Module.printErr(text)}}),funcWrappers:{},getFuncWrapper:(function(func,sig){assert(sig);if(!Runtime.funcWrappers[sig]){Runtime.funcWrappers[sig]={}}var sigCache=Runtime.funcWrappers[sig];if(!sigCache[func]){sigCache[func]=function dynCall_wrapper(){return Runtime.dynCall(sig,func,arguments)}}return sigCache[func]}),getCompilerSetting:(function(name){throw"You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work"}),stackAlloc:(function(size){var ret=STACKTOP;STACKTOP=STACKTOP+size|0;STACKTOP=STACKTOP+15&-16;return ret}),staticAlloc:(function(size){var ret=STATICTOP;STATICTOP=STATICTOP+size|0;STATICTOP=STATICTOP+15&-16;return ret}),dynamicAlloc:(function(size){var ret=DYNAMICTOP;DYNAMICTOP=DYNAMICTOP+size|0;DYNAMICTOP=DYNAMICTOP+15&-16;if(DYNAMICTOP>=TOTAL_MEMORY){var success=enlargeMemory();if(!success){DYNAMICTOP=ret;return 0}}return ret}),alignMemory:(function(size,quantum){var ret=size=Math.ceil(size/(quantum?quantum:16))*(quantum?quantum:16);return ret}),makeBigInt:(function(low,high,unsigned){var ret=unsigned?+(low>>>0)+ +(high>>>0)*+4294967296:+(low>>>0)+ +(high|0)*+4294967296;return ret}),GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module["Runtime"]=Runtime;var __THREW__=0;var ABORT=false;var EXITSTATUS=0;var undef=0;var tempValue,tempInt,tempBigInt,tempInt2,tempBigInt2,tempPair,tempBigIntI,tempBigIntR,tempBigIntS,tempBigIntP,tempBigIntD,tempDouble,tempFloat;var tempI64,tempI64b;var tempRet0,tempRet1,tempRet2,tempRet3,tempRet4,tempRet5,tempRet6,tempRet7,tempRet8,tempRet9;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var globalScope=this;function getCFunc(ident){var func=Module["_"+ident];if(!func){try{func=eval("_"+ident)}catch(e){}}assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)");return func}var cwrap,ccall;((function(){var JSfuncs={"stackSave":(function(){Runtime.stackSave()}),"stackRestore":(function(){Runtime.stackRestore()}),"arrayToC":(function(arr){var ret=Runtime.stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}),"stringToC":(function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=Runtime.stackAlloc((str.length<<2)+1);writeStringToMemory(str,ret)}return ret})};var toC={"string":JSfuncs["stringToC"],"array":JSfuncs["arrayToC"]};ccall=function ccallFunc(ident,returnType,argTypes,args,opts){var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i<args.length;i++){var converter=toC[argTypes[i]];if(converter){if(stack===0)stack=Runtime.stackSave();cArgs[i]=converter(args[i])}else{cArgs[i]=args[i]}}}var ret=func.apply(null,cArgs);if(returnType==="string")ret=Pointer_stringify(ret);if(stack!==0){if(opts&&opts.async){EmterpreterAsync.asyncFinalizers.push((function(){Runtime.stackRestore(stack)}));return}Runtime.stackRestore(stack)}return ret};var sourceRegex=/^function\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/;function parseJSFunc(jsfunc){var parsed=jsfunc.toString().match(sourceRegex).slice(1);return{arguments:parsed[0],body:parsed[1],returnValue:parsed[2]}}var JSsource={};for(var fun in JSfuncs){if(JSfuncs.hasOwnProperty(fun)){JSsource[fun]=parseJSFunc(JSfuncs[fun])}}cwrap=function cwrap(ident,returnType,argTypes){argTypes=argTypes||[];var cfunc=getCFunc(ident);var numericArgs=argTypes.every((function(type){return type==="number"}));var numericRet=returnType!=="string";if(numericRet&&numericArgs){return cfunc}var argNames=argTypes.map((function(x,i){return"$"+i}));var funcstr="(function("+argNames.join(",")+") {";var nargs=argTypes.length;if(!numericArgs){funcstr+="var stack = "+JSsource["stackSave"].body+";";for(var i=0;i<nargs;i++){var arg=argNames[i],type=argTypes[i];if(type==="number")continue;var convertCode=JSsource[type+"ToC"];funcstr+="var "+convertCode.arguments+" = "+arg+";";funcstr+=convertCode.body+";";funcstr+=arg+"="+convertCode.returnValue+";"}}var cfuncname=parseJSFunc((function(){return cfunc})).returnValue;funcstr+="var ret = "+cfuncname+"("+argNames.join(",")+");";if(!numericRet){var strgfy=parseJSFunc((function(){return Pointer_stringify})).returnValue;funcstr+="ret = "+strgfy+"(ret);"}if(!numericArgs){funcstr+=JSsource["stackRestore"].body.replace("()","(stack)")+";"}funcstr+="return ret})";return eval(funcstr)}}))();Module["ccall"]=ccall;Module["cwrap"]=cwrap;function setValue(ptr,value,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math_abs(tempDouble)>=+1?tempDouble>+0?(Math_min(+Math_floor(tempDouble/+4294967296),+4294967295)|0)>>>0:~~+Math_ceil((tempDouble- +(~~tempDouble>>>0))/+4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}Module["setValue"]=setValue;function getValue(ptr,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];default:abort("invalid type for setValue: "+type)}return null}Module["getValue"]=getValue;var ALLOC_NORMAL=0;var ALLOC_STACK=1;var ALLOC_STATIC=2;var ALLOC_DYNAMIC=3;var ALLOC_NONE=4;Module["ALLOC_NORMAL"]=ALLOC_NORMAL;Module["ALLOC_STACK"]=ALLOC_STACK;Module["ALLOC_STATIC"]=ALLOC_STATIC;Module["ALLOC_DYNAMIC"]=ALLOC_DYNAMIC;Module["ALLOC_NONE"]=ALLOC_NONE;function allocate(slab,types,allocator,ptr){var zeroinit,size;if(typeof slab==="number"){zeroinit=true;size=slab}else{zeroinit=false;size=slab.length}var singleType=typeof types==="string"?types:null;var ret;if(allocator==ALLOC_NONE){ret=ptr}else{ret=[_malloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][allocator===undefined?ALLOC_STATIC:allocator](Math.max(size,singleType?1:types.length))}if(zeroinit){var ptr=ret,stop;assert((ret&3)==0);stop=ret+(size&~3);for(;ptr<stop;ptr+=4){HEAP32[ptr>>2]=0}stop=ret+size;while(ptr<stop){HEAP8[ptr++>>0]=0}return ret}if(singleType==="i8"){if(slab.subarray||slab.slice){HEAPU8.set(slab,ret)}else{HEAPU8.set(new Uint8Array(slab),ret)}return ret}var i=0,type,typeSize,previousType;while(i<size){var curr=slab[i];if(typeof curr==="function"){curr=Runtime.getFunctionIndex(curr)}type=singleType||types[i];if(type===0){i++;continue}if(type=="i64")type="i32";setValue(ret+i,curr,type);if(previousType!==type){typeSize=Runtime.getNativeTypeSize(type);previousType=type}i+=typeSize}return ret}Module["allocate"]=allocate;function getMemory(size){if(!staticSealed)return Runtime.staticAlloc(size);if(typeof _sbrk!=="undefined"&&!_sbrk.called||!runtimeInitialized)return Runtime.dynamicAlloc(size);return _malloc(size)}Module["getMemory"]=getMemory;function Pointer_stringify(ptr,length){if(length===0||!ptr)return"";var hasUtf=0;var t;var i=0;while(1){t=HEAPU8[ptr+i>>0];hasUtf|=t;if(t==0&&!length)break;i++;if(length&&i==length)break}if(!length)length=i;var ret="";if(hasUtf<128){var MAX_CHUNK=1024;var curr;while(length>0){curr=String.fromCharCode.apply(String,HEAPU8.subarray(ptr,ptr+Math.min(length,MAX_CHUNK)));ret=ret?ret+curr:curr;ptr+=MAX_CHUNK;length-=MAX_CHUNK}return ret}return Module["UTF8ToString"](ptr)}Module["Pointer_stringify"]=Pointer_stringify;function AsciiToString(ptr){var str="";while(1){var ch=HEAP8[ptr++>>0];if(!ch)return str;str+=String.fromCharCode(ch)}}Module["AsciiToString"]=AsciiToString;function stringToAscii(str,outPtr){return writeAsciiToMemory(str,outPtr,false)}Module["stringToAscii"]=stringToAscii;function UTF8ArrayToString(u8Array,idx){var u0,u1,u2,u3,u4,u5;var str="";while(1){u0=u8Array[idx++];if(!u0)return str;if(!(u0&128)){str+=String.fromCharCode(u0);continue}u1=u8Array[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}u2=u8Array[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u3=u8Array[idx++]&63;if((u0&248)==240){u0=(u0&7)<<18|u1<<12|u2<<6|u3}else{u4=u8Array[idx++]&63;if((u0&252)==248){u0=(u0&3)<<24|u1<<18|u2<<12|u3<<6|u4}else{u5=u8Array[idx++]&63;u0=(u0&1)<<30|u1<<24|u2<<18|u3<<12|u4<<6|u5}}}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}}Module["UTF8ArrayToString"]=UTF8ArrayToString;function UTF8ToString(ptr){return UTF8ArrayToString(HEAPU8,ptr)}Module["UTF8ToString"]=UTF8ToString;function stringToUTF8Array(str,outU8Array,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127){if(outIdx>=endIdx)break;outU8Array[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;outU8Array[outIdx++]=192|u>>6;outU8Array[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;outU8Array[outIdx++]=224|u>>12;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else if(u<=2097151){if(outIdx+3>=endIdx)break;outU8Array[outIdx++]=240|u>>18;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else if(u<=67108863){if(outIdx+4>=endIdx)break;outU8Array[outIdx++]=248|u>>24;outU8Array[outIdx++]=128|u>>18&63;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else{if(outIdx+5>=endIdx)break;outU8Array[outIdx++]=252|u>>30;outU8Array[outIdx++]=128|u>>24&63;outU8Array[outIdx++]=128|u>>18&63;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}}outU8Array[outIdx]=0;return outIdx-startIdx}Module["stringToUTF8Array"]=stringToUTF8Array;function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}Module["stringToUTF8"]=stringToUTF8;function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127){++len}else if(u<=2047){len+=2}else if(u<=65535){len+=3}else if(u<=2097151){len+=4}else if(u<=67108863){len+=5}else{len+=6}}return len}Module["lengthBytesUTF8"]=lengthBytesUTF8;function UTF16ToString(ptr){var i=0;var str="";while(1){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)return str;++i;str+=String.fromCharCode(codeUnit)}}Module["UTF16ToString"]=UTF16ToString;function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite<str.length*2?maxBytesToWrite/2:str.length;for(var i=0;i<numCharsToWrite;++i){var codeUnit=str.charCodeAt(i);HEAP16[outPtr>>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}Module["stringToUTF16"]=stringToUTF16;function lengthBytesUTF16(str){return str.length*2}Module["lengthBytesUTF16"]=lengthBytesUTF16;function UTF32ToString(ptr){var i=0;var str="";while(1){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)return str;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}}Module["UTF32ToString"]=UTF32ToString;function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i<str.length;++i){var codeUnit=str.charCodeAt(i);if(codeUnit>=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}Module["stringToUTF32"]=stringToUTF32;function lengthBytesUTF32(str){var len=0;for(var i=0;i<str.length;++i){var codeUnit=str.charCodeAt(i);if(codeUnit>=55296&&codeUnit<=57343)++i;len+=4}return len}Module["lengthBytesUTF32"]=lengthBytesUTF32;function demangle(func){var hasLibcxxabi=!!Module["___cxa_demangle"];if(hasLibcxxabi){try{var buf=_malloc(func.length);writeStringToMemory(func.substr(1),buf);var status=_malloc(4);var ret=Module["___cxa_demangle"](buf,0,0,status);if(getValue(status,"i32")===0&&ret){return Pointer_stringify(ret)}}catch(e){}finally{if(buf)_free(buf);if(status)_free(status);if(ret)_free(ret)}}var i=3;var basicTypes={"v":"void","b":"bool","c":"char","s":"short","i":"int","l":"long","f":"float","d":"double","w":"wchar_t","a":"signed char","h":"unsigned char","t":"unsigned short","j":"unsigned int","m":"unsigned long","x":"long long","y":"unsigned long long","z":"..."};var subs=[];var first=true;function dump(x){if(x)Module.print(x);Module.print(func);var pre="";for(var a=0;a<i;a++)pre+=" ";Module.print(pre+"^")}function parseNested(){i++;if(func[i]==="K")i++;var parts=[];while(func[i]!=="E"){if(func[i]==="S"){i++;var next=func.indexOf("_",i);var num=func.substring(i,next)||0;parts.push(subs[num]||"?");i=next+1;continue}if(func[i]==="C"){parts.push(parts[parts.length-1]);i+=2;continue}var size=parseInt(func.substr(i));var pre=size.toString().length;if(!size||!pre){i--;break}var curr=func.substr(i+pre,size);parts.push(curr);subs.push(curr);i+=pre+size}i++;return parts}function parse(rawList,limit,allowVoid){limit=limit||Infinity;var ret="",list=[];function flushList(){return"("+list.join(", ")+")"}var name;if(func[i]==="N"){name=parseNested().join("::");limit--;if(limit===0)return rawList?[name]:name}else{if(func[i]==="K"||first&&func[i]==="L")i++;var size=parseInt(func.substr(i));if(size){var pre=size.toString().length;name=func.substr(i+pre,size);i+=pre+size}}first=false;if(func[i]==="I"){i++;var iList=parse(true);var iRet=parse(true,1,true);ret+=iRet[0]+" "+name+"<"+iList.join(", ")+">"}else{ret=name}paramLoop:while(i<func.length&&limit-->0){var c=func[i++];if(c in basicTypes){list.push(basicTypes[c])}else{switch(c){case"P":list.push(parse(true,1,true)[0]+"*");break;case"R":list.push(parse(true,1,true)[0]+"&");break;case"L":{i++;var end=func.indexOf("E",i);var size=end-i;list.push(func.substr(i,size));i+=size+2;break};case"A":{var size=parseInt(func.substr(i));i+=size.toString().length;if(func[i]!=="_")throw"?";i++;list.push(parse(true,1,true)[0]+" ["+size+"]");break};case"E":break paramLoop;default:ret+="?"+c;break paramLoop}}}if(!allowVoid&&list.length===1&&list[0]==="void")list=[];if(rawList){if(ret){list.push(ret+"?")}return list}else{return ret+flushList()}}var parsed=func;try{if(func=="Object._main"||func=="_main"){return"main()"}if(typeof func==="number")func=Pointer_stringify(func);if(func[0]!=="_")return func;if(func[1]!=="_")return func;if(func[2]!=="Z")return func;switch(func[3]){case"n":return"operator new()";case"d":return"operator delete()"}parsed=parse()}catch(e){parsed+="?"}if(parsed.indexOf("?")>=0&&!hasLibcxxabi){Runtime.warnOnce("warning: a problem occurred in builtin C++ name demangling; build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling")}return parsed}function demangleAll(text){return text.replace(/__Z[\w\d_]+/g,(function(x){var y=demangle(x);return x===y?x:x+" ["+y+"]"}))}function jsStackTrace(){var err=new Error;if(!err.stack){try{throw new Error(0)}catch(e){err=e}if(!err.stack){return"(no stack trace available)"}}return err.stack.toString()}function stackTrace(){return demangleAll(jsStackTrace())}Module["stackTrace"]=stackTrace;var PAGE_SIZE=4096;function alignMemoryPage(x){if(x%4096>0){x+=4096-x%4096}return x}var HEAP;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;var STATIC_BASE=0,STATICTOP=0,staticSealed=false;var STACK_BASE=0,STACKTOP=0,STACK_MAX=0;var DYNAMIC_BASE=0,DYNAMICTOP=0;function enlargeMemory(){var OLD_TOTAL_MEMORY=TOTAL_MEMORY;var LIMIT=Math.pow(2,31);if(DYNAMICTOP>=LIMIT)return false;while(TOTAL_MEMORY<=DYNAMICTOP){if(TOTAL_MEMORY<LIMIT/2){TOTAL_MEMORY=alignMemoryPage(2*TOTAL_MEMORY)}else{var last=TOTAL_MEMORY;TOTAL_MEMORY=alignMemoryPage((3*TOTAL_MEMORY+LIMIT)/4);if(TOTAL_MEMORY<=last)return false}}TOTAL_MEMORY=Math.max(TOTAL_MEMORY,16*1024*1024);if(TOTAL_MEMORY>=LIMIT)return false;try{if(ArrayBuffer.transfer){buffer=ArrayBuffer.transfer(buffer,TOTAL_MEMORY)}else{var oldHEAP8=HEAP8;buffer=new ArrayBuffer(TOTAL_MEMORY)}}catch(e){return false}var success=_emscripten_replace_memory(buffer);if(!success)return false;Module["buffer"]=buffer;Module["HEAP8"]=HEAP8=new Int8Array(buffer);Module["HEAP16"]=HEAP16=new Int16Array(buffer);Module["HEAP32"]=HEAP32=new Int32Array(buffer);Module["HEAPU8"]=HEAPU8=new Uint8Array(buffer);Module["HEAPU16"]=HEAPU16=new Uint16Array(buffer);Module["HEAPU32"]=HEAPU32=new Uint32Array(buffer);Module["HEAPF32"]=HEAPF32=new Float32Array(buffer);Module["HEAPF64"]=HEAPF64=new Float64Array(buffer);if(!ArrayBuffer.transfer){HEAP8.set(oldHEAP8)}return true}var byteLength;try{byteLength=Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype,"byteLength").get);byteLength(new ArrayBuffer(4))}catch(e){byteLength=(function(buffer){return buffer.byteLength})}var TOTAL_STACK=Module["TOTAL_STACK"]||5242880;var TOTAL_MEMORY=Module["TOTAL_MEMORY"]||16777216;var totalMemory=64*1024;while(totalMemory<TOTAL_MEMORY||totalMemory<2*TOTAL_STACK){if(totalMemory<16*1024*1024){totalMemory*=2}else{totalMemory+=16*1024*1024}}totalMemory=Math.max(totalMemory,16*1024*1024);if(totalMemory!==TOTAL_MEMORY){TOTAL_MEMORY=totalMemory}assert(typeof Int32Array!=="undefined"&&typeof Float64Array!=="undefined"&&!!(new Int32Array(1))["subarray"]&&!!(new Int32Array(1))["set"],"JS engine does not provide full typed array support");var buffer;buffer=new ArrayBuffer(TOTAL_MEMORY);HEAP8=new Int8Array(buffer);HEAP16=new Int16Array(buffer);HEAP32=new Int32Array(buffer);HEAPU8=new Uint8Array(buffer);HEAPU16=new Uint16Array(buffer);HEAPU32=new Uint32Array(buffer);HEAPF32=new Float32Array(buffer);HEAPF64=new Float64Array(buffer);HEAP32[0]=255;assert(HEAPU8[0]===255&&HEAPU8[3]===0,"Typed arrays 2 must be run on a little-endian system");Module["HEAP"]=HEAP;Module["buffer"]=buffer;Module["HEAP8"]=HEAP8;Module["HEAP16"]=HEAP16;Module["HEAP32"]=HEAP32;Module["HEAPU8"]=HEAPU8;Module["HEAPU16"]=HEAPU16;Module["HEAPU32"]=HEAPU32;Module["HEAPF32"]=HEAPF32;Module["HEAPF64"]=HEAPF64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Runtime.dynCall("v",func)}else{Runtime.dynCall("vi",func,[callback.arg])}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){if(runtimeInitialized)return;runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__);runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}Module["addOnPreRun"]=addOnPreRun;function addOnInit(cb){__ATINIT__.unshift(cb)}Module["addOnInit"]=addOnInit;function addOnPreMain(cb){__ATMAIN__.unshift(cb)}Module["addOnPreMain"]=addOnPreMain;function addOnExit(cb){__ATEXIT__.unshift(cb)}Module["addOnExit"]=addOnExit;function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}Module["addOnPostRun"]=addOnPostRun;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}Module["intArrayFromString"]=intArrayFromString;function intArrayToString(array){var ret=[];for(var i=0;i<array.length;i++){var chr=array[i];if(chr>255){chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}Module["intArrayToString"]=intArrayToString;function writeStringToMemory(string,buffer,dontAddNull){var array=intArrayFromString(string,dontAddNull);var i=0;while(i<array.length){var chr=array[i];HEAP8[buffer+i>>0]=chr;i=i+1}}Module["writeStringToMemory"]=writeStringToMemory;function writeArrayToMemory(array,buffer){for(var i=0;i<array.length;i++){HEAP8[buffer++>>0]=array[i]}}Module["writeArrayToMemory"]=writeArrayToMemory;function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i<str.length;++i){HEAP8[buffer++>>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}Module["writeAsciiToMemory"]=writeAsciiToMemory;function unSign(value,bits,ignore){if(value>=0){return value}return bits<=32?2*Math.abs(1<<bits-1)+value:Math.pow(2,bits)+value}function reSign(value,bits,ignore){if(value<=0){return value}var half=bits<=32?Math.abs(1<<bits-1):Math.pow(2,bits-1);if(value>=half&&(bits<=32||value>half)){value=-2*half+value}return value}if(!Math["imul"]||Math["imul"](4294967295,5)!==-5)Math["imul"]=function imul(a,b){var ah=a>>>16;var al=a&65535;var bh=b>>>16;var bl=b&65535;return al*bl+(ah*bl+al*bh<<16)|0};Math.imul=Math["imul"];if(!Math["clz32"])Math["clz32"]=(function(x){x=x>>>0;for(var i=0;i<32;i++){if(x&1<<31-i)return i}return 32});Math.clz32=Math["clz32"];var Math_abs=Math.abs;var Math_cos=Math.cos;var Math_sin=Math.sin;var Math_tan=Math.tan;var Math_acos=Math.acos;var Math_asin=Math.asin;var Math_atan=Math.atan;var Math_atan2=Math.atan2;var Math_exp=Math.exp;var Math_log=Math.log;var Math_sqrt=Math.sqrt;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_pow=Math.pow;var Math_imul=Math.imul;var Math_fround=Math.fround;var Math_min=Math.min;var Math_clz32=Math.clz32;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}Module["addRunDependency"]=addRunDependency;function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["removeRunDependency"]=removeRunDependency;Module["preloadedImages"]={};Module["preloadedAudios"]={};var memoryInitializer=null;var ASM_CONSTS=[];STATIC_BASE=8;STATICTOP=STATIC_BASE+5888;__ATINIT__.push();allocate([116,0,0,0,86,7,0,0,116,0,0,0,99,7,0,0,156,0,0,0,112,7,0,0,16,0,0,0,0,0,0,0,156,0,0,0,145,7,0,0,24,0,0,0,0,0,0,0,156,0,0,0,215,7,0,0,24,0,0,0,0,0,0,0,156,0,0,0,179,7,0,0,56,0,0,0,0,0,0,0,156,0,0,0,249,7,0,0,40,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,40,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,4,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,88,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,4,0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,108,1,0,0,220,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,4,0,0,0,227,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,4,0,0,0,219,16,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,115,40,37,117,41,58,32,65,115,115,101,114,116,105,111,110,32,102,97,105,108,117,114,101,58,32,34,37,115,34,10,0,109,95,115,105,122,101,32,60,61,32,109,95,99,97,112,97,99,105,116,121,0,46,47,105,110,99,92,99,114,110,95,100,101,99,111,109,112,46,104,0,109,105,110,95,110,101,119,95,99,97,112,97,99,105,116,121,32,60,32,40,48,120,55,70,70,70,48,48,48,48,85,32,47,32,101,108,101,109,101,110,116,95,115,105,122,101,41,0,110,101,119,95,99,97,112,97,99,105,116,121,32,38,38,32,40,110,101,119,95,99,97,112,97,99,105,116,121,32,62,32,109,95,99,97,112,97,99,105,116,121,41,0,110,117,109,95,99,111,100,101,115,91,99,93,0,115,111,114,116,101,100,95,112,111,115,32,60,32,116,111,116,97,108,95,117,115,101,100,95,115,121,109,115,0,112,67,111,100,101,115,105,122,101,115,91,115,121,109,95,105,110,100,101,120,93,32,61,61,32,99,111,100,101,115,105,122,101,0,116,32,60,32,40,49,85,32,60,60,32,116,97,98,108,101,95,98,105,116,115,41,0,109,95,108,111,111,107,117,112,91,116,93,32,61,61,32,99,85,73,78,84,51,50,95,77,65,88,0,99,114,110,100,95,109,97,108,108,111,99,58,32,115,105,122,101,32,116,111,111,32,98,105,103,0,99,114,110,100,95,109,97,108,108,111,99,58,32,111,117,116,32,111,102,32,109,101,109,111,114,121,0,40,40,117,105,110,116,51,50,41,112,95,110,101,119,32,38,32,40,67,82,78,68,95,77,73,78,95,65,76,76,79,67,95,65,76,73,71,78,77,69,78,84,32,45,32,49,41,41,32,61,61,32,48,0,99,114,110,100,95,114,101,97,108,108,111,99,58,32,98,97,100,32,112,116,114,0,99,114,110,100,95,102,114,101,101,58,32,98,97,100,32,112,116,114,0,102,97,108,115,101,0,40,116,111,116,97,108,95,115,121,109,115,32,62,61,32,49,41,32,38,38,32,40,116,111,116,97,108,95,115,121,109,115,32,60,61,32,112,114,101,102,105,120,95,99,111,100,105,110,103,58,58,99,77,97,120,83,117,112,112,111,114,116,101,100,83,121,109,115,41,0,17,18,19,20,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15,16,48,0,110,117,109,95,98,105,116,115,32,60,61,32,51,50,85,0,109,95,98,105,116,95,99,111,117,110,116,32,60,61,32,99,66,105,116,66,117,102,83,105,122,101,0,116,32,33,61,32,99,85,73,78,84,51,50,95,77,65,88,0,109,111,100,101,108,46,109,95,99,111,100,101,95,115,105,122,101,115,91,115,121,109,93,32,61,61,32,108,101,110,0,0,2,3,1,0,2,3,4,5,6,7,1,40,108,101,110,32,62,61,32,49,41,32,38,38,32,40,108,101,110,32,60,61,32,99,77,97,120,69,120,112,101,99,116,101,100,67,111,100,101,83,105,122,101,41,0,105,32,60,32,109,95,115,105,122,101,0,110,101,120,116,95,108,101,118,101,108,95,111,102,115,32,62,32,99,117,114,95,108,101,118,101,108,95,111,102,115,0,1,2,2,3,3,3,3,4,0,0,0,0,0,0,1,1,0,1,0,1,0,0,1,2,1,2,0,0,0,1,0,2,1,0,2,0,0,1,2,3,110,117,109,32,38,38,32,40,110,117,109,32,61,61,32,126,110,117,109,95,99,104,101,99,107,41,0,83,116,57,101,120,99,101,112,116,105,111,110,0,83,116,57,116,121,112,101,95,105,110,102,111,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,54,95,95,115,104,105,109,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,55,95,95,99,108,97,115,115,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,57,95,95,112,111,105,110,116,101,114,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,49,55,95,95,112,98,97,115,101,95,116,121,112,101,95,105,110,102,111,69,0,78,49,48,95,95,99,120,120,97,98,105,118,49,50,48,95,95,115,105,95,99,108,97,115,115,95,116,121,112,101,95,105,110,102,111,69,0,112,116,104,114,101,97,100,95,111,110,99,101,32,102,97,105,108,117,114,101,32,105,110,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,95,102,97,115,116,40,41,0,116,101,114,109,105,110,97,116,101,95,104,97,110,100,108,101,114,32,117,110,101,120,112,101,99,116,101,100,108,121,32,114,101,116,117,114,110,101,100,0,99,97,110,110,111,116,32,99,114,101,97,116,101,32,112,116,104,114,101,97,100,32,107,101,121,32,102,111,114,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,40,41,0,99,97,110,110,111,116,32,122,101,114,111,32,111,117,116,32,116,104,114,101,97,100,32,118,97,108,117,101,32,102,111,114,32,95,95,99,120,97,95,103,101,116,95,103,108,111,98,97,108,115,40,41,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,101,120,99,101,112,116,105,111,110,32,111,102,32,116,121,112,101,32,37,115,58,32,37,115,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,101,120,99,101,112,116,105,111,110,32,111,102,32,116,121,112,101,32,37,115,0,116,101,114,109,105,110,97,116,105,110,103,32,119,105,116,104,32,37,115,32,102,111,114,101,105,103,110,32,101,120,99,101,112,116,105,111,110,0,116,101,114,109,105,110,97,116,105,110,103,0,117,110,99,97,117,103,104,116,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,46,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=Runtime.alignMemory(allocate(12,"i8",ALLOC_STATIC),8);assert(tempDoublePtr%8==0);function copyTempFloat(ptr){HEAP8[tempDoublePtr]=HEAP8[ptr];HEAP8[tempDoublePtr+1]=HEAP8[ptr+1];HEAP8[tempDoublePtr+2]=HEAP8[ptr+2];HEAP8[tempDoublePtr+3]=HEAP8[ptr+3]}function copyTempDouble(ptr){HEAP8[tempDoublePtr]=HEAP8[ptr];HEAP8[tempDoublePtr+1]=HEAP8[ptr+1];HEAP8[tempDoublePtr+2]=HEAP8[ptr+2];HEAP8[tempDoublePtr+3]=HEAP8[ptr+3];HEAP8[tempDoublePtr+4]=HEAP8[ptr+4];HEAP8[tempDoublePtr+5]=HEAP8[ptr+5];HEAP8[tempDoublePtr+6]=HEAP8[ptr+6];HEAP8[tempDoublePtr+7]=HEAP8[ptr+7]}var _BDtoIHigh=true;Module["_i64Subtract"]=_i64Subtract;Module["_i64Add"]=_i64Add;function _pthread_cleanup_push(routine,arg){__ATEXIT__.push((function(){Runtime.dynCall("vi",routine,[arg])}));_pthread_cleanup_push.level=__ATEXIT__.length}Module["_memset"]=_memset;var _BDtoILow=true;Module["_bitshift64Lshr"]=_bitshift64Lshr;Module["_bitshift64Shl"]=_bitshift64Shl;function _pthread_cleanup_pop(){assert(_pthread_cleanup_push.level==__ATEXIT__.length,"cannot pop if something else added meanwhile!");__ATEXIT__.pop();_pthread_cleanup_push.level=__ATEXIT__.length}function _abort(){Module["abort"]()}function __ZSt18uncaught_exceptionv(){return!!__ZSt18uncaught_exceptionv.uncaught_exception}var EXCEPTIONS={last:0,caught:[],infos:{},deAdjust:(function(adjusted){if(!adjusted||EXCEPTIONS.infos[adjusted])return adjusted;for(var ptr in EXCEPTIONS.infos){var info=EXCEPTIONS.infos[ptr];if(info.adjusted===adjusted){return ptr}}return adjusted}),addRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];info.refcount++}),decRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];assert(info.refcount>0);info.refcount--;if(info.refcount===0){if(info.destructor){Runtime.dynCall("vi",info.destructor,[ptr])}delete EXCEPTIONS.infos[ptr];___cxa_free_exception(ptr)}}),clearRef:(function(ptr){if(!ptr)return;var info=EXCEPTIONS.infos[ptr];info.refcount=0})};function ___cxa_begin_catch(ptr){__ZSt18uncaught_exceptionv.uncaught_exception--;EXCEPTIONS.caught.push(ptr);EXCEPTIONS.addRef(EXCEPTIONS.deAdjust(ptr));return ptr}function _pthread_once(ptr,func){if(!_pthread_once.seen)_pthread_once.seen={};if(ptr in _pthread_once.seen)return;Runtime.dynCall("v",func);_pthread_once.seen[ptr]=1}function _emscripten_memcpy_big(dest,src,num){HEAPU8.set(HEAPU8.subarray(src,src+num),dest);return dest}Module["_memcpy"]=_memcpy;var SYSCALLS={varargs:0,get:(function(varargs){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret}),getStr:(function(){var ret=Pointer_stringify(SYSCALLS.get());return ret}),get64:(function(){var low=SYSCALLS.get(),high=SYSCALLS.get();if(low>=0)assert(high===0);else assert(high===-1);return low}),getZero:(function(){assert(SYSCALLS.get()===0)})};function ___syscall6(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD();FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var PTHREAD_SPECIFIC={};function _pthread_getspecific(key){return PTHREAD_SPECIFIC[key]||0}function ___setErrNo(value){if(Module["___errno_location"])HEAP32[Module["___errno_location"]()>>2]=value;return value}var ERRNO_CODES={EPERM:1,ENOENT:2,ESRCH:3,EINTR:4,EIO:5,ENXIO:6,E2BIG:7,ENOEXEC:8,EBADF:9,ECHILD:10,EAGAIN:11,EWOULDBLOCK:11,ENOMEM:12,EACCES:13,EFAULT:14,ENOTBLK:15,EBUSY:16,EEXIST:17,EXDEV:18,ENODEV:19,ENOTDIR:20,EISDIR:21,EINVAL:22,ENFILE:23,EMFILE:24,ENOTTY:25,ETXTBSY:26,EFBIG:27,ENOSPC:28,ESPIPE:29,EROFS:30,EMLINK:31,EPIPE:32,EDOM:33,ERANGE:34,ENOMSG:42,EIDRM:43,ECHRNG:44,EL2NSYNC:45,EL3HLT:46,EL3RST:47,ELNRNG:48,EUNATCH:49,ENOCSI:50,EL2HLT:51,EDEADLK:35,ENOLCK:37,EBADE:52,EBADR:53,EXFULL:54,ENOANO:55,EBADRQC:56,EBADSLT:57,EDEADLOCK:35,EBFONT:59,ENOSTR:60,ENODATA:61,ETIME:62,ENOSR:63,ENONET:64,ENOPKG:65,EREMOTE:66,ENOLINK:67,EADV:68,ESRMNT:69,ECOMM:70,EPROTO:71,EMULTIHOP:72,EDOTDOT:73,EBADMSG:74,ENOTUNIQ:76,EBADFD:77,EREMCHG:78,ELIBACC:79,ELIBBAD:80,ELIBSCN:81,ELIBMAX:82,ELIBEXEC:83,ENOSYS:38,ENOTEMPTY:39,ENAMETOOLONG:36,ELOOP:40,EOPNOTSUPP:95,EPFNOSUPPORT:96,ECONNRESET:104,ENOBUFS:105,EAFNOSUPPORT:97,EPROTOTYPE:91,ENOTSOCK:88,ENOPROTOOPT:92,ESHUTDOWN:108,ECONNREFUSED:111,EADDRINUSE:98,ECONNABORTED:103,ENETUNREACH:101,ENETDOWN:100,ETIMEDOUT:110,EHOSTDOWN:112,EHOSTUNREACH:113,EINPROGRESS:115,EALREADY:114,EDESTADDRREQ:89,EMSGSIZE:90,EPROTONOSUPPORT:93,ESOCKTNOSUPPORT:94,EADDRNOTAVAIL:99,ENETRESET:102,EISCONN:106,ENOTCONN:107,ETOOMANYREFS:109,EUSERS:87,EDQUOT:122,ESTALE:116,ENOTSUP:95,ENOMEDIUM:123,EILSEQ:84,EOVERFLOW:75,ECANCELED:125,ENOTRECOVERABLE:131,EOWNERDEAD:130,ESTRPIPE:86};function _sysconf(name){switch(name){case 30:return PAGE_SIZE;case 85:return totalMemory/PAGE_SIZE;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1e3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:{if(typeof navigator==="object")return navigator["hardwareConcurrency"]||1;return 1}}___setErrNo(ERRNO_CODES.EINVAL);return-1}function _sbrk(bytes){var self=_sbrk;if(!self.called){DYNAMICTOP=alignMemoryPage(DYNAMICTOP);self.called=true;assert(Runtime.dynamicAlloc);self.alloc=Runtime.dynamicAlloc;Runtime.dynamicAlloc=(function(){abort("cannot dynamically allocate, sbrk now has control")})}var ret=DYNAMICTOP;if(bytes!=0){var success=self.alloc(bytes);if(!success)return-1>>>0}return ret}var PTHREAD_SPECIFIC_NEXT_KEY=1;function _pthread_key_create(key,destructor){if(key==0){return ERRNO_CODES.EINVAL}HEAP32[key>>2]=PTHREAD_SPECIFIC_NEXT_KEY;PTHREAD_SPECIFIC[PTHREAD_SPECIFIC_NEXT_KEY]=0;PTHREAD_SPECIFIC_NEXT_KEY++;return 0}var _BItoD=true;var PATH=undefined;function _emscripten_set_main_loop_timing(mode,value){Browser.mainLoop.timingMode=mode;Browser.mainLoop.timingValue=value;if(!Browser.mainLoop.func){return 1}if(mode==0){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setTimeout(){setTimeout(Browser.mainLoop.runner,value)};Browser.mainLoop.method="timeout"}else if(mode==1){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_rAF(){Browser.requestAnimationFrame(Browser.mainLoop.runner)};Browser.mainLoop.method="rAF"}else if(mode==2){if(!window["setImmediate"]){var setImmediates=[];var emscriptenMainLoopMessageId="__emcc";function Browser_setImmediate_messageHandler(event){if(event.source===window&&event.data===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}}window.addEventListener("message",Browser_setImmediate_messageHandler,true);window["setImmediate"]=function Browser_emulated_setImmediate(func){setImmediates.push(func);window.postMessage(emscriptenMainLoopMessageId,"*")}}Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setImmediate(){window["setImmediate"](Browser.mainLoop.runner)};Browser.mainLoop.method="immediate"}return 0}function _emscripten_set_main_loop(func,fps,simulateInfiniteLoop,arg,noSetTiming){Module["noExitRuntime"]=true;assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func=func;Browser.mainLoop.arg=arg;var thisMainLoopId=Browser.mainLoop.currentlyRunningMainloop;Browser.mainLoop.runner=function Browser_mainLoop_runner(){if(ABORT)return;if(Browser.mainLoop.queue.length>0){var start=Date.now();var blocker=Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if(Browser.mainLoop.remainingBlockers){var remaining=Browser.mainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){Browser.mainLoop.remainingBlockers=next}else{next=next+.5;Browser.mainLoop.remainingBlockers=(8*remaining+next)/9}}console.log('main loop blocker "'+blocker.name+'" took '+(Date.now()-start)+" ms");Browser.mainLoop.updateStatus();setTimeout(Browser.mainLoop.runner,0);return}if(thisMainLoopId<Browser.mainLoop.currentlyRunningMainloop)return;Browser.mainLoop.currentFrameNumber=Browser.mainLoop.currentFrameNumber+1|0;if(Browser.mainLoop.timingMode==1&&Browser.mainLoop.timingValue>1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}if(Browser.mainLoop.method==="timeout"&&Module.ctx){Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!");Browser.mainLoop.method=""}Browser.mainLoop.runIter((function(){if(typeof arg!=="undefined"){Runtime.dynCall("vi",func,[arg])}else{Runtime.dynCall("v",func)}}));if(thisMainLoopId<Browser.mainLoop.currentlyRunningMainloop)return;if(typeof SDL==="object"&&SDL.audio&&SDL.audio.queueNewAudioData)SDL.audio.queueNewAudioData();Browser.mainLoop.scheduler()};if(!noSetTiming){if(fps&&fps>0)_emscripten_set_main_loop_timing(0,1e3/fps);else _emscripten_set_main_loop_timing(1,1);Browser.mainLoop.scheduler()}if(simulateInfiniteLoop){throw"SimulateInfiniteLoop"}}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:(function(){Browser.mainLoop.scheduler=null;Browser.mainLoop.currentlyRunningMainloop++}),resume:(function(){Browser.mainLoop.currentlyRunningMainloop++;var timingMode=Browser.mainLoop.timingMode;var timingValue=Browser.mainLoop.timingValue;var func=Browser.mainLoop.func;Browser.mainLoop.func=null;_emscripten_set_main_loop(func,0,false,Browser.mainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);Browser.mainLoop.scheduler()}),updateStatus:(function(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=Browser.mainLoop.remainingBlockers;var expected=Browser.mainLoop.expectedBlockers;if(remaining){if(remaining<expected){Module["setStatus"](message+" ("+(expected-remaining)+"/"+expected+")")}else{Module["setStatus"](message)}}else{Module["setStatus"]("")}}}),runIter:(function(func){if(ABORT)return;if(Module["preMainLoop"]){var preRet=Module["preMainLoop"]();if(preRet===false){return}}try{func()}catch(e){if(e instanceof ExitStatus){return}else{if(e&&typeof e==="object"&&e.stack)Module.printErr("exception thrown: "+[e,e.stack]);throw e}}if(Module["postMainLoop"])Module["postMainLoop"]()})},isFullScreen:false,pointerLock:false,moduleContextCreatedCallbacks:[],workers:[],init:(function(){if(!Module["preloadPlugins"])Module["preloadPlugins"]=[];if(Browser.initted)return;Browser.initted=true;try{new Blob;Browser.hasBlobConstructor=true}catch(e){Browser.hasBlobConstructor=false;console.log("warning: no blob constructor, cannot create blobs with mimetypes")}Browser.BlobBuilder=typeof MozBlobBuilder!="undefined"?MozBlobBuilder:typeof WebKitBlobBuilder!="undefined"?WebKitBlobBuilder:!Browser.hasBlobConstructor?console.log("warning: no BlobBuilder"):null;Browser.URLObject=typeof window!="undefined"?window.URL?window.URL:window.webkitURL:undefined;if(!Module.noImageDecoding&&typeof Browser.URLObject==="undefined"){console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.");Module.noImageDecoding=true}var imagePlugin={};imagePlugin["canHandle"]=function imagePlugin_canHandle(name){return!Module.noImageDecoding&&/\.(jpg|jpeg|png|bmp)$/i.test(name)};imagePlugin["handle"]=function imagePlugin_handle(byteArray,name,onload,onerror){var b=null;if(Browser.hasBlobConstructor){try{b=new Blob([byteArray],{type:Browser.getMimetype(name)});if(b.size!==byteArray.length){b=new Blob([(new Uint8Array(byteArray)).buffer],{type:Browser.getMimetype(name)})}}catch(e){Runtime.warnOnce("Blob constructor present but fails: "+e+"; falling back to blob builder")}}if(!b){var bb=new Browser.BlobBuilder;bb.append((new Uint8Array(byteArray)).buffer);b=bb.getBlob()}var url=Browser.URLObject.createObjectURL(b);var img=new Image;img.onload=function img_onload(){assert(img.complete,"Image "+name+" could not be decoded");var canvas=document.createElement("canvas");canvas.width=img.width;canvas.height=img.height;var ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);Module["preloadedImages"][name]=canvas;Browser.URLObject.revokeObjectURL(url);if(onload)onload(byteArray)};img.onerror=function img_onerror(event){console.log("Image "+url+" could not be decoded");if(onerror)onerror()};img.src=url};Module["preloadPlugins"].push(imagePlugin);var audioPlugin={};audioPlugin["canHandle"]=function audioPlugin_canHandle(name){return!Module.noAudioDecoding&&name.substr(-4)in{".ogg":1,".wav":1,".mp3":1}};audioPlugin["handle"]=function audioPlugin_handle(byteArray,name,onload,onerror){var done=false;function finish(audio){if(done)return;done=true;Module["preloadedAudios"][name]=audio;if(onload)onload(byteArray)}function fail(){if(done)return;done=true;Module["preloadedAudios"][name]=new Audio;if(onerror)onerror()}if(Browser.hasBlobConstructor){try{var b=new Blob([byteArray],{type:Browser.getMimetype(name)})}catch(e){return fail()}var url=Browser.URLObject.createObjectURL(b);var audio=new Audio;audio.addEventListener("canplaythrough",(function(){finish(audio)}),false);audio.onerror=function audio_onerror(event){if(done)return;console.log("warning: browser could not fully decode audio "+name+", trying slower base64 approach");function encode64(data){var BASE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD="=";var ret="";var leftchar=0;var leftbits=0;for(var i=0;i<data.length;i++){leftchar=leftchar<<8|data[i];leftbits+=8;while(leftbits>=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.substr(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;Browser.safeSetTimeout((function(){finish(audio)}),1e4)}else{return fail()}};Module["preloadPlugins"].push(audioPlugin);var canvas=Module["canvas"];function pointerLockChange(){Browser.pointerLock=document["pointerLockElement"]===canvas||document["mozPointerLockElement"]===canvas||document["webkitPointerLockElement"]===canvas||document["msPointerLockElement"]===canvas}if(canvas){canvas.requestPointerLock=canvas["requestPointerLock"]||canvas["mozRequestPointerLock"]||canvas["webkitRequestPointerLock"]||canvas["msRequestPointerLock"]||(function(){});canvas.exitPointerLock=document["exitPointerLock"]||document["mozExitPointerLock"]||document["webkitExitPointerLock"]||document["msExitPointerLock"]||(function(){});canvas.exitPointerLock=canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange",pointerLockChange,false);document.addEventListener("mozpointerlockchange",pointerLockChange,false);document.addEventListener("webkitpointerlockchange",pointerLockChange,false);document.addEventListener("mspointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",(function(ev){if(!Browser.pointerLock&&canvas.requestPointerLock){canvas.requestPointerLock();ev.preventDefault()}}),false)}}}),createContext:(function(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module.ctx&&canvas==Module.canvas)return Module.ctx;var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}canvas.style.backgroundColor="black"}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){if(!useWebGL)assert(typeof GLctx==="undefined","cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Module.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach((function(callback){callback()}));Browser.init()}return ctx}),destroyContext:(function(canvas,useWebGL,setInModule){}),fullScreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullScreen:(function(lockPointer,resizeCanvas,vrDevice){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;Browser.vrDevice=vrDevice;if(typeof Browser.lockPointer==="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas==="undefined")Browser.resizeCanvas=false;if(typeof Browser.vrDevice==="undefined")Browser.vrDevice=null;var canvas=Module["canvas"];function fullScreenChange(){Browser.isFullScreen=false;var canvasContainer=canvas.parentNode;if((document["webkitFullScreenElement"]||document["webkitFullscreenElement"]||document["mozFullScreenElement"]||document["mozFullscreenElement"]||document["fullScreenElement"]||document["fullscreenElement"]||document["msFullScreenElement"]||document["msFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvasContainer){canvas.cancelFullScreen=document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["webkitCancelFullScreen"]||document["msExitFullscreen"]||document["exitFullscreen"]||(function(){});canvas.cancelFullScreen=canvas.cancelFullScreen.bind(document);if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullScreen=true;if(Browser.resizeCanvas)Browser.setFullScreenCanvasSize()}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas)Browser.setWindowedCanvasSize()}if(Module["onFullScreen"])Module["onFullScreen"](Browser.isFullScreen);Browser.updateCanvasDimensions(canvas)}if(!Browser.fullScreenHandlersInstalled){Browser.fullScreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullScreenChange,false);document.addEventListener("mozfullscreenchange",fullScreenChange,false);document.addEventListener("webkitfullscreenchange",fullScreenChange,false);document.addEventListener("MSFullscreenChange",fullScreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullScreen=canvasContainer["requestFullScreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullScreen"]?(function(){canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"])}):null);if(vrDevice){canvasContainer.requestFullScreen({vrDisplay:vrDevice})}else{canvasContainer.requestFullScreen()}}),nextRAF:0,fakeRequestAnimationFrame:(function(func){var now=Date.now();if(Browser.nextRAF===0){Browser.nextRAF=now+1e3/60}else{while(now+2>=Browser.nextRAF){Browser.nextRAF+=1e3/60}}var delay=Math.max(Browser.nextRAF-now,0);setTimeout(func,delay)}),requestAnimationFrame:function requestAnimationFrame(func){if(typeof window==="undefined"){Browser.fakeRequestAnimationFrame(func)}else{if(!window.requestAnimationFrame){window.requestAnimationFrame=window["requestAnimationFrame"]||window["mozRequestAnimationFrame"]||window["webkitRequestAnimationFrame"]||window["msRequestAnimationFrame"]||window["oRequestAnimationFrame"]||Browser.fakeRequestAnimationFrame}window.requestAnimationFrame(func)}},safeCallback:(function(func){return(function(){if(!ABORT)return func.apply(null,arguments)})}),allowAsyncCallbacks:true,queuedAsyncCallbacks:[],pauseAsyncCallbacks:(function(){Browser.allowAsyncCallbacks=false}),resumeAsyncCallbacks:(function(){Browser.allowAsyncCallbacks=true;if(Browser.queuedAsyncCallbacks.length>0){var callbacks=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[];callbacks.forEach((function(func){func()}))}}),safeRequestAnimationFrame:(function(func){return Browser.requestAnimationFrame((function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}else{Browser.queuedAsyncCallbacks.push(func)}}))}),safeSetTimeout:(function(func,timeout){Module["noExitRuntime"]=true;return setTimeout((function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}else{Browser.queuedAsyncCallbacks.push(func)}}),timeout)}),safeSetInterval:(function(func,timeout){Module["noExitRuntime"]=true;return setInterval((function(){if(ABORT)return;if(Browser.allowAsyncCallbacks){func()}}),timeout)}),getMimetype:(function(name){return{"jpg":"image/jpeg","jpeg":"image/jpeg","png":"image/png","bmp":"image/bmp","ogg":"audio/ogg","wav":"audio/wav","mp3":"audio/mpeg"}[name.substr(name.lastIndexOf(".")+1)]}),getUserMedia:(function(func){if(!window.getUserMedia){window.getUserMedia=navigator["getUserMedia"]||navigator["mozGetUserMedia"]}window.getUserMedia(func)}),getMovementX:(function(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0}),getMovementY:(function(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0}),getMouseWheelDelta:(function(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail;break;case"mousewheel":delta=event.wheelDelta;break;case"wheel":delta=event["deltaY"];break;default:throw"unrecognized mouse wheel event: "+event.type}return delta}),mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:(function(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}if(typeof SDL!="undefined"){Browser.mouseX=SDL.mouseX+Browser.mouseMovementX;Browser.mouseY=SDL.mouseY+Browser.mouseMovementY}else{Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}}else{var rect=Module["canvas"].getBoundingClientRect();var cw=Module["canvas"].width;var ch=Module["canvas"].height;var scrollX=typeof window.scrollX!=="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!=="undefined"?window.scrollY:window.pageYOffset;if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var adjustedX=touch.pageX-(scrollX+rect.left);var adjustedY=touch.pageY-(scrollY+rect.top);adjustedX=adjustedX*(cw/rect.width);adjustedY=adjustedY*(ch/rect.height);var coords={x:adjustedX,y:adjustedY};if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];if(!last)last=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}var x=event.pageX-(scrollX+rect.left);var y=event.pageY-(scrollY+rect.top);x=x*(cw/rect.width);y=y*(ch/rect.height);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y}}),xhrLoad:(function(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response)}else{onerror()}};xhr.onerror=onerror;xhr.send(null)}),asyncLoad:(function(url,onload,onerror,noRunDep){Browser.xhrLoad(url,(function(arrayBuffer){assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(!noRunDep)removeRunDependency("al "+url)}),(function(event){if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}}));if(!noRunDep)addRunDependency("al "+url)}),resizeListeners:[],updateResizeListeners:(function(){var canvas=Module["canvas"];Browser.resizeListeners.forEach((function(listener){listener(canvas.width,canvas.height)}))}),setCanvasSize:(function(width,height,noUpdates){var canvas=Module["canvas"];Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()}),windowedWidth:0,windowedHeight:0,setFullScreenCanvasSize:(function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];flags=flags|8388608;HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=flags}Browser.updateResizeListeners()}),setWindowedCanvasSize:(function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];flags=flags&~8388608;HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=flags}Browser.updateResizeListeners()}),updateCanvasDimensions:(function(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]&&Module["forcedAspectRatio"]>0){if(w/h<Module["forcedAspectRatio"]){w=Math.round(h*Module["forcedAspectRatio"])}else{h=Math.round(w/Module["forcedAspectRatio"])}}if((document["webkitFullScreenElement"]||document["webkitFullscreenElement"]||document["mozFullScreenElement"]||document["mozFullscreenElement"]||document["fullScreenElement"]||document["fullscreenElement"]||document["msFullScreenElement"]||document["msFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvas.parentNode&&typeof screen!="undefined"){var factor=Math.min(screen.width/w,screen.height/h);w=Math.round(w*factor);h=Math.round(h*factor)}if(Browser.resizeCanvas){if(canvas.width!=w)canvas.width=w;if(canvas.height!=h)canvas.height=h;if(typeof canvas.style!="undefined"){canvas.style.removeProperty("width");canvas.style.removeProperty("height")}}else{if(canvas.width!=wNative)canvas.width=wNative;if(canvas.height!=hNative)canvas.height=hNative;if(typeof canvas.style!="undefined"){if(w!=wNative||h!=hNative){canvas.style.setProperty("width",w+"px","important");canvas.style.setProperty("height",h+"px","important")}else{canvas.style.removeProperty("width");canvas.style.removeProperty("height")}}}}),wgetRequests:{},nextWgetRequestHandle:0,getNextWgetRequestHandle:(function(){var handle=Browser.nextWgetRequestHandle;Browser.nextWgetRequestHandle++;return handle})};function _pthread_setspecific(key,value){if(!(key in PTHREAD_SPECIFIC)){return ERRNO_CODES.EINVAL}PTHREAD_SPECIFIC[key]=value;return 0}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _pthread_self(){return 0}function ___syscall140(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),offset_high=SYSCALLS.get(),offset_low=SYSCALLS.get(),result=SYSCALLS.get(),whence=SYSCALLS.get();var offset=offset_low;assert(offset_high===0);FS.llseek(stream,offset,whence);HEAP32[result>>2]=stream.position;if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall146(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.get(),iov=SYSCALLS.get(),iovcnt=SYSCALLS.get();var ret=0;if(!___syscall146.buffer)___syscall146.buffer=[];var buffer=___syscall146.buffer;for(var i=0;i<iovcnt;i++){var ptr=HEAP32[iov+i*8>>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;j<len;j++){var curr=HEAPU8[ptr+j];if(curr===0||curr===10){Module["print"](UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}}ret+=len}return ret}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall54(which,varargs){SYSCALLS.varargs=varargs;try{return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}Module["requestFullScreen"]=function Module_requestFullScreen(lockPointer,resizeCanvas,vrDevice){Browser.requestFullScreen(lockPointer,resizeCanvas,vrDevice)};Module["requestAnimationFrame"]=function Module_requestAnimationFrame(func){Browser.requestAnimationFrame(func)};Module["setCanvasSize"]=function Module_setCanvasSize(width,height,noUpdates){Browser.setCanvasSize(width,height,noUpdates)};Module["pauseMainLoop"]=function Module_pauseMainLoop(){Browser.mainLoop.pause()};Module["resumeMainLoop"]=function Module_resumeMainLoop(){Browser.mainLoop.resume()};Module["getUserMedia"]=function Module_getUserMedia(){Browser.getUserMedia()};Module["createContext"]=function Module_createContext(canvas,useWebGL,setInModule,webGLContextAttributes){return Browser.createContext(canvas,useWebGL,setInModule,webGLContextAttributes)};STACK_BASE=STACKTOP=Runtime.alignMemory(STATICTOP);staticSealed=true;STACK_MAX=STACK_BASE+TOTAL_STACK;DYNAMIC_BASE=DYNAMICTOP=Runtime.alignMemory(STACK_MAX);assert(DYNAMIC_BASE<TOTAL_MEMORY,"TOTAL_MEMORY not big enough for stack");var cttz_i8=allocate([8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0],"i8",ALLOC_DYNAMIC);function invoke_iiii(index,a1,a2,a3){try{return Module["dynCall_iiii"](index,a1,a2,a3)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){try{Module["dynCall_viiiii"](index,a1,a2,a3,a4,a5)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_vi(index,a1){try{Module["dynCall_vi"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_ii(index,a1){try{return Module["dynCall_ii"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viii(index,a1,a2,a3){try{Module["dynCall_viii"](index,a1,a2,a3)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_v(index){try{Module["dynCall_v"](index)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){try{Module["dynCall_viiiiii"](index,a1,a2,a3,a4,a5,a6)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiii(index,a1,a2,a3,a4){try{Module["dynCall_viiii"](index,a1,a2,a3,a4)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}Module.asmGlobalArg={"Math":Math,"Int8Array":Int8Array,"Int16Array":Int16Array,"Int32Array":Int32Array,"Uint8Array":Uint8Array,"Uint16Array":Uint16Array,"Uint32Array":Uint32Array,"Float32Array":Float32Array,"Float64Array":Float64Array,"NaN":NaN,"Infinity":Infinity,"byteLength":byteLength};Module.asmLibraryArg={"abort":abort,"assert":assert,"invoke_iiii":invoke_iiii,"invoke_viiiii":invoke_viiiii,"invoke_vi":invoke_vi,"invoke_ii":invoke_ii,"invoke_viii":invoke_viii,"invoke_v":invoke_v,"invoke_viiiiii":invoke_viiiiii,"invoke_viiii":invoke_viiii,"_pthread_cleanup_pop":_pthread_cleanup_pop,"_pthread_getspecific":_pthread_getspecific,"_pthread_setspecific":_pthread_setspecific,"__ZSt18uncaught_exceptionv":__ZSt18uncaught_exceptionv,"_emscripten_set_main_loop":_emscripten_set_main_loop,"_pthread_self":_pthread_self,"_abort":_abort,"_pthread_cleanup_push":_pthread_cleanup_push,"___syscall6":___syscall6,"_sbrk":_sbrk,"_time":_time,"_pthread_key_create":_pthread_key_create,"___setErrNo":___setErrNo,"_emscripten_memcpy_big":_emscripten_memcpy_big,"___syscall54":___syscall54,"___syscall140":___syscall140,"_pthread_once":_pthread_once,"_emscripten_set_main_loop_timing":_emscripten_set_main_loop_timing,"_sysconf":_sysconf,"___syscall146":___syscall146,"___cxa_begin_catch":___cxa_begin_catch,"STACKTOP":STACKTOP,"STACK_MAX":STACK_MAX,"tempDoublePtr":tempDoublePtr,"ABORT":ABORT,"cttz_i8":cttz_i8};// EMSCRIPTEN_START_ASM
    var asm=(function(global,env,buffer) {
        "use asm";var a=global.Int8Array;var b=global.Int16Array;var c=global.Int32Array;var d=global.Uint8Array;var e=global.Uint16Array;var f=global.Uint32Array;var g=global.Float32Array;var h=global.Float64Array;var i=new a(buffer);var j=new b(buffer);var k=new c(buffer);var l=new d(buffer);var m=new e(buffer);var n=new f(buffer);var o=new g(buffer);var p=new h(buffer);var q=global.byteLength;var r=env.STACKTOP|0;var s=env.STACK_MAX|0;var t=env.tempDoublePtr|0;var u=env.ABORT|0;var v=env.cttz_i8|0;var w=0;var x=0;var y=0;var z=0;var A=global.NaN,B=global.Infinity;var C=0,D=0,E=0,F=0,G=0.0,H=0,I=0,J=0,K=0.0;var L=0;var M=0;var N=0;var O=0;var P=0;var Q=0;var R=0;var S=0;var T=0;var U=0;var V=global.Math.floor;var W=global.Math.abs;var X=global.Math.sqrt;var Y=global.Math.pow;var Z=global.Math.cos;var _=global.Math.sin;var $=global.Math.tan;var aa=global.Math.acos;var ba=global.Math.asin;var ca=global.Math.atan;var da=global.Math.atan2;var ea=global.Math.exp;var fa=global.Math.log;var ga=global.Math.ceil;var ha=global.Math.imul;var ia=global.Math.min;var ja=global.Math.clz32;var ka=env.abort;var la=env.assert;var ma=env.invoke_iiii;var na=env.invoke_viiiii;var oa=env.invoke_vi;var pa=env.invoke_ii;var qa=env.invoke_viii;var ra=env.invoke_v;var sa=env.invoke_viiiiii;var ta=env.invoke_viiii;var ua=env._pthread_cleanup_pop;var va=env._pthread_getspecific;var wa=env._pthread_setspecific;var xa=env.__ZSt18uncaught_exceptionv;var ya=env._emscripten_set_main_loop;var za=env._pthread_self;var Aa=env._abort;var Ba=env._pthread_cleanup_push;var Ca=env.___syscall6;var Da=env._sbrk;var Ea=env._time;var Fa=env._pthread_key_create;var Ga=env.___setErrNo;var Ha=env._emscripten_memcpy_big;var Ia=env.___syscall54;var Ja=env.___syscall140;var Ka=env._pthread_once;var La=env._emscripten_set_main_loop_timing;var Ma=env._sysconf;var Na=env.___syscall146;var Oa=env.___cxa_begin_catch;var Pa=0.0;function Qa(newBuffer){if(q(newBuffer)&16777215||q(newBuffer)<=16777215||q(newBuffer)>2147483648)return false;i=new a(newBuffer);j=new b(newBuffer);k=new c(newBuffer);l=new d(newBuffer);m=new e(newBuffer);n=new f(newBuffer);o=new g(newBuffer);p=new h(newBuffer);buffer=newBuffer;return true}
// EMSCRIPTEN_START_FUNCS
        function Za(a){a=a|0;var b=0;b=r;r=r+a|0;r=r+15&-16;return b|0}function _a(){return r|0}function $a(a){a=a|0;r=a}function ab(a,b){a=a|0;b=b|0;r=a;s=b}function bb(a,b){a=a|0;b=b|0;if(!w){w=a;x=b}}function cb(a){a=a|0;i[t>>0]=i[a>>0];i[t+1>>0]=i[a+1>>0];i[t+2>>0]=i[a+2>>0];i[t+3>>0]=i[a+3>>0]}function db(a){a=a|0;i[t>>0]=i[a>>0];i[t+1>>0]=i[a+1>>0];i[t+2>>0]=i[a+2>>0];i[t+3>>0]=i[a+3>>0];i[t+4>>0]=i[a+4>>0];i[t+5>>0]=i[a+5>>0];i[t+6>>0]=i[a+6>>0];i[t+7>>0]=i[a+7>>0]}function eb(a){a=a|0;L=a}function fb(){return L|0}function gb(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0;o=r;r=r+576|0;l=o+48|0;h=o+32|0;g=o+16|0;f=o;j=o+64|0;m=o+60|0;i=a+4|0;n=a+8|0;if((k[i>>2]|0)>>>0>(k[n>>2]|0)>>>0){k[f>>2]=1138;k[f+4>>2]=2119;k[f+8>>2]=1117;yc(j,1084,f)|0;xc(j)|0}if((2147418112/(d>>>0)|0)>>>0<=b>>>0){k[g>>2]=1138;k[g+4>>2]=2120;k[g+8>>2]=1157;yc(j,1084,g)|0;xc(j)|0}g=k[n>>2]|0;if(g>>>0>=b>>>0){n=1;r=o;return n|0}do if(c){if(b){f=b+-1|0;if(!(f&b)){f=11;break}else b=f}else b=-1;b=b>>>16|b;b=b>>>8|b;b=b>>>4|b;b=b>>>2|b;b=(b>>>1|b)+1|0;f=10}else f=10;while(0);if((f|0)==10)if(!b){b=0;f=12}else f=11;if((f|0)==11)if(b>>>0<=g>>>0)f=12;if((f|0)==12){k[h>>2]=1138;k[h+4>>2]=2129;k[h+8>>2]=1205;yc(j,1084,h)|0;xc(j)|0}c=ha(b,d)|0;do if(!e){f=hb(k[a>>2]|0,c,m,1)|0;if(!f){n=0;r=o;return n|0}else{k[a>>2]=f;break}}else{g=ib(c,m)|0;if(!g){n=0;r=o;return n|0}Va[e&0](g,k[a>>2]|0,k[i>>2]|0);f=k[a>>2]|0;do if(f)if(!(f&7)){kb(f,0,0,1,0)|0;break}else{k[l>>2]=1138;k[l+4>>2]=2502;k[l+8>>2]=1504;yc(j,1084,l)|0;xc(j)|0;break}while(0);k[a>>2]=g}while(0);f=k[m>>2]|0;if(f>>>0>c>>>0)b=(f>>>0)/(d>>>0)|0;k[n>>2]=b;n=1;r=o;return n|0}function hb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;j=r;r=r+560|0;i=j+32|0;f=j+16|0;e=j;h=j+48|0;g=j+44|0;if(a&7){k[e>>2]=1138;k[e+4>>2]=2502;k[e+8>>2]=1482;yc(h,1084,e)|0;xc(h)|0;i=0;r=j;return i|0}if(b>>>0>2147418112){k[f>>2]=1138;k[f+4>>2]=2502;k[f+8>>2]=1375;yc(h,1084,f)|0;xc(h)|0;i=0;r=j;return i|0}k[g>>2]=b;d=kb(a,b,g,d,0)|0;if(c)k[c>>2]=k[g>>2];if(!(d&7)){i=d;r=j;return i|0}k[i>>2]=1138;k[i+4>>2]=2554;k[i+8>>2]=1428;yc(h,1084,i)|0;xc(h)|0;i=d;r=j;return i|0}function ib(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0;i=r;r=r+560|0;h=i+32|0;g=i+16|0;c=i;f=i+48|0;e=i+44|0;d=a+3&-4;d=(d|0)!=0?d:4;if(d>>>0>2147418112){k[c>>2]=1138;k[c+4>>2]=2502;k[c+8>>2]=1375;yc(f,1084,c)|0;xc(f)|0;h=0;r=i;return h|0}k[e>>2]=d;c=kb(0,d,e,1,0)|0;a=k[e>>2]|0;if(b)k[b>>2]=a;if((c|0)==0|a>>>0<d>>>0){k[g>>2]=1138;k[g+4>>2]=2502;k[g+8>>2]=1401;yc(f,1084,g)|0;xc(f)|0;h=0;r=i;return h|0}if(!(c&7)){h=c;r=i;return h|0}k[h>>2]=1138;k[h+4>>2]=2529;k[h+8>>2]=1428;yc(f,1084,h)|0;xc(f)|0;h=c;r=i;return h|0}function jb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=r;r=r+880|0;I=L+144|0;H=L+128|0;G=L+112|0;F=L+96|0;C=L+80|0;w=L+64|0;u=L+48|0;v=L+32|0;q=L+16|0;p=L;E=L+360|0;J=L+296|0;K=L+224|0;t=L+156|0;if((b|0)==0|d>>>0>11){a=0;r=L;return a|0}k[a>>2]=b;e=K;f=e+68|0;do{k[e>>2]=0;e=e+4|0}while((e|0)<(f|0));f=0;do{e=i[c+f>>0]|0;if(e<<24>>24){D=K+((e&255)<<2)|0;k[D>>2]=(k[D>>2]|0)+1}f=f+1|0}while((f|0)!=(b|0));f=0;o=1;g=0;h=-1;n=0;while(1){e=k[K+(o<<2)>>2]|0;if(!e)k[a+28+(o+-1<<2)>>2]=0;else{D=o+-1|0;k[J+(D<<2)>>2]=f;f=e+f|0;B=16-o|0;k[a+28+(D<<2)>>2]=(f+-1<<B|(1<<B)+-1)+1;k[a+96+(D<<2)>>2]=n;k[t+(o<<2)>>2]=n;g=g>>>0>o>>>0?g:o;h=h>>>0<o>>>0?h:o;n=e+n|0}o=o+1|0;if((o|0)==17){D=g;break}else f=f<<1}k[a+4>>2]=n;f=a+172|0;do if(n>>>0>(k[f>>2]|0)>>>0){k[f>>2]=n;if(n){e=n+-1|0;if(e&n)s=14}else{e=-1;s=14}if((s|0)==14){B=e>>>16|e;B=B>>>8|B;B=B>>>4|B;B=B>>>2|B;B=(B>>>1|B)+1|0;k[f>>2]=B>>>0>b>>>0?b:B}g=a+176|0;e=k[g>>2]|0;do if(e){B=k[e+-4>>2]|0;e=e+-8|0;if(!((B|0)!=0?(B|0)==(~k[e>>2]|0):0)){k[p>>2]=1138;k[p+4>>2]=647;k[p+8>>2]=1851;yc(E,1084,p)|0;xc(E)|0}if(!(e&7)){kb(e,0,0,1,0)|0;break}else{k[q>>2]=1138;k[q+4>>2]=2502;k[q+8>>2]=1504;yc(E,1084,q)|0;xc(E)|0;break}}while(0);f=k[f>>2]|0;f=(f|0)!=0?f:1;e=ib((f<<1)+8|0,0)|0;if(!e){k[g>>2]=0;e=0;break}else{k[e+4>>2]=f;k[e>>2]=~f;k[g>>2]=e+8;s=25;break}}else s=25;while(0);a:do if((s|0)==25){B=a+24|0;i[B>>0]=h;i[a+25>>0]=D;f=a+176|0;g=0;do{A=i[c+g>>0]|0;e=A&255;if(A<<24>>24){if(!(k[K+(e<<2)>>2]|0)){k[v>>2]=1138;k[v+4>>2]=2272;k[v+8>>2]=1249;yc(E,1084,v)|0;xc(E)|0}A=t+(e<<2)|0;e=k[A>>2]|0;k[A>>2]=e+1;if(e>>>0>=n>>>0){k[u>>2]=1138;k[u+4>>2]=2276;k[u+8>>2]=1262;yc(E,1084,u)|0;xc(E)|0}j[(k[f>>2]|0)+(e<<1)>>1]=g}g=g+1|0}while((g|0)!=(b|0));e=i[B>>0]|0;z=(e&255)>>>0<d>>>0?d:0;A=a+8|0;k[A>>2]=z;y=(z|0)!=0;if(y){x=1<<z;e=a+164|0;do if(x>>>0>(k[e>>2]|0)>>>0){k[e>>2]=x;g=a+168|0;e=k[g>>2]|0;do if(e){v=k[e+-4>>2]|0;e=e+-8|0;if(!((v|0)!=0?(v|0)==(~k[e>>2]|0):0)){k[w>>2]=1138;k[w+4>>2]=647;k[w+8>>2]=1851;yc(E,1084,w)|0;xc(E)|0}if(!(e&7)){kb(e,0,0,1,0)|0;break}else{k[C>>2]=1138;k[C+4>>2]=2502;k[C+8>>2]=1504;yc(E,1084,C)|0;xc(E)|0;break}}while(0);e=x<<2;f=ib(e+8|0,0)|0;if(!f){k[g>>2]=0;e=0;break a}else{C=f+8|0;k[f+4>>2]=x;k[f>>2]=~x;k[g>>2]=C;f=C;break}}else{f=a+168|0;e=x<<2;g=f;f=k[f>>2]|0}while(0);$c(f|0,-1,e|0)|0;t=a+176|0;w=1;do{if(k[K+(w<<2)>>2]|0){u=z-w|0;v=1<<u;f=w+-1|0;h=k[J+(f<<2)>>2]|0;if(f>>>0>=16){k[F>>2]=1138;k[F+4>>2]=1956;k[F+8>>2]=1725;yc(E,1084,F)|0;xc(E)|0}e=k[a+28+(f<<2)>>2]|0;if(!e)b=-1;else b=(e+-1|0)>>>(16-w|0);if(h>>>0<=b>>>0){q=(k[a+96+(f<<2)>>2]|0)-h|0;s=w<<16;do{e=m[(k[t>>2]|0)+(q+h<<1)>>1]|0;if((l[c+e>>0]|0|0)!=(w|0)){k[G>>2]=1138;k[G+4>>2]=2318;k[G+8>>2]=1291;yc(E,1084,G)|0;xc(E)|0}p=h<<u;f=e|s;o=0;do{n=o+p|0;if(n>>>0>=x>>>0){k[H>>2]=1138;k[H+4>>2]=2324;k[H+8>>2]=1325;yc(E,1084,H)|0;xc(E)|0}e=k[g>>2]|0;if((k[e+(n<<2)>>2]|0)!=-1){k[I>>2]=1138;k[I+4>>2]=2326;k[I+8>>2]=1348;yc(E,1084,I)|0;xc(E)|0;e=k[g>>2]|0}k[e+(n<<2)>>2]=f;o=o+1|0}while(o>>>0<v>>>0);h=h+1|0}while(h>>>0<=b>>>0)}}w=w+1|0}while(z>>>0>=w>>>0);e=i[B>>0]|0}f=a+96|0;k[f>>2]=(k[f>>2]|0)-(k[J>>2]|0);f=a+100|0;k[f>>2]=(k[f>>2]|0)-(k[J+4>>2]|0);f=a+104|0;k[f>>2]=(k[f>>2]|0)-(k[J+8>>2]|0);f=a+108|0;k[f>>2]=(k[f>>2]|0)-(k[J+12>>2]|0);f=a+112|0;k[f>>2]=(k[f>>2]|0)-(k[J+16>>2]|0);f=a+116|0;k[f>>2]=(k[f>>2]|0)-(k[J+20>>2]|0);f=a+120|0;k[f>>2]=(k[f>>2]|0)-(k[J+24>>2]|0);f=a+124|0;k[f>>2]=(k[f>>2]|0)-(k[J+28>>2]|0);f=a+128|0;k[f>>2]=(k[f>>2]|0)-(k[J+32>>2]|0);f=a+132|0;k[f>>2]=(k[f>>2]|0)-(k[J+36>>2]|0);f=a+136|0;k[f>>2]=(k[f>>2]|0)-(k[J+40>>2]|0);f=a+140|0;k[f>>2]=(k[f>>2]|0)-(k[J+44>>2]|0);f=a+144|0;k[f>>2]=(k[f>>2]|0)-(k[J+48>>2]|0);f=a+148|0;k[f>>2]=(k[f>>2]|0)-(k[J+52>>2]|0);f=a+152|0;k[f>>2]=(k[f>>2]|0)-(k[J+56>>2]|0);f=a+156|0;k[f>>2]=(k[f>>2]|0)-(k[J+60>>2]|0);f=a+16|0;k[f>>2]=0;g=a+20|0;k[g>>2]=e&255;b:do if(y){while(1){if(!d)break b;e=d+-1|0;if(!(k[K+(d<<2)>>2]|0))d=e;else break}k[f>>2]=k[a+28+(e<<2)>>2];e=z+1|0;k[g>>2]=e;if(e>>>0<=D>>>0){while(1){if(k[K+(e<<2)>>2]|0)break;e=e+1|0;if(e>>>0>D>>>0)break b}k[g>>2]=e}}while(0);k[a+92>>2]=-1;k[a+160>>2]=1048575;k[a+12>>2]=32-(k[A>>2]|0);e=1}while(0);a=e;r=L;return a|0}function kb(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;do if(!a){d=Sc(b)|0;if(c){if(!d)a=0;else a=Vc(d)|0;k[c>>2]=a}}else{if(!b){Tc(a);if(!c){d=0;break}k[c>>2]=0;d=0;break}if(d){d=Uc(a,b)|0;a=(d|0)==0?a:d}else d=0;if(c){b=Vc(a)|0;k[c>>2]=b}}while(0);return d|0}function lb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if(!((a|0)!=0&b>>>0>73&(c|0)!=0)){c=0;return c|0}if((k[c>>2]|0)!=40|b>>>0<74){c=0;return c|0}if(((l[a>>0]|0)<<8|(l[a+1>>0]|0)|0)!=18552){c=0;return c|0}if(((l[a+2>>0]|0)<<8|(l[a+3>>0]|0))>>>0<74){c=0;return c|0}if(((l[a+7>>0]|0)<<16|(l[a+6>>0]|0)<<24|(l[a+8>>0]|0)<<8|(l[a+9>>0]|0))>>>0>b>>>0){c=0;return c|0}k[c+4>>2]=(l[a+12>>0]|0)<<8|(l[a+13>>0]|0);k[c+8>>2]=(l[a+14>>0]|0)<<8|(l[a+15>>0]|0);k[c+12>>2]=l[a+16>>0];k[c+16>>2]=l[a+17>>0];b=a+18|0;d=c+32|0;k[d>>2]=l[b>>0];k[d+4>>2]=0;b=i[b>>0]|0;k[c+20>>2]=b<<24>>24==0|b<<24>>24==9?8:16;k[c+24>>2]=(l[a+26>>0]|0)<<16|(l[a+25>>0]|0)<<24|(l[a+27>>0]|0)<<8|(l[a+28>>0]|0);k[c+28>>2]=(l[a+30>>0]|0)<<16|(l[a+29>>0]|0)<<24|(l[a+31>>0]|0)<<8|(l[a+32>>0]|0);c=1;return c|0}function mb(a){a=a|0;Oa(a|0)|0;Sb()}function nb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;f=r;r=r+528|0;e=f;d=f+16|0;b=k[a+20>>2]|0;if(b)ob(b);b=a+4|0;c=k[b>>2]|0;if(!c){e=a+16|0;i[e>>0]=0;r=f;return}if(!(c&7))kb(c,0,0,1,0)|0;else{k[e>>2]=1138;k[e+4>>2]=2502;k[e+8>>2]=1504;yc(d,1084,e)|0;xc(d)|0}k[b>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;e=a+16|0;i[e>>0]=0;r=f;return}function ob(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;i=r;r=r+592|0;g=i+64|0;f=i+48|0;h=i+32|0;d=i+16|0;c=i;e=i+80|0;if(!a){r=i;return}b=k[a+168>>2]|0;do if(b){j=k[b+-4>>2]|0;b=b+-8|0;if(!((j|0)!=0?(j|0)==(~k[b>>2]|0):0)){k[c>>2]=1138;k[c+4>>2]=647;k[c+8>>2]=1851;yc(e,1084,c)|0;xc(e)|0}if(!(b&7)){kb(b,0,0,1,0)|0;break}else{k[d>>2]=1138;k[d+4>>2]=2502;k[d+8>>2]=1504;yc(e,1084,d)|0;xc(e)|0;break}}while(0);b=k[a+176>>2]|0;do if(b){j=k[b+-4>>2]|0;b=b+-8|0;if(!((j|0)!=0?(j|0)==(~k[b>>2]|0):0)){k[h>>2]=1138;k[h+4>>2]=647;k[h+8>>2]=1851;yc(e,1084,h)|0;xc(e)|0}if(!(b&7)){kb(b,0,0,1,0)|0;break}else{k[f>>2]=1138;k[f+4>>2]=2502;k[f+8>>2]=1504;yc(e,1084,f)|0;xc(e)|0;break}}while(0);if(!(a&7)){kb(a,0,0,1,0)|0;r=i;return}else{k[g>>2]=1138;k[g+4>>2]=2502;k[g+8>>2]=1504;yc(e,1084,g)|0;xc(e)|0;r=i;return}}function pb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0;i=r;r=r+544|0;g=i+16|0;b=i;f=i+32|0;e=a+8|0;c=k[e>>2]|0;if((c+-1|0)>>>0>=8192){k[b>>2]=1138;k[b+4>>2]=3002;k[b+8>>2]=1529;yc(f,1084,b)|0;xc(f)|0}k[a>>2]=c;d=a+20|0;b=k[d>>2]|0;if(!b){b=ib(180,0)|0;if(!b)b=0;else{h=b+164|0;k[h>>2]=0;k[h+4>>2]=0;k[h+8>>2]=0;k[h+12>>2]=0}k[d>>2]=b;h=k[a>>2]|0}else h=c;if(!(k[e>>2]|0)){k[g>>2]=1138;k[g+4>>2]=906;k[g+8>>2]=1769;yc(f,1084,g)|0;xc(f)|0;g=k[a>>2]|0}else g=h;f=k[a+4>>2]|0;if(g>>>0>16){d=g;c=0}else{a=0;a=jb(b,h,f,a)|0;r=i;return a|0}while(1){e=c+1|0;if(d>>>0>3){d=d>>>1;c=e}else{d=e;break}}a=c+2+((d|0)!=32&1<<d>>>0<g>>>0&1)|0;a=a>>>0<11?a&255:11;a=jb(b,h,f,a)|0;r=i;return a|0}function qb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0;E=r;r=r+720|0;z=E+160|0;y=E+144|0;x=E+128|0;w=E+112|0;v=E+96|0;u=E+80|0;t=E+64|0;s=E+48|0;n=E+32|0;h=E+16|0;e=E;C=E+200|0;D=E+176|0;A=rb(a,14)|0;if(!A){k[b>>2]=0;c=b+4|0;d=k[c>>2]|0;if(d){if(!(d&7))kb(d,0,0,1,0)|0;else{k[e>>2]=1138;k[e+4>>2]=2502;k[e+8>>2]=1504;yc(C,1084,e)|0;xc(C)|0}k[c>>2]=0;k[b+8>>2]=0;k[b+12>>2]=0}i[b+16>>0]=0;c=b+20|0;d=k[c>>2]|0;if(!d){b=1;r=E;return b|0}ob(d);k[c>>2]=0;b=1;r=E;return b|0}p=b+4|0;q=b+8|0;c=k[q>>2]|0;if((c|0)!=(A|0)){if(c>>>0<=A>>>0){do if((k[b+12>>2]|0)>>>0<A>>>0){if(gb(p,A,(c+1|0)==(A|0),1,0)|0){c=k[q>>2]|0;break}i[b+16>>0]=1;b=0;r=E;return b|0}while(0);$c((k[p>>2]|0)+c|0,0,A-c|0)|0}k[q>>2]=A}$c(k[p>>2]|0,0,A|0)|0;o=a+20|0;c=k[o>>2]|0;if((c|0)<5){f=a+4|0;g=a+8|0;e=a+16|0;do{d=k[f>>2]|0;if((d|0)==(k[g>>2]|0))d=0;else{k[f>>2]=d+1;d=l[d>>0]|0}c=c+8|0;k[o>>2]=c;if((c|0)>=33){k[h>>2]=1138;k[h+4>>2]=3204;k[h+8>>2]=1638;yc(C,1084,h)|0;xc(C)|0;c=k[o>>2]|0}d=d<<32-c|k[e>>2];k[e>>2]=d}while((c|0)<5)}else{d=a+16|0;e=d;d=k[d>>2]|0}m=d>>>27;k[e>>2]=d<<5;k[o>>2]=c+-5;if((m+-1|0)>>>0>20){b=0;r=E;return b|0}k[D+20>>2]=0;k[D>>2]=0;k[D+4>>2]=0;k[D+8>>2]=0;k[D+12>>2]=0;i[D+16>>0]=0;c=D+4|0;d=D+8|0;a:do if(gb(c,21,0,1,0)|0){h=k[d>>2]|0;j=k[c>>2]|0;$c(j+h|0,0,21-h|0)|0;k[d>>2]=21;if(m){e=a+4|0;f=a+8|0;g=a+16|0;h=0;do{c=k[o>>2]|0;if((c|0)<3)do{d=k[e>>2]|0;if((d|0)==(k[f>>2]|0))d=0;else{k[e>>2]=d+1;d=l[d>>0]|0}c=c+8|0;k[o>>2]=c;if((c|0)>=33){k[n>>2]=1138;k[n+4>>2]=3204;k[n+8>>2]=1638;yc(C,1084,n)|0;xc(C)|0;c=k[o>>2]|0}d=d<<32-c|k[g>>2];k[g>>2]=d}while((c|0)<3);else d=k[g>>2]|0;k[g>>2]=d<<3;k[o>>2]=c+-3;i[j+(l[1599+h>>0]|0)>>0]=d>>>29;h=h+1|0}while((h|0)!=(m|0))}if(pb(D)|0){m=a+4|0;h=a+8|0;j=a+16|0;d=0;b:while(1){g=A-d|0;c=sb(a,D)|0;c:do if(c>>>0<17){if((k[q>>2]|0)>>>0<=d>>>0){k[s>>2]=1138;k[s+4>>2]=906;k[s+8>>2]=1769;yc(C,1084,s)|0;xc(C)|0}i[(k[p>>2]|0)+d>>0]=c;c=d+1|0}else switch(c|0){case 17:{c=k[o>>2]|0;if((c|0)<3)do{e=k[m>>2]|0;if((e|0)==(k[h>>2]|0))e=0;else{k[m>>2]=e+1;e=l[e>>0]|0}c=c+8|0;k[o>>2]=c;if((c|0)>=33){k[t>>2]=1138;k[t+4>>2]=3204;k[t+8>>2]=1638;yc(C,1084,t)|0;xc(C)|0;c=k[o>>2]|0}e=e<<32-c|k[j>>2];k[j>>2]=e}while((c|0)<3);else e=k[j>>2]|0;k[j>>2]=e<<3;k[o>>2]=c+-3;c=(e>>>29)+3|0;if(c>>>0>g>>>0){c=0;break a}c=c+d|0;break c}case 18:{c=k[o>>2]|0;if((c|0)<7)do{e=k[m>>2]|0;if((e|0)==(k[h>>2]|0))e=0;else{k[m>>2]=e+1;e=l[e>>0]|0}c=c+8|0;k[o>>2]=c;if((c|0)>=33){k[u>>2]=1138;k[u+4>>2]=3204;k[u+8>>2]=1638;yc(C,1084,u)|0;xc(C)|0;c=k[o>>2]|0}e=e<<32-c|k[j>>2];k[j>>2]=e}while((c|0)<7);else e=k[j>>2]|0;k[j>>2]=e<<7;k[o>>2]=c+-7;c=(e>>>25)+11|0;if(c>>>0>g>>>0){c=0;break a}c=c+d|0;break c}default:{if((c+-19|0)>>>0>=2){B=90;break b}f=k[o>>2]|0;if((c|0)==19){if((f|0)<2){e=f;while(1){c=k[m>>2]|0;if((c|0)==(k[h>>2]|0))f=0;else{k[m>>2]=c+1;f=l[c>>0]|0}c=e+8|0;k[o>>2]=c;if((c|0)>=33){k[v>>2]=1138;k[v+4>>2]=3204;k[v+8>>2]=1638;yc(C,1084,v)|0;xc(C)|0;c=k[o>>2]|0}e=f<<32-c|k[j>>2];k[j>>2]=e;if((c|0)<2)e=c;else break}}else{e=k[j>>2]|0;c=f}k[j>>2]=e<<2;k[o>>2]=c+-2;f=(e>>>30)+3|0}else{if((f|0)<6){e=f;while(1){c=k[m>>2]|0;if((c|0)==(k[h>>2]|0))f=0;else{k[m>>2]=c+1;f=l[c>>0]|0}c=e+8|0;k[o>>2]=c;if((c|0)>=33){k[w>>2]=1138;k[w+4>>2]=3204;k[w+8>>2]=1638;yc(C,1084,w)|0;xc(C)|0;c=k[o>>2]|0}e=f<<32-c|k[j>>2];k[j>>2]=e;if((c|0)<6)e=c;else break}}else{e=k[j>>2]|0;c=f}k[j>>2]=e<<6;k[o>>2]=c+-6;f=(e>>>26)+7|0}if((d|0)==0|f>>>0>g>>>0){c=0;break a}c=d+-1|0;if((k[q>>2]|0)>>>0<=c>>>0){k[x>>2]=1138;k[x+4>>2]=906;k[x+8>>2]=1769;yc(C,1084,x)|0;xc(C)|0}e=i[(k[p>>2]|0)+c>>0]|0;if(!(e<<24>>24)){c=0;break a}c=f+d|0;if(d>>>0>=c>>>0){c=d;break c}do{if((k[q>>2]|0)>>>0<=d>>>0){k[y>>2]=1138;k[y+4>>2]=906;k[y+8>>2]=1769;yc(C,1084,y)|0;xc(C)|0}i[(k[p>>2]|0)+d>>0]=e;d=d+1|0}while((d|0)!=(c|0))}}while(0);if(A>>>0>c>>>0)d=c;else break}if((B|0)==90){k[z>>2]=1138;k[z+4>>2]=3145;k[z+8>>2]=1620;yc(C,1084,z)|0;xc(C)|0;c=0;break}if((A|0)==(c|0))c=pb(b)|0;else c=0}else c=0}else{i[D+16>>0]=1;c=0}while(0);nb(D);b=c;r=E;return b|0}function rb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;j=r;r=r+528|0;g=j;f=j+16|0;if(!b){i=0;r=j;return i|0}if(b>>>0<=16){i=tb(a,b)|0;r=j;return i|0}h=tb(a,b+-16|0)|0;i=a+20|0;b=k[i>>2]|0;if((b|0)<16){d=a+4|0;e=a+8|0;c=a+16|0;do{a=k[d>>2]|0;if((a|0)==(k[e>>2]|0))a=0;else{k[d>>2]=a+1;a=l[a>>0]|0}b=b+8|0;k[i>>2]=b;if((b|0)>=33){k[g>>2]=1138;k[g+4>>2]=3204;k[g+8>>2]=1638;yc(f,1084,g)|0;xc(f)|0;b=k[i>>2]|0}a=a<<32-b|k[c>>2];k[c>>2]=a}while((b|0)<16)}else{a=a+16|0;c=a;a=k[a>>2]|0}k[c>>2]=a<<16;k[i>>2]=b+-16;i=a>>>16|h<<16;r=j;return i|0}function sb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,n=0,o=0,p=0,q=0,s=0,t=0;t=r;r=r+576|0;o=t+48|0;n=t+32|0;j=t+16|0;i=t;q=t+64|0;p=k[b+20>>2]|0;s=a+20|0;h=k[s>>2]|0;do if((h|0)<24){g=a+4|0;d=k[g>>2]|0;e=k[a+8>>2]|0;c=d>>>0<e>>>0;if((h|0)>=16){if(c){k[g>>2]=d+1;c=l[d>>0]|0}else c=0;k[s>>2]=h+8;g=a+16|0;f=c<<24-h|k[g>>2];k[g>>2]=f;break}if(c){f=(l[d>>0]|0)<<8;c=d+1|0}else{f=0;c=d}if(c>>>0<e>>>0){d=l[c>>0]|0;c=c+1|0}else d=0;k[g>>2]=c;k[s>>2]=h+16;g=a+16|0;f=(d|f)<<16-h|k[g>>2];k[g>>2]=f}else{f=a+16|0;g=f;f=k[f>>2]|0}while(0);e=(f>>>16)+1|0;do if(e>>>0<=(k[p+16>>2]|0)>>>0){d=k[(k[p+168>>2]|0)+(f>>>(32-(k[p+8>>2]|0)|0)<<2)>>2]|0;if((d|0)==-1){k[i>>2]=1138;k[i+4>>2]=3249;k[i+8>>2]=1665;yc(q,1084,i)|0;xc(q)|0}c=d&65535;d=d>>>16;if((k[b+8>>2]|0)>>>0<=c>>>0){k[j>>2]=1138;k[j+4>>2]=905;k[j+8>>2]=1769;yc(q,1084,j)|0;xc(q)|0}if((l[(k[b+4>>2]|0)+c>>0]|0|0)!=(d|0)){k[n>>2]=1138;k[n+4>>2]=3253;k[n+8>>2]=1682;yc(q,1084,n)|0;xc(q)|0}}else{d=k[p+20>>2]|0;while(1){c=d+-1|0;if(e>>>0>(k[p+28+(c<<2)>>2]|0)>>>0)d=d+1|0;else break}c=(f>>>(32-d|0))+(k[p+96+(c<<2)>>2]|0)|0;if(c>>>0<(k[b>>2]|0)>>>0){c=m[(k[p+176>>2]|0)+(c<<1)>>1]|0;break}k[o>>2]=1138;k[o+4>>2]=3271;k[o+8>>2]=1620;yc(q,1084,o)|0;xc(q)|0;s=0;r=t;return s|0}while(0);k[g>>2]=k[g>>2]<<d;k[s>>2]=(k[s>>2]|0)-d;s=c;r=t;return s|0}function tb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;j=r;r=r+544|0;h=j+16|0;c=j;g=j+32|0;if(b>>>0>=33){k[c>>2]=1138;k[c+4>>2]=3195;k[c+8>>2]=1622;yc(g,1084,c)|0;xc(g)|0}i=a+20|0;c=k[i>>2]|0;if((c|0)>=(b|0)){e=a+16|0;f=e;e=k[e>>2]|0;g=c;h=32-b|0;h=e>>>h;e=e<<b;k[f>>2]=e;b=g-b|0;k[i>>2]=b;r=j;return h|0}e=a+4|0;f=a+8|0;d=a+16|0;do{a=k[e>>2]|0;if((a|0)==(k[f>>2]|0))a=0;else{k[e>>2]=a+1;a=l[a>>0]|0}c=c+8|0;k[i>>2]=c;if((c|0)>=33){k[h>>2]=1138;k[h+4>>2]=3204;k[h+8>>2]=1638;yc(g,1084,h)|0;xc(g)|0;c=k[i>>2]|0}a=a<<32-c|k[d>>2];k[d>>2]=a}while((c|0)<(b|0));h=32-b|0;h=a>>>h;g=a<<b;k[d>>2]=g;b=c-b|0;k[i>>2]=b;r=j;return h|0}function ub(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0;q=r;r=r+528|0;o=q;n=q+16|0;if((a|0)==0|b>>>0<62){p=0;r=q;return p|0}m=ib(300,0)|0;if(!m){p=0;r=q;return p|0}k[m>>2]=519686845;c=m+4|0;k[c>>2]=0;d=m+8|0;k[d>>2]=0;j=m+88|0;e=m+136|0;f=m+160|0;g=j;h=g+44|0;do{k[g>>2]=0;g=g+4|0}while((g|0)<(h|0));i[j+44>>0]=0;s=m+184|0;g=m+208|0;h=m+232|0;t=m+252|0;k[t>>2]=0;k[t+4>>2]=0;k[t+8>>2]=0;i[t+12>>0]=0;t=m+268|0;k[t>>2]=0;k[t+4>>2]=0;k[t+8>>2]=0;i[t+12>>0]=0;t=m+284|0;k[t>>2]=0;k[t+4>>2]=0;k[t+8>>2]=0;i[t+12>>0]=0;k[e>>2]=0;k[e+4>>2]=0;k[e+8>>2]=0;k[e+12>>2]=0;k[e+16>>2]=0;i[e+20>>0]=0;k[f>>2]=0;k[f+4>>2]=0;k[f+8>>2]=0;k[f+12>>2]=0;k[f+16>>2]=0;i[f+20>>0]=0;k[s>>2]=0;k[s+4>>2]=0;k[s+8>>2]=0;k[s+12>>2]=0;k[s+16>>2]=0;i[s+20>>0]=0;k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;k[g+12>>2]=0;k[g+16>>2]=0;i[g+20>>0]=0;k[h>>2]=0;k[h+4>>2]=0;k[h+8>>2]=0;k[h+12>>2]=0;i[h+16>>0]=0;do if(((b>>>0>=74?((l[a>>0]|0)<<8|(l[a+1>>0]|0)|0)==18552:0)?((l[a+2>>0]|0)<<8|(l[a+3>>0]|0))>>>0>=74:0)?((l[a+7>>0]|0)<<16|(l[a+6>>0]|0)<<24|(l[a+8>>0]|0)<<8|(l[a+9>>0]|0))>>>0<=b>>>0:0){k[j>>2]=a;k[c>>2]=a;k[d>>2]=b;if(Eb(m)|0){c=k[j>>2]|0;if((l[c+39>>0]|0)<<8|(l[c+40>>0]|0)){if(!(Fb(m)|0))break;if(!(Gb(m)|0))break;c=k[j>>2]|0}if(!((l[c+55>>0]|0)<<8|(l[c+56>>0]|0))){t=m;r=q;return t|0}if(Hb(m)|0?Ib(m)|0:0){t=m;r=q;return t|0}}}else p=7;while(0);if((p|0)==7)k[j>>2]=0;Nb(m);if(!(m&7)){kb(m,0,0,1,0)|0;t=0;r=q;return t|0}else{k[o>>2]=1138;k[o+4>>2]=2502;k[o+8>>2]=1504;yc(n,1084,o)|0;xc(n)|0;t=0;r=q;return t|0}return 0}function vb(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,m=0;m=r;r=r+528|0;j=m;i=m+16|0;f=k[a+88>>2]|0;h=(l[f+70+(e<<2)+1>>0]|0)<<16|(l[f+70+(e<<2)>>0]|0)<<24|(l[f+70+(e<<2)+2>>0]|0)<<8|(l[f+70+(e<<2)+3>>0]|0);g=e+1|0;if(g>>>0<(l[f+16>>0]|0)>>>0)f=(l[f+70+(g<<2)+1>>0]|0)<<16|(l[f+70+(g<<2)>>0]|0)<<24|(l[f+70+(g<<2)+2>>0]|0)<<8|(l[f+70+(g<<2)+3>>0]|0);else f=k[a+8>>2]|0;if(f>>>0>h>>>0){i=a+4|0;i=k[i>>2]|0;i=i+h|0;j=f-h|0;j=wb(a,i,j,b,c,d,e)|0;r=m;return j|0}k[j>>2]=1138;k[j+4>>2]=3690;k[j+8>>2]=1780;yc(i,1084,j)|0;xc(i)|0;i=a+4|0;i=k[i>>2]|0;i=i+h|0;j=f-h|0;j=wb(a,i,j,b,c,d,e)|0;r=m;return j|0}function wb(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,m=0,n=0;n=k[a+88>>2]|0;j=((l[n+12>>0]|0)<<8|(l[n+13>>0]|0))>>>g;m=((l[n+14>>0]|0)<<8|(l[n+15>>0]|0))>>>g;j=j>>>0>1?(j+3|0)>>>2:1;m=m>>>0>1?(m+3|0)>>>2:1;n=n+18|0;g=i[n>>0]|0;g=ha(g<<24>>24==0|g<<24>>24==9?8:16,j)|0;if(f)if((f&3|0)==0&g>>>0<=f>>>0)g=f;else{a=0;return a|0}if((ha(g,m)|0)>>>0>e>>>0){a=0;return a|0}f=(j+1|0)>>>1;h=(m+1|0)>>>1;if(!c){a=0;return a|0}k[a+92>>2]=b;k[a+96>>2]=b;k[a+104>>2]=c;k[a+100>>2]=b+c;k[a+108>>2]=0;k[a+112>>2]=0;switch(l[n>>0]|0|0){case 0:{Jb(a,d,e,g,j,m,f,h)|0;a=1;return a|0}case 4:case 6:case 5:case 3:case 2:{Kb(a,d,e,g,j,m,f,h)|0;a=1;return a|0}case 9:{Lb(a,d,e,g,j,m,f,h)|0;a=1;return a|0}case 8:case 7:{Mb(a,d,e,g,j,m,f,h)|0;a=1;return a|0}default:{a=0;return a|0}}return 0}function xb(a,b){a=a|0;b=b|0;var c=0,d=0;d=r;r=r+48|0;c=d;k[c>>2]=40;lb(a,b,c)|0;r=d;return k[c+4>>2]|0}function yb(a,b){a=a|0;b=b|0;var c=0,d=0;d=r;r=r+48|0;c=d;k[c>>2]=40;lb(a,b,c)|0;r=d;return k[c+8>>2]|0}function zb(a,b){a=a|0;b=b|0;var c=0,d=0;d=r;r=r+48|0;c=d;k[c>>2]=40;lb(a,b,c)|0;r=d;return k[c+12>>2]|0}function Ab(a,b){a=a|0;b=b|0;var c=0,d=0;d=r;r=r+48|0;c=d;k[c>>2]=40;lb(a,b,c)|0;r=d;return k[c+32>>2]|0}function Bb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;e=r;r=r+576|0;d=e+40|0;c=e+56|0;f=e;k[f>>2]=40;lb(a,b,f)|0;b=f+32|0;a=k[b+4>>2]|0;do switch(k[b>>2]|0){case 0:{if(!a){f=8;r=e;return f|0}else a=14;break}case 1:{if(!a)a=13;else a=14;break}case 2:{if(!a)a=13;else a=14;break}case 3:{if(!a)a=13;else a=14;break}case 4:{if(!a)a=13;else a=14;break}case 5:{if(!a)a=13;else a=14;break}case 6:{if(!a)a=13;else a=14;break}case 7:{if(!a)a=13;else a=14;break}case 8:{if(!a)a=13;else a=14;break}case 9:{if(!a){f=8;r=e;return f|0}else a=14;break}case 10:{if(!a){f=8;r=e;return f|0}else a=14;break}default:a=14}while(0);if((a|0)==13){f=16;r=e;return f|0}else if((a|0)==14){k[d>>2]=1138;k[d+4>>2]=2668;k[d+8>>2]=1523;yc(c,1084,d)|0;xc(c)|0;f=0;r=e;return f|0}return 0}function Cb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0;h=r;r=r+576|0;f=h+40|0;e=h+56|0;i=h;k[i>>2]=40;lb(a,b,i)|0;d=(((k[i+4>>2]|0)>>>c)+3|0)>>>2;b=(((k[i+8>>2]|0)>>>c)+3|0)>>>2;c=i+32|0;a=k[c+4>>2]|0;do switch(k[c>>2]|0){case 0:{if(!a)a=8;else g=14;break}case 1:{if(!a)g=13;else g=14;break}case 2:{if(!a)g=13;else g=14;break}case 3:{if(!a)g=13;else g=14;break}case 4:{if(!a)g=13;else g=14;break}case 5:{if(!a)g=13;else g=14;break}case 6:{if(!a)g=13;else g=14;break}case 7:{if(!a)g=13;else g=14;break}case 8:{if(!a)g=13;else g=14;break}case 9:{if(!a)a=8;else g=14;break}case 10:{if(!a)a=8;else g=14;break}default:g=14}while(0);if((g|0)==13)a=16;else if((g|0)==14){k[f>>2]=1138;k[f+4>>2]=2668;k[f+8>>2]=1523;yc(e,1084,f)|0;xc(e)|0;a=0}i=ha(ha(b,d)|0,a)|0;r=h;return i|0}function Db(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;q=r;r=r+592|0;p=q+56|0;g=q+40|0;n=q+72|0;m=q;o=q+68|0;k[m>>2]=40;lb(a,b,m)|0;h=(k[m+4>>2]|0)>>>e;i=(k[m+8>>2]|0)>>>e;m=m+32|0;d=k[m+4>>2]|0;do switch(k[m>>2]|0){case 0:{if(!d)m=8;else j=14;break}case 1:{if(!d)j=13;else j=14;break}case 2:{if(!d)j=13;else j=14;break}case 3:{if(!d)j=13;else j=14;break}case 4:{if(!d)j=13;else j=14;break}case 5:{if(!d)j=13;else j=14;break}case 6:{if(!d)j=13;else j=14;break}case 7:{if(!d)j=13;else j=14;break}case 8:{if(!d)j=13;else j=14;break}case 9:{if(!d)m=8;else j=14;break}case 10:{if(!d)m=8;else j=14;break}default:j=14}while(0);if((j|0)==13)m=16;else if((j|0)==14){k[g>>2]=1138;k[g+4>>2]=2668;k[g+8>>2]=1523;yc(n,1084,g)|0;xc(n)|0;m=0}k[o>>2]=c;l=ub(a,b)|0;b=f+e|0;if(b>>>0>e>>>0){j=(l|0)==0;a=c;while(1){d=ha((h+3|0)>>>2,m)|0;g=ha(d,(i+3|0)>>>2)|0;if(!(e>>>0>15|(j|g>>>0<8))?(k[l>>2]|0)==519686845:0)vb(l,o,g,d,e)|0;a=a+g|0;k[o>>2]=a;e=e+1|0;if((e|0)==(b|0))break;else{i=i>>>1;h=h>>>1}}}if(!l){r=q;return}if((k[l>>2]|0)!=519686845){r=q;return}Nb(l);if(!(l&7)){kb(l,0,0,1,0)|0;r=q;return}else{k[p>>2]=1138;k[p+4>>2]=2502;k[p+8>>2]=1504;yc(n,1084,p)|0;xc(n)|0;r=q;return}}function Eb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0;g=a+92|0;d=k[a+4>>2]|0;f=a+88|0;e=k[f>>2]|0;b=(l[e+68>>0]|0)<<8|(l[e+67>>0]|0)<<16|(l[e+69>>0]|0);c=d+b|0;e=(l[e+65>>0]|0)<<8|(l[e+66>>0]|0);if(!e){a=0;return a|0}k[g>>2]=c;k[a+96>>2]=c;k[a+104>>2]=e;k[a+100>>2]=d+(e+b);k[a+108>>2]=0;k[a+112>>2]=0;if(!(qb(g,a+116|0)|0)){a=0;return a|0}b=k[f>>2]|0;do if(!((l[b+39>>0]|0)<<8|(l[b+40>>0]|0))){if(!((l[b+55>>0]|0)<<8|(l[b+56>>0]|0))){a=0;return a|0}}else{if(!(qb(g,a+140|0)|0)){a=0;return a|0}if(qb(g,a+188|0)|0){b=k[f>>2]|0;break}else{a=0;return a|0}}while(0);if((l[b+55>>0]|0)<<8|(l[b+56>>0]|0)){if(!(qb(g,a+164|0)|0)){a=0;return a|0}if(!(qb(g,a+212|0)|0)){a=0;return a|0}}a=1;return a|0}function Fb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0;q=r;r=r+576|0;h=q;g=q+64|0;p=q+16|0;d=a+88|0;b=k[d>>2]|0;o=(l[b+39>>0]|0)<<8|(l[b+40>>0]|0);m=a+236|0;f=a+240|0;c=k[f>>2]|0;if((c|0)!=(o|0)){if(c>>>0<=o>>>0){do if((k[a+244>>2]|0)>>>0<o>>>0){if(gb(m,o,(c+1|0)==(o|0),4,0)|0){b=k[f>>2]|0;break}i[a+248>>0]=1;p=0;r=q;return p|0}else b=c;while(0);$c((k[m>>2]|0)+(b<<2)|0,0,o-b<<2|0)|0;b=k[d>>2]|0}k[f>>2]=o}j=a+92|0;c=k[a+4>>2]|0;d=(l[b+34>>0]|0)<<8|(l[b+33>>0]|0)<<16|(l[b+35>>0]|0);e=c+d|0;b=(l[b+37>>0]|0)<<8|(l[b+36>>0]|0)<<16|(l[b+38>>0]|0);if(!b){p=0;r=q;return p|0}k[j>>2]=e;k[a+96>>2]=e;k[a+104>>2]=b;k[a+100>>2]=c+(b+d);k[a+108>>2]=0;k[a+112>>2]=0;k[p+20>>2]=0;k[p>>2]=0;k[p+4>>2]=0;k[p+8>>2]=0;k[p+12>>2]=0;i[p+16>>0]=0;a=p+24|0;k[p+44>>2]=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;i[a+16>>0]=0;if(qb(j,p)|0?(n=p+24|0,qb(j,n)|0):0){if(!(k[f>>2]|0)){k[h>>2]=1138;k[h+4>>2]=906;k[h+8>>2]=1769;yc(g,1084,h)|0;xc(g)|0}if(!o)b=1;else{d=0;e=0;f=0;b=0;g=0;a=0;h=0;c=k[m>>2]|0;while(1){d=(sb(j,p)|0)+d&31;e=(sb(j,n)|0)+e&63;f=(sb(j,p)|0)+f&31;b=(sb(j,p)|0)+b|0;g=(sb(j,n)|0)+g&63;a=(sb(j,p)|0)+a&31;k[c>>2]=e<<5|d<<11|f|b<<27|g<<21|a<<16;h=h+1|0;if((h|0)==(o|0)){b=1;break}else{b=b&31;c=c+4|0}}}}else b=0;nb(p+24|0);nb(p);p=b;r=q;return p|0}function Gb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;D=r;r=r+1008|0;g=D;f=D+496|0;C=D+472|0;A=D+276|0;B=D+80|0;z=D+16|0;e=k[a+88>>2]|0;y=(l[e+47>>0]|0)<<8|(l[e+48>>0]|0);x=a+92|0;b=k[a+4>>2]|0;c=(l[e+42>>0]|0)<<8|(l[e+41>>0]|0)<<16|(l[e+43>>0]|0);d=b+c|0;e=(l[e+45>>0]|0)<<8|(l[e+44>>0]|0)<<16|(l[e+46>>0]|0);if(!e){C=0;r=D;return C|0}k[x>>2]=d;k[a+96>>2]=d;k[a+104>>2]=e;k[a+100>>2]=b+(e+c);k[a+108>>2]=0;k[a+112>>2]=0;k[C+20>>2]=0;k[C>>2]=0;k[C+4>>2]=0;k[C+8>>2]=0;k[C+12>>2]=0;i[C+16>>0]=0;if(qb(x,C)|0){c=0;d=-3;e=-3;while(1){k[A+(c<<2)>>2]=d;k[B+(c<<2)>>2]=e;b=(d|0)>2;c=c+1|0;if((c|0)==49)break;else{d=b?-3:d+1|0;e=(b&1)+e|0}}b=z;c=b+64|0;do{k[b>>2]=0;b=b+4|0}while((b|0)<(c|0));w=a+252|0;c=a+256|0;b=k[c>>2]|0;a:do if((b|0)==(y|0))h=13;else{if(b>>>0<=y>>>0){do if((k[a+260>>2]|0)>>>0<y>>>0)if(gb(w,y,(b+1|0)==(y|0),4,0)|0){b=k[c>>2]|0;break}else{i[a+264>>0]=1;b=0;break a}while(0);$c((k[w>>2]|0)+(b<<2)|0,0,y-b<<2|0)|0}k[c>>2]=y;h=13}while(0);do if((h|0)==13){if(!y){k[g>>2]=1138;k[g+4>>2]=906;k[g+8>>2]=1769;yc(f,1084,g)|0;xc(f)|0;b=1;break}d=z+4|0;e=z+8|0;a=z+12|0;f=z+16|0;g=z+20|0;h=z+24|0;j=z+28|0;m=z+32|0;n=z+36|0;o=z+40|0;p=z+44|0;q=z+48|0;s=z+52|0;t=z+56|0;u=z+60|0;v=0;c=k[w>>2]|0;while(1){b=0;do{E=sb(x,C)|0;w=b<<1;F=z+(w<<2)|0;k[F>>2]=(k[F>>2]|0)+(k[A+(E<<2)>>2]|0)&3;w=z+((w|1)<<2)|0;k[w>>2]=(k[w>>2]|0)+(k[B+(E<<2)>>2]|0)&3;b=b+1|0}while((b|0)!=8);k[c>>2]=(l[1713+(k[d>>2]|0)>>0]|0)<<2|(l[1713+(k[z>>2]|0)>>0]|0)|(l[1713+(k[e>>2]|0)>>0]|0)<<4|(l[1713+(k[a>>2]|0)>>0]|0)<<6|(l[1713+(k[f>>2]|0)>>0]|0)<<8|(l[1713+(k[g>>2]|0)>>0]|0)<<10|(l[1713+(k[h>>2]|0)>>0]|0)<<12|(l[1713+(k[j>>2]|0)>>0]|0)<<14|(l[1713+(k[m>>2]|0)>>0]|0)<<16|(l[1713+(k[n>>2]|0)>>0]|0)<<18|(l[1713+(k[o>>2]|0)>>0]|0)<<20|(l[1713+(k[p>>2]|0)>>0]|0)<<22|(l[1713+(k[q>>2]|0)>>0]|0)<<24|(l[1713+(k[s>>2]|0)>>0]|0)<<26|(l[1713+(k[t>>2]|0)>>0]|0)<<28|(l[1713+(k[u>>2]|0)>>0]|0)<<30;v=v+1|0;if((v|0)==(y|0)){b=1;break}else c=c+4|0}}while(0)}else b=0;nb(C);F=b;r=D;return F|0}function Hb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,m=0,n=0,o=0,p=0;p=r;r=r+560|0;h=p;g=p+40|0;o=p+16|0;e=k[a+88>>2]|0;n=(l[e+55>>0]|0)<<8|(l[e+56>>0]|0);m=a+92|0;b=k[a+4>>2]|0;c=(l[e+50>>0]|0)<<8|(l[e+49>>0]|0)<<16|(l[e+51>>0]|0);d=b+c|0;e=(l[e+53>>0]|0)<<8|(l[e+52>>0]|0)<<16|(l[e+54>>0]|0);if(!e){o=0;r=p;return o|0}k[m>>2]=d;k[a+96>>2]=d;k[a+104>>2]=e;k[a+100>>2]=b+(e+c);k[a+108>>2]=0;k[a+112>>2]=0;k[o+20>>2]=0;k[o>>2]=0;k[o+4>>2]=0;k[o+8>>2]=0;k[o+12>>2]=0;i[o+16>>0]=0;a:do if(qb(m,o)|0){f=a+268|0;c=a+272|0;b=k[c>>2]|0;if((b|0)!=(n|0)){if(b>>>0<=n>>>0){do if((k[a+276>>2]|0)>>>0<n>>>0)if(gb(f,n,(b+1|0)==(n|0),2,0)|0){b=k[c>>2]|0;break}else{i[a+280>>0]=1;b=0;break a}while(0);$c((k[f>>2]|0)+(b<<1)|0,0,n-b<<1|0)|0}k[c>>2]=n}if(!n){k[h>>2]=1138;k[h+4>>2]=906;k[h+8>>2]=1769;yc(g,1084,h)|0;xc(g)|0;b=1;break}c=0;d=0;e=0;b=k[f>>2]|0;while(1){h=sb(m,o)|0;c=h+c&255;d=(sb(m,o)|0)+d&255;j[b>>1]=d<<8|c;e=e+1|0;if((e|0)==(n|0)){b=1;break}else b=b+2|0}}else b=0;while(0);nb(o);o=b;r=p;return o|0}function Ib(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0;E=r;r=r+2416|0;g=E;f=E+1904|0;D=E+1880|0;B=E+980|0;C=E+80|0;A=E+16|0;e=k[a+88>>2]|0;z=(l[e+63>>0]|0)<<8|(l[e+64>>0]|0);y=a+92|0;b=k[a+4>>2]|0;c=(l[e+58>>0]|0)<<8|(l[e+57>>0]|0)<<16|(l[e+59>>0]|0);d=b+c|0;e=(l[e+61>>0]|0)<<8|(l[e+60>>0]|0)<<16|(l[e+62>>0]|0);if(!e){D=0;r=E;return D|0}k[y>>2]=d;k[a+96>>2]=d;k[a+104>>2]=e;k[a+100>>2]=b+(e+c);k[a+108>>2]=0;k[a+112>>2]=0;k[D+20>>2]=0;k[D>>2]=0;k[D+4>>2]=0;k[D+8>>2]=0;k[D+12>>2]=0;i[D+16>>0]=0;if(qb(y,D)|0){c=0;d=-7;e=-7;while(1){k[B+(c<<2)>>2]=d;k[C+(c<<2)>>2]=e;b=(d|0)>6;c=c+1|0;if((c|0)==225)break;else{d=b?-7:d+1|0;e=(b&1)+e|0}}b=A;c=b+64|0;do{k[b>>2]=0;b=b+4|0}while((b|0)<(c|0));x=a+284|0;c=z*3|0;d=a+288|0;b=k[d>>2]|0;a:do if((b|0)==(c|0))h=13;else{if(b>>>0<=c>>>0){do if((k[a+292>>2]|0)>>>0<c>>>0)if(gb(x,c,(b+1|0)==(c|0),2,0)|0){b=k[d>>2]|0;break}else{i[a+296>>0]=1;b=0;break a}while(0);$c((k[x>>2]|0)+(b<<1)|0,0,c-b<<1|0)|0}k[d>>2]=c;h=13}while(0);do if((h|0)==13){if(!z){k[g>>2]=1138;k[g+4>>2]=906;k[g+8>>2]=1769;yc(f,1084,g)|0;xc(f)|0;b=1;break}d=A+4|0;e=A+8|0;a=A+12|0;f=A+16|0;g=A+20|0;h=A+24|0;m=A+28|0;n=A+32|0;o=A+36|0;p=A+40|0;q=A+44|0;s=A+48|0;t=A+52|0;u=A+56|0;v=A+60|0;w=0;c=k[x>>2]|0;while(1){b=0;do{F=sb(y,D)|0;x=b<<1;G=A+(x<<2)|0;k[G>>2]=(k[G>>2]|0)+(k[B+(F<<2)>>2]|0)&7;x=A+((x|1)<<2)|0;k[x>>2]=(k[x>>2]|0)+(k[C+(F<<2)>>2]|0)&7;b=b+1|0}while((b|0)!=8);F=l[1717+(k[g>>2]|0)>>0]|0;j[c>>1]=(l[1717+(k[d>>2]|0)>>0]|0)<<3|(l[1717+(k[A>>2]|0)>>0]|0)|(l[1717+(k[e>>2]|0)>>0]|0)<<6|(l[1717+(k[a>>2]|0)>>0]|0)<<9|(l[1717+(k[f>>2]|0)>>0]|0)<<12|F<<15;G=l[1717+(k[p>>2]|0)>>0]|0;j[c+2>>1]=(l[1717+(k[h>>2]|0)>>0]|0)<<2|F>>>1|(l[1717+(k[m>>2]|0)>>0]|0)<<5|(l[1717+(k[n>>2]|0)>>0]|0)<<8|(l[1717+(k[o>>2]|0)>>0]|0)<<11|G<<14;j[c+4>>1]=(l[1717+(k[q>>2]|0)>>0]|0)<<1|G>>>2|(l[1717+(k[s>>2]|0)>>0]|0)<<4|(l[1717+(k[t>>2]|0)>>0]|0)<<7|(l[1717+(k[u>>2]|0)>>0]|0)<<10|(l[1717+(k[v>>2]|0)>>0]|0)<<13;w=w+1|0;if((w|0)==(z|0)){b=1;break}else c=c+6|0}}while(0)}else b=0;nb(D);G=b;r=E;return G|0}function Jb(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ia=0,ja=0;ga=r;r=r+656|0;ea=ga+112|0;ca=ga+96|0;ba=ga+80|0;aa=ga+64|0;$=ga+48|0;fa=ga+32|0;da=ga+16|0;_=ga;Y=ga+144|0;Z=ga+128|0;R=a+240|0;S=k[R>>2]|0;V=a+256|0;W=k[V>>2]|0;c=i[(k[a+88>>2]|0)+17>>0]|0;X=d>>>2;if(!(c<<24>>24)){r=ga;return 1}T=(h|0)==0;U=h+-1|0;K=(f&1|0)!=0;L=d<<1;M=a+92|0;N=a+116|0;O=a+140|0;P=a+236|0;Q=g+-1|0;J=(e&1|0)!=0;I=a+188|0;D=a+252|0;E=X+1|0;F=X+2|0;G=X+3|0;H=Q<<4;B=c&255;c=0;f=0;e=1;C=0;do{if(!T){z=k[b+(C<<2)>>2]|0;A=0;while(1){w=A&1;j=(w|0)==0;v=(w<<5^32)+-16|0;w=(w<<1^2)+-1|0;y=j?g:-1;m=j?0:Q;a=(A|0)==(U|0);x=K&a;if((m|0)!=(y|0)){u=K&a^1;t=j?z:z+H|0;while(1){if((e|0)==1)e=sb(M,N)|0|512;s=e&7;e=e>>>3;j=l[1811+s>>0]|0;a=0;do{p=(sb(M,O)|0)+f|0;q=p-S|0;f=q>>31;f=f&p|q&~f;if((k[R>>2]|0)>>>0<=f>>>0){k[_>>2]=1138;k[_+4>>2]=906;k[_+8>>2]=1769;yc(Y,1084,_)|0;xc(Y)|0}k[Z+(a<<2)>>2]=k[(k[P>>2]|0)+(f<<2)>>2];a=a+1|0}while(a>>>0<j>>>0);q=J&(m|0)==(Q|0);if(x|q){p=0;do{n=ha(p,d)|0;a=t+n|0;j=(p|0)==0|u;o=p<<1;ja=(sb(M,I)|0)+c|0;ia=ja-W|0;c=ia>>31;c=c&ja|ia&~c;do if(q){if(!j){ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;break}k[a>>2]=k[Z+((l[1819+(s<<2)+o>>0]|0)<<2)>>2];if((k[V>>2]|0)>>>0<=c>>>0){k[ca>>2]=1138;k[ca+4>>2]=906;k[ca+8>>2]=1769;yc(Y,1084,ca)|0;xc(Y)|0}k[t+(n+4)>>2]=k[(k[D>>2]|0)+(c<<2)>>2];ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c}else{if(!j){ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;break}k[a>>2]=k[Z+((l[1819+(s<<2)+o>>0]|0)<<2)>>2];if((k[V>>2]|0)>>>0<=c>>>0){k[ba>>2]=1138;k[ba+4>>2]=906;k[ba+8>>2]=1769;yc(Y,1084,ba)|0;xc(Y)|0}k[t+(n+4)>>2]=k[(k[D>>2]|0)+(c<<2)>>2];ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;k[t+(n+8)>>2]=k[Z+((l[(o|1)+(1819+(s<<2))>>0]|0)<<2)>>2];if((k[V>>2]|0)>>>0<=c>>>0){k[ea>>2]=1138;k[ea+4>>2]=906;k[ea+8>>2]=1769;yc(Y,1084,ea)|0;xc(Y)|0}k[t+(n+12)>>2]=k[(k[D>>2]|0)+(c<<2)>>2]}while(0);p=p+1|0}while((p|0)!=2)}else{k[t>>2]=k[Z+((l[1819+(s<<2)>>0]|0)<<2)>>2];ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;if((k[V>>2]|0)>>>0<=c>>>0){k[da>>2]=1138;k[da+4>>2]=906;k[da+8>>2]=1769;yc(Y,1084,da)|0;xc(Y)|0}k[t+4>>2]=k[(k[D>>2]|0)+(c<<2)>>2];k[t+8>>2]=k[Z+((l[1819+(s<<2)+1>>0]|0)<<2)>>2];ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;if((k[V>>2]|0)>>>0<=c>>>0){k[fa>>2]=1138;k[fa+4>>2]=906;k[fa+8>>2]=1769;yc(Y,1084,fa)|0;xc(Y)|0}k[t+12>>2]=k[(k[D>>2]|0)+(c<<2)>>2];k[t+(X<<2)>>2]=k[Z+((l[1819+(s<<2)+2>>0]|0)<<2)>>2];ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;if((k[V>>2]|0)>>>0<=c>>>0){k[$>>2]=1138;k[$+4>>2]=906;k[$+8>>2]=1769;yc(Y,1084,$)|0;xc(Y)|0}k[t+(E<<2)>>2]=k[(k[D>>2]|0)+(c<<2)>>2];k[t+(F<<2)>>2]=k[Z+((l[1819+(s<<2)+3>>0]|0)<<2)>>2];ia=(sb(M,I)|0)+c|0;ja=ia-W|0;c=ja>>31;c=c&ia|ja&~c;if((k[V>>2]|0)>>>0<=c>>>0){k[aa>>2]=1138;k[aa+4>>2]=906;k[aa+8>>2]=1769;yc(Y,1084,aa)|0;xc(Y)|0}k[t+(G<<2)>>2]=k[(k[D>>2]|0)+(c<<2)>>2]}m=m+w|0;if((m|0)==(y|0))break;else t=t+v|0}}A=A+1|0;if((A|0)==(h|0))break;else z=z+L|0}}C=C+1|0}while((C|0)!=(B|0));r=ga;return 1}function Kb(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0;ma=r;r=r+608|0;ja=ma+48|0;la=ma+32|0;ka=ma+16|0;ia=ma;ga=ma+96|0;ha=ma+80|0;fa=ma+64|0;S=a+240|0;T=k[S>>2]|0;W=a+256|0;ca=k[W>>2]|0;da=a+272|0;ea=k[da>>2]|0;c=k[a+88>>2]|0;U=(l[c+63>>0]|0)<<8|(l[c+64>>0]|0);c=i[c+17>>0]|0;if(!(c<<24>>24)){r=ma;return 1}V=(h|0)==0;X=h+-1|0;Y=d<<1;Z=a+92|0;_=a+116|0;$=g+-1|0;aa=a+212|0;ba=a+188|0;R=(e&1|0)==0;Q=(f&1|0)==0;K=a+288|0;L=a+284|0;M=a+252|0;N=a+140|0;O=a+236|0;P=a+164|0;I=a+268|0;J=$<<5;G=c&255;c=0;e=0;f=0;a=0;j=1;H=0;do{if(!V){E=k[b+(H<<2)>>2]|0;F=0;while(1){C=F&1;n=(C|0)==0;B=(C<<6^64)+-32|0;C=(C<<1^2)+-1|0;D=n?g:-1;o=n?0:$;if((o|0)!=(D|0)){A=Q|(F|0)!=(X|0);z=n?E:E+J|0;while(1){if((j|0)==1)j=sb(Z,_)|0|512;y=j&7;j=j>>>3;p=l[1811+y>>0]|0;n=0;do{w=(sb(Z,P)|0)+e|0;x=w-ea|0;e=x>>31;e=e&w|x&~e;if((k[da>>2]|0)>>>0<=e>>>0){k[ia>>2]=1138;k[ia+4>>2]=906;k[ia+8>>2]=1769;yc(ga,1084,ia)|0;xc(ga)|0}k[fa+(n<<2)>>2]=m[(k[I>>2]|0)+(e<<1)>>1];n=n+1|0}while(n>>>0<p>>>0);n=0;do{w=(sb(Z,N)|0)+a|0;x=w-T|0;a=x>>31;a=a&w|x&~a;if((k[S>>2]|0)>>>0<=a>>>0){k[ka>>2]=1138;k[ka+4>>2]=906;k[ka+8>>2]=1769;yc(ga,1084,ka)|0;xc(ga)|0}k[ha+(n<<2)>>2]=k[(k[O>>2]|0)+(a<<2)>>2];n=n+1|0}while(n>>>0<p>>>0);x=R|(o|0)!=($|0);v=0;w=z;while(1){u=A|(v|0)==0;t=v<<1;q=0;s=w;while(1){p=(sb(Z,aa)|0)+c|0;n=p-U|0;c=n>>31;c=c&p|n&~c;n=(sb(Z,ba)|0)+f|0;p=n-ca|0;f=p>>31;f=f&n|p&~f;if((x|(q|0)==0)&u){n=l[q+t+(1819+(y<<2))>>0]|0;p=c*3|0;if((k[K>>2]|0)>>>0<=p>>>0){k[la>>2]=1138;k[la+4>>2]=906;k[la+8>>2]=1769;yc(ga,1084,la)|0;xc(ga)|0}na=k[L>>2]|0;k[s>>2]=(m[na+(p<<1)>>1]|0)<<16|k[fa+(n<<2)>>2];k[s+4>>2]=(m[na+(p+2<<1)>>1]|0)<<16|(m[na+(p+1<<1)>>1]|0);k[s+8>>2]=k[ha+(n<<2)>>2];if((k[W>>2]|0)>>>0<=f>>>0){k[ja>>2]=1138;k[ja+4>>2]=906;k[ja+8>>2]=1769;yc(ga,1084,ja)|0;xc(ga)|0}k[s+12>>2]=k[(k[M>>2]|0)+(f<<2)>>2]}q=q+1|0;if((q|0)==2)break;else s=s+16|0}v=v+1|0;if((v|0)==2)break;else w=w+d|0}o=o+C|0;if((o|0)==(D|0))break;else z=z+B|0}}F=F+1|0;if((F|0)==(h|0))break;else E=E+Y|0}}H=H+1|0}while((H|0)!=(G|0));r=ma;return 1}function Lb(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0;$=r;r=r+576|0;_=$+32|0;Z=$+16|0;Y=$;X=$+64|0;W=$+48|0;M=a+272|0;N=k[M>>2]|0;c=k[a+88>>2]|0;O=(l[c+63>>0]|0)<<8|(l[c+64>>0]|0);c=i[c+17>>0]|0;if(!(c<<24>>24)){r=$;return 1}P=(h|0)==0;Q=h+-1|0;R=d<<1;S=a+92|0;T=a+116|0;U=g+-1|0;V=a+212|0;L=(f&1|0)==0;I=a+288|0;J=a+284|0;K=a+164|0;G=a+268|0;H=U<<4;F=c&255;E=(e&1|0)!=0;c=0;f=0;a=1;D=0;do{if(!P){B=k[b+(D<<2)>>2]|0;C=0;while(1){z=C&1;e=(z|0)==0;y=(z<<5^32)+-16|0;z=(z<<1^2)+-1|0;A=e?g:-1;j=e?0:U;if((j|0)!=(A|0)){x=L|(C|0)!=(Q|0);w=e?B:B+H|0;while(1){if((a|0)==1)a=sb(S,T)|0|512;v=a&7;a=a>>>3;n=l[1811+v>>0]|0;e=0;do{t=(sb(S,K)|0)+f|0;u=t-N|0;f=u>>31;f=f&t|u&~f;if((k[M>>2]|0)>>>0<=f>>>0){k[Y>>2]=1138;k[Y+4>>2]=906;k[Y+8>>2]=1769;yc(X,1084,Y)|0;xc(X)|0}k[W+(e<<2)>>2]=m[(k[G>>2]|0)+(f<<1)>>1];e=e+1|0}while(e>>>0<n>>>0);u=(j|0)==(U|0)&E;s=0;t=w;while(1){q=x|(s|0)==0;p=s<<1;e=(sb(S,V)|0)+c|0;o=e-O|0;n=o>>31;n=n&e|o&~n;if(q){c=l[1819+(v<<2)+p>>0]|0;e=n*3|0;if((k[I>>2]|0)>>>0<=e>>>0){k[Z>>2]=1138;k[Z+4>>2]=906;k[Z+8>>2]=1769;yc(X,1084,Z)|0;xc(X)|0}o=k[J>>2]|0;k[t>>2]=(m[o+(e<<1)>>1]|0)<<16|k[W+(c<<2)>>2];k[t+4>>2]=(m[o+(e+2<<1)>>1]|0)<<16|(m[o+(e+1<<1)>>1]|0)}o=t+8|0;e=(sb(S,V)|0)+n|0;n=e-O|0;c=n>>31;c=c&e|n&~c;if(!(u|q^1)){e=l[(p|1)+(1819+(v<<2))>>0]|0;n=c*3|0;if((k[I>>2]|0)>>>0<=n>>>0){k[_>>2]=1138;k[_+4>>2]=906;k[_+8>>2]=1769;yc(X,1084,_)|0;xc(X)|0}q=k[J>>2]|0;k[o>>2]=(m[q+(n<<1)>>1]|0)<<16|k[W+(e<<2)>>2];k[t+12>>2]=(m[q+(n+2<<1)>>1]|0)<<16|(m[q+(n+1<<1)>>1]|0)}s=s+1|0;if((s|0)==2)break;else t=t+d|0}j=j+z|0;if((j|0)==(A|0))break;else w=w+y|0}}C=C+1|0;if((C|0)==(h|0))break;else B=B+R|0}}D=D+1|0}while((D|0)!=(F|0));r=$;return 1}function Mb(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0;ha=r;r=r+608|0;ea=ha+48|0;ga=ha+32|0;fa=ha+16|0;da=ha;ca=ha+96|0;aa=ha+80|0;ba=ha+64|0;S=a+272|0;T=k[S>>2]|0;c=k[a+88>>2]|0;U=(l[c+63>>0]|0)<<8|(l[c+64>>0]|0);c=i[c+17>>0]|0;if(!(c<<24>>24)){r=ha;return 1}V=(h|0)==0;W=h+-1|0;X=d<<1;Y=a+92|0;Z=a+116|0;_=g+-1|0;$=a+212|0;R=(e&1|0)==0;Q=(f&1|0)==0;N=a+288|0;O=a+284|0;P=a+164|0;L=a+268|0;M=_<<5;J=c&255;c=0;e=0;f=0;a=0;j=1;K=0;do{if(!V){H=k[b+(K<<2)>>2]|0;I=0;while(1){F=I&1;n=(F|0)==0;E=(F<<6^64)+-32|0;F=(F<<1^2)+-1|0;G=n?g:-1;o=n?0:_;if((o|0)!=(G|0)){D=Q|(I|0)!=(W|0);C=n?H:H+M|0;while(1){if((j|0)==1)j=sb(Y,Z)|0|512;B=j&7;j=j>>>3;p=l[1811+B>>0]|0;n=0;do{z=(sb(Y,P)|0)+a|0;A=z-T|0;a=A>>31;a=a&z|A&~a;if((k[S>>2]|0)>>>0<=a>>>0){k[da>>2]=1138;k[da+4>>2]=906;k[da+8>>2]=1769;yc(ca,1084,da)|0;xc(ca)|0}k[aa+(n<<2)>>2]=m[(k[L>>2]|0)+(a<<1)>>1];n=n+1|0}while(n>>>0<p>>>0);n=0;do{z=(sb(Y,P)|0)+e|0;A=z-T|0;e=A>>31;e=e&z|A&~e;if((k[S>>2]|0)>>>0<=e>>>0){k[fa>>2]=1138;k[fa+4>>2]=906;k[fa+8>>2]=1769;yc(ca,1084,fa)|0;xc(ca)|0}k[ba+(n<<2)>>2]=m[(k[L>>2]|0)+(e<<1)>>1];n=n+1|0}while(n>>>0<p>>>0);A=R|(o|0)!=(_|0);y=0;z=C;while(1){x=D|(y|0)==0;w=y<<1;u=0;v=z;while(1){t=(sb(Y,$)|0)+f|0;s=t-U|0;f=s>>31;f=f&t|s&~f;s=(sb(Y,$)|0)+c|0;t=s-U|0;c=t>>31;c=c&s|t&~c;if((A|(u|0)==0)&x){s=l[u+w+(1819+(B<<2))>>0]|0;t=f*3|0;n=k[N>>2]|0;if(n>>>0<=t>>>0){k[ga>>2]=1138;k[ga+4>>2]=906;k[ga+8>>2]=1769;yc(ca,1084,ga)|0;xc(ca)|0;n=k[N>>2]|0}p=k[O>>2]|0;q=c*3|0;if(n>>>0>q>>>0)n=p;else{k[ea>>2]=1138;k[ea+4>>2]=906;k[ea+8>>2]=1769;yc(ca,1084,ea)|0;xc(ca)|0;n=k[O>>2]|0}k[v>>2]=(m[p+(t<<1)>>1]|0)<<16|k[aa+(s<<2)>>2];k[v+4>>2]=(m[p+(t+2<<1)>>1]|0)<<16|(m[p+(t+1<<1)>>1]|0);k[v+8>>2]=(m[n+(q<<1)>>1]|0)<<16|k[ba+(s<<2)>>2];k[v+12>>2]=(m[n+(q+2<<1)>>1]|0)<<16|(m[n+(q+1<<1)>>1]|0)}u=u+1|0;if((u|0)==2)break;else v=v+16|0}y=y+1|0;if((y|0)==2)break;else z=z+d|0}o=o+F|0;if((o|0)==(G|0))break;else C=C+E|0}}I=I+1|0;if((I|0)==(h|0))break;else H=H+X|0}}K=K+1|0}while((K|0)!=(J|0));r=ha;return 1}function Nb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,j=0;j=r;r=r+576|0;h=j+48|0;f=j+32|0;e=j+16|0;d=j;g=j+64|0;k[a>>2]=0;b=a+284|0;c=k[b>>2]|0;if(c){if(!(c&7))kb(c,0,0,1,0)|0;else{k[d>>2]=1138;k[d+4>>2]=2502;k[d+8>>2]=1504;yc(g,1084,d)|0;xc(g)|0}k[b>>2]=0;k[a+288>>2]=0;k[a+292>>2]=0}i[a+296>>0]=0;c=a+268|0;b=k[c>>2]|0;if(b){if(!(b&7))kb(b,0,0,1,0)|0;else{k[e>>2]=1138;k[e+4>>2]=2502;k[e+8>>2]=1504;yc(g,1084,e)|0;xc(g)|0}k[c>>2]=0;k[a+272>>2]=0;k[a+276>>2]=0}i[a+280>>0]=0;b=a+252|0;c=k[b>>2]|0;if(c){if(!(c&7))kb(c,0,0,1,0)|0;else{k[f>>2]=1138;k[f+4>>2]=2502;k[f+8>>2]=1504;yc(g,1084,f)|0;xc(g)|0}k[b>>2]=0;k[a+256>>2]=0;k[a+260>>2]=0}i[a+264>>0]=0;b=a+236|0;c=k[b>>2]|0;if(!c){h=a+248|0;i[h>>0]=0;h=a+212|0;nb(h);h=a+188|0;nb(h);h=a+164|0;nb(h);h=a+140|0;nb(h);h=a+116|0;nb(h);r=j;return}if(!(c&7))kb(c,0,0,1,0)|0;else{k[h>>2]=1138;k[h+4>>2]=2502;k[h+8>>2]=1504;yc(g,1084,h)|0;xc(g)|0}k[b>>2]=0;k[a+240>>2]=0;k[a+244>>2]=0;h=a+248|0;i[h>>0]=0;h=a+212|0;nb(h);h=a+188|0;nb(h);h=a+164|0;nb(h);h=a+140|0;nb(h);h=a+116|0;nb(h);r=j;return}function Ob(a,b){a=a|0;b=b|0;var c=0;c=r;r=r+16|0;k[c>>2]=b;b=k[60]|0;zc(b,a,c)|0;tc(10,b)|0;Aa()}function Pb(){var a=0,b=0;a=r;r=r+16|0;if(!(Ka(192,2)|0)){b=va(k[47]|0)|0;r=a;return b|0}else Ob(2078,a);return 0}function Qb(a){a=a|0;Tc(a);return}function Rb(a){a=a|0;var b=0;b=r;r=r+16|0;Wa[a&3]();Ob(2127,b)}function Sb(){var a=0,b=0;a=Pb()|0;if(((a|0)!=0?(b=k[a>>2]|0,(b|0)!=0):0)?(a=b+48|0,(k[a>>2]&-256|0)==1126902528?(k[a+4>>2]|0)==1129074247:0):0)Rb(k[b+12>>2]|0);b=k[26]|0;k[26]=b+0;Rb(b)}function Tb(a){a=a|0;return}function Ub(a){a=a|0;return}function Vb(a){a=a|0;return}function Wb(a){a=a|0;return}function Xb(a){a=a|0;Qb(a);return}function Yb(a){a=a|0;Qb(a);return}function Zb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;g=r;r=r+64|0;f=g;if((a|0)!=(b|0))if((b|0)!=0?(e=bc(b,24,40,0)|0,(e|0)!=0):0){b=f;d=b+56|0;do{k[b>>2]=0;b=b+4|0}while((b|0)<(d|0));k[f>>2]=e;k[f+8>>2]=a;k[f+12>>2]=-1;k[f+48>>2]=1;Ya[k[(k[e>>2]|0)+28>>2]&3](e,f,k[c>>2]|0,1);if((k[f+24>>2]|0)==1){k[c>>2]=k[f+16>>2];b=1}else b=0}else b=0;else b=1;r=g;return b|0}function _b(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;a=b+16|0;e=k[a>>2]|0;do if(e){if((e|0)!=(c|0)){d=b+36|0;k[d>>2]=(k[d>>2]|0)+1;k[b+24>>2]=2;i[b+54>>0]=1;break}a=b+24|0;if((k[a>>2]|0)==2)k[a>>2]=d}else{k[a>>2]=c;k[b+24>>2]=d;k[b+36>>2]=1}while(0);return}function $b(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if((a|0)==(k[b+8>>2]|0))_b(0,b,c,d);return}function ac(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if((a|0)==(k[b+8>>2]|0))_b(0,b,c,d);else{a=k[a+8>>2]|0;Ya[k[(k[a>>2]|0)+28>>2]&3](a,b,c,d)}return}function bc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0,p=0,q=0;q=r;r=r+64|0;p=q;o=k[a>>2]|0;n=a+(k[o+-8>>2]|0)|0;o=k[o+-4>>2]|0;k[p>>2]=c;k[p+4>>2]=a;k[p+8>>2]=b;k[p+12>>2]=d;d=p+16|0;a=p+20|0;b=p+24|0;e=p+28|0;f=p+32|0;g=p+40|0;h=(o|0)==(c|0);l=d;m=l+36|0;do{k[l>>2]=0;l=l+4|0}while((l|0)<(m|0));j[d+36>>1]=0;i[d+38>>0]=0;a:do if(h){k[p+48>>2]=1;Xa[k[(k[c>>2]|0)+20>>2]&3](c,p,n,n,1,0);d=(k[b>>2]|0)==1?n:0}else{Sa[k[(k[o>>2]|0)+24>>2]&3](o,p,n,1,0);switch(k[p+36>>2]|0){case 0:{d=(k[g>>2]|0)==1&(k[e>>2]|0)==1&(k[f>>2]|0)==1?k[a>>2]|0:0;break a}case 1:break;default:{d=0;break a}}if((k[b>>2]|0)!=1?!((k[g>>2]|0)==0&(k[e>>2]|0)==1&(k[f>>2]|0)==1):0){d=0;break}d=k[d>>2]|0}while(0);r=q;return d|0}function cc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;i[b+53>>0]=1;do if((k[b+4>>2]|0)==(d|0)){i[b+52>>0]=1;d=b+16|0;a=k[d>>2]|0;if(!a){k[d>>2]=c;k[b+24>>2]=e;k[b+36>>2]=1;if(!((e|0)==1?(k[b+48>>2]|0)==1:0))break;i[b+54>>0]=1;break}if((a|0)!=(c|0)){e=b+36|0;k[e>>2]=(k[e>>2]|0)+1;i[b+54>>0]=1;break}a=b+24|0;d=k[a>>2]|0;if((d|0)==2){k[a>>2]=e;d=e}if((d|0)==1?(k[b+48>>2]|0)==1:0)i[b+54>>0]=1}while(0);return}function dc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0;a:do if((a|0)==(k[b+8>>2]|0)){if((k[b+4>>2]|0)==(c|0)?(f=b+28|0,(k[f>>2]|0)!=1):0)k[f>>2]=d}else{if((a|0)!=(k[b>>2]|0)){h=k[a+8>>2]|0;Sa[k[(k[h>>2]|0)+24>>2]&3](h,b,c,d,e);break}if((k[b+16>>2]|0)!=(c|0)?(g=b+20|0,(k[g>>2]|0)!=(c|0)):0){k[b+32>>2]=d;d=b+44|0;if((k[d>>2]|0)==4)break;f=b+52|0;i[f>>0]=0;j=b+53|0;i[j>>0]=0;a=k[a+8>>2]|0;Xa[k[(k[a>>2]|0)+20>>2]&3](a,b,c,c,1,e);if(i[j>>0]|0){if(!(i[f>>0]|0)){f=1;h=13}}else{f=0;h=13}do if((h|0)==13){k[g>>2]=c;j=b+40|0;k[j>>2]=(k[j>>2]|0)+1;if((k[b+36>>2]|0)==1?(k[b+24>>2]|0)==2:0){i[b+54>>0]=1;if(f)break}else h=16;if((h|0)==16?f:0)break;k[d>>2]=4;break a}while(0);k[d>>2]=3;break}if((d|0)==1)k[b+32>>2]=1}while(0);return}function ec(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;do if((a|0)==(k[b+8>>2]|0)){if((k[b+4>>2]|0)==(c|0)?(g=b+28|0,(k[g>>2]|0)!=1):0)k[g>>2]=d}else if((a|0)==(k[b>>2]|0)){if((k[b+16>>2]|0)!=(c|0)?(f=b+20|0,(k[f>>2]|0)!=(c|0)):0){k[b+32>>2]=d;k[f>>2]=c;e=b+40|0;k[e>>2]=(k[e>>2]|0)+1;if((k[b+36>>2]|0)==1?(k[b+24>>2]|0)==2:0)i[b+54>>0]=1;k[b+44>>2]=4;break}if((d|0)==1)k[b+32>>2]=1}while(0);return}function fc(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if((a|0)==(k[b+8>>2]|0))cc(0,b,c,d,e);else{a=k[a+8>>2]|0;Xa[k[(k[a>>2]|0)+20>>2]&3](a,b,c,d,e,f)}return}function gc(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if((a|0)==(k[b+8>>2]|0))cc(0,b,c,d,e);return}function hc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=r;r=r+16|0;d=e;k[d>>2]=k[c>>2];a=Ra[k[(k[a>>2]|0)+16>>2]&7](a,b,d)|0;if(a)k[c>>2]=k[d>>2];r=e;return a&1|0}function ic(a){a=a|0;if(!a)a=0;else a=(bc(a,24,72,0)|0)!=0;return a&1|0}function jc(){var a=0,b=0,c=0,d=0,e=0,f=0,g=0,h=0;e=r;r=r+48|0;g=e+32|0;c=e+24|0;h=e+16|0;f=e;e=e+36|0;a=Pb()|0;if((a|0)!=0?(d=k[a>>2]|0,(d|0)!=0):0){a=d+48|0;b=k[a>>2]|0;a=k[a+4>>2]|0;if(!((b&-256|0)==1126902528&(a|0)==1129074247)){k[c>>2]=2406;Ob(2356,c)}if((b|0)==1126902529&(a|0)==1129074247)a=k[d+44>>2]|0;else a=d+80|0;k[e>>2]=a;d=k[d>>2]|0;a=k[d+4>>2]|0;if(Ra[k[(k[8>>2]|0)+16>>2]&7](8,d,e)|0){h=k[e>>2]|0;h=Ua[k[(k[h>>2]|0)+8>>2]&1](h)|0;k[f>>2]=2406;k[f+4>>2]=a;k[f+8>>2]=h;Ob(2270,f)}else{k[h>>2]=2406;k[h+4>>2]=a;Ob(2315,h)}}Ob(2394,g)}function kc(){var a=0;a=r;r=r+16|0;if(!(Fa(188,6)|0)){r=a;return}else Ob(2167,a)}function lc(a){a=a|0;var b=0;b=r;r=r+16|0;Tc(a);if(!(wa(k[47]|0,0)|0)){r=b;return}else Ob(2217,b)}function mc(a){a=a|0;var b=0,c=0;b=0;while(1){if((l[2415+b>>0]|0)==(a|0)){c=2;break}b=b+1|0;if((b|0)==87){b=87;a=2503;c=5;break}}if((c|0)==2)if(!b)a=2503;else{a=2503;c=5}if((c|0)==5)while(1){c=a;while(1){a=c+1|0;if(!(i[c>>0]|0))break;else c=a}b=b+-1|0;if(!b)break;else c=5}return a|0}function nc(){var a=0;if(!0)a=248;else{a=(za()|0)+60|0;a=k[a>>2]|0}return a|0}function oc(a){a=a|0;var b=0;if(a>>>0>4294963200){b=nc()|0;k[b>>2]=0-a;a=-1}return a|0}function pc(a,b){a=+a;b=b|0;var c=0,d=0,e=0;p[t>>3]=a;c=k[t>>2]|0;d=k[t+4>>2]|0;e=ad(c|0,d|0,52)|0;e=e&2047;switch(e|0){case 0:{if(a!=0.0){a=+pc(a*18446744073709552.0e3,b);c=(k[b>>2]|0)+-64|0}else c=0;k[b>>2]=c;break}case 2047:break;default:{k[b>>2]=e+-1022;k[t>>2]=c;k[t+4>>2]=d&-2146435073|1071644672;a=+p[t>>3]}}return +a}function qc(a,b){a=+a;b=b|0;return +(+pc(a,b))}function rc(a,b,c){a=a|0;b=b|0;c=c|0;do if(a){if(b>>>0<128){i[a>>0]=b;a=1;break}if(b>>>0<2048){i[a>>0]=b>>>6|192;i[a+1>>0]=b&63|128;a=2;break}if(b>>>0<55296|(b&-8192|0)==57344){i[a>>0]=b>>>12|224;i[a+1>>0]=b>>>6&63|128;i[a+2>>0]=b&63|128;a=3;break}if((b+-65536|0)>>>0<1048576){i[a>>0]=b>>>18|240;i[a+1>>0]=b>>>12&63|128;i[a+2>>0]=b>>>6&63|128;i[a+3>>0]=b&63|128;a=4;break}else{a=nc()|0;k[a>>2]=84;a=-1;break}}else a=1;while(0);return a|0}function sc(a,b){a=a|0;b=b|0;if(!a)a=0;else a=rc(a,b,0)|0;return a|0}function tc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;if((k[b+76>>2]|0)>=0?(Cc(b)|0)!=0:0){if((i[b+75>>0]|0)!=(a|0)?(d=b+20|0,e=k[d>>2]|0,e>>>0<(k[b+16>>2]|0)>>>0):0){k[d>>2]=e+1;i[e>>0]=a;c=a&255}else c=Ec(b,a)|0;Dc(b)}else g=3;do if((g|0)==3){if((i[b+75>>0]|0)!=(a|0)?(f=b+20|0,c=k[f>>2]|0,c>>>0<(k[b+16>>2]|0)>>>0):0){k[f>>2]=c+1;i[c>>0]=a;c=a&255;break}c=Ec(b,a)|0}while(0);return c|0}function uc(a,b){a=a|0;b=b|0;return (wc(a,Lc(a)|0,1,b)|0)+-1|0}function vc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=c+16|0;e=k[d>>2]|0;if(!e)if(!(Jc(c)|0)){e=k[d>>2]|0;f=4}else d=0;else f=4;a:do if((f|0)==4){g=c+20|0;f=k[g>>2]|0;if((e-f|0)>>>0<b>>>0){d=Ra[k[c+36>>2]&7](c,a,b)|0;break}b:do if((i[c+75>>0]|0)>-1){d=b;while(1){if(!d){e=f;d=0;break b}e=d+-1|0;if((i[a+e>>0]|0)==10)break;else d=e}if((Ra[k[c+36>>2]&7](c,a,d)|0)>>>0<d>>>0)break a;b=b-d|0;a=a+d|0;e=k[g>>2]|0}else{e=f;d=0}while(0);cd(e|0,a|0,b|0)|0;k[g>>2]=(k[g>>2]|0)+b;d=d+b|0}while(0);return d|0}function wc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;e=ha(c,b)|0;if((k[d+76>>2]|0)>-1){f=(Cc(d)|0)==0;a=vc(a,e,d)|0;if(!f)Dc(d)}else a=vc(a,e,d)|0;if((a|0)!=(e|0))c=(a>>>0)/(b>>>0)|0;return c|0}function xc(a){a=a|0;var b=0,c=0,d=0,e=0;d=k[61]|0;if((k[d+76>>2]|0)>-1)e=Cc(d)|0;else e=0;do if((uc(a,d)|0)<0)b=1;else{if((i[d+75>>0]|0)!=10?(b=d+20|0,c=k[b>>2]|0,c>>>0<(k[d+16>>2]|0)>>>0):0){k[b>>2]=c+1;i[c>>0]=10;b=0;break}b=(Ec(d,10)|0)<0}while(0);if(e)Dc(d);return b<<31>>31|0}function yc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=r;r=r+16|0;e=d;k[e>>2]=c;c=Bc(a,b,e)|0;r=d;return c|0}function zc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0;s=r;r=r+224|0;n=s+120|0;q=s+80|0;p=s;o=s+136|0;d=q;e=d+40|0;do{k[d>>2]=0;d=d+4|0}while((d|0)<(e|0));k[n>>2]=k[c>>2];if((Mc(0,b,n,p,q)|0)<0)c=-1;else{if((k[a+76>>2]|0)>-1)l=Cc(a)|0;else l=0;c=k[a>>2]|0;m=c&32;if((i[a+74>>0]|0)<1)k[a>>2]=c&-33;c=a+48|0;if(!(k[c>>2]|0)){e=a+44|0;f=k[e>>2]|0;k[e>>2]=o;g=a+28|0;k[g>>2]=o;h=a+20|0;k[h>>2]=o;k[c>>2]=80;j=a+16|0;k[j>>2]=o+80;d=Mc(a,b,n,p,q)|0;if(f){Ra[k[a+36>>2]&7](a,0,0)|0;d=(k[h>>2]|0)==0?-1:d;k[e>>2]=f;k[c>>2]=0;k[j>>2]=0;k[g>>2]=0;k[h>>2]=0}}else d=Mc(a,b,n,p,q)|0;c=k[a>>2]|0;k[a>>2]=c|m;if(l)Dc(a);c=(c&32|0)==0?d:-1}r=s;return c|0}function Ac(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,l=0,m=0;m=r;r=r+128|0;e=m+112|0;l=m;f=l;g=252;h=f+112|0;do{k[f>>2]=k[g>>2];f=f+4|0;g=g+4|0}while((f|0)<(h|0));if((b+-1|0)>>>0>2147483646)if(!b){b=1;j=4}else{b=nc()|0;k[b>>2]=75;b=-1}else{e=a;j=4}if((j|0)==4){j=-2-e|0;j=b>>>0>j>>>0?j:b;k[l+48>>2]=j;a=l+20|0;k[a>>2]=e;k[l+44>>2]=e;b=e+j|0;e=l+16|0;k[e>>2]=b;k[l+28>>2]=b;b=zc(l,c,d)|0;if(j){c=k[a>>2]|0;i[c+(((c|0)==(k[e>>2]|0))<<31>>31)>>0]=0}}r=m;return b|0}function Bc(a,b,c){a=a|0;b=b|0;c=c|0;return Ac(a,2147483647,b,c)|0}function Cc(a){a=a|0;return 0}function Dc(a){a=a|0;return}function Ec(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0;j=r;r=r+16|0;h=j;g=b&255;i[h>>0]=g;d=a+16|0;e=k[d>>2]|0;if(!e)if(!(Jc(a)|0)){e=k[d>>2]|0;f=4}else c=-1;else f=4;do if((f|0)==4){d=a+20|0;f=k[d>>2]|0;if(f>>>0<e>>>0?(c=b&255,(c|0)!=(i[a+75>>0]|0)):0){k[d>>2]=f+1;i[f>>0]=g;break}if((Ra[k[a+36>>2]&7](a,h,1)|0)==1)c=l[h>>0]|0;else c=-1}while(0);r=j;return c|0}function Fc(a){a=a|0;var b=0,c=0;b=r;r=r+16|0;c=b;k[c>>2]=k[a+60>>2];a=oc(Ca(6,c|0)|0)|0;r=b;return a|0}function Gc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;e=r;r=r+32|0;f=e;d=e+20|0;k[f>>2]=k[a+60>>2];k[f+4>>2]=0;k[f+8>>2]=b;k[f+12>>2]=d;k[f+16>>2]=c;if((oc(Ja(140,f|0)|0)|0)<0){k[d>>2]=-1;a=-1}else a=k[d>>2]|0;r=e;return a|0}function Hc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;p=r;r=r+48|0;m=p+16|0;l=p;d=p+32|0;n=a+28|0;e=k[n>>2]|0;k[d>>2]=e;o=a+20|0;e=(k[o>>2]|0)-e|0;k[d+4>>2]=e;k[d+8>>2]=b;k[d+12>>2]=c;i=a+60|0;j=a+44|0;b=2;e=e+c|0;while(1){if(!(k[49]|0)){k[m>>2]=k[i>>2];k[m+4>>2]=d;k[m+8>>2]=b;g=oc(Na(146,m|0)|0)|0}else{Ba(7,a|0);k[l>>2]=k[i>>2];k[l+4>>2]=d;k[l+8>>2]=b;g=oc(Na(146,l|0)|0)|0;ua(0)}if((e|0)==(g|0)){e=6;break}if((g|0)<0){e=8;break}e=e-g|0;f=k[d+4>>2]|0;if(g>>>0<=f>>>0)if((b|0)==2){k[n>>2]=(k[n>>2]|0)+g;h=f;b=2}else h=f;else{h=k[j>>2]|0;k[n>>2]=h;k[o>>2]=h;h=k[d+12>>2]|0;g=g-f|0;d=d+8|0;b=b+-1|0}k[d>>2]=(k[d>>2]|0)+g;k[d+4>>2]=h-g}if((e|0)==6){m=k[j>>2]|0;k[a+16>>2]=m+(k[a+48>>2]|0);a=m;k[n>>2]=a;k[o>>2]=a}else if((e|0)==8){k[a+16>>2]=0;k[n>>2]=0;k[o>>2]=0;k[a>>2]=k[a>>2]|32;if((b|0)==2)c=0;else c=c-(k[d+4>>2]|0)|0}r=p;return c|0}function Ic(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=r;r=r+80|0;d=e;k[a+36>>2]=3;if((k[a>>2]&64|0)==0?(k[d>>2]=k[a+60>>2],k[d+4>>2]=21505,k[d+8>>2]=e+12,(Ia(54,d|0)|0)!=0):0)i[a+75>>0]=-1;d=Hc(a,b,c)|0;r=e;return d|0}function Jc(a){a=a|0;var b=0,c=0;b=a+74|0;c=i[b>>0]|0;i[b>>0]=c+255|c;b=k[a>>2]|0;if(!(b&8)){k[a+8>>2]=0;k[a+4>>2]=0;b=k[a+44>>2]|0;k[a+28>>2]=b;k[a+20>>2]=b;k[a+16>>2]=b+(k[a+48>>2]|0);b=0}else{k[a>>2]=b|32;b=-1}return b|0}function Kc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;f=b&255;d=(c|0)!=0;a:do if(d&(a&3|0)!=0){e=b&255;while(1){if((i[a>>0]|0)==e<<24>>24){g=6;break a}a=a+1|0;c=c+-1|0;d=(c|0)!=0;if(!(d&(a&3|0)!=0)){g=5;break}}}else g=5;while(0);if((g|0)==5)if(d)g=6;else c=0;b:do if((g|0)==6){e=b&255;if((i[a>>0]|0)!=e<<24>>24){d=ha(f,16843009)|0;c:do if(c>>>0>3)while(1){f=k[a>>2]^d;if((f&-2139062144^-2139062144)&f+-16843009)break;a=a+4|0;c=c+-4|0;if(c>>>0<=3){g=11;break c}}else g=11;while(0);if((g|0)==11)if(!c){c=0;break}while(1){if((i[a>>0]|0)==e<<24>>24)break b;a=a+1|0;c=c+-1|0;if(!c){c=0;break}}}}while(0);return ((c|0)!=0?a:0)|0}function Lc(a){a=a|0;var b=0,c=0,d=0;d=a;a:do if(!(d&3))c=4;else{b=a;a=d;while(1){if(!(i[b>>0]|0))break a;b=b+1|0;a=b;if(!(a&3)){a=b;c=4;break}}}while(0);if((c|0)==4){while(1){b=k[a>>2]|0;if(!((b&-2139062144^-2139062144)&b+-16843009))a=a+4|0;else break}if((b&255)<<24>>24)do a=a+1|0;while((i[a>>0]|0)!=0)}return a-d|0}function Mc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,m=0,n=0.0,o=0,q=0,s=0,u=0,v=0.0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0;ga=r;r=r+624|0;ba=ga+24|0;da=ga+16|0;ca=ga+588|0;Y=ga+576|0;aa=ga;V=ga+536|0;fa=ga+8|0;ea=ga+528|0;M=(a|0)!=0;N=V+40|0;U=N;V=V+39|0;W=fa+4|0;X=Y+12|0;Y=Y+11|0;Z=ca;_=X;$=_-Z|0;O=-2-Z|0;P=_+2|0;Q=ba+288|0;R=ca+9|0;S=R;T=ca+8|0;f=0;w=b;g=0;b=0;a:while(1){do if((f|0)>-1)if((g|0)>(2147483647-f|0)){f=nc()|0;k[f>>2]=75;f=-1;break}else{f=g+f|0;break}while(0);g=i[w>>0]|0;if(!(g<<24>>24)){K=245;break}else h=w;b:while(1){switch(g<<24>>24){case 37:{g=h;K=9;break b}case 0:{g=h;break b}default:{}}J=h+1|0;g=i[J>>0]|0;h=J}c:do if((K|0)==9)while(1){K=0;if((i[g+1>>0]|0)!=37)break c;h=h+1|0;g=g+2|0;if((i[g>>0]|0)==37)K=9;else break}while(0);y=h-w|0;if(M?(k[a>>2]&32|0)==0:0)vc(w,y,a)|0;if((h|0)!=(w|0)){w=g;g=y;continue}o=g+1|0;h=i[o>>0]|0;m=(h<<24>>24)+-48|0;if(m>>>0<10){J=(i[g+2>>0]|0)==36;o=J?g+3|0:o;h=i[o>>0]|0;u=J?m:-1;b=J?1:b}else u=-1;g=h<<24>>24;d:do if((g&-32|0)==32){m=0;while(1){if(!(1<<g+-32&75913)){q=m;g=o;break d}m=1<<(h<<24>>24)+-32|m;o=o+1|0;h=i[o>>0]|0;g=h<<24>>24;if((g&-32|0)!=32){q=m;g=o;break}}}else{q=0;g=o}while(0);do if(h<<24>>24==42){m=g+1|0;h=(i[m>>0]|0)+-48|0;if(h>>>0<10?(i[g+2>>0]|0)==36:0){k[e+(h<<2)>>2]=10;b=1;g=g+3|0;h=k[d+((i[m>>0]|0)+-48<<3)>>2]|0}else{if(b){f=-1;break a}if(!M){x=q;g=m;b=0;J=0;break}b=(k[c>>2]|0)+(4-1)&~(4-1);h=k[b>>2]|0;k[c>>2]=b+4;b=0;g=m}if((h|0)<0){x=q|8192;J=0-h|0}else{x=q;J=h}}else{m=(h<<24>>24)+-48|0;if(m>>>0<10){h=0;do{h=(h*10|0)+m|0;g=g+1|0;m=(i[g>>0]|0)+-48|0}while(m>>>0<10);if((h|0)<0){f=-1;break a}else{x=q;J=h}}else{x=q;J=0}}while(0);e:do if((i[g>>0]|0)==46){m=g+1|0;h=i[m>>0]|0;if(h<<24>>24!=42){o=(h<<24>>24)+-48|0;if(o>>>0<10){g=m;h=0}else{g=m;o=0;break}while(1){h=(h*10|0)+o|0;g=g+1|0;o=(i[g>>0]|0)+-48|0;if(o>>>0>=10){o=h;break e}}}m=g+2|0;h=(i[m>>0]|0)+-48|0;if(h>>>0<10?(i[g+3>>0]|0)==36:0){k[e+(h<<2)>>2]=10;g=g+4|0;o=k[d+((i[m>>0]|0)+-48<<3)>>2]|0;break}if(b){f=-1;break a}if(M){g=(k[c>>2]|0)+(4-1)&~(4-1);o=k[g>>2]|0;k[c>>2]=g+4;g=m}else{g=m;o=0}}else o=-1;while(0);s=0;while(1){h=(i[g>>0]|0)+-65|0;if(h>>>0>57){f=-1;break a}m=g+1|0;h=i[5347+(s*58|0)+h>>0]|0;q=h&255;if((q+-1|0)>>>0<8){g=m;s=q}else{I=m;break}}if(!(h<<24>>24)){f=-1;break}m=(u|0)>-1;do if(h<<24>>24==19)if(m){f=-1;break a}else K=52;else{if(m){k[e+(u<<2)>>2]=q;G=d+(u<<3)|0;H=k[G+4>>2]|0;K=aa;k[K>>2]=k[G>>2];k[K+4>>2]=H;K=52;break}if(!M){f=0;break a}Pc(aa,q,c)}while(0);if((K|0)==52?(K=0,!M):0){w=I;g=y;continue}u=i[g>>0]|0;u=(s|0)!=0&(u&15|0)==3?u&-33:u;m=x&-65537;H=(x&8192|0)==0?x:m;f:do switch(u|0){case 110:switch(s|0){case 0:{k[k[aa>>2]>>2]=f;w=I;g=y;continue a}case 1:{k[k[aa>>2]>>2]=f;w=I;g=y;continue a}case 2:{w=k[aa>>2]|0;k[w>>2]=f;k[w+4>>2]=((f|0)<0)<<31>>31;w=I;g=y;continue a}case 3:{j[k[aa>>2]>>1]=f;w=I;g=y;continue a}case 4:{i[k[aa>>2]>>0]=f;w=I;g=y;continue a}case 6:{k[k[aa>>2]>>2]=f;w=I;g=y;continue a}case 7:{w=k[aa>>2]|0;k[w>>2]=f;k[w+4>>2]=((f|0)<0)<<31>>31;w=I;g=y;continue a}default:{w=I;g=y;continue a}}case 112:{s=H|8;o=o>>>0>8?o:8;u=120;K=64;break}case 88:case 120:{s=H;K=64;break}case 111:{m=aa;h=k[m>>2]|0;m=k[m+4>>2]|0;if((h|0)==0&(m|0)==0)g=N;else{g=N;do{g=g+-1|0;i[g>>0]=h&7|48;h=ad(h|0,m|0,3)|0;m=L}while(!((h|0)==0&(m|0)==0))}if(!(H&8)){h=H;s=0;q=5827;K=77}else{s=U-g+1|0;h=H;o=(o|0)<(s|0)?s:o;s=0;q=5827;K=77}break}case 105:case 100:{h=aa;g=k[h>>2]|0;h=k[h+4>>2]|0;if((h|0)<0){g=Zc(0,0,g|0,h|0)|0;h=L;m=aa;k[m>>2]=g;k[m+4>>2]=h;m=1;q=5827;K=76;break f}if(!(H&2048)){q=H&1;m=q;q=(q|0)==0?5827:5829;K=76}else{m=1;q=5828;K=76}break}case 117:{h=aa;g=k[h>>2]|0;h=k[h+4>>2]|0;m=0;q=5827;K=76;break}case 99:{i[V>>0]=k[aa>>2];w=V;h=1;s=0;u=5827;g=N;break}case 109:{g=nc()|0;g=mc(k[g>>2]|0)|0;K=82;break}case 115:{g=k[aa>>2]|0;g=(g|0)!=0?g:5837;K=82;break}case 67:{k[fa>>2]=k[aa>>2];k[W>>2]=0;k[aa>>2]=fa;o=-1;K=86;break}case 83:{if(!o){Rc(a,32,J,0,H);g=0;K=98}else K=86;break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{n=+p[aa>>3];k[da>>2]=0;p[t>>3]=n;if((k[t+4>>2]|0)>=0)if(!(H&2048)){G=H&1;F=G;G=(G|0)==0?5845:5850}else{F=1;G=5847}else{n=-n;F=1;G=5844}p[t>>3]=n;E=k[t+4>>2]&2146435072;do if(E>>>0<2146435072|(E|0)==2146435072&0<0){v=+qc(n,da)*2.0;h=v!=0.0;if(h)k[da>>2]=(k[da>>2]|0)+-1;C=u|32;if((C|0)==97){w=u&32;y=(w|0)==0?G:G+9|0;x=F|2;g=12-o|0;do if(!(o>>>0>11|(g|0)==0)){n=8.0;do{g=g+-1|0;n=n*16.0}while((g|0)!=0);if((i[y>>0]|0)==45){n=-(n+(-v-n));break}else{n=v+n-n;break}}else n=v;while(0);h=k[da>>2]|0;g=(h|0)<0?0-h|0:h;g=Qc(g,((g|0)<0)<<31>>31,X)|0;if((g|0)==(X|0)){i[Y>>0]=48;g=Y}i[g+-1>>0]=(h>>31&2)+43;s=g+-2|0;i[s>>0]=u+15;q=(o|0)<1;m=(H&8|0)==0;h=ca;while(1){G=~~n;g=h+1|0;i[h>>0]=l[5811+G>>0]|w;n=(n-+(G|0))*16.0;do if((g-Z|0)==1){if(m&(q&n==0.0))break;i[g>>0]=46;g=h+2|0}while(0);if(!(n!=0.0))break;else h=g}o=(o|0)!=0&(O+g|0)<(o|0)?P+o-s|0:$-s+g|0;m=o+x|0;Rc(a,32,J,m,H);if(!(k[a>>2]&32))vc(y,x,a)|0;Rc(a,48,J,m,H^65536);g=g-Z|0;if(!(k[a>>2]&32))vc(ca,g,a)|0;h=_-s|0;Rc(a,48,o-(g+h)|0,0,0);if(!(k[a>>2]&32))vc(s,h,a)|0;Rc(a,32,J,m,H^8192);g=(m|0)<(J|0)?J:m;break}g=(o|0)<0?6:o;if(h){h=(k[da>>2]|0)+-28|0;k[da>>2]=h;n=v*268435456.0}else{n=v;h=k[da>>2]|0}E=(h|0)<0?ba:Q;D=E;h=E;do{B=~~n>>>0;k[h>>2]=B;h=h+4|0;n=(n-+(B>>>0))*1.0e9}while(n!=0.0);m=h;h=k[da>>2]|0;if((h|0)>0){q=E;while(1){s=(h|0)>29?29:h;o=m+-4|0;do if(o>>>0<q>>>0)o=q;else{h=0;do{B=bd(k[o>>2]|0,0,s|0)|0;B=_c(B|0,L|0,h|0,0)|0;h=L;A=kd(B|0,h|0,1e9,0)|0;k[o>>2]=A;h=jd(B|0,h|0,1e9,0)|0;o=o+-4|0}while(o>>>0>=q>>>0);if(!h){o=q;break}o=q+-4|0;k[o>>2]=h}while(0);while(1){if(m>>>0<=o>>>0)break;h=m+-4|0;if(!(k[h>>2]|0))m=h;else break}h=(k[da>>2]|0)-s|0;k[da>>2]=h;if((h|0)>0)q=o;else break}}else o=E;if((h|0)<0){y=((g+25|0)/9|0)+1|0;z=(C|0)==102;w=o;while(1){x=0-h|0;x=(x|0)>9?9:x;do if(w>>>0<m>>>0){h=(1<<x)+-1|0;q=1e9>>>x;o=0;s=w;do{B=k[s>>2]|0;k[s>>2]=(B>>>x)+o;o=ha(B&h,q)|0;s=s+4|0}while(s>>>0<m>>>0);h=(k[w>>2]|0)==0?w+4|0:w;if(!o){o=h;break}k[m>>2]=o;o=h;m=m+4|0}else o=(k[w>>2]|0)==0?w+4|0:w;while(0);h=z?E:o;m=(m-h>>2|0)>(y|0)?h+(y<<2)|0:m;h=(k[da>>2]|0)+x|0;k[da>>2]=h;if((h|0)>=0){w=o;break}else w=o}}else w=o;do if(w>>>0<m>>>0){h=(D-w>>2)*9|0;q=k[w>>2]|0;if(q>>>0<10)break;else o=10;do{o=o*10|0;h=h+1|0}while(q>>>0>=o>>>0)}else h=0;while(0);A=(C|0)==103;B=(g|0)!=0;o=g-((C|0)!=102?h:0)+((B&A)<<31>>31)|0;if((o|0)<(((m-D>>2)*9|0)+-9|0)){s=o+9216|0;z=(s|0)/9|0;o=E+(z+-1023<<2)|0;s=((s|0)%9|0)+1|0;if((s|0)<9){q=10;do{q=q*10|0;s=s+1|0}while((s|0)!=9)}else q=10;x=k[o>>2]|0;y=(x>>>0)%(q>>>0)|0;if((y|0)==0?(E+(z+-1022<<2)|0)==(m|0):0)q=w;else K=163;do if((K|0)==163){K=0;v=(((x>>>0)/(q>>>0)|0)&1|0)==0?9007199254740992.0:9007199254740994.0;s=(q|0)/2|0;do if(y>>>0<s>>>0)n=.5;else{if((y|0)==(s|0)?(E+(z+-1022<<2)|0)==(m|0):0){n=1.0;break}n=1.5}while(0);do if(F){if((i[G>>0]|0)!=45)break;v=-v;n=-n}while(0);s=x-y|0;k[o>>2]=s;if(!(v+n!=v)){q=w;break}C=s+q|0;k[o>>2]=C;if(C>>>0>999999999){h=w;while(1){q=o+-4|0;k[o>>2]=0;if(q>>>0<h>>>0){h=h+-4|0;k[h>>2]=0}C=(k[q>>2]|0)+1|0;k[q>>2]=C;if(C>>>0>999999999)o=q;else{w=h;o=q;break}}}h=(D-w>>2)*9|0;s=k[w>>2]|0;if(s>>>0<10){q=w;break}else q=10;do{q=q*10|0;h=h+1|0}while(s>>>0>=q>>>0);q=w}while(0);C=o+4|0;w=q;m=m>>>0>C>>>0?C:m}y=0-h|0;while(1){if(m>>>0<=w>>>0){z=0;C=m;break}o=m+-4|0;if(!(k[o>>2]|0))m=o;else{z=1;C=m;break}}do if(A){g=(B&1^1)+g|0;if((g|0)>(h|0)&(h|0)>-5){u=u+-1|0;g=g+-1-h|0}else{u=u+-2|0;g=g+-1|0}m=H&8;if(m)break;do if(z){m=k[C+-4>>2]|0;if(!m){o=9;break}if(!((m>>>0)%10|0)){q=10;o=0}else{o=0;break}do{q=q*10|0;o=o+1|0}while(((m>>>0)%(q>>>0)|0|0)==0)}else o=9;while(0);m=((C-D>>2)*9|0)+-9|0;if((u|32|0)==102){m=m-o|0;m=(m|0)<0?0:m;g=(g|0)<(m|0)?g:m;m=0;break}else{m=m+h-o|0;m=(m|0)<0?0:m;g=(g|0)<(m|0)?g:m;m=0;break}}else m=H&8;while(0);x=g|m;q=(x|0)!=0&1;s=(u|32|0)==102;if(s){h=(h|0)>0?h:0;u=0}else{o=(h|0)<0?y:h;o=Qc(o,((o|0)<0)<<31>>31,X)|0;if((_-o|0)<2)do{o=o+-1|0;i[o>>0]=48}while((_-o|0)<2);i[o+-1>>0]=(h>>31&2)+43;D=o+-2|0;i[D>>0]=u;h=_-D|0;u=D}y=F+1+g+q+h|0;Rc(a,32,J,y,H);if(!(k[a>>2]&32))vc(G,F,a)|0;Rc(a,48,J,y,H^65536);do if(s){o=w>>>0>E>>>0?E:w;h=o;do{m=Qc(k[h>>2]|0,0,R)|0;do if((h|0)==(o|0)){if((m|0)!=(R|0))break;i[T>>0]=48;m=T}else{if(m>>>0<=ca>>>0)break;do{m=m+-1|0;i[m>>0]=48}while(m>>>0>ca>>>0)}while(0);if(!(k[a>>2]&32))vc(m,S-m|0,a)|0;h=h+4|0}while(h>>>0<=E>>>0);do if(x){if(k[a>>2]&32)break;vc(5879,1,a)|0}while(0);if((g|0)>0&h>>>0<C>>>0){m=h;while(1){h=Qc(k[m>>2]|0,0,R)|0;if(h>>>0>ca>>>0)do{h=h+-1|0;i[h>>0]=48}while(h>>>0>ca>>>0);if(!(k[a>>2]&32))vc(h,(g|0)>9?9:g,a)|0;m=m+4|0;h=g+-9|0;if(!((g|0)>9&m>>>0<C>>>0)){g=h;break}else g=h}}Rc(a,48,g+9|0,9,0)}else{s=z?C:w+4|0;if((g|0)>-1){q=(m|0)==0;o=w;do{h=Qc(k[o>>2]|0,0,R)|0;if((h|0)==(R|0)){i[T>>0]=48;h=T}do if((o|0)==(w|0)){m=h+1|0;if(!(k[a>>2]&32))vc(h,1,a)|0;if(q&(g|0)<1){h=m;break}if(k[a>>2]&32){h=m;break}vc(5879,1,a)|0;h=m}else{if(h>>>0<=ca>>>0)break;do{h=h+-1|0;i[h>>0]=48}while(h>>>0>ca>>>0)}while(0);m=S-h|0;if(!(k[a>>2]&32))vc(h,(g|0)>(m|0)?m:g,a)|0;g=g-m|0;o=o+4|0}while(o>>>0<s>>>0&(g|0)>-1)}Rc(a,48,g+18|0,18,0);if(k[a>>2]&32)break;vc(u,_-u|0,a)|0}while(0);Rc(a,32,J,y,H^8192);g=(y|0)<(J|0)?J:y}else{s=(u&32|0)!=0;q=n!=n|0.0!=0.0;h=q?0:F;o=h+3|0;Rc(a,32,J,o,m);g=k[a>>2]|0;if(!(g&32)){vc(G,h,a)|0;g=k[a>>2]|0}if(!(g&32))vc(q?(s?5871:5875):s?5863:5867,3,a)|0;Rc(a,32,J,o,H^8192);g=(o|0)<(J|0)?J:o}while(0);w=I;continue a}default:{m=H;h=o;s=0;u=5827;g=N}}while(0);g:do if((K|0)==64){m=aa;h=k[m>>2]|0;m=k[m+4>>2]|0;q=u&32;if(!((h|0)==0&(m|0)==0)){g=N;do{g=g+-1|0;i[g>>0]=l[5811+(h&15)>>0]|q;h=ad(h|0,m|0,4)|0;m=L}while(!((h|0)==0&(m|0)==0));K=aa;if((s&8|0)==0|(k[K>>2]|0)==0&(k[K+4>>2]|0)==0){h=s;s=0;q=5827;K=77}else{h=s;s=2;q=5827+(u>>4)|0;K=77}}else{g=N;h=s;s=0;q=5827;K=77}}else if((K|0)==76){g=Qc(g,h,N)|0;h=H;s=m;K=77}else if((K|0)==82){K=0;H=Kc(g,0,o)|0;G=(H|0)==0;w=g;h=G?o:H-g|0;s=0;u=5827;g=G?g+o|0:H}else if((K|0)==86){K=0;h=0;g=0;q=k[aa>>2]|0;while(1){m=k[q>>2]|0;if(!m)break;g=sc(ea,m)|0;if((g|0)<0|g>>>0>(o-h|0)>>>0)break;h=g+h|0;if(o>>>0>h>>>0)q=q+4|0;else break}if((g|0)<0){f=-1;break a}Rc(a,32,J,h,H);if(!h){g=0;K=98}else{m=0;o=k[aa>>2]|0;while(1){g=k[o>>2]|0;if(!g){g=h;K=98;break g}g=sc(ea,g)|0;m=g+m|0;if((m|0)>(h|0)){g=h;K=98;break g}if(!(k[a>>2]&32))vc(ea,g,a)|0;if(m>>>0>=h>>>0){g=h;K=98;break}else o=o+4|0}}}while(0);if((K|0)==98){K=0;Rc(a,32,J,g,H^8192);w=I;g=(J|0)>(g|0)?J:g;continue}if((K|0)==77){K=0;m=(o|0)>-1?h&-65537:h;h=aa;h=(k[h>>2]|0)!=0|(k[h+4>>2]|0)!=0;if((o|0)!=0|h){h=(h&1^1)+(U-g)|0;w=g;h=(o|0)>(h|0)?o:h;u=q;g=N}else{w=N;h=0;u=q;g=N}}q=g-w|0;h=(h|0)<(q|0)?q:h;o=s+h|0;g=(J|0)<(o|0)?o:J;Rc(a,32,g,o,m);if(!(k[a>>2]&32))vc(u,s,a)|0;Rc(a,48,g,o,m^65536);Rc(a,48,h,q,0);if(!(k[a>>2]&32))vc(w,q,a)|0;Rc(a,32,g,o,m^8192);w=I}h:do if((K|0)==245)if(!a)if(b){f=1;while(1){b=k[e+(f<<2)>>2]|0;if(!b)break;Pc(d+(f<<3)|0,b,c);f=f+1|0;if((f|0)>=10){f=1;break h}}if((f|0)<10)while(1){if(k[e+(f<<2)>>2]|0){f=-1;break h}f=f+1|0;if((f|0)>=10){f=1;break}}else f=1}else f=0;while(0);r=ga;return f|0}function Nc(a){a=a|0;if(!(k[a+68>>2]|0))Dc(a);return}function Oc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=a+20|0;e=k[d>>2]|0;a=(k[a+16>>2]|0)-e|0;a=a>>>0>c>>>0?c:a;cd(e|0,b|0,a|0)|0;k[d>>2]=(k[d>>2]|0)+a;return c|0}function Pc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0.0;a:do if(b>>>0<=20)do switch(b|0){case 9:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;k[a>>2]=b;break a}case 10:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;d=a;k[d>>2]=b;k[d+4>>2]=((b|0)<0)<<31>>31;break a}case 11:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;d=a;k[d>>2]=b;k[d+4>>2]=0;break a}case 12:{d=(k[c>>2]|0)+(8-1)&~(8-1);b=d;e=k[b>>2]|0;b=k[b+4>>2]|0;k[c>>2]=d+8;d=a;k[d>>2]=e;k[d+4>>2]=b;break a}case 13:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;d=(d&65535)<<16>>16;e=a;k[e>>2]=d;k[e+4>>2]=((d|0)<0)<<31>>31;break a}case 14:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;e=a;k[e>>2]=d&65535;k[e+4>>2]=0;break a}case 15:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;d=(d&255)<<24>>24;e=a;k[e>>2]=d;k[e+4>>2]=((d|0)<0)<<31>>31;break a}case 16:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;e=a;k[e>>2]=d&255;k[e+4>>2]=0;break a}case 17:{e=(k[c>>2]|0)+(8-1)&~(8-1);f=+p[e>>3];k[c>>2]=e+8;p[a>>3]=f;break a}case 18:{e=(k[c>>2]|0)+(8-1)&~(8-1);f=+p[e>>3];k[c>>2]=e+8;p[a>>3]=f;break a}default:break a}while(0);while(0);return}function Qc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if(b>>>0>0|(b|0)==0&a>>>0>4294967295)while(1){d=kd(a|0,b|0,10,0)|0;c=c+-1|0;i[c>>0]=d|48;d=jd(a|0,b|0,10,0)|0;if(b>>>0>9|(b|0)==9&a>>>0>4294967295){a=d;b=L}else{a=d;break}}if(a)while(1){c=c+-1|0;i[c>>0]=(a>>>0)%10|0|48;if(a>>>0<10)break;else a=(a>>>0)/10|0}return c|0}function Rc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0;h=r;r=r+256|0;g=h;do if((c|0)>(d|0)&(e&73728|0)==0){e=c-d|0;$c(g|0,b|0,(e>>>0>256?256:e)|0)|0;b=k[a>>2]|0;f=(b&32|0)==0;if(e>>>0>255){d=c-d|0;do{if(f){vc(g,256,a)|0;b=k[a>>2]|0}e=e+-256|0;f=(b&32|0)==0}while(e>>>0>255);if(f)e=d&255;else break}else if(!f)break;vc(g,e,a)|0}while(0);r=h;return}function Sc(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0;do if(a>>>0<245){o=a>>>0<11?16:a+11&-8;a=o>>>3;h=k[147]|0;c=h>>>a;if(c&3){a=(c&1^1)+a|0;d=a<<1;c=628+(d<<2)|0;d=628+(d+2<<2)|0;e=k[d>>2]|0;f=e+8|0;g=k[f>>2]|0;do if((c|0)!=(g|0)){if(g>>>0<(k[151]|0)>>>0)Aa();b=g+12|0;if((k[b>>2]|0)==(e|0)){k[b>>2]=c;k[d>>2]=g;break}else Aa()}else k[147]=h&~(1<<a);while(0);M=a<<3;k[e+4>>2]=M|3;M=e+(M|4)|0;k[M>>2]=k[M>>2]|1;M=f;return M|0}g=k[149]|0;if(o>>>0>g>>>0){if(c){d=2<<a;d=c<<a&(d|0-d);d=(d&0-d)+-1|0;i=d>>>12&16;d=d>>>i;e=d>>>5&8;d=d>>>e;f=d>>>2&4;d=d>>>f;c=d>>>1&2;d=d>>>c;a=d>>>1&1;a=(e|i|f|c|a)+(d>>>a)|0;d=a<<1;c=628+(d<<2)|0;d=628+(d+2<<2)|0;f=k[d>>2]|0;i=f+8|0;e=k[i>>2]|0;do if((c|0)!=(e|0)){if(e>>>0<(k[151]|0)>>>0)Aa();b=e+12|0;if((k[b>>2]|0)==(f|0)){k[b>>2]=c;k[d>>2]=e;j=k[149]|0;break}else Aa()}else{k[147]=h&~(1<<a);j=g}while(0);M=a<<3;g=M-o|0;k[f+4>>2]=o|3;h=f+o|0;k[f+(o|4)>>2]=g|1;k[f+M>>2]=g;if(j){e=k[152]|0;c=j>>>3;b=c<<1;d=628+(b<<2)|0;a=k[147]|0;c=1<<c;if(a&c){a=628+(b+2<<2)|0;b=k[a>>2]|0;if(b>>>0<(k[151]|0)>>>0)Aa();else{l=a;m=b}}else{k[147]=a|c;l=628+(b+2<<2)|0;m=d}k[l>>2]=e;k[m+12>>2]=e;k[e+8>>2]=m;k[e+12>>2]=d}k[149]=g;k[152]=h;M=i;return M|0}a=k[148]|0;if(a){c=(a&0-a)+-1|0;L=c>>>12&16;c=c>>>L;K=c>>>5&8;c=c>>>K;M=c>>>2&4;c=c>>>M;a=c>>>1&2;c=c>>>a;d=c>>>1&1;d=k[892+((K|L|M|a|d)+(c>>>d)<<2)>>2]|0;c=(k[d+4>>2]&-8)-o|0;a=d;while(1){b=k[a+16>>2]|0;if(!b){b=k[a+20>>2]|0;if(!b){i=c;break}}a=(k[b+4>>2]&-8)-o|0;M=a>>>0<c>>>0;c=M?a:c;a=b;d=M?b:d}f=k[151]|0;if(d>>>0<f>>>0)Aa();h=d+o|0;if(d>>>0>=h>>>0)Aa();g=k[d+24>>2]|0;c=k[d+12>>2]|0;do if((c|0)==(d|0)){a=d+20|0;b=k[a>>2]|0;if(!b){a=d+16|0;b=k[a>>2]|0;if(!b){n=0;break}}while(1){c=b+20|0;e=k[c>>2]|0;if(e){b=e;a=c;continue}c=b+16|0;e=k[c>>2]|0;if(!e)break;else{b=e;a=c}}if(a>>>0<f>>>0)Aa();else{k[a>>2]=0;n=b;break}}else{e=k[d+8>>2]|0;if(e>>>0<f>>>0)Aa();b=e+12|0;if((k[b>>2]|0)!=(d|0))Aa();a=c+8|0;if((k[a>>2]|0)==(d|0)){k[b>>2]=c;k[a>>2]=e;n=c;break}else Aa()}while(0);do if(g){b=k[d+28>>2]|0;a=892+(b<<2)|0;if((d|0)==(k[a>>2]|0)){k[a>>2]=n;if(!n){k[148]=k[148]&~(1<<b);break}}else{if(g>>>0<(k[151]|0)>>>0)Aa();b=g+16|0;if((k[b>>2]|0)==(d|0))k[b>>2]=n;else k[g+20>>2]=n;if(!n)break}a=k[151]|0;if(n>>>0<a>>>0)Aa();k[n+24>>2]=g;b=k[d+16>>2]|0;do if(b)if(b>>>0<a>>>0)Aa();else{k[n+16>>2]=b;k[b+24>>2]=n;break}while(0);b=k[d+20>>2]|0;if(b)if(b>>>0<(k[151]|0)>>>0)Aa();else{k[n+20>>2]=b;k[b+24>>2]=n;break}}while(0);if(i>>>0<16){M=i+o|0;k[d+4>>2]=M|3;M=d+(M+4)|0;k[M>>2]=k[M>>2]|1}else{k[d+4>>2]=o|3;k[d+(o|4)>>2]=i|1;k[d+(i+o)>>2]=i;b=k[149]|0;if(b){f=k[152]|0;c=b>>>3;b=c<<1;e=628+(b<<2)|0;a=k[147]|0;c=1<<c;if(a&c){b=628+(b+2<<2)|0;a=k[b>>2]|0;if(a>>>0<(k[151]|0)>>>0)Aa();else{p=b;q=a}}else{k[147]=a|c;p=628+(b+2<<2)|0;q=e}k[p>>2]=f;k[q+12>>2]=f;k[f+8>>2]=q;k[f+12>>2]=e}k[149]=i;k[152]=h}M=d+8|0;return M|0}else q=o}else q=o}else if(a>>>0<=4294967231){a=a+11|0;m=a&-8;l=k[148]|0;if(l){c=0-m|0;a=a>>>8;if(a)if(m>>>0>16777215)j=31;else{q=(a+1048320|0)>>>16&8;v=a<<q;p=(v+520192|0)>>>16&4;v=v<<p;j=(v+245760|0)>>>16&2;j=14-(p|q|j)+(v<<j>>>15)|0;j=m>>>(j+7|0)&1|j<<1}else j=0;a=k[892+(j<<2)>>2]|0;a:do if(!a){e=0;a=0;v=86}else{g=c;e=0;h=m<<((j|0)==31?0:25-(j>>>1)|0);i=a;a=0;while(1){f=k[i+4>>2]&-8;c=f-m|0;if(c>>>0<g>>>0)if((f|0)==(m|0)){f=i;a=i;v=90;break a}else a=i;else c=g;v=k[i+20>>2]|0;i=k[i+16+(h>>>31<<2)>>2]|0;e=(v|0)==0|(v|0)==(i|0)?e:v;if(!i){v=86;break}else{g=c;h=h<<1}}}while(0);if((v|0)==86){if((e|0)==0&(a|0)==0){a=2<<j;a=l&(a|0-a);if(!a){q=m;break}a=(a&0-a)+-1|0;n=a>>>12&16;a=a>>>n;l=a>>>5&8;a=a>>>l;p=a>>>2&4;a=a>>>p;q=a>>>1&2;a=a>>>q;e=a>>>1&1;e=k[892+((l|n|p|q|e)+(a>>>e)<<2)>>2]|0;a=0}if(!e){h=c;i=a}else{f=e;v=90}}if((v|0)==90)while(1){v=0;q=(k[f+4>>2]&-8)-m|0;e=q>>>0<c>>>0;c=e?q:c;a=e?f:a;e=k[f+16>>2]|0;if(e){f=e;v=90;continue}f=k[f+20>>2]|0;if(!f){h=c;i=a;break}else v=90}if((i|0)!=0?h>>>0<((k[149]|0)-m|0)>>>0:0){e=k[151]|0;if(i>>>0<e>>>0)Aa();g=i+m|0;if(i>>>0>=g>>>0)Aa();f=k[i+24>>2]|0;c=k[i+12>>2]|0;do if((c|0)==(i|0)){a=i+20|0;b=k[a>>2]|0;if(!b){a=i+16|0;b=k[a>>2]|0;if(!b){o=0;break}}while(1){c=b+20|0;d=k[c>>2]|0;if(d){b=d;a=c;continue}c=b+16|0;d=k[c>>2]|0;if(!d)break;else{b=d;a=c}}if(a>>>0<e>>>0)Aa();else{k[a>>2]=0;o=b;break}}else{d=k[i+8>>2]|0;if(d>>>0<e>>>0)Aa();b=d+12|0;if((k[b>>2]|0)!=(i|0))Aa();a=c+8|0;if((k[a>>2]|0)==(i|0)){k[b>>2]=c;k[a>>2]=d;o=c;break}else Aa()}while(0);do if(f){b=k[i+28>>2]|0;a=892+(b<<2)|0;if((i|0)==(k[a>>2]|0)){k[a>>2]=o;if(!o){k[148]=k[148]&~(1<<b);break}}else{if(f>>>0<(k[151]|0)>>>0)Aa();b=f+16|0;if((k[b>>2]|0)==(i|0))k[b>>2]=o;else k[f+20>>2]=o;if(!o)break}a=k[151]|0;if(o>>>0<a>>>0)Aa();k[o+24>>2]=f;b=k[i+16>>2]|0;do if(b)if(b>>>0<a>>>0)Aa();else{k[o+16>>2]=b;k[b+24>>2]=o;break}while(0);b=k[i+20>>2]|0;if(b)if(b>>>0<(k[151]|0)>>>0)Aa();else{k[o+20>>2]=b;k[b+24>>2]=o;break}}while(0);b:do if(h>>>0>=16){k[i+4>>2]=m|3;k[i+(m|4)>>2]=h|1;k[i+(h+m)>>2]=h;b=h>>>3;if(h>>>0<256){a=b<<1;d=628+(a<<2)|0;c=k[147]|0;b=1<<b;if(c&b){b=628+(a+2<<2)|0;a=k[b>>2]|0;if(a>>>0<(k[151]|0)>>>0)Aa();else{s=b;t=a}}else{k[147]=c|b;s=628+(a+2<<2)|0;t=d}k[s>>2]=g;k[t+12>>2]=g;k[i+(m+8)>>2]=t;k[i+(m+12)>>2]=d;break}b=h>>>8;if(b)if(h>>>0>16777215)d=31;else{L=(b+1048320|0)>>>16&8;M=b<<L;K=(M+520192|0)>>>16&4;M=M<<K;d=(M+245760|0)>>>16&2;d=14-(K|L|d)+(M<<d>>>15)|0;d=h>>>(d+7|0)&1|d<<1}else d=0;b=892+(d<<2)|0;k[i+(m+28)>>2]=d;k[i+(m+20)>>2]=0;k[i+(m+16)>>2]=0;a=k[148]|0;c=1<<d;if(!(a&c)){k[148]=a|c;k[b>>2]=g;k[i+(m+24)>>2]=b;k[i+(m+12)>>2]=g;k[i+(m+8)>>2]=g;break}b=k[b>>2]|0;c:do if((k[b+4>>2]&-8|0)!=(h|0)){d=h<<((d|0)==31?0:25-(d>>>1)|0);while(1){a=b+16+(d>>>31<<2)|0;c=k[a>>2]|0;if(!c)break;if((k[c+4>>2]&-8|0)==(h|0)){y=c;break c}else{d=d<<1;b=c}}if(a>>>0<(k[151]|0)>>>0)Aa();else{k[a>>2]=g;k[i+(m+24)>>2]=b;k[i+(m+12)>>2]=g;k[i+(m+8)>>2]=g;break b}}else y=b;while(0);b=y+8|0;a=k[b>>2]|0;M=k[151]|0;if(a>>>0>=M>>>0&y>>>0>=M>>>0){k[a+12>>2]=g;k[b>>2]=g;k[i+(m+8)>>2]=a;k[i+(m+12)>>2]=y;k[i+(m+24)>>2]=0;break}else Aa()}else{M=h+m|0;k[i+4>>2]=M|3;M=i+(M+4)|0;k[M>>2]=k[M>>2]|1}while(0);M=i+8|0;return M|0}else q=m}else q=m}else q=-1;while(0);c=k[149]|0;if(c>>>0>=q>>>0){b=c-q|0;a=k[152]|0;if(b>>>0>15){k[152]=a+q;k[149]=b;k[a+(q+4)>>2]=b|1;k[a+c>>2]=b;k[a+4>>2]=q|3}else{k[149]=0;k[152]=0;k[a+4>>2]=c|3;M=a+(c+4)|0;k[M>>2]=k[M>>2]|1}M=a+8|0;return M|0}a=k[150]|0;if(a>>>0>q>>>0){L=a-q|0;k[150]=L;M=k[153]|0;k[153]=M+q;k[M+(q+4)>>2]=L|1;k[M+4>>2]=q|3;M=M+8|0;return M|0}do if(!(k[265]|0)){a=Ma(30)|0;if(!(a+-1&a)){k[267]=a;k[266]=a;k[268]=-1;k[269]=-1;k[270]=0;k[258]=0;y=(Ea(0)|0)&-16^1431655768;k[265]=y;break}else Aa()}while(0);i=q+48|0;h=k[267]|0;j=q+47|0;g=h+j|0;h=0-h|0;l=g&h;if(l>>>0<=q>>>0){M=0;return M|0}a=k[257]|0;if((a|0)!=0?(t=k[255]|0,y=t+l|0,y>>>0<=t>>>0|y>>>0>a>>>0):0){M=0;return M|0}d:do if(!(k[258]&4)){a=k[153]|0;e:do if(a){e=1036;while(1){c=k[e>>2]|0;if(c>>>0<=a>>>0?(r=e+4|0,(c+(k[r>>2]|0)|0)>>>0>a>>>0):0){f=e;a=r;break}e=k[e+8>>2]|0;if(!e){v=174;break e}}c=g-(k[150]|0)&h;if(c>>>0<2147483647){e=Da(c|0)|0;y=(e|0)==((k[f>>2]|0)+(k[a>>2]|0)|0);a=y?c:0;if(y){if((e|0)!=(-1|0)){w=e;p=a;v=194;break d}}else v=184}else a=0}else v=174;while(0);do if((v|0)==174){f=Da(0)|0;if((f|0)!=(-1|0)){a=f;c=k[266]|0;e=c+-1|0;if(!(e&a))c=l;else c=l-a+(e+a&0-c)|0;a=k[255]|0;e=a+c|0;if(c>>>0>q>>>0&c>>>0<2147483647){y=k[257]|0;if((y|0)!=0?e>>>0<=a>>>0|e>>>0>y>>>0:0){a=0;break}e=Da(c|0)|0;y=(e|0)==(f|0);a=y?c:0;if(y){w=f;p=a;v=194;break d}else v=184}else a=0}else a=0}while(0);f:do if((v|0)==184){f=0-c|0;do if(i>>>0>c>>>0&(c>>>0<2147483647&(e|0)!=(-1|0))?(u=k[267]|0,u=j-c+u&0-u,u>>>0<2147483647):0)if((Da(u|0)|0)==(-1|0)){Da(f|0)|0;break f}else{c=u+c|0;break}while(0);if((e|0)!=(-1|0)){w=e;p=c;v=194;break d}}while(0);k[258]=k[258]|4;v=191}else{a=0;v=191}while(0);if((((v|0)==191?l>>>0<2147483647:0)?(w=Da(l|0)|0,x=Da(0)|0,w>>>0<x>>>0&((w|0)!=(-1|0)&(x|0)!=(-1|0))):0)?(z=x-w|0,A=z>>>0>(q+40|0)>>>0,A):0){p=A?z:a;v=194}if((v|0)==194){a=(k[255]|0)+p|0;k[255]=a;if(a>>>0>(k[256]|0)>>>0)k[256]=a;g=k[153]|0;g:do if(g){f=1036;do{a=k[f>>2]|0;c=f+4|0;e=k[c>>2]|0;if((w|0)==(a+e|0)){B=a;C=c;D=e;E=f;v=204;break}f=k[f+8>>2]|0}while((f|0)!=0);if(((v|0)==204?(k[E+12>>2]&8|0)==0:0)?g>>>0<w>>>0&g>>>0>=B>>>0:0){k[C>>2]=D+p;M=(k[150]|0)+p|0;L=g+8|0;L=(L&7|0)==0?0:0-L&7;K=M-L|0;k[153]=g+L;k[150]=K;k[g+(L+4)>>2]=K|1;k[g+(M+4)>>2]=40;k[154]=k[269];break}a=k[151]|0;if(w>>>0<a>>>0){k[151]=w;a=w}c=w+p|0;f=1036;while(1){if((k[f>>2]|0)==(c|0)){e=f;c=f;v=212;break}f=k[f+8>>2]|0;if(!f){c=1036;break}}if((v|0)==212)if(!(k[c+12>>2]&8)){k[e>>2]=w;n=c+4|0;k[n>>2]=(k[n>>2]|0)+p;n=w+8|0;n=(n&7|0)==0?0:0-n&7;j=w+(p+8)|0;j=(j&7|0)==0?0:0-j&7;b=w+(j+p)|0;m=n+q|0;o=w+m|0;l=b-(w+n)-q|0;k[w+(n+4)>>2]=q|3;h:do if((b|0)!=(g|0)){if((b|0)==(k[152]|0)){M=(k[149]|0)+l|0;k[149]=M;k[152]=o;k[w+(m+4)>>2]=M|1;k[w+(M+m)>>2]=M;break}h=p+4|0;c=k[w+(h+j)>>2]|0;if((c&3|0)==1){i=c&-8;f=c>>>3;i:do if(c>>>0>=256){g=k[w+((j|24)+p)>>2]|0;d=k[w+(p+12+j)>>2]|0;do if((d|0)==(b|0)){e=j|16;d=w+(h+e)|0;c=k[d>>2]|0;if(!c){d=w+(e+p)|0;c=k[d>>2]|0;if(!c){J=0;break}}while(1){e=c+20|0;f=k[e>>2]|0;if(f){c=f;d=e;continue}e=c+16|0;f=k[e>>2]|0;if(!f)break;else{c=f;d=e}}if(d>>>0<a>>>0)Aa();else{k[d>>2]=0;J=c;break}}else{e=k[w+((j|8)+p)>>2]|0;if(e>>>0<a>>>0)Aa();a=e+12|0;if((k[a>>2]|0)!=(b|0))Aa();c=d+8|0;if((k[c>>2]|0)==(b|0)){k[a>>2]=d;k[c>>2]=e;J=d;break}else Aa()}while(0);if(!g)break;a=k[w+(p+28+j)>>2]|0;c=892+(a<<2)|0;do if((b|0)!=(k[c>>2]|0)){if(g>>>0<(k[151]|0)>>>0)Aa();a=g+16|0;if((k[a>>2]|0)==(b|0))k[a>>2]=J;else k[g+20>>2]=J;if(!J)break i}else{k[c>>2]=J;if(J)break;k[148]=k[148]&~(1<<a);break i}while(0);c=k[151]|0;if(J>>>0<c>>>0)Aa();k[J+24>>2]=g;b=j|16;a=k[w+(b+p)>>2]|0;do if(a)if(a>>>0<c>>>0)Aa();else{k[J+16>>2]=a;k[a+24>>2]=J;break}while(0);b=k[w+(h+b)>>2]|0;if(!b)break;if(b>>>0<(k[151]|0)>>>0)Aa();else{k[J+20>>2]=b;k[b+24>>2]=J;break}}else{d=k[w+((j|8)+p)>>2]|0;e=k[w+(p+12+j)>>2]|0;c=628+(f<<1<<2)|0;do if((d|0)!=(c|0)){if(d>>>0<a>>>0)Aa();if((k[d+12>>2]|0)==(b|0))break;Aa()}while(0);if((e|0)==(d|0)){k[147]=k[147]&~(1<<f);break}do if((e|0)==(c|0))F=e+8|0;else{if(e>>>0<a>>>0)Aa();a=e+8|0;if((k[a>>2]|0)==(b|0)){F=a;break}Aa()}while(0);k[d+12>>2]=e;k[F>>2]=d}while(0);b=w+((i|j)+p)|0;e=i+l|0}else e=l;b=b+4|0;k[b>>2]=k[b>>2]&-2;k[w+(m+4)>>2]=e|1;k[w+(e+m)>>2]=e;b=e>>>3;if(e>>>0<256){a=b<<1;d=628+(a<<2)|0;c=k[147]|0;b=1<<b;do if(!(c&b)){k[147]=c|b;K=628+(a+2<<2)|0;L=d}else{b=628+(a+2<<2)|0;a=k[b>>2]|0;if(a>>>0>=(k[151]|0)>>>0){K=b;L=a;break}Aa()}while(0);k[K>>2]=o;k[L+12>>2]=o;k[w+(m+8)>>2]=L;k[w+(m+12)>>2]=d;break}b=e>>>8;do if(!b)d=0;else{if(e>>>0>16777215){d=31;break}K=(b+1048320|0)>>>16&8;L=b<<K;J=(L+520192|0)>>>16&4;L=L<<J;d=(L+245760|0)>>>16&2;d=14-(J|K|d)+(L<<d>>>15)|0;d=e>>>(d+7|0)&1|d<<1}while(0);b=892+(d<<2)|0;k[w+(m+28)>>2]=d;k[w+(m+20)>>2]=0;k[w+(m+16)>>2]=0;a=k[148]|0;c=1<<d;if(!(a&c)){k[148]=a|c;k[b>>2]=o;k[w+(m+24)>>2]=b;k[w+(m+12)>>2]=o;k[w+(m+8)>>2]=o;break}b=k[b>>2]|0;j:do if((k[b+4>>2]&-8|0)!=(e|0)){d=e<<((d|0)==31?0:25-(d>>>1)|0);while(1){a=b+16+(d>>>31<<2)|0;c=k[a>>2]|0;if(!c)break;if((k[c+4>>2]&-8|0)==(e|0)){M=c;break j}else{d=d<<1;b=c}}if(a>>>0<(k[151]|0)>>>0)Aa();else{k[a>>2]=o;k[w+(m+24)>>2]=b;k[w+(m+12)>>2]=o;k[w+(m+8)>>2]=o;break h}}else M=b;while(0);b=M+8|0;a=k[b>>2]|0;L=k[151]|0;if(a>>>0>=L>>>0&M>>>0>=L>>>0){k[a+12>>2]=o;k[b>>2]=o;k[w+(m+8)>>2]=a;k[w+(m+12)>>2]=M;k[w+(m+24)>>2]=0;break}else Aa()}else{M=(k[150]|0)+l|0;k[150]=M;k[153]=o;k[w+(m+4)>>2]=M|1}while(0);M=w+(n|8)|0;return M|0}else c=1036;while(1){a=k[c>>2]|0;if(a>>>0<=g>>>0?(b=k[c+4>>2]|0,d=a+b|0,d>>>0>g>>>0):0)break;c=k[c+8>>2]|0}e=a+(b+-39)|0;a=a+(b+-47+((e&7|0)==0?0:0-e&7))|0;e=g+16|0;a=a>>>0<e>>>0?g:a;b=a+8|0;c=w+8|0;c=(c&7|0)==0?0:0-c&7;M=p+-40-c|0;k[153]=w+c;k[150]=M;k[w+(c+4)>>2]=M|1;k[w+(p+-36)>>2]=40;k[154]=k[269];c=a+4|0;k[c>>2]=27;k[b>>2]=k[259];k[b+4>>2]=k[260];k[b+8>>2]=k[261];k[b+12>>2]=k[262];k[259]=w;k[260]=p;k[262]=0;k[261]=b;b=a+28|0;k[b>>2]=7;if((a+32|0)>>>0<d>>>0)do{M=b;b=b+4|0;k[b>>2]=7}while((M+8|0)>>>0<d>>>0);if((a|0)!=(g|0)){f=a-g|0;k[c>>2]=k[c>>2]&-2;k[g+4>>2]=f|1;k[a>>2]=f;b=f>>>3;if(f>>>0<256){a=b<<1;d=628+(a<<2)|0;c=k[147]|0;b=1<<b;if(c&b){b=628+(a+2<<2)|0;a=k[b>>2]|0;if(a>>>0<(k[151]|0)>>>0)Aa();else{G=b;H=a}}else{k[147]=c|b;G=628+(a+2<<2)|0;H=d}k[G>>2]=g;k[H+12>>2]=g;k[g+8>>2]=H;k[g+12>>2]=d;break}b=f>>>8;if(b)if(f>>>0>16777215)d=31;else{L=(b+1048320|0)>>>16&8;M=b<<L;K=(M+520192|0)>>>16&4;M=M<<K;d=(M+245760|0)>>>16&2;d=14-(K|L|d)+(M<<d>>>15)|0;d=f>>>(d+7|0)&1|d<<1}else d=0;c=892+(d<<2)|0;k[g+28>>2]=d;k[g+20>>2]=0;k[e>>2]=0;b=k[148]|0;a=1<<d;if(!(b&a)){k[148]=b|a;k[c>>2]=g;k[g+24>>2]=c;k[g+12>>2]=g;k[g+8>>2]=g;break}b=k[c>>2]|0;k:do if((k[b+4>>2]&-8|0)!=(f|0)){d=f<<((d|0)==31?0:25-(d>>>1)|0);while(1){a=b+16+(d>>>31<<2)|0;c=k[a>>2]|0;if(!c)break;if((k[c+4>>2]&-8|0)==(f|0)){I=c;break k}else{d=d<<1;b=c}}if(a>>>0<(k[151]|0)>>>0)Aa();else{k[a>>2]=g;k[g+24>>2]=b;k[g+12>>2]=g;k[g+8>>2]=g;break g}}else I=b;while(0);b=I+8|0;a=k[b>>2]|0;M=k[151]|0;if(a>>>0>=M>>>0&I>>>0>=M>>>0){k[a+12>>2]=g;k[b>>2]=g;k[g+8>>2]=a;k[g+12>>2]=I;k[g+24>>2]=0;break}else Aa()}}else{M=k[151]|0;if((M|0)==0|w>>>0<M>>>0)k[151]=w;k[259]=w;k[260]=p;k[262]=0;k[156]=k[265];k[155]=-1;b=0;do{M=b<<1;L=628+(M<<2)|0;k[628+(M+3<<2)>>2]=L;k[628+(M+2<<2)>>2]=L;b=b+1|0}while((b|0)!=32);M=w+8|0;M=(M&7|0)==0?0:0-M&7;L=p+-40-M|0;k[153]=w+M;k[150]=L;k[w+(M+4)>>2]=L|1;k[w+(p+-36)>>2]=40;k[154]=k[269]}while(0);b=k[150]|0;if(b>>>0>q>>>0){L=b-q|0;k[150]=L;M=k[153]|0;k[153]=M+q;k[M+(q+4)>>2]=L|1;k[M+4>>2]=q|3;M=M+8|0;return M|0}}M=nc()|0;k[M>>2]=12;M=0;return M|0}function Tc(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;if(!a)return;b=a+-8|0;h=k[151]|0;if(b>>>0<h>>>0)Aa();c=k[a+-4>>2]|0;d=c&3;if((d|0)==1)Aa();o=c&-8;q=a+(o+-8)|0;do if(!(c&1)){b=k[b>>2]|0;if(!d)return;i=-8-b|0;l=a+i|0;m=b+o|0;if(l>>>0<h>>>0)Aa();if((l|0)==(k[152]|0)){b=a+(o+-4)|0;c=k[b>>2]|0;if((c&3|0)!=3){u=l;f=m;break}k[149]=m;k[b>>2]=c&-2;k[a+(i+4)>>2]=m|1;k[q>>2]=m;return}e=b>>>3;if(b>>>0<256){d=k[a+(i+8)>>2]|0;c=k[a+(i+12)>>2]|0;b=628+(e<<1<<2)|0;if((d|0)!=(b|0)){if(d>>>0<h>>>0)Aa();if((k[d+12>>2]|0)!=(l|0))Aa()}if((c|0)==(d|0)){k[147]=k[147]&~(1<<e);u=l;f=m;break}if((c|0)!=(b|0)){if(c>>>0<h>>>0)Aa();b=c+8|0;if((k[b>>2]|0)==(l|0))g=b;else Aa()}else g=c+8|0;k[d+12>>2]=c;k[g>>2]=d;u=l;f=m;break}g=k[a+(i+24)>>2]|0;d=k[a+(i+12)>>2]|0;do if((d|0)==(l|0)){c=a+(i+20)|0;b=k[c>>2]|0;if(!b){c=a+(i+16)|0;b=k[c>>2]|0;if(!b){j=0;break}}while(1){d=b+20|0;e=k[d>>2]|0;if(e){b=e;c=d;continue}d=b+16|0;e=k[d>>2]|0;if(!e)break;else{b=e;c=d}}if(c>>>0<h>>>0)Aa();else{k[c>>2]=0;j=b;break}}else{e=k[a+(i+8)>>2]|0;if(e>>>0<h>>>0)Aa();b=e+12|0;if((k[b>>2]|0)!=(l|0))Aa();c=d+8|0;if((k[c>>2]|0)==(l|0)){k[b>>2]=d;k[c>>2]=e;j=d;break}else Aa()}while(0);if(g){b=k[a+(i+28)>>2]|0;c=892+(b<<2)|0;if((l|0)==(k[c>>2]|0)){k[c>>2]=j;if(!j){k[148]=k[148]&~(1<<b);u=l;f=m;break}}else{if(g>>>0<(k[151]|0)>>>0)Aa();b=g+16|0;if((k[b>>2]|0)==(l|0))k[b>>2]=j;else k[g+20>>2]=j;if(!j){u=l;f=m;break}}c=k[151]|0;if(j>>>0<c>>>0)Aa();k[j+24>>2]=g;b=k[a+(i+16)>>2]|0;do if(b)if(b>>>0<c>>>0)Aa();else{k[j+16>>2]=b;k[b+24>>2]=j;break}while(0);b=k[a+(i+20)>>2]|0;if(b)if(b>>>0<(k[151]|0)>>>0)Aa();else{k[j+20>>2]=b;k[b+24>>2]=j;u=l;f=m;break}else{u=l;f=m}}else{u=l;f=m}}else{u=b;f=o}while(0);if(u>>>0>=q>>>0)Aa();b=a+(o+-4)|0;c=k[b>>2]|0;if(!(c&1))Aa();if(!(c&2)){if((q|0)==(k[153]|0)){t=(k[150]|0)+f|0;k[150]=t;k[153]=u;k[u+4>>2]=t|1;if((u|0)!=(k[152]|0))return;k[152]=0;k[149]=0;return}if((q|0)==(k[152]|0)){t=(k[149]|0)+f|0;k[149]=t;k[152]=u;k[u+4>>2]=t|1;k[u+t>>2]=t;return}f=(c&-8)+f|0;e=c>>>3;do if(c>>>0>=256){g=k[a+(o+16)>>2]|0;b=k[a+(o|4)>>2]|0;do if((b|0)==(q|0)){c=a+(o+12)|0;b=k[c>>2]|0;if(!b){c=a+(o+8)|0;b=k[c>>2]|0;if(!b){p=0;break}}while(1){d=b+20|0;e=k[d>>2]|0;if(e){b=e;c=d;continue}d=b+16|0;e=k[d>>2]|0;if(!e)break;else{b=e;c=d}}if(c>>>0<(k[151]|0)>>>0)Aa();else{k[c>>2]=0;p=b;break}}else{c=k[a+o>>2]|0;if(c>>>0<(k[151]|0)>>>0)Aa();d=c+12|0;if((k[d>>2]|0)!=(q|0))Aa();e=b+8|0;if((k[e>>2]|0)==(q|0)){k[d>>2]=b;k[e>>2]=c;p=b;break}else Aa()}while(0);if(g){b=k[a+(o+20)>>2]|0;c=892+(b<<2)|0;if((q|0)==(k[c>>2]|0)){k[c>>2]=p;if(!p){k[148]=k[148]&~(1<<b);break}}else{if(g>>>0<(k[151]|0)>>>0)Aa();b=g+16|0;if((k[b>>2]|0)==(q|0))k[b>>2]=p;else k[g+20>>2]=p;if(!p)break}c=k[151]|0;if(p>>>0<c>>>0)Aa();k[p+24>>2]=g;b=k[a+(o+8)>>2]|0;do if(b)if(b>>>0<c>>>0)Aa();else{k[p+16>>2]=b;k[b+24>>2]=p;break}while(0);b=k[a+(o+12)>>2]|0;if(b)if(b>>>0<(k[151]|0)>>>0)Aa();else{k[p+20>>2]=b;k[b+24>>2]=p;break}}}else{d=k[a+o>>2]|0;c=k[a+(o|4)>>2]|0;b=628+(e<<1<<2)|0;if((d|0)!=(b|0)){if(d>>>0<(k[151]|0)>>>0)Aa();if((k[d+12>>2]|0)!=(q|0))Aa()}if((c|0)==(d|0)){k[147]=k[147]&~(1<<e);break}if((c|0)!=(b|0)){if(c>>>0<(k[151]|0)>>>0)Aa();b=c+8|0;if((k[b>>2]|0)==(q|0))n=b;else Aa()}else n=c+8|0;k[d+12>>2]=c;k[n>>2]=d}while(0);k[u+4>>2]=f|1;k[u+f>>2]=f;if((u|0)==(k[152]|0)){k[149]=f;return}}else{k[b>>2]=c&-2;k[u+4>>2]=f|1;k[u+f>>2]=f}b=f>>>3;if(f>>>0<256){c=b<<1;e=628+(c<<2)|0;d=k[147]|0;b=1<<b;if(d&b){b=628+(c+2<<2)|0;c=k[b>>2]|0;if(c>>>0<(k[151]|0)>>>0)Aa();else{r=b;s=c}}else{k[147]=d|b;r=628+(c+2<<2)|0;s=e}k[r>>2]=u;k[s+12>>2]=u;k[u+8>>2]=s;k[u+12>>2]=e;return}b=f>>>8;if(b)if(f>>>0>16777215)e=31;else{r=(b+1048320|0)>>>16&8;s=b<<r;q=(s+520192|0)>>>16&4;s=s<<q;e=(s+245760|0)>>>16&2;e=14-(q|r|e)+(s<<e>>>15)|0;e=f>>>(e+7|0)&1|e<<1}else e=0;b=892+(e<<2)|0;k[u+28>>2]=e;k[u+20>>2]=0;k[u+16>>2]=0;c=k[148]|0;d=1<<e;a:do if(c&d){b=k[b>>2]|0;b:do if((k[b+4>>2]&-8|0)!=(f|0)){e=f<<((e|0)==31?0:25-(e>>>1)|0);while(1){c=b+16+(e>>>31<<2)|0;d=k[c>>2]|0;if(!d)break;if((k[d+4>>2]&-8|0)==(f|0)){t=d;break b}else{e=e<<1;b=d}}if(c>>>0<(k[151]|0)>>>0)Aa();else{k[c>>2]=u;k[u+24>>2]=b;k[u+12>>2]=u;k[u+8>>2]=u;break a}}else t=b;while(0);b=t+8|0;c=k[b>>2]|0;s=k[151]|0;if(c>>>0>=s>>>0&t>>>0>=s>>>0){k[c+12>>2]=u;k[b>>2]=u;k[u+8>>2]=c;k[u+12>>2]=t;k[u+24>>2]=0;break}else Aa()}else{k[148]=c|d;k[b>>2]=u;k[u+24>>2]=b;k[u+12>>2]=u;k[u+8>>2]=u}while(0);u=(k[155]|0)+-1|0;k[155]=u;if(!u)b=1044;else return;while(1){b=k[b>>2]|0;if(!b)break;else b=b+8|0}k[155]=-1;return}function Uc(a,b){a=a|0;b=b|0;var c=0,d=0;if(!a){a=Sc(b)|0;return a|0}if(b>>>0>4294967231){a=nc()|0;k[a>>2]=12;a=0;return a|0}c=Wc(a+-8|0,b>>>0<11?16:b+11&-8)|0;if(c){a=c+8|0;return a|0}c=Sc(b)|0;if(!c){a=0;return a|0}d=k[a+-4>>2]|0;d=(d&-8)-((d&3|0)==0?8:4)|0;cd(c|0,a|0,(d>>>0<b>>>0?d:b)|0)|0;Tc(a);a=c;return a|0}function Vc(a){a=a|0;var b=0;if(!a){b=0;return b|0}a=k[a+-4>>2]|0;b=a&3;if((b|0)==1){b=0;return b|0}b=(a&-8)-((b|0)==0?8:4)|0;return b|0}function Wc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;o=a+4|0;p=k[o>>2]|0;i=p&-8;l=a+i|0;h=k[151]|0;c=p&3;if(!((c|0)!=1&a>>>0>=h>>>0&a>>>0<l>>>0))Aa();d=a+(i|4)|0;e=k[d>>2]|0;if(!(e&1))Aa();if(!c){if(b>>>0<256){a=0;return a|0}if(i>>>0>=(b+4|0)>>>0?(i-b|0)>>>0<=k[267]<<1>>>0:0)return a|0;a=0;return a|0}if(i>>>0>=b>>>0){c=i-b|0;if(c>>>0<=15)return a|0;k[o>>2]=p&1|b|2;k[a+(b+4)>>2]=c|3;k[d>>2]=k[d>>2]|1;Xc(a+b|0,c);return a|0}if((l|0)==(k[153]|0)){c=(k[150]|0)+i|0;if(c>>>0<=b>>>0){a=0;return a|0}n=c-b|0;k[o>>2]=p&1|b|2;k[a+(b+4)>>2]=n|1;k[153]=a+b;k[150]=n;return a|0}if((l|0)==(k[152]|0)){d=(k[149]|0)+i|0;if(d>>>0<b>>>0){a=0;return a|0}c=d-b|0;if(c>>>0>15){k[o>>2]=p&1|b|2;k[a+(b+4)>>2]=c|1;k[a+d>>2]=c;d=a+(d+4)|0;k[d>>2]=k[d>>2]&-2;d=a+b|0}else{k[o>>2]=p&1|d|2;d=a+(d+4)|0;k[d>>2]=k[d>>2]|1;d=0;c=0}k[149]=c;k[152]=d;return a|0}if(e&2){a=0;return a|0}m=(e&-8)+i|0;if(m>>>0<b>>>0){a=0;return a|0}n=m-b|0;f=e>>>3;do if(e>>>0>=256){g=k[a+(i+24)>>2]|0;f=k[a+(i+12)>>2]|0;do if((f|0)==(l|0)){d=a+(i+20)|0;c=k[d>>2]|0;if(!c){d=a+(i+16)|0;c=k[d>>2]|0;if(!c){j=0;break}}while(1){e=c+20|0;f=k[e>>2]|0;if(f){c=f;d=e;continue}e=c+16|0;f=k[e>>2]|0;if(!f)break;else{c=f;d=e}}if(d>>>0<h>>>0)Aa();else{k[d>>2]=0;j=c;break}}else{e=k[a+(i+8)>>2]|0;if(e>>>0<h>>>0)Aa();c=e+12|0;if((k[c>>2]|0)!=(l|0))Aa();d=f+8|0;if((k[d>>2]|0)==(l|0)){k[c>>2]=f;k[d>>2]=e;j=f;break}else Aa()}while(0);if(g){c=k[a+(i+28)>>2]|0;d=892+(c<<2)|0;if((l|0)==(k[d>>2]|0)){k[d>>2]=j;if(!j){k[148]=k[148]&~(1<<c);break}}else{if(g>>>0<(k[151]|0)>>>0)Aa();c=g+16|0;if((k[c>>2]|0)==(l|0))k[c>>2]=j;else k[g+20>>2]=j;if(!j)break}d=k[151]|0;if(j>>>0<d>>>0)Aa();k[j+24>>2]=g;c=k[a+(i+16)>>2]|0;do if(c)if(c>>>0<d>>>0)Aa();else{k[j+16>>2]=c;k[c+24>>2]=j;break}while(0);c=k[a+(i+20)>>2]|0;if(c)if(c>>>0<(k[151]|0)>>>0)Aa();else{k[j+20>>2]=c;k[c+24>>2]=j;break}}}else{e=k[a+(i+8)>>2]|0;d=k[a+(i+12)>>2]|0;c=628+(f<<1<<2)|0;if((e|0)!=(c|0)){if(e>>>0<h>>>0)Aa();if((k[e+12>>2]|0)!=(l|0))Aa()}if((d|0)==(e|0)){k[147]=k[147]&~(1<<f);break}if((d|0)!=(c|0)){if(d>>>0<h>>>0)Aa();c=d+8|0;if((k[c>>2]|0)==(l|0))g=c;else Aa()}else g=d+8|0;k[e+12>>2]=d;k[g>>2]=e}while(0);if(n>>>0<16){k[o>>2]=m|p&1|2;b=a+(m|4)|0;k[b>>2]=k[b>>2]|1;return a|0}else{k[o>>2]=p&1|b|2;k[a+(b+4)>>2]=n|3;p=a+(m|4)|0;k[p>>2]=k[p>>2]|1;Xc(a+b|0,n);return a|0}return 0}function Xc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;q=a+b|0;c=k[a+4>>2]|0;do if(!(c&1)){j=k[a>>2]|0;if(!(c&3))return;n=a+(0-j)|0;m=j+b|0;i=k[151]|0;if(n>>>0<i>>>0)Aa();if((n|0)==(k[152]|0)){d=a+(b+4)|0;c=k[d>>2]|0;if((c&3|0)!=3){t=n;g=m;break}k[149]=m;k[d>>2]=c&-2;k[a+(4-j)>>2]=m|1;k[q>>2]=m;return}f=j>>>3;if(j>>>0<256){e=k[a+(8-j)>>2]|0;d=k[a+(12-j)>>2]|0;c=628+(f<<1<<2)|0;if((e|0)!=(c|0)){if(e>>>0<i>>>0)Aa();if((k[e+12>>2]|0)!=(n|0))Aa()}if((d|0)==(e|0)){k[147]=k[147]&~(1<<f);t=n;g=m;break}if((d|0)!=(c|0)){if(d>>>0<i>>>0)Aa();c=d+8|0;if((k[c>>2]|0)==(n|0))h=c;else Aa()}else h=d+8|0;k[e+12>>2]=d;k[h>>2]=e;t=n;g=m;break}h=k[a+(24-j)>>2]|0;e=k[a+(12-j)>>2]|0;do if((e|0)==(n|0)){e=16-j|0;d=a+(e+4)|0;c=k[d>>2]|0;if(!c){d=a+e|0;c=k[d>>2]|0;if(!c){l=0;break}}while(1){e=c+20|0;f=k[e>>2]|0;if(f){c=f;d=e;continue}e=c+16|0;f=k[e>>2]|0;if(!f)break;else{c=f;d=e}}if(d>>>0<i>>>0)Aa();else{k[d>>2]=0;l=c;break}}else{f=k[a+(8-j)>>2]|0;if(f>>>0<i>>>0)Aa();c=f+12|0;if((k[c>>2]|0)!=(n|0))Aa();d=e+8|0;if((k[d>>2]|0)==(n|0)){k[c>>2]=e;k[d>>2]=f;l=e;break}else Aa()}while(0);if(h){c=k[a+(28-j)>>2]|0;d=892+(c<<2)|0;if((n|0)==(k[d>>2]|0)){k[d>>2]=l;if(!l){k[148]=k[148]&~(1<<c);t=n;g=m;break}}else{if(h>>>0<(k[151]|0)>>>0)Aa();c=h+16|0;if((k[c>>2]|0)==(n|0))k[c>>2]=l;else k[h+20>>2]=l;if(!l){t=n;g=m;break}}e=k[151]|0;if(l>>>0<e>>>0)Aa();k[l+24>>2]=h;c=16-j|0;d=k[a+c>>2]|0;do if(d)if(d>>>0<e>>>0)Aa();else{k[l+16>>2]=d;k[d+24>>2]=l;break}while(0);c=k[a+(c+4)>>2]|0;if(c)if(c>>>0<(k[151]|0)>>>0)Aa();else{k[l+20>>2]=c;k[c+24>>2]=l;t=n;g=m;break}else{t=n;g=m}}else{t=n;g=m}}else{t=a;g=b}while(0);i=k[151]|0;if(q>>>0<i>>>0)Aa();c=a+(b+4)|0;d=k[c>>2]|0;if(!(d&2)){if((q|0)==(k[153]|0)){s=(k[150]|0)+g|0;k[150]=s;k[153]=t;k[t+4>>2]=s|1;if((t|0)!=(k[152]|0))return;k[152]=0;k[149]=0;return}if((q|0)==(k[152]|0)){s=(k[149]|0)+g|0;k[149]=s;k[152]=t;k[t+4>>2]=s|1;k[t+s>>2]=s;return}g=(d&-8)+g|0;f=d>>>3;do if(d>>>0>=256){h=k[a+(b+24)>>2]|0;e=k[a+(b+12)>>2]|0;do if((e|0)==(q|0)){d=a+(b+20)|0;c=k[d>>2]|0;if(!c){d=a+(b+16)|0;c=k[d>>2]|0;if(!c){p=0;break}}while(1){e=c+20|0;f=k[e>>2]|0;if(f){c=f;d=e;continue}e=c+16|0;f=k[e>>2]|0;if(!f)break;else{c=f;d=e}}if(d>>>0<i>>>0)Aa();else{k[d>>2]=0;p=c;break}}else{f=k[a+(b+8)>>2]|0;if(f>>>0<i>>>0)Aa();c=f+12|0;if((k[c>>2]|0)!=(q|0))Aa();d=e+8|0;if((k[d>>2]|0)==(q|0)){k[c>>2]=e;k[d>>2]=f;p=e;break}else Aa()}while(0);if(h){c=k[a+(b+28)>>2]|0;d=892+(c<<2)|0;if((q|0)==(k[d>>2]|0)){k[d>>2]=p;if(!p){k[148]=k[148]&~(1<<c);break}}else{if(h>>>0<(k[151]|0)>>>0)Aa();c=h+16|0;if((k[c>>2]|0)==(q|0))k[c>>2]=p;else k[h+20>>2]=p;if(!p)break}d=k[151]|0;if(p>>>0<d>>>0)Aa();k[p+24>>2]=h;c=k[a+(b+16)>>2]|0;do if(c)if(c>>>0<d>>>0)Aa();else{k[p+16>>2]=c;k[c+24>>2]=p;break}while(0);c=k[a+(b+20)>>2]|0;if(c)if(c>>>0<(k[151]|0)>>>0)Aa();else{k[p+20>>2]=c;k[c+24>>2]=p;break}}}else{e=k[a+(b+8)>>2]|0;d=k[a+(b+12)>>2]|0;c=628+(f<<1<<2)|0;if((e|0)!=(c|0)){if(e>>>0<i>>>0)Aa();if((k[e+12>>2]|0)!=(q|0))Aa()}if((d|0)==(e|0)){k[147]=k[147]&~(1<<f);break}if((d|0)!=(c|0)){if(d>>>0<i>>>0)Aa();c=d+8|0;if((k[c>>2]|0)==(q|0))o=c;else Aa()}else o=d+8|0;k[e+12>>2]=d;k[o>>2]=e}while(0);k[t+4>>2]=g|1;k[t+g>>2]=g;if((t|0)==(k[152]|0)){k[149]=g;return}}else{k[c>>2]=d&-2;k[t+4>>2]=g|1;k[t+g>>2]=g}c=g>>>3;if(g>>>0<256){d=c<<1;f=628+(d<<2)|0;e=k[147]|0;c=1<<c;if(e&c){c=628+(d+2<<2)|0;d=k[c>>2]|0;if(d>>>0<(k[151]|0)>>>0)Aa();else{r=c;s=d}}else{k[147]=e|c;r=628+(d+2<<2)|0;s=f}k[r>>2]=t;k[s+12>>2]=t;k[t+8>>2]=s;k[t+12>>2]=f;return}c=g>>>8;if(c)if(g>>>0>16777215)f=31;else{r=(c+1048320|0)>>>16&8;s=c<<r;q=(s+520192|0)>>>16&4;s=s<<q;f=(s+245760|0)>>>16&2;f=14-(q|r|f)+(s<<f>>>15)|0;f=g>>>(f+7|0)&1|f<<1}else f=0;c=892+(f<<2)|0;k[t+28>>2]=f;k[t+20>>2]=0;k[t+16>>2]=0;d=k[148]|0;e=1<<f;if(!(d&e)){k[148]=d|e;k[c>>2]=t;k[t+24>>2]=c;k[t+12>>2]=t;k[t+8>>2]=t;return}c=k[c>>2]|0;a:do if((k[c+4>>2]&-8|0)!=(g|0)){f=g<<((f|0)==31?0:25-(f>>>1)|0);while(1){d=c+16+(f>>>31<<2)|0;e=k[d>>2]|0;if(!e)break;if((k[e+4>>2]&-8|0)==(g|0)){c=e;break a}else{f=f<<1;c=e}}if(d>>>0<(k[151]|0)>>>0)Aa();k[d>>2]=t;k[t+24>>2]=c;k[t+12>>2]=t;k[t+8>>2]=t;return}while(0);d=c+8|0;e=k[d>>2]|0;s=k[151]|0;if(!(e>>>0>=s>>>0&c>>>0>=s>>>0))Aa();k[e+12>>2]=t;k[d>>2]=t;k[t+8>>2]=e;k[t+12>>2]=c;k[t+24>>2]=0;return}function Yc(){}function Zc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;d=b-d-(c>>>0>a>>>0|0)>>>0;return (L=d,a-c>>>0|0)|0}function _c(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;c=a+c>>>0;return (L=b+d+(c>>>0<a>>>0|0)>>>0,c|0)|0}function $c(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=a+c|0;if((c|0)>=20){b=b&255;f=a&3;g=b|b<<8|b<<16|b<<24;e=d&~3;if(f){f=a+4-f|0;while((a|0)<(f|0)){i[a>>0]=b;a=a+1|0}}while((a|0)<(e|0)){k[a>>2]=g;a=a+4|0}}while((a|0)<(d|0)){i[a>>0]=b;a=a+1|0}return a-c|0}function ad(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b>>>c;return a>>>c|(b&(1<<c)-1)<<32-c}L=0;return b>>>c-32|0}function bd(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b<<c|(a&(1<<c)-1<<32-c)>>>32-c;return a<<c}L=a<<c-32;return 0}function cd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if((c|0)>=4096)return Ha(a|0,b|0,c|0)|0;d=a|0;if((a&3)==(b&3)){while(a&3){if(!c)return d|0;i[a>>0]=i[b>>0]|0;a=a+1|0;b=b+1|0;c=c-1|0}while((c|0)>=4){k[a>>2]=k[b>>2];a=a+4|0;b=b+4|0;c=c-4|0}}while((c|0)>0){i[a>>0]=i[b>>0]|0;a=a+1|0;b=b+1|0;c=c-1|0}return d|0}function dd(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b>>c;return a>>>c|(b&(1<<c)-1)<<32-c}L=(b|0)<0?-1:0;return b>>c-32|0}function ed(a){a=a|0;var b=0;b=i[v+(a&255)>>0]|0;if((b|0)<8)return b|0;b=i[v+(a>>8&255)>>0]|0;if((b|0)<8)return b+8|0;b=i[v+(a>>16&255)>>0]|0;if((b|0)<8)return b+16|0;return (i[v+(a>>>24)>>0]|0)+24|0}function fd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;f=a&65535;e=b&65535;c=ha(e,f)|0;d=a>>>16;a=(c>>>16)+(ha(e,d)|0)|0;e=b>>>16;b=ha(e,f)|0;return (L=(a>>>16)+(ha(e,d)|0)+(((a&65535)+b|0)>>>16)|0,a+b<<16|c&65535|0)|0}function gd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;j=b>>31|((b|0)<0?-1:0)<<1;i=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;f=d>>31|((d|0)<0?-1:0)<<1;e=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;h=Zc(j^a,i^b,j,i)|0;g=L;a=f^j;b=e^i;return Zc((ld(h,g,Zc(f^c,e^d,f,e)|0,L,0)|0)^a,L^b,a,b)|0}function hd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=r;r=r+16|0;h=e|0;g=b>>31|((b|0)<0?-1:0)<<1;f=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;j=d>>31|((d|0)<0?-1:0)<<1;i=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;a=Zc(g^a,f^b,g,f)|0;b=L;ld(a,b,Zc(j^c,i^d,j,i)|0,L,h)|0;d=Zc(k[h>>2]^g,k[h+4>>2]^f,g,f)|0;c=L;r=e;return (L=c,d)|0}function id(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;e=a;f=c;c=fd(e,f)|0;a=L;return (L=(ha(b,f)|0)+(ha(d,e)|0)+a|a&0,c|0|0)|0}function jd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return ld(a,b,c,d,0)|0}function kd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=r;r=r+16|0;e=f|0;ld(a,b,c,d,e)|0;r=f;return (L=k[e+4>>2]|0,k[e>>2]|0)|0}function ld(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;l=a;i=b;j=i;g=c;n=d;h=n;if(!j){f=(e|0)!=0;if(!h){if(f){k[e>>2]=(l>>>0)%(g>>>0);k[e+4>>2]=0}n=0;e=(l>>>0)/(g>>>0)>>>0;return (L=n,e)|0}else{if(!f){n=0;e=0;return (L=n,e)|0}k[e>>2]=a|0;k[e+4>>2]=b&0;n=0;e=0;return (L=n,e)|0}}f=(h|0)==0;do if(g){if(!f){f=(ja(h|0)|0)-(ja(j|0)|0)|0;if(f>>>0<=31){m=f+1|0;h=31-f|0;b=f-31>>31;g=m;a=l>>>(m>>>0)&b|j<<h;b=j>>>(m>>>0)&b;f=0;h=l<<h;break}if(!e){n=0;e=0;return (L=n,e)|0}k[e>>2]=a|0;k[e+4>>2]=i|b&0;n=0;e=0;return (L=n,e)|0}f=g-1|0;if(f&g){h=(ja(g|0)|0)+33-(ja(j|0)|0)|0;p=64-h|0;m=32-h|0;i=m>>31;o=h-32|0;b=o>>31;g=h;a=m-1>>31&j>>>(o>>>0)|(j<<m|l>>>(h>>>0))&b;b=b&j>>>(h>>>0);f=l<<p&i;h=(j<<p|l>>>(o>>>0))&i|l<<m&h-33>>31;break}if(e){k[e>>2]=f&l;k[e+4>>2]=0}if((g|0)==1){o=i|b&0;p=a|0|0;return (L=o,p)|0}else{p=ed(g|0)|0;o=j>>>(p>>>0)|0;p=j<<32-p|l>>>(p>>>0)|0;return (L=o,p)|0}}else{if(f){if(e){k[e>>2]=(j>>>0)%(g>>>0);k[e+4>>2]=0}o=0;p=(j>>>0)/(g>>>0)>>>0;return (L=o,p)|0}if(!l){if(e){k[e>>2]=0;k[e+4>>2]=(j>>>0)%(h>>>0)}o=0;p=(j>>>0)/(h>>>0)>>>0;return (L=o,p)|0}f=h-1|0;if(!(f&h)){if(e){k[e>>2]=a|0;k[e+4>>2]=f&j|b&0}o=0;p=j>>>((ed(h|0)|0)>>>0);return (L=o,p)|0}f=(ja(h|0)|0)-(ja(j|0)|0)|0;if(f>>>0<=30){b=f+1|0;h=31-f|0;g=b;a=j<<h|l>>>(b>>>0);b=j>>>(b>>>0);f=0;h=l<<h;break}if(!e){o=0;p=0;return (L=o,p)|0}k[e>>2]=a|0;k[e+4>>2]=i|b&0;o=0;p=0;return (L=o,p)|0}while(0);if(!g){j=h;i=0;h=0}else{m=c|0|0;l=n|d&0;j=_c(m|0,l|0,-1,-1)|0;c=L;i=h;h=0;do{d=i;i=f>>>31|i<<1;f=h|f<<1;d=a<<1|d>>>31|0;n=a>>>31|b<<1|0;Zc(j,c,d,n)|0;p=L;o=p>>31|((p|0)<0?-1:0)<<1;h=o&1;a=Zc(d,n,o&m,(((p|0)<0?-1:0)>>31|((p|0)<0?-1:0)<<1)&l)|0;b=L;g=g-1|0}while((g|0)!=0);j=i;i=0}g=0;if(e){k[e>>2]=a;k[e+4>>2]=b}o=(f|0)>>>31|(j|g)<<1|(g<<1|f>>>31)&0|i;p=(f<<1|0>>>31)&-2|h;return (L=o,p)|0}function md(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Ra[a&7](b|0,c|0,d|0)|0}function nd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;Sa[a&3](b|0,c|0,d|0,e|0,f|0)}function od(a,b){a=a|0;b=b|0;Ta[a&7](b|0)}function pd(a,b){a=a|0;b=b|0;return Ua[a&1](b|0)|0}function qd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;Va[a&0](b|0,c|0,d|0)}function rd(a){a=a|0;Wa[a&3]()}function sd(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;Xa[a&3](b|0,c|0,d|0,e|0,f|0,g|0)}function td(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;Ya[a&3](b|0,c|0,d|0,e|0)}function ud(a,b,c){a=a|0;b=b|0;c=c|0;ka(0);return 0}function vd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ka(1)}function wd(a){a=a|0;ka(2)}function xd(a){a=a|0;ka(3);return 0}function yd(a,b,c){a=a|0;b=b|0;c=c|0;ka(4)}function zd(){ka(5)}function Ad(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;ka(6)}function Bd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ka(7)}

// EMSCRIPTEN_END_FUNCS
        var Ra=[ud,Zb,Oc,Hc,Gc,Ic,ud,ud];var Sa=[vd,ec,dc,vd];var Ta=[wd,Ub,Xb,Vb,Wb,Yb,lc,Nc];var Ua=[xd,Fc];var Va=[yd];var Wa=[zd,jc,kc,zd];var Xa=[Ad,gc,fc,Ad];var Ya=[Bd,$b,ac,Bd];return{___cxa_can_catch:hc,_crn_get_levels:zb,_crn_get_uncompressed_size:Cb,_crn_decompress:Db,_i64Add:_c,_crn_get_width:xb,___cxa_is_pointer_type:ic,_crn_get_bytes_per_block:Bb,_memset:$c,_malloc:Sc,_memcpy:cd,_i64Subtract:Zc,_bitshift64Lshr:ad,_free:Tc,_bitshift64Shl:bd,_crn_get_height:yb,_crn_get_dxt_format:Ab,runPostSets:Yc,_emscripten_replace_memory:Qa,stackAlloc:Za,stackSave:_a,stackRestore:$a,establishStackSpace:ab,setThrew:bb,setTempRet0:eb,getTempRet0:fb,dynCall_iiii:md,dynCall_viiiii:nd,dynCall_vi:od,dynCall_ii:pd,dynCall_viii:qd,dynCall_v:rd,dynCall_viiiiii:sd,dynCall_viiii:td}})


    // EMSCRIPTEN_END_ASM
    (Module.asmGlobalArg,Module.asmLibraryArg,buffer);var ___cxa_can_catch=Module["___cxa_can_catch"]=asm["___cxa_can_catch"];var _crn_get_levels=Module["_crn_get_levels"]=asm["_crn_get_levels"];var runPostSets=Module["runPostSets"]=asm["runPostSets"];var _crn_get_uncompressed_size=Module["_crn_get_uncompressed_size"]=asm["_crn_get_uncompressed_size"];var _crn_decompress=Module["_crn_decompress"]=asm["_crn_decompress"];var _i64Add=Module["_i64Add"]=asm["_i64Add"];var _crn_get_height=Module["_crn_get_height"]=asm["_crn_get_height"];var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=asm["___cxa_is_pointer_type"];var _crn_get_bytes_per_block=Module["_crn_get_bytes_per_block"]=asm["_crn_get_bytes_per_block"];var _memset=Module["_memset"]=asm["_memset"];var _malloc=Module["_malloc"]=asm["_malloc"];var _memcpy=Module["_memcpy"]=asm["_memcpy"];var _emscripten_replace_memory=Module["_emscripten_replace_memory"]=asm["_emscripten_replace_memory"];var _i64Subtract=Module["_i64Subtract"]=asm["_i64Subtract"];var _bitshift64Lshr=Module["_bitshift64Lshr"]=asm["_bitshift64Lshr"];var _free=Module["_free"]=asm["_free"];var _crn_get_dxt_format=Module["_crn_get_dxt_format"]=asm["_crn_get_dxt_format"];var _crn_get_width=Module["_crn_get_width"]=asm["_crn_get_width"];var _bitshift64Shl=Module["_bitshift64Shl"]=asm["_bitshift64Shl"];var dynCall_iiii=Module["dynCall_iiii"]=asm["dynCall_iiii"];var dynCall_viiiii=Module["dynCall_viiiii"]=asm["dynCall_viiiii"];var dynCall_vi=Module["dynCall_vi"]=asm["dynCall_vi"];var dynCall_ii=Module["dynCall_ii"]=asm["dynCall_ii"];var dynCall_viii=Module["dynCall_viii"]=asm["dynCall_viii"];var dynCall_v=Module["dynCall_v"]=asm["dynCall_v"];var dynCall_viiiiii=Module["dynCall_viiiiii"]=asm["dynCall_viiiiii"];var dynCall_viiii=Module["dynCall_viiii"]=asm["dynCall_viiii"];Runtime.stackAlloc=asm["stackAlloc"];Runtime.stackSave=asm["stackSave"];Runtime.stackRestore=asm["stackRestore"];Runtime.establishStackSpace=asm["establishStackSpace"];Runtime.setTempRet0=asm["setTempRet0"];Runtime.getTempRet0=asm["getTempRet0"];function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}ExitStatus.prototype=new Error;ExitStatus.prototype.constructor=ExitStatus;var initialStackTop;var preloadStartTime=null;var calledMain=false;dependenciesFulfilled=function runCaller(){if(!Module["calledRun"])run();if(!Module["calledRun"])dependenciesFulfilled=runCaller};Module["callMain"]=Module.callMain=function callMain(args){assert(runDependencies==0,"cannot call main when async dependencies remain! (listen on __ATMAIN__)");assert(__ATPRERUN__.length==0,"cannot call main when preRun functions remain to be called");args=args||[];ensureInitRuntime();var argc=args.length+1;function pad(){for(var i=0;i<4-1;i++){argv.push(0)}}var argv=[allocate(intArrayFromString(Module["thisProgram"]),"i8",ALLOC_NORMAL)];pad();for(var i=0;i<argc-1;i=i+1){argv.push(allocate(intArrayFromString(args[i]),"i8",ALLOC_NORMAL));pad()}argv.push(0);argv=allocate(argv,"i32",ALLOC_NORMAL);try{var ret=Module["_main"](argc,argv,0);exit(ret,true)}catch(e){if(e instanceof ExitStatus){return}else if(e=="SimulateInfiniteLoop"){Module["noExitRuntime"]=true;return}else{if(e&&typeof e==="object"&&e.stack)Module.printErr("exception thrown: "+[e,e.stack]);throw e}}finally{calledMain=true}};function run(args){args=args||Module["arguments"];if(preloadStartTime===null)preloadStartTime=Date.now();if(runDependencies>0){return}preRun();if(runDependencies>0)return;if(Module["calledRun"])return;function doRun(){if(Module["calledRun"])return;Module["calledRun"]=true;if(ABORT)return;ensureInitRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(Module["_main"]&&shouldRunNow)Module["callMain"](args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout((function(){setTimeout((function(){Module["setStatus"]("")}),1);doRun()}),1)}else{doRun()}}Module["run"]=Module.run=run;function exit(status,implicit){if(implicit&&Module["noExitRuntime"]){return}if(Module["noExitRuntime"]){}else{ABORT=true;EXITSTATUS=status;STACKTOP=initialStackTop;exitRuntime();if(Module["onExit"])Module["onExit"](status)}if(ENVIRONMENT_IS_NODE){process["stdout"]["once"]("drain",(function(){process["exit"](status)}));console.log(" ");setTimeout((function(){process["exit"](status)}),500)}else if(ENVIRONMENT_IS_SHELL&&typeof quit==="function"){quit(status)}throw new ExitStatus(status)}Module["exit"]=Module.exit=exit;var abortDecorators=[];function abort(what){if(what!==undefined){Module.print(what);Module.printErr(what);what=JSON.stringify(what)}else{what=""}ABORT=true;EXITSTATUS=1;var extra="\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.";var output="abort("+what+") at "+stackTrace()+extra;if(abortDecorators){abortDecorators.forEach((function(decorator){output=decorator(output,what)}))}throw output}Module["abort"]=Module.abort=abort;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"]){shouldRunNow=false}Module["noExitRuntime"]=true;run()

    return Module;
});
/*!
 * Knockout JavaScript library v3.4.0
 * (c) Steven Sanderson - http://knockoutjs.com/
 * License: MIT (http://www.opensource.org/licenses/mit-license.php)
 */

(function() {(function(n){var x=this||(0,eval)("this"),u=x.document,M=x.navigator,v=x.jQuery,F=x.JSON;(function(n){"function"===typeof define&&define.amd?define('ThirdParty/knockout-3.4.0',["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in T?a===c:!1}function U(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function V(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function W(a,
c){c&&c!==I?"beforeChange"===c?this.Kb(a):this.Ha(a,c):this.Lb(a)}function X(a,c){null!==c&&c.k&&c.k()}function Y(a,c){var d=this.Hc,e=d[s];e.R||(this.lb&&this.Ma[c]?(d.Pb(c,a,this.Ma[c]),this.Ma[c]=null,--this.lb):e.r[c]||d.Pb(c,a,e.s?{ia:a}:d.uc(a)))}function K(b,c,d,e){a.d[b]={init:function(b,g,k,l,m){var h,r;a.m(function(){var q=a.a.c(g()),p=!d!==!q,A=!r;if(A||c||p!==h)A&&a.va.Aa()&&(r=a.a.ua(a.f.childNodes(b),!0)),p?(A||a.f.da(b,a.a.ua(r)),a.eb(e?e(m,q):m,b)):a.f.xa(b),h=p},null,{i:b});return{controlsDescendantBindings:!0}}};
a.h.ta[b]=!1;a.f.Z[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.G=function(a,c,d){a[c]=d};a.version="3.4.0";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=b;return a}function e(b,c,d,e){var h=b[c].match(r)||
[];a.a.q(d.match(r),function(b){a.a.pa(h,b,e)});b[c]=h.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,k={},l={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(k,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},h=u&&function(){for(var a=3,b=u.createElement("div"),c=
b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{cc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],q:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Sb:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];
return null},La:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Tb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},fb:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ka:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ra:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<d;c++)a.push(b[c]);return a},pa:function(b,c,d){var e=
a.a.o(a.a.zb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},ka:f,extend:c,Xa:d,Ya:f?d:c,D:b,Ca:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},ob:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},jc:function(b){b=a.a.V(b);for(var c=(b[0]&&b[0].ownerDocument||u).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.$(b[d]));return c},ua:function(b,c){for(var d=0,e=b.length,h=[];d<e;d++){var m=b[d].cloneNode(!0);h.push(c?a.$(m):m)}return h},
da:function(b,c){a.a.ob(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},qc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],h=e.parentNode,m=0,l=c.length;m<l;m++)h.insertBefore(c[m],e);m=0;for(l=d.length;m<l;m++)a.removeNode(d[m])}},za:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),
c=c.nextSibling;a.push(d)}}return a},sc:function(a,b){7>h?a.setAttribute("selected",b):a.selected=b},$a:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},nd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Mc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=
b;)a=a.parentNode;return!!a},nb:function(b){return a.a.Mc(b,b.ownerDocument.documentElement)},Qb:function(b){return!!a.a.Sb(b,a.a.nb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Wb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Wb(b),c)},$b:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},p:function(b,c,d){var e=a.a.Wb(d);d=h&&m[c];if(a.options.useOnlyNativeEvents||
d||!v)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var l=function(a){e.call(b,a)},f="on"+c;b.attachEvent(f,l);a.a.F.oa(b,function(){b.detachEvent(f,l)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else v(b).bind(c,e)},Da:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==
d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!v||d)if("function"==typeof u.createEvent)if("function"==typeof b.dispatchEvent)d=u.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");else v(b).trigger(c)},c:function(b){return a.H(b)?
b():b},zb:function(b){return a.H(b)?b.t():b},bb:function(b,c,d){var h;c&&("object"===typeof b.classList?(h=b.classList[d?"add":"remove"],a.a.q(c.match(r),function(a){h.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},Za:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.da(b,[b.ownerDocument.createTextNode(d)]):e.data=d;a.a.Rc(b)},rc:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(u.createElement("<input name='"+
a.name+"'/>"),!1)}catch(c){}},Rc:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Nc:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},hd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},V:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},Yb:function(a){return g?Symbol(a):a},rd:6===h,sd:7===h,C:h,ec:function(b,c){for(var d=a.a.V(b.getElementsByTagName("input")).concat(a.a.V(b.getElementsByTagName("textarea"))),
e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},h=[],m=d.length-1;0<=m;m--)e(d[m])&&h.push(d[m]);return h},ed:function(b){return"string"==typeof b&&(b=a.a.$a(b))?F&&F.parse?F.parse(b):(new Function("return "+b))():null},Eb:function(b,c,d){if(!F||!F.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return F.stringify(a.a.c(b),c,d)},fd:function(c,d,e){e=e||{};var h=e.params||{},m=e.includeFields||this.cc,l=c;if("object"==typeof c&&"form"===a.a.A(c))for(var l=c.action,f=m.length-1;0<=f;f--)for(var g=a.a.ec(c,m[f]),k=g.length-1;0<=k;k--)h[g[k].name]=g[k].value;d=a.a.c(d);var r=u.createElement("form");r.style.display="none";r.action=l;r.method="post";for(var n in d)c=u.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Eb(a.a.c(d[n])),r.appendChild(c);b(h,function(a,b){var c=u.createElement("input");
c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});u.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.q);a.b("utils.arrayFirst",a.a.Sb);a.b("utils.arrayFilter",a.a.Ka);a.b("utils.arrayGetDistinctValues",a.a.Tb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.fb);a.b("utils.arrayPushAll",a.a.ra);a.b("utils.arrayRemoveItem",a.a.La);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
a.a.cc);a.b("utils.getFormFields",a.a.ec);a.b("utils.peekObservable",a.a.zb);a.b("utils.postJson",a.a.fd);a.b("utils.parseJson",a.a.ed);a.b("utils.registerEventHandler",a.a.p);a.b("utils.stringifyJson",a.a.Eb);a.b("utils.range",a.a.hd);a.b("utils.toggleDomNodeCssClass",a.a.bb);a.b("utils.triggerEvent",a.a.Da);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.pa);a.b("utils.setTextContent",a.a.Za);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var k=b[d];if(!k||"null"===k||!e[k]){if(!g)return n;k=b[d]="ko"+c++;e[k]={}}return e[k]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},I:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.F=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.F.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.I(),e={1:!0,8:!0,9:!0},
f={1:!0,9:!0};return{oa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},pc:function(c,e){var l=b(c,!1);l&&(a.a.La(l,e),0==l.length&&a.a.e.set(c,d,n))},$:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ra(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.$(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){v&&"function"==typeof v.cleanData&&v.cleanData([a])}}};
a.$=a.a.F.$;a.removeNode=a.a.F.removeNode;a.b("cleanNode",a.$);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.F);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.F.oa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.F.pc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
g=8>=a.a.C;a.a.ma=function(c,d){var e;if(v)if(v.parseHTML)e=v.parseHTML(c,d)||[];else{if((e=v.clean([c],d))&&e[0]){for(var h=e[0];h.parentNode&&11!==h.parentNode.nodeType;)h=h.parentNode;h.parentNode&&h.parentNode.removeChild(h)}}else{(e=d)||(e=u);var h=e.parentWindow||e.defaultView||x,r=a.a.$a(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof h.innerShiv?q.appendChild(h.innerShiv(p)):(g&&e.appendChild(q),
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.V(q.lastChild.childNodes)}return e};a.a.Cb=function(b,c){a.a.ob(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),v)v(b).html(c);else for(var d=a.a.ma(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.ma);a.b("utils.setHtml",a.a.Cb);a.M=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.M.lc(c.nodeValue);null!=f&&e.push({Lc:c,cd:f})}else if(1==c.nodeType)for(var f=
0,g=c.childNodes,k=g.length;f<k;f++)b(g[f],e)}var c={};return{wb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},xc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
!0}finally{delete c[a]}},yc:function(c,e){var f=[];b(c,f);for(var g=0,k=f.length;g<k;g++){var l=f[g].Lc,m=[l];e&&a.a.ra(m,e);a.M.xc(f[g].cd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},lc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.M);a.b("memoization.memoize",a.M.wb);a.b("memoization.unmemoize",a.M.xc);a.b("memoization.parseMemoText",a.M.lc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.M.yc);a.Y=function(){function b(){if(e)for(var b=
e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.$b(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(h){a.a.$b(h)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=u.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):u&&"onreadystatechange"in u.createElement("script")?function(a){var b=u.createElement("script");b.onreadystatechange=
function(){b.onreadystatechange=null;u.documentElement.removeChild(b);b=null;a()};u.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Wa:function(b){e||a.Y.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},md:b}}();a.b("tasks",a.Y);a.b("tasks.schedule",a.Y.Wa);a.b("tasks.runEarly",a.Y.md);a.ya={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.cb=!1;f="notifyWhenChangesStop"==e?V:U;a.Ta(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.cb||(b.cb=!0,b.Ta(function(c){var e;return function(){a.Y.cancel(e);e=a.Y.Wa(c);b.notifySubscribers(n,"dirty")}}))},notify:function(a,c){a.equalityComparer=
"always"==c?null:J}};var T={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.ya);a.vc=function(b,c,d){this.ia=b;this.gb=c;this.Kc=d;this.R=!1;a.G(this,"dispose",this.k)};a.vc.prototype.k=function(){this.R=!0;this.Kc()};a.J=function(){a.a.Ya(this,D);D.rb(this)};var I="change",D={rb:function(a){a.K={};a.Nb=1},X:function(b,c,d){var e=this;d=d||I;var f=new a.vc(e,c?b.bind(c):b,function(){a.a.La(e.K[d],f);e.Ia&&e.Ia(d)});e.sa&&e.sa(d);e.K[d]||(e.K[d]=[]);e.K[d].push(f);return f},notifySubscribers:function(b,
c){c=c||I;c===I&&this.zc();if(this.Pa(c))try{a.l.Ub();for(var d=this.K[c].slice(0),e=0,f;f=d[e];++e)f.R||f.gb(b)}finally{a.l.end()}},Na:function(){return this.Nb},Uc:function(a){return this.Na()!==a},zc:function(){++this.Nb},Ta:function(b){var c=this,d=a.H(c),e,f,g;c.Ha||(c.Ha=c.notifySubscribers,c.notifySubscribers=W);var k=b(function(){c.Mb=!1;d&&g===c&&(g=c());e=!1;c.tb(f,g)&&c.Ha(f=g)});c.Lb=function(a){c.Mb=e=!0;g=a;k()};c.Kb=function(a){e||(f=a,c.Ha(a,"beforeChange"))}},Pa:function(a){return this.K[a]&&
this.K[a].length},Sc:function(b){if(b)return this.K[b]&&this.K[b].length||0;var c=0;a.a.D(this.K,function(a,b){"dirty"!==a&&(c+=b.length)});return c},tb:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.ya[b];"function"==typeof f&&(c=f(c,e)||c)});return c}};a.G(D,"subscribe",D.X);a.G(D,"extend",D.extend);a.G(D,"getSubscriptionsCount",D.Sc);a.a.ka&&a.a.Xa(D,Function.prototype);a.J.fn=D;a.hc=function(a){return null!=
a&&"function"==typeof a.X&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.J);a.b("isSubscribable",a.hc);a.va=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Ub:b,end:c,oc:function(b){if(e){if(!a.hc(b))throw Error("Only subscribable things can act as dependencies");e.gb.call(e.Gc,b,b.Cc||(b.Cc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Aa:function(){if(e)return e.m.Aa()},Sa:function(){if(e)return e.Sa}}}();a.b("computedContext",
a.va);a.b("computedContext.getDependenciesCount",a.va.Aa);a.b("computedContext.isInitial",a.va.Sa);a.b("ignoreDependencies",a.qd=a.l.w);var E=a.a.Yb("_latestValue");a.N=function(b){function c(){if(0<arguments.length)return c.tb(c[E],arguments[0])&&(c.ga(),c[E]=arguments[0],c.fa()),this;a.l.oc(c);return c[E]}c[E]=b;a.a.ka||a.a.extend(c,a.J.fn);a.J.fn.rb(c);a.a.Ya(c,B);a.options.deferUpdates&&a.ya.deferred(c,!0);return c};var B={equalityComparer:J,t:function(){return this[E]},fa:function(){this.notifySubscribers(this[E])},
ga:function(){this.notifySubscribers(this[E],"beforeChange")}};a.a.ka&&a.a.Xa(B,a.J.fn);var H=a.N.gd="__ko_proto__";B[H]=a.N;a.Oa=function(b,c){return null===b||b===n||b[H]===n?!1:b[H]===c?!0:a.Oa(b[H],c)};a.H=function(b){return a.Oa(b,a.N)};a.Ba=function(b){return"function"==typeof b&&b[H]===a.N||"function"==typeof b&&b[H]===a.B&&b.Vc?!0:!1};a.b("observable",a.N);a.b("isObservable",a.H);a.b("isWriteableObservable",a.Ba);a.b("isWritableObservable",a.Ba);a.b("observable.fn",B);a.G(B,"peek",B.t);a.G(B,
"valueHasMutated",B.fa);a.G(B,"valueWillMutate",B.ga);a.la=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.N(b);a.a.Ya(b,a.la.fn);return b.extend({trackArrayChanges:!0})};a.la.fn={remove:function(b){for(var c=this.t(),d=[],e="function"!=typeof b||a.H(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ga(),d.push(g),c.splice(f,1),f--)}d.length&&
this.fa();return d},removeAll:function(b){if(b===n){var c=this.t(),d=c.slice(0);this.ga();c.splice(0,c.length);this.fa();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.t(),d="function"!=typeof b||a.H(b)?function(a){return a===b}:b;this.ga();for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.fa()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=
this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ga(),this.t()[d]=c,this.fa())}};a.a.ka&&a.a.Xa(a.la.fn,a.N.fn);a.a.q("pop push reverse shift sort splice unshift".split(" "),function(b){a.la.fn[b]=function(){var a=this.t();this.ga();this.Vb(a,b,arguments);var d=a[b].apply(a,arguments);this.fa();return d===a?this:d}});a.a.q(["slice"],function(b){a.la.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.la);a.ya.trackArrayChanges=function(b,
c){function d(){if(!e){e=!0;var c=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==I||++k;return c.apply(this,arguments)};var d=[].concat(b.t()||[]);f=null;g=b.X(function(c){c=[].concat(c||[]);if(b.Pa("arrayChange")){var e;if(!f||1<k)f=a.a.ib(d,c,b.hb);e=f}d=c;f=null;k=0;e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.hb={};c&&"object"==typeof c&&a.a.extend(b.hb,c);b.hb.sparse=!0;if(!b.Vb){var e=!1,f=null,g,k=0,l=b.sa,m=b.Ia;b.sa=function(a){l&&l.call(b,a);"arrayChange"===a&&d()};
b.Ia=function(a){m&&m.call(b,a);"arrayChange"!==a||b.Pa("arrayChange")||(g.k(),e=!1)};b.Vb=function(b,c,d){function m(a,b,c){return l[l.length]={status:a,value:b,index:c}}if(e&&!k){var l=[],g=b.length,t=d.length,G=0;switch(c){case "push":G=g;case "unshift":for(c=0;c<t;c++)m("added",d[c],G+c);break;case "pop":G=g-1;case "shift":g&&m("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?g+d[0]:d[0]),g);for(var g=1===t?g:Math.min(c+(d[1]||0),g),t=c+t-2,G=Math.max(g,t),P=[],n=[],Q=2;c<G;++c,
++Q)c<g&&n.push(m("deleted",b[c],c)),c<t&&P.push(m("added",d[Q],c));a.a.dc(n,P);break;default:return}f=l}}}};var s=a.a.Yb("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.pb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");return this}a.l.oc(e);(g.S||g.s&&e.Qa())&&e.aa();return g.T}"object"===typeof b?d=b:(d=d||{},b&&(d.read=
b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={T:n,S:!0,Ra:!1,Fb:!1,R:!1,Va:!1,s:!1,jd:d.read,pb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,wa:d.disposeWhen||d.wa,mb:null,r:{},L:0,bc:null};e[s]=g;e.Vc="function"===typeof f;a.a.ka||a.a.extend(e,a.J.fn);a.J.fn.rb(e);a.a.Ya(e,z);d.pure?(g.Va=!0,g.s=!0,a.a.extend(e,$)):d.deferEvaluation&&a.a.extend(e,aa);a.options.deferUpdates&&a.ya.deferred(e,!0);g.i&&(g.Fb=!0,g.i.nodeType||
(g.i=null));g.s||d.deferEvaluation||e.aa();g.i&&e.ba()&&a.a.F.oa(g.i,g.mb=function(){e.k()});return e};var z={equalityComparer:J,Aa:function(){return this[s].L},Pb:function(a,c,d){if(this[s].Va&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].r[a]=d;d.Ga=this[s].L++;d.na=c.Na()},Qa:function(){var a,c,d=this[s].r;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],c.ia.Uc(c.na)))return!0},bd:function(){this.Fa&&!this[s].Ra&&this.Fa()},ba:function(){return this[s].S||0<this[s].L},
ld:function(){this.Mb||this.ac()},uc:function(a){if(a.cb&&!this[s].i){var c=a.X(this.bd,this,"dirty"),d=a.X(this.ld,this);return{ia:a,k:function(){c.k();d.k()}}}return a.X(this.ac,this)},ac:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].bc),this[s].bc=a.a.setTimeout(function(){b.aa(!0)},c)):b.Fa?b.Fa():b.aa(!0)},aa:function(b){var c=this[s],d=c.wa;if(!c.Ra&&!c.R){if(c.i&&!a.a.nb(c.i)||d&&d()){if(!c.Fb){this.k();return}}else c.Fb=!1;c.Ra=!0;try{this.Qc(b)}finally{c.Ra=!1}c.L||
this.k()}},Qc:function(b){var c=this[s],d=c.Va?n:!c.L,e={Hc:this,Ma:c.r,lb:c.L};a.l.Ub({Gc:e,gb:Y,m:this,Sa:d});c.r={};c.L=0;e=this.Pc(c,e);this.tb(c.T,e)&&(c.s||this.notifySubscribers(c.T,"beforeChange"),c.T=e,c.s?this.zc():b&&this.notifySubscribers(c.T));d&&this.notifySubscribers(c.T,"awake")},Pc:function(b,c){try{var d=b.jd;return b.pb?d.call(b.pb):d()}finally{a.l.end(),c.lb&&!b.s&&a.a.D(c.Ma,X),b.S=!1}},t:function(){var a=this[s];(a.S&&!a.L||a.s&&this.Qa())&&this.aa();return a.T},Ta:function(b){a.J.fn.Ta.call(this,
b);this.Fa=function(){this.Kb(this[s].T);this[s].S=!0;this.Lb(this)}},k:function(){var b=this[s];!b.s&&b.r&&a.a.D(b.r,function(a,b){b.k&&b.k()});b.i&&b.mb&&a.a.F.pc(b.i,b.mb);b.r=null;b.L=0;b.R=!0;b.S=!1;b.s=!1;b.i=null}},$={sa:function(b){var c=this,d=c[s];if(!d.R&&d.s&&"change"==b){d.s=!1;if(d.S||c.Qa())d.r=null,d.L=0,d.S=!0,c.aa();else{var e=[];a.a.D(d.r,function(a,b){e[b.Ga]=a});a.a.q(e,function(a,b){var e=d.r[a],l=c.uc(e.ia);l.Ga=b;l.na=e.na;d.r[a]=l})}d.R||c.notifySubscribers(d.T,"awake")}},
Ia:function(b){var c=this[s];c.R||"change"!=b||this.Pa("change")||(a.a.D(c.r,function(a,b){b.k&&(c.r[a]={ia:b.ia,Ga:b.Ga,na:b.na},b.k())}),c.s=!0,this.notifySubscribers(n,"asleep"))},Na:function(){var b=this[s];b.s&&(b.S||this.Qa())&&this.aa();return a.J.fn.Na.call(this)}},aa={sa:function(a){"change"!=a&&"beforeChange"!=a||this.t()}};a.a.ka&&a.a.Xa(z,a.J.fn);var R=a.N.gd;a.m[R]=a.N;z[R]=a.m;a.Xc=function(b){return a.Oa(b,a.m)};a.Yc=function(b){return a.Oa(b,a.m)&&b[s]&&b[s].Va};a.b("computed",a.m);
a.b("dependentObservable",a.m);a.b("isComputed",a.Xc);a.b("isPureComputed",a.Yc);a.b("computed.fn",z);a.G(z,"peek",z.t);a.G(z,"dispose",z.k);a.G(z,"isActive",z.ba);a.G(z,"getDependenciesCount",z.Aa);a.nc=function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.nc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof
Number||a instanceof Boolean)return a;var k=a instanceof Array?[]:{};g.save(a,k);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":k[c]=d;break;case "object":case "undefined":var h=g.get(d);k[c]=h!==n?h:b(d,f,g)}});return k}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Ib=[]}a.wc=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");
return b(c,function(b){for(var c=0;a.H(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.wc(b);return a.a.Eb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,b);0<=d?this.Ib[d]=c:(this.keys.push(b),this.Ib.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Ib[b]:n}}})();a.b("toJS",a.wc);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.xb):7>=a.a.C?b.getAttributeNode("value")&&
b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ha:function(b,c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.xb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.xb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||
null===c)c=n;for(var e=-1,f=0,g=b.options.length,k;f<g;++f)if(k=a.j.u(b.options[f]),k==c||""==k&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ha);a.h=function(){function b(b){b=a.a.$a(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,k=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var t=y.charCodeAt(0);
if(44===t){if(0>=p){c.push(r&&k.length?{key:r,value:k.join("")}:{unknown:r||k.join("")});r=p=0;k=[];continue}}else if(58===t){if(!p&&!r&&1===k.length){r=k.pop();continue}}else 47===t&&A&&1<y.length?(t=d[A-1].match(f))&&!g[t[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===t||123===t||91===t?++p:41===t||125===t||93===t?--p:r||k.length||34!==t&&39!==t||(y=y.slice(1,-1));k.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,
e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,g={"in":1,"return":1,"typeof":1},k={};return{ta:[],ea:k,yb:b,Ua:function(e,m){function h(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,h)))return;if(l=k[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e=
"function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.q(y,function(a){h(a.key||a.unknown,a.value)});g.length&&h("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},ad:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ea:function(b,c,d,e,f){if(b&&a.H(b))!a.Ba(b)||f&&b.t()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",
a.h.ta);a.b("expressionRewriting.parseObjectLiteral",a.h.yb);a.b("expressionRewriting.preProcessBindings",a.h.Ua);a.b("expressionRewriting._twoWayBindings",a.h.ea);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ua);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);
b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:null}var f=u&&"\x3c!--test--\x3e"===u.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,k=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={Z:{},childNodes:function(a){return b(a)?d(a):a.childNodes},xa:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=
0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.ob(c)},da:function(c,d){if(b(c)){a.f.xa(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.da(c,d)},mc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},gc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.mc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||
c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Tc:b,pd:function(a){return(a=(f?a.text:a.nodeValue).match(g))?a[1]:null},kc:function(d){if(l[a.a.A(d)]){var h=d.firstChild;if(h){do if(1===h.nodeType){var f;f=h.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var k=e(f,!0);k?f=k:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=h.nextSibling,k=0;k<f.length;k++)g?d.insertBefore(f[k],
g):d.appendChild(f[k])}while(h=h.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.Z);a.b("virtualElements.emptyNode",a.f.xa);a.b("virtualElements.insertAfter",a.f.gc);a.b("virtualElements.prepend",a.f.mc);a.b("virtualElements.setDomNodeChildren",a.f.da);(function(){a.Q=function(){this.Fc={}};a.a.extend(a.Q.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Tc(b);
default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Ob(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Ob(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.pd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Fc,g=b+(e&&e.valueAccessors||
""),k;if(!(k=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Ua(b,e)+"}}}";l=new Function("$context","$element",m);k=f[g]=l}return k(c,d)}catch(h){throw h.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+h.message,h;}}});a.Q.instance=new a.Q})();a.b("bindingProvider",a.Q);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ca(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,h){return"function"===
typeof c?d(c.bind(null,e,h)):a.a.Ca(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,h=a.f.firstChild(c),f=a.Q.instance,m=f.preprocessNode;if(m){for(;e=h;)h=a.f.nextSibling(e),m.call(f,e);h=a.f.firstChild(c)}for(;e=h;)h=a.f.nextSibling(e),k(b,e,d)}function k(b,c,d){var e=!0,h=1===c.nodeType;h&&a.f.kc(c);if(h&&d||a.Q.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!h)}function l(b){var c=[],d={},e=[];a.a.D(b,function Z(h){if(!d[h]){var f=
a.getBindingHandler(h);f&&(f.after&&(e.push(h),a.a.q(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));Z(c)}}),e.length--),c.push({key:h,fc:f}));d[h]=!0}});return c}function m(b,d,e,h){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&h&&a.tc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var k=a.Q.instance,r=k.getBindingAccessors||
f,p=a.B(function(){(g=d?d(e,b):r.call(k,b,e))&&e.P&&e.P();return g},null,{i:b});g&&p.ba()||(p=null)}var u;if(g){var v=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},s=function(){return a.a.Ca(p?p():g,c)};s.get=function(a){return g[a]&&c(v(a))};s.has=function(a){return a in g};h=l(g);a.a.q(h,function(c){var d=c.fc.init,h=c.fc.update,f=c.key;if(8===b.nodeType&&!a.f.Z[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=
d(b,v(f),s,e.$data,e);if(a&&a.controlsDescendantBindings){if(u!==n)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof h&&a.B(function(){h(b,v(f),s,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===n}}function h(b){return b&&b instanceof a.U?b:new a.U(b)}
a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.U=function(b,c,d,e){var h=this,f="function"==typeof b&&!a.H(b),m,g=a.B(function(){var m=f?b():b,l=a.a.c(m);c?(c.P&&c.P(),a.a.extend(h,c),g&&(h.P=g)):(h.$parents=[],h.$root=l,h.ko=a);h.$rawData=m;h.$data=l;d&&(h[d]=l);e&&e(h,c,l);return h.$data},null,{wa:function(){return m&&!a.a.Qb(m)},i:!0});g.ba()&&(h.P=g,g.equalityComparer=null,m=[],g.Ac=function(b){m.push(b);a.a.F.oa(b,function(b){a.a.La(m,b);m.length||
(g.k(),h.P=g=n)})})};a.U.prototype.createChildContext=function(b,c,d){return new a.U(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.U.prototype.extend=function(b){return new a.U(this.P||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.e.I(),p=a.a.e.I();a.tc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.P&&c.P.Ac(b);else return a.a.e.get(b,
p)};a.Ja=function(b,c,d){1===b.nodeType&&a.f.kc(b);return m(b,c,h(d),!0)};a.Dc=function(b,c,d){d=h(d);return a.Ja(b,e(c,d,b),d)};a.eb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(h(a),b,!0)};a.Rb=function(a,b){!v&&x.jQuery&&(v=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;k(h(a),b,!0)};a.kb=function(b){switch(b.nodeType){case 1:case 8:var c=a.tc(b);if(c)return c;
if(b.parentNode)return a.kb(b.parentNode)}return n};a.Jc=function(b){return(b=a.kb(b))?b.$data:n};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Rb);a.b("applyBindingsToDescendants",a.eb);a.b("applyBindingAccessorsToNode",a.Ja);a.b("applyBindingsToNode",a.Dc);a.b("contextFor",a.kb);a.b("dataFor",a.Jc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,h;m?m.X(e):(m=f[c]=new a.J,m.X(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,Zc:e};delete f[c];h||e?m.notifySubscribers(b):
a.Y.Wa(function(){m.notifySubscribers(b)})}),h=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,h){h||(h=a.g.loaders.slice(0));var g=h.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,h)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,h)}else f(null)}
var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.Zc?a.l.w(function(){e(f.definition)}):a.Y.Wa(function(){e(f.definition)}):c(d,e)},Xb:function(a){delete g[a]},Jb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.Xb)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(k)}var k={},y=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.Jb("loadTemplate",[b,c],function(a){k.template=a;g()})}):g();d?f(c,d,function(c){a.g.Jb("loadViewModel",
[b,c],function(a){k[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.ma(b.text);case "textarea":return a.a.ma(b.value);case "template":if(e(b.content))return a.a.ua(b.content.childNodes)}return a.a.ua(b.childNodes)}function e(a){return x.DocumentFragment?
a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var k={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.ub(b))throw Error("Component "+b+" is already registered");k[b]=c};a.g.ub=function(a){return k.hasOwnProperty(a)};a.g.od=function(b){delete k[b];
a.g.Xb(b)};a.g.Zb={getConfig:function(a,b){b(k.hasOwnProperty(a)?k[a]:null)},loadComponent:function(a,c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.ma(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.V(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=u.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+
c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.ub);a.b("components.unregister",a.g.od);a.b("components.defaultLoader",a.g.Zb);a.g.loaders.push(a.g.Zb);a.g.Bc=k})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ca(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ca(f,function(c){var e=
c.t();return c.ba()?a.m({read:function(){return a.a.c(c())},write:a.Ba(e)&&function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.ub(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Ob=function(c,e,f,g){if(1===e.nodeType){var k=a.g.getComponentNameForNode(e);if(k){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');
var l={name:k,params:b(e,f)};c.component=g?function(){return l}:l}}return c};var c=new a.Q;9>a.a.C&&(a.g.register=function(a){return function(b){u.createElement(b);return a.apply(this,arguments)}}(a.g.register),u.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Bc,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(u.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.ua(c);a.f.da(d,b)}
function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,k,l,m){function h(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.V(a.f.childNodes(f));a.a.F.oa(f,h);a.m(function(){var l=a.a.c(g()),k,t;"string"===typeof l?k=l:(k=a.a.c(l.name),t=a.a.c(l.params));if(!k)throw Error("No component name specified");var n=q=++e;a.g.get(k,function(e){if(q===n){h();if(!e)throw Error("Unknown component '"+k+
"'");c(k,e,f);var g=d(e,f,p,t);e=m.createChildContext(g,b,function(a){a.$component=g;a.$componentTemplateNodes=p});r=g;a.eb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.Z.component=!0})();var S={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in S?(c=S[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.rc(b,
g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){var e=b.checked,f=p?g():e;if(!a.va.Sa()&&(!l||e)){var m=a.l.w(c);if(h){var k=r?m.t():m;q!==f?(e&&(a.a.pa(k,f,!0),a.a.pa(k,q,!1)),q=f):a.a.pa(k,f,e);r&&a.Ba(m)&&m(k)}else a.h.Ea(m,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=h?0<=a.a.o(d,g()):k?d:g()===d}var g=a.nc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),k=
"checkbox"==b.type,l="radio"==b.type;if(k||l){var m=c(),h=k&&a.a.c(m)instanceof Array,r=!(h&&m.push&&m.splice),q=h?g():n,p=l||h;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.p(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ea.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.bb(b,c,d)}):(d=a.a.$a(String(d||"")),a.a.bb(b,b.__ko__cssValue,
!1),b.__ko__cssValue=d,a.a.bb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.p(b,g,function(b){var m,h=c()[g];if(h){try{var r=a.a.V(arguments);e=f.$data;r.unshift(e);m=h.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():
b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={ic:function(b){return function(){var c=b(),d=a.a.zb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.W.sb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.W.sb}}},init:function(b,c){return a.d.template.init(b,
a.d.foreach.ic(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.ic(c),d,e,f)}};a.h.ta.foreach=!1;a.f.Z.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(h){g=f.body}e=g===b}f=c();a.h.Ea(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.p(b,"focus",f);a.a.p(b,"focusin",f);a.a.p(b,"blur",g);a.a.p(b,
"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Da,null,[b,d?"focusin":"focusout"]))}};a.h.ea.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ea.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Cb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.createChildContext(c)});var L={};
a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ka(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&h)a.j.ha(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.sc(e[0],f);A&&!f&&a.l.w(a.a.Da,null,[b,
"change"])}}var k=b.multiple,l=0!=b.length&&k?b.scrollTop:null,m=a.a.c(c()),h=d.get("valueAllowUnset")&&d.has("value"),r=d.get("optionsIncludeDestroyed");c={};var q,p=[];h||(k?p=a.a.fb(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ka(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};
m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Bb(b,q,function(c,e,g){g.length&&(p=!h&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.Za(e,d.get("optionsCaption")),a.j.ha(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ha(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.Za(e,c));return[e]},c,m);a.l.w(function(){h?a.j.ha(b,a.a.c(d.get("value")),
!0):(k?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:p.length||0<=b.selectedIndex)&&a.a.Da(b,"change")});a.a.Nc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.xb=a.a.e.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.p(b,"change",function(){var e=c(),f=[];a.a.q(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ea(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=
a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;d&&"number"==typeof d.length&&a.a.q(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.sc(b,c)});b.scrollTop=e}};a.h.ea.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");
a.a.p(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Za(b,c())}};a.f.Z.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));
if(10>a.a.C)var g=a.a.e.I(),k=a.a.e.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.e.get(c,k))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.p(d,"selectionchange",l));a.a.e.set(b,k,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.p(b,c,d)}function k(){var c=a.a.c(d());if(null===c||c===n)c="";v!==n&&c===v?a.a.setTimeout(k,4):b.value!==c&&(u=c,b.value=c)}function y(){s||(v=b.value,s=a.a.setTimeout(t,4))}function t(){clearTimeout(s);v=s=n;var c=
b.value;u!==c&&(u=c,a.h.Ea(d(),g,"textInput",c))}var u=b.value,s,v,x=9==a.a.C?y:t;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",t),l("keydown",t)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",t),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",t),l("dragdrop",t),l("drop",t)));l("change",t);a.m(k,null,{i:b})}};a.h.ea.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",
a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Ic;a.a.rc(b,d)}}};a.d.uniqueName.Ic=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,k=null;f&&("string"==typeof f&&(f=[f]),a.a.ra(e,f),e=a.a.Tb(e));var l=function(){k=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ea(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||
"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||(a.a.p(b,"propertychange",function(){g=!0}),a.a.p(b,"focus",function(){g=!1}),a.a.p(b,"blur",function(){g&&l()}));a.a.q(e,function(c){var d=l;a.a.nd(c,"after")&&(d=function(){k=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.p(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==k&&e===k)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ha(b,
e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Da,null,[b,"change"])}else a.j.ha(b,e)};a.m(m,null,{i:b})}else a.Ja(b,{checkedValue:c})},update:function(){}};a.h.ea.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.O=function(){};a.O.prototype.renderTemplateSource=
function(){throw Error("Override renderTemplateSource");};a.O.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.O.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||u;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.qa(b);throw Error("Unknown template type: "+b);};a.O.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,
e);return this.renderTemplateSource(a,c,d,e)};a.O.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.O.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.O);a.Gb=function(){function b(b,c,d,k){b=a.h.yb(b);for(var l=a.h.ta,m=0;m<b.length;m++){var h=b[m].key;if(l.hasOwnProperty(h)){var r=l[h];if("function"===typeof r){if(h=
r(b[m].value))throw Error(h);}else if(!r)throw Error("This template engine does not support the '"+h+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ua(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return k.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Oc:function(b,
c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Gb.dd(b,c)},d)},dd:function(a,f){return a.replace(c,function(a,c,d,e,h){return b(h,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Ec:function(b,c){return a.M.wb(function(d,k){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.Ja(l,b,k)})}}}();a.b("__tr_ambtns",a.Gb.Ec);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.ab="script"===c?1:"textarea"===c?2:"template"==c&&
b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===this.ab?"text":2===this.ab?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Cb(this.n,c):this.n[b]=c};var b=a.a.e.I()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.I();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).jb||(3===this.ab?
b.content:4===this.ab?b:n);a.a.e.set(b,c,{jb:arguments[0]})};a.v.qa=function(a){this.n=a};a.v.qa.prototype=new a.v.n;a.v.qa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Hb===n&&b.jb&&(b.Hb=b.jb.innerHTML);return b.Hb}a.a.e.set(this.n,c,{Hb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.qa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),
d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,k=a.Q.instance,n=k.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.za(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Rb(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.M.yc(b,[d])});a.a.za(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,
e,f,k,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;a.Gb.Oc(f,n,p);f=n.renderTemplate(f,k,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.da(b,f);p=!0;break;case "replaceNode":a.a.qc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,k),q.afterRender&&a.l.w(q.afterRender,null,[f,k.$data]));
return f}function f(b,c,d){return a.H(b)?b():"function"===typeof b?b(c,d):b}var g;a.Db=function(b){if(b!=n&&!(b instanceof a.O))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Ab=function(b,c,h,k,q){h=h||{};if((h.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(k){var p=d(k);return a.B(function(){var g=c&&c instanceof a.U?c:new a.U(a.a.c(c)),n=f(b,g.$data,g),g=e(k,q,n,g,h);"replaceNode"==q&&(k=g,p=d(k))},null,
{wa:function(){return!p||!a.a.nb(p)},i:p&&"replaceNode"==q?p.parentNode:p})}return a.M.wb(function(d){a.Ab(b,c,h,d,"replaceNode")})};a.kd=function(b,d,g,k,q){function p(a,b){c(b,s);g.afterRender&&g.afterRender(b,a);s=null}function u(a,c){s=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,s);return e(null,"ignoreTargetNode",d,s,g)}var s;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ka(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});
a.l.w(a.a.Bb,null,[k,b,u,g,p])},null,{i:k})};var k=a.a.e.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.xa(b);else{if("nodes"in d){if(d=d.nodes||[],a.H(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.jc(d);(new a.v.qa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c(),s;c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in
c&&(d=!a.a.c(c.ifnot)),s=a.a.c(c.data));"foreach"in c?e=a.kd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(s,c.as):f,e=a.Ab(g||b,f,c,b)):a.f.xa(b);f=e;(s=a.a.e.get(b,k))&&"function"==typeof s.k&&s.k();a.a.e.set(b,k,f&&f.ba()?f:n)}};a.h.ta.template=function(b){b=a.h.yb(b);return 1==b.length&&b[0].unknown||a.h.ad(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.Z.template=!0})();a.b("setTemplateEngine",a.Db);a.b("renderTemplate",
a.Ab);a.a.dc=function(a,c,d){if(a.length&&c.length){var e,f,g,k,l;for(e=f=0;(!d||e<d)&&(k=a[f]);++f){for(g=0;l=c[g];++g)if(k.value===l.value){k.moved=l.index;l.moved=k.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.ib=function(){function b(b,d,e,f,g){var k=Math.min,l=Math.max,m=[],h,n=b.length,q,p=d.length,s=p-n||1,u=n+p+1,t,v,x;for(h=0;h<=n;h++)for(v=t,m.push(t=[]),x=k(p,h+s),q=l(0,h-1);q<=x;q++)t[q]=q?h?b[h-1]===d[q-1]?v[q-1]:k(v[q]||u,t[q-1]||u)+1:q+1:h+1;k=[];l=[];s=[];h=n;for(q=p;h||q;)p=m[h][q]-
1,q&&p===m[h][q-1]?l.push(k[k.length]={status:e,value:d[--q],index:q}):h&&p===m[h-1][q]?s.push(k[k.length]={status:f,value:b[--h],index:h}):(--q,--h,g.sparse||k.push({status:"retained",value:d[q]}));a.a.dc(s,l,!g.dontLimitMoves&&10*n);return k.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.ib);(function(){function b(b,c,d,k,l){var m=[],
h=a.B(function(){var h=c(d,l,a.a.za(m,b))||[];0<m.length&&(a.a.qc(m,h),k&&a.l.w(k,null,[d,h,l]));m.length=0;a.a.ra(m,h)},null,{i:b,wa:function(){return!a.a.Qb(m)}});return{ca:m,B:h.ba()?h:n}}var c=a.a.e.I(),d=a.a.e.I();a.a.Bb=function(e,f,g,k,l){function m(b,c){w=q[c];v!==c&&(D[b]=w);w.qb(v++);a.a.za(w.ca,e);u.push(w);z.push(w)}function h(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.q(c[d].ca,function(a){b(a,d,c[d].ja)})}f=f||[];k=k||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.fb(q,
function(a){return a.ja}),s=a.a.ib(p,f,k.dontLimitMoves),u=[],t=0,v=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[t],w.B&&(w.B.k(),w.B=n),a.a.za(w.ca,e).length&&(k.beforeRemove&&(u.push(w),z.push(w),w.ja===d?w=null:f[C]=w),w&&x.push.apply(x,w.ca)));t++;break;case "retained":m(C,t++);break;case "added":E!==n?m(C,E):(w={ja:B.value,qb:a.N(v++)},u.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,u);h(k.beforeMove,D);a.a.q(x,k.beforeRemove?a.$:
a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ca||a.a.extend(w,b(e,g,w.ja,l,w.qb));for(t=0;s=w.ca[t];r=s.nextSibling,F=s,t++)s!==r&&a.f.gc(e,s,F);!w.Wc&&l&&(l(w.ja,w.ca,w.qb),w.Wc=!0)}h(k.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ja=d);h(k.afterMove,D);h(k.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Bb);a.W=function(){this.allowTemplateRewriting=!1};a.W.prototype=new a.O;a.W.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?
b.nodes():null)return a.a.V(c.cloneNode(!0).childNodes);b=b.text();return a.a.ma(b,e)};a.W.sb=new a.W;a.Db(a.W.sb);a.b("nativeTemplateEngine",a.W);(function(){a.vb=function(){var a=this.$c=function(){if(!v||!v.tmpl)return 0;try{if(0<=v.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||u;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var k=b.data("precompiled");
k||(k=b.text()||"",k=v.template(null,"{{ko_with $item.koBindingContext}}"+k+"{{/ko_with}}"),b.data("precompiled",k));b=[e.$data];e=v.extend({koBindingContext:e},f.templateOptions);e=v.tmpl(k,b,e);e.appendTo(g.createElement("div"));v.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){u.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(v.tmpl.tag.ko_code={open:"__.push($1 || '');"},
v.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.vb.prototype=new a.O;var b=new a.vb;0<b.$c&&a.Db(b);a.b("jqueryTmplTemplateEngine",a.vb)})()})})();})();

/**
 * @license
 * Knockout ES5 plugin - https://github.com/SteveSanderson/knockout-es5
 * Copyright (c) Steve Sanderson
 * MIT license
 */

define('ThirdParty/knockout-es5',[],function() {
    'use strict';

    var OBSERVABLES_PROPERTY = '__knockoutObservables';
    var SUBSCRIBABLE_PROPERTY = '__knockoutSubscribable';

    // Model tracking
    // --------------
    //
    // This is the central feature of Knockout-ES5. We augment model objects by converting properties
    // into ES5 getter/setter pairs that read/write an underlying Knockout observable. This means you can
    // use plain JavaScript syntax to read/write the property while still getting the full benefits of
    // Knockout's automatic dependency detection and notification triggering.
    //
    // For comparison, here's Knockout ES3-compatible syntax:
    //
    //     var firstNameLength = myModel.user().firstName().length; // Read
    //     myModel.user().firstName('Bert'); // Write
    //
    // ... versus Knockout-ES5 syntax:
    //
    //     var firstNameLength = myModel.user.firstName.length; // Read
    //     myModel.user.firstName = 'Bert'; // Write

    // `ko.track(model)` converts each property on the given model object into a getter/setter pair that
    // wraps a Knockout observable. Optionally specify an array of property names to wrap; otherwise we
    // wrap all properties. If any of the properties are already observables, we replace them with
    // ES5 getter/setter pairs that wrap your original observable instances. In the case of readonly
    // ko.computed properties, we simply do not define a setter (so attempted writes will be ignored,
    // which is how ES5 readonly properties normally behave).
    //
    // By design, this does *not* recursively walk child object properties, because making literally
    // everything everywhere independently observable is usually unhelpful. When you do want to track
    // child object properties independently, define your own class for those child objects and put
    // a separate ko.track call into its constructor --- this gives you far more control.
    function track(obj, propertyNames) {
        if (!obj /*|| typeof obj !== 'object'*/) {
            throw new Error('When calling ko.track, you must pass an object as the first parameter.');
        }

        var ko = this,
            allObservablesForObject = getAllObservablesForObject(obj, true);
        propertyNames = propertyNames || Object.getOwnPropertyNames(obj);

        propertyNames.forEach(function(propertyName) {
            // Skip storage properties
            if (propertyName === OBSERVABLES_PROPERTY || propertyName === SUBSCRIBABLE_PROPERTY) {
                return;
            }
            // Skip properties that are already tracked
            if (propertyName in allObservablesForObject) {
                return;
            }

            var origValue = obj[propertyName],
                isArray = origValue instanceof Array,
                observable = ko.isObservable(origValue) ? origValue
                                              : isArray ? ko.observableArray(origValue)
                                                        : ko.observable(origValue);

            Object.defineProperty(obj, propertyName, {
                configurable: true,
                enumerable: true,
                get: observable,
                set: ko.isWriteableObservable(observable) ? observable : undefined
            });

            allObservablesForObject[propertyName] = observable;

            if (isArray) {
                notifyWhenPresentOrFutureArrayValuesMutate(ko, observable);
            }
        });

        return obj;
    }

    // Gets or creates the hidden internal key-value collection of observables corresponding to
    // properties on the model object.
    function getAllObservablesForObject(obj, createIfNotDefined) {
        var result = obj[OBSERVABLES_PROPERTY];
        if (!result && createIfNotDefined) {
            result = {};
            Object.defineProperty(obj, OBSERVABLES_PROPERTY, {
                value : result
            });
        }
        return result;
    }

    // Computed properties
    // -------------------
    //
    // The preceding code is already sufficient to upgrade ko.computed model properties to ES5
    // getter/setter pairs (or in the case of readonly ko.computed properties, just a getter).
    // These then behave like a regular property with a getter function, except they are smarter:
    // your evaluator is only invoked when one of its dependencies changes. The result is cached
    // and used for all evaluations until the next time a dependency changes).
    //
    // However, instead of forcing developers to declare a ko.computed property explicitly, it's
    // nice to offer a utility function that declares a computed getter directly.

    // Implements `ko.defineProperty`
    function defineComputedProperty(obj, propertyName, evaluatorOrOptions) {
        var ko = this,
            computedOptions = { owner: obj, deferEvaluation: true };

        if (typeof evaluatorOrOptions === 'function') {
            computedOptions.read = evaluatorOrOptions;
        } else {
            if ('value' in evaluatorOrOptions) {
                throw new Error('For ko.defineProperty, you must not specify a "value" for the property. You must provide a "get" function.');
            }

            if (typeof evaluatorOrOptions.get !== 'function') {
                throw new Error('For ko.defineProperty, the third parameter must be either an evaluator function, or an options object containing a function called "get".');
            }

            computedOptions.read = evaluatorOrOptions.get;
            computedOptions.write = evaluatorOrOptions.set;
        }

        obj[propertyName] = ko.computed(computedOptions);
        track.call(ko, obj, [propertyName]);
        return obj;
    }

    // Array handling
    // --------------
    //
    // Arrays are special, because unlike other property types, they have standard mutator functions
    // (`push`/`pop`/`splice`/etc.) and it's desirable to trigger a change notification whenever one of
    // those mutator functions is invoked.
    //
    // Traditionally, Knockout handles this by putting special versions of `push`/`pop`/etc. on observable
    // arrays that mutate the underlying array and then trigger a notification. That approach doesn't
    // work for Knockout-ES5 because properties now return the underlying arrays, so the mutator runs
    // in the context of the underlying array, not any particular observable:
    //
    //     // Operates on the underlying array value
    //     myModel.someCollection.push('New value');
    //
    // To solve this, Knockout-ES5 detects array values, and modifies them as follows:
    //  1. Associates a hidden subscribable with each array instance that it encounters
    //  2. Intercepts standard mutators (`push`/`pop`/etc.) and makes them trigger the subscribable
    // Then, for model properties whose values are arrays, the property's underlying observable
    // subscribes to the array subscribable, so it can trigger a change notification after mutation.

    // Given an observable that underlies a model property, watch for any array value that might
    // be assigned as the property value, and hook into its change events
    function notifyWhenPresentOrFutureArrayValuesMutate(ko, observable) {
        var watchingArraySubscription = null;
        ko.computed(function () {
            // Unsubscribe to any earlier array instance
            if (watchingArraySubscription) {
                watchingArraySubscription.dispose();
                watchingArraySubscription = null;
            }

            // Subscribe to the new array instance
            var newArrayInstance = observable();
            if (newArrayInstance instanceof Array) {
                watchingArraySubscription = startWatchingArrayInstance(ko, observable, newArrayInstance);
            }
        });
    }

    // Listens for array mutations, and when they happen, cause the observable to fire notifications.
    // This is used to make model properties of type array fire notifications when the array changes.
    // Returns a subscribable that can later be disposed.
    function startWatchingArrayInstance(ko, observable, arrayInstance) {
        var subscribable = getSubscribableForArray(ko, arrayInstance);
        return subscribable.subscribe(observable);
    }

    // Gets or creates a subscribable that fires after each array mutation
    function getSubscribableForArray(ko, arrayInstance) {
        var subscribable = arrayInstance[SUBSCRIBABLE_PROPERTY];
        if (!subscribable) {
            subscribable = new ko.subscribable();
            Object.defineProperty(arrayInstance, SUBSCRIBABLE_PROPERTY, {
                value : subscribable
            });

            var notificationPauseSignal = {};
            wrapStandardArrayMutators(arrayInstance, subscribable, notificationPauseSignal);
            addKnockoutArrayMutators(ko, arrayInstance, subscribable, notificationPauseSignal);
        }

        return subscribable;
    }

    // After each array mutation, fires a notification on the given subscribable
    function wrapStandardArrayMutators(arrayInstance, subscribable, notificationPauseSignal) {
        ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'].forEach(function(fnName) {
            var origMutator = arrayInstance[fnName];
            arrayInstance[fnName] = function() {
                var result = origMutator.apply(this, arguments);
                if (notificationPauseSignal.pause !== true) {
                    subscribable.notifySubscribers(this);
                }
                return result;
            };
        });
    }

    // Adds Knockout's additional array mutation functions to the array
    function addKnockoutArrayMutators(ko, arrayInstance, subscribable, notificationPauseSignal) {
        ['remove', 'removeAll', 'destroy', 'destroyAll', 'replace'].forEach(function(fnName) {
            // Make it a non-enumerable property for consistency with standard Array functions
            Object.defineProperty(arrayInstance, fnName, {
                enumerable: false,
                value: function() {
                    var result;

                    // These additional array mutators are built using the underlying push/pop/etc.
                    // mutators, which are wrapped to trigger notifications. But we don't want to
                    // trigger multiple notifications, so pause the push/pop/etc. wrappers and
                    // delivery only one notification at the end of the process.
                    notificationPauseSignal.pause = true;
                    try {
                        // Creates a temporary observableArray that can perform the operation.
                        result = ko.observableArray.fn[fnName].apply(ko.observableArray(arrayInstance), arguments);
                    }
                    finally {
                        notificationPauseSignal.pause = false;
                    }
                    subscribable.notifySubscribers(arrayInstance);
                    return result;
                }
            });
        });
    }

    // Static utility functions
    // ------------------------
    //
    // Since Knockout-ES5 sets up properties that return values, not observables, you can't
    // trivially subscribe to the underlying observables (e.g., `someProperty.subscribe(...)`),
    // or tell them that object values have mutated, etc. To handle this, we set up some
    // extra utility functions that can return or work with the underlying observables.

    // Returns the underlying observable associated with a model property (or `null` if the
    // model or property doesn't exist, or isn't associated with an observable). This means
    // you can subscribe to the property, e.g.:
    //
    //     ko.getObservable(model, 'propertyName')
    //       .subscribe(function(newValue) { ... });
    function getObservable(obj, propertyName) {
        if (!obj /*|| typeof obj !== 'object'*/) {
            return null;
        }

        var allObservablesForObject = getAllObservablesForObject(obj, false);
        return (allObservablesForObject && allObservablesForObject[propertyName]) || null;
    }

    // Causes a property's associated observable to fire a change notification. Useful when
    // the property value is a complex object and you've modified a child property.
    function valueHasMutated(obj, propertyName) {
        var observable = getObservable(obj, propertyName);

        if (observable) {
            observable.valueHasMutated();
        }
    }

    // Extends a Knockout instance with Knockout-ES5 functionality
    function attachToKo(ko) {
        ko.track = track;
        ko.getObservable = getObservable;
        ko.valueHasMutated = valueHasMutated;
        ko.defineProperty = defineComputedProperty;
    }

    return {
        attachToKo : attachToKo
    };
});

/*global define*/
define('Widgets/SvgPathBindingHandler',[],function() {
    'use strict';

    var svgNS = 'http://www.w3.org/2000/svg';
    var svgClassName = 'cesium-svgPath-svg';

    /**
     * A Knockout binding handler that creates a DOM element for a single SVG path.
     * This binding handler will be registered as cesiumSvgPath.
     *
     * <p>
     * The parameter to this binding is an object with the following properties:
     * </p>
     *
     * <ul>
     * <li>path: The SVG path as a string.</li>
     * <li>width: The width of the SVG path with no transformations applied.</li>
     * <li>height: The height of the SVG path with no transformations applied.</li>
     * <li>css: Optional. A string containing additional CSS classes to apply to the SVG. 'cesium-svgPath-svg' is always applied.</li>
     * </ul>
     *
     * @exports SvgPathBindingHandler
     *
     * @example
     * // Create an SVG as a child of a div
     * <div data-bind="cesiumSvgPath: { path: 'M 100 100 L 300 100 L 200 300 z', width: 28, height: 28 }"></div>
     *
     * // parameters can be observable from the view model
     * <div data-bind="cesiumSvgPath: { path: currentPath, width: currentWidth, height: currentHeight }"></div>
     *
     * // or the whole object can be observable from the view model
     * <div data-bind="cesiumSvgPath: svgPathOptions"></div>
     */
    var SvgPathBindingHandler = {
        register : function(knockout) {
            knockout.bindingHandlers.cesiumSvgPath = {
                init : function(element, valueAccessor) {
                    var svg = document.createElementNS(svgNS, 'svg:svg');
                    svg.setAttribute('class', svgClassName);

                    var pathElement = document.createElementNS(svgNS, 'path');
                    svg.appendChild(pathElement);

                    knockout.virtualElements.setDomNodeChildren(element, [svg]);

                    knockout.computed({
                        read : function() {
                            var value = knockout.unwrap(valueAccessor());

                            pathElement.setAttribute('d', knockout.unwrap(value.path));

                            var pathWidth = knockout.unwrap(value.width);
                            var pathHeight = knockout.unwrap(value.height);

                            svg.setAttribute('width', pathWidth);
                            svg.setAttribute('height', pathHeight);
                            svg.setAttribute('viewBox', '0 0 ' + pathWidth + ' ' + pathHeight);

                            if (value.css) {
                                svg.setAttribute('class', svgClassName + ' ' + knockout.unwrap(value.css));
                            }
                        },
                        disposeWhenNodeIsRemoved : element
                    });

                    return {
                        controlsDescendantBindings : true
                    };
                }
            };

            knockout.virtualElements.allowedBindings.cesiumSvgPath = true;
        }
    };

    return SvgPathBindingHandler;
});

/*global define*/
define('ThirdParty/knockout',[
        './knockout-3.4.0',
        './knockout-es5',
        '../Widgets/SvgPathBindingHandler'
    ], function(
        knockout,
        knockout_es5,
        SvgPathBindingHandler) {
    "use strict";

    // install the Knockout-ES5 plugin
    knockout_es5.attachToKo(knockout);

    // Register all Cesium binding handlers
    SvgPathBindingHandler.register(knockout);

    return knockout;
});

/**
 * @license
 * NoSleep.js v0.5.0 - git.io/vfn01
 * Rich Tibbett
 * MIT license
 **/
/*global define*/
define('ThirdParty/NoSleep',[],function() {
    "use strict";

    // UA matching
    var ua = {
        Android: typeof navigator !== 'undefined' && /Android/ig.test(navigator.userAgent),
        iOS: typeof navigator !== 'undefined' && /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)
    };

    var media = {
        WebM: "data:video/webm;base64,GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=",
        MP4: "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAAG21kYXQAAAGzABAHAAABthADAowdbb9/AAAC6W1vb3YAAABsbXZoZAAAAAB8JbCAfCWwgAAAA+gAAAAAAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAIVdHJhawAAAFx0a2hkAAAAD3wlsIB8JbCAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAIAAAACAAAAAABsW1kaWEAAAAgbWRoZAAAAAB8JbCAfCWwgAAAA+gAAAAAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAVxtaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEcc3RibAAAALhzdHNkAAAAAAAAAAEAAACobXA0dgAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAIAAgASAAAAEgAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABj//wAAAFJlc2RzAAAAAANEAAEABDwgEQAAAAADDUAAAAAABS0AAAGwAQAAAbWJEwAAAQAAAAEgAMSNiB9FAEQBFGMAAAGyTGF2YzUyLjg3LjQGAQIAAAAYc3R0cwAAAAAAAAABAAAAAQAAAAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAEAAAABAAAAFHN0c3oAAAAAAAAAEwAAAAEAAAAUc3RjbwAAAAAAAAABAAAALAAAAGB1ZHRhAAAAWG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAK2lsc3QAAAAjqXRvbwAAABtkYXRhAAAAAQAAAABMYXZmNTIuNzguMw=="
    };

    function addSourceToVideo(element, type, dataURI) {
        var source = document.createElement('source');
        source.src = dataURI;
        source.type = "video/" + type;
        element.appendChild(source);
    }

    // NoSleep instance constructor
    var NoSleep = function() {
        if (ua.iOS) {
            this.noSleepTimer = null;
        } else if (ua.Android) {
            // Set up no sleep video element
            this.noSleepVideo = document.createElement('video');
            this.noSleepVideo.setAttribute("loop", "");

            // Append nosleep video sources
            addSourceToVideo(this.noSleepVideo, "webm", media.WebM);
            addSourceToVideo(this.noSleepVideo, "mp4", media.MP4);
        }

        return this;
    };

    // Enable NoSleep instance
    NoSleep.prototype.enable = function(duration) {
        if (ua.iOS) {
            this.disable();
            this.noSleepTimer = window.setInterval(function() {
                window.location = window.location;
                window.setTimeout(window.stop, 0);
            }, duration || 15000);
        } else if (ua.Android) {
            this.noSleepVideo.play();
        }
    };

    // Disable NoSleep instance
    NoSleep.prototype.disable = function() {
        if (ua.iOS) {
            if (this.noSleepTimer) {
                window.clearInterval(this.noSleepTimer);
                this.noSleepTimer = null;
            }
        } else if (ua.Android) {
            this.noSleepVideo.pause();
        }
    };

    // Append NoSleep API to root object
    //root.NoSleep = NoSleep;

    return NoSleep;
});

/*global define*/
define('Widgets/subscribeAndEvaluate',[
        '../ThirdParty/knockout'
    ], function(
        knockout) {
    'use strict';

    /**
     * Subscribe to a Knockout observable ES5 property, and immediately fire
     * the callback with the current value of the property.
     *
     * @private
     *
     * @exports subscribeAndEvaluate
     *
     * @param {Object} owner The object containing the observable property.
     * @param {String} observablePropertyName The name of the observable property.
     * @param {Function} callback The callback function.
     * @param {Object} [target] The value of this in the callback function.
     * @param {String} [event='change'] The name of the event to receive notification for.
     * @returns The subscription object from Knockout which can be used to dispose the subscription later.
     */
    function subscribeAndEvaluate(owner, observablePropertyName, callback, target, event) {
        callback.call(target, owner[observablePropertyName]);
        return knockout.getObservable(owner, observablePropertyName).subscribe(callback, target, event);
    }

    return subscribeAndEvaluate;
});

/*global define*/
define('Widgets/Animation/Animation',[
        '../../Core/Color',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../getElement',
        '../subscribeAndEvaluate'
    ], function(
        Color,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        getElement,
        subscribeAndEvaluate) {
    'use strict';

    var svgNS = "http://www.w3.org/2000/svg";
    var xlinkNS = "http://www.w3.org/1999/xlink";

    var widgetForDrag;

    var gradientEnabledColor0 = Color.fromCssColorString('rgba(247,250,255,0.384)');
    var gradientEnabledColor1 = Color.fromCssColorString('rgba(143,191,255,0.216)');
    var gradientEnabledColor2 = Color.fromCssColorString('rgba(153,197,255,0.098)');
    var gradientEnabledColor3 = Color.fromCssColorString('rgba(255,255,255,0.086)');

    var gradientDisabledColor0 = Color.fromCssColorString('rgba(255,255,255,0.267)');
    var gradientDisabledColor1 = Color.fromCssColorString('rgba(255,255,255,0)');

    var gradientKnobColor = Color.fromCssColorString('rgba(66,67,68,0.3)');
    var gradientPointerColor = Color.fromCssColorString('rgba(0,0,0,0.5)');

    function getElementColor(element) {
        return Color.fromCssColorString(window.getComputedStyle(element).getPropertyValue('color'));
    }

    //Dynamically builds an SVG element from a JSON object.
    function svgFromObject(obj) {
        var ele = document.createElementNS(svgNS, obj.tagName);
        for ( var field in obj) {
            if (obj.hasOwnProperty(field) && field !== 'tagName') {
                if (field === 'children') {
                    var i;
                    var len = obj.children.length;
                    for (i = 0; i < len; ++i) {
                        ele.appendChild(svgFromObject(obj.children[i]));
                    }
                } else if (field.indexOf('xlink:') === 0) {
                    ele.setAttributeNS(xlinkNS, field.substring(6), obj[field]);
                } else if (field === 'textContent') {
                    ele.textContent = obj[field];
                } else {
                    ele.setAttribute(field, obj[field]);
                }
            }
        }
        return ele;
    }

    function svgText(x, y, msg) {
        var text = document.createElementNS(svgNS, 'text');
        text.setAttribute('x', x);
        text.setAttribute('y', y);
        text.setAttribute('class', 'cesium-animation-svgText');

        var tspan = document.createElementNS(svgNS, 'tspan');
        tspan.textContent = msg;
        text.appendChild(tspan);
        return text;
    }

    function setShuttleRingPointer(shuttleRingPointer, knobOuter, angle) {
        shuttleRingPointer.setAttribute('transform', 'translate(100,100) rotate(' + angle + ')');
        knobOuter.setAttribute('transform', 'rotate(' + angle + ')');
    }

    var makeColorStringScratch = new Color();
    function makeColorString(background, gradient) {
        var gradientAlpha = gradient.alpha;
        var backgroundAlpha = 1.0 - gradientAlpha;
        makeColorStringScratch.red = (background.red * backgroundAlpha) + (gradient.red * gradientAlpha);
        makeColorStringScratch.green = (background.green * backgroundAlpha) + (gradient.green * gradientAlpha);
        makeColorStringScratch.blue = (background.blue * backgroundAlpha) + (gradient.blue * gradientAlpha);
        return makeColorStringScratch.toCssColorString();
    }

    function rectButton(x, y, path) {
        var button = {
            tagName : 'g',
            'class' : 'cesium-animation-rectButton',
            transform : 'translate(' + x + ',' + y + ')',
            children : [{
                tagName : 'rect',
                'class' : 'cesium-animation-buttonGlow',
                width : 32,
                height : 32,
                rx : 2,
                ry : 2
            }, {
                tagName : 'rect',
                'class' : 'cesium-animation-buttonMain',
                width : 32,
                height : 32,
                rx : 4,
                ry : 4
            }, {
                tagName : 'use',
                'class' : 'cesium-animation-buttonPath',
                'xlink:href' : path
            }, {
                tagName : 'title',
                textContent : ''
            }]
        };
        return svgFromObject(button);
    }

    function wingButton(x, y, path) {
        var button = {
            tagName : 'g',
            'class' : 'cesium-animation-rectButton',
            transform : 'translate(' + x + ',' + y + ')',
            children : [{
                tagName : 'use',
                'class' : 'cesium-animation-buttonGlow',
                'xlink:href' : '#animation_pathWingButton'
            }, {
                tagName : 'use',
                'class' : 'cesium-animation-buttonMain',
                'xlink:href' : '#animation_pathWingButton'
            }, {
                tagName : 'use',
                'class' : 'cesium-animation-buttonPath',
                'xlink:href' : path
            }, {
                tagName : 'title',
                textContent : ''
            }]
        };
        return svgFromObject(button);
    }

    function setShuttleRingFromMouseOrTouch(widget, e) {
        var viewModel = widget._viewModel;
        var shuttleRingDragging = viewModel.shuttleRingDragging;

        if (shuttleRingDragging && (widgetForDrag !== widget)) {
            return;
        }

        if (e.type === 'mousedown' || (shuttleRingDragging && e.type === 'mousemove') ||
                (e.type === 'touchstart' && e.touches.length === 1) ||
                (shuttleRingDragging && e.type === 'touchmove' && e.touches.length === 1)) {
            var centerX = widget._centerX;
            var centerY = widget._centerY;
            var svg = widget._svgNode;
            var rect = svg.getBoundingClientRect();
            var clientX;
            var clientY;
            if (e.type === 'touchstart' || e.type === 'touchmove') {
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }

            if (!shuttleRingDragging &&
                (clientX > rect.right ||
                 clientX < rect.left ||
                 clientY < rect.top ||
                 clientY > rect.bottom)) {
                return;
            }

            var pointerRect = widget._shuttleRingPointer.getBoundingClientRect();

            var x = clientX - centerX - rect.left;
            var y = clientY - centerY - rect.top;

            var angle = Math.atan2(y, x) * 180 / Math.PI + 90;
            if (angle > 180) {
                angle -= 360;
            }
            var shuttleRingAngle = viewModel.shuttleRingAngle;
            if (shuttleRingDragging || (clientX < pointerRect.right && clientX > pointerRect.left && clientY > pointerRect.top && clientY < pointerRect.bottom)) {
                widgetForDrag = widget;
                viewModel.shuttleRingDragging = true;
                viewModel.shuttleRingAngle = angle;
            } else if (angle < shuttleRingAngle) {
                viewModel.slower();
            } else if (angle > shuttleRingAngle) {
                viewModel.faster();
            }
            e.preventDefault();
        } else {
            if (widget === widgetForDrag) {
                widgetForDrag = undefined;
            }
            viewModel.shuttleRingDragging = false;
        }
    }

    //This is a private class for treating an SVG element like a button.
    //If we ever need a general purpose SVG button, we can make this generic.
    function SvgButton(svgElement, viewModel) {
        this._viewModel = viewModel;
        this.svgElement = svgElement;
        this._enabled = undefined;
        this._toggled = undefined;

        var that = this;
        this._clickFunction = function() {
            var command = that._viewModel.command;
            if (command.canExecute) {
                command();
            }
        };

        svgElement.addEventListener('click', this._clickFunction, true);

        //TODO: Since the animation widget uses SVG and has no HTML backing,
        //we need to wire everything up manually.  Knockout can supposedly
        //bind to SVG, so we we figure that out we can modify our SVG
        //to include the binding information directly.

        this._subscriptions = [//
        subscribeAndEvaluate(viewModel, 'toggled', this.setToggled, this),//
        subscribeAndEvaluate(viewModel, 'tooltip', this.setTooltip, this),//
        subscribeAndEvaluate(viewModel.command, 'canExecute', this.setEnabled, this)];
    }

    SvgButton.prototype.destroy = function() {
        this.svgElement.removeEventListener('click', this._clickFunction, true);
        var subscriptions = this._subscriptions;
        for ( var i = 0, len = subscriptions.length; i < len; i++) {
            subscriptions[i].dispose();
        }
        destroyObject(this);
    };

    SvgButton.prototype.isDestroyed = function() {
        return false;
    };

    SvgButton.prototype.setEnabled = function(enabled) {
        if (this._enabled !== enabled) {
            this._enabled = enabled;

            if (!enabled) {
                this.svgElement.setAttribute('class', 'cesium-animation-buttonDisabled');
                return;
            }

            if (this._toggled) {
                this.svgElement.setAttribute('class', 'cesium-animation-rectButton cesium-animation-buttonToggled');
                return;
            }

            this.svgElement.setAttribute('class', 'cesium-animation-rectButton');
        }
    };

    SvgButton.prototype.setToggled = function(toggled) {
        if (this._toggled !== toggled) {
            this._toggled = toggled;

            if (this._enabled) {
                if (toggled) {
                    this.svgElement.setAttribute('class', 'cesium-animation-rectButton cesium-animation-buttonToggled');
                } else {
                    this.svgElement.setAttribute('class', 'cesium-animation-rectButton');
                }
            }
        }
    };

    SvgButton.prototype.setTooltip = function(tooltip) {
        this.svgElement.getElementsByTagName('title')[0].textContent = tooltip;
    };

    /**
     * <span style="display: block; text-align: center;">
     * <img src="images/AnimationWidget.png" width="211" height="142" alt="" />
     * <br />Animation widget
     * </span>
     * <br /><br />
     * The Animation widget provides buttons for play, pause, and reverse, along with the
     * current time and date, surrounded by a "shuttle ring" for controlling the speed of animation.
     * <br /><br />
     * The "shuttle ring" concept is borrowed from video editing, where typically a
     * "jog wheel" can be rotated to move past individual animation frames very slowly, and
     * a surrounding shuttle ring can be twisted to control direction and speed of fast playback.
     * Cesium typically treats time as continuous (not broken into pre-defined animation frames),
     * so this widget offers no jog wheel.  Instead, the shuttle ring is capable of both fast and
     * very slow playback.  Click and drag the shuttle ring pointer itself (shown above in green),
     * or click in the rest of the ring area to nudge the pointer to the next preset speed in that direction.
     * <br /><br />
     * The Animation widget also provides a "realtime" button (in the upper-left) that keeps
     * animation time in sync with the end user's system clock, typically displaying
     * "today" or "right now."  This mode is not available in {@link ClockRange.CLAMPED} or
     * {@link ClockRange.LOOP_STOP} mode if the current time is outside of {@link Clock}'s startTime and endTime.
     *
     * @alias Animation
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {AnimationViewModel} viewModel The view model used by this widget.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     *
     *
     * @example
     * // In HTML head, include a link to Animation.css stylesheet,
     * // and in the body, include: <div id="animationContainer"></div>
     *
     * var clock = new Cesium.Clock();
     * var clockViewModel = new Cesium.ClockViewModel(clock);
     * var viewModel = new Cesium.AnimationViewModel(clockViewModel);
     * var widget = new Cesium.Animation('animationContainer', viewModel);
     *
     * function tick() {
     *     clock.tick();
     *     Cesium.requestAnimationFrame(tick);
     * }
     * Cesium.requestAnimationFrame(tick);
     * 
     * @see AnimationViewModel
     * @see Clock
     */
    function Animation(container, viewModel) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        if (!defined(viewModel)) {
            throw new DeveloperError('viewModel is required.');
        }
        
        container = getElement(container);
        this._viewModel = viewModel;
        this._container = container;
        this._centerX = 0;
        this._centerY = 0;
        this._defsElement = undefined;
        this._svgNode = undefined;
        this._topG = undefined;
        this._lastHeight = undefined;
        this._lastWidth = undefined;

        // Firefox requires SVG references to be included directly, not imported from external CSS.
        // Also, CSS minifiers get confused by this being in an external CSS file.
        var cssStyle = document.createElement('style');
        cssStyle.textContent = '.cesium-animation-rectButton .cesium-animation-buttonGlow { filter: url(#animation_blurred); }\
.cesium-animation-rectButton .cesium-animation-buttonMain { fill: url(#animation_buttonNormal); }\
.cesium-animation-buttonToggled .cesium-animation-buttonMain { fill: url(#animation_buttonToggled); }\
.cesium-animation-rectButton:hover .cesium-animation-buttonMain { fill: url(#animation_buttonHovered); }\
.cesium-animation-buttonDisabled .cesium-animation-buttonMain { fill: url(#animation_buttonDisabled); }\
.cesium-animation-shuttleRingG .cesium-animation-shuttleRingSwoosh { fill: url(#animation_shuttleRingSwooshGradient); }\
.cesium-animation-shuttleRingG:hover .cesium-animation-shuttleRingSwoosh { fill: url(#animation_shuttleRingSwooshHovered); }\
.cesium-animation-shuttleRingPointer { fill: url(#animation_shuttleRingPointerGradient); }\
.cesium-animation-shuttleRingPausePointer { fill: url(#animation_shuttleRingPointerPaused); }\
.cesium-animation-knobOuter { fill: url(#animation_knobOuter); }\
.cesium-animation-knobInner { fill: url(#animation_knobInner); }';

        document.head.insertBefore(cssStyle, document.head.childNodes[0]);

        var themeEle = document.createElement('div');
        themeEle.className = 'cesium-animation-theme';
        themeEle.innerHTML = '<div class="cesium-animation-themeNormal"></div>\
<div class="cesium-animation-themeHover"></div>\
<div class="cesium-animation-themeSelect"></div>\
<div class="cesium-animation-themeDisabled"></div>\
<div class="cesium-animation-themeKnob"></div>\
<div class="cesium-animation-themePointer"></div>\
<div class="cesium-animation-themeSwoosh"></div>\
<div class="cesium-animation-themeSwooshHover"></div>';

        this._theme = themeEle;
        this._themeNormal = themeEle.childNodes[0];
        this._themeHover = themeEle.childNodes[1];
        this._themeSelect = themeEle.childNodes[2];
        this._themeDisabled = themeEle.childNodes[3];
        this._themeKnob = themeEle.childNodes[4];
        this._themePointer = themeEle.childNodes[5];
        this._themeSwoosh = themeEle.childNodes[6];
        this._themeSwooshHover = themeEle.childNodes[7];

        var svg = document.createElementNS(svgNS, 'svg:svg');
        this._svgNode = svg;

        // Define the XLink namespace that SVG uses
        svg.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', xlinkNS);

        var topG = document.createElementNS(svgNS, 'g');
        this._topG = topG;

        this._realtimeSVG = new SvgButton(wingButton(3, 4, '#animation_pathClock'), viewModel.playRealtimeViewModel);
        this._playReverseSVG = new SvgButton(rectButton(44, 99, '#animation_pathPlayReverse'), viewModel.playReverseViewModel);
        this._playForwardSVG = new SvgButton(rectButton(124, 99, '#animation_pathPlay'), viewModel.playForwardViewModel);
        this._pauseSVG = new SvgButton(rectButton(84, 99, '#animation_pathPause'), viewModel.pauseViewModel);

        var buttonsG = document.createElementNS(svgNS, 'g');
        buttonsG.appendChild(this._realtimeSVG.svgElement);
        buttonsG.appendChild(this._playReverseSVG.svgElement);
        buttonsG.appendChild(this._playForwardSVG.svgElement);
        buttonsG.appendChild(this._pauseSVG.svgElement);

        var shuttleRingBackPanel = svgFromObject({
            tagName : 'circle',
            'class' : 'cesium-animation-shuttleRingBack',
            cx : 100,
            cy : 100,
            r : 99
        });
        this._shuttleRingBackPanel = shuttleRingBackPanel;

        var shuttleRingSwooshG = svgFromObject({
            tagName : 'g',
            'class' : 'cesium-animation-shuttleRingSwoosh',
            children : [{
                tagName : 'use',
                transform : 'translate(100,97) scale(-1,1)',
                'xlink:href' : '#animation_pathSwooshFX'
            }, {
                tagName : 'use',
                transform : 'translate(100,97)',
                'xlink:href' : '#animation_pathSwooshFX'
            }, {
                tagName : 'line',
                x1 : 100,
                y1 : 8,
                x2 : 100,
                y2 : 22
            }]
        });
        this._shuttleRingSwooshG = shuttleRingSwooshG;

        this._shuttleRingPointer = svgFromObject({
            tagName : 'use',
            'class' : 'cesium-animation-shuttleRingPointer',
            'xlink:href' : '#animation_pathPointer'
        });

        var knobG = svgFromObject({
            tagName : 'g',
            transform : 'translate(100,100)'
        });

        this._knobOuter = svgFromObject({
            tagName : 'circle',
            'class' : 'cesium-animation-knobOuter',
            cx : 0,
            cy : 0,
            r : 71
        });

        var knobInnerAndShieldSize = 61;

        var knobInner = svgFromObject({
            tagName : 'circle',
            'class' : 'cesium-animation-knobInner',
            cx : 0,
            cy : 0,
            r : knobInnerAndShieldSize
        });

        this._knobDate = svgText(0, -24, '');
        this._knobTime = svgText(0, -7, '');
        this._knobStatus = svgText(0, -41, '');

        // widget shield catches clicks on the knob itself (even while DOM elements underneath are changing).
        var knobShield = svgFromObject({
            tagName : 'circle',
            'class' : 'cesium-animation-blank',
            cx : 0,
            cy : 0,
            r : knobInnerAndShieldSize
        });

        var shuttleRingBackG = document.createElementNS(svgNS, 'g');
        shuttleRingBackG.setAttribute('class', 'cesium-animation-shuttleRingG');

        container.appendChild(themeEle);
        topG.appendChild(shuttleRingBackG);
        topG.appendChild(knobG);
        topG.appendChild(buttonsG);

        shuttleRingBackG.appendChild(shuttleRingBackPanel);
        shuttleRingBackG.appendChild(shuttleRingSwooshG);
        shuttleRingBackG.appendChild(this._shuttleRingPointer);

        knobG.appendChild(this._knobOuter);
        knobG.appendChild(knobInner);
        knobG.appendChild(this._knobDate);
        knobG.appendChild(this._knobTime);
        knobG.appendChild(this._knobStatus);
        knobG.appendChild(knobShield);

        svg.appendChild(topG);
        container.appendChild(svg);

        var that = this;
        function mouseCallback(e) {
            setShuttleRingFromMouseOrTouch(that, e);
        }
        this._mouseCallback = mouseCallback;

        shuttleRingBackPanel.addEventListener('mousedown', mouseCallback, true);
        shuttleRingBackPanel.addEventListener('touchstart', mouseCallback, true);
        shuttleRingSwooshG.addEventListener('mousedown', mouseCallback, true);
        shuttleRingSwooshG.addEventListener('touchstart', mouseCallback, true);
        document.addEventListener('mousemove', mouseCallback, true);
        document.addEventListener('touchmove', mouseCallback, true);
        document.addEventListener('mouseup', mouseCallback, true);
        document.addEventListener('touchend', mouseCallback, true);
        document.addEventListener('touchcancel', mouseCallback, true);
        this._shuttleRingPointer.addEventListener('mousedown', mouseCallback, true);
        this._shuttleRingPointer.addEventListener('touchstart', mouseCallback, true);
        this._knobOuter.addEventListener('mousedown', mouseCallback, true);
        this._knobOuter.addEventListener('touchstart', mouseCallback, true);

        //TODO: Since the animation widget uses SVG and has no HTML backing,
        //we need to wire everything up manually.  Knockout can supposedly
        //bind to SVG, so we we figure that out we can modify our SVG
        //to include the binding information directly.

        var timeNode = this._knobTime.childNodes[0];
        var dateNode = this._knobDate.childNodes[0];
        var statusNode = this._knobStatus.childNodes[0];
        var isPaused;
        this._subscriptions = [//
        subscribeAndEvaluate(viewModel.pauseViewModel, 'toggled', function(value) {
            if (isPaused !== value) {
                isPaused = value;
                if (isPaused) {
                    that._shuttleRingPointer.setAttribute('class', 'cesium-animation-shuttleRingPausePointer');
                } else {
                    that._shuttleRingPointer.setAttribute('class', 'cesium-animation-shuttleRingPointer');
                }
            }
        }),

        subscribeAndEvaluate(viewModel, 'shuttleRingAngle', function(value) {
            setShuttleRingPointer(that._shuttleRingPointer, that._knobOuter, value);
        }),

        subscribeAndEvaluate(viewModel, 'dateLabel', function(value) {
            if (dateNode.textContent !== value) {
                dateNode.textContent = value;
            }
        }),

        subscribeAndEvaluate(viewModel, 'timeLabel', function(value) {
            if (timeNode.textContent !== value) {
                timeNode.textContent = value;
            }
        }),

        subscribeAndEvaluate(viewModel, 'multiplierLabel', function(value) {
            if (statusNode.textContent !== value) {
                statusNode.textContent = value;
            }
        })];

        this.applyThemeChanges();
        this.resize();
    }

    defineProperties(Animation.prototype, {
        /**
         * Gets the parent container.
         *
         * @memberof Animation.prototype
         * @type {Element}
         * @readonly
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         *
         * @memberof Animation.prototype
         * @type {AnimationViewModel}
         * @readonly
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    Animation.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the animation widget.  Should be called if permanently
     * removing the widget from layout.
     */
    Animation.prototype.destroy = function() {
        var mouseCallback = this._mouseCallback;
        this._shuttleRingBackPanel.removeEventListener('mousedown', mouseCallback, true);
        this._shuttleRingBackPanel.removeEventListener('touchstart', mouseCallback, true);
        this._shuttleRingSwooshG.removeEventListener('mousedown', mouseCallback, true);
        this._shuttleRingSwooshG.removeEventListener('touchstart', mouseCallback, true);
        document.removeEventListener('mousemove', mouseCallback, true);
        document.removeEventListener('touchmove', mouseCallback, true);
        document.removeEventListener('mouseup', mouseCallback, true);
        document.removeEventListener('touchend', mouseCallback, true);
        document.removeEventListener('touchcancel', mouseCallback, true);
        this._shuttleRingPointer.removeEventListener('mousedown', mouseCallback, true);
        this._shuttleRingPointer.removeEventListener('touchstart', mouseCallback, true);
        this._knobOuter.removeEventListener('mousedown', mouseCallback, true);
        this._knobOuter.removeEventListener('touchstart', mouseCallback, true);

        this._container.removeChild(this._svgNode);
        this._container.removeChild(this._theme);
        this._realtimeSVG.destroy();
        this._playReverseSVG.destroy();
        this._playForwardSVG.destroy();
        this._pauseSVG.destroy();

        var subscriptions = this._subscriptions;
        for ( var i = 0, len = subscriptions.length; i < len; i++) {
            subscriptions[i].dispose();
        }

        return destroyObject(this);
    };

    /**
     * Resizes the widget to match the container size.
     * This function should be called whenever the container size is changed.
     */
    Animation.prototype.resize = function() {
        var parentWidth = this._container.clientWidth;
        var parentHeight = this._container.clientHeight;
        if (parentWidth === this._lastWidth && parentHeight === this._lastHeight) {
            return;
        }

        var svg = this._svgNode;

        //The width and height as the SVG was originally drawn.
        var baseWidth = 200;
        var baseHeight = 132;

        var width = parentWidth;
        var height = parentHeight;

        if (parentWidth === 0 && parentHeight === 0) {
            width = baseWidth;
            height = baseHeight;
        } else if (parentWidth === 0) {
            height = parentHeight;
            width = baseWidth * (parentHeight / baseHeight);
        } else if (parentHeight === 0) {
            width = parentWidth;
            height = baseHeight * (parentWidth / baseWidth);
        }

        var scaleX = width / baseWidth;
        var scaleY = height / baseHeight;

        svg.style.cssText = 'width: ' + width + 'px; height: ' + height + 'px; position: absolute; bottom: 0; left: 0; overflow: hidden;';
        svg.setAttribute('width', width);
        svg.setAttribute('height', height);
        svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);

        this._topG.setAttribute('transform', 'scale(' + scaleX + ',' + scaleY + ')');

        this._centerX = Math.max(1, 100.0 * scaleX);
        this._centerY = Math.max(1, 100.0 * scaleY);

        this._lastHeight = parentWidth;
        this._lastWidth = parentHeight;
    };

    /**
     * Updates the widget to reflect any modified CSS rules for theming.
     *
     * @example
     * //Switch to the cesium-lighter theme.
     * document.body.className = 'cesium-lighter';
     * animation.applyThemeChanges();
     */
    Animation.prototype.applyThemeChanges = function() {
        var buttonNormalBackColor = getElementColor(this._themeNormal);
        var buttonHoverBackColor = getElementColor(this._themeHover);
        var buttonToggledBackColor = getElementColor(this._themeSelect);
        var buttonDisabledBackColor = getElementColor(this._themeDisabled);
        var knobBackColor = getElementColor(this._themeKnob);
        var pointerColor = getElementColor(this._themePointer);
        var swooshColor = getElementColor(this._themeSwoosh);
        var swooshHoverColor = getElementColor(this._themeSwooshHover);

        var defsElement = svgFromObject({
            tagName : 'defs',
            children : [{
                id : 'animation_buttonNormal',
                tagName : 'linearGradient',
                x1 : '50%',
                y1 : '0%',
                x2 : '50%',
                y2 : '100%',
                children : [
                //add a 'stop-opacity' field to make translucent.
                {
                    tagName : 'stop',
                    offset : '0%',
                    'stop-color' : makeColorString(buttonNormalBackColor, gradientEnabledColor0)
                }, {
                    tagName : 'stop',
                    offset : '12%',
                    'stop-color' : makeColorString(buttonNormalBackColor, gradientEnabledColor1)
                }, {
                    tagName : 'stop',
                    offset : '46%',
                    'stop-color' : makeColorString(buttonNormalBackColor, gradientEnabledColor2)
                }, {
                    tagName : 'stop',
                    offset : '81%',
                    'stop-color' : makeColorString(buttonNormalBackColor, gradientEnabledColor3)
                }]
            }, {
                id : 'animation_buttonHovered',
                tagName : 'linearGradient',
                x1 : '50%',
                y1 : '0%',
                x2 : '50%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-color' : makeColorString(buttonHoverBackColor, gradientEnabledColor0)
                }, {
                    tagName : 'stop',
                    offset : '12%',
                    'stop-color' : makeColorString(buttonHoverBackColor, gradientEnabledColor1)
                }, {
                    tagName : 'stop',
                    offset : '46%',
                    'stop-color' : makeColorString(buttonHoverBackColor, gradientEnabledColor2)
                }, {
                    tagName : 'stop',
                    offset : '81%',
                    'stop-color' : makeColorString(buttonHoverBackColor, gradientEnabledColor3)
                }]
            }, {
                id : 'animation_buttonToggled',
                tagName : 'linearGradient',
                x1 : '50%',
                y1 : '0%',
                x2 : '50%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-color' : makeColorString(buttonToggledBackColor, gradientEnabledColor0)
                }, {
                    tagName : 'stop',
                    offset : '12%',
                    'stop-color' : makeColorString(buttonToggledBackColor, gradientEnabledColor1)
                }, {
                    tagName : 'stop',
                    offset : '46%',
                    'stop-color' : makeColorString(buttonToggledBackColor, gradientEnabledColor2)
                }, {
                    tagName : 'stop',
                    offset : '81%',
                    'stop-color' : makeColorString(buttonToggledBackColor, gradientEnabledColor3)
                }]
            }, {
                id : 'animation_buttonDisabled',
                tagName : 'linearGradient',
                x1 : '50%',
                y1 : '0%',
                x2 : '50%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-color' : makeColorString(buttonDisabledBackColor, gradientDisabledColor0)
                }, {
                    tagName : 'stop',
                    offset : '75%',
                    'stop-color' : makeColorString(buttonDisabledBackColor, gradientDisabledColor1)
                }]
            }, {
                id : 'animation_blurred',
                tagName : 'filter',
                width : '200%',
                height : '200%',
                x : '-50%',
                y : '-50%',
                children : [{
                    tagName : 'feGaussianBlur',
                    stdDeviation : 4,
                    'in' : 'SourceGraphic'
                }]
            }, {
                id : 'animation_shuttleRingSwooshGradient',
                tagName : 'linearGradient',
                x1 : '50%',
                y1 : '0%',
                x2 : '50%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-opacity' : 0.2,
                    'stop-color' : swooshColor.toCssColorString()
                }, {
                    tagName : 'stop',
                    offset : '85%',
                    'stop-opacity' : 0.85,
                    'stop-color' : swooshColor.toCssColorString()
                }, {
                    tagName : 'stop',
                    offset : '95%',
                    'stop-opacity' : 0.05,
                    'stop-color' : swooshColor.toCssColorString()
                }]
            }, {
                id : 'animation_shuttleRingSwooshHovered',
                tagName : 'linearGradient',
                x1 : '50%',
                y1 : '0%',
                x2 : '50%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-opacity' : 0.2,
                    'stop-color' : swooshHoverColor.toCssColorString()
                }, {
                    tagName : 'stop',
                    offset : '85%',
                    'stop-opacity' : 0.85,
                    'stop-color' : swooshHoverColor.toCssColorString()
                }, {
                    tagName : 'stop',
                    offset : '95%',
                    'stop-opacity' : 0.05,
                    'stop-color' : swooshHoverColor.toCssColorString()
                }]
            }, {
                id : 'animation_shuttleRingPointerGradient',
                tagName : 'linearGradient',
                x1 : '0%',
                y1 : '50%',
                x2 : '100%',
                y2 : '50%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-color' : pointerColor.toCssColorString()
                }, {
                    tagName : 'stop',
                    offset : '40%',
                    'stop-color' : pointerColor.toCssColorString()
                }, {
                    tagName : 'stop',
                    offset : '60%',
                    'stop-color' : makeColorString(pointerColor, gradientPointerColor)
                }, {
                    tagName : 'stop',
                    offset : '100%',
                    'stop-color' : makeColorString(pointerColor, gradientPointerColor)
                }]
            }, {
                id : 'animation_shuttleRingPointerPaused',
                tagName : 'linearGradient',
                x1 : '0%',
                y1 : '50%',
                x2 : '100%',
                y2 : '50%',
                children : [{
                    tagName : 'stop',
                    offset : '0%',
                    'stop-color' : '#CCC'
                }, {
                    tagName : 'stop',
                    offset : '40%',
                    'stop-color' : '#CCC'
                }, {
                    tagName : 'stop',
                    offset : '60%',
                    'stop-color' : '#555'
                }, {
                    tagName : 'stop',
                    offset : '100%',
                    'stop-color' : '#555'
                }]
            }, {
                id : 'animation_knobOuter',
                tagName : 'linearGradient',
                x1 : '20%',
                y1 : '0%',
                x2 : '90%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '5%',
                    'stop-color' : makeColorString(knobBackColor, gradientEnabledColor0)
                }, {
                    tagName : 'stop',
                    offset : '60%',
                    'stop-color' : makeColorString(knobBackColor, gradientKnobColor)
                }, {
                    tagName : 'stop',
                    offset : '85%',
                    'stop-color' : makeColorString(knobBackColor, gradientEnabledColor1)
                }]
            }, {
                id : 'animation_knobInner',
                tagName : 'linearGradient',
                x1 : '20%',
                y1 : '0%',
                x2 : '90%',
                y2 : '100%',
                children : [{
                    tagName : 'stop',
                    offset : '5%',
                    'stop-color' : makeColorString(knobBackColor, gradientKnobColor)
                }, {
                    tagName : 'stop',
                    offset : '60%',
                    'stop-color' : makeColorString(knobBackColor, gradientEnabledColor0)
                }, {
                    tagName : 'stop',
                    offset : '85%',
                    'stop-color' : makeColorString(knobBackColor, gradientEnabledColor3)
                }]
            }, {
                id : 'animation_pathReset',
                tagName : 'path',
                transform : 'translate(16,16) scale(0.85) translate(-16,-16)',
                d : 'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z'
            }, {
                id : 'animation_pathPause',
                tagName : 'path',
                transform : 'translate(16,16) scale(0.85) translate(-16,-16)',
                d : 'M13,5.5,7.5,5.5,7.5,25.5,13,25.5zM24.5,5.5,19,5.5,19,25.5,24.5,25.5z'
            }, {
                id : 'animation_pathPlay',
                tagName : 'path',
                transform : 'translate(16,16) scale(0.85) translate(-16,-16)',
                d : 'M6.684,25.682L24.316,15.5L6.684,5.318V25.682z'
            }, {
                id : 'animation_pathPlayReverse',
                tagName : 'path',
                transform : 'translate(16,16) scale(-0.85,0.85) translate(-16,-16)',
                d : 'M6.684,25.682L24.316,15.5L6.684,5.318V25.682z'
            }, {
                id : 'animation_pathLoop',
                tagName : 'path',
                transform : 'translate(16,16) scale(0.85) translate(-16,-16)',
                d : 'M24.249,15.499c-0.009,4.832-3.918,8.741-8.75,8.75c-2.515,0-4.768-1.064-6.365-2.763l2.068-1.442l-7.901-3.703l0.744,8.694l2.193-1.529c2.244,2.594,5.562,4.242,9.26,4.242c6.767,0,12.249-5.482,12.249-12.249H24.249zM15.499,6.75c2.516,0,4.769,1.065,6.367,2.764l-2.068,1.443l7.901,3.701l-0.746-8.693l-2.192,1.529c-2.245-2.594-5.562-4.245-9.262-4.245C8.734,3.25,3.25,8.734,3.249,15.499H6.75C6.758,10.668,10.668,6.758,15.499,6.75z'
            }, {
                id : 'animation_pathClock',
                tagName : 'path',
                transform : 'translate(16,16) scale(0.85) translate(-16,-15.5)',
                d : 'M15.5,2.374C8.251,2.375,2.376,8.251,2.374,15.5C2.376,22.748,8.251,28.623,15.5,28.627c7.249-0.004,13.124-5.879,13.125-13.127C28.624,8.251,22.749,2.375,15.5,2.374zM15.5,25.623C9.909,25.615,5.385,21.09,5.375,15.5C5.385,9.909,9.909,5.384,15.5,5.374c5.59,0.01,10.115,4.535,10.124,10.125C25.615,21.09,21.091,25.615,15.5,25.623zM8.625,15.5c-0.001-0.552-0.448-0.999-1.001-1c-0.553,0-1,0.448-1,1c0,0.553,0.449,1,1,1C8.176,16.5,8.624,16.053,8.625,15.5zM8.179,18.572c-0.478,0.277-0.642,0.889-0.365,1.367c0.275,0.479,0.889,0.641,1.365,0.365c0.479-0.275,0.643-0.887,0.367-1.367C9.27,18.461,8.658,18.297,8.179,18.572zM9.18,10.696c-0.479-0.276-1.09-0.112-1.366,0.366s-0.111,1.09,0.365,1.366c0.479,0.276,1.09,0.113,1.367-0.366C9.821,11.584,9.657,10.973,9.18,10.696zM22.822,12.428c0.478-0.275,0.643-0.888,0.366-1.366c-0.275-0.478-0.89-0.642-1.366-0.366c-0.479,0.278-0.642,0.89-0.366,1.367C21.732,12.54,22.344,12.705,22.822,12.428zM12.062,21.455c-0.478-0.275-1.089-0.111-1.366,0.367c-0.275,0.479-0.111,1.09,0.366,1.365c0.478,0.277,1.091,0.111,1.365-0.365C12.704,22.344,12.54,21.732,12.062,21.455zM12.062,9.545c0.479-0.276,0.642-0.888,0.366-1.366c-0.276-0.478-0.888-0.642-1.366-0.366s-0.642,0.888-0.366,1.366C10.973,9.658,11.584,9.822,12.062,9.545zM22.823,18.572c-0.48-0.275-1.092-0.111-1.367,0.365c-0.275,0.479-0.112,1.092,0.367,1.367c0.477,0.275,1.089,0.113,1.365-0.365C23.464,19.461,23.3,18.848,22.823,18.572zM19.938,7.813c-0.477-0.276-1.091-0.111-1.365,0.366c-0.275,0.48-0.111,1.091,0.366,1.367s1.089,0.112,1.366-0.366C20.581,8.702,20.418,8.089,19.938,7.813zM23.378,14.5c-0.554,0.002-1.001,0.45-1.001,1c0.001,0.552,0.448,1,1.001,1c0.551,0,1-0.447,1-1C24.378,14.949,23.929,14.5,23.378,14.5zM15.501,6.624c-0.552,0-1,0.448-1,1l-0.466,7.343l-3.004,1.96c-0.478,0.277-0.642,0.889-0.365,1.365c0.275,0.479,0.889,0.643,1.365,0.367l3.305-1.676C15.39,16.99,15.444,17,15.501,17c0.828,0,1.5-0.671,1.5-1.5l-0.5-7.876C16.501,7.072,16.053,6.624,15.501,6.624zM15.501,22.377c-0.552,0-1,0.447-1,1s0.448,1,1,1s1-0.447,1-1S16.053,22.377,15.501,22.377zM18.939,21.455c-0.479,0.277-0.643,0.889-0.366,1.367c0.275,0.477,0.888,0.643,1.366,0.365c0.478-0.275,0.642-0.889,0.366-1.365C20.028,21.344,19.417,21.18,18.939,21.455z'
            }, {
                id : 'animation_pathWingButton',
                tagName : 'path',
                d : 'm 4.5,0.5 c -2.216,0 -4,1.784 -4,4 l 0,24 c 0,2.216 1.784,4 4,4 l 13.71875,0 C 22.478584,27.272785 27.273681,22.511272 32.5,18.25 l 0,-13.75 c 0,-2.216 -1.784,-4 -4,-4 l -24,0 z'
            }, {
                id : 'animation_pathPointer',
                tagName : 'path',
                d : 'M-15,-65,-15,-55,15,-55,15,-65,0,-95z'
            }, {
                id : 'animation_pathSwooshFX',
                tagName : 'path',
                d : 'm 85,0 c 0,16.617 -4.813944,35.356 -13.131081,48.4508 h 6.099803 c 8.317138,-13.0948 13.13322,-28.5955 13.13322,-45.2124 0,-46.94483 -38.402714,-85.00262 -85.7743869,-85.00262 -1.0218522,0 -2.0373001,0.0241 -3.0506131,0.0589 45.958443,1.59437 82.723058,35.77285 82.723058,81.70532 z'
            }]
        });

        if (!defined(this._defsElement)) {
            this._svgNode.appendChild(defsElement);
        } else {
            this._svgNode.replaceChild(defsElement, this._defsElement);
        }
        this._defsElement = defsElement;
    };

    return Animation;
});

/*global define*/
define('Widgets/createCommand',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../Core/Event',
        '../ThirdParty/knockout'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        knockout) {
    'use strict';

    /**
     * Create a Command from a given function, for use with ViewModels.
     *
     * A Command is a function with an extra <code>canExecute</code> observable property to determine
     * whether the command can be executed.  When executed, a Command function will check the
     * value of <code>canExecute</code> and throw if false.  It also provides events for when
     * a command has been or is about to be executed.
     *
     * @exports createCommand
     *
     * @param {Function} func The function to execute.
     * @param {Boolean} [canExecute=true] A boolean indicating whether the function can currently be executed.
     */
    function createCommand(func, canExecute) {
                if (!defined(func)) {
            throw new DeveloperError('func is required.');
        }
        
        canExecute = defaultValue(canExecute, true);

        var beforeExecute = new Event();
        var afterExecute = new Event();

        function command() {
                        if (!command.canExecute) {
                throw new DeveloperError('Cannot execute command, canExecute is false.');
            }
            
            var commandInfo = {
                args : arguments,
                cancel : false
            };

            var result;
            beforeExecute.raiseEvent(commandInfo);
            if (!commandInfo.cancel) {
                result = func.apply(null, arguments);
                afterExecute.raiseEvent(result);
            }
            return result;
        }

        command.canExecute = canExecute;
        knockout.track(command, ['canExecute']);

        defineProperties(command, {
            beforeExecute : {
                value : beforeExecute
            },
            afterExecute : {
                value : afterExecute
            }
        });

        return command;
    }

    return createCommand;
});

/*global define*/
define('Widgets/ToggleButtonViewModel',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/DeveloperError',
        '../ThirdParty/knockout'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        knockout) {
    'use strict';

    /**
     * A view model which exposes the properties of a toggle button.
     * @alias ToggleButtonViewModel
     * @constructor
     *
     * @param {Command} command The command which will be executed when the button is toggled.
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.toggled=false] A boolean indicating whether the button should be initially toggled.
     * @param {String} [options.tooltip=''] A string containing the button's tooltip.
     */
    function ToggleButtonViewModel(command, options) {
                if (!defined(command)) {
            throw new DeveloperError('command is required.');
        }
        
        this._command = command;

        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        /**
         * Gets or sets whether the button is currently toggled.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.toggled = defaultValue(options.toggled, false);

        /**
         * Gets or sets the button's tooltip.  This property is observable.
         * @type {String}
         * @default ''
         */
        this.tooltip = defaultValue(options.tooltip, '');

        knockout.track(this, ['toggled', 'tooltip']);
    }

    defineProperties(ToggleButtonViewModel.prototype, {
        /**
         * Gets the command which will be executed when the button is toggled.
         * @memberof ToggleButtonViewModel.prototype
         * @type {Command}
         */
        command : {
            get : function() {
                return this._command;
            }
        }
    });

    return ToggleButtonViewModel;
});

/*global define*/
define('Widgets/Animation/AnimationViewModel',[
        '../../Core/binarySearch',
        '../../Core/ClockRange',
        '../../Core/ClockStep',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../Core/JulianDate',
        '../../ThirdParty/knockout',
        '../../ThirdParty/sprintf',
        '../createCommand',
        '../ToggleButtonViewModel'
    ], function(
        binarySearch,
        ClockRange,
        ClockStep,
        defined,
        defineProperties,
        DeveloperError,
        JulianDate,
        knockout,
        sprintf,
        createCommand,
        ToggleButtonViewModel) {
    'use strict';

    var monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    var realtimeShuttleRingAngle = 15;
    var maxShuttleRingAngle = 105;

    function numberComparator(left, right) {
        return left - right;
    }

    function getTypicalMultiplierIndex(multiplier, shuttleRingTicks) {
        var index = binarySearch(shuttleRingTicks, multiplier, numberComparator);
        return index < 0 ? ~index : index;
    }

    function angleToMultiplier(angle, shuttleRingTicks) {
        //Use a linear scale for -1 to 1 between -15 < angle < 15 degrees
        if (Math.abs(angle) <= realtimeShuttleRingAngle) {
            return angle / realtimeShuttleRingAngle;
        }

        var minp = realtimeShuttleRingAngle;
        var maxp = maxShuttleRingAngle;
        var maxv;
        var minv = 0;
        var scale;
        if (angle > 0) {
            maxv = Math.log(shuttleRingTicks[shuttleRingTicks.length - 1]);
            scale = (maxv - minv) / (maxp - minp);
            return Math.exp(minv + scale * (angle - minp));
        }

        maxv = Math.log(-shuttleRingTicks[0]);
        scale = (maxv - minv) / (maxp - minp);
        return -Math.exp(minv + scale * (Math.abs(angle) - minp));
    }

    function multiplierToAngle(multiplier, shuttleRingTicks, clockViewModel) {
        if (clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK) {
            return realtimeShuttleRingAngle;
        }

        if (Math.abs(multiplier) <= 1) {
            return multiplier * realtimeShuttleRingAngle;
        }

        var fastedMultipler = shuttleRingTicks[shuttleRingTicks.length - 1];
        if(multiplier > fastedMultipler){
            multiplier = fastedMultipler;
        } else if(multiplier < -fastedMultipler){
            multiplier = -fastedMultipler;
        }

        var minp = realtimeShuttleRingAngle;
        var maxp = maxShuttleRingAngle;
        var maxv;
        var minv = 0;
        var scale;

        if (multiplier > 0) {
            maxv = Math.log(fastedMultipler);
            scale = (maxv - minv) / (maxp - minp);
            return (Math.log(multiplier) - minv) / scale + minp;
        }

        maxv = Math.log(-shuttleRingTicks[0]);
        scale = (maxv - minv) / (maxp - minp);
        return -((Math.log(Math.abs(multiplier)) - minv) / scale + minp);
    }

    /**
     * The view model for the {@link Animation} widget.
     * @alias AnimationViewModel
     * @constructor
     *
     * @param {ClockViewModel} clockViewModel The ClockViewModel instance to use.
     *
     * @see Animation
     */
    function AnimationViewModel(clockViewModel) {
                if (!defined(clockViewModel)) {
            throw new DeveloperError('clockViewModel is required.');
        }
        
        var that = this;
        this._clockViewModel = clockViewModel;
        this._allShuttleRingTicks = [];
        this._dateFormatter = AnimationViewModel.defaultDateFormatter;
        this._timeFormatter = AnimationViewModel.defaultTimeFormatter;

        /**
         * Gets or sets whether the shuttle ring is currently being dragged.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.shuttleRingDragging = false;

        /**
         * Gets or sets whether dragging the shuttle ring should cause the multiplier
         * to snap to the defined tick values rather than interpolating between them.
         * This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.snapToTicks = false;

        knockout.track(this, ['_allShuttleRingTicks', '_dateFormatter', '_timeFormatter', 'shuttleRingDragging', 'snapToTicks']);

        this._sortedFilteredPositiveTicks = [];

        this.setShuttleRingTicks(AnimationViewModel.defaultTicks);

        /**
         * Gets the string representation of the current time.  This property is observable.
         * @type {String}
         */
        this.timeLabel = undefined;
        knockout.defineProperty(this, 'timeLabel', function() {
            return that._timeFormatter(that._clockViewModel.currentTime, that);
        });

        /**
         * Gets the string representation of the current date.  This property is observable.
         * @type {String}
         */
        this.dateLabel = undefined;
        knockout.defineProperty(this, 'dateLabel', function() {
            return that._dateFormatter(that._clockViewModel.currentTime, that);
        });

        /**
         * Gets the string representation of the current multiplier.  This property is observable.
         * @type {String}
         */
        this.multiplierLabel = undefined;
        knockout.defineProperty(this, 'multiplierLabel', function() {
            var clockViewModel = that._clockViewModel;
            if (clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK) {
                return 'Today';
            }

            var multiplier = clockViewModel.multiplier;

            //If it's a whole number, just return it.
            if (multiplier % 1 === 0) {
                return multiplier.toFixed(0) + 'x';
            }

            //Convert to decimal string and remove any trailing zeroes
            return multiplier.toFixed(3).replace(/0{0,3}$/, "") + 'x';
        });

        /**
         * Gets or sets the current shuttle ring angle.  This property is observable.
         * @type {Number}
         */
        this.shuttleRingAngle = undefined;
        knockout.defineProperty(this, 'shuttleRingAngle', {
            get : function() {
                return multiplierToAngle(clockViewModel.multiplier, that._allShuttleRingTicks, clockViewModel);
            },
            set : function(angle) {
                angle = Math.max(Math.min(angle, maxShuttleRingAngle), -maxShuttleRingAngle);
                var ticks = that._allShuttleRingTicks;

                var clockViewModel = that._clockViewModel;
                clockViewModel.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER;

                //If we are at the max angle, simply return the max value in either direction.
                if (Math.abs(angle) === maxShuttleRingAngle) {
                    clockViewModel.multiplier = angle > 0 ? ticks[ticks.length - 1] : ticks[0];
                    return;
                }

                var multiplier = angleToMultiplier(angle, ticks);
                if (that.snapToTicks) {
                    multiplier = ticks[getTypicalMultiplierIndex(multiplier, ticks)];
                } else {
                    if (multiplier !== 0) {
                        var positiveMultiplier = Math.abs(multiplier);

                        if (positiveMultiplier > 100) {
                            var numDigits = positiveMultiplier.toFixed(0).length - 2;
                            var divisor = Math.pow(10, numDigits);
                            multiplier = (Math.round(multiplier / divisor) * divisor) | 0;
                        } else if (positiveMultiplier > realtimeShuttleRingAngle) {
                            multiplier = Math.round(multiplier);
                        } else if (positiveMultiplier > 1) {
                            multiplier = +multiplier.toFixed(1);
                        } else if (positiveMultiplier > 0) {
                            multiplier = +multiplier.toFixed(2);
                        }
                    }
                }
                clockViewModel.multiplier = multiplier;
            }
        });

        this._canAnimate = undefined;
        knockout.defineProperty(this, '_canAnimate', function() {
            var clockViewModel = that._clockViewModel;
            var clockRange = clockViewModel.clockRange;

            if (that.shuttleRingDragging || clockRange === ClockRange.UNBOUNDED) {
                return true;
            }

            var multiplier = clockViewModel.multiplier;
            var currentTime = clockViewModel.currentTime;
            var startTime = clockViewModel.startTime;

            var result = false;
            if (clockRange === ClockRange.LOOP_STOP) {
                result = JulianDate.greaterThan(currentTime, startTime) || (currentTime.equals(startTime) && multiplier > 0);
            } else {
                var stopTime = clockViewModel.stopTime;
                result = (JulianDate.greaterThan(currentTime, startTime) && JulianDate.lessThan(currentTime, stopTime)) || //
                (currentTime.equals(startTime) && multiplier > 0) || //
                (currentTime.equals(stopTime) && multiplier < 0);
            }

            if (!result) {
                clockViewModel.shouldAnimate = false;
            }
            return result;
        });

        this._isSystemTimeAvailable = undefined;
        knockout.defineProperty(this, '_isSystemTimeAvailable', function() {
            var clockViewModel = that._clockViewModel;
            var clockRange = clockViewModel.clockRange;
            if (clockRange === ClockRange.UNBOUNDED) {
                return true;
            }

            var systemTime = clockViewModel.systemTime;
            return JulianDate.greaterThanOrEquals(systemTime, clockViewModel.startTime) && JulianDate.lessThanOrEquals(systemTime, clockViewModel.stopTime);
        });

        this._isAnimating = undefined;
        knockout.defineProperty(this, '_isAnimating', function() {
            return that._clockViewModel.shouldAnimate && (that._canAnimate || that.shuttleRingDragging);
        });

        var pauseCommand = createCommand(function() {
            var clockViewModel = that._clockViewModel;
            if (clockViewModel.shouldAnimate) {
                clockViewModel.shouldAnimate = false;
            } else if (that._canAnimate) {
                clockViewModel.shouldAnimate = true;
            }
        });

        this._pauseViewModel = new ToggleButtonViewModel(pauseCommand, {
            toggled : knockout.computed(function() {
                return !that._isAnimating;
            }),
            tooltip : 'Pause'
        });

        var playReverseCommand = createCommand(function() {
            var clockViewModel = that._clockViewModel;
            var multiplier = clockViewModel.multiplier;
            if (multiplier > 0) {
                clockViewModel.multiplier = -multiplier;
            }
            clockViewModel.shouldAnimate = true;
        });

        this._playReverseViewModel = new ToggleButtonViewModel(playReverseCommand, {
            toggled : knockout.computed(function() {
                return that._isAnimating && (clockViewModel.multiplier < 0);
            }),
            tooltip : 'Play Reverse'
        });

        var playForwardCommand = createCommand(function() {
            var clockViewModel = that._clockViewModel;
            var multiplier = clockViewModel.multiplier;
            if (multiplier < 0) {
                clockViewModel.multiplier = -multiplier;
            }
            clockViewModel.shouldAnimate = true;
        });

        this._playForwardViewModel = new ToggleButtonViewModel(playForwardCommand, {
            toggled : knockout.computed(function() {
                return that._isAnimating && clockViewModel.multiplier > 0 && clockViewModel.clockStep !== ClockStep.SYSTEM_CLOCK;
            }),
            tooltip : 'Play Forward'
        });

        var playRealtimeCommand = createCommand(function() {
            that._clockViewModel.clockStep = ClockStep.SYSTEM_CLOCK;
        }, knockout.getObservable(this, '_isSystemTimeAvailable'));

        this._playRealtimeViewModel = new ToggleButtonViewModel(playRealtimeCommand, {
            toggled : knockout.computed(function() {
                return clockViewModel.clockStep === ClockStep.SYSTEM_CLOCK;
            }),
            tooltip : knockout.computed(function() {
                return that._isSystemTimeAvailable ? 'Today (real-time)' : 'Current time not in range';
            })
        });

        this._slower = createCommand(function() {
            var clockViewModel = that._clockViewModel;
            var shuttleRingTicks = that._allShuttleRingTicks;
            var multiplier = clockViewModel.multiplier;
            var index = getTypicalMultiplierIndex(multiplier, shuttleRingTicks) - 1;
            if (index >= 0) {
                clockViewModel.multiplier = shuttleRingTicks[index];
            }
        });

        this._faster = createCommand(function() {
            var clockViewModel = that._clockViewModel;
            var shuttleRingTicks = that._allShuttleRingTicks;
            var multiplier = clockViewModel.multiplier;
            var index = getTypicalMultiplierIndex(multiplier, shuttleRingTicks) + 1;
            if (index < shuttleRingTicks.length) {
                clockViewModel.multiplier = shuttleRingTicks[index];
            }
        });
    }

    /**
     * Gets or sets the default date formatter used by new instances.
     *
     * @member
     * @type {AnimationViewModel~DateFormatter}
     */
    AnimationViewModel.defaultDateFormatter = function(date, viewModel) {
        var gregorianDate = JulianDate.toGregorianDate(date);
        return monthNames[gregorianDate.month - 1] + ' ' + gregorianDate.day + ' ' + gregorianDate.year;
    };

    /**
     * Gets or sets the default array of known clock multipliers associated with new instances of the shuttle ring.
     * @type {Number[]}
     */
    AnimationViewModel.defaultTicks = [//
    0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0,//
    15.0, 30.0, 60.0, 120.0, 300.0, 600.0, 900.0, 1800.0, 3600.0, 7200.0, 14400.0,//
    21600.0, 43200.0, 86400.0, 172800.0, 345600.0, 604800.0];

    /**
     * Gets or sets the default time formatter used by new instances.
     *
     * @member
     * @type {AnimationViewModel~TimeFormatter}
     */
    AnimationViewModel.defaultTimeFormatter = function(date, viewModel) {
        var gregorianDate = JulianDate.toGregorianDate(date);
        var millisecond = Math.round(gregorianDate.millisecond);
        if (Math.abs(viewModel._clockViewModel.multiplier) < 1) {
            return sprintf("%02d:%02d:%02d.%03d", gregorianDate.hour, gregorianDate.minute, gregorianDate.second, millisecond);
        }
        return sprintf("%02d:%02d:%02d UTC", gregorianDate.hour, gregorianDate.minute, gregorianDate.second);
    };

    /**
     * Gets a copy of the array of positive known clock multipliers to associate with the shuttle ring.
     *
     * @returns {Number[]} The array of known clock multipliers associated with the shuttle ring.
     */
    AnimationViewModel.prototype.getShuttleRingTicks = function() {
        return this._sortedFilteredPositiveTicks.slice(0);
    };

    /**
     * Sets the array of positive known clock multipliers to associate with the shuttle ring.
     * These values will have negative equivalents created for them and sets both the minimum
     * and maximum range of values for the shuttle ring as well as the values that are snapped
     * to when a single click is made.  The values need not be in order, as they will be sorted
     * automatically, and duplicate values will be removed.
     *
     * @param {Number[]} positiveTicks The list of known positive clock multipliers to associate with the shuttle ring.
     */
    AnimationViewModel.prototype.setShuttleRingTicks = function(positiveTicks) {
                if (!defined(positiveTicks)) {
            throw new DeveloperError('positiveTicks is required.');
        }
        
        var i;
        var len;
        var tick;

        var hash = {};
        var sortedFilteredPositiveTicks = this._sortedFilteredPositiveTicks;
        sortedFilteredPositiveTicks.length = 0;
        for (i = 0, len = positiveTicks.length; i < len; ++i) {
            tick = positiveTicks[i];
            //filter duplicates
            if (!hash.hasOwnProperty(tick)) {
                hash[tick] = true;
                sortedFilteredPositiveTicks.push(tick);
            }
        }
        sortedFilteredPositiveTicks.sort(numberComparator);

        var allTicks = [];
        for (len = sortedFilteredPositiveTicks.length, i = len - 1; i >= 0; --i) {
            tick = sortedFilteredPositiveTicks[i];
            if (tick !== 0) {
                allTicks.push(-tick);
            }
        }
        Array.prototype.push.apply(allTicks, sortedFilteredPositiveTicks);

        this._allShuttleRingTicks = allTicks;
    };

    defineProperties(AnimationViewModel.prototype, {
        /**
         * Gets a command that decreases the speed of animation.
         * @memberof AnimationViewModel.prototype
         * @type {Command}
         */
        slower : {
            get : function() {
                return this._slower;
            }
        },

        /**
         * Gets a command that increases the speed of animation.
         * @memberof AnimationViewModel.prototype
         * @type {Command}
         */
        faster : {
            get : function() {
                return this._faster;
            }
        },

        /**
         * Gets the clock view model.
         * @memberof AnimationViewModel.prototype
         *
         * @type {ClockViewModel}
         */
        clockViewModel : {
            get : function() {
                return this._clockViewModel;
            }
        },

        /**
         * Gets the pause toggle button view model.
         * @memberof AnimationViewModel.prototype
         *
         * @type {ToggleButtonViewModel}
         */
        pauseViewModel : {
            get : function() {
                return this._pauseViewModel;
            }
        },

        /**
         * Gets the reverse toggle button view model.
         * @memberof AnimationViewModel.prototype
         *
         * @type {ToggleButtonViewModel}
         */
        playReverseViewModel : {
            get : function() {
                return this._playReverseViewModel;
            }
        },

        /**
         * Gets the play toggle button view model.
         * @memberof AnimationViewModel.prototype
         *
         * @type {ToggleButtonViewModel}
         */
        playForwardViewModel : {
            get : function() {
                return this._playForwardViewModel;
            }
        },

        /**
         * Gets the realtime toggle button view model.
         * @memberof AnimationViewModel.prototype
         *
         * @type {ToggleButtonViewModel}
         */
        playRealtimeViewModel : {
            get : function() {
                return this._playRealtimeViewModel;
            }
        },

        /**
         * Gets or sets the function which formats a date for display.
         * @memberof AnimationViewModel.prototype
         *
         * @type {AnimationViewModel~DateFormatter}
         * @default AnimationViewModel.defaultDateFormatter
         */
        dateFormatter : {
            //TODO:@exception {DeveloperError} dateFormatter must be a function.
            get : function() {
                return this._dateFormatter;
            },
            set : function(dateFormatter) {
                                if (typeof dateFormatter !== 'function') {
                    throw new DeveloperError('dateFormatter must be a function');
                }
                
                this._dateFormatter = dateFormatter;
            }
        },

        /**
         * Gets or sets the function which formats a time for display.
         * @memberof AnimationViewModel.prototype
         *
         * @type {AnimationViewModel~TimeFormatter}
         * @default AnimationViewModel.defaultTimeFormatter
         */
        timeFormatter : {
            //TODO:@exception {DeveloperError} timeFormatter must be a function.
            get : function() {
                return this._timeFormatter;
            },
            set : function(timeFormatter) {
                                if (typeof timeFormatter !== 'function') {
                    throw new DeveloperError('timeFormatter must be a function');
                }
                
                this._timeFormatter = timeFormatter;
            }
        }
    });

    //Currently exposed for tests.
    AnimationViewModel._maxShuttleRingAngle = maxShuttleRingAngle;
    AnimationViewModel._realtimeShuttleRingAngle = realtimeShuttleRingAngle;

    /**
     * A function that formats a date for display.
     * @callback AnimationViewModel~DateFormatter
     *
     * @param {JulianDate} date The date to be formatted
     * @param {AnimationViewModel} viewModel The AnimationViewModel instance requesting formatting.
     * @returns {String} The string representation of the calendar date portion of the provided date.
     */

    /**
     * A function that formats a time for display.
     * @callback AnimationViewModel~TimeFormatter
     *
     * @param {JulianDate} date The date to be formatted
     * @param {AnimationViewModel} viewModel The AnimationViewModel instance requesting formatting.
     * @returns {String} The string representation of the time portion of the provided date.
     */

    return AnimationViewModel;
});

/*global define*/
define('Widgets/BaseLayerPicker/BaseLayerPickerViewModel',[
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../Core/EllipsoidTerrainProvider',
        '../../Core/isArray',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        EllipsoidTerrainProvider,
        isArray,
        knockout,
        createCommand) {
    'use strict';

    /**
     * The view model for {@link BaseLayerPicker}.
     * @alias BaseLayerPickerViewModel
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Globe} options.globe The Globe to use.
     * @param {ProviderViewModel[]} [options.imageryProviderViewModels=[]] The array of ProviderViewModel instances to use for imagery.
     * @param {ProviderViewModel} [options.selectedImageryProviderViewModel] The view model for the current base imagery layer, if not supplied the first available imagery layer is used.
     * @param {ProviderViewModel[]} [options.terrainProviderViewModels=[]] The array of ProviderViewModel instances to use for terrain.
     * @param {ProviderViewModel} [options.selectedTerrainProviderViewModel] The view model for the current base terrain layer, if not supplied the first available terrain layer is used.
     *
     * @exception {DeveloperError} imageryProviderViewModels must be an array.
     * @exception {DeveloperError} terrainProviderViewModels must be an array.
     */
    function BaseLayerPickerViewModel(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var globe = options.globe;
        var imageryProviderViewModels = defaultValue(options.imageryProviderViewModels, []);
        var terrainProviderViewModels = defaultValue(options.terrainProviderViewModels, []);

                if (!defined(globe)) {
            throw new DeveloperError('globe is required');
        }
        
        this._globe = globe;

        /**
         * Gets or sets an array of ProviderViewModel instances available for imagery selection.
         * This property is observable.
         * @type {ProviderViewModel[]}
         */
        this.imageryProviderViewModels = imageryProviderViewModels.slice(0);

        /**
         * Gets or sets an array of ProviderViewModel instances available for terrain selection.
         * This property is observable.
         * @type {ProviderViewModel[]}
         */
        this.terrainProviderViewModels = terrainProviderViewModels.slice(0);

        /**
         * Gets or sets whether the imagery selection drop-down is currently visible.
         * @type {Boolean}
         * @default false
         */
        this.dropDownVisible = false;

        knockout.track(this, ['imageryProviderViewModels', 'terrainProviderViewModels', 'dropDownVisible']);

        /**
         * Gets the button tooltip.  This property is observable.
         * @type {String}
         */
        this.buttonTooltip = undefined;
        knockout.defineProperty(this, 'buttonTooltip', function() {
            var selectedImagery = this.selectedImagery;
            var selectedTerrain = this.selectedTerrain;

            var imageryTip = defined(selectedImagery) ? selectedImagery.name : undefined;
            var terrainTip = defined(selectedTerrain) ? selectedTerrain.name : undefined;

            if (defined(imageryTip) && defined(terrainTip)) {
                return imageryTip + '\n' + terrainTip;
            } else if (defined(imageryTip)) {
                return imageryTip;
            }
            return terrainTip;
        });

        /**
         * Gets the button background image.  This property is observable.
         * @type {String}
         */
        this.buttonImageUrl = undefined;
        knockout.defineProperty(this, 'buttonImageUrl', function() {
            var viewModel = this.selectedImagery;
            return defined(viewModel) ? viewModel.iconUrl : undefined;
        });

        /**
         * Gets or sets the currently selected imagery.  This property is observable.
         * @type {ProviderViewModel}
         * @default undefined
         */
        this.selectedImagery = undefined;
        var selectedImageryViewModel = knockout.observable();

        this._currentImageryProviders = [];
        knockout.defineProperty(this, 'selectedImagery', {
            get : function() {
                return selectedImageryViewModel();
            },
            set : function(value) {
                if (selectedImageryViewModel() === value) {
                    this.dropDownVisible = false;
                    return;
                }

                var i;
                var currentImageryProviders = this._currentImageryProviders;
                var currentImageryProvidersLength = currentImageryProviders.length;
                var imageryLayers = this._globe.imageryLayers;
                for (i = 0; i < currentImageryProvidersLength; i++) {
                    var layersLength = imageryLayers.length;
                    for ( var x = 0; x < layersLength; x++) {
                        var layer = imageryLayers.get(x);
                        if (layer.imageryProvider === currentImageryProviders[i]) {
                            imageryLayers.remove(layer);
                            break;
                        }
                    }
                }

                if (defined(value)) {
                    var newProviders = value.creationCommand();
                    if (isArray(newProviders)) {
                        var newProvidersLength = newProviders.length;
                        for (i = newProvidersLength - 1; i >= 0; i--) {
                            imageryLayers.addImageryProvider(newProviders[i], 0);
                        }
                        this._currentImageryProviders = newProviders.slice(0);
                    } else {
                        this._currentImageryProviders = [newProviders];
                        imageryLayers.addImageryProvider(newProviders, 0);
                    }
                }
                selectedImageryViewModel(value);
                this.dropDownVisible = false;
            }
        });

        /**
         * Gets or sets the currently selected terrain.  This property is observable.
         * @type {ProviderViewModel}
         * @default undefined
         */
        this.selectedTerrain = undefined;
        var selectedTerrainViewModel = knockout.observable();

        knockout.defineProperty(this, 'selectedTerrain', {
            get : function() {
                return selectedTerrainViewModel();
            },
            set : function(value) {
                if (selectedTerrainViewModel() === value) {
                    this.dropDownVisible = false;
                    return;
                }

                var newProvider;
                if (defined(value)) {
                    newProvider = value.creationCommand();
                }

                this._globe.depthTestAgainstTerrain = !(newProvider instanceof EllipsoidTerrainProvider);
                this._globe.terrainProvider = newProvider;
                selectedTerrainViewModel(value);
                this.dropDownVisible = false;
            }
        });

        var that = this;
        this._toggleDropDown = createCommand(function() {
            that.dropDownVisible = !that.dropDownVisible;
        });

        this.selectedImagery = defaultValue(options.selectedImageryProviderViewModel, imageryProviderViewModels[0]);
        this.selectedTerrain = defaultValue(options.selectedTerrainProviderViewModel, terrainProviderViewModels[0]);
    }

    defineProperties(BaseLayerPickerViewModel.prototype, {
        /**
         * Gets the command to toggle the visibility of the drop down.
         * @memberof BaseLayerPickerViewModel.prototype
         *
         * @type {Command}
         */
        toggleDropDown : {
            get : function() {
                return this._toggleDropDown;
            }
        },

        /**
         * Gets the globe.
         * @memberof BaseLayerPickerViewModel.prototype
         *
         * @type {Globe}
         */
        globe : {
            get : function() {
                return this._globe;
            }
        }
    });

    return BaseLayerPickerViewModel;
});

/*global define*/
define('Widgets/BaseLayerPicker/BaseLayerPicker',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/FeatureDetection',
        '../../ThirdParty/knockout',
        '../getElement',
        './BaseLayerPickerViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        knockout,
        getElement,
        BaseLayerPickerViewModel) {
    'use strict';

    /**
     * <span style="display: block; text-align: center;">
     * <img src="images/BaseLayerPicker.png" width="264" height="287" alt="" />
     * <br />BaseLayerPicker with its drop-panel open.
     * </span>
     * <br /><br />
     * The BaseLayerPicker is a single button widget that displays a panel of available imagery and
     * terrain providers.  When imagery is selected, the corresponding imagery layer is created and inserted
     * as the base layer of the imagery collection; removing the existing base.  When terrain is selected,
     * it replaces the current terrain provider.  Each item in the available providers list contains a name,
     * a representative icon, and a tooltip to display more information when hovered. The list is initially
     * empty, and must be configured before use, as illustrated in the below example.
     *
     * @alias BaseLayerPicker
     * @constructor
     *
     * @param {Element|String} container The parent HTML container node or ID for this widget.
     * @param {Object} options Object with the following properties:
     * @param {Globe} options.globe The Globe to use.
     * @param {ProviderViewModel[]} [options.imageryProviderViewModels=[]] The array of ProviderViewModel instances to use for imagery.
     * @param {ProviderViewModel} [options.selectedImageryProviderViewModel] The view model for the current base imagery layer, if not supplied the first available imagery layer is used.
     * @param {ProviderViewModel[]} [options.terrainProviderViewModels=[]] The array of ProviderViewModel instances to use for terrain.
     * @param {ProviderViewModel} [options.selectedTerrainProviderViewModel] The view model for the current base terrain layer, if not supplied the first available terrain layer is used.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     *
     *
     * @example
     * // In HTML head, include a link to the BaseLayerPicker.css stylesheet,
     * // and in the body, include: <div id="baseLayerPickerContainer"
     * //   style="position:absolute;top:24px;right:24px;width:38px;height:38px;"></div>
     *
     * //Create the list of available providers we would like the user to select from.
     * //This example uses 3, OpenStreetMap, The Black Marble, and a single, non-streaming world image.
     * var imageryViewModels = [];
     * imageryViewModels.push(new Cesium.ProviderViewModel({
     *      name : 'Open\u00adStreet\u00adMap',
     *      iconUrl : Cesium.buildModuleUrl('Widgets/Images/ImageryProviders/openStreetMap.png'),
     *      tooltip : 'OpenStreetMap (OSM) is a collaborative project to create a free editable \
     * map of the world.\nhttp://www.openstreetmap.org',
     *      creationFunction : function() {
     *          return Cesium.createOpenStreetMapImageryProvider({
     *              url : 'https://a.tile.openstreetmap.org/'
     *          });
     *      }
     *  }));
     *
     *  imageryViewModels.push(new Cesium.ProviderViewModel({
     *      name : 'Black Marble',
     *      iconUrl : Cesium.buildModuleUrl('Widgets/Images/ImageryProviders/blackMarble.png'),
     *      tooltip : 'The lights of cities and villages trace the outlines of civilization \
     * in this global view of the Earth at night as seen by NASA/NOAA\'s Suomi NPP satellite.',
     *      creationFunction : function() {
     *          return Cesium.createTileMapServiceImageryProvider({
     *              url : 'https://cesiumjs.org/blackmarble',
     *              credit : 'Black Marble imagery courtesy NASA Earth Observatory',
     *              flipXY : true
     *          });
     *      }
     *  }));
     *
     *  imageryViewModels.push(new Cesium.ProviderViewModel({
     *      name : 'Natural Earth\u00a0II',
     *      iconUrl : Cesium.buildModuleUrl('Widgets/Images/ImageryProviders/naturalEarthII.png'),
     *      tooltip : 'Natural Earth II, darkened for contrast.\nhttp://www.naturalearthdata.com/',
     *      creationFunction : function() {
     *          return Cesium.createTileMapServiceImageryProvider({
     *              url : Cesium.buildModuleUrl('Assets/Textures/NaturalEarthII')
     *          });
     *      }
     *  }));
     *
     * //Create a CesiumWidget without imagery, if you haven't already done so.
     * var cesiumWidget = new Cesium.CesiumWidget('cesiumContainer', { imageryProvider: false });
     *
     * //Finally, create the baseLayerPicker widget using our view models.
     * var layers = cesiumWidget.imageryLayers;
     * var baseLayerPicker = new Cesium.BaseLayerPicker('baseLayerPickerContainer', {
     *     globe : cesiumWidget.scene.globe,
     *     imageryProviderViewModels : imageryViewModels
     * });
     * 
     * @see TerrainProvider
     * @see ImageryProvider
     * @see ImageryLayerCollection
     */
    function BaseLayerPicker(container, options) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        container = getElement(container);

        var viewModel = new BaseLayerPickerViewModel(options);

        var element = document.createElement('button');
        element.type = 'button';
        element.className = 'cesium-button cesium-toolbar-button';
        element.setAttribute('data-bind', '\
attr: { title: buttonTooltip },\
click: toggleDropDown');
        container.appendChild(element);

        var imgElement = document.createElement('img');
        imgElement.setAttribute('draggable', 'false');
        imgElement.className = 'cesium-baseLayerPicker-selected';
        imgElement.setAttribute('data-bind', '\
attr: { src: buttonImageUrl }');
        element.appendChild(imgElement);

        var dropPanel = document.createElement('div');
        dropPanel.className = 'cesium-baseLayerPicker-dropDown';
        dropPanel.setAttribute('data-bind', '\
css: { "cesium-baseLayerPicker-dropDown-visible" : dropDownVisible }');
        container.appendChild(dropPanel);

        var imageryTitle = document.createElement('div');
        imageryTitle.className = 'cesium-baseLayerPicker-sectionTitle';
        imageryTitle.setAttribute('data-bind', 'visible: imageryProviderViewModels.length > 0');
        imageryTitle.innerHTML = 'Imagery';
        dropPanel.appendChild(imageryTitle);

        var imageryChoices = document.createElement('div');
        imageryChoices.className = 'cesium-baseLayerPicker-choices';
        imageryChoices.setAttribute('data-bind', 'foreach: imageryProviderViewModels');
        dropPanel.appendChild(imageryChoices);

        var imageryProvider = document.createElement('div');
        imageryProvider.className = 'cesium-baseLayerPicker-item';
        imageryProvider.setAttribute('data-bind', '\
css: { "cesium-baseLayerPicker-selectedItem" : $data === $parent.selectedImagery },\
attr: { title: tooltip },\
visible: creationCommand.canExecute,\
click: function($data) { $parent.selectedImagery = $data; }');
        imageryChoices.appendChild(imageryProvider);

        var providerIcon = document.createElement('img');
        providerIcon.className = 'cesium-baseLayerPicker-itemIcon';
        providerIcon.setAttribute('data-bind', 'attr: { src: iconUrl }');
        providerIcon.setAttribute('draggable', 'false');
        imageryProvider.appendChild(providerIcon);

        var providerLabel = document.createElement('div');
        providerLabel.className = 'cesium-baseLayerPicker-itemLabel';
        providerLabel.setAttribute('data-bind', 'text: name');
        imageryProvider.appendChild(providerLabel);

        var terrainTitle = document.createElement('div');
        terrainTitle.className = 'cesium-baseLayerPicker-sectionTitle';
        terrainTitle.setAttribute('data-bind', 'visible: terrainProviderViewModels.length > 0');
        terrainTitle.innerHTML = 'Terrain';
        dropPanel.appendChild(terrainTitle);

        var terrainChoices = document.createElement('div');
        terrainChoices.className = 'cesium-baseLayerPicker-choices';
        terrainChoices.setAttribute('data-bind', 'foreach: terrainProviderViewModels');
        dropPanel.appendChild(terrainChoices);

        var terrainProvider = document.createElement('div');
        terrainProvider.className = 'cesium-baseLayerPicker-item';
        terrainProvider.setAttribute('data-bind', '\
css: { "cesium-baseLayerPicker-selectedItem" : $data === $parent.selectedTerrain },\
attr: { title: tooltip },\
visible: creationCommand.canExecute,\
click: function($data) { $parent.selectedTerrain = $data; }');
        terrainChoices.appendChild(terrainProvider);

        var terrainProviderIcon = document.createElement('img');
        terrainProviderIcon.className = 'cesium-baseLayerPicker-itemIcon';
        terrainProviderIcon.setAttribute('data-bind', 'attr: { src: iconUrl }');
        terrainProviderIcon.setAttribute('draggable', 'false');
        terrainProvider.appendChild(terrainProviderIcon);

        var terrainProviderLabel = document.createElement('div');
        terrainProviderLabel.className = 'cesium-baseLayerPicker-itemLabel';
        terrainProviderLabel.setAttribute('data-bind', 'text: name');
        terrainProvider.appendChild(terrainProviderLabel);

        knockout.applyBindings(viewModel, element);
        knockout.applyBindings(viewModel, dropPanel);

        this._viewModel = viewModel;
        this._container = container;
        this._element = element;
        this._dropPanel = dropPanel;

        this._closeDropDown = function(e) {
            if (!(element.contains(e.target) || dropPanel.contains(e.target))) {
                viewModel.dropDownVisible = false;
            }
        };

        if (FeatureDetection.supportsPointerEvents()) {
            document.addEventListener('pointerdown', this._closeDropDown, true);
        } else {
            document.addEventListener('mousedown', this._closeDropDown, true);
            document.addEventListener('touchstart', this._closeDropDown, true);
        }
    }

    defineProperties(BaseLayerPicker.prototype, {
        /**
         * Gets the parent container.
         * @memberof BaseLayerPicker.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof BaseLayerPicker.prototype
         *
         * @type {BaseLayerPickerViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    BaseLayerPicker.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    BaseLayerPicker.prototype.destroy = function() {
        if (FeatureDetection.supportsPointerEvents()) {
            document.removeEventListener('pointerdown', this._closeDropDown, true);
        } else {
            document.removeEventListener('mousedown', this._closeDropDown, true);
            document.removeEventListener('touchstart', this._closeDropDown, true);
        }

        knockout.cleanNode(this._element);
        knockout.cleanNode(this._dropPanel);
        this._container.removeChild(this._element);
        this._container.removeChild(this._dropPanel);
        return destroyObject(this);
    };

    return BaseLayerPicker;
});

/*global define*/
define('Widgets/BaseLayerPicker/ProviderViewModel',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        knockout,
        createCommand) {
    'use strict';

    /**
     * A view model that represents each item in the {@link BaseLayerPicker}.
     *
     * @alias ProviderViewModel
     * @constructor
     *
     * @param {Object} options The object containing all parameters.
     * @param {String} options.name The name of the layer.
     * @param {String} options.tooltip The tooltip to show when the item is moused over.
     * @param {String} options.iconUrl An icon representing the layer.
     * @param {ProviderViewModel~CreationFunction|Command} options.creationFunction A function or Command
     *        that creates one or more providers which will be added to the globe when this item is selected.
     *
     * @see BaseLayerPicker
     * @see ImageryProvider
     * @see TerrainProvider
     */
    function ProviderViewModel(options) {
                if (!defined(options.name)) {
            throw new DeveloperError('options.name is required.');
        }
        if (!defined(options.tooltip)) {
            throw new DeveloperError('options.tooltip is required.');
        }
        if (!defined(options.iconUrl)) {
            throw new DeveloperError('options.iconUrl is required.');
        }
        if (typeof options.creationFunction !== 'function') {
            throw new DeveloperError('options.creationFunction is required.');
        }
        
        var creationCommand = options.creationFunction;
        if (!defined(creationCommand.canExecute)) {
            creationCommand = createCommand(creationCommand);
        }

        this._creationCommand = creationCommand;

        /**
         * Gets the display name.  This property is observable.
         * @type {String}
         */
        this.name = options.name;

        /**
         * Gets the tooltip.  This property is observable.
         * @type {String}
         */
        this.tooltip = options.tooltip;

        /**
         * Gets the icon.  This property is observable.
         * @type {String}
         */
        this.iconUrl = options.iconUrl;

        knockout.track(this, ['name', 'tooltip', 'iconUrl']);
    }

    defineProperties(ProviderViewModel.prototype, {
        /**
         * Gets the Command that creates one or more providers which will be added to
         * the globe when this item is selected.
         * @memberof ProviderViewModel.prototype
         *
         * @type {Command}
         */
        creationCommand : {
            get : function() {
                return this._creationCommand;
            }
        }
    });

    /**
     * A function which creates one or more providers.
     * @callback ProviderViewModel~CreationFunction
     * @returns {ImageryProvider|TerrainProvider|ImageryProvider[]|TerrainProvider[]}
     *          The ImageryProvider or TerrainProvider, or array of providers, to be added
     *          to the globe.
     */

    return ProviderViewModel;
});

/*global define*/
define('Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels',[
        '../../Core/buildModuleUrl',
        '../../Scene/ArcGisMapServerImageryProvider',
        '../../Scene/BingMapsImageryProvider',
        '../../Scene/BingMapsStyle',
        '../../Scene/createOpenStreetMapImageryProvider',
        '../../Scene/createTileMapServiceImageryProvider',
        '../../Scene/MapboxImageryProvider',
        '../BaseLayerPicker/ProviderViewModel'
    ], function(
        buildModuleUrl,
        ArcGisMapServerImageryProvider,
        BingMapsImageryProvider,
        BingMapsStyle,
        createOpenStreetMapImageryProvider,
        createTileMapServiceImageryProvider,
        MapboxImageryProvider,
        ProviderViewModel) {
    'use strict';

    /**
     * @private
     */
    function createDefaultImageryProviderViewModels() {
        var providerViewModels = [];
        providerViewModels.push(new ProviderViewModel({
            name : 'Bing Maps Aerial',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/bingAerial.png'),
            tooltip : 'Bing Maps aerial imagery \nhttp://www.bing.com/maps',
            creationFunction : function() {
                return new BingMapsImageryProvider({
                    url : 'https://dev.virtualearth.net',
                    mapStyle : BingMapsStyle.AERIAL
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'Bing Maps Aerial with Labels',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/bingAerialLabels.png'),
            tooltip : 'Bing Maps aerial imagery with label overlays \nhttp://www.bing.com/maps',
            creationFunction : function() {
                return new BingMapsImageryProvider({
                    url : 'https://dev.virtualearth.net',
                    mapStyle : BingMapsStyle.AERIAL_WITH_LABELS
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'Bing Maps Roads',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/bingRoads.png'),
            tooltip : 'Bing Maps standard road maps\nhttp://www.bing.com/maps',
            creationFunction : function() {
                return new BingMapsImageryProvider({
                    url : 'https://dev.virtualearth.net',
                    mapStyle : BingMapsStyle.ROAD
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name: 'Mapbox Satellite',
            tooltip: 'Mapbox satellite imagery https://www.mapbox.com/maps/',
            iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxSatellite.png'),
            creationFunction: function() {
                return new MapboxImageryProvider({
                    mapId: 'mapbox.satellite'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name: 'Mapbox Streets',
            tooltip: 'Mapbox streets imagery https://www.mapbox.com/maps/',
            iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxTerrain.png'),
            creationFunction: function() {
                return new MapboxImageryProvider({
                    mapId: 'mapbox.streets'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name: 'Mapbox Streets Classic',
            tooltip: 'Mapbox streets basic imagery https://www.mapbox.com/maps/',
            iconUrl: buildModuleUrl('Widgets/Images/ImageryProviders/mapboxStreets.png'),
            creationFunction: function() {
                return new MapboxImageryProvider({
                    mapId: 'mapbox.streets-basic'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'ESRI World Imagery',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/esriWorldImagery.png'),
            tooltip : '\
World Imagery provides one meter or better satellite and aerial imagery in many parts of the world and lower resolution \
satellite imagery worldwide.  The map includes NASA Blue Marble: Next Generation 500m resolution imagery at small scales \
(above 1:1,000,000), i-cubed 15m eSAT imagery at medium-to-large scales (down to 1:70,000) for the world, and USGS 15m Landsat \
imagery for Antarctica. The map features 0.3m resolution imagery in the continental United States and 0.6m resolution imagery in \
parts of Western Europe from DigitalGlobe. In other parts of the world, 1 meter resolution imagery is available from GeoEye IKONOS, \
i-cubed Nationwide Prime, Getmapping, AeroGRID, IGN Spain, and IGP Portugal.  Additionally, imagery at different resolutions has been \
contributed by the GIS User Community.\nhttp://www.esri.com',
            creationFunction : function() {
                return new ArcGisMapServerImageryProvider({
                    url : 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
                    enablePickFeatures : false
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'ESRI World Street Map',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/esriWorldStreetMap.png'),
            tooltip : '\
This worldwide street map presents highway-level data for the world. Street-level data includes the United States; much of \
Canada; Japan; most countries in Europe; Australia and New Zealand; India; parts of South America including Argentina, Brazil, \
Chile, Colombia, and Venezuela; Ghana; and parts of southern Africa including Botswana, Lesotho, Namibia, South Africa, and Swaziland.\n\
http://www.esri.com',
            creationFunction : function() {
                return new ArcGisMapServerImageryProvider({
                    url : 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer',
                    enablePickFeatures : false
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'ESRI National Geographic',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/esriNationalGeographic.png'),
            tooltip : '\
This web map contains the National Geographic World Map service. This map service is designed to be used as a general reference map \
for informational and educational purposes as well as a basemap by GIS professionals and other users for creating web maps and web \
mapping applications.\nhttp://www.esri.com',
            creationFunction : function() {
                return new ArcGisMapServerImageryProvider({
                    url : 'https://services.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/',
                    enablePickFeatures : false
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'Open\u00adStreet\u00adMap',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/openStreetMap.png'),
            tooltip : 'OpenStreetMap (OSM) is a collaborative project to create a free editable map \
of the world.\nhttp://www.openstreetmap.org',
            creationFunction : function() {
                return createOpenStreetMapImageryProvider({
                    url : 'https://a.tile.openstreetmap.org/'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'Stamen Watercolor',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/stamenWatercolor.png'),
            tooltip : 'Reminiscent of hand drawn maps, Stamen watercolor maps apply raster effect \
area washes and organic edges over a paper texture to add warm pop to any map.\nhttp://maps.stamen.com',
            creationFunction : function() {
                return createOpenStreetMapImageryProvider({
                    url : 'https://stamen-tiles.a.ssl.fastly.net/watercolor/',
                    credit : 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'Stamen Toner',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/stamenToner.png'),
            tooltip : 'A high contrast black and white map.\nhttp://maps.stamen.com',
            creationFunction : function() {
                return createOpenStreetMapImageryProvider({
                    url : 'https://stamen-tiles.a.ssl.fastly.net/toner/',
                    credit : 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'The Black Marble',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/blackMarble.png'),
            tooltip : 'The lights of cities and villages trace the outlines of civilization in this global view of the \
Earth at night as seen by NASA/NOAA\'s Suomi NPP satellite.',
            creationFunction : function() {
                return createTileMapServiceImageryProvider({
                    url : 'https://cesiumjs.org/blackmarble',
                    flipXY : true,
                    credit : 'Black Marble imagery courtesy NASA Earth Observatory'
                });
            }
        }));

        providerViewModels.push(new ProviderViewModel({
            name : 'Natural Earth\u00a0II',
            iconUrl : buildModuleUrl('Widgets/Images/ImageryProviders/naturalEarthII.png'),
            tooltip : 'Natural Earth II, darkened for contrast.\nhttp://www.naturalearthdata.com/',
            creationFunction : function() {
                return createTileMapServiceImageryProvider({
                    url : buildModuleUrl('Assets/Textures/NaturalEarthII')
                });
            }
        }));

        return providerViewModels;
    }

    return createDefaultImageryProviderViewModels;
});

/*global define*/
define('Widgets/BaseLayerPicker/createDefaultTerrainProviderViewModels',[
        '../../Core/buildModuleUrl',
        '../../Core/CesiumTerrainProvider',
        '../../Core/EllipsoidTerrainProvider',
        '../BaseLayerPicker/ProviderViewModel'
    ], function(
        buildModuleUrl,
        CesiumTerrainProvider,
        EllipsoidTerrainProvider,
        ProviderViewModel) {
    'use strict';

    /**
     * @private
     */
    function createDefaultTerrainProviderViewModels() {
        var providerViewModels = [];
        providerViewModels.push(new ProviderViewModel({
            name : 'WGS84 Ellipsoid',
            iconUrl : buildModuleUrl('Widgets/Images/TerrainProviders/Ellipsoid.png'),
            tooltip : 'WGS84 standard ellipsoid, also known as EPSG:4326',
            creationFunction : function() {
                return new EllipsoidTerrainProvider();
            }
        }));


        providerViewModels.push(new ProviderViewModel({
            name : 'STK World Terrain meshes',
            iconUrl : buildModuleUrl('Widgets/Images/TerrainProviders/STK.png'),
            tooltip : 'High-resolution, mesh-based terrain for the entire globe. Free for use on the Internet. Closed-network options are available.\nhttp://www.agi.com',
            creationFunction : function() {
                return new CesiumTerrainProvider({
                    url : 'https://assets.agi.com/stk-terrain/world',
                    requestWaterMask : true,
                    requestVertexNormals : true
                });
            }
        }));

        return providerViewModels;
    }

    return createDefaultTerrainProviderViewModels;
});

/*global define*/
define('Widgets/CesiumInspector/CesiumInspectorViewModel',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/Rectangle',
        '../../Core/ScreenSpaceEventHandler',
        '../../Core/ScreenSpaceEventType',
        '../../Scene/DebugModelMatrixPrimitive',
        '../../Scene/PerformanceDisplay',
        '../../Scene/TileCoordinatesImageryProvider',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Rectangle,
        ScreenSpaceEventHandler,
        ScreenSpaceEventType,
        DebugModelMatrixPrimitive,
        PerformanceDisplay,
        TileCoordinatesImageryProvider,
        knockout,
        createCommand) {
    'use strict';

    function frustumStatsToString(stats) {
        var str;
        if (defined(stats)) {
            str = 'Command Statistics';
            var com = stats.commandsInFrustums;
            for (var n in com) {
                if (com.hasOwnProperty(n)) {
                    var num = parseInt(n, 10);
                    var s;
                    if (num === 7) {
                        s = '1, 2 and 3';
                    } else {
                        var f = [];
                        for (var i = 2; i >= 0; i--) {
                            var p = Math.pow(2, i);
                            if (num >= p) {
                                f.push(i + 1);
                                num -= p;
                            }
                        }
                        s = f.reverse().join(' and ');
                    }
                    str += '<br>&nbsp;&nbsp;&nbsp;&nbsp;' + com[n] + ' in frustum ' + s;
                }
            }
            str += '<br>Total: ' + stats.totalCommands;
        }

        return str;
    }

    function boundDepthFrustum(lower, upper, proposed) {
        var bounded = Math.min(proposed, upper);
        bounded = Math.max(bounded, lower);
        return bounded;
    }

    /**
     * The view model for {@link CesiumInspector}.
     * @alias CesiumInspectorViewModel
     * @constructor
     *
     * @param {Scene} scene The scene instance to use.
     * @param {PerformanceContainer} performanceContainer The instance to use for performance container.
     *
     * @exception {DeveloperError} scene is required.
     */
    function CesiumInspectorViewModel(scene, performanceContainer) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required');
        }

        if (!defined(performanceContainer)) {
            throw new DeveloperError('performanceContainer is required');
        }
        
        var that = this;
        var canvas = scene.canvas;
        var eventHandler = new ScreenSpaceEventHandler(canvas);
        this._eventHandler = eventHandler;
        this._scene = scene;
        this._canvas = canvas;
        this._primitive = undefined;
        this._tile = undefined;
        this._modelMatrixPrimitive = undefined;
        this._performanceDisplay = undefined;
        this._performanceContainer = performanceContainer;

        var globe = this._scene.globe;
        globe.depthTestAgainstTerrain = true;

        /**
         * Gets or sets the show frustums state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.frustums = false;

        /**
         * Gets or sets the show frustum planes state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.frustumPlanes = false;

        /**
         * Gets or sets the show performance display state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.performance = false;

        /**
         * Gets or sets the shader cache text.  This property is observable.
         * @type {String}
         * @default ''
         */
        this.shaderCacheText = '';

        /**
         * Gets or sets the show primitive bounding sphere state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.primitiveBoundingSphere = false;

        /**
         * Gets or sets the show primitive reference frame state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.primitiveReferenceFrame = false;

        /**
         * Gets or sets the filter primitive state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.filterPrimitive = false;

        /**
         * Gets or sets the show tile bounding sphere state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.tileBoundingSphere = false;

        /**
         * Gets or sets the filter tile state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.filterTile = false;

        /**
         * Gets or sets the show wireframe state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.wireframe = false;

        /**
         * Gets or sets the show globe depth state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.globeDepth = false;

        /**
         * Gets or sets the show pick depth state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.pickDepth = false;

        /**
         * Gets or sets the index of the depth frustum to display.  This property is observable.
         * @type {Number}
         * @default 1
         */
        this.depthFrustum = 1;
        this._numberOfFrustums = 1;

        /**
         * Gets or sets the suspend updates state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.suspendUpdates = false;

        /**
         * Gets or sets the show tile coordinates state.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.tileCoordinates = false;

        /**
         * Gets or sets the frustum statistic text.  This property is observable.
         * @type {String}
         * @default ''
         */
        this.frustumStatisticText = false;

        /**
         * Gets or sets the selected tile information text.  This property is observable.
         * @type {String}
         * @default ''
         */
        this.tileText = '';

        /**
         * Gets if a primitive has been selected.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.hasPickedPrimitive = false;

        /**
         * Gets if a tile has been selected.  This property is observable
         * @type {Boolean}
         * @default false
         */
        this.hasPickedTile = false;

        /**
         * Gets if the picking primitive command is active.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.pickPrimitiveActive = false;

        /**
         * Gets if the picking tile command is active.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.pickTileActive = false;

        /**
         * Gets or sets if the cesium inspector drop down is visible.  This property is observable.
         * @type {Boolean}
         * @default true
         */
        this.dropDownVisible = true;

        /**
         * Gets or sets if the general section is visible.  This property is observable.
         * @type {Boolean}
         * @default true
         */
        this.generalVisible = true;

        /**
         * Gets or sets if the primitive section is visible.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.primitivesVisible = false;

        /**
         * Gets or sets if the terrain section is visible.  This property is observable.
         * @type {Boolean}
         * @default false
         */
        this.terrainVisible = false;

        /**
         * Gets or sets the index of the depth frustum text.  This property is observable.
         * @type {String}
         * @default ''
         */
        this.depthFrustumText = '';

        /**
         * Gets the text on the general section expand button.  This property is computed.
         * @type {String}
         * @default '-'
         */
        this.generalSwitchText = knockout.pureComputed(function() {
            return that.generalVisible ? '-' : '+';
        });

        /**
         * Gets the text on the primitives section expand button.  This property is computed.
         * @type {String}
         * @default '+'
         */
        this.primitivesSwitchText = knockout.pureComputed(function() {
            return that.primitivesVisible ? '-' : '+';
        });

        /**
         * Gets the text on the terrain section expand button.  This property is computed.
         * @type {String}
         * @default '+'
         */
        this.terrainSwitchText = knockout.pureComputed(function() {
            return that.terrainVisible ? '-' : '+';
        });

        knockout.track(this, [
            'frustums',
            'frustumPlanes',
            'performance',
            'shaderCacheText',
            'primitiveBoundingSphere',
            'primitiveReferenceFrame',
            'filterPrimitive',
            'tileBoundingSphere',
            'filterTile',
            'wireframe',
            'globeDepth',
            'pickDepth',
            'depthFrustum',
            'suspendUpdates',
            'tileCoordinates',
            'frustumStatisticText',
            'tileText',
            'hasPickedPrimitive',
            'hasPickedTile',
            'pickPrimitiveActive',
            'pickTileActive',
            'dropDownVisible',
            'generalVisible',
            'primitivesVisible',
            'terrainVisible',
            'depthFrustumText'
        ]);

        this._toggleDropDown = createCommand(function() {
            that.dropDownVisible = !that.dropDownVisible;
        });

        this._toggleGeneral = createCommand(function() {
            that.generalVisible = !that.generalVisible;
        });

        this._togglePrimitives = createCommand(function() {
            that.primitivesVisible = !that.primitivesVisible;
        });

        this._toggleTerrain = createCommand(function() {
            that.terrainVisible = !that.terrainVisible;
        });

        this._frustumsSubscription = knockout.getObservable(this, 'frustums').subscribe(function(val) {
            that._scene.debugShowFrustums = val;
        });

        this._frustumPlanesSubscription = knockout.getObservable(this, 'frustumPlanes').subscribe(function(val) {
            that._scene.debugShowFrustumPlanes = val;
        });

        this._performanceSubscription = knockout.getObservable(this, 'performance').subscribe(function(val) {
            if (val) {
                that._performanceDisplay = new PerformanceDisplay({
                    container : that._performanceContainer
                });
            } else {
                that._performanceContainer.innerHTML = '';
            }
        });

        this._showPrimitiveBoundingSphere = createCommand(function() {
            that._primitive.debugShowBoundingVolume = that.primitiveBoundingSphere;
            return true;
        });

        this._primitiveBoundingSphereSubscription = knockout.getObservable(this, 'primitiveBoundingSphere').subscribe(function() {
            that._showPrimitiveBoundingSphere();
        });

        this._showPrimitiveReferenceFrame = createCommand(function() {
            if (that.primitiveReferenceFrame) {
                var modelMatrix = that._primitive.modelMatrix;
                that._modelMatrixPrimitive = new DebugModelMatrixPrimitive({
                    modelMatrix : modelMatrix
                });
                that._scene.primitives.add(that._modelMatrixPrimitive);
            } else if (defined(that._modelMatrixPrimitive)) {
                that._scene.primitives.remove(that._modelMatrixPrimitive);
                that._modelMatrixPrimitive = undefined;
            }
            return true;
        });

        this._primitiveReferenceFrameSubscription = knockout.getObservable(this, 'primitiveReferenceFrame').subscribe(function() {
            that._showPrimitiveReferenceFrame();
        });

        this._doFilterPrimitive = createCommand(function() {
            if (that.filterPrimitive) {
                that._scene.debugCommandFilter = function(command) {
                    if (defined(that._modelMatrixPrimitive) && command.owner === that._modelMatrixPrimitive._primitive) {
                        return true;
                    } else if (defined(that._primitive)) {
                        return command.owner === that._primitive || command.owner === that._primitive._billboardCollection || command.owner.primitive === that._primitive;
                    }
                    return false;
                };
            } else {
                that._scene.debugCommandFilter = undefined;
            }
            return true;
        });

        this._filterPrimitiveSubscription = knockout.getObservable(this, 'filterPrimitive').subscribe(function() {
            that._doFilterPrimitive();
        });

        this._wireframeSubscription = knockout.getObservable(this, 'wireframe').subscribe(function(val) {
            globe._surface.tileProvider._debug.wireframe = val;
        });

        this._globeDepthSubscription = knockout.getObservable(this, 'globeDepth').subscribe(function(val) {
            that._scene.debugShowGlobeDepth = val;
        });

        this._pickDepthSubscription = knockout.getObservable(this, 'pickDepth').subscribe(function(val) {
            that._scene.debugShowPickDepth = val;
        });

        this._depthFrustumSubscription = knockout.getObservable(this, 'depthFrustum').subscribe(function(val) {
            that.scene.debugShowDepthFrustum = val;
        });

        this._incrementDepthFrustum = createCommand(function() {
            var next = that.depthFrustum + 1;
            that.depthFrustum = boundDepthFrustum(1, that._numberOfFrustums, next);
            return true;
        });

        this._decrementDepthFrustum = createCommand(function() {
            var next = that.depthFrustum - 1;
            that.depthFrustum = boundDepthFrustum(1, that._numberOfFrustums, next);
            return true;
        });

        this._suspendUpdatesSubscription = knockout.getObservable(this, 'suspendUpdates').subscribe(function(val) {
            globe._surface._debug.suspendLodUpdate = val;
            if (!val) {
                that.filterTile = false;
            }
        });

        var tileBoundariesLayer;
        this._showTileCoordinates = createCommand(function() {
            if (that.tileCoordinates && !defined(tileBoundariesLayer)) {
                tileBoundariesLayer = scene.imageryLayers.addImageryProvider(new TileCoordinatesImageryProvider({
                    tilingScheme : scene.terrainProvider.tilingScheme
                }));
            } else if (!that.tileCoordinates && defined(tileBoundariesLayer)) {
                scene.imageryLayers.remove(tileBoundariesLayer);
                tileBoundariesLayer = undefined;
            }
            return true;
        });

        this._tileCoordinatesSubscription = knockout.getObservable(this, 'tileCoordinates').subscribe(function() {
            that._showTileCoordinates();
        });

        this._tileBoundingSphereSubscription = knockout.getObservable(this, 'tileBoundingSphere').subscribe(function() {
            that._showTileBoundingSphere();
        });

        this._showTileBoundingSphere = createCommand(function() {
            if (that.tileBoundingSphere) {
                globe._surface.tileProvider._debug.boundingSphereTile = that._tile;
            } else {
                globe._surface.tileProvider._debug.boundingSphereTile = undefined;
            }
            return true;
        });

        this._doFilterTile = createCommand(function() {
            if (!that.filterTile) {
                that.suspendUpdates = false;
            } else {
                that.suspendUpdates = true;

                globe._surface._tilesToRender = [];

                if (defined(that._tile)) {
                    globe._surface._tilesToRender.push(that._tile);
                }
            }
            return true;
        });

        this._filterTileSubscription = knockout.getObservable(this, 'filterTile').subscribe(function() {
            that.doFilterTile();
        });

        function pickPrimitive(e) {
            var newPick = that._scene.pick({
                x : e.position.x,
                y : e.position.y
            });
            if (defined(newPick)) {
                that.primitive = defined(newPick.collection) ? newPick.collection : newPick.primitive;
            }

            that.pickPrimitiveActive = false;
        }

        this._pickPrimitive = createCommand(function() {
            that.pickPrimitiveActive = !that.pickPrimitiveActive;
        });

        this._pickPrimitiveActiveSubscription = knockout.getObservable(this, 'pickPrimitiveActive').subscribe(function(val) {
            if (val) {
                eventHandler.setInputAction(pickPrimitive, ScreenSpaceEventType.LEFT_CLICK);
            } else {
                eventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
            }
        });

        function selectTile(e) {
            var selectedTile;
            var ellipsoid = globe.ellipsoid;
            var cartesian = that._scene.camera.pickEllipsoid({
                x : e.position.x,
                y : e.position.y
            }, ellipsoid);

            if (defined(cartesian)) {
                var cartographic = ellipsoid.cartesianToCartographic(cartesian);
                var tilesRendered = globe._surface.tileProvider._tilesToRenderByTextureCount;
                for (var textureCount = 0; !selectedTile && textureCount < tilesRendered.length; ++textureCount) {
                    var tilesRenderedByTextureCount = tilesRendered[textureCount];
                    if (!defined(tilesRenderedByTextureCount)) {
                        continue;
                    }

                    for (var tileIndex = 0; !selectedTile && tileIndex < tilesRenderedByTextureCount.length; ++tileIndex) {
                        var tile = tilesRenderedByTextureCount[tileIndex];
                        if (Rectangle.contains(tile.rectangle, cartographic)) {
                            selectedTile = tile;
                        }
                    }
                }
            }

            that.tile = selectedTile;

            that.pickTileActive = false;
        }

        this._pickTile = createCommand(function() {
            that.pickTileActive = !that.pickTileActive;
        });

        this._pickTileActiveSubscription = knockout.getObservable(this, 'pickTileActive').subscribe(function(val) {
            if (val) {
                eventHandler.setInputAction(selectTile, ScreenSpaceEventType.LEFT_CLICK);
            } else {
                eventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
            }
        });
    }

    defineProperties(CesiumInspectorViewModel.prototype, {
        /**
         * Gets the scene to control.
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets the container of the PerformanceDisplay
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Element}
         */
        performanceContainer : {
            get : function() {
                return this._performanceContainer;
            }
        },

        /**
         * Gets the command to toggle the visibility of the drop down.
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        toggleDropDown : {
            get : function() {
                return this._toggleDropDown;
            }
        },


        /**
         * Gets the command to toggle the visibility of a BoundingSphere for a primitive
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        showPrimitiveBoundingSphere : {
            get : function() {
                return this._showPrimitiveBoundingSphere;
            }
        },

        /**
         * Gets the command to toggle the visibility of a {@link DebugModelMatrixPrimitive} for the model matrix of a primitive
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        showPrimitiveReferenceFrame : {
            get : function() {
                return this._showPrimitiveReferenceFrame;
            }
        },

        /**
         * Gets the command to toggle a filter that renders only a selected primitive
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        doFilterPrimitive : {
            get : function() {
                return this._doFilterPrimitive;
            }
        },


        /**
         * Gets the command to increment the depth frustum index to be shown
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        incrementDepthFrustum : {
            get : function() {
                return this._incrementDepthFrustum;
            }
        },

        /**
         * Gets the command to decrement the depth frustum index to be shown
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        decrementDepthFrustum : {
            get : function() {
                return this._decrementDepthFrustum;
            }
        },


        /**
         * Gets the command to toggle the visibility of tile coordinates
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        showTileCoordinates : {
            get : function() {
                return this._showTileCoordinates;
            }
        },

        /**
         * Gets the command to toggle the visibility of a BoundingSphere for a selected tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        showTileBoundingSphere : {
            get : function() {
                return this._showTileBoundingSphere;
            }
        },

        /**
         * Gets the command to toggle a filter that renders only a selected tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        doFilterTile : {
            get : function() {
                return this._doFilterTile;
            }
        },

        /**
         * Gets the command to expand and collapse the general section
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        toggleGeneral : {
            get : function() {
                return this._toggleGeneral;
            }
        },

        /**
         * Gets the command to expand and collapse the primitives section
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        togglePrimitives : {
            get : function() {
                return this._togglePrimitives;
            }
        },

        /**
         * Gets the command to expand and collapse the terrain section
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        toggleTerrain : {
            get : function() {
                return this._toggleTerrain;
            }
        },

        /**
         * Gets the command to pick a primitive
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        pickPrimitive : {
            get : function() {
                return this._pickPrimitive;
            }
        },

        /**
         * Gets the command to pick a tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        pickTile : {
            get : function() {
                return this._pickTile;
            }
        },

        /**
         * Gets the command to pick a tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        selectParent : {
            get : function() {
                var that = this;
                return createCommand(function() {
                    that.tile = that.tile.parent;
                });
            }
        },

        /**
         * Gets the command to pick a tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        selectNW : {
            get : function() {
                var that = this;
                return createCommand(function() {
                    that.tile = that.tile.northwestChild;
                });
            }
        },

        /**
         * Gets the command to pick a tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        selectNE : {
            get : function() {
                var that = this;
                return createCommand(function() {
                    that.tile = that.tile.northeastChild;
                });
            }
        },

        /**
         * Gets the command to pick a tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        selectSW : {
            get : function() {
                var that = this;
                return createCommand(function() {
                    that.tile = that.tile.southwestChild;
                });
            }
        },

        /**
         * Gets the command to pick a tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        selectSE : {
            get : function() {
                var that = this;
                return createCommand(function() {
                    that.tile = that.tile.southeastChild;
                });
            }
        },

        /**
         * Gets or sets the current selected primitive
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        primitive : {
            set : function(newPrimitive) {
                var oldPrimitive = this._primitive;
                if (newPrimitive !== oldPrimitive) {
                    this.hasPickedPrimitive = true;
                    if (defined(oldPrimitive)) {
                        oldPrimitive.debugShowBoundingVolume = false;
                    }
                    this._scene.debugCommandFilter = undefined;
                    if (defined(this._modelMatrixPrimitive)) {
                        this._scene.primitives.remove(this._modelMatrixPrimitive);
                        this._modelMatrixPrimitive = undefined;
                    }
                    this._primitive = newPrimitive;
                    newPrimitive.show = false;
                    setTimeout(function() {
                        newPrimitive.show = true;
                    }, 50);
                    this.showPrimitiveBoundingSphere();
                    this.showPrimitiveReferenceFrame();
                    this.doFilterPrimitive();
                }
            },

            get : function() {
                return this._primitive;
            }
        },

        /**
         * Gets or sets the current selected tile
         * @memberof CesiumInspectorViewModel.prototype
         *
         * @type {Command}
         */
        tile : {
            set : function(newTile) {
                if (defined(newTile)) {
                    this.hasPickedTile = true;
                    var oldTile = this._tile;
                    if (newTile !== oldTile) {
                        this.tileText = 'L: ' + newTile.level + ' X: ' + newTile.x + ' Y: ' + newTile.y;
                        this.tileText += '<br>SW corner: ' + newTile.rectangle.west + ', ' + newTile.rectangle.south;
                        this.tileText += '<br>NE corner: ' + newTile.rectangle.east + ', ' + newTile.rectangle.north;
                        this.tileText += '<br>Min: ' + newTile.data.minimumHeight + ' Max: ' + newTile.data.maximumHeight;
                    }
                    this._tile = newTile;
                    this.showTileBoundingSphere();
                    this.doFilterTile();
                } else {
                    this.hasPickedTile = false;
                    this._tile = undefined;
                }
            },

            get : function() {
                return this._tile;
            }
        },

        update : {
            get : function() {
                var that = this;
                return function() {
                    if (that.frustums) {
                        that.frustumStatisticText = frustumStatsToString(that._scene.debugFrustumStatistics);
                    }

                    // Determine the number of frustums being used.
                    var numberOfFrustums = that._scene.numberOfFrustums;
                    that._numberOfFrustums = numberOfFrustums;
                    // Bound the frustum to be displayed.
                    that.depthFrustum = boundDepthFrustum(1, numberOfFrustums, that.depthFrustum);
                    // Update the displayed text.
                    that.depthFrustumText = that.depthFrustum + ' of ' + numberOfFrustums;

                    if (that.performance) {
                        that._performanceDisplay.update();
                    }
                    if (that.primitiveReferenceFrame) {
                        that._modelMatrixPrimitive.modelMatrix = that._primitive.modelMatrix;
                    }

                    that.shaderCacheText = 'Cached shaders: ' + that._scene.context.shaderCache.numberOfShaders;
                };
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    CesiumInspectorViewModel.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    CesiumInspectorViewModel.prototype.destroy = function() {
        this._eventHandler.destroy();
        this._frustumsSubscription.dispose();
        this._frustumPlanesSubscription.dispose();
        this._performanceSubscription.dispose();
        this._primitiveBoundingSphereSubscription.dispose();
        this._primitiveReferenceFrameSubscription.dispose();
        this._filterPrimitiveSubscription.dispose();
        this._wireframeSubscription.dispose();
        this._globeDepthSubscription.dispose();
        this._pickDepthSubscription.dispose();
        this._depthFrustumSubscription.dispose();
        this._suspendUpdatesSubscription.dispose();
        this._tileCoordinatesSubscription.dispose();
        this._tileBoundingSphereSubscription.dispose();
        this._filterTileSubscription.dispose();
        this._pickPrimitiveActiveSubscription.dispose();
        this._pickTileActiveSubscription.dispose();
        return destroyObject(this);
    };

    return CesiumInspectorViewModel;
});

/*global define*/
define('Widgets/CesiumInspector/CesiumInspector',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        './CesiumInspectorViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        CesiumInspectorViewModel) {
    'use strict';

    /**
     * Inspector widget to aid in debugging
     *
     * @alias CesiumInspector
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Scene} scene The Scene instance to use.
     *
     * @exception {DeveloperError} container is required.
     * @exception {DeveloperError} scene is required.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Cesium%20Inspector.html|Cesium Sandcastle Cesium Inspector Demo}
     */
    function CesiumInspector(container, scene) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }

        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        container = getElement(container);

        var performanceContainer = document.createElement('div');

        var viewModel = new CesiumInspectorViewModel(scene, performanceContainer);
        this._viewModel = viewModel;
        this._container = container;

        var element = document.createElement('div');
        this._element = element;
        var text = document.createElement('div');
        text.textContent = 'Cesium Inspector';
        text.className = 'cesium-cesiumInspector-button';
        text.setAttribute('data-bind', 'click: toggleDropDown');
        element.appendChild(text);
        element.className = 'cesium-cesiumInspector';
        element.setAttribute('data-bind', 'css: { "cesium-cesiumInspector-visible" : dropDownVisible, "cesium-cesiumInspector-hidden" : !dropDownVisible }');
        container.appendChild(this._element);

        var panel = document.createElement('div');
        this._panel = panel;
        panel.className = 'cesium-cesiumInspector-dropDown';
        element.appendChild(panel);

        // General
        var general = document.createElement('div');
        general.className = 'cesium-cesiumInspector-sectionHeader';
        var plus = document.createElement('span');
        plus.className = 'cesium-cesiumInspector-toggleSwitch';
        plus.setAttribute('data-bind', 'click: toggleGeneral, text: generalSwitchText');
        general.appendChild(plus);
        general.appendChild(document.createTextNode('General'));
        panel.appendChild(general);

        var generalSection = document.createElement('div');
        generalSection.className = 'cesium-cesiumInspector-section';
        generalSection.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : generalVisible, "cesium-cesiumInspector-hide" : !generalVisible}');
        panel.appendChild(generalSection);

        var debugShowFrustums = document.createElement('div');
        generalSection.appendChild(debugShowFrustums);
        var frustumStats = document.createElement('div');
        frustumStats.className = 'cesium-cesiumInspector-frustumStats';
        frustumStats.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : frustums, "cesium-cesiumInspector-hide" :  !frustums}, html: frustumStatisticText');
        var frustumsCheckbox = document.createElement('input');
        frustumsCheckbox.type = 'checkbox';
        frustumsCheckbox.setAttribute('data-bind', 'checked: frustums');
        debugShowFrustums.appendChild(frustumsCheckbox);
        debugShowFrustums.appendChild(document.createTextNode('Show Frustums'));
        debugShowFrustums.appendChild(frustumStats);

        var debugShowFrustumPlanes = document.createElement('div');
        generalSection.appendChild(debugShowFrustumPlanes);
        var frustumPlanesCheckbox = document.createElement('input');
        frustumPlanesCheckbox.type = 'checkbox';
        frustumPlanesCheckbox.setAttribute('data-bind', 'checked: frustumPlanes');
        debugShowFrustumPlanes.appendChild(frustumPlanesCheckbox);
        debugShowFrustumPlanes.appendChild(document.createTextNode('Show Frustum Planes'));

        var performanceDisplay = document.createElement('div');
        generalSection.appendChild(performanceDisplay);
        var pdCheckbox = document.createElement('input');
        pdCheckbox.type = 'checkbox';
        pdCheckbox.setAttribute('data-bind', 'checked: performance');
        performanceDisplay.appendChild(pdCheckbox);
        performanceDisplay.appendChild(document.createTextNode('Performance Display'));

        performanceContainer.className = 'cesium-cesiumInspector-performanceDisplay';
        generalSection.appendChild(performanceContainer);

        var shaderCacheDisplay = document.createElement('div');
        shaderCacheDisplay.className = 'cesium-cesiumInspector-shaderCache';
        shaderCacheDisplay.setAttribute('data-bind', 'html: shaderCacheText');
        generalSection.appendChild(shaderCacheDisplay);

        var globeDepth = document.createElement('div');
        generalSection.appendChild(globeDepth);
        var gCheckbox = document.createElement('input');
        gCheckbox.type = 'checkbox';
        gCheckbox.setAttribute('data-bind', 'checked: globeDepth');
        globeDepth.appendChild(gCheckbox);
        globeDepth.appendChild(document.createTextNode('Show globe depth'));

        var globeDepthFrustum = document.createElement('div');
        globeDepth.appendChild(globeDepthFrustum);

        var pickDepth = document.createElement('div');
        generalSection.appendChild(pickDepth);
        var pCheckbox = document.createElement('input');
        pCheckbox.type = 'checkbox';
        pCheckbox.setAttribute('data-bind', 'checked: pickDepth');
        pickDepth.appendChild(pCheckbox);
        pickDepth.appendChild(document.createTextNode('Show pick depth'));

        var depthFrustum = document.createElement('div');
        generalSection.appendChild(depthFrustum);

        // Use a span with HTML binding so that we can indent with non-breaking spaces.
        var gLabel = document.createElement('span');
        gLabel.setAttribute('data-bind', 'html: "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Frustum:"');
        depthFrustum.appendChild(gLabel);

        var gText = document.createElement('span');
        gText.setAttribute('data-bind', 'text: depthFrustumText');
        depthFrustum.appendChild(gText);

        var gMinusButton = document.createElement('input');
        gMinusButton.type = 'button';
        gMinusButton.value = '-';
        gMinusButton.className = 'cesium-cesiumInspector-pickButton';
        gMinusButton.setAttribute('data-bind', 'click: decrementDepthFrustum');
        depthFrustum.appendChild(gMinusButton);

        var gPlusButton = document.createElement('input');
        gPlusButton.type = 'button';
        gPlusButton.value = '+';
        gPlusButton.className = 'cesium-cesiumInspector-pickButton';
        gPlusButton.setAttribute('data-bind', 'click: incrementDepthFrustum');
        depthFrustum.appendChild(gPlusButton);

        // Primitives
        var prim = document.createElement('div');
        prim.className = 'cesium-cesiumInspector-sectionHeader';
        plus = document.createElement('span');
        plus.className = 'cesium-cesiumInspector-toggleSwitch';
        plus.setAttribute('data-bind', 'click: togglePrimitives, text: primitivesSwitchText');
        prim.appendChild(plus);
        prim.appendChild(document.createTextNode('Primitives'));
        panel.appendChild(prim);

        var primitivesSection = document.createElement('div');
        primitivesSection.className = 'cesium-cesiumInspector-section';
        primitivesSection.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : primitivesVisible, "cesium-cesiumInspector-hide" : !primitivesVisible}');
        panel.appendChild(primitivesSection);
        var pickPrimRequired = document.createElement('div');
        pickPrimRequired.className = 'cesium-cesiumInspector-pickSection';
        primitivesSection.appendChild(pickPrimRequired);

        var pickPrimitiveButton = document.createElement('input');
        pickPrimitiveButton.type = 'button';
        pickPrimitiveButton.value = 'Pick a primitive';
        pickPrimitiveButton.className = 'cesium-cesiumInspector-pickButton';
        pickPrimitiveButton.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-pickButtonHighlight" : pickPrimitiveActive}, click: pickPrimitive');
        var buttonWrap = document.createElement('div');
        buttonWrap.className = 'cesium-cesiumInspector-center';
        buttonWrap.appendChild(pickPrimitiveButton);
        pickPrimRequired.appendChild(buttonWrap);

        var debugSphere = document.createElement('div');
        pickPrimRequired.appendChild(debugSphere);
        var bsCheckbox = document.createElement('input');
        bsCheckbox.type = 'checkbox';
        bsCheckbox.setAttribute('data-bind', 'checked: primitiveBoundingSphere, enable: hasPickedPrimitive');
        debugSphere.appendChild(bsCheckbox);
        debugSphere.appendChild(document.createTextNode('Show bounding sphere'));

        var refFrame = document.createElement('div');
        pickPrimRequired.appendChild(refFrame);
        var rfCheckbox = document.createElement('input');
        rfCheckbox.type = 'checkbox';
        rfCheckbox.setAttribute('data-bind', 'checked: primitiveReferenceFrame, enable: hasPickedPrimitive');
        refFrame.appendChild(rfCheckbox);
        refFrame.appendChild(document.createTextNode('Show reference frame'));

        var primitiveOnly = document.createElement('div');
        this._primitiveOnly = primitiveOnly;
        pickPrimRequired.appendChild(primitiveOnly);
        var primitiveOnlyCheckbox = document.createElement('input');
        primitiveOnlyCheckbox.type = 'checkbox';
        primitiveOnlyCheckbox.setAttribute('data-bind', 'checked: filterPrimitive, enable: hasPickedPrimitive');
        primitiveOnly.appendChild(primitiveOnlyCheckbox);
        primitiveOnly.appendChild(document.createTextNode('Show only selected'));

        // Terrain
        var terrain = document.createElement('div');
        terrain.className = 'cesium-cesiumInspector-sectionHeader';
        plus = document.createElement('span');
        plus.className = 'cesium-cesiumInspector-toggleSwitch';
        plus.setAttribute('data-bind', 'click: toggleTerrain, text: terrainSwitchText');
        terrain.appendChild(plus);
        terrain.appendChild(document.createTextNode('Terrain'));
        panel.appendChild(terrain);

        var terrainSection = document.createElement('div');
        terrainSection.className = 'cesium-cesiumInspector-section';
        terrainSection.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : terrainVisible, "cesium-cesiumInspector-hide" :  !terrainVisible}');
        panel.appendChild(terrainSection);
        var pickTileRequired = document.createElement('div');
        pickTileRequired.className = 'cesium-cesiumInspector-pickSection';
        terrainSection.appendChild(pickTileRequired);
        var pickTileButton = document.createElement('input');
        pickTileButton.type = 'button';
        pickTileButton.value = 'Pick a tile';
        pickTileButton.className = 'cesium-cesiumInspector-pickButton';
        pickTileButton.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-pickButtonHighlight" : pickTileActive}, click: pickTile');
        buttonWrap = document.createElement('div');
        buttonWrap.appendChild(pickTileButton);
        buttonWrap.className = 'cesium-cesiumInspector-center';
        pickTileRequired.appendChild(buttonWrap);
        var tileInfo = document.createElement('div');
        pickTileRequired.appendChild(tileInfo);
        var parentTile = document.createElement('input');
        parentTile.type = 'button';
        parentTile.value = 'Parent';
        parentTile.className = 'cesium-cesiumInspector-pickButton';
        parentTile.setAttribute('data-bind', 'click: selectParent');
        var nwTile = document.createElement('input');
        nwTile.type = 'button';
        nwTile.value = 'NW';
        nwTile.className = 'cesium-cesiumInspector-pickButton';
        nwTile.setAttribute('data-bind', 'click: selectNW');
        var neTile = document.createElement('input');
        neTile.type = 'button';
        neTile.value = 'NE';
        neTile.className = 'cesium-cesiumInspector-pickButton';
        neTile.setAttribute('data-bind', 'click: selectNE');
        var swTile = document.createElement('input');
        swTile.type = 'button';
        swTile.value = 'SW';
        swTile.className = 'cesium-cesiumInspector-pickButton';
        swTile.setAttribute('data-bind', 'click: selectSW');
        var seTile = document.createElement('input');
        seTile.type = 'button';
        seTile.value = 'SE';
        seTile.className = 'cesium-cesiumInspector-pickButton';
        seTile.setAttribute('data-bind', 'click: selectSE');

        var tileText = document.createElement('div');
        tileText.className = 'cesium-cesiumInspector-tileText';
        tileInfo.className = 'cesium-cesiumInspector-frustumStats';
        tileInfo.appendChild(tileText);
        tileInfo.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : hasPickedTile, "cesium-cesiumInspector-hide" :  !hasPickedTile}');
        tileText.setAttribute('data-bind', 'html: tileText');

        var relativeText = document.createElement('div');
        relativeText.className = 'cesium-cesiumInspector-relativeText';
        relativeText.textContent = 'Select relative:';
        tileInfo.appendChild(relativeText);

        var table = document.createElement('table');
        var tr1 = document.createElement('tr');
        var tr2 = document.createElement('tr');
        var td1 = document.createElement('td');
        td1.appendChild(parentTile);
        var td2 = document.createElement('td');
        td2.appendChild(nwTile);
        var td3 = document.createElement('td');
        td3.appendChild(neTile);
        tr1.appendChild(td1);
        tr1.appendChild(td2);
        tr1.appendChild(td3);
        var td4 = document.createElement('td');
        var td5 = document.createElement('td');
        td5.appendChild(swTile);
        var td6 = document.createElement('td');
        td6.appendChild(seTile);
        tr2.appendChild(td4);
        tr2.appendChild(td5);
        tr2.appendChild(td6);
        table.appendChild(tr1);
        table.appendChild(tr2);

        tileInfo.appendChild(table);

        var tileBoundingSphere = document.createElement('div');
        pickTileRequired.appendChild(tileBoundingSphere);
        var tbsCheck = document.createElement('input');
        tbsCheck.type = 'checkbox';
        tbsCheck.setAttribute('data-bind', 'checked: tileBoundingSphere, enable: hasPickedTile');
        tileBoundingSphere.appendChild(tbsCheck);
        tileBoundingSphere.appendChild(document.createTextNode('Show bounding volume'));

        var renderTile = document.createElement('div');
        pickTileRequired.appendChild(renderTile);
        var rCheck = document.createElement('input');
        rCheck.type = 'checkbox';
        rCheck.setAttribute('data-bind', 'checked: filterTile, enable: hasPickedTile');
        renderTile.appendChild(rCheck);
        renderTile.appendChild(document.createTextNode('Show only selected'));

        var wireframe = document.createElement('div');
        terrainSection.appendChild(wireframe);
        var wCheckbox = document.createElement('input');
        wCheckbox.type = 'checkbox';
        wCheckbox.setAttribute('data-bind', 'checked: wireframe');
        wireframe.appendChild(wCheckbox);
        wireframe.appendChild(document.createTextNode('Wireframe'));

        var suspendUpdates = document.createElement('div');
        terrainSection.appendChild(suspendUpdates);
        var upCheckbox = document.createElement('input');
        upCheckbox.type = 'checkbox';
        upCheckbox.setAttribute('data-bind', 'checked: suspendUpdates');
        suspendUpdates.appendChild(upCheckbox);
        suspendUpdates.appendChild(document.createTextNode('Suspend LOD update'));

        var tileCoords = document.createElement('div');
        terrainSection.appendChild(tileCoords);
        var coordCheck = document.createElement('input');
        coordCheck.type = 'checkbox';
        coordCheck.setAttribute('data-bind', 'checked: tileCoordinates');
        tileCoords.appendChild(coordCheck);
        tileCoords.appendChild(document.createTextNode('Show tile coordinates'));

        knockout.applyBindings(viewModel, this._element);
    }

    defineProperties(CesiumInspector.prototype, {
        /**
         * Gets the parent container.
         * @memberof CesiumInspector.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof CesiumInspector.prototype
         *
         * @type {CesiumInspectorViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    CesiumInspector.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    CesiumInspector.prototype.destroy = function() {
        knockout.cleanNode(this._element);
        this._container.removeChild(this._element);
        this.viewModel.destroy();

        return destroyObject(this);
    };

    return CesiumInspector;
});

/*global define*/
define('Widgets/CesiumWidget/CesiumWidget',[
        '../../Core/buildModuleUrl',
        '../../Core/Cartesian3',
        '../../Core/Clock',
        '../../Core/Credit',
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/Ellipsoid',
        '../../Core/FeatureDetection',
        '../../Core/formatError',
        '../../Core/requestAnimationFrame',
        '../../Core/ScreenSpaceEventHandler',
        '../../Scene/BingMapsImageryProvider',
        '../../Scene/Globe',
        '../../Scene/Moon',
        '../../Scene/Scene',
        '../../Scene/SceneMode',
        '../../Scene/ShadowMode',
        '../../Scene/SkyAtmosphere',
        '../../Scene/SkyBox',
        '../../Scene/Sun',
        '../getElement'
    ], function(
        buildModuleUrl,
        Cartesian3,
        Clock,
        Credit,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Ellipsoid,
        FeatureDetection,
        formatError,
        requestAnimationFrame,
        ScreenSpaceEventHandler,
        BingMapsImageryProvider,
        Globe,
        Moon,
        Scene,
        SceneMode,
        ShadowMode,
        SkyAtmosphere,
        SkyBox,
        Sun,
        getElement) {
    'use strict';

    function getDefaultSkyBoxUrl(suffix) {
        return buildModuleUrl('Assets/Textures/SkyBox/tycho2t3_80_' + suffix + '.jpg');
    }

    function startRenderLoop(widget) {
        widget._renderLoopRunning = true;

        var lastFrameTime = 0;
        function render(frameTime) {
            if (widget.isDestroyed()) {
                return;
            }

            if (widget._useDefaultRenderLoop) {
                try {
                    var targetFrameRate = widget._targetFrameRate;
                    if (!defined(targetFrameRate)) {
                        widget.resize();
                        widget.render();
                        requestAnimationFrame(render);
                    } else {
                        var interval = 1000.0 / targetFrameRate;
                        var delta = frameTime - lastFrameTime;

                        if (delta > interval) {
                            widget.resize();
                            widget.render();
                            lastFrameTime = frameTime - (delta % interval);
                        }
                        requestAnimationFrame(render);
                    }
                } catch (error) {
                    widget._useDefaultRenderLoop = false;
                    widget._renderLoopRunning = false;
                    if (widget._showRenderLoopErrors) {
                        var title = 'An error occurred while rendering.  Rendering has stopped.';
                        widget.showErrorPanel(title, undefined, error);
                    }
                }
            } else {
                widget._renderLoopRunning = false;
            }
        }

        requestAnimationFrame(render);
    }

    function configureCanvasSize(widget) {
        var canvas = widget._canvas;
        var width = canvas.clientWidth;
        var height = canvas.clientHeight;
        var resolutionScale = widget._resolutionScale;
        if (!widget._supportsImageRenderingPixelated) {
            resolutionScale *= defaultValue(window.devicePixelRatio, 1.0);
        }

        widget._canvasWidth = width;
        widget._canvasHeight = height;

        width *= resolutionScale;
        height *= resolutionScale;

        canvas.width = width;
        canvas.height = height;

        widget._canRender = width !== 0 && height !== 0;
    }

    function configureCameraFrustum(widget) {
        var canvas = widget._canvas;
        var width = canvas.width;
        var height = canvas.height;
        if (width !== 0 && height !== 0) {
            var frustum = widget._scene.camera.frustum;
            if (defined(frustum.aspectRatio)) {
                frustum.aspectRatio = width / height;
            } else {
                frustum.top = frustum.right * (height / width);
                frustum.bottom = -frustum.top;
            }
        }
    }

    var cesiumLogoData = 'data:image/png;base64, ';

    /**
     * A widget containing a Cesium scene.
     *
     * @alias CesiumWidget
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Object} [options] Object with the following properties:
     * @param {Clock} [options.clock=new Clock()] The clock to use to control current time.
     * @param {ImageryProvider} [options.imageryProvider=new BingMapsImageryProvider()] The imagery provider to serve as the base layer. If set to <code>false</code>, no imagery provider will be added.
     * @param {TerrainProvider} [options.terrainProvider=new EllipsoidTerrainProvider] The terrain provider.
     * @param {SkyBox} [options.skyBox] The skybox used to render the stars.  When <code>undefined</code>, the default stars are used. If set to <code>false</code>, no skyBox, Sun, or Moon will be added.
     * @param {SkyAtmosphere} [options.skyAtmosphere] Blue sky, and the glow around the Earth's limb.  Set to <code>false</code> to turn it off.
     * @param {SceneMode} [options.sceneMode=SceneMode.SCENE3D] The initial scene mode.
     * @param {Boolean} [options.scene3DOnly=false] When <code>true</code>, each geometry instance will only be rendered in 3D to save GPU memory.
     * @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
     * @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
     * @param {Globe} [options.globe=new Globe(mapProjection.ellipsoid)] The globe to use in the scene.  If set to <code>false</code>, no globe will be added.
     * @param {Boolean} [options.useDefaultRenderLoop=true] True if this widget should control the render loop, false otherwise.
     * @param {Number} [options.targetFrameRate] The target frame rate when using the default render loop.
     * @param {Boolean} [options.showRenderLoopErrors=true] If true, this widget will automatically display an HTML panel to the user containing the error, if a render loop error occurs.
     * @param {Object} [options.contextOptions] Context and WebGL creation properties corresponding to <code>options</code> passed to {@link Scene}.
     * @param {Element|String} [options.creditContainer] The DOM element or ID that will contain the {@link CreditDisplay}.  If not specified, the credits are added
     *        to the bottom of the widget itself.
     * @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
     * @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
     * @param {ShadowMode} [options.terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from the sun.
     * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Cesium%20Widget.html|Cesium Sandcastle Cesium Widget Demo}
     *
     * @example
     * // For each example, include a link to CesiumWidget.css stylesheet in HTML head,
     * // and in the body, include: <div id="cesiumContainer"></div>
     *
     * //Widget with no terrain and default Bing Maps imagery provider.
     * var widget = new Cesium.CesiumWidget('cesiumContainer');
     *
     * //Widget with OpenStreetMaps imagery provider and Cesium terrain provider hosted by AGI.
     * var widget = new Cesium.CesiumWidget('cesiumContainer', {
     *     imageryProvider : Cesium.createOpenStreetMapImageryProvider(),
     *     terrainProvider : new Cesium.CesiumTerrainProvider({
     *         url : 'https://assets.agi.com/stk-terrain/world'
     *     }),
     *     // Use high-res stars downloaded from https://github.com/AnalyticalGraphicsInc/cesium-assets
     *     skyBox : new Cesium.SkyBox({
     *         sources : {
     *           positiveX : 'stars/TychoSkymapII.t3_08192x04096_80_px.jpg',
     *           negativeX : 'stars/TychoSkymapII.t3_08192x04096_80_mx.jpg',
     *           positiveY : 'stars/TychoSkymapII.t3_08192x04096_80_py.jpg',
     *           negativeY : 'stars/TychoSkymapII.t3_08192x04096_80_my.jpg',
     *           positiveZ : 'stars/TychoSkymapII.t3_08192x04096_80_pz.jpg',
     *           negativeZ : 'stars/TychoSkymapII.t3_08192x04096_80_mz.jpg'
     *         }
     *     }),
     *     // Show Columbus View map with Web Mercator projection
     *     sceneMode : Cesium.SceneMode.COLUMBUS_VIEW,
     *     mapProjection : new Cesium.WebMercatorProjection()
     * });
     */
    function CesiumWidget(container, options) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        container = getElement(container);

        options = defaultValue(options, {});

        //Configure the widget DOM elements
        var element = document.createElement('div');
        element.className = 'cesium-widget';
        container.appendChild(element);

        var canvas = document.createElement('canvas');
        var supportsImageRenderingPixelated = FeatureDetection.supportsImageRenderingPixelated();
        this._supportsImageRenderingPixelated = supportsImageRenderingPixelated;
        if (supportsImageRenderingPixelated) {
            canvas.style.imageRendering = FeatureDetection.imageRenderingValue();
        }

        canvas.oncontextmenu = function() {
            return false;
        };
        canvas.onselectstart = function() {
            return false;
        };
        element.appendChild(canvas);

        var creditContainer = document.createElement('div');
        creditContainer.className = 'cesium-widget-credits';

        var creditContainerContainer = defined(options.creditContainer) ? getElement(options.creditContainer) : element;
        creditContainerContainer.appendChild(creditContainer);

        var showRenderLoopErrors = defaultValue(options.showRenderLoopErrors, true);

        this._element = element;
        this._container = container;
        this._canvas = canvas;
        this._canvasWidth = 0;
        this._canvasHeight = 0;
        this._creditContainer = creditContainer;
        this._canRender = false;
        this._renderLoopRunning = false;
        this._showRenderLoopErrors = showRenderLoopErrors;
        this._resolutionScale = 1.0;
        this._forceResize = false;
        this._clock = defined(options.clock) ? options.clock : new Clock();

        configureCanvasSize(this);

        try {
            var scene = new Scene({
                canvas : canvas,
                contextOptions : options.contextOptions,
                creditContainer : creditContainer,
                mapProjection : options.mapProjection,
                orderIndependentTranslucency : options.orderIndependentTranslucency,
                scene3DOnly : defaultValue(options.scene3DOnly, false),
                terrainExaggeration : options.terrainExaggeration,
                shadows : options.shadows,
                mapMode2D : options.mapMode2D
            });
            this._scene = scene;

            scene.camera.constrainedAxis = Cartesian3.UNIT_Z;

            configureCameraFrustum(this);

            var ellipsoid = defaultValue(scene.mapProjection.ellipsoid, Ellipsoid.WGS84);
            var creditDisplay = scene.frameState.creditDisplay;

            var cesiumCredit = new Credit('Cesium', cesiumLogoData, 'http://cesiumjs.org/');
            creditDisplay.addDefaultCredit(cesiumCredit);

            var globe = options.globe;
            if (!defined(globe)) {
                globe = new Globe(ellipsoid);
            }
            if (globe !== false) {
                scene.globe = globe;
                scene.globe.shadows = defaultValue(options.terrainShadows, ShadowMode.RECEIVE_ONLY);
            }

            var skyBox = options.skyBox;
            if (!defined(skyBox)) {
                skyBox = new SkyBox({
                    sources : {
                        positiveX : getDefaultSkyBoxUrl('px'),
                        negativeX : getDefaultSkyBoxUrl('mx'),
                        positiveY : getDefaultSkyBoxUrl('py'),
                        negativeY : getDefaultSkyBoxUrl('my'),
                        positiveZ : getDefaultSkyBoxUrl('pz'),
                        negativeZ : getDefaultSkyBoxUrl('mz')
                    }
                });
            }
            if (skyBox !== false) {
                scene.skyBox = skyBox;
                scene.sun = new Sun();
                scene.moon = new Moon();
            }

            // Blue sky, and the glow around the Earth's limb.
            var skyAtmosphere = options.skyAtmosphere;
            if (!defined(skyAtmosphere)) {
                skyAtmosphere = new SkyAtmosphere(ellipsoid);
            }
            if (skyAtmosphere !== false) {
                scene.skyAtmosphere = skyAtmosphere;
            }

            //Set the base imagery layer
            var imageryProvider = (options.globe === false) ? false : options.imageryProvider;
            if (!defined(imageryProvider)) {
                imageryProvider = new BingMapsImageryProvider({
                    url : 'https://dev.virtualearth.net'
                });
            }

            if (imageryProvider !== false) {
                scene.imageryLayers.addImageryProvider(imageryProvider);
            }

            //Set the terrain provider if one is provided.
            if (defined(options.terrainProvider) && options.globe !== false) {
                scene.terrainProvider = options.terrainProvider;
            }

            this._screenSpaceEventHandler = new ScreenSpaceEventHandler(canvas, false);

            if (defined(options.sceneMode)) {
                if (options.sceneMode === SceneMode.SCENE2D) {
                    this._scene.morphTo2D(0);
                }
                if (options.sceneMode === SceneMode.COLUMBUS_VIEW) {
                    this._scene.morphToColumbusView(0);
                }
            }

            this._useDefaultRenderLoop = undefined;
            this.useDefaultRenderLoop = defaultValue(options.useDefaultRenderLoop, true);

            this._targetFrameRate = undefined;
            this.targetFrameRate = options.targetFrameRate;

            var that = this;
            scene.renderError.addEventListener(function(scene, error) {
                that._useDefaultRenderLoop = false;
                that._renderLoopRunning = false;
                if (that._showRenderLoopErrors) {
                    var title = 'An error occurred while rendering.  Rendering has stopped.';
                    that.showErrorPanel(title, undefined, error);
                }
            });
        } catch (error) {
            if (showRenderLoopErrors) {
                var title = 'Error constructing CesiumWidget.';
                var message = 'Visit <a href="http://get.webgl.org">http://get.webgl.org</a> to verify that your web browser and hardware support WebGL.  Consider trying a different web browser or updating your video drivers.  Detailed error information is below:';
                this.showErrorPanel(title, message, error);
            }
            throw error;
        }
    }

    defineProperties(CesiumWidget.prototype, {
        /**
         * Gets the parent container.
         * @memberof CesiumWidget.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the canvas.
         * @memberof CesiumWidget.prototype
         *
         * @type {Canvas}
         */
        canvas : {
            get : function() {
                return this._canvas;
            }
        },

        /**
         * Gets the credit container.
         * @memberof CesiumWidget.prototype
         *
         * @type {Element}
         */
        creditContainer: {
            get : function() {
                return this._creditContainer;
            }
        },

        /**
         * Gets the scene.
         * @memberof CesiumWidget.prototype
         *
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets the collection of image layers that will be rendered on the globe.
         * @memberof CesiumWidget.prototype
         *
         * @type {ImageryLayerCollection}
         * @readonly
         */
        imageryLayers : {
            get : function() {
                return this._scene.imageryLayers;
            }
        },

        /**
         * The terrain provider providing surface geometry for the globe.
         * @memberof CesiumWidget.prototype
         *
         * @type {TerrainProvider}
         */
        terrainProvider : {
            get : function() {
                return this._scene.terrainProvider;
            },
            set : function(terrainProvider) {
                this._scene.terrainProvider = terrainProvider;
            }
        },

        /**
         * Gets the camera.
         * @memberof CesiumWidget.prototype
         *
         * @type {Camera}
         * @readonly
         */
        camera : {
            get : function() {
                return this._scene.camera;
            }
        },

        /**
         * Gets the clock.
         * @memberof CesiumWidget.prototype
         *
         * @type {Clock}
         */
        clock : {
            get : function() {
                return this._clock;
            }
        },

        /**
         * Gets the screen space event handler.
         * @memberof CesiumWidget.prototype
         *
         * @type {ScreenSpaceEventHandler}
         */
        screenSpaceEventHandler : {
            get : function() {
                return this._screenSpaceEventHandler;
            }
        },

        /**
         * Gets or sets the target frame rate of the widget when <code>useDefaultRenderLoop</code>
         * is true. If undefined, the browser's {@link requestAnimationFrame} implementation
         * determines the frame rate.  If defined, this value must be greater than 0.  A value higher
         * than the underlying requestAnimationFrame implementation will have no effect.
         * @memberof CesiumWidget.prototype
         *
         * @type {Number}
         */
        targetFrameRate : {
            get : function() {
                return this._targetFrameRate;
            },
            set : function(value) {
                                if (value <= 0) {
                    throw new DeveloperError('targetFrameRate must be greater than 0, or undefined.');
                }
                                this._targetFrameRate = value;
            }
        },

        /**
         * Gets or sets whether or not this widget should control the render loop.
         * If set to true the widget will use {@link requestAnimationFrame} to
         * perform rendering and resizing of the widget, as well as drive the
         * simulation clock. If set to false, you must manually call the
         * <code>resize</code>, <code>render</code> methods as part of a custom
         * render loop.  If an error occurs during rendering, {@link Scene}'s
         * <code>renderError</code> event will be raised and this property
         * will be set to false.  It must be set back to true to continue rendering
         * after the error.
         * @memberof CesiumWidget.prototype
         *
         * @type {Boolean}
         */
        useDefaultRenderLoop : {
            get : function() {
                return this._useDefaultRenderLoop;
            },
            set : function(value) {
                if (this._useDefaultRenderLoop !== value) {
                    this._useDefaultRenderLoop = value;
                    if (value && !this._renderLoopRunning) {
                        startRenderLoop(this);
                    }
                }
            }
        },

        /**
         * Gets or sets a scaling factor for rendering resolution.  Values less than 1.0 can improve
         * performance on less powerful devices while values greater than 1.0 will render at a higher
         * resolution and then scale down, resulting in improved visual fidelity.
         * For example, if the widget is laid out at a size of 640x480, setting this value to 0.5
         * will cause the scene to be rendered at 320x240 and then scaled up while setting
         * it to 2.0 will cause the scene to be rendered at 1280x960 and then scaled down.
         * @memberof CesiumWidget.prototype
         *
         * @type {Number}
         * @default 1.0
         */
        resolutionScale : {
            get : function() {
                return this._resolutionScale;
            },
            set : function(value) {
                                if (value <= 0) {
                    throw new DeveloperError('resolutionScale must be greater than 0.');
                }
                                this._resolutionScale = value;
                this._forceResize = true;
            }
        }
    });

    /**
     * Show an error panel to the user containing a title and a longer error message,
     * which can be dismissed using an OK button.  This panel is displayed automatically
     * when a render loop error occurs, if showRenderLoopErrors was not false when the
     * widget was constructed.
     *
     * @param {String} title The title to be displayed on the error panel.  This string is interpreted as text.
     * @param {String} message A helpful, user-facing message to display prior to the detailed error information.  This string is interpreted as HTML.
     * @param {String} [error] The error to be displayed on the error panel.  This string is formatted using {@link formatError} and then displayed as text.
     */
    CesiumWidget.prototype.showErrorPanel = function(title, message, error) {
        var element = this._element;
        var overlay = document.createElement('div');
        overlay.className = 'cesium-widget-errorPanel';

        var content = document.createElement('div');
        content.className = 'cesium-widget-errorPanel-content';
        overlay.appendChild(content);

        var errorHeader = document.createElement('div');
        errorHeader.className = 'cesium-widget-errorPanel-header';
        errorHeader.appendChild(document.createTextNode(title));
        content.appendChild(errorHeader);

        var errorPanelScroller = document.createElement('div');
        errorPanelScroller.className = 'cesium-widget-errorPanel-scroll';
        content.appendChild(errorPanelScroller);
        function resizeCallback() {
            errorPanelScroller.style.maxHeight = Math.max(Math.round(element.clientHeight * 0.9 - 100), 30) + 'px';
        }
        resizeCallback();
        if (defined(window.addEventListener)) {
            window.addEventListener('resize', resizeCallback, false);
        }

        if (defined(message)) {
            var errorMessage = document.createElement('div');
            errorMessage.className = 'cesium-widget-errorPanel-message';
            errorMessage.innerHTML = '<p>' + message + '</p>';
            errorPanelScroller.appendChild(errorMessage);
        }

        var errorDetails = '(no error details available)';
        if (defined(error)) {
            errorDetails = formatError(error);
        }

        var errorMessageDetails = document.createElement('div');
        errorMessageDetails.className = 'cesium-widget-errorPanel-message';
        errorMessageDetails.appendChild(document.createTextNode(errorDetails));
        errorPanelScroller.appendChild(errorMessageDetails);

        var buttonPanel = document.createElement('div');
        buttonPanel.className = 'cesium-widget-errorPanel-buttonPanel';
        content.appendChild(buttonPanel);

        var okButton = document.createElement('button');
        okButton.setAttribute('type', 'button');
        okButton.className = 'cesium-button';
        okButton.appendChild(document.createTextNode('OK'));
        okButton.onclick = function() {
            if (defined(resizeCallback) && defined(window.removeEventListener)) {
                window.removeEventListener('resize', resizeCallback, false);
            }
            element.removeChild(overlay);
        };

        buttonPanel.appendChild(okButton);

        element.appendChild(overlay);

        //IE8 does not have a console object unless the dev tools are open.
        if (typeof console !== 'undefined') {
            console.error(title + '\n' + message + '\n' + errorDetails);
        }
    };

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    CesiumWidget.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    CesiumWidget.prototype.destroy = function() {
        this._scene = this._scene && this._scene.destroy();
        this._container.removeChild(this._element);
        destroyObject(this);
    };

    /**
     * Updates the canvas size, camera aspect ratio, and viewport size.
     * This function is called automatically as needed unless
     * <code>useDefaultRenderLoop</code> is set to false.
     */
    CesiumWidget.prototype.resize = function() {
        var canvas = this._canvas;
        var width = canvas.clientWidth;
        var height = canvas.clientHeight;
        if (!this._forceResize && this._canvasWidth === width && this._canvasHeight === height) {
            return;
        }
        this._forceResize = false;

        configureCanvasSize(this);
        configureCameraFrustum(this);
    };

    /**
     * Renders the scene.  This function is called automatically
     * unless <code>useDefaultRenderLoop</code> is set to false;
     */
    CesiumWidget.prototype.render = function() {
        if (this._canRender) {
            this._scene.initializeFrame();
            var currentTime = this._clock.tick();
            this._scene.render(currentTime);
        } else {
            this._clock.tick();
        }
    };

    return CesiumWidget;
});

/*global define*/
define('Widgets/ClockViewModel',[
        '../Core/Clock',
        '../Core/defined',
        '../Core/defineProperties',
        '../Core/destroyObject',
        '../Core/EventHelper',
        '../Core/JulianDate',
        '../ThirdParty/knockout'
    ], function(
        Clock,
        defined,
        defineProperties,
        destroyObject,
        EventHelper,
        JulianDate,
        knockout) {
    'use strict';

    /**
     * A view model which exposes a {@link Clock} for user interfaces.
     * @alias ClockViewModel
     * @constructor
     *
     * @param {Clock} [clock] The clock object wrapped by this view model, if undefined a new instance will be created.
     *
     * @see Clock
     */
    function ClockViewModel(clock) {
        if (!defined(clock)) {
            clock = new Clock();
        }
        this._clock = clock;

        this._eventHelper = new EventHelper();
        this._eventHelper.add(clock.onTick, this.synchronize, this);

        /**
         * Gets the current system time.
         * This property is observable.
         * @type {JulianDate}
         */
        this.systemTime = knockout.observable(JulianDate.now());
        this.systemTime.equalityComparer = JulianDate.equals;

        /**
         * Gets or sets the start time of the clock.
         * See {@link Clock#startTime}.
         * This property is observable.
         * @type {JulianDate}
         */
        this.startTime = knockout.observable(clock.startTime);
        this.startTime.equalityComparer = JulianDate.equals;
        this.startTime.subscribe(function(value) {
            clock.startTime = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets the stop time of the clock.
         * See {@link Clock#stopTime}.
         * This property is observable.
         * @type {JulianDate}
         */
        this.stopTime = knockout.observable(clock.stopTime);
        this.stopTime.equalityComparer = JulianDate.equals;
        this.stopTime.subscribe(function(value) {
            clock.stopTime = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets the current time.
         * See {@link Clock#currentTime}.
         * This property is observable.
         * @type {JulianDate}
         */
        this.currentTime = knockout.observable(clock.currentTime);
        this.currentTime.equalityComparer = JulianDate.equals;
        this.currentTime.subscribe(function(value) {
            clock.currentTime = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets the clock multiplier.
         * See {@link Clock#multiplier}.
         * This property is observable.
         * @type {Number}
         */
        this.multiplier = knockout.observable(clock.multiplier);
        this.multiplier.subscribe(function(value) {
            clock.multiplier = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets the clock step setting.
         * See {@link Clock#clockStep}.
         * This property is observable.
         * @type {ClockStep}
         */
        this.clockStep = knockout.observable(clock.clockStep);
        this.clockStep.subscribe(function(value) {
            clock.clockStep = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets the clock range setting.
         * See {@link Clock#clockRange}.
         * This property is observable.
         * @type {ClockRange}
         */
        this.clockRange = knockout.observable(clock.clockRange);
        this.clockRange.subscribe(function(value) {
            clock.clockRange = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets whether the clock can animate.
         * See {@link Clock#canAnimate}.
         * This property is observable.
         * @type {Boolean}
         */
        this.canAnimate = knockout.observable(clock.canAnimate);
        this.canAnimate.subscribe(function(value) {
            clock.canAnimate = value;
            this.synchronize();
        }, this);

        /**
         * Gets or sets whether the clock should animate.
         * See {@link Clock#shouldAnimate}.
         * This property is observable.
         * @type {Boolean}
         */
        this.shouldAnimate = knockout.observable(clock.shouldAnimate);
        this.shouldAnimate.subscribe(function(value) {
            clock.shouldAnimate = value;
            this.synchronize();
        }, this);

        knockout.track(this, ['systemTime', 'startTime', 'stopTime', 'currentTime', 'multiplier', 'clockStep', 'clockRange', 'canAnimate', 'shouldAnimate']);
    }

    defineProperties(ClockViewModel.prototype, {
        /**
         * Gets the underlying Clock.
         * @memberof ClockViewModel.prototype
         * @type {Clock}
         */
        clock : {
            get : function() {
                return this._clock;
            }
        }
    });

    /**
     * Updates the view model with the contents of the underlying clock.
     * Can be called to force an update of the viewModel if the underlying
     * clock has changed and <code>Clock.tick</code> has not yet been called.
     */
    ClockViewModel.prototype.synchronize = function() {
        var clock = this._clock;

        this.systemTime = JulianDate.now();
        this.startTime = clock.startTime;
        this.stopTime = clock.stopTime;
        this.currentTime = clock.currentTime;
        this.multiplier = clock.multiplier;
        this.clockStep = clock.clockStep;
        this.clockRange = clock.clockRange;
        this.canAnimate = clock.canAnimate;
        this.shouldAnimate = clock.shouldAnimate;
    };

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    ClockViewModel.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the view model.  Should be called to
     * properly clean up the view model when it is no longer needed.
     */
    ClockViewModel.prototype.destroy = function() {
        this._eventHelper.removeAll();

        destroyObject(this);
    };

    return ClockViewModel;
});

/*global define*/
define('Widgets/Command',[
        '../Core/DeveloperError'
    ], function(
        DeveloperError) {
    'use strict';

    /**
     * A Command is a function with an extra <code>canExecute</code> observable property to determine
     * whether the command can be executed.  When executed, a Command function will check the
     * value of <code>canExecute</code> and throw if false.
     *
     * This type describes an interface and is not intended to be instantiated directly.
     * See {@link createCommand} to create a command from a function.
     *
     * @alias Command
     * @constructor
     */
    function Command() {
        /**
         * Gets whether this command can currently be executed.  This property is observable.
         * @type {Boolean}
         * @default undefined
         */
        this.canExecute = undefined;

        /**
         * Gets an event which is raised before the command executes, the event
         * is raised with an object containing two properties: a <code>cancel</code> property,
         * which if set to false by the listener will prevent the command from being executed, and
         * an <code>args</code> property, which is the array of arguments being passed to the command.
         * @type {Event}
         * @default undefined
         */
        this.beforeExecute = undefined;

        /**
         * Gets an event which is raised after the command executes, the event
         * is raised with the return value of the command as its only parameter.
         * @type {Event}
         * @default undefined
         */
        this.afterExecute = undefined;

        DeveloperError.throwInstantiationError();
    }

    return Command;
});

/*global define*/
define('Widgets/FullscreenButton/FullscreenButtonViewModel',[
        '../../Core/defaultValue',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/Fullscreen',
        '../../ThirdParty/knockout',
        '../createCommand',
        '../getElement'
    ], function(
        defaultValue,
        defineProperties,
        destroyObject,
        DeveloperError,
        Fullscreen,
        knockout,
        createCommand,
        getElement) {
    'use strict';

    /**
     * The view model for {@link FullscreenButton}.
     * @alias FullscreenButtonViewModel
     * @constructor
     *
     * @param {Element|String} [fullscreenElement=document.body] The element or id to be placed into fullscreen mode.
     */
    function FullscreenButtonViewModel(fullscreenElement) {
        var that = this;

        var tmpIsFullscreen = knockout.observable(Fullscreen.fullscreen);
        var tmpIsEnabled = knockout.observable(Fullscreen.enabled);

        /**
         * Gets whether or not fullscreen mode is active.  This property is observable.
         *
         * @type {Boolean}
         */
        this.isFullscreen = undefined;
        knockout.defineProperty(this, 'isFullscreen', {
            get : function() {
                return tmpIsFullscreen();
            }
        });

        /**
         * Gets or sets whether or not fullscreen functionality should be enabled.  This property is observable.
         *
         * @type {Boolean}
         * @see Fullscreen.enabled
         */
        this.isFullscreenEnabled = undefined;
        knockout.defineProperty(this, 'isFullscreenEnabled', {
            get : function() {
                return tmpIsEnabled();
            },
            set : function(value) {
                tmpIsEnabled(value && Fullscreen.enabled);
            }
        });

        /**
         * Gets the tooltip.  This property is observable.
         *
         * @type {String}
         */
        this.tooltip = undefined;
        knockout.defineProperty(this, 'tooltip', function() {
            if (!this.isFullscreenEnabled) {
                return 'Full screen unavailable';
            }
            return tmpIsFullscreen() ? 'Exit full screen' : 'Full screen';
        });

        this._command = createCommand(function() {
            if (Fullscreen.fullscreen) {
                Fullscreen.exitFullscreen();
            } else {
                Fullscreen.requestFullscreen(that._fullscreenElement);
            }
        }, knockout.getObservable(this, 'isFullscreenEnabled'));

        this._fullscreenElement = defaultValue(getElement(fullscreenElement), document.body);

        this._callback = function() {
            tmpIsFullscreen(Fullscreen.fullscreen);
        };
        document.addEventListener(Fullscreen.changeEventName, this._callback);
    }

    defineProperties(FullscreenButtonViewModel.prototype, {
        /**
         * Gets or sets the HTML element to place into fullscreen mode when the
         * corresponding button is pressed.
         * @memberof FullscreenButtonViewModel.prototype
         *
         * @type {Element}
         */
        fullscreenElement : {
            //TODO:@exception {DeveloperError} value must be a valid HTML Element.
            get : function() {
                return this._fullscreenElement;
            },
            set : function(value) {
                                if (!(value instanceof Element)) {
                    throw new DeveloperError('value must be a valid Element.');
                }
                
                this._fullscreenElement = value;
            }
        },

        /**
         * Gets the Command to toggle fullscreen mode.
         * @memberof FullscreenButtonViewModel.prototype
         *
         * @type {Command}
         */
        command : {
            get : function() {
                return this._command;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    FullscreenButtonViewModel.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the view model.  Should be called to
     * properly clean up the view model when it is no longer needed.
     */
    FullscreenButtonViewModel.prototype.destroy = function() {
        document.removeEventListener(Fullscreen.changeEventName, this._callback);
        destroyObject(this);
    };

    return FullscreenButtonViewModel;
});

/*global define*/
define('Widgets/FullscreenButton/FullscreenButton',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        './FullscreenButtonViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        FullscreenButtonViewModel) {
    'use strict';

    var enterFullScreenPath = 'M 83.96875 17.5625 L 83.96875 17.59375 L 76.65625 24.875 L 97.09375 24.96875 L 76.09375 45.96875 L 81.9375 51.8125 L 102.78125 30.9375 L 102.875 51.15625 L 110.15625 43.875 L 110.1875 17.59375 L 83.96875 17.5625 z M 44.125 17.59375 L 17.90625 17.625 L 17.9375 43.90625 L 25.21875 51.1875 L 25.3125 30.96875 L 46.15625 51.8125 L 52 45.96875 L 31 25 L 51.4375 24.90625 L 44.125 17.59375 z M 46.0625 76.03125 L 25.1875 96.875 L 25.09375 76.65625 L 17.8125 83.9375 L 17.8125 110.21875 L 44 110.25 L 51.3125 102.9375 L 30.90625 102.84375 L 51.875 81.875 L 46.0625 76.03125 z M 82 76.15625 L 76.15625 82 L 97.15625 103 L 76.71875 103.0625 L 84.03125 110.375 L 110.25 110.34375 L 110.21875 84.0625 L 102.9375 76.8125 L 102.84375 97 L 82 76.15625 z';
    var exitFullScreenPath = 'M 104.34375 17.5625 L 83.5 38.4375 L 83.40625 18.21875 L 76.125 25.5 L 76.09375 51.78125 L 102.3125 51.8125 L 102.3125 51.78125 L 109.625 44.5 L 89.1875 44.40625 L 110.1875 23.40625 L 104.34375 17.5625 z M 23.75 17.59375 L 17.90625 23.4375 L 38.90625 44.4375 L 18.5 44.53125 L 25.78125 51.8125 L 52 51.78125 L 51.96875 25.53125 L 44.6875 18.25 L 44.625 38.46875 L 23.75 17.59375 z M 25.6875 76.03125 L 18.375 83.3125 L 38.78125 83.40625 L 17.8125 104.40625 L 23.625 110.25 L 44.5 89.375 L 44.59375 109.59375 L 51.875 102.3125 L 51.875 76.0625 L 25.6875 76.03125 z M 102.375 76.15625 L 76.15625 76.1875 L 76.1875 102.4375 L 83.46875 109.71875 L 83.5625 89.53125 L 104.40625 110.375 L 110.25 104.53125 L 89.25 83.53125 L 109.6875 83.46875 L 102.375 76.15625 z';

    /**
     * A single button widget for toggling fullscreen mode.
     *
     * @alias FullscreenButton
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Element|String} [fullscreenElement=document.body] The element or id to be placed into fullscreen mode.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     *
     * @see Fullscreen
     */
    function FullscreenButton(container, fullscreenElement) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        

        container = getElement(container);

        var viewModel = new FullscreenButtonViewModel(fullscreenElement);

        viewModel._exitFullScreenPath = exitFullScreenPath;
        viewModel._enterFullScreenPath = enterFullScreenPath;

        var element = document.createElement('button');
        element.type = 'button';
        element.className = 'cesium-button cesium-fullscreenButton';
        element.setAttribute('data-bind', '\
attr: { title: tooltip },\
click: command,\
enable: isFullscreenEnabled,\
cesiumSvgPath: { path: isFullscreen ? _exitFullScreenPath : _enterFullScreenPath, width: 128, height: 128 }');

        container.appendChild(element);

        knockout.applyBindings(viewModel, element);

        this._container = container;
        this._viewModel = viewModel;
        this._element = element;
    }

    defineProperties(FullscreenButton.prototype, {
        /**
         * Gets the parent container.
         * @memberof FullscreenButton.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof FullscreenButton.prototype
         *
         * @type {FullscreenButtonViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    FullscreenButton.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    FullscreenButton.prototype.destroy = function() {
        this._viewModel.destroy();

        knockout.cleanNode(this._element);
        this._container.removeChild(this._element);

        return destroyObject(this);
    };

    return FullscreenButton;
});

/*global define*/
define('Widgets/Geocoder/GeocoderViewModel',[
        '../../Core/BingMapsGeocoderService',
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../Core/Event',
        '../../Core/CartographicGeocoderService',
        '../../Core/Matrix4',
        '../../ThirdParty/knockout',
        '../../ThirdParty/when',
        '../createCommand',
        '../getElement'
    ], function(
        BingMapsGeocoderService,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        CartographicGeocoderService,
        Matrix4,
        knockout,
        when,
        createCommand,
        getElement) {
    'use strict';

    /**
     * The view model for the {@link Geocoder} widget.
     * @alias GeocoderViewModel
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Scene} options.scene The Scene instance to use.
     * @param {GeocoderService[]} [options.geocoderServices] Geocoder services to use for geocoding queries.
     *        If more than one are supplied, suggestions will be gathered for the geocoders that support it,
     *        and if no suggestion is selected the result from the first geocoder service wil be used.
     * @param {Number} [options.flightDuration] The duration of the camera flight to an entered location, in seconds.
     */
    function GeocoderViewModel(options) {
                if (!defined(options) || !defined(options.scene)) {
            throw new DeveloperError('options.scene is required.');
        }
        
        if (defined(options.geocoderServices)) {
            this._geocoderServices = options.geocoderServices;
        } else {
            this._geocoderServices = [
                new CartographicGeocoderService(),
                new BingMapsGeocoderService({scene: options.scene})
            ];
        }

        this._viewContainer = options.container;
        this._scene = options.scene;
        this._flightDuration = options.flightDuration;
        this._searchText = '';
        this._isSearchInProgress = false;
        this._geocodePromise = undefined;
        this._complete = new Event();
        this._suggestions = [];
        this._selectedSuggestion = undefined;
        this._showSuggestions = true;
        this._updateCamera = updateCamera;
        this._adjustSuggestionsScroll = adjustSuggestionsScroll;
        this._updateSearchSuggestions = updateSearchSuggestions;
        this._handleArrowDown = handleArrowDown;
        this._handleArrowUp = handleArrowUp;

        var that = this;

        this._suggestionsVisible = knockout.pureComputed(function () {
            var suggestions = knockout.getObservable(that, '_suggestions');
            var suggestionsNotEmpty = suggestions().length > 0;
            var showSuggestions = knockout.getObservable(that, '_showSuggestions')();
            return suggestionsNotEmpty && showSuggestions;
        });

        this._searchCommand = createCommand(function() {
            that.hideSuggestions();
            that._focusTextbox = false;
            if (defined(that._selectedSuggestion)) {
                that.activateSuggestion(that._selectedSuggestion);
                return false;
            }
            if (that.isSearchInProgress) {
                cancelGeocode(that);
            } else {
                geocode(that, that._geocoderServices);
            }
        });

        this.deselectSuggestion = function () {
            that._selectedSuggestion = undefined;
        };

        this.handleKeyDown = function(data, event) {
            var downKey = event.key === 'ArrowDown' || event.key === 'Down' || event.keyCode === 40;
            var upKey = event.key === 'ArrowUp' || event.key === 'Up' || event.keyCode === 38;
            if (downKey || upKey) {
                event.preventDefault();
            }

            return true;
        };

        this.handleKeyUp = function (data, event) {
            var downKey = event.key === 'ArrowDown' || event.key === 'Down' || event.keyCode === 40;
            var upKey = event.key === 'ArrowUp' || event.key === 'Up' || event.keyCode === 38;
            var enterKey = event.key === 'Enter' || event.keyCode === 13;
            if (upKey) {
                handleArrowUp(that);
            } else if (downKey) {
                handleArrowDown(that);
            } else if (enterKey) {
                that._searchCommand();
            }
            return true;
        };

        this.activateSuggestion = function (data) {
            that.hideSuggestions();
            that._searchText = data.displayName;
            var destination = data.destination;
            clearSuggestions(that);
            updateCamera(that, destination);
        };

        this.hideSuggestions = function () {
            that._showSuggestions = false;
            that._selectedSuggestion = undefined;
        };

        this.showSuggestions = function () {
            that._showSuggestions = true;
        };

        this.handleMouseover = function (data, event) {
            if (data !== that._selectedSuggestion) {
                that._selectedSuggestion = data;
            }
        };

        /**
         * Gets or sets a value indicating if this instance should always show its text input field.
         *
         * @type {Boolean}
         * @default false
         */
        this.keepExpanded = false;

        /**
         * True if the geocoder should query as the user types to autocomplete
         * @type {Booelan}
         * @default true
         */
        this.autoComplete = defaultValue(options.autocomplete, true);

        this._focusTextbox = false;

        knockout.track(this, ['_searchText', '_isSearchInProgress', 'keepExpanded', '_suggestions', '_selectedSuggestion', '_showSuggestions', '_focusTextbox']);

        var searchTextObservable = knockout.getObservable(this, '_searchText');
        searchTextObservable.extend({ rateLimit: { timeout: 500 } });
        this._suggestionSubscription = searchTextObservable.subscribe(function() {
            updateSearchSuggestions(that);
        });
        /**
         * Gets a value indicating whether a search is currently in progress.  This property is observable.
         *
         * @type {Boolean}
         */
        this.isSearchInProgress = undefined;
        knockout.defineProperty(this, 'isSearchInProgress', {
            get : function() {
                return this._isSearchInProgress;
            }
        });

        /**
         * Gets or sets the text to search for.  The text can be an address, or longitude, latitude,
         * and optional height, where longitude and latitude are in degrees and height is in meters.
         *
         * @type {String}
         */
        this.searchText = undefined;
        knockout.defineProperty(this, 'searchText', {
            get : function() {
                if (this.isSearchInProgress) {
                    return 'Searching...';
                }

                return this._searchText;
            },
            set : function(value) {
                                if (typeof value !== 'string') {
                    throw new DeveloperError('value must be a valid string.');
                }
                                this._searchText = value;
            }
        });

        /**
         * Gets or sets the the duration of the camera flight in seconds.
         * A value of zero causes the camera to instantly switch to the geocoding location.
         * The duration will be computed based on the distance when undefined.
         *
         * @type {Number|undefined}
         * @default undefined
         */
        this.flightDuration = undefined;
        knockout.defineProperty(this, 'flightDuration', {
            get : function() {
                return this._flightDuration;
            },
            set : function(value) {
                                if (defined(value) && value < 0) {
                    throw new DeveloperError('value must be positive.');
                }
                
                this._flightDuration = value;
            }
        });
    }

    defineProperties(GeocoderViewModel.prototype, {
        /**
         * Gets the event triggered on flight completion.
         * @memberof GeocoderViewModel.prototype
         *
         * @type {Event}
         */
        complete : {
            get : function() {
                return this._complete;
            }
        },

        /**
         * Gets the scene to control.
         * @memberof GeocoderViewModel.prototype
         *
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets the Command that is executed when the button is clicked.
         * @memberof GeocoderViewModel.prototype
         *
         * @type {Command}
         */
        search : {
            get : function() {
                return this._searchCommand;
            }
        },

        /**
         * Gets the currently selected geocoder search suggestion
         * @memberof GeocoderViewModel.prototype
         *
         * @type {Object}
         */
        selectedSuggestion : {
            get : function() {
                return this._selectedSuggestion;
            }
        },

        /**
         * Gets the list of geocoder search suggestions
         * @memberof GeocoderViewModel.prototype
         *
         * @type {Object[]}
         */
        suggestions : {
            get : function() {
                return this._suggestions;
            }
        }
    });

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    GeocoderViewModel.prototype.destroy = function() {
        this._suggestionSubscription.dispose();
    };

    function handleArrowUp(viewModel) {
        if (viewModel._suggestions.length === 0) {
            return;
        }
        var next;
        var currentIndex = viewModel._suggestions.indexOf(viewModel._selectedSuggestion);
        if (currentIndex === -1 || currentIndex === 0) {
            viewModel._selectedSuggestion = undefined;
            return;
        }
        next = currentIndex - 1;
        viewModel._selectedSuggestion = viewModel._suggestions[next];
        adjustSuggestionsScroll(viewModel, next);
    }

    function handleArrowDown(viewModel) {
        if (viewModel._suggestions.length === 0) {
            return;
        }
        var numberOfSuggestions = viewModel._suggestions.length;
        var currentIndex = viewModel._suggestions.indexOf(viewModel._selectedSuggestion);
        var next = (currentIndex + 1) % numberOfSuggestions;
        viewModel._selectedSuggestion = viewModel._suggestions[next];

        adjustSuggestionsScroll(viewModel, next);
    }

    function updateCamera(viewModel, destination) {
        viewModel._scene.camera.flyTo({
            destination : destination,
            complete: function() {
                viewModel._complete.raiseEvent();
            },
            duration : viewModel._flightDuration,
            endTransform : Matrix4.IDENTITY
        });
    }

    function chainPromise(promise, geocoderService, query) {
        return promise
            .then(function(result) {
                if (defined(result) && result.state === 'fulfilled' && result.value.length > 0){
                    return result;
                }
                var nextPromise = geocoderService.geocode(query)
                    .then(function (result) {
                        return {state: 'fulfilled', value: result};
                    })
                    .otherwise(function (err) {
                        return {state: 'rejected', reason: err};
                    });

                return nextPromise;
            });
    }

    function geocode(viewModel, geocoderServices) {
        var query = viewModel._searchText;

        if (hasOnlyWhitespace(query)) {
            viewModel.showSuggestions();
            return;
        }

        viewModel._isSearchInProgress = true;

        var promise = when.resolve();
        for (var i = 0; i < geocoderServices.length; i++) {
            promise = chainPromise(promise, geocoderServices[i], query);
        }

        viewModel._geocodePromise = promise;
        promise
            .then(function (result) {
                if (promise.cancel) {
                    return;
                }
                viewModel._isSearchInProgress = false;

                var geocoderResults = result.value;
                if (result.state === 'fulfilled' && defined(geocoderResults) && geocoderResults.length > 0) {
                    viewModel._searchText = geocoderResults[0].displayName;
                    updateCamera(viewModel, geocoderResults[0].destination);
                    return;
                }
                viewModel._searchText = query + ' (not found)';
            });
    }

    function adjustSuggestionsScroll(viewModel, focusedItemIndex) {
        var container = getElement(viewModel._viewContainer);
        var searchResults = container.getElementsByClassName('search-results')[0];
        var listItems = container.getElementsByTagName('li');
        var element = listItems[focusedItemIndex];

        if (focusedItemIndex === 0) {
            searchResults.scrollTop = 0;
            return;
        }

        var offsetTop = element.offsetTop;
        if (offsetTop + element.clientHeight > searchResults.clientHeight) {
            searchResults.scrollTop = offsetTop + element.clientHeight;
        } else if (offsetTop < searchResults.scrollTop) {
            searchResults.scrollTop = offsetTop;
        }
    }

    function cancelGeocode(viewModel) {
        viewModel._isSearchInProgress = false;
        if (defined(viewModel._geocodePromise)) {
            viewModel._geocodePromise.cancel = true;
            viewModel._geocodePromise = undefined;
        }
    }

    function hasOnlyWhitespace(string) {
        return /^\s*$/.test(string);
    }

    function clearSuggestions(viewModel) {
        knockout.getObservable(viewModel, '_suggestions').removeAll();
    }

    function updateSearchSuggestions(viewModel) {
        if (!viewModel.autoComplete) {
            return;
        }

        var query = viewModel._searchText;

        clearSuggestions(viewModel);
        if (hasOnlyWhitespace(query)) {
            return;
        }

        var promise = when.resolve([]);
        viewModel._geocoderServices.forEach(function (service) {
            promise = promise.then(function(results) {
                if (results.length >= 5) {
                    return results;
                }
                return service.geocode(query)
                    .then(function(newResults) {
                        results = results.concat(newResults);
                        return results;
                    });
            });
        });
        promise
            .then(function (results) {
                var suggestions = viewModel._suggestions;
                for (var i = 0; i < results.length; i++) {
                    suggestions.push(results[i]);
                }
            });
    }

    return GeocoderViewModel;
});

/*global define*/
define('Widgets/Geocoder/Geocoder',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/FeatureDetection',
        '../../ThirdParty/knockout',
        '../getElement',
        './GeocoderViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        knockout,
        getElement,
        GeocoderViewModel) {
    'use strict';

    var startSearchPath = 'M29.772,26.433l-7.126-7.126c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127L29.772,26.433zM7.203,13.885c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486c-0.007,3.58-2.905,6.476-6.484,6.484C10.106,20.361,7.209,17.465,7.203,13.885z';
    var stopSearchPath = 'M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z';

    /**
     * A widget for finding addresses and landmarks, and flying the camera to them.  Geocoding is
     * performed using the {@link http://msdn.microsoft.com/en-us/library/ff701715.aspx|Bing Maps Locations API}.
     *
     * @alias Geocoder
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Element|String} options.container The DOM element or ID that will contain the widget.
     * @param {Scene} options.scene The Scene instance to use.
     * @param {GeocoderService[]} [options.geocoderServices] The geocoder services to be used
     * @param {Boolean} [options.autoComplete = true] True if the geocoder should query as the user types to autocomplete
     * @param {Number} [options.flightDuration=1.5] The duration of the camera flight to an entered location, in seconds.
     */
    function Geocoder(options) {
                if (!defined(options) || !defined(options.container)) {
            throw new DeveloperError('options.container is required.');
        }
        if (!defined(options.scene)) {
            throw new DeveloperError('options.scene is required.');
        }
        
        var container = getElement(options.container);
        var viewModel = new GeocoderViewModel(options);

        viewModel._startSearchPath = startSearchPath;
        viewModel._stopSearchPath = stopSearchPath;

        var form = document.createElement('form');
        form.setAttribute('data-bind', 'submit: search');

        var textBox = document.createElement('input');
        textBox.type = 'search';
        textBox.className = 'cesium-geocoder-input';
        textBox.setAttribute('placeholder', 'Enter an address or landmark...');
        textBox.setAttribute('data-bind', '\
textInput: searchText,\
disable: isSearchInProgress,\
event: { keyup: handleKeyUp, keydown: handleKeyDown, mouseover: deselectSuggestion },\
css: { "cesium-geocoder-input-wide" : keepExpanded || searchText.length > 0 },\
hasFocus: _focusTextbox');

        this._onTextBoxFocus = function() {
            // as of 2016-10-19, setTimeout is required to ensure that the
            // text is focused on Safari 10
            setTimeout(function() {
                textBox.select();
            }, 0);
        };

        textBox.addEventListener('focus', this._onTextBoxFocus, false);
        form.appendChild(textBox);
        this._textBox = textBox;

        var searchButton = document.createElement('span');
        searchButton.className = 'cesium-geocoder-searchButton';
        searchButton.setAttribute('data-bind', '\
click: search,\
cesiumSvgPath: { path: isSearchInProgress ? _stopSearchPath : _startSearchPath, width: 32, height: 32 }');
        form.appendChild(searchButton);

        container.appendChild(form);

        var searchSuggestionsContainer = document.createElement('div');
        searchSuggestionsContainer.className = 'search-results';
        searchSuggestionsContainer.setAttribute('data-bind', 'visible: _suggestionsVisible');

        var suggestionsList = document.createElement('ul');
        suggestionsList.setAttribute('data-bind', 'foreach: _suggestions');
        var suggestions = document.createElement('li');
        suggestionsList.appendChild(suggestions);
        suggestions.setAttribute('data-bind', 'text: $data.displayName, \
click: $parent.activateSuggestion, \
event: { mouseover: $parent.handleMouseover}, \
css: { active: $data === $parent._selectedSuggestion }');

        searchSuggestionsContainer.appendChild(suggestionsList);
        container.appendChild(searchSuggestionsContainer);

        knockout.applyBindings(viewModel, form);
        knockout.applyBindings(viewModel, searchSuggestionsContainer);

        this._container = container;
        this._searchSuggestionsContainer = searchSuggestionsContainer;
        this._viewModel = viewModel;
        this._form = form;

        this._onInputBegin = function(e) {
            if (!container.contains(e.target)) {
                viewModel._focusTextbox = false;
                viewModel.hideSuggestions();
            }
        };

        this._onInputEnd = function(e) {
            if (container.contains(e.target)) {
                viewModel._focusTextbox = true;
                viewModel.showSuggestions();
            }
        };

        //We subscribe to both begin and end events in order to give the text box
        //focus no matter where on the widget is clicked.

        if (FeatureDetection.supportsPointerEvents()) {
            document.addEventListener('pointerdown', this._onInputBegin, true);
            document.addEventListener('pointerup', this._onInputEnd, true);
            document.addEventListener('pointercancel', this._onInputEnd, true);
        } else {
            document.addEventListener('mousedown', this._onInputBegin, true);
            document.addEventListener('mouseup', this._onInputEnd, true);
            document.addEventListener('touchstart', this._onInputBegin, true);
            document.addEventListener('touchend', this._onInputEnd, true);
            document.addEventListener('touchcancel', this._onInputEnd, true);
        }

    }

    defineProperties(Geocoder.prototype, {
        /**
         * Gets the parent container.
         * @memberof Geocoder.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the parent container.
         * @memberof Geocoder.prototype
         *
         * @type {Element}
         */
        searchSuggestionsContainer : {
            get : function() {
                return this._searchSuggestionsContainer;
            }
        },

        /**
         * Gets the view model.
         * @memberof Geocoder.prototype
         *
         * @type {GeocoderViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    Geocoder.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    Geocoder.prototype.destroy = function() {
        if (FeatureDetection.supportsPointerEvents()) {
            document.removeEventListener('pointerdown', this._onInputBegin, true);
            document.removeEventListener('pointerup', this._onInputEnd, true);
        } else {
            document.removeEventListener('mousedown', this._onInputBegin, true);
            document.removeEventListener('mouseup', this._onInputEnd, true);
            document.removeEventListener('touchstart', this._onInputBegin, true);
            document.removeEventListener('touchend', this._onInputEnd, true);
        }
        this._viewModel.destroy();
        knockout.cleanNode(this._form);
        knockout.cleanNode(this._searchSuggestionsContainer);
        this._container.removeChild(this._form);
        this._container.removeChild(this._searchSuggestionsContainer);
        this._textBox.removeEventListener('focus', this._onTextBoxFocus, false);

        return destroyObject(this);
    };

    return Geocoder;
});

/*global define*/
define('Widgets/HomeButton/HomeButtonViewModel',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        knockout,
        createCommand) {
    'use strict';

    /**
     * The view model for {@link HomeButton}.
     * @alias HomeButtonViewModel
     * @constructor
     *
     * @param {Scene} scene The scene instance to use.
     * @param {Number} [duration] The duration of the camera flight in seconds.
     */
    function HomeButtonViewModel(scene, duration) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        this._scene = scene;
        this._duration = duration;

        var that = this;
        this._command = createCommand(function() {
            that._scene.camera.flyHome(that._duration);
        });

        /**
         * Gets or sets the tooltip.  This property is observable.
         *
         * @type {String}
         */
        this.tooltip = 'View Home';

        knockout.track(this, ['tooltip']);
    }

    defineProperties(HomeButtonViewModel.prototype, {
        /**
         * Gets the scene to control.
         * @memberof HomeButtonViewModel.prototype
         *
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets the Command that is executed when the button is clicked.
         * @memberof HomeButtonViewModel.prototype
         *
         * @type {Command}
         */
        command : {
            get : function() {
                return this._command;
            }
        },

        /**
         * Gets or sets the the duration of the camera flight in seconds.
         * A value of zero causes the camera to instantly switch to home view.
         * The duration will be computed based on the distance when undefined.
         * @memberof HomeButtonViewModel.prototype
         *
         * @type {Number|undefined}
         */
        duration : {
            get : function() {
                return this._duration;
            },
            set : function(value) {
                                if (defined(value) && value < 0) {
                    throw new DeveloperError('value must be positive.');
                }
                
                this._duration = value;
            }
        }
    });

    return HomeButtonViewModel;
});

/*global define*/
define('Widgets/HomeButton/HomeButton',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        './HomeButtonViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        HomeButtonViewModel) {
    'use strict';

    /**
     * A single button widget for returning to the default camera view of the current scene.
     *
     * @alias HomeButton
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Scene} scene The Scene instance to use.
     * @param {Number} [duration] The time, in seconds, it takes to complete the camera flight home.
     */
    function HomeButton(container, scene, duration) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        container = getElement(container);

        var viewModel = new HomeButtonViewModel(scene, duration);

        viewModel._svgPath = 'M14,4l-10,8.75h20l-4.25-3.7188v-4.6562h-2.812v2.1875l-2.938-2.5625zm-7.0938,9.906v10.094h14.094v-10.094h-14.094zm2.1876,2.313h3.3122v4.25h-3.3122v-4.25zm5.8442,1.281h3.406v6.438h-3.406v-6.438z';

        var element = document.createElement('button');
        element.type = 'button';
        element.className = 'cesium-button cesium-toolbar-button cesium-home-button';
        element.setAttribute('data-bind', '\
attr: { title: tooltip },\
click: command,\
cesiumSvgPath: { path: _svgPath, width: 28, height: 28 }');

        container.appendChild(element);

        knockout.applyBindings(viewModel, element);

        this._container = container;
        this._viewModel = viewModel;
        this._element = element;
    }

    defineProperties(HomeButton.prototype, {
        /**
         * Gets the parent container.
         * @memberof HomeButton.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof HomeButton.prototype
         *
         * @type {HomeButtonViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    HomeButton.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    HomeButton.prototype.destroy = function() {
        knockout.cleanNode(this._element);
        this._container.removeChild(this._element);

        return destroyObject(this);
    };

    return HomeButton;
});

/*global define*/
define('Widgets/InfoBox/InfoBoxViewModel',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/Event',
        '../../ThirdParty/knockout'
    ], function(
        defined,
        defineProperties,
        Event,
        knockout) {
    'use strict';

    var cameraEnabledPath = 'M 13.84375 7.03125 C 11.412798 7.03125 9.46875 8.975298 9.46875 11.40625 L 9.46875 11.59375 L 2.53125 7.21875 L 2.53125 24.0625 L 9.46875 19.6875 C 9.4853444 22.104033 11.423165 24.0625 13.84375 24.0625 L 25.875 24.0625 C 28.305952 24.0625 30.28125 22.087202 30.28125 19.65625 L 30.28125 11.40625 C 30.28125 8.975298 28.305952 7.03125 25.875 7.03125 L 13.84375 7.03125 z';
    var cameraDisabledPath = 'M 27.34375 1.65625 L 5.28125 27.9375 L 8.09375 30.3125 L 30.15625 4.03125 L 27.34375 1.65625 z M 13.84375 7.03125 C 11.412798 7.03125 9.46875 8.975298 9.46875 11.40625 L 9.46875 11.59375 L 2.53125 7.21875 L 2.53125 24.0625 L 9.46875 19.6875 C 9.4724893 20.232036 9.5676108 20.7379 9.75 21.21875 L 21.65625 7.03125 L 13.84375 7.03125 z M 28.21875 7.71875 L 14.53125 24.0625 L 25.875 24.0625 C 28.305952 24.0625 30.28125 22.087202 30.28125 19.65625 L 30.28125 11.40625 C 30.28125 9.8371439 29.456025 8.4902779 28.21875 7.71875 z';

    /**
     * The view model for {@link InfoBox}.
     * @alias InfoBoxViewModel
     * @constructor
     */
    function InfoBoxViewModel() {
        this._cameraClicked = new Event();
        this._closeClicked = new Event();

        /**
         * Gets or sets the maximum height of the info box in pixels.  This property is observable.
         * @type {Number}
         */
        this.maxHeight = 500;

        /**
         * Gets or sets whether the camera tracking icon is enabled.
         * @type {Boolean}
         */
        this.enableCamera = false;

        /**
         * Gets or sets the status of current camera tracking of the selected object.
         * @type {Boolean}
         */
        this.isCameraTracking = false;

        /**
         * Gets or sets the visibility of the info box.
         * @type {Boolean}
         */
        this.showInfo = false;

        /**
         * Gets or sets the title text in the info box.
         * @type {String}
         */
        this.titleText = '';

        /**
         * Gets or sets the description HTML for the info box.
         * @type {String}
         */
        this.description = '';

        knockout.track(this, ['showInfo', 'titleText', 'description', 'maxHeight', 'enableCamera', 'isCameraTracking']);

        this._loadingIndicatorHtml = '<div class="cesium-infoBox-loadingContainer"><span class="cesium-infoBox-loading"></span></div>';

        /**
         * Gets the SVG path of the camera icon, which can change to be "crossed out" or not.
         * @type {String}
         */
        this.cameraIconPath = undefined;
        knockout.defineProperty(this, 'cameraIconPath', {
            get : function() {
                return (!this.enableCamera || this.isCameraTracking) ? cameraDisabledPath : cameraEnabledPath;
            }
        });

        knockout.defineProperty(this, '_bodyless', {
            get : function() {
                return !defined(this.description) || this.description.length === 0;
            }
        });
    }

    /**
     * Gets the maximum height of sections within the info box, minus an offset, in CSS-ready form.
     * @param {Number} offset The offset in pixels.
     * @returns {String}
     */
    InfoBoxViewModel.prototype.maxHeightOffset = function(offset) {
        return (this.maxHeight - offset) + 'px';
    };

    defineProperties(InfoBoxViewModel.prototype, {
        /**
         * Gets an {@link Event} that is fired when the user clicks the camera icon.
         * @memberof InfoBoxViewModel.prototype
         * @type {Event}
         */
        cameraClicked : {
            get : function() {
                return this._cameraClicked;
            }
        },
        /**
         * Gets an {@link Event} that is fired when the user closes the info box.
         * @memberof InfoBoxViewModel.prototype
         * @type {Event}
         */
        closeClicked : {
            get : function() {
                return this._closeClicked;
            }
        }
    });

    return InfoBoxViewModel;
});

/*global define*/
define('Widgets/InfoBox/InfoBox',[
        '../../Core/buildModuleUrl',
        '../../Core/Color',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        '../subscribeAndEvaluate',
        './InfoBoxViewModel'
    ], function(
        buildModuleUrl,
        Color,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        subscribeAndEvaluate,
        InfoBoxViewModel) {
    'use strict';

    /**
     * A widget for displaying information or a description.
     *
     * @alias InfoBox
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     */
    function InfoBox(container) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        container = getElement(container);

        var infoElement = document.createElement('div');
        infoElement.className = 'cesium-infoBox';
        infoElement.setAttribute('data-bind', '\
css: { "cesium-infoBox-visible" : showInfo, "cesium-infoBox-bodyless" : _bodyless }');
        container.appendChild(infoElement);

        var titleElement = document.createElement('div');
        titleElement.className = 'cesium-infoBox-title';
        titleElement.setAttribute('data-bind', 'text: titleText');
        infoElement.appendChild(titleElement);

        var cameraElement = document.createElement('button');
        cameraElement.type = 'button';
        cameraElement.className = 'cesium-button cesium-infoBox-camera';
        cameraElement.setAttribute('data-bind', '\
attr: { title: "Focus camera on object" },\
click: function () { cameraClicked.raiseEvent(this); },\
enable: enableCamera,\
cesiumSvgPath: { path: cameraIconPath, width: 32, height: 32 }');
        infoElement.appendChild(cameraElement);

        var closeElement = document.createElement('button');
        closeElement.type = 'button';
        closeElement.className = 'cesium-infoBox-close';
        closeElement.setAttribute('data-bind', '\
click: function () { closeClicked.raiseEvent(this); }');
        closeElement.innerHTML = '&times;';
        infoElement.appendChild(closeElement);

        var frame = document.createElement('iframe');
        frame.className = 'cesium-infoBox-iframe';
        frame.setAttribute('sandbox', 'allow-same-origin allow-popups allow-forms'); //allow-pointer-lock allow-scripts allow-top-navigation
        frame.setAttribute('data-bind', 'style : { maxHeight : maxHeightOffset(40) }');
        frame.setAttribute('allowfullscreen', true);
        infoElement.appendChild(frame);

        var viewModel = new InfoBoxViewModel();
        knockout.applyBindings(viewModel, infoElement);

        this._container = container;
        this._element = infoElement;
        this._frame = frame;
        this._viewModel = viewModel;
        this._descriptionSubscription = undefined;

        var that = this;
        //We can't actually add anything into the frame until the load event is fired
        frame.addEventListener('load', function() {
            var frameDocument = frame.contentDocument;

            //We inject default css into the content iframe,
            //end users can remove it or add their own via the exposed frame property.
            var cssLink = frameDocument.createElement("link");
            cssLink.href = buildModuleUrl('Widgets/InfoBox/InfoBoxDescription.css');
            cssLink.rel = "stylesheet";
            cssLink.type = "text/css";

            //div to use for description content.
            var frameContent = frameDocument.createElement("div");
            frameContent.className = 'cesium-infoBox-description';

            frameDocument.head.appendChild(cssLink);
            frameDocument.body.appendChild(frameContent);

            //We manually subscribe to the description event rather than through a binding for two reasons.
            //1. It's an easy way to ensure order of operation so that we can adjust the height.
            //2. Knockout does not bind to elements inside of an iFrame, so we would have to apply a second binding
            //   model anyway.
            that._descriptionSubscription = subscribeAndEvaluate(viewModel, 'description', function(value) {
                // Set the frame to small height, force vertical scroll bar to appear, and text to wrap accordingly.
                frame.style.height = '5px';
                frameContent.innerHTML = value;

                //If the snippet is a single element, then use its background
                //color for the body of the InfoBox. This makes the padding match
                //the content and produces much nicer results.
                var background = null;
                var firstElementChild = frameContent.firstElementChild;
                if (firstElementChild !== null && frameContent.childNodes.length === 1) {
                    var style = window.getComputedStyle(firstElementChild);
                    if (style !== null) {
                        var backgroundColor = style['background-color'];
                        var color = Color.fromCssColorString(backgroundColor);
                        if (defined(color) && color.alpha !== 0) {
                            background = style['background-color'];
                        }
                    }
                }
                infoElement.style['background-color'] = background;

                // Measure and set the new custom height, based on text wrapped above.
                var height = frameContent.getBoundingClientRect().height;
                frame.style.height =  height + 'px';
            });
        });

        //Chrome does not send the load event unless we explicitly set a src
        frame.setAttribute('src', 'about:blank');
    }

    defineProperties(InfoBox.prototype, {
        /**
         * Gets the parent container.
         * @memberof InfoBox.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof InfoBox.prototype
         *
         * @type {InfoBoxViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        },

        /**
         * Gets the iframe used to display the description.
         * @memberof InfoBox.prototype
         *
         * @type {HTMLIFrameElement}
         */
        frame : {
            get : function() {
                return this._frame;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    InfoBox.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    InfoBox.prototype.destroy = function() {
        var container = this._container;
        knockout.cleanNode(this._element);
        container.removeChild(this._element);

        if (defined(this._descriptionSubscription)) {
            this._descriptionSubscription.dispose();
        }

        return destroyObject(this);
    };

    return InfoBox;
});

/*global define*/
define('Widgets/NavigationHelpButton/NavigationHelpButtonViewModel',[
        '../../Core/defineProperties',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defineProperties,
        knockout,
        createCommand) {
    'use strict';

    /**
     * The view model for {@link NavigationHelpButton}.
     * @alias NavigationHelpButtonViewModel
     * @constructor
     */
    function NavigationHelpButtonViewModel() {
        /**
         * Gets or sets whether the instructions are currently shown.  This property is observable.
         * @type {Boolean}
         * @default false
        */
        this.showInstructions = false;

        var that = this;
        this._command = createCommand(function() {
            that.showInstructions = !that.showInstructions;
        });
        this._showClick = createCommand(function() {
            that._touch = false;
        });
        this._showTouch = createCommand(function() {
            that._touch = true;
        });

        this._touch = false;

        /**
         * Gets or sets the tooltip.  This property is observable.
         *
         * @type {String}
         */
        this.tooltip = 'Navigation Instructions';

        knockout.track(this, ['tooltip', 'showInstructions', '_touch']);
    }

    defineProperties(NavigationHelpButtonViewModel.prototype, {
        /**
         * Gets the Command that is executed when the button is clicked.
         * @memberof NavigationHelpButtonViewModel.prototype
         *
         * @type {Command}
         */
        command : {
            get : function() {
                return this._command;
            }
        },

        /**
         * Gets the Command that is executed when the mouse instructions should be shown.
         * @memberof NavigationHelpButtonViewModel.prototype
         *
         * @type {Command}
         */
        showClick : {
            get : function() {
                return this._showClick;
            }
        },

        /**
         * Gets the Command that is executed when the touch instructions should be shown.
         * @memberof NavigationHelpButtonViewModel.prototype
         *
         * @type {Command}
         */
        showTouch : {
            get: function() {
                return this._showTouch;
            }
        }
    });

    return NavigationHelpButtonViewModel;
});

/*global define*/
define('Widgets/NavigationHelpButton/NavigationHelpButton',[
        '../../Core/buildModuleUrl',
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/FeatureDetection',
        '../../ThirdParty/knockout',
        '../getElement',
        './NavigationHelpButtonViewModel'
    ], function(
        buildModuleUrl,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        knockout,
        getElement,
        NavigationHelpButtonViewModel) {
    'use strict';

    /**
     * <p>The NavigationHelpButton is a single button widget for displaying instructions for
     * navigating the globe with the mouse.</p><p style="clear: both;"></p><br/>
     *
     * @alias NavigationHelpButton
     * @constructor
     *
     * @param {Object} options Object with the following properties:
     * @param {Element|String} options.container The DOM element or ID that will contain the widget.
     * @param {Boolean} [options.instructionsInitiallyVisible=false] True if the navigation instructions should initially be visible; otherwise, false.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     *
     * @example
     * // In HTML head, include a link to the NavigationHelpButton.css stylesheet,
     * // and in the body, include: <div id="navigationHelpButtonContainer"></div>
     *
     * var navigationHelpButton = new Cesium.NavigationHelpButton({
     *     container : 'navigationHelpButtonContainer'
     * });
     */
    function NavigationHelpButton(options) {
                if (!defined(options) || !defined(options.container)) {
            throw new DeveloperError('options.container is required.');
        }
        
        var container = getElement(options.container);

        var viewModel = new NavigationHelpButtonViewModel();

        var showInsructionsDefault = defaultValue(options.instructionsInitiallyVisible, false);
        viewModel.showInstructions = showInsructionsDefault;

        viewModel._svgPath = 'M16,1.466C7.973,1.466,1.466,7.973,1.466,16c0,8.027,6.507,14.534,14.534,14.534c8.027,0,14.534-6.507,14.534-14.534C30.534,7.973,24.027,1.466,16,1.466z M17.328,24.371h-2.707v-2.596h2.707V24.371zM17.328,19.003v0.858h-2.707v-1.057c0-3.19,3.63-3.696,3.63-5.963c0-1.034-0.924-1.826-2.134-1.826c-1.254,0-2.354,0.924-2.354,0.924l-1.541-1.915c0,0,1.519-1.584,4.137-1.584c2.487,0,4.796,1.54,4.796,4.136C21.156,16.208,17.328,16.627,17.328,19.003z';

        var wrapper = document.createElement('span');
        wrapper.className = 'cesium-navigationHelpButton-wrapper';
        container.appendChild(wrapper);

        var button = document.createElement('button');
        button.type = 'button';
        button.className = 'cesium-button cesium-toolbar-button cesium-navigation-help-button';
        button.setAttribute('data-bind', '\
attr: { title: tooltip },\
click: command,\
cesiumSvgPath: { path: _svgPath, width: 32, height: 32 }');
        wrapper.appendChild(button);

        var instructionContainer = document.createElement('div');
        instructionContainer.className = 'cesium-navigation-help';
        instructionContainer.setAttribute('data-bind', 'css: { "cesium-navigation-help-visible" : showInstructions}');
        wrapper.appendChild(instructionContainer);

        var mouseButton = document.createElement('button');
        mouseButton.type = 'button';
        mouseButton.className = 'cesium-navigation-button cesium-navigation-button-left';
        mouseButton.setAttribute('data-bind', 'click: showClick, css: {"cesium-navigation-button-selected": !_touch, "cesium-navigation-button-unselected": _touch}');
        var mouseIcon = document.createElement('img');
        mouseIcon.src = buildModuleUrl('Widgets/Images/NavigationHelp/Mouse.svg');
        mouseIcon.className = 'cesium-navigation-button-icon';
        mouseIcon.style.width = '25px';
        mouseIcon.style.height = '25px';
        mouseButton.appendChild(mouseIcon);
        mouseButton.appendChild(document.createTextNode('Mouse'));

        var touchButton = document.createElement('button');
        touchButton.type = 'button';
        touchButton.className = 'cesium-navigation-button cesium-navigation-button-right';
        touchButton.setAttribute('data-bind', 'click: showTouch, css: {"cesium-navigation-button-selected": _touch, "cesium-navigation-button-unselected": !_touch}');
        var touchIcon = document.createElement('img');
        touchIcon.src = buildModuleUrl('Widgets/Images/NavigationHelp/Touch.svg');
        touchIcon.className = 'cesium-navigation-button-icon';
        touchIcon.style.width = '25px';
        touchIcon.style.height = '25px';
        touchButton.appendChild(touchIcon);
        touchButton.appendChild(document.createTextNode('Touch'));

        instructionContainer.appendChild(mouseButton);
        instructionContainer.appendChild(touchButton);

        var clickInstructions = document.createElement('div');
        clickInstructions.className = 'cesium-click-navigation-help cesium-navigation-help-instructions';
        clickInstructions.setAttribute('data-bind', 'css: { "cesium-click-navigation-help-visible" : !_touch}');
        clickInstructions.innerHTML = '\
            <table>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/MouseLeft.svg') + '" width="48" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-pan">Pan view</div>\
                        <div class="cesium-navigation-help-details">Left click + drag</div>\
                    </td>\
                </tr>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/MouseRight.svg') + '" width="48" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-zoom">Zoom view</div>\
                        <div class="cesium-navigation-help-details">Right click + drag, or</div>\
                        <div class="cesium-navigation-help-details">Mouse wheel scroll</div>\
                    </td>\
                </tr>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/MouseMiddle.svg') + '" width="48" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-rotate">Rotate view</div>\
                        <div class="cesium-navigation-help-details">Middle click + drag, or</div>\
                        <div class="cesium-navigation-help-details">CTRL + Left/Right click + drag</div>\
                    </td>\
                </tr>\
            </table>';

        instructionContainer.appendChild(clickInstructions);

        var touchInstructions = document.createElement('div');
        touchInstructions.className = 'cesium-touch-navigation-help cesium-navigation-help-instructions';
        touchInstructions.setAttribute('data-bind', 'css: { "cesium-touch-navigation-help-visible" : _touch}');
        touchInstructions.innerHTML = '\
            <table>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/TouchDrag.svg') + '" width="70" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-pan">Pan view</div>\
                        <div class="cesium-navigation-help-details">One finger drag</div>\
                    </td>\
                </tr>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/TouchZoom.svg') + '" width="70" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-zoom">Zoom view</div>\
                        <div class="cesium-navigation-help-details">Two finger pinch</div>\
                    </td>\
                </tr>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/TouchTilt.svg') + '" width="70" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-rotate">Tilt view</div>\
                        <div class="cesium-navigation-help-details">Two finger drag, same direction</div>\
                    </td>\
                </tr>\
                <tr>\
                    <td><img src="' + buildModuleUrl('Widgets/Images/NavigationHelp/TouchRotate.svg') + '" width="70" height="48" /></td>\
                    <td>\
                        <div class="cesium-navigation-help-tilt">Rotate view</div>\
                        <div class="cesium-navigation-help-details">Two finger drag, opposite direction</div>\
                    </td>\
                </tr>\
            </table>';

        instructionContainer.appendChild(touchInstructions);

        knockout.applyBindings(viewModel, wrapper);

        this._container = container;
        this._viewModel = viewModel;
        this._wrapper = wrapper;

        this._closeInstructions = function(e) {
            if (!wrapper.contains(e.target)) {
                viewModel.showInstructions = false;
            }
        };

        if (FeatureDetection.supportsPointerEvents()) {
            document.addEventListener('pointerdown', this._closeInstructions, true);
        } else {
            document.addEventListener('mousedown', this._closeInstructions, true);
            document.addEventListener('touchstart', this._closeInstructions, true);
        }
    }

    defineProperties(NavigationHelpButton.prototype, {
        /**
         * Gets the parent container.
         * @memberof NavigationHelpButton.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof NavigationHelpButton.prototype
         *
         * @type {NavigationHelpButtonViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    NavigationHelpButton.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    NavigationHelpButton.prototype.destroy = function() {
        if (FeatureDetection.supportsPointerEvents()) {
            document.removeEventListener('pointerdown', this._closeInstructions, true);
        } else {
            document.removeEventListener('mousedown', this._closeInstructions, true);
            document.removeEventListener('touchstart', this._closeInstructions, true);
        }

        knockout.cleanNode(this._wrapper);
        this._container.removeChild(this._wrapper);

        return destroyObject(this);
    };

    return NavigationHelpButton;
});

/*global define*/
define('Widgets/PerformanceWatchdog/PerformanceWatchdogViewModel',[
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Scene/FrameRateMonitor',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FrameRateMonitor,
        knockout,
        createCommand) {
    'use strict';

    /**
     * The view model for {@link PerformanceWatchdog}.
     *
     * @alias PerformanceWatchdogViewModel
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Scene} options.scene The Scene instance for which to monitor performance.
     * @param {String} [options.lowFrameRateMessage='This application appears to be performing poorly on your system.  Please try using a different web browser or updating your video drivers.'] The
     *        message to display when a low frame rate is detected.  The message is interpeted as HTML, so make sure
     *        it comes from a trusted source so that your application is not vulnerable to cross-site scripting attacks.
     */
    function PerformanceWatchdogViewModel(options) {
                if (!defined(options) || !defined(options.scene)) {
            throw new DeveloperError('options.scene is required.');
        }
        
        this._scene = options.scene;

        /**
         * Gets or sets the message to display when a low frame rate is detected.  This string will be interpreted as HTML.
         * @type {String}
         */
        this.lowFrameRateMessage = defaultValue(options.lowFrameRateMessage, 'This application appears to be performing poorly on your system.  Please try using a different web browser or updating your video drivers.');

        /**
         * Gets or sets a value indicating whether the low frame rate message has previously been dismissed by the user.  If it has
         * been dismissed, the message will not be redisplayed, no matter the frame rate.
         * @type {Boolean}
         */
        this.lowFrameRateMessageDismissed = false;

        /**
         * Gets or sets a value indicating whether the low frame rate message is currently being displayed.
         * @type {Boolean}
         */
        this.showingLowFrameRateMessage = false;

        knockout.track(this, ['lowFrameRateMessage', 'lowFrameRateMessageDismissed', 'showingLowFrameRateMessage']);

        var that = this;
        this._dismissMessage = createCommand(function() {
            that.showingLowFrameRateMessage = false;
            that.lowFrameRateMessageDismissed = true;
        });

        var monitor = FrameRateMonitor.fromScene(options.scene);

        this._unsubscribeLowFrameRate = monitor.lowFrameRate.addEventListener(function() {
            if (!that.lowFrameRateMessageDismissed) {
                that.showingLowFrameRateMessage = true;
            }
        });

        this._unsubscribeNominalFrameRate = monitor.nominalFrameRate.addEventListener(function() {
            that.showingLowFrameRateMessage = false;
        });
    }

    defineProperties(PerformanceWatchdogViewModel.prototype, {
        /**
         * Gets the {@link Scene} instance for which to monitor performance.
         * @memberof PerformanceWatchdogViewModel.prototype
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets a command that dismisses the low frame rate message.  Once it is dismissed, the message
         * will not be redisplayed.
         * @memberof PerformanceWatchdogViewModel.prototype
         * @type {Command}
         */
        dismissMessage : {
            get : function() {
                return this._dismissMessage;
            }
        }
    });

    PerformanceWatchdogViewModel.prototype.destroy = function() {
        this._unsubscribeLowFrameRate();
        this._unsubscribeNominalFrameRate();

        return destroyObject(this);
    };

    return PerformanceWatchdogViewModel;
});

/*global define*/
define('Widgets/PerformanceWatchdog/PerformanceWatchdog',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        './PerformanceWatchdogViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        PerformanceWatchdogViewModel) {
    'use strict';

    /**
     * Monitors performance of the application and displays a message if poor performance is detected.
     *
     * @alias PerformanceWatchdog
     * @constructor
     *
     * @param {Object} [options] Object with the following properties:
     * @param {Element|String} options.container The DOM element or ID that will contain the widget.
     * @param {Scene} options.scene The {@link Scene} for which to monitor performance.
     * @param {String} [options.lowFrameRateMessage='This application appears to be performing poorly on your system.  Please try using a different web browser or updating your video drivers.'] The
     *        message to display when a low frame rate is detected.  The message is interpeted as HTML, so make sure
     *        it comes from a trusted source so that your application is not vulnerable to cross-site scripting attacks.
     */
    function PerformanceWatchdog(options) {
                if (!defined(options) || !defined(options.container)) {
            throw new DeveloperError('options.container is required.');
        }
        if (!defined(options.scene)) {
            throw new DeveloperError('options.scene is required.');
        }
        
        var container = getElement(options.container);

        var viewModel = new PerformanceWatchdogViewModel(options);

        var element = document.createElement('div');
        element.className = 'cesium-performance-watchdog-message-area';
        element.setAttribute('data-bind', 'visible: showingLowFrameRateMessage');

        var dismissButton = document.createElement('button');
        dismissButton.setAttribute('type', 'button');
        dismissButton.className = 'cesium-performance-watchdog-message-dismiss';
        dismissButton.innerHTML = '&times;';
        dismissButton.setAttribute('data-bind', 'click: dismissMessage');
        element.appendChild(dismissButton);

        var message = document.createElement('div');
        message.className = 'cesium-performance-watchdog-message';
        message.setAttribute('data-bind', 'html: lowFrameRateMessage');
        element.appendChild(message);

        container.appendChild(element);

        knockout.applyBindings(viewModel, element);

        this._container = container;
        this._viewModel = viewModel;
        this._element = element;
    }

    defineProperties(PerformanceWatchdog.prototype, {
        /**
         * Gets the parent container.
         * @memberof PerformanceWatchdog.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof PerformanceWatchdog.prototype
         *
         * @type {PerformanceWatchdogViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @memberof PerformanceWatchdog
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    PerformanceWatchdog.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     * @memberof PerformanceWatchdog
     */
    PerformanceWatchdog.prototype.destroy = function() {
        this._viewModel.destroy();
        knockout.cleanNode(this._element);
        this._container.removeChild(this._element);

        return destroyObject(this);
    };

    return PerformanceWatchdog;
});

/*global define*/
define('Widgets/SceneModePicker/SceneModePickerViewModel',[
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/EventHelper',
        '../../Scene/SceneMode',
        '../../ThirdParty/knockout',
        '../createCommand'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        EventHelper,
        SceneMode,
        knockout,
        createCommand) {
    'use strict';

    /**
     * The view model for {@link SceneModePicker}.
     * @alias SceneModePickerViewModel
     * @constructor
     *
     * @param {Scene} scene The Scene to morph
     * @param {Number} [duration=2.0] The duration of scene morph animations, in seconds
     */
    function SceneModePickerViewModel(scene, duration) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        this._scene = scene;

        var that = this;

        var morphStart = function(transitioner, oldMode, newMode, isMorphing) {
            that.sceneMode = newMode;
            that.dropDownVisible = false;
        };

        this._eventHelper = new EventHelper();
        this._eventHelper.add(scene.morphStart, morphStart);

        this._duration = defaultValue(duration, 2.0);

        /**
         * Gets or sets the current SceneMode.  This property is observable.
         * @type {SceneMode}
        */
        this.sceneMode = scene.mode;

        /**
         * Gets or sets whether the button drop-down is currently visible.  This property is observable.
         * @type {Boolean}
         * @default false
        */
        this.dropDownVisible = false;

        /**
         * Gets or sets the 2D tooltip.  This property is observable.
         * @type {String}
         * @default '2D'
        */
        this.tooltip2D = '2D';

        /**
         * Gets or sets the 3D tooltip.  This property is observable.
         * @type {String}
         * @default '3D'
        */
        this.tooltip3D = '3D';

        /**
         * Gets or sets the Columbus View tooltip.  This property is observable.
         * @type {String}
         * @default 'Columbus View'
        */
        this.tooltipColumbusView = 'Columbus View';

        knockout.track(this, ['sceneMode', 'dropDownVisible', 'tooltip2D', 'tooltip3D', 'tooltipColumbusView']);

        /**
         * Gets the currently active tooltip.  This property is observable.
         * @type {String}
         */
        this.selectedTooltip = undefined;
        knockout.defineProperty(this, 'selectedTooltip', function() {
            var mode = that.sceneMode;
            if (mode === SceneMode.SCENE2D) {
                return that.tooltip2D;
            }
            if (mode === SceneMode.SCENE3D) {
                return that.tooltip3D;
            }
            return that.tooltipColumbusView;
        });

        this._toggleDropDown = createCommand(function() {
            that.dropDownVisible = !that.dropDownVisible;
        });

        this._morphTo2D = createCommand(function() {
            scene.morphTo2D(that._duration);
        });

        this._morphTo3D = createCommand(function() {
            scene.morphTo3D(that._duration);
        });

        this._morphToColumbusView = createCommand(function() {
            scene.morphToColumbusView(that._duration);
        });

        //Used by knockout
        this._sceneMode = SceneMode;
    }

    defineProperties(SceneModePickerViewModel.prototype, {
        /**
         * Gets the scene
         * @memberof SceneModePickerViewModel.prototype
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        },

        /**
         * Gets or sets the the duration of scene mode transition animations in seconds.
         * A value of zero causes the scene to instantly change modes.
         * @memberof SceneModePickerViewModel.prototype
         * @type {Number}
         */
        duration : {
            get : function() {
                return this._duration;
            },
            set : function(value) {
                                if (value < 0.0) {
                    throw new DeveloperError('duration value must be positive.');
                }
                
                this._duration = value;
            }
        },

        /**
         * Gets the command to toggle the drop down box.
         * @memberof SceneModePickerViewModel.prototype
         *
         * @type {Command}
         */
        toggleDropDown : {
            get : function() {
                return this._toggleDropDown;
            }
        },

        /**
         * Gets the command to morph to 2D.
         * @memberof SceneModePickerViewModel.prototype
         *
         * @type {Command}
         */
        morphTo2D : {
            get : function() {
                return this._morphTo2D;
            }
        },

        /**
         * Gets the command to morph to 3D.
         * @memberof SceneModePickerViewModel.prototype
         *
         * @type {Command}
         */
        morphTo3D : {
            get : function() {
                return this._morphTo3D;
            }
        },

        /**
         * Gets the command to morph to Columbus View.
         * @memberof SceneModePickerViewModel.prototype
         *
         * @type {Command}
         */
        morphToColumbusView : {
            get : function() {
                return this._morphToColumbusView;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    SceneModePickerViewModel.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the view model.
     */
    SceneModePickerViewModel.prototype.destroy = function() {
        this._eventHelper.removeAll();

        destroyObject(this);
    };

    return SceneModePickerViewModel;
});

/*global define*/
define('Widgets/SceneModePicker/SceneModePicker',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/FeatureDetection',
        '../../ThirdParty/knockout',
        '../getElement',
        './SceneModePickerViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        FeatureDetection,
        knockout,
        getElement,
        SceneModePickerViewModel) {
    'use strict';

    var globePath = 'm 32.401392,4.9330437 c -7.087603,0 -14.096095,2.884602 -19.10793,7.8946843 -5.0118352,5.010083 -7.9296167,11.987468 -7.9296167,19.072999 0,7.085531 2.9177815,14.097848 7.9296167,19.107931 4.837653,4.835961 11.541408,7.631372 18.374354,7.82482 0.05712,0.01231 0.454119,0.139729 0.454119,0.139729 l 0.03493,-0.104797 c 0.08246,7.84e-4 0.162033,0.03493 0.244525,0.03493 0.08304,0 0.161515,-0.03414 0.244526,-0.03493 l 0.03493,0.104797 c 0,0 0.309474,-0.129487 0.349323,-0.139729 6.867765,-0.168094 13.582903,-2.965206 18.444218,-7.82482 2.558195,-2.5573 4.551081,-5.638134 5.903547,-8.977584 1.297191,-3.202966 2.02607,-6.661489 2.02607,-10.130347 0,-6.237309 -2.366261,-12.31219 -6.322734,-17.116794 -0.0034,-0.02316 0.0049,-0.04488 0,-0.06986 -0.01733,-0.08745 -0.104529,-0.278855 -0.104797,-0.279458 -5.31e-4,-0.0012 -0.522988,-0.628147 -0.523984,-0.62878 \
        -3.47e-4,-2.2e-4 -0.133444,-0.03532 -0.244525,-0.06987 C 51.944299,13.447603 51.751076,13.104317 51.474391,12.827728 46.462556,7.8176457 39.488996,4.9330437 32.401392,4.9330437 z m -2.130866,3.5281554 0.104797,9.6762289 c -4.111695,-0.08361 -7.109829,-0.423664 -9.257041,-0.943171 1.198093,-2.269271 2.524531,-4.124404 3.91241,-5.414496 2.167498,-2.0147811 3.950145,-2.8540169 5.239834,-3.3185619 z m 2.794579,0 c 1.280302,0.4754953 3.022186,1.3285948 5.065173,3.2486979 1.424667,1.338973 2.788862,3.303645 3.982275,5.728886 -2.29082,0.403367 -5.381258,0.621049 -8.942651,0.698645 L 33.065105,8.4611991 z m 5.728886,0.2445256 c 4.004072,1.1230822 7.793098,3.1481363 10.724195,6.0782083 0.03468,0.03466 0.07033,0.06991 0.104797,0.104797 -0.45375,0.313891 -0.923054,0.663002 -1.956205,1.082899 -0.647388,0.263114 -1.906242,0.477396 -2.829511,0.733577 -1.382296,-2.988132 \
        -3.027146,-5.368585 -4.785716,-7.0213781 -0.422866,-0.397432 -0.835818,-0.6453247 -1.25756,-0.9781032 z m -15.33525,0.7685092 c -0.106753,0.09503 -0.207753,0.145402 -0.31439,0.244526 -1.684973,1.5662541 -3.298068,3.8232211 -4.680919,6.5672591 -0.343797,-0.14942 -1.035052,-0.273198 -1.292493,-0.419186 -0.956528,-0.542427 -1.362964,-1.022024 -1.537018,-1.292493 -0.0241,-0.03745 -0.01868,-0.0401 -0.03493,-0.06986 2.250095,-2.163342 4.948824,-3.869984 7.859752,-5.0302421 z m -9.641296,7.0912431 c 0.464973,0.571618 0.937729,1.169056 1.956205,1.746612 0.349907,0.198425 1.107143,0.335404 1.537018,0.523983 -1.20166,3.172984 -1.998037,7.051901 -2.165798,11.772162 C 14.256557,30.361384 12.934823,30.161483 12.280427,29.90959 10.644437,29.279855 9.6888882,28.674891 9.1714586,28.267775 8.6540289,27.860658 8.6474751,27.778724 8.6474751,27.778724 l -0.069864,0.03493 C 9.3100294,23.691285 \
        11.163248,19.798527 13.817445,16.565477 z m 37.552149,0.523984 c 2.548924,3.289983 4.265057,7.202594 4.890513,11.318043 -0.650428,0.410896 -1.756876,1.001936 -3.563088,1.606882 -1.171552,0.392383 -3.163859,0.759153 -4.960377,1.117832 -0.04367,-4.752703 -0.784809,-8.591423 -1.88634,-11.807094 0.917574,-0.263678 2.170552,-0.486495 2.864443,-0.76851 1.274693,-0.518066 2.003942,-1.001558 2.654849,-1.467153 z m -31.439008,2.619917 c 2.487341,0.672766 5.775813,1.137775 10.479669,1.222628 l 0.104797,10.689263 0,0.03493 0,0.733577 c -5.435005,-0.09059 -9.512219,-0.519044 -12.610536,-1.117831 0.106127,-4.776683 0.879334,-8.55791 2.02607,-11.562569 z m 23.264866,0.31439 c 1.073459,3.067541 1.833795,6.821314 1.816476,11.702298 -3.054474,0.423245 -7.062018,0.648559 -11.702298,0.698644 l 0,-0.838373 -0.104796,-10.654331 c 4.082416,-0.0864 7.404468,-0.403886 9.990618,-0.908238 z \
        M 8.2632205,30.922625 c 0.7558676,0.510548 1.5529563,1.013339 3.0041715,1.57195 0.937518,0.360875 2.612202,0.647642 3.91241,0.978102 0.112814,3.85566 0.703989,7.107756 1.606883,9.920754 -1.147172,-0.324262 -2.644553,-0.640648 -3.423359,-0.978102 -1.516688,-0.657177 -2.386627,-1.287332 -2.864443,-1.71168 -0.477816,-0.424347 -0.489051,-0.489051 -0.489051,-0.489051 L 9.8002387,40.319395 C 8.791691,37.621767 8.1584238,34.769583 8.1584238,31.900727 c 0,-0.330153 0.090589,-0.648169 0.1047967,-0.978102 z m 48.2763445,0.419186 c 0.0047,0.188973 0.06986,0.36991 0.06986,0.558916 0,2.938869 -0.620228,5.873558 -1.676747,8.628261 -0.07435,0.07583 -0.06552,0.07411 -0.454119,0.349323 -0.606965,0.429857 -1.631665,1.042044 -3.318562,1.676747 -1.208528,0.454713 -3.204964,0.850894 -5.135038,1.25756 0.84593,-2.765726 1.41808,-6.005357 1.606883,-9.815957 2.232369,-0.413371 4.483758,-0.840201 \
        5.938479,-1.327425 1.410632,-0.472457 2.153108,-0.89469 2.96924,-1.327425 z m -38.530252,2.864443 c 3.208141,0.56697 7.372279,0.898588 12.575603,0.978103 l 0.174662,9.885821 c -4.392517,-0.06139 -8.106722,-0.320566 -10.863925,-0.803441 -1.051954,-2.664695 -1.692909,-6.043794 -1.88634,-10.060483 z m 26.793022,0.31439 c -0.246298,3.923551 -0.877762,7.263679 -1.816476,9.885822 -2.561957,0.361954 -5.766249,0.560708 -9.431703,0.62878 l -0.174661,-9.815957 c 4.491734,-0.04969 8.334769,-0.293032 11.42284,-0.698645 z M 12.035901,44.860585 c 0.09977,0.04523 0.105535,0.09465 0.209594,0.139729 1.337656,0.579602 3.441099,1.058072 5.589157,1.537018 1.545042,3.399208 3.548524,5.969402 5.589157,7.789888 -3.034411,-1.215537 -5.871615,-3.007978 -8.174142,-5.309699 -1.245911,-1.245475 -2.271794,-2.662961 -3.213766,-4.156936 z m 40.69605,0 c -0.941972,1.493975 -1.967855,2.911461 \
        -3.213765,4.156936 -2.74253,2.741571 -6.244106,4.696717 -9.955686,5.868615 0.261347,-0.241079 0.507495,-0.394491 0.768509,-0.663713 1.674841,-1.727516 3.320792,-4.181056 4.645987,-7.265904 2.962447,-0.503021 5.408965,-1.122293 7.161107,-1.781544 0.284034,-0.106865 0.337297,-0.207323 0.593848,-0.31439 z m -31.404076,2.305527 c 2.645807,0.376448 5.701178,0.649995 9.466635,0.698645 l 0.139729,7.789888 c -1.38739,-0.480844 -3.316218,-1.29837 -5.659022,-3.388427 -1.388822,-1.238993 -2.743668,-3.0113 -3.947342,-5.100106 z m 20.365491,0.104797 c -1.04872,2.041937 -2.174337,3.779068 -3.353494,4.995309 -1.853177,1.911459 -3.425515,2.82679 -4.611055,3.353494 l -0.139729,-7.789887 c 3.13091,-0.05714 5.728238,-0.278725 8.104278,-0.558916 z';
    var flatMapPath = 'm 2.9825053,17.550598 0,1.368113 0,26.267766 0,1.368113 1.36811,0 54.9981397,0 1.36811,0 0,-1.368113 0,-26.267766 0,-1.368113 -1.36811,0 -54.9981397,0 -1.36811,0 z m 2.73623,2.736226 10.3292497,0 0,10.466063 -10.3292497,0 0,-10.466063 z m 13.0654697,0 11.69737,0 0,10.466063 -11.69737,0 0,-10.466063 z m 14.43359,0 11.69737,0 0,10.466063 -11.69737,0 0,-10.466063 z m 14.43359,0 10.32926,0 0,10.466063 -10.32926,0 0,-10.466063 z m -41.9326497,13.202288 10.3292497,0 0,10.329252 -10.3292497,0 0,-10.329252 z m 13.0654697,0 11.69737,0 0,10.329252 -11.69737,0 0,-10.329252 z m 14.43359,0 11.69737,0 0,10.329252 -11.69737,0 0,-10.329252 z m 14.43359,0 10.32926,0 0,10.329252 -10.32926,0 0,-10.329252 z';
    var columbusViewPath = 'm 14.723969,17.675598 -0.340489,0.817175 -11.1680536,26.183638 -0.817175,1.872692 2.076986,0 54.7506996,0 2.07698,0 -0.81717,-1.872692 -11.16805,-26.183638 -0.34049,-0.817175 -0.91933,0 -32.414586,0 -0.919322,0 z m 1.838643,2.723916 6.196908,0 -2.928209,10.418977 -7.729111,0 4.460412,-10.418977 z m 9.02297,0 4.903049,0 0,10.418977 -7.831258,0 2.928209,-10.418977 z m 7.626964,0 5.584031,0 2.62176,10.418977 -8.205791,0 0,-10.418977 z m 8.410081,0 5.51593,0 4.46042,10.418977 -7.38863,0 -2.58772,-10.418977 z m -30.678091,13.142892 8.103649,0 -2.89416,10.282782 -9.6018026,0 4.3923136,-10.282782 z m 10.929711,0 8.614384,0 0,10.282782 -11.508544,0 2.89416,-10.282782 z m 11.338299,0 8.852721,0 2.58772,10.282782 -11.440441,0 0,-10.282782 z m 11.678781,0 7.86531,0 4.39231,10.282782 -9.6699,0 -2.58772,-10.282782 z';

    /**
     * <img src="images/sceneModePicker.png" style="float: left; margin-right: 10px;" width="44" height="116" />
     * <p>The SceneModePicker is a single button widget for switching between scene modes;
     * shown to the left in its expanded state. Programatic switching of scene modes will
     * be automatically reflected in the widget as long as the specified Scene
     * is used to perform the change.</p><p style="clear: both;"></p><br/>
     *
     * @alias SceneModePicker
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Scene} scene The Scene instance to use.
     * @param {Number} [duration=2.0] The time, in seconds, it takes for the scene to transition.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     *
     * @example
     * // In HTML head, include a link to the SceneModePicker.css stylesheet,
     * // and in the body, include: <div id="sceneModePickerContainer"></div>
     * // Note: This code assumes you already have a Scene instance.
     *
     * var sceneModePicker = new Cesium.SceneModePicker('sceneModePickerContainer', scene);
     */
    function SceneModePicker(container, scene, duration) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        container = getElement(container);

        var viewModel = new SceneModePickerViewModel(scene, duration);

        viewModel._globePath = globePath;
        viewModel._flatMapPath = flatMapPath;
        viewModel._columbusViewPath = columbusViewPath;

        var wrapper = document.createElement('span');
        wrapper.className = 'cesium-sceneModePicker-wrapper cesium-toolbar-button';
        container.appendChild(wrapper);

        var button = document.createElement('button');
        button.type = 'button';
        button.className = 'cesium-button cesium-toolbar-button';
        button.setAttribute('data-bind', '\
css: { "cesium-sceneModePicker-button2D": sceneMode === _sceneMode.SCENE2D,\
       "cesium-sceneModePicker-button3D": sceneMode === _sceneMode.SCENE3D,\
       "cesium-sceneModePicker-buttonColumbusView": sceneMode === _sceneMode.COLUMBUS_VIEW,\
       "cesium-sceneModePicker-selected": dropDownVisible },\
attr: { title: selectedTooltip },\
click: toggleDropDown');
        button.innerHTML = '\
<!-- ko cesiumSvgPath: { path: _globePath, width: 64, height: 64, css: "cesium-sceneModePicker-slide-svg cesium-sceneModePicker-icon3D" } --><!-- /ko -->\
<!-- ko cesiumSvgPath: { path: _flatMapPath, width: 64, height: 64, css: "cesium-sceneModePicker-slide-svg cesium-sceneModePicker-icon2D" } --><!-- /ko -->\
<!-- ko cesiumSvgPath: { path: _columbusViewPath, width: 64, height: 64, css: "cesium-sceneModePicker-slide-svg cesium-sceneModePicker-iconColumbusView" } --><!-- /ko -->';
        wrapper.appendChild(button);

        var morphTo3DButton = document.createElement('button');
        morphTo3DButton.type = 'button';
        morphTo3DButton.className = 'cesium-button cesium-toolbar-button cesium-sceneModePicker-dropDown-icon';
        morphTo3DButton.setAttribute('data-bind', '\
css: { "cesium-sceneModePicker-visible" : (dropDownVisible && (sceneMode !== _sceneMode.SCENE3D)) || (!dropDownVisible && (sceneMode === _sceneMode.SCENE3D)),\
       "cesium-sceneModePicker-none" : sceneMode === _sceneMode.SCENE3D,\
       "cesium-sceneModePicker-hidden" : !dropDownVisible },\
attr: { title: tooltip3D },\
click: morphTo3D,\
cesiumSvgPath: { path: _globePath, width: 64, height: 64 }');
        wrapper.appendChild(morphTo3DButton);

        var morphTo2DButton = document.createElement('button');
        morphTo2DButton.type = 'button';
        morphTo2DButton.className = 'cesium-button cesium-toolbar-button cesium-sceneModePicker-dropDown-icon';
        morphTo2DButton.setAttribute('data-bind', '\
css: { "cesium-sceneModePicker-visible" : (dropDownVisible && (sceneMode !== _sceneMode.SCENE2D)),\
       "cesium-sceneModePicker-none" : sceneMode === _sceneMode.SCENE2D,\
       "cesium-sceneModePicker-hidden" : !dropDownVisible },\
attr: { title: tooltip2D },\
click: morphTo2D,\
cesiumSvgPath: { path: _flatMapPath, width: 64, height: 64 }');
        wrapper.appendChild(morphTo2DButton);

        var morphToCVButton = document.createElement('button');
        morphToCVButton.type = 'button';
        morphToCVButton.className = 'cesium-button cesium-toolbar-button cesium-sceneModePicker-dropDown-icon';
        morphToCVButton.setAttribute('data-bind', '\
css: { "cesium-sceneModePicker-visible" : (dropDownVisible && (sceneMode !== _sceneMode.COLUMBUS_VIEW)) || (!dropDownVisible && (sceneMode === _sceneMode.COLUMBUS_VIEW)),\
       "cesium-sceneModePicker-none" : sceneMode === _sceneMode.COLUMBUS_VIEW,\
       "cesium-sceneModePicker-hidden" : !dropDownVisible},\
attr: { title: tooltipColumbusView },\
click: morphToColumbusView,\
cesiumSvgPath: { path: _columbusViewPath, width: 64, height: 64 }');
        wrapper.appendChild(morphToCVButton);

        knockout.applyBindings(viewModel, wrapper);

        this._viewModel = viewModel;
        this._container = container;
        this._wrapper = wrapper;

        this._closeDropDown = function(e) {
            if (!wrapper.contains(e.target)) {
                viewModel.dropDownVisible = false;
            }
        };
        if (FeatureDetection.supportsPointerEvents()) {
            document.addEventListener('pointerdown', this._closeDropDown, true);
        } else {
            document.addEventListener('mousedown', this._closeDropDown, true);
            document.addEventListener('touchstart', this._closeDropDown, true);
        }
    }

    defineProperties(SceneModePicker.prototype, {
        /**
         * Gets the parent container.
         * @memberof SceneModePicker.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof SceneModePicker.prototype
         *
         * @type {SceneModePickerViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    SceneModePicker.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    SceneModePicker.prototype.destroy = function() {
        this._viewModel.destroy();

        if (FeatureDetection.supportsPointerEvents()) {
            document.removeEventListener('pointerdown', this._closeDropDown, true);
        } else {
            document.removeEventListener('mousedown', this._closeDropDown, true);
            document.removeEventListener('touchstart', this._closeDropDown, true);
        }

        knockout.cleanNode(this._wrapper);
        this._container.removeChild(this._wrapper);

        return destroyObject(this);
    };

    return SceneModePicker;
});

/*global define*/
define('Widgets/SelectionIndicator/SelectionIndicatorViewModel',[
        '../../Core/Cartesian2',
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../Core/EasingFunction',
        '../../Scene/SceneTransforms',
        '../../ThirdParty/knockout'
    ], function(
        Cartesian2,
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        EasingFunction,
        SceneTransforms,
        knockout) {
    'use strict';

    var screenSpacePos = new Cartesian2();
    var offScreen = '-1000px';

    /**
     * The view model for {@link SelectionIndicator}.
     * @alias SelectionIndicatorViewModel
     * @constructor
     *
     * @param {Scene} scene The scene instance to use for screen-space coordinate conversion.
     * @param {Element} selectionIndicatorElement The element containing all elements that make up the selection indicator.
     * @param {Element} container The DOM element that contains the widget.
     */
    function SelectionIndicatorViewModel(scene, selectionIndicatorElement, container) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }

        if (!defined(selectionIndicatorElement)) {
            throw new DeveloperError('selectionIndicatorElement is required.');
        }

        if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        this._scene = scene;
        this._screenPositionX = offScreen;
        this._screenPositionY = offScreen;
        this._tweens = scene.tweens;
        this._container = defaultValue(container, document.body);
        this._selectionIndicatorElement = selectionIndicatorElement;
        this._scale = 1;

        /**
         * Gets or sets the world position of the object for which to display the selection indicator.
         * @type {Cartesian3}
         */
        this.position = undefined;

        /**
         * Gets or sets the visibility of the selection indicator.
         * @type {Boolean}
         */
        this.showSelection = false;

        knockout.track(this, ['position', '_screenPositionX', '_screenPositionY', '_scale', 'showSelection']);

        /**
         * Gets the visibility of the position indicator.  This can be false even if an
         * object is selected, when the selected object has no position.
         * @type {Boolean}
         */
        this.isVisible = undefined;
        knockout.defineProperty(this, 'isVisible', {
            get : function() {
                return this.showSelection && defined(this.position);
            }
        });

        knockout.defineProperty(this, '_transform', {
            get : function() {
                return 'scale(' + (this._scale) + ')';
            }
        });

        /**
         * Gets or sets the function for converting the world position of the object to the screen space position.
         *
         * @member
         * @type {SelectionIndicatorViewModel~ComputeScreenSpacePosition}
         * @default SceneTransforms.wgs84ToWindowCoordinates
         *
         * @example
         * selectionIndicatorViewModel.computeScreenSpacePosition = function(position, result) {
         *     return Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
         * };
         */
        this.computeScreenSpacePosition = function(position, result) {
            return SceneTransforms.wgs84ToWindowCoordinates(scene, position, result);
        };
    }

    /**
     * Updates the view of the selection indicator to match the position and content properties of the view model.
     * This function should be called as part of the render loop.
     */
    SelectionIndicatorViewModel.prototype.update = function() {
        if (this.showSelection && defined(this.position)) {
            var screenPosition = this.computeScreenSpacePosition(this.position, screenSpacePos);
            if (!defined(screenPosition)) {
                this._screenPositionX = offScreen;
                this._screenPositionY = offScreen;
            } else {
                var container = this._container;
                var containerWidth = container.parentNode.clientWidth;
                var containerHeight = container.parentNode.clientHeight;
                var indicatorSize = this._selectionIndicatorElement.clientWidth;
                var halfSize = indicatorSize * 0.5;

                screenPosition.x = Math.min(Math.max(screenPosition.x, -indicatorSize), containerWidth + indicatorSize) - halfSize;
                screenPosition.y = Math.min(Math.max(screenPosition.y, -indicatorSize), containerHeight + indicatorSize) - halfSize;

                this._screenPositionX = Math.floor(screenPosition.x + 0.25) + 'px';
                this._screenPositionY = Math.floor(screenPosition.y + 0.25) + 'px';
            }
        }
    };

    /**
     * Animate the indicator to draw attention to the selection.
     */
    SelectionIndicatorViewModel.prototype.animateAppear = function() {
        this._tweens.addProperty({
            object : this,
            property : '_scale',
            startValue : 2,
            stopValue : 1,
            duration : 0.8,
            easingFunction : EasingFunction.EXPONENTIAL_OUT
        });
    };

    /**
     * Animate the indicator to release the selection.
     */
    SelectionIndicatorViewModel.prototype.animateDepart = function() {
        this._tweens.addProperty({
            object : this,
            property : '_scale',
            startValue : this._scale,
            stopValue : 1.5,
            duration : 0.8,
            easingFunction : EasingFunction.EXPONENTIAL_OUT
        });
    };

    defineProperties(SelectionIndicatorViewModel.prototype, {
        /**
         * Gets the HTML element containing the selection indicator.
         * @memberof SelectionIndicatorViewModel.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the HTML element that holds the selection indicator.
         * @memberof SelectionIndicatorViewModel.prototype
         *
         * @type {Element}
         */
        selectionIndicatorElement : {
            get : function() {
                return this._selectionIndicatorElement;
            }
        },

        /**
         * Gets the scene being used.
         * @memberof SelectionIndicatorViewModel.prototype
         *
         * @type {Scene}
         */
        scene : {
            get : function() {
                return this._scene;
            }
        }
    });

    /**
     * A function that converts the world position of an object to a screen space position.
     * @callback SelectionIndicatorViewModel~ComputeScreenSpacePosition
     * @param {Cartesian3} position The position in WGS84 (world) coordinates.
     * @param {Cartesian2} result An object to return the input position transformed to window coordinates.
     * @returns {Cartesian2} The modified result parameter.
     */

    return SelectionIndicatorViewModel;
});

/*global define*/
define('Widgets/SelectionIndicator/SelectionIndicator',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        './SelectionIndicatorViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        SelectionIndicatorViewModel) {
    'use strict';

    /**
     * A widget for displaying an indicator on a selected object.
     *
     * @alias SelectionIndicator
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Scene} scene The Scene instance to use.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     */
    function SelectionIndicator(container, scene) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        container = getElement(container);

        this._container = container;

        var el = document.createElement('div');
        el.className = 'cesium-selection-wrapper';
        el.setAttribute('data-bind', '\
style: { "top" : _screenPositionY, "left" : _screenPositionX },\
css: { "cesium-selection-wrapper-visible" : isVisible }');
        container.appendChild(el);
        this._element = el;

        var svgNS = 'http://www.w3.org/2000/svg';
        var path = 'M -34 -34 L -34 -11.25 L -30 -15.25 L -30 -30 L -15.25 -30 L -11.25 -34 L -34 -34 z M 11.25 -34 L 15.25 -30 L 30 -30 L 30 -15.25 L 34 -11.25 L 34 -34 L 11.25 -34 z M -34 11.25 L -34 34 L -11.25 34 L -15.25 30 L -30 30 L -30 15.25 L -34 11.25 z M 34 11.25 L 30 15.25 L 30 30 L 15.25 30 L 11.25 34 L 34 34 L 34 11.25 z';

        var svg = document.createElementNS(svgNS, 'svg:svg');
        svg.setAttribute('width', 160);
        svg.setAttribute('height', 160);
        svg.setAttribute('viewBox', '0 0 160 160');

        var group = document.createElementNS(svgNS, 'g');
        group.setAttribute('transform', 'translate(80,80)');
        svg.appendChild(group);

        var pathElement = document.createElementNS(svgNS, 'path');
        pathElement.setAttribute('data-bind', 'attr: { transform: _transform }');
        pathElement.setAttribute('d', path);
        group.appendChild(pathElement);

        el.appendChild(svg);

        var viewModel = new SelectionIndicatorViewModel(scene, this._element, this._container);
        this._viewModel = viewModel;

        knockout.applyBindings(this._viewModel, this._element);
    }

    defineProperties(SelectionIndicator.prototype, {
        /**
         * Gets the parent container.
         * @memberof SelectionIndicator.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof SelectionIndicator.prototype
         *
         * @type {SelectionIndicatorViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    SelectionIndicator.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    SelectionIndicator.prototype.destroy = function() {
        var container = this._container;
        knockout.cleanNode(this._element);
        container.removeChild(this._element);
        return destroyObject(this);
    };

    return SelectionIndicator;
});

/*global define*/
define('Widgets/Timeline/TimelineHighlightRange',[
        '../../Core/defaultValue',
        '../../Core/JulianDate'
    ], function(
        defaultValue,
        JulianDate) {
    'use strict';

    /**
     * @private
     */
    function TimelineHighlightRange(color, heightInPx, base) {
        this._color = color;
        this._height = heightInPx;
        this._base = defaultValue(base, 0);
    }

    TimelineHighlightRange.prototype.getHeight = function() {
        return this._height;
    };

    TimelineHighlightRange.prototype.getBase = function() {
        return this._base;
    };

    TimelineHighlightRange.prototype.getStartTime = function() {
        return this._start;
    };

    TimelineHighlightRange.prototype.getStopTime = function() {
        return this._stop;
    };

    TimelineHighlightRange.prototype.setRange = function(start, stop) {
        this._start = start;
        this._stop = stop;
    };

    TimelineHighlightRange.prototype.render = function(renderState) {
        var range = '';
        if (this._start && this._stop && this._color) {
            var highlightStart = JulianDate.secondsDifference(this._start, renderState.epochJulian);
            var highlightLeft = Math.round(renderState.timeBarWidth * renderState.getAlpha(highlightStart));
            var highlightStop = JulianDate.secondsDifference(this._stop, renderState.epochJulian);
            var highlightWidth = Math.round(renderState.timeBarWidth * renderState.getAlpha(highlightStop)) - highlightLeft;
            if (highlightLeft < 0) {
                highlightWidth += highlightLeft;
                highlightLeft = 0;
            }
            if ((highlightLeft + highlightWidth) > renderState.timeBarWidth) {
                highlightWidth = renderState.timeBarWidth - highlightLeft;
            }
            if (highlightWidth > 0) {
                range = '<span class="cesium-timeline-highlight" style="left: ' + highlightLeft.toString() +
                        'px; width: ' + highlightWidth.toString() + 'px; bottom: ' + this._base.toString() +
                        'px; height: ' + this._height + 'px; background-color: ' + this._color + ';"></span>';
            }
        }
        return range;
    };

    return TimelineHighlightRange;
});

/*global define*/
define('Widgets/Timeline/TimelineTrack',[
        '../../Core/Color',
        '../../Core/defined',
        '../../Core/JulianDate'
    ], function(
        Color,
        defined,
        JulianDate) {
    'use strict';

    /**
     * @private
     */
    function TimelineTrack(interval, pixelHeight, color, backgroundColor) {
        this.interval = interval;
        this.height = pixelHeight;
        this.color = color || new Color(0.5, 0.5, 0.5, 1.0);
        this.backgroundColor = backgroundColor || new Color(0.0, 0.0, 0.0, 0.0);
    }

    TimelineTrack.prototype.render = function(context, renderState) {
        var startInterval = this.interval.start;
        var stopInterval = this.interval.stop;

        var spanStart = renderState.startJulian;
        var spanStop = JulianDate.addSeconds(renderState.startJulian, renderState.duration, new JulianDate());

        if (JulianDate.lessThan(startInterval, spanStart) && JulianDate.greaterThan(stopInterval, spanStop)) {
            //The track takes up the entire visible span.
            context.fillStyle = this.color.toCssColorString();
            context.fillRect(0, renderState.y, renderState.timeBarWidth, this.height);
        } else if (JulianDate.lessThanOrEquals(startInterval, spanStop) && JulianDate.greaterThanOrEquals(stopInterval, spanStart)) {
            //The track only takes up some of the visible span, compute that span.
            var x;
            var start, stop;
            for (x = 0; x < renderState.timeBarWidth; ++x) {
                var currentTime = JulianDate.addSeconds(renderState.startJulian, (x / renderState.timeBarWidth) * renderState.duration, new JulianDate());
                if (!defined(start) && JulianDate.greaterThanOrEquals(currentTime, startInterval)) {
                    start = x;
                } else if (!defined(stop) && JulianDate.greaterThanOrEquals(currentTime, stopInterval)) {
                    stop = x;
                }
            }

            context.fillStyle = this.backgroundColor.toCssColorString();
            context.fillRect(0, renderState.y, renderState.timeBarWidth, this.height);

            if (defined(start)) {
                if (!defined(stop)) {
                    stop = renderState.timeBarWidth;
                }
                context.fillStyle = this.color.toCssColorString();
                context.fillRect(start, renderState.y, Math.max(stop - start, 1), this.height);
            }
        }
    };

    return TimelineTrack;
});

/*global define*/
define('Widgets/Timeline/Timeline',[
        '../../Core/ClockRange',
        '../../Core/defined',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/JulianDate',
        '../getElement',
        './TimelineHighlightRange',
        './TimelineTrack'
    ], function(
        ClockRange,
        defined,
        destroyObject,
        DeveloperError,
        JulianDate,
        getElement,
        TimelineHighlightRange,
        TimelineTrack) {
    'use strict';

    var timelineWheelDelta = 1e12;

    var timelineMouseMode = {
        none : 0,
        scrub : 1,
        slide : 2,
        zoom : 3,
        touchOnly : 4
    };
    var timelineTouchMode = {
        none : 0,
        scrub : 1,
        slideZoom : 2,
        singleTap : 3,
        ignore : 4
    };

    var timelineTicScales = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0, 15.0, 30.0, 60.0, // 1min
    120.0, // 2min
    300.0, // 5min
    600.0, // 10min
    900.0, // 15min
    1800.0, // 30min
    3600.0, // 1hr
    7200.0, // 2hr
    14400.0, // 4hr
    21600.0, // 6hr
    43200.0, // 12hr
    86400.0, // 24hr
    172800.0, // 2days
    345600.0, // 4days
    604800.0, // 7days
    1296000.0, // 15days
    2592000.0, // 30days
    5184000.0, // 60days
    7776000.0, // 90days
    15552000.0, // 180days
    31536000.0, // 365days
    63072000.0, // 2years
    126144000.0, // 4years
    157680000.0, // 5years
    315360000.0, // 10years
    630720000.0, // 20years
    1261440000.0, // 40years
    1576800000.0, // 50years
    3153600000.0, // 100years
    6307200000.0, // 200years
    12614400000.0, // 400years
    15768000000.0, // 500years
    31536000000.0 // 1000years
    ];

    var timelineMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];


    /**
     * The Timeline is a widget for displaying and controlling the current scene time.
     * @alias Timeline
     * @constructor
     *
     * @param {Element} container The parent HTML container node for this widget.
     * @param {Clock} clock The clock to use.
     */
    function Timeline(container, clock) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        if (!defined(clock)) {
            throw new DeveloperError('clock is required.');
        }
        
        container = getElement(container);

        /**
         * Gets the parent container.
         * @type {Element}
         */
        this.container = container;

        var topDiv = document.createElement('div');
        topDiv.className = 'cesium-timeline-main';
        container.appendChild(topDiv);
        this._topDiv = topDiv;

        this._endJulian = undefined;
        this._epochJulian = undefined;
        this._lastXPos = undefined;
        this._scrubElement = undefined;
        this._startJulian = undefined;
        this._timeBarSecondsSpan = undefined;
        this._clock = clock;
        this._scrubJulian = clock.currentTime;
        this._mainTicSpan = -1;
        this._mouseMode = timelineMouseMode.none;
        this._touchMode = timelineTouchMode.none;
        this._touchState = {
            centerX : 0,
            spanX : 0
        };
        this._mouseX = 0;
        this._timelineDrag = 0;
        this._timelineDragLocation = undefined;
        this._lastHeight = undefined;
        this._lastWidth = undefined;

        this._topDiv.innerHTML = '<div class="cesium-timeline-bar"></div><div class="cesium-timeline-trackContainer">' +
                                     '<canvas class="cesium-timeline-tracks" width="10" height="1">' +
                                     '</canvas></div><div class="cesium-timeline-needle"></div><span class="cesium-timeline-ruler"></span>';
        this._timeBarEle = this._topDiv.childNodes[0];
        this._trackContainer = this._topDiv.childNodes[1];
        this._trackListEle = this._topDiv.childNodes[1].childNodes[0];
        this._needleEle = this._topDiv.childNodes[2];
        this._rulerEle = this._topDiv.childNodes[3];
        this._context = this._trackListEle.getContext('2d');

        this._trackList = [];
        this._highlightRanges = [];

        this.zoomTo(clock.startTime, clock.stopTime);

        this._onMouseDown = createMouseDownCallback(this);
        this._onMouseUp = createMouseUpCallback(this);
        this._onMouseMove = createMouseMoveCallback(this);
        this._onMouseWheel = createMouseWheelCallback(this);
        this._onTouchStart = createTouchStartCallback(this);
        this._onTouchMove = createTouchMoveCallback(this);
        this._onTouchEnd = createTouchEndCallback(this);

        var timeBarEle = this._timeBarEle;
        document.addEventListener('mouseup', this._onMouseUp, false);
        document.addEventListener('mousemove', this._onMouseMove, false);
        timeBarEle.addEventListener('mousedown', this._onMouseDown, false);
        timeBarEle.addEventListener('DOMMouseScroll', this._onMouseWheel, false); // Mozilla mouse wheel
        timeBarEle.addEventListener('mousewheel', this._onMouseWheel, false);
        timeBarEle.addEventListener('touchstart', this._onTouchStart, false);
        timeBarEle.addEventListener('touchmove', this._onTouchMove, false);
        timeBarEle.addEventListener('touchend', this._onTouchEnd, false);
        timeBarEle.addEventListener('touchcancel', this._onTouchEnd, false);

        this._topDiv.oncontextmenu = function() {
            return false;
        };

        clock.onTick.addEventListener(this.updateFromClock, this);
        this.updateFromClock();
    }

    /**
     * @private
     */
    Timeline.prototype.addEventListener = function(type, listener, useCapture) {
        this._topDiv.addEventListener(type, listener, useCapture);
    };

    /**
     * @private
     */
    Timeline.prototype.removeEventListener = function(type, listener, useCapture) {
        this._topDiv.removeEventListener(type, listener, useCapture);
    };

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    Timeline.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    Timeline.prototype.destroy = function() {
        this._clock.onTick.removeEventListener(this.updateFromClock, this);

        document.removeEventListener('mouseup', this._onMouseUp, false);
        document.removeEventListener('mousemove', this._onMouseMove, false);

        var timeBarEle = this._timeBarEle;
        timeBarEle.removeEventListener('mousedown', this._onMouseDown, false);
        timeBarEle.removeEventListener('DOMMouseScroll', this._onMouseWheel, false); // Mozilla mouse wheel
        timeBarEle.removeEventListener('mousewheel', this._onMouseWheel, false);
        timeBarEle.removeEventListener('touchstart', this._onTouchStart, false);
        timeBarEle.removeEventListener('touchmove', this._onTouchMove, false);
        timeBarEle.removeEventListener('touchend', this._onTouchEnd, false);
        timeBarEle.removeEventListener('touchcancel', this._onTouchEnd, false);
        this.container.removeChild(this._topDiv);
        destroyObject(this);
    };

    /**
     * @private
     */
    Timeline.prototype.addHighlightRange = function(color, heightInPx, base) {
        var newHighlightRange = new TimelineHighlightRange(color, heightInPx, base);
        this._highlightRanges.push(newHighlightRange);
        this.resize();
        return newHighlightRange;
    };

    /**
     * @private
     */
    Timeline.prototype.addTrack = function(interval, heightInPx, color, backgroundColor) {
        var newTrack = new TimelineTrack(interval, heightInPx, color, backgroundColor);
        this._trackList.push(newTrack);
        this._lastHeight = undefined;
        this.resize();
        return newTrack;
    };

    /**
     * Sets the view to the provided times.
     *
     * @param {JulianDate} startTime The start time.
     * @param {JulianDate} stopTime The stop time.
     */
    Timeline.prototype.zoomTo = function(startTime, stopTime) {
                if (!defined(startTime)) {
            throw new DeveloperError('startTime is required.');
        }
        if (!defined(stopTime)) {
            throw new DeveloperError('stopTime is required');
        }
        if (JulianDate.lessThanOrEquals(stopTime, startTime)) {
            throw new DeveloperError('Start time must come before end time.');
        }
        
        this._startJulian = startTime;
        this._endJulian = stopTime;
        this._timeBarSecondsSpan = JulianDate.secondsDifference(stopTime, startTime);

        // If clock is not unbounded, clamp timeline range to clock.
        if (this._clock && (this._clock.clockRange !== ClockRange.UNBOUNDED)) {
            var clockStart = this._clock.startTime;
            var clockEnd = this._clock.stopTime;
            var clockSpan = JulianDate.secondsDifference(clockEnd, clockStart);
            var startOffset = JulianDate.secondsDifference(clockStart, this._startJulian);
            var endOffset = JulianDate.secondsDifference(clockEnd, this._endJulian);

            if (this._timeBarSecondsSpan >= clockSpan) {
                // if new duration longer than clock range duration, clamp to full range.
                this._timeBarSecondsSpan = clockSpan;
                this._startJulian = this._clock.startTime;
                this._endJulian = this._clock.stopTime;
            } else if (startOffset > 0) {
                // if timeline start is before clock start, shift right
                this._endJulian = JulianDate.addSeconds(this._endJulian, startOffset, new JulianDate());
                this._startJulian = clockStart;
                this._timeBarSecondsSpan = JulianDate.secondsDifference(this._endJulian, this._startJulian);
            } else if (endOffset < 0) {
                // if timeline end is after clock end, shift left
                this._startJulian = JulianDate.addSeconds(this._startJulian, endOffset, new JulianDate());
                this._endJulian = clockEnd;
                this._timeBarSecondsSpan = JulianDate.secondsDifference(this._endJulian, this._startJulian);
            }
        }

        this._makeTics();

        var evt = document.createEvent('Event');
        evt.initEvent('setzoom', true, true);
        evt.startJulian = this._startJulian;
        evt.endJulian = this._endJulian;
        evt.epochJulian = this._epochJulian;
        evt.totalSpan = this._timeBarSecondsSpan;
        evt.mainTicSpan = this._mainTicSpan;
        this._topDiv.dispatchEvent(evt);
    };

    /**
     * @private
     */
    Timeline.prototype.zoomFrom = function(amount) {
        var centerSec = JulianDate.secondsDifference(this._scrubJulian, this._startJulian);
        if ((amount > 1) || (centerSec < 0) || (centerSec > this._timeBarSecondsSpan)) {
            centerSec = this._timeBarSecondsSpan * 0.5;
        } else {
            centerSec += (centerSec - this._timeBarSecondsSpan * 0.5);
        }
        var centerSecFlip = this._timeBarSecondsSpan - centerSec;
        this.zoomTo(JulianDate.addSeconds(this._startJulian, centerSec - (centerSec * amount), new JulianDate()), JulianDate.addSeconds(this._endJulian, (centerSecFlip * amount) - centerSecFlip, new JulianDate()));
    };

    function twoDigits(num) {
        return ((num < 10) ? ('0' + num.toString()) : num.toString());
    }

    /**
     * @private
     */
    Timeline.prototype.makeLabel = function(time) {
        var gregorian = JulianDate.toGregorianDate(time);
        var millisecond = gregorian.millisecond, millisecondString = ' UTC';
        if ((millisecond > 0) && (this._timeBarSecondsSpan < 3600)) {
            millisecondString = Math.floor(millisecond).toString();
            while (millisecondString.length < 3) {
                millisecondString = '0' + millisecondString;
            }
            millisecondString = '.' + millisecondString;
        }

        return timelineMonthNames[gregorian.month - 1] + ' ' + gregorian.day + ' ' + gregorian.year + ' ' + twoDigits(gregorian.hour) +
            ':' + twoDigits(gregorian.minute) + ':' + twoDigits(gregorian.second) + millisecondString;
    };

    /**
     * @private
     */
    Timeline.prototype.smallestTicInPixels = 7.0;

    /**
     * @private
     */
    Timeline.prototype._makeTics = function() {
        var timeBar = this._timeBarEle;

        var seconds = JulianDate.secondsDifference(this._scrubJulian, this._startJulian);
        var xPos = Math.round(seconds * this._topDiv.clientWidth / this._timeBarSecondsSpan);
        var scrubX = xPos - 8, tic;
        var widget = this;

        this._needleEle.style.left = xPos.toString() + 'px';

        var tics = '';

        var minimumDuration = 0.01;
        var maximumDuration = 31536000000.0; // ~1000 years
        var epsilon = 1e-10;

        // If time step size is known, enter it here...
        var minSize = 0;

        var duration = this._timeBarSecondsSpan;
        if (duration < minimumDuration) {
            duration = minimumDuration;
            this._timeBarSecondsSpan = minimumDuration;
            this._endJulian = JulianDate.addSeconds(this._startJulian, minimumDuration, new JulianDate());
        } else if (duration > maximumDuration) {
            duration = maximumDuration;
            this._timeBarSecondsSpan = maximumDuration;
            this._endJulian = JulianDate.addSeconds(this._startJulian, maximumDuration, new JulianDate());
        }

        var timeBarWidth = this._timeBarEle.clientWidth;
        if (timeBarWidth < 10) {
            timeBarWidth = 10;
        }
        var startJulian = this._startJulian;

        // epsilonTime: a small fraction of one pixel width of the timeline, measured in seconds.
        var epsilonTime = Math.min((duration / timeBarWidth) * 1e-5, 0.4);

        // epochJulian: a nearby time to be considered "zero seconds", should be a round-ish number by human standards.
        var epochJulian;
        var gregorianDate = JulianDate.toGregorianDate(startJulian);
        if (duration > 315360000) { // 3650+ days visible, epoch is start of the first visible century.
            epochJulian = JulianDate.fromDate(new Date(Date.UTC(Math.floor(gregorianDate.year / 100) * 100, 0)));
        } else if (duration > 31536000) { // 365+ days visible, epoch is start of the first visible decade.
            epochJulian = JulianDate.fromDate(new Date(Date.UTC(Math.floor(gregorianDate.year / 10) * 10, 0)));
        } else if (duration > 86400) { // 1+ day(s) visible, epoch is start of the year.
            epochJulian = JulianDate.fromDate(new Date(Date.UTC(gregorianDate.year, 0)));
        } else { // Less than a day on timeline, epoch is midnight of the visible day.
            epochJulian = JulianDate.fromDate(new Date(Date.UTC(gregorianDate.year, gregorianDate.month, gregorianDate.day)));
        }

        // startTime: Seconds offset of the left side of the timeline from epochJulian.
        var startTime = JulianDate.secondsDifference(this._startJulian, JulianDate.addSeconds(epochJulian, epsilonTime, new JulianDate()));
        // endTime: Seconds offset of the right side of the timeline from epochJulian.
        var endTime = startTime + duration;
        this._epochJulian = epochJulian;

        function getStartTic(ticScale) {
            return Math.floor(startTime / ticScale) * ticScale;
        }

        function getNextTic(tic, ticScale) {
            return Math.ceil((tic / ticScale) + 0.5) * ticScale;
        }

        function getAlpha(time) {
            return (time - startTime) / duration;
        }

        function remainder(x, y) {
            //return x % y;
            return x - (y * Math.round(x / y));
        }

        // Width in pixels of a typical label, plus padding
        this._rulerEle.innerHTML = this.makeLabel(JulianDate.addSeconds(this._endJulian, -minimumDuration, new JulianDate()));
        var sampleWidth = this._rulerEle.offsetWidth + 20;
        if (sampleWidth < 30) {
            // Workaround an apparent IE bug with measuring the width after going full-screen from inside an iframe.
            sampleWidth = 180;
        }

        var origMinSize = minSize;
        minSize -= epsilon;

        var renderState = {
            startTime : startTime,
            startJulian : startJulian,
            epochJulian : epochJulian,
            duration : duration,
            timeBarWidth : timeBarWidth,
            getAlpha : getAlpha
        };
        this._highlightRanges.forEach(function(highlightRange) {
            tics += highlightRange.render(renderState);
        });

        // Calculate tic mark label spacing in the TimeBar.
        var mainTic = 0.0, subTic = 0.0, tinyTic = 0.0;
        // Ideal labeled tic as percentage of zoom interval
        var idealTic = sampleWidth / timeBarWidth;
        if (idealTic > 1.0) {
            // Clamp to width of window, for thin windows.
            idealTic = 1.0;
        }
        // Ideal labeled tic size in seconds
        idealTic *= this._timeBarSecondsSpan;
        var ticIndex = -1, smallestIndex = -1;

        var i, ticScaleLen = timelineTicScales.length;
        for (i = 0; i < ticScaleLen; ++i) {
            var sc = timelineTicScales[i];
            ++ticIndex;
            mainTic = sc;
            // Find acceptable main tic size not smaller than ideal size.
            if ((sc > idealTic) && (sc > minSize)) {
                break;
            }
            if ((smallestIndex < 0) && ((timeBarWidth * (sc / this._timeBarSecondsSpan)) >= this.smallestTicInPixels)) {
                smallestIndex = ticIndex;
            }
        }
        if (ticIndex > 0) {
            while (ticIndex > 0) // Compute sub-tic size that evenly divides main tic.
            {
                --ticIndex;
                if (Math.abs(remainder(mainTic, timelineTicScales[ticIndex])) < 0.00001) {
                    if (timelineTicScales[ticIndex] >= minSize) {
                        subTic = timelineTicScales[ticIndex];
                    }
                    break;
                }
            }

            if (smallestIndex >= 0) {
                while (smallestIndex < ticIndex) // Compute tiny tic size that evenly divides sub-tic.
                {
                    if ((Math.abs(remainder(subTic, timelineTicScales[smallestIndex])) < 0.00001) && (timelineTicScales[smallestIndex] >= minSize)) {
                        tinyTic = timelineTicScales[smallestIndex];
                        break;
                    }
                    ++smallestIndex;
                }
            }
        }

        minSize = origMinSize;
        if ((minSize > epsilon) && (tinyTic < 0.00001) && (Math.abs(minSize - mainTic) > epsilon)) {
            tinyTic = minSize;
            if (minSize <= (mainTic + epsilon)) {
                subTic = 0.0;
            }
        }

        var lastTextLeft = -999999, textWidth;
        if ((timeBarWidth * (tinyTic / this._timeBarSecondsSpan)) >= 3.0) {
            for (tic = getStartTic(tinyTic); tic <= endTime; tic = getNextTic(tic, tinyTic)) {
                tics += '<span class="cesium-timeline-ticTiny" style="left: ' + Math.round(timeBarWidth * getAlpha(tic)).toString() + 'px;"></span>';
            }
        }
        if ((timeBarWidth * (subTic / this._timeBarSecondsSpan)) >= 3.0) {
            for (tic = getStartTic(subTic); tic <= endTime; tic = getNextTic(tic, subTic)) {
                tics += '<span class="cesium-timeline-ticSub" style="left: ' + Math.round(timeBarWidth * getAlpha(tic)).toString() + 'px;"></span>';
            }
        }
        if ((timeBarWidth * (mainTic / this._timeBarSecondsSpan)) >= 2.0) {
            this._mainTicSpan = mainTic;
            endTime += mainTic;
            tic = getStartTic(mainTic);
            var leapSecond = JulianDate.computeTaiMinusUtc(epochJulian);
            while (tic <= endTime) {
                var ticTime = JulianDate.addSeconds(startJulian, tic - startTime, new JulianDate());
                if (mainTic > 2.1) {
                    var ticLeap = JulianDate.computeTaiMinusUtc(ticTime);
                    if (Math.abs(ticLeap - leapSecond) > 0.1) {
                        tic += (ticLeap - leapSecond);
                        ticTime = JulianDate.addSeconds(startJulian, tic - startTime, new JulianDate());
                    }
                }
                var ticLeft = Math.round(timeBarWidth * getAlpha(tic));
                var ticLabel = this.makeLabel(ticTime);
                this._rulerEle.innerHTML = ticLabel;
                textWidth = this._rulerEle.offsetWidth;
                if (textWidth < 10) {
                    // IE iframe fullscreen sampleWidth workaround, continued.
                    textWidth = sampleWidth;
                }
                var labelLeft = ticLeft - ((textWidth / 2) - 1);
                if (labelLeft > lastTextLeft) {
                    lastTextLeft = labelLeft + textWidth + 5;
                    tics += '<span class="cesium-timeline-ticMain" style="left: ' + ticLeft.toString() + 'px;"></span>' + '<span class="cesium-timeline-ticLabel" style="left: ' + labelLeft.toString() +
                            'px;">' + ticLabel + '</span>';
                } else {
                    tics += '<span class="cesium-timeline-ticSub" style="left: ' + ticLeft.toString() + 'px;"></span>';
                }
                tic = getNextTic(tic, mainTic);
            }
        } else {
            this._mainTicSpan = -1;
        }

        tics += '<span class="cesium-timeline-icon16" style="left:' + scrubX + 'px;bottom:0;background-position: 0 0;"></span>';
        timeBar.innerHTML = tics;
        this._scrubElement = timeBar.lastChild;

        // Clear track canvas.
        this._context.clearRect(0, 0, this._trackListEle.width, this._trackListEle.height);

        renderState.y = 0;
        this._trackList.forEach(function(track) {
            track.render(widget._context, renderState);
            renderState.y += track.height;
        });
    };

    /**
     * @private
     */
    Timeline.prototype.updateFromClock = function() {
        this._scrubJulian = this._clock.currentTime;
        var scrubElement = this._scrubElement;
        if (defined(this._scrubElement)) {
            var seconds = JulianDate.secondsDifference(this._scrubJulian, this._startJulian);
            var xPos = Math.round(seconds * this._topDiv.clientWidth / this._timeBarSecondsSpan);

            if (this._lastXPos !== xPos) {
                this._lastXPos = xPos;

                scrubElement.style.left = (xPos - 8) + 'px';
                this._needleEle.style.left = xPos + 'px';
            }
        }
        if (defined(this._timelineDragLocation)) {
            this._setTimeBarTime(this._timelineDragLocation, this._timelineDragLocation * this._timeBarSecondsSpan / this._topDiv.clientWidth);
            this.zoomTo(JulianDate.addSeconds(this._startJulian, this._timelineDrag, new JulianDate()), JulianDate.addSeconds(this._endJulian, this._timelineDrag, new JulianDate()));
        }
    };

    /**
     * @private
     */
    Timeline.prototype._setTimeBarTime = function(xPos, seconds) {
        xPos = Math.round(xPos);
        this._scrubJulian = JulianDate.addSeconds(this._startJulian, seconds, new JulianDate());
        if (this._scrubElement) {
            var scrubX = xPos - 8;
            this._scrubElement.style.left = scrubX.toString() + 'px';
            this._needleEle.style.left = xPos.toString() + 'px';
        }

        var evt = document.createEvent('Event');
        evt.initEvent('settime', true, true);
        evt.clientX = xPos;
        evt.timeSeconds = seconds;
        evt.timeJulian = this._scrubJulian;
        evt.clock = this._clock;
        this._topDiv.dispatchEvent(evt);
    };

    function createMouseDownCallback(timeline) {
        return function(e) {
            if (timeline._mouseMode !== timelineMouseMode.touchOnly) {
                if (e.button === 0) {
                    timeline._mouseMode = timelineMouseMode.scrub;
                    if (timeline._scrubElement) {
                        timeline._scrubElement.style.backgroundPosition = '-16px 0';
                    }
                    timeline._onMouseMove(e);
                } else {
                    timeline._mouseX = e.clientX;
                    if (e.button === 2) {
                        timeline._mouseMode = timelineMouseMode.zoom;
                    } else {
                        timeline._mouseMode = timelineMouseMode.slide;
                    }
                }
            }
            e.preventDefault();
        };
    }

    function createMouseUpCallback(timeline) {
        return function(e) {
            timeline._mouseMode = timelineMouseMode.none;
            if (timeline._scrubElement) {
                timeline._scrubElement.style.backgroundPosition = '0 0';
            }
            timeline._timelineDrag = 0;
            timeline._timelineDragLocation = undefined;
        };
    }

    function createMouseMoveCallback(timeline) {
        return function(e) {
            var dx;
            if (timeline._mouseMode === timelineMouseMode.scrub) {
                e.preventDefault();
                var x = e.clientX - timeline._topDiv.getBoundingClientRect().left;

                if (x < 0) {
                    timeline._timelineDragLocation = 0;
                    timeline._timelineDrag = -0.01 * timeline._timeBarSecondsSpan;
                } else if (x > timeline._topDiv.clientWidth) {
                    timeline._timelineDragLocation = timeline._topDiv.clientWidth;
                    timeline._timelineDrag = 0.01 * timeline._timeBarSecondsSpan;
                } else {
                    timeline._timelineDragLocation = undefined;
                    timeline._setTimeBarTime(x, x * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth);
                }

            } else if (timeline._mouseMode === timelineMouseMode.slide) {
                dx = timeline._mouseX - e.clientX;
                timeline._mouseX = e.clientX;
                if (dx !== 0) {
                    var dsec = dx * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth;
                    timeline.zoomTo(JulianDate.addSeconds(timeline._startJulian, dsec, new JulianDate()), JulianDate.addSeconds(timeline._endJulian, dsec, new JulianDate()));
                }
            } else if (timeline._mouseMode === timelineMouseMode.zoom) {
                dx = timeline._mouseX - e.clientX;
                timeline._mouseX = e.clientX;
                if (dx !== 0) {
                    timeline.zoomFrom(Math.pow(1.01, dx));
                }
            }
        };
    }

    function createMouseWheelCallback(timeline) {
        return function(e) {
            var dy = e.wheelDeltaY || e.wheelDelta || (-e.detail);
            timelineWheelDelta = Math.max(Math.min(Math.abs(dy), timelineWheelDelta), 1);
            dy /= timelineWheelDelta;
            timeline.zoomFrom(Math.pow(1.05, -dy));
        };
    }

    function createTouchStartCallback(timeline) {
        return function(e) {
            var len = e.touches.length, seconds, xPos, leftX = timeline._topDiv.getBoundingClientRect().left;
            e.preventDefault();
            timeline._mouseMode = timelineMouseMode.touchOnly;
            if (len === 1) {
                seconds = JulianDate.secondsDifference(timeline._scrubJulian, timeline._startJulian);
                xPos = Math.round(seconds * timeline._topDiv.clientWidth / timeline._timeBarSecondsSpan + leftX);
                if (Math.abs(e.touches[0].clientX - xPos) < 50) {
                    timeline._touchMode = timelineTouchMode.scrub;
                    if (timeline._scrubElement) {
                        timeline._scrubElement.style.backgroundPosition = (len === 1) ? '-16px 0' : '0 0';
                    }
                } else {
                    timeline._touchMode = timelineTouchMode.singleTap;
                    timeline._touchState.centerX = e.touches[0].clientX - leftX;
                }
            } else if (len === 2) {
                timeline._touchMode = timelineTouchMode.slideZoom;
                timeline._touchState.centerX = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX;
                timeline._touchState.spanX = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
            } else {
                timeline._touchMode = timelineTouchMode.ignore;
            }
        };
    }

    function createTouchEndCallback(timeline) {
        return function(e) {
            var len = e.touches.length, leftX = timeline._topDiv.getBoundingClientRect().left;
            if (timeline._touchMode === timelineTouchMode.singleTap) {
                timeline._touchMode = timelineTouchMode.scrub;
                timeline._onTouchMove(e);
            } else if (timeline._touchMode === timelineTouchMode.scrub) {
                timeline._onTouchMove(e);
            }
            timeline._mouseMode = timelineMouseMode.touchOnly;
            if (len !== 1) {
                timeline._touchMode = (len > 0) ? timelineTouchMode.ignore : timelineTouchMode.none;
            } else if (timeline._touchMode === timelineTouchMode.slideZoom) {
                timeline._touchState.centerX = e.touches[0].clientX - leftX;
            }
            if (timeline._scrubElement) {
                timeline._scrubElement.style.backgroundPosition = '0 0';
            }
        };
    }

    function createTouchMoveCallback(timeline) {
        return function(e) {
            var dx, x, len, newCenter, newSpan, newStartTime, zoom = 1, leftX = timeline._topDiv.getBoundingClientRect().left;
            if (timeline._touchMode === timelineTouchMode.singleTap) {
                timeline._touchMode = timelineTouchMode.slideZoom;
            }
            timeline._mouseMode = timelineMouseMode.touchOnly;
            if (timeline._touchMode === timelineTouchMode.scrub) {
                e.preventDefault();
                if (e.changedTouches.length === 1) {
                    x = e.changedTouches[0].clientX - leftX;
                    if ((x >= 0) && (x <= timeline._topDiv.clientWidth)) {
                        timeline._setTimeBarTime(x, x * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth);
                    }
                }
            } else if (timeline._touchMode === timelineTouchMode.slideZoom) {
                len = e.touches.length;
                if (len === 2) {
                    newCenter = (e.touches[0].clientX + e.touches[1].clientX) * 0.5 - leftX;
                    newSpan = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
                } else if (len === 1) {
                    newCenter = e.touches[0].clientX - leftX;
                    newSpan = 0;
                }

                if (defined(newCenter)) {
                    if ((newSpan > 0) && (timeline._touchState.spanX > 0)) {
                        // Zoom and slide
                        zoom = (timeline._touchState.spanX / newSpan);
                        newStartTime = JulianDate.addSeconds(timeline._startJulian, ((timeline._touchState.centerX * timeline._timeBarSecondsSpan) - (newCenter * timeline._timeBarSecondsSpan * zoom)) / timeline._topDiv.clientWidth, new JulianDate());
                    } else {
                        // Slide to newCenter
                        dx = timeline._touchState.centerX - newCenter;
                        newStartTime = JulianDate.addSeconds(timeline._startJulian, dx * timeline._timeBarSecondsSpan / timeline._topDiv.clientWidth, new JulianDate());
                    }

                    timeline.zoomTo(newStartTime, JulianDate.addSeconds(newStartTime, timeline._timeBarSecondsSpan * zoom, new JulianDate()));
                    timeline._touchState.centerX = newCenter;
                    timeline._touchState.spanX = newSpan;
                }
            }
        };
    }

    /**
     * Resizes the widget to match the container size.
     */
    Timeline.prototype.resize = function() {
        var width = this.container.clientWidth;
        var height = this.container.clientHeight;

        if (width === this._lastWidth && height === this._lastHeight) {
            return;
        }

        this._trackContainer.style.height = height + 'px';

        var trackListHeight = 1;
        this._trackList.forEach(function(track) {
            trackListHeight += track.height;
        });
        this._trackListEle.style.height = trackListHeight.toString() + 'px';
        this._trackListEle.width = this._trackListEle.clientWidth;
        this._trackListEle.height = trackListHeight;
        this._makeTics();

        this._lastXPos = undefined;
        this._lastWidth = width;
        this._lastHeight = height;
    };

    return Timeline;
});

/*global define*/
define('Widgets/VRButton/VRButtonViewModel',[
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/Fullscreen',
        '../../ThirdParty/knockout',
        '../../ThirdParty/NoSleep',
        '../createCommand',
        '../getElement'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Fullscreen,
        knockout,
        NoSleep,
        createCommand,
        getElement) {
    'use strict';

    function lockScreen(orientation) {
        var locked = false;
        var screen = window.screen;
        if (defined(screen)) {
            if (defined(screen.lockOrientation)) {
                locked = screen.lockOrientation(orientation);
            } else if (defined(screen.mozLockOrientation)) {
                locked = screen.mozLockOrientation(orientation);
            } else if (defined(screen.msLockOrientation)) {
                locked = screen.msLockOrientation(orientation);
            } else if (defined(screen.orientation && screen.orientation.lock)) {
                locked = screen.orientation.lock(orientation);
            }
        }
        return locked;
    }

    function unlockScreen() {
        var screen = window.screen;
        if (defined(screen)) {
            if (defined(screen.unlockOrientation)) {
                screen.unlockOrientation();
            } else if (defined(screen.mozUnlockOrientation)) {
                screen.mozUnlockOrientation();
            } else if (defined(screen.msUnlockOrientation)) {
                screen.msUnlockOrientation();
            } else if (defined(screen.orientation && screen.orientation.unlock)) {
                screen.orientation.unlock();
            }
        }
    }

    function toggleVR(viewModel, scene, isVRMode) {
        if (isVRMode()) {
            scene.useWebVR = false;
            if (viewModel._locked) {
                unlockScreen();
                viewModel._locked = false;
            }
            viewModel._noSleep.disable();
            Fullscreen.exitFullscreen();
            isVRMode(false);
        } else {
            if (!Fullscreen.fullscreen) {
                Fullscreen.requestFullscreen(viewModel._vrElement);
            }
            viewModel._noSleep.enable();
            if (!viewModel._locked) {
                viewModel._locked = lockScreen('landscape');
            }
            scene.useWebVR = true;
            isVRMode(true);
        }
    }

    /**
     * The view model for {@link VRButton}.
     * @alias VRButtonViewModel
     * @constructor
     *
     * @param {Scene} scene The scene.
     * @param {Element|String} [vrElement=document.body] The element or id to be placed into VR mode.
     */
    function VRButtonViewModel(scene, vrElement) {
                if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        var that = this;

        var isEnabled = knockout.observable(Fullscreen.enabled);
        var isVRMode = knockout.observable(false);

        /**
         * Gets whether or not VR mode is active.
         *
         * @type {Boolean}
         */
        this.isVRMode = undefined;
        knockout.defineProperty(this, 'isVRMode', {
            get : function() {
                return isVRMode();
            }
        });

        /**
         * Gets or sets whether or not VR functionality should be enabled.
         *
         * @type {Boolean}
         * @see Fullscreen.enabled
         */
        this.isVREnabled = undefined;
        knockout.defineProperty(this, 'isVREnabled', {
            get : function() {
                return isEnabled();
            },
            set : function(value) {
                isEnabled(value && Fullscreen.enabled);
            }
        });

        /**
         * Gets the tooltip.  This property is observable.
         *
         * @type {String}
         */
        this.tooltip = undefined;
        knockout.defineProperty(this, 'tooltip', function() {
            if (!isEnabled()) {
                return 'VR mode is unavailable';
            }
            return isVRMode() ? 'Exit VR mode' : 'Enter VR mode';
        });

        this._locked = false;
        this._noSleep = new NoSleep();

        this._command = createCommand(function() {
            toggleVR(that, scene, isVRMode);
        }, knockout.getObservable(this, 'isVREnabled'));

        this._vrElement = defaultValue(getElement(vrElement), document.body);

        this._callback = function() {
            if (!Fullscreen.fullscreen && isVRMode()) {
                scene.useWebVR = false;
                if (that._locked) {
                    unlockScreen();
                    that._locked = false;
                }
                that._noSleep.disable();
                isVRMode(false);
            }
        };
        document.addEventListener(Fullscreen.changeEventName, this._callback);
    }

    defineProperties(VRButtonViewModel.prototype, {
        /**
         * Gets or sets the HTML element to place into VR mode when the
         * corresponding button is pressed.
         * @memberof VRButtonViewModel.prototype
         *
         * @type {Element}
         */
        vrElement : {
            //TODO:@exception {DeveloperError} value must be a valid HTML Element.
            get : function() {
                return this._vrElement;
            },
            set : function(value) {
                                if (!(value instanceof Element)) {
                    throw new DeveloperError('value must be a valid Element.');
                }
                
                this._vrElement = value;
            }
        },

        /**
         * Gets the Command to toggle VR mode.
         * @memberof VRButtonViewModel.prototype
         *
         * @type {Command}
         */
        command : {
            get : function() {
                return this._command;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    VRButtonViewModel.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the view model.  Should be called to
     * properly clean up the view model when it is no longer needed.
     */
    VRButtonViewModel.prototype.destroy = function() {
        destroyObject(this);
    };

    return VRButtonViewModel;
});

/*global define*/
define('Widgets/VRButton/VRButton',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../ThirdParty/knockout',
        '../getElement',
        './VRButtonViewModel'
    ], function(
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        knockout,
        getElement,
        VRButtonViewModel) {
    'use strict';

    var enterVRPath = 'M 5.3125 6.375 C 4.008126 6.375 2.96875 7.4141499 2.96875 8.71875 L 2.96875 19.5 C 2.96875 20.8043 4.008126 21.875 5.3125 21.875 L 13.65625 21.875 C 13.71832 20.0547 14.845166 18.59375 16.21875 18.59375 C 17.592088 18.59375 18.71881 20.0552 18.78125 21.875 L 27.09375 21.875 C 28.398125 21.875 29.4375 20.8043 29.4375 19.5 L 29.4375 8.71875 C 29.4375 7.4141499 28.398125 6.375 27.09375 6.375 L 5.3125 6.375 z M 9.625 10.4375 C 11.55989 10.4375 13.125 12.03385 13.125 13.96875 C 13.125 15.90365 11.55989 17.46875 9.625 17.46875 C 7.69011 17.46875 6.125 15.90365 6.125 13.96875 C 6.125 12.03385 7.69011 10.4375 9.625 10.4375 z M 22.46875 10.4375 C 24.40364 10.4375 25.96875 12.03385 25.96875 13.96875 C 25.96875 15.90365 24.40364 17.46875 22.46875 17.46875 C 20.53386 17.46875 18.96875 15.90365 18.96875 13.96875 C 18.96875 12.03385 20.53386 10.4375 22.46875 10.4375 z';
    var exitVRPath = 'M 25.770585,2.4552065 C 15.72282,13.962707 10.699956,19.704407 8.1768352,22.580207 c -1.261561,1.4379 -1.902282,2.1427 -2.21875,2.5 -0.141624,0.1599 -0.208984,0.2355 -0.25,0.2813 l 0.6875,0.75 c 10e-5,-10e-5 0.679191,0.727 0.6875,0.7187 0.01662,-0.016 0.02451,-0.024 0.03125,-0.031 0.01348,-0.014 0.04013,-0.038 0.0625,-0.062 0.04474,-0.05 0.120921,-0.1315 0.28125,-0.3126 0.320657,-0.3619 0.956139,-1.0921 2.2187499,-2.5312 2.5252219,-2.8781 7.5454589,-8.6169 17.5937499,-20.1250005 l -1.5,-1.3125 z m -20.5624998,3.9063 c -1.304375,0 -2.34375,1.0391 -2.34375,2.3437 l 0,10.8125005 c 0,1.3043 1.039375,2.375 2.34375,2.375 l 2.25,0 c 1.9518039,-2.2246 7.4710958,-8.5584 13.5624998,-15.5312005 l -15.8124998,0 z m 21.1249998,0 c -1.855467,2.1245 -2.114296,2.4005 -3.59375,4.0936995 1.767282,0.1815 3.15625,1.685301 3.15625,3.500001 0,1.9349 -1.56511,3.5 -3.5,3.5 -1.658043,0 -3.043426,-1.1411 -3.40625,-2.6875 -1.089617,1.2461 -2.647139,2.9988 -3.46875,3.9375 0.191501,-0.062 0.388502,-0.094 0.59375,-0.094 1.373338,0 2.50006,1.4614 2.5625,3.2812 l 8.3125,0 c 1.304375,0 2.34375,-1.0707 2.34375,-2.375 l 0,-10.8125005 c 0,-1.3046 -1.039375,-2.3437 -2.34375,-2.3437 l -0.65625,0 z M 9.5518351,10.423906 c 1.9348899,0 3.4999999,1.596401 3.4999999,3.531301 0,1.9349 -1.56511,3.5 -3.4999999,3.5 -1.9348899,0 -3.4999999,-1.5651 -3.4999999,-3.5 0,-1.9349 1.56511,-3.531301 3.4999999,-3.531301 z m 4.2187499,10.312601 c -0.206517,0.2356 -0.844218,0.9428 -1.03125,1.1562 l 0.8125,0 c 0.01392,-0.4081 0.107026,-0.7968 0.21875,-1.1562 z';

    /**
     * A single button widget for toggling vr mode.
     *
     * @alias VRButton
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Scene} scene The scene.
     * @param {Element|String} [vrElement=document.body] The element or id to be placed into vr mode.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     */
    function VRButton(container, scene, vrElement) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        if (!defined(scene)) {
            throw new DeveloperError('scene is required.');
        }
        
        container = getElement(container);

        var viewModel = new VRButtonViewModel(scene, vrElement);

        viewModel._exitVRPath = exitVRPath;
        viewModel._enterVRPath = enterVRPath;

        var element = document.createElement('button');
        element.type = 'button';
        element.className = 'cesium-button cesium-vrButton';
        element.setAttribute('data-bind', '\
attr: { title: tooltip },\
click: command,\
enable: isVREnabled,\
cesiumSvgPath: { path: isVRMode ? _exitVRPath : _enterVRPath, width: 32, height: 32 }');

        container.appendChild(element);

        knockout.applyBindings(viewModel, element);

        this._container = container;
        this._viewModel = viewModel;
        this._element = element;
    }

    defineProperties(VRButton.prototype, {
        /**
         * Gets the parent container.
         * @memberof VRButton.prototype
         *
         * @type {Element}
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the view model.
         * @memberof VRButton.prototype
         *
         * @type {VRButtonViewModel}
         */
        viewModel : {
            get : function() {
                return this._viewModel;
            }
        }
    });

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    VRButton.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    VRButton.prototype.destroy = function() {
        this._viewModel.destroy();

        knockout.cleanNode(this._element);
        this._container.removeChild(this._element);

        return destroyObject(this);
    };

    return VRButton;
});

/*global define*/
define('Widgets/Viewer/Viewer',[
        '../../Core/BoundingSphere',
        '../../Core/Cartesian3',
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/destroyObject',
        '../../Core/DeveloperError',
        '../../Core/Event',
        '../../Core/EventHelper',
        '../../Core/isArray',
        '../../Core/Matrix4',
        '../../Core/Rectangle',
        '../../Core/ScreenSpaceEventType',
        '../../DataSources/BoundingSphereState',
        '../../DataSources/ConstantPositionProperty',
        '../../DataSources/DataSourceCollection',
        '../../DataSources/DataSourceDisplay',
        '../../DataSources/Entity',
        '../../DataSources/EntityView',
        '../../DataSources/Property',
        '../../Scene/ImageryLayer',
        '../../Scene/SceneMode',
        '../../ThirdParty/knockout',
        '../../ThirdParty/when',
        '../Animation/Animation',
        '../Animation/AnimationViewModel',
        '../BaseLayerPicker/BaseLayerPicker',
        '../BaseLayerPicker/createDefaultImageryProviderViewModels',
        '../BaseLayerPicker/createDefaultTerrainProviderViewModels',
        '../CesiumWidget/CesiumWidget',
        '../ClockViewModel',
        '../FullscreenButton/FullscreenButton',
        '../Geocoder/Geocoder',
        '../getElement',
        '../HomeButton/HomeButton',
        '../InfoBox/InfoBox',
        '../NavigationHelpButton/NavigationHelpButton',
        '../SceneModePicker/SceneModePicker',
        '../SelectionIndicator/SelectionIndicator',
        '../subscribeAndEvaluate',
        '../Timeline/Timeline',
        '../VRButton/VRButton'
    ], function(
        BoundingSphere,
        Cartesian3,
        defaultValue,
        defined,
        defineProperties,
        destroyObject,
        DeveloperError,
        Event,
        EventHelper,
        isArray,
        Matrix4,
        Rectangle,
        ScreenSpaceEventType,
        BoundingSphereState,
        ConstantPositionProperty,
        DataSourceCollection,
        DataSourceDisplay,
        Entity,
        EntityView,
        Property,
        ImageryLayer,
        SceneMode,
        knockout,
        when,
        Animation,
        AnimationViewModel,
        BaseLayerPicker,
        createDefaultImageryProviderViewModels,
        createDefaultTerrainProviderViewModels,
        CesiumWidget,
        ClockViewModel,
        FullscreenButton,
        Geocoder,
        getElement,
        HomeButton,
        InfoBox,
        NavigationHelpButton,
        SceneModePicker,
        SelectionIndicator,
        subscribeAndEvaluate,
        Timeline,
        VRButton) {
    'use strict';

    var boundingSphereScratch = new BoundingSphere();

    function onTimelineScrubfunction(e) {
        var clock = e.clock;
        clock.currentTime = e.timeJulian;
        clock.shouldAnimate = false;
    }

    function pickEntity(viewer, e) {
        var picked = viewer.scene.pick(e.position);
        if (defined(picked)) {
            var id = defaultValue(picked.id, picked.primitive.id);
            if (id instanceof Entity) {
                return id;
            }
        }

        // No regular entity picked.  Try picking features from imagery layers.
        if (defined(viewer.scene.globe)) {
            return pickImageryLayerFeature(viewer, e.position);
        }
    }

    function trackDataSourceClock(timeline, clock, dataSource) {
        if (defined(dataSource)) {
            var dataSourceClock = dataSource.clock;
            if (defined(dataSourceClock)) {
                dataSourceClock.getValue(clock);
                if (defined(timeline)) {
                    timeline.updateFromClock();
                    timeline.zoomTo(dataSourceClock.startTime, dataSourceClock.stopTime);
                }
            }
        }
    }

    var cartesian3Scratch = new Cartesian3();

    function pickImageryLayerFeature(viewer, windowPosition) {
        var scene = viewer.scene;
        var pickRay = scene.camera.getPickRay(windowPosition);
        var imageryLayerFeaturePromise = scene.imageryLayers.pickImageryLayerFeatures(pickRay, scene);
        if (!defined(imageryLayerFeaturePromise)) {
            return;
        }

        // Imagery layer feature picking is asynchronous, so put up a message while loading.
        var loadingMessage = new Entity({
            id : 'Loading...',
            description : 'Loading feature information...'
        });

        when(imageryLayerFeaturePromise, function(features) {
            // Has this async pick been superseded by a later one?
            if (viewer.selectedEntity !== loadingMessage) {
                return;
            }

            if (!defined(features) || features.length === 0) {
                viewer.selectedEntity = createNoFeaturesEntity();
                return;
            }

            // Select the first feature.
            var feature = features[0];

            var entity = new Entity({
                id : feature.name,
                description : feature.description
            });

            if (defined(feature.position)) {
                var ecfPosition = viewer.scene.globe.ellipsoid.cartographicToCartesian(feature.position, cartesian3Scratch);
                entity.position = new ConstantPositionProperty(ecfPosition);
            }

            viewer.selectedEntity = entity;
        }, function() {
            // Has this async pick been superseded by a later one?
            if (viewer.selectedEntity !== loadingMessage) {
                return;
            }
            viewer.selectedEntity = createNoFeaturesEntity();
        });

        return loadingMessage;
    }

    function createNoFeaturesEntity() {
        return new Entity({
            id : 'None',
            description : 'No features found.'
        });
    }

    function enableVRUI(viewer, enabled) {
        var geocoder = viewer._geocoder;
        var homeButton = viewer._homeButton;
        var sceneModePicker = viewer._sceneModePicker;
        var baseLayerPicker = viewer._baseLayerPicker;
        var animation = viewer._animation;
        var timeline = viewer._timeline;
        var fullscreenButton = viewer._fullscreenButton;
        var infoBox = viewer._infoBox;
        var selectionIndicator = viewer._selectionIndicator;

        var visibility = enabled ? 'hidden' : 'visible';

        if (defined(geocoder)) {
            geocoder.container.style.visibility = visibility;
        }
        if (defined(homeButton)) {
            homeButton.container.style.visibility = visibility;
        }
        if(defined(sceneModePicker)) {
            sceneModePicker.container.style.visibility = visibility;
        }
        if(defined(baseLayerPicker)) {
            baseLayerPicker.container.style.visibility = visibility;
        }
        if (defined(animation)) {
            animation.container.style.visibility = visibility;
        }
        if (defined(timeline)) {
            timeline.container.style.visibility = visibility;
        }
        if (defined(fullscreenButton) && fullscreenButton.viewModel.isFullscreenEnabled) {
            fullscreenButton.container.style.visibility = visibility;
        }
        if (defined(infoBox)) {
            infoBox.container.style.visibility = visibility;
        }
        if (defined(selectionIndicator)) {
            selectionIndicator.container.style.visibility = visibility;
        }

        if (viewer._container) {
            var right = enabled || !defined(fullscreenButton) ? 0 : fullscreenButton.container.clientWidth;
            viewer._vrButton.container.style.right = right + 'px';

            viewer.forceResize();
        }
    }

    /**
     * A base widget for building applications.  It composites all of the standard Cesium widgets into one reusable package.
     * The widget can always be extended by using mixins, which add functionality useful for a variety of applications.
     *
     * @alias Viewer
     * @constructor
     *
     * @param {Element|String} container The DOM element or ID that will contain the widget.
     * @param {Object} [options] Object with the following properties:
     * @param {Boolean} [options.animation=true] If set to false, the Animation widget will not be created.
     * @param {Boolean} [options.baseLayerPicker=true] If set to false, the BaseLayerPicker widget will not be created.
     * @param {Boolean} [options.fullscreenButton=true] If set to false, the FullscreenButton widget will not be created.
     * @param {Boolean} [options.vrButton=false] If set to true, the VRButton widget will be created.
     * @param {Boolean} [options.geocoder=true] If set to false, the Geocoder widget will not be created.
     * @param {Boolean} [options.homeButton=true] If set to false, the HomeButton widget will not be created.
     * @param {Boolean} [options.infoBox=true] If set to false, the InfoBox widget will not be created.
     * @param {Boolean} [options.sceneModePicker=true] If set to false, the SceneModePicker widget will not be created.
     * @param {Boolean} [options.selectionIndicator=true] If set to false, the SelectionIndicator widget will not be created.
     * @param {Boolean} [options.timeline=true] If set to false, the Timeline widget will not be created.
     * @param {Boolean} [options.navigationHelpButton=true] If set to false, the navigation help button will not be created.
     * @param {Boolean} [options.navigationInstructionsInitiallyVisible=true] True if the navigation instructions should initially be visible, or false if the should not be shown until the user explicitly clicks the button.
     * @param {Boolean} [options.scene3DOnly=false] When <code>true</code>, each geometry instance will only be rendered in 3D to save GPU memory.
     * @param {Clock} [options.clock=new Clock()] The clock to use to control current time.
     * @param {ProviderViewModel} [options.selectedImageryProviderViewModel] The view model for the current base imagery layer, if not supplied the first available base layer is used.  This value is only valid if options.baseLayerPicker is set to true.
     * @param {ProviderViewModel[]} [options.imageryProviderViewModels=createDefaultImageryProviderViewModels()] The array of ProviderViewModels to be selectable from the BaseLayerPicker.  This value is only valid if options.baseLayerPicker is set to true.
     * @param {ProviderViewModel} [options.selectedTerrainProviderViewModel] The view model for the current base terrain layer, if not supplied the first available base layer is used.  This value is only valid if options.baseLayerPicker is set to true.
     * @param {ProviderViewModel[]} [options.terrainProviderViewModels=createDefaultTerrainProviderViewModels()] The array of ProviderViewModels to be selectable from the BaseLayerPicker.  This value is only valid if options.baseLayerPicker is set to true.
     * @param {ImageryProvider} [options.imageryProvider=new BingMapsImageryProvider()] The imagery provider to use.  This value is only valid if options.baseLayerPicker is set to false.
     * @param {TerrainProvider} [options.terrainProvider=new EllipsoidTerrainProvider()] The terrain provider to use
     * @param {SkyBox} [options.skyBox] The skybox used to render the stars.  When <code>undefined</code>, the default stars are used.
     * @param {SkyAtmosphere} [options.skyAtmosphere] Blue sky, and the glow around the Earth's limb.  Set to <code>false</code> to turn it off.
     * @param {Element|String} [options.fullscreenElement=document.body] The element or id to be placed into fullscreen mode when the full screen button is pressed.
     * @param {Boolean} [options.useDefaultRenderLoop=true] True if this widget should control the render loop, false otherwise.
     * @param {Number} [options.targetFrameRate] The target frame rate when using the default render loop.
     * @param {Boolean} [options.showRenderLoopErrors=true] If true, this widget will automatically display an HTML panel to the user containing the error, if a render loop error occurs.
     * @param {Boolean} [options.automaticallyTrackDataSourceClocks=true] If true, this widget will automatically track the clock settings of newly added DataSources, updating if the DataSource's clock changes.  Set this to false if you want to configure the clock independently.
     * @param {Object} [options.contextOptions] Context and WebGL creation properties corresponding to <code>options</code> passed to {@link Scene}.
     * @param {SceneMode} [options.sceneMode=SceneMode.SCENE3D] The initial scene mode.
     * @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
     * @param {Globe} [options.globe=new Globe(mapProjection.ellipsoid)] The globe to use in the scene.  If set to <code>false</code>, no globe will be added.
     * @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
     * @param {Element|String} [options.creditContainer] The DOM element or ID that will contain the {@link CreditDisplay}.  If not specified, the credits are added to the bottom of the widget itself.
     * @param {DataSourceCollection} [options.dataSources=new DataSourceCollection()] The collection of data sources visualized by the widget.  If this parameter is provided,
     *                               the instance is assumed to be owned by the caller and will not be destroyed when the viewer is destroyed.
     * @param {Number} [options.terrainExaggeration=1.0] A scalar used to exaggerate the terrain. Note that terrain exaggeration will not modify any other primitive as they are positioned relative to the ellipsoid.
     * @param {Boolean} [options.shadows=false] Determines if shadows are cast by the sun.
     * @param {ShadowMode} [options.terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from the sun.
     * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
     *
     * @exception {DeveloperError} Element with id "container" does not exist in the document.
     * @exception {DeveloperError} options.imageryProvider is not available when using the BaseLayerPicker widget, specify options.selectedImageryProviderViewModel instead.
     * @exception {DeveloperError} options.terrainProvider is not available when using the BaseLayerPicker widget, specify options.selectedTerrainProviderViewModel instead.
     * @exception {DeveloperError} options.selectedImageryProviderViewModel is not available when not using the BaseLayerPicker widget, specify options.imageryProvider instead.
     * @exception {DeveloperError} options.selectedTerrainProviderViewModel is not available when not using the BaseLayerPicker widget, specify options.terrainProvider instead.
     *
     * @see Animation
     * @see BaseLayerPicker
     * @see CesiumWidget
     * @see FullscreenButton
     * @see HomeButton
     * @see SceneModePicker
     * @see Timeline
     * @see viewerDragDropMixin
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Hello%20World.html|Cesium Sandcastle Hello World Demo}
     *
     * @example
     * //Initialize the viewer widget with several custom options and mixins.
     * var viewer = new Cesium.Viewer('cesiumContainer', {
     *     //Start in Columbus Viewer
     *     sceneMode : Cesium.SceneMode.COLUMBUS_VIEW,
     *     //Use standard Cesium terrain
     *     terrainProvider : new Cesium.CesiumTerrainProvider({
     *         url : 'https://assets.agi.com/stk-terrain/world'
     *     }),
     *     //Hide the base layer picker
     *     baseLayerPicker : false,
     *     //Use OpenStreetMaps
     *     imageryProvider : Cesium.createOpenStreetMapImageryProvider({
     *         url : 'https://a.tile.openstreetmap.org/'
     *     }),
     *     // Use high-res stars downloaded from https://github.com/AnalyticalGraphicsInc/cesium-assets
     *     skyBox : new Cesium.SkyBox({
     *         sources : {
     *           positiveX : 'stars/TychoSkymapII.t3_08192x04096_80_px.jpg',
     *           negativeX : 'stars/TychoSkymapII.t3_08192x04096_80_mx.jpg',
     *           positiveY : 'stars/TychoSkymapII.t3_08192x04096_80_py.jpg',
     *           negativeY : 'stars/TychoSkymapII.t3_08192x04096_80_my.jpg',
     *           positiveZ : 'stars/TychoSkymapII.t3_08192x04096_80_pz.jpg',
     *           negativeZ : 'stars/TychoSkymapII.t3_08192x04096_80_mz.jpg'
     *         }
     *     }),
     *     // Show Columbus View map with Web Mercator projection
     *     mapProjection : new Cesium.WebMercatorProjection()
     * });
     *
     * //Add basic drag and drop functionality
     * viewer.extend(Cesium.viewerDragDropMixin);
     *
     * //Show a pop-up alert if we encounter an error when processing a dropped file
     * viewer.dropError.addEventListener(function(dropHandler, name, error) {
     *     console.log(error);
     *     window.alert(error);
     * });
     */
    function Viewer(container, options) {
                if (!defined(container)) {
            throw new DeveloperError('container is required.');
        }
        
        container = getElement(container);
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var createBaseLayerPicker = (!defined(options.globe) || options.globe !== false) &&
            (!defined(options.baseLayerPicker) || options.baseLayerPicker !== false);

                // If using BaseLayerPicker, imageryProvider is an invalid option
        if (createBaseLayerPicker && defined(options.imageryProvider)) {
            throw new DeveloperError('options.imageryProvider is not available when using the BaseLayerPicker widget. \
Either specify options.selectedImageryProviderViewModel instead or set options.baseLayerPicker to false.');
        }

        // If not using BaseLayerPicker, selectedImageryProviderViewModel is an invalid option
        if (!createBaseLayerPicker && defined(options.selectedImageryProviderViewModel)) {
            throw new DeveloperError('options.selectedImageryProviderViewModel is not available when not using the BaseLayerPicker widget. \
Either specify options.imageryProvider instead or set options.baseLayerPicker to true.');
        }

        // If using BaseLayerPicker, terrainProvider is an invalid option
        if (createBaseLayerPicker && defined(options.terrainProvider)) {
            throw new DeveloperError('options.terrainProvider is not available when using the BaseLayerPicker widget. \
Either specify options.selectedTerrainProviderViewModel instead or set options.baseLayerPicker to false.');
        }

        // If not using BaseLayerPicker, selectedTerrainProviderViewModel is an invalid option
        if (!createBaseLayerPicker && defined(options.selectedTerrainProviderViewModel)) {
            throw new DeveloperError('options.selectedTerrainProviderViewModel is not available when not using the BaseLayerPicker widget. \
Either specify options.terrainProvider instead or set options.baseLayerPicker to true.');
        }
        
        var that = this;

        var viewerContainer = document.createElement('div');
        viewerContainer.className = 'cesium-viewer';
        container.appendChild(viewerContainer);

        // Cesium widget container
        var cesiumWidgetContainer = document.createElement('div');
        cesiumWidgetContainer.className = 'cesium-viewer-cesiumWidgetContainer';
        viewerContainer.appendChild(cesiumWidgetContainer);

        // Bottom container
        var bottomContainer = document.createElement('div');
        bottomContainer.className = 'cesium-viewer-bottom';

        viewerContainer.appendChild(bottomContainer);

        var scene3DOnly = defaultValue(options.scene3DOnly, false);

        // Cesium widget
        var cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
            terrainProvider : options.terrainProvider,
            imageryProvider : createBaseLayerPicker ? false : options.imageryProvider,
            clock : options.clock,
            skyBox : options.skyBox,
            skyAtmosphere : options.skyAtmosphere,
            sceneMode : options.sceneMode,
            mapProjection : options.mapProjection,
            globe : options.globe,
            orderIndependentTranslucency : options.orderIndependentTranslucency,
            contextOptions : options.contextOptions,
            useDefaultRenderLoop : options.useDefaultRenderLoop,
            targetFrameRate : options.targetFrameRate,
            showRenderLoopErrors : options.showRenderLoopErrors,
            creditContainer : defined(options.creditContainer) ? options.creditContainer : bottomContainer,
            scene3DOnly : scene3DOnly,
            terrainExaggeration : options.terrainExaggeration,
            shadows : options.shadows,
            terrainShadows : options.terrainShadows,
            mapMode2D : options.mapMode2D
        });

        var dataSourceCollection = options.dataSources;
        var destroyDataSourceCollection = false;
        if (!defined(dataSourceCollection)) {
            dataSourceCollection = new DataSourceCollection();
            destroyDataSourceCollection = true;
        }

        var dataSourceDisplay = new DataSourceDisplay({
            scene : cesiumWidget.scene,
            dataSourceCollection : dataSourceCollection
        });

        var clock = cesiumWidget.clock;
        var clockViewModel = new ClockViewModel(clock);
        var eventHelper = new EventHelper();

        eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);
        eventHelper.add(cesiumWidget.scene.morphStart, Viewer.prototype._clearTrackedObject, this);

        // Selection Indicator
        var selectionIndicator;
        if (!defined(options.selectionIndicator) || options.selectionIndicator !== false) {
            var selectionIndicatorContainer = document.createElement('div');
            selectionIndicatorContainer.className = 'cesium-viewer-selectionIndicatorContainer';
            viewerContainer.appendChild(selectionIndicatorContainer);
            selectionIndicator = new SelectionIndicator(selectionIndicatorContainer, cesiumWidget.scene);
        }

        // Info Box
        var infoBox;
        if (!defined(options.infoBox) || options.infoBox !== false) {
            var infoBoxContainer = document.createElement('div');
            infoBoxContainer.className = 'cesium-viewer-infoBoxContainer';
            viewerContainer.appendChild(infoBoxContainer);
            infoBox = new InfoBox(infoBoxContainer);

            var infoBoxViewModel = infoBox.viewModel;
            eventHelper.add(infoBoxViewModel.cameraClicked, Viewer.prototype._onInfoBoxCameraClicked, this);
            eventHelper.add(infoBoxViewModel.closeClicked, Viewer.prototype._onInfoBoxClockClicked, this);
        }

        // Main Toolbar
        var toolbar = document.createElement('div');
        toolbar.className = 'cesium-viewer-toolbar';
        viewerContainer.appendChild(toolbar);

        // Geocoder
        var geocoder;
        if (!defined(options.geocoder) || options.geocoder !== false) {
            var geocoderContainer = document.createElement('div');
            geocoderContainer.className = 'cesium-viewer-geocoderContainer';
            toolbar.appendChild(geocoderContainer);
            geocoder = new Geocoder({
                container : geocoderContainer,
                geocoderServices: defined(options.geocoder) ? (isArray(options.geocoder) ? options.geocoder : [options.geocoder]) : undefined,
                scene : cesiumWidget.scene
            });
            // Subscribe to search so that we can clear the trackedEntity when it is clicked.
            eventHelper.add(geocoder.viewModel.search.beforeExecute, Viewer.prototype._clearObjects, this);
        }

        // HomeButton
        var homeButton;
        if (!defined(options.homeButton) || options.homeButton !== false) {
            homeButton = new HomeButton(toolbar, cesiumWidget.scene);
            if (defined(geocoder)) {
                eventHelper.add(homeButton.viewModel.command.afterExecute, function() {
                    var viewModel = geocoder.viewModel;
                    viewModel.searchText = '';
                    if (viewModel.isSearchInProgress) {
                        viewModel.search();
                    }
                });
            }
            // Subscribe to the home button beforeExecute event so that we can clear the trackedEntity.
            eventHelper.add(homeButton.viewModel.command.beforeExecute, Viewer.prototype._clearTrackedObject, this);
        }

        // SceneModePicker
        // By default, we silently disable the scene mode picker if scene3DOnly is true,
        // but if sceneModePicker is explicitly set to true, throw an error.
                if ((options.sceneModePicker === true) && scene3DOnly) {
            throw new DeveloperError('options.sceneModePicker is not available when options.scene3DOnly is set to true.');
        }
        
        var sceneModePicker;
        if (!scene3DOnly && (!defined(options.sceneModePicker) || options.sceneModePicker !== false)) {
            sceneModePicker = new SceneModePicker(toolbar, cesiumWidget.scene);
        }

        // BaseLayerPicker
        var baseLayerPicker;
        var baseLayerPickerDropDown;
        if (createBaseLayerPicker) {
            var imageryProviderViewModels = defaultValue(options.imageryProviderViewModels, createDefaultImageryProviderViewModels());
            var terrainProviderViewModels = defaultValue(options.terrainProviderViewModels, createDefaultTerrainProviderViewModels());

            baseLayerPicker = new BaseLayerPicker(toolbar, {
                globe : cesiumWidget.scene.globe,
                imageryProviderViewModels : imageryProviderViewModels,
                selectedImageryProviderViewModel : options.selectedImageryProviderViewModel,
                terrainProviderViewModels : terrainProviderViewModels,
                selectedTerrainProviderViewModel : options.selectedTerrainProviderViewModel
            });

            //Grab the dropdown for resize code.
            var elements = toolbar.getElementsByClassName('cesium-baseLayerPicker-dropDown');
            baseLayerPickerDropDown = elements[0];
        }

        // Navigation Help Button
        var navigationHelpButton;
        if (!defined(options.navigationHelpButton) || options.navigationHelpButton !== false) {
            var showNavHelp = true;
            try {
                //window.localStorage is null if disabled in Firefox or undefined in browsers with implementation
                if (defined(window.localStorage)) {
                    var hasSeenNavHelp = window.localStorage.getItem('cesium-hasSeenNavHelp');
                    if (defined(hasSeenNavHelp) && Boolean(hasSeenNavHelp)) {
                        showNavHelp = false;
                    } else {
                        window.localStorage.setItem('cesium-hasSeenNavHelp', 'true');
                    }
                }
            } catch (e) {
                //Accessing window.localStorage throws if disabled in Chrome
                //window.localStorage.setItem throws if in Safari private browsing mode or in any browser if we are over quota.
            }
            navigationHelpButton = new NavigationHelpButton({
                container : toolbar,
                instructionsInitiallyVisible : defaultValue(options.navigationInstructionsInitiallyVisible, showNavHelp)
            });
        }

        // Animation
        var animation;
        if (!defined(options.animation) || options.animation !== false) {
            var animationContainer = document.createElement('div');
            animationContainer.className = 'cesium-viewer-animationContainer';
            viewerContainer.appendChild(animationContainer);
            animation = new Animation(animationContainer, new AnimationViewModel(clockViewModel));
        }

        // Timeline
        var timeline;
        if (!defined(options.timeline) || options.timeline !== false) {
            var timelineContainer = document.createElement('div');
            timelineContainer.className = 'cesium-viewer-timelineContainer';
            viewerContainer.appendChild(timelineContainer);
            timeline = new Timeline(timelineContainer, clock);
            timeline.addEventListener('settime', onTimelineScrubfunction, false);
            timeline.zoomTo(clock.startTime, clock.stopTime);
        }

        // Fullscreen
        var fullscreenButton;
        var fullscreenSubscription;
        if (!defined(options.fullscreenButton) || options.fullscreenButton !== false) {
            var fullscreenContainer = document.createElement('div');
            fullscreenContainer.className = 'cesium-viewer-fullscreenContainer';
            viewerContainer.appendChild(fullscreenContainer);
            fullscreenButton = new FullscreenButton(fullscreenContainer, options.fullscreenElement);

            //Subscribe to fullscreenButton.viewModel.isFullscreenEnabled so
            //that we can hide/show the button as well as size the timeline.
            fullscreenSubscription = subscribeAndEvaluate(fullscreenButton.viewModel, 'isFullscreenEnabled', function(isFullscreenEnabled) {
                fullscreenContainer.style.display = isFullscreenEnabled ? 'block' : 'none';
                if (defined(timeline)) {
                    timeline.container.style.right = fullscreenContainer.clientWidth + 'px';
                    timeline.resize();
                }
            });
        }

        // VR
        var vrButton;
        var vrSubscription;
        var vrModeSubscription;
        if (options.vrButton) {
            var vrContainer = document.createElement('div');
            vrContainer.className = 'cesium-viewer-vrContainer';
            viewerContainer.appendChild(vrContainer);
            vrButton = new VRButton(vrContainer, cesiumWidget.scene, options.fullScreenElement);

            vrSubscription = subscribeAndEvaluate(vrButton.viewModel, 'isVREnabled', function(isVREnabled) {
                vrContainer.style.display = isVREnabled ? 'block' : 'none';
                if (defined(fullscreenButton)) {
                    vrContainer.style.right = fullscreenContainer.clientWidth + 'px';
                }
                if (defined(timeline)) {
                    timeline.container.style.right = vrContainer.clientWidth + 'px';
                    timeline.resize();
                }
            });

            vrModeSubscription = subscribeAndEvaluate(vrButton.viewModel, 'isVRMode', function(isVRMode) {
                enableVRUI(that, isVRMode);
            });
        }

        //Assign all properties to this instance.  No "this" assignments should
        //take place above this line.
        this._baseLayerPickerDropDown = baseLayerPickerDropDown;
        this._fullscreenSubscription = fullscreenSubscription;
        this._vrSubscription = vrSubscription;
        this._vrModeSubscription = vrModeSubscription;
        this._dataSourceChangedListeners = {};
        this._automaticallyTrackDataSourceClocks = defaultValue(options.automaticallyTrackDataSourceClocks, true);
        this._container = container;
        this._bottomContainer = bottomContainer;
        this._element = viewerContainer;
        this._cesiumWidget = cesiumWidget;
        this._selectionIndicator = selectionIndicator;
        this._infoBox = infoBox;
        this._dataSourceCollection = dataSourceCollection;
        this._destroyDataSourceCollection = destroyDataSourceCollection;
        this._dataSourceDisplay = dataSourceDisplay;
        this._clockViewModel = clockViewModel;
        this._toolbar = toolbar;
        this._homeButton = homeButton;
        this._sceneModePicker = sceneModePicker;
        this._baseLayerPicker = baseLayerPicker;
        this._navigationHelpButton = navigationHelpButton;
        this._animation = animation;
        this._timeline = timeline;
        this._fullscreenButton = fullscreenButton;
        this._vrButton = vrButton;
        this._geocoder = geocoder;
        this._eventHelper = eventHelper;
        this._lastWidth = 0;
        this._lastHeight = 0;
        this._allowDataSourcesToSuspendAnimation = true;
        this._entityView = undefined;
        this._enableInfoOrSelection = defined(infoBox) || defined(selectionIndicator);
        this._clockTrackedDataSource = undefined;
        this._trackedEntity = undefined;
        this._needTrackedEntityUpdate = false;
        this._selectedEntity = undefined;
        this._clockTrackedDataSource = undefined;
        this._forceResize = false;
        this._zoomIsFlight = false;
        this._zoomTarget = undefined;
        this._zoomPromise = undefined;
        this._zoomOptions = undefined;
        this._selectedEntityChanged = new Event();

        knockout.track(this, ['_trackedEntity', '_selectedEntity', '_clockTrackedDataSource']);

        //Listen to data source events in order to track clock changes.
        eventHelper.add(dataSourceCollection.dataSourceAdded, Viewer.prototype._onDataSourceAdded, this);
        eventHelper.add(dataSourceCollection.dataSourceRemoved, Viewer.prototype._onDataSourceRemoved, this);

        // Prior to each render, check if anything needs to be resized.
        eventHelper.add(cesiumWidget.scene.preRender, Viewer.prototype.resize, this);
        eventHelper.add(cesiumWidget.scene.postRender, Viewer.prototype._postRender, this);

        // We need to subscribe to the data sources and collections so that we can clear the
        // tracked object when it is removed from the scene.
        // Subscribe to current data sources
        var dataSourceLength = dataSourceCollection.length;
        for (var i = 0; i < dataSourceLength; i++) {
            this._dataSourceAdded(dataSourceCollection, dataSourceCollection.get(i));
        }
        this._dataSourceAdded(undefined, dataSourceDisplay.defaultDataSource);

        // Hook up events so that we can subscribe to future sources.
        eventHelper.add(dataSourceCollection.dataSourceAdded, Viewer.prototype._dataSourceAdded, this);
        eventHelper.add(dataSourceCollection.dataSourceRemoved, Viewer.prototype._dataSourceRemoved, this);

        // Subscribe to left clicks and zoom to the picked object.
        function pickAndTrackObject(e) {
            var entity = pickEntity(that, e);
            if (defined(entity)) {
                //Only track the entity if it has a valid position at the current time.
                if (Property.getValueOrUndefined(entity.position, that.clock.currentTime)) {
                    that.trackedEntity = entity;
                } else {
                    that.zoomTo(entity);
                }
            }
        }

        function pickAndSelectObject(e) {
            that.selectedEntity = pickEntity(that, e);
        }

        cesiumWidget.screenSpaceEventHandler.setInputAction(pickAndSelectObject, ScreenSpaceEventType.LEFT_CLICK);
        cesiumWidget.screenSpaceEventHandler.setInputAction(pickAndTrackObject, ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
    }

    defineProperties(Viewer.prototype, {
        /**
         * Gets the parent container.
         * @memberof Viewer.prototype
         * @type {Element}
         * @readonly
         */
        container : {
            get : function() {
                return this._container;
            }
        },

        /**
         * Gets the DOM element for the area at the bottom of the window containing the
         * {@link CreditDisplay} and potentially other things.
         * @memberof Viewer.prototype
         * @type {Element}
         * @readonly
         */
        bottomContainer : {
            get : function() {
                return this._bottomContainer;
            }
        },

        /**
         * Gets the CesiumWidget.
         * @memberof Viewer.prototype
         * @type {CesiumWidget}
         * @readonly
         */
        cesiumWidget : {
            get : function() {
                return this._cesiumWidget;
            }
        },

        /**
         * Gets the selection indicator.
         * @memberof Viewer.prototype
         * @type {SelectionIndicator}
         * @readonly
         */
        selectionIndicator : {
            get : function() {
                return this._selectionIndicator;
            }
        },

        /**
         * Gets the info box.
         * @memberof Viewer.prototype
         * @type {InfoBox}
         * @readonly
         */
        infoBox : {
            get : function() {
                return this._infoBox;
            }
        },

        /**
         * Gets the Geocoder.
         * @memberof Viewer.prototype
         * @type {Geocoder}
         * @readonly
         */
        geocoder : {
            get : function() {
                return this._geocoder;
            }
        },

        /**
         * Gets the HomeButton.
         * @memberof Viewer.prototype
         * @type {HomeButton}
         * @readonly
         */
        homeButton : {
            get : function() {
                return this._homeButton;
            }
        },

        /**
         * Gets the SceneModePicker.
         * @memberof Viewer.prototype
         * @type {SceneModePicker}
         * @readonly
         */
        sceneModePicker : {
            get : function() {
                return this._sceneModePicker;
            }
        },

        /**
         * Gets the BaseLayerPicker.
         * @memberof Viewer.prototype
         * @type {BaseLayerPicker}
         * @readonly
         */
        baseLayerPicker : {
            get : function() {
                return this._baseLayerPicker;
            }
        },

        /**
         * Gets the NavigationHelpButton.
         * @memberof Viewer.prototype
         * @type {NavigationHelpButton}
         * @readonly
         */
        navigationHelpButton : {
            get : function() {
                return this._navigationHelpButton;
            }
        },

        /**
         * Gets the Animation widget.
         * @memberof Viewer.prototype
         * @type {Animation}
         * @readonly
         */
        animation : {
            get : function() {
                return this._animation;
            }
        },

        /**
         * Gets the Timeline widget.
         * @memberof Viewer.prototype
         * @type {Timeline}
         * @readonly
         */
        timeline : {
            get : function() {
                return this._timeline;
            }
        },

        /**
         * Gets the FullscreenButton.
         * @memberof Viewer.prototype
         * @type {FullscreenButton}
         * @readonly
         */
        fullscreenButton : {
            get : function() {
                return this._fullscreenButton;
            }
        },

        /**
         * Gets the VRButton.
         * @memberof Viewer.prototype
         * @type {VRButton}
         * @readonly
         */
        vrButton : {
            get : function() {
                return this._vrButton;
            }
        },

        /**
         * Gets the display used for {@link DataSource} visualization.
         * @memberof Viewer.prototype
         * @type {DataSourceDisplay}
         * @readonly
         */
        dataSourceDisplay : {
            get : function() {
                return this._dataSourceDisplay;
            }
        },

        /**
         * Gets the collection of entities not tied to a particular data source.
         * This is a shortcut to [dataSourceDisplay.defaultDataSource.entities]{@link Viewer#dataSourceDisplay}.
         * @memberof Viewer.prototype
         * @type {EntityCollection}
         * @readonly
         */
        entities : {
            get : function() {
                return this._dataSourceDisplay.defaultDataSource.entities;
            }
        },

        /**
         * Gets the set of {@link DataSource} instances to be visualized.
         * @memberof Viewer.prototype
         * @type {DataSourceCollection}
         * @readonly
         */
        dataSources : {
            get : function() {
                return this._dataSourceCollection;
            }
        },

        /**
         * Gets the canvas.
         * @memberof Viewer.prototype
         * @type {Canvas}
         * @readonly
         */
        canvas : {
            get : function() {
                return this._cesiumWidget.canvas;
            }
        },

        /**
         * Gets the Cesium logo element.
         * @memberof Viewer.prototype
         * @type {Element}
         * @readonly
         */
        cesiumLogo : {
            get : function() {
                return this._cesiumWidget.cesiumLogo;
            }
        },

        /**
         * Gets the scene.
         * @memberof Viewer.prototype
         * @type {Scene}
         * @readonly
         */
        scene : {
            get : function() {
                return this._cesiumWidget.scene;
            }
        },

        /**
         * Determines if shadows are cast by the sun.
         * @memberof Viewer.prototype
         * @type {Boolean}
         */
        shadows : {
            get : function() {
                return this.scene.shadowMap.enabled;
            },
            set : function(value) {
                this.scene.shadowMap.enabled = value;
            }
        },

        /**
         * Determines if the terrain casts or shadows from the sun.
         * @memberof Viewer.prototype
         * @type {ShadowMode}
         */
        terrainShadows : {
            get : function() {
                return this.scene.globe.shadows;
            },
            set : function(value) {
                this.scene.globe.shadows = value;
            }
        },

        /**
         * Get the scene's shadow map
         * @memberof Viewer.prototype
         * @type {ShadowMap}
         * @readonly
         */
        shadowMap : {
            get : function() {
                return this.scene.shadowMap;
            }
        },

        /**
         * Gets the collection of image layers that will be rendered on the globe.
         * @memberof Viewer.prototype
         *
         * @type {ImageryLayerCollection}
         * @readonly
         */
        imageryLayers : {
            get : function() {
                return this.scene.imageryLayers;
            }
        },

        /**
         * The terrain provider providing surface geometry for the globe.
         * @memberof Viewer.prototype
         *
         * @type {TerrainProvider}
         */
        terrainProvider : {
            get : function() {
                return this.scene.terrainProvider;
            },
            set : function(terrainProvider) {
                this.scene.terrainProvider = terrainProvider;
            }
        },

        /**
         * Gets the camera.
         * @memberof Viewer.prototype
         *
         * @type {Camera}
         * @readonly
         */
        camera : {
            get : function() {
                return this.scene.camera;
            }
        },

        /**
         * Gets the clock.
         * @memberof Viewer.prototype
         * @type {Clock}
         * @readonly
         */
        clock : {
            get : function() {
                return this._cesiumWidget.clock;
            }
        },

        /**
         * Gets the screen space event handler.
         * @memberof Viewer.prototype
         * @type {ScreenSpaceEventHandler}
         * @readonly
         */
        screenSpaceEventHandler : {
            get : function() {
                return this._cesiumWidget.screenSpaceEventHandler;
            }
        },

        /**
         * Gets or sets the target frame rate of the widget when <code>useDefaultRenderLoop</code>
         * is true. If undefined, the browser's {@link requestAnimationFrame} implementation
         * determines the frame rate.  If defined, this value must be greater than 0.  A value higher
         * than the underlying requestAnimationFrame implementation will have no effect.
         * @memberof Viewer.prototype
         *
         * @type {Number}
         */
        targetFrameRate : {
            get : function() {
                return this._cesiumWidget.targetFrameRate;
            },
            set : function(value) {
                this._cesiumWidget.targetFrameRate = value;
            }
        },

        /**
         * Gets or sets whether or not this widget should control the render loop.
         * If set to true the widget will use {@link requestAnimationFrame} to
         * perform rendering and resizing of the widget, as well as drive the
         * simulation clock. If set to false, you must manually call the
         * <code>resize</code>, <code>render</code> methods
         * as part of a custom render loop.  If an error occurs during rendering, {@link Scene}'s
         * <code>renderError</code> event will be raised and this property
         * will be set to false.  It must be set back to true to continue rendering
         * after the error.
         * @memberof Viewer.prototype
         *
         * @type {Boolean}
         */
        useDefaultRenderLoop : {
            get : function() {
                return this._cesiumWidget.useDefaultRenderLoop;
            },
            set : function(value) {
                this._cesiumWidget.useDefaultRenderLoop = value;
            }
        },

        /**
         * Gets or sets a scaling factor for rendering resolution.  Values less than 1.0 can improve
         * performance on less powerful devices while values greater than 1.0 will render at a higher
         * resolution and then scale down, resulting in improved visual fidelity.
         * For example, if the widget is laid out at a size of 640x480, setting this value to 0.5
         * will cause the scene to be rendered at 320x240 and then scaled up while setting
         * it to 2.0 will cause the scene to be rendered at 1280x960 and then scaled down.
         * @memberof Viewer.prototype
         *
         * @type {Number}
         * @default 1.0
         */
        resolutionScale : {
            get : function() {
                return this._cesiumWidget.resolutionScale;
            },
            set : function(value) {
                this._cesiumWidget.resolutionScale = value;
                this._forceResize = true;
            }
        },

        /**
         * Gets or sets whether or not data sources can temporarily pause
         * animation in order to avoid showing an incomplete picture to the user.
         * For example, if asynchronous primitives are being processed in the
         * background, the clock will not advance until the geometry is ready.
         *
         * @memberof Viewer.prototype
         *
         * @type {Boolean}
         */
        allowDataSourcesToSuspendAnimation : {
            get : function() {
                return this._allowDataSourcesToSuspendAnimation;
            },
            set : function(value) {
                this._allowDataSourcesToSuspendAnimation = value;
            }
        },

        /**
         * Gets or sets the Entity instance currently being tracked by the camera.
         * @memberof Viewer.prototype
         * @type {Entity}
         */
        trackedEntity : {
            get : function() {
                return this._trackedEntity;
            },
            set : function(value) {
                if (this._trackedEntity !== value) {
                    this._trackedEntity = value;

                    //Cancel any pending zoom
                    cancelZoom(this);

                    var scene = this.scene;
                    var sceneMode = scene.mode;

                    //Stop tracking
                    if (!defined(value) || !defined(value.position)) {
                        this._needTrackedEntityUpdate = false;
                        if (sceneMode === SceneMode.COLUMBUS_VIEW || sceneMode === SceneMode.SCENE2D) {
                            scene.screenSpaceCameraController.enableTranslate = true;
                        }

                        if (sceneMode === SceneMode.COLUMBUS_VIEW || sceneMode === SceneMode.SCENE3D) {
                            scene.screenSpaceCameraController.enableTilt = true;
                        }

                        this._entityView = undefined;
                        this.camera.lookAtTransform(Matrix4.IDENTITY);
                        return;
                    }

                    //We can't start tracking immediately, so we set a flag and start tracking
                    //when the bounding sphere is ready (most likely next frame).
                    this._needTrackedEntityUpdate = true;
                }
            }
        },
        /**
         * Gets or sets the object instance for which to display a selection indicator.
         * @memberof Viewer.prototype
         * @type {Entity}
         */
        selectedEntity : {
            get : function() {
                return this._selectedEntity;
            },
            set : function(value) {
                if (this._selectedEntity !== value) {
                    this._selectedEntity = value;
                    var selectionIndicatorViewModel = defined(this._selectionIndicator) ? this._selectionIndicator.viewModel : undefined;
                    if (defined(value)) {
                        if (defined(selectionIndicatorViewModel)) {
                            selectionIndicatorViewModel.animateAppear();
                        }
                    } else {
                        // Leave the info text in place here, it is needed during the exit animation.
                        if (defined(selectionIndicatorViewModel)) {
                            selectionIndicatorViewModel.animateDepart();
                        }
                    }
                    this._selectedEntityChanged.raiseEvent(value);
                }
            }
        },
        /**
         * Gets the event that is raised when the selected entity chages
         * @memberof Viewer.prototype
         * @type {Event}
         * @readonly
         */
        selectedEntityChanged : {
            get : function() {
                return this._selectedEntityChanged;
            }
        },
        /**
         * Gets or sets the data source to track with the viewer's clock.
         * @memberof Viewer.prototype
         * @type {DataSource}
         */
        clockTrackedDataSource : {
            get : function() {
                return this._clockTrackedDataSource;
            },
            set : function(value) {
                if (this._clockTrackedDataSource !== value) {
                    this._clockTrackedDataSource = value;
                    trackDataSourceClock(this._timeline, this.clock, value);
                }
            }
        }
    });

    /**
     * Extends the base viewer functionality with the provided mixin.
     * A mixin may add additional properties, functions, or other behavior
     * to the provided viewer instance.
     *
     * @param {Viewer~ViewerMixin} mixin The Viewer mixin to add to this instance.
     * @param {Object} [options] The options object to be passed to the mixin function.
     *
     * @see viewerDragDropMixin
     */
    Viewer.prototype.extend = function(mixin, options) {
                if (!defined(mixin)) {
            throw new DeveloperError('mixin is required.');
        }
        
        mixin(this, options);
    };

    /**
     * Resizes the widget to match the container size.
     * This function is called automatically as needed unless
     * <code>useDefaultRenderLoop</code> is set to false.
     */
    Viewer.prototype.resize = function() {
        var cesiumWidget = this._cesiumWidget;
        var container = this._container;
        var width = container.clientWidth;
        var height = container.clientHeight;
        var animationExists = defined(this._animation);
        var timelineExists = defined(this._timeline);

        if (!this._forceResize && width === this._lastWidth && height === this._lastHeight) {
            return;
        }

        cesiumWidget.resize();
        this._forceResize = false;
        var panelMaxHeight = height - 125;
        var baseLayerPickerDropDown = this._baseLayerPickerDropDown;

        if (defined(baseLayerPickerDropDown)) {
            baseLayerPickerDropDown.style.maxHeight = panelMaxHeight + 'px';
        }

        if (defined(this._geocoder)) {
            var geocoderSuggestions = this._geocoder.searchSuggestionsContainer;
            geocoderSuggestions.style.maxHeight = panelMaxHeight + 'px';
        }

        if (defined(this._infoBox)) {
            this._infoBox.viewModel.maxHeight = panelMaxHeight;
        }

        var timeline = this._timeline;
        var animationContainer;
        var animationWidth = 0;
        var creditLeft = 0;
        var creditBottom = 0;

        if (animationExists && window.getComputedStyle(this._animation.container).visibility !== 'hidden') {
            var lastWidth = this._lastWidth;
            animationContainer = this._animation.container;
            if (width > 900) {
                animationWidth = 169;
                if (lastWidth <= 900) {
                    animationContainer.style.width = '169px';
                    animationContainer.style.height = '112px';
                    this._animation.resize();
                }
            } else if (width >= 600) {
                animationWidth = 136;
                if (lastWidth < 600 || lastWidth > 900) {
                    animationContainer.style.width = '136px';
                    animationContainer.style.height = '90px';
                    this._animation.resize();
                }
            } else {
                animationWidth = 106;
                if (lastWidth > 600 || lastWidth === 0) {
                    animationContainer.style.width = '106px';
                    animationContainer.style.height = '70px';
                    this._animation.resize();
                }
            }
            creditLeft = animationWidth + 5;
        }

        if (timelineExists && window.getComputedStyle(this._timeline.container).visibility !== 'hidden') {
            var fullscreenButton = this._fullscreenButton;
            var vrButton = this._vrButton;
            var timelineContainer = timeline.container;
            var timelineStyle = timelineContainer.style;

            creditBottom = timelineContainer.clientHeight + 3;
            timelineStyle.left = animationWidth + 'px';

            var pixels = 0;
            if (defined(fullscreenButton)) {
                pixels += fullscreenButton.container.clientWidth;
            }
            if (defined(vrButton)) {
                pixels += vrButton.container.clientWidth;
            }

            timelineStyle.right = pixels + 'px';
            timeline.resize();
        }

        this._bottomContainer.style.left = creditLeft + 'px';
        this._bottomContainer.style.bottom = creditBottom + 'px';

        this._lastWidth = width;
        this._lastHeight = height;
    };

    /**
     * This forces the widget to re-think its layout, including
     * widget sizes and credit placement.
     */
    Viewer.prototype.forceResize = function() {
        this._lastWidth = 0;
        this.resize();
    };

    /**
     * Renders the scene.  This function is called automatically
     * unless <code>useDefaultRenderLoop</code> is set to false;
     */
    Viewer.prototype.render = function() {
        this._cesiumWidget.render();
    };

    /**
     * @returns {Boolean} true if the object has been destroyed, false otherwise.
     */
    Viewer.prototype.isDestroyed = function() {
        return false;
    };

    /**
     * Destroys the widget.  Should be called if permanently
     * removing the widget from layout.
     */
    Viewer.prototype.destroy = function() {
        var i;

        this.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
        this.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_DOUBLE_CLICK);

        // Unsubscribe from data sources
        var dataSources = this.dataSources;
        var dataSourceLength = dataSources.length;
        for (i = 0; i < dataSourceLength; i++) {
            this._dataSourceRemoved(dataSources, dataSources.get(i));
        }
        this._dataSourceRemoved(undefined, this._dataSourceDisplay.defaultDataSource);

        this._container.removeChild(this._element);
        this._element.removeChild(this._toolbar);

        this._eventHelper.removeAll();

        if (defined(this._geocoder)) {
            this._geocoder = this._geocoder.destroy();
        }

        if (defined(this._homeButton)) {
            this._homeButton = this._homeButton.destroy();
        }

        if (defined(this._sceneModePicker)) {
            this._sceneModePicker = this._sceneModePicker.destroy();
        }

        if (defined(this._baseLayerPicker)) {
            this._baseLayerPicker = this._baseLayerPicker.destroy();
        }

        if (defined(this._animation)) {
            this._element.removeChild(this._animation.container);
            this._animation = this._animation.destroy();
        }

        if (defined(this._timeline)) {
            this._timeline.removeEventListener('settime', onTimelineScrubfunction, false);
            this._element.removeChild(this._timeline.container);
            this._timeline = this._timeline.destroy();
        }

        if (defined(this._fullscreenButton)) {
            this._fullscreenSubscription.dispose();
            this._element.removeChild(this._fullscreenButton.container);
            this._fullscreenButton = this._fullscreenButton.destroy();
        }

        if (defined(this._vrButton)) {
            this._vrSubscription.dispose();
            this._vrModeSubscription.dispose();
            this._element.removeChild(this._vrButton.container);
            this._vrButton = this._vrButton.destroy();
        }

        if (defined(this._infoBox)) {
            this._element.removeChild(this._infoBox.container);
            this._infoBox = this._infoBox.destroy();
        }

        if (defined(this._selectionIndicator)) {
            this._element.removeChild(this._selectionIndicator.container);
            this._selectionIndicator = this._selectionIndicator.destroy();
        }

        this._clockViewModel = this._clockViewModel.destroy();
        this._dataSourceDisplay = this._dataSourceDisplay.destroy();
        this._cesiumWidget = this._cesiumWidget.destroy();

        if (this._destroyDataSourceCollection) {
            this._dataSourceCollection = this._dataSourceCollection.destroy();
        }

        return destroyObject(this);
    };

    /**
     * @private
     */
    Viewer.prototype._dataSourceAdded = function(dataSourceCollection, dataSource) {
        var entityCollection = dataSource.entities;
        entityCollection.collectionChanged.addEventListener(Viewer.prototype._onEntityCollectionChanged, this);
    };

    /**
     * @private
     */
    Viewer.prototype._dataSourceRemoved = function(dataSourceCollection, dataSource) {
        var entityCollection = dataSource.entities;
        entityCollection.collectionChanged.removeEventListener(Viewer.prototype._onEntityCollectionChanged, this);

        if (defined(this.trackedEntity)) {
            if (entityCollection.getById(this.trackedEntity.id) === this.trackedEntity) {
                this.trackedEntity = undefined;
            }
        }

        if (defined(this.selectedEntity)) {
            if (entityCollection.getById(this.selectedEntity.id) === this.selectedEntity) {
                this.selectedEntity = undefined;
            }
        }
    };

    /**
     * @private
     */
    Viewer.prototype._onTick = function(clock) {
        var time = clock.currentTime;

        var isUpdated = this._dataSourceDisplay.update(time);
        if (this._allowDataSourcesToSuspendAnimation) {
            this._clockViewModel.canAnimate = isUpdated;
        }

        var entityView = this._entityView;
        if (defined(entityView)) {
            var trackedEntity = this._trackedEntity;
            var trackedState = this._dataSourceDisplay.getBoundingSphere(trackedEntity, false, boundingSphereScratch);
            if (trackedState === BoundingSphereState.DONE) {
                entityView.update(time, boundingSphereScratch);
            }
        }

        var position;
        var enableCamera = false;
        var selectedEntity = this.selectedEntity;
        var showSelection = defined(selectedEntity) && this._enableInfoOrSelection;

        if (showSelection && selectedEntity.isShowing && selectedEntity.isAvailable(time)) {
            var state = this._dataSourceDisplay.getBoundingSphere(selectedEntity, true, boundingSphereScratch);
            if (state !== BoundingSphereState.FAILED) {
                position = boundingSphereScratch.center;
            } else if (defined(selectedEntity.position)) {
                position = selectedEntity.position.getValue(time, position);
            }
            enableCamera = defined(position);
        }

        var selectionIndicatorViewModel = defined(this._selectionIndicator) ? this._selectionIndicator.viewModel : undefined;
        if (defined(selectionIndicatorViewModel)) {
            selectionIndicatorViewModel.position = Cartesian3.clone(position, selectionIndicatorViewModel.position);
            selectionIndicatorViewModel.showSelection = showSelection && enableCamera;
            selectionIndicatorViewModel.update();
        }

        var infoBoxViewModel = defined(this._infoBox) ? this._infoBox.viewModel : undefined;
        if (defined(infoBoxViewModel)) {
            infoBoxViewModel.showInfo = showSelection;
            infoBoxViewModel.enableCamera = enableCamera;
            infoBoxViewModel.isCameraTracking = (this.trackedEntity === this.selectedEntity);

            if (showSelection) {
                infoBoxViewModel.titleText = defaultValue(selectedEntity.name, selectedEntity.id);
                infoBoxViewModel.description = Property.getValueOrDefault(selectedEntity.description, time, '');
            } else {
                infoBoxViewModel.titleText = '';
                infoBoxViewModel.description = '';
            }
        }
    };

    /**
     * @private
     */
    Viewer.prototype._onEntityCollectionChanged = function(collection, added, removed) {
        var length = removed.length;
        for (var i = 0; i < length; i++) {
            var removedObject = removed[i];
            if (this.trackedEntity === removedObject) {
                this.trackedEntity = undefined;
            }
            if (this.selectedEntity === removedObject) {
                this.selectedEntity = undefined;
            }
        }
    };

    /**
     * @private
     */
    Viewer.prototype._onInfoBoxCameraClicked = function(infoBoxViewModel) {
        if (infoBoxViewModel.isCameraTracking && (this.trackedEntity === this.selectedEntity)) {
            this.trackedEntity = undefined;
        } else {
            var selectedEntity = this.selectedEntity;
            var position = selectedEntity.position;
            if (defined(position)) {
                this.trackedEntity = this.selectedEntity;
            } else {
                this.zoomTo(this.selectedEntity);
            }
        }
    };

    /**
     * @private
     */
    Viewer.prototype._clearTrackedObject = function() {
        this.trackedEntity = undefined;
    };

    /**
     * @private
     */
    Viewer.prototype._onInfoBoxClockClicked = function(infoBoxViewModel) {
        this.selectedEntity = undefined;
    };

    /**
     * @private
     */
    Viewer.prototype._clearObjects = function() {
        this.trackedEntity = undefined;
        this.selectedEntity = undefined;
    };

    /**
     * @private
     */
    Viewer.prototype._onDataSourceChanged = function(dataSource) {
        if (this.clockTrackedDataSource === dataSource) {
            trackDataSourceClock(this.timeline, this.clock, dataSource);
        }
    };

    /**
     * @private
     */
    Viewer.prototype._onDataSourceAdded = function(dataSourceCollection, dataSource) {
        if (this._automaticallyTrackDataSourceClocks) {
            this.clockTrackedDataSource = dataSource;
        }
        var id = dataSource.entities.id;
        var removalFunc = this._eventHelper.add(dataSource.changedEvent, Viewer.prototype._onDataSourceChanged, this);
        this._dataSourceChangedListeners[id] = removalFunc;
    };

    /**
     * @private
     */
    Viewer.prototype._onDataSourceRemoved = function(dataSourceCollection, dataSource) {
        var resetClock = (this.clockTrackedDataSource === dataSource);
        var id = dataSource.entities.id;
        this._dataSourceChangedListeners[id]();
        this._dataSourceChangedListeners[id] = undefined;
        if (resetClock) {
            var numDataSources = dataSourceCollection.length;
            if (this._automaticallyTrackDataSourceClocks && numDataSources > 0) {
                this.clockTrackedDataSource = dataSourceCollection.get(numDataSources - 1);
            } else {
                this.clockTrackedDataSource = undefined;
            }
        }
    };

    /**
     * Asynchronously sets the camera to view the provided entity, entities, or data source.
     * If the data source is still in the process of loading or the visualization is otherwise still loading,
     * this method waits for the data to be ready before performing the zoom.
     *
     * <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
     * The heading and the pitch angles are defined in the local east-north-up reference frame.
     * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
     * angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
     * zero, a range will be computed such that the whole bounding sphere is visible.</p>
     *
     * <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
     * target will be the range. The heading will be determined from the offset. If the heading cannot be
     * determined from the offset, the heading will be north.</p>
     *
     * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer>} target The entity, array of entities, entity collection, data source or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
     * @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
     * @returns {Promise.<Boolean>} A Promise that resolves to true if the zoom was successful or false if the entity is not currently visualized in the scene or the zoom was cancelled.
     */
    Viewer.prototype.zoomTo = function(target, offset) {
        return zoomToOrFly(this, target, offset, false);
    };

    /**
     * Flies the camera to the provided entity, entities, or data source.
     * If the data source is still in the process of loading or the visualization is otherwise still loading,
     * this method waits for the data to be ready before performing the flight.
     *
     * <p>The offset is heading/pitch/range in the local east-north-up reference frame centered at the center of the bounding sphere.
     * The heading and the pitch angles are defined in the local east-north-up reference frame.
     * The heading is the angle from y axis and increasing towards the x axis. Pitch is the rotation from the xy-plane. Positive pitch
     * angles are above the plane. Negative pitch angles are below the plane. The range is the distance from the center. If the range is
     * zero, a range will be computed such that the whole bounding sphere is visible.</p>
     *
     * <p>In 2D, there must be a top down view. The camera will be placed above the target looking down. The height above the
     * target will be the range. The heading will be determined from the offset. If the heading cannot be
     * determined from the offset, the heading will be north.</p>
     *
     * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Promise.<Entity|Entity[]|EntityCollection|DataSource|ImageryLayer>} target The entity, array of entities, entity collection, data source or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
     * @param {Object} [options] Object with the following properties:
     * @param {Number} [options.duration=3.0] The duration of the flight in seconds.
     * @param {Number} [options.maximumHeight] The maximum height at the peak of the flight.
     * @param {HeadingPitchRange} [options.offset] The offset from the target in the local east-north-up reference frame centered at the target.
     * @returns {Promise.<Boolean>} A Promise that resolves to true if the flight was successful or false if the entity is not currently visualized in the scene or the flight was cancelled.
     */
    Viewer.prototype.flyTo = function(target, options) {
        return zoomToOrFly(this, target, options, true);
    };

    function zoomToOrFly(that, zoomTarget, options, isFlight) {
                if (!defined(zoomTarget)) {
            throw new DeveloperError('zoomTarget is required.');
        }
        
        cancelZoom(that);

        //We can't actually perform the zoom until all visualization is ready and
        //bounding spheres have been computed.  Therefore we create and return
        //a deferred which will be resolved as part of the post-render step in the
        //frame that actually performs the zoom
        var zoomPromise = when.defer();
        that._zoomPromise = zoomPromise;
        that._zoomIsFlight = isFlight;
        that._zoomOptions = options;

        when(zoomTarget, function(zoomTarget) {
            //Only perform the zoom if it wasn't cancelled before the promise resolved.
            if (that._zoomPromise !== zoomPromise) {
                return;
            }

            //If the zoom target is a rectangular imagery in an ImageLayer
            if (zoomTarget instanceof ImageryLayer) {
                zoomTarget.getViewableRectangle().then(function(rectangle) {
                    //Only perform the zoom if it wasn't cancelled before the promise was resolved
                    if (that._zoomPromise === zoomPromise) {
                        that._zoomTarget = rectangle;
                    }
                });
                return;
            }

            //If the zoom target is a data source, and it's in the middle of loading, wait for it to finish loading.
            if (zoomTarget.isLoading && defined(zoomTarget.loadingEvent)) {
                var removeEvent = zoomTarget.loadingEvent.addEventListener(function() {
                    removeEvent();

                    //Only perform the zoom if it wasn't cancelled before the data source finished.
                    if (that._zoomPromise === zoomPromise) {
                        that._zoomTarget = zoomTarget.entities.values.slice(0);
                    }
                });
                return;
            }

            //Zoom target is already an array, just copy it and return.
            if (isArray(zoomTarget)) {
                that._zoomTarget = zoomTarget.slice(0);
                return;
            }

            //If zoomTarget is an EntityCollection, this will retrieve the array
            zoomTarget = defaultValue(zoomTarget.values, zoomTarget);

            //If zoomTarget is a DataSource, this will retrieve the array.
            if (defined(zoomTarget.entities)) {
                zoomTarget = zoomTarget.entities.values;
            }

            if (isArray(zoomTarget)) {
                that._zoomTarget = zoomTarget.slice(0);
            } else {
                //Single entity
                that._zoomTarget = [zoomTarget];
            }
        });

        return zoomPromise.promise;
    }

    function clearZoom(viewer) {
        viewer._zoomPromise = undefined;
        viewer._zoomTarget = undefined;
        viewer._zoomOptions = undefined;
    }

    function cancelZoom(viewer) {
        var zoomPromise = viewer._zoomPromise;
        if (defined(zoomPromise)) {
            clearZoom(viewer);
            zoomPromise.resolve(false);
        }
    }

    /**
     * @private
     */
    Viewer.prototype._postRender = function() {
        updateZoomTarget(this);
        updateTrackedEntity(this);
    };

    function updateZoomTarget(viewer) {
        var entities = viewer._zoomTarget;
        if (!defined(entities) || viewer.scene.mode === SceneMode.MORPHING) {
            return;
        }

        var scene = viewer.scene;
        var camera = scene.camera;
        var zoomPromise = viewer._zoomPromise;
        var zoomOptions = defaultValue(viewer._zoomOptions, {});

        //If zoomTarget was an ImageryLayer
        if (entities instanceof Rectangle) {
            var options = {
                destination : entities,
                duration : zoomOptions.duration,
                maximumHeight : zoomOptions.maximumHeight,
                complete : function() {
                    zoomPromise.resolve(true);
                },
                cancel : function() {
                    zoomPromise.resolve(false);
                }
            };

            if (viewer._zoomIsFlight) {
                camera.flyTo(options);
            } else {
                camera.setView(options);
                zoomPromise.resolve(true);
            }
            clearZoom(viewer);
            return;
        }

        var boundingSpheres = [];
        for (var i = 0, len = entities.length; i < len; i++) {
            var state = viewer._dataSourceDisplay.getBoundingSphere(entities[i], false, boundingSphereScratch);

            if (state === BoundingSphereState.PENDING) {
                return;
            } else if (state !== BoundingSphereState.FAILED) {
                boundingSpheres.push(BoundingSphere.clone(boundingSphereScratch));
            }
        }

        if (boundingSpheres.length === 0) {
            cancelZoom(viewer);
            return;
        }

        //Stop tracking the current entity.
        viewer.trackedEntity = undefined;

        var boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres);
        var controller = scene.screenSpaceCameraController;
        controller.minimumZoomDistance = Math.min(controller.minimumZoomDistance, boundingSphere.radius * 0.5);

        if (!viewer._zoomIsFlight) {
            camera.viewBoundingSphere(boundingSphere, viewer._zoomOptions);
            camera.lookAtTransform(Matrix4.IDENTITY);
            clearZoom(viewer);
            zoomPromise.resolve(true);
        } else {
            clearZoom(viewer);
            camera.flyToBoundingSphere(boundingSphere, {
                duration : zoomOptions.duration,
                maximumHeight : zoomOptions.maximumHeight,
                complete : function() {
                    zoomPromise.resolve(true);
                },
                cancel : function() {
                    zoomPromise.resolve(false);
                },
                offset : zoomOptions.offset
            });
        }
    }

    function updateTrackedEntity(viewer) {
        if (!viewer._needTrackedEntityUpdate) {
            return;
        }

        var trackedEntity = viewer._trackedEntity;
        var currentTime = viewer.clock.currentTime;

        //Verify we have a current position at this time. This is only triggered if a position
        //has become undefined after trackedEntity is set but before the boundingSphere has been
        //computed. In this case, we will track the entity once it comes back into existence.
        var currentPosition = Property.getValueOrUndefined(trackedEntity.position, currentTime);

        if (!defined(currentPosition)) {
            return;
        }

        var scene = viewer.scene;

        var state = viewer._dataSourceDisplay.getBoundingSphere(trackedEntity, false, boundingSphereScratch);
        if (state === BoundingSphereState.PENDING) {
            return;
        }

        var sceneMode = scene.mode;
        if (sceneMode === SceneMode.COLUMBUS_VIEW || sceneMode === SceneMode.SCENE2D) {
            scene.screenSpaceCameraController.enableTranslate = false;
        }

        if (sceneMode === SceneMode.COLUMBUS_VIEW || sceneMode === SceneMode.SCENE3D) {
            scene.screenSpaceCameraController.enableTilt = false;
        }

        var bs = state !== BoundingSphereState.FAILED ? boundingSphereScratch : undefined;
        viewer._entityView = new EntityView(trackedEntity, scene, scene.mapProjection.ellipsoid);
        viewer._entityView.update(currentTime, bs);
        viewer._needTrackedEntityUpdate = false;
    }

    /**
     * A function that augments a Viewer instance with additional functionality.
     * @callback Viewer~ViewerMixin
     * @param {Viewer} viewer The viewer instance.
     * @param {Object} options Options object to be passed to the mixin function.
     *
     * @see Viewer#extend
     */

    return Viewer;
});

/*global define*/
define('Widgets/Viewer/viewerCesiumInspectorMixin',[
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../CesiumInspector/CesiumInspector'
    ], function(
        defined,
        defineProperties,
        DeveloperError,
        CesiumInspector) {
    'use strict';

    /**
     * A mixin which adds the CesiumInspector widget to the Viewer widget.
     * Rather than being called directly, this function is normally passed as
     * a parameter to {@link Viewer#extend}, as shown in the example below.
     * @exports viewerCesiumInspectorMixin
     *
     * @param {Viewer} viewer The viewer instance.
     *
     * @exception {DeveloperError} viewer is required.
     *
     * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Cesium%20Inspector.html|Cesium Sandcastle Cesium Inspector Demo}
     *
     * @example
     * var viewer = new Cesium.Viewer('cesiumContainer');
     * viewer.extend(Cesium.viewerCesiumInspectorMixin);
     */
    function viewerCesiumInspectorMixin(viewer) {
                if (!defined(viewer)) {
            throw new DeveloperError('viewer is required.');
        }
        
        var cesiumInspectorContainer = document.createElement('div');
        cesiumInspectorContainer.className = 'cesium-viewer-cesiumInspectorContainer';
        viewer.container.appendChild(cesiumInspectorContainer);
        var cesiumInspector = new CesiumInspector(cesiumInspectorContainer, viewer.scene);

        defineProperties(viewer, {
            cesiumInspector : {
                get : function() {
                    return cesiumInspector;
                }
            }
        });

        viewer.scene.postRender.addEventListener(function() {
            viewer.cesiumInspector.viewModel.update();
        });
    }

    return viewerCesiumInspectorMixin;
});

/*global define*/
define('Widgets/Viewer/viewerDragDropMixin',[
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../../Core/Event',
        '../../Core/wrapFunction',
        '../../DataSources/CzmlDataSource',
        '../../DataSources/GeoJsonDataSource',
        '../../DataSources/KmlDataSource',
        '../getElement'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        Event,
        wrapFunction,
        CzmlDataSource,
        GeoJsonDataSource,
        KmlDataSource,
        getElement) {
    'use strict';

    /**
     * A mixin which adds default drag and drop support for CZML files to the Viewer widget.
     * Rather than being called directly, this function is normally passed as
     * a parameter to {@link Viewer#extend}, as shown in the example below.
     * @exports viewerDragDropMixin
     *
     * @param {Viewer} viewer The viewer instance.
     * @param {Object} [options] Object with the following properties:
     * @param {Element|String} [options.dropTarget=viewer.container] The DOM element which will serve as the drop target.
     * @param {Boolean} [options.clearOnDrop=true] When true, dropping files will clear all existing data sources first, when false, new data sources will be loaded after the existing ones.
     * @param {Boolean} [options.flyToOnDrop=true] When true, dropping files will fly to the data source once it is loaded.
     * @param {Boolean} [options.clampToGround=true] When true, datasources are clamped to the ground.
     * @param {DefaultProxy} [options.proxy] The proxy to be used for KML network links.
     *
     * @exception {DeveloperError} Element with id <options.dropTarget> does not exist in the document.
     * @exception {DeveloperError} dropTarget is already defined by another mixin.
     * @exception {DeveloperError} dropEnabled is already defined by another mixin.
     * @exception {DeveloperError} dropError is already defined by another mixin.
     * @exception {DeveloperError} clearOnDrop is already defined by another mixin.
     *
     * @example
     * // Add basic drag and drop support and pop up an alert window on error.
     * var viewer = new Cesium.Viewer('cesiumContainer');
     * viewer.extend(Cesium.viewerDragDropMixin);
     * viewer.dropError.addEventListener(function(viewerArg, source, error) {
     *     window.alert('Error processing ' + source + ':' + error);
     * });
     */
    function viewerDragDropMixin(viewer, options) {
                if (!defined(viewer)) {
            throw new DeveloperError('viewer is required.');
        }
        if (viewer.hasOwnProperty('dropTarget')) {
            throw new DeveloperError('dropTarget is already defined by another mixin.');
        }
        if (viewer.hasOwnProperty('dropEnabled')) {
            throw new DeveloperError('dropEnabled is already defined by another mixin.');
        }
        if (viewer.hasOwnProperty('dropError')) {
            throw new DeveloperError('dropError is already defined by another mixin.');
        }
        if (viewer.hasOwnProperty('clearOnDrop')) {
            throw new DeveloperError('clearOnDrop is already defined by another mixin.');
        }
        if (viewer.hasOwnProperty('flyToOnDrop')) {
            throw new DeveloperError('flyToOnDrop is already defined by another mixin.');
        }
        
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        //Local variables to be closed over by defineProperties.
        var dropEnabled = true;
        var flyToOnDrop = defaultValue(options.flyToOnDrop, true);
        var dropError = new Event();
        var clearOnDrop = defaultValue(options.clearOnDrop, true);
        var dropTarget = defaultValue(options.dropTarget, viewer.container);
        var clampToGround = defaultValue(options.clampToGround, true);
        var proxy = options.proxy;

        dropTarget = getElement(dropTarget);

        defineProperties(viewer, {
            /**
             * Gets or sets the element to serve as the drop target.
             * @memberof viewerDragDropMixin.prototype
             * @type {Element}
             */
            dropTarget : {
                //TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832
                get : function() {
                    return dropTarget;
                },
                set : function(value) {
                                        if (!defined(value)) {
                        throw new DeveloperError('value is required.');
                    }
                    
                    unsubscribe(dropTarget, handleDrop);
                    dropTarget = value;
                    subscribe(dropTarget, handleDrop);
                }
            },

            /**
             * Gets or sets a value indicating if drag and drop support is enabled.
             * @memberof viewerDragDropMixin.prototype
             * @type {Element}
             */
            dropEnabled : {
                get : function() {
                    return dropEnabled;
                },
                set : function(value) {
                    if (value !== dropEnabled) {
                        if (value) {
                            subscribe(dropTarget, handleDrop);
                        } else {
                            unsubscribe(dropTarget, handleDrop);
                        }
                        dropEnabled = value;
                    }
                }
            },

            /**
             * Gets the event that will be raised when an error is encountered during drop processing.
             * @memberof viewerDragDropMixin.prototype
             * @type {Event}
             */
            dropError : {
                get : function() {
                    return dropError;
                }
            },

            /**
             * Gets or sets a value indicating if existing data sources should be cleared before adding the newly dropped sources.
             * @memberof viewerDragDropMixin.prototype
             * @type {Boolean}
             */
            clearOnDrop : {
                get : function() {
                    return clearOnDrop;
                },
                set : function(value) {
                    clearOnDrop = value;
                }
            },

            /**
             * Gets or sets a value indicating if the camera should fly to the data source after it is loaded.
             * @memberof viewerDragDropMixin.prototype
             * @type {Boolean}
             */
            flyToOnDrop : {
                get : function() {
                    return flyToOnDrop;
                },
                set : function(value) {
                    flyToOnDrop = value;
                }
            },

            /**
             * Gets or sets the proxy to be used for KML.
             * @memberof viewerDragDropMixin.prototype
             * @type {DefaultProxy}
             */
            proxy : {
                get : function() {
                    return proxy;
                },
                set : function(value) {
                    proxy = value;
                }
            },

            /**
             * Gets or sets a value indicating if the datasources should be clamped to the ground
             * @memberof viewerDragDropMixin.prototype
             * @type {Boolean}
             */
            clampToGround : {
                get : function() {
                    return clampToGround;
                },
                set : function(value) {
                    clampToGround = value;
                }
            }
        });

        function handleDrop(event) {
            stop(event);

            if (clearOnDrop) {
                viewer.entities.removeAll();
                viewer.dataSources.removeAll();
            }

            var files = event.dataTransfer.files;
            var length = files.length;
            for (var i = 0; i < length; i++) {
                var file = files[i];
                var reader = new FileReader();
                reader.onload = createOnLoadCallback(viewer, file, proxy, clampToGround);
                reader.onerror = createDropErrorCallback(viewer, file);
                reader.readAsText(file);
            }
        }

        //Enable drop by default;
        subscribe(dropTarget, handleDrop);

        //Wrap the destroy function to make sure all events are unsubscribed from
        viewer.destroy = wrapFunction(viewer, viewer.destroy, function() {
            viewer.dropEnabled = false;
        });

        //Specs need access to handleDrop
        viewer._handleDrop = handleDrop;
    }

    function stop(event) {
        event.stopPropagation();
        event.preventDefault();
    }

    function unsubscribe(dropTarget, handleDrop) {
        var currentTarget = dropTarget;
        if (defined(currentTarget)) {
            currentTarget.removeEventListener('drop', handleDrop, false);
            currentTarget.removeEventListener('dragenter', stop, false);
            currentTarget.removeEventListener('dragover', stop, false);
            currentTarget.removeEventListener('dragexit', stop, false);
        }
    }

    function subscribe(dropTarget, handleDrop) {
        dropTarget.addEventListener('drop', handleDrop, false);
        dropTarget.addEventListener('dragenter', stop, false);
        dropTarget.addEventListener('dragover', stop, false);
        dropTarget.addEventListener('dragexit', stop, false);
    }

    function createOnLoadCallback(viewer, file, proxy, clampToGround) {
        var scene = viewer.scene;
        return function(evt) {
            var fileName = file.name;
            try {
                var loadPromise;

                if (/\.czml$/i.test(fileName)) {
                    loadPromise = CzmlDataSource.load(JSON.parse(evt.target.result), {
                        sourceUri : fileName
                    });
                } else if (/\.geojson$/i.test(fileName) || /\.json$/i.test(fileName) || /\.topojson$/i.test(fileName)) {
                    loadPromise = GeoJsonDataSource.load(JSON.parse(evt.target.result), {
                        sourceUri : fileName,
                        clampToGround : clampToGround
                    });
                } else if (/\.(kml|kmz)$/i.test(fileName)) {
                    loadPromise = KmlDataSource.load(file, {
                        sourceUri : fileName,
                        proxy : proxy,
                        camera : scene.camera,
                        canvas : scene.canvas
                    });
                } else {
                    viewer.dropError.raiseEvent(viewer, fileName, 'Unrecognized file: ' + fileName);
                    return;
                }

                if (defined(loadPromise)) {
                    viewer.dataSources.add(loadPromise).then(function(dataSource) {
                        if (viewer.flyToOnDrop) {
                            viewer.flyTo(dataSource);
                        }
                    }).otherwise(function(error) {
                        viewer.dropError.raiseEvent(viewer, fileName, error);
                    });
                }
            } catch (error) {
                viewer.dropError.raiseEvent(viewer, fileName, error);
            }
        };
    }

    function createDropErrorCallback(viewer, file) {
        return function(evt) {
            viewer.dropError.raiseEvent(viewer, file.name, evt.target.error);
        };
    }

    return viewerDragDropMixin;
});

/*global define*/
define('Widgets/Viewer/viewerPerformanceWatchdogMixin',[
        '../../Core/defaultValue',
        '../../Core/defined',
        '../../Core/defineProperties',
        '../../Core/DeveloperError',
        '../PerformanceWatchdog/PerformanceWatchdog'
    ], function(
        defaultValue,
        defined,
        defineProperties,
        DeveloperError,
        PerformanceWatchdog) {
    'use strict';

    /**
     * A mixin which adds the {@link PerformanceWatchdog} widget to the {@link Viewer} widget.
     * Rather than being called directly, this function is normally passed as
     * a parameter to {@link Viewer#extend}, as shown in the example below.
     * @exports viewerPerformanceWatchdogMixin
     *
     * @param {Viewer} viewer The viewer instance.
     * @param {Object} [options] An object with properties.
     * @param {String} [options.lowFrameRateMessage='This application appears to be performing poorly on your system.  Please try using a different web browser or updating your video drivers.'] The
     *        message to display when a low frame rate is detected.  The message is interpeted as HTML, so make sure
     *        it comes from a trusted source so that your application is not vulnerable to cross-site scripting attacks.
     *
     * @exception {DeveloperError} viewer is required.
     *
     * @example
     * var viewer = new Cesium.Viewer('cesiumContainer');
     * viewer.extend(Cesium.viewerPerformanceWatchdogMixin, {
     *     lowFrameRateMessage : 'Why is this going so <em>slowly</em>?'
     * });
     */
    function viewerPerformanceWatchdogMixin(viewer, options) {
                if (!defined(viewer)) {
            throw new DeveloperError('viewer is required.');
        }
        
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        var performanceWatchdog = new PerformanceWatchdog({
            scene : viewer.scene,
            container : viewer.bottomContainer,
            lowFrameRateMessage : options.lowFrameRateMessage
        });

        defineProperties(viewer, {
            performanceWatchdog : {
                get : function() {
                    return performanceWatchdog;
                }
            }
        });
    }

    return viewerPerformanceWatchdogMixin;
});

/*global define*/
define('Workers/createTaskProcessorWorker',[
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/formatError'
    ], function(
        defaultValue,
        defined,
        formatError) {
    'use strict';

    /**
     * Creates an adapter function to allow a calculation function to operate as a Web Worker,
     * paired with TaskProcessor, to receive tasks and return results.
     *
     * @exports createTaskProcessorWorker
     *
     * @param {createTaskProcessorWorker~WorkerFunction} workerFunction The calculation function,
     *        which takes parameters and returns a result.
     * @returns {createTaskProcessorWorker~TaskProcessorWorkerFunction} A function that adapts the
     *          calculation function to work as a Web Worker onmessage listener with TaskProcessor.
     *
     *
     * @example
     * function doCalculation(parameters, transferableObjects) {
     *   // calculate some result using the inputs in parameters
     *   return result;
     * }
     *
     * return Cesium.createTaskProcessorWorker(doCalculation);
     * // the resulting function is compatible with TaskProcessor
     * 
     * @see TaskProcessor
     * @see {@link http://www.w3.org/TR/workers/|Web Workers}
     * @see {@link http://www.w3.org/TR/html5/common-dom-interfaces.html#transferable-objects|Transferable objects}
     */
    function createTaskProcessorWorker(workerFunction) {
        var postMessage;
        var transferableObjects = [];
        var responseMessage = {
            id : undefined,
            result : undefined,
            error : undefined
        };

        return function(event) {
            /*global self*/
            var data = event.data;

            transferableObjects.length = 0;
            responseMessage.id = data.id;
            responseMessage.error = undefined;
            responseMessage.result = undefined;

            try {
                responseMessage.result = workerFunction(data.parameters, transferableObjects);
            } catch (e) {
                if (e instanceof Error) {
                    // Errors can't be posted in a message, copy the properties
                    responseMessage.error = {
                        name : e.name,
                        message : e.message,
                        stack : e.stack
                    };
                } else {
                    responseMessage.error = e;
                }
            }

            if (!defined(postMessage)) {
                postMessage = defaultValue(self.webkitPostMessage, self.postMessage);
            }

            if (!data.canTransferArrayBuffer) {
                transferableObjects.length = 0;
            }

            try {
                postMessage(responseMessage, transferableObjects);
            } catch (e) {
                // something went wrong trying to post the message, post a simpler
                // error that we can be sure will be cloneable
                responseMessage.result = undefined;
                responseMessage.error = 'postMessage failed with error: ' + formatError(e) + '\n  with responseMessage: ' + JSON.stringify(responseMessage);
                postMessage(responseMessage);
            }
        };
    }

    /**
     * A function that performs a calculation in a Web Worker.
     * @callback createTaskProcessorWorker~WorkerFunction
     *
     * @param {Object} parameters Parameters to the calculation.
     * @param {Array} transferableObjects An array that should be filled with references to objects inside
     *        the result that should be transferred back to the main document instead of copied.
     * @returns {Object} The result of the calculation.
     *
     * @example
     * function calculate(parameters, transferableObjects) {
     *   // perform whatever calculation is necessary.
     *   var typedArray = new Float32Array(0);
     *
     *   // typed arrays are transferable
     *   transferableObjects.push(typedArray)
     *
     *   return {
     *      typedArray : typedArray
     *   };
     * }
     */

    /**
     * A Web Worker message event handler function that handles the interaction with TaskProcessor,
     * specifically, task ID management and posting a response message containing the result.
     * @callback createTaskProcessorWorker~TaskProcessorWorkerFunction
     *
     * @param {Object} event The onmessage event object.
     */

    return createTaskProcessorWorker;
});

/*global define*/
define('Cesium',['./Core/appendForwardSlash', './Core/ArcGisImageServerTerrainProvider', './Core/arrayRemoveDuplicates', './Core/AssociativeArray', './Core/AttributeCompression', './Core/AxisAlignedBoundingBox', './Core/barycentricCoordinates', './Core/binarySearch', './Core/BingMapsApi', './Core/BingMapsGeocoderService', './Core/BoundingRectangle', './Core/BoundingSphere', './Core/BoxGeometry', './Core/BoxOutlineGeometry', './Core/buildModuleUrl', './Core/cancelAnimationFrame', './Core/Cartesian2', './Core/Cartesian3', './Core/Cartesian4', './Core/Cartographic', './Core/CartographicGeocoderService', './Core/CatmullRomSpline', './Core/CesiumTerrainProvider', './Core/Check', './Core/CircleGeometry', './Core/CircleOutlineGeometry', './Core/Clock', './Core/ClockRange', './Core/ClockStep', './Core/clone', './Core/Color', './Core/ColorGeometryInstanceAttribute', './Core/combine', './Core/ComponentDatatype', './Core/CompressedTextureBuffer', './Core/CornerType', './Core/CorridorGeometry', './Core/CorridorGeometryLibrary', './Core/CorridorOutlineGeometry', './Core/createGuid', './Core/Credit', './Core/CubicRealPolynomial', './Core/CylinderGeometry', './Core/CylinderGeometryLibrary', './Core/CylinderOutlineGeometry', './Core/DefaultProxy', './Core/defaultValue', './Core/defined', './Core/defineProperties', './Core/deprecationWarning', './Core/destroyObject', './Core/DeveloperError', './Core/DistanceDisplayCondition', './Core/DistanceDisplayConditionGeometryInstanceAttribute', './Core/EarthOrientationParameters', './Core/EarthOrientationParametersSample', './Core/EasingFunction', './Core/EllipseGeometry', './Core/EllipseGeometryLibrary', './Core/EllipseOutlineGeometry', './Core/Ellipsoid', './Core/EllipsoidalOccluder', './Core/EllipsoidGeodesic', './Core/EllipsoidGeometry', './Core/EllipsoidOutlineGeometry', './Core/EllipsoidTangentPlane', './Core/EllipsoidTerrainProvider', './Core/EncodedCartesian3', './Core/Event', './Core/EventHelper', './Core/ExtrapolationType', './Core/FeatureDetection', './Core/formatError', './Core/freezeObject', './Core/Fullscreen', './Core/GeocoderService', './Core/GeographicProjection', './Core/GeographicTilingScheme', './Core/Geometry', './Core/GeometryAttribute', './Core/GeometryAttributes', './Core/GeometryInstance', './Core/GeometryInstanceAttribute', './Core/GeometryPipeline', './Core/GeometryType', './Core/getAbsoluteUri', './Core/getBaseUri', './Core/getExtensionFromUri', './Core/getFilenameFromUri', './Core/getImagePixels', './Core/getMagic', './Core/getStringFromTypedArray', './Core/getTimestamp', './Core/GregorianDate', './Core/HeadingPitchRange', './Core/HeadingPitchRoll', './Core/HeightmapTerrainData', './Core/HeightmapTessellator', './Core/HermitePolynomialApproximation', './Core/HermiteSpline', './Core/Iau2000Orientation', './Core/Iau2006XysData', './Core/Iau2006XysSample', './Core/IauOrientationAxes', './Core/IauOrientationParameters', './Core/IndexDatatype', './Core/InterpolationAlgorithm', './Core/Intersect', './Core/Intersections2D', './Core/IntersectionTests', './Core/Interval', './Core/isArray', './Core/isCrossOriginUrl', './Core/isLeapYear', './Core/Iso8601', './Core/joinUrls', './Core/JulianDate', './Core/KeyboardEventModifier', './Core/LagrangePolynomialApproximation', './Core/LeapSecond', './Core/LinearApproximation', './Core/LinearSpline', './Core/loadArrayBuffer', './Core/loadBlob', './Core/loadCRN', './Core/loadImage', './Core/loadImageFromTypedArray', './Core/loadImageViaBlob', './Core/loadJson', './Core/loadJsonp', './Core/loadKTX', './Core/loadText', './Core/loadWithXhr', './Core/loadXML', './Core/MapboxApi', './Core/MapProjection', './Core/Math', './Core/Matrix2', './Core/Matrix3', './Core/Matrix4', './Core/mergeSort', './Core/NearFarScalar', './Core/objectToQuery', './Core/Occluder', './Core/oneTimeWarning', './Core/OrientedBoundingBox', './Core/Packable', './Core/PackableForInterpolation', './Core/parseResponseHeaders', './Core/PinBuilder', './Core/PixelFormat', './Core/Plane', './Core/PointGeometry', './Core/pointInsideTriangle', './Core/PolygonGeometry', './Core/PolygonGeometryLibrary', './Core/PolygonHierarchy', './Core/PolygonOutlineGeometry', './Core/PolygonPipeline', './Core/PolylineGeometry', './Core/PolylinePipeline', './Core/PolylineVolumeGeometry', './Core/PolylineVolumeGeometryLibrary', './Core/PolylineVolumeOutlineGeometry', './Core/PrimitiveType', './Core/QuadraticRealPolynomial', './Core/QuantizedMeshTerrainData', './Core/QuarticRealPolynomial', './Core/Quaternion', './Core/QuaternionSpline', './Core/queryToObject', './Core/Queue', './Core/Ray', './Core/Rectangle', './Core/RectangleGeometry', './Core/RectangleGeometryLibrary', './Core/RectangleOutlineGeometry', './Core/ReferenceFrame', './Core/requestAnimationFrame', './Core/RequestErrorEvent', './Core/RuntimeError', './Core/sampleTerrain', './Core/sampleTerrainMostDetailed', './Core/scaleToGeodeticSurface', './Core/ScreenSpaceEventHandler', './Core/ScreenSpaceEventType', './Core/ShowGeometryInstanceAttribute', './Core/Simon1994PlanetaryPositions', './Core/SimplePolylineGeometry', './Core/SphereGeometry', './Core/SphereOutlineGeometry', './Core/Spherical', './Core/Spline', './Core/subdivideArray', './Core/TaskProcessor', './Core/TerrainData', './Core/TerrainEncoding', './Core/TerrainMesh', './Core/TerrainProvider', './Core/TerrainQuantization', './Core/throttleRequestByServer', './Core/TileAvailability', './Core/TileProviderError', './Core/TilingScheme', './Core/TimeConstants', './Core/TimeInterval', './Core/TimeIntervalCollection', './Core/TimeStandard', './Core/Tipsify', './Core/Transforms', './Core/TranslationRotationScale', './Core/TridiagonalSystemSolver', './Core/TrustedServers', './Core/VertexFormat', './Core/VideoSynchronizer', './Core/Visibility', './Core/VRTheWorldTerrainProvider', './Core/WallGeometry', './Core/WallGeometryLibrary', './Core/WallOutlineGeometry', './Core/WebGLConstants', './Core/WebMercatorProjection', './Core/WebMercatorTilingScheme', './Core/WindingOrder', './Core/wrapFunction', './Core/writeTextToCanvas', './DataSources/BillboardGraphics', './DataSources/BillboardVisualizer', './DataSources/BoundingSphereState', './DataSources/BoxGeometryUpdater', './DataSources/BoxGraphics', './DataSources/CallbackProperty', './DataSources/CheckerboardMaterialProperty', './DataSources/ColorMaterialProperty', './DataSources/CompositeEntityCollection', './DataSources/CompositeMaterialProperty', './DataSources/CompositePositionProperty', './DataSources/CompositeProperty', './DataSources/ConstantPositionProperty', './DataSources/ConstantProperty', './DataSources/CorridorGeometryUpdater', './DataSources/CorridorGraphics', './DataSources/createMaterialPropertyDescriptor', './DataSources/createPropertyDescriptor', './DataSources/createRawPropertyDescriptor', './DataSources/CustomDataSource', './DataSources/CylinderGeometryUpdater', './DataSources/CylinderGraphics', './DataSources/CzmlDataSource', './DataSources/DataSource', './DataSources/DataSourceClock', './DataSources/DataSourceCollection', './DataSources/DataSourceDisplay', './DataSources/dynamicGeometryGetBoundingSphere', './DataSources/DynamicGeometryUpdater', './DataSources/EllipseGeometryUpdater', './DataSources/EllipseGraphics', './DataSources/EllipsoidGeometryUpdater', './DataSources/EllipsoidGraphics', './DataSources/Entity', './DataSources/EntityCluster', './DataSources/EntityCollection', './DataSources/EntityView', './DataSources/GeoJsonDataSource', './DataSources/GeometryUpdater', './DataSources/GeometryVisualizer', './DataSources/GridMaterialProperty', './DataSources/ImageMaterialProperty', './DataSources/KmlDataSource', './DataSources/LabelGraphics', './DataSources/LabelVisualizer', './DataSources/MaterialProperty', './DataSources/ModelGraphics', './DataSources/ModelVisualizer', './DataSources/NodeTransformationProperty', './DataSources/PathGraphics', './DataSources/PathVisualizer', './DataSources/PointGraphics', './DataSources/PointVisualizer', './DataSources/PolygonGeometryUpdater', './DataSources/PolygonGraphics', './DataSources/PolylineArrowMaterialProperty', './DataSources/PolylineGeometryUpdater', './DataSources/PolylineGlowMaterialProperty', './DataSources/PolylineGraphics', './DataSources/PolylineOutlineMaterialProperty', './DataSources/PolylineVolumeGeometryUpdater', './DataSources/PolylineVolumeGraphics', './DataSources/PositionProperty', './DataSources/PositionPropertyArray', './DataSources/Property', './DataSources/PropertyArray', './DataSources/PropertyBag', './DataSources/RectangleGeometryUpdater', './DataSources/RectangleGraphics', './DataSources/ReferenceProperty', './DataSources/Rotation', './DataSources/SampledPositionProperty', './DataSources/SampledProperty', './DataSources/ScaledPositionProperty', './DataSources/StaticGeometryColorBatch', './DataSources/StaticGeometryPerMaterialBatch', './DataSources/StaticGroundGeometryColorBatch', './DataSources/StaticOutlineGeometryBatch', './DataSources/StripeMaterialProperty', './DataSources/StripeOrientation', './DataSources/TimeIntervalCollectionPositionProperty', './DataSources/TimeIntervalCollectionProperty', './DataSources/VelocityOrientationProperty', './DataSources/VelocityVectorProperty', './DataSources/Visualizer', './DataSources/WallGeometryUpdater', './DataSources/WallGraphics', './Renderer/AutomaticUniforms', './Renderer/Buffer', './Renderer/BufferUsage', './Renderer/ClearCommand', './Renderer/ComputeCommand', './Renderer/ComputeEngine', './Renderer/Context', './Renderer/ContextLimits', './Renderer/createUniform', './Renderer/createUniformArray', './Renderer/CubeMap', './Renderer/CubeMapFace', './Renderer/DrawCommand', './Renderer/Framebuffer', './Renderer/loadCubeMap', './Renderer/MipmapHint', './Renderer/Pass', './Renderer/PassState', './Renderer/PickFramebuffer', './Renderer/PixelDatatype', './Renderer/Renderbuffer', './Renderer/RenderbufferFormat', './Renderer/RenderState', './Renderer/Sampler', './Renderer/ShaderCache', './Renderer/ShaderProgram', './Renderer/ShaderSource', './Renderer/Texture', './Renderer/TextureMagnificationFilter', './Renderer/TextureMinificationFilter', './Renderer/TextureWrap', './Renderer/UniformState', './Renderer/VertexArray', './Renderer/VertexArrayFacade', './Scene/Appearance', './Scene/ArcGisMapServerImageryProvider', './Scene/Axis', './Scene/BatchTable', './Scene/Billboard', './Scene/BillboardCollection', './Scene/BingMapsImageryProvider', './Scene/BingMapsStyle', './Scene/BlendEquation', './Scene/BlendFunction', './Scene/BlendingState', './Scene/BlendOption', './Scene/Camera', './Scene/CameraEventAggregator', './Scene/CameraEventType', './Scene/CameraFlightPath', './Scene/ColorBlendMode', './Scene/createOpenStreetMapImageryProvider', './Scene/createTangentSpaceDebugPrimitive', './Scene/createTileMapServiceImageryProvider', './Scene/CreditDisplay', './Scene/CullFace', './Scene/CullingVolume', './Scene/DebugAppearance', './Scene/DebugCameraPrimitive', './Scene/DebugModelMatrixPrimitive', './Scene/DepthFunction', './Scene/DepthPlane', './Scene/DeviceOrientationCameraController', './Scene/DiscardMissingTileImagePolicy', './Scene/EllipsoidPrimitive', './Scene/EllipsoidSurfaceAppearance', './Scene/Fog', './Scene/FrameRateMonitor', './Scene/FrameState', './Scene/FrustumCommands', './Scene/FXAA', './Scene/getAttributeOrUniformBySemantic', './Scene/getBinaryAccessor', './Scene/GetFeatureInfoFormat', './Scene/Globe', './Scene/GlobeDepth', './Scene/GlobeSurfaceShaderSet', './Scene/GlobeSurfaceTile', './Scene/GlobeSurfaceTileProvider', './Scene/GoogleEarthImageryProvider', './Scene/GridImageryProvider', './Scene/GroundPrimitive', './Scene/HeightReference', './Scene/HorizontalOrigin', './Scene/ImageDrapingPrimitive', './Scene/Imagery', './Scene/ImageryLayer', './Scene/ImageryLayerCollection', './Scene/ImageryLayerFeatureInfo', './Scene/ImageryProvider', './Scene/ImagerySplitDirection', './Scene/ImageryState', './Scene/Label', './Scene/LabelCollection', './Scene/LabelStyle', './Scene/MapboxImageryProvider', './Scene/MapMode2D', './Scene/Material', './Scene/MaterialAppearance', './Scene/Model', './Scene/ModelAnimation', './Scene/ModelAnimationCache', './Scene/ModelAnimationCollection', './Scene/ModelAnimationLoop', './Scene/ModelAnimationState', './Scene/ModelMaterial', './Scene/modelMaterialsCommon', './Scene/ModelMesh', './Scene/ModelNode', './Scene/Moon', './Scene/NeverTileDiscardPolicy', './Scene/OIT', './Scene/OrthographicFrustum', './Scene/PerformanceDisplay', './Scene/PerInstanceColorAppearance', './Scene/PerspectiveFrustum', './Scene/PerspectiveOffCenterFrustum', './Scene/PickDepth', './Scene/PointAppearance', './Scene/PointPrimitive', './Scene/PointPrimitiveCollection', './Scene/Polyline', './Scene/PolylineCollection', './Scene/PolylineColorAppearance', './Scene/PolylineMaterialAppearance', './Scene/Primitive', './Scene/PrimitiveCollection', './Scene/PrimitivePipeline', './Scene/PrimitiveState', './Scene/QuadtreeOccluders', './Scene/QuadtreePrimitive', './Scene/QuadtreeTile', './Scene/QuadtreeTileLoadState', './Scene/QuadtreeTileProvider', './Scene/Scene', './Scene/SceneMode', './Scene/SceneTransforms', './Scene/SceneTransitioner', './Scene/ScreenSpaceCameraController', './Scene/ShadowMap', './Scene/ShadowMapShader', './Scene/ShadowMode', './Scene/SingleTileImageryProvider', './Scene/SkyAtmosphere', './Scene/SkyBox', './Scene/StencilFunction', './Scene/StencilOperation', './Scene/Sun', './Scene/SunPostProcess', './Scene/TerrainState', './Scene/TextureAtlas', './Scene/TileBoundingBox', './Scene/TileCoordinatesImageryProvider', './Scene/TileDiscardPolicy', './Scene/TileImagery', './Scene/TileReplacementQueue', './Scene/TileState', './Scene/TileTerrain', './Scene/TweenCollection', './Scene/UrlTemplateImageryProvider', './Scene/VerticalOrigin', './Scene/ViewportQuad', './Scene/WebMapServiceImageryProvider', './Scene/WebMapTileServiceImageryProvider', './Shaders/AdjustTranslucentFS', './Shaders/Appearances/AllMaterialAppearanceFS', './Shaders/Appearances/AllMaterialAppearanceVS', './Shaders/Appearances/BasicMaterialAppearanceFS', './Shaders/Appearances/BasicMaterialAppearanceVS', './Shaders/Appearances/EllipsoidSurfaceAppearanceFS', './Shaders/Appearances/EllipsoidSurfaceAppearanceVS', './Shaders/Appearances/PerInstanceColorAppearanceFS', './Shaders/Appearances/PerInstanceColorAppearanceVS', './Shaders/Appearances/PerInstanceFlatColorAppearanceFS', './Shaders/Appearances/PerInstanceFlatColorAppearanceVS', './Shaders/Appearances/PointAppearanceFS', './Shaders/Appearances/PointAppearanceVS', './Shaders/Appearances/PolylineColorAppearanceVS', './Shaders/Appearances/PolylineMaterialAppearanceVS', './Shaders/Appearances/TexturedMaterialAppearanceFS', './Shaders/Appearances/TexturedMaterialAppearanceVS', './Shaders/BillboardCollectionFS', './Shaders/BillboardCollectionVS', './Shaders/Builtin/Constants/degreesPerRadian', './Shaders/Builtin/Constants/depthRange', './Shaders/Builtin/Constants/epsilon1', './Shaders/Builtin/Constants/epsilon2', './Shaders/Builtin/Constants/epsilon3', './Shaders/Builtin/Constants/epsilon4', './Shaders/Builtin/Constants/epsilon5', './Shaders/Builtin/Constants/epsilon6', './Shaders/Builtin/Constants/epsilon7', './Shaders/Builtin/Constants/infinity', './Shaders/Builtin/Constants/oneOverPi', './Shaders/Builtin/Constants/oneOverTwoPi', './Shaders/Builtin/Constants/passCompute', './Shaders/Builtin/Constants/passEnvironment', './Shaders/Builtin/Constants/passGlobe', './Shaders/Builtin/Constants/passGround', './Shaders/Builtin/Constants/passOpaque', './Shaders/Builtin/Constants/passOverlay', './Shaders/Builtin/Constants/passTranslucent', './Shaders/Builtin/Constants/pi', './Shaders/Builtin/Constants/piOverFour', './Shaders/Builtin/Constants/piOverSix', './Shaders/Builtin/Constants/piOverThree', './Shaders/Builtin/Constants/piOverTwo', './Shaders/Builtin/Constants/radiansPerDegree', './Shaders/Builtin/Constants/sceneMode2D', './Shaders/Builtin/Constants/sceneMode3D', './Shaders/Builtin/Constants/sceneModeColumbusView', './Shaders/Builtin/Constants/sceneModeMorphing', './Shaders/Builtin/Constants/solarRadius', './Shaders/Builtin/Constants/threePiOver2', './Shaders/Builtin/Constants/twoPi', './Shaders/Builtin/Constants/webMercatorMaxLatitude', './Shaders/Builtin/CzmBuiltins', './Shaders/Builtin/Functions/alphaWeight', './Shaders/Builtin/Functions/antialias', './Shaders/Builtin/Functions/cascadeColor', './Shaders/Builtin/Functions/cascadeDistance', './Shaders/Builtin/Functions/cascadeMatrix', './Shaders/Builtin/Functions/cascadeWeights', './Shaders/Builtin/Functions/columbusViewMorph', './Shaders/Builtin/Functions/computePosition', './Shaders/Builtin/Functions/cosineAndSine', './Shaders/Builtin/Functions/decompressTextureCoordinates', './Shaders/Builtin/Functions/eastNorthUpToEyeCoordinates', './Shaders/Builtin/Functions/ellipsoidContainsPoint', './Shaders/Builtin/Functions/ellipsoidNew', './Shaders/Builtin/Functions/ellipsoidWgs84TextureCoordinates', './Shaders/Builtin/Functions/equalsEpsilon', './Shaders/Builtin/Functions/eyeOffset', './Shaders/Builtin/Functions/eyeToWindowCoordinates', './Shaders/Builtin/Functions/fog', './Shaders/Builtin/Functions/geodeticSurfaceNormal', './Shaders/Builtin/Functions/getDefaultMaterial', './Shaders/Builtin/Functions/getLambertDiffuse', './Shaders/Builtin/Functions/getSpecular', './Shaders/Builtin/Functions/getWaterNoise', './Shaders/Builtin/Functions/getWgs84EllipsoidEC', './Shaders/Builtin/Functions/hue', './Shaders/Builtin/Functions/isEmpty', './Shaders/Builtin/Functions/isFull', './Shaders/Builtin/Functions/latitudeToWebMercatorFraction', './Shaders/Builtin/Functions/luminance', './Shaders/Builtin/Functions/metersPerPixel', './Shaders/Builtin/Functions/modelToWindowCoordinates', './Shaders/Builtin/Functions/multiplyWithColorBalance', './Shaders/Builtin/Functions/nearFarScalar', './Shaders/Builtin/Functions/octDecode', './Shaders/Builtin/Functions/packDepth', './Shaders/Builtin/Functions/phong', './Shaders/Builtin/Functions/pointAlongRay', './Shaders/Builtin/Functions/rayEllipsoidIntersectionInterval', './Shaders/Builtin/Functions/RGBToXYZ', './Shaders/Builtin/Functions/saturation', './Shaders/Builtin/Functions/shadowDepthCompare', './Shaders/Builtin/Functions/shadowVisibility', './Shaders/Builtin/Functions/signNotZero', './Shaders/Builtin/Functions/tangentToEyeSpaceMatrix', './Shaders/Builtin/Functions/translateRelativeToEye', './Shaders/Builtin/Functions/translucentPhong', './Shaders/Builtin/Functions/transpose', './Shaders/Builtin/Functions/unpackDepth', './Shaders/Builtin/Functions/windowToEyeCoordinates', './Shaders/Builtin/Functions/XYZToRGB', './Shaders/Builtin/Structs/depthRangeStruct', './Shaders/Builtin/Structs/ellipsoid', './Shaders/Builtin/Structs/material', './Shaders/Builtin/Structs/materialInput', './Shaders/Builtin/Structs/ray', './Shaders/Builtin/Structs/raySegment', './Shaders/Builtin/Structs/shadowParameters', './Shaders/CompositeOITFS', './Shaders/DepthPlaneFS', './Shaders/DepthPlaneVS', './Shaders/EllipsoidFS', './Shaders/EllipsoidVS', './Shaders/GlobeFS', './Shaders/GlobeVS', './Shaders/GroundAtmosphere', './Shaders/ImageDrapingFS', './Shaders/ImageDrapingVS', './Shaders/Materials/BumpMapMaterial', './Shaders/Materials/CheckerboardMaterial', './Shaders/Materials/DotMaterial', './Shaders/Materials/FadeMaterial', './Shaders/Materials/GridMaterial', './Shaders/Materials/NormalMapMaterial', './Shaders/Materials/PolylineArrowMaterial', './Shaders/Materials/PolylineGlowMaterial', './Shaders/Materials/PolylineOutlineMaterial', './Shaders/Materials/RimLightingMaterial', './Shaders/Materials/StripeMaterial', './Shaders/Materials/Water', './Shaders/PointPrimitiveCollectionFS', './Shaders/PointPrimitiveCollectionVS', './Shaders/PolylineCommon', './Shaders/PolylineFS', './Shaders/PolylineVS', './Shaders/PostProcessFilters/AdditiveBlend', './Shaders/PostProcessFilters/BrightPass', './Shaders/PostProcessFilters/FXAA', './Shaders/PostProcessFilters/GaussianBlur1D', './Shaders/PostProcessFilters/PassThrough', './Shaders/ReprojectWebMercatorFS', './Shaders/ReprojectWebMercatorVS', './Shaders/ShadowVolumeFS', './Shaders/ShadowVolumeVS', './Shaders/SkyAtmosphereFS', './Shaders/SkyAtmosphereVS', './Shaders/SkyBoxFS', './Shaders/SkyBoxVS', './Shaders/SunFS', './Shaders/SunTextureFS', './Shaders/SunVS', './Shaders/ViewportQuadFS', './Shaders/ViewportQuadVS', './ThirdParty/Autolinker', './ThirdParty/crunch', './ThirdParty/earcut-2.1.1', './ThirdParty/gltfDefaults', './ThirdParty/kdbush', './ThirdParty/knockout-3.4.0', './ThirdParty/knockout-es5', './ThirdParty/knockout', './ThirdParty/measureText', './ThirdParty/mersenne-twister', './ThirdParty/NoSleep', './ThirdParty/sprintf', './ThirdParty/topojson', './ThirdParty/Tween', './ThirdParty/Uri', './ThirdParty/when', './ThirdParty/zip', './Widgets/Animation/Animation', './Widgets/Animation/AnimationViewModel', './Widgets/BaseLayerPicker/BaseLayerPicker', './Widgets/BaseLayerPicker/BaseLayerPickerViewModel', './Widgets/BaseLayerPicker/createDefaultImageryProviderViewModels', './Widgets/BaseLayerPicker/createDefaultTerrainProviderViewModels', './Widgets/BaseLayerPicker/ProviderViewModel', './Widgets/CesiumInspector/CesiumInspector', './Widgets/CesiumInspector/CesiumInspectorViewModel', './Widgets/CesiumWidget/CesiumWidget', './Widgets/ClockViewModel', './Widgets/Command', './Widgets/createCommand', './Widgets/FullscreenButton/FullscreenButton', './Widgets/FullscreenButton/FullscreenButtonViewModel', './Widgets/Geocoder/Geocoder', './Widgets/Geocoder/GeocoderViewModel', './Widgets/getElement', './Widgets/HomeButton/HomeButton', './Widgets/HomeButton/HomeButtonViewModel', './Widgets/InfoBox/InfoBox', './Widgets/InfoBox/InfoBoxViewModel', './Widgets/NavigationHelpButton/NavigationHelpButton', './Widgets/NavigationHelpButton/NavigationHelpButtonViewModel', './Widgets/PerformanceWatchdog/PerformanceWatchdog', './Widgets/PerformanceWatchdog/PerformanceWatchdogViewModel', './Widgets/SceneModePicker/SceneModePicker', './Widgets/SceneModePicker/SceneModePickerViewModel', './Widgets/SelectionIndicator/SelectionIndicator', './Widgets/SelectionIndicator/SelectionIndicatorViewModel', './Widgets/subscribeAndEvaluate', './Widgets/SvgPathBindingHandler', './Widgets/Timeline/Timeline', './Widgets/Timeline/TimelineHighlightRange', './Widgets/Timeline/TimelineTrack', './Widgets/ToggleButtonViewModel', './Widgets/Viewer/Viewer', './Widgets/Viewer/viewerCesiumInspectorMixin', './Widgets/Viewer/viewerDragDropMixin', './Widgets/Viewer/viewerPerformanceWatchdogMixin', './Widgets/VRButton/VRButton', './Widgets/VRButton/VRButtonViewModel', './Workers/createTaskProcessorWorker'], function(Core_appendForwardSlash, Core_ArcGisImageServerTerrainProvider, Core_arrayRemoveDuplicates, Core_AssociativeArray, Core_AttributeCompression, Core_AxisAlignedBoundingBox, Core_barycentricCoordinates, Core_binarySearch, Core_BingMapsApi, Core_BingMapsGeocoderService, Core_BoundingRectangle, Core_BoundingSphere, Core_BoxGeometry, Core_BoxOutlineGeometry, Core_buildModuleUrl, Core_cancelAnimationFrame, Core_Cartesian2, Core_Cartesian3, Core_Cartesian4, Core_Cartographic, Core_CartographicGeocoderService, Core_CatmullRomSpline, Core_CesiumTerrainProvider, Core_Check, Core_CircleGeometry, Core_CircleOutlineGeometry, Core_Clock, Core_ClockRange, Core_ClockStep, Core_clone, Core_Color, Core_ColorGeometryInstanceAttribute, Core_combine, Core_ComponentDatatype, Core_CompressedTextureBuffer, Core_CornerType, Core_CorridorGeometry, Core_CorridorGeometryLibrary, Core_CorridorOutlineGeometry, Core_createGuid, Core_Credit, Core_CubicRealPolynomial, Core_CylinderGeometry, Core_CylinderGeometryLibrary, Core_CylinderOutlineGeometry, Core_DefaultProxy, Core_defaultValue, Core_defined, Core_defineProperties, Core_deprecationWarning, Core_destroyObject, Core_DeveloperError, Core_DistanceDisplayCondition, Core_DistanceDisplayConditionGeometryInstanceAttribute, Core_EarthOrientationParameters, Core_EarthOrientationParametersSample, Core_EasingFunction, Core_EllipseGeometry, Core_EllipseGeometryLibrary, Core_EllipseOutlineGeometry, Core_Ellipsoid, Core_EllipsoidalOccluder, Core_EllipsoidGeodesic, Core_EllipsoidGeometry, Core_EllipsoidOutlineGeometry, Core_EllipsoidTangentPlane, Core_EllipsoidTerrainProvider, Core_EncodedCartesian3, Core_Event, Core_EventHelper, Core_ExtrapolationType, Core_FeatureDetection, Core_formatError, Core_freezeObject, Core_Fullscreen, Core_GeocoderService, Core_GeographicProjection, Core_GeographicTilingScheme, Core_Geometry, Core_GeometryAttribute, Core_GeometryAttributes, Core_GeometryInstance, Core_GeometryInstanceAttribute, Core_GeometryPipeline, Core_GeometryType, Core_getAbsoluteUri, Core_getBaseUri, Core_getExtensionFromUri, Core_getFilenameFromUri, Core_getImagePixels, Core_getMagic, Core_getStringFromTypedArray, Core_getTimestamp, Core_GregorianDate, Core_HeadingPitchRange, Core_HeadingPitchRoll, Core_HeightmapTerrainData, Core_HeightmapTessellator, Core_HermitePolynomialApproximation, Core_HermiteSpline, Core_Iau2000Orientation, Core_Iau2006XysData, Core_Iau2006XysSample, Core_IauOrientationAxes, Core_IauOrientationParameters, Core_IndexDatatype, Core_InterpolationAlgorithm, Core_Intersect, Core_Intersections2D, Core_IntersectionTests, Core_Interval, Core_isArray, Core_isCrossOriginUrl, Core_isLeapYear, Core_Iso8601, Core_joinUrls, Core_JulianDate, Core_KeyboardEventModifier, Core_LagrangePolynomialApproximation, Core_LeapSecond, Core_LinearApproximation, Core_LinearSpline, Core_loadArrayBuffer, Core_loadBlob, Core_loadCRN, Core_loadImage, Core_loadImageFromTypedArray, Core_loadImageViaBlob, Core_loadJson, Core_loadJsonp, Core_loadKTX, Core_loadText, Core_loadWithXhr, Core_loadXML, Core_MapboxApi, Core_MapProjection, Core_Math, Core_Matrix2, Core_Matrix3, Core_Matrix4, Core_mergeSort, Core_NearFarScalar, Core_objectToQuery, Core_Occluder, Core_oneTimeWarning, Core_OrientedBoundingBox, Core_Packable, Core_PackableForInterpolation, Core_parseResponseHeaders, Core_PinBuilder, Core_PixelFormat, Core_Plane, Core_PointGeometry, Core_pointInsideTriangle, Core_PolygonGeometry, Core_PolygonGeometryLibrary, Core_PolygonHierarchy, Core_PolygonOutlineGeometry, Core_PolygonPipeline, Core_PolylineGeometry, Core_PolylinePipeline, Core_PolylineVolumeGeometry, Core_PolylineVolumeGeometryLibrary, Core_PolylineVolumeOutlineGeometry, Core_PrimitiveType, Core_QuadraticRealPolynomial, Core_QuantizedMeshTerrainData, Core_QuarticRealPolynomial, Core_Quaternion, Core_QuaternionSpline, Core_queryToObject, Core_Queue, Core_Ray, Core_Rectangle, Core_RectangleGeometry, Core_RectangleGeometryLibrary, Core_RectangleOutlineGeometry, Core_ReferenceFrame, Core_requestAnimationFrame, Core_RequestErrorEvent, Core_RuntimeError, Core_sampleTerrain, Core_sampleTerrainMostDetailed, Core_scaleToGeodeticSurface, Core_ScreenSpaceEventHandler, Core_ScreenSpaceEventType, Core_ShowGeometryInstanceAttribute, Core_Simon1994PlanetaryPositions, Core_SimplePolylineGeometry, Core_SphereGeometry, Core_SphereOutlineGeometry, Core_Spherical, Core_Spline, Core_subdivideArray, Core_TaskProcessor, Core_TerrainData, Core_TerrainEncoding, Core_TerrainMesh, Core_TerrainProvider, Core_TerrainQuantization, Core_throttleRequestByServer, Core_TileAvailability, Core_TileProviderError, Core_TilingScheme, Core_TimeConstants, Core_TimeInterval, Core_TimeIntervalCollection, Core_TimeStandard, Core_Tipsify, Core_Transforms, Core_TranslationRotationScale, Core_TridiagonalSystemSolver, Core_TrustedServers, Core_VertexFormat, Core_VideoSynchronizer, Core_Visibility, Core_VRTheWorldTerrainProvider, Core_WallGeometry, Core_WallGeometryLibrary, Core_WallOutlineGeometry, Core_WebGLConstants, Core_WebMercatorProjection, Core_WebMercatorTilingScheme, Core_WindingOrder, Core_wrapFunction, Core_writeTextToCanvas, DataSources_BillboardGraphics, DataSources_BillboardVisualizer, DataSources_BoundingSphereState, DataSources_BoxGeometryUpdater, DataSources_BoxGraphics, DataSources_CallbackProperty, DataSources_CheckerboardMaterialProperty, DataSources_ColorMaterialProperty, DataSources_CompositeEntityCollection, DataSources_CompositeMaterialProperty, DataSources_CompositePositionProperty, DataSources_CompositeProperty, DataSources_ConstantPositionProperty, DataSources_ConstantProperty, DataSources_CorridorGeometryUpdater, DataSources_CorridorGraphics, DataSources_createMaterialPropertyDescriptor, DataSources_createPropertyDescriptor, DataSources_createRawPropertyDescriptor, DataSources_CustomDataSource, DataSources_CylinderGeometryUpdater, DataSources_CylinderGraphics, DataSources_CzmlDataSource, DataSources_DataSource, DataSources_DataSourceClock, DataSources_DataSourceCollection, DataSources_DataSourceDisplay, DataSources_dynamicGeometryGetBoundingSphere, DataSources_DynamicGeometryUpdater, DataSources_EllipseGeometryUpdater, DataSources_EllipseGraphics, DataSources_EllipsoidGeometryUpdater, DataSources_EllipsoidGraphics, DataSources_Entity, DataSources_EntityCluster, DataSources_EntityCollection, DataSources_EntityView, DataSources_GeoJsonDataSource, DataSources_GeometryUpdater, DataSources_GeometryVisualizer, DataSources_GridMaterialProperty, DataSources_ImageMaterialProperty, DataSources_KmlDataSource, DataSources_LabelGraphics, DataSources_LabelVisualizer, DataSources_MaterialProperty, DataSources_ModelGraphics, DataSources_ModelVisualizer, DataSources_NodeTransformationProperty, DataSources_PathGraphics, DataSources_PathVisualizer, DataSources_PointGraphics, DataSources_PointVisualizer, DataSources_PolygonGeometryUpdater, DataSources_PolygonGraphics, DataSources_PolylineArrowMaterialProperty, DataSources_PolylineGeometryUpdater, DataSources_PolylineGlowMaterialProperty, DataSources_PolylineGraphics, DataSources_PolylineOutlineMaterialProperty, DataSources_PolylineVolumeGeometryUpdater, DataSources_PolylineVolumeGraphics, DataSources_PositionProperty, DataSources_PositionPropertyArray, DataSources_Property, DataSources_PropertyArray, DataSources_PropertyBag, DataSources_RectangleGeometryUpdater, DataSources_RectangleGraphics, DataSources_ReferenceProperty, DataSources_Rotation, DataSources_SampledPositionProperty, DataSources_SampledProperty, DataSources_ScaledPositionProperty, DataSources_StaticGeometryColorBatch, DataSources_StaticGeometryPerMaterialBatch, DataSources_StaticGroundGeometryColorBatch, DataSources_StaticOutlineGeometryBatch, DataSources_StripeMaterialProperty, DataSources_StripeOrientation, DataSources_TimeIntervalCollectionPositionProperty, DataSources_TimeIntervalCollectionProperty, DataSources_VelocityOrientationProperty, DataSources_VelocityVectorProperty, DataSources_Visualizer, DataSources_WallGeometryUpdater, DataSources_WallGraphics, Renderer_AutomaticUniforms, Renderer_Buffer, Renderer_BufferUsage, Renderer_ClearCommand, Renderer_ComputeCommand, Renderer_ComputeEngine, Renderer_Context, Renderer_ContextLimits, Renderer_createUniform, Renderer_createUniformArray, Renderer_CubeMap, Renderer_CubeMapFace, Renderer_DrawCommand, Renderer_Framebuffer, Renderer_loadCubeMap, Renderer_MipmapHint, Renderer_Pass, Renderer_PassState, Renderer_PickFramebuffer, Renderer_PixelDatatype, Renderer_Renderbuffer, Renderer_RenderbufferFormat, Renderer_RenderState, Renderer_Sampler, Renderer_ShaderCache, Renderer_ShaderProgram, Renderer_ShaderSource, Renderer_Texture, Renderer_TextureMagnificationFilter, Renderer_TextureMinificationFilter, Renderer_TextureWrap, Renderer_UniformState, Renderer_VertexArray, Renderer_VertexArrayFacade, Scene_Appearance, Scene_ArcGisMapServerImageryProvider, Scene_Axis, Scene_BatchTable, Scene_Billboard, Scene_BillboardCollection, Scene_BingMapsImageryProvider, Scene_BingMapsStyle, Scene_BlendEquation, Scene_BlendFunction, Scene_BlendingState, Scene_BlendOption, Scene_Camera, Scene_CameraEventAggregator, Scene_CameraEventType, Scene_CameraFlightPath, Scene_ColorBlendMode, Scene_createOpenStreetMapImageryProvider, Scene_createTangentSpaceDebugPrimitive, Scene_createTileMapServiceImageryProvider, Scene_CreditDisplay, Scene_CullFace, Scene_CullingVolume, Scene_DebugAppearance, Scene_DebugCameraPrimitive, Scene_DebugModelMatrixPrimitive, Scene_DepthFunction, Scene_DepthPlane, Scene_DeviceOrientationCameraController, Scene_DiscardMissingTileImagePolicy, Scene_EllipsoidPrimitive, Scene_EllipsoidSurfaceAppearance, Scene_Fog, Scene_FrameRateMonitor, Scene_FrameState, Scene_FrustumCommands, Scene_FXAA, Scene_getAttributeOrUniformBySemantic, Scene_getBinaryAccessor, Scene_GetFeatureInfoFormat, Scene_Globe, Scene_GlobeDepth, Scene_GlobeSurfaceShaderSet, Scene_GlobeSurfaceTile, Scene_GlobeSurfaceTileProvider, Scene_GoogleEarthImageryProvider, Scene_GridImageryProvider, Scene_GroundPrimitive, Scene_HeightReference, Scene_HorizontalOrigin, Scene_ImageDrapingPrimitive, Scene_Imagery, Scene_ImageryLayer, Scene_ImageryLayerCollection, Scene_ImageryLayerFeatureInfo, Scene_ImageryProvider, Scene_ImagerySplitDirection, Scene_ImageryState, Scene_Label, Scene_LabelCollection, Scene_LabelStyle, Scene_MapboxImageryProvider, Scene_MapMode2D, Scene_Material, Scene_MaterialAppearance, Scene_Model, Scene_ModelAnimation, Scene_ModelAnimationCache, Scene_ModelAnimationCollection, Scene_ModelAnimationLoop, Scene_ModelAnimationState, Scene_ModelMaterial, Scene_modelMaterialsCommon, Scene_ModelMesh, Scene_ModelNode, Scene_Moon, Scene_NeverTileDiscardPolicy, Scene_OIT, Scene_OrthographicFrustum, Scene_PerformanceDisplay, Scene_PerInstanceColorAppearance, Scene_PerspectiveFrustum, Scene_PerspectiveOffCenterFrustum, Scene_PickDepth, Scene_PointAppearance, Scene_PointPrimitive, Scene_PointPrimitiveCollection, Scene_Polyline, Scene_PolylineCollection, Scene_PolylineColorAppearance, Scene_PolylineMaterialAppearance, Scene_Primitive, Scene_PrimitiveCollection, Scene_PrimitivePipeline, Scene_PrimitiveState, Scene_QuadtreeOccluders, Scene_QuadtreePrimitive, Scene_QuadtreeTile, Scene_QuadtreeTileLoadState, Scene_QuadtreeTileProvider, Scene_Scene, Scene_SceneMode, Scene_SceneTransforms, Scene_SceneTransitioner, Scene_ScreenSpaceCameraController, Scene_ShadowMap, Scene_ShadowMapShader, Scene_ShadowMode, Scene_SingleTileImageryProvider, Scene_SkyAtmosphere, Scene_SkyBox, Scene_StencilFunction, Scene_StencilOperation, Scene_Sun, Scene_SunPostProcess, Scene_TerrainState, Scene_TextureAtlas, Scene_TileBoundingBox, Scene_TileCoordinatesImageryProvider, Scene_TileDiscardPolicy, Scene_TileImagery, Scene_TileReplacementQueue, Scene_TileState, Scene_TileTerrain, Scene_TweenCollection, Scene_UrlTemplateImageryProvider, Scene_VerticalOrigin, Scene_ViewportQuad, Scene_WebMapServiceImageryProvider, Scene_WebMapTileServiceImageryProvider, Shaders_AdjustTranslucentFS, Shaders_Appearances_AllMaterialAppearanceFS, Shaders_Appearances_AllMaterialAppearanceVS, Shaders_Appearances_BasicMaterialAppearanceFS, Shaders_Appearances_BasicMaterialAppearanceVS, Shaders_Appearances_EllipsoidSurfaceAppearanceFS, Shaders_Appearances_EllipsoidSurfaceAppearanceVS, Shaders_Appearances_PerInstanceColorAppearanceFS, Shaders_Appearances_PerInstanceColorAppearanceVS, Shaders_Appearances_PerInstanceFlatColorAppearanceFS, Shaders_Appearances_PerInstanceFlatColorAppearanceVS, Shaders_Appearances_PointAppearanceFS, Shaders_Appearances_PointAppearanceVS, Shaders_Appearances_PolylineColorAppearanceVS, Shaders_Appearances_PolylineMaterialAppearanceVS, Shaders_Appearances_TexturedMaterialAppearanceFS, Shaders_Appearances_TexturedMaterialAppearanceVS, Shaders_BillboardCollectionFS, Shaders_BillboardCollectionVS, Shaders_Builtin_Constants_degreesPerRadian, Shaders_Builtin_Constants_depthRange, Shaders_Builtin_Constants_epsilon1, Shaders_Builtin_Constants_epsilon2, Shaders_Builtin_Constants_epsilon3, Shaders_Builtin_Constants_epsilon4, Shaders_Builtin_Constants_epsilon5, Shaders_Builtin_Constants_epsilon6, Shaders_Builtin_Constants_epsilon7, Shaders_Builtin_Constants_infinity, Shaders_Builtin_Constants_oneOverPi, Shaders_Builtin_Constants_oneOverTwoPi, Shaders_Builtin_Constants_passCompute, Shaders_Builtin_Constants_passEnvironment, Shaders_Builtin_Constants_passGlobe, Shaders_Builtin_Constants_passGround, Shaders_Builtin_Constants_passOpaque, Shaders_Builtin_Constants_passOverlay, Shaders_Builtin_Constants_passTranslucent, Shaders_Builtin_Constants_pi, Shaders_Builtin_Constants_piOverFour, Shaders_Builtin_Constants_piOverSix, Shaders_Builtin_Constants_piOverThree, Shaders_Builtin_Constants_piOverTwo, Shaders_Builtin_Constants_radiansPerDegree, Shaders_Builtin_Constants_sceneMode2D, Shaders_Builtin_Constants_sceneMode3D, Shaders_Builtin_Constants_sceneModeColumbusView, Shaders_Builtin_Constants_sceneModeMorphing, Shaders_Builtin_Constants_solarRadius, Shaders_Builtin_Constants_threePiOver2, Shaders_Builtin_Constants_twoPi, Shaders_Builtin_Constants_webMercatorMaxLatitude, Shaders_Builtin_CzmBuiltins, Shaders_Builtin_Functions_alphaWeight, Shaders_Builtin_Functions_antialias, Shaders_Builtin_Functions_cascadeColor, Shaders_Builtin_Functions_cascadeDistance, Shaders_Builtin_Functions_cascadeMatrix, Shaders_Builtin_Functions_cascadeWeights, Shaders_Builtin_Functions_columbusViewMorph, Shaders_Builtin_Functions_computePosition, Shaders_Builtin_Functions_cosineAndSine, Shaders_Builtin_Functions_decompressTextureCoordinates, Shaders_Builtin_Functions_eastNorthUpToEyeCoordinates, Shaders_Builtin_Functions_ellipsoidContainsPoint, Shaders_Builtin_Functions_ellipsoidNew, Shaders_Builtin_Functions_ellipsoidWgs84TextureCoordinates, Shaders_Builtin_Functions_equalsEpsilon, Shaders_Builtin_Functions_eyeOffset, Shaders_Builtin_Functions_eyeToWindowCoordinates, Shaders_Builtin_Functions_fog, Shaders_Builtin_Functions_geodeticSurfaceNormal, Shaders_Builtin_Functions_getDefaultMaterial, Shaders_Builtin_Functions_getLambertDiffuse, Shaders_Builtin_Functions_getSpecular, Shaders_Builtin_Functions_getWaterNoise, Shaders_Builtin_Functions_getWgs84EllipsoidEC, Shaders_Builtin_Functions_hue, Shaders_Builtin_Functions_isEmpty, Shaders_Builtin_Functions_isFull, Shaders_Builtin_Functions_latitudeToWebMercatorFraction, Shaders_Builtin_Functions_luminance, Shaders_Builtin_Functions_metersPerPixel, Shaders_Builtin_Functions_modelToWindowCoordinates, Shaders_Builtin_Functions_multiplyWithColorBalance, Shaders_Builtin_Functions_nearFarScalar, Shaders_Builtin_Functions_octDecode, Shaders_Builtin_Functions_packDepth, Shaders_Builtin_Functions_phong, Shaders_Builtin_Functions_pointAlongRay, Shaders_Builtin_Functions_rayEllipsoidIntersectionInterval, Shaders_Builtin_Functions_RGBToXYZ, Shaders_Builtin_Functions_saturation, Shaders_Builtin_Functions_shadowDepthCompare, Shaders_Builtin_Functions_shadowVisibility, Shaders_Builtin_Functions_signNotZero, Shaders_Builtin_Functions_tangentToEyeSpaceMatrix, Shaders_Builtin_Functions_translateRelativeToEye, Shaders_Builtin_Functions_translucentPhong, Shaders_Builtin_Functions_transpose, Shaders_Builtin_Functions_unpackDepth, Shaders_Builtin_Functions_windowToEyeCoordinates, Shaders_Builtin_Functions_XYZToRGB, Shaders_Builtin_Structs_depthRangeStruct, Shaders_Builtin_Structs_ellipsoid, Shaders_Builtin_Structs_material, Shaders_Builtin_Structs_materialInput, Shaders_Builtin_Structs_ray, Shaders_Builtin_Structs_raySegment, Shaders_Builtin_Structs_shadowParameters, Shaders_CompositeOITFS, Shaders_DepthPlaneFS, Shaders_DepthPlaneVS, Shaders_EllipsoidFS, Shaders_EllipsoidVS, Shaders_GlobeFS, Shaders_GlobeVS, Shaders_GroundAtmosphere, Shaders_ImageDrapingFS, Shaders_ImageDrapingVS, Shaders_Materials_BumpMapMaterial, Shaders_Materials_CheckerboardMaterial, Shaders_Materials_DotMaterial, Shaders_Materials_FadeMaterial, Shaders_Materials_GridMaterial, Shaders_Materials_NormalMapMaterial, Shaders_Materials_PolylineArrowMaterial, Shaders_Materials_PolylineGlowMaterial, Shaders_Materials_PolylineOutlineMaterial, Shaders_Materials_RimLightingMaterial, Shaders_Materials_StripeMaterial, Shaders_Materials_Water, Shaders_PointPrimitiveCollectionFS, Shaders_PointPrimitiveCollectionVS, Shaders_PolylineCommon, Shaders_PolylineFS, Shaders_PolylineVS, Shaders_PostProcessFilters_AdditiveBlend, Shaders_PostProcessFilters_BrightPass, Shaders_PostProcessFilters_FXAA, Shaders_PostProcessFilters_GaussianBlur1D, Shaders_PostProcessFilters_PassThrough, Shaders_ReprojectWebMercatorFS, Shaders_ReprojectWebMercatorVS, Shaders_ShadowVolumeFS, Shaders_ShadowVolumeVS, Shaders_SkyAtmosphereFS, Shaders_SkyAtmosphereVS, Shaders_SkyBoxFS, Shaders_SkyBoxVS, Shaders_SunFS, Shaders_SunTextureFS, Shaders_SunVS, Shaders_ViewportQuadFS, Shaders_ViewportQuadVS, ThirdParty_Autolinker, ThirdParty_crunch, ThirdParty_earcut_2_1_1, ThirdParty_gltfDefaults, ThirdParty_kdbush, ThirdParty_knockout_3_4_0, ThirdParty_knockout_es5, ThirdParty_knockout, ThirdParty_measureText, ThirdParty_mersenne_twister, ThirdParty_NoSleep, ThirdParty_sprintf, ThirdParty_topojson, ThirdParty_Tween, ThirdParty_Uri, ThirdParty_when, ThirdParty_zip, Widgets_Animation_Animation, Widgets_Animation_AnimationViewModel, Widgets_BaseLayerPicker_BaseLayerPicker, Widgets_BaseLayerPicker_BaseLayerPickerViewModel, Widgets_BaseLayerPicker_createDefaultImageryProviderViewModels, Widgets_BaseLayerPicker_createDefaultTerrainProviderViewModels, Widgets_BaseLayerPicker_ProviderViewModel, Widgets_CesiumInspector_CesiumInspector, Widgets_CesiumInspector_CesiumInspectorViewModel, Widgets_CesiumWidget_CesiumWidget, Widgets_ClockViewModel, Widgets_Command, Widgets_createCommand, Widgets_FullscreenButton_FullscreenButton, Widgets_FullscreenButton_FullscreenButtonViewModel, Widgets_Geocoder_Geocoder, Widgets_Geocoder_GeocoderViewModel, Widgets_getElement, Widgets_HomeButton_HomeButton, Widgets_HomeButton_HomeButtonViewModel, Widgets_InfoBox_InfoBox, Widgets_InfoBox_InfoBoxViewModel, Widgets_NavigationHelpButton_NavigationHelpButton, Widgets_NavigationHelpButton_NavigationHelpButtonViewModel, Widgets_PerformanceWatchdog_PerformanceWatchdog, Widgets_PerformanceWatchdog_PerformanceWatchdogViewModel, Widgets_SceneModePicker_SceneModePicker, Widgets_SceneModePicker_SceneModePickerViewModel, Widgets_SelectionIndicator_SelectionIndicator, Widgets_SelectionIndicator_SelectionIndicatorViewModel, Widgets_subscribeAndEvaluate, Widgets_SvgPathBindingHandler, Widgets_Timeline_Timeline, Widgets_Timeline_TimelineHighlightRange, Widgets_Timeline_TimelineTrack, Widgets_ToggleButtonViewModel, Widgets_Viewer_Viewer, Widgets_Viewer_viewerCesiumInspectorMixin, Widgets_Viewer_viewerDragDropMixin, Widgets_Viewer_viewerPerformanceWatchdogMixin, Widgets_VRButton_VRButton, Widgets_VRButton_VRButtonViewModel, Workers_createTaskProcessorWorker) {
  'use strict';
  /*jshint sub:true*/
  var Cesium = {
    VERSION : "1.31",
    _shaders : {}
  };
  Cesium['appendForwardSlash'] = Core_appendForwardSlash;
  Cesium['ArcGisImageServerTerrainProvider'] = Core_ArcGisImageServerTerrainProvider;
  Cesium['arrayRemoveDuplicates'] = Core_arrayRemoveDuplicates;
  Cesium['AssociativeArray'] = Core_AssociativeArray;
  Cesium['AttributeCompression'] = Core_AttributeCompression;
  Cesium['AxisAlignedBoundingBox'] = Core_AxisAlignedBoundingBox;
  Cesium['barycentricCoordinates'] = Core_barycentricCoordinates;
  Cesium['binarySearch'] = Core_binarySearch;
  Cesium['BingMapsApi'] = Core_BingMapsApi;
  Cesium['BingMapsGeocoderService'] = Core_BingMapsGeocoderService;
  Cesium['BoundingRectangle'] = Core_BoundingRectangle;
  Cesium['BoundingSphere'] = Core_BoundingSphere;
  Cesium['BoxGeometry'] = Core_BoxGeometry;
  Cesium['BoxOutlineGeometry'] = Core_BoxOutlineGeometry;
  Cesium['buildModuleUrl'] = Core_buildModuleUrl;
  Cesium['cancelAnimationFrame'] = Core_cancelAnimationFrame;
  Cesium['Cartesian2'] = Core_Cartesian2;
  Cesium['Cartesian3'] = Core_Cartesian3;
  Cesium['Cartesian4'] = Core_Cartesian4;
  Cesium['Cartographic'] = Core_Cartographic;
  Cesium['CartographicGeocoderService'] = Core_CartographicGeocoderService;
  Cesium['CatmullRomSpline'] = Core_CatmullRomSpline;
  Cesium['CesiumTerrainProvider'] = Core_CesiumTerrainProvider;
  Cesium['Check'] = Core_Check;
  Cesium['CircleGeometry'] = Core_CircleGeometry;
  Cesium['CircleOutlineGeometry'] = Core_CircleOutlineGeometry;
  Cesium['Clock'] = Core_Clock;
  Cesium['ClockRange'] = Core_ClockRange;
  Cesium['ClockStep'] = Core_ClockStep;
  Cesium['clone'] = Core_clone;
  Cesium['Color'] = Core_Color;
  Cesium['ColorGeometryInstanceAttribute'] = Core_ColorGeometryInstanceAttribute;
  Cesium['combine'] = Core_combine;
  Cesium['ComponentDatatype'] = Core_ComponentDatatype;
  Cesium['CompressedTextureBuffer'] = Core_CompressedTextureBuffer;
  Cesium['CornerType'] = Core_CornerType;
  Cesium['CorridorGeometry'] = Core_CorridorGeometry;
  Cesium['CorridorGeometryLibrary'] = Core_CorridorGeometryLibrary;
  Cesium['CorridorOutlineGeometry'] = Core_CorridorOutlineGeometry;
  Cesium['createGuid'] = Core_createGuid;
  Cesium['Credit'] = Core_Credit;
  Cesium['CubicRealPolynomial'] = Core_CubicRealPolynomial;
  Cesium['CylinderGeometry'] = Core_CylinderGeometry;
  Cesium['CylinderGeometryLibrary'] = Core_CylinderGeometryLibrary;
  Cesium['CylinderOutlineGeometry'] = Core_CylinderOutlineGeometry;
  Cesium['DefaultProxy'] = Core_DefaultProxy;
  Cesium['defaultValue'] = Core_defaultValue;
  Cesium['defined'] = Core_defined;
  Cesium['defineProperties'] = Core_defineProperties;
  Cesium['deprecationWarning'] = Core_deprecationWarning;
  Cesium['destroyObject'] = Core_destroyObject;
  Cesium['DeveloperError'] = Core_DeveloperError;
  Cesium['DistanceDisplayCondition'] = Core_DistanceDisplayCondition;
  Cesium['DistanceDisplayConditionGeometryInstanceAttribute'] = Core_DistanceDisplayConditionGeometryInstanceAttribute;
  Cesium['EarthOrientationParameters'] = Core_EarthOrientationParameters;
  Cesium['EarthOrientationParametersSample'] = Core_EarthOrientationParametersSample;
  Cesium['EasingFunction'] = Core_EasingFunction;
  Cesium['EllipseGeometry'] = Core_EllipseGeometry;
  Cesium['EllipseGeometryLibrary'] = Core_EllipseGeometryLibrary;
  Cesium['EllipseOutlineGeometry'] = Core_EllipseOutlineGeometry;
  Cesium['Ellipsoid'] = Core_Ellipsoid;
  Cesium['EllipsoidalOccluder'] = Core_EllipsoidalOccluder;
  Cesium['EllipsoidGeodesic'] = Core_EllipsoidGeodesic;
  Cesium['EllipsoidGeometry'] = Core_EllipsoidGeometry;
  Cesium['EllipsoidOutlineGeometry'] = Core_EllipsoidOutlineGeometry;
  Cesium['EllipsoidTangentPlane'] = Core_EllipsoidTangentPlane;
  Cesium['EllipsoidTerrainProvider'] = Core_EllipsoidTerrainProvider;
  Cesium['EncodedCartesian3'] = Core_EncodedCartesian3;
  Cesium['Event'] = Core_Event;
  Cesium['EventHelper'] = Core_EventHelper;
  Cesium['ExtrapolationType'] = Core_ExtrapolationType;
  Cesium['FeatureDetection'] = Core_FeatureDetection;
  Cesium['formatError'] = Core_formatError;
  Cesium['freezeObject'] = Core_freezeObject;
  Cesium['Fullscreen'] = Core_Fullscreen;
  Cesium['GeocoderService'] = Core_GeocoderService;
  Cesium['GeographicProjection'] = Core_GeographicProjection;
  Cesium['GeographicTilingScheme'] = Core_GeographicTilingScheme;
  Cesium['Geometry'] = Core_Geometry;
  Cesium['GeometryAttribute'] = Core_GeometryAttribute;
  Cesium['GeometryAttributes'] = Core_GeometryAttributes;
  Cesium['GeometryInstance'] = Core_GeometryInstance;
  Cesium['GeometryInstanceAttribute'] = Core_GeometryInstanceAttribute;
  Cesium['GeometryPipeline'] = Core_GeometryPipeline;
  Cesium['GeometryType'] = Core_GeometryType;
  Cesium['getAbsoluteUri'] = Core_getAbsoluteUri;
  Cesium['getBaseUri'] = Core_getBaseUri;
  Cesium['getExtensionFromUri'] = Core_getExtensionFromUri;
  Cesium['getFilenameFromUri'] = Core_getFilenameFromUri;
  Cesium['getImagePixels'] = Core_getImagePixels;
  Cesium['getMagic'] = Core_getMagic;
  Cesium['getStringFromTypedArray'] = Core_getStringFromTypedArray;
  Cesium['getTimestamp'] = Core_getTimestamp;
  Cesium['GregorianDate'] = Core_GregorianDate;
  Cesium['HeadingPitchRange'] = Core_HeadingPitchRange;
  Cesium['HeadingPitchRoll'] = Core_HeadingPitchRoll;
  Cesium['HeightmapTerrainData'] = Core_HeightmapTerrainData;
  Cesium['HeightmapTessellator'] = Core_HeightmapTessellator;
  Cesium['HermitePolynomialApproximation'] = Core_HermitePolynomialApproximation;
  Cesium['HermiteSpline'] = Core_HermiteSpline;
  Cesium['Iau2000Orientation'] = Core_Iau2000Orientation;
  Cesium['Iau2006XysData'] = Core_Iau2006XysData;
  Cesium['Iau2006XysSample'] = Core_Iau2006XysSample;
  Cesium['IauOrientationAxes'] = Core_IauOrientationAxes;
  Cesium['IauOrientationParameters'] = Core_IauOrientationParameters;
  Cesium['IndexDatatype'] = Core_IndexDatatype;
  Cesium['InterpolationAlgorithm'] = Core_InterpolationAlgorithm;
  Cesium['Intersect'] = Core_Intersect;
  Cesium['Intersections2D'] = Core_Intersections2D;
  Cesium['IntersectionTests'] = Core_IntersectionTests;
  Cesium['Interval'] = Core_Interval;
  Cesium['isArray'] = Core_isArray;
  Cesium['isCrossOriginUrl'] = Core_isCrossOriginUrl;
  Cesium['isLeapYear'] = Core_isLeapYear;
  Cesium['Iso8601'] = Core_Iso8601;
  Cesium['joinUrls'] = Core_joinUrls;
  Cesium['JulianDate'] = Core_JulianDate;
  Cesium['KeyboardEventModifier'] = Core_KeyboardEventModifier;
  Cesium['LagrangePolynomialApproximation'] = Core_LagrangePolynomialApproximation;
  Cesium['LeapSecond'] = Core_LeapSecond;
  Cesium['LinearApproximation'] = Core_LinearApproximation;
  Cesium['LinearSpline'] = Core_LinearSpline;
  Cesium['loadArrayBuffer'] = Core_loadArrayBuffer;
  Cesium['loadBlob'] = Core_loadBlob;
  Cesium['loadCRN'] = Core_loadCRN;
  Cesium['loadImage'] = Core_loadImage;
  Cesium['loadImageFromTypedArray'] = Core_loadImageFromTypedArray;
  Cesium['loadImageViaBlob'] = Core_loadImageViaBlob;
  Cesium['loadJson'] = Core_loadJson;
  Cesium['loadJsonp'] = Core_loadJsonp;
  Cesium['loadKTX'] = Core_loadKTX;
  Cesium['loadText'] = Core_loadText;
  Cesium['loadWithXhr'] = Core_loadWithXhr;
  Cesium['loadXML'] = Core_loadXML;
  Cesium['MapboxApi'] = Core_MapboxApi;
  Cesium['MapProjection'] = Core_MapProjection;
  Cesium['Math'] = Core_Math;
  Cesium['Matrix2'] = Core_Matrix2;
  Cesium['Matrix3'] = Core_Matrix3;
  Cesium['Matrix4'] = Core_Matrix4;
  Cesium['mergeSort'] = Core_mergeSort;
  Cesium['NearFarScalar'] = Core_NearFarScalar;
  Cesium['objectToQuery'] = Core_objectToQuery;
  Cesium['Occluder'] = Core_Occluder;
  Cesium['oneTimeWarning'] = Core_oneTimeWarning;
  Cesium['OrientedBoundingBox'] = Core_OrientedBoundingBox;
  Cesium['Packable'] = Core_Packable;
  Cesium['PackableForInterpolation'] = Core_PackableForInterpolation;
  Cesium['parseResponseHeaders'] = Core_parseResponseHeaders;
  Cesium['PinBuilder'] = Core_PinBuilder;
  Cesium['PixelFormat'] = Core_PixelFormat;
  Cesium['Plane'] = Core_Plane;
  Cesium['PointGeometry'] = Core_PointGeometry;
  Cesium['pointInsideTriangle'] = Core_pointInsideTriangle;
  Cesium['PolygonGeometry'] = Core_PolygonGeometry;
  Cesium['PolygonGeometryLibrary'] = Core_PolygonGeometryLibrary;
  Cesium['PolygonHierarchy'] = Core_PolygonHierarchy;
  Cesium['PolygonOutlineGeometry'] = Core_PolygonOutlineGeometry;
  Cesium['PolygonPipeline'] = Core_PolygonPipeline;
  Cesium['PolylineGeometry'] = Core_PolylineGeometry;
  Cesium['PolylinePipeline'] = Core_PolylinePipeline;
  Cesium['PolylineVolumeGeometry'] = Core_PolylineVolumeGeometry;
  Cesium['PolylineVolumeGeometryLibrary'] = Core_PolylineVolumeGeometryLibrary;
  Cesium['PolylineVolumeOutlineGeometry'] = Core_PolylineVolumeOutlineGeometry;
  Cesium['PrimitiveType'] = Core_PrimitiveType;
  Cesium['QuadraticRealPolynomial'] = Core_QuadraticRealPolynomial;
  Cesium['QuantizedMeshTerrainData'] = Core_QuantizedMeshTerrainData;
  Cesium['QuarticRealPolynomial'] = Core_QuarticRealPolynomial;
  Cesium['Quaternion'] = Core_Quaternion;
  Cesium['QuaternionSpline'] = Core_QuaternionSpline;
  Cesium['queryToObject'] = Core_queryToObject;
  Cesium['Queue'] = Core_Queue;
  Cesium['Ray'] = Core_Ray;
  Cesium['Rectangle'] = Core_Rectangle;
  Cesium['RectangleGeometry'] = Core_RectangleGeometry;
  Cesium['RectangleGeometryLibrary'] = Core_RectangleGeometryLibrary;
  Cesium['RectangleOutlineGeometry'] = Core_RectangleOutlineGeometry;
  Cesium['ReferenceFrame'] = Core_ReferenceFrame;
  Cesium['requestAnimationFrame'] = Core_requestAnimationFrame;
  Cesium['RequestErrorEvent'] = Core_RequestErrorEvent;
  Cesium['RuntimeError'] = Core_RuntimeError;
  Cesium['sampleTerrain'] = Core_sampleTerrain;
  Cesium['sampleTerrainMostDetailed'] = Core_sampleTerrainMostDetailed;
  Cesium['scaleToGeodeticSurface'] = Core_scaleToGeodeticSurface;
  Cesium['ScreenSpaceEventHandler'] = Core_ScreenSpaceEventHandler;
  Cesium['ScreenSpaceEventType'] = Core_ScreenSpaceEventType;
  Cesium['ShowGeometryInstanceAttribute'] = Core_ShowGeometryInstanceAttribute;
  Cesium['Simon1994PlanetaryPositions'] = Core_Simon1994PlanetaryPositions;
  Cesium['SimplePolylineGeometry'] = Core_SimplePolylineGeometry;
  Cesium['SphereGeometry'] = Core_SphereGeometry;
  Cesium['SphereOutlineGeometry'] = Core_SphereOutlineGeometry;
  Cesium['Spherical'] = Core_Spherical;
  Cesium['Spline'] = Core_Spline;
  Cesium['subdivideArray'] = Core_subdivideArray;
  Cesium['TaskProcessor'] = Core_TaskProcessor;
  Cesium['TerrainData'] = Core_TerrainData;
  Cesium['TerrainEncoding'] = Core_TerrainEncoding;
  Cesium['TerrainMesh'] = Core_TerrainMesh;
  Cesium['TerrainProvider'] = Core_TerrainProvider;
  Cesium['TerrainQuantization'] = Core_TerrainQuantization;
  Cesium['throttleRequestByServer'] = Core_throttleRequestByServer;
  Cesium['TileAvailability'] = Core_TileAvailability;
  Cesium['TileProviderError'] = Core_TileProviderError;
  Cesium['TilingScheme'] = Core_TilingScheme;
  Cesium['TimeConstants'] = Core_TimeConstants;
  Cesium['TimeInterval'] = Core_TimeInterval;
  Cesium['TimeIntervalCollection'] = Core_TimeIntervalCollection;
  Cesium['TimeStandard'] = Core_TimeStandard;
  Cesium['Tipsify'] = Core_Tipsify;
  Cesium['Transforms'] = Core_Transforms;
  Cesium['TranslationRotationScale'] = Core_TranslationRotationScale;
  Cesium['TridiagonalSystemSolver'] = Core_TridiagonalSystemSolver;
  Cesium['TrustedServers'] = Core_TrustedServers;
  Cesium['VertexFormat'] = Core_VertexFormat;
  Cesium['VideoSynchronizer'] = Core_VideoSynchronizer;
  Cesium['Visibility'] = Core_Visibility;
  Cesium['VRTheWorldTerrainProvider'] = Core_VRTheWorldTerrainProvider;
  Cesium['WallGeometry'] = Core_WallGeometry;
  Cesium['WallGeometryLibrary'] = Core_WallGeometryLibrary;
  Cesium['WallOutlineGeometry'] = Core_WallOutlineGeometry;
  Cesium['WebGLConstants'] = Core_WebGLConstants;
  Cesium['WebMercatorProjection'] = Core_WebMercatorProjection;
  Cesium['WebMercatorTilingScheme'] = Core_WebMercatorTilingScheme;
  Cesium['WindingOrder'] = Core_WindingOrder;
  Cesium['wrapFunction'] = Core_wrapFunction;
  Cesium['writeTextToCanvas'] = Core_writeTextToCanvas;
  Cesium['BillboardGraphics'] = DataSources_BillboardGraphics;
  Cesium['BillboardVisualizer'] = DataSources_BillboardVisualizer;
  Cesium['BoundingSphereState'] = DataSources_BoundingSphereState;
  Cesium['BoxGeometryUpdater'] = DataSources_BoxGeometryUpdater;
  Cesium['BoxGraphics'] = DataSources_BoxGraphics;
  Cesium['CallbackProperty'] = DataSources_CallbackProperty;
  Cesium['CheckerboardMaterialProperty'] = DataSources_CheckerboardMaterialProperty;
  Cesium['ColorMaterialProperty'] = DataSources_ColorMaterialProperty;
  Cesium['CompositeEntityCollection'] = DataSources_CompositeEntityCollection;
  Cesium['CompositeMaterialProperty'] = DataSources_CompositeMaterialProperty;
  Cesium['CompositePositionProperty'] = DataSources_CompositePositionProperty;
  Cesium['CompositeProperty'] = DataSources_CompositeProperty;
  Cesium['ConstantPositionProperty'] = DataSources_ConstantPositionProperty;
  Cesium['ConstantProperty'] = DataSources_ConstantProperty;
  Cesium['CorridorGeometryUpdater'] = DataSources_CorridorGeometryUpdater;
  Cesium['CorridorGraphics'] = DataSources_CorridorGraphics;
  Cesium['createMaterialPropertyDescriptor'] = DataSources_createMaterialPropertyDescriptor;
  Cesium['createPropertyDescriptor'] = DataSources_createPropertyDescriptor;
  Cesium['createRawPropertyDescriptor'] = DataSources_createRawPropertyDescriptor;
  Cesium['CustomDataSource'] = DataSources_CustomDataSource;
  Cesium['CylinderGeometryUpdater'] = DataSources_CylinderGeometryUpdater;
  Cesium['CylinderGraphics'] = DataSources_CylinderGraphics;
  Cesium['CzmlDataSource'] = DataSources_CzmlDataSource;
  Cesium['DataSource'] = DataSources_DataSource;
  Cesium['DataSourceClock'] = DataSources_DataSourceClock;
  Cesium['DataSourceCollection'] = DataSources_DataSourceCollection;
  Cesium['DataSourceDisplay'] = DataSources_DataSourceDisplay;
  Cesium['dynamicGeometryGetBoundingSphere'] = DataSources_dynamicGeometryGetBoundingSphere;
  Cesium['DynamicGeometryUpdater'] = DataSources_DynamicGeometryUpdater;
  Cesium['EllipseGeometryUpdater'] = DataSources_EllipseGeometryUpdater;
  Cesium['EllipseGraphics'] = DataSources_EllipseGraphics;
  Cesium['EllipsoidGeometryUpdater'] = DataSources_EllipsoidGeometryUpdater;
  Cesium['EllipsoidGraphics'] = DataSources_EllipsoidGraphics;
  Cesium['Entity'] = DataSources_Entity;
  Cesium['EntityCluster'] = DataSources_EntityCluster;
  Cesium['EntityCollection'] = DataSources_EntityCollection;
  Cesium['EntityView'] = DataSources_EntityView;
  Cesium['GeoJsonDataSource'] = DataSources_GeoJsonDataSource;
  Cesium['GeometryUpdater'] = DataSources_GeometryUpdater;
  Cesium['GeometryVisualizer'] = DataSources_GeometryVisualizer;
  Cesium['GridMaterialProperty'] = DataSources_GridMaterialProperty;
  Cesium['ImageMaterialProperty'] = DataSources_ImageMaterialProperty;
  Cesium['KmlDataSource'] = DataSources_KmlDataSource;
  Cesium['LabelGraphics'] = DataSources_LabelGraphics;
  Cesium['LabelVisualizer'] = DataSources_LabelVisualizer;
  Cesium['MaterialProperty'] = DataSources_MaterialProperty;
  Cesium['ModelGraphics'] = DataSources_ModelGraphics;
  Cesium['ModelVisualizer'] = DataSources_ModelVisualizer;
  Cesium['NodeTransformationProperty'] = DataSources_NodeTransformationProperty;
  Cesium['PathGraphics'] = DataSources_PathGraphics;
  Cesium['PathVisualizer'] = DataSources_PathVisualizer;
  Cesium['PointGraphics'] = DataSources_PointGraphics;
  Cesium['PointVisualizer'] = DataSources_PointVisualizer;
  Cesium['PolygonGeometryUpdater'] = DataSources_PolygonGeometryUpdater;
  Cesium['PolygonGraphics'] = DataSources_PolygonGraphics;
  Cesium['PolylineArrowMaterialProperty'] = DataSources_PolylineArrowMaterialProperty;
  Cesium['PolylineGeometryUpdater'] = DataSources_PolylineGeometryUpdater;
  Cesium['PolylineGlowMaterialProperty'] = DataSources_PolylineGlowMaterialProperty;
  Cesium['PolylineGraphics'] = DataSources_PolylineGraphics;
  Cesium['PolylineOutlineMaterialProperty'] = DataSources_PolylineOutlineMaterialProperty;
  Cesium['PolylineVolumeGeometryUpdater'] = DataSources_PolylineVolumeGeometryUpdater;
  Cesium['PolylineVolumeGraphics'] = DataSources_PolylineVolumeGraphics;
  Cesium['PositionProperty'] = DataSources_PositionProperty;
  Cesium['PositionPropertyArray'] = DataSources_PositionPropertyArray;
  Cesium['Property'] = DataSources_Property;
  Cesium['PropertyArray'] = DataSources_PropertyArray;
  Cesium['PropertyBag'] = DataSources_PropertyBag;
  Cesium['RectangleGeometryUpdater'] = DataSources_RectangleGeometryUpdater;
  Cesium['RectangleGraphics'] = DataSources_RectangleGraphics;
  Cesium['ReferenceProperty'] = DataSources_ReferenceProperty;
  Cesium['Rotation'] = DataSources_Rotation;
  Cesium['SampledPositionProperty'] = DataSources_SampledPositionProperty;
  Cesium['SampledProperty'] = DataSources_SampledProperty;
  Cesium['ScaledPositionProperty'] = DataSources_ScaledPositionProperty;
  Cesium['StaticGeometryColorBatch'] = DataSources_StaticGeometryColorBatch;
  Cesium['StaticGeometryPerMaterialBatch'] = DataSources_StaticGeometryPerMaterialBatch;
  Cesium['StaticGroundGeometryColorBatch'] = DataSources_StaticGroundGeometryColorBatch;
  Cesium['StaticOutlineGeometryBatch'] = DataSources_StaticOutlineGeometryBatch;
  Cesium['StripeMaterialProperty'] = DataSources_StripeMaterialProperty;
  Cesium['StripeOrientation'] = DataSources_StripeOrientation;
  Cesium['TimeIntervalCollectionPositionProperty'] = DataSources_TimeIntervalCollectionPositionProperty;
  Cesium['TimeIntervalCollectionProperty'] = DataSources_TimeIntervalCollectionProperty;
  Cesium['VelocityOrientationProperty'] = DataSources_VelocityOrientationProperty;
  Cesium['VelocityVectorProperty'] = DataSources_VelocityVectorProperty;
  Cesium['Visualizer'] = DataSources_Visualizer;
  Cesium['WallGeometryUpdater'] = DataSources_WallGeometryUpdater;
  Cesium['WallGraphics'] = DataSources_WallGraphics;
  Cesium['AutomaticUniforms'] = Renderer_AutomaticUniforms;
  Cesium['Buffer'] = Renderer_Buffer;
  Cesium['BufferUsage'] = Renderer_BufferUsage;
  Cesium['ClearCommand'] = Renderer_ClearCommand;
  Cesium['ComputeCommand'] = Renderer_ComputeCommand;
  Cesium['ComputeEngine'] = Renderer_ComputeEngine;
  Cesium['Context'] = Renderer_Context;
  Cesium['ContextLimits'] = Renderer_ContextLimits;
  Cesium['createUniform'] = Renderer_createUniform;
  Cesium['createUniformArray'] = Renderer_createUniformArray;
  Cesium['CubeMap'] = Renderer_CubeMap;
  Cesium['CubeMapFace'] = Renderer_CubeMapFace;
  Cesium['DrawCommand'] = Renderer_DrawCommand;
  Cesium['Framebuffer'] = Renderer_Framebuffer;
  Cesium['loadCubeMap'] = Renderer_loadCubeMap;
  Cesium['MipmapHint'] = Renderer_MipmapHint;
  Cesium['Pass'] = Renderer_Pass;
  Cesium['PassState'] = Renderer_PassState;
  Cesium['PickFramebuffer'] = Renderer_PickFramebuffer;
  Cesium['PixelDatatype'] = Renderer_PixelDatatype;
  Cesium['Renderbuffer'] = Renderer_Renderbuffer;
  Cesium['RenderbufferFormat'] = Renderer_RenderbufferFormat;
  Cesium['RenderState'] = Renderer_RenderState;
  Cesium['Sampler'] = Renderer_Sampler;
  Cesium['ShaderCache'] = Renderer_ShaderCache;
  Cesium['ShaderProgram'] = Renderer_ShaderProgram;
  Cesium['ShaderSource'] = Renderer_ShaderSource;
  Cesium['Texture'] = Renderer_Texture;
  Cesium['TextureMagnificationFilter'] = Renderer_TextureMagnificationFilter;
  Cesium['TextureMinificationFilter'] = Renderer_TextureMinificationFilter;
  Cesium['TextureWrap'] = Renderer_TextureWrap;
  Cesium['UniformState'] = Renderer_UniformState;
  Cesium['VertexArray'] = Renderer_VertexArray;
  Cesium['VertexArrayFacade'] = Renderer_VertexArrayFacade;
  Cesium['Appearance'] = Scene_Appearance;
  Cesium['ArcGisMapServerImageryProvider'] = Scene_ArcGisMapServerImageryProvider;
  Cesium['Axis'] = Scene_Axis;
  Cesium['BatchTable'] = Scene_BatchTable;
  Cesium['Billboard'] = Scene_Billboard;
  Cesium['BillboardCollection'] = Scene_BillboardCollection;
  Cesium['BingMapsImageryProvider'] = Scene_BingMapsImageryProvider;
  Cesium['BingMapsStyle'] = Scene_BingMapsStyle;
  Cesium['BlendEquation'] = Scene_BlendEquation;
  Cesium['BlendFunction'] = Scene_BlendFunction;
  Cesium['BlendingState'] = Scene_BlendingState;
  Cesium['BlendOption'] = Scene_BlendOption;
  Cesium['Camera'] = Scene_Camera;
  Cesium['CameraEventAggregator'] = Scene_CameraEventAggregator;
  Cesium['CameraEventType'] = Scene_CameraEventType;
  Cesium['CameraFlightPath'] = Scene_CameraFlightPath;
  Cesium['ColorBlendMode'] = Scene_ColorBlendMode;
  Cesium['createOpenStreetMapImageryProvider'] = Scene_createOpenStreetMapImageryProvider;
  Cesium['createTangentSpaceDebugPrimitive'] = Scene_createTangentSpaceDebugPrimitive;
  Cesium['createTileMapServiceImageryProvider'] = Scene_createTileMapServiceImageryProvider;
  Cesium['CreditDisplay'] = Scene_CreditDisplay;
  Cesium['CullFace'] = Scene_CullFace;
  Cesium['CullingVolume'] = Scene_CullingVolume;
  Cesium['DebugAppearance'] = Scene_DebugAppearance;
  Cesium['DebugCameraPrimitive'] = Scene_DebugCameraPrimitive;
  Cesium['DebugModelMatrixPrimitive'] = Scene_DebugModelMatrixPrimitive;
  Cesium['DepthFunction'] = Scene_DepthFunction;
  Cesium['DepthPlane'] = Scene_DepthPlane;
  Cesium['DeviceOrientationCameraController'] = Scene_DeviceOrientationCameraController;
  Cesium['DiscardMissingTileImagePolicy'] = Scene_DiscardMissingTileImagePolicy;
  Cesium['EllipsoidPrimitive'] = Scene_EllipsoidPrimitive;
  Cesium['EllipsoidSurfaceAppearance'] = Scene_EllipsoidSurfaceAppearance;
  Cesium['Fog'] = Scene_Fog;
  Cesium['FrameRateMonitor'] = Scene_FrameRateMonitor;
  Cesium['FrameState'] = Scene_FrameState;
  Cesium['FrustumCommands'] = Scene_FrustumCommands;
  Cesium['FXAA'] = Scene_FXAA;
  Cesium['getAttributeOrUniformBySemantic'] = Scene_getAttributeOrUniformBySemantic;
  Cesium['getBinaryAccessor'] = Scene_getBinaryAccessor;
  Cesium['GetFeatureInfoFormat'] = Scene_GetFeatureInfoFormat;
  Cesium['Globe'] = Scene_Globe;
  Cesium['GlobeDepth'] = Scene_GlobeDepth;
  Cesium['GlobeSurfaceShaderSet'] = Scene_GlobeSurfaceShaderSet;
  Cesium['GlobeSurfaceTile'] = Scene_GlobeSurfaceTile;
  Cesium['GlobeSurfaceTileProvider'] = Scene_GlobeSurfaceTileProvider;
  Cesium['GoogleEarthImageryProvider'] = Scene_GoogleEarthImageryProvider;
  Cesium['GridImageryProvider'] = Scene_GridImageryProvider;
  Cesium['GroundPrimitive'] = Scene_GroundPrimitive;
  Cesium['HeightReference'] = Scene_HeightReference;
  Cesium['HorizontalOrigin'] = Scene_HorizontalOrigin;
  Cesium['ImageDrapingPrimitive'] = Scene_ImageDrapingPrimitive;
  Cesium['Imagery'] = Scene_Imagery;
  Cesium['ImageryLayer'] = Scene_ImageryLayer;
  Cesium['ImageryLayerCollection'] = Scene_ImageryLayerCollection;
  Cesium['ImageryLayerFeatureInfo'] = Scene_ImageryLayerFeatureInfo;
  Cesium['ImageryProvider'] = Scene_ImageryProvider;
  Cesium['ImagerySplitDirection'] = Scene_ImagerySplitDirection;
  Cesium['ImageryState'] = Scene_ImageryState;
  Cesium['Label'] = Scene_Label;
  Cesium['LabelCollection'] = Scene_LabelCollection;
  Cesium['LabelStyle'] = Scene_LabelStyle;
  Cesium['MapboxImageryProvider'] = Scene_MapboxImageryProvider;
  Cesium['MapMode2D'] = Scene_MapMode2D;
  Cesium['Material'] = Scene_Material;
  Cesium['MaterialAppearance'] = Scene_MaterialAppearance;
  Cesium['Model'] = Scene_Model;
  Cesium['ModelAnimation'] = Scene_ModelAnimation;
  Cesium['ModelAnimationCache'] = Scene_ModelAnimationCache;
  Cesium['ModelAnimationCollection'] = Scene_ModelAnimationCollection;
  Cesium['ModelAnimationLoop'] = Scene_ModelAnimationLoop;
  Cesium['ModelAnimationState'] = Scene_ModelAnimationState;
  Cesium['ModelMaterial'] = Scene_ModelMaterial;
  Cesium['modelMaterialsCommon'] = Scene_modelMaterialsCommon;
  Cesium['ModelMesh'] = Scene_ModelMesh;
  Cesium['ModelNode'] = Scene_ModelNode;
  Cesium['Moon'] = Scene_Moon;
  Cesium['NeverTileDiscardPolicy'] = Scene_NeverTileDiscardPolicy;
  Cesium['OIT'] = Scene_OIT;
  Cesium['OrthographicFrustum'] = Scene_OrthographicFrustum;
  Cesium['PerformanceDisplay'] = Scene_PerformanceDisplay;
  Cesium['PerInstanceColorAppearance'] = Scene_PerInstanceColorAppearance;
  Cesium['PerspectiveFrustum'] = Scene_PerspectiveFrustum;
  Cesium['PerspectiveOffCenterFrustum'] = Scene_PerspectiveOffCenterFrustum;
  Cesium['PickDepth'] = Scene_PickDepth;
  Cesium['PointAppearance'] = Scene_PointAppearance;
  Cesium['PointPrimitive'] = Scene_PointPrimitive;
  Cesium['PointPrimitiveCollection'] = Scene_PointPrimitiveCollection;
  Cesium['Polyline'] = Scene_Polyline;
  Cesium['PolylineCollection'] = Scene_PolylineCollection;
  Cesium['PolylineColorAppearance'] = Scene_PolylineColorAppearance;
  Cesium['PolylineMaterialAppearance'] = Scene_PolylineMaterialAppearance;
  Cesium['Primitive'] = Scene_Primitive;
  Cesium['PrimitiveCollection'] = Scene_PrimitiveCollection;
  Cesium['PrimitivePipeline'] = Scene_PrimitivePipeline;
  Cesium['PrimitiveState'] = Scene_PrimitiveState;
  Cesium['QuadtreeOccluders'] = Scene_QuadtreeOccluders;
  Cesium['QuadtreePrimitive'] = Scene_QuadtreePrimitive;
  Cesium['QuadtreeTile'] = Scene_QuadtreeTile;
  Cesium['QuadtreeTileLoadState'] = Scene_QuadtreeTileLoadState;
  Cesium['QuadtreeTileProvider'] = Scene_QuadtreeTileProvider;
  Cesium['Scene'] = Scene_Scene;
  Cesium['SceneMode'] = Scene_SceneMode;
  Cesium['SceneTransforms'] = Scene_SceneTransforms;
  Cesium['SceneTransitioner'] = Scene_SceneTransitioner;
  Cesium['ScreenSpaceCameraController'] = Scene_ScreenSpaceCameraController;
  Cesium['ShadowMap'] = Scene_ShadowMap;
  Cesium['ShadowMapShader'] = Scene_ShadowMapShader;
  Cesium['ShadowMode'] = Scene_ShadowMode;
  Cesium['SingleTileImageryProvider'] = Scene_SingleTileImageryProvider;
  Cesium['SkyAtmosphere'] = Scene_SkyAtmosphere;
  Cesium['SkyBox'] = Scene_SkyBox;
  Cesium['StencilFunction'] = Scene_StencilFunction;
  Cesium['StencilOperation'] = Scene_StencilOperation;
  Cesium['Sun'] = Scene_Sun;
  Cesium['SunPostProcess'] = Scene_SunPostProcess;
  Cesium['TerrainState'] = Scene_TerrainState;
  Cesium['TextureAtlas'] = Scene_TextureAtlas;
  Cesium['TileBoundingBox'] = Scene_TileBoundingBox;
  Cesium['TileCoordinatesImageryProvider'] = Scene_TileCoordinatesImageryProvider;
  Cesium['TileDiscardPolicy'] = Scene_TileDiscardPolicy;
  Cesium['TileImagery'] = Scene_TileImagery;
  Cesium['TileReplacementQueue'] = Scene_TileReplacementQueue;
  Cesium['TileState'] = Scene_TileState;
  Cesium['TileTerrain'] = Scene_TileTerrain;
  Cesium['TweenCollection'] = Scene_TweenCollection;
  Cesium['UrlTemplateImageryProvider'] = Scene_UrlTemplateImageryProvider;
  Cesium['VerticalOrigin'] = Scene_VerticalOrigin;
  Cesium['ViewportQuad'] = Scene_ViewportQuad;
  Cesium['WebMapServiceImageryProvider'] = Scene_WebMapServiceImageryProvider;
  Cesium['WebMapTileServiceImageryProvider'] = Scene_WebMapTileServiceImageryProvider;
  Cesium._shaders['AdjustTranslucentFS'] = Shaders_AdjustTranslucentFS;
  Cesium._shaders['AllMaterialAppearanceFS'] = Shaders_Appearances_AllMaterialAppearanceFS;
  Cesium._shaders['AllMaterialAppearanceVS'] = Shaders_Appearances_AllMaterialAppearanceVS;
  Cesium._shaders['BasicMaterialAppearanceFS'] = Shaders_Appearances_BasicMaterialAppearanceFS;
  Cesium._shaders['BasicMaterialAppearanceVS'] = Shaders_Appearances_BasicMaterialAppearanceVS;
  Cesium._shaders['EllipsoidSurfaceAppearanceFS'] = Shaders_Appearances_EllipsoidSurfaceAppearanceFS;
  Cesium._shaders['EllipsoidSurfaceAppearanceVS'] = Shaders_Appearances_EllipsoidSurfaceAppearanceVS;
  Cesium._shaders['PerInstanceColorAppearanceFS'] = Shaders_Appearances_PerInstanceColorAppearanceFS;
  Cesium._shaders['PerInstanceColorAppearanceVS'] = Shaders_Appearances_PerInstanceColorAppearanceVS;
  Cesium._shaders['PerInstanceFlatColorAppearanceFS'] = Shaders_Appearances_PerInstanceFlatColorAppearanceFS;
  Cesium._shaders['PerInstanceFlatColorAppearanceVS'] = Shaders_Appearances_PerInstanceFlatColorAppearanceVS;
  Cesium._shaders['PointAppearanceFS'] = Shaders_Appearances_PointAppearanceFS;
  Cesium._shaders['PointAppearanceVS'] = Shaders_Appearances_PointAppearanceVS;
  Cesium._shaders['PolylineColorAppearanceVS'] = Shaders_Appearances_PolylineColorAppearanceVS;
  Cesium._shaders['PolylineMaterialAppearanceVS'] = Shaders_Appearances_PolylineMaterialAppearanceVS;
  Cesium._shaders['TexturedMaterialAppearanceFS'] = Shaders_Appearances_TexturedMaterialAppearanceFS;
  Cesium._shaders['TexturedMaterialAppearanceVS'] = Shaders_Appearances_TexturedMaterialAppearanceVS;
  Cesium._shaders['BillboardCollectionFS'] = Shaders_BillboardCollectionFS;
  Cesium._shaders['BillboardCollectionVS'] = Shaders_BillboardCollectionVS;
  Cesium._shaders['degreesPerRadian'] = Shaders_Builtin_Constants_degreesPerRadian;
  Cesium._shaders['depthRange'] = Shaders_Builtin_Constants_depthRange;
  Cesium._shaders['epsilon1'] = Shaders_Builtin_Constants_epsilon1;
  Cesium._shaders['epsilon2'] = Shaders_Builtin_Constants_epsilon2;
  Cesium._shaders['epsilon3'] = Shaders_Builtin_Constants_epsilon3;
  Cesium._shaders['epsilon4'] = Shaders_Builtin_Constants_epsilon4;
  Cesium._shaders['epsilon5'] = Shaders_Builtin_Constants_epsilon5;
  Cesium._shaders['epsilon6'] = Shaders_Builtin_Constants_epsilon6;
  Cesium._shaders['epsilon7'] = Shaders_Builtin_Constants_epsilon7;
  Cesium._shaders['infinity'] = Shaders_Builtin_Constants_infinity;
  Cesium._shaders['oneOverPi'] = Shaders_Builtin_Constants_oneOverPi;
  Cesium._shaders['oneOverTwoPi'] = Shaders_Builtin_Constants_oneOverTwoPi;
  Cesium._shaders['passCompute'] = Shaders_Builtin_Constants_passCompute;
  Cesium._shaders['passEnvironment'] = Shaders_Builtin_Constants_passEnvironment;
  Cesium._shaders['passGlobe'] = Shaders_Builtin_Constants_passGlobe;
  Cesium._shaders['passGround'] = Shaders_Builtin_Constants_passGround;
  Cesium._shaders['passOpaque'] = Shaders_Builtin_Constants_passOpaque;
  Cesium._shaders['passOverlay'] = Shaders_Builtin_Constants_passOverlay;
  Cesium._shaders['passTranslucent'] = Shaders_Builtin_Constants_passTranslucent;
  Cesium._shaders['pi'] = Shaders_Builtin_Constants_pi;
  Cesium._shaders['piOverFour'] = Shaders_Builtin_Constants_piOverFour;
  Cesium._shaders['piOverSix'] = Shaders_Builtin_Constants_piOverSix;
  Cesium._shaders['piOverThree'] = Shaders_Builtin_Constants_piOverThree;
  Cesium._shaders['piOverTwo'] = Shaders_Builtin_Constants_piOverTwo;
  Cesium._shaders['radiansPerDegree'] = Shaders_Builtin_Constants_radiansPerDegree;
  Cesium._shaders['sceneMode2D'] = Shaders_Builtin_Constants_sceneMode2D;
  Cesium._shaders['sceneMode3D'] = Shaders_Builtin_Constants_sceneMode3D;
  Cesium._shaders['sceneModeColumbusView'] = Shaders_Builtin_Constants_sceneModeColumbusView;
  Cesium._shaders['sceneModeMorphing'] = Shaders_Builtin_Constants_sceneModeMorphing;
  Cesium._shaders['solarRadius'] = Shaders_Builtin_Constants_solarRadius;
  Cesium._shaders['threePiOver2'] = Shaders_Builtin_Constants_threePiOver2;
  Cesium._shaders['twoPi'] = Shaders_Builtin_Constants_twoPi;
  Cesium._shaders['webMercatorMaxLatitude'] = Shaders_Builtin_Constants_webMercatorMaxLatitude;
  Cesium._shaders['CzmBuiltins'] = Shaders_Builtin_CzmBuiltins;
  Cesium._shaders['alphaWeight'] = Shaders_Builtin_Functions_alphaWeight;
  Cesium._shaders['antialias'] = Shaders_Builtin_Functions_antialias;
  Cesium._shaders['cascadeColor'] = Shaders_Builtin_Functions_cascadeColor;
  Cesium._shaders['cascadeDistance'] = Shaders_Builtin_Functions_cascadeDistance;
  Cesium._shaders['cascadeMatrix'] = Shaders_Builtin_Functions_cascadeMatrix;
  Cesium._shaders['cascadeWeights'] = Shaders_Builtin_Functions_cascadeWeights;
  Cesium._shaders['columbusViewMorph'] = Shaders_Builtin_Functions_columbusViewMorph;
  Cesium._shaders['computePosition'] = Shaders_Builtin_Functions_computePosition;
  Cesium._shaders['cosineAndSine'] = Shaders_Builtin_Functions_cosineAndSine;
  Cesium._shaders['decompressTextureCoordinates'] = Shaders_Builtin_Functions_decompressTextureCoordinates;
  Cesium._shaders['eastNorthUpToEyeCoordinates'] = Shaders_Builtin_Functions_eastNorthUpToEyeCoordinates;
  Cesium._shaders['ellipsoidContainsPoint'] = Shaders_Builtin_Functions_ellipsoidContainsPoint;
  Cesium._shaders['ellipsoidNew'] = Shaders_Builtin_Functions_ellipsoidNew;
  Cesium._shaders['ellipsoidWgs84TextureCoordinates'] = Shaders_Builtin_Functions_ellipsoidWgs84TextureCoordinates;
  Cesium._shaders['equalsEpsilon'] = Shaders_Builtin_Functions_equalsEpsilon;
  Cesium._shaders['eyeOffset'] = Shaders_Builtin_Functions_eyeOffset;
  Cesium._shaders['eyeToWindowCoordinates'] = Shaders_Builtin_Functions_eyeToWindowCoordinates;
  Cesium._shaders['fog'] = Shaders_Builtin_Functions_fog;
  Cesium._shaders['geodeticSurfaceNormal'] = Shaders_Builtin_Functions_geodeticSurfaceNormal;
  Cesium._shaders['getDefaultMaterial'] = Shaders_Builtin_Functions_getDefaultMaterial;
  Cesium._shaders['getLambertDiffuse'] = Shaders_Builtin_Functions_getLambertDiffuse;
  Cesium._shaders['getSpecular'] = Shaders_Builtin_Functions_getSpecular;
  Cesium._shaders['getWaterNoise'] = Shaders_Builtin_Functions_getWaterNoise;
  Cesium._shaders['getWgs84EllipsoidEC'] = Shaders_Builtin_Functions_getWgs84EllipsoidEC;
  Cesium._shaders['hue'] = Shaders_Builtin_Functions_hue;
  Cesium._shaders['isEmpty'] = Shaders_Builtin_Functions_isEmpty;
  Cesium._shaders['isFull'] = Shaders_Builtin_Functions_isFull;
  Cesium._shaders['latitudeToWebMercatorFraction'] = Shaders_Builtin_Functions_latitudeToWebMercatorFraction;
  Cesium._shaders['luminance'] = Shaders_Builtin_Functions_luminance;
  Cesium._shaders['metersPerPixel'] = Shaders_Builtin_Functions_metersPerPixel;
  Cesium._shaders['modelToWindowCoordinates'] = Shaders_Builtin_Functions_modelToWindowCoordinates;
  Cesium._shaders['multiplyWithColorBalance'] = Shaders_Builtin_Functions_multiplyWithColorBalance;
  Cesium._shaders['nearFarScalar'] = Shaders_Builtin_Functions_nearFarScalar;
  Cesium._shaders['octDecode'] = Shaders_Builtin_Functions_octDecode;
  Cesium._shaders['packDepth'] = Shaders_Builtin_Functions_packDepth;
  Cesium._shaders['phong'] = Shaders_Builtin_Functions_phong;
  Cesium._shaders['pointAlongRay'] = Shaders_Builtin_Functions_pointAlongRay;
  Cesium._shaders['rayEllipsoidIntersectionInterval'] = Shaders_Builtin_Functions_rayEllipsoidIntersectionInterval;
  Cesium._shaders['RGBToXYZ'] = Shaders_Builtin_Functions_RGBToXYZ;
  Cesium._shaders['saturation'] = Shaders_Builtin_Functions_saturation;
  Cesium._shaders['shadowDepthCompare'] = Shaders_Builtin_Functions_shadowDepthCompare;
  Cesium._shaders['shadowVisibility'] = Shaders_Builtin_Functions_shadowVisibility;
  Cesium._shaders['signNotZero'] = Shaders_Builtin_Functions_signNotZero;
  Cesium._shaders['tangentToEyeSpaceMatrix'] = Shaders_Builtin_Functions_tangentToEyeSpaceMatrix;
  Cesium._shaders['translateRelativeToEye'] = Shaders_Builtin_Functions_translateRelativeToEye;
  Cesium._shaders['translucentPhong'] = Shaders_Builtin_Functions_translucentPhong;
  Cesium._shaders['transpose'] = Shaders_Builtin_Functions_transpose;
  Cesium._shaders['unpackDepth'] = Shaders_Builtin_Functions_unpackDepth;
  Cesium._shaders['windowToEyeCoordinates'] = Shaders_Builtin_Functions_windowToEyeCoordinates;
  Cesium._shaders['XYZToRGB'] = Shaders_Builtin_Functions_XYZToRGB;
  Cesium._shaders['depthRangeStruct'] = Shaders_Builtin_Structs_depthRangeStruct;
  Cesium._shaders['ellipsoid'] = Shaders_Builtin_Structs_ellipsoid;
  Cesium._shaders['material'] = Shaders_Builtin_Structs_material;
  Cesium._shaders['materialInput'] = Shaders_Builtin_Structs_materialInput;
  Cesium._shaders['ray'] = Shaders_Builtin_Structs_ray;
  Cesium._shaders['raySegment'] = Shaders_Builtin_Structs_raySegment;
  Cesium._shaders['shadowParameters'] = Shaders_Builtin_Structs_shadowParameters;
  Cesium._shaders['CompositeOITFS'] = Shaders_CompositeOITFS;
  Cesium._shaders['DepthPlaneFS'] = Shaders_DepthPlaneFS;
  Cesium._shaders['DepthPlaneVS'] = Shaders_DepthPlaneVS;
  Cesium._shaders['EllipsoidFS'] = Shaders_EllipsoidFS;
  Cesium._shaders['EllipsoidVS'] = Shaders_EllipsoidVS;
  Cesium._shaders['GlobeFS'] = Shaders_GlobeFS;
  Cesium._shaders['GlobeVS'] = Shaders_GlobeVS;
  Cesium._shaders['GroundAtmosphere'] = Shaders_GroundAtmosphere;
  Cesium._shaders['ImageDrapingFS'] = Shaders_ImageDrapingFS;
  Cesium._shaders['ImageDrapingVS'] = Shaders_ImageDrapingVS;
  Cesium._shaders['BumpMapMaterial'] = Shaders_Materials_BumpMapMaterial;
  Cesium._shaders['CheckerboardMaterial'] = Shaders_Materials_CheckerboardMaterial;
  Cesium._shaders['DotMaterial'] = Shaders_Materials_DotMaterial;
  Cesium._shaders['FadeMaterial'] = Shaders_Materials_FadeMaterial;
  Cesium._shaders['GridMaterial'] = Shaders_Materials_GridMaterial;
  Cesium._shaders['NormalMapMaterial'] = Shaders_Materials_NormalMapMaterial;
  Cesium._shaders['PolylineArrowMaterial'] = Shaders_Materials_PolylineArrowMaterial;
  Cesium._shaders['PolylineGlowMaterial'] = Shaders_Materials_PolylineGlowMaterial;
  Cesium._shaders['PolylineOutlineMaterial'] = Shaders_Materials_PolylineOutlineMaterial;
  Cesium._shaders['RimLightingMaterial'] = Shaders_Materials_RimLightingMaterial;
  Cesium._shaders['StripeMaterial'] = Shaders_Materials_StripeMaterial;
  Cesium._shaders['Water'] = Shaders_Materials_Water;
  Cesium._shaders['PointPrimitiveCollectionFS'] = Shaders_PointPrimitiveCollectionFS;
  Cesium._shaders['PointPrimitiveCollectionVS'] = Shaders_PointPrimitiveCollectionVS;
  Cesium._shaders['PolylineCommon'] = Shaders_PolylineCommon;
  Cesium._shaders['PolylineFS'] = Shaders_PolylineFS;
  Cesium._shaders['PolylineVS'] = Shaders_PolylineVS;
  Cesium._shaders['AdditiveBlend'] = Shaders_PostProcessFilters_AdditiveBlend;
  Cesium._shaders['BrightPass'] = Shaders_PostProcessFilters_BrightPass;
  Cesium._shaders['FXAA'] = Shaders_PostProcessFilters_FXAA;
  Cesium._shaders['GaussianBlur1D'] = Shaders_PostProcessFilters_GaussianBlur1D;
  Cesium._shaders['PassThrough'] = Shaders_PostProcessFilters_PassThrough;
  Cesium._shaders['ReprojectWebMercatorFS'] = Shaders_ReprojectWebMercatorFS;
  Cesium._shaders['ReprojectWebMercatorVS'] = Shaders_ReprojectWebMercatorVS;
  Cesium._shaders['ShadowVolumeFS'] = Shaders_ShadowVolumeFS;
  Cesium._shaders['ShadowVolumeVS'] = Shaders_ShadowVolumeVS;
  Cesium._shaders['SkyAtmosphereFS'] = Shaders_SkyAtmosphereFS;
  Cesium._shaders['SkyAtmosphereVS'] = Shaders_SkyAtmosphereVS;
  Cesium._shaders['SkyBoxFS'] = Shaders_SkyBoxFS;
  Cesium._shaders['SkyBoxVS'] = Shaders_SkyBoxVS;
  Cesium._shaders['SunFS'] = Shaders_SunFS;
  Cesium._shaders['SunTextureFS'] = Shaders_SunTextureFS;
  Cesium._shaders['SunVS'] = Shaders_SunVS;
  Cesium._shaders['ViewportQuadFS'] = Shaders_ViewportQuadFS;
  Cesium._shaders['ViewportQuadVS'] = Shaders_ViewportQuadVS;
  Cesium['Autolinker'] = ThirdParty_Autolinker;
  Cesium['crunch'] = ThirdParty_crunch;
  Cesium['earcut-2.1.1'] = ThirdParty_earcut_2_1_1;
  Cesium['gltfDefaults'] = ThirdParty_gltfDefaults;
  Cesium['kdbush'] = ThirdParty_kdbush;
  Cesium['knockout-3.4.0'] = ThirdParty_knockout_3_4_0;
  Cesium['knockout-es5'] = ThirdParty_knockout_es5;
  Cesium['knockout'] = ThirdParty_knockout;
  Cesium['measureText'] = ThirdParty_measureText;
  Cesium['mersenne-twister'] = ThirdParty_mersenne_twister;
  Cesium['NoSleep'] = ThirdParty_NoSleep;
  Cesium['sprintf'] = ThirdParty_sprintf;
  Cesium['topojson'] = ThirdParty_topojson;
  Cesium['Tween'] = ThirdParty_Tween;
  Cesium['Uri'] = ThirdParty_Uri;
  Cesium['when'] = ThirdParty_when;
  Cesium['zip'] = ThirdParty_zip;
  Cesium['Animation'] = Widgets_Animation_Animation;
  Cesium['AnimationViewModel'] = Widgets_Animation_AnimationViewModel;
  Cesium['BaseLayerPicker'] = Widgets_BaseLayerPicker_BaseLayerPicker;
  Cesium['BaseLayerPickerViewModel'] = Widgets_BaseLayerPicker_BaseLayerPickerViewModel;
  Cesium['createDefaultImageryProviderViewModels'] = Widgets_BaseLayerPicker_createDefaultImageryProviderViewModels;
  Cesium['createDefaultTerrainProviderViewModels'] = Widgets_BaseLayerPicker_createDefaultTerrainProviderViewModels;
  Cesium['ProviderViewModel'] = Widgets_BaseLayerPicker_ProviderViewModel;
  Cesium['CesiumInspector'] = Widgets_CesiumInspector_CesiumInspector;
  Cesium['CesiumInspectorViewModel'] = Widgets_CesiumInspector_CesiumInspectorViewModel;
  Cesium['CesiumWidget'] = Widgets_CesiumWidget_CesiumWidget;
  Cesium['ClockViewModel'] = Widgets_ClockViewModel;
  Cesium['Command'] = Widgets_Command;
  Cesium['createCommand'] = Widgets_createCommand;
  Cesium['FullscreenButton'] = Widgets_FullscreenButton_FullscreenButton;
  Cesium['FullscreenButtonViewModel'] = Widgets_FullscreenButton_FullscreenButtonViewModel;
  Cesium['Geocoder'] = Widgets_Geocoder_Geocoder;
  Cesium['GeocoderViewModel'] = Widgets_Geocoder_GeocoderViewModel;
  Cesium['getElement'] = Widgets_getElement;
  Cesium['HomeButton'] = Widgets_HomeButton_HomeButton;
  Cesium['HomeButtonViewModel'] = Widgets_HomeButton_HomeButtonViewModel;
  Cesium['InfoBox'] = Widgets_InfoBox_InfoBox;
  Cesium['InfoBoxViewModel'] = Widgets_InfoBox_InfoBoxViewModel;
  Cesium['NavigationHelpButton'] = Widgets_NavigationHelpButton_NavigationHelpButton;
  Cesium['NavigationHelpButtonViewModel'] = Widgets_NavigationHelpButton_NavigationHelpButtonViewModel;
  Cesium['PerformanceWatchdog'] = Widgets_PerformanceWatchdog_PerformanceWatchdog;
  Cesium['PerformanceWatchdogViewModel'] = Widgets_PerformanceWatchdog_PerformanceWatchdogViewModel;
  Cesium['SceneModePicker'] = Widgets_SceneModePicker_SceneModePicker;
  Cesium['SceneModePickerViewModel'] = Widgets_SceneModePicker_SceneModePickerViewModel;
  Cesium['SelectionIndicator'] = Widgets_SelectionIndicator_SelectionIndicator;
  Cesium['SelectionIndicatorViewModel'] = Widgets_SelectionIndicator_SelectionIndicatorViewModel;
  Cesium['subscribeAndEvaluate'] = Widgets_subscribeAndEvaluate;
  Cesium['SvgPathBindingHandler'] = Widgets_SvgPathBindingHandler;
  Cesium['Timeline'] = Widgets_Timeline_Timeline;
  Cesium['TimelineHighlightRange'] = Widgets_Timeline_TimelineHighlightRange;
  Cesium['TimelineTrack'] = Widgets_Timeline_TimelineTrack;
  Cesium['ToggleButtonViewModel'] = Widgets_ToggleButtonViewModel;
  Cesium['Viewer'] = Widgets_Viewer_Viewer;
  Cesium['viewerCesiumInspectorMixin'] = Widgets_Viewer_viewerCesiumInspectorMixin;
  Cesium['viewerDragDropMixin'] = Widgets_Viewer_viewerDragDropMixin;
  Cesium['viewerPerformanceWatchdogMixin'] = Widgets_Viewer_viewerPerformanceWatchdogMixin;
  Cesium['VRButton'] = Widgets_VRButton_VRButton;
  Cesium['VRButtonViewModel'] = Widgets_VRButton_VRButtonViewModel;
  Cesium['createTaskProcessorWorker'] = Workers_createTaskProcessorWorker;
  return Cesium;
});
/*global require*/
// require in the complete Cesium object and reassign it globally.
// This is meant for use with the Almond loader.
require([
        'Cesium'
    ], function(
        Cesium) {
    'use strict';
    /*global self*/
    var scope = typeof window !== 'undefined' ? window : typeof self !== 'undefined' ? self : {};

    scope.Cesium = Cesium;
}, undefined, true);

}());
//
//  Copyright (c) 2015 Paperspace Co. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to
//  deal in the Software without restriction, including without limitation the
//  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
//  sell copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
//  IN THE SOFTWARE.
//


// universal module definition
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        root.YUVCanvas = factory();
    }
}(this, function () {


/**
 * This class can be used to render output pictures from an H264bsdDecoder to a canvas element.
 * If available the content is rendered using WebGL.
 */
  function YUVCanvas(parOptions) {
    
    parOptions = parOptions || {};
    
    this.canvasElement = parOptions.canvas || document.createElement("canvas");
    this.contextOptions = parOptions.contextOptions;
    
    this.type = parOptions.type || "yuv420";
    
    this.customYUV444 = parOptions.customYUV444;
    
    this.conversionType = parOptions.conversionType || "rec601";

    this.width = parOptions.width || 640;
    this.height = parOptions.height || 320;
    
    this.animationTime = parOptions.animationTime || 0;
    
    this.canvasElement.width = this.width;
    this.canvasElement.height = this.height;

    this.initContextGL();

    if(this.contextGL) {
      this.initProgram();
      this.initBuffers();
      this.initTextures();
    };
    

/**
 * Draw the next output picture using WebGL
 */
    if (this.type === "yuv420"){
      this.drawNextOuptutPictureGL = function(par) {
        var gl = this.contextGL;
        var texturePosBuffer = this.texturePosBuffer;
        var uTexturePosBuffer = this.uTexturePosBuffer;
        var vTexturePosBuffer = this.vTexturePosBuffer;
        
        var yTextureRef = this.yTextureRef;
        var uTextureRef = this.uTextureRef;
        var vTextureRef = this.vTextureRef;
        
        var yData = par.yData;
        var uData = par.uData;
        var vData = par.vData;
        
        var width = this.width;
        var height = this.height;
        
        var yDataPerRow = par.yDataPerRow || width;
        var yRowCnt     = par.yRowCnt || height;
        
        var uDataPerRow = par.uDataPerRow || (width / 2);
        var uRowCnt     = par.uRowCnt || (height / 2);
        
        var vDataPerRow = par.vDataPerRow || uDataPerRow;
        var vRowCnt     = par.vRowCnt || uRowCnt;
        
        gl.viewport(0, 0, width, height);

        var tTop = 0;
        var tLeft = 0;
        var tBottom = height / yRowCnt;
        var tRight = width / yDataPerRow;
        var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);

        gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
        
        if (this.customYUV444){
          tBottom = height / uRowCnt;
          tRight = width / uDataPerRow;
        }else{
          tBottom = (height / 2) / uRowCnt;
          tRight = (width / 2) / uDataPerRow;
        };
        var uTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);

        gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, uTexturePosValues, gl.DYNAMIC_DRAW);
        
        
        if (this.customYUV444){
          tBottom = height / vRowCnt;
          tRight = width / vDataPerRow;
        }else{
          tBottom = (height / 2) / vRowCnt;
          tRight = (width / 2) / vDataPerRow;
        };
        var vTexturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);

        gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vTexturePosValues, gl.DYNAMIC_DRAW);
        

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, yDataPerRow, yRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);

        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, uDataPerRow, uRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uData);

        gl.activeTexture(gl.TEXTURE2);
        gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, vDataPerRow, vRowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, vData);

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 
      };
      
    }else if (this.type === "yuv422"){
      this.drawNextOuptutPictureGL = function(par) {
        var gl = this.contextGL;
        var texturePosBuffer = this.texturePosBuffer;
        
        var textureRef = this.textureRef;
        
        var data = par.data;
        
        var width = this.width;
        var height = this.height;
        
        var dataPerRow = par.dataPerRow || (width * 2);
        var rowCnt     = par.rowCnt || height;

        gl.viewport(0, 0, width, height);

        var tTop = 0;
        var tLeft = 0;
        var tBottom = height / rowCnt;
        var tRight = width / (dataPerRow / 2);
        var texturePosValues = new Float32Array([tRight, tTop, tLeft, tTop, tRight, tBottom, tLeft, tBottom]);

        gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, texturePosValues, gl.DYNAMIC_DRAW);
        
        gl.uniform2f(gl.getUniformLocation(this.shaderProgram, 'resolution'), dataPerRow, height);
        
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, textureRef);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dataPerRow, rowCnt, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, data);

        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); 
      };
    };
    
  };

  /**
 * Returns true if the canvas supports WebGL
 */
  YUVCanvas.prototype.isWebGL = function() {
    return this.contextGL;
  };

  /**
 * Create the GL context from the canvas element
 */
  YUVCanvas.prototype.initContextGL = function() {
    var canvas = this.canvasElement;
    var gl = null;

    var validContextNames = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"];
    var nameIndex = 0;

    while(!gl && nameIndex < validContextNames.length) {
      var contextName = validContextNames[nameIndex];

      try {
        if (this.contextOptions){
          gl = canvas.getContext(contextName, this.contextOptions);
        }else{
          gl = canvas.getContext(contextName);
        };
      } catch (e) {
        gl = null;
      }

      if(!gl || typeof gl.getParameter !== "function") {
        gl = null;
      }    

      ++nameIndex;
    };

    this.contextGL = gl;
  };

/**
 * Initialize GL shader program
 */
YUVCanvas.prototype.initProgram = function() {
    var gl = this.contextGL;

  // vertex shader is the same for all types
  var vertexShaderScript;
  var fragmentShaderScript;
  
  if (this.type === "yuv420"){

    vertexShaderScript = [
      'attribute vec4 vertexPos;',
      'attribute vec4 texturePos;',
      'attribute vec4 uTexturePos;',
      'attribute vec4 vTexturePos;',
      'varying vec2 textureCoord;',
      'varying vec2 uTextureCoord;',
      'varying vec2 vTextureCoord;',

      'void main()',
      '{',
      '  gl_Position = vertexPos;',
      '  textureCoord = texturePos.xy;',
      '  uTextureCoord = uTexturePos.xy;',
      '  vTextureCoord = vTexturePos.xy;',
      '}'
    ].join('\n');
    
    fragmentShaderScript = [
      'precision highp float;',
      'varying highp vec2 textureCoord;',
      'varying highp vec2 uTextureCoord;',
      'varying highp vec2 vTextureCoord;',
      'uniform sampler2D ySampler;',
      'uniform sampler2D uSampler;',
      'uniform sampler2D vSampler;',
      'uniform mat4 YUV2RGB;',

      'void main(void) {',
      '  highp float y = texture2D(ySampler,  textureCoord).r;',
      '  highp float u = texture2D(uSampler,  uTextureCoord).r;',
      '  highp float v = texture2D(vSampler,  vTextureCoord).r;',
      '  gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
      '}'
    ].join('\n');
    
  }else if (this.type === "yuv422"){
    vertexShaderScript = [
      'attribute vec4 vertexPos;',
      'attribute vec4 texturePos;',
      'varying vec2 textureCoord;',

      'void main()',
      '{',
      '  gl_Position = vertexPos;',
      '  textureCoord = texturePos.xy;',
      '}'
    ].join('\n');
    
    fragmentShaderScript = [
      'precision highp float;',
      'varying highp vec2 textureCoord;',
      'uniform sampler2D sampler;',
      'uniform highp vec2 resolution;',
      'uniform mat4 YUV2RGB;',

      'void main(void) {',
      
      '  highp float texPixX = 1.0 / resolution.x;',
      '  highp float logPixX = 2.0 / resolution.x;', // half the resolution of the texture
      '  highp float logHalfPixX = 4.0 / resolution.x;', // half of the logical resolution so every 4th pixel
      '  highp float steps = floor(textureCoord.x / logPixX);',
      '  highp float uvSteps = floor(textureCoord.x / logHalfPixX);',
      '  highp float y = texture2D(sampler, vec2((logPixX * steps) + texPixX, textureCoord.y)).r;',
      '  highp float u = texture2D(sampler, vec2((logHalfPixX * uvSteps), textureCoord.y)).r;',
      '  highp float v = texture2D(sampler, vec2((logHalfPixX * uvSteps) + texPixX + texPixX, textureCoord.y)).r;',
      
      //'  highp float y = texture2D(sampler,  textureCoord).r;',
      //'  gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
      '  gl_FragColor = vec4(y, u, v, 1.0) * YUV2RGB;',
      '}'
    ].join('\n');
  };

  var YUV2RGB = [];

  if (this.conversionType == "rec709") {
      // ITU-T Rec. 709
      YUV2RGB = [
          1.16438,  0.00000,  1.79274, -0.97295,
          1.16438, -0.21325, -0.53291,  0.30148,
          1.16438,  2.11240,  0.00000, -1.13340,
          0, 0, 0, 1,
      ];
  } else {
      // assume ITU-T Rec. 601
      YUV2RGB = [
          1.16438,  0.00000,  1.59603, -0.87079,
          1.16438, -0.39176, -0.81297,  0.52959,
          1.16438,  2.01723,  0.00000, -1.08139,
          0, 0, 0, 1
      ];
  };

  var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader, vertexShaderScript);
  gl.compileShader(vertexShader);
  if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
    console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
  }

  var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader, fragmentShaderScript);
  gl.compileShader(fragmentShader);
  if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
    console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
  }

  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
  }

  gl.useProgram(program);

  var YUV2RGBRef = gl.getUniformLocation(program, 'YUV2RGB');
  gl.uniformMatrix4fv(YUV2RGBRef, false, YUV2RGB);

  this.shaderProgram = program;
};

/**
 * Initialize vertex buffers and attach to shader program
 */
YUVCanvas.prototype.initBuffers = function() {
  var gl = this.contextGL;
  var program = this.shaderProgram;

  var vertexPosBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);

  var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
  gl.enableVertexAttribArray(vertexPosRef);
  gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
  
  if (this.animationTime){
    
    var animationTime = this.animationTime;
    var timePassed = 0;
    var stepTime = 15;
  
    var aniFun = function(){
      
      timePassed += stepTime;
      var mul = ( 1 * timePassed ) / animationTime;
      
      if (timePassed >= animationTime){
        mul = 1;
      }else{
        setTimeout(aniFun, stepTime);
      };
      
      var neg = -1 * mul;
      var pos = 1 * mul;

      var vertexPosBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([pos, pos, neg, pos, pos, neg, neg, neg]), gl.STATIC_DRAW);

      var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
      gl.enableVertexAttribArray(vertexPosRef);
      gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
      
      try{
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      }catch(e){};

    };
    aniFun();
    
  };

  

  var texturePosBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);

  var texturePosRef = gl.getAttribLocation(program, 'texturePos');
  gl.enableVertexAttribArray(texturePosRef);
  gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);

  this.texturePosBuffer = texturePosBuffer;

  if (this.type === "yuv420"){
    var uTexturePosBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, uTexturePosBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);

    var uTexturePosRef = gl.getAttribLocation(program, 'uTexturePos');
    gl.enableVertexAttribArray(uTexturePosRef);
    gl.vertexAttribPointer(uTexturePosRef, 2, gl.FLOAT, false, 0, 0);

    this.uTexturePosBuffer = uTexturePosBuffer;
    
    
    var vTexturePosBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vTexturePosBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.STATIC_DRAW);

    var vTexturePosRef = gl.getAttribLocation(program, 'vTexturePos');
    gl.enableVertexAttribArray(vTexturePosRef);
    gl.vertexAttribPointer(vTexturePosRef, 2, gl.FLOAT, false, 0, 0);

    this.vTexturePosBuffer = vTexturePosBuffer;
  };

};

/**
 * Initialize GL textures and attach to shader program
 */
YUVCanvas.prototype.initTextures = function() {
  var gl = this.contextGL;
  var program = this.shaderProgram;

  if (this.type === "yuv420"){

    var yTextureRef = this.initTexture();
    var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
    gl.uniform1i(ySamplerRef, 0);
    this.yTextureRef = yTextureRef;

    var uTextureRef = this.initTexture();
    var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
    gl.uniform1i(uSamplerRef, 1);
    this.uTextureRef = uTextureRef;

    var vTextureRef = this.initTexture();
    var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
    gl.uniform1i(vSamplerRef, 2);
    this.vTextureRef = vTextureRef;
    
  }else if (this.type === "yuv422"){
    // only one texture for 422
    var textureRef = this.initTexture();
    var samplerRef = gl.getUniformLocation(program, 'sampler');
    gl.uniform1i(samplerRef, 0);
    this.textureRef = textureRef;

  };
};

/**
 * Create and configure a single texture
 */
YUVCanvas.prototype.initTexture = function() {
    var gl = this.contextGL;

    var textureRef = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, textureRef);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.bindTexture(gl.TEXTURE_2D, null);

    return textureRef;
};

/**
 * Draw picture data to the canvas.
 * If this object is using WebGL, the data must be an I420 formatted ArrayBuffer,
 * Otherwise, data must be an RGBA formatted ArrayBuffer.
 */
YUVCanvas.prototype.drawNextOutputPicture = function(width, height, croppingParams, data) {
    var gl = this.contextGL;

    if(gl) {
        this.drawNextOuptutPictureGL(width, height, croppingParams, data);
    } else {
        this.drawNextOuptutPictureRGBA(width, height, croppingParams, data);
    }
};



/**
 * Draw next output picture using ARGB data on a 2d canvas.
 */
YUVCanvas.prototype.drawNextOuptutPictureRGBA = function(width, height, croppingParams, data) {
    var canvas = this.canvasElement;

    var croppingParams = null;

    var argbData = data;

    var ctx = canvas.getContext('2d');
    var imageData = ctx.getImageData(0, 0, width, height);
    imageData.data.set(argbData);

    if(croppingParams === null) {
        ctx.putImageData(imageData, 0, 0);
    } else {
        ctx.putImageData(imageData, -croppingParams.left, -croppingParams.top, 0, 0, croppingParams.width, croppingParams.height);
    }
};
  
  return YUVCanvas;
  
}));

var Module;if(!Module)Module=(typeof Module!=="undefined"?Module:null)||{};var moduleOverrides={};for(var key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;if(Module["ENVIRONMENT"]){if(Module["ENVIRONMENT"]==="WEB"){ENVIRONMENT_IS_WEB=true}else if(Module["ENVIRONMENT"]==="WORKER"){ENVIRONMENT_IS_WORKER=true}else if(Module["ENVIRONMENT"]==="NODE"){ENVIRONMENT_IS_NODE=true}else if(Module["ENVIRONMENT"]==="SHELL"){ENVIRONMENT_IS_SHELL=true}else{throw new Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.")}}else{ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER}if(ENVIRONMENT_IS_NODE){if(!Module["print"])Module["print"]=console.log;if(!Module["printErr"])Module["printErr"]=console.warn;var nodeFS;var nodePath;Module["read"]=function read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);var ret=nodeFS["readFileSync"](filename);if(!ret&&filename!=nodePath["resolve"](filename)){filename=path.join(__dirname,"..","src",filename);ret=nodeFS["readFileSync"](filename)}if(ret&&!binary)ret=ret.toString();return ret};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};Module["load"]=function load(f){globalEval(read(f))};if(!Module["thisProgram"]){if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}else{Module["thisProgram"]="unknown-program"}}Module["arguments"]=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",(function(ex){if(!(ex instanceof ExitStatus)){throw ex}}));Module["inspect"]=(function(){return"[Emscripten Module object]"})}else if(ENVIRONMENT_IS_SHELL){if(!Module["print"])Module["print"]=print;if(typeof printErr!="undefined")Module["printErr"]=printErr;if(typeof read!="undefined"){Module["read"]=read}else{Module["read"]=function read(){throw"no read() available (jsc?)"}}Module["readBinary"]=function readBinary(f){if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}var data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};Module["readAsync"]=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response)}else{onerror()}};xhr.onerror=onerror;xhr.send(null)};if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof console!=="undefined"){if(!Module["print"])Module["print"]=function print(x){console.log(x)};if(!Module["printErr"])Module["printErr"]=function printErr(x){console.warn(x)}}else{var TRY_USE_DUMP=false;if(!Module["print"])Module["print"]=TRY_USE_DUMP&&typeof dump!=="undefined"?(function(x){dump(x)}):(function(x){})}if(ENVIRONMENT_IS_WORKER){Module["load"]=importScripts}if(typeof Module["setWindowTitle"]==="undefined"){Module["setWindowTitle"]=(function(title){document.title=title})}}else{throw"Unknown runtime environment. Where are we?"}function globalEval(x){eval.call(null,x)}if(!Module["load"]&&Module["read"]){Module["load"]=function load(f){globalEval(Module["read"](f))}}if(!Module["print"]){Module["print"]=(function(){})}if(!Module["printErr"]){Module["printErr"]=Module["print"]}if(!Module["arguments"]){Module["arguments"]=[]}if(!Module["thisProgram"]){Module["thisProgram"]="./this.program"}Module.print=Module["print"];Module.printErr=Module["printErr"];Module["preRun"]=[];Module["postRun"]=[];for(var key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=undefined;var Runtime={setTempRet0:(function(value){tempRet0=value}),getTempRet0:(function(){return tempRet0}),stackSave:(function(){return STACKTOP}),stackRestore:(function(stackTop){STACKTOP=stackTop}),getNativeTypeSize:(function(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return Runtime.QUANTUM_SIZE}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0);return bits/8}else{return 0}}}}),getNativeFieldSize:(function(type){return Math.max(Runtime.getNativeTypeSize(type),Runtime.QUANTUM_SIZE)}),STACK_ALIGN:16,prepVararg:(function(ptr,type){if(type==="double"||type==="i64"){if(ptr&7){assert((ptr&7)===4);ptr+=4}}else{assert((ptr&3)===0)}return ptr}),getAlignSize:(function(type,size,vararg){if(!vararg&&(type=="i64"||type=="double"))return 8;if(!type)return Math.min(size,8);return Math.min(size||(type?Runtime.getNativeFieldSize(type):0),Runtime.QUANTUM_SIZE)}),dynCall:(function(sig,ptr,args){if(args&&args.length){if(!args.splice)args=Array.prototype.slice.call(args);args.splice(0,0,ptr);return Module["dynCall_"+sig].apply(null,args)}else{return Module["dynCall_"+sig].call(null,ptr)}}),functionPointers:[],addFunction:(function(func){for(var i=0;i<Runtime.functionPointers.length;i++){if(!Runtime.functionPointers[i]){Runtime.functionPointers[i]=func;return 2*(1+i)}}throw"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS."}),removeFunction:(function(index){Runtime.functionPointers[(index-2)/2]=null}),warnOnce:(function(text){if(!Runtime.warnOnce.shown)Runtime.warnOnce.shown={};if(!Runtime.warnOnce.shown[text]){Runtime.warnOnce.shown[text]=1;Module.printErr(text)}}),funcWrappers:{},getFuncWrapper:(function(func,sig){assert(sig);if(!Runtime.funcWrappers[sig]){Runtime.funcWrappers[sig]={}}var sigCache=Runtime.funcWrappers[sig];if(!sigCache[func]){sigCache[func]=function dynCall_wrapper(){return Runtime.dynCall(sig,func,arguments)}}return sigCache[func]}),getCompilerSetting:(function(name){throw"You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work"}),stackAlloc:(function(size){var ret=STACKTOP;STACKTOP=STACKTOP+size|0;STACKTOP=STACKTOP+15&-16;return ret}),staticAlloc:(function(size){var ret=STATICTOP;STATICTOP=STATICTOP+size|0;STATICTOP=STATICTOP+15&-16;return ret}),dynamicAlloc:(function(size){var ret=DYNAMICTOP;DYNAMICTOP=DYNAMICTOP+size|0;DYNAMICTOP=DYNAMICTOP+15&-16;if(DYNAMICTOP>=TOTAL_MEMORY){var success=enlargeMemory();if(!success){DYNAMICTOP=ret;return 0}}return ret}),alignMemory:(function(size,quantum){var ret=size=Math.ceil(size/(quantum?quantum:16))*(quantum?quantum:16);return ret}),makeBigInt:(function(low,high,unsigned){var ret=unsigned?+(low>>>0)+ +(high>>>0)*+4294967296:+(low>>>0)+ +(high|0)*+4294967296;return ret}),GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module["Runtime"]=Runtime;var ABORT=false;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}function getCFunc(ident){var func=Module["_"+ident];if(!func){try{func=eval("_"+ident)}catch(e){}}assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)");return func}var cwrap,ccall;((function(){var JSfuncs={"stackSave":(function(){Runtime.stackSave()}),"stackRestore":(function(){Runtime.stackRestore()}),"arrayToC":(function(arr){var ret=Runtime.stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}),"stringToC":(function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=Runtime.stackAlloc((str.length<<2)+1);writeStringToMemory(str,ret)}return ret})};var toC={"string":JSfuncs["stringToC"],"array":JSfuncs["arrayToC"]};ccall=function ccallFunc(ident,returnType,argTypes,args,opts){var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i<args.length;i++){var converter=toC[argTypes[i]];if(converter){if(stack===0)stack=Runtime.stackSave();cArgs[i]=converter(args[i])}else{cArgs[i]=args[i]}}}var ret=func.apply(null,cArgs);if(returnType==="string")ret=Pointer_stringify(ret);if(stack!==0){if(opts&&opts.async){EmterpreterAsync.asyncFinalizers.push((function(){Runtime.stackRestore(stack)}));return}Runtime.stackRestore(stack)}return ret};var sourceRegex=/^function\s*[a-zA-Z$_0-9]*\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/;function parseJSFunc(jsfunc){var parsed=jsfunc.toString().match(sourceRegex).slice(1);return{arguments:parsed[0],body:parsed[1],returnValue:parsed[2]}}var JSsource=null;function ensureJSsource(){if(!JSsource){JSsource={};for(var fun in JSfuncs){if(JSfuncs.hasOwnProperty(fun)){JSsource[fun]=parseJSFunc(JSfuncs[fun])}}}}cwrap=function cwrap(ident,returnType,argTypes){argTypes=argTypes||[];var cfunc=getCFunc(ident);var numericArgs=argTypes.every((function(type){return type==="number"}));var numericRet=returnType!=="string";if(numericRet&&numericArgs){return cfunc}var argNames=argTypes.map((function(x,i){return"$"+i}));var funcstr="(function("+argNames.join(",")+") {";var nargs=argTypes.length;if(!numericArgs){ensureJSsource();funcstr+="var stack = "+JSsource["stackSave"].body+";";for(var i=0;i<nargs;i++){var arg=argNames[i],type=argTypes[i];if(type==="number")continue;var convertCode=JSsource[type+"ToC"];funcstr+="var "+convertCode.arguments+" = "+arg+";";funcstr+=convertCode.body+";";funcstr+=arg+"=("+convertCode.returnValue+");"}}var cfuncname=parseJSFunc((function(){return cfunc})).returnValue;funcstr+="var ret = "+cfuncname+"("+argNames.join(",")+");";if(!numericRet){var strgfy=parseJSFunc((function(){return Pointer_stringify})).returnValue;funcstr+="ret = "+strgfy+"(ret);"}if(!numericArgs){ensureJSsource();funcstr+=JSsource["stackRestore"].body.replace("()","(stack)")+";"}funcstr+="return ret})";return eval(funcstr)}}))();Module["ccall"]=ccall;Module["cwrap"]=cwrap;function setValue(ptr,value,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math_abs(tempDouble)>=+1?tempDouble>+0?(Math_min(+Math_floor(tempDouble/+4294967296),+4294967295)|0)>>>0:~~+Math_ceil((tempDouble- +(~~tempDouble>>>0))/+4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;default:abort("invalid type for setValue: "+type)}}Module["setValue"]=setValue;function getValue(ptr,type,noSafe){type=type||"i8";if(type.charAt(type.length-1)==="*")type="i32";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];default:abort("invalid type for setValue: "+type)}return null}Module["getValue"]=getValue;var ALLOC_NORMAL=0;var ALLOC_STACK=1;var ALLOC_STATIC=2;var ALLOC_DYNAMIC=3;var ALLOC_NONE=4;Module["ALLOC_NORMAL"]=ALLOC_NORMAL;Module["ALLOC_STACK"]=ALLOC_STACK;Module["ALLOC_STATIC"]=ALLOC_STATIC;Module["ALLOC_DYNAMIC"]=ALLOC_DYNAMIC;Module["ALLOC_NONE"]=ALLOC_NONE;function allocate(slab,types,allocator,ptr){var zeroinit,size;if(typeof slab==="number"){zeroinit=true;size=slab}else{zeroinit=false;size=slab.length}var singleType=typeof types==="string"?types:null;var ret;if(allocator==ALLOC_NONE){ret=ptr}else{ret=[typeof _malloc==="function"?_malloc:Runtime.staticAlloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][allocator===undefined?ALLOC_STATIC:allocator](Math.max(size,singleType?1:types.length))}if(zeroinit){var ptr=ret,stop;assert((ret&3)==0);stop=ret+(size&~3);for(;ptr<stop;ptr+=4){HEAP32[ptr>>2]=0}stop=ret+size;while(ptr<stop){HEAP8[ptr++>>0]=0}return ret}if(singleType==="i8"){if(slab.subarray||slab.slice){HEAPU8.set(slab,ret)}else{HEAPU8.set(new Uint8Array(slab),ret)}return ret}var i=0,type,typeSize,previousType;while(i<size){var curr=slab[i];if(typeof curr==="function"){curr=Runtime.getFunctionIndex(curr)}type=singleType||types[i];if(type===0){i++;continue}if(type=="i64")type="i32";setValue(ret+i,curr,type);if(previousType!==type){typeSize=Runtime.getNativeTypeSize(type);previousType=type}i+=typeSize}return ret}Module["allocate"]=allocate;function getMemory(size){if(!staticSealed)return Runtime.staticAlloc(size);if(typeof _sbrk!=="undefined"&&!_sbrk.called||!runtimeInitialized)return Runtime.dynamicAlloc(size);return _malloc(size)}Module["getMemory"]=getMemory;function Pointer_stringify(ptr,length){if(length===0||!ptr)return"";var hasUtf=0;var t;var i=0;while(1){t=HEAPU8[ptr+i>>0];hasUtf|=t;if(t==0&&!length)break;i++;if(length&&i==length)break}if(!length)length=i;var ret="";if(hasUtf<128){var MAX_CHUNK=1024;var curr;while(length>0){curr=String.fromCharCode.apply(String,HEAPU8.subarray(ptr,ptr+Math.min(length,MAX_CHUNK)));ret=ret?ret+curr:curr;ptr+=MAX_CHUNK;length-=MAX_CHUNK}return ret}return Module["UTF8ToString"](ptr)}Module["Pointer_stringify"]=Pointer_stringify;function AsciiToString(ptr){var str="";while(1){var ch=HEAP8[ptr++>>0];if(!ch)return str;str+=String.fromCharCode(ch)}}Module["AsciiToString"]=AsciiToString;function stringToAscii(str,outPtr){return writeAsciiToMemory(str,outPtr,false)}Module["stringToAscii"]=stringToAscii;function UTF8ArrayToString(u8Array,idx){var u0,u1,u2,u3,u4,u5;var str="";while(1){u0=u8Array[idx++];if(!u0)return str;if(!(u0&128)){str+=String.fromCharCode(u0);continue}u1=u8Array[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}u2=u8Array[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u3=u8Array[idx++]&63;if((u0&248)==240){u0=(u0&7)<<18|u1<<12|u2<<6|u3}else{u4=u8Array[idx++]&63;if((u0&252)==248){u0=(u0&3)<<24|u1<<18|u2<<12|u3<<6|u4}else{u5=u8Array[idx++]&63;u0=(u0&1)<<30|u1<<24|u2<<18|u3<<12|u4<<6|u5}}}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}}Module["UTF8ArrayToString"]=UTF8ArrayToString;function UTF8ToString(ptr){return UTF8ArrayToString(HEAPU8,ptr)}Module["UTF8ToString"]=UTF8ToString;function stringToUTF8Array(str,outU8Array,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127){if(outIdx>=endIdx)break;outU8Array[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;outU8Array[outIdx++]=192|u>>6;outU8Array[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;outU8Array[outIdx++]=224|u>>12;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else if(u<=2097151){if(outIdx+3>=endIdx)break;outU8Array[outIdx++]=240|u>>18;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else if(u<=67108863){if(outIdx+4>=endIdx)break;outU8Array[outIdx++]=248|u>>24;outU8Array[outIdx++]=128|u>>18&63;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}else{if(outIdx+5>=endIdx)break;outU8Array[outIdx++]=252|u>>30;outU8Array[outIdx++]=128|u>>24&63;outU8Array[outIdx++]=128|u>>18&63;outU8Array[outIdx++]=128|u>>12&63;outU8Array[outIdx++]=128|u>>6&63;outU8Array[outIdx++]=128|u&63}}outU8Array[outIdx]=0;return outIdx-startIdx}Module["stringToUTF8Array"]=stringToUTF8Array;function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}Module["stringToUTF8"]=stringToUTF8;function lengthBytesUTF8(str){var len=0;for(var i=0;i<str.length;++i){var u=str.charCodeAt(i);if(u>=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127){++len}else if(u<=2047){len+=2}else if(u<=65535){len+=3}else if(u<=2097151){len+=4}else if(u<=67108863){len+=5}else{len+=6}}return len}Module["lengthBytesUTF8"]=lengthBytesUTF8;function demangle(func){var hasLibcxxabi=!!Module["___cxa_demangle"];if(hasLibcxxabi){try{var buf=_malloc(func.length);writeStringToMemory(func.substr(1),buf);var status=_malloc(4);var ret=Module["___cxa_demangle"](buf,0,0,status);if(getValue(status,"i32")===0&&ret){return Pointer_stringify(ret)}}catch(e){return func}finally{if(buf)_free(buf);if(status)_free(status);if(ret)_free(ret)}}Runtime.warnOnce("warning: build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling");return func}function demangleAll(text){return text.replace(/__Z[\w\d_]+/g,(function(x){var y=demangle(x);return x===y?x:x+" ["+y+"]"}))}function jsStackTrace(){var err=new Error;if(!err.stack){try{throw new Error(0)}catch(e){err=e}if(!err.stack){return"(no stack trace available)"}}return err.stack.toString()}function stackTrace(){return demangleAll(jsStackTrace())}Module["stackTrace"]=stackTrace;var PAGE_SIZE=4096;function alignMemoryPage(x){if(x%4096>0){x+=4096-x%4096}return x}var HEAP;var buffer;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferViews(){Module["HEAP8"]=HEAP8=new Int8Array(buffer);Module["HEAP16"]=HEAP16=new Int16Array(buffer);Module["HEAP32"]=HEAP32=new Int32Array(buffer);Module["HEAPU8"]=HEAPU8=new Uint8Array(buffer);Module["HEAPU16"]=HEAPU16=new Uint16Array(buffer);Module["HEAPU32"]=HEAPU32=new Uint32Array(buffer);Module["HEAPF32"]=HEAPF32=new Float32Array(buffer);Module["HEAPF64"]=HEAPF64=new Float64Array(buffer)}var STATIC_BASE=0,STATICTOP=0,staticSealed=false;var STACK_BASE=0,STACKTOP=0,STACK_MAX=0;var DYNAMIC_BASE=0,DYNAMICTOP=0;function abortOnCannotGrowMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with  -s TOTAL_MEMORY=X  with X higher than the current value "+TOTAL_MEMORY+", (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which adjusts the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ")}function enlargeMemory(){abortOnCannotGrowMemory()}var TOTAL_STACK=Module["TOTAL_STACK"]||5242880;var TOTAL_MEMORY=Module["TOTAL_MEMORY"]||67108864;var totalMemory=64*1024;while(totalMemory<TOTAL_MEMORY||totalMemory<2*TOTAL_STACK){if(totalMemory<16*1024*1024){totalMemory*=2}else{totalMemory+=16*1024*1024}}if(totalMemory!==TOTAL_MEMORY){TOTAL_MEMORY=totalMemory}if(Module["buffer"]){buffer=Module["buffer"]}else{buffer=new ArrayBuffer(TOTAL_MEMORY)}updateGlobalBufferViews();HEAP32[0]=255;if(HEAPU8[0]!==255||HEAPU8[3]!==0)throw"Typed arrays 2 must be run on a little-endian system";Module["HEAP"]=HEAP;Module["buffer"]=buffer;Module["HEAP8"]=HEAP8;Module["HEAP16"]=HEAP16;Module["HEAP32"]=HEAP32;Module["HEAPU8"]=HEAPU8;Module["HEAPU16"]=HEAPU16;Module["HEAPU32"]=HEAPU32;Module["HEAPF32"]=HEAPF32;Module["HEAPF64"]=HEAPF64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback();continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){Runtime.dynCall("v",func)}else{Runtime.dynCall("vi",func,[callback.arg])}}else{func(callback.arg===undefined?null:callback.arg)}}}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){if(runtimeInitialized)return;runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__);runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}Module["addOnPreRun"]=addOnPreRun;function addOnInit(cb){__ATINIT__.unshift(cb)}Module["addOnInit"]=addOnInit;function addOnPreMain(cb){__ATMAIN__.unshift(cb)}Module["addOnPreMain"]=addOnPreMain;function addOnExit(cb){__ATEXIT__.unshift(cb)}Module["addOnExit"]=addOnExit;function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}Module["addOnPostRun"]=addOnPostRun;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}Module["intArrayFromString"]=intArrayFromString;function intArrayToString(array){var ret=[];for(var i=0;i<array.length;i++){var chr=array[i];if(chr>255){chr&=255}ret.push(String.fromCharCode(chr))}return ret.join("")}Module["intArrayToString"]=intArrayToString;function writeStringToMemory(string,buffer,dontAddNull){var array=intArrayFromString(string,dontAddNull);var i=0;while(i<array.length){var chr=array[i];HEAP8[buffer+i>>0]=chr;i=i+1}}Module["writeStringToMemory"]=writeStringToMemory;function writeArrayToMemory(array,buffer){for(var i=0;i<array.length;i++){HEAP8[buffer++>>0]=array[i]}}Module["writeArrayToMemory"]=writeArrayToMemory;function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i<str.length;++i){HEAP8[buffer++>>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}Module["writeAsciiToMemory"]=writeAsciiToMemory;if(!Math["imul"]||Math["imul"](4294967295,5)!==-5)Math["imul"]=function imul(a,b){var ah=a>>>16;var al=a&65535;var bh=b>>>16;var bl=b&65535;return al*bl+(ah*bl+al*bh<<16)|0};Math.imul=Math["imul"];if(!Math["clz32"])Math["clz32"]=(function(x){x=x>>>0;for(var i=0;i<32;i++){if(x&1<<31-i)return i}return 32});Math.clz32=Math["clz32"];var Math_abs=Math.abs;var Math_cos=Math.cos;var Math_sin=Math.sin;var Math_tan=Math.tan;var Math_acos=Math.acos;var Math_asin=Math.asin;var Math_atan=Math.atan;var Math_atan2=Math.atan2;var Math_exp=Math.exp;var Math_log=Math.log;var Math_sqrt=Math.sqrt;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_pow=Math.pow;var Math_imul=Math.imul;var Math_fround=Math.fround;var Math_min=Math.min;var Math_clz32=Math.clz32;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}Module["addRunDependency"]=addRunDependency;function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["removeRunDependency"]=removeRunDependency;Module["preloadedImages"]={};Module["preloadedAudios"]={};var ASM_CONSTS=[];STATIC_BASE=8;STATICTOP=STATIC_BASE+190416;__ATINIT__.push();allocate([13,227,0,0,20,227,0,0,108,6,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,0,0,0,0,0,0,27,227,0,0,27,227,0,0,112,6,3,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,64,0,0,0,0,0,0,0,0,43,227,0,0,53,227,0,0,100,27,3,0,1,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,240,191,0,0,0,0,0,0,240,63,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,172,23,1,0,174,23,1,0,72,0,0,0,1,0,0,0,64,13,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,198,23,1,0,174,23,1,0,72,0,0,0,1,0,0,0,0,244,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,9,0,0,0,0,0,0,0,201,23,1,0,204,23,1,0,76,0,0,0,1,0,0,0,0,9,61,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,224,24,1,0,0,0,0,0,88,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,255,239,65,59,0,0,0,224,24,1,0,230,24,1,0,240,24,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,224,24,1,0,27,25,1,0,31,25,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,79,25,1,0,84,25,1,0,0,0,0,0,128,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,116,25,1,0,121,25,1,0,0,0,0,0,128,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,137,25,1,0,144,25,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,224,24,1,0,161,25,1,0,165,25,1,0,0,0,0,0,128,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,173,25,1,0,177,25,1,0,0,0,0,0,128,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,207,25,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,224,24,1,0,223,25,1,0,229,25,1,0,0,0,0,0,128,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,224,24,1,0,25,26,1,0,31,26,1,0,0,0,0,0,128,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,224,24,1,0,155,145,1,0,83,26,1,0,0,0,0,0,128,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,224,24,1,0,112,26,1,0,121,26,1,0,0,0,0,0,128,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,224,24,1,0,139,26,1,0,144,26,1,0,0,0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,191,26,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,224,24,1,0,201,26,1,0,205,26,1,0,0,0,0,0,128,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,237,26,1,0,243,26,1,0,0,0,0,0,128,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,6,27,1,0,16,27,1,0,0,0,0,0,128,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,224,24,1,0,32,27,1,0,46,27,1,0,0,0,0,0,128,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,224,24,1,0,106,27,1,0,115,27,1,0,0,0,0,0,128,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,59,0,0,0,224,24,1,0,159,27,1,0,163,27,1,0,0,0,0,0,128,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,214,27,1,0,219,27,1,0,0,0,0,0,128,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,248,27,1,0,253,27,1,0,0,0,0,0,128,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,224,24,1,0,8,28,1,0,23,28,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,224,24,1,0,64,28,1,0,69,28,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,109,28,1,0,116,28,1,0,122,28,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,109,28,1,0,146,28,1,0,157,28,1,0,0,0,0,0,128,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,109,28,1,0,194,28,1,0,207,28,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,109,28,1,0,14,29,1,0,21,29,1,0,0,0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,109,28,1,0,68,29,1,0,76,29,1,0,0,0,0,0,128,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,109,28,1,0,118,29,1,0,129,29,1,0,0,0,0,0,128,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,109,28,1,0,175,29,1,0,187,29,1,0,0,0,0,0,128,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,109,28,1,0,254,29,1,0,8,30,1,0,144,0,0,0,1,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,37,30,1,0,42,30,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,75,30,1,0,80,30,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,113,30,1,0,118,30,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,151,30,1,0,155,30,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,194,30,1,0,199,30,1,0,0,0,0,0,128,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,222,30,1,0,226,30,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,13,31,1,0,17,31,1,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,39,31,1,0,45,31,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,69,31,1,0,72,31,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,93,31,1,0,97,31,1,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,119,31,1,0,123,31,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,145,31,1,0,150,31,1,0,0,0,0,0,128,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,254,29,1,0,173,31,1,0,0,0,0,0,104,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,183,31,1,0,185,31,1,0,136,0,0,0,1,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,221,31,1,0,224,31,1,0,156,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,11,0,0,0,0,0,0,0,0,32,1,0,3,32,1,0,160,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,11,0,0,0,0,0,0,0,32,32,1,0,39,32,1,0,180,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,0,0,0,0,60,32,1,0,0,0,0,0,168,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,0,0,0,0,71,32,1,0,0,0,0,0,172,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,84,32,1,0,0,0,0,0,116,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,90,32,1,0,96,32,1,0,236,1,0,0,4,0,0,0,0,0,0,0,0,0,224,63,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,216,32,1,0,222,32,1,0,240,1,0,0,4,0,0,0,0,0,0,0,0,0,224,63,0,0,0,0,0,0,240,191,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,255,32,1,0,4,33,1,0,244,1,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,240,191,0,0,0,0,0,64,81,64,17,0,0,0,0,0,0,0,40,33,1,0,45,33,1,0,248,1,0,0,1,0,0,0,31,0,0,0,0,0,0,0,0,0,0,0,0,0,240,191,0,0,0,0,0,0,144,64,17,0,0,0,0,0,0,0,81,33,1,0,87,33,1,0,252,1,0,0,1,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,141,33,1,0,144,33,1,0,156,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,191,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,196,33,1,0,206,33,1,0,160,0,0,0,4,0,0,0,0,0,0,0,0,0,244,63,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,240,33,1,0,252,33,1,0,164,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,15,34,1,0,26,34,1,0,168,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,66,34,1,0,69,34,1,0,116,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,95,34,1,0,0,0,0,0,120,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,103,34,1,0,0,0,0,0,124,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,115,34,1,0,0,0,0,0,128,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,126,34,1,0,0,0,0,0,132,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,137,34,1,0,0,0,0,0,136,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,145,34,1,0,0,0,0,0,140,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,153,34,1,0,0,0,0,0,144,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,164,34,1,0,0,0,0,0,148,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,174,34,1,0,0,0,0,0,152,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,185,34,1,0,0,0,0,0,52,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,195,34,1,0,199,34,1,0,164,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,241,34,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,252,34,1,0,8,35,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,67,35,1,0,78,35,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,132,35,1,0,137,35,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,170,35,1,0,181,35,1,0,0,0,0,0,128,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,208,35,1,0,0,0,0,0,0,0,0,0,128,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,212,35,1,0,219,35,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,5,36,1,0,0,0,0,0,0,0,0,0,128,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,17,36,1,0,26,36,1,0,0,0,0,0,128,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,78,36,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,91,36,1,0,108,36,1,0,0,0,0,0,128,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,168,36,1,0,173,36,1,0,0,0,0,0,128,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,224,36,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,236,36,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,244,36,1,0,247,36,1,0,0,0,0,0,128,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,47,37,1,0,53,37,1,0,0,0,0,0,128,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,195,34,1,0,70,37,1,0,77,37,1,0,168,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,27,0,0,0,70,37,1,0,114,37,1,0,119,37,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,70,37,1,0,70,37,1,0,201,37,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,70,37,1,0,24,38,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,70,37,1,0,31,38,1,0,42,38,1,0,0,0,0,0,128,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,70,37,1,0,70,38,1,0,83,38,1,0,0,0,0,0,128,0,0,0,254,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,70,37,1,0,126,38,1,0,136,38,1,0,172,0,0,0,4,0,0,0,0,0,0,0,0,0,244,63,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,170,38,1,0,181,38,1,0,184,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,207,38,1,0,216,38,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,237,38,1,0,247,38,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,33,39,1,0,40,39,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,73,39,1,0,81,39,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,121,39,1,0,132,39,1,0,0,0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,146,39,1,0,154,39,1,0,0,0,0,0,128,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,0,40,1,0,10,40,1,0,0,0,0,0,128,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,55,40,1,0,66,40,1,0,0,0,0,0,128,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,170,38,1,0,128,40,1,0,0,0,0,0,176,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,141,40,1,0,0,0,0,0,176,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,153,40,1,0,164,40,1,0,180,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,201,40,1,0,209,40,1,0,0,2,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,88,64,17,0,0,0,0,0,0,0,1,41,1,0,209,40,1,0,4,2,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,13,41,1,0,209,40,1,0,8,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,26,41,1,0,0,0,0,0,16,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,44,41,1,0,209,40,1,0,24,2,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,192,0,0,0,0,0,192,95,64,17,0,0,0,0,0,0,0,50,41,1,0,58,41,1,0,28,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,123,41,1,0,131,41,1,0,32,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,231,41,1,0,239,41,1,0,12,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,21,42,1,0,209,40,1,0,36,2,0,0,4,0,0,0,0,0,0,0,0,0,240,63,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,41,42,1,0,51,42,1,0,184,0,0,0,4,0,0,0,154,153,153,153,153,153,233,191,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,85,42,1,0,95,42,1,0,188,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,129,42,1,0,209,40,1,0,40,2,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,142,42,1,0,146,42,1,0,16,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,160,42,1,0,165,42,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,197,42,1,0,205,42,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,218,42,1,0,222,42,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,239,42,1,0,0,0,0,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,243,42,1,0,0,0,0,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,251,42,1,0,0,43,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,142,42,1,0,23,43,1,0,33,43,1,0,192,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,83,43,1,0,94,43,1,0,196,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,122,43,1,0,133,43,1,0,200,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,160,43,1,0,167,43,1,0,204,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,181,43,1,0,191,43,1,0,208,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,239,43,1,0,244,43,1,0,20,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,160,42,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,218,42,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,15,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,22,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,32,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,243,42,1,0,0,0,0,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,36,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,40,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,50,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,64,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,17,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,76,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,87,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,23,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,99,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,103,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,108,44,1,0,116,44,1,0,0,0,0,0,128,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,151,44,1,0,157,44,1,0,0,0,0,0,128,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,181,44,1,0,0,0,0,0,0,0,0,0,128,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,239,43,1,0,192,44,1,0,0,0,0,0,212,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,204,44,1,0,207,44,1,0,172,2,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,204,44,1,0,238,44,1,0,248,44,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,204,44,1,0,35,45,1,0,43,45,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,204,44,1,0,85,45,1,0,97,45,1,0,0,0,0,0,128,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,204,44,1,0,138,45,1,0,0,0,0,0,24,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,160,45,1,0,165,45,1,0,216,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,160,45,1,0,183,45,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,160,45,1,0,188,45,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,160,45,1,0,194,45,1,0,0,0,0,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,160,45,1,0,201,45,1,0,208,45,1,0,224,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,64,17,0,0,0,0,0,0,0,207,132,1,0,228,45,1,0,176,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,59,0,0,0,207,132,1,0,254,45,1,0,3,46,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,16,46,1,0,19,46,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,207,132,1,0,237,38,1,0,0,0,0,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,32,46,1,0,40,46,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,61,46,1,0,64,46,1,0,0,0,0,0,128,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,102,46,1,0,105,46,1,0,0,0,0,0,128,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,119,46,1,0,0,0,0,0,0,0,0,0,128,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,129,46,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,144,46,1,0,0,0,0,0,0,0,0,0,128,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,149,46,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,159,46,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,163,46,1,0,166,46,1,0,0,0,0,0,128,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,184,46,1,0,189,46,1,0,0,0,0,0,128,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,234,46,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,239,46,1,0,246,46,1,0,0,0,0,0,128,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,57,47,1,0,69,47,1,0,0,0,0,0,128,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,91,47,1,0,99,47,1,0,0,0,0,0,128,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,207,132,1,0,126,47,1,0,137,47,1,0,0,0,0,0,128,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,207,132,1,0,158,47,1,0,163,47,1,0,0,0,0,0,128,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,26,0,0,0,207,132,1,0,188,47,1,0,194,47,1,0,180,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,18,0,0,0,238,47,1,0,247,47,1,0,250,47,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,238,47,1,0,141,33,1,0,28,48,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,238,47,1,0,62,48,1,0,65,48,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,238,47,1,0,100,48,1,0,104,48,1,0,232,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,142,48,1,0,149,48,1,0,236,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,177,48,1,0,183,48,1,0,240,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,211,48,1,0,220,48,1,0,244,0,0,0,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,252,48,1,0,5,49,1,0,248,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,47,49,1,0,57,49,1,0,252,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,109,49,1,0,115,49,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,137,49,1,0,144,49,1,0,4,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,183,49,1,0,187,49,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,231,49,1,0,235,49,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,1,50,1,0,6,50,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,142,42,1,0,55,50,1,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,139,26,1,0,99,50,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,155,50,1,0,159,50,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,195,50,1,0,198,50,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,37,30,1,0,254,68,1,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,228,50,1,0,233,50,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,14,51,1,0,19,51,1,0,0,0,0,0,128,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,55,51,1,0,60,51,1,0,0,0,0,0,128,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,104,51,1,0,0,0,0,0,0,0,0,0,128,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,111,51,1,0,0,0,0,0,0,0,0,0,128],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);allocate([1,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,118,51,1,0,131,51,1,0,8,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,182,51,1,0,187,51,1,0,12,1,0,0,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,221,51,1,0,0,0,0,0,16,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,239,51,1,0,248,51,1,0,20,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,42,52,1,0,48,52,1,0,24,1,0,0,1,0,0,0,63,66,15,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,65,52,1,0,71,52,1,0,28,1,0,0,1,0,0,0,63,66,15,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,88,52,1,0,0,0,0,0,80,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,103,52,1,0,0,0,0,0,56,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,103,52,1,0,109,52,1,0,113,52,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,103,52,1,0,0,32,1,0,151,52,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,103,52,1,0,168,52,1,0,172,52,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,103,52,1,0,190,52,1,0,194,52,1,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,103,52,1,0,211,52,1,0,219,52,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,103,52,1,0,239,52,1,0,247,52,1,0,60,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,5,53,1,0,0,0,0,0,32,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,17,53,1,0,0,0,0,0,36,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,35,53,1,0,39,53,1,0,40,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,17,0,0,0,35,53,1,0,15,44,1,0,89,53,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,35,53,1,0,109,53,1,0,114,53,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,35,53,1,0,195,50,1,0,130,53,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,35,53,1,0,155,53,1,0,0,0,0,0,56,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,172,53,1,0,185,53,1,0,52,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,208,53,1,0,209,40,1,0,64,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,213,53,1,0,209,40,1,0,68,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,218,53,1,0,221,53,1,0,56,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,237,53,1,0,255,53,1,0,52,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,109,28,1,0,0,0,0,0,92,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,255,239,65,27,0,0,0,109,28,1,0,180,132,1,0,0,0,0,0,100,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,79,54,1,0,0,0,0,0,40,3,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,27,0,0,0,79,54,1,0,160,42,1,0,87,54,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,79,54,1,0,134,54,1,0,147,54,1,0,60,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,175,54,1,0,188,54,1,0,64,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,209,54,1,0,212,54,1,0,68,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,192,0,0,0,0,0,0,48,64,17,0,0,0,0,0,0,0,231,54,1,0,237,54,1,0,68,3,0,0,1,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,249,54,1,0,2,55,1,0,72,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,0,0,0,0,57,55,1,0,69,55,1,0,76,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,0,0,0,0,127,55,1,0,0,0,0,0,72,3,0,0,1,0,0,0,157,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,127,55,1,0,135,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,157,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,127,55,1,0,143,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,152,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,160,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,168,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,176,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,183,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,193,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,22,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,200,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,38,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,208,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,222,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,131,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,235,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,239,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,246,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,0,56,1,0,0,0,0,0,0,0,0,0,128,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,11,56,1,0,0,0,0,0,0,0,0,0,128,0,0,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,127,55,1,0,21,56,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,127,55,1,0,30,56,1,0,0,0,0,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,127,55,1,0,41,56,1,0,0,0,0,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,127,55,1,0,52,56,1,0,0,0,0,0,0,0,0,0,128,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,127,55,1,0,62,56,1,0,0,0,0,0,76,3,0,0,1,0,0,0,157,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,62,56,1,0,135,55,1,0,0,0,0,0,0,0,0,0,128,0,0,0,157,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,62,56,1,0,68,56,1,0,75,56,1,0,32,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,26,0,0,0,0,0,0,0,118,56,1,0,133,56,1,0,72,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,154,56,1,0,166,56,1,0,76,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,184,56,1,0,193,56,1,0,80,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,213,56,1,0,221,56,1,0,84,2,0,0,1,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,133,48,1,0,249,56,1,0,209,40,1,0,80,1,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,199,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,5,57,1,0,12,57,1,0,84,1,0,0,1,0,0,0,236,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,0,0,192,255,223,64,17,0,0,0,0,0,0,0,53,57,1,0,60,57,1,0,88,1,0,0,1,0,0,0,74,14,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,0,0,192,255,223,64,17,0,0,0,0,0,0,0,101,57,1,0,106,57,1,0,92,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,165,57,1,0,182,57,1,0,80,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,244,57,1,0,254,57,1,0,84,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,47,58,1,0,58,58,1,0,88,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,231,135,1,0,96,58,1,0,0,0,0,0,128,0,0,0,240,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,215,135,1,0,113,58,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,136,58,1,0,142,58,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,175,58,1,0,181,58,1,0,0,0,0,0,128,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,214,58,1,0,220,58,1,0,0,0,0,0,128,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,0,59,1,0,8,59,1,0,0,0,0,0,128,0,0,0,24,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,236,135,1,0,43,59,1,0,0,0,0,0,128,0,0,0,48,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,18,0,0,0,234,57,1,0,62,59,1,0,75,59,1,0,96,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,64,17,0,0,0,0,0,0,0,139,59,1,0,149,59,1,0,100,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,64,17,0,0,0,0,0,0,0,195,59,1,0,206,59,1,0,104,1,0,0,1,0,0,0,25,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,242,59,1,0,247,59,1,0,108,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,44,60,1,0,57,60,1,0,112,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,84,60,1,0,92,60,1,0,88,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,129,60,1,0,139,60,1,0,116,1,0,0,1,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,207,60,1,0,0,0,0,0,120,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,221,60,1,0,235,60,1,0,124,1,0,0,1,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,20,61,1,0,0,0,0,0,84,0,0,0,1,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,25,0,0,0,0,0,0,0,38,61,1,0,0,0,0,0,92,2,0,0,1,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,0,0,0,0,59,61,1,0,0,0,0,0,96,2,0,0,1,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,0,0,0,0,80,61,1,0,101,61,1,0,104,2,0,0,2,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,240,191,0,0,0,0,0,0,224,67,17,0,0,0,0,0,0,0,159,61,1,0,176,61,1,0,184,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,10,0,0,0,0,0,0,0,213,61,1,0,0,0,0,0,28,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,233,61,1,0,0,0,0,0,192,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,67,11,0,0,0,233,61,1,0,248,61,1,0,0,0,0,0,200,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,67,10,0,0,0,248,61,1,0,15,62,1,0,0,0,0,0,44,2,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,30,62,1,0,0,0,0,0,48,2,0,0,4,0,0,0,0,0,0,0,0,0,8,64,0,0,0,0,0,0,0,0,0,0,0,224,255,255,239,71,17,0,0,0,0,0,0,0,45,62,1,0,0,0,0,0,112,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,192,255,255,255,223,65,27,0,0,0,0,0,0,0,61,62,1,0,77,62,1,0,128,1,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,0,0,0,0,34,64,19,0,0,0,93,62,1,0,114,62,1,0,120,62,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,127,62,1,0,139,62,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,151,62,1,0,158,62,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,167,62,1,0,175,62,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,185,62,1,0,195,62,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,207,62,1,0,217,62,1,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,29,138,1,0,229,62,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,234,62,1,0,241,62,1,0,0,0,0,0,128,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,93,62,1,0,249,62,1,0,3,63,1,0,132,1,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,0,0,0,0,0,0,46,64,19,0,0,0,34,63,1,0,114,62,1,0,120,62,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,127,62,1,0,139,62,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,49,63,1,0,158,62,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,57,63,1,0,175,62,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,185,62,1,0,195,62,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,207,62,1,0,217,62,1,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,65,63,1,0,72,63,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,13,31,1,0,79,63,1,0,0,0,0,0,128,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,83,63,1,0,92,63,1,0,0,0,0,0,128,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,108,63,1,0,121,63,1,0,0,0,0,0,128,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,135,63,1,0,142,63,1,0,0,0,0,0,128,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,150,63,1,0,163,63,1,0,0,0,0,0,128,0,0,0,13,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,177,63,1,0,190,63,1,0,0,0,0,0,128,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,207,63,1,0,220,63,1,0,0,0,0,0,128,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,34,63,1,0,237,63,1,0,248,63,1,0,136,1,0,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,36,64,19,0,0,0,4,64,1,0,20,64,1,0,24,64,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,114,62,1,0,120,62,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,127,62,1,0,139,62,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,28,64,1,0,32,64,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,167,62,1,0,175,62,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,185,62,1,0,195,62,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,207,62,1,0,217,62,1,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,36,64,1,0,42,64,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,48,64,1,0,59,64,1,0,0,0,0,0,128,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,71,64,1,0,81,64,1,0,0,0,0,0,128,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,4,64,1,0,92,64,1,0,104,64,1,0,140,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,19,0,0,0,116,64,1,0,127,62,1,0,139,62,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,116,64,1,0,133,64,1,0,138,64,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,116,64,1,0,157,64,1,0,162,64,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,116,64,1,0,175,64,1,0,198,64,1,0,144,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,64,19,0,0,0,221,64,1,0,127,62,1,0,139,62,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,183,45,1,0,249,64,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,254,64,1,0,5,65,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,12,65,1,0,20,65,1,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,29,65,1,0,33,65,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,37,65,1,0,48,65,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,60,65,1,0,67,65,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,221,64,1,0,74,65,1,0,91,65,1,0,4,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,116,65,1,0,123,65,1,0,148,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,17,0,0,0,0,0,0,0,171,65,1,0,183,65,1,0,44,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,27,0,0,0,171,65,1,0,210,65,1,0,0,0,0,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,171,65,1,0,216,65,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,19,0,0,0,171,65,1,0,222,65,1,0,241,65,1,0,208,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,64,9,0,0,0,222,65,1,0,4,66,1,0,7,66,1,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,26,66,1,0,29,66,1,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,37,66,1,0,40,66,1,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,58,66,1,0,61,66,1,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,78,66,1,0,81,66,1,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,90,66,1,0,93,66,1,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,104,66,1,0,107,66,1,0,0,0,0,0,128,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,117,66,1,0,120,66,1,0,0,0,0,0,128,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,131,66,1,0,134,66,1,0,0,0,0,0,128,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,9,0,0,0,222,65,1,0,142,66,1,0,161,66,1,0,212,1,0,0,84,77,70,83,255,255,255,255,255,255,255,255,0,0,0,0,0,0,240,191,0,0,192,255,255,255,223,65,10,0,0,0,142,66,1,0,204,66,1,0,0,0,0,0,140,3,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,217,66,1,0,229,66,1,0,184,3,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,192,0,0,0,0,0,192,95,64,34,0,0,0,0,0,0,0,17,67,1,0,34,67,1,0,188,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,191,0,0,192,255,255,255,223,65,34,0,0,0,17,67,1,0,83,67,1,0,0,0,0,0,0,0,0,0,128,0,0,0,255,255,255,255,255,255,255,255,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,34,0,0,0,17,67,1,0,160,42,1,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,34,0,0,0,17,67,1,0,94,67,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,224,193,0,0,192,255,255,255,223,65,34,0,0,0,17,67,1,0,106,67,1,0,0,0,0,0,232,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,26,0,0,0,0,0,0,0,124,67,1,0,0,0,0,0,120,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,25,0,0,0,0,0,0,0,147,67,1,0,158,67,1,0,192,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,63,18,0,0,0,0,0,0,0,180,67,1,0,192,67,1,0,152,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,64,19,0,0,0,180,67,1,0,204,67,1,0,0,0,0,0,0,0,0,0,128,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,180,67,1,0,216,67,1,0,0,0,0,0,0,0,0,0,128,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,180,67,1,0,62,48,1,0,0,0,0,0,0,0,0,0,128,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,180,67,1,0,219,67,1,0,0,0,0,0,0,0,0,0,128,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,180,67,1,0,201,23,1,0,0,0,0,0,0,0,0,0,128,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,19,0,0,0,180,67,1,0,222,67,1,0,237,67,1,0,204,3,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,192,0,0,0,0,0,192,95,64,59,0,0,0,0,0,0,0,18,68,1,0,34,68,1,0,208,3,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,192,0,0,0,0,0,192,95,64,58,0,0,0,0,0,0,0,79,68,1,0,92,68,1,0,140,0,0,0,84,77,70,80,255,255,255,255,255,255,255,255,0,0,0,0,0,0,240,191,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,109,68,1,0,120,68,1,0,120,0,0,0,69,90,73,83,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,255,255,255,223,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,147,127,1,0,1,0,0,0,4,0,0,0,0,0,0,0,152,127,1,0,2,0,0,0,3,0,0,0,0,0,0,0,159,127,1,0,3,0,0,0,11,0,0,0,0,0,0,0,163,127,1,0,3,0,0,0,7,0,0,0,0,0,0,0,167,127,1,0,3,0,0,0,3,1,0,0,0,0,0,0,177,127,1,0,4,0,0,0,7,1,0,0,0,0,0,0,181,127,1,0,4,0,0,0,51,0,0,0,0,0,0,0,186,127,1,0,4,0,0,0,3,6,0,0,0,0,0,0,197,127,1,0,4,0,0,0,15,0,0,0,0,0,0,0,201,127,1,0,5,0,0,0,55,0,0,0,0,0,0,0,205,127,1,0,5,0,0,0,7,6,0,0,0,0,0,0,215,127,1,0,5,0,0,0,15,1,0,0,0,0,0,0,219,127,1,0,6,0,0,0,63,0,0,0,0,0,0,0,223,127,1,0,6,0,0,0,15,6,0,0,0,0,0,0,233,127,1,0,6,0,0,0,7,7,0,0,0,0,0,0,237,127,1,0,6,0,0,0,195,6,0,0,0,0,0,0,248,127,1,0,6,0,0,0,55,1,0,0,0,0,0,0,2,128,1,0,7,0,0,0,15,7,0,0,0,0,0,0,2,128,1,0,7,0,0,0,63,1,0,0,0,0,0,0,6,128,1,0,7,0,0,0,203,6,0,0,0,0,0,0,17,128,1,0,7,0,0,0,55,6,0,0,0,0,0,0,21,128,1,0,7,0,0,0,199,6,0,0,0,0,0,0,32,128,1,0,8,0,0,0,63,6,0,0,0,0,0,0,36,128,1,0,8,0,0,0,255,0,0,0,0,0,0,0,46,128,1,0,8,0,0,0,207,6,0,0,0,0,0,0,61,128,1,0,8,0,0,0,55,7,0,0,0,0,0,0,71,128,1,0,16,0,0,0,55,247,3,128,1,0,0,0,85,128,1,0,2,0,0,0,0,0,0,96,0,0,0,0,30,131,1,0,0,0,0,0,105,87,20,139,10,191,5,64,32,131,1,0,0,0,0,0,24,45,68,84,251,33,9,64,35,131,1,0,0,0,0,0,168,244,151,155,119,227,249,63,39,131,1,0,0,0,0,0,0,0,0,0,0,128,93,64,93,61,127,102,158,160,230,63,0,0,0,0,0,136,57,61,68,23,117,250,82,176,230,63,0,0,0,0,0,0,216,60,254,217,11,117,18,192,230,63,0,0,0,0,0,120,40,189,191,118,212,221,220,207,230,63,0,0,0,0,0,192,30,61,41,26,101,60,178,223,230,63,0,0,0,0,0,0,216,188,227,58,89,152,146,239,230,63,0,0,0,0,0,0,188,188,134,147,81,249,125,255,230,63,0,0,0,0,0,216,47,189,163,45,244,102,116,15,231,63,0,0,0,0,0,136,44,189,195,95,236,232,117,31,231,63,0,0,0,0,0,192,19,61,5,207,234,134,130,47,231,63,0,0,0,0,0,48,56,189,82,129,165,72,154,63,231,63,0,0,0,0,0,192,0,189,252,204,215,53,189,79,231,63,0,0,0,0,0,136,47,61,241,103,66,86,235,95,231,63,0,0,0,0,0,224,3,61,72,109,171,177,36,112,231,63,0,0,0,0,0,208,39,189,56,93,222,79,105,128,231,63,0,0,0,0,0,0,221,188,0,29,172,56,185,144,231,63,0,0,0,0,0,0,227,60,120,1,235,115,20,161,231,63,0,0,0,0,0,0,237,188,96,208,118,9,123,177,231,63,0,0,0,0,0,64,32,61,51,193,48,1,237,193,231,63,0,0,0,0,0,0,160,60,54,134,255,98,106,210,231,63,0,0,0,0,0,144,38,189,59,78,207,54,243,226,231,63,0,0,0,0,0,224,2,189,232,195,145,132,135,243,231,63,0,0,0,0,0,88,36,189,78,27,62,84,39,4,232,63,0,0,0,0,0,0,51,61,26,7,209,173,210,20,232,63,0,0,0,0,0,0,15,61,126,205,76,153,137,37,232,63,0,0,0,0,0,192,33,189,208,66,185,30,76,54,232,63,0,0,0,0,0,208,41,61,181,202,35,70,26,71,232,63,0,0,0,0,0,16,71,61,188,91,159,23,244,87,232,63,0,0,0,0,0,96,34,61,175,145,68,155,217,104,232,63,0,0,0,0,0,196,50,189,149,163,49,217,202,121,232,63,0,0,0,0,0,0,35,189,184,101,138,217,199,138,232,63,0,0,0,0,0,128,42,189,0,88,120,164,208,155,232,63,0,0,0,0,0,0,237,188,35,162,42,66,229,172,232,63,0,0,0,0,0,40,51,61,250,25,214,186,5,190,232,63,0,0,0,0,0,180,66,61,131,67,181,22,50,207,232,63,0,0,0,0,0,208,46,189,76,102,8,94,106,224,232,63,0,0,0,0,0,80,32,189,7,120,21,153,174,241,232,63,0,0,0,0,0,40,40,61,14,44,40,208,254,2,233,63,0,0,0,0,0,176,28,189,150,255,145,11,91,20,233,63,0,0,0,0,0,224,5,189,249,47,170,83,195,37,233,63,0,0,0,0,0,64,245,60,74,198,205,176,55,55,233,63,0,0,0,0,0,32,23,61,174,152,95,43,184,72,233,63,0,0,0,0,0,0,9,189,203,82,200,203,68,90,233,63,0,0,0,0,0,104,37,61,33,111,118,154,221,107,233,63,0,0,0,0,0,208,54,189,42,78,222,159,130,125,233,63,0,0,0,0,0,0,1,189,163,35,122,228,51,143,233,63,0,0,0,0,0,0,45,61,4,6,202,112,241,160,233,63,0,0,0,0,0,164,56,189,137,255,83,77,187,178,233,63,0,0,0,0,0,92,53,61,91,241,163,130,145,196,233,63,0,0,0,0,0,184,38,61,197,184,75,25,116,214,233,63,0,0,0,0,0,0,236,188,142,35,227,25,99,232,233,63,0,0,0,0,0,208,23,61,2,243,7,141,94,250,233,63,0,0,0,0,0,64,22,61,77,229,93,123,102,12,234,63,0,0,0,0,0,0,245,188,246,184,142,237,122,30,234,63,0,0,0,0,0,224,9,61,39,46,74,236,155,48,234,63,0,0,0,0,0,216,42,61,93,10,70,128,201,66,234,63,0,0,0,0,0,240,26,189,155,37,62,178,3,85,234,63,0,0,0,0,0,96,11,61,19,98,244,138,74,103,234,63,0,0,0,0,0,136,56,61,167,179,48,19,158,121,234,63,0,0,0,0,0,32,17,61,141,46,193,83,254,139,234,63,0,0,0,0,0,192,6,61,210,252,121,85,107,158,234,63,0,0,0,0,0,184,41,189,184,111,53,33,229,176,234,63,0,0,0,0,0,112,43,61,129,243,211,191,107,195,234,63,0,0,0,0,0,0,217,60,128],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+10241);allocate([39,60,58,255,213,234,63,0,0,0,0,0,0,228,60,163,210,90,153,159,232,234,63,0,0,0,0,0,144,44,189,103,243,34,230,76,251,234,63,0,0,0,0,0,80,22,61,144,183,141,41,7,14,235,63,0,0,0,0,0,212,47,61,169,137,154,108,206,32,235,63,0,0,0,0,0,112,18,61,75,26,79,184,162,51,235,63,0,0,0,0,0,71,77,61,231,71,183,21,132,70,235,63,0,0,0,0,0,56,56,189,58,89,229,141,114,89,235,63,0,0,0,0,0,0,152,60,106,197,241,41,110,108,235,63,0,0,0,0,0,208,10,61,80,94,251,242,118,127,235,63,0,0,0,0,0,128,222,60,178,73,39,242,140,146,235,63,0,0,0,0,0,192,4,189,3,6,161,48,176,165,235,63,0,0,0,0,0,112,13,189,102,111,154,183,224,184,235,63,0,0,0,0,0,144,13,61,255,193,75,144,30,204,235,63,0,0,0,0,0,160,2,61,111,161,243,195,105,223,235,63,0,0,0,0,0,120,31,189,184,29,215,91,194,242,235,63,0,0,0,0,0,160,16,189,233,178,65,97,40,6,236,63,0,0,0,0,0,64,17,189,224,82,133,221,155,25,236,63,0,0,0,0,0,224,11,61,238,100,250,217,28,45,236,63,0,0,0,0,0,64,9,189,47,208,255,95,171,64,236,63,0,0,0,0,0,208,14,189,21,253,250,120,71,84,236,63,0,0,0,0,0,102,57,61,203,208,87,46,241,103,236,63,0,0,0,0,0,16,26,189,182,193,136,137,168,123,236,63,0,0,0,0,128,69,88,189,51,231,6,148,109,143,236,63,0,0,0,0,0,72,26,189,223,196,81,87,64,163,236,63,0,0,0,0,0,0,203,60,148,144,239,220,32,183,236,63,0,0,0,0,0,64,1,61,137,22,109,46,15,203,236,63,0,0,0,0,0,32,240,60,18,196,93,85,11,223,236,63,0,0,0,0,0,96,243,60,59,171,91,91,21,243,236,63,0,0,0,0,0,144,6,189,188,137,7,74,45,7,237,63,0,0,0,0,0,160,9,61,250,200,8,43,83,27,237,63,0,0,0,0,0,224,21,189,133,138,13,8,135,47,237,63,0,0,0,0,0,40,29,61,3,162,202,234,200,67,237,63,0,0,0,0,0,160,1,61,145,164,251,220,24,88,237,63,0,0,0,0,0,0,223,60,161,230,98,232,118,108,237,63,0,0,0,0,0,160,3,189,78,131,201,22,227,128,237,63,0,0,0,0,0,216,12,189,144,96,255,113,93,149,237,63,0,0,0,0,0,192,244,60,174,50,219,3,230,169,237,63,0,0,0,0,0,144,255,60,37,131,58,214,124,190,237,63,0,0,0,0,0,128,233,60,69,180,1,243,33,211,237,63,0,0,0,0,0,32,245,188,191,5,28,100,213,231,237,63,0,0,0,0,0,112,29,189,236,154,123,51,151,252,237,63,0,0,0,0,0,20,22,189,94,125,25,107,103,17,238,63,0,0,0,0,0,72,11,61,231,163,245,20,70,38,238,63,0,0,0,0,0,206,64,61,92,238,22,59,51,59,238,63,0,0,0,0,0,104,12,61,180,63,139,231,46,80,238,63,0,0,0,0,0,48,9,189,104,109,103,36,57,101,238,63,0,0,0,0,0,0,229,188,68,76,199,251,81,122,238,63,0,0,0,0,0,248,7,189,38,183,205,119,121,143,238,63,0,0,0,0,0,112,243,188,232,144,164,162,175,164,238,63,0,0,0,0,0,208,229,60,228,202,124,134,244,185,238,63,0,0,0,0,0,26,22,61,13,104,142,45,72,207,238,63,0,0,0,0,0,80,245,60,20,133,24,162,170,228,238,63,0,0,0,0,0,64,198,60,19,90,97,238,27,250,238,63,0,0,0,0,0,128,238,188,6,65,182,28,156,15,239,63,0,0,0,0,0,136,250,188,99,185,107,55,43,37,239,63,0,0,0,0,0,144,44,189,117,114,221,72,201,58,239,63,0,0,0,0,0,0,170,60,36,69,110,91,118,80,239,63,0,0,0,0,0,240,244,188,253,68,136,121,50,102,239,63,0,0,0,0,0,128,202,60,56,190,156,173,253,123,239,63,0,0,0,0,0,188,250,60,130,60,36,2,216,145,239,63,0,0,0,0,0,96,212,188,142,144,158,129,193,167,239,63,0,0,0,0,0,12,11,189,17,213,146,54,186,189,239,63,0,0,0,0,0,224,192,188,148,113,143,43,194,211,239,63,0,0,0,0,128,222,16,189,238,35,42,107,217,233,239,63,0,0,0,0,0,67,238,60,0,0,0,0,0,0,240,63,0,0,0,0,0,0,0,0,190,188,90,250,26,11,240,63,0,0,0,0,0,64,179,188,3,51,251,169,61,22,240,63,0,0,0,0,0,23,18,189,130,2,59,20,104,33,240,63,0,0,0,0,0,64,186,60,108,128,119,62,154,44,240,63,0,0,0,0,0,152,239,60,202,187,17,46,212,55,240,63,0,0,0,0,0,64,199,188,137,127,110,232,21,67,240,63,0,0,0,0,0,48,216,60,103,84,246,114,95,78,240,63,0,0,0,0,0,63,26,189,90,133,21,211,176,89,240,63,0,0,0,0,0,132,2,189,149,31,60,14,10,101,240,63,0,0,0,0,0,96,241,60,26,247,221,41,107,112,240,63,0,0,0,0,0,36,21,61,45,168,114,43,212,123,240,63,0,0,0,0,0,160,233,188,208,155,117,24,69,135,240,63,0,0,0,0,0,64,230,60,200,7,102,246,189,146,240,63,0,0,0,0,0,120,0,189,131,243,198,202,62,158,240,63,0,0,0,0,0,0,152,188,48,57,31,155,199,169,240,63,0,0,0,0,0,160,255,60,252,136,249,108,88,181,240,63,0,0,0,0,0,200,250,188,138,108,228,69,241,192,240,63,0,0,0,0,0,192,217,60,22,72,114,43,146,204,240,63,0,0,0,0,0,32,5,61,216,93,57,35,59,216,240,63,0,0,0,0,0,208,250,188,243,209,211,50,236,227,240,63,0,0,0,0,0,172,27,61,166,169,223,95,165,239,240,63,0,0,0,0,0,232,4,189,240,210,254,175,102,251,240,63,0,0,0,0,0,48,13,189,75,35,215,40,48,7,241,63,0,0,0,0,0,80,241,60,91,91,18,208,1,19,241,63,0,0,0,0,0,0,236,60,249,42,94,171,219,30,241,63,0,0,0,0,0,188,22,61,213,49,108,192,189,42,241,63,0,0,0,0,0,64,232,60,125,4,242,20,168,54,241,63,0,0,0,0,0,208,14,189,233,45,169,174,154,66,241,63,0,0,0,0,0,224,232,60,56,49,79,147,149,78,241,63,0,0,0,0,0,64,235,60,113,142,165,200,152,90,241,63,0,0,0,0,0,48,5,61,223,195,113,84,164,102,241,63,0,0,0,0,0,56,3,61,17,82,125,60,184,114,241,63,0,0,0,0,0,212,40,61,159,187,149,134,212,126,241,63,0,0,0,0,0,208,5,189,147,141,140,56,249,138,241,63,0,0,0,0,0,136,28,189,102,93,55,88,38,151,241,63,0,0,0,0,0,240,17,61,167,203,111,235,91,163,241,63,0,0,0,0,0,72,16,61,227,135,19,248,153,175,241,63,0,0,0,0,0,57,71,189,84,93,4,132,224,187,241,63,0,0,0,0,0,228,36,61,67,28,40,149,47,200,241,63,0,0,0,0,0,32,10,189,178,185,104,49,135,212,241,63,0,0,0,0,0,128,227,60,49,64,180,94,231,224,241,63,0,0,0,0,0,192,234,60,56,217,252,34,80,237,241,63,0,0,0,0,0,144,1,61,247,205,56,132,193,249,241,63,0,0,0,0,0,120,27,189,143,141,98,136,59,6,242,63,0,0,0,0,0,148,45,61,30,168,120,53,190,18,242,63,0,0,0,0,0,0,216,60,65,221,125,145,73,31,242,63,0,0,0,0,0,52,43,61,35,19,121,162,221,43,242,63,0,0,0,0,0,248,25,61,231,97,117,110,122,56,242,63,0,0,0,0,0,200,25,189,39,20,130,251,31,69,242,63,0,0,0,0,0,48,2,61,2,166,178,79,206,81,242,63,0,0,0,0,0,72,19,189,176,206,30,113,133,94,242,63,0,0,0,0,0,112,18,61,22,125,226,101,69,107,242,63,0,0,0,0,0,208,17,61,15,224,29,52,14,120,242,63,0,0,0,0,0,238,49,61,62,99,245,225,223,132,242,63,0,0,0,0,0,192,20,189,48,187,145,117,186,145,242,63,0,0,0,0,0,216,19,189,9,223,31,245,157,158,242,63,0,0,0,0,0,176,8,61,155,14,209,102,138,171,242,63,0,0,0,0,0,124,34,189,58,218,218,208,127,184,242,63,0,0,0,0,0,52,42,61,249,26,119,57,126,197,242,63,0,0,0,0,0,128,16,189,217,2,228,166,133,210,242,63,0,0,0,0,0,208,14,189,121,21,100,31,150,223,242,63,0,0,0,0,0,32,244,188,207,46,62,169,175,236,242,63,0,0,0,0,0,152,36,189,34,136,189,74,210,249,242,63,0,0,0,0,0,48,22,189,37,182,49,10,254,6,243,63,0,0,0,0,0,54,50,189,11,165,238,237,50,20,243,63,0,0,0,0,128,223,112,189,184,215,76,252,112,33,243,63,0,0,0,0,0,72,34,189,162,233,168,59,184,46,243,63,0,0,0,0,0,152,37,189,102,23,100,178,8,60,243,63,0,0,0,0,0,208,30,61,39,250,227,102,98,73,243,63,0,0,0,0,0,0,220,188,15,159,146,95,197,86,243,63,0,0,0,0,0,216,48,189,185,136,222,162,49,100,243,63,0,0,0,0,0,200,34,61,57,170,58,55,167,113,243,63,0,0,0,0,0,96,32,61,254,116,30,35,38,127,243,63,0,0,0,0,0,96,22,189,56,216,5,109,174,140,243,63,0,0,0,0,0,224,10,189,195,62,113,27,64,154,243,63,0,0,0,0,0,114,68,189,32,160,229,52,219,167,243,63,0,0,0,0,0,32,8,61,149,110,236,191,127,181,243,63,0,0,0,0,0,128,62,61,242,168,19,195,45,195,243,63,0,0,0,0,0,128,239,60,34,225,237,68,229,208,243,63,0,0,0,0,0,160,23,189,187,52,18,76,166,222,243,63,0,0,0,0,0,48,38,61,204,78,28,223,112,236,243,63,0,0,0,0,0,166,72,189,140,126,172,4,69,250,243,63,0,0,0,0,0,220,60,189,187,160,103,195,34,8,244,63,0,0,0,0,0,184,37,61,149,46,247,33,10,22,244,63,0,0,0,0,0,192,30,61,70,70,9,39,251,35,244,63,0,0,0,0,0,96,19,189,32,169,80,217,245,49,244,63,0,0,0,0,0,152,35,61,235,185,132,63,250,63,244,63,0,0,0,0,0,0,250,60,25,137,97,96,8,78,244,63,0,0,0,0,0,192,246,188,1,210,167,66,32,92,244,63,0,0,0,0,0,192,11,189,22,0,29,237,65,106,244,63,0,0,0,0,0,128,18,189,38,51,139,102,109,120,244,63,0,0,0,0,0,224,48,61,0,60,193,181,162,134,244,63,0,0,0,0,0,64,45,189,4,175,146,225,225,148,244,63,0,0,0,0,0,32,12,61,114,211,215,240,42,163,244,63,0,0,0,0,0,80,30,189,1,184,109,234,125,177,244,63,0,0,0,0,0,128,7,61,225,41,54,213,218,191,244,63,0,0,0,0,0,128,19,189,50,193,23,184,65,206,244,63,0,0,0,0,0,128,0,61,219,221,253,153,178,220,244,63,0,0,0,0,0,112,44,61,150,171,216,129,45,235,244,63,0,0,0,0,0,224,28,189,2,45,157,118,178,249,244,63,0,0,0,0,0,32,25,61,193,49,69,127,65,8,245,63,0,0,0,0,0,192,8,189,42,102,207,162,218,22,245,63,0,0,0,0,0,0,250,188,234,81,63,232,125,37,245,63,0,0,0,0,0,8,74,61,218,78,157,86,43,52,245,63,0,0,0,0,0,216,38,189,26,172,246,244,226,66,245,63,0,0,0,0,0,68,50,189,219,148,93,202,164,81,245,63,0,0,0,0,0,60,72,61,107,17,233,221,112,96,245,63,0,0,0,0,0,176,36,61,222,41,181,54,71,111,245,63,0,0,0,0,0,90,65,61,14,196,226,219,39,126,245,63,0,0,0,0,0,224,41,189,111,199,151,212,18,141,245,63,0,0,0,0,0,8,35,189,76,11,255,39,8,156,245,63,0,0,0,0,0,236,77,61,39,84,72,221,7,171,245,63,0,0,0,0,0,0,196,188,244,122,168,251,17,186,245,63,0,0,0,0,0,8,48,61,11,70,89,138,38,201,245,63,0,0,0,0,0,200,38,189,63,142,153,144,69,216,245,63,0,0,0,0,0,154,70,61,225,32,173,21,111,231,245,63,0,0,0,0,0,64,27,189,202,235,220,32,163,246,245,63,0,0,0,0,0,112,23,61,184,220,118,185,225,5,246,63,0,0,0,0,0,248,38,61,21,247,205,230,42,21,246,63,0,0,0,0,0,0,1,61,49,85,58,176,126,36,246,63,0,0,0,0,0,208,21,189,181,41,25,29,221,51,246,63,0,0,0,0,0,208,18,189,19,195,204,52,70,67,246,63,0,0,0,0,0,128,234,188,250,142,188,254,185,82,246,63,0,0,0,0,0,96,40,189,151,51,85,130,56,98,246,63,0,0,0,0,0,254,113,61,142,50,8,199,193,113,246,63,0,0,0,0,0,32,55,189,126,169,76,212,85,129,246,63,0,0,0,0,0,128,230,60,113,148,158,177,244,144,246,63,0,0,0,0,0,120,41,189,1,0,0,0,0,0,0,0,120,174,0,0,131,174,0,0,10,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,144,174,0,0,155,174,0,0,10,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,168,174,0,0,183,174,0,0,2,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,233,174,0,0,238,174,0,0,2,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,244,174,0,0,249,174,0,0,10,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,51,175,0,0,56,175,0,0,2,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,70,175,0,0,75,175,0,0,10,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,89,175,0,0,95,175,0,0,3,0,0,0,112,130,0,0,9,0,0,0,0,0,0,0,118,175,0,0,125,175,0,0,3,0,0,0,0,0,0,0,13,0,0,0,0,0,0,0,139,175,0,0,145,175,0,0,10,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,159,175,0,0,168,175,0,0,5,0,0,0,0,0,0,0,15,0,0,0,0,0,0,0,178,175,0,0,188,175,0,0,2,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,230,175,0,0,240,175,0,0,2,0,0,0,0,0,0,0,17,0,0,0,0,0,0,0,26,176,0,0,36,176,0,0,2,0,0,0,0,0,0,0,18,0,0,0,0,0,0,0,78,176,0,0,83,176,0,0,2,0,0,0,0,0,0,0,19,0,0,0,0,0,0,0,105,176,0,0,110,176,0,0,2,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,132,176,0,0,138,176,0,0,10,0,0,0,0,0,0,0,21,0,0,0,0,0,0,0,176,176,0,0,182,176,0,0,10,0,0,0,0,0,0,0,22,0,0,0,0,0,0,0,194,176,0,0,199,176,0,0,2,0,0,0,0,0,0,0,23,0,0,0,0,0,0,0,251,176,0,0,0,177,0,0,2,0,0,0,0,0,0,0,24,0,0,0,0,0,0,0,54,177,0,0,59,177,0,0,10,0,0,0,0,0,0,0,25,0,0,0,0,0,0,0,113,177,0,0,121,177,0,0,3,0,0,0,0,0,0,0,26,0,0,0,0,0,0,0,140,177,0,0,148,177,0,0,5,0,0,0,0,0,0,0,27,0,0,0,0,0,0,0,156,177,0,0,161,177,0,0,3,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,209,226,0,0,214,226,0,0,14,0,0,0,0,0,0,0,29,0,0,0,0,0,0,0,181,177,0,0,188,177,0,0,2,0,0,0,0,0,0,0,30,0,0,0,0,0,0,0,202,177,0,0,206,177,0,0,2,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,214,177,0,0,221,177,0,0,2,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,228,177,0,0,233,177,0,0,3,0,0,0,0,0,0,0,33,0,0,0,0,0,0,0,241,177,0,0,246,177,0,0,3,0,0,0,0,0,0,0,34,0,0,0,0,0,0,0,254,177,0,0,3,178,0,0,4,0,0,0,0,0,0,0,35,0,0,0,0,0,0,0,25,178,0,0,29,178,0,0,2,0,0,0,0,0,0,0,36,0,0,0,0,0,0,0,38,178,0,0,43,178,0,0,3,0,0,0,0,0,0,0,37,0,0,0,0,0,0,0,52,178,0,0,57,178,0,0,3,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,78,178,0,0,83,178,0,0,3,0,0,0,0,0,0,0,39,0,0,0,0,0,0,0,122,178,0,0,126,178,0,0,2,0,0,0,0,0,0,0,40,0,0,0,0,0,0,0,139,178,0,0,154,178,0,0,2,0,0,0,0,0,0,0,41,0,0,0,0,0,0,0,174,178,0,0,182,178,0,0,2,0,0,0,0,0,0,0,42,0,0,0,0,0,0,0,207,178,0,0,215,178,0,0,2,0,0,0,0,0,0,0,43,0,0,0,0,0,0,0,240,178,0,0,245,178,0,0,2,0,0,0,0,0,0,0,44,0,0,0,0,0,0,0,12,179,0,0,20,179,0,0,2,0,0,0,0,0,0,0,45,0,0,0,0,0,0,0,28,179,0,0,35,179,0,0,2,0,0,0,0,0,0,0,46,0,0,0,0,0,0,0,91,179,0,0,97,179,0,0,4,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,111,179,0,0,120,179,0,0,2,0,0,0,0,0,0,0,48,0,0,0,0,0,0,0,138,179,0,0,144,179,0,0,2,0,0,0,0,0,0,0,49,0,0,0,0,0,0,0,166,179,0,0,171,179,0,0,5,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,192,179,0,0,196,179,0,0,2,0,0,0,0,0,0,0,51,0,0,0,0,0,0,0,221,179,0,0,226,179,0,0,4,0,0,0,0,0,0,0,52,0,0,0,0,0,0,0,255,179,0,0,11,180,0,0,2,0,0,0,0,0,0,0,53,0,0,0,0,0,0,0,31,180,0,0,40,180,0,0,2,0,0,0,0,0,0,0,54,0,0,0,0,0,0,0,57,180,0,0,62,180,0,0,5,0,0,0,0,0,0,0,55,0,0,0,0,0,0,0,96,180,0,0,101,180,0,0,5,0,0,0,0,0,0,0,56,0,0,0,0,0,0,0,135,180,0,0,141,180,0,0,4,0,0,0,0,0,0,0,87,79,78,83,0,0,0,0,173,180,0,0,122,144,1,0,6,0,0,0,0,0,0,0,57,0,0,0,0,0,0,0,178,180,0,0,183,180,0,0,4,0,0,0,0,0,0,0,58,0,0,0,0,0,0,0,214,180,0,0,219,180,0,0,2,0,0,0,0,0,0,0,59,0,0,0,0,0,0,0,234,180,0,0,240,180,0,0,5,0,0,0,0,0,0,0,60,0,0,0,0,0,0,0,0,181,0,0,5,181,0,0,3,0,0,0,0,0,0,0,61,0,0,0,0,0,0,0,18,181,0,0,23,181,0,0,2,0,0,0,0,0,0,0,68,0,0,0,0,0,0,0,35,181,0,0,43,181,0,0,5,0,0,0,0,0,0,0,69,0,0,0,0,0,0,0,66,181,0,0,71,181,0,0,10,0,0,0,0,0,0,0,70,0,0,0,0,0,0,0,85,181,0,0,90,181,0,0,10,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,104,181,0,0,108,181,0,0,10,0,0,0,0,0,0,0,72,0,0,0,0,0,0,0,119,181,0,0,124,181,0,0,10,0,0,0,0,0,0,0,73,0,0,0,0,0,0,0,146,181,0,0,151,181,0,0,5,0,0,0,0,0,0,0,74,0,0,0,0,0,0,0,156,181,0,0,161,181,0,0,3,0,0,0,0,0,0,0,75,0,0,0,0,0,0,0,173,181,0,0,178,181,0,0,4,0,0,0,0,0,0,0,76,0,0,0,0,0,0,0,191,181,0,0,198,181,0,0,2,0,0,0,0,0,0,0,77,0,0,0,0,0,0,0,212,181,0,0,218,181,0,0,4,0,0,0,0,0,0,0,78,0,0,0,0,0,0,0,224,181,0,0,236,181,0,0,2,0,0,0,0,0,0,0,79,0,0,0,0,0,0,0,0,182,0,0,4,182,0,0,5,0,0,0,120,130,0,0,80,0,0,0,0,0,0,0,49,182,0,0,54,182,0,0,4,0,0,0,0,0,0,0,81,0,0,0,0,0,0,0,64,182,0,0,72,182,0,0,2,0,0,0,0,0,0,0,82,0,0,0,0,0,0,0,102,182,0,0,107,182,0,0,4,0,0,0,0,0,0,0,83,0,0,0,0,0,0,0,131,182,0,0,135,182,0,0,2,0,0,0,0,0,0,0,84,0,0,0,0,0,0,0,168,182,0,0,179,182,0,0,2,0,0,0,0,0,0,0,85,0,0,0,0,0,0,0,193,182,0,0,197,182,0,0,2,0,0,0,0,0,0,0,86,0,0,0,0,0,0,0,216,182,0,0,221,182,0,0,2,0,0,0,0,0,0,0,87,0,0,0,0,0,0,0,247,182,0,0,255,182,0,0,4,0,0,0,0,0,0,0,88,0,0,0,0,0,0,0,21,183,0,0,26,183,0,0,10,0,0,0,0,0,0,0,89,0,0,0,0,0,0,0,87,183,0,0,96,183,0,0,7,0,0,0,128,130,0,0,90,0,0,0,0,0,0,0,116,183,0,0,121,183,0,0,4,0,0,0,0,0,0,0,91,0,0,0,0,0,0,0,156,183,0,0,160,183,0,0,2,0,0,0,0,0,0,0,92,0,0,0,0,0,0,0,168,183,0,0,172,183,0,0,2,0,0,0,0,0,0,0,93,0,0,0,0,0,0,0,180,183,0,0,185,183,0,0,2,0,0,0,0,0,0,0,95,0,0,0,0,0,0,0,209,183,0,0,221,183,0,0,2,0,0,0,0,0,0,0,96,0,0,0,0,0,0,0,7,184,0,0,23,184,0,0,2,0,0,0,0,0,0,0,99,0,0,0,0,0,0,0,49,184,0,0,53,184,0,0,4,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,78,184,0,0,84,184,0,0,3,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,94,184,0,0,98,184,0,0,3,0,0,0,0,0,0,0,103,0,0,0,0,0,0,0,126,184,0,0,130,184,0,0,2,0,0,0,0,0,0,0,104,0,0,0,0,0,0,0,144,184,0,0,156,184,0,0,2,0,0,0,0,0,0,0,107,0,0,0,0,0,0,0,175,184,0,0,180,184,0,0,2,0,0,0,0,0,0,0,108,0,0,0,0,0,0,0,208,35,1,0,224,184,0,0,3,0,0,0,0,0,0,0,109,0,0,0,0,0,0,0,234,184,0,0,237,184,0,0,2,0,0,0,0,0,0,0,112,0,0,0,0,0,0,0,254,184,0,0,5,185,0,0,2,0,0,0,0,0,0,0,113,0,0,0,0,0,0,0,37,185,0,0,44,185,0,0,2,0,0,0,0,0,0,0,114,0,0,0,0,0,0,0,76,185,0,0,82,185,0,0,2,0,0,0,0,0,0,0,115,0,0,0,0,0,0,0,88,185,0,0,92,185,0,0,3,0,0,0,0,0,0,0,116,0,0,0,0,0,0,0,102,185,0,0,112,185,0,0,2,0,0,0,0,0,0,0,117,0,0,0,0,0,0,0,123,185,0,0,129,185,0,0,14,0,0,0,0,0,0,0,118,0,0,0,0,0,0,0,135,185,0,0,139,185,0,0,2,0,0,0,0,0,0,0,119,0,0,0,0,0,0,0,163,185,0,0,167,185,0,0,2,0,0,0,0,0,0,0,120,0,0,0,0,0,0,0,193,185,0,0,206,185,0,0,2,0,0,0,0,0,0,0,121,0,0,0,0,0,0,0,226,185,0,0,230,185,0,0,2,0,0,0,0,0,0,0,122,0,0,0,0,0,0,0,0,186,0,0,4,186,0,0,2,0,0,0,0,0,0,0,123,0,0,0,0,0,0,0,30,186,0,0,34,186,0,0,2,0,0,0,0,0,0,0,124,0,0,0,0,0,0,0,60,186,0,0,65,186,0,0,3,0,0,0,0,0,0,0,125,0,0,0,0,0,0,0,81,186,0,0,87,186,0,0,3,0,0,0,0,0,0,0,126,0,0,0,0,0,0,0,105,186,0,0,111,186,0,0,5,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,137,186,0,0,141,186,0,0,3,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,154,186,0,0,111,186,0,0,5,0,0,0,0,0,0,0,130,0,0,0,0,0,0,0,159,186,0,0,163,186,0,0,2,0,0,0,0,0,0,0,131,0,0,0,0,0,0,0,192,186,0,0,197,186,0,0,5,0,0,0,0,0,0,0,132,0,0,0,0,0,0,0,218,186,0,0,227,186,0,0,2,0,0,0,0,0,0,0,133,0,0,0,0,0,0,0,249,186,0,0,4,187,0,0,2,0,0,0,0,0,0,0,134,0,0,0,0,0,0,0,22,187,0,0,27,187,0,0,5,0,0,0,0,0,0,0,135,0,0,0,0,0,0,0,51,187,0,0,55,187,0,0,2,0,0,0,0,0,0,0,136,0,0,0,0,0,0,0,78,187,0,0,88,187,0,0,2,0,0,0,0,0,0,0,137,0,0,0,0,0,0,0,99,187,0,0,108,187,0,0,2,0,0,0,0,0,0,0,138,0,0,0,0,0,0,0,117,187,0,0,130,187,0,0,2,0,0,0,0,0,0,0,139,0,0,0,0,0,0,0,143,187,0,0,148,187,0,0,2,0,0,0,0,0,0,0,140,0,0,0,0,0,0,0,164,187,0,0,168,187,0,0,2,0,0,0,0,0,0,0,141,0,0,0,0,0,0,0,188,187,0,0,192,187,0,0,2,0,0,0,0,0,0,0,169,0,0,0,0,0,0,0,200,187,0,0,204,187,0,0,2,0,0,0,0,0,0,0,142,0,0,0,0,0,0,0,215,187,0,0,222,187,0,0,3,0,0,0,0,0,0,0,144,0,0,0,0,0,0,0,238,187,0,0,248,187,0,0,3,0,0,0,0,0,0,0,145,0,0,0,0,0,0,0,28,188,0,0,39,188,0,0,3,0,0,0,0,0,0,0,146,0,0,0,0,0,0,0,109,188,0,0,114,188,0,0,5,0,0,0,0,0,0,0,49,67,86,77,0,0,0,0,140,188,0,0,145,188,0,0,3,0,0,0,0,0,0,0,50,67,86,77,0,0,0,0,188,188,0,0,193,188,0,0,3,0,0,0,0,0,0,0,147,0,0,0,0,0,0,0,236,188,0,0,242,188,0,0,2,0,0,0,0,0,0,0,148,0,0,0,0,0,0,0,6,189,0,0,15,189,0,0,5,0,0,0,0,0,0,0,149,0,0,0,0,0,0,0,33,189,0,0,40,189,0,0,3,0,0,0,0,0,0,0,150,0,0,0,0,0,0,0,66,189,0,0,69,189,0,0,3,0,0,0,0,0,0,0,151,0,0,0,0,0,0,0,94,189,0,0,98,189,0,0,2,0,0,0,0,0,0,0,154,0,0,0,0,0,0,0,115,189,0,0,123,189,0,0,5,0,0,0,0,0,0,0,155,0,0,0,0,0,0,0,132,189,0,0,142,189,0,0,4,0,0,0,0,0,0,0,156,0,0,0,0,0,0,0,165,189,0,0,170,189,0,0,5,0,0,0,0,0,0,0,157,0,0,0,0,0,0,0,190,189,0,0,197,189,0,0,5,0,0,0,0,0,0,0,158,0,0,0,0,0,0,0,204,189,0,0,209,189,0,0,5,0,0,0,0,0,0,0,160,0,0,0,0,0,0,0,235,189,0,0,240,189,0,0,3,0,0,0,0,0,0,0,162,0,0,0,0,0,0,0,5,190,0,0,15,190,0,0,4,0,0,0,0,0,0,0,163,0,0,0,0,0,0,0,40,190,0,0,45,190,0,0,2,0,0,0,0,0,0,0,164,0,0,0,0,0,0,0,57,190,0,0,62,190,0,0,2,0,0,0,0,0,0,0,165,0,0,0,0,0,0,0,76,190,0,0,82,190,0,0,2,0,0,0,0,0,0,0,166,0,0,0,0,0,0,0,107,190,0,0,112,190,0,0,2,0,0,0,0,0,0,0,167,0,0,0,0,0,0,0,141,190,0,0,146,190,0,0,5,0,0,0,0,0,0,0,168,0,0,0,0,0,0,0,169,190,0,0,174,190,0,0,3,0,0,0,0,0,0,0,170,0,0,0,0,0,0,0,159,27,1,0,207,190,0,0,3,0,0,0,0,0,0,0,80,49,52,89,0,0,0,0,232,190,0,0,237,190,0,0,1,0,0,0,0,0,0,0,48,51,49,69,0,0,0,0,11,191,0,0,21,191,0,0,2,0,0,0,0,0,0,0,80,82,86,65,0,0,0,0,32,191,0,0,37,191,0,0,1,0,0,0,0,0,0,0,86,50,49,48,0,0,0,0,64,191,0,0,111,186,0,0,1,0,0,0,0,0,0,0,73,85,86,65,0,0,0,0,69,191,0,0,74,191,0,0,1,0,0,0,0,0,0,0,86,85,89,65,0,0,0,0,101,191,0,0,106,191,0,0,1,0,0,0,0,0,0,0,54,49,50,84,0,0,0,0,137,191,0,0,148,191,0,0,1,0,0,0,0,0,0,0,56,48,51,86,0,0,0,0,178,191,0,0,183,191,0,0,1,0,0,0,0,0,0,0,56,48,52,86,0,0,0,0,209,191,0,0,214,191,0,0,1,0,0,0,0,0,0,0,52,86,85,89,0,0,0,0,245,191,0,0,250,191,0,0,1,0,0,0,0,0,0,0,110,82,86,65,0,0,0,0,20,192,0,0,25,192,0,0,0,0,0,0,0,0,0,0,65,73,80,67,0,0,0,0,40,192,0,0,45,192,0,0,0,0,0,0,0,0,0,0,67,65,70,88,0,0,0,0,63,192,0,0,69,192,0,0,3,0,0,0,0,0,0,0,74,86,77,83,0,0,0,0,82,192,0,0,90,192,0,0,0,0,0,0,0,0,0,0,77,50,71,0,0,0,0,0,112,192,0,0,116,192,0,0,2,0,0,0,0,0,0,0,174,0,0,0,0,0,0,0,127,192,0,0,137,192,0,0,2,0,0,0,0,0,0,0,53,54,50,72,0,0,0,0,149,192,0,0,154,192,0,0,10,0,0,0,0,0,0,0,176,0,0,0,0,0,0,0,198,192,0,0,202,192,0,0,2,0,0,0,0,0,0,0,86,70,65,80,0,0,0,0,215,192,0,0,225,192,0,0,2,0,0,0,0,0,0,0,48,55,80,86,0,0,0,0,12,193,0,0,16,193,0,0,2,0,0,0,0,0,0,0,77,78,65,83,0,0,0,0,24,193,0,0,29,193,0,0,2,0,0,0,0,0,0,0,82,73,71,83,0,0,0,0,56,193,0,0,63,193,0,0,5,0,0,0,0,0,0,0,186,0,0,0,0,0,0,0,77,193,0,0,81,193,0,0,3,0,0,0,0,0,0,0,188,0,0,0,0,0,0,0,93,193,0,0,100,193,0,0,3,0,0,0,0,0,0,0,189,0,0,0,0,0,0,0,115,193,0,0,119,193,0,0,3,0,0,0,0,0,0,0,177,0,0,0,0,0,0,0,138,193,0,0,148,193,0,0,5,0,0,0,0,0,0,0,143,0,0,0,0,0,0,0,174,193,0,0,179,193,0,0,2,0,0,0,0,0,0,0,88,73,80,66,0,0,0,0,194,193,0,0,206,193,0,0,5,0,0,0,0,0,0,0,190,0,0,0,0,0,0,0,224,193,0,0,228,193,0,0,7,0,0,0,0,0,0,0,129,0,0,0,0,0,0,0,5,194,0,0,9,194,0,0,5,0,0,0,0,0,0,0,82,88,69,48,0,0,0,0,46,194,0,0,50,194,0,0,7,0,0,0,0,0,0,0,98,0,0,0,0,0,0,0,64,194,0,0,68,194,0,0,4,0,0,0,136,130,0,0,12,0,0,0,0,0,0,0,112,194,0,0,119,194,0,0,7,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,127,194,0,0,133,194,0,0,5,0,0,0,0,0,0,0,67,0,0,0,0,0,0,0,147,194,0,0,151,194,0,0,5,0,0,0,144,130,0,0,64,0,0,0,0,0,0,0,203,194,0,0,207,194,0,0,5,0,0,0,0,0,0,0,110,0,0,0,0,0,0,0,235,194,0,0,239,194,0,0,5,0,0,0,152,130,0,0,65,0,0,0,0,0,0,0,19,195,0,0,23,195,0,0,5,0,0,0,0,0,0,0,66,0,0,0,0,0,0,0,52,195,0,0,59,195,0,0,5,0,0,0,0,0,0,0,62,0,0,0,0,0,0,0,95,195,0,0,99,195,0,0,4,0,0,0,160,130,0,0,63,0,0,0,0,0,0,0,147,195,0,0,151,195,0,0,5,0,0,0,0,0,0,0,105,0,0,0,0,0,0,0,181,195,0,0,185,195,0,0,3,0,0,0,0,0,0,0,102,0,0,0,0,0,0,0,203,195,0,0,207,195,0,0,5,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,217,195,0,0,222,195,0,0,3,0,0,0,0,0,0,0,111,0,0,0,0,0,0,0,242,195,0,0,250,195,0,0,5,0,0,0,0,0,0,0,94,0,0,0,0,0,0,0,15,196,0,0,21,196,0,0,5,0,0,0,168,130,0,0,187,0,0,0,0,0,0,0,70,196,0,0,75,196,0,0,2,0,0,0,0,0,0,0,97,0,0,0,0,0,0,0,80,196,0,0,85,196,0,0,5,0,0,0,180,130,0,0,106,0,0,0,0,0,0,0,107,196,0,0,111,196,0,0,3,0,0,0,0,0,0,0,153,0,0,0,0,0,0,0,153,196,0,0,162,196,0,0,2,0,0,0,0,0,0,0,80,66,69,87,0,0,0,0,193,196,0,0,198,196,0,0,7,0,0,0,188,130,0,0,152,0,0,0,0,0,0,0,214,196,0,0,224,196,0,0,2,0,0,0,0,0,0,0,161,0,0,0,0,0,0,0,252,196,0,0,0,197,0,0,5,0,0,0,0,0,0,0,159,0,0,0,0,0,0,0,21,197,0,0,25,197,0,0,5,0,0,0,196,130,0,0,71,78,80,65,0,0,0,0,71,197,0,0,76,197,0,0,4,0,0,0,204,130,0,0,0,0,1,0,1,0,0,0,124,197,0,0,134,197,0,0,4,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,166,197,0,0,176,197,0,0,4,0,0,0,0,0,0,0,2,0,1,0,1,0,0,0,205,197,0,0,215,197,0,0,4,0,0,0,0,0,0,0,3,0,1,0,1,0,0,0,249,197,0,0,3,198,0,0,4,0,0,0,0,0,0,0,4,0,1,0,1,0,0,0,34,198,0,0,41,198,0,0,4,0,0,0,0,0,0,0,5,0,1,0,1,0,0,0,58,198,0,0,65,198,0,0,4,0,0,0,0,0,0,0,6,0,1,0,1,0,0,0,84,198,0,0,94,198,0,0,2,0,0,0,0,0,0,0,7,0,1,0,1,0,0,0,120,198,0,0,129,198,0,0,2,0,0,0,0,0,0,0,8,0,1,0,1,0,0,0,153,198,0,0,163,198,0,0,4,0,0,0,0,0,0,0,9,0,1,0,1,0,0,0,195,198,0,0,205,198,0,0,4,0,0,0,0,0,0,0,10,0,1,0,1,0,0,0,234,198,0,0,244,198,0,0,4,0,0,0,0,0,0,0,11,0,1,0,1,0,0,0,22,199,0,0,32,199,0,0,4,0,0,0,0,0,0,0,12,0,1,0,1,0,0,0,63,199,0,0,73,199,0,0,4,0,0,0,0,0,0,0,13,0,1,0,1,0,0,0,105,199,0,0,115,199,0,0,4,0,0,0,0,0,0,0,14,0,1,0,1,0,0,0,144,199,0,0,154,199,0,0,4,0,0,0,0,0,0,0,15,0,1,0,1,0,0,0,188,199,0,0,198,199,0,0,4,0,0,0,0,0,0,0,16,0,1,0,1,0,0,0,229,199,0,0,241,199,0,0,4,0,0,0,0,0,0,0,17,0,1,0,1,0,0,0,18,200,0,0,27,200,0,0,2,0,0,0,0,0,0,0,16,80,83,80,1,0,0,0,36,200,0,0,53,200,0,0,4,0,0,0,0,0,0,0,18,0,1,0,1,0,0,0,89,200,0,0,106,200,0,0,4,0,0,0,0,0,0,0,80,83,80,24,1,0,0,0,145,200,0,0,162,200,0,0,4,0,0,0,0,0,0,0,80,83,80,32,1,0,0,0,201,200,0,0,218,200,0,0,4,0,0,0,0,0,0,0,19,0,1,0,1,0,0,0,1,201,0,0,9,201,0,0,4,0,0,0,0,0,0,0,20,0,1,0,1,0,0,0,41,201,0,0,51,201,0,0,4,0,0,0,0,0,0,0,21,0,1,0,1,0,0,0,88,201,0,0,98,201,0,0,4,0,0,0,0,0,0,0,22,0,1,0,1,0,0,0,138,201,0,0,148,201,0,0,4,0,0,0,0,0,0,0,23,0,1,0,1,0,0,0,185,201,0,0,195,201,0,0,4,0,0,0,0,0,0,0,24,0,1,0,1,0,0,0,235,201,0,0,246,201,0,0,4,0,0,0,0,0,0,0,25,0,1,0,1,0,0,0,43,202,0,0,51,202,0,0,4,0,0,0,0,0,0,0,26,0,1,0,1,0,0,0,90,202,0,0,96,202,0,0,4,0,0,0,0,0,0,0,27,0,1,0,1,0,0,0,107,202,0,0,121,202,0,0,4,0,0,0,0,0,0,0,0,16,1,0,1,0,0,0,145,202,0,0,158,202,0,0,2,0,0,0,0,0,0,0,1,16,1,0,1,0,0,0,178,202,0,0,192,202,0,0,2,0,0,0,0,0,0,0,2,16,1,0,1,0,0,0,206,202,0,0,220,202,0,0,2,0,0,0,0,0,0,0,3,16,1,0,1,0,0,0,239,202,0,0,253,202,0,0,2,0,0,0,0,0,0,0,4,16,1,0,1,0,0,0,16,203,0,0,29,203,0,0,2,0,0,0,0,0,0,0,5,16,1,0,1,0,0,0,48,203,0,0,65,203,0,0,2,0,0,0,0,0,0,0,6,16,1,0,1,0,0,0,90,203,0,0,99,203,0,0,2,0,0,0,0,0,0,0,7,16,1,0,1,0,0,0,115,203,0,0,125,203,0,0,2,0,0,0,0,0,0,0,8,16,1,0,1,0,0,0,140,203,0,0,149,203,0,0,2,0,0,0,0,0,0,0,9,16,1,0,1,0,0,0,164,203,0,0,174,203,0,0,2,0,0,0,0,0,0,0,10,16,1,0,1,0,0,0,193,203,0,0,202,203,0,0,2,0,0,0,0,0,0,0,11,16,1,0,1,0,0,0,224,203,0,0,235,203,0,0,2,0,0,0,0,0,0,0,12,16,1,0,1,0,0,0,247,203,0,0,0,204,0,0,2,0,0,0,0,0,0,0,13,16,1,0,1,0,0,0,26,204,0,0,36,204,0,0,2,0,0,0,0,0,0,0,14,16,1,0,1,0,0,0,58,204,0,0,71,204,0,0,2,0,0,0,0,0,0,0,15,16,1,0,1,0,0,0,84,204,0,0,98,204,0,0,2,0,0,0,0,0,0,0,16,16,1,0,1,0,0,0,128,204,0,0,142,204,0,0,2,0,0,0,0,0,0,0,17,16,1,0,1,0,0,0,174,204,0,0,188,204,0,0,2,0,0,0,0,0,0,0,18,16,1,0,1,0,0,0,218,204,0,0,228,204,0,0,2,0,0,0,0,0,0,0,76,80,72,84,1,0,0,0,247,204,0,0,4,205,0,0,2,0,0,0,0,0,0,0,19,16,1,0,1,0,0,0,39,205,0,0,53,205,0,0,2,0,0,0,0,0,0,0,20,16,1,0,1,0,0,0,67,205,0,0,79,205,0,0,2,0,0,0,0,0,0,0,21,16,1,0,1,0,0,0,104,205,0,0,116,205,0,0,2,0,0,0,0,0,0,0,22,16,1,0,1,0,0,0,141,205,0,0,153,205,0,0,2,0,0,0,0,0,0,0,23,16,1,0,1,0,0,0,178,205,0,0,196,205,0,0,2,0,0,0,0,0,0,0,24,16,1,0,1,0,0,0,227,205,0,0,245,205,0,0,2,0,0,0,0,0,0,0,25,16,1,0,1,0,0,0,20,206,0,0,33,206,0,0,2,0,0,0,0,0,0,0,26,16,1,0,1,0,0,0,59,206,0,0,77,206,0,0,2,0,0,0,0,0,0,0,27,16,1,0,1,0,0,0,114,206,0,0,128,206,0,0,2,0,0,0,0,0,0,0,28,16,1,0,1,0,0,0,149,206,0,0,160,206,0,0,2,0,0,0,0,0,0,0,29,16,1,0,1,0,0,0,172,206,0,0,186,206,0,0,2,0,0,0,0,0,0,0,32,67,70,65,1,0,0,0,205,206,0,0,215,206,0,0,2,0,0,0,0,0,0,0,32,73,75,79,1,0,0,0,243,206,0,0,1,207,0,0,2,0,0,0,0,0,0,0,32,75,84,68,1,0,0,0,24,207,0,0,34,207,0,0,2,0,0,0,0,0,0,0,32,68,65,82,1,0,0,0,62,207,0,0,76,207,0,0,2,0,0,0,0,0,0,0,71,55,50,54,1,0,0,0,94,207,0,0,107,207,0,0,2,0,0,0,0,0,0,0,65,77,73,86,1,0,0,0,133,207,0,0,144,207,0,0,2,0,0,0,0,0,0,0,0,32,1,0,1,0,0,0,165,207,0,0,172,207,0,0,2,0,0,0,0,0,0,0,1,32,1,0,1,0,0,0,212,207,0,0,219,207,0,0,2,0,0,0,0,0,0,0,0,48,1,0,1,0,0,0,1,208,0,0,8,208,0,0,2,0,0,0,0,0,0,0,1,48,1,0,1,0,0,0,30,208,0,0,37,208,0,0,2,0,0,0,0,0,0,0,0,64,1,0,1,0,0,0,59,208,0,0,68,208,0,0,2,0,0,0,0,0,0,0,1,64,1,0,1,0,0,0,80,208,0,0,95,208,0,0,2,0,0,0,0,0,0,0,2,64,1,0,1,0,0,0,110,208,0,0,119,208,0,0,2,0,0,0,0,0,0,0,3,64,1,0,1,0,0,0,128,208,0,0,137,208,0,0,2,0,0,0,0,0,0,0,0,80,1,0,1,0,0,0,146,208,0,0,150,208,0,0,2,0,0,0,0,0,0,0,1,80,1,0,1,0,0,0,175,208,0,0,179,208,0,0,2,0,0,0,0,0,0,0,2,80,1,0,1,0,0,0,204,208,0,0,208,208,0,0,2,0,0,0,0,0,0,0,3,80,1,0,1,0,0,0,236,208,0,0,240,208,0,0,2,0,0,0,0,0,0,0,4,80,1,0,1,0,0,0,235,55,1,0,2,209,0,0,6,0,0,0,0,0,0,0,5,80,1,0,1,0,0,0,31,209,0,0,38,209,0,0,2,0,0,0,0,0,0,0,6,80,1,0,1,0,0,0,45,209,0,0,53,209,0,0,2,0,0,0,0,0,0,0,7,80,1,0,1,0,0,0,62,209,0,0,68,209,0,0,2,0,0,0,0,0,0,0,8,80,1,0,1,0,0,0,90,209,0,0,96,209,0,0,2,0,0,0,0,0,0,0,9,80,1,0,1,0,0,0,118,209,0,0,124,209,0,0,2,0,0,0,0,0,0,0,10,80,1,0,1,0,0,0,173,209,0,0,179,209,0,0,2,0,0,0,0,0,0,0,11,80,1,0,1,0,0,0,228,209,0,0,237,209,0,0,2,0,0,0,0,0,0,0,12,80,1,0,1,0,0,0,254,209,0,0,3,210,0,0,4,0,0,0,0,0,0,0,13,80,1,0,1,0,0,0,36,210,0,0,43,210,0,0,2,0,0,0,0,0,0,0,14,80,1,0,1,0,0,0,96,210,0,0,103,210,0,0,2,0,0,0,0,0,0,0,15,80,1,0,1,0,0,0,112,210,0,0,120,210,0,0,4,0,0,0,0,0,0,0,16,80,1,0,1,0,0,0,128,210,0,0,133,210,0,0,4,0,0,0,0,0,0,0,17],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+20481);allocate([80,1,0,1,0,0,0,167,210,0,0,181,210,0,0,2,0,0,0,0,0,0,0,18,80,1,0,1,0,0,0,203,210,0,0,207,210,0,0,2,0,0,0,0,0,0,0,19,80,1,0,1,0,0,0,211,210,0,0,216,210,0,0,2,0,0,0,0,0,0,0,20,80,1,0,1,0,0,0,238,210,0,0,243,210,0,0,2,0,0,0,0,0,0,0,21,80,1,0,1,0,0,0,24,211,0,0,35,211,0,0,2,0,0,0,0,0,0,0,22,80,1,0,1,0,0,0,56,211,0,0,60,211,0,0,4,0,0,0,0,0,0,0,23,80,1,0,1,0,0,0,77,211,0,0,88,211,0,0,2,0,0,0,0,0,0,0,24,80,1,0,1,0,0,0,102,211,0,0,108,211,0,0,2,0,0,0,0,0,0,0,25,80,1,0,1,0,0,0,126,211,0,0,134,211,0,0,6,0,0,0,0,0,0,0,26,80,1,0,1,0,0,0,142,211,0,0,154,211,0,0,2,0,0,0,0,0,0,0,27,80,1,0,1,0,0,0,196,211,0,0,200,211,0,0,2,0,0,0,0,0,0,0,28,80,1,0,1,0,0,0,224,211,0,0,234,211,0,0,2,0,0,0,0,0,0,0,29,80,1,0,1,0,0,0,247,211,0,0,251,211,0,0,4,0,0,0,0,0,0,0,30,80,1,0,1,0,0,0,27,212,0,0,34,212,0,0,2,0,0,0,0,0,0,0,31,80,1,0,1,0,0,0,56,212,0,0,63,212,0,0,2,0,0,0,0,0,0,0,32,80,1,0,1,0,0,0,109,212,0,0,117,212,0,0,2,0,0,0,0,0,0,0,33,80,1,0,1,0,0,0,140,212,0,0,144,212,0,0,4,0,0,0,0,0,0,0,34,80,1,0,1,0,0,0,159,212,0,0,170,212,0,0,2,0,0,0,0,0,0,0,35,80,1,0,1,0,0,0,186,212,0,0,196,212,0,0,2,0,0,0,0,0,0,0,36,80,1,0,1,0,0,0,209,212,0,0,215,212,0,0,2,0,0,0,0,0,0,0,37,80,1,0,1,0,0,0,221,212,0,0,230,212,0,0,2,0,0,0,0,0,0,0,38,80,1,0,1,0,0,0,0,213,0,0,7,213,0,0,2,0,0,0,0,0,0,0,39,80,1,0,1,0,0,0,42,213,0,0,54,213,0,0,4,0,0,0,0,0,0,0,40,80,1,0,1,0,0,0,83,213,0,0,91,213,0,0,2,0,0,0,0,0,0,0,41,80,1,0,1,0,0,0,139,213,0,0,144,213,0,0,2,0,0,0,0,0,0,0,42,80,1,0,1,0,0,0,170,213,0,0,175,213,0,0,2,0,0,0,0,0,0,0,43,80,1,0,1,0,0,0,202,213,0,0,206,213,0,0,2,0,0,0,0,0,0,0,44,80,1,0,1,0,0,0,231,213,0,0,238,213,0,0,2,0,0,0,0,0,0,0,45,80,1,0,1,0,0,0,249,213,0,0,0,214,0,0,4,0,0,0,0,0,0,0,46,80,1,0,1,0,0,0,7,214,0,0,14,214,0,0,4,0,0,0,0,0,0,0,47,80,1,0,1,0,0,0,49,214,0,0,56,214,0,0,2,0,0,0,0,0,0,0,48,80,1,0,1,0,0,0,100,214,0,0,115,214,0,0,2,0,0,0,0,0,0,0,49,80,1,0,1,0,0,0,133,214,0,0,147,214,0,0,2,0,0,0,0,0,0,0,50,80,1,0,1,0,0,0,164,214,0,0,173,214,0,0,2,0,0,0,0,0,0,0,51,80,1,0,1,0,0,0,218,214,0,0,223,214,0,0,2,0,0,0,0,0,0,0,52,80,1,0,1,0,0,0,237,214,0,0,242,214,0,0,2,0,0,0,0,0,0,0,53,80,1,0,1,0,0,0,29,215,0,0,36,215,0,0,2,0,0,0,0,0,0,0,67,80,1,0,1,0,0,0,44,215,0,0,51,215,0,0,2,0,0,0,0,0,0,0,54,80,1,0,1,0,0,0,105,215,0,0,110,215,0,0,2,0,0,0,0,0,0,0,55,80,1,0,1,0,0,0,116,215,0,0,125,215,0,0,2,0,0,0,0,0,0,0,56,80,1,0,1,0,0,0,142,215,0,0,151,215,0,0,2,0,0,0,0,0,0,0,57,80,1,0,1,0,0,0,166,215,0,0,176,215,0,0,2,0,0,0,0,0,0,0,58,80,1,0,1,0,0,0,199,215,0,0,204,215,0,0,4,0,0,0,0,0,0,0,59,80,1,0,1,0,0,0,223,215,0,0,227,215,0,0,2,0,0,0,0,0,0,0,60,80,1,0,1,0,0,0,251,215,0,0,0,216,0,0,2,0,0,0,0,0,0,0,83,87,70,70,1,0,0,0,34,216,0,0,44,216,0,0,0,0,0,0,0,0,0,0,67,78,79,83,1,0,0,0,72,216,0,0,78,216,0,0,0,0,0,0,0,0,0,0,76,78,79,83,1,0,0,0,84,216,0,0,92,216,0,0,0,0,0,0,0,0,0,0,83,85,80,79,1,0,0,0,107,216,0,0,112,216,0,0,2,0,0,0,0,0,0,0,62,80,1,0,1,0,0,0,148,216,0,0,161,216,0,0,2,0,0,0,0,0,0,0,75,97,66,116,1,0,0,0,184,216,0,0,188,216,0,0,4,0,0,0,0,0,0,0,64,80,1,0,1,0,0,0,226,216,0,0,236,216,0,0,2,0,0,0,0,0,0,0,65,70,65,80,1,0,0,0,254,216,0,0,8,217,0,0,2,0,0,0,0,0,0,0,66,80,1,0,1,0,0,0,51,217,0,0,55,217,0,0,2,0,0,0,0,0,0,0,99,118,101,115,1,0,0,0,81,217,0,0,86,217,0,0,2,0,0,0,0,0,0,0,118,109,115,115,1,0,0,0,122,217,0,0,126,217,0,0,2,0,0,0,0,0,0,0,118,103,52,115,1,0,0,0,156,217,0,0,160,217,0,0,2,0,0,0,0,0,0,0,76,68,83,68,1,0,0,0,192,217,0,0,201,217,0,0,2,0,0,0,0,0,0,0,77,68,83,68,1,0,0,0,2,218,0,0,11,218,0,0,2,0,0,0,0,0,0,0,49,68,83,68,1,0,0,0,67,218,0,0,83,218,0,0,2,0,0,0,0,0,0,0,56,68,83,68,1,0,0,0,148,218,0,0,164,218,0,0,2,0,0,0,0,0,0,0,0,112,1,0,3,0,0,0,228,218,0,0,241,218,0,0,0,0,1,0,0,0,0,0,1,112,1,0,3,0,0,0,255,218,0,0,12,219,0,0,0,0,1,0,0,0,0,0,2,112,1,0,3,0,0,0,26,219,0,0,31,219,0,0,0,0,2,0,0,0,0,0,3,112,1,0,3,0,0,0,46,219,0,0,51,219,0,0,0,0,1,0,0,0,0,0,32,83,83,65,3,0,0,0,56,219,0,0,60,219,0,0,0,0,2,0,0,0,0,0,4,112,1,0,3,0,0,0,88,219,0,0,92,219,0,0,0,0,2,0,0,0,0,0,5,112,1,0,3,0,0,0,124,219,0,0,133,219,0,0,0,0,2,0,0,0,0,0,6,112,1,0,3,0,0,0,142,219,0,0,160,219,0,0,0,0,1,0,0,0,0,0,7,112,1,0,3,0,0,0,203,219,0,0,216,219,0,0,0,0,0,0,0,0,0,0,8,112,1,0,3,0,0,0,229,219,0,0,233,219,0,0,0,0,2,0,0,0,0,0,112,105,82,83,3,0,0,0,14,220,0,0,21,220,0,0,0,0,2,0,0,0,0,0,68,86,68,109,3,0,0,0,37,220,0,0,46,220,0,0,0,0,2,0,0,0,0,0,50,76,80,77,3,0,0,0,64,220,0,0,69,220,0,0,0,0,2,0,0,0,0,0,56,48,54,99,3,0,0,0,83,220,0,0,91,220,0,0,0,0,2,0,0,0,0,0,66,85,83,74,3,0,0,0,115,220,0,0,123,220,0,0,0,0,2,0,0,0,0,0,83,74,104,80,3,0,0,0,140,220,0,0,144,220,0,0,0,0,2,0,0,0,0,0,73,77,65,83,3,0,0,0,188,220,0,0,193,220,0,0,0,0,2,0,0,0,0,0,84,88,84,82,3,0,0,0,207,220,0,0,216,220,0,0,0,0,2,0,0,0,0,0,76,84,112,83,3,0,0,0,234,220,0,0,238,220,0,0,0,0,2,0,0,0,0,0,49,86,98,83,3,0,0,0,5,221,0,0,16,221,0,0,0,0,2,0,0,0,0,0,86,98,117,83,3,0,0,0,38,221,0,0,48,221,0,0,0,0,2,0,0,0,0,0,114,108,80,86,3,0,0,0,67,221,0,0,75,221,0,0,0,0,2,0,0,0,0,0,84,84,86,87,3,0,0,0,92,221,0,0,99,221,0,0,0,0,2,0,0,0,0,0,88,84,68,66,3,0,0,0,115,221,0,0,134,221,0,0,0,0,2,0,0,0,0,0,0,128,1,0,2,0,0,0,153,221,0,0,157,221,0,0,0,0,0,0,212,130,0,0,84,88,84,66,0,0,0,0,218,221,0,0,226,221,0,0,1,0,0,0,0,0,0,0,78,73,66,88,0,0,0,0,238,221,0,0,243,221,0,0,1,0,0,0,0,0,0,0,70,68,73,0,0,0,0,0,8,222,0,0,12,222,0,0,1,0,0,0,0,0,0,0,70,84,79,0,2,0,0,0,25,222,0,0,29,222,0,0,0,0,0,0,224,130,0,0,65,86,76,75,2,0,0,0,71,222,0,0,75,222,0,0,0,0,0,0,0,0,0,0,86,65,78,68,2,0,0,0,118,222,0,0,133,222,0,0,0,0,0,0,0,0,0,0,51,68,73,84,2,0,0,0,148,222,0,0,158,222,0,0,0,0,0,0,0,0,0,0,65,84,65,68,2,0,0,0,177,222,0,0,186,222,0,0,0,0,0,0,232,130,0,0,178,0,0,0,0,0,0,0,223,222,0,0,246,222,0,0,5,0,0,0,0,0,0,0,171,0,0,0,0,0,0,0,24,223,0,0,45,223,0,0,2,0,0,0,0,0,0,0,180,0,0,0,0,0,0,0,72,223,0,0,87,223,0,0,7,0,0,0,0,0,0,0,172,0,0,0,0,0,0,0,117,223,0,0,132,223,0,0,2,0,0,0,0,0,0,0,175,0,0,0,0,0,0,0,159,223,0,0,175,223,0,0,2,0,0,0,0,0,0,0,179,0,0,0,0,0,0,0,235,223,0,0,0,224,0,0,2,0,0,0,0,0,0,0,182,0,0,0,0,0,0,0,59,224,0,0,75,224,0,0,2,0,0,0,0,0,0,0,181,0,0,0,0,0,0,0,118,224,0,0,133,224,0,0,2,0,0,0,0,0,0,0,173,0,0,0,0,0,0,0,157,224,0,0,173,224,0,0,7,0,0,0,0,0,0,0,65,77,73,86,1,0,0,0,194,224,0,0,199,224,0,0,2,0,0,0,0,0,0,0,30,16,1,0,1,0,0,0,236,224,0,0,199,224,0,0,2,0,0,0,0,0,0,0,61,80,1,0,1,0,0,0,2,225,0,0,18,225,0,0,2,0,0,0,0,0,0,0,65,80,1,0,1,0,0,0,70,225,0,0,91,225,0,0,2,0,0,0,0,0,0,0,28,0,1,0,1,0,0,0,150,225,0,0,178,225,0,0,4,0,0,0,0,0,0,0,29,0,1,0,1,0,0,0,233,225,0,0,5,226,0,0,4,0,0,0,0,0,0,0,30,0,1,0,1,0,0,0,60,226,0,0,88,226,0,0,4,0,0,0,0,0,0,0,63,80,1,0,1,0,0,0,140,226,0,0,155,226,0,0,4,0,0,0,0,0,0,0,107,175,0,0,0,0,0,0,34,182,0,0,0,0,0,0,106,183,0,0,0,0,0,0,102,194,0,0,0,0,0,0,179,194,0,0,0,0,0,0,7,195,0,0,0,0,0,0,137,195,0,0,0,0,0,0,44,196,0,0,58,196,0,0,0,0,0,0,96,196,0,0,0,0,0,0,203,196,0,0,0,0,0,0,51,197,0,0,0,0,0,0,137,195,0,0,0,0,0,0,171,221,0,0,199,221,0,0,0,0,0,0,43,222,0,0,0,0,0,0,198,222,0,0,0,0,0,0,209,226,0,0,214,226,0,0,0,0,0,0,28,0,0,0,34,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,131,0,0,132,131,0,0,144,27,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,227,0,0,3,0,0,0,8,0,0,0,100,31,54,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,66,0,0,0,104,227,0,0,66,2,0,0,113,227,0,0,77,0,0,0,134,227,0,0,88,0,0,0,139,227,0,0,100,0,0,0,148,227,0,0,110,0,0,0,153,227,0,0,110,8,0,0,161,227,0,0,122,0,0,0,175,227,0,0,122,8,0,0,186,227,0,0,144,0,0,0,203,227,0,0,244,0,0,0,214,227,0,0,244,8,0,0,236,227,0,0,44,0,0,0,253,227,0,0,157,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,2,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,6,0,0,0,12,0,0,0,24,0,0,0,48,0,0,0,255,255,255,127,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,12,0,0,0,11,0,0,0,10,0,0,0,11,0,0,0,16,0,0,0,11,0,0,0,40,0,0,0,33,0,0,0,24,0,0,0,11,0,0,0,20,0,0,0,11,0,0,0,32,0,0,0,11,0,0,0,80,0,0,0,33,0,0,0,18,0,0,0,11,0,0,0,15,0,0,0,11,0,0,0,64,0,0,0,33,0,0,0,160,0,0,0,99,0,0,0,4,0,0,0,3,0,0,0,3,0,0,0,2,0,0,0,2,0,0,0,1,0,0,0,152,23,1,0,4,0,0,0,200,0,0,0,100,31,54,0,4,0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,5,0,0,0,6,0,0,0,0,0,0,0,108,166,1,0,105,0,0,0,120,0,0,0,134,0,0,0,149,0,0,0,152,0,0,0,146,1,0,0,228,1,0,0,243,1,0,0,1,2,0,0,148,2,0,0,16,2,0,0,31,2,0,0,45,2,0,0,206,2,0,0,21,1,0,0,36,1,0,0,50,1,0,0,65,1,0,0,68,1,0,0,180,1,0,0,8,3,0,0,23,3,0,0,37,3,0,0,163,2,0,0,52,3,0,0,67,3,0,0,81,3,0,0,221,2,0,0,166,0,0,0,181,0,0,0,195,0,0,0,210,0,0,0,213,0,0,0,161,1,0,0,60,2,0,0,75,2,0,0,89,2,0,0,178,2,0,0,104,2,0,0,119,2,0,0,133,2,0,0,236,2,0,0,82,1,0,0,97,1,0,0,111,1,0,0,126,1,0,0,129,1,0,0,195,1,0,0,96,3,0,0,111,3,0,0,125,3,0,0,187,2,0,0,140,3,0,0,155,3,0,0,169,3,0,0,245,2,0,0,227,0,0,0,237,0,0,0,247,0,0,0,1,1,0,0,10,1,0,0,170,1,0,0,184,3,0,0,194,3,0,0,204,3,0,0,196,2,0,0,214,3,0,0,224,3,0,0,234,3,0,0,254,2,0,0,93,128,1,0,96,128,1,0,107,128,1,0,110,128,1,0,122,128,1,0,125,128,1,0,138,128,1,0,142,128,1,0,156,128,1,0,159,128,1,0,169,128,1,0,172,128,1,0,183,128,1,0,187,128,1,0,208,128,1,0,212,128,1,0,234,128,1,0,237,128,1,0,249,128,1,0,252,128,1,0,6,129,1,0,9,129,1,0,20,129,1,0,23,129,1,0,34,129,1,0,38,129,1,0,53,129,1,0,57,129,1,0,74,129,1,0,78,129,1,0,94,129,1,0,98,129,1,0,112,129,1,0,116,129,1,0,132,129,1,0,136,129,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,151,129,1,0,154,129,1,0,167,129,1,0,170,129,1,0,184,129,1,0,187,129,1,0,197,129,1,0,200,129,1,0,211,129,1,0,215,129,1,0,236,129,1,0,240,129,1,0,6,130,1,0,11,130,1,0,25,131,1,0,3,0,0,0,0,0,0,0,100,31,54,0,40,0,0,0,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,132,1,0,3,0,0,0,0,0,0,0,100,31,54,0,4,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,255,255,255,255,65,196,52,0,65,208,0,0,17,196,0,0,3,226,0,0,9,253,0,0,2,40,0,0,2,34,0,0,7,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,250,0,0,21,219,0,0,5,201,0,0,21,213,0,0,5,207,0,0,22,51,0,0,6,39,0,0,18,155,0,0,20,192,0,0,20,153,0,0,20,147,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,213,0,0,5,207,0,0,21,213,0,0,5,207,0,0,21,213,0,0,5,207,0,0,254,137,1,0,208,2,0,0,224,1,0,0,3,138,1,0,208,2,0,0,64,2,0,0,7,138,1,0,96,1,0,0,240,0,0,0,13,138,1,0,96,1,0,0,32,1,0,0,18,138,1,0,128,2,0,0,224,1,0,0,24,138,1,0,0,3,0,0,64,2,0,0,29,138,1,0,96,1,0,0,240,0,0,0,34,138,1,0,96,1,0,0,240,0,0,0,44,138,1,0,128,0,0,0,96,0,0,0,50,138,1,0,176,0,0,0,144,0,0,0,55,138,1,0,96,1,0,0,32,1,0,0,59,138,1,0,192,2,0,0,64,2,0,0,64,138,1,0,128,5,0,0,128,4,0,0,70,138,1,0,160,0,0,0,120,0,0,0,76,138,1,0,64,1,0,0,240,0,0,0,81,138,1,0,128,2,0,0,224,1,0,0,85,138,1,0,32,3,0,0,88,2,0,0,90,138,1,0,0,4,0,0,0,3,0,0,94,138,1,0,64,6,0,0,176,4,0,0,99,138,1,0,0,8,0,0,0,6,0,0,104,138,1,0,0,5,0,0,0,4,0,0,109,138,1,0,0,10,0,0,0,8,0,0,115,138,1,0,0,20,0,0,0,16,0,0,121,138,1,0,84,3,0,0,224,1,0,0,126,138,1,0,86,5,0,0,0,3,0,0,131,138,1,0,64,6,0,0,0,4,0,0,137,138,1,0,128,7,0,0,176,4,0,0,143,138,1,0,0,10,0,0,64,6,0,0,149,138,1,0,128,12,0,0,0,8,0,0,156,138,1,0,0,15,0,0,96,9,0,0,163,138,1,0,0,25,0,0,0,16,0,0,170,138,1,0,0,30,0,0,192,18,0,0,177,138,1,0,64,1,0,0,200,0,0,0,181,138,1,0,128,2,0,0,94,1,0,0,185,138,1,0,84,3,0,0,224,1,0,0,191,138,1,0,0,5,0,0,208,2,0,0,197,138,1,0,128,7,0,0,56,4,0,0,204,138,1,0,0,8,0,0,56,4,0,0,207,138,1,0,0,8,0,0,56,4,0,0,213,138,1,0,206,7,0,0,56,4,0,0,220,138,1,0,0,8,0,0,90,3,0,0,228,138,1,0,0,16,0,0,112,8,0,0,231,138,1,0,0,16,0,0,112,8,0,0,237,138,1,0,156,15,0,0,112,8,0,0,244,138,1,0,0,16,0,0,180,6,0,0,252,138,1,0,128,2,0,0,104,1,0,0,0,139,1,0,240,0,0,0,160,0,0,0,6,139,1,0,144,1,0,0,240,0,0,0,12,139,1,0,176,1,0,0,240,0,0,0,19,139,1,0,224,1,0,0,64,1,0,0,24,139,1,0,192,3,0,0,28,2,0,0,28,139,1,0,0,15,0,0,112,8,0,0,36,139,1,0,0,30,0,0,224,16,0,0,254,137,1,0,48,117,0,0,233,3,0,0,3,138,1,0,25,0,0,0,1,0,0,0,7,138,1,0,48,117,0,0,233,3,0,0,13,138,1,0,25,0,0,0,1,0,0,0,18,138,1,0,48,117,0,0,233,3,0,0,24,138,1,0,25,0,0,0,1,0,0,0,29,138,1,0,24,0,0,0,1,0,0,0,34,138,1,0,192,93,0,0,233,3,0,0,127,139,1,0,240,248,255,0,137,139,1,0,250,235,215,0,150,139,1,0,0,255,255,0,155,139,1,0,127,255,212,0,166,139,1,0,240,255,255,0,172,139,1,0,245,245,220,0,178,139,1,0,255,228,196,0,185,139,1,0,0,0,0,0,191,139,1,0,255,235,205,0,206,139,1,0,0,0,255,0,211,139,1,0,138,43,226,0,222,139,1,0,165,42,42,0,228,139,1,0,222,184,135,0,238,139,1,0,95,158,160,0,248,139,1,0,127,255,0,0,3,140,1,0,210,105,30,0,13,140,1,0,255,127,80,0,19,140,1,0,100,149,237,0,34,140,1,0,255,248,220,0,43,140,1,0,220,20,60,0,51,140,1,0,0,255,255,0,56,140,1,0,0,0,139,0,65,140,1,0,0,139,139,0,74,140,1,0,184,134,11,0,88,140,1,0,169,169,169,0,97,140,1,0,0,100,0,0,107,140,1,0,189,183,107,0,117,140,1,0,139,0,139,0,129,140,1,0,85,107,47,0,144,140,1,0,255,140,0,0,155,140,1,0,153,50,204,0,166,140,1,0,139,0,0,0,174,140,1,0,233,150,122,0,185,140,1,0,143,188,143,0,198,140,1,0,72,61,139,0,212,140,1,0,47,79,79,0,226,140,1,0,0,206,209,0,240,140,1,0,148,0,211,0,251,140,1,0,255,20,147,0,4,141,1,0,0,191,255,0,16,141,1,0,105,105,105,0,24,141,1,0,30,144,255,0,35,141,1,0,178,34,34,0,45,141,1,0,255,250,240,0,57,141,1,0,34,139,34,0,69,141,1,0,255,0,255,0,77,141,1,0,220,220,220,0,87,141,1,0,248,248,255,0,98,141,1,0,255,215,0,0,103,141,1,0,218,165,32,0,113,141,1,0,128,128,128,0,118,141,1,0,0,128,0,0,124,141,1,0,173,255,47,0,136,141,1,0,240,255,240,0,145,141,1,0,255,105,180,0,153,141,1,0,205,92,92,0,163,141,1,0,75,0,130,0,170,141,1,0,255,255,240,0,176,141,1,0,240,230,140,0,182,141,1,0,230,230,250,0,191,141,1,0,255,240,245,0,205,141,1,0,124,252,0,0,215,141,1,0,255,250,205,0,228,141,1,0,173,216,230,0,238,141,1,0,240,128,128,0,249,141,1,0,224,255,255,0,3,142,1,0,250,250,210,0,24,142,1,0,144,238,144,0,35,142,1,0,211,211,211,0,45,142,1,0,255,182,193,0,55,142,1,0,255,160,122,0,67,142,1,0,32,178,170,0,81,142,1,0,135,206,250,0,94,142,1,0,119,136,153,0,109,142,1,0,176,196,222,0,124,142,1,0,255,255,224,0,136,142,1,0,0,255,0,0,141,142,1,0,50,205,50,0,151,142,1,0,250,240,230,0,157,142,1,0,255,0,255,0,165,142,1,0,128,0,0,0,172,142,1,0,102,205,170,0,189,142,1,0,0,0,205,0,200,142,1,0,186,85,211,0,213,142,1,0,147,112,216,0,226,142,1,0,60,179,113,0,241,142,1,0,123,104,238,0,1,143,1,0,0,250,154,0,19,143,1,0,72,209,204,0,35,143,1,0,199,21,133,0,51,143,1,0,25,25,112,0,64,143,1,0,245,255,250,0,74,143,1,0,255,228,225,0,84,143,1,0,255,228,181,0,93,143,1,0,255,222,173,0,105,143,1,0,0,0,128,0,110,143,1,0,253,245,230,0,118,143,1,0,128,128,0,0,124,143,1,0,107,142,35,0,134,143,1,0,255,165,0,0,141,143,1,0,255,69,0,0,151,143,1,0,218,112,214,0,158,143,1,0,238,232,170,0,172,143,1,0,152,251,152,0,182,143,1,0,175,238,238,0,196,143,1,0,216,112,147,0,210,143,1,0,255,239,213,0,221,143,1,0,255,218,185,0,231,143,1,0,205,133,63,0,236,143,1,0,255,192,203,0,241,143,1,0,221,160,221,0,246,143,1,0,176,224,230,0,1,144,1,0,128,0,128,0,8,144,1,0,255,0,0,0,12,144,1,0,188,143,143,0,22,144,1,0,65,105,225,0,32,144,1,0,139,69,19,0,44,144,1,0,250,128,114,0,51,144,1,0,244,164,96,0,62,144,1,0,46,139,87,0,71,144,1,0,255,245,238,0,80,144,1,0,160,82,45,0,87,144,1,0,192,192,192,0,94,144,1,0,135,206,235,0,102,144,1,0,106,90,205,0,112,144,1,0,112,128,144,0,122,144,1,0,255,250,250,0,127,144,1,0,0,255,127,0,139,144,1,0,70,130,180,0,149,144,1,0,210,180,140,0,153,144,1,0,0,128,128,0,158,144,1,0,216,191,216,0,166,144,1,0,255,99,71,0,173,144,1,0,64,224,208,0,183,144,1,0,238,130,238,0,190,144,1,0,245,222,179,0,196,144,1,0,255,255,255,0,202,144,1,0,245,245,245,0,213,144,1,0,255,255,0,0,220,144,1,0,154,205,50,0,95,145,1,0,3,1,1,16,32,56,33,56,34,56,0,0,0,0,0,0,103,145,1,0,3,1,0,0,36,56,76,56,140,56,0,0,0,0,0,0,111,145,1,0,3,0,0,32,40,56,72,56,104,56,0,0,0,0,0,0,117,145,1,0,3,0,0,32,104,56,72,56,40,56,0,0,0,0,0,0,123,145,1,0,3,1,0,16,32,56,33,56,34,56,0,0,0,0,0,0,131,145,1,0,3,0,0,16,32,56,33,56,34,56,0,0,0,0,0,0,139,145,1,0,3,2,2,16,32,56,33,56,34,56,0,0,0,0,0,0,147,145,1,0,3,2,0,16,32,56,33,56,34,56,0,0,0,0,0,0,155,145,1,0,1,0,0,64,32,56,0,0,0,0,0,0,160,145,1,0,169,145,1,0,1,0,0,4,32,0,0,0,0,0,0,0,0,0,0,0,175,145,1,0,1,0,0,4,32,7,0,0,0,0,0,0,0,0,0,0,181,145,1,0,1,0,0,2,32,56,0,0,0,0,0,0,0,0,0,0,186,145,1,0,3,1,1,16,32,56,33,56,34,56,0,0,0,0,0,0,195,145,1,0,3,1,0,16,32,56,33,56,34,56,0,0,0,0,0,0,204,145,1,0,3,0,0,16,32,56,33,56,34,56,0,0,0,0,0,0,213,145,1,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,220,145,1,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,229,145,1,0,3,1,0,0,68,56,44,56,108,56,0,0,0,0,0,0,237,145,1,0,3,2,0,0,76,56,52,56,148,56,0,0,0,0,0,0,247,145,1,0,3,0,0,96,32,16,32,19,32,14,0,0,0,0,0,0,252,145,1,0,3,0,0,36,140,0,76,8,44,0,0,0,0,0,0,0,1,146,1,0,3,0,0,96,32,0,32,9,32,3,0,0,0,0,0,0,11,146,1,0,3,0,0,96,32,14,32,19,32,16,0,0,0,0,0,0,16,146,1,0,3,0,0,36,44,0,76,8,140,0,0,0,0,0,0,0,21,146,1,0,3,0,0,96,32,3,32,9,32,0,0,0,0,0,0,0,31,146,1,0,3,1,1,16,32,56,37,56,69,56,0,0,0,0,0,0,36,146,1,0,3,1,1,16,32,56,69,56,37,56,0,0,0,0,0,0,41,146,1,0,4,0,0,160,76,56,108,56,140,56,44,56,0,0,0,0,46,146,1,0,4,0,0,160,44,56,76,56,108,56,140,56,0,0,0,0,51,146,1,0,4,0,0,160,140,56,108,56,76,56,44,56,0,0,0,0,56,146,1,0,4,0,0,160,108,56,76,56,44,56,140,56,0,0,0,0,61,146,1,0,1,0,0,1,36,120,0,0,0,0,0,0,70,146,1,0,76,146,1,0,1,0,0,0,36,120,0,0,0,0,0,0,85,146,1,0,91,146,1,0,3,0,1,16,32,56,33,56,34,56,0,0,0,0,0,0,99,146,1,0,3,0,1,16,32,56,33,56,34,56,0,0,0,0,0,0,108,146,1,0,4,1,1,144,32,56,33,56,34,56,35,56,0,0,0,0,117,146,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,128,146,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,140,146,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,152,146,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,163,146,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,173,146,1,0,3,0,0,33,52,120,116,120,180,120,0,0,0,0,0,0,181,146,1,0,3,0,0,32,52,120,116,120,180,120,0,0,0,0,0,0,189,146,1,0,3,0,0,33,4,35,36,45,36,32,0,0,0,0,0,0,198,146,1,0,3,0,0,32,68,35,36,45,36,32,0,0,0,0,0,0,207,146,1,0,3,0,0,33,4,34,36,37,36,32,0,0,0,0,0,0,216,146,1,0,3,0,0,32,68,34,36,37,36,32,0,0,0,0,0,0,225,146,1,0,3,0,0,33,36,32,36,45,4,35,0,0,0,0,0,0,234,146,1,0,3,0,0,32,36,32,36,45,68,35,0,0,0,0,0,0,243,146,1,0,3,0,0,33,36,32,36,37,4,34,0,0,0,0,0,0,252,146,1,0,3,0,0,32,36,32,36,37,68,34,0,0,0,0,0,0,5,147,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,16,147,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,27,147,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,37,147,1,0,3,1,1,16,36,120,37,120,38,120,0,0,0,0,0,0,49,147,1,0,3,1,1,17,36,120,37,120,38,120,0,0,0,0,0,0,61,147,1,0,3,1,0,16,36,120,37,120,38,120,0,0,0,0,0,0,73,147,1,0,3,1,0,17,36,120,37,120,38,120,0,0,0,0,0,0,85,147,1,0,3,0,0,16,36,120,37,120,38,120,0,0,0,0,0,0,97,147,1,0,3,0,0,17,36,120,37,120,38,120,0,0,0,0,0,0,109,147,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,121,147,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,131,147,1,0,3,0,0,32,68,24,36,28,36,24,0,0,0,0,0,0,140,147,1,0,3,0,0,33,4,24,36,28,36,24,0,0,0,0,0,0,149,147,1,0,3,0,0,32,36,24,36,28,68,24,0,0,0,0,0,0,158,147,1,0,3,0,0,33,36,24,36,28,4,24,0,0,0,0,0,0,167,147,1,0,2,0,0,128,36,56,68,56,0,0,0,0,171,147,1,0,178,147,1,0,3,0,0,33,180,120,116,120,52,120,0,0,0,0,0,0,186,147,1,0,3,0,0,32,180,120,116,120,52,120,0,0,0,0,0,0,194,147,1,0,3,1,1,17,36,64,37,64,38,64,0,0,0,0,0,0,205,147,1,0,3,1,1,16,36,64,37,64,38,64,0,0,0,0,0,0,216,147,1,0,3,1,1,17,36,72,37,72,38,72,0,0,0,0,0,0,228,147,1,0,3,1,1,16,36,72,37,72,38,72,0,0,0,0,0,0,240,147,1,0,3,1,0,17,36,72,37,72,38,72,0,0,0,0,0,0,252,147,1,0,3,1,0,16,36,72,37,72,38,72,0,0,0,0,0,0,8,148,1,0,3,0,0,17,36,64,37,64,38,64,0,0,0,0,0,0,19,148,1,0,3,0,0,16,36,64,37,64,38,64,0,0,0,0,0,0,30,148,1,0,3,0,0,17,36,72,37,72,38,72,0,0,0,0,0,0,42,148,1,0,3,0,0,16,36,72,37,72,38,72,0,0,0,0,0,0,54,148,1,0,3,1,0,17,36,64,37,64,38,64,0,0,0,0,0,0,65,148,1,0,3,1,0,16,36,64,37,64,38,64,0,0,0,0,0,0,76,148,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,84,148,1,0,3,0,0,48,34,56,32,56,33,56,0,0,0,0,0,0,89,148,1,0,3,0,0,49,38,64,36,64,37,64,0,0,0,0,0,0,97,148,1,0,3,0,0,48,38,64,36,64,37,64,0,0,0,0,0,0,105,148,1,0,3,0,0,49,38,72,36,72,37,72,0,0,0,0,0,0,114,148,1,0,3,0,0,48,38,72,36,72,37,72,0,0,0,0,0,0,123,148,1,0,3,0,0,49,38,120,36,120,37,120,0,0,0,0,0,0,132,148,1,0,3,0,0,48,38,120,36,120,37,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,141,148,1,0,4,1,1,145,36,64,37,64,38,64,39,64,0,0,0,0,153,148,1,0,4,1,1,144,36,64,37,64,38,64,39,64,0,0,0,0,165,148,1,0,4,1,0,145,36,64,37,64,38,64,39,64,0,0,0,0,177,148,1,0,4,1,0,144,36,64,37,64,38,64,39,64,0,0,0,0,189,148,1,0,4,0,0,145,36,64,37,64,38,64,39,64,0,0,0,0,201,148,1,0,4,0,0,144,36,64,37,64,38,64,39,64,0,0,0,0,213,148,1,0,4,1,1,145,36,72,37,72,38,72,39,72,0,0,0,0,226,148,1,0,4,1,1,144,36,72,37,72,38,72,39,72,0,0,0,0,239,148,1,0,4,1,0,145,36,72,37,72,38,72,39,72,0,0,0,0,252,148,1,0,4,1,0,144,36,72,37,72,38,72,39,72,0,0,0,0,9,149,1,0,4,0,0,145,36,72,37,72,38,72,39,72,0,0,0,0,22,149,1,0,4,0,0,144,36,72,37,72,38,72,39,72,0,0,0,0,35,149,1,0,4,1,1,145,36,120,37,120,38,120,39,120,0,0,0,0,48,149,1,0,4,1,1,144,36,120,37,120,38,120,39,120,0,0,0,0,61,149,1,0,4,1,0,145,36,120,37,120,38,120,39,120,0,0,0,0,74,149,1,0,4,1,0,144,36,120,37,120,38,120,39,120,0,0,0,0,87,149,1,0,4,0,0,145,36,120,37,120,38,120,39,120,0,0,0,0,100,149,1,0,4,0,0,144,36,120,37,120,38,120,39,120,0,0,0,0,113,149,1,0,0,1,1,8,0,0,0,0,0,0,0,0,0,0,0,0,119,149,1,0,3,0,0,0,52,92,116,92,180,92,0,0,0,0,0,0,127,149,1,0,3,0,0,1,52,92,116,92,180,92,0,0,0,0,0,0,135,149,1,0,3,1,0,16,32,56,37,56,69,56,0,0,0,0,0,0,140,149,1,0,3,1,0,16,36,72,45,72,109,72,0,0,0,0,0,0,147,149,1,0,3,1,0,17,36,72,45,72,109,72,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,154,149,1,0,3,1,0,0,36,56,76,56,140,56,0,0,0,0,0,0,162,149,1,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,166,149,1,0,2,0,0,129,44,120,108,120,0,0,0,0,0,0,0,0,173,149,1,0,2,0,0,128,44,120,108,120,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,180,149,1,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,184,149,1,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,189,149,1,0,0,1,1,8],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+30721);allocate([201,149,1,0,4,0,0,161,60,120,124,120,188,120,252,120,0,0,0,0,210,149,1,0,4,0,0,160,60,120,124,120,188,120,252,120,0,0,0,0,219,149,1,0,4,0,0,161,188,120,124,120,60,120,252,120,0,0,0,0,228,149,1,0,4,0,0,160,188,120,124,120,60,120,252,120,0,0,0,0,237,149,1,0,3,0,0,32,76,56,108,56,140,56,0,0,0,0,0,0,242,149,1,0,3,0,0,32,44,56,76,56,108,56,0,0,0,0,0,0,247,149,1,0,3,0,0,32,140,56,108,56,76,56,0,0,0,0,0,0,252,149,1,0,3,0,0,32,108,56,76,56,44,56,0,0,0,0,0,0,1,150,1,0,4,0,0,144,32,56,33,56,34,56,35,56,0,0,0,0,10,150,1,0,4,1,0,144,32,56,33,56,34,56,35,56,0,0,0,0,19,150,1,0,3,1,1,17,36,88,37,88,38,88,0,0,0,0,0,0,31,150,1,0,3,1,1,16,36,88,37,88,38,88,0,0,0,0,0,0,43,150,1,0,3,1,1,17,36,104,37,104,38,104,0,0,0,0,0,0,55,150,1,0,3,1,1,16,36,104,37,104,38,104,0,0,0,0,0,0,67,150,1,0,3,1,0,17,36,88,37,88,38,88,0,0,0,0,0,0,79,150,1,0,3,1,0,16,36,88,37,88,38,88,0,0,0,0,0,0,91,150,1,0,3,1,0,17,36,104,37,104,38,104,0,0,0,0,0,0,103,150,1,0,3,1,0,16,36,104,37,104,38,104,0,0,0,0,0,0,115,150,1,0,3,0,0,17,36,88,37,88,38,88,0,0,0,0,0,0,127,150,1,0,3,0,0,16,36,88,37,88,38,88,0,0,0,0,0,0,139,150,1,0,3,0,0,17,36,104,37,104,38,104,0,0,0,0,0,0,151,150,1,0,3,0,0,16,36,104,37,104,38,104,0,0,0,0,0,0,163,150,1,0,3,0,0,49,38,88,36,88,37,88,0,0,0,0,0,0,172,150,1,0,3,0,0,48,38,88,36,88,37,88,0,0,0,0,0,0,181,150,1,0,3,0,0,49,38,104,36,104,37,104,0,0,0,0,0,0,190,150,1,0,3,0,0,48,38,104,36,104,37,104,0,0,0,0,0,0,199,150,1,0,4,0,0,176,34,56,32,56,33,56,35,56,0,0,0,0,205,150,1,0,4,0,0,177,38,120,36,120,37,120,39,120,0,0,0,0,215,150,1,0,4,0,0,176,38,120,36,120,37,120,39,120,0,0,0,0,225,150,1,0,3,2,0,16,32,56,33,56,34,56,0,0,0,0,0,0,234,150,1,0,3,0,0,32,0,8,0,24,0,8,0,0,0,0,0,0,246,150,1,0,3,0,0,32,0,8,0,24,0,8,0,0,0,0,0,0,2,151,1,0,3,0,0,32,0,8,0,24,0,8,0,0,0,0,0,0,14,151,1,0,3,0,0,32,0,8,0,24,0,8,0,0,0,0,0,0,26,151,1,0,3,0,0,32,4,24,4,56,4,24,0,0,0,0,0,0,41,151,1,0,3,0,0,33,4,24,4,56,4,24,0,0,0,0,0,0,56,151,1,0,3,0,0,32,4,24,4,56,4,24,0,0,0,0,0,0,71,151,1,0,3,0,0,33,4,24,4,56,4,24,0,0,0,0,0,0,86,151,1,0,3,0,0,32,4,24,4,56,4,24,0,0,0,0,0,0,101,151,1,0,3,0,0,33,4,24,4,56,4,24,0,0,0,0,0,0,116,151,1,0,3,0,0,32,4,24,4,56,4,24,0,0,0,0,0,0,131,151,1,0,3,0,0,33,4,24,4,56,4,24,0,0,0,0,0,0,146,151,1,0,3,0,1,16,36,72,37,72,38,72,0,0,0,0,0,0,158,151,1,0,3,0,1,17,36,72,37,72,38,72,0,0,0,0,0,0,170,151,1,0,3,0,1,16,36,88,37,88,38,88,0,0,0,0,0,0,182,151,1,0,3,0,1,17,36,88,37,88,38,88,0,0,0,0,0,0,194,151,1,0,4,0,0,128,124,120,188,120,252,120,60,120,0,0,0,0,203,151,1,0,4,0,0,129,124,120,188,120,252,120,60,120,0,0,0,0,212,151,1,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,117,56,0,0,0,0,0,0,8,0,0,0,0,0,0,0,5,0,0,0,115,49,54,0,0,0,0,0,16,0,0,0,0,0,0,0,6,0,0,0,115,51,50,0,0,0,0,0,32,0,0,0,0,0,0,0,7,0,0,0,102,108,116,0,0,0,0,0,32,0,0,0,0,0,0,0,8,0,0,0,100,98,108,0,0,0,0,0,64,0,0,0,0,0,0,0,9,0,0,0,117,56,112,0,0,0,0,0,8,0,0,0,1,0,0,0,0,0,0,0,115,49,54,112,0,0,0,0,16,0,0,0,1,0,0,0,1,0,0,0,115,51,50,112,0,0,0,0,32,0,0,0,1,0,0,0,2,0,0,0,102,108,116,112,0,0,0,0,32,0,0,0,1,0,0,0,3,0,0,0,100,98,108,112,0,0,0,0,64,0,0,0,1,0,0,0,4,0,0,0,120,0,0,0,56,171,0,0,5,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,200,231,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,192,3,0,0,192,4,0,0,192,5,0,0,192,6,0,0,192,7,0,0,192,8,0,0,192,9,0,0,192,10,0,0,192,11,0,0,192,12,0,0,192,13,0,0,192,14,0,0,192,15,0,0,192,16,0,0,192,17,0,0,192,18,0,0,192,19,0,0,192,20,0,0,192,21,0,0,192,22,0,0,192,23,0,0,192,24,0,0,192,25,0,0,192,26,0,0,192,27,0,0,192,28,0,0,192,29,0,0,192,30,0,0,192,31,0,0,192,0,0,0,179,1,0,0,195,2,0,0,195,3,0,0,195,4,0,0,195,5,0,0,195,6,0,0,195,7,0,0,195,8,0,0,195,9,0,0,195,10,0,0,195,11,0,0,195,12,0,0,195,13,0,0,211,14,0,0,195,15,0,0,195,0,0,12,187,1,0,12,195,2,0,12,195,3,0,12,195,4,0,12,211,10,0,0,0,100,0,0,0,232,3,0,0,16,39,0,0,160,134,1,0,64,66,15,0,128,150,152,0,0,225,245,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,128,1,0,2,0,3,0,241,1,0,8,16,1,0,8,64,1,0,8,80,1,0,16,48,2,0,32,48,2,0,16,192,2,0,32,192,2,0,16,144,2,0,32,144,2,0,16,96,2,0,32,96,2,0,16,176,2,0,32,176,2,0,16,224,2,0,32,224,2,0,16,112,2,0,32,112,2,0,16,208,2,0,32,208,2,0,16,240,2,0,32,240,2,0,64,240,4,0,8,16,1,0,16,48,2,0,32,48,2,0,64,48,4,0,64,50,4,0,1,0,255,255,2,0,2,0,2,0,1,0,2,0,0,0,2,0,3,0,2,0,2,16,2,0,1,16,2,0,0,16,2,0,3,16,2,0,2,32,2,0,1,32,2,0,0,32,2,0,3,32,2,0,2,15,2,0,1,15,2,0,0,15,2,0,3,15,2,0,2,31,2,0,1,31,2,0,0,31,2,0,3,31,2,0,2,47,2,0,1,47,2,0,0,47,2,0,3,47,4,0,255,255,0,1,1,0,8,16,1,0,8,64,1,0,8,80,1,0,16,48,2,0,32,48,2,0,16,192,2,0,32,192,2,0,16,240,2,0,32,240,2,0,64,48,4,0,64,192,4,0,64,240,4,0,8,16,1,0,16,16,2,0,32,16,2,0,64,16,4,0,3,0,3,0,3,0,3,0,4,0,4,0,4,0,4,0,108,105,98,97,118,99,111,100,101,99,47,97,118,112,97,99,107,101,116,46,99,0,115,105,122,101,60,61,48,120,55,102,102,102,102,102,102,102,32,38,38,32,112,32,45,32,112,107,116,45,62,100,97,116,97,32,62,61,32,115,105,122,101,0,109,112,101,103,49,118,105,100,101,111,0,77,80,69,71,45,49,32,118,105,100,101,111,0,109,112,101,103,50,118,105,100,101,111,0,77,80,69,71,45,50,32,118,105,100,101,111,0,109,112,101,103,118,105,100,101,111,95,120,118,109,99,0,77,80,69,71,45,49,47,50,32,118,105,100,101,111,32,88,118,77,67,32,40,88,45,86,105,100,101,111,32,77,111,116,105,111,110,32,67,111,109,112,101,110,115,97,116,105,111,110,41,0,104,50,54,49,0,72,46,50,54,49,0,104,50,54,51,0,72,46,50,54,51,32,47,32,72,46,50,54,51,45,49,57,57,54,44,32,72,46,50,54,51,43,32,47,32,72,46,50,54,51,45,49,57,57,56,32,47,32,72,46,50,54,51,32,118,101,114,115,105,111,110,32,50,0,114,118,49,48,0,82,101,97,108,86,105,100,101,111,32,49,46,48,0,114,118,50,48,0,82,101,97,108,86,105,100,101,111,32,50,46,48,0,109,106,112,101,103,0,77,111,116,105,111,110,32,74,80,69,71,0,105,109,97,103,101,47,106,112,101,103,0,109,106,112,101,103,98,0,65,112,112,108,101,32,77,74,80,69,71,45,66,0,109,112,101,103,52,0,77,80,69,71,45,52,32,112,97,114,116,32,50,0,114,97,119,118,105,100,101,111,0,114,97,119,32,118,105,100,101,111,0,109,115,109,112,101,103,52,118,49,0,77,80,69,71,45,52,32,112,97,114,116,32,50,32,77,105,99,114,111,115,111,102,116,32,118,97,114,105,97,110,116,32,118,101,114,115,105,111,110,32,49,0,109,115,109,112,101,103,52,118,50,0,77,80,69,71,45,52,32,112,97,114,116,32,50,32,77,105,99,114,111,115,111,102,116,32,118,97,114,105,97,110,116,32,118,101,114,115,105,111,110,32,50,0,109,115,109,112,101,103,52,118,51,0,77,80,69,71,45,52,32,112,97,114,116,32,50,32,77,105,99,114,111,115,111,102,116,32,118,97,114,105,97,110,116,32,118,101,114,115,105,111,110,32,51,0,119,109,118,49,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,86,105,100,101,111,32,55,0,119,109,118,50,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,86,105,100,101,111,32,56,0,104,50,54,51,112,0,72,46,50,54,51,43,32,47,32,72,46,50,54,51,45,49,57,57,56,32,47,32,72,46,50,54,51,32,118,101,114,115,105,111,110,32,50,0,104,50,54,51,105,0,73,110,116,101,108,32,72,46,50,54,51,0,102,108,118,49,0,70,76,86,32,47,32,83,111,114,101,110,115,111,110,32,83,112,97,114,107,32,47,32,83,111,114,101,110,115,111,110,32,72,46,50,54,51,32,40,70,108,97,115,104,32,86,105,100,101,111,41,0,115,118,113,49,0,83,111,114,101,110,115,111,110,32,86,101,99,116,111,114,32,81,117,97,110,116,105,122,101,114,32,49,32,47,32,83,111,114,101,110,115,111,110,32,86,105,100,101,111,32,49,32,47,32,83,86,81,49,0,115,118,113,51,0,83,111,114,101,110,115,111,110,32,86,101,99,116,111,114,32,81,117,97,110,116,105,122,101,114,32,51,32,47,32,83,111,114,101,110,115,111,110,32,86,105,100,101,111,32,51,32,47,32,83,86,81,51,0,100,118,118,105,100,101,111,0,68,86,32,40,68,105,103,105,116,97,108,32,86,105,100,101,111,41,0,104,117,102,102,121,117,118,0,72,117,102,102,89,85,86,0,99,121,117,118,0,67,114,101,97,116,105,118,101,32,89,85,86,32,40,67,89,85,86,41,0,105,110,100,101,111,51,0,73,110,116,101,108,32,73,110,100,101,111,32,51,0,118,112,51,0,79,110,50,32,86,80,51,0,116,104,101,111,114,97,0,84,104,101,111,114,97,0,97,115,118,49,0,65,83,85,83,32,86,49,0,97,115,118,50,0,65,83,85,83,32,86,50,0,102,102,118,49,0,70,70,109,112,101,103,32,118,105,100,101,111,32,99,111,100,101,99,32,35,49,0,52,120,109,0,52,88,32,77,111,118,105,101,0,118,99,114,49,0,65,84,73,32,86,67,82,49,0,99,108,106,114,0,67,105,114,114,117,115,32,76,111,103,105,99,32,65,99,99,117,80,97,107,0,109,100,101,99,0,83,111,110,121,32,80,108,97,121,83,116,97,116,105,111,110,32,77,68,69,67,32,40,77,111,116,105,111,110,32,68,69,67,111,100,101,114,41,0,114,111,113,0,105,100,32,82,111,81,32,118,105,100,101,111,0,105,110,116,101,114,112,108,97,121,118,105,100,101,111,0,73,110,116,101,114,112,108,97,121,32,77,86,69,32,118,105,100,101,111,0,120,97,110,95,119,99,51,0,87,105,110,103,32,67,111,109,109,97,110,100,101,114,32,73,73,73,32,47,32,88,97,110,0,120,97,110,95,119,99,52,0,87,105,110,103,32,67,111,109,109,97,110,100,101,114,32,73,86,32,47,32,88,120,97,110,0,114,112,122,97,0,81,117,105,99,107,84,105,109,101,32,118,105,100,101,111,32,40,82,80,90,65,41,0,99,105,110,101,112,97,107,0,67,105,110,101,112,97,107,0,119,115,95,118,113,97,0,87,101,115,116,119,111,111,100,32,83,116,117,100,105,111,115,32,86,81,65,32,40,86,101,99,116,111,114,32,81,117,97,110,116,105,122,101,100,32,65,110,105,109,97,116,105,111,110,41,32,118,105,100,101,111,0,109,115,114,108,101,0,77,105,99,114,111,115,111,102,116,32,82,76,69,0,109,115,118,105,100,101,111,49,0,77,105,99,114,111,115,111,102,116,32,86,105,100,101,111,32,49,0,105,100,99,105,110,0,105,100,32,81,117,97,107,101,32,73,73,32,67,73,78,32,118,105,100,101,111,0,56,98,112,115,0,81,117,105,99,107,84,105,109,101,32,56,66,80,83,32,118,105,100,101,111,0,115,109,99,0,81,117,105,99,107,84,105,109,101,32,71,114,97,112,104,105,99,115,32,40,83,77,67,41,0,102,108,105,99,0,65,117,116,111,100,101,115,107,32,65,110,105,109,97,116,111,114,32,70,108,105,99,32,118,105,100,101,111,0,116,114,117,101,109,111,116,105,111,110,49,0,68,117,99,107,32,84,114,117,101,77,111,116,105,111,110,32,49,46,48,0,118,109,100,118,105,100,101,111,0,83,105,101,114,114,97,32,86,77,68,32,118,105,100,101,111,0,109,115,122,104,0,76,67,76,32,40,76,111,115,115,76,101,115,115,32,67,111,100,101,99,32,76,105,98,114,97,114,121,41,32,77,83,90,72,0,122,108,105,98,0,76,67,76,32,40,76,111,115,115,76,101,115,115,32,67,111,100,101,99,32,76,105,98,114,97,114,121,41,32,90,76,73,66,0,113,116,114,108,101,0,81,117,105,99,107,84,105,109,101,32,65,110,105,109,97,116,105,111,110,32,40,82,76,69,41,32,118,105,100,101,111,0,115,110,111,119,0,116,115,99,99,0,84,101,99,104,83,109,105,116,104,32,83,99,114,101,101,110,32,67,97,112,116,117,114,101,32,67,111,100,101,99,0,117,108,116,105,0,73,66,77,32,85,108,116,105,77,111,116,105,111,110,0,113,100,114,97,119,0,65,112,112,108,101,32,81,117,105,99,107,68,114,97,119,0,118,105,120,108,0,77,105,114,111,32,86,105,100,101,111,88,76,0,113,112,101,103,0,81,45,116,101,97,109,32,81,80,69,71,0,102,102,118,104,117,102,102,0,72,117,102,102,121,117,118,32,70,70,109,112,101,103,32,118,97,114,105,97,110,116,0,114,118,51,48,0,82,101,97,108,86,105,100,101,111,32,51,46,48,0,114,118,52,48,0,82,101,97,108,86,105,100,101,111,32,52,46,48,0,118,99,49,0,83,77,80,84,69,32,86,67,45,49,0,119,109,118,51,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,86,105,100,101,111,32,57,0,108,111,99,111,0,76,79,67,79,0,119,110,118,49,0,87,105,110,110,111,118,32,87,78,86,49,0,97,97,115,99,0,65,117,116,111,100,101,115,107,32,82,76,69,0,105,110,100,101,111,50,0,73,110,116,101,108,32,73,110,100,101,111,32,50,0,102,114,97,112,115,0,70,114,97,112,115,0,116,114,117,101,109,111,116,105,111,110,50,0,68,117,99,107,32,84,114,117,101,77,111,116,105,111,110,32,50,46,48,0,98,109,112,0,66,77,80,32,40,87,105,110,100,111,119,115,32,97,110,100,32,79,83,47,50,32,98,105,116,109,97,112,41,0,105,109,97,103,101,47,120,45,109,115,45,98,109,112,0,99,115,99,100,0,67,97,109,83,116,117,100,105,111,0,109,109,118,105,100,101,111,0,65,109,101,114,105,99,97,110,32,76,97,115,101,114,32,71,97,109,101,115,32,77,77,32,86,105,100,101,111,0,122,109,98,118,0,90,105,112,32,77,111,116,105,111,110,32,66,108,111,99,107,115,32,86,105,100,101,111,0,97,118,115,0,65,86,83,32,40,65,117,100,105,111,32,86,105,100,101,111,32,83,116,97,110,100,97,114,100,41,32,118,105,100,101,111,0,115,109,97,99,107,118,105,100,101,111,0,83,109,97,99,107,101,114,32,118,105,100,101,111,0,110,117,118,0,78,117,112,112,101,108,86,105,100,101,111,47,82,84,74,80,69,71,0,107,109,118,99,0,75,97,114,108,32,77,111,114,116,111,110,39,115,32,118,105,100,101,111,32,99,111,100,101,99,0,102,108,97,115,104,115,118,0,70,108,97,115,104,32,83,99,114,101,101,110,32,86,105,100,101,111,32,118,49,0,99,97,118,115,0,67,104,105,110,101,115,101,32,65,86,83,32,40,65,117,100,105,111,32,86,105,100,101,111,32,83,116,97,110,100,97,114,100,41,32,40,65,86,83,49,45,80,50,44,32,74,105,90,104,117,110,32,112,114,111,102,105,108,101,41,0,106,112,101,103,50,48,48,48,0,74,80,69,71,32,50,48,48,48,0,105,109,97,103,101,47,106,112,50,0,118,109,110,99,0,86,77,119,97,114,101,32,83,99,114,101,101,110,32,67,111,100,101,99,32,47,32,86,77,119,97,114,101,32,86,105,100,101,111,0,118,112,53,0,79,110,50,32,86,80,53,0,118,112,54,0,79,110,50,32,86,80,54,0,118,112,54,102,0,79,110,50,32,86,80,54,32,40,70,108,97,115,104,32,118,101,114,115,105,111,110,41,0,100,115,105,99,105,110,118,105,100,101,111,0,68,101,108,112,104,105,110,101,32,83,111,102,116,119,97,114,101,32,73,110,116,101,114,110,97,116,105,111,110,97,108,32,67,73,78,32,118,105,100,101,111,0,116,105,101,114,116,101,120,115,101,113,118,105,100,101,111,0,84,105,101,114,116,101,120,32,76,105,109,105,116,101,100,32,83,69,81,32,118,105,100,101,111,0,100,120,97,0,70,101,101,98,108,101,32,70,105,108,101,115,47,83,99,117,109,109,86,77,32,68,88,65,0,100,110,120,104,100,0,86,67,51,47,68,78,120,72,68,0,116,104,112,0,78,105,110,116,101,110,100,111,32,71,97,109,101,99,117,98,101,32,84,72,80,32,118,105,100,101,111,0,99,57,51,0,73,110,116,101,114,112,108,97,121,32,67,57,51,0,98,101,116,104,115,111,102,116,118,105,100,0,66,101,116,104,101,115,100,97,32,86,73,68,32,118,105,100,101,111,0,118,112,54,97,0,79,110,50,32,86,80,54,32,40,70,108,97,115,104,32,118,101,114,115,105,111,110,44,32,119,105,116,104,32,97,108,112,104,97,32,99,104,97,110,110,101,108,41,0,65,77,86,32,86,105,100,101,111,0,118,98,0,66,101,97,109,32,83,111,102,116,119,97,114,101,32,86,66,0,105,110,100,101,111,52,0,73,110,116,101,108,32,73,110,100,101,111,32,86,105,100,101,111,32,73,110,116,101,114,97,99,116,105,118,101,32,52,0,105,110,100,101,111,53,0,73,110,116,101,108,32,73,110,100,101,111,32,86,105,100,101,111,32,73,110,116,101,114,97,99,116,105,118,101,32,53,0,109,105,109,105,99,0,77,105,109,105,99,0,114,108,50,0,82,76,50,32,118,105,100,101,111,0,101,115,99,97,112,101,49,50,52,0,69,115,99,97,112,101,32,49,50,52,0,100,105,114,97,99,0,68,105,114,97,99,0,98,102,105,0,66,114,117,116,101,32,70,111,114,99,101,32,38,32,73,103,110,111,114,97,110,99,101,0,99,109,118,0,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,67,77,86,32,118,105,100,101,111,0,109,111,116,105,111,110,112,105,120,101,108,115,0,77,111,116,105,111,110,32,80,105,120,101,108,115,32,118,105,100,101,111,0,116,103,118,0,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,84,71,86,32,118,105,100,101,111,0,116,103,113,0,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,84,71,81,32,118,105,100,101,111,0,116,113,105,0,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,84,81,73,32,118,105,100,101,111,0,97,117,114,97,0,65,117,114,97,118,105,115,105,111,110,32,65,85,82,65,0,97,117,114,97,50,0,65,117,114,97,118,105,115,105,111,110,32,65,117,114,97,32,50,0,118,50,49,48,120,0,85,110,99,111,109,112,114,101,115,115,101,100,32,52,58,50,58,50,32,49,48,45,98,105,116,0,116,109,118,0,56,48,56,56,102,108,101,120,32,84,77,86,0,118,50,49,48,0,109,97,100,0,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,77,97,100,99,111,119,32,86,105,100,101,111,0,102,114,119,117,0,70,111,114,119,97,114,100,32,85,110,99,111,109,112,114,101,115,115,101,100,0,102,108,97,115,104,115,118,50,0,70,108,97,115,104,32,83,99,114,101,101,110,32,86,105,100,101,111,32,118,50,0,99,100,103,114,97,112,104,105,99,115,0,67,68,32,71,114,97,112,104,105,99,115,32,118,105,100,101,111,0,114,50,49,48,0,85,110,99,111,109,112,114,101,115,115,101,100,32,82,71,66,32,49,48,45,98,105,116,0,97,110,109,0,68,101,108,117,120,101,32,80,97,105,110,116,32,65,110,105,109,97,116,105,111,110,0,98,105,110,107,118,105,100,101,111,0,66,105,110,107,32,118,105,100,101,111,0,105,102,102,95,105,108,98,109,0,73,70,70,32,73,76,66,77,0,105,102,102,95,98,121,116,101,114,117,110,49,0,73,70,70,32,66,121,116,101,82,117,110,49,0,107,103,118,49,0,75,101,103,97,32,71,97,109,101,32,86,105,100,101,111,0,121,111,112,0,80,115,121,103,110,111,115,105,115,32,89,79,80,32,86,105,100,101,111,0,118,112,56,0,79,110,50,32,86,80,56,0,118,112,57,0,71,111,111,103,108,101,32,86,80,57,0,112,105,99,116,111,114,0,80,105,99,116,111,114,47,80,67,32,80,97,105,110,116,0,97,54,52,95,109,117,108,116,105,0,77,117,108,116,105,99,111,108,111,114,32,99,104,97,114,115,101,116,32,102,111,114,32,67,111,109,109,111,100,111,114,101,32,54,52,0,97,54,52,95,109,117,108,116,105,53,0,77,117,108,116,105,99,111,108,111,114,32,99,104,97,114,115,101,116,32,102,111,114,32,67,111,109,109,111,100,111,114,101,32,54,52,44,32,101,120,116,101,110,100,101,100,32,119,105,116,104,32,53,116,104,32,99,111,108,111,114,32,40,99,111,108,114,97,109,41,0,114,49,48,107,0,65,74,65,32,75,111,110,97,32,49,48,45,98,105,116,32,82,71,66,32,67,111,100,101,99,0,109,118,99,49,0,83,105,108,105,99,111,110,32,71,114,97,112,104,105,99,115,32,77,111,116,105,111,110,32,86,105,100,101,111,32,67,111,109,112,114,101,115,115,111,114,32,49,0,109,118,99,50,0,83,105,108,105,99,111,110,32,71,114,97,112,104,105,99,115,32,77,111,116,105,111,110,32,86,105,100,101,111,32,67,111,109,112,114,101,115,115,111,114,32,50,0,109,120,112,101,103,0,77,111,98,111,116,105,120,32,77,120,80,69,71,32,118,105,100,101,111,0,108,97,103,97,114,105,116,104,0,76,97,103,97,114,105,116,104,32,108,111,115,115,108,101,115,115,0,112,114,111,114,101,115,0,65,112,112,108,101,32,80,114,111,82,101,115,32,40,105,67,111,100,101,99,32,80,114,111,41,0,106,118,0,66,105,116,109,97,112,32,66,114,111,116,104,101,114,115,32,74,86,32,118,105,100,101,111,0,100,102,97,0,67,104,114,111,110,111,109,97,115,116,101,114,32,68,70,65,0,117,116,118,105,100,101,111,0,85,116,32,86,105,100,101,111,0,98,109,118,95,118,105,100,101,111,0,68,105,115,99,119,111,114,108,100,32,73,73,32,66,77,86,32,118,105,100,101,111,0,118,98,108,101,0,86,66,76,69,32,76,111,115,115,108,101,115,115,32,67,111,100,101,99,0,100,120,116,111,114,121,0,68,120,116,111,114,121,0,118,52,49,48,0,85,110,99,111,109,112,114,101,115,115,101,100,32,52,58,52,58,52,32,49,48,45,98,105,116,0,99,100,120,108,0,67,111,109,109,111,100,111,114,101,32,67,68,88,76,32,118,105,100,101,111,0,122,101,114,111,99,111,100,101,99,0,90,101,114,111,67,111,100,101,99,32,76,111,115,115,108,101,115,115,32,86,105,100,101,111,0,109,115,115,49,0,77,83,32,83,99,114,101,101,110,32,49,0,109,115,97,49,0,77,83,32,65,84,67,32,83,99,114,101,101,110,0,116,115,99,99,50,0,84,101,99,104,83,109,105,116,104,32,83,99,114,101,101,110,32,67,111,100,101,99,32,50,0,109,116,115,50,0,77,83,32,69,120,112,114,101,115,115,105,111,110,32,69,110,99,111,100,101,114,32,83,99,114,101,101,110,0,99,108,108,99,0,67,97,110,111,112,117,115,32,76,111,115,115,108,101,115,115,32,67,111,100,101,99,0,109,115,115,50,0,77,83,32,87,105,110,100,111,119,115,32,77,101,100,105,97,32,86,105,100,101,111,32,86,57,32,83,99,114,101,101,110,0,65,112,112,108,101,32,73,110,116,101,114,109,101,100,105,97,116,101,32,67,111,100,101,99,0,121,52,49,112,0,85,110,99,111,109,112,114,101,115,115,101,100,32,89,85,86,32,52,58,49,58,49,32,49,50,45,98,105,116,0,101,115,99,97,112,101,49,51,48,0,69,115,99,97,112,101,32,49,51,48,0,97,118,114,112,0,65,118,105,100,32,49,58,49,32,49,48,45,98,105,116,32,82,71,66,32,80,97,99,107,101,114,0,48,49,50,118,0,97,118,117,105,0,65,118,105,100,32,77,101,114,105,100,105,101,110,32,85,110,99,111,109,112,114,101,115,115,101,100,0,97,121,117,118,0,85,110,99,111,109,112,114,101,115,115,101,100,32,112,97,99,107,101,100,32,77,83,32,52,58,52,58,52,58,52,0,116,97,114,103,97,95,121,50,49,54,0,80,105,110,110,97,99,108,101,32,84,65,82,71,65,32,67,105,110,101,87,97,118,101,32,89,85,86,49,54,0,118,51,48,56,0,85,110,99,111,109,112,114,101,115,115,101,100,32,112,97,99,107,101,100,32,52,58,52,58,52,0,118,52,48,56,0,85,110,99,111,109,112,114,101,115,115,101,100,32,112,97,99,107,101,100,32,81,84,32,52,58,52,58,52,58,52,0,121,117,118,52,0,85,110,99,111,109,112,114,101,115,115,101,100,32,112,97,99,107,101,100,32,52,58,50,58,48,0,97,118,114,110,0,65,118,105,100,32,65,86,73,32,67,111,100,101,99,0,99,112,105,97,0,67,80,105,65,32,118,105,100,101,111,32,102,111,114,109,97,116,0,120,102,97,99,101,0,88,45,102,97,99,101,32,105,109,97,103,101,0,115,109,118,106,112,101,103,0,83,105,103,109,97,116,101,108,32,77,111,116,105,111,110,32,86,105,100,101,111,0,103,50,109,0,71,111,50,77,101,101,116,105,110,103,0,104,110,109,52,118,105,100,101,111,0,72,78,77,32,52,32,118,105,100,101,111,0,104,101,118,99,0,72,46,50,54,53,32,47,32,72,69,86,67,32,40,72,105,103,104,32,69,102,102,105,99,105,101,110,99,121,32,86,105,100,101,111,32,67,111,100,105,110,103,41,0,102,105,99,0,77,105,114,105,108,108,105,115,32,70,73,67,0,112,97,102,95,118,105,100,101,111,0,65,109,97,122,105,110,103,32,83,116,117,100,105,111,32,80,97,99,107,101,100,32,65,110,105,109,97,116,105,111,110,32,70,105,108,101,32,86,105,100,101,111,0,118,112,55,0,79,110,50,32,86,80,55,0,115,97,110,109,0,76,117,99,97,115,65,114,116,115,32,83,65,78,77,47,83,77,85,83,72,32,118,105,100,101,111,0,115,103,105,114,108,101,0,83,71,73,32,82,76,69,32,56,45,98,105,116,0,104,113,120,0,67,97,110,111,112,117,115,32,72,81,88,0,104,113,95,104,113,97,0,67,97,110,111,112,117,115,32,72,81,47,72,81,65,0,104,97,112,0,86,105,100,118,111,120,32,72,97,112,32,100,101,99,111,100,101,114,0,97,108,105,97,115,95,112,105,120,0,65,108,105,97,115,47,87,97,118,101,102,114,111,110,116,32,80,73,88,32,105,109,97,103,101,0,97,110,115,105,0,65,83,67,73,73,47,65,78,83,73,32,97,114,116,0,98,114,101,110,100,101,114,95,112,105,120,0,66,82,101,110,100,101,114,32,80,73,88,32,105,109,97,103,101,0,100,100,115,0,68,105,114,101,99,116,68,114,97,119,32,83,117,114,102,97,99,101,32,105,109,97,103,101,32,100,101,99,111,100,101,114,0,100,112,120,0,68,80,88,32,40,68,105,103,105,116,97,108,32,80,105,99,116,117,114,101,32,69,120,99,104,97,110,103,101,41,32,105,109,97,103,101,0,101,120,114,0,79,112,101,110,69,88,82,32,105,109,97,103,101,0,103,105,102,0,71,73,70,32,40,71,114,97,112,104,105,99,115,32,73,110,116,101,114,99,104,97,110,103,101,32,70,111,114,109,97,116,41,0,105,109,97,103,101,47,103,105,102,0,106,112,101,103,108,115,0,74,80,69,71,45,76,83,0,108,106,112,101,103,0,76,111,115,115,108,101,115,115,32,74,80,69,71,0,112,97,109,0,80,65,77,32,40,80,111,114,116,97,98,108,101,32,65,110,121,77,97,112,41,32,105,109,97,103,101,0,105,109,97,103,101,47,120,45,112,111,114,116,97,98,108,101,45,112,105,120,109,97,112,0,112,98,109,0,80,66,77,32,40,80,111,114,116,97,98,108,101,32,66,105,116,77,97,112,41,32,105,109,97,103,101,0,112,99,120,0,80,67,32,80,97,105,110,116,98,114,117,115,104,32,80,67,88,32,105,109,97,103,101,0,105,109,97,103,101,47,120,45,112,99,120,0,112,103,109,0,80,71,77,32,40,80,111,114,116,97,98,108,101,32,71,114,97,121,77,97,112,41,32,105,109,97,103,101,0,112,103,109,121,117,118,0,80,71,77,89,85,86,32,40,80,111,114,116,97,98,108,101,32,71,114,97,121,77,97,112,32,89,85,86,41,32,105,109,97,103,101,0,112,110,103,0,80,78,71,32,40,80,111,114,116,97,98,108,101,32,78,101,116,119,111,114,107,32,71,114,97,112,104,105,99,115,41,32,105,109,97,103,101,0,105,109,97,103,101,47,112,110,103,0,112,112,109,0,80,80,77,32,40,80,111,114,116,97,98,108,101,32,80,105,120,101,108,77,97,112,41,32,105,109,97,103,101,0,112,116,120,0,86,46,70,108,97,115,104,32,80,84,88,32,105,109,97,103,101,0,115,103,105,0,83,71,73,32,105,109,97,103,101,0,115,112,53,120,0,83,117,110,112,108,117,115,32,74,80,69,71,32,40,83,80,53,88,41,0,115,117,110,114,97,115,116,0,83,117,110,32,82,97,115,116,101,114,102,105,108,101,32,105,109,97,103,101,0,116,97,114,103,97,0,84,114,117,101,118,105,115,105,111,110,32,84,97,114,103,97,32,105,109,97,103,101,0,105,109,97,103,101,47,120,45,116,97,114,103,97,0,105,109,97,103,101,47,120,45,116,103,97,0,116,100,115,99,0,84,68,83,67,0,116,105,102,102,0,84,73,70,70,32,105,109,97,103,101,0,105,109,97,103,101,47,116,105,102,102,0,116,120,100,0,82,101,110,100,101,114,119,97,114,101,32,84,88,68,32,40,84,101,88,116,117,114,101,32,68,105,99,116,105,111,110,97,114,121,41,32,105,109,97,103,101,0,118,99,49,105,109,97,103,101,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,86,105,100,101,111,32,57,32,73,109,97,103,101,32,118,50,0,119,101,98,112,0,87,101,98,80,0,105,109,97,103,101,47,119,101,98,112,0,119,109,118,51,105,109,97,103,101,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,86,105,100,101,111,32,57,32,73,109,97,103,101,0,120,98,109,0,88,66,77,32,40,88,32,66,105,116,77,97,112,41,32,105,109,97,103,101,0,120,119,100,0,88,87,68,32,40,88,32,87,105,110,100,111,119,32,68,117,109,112,41,32,105,109,97,103,101,0,105,109,97,103,101,47,120,45,120,119,105,110,100,111,119,100,117,109,112,0,97,112,110,103,0,65,80,78,71,32,40,65,110,105,109,97,116,101,100,32,80,111,114,116,97,98,108,101,32,78,101,116,119,111,114,107,32,71,114,97,112,104,105,99,115,41,32,105,109,97,103,101,0,112,99,109,95,115,49,54,108,101,0,80,67,77,32,115,105,103,110,101,100,32,49,54,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,115,49,54,98,101,0,80,67,77,32,115,105,103,110,101,100,32,49,54,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,117,49,54,108,101,0,80,67,77,32,117,110,115,105,103,110,101,100,32,49,54,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,117,49,54,98,101,0,80,67,77,32,117,110,115,105,103,110,101,100,32,49,54,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,115,56,0,80,67,77,32,115,105,103,110,101,100,32,56,45,98,105,116,0,112,99,109,95,117,56,0,80,67,77,32,117,110,115,105,103,110,101,100,32,56,45,98,105,116,0,112,99,109,95,109,117,108,97,119,0,80,67,77,32,109,117,45,108,97,119,32,47,32,71,46,55,49,49,32,109,117,45,108,97,119,0,112,99,109,95,97,108,97,119,0,80,67,77,32,65,45,108,97,119,32,47,32,71,46,55,49,49,32,65,45,108,97,119,0,112,99,109,95,115,51,50,108,101,0,80,67,77,32,115,105,103,110,101,100,32,51,50,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,115,51,50,98,101,0,80,67,77,32,115,105,103,110,101,100,32,51,50,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,117,51,50,108,101,0,80,67,77,32,117,110,115,105,103,110,101,100,32,51,50,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,117,51,50,98,101,0,80,67,77,32,117,110,115,105,103,110,101,100,32,51,50,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,115,50,52,108,101,0,80,67,77,32,115,105,103,110,101,100,32,50,52,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,115,50,52,98,101,0,80,67,77,32,115,105,103,110,101,100,32,50,52,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,117,50,52,108,101,0,80,67,77,32,117,110,115,105,103,110,101,100,32,50,52,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,117,50,52,98,101,0,80,67,77,32,117,110,115,105,103,110,101,100,32,50,52,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,115,50,52,100,97,117,100,0,80,67,77,32,68,45,67,105,110,101,109,97,32,97,117,100,105,111,32,115,105,103,110,101,100,32,50,52,45,98,105,116,0,112,99,109,95,122,111,114,107,0,80,67,77,32,90,111,114,107,0,112,99,109,95,115,49,54,98,101,95,112,108,97,110,97,114,0,80,67,77,32,115,105,103,110,101,100,32,49,54,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,32,112,108,97,110,97,114,0,112,99,109,95,115,49,54,108,101,95,112,108,97,110,97,114,0,80,67,77,32,115,105,103,110,101,100,32,49,54,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,32,112,108,97,110,97,114,0,112,99,109,95,115,50,52,108,101,95,112,108,97,110,97,114,0,80,67,77,32,115,105,103,110,101,100,32,50,52,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,32,112,108,97,110,97,114,0,112,99,109,95,115,51,50,108,101,95,112,108,97,110,97,114,0,80,67,77,32,115,105,103,110,101,100,32,51,50,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,32,112,108,97,110,97,114,0,112,99,109,95,100,118,100,0,80,67,77,32,115,105,103,110,101,100,32,50,48,124,50,52,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,102,51,50,98,101,0,80,67,77,32,51,50,45,98,105,116,32,102,108,111,97,116,105,110,103,32,112,111,105,110,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,102,51,50,108,101,0,80,67,77,32,51,50,45,98,105,116,32,102,108,111,97,116,105,110,103,32,112,111,105,110,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,102,54,52,98,101,0,80,67,77,32,54,52,45,98,105,116,32,102,108,111,97,116,105,110,103,32,112,111,105,110,116,32,98,105,103,45,101,110,100,105,97,110,0,112,99,109,95,102,54,52,108,101,0,80,67,77,32,54,52,45,98,105,116,32,102,108,111,97,116,105,110,103,32,112,111,105,110,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,112,99,109,95,98,108,117,114,97,121,0,80,67,77,32,115,105,103,110,101,100,32,49,54,124,50,48,124,50,52,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,32,102,111,114,32,66,108,117,45,114,97,121,32,109,101,100,105,97,0,112,99,109,95,108,120,102,0,80,67,77,32,115,105,103,110,101,100,32,50,48,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,32,112,108,97,110,97,114,0,115,51,48,50,109,0,83,77,80,84,69,32,51,48,50,77,0,112,99,109,95,115,56,95,112,108,97,110,97,114,0,80,67,77,32,115,105,103,110,101,100,32,56,45,98,105,116,32,112,108,97,110,97,114,0,97,100,112,99,109,95,105,109,97,95,113,116,0,65,68,80,67,77,32,73,77,65,32,81,117,105,99,107,84,105,109,101,0,97,100,112,99,109,95,105,109,97,95,119,97,118,0,65,68,80,67,77,32,73,77,65,32,87,65,86,0,97,100,112,99,109,95,105,109,97,95,100,107,51,0,65,68,80,67,77,32,73,77,65,32,68,117,99,107,32,68,75,51,0,97,100,112,99,109,95,105,109,97,95,100,107,52,0,65,68,80,67,77,32,73,77,65,32,68,117,99,107,32,68,75,52,0,97,100,112,99,109,95,105,109,97,95,119,115,0,65,68,80,67,77,32,73,77,65,32,87,101,115,116,119,111,111,100,0,97,100,112,99,109,95,105,109,97,95,115,109,106,112,101,103,0,65,68,80,67,77,32,73,77,65,32,76,111,107,105,32,83,68,76,32,77,74,80,69,71,0,97,100,112,99,109,95,109,115,0,65,68,80,67,77,32,77,105,99,114,111,115,111,102,116,0,97,100,112,99,109,95,52,120,109,0,65,68,80,67,77,32,52,88,32,77,111,118,105,101,0,97,100,112,99,109,95,120,97,0,65,68,80,67,77,32,67,68,82,79,77,32,88,65,0,97,100,112,99,109,95,97,100,120,0,83,69,71,65,32,67,82,73,32,65,68,88,32,65,68,80,67,77,0,97,100,112,99,109,95,101,97,0,65,68,80,67,77,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,0,97,100,112,99,109,95,103,55,50,54,0,71,46,55,50,54,32,65,68,80,67,77,0,97,100,112,99,109,95,99,116,0,65,68,80,67,77,32,67,114,101,97,116,105,118,101,32,84,101,99,104,110,111,108,111,103,121,0,97,100,112,99,109,95,115,119,102,0,65,68,80,67,77,32,83,104,111,99,107,119,97,118,101,32,70,108,97,115,104,0,97,100,112,99,109,95,121,97,109,97,104,97,0,65,68,80,67,77,32,89,97,109,97,104,97,0,97,100,112,99,109,95,115,98,112,114,111,95,52,0,65,68,80,67,77,32,83,111,117,110,100,32,66,108,97,115,116,101,114,32,80,114,111,32,52,45,98,105,116,0,97,100,112,99,109,95,115,98,112,114,111,95,51,0,65,68,80,67,77,32,83,111,117,110,100,32,66,108,97,115,116,101,114,32,80,114,111,32,50,46,54,45,98,105,116,0,97,100,112,99,109,95,115,98,112,114,111,95,50,0,65,68,80,67,77,32,83,111,117,110,100,32,66,108,97,115,116,101,114,32,80,114,111,32,50,45,98,105,116,0,97,100,112,99,109,95,116,104,112,0,65,68,80,67,77,32,78,105,110,116,101,110,100,111,32,84,72,80,0,97,100,112,99,109,95,116,104,112,95,108,101,0,65,68,80,67,77,32,78,105,110,116,101,110,100,111,32,84,72,80,32,40,76,105,116,116,108,101,45,69,110,100,105,97,110,41,0,97,100,112,99,109,95,105,109,97,95,97,109,118,0,65,68,80,67,77,32,73,77,65,32,65,77,86,0,97,100,112,99,109,95,101,97,95,114,49,0,65,68,80,67,77,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,82,49,0,97,100,112,99,109,95,101,97,95,114,51,0,65,68,80,67,77,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,82,51,0,97,100,112,99,109,95,101,97,95,114,50,0,65,68,80,67,77,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,82,50,0,97,100,112,99,109,95,105,109,97,95,101,97,95,115,101,97,100,0,65,68,80,67,77,32,73,77,65,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,83,69,65,68,0,97,100,112,99,109,95,105,109,97,95,101,97,95,101,97,99,115,0,65,68,80,67,77,32,73,77,65,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,69,65,67,83,0,97,100,112,99,109,95,101,97,95,120,97,115,0,65,68,80,67,77,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,88,65,83,0,97,100,112,99,109,95,101,97,95,109,97,120,105,115,95,120,97,0,65,68,80,67,77,32,69,108,101,99,116,114,111,110,105,99,32,65,114,116,115,32,77,97,120,105,115,32,67,68,82,79,77,32,88,65,0,97,100,112,99,109,95,105,109,97,95,105,115,115,0,65,68,80,67,77,32,73,77,65,32,70,117,110,99,111,109,32,73,83,83],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+42636);allocate([0,97,100,112,99,109,95,103,55,50,50,0,71,46,55,50,50,32,65,68,80,67,77,0,97,100,112,99,109,95,105,109,97,95,97,112,99,0,65,68,80,67,77,32,73,77,65,32,67,82,89,79,32,65,80,67,0,97,100,112,99,109,95,97,102,99,0,65,68,80,67,77,32,78,105,110,116,101,110,100,111,32,71,97,109,101,99,117,98,101,32,65,70,67,0,97,100,112,99,109,95,105,109,97,95,111,107,105,0,65,68,80,67,77,32,73,77,65,32,68,105,97,108,111,103,105,99,32,79,75,73,0,97,100,112,99,109,95,100,116,107,0,65,68,80,67,77,32,78,105,110,116,101,110,100,111,32,71,97,109,101,99,117,98,101,32,68,84,75,0,97,100,112,99,109,95,105,109,97,95,114,97,100,0,65,68,80,67,77,32,73,77,65,32,82,97,100,105,99,97,108,0,97,100,112,99,109,95,103,55,50,54,108,101,0,71,46,55,50,54,32,65,68,80,67,77,32,108,105,116,116,108,101,45,101,110,100,105,97,110,0,97,100,112,99,109,95,118,105,109,97,0,76,117,99,97,115,65,114,116,115,32,86,73,77,65,32,97,117,100,105,111,0,97,109,114,95,110,98,0,65,77,82,45,78,66,32,40,65,100,97,112,116,105,118,101,32,77,117,108,116,105,45,82,97,116,101,32,78,97,114,114,111,119,66,97,110,100,41,0,97,109,114,95,119,98,0,65,77,82,45,87,66,32,40,65,100,97,112,116,105,118,101,32,77,117,108,116,105,45,82,97,116,101,32,87,105,100,101,66,97,110,100,41,0,114,97,95,49,52,52,0,82,101,97,108,65,117,100,105,111,32,49,46,48,32,40,49,52,46,52,75,41,0,114,97,95,50,56,56,0,82,101,97,108,65,117,100,105,111,32,50,46,48,32,40,50,56,46,56,75,41,0,114,111,113,95,100,112,99,109,0,68,80,67,77,32,105,100,32,82,111,81,0,105,110,116,101,114,112,108,97,121,95,100,112,99,109,0,68,80,67,77,32,73,110,116,101,114,112,108,97,121,0,120,97,110,95,100,112,99,109,0,68,80,67,77,32,88,97,110,0,115,111,108,95,100,112,99,109,0,68,80,67,77,32,83,111,108,0,109,112,50,0,77,80,50,32,40,77,80,69,71,32,97,117,100,105,111,32,108,97,121,101,114,32,50,41,0,109,112,51,0,77,80,51,32,40,77,80,69,71,32,97,117,100,105,111,32,108,97,121,101,114,32,51,41,0,97,97,99,0,65,65,67,32,40,65,100,118,97,110,99,101,100,32,65,117,100,105,111,32,67,111,100,105,110,103,41,0,97,99,51,0,65,84,83,67,32,65,47,53,50,65,32,40,65,67,45,51,41,0,68,67,65,32,40,68,84,83,32,67,111,104,101,114,101,110,116,32,65,99,111,117,115,116,105,99,115,41,0,118,111,114,98,105,115,0,86,111,114,98,105,115,0,100,118,97,117,100,105,111,0,68,86,32,97,117,100,105,111,0,119,109,97,118,49,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,65,117,100,105,111,32,49,0,119,109,97,118,50,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,65,117,100,105,111,32,50,0,109,97,99,101,51,0,77,65,67,69,32,40,77,97,99,105,110,116,111,115,104,32,65,117,100,105,111,32,67,111,109,112,114,101,115,115,105,111,110,47,69,120,112,97,110,115,105,111,110,41,32,51,58,49,0,109,97,99,101,54,0,77,65,67,69,32,40,77,97,99,105,110,116,111,115,104,32,65,117,100,105,111,32,67,111,109,112,114,101,115,115,105,111,110,47,69,120,112,97,110,115,105,111,110,41,32,54,58,49,0,118,109,100,97,117,100,105,111,0,83,105,101,114,114,97,32,86,77,68,32,97,117,100,105,111,0,102,108,97,99,0,70,76,65,67,32,40,70,114,101,101,32,76,111,115,115,108,101,115,115,32,65,117,100,105,111,32,67,111,100,101,99,41,0,109,112,51,97,100,117,0,65,68,85,32,40,65,112,112,108,105,99,97,116,105,111,110,32,68,97,116,97,32,85,110,105,116,41,32,77,80,51,32,40,77,80,69,71,32,97,117,100,105,111,32,108,97,121,101,114,32,51,41,0,109,112,51,111,110,52,0,77,80,51,111,110,77,80,52,0,115,104,111,114,116,101,110,0,83,104,111,114,116,101,110,0,97,108,97,99,0,65,76,65,67,32,40,65,112,112,108,101,32,76,111,115,115,108,101,115,115,32,65,117,100,105,111,32,67,111,100,101,99,41,0,119,101,115,116,119,111,111,100,95,115,110,100,49,0,87,101,115,116,119,111,111,100,32,65,117,100,105,111,32,40,83,78,68,49,41,0,103,115,109,0,71,83,77,0,113,100,109,50,0,81,68,101,115,105,103,110,32,77,117,115,105,99,32,67,111,100,101,99,32,50,0,99,111,111,107,0,67,111,111,107,32,47,32,67,111,111,107,101,114,32,47,32,71,101,99,107,111,32,40,82,101,97,108,65,117,100,105,111,32,71,50,41,0,116,114,117,101,115,112,101,101,99,104,0,68,83,80,32,71,114,111,117,112,32,84,114,117,101,83,112,101,101,99,104,0,116,116,97,0,84,84,65,32,40,84,114,117,101,32,65,117,100,105,111,41,0,115,109,97,99,107,97,117,100,105,111,0,83,109,97,99,107,101,114,32,97,117,100,105,111,0,113,99,101,108,112,0,81,67,69,76,80,32,47,32,80,117,114,101,86,111,105,99,101,0,119,97,118,112,97,99,107,0,87,97,118,80,97,99,107,0,100,115,105,99,105,110,97,117,100,105,111,0,68,101,108,112,104,105,110,101,32,83,111,102,116,119,97,114,101,32,73,110,116,101,114,110,97,116,105,111,110,97,108,32,67,73,78,32,97,117,100,105,111,0,105,109,99,0,73,77,67,32,40,73,110,116,101,108,32,77,117,115,105,99,32,67,111,100,101,114,41,0,109,117,115,101,112,97,99,107,55,0,77,117,115,101,112,97,99,107,32,83,86,55,0,109,108,112,0,77,76,80,32,40,77,101,114,105,100,105,97,110,32,76,111,115,115,108,101,115,115,32,80,97,99,107,105,110,103,41,0,103,115,109,95,109,115,0,71,83,77,32,77,105,99,114,111,115,111,102,116,32,118,97,114,105,97,110,116,0,97,116,114,97,99,51,0,65,84,82,65,67,51,32,40,65,100,97,112,116,105,118,101,32,84,82,97,110,115,102,111,114,109,32,65,99,111,117,115,116,105,99,32,67,111,100,105,110,103,32,51,41,0,118,111,120,119,97,114,101,0,86,111,120,119,97,114,101,32,82,84,50,57,32,77,101,116,97,115,111,117,110,100,0,97,112,101,0,77,111,110,107,101,121,39,115,32,65,117,100,105,111,0,110,101,108,108,121,109,111,115,101,114,0,78,101,108,108,121,109,111,115,101,114,32,65,115,97,111,0,109,117,115,101,112,97,99,107,56,0,77,117,115,101,112,97,99,107,32,83,86,56,0,115,112,101,101,120,0,83,112,101,101,120,0,119,109,97,118,111,105,99,101,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,65,117,100,105,111,32,86,111,105,99,101,0,119,109,97,112,114,111,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,65,117,100,105,111,32,57,32,80,114,111,102,101,115,115,105,111,110,97,108,0,119,109,97,108,111,115,115,108,101,115,115,0,87,105,110,100,111,119,115,32,77,101,100,105,97,32,65,117,100,105,111,32,76,111,115,115,108,101,115,115,0,97,116,114,97,99,51,112,0,65,84,82,65,67,51,43,32,40,65,100,97,112,116,105,118,101,32,84,82,97,110,115,102,111,114,109,32,65,99,111,117,115,116,105,99,32,67,111,100,105,110,103,32,51,43,41,0,101,97,99,51,0,65,84,83,67,32,65,47,53,50,66,32,40,65,67,45,51,44,32,69,45,65,67,45,51,41,0,115,105,112,114,0,82,101,97,108,65,117,100,105,111,32,83,73,80,82,32,47,32,65,67,69,76,80,46,78,69,84,0,109,112,49,0,77,80,49,32,40,77,80,69,71,32,97,117,100,105,111,32,108,97,121,101,114,32,49,41,0,116,119,105,110,118,113,0,86,81,70,32,84,119,105,110,86,81,0,116,114,117,101,104,100,0,84,114,117,101,72,68,0,109,112,52,97,108,115,0,77,80,69,71,45,52,32,65,117,100,105,111,32,76,111,115,115,108,101,115,115,32,67,111,100,105,110,103,32,40,65,76,83,41,0,97,116,114,97,99,49,0,65,84,82,65,67,49,32,40,65,100,97,112,116,105,118,101,32,84,82,97,110,115,102,111,114,109,32,65,99,111,117,115,116,105,99,32,67,111,100,105,110,103,41,0,98,105,110,107,97,117,100,105,111,95,114,100,102,116,0,66,105,110,107,32,65,117,100,105,111,32,40,82,68,70,84,41,0,98,105,110,107,97,117,100,105,111,95,100,99,116,0,66,105,110,107,32,65,117,100,105,111,32,40,68,67,84,41,0,97,97,99,95,108,97,116,109,0,65,65,67,32,76,65,84,77,32,40,65,100,118,97,110,99,101,100,32,65,117,100,105,111,32,67,111,100,105,110,103,32,76,65,84,77,32,115,121,110,116,97,120,41,0,113,100,109,99,0,81,68,101,115,105,103,110,32,77,117,115,105,99,0,99,101,108,116,0,67,111,110,115,116,114,97,105,110,101,100,32,69,110,101,114,103,121,32,76,97,112,112,101,100,32,84,114,97,110,115,102,111,114,109,32,40,67,69,76,84,41,0,103,55,50,51,95,49,0,71,46,55,50,51,46,49,0,100,115,115,95,115,112,0,68,105,103,105,116,97,108,32,83,112,101,101,99,104,32,83,116,97,110,100,97,114,100,32,45,32,83,116,97,110,100,97,114,100,32,80,108,97,121,32,109,111,100,101,32,40,68,83,83,32,83,80,41,0,103,55,50,57,0,71,46,55,50,57,0,56,115,118,120,95,101,120,112,0,56,83,86,88,32,101,120,112,111,110,101,110,116,105,97,108,0,56,115,118,120,95,102,105,98,0,56,83,86,88,32,102,105,98,111,110,97,99,99,105,0,98,109,118,95,97,117,100,105,111,0,68,105,115,99,119,111,114,108,100,32,73,73,32,66,77,86,32,97,117,100,105,111,0,114,97,108,102,0,82,101,97,108,65,117,100,105,111,32,76,111,115,115,108,101,115,115,0,105,97,99,0,73,65,67,32,40,73,110,100,101,111,32,65,117,100,105,111,32,67,111,100,101,114,41,0,105,108,98,99,0,105,76,66,67,32,40,73,110,116,101,114,110,101,116,32,76,111,119,32,66,105,116,114,97,116,101,32,67,111,100,101,99,41,0,119,97,118,101,115,121,110,116,104,0,87,97,118,101,32,115,121,110,116,104,101,115,105,115,32,112,115,101,117,100,111,45,99,111,100,101,99,0,115,111,110,105,99,0,83,111,110,105,99,0,115,111,110,105,99,108,115,0,83,111,110,105,99,32,108,111,115,115,108,101,115,115,0,111,112,117,115,0,79,112,117,115,32,40,79,112,117,115,32,73,110,116,101,114,97,99,116,105,118,101,32,65,117,100,105,111,32,67,111,100,101,99,41,0,99,111,109,102,111,114,116,110,111,105,115,101,0,82,70,67,32,51,51,56,57,32,67,111,109,102,111,114,116,32,78,111,105,115,101,0,116,97,107,0,84,65,75,32,40,84,111,109,39,115,32,108,111,115,115,108,101,115,115,32,65,117,100,105,111,32,75,111,109,112,114,101,115,115,111,114,41,0,109,101,116,97,115,111,117,110,100,0,86,111,120,119,97,114,101,32,77,101,116,97,83,111,117,110,100,0,112,97,102,95,97,117,100,105,111,0,65,109,97,122,105,110,103,32,83,116,117,100,105,111,32,80,97,99,107,101,100,32,65,110,105,109,97,116,105,111,110,32,70,105,108,101,32,65,117,100,105,111,0,97,118,99,0,79,110,50,32,65,117,100,105,111,32,102,111,114,32,86,105,100,101,111,32,67,111,100,101,99,0,101,118,114,99,0,69,86,82,67,32,40,69,110,104,97,110,99,101,100,32,86,97,114,105,97,98,108,101,32,82,97,116,101,32,67,111,100,101,99,41,0,115,109,118,0,83,77,86,32,40,83,101,108,101,99,116,97,98,108,101,32,77,111,100,101,32,86,111,99,111,100,101,114,41,0,52,103,118,0,52,71,86,32,40,70,111,117,114,116,104,32,71,101,110,101,114,97,116,105,111,110,32,86,111,99,111,100,101,114,41,0,100,115,100,95,108,115,98,102,0,68,83,68,32,40,68,105,114,101,99,116,32,83,116,114,101,97,109,32,68,105,103,105,116,97,108,41,44,32,108,101,97,115,116,32,115,105,103,110,105,102,105,99,97,110,116,32,98,105,116,32,102,105,114,115,116,0,100,115,100,95,109,115,98,102,0,68,83,68,32,40,68,105,114,101,99,116,32,83,116,114,101,97,109,32,68,105,103,105,116,97,108,41,44,32,109,111,115,116,32,115,105,103,110,105,102,105,99,97,110,116,32,98,105,116,32,102,105,114,115,116,0,100,115,100,95,108,115,98,102,95,112,108,97,110,97,114,0,68,83,68,32,40,68,105,114,101,99,116,32,83,116,114,101,97,109,32,68,105,103,105,116,97,108,41,44,32,108,101,97,115,116,32,115,105,103,110,105,102,105,99,97,110,116,32,98,105,116,32,102,105,114,115,116,44,32,112,108,97,110,97,114,0,100,115,100,95,109,115,98,102,95,112,108,97,110,97,114,0,68,83,68,32,40,68,105,114,101,99,116,32,83,116,114,101,97,109,32,68,105,103,105,116,97,108,41,44,32,109,111,115,116,32,115,105,103,110,105,102,105,99,97,110,116,32,98,105,116,32,102,105,114,115,116,44,32,112,108,97,110,97,114,0,100,118,100,95,115,117,98,116,105,116,108,101,0,68,86,68,32,115,117,98,116,105,116,108,101,115,0,100,118,98,95,115,117,98,116,105,116,108,101,0,68,86,66,32,115,117,98,116,105,116,108,101,115,0,116,101,120,116,0,114,97,119,32,85,84,70,45,56,32,116,101,120,116,0,120,115,117,98,0,88,83,85,66,0,97,115,115,0,65,83,83,32,40,65,100,118,97,110,99,101,100,32,83,83,65,41,32,115,117,98,116,105,116,108,101,0,115,115,97,0,83,83,65,32,40,83,117,98,83,116,97,116,105,111,110,32,65,108,112,104,97,41,32,115,117,98,116,105,116,108,101,0,109,111,118,95,116,101,120,116,0,77,79,86,32,116,101,120,116,0,104,100,109,118,95,112,103,115,95,115,117,98,116,105,116,108,101,0,72,68,77,86,32,80,114,101,115,101,110,116,97,116,105,111,110,32,71,114,97,112,104,105,99,32,83,116,114,101,97,109,32,115,117,98,116,105,116,108,101,115,0,100,118,98,95,116,101,108,101,116,101,120,116,0,68,86,66,32,116,101,108,101,116,101,120,116,0,115,114,116,0,83,117,98,82,105,112,32,115,117,98,116,105,116,108,101,32,119,105,116,104,32,101,109,98,101,100,100,101,100,32,116,105,109,105,110,103,0,115,117,98,114,105,112,0,83,117,98,82,105,112,32,115,117,98,116,105,116,108,101,0,109,105,99,114,111,100,118,100,0,77,105,99,114,111,68,86,68,32,115,117,98,116,105,116,108,101,0,109,112,108,50,0,77,80,76,50,32,115,117,98,116,105,116,108,101,0,101,105,97,95,54,48,56,0,69,73,65,45,54,48,56,32,99,108,111,115,101,100,32,99,97,112,116,105,111,110,115,0,106,97,99,111,115,117,98,0,74,65,67,79,115,117,98,32,115,117,98,116,105,116,108,101,0,112,106,115,0,80,74,83,32,40,80,104,111,101,110,105,120,32,74,97,112,97,110,105,109,97,116,105,111,110,32,83,111,99,105,101,116,121,41,32,115,117,98,116,105,116,108,101,0,115,97,109,105,0,83,65,77,73,32,115,117,98,116,105,116,108,101,0,114,101,97,108,116,101,120,116,0,82,101,97,108,84,101,120,116,32,115,117,98,116,105,116,108,101,0,115,116,108,0,83,112,114,117,99,101,32,115,117,98,116,105,116,108,101,32,102,111,114,109,97,116,0,115,117,98,118,105,101,119,101,114,49,0,83,117,98,86,105,101,119,101,114,32,118,49,32,115,117,98,116,105,116,108,101,0,115,117,98,118,105,101,119,101,114,0,83,117,98,86,105,101,119,101,114,32,115,117,98,116,105,116,108,101,0,118,112,108,97,121,101,114,0,86,80,108,97,121,101,114,32,115,117,98,116,105,116,108,101,0,119,101,98,118,116,116,0,87,101,98,86,84,84,32,115,117,98,116,105,116,108,101,0,104,100,109,118,95,116,101,120,116,95,115,117,98,116,105,116,108,101,0,72,68,77,86,32,84,101,120,116,32,115,117,98,116,105,116,108,101,0,116,116,102,0,84,114,117,101,84,121,112,101,32,102,111,110,116,0,97,112,112,108,105,99,97,116,105,111,110,47,120,45,116,114,117,101,116,121,112,101,45,102,111,110,116,0,97,112,112,108,105,99,97,116,105,111,110,47,120,45,102,111,110,116,0,98,105,110,116,101,120,116,0,66,105,110,97,114,121,32,116,101,120,116,0,120,98,105,110,0,101,88,116,101,110,100,101,100,32,66,73,78,97,114,121,32,116,101,120,116,0,105,100,102,0,105,67,69,68,114,97,119,32,116,101,120,116,0,111,116,102,0,79,112,101,110,84,121,112,101,32,102,111,110,116,0,97,112,112,108,105,99,97,116,105,111,110,47,118,110,100,46,109,115,45,111,112,101,110,116,121,112,101,0,107,108,118,0,83,77,80,84,69,32,51,51,54,77,32,75,101,121,45,76,101,110,103,116,104,45,86,97,108,117,101,32,40,75,76,86,41,32,109,101,116,97,100,97,116,97,0,100,118,100,95,110,97,118,95,112,97,99,107,101,116,0,68,86,68,32,78,97,118,32,112,97,99,107,101,116,0,116,105,109,101,100,95,105,100,51,0,116,105,109,101,100,32,73,68,51,32,109,101,116,97,100,97,116,97,0,98,105,110,95,100,97,116,97,0,98,105,110,97,114,121,32,100,97,116,97,0,97,112,112,108,105,99,97,116,105,111,110,47,111,99,116,101,116,45,115,116,114,101,97,109,0,98,114,101,110,100,101,114,95,112,105,120,95,100,101,112,114,101,99,97,116,101,100,0,66,82,101,110,100,101,114,32,80,73,88,32,105,109,97,103,101,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,101,115,99,97,112,101,49,51,48,95,100,101,112,114,101,99,97,116,101,100,0,69,115,99,97,112,101,32,49,51,48,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,101,120,114,95,100,101,112,114,101,99,97,116,101,100,0,79,112,101,110,69,88,82,32,105,109,97,103,101,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,103,50,109,95,100,101,112,114,101,99,97,116,101,100,0,71,111,50,77,101,101,116,105,110,103,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,104,101,118,99,95,100,101,112,114,101,99,97,116,101,100,0,72,46,50,54,53,32,47,32,72,69,86,67,32,40,72,105,103,104,32,69,102,102,105,99,105,101,110,99,121,32,86,105,100,101,111,32,67,111,100,105,110,103,41,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,112,97,102,95,118,105,100,101,111,95,100,101,112,114,101,99,97,116,101,100,0,65,109,97,122,105,110,103,32,83,116,117,100,105,111,32,80,97,99,107,101,100,32,65,110,105,109,97,116,105,111,110,32,70,105,108,101,32,86,105,100,101,111,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,115,97,110,109,95,100,101,112,114,101,99,97,116,101,100,0,76,117,99,97,115,65,114,116,115,32,83,65,78,77,47,83,77,85,83,72,32,118,105,100,101,111,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,118,112,55,95,100,101,112,114,101,99,97,116,101,100,0,79,110,50,32,86,80,55,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,119,101,98,112,95,100,101,112,114,101,99,97,116,101,100,0,87,101,98,80,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,118,105,109,97,0,76,117,99,97,115,65,114,116,115,32,86,73,77,65,32,97,117,100,105,111,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,97,100,112,99,109,95,118,105,109,97,95,100,101,112,114,101,99,97,116,101,100,0,111,112,117,115,95,100,101,112,114,101,99,97,116,101,100,0,79,112,117,115,32,40,79,112,117,115,32,73,110,116,101,114,97,99,116,105,118,101,32,65,117,100,105,111,32,67,111,100,101,99,41,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,112,97,102,95,97,117,100,105,111,95,100,101,112,114,101,99,97,116,101,100,0,65,109,97,122,105,110,103,32,83,116,117,100,105,111,32,80,97,99,107,101,100,32,65,110,105,109,97,116,105,111,110,32,70,105,108,101,32,65,117,100,105,111,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,112,99,109,95,115,50,52,108,101,95,112,108,97,110,97,114,95,100,101,112,114,101,99,97,116,101,100,0,80,67,77,32,115,105,103,110,101,100,32,50,52,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,32,112,108,97,110,97,114,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,112,99,109,95,115,51,50,108,101,95,112,108,97,110,97,114,95,100,101,112,114,101,99,97,116,101,100,0,80,67,77,32,115,105,103,110,101,100,32,51,50,45,98,105,116,32,108,105,116,116,108,101,45,101,110,100,105,97,110,32,112,108,97,110,97,114,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,112,99,109,95,115,49,54,98,101,95,112,108,97,110,97,114,95,100,101,112,114,101,99,97,116,101,100,0,80,67,77,32,115,105,103,110,101,100,32,49,54,45,98,105,116,32,98,105,103,45,101,110,100,105,97,110,32,112,108,97,110,97,114,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,116,97,107,95,100,101,112,114,101,99,97,116,101,100,0,84,65,75,32,40,84,111,109,39,115,32,108,111,115,115,108,101,115,115,32,65,117,100,105,111,32,75,111,109,112,114,101,115,115,111,114,41,32,40,100,101,112,114,101,99,97,116,101,100,32,105,100,41,0,104,50,54,52,0,72,46,50,54,52,32,47,32,65,86,67,32,47,32,77,80,69,71,45,52,32,65,86,67,32,47,32,77,80,69,71,45,52,32,112,97,114,116,32,49,48,0,72,50,54,52,32,68,101,99,111,100,101,114,0,105,115,95,97,118,99,0,105,115,32,97,118,99,0,110,97,108,95,108,101,110,103,116,104,95,115,105,122,101,0,101,110,97,98,108,101,95,101,114,0,69,110,97,98,108,101,32,101,114,114,111,114,32,114,101,115,105,108,105,101,110,99,101,32,111,110,32,100,97,109,97,103,101,100,32,102,114,97,109,101,115,32,40,117,110,115,97,102,101,41,0,66,97,115,101,108,105,110,101,0,67,111,110,115,116,114,97,105,110,101,100,32,66,97,115,101,108,105,110,101,0,77,97,105,110,0,69,120,116,101,110,100,101,100,0,72,105,103,104,0,72,105,103,104,32,49,48,0,72,105,103,104,32,49,48,32,73,110,116,114,97,0,72,105,103,104,32,52,58,50,58,50,0,72,105,103,104,32,52,58,50,58,50,32,73,110,116,114,97,0,72,105,103,104,32,52,58,52,58,52,0,72,105,103,104,32,52,58,52,58,52,32,80,114,101,100,105,99,116,105,118,101,0,72,105,103,104,32,52,58,52,58,52,32,73,110,116,114,97,0,67,65,86,76,67,32,52,58,52,58,52,0,97,118,99,67,32,37,100,32,116,111,111,32,115,104,111,114,116,10,0,65,86,67,58,32,110,97,108,32,115,105,122,101,32,37,100,10,0,78,65,76,32,37,100,47,37,100,32,97,116,32,37,100,47,37,100,32,108,101,110,103,116,104,32,37,100,10,0,65,86,67,58,32,67,111,110,115,117,109,101,100,32,111,110,108,121,32,37,100,32,98,121,116,101,115,32,105,110,115,116,101,97,100,32,111,102,32,37,100,10,0,73,103,110,111,114,105,110,103,32,78,65,76,32,37,100,32,105,110,32,103,108,111,98,97,108,32,104,101,97,100,101,114,47,101,120,116,114,97,100,97,116,97,10,0,73,110,118,97,108,105,100,32,105,110,116,101,114,32,73,68,82,32,102,114,97,109,101,10,0,105,110,118,97,108,105,100,32,109,105,120,101,100,32,73,68,82,32,47,32,110,111,110,32,73,68,82,32,102,114,97,109,101,115,32,99,97,110,110,111,116,32,98,101,32,100,101,99,111,100,101,100,32,105,110,32,115,108,105,99,101,32,109,117,108,116,105,116,104,114,101,97,100,105,110,103,32,109,111,100,101,10,0,73,110,118,97,108,105,100,32,80,79,67,32,37,100,60,37,100,10,0,73,110,99,114,101,97,115,105,110,103,32,114,101,111,114,100,101,114,32,98,117,102,102,101,114,32,116,111,32,37,100,10,0,112,105,99,115,32,60,61,32,49,54,0,108,105,98,97,118,99,111,100,101,99,47,104,50,54,52,46,99,0,111,111,111,0,110,111,32,112,105,99,116,117,114,101,32,37,115,10,0,100,97,116,97,32,112,97,114,116,105,116,105,111,110,105,110,103,0,83,80,83,32,100,101,99,111,100,105,110,103,32,102,97,105,108,117,114,101,44,32,116,114,121,105,110,103,32,97,103,97,105,110,32,119,105,116,104,32,116,104,101,32,99,111,109,112,108,101,116,101,32,78,65,76,10,0,110,101,120,116,95,97,118,99,32,45,32,98,117,102,95,105,110,100,101,120,32,43,32,99,111,110,115,117,109,101,100,32,61,61,32,110,97,108,115,105,122,101,0,85,110,107,110,111,119,110,32,78,65,76,32,99,111,100,101,58,32,37,100,32,40,37,100,32,98,105,116,115,41,10,0,100,101,99,111,100,101,95,115,108,105,99,101,95,104,101,97,100,101,114,32,101,114,114,111,114,10,0,68,101,99,111,100,105,110,103,32,115,112,115,32,37,100,32,102,114,111,109,32,97,118,99,67,32,102,97,105,108,101,100,10,0,68,101,99,111,100,105,110,103,32,112,112,115,32,37,100,32,102,114,111,109,32,97,118,99,67,32,102,97,105,108,101,100,10,0,69,114,114,111,114,32,114,101,115,105,108,105,101,110,99,101,32,119,105,116,104,32,115,108,105,99,101,32,116,104,114,101,97,100,115,32,105,115,32,101,110,97,98,108,101,100,46,32,73,116,32,105,115,32,117,110,115,97,102,101,32,97,110,100,32,117,110,115,117,112,112,111,114,116,101,100,32,97,110,100,32,109,97,121,32,99,114,97,115,104,46,32,85,115,101,32,105,116,32,97,116,32,121,111,117,114,32,111,119,110,32,114,105,115,107,10,0,115,116,101,114,101,111,95,109,111,100,101,0,98,117,102,95,105,110,100,101,120,32,60,61,32,98,117,102,95,115,105,122,101,0,81,50,54,52,0,110,111,32,102,114,97,109,101,33,10,0,68,117,112,108,105,99,97,116,105,110,103,32,102,105,101,108,100,32,37,100,32,116,111,32,102,105,108,108,32,109,105,115,115,105,110,103,10,0,112,105,99,116,45,62,98,117,102,91,48,93,32,124,124,32,33,42,103,111,116,95,102,114,97,109,101,0,255,0,9,255,255,255,255,255,0,0,0,0,116,111,112,32,98,108,111,99,107,32,117,110,97,118,97,105,108,97,98,108,101,32,102,111,114,32,114,101,113,117,101,115,116,101,100,32,105,110,116,114,97,52,120,52,32,109,111,100,101,32,37,100,32,97,116,32,37,100,32,37,100,10,0,0,255,10,0,255,255,255,0,255,11,0,0,108,101,102,116,32,98,108,111,99,107,32,117,110,97,118,97,105,108,97,98,108,101,32,102,111,114,32,114,101,113,117,101,115,116,101,100,32,105,110,116,114,97,52,120,52,32,109,111,100,101,32,37,100,32,97,116,32,37,100,32,37,100,10,0,111,117,116,32,111,102,32,114,97,110,103,101,32,105,110,116,114,97,32,99,104,114,111,109,97,32,112,114,101,100,32,109,111,100,101,32,97,116,32,37,100,32,37,100,10,0,4,1,255,255,116,111,112,32,98,108,111,99,107,32,117,110,97,118,97,105,108,97,98,108,101,32,102,111,114,32,114,101,113,117,101,115,116,101,100,32,105,110,116,114,97,32,109,111,100,101,32,97,116,32,37,100,32,37,100,10,0,108,101,102,116,32,98,108,111,99,107,32,117,110,97,118,97,105,108,97,98,108,101,32,102,111,114,32,114,101,113,117,101,115,116,101,100,32,105,110,116,114,97,32,109,111,100,101,32,97,116,32,37,100,32,37,100,10,0,5,255,2,255,6,67,97,110,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,46,10,0,82,101,102,101,114,101,110,99,101,32,110,111,116,32,97,118,97,105,108,97,98,108,101,32,102,111,114,32,101,114,114,111,114,32,99,111,110,99,101,97,108,105,110,103,10,0,82,101,102,101,114,101,110,99,101,32,105,110,118,97,108,105,100,10,0,108,117,109,97,95,108,111,103,50,95,119,101,105,103,104,116,95,100,101,110,111,109,32,37,100,32,105,115,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,99,104,114,111,109,97,95,108,111,103,50,95,119,101,105,103,104,116,95,100,101,110,111,109,32,37,100,32,105,115,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,114,101,102,101,114,101,110,99,101,32,111,118,101,114,102,108,111,119,32,37,117,32,62,32,37,117,32,111,114,32,37,117,32,62,32,37,117,10,0,2,0,0,0,6,1,0,0,6,6,3,0,6,7,7,6,6,8,8,7,1,0,0,0,7,1,0,0,4,6,1,0,3,3,2,5,2,3,2,0,1,0,0,0,7,2,0,0,7,7,3,0,9,7,7,5,9,9,7,6,10,10,9,7,11,11,10,7,12,12,11,10,13,12,12,11,1,0,0,0,15,1,0,0,14,13,1,0,7,12,11,1,6,5,10,1,7,6,4,9,7,6,5,8,7,6,5,4,7,5,4,4,1,0,0,0,6,2,0,0,8,6,3,0,9,8,7,5,10,9,8,6,11,10,9,7,13,11,10,8,13,13,11,9,13,13,13,10,14,14,13,11,14,14,14,13,15,15,14,14,15,15,15,14,16,15,15,15,16,16,16,15,16,16,16,16,16,16,16,16,2,0,0,0,6,2,0,0,6,5,3,0,7,6,6,4,8,6,6,4,8,7,7,5,9,8,8,6,11,9,9,6,11,11,11,7,12,11,11,9,12,12,12,11,12,12,12,11,13,13,13,12,13,13,13,13,13,14,13,13,14,14,14,13,14,14,14,14,4,0,0,0,6,4,0,0,6,5,4,0,6,5,5,4,7,5,5,4,7,5,5,4,7,6,6,4,7,6,6,4,8,7,7,5,8,8,7,6,9,8,8,7,9,9,8,8,9,9,9,8,10,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,6,0,0,0,6,6,0,0,6,6,6,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,1,0,0,0,5,1,0,0,7,4,1,0,7,6,5,3,7,6,5,3,7,6,5,4,15,6,5,4,11,14,5,4,8,10,13,4,15,14,9,4,11,10,13,12,15,14,9,12,11,10,13,8,15,1,9,12,11,14,13,8,7,10,9,12,4,6,5,8,3,0,0,0,11,2,0,0,7,7,3,0,7,10,9,5,7,6,5,4,4,6,5,6,7,6,5,8,15,6,5,4,11,14,13,4,15,10,9,4,11,14,13,12,8,10,9,8,15,14,13,12,11,10,9,12,7,11,6,8,9,8,10,1,7,6,5,4,15,0,0,0,15,14,0,0,11,15,13,0,8,12,14,12,15,10,11,11,11,8,9,10,9,14,13,9,8,10,9,8,15,14,13,13,11,14,10,12,15,10,13,12,11,14,9,12,8,10,13,8,13,7,9,12,9,12,11,10,5,8,7,6,1,4,3,2,3,0,0,0,0,1,0,0,4,5,6,0,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,1,2,3,3,1,2,2,0,1,1,0,0,1,1,1,0,1,1,0,0,1,0,0,0,1,3,3,4,4,4,5,5,3,2,3,3,3,3,3,0,3,3,2,2,3,3,0,0,3,2,2,2,3,0,0,0,2,2,2,2,0,0,0,0,2,2,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1,2,3,2,3,1,1,0,0,1,1,4,5,6,7,0,0,1,1,2,6,7,0,0,6,0,1,2,7,0,0,0,0,1,2,3,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9,3,3,3,3,3,4,4,4,4,5,5,6,6,6,6,0,4,3,3,3,4,4,3,3,4,5,5,6,5,6,0,0,5,3,4,4,3,3,3,4,3,4,5,5,5,0,0,0,4,4,4,3,3,3,3,3,4,5,4,5,0,0,0,0,6,5,3,3,3,3,3,3,4,3,6,0,0,0,0,0,6,5,3,3,3,2,3,4,3,6,0,0,0,0,0,0,6,4,5,3,2,2,3,3,6,0,0,0,0,0,0,0,6,6,4,2,2,3,2,5,0,0,0,0,0,0,0,0,5,5,3,2,2,2,4,0,0,0,0,0,0,0,0,0,4,4,3,3,1,3,0,0,0,0,0,0,0,0,0,0,4,4,2,1,3,0,0,0,0,0,0,0,0,0,0,0,3,3,1,2,0,0,0,0,0,0,0,0,0,0,0,0,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,1,7,6,5,4,3,5,4,3,2,3,2,3,2,1,0,0,5,7,6,5,4,3,4,3,2,3,2,1,1,0,0,0,3,7,5,4,6,5,4,3,3,2,2,1,0,0,0,0,5,4,3,7,6,5,4,3,2,1,1,0,0,0,0,0,1,1,7,6,5,4,3,2,1,1,0,0,0,0,0,0,1,1,5,4,3,3,2,1,1,0,0,0,0,0,0,0,1,1,1,3,3,2,2,1,0,0,0,0,0,0,0,0,1,0,1,3,2,1,1,1,0,0,0,0,0,0,0,0,1,0,1,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,1,2,1,3,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,3,3,0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,0,0,0,0,0,0,0,0,0,0,2,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,4,5,6,7,8,9,10,11,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,3,2,3,2,1,0,0,0,0,0,0,0,0,0,0,0,3,0,1,3,2,5,4,0,0,0,0,0,0,0,0,0,7,6,5,4,3,2,1,1,1,1,1,1,1,1,1,0,0,1,2,3,7,10,8,11,3,7,11,15,17,33,21,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,8,11,8,11,11,11,15,15,21,37,21,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,7,10,7,10,3,3,7,7,17,33,17,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,7,10,7,10,3,11,3,11,17,33,17,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,109,98,95,116,121,112,101,32,37,100,32,105,110,32,37,99,32,115,108,105,99,101,32,116,111,111,32,108,97,114,103,101,32,97,116,32,37,100,32,37,100,10,0,78,111,116,32,101,110,111,117,103,104,32,100,97,116,97,32,102,111,114,32,97,110,32,105,110,116,114,97,32,80,67,77,32,98,108,111,99,107,46,10,0,66,32,115,117,98,95,109,98,95,116,121,112,101,32,37,117,32,111,117,116,32,111,102,32,114,97,110,103,101,32,97,116,32,37,100,32,37,100,10,0,80,32,115,117,98,95,109,98,95,116,121,112,101,32,37,117,32,111,117,116,32,111,102,32,114,97,110,103,101,32,97,116,32,37,100,32,37,100,10,0,114,101,102,32,37,117,32,111,118,101,114,102,108,111,119,10,0,99,98,112,32,116,111,111,32,108,97,114,103,101,32,40,37,117,41,32,97,116,32,37,100,32,37,100,10,0,47,31,15,0,23,27,29,30,7,11,13,14,39,43,45,46,16,3,5,10,12,19,21,26,28,35,37,42,44,1,2,4,8,17,18,20,24,6,9,22,25,32,33,34,36,40,38,41,0,16,1,2,4,8,32,3,5,10,12,15,47,7,11,13,14,6,9,31,35,37,42,44,33,34,36,40,39,43,45,46,17,18,20,24,19,21,26,28,23,27,29,30,22,25,38,41,15,0,7,11,13,14,3,5,10,12,1,2,4,8,6,9,0,1,2,4,8,3,5,10,12,15,7,11,13,14,6,9,100,113,117,97,110,116,32,111,117,116,32,111,102,32,114,97,110,103,101,32,40,37,100,41,32,97,116,32,37,100,32,37,100,10,0,99,111,114,114,117,112,116,101,100,32,109,97,99,114,111,98,108,111,99,107,32,37,100,32,37,100,32,40,116,111,116,97,108,95,99,111,101,102,102,61,37,100,41,10,0,73,110,118,97,108,105,100,32,108,101,118,101,108,32,112,114,101,102,105,120,10,0,110,101,103,97,116,105,118,101,32,110,117,109,98,101,114,32,111,102,32,122,101,114,111,32,99,111,101,102,102,115,32,97,116,32,37,100,32,37,100,10,0,0,16,32,48,33,100,115,116,45,62,102,45,62,98,117,102,91,48,93,0,108,105,98,97,118,99,111,100,101,99,47,104,50,54,52,95,112,105,99,116,117,114,101,46,99,0,115,114,99,45,62,102,45,62,98,117,102,91,48,93,0,104,97,114,100,119,97,114,101,32,97,99,99,101,108,101,114,97,116,111,114,32,102,97,105,108,101,100,32,116,111,32,100,101,99,111,100,101,32,112,105,99,116,117,114,101,10,0,115,108,32,61,61,32,104,45,62,115,108,105,99,101,95,99,116,120,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,29,30,31,32,32,33,34,34,35,35,36,36,37,37,37,38,38,38,39,39,39,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,35,36,37,38,38,39,40,40,41,41,42,42,43,43,43,44,44,44,45,45,45,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,41,42,43,44,44,45,46,46,47,47,48,48,49,49,49,50,50,50,51,51,51,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,47,48,49,50,50,51,52,52,53,53,54,54,55,55,55,56,56,56,57,57,57,57,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,53,54,55,56,56,57,58,58,59,59,60,60,61,61,61,62,62,62,63,63,63,63,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,59,60,61,62,62,63,64,64,65,65,66,66,67,67,67,68,68,68,69,69,69,69,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,65,66,67,68,68,69,70,70,71,71,72,72,73,73,73,74,74,74,75,75,75,75,115,112,115,95,105,100,32,37,117,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,99,104,114,111,109,97,95,102,111,114,109,97,116,95,105,100,99,32,37,117,0,115,101,112,97,114,97,116,101,32,99,111,108,111,114,32,112,108,97,110,101,115,32,97,114,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,68,105,102,102,101,114,101,110,116,32,99,104,114,111,109,97,32,97,110,100,32,108,117,109,97,32,98,105,116,32,100,101,112,116,104,0,105,108,108,101,103,97,108,32,98,105,116,32,100,101,112,116,104,32,118,97,108,117,101,32,40,37,100,44,32,37,100,41,10,0,6,10,13,16,18,23,25,27,10,11,16,18,23,25,27,29,13,16,18,23,25,27,29,31,16,18,23,25,27,29,31,33,18,23,25,27,29,31,33,36,23,25,27,29,31,33,36,38,25,27,29,31,33,36,38,40,27,29,31,33,36,38,40,42,9,13,15,17,19,21,22,24,13,13,17,19,21,22,24,25,15,17,19,21,22,24,25,27,17,19,21,22,24,25,27,28,19,21,22,24,25,27,28,30,21,22,24,25,27,28,30,32,22,24,25,27,28,30,32,33,24,25,27,28,30,32,33,35,6,13,20,28,13,20,28,32,20,28,32,37,28,32,37,42,10,14,20,24,14,20,24,27,20,24,27,30,24,27,30,34,108,111,103,50,95,109,97,120,95,102,114,97,109,101,95,110,117,109,95,109,105,110,117,115,52,32,111,117,116,32,111,102,32,114,97,110,103,101,32,40,48,45,49,50,41,58,32,37,100,10,0,108,111,103,50,95,109,97,120,95,112,111,99,95,108,115,98,32,40,37,100,41,32,105,115,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,112,111,99,95,99,121,99,108,101,95,108,101,110,103,116,104,32,111,118,101,114,102,108,111,119,32,37,100,10,0,105,108,108,101,103,97,108,32,80,79,67,32,116,121,112,101,32,37,100,10,0,116,111,111,32,109,97,110,121,32,114,101,102,101,114,101,110,99,101,32,102,114,97,109,101,115,32,37,100,10,0,109],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+52876);allocate([98,95,119,105,100,116,104,47,104,101,105,103,104,116,32,111,118,101,114,102,108,111,119,10,0,100,105,115,99,97,114,100,105,110,103,32,115,112,115,32,99,114,111,112,112,105,110,103,44,32,111,114,105,103,105,110,97,108,32,118,97,108,117,101,115,32,97,114,101,32,108,58,37,100,32,114,58,37,100,32,116,58,37,100,32,98,58,37,100,10,0,82,101,100,117,99,105,110,103,32,108,101,102,116,32,99,114,111,112,112,105,110,103,32,116,111,32,37,100,32,99,104,114,111,109,97,32,115,97,109,112,108,101,115,32,116,111,32,112,114,101,115,101,114,118,101,32,97,108,105,103,110,109,101,110,116,46,10,0,99,114,111,112,32,118,97,108,117,101,115,32,105,110,118,97,108,105,100,32,37,100,32,37,100,32,37,100,32,37,100,32,47,32,37,100,32,37,100,10,0,105,108,108,101,103,97,108,32,97,115,112,101,99,116,32,114,97,116,105,111,10,0,84,114,117,110,99,97,116,101,100,32,86,85,73,10,0,116,105,109,101,95,115,99,97,108,101,47,110,117,109,95,117,110,105,116,115,95,105,110,95,116,105,99,107,32,105,110,118,97,108,105,100,32,111,114,32,117,110,115,117,112,112,111,114,116,101,100,32,40,37,117,47,37,117,41,10,0,99,112,98,95,99,111,117,110,116,32,37,100,32,105,110,118,97,108,105,100,10,0,67,108,105,112,112,105,110,103,32,105,108,108,101,103,97,108,32,110,117,109,95,114,101,111,114,100,101,114,95,102,114,97,109,101,115,32,37,100,10,0,86,85,73,0,83,80,83,0,79,118,101,114,114,101,97,100,32,37,115,32,98,121,32,37,100,32,98,105,116,115,10,0,77,66,45,65,70,70,0,80,73,67,45,65,70,70,0,70,82,77,0,56,66,56,0,71,114,97,121,0,52,50,48,0,0,52,50,50,0,0,52,52,52,0,0,115,112,115,58,37,117,32,112,114,111,102,105,108,101,58,37,100,47,37,100,32,112,111,99,58,37,100,32,114,101,102,58,37,100,32,37,100,120,37,100,32,37,115,32,37,115,32,99,114,111,112,58,37,117,47,37,117,47,37,117,47,37,117,32,37,115,32,37,115,32,37,100,47,37,100,32,98,37,100,32,114,101,111,58,37,100,10,0,73,110,118,97,108,105,100,32,108,117,109,97,32,98,105,116,32,100,101,112,116,104,61,37,100,10,0,85,110,105,109,112,108,101,109,101,110,116,101,100,32,108,117,109,97,32,98,105,116,32,100,101,112,116,104,61,37,100,10,0,70,77,79,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,114,101,102,101,114,101,110,99,101,32,111,118,101,114,102,108,111,119,32,40,112,112,115,41,10,0,67,117,114,114,101,110,116,32,112,114,111,102,105,108,101,32,100,111,101,115,110,39,116,32,112,114,111,118,105,100,101,32,109,111,114,101,32,82,66,83,80,32,100,97,116,97,32,105,110,32,80,80,83,44,32,115,107,105,112,112,105,110,103,10,0,67,65,66,65,67,0,67,65,86,76,67,0,119,101,105,103,104,116,101,100,0,76,80,65,82,0,67,79,78,83,84,82,0,82,69,68,85,0,56,120,56,68,67,84,0,112,112,115,58,37,117,32,115,112,115,58,37,117,32,37,115,32,115,108,105,99,101,95,103,114,111,117,112,115,58,37,100,32,114,101,102,58,37,117,47,37,117,32,37,115,32,113,112,58,37,100,47,37,100,47,37,100,47,37,100,32,37,115,32,37,115,32,37,115,32,37,115,10,0,108,101,110,32,60,61,32,51,50,0,108,105,98,97,118,99,111,100,101,99,47,104,50,54,52,95,114,101,102,115,46,99,0,105,110,100,101,120,32,60,32,100,101,102,95,108,101,110,0,68,105,115,99,97,114,100,105,110,103,32,109,105,115,109,97,116,99,104,105,110,103,32,114,101,102,101,114,101,110,99,101,10,0,115,104,111,114,116,32,116,101,114,109,32,108,105,115,116,58,10,0,37,117,32,102,110,58,37,100,32,112,111,99,58,37,100,32,37,112,10,0,108,111,110,103,32,116,101,114,109,32,108,105,115,116,58,10,0,114,101,102,101,114,101,110,99,101,32,99,111,117,110,116,32,111,118,101,114,102,108,111,119,10,0,97,98,115,95,100,105,102,102,95,112,105,99,95,110,117,109,32,111,118,101,114,102,108,111,119,10,0,108,111,110,103,95,116,101,114,109,95,112,105,99,95,105,100,120,32,111,118,101,114,102,108,111,119,10,0,105,108,108,101,103,97,108,32,109,111,100,105,102,105,99,97,116,105,111,110,95,111,102,95,112,105,99,95,110,117,109,115,95,105,100,99,32,37,117,10,0,114,101,102,101,114,101,110,99,101,32,112,105,99,116,117,114,101,32,109,105,115,115,105,110,103,32,100,117,114,105,110,103,32,114,101,111,114,100,101,114,10,0,77,105,115,115,105,110,103,32,114,101,102,101,114,101,110,99,101,32,112,105,99,116,117,114,101,44,32,100,101,102,97,117,108,116,32,105,115,32,37,100,10,0,97,118,95,98,117,102,102,101,114,95,103,101,116,95,114,101,102,95,99,111,117,110,116,40,115,108,45,62,114,101,102,95,108,105,115,116,91,108,105,115,116,93,91,105,110,100,101,120,93,46,112,97,114,101,110,116,45,62,102,45,62,98,117,102,91,48,93,41,32,62,32,48,0,77,77,67,79,32,111,112,99,111,100,101,32,91,37,100,44,32,37,100,93,32,97,116,32,37,100,32,109,105,115,109,97,116,99,104,101,115,32,98,101,116,119,101,101,110,32,115,108,105,99,101,115,10,0,73,110,99,111,110,115,105,115,116,101,110,116,32,77,77,67,79,32,115,116,97,116,101,32,98,101,116,119,101,101,110,32,115,108,105,99,101,115,32,91,37,100,44,32,37,100,93,10,0,110,111,32,109,109,99,111,32,104,101,114,101,10,0,109,109,99,111,58,37,100,32,37,100,32,37,100,10,0,37,100,32,37,100,32,37,112,10,0,109,109,99,111,58,32,117,110,114,101,102,32,115,104,111,114,116,32,102,97,105,108,117,114,101,10,0,109,109,99,111,58,32,117,110,114,101,102,32,115,104,111,114,116,32,37,100,32,99,111,117,110,116,32,37,100,10,0,114,101,109,111,118,101,32,115,104,111,114,116,32,37,100,32,99,111,117,110,116,32,37,100,10,0,109,109,99,111,58,32,117,110,114,101,102,32,108,111,110,103,32,102,97,105,108,117,114,101,10,0,109,109,99,111,58,32,99,97,110,110,111,116,32,97,115,115,105,103,110,32,99,117,114,114,101,110,116,32,112,105,99,116,117,114,101,32,116,111,32,115,104,111,114,116,32,97,110,100,32,108,111,110,103,32,97,116,32,116,104,101,32,115,97,109,101,32,116,105,109,101,10,0,109,109,99,111,58,32,99,97,110,110,111,116,32,97,115,115,105,103,110,32,99,117,114,114,101,110,116,32,112,105,99,116,117,114,101,32,116,111,32,50,32,108,111,110,103,32,116,101,114,109,32,114,101,102,101,114,101,110,99,101,115,10,0,33,104,45,62,99,117,114,95,112,105,99,95,112,116,114,45,62,108,111,110,103,95,114,101,102,0,105,108,108,101,103,97,108,32,115,104,111,114,116,32,116,101,114,109,32,114,101,102,101,114,101,110,99,101,32,97,115,115,105,103,110,109,101,110,116,32,102,111,114,32,115,101,99,111,110,100,32,102,105,101,108,100,32,105,110,32,99,111,109,112,108,101,109,101,110,116,97,114,121,32,102,105,101,108,100,32,112,97,105,114,32,40,102,105,114,115,116,32,102,105,101,108,100,32,105,115,32,108,111,110,103,32,116,101,114,109,41,10,0,105,108,108,101,103,97,108,32,115,104,111,114,116,32,116,101,114,109,32,98,117,102,102,101,114,32,115,116,97,116,101,32,100,101,116,101,99,116,101,100,10,0,110,117,109,98,101,114,32,111,102,32,114,101,102,101,114,101,110,99,101,32,102,114,97,109,101,115,32,40,37,100,43,37,100,41,32,101,120,99,101,101,100,115,32,109,97,120,32,40,37,100,59,32,112,114,111,98,97,98,108,121,32,99,111,114,114,117,112,116,32,105,110,112,117,116,41,44,32,100,105,115,99,97,114,100,105,110,103,32,111,110,101,10,0,105,108,108,101,103,97,108,32,108,111,110,103,32,114,101,102,32,105,110,32,109,101,109,111,114,121,32,109,97,110,97,103,101,109,101,110,116,32,99,111,110,116,114,111,108,32,111,112,101,114,97,116,105,111,110,32,37,100,10,0,105,108,108,101,103,97,108,32,109,101,109,111,114,121,32,109,97,110,97,103,101,109,101,110,116,32,99,111,110,116,114,111,108,32,111,112,101,114,97,116,105,111,110,32,37,100,10,0,83,69,73,32,37,100,32,108,101,110,58,37,100,10,0,83,69,73,32,116,121,112,101,32,37,100,32,115,105,122,101,32,37,100,32,116,114,117,110,99,97,116,101,100,32,97,116,32,37,100,10,0,1,1,1,2,2,3,3,2,3,99,116,95,116,121,112,101,58,37,88,32,112,105,99,95,115,116,114,117,99,116,58,37,100,10,0,120,50,54,52,32,45,32,99,111,114,101,32,37,100,0,120,50,54,52,32,45,32,99,111,114,101,32,48,48,48,48,0,117,115,101,114,32,100,97,116,97,58,34,37,115,34,10,0,115,101,105,95,114,101,99,111,118,101,114,121,95,102,114,97,109,101,95,99,110,116,58,32,37,100,10,0,110,111,110,45,101,120,105,115,116,105,110,103,32,83,80,83,32,37,100,32,114,101,102,101,114,101,110,99,101,100,32,105,110,32,98,117,102,102,101,114,105,110,103,32,112,101,114,105,111,100,10,0,83,69,73,32,70,80,65,32,37,100,32,37,100,32,37,100,32,37,100,32,37,100,32,37,100,10,0,71,114,101,101,110,32,77,101,116,97,100,97,116,97,32,73,110,102,111,32,83,69,73,32,109,101,115,115,97,103,101,10,0,103,114,101,101,110,95,109,101,116,97,100,97,116,97,95,116,121,112,101,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,100,10,0,103,114,101,101,110,95,109,101,116,97,100,97,116,97,95,112,101,114,105,111,100,95,116,121,112,101,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,100,10,0,103,114,101,101,110,95,109,101,116,97,100,97,116,97,95,110,117,109,95,115,101,99,111,110,100,115,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,100,10,0,103,114,101,101,110,95,109,101,116,97,100,97,116,97,95,110,117,109,95,112,105,99,116,117,114,101,115,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,100,10,0,83,69,73,32,71,82,69,69,78,32,67,111,109,112,108,101,120,105,116,121,32,77,101,116,114,105,99,115,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,102,32,37,102,32,37,102,32,37,102,10,0,120,115,100,95,109,101,116,114,105,99,95,116,121,112,101,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,100,10,0,120,115,100,95,109,101,116,114,105,99,95,118,97,108,117,101,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,61,32,37,102,10,0,117,110,107,110,111,119,110,32,83,69,73,32,116,121,112,101,32,37,100,10,0,99,104,101,99,107,101,114,98,111,97,114,100,95,114,108,0,99,104,101,99,107,101,114,98,111,97,114,100,95,108,114,0,99,111,108,95,105,110,116,101,114,108,101,97,118,101,100,95,114,108,0,99,111,108,95,105,110,116,101,114,108,101,97,118,101,100,95,108,114,0,114,111,119,95,105,110,116,101,114,108,101,97,118,101,100,95,114,108,0,114,111,119,95,105,110,116,101,114,108,101,97,118,101,100,95,108,114,0,114,105,103,104,116,95,108,101,102,116,0,108,101,102,116,95,114,105,103,104,116,0,98,111,116,116,111,109,95,116,111,112,0,116,111,112,95,98,111,116,116,111,109,0,98,108,111,99,107,95,114,108,0,98,108,111,99,107,95,108,114,0,0,0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,3,3,4,4,4,4,4,4,5,5,5,5,5,5,6,6,6,6,6,6,7,7,7,7,7,7,8,8,8,8,8,8,9,9,9,9,9,9,10,10,10,10,10,10,11,11,11,11,11,11,12,12,12,12,12,12,13,13,13,13,13,13,14,14,14,14,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,4,5,0,1,2,3,10,13,16,11,14,18,13,16,20,14,18,23,16,20,25,18,23,29,0,3,4,3,3,1,5,1,4,5,2,5,3,1,5,1,20,18,32,19,25,24,22,19,35,21,28,26,26,23,42,24,33,31,28,25,45,26,35,33,32,28,51,30,40,38,36,32,58,34,46,43,0,1,4,8,5,2,3,6,9,12,13,10,7,11,14,15,0,0,4,1,8,12,5,9,13,2,6,10,14,3,7,11,15,0,0,9,17,18,12,40,27,7,35,57,29,30,58,38,53,47,1,2,24,11,19,48,20,14,42,50,22,37,59,31,60,55,8,3,32,4,26,41,13,21,49,43,15,44,52,39,61,62,16,10,25,5,33,34,6,28,56,36,23,51,45,46,54,63,0,0,8,16,1,9,24,32,17,2,25,40,48,56,33,10,3,18,41,49,57,26,11,4,19,34,42,50,58,27,12,5,20,35,43,51,59,28,13,6,21,36,44,52,60,29,14,22,37,45,53,61,30,7,15,38,46,54,62,23,31,39,47,55,63,0,0,9,2,56,18,26,34,27,35,28,36,29,45,7,54,39,8,24,25,33,41,11,42,12,43,13,44,14,53,15,62,47,16,32,40,10,49,4,50,5,51,6,52,22,61,38,23,55,1,17,48,3,57,19,58,20,59,21,60,37,30,46,31,63,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,10,0,86,68,80,65,85,32,100,101,99,111,100,105,110,103,32,100,111,101,115,32,110,111,116,32,115,117,112,112,111,114,116,32,118,105,100,101,111,32,99,111,108,111,114,115,112,97,99,101,46,10,0,85,110,115,117,112,112,111,114,116,101,100,32,98,105,116,32,100,101,112,116,104,32,37,100,10,0,116,111,111,32,109,97,110,121,32,116,104,114,101,97,100,115,47,115,108,105,99,101,115,32,37,100,44,32,114,101,100,117,99,105,110,103,32,116,111,32,37,100,10,0,99,111,110,116,101,120,116,95,105,110,105,116,40,41,32,102,97,105,108,101,100,46,10,0,33,104,45,62,115,101,116,117,112,95,102,105,110,105,115,104,101,100,0,108,105,98,97,118,99,111,100,101,99,47,104,50,54,52,95,115,108,105,99,101,46,99,0,84,111,111,32,109,97,110,121,32,102,105,101,108,100,115,10,0,67,97,110,110,111,116,32,100,101,99,111,100,101,32,109,117,108,116,105,112,108,101,32,97,99,99,101,115,115,32,117,110,105,116,115,32,97,115,32,115,108,105,99,101,32,116,104,114,101,97,100,115,10,0,66,114,111,107,101,110,32,102,114,97,109,101,32,112,97,99,107,101,116,105,122,105,110,103,10,0,115,108,105,99,101,32,116,121,112,101,32,37,100,32,116,111,111,32,108,97,114,103,101,32,97,116,32,37,100,10,0,2,3,1,6,5,65,32,110,111,110,45,105,110,116,114,97,32,115,108,105,99,101,32,105,110,32,97,110,32,73,68,82,32,78,65,76,32,117,110,105,116,46,10,0,73,110,118,97,108,105,100,32,85,69,32,103,111,108,111,109,98,32,99,111,100,101,10,0,112,112,115,95,105,100,32,37,117,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,110,111,110,45,101,120,105,115,116,105,110,103,32,80,80,83,32,37,117,32,114,101,102,101,114,101,110,99,101,100,10,0,80,80,83,32,99,104,97,110,103,101,32,102,114,111,109,32,37,100,32,116,111,32,37,100,32,102,111,114,98,105,100,100,101,110,10,0,110,111,110,45,101,120,105,115,116,105,110,103,32,83,80,83,32,37,117,32,114,101,102,101,114,101,110,99,101,100,10,0,80,80,83,32,99,104,97,110,103,101,100,32,98,101,116,119,101,101,110,32,115,108,105,99,101,115,10,0,83,80,83,32,99,104,97,110,103,101,100,32,105,110,32,116,104,101,32,109,105,100,100,108,101,32,111,102,32,116,104,101,32,102,114,97,109,101,10,0,68,101,108,97,121,101,100,32,102,114,97,109,101,115,32,115,101,101,110,46,32,82,101,101,110,97,98,108,105,110,103,32,108,111,119,32,100,101,108,97,121,32,114,101,113,117,105,114,101,115,32,97,32,99,111,100,101,99,32,102,108,117,115,104,46,10,0,104,45,62,115,112,115,46,99,114,111,112,95,114,105,103,104,116,32,43,32,104,45,62,115,112,115,46,99,114,111,112,95,108,101,102,116,32,60,32,40,117,110,115,105,103,110,101,100,41,104,45,62,119,105,100,116,104,0,104,45,62,115,112,115,46,99,114,111,112,95,116,111,112,32,43,32,104,45,62,115,112,115,46,99,114,111,112,95,98,111,116,116,111,109,32,60,32,40,117,110,115,105,103,110,101,100,41,104,45,62,104,101,105,103,104,116,0,73,110,118,97,108,105,100,32,99,114,111,112,112,101,100,32,100,105,109,101,110,115,105,111,110,115,58,32,37,100,120,37,100,46,10,0,73,103,110,111,114,105,110,103,32,99,114,111,112,112,105,110,103,32,105,110,102,111,114,109,97,116,105,111,110,46,10,0,99,104,97,110,103,105,110,103,32,119,105,100,116,104,32,37,100,32,45,62,32,37,100,32,47,32,104,101,105,103,104,116,32,37,100,32,45,62,32,37,100,32,111,110,32,115,108,105,99,101,32,37,100,10,0,82,101,105,110,105,116,32,99,111,110,116,101,120,116,32,116,111,32,37,100,120,37,100,44,32,112,105,120,95,102,109,116,58,32,37,115,10,0,104,50,54,52,95,115,108,105,99,101,95,104,101,97,100,101,114,95,105,110,105,116,40,41,32,102,97,105,108,101,100,10,0,67,97,110,110,111,116,32,40,114,101,45,41,105,110,105,116,105,97,108,105,122,101,32,99,111,110,116,101,120,116,32,100,117,114,105,110,103,32,112,97,114,97,108,108,101,108,32,100,101,99,111,100,105,110,103,46,10,0,70,114,97,109,101,32,110,117,109,32,99,104,97,110,103,101,32,102,114,111,109,32,37,100,32,116,111,32,37,100,10,0,84,104,105,115,32,115,116,114,101,97,109,32,119,97,115,32,103,101,110,101,114,97,116,101,100,32,98,121,32,97,32,98,114,111,107,101,110,32,101,110,99,111,100,101,114,44,32,105,110,118,97,108,105,100,32,56,120,56,32,105,110,102,101,114,101,110,99,101,10,0,67,104,97,110,103,105,110,103,32,102,105,101,108,100,32,109,111,100,101,32,40,37,100,32,45,62,32,37,100,41,32,98,101,116,119,101,101,110,32,115,108,105,99,101,115,32,105,115,32,110,111,116,32,97,108,108,111,119,101,100,10,0,117,110,115,101,116,32,99,117,114,95,112,105,99,95,112,116,114,32,111,110,32,115,108,105,99,101,32,37,100,10,0,104,45,62,99,117,114,95,112,105,99,95,112,116,114,0,104,45,62,99,117,114,95,112,105,99,95,112,116,114,45,62,102,45,62,98,117,102,91,48,93,0,73,110,118,97,108,105,100,32,102,105,101,108,100,32,109,111,100,101,32,99,111,109,98,105,110,97,116,105,111,110,32,37,100,47,37,100,10,0,70,111,117,110,100,32,114,101,102,101,114,101,110,99,101,32,97,110,100,32,110,111,110,45,114,101,102,101,114,101,110,99,101,32,102,105,101,108,100,115,32,105,110,32,116,104,101,32,115,97,109,101,32,102,114,97,109,101,44,32,119,104,105,99,104,0,70,114,97,109,101,32,110,117,109,32,103,97,112,32,37,100,32,37,100,10,0,65,116,116,101,109,112,116,32,116,111,32,115,116,97,114,116,32,97,32,102,114,97,109,101,32,111,117,116,115,105,100,101,32,83,69,84,85,80,32,115,116,97,116,101,10,0,110,111,32,102,114,97,109,101,32,98,117,102,102,101,114,32,97,118,97,105,108,97,98,108,101,10,0,33,112,105,99,45,62,102,45,62,100,97,116,97,91,48,93,0,33,112,105,99,45,62,104,119,97,99,99,101,108,95,112,105,99,116,117,114,101,95,112,114,105,118,97,116,101,0,102,105,114,115,116,95,109,98,95,105,110,95,115,108,105,99,101,32,111,118,101,114,102,108,111,119,10,0,99,97,98,97,99,95,105,110,105,116,95,105,100,99,32,37,117,32,111,118,101,114,102,108,111,119,10,0,81,80,32,37,117,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,100,101,98,108,111,99,107,105,110,103,95,102,105,108,116,101,114,95,105,100,99,32,37,117,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,100,101,98,108,111,99,107,105,110,103,32,102,105,108,116,101,114,32,112,97,114,97,109,101,116,101,114,115,32,37,100,32,37,100,32,111,117,116,32,111,102,32,114,97,110,103,101,10,0,67,97,110,110,111,116,32,112,97,114,97,108,108,101,108,105,122,101,32,115,108,105,99,101,32,100,101,99,111,100,105,110,103,32,119,105,116,104,32,100,101,98,108,111,99,107,105,110,103,32,102,105,108,116,101,114,32,116,121,112,101,32,49,44,32,100,101,99,111,100,105,110,103,32,115,117,99,104,32,102,114,97,109,101,115,32,105,110,32,115,101,113,117,101,110,116,105,97,108,32,111,114,100,101,114,10,84,111,32,112,97,114,97,108,108,101,108,105,122,101,32,115,108,105,99,101,32,100,101,99,111,100,105,110,103,32,121,111,117,32,110,101,101,100,32,118,105,100,101,111,32,101,110,99,111,100,101,100,32,119,105,116,104,32,100,105,115,97,98,108,101,95,100,101,98,108,111,99,107,105,110,103,95,102,105,108,116,101,114,95,105,100,99,32,115,101,116,32,116,111,32,50,32,40,100,101,98,108,111,99,107,32,111,110,108,121,32,101,100,103,101,115,32,116,104,97,116,32,100,111,32,110,111,116,32,99,114,111,115,115,32,115,108,105,99,101,115,41,46,10,83,101,116,116,105,110,103,32,116,104,101,32,102,108,97,103,115,50,32,108,105,98,97,118,99,111,100,101,99,32,111,112,116,105,111,110,32,116,111,32,43,102,97,115,116,32,40,45,102,108,97,103,115,50,32,43,102,97,115,116,41,32,119,105,108,108,32,100,105,115,97,98,108,101,32,100,101,98,108,111,99,107,105,110,103,32,97,99,114,111,115,115,32,115,108,105,99,101,115,32,97,110,100,32,101,110,97,98,108,101,32,112,97,114,97,108,108,101,108,32,115,108,105,99,101,32,100,101,99,111,100,105,110,103,32,98,117,116,32,119,105,108,108,32,103,101,110,101,114,97,116,101,32,110,111,110,45,115,116,97,110,100,97,114,100,45,99,111,109,112,108,105,97,110,116,32,111,117,116,112,117,116,46,10,0,68,101,98,108,111,99,107,105,110,103,32,115,119,105,116,99,104,101,100,32,105,110,115,105,100,101,32,102,114,97,109,101,46,10,0,80,111,115,115,105,98,108,121,32,116,111,111,32,109,97,110,121,32,115,108,105,99,101,115,32,40,37,100,32,62,61,32,37,100,41,44,32,105,110,99,114,101,97,115,101,32,77,65,88,95,83,76,73,67,69,83,32,97,110,100,32,114,101,99,111,109,112,105,108,101,32,105,102,32,116,104,101,114,101,32,97,114,101,32,97,114,116,105,102,97,99,116,115,10,0,84,0,66,0,70,0,32,102,105,120,0,32,73,68,82,0,99,0,83,80,65,84,0,84,69,77,80,0,115,108,105,99,101,58,37,100,32,37,115,32,109,98,58,37,100,32,37,99,37,115,37,115,32,112,112,115,58,37,117,32,102,114,97,109,101,58,37,100,32,112,111,99,58,37,100,47,37,100,32,114,101,102,58,37,100,47,37,100,32,113,112,58,37,100,32,108,111,111,112,58,37,100,58,37,100,58,37,100,32,119,101,105,103,104,116,58,37,100,37,115,32,37,115,10,0,99,111,110,116,101,120,116,95,99,111,117,110,116,32,38,38,32,104,45,62,115,108,105,99,101,95,99,116,120,91,99,111,110,116,101,120,116,95,99,111,117,110,116,32,45,32,49,93,46,109,98,95,121,32,60,32,104,45,62,109,98,95,104,101,105,103,104,116,0,104,45,62,98,108,111,99,107,95,111,102,102,115,101,116,91,49,53,93,32,61,61,32,40,52,32,42,32,40,40,115,99,97,110,56,91,49,53,93,32,45,32,115,99,97,110,56,91,48,93,41,32,38,32,55,41,32,60,60,32,104,45,62,112,105,120,101,108,95,115,104,105,102,116,41,32,43,32,52,32,42,32,115,108,45,62,108,105,110,101,115,105,122,101,32,42,32,40,40,115,99,97,110,56,91,49,53,93,32,45,32,115,99,97,110,56,91,48,93,41,32,62,62,32,51,41,0,83,108,105,99,101,32,111,118,101,114,108,97,112,115,32,119,105,116,104,32,110,101,120,116,32,97,116,32,37,100,10,0,98,121,116,101,115,116,114,101,97,109,32,111,118,101,114,114,101,97,100,32,37,116,100,10,0,101,114,114,111,114,32,119,104,105,108,101,32,100,101,99,111,100,105,110,103,32,77,66,32,37,100,32,37,100,44,32,98,121,116,101,115,116,114,101,97,109,32,37,116,100,10,0,101,114,114,111,114,32,119,104,105,108,101,32,100,101,99,111,100,105,110,103,32,77,66,32,37,100,32,37,100,10,0,108,105,98,97,118,99,111,100,101,99,47,104,50,54,52,100,115,112,46,99,0,0,32,128,160,98,105,116,95,100,101,112,116,104,60,61,56,0,108,105,98,97,118,99,111,100,101,99,47,104,50,54,52,112,114,101,100,46,99],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+63116);allocate([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63,65,86,67,111,100,101,99,67,111,110,116,101,120,116,0,78,85,76,76,0,98,0,115,101,116,32,98,105,116,114,97,116,101,32,40,105,110,32,98,105,116,115,47,115,41,0,97,98,0,98,116,0,83,101,116,32,118,105,100,101,111,32,98,105,116,114,97,116,101,32,116,111,108,101,114,97,110,99,101,32,40,105,110,32,98,105,116,115,47,115,41,46,32,73,110,32,49,45,112,97,115,115,32,109,111,100,101,44,32,98,105,116,114,97,116,101,32,116,111,108,101,114,97,110,99,101,32,115,112,101,99,105,102,105,101,115,32,104,111,119,32,102,97,114,32,114,97,116,101,99,111,110,116,114,111,108,32,105,115,32,119,105,108,108,105,110,103,32,116,111,32,100,101,118,105,97,116,101,32,102,114,111,109,32,116,104,101,32,116,97,114,103,101,116,32,97,118,101,114,97,103,101,32,98,105,116,114,97,116,101,32,118,97,108,117,101,46,32,84,104,105,115,32,105,115,32,110,111,116,32,114,101,108,97,116,101,100,32,116,111,32,109,105,110,105,109,117,109,47,109,97,120,105,109,117,109,32,98,105,116,114,97,116,101,46,32,76,111,119,101,114,105,110,103,32,116,111,108,101,114,97,110,99,101,32,116,111,111,32,109,117,99,104,32,104,97,115,32,97,110,32,97,100,118,101,114,115,101,32,101,102,102,101,99,116,32,111,110,32,113,117,97,108,105,116,121,46,0,102,108,97,103,115,0,117,110,97,108,105,103,110,101,100,0,97,108,108,111,119,32,100,101,99,111,100,101,114,115,32,116,111,32,112,114,111,100,117,99,101,32,117,110,97,108,105,103,110,101,100,32,111,117,116,112,117,116,0,109,118,52,0,117,115,101,32,102,111,117,114,32,109,111,116,105,111,110,32,118,101,99,116,111,114,115,32,112,101,114,32,109,97,99,114,111,98,108,111,99,107,32,40,77,80,69,71,45,52,41,0,113,112,101,108,0,117,115,101,32,49,47,52,45,112,101,108,32,109,111,116,105,111,110,32,99,111,109,112,101,110,115,97,116,105,111,110,0,108,111,111,112,0,117,115,101,32,108,111,111,112,32,102,105,108,116,101,114,0,113,115,99,97,108,101,0,117,115,101,32,102,105,120,101,100,32,113,115,99,97,108,101,0,103,109,99,0,117,115,101,32,103,109,99,0,109,118,48,0,97,108,119,97,121,115,32,116,114,121,32,97,32,109,98,32,119,105,116,104,32,109,118,61,60,48,44,48,62,0,105,110,112,117,116,95,112,114,101,115,101,114,118,101,100,0,112,97,115,115,49,0,117,115,101,32,105,110,116,101,114,110,97,108,32,50,45,112,97,115,115,32,114,97,116,101,99,111,110,116,114,111,108,32,105,110,32,102,105,114,115,116,32,32,112,97,115,115,32,109,111,100,101,0,112,97,115,115,50,0,117,115,101,32,105,110,116,101,114,110,97,108,32,50,45,112,97,115,115,32,114,97,116,101,99,111,110,116,114,111,108,32,105,110,32,115,101,99,111,110,100,32,112,97,115,115,32,109,111,100,101,0,111,110,108,121,32,100,101,99,111,100,101,47,101,110,99,111,100,101,32,103,114,97,121,115,99,97,108,101,0,101,109,117,95,101,100,103,101,0,100,111,32,110,111,116,32,100,114,97,119,32,101,100,103,101,115,0,112,115,110,114,0,101,114,114,111,114,91,63,93,32,118,97,114,105,97,98,108,101,115,32,119,105,108,108,32,98,101,32,115,101,116,32,100,117,114,105,110,103,32,101,110,99,111,100,105,110,103,0,116,114,117,110,99,97,116,101,100,0,110,97,113,0,110,111,114,109,97,108,105,122,101,32,97,100,97,112,116,105,118,101,32,113,117,97,110,116,105,122,97,116,105,111,110,0,105,108,100,99,116,0,117,115,101,32,105,110,116,101,114,108,97,99,101,100,32,68,67,84,0,108,111,119,95,100,101,108,97,121,0,102,111,114,99,101,32,108,111,119,32,100,101,108,97,121,0,103,108,111,98,97,108,95,104,101,97,100,101,114,0,112,108,97,99,101,32,103,108,111,98,97,108,32,104,101,97,100,101,114,115,32,105,110,32,101,120,116,114,97,100,97,116,97,32,105,110,115,116,101,97,100,32,111,102,32,101,118,101,114,121,32,107,101,121,102,114,97,109,101,0,98,105,116,101,120,97,99,116,0,117,115,101,32,111,110,108,121,32,98,105,116,101,120,97,99,116,32,102,117,110,99,116,105,111,110,115,32,40,101,120,99,101,112,116,32,40,73,41,68,67,84,41,0,97,105,99,0,72,46,50,54,51,32,97,100,118,97,110,99,101,100,32,105,110,116,114,97,32,99,111,100,105,110,103,32,47,32,77,80,69,71,45,52,32,65,67,32,112,114,101,100,105,99,116,105,111,110,0,105,108,109,101,0,105,110,116,101,114,108,97,99,101,100,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,99,103,111,112,0,99,108,111,115,101,100,32,71,79,80,0,111,117,116,112,117,116,95,99,111,114,114,117,112,116,0,79,117,116,112,117,116,32,101,118,101,110,32,112,111,116,101,110,116,105,97,108,108,121,32,99,111,114,114,117,112,116,101,100,32,102,114,97,109,101,115,0,102,97,115,116,0,97,108,108,111,119,32,110,111,110,45,115,112,101,99,45,99,111,109,112,108,105,97,110,116,32,115,112,101,101,100,117,112,32,116,114,105,99,107,115,0,102,108,97,103,115,50,0,110,111,111,117,116,0,115,107,105,112,32,98,105,116,115,116,114,101,97,109,32,101,110,99,111,100,105,110,103,0,105,103,110,111,114,101,99,114,111,112,0,105,103,110,111,114,101,32,99,114,111,112,112,105,110,103,32,105,110,102,111,114,109,97,116,105,111,110,32,102,114,111,109,32,115,112,115,0,108,111,99,97,108,95,104,101,97,100,101,114,0,112,108,97,99,101,32,103,108,111,98,97,108,32,104,101,97,100,101,114,115,32,97,116,32,101,118,101,114,121,32,107,101,121,102,114,97,109,101,32,105,110,115,116,101,97,100,32,111,102,32,105,110,32,101,120,116,114,97,100,97,116,97,0,99,104,117,110,107,115,0,70,114,97,109,101,32,100,97,116,97,32,109,105,103,104,116,32,98,101,32,115,112,108,105,116,32,105,110,116,111,32,109,117,108,116,105,112,108,101,32,99,104,117,110,107,115,0,115,104,111,119,97,108,108,0,83,104,111,119,32,97,108,108,32,102,114,97,109,101,115,32,98,101,102,111,114,101,32,116,104,101,32,102,105,114,115,116,32,107,101,121,102,114,97,109,101,0,101,120,112,111,114,116,95,109,118,115,0,101,120,112,111,114,116,32,109,111,116,105,111,110,32,118,101,99,116,111,114,115,32,116,104,114,111,117,103,104,32,102,114,97,109,101,32,115,105,100,101,32,100,97,116,97,0,115,107,105,112,95,109,97,110,117,97,108,0,100,111,32,110,111,116,32,115,107,105,112,32,115,97,109,112,108,101,115,32,97,110,100,32,101,120,112,111,114,116,32,115,107,105,112,32,105,110,102,111,114,109,97,116,105,111,110,32,97,115,32,102,114,97,109,101,32,115,105,100,101,32,100,97,116,97,0,109,101,95,109,101,116,104,111,100,0,115,101,116,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,109,101,116,104,111,100,0,122,101,114,111,0,122,101,114,111,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,40,102,97,115,116,101,115,116,41,0,102,117,108,108,0,102,117,108,108,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,40,115,108,111,119,101,115,116,41,0,101,112,122,115,0,69,80,90,83,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,40,100,101,102,97,117,108,116,41,0,101,115,97,0,101,115,97,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,40,97,108,105,97,115,32,102,111,114,32,102,117,108,108,41,0,116,101,115,97,0,116,101,115,97,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,100,105,97,0,100,105,97,109,111,110,100,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,40,97,108,105,97,115,32,102,111,114,32,69,80,90,83,41,0,108,111,103,0,108,111,103,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,112,104,111,100,115,0,112,104,111,100,115,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,120,49,0,88,49,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,104,101,120,0,104,101,120,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,117,109,104,0,117,109,104,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,105,116,101,114,0,105,116,101,114,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,116,105,109,101,95,98,97,115,101,0,103,0,115,101,116,32,116,104,101,32,103,114,111,117,112,32,111,102,32,112,105,99,116,117,114,101,32,40,71,79,80,41,32,115,105,122,101,0,97,114,0,115,101,116,32,97,117,100,105,111,32,115,97,109,112,108,105,110,103,32,114,97,116,101,32,40,105,110,32,72,122,41,0,97,99,0,115,101,116,32,110,117,109,98,101,114,32,111,102,32,97,117,100,105,111,32,99,104,97,110,110,101,108,115,0,99,117,116,111,102,102,0,115,101,116,32,99,117,116,111,102,102,32,98,97,110,100,119,105,100,116,104,0,102,114,97,109,101,95,115,105,122,101,0,102,114,97,109,101,95,110,117,109,98,101,114,0,100,101,108,97,121,0,113,99,111,109,112,0,118,105,100,101,111,32,113,117,97,110,116,105,122,101,114,32,115,99,97,108,101,32,99,111,109,112,114,101,115,115,105,111,110,32,40,86,66,82,41,46,32,67,111,110,115,116,97,110,116,32,111,102,32,114,97,116,101,99,111,110,116,114,111,108,32,101,113,117,97,116,105,111,110,46,32,82,101,99,111,109,109,101,110,100,101,100,32,114,97,110,103,101,32,102,111,114,32,100,101,102,97,117,108,116,32,114,99,95,101,113,58,32,48,46,48,45,49,46,48,0,113,98,108,117,114,0,118,105,100,101,111,32,113,117,97,110,116,105,122,101,114,32,115,99,97,108,101,32,98,108,117,114,32,40,86,66,82,41,0,113,109,105,110,0,109,105,110,105,109,117,109,32,118,105,100,101,111,32,113,117,97,110,116,105,122,101,114,32,115,99,97,108,101,32,40,86,66,82,41,0,113,109,97,120,0,109,97,120,105,109,117,109,32,118,105,100,101,111,32,113,117,97,110,116,105,122,101,114,32,115,99,97,108,101,32,40,86,66,82,41,0,113,100,105,102,102,0,109,97,120,105,109,117,109,32,100,105,102,102,101,114,101,110,99,101,32,98,101,116,119,101,101,110,32,116,104,101,32,113,117,97,110,116,105,122,101,114,32,115,99,97,108,101,115,32,40,86,66,82,41,0,98,102,0,115,101,116,32,109,97,120,105,109,117,109,32,110,117,109,98,101,114,32,111,102,32,66,32,102,114,97,109,101,115,32,98,101,116,119,101,101,110,32,110,111,110,45,66,45,102,114,97,109,101,115,0,98,95,113,102,97,99,116,111,114,0,81,80,32,102,97,99,116,111,114,32,98,101,116,119,101,101,110,32,80,45,32,97,110,100,32,66,45,102,114,97,109,101,115,0,114,99,95,115,116,114,97,116,101,103,121,0,114,97,116,101,99,111,110,116,114,111,108,32,109,101,116,104,111,100,0,98,95,115,116,114,97,116,101,103,121,0,115,116,114,97,116,101,103,121,32,116,111,32,99,104,111,111,115,101,32,98,101,116,119,101,101,110,32,73,47,80,47,66,45,102,114,97,109,101,115,0,112,115,0,82,84,80,32,112,97,121,108,111,97,100,32,115,105,122,101,32,105,110,32,98,121,116,101,115,0,109,118,95,98,105,116,115,0,104,101,97,100,101,114,95,98,105,116,115,0,105,95,116,101,120,95,98,105,116,115,0,112,95,116,101,120,95,98,105,116,115,0,105,95,99,111,117,110,116,0,112,95,99,111,117,110,116,0,115,107,105,112,95,99,111,117,110,116,0,109,105,115,99,95,98,105,116,115,0,102,114,97,109,101,95,98,105,116,115,0,99,111,100,101,99,95,116,97,103,0,98,117,103,0,119,111,114,107,32,97,114,111,117,110,100,32,110,111,116,32,97,117,116,111,100,101,116,101,99,116,101,100,32,101,110,99,111,100,101,114,32,98,117,103,115,0,97,117,116,111,100,101,116,101,99,116,0,111,108,100,95,109,115,109,112,101,103,52,0,115,111,109,101,32,111,108,100,32,108,97,118,99,45,103,101,110,101,114,97,116,101,100,32,77,83,77,80,69,71,52,118,51,32,102,105,108,101,115,32,40,110,111,32,97,117,116,111,100,101,116,101,99,116,105,111,110,41,0,120,118,105,100,95,105,108,97,99,101,0,88,118,105,100,32,105,110,116,101,114,108,97,99,105,110,103,32,98,117,103,32,40,97,117,116,111,100,101,116,101,99,116,101,100,32,105,102,32,70,79,85,82,67,67,32,61,61,32,88,86,73,88,41,0,117,109,112,52,0,40,97,117,116,111,100,101,116,101,99,116,101,100,32,105,102,32,70,79,85,82,67,67,32,61,61,32,85,77,80,52,41,0,110,111,95,112,97,100,100,105,110,103,0,112,97,100,100,105,110,103,32,98,117,103,32,40,97,117,116,111,100,101,116,101,99,116,101,100,41,0,97,109,118,0,97,99,95,118,108,99,0,105,108,108,101,103,97,108,32,86,76,67,32,98,117,103,32,40,97,117,116,111,100,101,116,101,99,116,101,100,32,112,101,114,32,70,79,85,82,67,67,41,0,113,112,101,108,95,99,104,114,111,109,97,0,115,116,100,95,113,112,101,108,0,111,108,100,32,115,116,97,110,100,97,114,100,32,113,112,101,108,32,40,97,117,116,111,100,101,116,101,99,116,101,100,32,112,101,114,32,70,79,85,82,67,67,47,118,101,114,115,105,111,110,41,0,113,112,101,108,95,99,104,114,111,109,97,50,0,100,105,114,101,99,116,95,98,108,111,99,107,115,105,122,101,0,100,105,114,101,99,116,45,113,112,101,108,45,98,108,111,99,107,115,105,122,101,32,98,117,103,32,40,97,117,116,111,100,101,116,101,99,116,101,100,32,112,101,114,32,70,79,85,82,67,67,47,118,101,114,115,105,111,110,41,0,101,100,103,101,0,101,100,103,101,32,112,97,100,100,105,110,103,32,98,117,103,32,40,97,117,116,111,100,101,116,101,99,116,101,100,32,112,101,114,32,70,79,85,82,67,67,47,118,101,114,115,105,111,110,41,0,104,112,101,108,95,99,104,114,111,109,97,0,100,99,95,99,108,105,112,0,109,115,0,119,111,114,107,32,97,114,111,117,110,100,32,118,97,114,105,111,117,115,32,98,117,103,115,32,105,110,32,77,105,99,114,111,115,111,102,116,39,115,32,98,114,111,107,101,110,32,100,101,99,111,100,101,114,115,0,116,114,117,110,99,0,116,114,117,110,99,97,116,101,100,32,102,114,97,109,101,115,0,115,116,114,105,99,116,0,104,111,119,32,115,116,114,105,99,116,108,121,32,116,111,32,102,111,108,108,111,119,32,116,104,101,32,115,116,97,110,100,97,114,100,115,0,118,101,114,121,0,115,116,114,105,99,116,108,121,32,99,111,110,102,111,114,109,32,116,111,32,97,32,111,108,100,101,114,32,109,111,114,101,32,115,116,114,105,99,116,32,118,101,114,115,105,111,110,32,111,102,32,116,104,101,32,115,112,101,99,32,111,114,32,114,101,102,101,114,101,110,99,101,32,115,111,102,116,119,97,114,101,0,115,116,114,105,99,116,108,121,32,99,111,110,102,111,114,109,32,116,111,32,97,108,108,32,116,104,101,32,116,104,105,110,103,115,32,105,110,32,116,104,101,32,115,112,101,99,32,110,111,32,109,97,116,116,101,114,32,119,104,97,116,32,116,104,101,32,99,111,110,115,101,113,117,101,110,99,101,115,0,110,111,114,109,97,108,0,117,110,111,102,102,105,99,105,97,108,0,97,108,108,111,119,32,117,110,111,102,102,105,99,105,97,108,32,101,120,116,101,110,115,105,111,110,115,0,101,120,112,101,114,105,109,101,110,116,97,108,0,97,108,108,111,119,32,110,111,110,45,115,116,97,110,100,97,114,100,105,122,101,100,32,101,120,112,101,114,105,109,101,110,116,97,108,32,116,104,105,110,103,115,0,98,95,113,111,102,102,115,101,116,0,81,80,32,111,102,102,115,101,116,32,98,101,116,119,101,101,110,32,80,45,32,97,110,100,32,66,45,102,114,97,109,101,115,0,101,114,114,95,100,101,116,101,99,116,0,115,101,116,32,101,114,114,111,114,32,100,101,116,101,99,116,105,111,110,32,102,108,97,103,115,0,99,114,99,99,104,101,99,107,0,118,101,114,105,102,121,32,101,109,98,101,100,100,101,100,32,67,82,67,115,0,98,105,116,115,116,114,101,97,109,0,100,101,116,101,99,116,32,98,105,116,115,116,114,101,97,109,32,115,112,101,99,105,102,105,99,97,116,105,111,110,32,100,101,118,105,97,116,105,111,110,115,0,98,117,102,102,101,114,0,100,101,116,101,99,116,32,105,109,112,114,111,112,101,114,32,98,105,116,115,116,114,101,97,109,32,108,101,110,103,116,104,0,101,120,112,108,111,100,101,0,97,98,111,114,116,32,100,101,99,111,100,105,110,103,32,111,110,32,109,105,110,111,114,32,101,114,114,111,114,32,100,101,116,101,99,116,105,111,110,0,105,103,110,111,114,101,95,101,114,114,0,105,103,110,111,114,101,32,101,114,114,111,114,115,0,99,97,114,101,102,117,108,0,99,111,110,115,105,100,101,114,32,116,104,105,110,103,115,32,116,104,97,116,32,118,105,111,108,97,116,101,32,116,104,101,32,115,112,101,99,44,32,97,114,101,32,102,97,115,116,32,116,111,32,99,104,101,99,107,32,97,110,100,32,104,97,118,101,32,110,111,116,32,98,101,101,110,32,115,101,101,110,32,105,110,32,116,104,101,32,119,105,108,100,32,97,115,32,101,114,114,111,114,115,0,99,111,109,112,108,105,97,110,116,0,99,111,110,115,105,100,101,114,32,97,108,108,32,115,112,101,99,32,110,111,110,32,99,111,109,112,108,105,97,110,99,105,101,115,32,97,115,32,101,114,114,111,114,115,0,97,103,103,114,101,115,115,105,118,101,0,99,111,110,115,105,100,101,114,32,116,104,105,110,103,115,32,116,104,97,116,32,97,32,115,97,110,101,32,101,110,99,111,100,101,114,32,115,104,111,117,108,100,32,110,111,116,32,100,111,32,97,115,32,97,110,32,101,114,114,111,114,0,104,97,115,95,98,95,102,114,97,109,101,115,0,98,108,111,99,107,95,97,108,105,103,110,0,109,112,101,103,95,113,117,97,110,116,0,117,115,101,32,77,80,69,71,32,113,117,97,110,116,105,122,101,114,115,32,105,110,115,116,101,97,100,32,111,102,32,72,46,50,54,51,0,113,115,113,117,105,115,104,0,100,101,112,114,101,99,97,116,101,100,44,32,117,115,101,32,101,110,99,111,100,101,114,32,112,114,105,118,97,116,101,32,111,112,116,105,111,110,115,32,105,110,115,116,101,97,100,0,114,99,95,113,109,111,100,95,97,109,112,0,114,99,95,113,109,111,100,95,102,114,101,113,0,114,99,95,111,118,101,114,114,105,100,101,95,99,111,117,110,116,0,114,99,95,101,113,0,109,97,120,114,97,116,101,0,109,97,120,105,109,117,109,32,98,105,116,114,97,116,101,32,40,105,110,32,98,105,116,115,47,115,41,46,32,85,115,101,100,32,102,111,114,32,86,66,86,32,116,111,103,101,116,104,101,114,32,119,105,116,104,32,98,117,102,115,105,122,101,46,0,109,105,110,114,97,116,101,0,109,105,110,105,109,117,109,32,98,105,116,114,97,116,101,32,40,105,110,32,98,105,116,115,47,115,41,46,32,77,111,115,116,32,117,115,101,102,117,108,32,105,110,32,115,101,116,116,105,110,103,32,117,112,32,97,32,67,66,82,32,101,110,99,111,100,101,46,32,73,116,32,105,115,32,111,102,32,108,105,116,116,108,101,32,117,115,101,32,111,116,104,101,114,119,105,115,101,46,0,98,117,102,115,105,122,101,0,115,101,116,32,114,97,116,101,99,111,110,116,114,111,108,32,98,117,102,102,101,114,32,115,105,122,101,32,40,105,110,32,98,105,116,115,41,0,114,99,95,98,117,102,95,97,103,103,114,101,115,115,105,118,105,116,121,0,105,95,113,102,97,99,116,111,114,0,81,80,32,102,97,99,116,111,114,32,98,101,116,119,101,101,110,32,80,45,32,97,110,100,32,73,45,102,114,97,109,101,115,0,105,95,113,111,102,102,115,101,116,0,81,80,32,111,102,102,115,101,116,32,98,101,116,119,101,101,110,32,80,45,32,97,110,100,32,73,45,102,114,97,109,101,115,0,114,99,95,105,110,105,116,95,99,112,108,120,0,100,99,116,0,68,67,84,32,97,108,103,111,114,105,116,104,109,0,97,117,116,111,0,97,117,116,111,115,101,108,101,99,116,32,97,32,103,111,111,100,32,111,110,101,32,40,100,101,102,97,117,108,116,41,0,102,97,115,116,105,110,116,0,102,97,115,116,32,105,110,116,101,103,101,114,0,105,110,116,0,97,99,99,117,114,97,116,101,32,105,110,116,101,103,101,114,0,109,109,120,0,97,108,116,105,118,101,99,0,102,97,97,110,0,102,108,111,97,116,105,110,103,32,112,111,105,110,116,32,65,65,78,32,68,67,84,0,108,117,109,105,95,109,97,115,107,0,99,111,109,112,114,101,115,115,101,115,32,98,114,105,103,104,116,32,97,114,101,97,115,32,115,116,114,111,110,103,101,114,32,116,104,97,110,32,109,101,100,105,117,109,32,111,110,101,115,0,116,99,112,108,120,95,109,97,115,107,0,116,101,109,112,111,114,97,108,32,99,111,109,112,108,101,120,105,116,121,32,109,97,115,107,105,110,103,0,115,99,112,108,120,95,109,97,115,107,0,115,112,97,116,105,97,108,32,99,111,109,112,108,101,120,105,116,121,32,109,97,115,107,105,110,103,0,112,95,109,97,115,107,0,105,110,116,101,114,32,109,97,115,107,105,110,103,0,100,97,114,107,95,109,97,115,107,0,99,111,109,112,114,101,115,115,101,115,32,100,97,114,107,32,97,114,101,97,115,32,115,116,114,111,110,103,101,114,32,116,104,97,110,32,109,101,100,105,117,109,32,111,110,101,115,0,105,100,99,116,0,115,101,108,101,99,116,32,73,68,67,84,32,105,109,112,108,101,109,101,110,116,97,116,105,111,110,0,115,105,109,112,108,101,0,115,105,109,112,108,101,109,109,120,0,97,114,109,0,115,104,52,0,115,105,109,112,108,101,97,114,109,0,115,105,109,112,108,101,97,114,109,118,53,116,101,0,115,105,109,112,108,101,97,114,109,118,54,0,115,105,109,112,108,101,110,101,111,110,0,115,105,109,112,108,101,97,108,112,104,97,0,105,112,112,0,120,118,105,100,0,120,118,105,100,109,109,120,0,100,101,112,114,101,99,97,116,101,100,44,32,102,111,114,32,99,111,109,112,97,116,105,98,105,108,105,116,121,32,111,110,108,121,0,102,97,97,110,105,0,102,108,111,97,116,105,110,103,32,112,111,105,110,116,32,65,65,78,32,73,68,67,84,0,115,105,109,112,108,101,97,117,116,111,0,115,108,105,99,101,95,99,111,117,110,116,0,101,99,0,115,101,116,32,101,114,114,111,114,32,99,111,110,99,101,97,108,109,101,110,116,32,115,116,114,97,116,101,103,121,0,103,117,101,115,115,95,109,118,115,0,105,116,101,114,97,116,105,118,101,32,109,111,116,105,111,110,32,118,101,99,116,111,114,32,40,77,86,41,32,115,101,97,114,99,104,32,40,115,108,111,119,41,0,100,101,98,108,111,99,107,0,117,115,101,32,115,116,114,111,110,103,32,100,101,98,108,111,99,107,32,102,105,108,116,101,114,32,102,111,114,32,100,97,109,97,103,101,100,32,77,66,115,0,102,97,118,111,114,95,105,110,116,101,114,0,102,97,118,111,114,32,112,114,101,100,105,99,116,105,110,103,32,102,114,111,109,32,116,104,101,32,112,114,101,118,105,111,117,115,32,102,114,97,109,101,0,98,105,116,115,95,112,101,114,95,99,111,100,101,100,95,115,97,109,112,108,101,0,112,114,101,100,0,112,114,101,100,105,99,116,105,111,110,32,109,101,116,104,111,100,0,108,101,102,116,0,112,108,97,110,101,0,109,101,100,105,97,110,0,97,115,112,101,99,116,0,115,97,109,112,108,101,32,97,115,112,101,99,116,32,114,97,116,105,111,0,112,114,105,110,116,32,115,112,101,99,105,102,105,99,32,100,101,98,117,103,32,105,110,102,111,0,112,105,99,116,0,112,105,99,116,117,114,101,32,105,110,102,111,0,114,99,0,114,97,116,101,32,99,111,110,116,114,111,108,0,109,98,95,116,121,112,101,0,109,97,99,114,111,98,108,111,99,107,32,40,77,66,41,32,116,121,112,101,0,113,112,0,112,101,114,45,98,108,111,99,107,32,113,117,97,110,116,105,122,97,116,105,111,110,32,112,97,114,97,109,101,116,101,114,32,40,81,80,41,0,109,118,0,109,111,116,105,111,110,32,118,101,99,116,111,114,0,100,99,116,95,99,111,101,102,102,0,103,114,101,101,110,95,109,101,116,97,100,97,116,97,0,115,107,105,112,0,115,116,97,114,116,99,111,100,101,0,112,116,115,0,101,114,0,101,114,114,111,114,32,114,101,99,111,103,110,105,116,105,111,110,0,109,109,99,111,0,109,101,109,111,114,121,32,109,97,110,97,103,101,109,101,110,116,32,99,111,110,116,114,111,108,32,111,112,101,114,97,116,105,111,110,115,32,40,72,46,50,54,52,41,0,98,117,103,115,0,118,105,115,95,113,112,0,118,105,115,117,97,108,105,122,101,32,113,117,97,110,116,105,122,97,116,105,111,110,32,112,97,114,97,109,101,116,101,114,32,40,81,80,41,44,32,108,111,119,101,114,32,81,80,32,97,114,101,32,116,105,110,116,101,100,32,103,114,101,101,110,101,114,0,118,105,115,95,109,98,95,116,121,112,101,0,118,105,115,117,97,108,105,122,101,32,98,108,111,99,107,32,116,121,112,101,115,0,98,117,102,102,101,114,115,0,112,105,99,116,117,114,101,32,98,117,102,102,101,114,32,97,108,108,111,99,97,116,105,111,110,115,0,116,104,114,101,97,100,95,111,112,115,0,116,104,114,101,97,100,105,110,103,32,111,112,101,114,97,116,105,111,110,115,0,110,111,109,99,0,115,107,105,112,32,109,111,116,105,111,110,32,99,111,109,112,101,110,115,97,116,105,111,110,0,118,105,115,109,118,0,118,105,115,117,97,108,105,122,101,32,109,111,116,105,111,110,32,118,101,99,116,111,114,115,32,40,77,86,115,41,32,40,100,101,112,114,101,99,97,116,101,100,41,0,100,101,98,117,103,95,109,118,0,112,102,0,102,111,114,119,97,114,100,32,112,114,101,100,105,99,116,101,100,32,77,86,115,32,111,102,32,80,45,102,114,97,109,101,115,0,102,111,114,119,97,114,100,32,112,114,101,100,105,99,116,101,100,32,77,86,115,32,111,102,32,66,45,102,114,97,109,101,115,0,98,98,0,98,97,99,107,119,97,114,100,32,112,114,101,100,105,99,116,101,100,32,77,86,115,32,111,102,32,66,45,102,114,97,109,101,115,0,99,109,112,0,102,117,108,108,45,112,101,108,32,77,69,32,99,111,109,112,97,114,101,32,102,117,110,99,116,105,111,110,0,99,109,112,95,102,117,110,99,0,115,117,98,99,109,112,0,115,117,98,45,112,101,108,32,77,69,32,99,111,109,112,97,114,101,32,102,117,110,99,116,105,111,110,0,109,98,99,109,112,0,109,97,99,114,111,98,108,111,99,107,32,99,111,109,112,97,114,101,32,102,117,110,99,116,105,111,110,0,105,108,100,99,116,99,109,112,0,105,110,116,101,114,108,97,99,101,100,32,68,67,84,32,99,111,109,112,97,114,101,32,102,117,110,99,116,105,111,110,0,100,105,97,95,115,105,122,101,0,100,105,97,109,111,110,100,32,116,121,112,101,32,38,32,115,105,122,101,32,102,111,114,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,108,97,115,116,95,112,114,101,100,0,97,109,111,117,110,116,32,111,102,32,109,111,116,105,111,110,32,112,114,101,100,105,99,116,111,114,115,32,102,114,111,109,32,116,104,101,32,112,114,101,118,105,111,117,115,32,102,114,97,109,101,0,112,114,101,109,101,0,112,114,101,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,0,112,114,101,99,109,112,0,112,114,101,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,99,111,109,112,97,114,101,32,102,117,110,99,116,105,111,110,0,115,97,100,0,115,117,109,32,111,102,32,97,98,115,111,108,117,116,101,32,100,105,102,102,101,114,101,110,99,101,115,44,32,102,97,115,116,32,40,100,101,102,97,117,108,116,41,0,115,115,101,0,115,117,109,32,111,102,32,115,113,117,97,114,101,100,32,101,114,114,111,114,115,0,115,97,116,100,0,115,117,109,32,111,102,32,97,98,115,111,108,117,116,101,32,72,97,100,97,109,97,114,100,32,116,114,97,110,115,102,111,114,109,101,100,32,100,105,102,102,101,114,101,110,99,101,115,0,115,117,109,32,111,102,32,97,98,115,111,108,117,116,101,32,68,67,84,32,116,114,97,110,115,102,111,114,109,101,100,32,100,105,102,102,101,114,101,110,99,101,115,0,115,117,109,32,111,102,32,115,113,117,97,114,101,100,32,113,117,97,110,116,105,122,97,116,105,111,110,32,101,114,114,111,114,115,32,40,97,118,111,105,100,44,32,108,111,119,32,113,117,97,108,105,116,121,41,0,98,105,116,0,110,117,109,98,101,114,32,111,102,32,98,105,116,115,32,110,101,101,100,101,100,32,102,111,114,32,116,104,101,32,98,108,111,99,107,0,114,100,0,114,97,116,101,32,100,105,115,116,111,114,116,105,111,110,32,111,112,116,105,109,97,108,44,32,115,108,111,119,0,118,115,97,100,0,115,117,109,32,111,102,32,97,98,115,111,108,117,116,101,32,118,101,114,116,105,99,97,108,32,100,105,102,102,101,114,101,110,99,101,115,0,118,115,115,101,0,115,117,109,32,111,102,32,115,113,117,97,114,101,100,32,118,101,114,116,105,99,97,108,32,100,105,102,102,101,114,101,110,99,101,115,0,110,115,115,101,0,110,111,105,115,101,32,112,114,101,115,101,114,118,105,110,103,32,115,117,109,32,111,102,32,115,113,117,97,114,101,100,32,100,105,102,102,101,114,101,110,99,101,115,0,100,99,116,109,97,120,0,99,104,114,111,109,97,0,112,114,101,95,100,105,97,95,115,105,122,101,0,100,105,97,109,111,110,100,32,116,121,112,101,32,38,32,115,105,122,101,32,102,111,114,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,112,114,101,45,112,97,115,115,0,115,117,98,113,0,115,117,98,45,112,101,108,32,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,113,117,97,108,105,116,121,0,100,116,103,95,97,99,116,105,118,101,95,102,111,114,109,97,116,0,109,101,95,114,97,110,103,101,0,108,105,109,105,116,32,109,111,116,105,111,110,32,118,101,99,116,111,114,115,32,114,97,110,103,101,32,40,49,48,50,51,32,102,111,114,32,68,105,118,88,32,112,108,97,121,101,114,41,0,105,98,105,97,115,0,105,110,116,114,97,32,113,117,97,110,116,32,98,105,97,115,0,112,98,105,97,115,0,105,110,116,101,114,32,113,117,97,110,116,32,98,105,97,115,0,103,108,111,98,97,108,95,113,117,97,108,105,116,121,0,99,111,100,101,114,0,118,108,99,0,118,97,114,105,97,98,108,101,32,108,101,110,103,116,104,32,99,111,100,101,114,32,47,32,72,117,102,102,109,97,110,32,99,111,100,101,114,0,97,114,105,116,104,109,101,116,105,99,32,99,111,100,101,114,0,114,97,119,0,114,97,119,32,40,110,111,32,101,110,99,111,100,105,110,103,41,0,114,108,101,0,114,117,110,45,108,101,110,103,116,104,32,99,111,100,101,114,0,100,101,102,108,97,116,101,0,100,101,102,108,97,116,101,45,98,97,115,101,100,32,99,111,100,101,114,0,99,111,110,116,101,120,116,0,99,111,110,116,101,120,116,32,109,111,100,101,108,0,115,108,105,99,101,95,102,108,97,103,115,0,120,118,109,99,95,97,99,99,101,108,101,114,97,116,105,111,110,0,109,98,100,0,109,97,99,114,111,98,108,111,99,107,32,100,101,99,105,115,105,111,110,32,97,108,103,111,114,105,116,104,109,32,40,104,105,103,104,32,113,117,97,108,105,116,121,32,109,111,100,101,41,0,117,115,101,32,109,98,99,109,112,32,40,100,101,102,97,117,108,116,41,0,98,105,116,115,0,117,115,101,32,102,101,119,101,115,116,32,98,105,116,115,0,117,115,101,32,98,101,115,116,32,114,97,116,101,32,100,105,115,116,111,114,116,105,111,110,0,115,116,114,101,97,109,95,99,111,100,101,99,95,116,97,103,0,115,99,95,116,104,114,101,115,104,111,108,100,0,115,99,101,110,101,32,99,104,97,110,103,101,32,116,104,114,101,115,104,111,108,100,0,108,109,105,110,0,108,109,97,120,0,110,114,0,110,111,105,115,101,32,114,101,100,117,99,116,105,111,110,0,114,99,95,105,110,105,116,95,111,99,99,117,112,97,110,99,121,0,110,117,109,98,101,114,32,111,102,32,98,105,116,115,32,119,104,105,99,104,32,115,104,111,117,108,100,32,98,101,32,108,111,97,100,101,100,32,105,110,116,111,32,116,104,101,32,114,99,32,98,117,102,102,101,114,32,98,101,102,111,114,101,32,100,101,99,111,100,105,110,103,32,115,116,97,114,116,115,0,116,104,114,101,97,100,115,0,97,117,116,111,100,101,116,101,99,116,32,97,32,115,117,105,116,97,98,108,101,32,110,117,109,98,101,114,32,111,102,32,116,104,114,101,97,100,115,32,116,111,32,117,115,101,0,109,101,95,116,104,114,101,115,104,111,108,100,0,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,116,104,114,101,115,104,111,108,100,0,109,98,95,116,104,114,101,115,104,111,108,100,0,109,97,99,114,111,98,108,111,99,107,32,116,104,114,101,115,104,111,108,100,0,100,99,0,105,110,116,114,97,95,100,99,95,112,114,101,99,105,115,105,111,110,0,110,115,115,101,119,0,110,115,115,101,32,119,101,105,103,104,116,0,115,107,105,112,95,116,111,112,0,110,117,109,98,101,114,32,111,102,32,109,97,99,114,111,98,108,111,99,107,32,114,111,119,115,32,97,116,32,116,104,101,32,116,111,112,32,119,104,105,99,104,32,97,114,101,32,115,107,105,112,112,101,100,0,115,107,105,112,95,98,111,116,116,111,109,0,110,117,109,98,101,114,32,111,102,32,109,97,99,114,111,98,108,111,99,107,32,114,111,119,115,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,119,104,105,99,104,32,97,114,101,32,115,107,105,112,112,101,100,0,112,114,111,102,105,108,101,0,117,110,107,110,111,119,110,0,97,97,99,95,109,97,105,110,0,97,97,99,95,108,111,119,0,97,97,99,95,115,115,114,0,97,97,99,95,108,116,112,0,97,97,99,95,104,101,0,97,97,99,95,104,101,95,118,50,0,97,97,99,95,108,100,0,97,97,99,95,101,108,100,0,109,112,101,103,50,95,97,97,99,95,108,111,119,0,109,112,101,103,50,95,97,97,99,95,104,101,0,100,116,115,0,100,116,115,95,101,115,0,100,116,115,95,57,54,95,50,52,0,100,116,115,95,104,100,95,104,114,97,0,100,116,115,95,104,100,95,109,97,0,109,112,101,103,52,95,115,112,0,109,112,101,103,52,95,99,111,114,101,0,109,112,101,103,52,95,109,97,105,110,0,109,112,101,103,52,95,97,115,112,0,108,101,118,101,108,0,108,111,119,114,101,115,0,100,101,99,111,100,101,32,97,116,32,49,61,32,49,47,50,44,32,50,61,49,47,52,44,32,51,61,49,47,56,32,114,101,115,111,108,117,116,105,111,110,115,0,115,107,105,112,95,116,104,114,101,115,104,111,108,100,0,102,114,97,109,101,32,115,107,105,112,32,116,104,114,101,115,104,111,108,100,0,115,107,105,112,95,102,97,99,116,111,114,0,102,114,97,109,101,32,115,107,105,112,32,102,97,99,116,111,114,0,115,107,105,112,95,101,120,112,0,102,114,97,109,101,32,115,107,105,112,32,101,120,112,111,110,101,110,116,0,115,107,105,112,99,109,112,0,102,114,97,109,101,32,115,107,105,112,32,99,111,109,112,97,114,101,32,102,117,110,99,116,105,111,110,0,98,111,114,100,101,114,95,109,97,115,107,0,109,98,108,109,105,110,0,109,105,110,105,109,117,109,32,109,97,99,114,111,98,108,111,99,107,32,76,97,103,114,97,110,103,101,32,102,97,99,116,111,114,32,40,86,66,82,41,0,109,98,108,109,97,120,0,109,97,120,105,109,117,109,32,109,97,99,114,111,98,108,111,99,107,32,76,97,103,114,97,110,103,101,32,102,97,99,116,111,114,32,40,86,66,82,41,0,109,101,112,99,0,109,111,116,105,111,110,32,101,115,116,105,109,97,116,105,111,110,32,98,105,116,114,97,116,101,32,112,101,110,97,108,116,121,32,99,111,109,112,101,110,115,97,116,105,111,110,32,40,49,46,48,32,61,32,50,53,54,41,0,115,107,105,112,95,108,111,111,112,95,102,105,108,116,101,114,0,115,107,105,112,32,108,111,111,112,32,102,105,108,116,101,114,105,110,103,32,112,114,111,99,101,115,115,32,102,111,114,32,116,104,101,32,115,101,108,101,99,116,101,100,32,102,114,97,109,101,115,0,97,118,100,105,115,99,97,114,100,0,115,107,105,112,95,105,100,99,116,0,115,107,105,112,32,73,68,67,84,47,100,101,113,117,97,110,116,105,122,97,116,105,111,110,32,102,111,114,32,116,104,101,32,115,101,108,101,99,116,101,100,32,102,114,97,109,101,115,0,115,107,105,112,95,102,114,97,109,101,0,115,107,105,112,32,100,101,99,111,100,105,110,103,32,102,111,114,32,116,104,101,32,115,101,108,101,99,116,101,100,32],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+70225);allocate([102,114,97,109,101,115,0,100,105,115,99,97,114,100,32,110,111,32,102,114,97,109,101,0,100,105,115,99,97,114,100,32,117,115,101,108,101,115,115,32,102,114,97,109,101,115,0,110,111,114,101,102,0,100,105,115,99,97,114,100,32,97,108,108,32,110,111,110,45,114,101,102,101,114,101,110,99,101,32,102,114,97,109,101,115,0,98,105,100,105,114,0,100,105,115,99,97,114,100,32,97,108,108,32,98,105,100,105,114,101,99,116,105,111,110,97,108,32,102,114,97,109,101,115,0,110,111,107,101,121,0,100,105,115,99,97,114,100,32,97,108,108,32,102,114,97,109,101,115,32,101,120,99,101,112,116,32,107,101,121,102,114,97,109,101,115,0,110,111,105,110,116,114,97,0,100,105,115,99,97,114,100,32,97,108,108,32,102,114,97,109,101,115,32,101,120,99,101,112,116,32,73,32,102,114,97,109,101,115,0,100,105,115,99,97,114,100,32,97,108,108,32,102,114,97,109,101,115,0,98,105,100,105,114,95,114,101,102,105,110,101,0,114,101,102,105,110,101,32,116,104,101,32,116,119,111,32,109,111,116,105,111,110,32,118,101,99,116,111,114,115,32,117,115,101,100,32,105,110,32,98,105,100,105,114,101,99,116,105,111,110,97,108,32,109,97,99,114,111,98,108,111,99,107,115,0,98,114,100,95,115,99,97,108,101,0,100,111,119,110,115,99,97,108,101,32,102,114,97,109,101,115,32,102,111,114,32,100,121,110,97,109,105,99,32,66,45,102,114,97,109,101,32,100,101,99,105,115,105,111,110,0,107,101,121,105,110,116,95,109,105,110,0,109,105,110,105,109,117,109,32,105,110,116,101,114,118,97,108,32,98,101,116,119,101,101,110,32,73,68,82,45,102,114,97,109,101,115,0,114,101,102,115,0,114,101,102,101,114,101,110,99,101,32,102,114,97,109,101,115,32,116,111,32,99,111,110,115,105,100,101,114,32,102,111,114,32,109,111,116,105,111,110,32,99,111,109,112,101,110,115,97,116,105,111,110,0,99,104,114,111,109,97,111,102,102,115,101,116,0,99,104,114,111,109,97,32,81,80,32,111,102,102,115,101,116,32,102,114,111,109,32,108,117,109,97,0,116,114,101,108,108,105,115,0,114,97,116,101,45,100,105,115,116,111,114,116,105,111,110,32,111,112,116,105,109,97,108,32,113,117,97,110,116,105,122,97,116,105,111,110,0,115,99,95,102,97,99,116,111,114,0,109,117,108,116,105,112,108,105,101,100,32,98,121,32,113,115,99,97,108,101,32,102,111,114,32,101,97,99,104,32,102,114,97,109,101,32,97,110,100,32,97,100,100,101,100,32,116,111,32,115,99,101,110,101,95,99,104,97,110,103,101,95,115,99,111,114,101,0,109,118,48,95,116,104,114,101,115,104,111,108,100,0,98,95,115,101,110,115,105,116,105,118,105,116,121,0,97,100,106,117,115,116,32,115,101,110,115,105,116,105,118,105,116,121,32,111,102,32,98,95,102,114,97,109,101,95,115,116,114,97,116,101,103,121,32,49,0,99,111,109,112,114,101,115,115,105,111,110,95,108,101,118,101,108,0,109,105,110,95,112,114,101,100,105,99,116,105,111,110,95,111,114,100,101,114,0,109,97,120,95,112,114,101,100,105,99,116,105,111,110,95,111,114,100,101,114,0,116,105,109,101,99,111,100,101,95,102,114,97,109,101,95,115,116,97,114,116,0,71,79,80,32,116,105,109,101,99,111,100,101,32,102,114,97,109,101,32,115,116,97,114,116,32,110,117,109,98,101,114,44,32,105,110,32,110,111,110,45,100,114,111,112,45,102,114,97,109,101,32,102,111,114,109,97,116,0,114,101,113,117,101,115,116,95,99,104,97,110,110,101,108,115,0,115,101,116,32,100,101,115,105,114,101,100,32,110,117,109,98,101,114,32,111,102,32,97,117,100,105,111,32,99,104,97,110,110,101,108,115,0,98,105,116,115,95,112,101,114,95,114,97,119,95,115,97,109,112,108,101,0,99,104,97,110,110,101,108,95,108,97,121,111,117,116,0,114,101,113,117,101,115,116,95,99,104,97,110,110,101,108,95,108,97,121,111,117,116,0,114,99,95,109,97,120,95,118,98,118,95,117,115,101,0,114,99,95,109,105,110,95,118,98,118,95,117,115,101,0,116,105,99,107,115,95,112,101,114,95,102,114,97,109,101,0,99,111,108,111,114,95,112,114,105,109,97,114,105,101,115,0,99,111,108,111,114,32,112,114,105,109,97,114,105,101,115,0,99,111,108,111,114,95,112,114,105,109,97,114,105,101,115,95,116,121,112,101,0,98,116,55,48,57,0,66,84,46,55,48,57,0,117,110,115,112,101,99,105,102,105,101,100,0,85,110,115,112,101,99,105,102,105,101,100,0,98,116,52,55,48,109,0,66,84,46,52,55,48,32,77,0,98,116,52,55,48,98,103,0,66,84,46,52,55,48,32,66,71,0,115,109,112,116,101,49,55,48,109,0,83,77,80,84,69,32,49,55,48,32,77,0,115,109,112,116,101,50,52,48,109,0,83,77,80,84,69,32,50,52,48,32,77,0,70,105,108,109,0,98,116,50,48,50,48,0,66,84,46,50,48,50,48,0,99,111,108,111,114,95,116,114,99,0,99,111,108,111,114,32,116,114,97,110,115,102,101,114,32,99,104,97,114,97,99,116,101,114,105,115,116,105,99,115,0,99,111,108,111,114,95,116,114,99,95,116,121,112,101,0,103,97,109,109,97,50,50,0,103,97,109,109,97,50,56,0,108,105,110,101,97,114,0,76,105,110,101,97,114,0,76,111,103,0,108,111,103,95,115,113,114,116,0,76,111,103,32,115,113,117,97,114,101,32,114,111,111,116,0,105,101,99,54,49,57,54,54,95,50,95,52,0,73,69,67,32,54,49,57,54,54,45,50,45,52,0,98,116,49,51,54,49,0,66,84,46,49,51,54,49,0,105,101,99,54,49,57,54,54,95,50,95,49,0,73,69,67,32,54,49,57,54,54,45,50,45,49,0,98,116,50,48,50,48,95,49,48,98,105,116,0,66,84,46,50,48,50,48,32,45,32,49,48,32,98,105,116,0,98,116,50,48,50,48,95,49,50,98,105,116,0,66,84,46,50,48,50,48,32,45,32,49,50,32,98,105,116,0,99,111,108,111,114,115,112,97,99,101,0,99,111,108,111,114,32,115,112,97,99,101,0,99,111,108,111,114,115,112,97,99,101,95,116,121,112,101,0,114,103,98,0,82,71,66,0,102,99,99,0,70,67,67,0,121,99,111,99,103,0,89,67,79,67,71,0,98,116,50,48,50,48,95,110,99,108,0,66,84,46,50,48,50,48,32,78,67,76,0,98,116,50,48,50,48,95,99,108,0,66,84,46,50,48,50,48,32,67,76,0,99,111,108,111,114,95,114,97,110,103,101,0,99,111,108,111,114,32,114,97,110,103,101,0,99,111,108,111,114,95,114,97,110,103,101,95,116,121,112,101,0,109,112,101,103,0,77,80,69,71,32,40,50,49,57,42,50,94,40,110,45,56,41,41,0,106,112,101,103,0,74,80,69,71,32,40,50,94,110,45,49,41,0,99,104,114,111,109,97,95,115,97,109,112,108,101,95,108,111,99,97,116,105,111,110,0,99,104,114,111,109,97,32,115,97,109,112,108,101,32,108,111,99,97,116,105,111,110,0,99,104,114,111,109,97,95,115,97,109,112,108,101,95,108,111,99,97,116,105,111,110,95,116,121,112,101,0,76,101,102,116,0,99,101,110,116,101,114,0,67,101,110,116,101,114,0,116,111,112,108,101,102,116,0,84,111,112,45,108,101,102,116,0,116,111,112,0,84,111,112,0,98,111,116,116,111,109,108,101,102,116,0,66,111,116,116,111,109,45,108,101,102,116,0,98,111,116,116,111,109,0,66,111,116,116,111,109,0,108,111,103,95,108,101,118,101,108,95,111,102,102,115,101,116,0,115,101,116,32,116,104,101,32,108,111,103,32,108,101,118,101,108,32,111,102,102,115,101,116,0,115,108,105,99,101,115,0,110,117,109,98,101,114,32,111,102,32,115,108,105,99,101,115,44,32,117,115,101,100,32,105,110,32,112,97,114,97,108,108,101,108,105,122,101,100,32,101,110,99,111,100,105,110,103,0,116,104,114,101,97,100,95,116,121,112,101,0,115,101,108,101,99,116,32,109,117,108,116,105,116,104,114,101,97,100,105,110,103,32,116,121,112,101,0,115,108,105,99,101,0,102,114,97,109,101,0,97,117,100,105,111,95,115,101,114,118,105,99,101,95,116,121,112,101,0,97,117,100,105,111,32,115,101,114,118,105,99,101,32,116,121,112,101,0,109,97,0,77,97,105,110,32,65,117,100,105,111,32,83,101,114,118,105,99,101,0,101,102,0,69,102,102,101,99,116,115,0,118,105,0,86,105,115,117,97,108,108,121,32,73,109,112,97,105,114,101,100,0,104,105,0,72,101,97,114,105,110,103,32,73,109,112,97,105,114,101,100,0,100,105,0,68,105,97,108,111,103,117,101,0,99,111,0,67,111,109,109,101,110,116,97,114,121,0,101,109,0,69,109,101,114,103,101,110,99,121,0,118,111,0,86,111,105,99,101,32,79,118,101,114,0,107,97,0,75,97,114,97,111,107,101,0,114,101,113,117,101,115,116,95,115,97,109,112,108,101,95,102,109,116,0,115,97,109,112,108,101,32,102,111,114,109,97,116,32,97,117,100,105,111,32,100,101,99,111,100,101,114,115,32,115,104,111,117,108,100,32,112,114,101,102,101,114,0,112,107,116,95,116,105,109,101,98,97,115,101,0,115,117,98,95,99,104,97,114,101,110,99,0,115,101,116,32,105,110,112,117,116,32,116,101,120,116,32,115,117,98,116,105,116,108,101,115,32,99,104,97,114,97,99,116,101,114,32,101,110,99,111,100,105,110,103,0,115,117,98,95,99,104,97,114,101,110,99,95,109,111,100,101,0,115,101,116,32,105,110,112,117,116,32,116,101,120,116,32,115,117,98,116,105,116,108,101,115,32,99,104,97,114,97,99,116,101,114,32,101,110,99,111,100,105,110,103,32,109,111,100,101,0,100,111,95,110,111,116,104,105,110,103,0,112,114,101,95,100,101,99,111,100,101,114,0,114,101,102,99,111,117,110,116,101,100,95,102,114,97,109,101,115,0,115,105,100,101,95,100,97,116,97,95,111,110,108,121,95,112,97,99,107,101,116,115,0,115,107,105,112,95,97,108,112,104,97,0,83,107,105,112,32,112,114,111,99,101,115,115,105,110,103,32,97,108,112,104,97,0,102,105,101,108,100,95,111,114,100,101,114,0,70,105,101,108,100,32,111,114,100,101,114,0,112,114,111,103,114,101,115,115,105,118,101,0,116,116,0,116,98,0,100,117,109,112,95,115,101,112,97,114,97,116,111,114,0,115,101,116,32,105,110,102,111,114,109,97,116,105,111,110,32,100,117,109,112,32,102,105,101,108,100,32,115,101,112,97,114,97,116,111,114,0,99,111,100,101,99,95,119,104,105,116,101,108,105,115,116,0,76,105,115,116,32,111,102,32,100,101,99,111,100,101,114,115,32,116,104,97,116,32,97,114,101,32,97,108,108,111,119,101,100,32,116,111,32,98,101,32,117,115,101,100,0,112,105,120,101,108,95,102,111,114,109,97,116,0,115,101,116,32,112,105,120,101,108,32,102,111,114,109,97,116,0,118,105,100,101,111,95,115,105,122,101,0,115,101,116,32,118,105,100,101,111,32,115,105,122,101,0,108,105,98,97,118,99,111,100,101,99,47,111,112,116,105,111,110,115,46,99,0,46,47,108,105,98,97,118,117,116,105,108,47,109,101,109,95,105,110,116,101,114,110,97,108,46,104,0,105,103,110,111,114,105,110,103,32,105,110,118,97,108,105,100,32,83,65,82,58,32,37,100,47,37,100,10,0,100,101,115,99,45,62,102,108,97,103,115,32,38,32,40,49,32,60,60,32,52,41,0,108,105,98,97,118,99,111,100,101,99,47,117,116,105,108,115,46,99,0,48,0,112,105,99,45,62,100,97,116,97,91,48,93,33,61,78,85,76,76,32,105,110,32,97,118,99,111,100,101,99,95,100,101,102,97,117,108,116,95,103,101,116,95,98,117,102,102,101,114,10,0,100,101,102,97,117,108,116,95,103,101,116,95,98,117,102,102,101,114,32,99,97,108,108,101,100,32,111,110,32,112,105,99,32,37,112,10,0,102,114,97,109,101,45,62,110,98,95,101,120,116,101,110,100,101,100,95,98,117,102,32,61,61,32,48,0,100,101,102,97,117,108,116,95,103,101,116,95,98,117,102,102,101,114,32,99,97,108,108,101,100,32,111,110,32,102,114,97,109,101,32,37,112,0,105,103,110,111,114,105,110,103,32,105,110,118,97,108,105,100,32,83,65,82,58,32,37,117,47,37,117,10,0,73,110,99,111,110,115,105,115,116,101,110,116,32,99,104,97,110,110,101,108,32,99,111,110,102,105,103,117,114,97,116,105,111,110,46,10,0,84,111,111,32,109,97,110,121,32,99,104,97,110,110,101,108,115,58,32,37,100,46,10,0,118,105,100,101,111,95,103,101,116,95,98,117,102,102,101,114,58,32,105,109,97,103,101,32,112,97,114,97,109,101,116,101,114,115,32,105,110,118,97,108,105,100,10,0,103,101,116,95,98,117,102,102,101,114,40,41,32,102,97,105,108,101,100,10,0,110,32,62,61,32,49,0,67,111,117,108,100,32,110,111,116,32,102,105,110,100,32,97,110,32,65,86,72,87,65,99,99,101,108,32,102,111,114,32,116,104,101,32,112,105,120,101,108,32,102,111,114,109,97,116,58,32,37,115,0,73,103,110,111,114,105,110,103,32,101,120,112,101,114,105,109,101,110,116,97,108,32,104,119,97,99,99,101,108,58,32,37,115,10,0,99,104,111,105,99,101,115,91,110,93,32,33,61,32,65,86,95,80,73,88,95,70,77,84,95,78,79,78,69,0,102,102,95,97,118,99,111,100,101,99,95,108,111,99,107,101,100,0,78,111,32,99,111,100,101,99,32,112,114,111,118,105,100,101,100,32,116,111,32,97,118,99,111,100,101,99,95,111,112,101,110,50,40,41,10,0,84,104,105,115,32,65,86,67,111,100,101,99,67,111,110,116,101,120,116,32,119,97,115,32,97,108,108,111,99,97,116,101,100,32,102,111,114,32,37,115,44,32,98,117,116,32,37,115,32,112,97,115,115,101,100,32,116,111,32,97,118,99,111,100,101,99,95,111,112,101,110,50,40,41,10,0,73,110,115,117,102,102,105,99,105,101,110,116,32,116,104,114,101,97,100,32,108,111,99,107,105,110,103,46,32,65,116,32,108,101,97,115,116,32,37,100,32,116,104,114,101,97,100,115,32,97,114,101,32,99,97,108,108,105,110,103,32,97,118,99,111,100,101,99,95,111,112,101,110,50,40,41,32,97,116,32,116,104,101,32,115,97,109,101,32,116,105,109,101,32,114,105,103,104,116,32,110,111,119,46,10,0,78,111,32,108,111,99,107,32,109,97,110,97,103,101,114,32,105,115,32,115,101,116,44,32,112,108,101,97,115,101,32,115,101,101,32,97,118,95,108,111,99,107,109,103,114,95,114,101,103,105,115,116,101,114,40,41,10,0,33,102,102,95,97,118,99,111,100,101,99,95,108,111,99,107,101,100,0,67,111,100,101,99,32,40,37,115,41,32,110,111,116,32,111,110,32,119,104,105,116,101,108,105,115,116,10,0,73,103,110,111,114,105,110,103,32,105,110,118,97,108,105,100,32,119,105,100,116,104,47,104,101,105,103,104,116,32,118,97,108,117,101,115,10,0,67,111,100,101,99,32,116,121,112,101,32,111,114,32,105,100,32,109,105,115,109,97,116,99,104,101,115,10,0,101,110,99,111,100,101,114,0,100,101,99,111,100,101,114,0,84,104,101,32,37,115,32,39,37,115,39,32,105,115,32,101,120,112,101,114,105,109,101,110,116,97,108,32,98,117,116,32,101,120,112,101,114,105,109,101,110,116,97,108,32,99,111,100,101,99,115,32,97,114,101,32,110,111,116,32,101,110,97,98,108,101,100,44,32,97,100,100,32,39,45,115,116,114,105,99,116,32,37,100,39,32,105,102,32,121,111,117,32,119,97,110,116,32,116,111,32,117,115,101,32,105,116,46,10,0,65,108,116,101,114,110,97,116,105,118,101,108,121,32,117,115,101,32,116,104,101,32,110,111,110,32,101,120,112,101,114,105,109,101,110,116,97,108,32,37,115,32,39,37,115,39,46,10,0,87,97,114,110,105,110,103,58,32,110,111,116,32,99,111,109,112,105,108,101,100,32,119,105,116,104,32,116,104,114,101,97,100,32,115,117,112,112,111,114,116,44,32,117,115,105,110,103,32,116,104,114,101,97,100,32,101,109,117,108,97,116,105,111,110,10,0,84,104,101,32,109,97,120,105,109,117,109,32,118,97,108,117,101,32,102,111,114,32,108,111,119,114,101,115,32,115,117,112,112,111,114,116,101,100,32,98,121,32,116,104,101,32,100,101,99,111,100,101,114,32,105,115,32,37,100,10,0,84,104,101,32,39,118,105,115,109,118,39,32,111,112,116,105,111,110,32,105,115,32,100,101,112,114,101,99,97,116,101,100,44,32,115,101,101,32,116,104,101,32,99,111,100,101,99,118,105,101,119,32,102,105,108,116,101,114,32,105,110,115,116,101,97,100,46,10,0,37,100,0,83,112,101,99,105,102,105,101,100,32,115,97,109,112,108,101,32,102,111,114,109,97,116,32,37,115,32,105,115,32,105,110,118,97,108,105,100,32,111,114,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,83,112,101,99,105,102,105,101,100,32,112,105,120,101,108,32,102,111,114,109,97,116,32,37,115,32,105,115,32,105,110,118,97,108,105,100,32,111,114,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,83,112,101,99,105,102,105,101,100,32,115,97,109,112,108,101,32,114,97,116,101,32,37,100,32,105,115,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,67,104,97,110,110,101,108,32,108,97,121,111,117,116,32,110,111,116,32,115,112,101,99,105,102,105,101,100,10,0,83,112,101,99,105,102,105,101,100,32,99,104,97,110,110,101,108,32,108,97,121,111,117,116,32,39,37,115,39,32,105,115,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,67,104,97,110,110,101,108,32,108,97,121,111,117,116,32,39,37,115,39,32,119,105,116,104,32,37,100,32,99,104,97,110,110,101,108,115,32,100,111,101,115,32,110,111,116,32,109,97,116,99,104,32,110,117,109,98,101,114,32,111,102,32,115,112,101,99,105,102,105,101,100,32,99,104,97,110,110,101,108,115,32,37,100,10,0,83,112,101,99,105,102,105,101,100,32,110,117,109,98,101,114,32,111,102,32,99,104,97,110,110,101,108,115,32,37,100,32,105,115,32,110,111,116,32,115,117,112,112,111,114,116,101,100,10,0,100,105,109,101,110,115,105,111,110,115,32,110,111,116,32,115,101,116,10,0,66,105,116,114,97,116,101,32,37,100,32,105,115,32,101,120,116,114,101,109,101,108,121,32,108,111,119,44,32,109,97,121,98,101,32,121,111,117,32,109,101,97,110,32,37,100,107,10,0,103,114,97,121,32,100,101,99,111,100,105,110,103,32,114,101,113,117,101,115,116,101,100,32,98,117,116,32,110,111,116,32,101,110,97,98,108,101,100,32,97,116,32,99,111,110,102,105,103,117,114,97,116,105,111,110,32,116,105,109,101,10,0,67,104,97,110,110,101,108,32,108,97,121,111,117,116,32,39,37,115,39,32,119,105,116,104,32,37,100,32,99,104,97,110,110,101,108,115,32,100,111,101,115,32,110,111,116,32,109,97,116,99,104,32,115,112,101,99,105,102,105,101,100,32,110,117,109,98,101,114,32,111,102,32,99,104,97,110,110,101,108,115,32,37,100,58,32,105,103,110,111,114,105,110,103,32,115,112,101,99,105,102,105,101,100,32,99,104,97,110,110,101,108,32,108,97,121,111,117,116,10,0,67,104,97,114,97,99,116,101,114,32,101,110,99,111,100,105,110,103,32,105,115,32,111,110,108,121,32,115,117,112,112,111,114,116,101,100,32,119,105,116,104,32,115,117,98,116,105,116,108,101,115,32,99,111,100,101,99,115,10,0,67,111,100,101,99,32,39,37,115,39,32,105,115,32,98,105,116,109,97,112,45,98,97,115,101,100,44,32,115,117,98,116,105,116,108,101,115,32,99,104,97,114,97,99,116,101,114,32,101,110,99,111,100,105,110,103,32,119,105,108,108,32,98,101,32,105,103,110,111,114,101,100,10,0,67,104,97,114,97,99,116,101,114,32,101,110,99,111,100,105,110,103,32,115,117,98,116,105,116,108,101,115,32,99,111,110,118,101,114,115,105,111,110,32,110,101,101,100,115,32,97,32,108,105,98,97,118,99,111,100,101,99,32,98,117,105,108,116,32,119,105,116,104,32,105,99,111,110,118,32,115,117,112,112,111,114,116,32,102,111,114,32,116,104,105,115,32,99,111,100,101,99,10,0,42,40,99,111,110,115,116,32,65,86,67,108,97,115,115,32,42,42,41,97,118,99,116,120,45,62,112,114,105,118,95,100,97,116,97,32,61,61,32,99,111,100,101,99,45,62,112,114,105,118,95,99,108,97,115,115,0,73,110,118,97,108,105,100,32,109,101,100,105,97,32,116,121,112,101,32,102,111,114,32,118,105,100,101,111,10,0,84,104,105,115,32,100,101,99,111,100,101,114,32,100,111,101,115,32,110,111,116,32,115,117,112,112,111,114,116,32,112,97,114,97,109,101,116,101,114,32,99,104,97,110,103,101,115,44,32,98,117,116,32,80,65,82,65,77,95,67,72,65,78,71,69,32,115,105,100,101,32,100,97,116,97,32,119,97,115,32,115,101,110,116,32,116,111,32,105,116,46,10,0,73,110,118,97,108,105,100,32,99,104,97,110,110,101,108,32,99,111,117,110,116,0,73,110,118,97,108,105,100,32,115,97,109,112,108,101,32,114,97,116,101,0,80,65,82,65,77,95,67,72,65,78,71,69,32,115,105,100,101,32,100,97,116,97,32,116,111,111,32,115,109,97,108,108,46,10,0,69,114,114,111,114,32,97,112,112,108,121,105,110,103,32,112,97,114,97,109,101,116,101,114,32,99,104,97,110,103,101,115,46,10,0,33,112,105,99,116,117,114,101,45,62,101,120,116,101,110,100,101,100,95,100,97,116,97,32,124,124,32,112,105,99,116,117,114,101,45,62,101,120,116,101,110,100,101,100,95,100,97,116,97,32,61,61,32,112,105,99,116,117,114,101,45,62,100,97,116,97,0,33,100,115,116,45,62,112,114,111,103,114,101,115,115,0,112,32,60,61,32,101,110,100,0,108,105,98,97,118,99,111,100,101,99,47,98,105,116,115,116,114,101,97,109,46,99,0,110,98,95,99,111,100,101,115,32,43,32,49,32,60,61,32,40,115,105,122,101,111,102,40,108,111,99,97,108,98,117,102,41,32,47,32,115,105,122,101,111,102,40,40,108,111,99,97,108,98,117,102,41,91,48,93,41,41,0,115,121,109,98,111,108,115,95,115,105,122,101,32,60,61,32,50,32,124,124,32,33,115,121,109,98,111,108,115,0,84,111,111,32,108,111,110,103,32,86,76,67,32,40,37,100,41,32,105,110,32,105,110,105,116,95,118,108,99,10,0,73,110,118,97,108,105,100,32,99,111,100,101,32,105,110,32,105,110,105,116,95,118,108,99,10,0,105,110,99,111,114,114,101,99,116,32,99,111,100,101,115,10,0,110,101,101,100,101,100,32,37,100,32,104,97,100,32,37,100,10,0,114,101,116,32,62,61,32,48,0,128,176,208,240,128,167,197,227,128,158,187,216,123,150,178,205,116,142,169,195,111,135,160,185,105,128,152,175,100,122,144,166,95,116,137,158,90,110,130,150,85,104,123,142,81,99,117,135,77,94,111,128,73,89,105,122,69,85,100,116,66,80,95,110,62,76,90,104,59,72,86,99,56,69,81,94,53,65,77,89,51,62,73,85,48,59,69,80,46,56,66,76,43,53,63,72,41,50,59,69,39,48,56,65,37,45,54,62,35,43,51,59,33,41,48,56,32,39,46,53,30,37,43,50,29,35,41,48,27,33,39,45,26,31,37,43,24,30,35,41,23,28,33,39,22,27,32,37,21,26,30,35,20,24,29,33,19,23,27,31,18,22,26,30,17,21,25,28,16,20,23,27,15,19,22,25,14,18,21,24,14,17,20,23,13,16,19,22,12,15,18,21,12,14,17,20,11,14,16,19,11,13,15,18,10,12,15,17,10,12,14,16,9,11,13,15,9,11,12,14,8,10,12,14,8,9,11,13,7,9,11,12,7,9,10,12,7,8,10,11,6,8,9,11,6,7,9,10,6,7,8,9,2,2,2,2,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,62,63,0,0,1,2,2,4,4,5,6,7,8,9,9,11,11,12,13,13,15,15,16,16,18,18,19,19,21,21,22,22,23,24,24,25,26,26,27,27,28,29,29,30,30,30,31,32,32,33,33,33,34,34,35,35,35,36,36,36,37,37,37,38,38,63,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,105,110,116,101,114,110,97,108,32,101,114,114,111,114,44,32,115,108,105,99,101,32,101,110,100,32,98,101,102,111,114,101,32,115,116,97,114,116,10,0,105,103,110,111,114,105,110,103,32,108,97,115,116,32,109,105,115,115,105,110,103,32,115,108,105,99,101,10,0,67,97,110,110,111,116,32,117,115,101,32,112,114,101,118,105,111,117,115,32,112,105,99,116,117,114,101,32,105,110,32,101,114,114,111,114,32,99,111,110,99,101,97,108,109,101,110,116,10,0,67,97,110,110,111,116,32,117,115,101,32,110,101,120,116,32,112,105,99,116,117,114,101,32,105,110,32,101,114,114,111,114,32,99,111,110,99,101,97,108,109,101,110,116,10,0,87,97,114,110,105,110,103,32,77,86,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,10,0,37,50,88,32,0,10,0,99,111,110,99,101,97,108,105,110,103,32,37,100,32,68,67,44,32,37,100,32,65,67,44,32,37,100,32,77,86,32,101,114,114,111,114,115,32,105,110,32,37,99,32,102,114,97,109,101,10,0,115,45,62,97,118,99,116,120,45,62,99,111,100,101,99,95,105,100,32,33,61,32,65,86,95,67,79,68,69,67,95,73,68,95,72,50,54,52,0,108,105,98,97,118,99,111,100,101,99,47,101,114,114,111,114,95,114,101,115,105,108,105,101,110,99,101,46,99,0,115,45,62,113,117,97,114,116,101,114,95,115,97,109,112,108,101,0,103,117,101,115,115,95,100,99,40,41,32,105,115,32,111,117,116,32,111,102,32,109,101,109,111,114,121,10,0,19,17,15,15,13,13,13,13,11,11,11,11,11,11,11,11,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,32,32,32,32,32,32,32,32,31,32,32,32,32,32,32,32,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,7,7,7,7,8,8,8,8,9,9,9,9,10,10,10,10,11,11,11,11,12,12,12,12,13,13,13,13,14,14,14,14,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,17,16,17,17,17,17,17,17,17,8,248,9,247,10,246,11,245,12,244,13,243,14,242,15,241,4,4,4,4,252,252,252,252,5,5,5,5,251,251,251,251,6,6,6,6,250,250,250,250,7,7,7,7,249,249,249,249,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,241,2,54,3,74,20,241,2,54,3,74,228,127,233,104,250,53,255,54,7,51,23,33,23,2,21,0,1,9,0,49,219,118,5,57,243,78,245,65,1,62,12,49,252,73,17,50,18,64,9,43,29,0,26,67,16,90,9,104,210,127,236,104,1,67,243,78,245,65,1,62,250,86,239,95,250,61,9,45,253,69,250,81,245,96,6,55,7,67,251,86,2,88,0,58,253,76,246,94,5,54,4,69,253,81,0,88,249,67,251,74,252,74,251,80,249,72,1,58,0,41,0,63,0,63,0,63,247,83,4,86,0,97,249,72,13,41,3,62,0,45,252,78,253,96,229,126,228,98,231,101,233,67,228,82,236,94,240,83,234,110,235,91,238,102,243,93,227,127,249,92,251,89,249,96,243,108,253,46,255,65,255,57,247,93,253,74,247,92,248,87,233,126,5,54,6,60,6,59,6,69,255,48,0,68,252,69,248,88,254,85,250,78,255,75,249,77,2,54,5,50,253,68,1,50,6,42,252,81,1,63,252,70,0,67,2,57,254,76,11,35,4,64,1,61,11,35,18,25,12,24,13,29,13,36,246,93,249,73,254,73,13,46,9,49,249,100,9,53,2,53,5,53,254,61,0,56,0,56,243,63,251,60,255,62,4,57,250,69,4,57,14,39,4,51,13,68,3,64,1,61,9,63,7,50,16,39,5,44,4,52,11,48,251,60,255,59,0,59,22,33,5,44,14,43,255,78,0,60,9,69,11,28,2,40,3,44,0,49,0,46,2,44,2,51,0,47,4,39,2,62,6,46,0,54,3,54,2,58,4,63,6,51,6,57,7,53,6,52,6,55,11,45,14,36,8,53,255,82,7,55,253,78,15,46,22,31,255,84,25,7,30,249,28,3,28,4,32,0,34,255,30,6,30,6,32,9,31,19,26,27,26,30,37,20,28,34,17,70,1,67,5,59,9,67,16,30,18,32,18,35,22,29,24,31,23,38,18,43,20,41,11,63,9,59,9,64,255,94,254,89,247,108,250,76,254,44,0,45,0,52,253,64,254,59,252,70,252,75,248,82,239,102,247,77,3,24,0,42,0,48,0,55,250,59,249,71,244,83,245,87,226,119,1,58,253,29,255,36,1,38,2,43,250,55,0,58,0,64,253,74,246,90,0,70,252,29,5,31,7,42,1,59,254,58,253,72,253,81,245,97,0,58,8,5,10,14,14,18,13,27,2,40,0,58,253,70,250,79,248,85,0,0,243,106,240,106,246,87,235,114,238,110,242,98,234,110,235,106,238,103,235,107,233,108,230,112,246,96,244,95,251,91,247,93,234,94,251,86,9,67,252,80,246,85,255,70,7,60,9,58,5,61,12,50,15,50,18,49,17,54,10,41,7,46,255,51,7,49,8,52,9,41,6,47,2,55,13,41,10,44,6,50,5,53,13,49,4,63,6,64,254,69,254,59,6,70,10,44,9,31,12,43,3,53,14,34,10,38,253,52,13,40,17,32,7,44,7,38,13,50,10,57,26,43,14,11,11,14,9,11,18,11,21,9,23,254,32,241,32,241,34,235,39,233,42,223,41,225,46,228,38,244,21,29,45,232,53,211,48,230,65,213,43,237,39,246,30,9,18,26,20,27,0,57,242,82,251,75,237,97,221,125,27,0,28,0,31,252,27,6,34,8,30,10,24,22,33,19,22,32,26,31,21,41,26,44,23,47,16,65,14,71,8,60,6,63,17,65,21,24,23,20,26,23,27,32,28,23,28,24,23,40,24,32,28,29,23,42,19,57,22,53,22,61,11,86,12,40,11,51,14,59,252,79,249,71,251,69,247,70,248,66,246,68,237,73,244,69,240,70,241,67,236,62,237,70,240,66,234,65,236,63,9,254,26,247,33,247,39,249,41,254,45,3,49,9,45,27,36,59,250,66,249,35,249,42,248,45,251,48,244,56,250,60,251,62,248,66,248,76,251,85,250,81,246,77,249,81,239,80,238,73,252,74,246,83,247,71,247,67,255,61,248,66,242,66,0,59,2,59,21,243,33,242,39,249,46,254,51,2,60,6,61,17,55,34,42,62,249,92,251,89,249,96,243,108,253,46,255,65,255,57,247,93,253,74,247,92,248,87,233,126,249,92,251,89,249,96,243,108,253,46,255,65,255,57,247,93,253,74,247,92,248,87,233,126,254,85,250,78,255,75,249,77,2,54,5,50,253,68,1,50,6,42,252,81,1,63,252,70,0,67,2,57,254,76,11,35,4,64,1,61,11,35,18,25,12,24,13,29,13,36,246,93,249,73,254,73,13,46,9,49,249,100,9,53,2,53,5,53,254,61,0,56,0,56,243,63,251,60,255,62,4,57,250,69,4,57,14,39,4,51,13,68,254,85,250,78,255,75,249,77,2,54,5,50,253,68,1,50,6,42,252,81,1,63,252,70,0,67,2,57,254,76,11,35,4,64,1,61,11,35,18,25,12,24,13,29,13,36,246,93,249,73,254,73,13,46,9,49,249,100,9,53,2,53,5,53,254,61,0,56,0,56,243,63,251,60,255,62,4,57,250,69,4,57,14,39,4,51,13,68,11,28,2,40,3,44,0,49,0,46,2,44,2,51,0,47,4,39,2,62,6,46,0,54,3,54,2,58,4,63,6,51,6,57,7,53,6,52,6,55,11,45,14,36,8,53,255,82,7,55,253,78,15,46,22,31,255,84,25,7,30,249,28,3,28,4,32,0,34,255,30,6,30,6,32,9,31,19,26,27,26,30,37,20,28,34,17,70,11,28,2,40,3,44,0,49,0,46,2,44,2,51,0,47,4,39,2,62,6,46,0,54,3,54,2,58,4,63,6,51,6,57,7,53,6,52,6,55,11,45,14,36,8,53,255,82,7,55,253,78,15,46,22,31,255,84,25,7,30,249,28,3,28,4,32,0,34,255,30,6,30,6,32,9,31,19,26,27,26,30,37,20,28,34,17,70,252,79,249,71,251,69,247,70,248,66,246,68,237,73,244,69,240,70,241,67,236,62,237,70,240,66,234,65,236,63,251,85,250,81,246,77,249,81,239,80,238,73,252,74,246,83,247,71,247,67,255,61,248,66,242,66,0,59,2,59,9,254,26,247,33,247,39,249,41,254,45,3,49,9,45,27,36,59,21,243,33,242,39,249,46,254,51,2,60,6,61,17,55,34,42,62,250,66,249,35,249,42,248,45,251,48,244,56,250,60,251,62,248,66,248,76,252,79,249,71,251,69,247,70,248,66,246,68,237,73,244,69,240,70,241,67,236,62,237,70,240,66,234,65,236,63,251,85,250,81,246,77,249,81,239,80,238,73,252,74,246,83,247,71,247,67,255,61,248,66,242,66,0,59,2,59,9,254,26,247,33,247,39,249,41,254,45,3,49,9,45,27,36,59,21,243,33,242,39,249,46,254,51,2,60,6,61,17,55,34,42,62,250,66,249,35,249,42,248,45,251,48,244,56,250,60,251,62,248,66,248,76,243,106,240,106,246,87,235,114,238,110,242,98,234,110,235,106,238,103,235,107,233,108,230,112,246,96,244,95,251,91,247,93,234,94,251,86,9,67,252,80,246,85,255,70,7,60,9,58,5,61,12,50,15,50,18,49,17,54,10,41,7,46,255,51,7,49,8,52,9,41,6,47,2,55,13,41,10,44,6,50,5,53,13,49,4,63,6,64,243,106,240,106,246,87,235,114,238,110,242,98,234,110,235,106,238,103,235,107,233,108,230,112,246,96,244,95,251,91,247,93,234,94,251,86,9,67,252,80,246,85,255,70,7,60,9,58,5,61,12,50,15,50,18,49,17,54,10,41,7,46,255,51,7,49,8,52,9,41,6,47,2,55,13,41,10,44,6,50,5,53,13,49,4,63,6,64,14,11,11,14,9,11,18,11,21,9,23,254,32,241,32,241,34,235,39,233,42,223,41,225,46,228,38,244,21,29,45,232,53,211,48,230,65,213,43,237,39,246,30,9,18,26,20,27,0,57,242,82,251,75,237,97,221,125,27,0,28,0,31,252,27,6,34,8,30,10,24,22,33,19,22,32,26,31,21,41,26,44,23,47,16,65,14,71,14,11,11,14,9,11,18,11,21,9,23,254,32,241,32,241,34,235,39,233,42,223,41,225,46,228,38,244,21,29,45,232,53,211,48,230,65,213,43,237,39,246,30,9,18,26,20,27,0,57,242,82,251,75,237,97,221,125,27,0,28,0,31,252,27,6,34,8,30,10,24,22,33,19,22,32,26,31,21,41,26,44,23,47,16,65,14,71,250,76,254,44,0,45,0,52,253,64,254,59,252,70,252,75,248,82,239,102,247,77,3,24,0,42,0,48,0,55,250,59,249,71,244,83,245,87,226,119,1,58,253,29,255,36,1,38,2,43,250,55,0,58,0,64,253,74,246,90,250,76,254,44,0,45,0,52,253,64,254,59,252,70,252,75,248,82,239,102,247,77,3,24,0,42,0,48,0,55,250,59,249,71,244,83,245,87,226,119,1,58,253,29,255,36,1,38,2,43,250,55,0,58,0,64,253,74,246,90,253,74,247,92,248,87,233,126,253,74,247,92,248,87,233,126,253,74,247,92,248,87,233,126,20,241,2,54,3,74,20,241,2,54,3,74,228,127,233,104,250,53,255,54,7,51,22,25,34,0,16,0,254,9,4,41,227,118,2,65,250,71,243,79,5,52,9,50,253,70,10,54,26,34,19,22,40,0,57,2,41,36,26,69,211,127,241,101,252,76,250,71,243,79,5,52,6,69,243,90,0,52,8,43,254,69,251,82,246,96,2,59,2,75,253,87,253,100,1,56,253,74,250,85,0,59,253,81,249,86,251,95,255,66,255,77,1,70,254,86,251,72,0,61,0,41,0,63,0,63,0,63,247,83,4,86,0,97,249,72,13,41,3,62,13,15,7,51,2,80,217,127,238,91,239,96,230,81,221,98,232,102,233,97,229,119,232,99,235,110,238,102,220,127,0,80,251,89,249,94,252,92,0,39,0,65,241,84,221,127,254,73,244,104,247,91,225,127,3,55,7,56,7,55,8,61,253,53,0,68,249,74,247,88,243,103,243,91,247,89,242,92,248,76,244,87,233,110,232,105,246,78,236,112,239,99,178,127,186,127,206,127,210,127,252,66,251,78,252,71,248,72,2,59,255,55,249,70,250,75,248,89,222,119,253,75,32,20,30,22,212,127,0,54,251,61,0,58,255,60,253,61,248,67,231,84,242,74,251,65,5,52,2,57,0,61,247,69,245,70,18,55,252,71,0,58,7,61,9,41,18,25,9,32,5,43,9,47,0,44,0,51,2,46,19,38,252,66,15,38,12,42,9,34,0,89,4,45,10,28,10,31,33,245,52,213,18,15,28,0,35,234,38,231,34,0,39,238,32,244,102,162,0,0,56,241,33,252,29,10,37,251,51,227,39,247,52,222,69,198,67,193,44,251,32,7,55,227,32,1,0,0,27,36,33,231,34,226,36,228,38,228,38,229,34,238,35,240,34,242,32,248,37,250,35,0,30,10,28,18,26,25,29,41,0,75,2,72,8,77,14,35,18,31,17,35,21,30,17,45,20,42,18,45,27,26,16,54,7,66,16,56],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+80465);allocate([11,73,10,67,246,116,233,112,241,71,249,61,0,53,251,66,245,77,247,80,247,84,246,87,222,127,235,101,253,39,251,53,249,61,245,75,241,77,239,91,231,107,231,111,228,122,245,76,246,44,246,52,246,57,247,58,240,72,249,69,252,69,251,74,247,86,2,66,247,34,1,32,11,31,5,52,254,55,254,67,0,73,248,89,3,52,7,4,10,8,17,8,16,19,3,37,255,61,251,73,255,70,252,78,0,0,235,126,233,124,236,110,230,126,231,124,239,105,229,121,229,117,239,102,230,117,229,116,223,122,246,95,242,100,248,95,239,111,228,114,250,89,254,80,252,82,247,85,248,81,255,72,5,64,1,67,9,56,0,69,1,69,7,69,249,69,250,67,240,77,254,64,2,61,250,67,253,64,2,57,253,65,253,66,0,62,9,51,255,66,254,71,254,75,255,70,247,72,14,60,16,37,0,47,18,35,11,37,12,41,10,41,2,48,12,41,13,41,0,59,3,50,19,40,3,66,18,50,19,250,18,250,14,0,26,244,31,240,33,231,33,234,37,228,39,226,42,226,47,214,45,220,49,222,41,239,32,9,69,185,63,193,66,192,77,182,54,217,52,221,41,246,36,0,40,255,30,14,28,26,23,37,12,55,11,65,37,223,39,220,40,219,38,226,46,223,42,226,40,232,49,227,38,244,40,246,38,253,46,251,31,20,29,30,25,44,12,48,11,49,26,45,22,22,23,22,27,21,33,20,26,28,30,24,27,34,18,42,25,39,18,50,12,70,21,54,14,71,11,83,25,32,21,49,21,54,251,85,250,81,246,77,249,81,239,80,238,73,252,74,246,83,247,71,247,67,255,61,248,66,242,66,0,59,2,59,17,246,32,243,42,247,49,251,53,0,64,3,68,10,66,27,47,57,251,71,0,24,255,36,254,42,254,52,247,57,250,63,252,65,252,67,249,82,253,81,253,76,249,72,250,78,244,72,242,68,253,70,250,76,251,66,251,62,0,57,252,61,247,60,1,54,2,58,17,246,32,243,42,247,49,251,53,0,64,3,68,10,66,27,47,57,0,80,251,89,249,94,252,92,0,39,0,65,241,84,221,127,254,73,244,104,247,91,225,127,0,80,251,89,249,94,252,92,0,39,0,65,241,84,221,127,254,73,244,104,247,91,225,127,243,103,243,91,247,89,242,92,248,76,244,87,233,110,232,105,246,78,236,112,239,99,178,127,186,127,206,127,210,127,252,66,251,78,252,71,248,72,2,59,255,55,249,70,250,75,248,89,222,119,253,75,32,20,30,22,212,127,0,54,251,61,0,58,255,60,253,61,248,67,231,84,242,74,251,65,5,52,2,57,0,61,247,69,245,70,18,55,243,103,243,91,247,89,242,92,248,76,244,87,233,110,232,105,246,78,236,112,239,99,178,127,186,127,206,127,210,127,252,66,251,78,252,71,248,72,2,59,255,55,249,70,250,75,248,89,222,119,253,75,32,20,30,22,212,127,0,54,251,61,0,58,255,60,253,61,248,67,231,84,242,74,251,65,5,52,2,57,0,61,247,69,245,70,18,55,4,45,10,28,10,31,33,245,52,213,18,15,28,0,35,234,38,231,34,0,39,238,32,244,102,162,0,0,56,241,33,252,29,10,37,251,51,227,39,247,52,222,69,198,67,193,44,251,32,7,55,227,32,1,0,0,27,36,33,231,34,226,36,228,38,228,38,229,34,238,35,240,34,242,32,248,37,250,35,0,30,10,28,18,26,25,29,41,4,45,10,28,10,31,33,245,52,213,18,15,28,0,35,234,38,231,34,0,39,238,32,244,102,162,0,0,56,241,33,252,29,10,37,251,51,227,39,247,52,222,69,198,67,193,44,251,32,7,55,227,32,1,0,0,27,36,33,231,34,226,36,228,38,228,38,229,34,238,35,240,34,242,32,248,37,250,35,0,30,10,28,18,26,25,29,41,251,85,250,81,246,77,249,81,239,80,238,73,252,74,246,83,247,71,247,67,255,61,248,66,242,66,0,59,2,59,253,81,253,76,249,72,250,78,244,72,242,68,253,70,250,76,251,66,251,62,0,57,252,61,247,60,1,54,2,58,17,246,32,243,42,247,49,251,53,0,64,3,68,10,66,27,47,57,17,246,32,243,42,247,49,251,53,0,64,3,68,10,66,27,47,57,251,71,0,24,255,36,254,42,254,52,247,57,250,63,252,65,252,67,249,82,251,85,250,81,246,77,249,81,239,80,238,73,252,74,246,83,247,71,247,67,255,61,248,66,242,66,0,59,2,59,253,81,253,76,249,72,250,78,244,72,242,68,253,70,250,76,251,66,251,62,0,57,252,61,247,60,1,54,2,58,17,246,32,243,42,247,49,251,53,0,64,3,68,10,66,27,47,57,17,246,32,243,42,247,49,251,53,0,64,3,68,10,66,27,47,57,251,71,0,24,255,36,254,42,254,52,247,57,250,63,252,65,252,67,249,82,235,126,233,124,236,110,230,126,231,124,239,105,229,121,229,117,239,102,230,117,229,116,223,122,246,95,242,100,248,95,239,111,228,114,250,89,254,80,252,82,247,85,248,81,255,72,5,64,1,67,9,56,0,69,1,69,7,69,249,69,250,67,240,77,254,64,2,61,250,67,253,64,2,57,253,65,253,66,0,62,9,51,255,66,254,71,254,75,235,126,233,124,236,110,230,126,231,124,239,105,229,121,229,117,239,102,230,117,229,116,223,122,246,95,242,100,248,95,239,111,228,114,250,89,254,80,252,82,247,85,248,81,255,72,5,64,1,67,9,56,0,69,1,69,7,69,249,69,250,67,240,77,254,64,2,61,250,67,253,64,2,57,253,65,253,66,0,62,9,51,255,66,254,71,254,75,19,250,18,250,14,0,26,244,31,240,33,231,33,234,37,228,39,226,42,226,47,214,45,220,49,222,41,239,32,9,69,185,63,193,66,192,77,182,54,217,52,221,41,246,36,0,40,255,30,14,28,26,23,37,12,55,11,65,37,223,39,220,40,219,38,226,46,223,42,226,40,232,49,227,38,244,40,246,38,253,46,251,31,20,29,30,25,44,19,250,18,250,14,0,26,244,31,240,33,231,33,234,37,228,39,226,42,226,47,214,45,220,49,222,41,239,32,9,69,185,63,193,66,192,77,182,54,217,52,221,41,246,36,0,40,255,30,14,28,26,23,37,12,55,11,65,37,223,39,220,40,219,38,226,46,223,42,226,40,232,49,227,38,244,40,246,38,253,46,251,31,20,29,30,25,44,233,112,241,71,249,61,0,53,251,66,245,77,247,80,247,84,246,87,222,127,235,101,253,39,251,53,249,61,245,75,241,77,239,91,231,107,231,111,228,122,245,76,246,44,246,52,246,57,247,58,240,72,249,69,252,69,251,74,247,86,233,112,241,71,249,61,0,53,251,66,245,77,247,80,247,84,246,87,222,127,235,101,253,39,251,53,249,61,245,75,241,77,239,91,231,107,231,111,228,122,245,76,246,44,246,52,246,57,247,58,240,72,249,69,252,69,251,74,247,86,254,73,244,104,247,91,225,127,254,73,244,104,247,91,225,127,254,73,244,104,247,91,225,127,20,241,2,54,3,74,20,241,2,54,3,74,228,127,233,104,250,53,255,54,7,51,29,16,25,0,14,0,246,51,253,62,229,99,26,16,252,85,232,102,5,57,6,57,239,73,14,57,20,40,20,10,29,0,54,0,37,42,12,97,224,127,234,117,254,74,252,85,232,102,5,57,250,93,242,88,250,44,4,55,245,89,241,103,235,116,19,57,20,58,4,84,6,96,1,63,251,85,243,106,5,63,6,75,253,90,255,101,3,55,252,79,254,75,244,97,249,50,1,60,0,41,0,63,0,63,0,63,247,83,4,86,0,97,249,72,13,41,3,62,7,34,247,88,236,127,220,127,239,91,242,95,231,84,231,86,244,89,239,91,225,127,242,76,238,103,243,90,219,127,11,80,5,76,2,84,5,78,250,55,4,61,242,83,219,127,251,79,245,104,245,91,226,127,0,65,254,79,0,72,252,92,250,56,3,68,248,71,243,98,252,86,244,88,251,82,253,72,252,67,248,72,240,89,247,69,255,59,5,66,4,57,252,71,254,71,2,58,255,74,252,44,255,69,0,62,249,51,252,47,250,42,253,41,250,53,8,76,247,78,245,83,9,52,0,67,251,90,1,67,241,72,251,75,248,80,235,83,235,64,243,31,231,64,227,94,9,75,17,63,248,74,251,35,254,27,13,91,3,65,249,69,8,77,246,66,3,62,253,68,236,81,0,30,1,7,253,23,235,74,16,66,233,124,17,37,44,238,50,222,234,127,4,39,0,42,7,34,11,29,8,31,6,37,7,42,3,40,8,33,13,43,13,36,4,47,3,55,2,58,6,60,8,44,11,44,14,42,7,48,4,56,4,52,13,37,9,49,19,58,10,48,12,45,0,69,20,33,8,63,35,238,33,231,28,253,24,10,27,0,34,242,52,212,39,232,19,17,31,25,36,29,24,33,34,15,30,20,22,73,20,34,19,31,27,44,19,16,15,36,15,36,21,28,25,21,30,20,31,12,27,16,24,42,0,93,14,56,15,57,26,38,232,127,232,115,234,82,247,62,0,53,0,59,242,85,243,89,243,94,245,92,227,127,235,100,242,57,244,67,245,71,246,77,235,85,240,88,233,104,241,98,219,127,246,82,248,48,248,61,248,66,249,70,242,75,246,79,247,83,244,92,238,108,252,79,234,69,240,75,254,58,1,58,243,78,247,83,252,81,243,99,243,81,250,38,243,62,250,58,254,59,240,73,246,76,243,86,247,83,246,87,0,0,234,127,231,127,231,120,229,127,237,114,233,117,231,118,230,117,232,113,228,118,225,120,219,124,246,94,241,102,246,99,243,106,206,127,251,92,17,57,251,86,243,94,244,91,254,77,0,71,255,73,4,64,249,81,5,64,15,57,1,67,0,68,246,67,1,68,0,77,2,64,0,68,251,78,7,55,5,59,2,65,14,54,15,44,5,60,2,70,254,76,238,86,12,70,5,64,244,70,11,55,5,56,0,69,2,65,250,74,5,54,7,54,250,76,245,82,254,77,254,77,25,42,17,243,16,247,17,244,27,235,37,226,41,216,42,215,48,209,39,224,46,216,52,205,46,215,52,217,43,237,32,11,61,201,56,210,62,206,81,189,45,236,35,254,28,15,34,1,39,1,30,17,20,38,18,45,15,54,0,79,36,240,37,242,37,239,32,1,34,15,29,15,24,25,34,22,31,16,35,18,31,28,33,41,36,28,27,47,21,62,18,31,19,26,36,24,24,23,27,16,24,30,31,29,22,41,22,42,16,60,15,52,14,60,3,78,240,123,21,53,22,56,25,61,21,33,19,50,17,61,253,78,248,74,247,72,246,72,238,75,244,71,245,63,251,70,239,75,242,72,240,67,248,53,242,59,247,52,245,68,9,254,30,246,31,252,33,255,33,7,31,12,37,23,31,38,20,64,247,71,249,37,248,44,245,49,246,56,244,59,248,63,247,67,250,68,246,79,253,78,248,74,247,72,246,72,238,75,244,71,245,63,251,70,239,75,242,72,240,67,248,53,242,59,247,52,245,68,9,254,30,246,31,252,33,255,33,7,31,12,37,23,31,38,20,64,11,80,5,76,2,84,5,78,250,55,4,61,242,83,219,127,251,79,245,104,245,91,226,127,11,80,5,76,2,84,5,78,250,55,4,61,242,83,219,127,251,79,245,104,245,91,226,127,252,86,244,88,251,82,253,72,252,67,248,72,240,89,247,69,255,59,5,66,4,57,252,71,254,71,2,58,255,74,252,44,255,69,0,62,249,51,252,47,250,42,253,41,250,53,8,76,247,78,245,83,9,52,0,67,251,90,1,67,241,72,251,75,248,80,235,83,235,64,243,31,231,64,227,94,9,75,17,63,248,74,251,35,254,27,13,91,252,86,244,88,251,82,253,72,252,67,248,72,240,89,247,69,255,59,5,66,4,57,252,71,254,71,2,58,255,74,252,44,255,69,0,62,249,51,252,47,250,42,253,41,250,53,8,76,247,78,245,83,9,52,0,67,251,90,1,67,241,72,251,75,248,80,235,83,235,64,243,31,231,64,227,94,9,75,17,63,248,74,251,35,254,27,13,91,4,39,0,42,7,34,11,29,8,31,6,37,7,42,3,40,8,33,13,43,13,36,4,47,3,55,2,58,6,60,8,44,11,44,14,42,7,48,4,56,4,52,13,37,9,49,19,58,10,48,12,45,0,69,20,33,8,63,35,238,33,231,28,253,24,10,27,0,34,242,52,212,39,232,19,17,31,25,36,29,24,33,34,15,30,20,22,73,4,39,0,42,7,34,11,29,8,31,6,37,7,42,3,40,8,33,13,43,13,36,4,47,3,55,2,58,6,60,8,44,11,44,14,42,7,48,4,56,4,52,13,37,9,49,19,58,10,48,12,45,0,69,20,33,8,63,35,238,33,231,28,253,24,10,27,0,34,242,52,212,39,232,19,17,31,25,36,29,24,33,34,15,30,20,22,73,253,78,248,74,247,72,246,72,238,75,244,71,245,63,251,70,239,75,242,72,240,67,248,53,242,59,247,52,245,68,253,78,248,74,247,72,246,72,238,75,244,71,245,63,251,70,239,75,242,72,240,67,248,53,242,59,247,52,245,68,9,254,30,246,31,252,33,255,33,7,31,12,37,23,31,38,20,64,9,254,30,246,31,252,33,255,33,7,31,12,37,23,31,38,20,64,247,71,249,37,248,44,245,49,246,56,244,59,248,63,247,67,250,68,246,79,253,78,248,74,247,72,246,72,238,75,244,71,245,63,251,70,239,75,242,72,240,67,248,53,242,59,247,52,245,68,253,78,248,74,247,72,246,72,238,75,244,71,245,63,251,70,239,75,242,72,240,67,248,53,242,59,247,52,245,68,9,254,30,246,31,252,33,255,33,7,31,12,37,23,31,38,20,64,9,254,30,246,31,252,33,255,33,7,31,12,37,23,31,38,20,64,247,71,249,37,248,44,245,49,246,56,244,59,248,63,247,67,250,68,246,79,234,127,231,127,231,120,229,127,237,114,233,117,231,118,230,117,232,113,228,118,225,120,219,124,246,94,241,102,246,99,243,106,206,127,251,92,17,57,251,86,243,94,244,91,254,77,0,71,255,73,4,64,249,81,5,64,15,57,1,67,0,68,246,67,1,68,0,77,2,64,0,68,251,78,7,55,5,59,2,65,14,54,15,44,5,60,2,70,234,127,231,127,231,120,229,127,237,114,233,117,231,118,230,117,232,113,228,118,225,120,219,124,246,94,241,102,246,99,243,106,206,127,251,92,17,57,251,86,243,94,244,91,254,77,0,71,255,73,4,64,249,81,5,64,15,57,1,67,0,68,246,67,1,68,0,77,2,64,0,68,251,78,7,55,5,59,2,65,14,54,15,44,5,60,2,70,17,243,16,247,17,244,27,235,37,226,41,216,42,215,48,209,39,224,46,216,52,205,46,215,52,217,43,237,32,11,61,201,56,210,62,206,81,189,45,236,35,254,28,15,34,1,39,1,30,17,20,38,18,45,15,54,0,79,36,240,37,242,37,239,32,1,34,15,29,15,24,25,34,22,31,16,35,18,31,28,33,41,36,28,27,47,21,62,17,243,16,247,17,244,27,235,37,226,41,216,42,215,48,209,39,224,46,216,52,205,46,215,52,217,43,237,32,11,61,201,56,210,62,206,81,189,45,236,35,254,28,15,34,1,39,1,30,17,20,38,18,45,15,54,0,79,36,240,37,242,37,239,32,1,34,15,29,15,24,25,34,22,31,16,35,18,31,28,33,41,36,28,27,47,21,62,232,115,234,82,247,62,0,53,0,59,242,85,243,89,243,94,245,92,227,127,235,100,242,57,244,67,245,71,246,77,235,85,240,88,233,104,241,98,219,127,246,82,248,48,248,61,248,66,249,70,242,75,246,79,247,83,244,92,238,108,232,115,234,82,247,62,0,53,0,59,242,85,243,89,243,94,245,92,227,127,235,100,242,57,244,67,245,71,246,77,235,85,240,88,233,104,241,98,219,127,246,82,248,48,248,61,248,66,249,70,242,75,246,79,247,83,244,92,238,108,251,79,245,104,245,91,226,127,251,79,245,104,245,91,226,127,251,79,245,104,245,91,226,127,20,241,2,54,3,74,20,241,2,54,3,74,228,127,233,104,250,53,255,54,7,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,0,63,0,63,0,63,247,83,4,86,0,97,249,72,13,41,3,62,0,11,1,55,0,69,239,127,243,102,0,82,249,74,235,107,229,127,225,127,232,127,238,95,229,127,235,114,226,127,239,123,244,115,240,122,245,115,244,63,254,68,241,84,243,104,253,70,248,93,246,90,226,127,255,74,250,97,249,91,236,127,252,56,251,82,249,76,234,125,249,93,245,87,253,77,251,71,252,63,252,68,244,84,249,62,249,65,8,61,5,56,254,66,1,64,0,61,254,78,1,50,7,52,10,35,0,44,11,38,1,45,0,46,5,44,31,17,1,51,7,50,28,19,16,33,14,62,243,108,241,100,243,101,243,91,244,94,246,88,240,84,246,86,249,83,243,87,237,94,1,70,0,72,251,74,18,59,248,102,241,100,0,95,252,75,2,72,245,75,253,71,15,46,243,69,0,62,0,65,21,37,241,72,9,57,16,54,0,62,12,72,24,0,15,9,8,25,13,18,15,9,13,19,10,37,12,18,6,29,20,33,15,30,4,45,1,58,0,62,7,61,12,38,11,45,15,39,11,42,13,44,16,45,12,41,10,49,30,34,18,42,10,55,17,51,17,46,0,89,26,237,22,239,26,239,30,231,28,236,33,233,37,229,33,233,40,228,38,239,33,245,40,241,41,250,38,1,41,17,30,250,27,3,26,22,37,240,35,252,38,248,38,253,37,3,38,5,42,0,35,16,39,22,14,48,27,37,21,60,12,68,2,97,253,71,250,42,251,50,253,54,254,62,0,58,1,63,254,72,255,74,247,91,251,67,251,27,253,39,254,44,0,46,240,64,248,68,246,78,250,77,246,86,244,92,241,55,246,60,250,62,252,65,244,73,248,76,249,80,247,88,239,110,245,97,236,84,245,79,250,73,252,74,243,86,243,96,245,97,237,117,248,78,251,33,252,48,254,53,253,62,243,71,246,79,244,86,243,90,242,97,0,0,250,93,250,84,248,79,0,66,255,71,0,62,254,60,254,59,251,75,253,62,252,58,247,66,255,79,0,71,3,68,10,44,249,62,15,36,14,40,16,27,12,29,1,44,20,36,18,32,5,42,1,48,10,62,17,46,9,64,244,104,245,97,240,96,249,88,248,85,249,85,247,85,243,88,4,66,253,77,253,76,250,76,10,58,255,76,255,83,249,99,242,95,2,95,0,76,251,74,0,70,245,75,1,68,0,65,242,73,3,62,4,62,255,68,243,75,11,55,5,64,12,70,15,6,6,19,7,16,12,14,18,13,13,11,13,15,15,16,12,23,13,23,15,20,14,26,14,44,17,40,17,47,24,17,21,21,25,22,31,27,22,29,19,35,14,50,10,57,7,63,254,77,252,82,253,94,9,69,244,109,36,221,36,222,32,230,37,226,44,224,34,238,34,241,40,241,33,249,35,251,33,0,38,2,33,13,23,35,13,58,29,253,26,0,22,30,31,249,35,241,34,253,34,3,36,255,34,5,32,11,35,5,34,12,39,11,30,29,34,26,29,39,19,66,31,21,31,31,25,50,239,120,236,112,238,114,245,85,241,92,242,89,230,71,241,81,242,80,0,68,242,70,232,56,233,68,232,50,245,74,23,243,26,243,40,241,49,242,44,3,45,6,44,34,33,54,19,82,253,75,255,23,1,34,1,43,0,54,254,55,0,61,1,64,0,68,247,92,242,106,243,97,241,90,244,90,238,88,246,73,247,79,242,86,246,73,246,70,246,69,251,66,247,64,251,58,2,59,21,246,24,245,28,248,28,255,29,3,29,9,35,20,29,36,14,67,239,123,244,115,240,122,245,115,244,63,254,68,241,84,243,104,253,70,248,93,246,90,226,127,239,123,244,115,240,122,245,115,244,63,254,68,241,84,243,104,253,70,248,93,246,90,226,127,249,93,245,87,253,77,251,71,252,63,252,68,244,84,249,62,249,65,8,61,5,56,254,66,1,64,0,61,254,78,1,50,7,52,10,35,0,44,11,38,1,45,0,46,5,44,31,17,1,51,7,50,28,19,16,33,14,62,243,108,241,100,243,101,243,91,244,94,246,88,240,84,246,86,249,83,243,87,237,94,1,70,0,72,251,74,18,59,249,93,245,87,253,77,251,71,252,63,252,68,244,84,249,62,249,65,8,61,5,56,254,66,1,64,0,61,254,78,1,50,7,52,10,35,0,44,11,38,1,45,0,46,5,44,31,17,1,51,7,50,28,19,16,33,14,62,243,108,241,100,243,101,243,91,244,94,246,88,240,84,246,86,249,83,243,87,237,94,1,70,0,72,251,74,18,59,24,0,15,9,8,25,13,18,15,9,13,19,10,37,12,18,6,29,20,33,15,30,4,45,1,58,0,62,7,61,12,38,11,45,15,39,11,42,13,44,16,45,12,41,10,49,30,34,18,42,10,55,17,51,17,46,0,89,26,237,22,239,26,239,30,231,28,236,33,233,37,229,33,233,40,228,38,239,33,245,40,241,41,250,38,1,41,17,24,0,15,9,8,25,13,18,15,9,13,19,10,37,12,18,6,29,20,33,15,30,4,45,1,58,0,62,7,61,12,38,11,45,15,39,11,42,13,44,16,45,12,41,10,49,30,34,18,42,10,55,17,51,17,46,0,89,26,237,22,239,26,239,30,231,28,236,33,233,37,229,33,233,40,228,38,239,33,245,40,241,41,250,38,1,41,17,239,120,236,112,238,114,245,85,241,92,242,89,230,71,241,81,242,80,0,68,242,70,232,56,233,68,232,50,245,74,242,106,243,97,241,90,244,90,238,88,246,73,247,79,242,86,246,73,246,70,246,69,251,66,247,64,251,58,2,59,23,243,26,243,40,241,49,242,44,3,45,6,44,34,33,54,19,82,21,246,24,245,28,248,28,255,29,3,29,9,35,20,29,36,14,67,253,75,255,23,1,34,1,43,0,54,254,55,0,61,1,64,0,68,247,92,239,120,236,112,238,114,245,85,241,92,242,89,230,71,241,81,242,80,0,68,242,70,232,56,233,68,232,50,245,74,242,106,243,97,241,90,244,90,238,88,246,73,247,79,242,86,246,73,246,70,246,69,251,66,247,64,251,58,2,59,23,243,26,243,40,241,49,242,44,3,45,6,44,34,33,54,19,82,21,246,24,245,28,248,28,255,29,3,29,9,35,20,29,36,14,67,253,75,255,23,1,34,1,43,0,54,254,55,0,61,1,64,0,68,247,92,250,93,250,84,248,79,0,66,255,71,0,62,254,60,254,59,251,75,253,62,252,58,247,66,255,79,0,71,3,68,10,44,249,62,15,36,14,40,16,27,12,29,1,44,20,36,18,32,5,42,1,48,10,62,17,46,9,64,244,104,245,97,240,96,249,88,248,85,249,85,247,85,243,88,4,66,253,77,253,76,250,76,10,58,255,76,255,83,250,93,250,84,248,79,0,66,255,71,0,62,254,60,254,59,251,75,253,62,252,58,247,66,255,79,0,71,3,68,10,44,249,62,15,36,14,40,16,27,12,29,1,44,20,36,18,32,5,42,1,48,10,62,17,46,9,64,244,104,245,97,240,96,249,88,248,85,249,85,247,85,243,88,4,66,253,77,253,76,250,76,10,58,255,76,255,83,15,6,6,19,7,16,12,14,18,13,13,11,13,15,15,16,12,23,13,23,15,20,14,26,14,44,17,40,17,47,24,17,21,21,25,22,31,27,22,29,19,35,14,50,10,57,7,63,254,77,252,82,253,94,9,69,244,109,36,221,36,222,32,230,37,226,44,224,34,238,34,241,40,241,33,249,35,251,33,0,38,2,33,13,23,35,13,58,15,6,6,19,7,16,12,14,18,13,13,11,13,15,15,16,12,23,13,23,15,20,14,26,14,44,17,40,17,47,24,17,21,21,25,22,31,27,22,29,19,35,14,50,10,57,7,63,254,77,252,82,253,94,9,69,244,109,36,221,36,222,32,230,37,226,44,224,34,238,34,241,40,241,33,249,35,251,33,0,38,2,33,13,23,35,13,58,253,71,250,42,251,50,253,54,254,62,0,58,1,63,254,72,255,74,247,91,251,67,251,27,253,39,254,44,0,46,240,64,248,68,246,78,250,77,246,86,244,92,241,55,246,60,250,62,252,65,244,73,248,76,249,80,247,88,239,110,253,71,250,42,251,50,253,54,254,62,0,58,1,63,254,72,255,74,247,91,251,67,251,27,253,39,254,44,0,46,240,64,248,68,246,78,250,77,246,86,244,92,241,55,246,60,250,62,252,65,244,73,248,76,249,80,247,88,239,110,253,70,248,93,246,90,226,127,253,70,248,93,246,90,226,127,253,70,248,93,246,90,226,127,0,1,2,3,7,10,8,11,3,7,11,15,17,33,21,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,8,11,8,11,11,11,15,15,21,37,21,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,7,10,7,10,3,3,7,7,17,33,17,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,7,10,7,10,3,11,3,11,17,33,17,33,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,13,20,21,14,15,22,23,28,29,36,37,30,31,38,39,52,53,60,61,54,55,62,63,68,69,76,77,70,71,78,79,92,93,100,101,94,95,102,103,108,109,116,117,110,111,118,119,0,40,80,111,118,101,114,102,108,111,119,32,105,110,32,100,101,99,111,100,101,95,99,97,98,97,99,95,109,98,95,109,118,100,10,0,82,101,102,101,114,101,110,99,101,32,37,100,32,62,61,32,37,100,10,0,103,114,97,121,32,99,104,114,111,109,97,10,0,1,2,3,4,0,0,0,0,1,2,3,3,4,5,6,7,4,4,4,4,5,6,7,7,5,5,5,5,6,7,8,9,5,5,5,5,6,7,8,8,0,1,2,3,4,5,5,4,4,3,3,4,4,4,5,5,4,4,4,4,3,3,6,7,7,7,8,9,10,9,8,7,7,6,11,12,13,11,6,7,8,9,14,10,9,8,6,11,12,13,11,6,9,14,10,9,11,12,13,11,14,10,12,0,1,1,2,2,3,3,4,5,6,7,7,7,8,4,5,6,9,10,10,8,11,12,11,9,9,10,10,8,11,12,11,9,9,10,10,8,11,12,11,9,9,10,10,8,13,13,9,9,10,10,8,13,13,9,9,10,10,14,14,14,14,14,0,0,1,1,2,2,2,0,32,16,64,96,48,80,112,0,16,32,48,99,97,98,97,99,32,100,101,99,111,100,101,32,111,102,32,113,115,99,97,108,101,32,100,105,102,102,32,102,97,105,108,101,100,32,97,116,32,37,100,32,37,100,10,0,3,3,3,3,7,7,7,7,11,11,11,11,15,15,15,15,3,7,11,15,3,7,11,15,3,7,11,15,3,7,11,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,4,5,6,7,8,9,10,12,13,15,17,20,22,25,28,32,36,40,45,50,56,63,71,80,90,101,113,127,144,162,182,203,226,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,3,3,3,3,4,4,4,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,18,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,0,255,0,0,1,255,0,0,1,255,0,0,1,255,0,0,1,255,0,1,1,255,0,1,1,255,1,1,1,255,1,1,1,255,1,1,1,255,1,1,1,255,1,1,2,255,1,1,2,255,1,1,2,255,1,1,2,255,1,2,3,255,1,2,3,255,2,2,3,255,2,2,4,255,2,3,4,255,2,3,4,255,3,3,5,255,3,4,6,255,3,4,6,255,4,5,7,255,4,5,8,255,4,6,9,255,5,7,10,255,6,8,11,255,6,8,13,255,7,10,14,255,8,11,16,255,9,12,18,255,10,13,20,255,11,15,23,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,255,13,17,25,0,3,3,3,1,1,1,1,0,3,1,1,3,3,3,3,0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,80,111,111,108,32,114,97,99,101,32,100,101,99,116,101,99,116,101,100,44,32,115,112,105,110,105,110,103,32,116,111,32,97,118,111,105,100,32,111,118,101,114,97,108,108,111,99,97,116,105,111,110,32,97,110,100,32,101,118,101,110,116,117,97,108,32,79,79,77,10,0,109,111,110,111,0,115,116,101,114,101,111,0,50,46,49,0,51,46,48,0,51,46,48,40,98,97,99,107,41,0,52,46,48,0,113,117,97,100,0,113,117,97,100,40,115,105,100,101,41,0,51,46,49,0,53,46,48,0,53,46,48,40,115,105,100,101,41,0,52,46,49,0,53,46,49,0,53,46,49,40,115,105,100,101,41,0,54,46,48,0,54,46,48,40,102,114,111,110,116,41,0,104,101,120,97,103,111,110,97,108,0,54,46,49,0,54,46,49,40,102,114,111,110,116,41,0,55,46,48,0,55,46,48,40,102,114,111,110,116,41,0,55,46,49,0,55,46,49,40,119,105,100,101,41,0,55,46,49,40,119,105,100,101,45,115,105,100,101,41,0,111,99,116,97,103,111,110,97,108,0,104,101,120,97,100,101,99,97,103,111,110,97,108,0,100,111,119,110,109,105,120,0,70,76,0,102,114,111,110,116,32,108,101,102,116,0,70,82,0,102,114,111,110,116,32,114,105,103,104,116,0,70,67,0,102,114,111,110,116,32,99,101,110,116,101,114,0,76,70,69,0,108,111,119,32,102,114,101,113,117,101,110,99,121,0,66,76,0,98,97,99,107,32,108,101,102,116,0,66,82,0,98,97,99,107,32,114,105,103,104,116,0,70,76,67,0,102,114,111,110,116,32,108,101,102,116,45,111,102,45,99,101,110,116,101,114,0,70,82,67,0,102,114,111,110,116,32,114,105,103,104,116,45,111,102,45,99,101,110,116,101,114,0,66,67,0,98,97,99,107,32,99,101,110,116,101,114,0,83,76,0,115,105,100,101,32,108,101,102,116,0,83,82,0,115,105,100,101,32,114,105,103,104,116,0,84,67,0,116,111,112,32,99,101,110,116,101,114,0,84,70,76,0,116,111,112,32,102,114,111,110,116,32,108,101,102,116,0,84,70,67,0,116,111,112,32,102,114,111,110,116,32,99,101,110,116,101,114,0,84,70,82,0,116,111,112,32,102,114,111,110,116,32,114,105,103,104,116,0,84,66,76,0,116,111,112,32,98,97,99,107,32,108,101,102,116,0,84,66,67,0,116,111,112,32,98,97,99,107,32,99,101,110,116,101,114,0,84,66,82,0,116,111,112,32,98,97,99,107,32,114,105,103,104,116,0,68,76,0,100,111,119,110,109,105,120,32,108,101,102,116,0,68,82,0,100,111,119,110,109,105,120,32,114,105,103,104,116,0,87,76,0,119,105,100,101,32,108,101,102,116,0,87,82,0,119,105,100,101,32,114,105,103,104,116,0,83,68,76,0,115,117,114,114,111,117,110,100,32,100,105,114,101,99,116,32,108,101,102,116,0,83,68,82,0,115,117,114,114,111,117,110,100,32,100,105,114,101,99,116,32,114,105,103,104,116,0,76,70,69,50,0,108,111,119,32,102,114,101,113,117,101,110,99,121,32,50,0,83,105,110,103,108,101,32,99,104,97,110,110,101,108,32,108,97,121,111,117,116,32,39,37,46,42,115,39,32,105,115,32,105,110,116,101,114,112,114,101,116,101,100,32,97,115,32,97,32,110,117,109,98,101,114,32,111,102,32,99,104,97,110,110,101,108,115,44,32,115,119,105,116,99,104,32,116,111,32,116,104,101,32,115,121,110,116,97,120,32,39,37,46,42,115,99,39,32,111,116,104,101,114,119,105,115,101,32,105,116,32,119,105,108,108,32,98,101,32,105,110,116,101,114,112,114,101,116,101,100,32,97,115,32,97,32,99,104,97,110,110,101,108,32,108,97,121,111,117,116,32,110,117,109,98,101,114,32,105,110,32,97,32,108,97,116,101,114,32,118,101,114,115,105,111,110,10,0,37,115,0,37,100,32,99,104,97,110,110,101,108,115,0,32,40,0,43,0,41,0,18,0,9,0,0,0,3,0,6,0,0,15,0,0,0,12,0,0,0,0,24,21,0,0,0,0,0,0,238,0,254,255,0,241,0,2,0,0,3,0,253,247,0,244,0,0,0,0,250,0,0,0,232,235,69,118,97,108,0,69,0,80,73,0,80,72,73,0,81,80,50,76,65,77,66,68,65,0,85,110,100,101,102,105,110,101,100,32,99,111,110,115,116,97,110,116,32,111,114,32,109,105,115,115,105,110,103,32,39,40,39,32,105,110,32,39,37,115,39,10,0,77,105,115,115,105,110,103,32,39,41,39,32,105,110,32,39,37,115,39,10,0,77,105,115,115,105,110,103,32,39,41,39,32,111,114,32,116,111,111,32,109,97,110,121,32,97,114,103,115,32,105,110,32,39,37,115,39,10,0,85,110,107,110,111,119,110,32,102,117,110,99,116,105,111,110,32,105,110,32,39,37,115,39,10,0,73,110,118,97,108,105,100,32,99,104,97,114,115,32,39,37,115,39,32,97,116,32,116,104,101,32,101,110,100,32,111,102,32,101,120,112,114,101,115,115,105,111,110,32,39,37,115,39,10,0,37,102,10,0,70,97,105,108,101,100,32,116,111,32,115,101,116,32,99,108,111,115,101,32,111,110,32,101,120,101,99,10,0,73,77,71,85,84,73,76,83,0,80,105,99,116,117,114,101,32,115,105,122,101,32,37,117,120,37,117,32,105,115,32,105,110,118,97,108,105,100,10,0,97,98,115,40,115,114,99,95,108,105,110,101,115,105,122,101,41,32,62,61,32,98,121,116,101,119,105,100,116,104,0,108,105,98,97,118,117,116,105,108,47,105,109,103,117,116,105,108,115,46,99,0,97,98,115,40,100,115,116,95,108,105,110,101,115,105,122,101,41,32,62,61,32,98,121,116,101,119,105,100,116,104,0,97,118,95,105,109,97,103,101,95,103,101,116,95,108,105,110,101,115,105,122,101,32,102,97,105,108,101,100,10,0,91,37,115,32,64,32,37,112,93,32,0,112,97,110,105,99,0,102,97,116,97,108,0,101,114,114,111,114,0,119,97,114,110,105,110,103,0,105,110,102,111,0,118,101,114,98,111,115,101,0,100,101,98,117,103,0,113,117,105,101,116,0,91,37,115,93,32,0,37,115,37,115,37,115,37,115,0,32,32,32,32,76,97,115,116,32,109,101,115,115,97,103,101,32,114,101,112,101,97,116,101,100,32,37,100,32,116,105,109,101,115,13,0,32,32,32,32,76,97,115,116,32,109,101,115,115,97,103,101,32,114,101,112,101,97,116,101,100,32,37,100,32,116,105,109,101,115,10,0,84,69,82,77,0,78,79,95,67,79,76,79,82,0,65,86,95,76,79,71,95,70,79,82,67,69,95,78,79,67,79,76,79,82,0,65,86,95,76,79,71,95,70,79,82,67,69,95,67,79,76,79,82,0,65,86,95,76,79,71,95,70,79,82,67,69,95,50,53,54,67,79,76,79,82,0,50,53,54,99,111,108,111,114,0,27,91,37,100,59,51,37,100,109,37,115,27,91,48,109,0,27,91,52,56,59,53,59,37,100,109,27,91,51,56,59,53,59,37,100,109,37,115,27,91,48,109,0,32,105,115,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,46,32,85,112,100,97,116,101,32,121,111,117,114,32,70,70,109,112,101,103,32,118,101,114,115,105,111,110,32,116,111,32,116,104,101,32,110,101,119,101,115,116,32,111,110,101,32,102,114,111,109,32,71,105,116,46,32,73,102,32,116,104,101,32,112,114,111,98,108,101,109,32,115,116,105,108,108,32,111,99,99,117,114,115,44,32,105,116,32,109,101,97,110,115,32,116,104,97,116,32,121,111,117,114,32,102,105,108,101,32,104,97,115,32,97,32,102,101,97,116,117,114,101,32,119,104,105,99,104,32,104,97,115,32,110,111,116,32,98,101,101,110,32,105,109,112,108,101,109,101,110,116,101,100,46,10,0,73,102,32,121,111,117,32,119,97,110,116,32,116,111,32,104,101,108,112,44,32,117,112,108,111,97,100,32,97,32,115,97,109,112,108,101,32,111,102,32,116,104,105,115,32,102,105,108,101,32,116,111,32,102,116,112,58,47,47,117,112,108,111,97,100,46,102,102,109,112,101,103,46,111,114,103,47,105,110,99,111,109,105,110,103,47,32,97,110,100,32,99,111,110,116,97,99,116,32,116,104,101,32,102,102,109,112,101,103,45,100,101,118,101,108,32,109,97,105,108,105,110,103,32,108,105,115,116,46,32,40,102,102,109,112,101,103,45,100,101,118,101,108,64,102,102,109,112,101,103,46,111,114,103,41,10,0,118,97,108,32,124,124,32,33,109,105,110,95,115,105,122,101,0,108,105,98,97,118,117,116,105,108,47,109,101,109,95,105,110,116,101,114,110,97,108,46,104,0,37,100,37,42,49,91,58,47,93,37,100,37,99,0,86,97,108,117,101,32,37,102,32,102,111,114,32,112,97,114,97,109,101,116,101,114,32,39,37,115,39,32,111,117,116,32,111,102,32,114,97,110,103,101,32,91,37,103,32,45,32,37,103,93,10,0,86,97,108,117,101,32,37,102,32,102,111,114,32,112,97,114,97,109,101,116,101,114,32,39,37,115,39,32,105,115,32,110,111,116,32,97,32,118,97,108,105,100,32,115,101,116,32,111,102,32,51,50,98,105,116,32,105,110,116,101,103,101,114,32,102,108,97,103,115,10,0,99,111,110,115,116,95,118,97,108,117,101,115,32,97,114,114,97,121,32,116,111,111,32,115,109,97,108,108,32,102,111,114,32,37,115,10,0,100,101,102,97,117,108,116,0,109,97,120,0,109,105,110,0,110,111,110,101,0,97,108,108,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,10,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,32,97,115,32,105,109,97,103,101,32,115,105,122,101,10,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,32,97,115,32,118,105,100,101,111,32,114,97,116,101,10,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,32,97,115,32,37,115,10,0,112,105,120,101,108,32,102,111,114,109,97,116,0,86,97,108,117,101,32,37,100,32,102,111,114,32,112,97,114,97,109,101,116,101,114,32,39,37,115,39,32,111,117,116,32,111,102,32,37,115,32,102,111,114,109,97,116,32,114,97,110,103,101,32,91,37,100,32,45,32,37,100,93,10,0,115,97,109,112,108,101,32,102,111,114,109,97,116,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,32,97,115,32,100,117,114,97,116,105,111,110,10,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,32,97,115,32,99,111,108,111,114,10,0,85,110,97,98,108,101,32,116,111,32,112,97,114,115,101,32,111,112,116,105,111,110,32,118,97,108,117,101,32,34,37,115,34,32,97,115,32,99,104,97,110,110,101,108,32,108,97,121,111,117,116,10,0,73,110,118,97,108,105,100,32,111,112,116,105,111,110,32,116,121,112,101,46,10,0,65,86,79,112,116,105,111,110,32,116,121,112,101,32,37,100,32,111,102,32,111,112,116,105,111,110,32,37,115,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,32,121,101,116,10,0,69,114,114,111,114,32,115,101,116,116,105,110,103,32,111,112,116,105,111,110,32,37,115,32,116,111,32,118,97,108,117,101,32,37,115,46,10,0,37,100,58,37,100,37,99,0,110,116,115,99,0,112,97,108,0,113,110,116,115,99,0,113,112,97,108,0,115,110,116,115,99,0,115,112,97,108,0,102,105,108,109,0,110,116,115,99,45,102,105,108,109,0,115,113,99,105,102,0,113,99,105,102,0,99,105,102,0,52,99,105,102,0,49,54,99,105,102,0,113,113,118,103,97,0,113,118,103,97,0,118,103,97,0,115,118,103,97],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+90705);allocate([0,120,103,97,0,117,120,103,97,0,113,120,103,97,0,115,120,103,97,0,113,115,120,103,97,0,104,115,120,103,97,0,119,118,103,97,0,119,120,103,97,0,119,115,120,103,97,0,119,117,120,103,97,0,119,111,120,103,97,0,119,113,115,120,103,97,0,119,113,117,120,103,97,0,119,104,115,120,103,97,0,119,104,117,120,103,97,0,99,103,97,0,101,103,97,0,104,100,52,56,48,0,104,100,55,50,48,0,104,100,49,48,56,48,0,50,107,0,50,107,100,99,105,0,50,107,102,108,97,116,0,50,107,115,99,111,112,101,0,52,107,0,52,107,100,99,105,0,52,107,102,108,97,116,0,52,107,115,99,111,112,101,0,110,104,100,0,104,113,118,103,97,0,119,113,118,103,97,0,102,119,113,118,103,97,0,104,118,103,97,0,113,104,100,0,117,104,100,50,49,54,48,0,117,104,100,52,51,50,48,0,48,120,0,114,97,110,100,111,109,0,98,105,107,101,115,104,101,100,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,97,98,99,100,101,102,0,73,110,118,97,108,105,100,32,48,120,82,82,71,71,66,66,91,65,65,93,32,99,111,108,111,114,32,115,116,114,105,110,103,58,32,39,37,115,39,10,0,65,108,105,99,101,66,108,117,101,0,65,110,116,105,113,117,101,87,104,105,116,101,0,65,113,117,97,0,65,113,117,97,109,97,114,105,110,101,0,65,122,117,114,101,0,66,101,105,103,101,0,66,105,115,113,117,101,0,66,108,97,99,107,0,66,108,97,110,99,104,101,100,65,108,109,111,110,100,0,66,108,117,101,0,66,108,117,101,86,105,111,108,101,116,0,66,114,111,119,110,0,66,117,114,108,121,87,111,111,100,0,67,97,100,101,116,66,108,117,101,0,67,104,97,114,116,114,101,117,115,101,0,67,104,111,99,111,108,97,116,101,0,67,111,114,97,108,0,67,111,114,110,102,108,111,119,101,114,66,108,117,101,0,67,111,114,110,115,105,108,107,0,67,114,105,109,115,111,110,0,67,121,97,110,0,68,97,114,107,66,108,117,101,0,68,97,114,107,67,121,97,110,0,68,97,114,107,71,111,108,100,101,110,82,111,100,0,68,97,114,107,71,114,97,121,0,68,97,114,107,71,114,101,101,110,0,68,97,114,107,75,104,97,107,105,0,68,97,114,107,77,97,103,101,110,116,97,0,68,97,114,107,79,108,105,118,101,71,114,101,101,110,0,68,97,114,107,111,114,97,110,103,101,0,68,97,114,107,79,114,99,104,105,100,0,68,97,114,107,82,101,100,0,68,97,114,107,83,97,108,109,111,110,0,68,97,114,107,83,101,97,71,114,101,101,110,0,68,97,114,107,83,108,97,116,101,66,108,117,101,0,68,97,114,107,83,108,97,116,101,71,114,97,121,0,68,97,114,107,84,117,114,113,117,111,105,115,101,0,68,97,114,107,86,105,111,108,101,116,0,68,101,101,112,80,105,110,107,0,68,101,101,112,83,107,121,66,108,117,101,0,68,105,109,71,114,97,121,0,68,111,100,103,101,114,66,108,117,101,0,70,105,114,101,66,114,105,99,107,0,70,108,111,114,97,108,87,104,105,116,101,0,70,111,114,101,115,116,71,114,101,101,110,0,70,117,99,104,115,105,97,0,71,97,105,110,115,98,111,114,111,0,71,104,111,115,116,87,104,105,116,101,0,71,111,108,100,0,71,111,108,100,101,110,82,111,100,0,71,114,97,121,0,71,114,101,101,110,0,71,114,101,101,110,89,101,108,108,111,119,0,72,111,110,101,121,68,101,119,0,72,111,116,80,105,110,107,0,73,110,100,105,97,110,82,101,100,0,73,110,100,105,103,111,0,73,118,111,114,121,0,75,104,97,107,105,0,76,97,118,101,110,100,101,114,0,76,97,118,101,110,100,101,114,66,108,117,115,104,0,76,97,119,110,71,114,101,101,110,0,76,101,109,111,110,67,104,105,102,102,111,110,0,76,105,103,104,116,66,108,117,101,0,76,105,103,104,116,67,111,114,97,108,0,76,105,103,104,116,67,121,97,110,0,76,105,103,104,116,71,111,108,100,101,110,82,111,100,89,101,108,108,111,119,0,76,105,103,104,116,71,114,101,101,110,0,76,105,103,104,116,71,114,101,121,0,76,105,103,104,116,80,105,110,107,0,76,105,103,104,116,83,97,108,109,111,110,0,76,105,103,104,116,83,101,97,71,114,101,101,110,0,76,105,103,104,116,83,107,121,66,108,117,101,0,76,105,103,104,116,83,108,97,116,101,71,114,97,121,0,76,105,103,104,116,83,116,101,101,108,66,108,117,101,0,76,105,103,104,116,89,101,108,108,111,119,0,76,105,109,101,0,76,105,109,101,71,114,101,101,110,0,76,105,110,101,110,0,77,97,103,101,110,116,97,0,77,97,114,111,111,110,0,77,101,100,105,117,109,65,113,117,97,77,97,114,105,110,101,0,77,101,100,105,117,109,66,108,117,101,0,77,101,100,105,117,109,79,114,99,104,105,100,0,77,101,100,105,117,109,80,117,114,112,108,101,0,77,101,100,105,117,109,83,101,97,71,114,101,101,110,0,77,101,100,105,117,109,83,108,97,116,101,66,108,117,101,0,77,101,100,105,117,109,83,112,114,105,110,103,71,114,101,101,110,0,77,101,100,105,117,109,84,117,114,113,117,111,105,115,101,0,77,101,100,105,117,109,86,105,111,108,101,116,82,101,100,0,77,105,100,110,105,103,104,116,66,108,117,101,0,77,105,110,116,67,114,101,97,109,0,77,105,115,116,121,82,111,115,101,0,77,111,99,99,97,115,105,110,0,78,97,118,97,106,111,87,104,105,116,101,0,78,97,118,121,0,79,108,100,76,97,99,101,0,79,108,105,118,101,0,79,108,105,118,101,68,114,97,98,0,79,114,97,110,103,101,0,79,114,97,110,103,101,82,101,100,0,79,114,99,104,105,100,0,80,97,108,101,71,111,108,100,101,110,82,111,100,0,80,97,108,101,71,114,101,101,110,0,80,97,108,101,84,117,114,113,117,111,105,115,101,0,80,97,108,101,86,105,111,108,101,116,82,101,100,0,80,97,112,97,121,97,87,104,105,112,0,80,101,97,99,104,80,117,102,102,0,80,101,114,117,0,80,105,110,107,0,80,108,117,109,0,80,111,119,100,101,114,66,108,117,101,0,80,117,114,112,108,101,0,82,101,100,0,82,111,115,121,66,114,111,119,110,0,82,111,121,97,108,66,108,117,101,0,83,97,100,100,108,101,66,114,111,119,110,0,83,97,108,109,111,110,0,83,97,110,100,121,66,114,111,119,110,0,83,101,97,71,114,101,101,110,0,83,101,97,83,104,101,108,108,0,83,105,101,110,110,97,0,83,105,108,118,101,114,0,83,107,121,66,108,117,101,0,83,108,97,116,101,66,108,117,101,0,83,108,97,116,101,71,114,97,121,0,83,110,111,119,0,83,112,114,105,110,103,71,114,101,101,110,0,83,116,101,101,108,66,108,117,101,0,84,97,110,0,84,101,97,108,0,84,104,105,115,116,108,101,0,84,111,109,97,116,111,0,84,117,114,113,117,111,105,115,101,0,86,105,111,108,101,116,0,87,104,101,97,116,0,87,104,105,116,101,0,87,104,105,116,101,83,109,111,107,101,0,89,101,108,108,111,119,0,89,101,108,108,111,119,71,114,101,101,110,0,67,97,110,110,111,116,32,102,105,110,100,32,99,111,108,111,114,32,39,37,115,39,10,0,73,110,118,97,108,105,100,32,97,108,112,104,97,32,118,97,108,117,101,32,115,112,101,99,105,102,105,101,114,32,39,37,115,39,32,105,110,32,39,37,115,39,10,0,37,72,58,37,77,58,37,83,0,110,111,119,0,37,89,45,37,109,45,37,100,0,37,89,37,109,37,100,0,37,72,37,77,37,83,0,37,74,58,37,77,58,37,83,0,37,77,58,37,83,0,121,117,118,52,50,48,112,0,121,117,121,118,52,50,50,0,114,103,98,50,52,0,98,103,114,50,52,0,121,117,118,52,50,50,112,0,121,117,118,52,52,52,112,0,121,117,118,52,49,48,112,0,121,117,118,52,49,49,112,0,103,114,97,121,0,103,114,97,121,56,44,121,56,0,109,111,110,111,119,0,109,111,110,111,98,0,112,97,108,56,0,121,117,118,106,52,50,48,112,0,121,117,118,106,52,50,50,112,0,121,117,118,106,52,52,52,112,0,120,118,109,99,109,99,0,120,118,109,99,105,100,99,116,0,117,121,118,121,52,50,50,0,117,121,121,118,121,121,52,49,49,0,98,103,114,56,0,98,103,114,52,0,98,103,114,52,95,98,121,116,101,0,114,103,98,56,0,114,103,98,52,0,114,103,98,52,95,98,121,116,101,0,110,118,49,50,0,110,118,50,49,0,97,114,103,98,0,114,103,98,97,0,97,98,103,114,0,98,103,114,97,0,103,114,97,121,49,54,98,101,0,121,49,54,98,101,0,103,114,97,121,49,54,108,101,0,121,49,54,108,101,0,121,117,118,52,52,48,112,0,121,117,118,106,52,52,48,112,0,121,117,118,97,52,50,48,112,0,118,100,112,97,117,95,104,50,54,52,0,118,100,112,97,117,95,109,112,101,103,49,0,118,100,112,97,117,95,109,112,101,103,50,0,118,100,112,97,117,95,119,109,118,51,0,118,100,112,97,117,95,118,99,49,0,114,103,98,52,56,98,101,0,114,103,98,52,56,108,101,0,114,103,98,53,54,53,98,101,0,114,103,98,53,54,53,108,101,0,114,103,98,53,53,53,98,101,0,114,103,98,53,53,53,108,101,0,98,103,114,53,54,53,98,101,0,98,103,114,53,54,53,108,101,0,98,103,114,53,53,53,98,101,0,98,103,114,53,53,53,108,101,0,118,97,97,112,105,95,109,111,99,111,0,118,97,97,112,105,95,105,100,99,116,0,118,97,97,112,105,95,118,108,100,0,121,117,118,52,50,48,112,49,54,108,101,0,121,117,118,52,50,48,112,49,54,98,101,0,121,117,118,52,50,50,112,49,54,108,101,0,121,117,118,52,50,50,112,49,54,98,101,0,121,117,118,52,52,52,112,49,54,108,101,0,121,117,118,52,52,52,112,49,54,98,101,0,118,100,112,97,117,95,109,112,101,103,52,0,100,120,118,97,50,95,118,108,100,0,114,103,98,52,52,52,108,101,0,114,103,98,52,52,52,98,101,0,98,103,114,52,52,52,108,101,0,98,103,114,52,52,52,98,101,0,121,97,56,0,103,114,97,121,56,97,0,98,103,114,52,56,98,101,0,98,103,114,52,56,108,101,0,121,117,118,52,50,48,112,57,98,101,0,121,117,118,52,50,48,112,57,108,101,0,121,117,118,52,50,48,112,49,48,98,101,0,121,117,118,52,50,48,112,49,48,108,101,0,121,117,118,52,50,50,112,49,48,98,101,0,121,117,118,52,50,50,112,49,48,108,101,0,121,117,118,52,52,52,112,57,98,101,0,121,117,118,52,52,52,112,57,108,101,0,121,117,118,52,52,52,112,49,48,98,101,0,121,117,118,52,52,52,112,49,48,108,101,0,121,117,118,52,50,50,112,57,98,101,0,121,117,118,52,50,50,112,57,108,101,0,118,100,97,95,118,108,100,0,103,98,114,112,0,103,98,114,112,57,98,101,0,103,98,114,112,57,108,101,0,103,98,114,112,49,48,98,101,0,103,98,114,112,49,48,108,101,0,103,98,114,112,49,54,98,101,0,103,98,114,112,49,54,108,101,0,121,117,118,97,52,50,48,112,57,98,101,0,121,117,118,97,52,50,48,112,57,108,101,0,121,117,118,97,52,50,50,112,57,98,101,0,121,117,118,97,52,50,50,112,57,108,101,0,121,117,118,97,52,52,52,112,57,98,101,0,121,117,118,97,52,52,52,112,57,108,101,0,121,117,118,97,52,50,48,112,49,48,98,101,0,121,117,118,97,52,50,48,112,49,48,108,101,0,121,117,118,97,52,50,50,112,49,48,98,101,0,121,117,118,97,52,50,50,112,49,48,108,101,0,121,117,118,97,52,52,52,112,49,48,98,101,0,121,117,118,97,52,52,52,112,49,48,108,101,0,121,117,118,97,52,50,48,112,49,54,98,101,0,121,117,118,97,52,50,48,112,49,54,108,101,0,121,117,118,97,52,50,50,112,49,54,98,101,0,121,117,118,97,52,50,50,112,49,54,108,101,0,121,117,118,97,52,52,52,112,49,54,98,101,0,121,117,118,97,52,52,52,112,49,54,108,101,0,118,100,112,97,117,0,120,121,122,49,50,108,101,0,120,121,122,49,50,98,101,0,110,118,49,54,0,110,118,50,48,108,101,0,110,118,50,48,98,101,0,121,118,121,117,52,50,50,0,118,100,97,0,121,97,49,54,98,101,0,121,97,49,54,108,101,0,113,115,118,0,109,109,97,108,0,100,51,100,49,49,118,97,95,118,108,100,0,114,103,98,97,54,52,98,101,0,114,103,98,97,54,52,108,101,0,98,103,114,97,54,52,98,101,0,98,103,114,97,54,52,108,101,0,48,114,103,98,0,114,103,98,48,0,48,98,103,114,0,98,103,114,48,0,121,117,118,97,52,52,52,112,0,121,117,118,97,52,50,50,112,0,121,117,118,52,50,48,112,49,50,98,101,0,121,117,118,52,50,48,112,49,50,108,101,0,121,117,118,52,50,48,112,49,52,98,101,0,121,117,118,52,50,48,112,49,52,108,101,0,121,117,118,52,50,50,112,49,50,98,101,0,121,117,118,52,50,50,112,49,50,108,101,0,121,117,118,52,50,50,112,49,52,98,101,0,121,117,118,52,50,50,112,49,52,108,101,0,121,117,118,52,52,52,112,49,50,98,101,0,121,117,118,52,52,52,112,49,50,108,101,0,121,117,118,52,52,52,112,49,52,98,101,0,121,117,118,52,52,52,112,49,52,108,101,0,103,98,114,112,49,50,98,101,0,103,98,114,112,49,50,108,101,0,103,98,114,112,49,52,98,101,0,103,98,114,112,49,52,108,101,0,103,98,114,97,112,0,103,98,114,97,112,49,54,98,101,0,103,98,114,97,112,49,54,108,101,0,121,117,118,106,52,49,49,112,0,98,97,121,101,114,95,98,103,103,114,56,0,98,97,121,101,114,95,114,103,103,98,56,0,98,97,121,101,114,95,103,98,114,103,56,0,98,97,121,101,114,95,103,114,98,103,56,0,98,97,121,101,114,95,98,103,103,114,49,54,108,101,0,98,97,121,101,114,95,98,103,103,114,49,54,98,101,0,98,97,121,101,114,95,114,103,103,98,49,54,108,101,0,98,97,121,101,114,95,114,103,103,98,49,54,98,101,0,98,97,121,101,114,95,103,98,114,103,49,54,108,101,0,98,97,121,101,114,95,103,98,114,103,49,54,98,101,0,98,97,121,101,114,95,103,114,98,103,49,54,108,101,0,98,97,121,101,114,95,103,114,98,103,49,54,98,101,0,121,117,118,52,52,48,112,49,48,108,101,0,121,117,118,52,52,48,112,49,48,98,101,0,121,117,118,52,52,48,112,49,50,108,101,0,121,117,118,52,52,48,112,49,50,98,101,0,97,121,117,118,54,52,108,101,0,97,121,117,118,54,52,98,101,0,118,105,100,101,111,116,111,111,108,98,111,120,95,118,108,100,0,114,103,98,51,50,0,98,103,114,51,50,0,37,115,37,115,0,108,101,0,47,100,101,118,47,117,114,97,110,100,111,109,0,47,100,101,118,47,114,97,110,100,111,109,0,65,115,115,101,114,116,105,111,110,32,37,115,32,102,97,105,108,101,100,32,97,116,32,37,115,58,37,100,10,0,115,105,122,101,111,102,40,116,109,112,41,32,62,61,32,97,118,95,115,104,97,95,115,105,122,101,0,108,105,98,97,118,117,116,105,108,47,114,97,110,100,111,109,95,115,101,101,100,46,99,0,0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255,128,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,2,3,4,5,6,7,8,9,255,255,255,255,255,255,255,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,255,255,255,255,255,255,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,1,2,4,7,3,6,5,0,105,110,102,105,110,105,116,121,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,46,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE+100945);var tempDoublePtr=STATICTOP;STATICTOP+=16;var ___tm_timezone=allocate(intArrayFromString("GMT"),"i8",ALLOC_STATIC);function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+40>>2]=___tm_timezone;return tmPtr}Module["_i64Subtract"]=_i64Subtract;function ___setErrNo(value){if(Module["___errno_location"])HEAP32[Module["___errno_location"]()>>2]=value;return value}var ERRNO_CODES={EPERM:1,ENOENT:2,ESRCH:3,EINTR:4,EIO:5,ENXIO:6,E2BIG:7,ENOEXEC:8,EBADF:9,ECHILD:10,EAGAIN:11,EWOULDBLOCK:11,ENOMEM:12,EACCES:13,EFAULT:14,ENOTBLK:15,EBUSY:16,EEXIST:17,EXDEV:18,ENODEV:19,ENOTDIR:20,EISDIR:21,EINVAL:22,ENFILE:23,EMFILE:24,ENOTTY:25,ETXTBSY:26,EFBIG:27,ENOSPC:28,ESPIPE:29,EROFS:30,EMLINK:31,EPIPE:32,EDOM:33,ERANGE:34,ENOMSG:42,EIDRM:43,ECHRNG:44,EL2NSYNC:45,EL3HLT:46,EL3RST:47,ELNRNG:48,EUNATCH:49,ENOCSI:50,EL2HLT:51,EDEADLK:35,ENOLCK:37,EBADE:52,EBADR:53,EXFULL:54,ENOANO:55,EBADRQC:56,EBADSLT:57,EDEADLOCK:35,EBFONT:59,ENOSTR:60,ENODATA:61,ETIME:62,ENOSR:63,ENONET:64,ENOPKG:65,EREMOTE:66,ENOLINK:67,EADV:68,ESRMNT:69,ECOMM:70,EPROTO:71,EMULTIHOP:72,EDOTDOT:73,EBADMSG:74,ENOTUNIQ:76,EBADFD:77,EREMCHG:78,ELIBACC:79,ELIBBAD:80,ELIBSCN:81,ELIBMAX:82,ELIBEXEC:83,ENOSYS:38,ENOTEMPTY:39,ENAMETOOLONG:36,ELOOP:40,EOPNOTSUPP:95,EPFNOSUPPORT:96,ECONNRESET:104,ENOBUFS:105,EAFNOSUPPORT:97,EPROTOTYPE:91,ENOTSOCK:88,ENOPROTOOPT:92,ESHUTDOWN:108,ECONNREFUSED:111,EADDRINUSE:98,ECONNABORTED:103,ENETUNREACH:101,ENETDOWN:100,ETIMEDOUT:110,EHOSTDOWN:112,EHOSTUNREACH:113,EINPROGRESS:115,EALREADY:114,EDESTADDRREQ:89,EMSGSIZE:90,EPROTONOSUPPORT:93,ESOCKTNOSUPPORT:94,EADDRNOTAVAIL:99,ENETRESET:102,EISCONN:106,ENOTCONN:107,ETOOMANYREFS:109,EUSERS:87,EDQUOT:122,ESTALE:116,ENOTSUP:95,ENOMEDIUM:123,EILSEQ:84,EOVERFLOW:75,ECANCELED:125,ENOTRECOVERABLE:131,EOWNERDEAD:130,ESTRPIPE:86};function _sysconf(name){switch(name){case 30:return PAGE_SIZE;case 85:return totalMemory/PAGE_SIZE;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1e3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:{if(typeof navigator==="object")return navigator["hardwareConcurrency"]||1;return 1}}___setErrNo(ERRNO_CODES.EINVAL);return-1}var _acos=Math_acos;Module["_memset"]=_memset;Module["_bitshift64Shl"]=_bitshift64Shl;function _abort(){Module["abort"]()}var _tan=Math_tan;var _asin=Math_asin;var _llvm_fabs_f64=Math_abs;Module["_i64Add"]=_i64Add;var _fabs=Math_abs;function _clock(){if(_clock.start===undefined)_clock.start=Date.now();return(Date.now()-_clock.start)*(1e6/1e3)|0}var _tzname=STATICTOP;STATICTOP+=16;var _daylight=STATICTOP;STATICTOP+=16;var _timezone=STATICTOP;STATICTOP+=16;function _tzset(){if(_tzset.called)return;_tzset.called=true;HEAP32[_timezone>>2]=-(new Date).getTimezoneOffset()*60;var winter=new Date(2e3,0,1);var summer=new Date(2e3,6,1);HEAP32[_daylight>>2]=Number(winter.getTimezoneOffset()!=summer.getTimezoneOffset());function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocate(intArrayFromString(winterName),"i8",ALLOC_NORMAL);var summerNamePtr=allocate(intArrayFromString(summerName),"i8",ALLOC_NORMAL);if(summer.getTimezoneOffset()<winter.getTimezoneOffset()){HEAP32[_tzname>>2]=winterNamePtr;HEAP32[_tzname+4>>2]=summerNamePtr}else{HEAP32[_tzname>>2]=summerNamePtr;HEAP32[_tzname+4>>2]=winterNamePtr}}function _mktime(tmPtr){_tzset();var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=(new Date(2e3,6,1)).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;return date.getTime()/1e3|0}var SYSCALLS={varargs:0,get:(function(varargs){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret}),getStr:(function(){var ret=Pointer_stringify(SYSCALLS.get());return ret}),get64:(function(){var low=SYSCALLS.get(),high=SYSCALLS.get();if(low>=0)assert(high===0);else assert(high===-1);return low}),getZero:(function(){assert(SYSCALLS.get()===0)})};function ___syscall6(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD();FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var _sin=Math_sin;Module["_bitshift64Ashr"]=_bitshift64Ashr;Module["_bitshift64Lshr"]=_bitshift64Lshr;var _atan=Math_atan;function _pthread_cleanup_push(routine,arg){__ATEXIT__.push((function(){Runtime.dynCall("vi",routine,[arg])}));_pthread_cleanup_push.level=__ATEXIT__.length}var _environ=STATICTOP;STATICTOP+=16;function ___buildEnvironment(env){var MAX_ENV_VALUES=64;var TOTAL_ENV_SIZE=1024;var poolPtr;var envPtr;if(!___buildEnvironment.called){___buildEnvironment.called=true;ENV["USER"]=ENV["LOGNAME"]="web_user";ENV["PATH"]="/";ENV["PWD"]="/";ENV["HOME"]="/home/web_user";ENV["LANG"]="C";ENV["_"]=Module["thisProgram"];poolPtr=allocate(TOTAL_ENV_SIZE,"i8",ALLOC_STATIC);envPtr=allocate(MAX_ENV_VALUES*4,"i8*",ALLOC_STATIC);HEAP32[envPtr>>2]=poolPtr;HEAP32[_environ>>2]=envPtr}else{envPtr=HEAP32[_environ>>2];poolPtr=HEAP32[envPtr>>2]}var strings=[];var totalSize=0;for(var key in env){if(typeof env[key]==="string"){var line=key+"="+env[key];strings.push(line);totalSize+=line.length}}if(totalSize>TOTAL_ENV_SIZE){throw new Error("Environment size exceeded TOTAL_ENV_SIZE!")}var ptrSize=4;for(var i=0;i<strings.length;i++){var line=strings[i];writeAsciiToMemory(line,poolPtr);HEAP32[envPtr+i*ptrSize>>2]=poolPtr;poolPtr+=line.length+1}HEAP32[envPtr+strings.length*ptrSize>>2]=0}var ENV={};function _getenv(name){if(name===0)return 0;name=Pointer_stringify(name);if(!ENV.hasOwnProperty(name))return 0;if(_getenv.ret)_free(_getenv.ret);_getenv.ret=allocate(intArrayFromString(ENV[name]),"i8",ALLOC_NORMAL);return _getenv.ret}function _gettimeofday(ptr){var now=Date.now();HEAP32[ptr>>2]=now/1e3|0;HEAP32[ptr+4>>2]=now%1e3*1e3|0;return 0}function _localtime_r(time,tmPtr){_tzset();var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var start=new Date(date.getFullYear(),0,1);var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var summerOffset=(new Date(2e3,6,1)).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=date.getTimezoneOffset()==Math.min(winterOffset,summerOffset)|0;HEAP32[tmPtr+32>>2]=dst;var zonePtr=HEAP32[_tzname+(dst?Runtime.QUANTUM_SIZE:0)>>2];HEAP32[tmPtr+40>>2]=zonePtr;return tmPtr}function ___syscall3(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),buf=SYSCALLS.get(),count=SYSCALLS.get();return FS.read(stream,HEAP8,buf,count)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall5(which,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(),flags=SYSCALLS.get(),mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function _emscripten_memcpy_big(dest,src,num){HEAPU8.set(HEAPU8.subarray(src,src+num),dest);return dest}Module["_memcpy"]=_memcpy;var _log=Math_log;var _cos=Math_cos;var _llvm_pow_f64=Math_pow;function _sbrk(bytes){var self=_sbrk;if(!self.called){DYNAMICTOP=alignMemoryPage(DYNAMICTOP);self.called=true;assert(Runtime.dynamicAlloc);self.alloc=Runtime.dynamicAlloc;Runtime.dynamicAlloc=(function(){abort("cannot dynamically allocate, sbrk now has control")})}var ret=DYNAMICTOP;if(bytes!=0){var success=self.alloc(bytes);if(!success)return-1>>>0}return ret}Module["_memmove"]=_memmove;function _pthread_cleanup_pop(){assert(_pthread_cleanup_push.level==__ATEXIT__.length,"cannot pop if something else added meanwhile!");__ATEXIT__.pop();_pthread_cleanup_push.level=__ATEXIT__.length}function ___syscall54(which,varargs){SYSCALLS.varargs=varargs;try{return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}Module["_llvm_bswap_i32"]=_llvm_bswap_i32;function ___syscall221(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),cmd=SYSCALLS.get();switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-ERRNO_CODES.EINVAL}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd};case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0};case 12:case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0};case 13:case 14:case 13:case 14:return 0;case 16:case 8:return-ERRNO_CODES.EINVAL;case 9:___setErrNo(ERRNO_CODES.EINVAL);return-1;default:{return-ERRNO_CODES.EINVAL}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var _exp=Math_exp;function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _pthread_self(){return 0}function ___syscall140(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(),offset_high=SYSCALLS.get(),offset_low=SYSCALLS.get(),result=SYSCALLS.get(),whence=SYSCALLS.get();var offset=offset_low;assert(offset_high===0);FS.llseek(stream,offset,whence);HEAP32[result>>2]=stream.position;if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___syscall146(which,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.get(),iov=SYSCALLS.get(),iovcnt=SYSCALLS.get();var ret=0;if(!___syscall146.buffer){___syscall146.buffers=[null,[],[]];___syscall146.printChar=(function(stream,curr){var buffer=___syscall146.buffers[stream];assert(buffer);if(curr===0||curr===10){(stream===1?Module["print"]:Module["printErr"])(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}})}for(var i=0;i<iovcnt;i++){var ptr=HEAP32[iov+i*8>>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;j<len;j++){___syscall146.printChar(stream,HEAPU8[ptr+j])}ret+=len}return ret}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}___buildEnvironment(ENV);__ATEXIT__.push((function(){var fflush=Module["_fflush"];if(fflush)fflush(0);var printChar=___syscall146.printChar;if(!printChar)return;var buffers=___syscall146.buffers;if(buffers[1].length)printChar(1,10);if(buffers[2].length)printChar(2,10)}));STACK_BASE=STACKTOP=Runtime.alignMemory(STATICTOP);staticSealed=true;STACK_MAX=STACK_BASE+TOTAL_STACK;DYNAMIC_BASE=DYNAMICTOP=Runtime.alignMemory(STACK_MAX);var cttz_i8=allocate([8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0],"i8",ALLOC_DYNAMIC);function invoke_didd(index,a1,a2,a3){try{return Module["dynCall_didd"](index,a1,a2,a3)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){try{Module["dynCall_viiiiiiiiii"](index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){try{Module["dynCall_viiiii"](index,a1,a2,a3,a4,a5)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_did(index,a1,a2){try{return Module["dynCall_did"](index,a1,a2)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_vi(index,a1){try{Module["dynCall_vi"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_dd(index,a1){try{return Module["dynCall_dd"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_vii(index,a1,a2){try{Module["dynCall_vii"](index,a1,a2)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){try{return Module["dynCall_iiiiiii"](index,a1,a2,a3,a4,a5,a6)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){try{Module["dynCall_viiiiiiiii"](index,a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_iiii(index,a1,a2,a3){try{return Module["dynCall_iiii"](index,a1,a2,a3)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_ii(index,a1){try{return Module["dynCall_ii"](index,a1)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viii(index,a1,a2,a3){try{Module["dynCall_viii"](index,a1,a2,a3)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){try{Module["dynCall_viiiiiiii"](index,a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_iiiii(index,a1,a2,a3,a4){try{return Module["dynCall_iiiii"](index,a1,a2,a3,a4)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){try{Module["dynCall_viiiiii"](index,a1,a2,a3,a4,a5,a6)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_iii(index,a1,a2){try{return Module["dynCall_iii"](index,a1,a2)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){try{return Module["dynCall_iiiiii"](index,a1,a2,a3,a4,a5)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}function invoke_viiii(index,a1,a2,a3,a4){try{Module["dynCall_viiii"](index,a1,a2,a3,a4)}catch(e){if(typeof e!=="number"&&e!=="longjmp")throw e;asm["setThrew"](1,0)}}Module.asmGlobalArg={"Math":Math,"Int8Array":Int8Array,"Int16Array":Int16Array,"Int32Array":Int32Array,"Uint8Array":Uint8Array,"Uint16Array":Uint16Array,"Uint32Array":Uint32Array,"Float32Array":Float32Array,"Float64Array":Float64Array,"NaN":NaN,"Infinity":Infinity};Module.asmLibraryArg={"abort":abort,"assert":assert,"invoke_didd":invoke_didd,"invoke_viiiiiiiiii":invoke_viiiiiiiiii,"invoke_viiiii":invoke_viiiii,"invoke_did":invoke_did,"invoke_vi":invoke_vi,"invoke_dd":invoke_dd,"invoke_vii":invoke_vii,"invoke_iiiiiii":invoke_iiiiiii,"invoke_viiiiiiiii":invoke_viiiiiiiii,"invoke_iiii":invoke_iiii,"invoke_ii":invoke_ii,"invoke_viii":invoke_viii,"invoke_viiiiiiii":invoke_viiiiiiii,"invoke_iiiii":invoke_iiiii,"invoke_viiiiii":invoke_viiiiii,"invoke_iii":invoke_iii,"invoke_iiiiii":invoke_iiiiii,"invoke_viiii":invoke_viiii,"_fabs":_fabs,"_pthread_cleanup_pop":_pthread_cleanup_pop,"_sin":_sin,"_exp":_exp,"_llvm_pow_f64":_llvm_pow_f64,"_acos":_acos,"_log":_log,"___syscall6":___syscall6,"_llvm_fabs_f64":_llvm_fabs_f64,"_pthread_cleanup_push":_pthread_cleanup_push,"_asin":_asin,"___buildEnvironment":___buildEnvironment,"_localtime_r":_localtime_r,"_tzset":_tzset,"___setErrNo":___setErrNo,"_sbrk":_sbrk,"_atan":_atan,"_emscripten_memcpy_big":_emscripten_memcpy_big,"_sysconf":_sysconf,"_mktime":_mktime,"_clock":_clock,"___syscall221":___syscall221,"_cos":_cos,"_pthread_self":_pthread_self,"_getenv":_getenv,"___syscall54":___syscall54,"_tan":_tan,"_gmtime_r":_gmtime_r,"___syscall3":___syscall3,"_abort":_abort,"___syscall5":___syscall5,"_time":_time,"_gettimeofday":_gettimeofday,"___syscall140":___syscall140,"___syscall146":___syscall146,"STACKTOP":STACKTOP,"STACK_MAX":STACK_MAX,"tempDoublePtr":tempDoublePtr,"ABORT":ABORT,"cttz_i8":cttz_i8};// EMSCRIPTEN_START_ASM
var asm=(function(global,env,buffer) {
"use asm";var a=new global.Int8Array(buffer);var b=new global.Int16Array(buffer);var c=new global.Int32Array(buffer);var d=new global.Uint8Array(buffer);var e=new global.Uint16Array(buffer);var f=new global.Uint32Array(buffer);var g=new global.Float32Array(buffer);var h=new global.Float64Array(buffer);var i=env.STACKTOP|0;var j=env.STACK_MAX|0;var k=env.tempDoublePtr|0;var l=env.ABORT|0;var m=env.cttz_i8|0;var n=0;var o=0;var p=0;var q=0;var r=global.NaN,s=global.Infinity;var t=0,u=0,v=0,w=0,x=0.0,y=0,z=0,A=0,B=0.0;var C=0;var D=0;var E=0;var F=0;var G=0;var H=0;var I=0;var J=0;var K=0;var L=0;var M=global.Math.floor;var N=global.Math.abs;var O=global.Math.sqrt;var P=global.Math.pow;var Q=global.Math.cos;var R=global.Math.sin;var S=global.Math.tan;var T=global.Math.acos;var U=global.Math.asin;var V=global.Math.atan;var W=global.Math.atan2;var X=global.Math.exp;var Y=global.Math.log;var Z=global.Math.ceil;var _=global.Math.imul;var $=global.Math.min;var aa=global.Math.clz32;var ba=env.abort;var ca=env.assert;var da=env.invoke_didd;var ea=env.invoke_viiiiiiiiii;var fa=env.invoke_viiiii;var ga=env.invoke_did;var ha=env.invoke_vi;var ia=env.invoke_dd;var ja=env.invoke_vii;var ka=env.invoke_iiiiiii;var la=env.invoke_viiiiiiiii;var ma=env.invoke_iiii;var na=env.invoke_ii;var oa=env.invoke_viii;var pa=env.invoke_viiiiiiii;var qa=env.invoke_iiiii;var ra=env.invoke_viiiiii;var sa=env.invoke_iii;var ta=env.invoke_iiiiii;var ua=env.invoke_viiii;var va=env._fabs;var wa=env._pthread_cleanup_pop;var xa=env._sin;var ya=env._exp;var za=env._llvm_pow_f64;var Aa=env._acos;var Ba=env._log;var Ca=env.___syscall6;var Da=env._llvm_fabs_f64;var Ea=env._pthread_cleanup_push;var Fa=env._asin;var Ga=env.___buildEnvironment;var Ha=env._localtime_r;var Ia=env._tzset;var Ja=env.___setErrNo;var Ka=env._sbrk;var La=env._atan;var Ma=env._emscripten_memcpy_big;var Na=env._sysconf;var Oa=env._mktime;var Pa=env._clock;var Qa=env.___syscall221;var Ra=env._cos;var Sa=env._pthread_self;var Ta=env._getenv;var Ua=env.___syscall54;var Va=env._tan;var Wa=env._gmtime_r;var Xa=env.___syscall3;var Ya=env._abort;var Za=env.___syscall5;var _a=env._time;var $a=env._gettimeofday;var ab=env.___syscall140;var bb=env.___syscall146;var cb=0.0;
// EMSCRIPTEN_START_FUNCS
function Bo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b+e|0,8,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{j=b<<3;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+j|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=_(b,e)|0;c[a+l>>2]=(m|k)-((m^k)>>>1&2139062143);j=j|4;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=g+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[a+(l+4)>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Co(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+336|0;f=h+272|0;g=h+208|0;Yn(f,b+e|0,8,e);eo(g,h,b,8,8,e);b=0;do{l=b<<3;k=f+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;j=_(b,e)|0;c[a+j>>2]=(m|k)-((m^k)>>>1&2139062143);l=l|4;k=f+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=g+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+(j+4)>>2]=(l|k)-((l^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Do(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b+e|0,8,e);b=b+(0-(e<<1))+1|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{j=b<<3;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+j|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=_(b,e)|0;c[a+l>>2]=(m|k)-((m^k)>>>1&2139062143);j=j|4;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=g+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[a+(l+4)>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Eo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+e|0;b=a+e|0;c[b>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=f+e|0;b=b+e|0;c[b>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;a=a+e|0;c[b+e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;return}function Fo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+16|0;g=f;Go(g,b,4,e);h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2139062143);h=b+e|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;j=c[g+4>>2]|0;c[a+e>>2]=(j|h)-((j^h)>>>1&2139062143);h=e<<1;j=b+h|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=c[g+8>>2]|0;c[a+h>>2]=(k|j)-((k^j)>>>1&2139062143);e=e*3|0;b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+12>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Go(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;g=0;while(1){i=c+1|0;l=c+-1|0;h=c+2|0;m=_((d[h>>0]|0)+(d[l>>0]|0)|0,-5)|0;j=c+3|0;m=(((d[i>>0]|0)+(d[c>>0]|0)|0)*20|0)+16+(d[c+-2>>0]|0)+m+(d[j>>0]|0)>>5;a[b>>0]=m>>>0>255?0-m>>31:m;m=_((d[j>>0]|0)+(d[c>>0]|0)|0,-5)|0;k=c+4|0;m=(((d[h>>0]|0)+(d[i>>0]|0)|0)*20|0)+16+(d[l>>0]|0)+m+(d[k>>0]|0)>>5;a[b+1>>0]=m>>>0>255?0-m>>31:m;m=_((d[k>>0]|0)+(d[i>>0]|0)|0,-5)|0;l=c+5|0;m=(((d[j>>0]|0)+(d[h>>0]|0)|0)*20|0)+16+(d[c>>0]|0)+m+(d[l>>0]|0)>>5;a[b+2>>0]=m>>>0>255?0-m>>31:m;h=_((d[l>>0]|0)+(d[h>>0]|0)|0,-5)|0;h=(((d[k>>0]|0)+(d[j>>0]|0)|0)*20|0)+16+(d[i>>0]|0)+h+(d[c+6>>0]|0)>>5;a[b+3>>0]=h>>>0>255?0-h>>31:h;g=g+1|0;if((g|0)==4)break;else{b=b+e|0;c=c+f|0}}return}function Ho(a,b,c){a=a|0;b=b|0;c=c|0;Go(a,b,c,c);return}function Io(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+16|0;g=f;Go(g,b,4,e);b=b+1|0;h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2139062143);h=b+e|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;j=c[g+4>>2]|0;c[a+e>>2]=(j|h)-((j^h)>>>1&2139062143);h=e<<1;j=b+h|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=c[g+8>>2]|0;c[a+h>>2]=(k|j)-((k^j)>>>1&2139062143);e=e*3|0;b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+12>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Jo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+64|0;n=f+16|0;g=f;m=n+8|0;h=e<<1;o=b+(0-h)|0;c[n>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;c[n+4>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;l=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m>>2]=l;o=o+e|0;k=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+12>>2]=k;o=o+e|0;j=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+16>>2]=j;o=o+e|0;b=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+20>>2]=b;o=o+e|0;c[n+24>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;c[n+28>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;c[n+32>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;Ko(g,m,4);m=c[g>>2]|0;c[a>>2]=(m|l)-((m^l)>>>1&2139062143);l=c[g+4>>2]|0;c[a+e>>2]=(l|k)-((l^k)>>>1&2139062143);k=c[g+8>>2]|0;c[a+h>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Ko(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;g=e<<1;h=e*3|0;f=0;while(1){p=d[c+-4>>0]|0;o=d[c>>0]|0;n=d[c+4>>0]|0;i=d[c+8>>0]|0;k=d[c+12>>0]|0;l=d[c+16>>0]|0;j=d[c+20>>0]|0;m=d[c+24>>0]|0;q=(d[c+-8>>0]|0)+16+k+((n+o|0)*20|0)+(_(i+p|0,-5)|0)>>5;a[b>>0]=q>>>0>255?0-q>>31:q;p=p+16+l+((i+n|0)*20|0)+(_(k+o|0,-5)|0)>>5;a[b+e>>0]=p>>>0>255?0-p>>31:p;o=o+16+j+((k+i|0)*20|0)+(_(l+n|0,-5)|0)>>5;a[b+g>>0]=o>>>0>255?0-o>>31:o;i=n+16+m+((l+k|0)*20|0)+(_(j+i|0,-5)|0)>>5;a[b+h>>0]=i>>>0>255?0-i>>31:i;f=f+1|0;if((f|0)==4)break;else{b=b+1|0;c=c+1|0}}return}function Lo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;l=f+32|0;h=f+16|0;g=f;k=l+8|0;Go(h,b,4,e);j=e<<1;b=b+(0-j)|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(g,k,4);b=c[h>>2]|0;k=c[g>>2]|0;c[a>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+4>>2]|0;k=c[g+4>>2]|0;c[a+e>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+8>>2]|0;k=c[g+8>>2]|0;c[a+j>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+12>>2]|0;g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Mo(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+112|0;g=e+16|0;f=e;Go(g,b,4,d);No(f,e+32|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[g+4>>2]|0;h=c[f+4>>2]|0;c[a+d>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[g+8>>2]|0;h=c[f+8>>2]|0;c[a+(d<<1)>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[g+12>>2]|0;f=c[f+12>>2]|0;c[a+(d*3|0)>>2]=(f|b)-((f^b)>>>1&2139062143);i=e;return}function No(c,e,f,g,h){c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;i=e;j=0;f=f+(0-(h<<1))|0;while(1){n=f+1|0;o=f+-1|0;k=f+2|0;p=_((d[k>>0]|0)+(d[o>>0]|0)|0,-5)|0;l=f+3|0;b[i>>1]=(d[f+-2>>0]|0)+(((d[n>>0]|0)+(d[f>>0]|0)|0)*20|0)+p+(d[l>>0]|0);p=_((d[l>>0]|0)+(d[f>>0]|0)|0,-5)|0;m=f+4|0;b[i+2>>1]=(d[o>>0]|0)+(((d[k>>0]|0)+(d[n>>0]|0)|0)*20|0)+p+(d[m>>0]|0);p=_((d[m>>0]|0)+(d[n>>0]|0)|0,-5)|0;o=f+5|0;b[i+4>>1]=(d[f>>0]|0)+(((d[l>>0]|0)+(d[k>>0]|0)|0)*20|0)+p+(d[o>>0]|0);k=_((d[o>>0]|0)+(d[k>>0]|0)|0,-5)|0;b[i+6>>1]=(d[n>>0]|0)+(((d[m>>0]|0)+(d[l>>0]|0)|0)*20|0)+k+(d[f+6>>0]|0);j=j+1|0;if((j|0)==9)break;else{i=i+8|0;f=f+h|0}}j=g<<1;h=g*3|0;i=e+16|0;f=0;while(1){q=b[i+-8>>1]|0;e=b[i>>1]|0;k=b[i+8>>1]|0;p=b[i+16>>1]|0;n=b[i+24>>1]|0;m=b[i+32>>1]|0;o=b[i+40>>1]|0;l=b[i+48>>1]|0;r=(b[i+-16>>1]|0)+512+n+((k+e|0)*20|0)+(_(p+q|0,-5)|0)>>10;a[c>>0]=r>>>0>255?0-r>>31:r;q=q+512+m+((p+k|0)*20|0)+(_(n+e|0,-5)|0)>>10;a[c+g>>0]=q>>>0>255?0-q>>31:q;e=e+512+o+((n+p|0)*20|0)+(_(m+k|0,-5)|0)>>10;a[c+j>>0]=e>>>0>255?0-e>>31:e;p=k+512+l+((m+n|0)*20|0)+(_(o+p|0,-5)|0)>>10;a[c+h>>0]=p>>>0>255?0-p>>31:p;f=f+1|0;if((f|0)==4)break;else{i=i+2|0;c=c+1|0}}return}function Oo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;l=f+32|0;h=f+16|0;g=f;k=l+8|0;Go(h,b,4,e);j=e<<1;b=b+(0-j)+1|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(g,k,4);b=c[h>>2]|0;k=c[g>>2]|0;c[a>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+4>>2]|0;k=c[g+4>>2]|0;c[a+e>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+8>>2]|0;k=c[g+8>>2]|0;c[a+j>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+12>>2]|0;g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Po(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;f=i;i=i+48|0;h=f;g=h+8|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(a,g,e);i=f;return}function Qo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+144|0;l=f+32|0;h=f+16|0;g=f;k=l+8|0;j=e<<1;m=b+(0-j)|0;c[l>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+4>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+12>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+16>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+20>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+24>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+28>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+32>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;Ko(h,k,4);No(g,f+72|0,b,4,e);b=c[h>>2]|0;k=c[g>>2]|0;c[a>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+4>>2]|0;k=c[g+4>>2]|0;c[a+e>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+8>>2]|0;k=c[g+8>>2]|0;c[a+j>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+12>>2]|0;g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Ro(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+80|0;No(a,d,b,c,c);i=d;return}function So(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+144|0;l=f+32|0;h=f+16|0;g=f;k=l+8|0;j=e<<1;m=b+(0-j)+1|0;c[l>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+4>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+12>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+16>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+20>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+24>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+28>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;m=m+e|0;c[l+32>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;Ko(h,k,4);No(g,f+72|0,b,4,e);b=c[h>>2]|0;k=c[g>>2]|0;c[a>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+4>>2]|0;k=c[g+4>>2]|0;c[a+e>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+8>>2]|0;k=c[g+8>>2]|0;c[a+j>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+12>>2]|0;g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function To(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+64|0;n=f+16|0;g=f;m=n+8|0;h=e<<1;o=b+(0-h)|0;c[n>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;c[n+4>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;l=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+12>>2]=l;o=o+e|0;k=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+16>>2]=k;o=o+e|0;j=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+20>>2]=j;o=o+e|0;b=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[n+24>>2]=b;o=o+e|0;c[n+28>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;o=o+e|0;c[n+32>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;Ko(g,m,4);m=c[g>>2]|0;c[a>>2]=(m|l)-((m^l)>>>1&2139062143);l=c[g+4>>2]|0;c[a+e>>2]=(l|k)-((l^k)>>>1&2139062143);k=c[g+8>>2]|0;c[a+h>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Uo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;l=f+32|0;h=f+16|0;g=f;k=l+8|0;Go(h,b+e|0,4,e);j=e<<1;b=b+(0-j)|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(g,k,4);b=c[h>>2]|0;k=c[g>>2]|0;c[a>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+4>>2]|0;k=c[g+4>>2]|0;c[a+e>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+8>>2]|0;k=c[g+8>>2]|0;c[a+j>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+12>>2]|0;g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Vo(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+112|0;g=e+16|0;f=e;Go(g,b+d|0,4,d);No(f,e+32|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[g+4>>2]|0;h=c[f+4>>2]|0;c[a+d>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[g+8>>2]|0;h=c[f+8>>2]|0;c[a+(d<<1)>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[g+12>>2]|0;f=c[f+12>>2]|0;c[a+(d*3|0)>>2]=(f|b)-((f^b)>>>1&2139062143);i=e;return}function Wo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;l=f+32|0;h=f+16|0;g=f;k=l+8|0;Go(h,b+e|0,4,e);j=e<<1;b=b+(0-j)+1|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[l+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(g,k,4);b=c[h>>2]|0;k=c[g>>2]|0;c[a>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+4>>2]|0;k=c[g+4>>2]|0;c[a+e>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+8>>2]|0;k=c[g+8>>2]|0;c[a+j>>2]=(k|b)-((k^b)>>>1&2139062143);b=c[h+12>>2]|0;g=c[g+12>>2]|0;c[a+(e*3|0)>>2]=(g|b)-((g^b)>>>1&2139062143);i=f;return}function Xo(a,c,e){a=a|0;c=c|0;e=e|0;b[a>>1]=d[c>>0]|d[c+1>>0]<<8;c=c+e|0;b[a+e>>1]=d[c>>0]|d[c+1>>0]<<8;return}function Yo(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;Zo(g,c,2,e);h=d[c>>0]|d[c+1>>0]<<8;j=b[g>>1]|0;b[a>>1]=((j|h)&65535)-(((j^h)&65535)>>>1&32639);c=c+e|0;c=d[c>>0]|d[c+1>>0]<<8;g=b[g+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function Zo(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;i=c+1|0;h=c+-1|0;j=c+2|0;k=_((d[j>>0]|0)+(d[h>>0]|0)|0,-5)|0;g=c+3|0;k=(((d[i>>0]|0)+(d[c>>0]|0)|0)*20|0)+16+(d[c+-2>>0]|0)+k+(d[g>>0]|0)>>5;a[b>>0]=k>>>0>255?0-k>>31:k;g=_((d[g>>0]|0)+(d[c>>0]|0)|0,-5)|0;g=(((d[j>>0]|0)+(d[i>>0]|0)|0)*20|0)+16+(d[h>>0]|0)+g+(d[c+4>>0]|0)>>5;a[b+1>>0]=g>>>0>255?0-g>>31:g;b=b+e|0;f=c+f|0;g=f+1|0;e=f+-1|0;h=f+2|0;i=_((d[h>>0]|0)+(d[e>>0]|0)|0,-5)|0;c=f+3|0;i=(((d[g>>0]|0)+(d[f>>0]|0)|0)*20|0)+16+(d[f+-2>>0]|0)+i+(d[c>>0]|0)>>5;a[b>>0]=i>>>0>255?0-i>>31:i;c=_((d[c>>0]|0)+(d[f>>0]|0)|0,-5)|0;f=(((d[h>>0]|0)+(d[g>>0]|0)|0)*20|0)+16+(d[e>>0]|0)+c+(d[f+4>>0]|0)>>5;a[b+1>>0]=f>>>0>255?0-f>>31:f;return}function _o(a,b,c){a=a|0;b=b|0;c=c|0;Zo(a,b,c,c);return}function $o(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;Zo(g,c,2,e);c=c+1|0;h=d[c>>0]|d[c+1>>0]<<8;j=b[g>>1]|0;b[a>>1]=((j|h)&65535)-(((j^h)&65535)>>>1&32639);c=c+e|0;c=d[c>>0]|d[c+1>>0]<<8;g=b[g+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function ap(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;n=c+(0-(e<<1))|0;i=d[n>>0]|d[n+1>>0]<<8;n=n+e|0;q=d[n>>0]|d[n+1>>0]<<8;o=(q&65535)>>>8&65535;n=n+e|0;g=d[n>>0]|d[n+1>>0]<<8;n=n+e|0;c=d[n>>0]|d[n+1>>0]<<8;n=n+e|0;p=d[n>>0]|d[n+1>>0]<<8;m=(p&65535)>>>8&65535;n=n+e|0;f=d[n>>0]|d[n+1>>0]<<8;k=(f&65535)>>>8&65535;n=n+e|0;n=d[n>>0]|d[n+1>>0]<<8;q=q&255;j=g&255;l=c&255;p=p&255;f=f&255;h=(i&255)+16+f+((l+j|0)*20|0)+(_(p+q|0,-5)|0)>>5;j=q+16+(n&255)+((p+l|0)*20|0)+(_(f+j|0,-5)|0)>>5;f=(g&65535)>>>8&65535;l=(c&65535)>>>8&65535;i=((i&65535)>>>8&65535)+16+k+((l+f|0)*20|0)+(_(m+o|0,-5)|0)>>5;f=o+16+((n&65535)>>>8&65535)+((m+l|0)*20|0)+(_(k+f|0,-5)|0)>>5;f=(j>>>0>255?0-j>>31:j)&255|((f>>>0>255?0-f>>31:f)&65535)<<8&65535;h=((i>>>0>255?0-i>>31:i)&65535)<<8&65535|(h>>>0>255?0-h>>31:h)&255;b[a>>1]=((h|g)&65535)-(((h^g)&65535)>>>1&32639);b[a+e>>1]=((f|c)&65535)-(((f^c)&65535)>>>1&32639);return}function bp(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;f=i;i=i+16|0;h=f;Zo(h,c,2,e);o=c+(0-(e<<1))|0;k=d[o>>0]|d[o+1>>0]<<8;o=o+e|0;t=d[o>>0]|d[o+1>>0]<<8;p=(t&65535)>>>8&65535;o=o+e|0;c=d[o>>0]|d[o+1>>0]<<8;g=(c&65535)>>>8&65535;o=o+e|0;r=d[o>>0]|d[o+1>>0]<<8;m=(r&65535)>>>8&65535;o=o+e|0;s=d[o>>0]|d[o+1>>0]<<8;n=(s&65535)>>>8&65535;o=o+e|0;q=d[o>>0]|d[o+1>>0]<<8;l=(q&65535)>>>8&65535;o=o+e|0;o=d[o>>0]|d[o+1>>0]<<8;t=t&255;c=c&255;r=r&255;s=s&255;q=q&255;j=(k&255)+16+q+((r+c|0)*20|0)+(_(s+t|0,-5)|0)>>5;c=t+16+(o&255)+((s+r|0)*20|0)+(_(q+c|0,-5)|0)>>5;k=((k&65535)>>>8&65535)+16+l+((m+g|0)*20|0)+(_(n+p|0,-5)|0)>>5;g=p+16+((o&65535)>>>8&65535)+((n+m|0)*20|0)+(_(l+g|0,-5)|0)>>5;g=(c>>>0>255?0-c>>31:c)&255|((g>>>0>255?0-g>>31:g)&65535)<<8&65535;c=b[h>>1]|0;j=((k>>>0>255?0-k>>31:k)&65535)<<8&65535|(j>>>0>255?0-j>>31:j)&255;b[a>>1]=((j|c)&65535)-(((j^c)&65535)>>>1&32639);c=b[h+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function cp(a,c,d){a=a|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+48|0;g=e+4|0;f=e;Zo(g,c,2,d);dp(f,e+8|0,c,2,d);c=b[g>>1]|0;h=b[f>>1]|0;b[a>>1]=((h|c)&65535)-(((h^c)&65535)>>>1&32639);c=b[g+2>>1]|0;f=b[f+2>>1]|0;b[a+d>>1]=((f|c)&65535)-(((f^c)&65535)>>>1&32639);i=e;return}function dp(c,e,f,g,h){c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0;i=e;j=0;f=f+(0-(h<<1))|0;while(1){l=f+1|0;n=f+-1|0;m=f+2|0;o=_((d[m>>0]|0)+(d[n>>0]|0)|0,-5)|0;k=f+3|0;b[i>>1]=(d[f+-2>>0]|0)+(((d[l>>0]|0)+(d[f>>0]|0)|0)*20|0)+o+(d[k>>0]|0);k=_((d[k>>0]|0)+(d[f>>0]|0)|0,-5)|0;b[i+2>>1]=(d[n>>0]|0)+(((d[m>>0]|0)+(d[l>>0]|0)|0)*20|0)+k+(d[f+4>>0]|0);j=j+1|0;if((j|0)==7)break;else{i=i+4|0;f=f+h|0}}m=b[e+4>>1]|0;o=b[e+8>>1]|0;n=b[e+12>>1]|0;l=b[e+16>>1]|0;j=b[e+20>>1]|0;k=b[e+24>>1]|0;h=(b[e>>1]|0)+512+j+((n+o|0)*20|0)+(_(l+m|0,-5)|0)>>10;a[c>>0]=h>>>0>255?0-h>>31:h;o=m+512+k+((l+n|0)*20|0)+(_(j+o|0,-5)|0)>>10;a[c+g>>0]=o>>>0>255?0-o>>31:o;o=c+1|0;j=b[e+6>>1]|0;n=b[e+10>>1]|0;l=b[e+14>>1]|0;k=b[e+18>>1]|0;m=b[e+22>>1]|0;h=b[e+26>>1]|0;e=(b[e+2>>1]|0)+512+m+((l+n|0)*20|0)+(_(k+j|0,-5)|0)>>10;a[o>>0]=e>>>0>255?0-e>>31:e;n=j+512+h+((k+l|0)*20|0)+(_(m+n|0,-5)|0)>>10;a[o+g>>0]=n>>>0>255?0-n>>31:n;return}function ep(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;f=i;i=i+16|0;h=f;Zo(h,c,2,e);o=c+(0-(e<<1))+1|0;k=d[o>>0]|d[o+1>>0]<<8;o=o+e|0;t=d[o>>0]|d[o+1>>0]<<8;p=(t&65535)>>>8&65535;o=o+e|0;c=d[o>>0]|d[o+1>>0]<<8;g=(c&65535)>>>8&65535;o=o+e|0;r=d[o>>0]|d[o+1>>0]<<8;m=(r&65535)>>>8&65535;o=o+e|0;s=d[o>>0]|d[o+1>>0]<<8;n=(s&65535)>>>8&65535;o=o+e|0;q=d[o>>0]|d[o+1>>0]<<8;l=(q&65535)>>>8&65535;o=o+e|0;o=d[o>>0]|d[o+1>>0]<<8;t=t&255;c=c&255;r=r&255;s=s&255;q=q&255;j=(k&255)+16+q+((r+c|0)*20|0)+(_(s+t|0,-5)|0)>>5;c=t+16+(o&255)+((s+r|0)*20|0)+(_(q+c|0,-5)|0)>>5;k=((k&65535)>>>8&65535)+16+l+((m+g|0)*20|0)+(_(n+p|0,-5)|0)>>5;g=p+16+((o&65535)>>>8&65535)+((n+m|0)*20|0)+(_(l+g|0,-5)|0)>>5;g=(c>>>0>255?0-c>>31:c)&255|((g>>>0>255?0-g>>31:g)&65535)<<8&65535;c=b[h>>1]|0;j=((k>>>0>255?0-k>>31:k)&65535)<<8&65535|(j>>>0>255?0-j>>31:j)&255;b[a>>1]=((j|c)&65535)-(((j^c)&65535)>>>1&32639);c=b[h+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function fp(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;j=c+(0-(e<<1))|0;l=d[j>>0]|d[j+1>>0]<<8;j=j+e|0;p=d[j>>0]|d[j+1>>0]<<8;k=(p&65535)>>>8&65535;j=j+e|0;c=d[j>>0]|d[j+1>>0]<<8;f=(c&65535)>>>8&65535;j=j+e|0;n=d[j>>0]|d[j+1>>0]<<8;h=(n&65535)>>>8&65535;j=j+e|0;o=d[j>>0]|d[j+1>>0]<<8;i=(o&65535)>>>8&65535;j=j+e|0;m=d[j>>0]|d[j+1>>0]<<8;g=(m&65535)>>>8&65535;j=j+e|0;j=d[j>>0]|d[j+1>>0]<<8;p=p&255;c=c&255;n=n&255;o=o&255;m=m&255;q=(l&255)+16+m+((n+c|0)*20|0)+(_(o+p|0,-5)|0)>>5;a[b>>0]=q>>>0>255?0-q>>31:q;c=p+16+(j&255)+((o+n|0)*20|0)+(_(m+c|0,-5)|0)>>5;a[b+e>>0]=c>>>0>255?0-c>>31:c;c=b+1|0;b=((l&65535)>>>8&65535)+16+g+((h+f|0)*20|0)+(_(i+k|0,-5)|0)>>5;a[c>>0]=b>>>0>255?0-b>>31:b;b=k+16+((j&65535)>>>8&65535)+((i+h|0)*20|0)+(_(g+f|0,-5)|0)>>5;a[c+e>>0]=b>>>0>255?0-b>>31:b;return}function gp(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;f=i;i=i+32|0;h=f;p=c+(0-(e<<1))|0;k=d[p>>0]|d[p+1>>0]<<8;p=p+e|0;u=d[p>>0]|d[p+1>>0]<<8;q=(u&65535)>>>8&65535;p=p+e|0;l=d[p>>0]|d[p+1>>0]<<8;g=(l&65535)>>>8&65535;p=p+e|0;s=d[p>>0]|d[p+1>>0]<<8;n=(s&65535)>>>8&65535;p=p+e|0;t=d[p>>0]|d[p+1>>0]<<8;o=(t&65535)>>>8&65535;p=p+e|0;r=d[p>>0]|d[p+1>>0]<<8;m=(r&65535)>>>8&65535;p=p+e|0;p=d[p>>0]|d[p+1>>0]<<8;u=u&255;l=l&255;s=s&255;t=t&255;r=r&255;j=(k&255)+16+r+((s+l|0)*20|0)+(_(t+u|0,-5)|0)>>5;l=u+16+(p&255)+((t+s|0)*20|0)+(_(r+l|0,-5)|0)>>5;k=((k&65535)>>>8&65535)+16+m+((n+g|0)*20|0)+(_(o+q|0,-5)|0)>>5;g=q+16+((p&65535)>>>8&65535)+((o+n|0)*20|0)+(_(m+g|0,-5)|0)>>5;g=(l>>>0>255?0-l>>31:l)&255|((g>>>0>255?0-g>>31:g)&65535)<<8&65535;dp(h,f+4|0,c,2,e);j=((k>>>0>255?0-k>>31:k)&65535)<<8&65535|(j>>>0>255?0-j>>31:j)&255;c=b[h>>1]|0;b[a>>1]=((j|c)&65535)-(((j^c)&65535)>>>1&32639);c=b[h+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function hp(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+32|0;dp(a,d,b,c,c);i=d;return}function ip(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;f=i;i=i+32|0;h=f;p=c+(0-(e<<1))+1|0;k=d[p>>0]|d[p+1>>0]<<8;p=p+e|0;u=d[p>>0]|d[p+1>>0]<<8;q=(u&65535)>>>8&65535;p=p+e|0;l=d[p>>0]|d[p+1>>0]<<8;g=(l&65535)>>>8&65535;p=p+e|0;s=d[p>>0]|d[p+1>>0]<<8;n=(s&65535)>>>8&65535;p=p+e|0;t=d[p>>0]|d[p+1>>0]<<8;o=(t&65535)>>>8&65535;p=p+e|0;r=d[p>>0]|d[p+1>>0]<<8;m=(r&65535)>>>8&65535;p=p+e|0;p=d[p>>0]|d[p+1>>0]<<8;u=u&255;l=l&255;s=s&255;t=t&255;r=r&255;j=(k&255)+16+r+((s+l|0)*20|0)+(_(t+u|0,-5)|0)>>5;l=u+16+(p&255)+((t+s|0)*20|0)+(_(r+l|0,-5)|0)>>5;k=((k&65535)>>>8&65535)+16+m+((n+g|0)*20|0)+(_(o+q|0,-5)|0)>>5;g=q+16+((p&65535)>>>8&65535)+((o+n|0)*20|0)+(_(m+g|0,-5)|0)>>5;g=(l>>>0>255?0-l>>31:l)&255|((g>>>0>255?0-g>>31:g)&65535)<<8&65535;dp(h,f+4|0,c,2,e);j=((k>>>0>255?0-k>>31:k)&65535)<<8&65535|(j>>>0>255?0-j>>31:j)&255;c=b[h>>1]|0;b[a>>1]=((j|c)&65535)-(((j^c)&65535)>>>1&32639);c=b[h+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function jp(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;n=c+(0-(e<<1))|0;i=d[n>>0]|d[n+1>>0]<<8;n=n+e|0;q=d[n>>0]|d[n+1>>0]<<8;o=(q&65535)>>>8&65535;n=n+e|0;j=d[n>>0]|d[n+1>>0]<<8;f=(j&65535)>>>8&65535;n=n+e|0;g=d[n>>0]|d[n+1>>0]<<8;n=n+e|0;c=d[n>>0]|d[n+1>>0]<<8;n=n+e|0;l=d[n>>0]|d[n+1>>0]<<8;k=(l&65535)>>>8&65535;n=n+e|0;n=d[n>>0]|d[n+1>>0]<<8;q=q&255;j=j&255;m=g&255;p=c&255;l=l&255;h=(i&255)+16+l+((m+j|0)*20|0)+(_(p+q|0,-5)|0)>>5;j=q+16+(n&255)+((p+m|0)*20|0)+(_(l+j|0,-5)|0)>>5;l=(g&65535)>>>8&65535;m=(c&65535)>>>8&65535;i=((i&65535)>>>8&65535)+16+k+((l+f|0)*20|0)+(_(m+o|0,-5)|0)>>5;f=o+16+((n&65535)>>>8&65535)+((m+l|0)*20|0)+(_(k+f|0,-5)|0)>>5;f=(j>>>0>255?0-j>>31:j)&255|((f>>>0>255?0-f>>31:f)&65535)<<8&65535;h=((i>>>0>255?0-i>>31:i)&65535)<<8&65535|(h>>>0>255?0-h>>31:h)&255;b[a>>1]=((h|g)&65535)-(((h^g)&65535)>>>1&32639);b[a+e>>1]=((f|c)&65535)-(((f^c)&65535)>>>1&32639);return}function kp(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;f=i;i=i+16|0;h=f;Zo(h,c+e|0,2,e);o=c+(0-(e<<1))|0;k=d[o>>0]|d[o+1>>0]<<8;o=o+e|0;t=d[o>>0]|d[o+1>>0]<<8;p=(t&65535)>>>8&65535;o=o+e|0;c=d[o>>0]|d[o+1>>0]<<8;g=(c&65535)>>>8&65535;o=o+e|0;r=d[o>>0]|d[o+1>>0]<<8;m=(r&65535)>>>8&65535;o=o+e|0;s=d[o>>0]|d[o+1>>0]<<8;n=(s&65535)>>>8&65535;o=o+e|0;q=d[o>>0]|d[o+1>>0]<<8;l=(q&65535)>>>8&65535;o=o+e|0;o=d[o>>0]|d[o+1>>0]<<8;t=t&255;c=c&255;r=r&255;s=s&255;q=q&255;j=(k&255)+16+q+((r+c|0)*20|0)+(_(s+t|0,-5)|0)>>5;c=t+16+(o&255)+((s+r|0)*20|0)+(_(q+c|0,-5)|0)>>5;k=((k&65535)>>>8&65535)+16+l+((m+g|0)*20|0)+(_(n+p|0,-5)|0)>>5;g=p+16+((o&65535)>>>8&65535)+((n+m|0)*20|0)+(_(l+g|0,-5)|0)>>5;g=(c>>>0>255?0-c>>31:c)&255|((g>>>0>255?0-g>>31:g)&65535)<<8&65535;c=b[h>>1]|0;j=((k>>>0>255?0-k>>31:k)&65535)<<8&65535|(j>>>0>255?0-j>>31:j)&255;b[a>>1]=((j|c)&65535)-(((j^c)&65535)>>>1&32639);c=b[h+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function lp(a,c,d){a=a|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+48|0;g=e+4|0;f=e;Zo(g,c+d|0,2,d);dp(f,e+8|0,c,2,d);c=b[g>>1]|0;h=b[f>>1]|0;b[a>>1]=((h|c)&65535)-(((h^c)&65535)>>>1&32639);c=b[g+2>>1]|0;f=b[f+2>>1]|0;b[a+d>>1]=((f|c)&65535)-(((f^c)&65535)>>>1&32639);i=e;return}function mp(a,c,e){a=a|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;f=i;i=i+16|0;h=f;Zo(h,c+e|0,2,e);o=c+(0-(e<<1))+1|0;k=d[o>>0]|d[o+1>>0]<<8;o=o+e|0;t=d[o>>0]|d[o+1>>0]<<8;p=(t&65535)>>>8&65535;o=o+e|0;c=d[o>>0]|d[o+1>>0]<<8;g=(c&65535)>>>8&65535;o=o+e|0;r=d[o>>0]|d[o+1>>0]<<8;m=(r&65535)>>>8&65535;o=o+e|0;s=d[o>>0]|d[o+1>>0]<<8;n=(s&65535)>>>8&65535;o=o+e|0;q=d[o>>0]|d[o+1>>0]<<8;l=(q&65535)>>>8&65535;o=o+e|0;o=d[o>>0]|d[o+1>>0]<<8;t=t&255;c=c&255;r=r&255;s=s&255;q=q&255;j=(k&255)+16+q+((r+c|0)*20|0)+(_(s+t|0,-5)|0)>>5;c=t+16+(o&255)+((s+r|0)*20|0)+(_(q+c|0,-5)|0)>>5;k=((k&65535)>>>8&65535)+16+l+((m+g|0)*20|0)+(_(n+p|0,-5)|0)>>5;g=p+16+((o&65535)>>>8&65535)+((n+m|0)*20|0)+(_(l+g|0,-5)|0)>>5;g=(c>>>0>255?0-c>>31:c)&255|((g>>>0>255?0-g>>31:g)&65535)<<8&65535;c=b[h>>1]|0;j=((k>>>0>255?0-k>>31:k)&65535)<<8&65535|(j>>>0>255?0-j>>31:j)&255;b[a>>1]=((j|c)&65535)-(((j^c)&65535)>>>1&32639);c=b[h+2>>1]|0;b[a+e>>1]=((g|c)&65535)-(((g^c)&65535)>>>1&32639);i=f;return}function np(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;f=b;g=a;h=0;while(1){i=c[g>>2]|0;j=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[g>>2]=(j|i)-((j^i)>>>1&2139062143);i=g+4|0;j=c[i>>2]|0;k=f+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[i>>2]=(k|j)-((k^j)>>>1&2139062143);h=h+1|0;if((h|0)==16)break;else{f=f+e|0;g=g+e|0}}h=b+8|0;f=a+8|0;g=0;while(1){k=c[f>>2]|0;j=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(j|k)-((j^k)>>>1&2139062143);k=f+4|0;j=c[k>>2]|0;i=h+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[k>>2]=(i|j)-((i^j)>>>1&2139062143);g=g+1|0;if((g|0)==16)break;else{h=h+e|0;f=f+e|0}}return}function op(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+256|0;f=k;Yn(f,b,16,e);h=f+8|0;j=b+8|0;Yn(h,j,16,e);g=b+(e<<3)|0;Yn(f+128|0,g,16,e);Yn(f+136|0,g+8|0,16,e);g=0;do{l=_(g,e)|0;p=b+l|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g<<4;q=f+o|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=a+l|0;m=c[n>>2]|0;p=(q|p)-((q^p)>>>1&2139062143)|0;c[n>>2]=(p|m)-((p^m)>>>1&2139062143);l=l+4|0;n=b+l|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+(o|4)|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=a+l|0;m=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|m)-((n^m)>>>1&2139062143);g=g+1|0}while((g|0)!=16);a=a+8|0;f=0;do{q=_(f,e)|0;m=j+q|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f<<4;l=h+n|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=a+q|0;p=c[o>>2]|0;m=(l|m)-((l^m)>>>1&2139062143)|0;c[o>>2]=(m|p)-((m^p)>>>1&2139062143);q=q+4|0;o=j+q|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+(n|4)|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=a+q|0;p=c[q>>2]|0;o=(n|o)-((n^o)>>>1&2139062143)|0;c[q>>2]=(o|p)-((o^p)>>>1&2139062143);f=f+1|0}while((f|0)!=16);i=k;return}function pp(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;qp(a,b,c,c);qp(a+8|0,b+8|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;qp(a,b,c,c);qp(a+8|0,b+8|0,c,c);return}function qp(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;g=0;while(1){k=c+1|0;j=c+-1|0;l=c+2|0;h=_((d[l>>0]|0)+(d[j>>0]|0)|0,-5)|0;m=c+3|0;h=(((d[k>>0]|0)+(d[c>>0]|0)|0)*20|0)+16+(d[c+-2>>0]|0)+h+(d[m>>0]|0)>>5;a[b>>0]=((d[b>>0]|0)+1+((h>>>0>255?0-h>>31:h)&255)|0)>>>1;h=b+1|0;n=_((d[m>>0]|0)+(d[c>>0]|0)|0,-5)|0;o=c+4|0;n=(((d[l>>0]|0)+(d[k>>0]|0)|0)*20|0)+16+(d[j>>0]|0)+n+(d[o>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((n>>>0>255?0-n>>31:n)&255)|0)>>>1;h=b+2|0;n=_((d[o>>0]|0)+(d[k>>0]|0)|0,-5)|0;j=c+5|0;n=(((d[m>>0]|0)+(d[l>>0]|0)|0)*20|0)+16+(d[c>>0]|0)+n+(d[j>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((n>>>0>255?0-n>>31:n)&255)|0)>>>1;h=b+3|0;n=_((d[j>>0]|0)+(d[l>>0]|0)|0,-5)|0;i=c+6|0;n=(((d[o>>0]|0)+(d[m>>0]|0)|0)*20|0)+16+(d[k>>0]|0)+n+(d[i>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((n>>>0>255?0-n>>31:n)&255)|0)>>>1;h=b+4|0;n=_((d[i>>0]|0)+(d[m>>0]|0)|0,-5)|0;k=c+7|0;n=(((d[j>>0]|0)+(d[o>>0]|0)|0)*20|0)+16+(d[l>>0]|0)+n+(d[k>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((n>>>0>255?0-n>>31:n)&255)|0)>>>1;h=b+5|0;n=_((d[k>>0]|0)+(d[o>>0]|0)|0,-5)|0;l=c+8|0;n=(((d[i>>0]|0)+(d[j>>0]|0)|0)*20|0)+16+(d[m>>0]|0)+n+(d[l>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((n>>>0>255?0-n>>31:n)&255)|0)>>>1;h=b+6|0;n=_((d[l>>0]|0)+(d[j>>0]|0)|0,-5)|0;m=c+9|0;n=(((d[k>>0]|0)+(d[i>>0]|0)|0)*20|0)+16+(d[o>>0]|0)+n+(d[m>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((n>>>0>255?0-n>>31:n)&255)|0)>>>1;h=b+7|0;i=_((d[m>>0]|0)+(d[i>>0]|0)|0,-5)|0;i=(((d[l>>0]|0)+(d[k>>0]|0)|0)*20|0)+16+(d[j>>0]|0)+i+(d[c+10>>0]|0)>>5;a[h>>0]=((d[h>>0]|0)+1+((i>>>0>255?0-i>>31:i)&255)|0)>>>1;g=g+1|0;if((g|0)==8)break;else{b=b+e|0;c=c+f|0}}return}function rp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+256|0;g=k;Yn(g,b,16,e);j=g+8|0;Yn(j,b+8|0,16,e);f=b+(e<<3)|0;Yn(g+128|0,f,16,e);Yn(g+136|0,f+8|0,16,e);f=b+1|0;h=0;do{l=_(h,e)|0;p=f+l|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h<<4;q=g+o|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=a+l|0;m=c[n>>2]|0;p=(q|p)-((q^p)>>>1&2139062143)|0;c[n>>2]=(p|m)-((p^m)>>>1&2139062143);l=l+4|0;n=f+l|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+(o|4)|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=a+l|0;m=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|m)-((n^m)>>>1&2139062143);h=h+1|0}while((h|0)!=16);g=a+8|0;f=b+9|0;a=0;do{q=_(a,e)|0;m=f+q|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a<<4;l=j+n|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=g+q|0;p=c[o>>2]|0;m=(l|m)-((l^m)>>>1&2139062143)|0;c[o>>2]=(m|p)-((m^p)>>>1&2139062143);q=q+4|0;o=f+q|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+(n|4)|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=g+q|0;p=c[q>>2]|0;o=(n|o)-((n^o)>>>1&2139062143)|0;c[q>>2]=(o|p)-((o^p)>>>1&2139062143);a=a+1|0}while((a|0)!=16);i=k;return}function sp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+592|0;k=m+256|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){n=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=n;a[g+1>>0]=n>>8;a[g+2>>0]=n>>16;a[g+3>>0]=n>>24;n=e+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;j=e+8|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;n=g+8|0;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;n=e+12|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+12|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}g=k+32|0;ao(l,g,16,16);h=l+8|0;j=k+40|0;ao(h,j,16,16);ao(l+128|0,k+160|0,16,16);ao(l+136|0,k+168|0,16,16);e=0;do{p=e<<4;q=g+p|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=l+p|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;n=_(e,f)|0;o=b+n|0;k=c[o>>2]|0;q=(r|q)-((r^q)>>>1&2139062143)|0;c[o>>2]=(q|k)-((q^k)>>>1&2139062143);p=p|4;o=g+p|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=l+p|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=b+(n+4)|0;k=c[n>>2]|0;o=(p|o)-((p^o)>>>1&2139062143)|0;c[n>>2]=(o|k)-((o^k)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{o=g<<4;n=j+o|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=h+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=_(g,f)|0;p=e+r|0;q=c[p>>2]|0;n=(l|n)-((l^n)>>>1&2139062143)|0;c[p>>2]=(n|q)-((n^q)>>>1&2139062143);o=o|4;p=j+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=e+(r+4)|0;q=c[r>>2]|0;p=(o|p)-((o^p)>>>1&2139062143)|0;c[r>>2]=(p|q)-((p^q)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=m;return}function tp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;Yn(k,e,16,f);m=k+8|0;Yn(m,e+8|0,16,f);g=e+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{g=e<<4;q=k+g|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=l+g|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=_(e,f)|0;j=b+p|0;o=c[j>>2]|0;q=(r|q)-((r^q)>>>1&2139062143)|0;c[j>>2]=(q|o)-((q^o)>>>1&2139062143);g=g|4;j=k+g|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=l+g|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=b+(p+4)|0;o=c[p>>2]|0;j=(g|j)-((g^j)>>>1&2139062143)|0;c[p>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{o=g<<4;l=m+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=h+o|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=_(g,f)|0;p=e+r|0;q=c[p>>2]|0;l=(k|l)-((k^l)>>>1&2139062143)|0;c[p>>2]=(l|q)-((l^q)>>>1&2139062143);o=o|4;p=m+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=e+(r+4)|0;q=c[r>>2]|0;p=(o|p)-((o^p)>>>1&2139062143)|0;c[r>>2]=(p|q)-((p^q)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function up(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+1184|0;n=k;f=k+928|0;g=k+672|0;Yn(f,b,16,e);j=f+8|0;o=b+8|0;Yn(j,o,16,e);m=b+(e<<3)|0;Yn(f+128|0,m,16,e);l=m+8|0;Yn(f+136|0,l,16,e);eo(g,n,b,16,16,e);h=g+8|0;b=n+16|0;eo(h,b,o,16,16,e);eo(g+128|0,n,m,16,16,e);eo(g+136|0,b,l,16,16,e);b=0;do{l=b<<4;p=f+l|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;q=g+l|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=_(b,e)|0;m=a+o|0;n=c[m>>2]|0;p=(q|p)-((q^p)>>>1&2139062143)|0;c[m>>2]=(p|n)-((p^n)>>>1&2139062143);l=l|4;m=f+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=g+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=a+(o+4)|0;n=c[o>>2]|0;m=(l|m)-((l^m)>>>1&2139062143)|0;c[o>>2]=(m|n)-((m^n)>>>1&2139062143);b=b+1|0}while((b|0)!=16);b=a+8|0;a=0;do{n=a<<4;m=j+n|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=h+n|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=_(a,e)|0;o=b+q|0;p=c[o>>2]|0;m=(l|m)-((l^m)>>>1&2139062143)|0;c[o>>2]=(m|p)-((m^p)>>>1&2139062143);n=n|4;o=j+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=b+(q+4)|0;p=c[q>>2]|0;o=(n|o)-((n^o)>>>1&2139062143)|0;c[q>>2]=(o|p)-((o^p)>>>1&2139062143);a=a+1|0}while((a|0)!=16);i=k;return}function vp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;Yn(k,e,16,f);m=k+8|0;Yn(m,e+8|0,16,f);g=e+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))+1|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{g=e<<4;q=k+g|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=l+g|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=_(e,f)|0;j=b+p|0;o=c[j>>2]|0;q=(r|q)-((r^q)>>>1&2139062143)|0;c[j>>2]=(q|o)-((q^o)>>>1&2139062143);g=g|4;j=k+g|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=l+g|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=b+(p+4)|0;o=c[p>>2]|0;j=(g|j)-((g^j)>>>1&2139062143)|0;c[p>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{o=g<<4;l=m+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=h+o|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=_(g,f)|0;p=e+r|0;q=c[p>>2]|0;l=(k|l)-((k^l)>>>1&2139062143)|0;c[p>>2]=(l|q)-((l^q)>>>1&2139062143);o=o|4;p=m+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=e+(r+4)|0;q=c[r>>2]|0;p=(o|p)-((o^p)>>>1&2139062143)|0;c[r>>2]=(p|q)-((p^q)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function wp(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;j=i;i=i+336|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){l=d[c>>0]|d[c+1>>0]<<8|d[c+2>>0]<<16|d[c+3>>0]<<24;a[f>>0]=l;a[f+1>>0]=l>>8;a[f+2>>0]=l>>16;a[f+3>>0]=l>>24;l=c+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;k=c+8|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=f+8|0;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;l=c+12|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+12|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+16|0}}xp(b,h+32|0,e,16);xp(b+8|0,h+40|0,e,16);l=b+(e<<3)|0;xp(l,h+160|0,e,16);xp(l+8|0,h+168|0,e,16);i=j;return}function xp(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;h=_(f,-2)|0;i=0-f|0;q=f<<1;r=f*3|0;s=f<<2;t=f*5|0;u=f*6|0;v=f*7|0;w=f<<3;x=f*9|0;j=f*10|0;k=e<<1;l=e*3|0;m=e<<2;n=e*5|0;o=e*6|0;p=e*7|0;g=0;while(1){K=d[c+i>>0]|0;J=d[c>>0]|0;I=d[c+f>>0]|0;H=d[c+q>>0]|0;G=d[c+r>>0]|0;F=d[c+s>>0]|0;E=d[c+t>>0]|0;z=d[c+u>>0]|0;B=d[c+v>>0]|0;C=d[c+w>>0]|0;A=d[c+x>>0]|0;D=d[c+j>>0]|0;y=(d[c+h>>0]|0)+16+G+((I+J|0)*20|0)+(_(H+K|0,-5)|0)>>5;a[b>>0]=(((y>>>0>255?0-y>>31:y)&255)+1+(d[b>>0]|0)|0)>>>1;y=b+e|0;K=K+16+F+((H+I|0)*20|0)+(_(G+J|0,-5)|0)>>5;a[y>>0]=(((K>>>0>255?0-K>>31:K)&255)+1+(d[y>>0]|0)|0)>>>1;y=b+k|0;J=J+16+E+((G+H|0)*20|0)+(_(F+I|0,-5)|0)>>5;a[y>>0]=(((J>>>0>255?0-J>>31:J)&255)+1+(d[y>>0]|0)|0)>>>1;y=b+l|0;I=I+16+z+((F+G|0)*20|0)+(_(E+H|0,-5)|0)>>5;a[y>>0]=(((I>>>0>255?0-I>>31:I)&255)+1+(d[y>>0]|0)|0)>>>1;y=b+m|0;H=H+16+B+((E+F|0)*20|0)+(_(z+G|0,-5)|0)>>5;a[y>>0]=(((H>>>0>255?0-H>>31:H)&255)+1+(d[y>>0]|0)|0)>>>1;y=b+n|0;G=G+16+C+((z+E|0)*20|0)+(_(B+F|0,-5)|0)>>5;a[y>>0]=(((G>>>0>255?0-G>>31:G)&255)+1+(d[y>>0]|0)|0)>>>1;y=b+o|0;F=F+16+A+((B+z|0)*20|0)+(_(C+E|0,-5)|0)>>5;a[y>>0]=(((F>>>0>255?0-F>>31:F)&255)+1+(d[y>>0]|0)|0)>>>1;y=b+p|0;z=E+16+D+((C+B|0)*20|0)+(_(A+z|0,-5)|0)>>5;a[y>>0]=(((z>>>0>255?0-z>>31:z)&255)+1+(d[y>>0]|0)|0)>>>1;g=g+1|0;if((g|0)==8)break;else{b=b+1|0;c=c+1|0}}return}function yp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;p=i;i=i+1520|0;k=p+1184|0;m=p;o=p+928|0;n=p+672|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){q=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a[h>>0]=q;a[h+1>>0]=q>>8;a[h+2>>0]=q>>16;a[h+3>>0]=q>>24;q=g+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;l=g+8|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=h+8|0;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;q=g+12|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+12|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+16|0}}ao(o,k+32|0,16,16);l=o+8|0;ao(l,k+40|0,16,16);ao(o+128|0,k+160|0,16,16);ao(o+136|0,k+168|0,16,16);eo(n,m,e,16,16,f);j=n+8|0;q=m+16|0;eo(j,q,e+8|0,16,16,f);g=e+(f<<3)|0;eo(n+128|0,m,g,16,16,f);eo(n+136|0,q,g+8|0,16,16,f);g=0;do{k=g<<4;h=o+k|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;r=n+k|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=_(g,f)|0;e=b+q|0;m=c[e>>2]|0;h=(r|h)-((r^h)>>>1&2139062143)|0;c[e>>2]=(h|m)-((h^m)>>>1&2139062143);k=k|4;e=o+k|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;k=n+k|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;q=b+(q+4)|0;m=c[q>>2]|0;e=(k|e)-((k^e)>>>1&2139062143)|0;c[q>>2]=(e|m)-((e^m)>>>1&2139062143);g=g+1|0}while((g|0)!=16);g=b+8|0;h=0;do{n=h<<4;b=l+n|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=j+n|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=_(h,f)|0;o=g+r|0;q=c[o>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[o>>2]=(b|q)-((b^q)>>>1&2139062143);n=n|4;o=l+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=g+(r+4)|0;q=c[r>>2]|0;o=(n|o)-((n^o)>>>1&2139062143)|0;c[r>>2]=(o|q)-((o^q)>>>1&2139062143);h=h+1|0}while((h|0)!=16);i=p;return}function zp(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+672|0;f=d;Ap(a,f,b,c,16,c);e=f+16|0;Ap(a+8|0,e,b+8|0,c,16,c);g=c<<3;b=b+g|0;a=a+g|0;Ap(a,f,b,c,16,c);Ap(a+8|0,e,b+8|0,c,16,c);i=d;return}function Ap(c,e,f,g,h,i){c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;B=h*13|0;j=e;k=0;f=f+(0-(i<<1))|0;while(1){z=f+1|0;x=f+-1|0;y=f+2|0;v=_((d[y>>0]|0)+(d[x>>0]|0)|0,-5)|0;w=f+3|0;b[j>>1]=(d[f+-2>>0]|0)+(((d[z>>0]|0)+(d[f>>0]|0)|0)*20|0)+v+(d[w>>0]|0);v=_((d[w>>0]|0)+(d[f>>0]|0)|0,-5)|0;u=f+4|0;b[j+2>>1]=(d[x>>0]|0)+(((d[y>>0]|0)+(d[z>>0]|0)|0)*20|0)+v+(d[u>>0]|0);v=_((d[u>>0]|0)+(d[z>>0]|0)|0,-5)|0;x=f+5|0;b[j+4>>1]=(d[f>>0]|0)+(((d[w>>0]|0)+(d[y>>0]|0)|0)*20|0)+v+(d[x>>0]|0);v=_((d[x>>0]|0)+(d[y>>0]|0)|0,-5)|0;A=f+6|0;b[j+6>>1]=(d[z>>0]|0)+(((d[u>>0]|0)+(d[w>>0]|0)|0)*20|0)+v+(d[A>>0]|0);v=_((d[A>>0]|0)+(d[w>>0]|0)|0,-5)|0;z=f+7|0;b[j+8>>1]=(d[y>>0]|0)+(((d[x>>0]|0)+(d[u>>0]|0)|0)*20|0)+v+(d[z>>0]|0);v=_((d[z>>0]|0)+(d[u>>0]|0)|0,-5)|0;y=f+8|0;b[j+10>>1]=(d[w>>0]|0)+(((d[A>>0]|0)+(d[x>>0]|0)|0)*20|0)+v+(d[y>>0]|0);v=_((d[y>>0]|0)+(d[x>>0]|0)|0,-5)|0;w=f+9|0;b[j+12>>1]=(d[u>>0]|0)+(((d[z>>0]|0)+(d[A>>0]|0)|0)*20|0)+v+(d[w>>0]|0);A=_((d[w>>0]|0)+(d[A>>0]|0)|0,-5)|0;b[j+14>>1]=(d[x>>0]|0)+(((d[y>>0]|0)+(d[z>>0]|0)|0)*20|0)+A+(d[f+10>>0]|0);k=k+1|0;if((k|0)==13)break;else{j=j+(h<<1)|0;f=f+i|0}}i=_(h,-2)|0;l=0-h|0;m=h<<1;n=h*3|0;o=h<<2;p=h*5|0;q=h*6|0;r=h*7|0;s=h<<3;t=h*9|0;u=h*10|0;v=g<<1;w=g*3|0;x=g<<2;y=g*5|0;z=g*6|0;A=g*7|0;k=e+(B<<1)+((_(h,-11)|0)<<1)|0;f=c;j=0;while(1){L=b[k+(l<<1)>>1]|0;K=b[k>>1]|0;J=b[k+(h<<1)>>1]|0;I=b[k+(m<<1)>>1]|0;H=b[k+(n<<1)>>1]|0;G=b[k+(o<<1)>>1]|0;F=b[k+(p<<1)>>1]|0;e=b[k+(q<<1)>>1]|0;C=b[k+(r<<1)>>1]|0;D=b[k+(s<<1)>>1]|0;B=b[k+(t<<1)>>1]|0;E=b[k+(u<<1)>>1]|0;c=(b[k+(i<<1)>>1]|0)+512+H+((J+K|0)*20|0)+(_(I+L|0,-5)|0)>>10;a[f>>0]=(((c>>>0>255?0-c>>31:c)&255)+1+(d[f>>0]|0)|0)>>>1;c=f+g|0;L=L+512+G+((I+J|0)*20|0)+(_(H+K|0,-5)|0)>>10;a[c>>0]=(((L>>>0>255?0-L>>31:L)&255)+1+(d[c>>0]|0)|0)>>>1;c=f+v|0;K=K+512+F+((H+I|0)*20|0)+(_(G+J|0,-5)|0)>>10;a[c>>0]=(((K>>>0>255?0-K>>31:K)&255)+1+(d[c>>0]|0)|0)>>>1;c=f+w|0;J=J+512+e+((G+H|0)*20|0)+(_(F+I|0,-5)|0)>>10;a[c>>0]=(((J>>>0>255?0-J>>31:J)&255)+1+(d[c>>0]|0)|0)>>>1;c=f+x|0;I=I+512+C+((F+G|0)*20|0)+(_(e+H|0,-5)|0)>>10;a[c>>0]=(((I>>>0>255?0-I>>31:I)&255)+1+(d[c>>0]|0)|0)>>>1;c=f+y|0;H=H+512+D+((e+F|0)*20|0)+(_(C+G|0,-5)|0)>>10;a[c>>0]=(((H>>>0>255?0-H>>31:H)&255)+1+(d[c>>0]|0)|0)>>>1;c=f+z|0;G=G+512+B+((C+e|0)*20|0)+(_(D+F|0,-5)|0)>>10;a[c>>0]=(((G>>>0>255?0-G>>31:G)&255)+1+(d[c>>0]|0)|0)>>>1;c=f+A|0;e=F+512+E+((D+C|0)*20|0)+(_(B+e|0,-5)|0)>>10;a[c>>0]=(((e>>>0>255?0-e>>31:e)&255)+1+(d[c>>0]|0)|0)>>>1;j=j+1|0;if((j|0)==8)break;else{k=k+2|0;f=f+1|0}}return}function Bp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;p=i;i=i+1520|0;k=p+1184|0;m=p;o=p+928|0;n=p+672|0;g=e+(0-(f<<1))+1|0;h=k;j=0;while(1){q=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a[h>>0]=q;a[h+1>>0]=q>>8;a[h+2>>0]=q>>16;a[h+3>>0]=q>>24;q=g+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;l=g+8|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=h+8|0;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;q=g+12|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+12|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+16|0}}ao(o,k+32|0,16,16);l=o+8|0;ao(l,k+40|0,16,16);ao(o+128|0,k+160|0,16,16);ao(o+136|0,k+168|0,16,16);eo(n,m,e,16,16,f);j=n+8|0;q=m+16|0;eo(j,q,e+8|0,16,16,f);g=e+(f<<3)|0;eo(n+128|0,m,g,16,16,f);eo(n+136|0,q,g+8|0,16,16,f);g=0;do{k=g<<4;h=o+k|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;r=n+k|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=_(g,f)|0;e=b+q|0;m=c[e>>2]|0;h=(r|h)-((r^h)>>>1&2139062143)|0;c[e>>2]=(h|m)-((h^m)>>>1&2139062143);k=k|4;e=o+k|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;k=n+k|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;q=b+(q+4)|0;m=c[q>>2]|0;e=(k|e)-((k^e)>>>1&2139062143)|0;c[q>>2]=(e|m)-((e^m)>>>1&2139062143);g=g+1|0}while((g|0)!=16);g=b+8|0;h=0;do{n=h<<4;b=l+n|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=j+n|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=_(h,f)|0;o=g+r|0;q=c[o>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[o>>2]=(b|q)-((b^q)>>>1&2139062143);n=n|4;o=l+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=g+(r+4)|0;q=c[r>>2]|0;o=(n|o)-((n^o)>>>1&2139062143)|0;c[r>>2]=(o|q)-((o^q)>>>1&2139062143);h=h+1|0}while((h|0)!=16);i=p;return}function Cp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+592|0;l=m+256|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){n=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=n;a[g+1>>0]=n>>8;a[g+2>>0]=n>>16;a[g+3>>0]=n>>24;n=e+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;j=e+8|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;n=g+8|0;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;n=e+12|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+12|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(k,l+32|0,16,16);j=k+8|0;ao(j,l+40|0,16,16);ao(k+128|0,l+160|0,16,16);ao(k+136|0,l+168|0,16,16);e=l+48|0;g=0;do{p=g<<4;q=e+p|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=k+p|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;n=_(g,f)|0;o=b+n|0;h=c[o>>2]|0;q=(r|q)-((r^q)>>>1&2139062143)|0;c[o>>2]=(q|h)-((q^h)>>>1&2139062143);p=p|4;o=e+p|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=k+p|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=b+(n+4)|0;h=c[n>>2]|0;o=(p|o)-((p^o)>>>1&2139062143)|0;c[n>>2]=(o|h)-((o^h)>>>1&2139062143);g=g+1|0}while((g|0)!=16);h=b+8|0;e=l+56|0;g=0;do{o=g<<4;n=e+o|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=j+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=_(g,f)|0;p=h+r|0;q=c[p>>2]|0;n=(l|n)-((l^n)>>>1&2139062143)|0;c[p>>2]=(n|q)-((n^q)>>>1&2139062143);o=o|4;p=e+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=h+(r+4)|0;q=c[r>>2]|0;p=(o|p)-((o^p)>>>1&2139062143)|0;c[r>>2]=(p|q)-((p^q)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=m;return}function Dp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;g=e+f|0;Yn(k,g,16,f);m=k+8|0;Yn(m,g+8|0,16,f);g=g+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{g=e<<4;q=k+g|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=l+g|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=_(e,f)|0;j=b+p|0;o=c[j>>2]|0;q=(r|q)-((r^q)>>>1&2139062143)|0;c[j>>2]=(q|o)-((q^o)>>>1&2139062143);g=g|4;j=k+g|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=l+g|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=b+(p+4)|0;o=c[p>>2]|0;j=(g|j)-((g^j)>>>1&2139062143)|0;c[p>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{o=g<<4;l=m+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=h+o|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=_(g,f)|0;p=e+r|0;q=c[p>>2]|0;l=(k|l)-((k^l)>>>1&2139062143)|0;c[p>>2]=(l|q)-((l^q)>>>1&2139062143);o=o|4;p=m+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=e+(r+4)|0;q=c[r>>2]|0;p=(o|p)-((o^p)>>>1&2139062143)|0;c[r>>2]=(p|q)-((p^q)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function Ep(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+1184|0;m=k;f=k+928|0;g=k+672|0;j=b+e|0;Yn(f,j,16,e);h=f+8|0;Yn(h,j+8|0,16,e);n=e<<3;j=j+n|0;Yn(f+128|0,j,16,e);Yn(f+136|0,j+8|0,16,e);eo(g,m,b,16,16,e);j=g+8|0;l=m+16|0;eo(j,l,b+8|0,16,16,e);b=b+n|0;eo(g+128|0,m,b,16,16,e);eo(g+136|0,l,b+8|0,16,16,e);b=0;do{o=b<<4;p=f+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;q=g+o|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=_(b,e)|0;l=a+n|0;m=c[l>>2]|0;p=(q|p)-((q^p)>>>1&2139062143)|0;c[l>>2]=(p|m)-((p^m)>>>1&2139062143);o=o|4;l=f+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=g+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=a+(n+4)|0;m=c[n>>2]|0;l=(o|l)-((o^l)>>>1&2139062143)|0;c[n>>2]=(l|m)-((l^m)>>>1&2139062143);b=b+1|0}while((b|0)!=16);b=a+8|0;a=0;do{n=a<<4;m=h+n|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+n|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=_(a,e)|0;o=b+q|0;p=c[o>>2]|0;m=(l|m)-((l^m)>>>1&2139062143)|0;c[o>>2]=(m|p)-((m^p)>>>1&2139062143);n=n|4;o=h+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=b+(q+4)|0;p=c[q>>2]|0;o=(n|o)-((n^o)>>>1&2139062143)|0;c[q>>2]=(o|p)-((o^p)>>>1&2139062143);a=a+1|0}while((a|0)!=16);i=k;return}function Fp(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;g=e+f|0;Yn(k,g,16,f);m=k+8|0;Yn(m,g+8|0,16,f);g=g+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))+1|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{g=e<<4;q=k+g|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=l+g|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=_(e,f)|0;j=b+p|0;o=c[j>>2]|0;q=(r|q)-((r^q)>>>1&2139062143)|0;c[j>>2]=(q|o)-((q^o)>>>1&2139062143);g=g|4;j=k+g|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=l+g|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=b+(p+4)|0;o=c[p>>2]|0;j=(g|j)-((g^j)>>>1&2139062143)|0;c[p>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{o=g<<4;l=m+o|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=h+o|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=_(g,f)|0;p=e+r|0;q=c[p>>2]|0;l=(k|l)-((k^l)>>>1&2139062143)|0;c[p>>2]=(l|q)-((l^q)>>>1&2139062143);o=o|4;p=m+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=e+(r+4)|0;q=c[r>>2]|0;p=(o|p)-((o^p)>>>1&2139062143)|0;c[r>>2]=(p|q)-((p^q)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function Gp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;f=c[a>>2]|0;g=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[a>>2]=(g|f)-((g^f)>>>1&2139062143);f=a+4|0;g=c[f>>2]|0;h=b+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(h|g)-((h^g)>>>1&2139062143);f=b+e|0;b=a+e|0;a=c[b>>2]|0;g=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[b>>2]=(g|a)-((g^a)>>>1&2139062143);a=b+4|0;g=c[a>>2]|0;h=f+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[a>>2]=(h|g)-((h^g)>>>1&2139062143);a=f+e|0;b=b+e|0;f=c[b>>2]|0;g=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[b>>2]=(g|f)-((g^f)>>>1&2139062143);f=b+4|0;g=c[f>>2]|0;h=a+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(h|g)-((h^g)>>>1&2139062143);a=a+e|0;b=b+e|0;f=c[b>>2]|0;g=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[b>>2]=(g|f)-((g^f)>>>1&2139062143);f=b+4|0;g=c[f>>2]|0;h=a+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(h|g)-((h^g)>>>1&2139062143);a=a+e|0;b=b+e|0;f=c[b>>2]|0;g=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[b>>2]=(g|f)-((g^f)>>>1&2139062143);f=b+4|0;g=c[f>>2]|0;h=a+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(h|g)-((h^g)>>>1&2139062143);a=a+e|0;b=b+e|0;f=c[b>>2]|0;g=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[b>>2]=(g|f)-((g^f)>>>1&2139062143);f=b+4|0;g=c[f>>2]|0;h=a+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(h|g)-((h^g)>>>1&2139062143);a=a+e|0;b=b+e|0;f=c[b>>2]|0;g=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[b>>2]=(g|f)-((g^f)>>>1&2139062143);f=b+4|0;g=c[f>>2]|0;h=a+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f>>2]=(h|g)-((h^g)>>>1&2139062143);a=a+e|0;e=b+e|0;b=c[e>>2]|0;f=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e>>2]=(f|b)-((f^b)>>>1&2139062143);e=e+4|0;b=c[e>>2]|0;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);return}function Hp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+64|0;f=h;Yn(f,b,8,e);g=0;do{j=_(g,e)|0;n=b+j|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=g<<3;o=f+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=a+j|0;k=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|k)-((n^k)>>>1&2139062143);j=j+4|0;l=b+j|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f+(m|4)|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;j=a+j|0;k=c[j>>2]|0;l=(m|l)-((m^l)>>>1&2139062143)|0;c[j>>2]=(l|k)-((l^k)>>>1&2139062143);g=g+1|0}while((g|0)!=8);i=h;return}function Ip(a,b,c){a=a|0;b=b|0;c=c|0;qp(a,b,c,c);return}function Jp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+64|0;g=h;Yn(g,b,8,e);b=b+1|0;f=0;do{j=_(f,e)|0;n=b+j|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f<<3;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=a+j|0;k=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|k)-((n^k)>>>1&2139062143);j=j+4|0;l=b+j|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+(m|4)|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;j=a+j|0;k=c[j>>2]|0;l=(m|l)-((m^l)>>>1&2139062143)|0;c[j>>2]=(l|k)-((l^k)>>>1&2139062143);f=f+1|0}while((f|0)!=8);i=h;return}function Kp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+176|0;j=h;g=h+104|0;f=j+16|0;b=b+(0-(e<<1))|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+4>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+12>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[f>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+20>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+28>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+36>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+44>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+52>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+60>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+68>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+76>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+84>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+92>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[j+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,f,8,8);b=0;do{m=b<<3;n=c[f+m>>2]|0;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=_(b,e)|0;l=a+k|0;j=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|j)-((n^j)>>>1&2139062143);m=m|4;l=c[f+m>>2]|0;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=a+(k+4)|0;j=c[k>>2]|0;l=(m|l)-((m^l)>>>1&2139062143)|0;c[k>>2]=(l|j)-((l^j)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Lp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b,8,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{m=b<<3;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=_(b,e)|0;j=a+l|0;k=c[j>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[j>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;j=f+m|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+(l+4)|0;k=c[l>>2]|0;j=(m|j)-((m^j)>>>1&2139062143)|0;c[l>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Mp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;f=h+272|0;g=h+208|0;Yn(f,b,8,e);eo(g,h,b,8,8,e);b=0;do{m=b<<3;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=_(b,e)|0;l=a+j|0;k=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;l=f+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;j=a+(j+4)|0;k=c[j>>2]|0;l=(m|l)-((m^l)>>>1&2139062143)|0;c[j>>2]=(l|k)-((l^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Np(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b,8,e);b=b+(0-(e<<1))+1|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{m=b<<3;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=_(b,e)|0;j=a+l|0;k=c[j>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[j>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;j=f+m|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+(l+4)|0;k=c[l>>2]|0;j=(m|j)-((m^j)>>>1&2139062143)|0;c[l>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Op(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+112|0;h=f;g=h+16|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+4>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+12>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+20>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+28>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+36>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+44>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+52>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+60>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+68>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+76>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+84>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+92>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[h+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;xp(a,g,e,8);i=f;return}function Pp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+448|0;k=h;g=h+376|0;f=h+312|0;j=k+16|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+4>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+8>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+12>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+20>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+28>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+32>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+36>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+40>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+44>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+48>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+52>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+56>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+60>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+64>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+68>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+72>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+76>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+80>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+84>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+88>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+92>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+96>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+4|0;c[k+100>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ao(g,j,8,8);eo(f,h+104|0,b,8,8,e);b=0;do{j=b<<3;n=g+j|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=_(b,e)|0;k=a+m|0;l=c[k>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[k>>2]=(n|l)-((n^l)>>>1&2139062143);j=j|4;k=g+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=f+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=a+(m+4)|0;l=c[m>>2]|0;k=(j|k)-((j^k)>>>1&2139062143)|0;c[m>>2]=(k|l)-((k^l)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Qp(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+208|0;Ap(a,d,b,c,8,c);i=d;return}function Rp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+448|0;k=h;g=h+376|0;f=h+312|0;j=k+16|0;l=b+(0-(e<<1))+1|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+4>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+8>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+12>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+20>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+28>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+32>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+36>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+40>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+44>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+48>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+52>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+56>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+60>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+64>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+68>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+72>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+76>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+80>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+84>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+88>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+92>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+96>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+4|0;c[k+100>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ao(g,j,8,8);eo(f,h+104|0,b,8,8,e);b=0;do{j=b<<3;n=g+j|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=_(b,e)|0;k=a+m|0;l=c[k>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[k>>2]=(n|l)-((n^l)>>>1&2139062143);j=j|4;k=g+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=f+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=a+(m+4)|0;l=c[m>>2]|0;k=(j|k)-((j^k)>>>1&2139062143)|0;c[m>>2]=(k|l)-((k^l)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Sp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+176|0;j=h;g=h+104|0;f=j+16|0;k=b+(0-(e<<1))|0;c[j>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=k+4|0;c[j+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=k+e|0;c[j+8>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=k+4|0;c[j+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=k+e|0;c[f>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=k+4|0;c[j+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=j+24|0;k=k+e|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+32>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+40>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+48>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+56>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+64>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+72>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+80>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+88>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+96>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;k=k+4|0;c[j+100>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;ao(g,f,8,8);f=0;do{m=f<<3;n=c[b+m>>2]|0;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=_(f,e)|0;j=a+l|0;k=c[j>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[j>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;j=c[b+m>>2]|0;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+(l+4)|0;k=c[l>>2]|0;j=(m|j)-((m^j)>>>1&2139062143)|0;c[l>>2]=(j|k)-((j^k)>>>1&2139062143);f=f+1|0}while((f|0)!=8);i=h;return}function Tp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b+e|0,8,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{m=b<<3;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=_(b,e)|0;j=a+l|0;k=c[j>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[j>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;j=f+m|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+(l+4)|0;k=c[l>>2]|0;j=(m|j)-((m^j)>>>1&2139062143)|0;c[l>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Up(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;f=h+272|0;g=h+208|0;Yn(f,b+e|0,8,e);eo(g,h,b,8,8,e);b=0;do{m=b<<3;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=_(b,e)|0;l=a+j|0;k=c[l>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[l>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;l=f+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;j=a+(j+4)|0;k=c[j>>2]|0;l=(m|l)-((m^l)>>>1&2139062143)|0;c[j>>2]=(l|k)-((l^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Vp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b+e|0,8,e);b=b+(0-(e<<1))+1|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{m=b<<3;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+m|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=_(b,e)|0;j=a+l|0;k=c[j>>2]|0;n=(o|n)-((o^n)>>>1&2139062143)|0;c[j>>2]=(n|k)-((n^k)>>>1&2139062143);m=m|4;j=f+m|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+(l+4)|0;k=c[l>>2]|0;j=(m|j)-((m^j)>>>1&2139062143)|0;c[l>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Wp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0;f=c[a>>2]|0;g=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[a>>2]=(g|f)-((g^f)>>>1&2139062143);f=b+e|0;b=a+e|0;a=c[b>>2]|0;g=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[b>>2]=(g|a)-((g^a)>>>1&2139062143);a=f+e|0;b=b+e|0;f=c[b>>2]|0;g=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[b>>2]=(g|f)-((g^f)>>>1&2139062143);a=a+e|0;e=b+e|0;b=c[e>>2]|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);return}function Xp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+16|0;h=f;Go(h,b,4,e);j=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[h>>2]|0;l=c[a>>2]|0;j=(g|j)-((g^j)>>>1&2139062143)|0;c[a>>2]=(j|l)-((j^l)>>>1&2139062143);l=b+e|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=c[h+4>>2]|0;g=a+e|0;k=c[g>>2]|0;l=(j|l)-((j^l)>>>1&2139062143)|0;c[g>>2]=(l|k)-((l^k)>>>1&2139062143);g=e<<1;k=b+g|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=c[h+8>>2]|0;g=a+g|0;j=c[g>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[g>>2]=(k|j)-((k^j)>>>1&2139062143);e=e*3|0;g=b+e|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=c[h+12>>2]|0;e=a+e|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function Yp(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;f=0;while(1){i=c+1|0;l=c+-1|0;h=c+2|0;g=_((d[h>>0]|0)+(d[l>>0]|0)|0,-5)|0;j=c+3|0;g=(((d[i>>0]|0)+(d[c>>0]|0)|0)*20|0)+16+(d[c+-2>>0]|0)+g+(d[j>>0]|0)>>5;a[b>>0]=((d[b>>0]|0)+1+((g>>>0>255?0-g>>31:g)&255)|0)>>>1;g=b+1|0;m=_((d[j>>0]|0)+(d[c>>0]|0)|0,-5)|0;k=c+4|0;m=(((d[h>>0]|0)+(d[i>>0]|0)|0)*20|0)+16+(d[l>>0]|0)+m+(d[k>>0]|0)>>5;a[g>>0]=((d[g>>0]|0)+1+((m>>>0>255?0-m>>31:m)&255)|0)>>>1;g=b+2|0;m=_((d[k>>0]|0)+(d[i>>0]|0)|0,-5)|0;l=c+5|0;m=(((d[j>>0]|0)+(d[h>>0]|0)|0)*20|0)+16+(d[c>>0]|0)+m+(d[l>>0]|0)>>5;a[g>>0]=((d[g>>0]|0)+1+((m>>>0>255?0-m>>31:m)&255)|0)>>>1;g=b+3|0;h=_((d[l>>0]|0)+(d[h>>0]|0)|0,-5)|0;h=(((d[k>>0]|0)+(d[j>>0]|0)|0)*20|0)+16+(d[i>>0]|0)+h+(d[c+6>>0]|0)>>5;a[g>>0]=((d[g>>0]|0)+1+((h>>>0>255?0-h>>31:h)&255)|0)>>>1;f=f+1|0;if((f|0)==4)break;else{b=b+e|0;c=c+e|0}}return}function Zp(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+16|0;h=f;Go(h,b,4,e);g=b+1|0;j=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=c[h>>2]|0;l=c[a>>2]|0;j=(b|j)-((b^j)>>>1&2139062143)|0;c[a>>2]=(j|l)-((j^l)>>>1&2139062143);l=g+e|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=c[h+4>>2]|0;b=a+e|0;k=c[b>>2]|0;l=(j|l)-((j^l)>>>1&2139062143)|0;c[b>>2]=(l|k)-((l^k)>>>1&2139062143);b=e<<1;k=g+b|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=c[h+8>>2]|0;b=a+b|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);e=e*3|0;g=g+e|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=c[h+12>>2]|0;e=a+e|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function _p(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+64|0;o=f+16|0;h=f;n=o+8|0;j=e<<1;b=b+(0-j)|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[o+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;l=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n>>2]=l;b=b+e|0;m=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+12>>2]=m;b=b+e|0;k=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+16>>2]=k;b=b+e|0;g=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+20>>2]=g;b=b+e|0;c[o+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[o+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[o+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(h,n,4);b=c[h>>2]|0;n=c[a>>2]|0;l=(b|l)-((b^l)>>>1&2139062143)|0;c[a>>2]=(l|n)-((l^n)>>>1&2139062143);n=c[h+4>>2]|0;l=a+e|0;b=c[l>>2]|0;m=(n|m)-((n^m)>>>1&2139062143)|0;c[l>>2]=(m|b)-((m^b)>>>1&2139062143);l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function $p(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+80|0;k=f+32|0;g=f+16|0;h=f;m=k+8|0;Go(g,b,4,e);j=e<<1;b=b+(0-j)|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(h,m,4);m=c[g>>2]|0;k=c[h>>2]|0;b=c[a>>2]|0;m=(k|m)-((k^m)>>>1&2139062143)|0;c[a>>2]=(m|b)-((m^b)>>>1&2139062143);b=c[g+4>>2]|0;m=c[h+4>>2]|0;k=a+e|0;l=c[k>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[k>>2]=(b|l)-((b^l)>>>1&2139062143);k=c[g+8>>2]|0;l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function aq(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0;e=i;i=i+112|0;f=e+16|0;g=e;Go(f,b,4,d);No(g,e+32|0,b,4,d);h=c[f>>2]|0;j=c[g>>2]|0;b=c[a>>2]|0;h=(j|h)-((j^h)>>>1&2139062143)|0;c[a>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[f+4>>2]|0;h=c[g+4>>2]|0;j=a+d|0;k=c[j>>2]|0;b=(h|b)-((h^b)>>>1&2139062143)|0;c[j>>2]=(b|k)-((b^k)>>>1&2139062143);j=c[f+8>>2]|0;k=c[g+8>>2]|0;b=a+(d<<1)|0;h=c[b>>2]|0;j=(k|j)-((k^j)>>>1&2139062143)|0;c[b>>2]=(j|h)-((j^h)>>>1&2139062143);f=c[f+12>>2]|0;g=c[g+12>>2]|0;d=a+(d*3|0)|0;b=c[d>>2]|0;a=(g|f)-((g^f)>>>1&2139062143)|0;c[d>>2]=(a|b)-((a^b)>>>1&2139062143);i=e;return}function bq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+80|0;k=f+32|0;g=f+16|0;h=f;m=k+8|0;Go(g,b,4,e);j=e<<1;b=b+(0-j)+1|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(h,m,4);m=c[g>>2]|0;k=c[h>>2]|0;b=c[a>>2]|0;m=(k|m)-((k^m)>>>1&2139062143)|0;c[a>>2]=(m|b)-((m^b)>>>1&2139062143);b=c[g+4>>2]|0;m=c[h+4>>2]|0;k=a+e|0;l=c[k>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[k>>2]=(b|l)-((b^l)>>>1&2139062143);k=c[g+8>>2]|0;l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function cq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;l=i;i=i+48|0;h=l;g=h+8|0;k=f<<1;e=e+(0-k)|0;c[h>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;c[h+4>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;j=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;c[g>>2]=j;e=e+f|0;c[h+12>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;c[h+16>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;c[h+20>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;c[h+24>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;c[h+28>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;e=e+f|0;c[h+32>>2]=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;h=f*3|0;j=j&255;e=0;while(1){t=d[g+-4>>0]|0;s=j&255;r=d[g+4>>0]|0;m=d[g+8>>0]|0;o=d[g+12>>0]|0;p=d[g+16>>0]|0;n=d[g+20>>0]|0;q=d[g+24>>0]|0;j=(d[g+-8>>0]|0)+16+o+((r+s|0)*20|0)+(_(m+t|0,-5)|0)>>5;a[b>>0]=((d[b>>0]|0)+1+((j>>>0>255?0-j>>31:j)&255)|0)>>>1;j=b+f|0;t=t+16+p+((m+r|0)*20|0)+(_(o+s|0,-5)|0)>>5;a[j>>0]=((d[j>>0]|0)+1+((t>>>0>255?0-t>>31:t)&255)|0)>>>1;j=b+k|0;s=s+16+n+((o+m|0)*20|0)+(_(p+r|0,-5)|0)>>5;a[j>>0]=((d[j>>0]|0)+1+((s>>>0>255?0-s>>31:s)&255)|0)>>>1;j=b+h|0;m=r+16+q+((p+o|0)*20|0)+(_(n+m|0,-5)|0)>>5;a[j>>0]=((d[j>>0]|0)+1+((m>>>0>255?0-m>>31:m)&255)|0)>>>1;g=g+1|0;e=e+1|0;if((e|0)==4)break;j=a[g>>0]|0;b=b+1|0}i=l;return}function dq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+144|0;k=f+32|0;g=f+16|0;h=f;m=k+8|0;j=e<<1;l=b+(0-j)|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+32>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Ko(g,m,4);No(h,f+72|0,b,4,e);m=c[g>>2]|0;k=c[h>>2]|0;b=c[a>>2]|0;m=(k|m)-((k^m)>>>1&2139062143)|0;c[a>>2]=(m|b)-((m^b)>>>1&2139062143);b=c[g+4>>2]|0;m=c[h+4>>2]|0;k=a+e|0;l=c[k>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[k>>2]=(b|l)-((b^l)>>>1&2139062143);k=c[g+8>>2]|0;l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function eq(c,e,f){c=c|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;l=i;i=i+80|0;j=l;k=f<<1;g=j;h=0;e=e+(0-k)|0;while(1){s=d[e>>0]|0;p=d[e+1>>0]|0;q=d[e+-1>>0]|0;m=d[e+2>>0]|0;r=_(m+q|0,-5)|0;n=d[e+3>>0]|0;b[g>>1]=(d[e+-2>>0]|0)+((p+s|0)*20|0)+r+n;r=_(n+s|0,-5)|0;o=d[e+4>>0]|0;b[g+2>>1]=q+((m+p|0)*20|0)+r+o;r=_(o+p|0,-5)|0;q=d[e+5>>0]|0;b[g+4>>1]=s+((n+m|0)*20|0)+r+q;m=_(q+m|0,-5)|0;b[g+6>>1]=p+((o+n|0)*20|0)+m+(d[e+6>>0]|0);h=h+1|0;if((h|0)==9)break;else{g=g+8|0;e=e+f|0}}h=f*3|0;g=j+16|0;e=0;while(1){t=b[g+-8>>1]|0;j=b[g>>1]|0;m=b[g+8>>1]|0;r=b[g+16>>1]|0;p=b[g+24>>1]|0;o=b[g+32>>1]|0;q=b[g+40>>1]|0;n=b[g+48>>1]|0;s=(b[g+-16>>1]|0)+512+p+((m+j|0)*20|0)+(_(r+t|0,-5)|0)>>10;a[c>>0]=((d[c>>0]|0)+1+((s>>>0>255?0-s>>31:s)&255)|0)>>>1;s=c+f|0;t=t+512+o+((r+m|0)*20|0)+(_(p+j|0,-5)|0)>>10;a[s>>0]=((d[s>>0]|0)+1+((t>>>0>255?0-t>>31:t)&255)|0)>>>1;s=c+k|0;j=j+512+q+((p+r|0)*20|0)+(_(o+m|0,-5)|0)>>10;a[s>>0]=((d[s>>0]|0)+1+((j>>>0>255?0-j>>31:j)&255)|0)>>>1;s=c+h|0;r=m+512+n+((o+p|0)*20|0)+(_(q+r|0,-5)|0)>>10;a[s>>0]=((d[s>>0]|0)+1+((r>>>0>255?0-r>>31:r)&255)|0)>>>1;e=e+1|0;if((e|0)==4)break;else{g=g+2|0;c=c+1|0}}i=l;return}function fq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+144|0;k=f+32|0;g=f+16|0;h=f;m=k+8|0;j=e<<1;l=b+(0-j)+1|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+32>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Ko(g,m,4);No(h,f+72|0,b,4,e);m=c[g>>2]|0;k=c[h>>2]|0;b=c[a>>2]|0;m=(k|m)-((k^m)>>>1&2139062143)|0;c[a>>2]=(m|b)-((m^b)>>>1&2139062143);b=c[g+4>>2]|0;m=c[h+4>>2]|0;k=a+e|0;l=c[k>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[k>>2]=(b|l)-((b^l)>>>1&2139062143);k=c[g+8>>2]|0;l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function gq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+64|0;o=f+16|0;h=f;n=o+8|0;j=e<<1;b=b+(0-j)|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[o+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;l=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+12>>2]=l;b=b+e|0;m=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+16>>2]=m;b=b+e|0;k=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+20>>2]=k;b=b+e|0;g=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+24>>2]=g;b=b+e|0;c[o+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[o+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(h,n,4);b=c[h>>2]|0;n=c[a>>2]|0;l=(b|l)-((b^l)>>>1&2139062143)|0;c[a>>2]=(l|n)-((l^n)>>>1&2139062143);n=c[h+4>>2]|0;l=a+e|0;b=c[l>>2]|0;m=(n|m)-((n^m)>>>1&2139062143)|0;c[l>>2]=(m|b)-((m^b)>>>1&2139062143);l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function hq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+80|0;k=f+32|0;g=f+16|0;h=f;m=k+8|0;Go(g,b+e|0,4,e);j=e<<1;b=b+(0-j)|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(h,m,4);m=c[g>>2]|0;k=c[h>>2]|0;b=c[a>>2]|0;m=(k|m)-((k^m)>>>1&2139062143)|0;c[a>>2]=(m|b)-((m^b)>>>1&2139062143);b=c[g+4>>2]|0;m=c[h+4>>2]|0;k=a+e|0;l=c[k>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[k>>2]=(b|l)-((b^l)>>>1&2139062143);k=c[g+8>>2]|0;l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function iq(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0;e=i;i=i+112|0;f=e+16|0;g=e;Go(f,b+d|0,4,d);No(g,e+32|0,b,4,d);h=c[f>>2]|0;j=c[g>>2]|0;b=c[a>>2]|0;h=(j|h)-((j^h)>>>1&2139062143)|0;c[a>>2]=(h|b)-((h^b)>>>1&2139062143);b=c[f+4>>2]|0;h=c[g+4>>2]|0;j=a+d|0;k=c[j>>2]|0;b=(h|b)-((h^b)>>>1&2139062143)|0;c[j>>2]=(b|k)-((b^k)>>>1&2139062143);j=c[f+8>>2]|0;k=c[g+8>>2]|0;b=a+(d<<1)|0;h=c[b>>2]|0;j=(k|j)-((k^j)>>>1&2139062143)|0;c[b>>2]=(j|h)-((j^h)>>>1&2139062143);f=c[f+12>>2]|0;g=c[g+12>>2]|0;d=a+(d*3|0)|0;b=c[d>>2]|0;a=(g|f)-((g^f)>>>1&2139062143)|0;c[d>>2]=(a|b)-((a^b)>>>1&2139062143);i=e;return}function jq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;f=i;i=i+80|0;k=f+32|0;g=f+16|0;h=f;m=k+8|0;Go(g,b+e|0,4,e);j=e<<1;b=b+(0-j)+1|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+28>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Ko(h,m,4);m=c[g>>2]|0;k=c[h>>2]|0;b=c[a>>2]|0;m=(k|m)-((k^m)>>>1&2139062143)|0;c[a>>2]=(m|b)-((m^b)>>>1&2139062143);b=c[g+4>>2]|0;m=c[h+4>>2]|0;k=a+e|0;l=c[k>>2]|0;b=(m|b)-((m^b)>>>1&2139062143)|0;c[k>>2]=(b|l)-((b^l)>>>1&2139062143);k=c[g+8>>2]|0;l=c[h+8>>2]|0;b=a+j|0;j=c[b>>2]|0;k=(l|k)-((l^k)>>>1&2139062143)|0;c[b>>2]=(k|j)-((k^j)>>>1&2139062143);g=c[g+12>>2]|0;h=c[h+12>>2]|0;e=a+(e*3|0)|0;b=c[e>>2]|0;a=(h|g)-((h^g)>>>1&2139062143)|0;c[e>>2]=(a|b)-((a^b)>>>1&2139062143);i=f;return}function kq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;f=b+8|0;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;h=a+8|0;c[h>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[h+4>>2]=f;h=b+e|0;f=a+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+8|0;i=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+8|0;c[f>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[f+4>>2]=h;f=a+16|0;h=b+16|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=b+24|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=a+24|0;c[b>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[b+4>>2]=g;b=h+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function lq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;f=k;mq(f,b,32,e);h=f+16|0;j=b+16|0;mq(h,j,32,e);g=b+(e<<3)|0;mq(f+256|0,g,32,e);mq(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g<<5;q=f+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=b+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{r=_(f,e)|0;p=j+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=f<<5;m=h+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=a+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=j+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=a+r|0;c[r>>2]=q;c[r+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function mq(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=d>>1;o=f>>1;n=0;while(1){g=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;k=c+6|0;d=(((e[g>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=_((e[k>>1]|0)+(e[c>>1]|0)|0,-5)|0;m=c+8|0;d=(((e[i>>1]|0)+(e[g>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+2>>1]=d;d=_((e[m>>1]|0)+(e[g>>1]|0)|0,-5)|0;l=c+10|0;d=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+4>>1]=d;d=_((e[l>>1]|0)+(e[i>>1]|0)|0,-5)|0;j=c+12|0;d=(((e[m>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[g>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+6>>1]=d;d=_((e[j>>1]|0)+(e[k>>1]|0)|0,-5)|0;h=c+14|0;d=(((e[l>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[i>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+8>>1]=d;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+16|0;d=(((e[j>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+10>>1]=d;d=_((e[g>>1]|0)+(e[l>>1]|0)|0,-5)|0;f=c+18|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+12>>1]=d;d=_((e[f>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+14>>1]=d;n=n+1|0;if((n|0)==8)break;else{a=a+(p<<1)|0;c=c+(o<<1)|0}}return}function nq(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;mq(a,b,c,c);mq(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;mq(a,b,c,c);mq(a+16|0,b+16|0,c,c);return}function oq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;g=k;mq(g,b,32,e);j=g+16|0;mq(j,b+16|0,32,e);f=b+(e<<3)|0;mq(g+256|0,f,32,e);mq(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=h<<5;q=g+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=f+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{r=_(a,e)|0;p=f+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=a<<5;m=j+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=g+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=f+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=g+r|0;c[r>>2]=q;c[r+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function pq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;qq(l,g,32,32);h=l+16|0;j=k+80|0;qq(h,j,32,32);qq(l+256|0,k+320|0,32,32);qq(l+272|0,k+336|0,32,32);e=0;do{k=e<<5;p=g+k|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=l+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(e,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;k=k|8;n=g+k|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;k=l+k|0;r=k;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=j+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=j+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function qq(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;K=d>>1;s=f>>1;C=_(s,-2)|0;D=0-s|0;E=s<<1;F=s*3|0;G=s<<2;H=s*5|0;I=s*6|0;J=s*7|0;t=s<<3;u=s*9|0;v=s*10|0;w=K<<1;x=K*3|0;y=K<<2;z=K*5|0;A=K*6|0;B=K*7|0;r=0;while(1){f=e[c+(D<<1)>>1]|0;g=e[c>>1]|0;h=e[c+(s<<1)>>1]|0;i=e[c+(E<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(t<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;d=(e[c+(C<<1)>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(K<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(w<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(x<<1)>>1]=d;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(y<<1)>>1]=d;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(z<<1)>>1]=d;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(A<<1)>>1]=d;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(B<<1)>>1]=d;r=r+1|0;if((r|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function rq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;mq(k,e,32,f);m=k+16|0;mq(m,e+16|0,32,f);g=e+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function sq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+2368|0;n=k;f=k+1856|0;g=k+1344|0;mq(f,b,32,e);j=f+16|0;o=b+16|0;mq(j,o,32,e);m=b+(e<<3)|0;mq(f+256|0,m,32,e);l=m+16|0;mq(f+272|0,l,32,e);tq(g,n,b,32,32,e);h=g+16|0;b=n+16|0;tq(h,b,o,32,32,e);tq(g+256|0,n,m,32,32,e);tq(g+272|0,b,l,32,32,e);b=0;do{l=b<<5;o=f+l|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=JF(n^q|0,m^o|0,1)|0;p=EF(n|q|0,m|o|0,p&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=p;c[m+4>>2]=C;l=l|8;m=f+l|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=j+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=h+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=j+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function tq(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;j=h>>1;l=g*13|0;i=c;k=0;h=d+(0-(j<<1)<<1)|0;while(1){J=h+2|0;H=h+-2|0;I=h+4|0;F=_((e[I>>1]|0)+(e[H>>1]|0)|0,-5)|0;G=h+6|0;b[i>>1]=(e[h+-4>>1]|0)+(((e[J>>1]|0)+(e[h>>1]|0)|0)*20|0)+F+(e[G>>1]|0);F=_((e[G>>1]|0)+(e[h>>1]|0)|0,-5)|0;E=h+8|0;b[i+2>>1]=(e[H>>1]|0)+(((e[I>>1]|0)+(e[J>>1]|0)|0)*20|0)+F+(e[E>>1]|0);F=_((e[E>>1]|0)+(e[J>>1]|0)|0,-5)|0;H=h+10|0;b[i+4>>1]=(e[h>>1]|0)+(((e[G>>1]|0)+(e[I>>1]|0)|0)*20|0)+F+(e[H>>1]|0);F=_((e[H>>1]|0)+(e[I>>1]|0)|0,-5)|0;K=h+12|0;b[i+6>>1]=(e[J>>1]|0)+(((e[E>>1]|0)+(e[G>>1]|0)|0)*20|0)+F+(e[K>>1]|0);F=_((e[K>>1]|0)+(e[G>>1]|0)|0,-5)|0;J=h+14|0;b[i+8>>1]=(e[I>>1]|0)+(((e[H>>1]|0)+(e[E>>1]|0)|0)*20|0)+F+(e[J>>1]|0);F=_((e[J>>1]|0)+(e[E>>1]|0)|0,-5)|0;I=h+16|0;b[i+10>>1]=(e[G>>1]|0)+(((e[K>>1]|0)+(e[H>>1]|0)|0)*20|0)+F+(e[I>>1]|0);F=_((e[I>>1]|0)+(e[H>>1]|0)|0,-5)|0;G=h+18|0;b[i+12>>1]=(e[E>>1]|0)+(((e[J>>1]|0)+(e[K>>1]|0)|0)*20|0)+F+(e[G>>1]|0);K=_((e[G>>1]|0)+(e[K>>1]|0)|0,-5)|0;b[i+14>>1]=(e[H>>1]|0)+(((e[I>>1]|0)+(e[J>>1]|0)|0)*20|0)+K+(e[h+20>>1]|0);k=k+1|0;if((k|0)==13)break;else{i=i+(g<<1)|0;h=h+(j<<1)|0}}t=f>>1;u=_(g,-2)|0;v=0-g|0;w=g<<1;x=g*3|0;y=g<<2;z=g*5|0;A=g*6|0;B=g*7|0;C=g<<3;D=g*9|0;E=g*10|0;F=t<<1;G=t*3|0;H=t<<2;I=t*5|0;J=t*6|0;K=t*7|0;s=c+(l<<1)+((_(g,-11)|0)<<1)|0;r=0;while(1){i=b[s+(v<<1)>>1]|0;j=b[s>>1]|0;d=b[s+(g<<1)>>1]|0;k=b[s+(w<<1)>>1]|0;f=b[s+(x<<1)>>1]|0;l=b[s+(y<<1)>>1]|0;c=b[s+(z<<1)>>1]|0;m=b[s+(A<<1)>>1]|0;n=b[s+(B<<1)>>1]|0;o=b[s+(C<<1)>>1]|0;p=b[s+(D<<1)>>1]|0;q=b[s+(E<<1)>>1]|0;h=(b[s+(u<<1)>>1]|0)+512+f+((d+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a>>1]=h;h=i+512+l+((k+d|0)*20|0)+(_(f+j|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(t<<1)>>1]=h;h=j+512+c+((f+k|0)*20|0)+(_(l+d|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(F<<1)>>1]=h;h=d+512+m+((l+f|0)*20|0)+(_(c+k|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(G<<1)>>1]=h;h=k+512+n+((c+l|0)*20|0)+(_(m+f|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(H<<1)>>1]=h;h=f+512+o+((m+c|0)*20|0)+(_(n+l|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(I<<1)>>1]=h;h=l+512+p+((n+m|0)*20|0)+(_(o+c|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(J<<1)>>1]=h;h=c+512+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a+(K<<1)>>1]=h;r=r+1|0;if((r|0)==8)break;else{s=s+2|0;a=a+2|0}}return}function uq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;mq(k,e,32,f);m=k+16|0;mq(m,e+16|0,32,f);g=e+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function vq(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}qq(b,h+64|0,e,32);qq(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;qq(n,h+320|0,e,32);qq(n+16|0,h+336|0,e,32);i=j;return}function wq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}qq(o,k+64|0,32,32);l=o+16|0;qq(l,k+80|0,32,32);qq(o+256|0,k+320|0,32,32);qq(o+272|0,k+336|0,32,32);tq(n,m,e,32,32,f);j=n+16|0;s=m+16|0;tq(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;tq(n+256|0,m,g,32,32,f);tq(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function xq(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+1344|0;f=d;tq(a,f,b,c,32,c);e=f+16|0;tq(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;tq(a,f,b,c,32,c);tq(a+16|0,e,b+16|0,c,32,c);i=d;return}function yq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}qq(o,k+64|0,32,32);l=o+16|0;qq(l,k+80|0,32,32);qq(o+256|0,k+320|0,32,32);qq(o+272|0,k+336|0,32,32);tq(n,m,e,32,32,f);j=n+16|0;s=m+16|0;tq(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;tq(n+256|0,m,g,32,32,f);tq(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}
function dv(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(k,l+64|0,32,32);j=k+16|0;Wu(j,l+80|0,32,32);Wu(k+256|0,l+320|0,32,32);Wu(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{h=g<<5;p=e+h|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=k+h|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(g,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;h=h|8;n=e+h|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;h=k+h|0;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{o=g<<5;r=e+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=j+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=h+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=e+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=h+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function ev(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Su(k,g,32,f);m=k+16|0;Su(m,g+16|0,32,f);g=g+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function fv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+3712|0;m=k;f=k+3200|0;g=k+2688|0;j=b+e|0;Su(f,j,32,e);h=f+16|0;Su(h,j+16|0,32,e);n=e<<3;j=j+n|0;Su(f+256|0,j,32,e);Su(f+272|0,j+16|0,32,e);Zu(g,m,b,32,32,e);j=g+16|0;l=m+32|0;Zu(j,l,b+16|0,32,32,e);b=b+n|0;Zu(g+256|0,m,b,32,32,e);Zu(g+272|0,l,b+16|0,32,32,e);b=0;do{o=b<<5;n=f+o|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+o|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=JF(m^q|0,l^n|0,1)|0;p=EF(m|q|0,l|n|0,p&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=p;c[l+4>>2]=C;o=o|8;l=f+o|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=g+o|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=h+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=h+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function gv(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Su(k,g,32,f);m=k+16|0;Su(m,g+16|0,32,f);g=g+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function hv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;f=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=a;c[g>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[g+4>>2]=h;g=b+8|0;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a+8|0;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;b=b+e|0;f=a+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function iv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;f=h;Su(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g<<4;o=f+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function jv(a,b,c){a=a|0;b=b|0;c=c|0;Su(a,b,c,c);return}function kv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;g=h;Su(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f<<4;o=g+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function lv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;Wu(g,f,16,16);b=0;do{j=b<<4;m=f+j|0;o=c[m>>2]|0;m=c[m+4>>2]|0;k=g+j|0;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;n=JF(l^o|0,k^m|0,1)|0;n=EF(l|o|0,k|m|0,n&2147450879|0,C&2147450879|0)|0;m=_(b,e)|0;k=a+m|0;c[k>>2]=n;c[k+4>>2]=C;j=j|8;k=f+j|0;n=c[k>>2]|0;k=c[k+4>>2]|0;j=g+j|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;m=a+(m+8)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function mv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function nv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;Su(f,b,16,e);Zu(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function ov(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function pv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Wu(a,g,e,16);i=f;return}function qv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Wu(g,j,16,16);Zu(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function rv(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+832|0;Zu(a,d,b,c,16,c);i=d;return}function sv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Wu(g,j,16,16);Zu(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function tv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;Wu(g,f,16,16);f=0;do{k=f<<4;n=b+k|0;o=c[n>>2]|0;n=c[n+4>>2]|0;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(f,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=b+k|0;j=c[l>>2]|0;l=c[l+4>>2]|0;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function uv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function vv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;Su(f,b+e|0,16,e);Zu(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function wv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function xv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=a;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[f+4>>2]=h;b=b+e|0;f=a+e|0;h=b;a=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=f;c[g>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[g+4>>2]=h;b=b+e|0;f=f+e|0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a=f;c[a>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[a+4>>2]=g;b=b+e|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=f+e|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function yv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;zv(h,b,8,e);g=b;n=g;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h;k=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(k^n|0,m^g|0,1)|0;l=EF(k|n|0,m|g|0,l&2147450879|0,C&2147450879|0)|0;g=a;c[g>>2]=l;c[g+4>>2]=C;g=b+e|0;l=g;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;k=JF(n^l|0,m^g|0,1)|0;k=EF(n|l|0,m|g|0,k&2147450879|0,C&2147450879|0)|0;g=a+e|0;c[g>>2]=k;c[g+4>>2]=C;g=e<<1;k=b+g|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;g=a+g|0;c[g>>2]=j;c[g+4>>2]=C;e=e*3|0;g=b+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function zv(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=f>>1;k=0;while(1){j=c+2|0;f=c+-2|0;h=c+4|0;d=_((e[h>>1]|0)+(e[f>>1]|0)|0,-5)|0;i=c+6|0;d=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=c+8|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+2>>1]=d;d=_((e[g>>1]|0)+(e[j>>1]|0)|0,-5)|0;f=c+10|0;d=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+4>>1]=d;d=_((e[f>>1]|0)+(e[h>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+6>>1]=d;k=k+1|0;if((k|0)==4)break;else{a=a+(m<<1)|0;c=c+(l<<1)|0}}return}function Av(a,b,c){a=a|0;b=b|0;c=c|0;zv(a,b,c,c);return}function Bv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;zv(h,b,8,e);g=b+2|0;j=g;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h;b=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(b^n|0,m^j|0,1)|0;l=EF(b|n|0,m|j|0,l&2147450879|0,C&2147450879|0)|0;j=a;c[j>>2]=l;c[j+4>>2]=C;j=g+e|0;l=j;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(n^l|0,m^j|0,1)|0;b=EF(n|l|0,m|j|0,b&2147450879|0,C&2147450879|0)|0;j=a+e|0;c[j>>2]=b;c[j+4>>2]=C;j=e<<1;b=g+j|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;e=e*3|0;g=g+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Cv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=s+8|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=o;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+24|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+32|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=s+40|0;c[v>>2]=j;c[v+4>>2]=g;q=q+e|0;v=q;t=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;u=s+48|0;c[u>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[u+4>>2]=v;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;Dv(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Dv(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;o=d>>1;p=o<<1;q=o*3|0;n=0;while(1){f=e[c+-8>>1]|0;g=e[c>>1]|0;h=e[c+8>>1]|0;i=e[c+16>>1]|0;j=e[c+24>>1]|0;k=e[c+32>>1]|0;l=e[c+40>>1]|0;m=e[c+48>>1]|0;d=(e[c+-16>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(o<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(p<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(q<<1)>>1]=d;n=n+1|0;if((n|0)==4)break;else{a=a+2|0;c=c+2|0}}return}function Ev(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;zv(g,b,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Dv(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Fv(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+352|0;f=e+32|0;g=e;zv(f,b,8,d);Gv(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function Gv(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;j=h>>1;i=d;k=0;h=f+(0-(j<<1)<<1)|0;while(1){l=e[h>>1]|0;o=e[h+2>>1]|0;n=e[h+-2>>1]|0;r=e[h+4>>1]|0;m=_(r+n|0,-5)|0;q=e[h+6>>1]|0;c[i>>2]=(e[h+-4>>1]|0)+((o+l|0)*20|0)+m+q;m=_(q+l|0,-5)|0;p=e[h+8>>1]|0;c[i+4>>2]=n+((r+o|0)*20|0)+m+p;m=_(p+o|0,-5)|0;n=e[h+10>>1]|0;c[i+8>>2]=l+((q+r|0)*20|0)+m+n;r=_(n+r|0,-5)|0;c[i+12>>2]=o+((p+q|0)*20|0)+r+(e[h+12>>1]|0);k=k+1|0;if((k|0)==9)break;else{i=i+32|0;h=h+(j<<1)|0}}p=g>>1;q=p<<1;r=p*3|0;o=d+64|0;n=0;while(1){i=c[o+-32>>2]|0;j=c[o>>2]|0;f=c[o+32>>2]|0;k=c[o+64>>2]|0;g=c[o+96>>2]|0;d=c[o+128>>2]|0;l=c[o+160>>2]|0;m=c[o+192>>2]|0;h=(c[o+-64>>2]|0)+512+g+((f+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;h=i+512+d+((k+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a+(p<<1)>>1]=h;h=j+512+l+((g+k|0)*20|0)+(_(d+f|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a+(q<<1)>>1]=h;h=f+512+m+((d+g|0)*20|0)+(_(l+k|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a+(r<<1)>>1]=h;n=n+1|0;if((n|0)==4)break;else{o=o+4|0;a=a+2|0}}return}function Hv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;zv(g,b,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Dv(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Iv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;k=f;g=k+16|0;h=b+(0-(e<<1))|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+8|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+24|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+32|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+40|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+48|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+56|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Dv(a,g,e);i=f;return}function Jv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+432|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;Dv(g,k,8);Gv(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Kv(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+288|0;Gv(a,d,b,c,c);i=d;return}function Lv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+432|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)+2|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;Dv(g,k,8);Gv(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Mv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;b=q;r=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s;c[m>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[m+4>>2]=b;q=q+e|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s+8|0;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=o;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s+24|0;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+32|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+40|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;u=s+48|0;c[u>>2]=j;c[u+4>>2]=g;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;Dv(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Nv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;zv(g,b+e|0,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Dv(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Ov(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+352|0;f=e+32|0;g=e;zv(f,b+d|0,8,d);Gv(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function Pv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;zv(g,b+e|0,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Dv(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Qv(a,b,e){a=a|0;b=b|0;e=e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[a+e>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;return}function Rv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;Sv(g,b,4,e);h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Sv(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;j=c+2|0;k=c+-2|0;h=c+4|0;g=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;i=c+6|0;g=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+g+(e[i>>1]|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[a>>1]=g;g=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+g+(e[c+8>>1]|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[a+2>>1]=g;a=a+(d>>1<<1)|0;h=c+(f>>1<<1)|0;i=h+2|0;j=h+-2|0;k=h+4|0;g=_((e[k>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=h+6|0;g=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[h+-4>>1]|0)+g+(e[d>>1]|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[a>>1]=g;g=_((e[d>>1]|0)+(e[h>>1]|0)|0,-5)|0;g=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+g+(e[h+8>>1]|0)>>5;if(g>>>0<4096){c=g;c=c&65535;f=a+2|0;b[f>>1]=c;return}c=0-g>>31&4095;c=c&65535;f=a+2|0;b[f>>1]=c;return}function Tv(a,b,c){a=a|0;b=b|0;c=c|0;Sv(a,b,c,c);return}function Uv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;Sv(g,b,4,e);b=b+2|0;h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Vv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=b;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Wv(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Wv(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=d>>1;j=e[c+-4>>1]|0;k=e[c>>1]|0;f=e[c+4>>1]|0;g=e[c+8>>1]|0;h=e[c+12>>1]|0;i=e[c+16>>1]|0;d=(e[c+-8>>1]|0)+16+h+((f+k|0)*20|0)+(_(g+j|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=j+16+i+((g+f|0)*20|0)+(_(h+k|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(l<<1)>>1]=d;a=a+2|0;f=e[c+-2>>1]|0;g=e[c+2>>1]|0;h=e[c+6>>1]|0;i=e[c+10>>1]|0;j=e[c+14>>1]|0;k=e[c+18>>1]|0;d=(e[c+-6>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0<4096){c=d;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}c=0-d>>31&4095;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}function Xv(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Sv(h,b,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Wv(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Yv(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+128|0;g=e+8|0;f=e;Sv(g,b,4,d);Zv(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function Zv(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;j=h>>1;i=d;k=0;h=f+(0-(j<<1)<<1)|0;while(1){m=e[h>>1]|0;l=e[h+2>>1]|0;n=e[h+-2>>1]|0;f=e[h+4>>1]|0;p=_(f+n|0,-5)|0;o=e[h+6>>1]|0;c[i>>2]=(e[h+-4>>1]|0)+((l+m|0)*20|0)+p+o;m=_(o+m|0,-5)|0;c[i+4>>2]=n+((f+l|0)*20|0)+m+(e[h+8>>1]|0);k=k+1|0;if((k|0)==7)break;else{i=i+16|0;h=h+(j<<1)|0}}m=g>>1;i=c[d+16>>2]|0;j=c[d+32>>2]|0;f=c[d+48>>2]|0;k=c[d+64>>2]|0;g=c[d+80>>2]|0;l=c[d+96>>2]|0;h=(c[d>>2]|0)+512+g+((f+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;h=i+512+l+((k+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a+(m<<1)>>1]=h;a=a+2|0;i=c[d+20>>2]|0;j=c[d+36>>2]|0;f=c[d+52>>2]|0;k=c[d+68>>2]|0;g=c[d+84>>2]|0;l=c[d+100>>2]|0;h=(c[d+4>>2]|0)+512+g+((f+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;h=i+512+l+((k+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(h>>>0<4096){o=h;o=o&65535;p=a+(m<<1)|0;b[p>>1]=o;return}o=0-h>>31&4095;o=o&65535;p=a+(m<<1)|0;b[p>>1]=o;return}function _v(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Sv(h,b,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Wv(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function $v(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;f=i;i=i+32|0;h=f;g=h+8|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Wv(a,g,e);i=f;return}function aw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+160|0;k=f+128|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Wv(h,j,4);Zv(g,f+16|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function bw(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+112|0;Zv(a,d,b,c,c);i=d;return}function cw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+160|0;k=f+128|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))+2|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Wv(h,j,4);Zv(g,f+16|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function dw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+16>>2]=b;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Wv(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function ew(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Sv(h,b+e|0,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Wv(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function fw(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+128|0;g=e+8|0;f=e;Sv(g,b+d|0,4,d);Zv(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function gw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Sv(h,b+e|0,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Wv(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function hw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;f=b;g=a;h=0;while(1){i=g;l=c[i>>2]|0;i=c[i+4>>2]|0;m=f;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^l|0,m^i|0,1)|0;k=EF(n|l|0,m|i|0,k&2147450879|0,C&2147450879|0)|0;i=g;c[i>>2]=k;c[i+4>>2]=C;i=g+8|0;k=i;m=c[k>>2]|0;k=c[k+4>>2]|0;l=f+8|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;c[i>>2]=j;c[i+4>>2]=C;h=h+1|0;if((h|0)==16)break;else{f=f+e|0;g=g+e|0}}h=b+16|0;f=a+16|0;g=0;while(1){n=f;k=c[n>>2]|0;n=c[n+4>>2]|0;j=h;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(i^k|0,j^n|0,1)|0;l=EF(i|k|0,j|n|0,l&2147450879|0,C&2147450879|0)|0;n=f;c[n>>2]=l;c[n+4>>2]=C;n=f+8|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;k=h+8|0;i=k;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(i^j|0,k^l|0,1)|0;m=EF(i|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;g=g+1|0;if((g|0)==16)break;else{h=h+e|0;f=f+e|0}}return}function iw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;f=k;Su(f,b,32,e);h=f+16|0;j=b+16|0;Su(h,j,32,e);g=b+(e<<3)|0;Su(f+256|0,g,32,e);Su(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=g<<5;r=f+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=b+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{u=_(f,e)|0;s=j+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=f<<5;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=a+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=j+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=a+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function jw(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;kw(a,b,c,c);kw(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;kw(a,b,c,c);kw(a+16|0,b+16|0,c,c);return}function kw(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=d>>1;p=f>>1;o=0;while(1){h=c+2|0;f=c+-2|0;j=c+4|0;d=_((e[j>>1]|0)+(e[f>>1]|0)|0,-5)|0;l=c+6|0;d=(((e[h>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[l>>1]|0)+(e[c>>1]|0)|0,-5)|0;n=c+8|0;d=(((e[j>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[n>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[n>>1]|0)+(e[h>>1]|0)|0,-5)|0;m=c+10|0;d=(((e[l>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[m>>1]|0)+(e[j>>1]|0)|0,-5)|0;k=c+12|0;d=(((e[n>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[h>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+8|0;d=_((e[k>>1]|0)+(e[l>>1]|0)|0,-5)|0;i=c+14|0;d=(((e[m>>1]|0)+(e[n>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+10|0;d=_((e[i>>1]|0)+(e[n>>1]|0)|0,-5)|0;h=c+16|0;d=(((e[k>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+12|0;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+18|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[n>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+14|0;d=_((e[g>>1]|0)+(e[k>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;o=o+1|0;if((o|0)==8)break;else{a=a+(q<<1)|0;c=c+(p<<1)|0}}return}function lw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;g=k;Su(g,b,32,e);j=g+16|0;Su(j,b+16|0,32,e);f=b+(e<<3)|0;Su(g+256|0,f,32,e);Su(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=h<<5;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=f+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{u=_(a,e)|0;s=f+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=a<<5;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=g+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=f+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=g+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function mw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;Wu(l,g,32,32);h=l+16|0;j=k+80|0;Wu(h,j,32,32);Wu(l+256|0,k+320|0,32,32);Wu(l+272|0,k+336|0,32,32);e=0;do{o=e<<5;n=g+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=l+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(e,f)|0;k=b+p|0;t=k;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[k>>2]=s;c[k+4>>2]=C;o=o|8;k=g+o|0;s=k;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=l+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^k|0,1)|0;r=EF(t|s|0,o|k|0,r&2147450879|0,C&2147450879|0)|0;k=C;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=j+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=j+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function nw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Su(k,e,32,f);m=k+16|0;Su(m,e+16|0,32,f);g=e+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function ow(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+3712|0;n=k;f=k+3200|0;g=k+2688|0;Su(f,b,32,e);j=f+16|0;o=b+16|0;Su(j,o,32,e);m=b+(e<<3)|0;Su(f+256|0,m,32,e);l=m+16|0;Su(f+272|0,l,32,e);Zu(g,n,b,32,32,e);h=g+16|0;b=n+32|0;Zu(h,b,o,32,32,e);Zu(g+256|0,n,m,32,32,e);Zu(g+272|0,b,l,32,32,e);b=0;do{n=b<<5;m=f+n|0;t=m;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=g+n|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=_(b,e)|0;l=a+o|0;s=l;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^m|0,1)|0;q=EF(u|t|0,r|m|0,q&2147450879|0,C&2147450879|0)|0;m=C;r=JF(q^p|0,m^s|0,1)|0;r=EF(q|p|0,m|s|0,r&2147450879|0,C&2147450879|0)|0;c[l>>2]=r;c[l+4>>2]=C;n=n|8;l=f+n|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=g+n|0;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;p=c[m>>2]|0;m=c[m+4>>2]|0;q=JF(s^r|0,n^l|0,1)|0;q=EF(s|r|0,n|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=j+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=j+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function pw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Su(k,e,32,f);m=k+16|0;Su(m,e+16|0,32,f);g=e+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function qw(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}rw(b,h+64|0,e,32);rw(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;rw(n,h+320|0,e,32);rw(n+16|0,h+336|0,e,32);i=j;return}function rw(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=d>>1;t=f>>1;D=_(t,-2)|0;E=0-t|0;F=t<<1;G=t*3|0;H=t<<2;I=t*5|0;J=t*6|0;K=t*7|0;u=t<<3;v=t*9|0;w=t*10|0;x=L<<1;y=L*3|0;z=L<<2;A=L*5|0;B=L*6|0;C=L*7|0;s=0;while(1){f=e[c+(E<<1)>>1]|0;h=e[c>>1]|0;i=e[c+(t<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(K<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;r=e[c+(w<<1)>>1]|0;d=(e[c+(D<<1)>>1]|0)+16+k+((i+h|0)*20|0)+(_(j+f|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+(L<<1)|0;d=f+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+(x<<1)|0;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(y<<1)|0;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(z<<1)|0;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(A<<1)|0;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(B<<1)|0;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(C<<1)|0;d=m+16+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;s=s+1|0;if((s|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function sw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Wu(o,k+64|0,32,32);l=o+16|0;Wu(l,k+80|0,32,32);Wu(o+256|0,k+320|0,32,32);Wu(o+272|0,k+336|0,32,32);Zu(n,m,e,32,32,f);j=n+16|0;s=m+32|0;Zu(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Zu(n+256|0,m,g,32,32,f);Zu(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function tw(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+2688|0;f=d;uw(a,f,b,c,32,c);e=f+32|0;uw(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;uw(a,f,b,c,32,c);uw(a+16|0,e,b+16|0,c,32,c);i=d;return}function uw(a,d,f,g,h,i){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0;k=i>>1;m=h*13|0;j=d;l=0;i=f+(0-(k<<1)<<1)|0;while(1){M=e[i>>1]|0;L=e[i+2>>1]|0;J=e[i+-2>>1]|0;K=e[i+4>>1]|0;H=_(K+J|0,-5)|0;I=e[i+6>>1]|0;c[j>>2]=(e[i+-4>>1]|0)+((L+M|0)*20|0)+H+I;H=_(I+M|0,-5)|0;G=e[i+8>>1]|0;c[j+4>>2]=J+((K+L|0)*20|0)+H+G;H=_(G+L|0,-5)|0;J=e[i+10>>1]|0;c[j+8>>2]=M+((I+K|0)*20|0)+H+J;H=_(J+K|0,-5)|0;M=e[i+12>>1]|0;c[j+12>>2]=L+((G+I|0)*20|0)+H+M;H=_(M+I|0,-5)|0;L=e[i+14>>1]|0;c[j+16>>2]=K+((J+G|0)*20|0)+H+L;H=_(L+G|0,-5)|0;K=e[i+16>>1]|0;c[j+20>>2]=I+((M+J|0)*20|0)+H+K;H=_(K+J|0,-5)|0;I=e[i+18>>1]|0;c[j+24>>2]=G+((L+M|0)*20|0)+H+I;M=_(I+M|0,-5)|0;c[j+28>>2]=J+((K+L|0)*20|0)+M+(e[i+20>>1]|0);l=l+1|0;if((l|0)==13)break;else{j=j+(h<<2)|0;i=i+(k<<1)|0}}v=g>>1;w=_(h,-2)|0;x=0-h|0;y=h<<1;z=h*3|0;A=h<<2;B=h*5|0;C=h*6|0;D=h*7|0;E=h<<3;F=h*9|0;G=h*10|0;H=v<<1;I=v*3|0;J=v<<2;K=v*5|0;L=v*6|0;M=v*7|0;u=d+(m<<2)+((_(h,-11)|0)<<2)|0;t=0;while(1){j=c[u+(x<<2)>>2]|0;f=c[u>>2]|0;l=c[u+(h<<2)>>2]|0;g=c[u+(y<<2)>>2]|0;m=c[u+(z<<2)>>2]|0;d=c[u+(A<<2)>>2]|0;n=c[u+(B<<2)>>2]|0;o=c[u+(C<<2)>>2]|0;p=c[u+(D<<2)>>2]|0;q=c[u+(E<<2)>>2]|0;r=c[u+(F<<2)>>2]|0;s=c[u+(G<<2)>>2]|0;i=(c[u+(w<<2)>>2]|0)+512+m+((l+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a>>1]=((e[a>>1]|0)+1+i|0)>>>1;k=a+(v<<1)|0;i=j+512+d+((g+l|0)*20|0)+(_(m+f|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[k>>1]=((e[k>>1]|0)+1+i|0)>>>1;j=a+(H<<1)|0;i=f+512+n+((m+g|0)*20|0)+(_(d+l|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(I<<1)|0;i=l+512+o+((d+m|0)*20|0)+(_(n+g|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(J<<1)|0;i=g+512+p+((n+d|0)*20|0)+(_(o+m|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(K<<1)|0;i=m+512+q+((o+n|0)*20|0)+(_(p+d|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(L<<1)|0;i=d+512+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(M<<1)|0;i=n+512+s+((q+p|0)*20|0)+(_(r+o|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;t=t+1|0;if((t|0)==8)break;else{u=u+4|0;a=a+2|0}}return}function vw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Wu(o,k+64|0,32,32);l=o+16|0;Wu(l,k+80|0,32,32);Wu(o+256|0,k+320|0,32,32);Wu(o+272|0,k+336|0,32,32);Zu(n,m,e,32,32,f);j=n+16|0;s=m+32|0;Zu(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Zu(n+256|0,m,g,32,32,f);Zu(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function ww(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(k,l+64|0,32,32);j=k+16|0;Wu(j,l+80|0,32,32);Wu(k+256|0,l+320|0,32,32);Wu(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{o=g<<5;n=e+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=k+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(g,f)|0;h=b+p|0;t=h;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[h>>2]=s;c[h+4>>2]=C;o=o|8;h=e+o|0;s=h;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=k+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^h|0,1)|0;r=EF(t|s|0,o|h|0,r&2147450879|0,C&2147450879|0)|0;h=C;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{u=g<<5;t=e+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=j+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=h+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=e+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=h+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function xw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Su(k,g,32,f);m=k+16|0;Su(m,g+16|0,32,f);g=g+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}
function vx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+3712|0;m=k;f=k+3200|0;g=k+2688|0;j=b+e|0;gx(f,j,32,e);h=f+16|0;gx(h,j+16|0,32,e);n=e<<3;j=j+n|0;gx(f+256|0,j,32,e);gx(f+272|0,j+16|0,32,e);nx(g,m,b,32,32,e);j=g+16|0;l=m+32|0;nx(j,l,b+16|0,32,32,e);b=b+n|0;nx(g+256|0,m,b,32,32,e);nx(g+272|0,l,b+16|0,32,32,e);b=0;do{o=b<<5;n=f+o|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+o|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=JF(m^q|0,l^n|0,1)|0;p=EF(m|q|0,l|n|0,p&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=p;c[l+4>>2]=C;o=o|8;l=f+o|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=g+o|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=h+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=h+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function wx(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;gx(k,g,32,f);m=k+16|0;gx(m,g+16|0,32,f);g=g+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function xx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;f=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=a;c[g>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[g+4>>2]=h;g=b+8|0;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a+8|0;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;b=b+e|0;f=a+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function yx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;f=h;gx(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g<<4;o=f+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function zx(a,b,c){a=a|0;b=b|0;c=c|0;gx(a,b,c,c);return}function Ax(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;g=h;gx(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f<<4;o=g+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Bx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;kx(g,f,16,16);b=0;do{j=b<<4;m=f+j|0;o=c[m>>2]|0;m=c[m+4>>2]|0;k=g+j|0;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;n=JF(l^o|0,k^m|0,1)|0;n=EF(l|o|0,k|m|0,n&2147450879|0,C&2147450879|0)|0;m=_(b,e)|0;k=a+m|0;c[k>>2]=n;c[k+4>>2]=C;j=j|8;k=f+j|0;n=c[k>>2]|0;k=c[k+4>>2]|0;j=g+j|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;m=a+(m+8)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Cx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Dx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;gx(f,b,16,e);nx(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Ex(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Fx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;kx(a,g,e,16);i=f;return}function Gx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;kx(g,j,16,16);nx(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Hx(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+832|0;nx(a,d,b,c,16,c);i=d;return}function Ix(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;kx(g,j,16,16);nx(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Jx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;kx(g,f,16,16);f=0;do{k=f<<4;n=b+k|0;o=c[n>>2]|0;n=c[n+4>>2]|0;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(f,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=b+k|0;j=c[l>>2]|0;l=c[l+4>>2]|0;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Kx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Lx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;gx(f,b+e|0,16,e);nx(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Mx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Nx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=a;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[f+4>>2]=h;b=b+e|0;f=a+e|0;h=b;a=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=f;c[g>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[g+4>>2]=h;b=b+e|0;f=f+e|0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a=f;c[a>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[a+4>>2]=g;b=b+e|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=f+e|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function Ox(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;Px(h,b,8,e);g=b;n=g;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h;k=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(k^n|0,m^g|0,1)|0;l=EF(k|n|0,m|g|0,l&2147450879|0,C&2147450879|0)|0;g=a;c[g>>2]=l;c[g+4>>2]=C;g=b+e|0;l=g;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;k=JF(n^l|0,m^g|0,1)|0;k=EF(n|l|0,m|g|0,k&2147450879|0,C&2147450879|0)|0;g=a+e|0;c[g>>2]=k;c[g+4>>2]=C;g=e<<1;k=b+g|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;g=a+g|0;c[g>>2]=j;c[g+4>>2]=C;e=e*3|0;g=b+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Px(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=f>>1;k=0;while(1){j=c+2|0;f=c+-2|0;h=c+4|0;d=_((e[h>>1]|0)+(e[f>>1]|0)|0,-5)|0;i=c+6|0;d=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=c+8|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+2>>1]=d;d=_((e[g>>1]|0)+(e[j>>1]|0)|0,-5)|0;f=c+10|0;d=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+4>>1]=d;d=_((e[f>>1]|0)+(e[h>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+6>>1]=d;k=k+1|0;if((k|0)==4)break;else{a=a+(m<<1)|0;c=c+(l<<1)|0}}return}function Qx(a,b,c){a=a|0;b=b|0;c=c|0;Px(a,b,c,c);return}function Rx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;Px(h,b,8,e);g=b+2|0;j=g;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h;b=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(b^n|0,m^j|0,1)|0;l=EF(b|n|0,m|j|0,l&2147450879|0,C&2147450879|0)|0;j=a;c[j>>2]=l;c[j+4>>2]=C;j=g+e|0;l=j;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(n^l|0,m^j|0,1)|0;b=EF(n|l|0,m|j|0,b&2147450879|0,C&2147450879|0)|0;j=a+e|0;c[j>>2]=b;c[j+4>>2]=C;j=e<<1;b=g+j|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;e=e*3|0;g=g+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Sx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=s+8|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=o;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+24|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+32|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=s+40|0;c[v>>2]=j;c[v+4>>2]=g;q=q+e|0;v=q;t=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;u=s+48|0;c[u>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[u+4>>2]=v;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;Tx(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Tx(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;o=d>>1;p=o<<1;q=o*3|0;n=0;while(1){f=e[c+-8>>1]|0;g=e[c>>1]|0;h=e[c+8>>1]|0;i=e[c+16>>1]|0;j=e[c+24>>1]|0;k=e[c+32>>1]|0;l=e[c+40>>1]|0;m=e[c+48>>1]|0;d=(e[c+-16>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(o<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(p<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(q<<1)>>1]=d;n=n+1|0;if((n|0)==4)break;else{a=a+2|0;c=c+2|0}}return}function Ux(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Px(g,b,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Tx(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Vx(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+352|0;f=e+32|0;g=e;Px(f,b,8,d);Wx(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function Wx(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;j=h>>1;i=d;k=0;h=f+(0-(j<<1)<<1)|0;while(1){l=e[h>>1]|0;o=e[h+2>>1]|0;n=e[h+-2>>1]|0;r=e[h+4>>1]|0;m=_(r+n|0,-5)|0;q=e[h+6>>1]|0;c[i>>2]=(e[h+-4>>1]|0)+((o+l|0)*20|0)+m+q;m=_(q+l|0,-5)|0;p=e[h+8>>1]|0;c[i+4>>2]=n+((r+o|0)*20|0)+m+p;m=_(p+o|0,-5)|0;n=e[h+10>>1]|0;c[i+8>>2]=l+((q+r|0)*20|0)+m+n;r=_(n+r|0,-5)|0;c[i+12>>2]=o+((p+q|0)*20|0)+r+(e[h+12>>1]|0);k=k+1|0;if((k|0)==9)break;else{i=i+32|0;h=h+(j<<1)|0}}p=g>>1;q=p<<1;r=p*3|0;o=d+64|0;n=0;while(1){i=c[o+-32>>2]|0;j=c[o>>2]|0;f=c[o+32>>2]|0;k=c[o+64>>2]|0;g=c[o+96>>2]|0;d=c[o+128>>2]|0;l=c[o+160>>2]|0;m=c[o+192>>2]|0;h=(c[o+-64>>2]|0)+512+g+((f+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;h=i+512+d+((k+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a+(p<<1)>>1]=h;h=j+512+l+((g+k|0)*20|0)+(_(d+f|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a+(q<<1)>>1]=h;h=f+512+m+((d+g|0)*20|0)+(_(l+k|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a+(r<<1)>>1]=h;n=n+1|0;if((n|0)==4)break;else{o=o+4|0;a=a+2|0}}return}function Xx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Px(g,b,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Tx(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Yx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;k=f;g=k+16|0;h=b+(0-(e<<1))|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+8|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+24|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+32|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+40|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+48|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+56|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Tx(a,g,e);i=f;return}function Zx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+432|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;Tx(g,k,8);Wx(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function _x(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+288|0;Wx(a,d,b,c,c);i=d;return}function $x(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+432|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)+2|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;Tx(g,k,8);Wx(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ay(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;b=q;r=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s;c[m>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[m+4>>2]=b;q=q+e|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s+8|0;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=o;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s+24|0;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+32|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+40|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;u=s+48|0;c[u>>2]=j;c[u+4>>2]=g;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;Tx(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function by(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Px(g,b+e|0,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Tx(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function cy(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+352|0;f=e+32|0;g=e;Px(f,b+d|0,8,d);Wx(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function dy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Px(g,b+e|0,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Tx(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ey(a,b,e){a=a|0;b=b|0;e=e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[a+e>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;return}function fy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;gy(g,b,4,e);h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function gy(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;j=c+2|0;k=c+-2|0;h=c+4|0;g=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;i=c+6|0;g=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+g+(e[i>>1]|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[a>>1]=g;g=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+g+(e[c+8>>1]|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[a+2>>1]=g;a=a+(d>>1<<1)|0;h=c+(f>>1<<1)|0;i=h+2|0;j=h+-2|0;k=h+4|0;g=_((e[k>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=h+6|0;g=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[h+-4>>1]|0)+g+(e[d>>1]|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[a>>1]=g;g=_((e[d>>1]|0)+(e[h>>1]|0)|0,-5)|0;g=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+g+(e[h+8>>1]|0)>>5;if(g>>>0<16384){c=g;c=c&65535;f=a+2|0;b[f>>1]=c;return}c=0-g>>31&16383;c=c&65535;f=a+2|0;b[f>>1]=c;return}function hy(a,b,c){a=a|0;b=b|0;c=c|0;gy(a,b,c,c);return}function iy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;gy(g,b,4,e);b=b+2|0;h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function jy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=b;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ky(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function ky(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=d>>1;j=e[c+-4>>1]|0;k=e[c>>1]|0;f=e[c+4>>1]|0;g=e[c+8>>1]|0;h=e[c+12>>1]|0;i=e[c+16>>1]|0;d=(e[c+-8>>1]|0)+16+h+((f+k|0)*20|0)+(_(g+j|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=j+16+i+((g+f|0)*20|0)+(_(h+k|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(l<<1)>>1]=d;a=a+2|0;f=e[c+-2>>1]|0;g=e[c+2>>1]|0;h=e[c+6>>1]|0;i=e[c+10>>1]|0;j=e[c+14>>1]|0;k=e[c+18>>1]|0;d=(e[c+-6>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0<16384){c=d;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}c=0-d>>31&16383;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}function ly(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;gy(h,b,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ky(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function my(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+128|0;g=e+8|0;f=e;gy(g,b,4,d);ny(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function ny(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;j=h>>1;i=d;k=0;h=f+(0-(j<<1)<<1)|0;while(1){m=e[h>>1]|0;l=e[h+2>>1]|0;n=e[h+-2>>1]|0;f=e[h+4>>1]|0;p=_(f+n|0,-5)|0;o=e[h+6>>1]|0;c[i>>2]=(e[h+-4>>1]|0)+((l+m|0)*20|0)+p+o;m=_(o+m|0,-5)|0;c[i+4>>2]=n+((f+l|0)*20|0)+m+(e[h+8>>1]|0);k=k+1|0;if((k|0)==7)break;else{i=i+16|0;h=h+(j<<1)|0}}m=g>>1;i=c[d+16>>2]|0;j=c[d+32>>2]|0;f=c[d+48>>2]|0;k=c[d+64>>2]|0;g=c[d+80>>2]|0;l=c[d+96>>2]|0;h=(c[d>>2]|0)+512+g+((f+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;h=i+512+l+((k+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a+(m<<1)>>1]=h;a=a+2|0;i=c[d+20>>2]|0;j=c[d+36>>2]|0;f=c[d+52>>2]|0;k=c[d+68>>2]|0;g=c[d+84>>2]|0;l=c[d+100>>2]|0;h=(c[d+4>>2]|0)+512+g+((f+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;h=i+512+l+((k+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(h>>>0<16384){o=h;o=o&65535;p=a+(m<<1)|0;b[p>>1]=o;return}o=0-h>>31&16383;o=o&65535;p=a+(m<<1)|0;b[p>>1]=o;return}function oy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;gy(h,b,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ky(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function py(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;f=i;i=i+32|0;h=f;g=h+8|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ky(a,g,e);i=f;return}function qy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+160|0;k=f+128|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ky(h,j,4);ny(g,f+16|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function ry(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+112|0;ny(a,d,b,c,c);i=d;return}function sy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+160|0;k=f+128|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))+2|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ky(h,j,4);ny(g,f+16|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function ty(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+16>>2]=b;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ky(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function uy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;gy(h,b+e|0,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ky(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function vy(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+128|0;g=e+8|0;f=e;gy(g,b+d|0,4,d);ny(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function wy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;gy(h,b+e|0,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ky(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function xy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;f=b;g=a;h=0;while(1){i=g;l=c[i>>2]|0;i=c[i+4>>2]|0;m=f;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^l|0,m^i|0,1)|0;k=EF(n|l|0,m|i|0,k&2147450879|0,C&2147450879|0)|0;i=g;c[i>>2]=k;c[i+4>>2]=C;i=g+8|0;k=i;m=c[k>>2]|0;k=c[k+4>>2]|0;l=f+8|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;c[i>>2]=j;c[i+4>>2]=C;h=h+1|0;if((h|0)==16)break;else{f=f+e|0;g=g+e|0}}h=b+16|0;f=a+16|0;g=0;while(1){n=f;k=c[n>>2]|0;n=c[n+4>>2]|0;j=h;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(i^k|0,j^n|0,1)|0;l=EF(i|k|0,j|n|0,l&2147450879|0,C&2147450879|0)|0;n=f;c[n>>2]=l;c[n+4>>2]=C;n=f+8|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;k=h+8|0;i=k;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(i^j|0,k^l|0,1)|0;m=EF(i|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;g=g+1|0;if((g|0)==16)break;else{h=h+e|0;f=f+e|0}}return}function yy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;f=k;gx(f,b,32,e);h=f+16|0;j=b+16|0;gx(h,j,32,e);g=b+(e<<3)|0;gx(f+256|0,g,32,e);gx(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=g<<5;r=f+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=b+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{u=_(f,e)|0;s=j+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=f<<5;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=a+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=j+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=a+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function zy(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;Ay(a,b,c,c);Ay(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;Ay(a,b,c,c);Ay(a+16|0,b+16|0,c,c);return}function Ay(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=d>>1;p=f>>1;o=0;while(1){h=c+2|0;f=c+-2|0;j=c+4|0;d=_((e[j>>1]|0)+(e[f>>1]|0)|0,-5)|0;l=c+6|0;d=(((e[h>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[l>>1]|0)+(e[c>>1]|0)|0,-5)|0;n=c+8|0;d=(((e[j>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[n>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[n>>1]|0)+(e[h>>1]|0)|0,-5)|0;m=c+10|0;d=(((e[l>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[m>>1]|0)+(e[j>>1]|0)|0,-5)|0;k=c+12|0;d=(((e[n>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[h>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+8|0;d=_((e[k>>1]|0)+(e[l>>1]|0)|0,-5)|0;i=c+14|0;d=(((e[m>>1]|0)+(e[n>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+10|0;d=_((e[i>>1]|0)+(e[n>>1]|0)|0,-5)|0;h=c+16|0;d=(((e[k>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+12|0;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+18|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[n>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+14|0;d=_((e[g>>1]|0)+(e[k>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;o=o+1|0;if((o|0)==8)break;else{a=a+(q<<1)|0;c=c+(p<<1)|0}}return}function By(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;g=k;gx(g,b,32,e);j=g+16|0;gx(j,b+16|0,32,e);f=b+(e<<3)|0;gx(g+256|0,f,32,e);gx(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=h<<5;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=f+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{u=_(a,e)|0;s=f+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=a<<5;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=g+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=f+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=g+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Cy(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;kx(l,g,32,32);h=l+16|0;j=k+80|0;kx(h,j,32,32);kx(l+256|0,k+320|0,32,32);kx(l+272|0,k+336|0,32,32);e=0;do{o=e<<5;n=g+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=l+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(e,f)|0;k=b+p|0;t=k;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[k>>2]=s;c[k+4>>2]=C;o=o|8;k=g+o|0;s=k;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=l+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^k|0,1)|0;r=EF(t|s|0,o|k|0,r&2147450879|0,C&2147450879|0)|0;k=C;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=j+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=j+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Dy(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;gx(k,e,32,f);m=k+16|0;gx(m,e+16|0,32,f);g=e+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Ey(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+3712|0;n=k;f=k+3200|0;g=k+2688|0;gx(f,b,32,e);j=f+16|0;o=b+16|0;gx(j,o,32,e);m=b+(e<<3)|0;gx(f+256|0,m,32,e);l=m+16|0;gx(f+272|0,l,32,e);nx(g,n,b,32,32,e);h=g+16|0;b=n+32|0;nx(h,b,o,32,32,e);nx(g+256|0,n,m,32,32,e);nx(g+272|0,b,l,32,32,e);b=0;do{n=b<<5;m=f+n|0;t=m;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=g+n|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=_(b,e)|0;l=a+o|0;s=l;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^m|0,1)|0;q=EF(u|t|0,r|m|0,q&2147450879|0,C&2147450879|0)|0;m=C;r=JF(q^p|0,m^s|0,1)|0;r=EF(q|p|0,m|s|0,r&2147450879|0,C&2147450879|0)|0;c[l>>2]=r;c[l+4>>2]=C;n=n|8;l=f+n|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=g+n|0;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;p=c[m>>2]|0;m=c[m+4>>2]|0;q=JF(s^r|0,n^l|0,1)|0;q=EF(s|r|0,n|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=j+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=j+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Fy(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;gx(k,e,32,f);m=k+16|0;gx(m,e+16|0,32,f);g=e+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Gy(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}Hy(b,h+64|0,e,32);Hy(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;Hy(n,h+320|0,e,32);Hy(n+16|0,h+336|0,e,32);i=j;return}function Hy(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=d>>1;t=f>>1;D=_(t,-2)|0;E=0-t|0;F=t<<1;G=t*3|0;H=t<<2;I=t*5|0;J=t*6|0;K=t*7|0;u=t<<3;v=t*9|0;w=t*10|0;x=L<<1;y=L*3|0;z=L<<2;A=L*5|0;B=L*6|0;C=L*7|0;s=0;while(1){f=e[c+(E<<1)>>1]|0;h=e[c>>1]|0;i=e[c+(t<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(K<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;r=e[c+(w<<1)>>1]|0;d=(e[c+(D<<1)>>1]|0)+16+k+((i+h|0)*20|0)+(_(j+f|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+(L<<1)|0;d=f+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+(x<<1)|0;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(y<<1)|0;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(z<<1)|0;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(A<<1)|0;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(B<<1)|0;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(C<<1)|0;d=m+16+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;s=s+1|0;if((s|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function Iy(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}kx(o,k+64|0,32,32);l=o+16|0;kx(l,k+80|0,32,32);kx(o+256|0,k+320|0,32,32);kx(o+272|0,k+336|0,32,32);nx(n,m,e,32,32,f);j=n+16|0;s=m+32|0;nx(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;nx(n+256|0,m,g,32,32,f);nx(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function Jy(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+2688|0;f=d;Ky(a,f,b,c,32,c);e=f+32|0;Ky(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;Ky(a,f,b,c,32,c);Ky(a+16|0,e,b+16|0,c,32,c);i=d;return}function Ky(a,d,f,g,h,i){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0;k=i>>1;m=h*13|0;j=d;l=0;i=f+(0-(k<<1)<<1)|0;while(1){M=e[i>>1]|0;L=e[i+2>>1]|0;J=e[i+-2>>1]|0;K=e[i+4>>1]|0;H=_(K+J|0,-5)|0;I=e[i+6>>1]|0;c[j>>2]=(e[i+-4>>1]|0)+((L+M|0)*20|0)+H+I;H=_(I+M|0,-5)|0;G=e[i+8>>1]|0;c[j+4>>2]=J+((K+L|0)*20|0)+H+G;H=_(G+L|0,-5)|0;J=e[i+10>>1]|0;c[j+8>>2]=M+((I+K|0)*20|0)+H+J;H=_(J+K|0,-5)|0;M=e[i+12>>1]|0;c[j+12>>2]=L+((G+I|0)*20|0)+H+M;H=_(M+I|0,-5)|0;L=e[i+14>>1]|0;c[j+16>>2]=K+((J+G|0)*20|0)+H+L;H=_(L+G|0,-5)|0;K=e[i+16>>1]|0;c[j+20>>2]=I+((M+J|0)*20|0)+H+K;H=_(K+J|0,-5)|0;I=e[i+18>>1]|0;c[j+24>>2]=G+((L+M|0)*20|0)+H+I;M=_(I+M|0,-5)|0;c[j+28>>2]=J+((K+L|0)*20|0)+M+(e[i+20>>1]|0);l=l+1|0;if((l|0)==13)break;else{j=j+(h<<2)|0;i=i+(k<<1)|0}}v=g>>1;w=_(h,-2)|0;x=0-h|0;y=h<<1;z=h*3|0;A=h<<2;B=h*5|0;C=h*6|0;D=h*7|0;E=h<<3;F=h*9|0;G=h*10|0;H=v<<1;I=v*3|0;J=v<<2;K=v*5|0;L=v*6|0;M=v*7|0;u=d+(m<<2)+((_(h,-11)|0)<<2)|0;t=0;while(1){j=c[u+(x<<2)>>2]|0;f=c[u>>2]|0;l=c[u+(h<<2)>>2]|0;g=c[u+(y<<2)>>2]|0;m=c[u+(z<<2)>>2]|0;d=c[u+(A<<2)>>2]|0;n=c[u+(B<<2)>>2]|0;o=c[u+(C<<2)>>2]|0;p=c[u+(D<<2)>>2]|0;q=c[u+(E<<2)>>2]|0;r=c[u+(F<<2)>>2]|0;s=c[u+(G<<2)>>2]|0;i=(c[u+(w<<2)>>2]|0)+512+m+((l+f|0)*20|0)+(_(g+j|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a>>1]=((e[a>>1]|0)+1+i|0)>>>1;k=a+(v<<1)|0;i=j+512+d+((g+l|0)*20|0)+(_(m+f|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[k>>1]=((e[k>>1]|0)+1+i|0)>>>1;j=a+(H<<1)|0;i=f+512+n+((m+g|0)*20|0)+(_(d+l|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(I<<1)|0;i=l+512+o+((d+m|0)*20|0)+(_(n+g|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(J<<1)|0;i=g+512+p+((n+d|0)*20|0)+(_(o+m|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(K<<1)|0;i=m+512+q+((o+n|0)*20|0)+(_(p+d|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(L<<1)|0;i=d+512+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;j=a+(M<<1)|0;i=n+512+s+((q+p|0)*20|0)+(_(r+o|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[j>>1]=((e[j>>1]|0)+1+i|0)>>>1;t=t+1|0;if((t|0)==8)break;else{u=u+4|0;a=a+2|0}}return}function Ly(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}kx(o,k+64|0,32,32);l=o+16|0;kx(l,k+80|0,32,32);kx(o+256|0,k+320|0,32,32);kx(o+272|0,k+336|0,32,32);nx(n,m,e,32,32,f);j=n+16|0;s=m+32|0;nx(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;nx(n+256|0,m,g,32,32,f);nx(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function My(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(k,l+64|0,32,32);j=k+16|0;kx(j,l+80|0,32,32);kx(k+256|0,l+320|0,32,32);kx(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{o=g<<5;n=e+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=k+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(g,f)|0;h=b+p|0;t=h;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[h>>2]=s;c[h+4>>2]=C;o=o|8;h=e+o|0;s=h;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=k+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^h|0,1)|0;r=EF(t|s|0,o|h|0,r&2147450879|0,C&2147450879|0)|0;h=C;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{u=g<<5;t=e+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=j+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=h+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=e+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=h+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Ny(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;gx(k,g,32,f);m=k+16|0;gx(m,g+16|0,32,f);g=g+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Oy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+3712|0;m=k;f=k+3200|0;g=k+2688|0;j=b+e|0;gx(f,j,32,e);h=f+16|0;gx(h,j+16|0,32,e);n=e<<3;j=j+n|0;gx(f+256|0,j,32,e);gx(f+272|0,j+16|0,32,e);nx(g,m,b,32,32,e);j=g+16|0;l=m+32|0;nx(j,l,b+16|0,32,32,e);b=b+n|0;nx(g+256|0,m,b,32,32,e);nx(g+272|0,l,b+16|0,32,32,e);b=0;do{m=b<<5;l=f+m|0;t=l;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;n=_(b,e)|0;o=a+n|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^l|0,1)|0;q=EF(u|t|0,r|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;r=JF(q^p|0,l^s|0,1)|0;r=EF(q|p|0,l|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;m=m|8;o=f+m|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+m|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;p=c[l>>2]|0;l=c[l+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=h+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=h+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Py(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;gx(k,g,32,f);m=k+16|0;gx(m,g+16|0,32,f);g=g+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}
function yw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+3712|0;m=k;f=k+3200|0;g=k+2688|0;j=b+e|0;Su(f,j,32,e);h=f+16|0;Su(h,j+16|0,32,e);n=e<<3;j=j+n|0;Su(f+256|0,j,32,e);Su(f+272|0,j+16|0,32,e);Zu(g,m,b,32,32,e);j=g+16|0;l=m+32|0;Zu(j,l,b+16|0,32,32,e);b=b+n|0;Zu(g+256|0,m,b,32,32,e);Zu(g+272|0,l,b+16|0,32,32,e);b=0;do{m=b<<5;l=f+m|0;t=l;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;n=_(b,e)|0;o=a+n|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^l|0,1)|0;q=EF(u|t|0,r|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;r=JF(q^p|0,l^s|0,1)|0;r=EF(q|p|0,l|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;m=m|8;o=f+m|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+m|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;p=c[l>>2]|0;l=c[l+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=h+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=h+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function zw(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Su(k,g,32,f);m=k+16|0;Su(m,g+16|0,32,f);g=g+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Aw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;j=b;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^f|0,1)|0;i=EF(k|g|0,j|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;f=a+8|0;i=f;j=c[i>>2]|0;i=c[i+4>>2]|0;g=b+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[f>>2]=h;c[f+4>>2]=C;f=b+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;i=f;g=i;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(g^h|0,i^b|0,1)|0;j=EF(g|h|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^i|0,h^j|0,1)|0;k=EF(g|i|0,h|j|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;j=f;h=j;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(h^k|0,j^b|0,1)|0;i=EF(h|k|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^j|0,k^i|0,1)|0;g=EF(h|j|0,k|i|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;i=f;k=i;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(k^g|0,i^b|0,1)|0;j=EF(k|g|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^i|0,g^j|0,1)|0;h=EF(k|i|0,g|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;j=f;g=j;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(g^h|0,j^b|0,1)|0;i=EF(g|h|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^j|0,h^i|0,1)|0;k=EF(g|j|0,h|i|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;i=f;h=i;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(h^k|0,i^b|0,1)|0;j=EF(h|k|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^i|0,k^j|0,1)|0;g=EF(h|i|0,k|j|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;j=f;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^b|0,1)|0;i=EF(k|g|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;e=a+e|0;a=e;b=c[a>>2]|0;a=c[a+4>>2]|0;h=f;i=h;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=JF(i^b|0,h^a|0,1)|0;g=EF(i|b|0,h|a|0,g&2147450879|0,C&2147450879|0)|0;a=e;c[a>>2]=g;c[a+4>>2]=C;e=e+8|0;a=e;g=c[a>>2]|0;a=c[a+4>>2]|0;f=f+8|0;h=f;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;return}function Bw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;f=h;Su(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g<<4;p=f+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=f+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function Cw(a,b,c){a=a|0;b=b|0;c=c|0;kw(a,b,c,c);return}function Dw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;g=h;Su(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f<<4;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Ew(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;Wu(g,f,16,16);b=0;do{l=b<<4;k=f+l|0;r=c[k>>2]|0;k=c[k+4>>2]|0;p=g+l|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=_(b,e)|0;j=a+m|0;q=j;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^k|0,1)|0;o=EF(s|r|0,p|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;p=JF(o^n|0,k^q|0,1)|0;p=EF(o|n|0,k|q|0,p&2147450879|0,C&2147450879|0)|0;c[j>>2]=p;c[j+4>>2]=C;l=l|8;j=f+l|0;p=c[j>>2]|0;j=c[j+4>>2]|0;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=a+(m+8)|0;k=m;n=c[k>>2]|0;k=c[k+4>>2]|0;o=JF(q^p|0,l^j|0,1)|0;o=EF(q|p|0,l|j|0,o&2147450879|0,C&2147450879|0)|0;j=C;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Fw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Gw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;Su(f,b,16,e);Zu(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Hw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Iw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;rw(a,g,e,16);i=f;return}function Jw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Wu(g,j,16,16);Zu(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Kw(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+832|0;uw(a,d,b,c,16,c);i=d;return}function Lw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Wu(g,j,16,16);Zu(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Mw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;Wu(g,f,16,16);f=0;do{m=f<<4;l=b+m|0;r=c[l>>2]|0;l=c[l+4>>2]|0;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(f,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=b+m|0;p=c[k>>2]|0;k=c[k+4>>2]|0;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Nw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Ow(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;Su(f,b+e|0,16,e);Zu(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Pw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Su(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Wu(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Qw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;h=a;g=c[h>>2]|0;h=c[h+4>>2]|0;j=b;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;f=JF(i^g|0,j^h|0,1)|0;f=EF(i|g|0,j|h|0,f&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=f;c[h+4>>2]=C;h=b+e|0;a=a+e|0;f=a;j=c[f>>2]|0;f=c[f+4>>2]|0;b=h;g=b;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;i=JF(g^j|0,b^f|0,1)|0;i=EF(g|j|0,b|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;h=h+e|0;a=a+e|0;f=a;i=c[f>>2]|0;f=c[f+4>>2]|0;b=h;j=b;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=JF(j^i|0,b^f|0,1)|0;g=EF(j|i|0,b|f|0,g&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=g;c[f+4>>2]=C;a=a+e|0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;e=h+e|0;h=e;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;e=e+4|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;b=JF(h^g|0,e^f|0,1)|0;b=EF(h|g|0,e|f|0,b&2147450879|0,C&2147450879|0)|0;e=a;c[e>>2]=b;c[e+4>>2]=C;return}function Rw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;zv(l,b,8,e);j=b;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;k=a;n=c[k>>2]|0;k=c[k+4>>2]|0;g=JF(p^o|0,m^j|0,1)|0;g=EF(p|o|0,m|j|0,g&2147450879|0,C&2147450879|0)|0;j=C;m=JF(g^n|0,j^k|0,1)|0;m=EF(g|n|0,j|k|0,m&2147450879|0,C&2147450879|0)|0;k=a;c[k>>2]=m;c[k+4>>2]=C;k=b+e|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;g=a+e|0;o=g;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^k|0,1)|0;h=EF(n|m|0,j|k|0,h&2147450879|0,C&2147450879|0)|0;k=C;j=JF(h^p|0,k^o|0,1)|0;j=EF(h|p|0,k|o|0,j&2147450879|0,C&2147450879|0)|0;c[g>>2]=j;c[g+4>>2]=C;g=e<<1;j=b+g|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=l+16|0;p=c[k>>2]|0;k=c[k+4>>2]|0;g=a+g|0;h=g;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,k^j|0,1)|0;n=EF(p|o|0,k|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;k=JF(n^m|0,j^h|0,1)|0;k=EF(n|m|0,j|h|0,k&2147450879|0,C&2147450879|0)|0;c[g>>2]=k;c[g+4>>2]=C;e=e*3|0;g=b+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Sw(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=0;while(1){k=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;j=c+6|0;d=(((e[k>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[j>>1]|0)+(e[c>>1]|0)|0,-5)|0;h=c+8|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;g=c+10|0;d=(((e[j>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[g>>1]|0)+(e[i>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;l=l+1|0;if((l|0)==4)break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Tw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;zv(l,b,8,e);g=b+2|0;j=g;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;b=a;n=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(p^o|0,m^j|0,1)|0;k=EF(p|o|0,m|j|0,k&2147450879|0,C&2147450879|0)|0;j=C;m=JF(k^n|0,j^b|0,1)|0;m=EF(k|n|0,j|b|0,m&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=m;c[b+4>>2]=C;b=g+e|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;k=a+e|0;o=k;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^b|0,1)|0;h=EF(n|m|0,j|b|0,h&2147450879|0,C&2147450879|0)|0;b=C;j=JF(h^p|0,b^o|0,1)|0;j=EF(h|p|0,b|o|0,j&2147450879|0,C&2147450879|0)|0;c[k>>2]=j;c[k+4>>2]=C;k=e<<1;j=g+k|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;e=e*3|0;g=g+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Uw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=u+8|0;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=t;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+24|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+32|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=u+40|0;c[v>>2]=k;c[v+4>>2]=g;q=q+e|0;v=q;b=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;o=u+48|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=v;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;Dv(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Vw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;zv(g,b,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Dv(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Ww(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+352|0;f=e+32|0;k=e;zv(f,b,8,d);Gv(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function Xw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;zv(g,b,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Dv(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Yw(a,f,g){a=a|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=i;i=i+80|0;s=v;r=s+16|0;t=f+(0-(g<<1))|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=s;c[h>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[h+4>>2]=q;t=t+g|0;h=t;q=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=s+8|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=h;t=t+g|0;u=t;h=u;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=r;c[q>>2]=h;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+24|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+32|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;f=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=s+40|0;c[q>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+48|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+56|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;s=s+64|0;c[s>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[s+4>>2]=t;s=g>>1;t=s<<1;u=s*3|0;h=h&65535;f=0;while(1){j=e[r+-8>>1]|0;k=h&65535;l=e[r+8>>1]|0;m=e[r+16>>1]|0;n=e[r+24>>1]|0;o=e[r+32>>1]|0;p=e[r+40>>1]|0;q=e[r+48>>1]|0;g=(e[r+-16>>1]|0)+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[a>>1]=((e[a>>1]|0)+1+g|0)>>>1;h=a+(s<<1)|0;g=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(t<<1)|0;g=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(u<<1)|0;g=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;g=r+2|0;f=f+1|0;if((f|0)==4)break;h=b[g>>1]|0;a=a+2|0;r=g}i=v;return}function Zw(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+432|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;Dv(g,o,8);Gv(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function _w(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+288|0;j=u;s=f>>1;t=s<<1;g=j;h=0;f=d+(0-t<<1)|0;while(1){l=e[f>>1]|0;q=e[f+2>>1]|0;n=e[f+-2>>1]|0;r=e[f+4>>1]|0;m=_(r+n|0,-5)|0;p=e[f+6>>1]|0;c[g>>2]=(e[f+-4>>1]|0)+((q+l|0)*20|0)+m+p;m=_(p+l|0,-5)|0;o=e[f+8>>1]|0;c[g+4>>2]=((r+q|0)*20|0)+n+m+o;m=_(o+q|0,-5)|0;n=e[f+10>>1]|0;c[g+8>>2]=((p+r|0)*20|0)+l+m+n;r=_(n+r|0,-5)|0;c[g+12>>2]=((o+p|0)*20|0)+q+r+(e[f+12>>1]|0);h=h+1|0;if((h|0)==9)break;else{g=g+32|0;f=f+(s<<1)|0}}r=s*3|0;q=j+64|0;p=0;while(1){h=c[q+-32>>2]|0;d=c[q>>2]|0;j=c[q+32>>2]|0;k=c[q+64>>2]|0;l=c[q+96>>2]|0;m=c[q+128>>2]|0;n=c[q+160>>2]|0;o=c[q+192>>2]|0;f=(c[q+-64>>2]|0)+512+l+((j+d|0)*20|0)+(_(k+h|0,-5)|0)>>10;if(f>>>0>=4096)f=0-f>>31&4095;b[a>>1]=((e[a>>1]|0)+1+f|0)>>>1;g=a+(s<<1)|0;f=h+512+m+((k+j|0)*20|0)+(_(l+d|0,-5)|0)>>10;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=((e[g>>1]|0)+1+f|0)>>>1;g=a+(t<<1)|0;f=d+512+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>10;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=((e[g>>1]|0)+1+f|0)>>>1;g=a+(r<<1)|0;f=j+512+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>10;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=((e[g>>1]|0)+1+f|0)>>>1;p=p+1|0;if((p|0)==4)break;else{q=q+4|0;a=a+2|0}}i=u;return}function $w(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+432|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)+2|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;Dv(g,o,8);Gv(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ax(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;h=q;r=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u;c[n>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[n+4>>2]=h;q=q+e|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u+8|0;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=t;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u+24|0;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+32|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+40|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=u+48|0;c[o>>2]=k;c[o+4>>2]=g;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;Dv(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function bx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;zv(g,b+e|0,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Dv(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function cx(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+352|0;f=e+32|0;k=e;zv(f,b+d|0,8,d);Gv(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function dx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;zv(g,b+e|0,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Dv(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ex(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;f=b+8|0;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;h=a+8|0;c[h>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[h+4>>2]=f;h=b+e|0;f=a+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+8|0;i=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+8|0;c[f>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[f+4>>2]=h;f=a+16|0;h=b+16|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=b+24|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=a+24|0;c[b>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[b+4>>2]=g;b=h+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function fx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;f=k;gx(f,b,32,e);h=f+16|0;j=b+16|0;gx(h,j,32,e);g=b+(e<<3)|0;gx(f+256|0,g,32,e);gx(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g<<5;q=f+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=b+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{r=_(f,e)|0;p=j+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=f<<5;m=h+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=a+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=j+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=a+r|0;c[r>>2]=q;c[r+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function gx(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=d>>1;o=f>>1;n=0;while(1){g=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;k=c+6|0;d=(((e[g>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=_((e[k>>1]|0)+(e[c>>1]|0)|0,-5)|0;m=c+8|0;d=(((e[i>>1]|0)+(e[g>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+2>>1]=d;d=_((e[m>>1]|0)+(e[g>>1]|0)|0,-5)|0;l=c+10|0;d=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+4>>1]=d;d=_((e[l>>1]|0)+(e[i>>1]|0)|0,-5)|0;j=c+12|0;d=(((e[m>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[g>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+6>>1]=d;d=_((e[j>>1]|0)+(e[k>>1]|0)|0,-5)|0;h=c+14|0;d=(((e[l>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[i>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+8>>1]=d;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+16|0;d=(((e[j>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+10>>1]=d;d=_((e[g>>1]|0)+(e[l>>1]|0)|0,-5)|0;f=c+18|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+12>>1]=d;d=_((e[f>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+14>>1]=d;n=n+1|0;if((n|0)==8)break;else{a=a+(p<<1)|0;c=c+(o<<1)|0}}return}function hx(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;gx(a,b,c,c);gx(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;gx(a,b,c,c);gx(a+16|0,b+16|0,c,c);return}function ix(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;g=k;gx(g,b,32,e);j=g+16|0;gx(j,b+16|0,32,e);f=b+(e<<3)|0;gx(g+256|0,f,32,e);gx(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=h<<5;q=g+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=f+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{r=_(a,e)|0;p=f+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=a<<5;m=j+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=g+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=f+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=g+r|0;c[r>>2]=q;c[r+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function jx(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;kx(l,g,32,32);h=l+16|0;j=k+80|0;kx(h,j,32,32);kx(l+256|0,k+320|0,32,32);kx(l+272|0,k+336|0,32,32);e=0;do{k=e<<5;p=g+k|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=l+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(e,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;k=k|8;n=g+k|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;k=l+k|0;r=k;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=j+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=j+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function kx(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;K=d>>1;s=f>>1;C=_(s,-2)|0;D=0-s|0;E=s<<1;F=s*3|0;G=s<<2;H=s*5|0;I=s*6|0;J=s*7|0;t=s<<3;u=s*9|0;v=s*10|0;w=K<<1;x=K*3|0;y=K<<2;z=K*5|0;A=K*6|0;B=K*7|0;r=0;while(1){f=e[c+(D<<1)>>1]|0;g=e[c>>1]|0;h=e[c+(s<<1)>>1]|0;i=e[c+(E<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(t<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;d=(e[c+(C<<1)>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(K<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(w<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(x<<1)>>1]=d;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(y<<1)>>1]=d;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(z<<1)>>1]=d;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(A<<1)>>1]=d;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a+(B<<1)>>1]=d;r=r+1|0;if((r|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function lx(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;gx(k,e,32,f);m=k+16|0;gx(m,e+16|0,32,f);g=e+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function mx(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+3712|0;n=k;f=k+3200|0;g=k+2688|0;gx(f,b,32,e);j=f+16|0;o=b+16|0;gx(j,o,32,e);m=b+(e<<3)|0;gx(f+256|0,m,32,e);l=m+16|0;gx(f+272|0,l,32,e);nx(g,n,b,32,32,e);h=g+16|0;b=n+32|0;nx(h,b,o,32,32,e);nx(g+256|0,n,m,32,32,e);nx(g+272|0,b,l,32,32,e);b=0;do{l=b<<5;o=f+l|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=JF(n^q|0,m^o|0,1)|0;p=EF(n|q|0,m|o|0,p&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=p;c[m+4>>2]=C;l=l|8;m=f+l|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=j+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=h+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=j+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function nx(a,d,f,g,h,i){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;k=i>>1;m=h*13|0;j=d;l=0;i=f+(0-(k<<1)<<1)|0;while(1){L=e[i>>1]|0;K=e[i+2>>1]|0;I=e[i+-2>>1]|0;J=e[i+4>>1]|0;G=_(J+I|0,-5)|0;H=e[i+6>>1]|0;c[j>>2]=(e[i+-4>>1]|0)+((K+L|0)*20|0)+G+H;G=_(H+L|0,-5)|0;F=e[i+8>>1]|0;c[j+4>>2]=I+((J+K|0)*20|0)+G+F;G=_(F+K|0,-5)|0;I=e[i+10>>1]|0;c[j+8>>2]=L+((H+J|0)*20|0)+G+I;G=_(I+J|0,-5)|0;L=e[i+12>>1]|0;c[j+12>>2]=K+((F+H|0)*20|0)+G+L;G=_(L+H|0,-5)|0;K=e[i+14>>1]|0;c[j+16>>2]=J+((I+F|0)*20|0)+G+K;G=_(K+F|0,-5)|0;J=e[i+16>>1]|0;c[j+20>>2]=H+((L+I|0)*20|0)+G+J;G=_(J+I|0,-5)|0;H=e[i+18>>1]|0;c[j+24>>2]=F+((K+L|0)*20|0)+G+H;L=_(H+L|0,-5)|0;c[j+28>>2]=I+((J+K|0)*20|0)+L+(e[i+20>>1]|0);l=l+1|0;if((l|0)==13)break;else{j=j+(h<<2)|0;i=i+(k<<1)|0}}u=g>>1;v=_(h,-2)|0;w=0-h|0;x=h<<1;y=h*3|0;z=h<<2;A=h*5|0;B=h*6|0;C=h*7|0;D=h<<3;E=h*9|0;F=h*10|0;G=u<<1;H=u*3|0;I=u<<2;J=u*5|0;K=u*6|0;L=u*7|0;t=d+(m<<2)+((_(h,-11)|0)<<2)|0;s=0;while(1){j=c[t+(w<<2)>>2]|0;k=c[t>>2]|0;f=c[t+(h<<2)>>2]|0;l=c[t+(x<<2)>>2]|0;g=c[t+(y<<2)>>2]|0;m=c[t+(z<<2)>>2]|0;d=c[t+(A<<2)>>2]|0;n=c[t+(B<<2)>>2]|0;o=c[t+(C<<2)>>2]|0;p=c[t+(D<<2)>>2]|0;q=c[t+(E<<2)>>2]|0;r=c[t+(F<<2)>>2]|0;i=(c[t+(v<<2)>>2]|0)+512+g+((f+k|0)*20|0)+(_(l+j|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a>>1]=i;i=j+512+m+((l+f|0)*20|0)+(_(g+k|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(u<<1)>>1]=i;i=k+512+d+((g+l|0)*20|0)+(_(m+f|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(G<<1)>>1]=i;i=f+512+n+((m+g|0)*20|0)+(_(d+l|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(H<<1)>>1]=i;i=l+512+o+((d+m|0)*20|0)+(_(n+g|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(I<<1)>>1]=i;i=g+512+p+((n+d|0)*20|0)+(_(o+m|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(J<<1)>>1]=i;i=m+512+q+((o+n|0)*20|0)+(_(p+d|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(K<<1)>>1]=i;i=d+512+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>10;if(i>>>0>=16384)i=0-i>>31&16383;b[a+(L<<1)>>1]=i;s=s+1|0;if((s|0)==8)break;else{t=t+4|0;a=a+2|0}}return}function ox(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;gx(k,e,32,f);m=k+16|0;gx(m,e+16|0,32,f);g=e+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function px(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}kx(b,h+64|0,e,32);kx(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;kx(n,h+320|0,e,32);kx(n+16|0,h+336|0,e,32);i=j;return}function qx(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}kx(o,k+64|0,32,32);l=o+16|0;kx(l,k+80|0,32,32);kx(o+256|0,k+320|0,32,32);kx(o+272|0,k+336|0,32,32);nx(n,m,e,32,32,f);j=n+16|0;s=m+32|0;nx(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;nx(n+256|0,m,g,32,32,f);nx(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function rx(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+2688|0;f=d;nx(a,f,b,c,32,c);e=f+32|0;nx(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;nx(a,f,b,c,32,c);nx(a+16|0,e,b+16|0,c,32,c);i=d;return}function sx(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}kx(o,k+64|0,32,32);l=o+16|0;kx(l,k+80|0,32,32);kx(o+256|0,k+320|0,32,32);kx(o+272|0,k+336|0,32,32);nx(n,m,e,32,32,f);j=n+16|0;s=m+32|0;nx(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;nx(n+256|0,m,g,32,32,f);nx(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function tx(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(k,l+64|0,32,32);j=k+16|0;kx(j,l+80|0,32,32);kx(k+256|0,l+320|0,32,32);kx(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{h=g<<5;p=e+h|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=k+h|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(g,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;h=h|8;n=e+h|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;h=k+h|0;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{o=g<<5;r=e+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=j+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=h+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=e+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=h+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function ux(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;gx(k,g,32,f);m=k+16|0;gx(m,g+16|0,32,f);g=g+(f<<3)|0;gx(k+256|0,g,32,f);gx(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}kx(l,j+64|0,32,32);h=l+16|0;kx(h,j+80|0,32,32);kx(l+256|0,j+320|0,32,32);kx(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}
function Ps(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(k,l+64|0,32,32);j=k+16|0;Gs(j,l+80|0,32,32);Gs(k+256|0,l+320|0,32,32);Gs(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{h=g<<5;p=e+h|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=k+h|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(g,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;h=h|8;n=e+h|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;h=k+h|0;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{o=g<<5;r=e+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=j+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=h+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=e+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=h+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Qs(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Cs(k,g,32,f);m=k+16|0;Cs(m,g+16|0,32,f);g=g+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Rs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+2368|0;m=k;f=k+1856|0;g=k+1344|0;j=b+e|0;Cs(f,j,32,e);h=f+16|0;Cs(h,j+16|0,32,e);n=e<<3;j=j+n|0;Cs(f+256|0,j,32,e);Cs(f+272|0,j+16|0,32,e);Js(g,m,b,32,32,e);j=g+16|0;l=m+16|0;Js(j,l,b+16|0,32,32,e);b=b+n|0;Js(g+256|0,m,b,32,32,e);Js(g+272|0,l,b+16|0,32,32,e);b=0;do{o=b<<5;n=f+o|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+o|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=JF(m^q|0,l^n|0,1)|0;p=EF(m|q|0,l|n|0,p&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=p;c[l+4>>2]=C;o=o|8;l=f+o|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=g+o|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=h+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=h+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Ss(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Cs(k,g,32,f);m=k+16|0;Cs(m,g+16|0,32,f);g=g+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Ts(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;f=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=a;c[g>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[g+4>>2]=h;g=b+8|0;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a+8|0;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;b=b+e|0;f=a+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function Us(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;f=h;Cs(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g<<4;o=f+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function Vs(a,b,c){a=a|0;b=b|0;c=c|0;Cs(a,b,c,c);return}function Ws(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;g=h;Cs(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f<<4;o=g+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Xs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;Gs(g,f,16,16);b=0;do{j=b<<4;m=f+j|0;o=c[m>>2]|0;m=c[m+4>>2]|0;k=g+j|0;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;n=JF(l^o|0,k^m|0,1)|0;n=EF(l|o|0,k|m|0,n&2147450879|0,C&2147450879|0)|0;m=_(b,e)|0;k=a+m|0;c[k>>2]=n;c[k+4>>2]=C;j=j|8;k=f+j|0;n=c[k>>2]|0;k=c[k+4>>2]|0;j=g+j|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;m=a+(m+8)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Ys(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Zs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;Cs(f,b,16,e);Js(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function _s(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function $s(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Gs(a,g,e,16);i=f;return}function at(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Gs(g,j,16,16);Js(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function bt(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+416|0;Js(a,d,b,c,16,c);i=d;return}function ct(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Gs(g,j,16,16);Js(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function dt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;Gs(g,f,16,16);f=0;do{k=f<<4;n=b+k|0;o=c[n>>2]|0;n=c[n+4>>2]|0;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(f,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=b+k|0;j=c[l>>2]|0;l=c[l+4>>2]|0;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function et(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function ft(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;Cs(f,b+e|0,16,e);Js(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function gt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function ht(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=a;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[f+4>>2]=h;b=b+e|0;f=a+e|0;h=b;a=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=f;c[g>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[g+4>>2]=h;b=b+e|0;f=f+e|0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a=f;c[a>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[a+4>>2]=g;b=b+e|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=f+e|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function it(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;jt(h,b,8,e);g=b;n=g;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h;k=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(k^n|0,m^g|0,1)|0;l=EF(k|n|0,m|g|0,l&2147450879|0,C&2147450879|0)|0;g=a;c[g>>2]=l;c[g+4>>2]=C;g=b+e|0;l=g;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;k=JF(n^l|0,m^g|0,1)|0;k=EF(n|l|0,m|g|0,k&2147450879|0,C&2147450879|0)|0;g=a+e|0;c[g>>2]=k;c[g+4>>2]=C;g=e<<1;k=b+g|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;g=a+g|0;c[g>>2]=j;c[g+4>>2]=C;e=e*3|0;g=b+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function jt(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=f>>1;k=0;while(1){j=c+2|0;f=c+-2|0;h=c+4|0;d=_((e[h>>1]|0)+(e[f>>1]|0)|0,-5)|0;i=c+6|0;d=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=c+8|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+2>>1]=d;d=_((e[g>>1]|0)+(e[j>>1]|0)|0,-5)|0;f=c+10|0;d=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+4>>1]=d;d=_((e[f>>1]|0)+(e[h>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+6>>1]=d;k=k+1|0;if((k|0)==4)break;else{a=a+(m<<1)|0;c=c+(l<<1)|0}}return}function kt(a,b,c){a=a|0;b=b|0;c=c|0;jt(a,b,c,c);return}function lt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;jt(h,b,8,e);g=b+2|0;j=g;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h;b=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(b^n|0,m^j|0,1)|0;l=EF(b|n|0,m|j|0,l&2147450879|0,C&2147450879|0)|0;j=a;c[j>>2]=l;c[j+4>>2]=C;j=g+e|0;l=j;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(n^l|0,m^j|0,1)|0;b=EF(n|l|0,m|j|0,b&2147450879|0,C&2147450879|0)|0;j=a+e|0;c[j>>2]=b;c[j+4>>2]=C;j=e<<1;b=g+j|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;e=e*3|0;g=g+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function mt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=s+8|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=o;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+24|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+32|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=s+40|0;c[v>>2]=j;c[v+4>>2]=g;q=q+e|0;v=q;t=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;u=s+48|0;c[u>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[u+4>>2]=v;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;nt(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function nt(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;o=d>>1;p=o<<1;q=o*3|0;n=0;while(1){f=e[c+-8>>1]|0;g=e[c>>1]|0;h=e[c+8>>1]|0;i=e[c+16>>1]|0;j=e[c+24>>1]|0;k=e[c+32>>1]|0;l=e[c+40>>1]|0;m=e[c+48>>1]|0;d=(e[c+-16>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(o<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(p<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(q<<1)>>1]=d;n=n+1|0;if((n|0)==4)break;else{a=a+2|0;c=c+2|0}}return}function ot(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;jt(g,b,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;nt(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function pt(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+208|0;f=e+32|0;g=e;jt(f,b,8,d);qt(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function qt(a,c,d,f,g){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;i=g>>1;h=c;j=0;g=d+(0-(i<<1)<<1)|0;while(1){r=g+2|0;o=g+-2|0;s=g+4|0;n=_((e[s>>1]|0)+(e[o>>1]|0)|0,-5)|0;q=g+6|0;b[h>>1]=(((e[r>>1]|0)+(e[g>>1]|0)|0)*20|0)+55306+(e[g+-4>>1]|0)+n+(e[q>>1]|0);n=_((e[q>>1]|0)+(e[g>>1]|0)|0,-5)|0;p=g+8|0;b[h+2>>1]=(((e[s>>1]|0)+(e[r>>1]|0)|0)*20|0)+55306+(e[o>>1]|0)+n+(e[p>>1]|0);n=_((e[p>>1]|0)+(e[r>>1]|0)|0,-5)|0;o=g+10|0;b[h+4>>1]=(((e[q>>1]|0)+(e[s>>1]|0)|0)*20|0)+55306+(e[g>>1]|0)+n+(e[o>>1]|0);s=_((e[o>>1]|0)+(e[s>>1]|0)|0,-5)|0;b[h+6>>1]=(((e[p>>1]|0)+(e[q>>1]|0)|0)*20|0)+55306+(e[r>>1]|0)+s+(e[g+12>>1]|0);j=j+1|0;if((j|0)==9)break;else{h=h+16|0;g=g+(i<<1)|0}}q=f>>1;r=q<<1;s=q*3|0;p=c+32|0;o=0;while(1){h=b[p+-16>>1]|0;d=b[p>>1]|0;i=d+10230|0;f=b[p+16>>1]|0;j=f+10230|0;c=(b[p+32>>1]|0)+10230|0;k=(b[p+48>>1]|0)+10230|0;l=(b[p+64>>1]|0)+10230|0;m=(b[p+80>>1]|0)+10230|0;n=b[p+96>>1]|0;g=(b[p+-32>>1]|0)+10742+k+((j+i|0)*20|0)+(_(c+(h+10230)|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a>>1]=g;g=h+10742+l+((c+j|0)*20|0)+(_(k+i|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a+(q<<1)>>1]=g;g=d+10742+m+((k+c|0)*20|0)+(_(l+j|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a+(r<<1)>>1]=g;g=f+20972+n+((l+k|0)*20|0)+(_(m+c|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a+(s<<1)>>1]=g;o=o+1|0;if((o|0)==4)break;else{p=p+2|0;a=a+2|0}}return}function rt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;jt(g,b,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;nt(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function st(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;k=f;g=k+16|0;h=b+(0-(e<<1))|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+8|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+24|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+32|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+40|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+48|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+56|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;nt(a,g,e);i=f;return}function tt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+288|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;nt(g,k,8);qt(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ut(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+144|0;qt(a,d,b,c,c);i=d;return}function vt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+288|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)+2|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;nt(g,k,8);qt(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function wt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;b=q;r=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s;c[m>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[m+4>>2]=b;q=q+e|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s+8|0;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=o;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s+24|0;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+32|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+40|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;u=s+48|0;c[u>>2]=j;c[u+4>>2]=g;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;nt(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function xt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;jt(g,b+e|0,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;nt(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function yt(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+208|0;f=e+32|0;g=e;jt(f,b+d|0,8,d);qt(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function zt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;jt(g,b+e|0,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;nt(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function At(a,b,e){a=a|0;b=b|0;e=e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[a+e>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;return}function Bt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;Ct(g,b,4,e);h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Ct(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;j=c+2|0;k=c+-2|0;h=c+4|0;g=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;i=c+6|0;g=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+g+(e[i>>1]|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[a>>1]=g;g=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+g+(e[c+8>>1]|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[a+2>>1]=g;a=a+(d>>1<<1)|0;h=c+(f>>1<<1)|0;i=h+2|0;j=h+-2|0;k=h+4|0;g=_((e[k>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=h+6|0;g=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[h+-4>>1]|0)+g+(e[d>>1]|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[a>>1]=g;g=_((e[d>>1]|0)+(e[h>>1]|0)|0,-5)|0;g=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+g+(e[h+8>>1]|0)>>5;if(g>>>0<1024){c=g;c=c&65535;f=a+2|0;b[f>>1]=c;return}c=0-g>>31&1023;c=c&65535;f=a+2|0;b[f>>1]=c;return}function Dt(a,b,c){a=a|0;b=b|0;c=c|0;Ct(a,b,c,c);return}function Et(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;Ct(g,b,4,e);b=b+2|0;h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Ft(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=b;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Gt(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Gt(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=d>>1;j=e[c+-4>>1]|0;k=e[c>>1]|0;f=e[c+4>>1]|0;g=e[c+8>>1]|0;h=e[c+12>>1]|0;i=e[c+16>>1]|0;d=(e[c+-8>>1]|0)+16+h+((f+k|0)*20|0)+(_(g+j|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=j+16+i+((g+f|0)*20|0)+(_(h+k|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(l<<1)>>1]=d;a=a+2|0;f=e[c+-2>>1]|0;g=e[c+2>>1]|0;h=e[c+6>>1]|0;i=e[c+10>>1]|0;j=e[c+14>>1]|0;k=e[c+18>>1]|0;d=(e[c+-6>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0<1024){c=d;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}c=0-d>>31&1023;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}function Ht(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Ct(h,b,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Gt(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function It(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+80|0;g=e+8|0;f=e;Ct(g,b,4,d);Jt(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function Jt(a,c,d,f,g){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0;i=g>>1;h=c;j=0;g=d+(0-(i<<1)<<1)|0;while(1){d=g+2|0;k=g+-2|0;m=g+4|0;n=_((e[m>>1]|0)+(e[k>>1]|0)|0,-5)|0;l=g+6|0;b[h>>1]=(((e[d>>1]|0)+(e[g>>1]|0)|0)*20|0)+55306+(e[g+-4>>1]|0)+n+(e[l>>1]|0);l=_((e[l>>1]|0)+(e[g>>1]|0)|0,-5)|0;b[h+2>>1]=(((e[m>>1]|0)+(e[d>>1]|0)|0)*20|0)+55306+(e[k>>1]|0)+l+(e[g+8>>1]|0);j=j+1|0;if((j|0)==7)break;else{h=h+8|0;g=g+(i<<1)|0}}l=f>>1;h=b[c+8>>1]|0;i=(b[c+16>>1]|0)+10230|0;d=(b[c+24>>1]|0)+10230|0;j=(b[c+32>>1]|0)+10230|0;f=(b[c+40>>1]|0)+10230|0;k=b[c+48>>1]|0;g=(b[c>>1]|0)+10742+f+((d+i|0)*20|0)+(_(j+(h+10230)|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a>>1]=g;g=h+20972+k+((j+d|0)*20|0)+(_(f+i|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a+(l<<1)>>1]=g;a=a+2|0;h=b[c+10>>1]|0;i=(b[c+18>>1]|0)+10230|0;d=(b[c+26>>1]|0)+10230|0;j=(b[c+34>>1]|0)+10230|0;f=(b[c+42>>1]|0)+10230|0;k=b[c+50>>1]|0;g=(b[c+2>>1]|0)+10742+f+((d+i|0)*20|0)+(_(j+(h+10230)|0,-5)|0)>>10;if(g>>>0>=1024)g=0-g>>31&1023;b[a>>1]=g;g=h+20972+k+((j+d|0)*20|0)+(_(f+i|0,-5)|0)>>10;if(g>>>0<1024){m=g;m=m&65535;n=a+(l<<1)|0;b[n>>1]=m;return}m=0-g>>31&1023;m=m&65535;n=a+(l<<1)|0;b[n>>1]=m;return}function Kt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Ct(h,b,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Gt(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Lt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;f=i;i=i+32|0;h=f;g=h+8|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Gt(a,g,e);i=f;return}function Mt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+112|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Gt(h,j,4);Jt(g,f+48|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Nt(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+64|0;Jt(a,d,b,c,c);i=d;return}function Ot(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+112|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))+2|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Gt(h,j,4);Jt(g,f+48|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Pt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+16>>2]=b;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;Gt(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Qt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Ct(h,b+e|0,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Gt(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Rt(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+80|0;g=e+8|0;f=e;Ct(g,b+d|0,4,d);Jt(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function St(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;Ct(h,b+e|0,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;Gt(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Tt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;f=b;g=a;h=0;while(1){i=g;l=c[i>>2]|0;i=c[i+4>>2]|0;m=f;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^l|0,m^i|0,1)|0;k=EF(n|l|0,m|i|0,k&2147450879|0,C&2147450879|0)|0;i=g;c[i>>2]=k;c[i+4>>2]=C;i=g+8|0;k=i;m=c[k>>2]|0;k=c[k+4>>2]|0;l=f+8|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;c[i>>2]=j;c[i+4>>2]=C;h=h+1|0;if((h|0)==16)break;else{f=f+e|0;g=g+e|0}}h=b+16|0;f=a+16|0;g=0;while(1){n=f;k=c[n>>2]|0;n=c[n+4>>2]|0;j=h;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(i^k|0,j^n|0,1)|0;l=EF(i|k|0,j|n|0,l&2147450879|0,C&2147450879|0)|0;n=f;c[n>>2]=l;c[n+4>>2]=C;n=f+8|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;k=h+8|0;i=k;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(i^j|0,k^l|0,1)|0;m=EF(i|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;g=g+1|0;if((g|0)==16)break;else{h=h+e|0;f=f+e|0}}return}function Ut(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;f=k;Cs(f,b,32,e);h=f+16|0;j=b+16|0;Cs(h,j,32,e);g=b+(e<<3)|0;Cs(f+256|0,g,32,e);Cs(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=g<<5;r=f+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=b+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{u=_(f,e)|0;s=j+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=f<<5;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=a+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=j+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=a+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function Vt(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;Wt(a,b,c,c);Wt(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;Wt(a,b,c,c);Wt(a+16|0,b+16|0,c,c);return}function Wt(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=d>>1;p=f>>1;o=0;while(1){h=c+2|0;f=c+-2|0;j=c+4|0;d=_((e[j>>1]|0)+(e[f>>1]|0)|0,-5)|0;l=c+6|0;d=(((e[h>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[l>>1]|0)+(e[c>>1]|0)|0,-5)|0;n=c+8|0;d=(((e[j>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[n>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[n>>1]|0)+(e[h>>1]|0)|0,-5)|0;m=c+10|0;d=(((e[l>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[m>>1]|0)+(e[j>>1]|0)|0,-5)|0;k=c+12|0;d=(((e[n>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[h>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+8|0;d=_((e[k>>1]|0)+(e[l>>1]|0)|0,-5)|0;i=c+14|0;d=(((e[m>>1]|0)+(e[n>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+10|0;d=_((e[i>>1]|0)+(e[n>>1]|0)|0,-5)|0;h=c+16|0;d=(((e[k>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+12|0;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+18|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[n>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+14|0;d=_((e[g>>1]|0)+(e[k>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;o=o+1|0;if((o|0)==8)break;else{a=a+(q<<1)|0;c=c+(p<<1)|0}}return}function Xt(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;g=k;Cs(g,b,32,e);j=g+16|0;Cs(j,b+16|0,32,e);f=b+(e<<3)|0;Cs(g+256|0,f,32,e);Cs(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=h<<5;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=f+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{u=_(a,e)|0;s=f+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=a<<5;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=g+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=f+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=g+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Yt(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;Gs(l,g,32,32);h=l+16|0;j=k+80|0;Gs(h,j,32,32);Gs(l+256|0,k+320|0,32,32);Gs(l+272|0,k+336|0,32,32);e=0;do{o=e<<5;n=g+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=l+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(e,f)|0;k=b+p|0;t=k;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[k>>2]=s;c[k+4>>2]=C;o=o|8;k=g+o|0;s=k;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=l+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^k|0,1)|0;r=EF(t|s|0,o|k|0,r&2147450879|0,C&2147450879|0)|0;k=C;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=j+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=j+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Zt(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Cs(k,e,32,f);m=k+16|0;Cs(m,e+16|0,32,f);g=e+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function _t(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+2368|0;n=k;f=k+1856|0;g=k+1344|0;Cs(f,b,32,e);j=f+16|0;o=b+16|0;Cs(j,o,32,e);m=b+(e<<3)|0;Cs(f+256|0,m,32,e);l=m+16|0;Cs(f+272|0,l,32,e);Js(g,n,b,32,32,e);h=g+16|0;b=n+16|0;Js(h,b,o,32,32,e);Js(g+256|0,n,m,32,32,e);Js(g+272|0,b,l,32,32,e);b=0;do{n=b<<5;m=f+n|0;t=m;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=g+n|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=_(b,e)|0;l=a+o|0;s=l;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^m|0,1)|0;q=EF(u|t|0,r|m|0,q&2147450879|0,C&2147450879|0)|0;m=C;r=JF(q^p|0,m^s|0,1)|0;r=EF(q|p|0,m|s|0,r&2147450879|0,C&2147450879|0)|0;c[l>>2]=r;c[l+4>>2]=C;n=n|8;l=f+n|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=g+n|0;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;p=c[m>>2]|0;m=c[m+4>>2]|0;q=JF(s^r|0,n^l|0,1)|0;q=EF(s|r|0,n|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=j+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=j+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function $t(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Cs(k,e,32,f);m=k+16|0;Cs(m,e+16|0,32,f);g=e+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function au(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}bu(b,h+64|0,e,32);bu(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;bu(n,h+320|0,e,32);bu(n+16|0,h+336|0,e,32);i=j;return}function bu(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=d>>1;t=f>>1;D=_(t,-2)|0;E=0-t|0;F=t<<1;G=t*3|0;H=t<<2;I=t*5|0;J=t*6|0;K=t*7|0;u=t<<3;v=t*9|0;w=t*10|0;x=L<<1;y=L*3|0;z=L<<2;A=L*5|0;B=L*6|0;C=L*7|0;s=0;while(1){f=e[c+(E<<1)>>1]|0;h=e[c>>1]|0;i=e[c+(t<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(K<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;r=e[c+(w<<1)>>1]|0;d=(e[c+(D<<1)>>1]|0)+16+k+((i+h|0)*20|0)+(_(j+f|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+(L<<1)|0;d=f+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+(x<<1)|0;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(y<<1)|0;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(z<<1)|0;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(A<<1)|0;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(B<<1)|0;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(C<<1)|0;d=m+16+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;s=s+1|0;if((s|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function cu(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Gs(o,k+64|0,32,32);l=o+16|0;Gs(l,k+80|0,32,32);Gs(o+256|0,k+320|0,32,32);Gs(o+272|0,k+336|0,32,32);Js(n,m,e,32,32,f);j=n+16|0;s=m+16|0;Js(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Js(n+256|0,m,g,32,32,f);Js(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function du(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+1344|0;f=d;eu(a,f,b,c,32,c);e=f+16|0;eu(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;eu(a,f,b,c,32,c);eu(a+16|0,e,b+16|0,c,32,c);i=d;return}function eu(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0;j=h>>1;l=g*13|0;i=c;k=0;h=d+(0-(j<<1)<<1)|0;while(1){P=h+2|0;Q=h+-2|0;O=h+4|0;M=_((e[O>>1]|0)+(e[Q>>1]|0)|0,-5)|0;N=h+6|0;b[i>>1]=(((e[P>>1]|0)+(e[h>>1]|0)|0)*20|0)+55306+(e[h+-4>>1]|0)+M+(e[N>>1]|0);M=_((e[N>>1]|0)+(e[h>>1]|0)|0,-5)|0;L=h+8|0;b[i+2>>1]=(((e[O>>1]|0)+(e[P>>1]|0)|0)*20|0)+55306+(e[Q>>1]|0)+M+(e[L>>1]|0);M=_((e[L>>1]|0)+(e[P>>1]|0)|0,-5)|0;Q=h+10|0;b[i+4>>1]=(((e[N>>1]|0)+(e[O>>1]|0)|0)*20|0)+55306+(e[h>>1]|0)+M+(e[Q>>1]|0);M=_((e[Q>>1]|0)+(e[O>>1]|0)|0,-5)|0;R=h+12|0;b[i+6>>1]=(((e[L>>1]|0)+(e[N>>1]|0)|0)*20|0)+55306+(e[P>>1]|0)+M+(e[R>>1]|0);M=_((e[R>>1]|0)+(e[N>>1]|0)|0,-5)|0;P=h+14|0;b[i+8>>1]=(((e[Q>>1]|0)+(e[L>>1]|0)|0)*20|0)+55306+(e[O>>1]|0)+M+(e[P>>1]|0);M=_((e[P>>1]|0)+(e[L>>1]|0)|0,-5)|0;O=h+16|0;b[i+10>>1]=(((e[R>>1]|0)+(e[Q>>1]|0)|0)*20|0)+55306+(e[N>>1]|0)+M+(e[O>>1]|0);M=_((e[O>>1]|0)+(e[Q>>1]|0)|0,-5)|0;N=h+18|0;b[i+12>>1]=(((e[P>>1]|0)+(e[R>>1]|0)|0)*20|0)+55306+(e[L>>1]|0)+M+(e[N>>1]|0);R=_((e[N>>1]|0)+(e[R>>1]|0)|0,-5)|0;b[i+14>>1]=(((e[O>>1]|0)+(e[P>>1]|0)|0)*20|0)+55306+(e[Q>>1]|0)+R+(e[h+20>>1]|0);k=k+1|0;if((k|0)==13)break;else{i=i+(g<<1)|0;h=h+(j<<1)|0}}A=f>>1;B=_(g,-2)|0;C=0-g|0;D=g<<1;E=g*3|0;F=g<<2;G=g*5|0;H=g*6|0;I=g*7|0;J=g<<3;K=g*9|0;L=g*10|0;M=A<<1;N=A*3|0;O=A<<2;P=A*5|0;Q=A*6|0;R=A*7|0;z=c+(l<<1)+((_(g,-11)|0)<<1)|0;y=0;while(1){i=b[z+(C<<1)>>1]|0;k=b[z>>1]|0;j=k+10230|0;l=b[z+(g<<1)>>1]|0;f=l+10230|0;m=b[z+(D<<1)>>1]|0;c=m+10230|0;o=b[z+(E<<1)>>1]|0;n=o+10230|0;q=b[z+(F<<1)>>1]|0;p=q+10230|0;s=b[z+(G<<1)>>1]|0;r=s+10230|0;t=(b[z+(H<<1)>>1]|0)+10230|0;u=(b[z+(I<<1)>>1]|0)+10230|0;v=(b[z+(J<<1)>>1]|0)+10230|0;w=(b[z+(K<<1)>>1]|0)+10230|0;x=b[z+(L<<1)>>1]|0;h=(b[z+(B<<1)>>1]|0)+10742+n+((f+j|0)*20|0)+(_(c+(i+10230)|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a>>1]=((e[a>>1]|0)+1+h|0)>>>1;d=a+(A<<1)|0;h=i+10742+p+((c+f|0)*20|0)+(_(n+j|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[d>>1]=((e[d>>1]|0)+1+h|0)>>>1;i=a+(M<<1)|0;h=k+10742+r+((n+c|0)*20|0)+(_(p+f|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(N<<1)|0;h=l+10742+t+((p+n|0)*20|0)+(_(r+c|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(O<<1)|0;h=m+10742+u+((r+p|0)*20|0)+(_(t+n|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(P<<1)|0;h=o+10742+v+((t+r|0)*20|0)+(_(u+p|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(Q<<1)|0;h=q+10742+w+((u+t|0)*20|0)+(_(v+r|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(R<<1)|0;h=s+20972+x+((v+u|0)*20|0)+(_(w+t|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;y=y+1|0;if((y|0)==8)break;else{z=z+2|0;a=a+2|0}}return}function fu(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Gs(o,k+64|0,32,32);l=o+16|0;Gs(l,k+80|0,32,32);Gs(o+256|0,k+320|0,32,32);Gs(o+272|0,k+336|0,32,32);Js(n,m,e,32,32,f);j=n+16|0;s=m+16|0;Js(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Js(n+256|0,m,g,32,32,f);Js(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function gu(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(k,l+64|0,32,32);j=k+16|0;Gs(j,l+80|0,32,32);Gs(k+256|0,l+320|0,32,32);Gs(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{o=g<<5;n=e+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=k+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(g,f)|0;h=b+p|0;t=h;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[h>>2]=s;c[h+4>>2]=C;o=o|8;h=e+o|0;s=h;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=k+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^h|0,1)|0;r=EF(t|s|0,o|h|0,r&2147450879|0,C&2147450879|0)|0;h=C;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{u=g<<5;t=e+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=j+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=h+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=e+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=h+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}
function zq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(k,l+64|0,32,32);j=k+16|0;qq(j,l+80|0,32,32);qq(k+256|0,l+320|0,32,32);qq(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{h=g<<5;p=e+h|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=k+h|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(g,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;h=h|8;n=e+h|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;h=k+h|0;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{o=g<<5;r=e+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=j+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=h+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=e+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=h+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Aq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;mq(k,g,32,f);m=k+16|0;mq(m,g+16|0,32,f);g=g+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Bq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+2368|0;m=k;f=k+1856|0;g=k+1344|0;j=b+e|0;mq(f,j,32,e);h=f+16|0;mq(h,j+16|0,32,e);n=e<<3;j=j+n|0;mq(f+256|0,j,32,e);mq(f+272|0,j+16|0,32,e);tq(g,m,b,32,32,e);j=g+16|0;l=m+16|0;tq(j,l,b+16|0,32,32,e);b=b+n|0;tq(g+256|0,m,b,32,32,e);tq(g+272|0,l,b+16|0,32,32,e);b=0;do{o=b<<5;n=f+o|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+o|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=JF(m^q|0,l^n|0,1)|0;p=EF(m|q|0,l|n|0,p&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=p;c[l+4>>2]=C;o=o|8;l=f+o|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=g+o|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=h+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=h+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Cq(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;mq(k,g,32,f);m=k+16|0;mq(m,g+16|0,32,f);g=g+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Dq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;f=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=a;c[g>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[g+4>>2]=h;g=b+8|0;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a+8|0;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;b=b+e|0;f=a+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function Eq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;f=h;mq(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g<<4;o=f+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function Fq(a,b,c){a=a|0;b=b|0;c=c|0;mq(a,b,c,c);return}function Gq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;h=i;i=i+128|0;g=h;mq(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=f<<4;o=g+m|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=JF(p^k|0,o^l|0,1)|0;n=EF(p|k|0,o|l|0,n&2147450879|0,C&2147450879|0)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;j=j+8|0;l=b+j|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+(m|8)|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Hq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;qq(g,f,16,16);b=0;do{j=b<<4;m=f+j|0;o=c[m>>2]|0;m=c[m+4>>2]|0;k=g+j|0;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;n=JF(l^o|0,k^m|0,1)|0;n=EF(l|o|0,k|m|0,n&2147450879|0,C&2147450879|0)|0;m=_(b,e)|0;k=a+m|0;c[k>>2]=n;c[k+4>>2]=C;j=j|8;k=f+j|0;n=c[k>>2]|0;k=c[k+4>>2]|0;j=g+j|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;m=a+(m+8)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Iq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Jq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;mq(f,b,16,e);tq(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Kq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Lq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;qq(a,g,e,16);i=f;return}function Mq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;qq(g,j,16,16);tq(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Nq(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+416|0;tq(a,d,b,c,16,c);i=d;return}function Oq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;qq(g,j,16,16);tq(f,h+208|0,b,16,16,e);b=0;do{l=b<<4;o=g+l|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^j|0,m^o|0,1)|0;k=EF(n|j|0,m|o|0,k&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=k;c[m+4>>2]=C;l=l|8;m=g+l|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=f+l|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Pq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;qq(g,f,16,16);f=0;do{k=f<<4;n=b+k|0;o=c[n>>2]|0;n=c[n+4>>2]|0;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(f,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=b+k|0;j=c[l>>2]|0;l=c[l+4>>2]|0;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Qq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Rq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;mq(f,b+e|0,16,e);tq(g,h,b,16,16,e);b=0;do{m=b<<4;j=f+m|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g+m|0;k=l;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(k^o|0,l^j|0,1)|0;n=EF(k|o|0,l|j|0,n&2147450879|0,C&2147450879|0)|0;j=_(b,e)|0;l=a+j|0;c[l>>2]=n;c[l+4>>2]=C;m=m|8;l=f+m|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=g+m|0;o=m;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;j=a+(j+8)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Sq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{k=b<<4;n=f+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+k|0;m=l;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(m^o|0,l^n|0,1)|0;j=EF(m|o|0,l|n|0,j&2147450879|0,C&2147450879|0)|0;n=_(b,e)|0;l=a+n|0;c[l>>2]=j;c[l+4>>2]=C;k=k|8;l=f+k|0;j=l;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g+k|0;o=k;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;n=a+(n+8)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Tq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=a;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[f+4>>2]=h;b=b+e|0;f=a+e|0;h=b;a=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=f;c[g>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[g+4>>2]=h;b=b+e|0;f=f+e|0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a=f;c[a>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[a+4>>2]=g;b=b+e|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=f+e|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function Uq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;Vq(h,b,8,e);g=b;n=g;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h;k=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(k^n|0,m^g|0,1)|0;l=EF(k|n|0,m|g|0,l&2147450879|0,C&2147450879|0)|0;g=a;c[g>>2]=l;c[g+4>>2]=C;g=b+e|0;l=g;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;k=JF(n^l|0,m^g|0,1)|0;k=EF(n|l|0,m|g|0,k&2147450879|0,C&2147450879|0)|0;g=a+e|0;c[g>>2]=k;c[g+4>>2]=C;g=e<<1;k=b+g|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;g=a+g|0;c[g>>2]=j;c[g+4>>2]=C;e=e*3|0;g=b+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Vq(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=f>>1;k=0;while(1){j=c+2|0;f=c+-2|0;h=c+4|0;d=_((e[h>>1]|0)+(e[f>>1]|0)|0,-5)|0;i=c+6|0;d=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=c+8|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+2>>1]=d;d=_((e[g>>1]|0)+(e[j>>1]|0)|0,-5)|0;f=c+10|0;d=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+4>>1]=d;d=_((e[f>>1]|0)+(e[h>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+6>>1]=d;k=k+1|0;if((k|0)==4)break;else{a=a+(m<<1)|0;c=c+(l<<1)|0}}return}function Wq(a,b,c){a=a|0;b=b|0;c=c|0;Vq(a,b,c,c);return}function Xq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;f=i;i=i+32|0;h=f;Vq(h,b,8,e);g=b+2|0;j=g;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h;b=c[m>>2]|0;m=c[m+4>>2]|0;l=JF(b^n|0,m^j|0,1)|0;l=EF(b|n|0,m|j|0,l&2147450879|0,C&2147450879|0)|0;j=a;c[j>>2]=l;c[j+4>>2]=C;j=g+e|0;l=j;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=h+8|0;n=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(n^l|0,m^j|0,1)|0;b=EF(n|l|0,m|j|0,b&2147450879|0,C&2147450879|0)|0;j=a+e|0;c[j>>2]=b;c[j+4>>2]=C;j=e<<1;b=g+j|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;e=e*3|0;g=g+e|0;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Yq(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=s+8|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=o;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+24|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+32|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=s+40|0;c[v>>2]=j;c[v+4>>2]=g;q=q+e|0;v=q;t=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;u=s+48|0;c[u>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[u+4>>2]=v;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;Zq(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Zq(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;o=d>>1;p=o<<1;q=o*3|0;n=0;while(1){f=e[c+-8>>1]|0;g=e[c>>1]|0;h=e[c+8>>1]|0;i=e[c+16>>1]|0;j=e[c+24>>1]|0;k=e[c+32>>1]|0;l=e[c+40>>1]|0;m=e[c+48>>1]|0;d=(e[c+-16>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(o<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(p<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(q<<1)>>1]=d;n=n+1|0;if((n|0)==4)break;else{a=a+2|0;c=c+2|0}}return}function _q(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Vq(g,b,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Zq(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function $q(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+208|0;f=e+32|0;g=e;Vq(f,b,8,d);ar(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function ar(a,c,d,f,g){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;i=g>>1;h=c;j=0;g=d+(0-(i<<1)<<1)|0;while(1){n=g+2|0;m=g+-2|0;q=g+4|0;l=_((e[q>>1]|0)+(e[m>>1]|0)|0,-5)|0;p=g+6|0;b[h>>1]=(e[g+-4>>1]|0)+(((e[n>>1]|0)+(e[g>>1]|0)|0)*20|0)+l+(e[p>>1]|0);l=_((e[p>>1]|0)+(e[g>>1]|0)|0,-5)|0;o=g+8|0;b[h+2>>1]=(e[m>>1]|0)+(((e[q>>1]|0)+(e[n>>1]|0)|0)*20|0)+l+(e[o>>1]|0);l=_((e[o>>1]|0)+(e[n>>1]|0)|0,-5)|0;m=g+10|0;b[h+4>>1]=(e[g>>1]|0)+(((e[p>>1]|0)+(e[q>>1]|0)|0)*20|0)+l+(e[m>>1]|0);q=_((e[m>>1]|0)+(e[q>>1]|0)|0,-5)|0;b[h+6>>1]=(e[n>>1]|0)+(((e[o>>1]|0)+(e[p>>1]|0)|0)*20|0)+q+(e[g+12>>1]|0);j=j+1|0;if((j|0)==9)break;else{h=h+16|0;g=g+(i<<1)|0}}q=f>>1;o=q<<1;p=q*3|0;n=c+32|0;m=0;while(1){h=b[n+-16>>1]|0;i=b[n>>1]|0;d=b[n+16>>1]|0;j=b[n+32>>1]|0;f=b[n+48>>1]|0;c=b[n+64>>1]|0;k=b[n+80>>1]|0;l=b[n+96>>1]|0;g=(b[n+-32>>1]|0)+512+f+((d+i|0)*20|0)+(_(j+h|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a>>1]=g;g=h+512+c+((j+d|0)*20|0)+(_(f+i|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a+(q<<1)>>1]=g;g=i+512+k+((f+j|0)*20|0)+(_(c+d|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a+(o<<1)>>1]=g;g=d+512+l+((c+f|0)*20|0)+(_(k+j|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a+(p<<1)>>1]=g;m=m+1|0;if((m|0)==4)break;else{n=n+2|0;a=a+2|0}}return}function br(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Vq(g,b,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Zq(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function cr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+80|0;k=f;g=k+16|0;h=b+(0-(e<<1))|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+8|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+24|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+32|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+40|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+48|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+56|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Zq(a,g,e);i=f;return}function dr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+288|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;Zq(g,k,8);ar(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function er(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+144|0;ar(a,d,b,c,c);i=d;return}function fr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+288|0;l=f+64|0;g=f+32|0;h=f;k=l+16|0;j=e<<1;m=b+(0-j)+2|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+8|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=k;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+24|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+32|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=l+40|0;c[p>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[p+4>>2]=n;m=m+e|0;p=m;n=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+48|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=p;m=m+e|0;o=m;p=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=l+56|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=o;m=m+e|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+64|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;Zq(g,k,8);ar(h,f+136|0,b,8,e);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function gr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;s=f+32|0;h=f;o=s+16|0;k=e<<1;q=b+(0-k)|0;b=q;r=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s;c[m>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[m+4>>2]=b;q=q+e|0;m=q;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=s+8|0;c[r>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[r+4>>2]=m;q=q+e|0;r=q;m=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=o;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=r;q=q+e|0;b=q;r=b;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=s+24|0;c[m>>2]=r;c[m+4>>2]=b;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=s+32|0;c[l>>2]=p;c[l+4>>2]=m;q=q+e|0;l=q;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;g=s+40|0;c[g>>2]=n;c[g+4>>2]=l;q=q+e|0;g=q;j=g;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;u=s+48|0;c[u>>2]=j;c[u+4>>2]=g;q=q+e|0;u=q;v=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;t=s+56|0;c[t>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[t+4>>2]=u;q=q+e|0;t=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;s=s+64|0;c[s>>2]=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;c[s+4>>2]=q;Zq(h,o,8);o=h;s=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(s^r|0,o^b|0,1)|0;q=EF(s|r|0,o|b|0,q&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=q;c[b+4>>2]=C;b=h+8|0;q=c[b>>2]|0;b=c[b+4>>2]|0;o=JF(q^p|0,b^m|0,1)|0;o=EF(q|p|0,b|m|0,o&2147450879|0,C&2147450879|0)|0;m=a+e|0;c[m>>2]=o;c[m+4>>2]=C;m=h+16|0;o=c[m>>2]|0;m=c[m+4>>2]|0;b=JF(o^n|0,m^l|0,1)|0;b=EF(o|n|0,m|l|0,b&2147450879|0,C&2147450879|0)|0;k=a+k|0;c[k>>2]=b;c[k+4>>2]=C;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function hr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Vq(g,b+e|0,8,e);j=e<<1;l=b+(0-j)|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Zq(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ir(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;e=i;i=i+208|0;f=e+32|0;g=e;Vq(f,b+d|0,8,d);ar(g,e+64|0,b,8,d);h=f;j=c[h>>2]|0;h=c[h+4>>2]|0;b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=JF(k^j|0,b^h|0,1)|0;l=EF(k|j|0,b|h|0,l&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=l;c[h+4>>2]=C;h=f+8|0;l=c[h>>2]|0;h=c[h+4>>2]|0;b=g+8|0;j=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(j^l|0,b^h|0,1)|0;k=EF(j|l|0,b|h|0,k&2147450879|0,C&2147450879|0)|0;h=a+d|0;c[h>>2]=k;c[h+4>>2]=C;h=f+16|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=g+16|0;l=c[b>>2]|0;b=c[b+4>>2]|0;j=JF(l^k|0,b^h|0,1)|0;j=EF(l|k|0,b|h|0,j&2147450879|0,C&2147450879|0)|0;h=a+(d<<1)|0;c[h>>2]=j;c[h+4>>2]=C;f=f+24|0;h=c[f>>2]|0;f=c[f+4>>2]|0;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;b=JF(j^h|0,g^f|0,1)|0;b=EF(j|h|0,g|f|0,b&2147450879|0,C&2147450879|0)|0;d=a+(d*3|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function jr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;f=i;i=i+144|0;n=f+64|0;g=f+32|0;h=f;k=n+16|0;Vq(g,b+e|0,8,e);j=e<<1;l=b+(0-j)+2|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+8|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+24|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+32|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;b=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=n+40|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=n+48|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=o;l=l+e|0;b=l;o=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;m=n+56|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=b;l=l+e|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=n+64|0;c[b>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+4>>2]=l;Zq(h,k,8);b=g;k=c[b>>2]|0;b=c[b+4>>2]|0;l=h;m=c[l>>2]|0;l=c[l+4>>2]|0;n=JF(m^k|0,l^b|0,1)|0;n=EF(m|k|0,l|b|0,n&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=n;c[b+4>>2]=C;b=g+8|0;n=c[b>>2]|0;b=c[b+4>>2]|0;l=h+8|0;k=c[l>>2]|0;l=c[l+4>>2]|0;m=JF(k^n|0,l^b|0,1)|0;m=EF(k|n|0,l|b|0,m&2147450879|0,C&2147450879|0)|0;b=a+e|0;c[b>>2]=m;c[b+4>>2]=C;b=g+16|0;m=c[b>>2]|0;b=c[b+4>>2]|0;l=h+16|0;n=c[l>>2]|0;l=c[l+4>>2]|0;k=JF(n^m|0,l^b|0,1)|0;k=EF(n|m|0,l|b|0,k&2147450879|0,C&2147450879|0)|0;j=a+j|0;c[j>>2]=k;c[j+4>>2]=C;g=g+24|0;j=c[g>>2]|0;g=c[g+4>>2]|0;h=h+24|0;k=c[h>>2]|0;h=c[h+4>>2]|0;b=JF(k^j|0,h^g|0,1)|0;b=EF(k|j|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;e=a+(e*3|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function kr(a,b,e){a=a|0;b=b|0;e=e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[a+e>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;return}function lr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;mr(g,b,4,e);h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function mr(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;j=c+2|0;k=c+-2|0;h=c+4|0;g=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;i=c+6|0;g=(((e[j>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+g+(e[i>>1]|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[a>>1]=g;g=_((e[i>>1]|0)+(e[c>>1]|0)|0,-5)|0;g=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+g+(e[c+8>>1]|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[a+2>>1]=g;a=a+(d>>1<<1)|0;h=c+(f>>1<<1)|0;i=h+2|0;j=h+-2|0;k=h+4|0;g=_((e[k>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=h+6|0;g=(((e[i>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[h+-4>>1]|0)+g+(e[d>>1]|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[a>>1]=g;g=_((e[d>>1]|0)+(e[h>>1]|0)|0,-5)|0;g=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+g+(e[h+8>>1]|0)>>5;if(g>>>0<512){c=g;c=c&65535;f=a+2|0;b[f>>1]=c;return}c=0-g>>31&511;c=c&65535;f=a+2|0;b[f>>1]=c;return}function nr(a,b,c){a=a|0;b=b|0;c=c|0;mr(a,b,c,c);return}function or(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+16|0;g=f;mr(g,b,4,e);b=b+2|0;h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);b=b+e|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function pr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=b;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;qr(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function qr(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=d>>1;j=e[c+-4>>1]|0;k=e[c>>1]|0;f=e[c+4>>1]|0;g=e[c+8>>1]|0;h=e[c+12>>1]|0;i=e[c+16>>1]|0;d=(e[c+-8>>1]|0)+16+h+((f+k|0)*20|0)+(_(g+j|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=j+16+i+((g+f|0)*20|0)+(_(h+k|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a+(l<<1)>>1]=d;a=a+2|0;f=e[c+-2>>1]|0;g=e[c+2>>1]|0;h=e[c+6>>1]|0;i=e[c+10>>1]|0;j=e[c+14>>1]|0;k=e[c+18>>1]|0;d=(e[c+-6>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0<512){c=d;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}c=0-d>>31&511;c=c&65535;l=a+(l<<1)|0;b[l>>1]=c;return}function rr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;mr(h,b,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;qr(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function sr(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+80|0;g=e+8|0;f=e;mr(g,b,4,d);tr(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function tr(a,c,d,f,g){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0;i=g>>1;h=c;j=0;g=d+(0-(i<<1)<<1)|0;while(1){k=g+2|0;m=g+-2|0;d=g+4|0;n=_((e[d>>1]|0)+(e[m>>1]|0)|0,-5)|0;l=g+6|0;b[h>>1]=(e[g+-4>>1]|0)+(((e[k>>1]|0)+(e[g>>1]|0)|0)*20|0)+n+(e[l>>1]|0);l=_((e[l>>1]|0)+(e[g>>1]|0)|0,-5)|0;b[h+2>>1]=(e[m>>1]|0)+(((e[d>>1]|0)+(e[k>>1]|0)|0)*20|0)+l+(e[g+8>>1]|0);j=j+1|0;if((j|0)==7)break;else{h=h+8|0;g=g+(i<<1)|0}}l=f>>1;h=b[c+8>>1]|0;i=b[c+16>>1]|0;d=b[c+24>>1]|0;j=b[c+32>>1]|0;f=b[c+40>>1]|0;k=b[c+48>>1]|0;g=(b[c>>1]|0)+512+f+((d+i|0)*20|0)+(_(j+h|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a>>1]=g;g=h+512+k+((j+d|0)*20|0)+(_(f+i|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a+(l<<1)>>1]=g;a=a+2|0;h=b[c+10>>1]|0;i=b[c+18>>1]|0;d=b[c+26>>1]|0;j=b[c+34>>1]|0;f=b[c+42>>1]|0;k=b[c+50>>1]|0;g=(b[c+2>>1]|0)+512+f+((d+i|0)*20|0)+(_(j+h|0,-5)|0)>>10;if(g>>>0>=512)g=0-g>>31&511;b[a>>1]=g;g=h+512+k+((j+d|0)*20|0)+(_(f+i|0,-5)|0)>>10;if(g>>>0<512){m=g;m=m&65535;n=a+(l<<1)|0;b[n>>1]=m;return}m=0-g>>31&511;m=m&65535;n=a+(l<<1)|0;b[n>>1]=m;return}function ur(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;mr(h,b,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;qr(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function vr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;f=i;i=i+32|0;h=f;g=h+8|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;qr(a,g,e);i=f;return}function wr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+112|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;qr(h,j,4);tr(g,f+48|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function xr(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+64|0;tr(a,d,b,c,c);i=d;return}function yr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+112|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))+2|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+16>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;qr(h,j,4);tr(g,f+48|0,b,4,e);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function zr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+48|0;k=f+8|0;g=f;j=k+8|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;h=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+12>>2]=h;l=l+e|0;b=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+16>>2]=b;l=l+e|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;qr(g,j,4);j=c[g>>2]|0;c[a>>2]=(j|h)-((j^h)>>>1&2147450879);g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Ar(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;mr(h,b+e|0,4,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;qr(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Br(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+80|0;g=e+8|0;f=e;mr(g,b+d|0,4,d);tr(f,e+16|0,b,4,d);b=c[g>>2]|0;h=c[f>>2]|0;c[a>>2]=(h|b)-((h^b)>>>1&2147450879);b=c[g+4>>2]|0;f=c[f+4>>2]|0;c[a+d>>2]=(f|b)-((f^b)>>>1&2147450879);i=e;return}function Cr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0;f=i;i=i+48|0;k=f+16|0;h=f+8|0;g=f;j=k+8|0;mr(h,b+e|0,4,e);b=b+(0-(e<<1))+2|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+16>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;qr(g,j,4);b=c[h>>2]|0;j=c[g>>2]|0;c[a>>2]=(j|b)-((j^b)>>>1&2147450879);b=c[h+4>>2]|0;g=c[g+4>>2]|0;c[a+e>>2]=(g|b)-((g^b)>>>1&2147450879);i=f;return}function Dr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;f=b;g=a;h=0;while(1){i=g;l=c[i>>2]|0;i=c[i+4>>2]|0;m=f;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=JF(n^l|0,m^i|0,1)|0;k=EF(n|l|0,m|i|0,k&2147450879|0,C&2147450879|0)|0;i=g;c[i>>2]=k;c[i+4>>2]=C;i=g+8|0;k=i;m=c[k>>2]|0;k=c[k+4>>2]|0;l=f+8|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=JF(n^m|0,l^k|0,1)|0;j=EF(n|m|0,l|k|0,j&2147450879|0,C&2147450879|0)|0;c[i>>2]=j;c[i+4>>2]=C;h=h+1|0;if((h|0)==16)break;else{f=f+e|0;g=g+e|0}}h=b+16|0;f=a+16|0;g=0;while(1){n=f;k=c[n>>2]|0;n=c[n+4>>2]|0;j=h;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=JF(i^k|0,j^n|0,1)|0;l=EF(i|k|0,j|n|0,l&2147450879|0,C&2147450879|0)|0;n=f;c[n>>2]=l;c[n+4>>2]=C;n=f+8|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;k=h+8|0;i=k;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=JF(i^j|0,k^l|0,1)|0;m=EF(i|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;g=g+1|0;if((g|0)==16)break;else{h=h+e|0;f=f+e|0}}return}function Er(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;f=k;mq(f,b,32,e);h=f+16|0;j=b+16|0;mq(h,j,32,e);g=b+(e<<3)|0;mq(f+256|0,g,32,e);mq(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=g<<5;r=f+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=b+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=f+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{u=_(f,e)|0;s=j+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=f<<5;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=a+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=j+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=a+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function Fr(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;Gr(a,b,c,c);Gr(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;Gr(a,b,c,c);Gr(a+16|0,b+16|0,c,c);return}function Gr(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=d>>1;p=f>>1;o=0;while(1){h=c+2|0;f=c+-2|0;j=c+4|0;d=_((e[j>>1]|0)+(e[f>>1]|0)|0,-5)|0;l=c+6|0;d=(((e[h>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[l>>1]|0)+(e[c>>1]|0)|0,-5)|0;n=c+8|0;d=(((e[j>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[n>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[n>>1]|0)+(e[h>>1]|0)|0,-5)|0;m=c+10|0;d=(((e[l>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[m>>1]|0)+(e[j>>1]|0)|0,-5)|0;k=c+12|0;d=(((e[n>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[h>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+8|0;d=_((e[k>>1]|0)+(e[l>>1]|0)|0,-5)|0;i=c+14|0;d=(((e[m>>1]|0)+(e[n>>1]|0)|0)*20|0)+16+(e[j>>1]|0)+d+(e[i>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+10|0;d=_((e[i>>1]|0)+(e[n>>1]|0)|0,-5)|0;h=c+16|0;d=(((e[k>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+12|0;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+18|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[n>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+14|0;d=_((e[g>>1]|0)+(e[k>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;o=o+1|0;if((o|0)==8)break;else{a=a+(q<<1)|0;c=c+(p<<1)|0}}return}function Hr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+512|0;g=k;mq(g,b,32,e);j=g+16|0;mq(j,b+16|0,32,e);f=b+(e<<3)|0;mq(g+256|0,f,32,e);mq(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;t=n;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=h<<5;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=a+l|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^n|0,1)|0;q=EF(u|t|0,r|n|0,q&2147450879|0,C&2147450879|0)|0;n=C;r=JF(q^p|0,n^s|0,1)|0;r=EF(q|p|0,n|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;l=l+8|0;o=f+l|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+(m|8)|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=a+l|0;n=l;p=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{u=_(a,e)|0;s=f+u|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;t=a<<5;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=g+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;u=u+8|0;r=f+u|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+(t|8)|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=g+u|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Ir(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;qq(l,g,32,32);h=l+16|0;j=k+80|0;qq(h,j,32,32);qq(l+256|0,k+320|0,32,32);qq(l+272|0,k+336|0,32,32);e=0;do{o=e<<5;n=g+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=l+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(e,f)|0;k=b+p|0;t=k;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[k>>2]=s;c[k+4>>2]=C;o=o|8;k=g+o|0;s=k;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=l+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^k|0,1)|0;r=EF(t|s|0,o|k|0,r&2147450879|0,C&2147450879|0)|0;k=C;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=j+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=j+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Jr(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;mq(k,e,32,f);m=k+16|0;mq(m,e+16|0,32,f);g=e+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Kr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+2368|0;n=k;f=k+1856|0;g=k+1344|0;mq(f,b,32,e);j=f+16|0;o=b+16|0;mq(j,o,32,e);m=b+(e<<3)|0;mq(f+256|0,m,32,e);l=m+16|0;mq(f+272|0,l,32,e);tq(g,n,b,32,32,e);h=g+16|0;b=n+16|0;tq(h,b,o,32,32,e);tq(g+256|0,n,m,32,32,e);tq(g+272|0,b,l,32,32,e);b=0;do{n=b<<5;m=f+n|0;t=m;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=g+n|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=_(b,e)|0;l=a+o|0;s=l;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^m|0,1)|0;q=EF(u|t|0,r|m|0,q&2147450879|0,C&2147450879|0)|0;m=C;r=JF(q^p|0,m^s|0,1)|0;r=EF(q|p|0,m|s|0,r&2147450879|0,C&2147450879|0)|0;c[l>>2]=r;c[l+4>>2]=C;n=n|8;l=f+n|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=g+n|0;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;p=c[m>>2]|0;m=c[m+4>>2]|0;q=JF(s^r|0,n^l|0,1)|0;q=EF(s|r|0,n|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=j+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=h+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=j+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=h+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Lr(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;mq(k,e,32,f);m=k+16|0;mq(m,e+16|0,32,f);g=e+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Mr(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}Nr(b,h+64|0,e,32);Nr(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;Nr(n,h+320|0,e,32);Nr(n+16|0,h+336|0,e,32);i=j;return}function Nr(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=d>>1;t=f>>1;D=_(t,-2)|0;E=0-t|0;F=t<<1;G=t*3|0;H=t<<2;I=t*5|0;J=t*6|0;K=t*7|0;u=t<<3;v=t*9|0;w=t*10|0;x=L<<1;y=L*3|0;z=L<<2;A=L*5|0;B=L*6|0;C=L*7|0;s=0;while(1){f=e[c+(E<<1)>>1]|0;h=e[c>>1]|0;i=e[c+(t<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(K<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;r=e[c+(w<<1)>>1]|0;d=(e[c+(D<<1)>>1]|0)+16+k+((i+h|0)*20|0)+(_(j+f|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+(L<<1)|0;d=f+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+(x<<1)|0;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(y<<1)|0;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(z<<1)|0;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(A<<1)|0;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(B<<1)|0;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(C<<1)|0;d=m+16+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;s=s+1|0;if((s|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function Or(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}qq(o,k+64|0,32,32);l=o+16|0;qq(l,k+80|0,32,32);qq(o+256|0,k+320|0,32,32);qq(o+272|0,k+336|0,32,32);tq(n,m,e,32,32,f);j=n+16|0;s=m+16|0;tq(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;tq(n+256|0,m,g,32,32,f);tq(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function Pr(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+1344|0;f=d;Qr(a,f,b,c,32,c);e=f+16|0;Qr(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;Qr(a,f,b,c,32,c);Qr(a+16|0,e,b+16|0,c,32,c);i=d;return}function Qr(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;j=h>>1;l=g*13|0;i=c;k=0;h=d+(0-(j<<1)<<1)|0;while(1){K=h+2|0;I=h+-2|0;J=h+4|0;G=_((e[J>>1]|0)+(e[I>>1]|0)|0,-5)|0;H=h+6|0;b[i>>1]=(e[h+-4>>1]|0)+(((e[K>>1]|0)+(e[h>>1]|0)|0)*20|0)+G+(e[H>>1]|0);G=_((e[H>>1]|0)+(e[h>>1]|0)|0,-5)|0;F=h+8|0;b[i+2>>1]=(e[I>>1]|0)+(((e[J>>1]|0)+(e[K>>1]|0)|0)*20|0)+G+(e[F>>1]|0);G=_((e[F>>1]|0)+(e[K>>1]|0)|0,-5)|0;I=h+10|0;b[i+4>>1]=(e[h>>1]|0)+(((e[H>>1]|0)+(e[J>>1]|0)|0)*20|0)+G+(e[I>>1]|0);G=_((e[I>>1]|0)+(e[J>>1]|0)|0,-5)|0;L=h+12|0;b[i+6>>1]=(e[K>>1]|0)+(((e[F>>1]|0)+(e[H>>1]|0)|0)*20|0)+G+(e[L>>1]|0);G=_((e[L>>1]|0)+(e[H>>1]|0)|0,-5)|0;K=h+14|0;b[i+8>>1]=(e[J>>1]|0)+(((e[I>>1]|0)+(e[F>>1]|0)|0)*20|0)+G+(e[K>>1]|0);G=_((e[K>>1]|0)+(e[F>>1]|0)|0,-5)|0;J=h+16|0;b[i+10>>1]=(e[H>>1]|0)+(((e[L>>1]|0)+(e[I>>1]|0)|0)*20|0)+G+(e[J>>1]|0);G=_((e[J>>1]|0)+(e[I>>1]|0)|0,-5)|0;H=h+18|0;b[i+12>>1]=(e[F>>1]|0)+(((e[K>>1]|0)+(e[L>>1]|0)|0)*20|0)+G+(e[H>>1]|0);L=_((e[H>>1]|0)+(e[L>>1]|0)|0,-5)|0;b[i+14>>1]=(e[I>>1]|0)+(((e[J>>1]|0)+(e[K>>1]|0)|0)*20|0)+L+(e[h+20>>1]|0);k=k+1|0;if((k|0)==13)break;else{i=i+(g<<1)|0;h=h+(j<<1)|0}}u=f>>1;v=_(g,-2)|0;w=0-g|0;x=g<<1;y=g*3|0;z=g<<2;A=g*5|0;B=g*6|0;C=g*7|0;D=g<<3;E=g*9|0;F=g*10|0;G=u<<1;H=u*3|0;I=u<<2;J=u*5|0;K=u*6|0;L=u*7|0;t=c+(l<<1)+((_(g,-11)|0)<<1)|0;s=0;while(1){i=b[t+(w<<1)>>1]|0;d=b[t>>1]|0;k=b[t+(g<<1)>>1]|0;f=b[t+(x<<1)>>1]|0;l=b[t+(y<<1)>>1]|0;c=b[t+(z<<1)>>1]|0;m=b[t+(A<<1)>>1]|0;n=b[t+(B<<1)>>1]|0;o=b[t+(C<<1)>>1]|0;p=b[t+(D<<1)>>1]|0;q=b[t+(E<<1)>>1]|0;r=b[t+(F<<1)>>1]|0;h=(b[t+(v<<1)>>1]|0)+512+l+((k+d|0)*20|0)+(_(f+i|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[a>>1]=((e[a>>1]|0)+1+h|0)>>>1;j=a+(u<<1)|0;h=i+512+c+((f+k|0)*20|0)+(_(l+d|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[j>>1]=((e[j>>1]|0)+1+h|0)>>>1;i=a+(G<<1)|0;h=d+512+m+((l+f|0)*20|0)+(_(c+k|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(H<<1)|0;h=k+512+n+((c+l|0)*20|0)+(_(m+f|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(I<<1)|0;h=f+512+o+((m+c|0)*20|0)+(_(n+l|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(J<<1)|0;h=l+512+p+((n+m|0)*20|0)+(_(o+c|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(K<<1)|0;h=c+512+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;i=a+(L<<1)|0;h=m+512+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>10;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=((e[i>>1]|0)+1+h|0)>>>1;s=s+1|0;if((s|0)==8)break;else{t=t+2|0;a=a+2|0}}return}function Rr(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}qq(o,k+64|0,32,32);l=o+16|0;qq(l,k+80|0,32,32);qq(o+256|0,k+320|0,32,32);qq(o+272|0,k+336|0,32,32);tq(n,m,e,32,32,f);j=n+16|0;s=m+16|0;tq(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;tq(n+256|0,m,g,32,32,f);tq(n+272|0,s,g+16|0,32,32,f);g=0;do{r=g<<5;q=o+r|0;u=q;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=n+r|0;v=h;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;s=_(g,f)|0;m=b+s|0;t=m;e=c[t>>2]|0;t=c[t+4>>2]|0;k=JF(v^u|0,h^q|0,1)|0;k=EF(v|u|0,h|q|0,k&2147450879|0,C&2147450879|0)|0;q=C;h=JF(k^e|0,q^t|0,1)|0;h=EF(k|e|0,q|t|0,h&2147450879|0,C&2147450879|0)|0;c[m>>2]=h;c[m+4>>2]=C;r=r|8;m=o+r|0;h=m;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=n+r|0;t=r;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=b+(s+8)|0;q=s;e=c[q>>2]|0;q=c[q+4>>2]|0;k=JF(t^h|0,r^m|0,1)|0;k=EF(t|h|0,r|m|0,k&2147450879|0,C&2147450879|0)|0;m=C;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{u=h<<5;t=l+u|0;b=t;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=j+u|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;v=_(h,f)|0;s=g+v|0;n=s;r=c[n>>2]|0;n=c[n+4>>2]|0;q=JF(m^b|0,o^t|0,1)|0;q=EF(m|b|0,o|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;o=JF(q^r|0,t^n|0,1)|0;o=EF(q|r|0,t|n|0,o&2147450879|0,C&2147450879|0)|0;c[s>>2]=o;c[s+4>>2]=C;u=u|8;s=l+u|0;o=s;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;n=u;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=g+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(n^o|0,u^s|0,1)|0;q=EF(n|o|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function Sr(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=i;i=i+1184|0;l=m+512|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(k,l+64|0,32,32);j=k+16|0;qq(j,l+80|0,32,32);qq(k+256|0,l+320|0,32,32);qq(k+272|0,l+336|0,32,32);e=l+96|0;g=0;do{o=g<<5;n=e+o|0;u=n;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;s=k+o|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=_(g,f)|0;h=b+p|0;t=h;q=c[t>>2]|0;t=c[t+4>>2]|0;r=JF(v^u|0,s^n|0,1)|0;r=EF(v|u|0,s|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;s=JF(r^q|0,n^t|0,1)|0;s=EF(r|q|0,n|t|0,s&2147450879|0,C&2147450879|0)|0;c[h>>2]=s;c[h+4>>2]=C;o=o|8;h=e+o|0;s=h;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=k+o|0;t=o;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=b+(p+8)|0;n=p;q=c[n>>2]|0;n=c[n+4>>2]|0;r=JF(t^s|0,o^h|0,1)|0;r=EF(t|s|0,o|h|0,r&2147450879|0,C&2147450879|0)|0;h=C;o=JF(r^q|0,h^n|0,1)|0;o=EF(r|q|0,h|n|0,o&2147450879|0,C&2147450879|0)|0;c[p>>2]=o;c[p+4>>2]=C;g=g+1|0}while((g|0)!=16);h=b+16|0;e=l+112|0;g=0;do{u=g<<5;t=e+u|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=j+u|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=h+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(l^n|0,p^t|0,1)|0;q=EF(l|n|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=e+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=j+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=h+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}
function Tr(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;mq(k,g,32,f);m=k+16|0;mq(m,g+16|0,32,f);g=g+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Ur(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+2368|0;m=k;f=k+1856|0;g=k+1344|0;j=b+e|0;mq(f,j,32,e);h=f+16|0;mq(h,j+16|0,32,e);n=e<<3;j=j+n|0;mq(f+256|0,j,32,e);mq(f+272|0,j+16|0,32,e);tq(g,m,b,32,32,e);j=g+16|0;l=m+16|0;tq(j,l,b+16|0,32,32,e);b=b+n|0;tq(g+256|0,m,b,32,32,e);tq(g+272|0,l,b+16|0,32,32,e);b=0;do{m=b<<5;l=f+m|0;t=l;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;n=_(b,e)|0;o=a+n|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^l|0,1)|0;q=EF(u|t|0,r|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;r=JF(q^p|0,l^s|0,1)|0;r=EF(q|p|0,l|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;m=m|8;o=f+m|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+m|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;p=c[l>>2]|0;l=c[l+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=h+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=h+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Vr(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;mq(k,g,32,f);m=k+16|0;mq(m,g+16|0,32,f);g=g+(f<<3)|0;mq(k+256|0,g,32,f);mq(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}qq(l,j+64|0,32,32);h=l+16|0;qq(h,j+80|0,32,32);qq(l+256|0,j+320|0,32,32);qq(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Wr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;j=b;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^f|0,1)|0;i=EF(k|g|0,j|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;f=a+8|0;i=f;j=c[i>>2]|0;i=c[i+4>>2]|0;g=b+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[f>>2]=h;c[f+4>>2]=C;f=b+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;i=f;g=i;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(g^h|0,i^b|0,1)|0;j=EF(g|h|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^i|0,h^j|0,1)|0;k=EF(g|i|0,h|j|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;j=f;h=j;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(h^k|0,j^b|0,1)|0;i=EF(h|k|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^j|0,k^i|0,1)|0;g=EF(h|j|0,k|i|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;i=f;k=i;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(k^g|0,i^b|0,1)|0;j=EF(k|g|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^i|0,g^j|0,1)|0;h=EF(k|i|0,g|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;j=f;g=j;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(g^h|0,j^b|0,1)|0;i=EF(g|h|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^j|0,h^i|0,1)|0;k=EF(g|j|0,h|i|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;i=f;h=i;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(h^k|0,i^b|0,1)|0;j=EF(h|k|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^i|0,k^j|0,1)|0;g=EF(h|i|0,k|j|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;j=f;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^b|0,1)|0;i=EF(k|g|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;e=a+e|0;a=e;b=c[a>>2]|0;a=c[a+4>>2]|0;h=f;i=h;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=JF(i^b|0,h^a|0,1)|0;g=EF(i|b|0,h|a|0,g&2147450879|0,C&2147450879|0)|0;a=e;c[a>>2]=g;c[a+4>>2]=C;e=e+8|0;a=e;g=c[a>>2]|0;a=c[a+4>>2]|0;f=f+8|0;h=f;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;return}function Xr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;f=h;mq(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g<<4;p=f+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=f+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function Yr(a,b,c){a=a|0;b=b|0;c=c|0;Gr(a,b,c,c);return}function Zr(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;g=h;mq(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f<<4;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function _r(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;qq(g,f,16,16);b=0;do{l=b<<4;k=f+l|0;r=c[k>>2]|0;k=c[k+4>>2]|0;p=g+l|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=_(b,e)|0;j=a+m|0;q=j;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^k|0,1)|0;o=EF(s|r|0,p|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;p=JF(o^n|0,k^q|0,1)|0;p=EF(o|n|0,k|q|0,p&2147450879|0,C&2147450879|0)|0;c[j>>2]=p;c[j+4>>2]=C;l=l|8;j=f+l|0;p=c[j>>2]|0;j=c[j+4>>2]|0;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=a+(m+8)|0;k=m;n=c[k>>2]|0;k=c[k+4>>2]|0;o=JF(q^p|0,l^j|0,1)|0;o=EF(q|p|0,l|j|0,o&2147450879|0,C&2147450879|0)|0;j=C;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function $r(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function as(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;mq(f,b,16,e);tq(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function bs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function cs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Nr(a,g,e,16);i=f;return}function ds(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;qq(g,j,16,16);tq(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function es(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+416|0;Qr(a,d,b,c,16,c);i=d;return}function fs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;qq(g,j,16,16);tq(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function gs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;qq(g,f,16,16);f=0;do{m=f<<4;l=b+m|0;r=c[l>>2]|0;l=c[l+4>>2]|0;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(f,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=b+m|0;p=c[k>>2]|0;k=c[k+4>>2]|0;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function hs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function is(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;mq(f,b+e|0,16,e);tq(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function js(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;mq(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;qq(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function ks(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;h=a;g=c[h>>2]|0;h=c[h+4>>2]|0;j=b;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;f=JF(i^g|0,j^h|0,1)|0;f=EF(i|g|0,j|h|0,f&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=f;c[h+4>>2]=C;h=b+e|0;a=a+e|0;f=a;j=c[f>>2]|0;f=c[f+4>>2]|0;b=h;g=b;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;i=JF(g^j|0,b^f|0,1)|0;i=EF(g|j|0,b|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;h=h+e|0;a=a+e|0;f=a;i=c[f>>2]|0;f=c[f+4>>2]|0;b=h;j=b;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=JF(j^i|0,b^f|0,1)|0;g=EF(j|i|0,b|f|0,g&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=g;c[f+4>>2]=C;a=a+e|0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;e=h+e|0;h=e;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;e=e+4|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;b=JF(h^g|0,e^f|0,1)|0;b=EF(h|g|0,e|f|0,b&2147450879|0,C&2147450879|0)|0;e=a;c[e>>2]=b;c[e+4>>2]=C;return}function ls(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;Vq(l,b,8,e);j=b;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;k=a;n=c[k>>2]|0;k=c[k+4>>2]|0;g=JF(p^o|0,m^j|0,1)|0;g=EF(p|o|0,m|j|0,g&2147450879|0,C&2147450879|0)|0;j=C;m=JF(g^n|0,j^k|0,1)|0;m=EF(g|n|0,j|k|0,m&2147450879|0,C&2147450879|0)|0;k=a;c[k>>2]=m;c[k+4>>2]=C;k=b+e|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;g=a+e|0;o=g;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^k|0,1)|0;h=EF(n|m|0,j|k|0,h&2147450879|0,C&2147450879|0)|0;k=C;j=JF(h^p|0,k^o|0,1)|0;j=EF(h|p|0,k|o|0,j&2147450879|0,C&2147450879|0)|0;c[g>>2]=j;c[g+4>>2]=C;g=e<<1;j=b+g|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=l+16|0;p=c[k>>2]|0;k=c[k+4>>2]|0;g=a+g|0;h=g;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,k^j|0,1)|0;n=EF(p|o|0,k|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;k=JF(n^m|0,j^h|0,1)|0;k=EF(n|m|0,j|h|0,k&2147450879|0,C&2147450879|0)|0;c[g>>2]=k;c[g+4>>2]=C;e=e*3|0;g=b+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ms(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=0;while(1){k=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;j=c+6|0;d=(((e[k>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[j>>1]|0)+(e[c>>1]|0)|0,-5)|0;h=c+8|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;g=c+10|0;d=(((e[j>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[g>>1]|0)+(e[i>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;l=l+1|0;if((l|0)==4)break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function ns(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;Vq(l,b,8,e);g=b+2|0;j=g;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;b=a;n=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(p^o|0,m^j|0,1)|0;k=EF(p|o|0,m|j|0,k&2147450879|0,C&2147450879|0)|0;j=C;m=JF(k^n|0,j^b|0,1)|0;m=EF(k|n|0,j|b|0,m&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=m;c[b+4>>2]=C;b=g+e|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;k=a+e|0;o=k;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^b|0,1)|0;h=EF(n|m|0,j|b|0,h&2147450879|0,C&2147450879|0)|0;b=C;j=JF(h^p|0,b^o|0,1)|0;j=EF(h|p|0,b|o|0,j&2147450879|0,C&2147450879|0)|0;c[k>>2]=j;c[k+4>>2]=C;k=e<<1;j=g+k|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;e=e*3|0;g=g+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function os(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=u+8|0;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=t;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+24|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+32|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=u+40|0;c[v>>2]=k;c[v+4>>2]=g;q=q+e|0;v=q;b=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;o=u+48|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=v;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;Zq(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ps(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Vq(g,b,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Zq(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function qs(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+208|0;f=e+32|0;k=e;Vq(f,b,8,d);ar(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function rs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Vq(g,b,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Zq(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ss(a,f,g){a=a|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=i;i=i+80|0;s=v;r=s+16|0;t=f+(0-(g<<1))|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=s;c[h>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[h+4>>2]=q;t=t+g|0;h=t;q=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=s+8|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=h;t=t+g|0;u=t;h=u;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=r;c[q>>2]=h;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+24|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+32|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;f=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=s+40|0;c[q>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+48|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+56|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;s=s+64|0;c[s>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[s+4>>2]=t;s=g>>1;t=s<<1;u=s*3|0;h=h&65535;f=0;while(1){j=e[r+-8>>1]|0;k=h&65535;l=e[r+8>>1]|0;m=e[r+16>>1]|0;n=e[r+24>>1]|0;o=e[r+32>>1]|0;p=e[r+40>>1]|0;q=e[r+48>>1]|0;g=(e[r+-16>>1]|0)+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[a>>1]=((e[a>>1]|0)+1+g|0)>>>1;h=a+(s<<1)|0;g=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(t<<1)|0;g=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(u<<1)|0;g=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;g=r+2|0;f=f+1|0;if((f|0)==4)break;h=b[g>>1]|0;a=a+2|0;r=g}i=v;return}function ts(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+288|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;Zq(g,o,8);ar(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function us(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i;i=i+144|0;h=t;r=d>>1;s=r<<1;f=h;g=0;d=c+(0-s<<1)|0;while(1){k=e[d>>1]|0;n=e[d+2>>1]|0;m=e[d+-2>>1]|0;q=e[d+4>>1]|0;l=_(q+m|0,-5)|0;p=e[d+6>>1]|0;b[f>>1]=(e[d+-4>>1]|0)+((n+k|0)*20|0)+l+p;l=_(p+k|0,-5)|0;o=e[d+8>>1]|0;b[f+2>>1]=m+((q+n|0)*20|0)+l+o;l=_(o+n|0,-5)|0;m=e[d+10>>1]|0;b[f+4>>1]=k+((p+q|0)*20|0)+l+m;q=_(m+q|0,-5)|0;b[f+6>>1]=n+((o+p|0)*20|0)+q+(e[d+12>>1]|0);g=g+1|0;if((g|0)==9)break;else{f=f+16|0;d=d+(r<<1)|0}}q=r*3|0;p=h+32|0;o=0;while(1){g=b[p+-16>>1]|0;c=b[p>>1]|0;h=b[p+16>>1]|0;j=b[p+32>>1]|0;k=b[p+48>>1]|0;l=b[p+64>>1]|0;m=b[p+80>>1]|0;n=b[p+96>>1]|0;d=(b[p+-32>>1]|0)+512+k+((h+c|0)*20|0)+(_(j+g|0,-5)|0)>>10;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;f=a+(r<<1)|0;d=g+512+l+((j+h|0)*20|0)+(_(k+c|0,-5)|0)>>10;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(s<<1)|0;d=c+512+m+((k+j|0)*20|0)+(_(l+h|0,-5)|0)>>10;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(q<<1)|0;d=h+512+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>10;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;o=o+1|0;if((o|0)==4)break;else{p=p+2|0;a=a+2|0}}i=t;return}function vs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+288|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)+2|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;Zq(g,o,8);ar(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ws(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;h=q;r=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u;c[n>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[n+4>>2]=h;q=q+e|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u+8|0;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=t;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u+24|0;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+32|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+40|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=u+48|0;c[o>>2]=k;c[o+4>>2]=g;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;Zq(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function xs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Vq(g,b+e|0,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Zq(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function ys(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+208|0;f=e+32|0;k=e;Vq(f,b+d|0,8,d);ar(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function zs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Vq(g,b+e|0,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Zq(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function As(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;f=b+8|0;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;h=a+8|0;c[h>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[h+4>>2]=f;h=b+e|0;f=a+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+8|0;i=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+8|0;c[f>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[f+4>>2]=h;f=a+16|0;h=b+16|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=b+24|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=a+24|0;c[b>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[b+4>>2]=g;b=h+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function Bs(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;f=k;Cs(f,b,32,e);h=f+16|0;j=b+16|0;Cs(h,j,32,e);g=b+(e<<3)|0;Cs(f+256|0,g,32,e);Cs(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g<<5;q=f+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=b+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{r=_(f,e)|0;p=j+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=f<<5;m=h+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=a+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=j+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=a+r|0;c[r>>2]=q;c[r+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function Cs(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=d>>1;o=f>>1;n=0;while(1){g=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;k=c+6|0;d=(((e[g>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=_((e[k>>1]|0)+(e[c>>1]|0)|0,-5)|0;m=c+8|0;d=(((e[i>>1]|0)+(e[g>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+2>>1]=d;d=_((e[m>>1]|0)+(e[g>>1]|0)|0,-5)|0;l=c+10|0;d=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+4>>1]=d;d=_((e[l>>1]|0)+(e[i>>1]|0)|0,-5)|0;j=c+12|0;d=(((e[m>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[g>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+6>>1]=d;d=_((e[j>>1]|0)+(e[k>>1]|0)|0,-5)|0;h=c+14|0;d=(((e[l>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[i>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+8>>1]=d;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+16|0;d=(((e[j>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+10>>1]=d;d=_((e[g>>1]|0)+(e[l>>1]|0)|0,-5)|0;f=c+18|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+12>>1]=d;d=_((e[f>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+14>>1]=d;n=n+1|0;if((n|0)==8)break;else{a=a+(p<<1)|0;c=c+(o<<1)|0}}return}function Ds(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;Cs(a,b,c,c);Cs(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;Cs(a,b,c,c);Cs(a+16|0,b+16|0,c,c);return}function Es(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;g=k;Cs(g,b,32,e);j=g+16|0;Cs(j,b+16|0,32,e);f=b+(e<<3)|0;Cs(g+256|0,f,32,e);Cs(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=h<<5;q=g+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=f+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{r=_(a,e)|0;p=f+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=a<<5;m=j+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=g+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=f+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=g+r|0;c[r>>2]=q;c[r+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Fs(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;Gs(l,g,32,32);h=l+16|0;j=k+80|0;Gs(h,j,32,32);Gs(l+256|0,k+320|0,32,32);Gs(l+272|0,k+336|0,32,32);e=0;do{k=e<<5;p=g+k|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=l+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(e,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;k=k|8;n=g+k|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;k=l+k|0;r=k;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=j+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=j+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Gs(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;K=d>>1;s=f>>1;C=_(s,-2)|0;D=0-s|0;E=s<<1;F=s*3|0;G=s<<2;H=s*5|0;I=s*6|0;J=s*7|0;t=s<<3;u=s*9|0;v=s*10|0;w=K<<1;x=K*3|0;y=K<<2;z=K*5|0;A=K*6|0;B=K*7|0;r=0;while(1){f=e[c+(D<<1)>>1]|0;g=e[c>>1]|0;h=e[c+(s<<1)>>1]|0;i=e[c+(E<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(t<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;d=(e[c+(C<<1)>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(K<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(w<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(x<<1)>>1]=d;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(y<<1)>>1]=d;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(z<<1)>>1]=d;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(A<<1)>>1]=d;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a+(B<<1)>>1]=d;r=r+1|0;if((r|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function Hs(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Cs(k,e,32,f);m=k+16|0;Cs(m,e+16|0,32,f);g=e+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Is(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+2368|0;n=k;f=k+1856|0;g=k+1344|0;Cs(f,b,32,e);j=f+16|0;o=b+16|0;Cs(j,o,32,e);m=b+(e<<3)|0;Cs(f+256|0,m,32,e);l=m+16|0;Cs(f+272|0,l,32,e);Js(g,n,b,32,32,e);h=g+16|0;b=n+16|0;Js(h,b,o,32,32,e);Js(g+256|0,n,m,32,32,e);Js(g+272|0,b,l,32,32,e);b=0;do{l=b<<5;o=f+l|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=JF(n^q|0,m^o|0,1)|0;p=EF(n|q|0,m|o|0,p&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=p;c[m+4>>2]=C;l=l|8;m=f+l|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=j+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=h+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=j+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Js(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0;j=h>>1;l=g*13|0;i=c;k=0;h=d+(0-(j<<1)<<1)|0;while(1){O=h+2|0;P=h+-2|0;N=h+4|0;L=_((e[N>>1]|0)+(e[P>>1]|0)|0,-5)|0;M=h+6|0;b[i>>1]=(((e[O>>1]|0)+(e[h>>1]|0)|0)*20|0)+55306+(e[h+-4>>1]|0)+L+(e[M>>1]|0);L=_((e[M>>1]|0)+(e[h>>1]|0)|0,-5)|0;K=h+8|0;b[i+2>>1]=(((e[N>>1]|0)+(e[O>>1]|0)|0)*20|0)+55306+(e[P>>1]|0)+L+(e[K>>1]|0);L=_((e[K>>1]|0)+(e[O>>1]|0)|0,-5)|0;P=h+10|0;b[i+4>>1]=(((e[M>>1]|0)+(e[N>>1]|0)|0)*20|0)+55306+(e[h>>1]|0)+L+(e[P>>1]|0);L=_((e[P>>1]|0)+(e[N>>1]|0)|0,-5)|0;Q=h+12|0;b[i+6>>1]=(((e[K>>1]|0)+(e[M>>1]|0)|0)*20|0)+55306+(e[O>>1]|0)+L+(e[Q>>1]|0);L=_((e[Q>>1]|0)+(e[M>>1]|0)|0,-5)|0;O=h+14|0;b[i+8>>1]=(((e[P>>1]|0)+(e[K>>1]|0)|0)*20|0)+55306+(e[N>>1]|0)+L+(e[O>>1]|0);L=_((e[O>>1]|0)+(e[K>>1]|0)|0,-5)|0;N=h+16|0;b[i+10>>1]=(((e[Q>>1]|0)+(e[P>>1]|0)|0)*20|0)+55306+(e[M>>1]|0)+L+(e[N>>1]|0);L=_((e[N>>1]|0)+(e[P>>1]|0)|0,-5)|0;M=h+18|0;b[i+12>>1]=(((e[O>>1]|0)+(e[Q>>1]|0)|0)*20|0)+55306+(e[K>>1]|0)+L+(e[M>>1]|0);Q=_((e[M>>1]|0)+(e[Q>>1]|0)|0,-5)|0;b[i+14>>1]=(((e[N>>1]|0)+(e[O>>1]|0)|0)*20|0)+55306+(e[P>>1]|0)+Q+(e[h+20>>1]|0);k=k+1|0;if((k|0)==13)break;else{i=i+(g<<1)|0;h=h+(j<<1)|0}}z=f>>1;A=_(g,-2)|0;B=0-g|0;C=g<<1;D=g*3|0;E=g<<2;F=g*5|0;G=g*6|0;H=g*7|0;I=g<<3;J=g*9|0;K=g*10|0;L=z<<1;M=z*3|0;N=z<<2;O=z*5|0;P=z*6|0;Q=z*7|0;y=c+(l<<1)+((_(g,-11)|0)<<1)|0;x=0;while(1){i=b[y+(B<<1)>>1]|0;d=b[y>>1]|0;j=d+10230|0;f=b[y+(g<<1)>>1]|0;k=f+10230|0;c=b[y+(C<<1)>>1]|0;l=c+10230|0;n=b[y+(D<<1)>>1]|0;m=n+10230|0;p=b[y+(E<<1)>>1]|0;o=p+10230|0;r=b[y+(F<<1)>>1]|0;q=r+10230|0;s=(b[y+(G<<1)>>1]|0)+10230|0;t=(b[y+(H<<1)>>1]|0)+10230|0;u=(b[y+(I<<1)>>1]|0)+10230|0;v=(b[y+(J<<1)>>1]|0)+10230|0;w=b[y+(K<<1)>>1]|0;h=(b[y+(A<<1)>>1]|0)+10742+m+((k+j|0)*20|0)+(_(l+(i+10230)|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a>>1]=h;h=i+10742+o+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(z<<1)>>1]=h;h=d+10742+q+((m+l|0)*20|0)+(_(o+k|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(L<<1)>>1]=h;h=f+10742+s+((o+m|0)*20|0)+(_(q+l|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(M<<1)>>1]=h;h=c+10742+t+((q+o|0)*20|0)+(_(s+m|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(N<<1)>>1]=h;h=n+10742+u+((s+q|0)*20|0)+(_(t+o|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(O<<1)>>1]=h;h=p+10742+v+((t+s|0)*20|0)+(_(u+q|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(P<<1)>>1]=h;h=r+20972+w+((u+t|0)*20|0)+(_(v+s|0,-5)|0)>>10;if(h>>>0>=1024)h=0-h>>31&1023;b[a+(Q<<1)>>1]=h;x=x+1|0;if((x|0)==8)break;else{y=y+2|0;a=a+2|0}}return}function Ks(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Cs(k,e,32,f);m=k+16|0;Cs(m,e+16|0,32,f);g=e+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Ls(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}Gs(b,h+64|0,e,32);Gs(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;Gs(n,h+320|0,e,32);Gs(n+16|0,h+336|0,e,32);i=j;return}function Ms(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Gs(o,k+64|0,32,32);l=o+16|0;Gs(l,k+80|0,32,32);Gs(o+256|0,k+320|0,32,32);Gs(o+272|0,k+336|0,32,32);Js(n,m,e,32,32,f);j=n+16|0;s=m+16|0;Js(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Js(n+256|0,m,g,32,32,f);Js(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function Ns(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+1344|0;f=d;Js(a,f,b,c,32,c);e=f+16|0;Js(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;Js(a,f,b,c,32,c);Js(a+16|0,e,b+16|0,c,32,c);i=d;return}function Os(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+3040|0;k=p+2368|0;m=p;o=p+1856|0;n=p+1344|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Gs(o,k+64|0,32,32);l=o+16|0;Gs(l,k+80|0,32,32);Gs(o+256|0,k+320|0,32,32);Gs(o+272|0,k+336|0,32,32);Js(n,m,e,32,32,f);j=n+16|0;s=m+16|0;Js(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Js(n+256|0,m,g,32,32,f);Js(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}
function hu(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Cs(k,g,32,f);m=k+16|0;Cs(m,g+16|0,32,f);g=g+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function iu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;k=i;i=i+2368|0;m=k;f=k+1856|0;g=k+1344|0;j=b+e|0;Cs(f,j,32,e);h=f+16|0;Cs(h,j+16|0,32,e);n=e<<3;j=j+n|0;Cs(f+256|0,j,32,e);Cs(f+272|0,j+16|0,32,e);Js(g,m,b,32,32,e);j=g+16|0;l=m+16|0;Js(j,l,b+16|0,32,32,e);b=b+n|0;Js(g+256|0,m,b,32,32,e);Js(g+272|0,l,b+16|0,32,32,e);b=0;do{m=b<<5;l=f+m|0;t=l;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=g+m|0;u=r;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;n=_(b,e)|0;o=a+n|0;s=o;p=c[s>>2]|0;s=c[s+4>>2]|0;q=JF(u^t|0,r^l|0,1)|0;q=EF(u|t|0,r|l|0,q&2147450879|0,C&2147450879|0)|0;l=C;r=JF(q^p|0,l^s|0,1)|0;r=EF(q|p|0,l|s|0,r&2147450879|0,C&2147450879|0)|0;c[o>>2]=r;c[o+4>>2]=C;m=m|8;o=f+m|0;r=o;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+m|0;s=m;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;p=c[l>>2]|0;l=c[l+4>>2]|0;q=JF(s^r|0,m^o|0,1)|0;q=EF(s|r|0,m|o|0,q&2147450879|0,C&2147450879|0)|0;o=C;m=JF(q^p|0,o^l|0,1)|0;m=EF(q|p|0,o|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{t=a<<5;s=h+t|0;m=s;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=j+t|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=_(a,e)|0;r=b+u|0;n=r;q=c[n>>2]|0;n=c[n+4>>2]|0;p=JF(l^m|0,o^s|0,1)|0;p=EF(l|m|0,o|s|0,p&2147450879|0,C&2147450879|0)|0;s=C;o=JF(p^q|0,s^n|0,1)|0;o=EF(p|q|0,s|n|0,o&2147450879|0,C&2147450879|0)|0;c[r>>2]=o;c[r+4>>2]=C;t=t|8;r=h+t|0;o=r;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;t=j+t|0;n=t;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;u=b+(u+8)|0;s=u;q=c[s>>2]|0;s=c[s+4>>2]|0;p=JF(n^o|0,t^r|0,1)|0;p=EF(n|o|0,t|r|0,p&2147450879|0,C&2147450879|0)|0;r=C;t=JF(p^q|0,r^s|0,1)|0;t=EF(p|q|0,r|s|0,t&2147450879|0,C&2147450879|0)|0;c[u>>2]=t;c[u+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function ju(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;g=e+f|0;Cs(k,g,32,f);m=k+16|0;Cs(m,g+16|0,32,f);g=g+(f<<3)|0;Cs(k+256|0,g,32,f);Cs(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Gs(l,j+64|0,32,32);h=l+16|0;Gs(h,j+80|0,32,32);Gs(l+256|0,j+320|0,32,32);Gs(l+272|0,j+336|0,32,32);e=0;do{q=e<<5;p=k+q|0;u=p;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=l+q|0;v=s;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;r=_(e,f)|0;o=b+r|0;t=o;j=c[t>>2]|0;t=c[t+4>>2]|0;g=JF(v^u|0,s^p|0,1)|0;g=EF(v|u|0,s|p|0,g&2147450879|0,C&2147450879|0)|0;p=C;s=JF(g^j|0,p^t|0,1)|0;s=EF(g|j|0,p|t|0,s&2147450879|0,C&2147450879|0)|0;c[o>>2]=s;c[o+4>>2]=C;q=q|8;o=k+q|0;s=o;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=l+q|0;t=q;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=b+(r+8)|0;p=r;j=c[p>>2]|0;p=c[p+4>>2]|0;g=JF(t^s|0,q^o|0,1)|0;g=EF(t|s|0,q|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{u=g<<5;t=m+u|0;l=t;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;p=h+u|0;k=p;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;v=_(g,f)|0;s=e+v|0;o=s;r=c[o>>2]|0;o=c[o+4>>2]|0;q=JF(k^l|0,p^t|0,1)|0;q=EF(k|l|0,p|t|0,q&2147450879|0,C&2147450879|0)|0;t=C;p=JF(q^r|0,t^o|0,1)|0;p=EF(q|r|0,t|o|0,p&2147450879|0,C&2147450879|0)|0;c[s>>2]=p;c[s+4>>2]=C;u=u|8;s=m+u|0;p=s;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;u=h+u|0;o=u;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;v=e+(v+8)|0;t=v;r=c[t>>2]|0;t=c[t+4>>2]|0;q=JF(o^p|0,u^s|0,1)|0;q=EF(o|p|0,u|s|0,q&2147450879|0,C&2147450879|0)|0;s=C;u=JF(q^r|0,s^t|0,1)|0;u=EF(q|r|0,s|t|0,u&2147450879|0,C&2147450879|0)|0;c[v>>2]=u;c[v+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function ku(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;j=b;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^f|0,1)|0;i=EF(k|g|0,j|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;f=a+8|0;i=f;j=c[i>>2]|0;i=c[i+4>>2]|0;g=b+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[f>>2]=h;c[f+4>>2]=C;f=b+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;i=f;g=i;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(g^h|0,i^b|0,1)|0;j=EF(g|h|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^i|0,h^j|0,1)|0;k=EF(g|i|0,h|j|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;j=f;h=j;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(h^k|0,j^b|0,1)|0;i=EF(h|k|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^j|0,k^i|0,1)|0;g=EF(h|j|0,k|i|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;i=f;k=i;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(k^g|0,i^b|0,1)|0;j=EF(k|g|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^i|0,g^j|0,1)|0;h=EF(k|i|0,g|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;j=f;g=j;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(g^h|0,j^b|0,1)|0;i=EF(g|h|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^j|0,h^i|0,1)|0;k=EF(g|j|0,h|i|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;i=f;h=i;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(h^k|0,i^b|0,1)|0;j=EF(h|k|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^i|0,k^j|0,1)|0;g=EF(h|i|0,k|j|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;j=f;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^b|0,1)|0;i=EF(k|g|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;e=a+e|0;a=e;b=c[a>>2]|0;a=c[a+4>>2]|0;h=f;i=h;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=JF(i^b|0,h^a|0,1)|0;g=EF(i|b|0,h|a|0,g&2147450879|0,C&2147450879|0)|0;a=e;c[a>>2]=g;c[a+4>>2]=C;e=e+8|0;a=e;g=c[a>>2]|0;a=c[a+4>>2]|0;f=f+8|0;h=f;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;return}function lu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;f=h;Cs(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g<<4;p=f+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=f+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function mu(a,b,c){a=a|0;b=b|0;c=c|0;Wt(a,b,c,c);return}function nu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;g=h;Cs(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f<<4;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function ou(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;Gs(g,f,16,16);b=0;do{l=b<<4;k=f+l|0;r=c[k>>2]|0;k=c[k+4>>2]|0;p=g+l|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=_(b,e)|0;j=a+m|0;q=j;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^k|0,1)|0;o=EF(s|r|0,p|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;p=JF(o^n|0,k^q|0,1)|0;p=EF(o|n|0,k|q|0,p&2147450879|0,C&2147450879|0)|0;c[j>>2]=p;c[j+4>>2]=C;l=l|8;j=f+l|0;p=c[j>>2]|0;j=c[j+4>>2]|0;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=a+(m+8)|0;k=m;n=c[k>>2]|0;k=c[k+4>>2]|0;o=JF(q^p|0,l^j|0,1)|0;o=EF(q|p|0,l|j|0,o&2147450879|0,C&2147450879|0)|0;j=C;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function pu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function qu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;Cs(f,b,16,e);Js(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function ru(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function su(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;bu(a,g,e,16);i=f;return}function tu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Gs(g,j,16,16);Js(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function uu(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+416|0;eu(a,d,b,c,16,c);i=d;return}function vu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+880|0;k=h;g=h+752|0;f=h+624|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;Gs(g,j,16,16);Js(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function wu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;Gs(g,f,16,16);f=0;do{m=f<<4;l=b+m|0;r=c[l>>2]|0;l=c[l+4>>2]|0;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(f,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=b+m|0;p=c[k>>2]|0;k=c[k+4>>2]|0;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function xu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function yu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+672|0;f=h+544|0;g=h+416|0;Cs(f,b+e|0,16,e);Js(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function zu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;Cs(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;Gs(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Au(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;h=a;g=c[h>>2]|0;h=c[h+4>>2]|0;j=b;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;f=JF(i^g|0,j^h|0,1)|0;f=EF(i|g|0,j|h|0,f&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=f;c[h+4>>2]=C;h=b+e|0;a=a+e|0;f=a;j=c[f>>2]|0;f=c[f+4>>2]|0;b=h;g=b;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;i=JF(g^j|0,b^f|0,1)|0;i=EF(g|j|0,b|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;h=h+e|0;a=a+e|0;f=a;i=c[f>>2]|0;f=c[f+4>>2]|0;b=h;j=b;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=JF(j^i|0,b^f|0,1)|0;g=EF(j|i|0,b|f|0,g&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=g;c[f+4>>2]=C;a=a+e|0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;e=h+e|0;h=e;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;e=e+4|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;b=JF(h^g|0,e^f|0,1)|0;b=EF(h|g|0,e|f|0,b&2147450879|0,C&2147450879|0)|0;e=a;c[e>>2]=b;c[e+4>>2]=C;return}function Bu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;jt(l,b,8,e);j=b;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;k=a;n=c[k>>2]|0;k=c[k+4>>2]|0;g=JF(p^o|0,m^j|0,1)|0;g=EF(p|o|0,m|j|0,g&2147450879|0,C&2147450879|0)|0;j=C;m=JF(g^n|0,j^k|0,1)|0;m=EF(g|n|0,j|k|0,m&2147450879|0,C&2147450879|0)|0;k=a;c[k>>2]=m;c[k+4>>2]=C;k=b+e|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;g=a+e|0;o=g;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^k|0,1)|0;h=EF(n|m|0,j|k|0,h&2147450879|0,C&2147450879|0)|0;k=C;j=JF(h^p|0,k^o|0,1)|0;j=EF(h|p|0,k|o|0,j&2147450879|0,C&2147450879|0)|0;c[g>>2]=j;c[g+4>>2]=C;g=e<<1;j=b+g|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=l+16|0;p=c[k>>2]|0;k=c[k+4>>2]|0;g=a+g|0;h=g;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,k^j|0,1)|0;n=EF(p|o|0,k|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;k=JF(n^m|0,j^h|0,1)|0;k=EF(n|m|0,j|h|0,k&2147450879|0,C&2147450879|0)|0;c[g>>2]=k;c[g+4>>2]=C;e=e*3|0;g=b+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Cu(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=0;while(1){k=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;j=c+6|0;d=(((e[k>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[j>>1]|0)+(e[c>>1]|0)|0,-5)|0;h=c+8|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;g=c+10|0;d=(((e[j>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[g>>1]|0)+(e[i>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;l=l+1|0;if((l|0)==4)break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Du(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;jt(l,b,8,e);g=b+2|0;j=g;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;b=a;n=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(p^o|0,m^j|0,1)|0;k=EF(p|o|0,m|j|0,k&2147450879|0,C&2147450879|0)|0;j=C;m=JF(k^n|0,j^b|0,1)|0;m=EF(k|n|0,j|b|0,m&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=m;c[b+4>>2]=C;b=g+e|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;k=a+e|0;o=k;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^b|0,1)|0;h=EF(n|m|0,j|b|0,h&2147450879|0,C&2147450879|0)|0;b=C;j=JF(h^p|0,b^o|0,1)|0;j=EF(h|p|0,b|o|0,j&2147450879|0,C&2147450879|0)|0;c[k>>2]=j;c[k+4>>2]=C;k=e<<1;j=g+k|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;e=e*3|0;g=g+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Eu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=u+8|0;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=t;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+24|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+32|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=u+40|0;c[v>>2]=k;c[v+4>>2]=g;q=q+e|0;v=q;b=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;o=u+48|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=v;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;nt(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Fu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;jt(g,b,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;nt(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Gu(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+208|0;f=e+32|0;k=e;jt(f,b,8,d);qt(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function Hu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;jt(g,b,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;nt(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Iu(a,f,g){a=a|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=i;i=i+80|0;s=v;r=s+16|0;t=f+(0-(g<<1))|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=s;c[h>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[h+4>>2]=q;t=t+g|0;h=t;q=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=s+8|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=h;t=t+g|0;u=t;h=u;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=r;c[q>>2]=h;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+24|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+32|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;f=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=s+40|0;c[q>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+48|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+56|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;s=s+64|0;c[s>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[s+4>>2]=t;s=g>>1;t=s<<1;u=s*3|0;h=h&65535;f=0;while(1){j=e[r+-8>>1]|0;k=h&65535;l=e[r+8>>1]|0;m=e[r+16>>1]|0;n=e[r+24>>1]|0;o=e[r+32>>1]|0;p=e[r+40>>1]|0;q=e[r+48>>1]|0;g=(e[r+-16>>1]|0)+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[a>>1]=((e[a>>1]|0)+1+g|0)>>>1;h=a+(s<<1)|0;g=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(t<<1)|0;g=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(u<<1)|0;g=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;g=r+2|0;f=f+1|0;if((f|0)==4)break;h=b[g>>1]|0;a=a+2|0;r=g}i=v;return}function Ju(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+288|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;nt(g,o,8);qt(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Ku(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=i;i=i+144|0;h=v;t=d>>1;u=t<<1;f=h;g=0;d=c+(0-u<<1)|0;while(1){m=e[d>>1]|0;r=e[d+2>>1]|0;o=e[d+-2>>1]|0;s=e[d+4>>1]|0;n=_(s+o|0,-5)|0;q=e[d+6>>1]|0;b[f>>1]=((r+m|0)*20|0)+55306+(e[d+-4>>1]|0)+n+q;n=_(q+m|0,-5)|0;p=e[d+8>>1]|0;b[f+2>>1]=((s+r|0)*20|0)+55306+o+n+p;n=_(p+r|0,-5)|0;o=e[d+10>>1]|0;b[f+4>>1]=((q+s|0)*20|0)+55306+m+n+o;s=_(o+s|0,-5)|0;b[f+6>>1]=((p+q|0)*20|0)+55306+r+s+(e[d+12>>1]|0);g=g+1|0;if((g|0)==9)break;else{f=f+16|0;d=d+(t<<1)|0}}s=t*3|0;r=h+32|0;q=0;while(1){g=b[r+-16>>1]|0;h=b[r>>1]|0;c=h+10230|0;m=b[r+16>>1]|0;j=m+10230|0;n=(b[r+32>>1]|0)+10230|0;o=(b[r+48>>1]|0)+10230|0;p=(b[r+64>>1]|0)+10230|0;k=(b[r+80>>1]|0)+10230|0;l=b[r+96>>1]|0;d=(b[r+-32>>1]|0)+10742+o+((j+c|0)*20|0)+(_(g+10230+n|0,-5)|0)>>10;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;f=a+(t<<1)|0;d=g+10742+p+((n+j|0)*20|0)+(_(o+c|0,-5)|0)>>10;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(u<<1)|0;d=h+10742+k+((o+n|0)*20|0)+(_(p+j|0,-5)|0)>>10;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+(s<<1)|0;d=m+20972+l+((p+o|0)*20|0)+(_(k+n|0,-5)|0)>>10;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;q=q+1|0;if((q|0)==4)break;else{r=r+2|0;a=a+2|0}}i=v;return}function Lu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+288|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)+2|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;nt(g,o,8);qt(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Mu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;h=q;r=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u;c[n>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[n+4>>2]=h;q=q+e|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u+8|0;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=t;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u+24|0;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+32|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+40|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=u+48|0;c[o>>2]=k;c[o+4>>2]=g;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;nt(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Nu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;jt(g,b+e|0,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;nt(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Ou(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+208|0;f=e+32|0;k=e;jt(f,b+d|0,8,d);qt(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function Pu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;jt(g,b+e|0,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;nt(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function Qu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;g=b;h=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a;c[f>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[f+4>>2]=g;f=b+8|0;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;h=a+8|0;c[h>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[h+4>>2]=f;h=b+e|0;f=a+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;i=h+8|0;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f+8|0;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;h=h+e|0;f=f+e|0;g=h;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;j=h+8|0;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f+8|0;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+e|0;f=f+e|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=h+8|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=f+8|0;c[j>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[j+4>>2]=g;h=h+e|0;f=f+e|0;j=h;g=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=f;c[i>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[i+4>>2]=j;h=h+8|0;i=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+8|0;c[f>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[f+4>>2]=h;f=a+16|0;h=b+16|0;i=h;j=i;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;g=f;c[g>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[g+4>>2]=i;g=b+24|0;i=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=a+24|0;c[b>>2]=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;c[b+4>>2]=g;b=h+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;f=f+e|0;g=b;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;h=b+8|0;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f+8|0;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;b=b+e|0;f=f+e|0;a=b;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;g=b+8|0;a=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=f+8|0;c[h>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[h+4>>2]=g;b=b+e|0;f=f+e|0;h=b;g=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;a=f;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=h;a=b+8|0;h=a;a=a+4|0;a=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;g=f+8|0;c[g>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[g+4>>2]=a;b=b+e|0;e=f+e|0;f=b;g=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;a=e;c[a>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;c[a+4>>2]=f;b=b+8|0;a=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;e=e+8|0;c[e>>2]=d[a>>0]|d[a+1>>0]<<8|d[a+2>>0]<<16|d[a+3>>0]<<24;c[e+4>>2]=b;return}function Ru(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;f=k;Su(f,b,32,e);h=f+16|0;j=b+16|0;Su(h,j,32,e);g=b+(e<<3)|0;Su(f+256|0,g,32,e);Su(f+272|0,g+16|0,32,e);g=0;do{l=_(g,e)|0;n=b+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g<<5;q=f+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=b+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=f+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;g=g+1|0}while((g|0)!=16);a=a+16|0;f=0;do{r=_(f,e)|0;p=j+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=f<<5;m=h+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=a+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=j+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=a+r|0;c[r>>2]=q;c[r+4>>2]=C;f=f+1|0}while((f|0)!=16);i=k;return}function Su(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=d>>1;o=f>>1;n=0;while(1){g=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;k=c+6|0;d=(((e[g>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[k>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=_((e[k>>1]|0)+(e[c>>1]|0)|0,-5)|0;m=c+8|0;d=(((e[i>>1]|0)+(e[g>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[m>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+2>>1]=d;d=_((e[m>>1]|0)+(e[g>>1]|0)|0,-5)|0;l=c+10|0;d=(((e[k>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[l>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+4>>1]=d;d=_((e[l>>1]|0)+(e[i>>1]|0)|0,-5)|0;j=c+12|0;d=(((e[m>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[g>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+6>>1]=d;d=_((e[j>>1]|0)+(e[k>>1]|0)|0,-5)|0;h=c+14|0;d=(((e[l>>1]|0)+(e[m>>1]|0)|0)*20|0)+16+(e[i>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+8>>1]=d;d=_((e[h>>1]|0)+(e[m>>1]|0)|0,-5)|0;g=c+16|0;d=(((e[j>>1]|0)+(e[l>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+10>>1]=d;d=_((e[g>>1]|0)+(e[l>>1]|0)|0,-5)|0;f=c+18|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[m>>1]|0)+d+(e[f>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+12>>1]=d;d=_((e[f>>1]|0)+(e[j>>1]|0)|0,-5)|0;d=(((e[g>>1]|0)+(e[h>>1]|0)|0)*20|0)+16+(e[l>>1]|0)+d+(e[c+20>>1]|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+14>>1]=d;n=n+1|0;if((n|0)==8)break;else{a=a+(p<<1)|0;c=c+(o<<1)|0}}return}function Tu(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;Su(a,b,c,c);Su(a+16|0,b+16|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;Su(a,b,c,c);Su(a+16|0,b+16|0,c,c);return}function Uu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;k=i;i=i+512|0;g=k;Su(g,b,32,e);j=g+16|0;Su(j,b+16|0,32,e);f=b+(e<<3)|0;Su(g+256|0,f,32,e);Su(g+272|0,f+16|0,32,e);f=b+2|0;h=0;do{l=_(h,e)|0;n=f+l|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=h<<5;q=g+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=JF(r^m|0,q^n|0,1)|0;p=EF(r|m|0,q|n|0,p&2147450879|0,C&2147450879|0)|0;n=a+l|0;c[n>>2]=p;c[n+4>>2]=C;l=l+8|0;n=f+l|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=g+(o|8)|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(q^p|0,o^n|0,1)|0;m=EF(q|p|0,o|n|0,m&2147450879|0,C&2147450879|0)|0;l=a+l|0;c[l>>2]=m;c[l+4>>2]=C;h=h+1|0}while((h|0)!=16);g=a+16|0;f=b+18|0;a=0;do{r=_(a,e)|0;p=f+r|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=a<<5;m=j+o|0;l=m;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=JF(l^q|0,m^p|0,1)|0;n=EF(l|q|0,m|p|0,n&2147450879|0,C&2147450879|0)|0;p=g+r|0;c[p>>2]=n;c[p+4>>2]=C;r=r+8|0;p=f+r|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=j+(o|8)|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(m^n|0,o^p|0,1)|0;q=EF(m|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=g+r|0;c[r>>2]=q;c[r+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Vu(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i;i=i+1184|0;k=m+512|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){j=e;n=j;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=g;p=o;a[p>>0]=n;a[p+1>>0]=n>>8;a[p+2>>0]=n>>16;a[p+3>>0]=n>>24;o=o+4|0;a[o>>0]=j;a[o+1>>0]=j>>8;a[o+2>>0]=j>>16;a[o+3>>0]=j>>24;o=e+8|0;j=o;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;n=p;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;p=p+4|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+16|0;o=p;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=g+16|0;j=n;a[j>>0]=o;a[j+1>>0]=o>>8;a[j+2>>0]=o>>16;a[j+3>>0]=o>>24;n=n+4|0;a[n>>0]=p;a[n+1>>0]=p>>8;a[n+2>>0]=p>>16;a[n+3>>0]=p>>24;n=e+24|0;p=n;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+24|0;o=j;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;j=j+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}g=k+64|0;Wu(l,g,32,32);h=l+16|0;j=k+80|0;Wu(h,j,32,32);Wu(l+256|0,k+320|0,32,32);Wu(l+272|0,k+336|0,32,32);e=0;do{k=e<<5;p=g+k|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=l+k|0;o=n;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=JF(o^r|0,n^p|0,1)|0;q=EF(o|r|0,n|p|0,q&2147450879|0,C&2147450879|0)|0;p=_(e,f)|0;n=b+p|0;c[n>>2]=q;c[n+4>>2]=C;k=k|8;n=g+k|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;k=l+k|0;r=k;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=JF(r^q|0,k^n|0,1)|0;o=EF(r|q|0,k|n|0,o&2147450879|0,C&2147450879|0)|0;p=b+(p+8)|0;c[p>>2]=o;c[p+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=j+o|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=JF(q^l|0,p^r|0,1)|0;n=EF(q|l|0,p|r|0,n&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=n;c[p+4>>2]=C;o=o|8;p=j+o|0;n=p;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;l=o;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(l^n|0,o^p|0,1)|0;q=EF(l|n|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=m;return}function Wu(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;K=d>>1;s=f>>1;C=_(s,-2)|0;D=0-s|0;E=s<<1;F=s*3|0;G=s<<2;H=s*5|0;I=s*6|0;J=s*7|0;t=s<<3;u=s*9|0;v=s*10|0;w=K<<1;x=K*3|0;y=K<<2;z=K*5|0;A=K*6|0;B=K*7|0;r=0;while(1){f=e[c+(D<<1)>>1]|0;g=e[c>>1]|0;h=e[c+(s<<1)>>1]|0;i=e[c+(E<<1)>>1]|0;j=e[c+(F<<1)>>1]|0;k=e[c+(G<<1)>>1]|0;l=e[c+(H<<1)>>1]|0;m=e[c+(I<<1)>>1]|0;n=e[c+(J<<1)>>1]|0;o=e[c+(t<<1)>>1]|0;p=e[c+(u<<1)>>1]|0;q=e[c+(v<<1)>>1]|0;d=(e[c+(C<<1)>>1]|0)+16+j+((h+g|0)*20|0)+(_(i+f|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=f+16+k+((i+h|0)*20|0)+(_(j+g|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(K<<1)>>1]=d;d=g+16+l+((j+i|0)*20|0)+(_(k+h|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(w<<1)>>1]=d;d=h+16+m+((k+j|0)*20|0)+(_(l+i|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(x<<1)>>1]=d;d=i+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(y<<1)>>1]=d;d=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(z<<1)>>1]=d;d=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(A<<1)>>1]=d;d=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(d>>>0>=4096)d=0-d>>31&4095;b[a+(B<<1)>>1]=d;r=r+1|0;if((r|0)==8)break;else{a=a+2|0;c=c+2|0}}return}function Xu(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Su(k,e,32,f);m=k+16|0;Su(m,e+16|0,32,f);g=e+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function Yu(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;k=i;i=i+3712|0;n=k;f=k+3200|0;g=k+2688|0;Su(f,b,32,e);j=f+16|0;o=b+16|0;Su(j,o,32,e);m=b+(e<<3)|0;Su(f+256|0,m,32,e);l=m+16|0;Su(f+272|0,l,32,e);Zu(g,n,b,32,32,e);h=g+16|0;b=n+32|0;Zu(h,b,o,32,32,e);Zu(g+256|0,n,m,32,32,e);Zu(g+272|0,b,l,32,32,e);b=0;do{l=b<<5;o=f+l|0;q=o;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=g+l|0;n=m;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=JF(n^q|0,m^o|0,1)|0;p=EF(n|q|0,m|o|0,p&2147450879|0,C&2147450879|0)|0;o=_(b,e)|0;m=a+o|0;c[m>>2]=p;c[m+4>>2]=C;l=l|8;m=f+l|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=JF(q^p|0,l^m|0,1)|0;n=EF(q|p|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;o=a+(o+8)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=16);b=a+16|0;a=0;do{n=a<<5;q=j+n|0;l=q;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=h+n|0;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;m=JF(p^l|0,o^q|0,1)|0;m=EF(p|l|0,o|q|0,m&2147450879|0,C&2147450879|0)|0;q=_(a,e)|0;o=b+q|0;c[o>>2]=m;c[o+4>>2]=C;n=n|8;o=j+n|0;m=o;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+n|0;l=n;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;p=JF(l^m|0,n^o|0,1)|0;p=EF(l|m|0,n|o|0,p&2147450879|0,C&2147450879|0)|0;q=b+(q+8)|0;c[q>>2]=p;c[q+4>>2]=C;a=a+1|0}while((a|0)!=16);i=k;return}function Zu(a,d,f,g,h,i){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;k=i>>1;m=h*13|0;j=d;l=0;i=f+(0-(k<<1)<<1)|0;while(1){L=e[i>>1]|0;K=e[i+2>>1]|0;I=e[i+-2>>1]|0;J=e[i+4>>1]|0;G=_(J+I|0,-5)|0;H=e[i+6>>1]|0;c[j>>2]=(e[i+-4>>1]|0)+((K+L|0)*20|0)+G+H;G=_(H+L|0,-5)|0;F=e[i+8>>1]|0;c[j+4>>2]=I+((J+K|0)*20|0)+G+F;G=_(F+K|0,-5)|0;I=e[i+10>>1]|0;c[j+8>>2]=L+((H+J|0)*20|0)+G+I;G=_(I+J|0,-5)|0;L=e[i+12>>1]|0;c[j+12>>2]=K+((F+H|0)*20|0)+G+L;G=_(L+H|0,-5)|0;K=e[i+14>>1]|0;c[j+16>>2]=J+((I+F|0)*20|0)+G+K;G=_(K+F|0,-5)|0;J=e[i+16>>1]|0;c[j+20>>2]=H+((L+I|0)*20|0)+G+J;G=_(J+I|0,-5)|0;H=e[i+18>>1]|0;c[j+24>>2]=F+((K+L|0)*20|0)+G+H;L=_(H+L|0,-5)|0;c[j+28>>2]=I+((J+K|0)*20|0)+L+(e[i+20>>1]|0);l=l+1|0;if((l|0)==13)break;else{j=j+(h<<2)|0;i=i+(k<<1)|0}}u=g>>1;v=_(h,-2)|0;w=0-h|0;x=h<<1;y=h*3|0;z=h<<2;A=h*5|0;B=h*6|0;C=h*7|0;D=h<<3;E=h*9|0;F=h*10|0;G=u<<1;H=u*3|0;I=u<<2;J=u*5|0;K=u*6|0;L=u*7|0;t=d+(m<<2)+((_(h,-11)|0)<<2)|0;s=0;while(1){j=c[t+(w<<2)>>2]|0;k=c[t>>2]|0;f=c[t+(h<<2)>>2]|0;l=c[t+(x<<2)>>2]|0;g=c[t+(y<<2)>>2]|0;m=c[t+(z<<2)>>2]|0;d=c[t+(A<<2)>>2]|0;n=c[t+(B<<2)>>2]|0;o=c[t+(C<<2)>>2]|0;p=c[t+(D<<2)>>2]|0;q=c[t+(E<<2)>>2]|0;r=c[t+(F<<2)>>2]|0;i=(c[t+(v<<2)>>2]|0)+512+g+((f+k|0)*20|0)+(_(l+j|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a>>1]=i;i=j+512+m+((l+f|0)*20|0)+(_(g+k|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(u<<1)>>1]=i;i=k+512+d+((g+l|0)*20|0)+(_(m+f|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(G<<1)>>1]=i;i=f+512+n+((m+g|0)*20|0)+(_(d+l|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(H<<1)>>1]=i;i=l+512+o+((d+m|0)*20|0)+(_(n+g|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(I<<1)>>1]=i;i=g+512+p+((n+d|0)*20|0)+(_(o+m|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(J<<1)>>1]=i;i=m+512+q+((o+n|0)*20|0)+(_(p+d|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(K<<1)>>1]=i;i=d+512+r+((p+o|0)*20|0)+(_(q+n|0,-5)|0)>>10;if(i>>>0>=4096)i=0-i>>31&4095;b[a+(L<<1)>>1]=i;s=s+1|0;if((s|0)==8)break;else{t=t+4|0;a=a+2|0}}return}function _u(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=i;i=i+1696|0;j=n+1024|0;k=n+512|0;l=n;Su(k,e,32,f);m=k+16|0;Su(m,e+16|0,32,f);g=e+(f<<3)|0;Su(k+256|0,g,32,f);Su(k+272|0,g+16|0,32,f);e=e+(0-(f<<1))+2|0;g=j;h=0;while(1){o=e;p=o;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=g;r=q;a[r>>0]=p;a[r+1>>0]=p>>8;a[r+2>>0]=p>>16;a[r+3>>0]=p>>24;q=q+4|0;a[q>>0]=o;a[q+1>>0]=o>>8;a[q+2>>0]=o>>16;a[q+3>>0]=o>>24;q=e+8|0;o=q;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=g+8|0;p=r;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;r=r+4|0;a[r>>0]=q;a[r+1>>0]=q>>8;a[r+2>>0]=q>>16;a[r+3>>0]=q>>24;r=e+16|0;q=r;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=g+16|0;o=p;a[o>>0]=q;a[o+1>>0]=q>>8;a[o+2>>0]=q>>16;a[o+3>>0]=q>>24;p=p+4|0;a[p>>0]=r;a[p+1>>0]=r>>8;a[p+2>>0]=r>>16;a[p+3>>0]=r>>24;p=e+24|0;r=p;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+24|0;q=o;a[q>>0]=r;a[q+1>>0]=r>>8;a[q+2>>0]=r>>16;a[q+3>>0]=r>>24;o=o+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+32|0}}Wu(l,j+64|0,32,32);h=l+16|0;Wu(h,j+80|0,32,32);Wu(l+256|0,j+320|0,32,32);Wu(l+272|0,j+336|0,32,32);e=0;do{o=e<<5;r=k+o|0;g=r;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=l+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=JF(q^g|0,p^r|0,1)|0;j=EF(q|g|0,p|r|0,j&2147450879|0,C&2147450879|0)|0;r=_(e,f)|0;p=b+r|0;c[p>>2]=j;c[p+4>>2]=C;o=o|8;p=k+o|0;j=p;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=l+o|0;g=o;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(g^j|0,o^p|0,1)|0;q=EF(g|j|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=b+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;e=e+1|0}while((e|0)!=16);e=b+16|0;g=0;do{o=g<<5;r=m+o|0;k=r;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;p=h+o|0;q=p;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=JF(q^k|0,p^r|0,1)|0;l=EF(q|k|0,p|r|0,l&2147450879|0,C&2147450879|0)|0;r=_(g,f)|0;p=e+r|0;c[p>>2]=l;c[p+4>>2]=C;o=o|8;p=m+o|0;l=p;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=h+o|0;k=o;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;q=JF(k^l|0,o^p|0,1)|0;q=EF(k|l|0,o|p|0,q&2147450879|0,C&2147450879|0)|0;r=e+(r+8)|0;c[r>>2]=q;c[r+4>>2]=C;g=g+1|0}while((g|0)!=16);i=n;return}function $u(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;j=i;i=i+672|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){k=c;l=k;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;n=m;a[n>>0]=l;a[n+1>>0]=l>>8;a[n+2>>0]=l>>16;a[n+3>>0]=l>>24;m=m+4|0;a[m>>0]=k;a[m+1>>0]=k>>8;a[m+2>>0]=k>>16;a[m+3>>0]=k>>24;m=c+8|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+8|0;l=n;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;n=n+4|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;n=c+16|0;m=n;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=f+16|0;k=l;a[k>>0]=m;a[k+1>>0]=m>>8;a[k+2>>0]=m>>16;a[k+3>>0]=m>>24;l=l+4|0;a[l>>0]=n;a[l+1>>0]=n>>8;a[l+2>>0]=n>>16;a[l+3>>0]=n>>24;l=c+24|0;n=l;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+24|0;m=k;a[m>>0]=n;a[m+1>>0]=n>>8;a[m+2>>0]=n>>16;a[m+3>>0]=n>>24;k=k+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+32|0}}Wu(b,h+64|0,e,32);Wu(b+16|0,h+80|0,e,32);n=b+(e<<3)|0;Wu(n,h+320|0,e,32);Wu(n+16|0,h+336|0,e,32);i=j;return}function av(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Wu(o,k+64|0,32,32);l=o+16|0;Wu(l,k+80|0,32,32);Wu(o+256|0,k+320|0,32,32);Wu(o+272|0,k+336|0,32,32);Zu(n,m,e,32,32,f);j=n+16|0;s=m+32|0;Zu(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Zu(n+256|0,m,g,32,32,f);Zu(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}function bv(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+2688|0;f=d;Zu(a,f,b,c,32,c);e=f+32|0;Zu(a+16|0,e,b+16|0,c,32,c);g=c<<3;b=b+g|0;a=a+g|0;Zu(a,f,b,c,32,c);Zu(a+16|0,e,b+16|0,c,32,c);i=d;return}function cv(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=i;i=i+4384|0;k=p+3712|0;m=p;o=p+3200|0;n=p+2688|0;g=e+(0-(f<<1))+2|0;h=k;j=0;while(1){l=g;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=h;s=r;a[s>>0]=q;a[s+1>>0]=q>>8;a[s+2>>0]=q>>16;a[s+3>>0]=q>>24;r=r+4|0;a[r>>0]=l;a[r+1>>0]=l>>8;a[r+2>>0]=l>>16;a[r+3>>0]=l>>24;r=g+8|0;l=r;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=h+8|0;q=s;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;s=s+4|0;a[s>>0]=r;a[s+1>>0]=r>>8;a[s+2>>0]=r>>16;a[s+3>>0]=r>>24;s=g+16|0;r=s;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=h+16|0;l=q;a[l>>0]=r;a[l+1>>0]=r>>8;a[l+2>>0]=r>>16;a[l+3>>0]=r>>24;q=q+4|0;a[q>>0]=s;a[q+1>>0]=s>>8;a[q+2>>0]=s>>16;a[q+3>>0]=s>>24;q=g+24|0;s=q;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+24|0;r=l;a[r>>0]=s;a[r+1>>0]=s>>8;a[r+2>>0]=s>>16;a[r+3>>0]=s>>24;l=l+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+32|0}}Wu(o,k+64|0,32,32);l=o+16|0;Wu(l,k+80|0,32,32);Wu(o+256|0,k+320|0,32,32);Wu(o+272|0,k+336|0,32,32);Zu(n,m,e,32,32,f);j=n+16|0;s=m+32|0;Zu(j,s,e+16|0,32,32,f);g=e+(f<<3)|0;Zu(n+256|0,m,g,32,32,f);Zu(n+272|0,s,g+16|0,32,32,f);g=0;do{m=g<<5;s=o+m|0;k=s;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=n+m|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;e=JF(r^k|0,q^s|0,1)|0;e=EF(r|k|0,q|s|0,e&2147450879|0,C&2147450879|0)|0;s=_(g,f)|0;q=b+s|0;c[q>>2]=e;c[q+4>>2]=C;m=m|8;q=o+m|0;e=q;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=n+m|0;k=m;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;r=JF(k^e|0,m^q|0,1)|0;r=EF(k|e|0,m|q|0,r&2147450879|0,C&2147450879|0)|0;s=b+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;g=g+1|0}while((g|0)!=16);g=b+16|0;h=0;do{o=h<<5;s=l+o|0;b=s;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;s=s+4|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;q=j+o|0;r=q;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=JF(r^b|0,q^s|0,1)|0;n=EF(r|b|0,q|s|0,n&2147450879|0,C&2147450879|0)|0;s=_(h,f)|0;q=g+s|0;c[q>>2]=n;c[q+4>>2]=C;o=o|8;q=l+o|0;n=q;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;o=j+o|0;b=o;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;r=JF(b^n|0,o^q|0,1)|0;r=EF(b|n|0,o|q|0,r&2147450879|0,C&2147450879|0)|0;s=g+(s+8)|0;c[s>>2]=r;c[s+4>>2]=C;h=h+1|0}while((h|0)!=16);i=p;return}
function Qy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;j=b;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^f|0,1)|0;i=EF(k|g|0,j|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;f=a+8|0;i=f;j=c[i>>2]|0;i=c[i+4>>2]|0;g=b+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[f>>2]=h;c[f+4>>2]=C;f=b+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;i=f;g=i;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(g^h|0,i^b|0,1)|0;j=EF(g|h|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^i|0,h^j|0,1)|0;k=EF(g|i|0,h|j|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;j=f;h=j;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(h^k|0,j^b|0,1)|0;i=EF(h|k|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^j|0,k^i|0,1)|0;g=EF(h|j|0,k|i|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;i=f;k=i;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(k^g|0,i^b|0,1)|0;j=EF(k|g|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^i|0,g^j|0,1)|0;h=EF(k|i|0,g|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;h=c[b>>2]|0;b=c[b+4>>2]|0;j=f;g=j;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(g^h|0,j^b|0,1)|0;i=EF(g|h|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;h=f+8|0;g=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=JF(g^j|0,h^i|0,1)|0;k=EF(g|j|0,h|i|0,k&2147450879|0,C&2147450879|0)|0;c[b>>2]=k;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;k=c[b>>2]|0;b=c[b+4>>2]|0;i=f;h=i;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;i=i+4|0;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=JF(h^k|0,i^b|0,1)|0;j=EF(h|k|0,i|b|0,j&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=j;c[b+4>>2]=C;b=a+8|0;j=b;i=c[j>>2]|0;j=c[j+4>>2]|0;k=f+8|0;h=k;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=JF(h^i|0,k^j|0,1)|0;g=EF(h|i|0,k|j|0,g&2147450879|0,C&2147450879|0)|0;c[b>>2]=g;c[b+4>>2]=C;f=f+e|0;a=a+e|0;b=a;g=c[b>>2]|0;b=c[b+4>>2]|0;j=f;k=j;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;i=JF(k^g|0,j^b|0,1)|0;i=EF(k|g|0,j|b|0,i&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=i;c[b+4>>2]=C;b=a+8|0;i=b;j=c[i>>2]|0;i=c[i+4>>2]|0;g=f+8|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=JF(k^j|0,g^i|0,1)|0;h=EF(k|j|0,g|i|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;f=f+e|0;e=a+e|0;a=e;b=c[a>>2]|0;a=c[a+4>>2]|0;h=f;i=h;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=JF(i^b|0,h^a|0,1)|0;g=EF(i|b|0,h|a|0,g&2147450879|0,C&2147450879|0)|0;a=e;c[a>>2]=g;c[a+4>>2]=C;e=e+8|0;a=e;g=c[a>>2]|0;a=c[a+4>>2]|0;f=f+8|0;h=f;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;return}function Ry(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;f=h;gx(f,b,16,e);g=0;do{j=_(g,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=g<<4;p=f+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=f+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;g=g+1|0}while((g|0)!=8);i=h;return}function Sy(a,b,c){a=a|0;b=b|0;c=c|0;Ay(a,b,c,c);return}function Ty(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+128|0;g=h;gx(g,b,16,e);b=b+2|0;f=0;do{j=_(f,e)|0;l=b+j|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f<<4;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;j=j+8|0;m=b+j|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+(k|8)|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+j|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function Uy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;l=h;g=h+208|0;f=l+32|0;j=b+(0-(e<<1))|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+16|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+24|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+40|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+48|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+56|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+64|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+72|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+80|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+88|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+96|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+104|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+112|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+120|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+128|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+136|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+144|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;k=j+8|0;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+152|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;j=j+e|0;m=j;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+160|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;b=j+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+168|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+e|0;k=j;b=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=l+176|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=k;m=j+8|0;k=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=l+184|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=m;j=j+e|0;b=j;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=l+192|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=b;j=j+8|0;k=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+200|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[b+4>>2]=j;kx(g,f,16,16);b=0;do{l=b<<4;k=f+l|0;r=c[k>>2]|0;k=c[k+4>>2]|0;p=g+l|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=_(b,e)|0;j=a+m|0;q=j;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^k|0,1)|0;o=EF(s|r|0,p|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;p=JF(o^n|0,k^q|0,1)|0;p=EF(o|n|0,k|q|0,p&2147450879|0,C&2147450879|0)|0;c[j>>2]=p;c[j+4>>2]=C;l=l|8;j=f+l|0;p=c[j>>2]|0;j=c[j+4>>2]|0;l=g+l|0;q=l;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=a+(m+8)|0;k=m;n=c[k>>2]|0;k=c[k+4>>2]|0;o=JF(q^p|0,l^j|0,1)|0;o=EF(q|p|0,l|j|0,o&2147450879|0,C&2147450879|0)|0;j=C;l=JF(o^n|0,j^k|0,1)|0;l=EF(o|n|0,j|k|0,l&2147450879|0,C&2147450879|0)|0;c[m>>2]=l;c[m+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Vy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Wy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;gx(f,b,16,e);nx(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Xy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function Yy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+208|0;k=f;g=k+32|0;h=b+(0-(e<<1))|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+8|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+16|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+24|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=g;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+40|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+48|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+56|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+64|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+72|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+80|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+88|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+96|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+104|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+112|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+120|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+128|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+136|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+144|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;j=h+8|0;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+152|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;h=h+e|0;l=h;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+160|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;b=h+8|0;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+168|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+e|0;j=h;b=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=k+176|0;c[l>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[l+4>>2]=j;l=h+8|0;j=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=k+184|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=l;h=h+e|0;b=h;l=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=k+192|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=b;h=h+8|0;j=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;b=k+200|0;c[b>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+4>>2]=h;Hy(a,g,e,16);i=f;return}function Zy(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;kx(g,j,16,16);nx(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function _y(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+832|0;Ky(a,d,b,c,16,c);i=d;return}function $y(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1296|0;k=h;g=h+1168|0;f=h+1040|0;j=k+32|0;l=b+(0-(e<<1))+2|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+8|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+24|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=j;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+48|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+56|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+64|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+72|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+80|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+88|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+96|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+104|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+112|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+120|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+128|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+136|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+144|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;m=l+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+152|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;l=l+e|0;o=l;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+160|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;n=l+8|0;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+168|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+e|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=k+176|0;c[o>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[o+4>>2]=m;o=l+8|0;m=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=k+184|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=o;l=l+e|0;n=l;o=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=k+192|0;c[m>>2]=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[m+4>>2]=n;l=l+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+200|0;c[k>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[k+4>>2]=l;kx(g,j,16,16);nx(f,h+208|0,b,16,16,e);b=0;do{n=b<<4;m=g+n|0;r=m;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;p=f+n|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=_(b,e)|0;l=a+o|0;q=l;k=c[q>>2]|0;q=c[q+4>>2]|0;j=JF(s^r|0,p^m|0,1)|0;j=EF(s|r|0,p|m|0,j&2147450879|0,C&2147450879|0)|0;m=C;p=JF(j^k|0,m^q|0,1)|0;p=EF(j|k|0,m|q|0,p&2147450879|0,C&2147450879|0)|0;c[l>>2]=p;c[l+4>>2]=C;n=n|8;l=g+n|0;p=l;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=f+n|0;q=n;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;o=a+(o+8)|0;m=o;k=c[m>>2]|0;m=c[m+4>>2]|0;j=JF(q^p|0,n^l|0,1)|0;j=EF(q|p|0,n|l|0,j&2147450879|0,C&2147450879|0)|0;l=C;n=JF(j^k|0,l^m|0,1)|0;n=EF(j|k|0,l|m|0,n&2147450879|0,C&2147450879|0)|0;c[o>>2]=n;c[o+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function az(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+336|0;j=h;g=h+208|0;f=j+32|0;k=b+(0-(e<<1))|0;b=k;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;n=k+8|0;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+8|0;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+16|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=k+8|0;m=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;n=j+24|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=b;k=k+e|0;n=k;b=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f;c[m>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=j+40|0;c[b>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[b+4>>2]=m;b=j+48|0;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=b;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+64|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+80|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+88|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+104|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+112|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+128|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+136|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=k+8|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+152|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+160|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=k+8|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+e|0;l=k;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j+176|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+184|0;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;k=k+e|0;m=k;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=j+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=j+200|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[j+4>>2]=k;kx(g,f,16,16);f=0;do{m=f<<4;l=b+m|0;r=c[l>>2]|0;l=c[l+4>>2]|0;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(f,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=b+m|0;p=c[k>>2]|0;k=c[k+4>>2]|0;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;f=f+1|0}while((f|0)!=8);i=h;return}function bz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b+e|0,16,e);k=b+(0-(e<<1))|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function cz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+1088|0;f=h+960|0;g=h+832|0;gx(f,b+e|0,16,e);nx(g,h,b,16,16,e);b=0;do{k=b<<4;l=f+k|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+k|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=_(b,e)|0;m=a+j|0;q=m;n=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^n|0,l^q|0,1)|0;p=EF(o|n|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[m>>2]=p;c[m+4>>2]=C;k=k|8;m=f+k|0;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=g+k|0;q=k;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=a+(j+8)|0;l=j;n=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,k^m|0,1)|0;o=EF(q|p|0,k|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;k=JF(o^n|0,m^l|0,1)|0;k=EF(o|n|0,m|l|0,k&2147450879|0,C&2147450879|0)|0;c[j>>2]=k;c[j+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function dz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;h=i;i=i+464|0;m=h;f=h+336|0;g=h+208|0;j=m+32|0;gx(f,b+e|0,16,e);k=b+(0-(e<<1))+2|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+8|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+16|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+24|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=j;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+40|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+48|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+56|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+64|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+72|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+80|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+88|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+96|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+104|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+112|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+120|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+128|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+136|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+144|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;l=k+8|0;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+152|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;k=k+e|0;n=k;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+160|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;b=k+8|0;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+168|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+e|0;l=k;b=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=m+176|0;c[n>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[n+4>>2]=l;n=k+8|0;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=m+184|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=n;k=k+e|0;b=k;n=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=m+192|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=b;k=k+8|0;l=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=m+200|0;c[b>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[b+4>>2]=k;kx(g,j,16,16);b=0;do{m=b<<4;l=f+m|0;r=l;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=g+m|0;s=p;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(b,e)|0;k=a+n|0;q=k;j=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(s^r|0,p^l|0,1)|0;o=EF(s|r|0,p|l|0,o&2147450879|0,C&2147450879|0)|0;l=C;p=JF(o^j|0,l^q|0,1)|0;p=EF(o|j|0,l|q|0,p&2147450879|0,C&2147450879|0)|0;c[k>>2]=p;c[k+4>>2]=C;m=m|8;k=f+m|0;p=k;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+m|0;q=m;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=a+(n+8)|0;l=n;j=c[l>>2]|0;l=c[l+4>>2]|0;o=JF(q^p|0,m^k|0,1)|0;o=EF(q|p|0,m|k|0,o&2147450879|0,C&2147450879|0)|0;k=C;m=JF(o^j|0,k^l|0,1)|0;m=EF(o|j|0,k|l|0,m&2147450879|0,C&2147450879|0)|0;c[n>>2]=m;c[n+4>>2]=C;b=b+1|0}while((b|0)!=8);i=h;return}function ez(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;h=a;g=c[h>>2]|0;h=c[h+4>>2]|0;j=b;i=j;i=d[i>>0]|d[i+1>>0]<<8|d[i+2>>0]<<16|d[i+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;f=JF(i^g|0,j^h|0,1)|0;f=EF(i|g|0,j|h|0,f&2147450879|0,C&2147450879|0)|0;h=a;c[h>>2]=f;c[h+4>>2]=C;h=b+e|0;a=a+e|0;f=a;j=c[f>>2]|0;f=c[f+4>>2]|0;b=h;g=b;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;i=JF(g^j|0,b^f|0,1)|0;i=EF(g|j|0,b|f|0,i&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=i;c[f+4>>2]=C;h=h+e|0;a=a+e|0;f=a;i=c[f>>2]|0;f=c[f+4>>2]|0;b=h;j=b;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=JF(j^i|0,b^f|0,1)|0;g=EF(j|i|0,b|f|0,g&2147450879|0,C&2147450879|0)|0;f=a;c[f>>2]=g;c[f+4>>2]=C;a=a+e|0;f=a;g=c[f>>2]|0;f=c[f+4>>2]|0;e=h+e|0;h=e;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;e=e+4|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;b=JF(h^g|0,e^f|0,1)|0;b=EF(h|g|0,e|f|0,b&2147450879|0,C&2147450879|0)|0;e=a;c[e>>2]=b;c[e+4>>2]=C;return}function fz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;Px(l,b,8,e);j=b;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;k=a;n=c[k>>2]|0;k=c[k+4>>2]|0;g=JF(p^o|0,m^j|0,1)|0;g=EF(p|o|0,m|j|0,g&2147450879|0,C&2147450879|0)|0;j=C;m=JF(g^n|0,j^k|0,1)|0;m=EF(g|n|0,j|k|0,m&2147450879|0,C&2147450879|0)|0;k=a;c[k>>2]=m;c[k+4>>2]=C;k=b+e|0;m=k;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;g=a+e|0;o=g;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^k|0,1)|0;h=EF(n|m|0,j|k|0,h&2147450879|0,C&2147450879|0)|0;k=C;j=JF(h^p|0,k^o|0,1)|0;j=EF(h|p|0,k|o|0,j&2147450879|0,C&2147450879|0)|0;c[g>>2]=j;c[g+4>>2]=C;g=e<<1;j=b+g|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=l+16|0;p=c[k>>2]|0;k=c[k+4>>2]|0;g=a+g|0;h=g;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,k^j|0,1)|0;n=EF(p|o|0,k|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;k=JF(n^m|0,j^h|0,1)|0;k=EF(n|m|0,j|h|0,k&2147450879|0,C&2147450879|0)|0;c[g>>2]=k;c[g+4>>2]=C;e=e*3|0;g=b+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function gz(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;m=d>>1;l=0;while(1){k=c+2|0;f=c+-2|0;i=c+4|0;d=_((e[i>>1]|0)+(e[f>>1]|0)|0,-5)|0;j=c+6|0;d=(((e[k>>1]|0)+(e[c>>1]|0)|0)*20|0)+16+(e[c+-4>>1]|0)+d+(e[j>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=((e[a>>1]|0)+1+d|0)>>>1;g=a+2|0;d=_((e[j>>1]|0)+(e[c>>1]|0)|0,-5)|0;h=c+8|0;d=(((e[i>>1]|0)+(e[k>>1]|0)|0)*20|0)+16+(e[f>>1]|0)+d+(e[h>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[g>>1]=((e[g>>1]|0)+1+d|0)>>>1;f=a+4|0;d=_((e[h>>1]|0)+(e[k>>1]|0)|0,-5)|0;g=c+10|0;d=(((e[j>>1]|0)+(e[i>>1]|0)|0)*20|0)+16+(e[c>>1]|0)+d+(e[g>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;f=a+6|0;d=_((e[g>>1]|0)+(e[i>>1]|0)|0,-5)|0;d=(((e[h>>1]|0)+(e[j>>1]|0)|0)*20|0)+16+(e[k>>1]|0)+d+(e[c+12>>1]|0)>>5;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=((e[f>>1]|0)+1+d|0)>>>1;l=l+1|0;if((l|0)==4)break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function hz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;f=i;i=i+32|0;l=f;Px(l,b,8,e);g=b+2|0;j=g;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=l;p=c[m>>2]|0;m=c[m+4>>2]|0;b=a;n=c[b>>2]|0;b=c[b+4>>2]|0;k=JF(p^o|0,m^j|0,1)|0;k=EF(p|o|0,m|j|0,k&2147450879|0,C&2147450879|0)|0;j=C;m=JF(k^n|0,j^b|0,1)|0;m=EF(k|n|0,j|b|0,m&2147450879|0,C&2147450879|0)|0;b=a;c[b>>2]=m;c[b+4>>2]=C;b=g+e|0;m=b;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=l+8|0;n=c[j>>2]|0;j=c[j+4>>2]|0;k=a+e|0;o=k;p=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(n^m|0,j^b|0,1)|0;h=EF(n|m|0,j|b|0,h&2147450879|0,C&2147450879|0)|0;b=C;j=JF(h^p|0,b^o|0,1)|0;j=EF(h|p|0,b|o|0,j&2147450879|0,C&2147450879|0)|0;c[k>>2]=j;c[k+4>>2]=C;k=e<<1;j=g+k|0;o=j;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;e=e*3|0;g=g+e|0;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+e|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function iz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=u+8|0;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=t;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+24|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+32|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;v=u+40|0;c[v>>2]=k;c[v+4>>2]=g;q=q+e|0;v=q;b=v;v=v+4|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;o=u+48|0;c[o>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[o+4>>2]=v;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;Tx(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function jz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Px(g,b,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Tx(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function kz(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+352|0;f=e+32|0;k=e;Px(f,b,8,d);Wx(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function lz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Px(g,b,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Tx(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function mz(a,f,g){a=a|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=i;i=i+80|0;s=v;r=s+16|0;t=f+(0-(g<<1))|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;h=s;c[h>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[h+4>>2]=q;t=t+g|0;h=t;q=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=s+8|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=h;t=t+g|0;u=t;h=u;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=r;c[q>>2]=h;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+24|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+32|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;f=u;u=u+4|0;u=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;q=s+40|0;c[q>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[q+4>>2]=u;t=t+g|0;q=t;u=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;f=s+48|0;c[f>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[f+4>>2]=q;t=t+g|0;f=t;q=f;f=f+4|0;f=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;u=s+56|0;c[u>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[u+4>>2]=f;t=t+g|0;u=t;t=t+4|0;t=d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24;s=s+64|0;c[s>>2]=d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24;c[s+4>>2]=t;s=g>>1;t=s<<1;u=s*3|0;h=h&65535;f=0;while(1){j=e[r+-8>>1]|0;k=h&65535;l=e[r+8>>1]|0;m=e[r+16>>1]|0;n=e[r+24>>1]|0;o=e[r+32>>1]|0;p=e[r+40>>1]|0;q=e[r+48>>1]|0;g=(e[r+-16>>1]|0)+16+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[a>>1]=((e[a>>1]|0)+1+g|0)>>>1;h=a+(s<<1)|0;g=j+16+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(t<<1)|0;g=k+16+p+((n+m|0)*20|0)+(_(o+l|0,-5)|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;h=a+(u<<1)|0;g=l+16+q+((o+n|0)*20|0)+(_(p+m|0,-5)|0)>>5;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=((e[h>>1]|0)+1+g|0)>>>1;g=r+2|0;f=f+1|0;if((f|0)==4)break;h=b[g>>1]|0;a=a+2|0;r=g}i=v;return}function nz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+432|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;Tx(g,o,8);Wx(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function oz(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+288|0;j=u;s=f>>1;t=s<<1;g=j;h=0;f=d+(0-t<<1)|0;while(1){l=e[f>>1]|0;q=e[f+2>>1]|0;n=e[f+-2>>1]|0;r=e[f+4>>1]|0;m=_(r+n|0,-5)|0;p=e[f+6>>1]|0;c[g>>2]=(e[f+-4>>1]|0)+((q+l|0)*20|0)+m+p;m=_(p+l|0,-5)|0;o=e[f+8>>1]|0;c[g+4>>2]=((r+q|0)*20|0)+n+m+o;m=_(o+q|0,-5)|0;n=e[f+10>>1]|0;c[g+8>>2]=((p+r|0)*20|0)+l+m+n;r=_(n+r|0,-5)|0;c[g+12>>2]=((o+p|0)*20|0)+q+r+(e[f+12>>1]|0);h=h+1|0;if((h|0)==9)break;else{g=g+32|0;f=f+(s<<1)|0}}r=s*3|0;q=j+64|0;p=0;while(1){h=c[q+-32>>2]|0;d=c[q>>2]|0;j=c[q+32>>2]|0;k=c[q+64>>2]|0;l=c[q+96>>2]|0;m=c[q+128>>2]|0;n=c[q+160>>2]|0;o=c[q+192>>2]|0;f=(c[q+-64>>2]|0)+512+l+((j+d|0)*20|0)+(_(k+h|0,-5)|0)>>10;if(f>>>0>=16384)f=0-f>>31&16383;b[a>>1]=((e[a>>1]|0)+1+f|0)>>>1;g=a+(s<<1)|0;f=h+512+m+((k+j|0)*20|0)+(_(l+d|0,-5)|0)>>10;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=((e[g>>1]|0)+1+f|0)>>>1;g=a+(t<<1)|0;f=d+512+n+((l+k|0)*20|0)+(_(m+j|0,-5)|0)>>10;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=((e[g>>1]|0)+1+f|0)>>>1;g=a+(r<<1)|0;f=j+512+o+((m+l|0)*20|0)+(_(n+k|0,-5)|0)>>10;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=((e[g>>1]|0)+1+f|0)>>>1;p=p+1|0;if((p|0)==4)break;else{q=q+4|0;a=a+2|0}}i=u;return}function pz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+432|0;n=f+64|0;g=f+32|0;l=f;o=n+16|0;k=e<<1;h=b+(0-k)+2|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+8|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=o;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+24|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+32|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;q=p;p=p+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;j=n+40|0;c[j>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[j+4>>2]=p;h=h+e|0;j=h;p=j;j=j+4|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;q=n+48|0;c[q>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[q+4>>2]=j;h=h+e|0;q=h;j=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;p=n+56|0;c[p>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[p+4>>2]=q;h=h+e|0;p=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=n+64|0;c[n>>2]=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;c[n+4>>2]=h;Tx(g,o,8);Wx(l,f+136|0,b,8,e);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function qz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;f=i;i=i+112|0;u=f+32|0;l=f;t=u+16|0;j=e<<1;q=b+(0-j)|0;h=q;r=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u;c[n>>2]=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;c[n+4>>2]=h;q=q+e|0;n=q;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;r=u+8|0;c[r>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[r+4>>2]=n;q=q+e|0;r=q;n=r;r=r+4|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=t;c[h>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+4>>2]=r;q=q+e|0;h=q;r=h;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;n=u+24|0;c[n>>2]=r;c[n+4>>2]=h;q=q+e|0;n=q;s=n;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=u+32|0;c[m>>2]=s;c[m+4>>2]=n;q=q+e|0;m=q;p=m;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;g=u+40|0;c[g>>2]=p;c[g+4>>2]=m;q=q+e|0;g=q;k=g;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;o=u+48|0;c[o>>2]=k;c[o+4>>2]=g;q=q+e|0;o=q;v=o;o=o+4|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=u+56|0;c[b>>2]=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;c[b+4>>2]=o;q=q+e|0;b=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;u=u+64|0;c[u>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[u+4>>2]=q;Tx(l,t,8);t=l;u=c[t>>2]|0;t=c[t+4>>2]|0;q=a;b=c[q>>2]|0;q=c[q+4>>2]|0;o=JF(u^r|0,t^h|0,1)|0;o=EF(u|r|0,t|h|0,o&2147450879|0,C&2147450879|0)|0;h=C;t=JF(o^b|0,h^q|0,1)|0;t=EF(o|b|0,h|q|0,t&2147450879|0,C&2147450879|0)|0;q=a;c[q>>2]=t;c[q+4>>2]=C;q=l+8|0;t=c[q>>2]|0;q=c[q+4>>2]|0;h=a+e|0;b=h;o=c[b>>2]|0;b=c[b+4>>2]|0;r=JF(t^s|0,q^n|0,1)|0;r=EF(t|s|0,q|n|0,r&2147450879|0,C&2147450879|0)|0;n=C;q=JF(r^o|0,n^b|0,1)|0;q=EF(r|o|0,n|b|0,q&2147450879|0,C&2147450879|0)|0;c[h>>2]=q;c[h+4>>2]=C;h=l+16|0;q=c[h>>2]|0;h=c[h+4>>2]|0;b=a+j|0;j=b;n=c[j>>2]|0;j=c[j+4>>2]|0;o=JF(q^p|0,h^m|0,1)|0;o=EF(q|p|0,h|m|0,o&2147450879|0,C&2147450879|0)|0;m=C;h=JF(o^n|0,m^j|0,1)|0;h=EF(o|n|0,m|j|0,h&2147450879|0,C&2147450879|0)|0;c[b>>2]=h;c[b+4>>2]=C;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function rz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Px(g,b+e|0,8,e);k=e<<1;n=b+(0-k)|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Tx(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function sz(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;e=i;i=i+352|0;f=e+32|0;k=e;Px(f,b+d|0,8,d);Wx(k,e+64|0,b,8,d);n=f;b=c[n>>2]|0;n=c[n+4>>2]|0;l=k;j=c[l>>2]|0;l=c[l+4>>2]|0;o=a;m=c[o>>2]|0;o=c[o+4>>2]|0;h=JF(j^b|0,l^n|0,1)|0;h=EF(j|b|0,l|n|0,h&2147450879|0,C&2147450879|0)|0;n=C;l=JF(h^m|0,n^o|0,1)|0;l=EF(h|m|0,n|o|0,l&2147450879|0,C&2147450879|0)|0;o=a;c[o>>2]=l;c[o+4>>2]=C;o=f+8|0;l=c[o>>2]|0;o=c[o+4>>2]|0;n=k+8|0;m=c[n>>2]|0;n=c[n+4>>2]|0;h=a+d|0;b=h;j=c[b>>2]|0;b=c[b+4>>2]|0;g=JF(m^l|0,n^o|0,1)|0;g=EF(m|l|0,n|o|0,g&2147450879|0,C&2147450879|0)|0;o=C;n=JF(g^j|0,o^b|0,1)|0;n=EF(g|j|0,o|b|0,n&2147450879|0,C&2147450879|0)|0;c[h>>2]=n;c[h+4>>2]=C;h=f+16|0;n=c[h>>2]|0;h=c[h+4>>2]|0;b=k+16|0;o=c[b>>2]|0;b=c[b+4>>2]|0;j=a+(d<<1)|0;g=j;l=c[g>>2]|0;g=c[g+4>>2]|0;m=JF(o^n|0,b^h|0,1)|0;m=EF(o|n|0,b|h|0,m&2147450879|0,C&2147450879|0)|0;h=C;b=JF(m^l|0,h^g|0,1)|0;b=EF(m|l|0,h|g|0,b&2147450879|0,C&2147450879|0)|0;c[j>>2]=b;c[j+4>>2]=C;f=f+24|0;j=c[f>>2]|0;f=c[f+4>>2]|0;b=k+24|0;k=c[b>>2]|0;b=c[b+4>>2]|0;d=a+(d*3|0)|0;a=d;g=c[a>>2]|0;a=c[a+4>>2]|0;h=JF(k^j|0,b^f|0,1)|0;h=EF(k|j|0,b|f|0,h&2147450879|0,C&2147450879|0)|0;f=C;b=JF(h^g|0,f^a|0,1)|0;b=EF(h|g|0,f|a|0,b&2147450879|0,C&2147450879|0)|0;c[d>>2]=b;c[d+4>>2]=C;i=e;return}function tz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;f=i;i=i+144|0;p=f+64|0;g=f+32|0;l=f;o=p+16|0;Px(g,b+e|0,8,e);k=e<<1;n=b+(0-k)+2|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+8|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=o;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+24|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+32|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;b=h;h=h+4|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;q=p+40|0;c[q>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;c[q+4>>2]=h;n=n+e|0;q=n;h=q;q=q+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;b=p+48|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=q;n=n+e|0;b=n;q=b;b=b+4|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;h=p+56|0;c[h>>2]=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;c[h+4>>2]=b;n=n+e|0;h=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;b=p+64|0;c[b>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;c[b+4>>2]=n;Tx(l,o,8);o=g;b=c[o>>2]|0;o=c[o+4>>2]|0;n=l;h=c[n>>2]|0;n=c[n+4>>2]|0;p=a;q=c[p>>2]|0;p=c[p+4>>2]|0;j=JF(h^b|0,n^o|0,1)|0;j=EF(h|b|0,n|o|0,j&2147450879|0,C&2147450879|0)|0;o=C;n=JF(j^q|0,o^p|0,1)|0;n=EF(j|q|0,o|p|0,n&2147450879|0,C&2147450879|0)|0;p=a;c[p>>2]=n;c[p+4>>2]=C;p=g+8|0;n=c[p>>2]|0;p=c[p+4>>2]|0;o=l+8|0;q=c[o>>2]|0;o=c[o+4>>2]|0;j=a+e|0;b=j;h=c[b>>2]|0;b=c[b+4>>2]|0;m=JF(q^n|0,o^p|0,1)|0;m=EF(q|n|0,o|p|0,m&2147450879|0,C&2147450879|0)|0;p=C;o=JF(m^h|0,p^b|0,1)|0;o=EF(m|h|0,p|b|0,o&2147450879|0,C&2147450879|0)|0;c[j>>2]=o;c[j+4>>2]=C;j=g+16|0;o=c[j>>2]|0;j=c[j+4>>2]|0;b=l+16|0;p=c[b>>2]|0;b=c[b+4>>2]|0;k=a+k|0;h=k;m=c[h>>2]|0;h=c[h+4>>2]|0;n=JF(p^o|0,b^j|0,1)|0;n=EF(p|o|0,b|j|0,n&2147450879|0,C&2147450879|0)|0;j=C;b=JF(n^m|0,j^h|0,1)|0;b=EF(n|m|0,j|h|0,b&2147450879|0,C&2147450879|0)|0;c[k>>2]=b;c[k+4>>2]=C;g=g+24|0;k=c[g>>2]|0;g=c[g+4>>2]|0;b=l+24|0;l=c[b>>2]|0;b=c[b+4>>2]|0;e=a+(e*3|0)|0;a=e;h=c[a>>2]|0;a=c[a+4>>2]|0;j=JF(l^k|0,b^g|0,1)|0;j=EF(l|k|0,b|g|0,j&2147450879|0,C&2147450879|0)|0;g=C;b=JF(j^h|0,g^a|0,1)|0;b=EF(j|h|0,g|a|0,b&2147450879|0,C&2147450879|0)|0;c[e>>2]=b;c[e+4>>2]=C;i=f;return}function uz(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;h=i;i=i+16|0;g=h;FF(a|0,0,984)|0;c[a>>2]=34012;e=(b|0)!=0;a:do if(e){d=c[b+8>>2]|0;c[a+8>>2]=d;c[a+12>>2]=b;c[a+48>>2]=c[b+12>>2];switch(d|0){case 1:{d=8;break a}case 0:break;default:{f=5;break a}}d=16}else{c[a+8>>2]=-1;d=-1;f=5}while(0);if((f|0)==5)d=(d|0)==3?32:0;lD(a,d,d);c[a+104>>2]=0;c[a+108>>2]=1;c[a+896>>2]=0;c[a+900>>2]=1;c[a+908>>2]=0;c[a+912>>2]=1;c[a+484>>2]=4;c[a+152>>2]=4;c[a+824>>2]=1;c[a+828>>2]=1;c[a+224>>2]=0;c[a+228>>2]=1;c[a+140>>2]=-1;c[a+420>>2]=-1;f=a+704|0;c[f>>2]=0;c[f+4>>2]=-2147483648;if(!e){g=0;i=h;return g|0}e=c[b+52>>2]|0;if(e|0){f=a+60|0;d=c[f>>2]|0;if(!d){d=cD(e)|0;c[f>>2]=d;if(!d){g=-12;i=h;return g|0}}e=c[b+44>>2]|0;if(e|0){c[d>>2]=e;kD(c[f>>2]|0)}}e=c[b+68>>2]|0;if(!e){g=0;i=h;return g|0}d=c[e>>2]|0;if(!d){g=0;i=h;return g|0}while(1){if((fD(a,d,c[e+4>>2]|0,0)|0)<=-1){f=16;break}e=e+8|0;d=c[e>>2]|0;if(!d){d=0;f=17;break}}if((f|0)==16){c[g>>2]=85859;c[g+4>>2]=83079;c[g+8>>2]=142;UC(0,0,104466,g);Ya()}else if((f|0)==17){i=h;return d|0}return 0}function vz(a){a=a|0;if(a|0){a=c[a+12>>2]|0;if(a|0){a=c[a>>2]|0;if(a|0)return a|0}}a=71591;return a|0}function wz(a,b){a=a|0;b=b|0;if(!b){b=c[a+12>>2]|0;if(b|0)if(c[b+44>>2]|0){b=c[a+60>>2]|0;if(b|0){a=b;return a|0}}}a=0;return a|0}function xz(a){a=a|0;var b=0;a:do if(!a)a=0;else{b=0;while(1){b=Cz(b)|0;if(!b){a=0;break a}if((c[b+44>>2]|0)==(a|0)){a=b;break}}}while(0);while(1){a=Cz(a)|0;if(!a){b=0;a=6;break}b=c[a+44>>2]|0;if(b|0){a=6;break}}if((a|0)==6)return b|0;return 0}function yz(a){a=a|0;a=c[a+12>>2]|0;if(a|0)if(c[a+88>>2]|0){a=6;return a|0}a=5;return a|0}function zz(a){a=a|0;var b=0;b=YC(984)|0;if(!b)b=0;else if((uz(b,a)|0)<0){$C(b);b=0}return b|0}function Az(b,c){b=b|0;c=c|0;var d=0;a:do if((c|0)>0){d=0;do{if(!(a[b+d>>0]|0))break a;d=d+1|0}while((d|0)<(c|0))}else d=0;while(0);return d|0}function Bz(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0;j=i;i=i+16|0;h=j;if(f>>>0>4294967263){bD(b);c[e>>2]=0;i=j;return}g=f+32|0;if((c[e>>2]|0)>>>0<g>>>0){h=f+64+(g>>>4)|0;h=h>>>0>g>>>0?h:g;bD(b);f=cD(h)|0;a[b>>0]=f;a[b+1>>0]=f>>8;a[b+2>>0]=f>>16;a[b+3>>0]=f>>24;c[e>>2]=(f|0)==0?0:h;i=j;return}g=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;if(!g){c[h>>2]=100095;c[h+4>>2]=83100;c[h+8>>2]=33;UC(0,0,104466,h);Ya()}g=g+f|0;b=g+32|0;do{a[g>>0]=0;g=g+1|0}while((g|0)<(b|0));i=j;return}function Cz(a){a=a|0;return c[((a|0)==0?108140:a+56|0)>>2]|0}function Dz(a){a=a|0;var b=0,d=0,e=0,f=0;if(!(c[27036]|0)){c[27036]=1;KA()}d=c[8514]|0;e=a+56|0;c[e>>2]=0;f=a;while(1){b=c[d>>2]|0;if(!b){b=c[d>>2]|0;if(!b)c[d>>2]=f;if(!b)break;b=c[d>>2]|0}d=b+56|0}c[8514]=e;b=c[a+72>>2]|0;if(!b)return;hb[b&3](a);return}function Ez(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;f=i;i=i+16|0;d=f+8|0;e=f;h=c[a+120>>2]|0;g=c[a+124>>2]|0;c[d>>2]=c[b>>2];c[d+4>>2]=c[b+4>>2];d=OC(h,g,d)|0;if((d|0)<0){h=c[b+4>>2]|0;c[e>>2]=c[b>>2];c[e+4>>2]=h;UC(a,24,83127,e);c[a+224>>2]=0;c[a+228>>2]=1;h=d;i=f;return h|0}else{e=b;g=c[e+4>>2]|0;h=a+224|0;c[h>>2]=c[e>>2];c[h+4>>2]=g;h=0;i=f;return h|0}return 0}function Fz(a,b,e,f){a=a|0;b=b|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0;h=a+140|0;g=yD(c[h>>2]|0)|0;if(!g){j=1;g=1}else{j=1<<(d[g+6>>0]|0);g=1<<(d[g+5>>0]|0)}switch(c[h>>2]|0){case 87:case 88:case 315:case 316:case 313:case 314:case 85:case 86:case 83:case 84:case 107:case 108:case 101:case 102:case 95:case 96:case 59:case 58:case 311:case 312:case 309:case 310:case 77:case 78:case 75:case 76:case 336:case 335:case 334:case 333:case 105:case 106:case 99:case 100:case 93:case 94:case 57:case 56:case 307:case 308:case 305:case 306:case 73:case 74:case 79:case 80:case 103:case 104:case 97:case 98:case 91:case 92:case 55:case 54:case 303:case 304:case 301:case 302:case 71:case 72:case 69:case 70:case 299:case 300:case 35:case 14:case 34:case 13:case 12:case 32:case 31:case 8:case 317:case 82:case 5:case 33:case 4:case 17:case 119:case 1:case 0:{h=32;g=16;break}case 18:case 320:case 7:{h=32;g=32;break}case 6:{i=(c[a+48>>2]|0)==23;h=i?64:j;g=i?64:g;break}case 46:{i=(c[a+48>>2]|0)==43;h=i?4:j;g=i?4:g;break}case 22:case 19:case 11:{i=c[a+48>>2]|0;switch(i|0){case 44:case 50:{h=4;g=4;break}default:h=j}j=(i|0)==150;h=j?8:h;g=j?8:g;break}case 3:{i=(c[a+48>>2]&-2|0)==54;h=i?4:j;g=i?4:g;break}case 2:{i=(c[a+48>>2]|0)==44;h=i?4:j;g=i?4:g;break}default:h=j}j=c[a+48>>2]|0;g=(j+-137|0)>>>0<2?((g|0)>8?g:8):g;c[b>>2]=(c[b>>2]|0)+-1+g&0-g;g=h+-1+(c[e>>2]|0)&0-h;c[e>>2]=g;if((j|0)!=28)if(!(c[a+800>>2]|0)){c[f>>2]=8;b=f+4|0;c[b>>2]=8;b=f+8|0;c[b>>2]=8;b=f+12|0;c[b>>2]=8;return}c[e>>2]=g+2;a=c[b>>2]|0;c[b>>2]=(a|0)>32?a:32;c[f>>2]=8;b=f+4|0;c[b>>2]=8;b=f+8|0;c[b>>2]=8;b=f+12|0;c[b>>2]=8;return}function Gz(d,e){d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i;i=i+16|0;f=t;g=yD(c[d+80>>2]|0)|0;if(!(a[g+7>>0]&16)){c[f>>2]=83156;c[f+4>>2]=83179;c[f+8>>2]=695;UC(0,0,104466,f);Ya()}s=g+4|0;f=c[s>>2]|0;if(!((f&255)<<24>>24)){i=t;return}q=d+68|0;r=d+72|0;p=g+8|0;o=0;do{h=c[d+(o<<2)>>2]|0;g=c[q>>2]|0;if((o+-1|0)>>>0<2){n=0-(0-(c[r>>2]|0)>>(f>>>16&255))|0;g=0-(0-g>>(f>>>8&255))|0}else n=c[r>>2]|0;a:do if((n|0)>0){l=e+(o<<2)|0;m=d+32+(o<<2)|0;if((g|0)>0)k=0;else{f=h;h=0;while(1){if(!(b[p>>1]&16384))FF(f|0,c[l>>2]&255|0,g|0)|0;h=h+1|0;if((h|0)==(n|0))break a;else f=f+(c[m>>2]|0)|0}}while(1){f=c[l>>2]|0;if(!(b[p>>1]&16384))FF(h|0,f&255|0,g|0)|0;else{f=f&65535;j=0;do{b[h+(j<<1)>>1]=f;j=j+1|0}while((j|0)!=(g|0))}k=k+1|0;if((k|0)==(n|0))break;else h=h+(c[m>>2]|0)|0}}while(0);o=o+1|0;f=c[s>>2]|0}while((o|0)<(f&255|0));i=t;return}function Hz(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0;E=i;i=i+144|0;D=E+48|0;A=E+32|0;B=E+24|0;y=E+16|0;d=E;p=E+80|0;v=E+64|0;m=E+56|0;l=E+52|0;w=a+64|0;z=c[(c[w>>2]|0)+24>>2]|0;x=a+8|0;a:do switch(c[x>>2]|0){case 0:{c[v>>2]=0;c[v+4>>2]=0;c[v+8>>2]=0;c[v+12>>2]=0;t=b+68|0;d=c[t>>2]|0;c[m>>2]=d;u=b+72|0;f=c[u>>2]|0;c[l>>2]=f;e=z+16|0;s=b+80|0;if((c[e>>2]|0)==(c[s>>2]|0))if((c[z+20>>2]|0)==(d|0))if((c[z+24>>2]|0)==(f|0))break a;f=z+28|0;Fz(a,m,l,f);n=p+32|0;k=a+140|0;o=p+36|0;g=z+32|0;q=p+40|0;h=z+36|0;r=p+44|0;j=z+40|0;d=c[m>>2]|0;do{KC(n,c[k>>2]|0,d)|0;d=(d&0-d)+d|0}while(((c[o>>2]|0)%(c[g>>2]|0)|0|((c[n>>2]|0)%(c[f>>2]|0)|0)|((c[q>>2]|0)%(c[h>>2]|0)|0)|((c[r>>2]|0)%(c[j>>2]|0)|0)|0)!=0);c[m>>2]=d;h=LC(p,c[k>>2]|0,c[l>>2]|0,0,n)|0;if((h|0)<0){b=-1;i=E;return b|0}m=c[p+4>>2]|0;d=m;g=c[p>>2]|0;if(!m){d=v;f=0}else{c[v>>2]=d-g;m=c[p+8>>2]|0;f=m;if(!m){d=v;f=1}else{c[v+4>>2]=f-d;d=c[p+12>>2]|0;if(!d){d=v;f=2}else{c[v+8>>2]=d-f;d=v;f=3}}}c[v+(f<<2)>>2]=g+h-(c[p+(f<<2)>>2]|0);QB(z);c[z+60>>2]=c[n>>2];d=c[d>>2]|0;if(!d)C=13;else{p=PB(d+23|0,8)|0;c[z>>2]=p;if(p|0)C=13}do if((C|0)==13){f=z+4|0;QB(f);c[z+64>>2]=c[o>>2];d=c[v+4>>2]|0;if(d|0){p=PB(d+23|0,8)|0;c[f>>2]=p;if(!p)break}f=z+8|0;QB(f);c[z+68>>2]=c[q>>2];d=c[v+8>>2]|0;if(d|0){q=PB(d+23|0,8)|0;c[f>>2]=q;if(!q)break}f=z+12|0;QB(f);c[z+72>>2]=c[r>>2];d=c[v+12>>2]|0;if(d|0){v=PB(d+23|0,8)|0;c[f>>2]=v;if(!v)break}c[e>>2]=c[s>>2];c[z+20>>2]=c[t>>2];c[z+24>>2]=c[u>>2];break a}while(0);f=z;d=-12;C=23;break}case 1:{h=rC(b)|0;j=b+80|0;k=(ID(c[j>>2]|0)|0)!=0;k=k?h:1;e=z+16|0;if((c[e>>2]|0)==(c[j>>2]|0))if((c[z+76>>2]|0)==(k|0))if((c[z+80>>2]|0)==(h|0))if((c[b+76>>2]|0)==(c[z+84>>2]|0))break a;QB(z);f=z+60|0;g=b+76|0;d=JD(f,h,c[g>>2]|0,c[j>>2]|0,0)|0;if((d|0)<0){f=z;C=23}else{v=PB(c[f>>2]|0,0)|0;c[z>>2]=v;if(!v){f=z;d=-12;C=23}else{c[e>>2]=c[j>>2];c[z+76>>2]=k;c[z+80>>2]=h;c[z+84>>2]=c[g>>2]}}break}default:{c[d>>2]=83198;c[d+4>>2]=83179;c[d+8>>2]=595;UC(0,0,104466,d);Ya()}}while(0);if((C|0)==23){QB(f);QB(z+4|0);QB(z+8|0);QB(z+12|0);c[e>>2]=-1;c[z+84>>2]=0;c[z+80>>2]=0;c[z+76>>2]=0;c[z+24>>2]=0;c[z+20>>2]=0;b=d;i=E;return b|0}c[b+288>>2]=1;switch(c[x>>2]|0){case 0:{e=c[(c[w>>2]|0)+24>>2]|0;if(c[b>>2]|0){UC(a,16,83200,y);b=-1;i=E;return b|0};c[b>>2]=0;c[b+4>>2]=0;c[b+8>>2]=0;c[b+12>>2]=0;c[b+16>>2]=0;c[b+20>>2]=0;c[b+24>>2]=0;c[b+28>>2]=0;c[b+64>>2]=b;d=c[e>>2]|0;do if(!d)d=0;else{c[b+32>>2]=c[e+60>>2];d=RB(d)|0;c[b+360>>2]=d;if(d|0){c[b>>2]=c[d+4>>2];d=c[e+4>>2]|0;if(!d){d=1;break}c[b+36>>2]=c[e+64>>2];d=RB(d)|0;c[b+364>>2]=d;if(d|0){c[b+4>>2]=c[d+4>>2];d=c[e+8>>2]|0;if(!d){d=2;break}c[b+40>>2]=c[e+68>>2];d=RB(d)|0;c[b+368>>2]=d;if(d|0){c[b+8>>2]=c[d+4>>2];d=c[e+12>>2]|0;if(!d){d=3;break}c[b+44>>2]=c[e+72>>2];d=RB(d)|0;c[b+372>>2]=d;if(d|0){c[b+12>>2]=c[d+4>>2];d=4;break}}}}BC(b);b=-12;i=E;return b|0}while(0);do{c[b+(d<<2)>>2]=0;c[b+32+(d<<2)>>2]=0;d=d+1|0}while((d|0)!=8);d=c[b+4>>2]|0;if(d|0)if(!(c[b+8>>2]|0))MC(d,c[a+140>>2]|0)|0;if(!(c[a+688>>2]&32768)){b=0;i=E;return b|0}c[B>>2]=b;UC(a,48,83250,B);b=0;i=E;return b|0}case 1:{j=c[(c[w>>2]|0)+24>>2]|0;f=c[j+76>>2]|0;c[b+32>>2]=c[j+60>>2];do if((f|0)>8){if(f>>>0>536870910)d=0;else d=cD(f<<2)|0;e=b+64|0;c[e>>2]=d;d=f+-8|0;c[b+396>>2]=d;if(d>>>0>536870910){d=b+392|0;c[d>>2]=0}else{C=cD(d<<2)|0;d=b+392|0;c[d>>2]=C;if(!((C|0)==0|(c[e>>2]|0)==0)){d=e;f=8;C=61;break}}bD(e);bD(d);b=-12;i=E;return b|0}else{d=b+64|0;c[d>>2]=b;if(!(c[b+396>>2]|0))if((f|0)>0){C=61;break}else break;else{c[A>>2]=83287;c[A+4>>2]=83179;c[A+8>>2]=627;UC(0,0,104466,A);Ya()}}while(0);b:do if((C|0)==61){g=0;while(1){e=RB(c[j>>2]|0)|0;c[b+360+(g<<2)>>2]=e;if(!e)break;B=c[e+4>>2]|0;c[b+(g<<2)>>2]=B;c[(c[d>>2]|0)+(g<<2)>>2]=B;g=g+1|0;if((g|0)>=(f|0)){C=63;break}}c:do if((C|0)==63){d=b+396|0;if((c[d>>2]|0)<=0)break b;e=b+392|0;f=b+64|0;h=0;while(1){g=RB(c[j>>2]|0)|0;c[(c[e>>2]|0)+(h<<2)>>2]=g;g=c[(c[e>>2]|0)+(h<<2)>>2]|0;if(!g)break c;c[(c[f>>2]|0)+(h+8<<2)>>2]=c[g+4>>2];h=h+1|0;if((h|0)>=(c[d>>2]|0))break b}}while(0);BC(b);b=-12;i=E;return b|0}while(0);if(!(c[a+688>>2]&32768)){b=0;i=E;return b|0}c[D>>2]=b;UC(a,48,83315,D);b=0;i=E;return b|0}default:{b=-1;i=E;return b|0}}return 0}function Iz(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;m=i;i=i+32|0;h=m+24|0;l=m+16|0;k=m+8|0;j=m;g=c[(c[a+64>>2]|0)+32>>2]|0;a:do if(!g){g=b+144|0;c[g>>2]=0;c[g+4>>2]=-2147483648;qC(b,-1,-1);pC(b,0,0);tC(b,-1)}else{n=g+8|0;e=c[n+4>>2]|0;d=b+144|0;c[d>>2]=c[n>>2];c[d+4>>2]=e;d=g+64|0;qC(b,c[d>>2]|0,c[d+4>>2]|0);d=c[g+48>>2]|0;pC(b,d,((d|0)<0)<<31>>31);tC(b,c[g+28>>2]|0);d=Jb(g,4,h)|0;if(!d)f=6;else{e=FC(b,5,c[h>>2]|0)|0;if(e|0){KF(c[e+4>>2]|0,d|0,c[h>>2]|0)|0;f=6}}do if((f|0)==6){e=Jb(g,5,h)|0;if(e|0){d=FC(b,6,c[h>>2]|0)|0;if(!d)break;KF(c[d+4>>2]|0,e|0,c[h>>2]|0)|0}d=Jb(g,6,h)|0;if(d|0){e=FC(b,2,c[h>>2]|0)|0;if(!e)break;KF(c[e+4>>2]|0,d|0,c[h>>2]|0)|0}d=Jb(g,7,h)|0;if(d|0){e=FC(b,10,c[h>>2]|0)|0;if(!e)break;KF(c[e+4>>2]|0,d|0,c[h>>2]|0)|0}n=yC(b)|0;g=Jb(g,72,h)|0;Lb(g,c[h>>2]|0,n)|0;break a}while(0);n=-12;i=m;return n|0}while(0);g=a+704|0;n=c[g+4>>2]|0;d=b+320|0;c[d>>2]=c[g>>2];c[d+4>>2]=n;d=b+416|0;if((c[d>>2]|0)==2)c[d>>2]=c[a+384>>2];d=b+420|0;if((c[d>>2]|0)==2)c[d>>2]=c[a+388>>2];if((uC(b)|0)==2)vC(b,c[a+392>>2]|0);if(!(wC(b)|0))xC(b,c[a+396>>2]|0);d=b+428|0;if(!(c[d>>2]|0))c[d>>2]=c[a+400>>2];switch(c[(c[a+12>>2]|0)+8>>2]|0){case 0:{c[b+80>>2]=c[a+140>>2];f=b+124|0;if(!(c[f>>2]|0)){k=a+224|0;l=c[k+4>>2]|0;n=f;c[n>>2]=c[k>>2];c[n+4>>2]=l}d=c[b+68>>2]|0;if(!d){n=0;i=m;return n|0}e=c[b+72>>2]|0;if(!e){n=0;i=m;return n|0};c[h>>2]=c[f>>2];c[h+4>>2]=c[f+4>>2];if((OC(d,e,h)|0)>=0){n=0;i=m;return n|0}n=b+128|0;l=c[n>>2]|0;c[j>>2]=c[f>>2];c[j+4>>2]=l;UC(a,24,83353,j);c[b+124>>2]=0;c[n>>2]=1;n=0;i=m;return n|0}case 1:{d=b+344|0;if(!(c[d>>2]|0))c[d>>2]=c[a+412>>2];d=b+80|0;if((c[d>>2]|0)<0)c[d>>2]=c[a+420>>2];f=b+352|0;n=f;do if((c[n>>2]|0)==0&(c[n+4>>2]|0)==0){g=a+448|0;e=g;d=c[e>>2]|0;e=c[e+4>>2]|0;if((d|0)==0&(e|0)==0){d=c[a+416>>2]|0;if(d>>>0<=64)break;c[l>>2]=d;UC(a,16,83419,l);n=-38;i=m;return n|0}d=VB(d,e)|0;if((d|0)==(c[a+416>>2]|0)){l=g;a=c[l+4>>2]|0;n=f;c[n>>2]=c[l>>2];c[n+4>>2]=a;break}UC(a,16,83382,k);n=-22;i=m;return n|0}else d=c[a+416>>2]|0;while(0);sC(b,d);n=0;i=m;return n|0}default:{n=0;i=m;return n|0}}return 0}function Jz(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+32|0;t=u+8|0;g=u;r=u+20|0;q=u+16|0;p=u+12|0;l=c[b+712>>2]|0;m=b+8|0;do if(!(c[m>>2]|0)){j=b+120|0;k=b+124|0;if((NC(c[j>>2]|0,c[k>>2]|0,0,b)|0)>=0)if((c[b+140>>2]|0)>=0){if(c[m>>2]|0){h=1;s=9;break}h=e+68|0;g=e+72|0;if((c[h>>2]|0)>=1)if((c[g>>2]|0)>=1){h=1;s=9;break}n=c[j>>2]|0;o=c[b+800>>2]|0;s=0-(0-(c[b+128>>2]|0)>>o)|0;c[h>>2]=(n|0)>(s|0)?n:s;s=c[k>>2]|0;h=0-(0-(c[b+132>>2]|0)>>o)|0;c[g>>2]=(s|0)>(h|0)?s:h;h=0;s=9;break}UC(b,16,83443,g);g=-22}else{h=1;s=9}while(0);if((s|0)==9){g=Iz(b,e)|0;if((g|0)>=0){if(!l){c[b+904>>2]=c[b+140>>2];s=14}else{g=c[l+24>>2]|0;if(!g)s=14;else{g=sb[g&7](b,e)|0;s=57}}do if((s|0)==14){g=c[b+472>>2]|0;if(!g){g=mb[c[b+484>>2]&7](b,e,f)|0;s=57;break}c[r>>2]=0;c[q>>2]=0;if(f&1|0)c[e+172>>2]=1;g=sb[g&7](b,e)|0;a:do if((g|0)>=0){b:do if(!(c[e+360>>2]|0)){g=cD(2488)|0;c[r>>2]=g;c:do if(!g)g=-12;else{KF(g|0,b|0,984)|0;KF(g+984|0,e|0,480)|0;o=IB(0,0,184,g,0)|0;c[q>>2]=o;if(!o)g=-12;else{h=e+80|0;g=c[h>>2]|0;d:do if(!(c[m>>2]|0)){j=yD(g)|0;g=AD(c[h>>2]|0)|0;if(!j){g=-22;break c}n=(a[j+7>>0]&8)==0?g:1;if((n|0)<1){g=-22;break c}g=j+6|0;h=e+72|0;m=0;while(1){if((m+-1|0)>>>0<2)f=d[g>>0]|0;else f=0;j=c[h>>2]|0;k=c[e+32+(m<<2)>>2]|0;l=MB(o)|0;c[p>>2]=l;if(!l)break;f=_(j>>f,k)|0;f=IB(c[e+(m<<2)>>2]|0,f,185,l,0)|0;c[e+360+(m<<2)>>2]=f;if(!f){s=29;break}m=m+1|0;if((m|0)>=(n|0))break d}if((s|0)==29){NB(p);BC(e)}g=-12;break c}else{e:do if(!(ID(g)|0)){g=1;s=39}else{g=c[b+416>>2]|0;if(g>>>0<=8)if(!g)break;else{s=39;break}g=g+-8|0;c[e+396>>2]=g;do if(g|0){if((2147483647/(g>>>0)|0)>>>0<=4)break;n=YC(g<<2)|0;c[e+392>>2]=n;if(!n){g=-12;break c}else{g=8;s=39;break e}}while(0);c[e+392>>2]=0;g=-12;break c}while(0);f:do if((s|0)==39){h=e+64|0;j=e+32|0;l=0;while(1){k=MB(o)|0;c[p>>2]=k;if(!k)break;n=IB(c[(c[h>>2]|0)+(l<<2)>>2]|0,c[j>>2]|0,185,k,0)|0;c[e+360+(l<<2)>>2]=n;if(!n){s=44;break}l=l+1|0;if(l>>>0>=g>>>0)break f}if((s|0)==44){NB(p);BC(e)}g=-12;break c}while(0);j=e+396|0;if((c[j>>2]|0)>0){k=e+64|0;l=e+32|0;f=e+392|0;g=MB(o)|0;c[p>>2]=g;g:do if(g|0){h=0;while(1){o=IB(c[(c[k>>2]|0)+(h+8<<2)>>2]|0,c[l>>2]|0,185,g,0)|0;c[(c[f>>2]|0)+(h<<2)>>2]=o;if(!(c[(c[f>>2]|0)+(h<<2)>>2]|0))break;h=h+1|0;if((h|0)>=(c[j>>2]|0))break d;g=MB(c[q>>2]|0)|0;c[p>>2]=g;if(!g)break g}NB(p);BC(e)}while(0);g=-12;break c}}while(0);NB(q);break b}}while(0);jb[c[b+476>>2]&255](b,e);bD(r);NB(q);break a}while(0);c[e+68>>2]=c[b+120>>2];c[e+72>>2]=c[b+124>>2];g=0}while(0)}while(0);if((s|0)==57)if(!(c[m>>2]|h)){c[e+68>>2]=c[b+120>>2];c[e+72>>2]=c[b+124>>2]}if((g|0)>=0){t=g;i=u;return t|0}}}UC(b,16,83487,t);c[e+72>>2]=0;c[e+68>>2]=0;t=g;i=u;return t|0}function Kz(a,b){a=a|0;b=b|0;var d=0,e=0;e=i;i=i+16|0;d=e;c[d>>2]=a;b=c[a+476>>2]|0;if(!b){bD(d);i=e;return}jb[b&255](a,a+984|0);bD(d);i=e;return}function Lz(a,b){a=a|0;b=b|0;var d=0;b=i;i=i+16|0;d=b;c[d>>2]=a;NB(d);i=b;return}function Mz(a,b,d,e,f,g){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0;if((f|0)<=0)return 0;if(!e){h=0;do{e=d+(_(h,g)|0)|0;sb[b&7](a,e)|0;h=h+1|0}while((h|0)!=(f|0));return 0}else{h=0;do{i=d+(_(h,g)|0)|0;c[e+(h<<2)>>2]=sb[b&7](a,i)|0;h=h+1|0}while((h|0)!=(f|0));return 0}return 0}function Nz(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0;if((f|0)<=0)return 0;if(!e){g=0;do{qb[b&1](a,d,g,0)|0;g=g+1|0}while((g|0)!=(f|0));return 0}else{g=0;do{c[e+(g<<2)>>2]=qb[b&1](a,d,g,0)|0;g=g+1|0}while((g|0)!=(f|0));return 0}return 0}function Oz(b,d){b=b|0;d=d|0;var e=0;b=c[d>>2]|0;if((b|0)==-1){e=-1;return e|0}while(1){if(!(a[(yD(b)|0)+7>>0]&8))break;d=d+4|0;b=c[d>>2]|0;if((b|0)==-1){d=-1;e=5;break}}if((e|0)==5)return d|0;e=c[d>>2]|0;return e|0}function Pz(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+48|0;r=u+32|0;q=u+24|0;p=u+16|0;g=u;s=u+44|0;f=0;while(1){e=f+1|0;if((c[d+(f<<2)>>2]|0)==-1)break;else f=e}if(!f){c[g>>2]=83508;c[g+4>>2]=83179;c[g+8>>2]=1222;UC(0,0,104466,g);Ya()}c[b+904>>2]=c[d+(f+-1<<2)>>2];if(e>>>0>=536870911){c[s>>2]=0;t=-1;i=u;return t|0}e=e<<2;f=YC(e)|0;c[s>>2]=f;if(!f){t=-1;i=u;return t|0}KF(f|0,d|0,e|0)|0;n=b+712|0;h=b+64|0;j=b+152|0;k=b+12|0;l=b+48|0;m=b+680|0;a:while(1){e=c[n>>2]|0;if(e|0){e=c[e+52>>2]|0;if(e|0)nb[e&15](b)|0}bD((c[h>>2]|0)+52|0);c[n>>2]=0;e=sb[c[j>>2]&7](b,f)|0;f=yD(e)|0;if(!f){o=-1;break}if(!(a[f+7>>0]&8)){o=e;break}if(c[(c[k>>2]|0)+16>>2]&128|0){o=e;break}g=c[f>>2]|0;d=c[l>>2]|0;b:do if(!0)t=19;else{f=0;while(1){if((c[f+8>>2]|0)==(d|0))if((c[f+12>>2]|0)==(e|0))break;f=c[f+20>>2]|0;if(!f){t=19;break b}}if(c[f+16>>2]&512|0)if((c[m>>2]|0)>-2){c[q>>2]=c[f>>2];UC(b,24,83568,q);break}g=c[f+56>>2]|0;if(g|0){d=cD(g)|0;c[(c[h>>2]|0)+52>>2]=d;if(!d)break}g=c[f+48>>2]|0;if(!g){t=29;break a}if((nb[g&15](b)|0)>=0){t=29;break a}bD((c[h>>2]|0)+52|0)}while(0);if((t|0)==19){t=0;c[p>>2]=g;UC(b,16,83515,p)}f=c[s>>2]|0;g=c[f>>2]|0;if((g|0)==(e|0))d=0;else{d=0;do{if((g|0)==-1){t=31;break a}d=d+1|0;g=c[f+(d<<2)>>2]|0}while((g|0)!=(e|0))}while(1){e=d+1|0;c[f+(d<<2)>>2]=c[f+(e<<2)>>2];f=c[s>>2]|0;if((c[f+(d<<2)>>2]|0)==-1)continue a;else d=e}}if((t|0)==29){c[n>>2]=f;o=e}else if((t|0)==31){c[r>>2]=83603;c[r+4>>2]=83179;c[r+8>>2]=1258;UC(0,0,104466,r);Ya()}bD(s);t=o;i=u;return t|0}function Qz(){return zC()|0}function Rz(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0;aa=i;i=i+816|0;Q=aa+304|0;P=aa+272|0;$=aa+256|0;U=aa+240|0;O=aa+232|0;N=aa+224|0;M=aa+216|0;L=aa+200|0;H=aa+192|0;G=aa+184|0;F=aa+176|0;E=aa+168|0;D=aa+152|0;C=aa+144|0;B=aa+136|0;A=aa+128|0;z=aa+120|0;y=aa+112|0;x=aa+104|0;u=aa+96|0;t=aa+88|0;q=aa+80|0;p=aa+72|0;r=aa+64|0;o=aa+48|0;T=aa+40|0;n=aa+32|0;m=aa+24|0;k=aa+16|0;j=aa+8|0;h=aa;Z=aa+280|0;R=aa+296|0;S=aa+288|0;c[Z>>2]=0;X=a+64|0;if(c[X>>2]|0){$=0;i=aa;return $|0}Y=a+12|0;f=c[Y>>2]|0;g=(f|0)==0;if(!b)if(g){UC(a,16,83651,h);$=-22;i=aa;return $|0}else V=f;else if(g|(f|0)==(b|0))V=b;else{$=c[b>>2]|0;c[j>>2]=c[f>>2];c[j+4>>2]=$;UC(a,16,83689,j);$=-22;i=aa;return $|0}if((c[a+100>>2]|0)>>>0>268435423){$=-22;i=aa;return $|0}W=(e|0)!=0;if(W)_B(Z,c[e>>2]|0,0);f=Sz(a,V)|0;if((f|0)<0){$=f;i=aa;return $|0}K=cD(56)|0;c[X>>2]=K;a:do if(!K)f=-12;else{K=cD(88)|0;c[(c[X>>2]|0)+24>>2]=K;b:do if(!K)f=-12;else{K=zC()|0;c[(c[X>>2]|0)+20>>2]=K;if(!K)f=-12;else{J=V+52|0;f=c[J>>2]|0;K=a+60|0;if((f|0)>0){if(!(c[K>>2]|0)){g=cD(f)|0;c[K>>2]=g;if(!g){f=-12;break a}f=V+44|0;b=c[f>>2]|0;if(b){c[g>>2]=b;kD(g)}}else f=V+44|0;if(c[f>>2]|0){f=oD(c[K>>2]|0,Z)|0;if((f|0)<0)break}}else c[K>>2]=0;f=oD(a,Z)|0;if((f|0)>=0){g=c[a+976>>2]|0;if(g|0)if((CB(c[V>>2]|0,g,44)|0)<1){c[k>>2]=c[V>>2];UC(a,16,83948,k);f=-22;break}l=a+128|0;k=c[l>>2]|0;c:do if(!k)I=33;else{g=a+132|0;b=c[g>>2]|0;if(!b)I=33;else{if(c[a+120>>2]|0)if(c[a+124>>2]|0)switch(c[a+48>>2]|0){case 93:case 28:{f=k;I=38;break c}default:{}}f=NC(k,b,0,a)|0;I=(f|0)<0;w=I?0:b;I=I?0:k;c[l>>2]=I;c[g>>2]=w;g=c[a+800>>2]|0;b=0-(0-I>>g)|0;c[a+120>>2]=b;c[a+124>>2]=0-(0-w>>g);g=I;I=36}}while(0);if((I|0)==33){g=a+120|0;b=c[g>>2]|0;if(!b){b=0;g=k;I=36}else{h=a+124|0;j=c[h>>2]|0;if(!j){g=k;I=36}else{f=NC(b,j,0,a)|0;I=(f|0)<0;v=I?0:j;I=I?0:b;c[l>>2]=I;c[a+132>>2]=v;w=c[a+800>>2]|0;b=0-(0-I>>w)|0;c[g>>2]=b;c[h>>2]=0-(0-v>>w);g=I;I=36}}}if((I|0)==36){if((f|0)<0)break;if(!g){f=a+132|0;g=c[f>>2]|0;if(!g)if(!b)if(!(c[a+124>>2]|0))m=a+120|0;else{b=f;f=0;g=0;I=42}else{b=f;f=0;g=0;I=42}else{b=f;f=0;I=42}}else{f=g;I=38}}if((I|0)==38){g=a+132|0;b=g;g=c[g>>2]|0;I=42}do if((I|0)==42){h=a+120|0;if((NC(f,g,0,a)|0)<0)f=a+124|0;else{f=a+124|0;if((NC(c[h>>2]|0,c[f>>2]|0,0,a)|0)>=0){f=c[h>>2]|0;if((f|0)<=0){m=h;break}g=c[a+124>>2]|0;if((g|0)<=0){m=h;break}b=a+224|0;c[Q>>2]=c[b>>2];c[Q+4>>2]=c[b+4>>2];if((OC(f,g,Q)|0)>=0){m=h;break}m=a+228|0;w=c[m>>2]|0;c[n>>2]=c[b>>2];c[n+4>>2]=w;UC(a,24,83353,n);c[a+224>>2]=0;c[m>>2]=1;m=h;break}}UC(a,24,83977,m);NC(0,0,0,a)|0;c[l>>2]=0;c[b>>2]=0;c[h>>2]=0;c[f>>2]=0;m=h}while(0);if(V|0)if(c[V+88>>2]|0)bD(a+860|0);v=a+416|0;if((c[v>>2]|0)>>>0>64)f=-22;else{c[Y>>2]=V;w=a+8|0;b=c[w>>2]|0;if((b|0)==-1)I=57;else if((b|0)==(c[V+8>>2]|0))I=57;else{g=a+48|0;n=g;g=c[g>>2]|0;f=b}if((I|0)==57){f=a+48|0;g=c[f>>2]|0;if(!g){s=c[V+8>>2]|0;c[w>>2]=s;g=c[V+12>>2]|0;c[f>>2]=g;n=f;f=s}else{n=f;f=b}}b=V+12|0;if((g|0)==(c[b>>2]|0))if((f|0)==4?1:(f|0)==(c[V+8>>2]|0)){c[a+428>>2]=0;s=a+916|0;c[s>>2]=Mb(g)|0;do if(c[(c[Y>>2]|0)+16>>2]&512|0){if((c[a+680>>2]|0)<=-2)break;f=V+80|0;if(!(c[f>>2]|0))if(!(c[V+84>>2]|0))h=84052;else I=66;else I=66;if((I|0)==66)h=84044;U=c[V>>2]|0;c[o>>2]=h;c[o+4>>2]=U;c[o+8>>2]=-2;UC(a,16,84060,o);d:do if(!(c[f>>2]|0)){f=c[b>>2]|0;g=c[27035]|0;if(c[V+84>>2]|0){I=70;break}e:do if((f|0)<183)switch(f|0){case 178:{b=1112557912;break e}case 171:{b=1160852272;break e}case 180:{b=809850962;break e}case 172:{b=4665933;break e}case 179:{b=1346455126;break e}case 173:{b=1464156752;break e}case 175:{b=1211250229;break e}case 182:{b=1396788813;break e}case 181:{b=1448097584;break e}default:{I=115;break e}}else if((f|0)<65566){if((f|0)<185)switch(f|0){case 184:{b=1297498929;break e}case 183:{b=1397180754;break e}default:{I=115;break e}}if((f|0)<65564){switch(f|0){case 185:break;default:{I=115;break e}}b=1297498930;break}switch(f|0){case 65564:{b=407917392;break e}case 65565:{b=542135120;break e}default:{I=115;break e}}}else{if((f|0)>=86077)switch(f|0){case 86077:{b=1330664787;break e}case 86079:{b=1950507339;break e}case 86081:{b=1346455105;break e}default:{I=115;break e}}if((f|0)<69662){switch(f|0){case 65566:break;default:{I=115;break e}}b=1347637264;break}else{switch(f|0){case 69662:break;default:{I=115;break e}}b=1447644481;break}}while(0);if((I|0)==115)b=f;f=0;do{do if(c[g+88>>2]|0){if((c[g+12>>2]|0)!=(b|0))break;if(f|0?1:(c[g+16>>2]&512|0)==0){f=g;break d}else f=g}while(0);g=c[g+56>>2]|0}while((g|0)!=0)}else{f=c[b>>2]|0;g=c[27035]|0;I=70}while(0);f:do if((I|0)==70){g:do if((f|0)<183)switch(f|0){case 178:{b=1112557912;break g}case 171:{b=1160852272;break g}case 180:{b=809850962;break g}case 172:{b=4665933;break g}case 179:{b=1346455126;break g}case 173:{b=1464156752;break g}case 175:{b=1211250229;break g}case 182:{b=1396788813;break g}case 181:{b=1448097584;break g}default:{I=89;break g}}else if((f|0)<65566){if((f|0)<185)switch(f|0){case 184:{b=1297498929;break g}case 183:{b=1397180754;break g}default:{I=89;break g}}if((f|0)<65564){switch(f|0){case 185:break;default:{I=89;break g}}b=1297498930;break}switch(f|0){case 65564:{b=407917392;break g}case 65565:{b=542135120;break g}default:{I=89;break g}}}else{if((f|0)>=86077)switch(f|0){case 86077:{b=1330664787;break g}case 86079:{b=1950507339;break g}case 86081:{b=1346455105;break g}default:{I=89;break g}}if((f|0)<69662){switch(f|0){case 65566:break;default:{I=89;break g}}b=1347637264;break}else{switch(f|0){case 69662:break;default:{I=89;break g}}b=1447644481;break}}while(0);if((I|0)==89)b=f;f=0;do{if(!(c[g+80>>2]|0)){if(c[g+84>>2]|0)I=93}else I=93;do if((I|0)==93){I=0;if((c[g+12>>2]|0)!=(b|0))break;if(f|0?1:(c[g+16>>2]&512|0)==0){f=g;break f}else f=g}while(0);g=c[g+56>>2]|0}while((g|0)!=0)}while(0);if(c[f+16>>2]&512|0){f=-733130664;break b}f=c[f>>2]|0;c[r>>2]=h;c[r+4>>2]=f;UC(a,16,84170,r);f=-733130664;break b}while(0);do if((c[w>>2]|0)==1){g=a+104|0;f=a+108|0;if(c[g>>2]|0)if(c[f>>2]|0)break;c[g>>2]=1;c[f>>2]=c[a+412>>2]}while(0);UC(a,24,84219,P);if(!(c[V+16>>2]&32768))c[a+808>>2]=1;f=c[Y>>2]|0;g=d[f+40>>0]|0;if((c[a+800>>2]|0)>>>0>g>>>0){c[p>>2]=g;UC(a,16,84286,p);f=-22;break}if(c[a+692>>2]|0){UC(a,24,84347,q);f=c[Y>>2]|0}do if(f|0){if(!(c[f+80>>2]|0))if(!(c[f+84>>2]|0))break;r=zC()|0;c[a+804>>2]=r;if(!r){f=-12;break b}h=c[Y>>2]|0;f=c[h+32>>2]|0;do if(!f)j=h;else{b=c[f>>2]|0;h:do if((b|0)==-1){g=f;b=h;f=0}else{k=a+420|0;g=f;j=h;f=0;while(1){h=c[k>>2]|0;if((h|0)==(b|0)){b=j;break h}if((c[v>>2]|0)==1){r=HD(h)|0;r=(r|0)==(HD(c[(c[(c[Y>>2]|0)+32>>2]|0)+(f<<2)>>2]|0)|0);b=c[Y>>2]|0;g=c[b+32>>2]|0;if(r)break;else h=b}else h=j;f=f+1|0;b=c[g+(f<<2)>>2]|0;if((b|0)==-1){b=h;break h}else j=h}c[k>>2]=c[g+(f<<2)>>2]}while(0);if((c[g+(f<<2)>>2]|0)!=-1){j=b;break}f=a+420|0;c[t>>2]=c[f>>2];$E(Q,128,84416,t)|0;f=FD(c[f>>2]|0)|0;c[u>>2]=f|0?f:Q;UC(a,16,84419,u);f=-22;break b}while(0);b=c[j+24>>2]|0;i:do if(b|0){f=c[b>>2]|0;j:do if((f|0)!=-1){h=c[a+140>>2]|0;g=0;while(1){if((h|0)==(f|0))break;g=g+1|0;f=c[b+(g<<2)>>2]|0;if((f|0)==-1)break j}switch(h|0){case -1:break j;case 14:case 34:case 13:case 320:case 12:break;default:break i}c[a+396>>2]=2;break i}while(0);switch(c[n>>2]|0){case 10:case 8:{if((c[a+680>>2]|0)<0)break i;break}default:{}}f=a+140|0;c[x>>2]=c[f>>2];$E(Q,128,84416,x)|0;f=wD(c[f>>2]|0)|0;c[y>>2]=f|0?f:Q;UC(a,16,84475,y);f=-22;break b}while(0);b=c[j+28>>2]|0;k:do if(b|0){f=c[b>>2]|0;l:do if(f|0){h=c[a+412>>2]|0;g=0;while(1){if((h|0)==(f|0))break;g=g+1|0;f=c[b+(g<<2)>>2]|0;if(!f)break l}if(h|0)break k}while(0);c[z>>2]=c[a+412>>2];UC(a,16,84530,z);f=-22;break b}while(0);f=c[a+412>>2]|0;if((f|0)<0){c[A>>2]=f;UC(a,16,84530,A);f=-22;break b}h=c[j+36>>2]|0;l=a+448|0;m:do if(h|0){k=l;j=c[k>>2]|0;k=c[k+4>>2]|0;if((j|0)==0&(k|0)==0){UC(a,24,84573,B);break}g=h;f=c[g>>2]|0;g=c[g+4>>2]|0;n:do if(!((f|0)==0&(g|0)==0)){b=0;while(1){if((j|0)==(f|0)&(k|0)==(g|0))break;b=b+1|0;g=h+(b<<3)|0;f=c[g>>2]|0;g=c[g+4>>2]|0;if((f|0)==0&(g|0)==0)break n}if(!((j|0)==0&(k|0)==0))break m}while(0);WB(Q,512,-1,j,k);c[C>>2]=Q;UC(a,16,84603,C);f=-22;break b}while(0);b=l;g=c[b>>2]|0;b=c[b+4>>2]|0;f=c[v>>2]|0;do if(!((g|0)==0&(b|0)==0)){g=VB(g,b)|0;if(!f){c[v>>2]=g;f=g;break}if((g|0)==(c[v>>2]|0)){f=g;break}f=l;WB(Q,512,-1,c[f>>2]|0,c[f+4>>2]|0);f=c[v>>2]|0;c[D>>2]=Q;c[D+4>>2]=g;c[D+8>>2]=f;UC(a,16,84651,D);f=-22;break b}while(0);if((f|0)<0){c[E>>2]=f;UC(a,16,84736,E);f=-22;break b}f=c[w>>2]|0;do if(!f){if((c[m>>2]|0)>=1)if((c[a+124>>2]|0)>=1){I=186;break}UC(a,16,84786,F);f=-22;break b}else if(f>>>0<2)I=186;while(0);do if((I|0)==186){f=c[a+72>>2]|0;if((f+-1|0)>>>0>=999)break;c[G>>2]=f;c[G+4>>2]=f;UC(a,24,84806,G)}while(0);f=a+564|0;if(c[f>>2]|0)break;c[f>>2]=((c[a+524>>2]|0)*3|0)/4|0}while(0);G=a+920|0;c[G>>2]=0;c[G+4>>2]=0;c[G+8>>2]=0;c[G+12>>2]=0;G=a+944|0;c[G>>2]=0;c[G+4>>2]=-2147483648;G=a+936|0;c[G>>2]=0;c[G+4>>2]=-2147483648;do if(c[a+88>>2]&8192|0){if(c[(c[s>>2]|0)+4>>2]|0)break;UC(a,24,84855,H)}while(0);f=c[Y>>2]|0;g=c[f+76>>2]|0;do if(g){if(c[a+816>>2]&1|0)if(!(c[(c[X>>2]|0)+44>>2]|0))break;f=nb[g&15](a)|0;if((f|0)<0)break b;f=c[Y>>2]|0}while(0);do if(f|0){if(!(c[f+80>>2]|0)){if(c[f+84>>2]|0)I=201}else I=201;if((I|0)==201)c[a+116>>2]=c[a+892>>2];if(!(c[f+88>>2]|0))break;g=a+72|0;if(!(c[g>>2]|0)){o:do if((c[w>>2]|0)==1){f=c[n>>2]|0;p:do switch(f|0){case 69649:{f=2;break}case 69648:{f=3;break}case 69638:case 69645:case 69632:case 69633:case 69647:{f=4;break}default:{q:do if((f|0)<65557)switch(f|0){case 65553:case 65541:case 65540:case 65542:case 65543:{I=209;break}case 65538:case 65539:case 65554:case 65536:case 65537:{I=210;break}case 65550:case 65551:case 65548:case 65549:case 65552:{I=211;break}case 65556:case 65546:case 65547:case 65544:case 65545:{I=212;break}default:{f=0;break o}}else{if((f|0)<86071){switch(f|0){case 69646:case 69660:case 69636:case 69655:case 69661:case 69644:{f=4;break p}case 65563:{I=209;break q}case 65557:{I=212;break q}case 65559:case 65558:break;default:{f=0;break o}}f=64;break p}if((f|0)<1146307640){if((f|0)<407917392)switch(f|0){case 86072:case 86071:{f=4;break p}default:{f=0;break o}}if((f|0)<542135120)switch(f|0){case 407917392:{I=211;break q}default:{f=0;break o}}if((f|0)<1146307633)switch(f|0){case 542135120:{I=212;break q}default:{f=0;break o}}else switch(f|0){case 1146307633:{I=209;break q}default:{f=0;break o}}}else{if((f|0)<1146307661)switch(f|0){case 1146307640:case 1146307660:{I=209;break q}default:{f=0;break o}}if((f|0)<1330333984)switch(f|0){case 1146307661:{I=209;break q}default:{f=0;break o}}if((f|0)<1347637264)switch(f|0){case 1330333984:{f=4;break p}default:{f=0;break o}}else switch(f|0){case 1347637264:{I=210;break q}default:{f=0;break o}}}}while(0);if((I|0)==209){f=8;break p}else if((I|0)==210){f=16;break p}else if((I|0)==211){f=24;break p}else if((I|0)==212){f=32;break p}}}while(0);f=_(_(c[a+412>>2]|0,f)|0,c[v>>2]|0)|0}else f=0;while(0);c[g>>2]=f}b=a+448|0;g=b;f=c[g>>2]|0;g=c[g+4>>2]|0;do if((f|0)==0&(g|0)==0)I=221;else{f=VB(f,g)|0;g=c[v>>2]|0;if(!g){c[v>>2]=f;break}if((f|0)==(g|0)){I=221;break}I=b;WB(Q,512,-1,c[I>>2]|0,c[I+4>>2]|0);I=c[v>>2]|0;c[L>>2]=Q;c[L+4>>2]=f;c[L+8>>2]=I;UC(a,24,84918,L);I=b;c[I>>2]=0;c[I+4>>2]=0;I=221}while(0);if((I|0)==221)f=c[v>>2]|0;if((f|0)<0|f>>>0>64){f=-22;break b}r:do if(c[a+952>>2]|0){if((c[w>>2]|0)!=3){UC(a,16,85038,M);f=-22;break b}f=c[s>>2]|0;if(c[f+16>>2]&65536|0){c[N>>2]=c[f+8>>2];UC(a,24,85098,N);c[a+956>>2]=-1;break}f=a+956|0;switch(c[f>>2]|0){case 0:{c[f>>2]=1;break}case 1:break;default:break r}UC(a,16,85172,O);f=-38;break b}while(0);f=a+896|0;if((c[f>>2]|0)<=0)break;if((c[a+900>>2]|0)<=0)break;c[S>>2]=c[a+112>>2];c[S+4>>2]=1;c[P>>2]=c[f>>2];c[P+4>>2]=c[f+4>>2];c[Q>>2]=c[S>>2];c[Q+4>>2]=c[S+4>>2];DD(R,P,Q);Q=c[R>>2]|0;S=a+104|0;c[S>>2]=c[R+4>>2];c[S+4>>2]=Q}while(0);if((c[J>>2]|0)<=0){f=0;break a}f=c[K>>2]|0;if(!f){f=0;break a}g=c[V+44>>2]|0;if(!g){f=0;break a}if((c[f>>2]|0)==(g|0)){f=0;break a}c[U>>2]=85272;c[U+4>>2]=83179;c[U+8>>2]=1747;UC(0,0,104466,U);Ya()}UC(a,16,84015,T);f=-22}}}}while(0);g=c[Y>>2]|0;if(g|0)if(c[g+100>>2]&2|0)nb[c[g+92>>2]&15](a)|0;if(c[V+44>>2]|0)if(c[V+52>>2]|0)mD(c[a+60>>2]|0);mD(a);AC(a+804|0);ZB(Z);bD(a+60|0);g=c[X>>2]|0;if(g|0){AC(g+20|0);bD((c[X>>2]|0)+24|0)}bD(X);c[Y>>2]=0}while(0);if(!(c[27034]|0)){c[$>>2]=83633;c[$+4>>2]=83179;c[$+8>>2]=3727;UC(0,0,104466,$);Ya()}c[27034]=0;c[27037]=(c[27037]|0)+-1;if(0)ba(108152,2)|0;if(!W){$=f;i=aa;return $|0}ZB(e);c[e>>2]=c[Z>>2];$=f;i=aa;return $|0}function Sz(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0;h=i;i=i+48|0;g=h+32|0;f=h+16|0;e=h+8|0;d=h;if(0)if(ba(108152,1)|0){g=-1;i=h;return g|0}j=c[27037]|0;c[27037]=j+1;if(j|0)if(!(c[b+100>>2]&1)){c[d>>2]=c[27037];UC(a,16,83765,d);if(!0)UC(a,16,83871,e);c[27034]=1;if(!(c[27034]|0)){c[f>>2]=83633;c[f+4>>2]=83179;c[f+8>>2]=3727;UC(0,0,104466,f);Ya()}c[27034]=0;c[27037]=(c[27037]|0)+-1;if(!0){j=-22;i=h;return j|0}ba(108152,2)|0;j=-22;i=h;return j|0}if(c[27034]|0){c[g>>2]=83929;c[g+4>>2]=83179;c[g+8>>2]=3720;UC(0,0,104466,g);Ya()}c[27034]=1;j=0;i=h;return j|0}function Tz(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;u=i;i=i+144|0;r=u+136|0;q=u+128|0;o=u+96|0;k=u+88|0;j=u+80|0;m=u;s=u+120|0;t=u+112|0;l=a+64|0;n=c[l>>2]|0;f=m;g=e;h=f+80|0;do{c[f>>2]=c[g>>2];f=f+4|0;g=g+4|0}while((f|0)<(h|0));h=a+12|0;f=c[h>>2]|0;if(!f){a=-22;i=u;return a|0}if(c[f+8>>2]|0){UC(a,16,85329,j);a=-22;i=u;return a|0}c[d>>2]=0;f=c[a+128>>2]|0;g=c[a+132>>2]|0;if(f|g|0)if(NC(f,g,0,a)|0){a=-22;i=u;return a|0}BC(b);if(!(c[(c[h>>2]|0)+16>>2]&32))if(!(c[e+28>>2]|0))if(!(c[a+816>>2]&1))g=0;else p=9;else p=9;else p=9;do if((p|0)==9){j=Kb(m)|0;g=Uz(a,m)|0;if((g|0)<0){UC(a,16,85529,k);if(!(c[a+696>>2]&8))p=11}else p=11;if((p|0)==11){c[(c[l>>2]|0)+32>>2]=m;g=qb[c[(c[h>>2]|0)+88>>2]&1](a,b,d,m)|0;v=e+16|0;f=c[v+4>>2]|0;k=b+152|0;c[k>>2]=c[v>>2];c[k+4>>2]=f;if(!(c[a+176>>2]|0)){v=e+64|0;qC(b,c[v>>2]|0,c[v+4>>2]|0)}if(!(c[(c[h>>2]|0)+16>>2]&2)){f=b+124|0;if(!(c[f>>2]|0)){h=a+224|0;k=c[h+4>>2]|0;v=f;c[v>>2]=c[h>>2];c[v+4>>2]=k}f=b+68|0;if(!(c[f>>2]|0))c[f>>2]=c[a+120>>2];f=b+72|0;if(!(c[f>>2]|0))c[f>>2]=c[a+124>>2];f=b+80|0;if((c[f>>2]|0)==-1)c[f>>2]=c[a+140>>2]}}c[(c[l>>2]|0)+32>>2]=0;if(j){Ib(m);if((g|0)==(c[m+28>>2]|0))g=c[e+28>>2]|0}if(!(c[d>>2]|0)){BC(b);break}if(!(c[a+488>>2]|0)){f=Vz(n,b)|0;if((f|0)<=-1){v=f;i=u;return v|0}}h=a+428|0;c[h>>2]=(c[h>>2]|0)+1;h=b+144|0;f=c[h>>2]|0;h=c[h+4>>2]|0;k=b+152|0;j=c[k>>2]|0;k=c[k+4>>2]|0;l=(j|0)==0&(k|0)==-2147483648;if(l)if((f|0)==0&(h|0)==-2147483648)e=0;else{p=a+944|0;c[p>>2]=f;c[p+4>>2]=h;p=32}else{v=a+944|0;m=v;e=c[m+4>>2]|0;d=a+928|0;n=d;n=HF(((e|0)>(k|0)|((e|0)==(k|0)?(c[m>>2]|0)>>>0>=j>>>0:0))&1|0,0,c[n>>2]|0,c[n+4>>2]|0)|0;c[d>>2]=n;c[d+4>>2]=C;c[v>>2]=j;c[v+4>>2]=k;if((f|0)==0&(h|0)==-2147483648){e=a+936|0;c[e>>2]=j;c[e+4>>2]=k;e=0}else p=32}if((p|0)==32){e=a+936|0;d=e;n=c[d+4>>2]|0;v=a+920|0;p=v;p=HF(((n|0)>(h|0)|((n|0)==(h|0)?(c[d>>2]|0)>>>0>=f>>>0:0))&1|0,0,c[p>>2]|0,c[p+4>>2]|0)|0;c[v>>2]=p;c[v+4>>2]=C;c[e>>2]=f;c[e+4>>2]=h;e=1}p=a+920|0;n=c[p+4>>2]|0;v=a+928|0;d=c[v+4>>2]|0;v=e&(l|((n|0)<(d|0)|((n|0)==(d|0)?(c[p>>2]|0)>>>0<=(c[v>>2]|0)>>>0:0)));oC(b,v?f:j,v?h:k)}while(0);v=c[b+64>>2]|0;if(!((v|0)==0|(v|0)==(b|0))){c[o>>2]=85564;c[o+4>>2]=83179;c[o+8>>2]=2493;UC(0,0,104466,o);Ya()}f=a+896|0;if((c[f>>2]|0)<=0){v=g;i=u;return v|0}if((c[a+900>>2]|0)<=0){v=g;i=u;return v|0}c[t>>2]=c[a+112>>2];c[t+4>>2]=1;c[q>>2]=c[f>>2];c[q+4>>2]=c[f+4>>2];c[r>>2]=c[t>>2];c[r+4>>2]=c[t+4>>2];DD(s,q,r);t=c[s>>2]|0;v=a+104|0;c[v>>2]=c[s+4>>2];c[v+4>>2]=t;v=g;i=u;return v|0}function Uz(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;n=i;i=i+32|0;m=n+24|0;j=n+16|0;h=n+8|0;l=n+28|0;c[l>>2]=0;g=Jb(b,2,l)|0;if(!g){a=0;i=n;return a|0}if(!(c[(c[a+12>>2]|0)+16>>2]&16384)){UC(a,16,85359,n);a=-22;i=n;return a|0}f=c[l>>2]|0;a:do if((f|0)>=4){b=g+4|0;k=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;e=f+-4|0;c[l>>2]=e;do if(k&1){if((e|0)<4)break a;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;if((b|0)>=1){c[a+416>>2]=b;e=f+-8|0;c[l>>2]=e;b=g+8|0;break}UC(a,16,85452,h);a=-1094995529;i=n;return a|0}while(0);if(k&2){if((e|0)<8)break;g=b;f=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=a+448|0;c[h>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;c[h+4>>2]=g;e=e+-8|0;c[l>>2]=e;b=b+8|0}do if(k&4){if((e|0)<4)break a;f=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;if((f|0)>=1){c[a+412>>2]=f;e=e+-4|0;c[l>>2]=e;b=b+4|0;break}UC(a,16,85474,j);a=-1094995529;i=n;return a|0}while(0);if(k&8|0){if((e|0)<8)break;g=b+4|0;h=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=a+120|0;c[j>>2]=h;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;k=a+124|0;c[k>>2]=g;c[l>>2]=e+-8;b=NC(h,g,0,a)|0;m=(b|0)<0;l=m?0:g;h=m?0:h;c[a+128>>2]=h;c[a+132>>2]=l;a=c[a+800>>2]|0;c[j>>2]=0-(0-h>>a);c[k>>2]=0-(0-l>>a);if(m){a=b;i=n;return a|0}}a=0;i=n;return a|0}while(0);UC(a,16,85494,m);a=-1094995529;i=n;return a|0}function Vz(a,b){a=a|0;b=b|0;var d=0,e=0,f=0;e=a+20|0;BC(c[e>>2]|0);HC(c[e>>2]|0,b);a=IC(b,c[e>>2]|0)|0;if((a|0)<0){b=a;return b|0}a=c[e>>2]|0;c[b>>2]=c[a>>2];c[b+4>>2]=c[a+4>>2];c[b+8>>2]=c[a+8>>2];c[b+12>>2]=c[a+12>>2];c[b+16>>2]=c[a+16>>2];c[b+20>>2]=c[a+20>>2];c[b+24>>2]=c[a+24>>2];c[b+28>>2]=c[a+28>>2];a=b+32|0;d=(c[e>>2]|0)+32|0;c[a>>2]=c[d>>2];c[a+4>>2]=c[d+4>>2];c[a+8>>2]=c[d+8>>2];c[a+12>>2]=c[d+12>>2];c[a+16>>2]=c[d+16>>2];c[a+20>>2]=c[d+20>>2];c[a+24>>2]=c[d+24>>2];c[a+28>>2]=c[d+28>>2];a=c[e>>2]|0;do if((c[a+64>>2]|0)==(a|0))c[b+64>>2]=b;else{a=(rC(a)|0)<<2;if(!a){BC(b);b=-558323010;return b|0}d=YC(a)|0;c[b+64>>2]=d;if(d|0){e=c[e>>2]|0;KF(d|0,c[e+64>>2]|0,a|0)|0;a=e;break}BC(b);b=-12;return b|0}while(0);c[b+80>>2]=c[a+80>>2];c[b+68>>2]=c[a+68>>2];c[b+72>>2]=c[a+72>>2];f=a+352|0;d=c[f+4>>2]|0;e=b+352|0;c[e>>2]=c[f>>2];c[e+4>>2]=d;c[b+76>>2]=c[a+76>>2];sC(b,rC(a)|0);b=0;return b|0}function Wz(a){a=a|0;var b=0;if(!a){a=0;return a|0}b=c[27035]|0;if(!b){a=0;return a|0}while(1){if(c[b+88>>2]|0)if(!(_D(a,c[b>>2]|0)|0)){a=6;break}b=c[b+56>>2]|0;if(!b){b=0;a=6;break}}if((a|0)==6)return b|0;return 0}function Xz(a){a=a|0;var b=0;b=c[(c[a+12>>2]|0)+96>>2]|0;if(b|0)hb[b&3](a);b=a+944|0;c[b>>2]=0;c[b+4>>2]=-2147483648;b=a+936|0;c[b>>2]=0;c[b+4>>2]=-2147483648;if(c[a+488>>2]|0)return;BC(c[(c[a+64>>2]|0)+20>>2]|0);return}function Yz(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0;g=i;i=i+16|0;f=g;c[a+4>>2]=c[b+4>>2];d=DC(c[a>>2]|0,c[b>>2]|0)|0;if((d|0)<0){a=d;i=g;return a|0}e=a+8|0;if(c[e>>2]|0){c[f>>2]=85631;c[f+4>>2]=83179;c[f+8>>2]=3774;UC(0,0,104466,f);Ya()}d=c[b+8>>2]|0;if(!d){a=0;i=g;return a|0}f=MB(d)|0;c[e>>2]=f;if(f|0){a=0;i=g;return a|0}d=c[a>>2]|0;if(!d){a=-12;i=g;return a|0}BC(d);a=-12;i=g;return a|0}function Zz(a,b){a=a|0;b=b|0;a=c[b>>2]|0;if(!a)return;BC(a);return}function _z(a,b){a=a|0;b=b|0;return Pz(a,b)|0}function $z(a,b,d){a=a|0;b=b|0;d=d|0;c[b+4>>2]=a;return Jz(a,c[b>>2]|0,d)|0}function aA(a){a=a|0;return}function bA(a,b,c){a=a|0;b=b|0;c=c|0;return}function cA(a,b,c){a=a|0;b=b|0;c=c|0;return}function dA(a){a=a|0;return 1}function eA(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0;k=i;i=i+16|0;g=k;if(b>>>0>e>>>0){c[g>>2]=85646;c[g+4>>2]=83179;c[g+8>>2]=3894;UC(0,0,104466,g);Ya()}if(b>>>0>=e>>>0){f=e;i=k;return f|0}j=c[f>>2]<<8;h=b+1|0;g=d[b>>0]|j;c[f>>2]=g;if((h|0)==(e|0)|(j|0)==256){f=h;i=k;return f|0}l=g<<8;j=b+2|0;g=d[h>>0]|l;c[f>>2]=g;if((j|0)==(e|0)|(l|0)==256){l=j;i=k;return l|0}l=g<<8;g=b+3|0;c[f>>2]=d[j>>0]|l;if((g|0)==(e|0)|(l|0)==256){l=g;i=k;return l|0}a:do if(g>>>0<e>>>0)do{l=a[g+-1>>0]|0;h=l&255;do if((l&255)>1)g=g+3|0;else if(!(a[g+-2>>0]|0)){j=g+1|0;if(!(d[g+-3>>0]|h+-1)){g=j;break a}else{g=j;break}}else{g=g+2|0;break}while(0)}while(g>>>0<e>>>0);while(0);l=g>>>0>e>>>0?e:g;e=l+-4|0;c[f>>2]=MF(d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24|0)|0;i=k;return l|0}function fA(b,c,d,e,f,g,h,i,j,k){b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0;if(!((j|0)!=0&(k|0)!=0))return;if((i|0)<(k|0)){if((i|0)<=(0-g|0)){p=1-g|0;c=c+(_(0-e|0,i)|0)+(_(p,e)|0)|0;i=p}}else{p=k+-1|0;c=c+(_(0-e|0,i)|0)+(_(p,e)|0)|0;i=p}if((h|0)<(j|0))if((h|0)>(0-f|0))l=h;else{p=1-f|0;l=p;c=c+(p-h)|0}else{p=j+-1|0;l=p;c=c+(p-h)|0}m=0-i|0;m=(m|0)<0?0:m;p=0-l|0;p=(p|0)<0?0:p;i=k-i|0;i=(i|0)<(g|0)?i:g;o=j-l|0;n=(o|0)<(f|0);o=n?o:f;j=o-p|0;h=c+(p+(_(m,e)|0))|0;c=b+p|0;if((m|0)>0){l=0;do{KF(c|0,h|0,j|0)|0;c=c+d|0;l=l+1|0}while((l|0)<(m|0));l=m}else l=0;if((l|0)<(i|0))do{KF(c|0,h|0,j|0)|0;h=h+e|0;c=c+d|0;l=l+1|0}while((l|0)<(i|0));else i=l;l=h+(0-e)|0;if((i|0)<(g|0)){k=_(g-i|0,d)|0;h=c;while(1){KF(h|0,l|0,j|0)|0;i=i+1|0;if((i|0)==(g|0))break;else h=h+d|0}c=c+k|0}l=c+(0-((_(g,d)|0)+p))|0;i=g+-1|0;if(!g)return;k=o+-1|0;if((p|0)<=0){if(!n)return;while(1){c=l+k|0;h=o;do{a[l+h>>0]=a[c>>0]|0;h=h+1|0}while((h|0)<(f|0));if(!i)break;else{l=l+d|0;i=i+-1|0}}return}if(!n){while(1){c=l+p|0;h=0;do{a[l+h>>0]=a[c>>0]|0;h=h+1|0}while((h|0)<(p|0));if(!i)break;else{l=l+d|0;i=i+-1|0}}return}while(1){c=l+p|0;h=0;do{a[l+h>>0]=a[c>>0]|0;h=h+1|0}while((h|0)<(p|0));c=l+k|0;h=o;do{a[l+h>>0]=a[c>>0]|0;h=h+1|0}while((h|0)<(f|0));if(!i)break;else{l=l+d|0;i=i+-1|0}}return}
function vm(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+16|0;t=u;s=h>>1;k=0-s|0;r=e[a+(k<<1)>>1]|0;j=e[a+(1-s<<1)>>1]|0;l=e[a+(2-s<<1)>>1]|0;m=e[a+(3-s<<1)>>1]|0;n=e[a+(4-s<<1)>>1]|0;o=e[a+(5-s<<1)>>1]|0;p=e[a+(6-s<<1)>>1]|0;h=b[a+(7-s<<1)>>1]|0;q=h&65535;if(g)h=b[a+(8-s<<1)>>1]|0;f=((e[a+(((f|0)==0?k:~s)<<1)>>1]|0)+2+(r<<1)+j|0)>>>2&65535;b[t>>1]=f;b[t+2>>1]=(r+2+(j<<1)+l|0)>>>2;b[t+4>>1]=(j+2+(l<<1)+m|0)>>>2;b[t+6>>1]=(l+2+(m<<1)+n|0)>>>2;b[t+8>>1]=(m+2+(n<<1)+o|0)>>>2;b[t+10>>1]=(n+2+(o<<1)+p|0)>>>2;b[t+12>>1]=(o+2+(p<<1)+q|0)>>>2;b[t+14>>1]=((h&65535)+2+(q<<1)+p|0)>>>2;m=s<<1;n=s*3|0;o=s<<2;p=s*5|0;j=s*6|0;k=s*7|0;l=f;q=d;h=0;while(1){f=(l&65535)+(c[q>>2]|0)|0;b[a>>1]=f;f=(c[q+32>>2]|0)+f|0;b[a+(s<<1)>>1]=f;f=(c[q+64>>2]|0)+f|0;b[a+(m<<1)>>1]=f;f=(c[q+96>>2]|0)+f|0;b[a+(n<<1)>>1]=f;f=(c[q+128>>2]|0)+f|0;b[a+(o<<1)>>1]=f;f=(c[q+160>>2]|0)+f|0;b[a+(p<<1)>>1]=f;f=(c[q+192>>2]|0)+f|0;b[a+(j<<1)>>1]=f;b[a+(k<<1)>>1]=(c[q+224>>2]|0)+f;h=h+1|0;if((h|0)==8)break;l=b[t+(h<<1)>>1]|0;q=q+4|0;a=a+2|0}FF(d|0,0,256)|0;i=u;return}function wm(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;m=i;i=i+16|0;l=m;k=h>>1;s=a+-2|0;r=e[s>>1]|0;q=e[a+(k+-1<<1)>>1]|0;p=e[a+((k<<1)+-1<<1)>>1]|0;o=e[a+((k*3|0)+-1<<1)>>1]|0;n=e[a+((k<<2)+-1<<1)>>1]|0;h=e[a+((k*5|0)+-1<<1)>>1]|0;g=e[a+((k*6|0)+-1<<1)>>1]|0;j=e[a+((k*7|0)+-1<<1)>>1]|0;f=((e[((f|0)==0?s:a+(~k<<1)|0)>>1]|0)+2+(r<<1)+q|0)>>>2&65535;b[l>>1]=f;b[l+2>>1]=(r+2+(q<<1)+p|0)>>>2;b[l+4>>1]=(q+2+(p<<1)+o|0)>>>2;b[l+6>>1]=(p+2+(o<<1)+n|0)>>>2;b[l+8>>1]=(o+2+(n<<1)+h|0)>>>2;b[l+10>>1]=(n+2+(h<<1)+g|0)>>>2;b[l+12>>1]=(h+2+(g<<1)+j|0)>>>2;b[l+14>>1]=(g+2+(j*3|0)|0)>>>2;j=d;g=0;h=a;while(1){s=(f&65535)+(c[j>>2]|0)|0;b[h>>1]=s;s=(c[j+4>>2]|0)+s|0;b[h+2>>1]=s;s=(c[j+8>>2]|0)+s|0;b[h+4>>1]=s;s=(c[j+12>>2]|0)+s|0;b[h+6>>1]=s;s=(c[j+16>>2]|0)+s|0;b[h+8>>1]=s;s=(c[j+20>>2]|0)+s|0;b[h+10>>1]=s;s=(c[j+24>>2]|0)+s|0;b[h+12>>1]=s;b[h+14>>1]=(c[j+28>>2]|0)+s;g=g+1|0;if((g|0)==8)break;f=b[l+(g<<1)>>1]|0;j=j+32|0;h=h+(k<<1)|0}FF(d|0,0,256)|0;i=m;return}function xm(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;rm(a+(c[b>>2]|0)|0,d,e);rm(a+(c[b+4>>2]|0)|0,d+64|0,e);rm(a+(c[b+8>>2]|0)|0,d+128|0,e);rm(a+(c[b+12>>2]|0)|0,d+192|0,e);return}function ym(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;rm(a+(c[b>>2]|0)|0,d,e);rm(a+(c[b+4>>2]|0)|0,d+64|0,e);rm(a+(c[b+8>>2]|0)|0,d+128|0,e);rm(a+(c[b+12>>2]|0)|0,d+192|0,e);rm(a+(c[b+32>>2]|0)|0,d+256|0,e);rm(a+(c[b+36>>2]|0)|0,d+320|0,e);rm(a+(c[b+40>>2]|0)|0,d+384|0,e);rm(a+(c[b+44>>2]|0)|0,d+448|0,e);return}function zm(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=4);return}function Am(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;h=g>>1;g=0;do{j=a+(c[d+(g<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=4);g=4;do{j=a+(c[d+(g+4<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=8);return}function Bm(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;rm(a+(c[b>>2]|0)|0,d,e);rm(a+(c[b+4>>2]|0)|0,d+64|0,e);rm(a+(c[b+8>>2]|0)|0,d+128|0,e);rm(a+(c[b+12>>2]|0)|0,d+192|0,e);rm(a+(c[b+16>>2]|0)|0,d+256|0,e);rm(a+(c[b+20>>2]|0)|0,d+320|0,e);rm(a+(c[b+24>>2]|0)|0,d+384|0,e);rm(a+(c[b+28>>2]|0)|0,d+448|0,e);rm(a+(c[b+32>>2]|0)|0,d+512|0,e);rm(a+(c[b+36>>2]|0)|0,d+576|0,e);rm(a+(c[b+40>>2]|0)|0,d+640|0,e);rm(a+(c[b+44>>2]|0)|0,d+704|0,e);rm(a+(c[b+48>>2]|0)|0,d+768|0,e);rm(a+(c[b+52>>2]|0)|0,d+832|0,e);rm(a+(c[b+56>>2]|0)|0,d+896|0,e);rm(a+(c[b+60>>2]|0)|0,d+960|0,e);return}function Cm(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=16);return}function Dm(a,b,d){a=a|0;b=b|0;d=d|0;b=c[a+(0-d)>>2]|0;c[a>>2]=b;c[a+d>>2]=b;c[a+(d<<1)>>2]=b;c[a+(d*3|0)>>2]=b;return}function Em(a,b,e){a=a|0;b=b|0;e=e|0;c[a>>2]=_(d[a+-1>>0]|0,16843009)|0;c[a+e>>2]=_(d[a+(e+-1)>>0]|0,16843009)|0;b=e<<1;c[a+b>>2]=_(d[a+(b+-1)>>0]|0,16843009)|0;b=e*3|0;c[a+b>>2]=_(d[a+(b+-1)>>0]|0,16843009)|0;return}function Fm(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0;g=e<<1;b=e*3|0;f=_((d[a+(0-e)>>0]|0)+4+(d[a+(1-e)>>0]|0)+(d[a+(2-e)>>0]|0)+(d[a+(3-e)>>0]|0)+(d[a+-1>>0]|0)+(d[a+(e+-1)>>0]|0)+(d[a+(g+-1)>>0]|0)+(d[a+(b+-1)>>0]|0)>>3,16843009)|0;c[a>>2]=f;c[a+e>>2]=f;c[a+g>>2]=f;c[a+b>>2]=f;return}function Gm(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=d[b+(1-e)>>0]|0;h=d[b+(2-e)>>0]|0;k=d[b+(3-e)>>0]|0;j=d[c>>0]|0;i=d[c+1>>0]|0;g=d[c+2>>0]|0;f=d[c+3>>0]|0;a[b>>0]=((d[b+(0-e)>>0]|0)+2+h+(l<<1)|0)>>>2;c=(l+2+k+(h<<1)|0)>>>2&255;a[b+e>>0]=c;a[b+1>>0]=c;c=(h+2+j+(k<<1)|0)>>>2&255;h=e<<1;a[b+h>>0]=c;a[b+(e+1)>>0]=c;a[b+2>>0]=c;k=(k+2+i+(j<<1)|0)>>>2&255;c=e*3|0;a[b+c>>0]=k;a[b+(h|1)>>0]=k;a[b+(e+2)>>0]=k;a[b+3>>0]=k;j=(j+2+g+(i<<1)|0)>>>2&255;a[b+(c+1)>>0]=j;a[b+(h+2)>>0]=j;a[b+(e+3)>>0]=j;e=(i+2+f+(g<<1)|0)>>>2&255;a[b+(c+2)>>0]=e;a[b+(h+3)>>0]=e;a[b+(c+3)>>0]=(g+2+(f*3|0)|0)>>>2;return}function Hm(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;m=d[b+~e>>0]|0;h=d[b+(0-e)>>0]|0;g=d[b+(1-e)>>0]|0;f=d[b+(2-e)>>0]|0;c=d[b+(3-e)>>0]|0;k=d[b+-1>>0]|0;n=d[b+(e+-1)>>0]|0;j=e<<1;i=d[b+(j+-1)>>0]|0;l=e*3|0;a[b+l>>0]=(n+2+(i<<1)+(d[b+(l+-1)>>0]|0)|0)>>>2;i=(k+2+(n<<1)+i|0)>>>2&255;a[b+(l+1)>>0]=i;a[b+j>>0]=i;i=m+2|0;n=(i+(k<<1)+n|0)>>>2&255;a[b+(l+2)>>0]=n;a[b+(j|1)>>0]=n;a[b+e>>0]=n;k=((m<<1)+2+h+k|0)>>>2&255;a[b+(l+3)>>0]=k;a[b+(j+2)>>0]=k;a[b+(e+1)>>0]=k;a[b>>0]=k;i=(i+(h<<1)+g|0)>>>2&255;a[b+(j+3)>>0]=i;a[b+(e+2)>>0]=i;a[b+1>>0]=i;h=(h+2+(g<<1)+f|0)>>>2&255;a[b+(e+3)>>0]=h;a[b+2>>0]=h;a[b+3>>0]=(g+2+(f<<1)+c|0)>>>2;return}function Im(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d[b+~e>>0]|0;n=d[b+(0-e)>>0]|0;m=d[b+(1-e)>>0]|0;l=d[b+(2-e)>>0]|0;k=d[b+(3-e)>>0]|0;h=d[b+-1>>0]|0;g=d[b+(e+-1)>>0]|0;i=e<<1;f=d[b+(i+-1)>>0]|0;c=e*3|0;o=(j+1+n|0)>>>1&255;a[b+(i|1)>>0]=o;a[b>>0]=o;o=(n+1+m|0)>>>1&255;a[b+(i+2)>>0]=o;a[b+1>>0]=o;o=(m+1+l|0)>>>1&255;a[b+(i+3)>>0]=o;a[b+2>>0]=o;a[b+3>>0]=(l+1+k|0)>>>1;o=((j<<1)+2+n+h|0)>>>2&255;a[b+(c+1)>>0]=o;a[b+e>>0]=o;j=j+2|0;o=(j+(n<<1)+m|0)>>>2&255;a[b+(c+2)>>0]=o;a[b+(e+1)>>0]=o;n=(n+2+(m<<1)+l|0)>>>2&255;a[b+(c+3)>>0]=n;a[b+(e+2)>>0]=n;a[b+(e+3)>>0]=(m+2+(l<<1)+k|0)>>>2;a[b+i>>0]=(j+(h<<1)+g|0)>>>2;a[b+c>>0]=(h+2+(g<<1)+f|0)>>>2;return}function Jm(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;k=d[b+~e>>0]|0;n=d[b+(0-e)>>0]|0;m=d[b+(1-e)>>0]|0;l=d[b+(2-e)>>0]|0;j=d[b+-1>>0]|0;h=d[b+(e+-1)>>0]|0;i=e<<1;g=d[b+(i+-1)>>0]|0;c=e*3|0;f=d[b+(c+-1)>>0]|0;o=(k+1+j|0)>>>1&255;a[b+(e+2)>>0]=o;a[b>>0]=o;o=((k<<1)+2+n+j|0)>>>2&255;a[b+(e+3)>>0]=o;a[b+1>>0]=o;k=k+2|0;a[b+2>>0]=(k+(n<<1)+m|0)>>>2;a[b+3>>0]=(n+2+(m<<1)+l|0)>>>2;l=(j+1+h|0)>>>1&255;a[b+(i+2)>>0]=l;a[b+e>>0]=l;k=(k+(j<<1)+h|0)>>>2&255;a[b+(i+3)>>0]=k;a[b+(e+1)>>0]=k;e=(h+1+g|0)>>>1&255;a[b+(c+2)>>0]=e;a[b+i>>0]=e;e=(j+2+(h<<1)+g|0)>>>2&255;a[b+(c+3)>>0]=e;a[b+(i|1)>>0]=e;a[b+c>>0]=(g+1+f|0)>>>1;a[b+(c+1)>>0]=(h+2+(g<<1)+f|0)>>>2;return}function Km(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=d[b+(0-e)>>0]|0;k=d[b+(1-e)>>0]|0;j=d[b+(2-e)>>0]|0;i=d[b+(3-e)>>0]|0;h=d[c>>0]|0;g=d[c+1>>0]|0;f=d[c+2>>0]|0;a[b>>0]=(l+1+k|0)>>>1;m=(k+1+j|0)>>>1&255;c=e<<1;a[b+c>>0]=m;a[b+1>>0]=m;m=(j+1+i|0)>>>1&255;a[b+(c|1)>>0]=m;a[b+2>>0]=m;m=(i+1+h|0)>>>1&255;a[b+(c+2)>>0]=m;a[b+3>>0]=m;a[b+(c+3)>>0]=(h+1+g|0)>>>1;a[b+e>>0]=(l+2+(k<<1)+j|0)>>>2;k=(k+2+(j<<1)+i|0)>>>2&255;c=e*3|0;a[b+c>>0]=k;a[b+(e+1)>>0]=k;j=(j+2+(i<<1)+h|0)>>>2&255;a[b+(c+1)>>0]=j;a[b+(e+2)>>0]=j;i=(i+2+(h<<1)+g|0)>>>2&255;a[b+(c+2)>>0]=i;a[b+(e+3)>>0]=i;a[b+(c+3)>>0]=(h+2+(g<<1)+f|0)>>>2;return}function Lm(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;k=d[b+-1>>0]|0;j=d[b+(e+-1)>>0]|0;c=e<<1;i=d[b+(c+-1)>>0]|0;g=e*3|0;f=a[b+(g+-1)>>0]|0;h=f&255;a[b>>0]=(k+1+j|0)>>>1;a[b+1>>0]=(k+2+(j<<1)+i|0)>>>2;k=(j+1+i|0)>>>1&255;a[b+e>>0]=k;a[b+2>>0]=k;j=(j+2+(i<<1)+h|0)>>>2&255;a[b+(e+1)>>0]=j;a[b+3>>0]=j;j=(i+1+h|0)>>>1&255;a[b+c>>0]=j;a[b+(e+2)>>0]=j;h=(i+2+h+(h<<1)|0)>>>2&255;a[b+(c|1)>>0]=h;a[b+(e+3)>>0]=h;a[b+(g+3)>>0]=f;a[b+(g+2)>>0]=f;a[b+(c+2)>>0]=f;a[b+g>>0]=f;a[b+(g+1)>>0]=f;a[b+(c+3)>>0]=f;return}function Mm(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0;g=e<<1;b=e*3|0;f=_(((d[a+-1>>0]|0)+2+(d[a+(e+-1)>>0]|0)+(d[a+(g+-1)>>0]|0)+(d[a+(b+-1)>>0]|0)|0)>>>2,16843009)|0;c[a>>2]=f;c[a+e>>2]=f;c[a+g>>2]=f;c[a+b>>2]=f;return}function Nm(a,b,e){a=a|0;b=b|0;e=e|0;b=_(((d[a+(0-e)>>0]|0)+2+(d[a+(1-e)>>0]|0)+(d[a+(2-e)>>0]|0)+(d[a+(3-e)>>0]|0)|0)>>>2,16843009)|0;c[a>>2]=b;c[a+e>>2]=b;c[a+(e<<1)>>2]=b;c[a+(e*3|0)>>2]=b;return}function Om(a,b,d){a=a|0;b=b|0;d=d|0;c[a>>2]=2139062143;c[a+d>>2]=2139062143;c[a+(d<<1)>>2]=2139062143;c[a+(d*3|0)>>2]=2139062143;return}function Pm(a,b,d){a=a|0;b=b|0;d=d|0;c[a>>2]=-2122219135;c[a+d>>2]=-2122219135;c[a+(d<<1)>>2]=-2122219135;c[a+(d*3|0)>>2]=-2122219135;return}function Qm(a,b,d){a=a|0;b=b|0;d=d|0;c[a>>2]=-2139062144;c[a+d>>2]=-2139062144;c[a+(d<<1)>>2]=-2139062144;c[a+(d*3|0)>>2]=-2139062144;return}function Rm(b,e,f,g){b=b|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;i=0-g|0;p=d[b+i>>0]|0;j=d[b+(1-g)>>0]|0;k=d[b+(2-g)>>0]|0;l=d[b+(3-g)>>0]|0;m=d[b+(4-g)>>0]|0;n=d[b+(5-g)>>0]|0;o=d[b+(6-g)>>0]|0;h=a[b+(7-g)>>0]|0;q=h&255;if(f)h=a[b+(8-g)>>0]|0;a[b>>0]=((d[b+((e|0)==0?i:~g)>>0]|0)+2+(p<<1)+j|0)>>>2;a[b+1>>0]=(p+2+(j<<1)+k|0)>>>2;a[b+2>>0]=(j+2+(k<<1)+l|0)>>>2;a[b+3>>0]=(k+2+(l<<1)+m|0)>>>2;e=b+4|0;a[e>>0]=(l+2+(m<<1)+n|0)>>>2;a[b+5>>0]=(m+2+(n<<1)+o|0)>>>2;a[b+6>>0]=(n+2+(o<<1)+q|0)>>>2;a[b+7>>0]=((h&255)+2+(q<<1)+o|0)>>>2;p=c[b>>2]|0;q=c[e>>2]|0;e=b+g|0;c[e>>2]=p;c[e+4>>2]=q;e=b+(g<<1)|0;c[e>>2]=p;c[e+4>>2]=q;e=b+(g*3|0)|0;c[e>>2]=p;c[e+4>>2]=q;e=b+(g<<2)|0;c[e>>2]=p;c[e+4>>2]=q;e=b+(g*5|0)|0;c[e>>2]=p;c[e+4>>2]=q;e=b+(g*6|0)|0;c[e>>2]=p;c[e+4>>2]=q;e=b+(g*7|0)|0;c[e>>2]=p;c[e+4>>2]=q;return}function Sm(a,b,e,f){a=a|0;b=b|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=a+-1|0;s=d[t>>0]|0;r=d[a+(f+-1)>>0]|0;q=f<<1;p=d[a+(q+-1)>>0]|0;o=f*3|0;n=d[a+(o+-1)>>0]|0;m=f<<2;l=d[a+(m+-1)>>0]|0;k=f*5|0;j=d[a+(k+-1)>>0]|0;i=f*6|0;h=d[a+(i+-1)>>0]|0;e=f*7|0;g=d[a+(e+-1)>>0]|0;b=_(((d[((b|0)==0?t:a+~f|0)>>0]|0)+2+(s<<1)+r|0)>>>2,16843009)|0;c[a>>2]=b;c[a+4>>2]=b;b=_((s+2+(r<<1)+p|0)>>>2,16843009)|0;f=a+f|0;c[f>>2]=b;c[f+4>>2]=b;f=_((r+2+(p<<1)+n|0)>>>2,16843009)|0;b=a+q|0;c[b>>2]=f;c[b+4>>2]=f;b=_((p+2+(n<<1)+l|0)>>>2,16843009)|0;f=a+o|0;c[f>>2]=b;c[f+4>>2]=b;f=_((n+2+(l<<1)+j|0)>>>2,16843009)|0;b=a+m|0;c[b>>2]=f;c[b+4>>2]=f;b=_((l+2+(j<<1)+h|0)>>>2,16843009)|0;f=a+k|0;c[f>>2]=b;c[f+4>>2]=b;f=_((j+2+(h<<1)+g|0)>>>2,16843009)|0;b=a+i|0;c[b>>2]=f;c[b+4>>2]=f;b=_((h+2+(g*3|0)|0)>>>2,16843009)|0;e=a+e|0;c[e>>2]=b;c[e+4>>2]=b;return}function Tm(b,e,f,g){b=b|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;h=(e|0)!=0;i=~g;q=b+-1|0;u=d[q>>0]|0;k=d[b+(g+-1)>>0]|0;p=d[b+((g<<1)+-1)>>0]|0;r=d[b+((g*3|0)+-1)>>0]|0;s=d[b+((g<<2)+-1)>>0]|0;t=d[b+((g*5|0)+-1)>>0]|0;v=d[b+((g*6|0)+-1)>>0]|0;w=d[b+((g*7|0)+-1)>>0]|0;x=0-g|0;y=d[b+x>>0]|0;z=d[b+(1-g)>>0]|0;A=d[b+(2-g)>>0]|0;j=d[b+(3-g)>>0]|0;l=d[b+(4-g)>>0]|0;m=d[b+(5-g)>>0]|0;n=d[b+(6-g)>>0]|0;e=a[b+(7-g)>>0]|0;o=e&255;if(f)e=a[b+(8-g)>>0]|0;f=_((((d[(h?b+i|0:q)>>0]|0)+2+(u<<1)+k|0)>>>2)+8+((u+2+(k<<1)+p|0)>>>2)+((k+2+(p<<1)+r|0)>>>2)+((p+2+(r<<1)+s|0)>>>2)+((r+2+(s<<1)+t|0)>>>2)+((s+2+(t<<1)+v|0)>>>2)+((t+2+(v<<1)+w|0)>>>2)+((v+2+(w*3|0)|0)>>>2)+(((d[b+(h?i:x)>>0]|0)+2+(y<<1)+z|0)>>>2)+((y+2+(z<<1)+A|0)>>>2)+((z+2+(A<<1)+j|0)>>>2)+((A+2+(j<<1)+l|0)>>>2)+((j+2+(l<<1)+m|0)>>>2)+((l+2+(m<<1)+n|0)>>>2)+((m+2+(n<<1)+o|0)>>>2)+(((e&255)+2+(o<<1)+n|0)>>>2)>>4,16843009)|0;c[b>>2]=f;c[b+4>>2]=f;b=b+g|0;c[b>>2]=f;c[b+4>>2]=f;b=b+g|0;c[b>>2]=f;c[b+4>>2]=f;b=b+g|0;c[b>>2]=f;c[b+4>>2]=f;b=b+g|0;c[b>>2]=f;c[b+4>>2]=f;b=b+g|0;c[b>>2]=f;c[b+4>>2]=f;b=b+g|0;c[b>>2]=f;c[b+4>>2]=f;g=b+g|0;c[g>>2]=f;c[g+4>>2]=f;return}function Um(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;p=0-f|0;w=d[b+p>>0]|0;q=d[b+(1-f)>>0]|0;t=d[b+(2-f)>>0]|0;r=(w+2+(q<<1)+t|0)>>>2;u=d[b+(3-f)>>0]|0;s=(q+2+(t<<1)+u|0)>>>2;x=d[b+(4-f)>>0]|0;t=(t+2+(u<<1)+x|0)>>>2;y=d[b+(5-f)>>0]|0;u=(u+2+(x<<1)+y|0)>>>2;v=d[b+(6-f)>>0]|0;x=(x+2+(y<<1)+v|0)>>>2;g=a[b+(7-f)>>0]|0;o=g&255;y=(y+2+(v<<1)+o|0)>>>2;if(!e){e=(g&255)+2+(o<<1)|0;h=o;i=o;j=o;k=o;l=o;m=o;n=o;g=o}else{A=d[b+(8-f)>>0]|0;z=d[b+(9-f)>>0]|0;g=d[b+(10-f)>>0]|0;j=d[b+(11-f)>>0]|0;k=d[b+(12-f)>>0]|0;l=d[b+(13-f)>>0]|0;n=d[b+(14-f)>>0]|0;m=d[b+(15-f)>>0]|0;e=(d[b+(8-f)>>0]|0)+2+(o<<1)|0;h=(z+2+(g<<1)+j|0)>>>2;i=(g+2+(j<<1)+k|0)>>>2;j=(j+2+(k<<1)+l|0)>>>2;k=(k+2+(l<<1)+n|0)>>>2;l=(l+2+(n<<1)+m|0)>>>2;m=(n+2+(m*3|0)|0)>>>2;n=(o+2+(A<<1)+z|0)>>>2;g=(A+2+(z<<1)+g|0)>>>2}z=(e+v|0)>>>2;a[b>>0]=((((d[b+((c|0)==0?p:~f)>>0]|0)+2+(w<<1)+q|0)>>>2)+2+(r<<1)+s|0)>>>2;A=(r+2+(s<<1)+t|0)>>>2&255;a[b+1>>0]=A;a[b+f>>0]=A;A=(s+2+(t<<1)+u|0)>>>2&255;a[b+2>>0]=A;a[b+(f+1)>>0]=A;s=f<<1;a[b+s>>0]=A;A=(t+2+(u<<1)+x|0)>>>2&255;a[b+3>>0]=A;a[b+(f+2)>>0]=A;a[b+(s|1)>>0]=A;v=f*3|0;a[b+v>>0]=A;A=(u+2+(x<<1)+y|0)>>>2&255;a[b+4>>0]=A;a[b+(f+3)>>0]=A;a[b+(s+2)>>0]=A;a[b+(v+1)>>0]=A;w=f<<2;a[b+w>>0]=A;A=(x+2+(y<<1)+z|0)>>>2&255;a[b+5>>0]=A;a[b+(f+4)>>0]=A;a[b+(s+3)>>0]=A;a[b+(v+2)>>0]=A;a[b+(w|1)>>0]=A;x=f*5|0;a[b+x>>0]=A;A=(y+2+(z<<1)+n|0)>>>2&255;a[b+6>>0]=A;a[b+(f+5)>>0]=A;a[b+(s+4)>>0]=A;a[b+(v+3)>>0]=A;a[b+(w|2)>>0]=A;a[b+(x+1)>>0]=A;c=f*6|0;a[b+c>>0]=A;z=(z+2+(n<<1)+g|0)>>>2&255;a[b+7>>0]=z;a[b+(f+6)>>0]=z;a[b+(s+5)>>0]=z;a[b+(v+4)>>0]=z;a[b+(w|3)>>0]=z;a[b+(x+2)>>0]=z;a[b+(c|1)>>0]=z;A=f*7|0;a[b+A>>0]=z;z=(n+2+(g<<1)+h|0)>>>2&255;a[b+(f+7)>>0]=z;a[b+(s+6)>>0]=z;a[b+(v+5)>>0]=z;a[b+(w+4)>>0]=z;a[b+(x+3)>>0]=z;a[b+(c+2)>>0]=z;a[b+(A+1)>>0]=z;z=(g+2+(h<<1)+i|0)>>>2&255;a[b+(s+7)>>0]=z;a[b+(v+6)>>0]=z;a[b+(w+5)>>0]=z;a[b+(x+4)>>0]=z;a[b+(c+3)>>0]=z;a[b+(A+2)>>0]=z;z=(h+2+(i<<1)+j|0)>>>2&255;a[b+(v+7)>>0]=z;a[b+(w+6)>>0]=z;a[b+(x+5)>>0]=z;a[b+(c+4)>>0]=z;a[b+(A+3)>>0]=z;z=(i+2+(j<<1)+k|0)>>>2&255;a[b+(w+7)>>0]=z;a[b+(x+6)>>0]=z;a[b+(c+5)>>0]=z;a[b+(A+4)>>0]=z;z=(j+2+(k<<1)+l|0)>>>2&255;a[b+(x+7)>>0]=z;a[b+(c+6)>>0]=z;a[b+(A+5)>>0]=z;z=(k+2+(l<<1)+m|0)>>>2&255;a[b+(c+7)>>0]=z;a[b+(A+6)>>0]=z;a[b+(A+7)>>0]=(l+2+(m*3|0)|0)>>>2;return}function Vm(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;g=(c|0)!=0;h=0-f|0;i=~f;o=d[b+h>>0]|0;k=d[b+(1-f)>>0]|0;h=((d[b+(g?i:h)>>0]|0)+2+(o<<1)+k|0)>>>2;l=d[b+(2-f)>>0]|0;j=(o+2+(k<<1)+l|0)>>>2;m=d[b+(3-f)>>0]|0;k=(k+2+(l<<1)+m|0)>>>2;p=d[b+(4-f)>>0]|0;l=(l+2+(m<<1)+p|0)>>>2;r=d[b+(5-f)>>0]|0;m=(m+2+(p<<1)+r|0)>>>2;n=d[b+(6-f)>>0]|0;p=(p+2+(r<<1)+n|0)>>>2;c=a[b+(7-f)>>0]|0;q=c&255;r=(r+2+(n<<1)+q|0)>>>2;if(e)c=a[b+(8-f)>>0]|0;E=b+i|0;v=b+-1|0;F=d[v>>0]|0;x=d[b+(f+-1)>>0]|0;v=((d[(g?E:v)>>0]|0)+2+(F<<1)+x|0)>>>2;i=f<<1;y=d[b+(i+-1)>>0]|0;F=F+2|0;e=(F+(x<<1)+y|0)>>>2;g=f*3|0;z=d[b+(g+-1)>>0]|0;x=(x+2+(y<<1)+z|0)>>>2;s=f<<2;A=d[b+(s+-1)>>0]|0;y=(y+2+(z<<1)+A|0)>>>2;t=f*5|0;B=d[b+(t+-1)>>0]|0;z=(z+2+(A<<1)+B|0)>>>2;u=f*6|0;D=d[b+(u+-1)>>0]|0;A=(A+2+(B<<1)+D|0)>>>2;w=f*7|0;C=d[b+(w+-1)>>0]|0;B=(B+2+(D<<1)+C|0)>>>2;o=(F+((d[E>>0]|0)<<1)+o|0)>>>2;a[b+w>>0]=(A+2+((D+2+(C*3|0)|0)>>>2)+(B<<1)|0)>>>2;B=(z+2+(A<<1)+B|0)>>>2&255;a[b+(w+1)>>0]=B;a[b+u>>0]=B;A=(y+2+(z<<1)+A|0)>>>2&255;a[b+(w+2)>>0]=A;a[b+(u|1)>>0]=A;a[b+t>>0]=A;z=(x+2+(y<<1)+z|0)>>>2&255;a[b+(w+3)>>0]=z;a[b+(u+2)>>0]=z;a[b+(t+1)>>0]=z;a[b+s>>0]=z;y=(e+2+(x<<1)+y|0)>>>2&255;a[b+(w+4)>>0]=y;a[b+(u+3)>>0]=y;a[b+(t+2)>>0]=y;a[b+(s|1)>>0]=y;a[b+g>>0]=y;x=(v+2+(e<<1)+x|0)>>>2&255;a[b+(w+5)>>0]=x;a[b+(u+4)>>0]=x;a[b+(t+3)>>0]=x;a[b+(s|2)>>0]=x;a[b+(g+1)>>0]=x;a[b+i>>0]=x;e=((v<<1)+2+e+o|0)>>>2&255;a[b+(w+6)>>0]=e;a[b+(u+5)>>0]=e;a[b+(t+4)>>0]=e;a[b+(s|3)>>0]=e;a[b+(g+2)>>0]=e;a[b+(i|1)>>0]=e;a[b+f>>0]=e;e=h+2|0;v=(e+v+(o<<1)|0)>>>2&255;a[b+(w+7)>>0]=v;a[b+(u+6)>>0]=v;a[b+(t+5)>>0]=v;a[b+(s+4)>>0]=v;a[b+(g+3)>>0]=v;a[b+(i+2)>>0]=v;a[b+(f+1)>>0]=v;a[b>>0]=v;o=((h<<1)+2+j+o|0)>>>2&255;a[b+(u+7)>>0]=o;a[b+(t+6)>>0]=o;a[b+(s+5)>>0]=o;a[b+(g+4)>>0]=o;a[b+(i+3)>>0]=o;a[b+(f+2)>>0]=o;a[b+1>>0]=o;e=(e+(j<<1)+k|0)>>>2&255;a[b+(t+7)>>0]=e;a[b+(s+6)>>0]=e;a[b+(g+5)>>0]=e;a[b+(i+4)>>0]=e;a[b+(f+3)>>0]=e;a[b+2>>0]=e;e=(j+2+(k<<1)+l|0)>>>2&255;a[b+(s+7)>>0]=e;a[b+(g+6)>>0]=e;a[b+(i+5)>>0]=e;a[b+(f+4)>>0]=e;a[b+3>>0]=e;e=(k+2+(l<<1)+m|0)>>>2&255;a[b+(g+7)>>0]=e;a[b+(i+6)>>0]=e;a[b+(f+5)>>0]=e;a[b+4>>0]=e;e=(l+2+(m<<1)+p|0)>>>2&255;a[b+(i+7)>>0]=e;a[b+(f+6)>>0]=e;a[b+5>>0]=e;e=(m+2+(p<<1)+r|0)>>>2&255;a[b+(f+7)>>0]=e;a[b+6>>0]=e;a[b+7>>0]=(p+2+(r<<1)+(((c&255)+2+(q<<1)+n|0)>>>2)|0)>>>2;return}function Wm(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;g=(c|0)!=0;h=0-f|0;i=~f;o=d[b+h>>0]|0;k=d[b+(1-f)>>0]|0;h=((d[b+(g?i:h)>>0]|0)+2+(o<<1)+k|0)>>>2;l=d[b+(2-f)>>0]|0;j=(o+2+(k<<1)+l|0)>>>2;m=d[b+(3-f)>>0]|0;k=(k+2+(l<<1)+m|0)>>>2;p=d[b+(4-f)>>0]|0;l=(l+2+(m<<1)+p|0)>>>2;r=d[b+(5-f)>>0]|0;m=(m+2+(p<<1)+r|0)>>>2;n=d[b+(6-f)>>0]|0;p=(p+2+(r<<1)+n|0)>>>2;c=a[b+(7-f)>>0]|0;q=c&255;r=(r+2+(n<<1)+q|0)>>>2;if(e)c=a[b+(8-f)>>0]|0;e=((c&255)+2+(q<<1)+n|0)>>>2;u=b+i|0;v=b+-1|0;B=d[v>>0]|0;q=d[b+(f+-1)>>0]|0;v=((d[(g?u:v)>>0]|0)+2+(B<<1)+q|0)>>>2;n=f<<1;y=d[b+(n+-1)>>0]|0;B=B+2|0;w=(B+(q<<1)+y|0)>>>2;i=f*3|0;x=d[b+(i+-1)>>0]|0;q=(q+2+(y<<1)+x|0)>>>2;g=f<<2;A=d[b+(g+-1)>>0]|0;y=(y+2+(x<<1)+A|0)>>>2;c=f*5|0;C=d[b+(c+-1)>>0]|0;x=(x+2+(A<<1)+C|0)>>>2;s=f*6|0;z=d[b+(s+-1)>>0]|0;A=(A+2+(C<<1)+z|0)>>>2;t=f*7|0;z=(C+2+(z<<1)+(d[b+(t+-1)>>0]|0)|0)>>>2;u=(B+((d[u>>0]|0)<<1)+o|0)>>>2;a[b+s>>0]=(y+2+(x<<1)+A|0)>>>2;a[b+t>>0]=(x+2+(A<<1)+z|0)>>>2;o=(w+2+(q<<1)+y|0)>>>2&255;a[b+(s|1)>>0]=o;a[b+g>>0]=o;o=(q+2+(y<<1)+x|0)>>>2&255;a[b+(t+1)>>0]=o;a[b+c>>0]=o;o=((v<<1)+2+w+u|0)>>>2&255;a[b+(s+2)>>0]=o;a[b+(g|1)>>0]=o;a[b+n>>0]=o;q=(v+2+(w<<1)+q|0)>>>2&255;a[b+(t+2)>>0]=q;a[b+(c+1)>>0]=q;a[b+i>>0]=q;q=h+2|0;o=(q+v+(u<<1)|0)>>>2&255;a[b+(t+3)>>0]=o;a[b+(c+2)>>0]=o;a[b+(i+1)>>0]=o;a[b+f>>0]=o;o=h+1|0;v=(o+u|0)>>>1&255;a[b+(s+3)>>0]=v;a[b+(g|2)>>0]=v;a[b+(n|1)>>0]=v;a[b>>0]=v;h=((h<<1)+2+j+u|0)>>>2&255;a[b+(t+4)>>0]=h;a[b+(c+3)>>0]=h;a[b+(i+2)>>0]=h;a[b+(f+1)>>0]=h;o=(o+j|0)>>>1&255;a[b+(s+4)>>0]=o;a[b+(g|3)>>0]=o;a[b+(n+2)>>0]=o;a[b+1>>0]=o;q=(q+(j<<1)+k|0)>>>2&255;a[b+(t+5)>>0]=q;a[b+(c+4)>>0]=q;a[b+(i+3)>>0]=q;a[b+(f+2)>>0]=q;q=(j+1+k|0)>>>1&255;a[b+(s+5)>>0]=q;a[b+(g+4)>>0]=q;a[b+(n+3)>>0]=q;a[b+2>>0]=q;q=(j+2+(k<<1)+l|0)>>>2&255;a[b+(t+6)>>0]=q;a[b+(c+5)>>0]=q;a[b+(i+4)>>0]=q;a[b+(f+3)>>0]=q;q=(k+1+l|0)>>>1&255;a[b+(s+6)>>0]=q;a[b+(g+5)>>0]=q;a[b+(n+4)>>0]=q;a[b+3>>0]=q;q=(k+2+(l<<1)+m|0)>>>2&255;a[b+(t+7)>>0]=q;a[b+(c+6)>>0]=q;a[b+(i+5)>>0]=q;a[b+(f+4)>>0]=q;q=(l+1+m|0)>>>1&255;a[b+(s+7)>>0]=q;a[b+(g+6)>>0]=q;a[b+(n+5)>>0]=q;a[b+4>>0]=q;q=(l+2+(m<<1)+p|0)>>>2&255;a[b+(c+7)>>0]=q;a[b+(i+6)>>0]=q;a[b+(f+5)>>0]=q;q=(m+1+p|0)>>>1&255;a[b+(g+7)>>0]=q;a[b+(n+6)>>0]=q;a[b+5>>0]=q;q=(m+2+(p<<1)+r|0)>>>2&255;a[b+(i+7)>>0]=q;a[b+(f+6)>>0]=q;q=(p+1+r|0)>>>1&255;a[b+(n+7)>>0]=q;a[b+6>>0]=q;a[b+(f+7)>>0]=(p+2+(r<<1)+e|0)>>>2;a[b+7>>0]=(r+1+e|0)>>>1;return}function Xm(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;l=(c|0)!=0;n=0-f|0;m=~f;B=d[b+n>>0]|0;i=d[b+(1-f)>>0]|0;n=((d[b+(l?m:n)>>0]|0)+2+(B<<1)+i|0)>>>2;h=d[b+(2-f)>>0]|0;B=B+2|0;j=(B+(i<<1)+h|0)>>>2;g=d[b+(3-f)>>0]|0;i=(i+2+(h<<1)+g|0)>>>2;c=d[b+(4-f)>>0]|0;h=(h+2+(g<<1)+c|0)>>>2;o=d[b+(5-f)>>0]|0;g=(g+2+(c<<1)+o|0)>>>2;e=d[b+(6-f)>>0]|0;c=(c+2+(o<<1)+e|0)>>>2;e=(o+2+(e<<1)+(d[b+(7-f)>>0]|0)|0)>>>2;m=b+m|0;o=b+-1|0;A=d[o>>0]|0;k=d[b+(f+-1)>>0]|0;o=((d[(l?m:o)>>0]|0)+2+(A<<1)+k|0)>>>2;l=f<<1;t=d[b+(l+-1)>>0]|0;q=(A+2+(k<<1)+t|0)>>>2;p=f*3|0;v=d[b+(p+-1)>>0]|0;k=(k+2+(t<<1)+v|0)>>>2;r=f<<2;x=d[b+(r+-1)>>0]|0;t=(t+2+(v<<1)+x|0)>>>2;s=f*5|0;y=d[b+(s+-1)>>0]|0;v=(v+2+(x<<1)+y|0)>>>2;u=f*6|0;C=d[b+(u+-1)>>0]|0;x=(x+2+(y<<1)+C|0)>>>2;w=f*7|0;z=d[b+(w+-1)>>0]|0;y=(y+2+(C<<1)+z|0)>>>2;z=(C+2+(z*3|0)|0)>>>2;m=(B+A+((d[m>>0]|0)<<1)|0)>>>2;a[b+w>>0]=(z+1+y|0)>>>1;a[b+(w+1)>>0]=(x+2+z+(y<<1)|0)>>>2;z=(x+1+y|0)>>>1&255;a[b+(w+2)>>0]=z;a[b+u>>0]=z;y=(v+2+(x<<1)+y|0)>>>2&255;a[b+(w+3)>>0]=y;a[b+(u|1)>>0]=y;y=(v+1+x|0)>>>1&255;a[b+(w+4)>>0]=y;a[b+(u+2)>>0]=y;a[b+s>>0]=y;x=(t+2+(v<<1)+x|0)>>>2&255;a[b+(w+5)>>0]=x;a[b+(u+3)>>0]=x;a[b+(s+1)>>0]=x;x=(t+1+v|0)>>>1&255;a[b+(w+6)>>0]=x;a[b+(u+4)>>0]=x;a[b+(s+2)>>0]=x;a[b+r>>0]=x;v=(k+2+(t<<1)+v|0)>>>2&255;a[b+(w+7)>>0]=v;a[b+(u+5)>>0]=v;a[b+(s+3)>>0]=v;a[b+(r|1)>>0]=v;v=(k+1+t|0)>>>1&255;a[b+(u+6)>>0]=v;a[b+(s+4)>>0]=v;a[b+(r|2)>>0]=v;a[b+p>>0]=v;t=(q+2+(k<<1)+t|0)>>>2&255;a[b+(u+7)>>0]=t;a[b+(s+5)>>0]=t;a[b+(r|3)>>0]=t;a[b+(p+1)>>0]=t;t=(q+1+k|0)>>>1&255;a[b+(s+6)>>0]=t;a[b+(r+4)>>0]=t;a[b+(p+2)>>0]=t;a[b+l>>0]=t;k=(o+2+(q<<1)+k|0)>>>2&255;a[b+(s+7)>>0]=k;a[b+(r+5)>>0]=k;a[b+(p+3)>>0]=k;a[b+(l|1)>>0]=k;k=o+1|0;s=(k+q|0)>>>1&255;a[b+(r+6)>>0]=s;a[b+(p+4)>>0]=s;a[b+(l+2)>>0]=s;a[b+f>>0]=s;q=((o<<1)+2+q+m|0)>>>2&255;a[b+(r+7)>>0]=q;a[b+(p+5)>>0]=q;a[b+(l+3)>>0]=q;a[b+(f+1)>>0]=q;k=(k+m|0)>>>1&255;a[b+(p+6)>>0]=k;a[b+(l+4)>>0]=k;a[b+(f+2)>>0]=k;a[b>>0]=k;k=n+2|0;o=(k+o+(m<<1)|0)>>>2&255;a[b+(p+7)>>0]=o;a[b+(l+5)>>0]=o;a[b+(f+3)>>0]=o;a[b+1>>0]=o;m=((n<<1)+2+j+m|0)>>>2&255;a[b+(l+6)>>0]=m;a[b+(f+4)>>0]=m;a[b+2>>0]=m;k=(k+(j<<1)+i|0)>>>2&255;a[b+(l+7)>>0]=k;a[b+(f+5)>>0]=k;a[b+3>>0]=k;j=(j+2+(i<<1)+h|0)>>>2&255;a[b+(f+6)>>0]=j;a[b+4>>0]=j;i=(i+2+(h<<1)+g|0)>>>2&255;a[b+(f+7)>>0]=i;a[b+5>>0]=i;a[b+6>>0]=(h+2+(g<<1)+c|0)>>>2;a[b+7>>0]=(g+2+(c<<1)+e|0)>>>2;return}function Ym(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;l=0-f|0;m=d[b+l>>0]|0;n=d[b+(1-f)>>0]|0;l=((d[b+((c|0)==0?l:~f)>>0]|0)+2+(m<<1)+n|0)>>>2;o=d[b+(2-f)>>0]|0;m=(m+2+(n<<1)+o|0)>>>2;p=d[b+(3-f)>>0]|0;n=(n+2+(o<<1)+p|0)>>>2;r=d[b+(4-f)>>0]|0;o=(o+2+(p<<1)+r|0)>>>2;s=d[b+(5-f)>>0]|0;p=(p+2+(r<<1)+s|0)>>>2;q=d[b+(6-f)>>0]|0;r=(r+2+(s<<1)+q|0)>>>2;c=a[b+(7-f)>>0]|0;k=c&255;s=(s+2+(q<<1)+k|0)>>>2;if(!e){e=(c&255)+2+(k<<1)|0;g=k;h=k;i=k;j=k;c=k}else{u=d[b+(8-f)>>0]|0;t=d[b+(9-f)>>0]|0;c=d[b+(10-f)>>0]|0;j=d[b+(11-f)>>0]|0;i=d[b+(12-f)>>0]|0;e=(d[b+(8-f)>>0]|0)+2+(k<<1)|0;g=(t+2+(c<<1)+j|0)>>>2;h=(c+2+(j<<1)+i|0)>>>2;i=(j+2+(i<<1)+(d[b+(13-f)>>0]|0)|0)>>>2;j=(k+2+(u<<1)+t|0)>>>2;c=(u+2+(t<<1)+c|0)>>>2}e=(e+q|0)>>>2;a[b>>0]=(l+1+m|0)>>>1;a[b+f>>0]=(l+2+(m<<1)+n|0)>>>2;u=(m+1+n|0)>>>1&255;a[b+1>>0]=u;k=f<<1;a[b+k>>0]=u;u=(m+2+(n<<1)+o|0)>>>2&255;a[b+(f+1)>>0]=u;l=f*3|0;a[b+l>>0]=u;u=(n+1+o|0)>>>1&255;a[b+2>>0]=u;a[b+(k|1)>>0]=u;m=f<<2;a[b+m>>0]=u;u=(n+2+(o<<1)+p|0)>>>2&255;a[b+(f+2)>>0]=u;a[b+(l+1)>>0]=u;q=f*5|0;a[b+q>>0]=u;u=(o+1+p|0)>>>1&255;a[b+3>>0]=u;a[b+(k+2)>>0]=u;a[b+(m|1)>>0]=u;t=f*6|0;a[b+t>>0]=u;o=(o+2+(p<<1)+r|0)>>>2&255;a[b+(f+3)>>0]=o;a[b+(l+2)>>0]=o;a[b+(q+1)>>0]=o;u=f*7|0;a[b+u>>0]=o;o=(p+1+r|0)>>>1&255;a[b+4>>0]=o;a[b+(k+3)>>0]=o;a[b+(m|2)>>0]=o;a[b+(t|1)>>0]=o;p=(p+2+(r<<1)+s|0)>>>2&255;a[b+(f+4)>>0]=p;a[b+(l+3)>>0]=p;a[b+(q+2)>>0]=p;a[b+(u+1)>>0]=p;p=(r+1+s|0)>>>1&255;a[b+5>>0]=p;a[b+(k+4)>>0]=p;a[b+(m|3)>>0]=p;a[b+(t+2)>>0]=p;r=(r+2+(s<<1)+e|0)>>>2&255;a[b+(f+5)>>0]=r;a[b+(l+4)>>0]=r;a[b+(q+3)>>0]=r;a[b+(u+2)>>0]=r;r=(s+1+e|0)>>>1&255;a[b+6>>0]=r;a[b+(k+5)>>0]=r;a[b+(m+4)>>0]=r;a[b+(t+3)>>0]=r;s=(s+2+(e<<1)+j|0)>>>2&255;a[b+(f+6)>>0]=s;a[b+(l+5)>>0]=s;a[b+(q+4)>>0]=s;a[b+(u+3)>>0]=s;s=(e+1+j|0)>>>1&255;a[b+7>>0]=s;a[b+(k+6)>>0]=s;a[b+(m+5)>>0]=s;a[b+(t+4)>>0]=s;s=(e+2+(j<<1)+c|0)>>>2&255;a[b+(f+7)>>0]=s;a[b+(l+6)>>0]=s;a[b+(q+5)>>0]=s;a[b+(u+4)>>0]=s;f=(j+1+c|0)>>>1&255;a[b+(k+7)>>0]=f;a[b+(m+6)>>0]=f;a[b+(t+5)>>0]=f;f=(j+2+(c<<1)+g|0)>>>2&255;a[b+(l+7)>>0]=f;a[b+(q+6)>>0]=f;a[b+(u+5)>>0]=f;f=(c+1+g|0)>>>1&255;a[b+(m+7)>>0]=f;a[b+(t+6)>>0]=f;f=(c+2+(g<<1)+h|0)>>>2&255;a[b+(q+7)>>0]=f;a[b+(u+6)>>0]=f;a[b+(t+7)>>0]=(g+1+h|0)>>>1;a[b+(u+7)>>0]=(g+2+(h<<1)+i|0)>>>2;return}function Zm(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=b+-1|0;q=d[r>>0]|0;p=d[b+(f+-1)>>0]|0;r=((d[((c|0)==0?r:b+~f|0)>>0]|0)+2+(q<<1)+p|0)>>>2;l=f<<1;o=d[b+(l+-1)>>0]|0;q=(q+2+(p<<1)+o|0)>>>2;j=f*3|0;n=d[b+(j+-1)>>0]|0;p=(p+2+(o<<1)+n|0)>>>2;i=f<<2;m=d[b+(i+-1)>>0]|0;o=(o+2+(n<<1)+m|0)>>>2;h=f*5|0;k=d[b+(h+-1)>>0]|0;n=(n+2+(m<<1)+k|0)>>>2;g=f*6|0;s=d[b+(g+-1)>>0]|0;m=(m+2+(k<<1)+s|0)>>>2;e=f*7|0;c=d[b+(e+-1)>>0]|0;k=(k+2+(s<<1)+c|0)>>>2;c=(s+2+(c*3|0)|0)>>>2;a[b>>0]=(r+1+q|0)>>>1;a[b+1>>0]=(r+2+(q<<1)+p|0)>>>2;r=(q+1+p|0)>>>1&255;a[b+2>>0]=r;a[b+f>>0]=r;q=(q+2+(p<<1)+o|0)>>>2&255;a[b+3>>0]=q;a[b+(f+1)>>0]=q;q=(p+1+o|0)>>>1&255;a[b+4>>0]=q;a[b+(f+2)>>0]=q;a[b+l>>0]=q;p=(p+2+(o<<1)+n|0)>>>2&255;a[b+5>>0]=p;a[b+(f+3)>>0]=p;a[b+(l|1)>>0]=p;p=(o+1+n|0)>>>1&255;a[b+6>>0]=p;a[b+(f+4)>>0]=p;a[b+(l+2)>>0]=p;a[b+j>>0]=p;o=(o+2+(n<<1)+m|0)>>>2&255;a[b+7>>0]=o;a[b+(f+5)>>0]=o;a[b+(l+3)>>0]=o;a[b+(j+1)>>0]=o;o=(n+1+m|0)>>>1&255;a[b+(f+6)>>0]=o;a[b+(l+4)>>0]=o;a[b+(j+2)>>0]=o;a[b+i>>0]=o;n=(n+2+(m<<1)+k|0)>>>2&255;a[b+(f+7)>>0]=n;a[b+(l+5)>>0]=n;a[b+(j+3)>>0]=n;a[b+(i|1)>>0]=n;f=(m+1+k|0)>>>1&255;a[b+(l+6)>>0]=f;a[b+(j+4)>>0]=f;a[b+(i|2)>>0]=f;a[b+h>>0]=f;f=(m+2+c+(k<<1)|0)>>>2&255;a[b+(l+7)>>0]=f;a[b+(j+5)>>0]=f;a[b+(i|3)>>0]=f;a[b+(h+1)>>0]=f;f=(c+1+k|0)>>>1&255;a[b+(j+6)>>0]=f;a[b+(i+4)>>0]=f;a[b+(h+2)>>0]=f;a[b+g>>0]=f;f=(k+2+(c*3|0)|0)>>>2&255;a[b+(j+7)>>0]=f;a[b+(i+5)>>0]=f;a[b+(h+3)>>0]=f;a[b+(g|1)>>0]=f;c=c&255;a[b+(e+7)>>0]=c;a[b+(g+7)>>0]=c;a[b+(h+7)>>0]=c;a[b+(i+7)>>0]=c;a[b+(e+6)>>0]=c;a[b+(g+6)>>0]=c;a[b+(h+6)>>0]=c;a[b+(i+6)>>0]=c;a[b+(e+5)>>0]=c;a[b+(g+5)>>0]=c;a[b+(h+5)>>0]=c;a[b+(e+4)>>0]=c;a[b+(g+4)>>0]=c;a[b+(h+4)>>0]=c;a[b+(e+3)>>0]=c;a[b+(g+3)>>0]=c;a[b+(e+2)>>0]=c;a[b+(g+2)>>0]=c;a[b+(e+1)>>0]=c;a[b+e>>0]=c;return}function _m(a,b,e,f){a=a|0;b=b|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;n=a+-1|0;m=d[n>>0]|0;l=d[a+(f+-1)>>0]|0;k=d[a+((f<<1)+-1)>>0]|0;j=d[a+((f*3|0)+-1)>>0]|0;i=d[a+((f<<2)+-1)>>0]|0;h=d[a+((f*5|0)+-1)>>0]|0;g=d[a+((f*6|0)+-1)>>0]|0;e=d[a+((f*7|0)+-1)>>0]|0;b=_((((d[((b|0)==0?n:a+~f|0)>>0]|0)+2+(m<<1)+l|0)>>>2)+4+((m+2+(l<<1)+k|0)>>>2)+((l+2+(k<<1)+j|0)>>>2)+((k+2+(j<<1)+i|0)>>>2)+((j+2+(i<<1)+h|0)>>>2)+((i+2+(h<<1)+g|0)>>>2)+((h+2+(g<<1)+e|0)>>>2)+((g+2+(e*3|0)|0)>>>2)>>3,16843009)|0;c[a>>2]=b;c[a+4>>2]=b;e=a+f|0;c[e>>2]=b;c[e+4>>2]=b;e=e+f|0;c[e>>2]=b;c[e+4>>2]=b;e=e+f|0;c[e>>2]=b;c[e+4>>2]=b;e=e+f|0;c[e>>2]=b;c[e+4>>2]=b;e=e+f|0;c[e>>2]=b;c[e+4>>2]=b;e=e+f|0;c[e>>2]=b;c[e+4>>2]=b;e=e+f|0;c[e>>2]=b;c[e+4>>2]=b;return}function $m(b,e,f,g){b=b|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;i=0-g|0;p=d[b+i>>0]|0;j=d[b+(1-g)>>0]|0;k=d[b+(2-g)>>0]|0;l=d[b+(3-g)>>0]|0;m=d[b+(4-g)>>0]|0;n=d[b+(5-g)>>0]|0;o=d[b+(6-g)>>0]|0;h=a[b+(7-g)>>0]|0;q=h&255;if(f)h=a[b+(8-g)>>0]|0;q=_((((d[b+((e|0)==0?i:~g)>>0]|0)+2+(p<<1)+j|0)>>>2)+4+((p+2+(j<<1)+k|0)>>>2)+((j+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+((l+2+(m<<1)+n|0)>>>2)+((m+2+(n<<1)+o|0)>>>2)+((n+2+(o<<1)+q|0)>>>2)+(((h&255)+2+(q<<1)+o|0)>>>2)>>3,16843009)|0;c[b>>2]=q;c[b+4>>2]=q;e=b+g|0;c[e>>2]=q;c[e+4>>2]=q;e=e+g|0;c[e>>2]=q;c[e+4>>2]=q;e=e+g|0;c[e>>2]=q;c[e+4>>2]=q;e=e+g|0;c[e>>2]=q;c[e+4>>2]=q;e=e+g|0;c[e>>2]=q;c[e+4>>2]=q;e=e+g|0;c[e>>2]=q;c[e+4>>2]=q;e=e+g|0;c[e>>2]=q;c[e+4>>2]=q;return}function an(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;d=a+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=d+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=d+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=d+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=d+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=d+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=d+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;return}function bn(a,b){a=a|0;b=b|0;var d=0,e=0,f=0;d=a+(0-b)|0;e=c[d>>2]|0;d=c[d+4>>2]|0;c[a>>2]=e;c[a+4>>2]=d;f=a+b|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*3|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<2)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*5|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*6|0)|0;c[f>>2]=e;c[f+4>>2]=d;b=a+(b*7|0)|0;c[b>>2]=e;c[b+4>>2]=d;return}function cn(a,b){a=a|0;b=b|0;var d=0,e=0,f=0;d=a+(0-b)|0;e=c[d>>2]|0;d=c[d+4>>2]|0;c[a>>2]=e;c[a+4>>2]=d;f=a+b|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*3|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<2)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*5|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*6|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*7|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<3)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*9|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*10|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*11|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*12|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*13|0)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b*14|0)|0;c[f>>2]=e;c[f+4>>2]=d;b=a+(b*15|0)|0;c[b>>2]=e;c[b+4>>2]=d;return}function dn(a,b){a=a|0;b=b|0;var e=0,f=0;f=_(d[a+-1>>0]|0,16843009)|0;c[a>>2]=f;c[a+4>>2]=f;f=_(d[a+(b+-1)>>0]|0,16843009)|0;e=a+b|0;c[e>>2]=f;c[e+4>>2]=f;e=b<<1;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*3|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b<<2;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*5|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*6|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;b=b*7|0;e=_(d[a+(b+-1)>>0]|0,16843009)|0;b=a+b|0;c[b>>2]=e;c[b+4>>2]=e;return}function en(a,b){a=a|0;b=b|0;var e=0,f=0;f=_(d[a+-1>>0]|0,16843009)|0;c[a>>2]=f;c[a+4>>2]=f;f=_(d[a+(b+-1)>>0]|0,16843009)|0;e=a+b|0;c[e>>2]=f;c[e+4>>2]=f;e=b<<1;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*3|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b<<2;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*5|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*6|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*7|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b<<3;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*9|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*10|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*11|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*12|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*13|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;e=b*14|0;f=_(d[a+(e+-1)>>0]|0,16843009)|0;e=a+e|0;c[e>>2]=f;c[e+4>>2]=f;b=b*15|0;e=_(d[a+(b+-1)>>0]|0,16843009)|0;b=a+b|0;c[b>>2]=e;c[b+4>>2]=e;return}function fn(b,c){b=b|0;c=c|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;l=0-c|0;e=b+3+l|0;g=b+(c<<2)+-1|0;f=g+(0-(c<<1))|0;i=g+c|0;h=f+l|0;k=i+c|0;j=h+l|0;m=d[k+c>>0]|0;e=((((d[e+4>>0]|0)-(d[e+-4>>0]|0)<<2)+((((d[e+3>>0]|0)-(d[e+-3>>0]|0)|0)*3|0)+(((d[e+2>>0]|0)-(d[e+-2>>0]|0)<<1)+((d[e+1>>0]|0)-(d[e+-1>>0]|0))))|0)*17|0)+16>>5;f=(((m-(d[j+l>>0]|0)<<2)+((((d[k>>0]|0)-(d[j>>0]|0)|0)*3|0)+(((d[i>>0]|0)-(d[h>>0]|0)<<1)+((d[g>>0]|0)-(d[f>>0]|0))))|0)*17|0)+16>>5;g=e<<1;h=e*3|0;i=e<<2;j=e*5|0;k=e*6|0;l=e*7|0;m=(_(f+e|0,-3)|0)+16+((d[b+~c+8>>0]|0)+m<<4)|0;n=8;while(1){o=m>>5;a[b>>0]=o>>>0>255?0-o>>31:o;o=m+e>>5;a[b+1>>0]=o>>>0>255?0-o>>31:o;o=m+g>>5;a[b+2>>0]=o>>>0>255?0-o>>31:o;o=m+h>>5;a[b+3>>0]=o>>>0>255?0-o>>31:o;o=m+i>>5;a[b+4>>0]=o>>>0>255?0-o>>31:o;o=m+j>>5;a[b+5>>0]=o>>>0>255?0-o>>31:o;o=m+k>>5;a[b+6>>0]=o>>>0>255?0-o>>31:o;o=m+l>>5;a[b+7>>0]=o>>>0>255?0-o>>31:o;if((n|0)>1){m=m+f|0;n=n+-1|0;b=b+c|0}else break}return}function gn(b,c){b=b|0;c=c|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;m=0-c|0;e=b+3+m|0;f=b+(c<<3)+-1|0;n=f+(0-(c<<1))|0;o=c*3|0;h=f+c|0;g=n+m|0;j=h+c|0;i=g+m|0;p=b+(o+-1)|0;q=b+((c*11|0)+-1)+c|0;k=p+m|0;s=q+c|0;r=k+m|0;u=s+c|0;t=r+m|0;l=a[u+c>>0]|0;n=((((l&255)-(d[t+m>>0]|0)<<3)+((((d[u>>0]|0)-(d[t>>0]|0)|0)*7|0)+((((d[s>>0]|0)-(d[r>>0]|0)|0)*6|0)+((((d[q>>0]|0)-(d[k>>0]|0)|0)*5|0)+(((d[j+c>>0]|0)-(d[i+m>>0]|0)<<2)+((((d[j>>0]|0)-(d[i>>0]|0)|0)*3|0)+(((d[h>>0]|0)-(d[g>>0]|0)<<1)+((d[f>>0]|0)-(d[n>>0]|0))))))))|0)*5|0)+32>>6;e=((((d[e+4>>0]|0)-(d[e+-4>>0]|0)<<2)+((((d[e+3>>0]|0)-(d[e+-3>>0]|0)|0)*3|0)+(((d[e+2>>0]|0)-(d[e+-2>>0]|0)<<1)+((d[e+1>>0]|0)-(d[e+-1>>0]|0))))|0)*17|0)+16>>5;f=e*3|0;g=e<<1;h=e<<2;i=e*5|0;j=e*6|0;k=e*7|0;l=16-f+(_(n,-7)|0)+((d[p+(0-o)+m+8>>0]|0)+(l&255)<<4)|0;m=16;while(1){u=l>>5;a[b>>0]=u>>>0>255?0-u>>31:u;u=l+e>>5;a[b+1>>0]=u>>>0>255?0-u>>31:u;u=l+g>>5;a[b+2>>0]=u>>>0>255?0-u>>31:u;u=l+f>>5;a[b+3>>0]=u>>>0>255?0-u>>31:u;u=l+h>>5;a[b+4>>0]=u>>>0>255?0-u>>31:u;u=l+i>>5;a[b+5>>0]=u>>>0>255?0-u>>31:u;u=l+j>>5;a[b+6>>0]=u>>>0>255?0-u>>31:u;u=l+k>>5;a[b+7>>0]=u>>>0>255?0-u>>31:u;if((m|0)>1){l=l+n|0;m=m+-1|0;b=b+c|0}else break}return}function hn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;j=b<<2;i=b*5|0;n=b<<1;h=b*6|0;f=b*3|0;e=(d[a+(7-b)>>0]|0)+((d[a+(6-b)>>0]|0)+((d[a+(5-b)>>0]|0)+(d[a+(4-b)>>0]|0)))|0;g=b*7|0;k=(d[a+(g+-1)>>0]|0)+((d[a+(h+-1)>>0]|0)+((d[a+(i+-1)>>0]|0)+(d[a+(j+-1)>>0]|0)))|0;m=_((d[a+(f+-1)>>0]|0)+((d[a+(n+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+((d[a+-1>>0]|0)+(d[a+(0-b)>>0]|0))+(d[a+(1-b)>>0]|0))+(d[a+(2-b)>>0]|0))+(d[a+(3-b)>>0]|0)+4>>3,16843009)|0;l=_((e+2|0)>>>2,16843009)|0;c[a>>2]=m;c[a+4>>2]=l;b=a+b|0;c[b>>2]=m;c[b+4>>2]=l;b=a+n|0;c[b>>2]=m;c[b+4>>2]=l;f=a+f|0;c[f>>2]=m;c[f+4>>2]=l;f=_((k+2|0)>>>2,16843009)|0;e=_((k+4+e|0)>>>3,16843009)|0;b=a+j|0;c[b>>2]=f;c[b+4>>2]=e;b=a+i|0;c[b>>2]=f;c[b+4>>2]=e;b=a+h|0;c[b>>2]=f;c[b+4>>2]=e;b=a+g|0;c[b>>2]=f;c[b+4>>2]=e;return}function jn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=b<<2;h=b*5|0;l=b<<1;g=b*6|0;k=b*3|0;f=b*7|0;j=_(((d[a+(k+-1)>>0]|0)+((d[a+(l+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+(d[a+-1>>0]|0)))+2|0)>>>2,16843009)|0;e=((d[a+(f+-1)>>0]|0)+((d[a+(g+-1)>>0]|0)+((d[a+(h+-1)>>0]|0)+(d[a+(i+-1)>>0]|0)))+2|0)>>>2;c[a>>2]=j;c[a+4>>2]=j;b=a+b|0;c[b>>2]=j;c[b+4>>2]=j;b=a+l|0;c[b>>2]=j;c[b+4>>2]=j;b=a+k|0;c[b>>2]=j;c[b+4>>2]=j;e=_(e,16843009)|0;b=a+i|0;c[b>>2]=e;c[b+4>>2]=e;b=a+h|0;c[b>>2]=e;c[b+4>>2]=e;b=a+g|0;c[b>>2]=e;c[b+4>>2]=e;b=a+f|0;c[b>>2]=e;c[b+4>>2]=e;return}function kn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0;e=4-b|0;f=_(((d[a+(3-b)>>0]|0)+((d[a+(2-b)>>0]|0)+((d[a+(1-b)>>0]|0)+(d[a+(0-b)>>0]|0)))+2|0)>>>2,16843009)|0;e=_(((d[a+(e+3)>>0]|0)+((d[a+(e+2)>>0]|0)+((d[a+(e+1)>>0]|0)+(d[a+e>>0]|0)))+2|0)>>>2,16843009)|0;c[a>>2]=f;c[a+4>>2]=e;g=a+b|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*3|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b<<2)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*5|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*6|0)|0;c[g>>2]=f;c[g+4>>2]=e;b=a+(b*7|0)|0;c[b>>2]=f;c[b+4>>2]=e;return}function ln(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;o=4-b|0;m=a+(0-b)|0;l=a+(1-b)|0;k=a+(2-b)|0;j=a+(3-b)|0;p=_(((d[m>>0]|0)+2+(d[l>>0]|0)+(d[k>>0]|0)+(d[j>>0]|0)|0)>>>2,16843009)|0;o=_(((d[a+o>>0]|0)+2+(d[a+(o+1)>>0]|0)+(d[a+(o+2)>>0]|0)+(d[a+(o+3)>>0]|0)|0)>>>2,16843009)|0;c[a>>2]=p;c[a+4>>2]=o;g=a+b|0;c[g>>2]=p;c[g+4>>2]=o;i=b<<1;f=a+i|0;c[f>>2]=p;c[f+4>>2]=o;h=b*3|0;e=a+h|0;c[e>>2]=p;c[e+4>>2]=o;n=a+(b<<2)|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(b*5|0)|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(b*6|0)|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(b*7|0)|0;c[n>>2]=p;c[n+4>>2]=o;b=_((d[m>>0]|0)+4+(d[l>>0]|0)+(d[k>>0]|0)+(d[j>>0]|0)+(d[a+-1>>0]|0)+(d[a+(b+-1)>>0]|0)+(d[a+(i+-1)>>0]|0)+(d[a+(h+-1)>>0]|0)>>3,16843009)|0;c[a>>2]=b;c[g>>2]=b;c[f>>2]=b;c[e>>2]=b;return}function mn(a,b){a=a|0;b=b|0;var e=0;hn(a,b);e=_(((d[a+(0-b)>>0]|0)+2+(d[a+(1-b)>>0]|0)+(d[a+(2-b)>>0]|0)+(d[a+(3-b)>>0]|0)|0)>>>2,16843009)|0;c[a>>2]=e;c[a+b>>2]=e;c[a+(b<<1)>>2]=e;c[a+(b*3|0)>>2]=e;return}function nn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=b<<2;l=b*5|0;g=b<<1;k=b*6|0;e=b*3|0;j=b*7|0;m=_(((d[a+-1>>0]|0)+2+(d[a+(b+-1)>>0]|0)+(d[a+(g+-1)>>0]|0)+(d[a+(e+-1)>>0]|0)|0)>>>2,16843009)|0;i=((d[a+(h+-1)>>0]|0)+2+(d[a+(l+-1)>>0]|0)+(d[a+(k+-1)>>0]|0)+(d[a+(j+-1)>>0]|0)|0)>>>2;c[a>>2]=m;c[a+4>>2]=m;f=a+b|0;c[f>>2]=m;c[f+4>>2]=m;f=a+g|0;c[f>>2]=m;c[f+4>>2]=m;f=a+e|0;c[f>>2]=m;c[f+4>>2]=m;i=_(i,16843009)|0;h=a+h|0;f=h+4|0;l=a+l|0;c[l>>2]=i;c[l+4>>2]=i;k=a+k|0;c[k>>2]=i;c[k+4>>2]=i;a=a+j|0;c[a>>2]=i;c[a+4>>2]=i;c[h>>2]=-2139062144;c[h+b>>2]=-2139062144;c[h+g>>2]=-2139062144;c[h+e>>2]=-2139062144;c[f>>2]=-2139062144;c[f+b>>2]=-2139062144;c[f+g>>2]=-2139062144;c[f+e>>2]=-2139062144;return}function on(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;o=b<<2;n=b*5|0;g=b<<1;m=b*6|0;e=b*3|0;k=b*7|0;p=_(((d[a+-1>>0]|0)+2+(d[a+(b+-1)>>0]|0)+(d[a+(g+-1)>>0]|0)+(d[a+(e+-1)>>0]|0)|0)>>>2,16843009)|0;l=((d[a+(o+-1)>>0]|0)+2+(d[a+(n+-1)>>0]|0)+(d[a+(m+-1)>>0]|0)+(d[a+(k+-1)>>0]|0)|0)>>>2;f=a+4|0;j=a+b|0;c[j+4>>2]=p;i=a+g|0;c[i+4>>2]=p;h=a+e|0;c[h+4>>2]=p;l=_(l,16843009)|0;o=a+o|0;c[o>>2]=l;c[o+4>>2]=l;n=a+n|0;c[n>>2]=l;c[n+4>>2]=l;m=a+m|0;c[m>>2]=l;c[m+4>>2]=l;k=a+k|0;c[k>>2]=l;c[k+4>>2]=l;c[a>>2]=-2139062144;c[j>>2]=-2139062144;c[i>>2]=-2139062144;c[h>>2]=-2139062144;c[f>>2]=-2139062144;c[f+b>>2]=-2139062144;c[f+g>>2]=-2139062144;c[f+e>>2]=-2139062144;return}function pn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;v=b<<2;p=b<<3;j=b*12|0;u=b*5|0;o=b*9|0;i=b*13|0;x=b<<1;t=b*6|0;n=b*10|0;h=b*14|0;r=b*3|0;e=(d[a+(7-b)>>0]|0)+((d[a+(6-b)>>0]|0)+((d[a+(5-b)>>0]|0)+(d[a+(4-b)>>0]|0)))|0;s=b*7|0;q=(d[a+(s+-1)>>0]|0)+((d[a+(t+-1)>>0]|0)+((d[a+(u+-1)>>0]|0)+(d[a+(v+-1)>>0]|0)))|0;m=b*11|0;k=(d[a+(m+-1)>>0]|0)+((d[a+(n+-1)>>0]|0)+((d[a+(o+-1)>>0]|0)+(d[a+(p+-1)>>0]|0)))|0;g=b*15|0;w=(d[a+(g+-1)>>0]|0)+((d[a+(h+-1)>>0]|0)+((d[a+(i+-1)>>0]|0)+(d[a+(j+-1)>>0]|0)))|0;f=_((d[a+(r+-1)>>0]|0)+((d[a+(x+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+((d[a+-1>>0]|0)+(d[a+(0-b)>>0]|0))+(d[a+(1-b)>>0]|0))+(d[a+(2-b)>>0]|0))+(d[a+(3-b)>>0]|0)+4>>3,16843009)|0;l=_((e+2|0)>>>2,16843009)|0;c[a>>2]=f;c[a+4>>2]=l;b=a+b|0;c[b>>2]=f;c[b+4>>2]=l;b=a+x|0;c[b>>2]=f;c[b+4>>2]=l;r=a+r|0;c[r>>2]=f;c[r+4>>2]=l;r=_((q+2|0)>>>2,16843009)|0;q=_((q+4+e|0)>>>3,16843009)|0;l=_((k+2|0)>>>2,16843009)|0;k=_((k+4+e|0)>>>3,16843009)|0;f=_((w+2|0)>>>2,16843009)|0;e=_((w+4+e|0)>>>3,16843009)|0;b=a+v|0;c[b>>2]=r;c[b+4>>2]=q;b=a+u|0;c[b>>2]=r;c[b+4>>2]=q;b=a+t|0;c[b>>2]=r;c[b+4>>2]=q;b=a+s|0;c[b>>2]=r;c[b+4>>2]=q;b=a+p|0;c[b>>2]=l;c[b+4>>2]=k;b=a+o|0;c[b>>2]=l;c[b+4>>2]=k;b=a+n|0;c[b>>2]=l;c[b+4>>2]=k;b=a+m|0;c[b>>2]=l;c[b+4>>2]=k;b=a+j|0;c[b>>2]=f;c[b+4>>2]=e;b=a+i|0;c[b>>2]=f;c[b+4>>2]=e;b=a+h|0;c[b>>2]=f;c[b+4>>2]=e;b=a+g|0;c[b>>2]=f;c[b+4>>2]=e;return}function qn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;i=b<<2;p=i+-1|0;r=b+-1|0;h=b*5|0;o=h+-1|0;l=b<<1;q=l+-1|0;g=b*6|0;n=g+-1|0;k=b*3|0;j=k+-1|0;e=b*7|0;m=e+-1|0;t=_(((d[a+-1>>0]|0)+2+(d[a+r>>0]|0)+(d[a+q>>0]|0)+(d[a+j>>0]|0)|0)>>>2,16843009)|0;s=((d[a+p>>0]|0)+2+(d[a+o>>0]|0)+(d[a+n>>0]|0)+(d[a+m>>0]|0)|0)>>>2;c[a>>2]=t;c[a+4>>2]=t;f=a+b|0;c[f>>2]=t;c[f+4>>2]=t;f=a+l|0;c[f>>2]=t;c[f+4>>2]=t;f=a+k|0;c[f>>2]=t;c[f+4>>2]=t;s=_(s,16843009)|0;f=a+i|0;c[f>>2]=s;c[f+4>>2]=s;f=a+h|0;c[f>>2]=s;c[f+4>>2]=s;f=a+g|0;c[f>>2]=s;c[f+4>>2]=s;f=a+e|0;c[f>>2]=s;c[f+4>>2]=s;f=a+(b<<3)|0;j=_(((d[f+-1>>0]|0)+2+(d[f+r>>0]|0)+(d[f+q>>0]|0)+(d[f+j>>0]|0)|0)>>>2,16843009)|0;a=((d[f+p>>0]|0)+2+(d[f+o>>0]|0)+(d[f+n>>0]|0)+(d[f+m>>0]|0)|0)>>>2;c[f>>2]=j;c[f+4>>2]=j;b=f+b|0;c[b>>2]=j;c[b+4>>2]=j;b=f+l|0;c[b>>2]=j;c[b+4>>2]=j;b=f+k|0;c[b>>2]=j;c[b+4>>2]=j;a=_(a,16843009)|0;b=f+i|0;c[b>>2]=a;c[b+4>>2]=a;b=f+h|0;c[b>>2]=a;c[b+4>>2]=a;b=f+g|0;c[b>>2]=a;c[b+4>>2]=a;b=f+e|0;c[b>>2]=a;c[b+4>>2]=a;return}function rn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0;e=4-b|0;f=_(((d[a+(3-b)>>0]|0)+((d[a+(2-b)>>0]|0)+((d[a+(1-b)>>0]|0)+(d[a+(0-b)>>0]|0)))+2|0)>>>2,16843009)|0;e=_(((d[a+(e+3)>>0]|0)+((d[a+(e+2)>>0]|0)+((d[a+(e+1)>>0]|0)+(d[a+e>>0]|0)))+2|0)>>>2,16843009)|0;c[a>>2]=f;c[a+4>>2]=e;g=a+b|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*3|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b<<2)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*5|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*6|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*7|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b<<3)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*9|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*10|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*11|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*12|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*13|0)|0;c[g>>2]=f;c[g+4>>2]=e;g=a+(b*14|0)|0;c[g>>2]=f;c[g+4>>2]=e;b=a+(b*15|0)|0;c[b>>2]=f;c[b+4>>2]=e;return}function sn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0;rn(a,b);g=b<<1;e=b*3|0;f=_((d[a+(0-b)>>0]|0)+4+(d[a+(1-b)>>0]|0)+(d[a+(2-b)>>0]|0)+(d[a+(3-b)>>0]|0)+(d[a+-1>>0]|0)+(d[a+(b+-1)>>0]|0)+(d[a+(g+-1)>>0]|0)+(d[a+(e+-1)>>0]|0)>>3,16843009)|0;c[a>>2]=f;c[a+b>>2]=f;c[a+g>>2]=f;c[a+e>>2]=f;return}function tn(a,b){a=a|0;b=b|0;var e=0;pn(a,b);e=_(((d[a+(0-b)>>0]|0)+2+(d[a+(1-b)>>0]|0)+(d[a+(2-b)>>0]|0)+(d[a+(3-b)>>0]|0)|0)>>>2,16843009)|0;c[a>>2]=e;c[a+b>>2]=e;c[a+(b<<1)>>2]=e;c[a+(b*3|0)>>2]=e;return}function un(a,b){a=a|0;b=b|0;var d=0,e=0;qn(a,b);d=a+(b<<2)|0;c[d>>2]=-2139062144;c[d+b>>2]=-2139062144;e=b<<1;c[d+e>>2]=-2139062144;a=b*3|0;c[d+a>>2]=-2139062144;d=d+4|0;c[d>>2]=-2139062144;c[d+b>>2]=-2139062144;c[d+e>>2]=-2139062144;c[d+a>>2]=-2139062144;return}function vn(a,b){a=a|0;b=b|0;var d=0,e=0;qn(a,b);c[a>>2]=-2139062144;c[a+b>>2]=-2139062144;e=b<<1;c[a+e>>2]=-2139062144;d=b*3|0;c[a+d>>2]=-2139062144;a=a+4|0;c[a>>2]=-2139062144;c[a+b>>2]=-2139062144;c[a+e>>2]=-2139062144;c[a+d>>2]=-2139062144;return}function wn(a,b){a=a|0;b=b|0;var d=0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;d=a+b|0;c[d>>2]=2139062143;c[d+4>>2]=2139062143;d=a+(b<<1)|0;c[d>>2]=2139062143;c[d+4>>2]=2139062143;d=a+(b*3|0)|0;c[d>>2]=2139062143;c[d+4>>2]=2139062143;d=a+(b<<2)|0;c[d>>2]=2139062143;c[d+4>>2]=2139062143;d=a+(b*5|0)|0;c[d>>2]=2139062143;c[d+4>>2]=2139062143;d=a+(b*6|0)|0;c[d>>2]=2139062143;c[d+4>>2]=2139062143;b=a+(b*7|0)|0;c[b>>2]=2139062143;c[b+4>>2]=2139062143;return}function xn(a,b){a=a|0;b=b|0;var d=0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;d=a+b|0;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;d=a+(b<<1)|0;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;d=a+(b*3|0)|0;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;d=a+(b<<2)|0;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;d=a+(b*5|0)|0;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;d=a+(b*6|0)|0;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;b=a+(b*7|0)|0;c[b>>2]=-2122219135;c[b+4>>2]=-2122219135;return}function yn(a,b){a=a|0;b=b|0;var d=0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;d=a+b|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=a+(b<<1)|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=a+(b*3|0)|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=a+(b<<2)|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=a+(b*5|0)|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=a+(b*6|0)|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;b=a+(b*7|0)|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;return}function zn(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;i=a+b|0;c[i>>2]=-2139062144;c[i+4>>2]=-2139062144;i=b<<1;h=a+i|0;c[h>>2]=-2139062144;c[h+4>>2]=-2139062144;h=b*3|0;g=a+h|0;c[g>>2]=-2139062144;c[g+4>>2]=-2139062144;g=b<<2;f=a+g|0;c[f>>2]=-2139062144;c[f+4>>2]=-2139062144;f=b*5|0;e=a+f|0;c[e>>2]=-2139062144;c[e+4>>2]=-2139062144;e=b*6|0;d=a+e|0;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;d=b*7|0;j=a+d|0;c[j>>2]=-2139062144;c[j+4>>2]=-2139062144;a=a+(b<<3)|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;b=a+b|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;b=a+i|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;b=a+h|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;b=a+g|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;b=a+f|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;b=a+e|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;b=a+d|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;return}function An(a,b){a=a|0;b=b|0;var e=0;e=_((d[a+(15-b)>>0]|0)+((d[a+(14-b)>>0]|0)+((d[a+(13-b)>>0]|0)+((d[a+(12-b)>>0]|0)+((d[a+(11-b)>>0]|0)+((d[a+(10-b)>>0]|0)+((d[a+(9-b)>>0]|0)+((d[a+(8-b)>>0]|0)+((d[a+(7-b)>>0]|0)+((d[a+(6-b)>>0]|0)+((d[a+(5-b)>>0]|0)+((d[a+(4-b)>>0]|0)+((d[a+(3-b)>>0]|0)+((d[a+(2-b)>>0]|0)+((d[a+(1-b)>>0]|0)+((d[a+(0-b)>>0]|0)+((d[a+((b*15|0)+-1)>>0]|0)+((d[a+((b*14|0)+-1)>>0]|0)+((d[a+((b*13|0)+-1)>>0]|0)+((d[a+((b*12|0)+-1)>>0]|0)+((d[a+((b*11|0)+-1)>>0]|0)+((d[a+((b*10|0)+-1)>>0]|0)+((d[a+((b*9|0)+-1)>>0]|0)+((d[a+((b<<3)+-1)>>0]|0)+((d[a+((b*7|0)+-1)>>0]|0)+((d[a+((b*6|0)+-1)>>0]|0)+((d[a+((b*5|0)+-1)>>0]|0)+((d[a+((b<<2)+-1)>>0]|0)+((d[a+((b*3|0)+-1)>>0]|0)+((d[a+((b<<1)+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+(d[a+-1>>0]|0)))))))))))))))))))))))))))))))+16>>5,16843009)|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;b=a+b|0;c[b>>2]=e;c[b+4>>2]=e;c[b+8>>2]=e;c[b+12>>2]=e;return}function Bn(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;d=a+(0-b)|0;g=c[d>>2]|0;f=c[d+4>>2]|0;e=c[d+8>>2]|0;d=c[d+12>>2]|0;c[a>>2]=g;c[a+4>>2]=f;c[a+8>>2]=e;c[a+12>>2]=d;h=a+b|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b<<1)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*3|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b<<2)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*5|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*6|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*7|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b<<3)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*9|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*10|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*11|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*12|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*13|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;h=a+(b*14|0)|0;c[h>>2]=g;c[h+4>>2]=f;c[h+8>>2]=e;c[h+12>>2]=d;b=a+(b*15|0)|0;c[b>>2]=g;c[b+4>>2]=f;c[b+8>>2]=e;c[b+12>>2]=d;return}function Cn(a,b){a=a|0;b=b|0;var e=0,f=0,g=0;e=0;do{f=_(e,b)|0;g=_(d[a+(f+-1)>>0]|0,16843009)|0;f=a+f|0;c[f>>2]=g;c[f+4>>2]=g;c[f+8>>2]=g;c[f+12>>2]=g;e=e+1|0}while((e|0)!=16);return}function Dn(a,b){a=a|0;b=b|0;var d=0;d=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;c[d>>2]=2139062143;c[d+4>>2]=2139062143;c[d+8>>2]=2139062143;c[d+12>>2]=2139062143;a=d+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;a=a+b|0;c[a>>2]=2139062143;c[a+4>>2]=2139062143;c[a+8>>2]=2139062143;c[a+12>>2]=2139062143;b=a+b|0;c[b>>2]=2139062143;c[b+4>>2]=2139062143;c[b+8>>2]=2139062143;c[b+12>>2]=2139062143;return}function En(a,b){a=a|0;b=b|0;var d=0;d=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;c[d>>2]=-2122219135;c[d+4>>2]=-2122219135;c[d+8>>2]=-2122219135;c[d+12>>2]=-2122219135;a=d+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;a=a+b|0;c[a>>2]=-2122219135;c[a+4>>2]=-2122219135;c[a+8>>2]=-2122219135;c[a+12>>2]=-2122219135;b=a+b|0;c[b>>2]=-2122219135;c[b+4>>2]=-2122219135;c[b+8>>2]=-2122219135;c[b+12>>2]=-2122219135;return}function Fn(a,b){a=a|0;b=b|0;li(a,b,0,0);return}function Gn(a,b){a=a|0;b=b|0;var e=0;e=_((d[a+((b*15|0)+-1)>>0]|0)+((d[a+((b*14|0)+-1)>>0]|0)+((d[a+((b*13|0)+-1)>>0]|0)+((d[a+((b*12|0)+-1)>>0]|0)+((d[a+((b*11|0)+-1)>>0]|0)+((d[a+((b*10|0)+-1)>>0]|0)+((d[a+((b*9|0)+-1)>>0]|0)+((d[a+((b<<3)+-1)>>0]|0)+((d[a+((b*7|0)+-1)>>0]|0)+((d[a+((b*6|0)+-1)>>0]|0)+((d[a+((b*5|0)+-1)>>0]|0)+((d[a+((b<<2)+-1)>>0]|0)+((d[a+((b*3|0)+-1)>>0]|0)+((d[a+((b<<1)+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+(d[a+-1>>0]|0)))))))))))))))+8>>4,16843009)|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;b=a+b|0;c[b>>2]=e;c[b+4>>2]=e;c[b+8>>2]=e;c[b+12>>2]=e;return}function Hn(a,b){a=a|0;b=b|0;var e=0;e=_((d[a+(15-b)>>0]|0)+((d[a+(14-b)>>0]|0)+((d[a+(13-b)>>0]|0)+((d[a+(12-b)>>0]|0)+((d[a+(11-b)>>0]|0)+((d[a+(10-b)>>0]|0)+((d[a+(9-b)>>0]|0)+((d[a+(8-b)>>0]|0)+((d[a+(7-b)>>0]|0)+((d[a+(6-b)>>0]|0)+((d[a+(5-b)>>0]|0)+((d[a+(4-b)>>0]|0)+((d[a+(3-b)>>0]|0)+((d[a+(2-b)>>0]|0)+((d[a+(1-b)>>0]|0)+(d[a+(0-b)>>0]|0)))))))))))))))+8>>4,16843009)|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;a=a+b|0;c[a>>2]=e;c[a+4>>2]=e;c[a+8>>2]=e;c[a+12>>2]=e;b=a+b|0;c[b>>2]=e;c[b+4>>2]=e;c[b+8>>2]=e;c[b+12>>2]=e;return}function In(a,b){a=a|0;b=b|0;var d=0;d=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;c[d>>2]=-2139062144;c[d+4>>2]=-2139062144;c[d+8>>2]=-2139062144;c[d+12>>2]=-2139062144;a=d+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;a=a+b|0;c[a>>2]=-2139062144;c[a+4>>2]=-2139062144;c[a+8>>2]=-2139062144;c[a+12>>2]=-2139062144;b=a+b|0;c[b>>2]=-2139062144;c[b+4>>2]=-2139062144;c[b+8>>2]=-2139062144;c[b+12>>2]=-2139062144;return}function Jn(c,f,g){c=c|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;h=c+(0-g)|0;j=g<<1;i=g*3|0;c=g<<2;k=(e[f>>1]|0)+(d[h>>0]|0)|0;a[h+g>>0]=k;k=(e[f+8>>1]|0)+k|0;a[h+j>>0]=k;k=(e[f+16>>1]|0)+k|0;a[h+i>>0]=k;a[h+c>>0]=(e[f+24>>1]|0)+k;h=h+1|0;k=(e[f+2>>1]|0)+(d[h>>0]|0)|0;a[h+g>>0]=k;k=(e[f+10>>1]|0)+k|0;a[h+j>>0]=k;k=(e[f+18>>1]|0)+k|0;a[h+i>>0]=k;a[h+c>>0]=(e[f+26>>1]|0)+k;h=h+1|0;k=(e[f+4>>1]|0)+(d[h>>0]|0)|0;a[h+g>>0]=k;k=(e[f+12>>1]|0)+k|0;a[h+j>>0]=k;k=(e[f+20>>1]|0)+k|0;a[h+i>>0]=k;a[h+c>>0]=(e[f+28>>1]|0)+k;h=h+1|0;k=(e[f+6>>1]|0)+(d[h>>0]|0)|0;a[h+g>>0]=k;g=(e[f+14>>1]|0)+k|0;a[h+j>>0]=g;g=(e[f+22>>1]|0)+g|0;a[h+i>>0]=g;a[h+c>>0]=(e[f+30>>1]|0)+g;c=f+32|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(c|0));return}function Kn(c,f,g){c=c|0;f=f|0;g=g|0;var h=0;h=(e[f>>1]|0)+(d[c+-1>>0]|0)|0;a[c>>0]=h;h=(e[f+2>>1]|0)+h|0;a[c+1>>0]=h;h=(e[f+4>>1]|0)+h|0;a[c+2>>0]=h;a[c+3>>0]=(e[f+6>>1]|0)+h;c=c+g|0;h=(e[f+8>>1]|0)+(d[c+-1>>0]|0)|0;a[c>>0]=h;h=(e[f+10>>1]|0)+h|0;a[c+1>>0]=h;h=(e[f+12>>1]|0)+h|0;a[c+2>>0]=h;a[c+3>>0]=(e[f+14>>1]|0)+h;c=c+g|0;h=(e[f+16>>1]|0)+(d[c+-1>>0]|0)|0;a[c>>0]=h;h=(e[f+18>>1]|0)+h|0;a[c+1>>0]=h;h=(e[f+20>>1]|0)+h|0;a[c+2>>0]=h;a[c+3>>0]=(e[f+22>>1]|0)+h;c=c+g|0;g=(e[f+24>>1]|0)+(d[c+-1>>0]|0)|0;a[c>>0]=g;g=(e[f+26>>1]|0)+g|0;a[c+1>>0]=g;g=(e[f+28>>1]|0)+g|0;a[c+2>>0]=g;a[c+3>>0]=(e[f+30>>1]|0)+g;c=f+32|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(c|0));return}function Ln(c,f,g){c=c|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g<<1;i=g*3|0;j=g<<2;k=g*5|0;l=g*6|0;m=g*7|0;n=g<<3;o=f;p=0;c=c+(0-g)|0;while(1){q=(e[o>>1]|0)+(d[c>>0]|0)|0;a[c+g>>0]=q;q=(e[o+16>>1]|0)+q|0;a[c+h>>0]=q;q=(e[o+32>>1]|0)+q|0;a[c+i>>0]=q;q=(e[o+48>>1]|0)+q|0;a[c+j>>0]=q;q=(e[o+64>>1]|0)+q|0;a[c+k>>0]=q;q=(e[o+80>>1]|0)+q|0;a[c+l>>0]=q;q=(e[o+96>>1]|0)+q|0;a[c+m>>0]=q;a[c+n>>0]=(e[o+112>>1]|0)+q;p=p+1|0;if((p|0)==8)break;else{o=o+2|0;c=c+1|0}}c=f+128|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(c|0));return}function Mn(c,f,g){c=c|0;f=f|0;g=g|0;var h=0,i=0,j=0;h=f;i=0;while(1){j=(e[h>>1]|0)+(d[c+-1>>0]|0)|0;a[c>>0]=j;j=(e[h+2>>1]|0)+j|0;a[c+1>>0]=j;j=(e[h+4>>1]|0)+j|0;a[c+2>>0]=j;j=(e[h+6>>1]|0)+j|0;a[c+3>>0]=j;j=(e[h+8>>1]|0)+j|0;a[c+4>>0]=j;j=(e[h+10>>1]|0)+j|0;a[c+5>>0]=j;j=(e[h+12>>1]|0)+j|0;a[c+6>>0]=j;a[c+7>>0]=(e[h+14>>1]|0)+j;i=i+1|0;if((i|0)==8)break;else{h=h+16|0;c=c+g|0}}c=f+128|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(c|0));return}function Nn(c,f,g,h,j){c=c|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=i;i=i+16|0;u=v;l=0-j|0;s=d[c+l>>0]|0;m=d[c+(1-j)>>0]|0;n=d[c+(2-j)>>0]|0;o=d[c+(3-j)>>0]|0;p=d[c+(4-j)>>0]|0;q=d[c+(5-j)>>0]|0;r=d[c+(6-j)>>0]|0;k=a[c+(7-j)>>0]|0;t=k&255;if(h)k=a[c+(8-j)>>0]|0;l=((d[c+((g|0)==0?l:~j)>>0]|0)+2+(s<<1)+m|0)>>>2&255;a[u>>0]=l;a[u+1>>0]=(s+2+(m<<1)+n|0)>>>2;a[u+2>>0]=(m+2+(n<<1)+o|0)>>>2;a[u+3>>0]=(n+2+(o<<1)+p|0)>>>2;a[u+4>>0]=(o+2+(p<<1)+q|0)>>>2;a[u+5>>0]=(p+2+(q<<1)+r|0)>>>2;a[u+6>>0]=(q+2+(r<<1)+t|0)>>>2;a[u+7>>0]=((k&255)+2+(t<<1)+r|0)>>>2;m=j<<1;n=j*3|0;o=j<<2;p=j*5|0;q=j*6|0;h=j*7|0;r=f;k=0;while(1){g=(e[r>>1]|0)+(l&255)|0;a[c>>0]=g;g=(e[r+16>>1]|0)+g|0;a[c+j>>0]=g;g=(e[r+32>>1]|0)+g|0;a[c+m>>0]=g;g=(e[r+48>>1]|0)+g|0;a[c+n>>0]=g;g=(e[r+64>>1]|0)+g|0;a[c+o>>0]=g;g=(e[r+80>>1]|0)+g|0;a[c+p>>0]=g;g=(e[r+96>>1]|0)+g|0;a[c+q>>0]=g;a[c+h>>0]=(e[r+112>>1]|0)+g;k=k+1|0;if((k|0)==8)break;l=a[u+k>>0]|0;r=r+2|0;c=c+1|0}k=f+128|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(k|0));i=v;return}function On(c,f,g,h,j){c=c|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;n=i;i=i+16|0;m=n;k=c+-1|0;t=d[k>>0]|0;s=d[c+(j+-1)>>0]|0;r=d[c+((j<<1)+-1)>>0]|0;q=d[c+((j*3|0)+-1)>>0]|0;p=d[c+((j<<2)+-1)>>0]|0;o=d[c+((j*5|0)+-1)>>0]|0;h=d[c+((j*6|0)+-1)>>0]|0;l=d[c+((j*7|0)+-1)>>0]|0;k=((d[((g|0)==0?k:c+~j|0)>>0]|0)+2+(t<<1)+s|0)>>>2&255;a[m>>0]=k;a[m+1>>0]=(t+2+(s<<1)+r|0)>>>2;a[m+2>>0]=(s+2+(r<<1)+q|0)>>>2;a[m+3>>0]=(r+2+(q<<1)+p|0)>>>2;a[m+4>>0]=(q+2+(p<<1)+o|0)>>>2;a[m+5>>0]=(p+2+(o<<1)+h|0)>>>2;a[m+6>>0]=(o+2+(h<<1)+l|0)>>>2;a[m+7>>0]=(h+2+(l*3|0)|0)>>>2;l=f;h=0;g=c;while(1){t=(e[l>>1]|0)+(k&255)|0;a[g>>0]=t;t=(e[l+2>>1]|0)+t|0;a[g+1>>0]=t;t=(e[l+4>>1]|0)+t|0;a[g+2>>0]=t;t=(e[l+6>>1]|0)+t|0;a[g+3>>0]=t;t=(e[l+8>>1]|0)+t|0;a[g+4>>0]=t;t=(e[l+10>>1]|0)+t|0;a[g+5>>0]=t;t=(e[l+12>>1]|0)+t|0;a[g+6>>0]=t;a[g+7>>0]=(e[l+14>>1]|0)+t;h=h+1|0;if((h|0)==8)break;k=a[m+h>>0]|0;l=l+16|0;g=g+j|0}h=f+128|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(h|0));i=n;return}function Pn(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Jn(a+(c[b>>2]|0)|0,d,e);Jn(a+(c[b+4>>2]|0)|0,d+32|0,e);Jn(a+(c[b+8>>2]|0)|0,d+64|0,e);Jn(a+(c[b+12>>2]|0)|0,d+96|0,e);return}function Qn(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Jn(a+(c[b>>2]|0)|0,d,e);Jn(a+(c[b+4>>2]|0)|0,d+32|0,e);Jn(a+(c[b+8>>2]|0)|0,d+64|0,e);Jn(a+(c[b+12>>2]|0)|0,d+96|0,e);Jn(a+(c[b+32>>2]|0)|0,d+128|0,e);Jn(a+(c[b+36>>2]|0)|0,d+160|0,e);Jn(a+(c[b+40>>2]|0)|0,d+192|0,e);Jn(a+(c[b+44>>2]|0)|0,d+224|0,e);return}function Rn(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Kn(a+(c[b>>2]|0)|0,d,e);Kn(a+(c[b+4>>2]|0)|0,d+32|0,e);Kn(a+(c[b+8>>2]|0)|0,d+64|0,e);Kn(a+(c[b+12>>2]|0)|0,d+96|0,e);return}function Sn(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Kn(a+(c[b>>2]|0)|0,d,e);Kn(a+(c[b+4>>2]|0)|0,d+32|0,e);Kn(a+(c[b+8>>2]|0)|0,d+64|0,e);Kn(a+(c[b+12>>2]|0)|0,d+96|0,e);Kn(a+(c[b+32>>2]|0)|0,d+128|0,e);Kn(a+(c[b+36>>2]|0)|0,d+160|0,e);Kn(a+(c[b+40>>2]|0)|0,d+192|0,e);Kn(a+(c[b+44>>2]|0)|0,d+224|0,e);return}function Tn(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Jn(a+(c[b>>2]|0)|0,d,e);Jn(a+(c[b+4>>2]|0)|0,d+32|0,e);Jn(a+(c[b+8>>2]|0)|0,d+64|0,e);Jn(a+(c[b+12>>2]|0)|0,d+96|0,e);Jn(a+(c[b+16>>2]|0)|0,d+128|0,e);Jn(a+(c[b+20>>2]|0)|0,d+160|0,e);Jn(a+(c[b+24>>2]|0)|0,d+192|0,e);Jn(a+(c[b+28>>2]|0)|0,d+224|0,e);Jn(a+(c[b+32>>2]|0)|0,d+256|0,e);Jn(a+(c[b+36>>2]|0)|0,d+288|0,e);Jn(a+(c[b+40>>2]|0)|0,d+320|0,e);Jn(a+(c[b+44>>2]|0)|0,d+352|0,e);Jn(a+(c[b+48>>2]|0)|0,d+384|0,e);Jn(a+(c[b+52>>2]|0)|0,d+416|0,e);Jn(a+(c[b+56>>2]|0)|0,d+448|0,e);Jn(a+(c[b+60>>2]|0)|0,d+480|0,e);return}function Un(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Kn(a+(c[b>>2]|0)|0,d,e);Kn(a+(c[b+4>>2]|0)|0,d+32|0,e);Kn(a+(c[b+8>>2]|0)|0,d+64|0,e);Kn(a+(c[b+12>>2]|0)|0,d+96|0,e);Kn(a+(c[b+16>>2]|0)|0,d+128|0,e);Kn(a+(c[b+20>>2]|0)|0,d+160|0,e);Kn(a+(c[b+24>>2]|0)|0,d+192|0,e);Kn(a+(c[b+28>>2]|0)|0,d+224|0,e);Kn(a+(c[b+32>>2]|0)|0,d+256|0,e);Kn(a+(c[b+36>>2]|0)|0,d+288|0,e);Kn(a+(c[b+40>>2]|0)|0,d+320|0,e);Kn(a+(c[b+44>>2]|0)|0,d+352|0,e);Kn(a+(c[b+48>>2]|0)|0,d+384|0,e);Kn(a+(c[b+52>>2]|0)|0,d+416|0,e);Kn(a+(c[b+56>>2]|0)|0,d+448|0,e);Kn(a+(c[b+60>>2]|0)|0,d+480|0,e);return}function Vn(a,b){a=a|0;b=b|0;switch(b|0){case 9:{c[a>>2]=243;c[a+4>>2]=244;c[a+8>>2]=245;c[a+12>>2]=246;c[a+16>>2]=247;c[a+20>>2]=248;c[a+24>>2]=249;c[a+28>>2]=250;c[a+32>>2]=251;c[a+36>>2]=252;c[a+40>>2]=253;c[a+44>>2]=254;c[a+48>>2]=255;c[a+52>>2]=256;c[a+56>>2]=257;c[a+60>>2]=258;c[a+64>>2]=259;c[a+68>>2]=260;c[a+72>>2]=261;c[a+76>>2]=262;c[a+80>>2]=263;c[a+84>>2]=264;c[a+88>>2]=265;c[a+92>>2]=266;c[a+96>>2]=267;c[a+100>>2]=268;c[a+104>>2]=269;c[a+108>>2]=270;c[a+112>>2]=271;c[a+116>>2]=272;c[a+120>>2]=273;c[a+124>>2]=274;c[a+128>>2]=275;c[a+132>>2]=276;c[a+136>>2]=277;c[a+140>>2]=278;c[a+144>>2]=279;c[a+148>>2]=280;c[a+152>>2]=281;c[a+156>>2]=282;c[a+160>>2]=283;c[a+164>>2]=284;c[a+168>>2]=285;c[a+172>>2]=286;c[a+176>>2]=287;c[a+180>>2]=288;c[a+184>>2]=289;c[a+188>>2]=290;c[a+192>>2]=291;c[a+196>>2]=292;c[a+200>>2]=293;c[a+204>>2]=294;c[a+208>>2]=295;c[a+212>>2]=296;c[a+216>>2]=297;c[a+220>>2]=298;c[a+224>>2]=299;c[a+228>>2]=300;c[a+232>>2]=301;c[a+236>>2]=302;c[a+240>>2]=303;c[a+244>>2]=304;c[a+248>>2]=305;c[a+252>>2]=306;c[a+256>>2]=307;c[a+260>>2]=308;c[a+264>>2]=309;c[a+268>>2]=310;c[a+272>>2]=311;c[a+276>>2]=312;c[a+280>>2]=313;c[a+284>>2]=314;c[a+288>>2]=315;c[a+292>>2]=316;c[a+296>>2]=317;c[a+300>>2]=318;c[a+304>>2]=319;c[a+308>>2]=320;c[a+312>>2]=321;c[a+316>>2]=322;c[a+320>>2]=323;c[a+324>>2]=324;c[a+328>>2]=325;c[a+332>>2]=326;c[a+336>>2]=327;c[a+340>>2]=328;c[a+344>>2]=329;c[a+348>>2]=330;c[a+352>>2]=331;c[a+356>>2]=332;c[a+360>>2]=333;c[a+364>>2]=334;c[a+368>>2]=335;c[a+372>>2]=336;c[a+376>>2]=337;c[a+380>>2]=338;c[a+384>>2]=339;c[a+388>>2]=340;c[a+392>>2]=341;c[a+396>>2]=342;c[a+400>>2]=343;c[a+404>>2]=344;c[a+408>>2]=345;c[a+412>>2]=346;c[a+416>>2]=347;c[a+420>>2]=348;c[a+424>>2]=349;c[a+428>>2]=350;c[a+432>>2]=351;c[a+436>>2]=352;c[a+440>>2]=353;c[a+444>>2]=354;return}case 10:{c[a>>2]=355;c[a+4>>2]=356;c[a+8>>2]=357;c[a+12>>2]=358;c[a+16>>2]=359;c[a+20>>2]=360;c[a+24>>2]=361;c[a+28>>2]=362;c[a+32>>2]=363;c[a+36>>2]=364;c[a+40>>2]=365;c[a+44>>2]=366;c[a+48>>2]=367;c[a+52>>2]=368;c[a+56>>2]=369;c[a+60>>2]=370;c[a+64>>2]=371;c[a+68>>2]=372;c[a+72>>2]=373;c[a+76>>2]=374;c[a+80>>2]=375;c[a+84>>2]=376;c[a+88>>2]=377;c[a+92>>2]=378;c[a+96>>2]=379;c[a+100>>2]=380;c[a+104>>2]=381;c[a+108>>2]=382;c[a+112>>2]=383;c[a+116>>2]=384;c[a+120>>2]=385;c[a+124>>2]=386;c[a+128>>2]=387;c[a+132>>2]=388;c[a+136>>2]=389;c[a+140>>2]=390;c[a+144>>2]=391;c[a+148>>2]=392;c[a+152>>2]=393;c[a+156>>2]=394;c[a+160>>2]=395;c[a+164>>2]=396;c[a+168>>2]=397;c[a+172>>2]=398;c[a+176>>2]=399;c[a+180>>2]=400;c[a+184>>2]=401;c[a+188>>2]=402;c[a+192>>2]=403;c[a+196>>2]=404;c[a+200>>2]=405;c[a+204>>2]=406;c[a+208>>2]=407;c[a+212>>2]=408;c[a+216>>2]=409;c[a+220>>2]=410;c[a+224>>2]=411;c[a+228>>2]=412;c[a+232>>2]=413;c[a+236>>2]=414;c[a+240>>2]=415;c[a+244>>2]=416;c[a+248>>2]=417;c[a+252>>2]=418;c[a+256>>2]=419;c[a+260>>2]=420;c[a+264>>2]=421;c[a+268>>2]=422;c[a+272>>2]=423;c[a+276>>2]=424;c[a+280>>2]=425;c[a+284>>2]=426;c[a+288>>2]=427;c[a+292>>2]=428;c[a+296>>2]=429;c[a+300>>2]=430;c[a+304>>2]=431;c[a+308>>2]=432;c[a+312>>2]=433;c[a+316>>2]=434;c[a+320>>2]=435;c[a+324>>2]=436;c[a+328>>2]=437;c[a+332>>2]=438;c[a+336>>2]=439;c[a+340>>2]=440;c[a+344>>2]=441;c[a+348>>2]=442;c[a+352>>2]=443;c[a+356>>2]=444;c[a+360>>2]=445;c[a+364>>2]=446;c[a+368>>2]=447;c[a+372>>2]=448;c[a+376>>2]=449;c[a+380>>2]=450;c[a+384>>2]=451;c[a+388>>2]=452;c[a+392>>2]=453;c[a+396>>2]=454;c[a+400>>2]=455;c[a+404>>2]=456;c[a+408>>2]=457;c[a+412>>2]=458;c[a+416>>2]=459;c[a+420>>2]=460;c[a+424>>2]=461;c[a+428>>2]=462;c[a+432>>2]=463;c[a+436>>2]=464;c[a+440>>2]=465;c[a+444>>2]=466;return}case 12:{c[a>>2]=467;c[a+4>>2]=468;c[a+8>>2]=469;c[a+12>>2]=470;c[a+16>>2]=471;c[a+20>>2]=472;c[a+24>>2]=473;c[a+28>>2]=474;c[a+32>>2]=475;c[a+36>>2]=476;c[a+40>>2]=477;c[a+44>>2]=478;c[a+48>>2]=479;c[a+52>>2]=480;c[a+56>>2]=481;c[a+60>>2]=482;c[a+64>>2]=483;c[a+68>>2]=484;c[a+72>>2]=485;c[a+76>>2]=486;c[a+80>>2]=487;c[a+84>>2]=488;c[a+88>>2]=489;c[a+92>>2]=490;c[a+96>>2]=491;c[a+100>>2]=492;c[a+104>>2]=493;c[a+108>>2]=494;c[a+112>>2]=495;c[a+116>>2]=496;c[a+120>>2]=497;c[a+124>>2]=498;c[a+128>>2]=499;c[a+132>>2]=500;c[a+136>>2]=501;c[a+140>>2]=502;c[a+144>>2]=503;c[a+148>>2]=504;c[a+152>>2]=505;c[a+156>>2]=506;c[a+160>>2]=507;c[a+164>>2]=508;c[a+168>>2]=509;c[a+172>>2]=510;c[a+176>>2]=511;c[a+180>>2]=512;c[a+184>>2]=513;c[a+188>>2]=514;c[a+192>>2]=515;c[a+196>>2]=516;c[a+200>>2]=517;c[a+204>>2]=518;c[a+208>>2]=519;c[a+212>>2]=520;c[a+216>>2]=521;c[a+220>>2]=522;c[a+224>>2]=523;c[a+228>>2]=524;c[a+232>>2]=525;c[a+236>>2]=526;c[a+240>>2]=527;c[a+244>>2]=528;c[a+248>>2]=529;c[a+252>>2]=530;c[a+256>>2]=531;c[a+260>>2]=532;c[a+264>>2]=533;c[a+268>>2]=534;c[a+272>>2]=535;c[a+276>>2]=536;c[a+280>>2]=537;c[a+284>>2]=538;c[a+288>>2]=539;c[a+292>>2]=540;c[a+296>>2]=541;c[a+300>>2]=542;c[a+304>>2]=543;c[a+308>>2]=544;c[a+312>>2]=545;c[a+316>>2]=546;c[a+320>>2]=547;c[a+324>>2]=548;c[a+328>>2]=549;c[a+332>>2]=550;c[a+336>>2]=551;c[a+340>>2]=552;c[a+344>>2]=553;c[a+348>>2]=554;c[a+352>>2]=555;c[a+356>>2]=556;c[a+360>>2]=557;c[a+364>>2]=558;c[a+368>>2]=559;c[a+372>>2]=560;c[a+376>>2]=561;c[a+380>>2]=562;c[a+384>>2]=563;c[a+388>>2]=564;c[a+392>>2]=565;c[a+396>>2]=566;c[a+400>>2]=567;c[a+404>>2]=568;c[a+408>>2]=569;c[a+412>>2]=570;c[a+416>>2]=571;c[a+420>>2]=572;c[a+424>>2]=573;c[a+428>>2]=574;c[a+432>>2]=575;c[a+436>>2]=576;c[a+440>>2]=577;c[a+444>>2]=578;return}case 14:{c[a>>2]=579;c[a+4>>2]=580;c[a+8>>2]=581;c[a+12>>2]=582;c[a+16>>2]=583;c[a+20>>2]=584;c[a+24>>2]=585;c[a+28>>2]=586;c[a+32>>2]=587;c[a+36>>2]=588;c[a+40>>2]=589;c[a+44>>2]=590;c[a+48>>2]=591;c[a+52>>2]=592;c[a+56>>2]=593;c[a+60>>2]=594;c[a+64>>2]=595;c[a+68>>2]=596;c[a+72>>2]=597;c[a+76>>2]=598;c[a+80>>2]=599;c[a+84>>2]=600;c[a+88>>2]=601;c[a+92>>2]=602;c[a+96>>2]=603;c[a+100>>2]=604;c[a+104>>2]=605;c[a+108>>2]=606;c[a+112>>2]=607;c[a+116>>2]=608;c[a+120>>2]=609;c[a+124>>2]=610;c[a+128>>2]=611;c[a+132>>2]=612;c[a+136>>2]=613;c[a+140>>2]=614;c[a+144>>2]=615;c[a+148>>2]=616;c[a+152>>2]=617;c[a+156>>2]=618;c[a+160>>2]=619;c[a+164>>2]=620;c[a+168>>2]=621;c[a+172>>2]=622;c[a+176>>2]=623;c[a+180>>2]=624;c[a+184>>2]=625;c[a+188>>2]=626;c[a+192>>2]=627;c[a+196>>2]=628;c[a+200>>2]=629;c[a+204>>2]=630;c[a+208>>2]=631;c[a+212>>2]=632;c[a+216>>2]=633;c[a+220>>2]=634;c[a+224>>2]=635;c[a+228>>2]=636;c[a+232>>2]=637;c[a+236>>2]=638;c[a+240>>2]=639;c[a+244>>2]=640;c[a+248>>2]=641;c[a+252>>2]=642;c[a+256>>2]=643;c[a+260>>2]=644;c[a+264>>2]=645;c[a+268>>2]=646;c[a+272>>2]=647;c[a+276>>2]=648;c[a+280>>2]=649;c[a+284>>2]=650;c[a+288>>2]=651;c[a+292>>2]=652;c[a+296>>2]=653;c[a+300>>2]=654;c[a+304>>2]=655;c[a+308>>2]=656;c[a+312>>2]=657;c[a+316>>2]=658;c[a+320>>2]=659;c[a+324>>2]=660;c[a+328>>2]=661;c[a+332>>2]=662;c[a+336>>2]=663;c[a+340>>2]=664;c[a+344>>2]=665;c[a+348>>2]=666;c[a+352>>2]=667;c[a+356>>2]=668;c[a+360>>2]=669;c[a+364>>2]=670;c[a+368>>2]=671;c[a+372>>2]=672;c[a+376>>2]=673;c[a+380>>2]=674;c[a+384>>2]=675;c[a+388>>2]=676;c[a+392>>2]=677;c[a+396>>2]=678;c[a+400>>2]=679;c[a+404>>2]=680;c[a+408>>2]=681;c[a+412>>2]=682;c[a+416>>2]=683;c[a+420>>2]=684;c[a+424>>2]=685;c[a+428>>2]=686;c[a+432>>2]=687;c[a+436>>2]=688;c[a+440>>2]=689;c[a+444>>2]=690;return}default:{c[a>>2]=131;c[a+4>>2]=132;c[a+8>>2]=133;c[a+12>>2]=134;c[a+16>>2]=135;c[a+20>>2]=136;c[a+24>>2]=137;c[a+28>>2]=138;c[a+32>>2]=139;c[a+36>>2]=140;c[a+40>>2]=141;c[a+44>>2]=142;c[a+48>>2]=143;c[a+52>>2]=144;c[a+56>>2]=145;c[a+60>>2]=146;c[a+64>>2]=147;c[a+68>>2]=148;c[a+72>>2]=149;c[a+76>>2]=150;c[a+80>>2]=151;c[a+84>>2]=152;c[a+88>>2]=153;c[a+92>>2]=154;c[a+96>>2]=155;c[a+100>>2]=156;c[a+104>>2]=157;c[a+108>>2]=158;c[a+112>>2]=159;c[a+116>>2]=160;c[a+120>>2]=161;c[a+124>>2]=162;c[a+128>>2]=163;c[a+132>>2]=164;c[a+136>>2]=165;c[a+140>>2]=166;c[a+144>>2]=167;c[a+148>>2]=168;c[a+152>>2]=169;c[a+156>>2]=170;c[a+160>>2]=171;c[a+164>>2]=172;c[a+168>>2]=173;c[a+172>>2]=174;c[a+176>>2]=175;c[a+180>>2]=176;c[a+184>>2]=177;c[a+188>>2]=178;c[a+192>>2]=179;c[a+196>>2]=180;c[a+200>>2]=181;c[a+204>>2]=182;c[a+208>>2]=183;c[a+212>>2]=184;c[a+216>>2]=185;c[a+220>>2]=186;c[a+224>>2]=187;c[a+228>>2]=188;c[a+232>>2]=189;c[a+236>>2]=190;c[a+240>>2]=191;c[a+244>>2]=192;c[a+248>>2]=193;c[a+252>>2]=194;c[a+256>>2]=195;c[a+260>>2]=196;c[a+264>>2]=197;c[a+268>>2]=198;c[a+272>>2]=199;c[a+276>>2]=200;c[a+280>>2]=201;c[a+284>>2]=202;c[a+288>>2]=203;c[a+292>>2]=204;c[a+296>>2]=205;c[a+300>>2]=206;c[a+304>>2]=207;c[a+308>>2]=208;c[a+312>>2]=209;c[a+316>>2]=210;c[a+320>>2]=211;c[a+324>>2]=212;c[a+328>>2]=213;c[a+332>>2]=214;c[a+336>>2]=215;c[a+340>>2]=216;c[a+344>>2]=217;c[a+348>>2]=218;c[a+352>>2]=219;c[a+356>>2]=220;c[a+360>>2]=221;c[a+364>>2]=222;c[a+368>>2]=223;c[a+372>>2]=224;c[a+376>>2]=225;c[a+380>>2]=226;c[a+384>>2]=227;c[a+388>>2]=228;c[a+392>>2]=229;c[a+396>>2]=230;c[a+400>>2]=231;c[a+404>>2]=232;c[a+408>>2]=233;c[a+412>>2]=234;c[a+416>>2]=235;c[a+420>>2]=236;c[a+424>>2]=237;c[a+428>>2]=238;c[a+432>>2]=239;c[a+436>>2]=240;c[a+440>>2]=241;c[a+444>>2]=242;return}}}function Wn(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;g=b+4|0;c[a+4>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;g=b+e|0;f=a+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;h=g+4|0;c[f+4>>2]=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;g=g+e|0;f=f+e|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;g=g+4|0;c[f+4>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;f=a+8|0;g=b+8|0;c[f>>2]=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;b=b+12|0;c[a+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=g+e|0;a=f+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;e=a+e|0;c[e>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[e+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;return}function Xn(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;k=i;i=i+256|0;f=k;Yn(f,b,16,e);h=f+8|0;j=b+8|0;Yn(h,j,16,e);g=b+(e<<3)|0;Yn(f+128|0,g,16,e);Yn(f+136|0,g+8|0,16,e);g=0;do{l=_(g,e)|0;m=b+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=g<<4;o=f+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[a+l>>2]=(o|m)-((o^m)>>>1&2139062143);l=l+4|0;m=b+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=f+(n|4)|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[a+l>>2]=(n|m)-((n^m)>>>1&2139062143);g=g+1|0}while((g|0)!=16);a=a+8|0;f=0;do{o=_(f,e)|0;n=j+o|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=f<<4;l=h+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+o>>2]=(l|n)-((l^n)>>>1&2139062143);o=o+4|0;n=j+o|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=h+(m|4)|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[a+o>>2]=(m|n)-((m^n)>>>1&2139062143);f=f+1|0}while((f|0)!=16);i=k;return}function Yn(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;g=0;while(1){j=c+1|0;i=c+-1|0;k=c+2|0;m=_((d[k>>0]|0)+(d[i>>0]|0)|0,-5)|0;l=c+3|0;m=(((d[j>>0]|0)+(d[c>>0]|0)|0)*20|0)+16+(d[c+-2>>0]|0)+m+(d[l>>0]|0)>>5;a[b>>0]=m>>>0>255?0-m>>31:m;m=_((d[l>>0]|0)+(d[c>>0]|0)|0,-5)|0;n=c+4|0;m=(((d[k>>0]|0)+(d[j>>0]|0)|0)*20|0)+16+(d[i>>0]|0)+m+(d[n>>0]|0)>>5;a[b+1>>0]=m>>>0>255?0-m>>31:m;m=_((d[n>>0]|0)+(d[j>>0]|0)|0,-5)|0;i=c+5|0;m=(((d[l>>0]|0)+(d[k>>0]|0)|0)*20|0)+16+(d[c>>0]|0)+m+(d[i>>0]|0)>>5;a[b+2>>0]=m>>>0>255?0-m>>31:m;m=_((d[i>>0]|0)+(d[k>>0]|0)|0,-5)|0;h=c+6|0;m=(((d[n>>0]|0)+(d[l>>0]|0)|0)*20|0)+16+(d[j>>0]|0)+m+(d[h>>0]|0)>>5;a[b+3>>0]=m>>>0>255?0-m>>31:m;m=_((d[h>>0]|0)+(d[l>>0]|0)|0,-5)|0;j=c+7|0;m=(((d[i>>0]|0)+(d[n>>0]|0)|0)*20|0)+16+(d[k>>0]|0)+m+(d[j>>0]|0)>>5;a[b+4>>0]=m>>>0>255?0-m>>31:m;m=_((d[j>>0]|0)+(d[n>>0]|0)|0,-5)|0;k=c+8|0;m=(((d[h>>0]|0)+(d[i>>0]|0)|0)*20|0)+16+(d[l>>0]|0)+m+(d[k>>0]|0)>>5;a[b+5>>0]=m>>>0>255?0-m>>31:m;m=_((d[k>>0]|0)+(d[i>>0]|0)|0,-5)|0;l=c+9|0;m=(((d[j>>0]|0)+(d[h>>0]|0)|0)*20|0)+16+(d[n>>0]|0)+m+(d[l>>0]|0)>>5;a[b+6>>0]=m>>>0>255?0-m>>31:m;h=_((d[l>>0]|0)+(d[h>>0]|0)|0,-5)|0;h=(((d[k>>0]|0)+(d[j>>0]|0)|0)*20|0)+16+(d[i>>0]|0)+h+(d[c+10>>0]|0)>>5;a[b+7>>0]=h>>>0>255?0-h>>31:h;g=g+1|0;if((g|0)==8)break;else{b=b+e|0;c=c+f|0}}return}function Zn(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;Yn(a,b,c,c);Yn(a+8|0,b+8|0,c,c);d=c<<3;b=b+d|0;a=a+d|0;Yn(a,b,c,c);Yn(a+8|0,b+8|0,c,c);return}function _n(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;k=i;i=i+256|0;g=k;Yn(g,b,16,e);j=g+8|0;Yn(j,b+8|0,16,e);f=b+(e<<3)|0;Yn(g+128|0,f,16,e);Yn(g+136|0,f+8|0,16,e);f=b+1|0;h=0;do{l=_(h,e)|0;m=f+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=h<<4;o=g+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[a+l>>2]=(o|m)-((o^m)>>>1&2139062143);l=l+4|0;m=f+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;n=g+(n|4)|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[a+l>>2]=(n|m)-((n^m)>>>1&2139062143);h=h+1|0}while((h|0)!=16);g=a+8|0;f=b+9|0;a=0;do{o=_(a,e)|0;n=f+o|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=a<<4;l=j+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[g+o>>2]=(l|n)-((l^n)>>>1&2139062143);o=o+4|0;n=f+o|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+(m|4)|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[g+o>>2]=(m|n)-((m^n)>>>1&2139062143);a=a+1|0}while((a|0)!=16);i=k;return}function $n(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;m=i;i=i+592|0;k=m+256|0;l=m;e=e+(0-(f<<1))|0;g=k;h=0;while(1){n=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=n;a[g+1>>0]=n>>8;a[g+2>>0]=n>>16;a[g+3>>0]=n>>24;n=e+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;j=e+8|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;n=g+8|0;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;n=e+12|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+12|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}g=k+32|0;ao(l,g,16,16);h=l+8|0;j=k+40|0;ao(h,j,16,16);ao(l+128|0,k+160|0,16,16);ao(l+136|0,k+168|0,16,16);e=0;do{o=e<<4;k=g+o|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=l+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(e,f)|0;c[b+n>>2]=(p|k)-((p^k)>>>1&2139062143);o=o|4;k=g+o|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;o=l+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[b+(n+4)>>2]=(o|k)-((o^k)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{n=g<<4;o=j+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=h+n|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=_(g,f)|0;c[e+p>>2]=(l|o)-((l^o)>>>1&2139062143);n=n|4;o=j+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=h+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[e+(p+4)>>2]=(n|o)-((n^o)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=m;return}function ao(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;h=_(f,-2)|0;i=0-f|0;q=f<<1;r=f*3|0;s=f<<2;t=f*5|0;u=f*6|0;v=f*7|0;w=f<<3;x=f*9|0;j=f*10|0;k=e<<1;l=e*3|0;m=e<<2;n=e*5|0;o=e*6|0;p=e*7|0;g=0;while(1){J=d[c+i>>0]|0;I=d[c>>0]|0;H=d[c+f>>0]|0;G=d[c+q>>0]|0;F=d[c+r>>0]|0;E=d[c+s>>0]|0;D=d[c+t>>0]|0;y=d[c+u>>0]|0;A=d[c+v>>0]|0;B=d[c+w>>0]|0;z=d[c+x>>0]|0;C=d[c+j>>0]|0;K=(d[c+h>>0]|0)+16+F+((H+I|0)*20|0)+(_(G+J|0,-5)|0)>>5;a[b>>0]=K>>>0>255?0-K>>31:K;J=J+16+E+((G+H|0)*20|0)+(_(F+I|0,-5)|0)>>5;a[b+e>>0]=J>>>0>255?0-J>>31:J;I=I+16+D+((F+G|0)*20|0)+(_(E+H|0,-5)|0)>>5;a[b+k>>0]=I>>>0>255?0-I>>31:I;H=H+16+y+((E+F|0)*20|0)+(_(D+G|0,-5)|0)>>5;a[b+l>>0]=H>>>0>255?0-H>>31:H;G=G+16+A+((D+E|0)*20|0)+(_(y+F|0,-5)|0)>>5;a[b+m>>0]=G>>>0>255?0-G>>31:G;F=F+16+B+((y+D|0)*20|0)+(_(A+E|0,-5)|0)>>5;a[b+n>>0]=F>>>0>255?0-F>>31:F;E=E+16+z+((A+y|0)*20|0)+(_(B+D|0,-5)|0)>>5;a[b+o>>0]=E>>>0>255?0-E>>31:E;y=D+16+C+((B+A|0)*20|0)+(_(z+y|0,-5)|0)>>5;a[b+p>>0]=y>>>0>255?0-y>>31:y;g=g+1|0;if((g|0)==8)break;else{b=b+1|0;c=c+1|0}}return}function bo(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;Yn(k,e,16,f);m=k+8|0;Yn(m,e+8|0,16,f);g=e+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{j=e<<4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;g=l+j|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=_(e,f)|0;c[b+p>>2]=(g|o)-((g^o)>>>1&2139062143);j=j|4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=l+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+(p+4)>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{l=g<<4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=h+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=_(g,f)|0;c[e+p>>2]=(k|o)-((k^o)>>>1&2139062143);l=l|4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=h+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[e+(p+4)>>2]=(l|o)-((l^o)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function co(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;k=i;i=i+1184|0;n=k;f=k+928|0;g=k+672|0;Yn(f,b,16,e);j=f+8|0;o=b+8|0;Yn(j,o,16,e);m=b+(e<<3)|0;Yn(f+128|0,m,16,e);l=m+8|0;Yn(f+136|0,l,16,e);eo(g,n,b,16,16,e);h=g+8|0;b=n+16|0;eo(h,b,o,16,16,e);eo(g+128|0,n,m,16,16,e);eo(g+136|0,b,l,16,16,e);b=0;do{m=b<<4;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=g+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=_(b,e)|0;c[a+o>>2]=(l|n)-((l^n)>>>1&2139062143);m=m|4;n=f+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=g+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[a+(o+4)>>2]=(m|n)-((m^n)>>>1&2139062143);b=b+1|0}while((b|0)!=16);b=a+8|0;a=0;do{m=a<<4;n=j+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=h+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=_(a,e)|0;c[b+o>>2]=(l|n)-((l^n)>>>1&2139062143);m=m|4;n=j+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=h+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+(o+4)>>2]=(m|n)-((m^n)>>>1&2139062143);a=a+1|0}while((a|0)!=16);i=k;return}function eo(c,e,f,g,h,i){c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;B=h*13|0;j=e;k=0;f=f+(0-(i<<1))|0;while(1){z=f+1|0;x=f+-1|0;y=f+2|0;v=_((d[y>>0]|0)+(d[x>>0]|0)|0,-5)|0;w=f+3|0;b[j>>1]=(d[f+-2>>0]|0)+(((d[z>>0]|0)+(d[f>>0]|0)|0)*20|0)+v+(d[w>>0]|0);v=_((d[w>>0]|0)+(d[f>>0]|0)|0,-5)|0;u=f+4|0;b[j+2>>1]=(d[x>>0]|0)+(((d[y>>0]|0)+(d[z>>0]|0)|0)*20|0)+v+(d[u>>0]|0);v=_((d[u>>0]|0)+(d[z>>0]|0)|0,-5)|0;x=f+5|0;b[j+4>>1]=(d[f>>0]|0)+(((d[w>>0]|0)+(d[y>>0]|0)|0)*20|0)+v+(d[x>>0]|0);v=_((d[x>>0]|0)+(d[y>>0]|0)|0,-5)|0;A=f+6|0;b[j+6>>1]=(d[z>>0]|0)+(((d[u>>0]|0)+(d[w>>0]|0)|0)*20|0)+v+(d[A>>0]|0);v=_((d[A>>0]|0)+(d[w>>0]|0)|0,-5)|0;z=f+7|0;b[j+8>>1]=(d[y>>0]|0)+(((d[x>>0]|0)+(d[u>>0]|0)|0)*20|0)+v+(d[z>>0]|0);v=_((d[z>>0]|0)+(d[u>>0]|0)|0,-5)|0;y=f+8|0;b[j+10>>1]=(d[w>>0]|0)+(((d[A>>0]|0)+(d[x>>0]|0)|0)*20|0)+v+(d[y>>0]|0);v=_((d[y>>0]|0)+(d[x>>0]|0)|0,-5)|0;w=f+9|0;b[j+12>>1]=(d[u>>0]|0)+(((d[z>>0]|0)+(d[A>>0]|0)|0)*20|0)+v+(d[w>>0]|0);A=_((d[w>>0]|0)+(d[A>>0]|0)|0,-5)|0;b[j+14>>1]=(d[x>>0]|0)+(((d[y>>0]|0)+(d[z>>0]|0)|0)*20|0)+A+(d[f+10>>0]|0);k=k+1|0;if((k|0)==13)break;else{j=j+(h<<1)|0;f=f+i|0}}i=_(h,-2)|0;l=0-h|0;m=h<<1;n=h*3|0;o=h<<2;p=h*5|0;q=h*6|0;r=h*7|0;s=h<<3;t=h*9|0;u=h*10|0;v=g<<1;w=g*3|0;x=g<<2;y=g*5|0;z=g*6|0;A=g*7|0;k=e+(B<<1)+((_(h,-11)|0)<<1)|0;f=c;j=0;while(1){K=b[k+(l<<1)>>1]|0;J=b[k>>1]|0;I=b[k+(h<<1)>>1]|0;H=b[k+(m<<1)>>1]|0;G=b[k+(n<<1)>>1]|0;F=b[k+(o<<1)>>1]|0;E=b[k+(p<<1)>>1]|0;c=b[k+(q<<1)>>1]|0;B=b[k+(r<<1)>>1]|0;C=b[k+(s<<1)>>1]|0;e=b[k+(t<<1)>>1]|0;D=b[k+(u<<1)>>1]|0;L=(b[k+(i<<1)>>1]|0)+512+G+((I+J|0)*20|0)+(_(H+K|0,-5)|0)>>10;a[f>>0]=L>>>0>255?0-L>>31:L;K=K+512+F+((H+I|0)*20|0)+(_(G+J|0,-5)|0)>>10;a[f+g>>0]=K>>>0>255?0-K>>31:K;J=J+512+E+((G+H|0)*20|0)+(_(F+I|0,-5)|0)>>10;a[f+v>>0]=J>>>0>255?0-J>>31:J;I=I+512+c+((F+G|0)*20|0)+(_(E+H|0,-5)|0)>>10;a[f+w>>0]=I>>>0>255?0-I>>31:I;H=H+512+B+((E+F|0)*20|0)+(_(c+G|0,-5)|0)>>10;a[f+x>>0]=H>>>0>255?0-H>>31:H;G=G+512+C+((c+E|0)*20|0)+(_(B+F|0,-5)|0)>>10;a[f+y>>0]=G>>>0>255?0-G>>31:G;F=F+512+e+((B+c|0)*20|0)+(_(C+E|0,-5)|0)>>10;a[f+z>>0]=F>>>0>255?0-F>>31:F;c=E+512+D+((C+B|0)*20|0)+(_(e+c|0,-5)|0)>>10;a[f+A>>0]=c>>>0>255?0-c>>31:c;j=j+1|0;if((j|0)==8)break;else{k=k+2|0;f=f+1|0}}return}function fo(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;Yn(k,e,16,f);m=k+8|0;Yn(m,e+8|0,16,f);g=e+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))+1|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{j=e<<4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;g=l+j|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=_(e,f)|0;c[b+p>>2]=(g|o)-((g^o)>>>1&2139062143);j=j|4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=l+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+(p+4)>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{l=g<<4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=h+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=_(g,f)|0;c[e+p>>2]=(k|o)-((k^o)>>>1&2139062143);l=l|4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=h+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[e+(p+4)>>2]=(l|o)-((l^o)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function go(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;j=i;i=i+336|0;h=j;c=c+(0-(e<<1))|0;f=h;g=0;while(1){l=d[c>>0]|d[c+1>>0]<<8|d[c+2>>0]<<16|d[c+3>>0]<<24;a[f>>0]=l;a[f+1>>0]=l>>8;a[f+2>>0]=l>>16;a[f+3>>0]=l>>24;l=c+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+4|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;k=c+8|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=f+8|0;a[l>>0]=k;a[l+1>>0]=k>>8;a[l+2>>0]=k>>16;a[l+3>>0]=k>>24;l=c+12|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+12|0;a[k>>0]=l;a[k+1>>0]=l>>8;a[k+2>>0]=l>>16;a[k+3>>0]=l>>24;g=g+1|0;if((g|0)==21)break;else{c=c+e|0;f=f+16|0}}ao(b,h+32|0,e,16);ao(b+8|0,h+40|0,e,16);l=b+(e<<3)|0;ao(l,h+160|0,e,16);ao(l+8|0,h+168|0,e,16);i=j;return}function ho(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=i;i=i+1520|0;k=p+1184|0;m=p;o=p+928|0;n=p+672|0;g=e+(0-(f<<1))|0;h=k;j=0;while(1){q=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a[h>>0]=q;a[h+1>>0]=q>>8;a[h+2>>0]=q>>16;a[h+3>>0]=q>>24;q=g+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;l=g+8|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=h+8|0;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;q=g+12|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+12|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+16|0}}ao(o,k+32|0,16,16);l=o+8|0;ao(l,k+40|0,16,16);ao(o+128|0,k+160|0,16,16);ao(o+136|0,k+168|0,16,16);eo(n,m,e,16,16,f);j=n+8|0;q=m+16|0;eo(j,q,e+8|0,16,16,f);g=e+(f<<3)|0;eo(n+128|0,m,g,16,16,f);eo(n+136|0,q,g+8|0,16,16,f);g=0;do{e=g<<4;m=o+e|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=n+e|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;q=_(g,f)|0;c[b+q>>2]=(k|m)-((k^m)>>>1&2139062143);e=e|4;m=o+e|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;e=n+e|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;c[b+(q+4)>>2]=(e|m)-((e^m)>>>1&2139062143);g=g+1|0}while((g|0)!=16);g=b+8|0;h=0;do{n=h<<4;o=l+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=j+n|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;q=_(h,f)|0;c[g+q>>2]=(b|o)-((b^o)>>>1&2139062143);n=n|4;o=l+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[g+(q+4)>>2]=(n|o)-((n^o)>>>1&2139062143);h=h+1|0}while((h|0)!=16);i=p;return}function io(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=i;i=i+672|0;f=d;eo(a,f,b,c,16,c);e=f+16|0;eo(a+8|0,e,b+8|0,c,16,c);g=c<<3;b=b+g|0;a=a+g|0;eo(a,f,b,c,16,c);eo(a+8|0,e,b+8|0,c,16,c);i=d;return}function jo(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=i;i=i+1520|0;k=p+1184|0;m=p;o=p+928|0;n=p+672|0;g=e+(0-(f<<1))+1|0;h=k;j=0;while(1){q=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;a[h>>0]=q;a[h+1>>0]=q>>8;a[h+2>>0]=q>>16;a[h+3>>0]=q>>24;q=g+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+4|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;l=g+8|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;q=h+8|0;a[q>>0]=l;a[q+1>>0]=l>>8;a[q+2>>0]=l>>16;a[q+3>>0]=l>>24;q=g+12|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;l=h+12|0;a[l>>0]=q;a[l+1>>0]=q>>8;a[l+2>>0]=q>>16;a[l+3>>0]=q>>24;j=j+1|0;if((j|0)==21)break;else{g=g+f|0;h=h+16|0}}ao(o,k+32|0,16,16);l=o+8|0;ao(l,k+40|0,16,16);ao(o+128|0,k+160|0,16,16);ao(o+136|0,k+168|0,16,16);eo(n,m,e,16,16,f);j=n+8|0;q=m+16|0;eo(j,q,e+8|0,16,16,f);g=e+(f<<3)|0;eo(n+128|0,m,g,16,16,f);eo(n+136|0,q,g+8|0,16,16,f);g=0;do{e=g<<4;m=o+e|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=n+e|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;q=_(g,f)|0;c[b+q>>2]=(k|m)-((k^m)>>>1&2139062143);e=e|4;m=o+e|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;e=n+e|0;e=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;c[b+(q+4)>>2]=(e|m)-((e^m)>>>1&2139062143);g=g+1|0}while((g|0)!=16);g=b+8|0;h=0;do{n=h<<4;o=l+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;b=j+n|0;b=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;q=_(h,f)|0;c[g+q>>2]=(b|o)-((b^o)>>>1&2139062143);n=n|4;o=l+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[g+(q+4)>>2]=(n|o)-((n^o)>>>1&2139062143);h=h+1|0}while((h|0)!=16);i=p;return}function ko(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;m=i;i=i+592|0;l=m+256|0;k=m;e=e+(0-(f<<1))|0;g=l;h=0;while(1){n=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=n;a[g+1>>0]=n>>8;a[g+2>>0]=n>>16;a[g+3>>0]=n>>24;n=e+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+4|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;j=e+8|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;n=g+8|0;a[n>>0]=j;a[n+1>>0]=j>>8;a[n+2>>0]=j>>16;a[n+3>>0]=j>>24;n=e+12|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;j=g+12|0;a[j>>0]=n;a[j+1>>0]=n>>8;a[j+2>>0]=n>>16;a[j+3>>0]=n>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(k,l+32|0,16,16);j=k+8|0;ao(j,l+40|0,16,16);ao(k+128|0,l+160|0,16,16);ao(k+136|0,l+168|0,16,16);e=l+48|0;g=0;do{o=g<<4;h=e+o|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;p=k+o|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;n=_(g,f)|0;c[b+n>>2]=(p|h)-((p^h)>>>1&2139062143);o=o|4;h=e+o|0;h=d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24;o=k+o|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;c[b+(n+4)>>2]=(o|h)-((o^h)>>>1&2139062143);g=g+1|0}while((g|0)!=16);h=b+8|0;e=l+56|0;g=0;do{n=g<<4;o=e+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=j+n|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;p=_(g,f)|0;c[h+p>>2]=(l|o)-((l^o)>>>1&2139062143);n=n|4;o=e+n|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=j+n|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[h+(p+4)>>2]=(n|o)-((n^o)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=m;return}function lo(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;g=e+f|0;Yn(k,g,16,f);m=k+8|0;Yn(m,g+8|0,16,f);g=g+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{j=e<<4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;g=l+j|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=_(e,f)|0;c[b+p>>2]=(g|o)-((g^o)>>>1&2139062143);j=j|4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=l+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+(p+4)>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{l=g<<4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=h+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=_(g,f)|0;c[e+p>>2]=(k|o)-((k^o)>>>1&2139062143);l=l|4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=h+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[e+(p+4)>>2]=(l|o)-((l^o)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function mo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;k=i;i=i+1184|0;m=k;f=k+928|0;g=k+672|0;j=b+e|0;Yn(f,j,16,e);h=f+8|0;Yn(h,j+8|0,16,e);n=e<<3;j=j+n|0;Yn(f+128|0,j,16,e);Yn(f+136|0,j+8|0,16,e);eo(g,m,b,16,16,e);j=g+8|0;l=m+16|0;eo(j,l,b+8|0,16,16,e);b=b+n|0;eo(g+128|0,m,b,16,16,e);eo(g+136|0,l,b+8|0,16,16,e);b=0;do{l=b<<4;m=f+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;o=g+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;n=_(b,e)|0;c[a+n>>2]=(o|m)-((o^m)>>>1&2139062143);l=l|4;m=f+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=g+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+(n+4)>>2]=(l|m)-((l^m)>>>1&2139062143);b=b+1|0}while((b|0)!=16);b=a+8|0;a=0;do{m=a<<4;n=h+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;l=j+m|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;o=_(a,e)|0;c[b+o>>2]=(l|n)-((l^n)>>>1&2139062143);m=m|4;n=h+m|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=j+m|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[b+(o+4)>>2]=(m|n)-((m^n)>>>1&2139062143);a=a+1|0}while((a|0)!=16);i=k;return}function no(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;n=i;i=i+848|0;j=n+512|0;k=n+256|0;l=n;g=e+f|0;Yn(k,g,16,f);m=k+8|0;Yn(m,g+8|0,16,f);g=g+(f<<3)|0;Yn(k+128|0,g,16,f);Yn(k+136|0,g+8|0,16,f);e=e+(0-(f<<1))+1|0;g=j;h=0;while(1){p=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;a[g>>0]=p;a[g+1>>0]=p>>8;a[g+2>>0]=p>>16;a[g+3>>0]=p>>24;p=e+4|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+4|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;o=e+8|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;p=g+8|0;a[p>>0]=o;a[p+1>>0]=o>>8;a[p+2>>0]=o>>16;a[p+3>>0]=o>>24;p=e+12|0;p=d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24;o=g+12|0;a[o>>0]=p;a[o+1>>0]=p>>8;a[o+2>>0]=p>>16;a[o+3>>0]=p>>24;h=h+1|0;if((h|0)==21)break;else{e=e+f|0;g=g+16|0}}ao(l,j+32|0,16,16);h=l+8|0;ao(h,j+40|0,16,16);ao(l+128|0,j+160|0,16,16);ao(l+136|0,j+168|0,16,16);e=0;do{j=e<<4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;g=l+j|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;p=_(e,f)|0;c[b+p>>2]=(g|o)-((g^o)>>>1&2139062143);j=j|4;o=k+j|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;j=l+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[b+(p+4)>>2]=(j|o)-((j^o)>>>1&2139062143);e=e+1|0}while((e|0)!=16);e=b+8|0;g=0;do{l=g<<4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;k=h+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;p=_(g,f)|0;c[e+p>>2]=(k|o)-((k^o)>>>1&2139062143);l=l|4;o=m+l|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;l=h+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[e+(p+4)>>2]=(l|o)-((l^o)>>>1&2139062143);g=g+1|0}while((g|0)!=16);i=n;return}function oo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;a=a+e|0;c[a>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;f=b+4|0;c[a+4>>2]=d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24;b=b+e|0;e=a+e|0;c[e>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[e+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;return}function po(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+64|0;f=h;Yn(f,b,8,e);g=0;do{j=_(g,e)|0;k=b+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=g<<3;m=f+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[a+j>>2]=(m|k)-((m^k)>>>1&2139062143);j=j+4|0;k=b+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=f+(l|4)|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+j>>2]=(l|k)-((l^k)>>>1&2139062143);g=g+1|0}while((g|0)!=8);i=h;return}function qo(a,b,c){a=a|0;b=b|0;c=c|0;Yn(a,b,c,c);return}function ro(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+64|0;g=h;Yn(g,b,8,e);b=b+1|0;f=0;do{j=_(f,e)|0;k=b+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=f<<3;m=g+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[a+j>>2]=(m|k)-((m^k)>>>1&2139062143);j=j+4|0;k=b+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=g+(l|4)|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+j>>2]=(l|k)-((l^k)>>>1&2139062143);f=f+1|0}while((f|0)!=8);i=h;return}function so(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+176|0;j=h;g=h+104|0;f=j+16|0;b=b+(0-(e<<1))|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+4>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+12>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[f>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+20>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+28>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+36>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+44>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+52>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+60>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+68>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+76>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+84>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=b+4|0;c[j+92>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=b+e|0;c[j+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[j+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,f,8,8);b=0;do{l=b<<3;j=c[f+l>>2]|0;m=g+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=_(b,e)|0;c[a+k>>2]=(m|j)-((m^j)>>>1&2139062143);l=l|4;j=c[f+l>>2]|0;l=g+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+(k+4)>>2]=(l|j)-((l^j)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function to(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b,8,e);b=b+(0-(e<<1))|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{j=b<<3;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+j|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=_(b,e)|0;c[a+l>>2]=(m|k)-((m^k)>>>1&2139062143);j=j|4;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=g+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[a+(l+4)>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function uo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+336|0;f=h+272|0;g=h+208|0;Yn(f,b,8,e);eo(g,h,b,8,8,e);b=0;do{l=b<<3;k=f+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+l|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;j=_(b,e)|0;c[a+j>>2]=(m|k)-((m^k)>>>1&2139062143);l=l|4;k=f+l|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=g+l|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[a+(j+4)>>2]=(l|k)-((l^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function vo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+240|0;k=h;f=h+168|0;g=h+104|0;j=k+16|0;Yn(f,b,8,e);b=b+(0-(e<<1))+1|0;c[k>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+4>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+12>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[j>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+20>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;l=b+4|0;c[k+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;b=b+e|0;c[k+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[k+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(g,j,8,8);b=0;do{j=b<<3;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;m=g+j|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=_(b,e)|0;c[a+l>>2]=(m|k)-((m^k)>>>1&2139062143);j=j|4;k=f+j|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;j=g+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[a+(l+4)>>2]=(j|k)-((j^k)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function wo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0;f=i;i=i+112|0;h=f;g=h+16|0;b=b+(0-(e<<1))|0;c[h>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+4>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+8>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+12>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[g>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+20>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+24>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+28>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+32>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+36>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+40>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+44>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+48>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+52>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+56>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+60>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+64>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+68>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+72>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+76>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+80>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+84>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+88>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;j=b+4|0;c[h+92>>2]=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;b=b+e|0;c[h+96>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=b+4|0;c[h+100>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;ao(a,g,e,8);i=f;return}function xo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+448|0;k=h;g=h+376|0;f=h+312|0;j=k+16|0;l=b+(0-(e<<1))|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+4>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+8>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+12>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+20>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+28>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+32>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+36>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+40>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+44>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+48>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+52>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+56>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+60>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+64>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+68>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+72>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+76>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+80>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+84>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+88>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+92>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+96>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+4|0;c[k+100>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ao(g,j,8,8);eo(f,h+104|0,b,8,8,e);b=0;do{k=b<<3;l=g+k|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=f+k|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=_(b,e)|0;c[a+m>>2]=(j|l)-((j^l)>>>1&2139062143);k=k|4;l=g+k|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+k|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[a+(m+4)>>2]=(k|l)-((k^l)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function yo(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=i;i=i+208|0;eo(a,d,b,c,8,c);i=d;return}function zo(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+448|0;k=h;g=h+376|0;f=h+312|0;j=k+16|0;l=b+(0-(e<<1))+1|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+4>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+8>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+12>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[j>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+20>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+24>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+28>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+32>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+36>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+40>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+44>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+48>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+52>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+56>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+60>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+64>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+68>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+72>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+76>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+80>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+84>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+88>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;m=l+4|0;c[k+92>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=l+e|0;c[k+96>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;l=l+4|0;c[k+100>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;ao(g,j,8,8);eo(f,h+104|0,b,8,8,e);b=0;do{k=b<<3;l=g+k|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;j=f+k|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;m=_(b,e)|0;c[a+m>>2]=(j|l)-((j^l)>>>1&2139062143);k=k|4;l=g+k|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=f+k|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[a+(m+4)>>2]=(k|l)-((k^l)>>>1&2139062143);b=b+1|0}while((b|0)!=8);i=h;return}function Ao(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0;h=i;i=i+176|0;j=h;g=h+104|0;f=j+16|0;k=b+(0-(e<<1))|0;c[j>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=k+4|0;c[j+4>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=k+e|0;c[j+8>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=k+4|0;c[j+12>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;k=k+e|0;c[f>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;b=k+4|0;c[j+20>>2]=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;b=j+24|0;k=k+e|0;c[b>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+28>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+32>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+36>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+40>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+44>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+48>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+52>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+56>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+60>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+64>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+68>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+72>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+76>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+80>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+84>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+88>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=k+4|0;c[j+92>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;k=k+e|0;c[j+96>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;k=k+4|0;c[j+100>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;ao(g,f,8,8);f=0;do{j=f<<3;k=c[b+j>>2]|0;m=g+j|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=_(f,e)|0;c[a+l>>2]=(m|k)-((m^k)>>>1&2139062143);j=j|4;k=c[b+j>>2]|0;j=g+j|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;c[a+(l+4)>>2]=(j|k)-((j^k)>>>1&2139062143);f=f+1|0}while((f|0)!=8);i=h;return}
function vb(a){a=a|0;var b=0;b=i;i=i+a|0;i=i+15&-16;return b|0}function wb(){return i|0}function xb(a){a=a|0;i=a}function yb(a,b){a=a|0;b=b|0;i=a;j=b}function zb(a,b){a=a|0;b=b|0;if(!n){n=a;o=b}}function Ab(b){b=b|0;a[k>>0]=a[b>>0];a[k+1>>0]=a[b+1>>0];a[k+2>>0]=a[b+2>>0];a[k+3>>0]=a[b+3>>0]}function Bb(b){b=b|0;a[k>>0]=a[b>>0];a[k+1>>0]=a[b+1>>0];a[k+2>>0]=a[b+2>>0];a[k+3>>0]=a[b+3>>0];a[k+4>>0]=a[b+4>>0];a[k+5>>0]=a[b+5>>0];a[k+6>>0]=a[b+6>>0];a[k+7>>0]=a[b+7>>0]}function Cb(a){a=a|0;C=a}function Db(){return C|0}function Eb(){if(c[26880]|0)return;c[26880]=1;Dz(33520);return}function Fb(a){a=a|0;var b=0;b=a+8|0;c[b>>2]=0;c[b+4>>2]=-2147483648;b=a+16|0;c[b>>2]=0;c[b+4>>2]=-2147483648;b=a+64|0;c[b>>2]=-1;c[b+4>>2]=-1;b=a+72|0;c[b>>2]=0;c[b+4>>2]=0;b=a+32|0;c[a>>2]=0;c[b>>2]=0;c[b+4>>2]=0;c[b+8>>2]=0;c[b+12>>2]=0;c[b+16>>2]=0;c[b+20>>2]=0;return}function Gb(a){a=a|0;a=i;i=i+16|0;c[a>>2]=83198;c[a+4>>2]=44600;c[a+8>>2]=45;UC(0,0,104466,a);Ya()}function Hb(a,b,d){a=a|0;b=b|0;d=d|0;var e=0;if((d|0)>2147483614){d=-22;return d|0}e=IB(b,d+32|0,1,0,0)|0;c[a>>2]=e;if(!e){e=-12;return e|0}c[a+24>>2]=b;c[a+28>>2]=d;c[a+52>>2]=2;e=0;return e|0}function Ib(a){a=a|0;var b=0,d=0;d=a+44|0;a=a+40|0;if((c[d>>2]|0)>0)b=0;else{bD(a);c[d>>2]=0;return}do{bD((c[a>>2]|0)+(b*12|0)|0);b=b+1|0}while((b|0)<(c[d>>2]|0));bD(a);c[d>>2]=0;return}function Jb(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0;e=c[a+44>>2]|0;if((e|0)<=0){g=0;return g|0}f=c[a+40>>2]|0;a=0;while(1){if((c[f+(a*12|0)+8>>2]|0)==(b|0))break;a=a+1|0;if((a|0)>=(e|0)){a=0;g=8;break}}if((g|0)==8)return a|0;if(d|0)c[d>>2]=c[f+(a*12|0)+4>>2];g=c[f+(a*12|0)>>2]|0;return g|0}function Kb(b){b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=i;i=i+16|0;r=s;n=b+44|0;if(c[n>>2]|0){r=0;i=s;return r|0}o=b+28|0;e=c[o>>2]|0;if((e|0)<=12){r=0;i=s;return r|0}p=b+24|0;h=c[p>>2]|0;e=h+e+-8|0;g=e;k=g;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;k=MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0;if(!((MF(g|0)|0)==-1910117890&(k|0)==-1941070576)){r=0;i=s;return r|0}f=e+-5|0;e=MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0;if((e|0)<0){r=0;i=s;return r|0}g=1;while(1){if((f-h|0)>>>0<e>>>0){l=0;k=20;break}if((a[f+4>>0]|0)<0){j=g;k=9;break}f=f+(-5-e)|0;e=MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0;if((e|0)<0){l=0;k=20;break}else g=g+1|0}if((k|0)==9){if(j>>>0>178956969){c[b+40>>2]=0;r=-12;i=s;return r|0}j=YC(j*12|0)|0;b=b+40|0;c[b>>2]=j;if(!j){r=-12;i=s;return r|0}e=c[p>>2]|0;g=e+(c[o>>2]|0)+-8+-5|0;f=MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0;a:do if((f|0)>-1)if((g-e|0)>>>0>=f>>>0){j=0;while(1){e=cD(f+32|0)|0;t=c[b>>2]|0;c[t+(j*12|0)>>2]=e;c[t+(j*12|0)+4>>2]=f;h=g+4|0;c[t+(j*12|0)+8>>2]=d[h>>0]&127;if(!e){l=-12;k=20;break}KF(e|0,g+(0-f)|0,f|0)|0;e=-5-f+(c[o>>2]|0)|0;c[o>>2]=e;if((a[h>>0]|0)<0){m=e;q=j;break}g=g+(-5-f)|0;f=MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0;if((f|0)<=-1)break a;if((g-(c[p>>2]|0)|0)>>>0<f>>>0)break a;else j=j+1|0}if((k|0)==20){i=s;return l|0}c[o>>2]=m+-8;c[n>>2]=q+1;t=1;i=s;return t|0}while(0);c[r>>2]=44622;c[r+4>>2]=44600;c[r+8>>2]=427;UC(0,0,104466,r);Ya()}else if((k|0)==20){i=s;return l|0}return 0}function Lb(b,c,d){b=b|0;c=c|0;d=d|0;var e=0,f=0;f=b+c|0;if(!((c|0)!=0&((b|0)!=0&(d|0)!=0))){d=0;return d|0}if(a[f+-1>>0]|0){d=-1094995529;return d|0}if((c|0)>0)c=b;else{d=0;return d|0}while(1){e=c+(aE(c)|0)+1|0;if(e>>>0>=f>>>0){b=-1094995529;c=7;break}b=YB(d,c,e,0)|0;if((b|0)<0){c=7;break}c=e+(aE(e)|0)+1|0;if(c>>>0>=f>>>0){c=7;break}}if((c|0)==7)return b|0;return 0}function Mb(a){a=a|0;var b=0,d=0;d=0;while(1){b=23576+(d*24|0)|0;d=d+1|0;if((c[b>>2]|0)==(a|0)){d=4;break}if(d>>>0>=409){b=0;d=4;break}}if((d|0)==4)return b|0;return 0}function Nb(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;m=i;i=i+16|0;l=m;j=c[a+60>>2]|0;h=j+4|0;c[h>>2]=a;c[j+25980>>2]=-1;c[j+25984>>2]=-1;c[j+25988>>2]=-1;c[j+199420>>2]=-1;c[j+26656>>2]=-1;c[j+203532>>2]=-1;c[j+197612>>2]=3;c[j+203256>>2]=1;c[j+26012>>2]=c[a+676>>2];c[j+26008>>2]=c[a+88>>2];c[j+199452>>2]=65536;c[j+198220>>2]=-1;c[j+203380>>2]=-1;c[j+203384>>2]=0;c[j+199468>>2]=-1;c[j+203360>>2]=-1;c[j+202436>>2]=-2147483648;c[j+202368>>2]=-2147483648;c[j+202372>>2]=-2147483648;c[j+202376>>2]=-2147483648;c[j+202380>>2]=-2147483648;c[j+202384>>2]=-2147483648;c[j+202388>>2]=-2147483648;c[j+202392>>2]=-2147483648;c[j+202396>>2]=-2147483648;c[j+202400>>2]=-2147483648;c[j+202404>>2]=-2147483648;c[j+202408>>2]=-2147483648;c[j+202412>>2]=-2147483648;c[j+202416>>2]=-2147483648;c[j+202420>>2]=-2147483648;c[j+202424>>2]=-2147483648;c[j+202428>>2]=-2147483648;Wc(j);c[a+400>>2]=1;k=a+816|0;d=c[k>>2]&2|0?32:1;e=j+25956|0;c[e>>2]=d;d=cD(d*49776|0)|0;f=j+25952|0;c[f>>2]=d;if(!d){c[e>>2]=0;l=-12;i=m;return l|0}b=zC()|0;d=0;do{c[j+716+(d*664|0)>>2]=b;d=d+1|0;if(!b){b=-12;g=32;break}b=zC()|0}while((d|0)<36);if((g|0)==32){i=m;return b|0}c[j+24624>>2]=b;if(!b){l=-12;i=m;return l|0}g=zC()|0;c[j+25288>>2]=g;if(!g){l=-12;i=m;return l|0}e=c[e>>2]|0;if((e|0)>0){b=c[f>>2]|0;d=0;do{c[b+(d*49776|0)>>2]=j;d=d+1|0}while((d|0)!=(e|0))}if(!(c[a+176>>2]|0))c[j+26e3>>2]=1;ic();nA();if((c[a+48>>2]|0)==28){b=a+112|0;do if((c[b>>2]|0)==1){d=c[h>>2]|0;e=d+108|0;f=c[e>>2]|0;if((f|0)<1073741823){c[e>>2]=f<<1;break}else{g=d+104|0;c[g>>2]=(c[g>>2]|0)/2|0;break}}while(0);c[b>>2]=2}b=c[a+100>>2]|0;if((b|0)>0){d=c[a+96>>2]|0;if(d|0){b=Ob(j,d,b)|0;if((b|0)<0){Tb(j);l=b;i=m;return l|0}}}if(c[j+27320>>2]|0){d=(c[h>>2]|0)+176|0;b=c[j+27324>>2]|0;if((c[d>>2]|0)<(b|0)){c[d>>2]=b;c[j+26e3>>2]=0}}c[(c[a+64>>2]|0)+4>>2]=1;Vb(j);b=j+203620|0;d=c[b>>2]|0;if((d|0)<0){if(c[k>>2]&2|0){c[b>>2]=0;l=0;i=m;return l|0}}else if(!d){l=0;i=m;return l|0}if(!(c[k>>2]&2)){l=0;i=m;return l|0}UC(a,24,58985,l);l=0;i=m;return l|0}function Ob(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;x=i;i=i+32|0;v=x+16|0;p=x+8|0;h=x;t=c[b+4>>2]|0;if((e|0)==0|(f|0)<1){f=-1;i=x;return f|0}g=b+198252|0;if((a[e>>0]|0)==1){c[g>>2]=1;if((f|0)<7){c[h>>2]=f;UC(t,16,58377,h);f=-1094995529;i=x;return f|0}q=b+198256|0;c[q>>2]=2;k=d[e+5>>0]&31;g=e+6|0;a:do if(k){l=e+f|0;m=0;while(1){h=(d[g>>0]|d[g+1>>0]<<8)&65535;h=(h<<8&65280|h>>>8)+2|0;if((h|0)>(l-g|0)){r=-1094995529;w=20;break}j=Pb(b,g,h,1)|0;if((j|0)<0){n=j;o=m;break}g=g+h|0;m=m+1|0;if((m|0)>=(k|0))break a}if((w|0)==20){i=x;return r|0}c[p>>2]=o;UC(t,16,58917,p);f=n;i=x;return f|0}while(0);p=a[g>>0]|0;k=p&255;b:do if(p<<24>>24){l=e+f|0;m=0;j=g+1|0;while(1){g=(d[j>>0]|d[j+1>>0]<<8)&65535;g=(g<<8&65280|g>>>8)+2|0;if((g|0)>(l-j|0)){r=-1094995529;w=20;break}h=Pb(b,j,g,1)|0;if((h|0)<0){s=h;u=m;break}m=m+1|0;if((m|0)>=(k|0))break b;else j=j+g|0}if((w|0)==20){i=x;return r|0}c[v>>2]=u;UC(t,16,58951,v);f=s;i=x;return f|0}while(0);c[q>>2]=(d[e+4>>0]&3)+1}else{c[g>>2]=0;g=Pb(b,e,f,1)|0;if((g|0)<0){f=g;i=x;return f|0}}i=x;return f|0}function Pb(b,e,f,g){b=b|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0,Aa=0,Ba=0,Ca=0,Da=0,Ea=0,Fa=0,Ga=0,Ha=0,Ia=0,Ja=0,Ka=0,La=0,Ma=0,Na=0,Oa=0,Pa=0,Qa=0,Ra=0,Sa=0,Ta=0,Ua=0,Va=0,Wa=0;Wa=i;i=i+144|0;Oa=Wa+120|0;Na=Wa+112|0;Ma=Wa+96|0;La=Wa+88|0;Ka=Wa+80|0;Ra=Wa+72|0;Qa=Wa+64|0;Ja=Wa+56|0;Ia=Wa+48|0;Pa=Wa+24|0;Ua=Wa+16|0;u=Wa+8|0;v=Wa;Ha=Wa+132|0;Ga=Wa+128|0;Fa=Wa+124|0;Va=b+4|0;Da=c[Va>>2]|0;Ea=b+198248|0;c[Ea>>2]=0;h=b+203256|0;k=c[h>>2]|0;if(!k){c[h>>2]=1;k=1}Ca=b+203252|0;c[Ca>>2]=k;Ba=Da+92|0;if(!(c[Ba>>2]&32768)){c[b+203248>>2]=0;if(!(c[b+197616>>2]|0))c[b+24620>>2]=0;Wc(b)}Aa=b+198256|0;do if((c[Aa>>2]|0)==4){if((f|0)>8){h=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24;if((h|0)==16777216){za=e+5|0;if((MF(d[za>>0]|d[za+1>>0]<<8|d[za+2>>0]<<16|d[za+3>>0]<<24|0)|0)>>>0>f>>>0){c[b+198252>>2]=0;break}else h=16777216}}else{if((f|0)<=3)break;h=d[e>>0]|d[e+1>>0]<<8|d[e+2>>0]<<16|d[e+3>>0]<<24}za=MF(h|0)|0;if(!(za>>>0<2|za>>>0>f>>>0))c[b+198252>>2]=1}while(0);za=Da+816|0;ya=b+198252|0;do if(!(c[za>>2]&1)){s=e;t=b+25952|0;wa=0}else{s=e;t=b+25952|0;o=0;r=0;q=0;h=0;l=c[ya>>2]|0?0:f;a:while(1){if((o|0)<(l|0)){c[Ha>>2]=-1;k=(eA(e+o|0,e+l+1|0,Ha)|0)-s+-1|0;k=(k|0)>(f|0)?f:k;if((k|0)>=(f|0))break;if((k|0)<(l|0)){n=k;p=l;Sa=29}else{m=r;n=q}}else{n=c[Aa>>2]|0;if((o|0)>=(f-n|0))break;if((n|0)>0){l=o;m=0;k=0}else{k=0;Sa=24;break}while(1){k=d[e+l>>0]|0|k<<8;m=m+1|0;if((m|0)==(n|0))break;else l=l+1|0}l=o+n|0;if((k|0)<1|(k|0)>(f-l|0)){Sa=24;break}if((k|0)<0)break;n=l;p=k+l|0;Sa=29}b:do if((Sa|0)==29){Sa=0;m=Qb(b,c[t>>2]|0,e+n|0,Ga,Fa,p-n|0)|0;l=c[Ga>>2]|0;if((m|0)==0|(l|0)<0){Sa=43;break a}k=(c[Fa>>2]|0)+n|0;Rb(b,e,m,l,k,p)|0;o=q+1|0;switch(c[Ea>>2]|0){case 8:case 7:{m=r;n=o;h=o;l=p;break b}case 1:case 5:case 2:break;default:{m=r;n=o;l=p;break b}}m=MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0;do if(m>>>0>134217727)l=d[87193+(m>>>23)>>0]|0;else{l=m>>>0>65535;wa=l?m>>>16:m;l=l?16:0;xa=(wa&65280|0)==0;l=((d[97868+(xa?wa:wa>>>8)>>0]|0)+(xa?l:l|8)<<1)+-31|0;if((l|0)<7){UC(0,16,66830,u);l=-1094995529;break}else{l=(m>>>l)+-1|0;break}}while(0);m=(r|0)!=0;if(m&(l|0)!=0){if((r|0)!=(c[Ea>>2]|0))Sa=39}else Sa=39;if((Sa|0)==39){Sa=0;h=o}if(m){m=r;n=o;l=p}else{m=c[Ea>>2]|0;n=o;l=p}}while(0);o=k;r=m;q=n}if((Sa|0)==24){xa=c[Va>>2]|0;c[v>>2]=k;UC(xa,16,58396,v)}else if((Sa|0)==43){wa=-1094995529;break}wa=h}while(0);ua=Da+856|0;va=b+198244|0;L=(g|0)==0;M=b+203248|0;N=b+199464|0;O=b+199468|0;P=b+199452|0;Q=b+199456|0;R=b+202368|0;S=b+202372|0;T=b+202376|0;U=b+202380|0;V=b+202384|0;W=b+202388|0;X=b+202392|0;Y=b+202396|0;Z=b+202400|0;_=b+202404|0;$=b+202408|0;aa=b+202412|0;ba=b+202416|0;ca=b+202420|0;da=b+202424|0;ea=b+202428|0;fa=b+203388|0;ga=b+28592|0;ha=b+203348|0;ia=b+199448|0;ja=b+203380|0;ka=b+26680|0;la=b+203352|0;xa=b+24620|0;ma=b+203384|0;na=Da+88|0;oa=Da+712|0;pa=b+696|0;qa=b+708|0;ra=b+712|0;sa=b+700|0;ta=b+704|0;h=0;u=0;r=0;q=0;k=c[ya>>2]|0?0:f;c:while(1){if((h|0)<(k|0)){c[Ha>>2]=-1;h=(eA(e+h|0,e+k+1|0,Ha)|0)-s+-1|0;h=(h|0)>(f|0)?f:h;if((h|0)>=(f|0)){j=u;Sa=132;break}if((h|0)<(k|0)){n=h;p=0;Sa=56}else{n=u;m=r;l=q}}else{n=c[Aa>>2]|0;if((h|0)>=(f-n|0)){j=u;Sa=132;break}if((n|0)>0){l=h;m=0;k=0}else{j=u;k=0;Sa=51;break}while(1){h=l+1|0;k=d[e+l>>0]|0|k<<8;m=m+1|0;if((m|0)>=(n|0))break;else l=h}if((k|0)<1|(k|0)>(f-h|0)){j=u;Sa=51;break}if((k|0)<0){j=u;Sa=132;break}n=h;p=k;k=k+h|0;Sa=56}d:do if((Sa|0)==56){Sa=0;o=(c[t>>2]|0)+(u*49776|0)|0;H=e+n|0;I=Qb(b,o,H,Fa,Ga,k-n|0)|0;l=c[Fa>>2]|0;if((I|0)==0|(l|0)<0){h=n;j=-1;Sa=130;break c}J=c[Ga>>2]|0;h=J+n|0;K=Rb(b,e,I,l,h,k)|0;m=c[Va>>2]|0;if(c[m+688>>2]&256|0){G=c[va>>2]|0;c[Pa>>2]=c[Ea>>2];c[Pa+4>>2]=G;c[Pa+8>>2]=n;c[Pa+12>>2]=f;c[Pa+16>>2]=l;UC(m,48,58414,Pa)}if(c[ya>>2]|0)if((p|0)!=0&(p|0)!=(J|0)){G=c[Va>>2]|0;c[Ia>>2]=J;c[Ia+4>>2]=p;UC(G,48,58444,Ia)}l=q+1|0;if((c[ua>>2]|0)>7)if(!(c[va>>2]|0))if((c[Ea>>2]|0)!=6){n=u;m=r;break}G=K>>>0<2147483640;F=G?K:0;G=G?I:0;D=F+8|0;E=G+(F+7>>3)|0;g=(wa|0)>(q|0);w=(l|0)>=(wa|0)&1;x=(p|0)!=0;B=k+-1-h+J|0;y=(B|0)>268435454;C=e+(h+1-J)|0;B=B<<3;z=(C|0)!=0&B>>>0<2147483640;B=z?B:0;C=z?C:0;z=B+8|0;A=C+(B>>3)|0;v=(k-h+J|0)==(p|0);n=u;m=r;u=o;while(1){o=c[Ea>>2]|0;e:do if(L){if((o|0)>=8){if((o|0)<12)switch(o|0){case 11:case 10:case 9:{o=0;break e}case 8:{Sa=115;break e}default:{Sa=117;break e}}if((o|0)<19)switch(o|0){case 13:case 12:{o=0;break e}default:{Sa=117;break e}}if((o|0)<267448321)switch(o|0){case 19:{o=0;break e}default:{Sa=117;break e}}else switch(o|0){case 267448321:{o=0;break e}default:{Sa=117;break e}}}switch(o|0){case 6:{Sa=106;break e}case 7:{Sa=108;break e}case 5:{Sa=71;break}case 1:break;case 4:case 3:case 2:{VC(Da,58739,Ka);o=0;break e}default:{Sa=117;break e}}if((Sa|0)==71){Sa=0;if((a[I>>0]&-4)<<24>>24==-104){Sa=72;break c}if(!m){if(c[M>>2]|0)if(c[za>>2]&2|0){Sa=76;break c}Sc(b);c[N>>2]=0;c[O>>2]=0;c[P>>2]=65536;c[Q>>2]=0;c[R>>2]=-2147483648;c[S>>2]=-2147483648;c[T>>2]=-2147483648;c[U>>2]=-2147483648;c[V>>2]=-2147483648;c[W>>2]=-2147483648;c[X>>2]=-2147483648;c[Y>>2]=-2147483648;c[Z>>2]=-2147483648;c[_>>2]=-2147483648;c[$>>2]=-2147483648;c[aa>>2]=-2147483648;c[ba>>2]=-2147483648;c[ca>>2]=-2147483648;c[da>>2]=-2147483648;c[ea>>2]=-2147483648}c[fa>>2]=1;m=1}c[u+4>>2]=G;c[u+16>>2]=F;c[u+20>>2]=D;c[u+8>>2]=E;c[u+12>>2]=0;if(g)Sa=81;else if(!(c[za>>2]&1|n))Sa=81;if((Sa|0)==81){Sa=0;c[ga>>2]=-1}o=$c(b,u)|0;if(!o){q=c[ha>>2]|0;do if((q|0)>-1){r=c[ia>>2]|0;if((r|0)==(q|0)){if((c[u+816>>2]|0)!=1)Sa=86}else Sa=86;if((Sa|0)==86){Sa=0;c[la>>2]=1}p=c[ja>>2]|0;if((p|0)<0)o=c[ka>>2]|0;else{o=c[ka>>2]|0;if(((1<<o)+-1&p-r)>>>0<=q>>>0)break}q=(1<<o)+-1&q+r;c[ja>>2]=q;c[ja>>2]=(c[la>>2]|0)==0?r:q}while(0);r=(c[Ea>>2]|0)==5;o=c[xa>>2]|0;q=(c[o>>2]|0)+84|0;c[q>>2]=c[q>>2]|r&1;if(r){c[ja>>2]=-1;c[o+640>>2]=1;q=c[ma>>2]|1;c[ma>>2]=q}else{if((c[ja>>2]|0)==(c[ia>>2]|0)){c[ja>>2]=-1;c[o+640>>2]=1}q=c[ma>>2]|0}p=c[Ba>>2]|0;q=0-((c[na>>2]|0)>>>3&1)&3|(0-(p>>>22&1)&3|q);c[ma>>2]=q;r=o+640|0;c[r>>2]=q|c[r>>2];do if((c[M>>2]|0)==1){if(!(p&32768))Sb(b,w);o=c[Va>>2]|0;p=c[o+712>>2]|0;if(!p)break;o=mb[c[p+28>>2]&7](o,e,f)|0;if((o|0)<0){j=o;Sa=130;break c}}while(0);if(c[u+21804>>2]|0){o=0;break}o=c[oa>>2]|0;if(!o){n=n+1|0;o=0;break}else{o=mb[c[o+32>>2]&7](Da,H,J)|0;if((o|0)<0){j=o;Sa=130;break c}else{o=0;break}}}}else{f:do if((o|0)<8){switch(o|0){case 6:{Sa=106;break e}case 7:{Sa=108;break e}case 4:case 3:case 2:case 1:case 5:break;default:{Sa=117;break e}}r=c[Va>>2]|0;c[Ja>>2]=o;UC(r,24,58487,Ja)}else{if((o|0)<12)switch(o|0){case 8:{Sa=115;break e}case 11:case 10:case 9:{o=0;break e}default:{Sa=117;break e}}if((o|0)<19)switch(o|0){case 13:case 12:{o=0;break e}default:{Sa=117;break e}}if((o|0)<267448321)switch(o|0){case 19:break f;default:{Sa=117;break e}}else switch(o|0){case 267448321:{o=0;break e}default:{Sa=117;break e}}}while(0);c[Ea>>2]=267448321;o=0}while(0);do if((Sa|0)==106){Sa=0;c[pa>>2]=G;c[qa>>2]=F;c[ra>>2]=D;c[sa>>2]=E;c[ta>>2]=0;o=Xc(b)|0;if((o|0)<0)if(!(c[(c[Va>>2]|0)+696>>2]&8))o=0;else{j=o;Sa=130;break c}else o=0}else if((Sa|0)==108){Sa=0;c[pa>>2]=G;c[qa>>2]=F;c[ra>>2]=D;c[sa>>2]=E;c[ta>>2]=0;if((Kc(b,0)|0)>-1)o=0;else{if(x|(c[ya>>2]|0)==0){UC(c[Va>>2]|0,48,58757,La);if(!((c[ya>>2]|0)==0|v)){Sa=111;break c}if(y){o=0;break}c[pa>>2]=C;c[qa>>2]=B;c[ra>>2]=z;c[sa>>2]=A;c[ta>>2]=0;if((Kc(b,0)|0)>-1){o=0;break}}c[pa>>2]=G;c[qa>>2]=F;c[ra>>2]=D;c[sa>>2]=E;c[ta>>2]=0;Kc(b,1)|0;o=0}}else if((Sa|0)==115){Sa=0;c[pa>>2]=G;c[qa>>2]=F;c[ra>>2]=D;c[sa>>2]=E;c[ta>>2]=0;o=Nc(b,K)|0;if((o|0)<0)if(!(c[(c[Va>>2]|0)+696>>2]&8))o=0;else{j=o;Sa=130;break c}else o=0}else if((Sa|0)==117){Sa=0;c[Na>>2]=o;c[Na+4>>2]=K;UC(Da,48,58858,Na);o=0}while(0);if((n|0)==(c[Ca>>2]|0)){n=dd(b,n)|0;if((n|0)<0)if(!(c[(c[Va>>2]|0)+696>>2]&8))n=0;else{j=n;Sa=130;break c}else n=0}p=(o|0)<0;if(p|(o|0)==2)break;if((o|0)!=1)break d;if(n>>>0>1){n=dd(b,n+-1|0)|0;if((n|0)<0)if(!(c[(c[Va>>2]|0)+696>>2]&8))n=0;else{j=n;Sa=130;break c}else n=0}u=c[t>>2]|0}if(p)UC(c[Va>>2]|0,16,58890,Oa);c[u+23380>>2]=0;c[u+23376>>2]=0;c[u+23372>>2]=0}while(0);u=n;r=m;q=l}if((Sa|0)==51){Ta=c[Va>>2]|0;c[Ua>>2]=k;UC(Ta,16,58396,Ua);Sa=132}else if((Sa|0)==72){UC(c[Va>>2]|0,16,58531,Qa);c[b+202436>>2]=-2147483648;j=-1;Sa=130}else if((Sa|0)==76){UC(b,16,58556,Ra);j=-1094995529;Sa=130}else if((Sa|0)==111){c[Ma>>2]=58815;c[Ma+4>>2]=58702;c[Ma+8>>2]=1611;UC(0,0,104466,Ma);Ya()}do if((Sa|0)==130)Ta=h;else if((Sa|0)==132){if(j|0){j=dd(b,j)|0;if((j|0)<0)if(c[(c[Va>>2]|0)+696>>2]&8|0){Ta=h;break}}Ta=h;j=0}while(0);h=c[xa>>2]|0;if(!h){b=(j|0)<0;b=b?j:Ta;i=Wa;return b|0}if(c[b+25992>>2]|0){b=(j|0)<0;b=b?j:Ta;i=Wa;return b|0}bA(h+4|0,2147483647,(c[b+197612>>2]|0)==2&1);b=(j|0)<0;b=b?j:Ta;i=Wa;return b|0}function Qb(b,e,f,g,h,i){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0;c[b+198244>>2]=(d[f>>0]|0)>>>5;c[b+198248>>2]=d[f>>0]&31;m=f+1|0;j=i+-1|0;a:do if((j|0)>1){f=0;while(1){if(!(a[m+f>>0]|0)){if((f|0)>0){l=f+-1|0;f=(a[m+l>>0]|0)==0?l:f}i=f+2|0;if((i|0)<(j|0))if(!(a[m+(f+1)>>0]|0)){i=a[m+i>>0]|0;if((i&255)<4)break;else i=f}else i=f;else i=f}else i=f;f=i+2|0;if((i+3|0)>=(j|0)){i=f;break a}}switch(i<<24>>24){case 0:case 3:{i=f;break a}default:{}}j=f;i=f}else i=0;while(0);l=e+49756|0;Bz(l,e+49760|0,j+262144|0);l=c[l>>2]|0;if(!l){g=0;return g|0}if((i|0)>=(j+-1|0)){c[g>>2]=j;c[h>>2]=j+1;if(c[(c[b+4>>2]|0)+92>>2]&1|0){g=m;return g|0}KF(l|0,m|0,j|0)|0;g=l;return g|0}KF(l|0,m|0,i|0)|0;f=i+2|0;b:do if((f|0)<(j|0)){e=i;b=i;while(1){i=m+f|0;k=a[i>>0]|0;if((k&255)>3){a[l+e>>0]=a[m+b>>0]|0;a[l+(e+1)>>0]=a[m+(b+1)>>0]|0;k=a[i>>0]|0;i=e+2|0;n=25}else{f=a[m+b>>0]|0;if(!(f<<24>>24))if(k<<24>>24==0?1:(a[m+(b+1)>>0]|0)!=0){k=0;i=e;f=b;n=25}else{if(k<<24>>24!=3){j=b;break b}a[l+e>>0]=0;a[l+(e+1)>>0]=0;i=e+2|0;b=b+3|0}else{k=f;i=e;f=b;n=25}}if((n|0)==25){n=0;a[l+i>>0]=k;i=i+1|0;b=f+1|0}f=b+2|0;if((f|0)>=(j|0)){f=i;n=16;break}else e=i}}else{f=i;b=i;n=16}while(0);if((n|0)==16)if((b|0)<(j|0)){e=j+f|0;i=b;while(1){a[l+f>>0]=a[m+i>>0]|0;i=i+1|0;if((i|0)==(j|0))break;else f=f+1|0}e=e-b|0}else{e=f;j=b}f=l+e|0;i=f+32|0;do{a[f>>0]=0;f=f+1|0}while((f|0)<(i|0));c[g>>2]=e;c[h>>2]=j+1;g=l;return g|0}function Rb(b,e,f,g,h,i){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0;j=b+26012|0;b=c[j>>2]|0;if(b&1){k=h+3|0;if((k|0)<(i|0))if(!(a[e+h>>0]|0))if(!(a[e+(h+1)>>0]|0))if((a[e+(h+2)>>0]|0)==1)if((a[e+k>>0]|0)==-32){b=b|16384;c[j>>2]=b}}a:do if((g|0)>0&(b&16384|0)==0)while(1){b=g+-1|0;if(a[f+b>>0]|0)break a;if((g|0)>1)g=b;else{g=b;break}}while(0);if(!g){f=0;return f|0}b=g<<3;g=d[f+g+-1>>0]|0;if(!(g&1))if(!(g&2))if(!(g&4))if(!(g&8))if(!(g&16))if(!(g&32))if(!(g&64))g=g>>>4&8;else g=7;else g=6;else g=5;else g=4;else g=3;else g=2;else g=1;f=b-g|0;return f|0}function Sb(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+48|0;t=u+32|0;q=u+16|0;n=u+8|0;m=u;p=c[b+24620>>2]|0;l=c[p>>2]|0;c[l+88>>2]=c[b+203264>>2];s=b+202432|0;if(c[s>>2]|0){i=u;return}k=p+72|0;e=(c[p+76>>2]|0)==2147483647;if((c[k>>2]|0)==2147483647)if(e){i=u;return}else o=5;else if(e)o=5;if((o|0)==5){if(c[(c[b+4>>2]|0)+712>>2]|0){i=u;return}if((c[b+203392>>2]|0)<2){i=u;return}}h=l+296|0;c[h>>2]=0;e=l+292|0;c[e>>2]=0;j=(c[b+27820>>2]|0)==0;if(j){if(!(c[b+197608>>2]|0))e=(c[b+197612>>2]|0)!=3;else e=1;g=e&1;c[h>>2]=g}else{f=c[b+203280>>2]|0;a:do switch(f|0){case 8:{c[e>>2]=4;g=0;break}case 2:case 1:{c[h>>2]=1;g=1;break}case 4:case 3:{if(!(c[b+197608>>2]|0))if((c[b+197612>>2]|0)==3){g=c[b+203284>>2]|0;c[h>>2]=g;break a}c[h>>2]=1;g=1;break}case 6:case 5:{c[e>>2]=1;g=0;break}case 7:{c[e>>2]=2;g=0;break}default:g=0}while(0);e=c[b+203336>>2]|0;if((e&3|0)!=0&f>>>0<5){g=e>>>1&1;c[h>>2]=g}}c[b+203284>>2]=g;e=c[k>>2]|0;f=c[p+76>>2]|0;b:do if((e|0)==(f|0)){if(!((g|0)!=0|j^1)){c[l+300>>2]=0;break}switch(c[b+203280>>2]|0){case 5:case 3:{c[l+300>>2]=1;break b}default:{c[l+300>>2]=0;break b}}}else c[l+300>>2]=(e|0)<(f|0)&1;while(0);if(c[b+203288>>2]|0){f=b+203292|0;if((c[f>>2]|0)>>>0<7){g=b+203296|0;if(((c[g>>2]|0)+-1|0)>>>0<2){e=QD(l)|0;if(e|0){c:do switch(c[f>>2]|0){case 0:{c[e>>2]=4;break}case 1:{c[e>>2]=7;break}case 2:{c[e>>2]=6;break}case 3:if(!(c[b+203300>>2]|0)){c[e>>2]=1;break c}else{c[e>>2]=5;break c}case 4:{c[e>>2]=2;break}case 5:{c[e>>2]=3;break}case 6:{c[e>>2]=0;break}default:{}}while(0);if((c[g>>2]|0)==2)c[e+4>>2]=1}}}}do if(c[b+203304>>2]|0){e=c[b+203308>>2]|0;if(!e)if(!(c[b+203312>>2]|0))if(!(c[b+203316>>2]|0))break;f=FC(c[p>>2]|0,6,36)|0;if(f|0){r=f+4|0;$B(c[r>>2]|0,+(e*360|0)*.0000152587890625);aC(c[r>>2]|0,c[b+203312>>2]|0,c[b+203316>>2]|0)}}while(0);e=b+203320|0;if(c[e>>2]|0){f=FC(c[p>>2]|0,7,1)|0;if(f|0){a[c[f+4>>2]>>0]=a[b+203324>>0]|0;c[e>>2]=0}}e=b+203332|0;if(c[e>>2]|0){f=b+203328|0;g=FC(c[p>>2]|0,1,c[f>>2]|0)|0;if(g|0)KF(c[g+4>>2]|0,c[e>>2]|0,c[f>>2]|0)|0;bD(e);c[f>>2]=0;r=(c[b+4>>2]|0)+980|0;c[r>>2]=c[r>>2]|2}k=b+203236|0;j=p+88|0;c[j>>2]=c[k>>2];c[k>>2]=0;k=b+27320|0;f=(c[k>>2]|0)==0;r=b+4|0;e=c[r>>2]|0;if(!f){g=e+176|0;h=c[b+27324>>2]|0;if((c[g>>2]|0)<(h|0)){c[g>>2]=h;c[b+26e3>>2]=0}}if(!((c[e+680>>2]|0)<1|f^1)){c[e+176>>2]=15;c[b+26e3>>2]=0}h=p+80|0;e=c[h>>2]|0;f=c[b+202368>>2]|0;d:do if((e|0)<(f|0)){g=e;f=1;e=0;o=64}else{g=1;e=0;while(1){if(g)e=1;else{c[b+202368+(e+-1<<2)>>2]=f;e=e+1|0;if((e|0)==16)break}g=c[h>>2]|0;f=c[b+202368+(e<<2)>>2]|0;if((g|0)<(f|0)){f=0;o=64;break d}else g=0}g=c[h>>2]|0;e=16;o=66}while(0);if((o|0)==64)if(f)g=0;else o=66;if((o|0)==66){c[b+202368+(e+-1<<2)>>2]=g;g=e}e=16-g|0;if((c[(c[p>>2]|0)+88>>2]|0)==3)if((e|0)>1)o=75;else o=74;else{f=c[b+202424>>2]|0;if((f|0)==-2147483648)o=75;else if((e|0)>1?1:((c[b+202428>>2]|0)-f|0)<3)o=75;else o=74}if((o|0)==74){f=c[r>>2]|0;e=1;o=77}else if((o|0)==75){f=c[r>>2]|0;if(!g){n=b+202368|0;l=c[n>>2]|0;c[m>>2]=c[h>>2];c[m+4>>2]=l;UC(f,40,58639,m);c[b+202372>>2]=-2147483648;c[b+202376>>2]=-2147483648;c[b+202380>>2]=-2147483648;c[b+202384>>2]=-2147483648;c[b+202388>>2]=-2147483648;c[b+202392>>2]=-2147483648;c[b+202396>>2]=-2147483648;c[b+202400>>2]=-2147483648;c[b+202404>>2]=-2147483648;c[b+202408>>2]=-2147483648;c[b+202412>>2]=-2147483648;c[b+202416>>2]=-2147483648;c[b+202420>>2]=-2147483648;c[b+202424>>2]=-2147483648;c[b+202428>>2]=-2147483648;c[n>>2]=c[h>>2];c[j>>2]=1;f=0}else o=77}if((o|0)==77)if((c[f+176>>2]|0)<(e|0))if(!(c[k>>2]|0)){c[n>>2]=e;UC(f,40,58658,n);c[(c[r>>2]|0)+176>>2]=e;c[b+26e3>>2]=0;f=0}else f=0;else f=0;while(1){e=b+202296+(f<<2)|0;if(!(c[e>>2]|0))break;else f=f+1|0}if((f|0)>=17){c[q>>2]=58691;c[q+4>>2]=58702;c[q+8>>2]=941;UC(0,0,104466,q);Ya()}c[e>>2]=p;e=p+636|0;if(!(c[e>>2]|0))c[e>>2]=4;n=b+202296|0;k=c[n>>2]|0;e=c[b+202300>>2]|0;e:do if(!e){e=k;m=0}else{h=e;j=1;e=k;g=0;while(1){if(c[(c[h>>2]|0)+84>>2]|0){m=g;break e}if(c[h+88>>2]|0){m=g;break e}q=(c[h+80>>2]|0)<(c[e+80>>2]|0);g=q?j:g;e=q?h:e;j=j+1|0;h=c[b+202296+(j<<2)>>2]|0;if(!h){m=g;break}}}while(0);g=c[r>>2]|0;h=c[g+176>>2]|0;do if(!h){if(!(c[(c[k>>2]|0)+84>>2]|0))if(!(c[k+88>>2]|0))break;c[b+202436>>2]=-2147483648}while(0);j=c[e+80>>2]|0;k=b+202436|0;l=(j|0)<(c[k>>2]|0);do if((f|0)<(h|0)&(l^1))o=99;else{g=e+636|0;c[g>>2]=c[g>>2]&-5;g=b+202296+(m<<2)|0;if(c[g>>2]|0){h=m;do{h=h+1|0;q=g;g=b+202296+(h<<2)|0;c[q>>2]=c[g>>2]}while((c[g>>2]|0)!=0)}if(l){e=c[r>>2]|0;o=107;break}else{h=c[r>>2]|0;g=h;h=c[h+176>>2]|0;o=99;break}}while(0);f:do if((o|0)==99){if((f|0)<(h|0)){e=g;o=107;break}c[s>>2]=e;do if(!m){f=c[n>>2]|0;if(!f)break;if(!(c[(c[f>>2]|0)+84>>2]|0))if(!(c[f+88>>2]|0))break;c[k>>2]=-2147483648;break f}while(0);c[k>>2]=j}while(0);if((o|0)==107){c[t>>2]=l?58720:190399;UC(e,48,58724,t);e=c[s>>2]|0}if(e|0){g=e+640|0;h=c[g>>2]|0;f=b+203384|0;e=c[f>>2]|0;if(h){e=e|2;c[f>>2]=e}c[g>>2]=h|e>>>1&1}if(!d){i=u;return}e=c[r>>2]|0;if(c[e+712>>2]|0){i=u;return}aA(e);if(!(c[(c[r>>2]|0)+816>>2]&1)){i=u;return}c[b+203396>>2]=1;i=u;return}function Tb(a){a=a|0;var b=0,d=0,e=0;Ub(a);b=0;do{e=a+716+(b*664|0)|0;Gc(a,e);AC(e);b=b+1|0}while((b|0)!=36);b=a+202296|0;d=b+72|0;do{c[b>>2]=0;b=b+4|0}while((b|0)<(d|0));c[a+24620>>2]=0;b=a+25956|0;d=a+25952|0;if((c[b>>2]|0)>0){e=0;do{bD((c[d>>2]|0)+(e*49776|0)+49756|0);e=e+1|0}while((e|0)<(c[b>>2]|0))}bD(d);c[b>>2]=0;bD(a+198268|0);bD(a+198272|0);bD(a+198276|0);bD(a+198280|0);bD(a+198284|0);bD(a+198288|0);bD(a+198292|0);bD(a+198296|0);bD(a+198300|0);bD(a+198304|0);bD(a+198308|0);bD(a+198312|0);bD(a+198316|0);bD(a+198320|0);bD(a+198324|0);bD(a+198328|0);bD(a+198332|0);bD(a+198336|0);bD(a+198340|0);bD(a+198344|0);bD(a+198348|0);bD(a+198352|0);bD(a+198356|0);bD(a+198360|0);bD(a+198364|0);bD(a+198368|0);bD(a+198372|0);bD(a+198376|0);bD(a+198380|0);bD(a+198384|0);bD(a+198388|0);bD(a+198392|0);b=0;do{bD(a+198396+(b<<2)|0);b=b+1|0}while((b|0)!=256);return}function Ub(a){a=a|0;var b=0,d=0,e=0;bD(a+26016|0);bD(a+197628|0);bD(a+197624|0);bD(a+197632|0);bD(a+197636|0);bD(a+197640|0);bD(a+26256|0);bD(a+199424|0);c[a+197604>>2]=0;bD(a+197620|0);bD(a+26644|0);bD(a+26648|0);QB(a+203624|0);QB(a+203628|0);QB(a+203632|0);QB(a+203636|0);d=a+25956|0;if((c[d>>2]|0)<=0)return;a=a+25952|0;b=0;do{e=c[a>>2]|0;bD(e+(b*49776|0)+43612|0);bD(e+(b*49776|0)+548|0);bD(e+(b*49776|0)+580|0);bD(e+(b*49776|0)+584|0);bD(e+(b*49776|0)+43616|0);bD(e+(b*49776|0)+43620|0);bD(e+(b*49776|0)+43624|0);bD(e+(b*49776|0)+43628|0);e=e+(b*49776|0)+43632|0;b=b+1|0;c[e>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0}while((b|0)<(c[d>>2]|0));return}function Vb(a){a=a|0;var b=0,d=0,e=0,f=0,g=0;c[a+202436>>2]=-2147483648;c[a+203284>>2]=1;Sc(a);c[a+199464>>2]=0;c[a+199452>>2]=65536;c[a+199456>>2]=0;c[a+202368>>2]=-2147483648;c[a+202372>>2]=-2147483648;c[a+202376>>2]=-2147483648;c[a+202380>>2]=-2147483648;c[a+202384>>2]=-2147483648;c[a+202388>>2]=-2147483648;c[a+202392>>2]=-2147483648;c[a+202396>>2]=-2147483648;c[a+202400>>2]=-2147483648;c[a+202404>>2]=-2147483648;c[a+202408>>2]=-2147483648;c[a+202412>>2]=-2147483648;c[a+202416>>2]=-2147483648;c[a+202420>>2]=-2147483648;c[a+202424>>2]=-2147483648;c[a+202428>>2]=-2147483648;c[a+199468>>2]=-1;g=a+24620|0;d=c[g>>2]|0;if(d|0){c[d+636>>2]=0;b=c[a+202296>>2]|0;a:do if(!b)b=0;else{f=b;e=0;b=0;while(1){if((f|0)!=(d|0)){c[a+202296+(b<<2)>>2]=f;b=b+1|0}e=e+1|0;d=c[a+202296+(e<<2)>>2]|0;if(!d)break a;f=d;d=c[g>>2]|0}}while(0);c[a+202296+(b<<2)>>2]=0}Gc(a,a+25288|0);c[a+197616>>2]=0;Wc(a);c[a+203380>>2]=-1;c[a+203384>>2]=0;c[a+203248>>2]=0;c[a+203236>>2]=1;e=c[a+25956>>2]|0;if((e|0)<=0)return;b=c[a+25952>>2]|0;d=0;do{c[b+(d*49776|0)+23380>>2]=0;d=d+1|0}while((d|0)<(e|0));return}function Wb(b,e,f,g){b=b|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0;B=i;i=i+96|0;z=B+32|0;v=B+24|0;s=B+16|0;r=B;x=B+80|0;j=B+64|0;w=B+48|0;q=c[g+24>>2]|0;y=c[g+28>>2]|0;A=c[b+60>>2]|0;c[A+26008>>2]=c[b+88>>2];c[A+203396>>2]=0;h=A+25980|0;k=c[h>>2]|0;if((k|0)!=-1){c[b+120>>2]=k;c[h>>2]=-1}h=A+25984|0;k=c[h>>2]|0;if((k|0)!=-1){c[b+124>>2]=k;c[h>>2]=-1}h=A+25988|0;k=c[h>>2]|0;if((k|0)!=-1){c[b+140>>2]=k;c[h>>2]=-1}u=A+25288|0;Gc(A,u);do if(!y){h=A+24620|0;l=0}else{p=A+198252|0;if(c[p>>2]|0)if(Jb(g,1,0)|0){m=Jb(g,1,x)|0;n=c[x>>2]|0;o=m+n|0;k=d[m+5>>0]&31;g=m+6|0;while(1){if(!k){t=26;break}h=(d[g>>0]|d[g+1>>0]<<8)&65535;h=(h<<8&65280|h>>>8)+2|0;if((h|0)>(o-g|0))break;if((a[g+2>>0]|0)==103){k=k+-1|0;g=g+h|0}else break}a:do if((t|0)==26){h=a[g>>0]|0;if(h<<24>>24){l=h&255;k=g+1|0;while(1){if(!l)break;h=(d[k>>0]|d[k+1>>0]<<8)&65535;h=(h<<8&65280|h>>>8)+2|0;if((h|0)>(o-k|0))break a;if((a[k+2>>0]|0)==104){l=l+-1|0;k=k+h|0}else break a}Ob(A,m,n)|0}}while(0)}b:do if((y|0)>8&(c[p>>2]|0)!=0)if((a[q>>0]|0)==1)if(!(a[q+2>>0]|0))if((d[q+4>>0]|0)>251){h=a[q+5>>0]|0;if(h&31)if((a[q+8>>0]|0)==103){m=q+y|0;k=h&31;g=q+6|0;while(1){if(!k)break;h=(d[g>>0]|d[g+1>>0]<<8)&65535;h=(h<<8&65280|h>>>8)+2|0;if((h|0)>(m-g|0))break b;if((a[g+2>>0]|0)==103){k=k+-1|0;g=g+h|0}else break b}h=a[g>>0]|0;if(h<<24>>24){l=h&255;k=g+1|0;while(1){if(!l)break;h=(d[k>>0]|d[k+1>>0]<<8)&65535;h=(h<<8&65280|h>>>8)+2|0;if((h|0)>(m-k|0))break b;if((a[k+2>>0]|0)==104){l=l+-1|0;k=k+h|0}else break b}f=Ob(A,q,y)|0;i=B;return f|0}}}while(0);n=Pb(A,q,y,0)|0;if((n|0)<0){f=-1094995529;i=B;return f|0}h=A+24620|0;k=(c[h>>2]|0)==0;if(k)if((c[A+198248>>2]|0)==10){if((n|0)<=(y|0)){l=n;break}c[r>>2]=59114;c[r+4>>2]=58702;c[r+8>>2]=1834;UC(0,0,104466,r);Ya()}if(!(c[b+92>>2]&32768))if(k){if((c[b+856>>2]|0)>7){f=y;i=B;return f|0}if((y|0)>3)if(!(bE(59136,q,4)|0)){f=y;i=B;return f|0}UC(b,16,59141,s);f=-1094995529;i=B;return f|0}else t=62;else{s=c[A+198228>>2]|0;if(!((s|0)==0?1:(c[A+198224>>2]|0)<(s|0))){Sb(A,1);t=62}}do if((t|0)==62){h=Jc(A,c[A+25952>>2]|0,0)|0;if((h|0)<0){f=h;i=B;return f|0}c[f>>2]=0;m=A+202432|0;h=c[m>>2]|0;if(h|0)if(c[h+640>>2]|0){k=c[A+4>>2]|0;do if(!(c[k+712>>2]|0)){g=(c[h+72>>2]|0)==2147483647;if(!g)if((c[h+76>>2]|0)!=2147483647)break;l=c[h>>2]|0;h=g&1;c[v>>2]=h;UC(k,48,59152,v);h=0-(h^1)|0;k=c[l+32>>2]|0;c[x>>2]=(c[l>>2]|0)+(k&h);if(g){c[w>>2]=(c[l>>2]|0)+k;c[j>>2]=k<<1;t=l+4|0;v=c[l+36>>2]|0;c[x+4>>2]=(c[t>>2]|0)+(v&h);c[w+4>>2]=(c[t>>2]|0)+v;c[j+4>>2]=v<<1;v=l+8|0;t=c[l+40>>2]|0;c[x+8>>2]=(c[v>>2]|0)+(t&h);c[w+8>>2]=(c[v>>2]|0)+t;c[j+8>>2]=t<<1;t=l+12|0;v=c[l+44>>2]|0;c[x+12>>2]=(c[t>>2]|0)+(v&h);c[w+12>>2]=(c[t>>2]|0)+v;c[j+12>>2]=v<<1;h=w}else{c[w>>2]=c[l>>2];c[j>>2]=k<<1;t=l+4|0;v=c[l+36>>2]|0;c[x+4>>2]=(c[t>>2]|0)+(v&h);c[w+4>>2]=c[t>>2];c[j+4>>2]=v<<1;v=l+8|0;t=c[l+40>>2]|0;c[x+8>>2]=(c[v>>2]|0)+(t&h);c[w+8>>2]=c[v>>2];c[j+8>>2]=t<<1;t=l+12|0;v=c[l+44>>2]|0;c[x+12>>2]=(c[t>>2]|0)+(v&h);c[w+12>>2]=c[t>>2];c[j+12>>2]=v<<1;h=w}PC(x,j,h,j,c[l+80>>2]|0,c[l+68>>2]|0,c[l+72>>2]>>1);h=c[m>>2]|0}while(0);h=Xb(A,e,h)|0;if((h|0)<0){f=h;i=B;return f|0}else{c[f>>2]=1;break}}}while(0);if(!(c[e+360>>2]|0))if(c[f>>2]|0){c[z>>2]=59190;c[z+4>>2]=58702;c[z+8>>2]=1899;UC(0,0,104466,z);Ya()}Gc(A,u);f=(n|0)==0?1:n;f=(f+10|0)>(y|0)?y:f;i=B;return f|0}while(0);c[h>>2]=0;c[A+197616>>2]=0;j=c[A+202296>>2]|0;h=c[A+202300>>2]|0;if(!h){k=j;h=0}else{g=1;k=0;while(1){if(c[(c[h>>2]|0)+84>>2]|0){h=k;break}if(c[h+88>>2]|0){h=k;break}z=(c[h+80>>2]|0)<(c[j+80>>2]|0);k=z?g:k;j=z?h:j;g=g+1|0;h=c[A+202296+(g<<2)>>2]|0;if(!h){h=k;break}}k=c[A+202296+(h<<2)>>2]|0}if(k|0){k=A+202296+(h<<2)|0;do{h=h+1|0;z=k;k=A+202296+(h<<2)|0;c[z>>2]=c[k>>2]}while((c[k>>2]|0)!=0)}if(!j){f=l;i=B;return f|0}h=j+636|0;c[h>>2]=c[h>>2]&-5;h=Xb(A,e,j)|0;if((h|0)<0){f=h;i=B;return f|0}c[f>>2]=1;f=l;i=B;return f|0}function Xb(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;f=c[d>>2]|0;e=yD(c[f+80>>2]|0)|0;f=DC(b,f)|0;if((f|0)<0){b=f;return b|0}YB(b+456|0,59102,Yc(a)|0,0)|0;i=c[a+4>>2]|0;g=i+120|0;c[a+25980>>2]=c[g>>2];h=i+124|0;c[a+25984>>2]=c[h>>2];i=i+140|0;c[a+25988>>2]=c[i>>2];c[g>>2]=c[b+68>>2];c[h>>2]=c[b+72>>2];c[i>>2]=c[b+80>>2];if(!(c[d+648>>2]|0))c[b+84>>2]=1;if(!(c[d+652>>2]|0)){b=0;return b|0}e=c[e+4>>2]|0;h=e>>>8&255;if(!((e&255)<<24>>24)){b=0;return b|0}i=c[d+656>>2]|0;g=c[a+25960>>2]|0;a=c[d+660>>2]|0;d=e&255;e=e>>>16&255;f=0;do{k=(f|0)>0;k=(_(a>>(k?e:0),c[b+32+(f<<2)>>2]|0)|0)+(i>>(k?h:0)<<g)|0;j=b+(f<<2)|0;c[j>>2]=(c[j>>2]|0)+k;f=f+1|0}while((f|0)<(d|0));e=0;return e|0}function Yb(a){a=a|0;var b=0;b=c[a+60>>2]|0;Sc(b);Tb(b);a=b+24624|0;Gc(b,a);AC(a);a=b+25288|0;Gc(b,a);AC(a);return 0}function Zb(a){a=a|0;var b=0,d=0;d=c[a+60>>2]|0;a=d+202296|0;b=a+72|0;do{c[a>>2]=0;a=a+4|0}while((a|0)<(b|0));Vb(d);a=0;do{Gc(d,d+716+(a*664|0)|0);a=a+1|0}while((a|0)!=36);c[d+24620>>2]=0;Gc(d,d+24624|0);c[d+198224>>2]=0;Ub(d);c[d+26004>>2]=0;return}function _b(a,b,e,f){a=a|0;b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0;m=i;i=i+32|0;l=m;g=c[a+4>>2]|0;j=c[a+24624>>2]|0;k=d[(yD(c[g+140>>2]|0)|0)+6>>0]|0;h=c[a+197612>>2]|0;b=(h|0)!=3;n=b&1;f=f<<n;e=e<<n;n=(c[g+124>>2]|0)-e|0;f=(f|0)>(n|0)?n:f;if(b)if(c[a+197616>>2]|0)if(!(c[g+288>>2]&2)){i=m;return}b=g+148|0;if(!(c[b>>2]|0)){i=m;return}c[l>>2]=_(c[j+32>>2]|0,e)|0;n=_(c[j+36>>2]|0,e>>k)|0;c[l+8>>2]=n;c[l+4>>2]=n;n=l+12|0;c[n>>2]=0;c[n+4>>2]=0;c[n+8>>2]=0;c[n+12>>2]=0;c[n+16>>2]=0;rb[c[b>>2]&63](g,j,l,e,h,f);i=m;return}function $b(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0;l=i;i=i+32|0;k=l+16|0;h=l;do if(!(c[d+21736>>2]&32768)){f=d+21652|0;g=a[f>>0]|0;e=a[59218+g>>0]|0;if(!(249>>>g&1)){if(253>>>g&1|0)a[f>>0]=e;f=d+21653|0;g=a[f>>0]|0;e=a[59218+g>>0]|0;if(!(249>>>g&1)){if(253>>>g&1|0)a[f>>0]=e;f=d+21654|0;g=a[f>>0]|0;e=a[59218+g>>0]|0;if(!(249>>>g&1)){if(253>>>g&1|0)a[f>>0]=e;f=d+21655|0;g=a[f>>0]|0;e=a[59218+g>>0]|0;if(!(249>>>g&1)){if(!(253>>>g&1))break;a[f>>0]=e;break}}}}b=c[b+4>>2]|0;k=c[d+21764>>2]|0;d=c[d+21768>>2]|0;c[h>>2]=e<<24>>24;c[h+4>>2]=k;c[h+8>>2]=d;UC(b,16,59230,h);d=-1094995529;i=l;return d|0}while(0);h=c[d+21744>>2]|0;if((h&34952|0)==34952){d=0;i=l;return d|0}if(!(h&32768)){f=d+21652|0;g=a[f>>0]|0;e=a[59293+g>>0]|0;if(!(370>>>g&1))if(!(886>>>g&1))j=13;else{a[f>>0]=e;j=13}}else j=13;do if((j|0)==13){if(!(h&8192)){f=d+21660|0;g=a[f>>0]|0;e=a[59293+g>>0]|0;if(370>>>g&1|0)break;if(886>>>g&1|0)a[f>>0]=e}if(!(h&128)){f=d+21668|0;g=a[f>>0]|0;e=a[59293+g>>0]|0;if(370>>>g&1|0)break;if(886>>>g&1|0)a[f>>0]=e}if(h&32|0){d=0;i=l;return d|0}f=d+21676|0;g=a[f>>0]|0;e=a[59293+g>>0]|0;if(!(370>>>g&1)){if(!(886>>>g&1)){d=0;i=l;return d|0}a[f>>0]=e;d=0;i=l;return d|0}}while(0);b=c[b+4>>2]|0;j=c[d+21764>>2]|0;d=c[d+21768>>2]|0;c[k>>2]=e<<24>>24;c[k+4>>2]=j;c[k+8>>2]=d;UC(b,16,59305,k);d=-1094995529;i=l;return d|0}function ac(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0;k=i;i=i+32|0;j=k+16|0;h=k+8|0;g=k;if(e>>>0>3){j=c[b+4>>2]|0;f=c[d+21768>>2]|0;c[g>>2]=c[d+21764>>2];c[g+4>>2]=f;UC(j,16,59369,g);j=-1094995529;i=k;return j|0}if(!(c[d+21736>>2]&32768)){g=a[59415+e>>0]|0;if((e|1|0)==3){j=c[b+4>>2]|0;f=c[d+21768>>2]|0;c[h>>2]=c[d+21764>>2];c[h+4>>2]=f;UC(j,16,59419,h);j=-1094995529;i=k;return j|0}}else g=e;e=c[d+21744>>2]|0;h=e&32896;if((h|0)==32896){j=g;i=k;return j|0}if((g|2|0)==3){f=c[b+4>>2]|0;b=c[d+21768>>2]|0;c[j>>2]=c[d+21764>>2];c[j+4>>2]=b;UC(f,16,59476,j);j=-1094995529;i=k;return j|0}if((f|0)==0|(h|0)==0){j=a[59534+g>>0]|0;i=k;return j|0}j=8-(e>>>15&1)+(((g|0)==4&1)<<1)|0;i=k;return j|0}function bc(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;x=i;i=i+96|0;t=x+80|0;r=x+72|0;q=x+64|0;p=x+56|0;n=x+48|0;m=x+40|0;j=x+32|0;h=x+24|0;g=x+16|0;e=x+8|0;d=x;v=a+198236|0;f=c[v>>2]|0;w=a+198228|0;o=_((c[w>>2]|0)+1|0,f)|0;s=a+4|0;b=c[s>>2]|0;l=c[b+808>>2]|0;f=_((l|0)>1?l:1,f<<1)|0;do if(f>>>0<268435455){b=cD(f<<3)|0;c[a+26016>>2]=b;if(!b){b=c[s>>2]|0;u=5;break}k=a+25952|0;c[(c[k>>2]|0)+21680>>2]=b;d=cD(o*48|0)|0;c[a+26256>>2]=d;l=(o|0)==0;if(!(l|(d|0)!=0)){UC(c[s>>2]|0,16,59539,e);break}d=cD((c[v>>2]|0)+o<<1)|0;e=a+199424|0;c[e>>2]=d;if(!d)if((c[v>>2]|0)+o&2147483647|0){UC(c[s>>2]|0,16,59539,g);break}d=o<<1;g=cD(d)|0;c[a+197624>>2]=g;if(!((d|0)==0|(g|0)!=0)){UC(c[s>>2]|0,16,59539,h);break}h=cD(o)|0;c[a+197628>>2]=h;if(!(l|(h|0)!=0)){UC(c[s>>2]|0,16,59539,j);break}if(f>>>0<134217727){b=f<<4;j=cD(b)|0;d=a+197632|0;c[d>>2]=j;if(j|0){m=cD(b)|0;b=a+197636|0;c[b>>2]=m;if(!m){UC(c[s>>2]|0,16,59539,n);break}c[(c[k>>2]|0)+48672>>2]=c[d>>2];c[(c[k>>2]|0)+48676>>2]=c[b>>2];d=o<<2;n=cD(d)|0;c[a+197640>>2]=n;if(!(l|(n|0)!=0)){UC(c[s>>2]|0,16,59539,p);break}p=cD(o)|0;c[a+197620>>2]=p;if(!(l|(p|0)!=0)){UC(c[s>>2]|0,16,59539,q);break}FF(c[e>>2]|0,-1,(c[v>>2]|0)+o<<1|0)|0;c[a+197604>>2]=(c[e>>2]|0)+(c[v>>2]<<1<<1)+2;q=cD(d)|0;l=a+26644|0;c[l>>2]=q;b=(d|0)==0;if(!(b|(q|0)!=0)){UC(c[s>>2]|0,16,59539,r);break}r=cD(d)|0;k=a+26648|0;c[k>>2]=r;if(!(b|(r|0)!=0)){UC(c[s>>2]|0,16,59539,t);break}b=c[w>>2]|0;if((b|0)>0){h=a+198232|0;j=a+26652|0;d=c[h>>2]|0;g=0;do{if((d|0)>0){b=c[l>>2]|0;e=c[k>>2]|0;f=0;do{d=(_(c[v>>2]|0,g)|0)+f|0;c[b+(d<<2)>>2]=(_(c[j>>2]|0,g)|0)+f<<2;c[e+(d<<2)>>2]=((d|0)%(c[v>>2]<<1|0)|0)<<3;f=f+1|0;d=c[h>>2]|0}while((f|0)<(d|0));b=c[w>>2]|0}g=g+1|0}while((g|0)<(b|0))}if(c[a+197556>>2]|0){a=0;i=x;return a|0}Zc(a);a=0;i=x;return a|0}}else c[a+197632>>2]=0;UC(c[s>>2]|0,16,59539,m)}else{c[a+26016>>2]=0;u=5}while(0);if((u|0)==5)UC(b,16,59539,d);Ub(a);a=-12;i=x;return a|0}function cc(d,e){d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;y=i;i=i+32|0;x=y+24|0;n=y+16|0;m=y+8|0;g=y;f=e+24|0;k=d+198228|0;q=c[k>>2]|0;u=d+198236|0;r=c[u>>2]|0;l=_(r,q)|0;v=d+198232|0;w=c[v>>2]<<1;o=q<<1;p=_(w|1,o|1)|0;q=_(r,q+1|0)|0;r=q<<1;s=p+r|0;a[e+44168>>0]=-2;a[e+44160>>0]=-2;a[e+44152>>0]=-2;a[e+44128>>0]=-2;a[e+44120>>0]=-2;a[e+44112>>0]=-2;if((c[d+25952>>2]|0)!=(e|0)){FF(f|0,0,784)|0;x=0;i=y;return x|0}t=d+4|0;c[f>>2]=c[t>>2];c[e+800>>2]=1;c[e+804>>2]=d;c[e+788>>2]=1;f=d+198240|0;d=c[f>>2]|0;c[e+552>>2]=d;j=c[v>>2]|0;c[e+556>>2]=j;c[e+560>>2]=c[k>>2];c[e+564>>2]=c[u>>2];c[e+568>>2]=j<<1|1;d=cD((d<<2)+4|0)|0;j=e+548|0;c[j>>2]=d;if(!d)if((c[f>>2]&1073741823|0)!=1073741823){UC(c[t>>2]|0,16,59539,g);x=-12;i=y;return x|0}g=c[k>>2]|0;f=c[v>>2]|0;if((g|0)>0){d=g;h=0;do{if((f|0)>0){g=c[j>>2]|0;d=0;do{c[g+((_(f,h)|0)+d<<2)>>2]=(_(c[u>>2]|0,h)|0)+d;d=d+1|0;f=c[v>>2]|0}while((d|0)<(f|0));d=c[k>>2]|0}h=h+1|0}while((h|0)<(d|0));g=d;d=c[j>>2]|0}c[d+((_(f,g)|0)<<2)>>2]=(_(c[u>>2]|0,g+-1|0)|0)+f;j=cD(l)|0;c[e+580>>2]=j;if(!((l|0)==0|(j|0)!=0)){UC(c[t>>2]|0,16,59539,m);x=-12;i=y;return x|0}m=YC(_(c[u>>2]|0,c[k>>2]|0)|0)|0;c[e+584>>2]=m;if(!m)if(_(c[u>>2]|0,c[k>>2]|0)|0){UC(c[t>>2]|0,16,59539,n);x=-12;i=y;return x|0}n=s<<1;g=cD(n)|0;c[e+43612>>2]=g;if(!((n|0)==0|(g|0)!=0)){UC(c[t>>2]|0,16,59539,x);x=-12;i=y;return x|0}c[e+588>>2]=g+(c[v>>2]<<1<<1)+4;x=g+(p<<1)+(c[u>>2]<<1)+2|0;c[e+592>>2]=x;c[e+596>>2]=x+(q<<1);if((s|0)<=0){x=0;i=y;return x|0}f=r+(_(w|1,o|1)|0)|0;d=0;do{b[g+(d<<1)>>1]=1024;d=d+1|0}while((d|0)!=(f|0));f=0;i=y;return f|0}function dc(a,d,e,f,g,h,j,k,l){a=a|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;var m=0,n=0,o=0,p=0;p=i;i=i+16|0;o=p+8|0;e=p;f=a+25952|0;m=c[f>>2]|0;c[m+21764>>2]=h;c[m+21768>>2]=j;n=m+21772|0;c[n>>2]=(_(c[a+198236>>2]|0,j)|0)+h;k=m+43648|0;l=k+120|0;do{c[k>>2]=0;k=k+4|0}while((k|0)<(l|0));k=(c[m+23372>>2]|0)>>>0>d>>>0?d:0;if(!(c[m+23384+(k*40|0)>>2]|0)){UC(c[a+4>>2]|0,48,59564,e);k=0}if((c[m+23384+(k*40|0)+24>>2]&3|0)==3){d=(c[a+24688>>2]|0)+(c[n>>2]<<2)|0;o=k*257&65535;b[d>>1]=o;b[d+2>>1]=o;d=_(k,16843009)|0;c[m+44108>>2]=d;c[m+44116>>2]=d;c[m+44124>>2]=d;c[m+44132>>2]=d;g=c[g+4>>2]<<16|c[g>>2]&65535;c[m+43824>>2]=g;c[m+43828>>2]=g;c[m+43832>>2]=g;c[m+43836>>2]=g;c[m+43856>>2]=g;c[m+43860>>2]=g;c[m+43864>>2]=g;c[m+43868>>2]=g;c[m+43888>>2]=g;c[m+43892>>2]=g;c[m+43896>>2]=g;c[m+43900>>2]=g;c[m+43920>>2]=g;c[m+43924>>2]=g;c[m+43928>>2]=g;c[m+43932>>2]=g;c[m+21796>>2]=0;c[m+21800>>2]=0;sc(a,c[f>>2]|0);i=p;return}else{UC(c[a+4>>2]|0,48,59610,o);i=p;return}}function ec(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;x=i;i=i+32|0;n=x+24|0;m=x+16|0;k=x+8|0;j=x;t=e+856|0;c[t>>2]=0;u=e+860|0;c[u>>2]=0;v=e+12|0;g=c[v>>2]|0;w=e+4|0;f=(c[w>>2]|0)+(g>>>3)|0;f=(MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0)<<(g&7);do if(f>>>0>134217727){f=f>>>23;c[v>>2]=(d[86681+f>>0]|0)+g;f=d[87193+f>>0]|0}else{s=f>>>0>65535;r=s?f>>>16:f;s=s?16:0;h=(r&65280|0)==0;s=(d[97868+(h?r:r>>>8)>>0]|0)+(h?s:s|8)<<1;h=s+-31|0;c[v>>2]=g+63-s;if((h|0)<7){UC(0,16,66830,j);f=-1094995529;break}else{f=(f>>>h)+-1|0;break}}while(0);l=e+864|0;c[l>>2]=f;s=b+26672|0;if(c[s>>2]|0){g=c[v>>2]|0;h=(c[w>>2]|0)+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);do if(h>>>0>134217727){r=h>>>23;c[v>>2]=(d[86681+r>>0]|0)+g;g=d[87193+r>>0]|0}else{r=h>>>0>65535;q=r?h>>>16:h;r=r?16:0;j=(q&65280|0)==0;r=(d[97868+(j?q:q>>>8)>>0]|0)+(j?r:r|8)<<1;j=r+-31|0;c[v>>2]=g+63-r;if((j|0)<7){UC(0,16,66830,k);g=-1094995529;f=c[l>>2]|0;break}else{g=(h>>>j)+-1|0;break}}while(0);c[e+868>>2]=g}if(f>>>0>7){r=c[b+4>>2]|0;c[m>>2]=f;UC(r,16,59629,m);c[l>>2]=0;f=0}h=e+868|0;g=c[h>>2]|0;if(g>>>0>7){f=c[b+4>>2]|0;c[n>>2]=g;UC(f,16,59672,n);c[h>>2]=0;f=c[l>>2]|0;g=0}r=1<<f;l=1<<g;m=e+816|0;q=0;do{b=e+872+(q<<2)|0;c[b>>2]=0;n=e+880+(q<<2)|0;c[n>>2]=0;o=e+23372+(q<<2)|0;if(c[o>>2]|0){p=0;do{k=c[v>>2]|0;j=c[w>>2]|0;h=d[j+(k>>>3)>>0]|0;f=k+1|0;c[v>>2]=f;if(!(h&128>>>(k&7))){c[e+888+(p<<4)+(q<<3)>>2]=r;c[e+888+(p<<4)+(q<<3)+4>>2]=0}else{g=j+(f>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(f&7);if(g>>>0>134217727){h=g>>>23;f=(d[86681+h>>0]|0)+f|0;c[v>>2]=f;h=a[87705+h>>0]|0}else{h=g>>>0>65535;g=h?g>>>16:g;h=h?16:0;k=(g&65280|0)==0;h=(d[97868+(k?g:g>>>8)>>0]|0)+(k?h:h|8)|0;f=31-h+f|0;k=j+(f>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(f&7)>>>h;f=32-h+f|0;c[v>>2]=f;h=k&1;h=(k>>>1^0-h)+h|0}c[e+888+(p<<4)+(q<<3)>>2]=h;g=j+(f>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(f&7);if(g>>>0>134217727){k=g>>>23;c[v>>2]=(d[86681+k>>0]|0)+f;f=a[87705+k>>0]|0}else{k=g>>>0>65535;y=k?g>>>16:g;g=k?16:0;k=(y&65280|0)==0;g=(d[97868+(k?y:y>>>8)>>0]|0)+(k?g:g|8)|0;f=31-g+f|0;k=j+(f>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(f&7)>>>g;c[v>>2]=32-g+f;f=k&1;f=(k>>>1^0-f)+f|0}c[e+888+(p<<4)+(q<<3)+4>>2]=f;if(!((f|0)==0&(h|0)==(r|0))){c[t>>2]=1;c[b>>2]=1}}a:do if(c[s>>2]|0){y=c[v>>2]|0;g=c[w>>2]|0;k=d[g+(y>>>3)>>0]|0;f=y+1|0;c[v>>2]=f;if(!(k&128>>>(y&7))){c[e+1656+(p<<5)+(q<<4)>>2]=l;c[e+1656+(p<<5)+(q<<4)+4>>2]=0;c[e+1656+(p<<5)+(q<<4)+8>>2]=l;c[e+1656+(p<<5)+(q<<4)+12>>2]=0;break}else h=0;while(1){j=g+(f>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(f&7);if(j>>>0>134217727){k=j>>>23;f=(d[86681+k>>0]|0)+f|0;c[v>>2]=f;k=a[87705+k>>0]|0}else{k=j>>>0>65535;j=k?j>>>16:j;k=k?16:0;y=(j&65280|0)==0;k=(d[97868+(y?j:j>>>8)>>0]|0)+(y?k:k|8)|0;f=31-k+f|0;y=g+(f>>>3)|0;y=(MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0)<<(f&7)>>>k;f=32-k+f|0;c[v>>2]=f;k=y&1;k=(y>>>1^0-k)+k|0}c[e+1656+(p<<5)+(q<<4)+(h<<3)>>2]=k;j=g+(f>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(f&7);if(j>>>0>134217727){y=j>>>23;c[v>>2]=(d[86681+y>>0]|0)+f;f=a[87705+y>>0]|0}else{y=j>>>0>65535;z=y?j>>>16:j;j=y?16:0;y=(z&65280|0)==0;j=(d[97868+(y?z:z>>>8)>>0]|0)+(y?j:j|8)|0;f=31-j+f|0;y=g+(f>>>3)|0;y=(MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0)<<(f&7)>>>j;c[v>>2]=32-j+f;f=y&1;f=(y>>>1^0-f)+f|0}c[e+1656+(p<<5)+(q<<4)+(h<<3)+4>>2]=f;if(!((f|0)==0&(k|0)==(l|0))){c[u>>2]=1;c[n>>2]=1}h=h+1|0;if((h|0)==2)break a;f=c[v>>2]|0;g=c[w>>2]|0}}while(0);p=p+1|0}while(p>>>0<(c[o>>2]|0)>>>0)}q=q+1|0}while((c[m>>2]|0)==3&(q|0)<2);if(c[t>>2]|0){z=1;z=z&1;c[t>>2]=z;i=x;return 0}z=(c[u>>2]|0)!=0;z=z&1;c[t>>2]=z;i=x;return 0}function fc(a,d,e){a=a|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;g=c[a+26680>>2]|0;f=c[a+199464>>2]|0;h=a+199460|0;c[h>>2]=f;i=c[a+199448>>2]|0;if((i|0)<(c[a+199468>>2]|0)){f=f+(1<<g)|0;c[h>>2]=f}switch(c[a+26684>>2]|0){case 0:{f=1<<c[a+26688>>2];h=c[a+199428>>2]|0;g=c[a+199456>>2]|0;if((g|0)>(h|0))if((g-h|0)<((f|0)/2|0|0))j=7;else{f=(c[a+199452>>2]|0)+f|0;c[a+199432>>2]=f}else j=7;do if((j|0)==7){if((g|0)<(h|0))if((g-h|0)<((f|0)/-2|0|0)){f=(c[a+199452>>2]|0)-f|0;c[a+199432>>2]=f;break}f=c[a+199452>>2]|0;c[a+199432>>2]=f}while(0);j=h+f|0;f=a+197612|0;g=c[f>>2]|0;if((g|0)==3){h=f;g=j;f=(c[a+199436>>2]|0)+j|0;j=25}else{h=f;i=g;g=j;f=j;j=23}break}case 1:{j=c[a+26704>>2]|0;h=(j|0)==0?0:i+f|0;i=(c[a+198244>>2]|0)==0;h=(((h|0)>0&i)<<31>>31)+h|0;if((j|0)>0){f=0;g=0;do{f=(b[a+26808+(g<<1)>>1]|0)+f|0;g=g+1|0}while((g|0)<(j|0))}else f=0;if((h|0)>0){g=h+-1|0;h=(g|0)%(j|0)|0;f=_((g|0)/(j|0)|0,f)|0;if((h|0)>=0){g=0;while(1){f=(b[a+26808+(g<<1)>>1]|0)+f|0;if((g|0)==(h|0))break;else g=g+1|0}}}else f=0;if(i)f=(c[a+26696>>2]|0)+f|0;g=(c[a+199440>>2]|0)+f|0;f=g+(c[a+26700>>2]|0)|0;h=a+197612|0;i=c[h>>2]|0;if((i|0)==3){f=(c[a+199444>>2]|0)+f|0;j=25}else j=23;break}default:{f=(((c[a+198244>>2]|0)==0)<<31>>31)+(i+f<<1)|0;i=a+197612|0;h=i;i=c[i>>2]|0;g=f;j=23}}if((j|0)==23)if((i|0)==2)g=c[d>>2]|0;else j=25;if((j|0)==25){c[d>>2]=g;if((c[h>>2]|0)==1){a=g;d=c[d+4>>2]|0;j=(a|0)>(d|0);d=j?d:a;c[e>>2]=d;return 0}}c[d+4>>2]=f;d=g;a=f;j=(d|0)>(a|0);d=j?a:d;c[e>>2]=d;return 0}function gc(a){a=a|0;var b=0;b=c[a+4>>2]|0;switch(b|0){case 66:{a=c[a+1196>>2]<<8&512|66;return a|0}case 244:case 122:case 110:{a=c[a+1196>>2]<<8&2048|b;return a|0}default:{a=b;return a|0}}return 0}function hc(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=i;i=i+32|0;q=r+16|0;n=r+8|0;k=r;h=c[a+27884>>2]|0;g=c[a+27888>>2]|0;p=b+816|0;f=c[p>>2]|0;do if((f|0)==1){j=0;h=0;g=0}else{o=(c[a+197612>>2]|0)==3?15:31;m=b+12|0;e=c[m>>2]|0;if((f|0)==3){l=b+4|0;j=c[l>>2]|0;s=(d[j+(e>>>3)>>0]|0)<<(e&7)>>>7&1;e=e+1|0;c[m>>2]=e;c[b+21812>>2]=s}else{j=b+4|0;l=j;j=c[j>>2]|0}s=d[j+(e>>>3)>>0]|0;j=e+1|0;c[m>>2]=j;if(s&128>>>(e&7)){h=(c[l>>2]|0)+(j>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(j&7);do if(h>>>0>134217727){e=h>>>23;c[m>>2]=(d[86681+e>>0]|0)+j;e=d[87193+e>>0]|0}else{s=h>>>0>65535;j=s?h>>>16:h;s=s?16:0;g=(j&65280|0)==0;s=(d[97868+(g?j:j>>>8)>>0]|0)+(g?s:s|8)<<1;g=s+-31|0;c[m>>2]=e+64-s;if((g|0)<7){UC(0,16,66830,k);e=-1094995529;f=c[p>>2]|0;break}else{e=(h>>>g)+-1|0;break}}while(0);h=e+1|0;if((f|0)==3){g=c[m>>2]|0;f=(c[l>>2]|0)+(g>>>3)|0;f=(MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0)<<(g&7);do if(f>>>0>134217727){e=f>>>23;c[m>>2]=(d[86681+e>>0]|0)+g;e=d[87193+e>>0]|0}else{s=f>>>0>65535;l=s?f>>>16:f;s=s?16:0;e=(l&65280|0)==0;s=(d[97868+(e?l:l>>>8)>>0]|0)+(e?s:s|8)<<1;e=s+-31|0;c[m>>2]=g+63-s;if((e|0)<7){UC(0,16,66830,n);e=-1094995529;break}else{e=(f>>>e)+-1|0;break}}while(0);g=e+1|0}else g=1}e=h+-1|0;f=g+-1|0;if(!(e>>>0>o>>>0|f>>>0>o>>>0)){j=(c[p>>2]|0)==3?2:1;break}s=c[a+4>>2]|0;c[q>>2]=e;c[q+4>>2]=o;c[q+8>>2]=f;c[q+12>>2]=o;UC(s,16,59717,q);c[b+23376>>2]=0;c[b+23372>>2]=0;c[b+23380>>2]=0;s=-1094995529;i=r;return s|0}while(0);e=b+23380|0;f=b+23372|0;if((j|0)==(c[e>>2]|0))if((h|0)==(c[f>>2]|0))if((g|0)==(c[b+23376>>2]|0)){s=0;i=r;return s|0}c[f>>2]=h;c[b+23376>>2]=g;c[e>>2]=j;s=1;i=r;return s|0}function ic(){var b=0,e=0,f=0,g=0,h=0,i=0;if(c[26881]|0)return;c[26881]=1;c[26883]=112816;c[26885]=256;jA(107528,8,20,59756,1,1,59776,1,1,0,0,0,4)|0;c[26887]=113840;c[26889]=8192;jA(107544,13,36,59796,1,1,59832,1,1,0,0,0,4)|0;c[26891]=146608;c[26893]=520;jA(107560,8,68,59868,1,1,60140,1,1,0,0,0,4)|0;c[26895]=148688;c[26897]=332;jA(107576,8,68,59936,1,1,60208,1,1,0,0,0,4)|0;c[26899]=150016;c[26901]=280;jA(107592,8,68,60004,1,1,60276,1,1,0,0,0,4)|0;c[26903]=151136;c[26905]=256;jA(107608,8,68,60072,1,1,60344,1,1,0,0,0,4)|0;c[26907]=152160;c[26909]=8;jA(107624,3,4,60412,1,1,60424,1,1,0,0,0,4)|0;c[26911]=152192;c[26913]=8;jA(107640,3,4,60416,1,1,60428,1,1,0,0,0,4)|0;c[26915]=152224;c[26917]=8;jA(107656,3,4,60420,1,1,60432,1,1,0,0,0,4)|0;b=0;do{c[107672+(b<<4)+4>>2]=152256+(b<<7);c[107672+(b<<4)+12>>2]=32;jA(107672+(b<<4)|0,5,8,60436+(b<<3)|0,1,1,60492+(b<<3)|0,1,1,0,0,0,4)|0;b=b+1|0}while((b|0)!=7);b=0;do{c[107784+(b<<4)+4>>2]=153152+(b<<11);c[107784+(b<<4)+12>>2]=512;jA(107784+(b<<4)|0,9,16,60548+(b<<4)|0,1,1,60804+(b<<4)|0,1,1,0,0,0,4)|0;b=b+1|0}while((b|0)!=15);c[27007]=183872;c[27009]=8;jA(108024,3,7,61060,1,1,61172,1,1,0,0,0,4)|0;c[27011]=183904;c[27013]=8;jA(108040,3,7,61076,1,1,61188,1,1,0,0,0,4)|0;c[27015]=183936;c[27017]=8;jA(108056,3,7,61092,1,1,61204,1,1,0,0,0,4)|0;c[27019]=183968;c[27021]=8;jA(108072,3,7,61108,1,1,61220,1,1,0,0,0,4)|0;c[27023]=184e3;c[27025]=8;jA(108088,3,7,61124,1,1,61236,1,1,0,0,0,4)|0;c[27027]=184032;c[27029]=8;jA(108104,3,7,61140,1,1,61252,1,1,0,0,0,4)|0;c[27031]=184064;c[27033]=96;jA(108120,6,16,61156,1,1,61268,1,1,0,0,0,4)|0;i=0;do{b=-1<<i;h=0;do{e=h<<1;f=(e&65280|0)==0;e=(f?8:0)-(d[97868+(f?e:h>>>7&16777215)>>0]|0)|0;f=e+1|0;g=f+i|0;do if((g|0)<9){f=(h&65280|0)==0;e=(e<<i)+b+(h>>>((f?0:8)-i+(d[97868+(f?h:h>>>8)>>0]|0)|0))|0;f=e&1;a[184448+(i<<9)+(h<<1)>>0]=((e+2|0)>>>1^0-f)+f;a[184448+(i<<9)+(h<<1)+1>>0]=g}else if((f|0)<9){a[184448+(i<<9)+(h<<1)>>0]=e+100;a[184448+(i<<9)+(h<<1)+1>>0]=f;break}else{a[184448+(i<<9)+(h<<1)>>0]=108;a[184448+(i<<9)+(h<<1)+1>>0]=8;break}while(0);h=h+1|0}while((h|0)!=256);i=i+1|0}while((i|0)!=7);return}function jc(f,h){f=f|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,u=0,v=0,w=0,x=0,y=0,z=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;E=0;K=i;i=i+6496|0;r=c[f+27928>>2]|0;v=((c[(f+26672|0)>>2]|0)+-1|0)>>>0<2;I=c[f+25960>>2]|0;p=c[(h+21768|0)>>2]|0;J=(_(c[(f+198236|0)>>2]|0,p)|0)+(c[(h+21764|0)>>2]|0)|0;c[(h+21772|0)>>2]=J;k=c[(h+816|0)>>2]|0;if((k|0)!=1){l=c[(h+21788|0)>>2]|0;if((l|0)==-1){l=c[h+4>>2]|0;n=c[(h+12|0)>>2]|0;m=(MF(d[(l+(n>>>3)|0)>>0]|d[(l+(n>>>3)|0)+1>>0]<<8|d[(l+(n>>>3)|0)+2>>0]<<16|d[(l+(n>>>3)|0)+3>>0]<<24|0)|0)<<(n&7);o=(MF(d[(l+((n+16|0)>>>3)|0)>>0]|d[(l+((n+16|0)>>>3)|0)+1>>0]<<8|d[(l+((n+16|0)>>>3)|0)+2>>0]<<16|d[(l+((n+16|0)>>>3)|0)+3>>0]<<24|0)|0)<<((n+16|0)&7)>>>16|m&-65536;m=o>>>0>65535?m>>>16:o;o=o>>>0>65535?16:0;m=((m&65280|0)==0?o:o|8)+(d[97868+((m&65280|0)==0?m:m>>>8)>>0]|0)|0;n=(31-m|0)+n|0;c[(h+12|0)>>2]=n;do if(!((31-m|0)+1|0))l=0;else{o=(MF(d[(l+(n>>>3)|0)>>0]|d[(l+(n>>>3)|0)+1>>0]<<8|d[(l+(n>>>3)|0)+2>>0]<<16|d[(l+(n>>>3)|0)+3>>0]<<24|0)|0)<<(n&7);if(((31-m|0)+1|0|0)<26){c[(h+12|0)>>2]=n+((31-m|0)+1|0);l=o>>>m;break}else{c[(h+12|0)>>2]=n+16|0;l=(MF(d[(l+((n+16|0)>>>3)|0)>>0]|d[(l+((n+16|0)>>>3)|0)+1>>0]<<8|d[(l+((n+16|0)>>>3)|0)+2>>0]<<16|d[(l+((n+16|0)>>>3)|0)+3>>0]<<24|0)|0)<<((n+16|0)&7)>>>(47-(31-m|0)|0);c[(h+12|0)>>2]=n+((31-m|0)+1|0);l=l|o>>>16<<(31-m|0)+-15;break}}while(0);l=l+-1|0;c[(h+21788|0)>>2]=l}m=l+-1|0;c[(h+21788|0)>>2]=m;if(l|0){if(c[(f+197608|0)>>2]|0)if(!(p&1|m)){H=c[(h+12|0)>>2]|0;I=d[(c[h+4>>2]|0)+(H>>>3)>>0]<<(H&7)>>>7&1;c[(h+12|0)>>2]=H+1;c[h+21796>>2]=I;c[h+21800>>2]=I}c[(K+160|0)>>2]=0;j=(c[f+26256>>2]|0)+(J*48|0)|0;k=j+48|0;do{a[j>>0]=0;j=j+1|0}while((j|0)<(k|0));if(!(c[(h+21796|0)>>2]|0))j=0;else{c[(K+160|0)>>2]=128;j=128}if((c[(h+816|0)>>2]|0)==3){j=j|63744;c[(K+160|0)>>2]=j;if(c[h+21812>>2]|0){kc(f,h,j);lc(f,h,j)}rc(f,h,K+160|0);m=c[(K+160|0)>>2]|2048;c[(K+160|0)>>2]=m;j=f+26652|0}else{m=j|14344;c[(K+160|0)>>2]=m;kc(f,h,m);o=c[f+24688>>2]|0;p=c[f+24656>>2]|0;q=c[(f+26652|0)>>2]|0;c[h+44108>>2]=0;c[h+44116>>2]=0;c[h+44124>>2]=0;c[h+44132>>2]=0;k=c[h+21716>>2]|0;if(!(k&12288))if(!k)E=80;else{C=108160;y=-1;E=27}else{l=c[h+21696>>2]|0;I=d[c[h+21724>>2]>>0]|0;j=a[o+((I&254)+(l<<2|1))>>0]|0;l=p+((_(I,q)|0)+3+(c[(c[f+26644>>2]|0)+(l<<2)>>2]|0)<<2)|0;do if(c[(f+197608|0)>>2]|0)if(!(c[(h+21796|0)>>2]|0)){if(!(k&128))break;l=c[l>>2]|0;c[(K+192|0)>>2]=l;b[(K+192|0)+2>>1]=l>>16<<1;l=K+192|0;j=j>>1;break}else{if(k&128)break;l=c[l>>2]|0;c[(K+192|0)>>2]=l;b[(K+192|0)+2>>1]=(l>>16|0)/2|0;l=K+192|0;j=j<<1;break}while(0);if(!(c[l>>2]|j))E=80;else{C=l;y=j;E=27}}do if((E|0)==27){j=c[h+21708>>2]|0;if(!(j&12288))if(!j){E=80;break}else{r=108160;n=-1}else{k=c[h+21688>>2]|0;l=a[o+(k<<2|2)>>0]|0;k=p+((c[(c[f+26644>>2]|0)+(k<<2)>>2]|0)+(q*3|0)<<2)|0;do if(!(c[(f+197608|0)>>2]|0))j=l;else if(!(c[(h+21796|0)>>2]|0)){if(!(j&128)){j=l;break}k=c[k>>2]|0;c[((K+192|0)+4|0)>>2]=k;b[(K+192|0)+6>>1]=k>>16<<1;k=(K+192|0)+4|0;j=l>>1;break}else{if(j&128){j=l;break}k=c[k>>2]|0;c[((K+192|0)+4|0)>>2]=k;b[(K+192|0)+6>>1]=(k>>16|0)/2|0;k=(K+192|0)+4|0;j=l<<1;break}while(0);if(!(c[k>>2]|j)){E=80;break}else{r=k;n=j}}k=c[h+21712>>2]|0;do if(!(k&12288))if(!k){l=c[h+21704>>2]|0;if(!(l&12288)){o=108160;j=(l|0)==0?-2:-1;break}j=c[h+21684>>2]|0;I=c[h+21728>>2]|0;k=a[o+(j<<2|I&2|1)>>0]|0;j=p+(q+3+(I&q<<1)+(c[(c[f+26644>>2]|0)+(j<<2)>>2]|0)<<2)|0;if(!(c[(f+197608|0)>>2]|0)){o=j;j=k}else if(!(c[(h+21796|0)>>2]|0)){if(!(l&128)){o=j;j=k;break}o=c[j>>2]|0;c[((K+192|0)+8|0)>>2]=o;b[(K+192|0)+10>>1]=o>>16<<1;o=(K+192|0)+8|0;j=k>>1;break}else{if(l&128){o=j;j=k;break}o=c[j>>2]|0;c[((K+192|0)+8|0)>>2]=o;b[(K+192|0)+10>>1]=(o>>16|0)/2|0;o=(K+192|0)+8|0;j=k<<1;break}}else{o=108160;j=-1}else{j=c[h+21692>>2]|0;l=a[o+(j<<2|2)>>0]|0;j=p+((c[(c[f+26644>>2]|0)+(j<<2)>>2]|0)+(q*3|0)<<2)|0;if(!(c[(f+197608|0)>>2]|0)){o=j;j=l}else if(!(c[(h+21796|0)>>2]|0)){if(!(k&128)){o=j;j=l;break}o=c[j>>2]|0;c[((K+192|0)+8|0)>>2]=o;b[(K+192|0)+10>>1]=o>>16<<1;o=(K+192|0)+8|0;j=l>>1;break}else{if(k&128){o=j;j=l;break}o=c[j>>2]|0;c[((K+192|0)+8|0)>>2]=o;b[(K+192|0)+10>>1]=(o>>16|0)/2|0;o=(K+192|0)+8|0;j=l<<1;break}}while(0);l=(n|0)!=0;k=(y|0)!=0;j=(l&1^1)+(k&1^1)+((j|0)==0&1)|0;do if(j>>>0>1){j=b[C>>1]|0;k=b[r>>1]|0;l=b[o>>1]|0;if(j<<16>>16>k<<16>>16)if(l<<16>>16>k<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:l<<16>>16;else l=k<<16>>16;else if(k<<16>>16>l<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?l<<16>>16:j<<16>>16;else l=k<<16>>16;n=b[C+2>>1]|0;k=b[r+2>>1]|0;j=b[o+2>>1]|0;if(n<<16>>16>k<<16>>16){if(j<<16>>16<=k<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?n<<16>>16:j<<16>>16;break}else{if(k<<16>>16<=j<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?j<<16>>16:n<<16>>16;break}}else{if((j|0)==1){if(!k){l=b[C>>1]|0;j=b[C+2>>1]|0;break}if(l){l=b[o>>1]|0;j=b[o+2>>1]|0;break}else{l=b[r>>1]|0;j=b[r+2>>1]|0;break}}j=b[C>>1]|0;k=b[r>>1]|0;l=b[o>>1]|0;if(j<<16>>16>k<<16>>16)if(l<<16>>16>k<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:l<<16>>16;else l=k<<16>>16;else if(k<<16>>16>l<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?l<<16>>16:j<<16>>16;else l=k<<16>>16;n=b[C+2>>1]|0;k=b[r+2>>1]|0;j=b[o+2>>1]|0;if(n<<16>>16>k<<16>>16){if(j<<16>>16<=k<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?n<<16>>16:j<<16>>16;break}else{if(k<<16>>16<=j<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?j<<16>>16:n<<16>>16;break}}while(0);I=j<<16|l&65535;c[h+43824>>2]=I;c[h+43828>>2]=I;c[h+43832>>2]=I;c[h+43836>>2]=I;c[h+43856>>2]=I;c[h+43860>>2]=I;c[h+43864>>2]=I;c[h+43868>>2]=I;c[h+43888>>2]=I;c[h+43892>>2]=I;c[h+43896>>2]=I;c[h+43900>>2]=I;c[h+43920>>2]=I;c[h+43924>>2]=I;c[h+43928>>2]=I;c[h+43932>>2]=I}while(0);if((E|0)==80){c[(h+43824|0)>>2]=0;c[(h+43824|0)+4>>2]=0;c[(h+43824|0)+8>>2]=0;c[(h+43824|0)+12>>2]=0;c[(h+43856|0)>>2]=0;c[(h+43856|0)+4>>2]=0;c[(h+43856|0)+8>>2]=0;c[(h+43856|0)+12>>2]=0;c[(h+43888|0)>>2]=0;c[(h+43888|0)+4>>2]=0;c[(h+43888|0)+8>>2]=0;c[(h+43888|0)+12>>2]=0;c[(h+43920|0)>>2]=0;c[(h+43920|0)+4>>2]=0;c[(h+43920|0)+8>>2]=0;c[(h+43920|0)+12>>2]=0}j=f+26652|0}j=c[j>>2]|0;k=(_(j<<2,c[(h+21768|0)>>2]|0)|0)+(c[(h+21764|0)>>2]<<2)|0;l=c[(h+21772|0)>>2]<<2;if(!(m&12288)){I=(c[f+24688>>2]|0)+l|0;b[I>>1]=-1;b[I+2>>1]=-1}else{I=(c[f+24656>>2]|0)+(k<<2)|0;H=c[(h+43824|0)+4>>2]|0;c[I>>2]=c[(h+43824|0)>>2];c[I+4>>2]=H;H=c[(h+43832|0)+4>>2]|0;c[(I+8|0)>>2]=c[(h+43832|0)>>2];c[(I+8|0)+4>>2]=H;H=c[(h+43856|0)+4>>2]|0;c[(I+(j<<2)|0)>>2]=c[(h+43856|0)>>2];c[(I+(j<<2)|0)+4>>2]=H;H=c[(h+43864|0)+4>>2]|0;c[((I+(j<<2)|0)+8|0)>>2]=c[(h+43864|0)>>2];c[((I+(j<<2)|0)+8|0)+4>>2]=H;H=c[(h+43888|0)+4>>2]|0;c[(I+(j<<1<<2)|0)>>2]=c[(h+43888|0)>>2];c[(I+(j<<1<<2)|0)+4>>2]=H;H=c[(h+43896|0)+4>>2]|0;c[((I+(j<<1<<2)|0)+8|0)>>2]=c[(h+43896|0)>>2];c[((I+(j<<1<<2)|0)+8|0)+4>>2]=H;H=c[(h+43920|0)+4>>2]|0;c[(I+(j*3<<2)|0)>>2]=c[(h+43920|0)>>2];c[(I+(j*3<<2)|0)+4>>2]=H;H=c[(h+43928|0)+4>>2]|0;c[((I+(j*3<<2)|0)+8|0)>>2]=c[(h+43928|0)>>2];c[((I+(j*3<<2)|0)+8|0)+4>>2]=H;I=(c[f+24688>>2]|0)+l|0;a[I>>0]=a[h+44108>>0]|0;a[I+1>>0]=a[h+44110>>0]|0;a[I+2>>0]=a[h+44124>>0]|0;a[I+3>>0]=a[h+44126>>0]|0}if(m&49152|0){I=(c[f+24660>>2]|0)+(k<<2)|0;H=c[(h+43984|0)+4>>2]|0;c[I>>2]=c[(h+43984|0)>>2];c[I+4>>2]=H;H=c[(h+43992|0)+4>>2]|0;c[(I+8|0)>>2]=c[(h+43992|0)>>2];c[(I+8|0)+4>>2]=H;H=c[(h+44016|0)+4>>2]|0;c[(I+(j<<2)|0)>>2]=c[(h+44016|0)>>2];c[(I+(j<<2)|0)+4>>2]=H;H=c[(h+44024|0)+4>>2]|0;c[((I+(j<<2)|0)+8|0)>>2]=c[(h+44024|0)>>2];c[((I+(j<<2)|0)+8|0)+4>>2]=H;H=c[(h+44048|0)+4>>2]|0;c[(I+(j<<1<<2)|0)>>2]=c[(h+44048|0)>>2];c[(I+(j<<1<<2)|0)+4>>2]=H;H=c[(h+44056|0)+4>>2]|0;c[((I+(j<<1<<2)|0)+8|0)>>2]=c[(h+44056|0)>>2];c[((I+(j<<1<<2)|0)+8|0)+4>>2]=H;H=c[(h+44080|0)+4>>2]|0;c[(I+(j*3<<2)|0)>>2]=c[(h+44080|0)>>2];c[(I+(j*3<<2)|0)+4>>2]=H;H=c[(h+44088|0)+4>>2]|0;c[((I+(j*3<<2)|0)+8|0)>>2]=c[(h+44088|0)>>2];c[((I+(j*3<<2)|0)+8|0)+4>>2]=H;I=(c[f+24692>>2]|0)+l|0;a[I>>0]=a[h+44148>>0]|0;a[I+1>>0]=a[h+44150>>0]|0;a[I+2>>0]=a[h+44164>>0]|0;a[I+3>>0]=a[h+44166>>0]|0}c[(c[f+24668>>2]|0)+(J<<2)>>2]=c[(K+160|0)>>2];a[(c[f+24644>>2]|0)+J>>0]=c[h+824>>2];b[(c[f+197604>>2]|0)+(J<<1)>>1]=c[h+808>>2];c[h+21624>>2]=1;h=0;i=K;return h|0}}if((c[(f+197608|0)>>2]|0)!=0&(p&1|0)==0){y=c[(h+12|0)>>2]|0;C=d[(c[h+4>>2]|0)+(y>>>3)>>0]<<(y&7)>>>7&1;c[(h+12|0)>>2]=y+1;c[h+21796>>2]=C;c[h+21800>>2]=C}c[h+21624>>2]=0;l=c[(h+12|0)>>2]|0;m=(c[(h+4|0)>>2]|0)+(l>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(l&7);do if(m>>>0>134217727){c[(h+12|0)>>2]=(d[86681+(m>>>23)>>0]|0)+l;l=d[87193+(m>>>23)>>0]|0}else{C=m>>>0>65535?m>>>16:m;n=m>>>0>65535?16:0;n=(d[97868+((C&65280|0)==0?C:C>>>8)>>0]|0)+((C&65280|0)==0?n:n|8)<<1;c[(h+12|0)>>2]=l+63-n;if((n+-31|0|0)<7){UC(0,16,66830,K);l=-1094995529;k=c[(h+816|0)>>2]|0;break}else{l=(m>>>(n+-31|0))+-1|0;break}}while(0);c[(K+156|0)>>2]=l;a:do switch(k|0){case 3:if(l>>>0<23){s=a[44300+(l<<2)+2>>0]|0;j=e[44300+(l<<2)>>1]|0;c[(K+156|0)>>2]=j;u=0;break a}else{q=l+-23|0;c[(K+156|0)>>2]=q;E=104;break a}case 2:if(l>>>0<5){s=a[44392+(l<<2)+2>>0]|0;j=e[44392+(l<<2)>>1]|0;c[(K+156|0)>>2]=j;u=0;break a}else{q=l+-5|0;c[(K+156|0)>>2]=q;E=104;break a}default:if(l|0?(c[h+812>>2]|0)==5:0){q=l+-1|0;c[(K+156|0)>>2]=q;E=104}else{q=l;E=104}}while(0);do if((E|0)==104){if(q>>>0<=25){u=d[44412+(q<<2)+3>>0]|0;c[h+21636>>2]=d[44412+(q<<2)+2>>0];j=e[44412+(q<<2)>>1]|0;c[(K+156|0)>>2]=j;s=0;break}f=c[f+4>>2]|0;I=(SD(c[h+812>>2]|0)|0)<<24>>24;J=c[(h+21764|0)>>2]|0;h=c[(h+21768|0)>>2]|0;c[(K+8|0)>>2]=q;c[(K+8|0)+4>>2]=I;c[(K+8|0)+8>>2]=J;c[(K+8|0)+12>>2]=h;UC(f,16,61412,K+8|0);h=-1;i=K;return h|0}while(0);if(c[(h+21796|0)>>2]|0){j=j|128;c[(K+156|0)>>2]=j}b[(c[f+197604>>2]|0)+(J<<1)>>1]=c[h+808>>2];if(j&4|0){k=_(e[44292+(c[(f+26672|0)>>2]<<1)>>1]|0,c[f+27844>>2]|0)|0;j=c[(h+12|0)>>2]|0;if(0-j&7){c[(h+12|0)>>2]=(0-j&7)+j|0;j=(0-j&7)+j|0}c[h+43608>>2]=(c[(h+4|0)>>2]|0)+(j>>3);if(((c[h+16>>2]|0)-j|0)<(k|0)){UC(c[f+4>>2]|0,16,61455,K+24|0);h=-1094995529;i=K;return h|0}else{c[(h+12|0)>>2]=j+k;a[(c[f+24644>>2]|0)+J>>0]=0;j=(c[f+26256>>2]|0)+(J*48|0)|0;k=j+48|0;do{a[j>>0]=16;j=j+1|0}while((j|0)<(k|0));c[(c[f+24668>>2]|0)+(J<<2)>>2]=c[(K+156|0)>>2];h=0;i=K;return h|0}}kc(f,h,j);lc(f,h,j);b:do{C=1;c[K+224>>2]=f;c[K+232>>2]=h;c[K+584>>2]=F;c[K+832>>2]=r;c[K+4800>>2]=v;c[K+5344>>2]=j;c[K+5824>>2]=w;c[K+6264>>2]=s;c[K+6384>>2]=E;c[K+6392>>2]=C;c[K+6408>>2]=0;c[K+6412>>2]=0;mc(K);F=c[K+584>>2]|0;w=c[K+5824>>2]|0;A=c[K+6408>>2]|0;t=c[K+6412>>2]|0;B=+g[K+6412>>2];c[K+6408>>2]=0;c[K+6412>>2]=0;if((A|0)==6)return t|0;if((A|0)==1)break;if((A|0)==2)switch(t|0){case 14:break b}}while(0);j=c[(K+156|0)>>2]|0;if(j&120|0){k=c[f+26652>>2]|0;l=(_(k<<2,c[(h+21768|0)>>2]|0)|0)+(c[(h+21764|0)>>2]<<2)|0;m=c[(h+21772|0)>>2]<<2;if(!(j&12288)){E=(c[f+24688>>2]|0)+m|0;b[E>>1]=-1;b[E+2>>1]=-1}else{E=(c[f+24656>>2]|0)+(l<<2)|0;C=c[(h+43824|0)+4>>2]|0;c[E>>2]=c[(h+43824|0)>>2];c[E+4>>2]=C;C=c[(h+43832|0)+4>>2]|0;c[(E+8|0)>>2]=c[(h+43832|0)>>2];c[(E+8|0)+4>>2]=C;C=c[(h+43856|0)+4>>2]|0;c[(E+(k<<2)|0)>>2]=c[(h+43856|0)>>2];c[(E+(k<<2)|0)+4>>2]=C;C=c[(h+43864|0)+4>>2]|0;c[((E+(k<<2)|0)+8|0)>>2]=c[(h+43864|0)>>2];c[((E+(k<<2)|0)+8|0)+4>>2]=C;C=c[(h+43888|0)+4>>2]|0;c[(E+(k<<1<<2)|0)>>2]=c[(h+43888|0)>>2];c[(E+(k<<1<<2)|0)+4>>2]=C;C=c[(h+43896|0)+4>>2]|0;c[((E+(k<<1<<2)|0)+8|0)>>2]=c[(h+43896|0)>>2];c[((E+(k<<1<<2)|0)+8|0)+4>>2]=C;C=c[(h+43920|0)+4>>2]|0;c[(E+(k*3<<2)|0)>>2]=c[(h+43920|0)>>2];c[(E+(k*3<<2)|0)+4>>2]=C;C=c[(h+43928|0)+4>>2]|0;c[((E+(k*3<<2)|0)+8|0)>>2]=c[(h+43928|0)>>2];c[((E+(k*3<<2)|0)+8|0)+4>>2]=C;E=(c[f+24688>>2]|0)+m|0;a[E>>0]=a[h+44108>>0]|0;a[E+1>>0]=a[h+44110>>0]|0;a[E+2>>0]=a[h+44124>>0]|0;a[E+3>>0]=a[h+44126>>0]|0}if(j&49152|0){E=(c[f+24660>>2]|0)+(l<<2)|0;C=c[(h+43984|0)+4>>2]|0;c[E>>2]=c[(h+43984|0)>>2];c[E+4>>2]=C;C=c[(h+43992|0)+4>>2]|0;c[(E+8|0)>>2]=c[(h+43992|0)>>2];c[(E+8|0)+4>>2]=C;C=c[(h+44016|0)+4>>2]|0;c[(E+(k<<2)|0)>>2]=c[(h+44016|0)>>2];c[(E+(k<<2)|0)+4>>2]=C;C=c[(h+44024|0)+4>>2]|0;c[((E+(k<<2)|0)+8|0)>>2]=c[(h+44024|0)>>2];c[((E+(k<<2)|0)+8|0)+4>>2]=C;C=c[(h+44048|0)+4>>2]|0;c[(E+(k<<1<<2)|0)>>2]=c[(h+44048|0)>>2];c[(E+(k<<1<<2)|0)+4>>2]=C;C=c[(h+44056|0)+4>>2]|0;c[((E+(k<<1<<2)|0)+8|0)>>2]=c[(h+44056|0)>>2];c[((E+(k<<1<<2)|0)+8|0)+4>>2]=C;C=c[(h+44080|0)+4>>2]|0;c[(E+(k*3<<2)|0)>>2]=c[(h+44080|0)>>2];c[(E+(k*3<<2)|0)+4>>2]=C;C=c[(h+44088|0)+4>>2]|0;c[((E+(k*3<<2)|0)+8|0)>>2]=c[(h+44088|0)>>2];c[((E+(k*3<<2)|0)+8|0)+4>>2]=C;E=(c[f+24692>>2]|0)+m|0;a[E>>0]=a[h+44148>>0]|0;a[E+1>>0]=a[h+44150>>0]|0;a[E+2>>0]=a[h+44164>>0]|0;a[E+3>>0]=a[h+44166>>0]|0}}j=c[(K+156|0)>>2]|0;do if(!(j&2)){j=c[(h+12|0)>>2]|0;k=(c[(h+4|0)>>2]|0)+(j>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(j&7);do if(k>>>0>134217727){c[(h+12|0)>>2]=(d[86681+(k>>>23)>>0]|0)+j;k=d[87193+(k>>>23)>>0]|0}else{E=k>>>0>65535?k>>>16:k;l=k>>>0>65535?16:0;l=(d[97868+((E&65280|0)==0?E:E>>>8)>>0]|0)+((E&65280|0)==0?l:l|8)<<1;c[(h+12|0)>>2]=j+63-l;if((l+-31|0|0)<7){UC(0,16,66830,K+96|0);k=-1094995529;break}else{k=(k>>>(l+-31|0))+-1|0;break}}while(0);if(v){if(k>>>0>47){f=c[f+4>>2]|0;J=c[(h+21764|0)>>2]|0;h=c[(h+21768|0)>>2]|0;c[(K+104|0)>>2]=k;c[(K+104|0)+4>>2]=J;c[(K+104|0)+8>>2]=h;UC(f,16,61593,K+104|0);h=-1;i=K;return h|0}j=c[(K+156|0)>>2]|0;if(!(j&1)){u=d[61670+k>>0]|0;break}else{u=d[61622+k>>0]|0;break}}else{if(k>>>0>15){f=c[f+4>>2]|0;J=c[(h+21764|0)>>2]|0;h=c[(h+21768|0)>>2]|0;c[(K+120|0)>>2]=k;c[(K+120|0)+4>>2]=J;c[(K+120|0)+8>>2]=h;UC(f,16,61593,K+120|0);h=-1;i=K;return h|0}j=c[(K+156|0)>>2]|0;if(!(j&1)){u=d[61734+k>>0]|0;break}else{u=d[61718+k>>0]|0;break}}}else if(u>>>0>15&(v^1)){UC(c[f+4>>2]|0,16,96641,K+136|0);h=-1094995529;i=K;return h|0}while(0);s=(u&15|0)==0;if((j&7|0)==0&(((w|0)==0|s)^1)){C=c[(h+12|0)>>2]|0;E=d[(c[(h+4|0)>>2]|0)+(C>>>3)>>0]<<(C&7);c[(h+12|0)>>2]=C+1;j=E<<17&16777216|j;c[(K+156|0)>>2]=j}b[(c[(f+197624|0)>>2]|0)+(J<<1)>>1]=u;c[h+21824>>2]=u&65535;c[(c[f+24668>>2]|0)+(J<<2)>>2]=j;o=c[(K+156|0)>>2]|0;c:do{j=1;if(j){j=0;if(!(u|o&2)){c[h+43660>>2]=0;c[h+43668>>2]=0;c[h+43676>>2]=0;c[h+43684>>2]=0;c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;x=h+824|0}else j=1}if(j)if(1){n=(c[f+27844>>2]|0)*6|0;m=c[(h+824|0)>>2]|0;if(!(o&128)){w=(m|0)!=0?f+197724|0:f+198012|0;v=(m|0)!=0?f+197644|0:f+197932|0}else{w=(m|0)!=0?f+197868|0:f+198156|0;v=(m|0)!=0?f+197788|0:f+198076|0}j=c[(h+12|0)>>2]|0;k=c[(h+4|0)>>2]|0;l=(MF(d[(k+(j>>>3)|0)>>0]|d[(k+(j>>>3)|0)+1>>0]<<8|d[(k+(j>>>3)|0)+2>>0]<<16|d[(k+(j>>>3)|0)+3>>0]<<24|0)|0)<<(j&7);if(l>>>0>134217727){c[(h+12|0)>>2]=(d[86681+(l>>>23)>>0]|0)+j;k=a[87705+(l>>>23)>>0]|0}else{C=l>>>0>65535?l>>>16:l;E=l>>>0>65535?16:0;E=(d[97868+((C&65280|0)==0?C:C>>>8)>>0]|0)+((C&65280|0)==0?E:E|8)|0;k=k+((31-E+j|0)>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<((31-E+j|0)&7)>>>E;c[(h+12|0)>>2]=32-E+(31-E+j|0);k=(k>>>1^0-(k&1))+(k&1)|0}j=m+k|0;c[(h+824|0)>>2]=j;if(j>>>0>(n+3|0)>>>0){j=((j|0)<0?n+4|0:-4-n|0)+j|0;c[(h+824|0)>>2]=j;if(j>>>0>(n+3|0)>>>0){f=c[f+4>>2]|0;J=c[(h+21764|0)>>2]|0;h=c[(h+21768|0)>>2]|0;c[(K+144|0)>>2]=k;c[(K+144|0)+4>>2]=J;c[(K+144|0)+8>>2]=h;UC(f,16,61750,K+144|0);h=-1;i=K;return h|0}else r=j}else r=j;c[(h+828|0)>>2]=d[f+28412+r>>0];c[(h+832|0)>>2]=d[f+28500+r>>0];d:do if(!(o&2)){q=o&7|0?0:3;e:do if(!(o&16777216)){o=0;j=0;while(1){k=o<<2;if(!(1<<o&u)){E=(d[96537+k>>0]|0)+(h+43648)|0;a[E+9>>0]=0;a[E+8>>0]=0;a[E+1>>0]=0;a[E>>0]=0}else{if((nc(f,h,h+4|0,h+44384+(o<<6<<I<<1)|0,k,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}l=a[(d[96537+k>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|1)<<4<<I<<1)|0,k|1,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}m=a[(d[96537+(k|1)>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|2)<<4<<I<<1)|0,k|2,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}n=a[(d[96537+(k|2)>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|3)<<4<<I<<1)|0,k|3,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}j=(l&255)<<o|j|(m&255)<<o|(n&255)<<o|d[(d[96537+(k|3)>>0]|0)+(h+43648)>>0]<<o}o=o+1|0;if((o|0)>=4){D=j;break e}}if((E|0)==566){i=K;return F|0}}else{m=w+16|0;n=w+32|0;o=w+48|0;p=0;j=0;while(1){if(!(1<<p&u)){E=(d[96537+(p<<2)>>0]|0)+(h+43648)|0;a[E+9>>0]=0;a[E+8>>0]=0;a[E+1>>0]=0;a[E>>0]=0}else{k=h+44384+(p<<6<<I<<1)|0;l=p<<2;if((nc(f,h,h+4|0,k,l,w,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|1,m,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|2,n,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|3,o,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}C=(d[96537+l>>0]|0)+(h+43648)|0;E=(d[C+8>>0]|0)+(d[C+1>>0]|0)+(d[C+9>>0]|0)+(d[C>>0]|0)&255;a[C>>0]=E;j=(E<<24>>24!=0&1)<<p|j}p=p+1|0;if((p|0)>=4){D=j;break e}}if((E|0)==566){i=K;return F|0}}while(0);if((D|0)<0){h=-1;i=K;return h|0}else z=D}else{j=h+47456|0;k=j+64|0;do{c[j>>2]=0;j=j+4|0}while((j|0)<(k|0));if((nc(f,h,h+4|0,h+47456|0,48,v,0,16)|0)<0){h=-1;i=K;return h|0}if(s){c[h+43660>>2]=0;c[h+43668>>2]=0;c[h+43676>>2]=0;c[h+43684>>2]=0;z=0;break}j=v+1|0;l=0;while(1){k=l<<2;if((nc(f,h,h+4|0,h+44384+(l<<6<<I<<1)|0,k,j,(c[(f+197556|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|1)<<4<<I<<1)|0,k|1,j,(c[(f+197556|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|2)<<4<<I<<1)|0,k|2,j,(c[(f+197556|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|3)<<4<<I<<1)|0,k|3,j,(c[(f+197556|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}l=l+1|0;if((l|0)>=4){z=15;break d}}if((E|0)==566){i=K;return F|0}}while(0);n=(c[(f+197624|0)>>2]|0)+(J<<1)|0;b[n>>1]=e[n>>1]|z<<12;n=c[(f+26672|0)>>2]|0;if((n|0)!=3){do if(u&48|0){if((nc(f,h,h+4|0,h+44384+(256<<I<<1)|0,49,(n|0)==2?96827:61893,0,n<<2)|0)<0){h=-1;i=K;return h|0}if((nc(f,h,h+4|0,h+44384+(512<<I<<1)|0,50,(c[(f+26672|0)>>2]|0)==2?96827:61893,0,n<<2)|0)<0)F=-1;else break;i=K;return F|0}while(0);if(!(u&32)){c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;x=h+824|0;break}o=v+1|0;if((n|0)<=0){x=h+824|0;break}k=(c[f+197556+((c[(K+156|0)>>2]&7|0?1:4)<<2)>>2]|0)+(c[h+828>>2]<<6)|0;m=0;j=h+44384+(256<<I<<1)|0;while(1){l=(m<<3)+16|0;if((nc(f,h,h+4|0,j,l,o,k,15)|0)<=-1){F=-1;E=566;break}j=j+(16<<I<<1)|0;if((nc(f,h,h+4|0,j,l|1,o,k,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,j+(16<<I<<1)|0,l|2,o,k,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,(j+(16<<I<<1)|0)+(16<<I<<1)|0,l|3,o,k,15)|0)<=-1){F=-1;E=566;break}m=m+1|0;if((m|0)>=(n|0)){E=561;break}else j=((j+(16<<I<<1)|0)+(16<<I<<1)|0)+(16<<I<<1)|0}if((E|0)==561){k=(c[f+197556+((c[(K+156|0)>>2]&7|0?2:5)<<2)>>2]|0)+(c[h+832>>2]<<6)|0;m=0;j=h+44384+(512<<I<<1)|0;while(1){l=(m<<3)+32|0;if((nc(f,h,h+4|0,j,l,o,k,15)|0)<=-1){F=-1;E=566;break}j=j+(16<<I<<1)|0;if((nc(f,h,h+4|0,j,l|1,o,k,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,j+(16<<I<<1)|0,l|2,o,k,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,(j+(16<<I<<1)|0)+(16<<I<<1)|0,l|3,o,k,15)|0)<=-1){F=-1;E=566;break}m=m+1|0;if((m|0)>=(n|0)){x=h+824|0;break c}else j=((j+(16<<I<<1)|0)+(16<<I<<1)|0)+(16<<I<<1)|0}if((E|0)==566){i=K;return F|0}}else if((E|0)==566){i=K;return F|0}}j=c[(K+156|0)>>2]|0;r=c[(h+828|0)>>2]|0;f:do if(!(j&2)){q=j&7|0?1:4;g:do if(!(j&16777216)){o=0;j=0;while(1){k=(o<<2)+16|0;if(!(1<<o&u)){F=(d[96537+k>>0]|0)+(h+43648)|0;a[F+9>>0]=0;a[F+8>>0]=0;a[F+1>>0]=0;a[F>>0]=0}else{if((nc(f,h,h+4|0,h+44384+(k<<4<<I<<1)|0,k,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}l=a[(d[96537+k>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|1)<<4<<I<<1)|0,k|1,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}m=a[(d[96537+(k|1)>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|2)<<4<<I<<1)|0,k|2,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}n=a[(d[96537+(k|2)>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|3)<<4<<I<<1)|0,k|3,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}j=(l&255)<<o|j|(m&255)<<o|(n&255)<<o|d[(d[96537+(k|3)>>0]|0)+(h+43648)>>0]<<o}o=o+1|0;if((o|0)>=4){G=j;break g}}if((E|0)==566){i=K;return F|0}}else{m=w+16|0;n=w+32|0;o=w+48|0;p=0;j=0;while(1){if(!(1<<p&u)){F=(d[96537+((p<<2)+16)>>0]|0)+(h+43648)|0;a[F+9>>0]=0;a[F+8>>0]=0;a[F+1>>0]=0;a[F>>0]=0}else{k=h+44384+((p<<6)+256<<I<<1)|0;l=(p<<2)+16|0;if((nc(f,h,h+4|0,k,l,w,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|1,m,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|2,n,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|3,o,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}E=(d[96537+l>>0]|0)+(h+43648)|0;F=(d[E+8>>0]|0)+(d[E+1>>0]|0)+(d[E+9>>0]|0)+(d[E>>0]|0)&255;a[E>>0]=F;j=(F<<24>>24!=0&1)<<p|j}p=p+1|0;if((p|0)>=4){G=j;break g}}if((E|0)==566){i=K;return F|0}}while(0);if((G|0)<0){h=-1;i=K;return h|0}}else{j=h+47520|0;k=j+64|0;do{c[j>>2]=0;j=j+4|0}while((j|0)<(k|0));if((nc(f,h,h+4|0,h+47520|0,49,v,0,16)|0)<0){h=-1;i=K;return h|0}if(s){c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;break}j=v+1|0;l=0;while(1){k=(l<<2)+16|0;if((nc(f,h,h+4|0,h+44384+(k<<4<<I<<1)|0,k,j,(c[(f+197560|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|1)<<4<<I<<1)|0,k|1,j,(c[(f+197560|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|2)<<4<<I<<1)|0,k|2,j,(c[(f+197560|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|3)<<4<<I<<1)|0,k|3,j,(c[(f+197560|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}l=l+1|0;if((l|0)>=4)break f}if((E|0)==566){i=K;return F|0}}while(0);m=c[(K+156|0)>>2]|0;r=c[(h+832|0)>>2]|0;if(m&2|0){j=h+47584|0;k=j+64|0;do{c[j>>2]=0;j=j+4|0}while((j|0)<(k|0));if((nc(f,h,h+4|0,h+47584|0,50,v,0,16)|0)<0){h=-1;i=K;return h|0}if(s){c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;x=h+824|0;break}j=v+1|0;l=0;while(1){k=(l<<2)+32|0;if((nc(f,h,h+4|0,h+44384+(k<<4<<I<<1)|0,k,j,(c[(f+197564|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|1)<<4<<I<<1)|0,k|1,j,(c[(f+197564|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|2)<<4<<I<<1)|0,k|2,j,(c[(f+197564|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,h+44384+((k|3)<<4<<I<<1)|0,k|3,j,(c[(f+197564|0)>>2]|0)+(r<<6)|0,15)|0)<=-1){F=-1;E=566;break}l=l+1|0;if((l|0)>=4){x=h+824|0;break c}}if((E|0)==566){i=K;return F|0}}q=m&7|0?2:5;h:do if(!(m&16777216)){o=0;j=0;while(1){k=(o<<2)+32|0;if(!(1<<o&u)){G=(d[96537+k>>0]|0)+(h+43648)|0;a[G+9>>0]=0;a[G+8>>0]=0;a[G+1>>0]=0;a[G>>0]=0}else{if((nc(f,h,h+4|0,h+44384+(k<<4<<I<<1)|0,k,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}l=a[(d[96537+k>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|1)<<4<<I<<1)|0,k|1,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}m=a[(d[96537+(k|1)>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|2)<<4<<I<<1)|0,k|2,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}n=a[(d[96537+(k|2)>>0]|0)+(h+43648)>>0]|0;if((nc(f,h,h+4|0,h+44384+((k|3)<<4<<I<<1)|0,k|3,v,(c[(f+197556+(q<<2)|0)>>2]|0)+(r<<6)|0,16)|0)<0){F=-1;E=566;break}j=(l&255)<<o|j|(m&255)<<o|(n&255)<<o|d[(d[96537+(k|3)>>0]|0)+(h+43648)>>0]<<o}o=o+1|0;if((o|0)>=4){H=j;break h}}if((E|0)==566){i=K;return F|0}}else{m=w+16|0;n=w+32|0;o=w+48|0;p=0;j=0;while(1){if(!(1<<p&u)){G=(d[96537+((p<<2)+32)>>0]|0)+(h+43648)|0;a[G+9>>0]=0;a[G+8>>0]=0;a[G+1>>0]=0;a[G>>0]=0}else{k=h+44384+((p<<6)+512<<I<<1)|0;l=(p<<2)+32|0;if((nc(f,h,h+4|0,k,l,w,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|1,m,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|2,n,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}if((nc(f,h,h+4|0,k,l|3,o,(c[(f+197580+(q<<2)|0)>>2]|0)+(r<<8)|0,16)|0)<=-1){F=-1;E=566;break}F=(d[96537+l>>0]|0)+(h+43648)|0;G=(d[F+8>>0]|0)+(d[F+1>>0]|0)+(d[F+9>>0]|0)+(d[F>>0]|0)&255;a[F>>0]=G;j=(G<<24>>24!=0&1)<<p|j}p=p+1|0;if((p|0)>=4){H=j;break h}}if((E|0)==566){i=K;return F|0}}while(0);if((H|0)<0){h=-1;i=K;return h|0}else x=h+824|0}}while(0);a[(c[f+24644>>2]|0)+J>>0]=c[x>>2];j=c[(h+21772|0)>>2]|0;k=c[f+26256>>2]|0;c[k+(j*48|0)>>2]=c[h+43660>>2];c[k+(j*48|0)+4>>2]=c[h+43668>>2];c[k+(j*48|0)+8>>2]=c[h+43676>>2];c[k+(j*48|0)+12>>2]=c[h+43684>>2];c[k+(j*48|0)+16>>2]=c[h+43700>>2];c[k+(j*48|0)+20>>2]=c[h+43708>>2];c[k+(j*48|0)+32>>2]=c[h+43740>>2];c[k+(j*48|0)+36>>2]=c[h+43748>>2];if(c[f+25976>>2]|0){h=0;i=K;return h|0}c[k+(j*48|0)+24>>2]=c[h+43716>>2];c[k+(j*48|0)+28>>2]=c[h+43724>>2];c[k+(j*48|0)+40>>2]=c[h+43756>>2];c[k+(j*48|0)+44>>2]=c[h+43764>>2];h=0;i=K;return h|0}function kc(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;f=c[b+21772>>2]|0;g=b+21728|0;c[g>>2]=-1;p=c[a+198236>>2]|0;j=f-(p<<c[b+21796>>2])|0;m=j+-1|0;l=j+1|0;n=f+-1|0;k=b+21724|0;c[k>>2]=61284;r=c[a+24668>>2]|0;do if(!(c[a+197608>>2]|0)){q=n;o=j}else{h=c[r+(n<<2)>>2]&128;i=d&128;if(c[b+21768>>2]&1|0){if((h|0)==(i|0)){q=n;o=j;break}d=f-p+-1|0;if(!i){c[g>>2]=0;c[k>>2]=61316;q=d;n=d;o=j;m=p+m|0;break}else{c[k>>2]=61380;q=d;o=j;break}}d=(i|0)!=0;if(d){f=(((c[r+(j<<2)>>2]|0)>>>7&1)+-1&p)+j|0;g=(((c[r+(m<<2)>>2]|0)>>>7&1)+-1&p)+m|0;l=(((c[r+(l<<2)>>2]|0)>>>7&1)+-1&p)+l|0}else{f=j;g=m}if((h|0)==(i|0)){q=n;o=f;m=g}else if(d){c[k>>2]=61380;q=n;n=p+n|0;o=f;m=g;break}else{c[k>>2]=61348;q=n;o=f;m=g;break}}while(0);c[b+21684>>2]=m;c[b+21688>>2]=o;c[b+21692>>2]=l;c[b+21696>>2]=q;c[b+21700>>2]=n;g=b+21704|0;c[g>>2]=c[r+(m<<2)>>2];h=b+21708|0;c[h>>2]=c[r+(o<<2)>>2];i=b+21712|0;c[i>>2]=c[r+(l<<2)>>2];j=b+21716|0;c[j>>2]=c[r+(q<<2)>>2];k=b+21720|0;c[k>>2]=c[r+(n<<2)>>2];f=c[a+197604>>2]|0;d=c[b+808>>2]|0;if((e[f+(m<<1)>>1]|0|0)!=(d|0)){c[g>>2]=0;if((e[f+(o<<1)>>1]|0|0)!=(d|0))c[h>>2]=0;if((e[f+(q<<1)>>1]|0|0)!=(d|0)){c[k>>2]=0;c[j>>2]=0}}if((e[f+(l<<1)>>1]|0|0)==(d|0))return;c[i>>2]=0;return}function lc(e,f,g){e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0;da=i;i=i+16|0;s=da+8|0;r=da;T=c[f+21724>>2]|0;$=c[f+21684>>2]|0;aa=c[f+21688>>2]|0;ba=c[f+21692>>2]|0;ca=c[f+21696>>2]|0;c[s>>2]=ca;U=c[f+21700>>2]|0;c[s+4>>2]=U;V=c[f+21704>>2]|0;W=c[f+21708>>2]|0;X=c[f+21712>>2]|0;Y=c[f+21716>>2]|0;c[r>>2]=Y;Z=c[f+21720>>2]|0;c[r+4>>2]=Z;a:do if(!(g&2048)){if(g&7|0){p=c[e+27920>>2]|0?7:-1;k=f+21744|0;c[k>>2]=65535;h=f+21736|0;c[h>>2]=65535;m=f+21732|0;c[m>>2]=65535;n=f+21740|0;c[n>>2]=61162;o=(p&W|0)!=0;if(o){h=65535;l=60138}else{c[m>>2]=46079;c[h>>2]=13311;c[n>>2]=9962;h=46079;l=8938}j=g&128;do if((j|0)==(Y&128|0)){if(!(Y&p)){h=h&57183;c[m>>2]=h;c[k>>2]=24415}}else{if(!j){if(!((Y&p|0)==0?1:(c[(c[e+24668>>2]|0)+((c[e+198236>>2]|0)+ca<<2)>>2]&p|0)==0))break;h=h&57183;c[m>>2]=h;c[k>>2]=24415;break}if(!(Y&p)){h=h&57343;c[m>>2]=h;c[k>>2]=24575;j=24415}else j=65375;if(!(Z&p)){h=h&65375;c[m>>2]=h;c[k>>2]=j}}while(0);if(!(p&V))c[m>>2]=h&32767;if(!(p&X))c[n>>2]=l;if(g&1|0){if(!(W&1)){FF(f+21644|0,2-(0-(o&1^1)&3)&255|0,4)|0;h=f+21680|0;j=e+26648|0}else{h=f+21680|0;j=e+26648|0;c[f+21644>>2]=c[(c[h>>2]|0)+(c[(c[j>>2]|0)+(aa<<2)>>2]|0)>>2]}if(!(Y&1)){k=(Y&p|0)==0?-1:2;a[f+21651>>0]=k}else{k=(c[h>>2]|0)+(c[(c[j>>2]|0)+(ca<<2)>>2]|0)|0;a[f+21651>>0]=a[k+(6-(d[T>>0]|0))>>0]|0;k=a[k+(6-(d[T+1>>0]|0))>>0]|0}a[f+21659>>0]=k;if(!(Z&1)){h=(Z&p|0)==0?-1:2;a[f+21667>>0]=h}else{h=(c[h>>2]|0)+(c[(c[j>>2]|0)+(U<<2)>>2]|0)|0;a[f+21667>>0]=a[h+(6-(d[T+2>>0]|0))>>0]|0;h=a[h+(6-(d[T+3>>0]|0))>>0]|0}a[f+21675>>0]=h}}do if(!W){c[f+43652>>2]=1077952576;c[f+43692>>2]=1077952576;c[f+43732>>2]=1077952576;h=e+26256|0}else{h=e+26256|0;j=c[h>>2]|0;c[f+43652>>2]=c[j+(aa*48|0)+12>>2];if(!(c[e+25976>>2]|0)){c[f+43692>>2]=c[j+(aa*48|0)+28>>2];c[f+43732>>2]=c[j+(aa*48|0)+44>>2];break}else{c[f+43692>>2]=c[j+(aa*48|0)+20>>2];c[f+43732>>2]=c[j+(aa*48|0)+36>>2];break}}while(0);q=e+26672|0;k=Y;j=0;while(1){b:do if(!k){S=j<<4;a[S+99+(f+43648)>>0]=64;a[S+91+(f+43648)>>0]=64;a[S+59+(f+43648)>>0]=64;a[S+51+(f+43648)>>0]=64;a[S+19+(f+43648)>>0]=64;a[(S|11)+(f+43648)>>0]=64}else{k=c[s+(j<<2)>>2]|0;l=c[h>>2]|0;m=j<<1;n=T+(m+8)|0;o=j<<4;a[(o|11)+(f+43648)>>0]=a[(d[n>>0]|0)+(l+(k*48|0))>>0]|0;p=T+(m+9)|0;a[o+19+(f+43648)>>0]=a[(d[p>>0]|0)+(l+(k*48|0))>>0]|0;switch(c[q>>2]|0){case 3:{a[o+51+(f+43648)>>0]=a[(d[n>>0]|0)+16+(l+(k*48|0))>>0]|0;a[o+59+(f+43648)>>0]=a[(d[p>>0]|0)+16+(l+(k*48|0))>>0]|0;a[o+91+(f+43648)>>0]=a[(d[n>>0]|0)+32+(l+(k*48|0))>>0]|0;a[o+99+(f+43648)>>0]=a[(d[p>>0]|0)+32+(l+(k*48|0))>>0]|0;break b}case 2:{a[o+51+(f+43648)>>0]=a[(d[n>>0]|0)+14+(l+(k*48|0))>>0]|0;a[o+59+(f+43648)>>0]=a[(d[p>>0]|0)+14+(l+(k*48|0))>>0]|0;a[o+91+(f+43648)>>0]=a[(d[n>>0]|0)+30+(l+(k*48|0))>>0]|0;a[o+99+(f+43648)>>0]=a[(d[p>>0]|0)+30+(l+(k*48|0))>>0]|0;break b}default:{S=j<<3;a[S+51+(f+43648)>>0]=a[(d[T+(m+12)>>0]|0)+(l+(k*48|0))>>0]|0;a[S+91+(f+43648)>>0]=a[(d[T+(m+13)>>0]|0)+(l+(k*48|0))>>0]|0;break b}}}while(0);j=j+1|0;if((j|0)==2)break a;k=c[r+(j<<2)>>2]|0}}while(0);if(!(g&120)){if(!(g&256)){g=W>>>24;g=g&1;e=Y>>>24;e=e&1;g=e+g|0;f=f+21808|0;c[f>>2]=g;i=da;return}if(!(c[f+21812>>2]|0)){g=W>>>24;g=g&1;e=Y>>>24;e=e&1;g=e+g|0;f=f+21808|0;c[f>>2]=g;i=da;return}}o=c[e+26652>>2]|0;p=f+23380|0;if(!(c[p>>2]|0)){g=W>>>24;g=g&1;e=Y>>>24;e=e&1;g=e+g|0;f=f+21808|0;c[f>>2]=g;i=da;return}q=W|0?-1:-16843010;r=(g&80|0)==0;s=Y|0?-1:-2;t=e+26644|0;u=ca<<2|1;v=X|0?-1:-2;w=V|0?-1:-2;x=f+21728|0;y=o<<1;z=o+3|0;A=$<<2;B=(g&2304|0)==0;C=e+197608|0;D=f+21796|0;E=(V&128|0)!=0;F=(W&128|0)==0;G=(X&128|0)==0;H=(Y&128|0)==0;I=(Z&128|0)==0;J=o*3|0;K=ba<<2|2;M=aa<<2;L=M|2;M=M|3;N=Z|0?-1:-2;O=U<<2|1;P=T+2|0;Q=T+3|0;R=T+1|0;S=0;do{j=c[e+24688+(S<<2)>>2]|0;k=c[e+24656+(S<<2)>>2]|0;l=12288<<(S<<1);do if(l&g|0){if(!(l&W)){n=f+43776+(S*160|0)+16|0;c[n>>2]=0;c[n+4>>2]=0;c[n+8>>2]=0;c[n+12>>2]=0;c[f+44096+(S*40|0)+4>>2]=q}else{h=(c[(c[t>>2]|0)+(aa<<2)>>2]|0)+J|0;ea=k+(h<<2)|0;n=c[ea+4>>2]|0;m=f+43776+(S*160|0)+16|0;c[m>>2]=c[ea>>2];c[m+4>>2]=n;h=k+(h<<2)+8|0;m=c[h+4>>2]|0;n=f+43776+(S*160|0)+24|0;c[n>>2]=c[h>>2];c[n+4>>2]=m;n=a[j+L>>0]|0;a[f+44096+(S*40|0)+5>>0]=n;a[f+44096+(S*40|0)+4>>0]=n;n=a[j+M>>0]|0;a[f+44096+(S*40|0)+7>>0]=n;a[f+44096+(S*40|0)+6>>0]=n}h=(Y&l|0)==0;do if(r)if(h){c[f+43776+(S*160|0)+44>>2]=0;a[f+44096+(S*40|0)+11>>0]=s;break}else{c[f+43776+(S*160|0)+44>>2]=c[k+((c[(c[t>>2]|0)+(ca<<2)>>2]|0)+3+(_(d[T>>0]|0,o)|0)<<2)>>2];a[f+44096+(S*40|0)+11>>0]=a[j+((d[T>>0]&254)+u)>>0]|0;break}else{if(h){c[f+43776+(S*160|0)+44>>2]=0;c[f+43776+(S*160|0)+76>>2]=0;a[f+44096+(S*40|0)+11>>0]=s;h=s}else{h=(c[(c[t>>2]|0)+(ca<<2)>>2]|0)+3|0;c[f+43776+(S*160|0)+44>>2]=c[k+((_(d[T>>0]|0,o)|0)+h<<2)>>2];c[f+43776+(S*160|0)+76>>2]=c[k+((_(d[R>>0]|0,o)|0)+h<<2)>>2];a[f+44096+(S*40|0)+11>>0]=a[j+((d[T>>0]&254)+u)>>0]|0;h=a[j+((d[R>>0]&254)+u)>>0]|0}a[f+44096+(S*40|0)+19>>0]=h;if(!(Z&l)){c[f+43776+(S*160|0)+108>>2]=0;c[f+43776+(S*160|0)+140>>2]=0;a[f+44096+(S*40|0)+27>>0]=N;h=N}else{h=(c[(c[t>>2]|0)+(U<<2)>>2]|0)+3|0;c[f+43776+(S*160|0)+108>>2]=c[k+((_(d[P>>0]|0,o)|0)+h<<2)>>2];c[f+43776+(S*160|0)+140>>2]=c[k+((_(d[Q>>0]|0,o)|0)+h<<2)>>2];a[f+44096+(S*40|0)+27>>0]=a[j+((d[P>>0]&254)+O)>>0]|0;h=a[j+((d[Q>>0]&254)+O)>>0]|0}a[f+44096+(S*40|0)+35>>0]=h}while(0);if(!(l&X)){c[f+43776+(S*160|0)+32>>2]=0;h=v}else{c[f+43776+(S*160|0)+32>>2]=c[k+((c[(c[t>>2]|0)+(ba<<2)>>2]|0)+J<<2)>>2];h=a[j+K>>0]|0}n=f+44096+(S*40|0)+8|0;a[n>>0]=h;m=f+44096+(S*40|0)+6|0;do if((a[m>>0]|h)<<24>>24<0)if(!(l&V)){c[f+43776+(S*160|0)+12>>2]=0;a[f+44096+(S*40|0)+3>>0]=w;break}else{ea=c[x>>2]|0;c[f+43776+(S*160|0)+12>>2]=c[k+(z+(c[(c[t>>2]|0)+($<<2)>>2]|0)+(ea&y)<<2)>>2];a[f+44096+(S*40|0)+3>>0]=a[j+(A|ea&2|1)>>0]|0;break}while(0);if(B){a[f+44096+(S*40|0)+30>>0]=-2;a[f+44096+(S*40|0)+14>>0]=-2;c[f+43776+(S*160|0)+56>>2]=0;c[f+43776+(S*160|0)+120>>2]=0;if(!(c[C>>2]|0))break}else if(!(c[C>>2]|0))break;if(!(c[D>>2]|0)){if(E){h=f+44096+(S*40|0)+3|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+14|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+7|0;a[ea>>0]=d[ea>>0]<<1}}if(!F){h=f+44096+(S*40|0)+4|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+18|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+9|0;a[ea>>0]=d[ea>>0]<<1}h=f+44096+(S*40|0)+5|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+22|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+11|0;a[ea>>0]=d[ea>>0]<<1}h=a[m>>0]|0;if(h<<24>>24>-1){a[m>>0]=h<<24>>24>>1;ea=f+43776+(S*160|0)+26|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+13|0;a[ea>>0]=d[ea>>0]<<1}h=f+44096+(S*40|0)+7|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+30|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+15|0;a[ea>>0]=d[ea>>0]<<1}}if(!G){h=a[n>>0]|0;if(h<<24>>24>-1){a[n>>0]=h<<24>>24>>1;ea=f+43776+(S*160|0)+34|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+17|0;a[ea>>0]=d[ea>>0]<<1}}if(!H){h=f+44096+(S*40|0)+11|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+46|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+23|0;a[ea>>0]=d[ea>>0]<<1}h=f+44096+(S*40|0)+19|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+78|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+39|0;a[ea>>0]=d[ea>>0]<<1}}if(I)break;h=f+44096+(S*40|0)+27|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+110|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+55|0;a[ea>>0]=d[ea>>0]<<1}h=f+44096+(S*40|0)+35|0;j=a[h>>0]|0;if(j<<24>>24<=-1)break;a[h>>0]=j<<24>>24>>1;ea=f+43776+(S*160|0)+142|0;b[ea>>1]=b[ea>>1]<<1;ea=f+44176+(S*80|0)+71|0;a[ea>>0]=d[ea>>0]<<1;break}else{if(!E){h=f+44096+(S*40|0)+3|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+14|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+7|0;a[ea>>0]=(d[ea>>0]|0)>>>1}}if(F){h=f+44096+(S*40|0)+4|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+18|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+9|0;a[ea>>0]=(d[ea>>0]|0)>>>1}h=f+44096+(S*40|0)+5|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+22|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+11|0;a[ea>>0]=(d[ea>>0]|0)>>>1}h=a[m>>0]|0;if(h<<24>>24>-1){a[m>>0]=h<<24>>24<<1;ea=f+43776+(S*160|0)+26|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+13|0;a[ea>>0]=(d[ea>>0]|0)>>>1}h=f+44096+(S*40|0)+7|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+30|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+15|0;a[ea>>0]=(d[ea>>0]|0)>>>1}}if(G){h=a[n>>0]|0;if(h<<24>>24>-1){a[n>>0]=h<<24>>24<<1;ea=f+43776+(S*160|0)+34|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+17|0;a[ea>>0]=(d[ea>>0]|0)>>>1}}if(H){h=f+44096+(S*40|0)+11|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+46|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+23|0;a[ea>>0]=(d[ea>>0]|0)>>>1}h=f+44096+(S*40|0)+19|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+78|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+39|0;a[ea>>0]=(d[ea>>0]|0)>>>1}}if(!I)break;h=f+44096+(S*40|0)+27|0;j=a[h>>0]|0;if(j<<24>>24>-1){a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+110|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+55|0;a[ea>>0]=(d[ea>>0]|0)>>>1}h=f+44096+(S*40|0)+35|0;j=a[h>>0]|0;if(j<<24>>24<=-1)break;a[h>>0]=j<<24>>24<<1;ea=f+43776+(S*160|0)+142|0;b[ea>>1]=(b[ea>>1]|0)/2|0;ea=f+44176+(S*80|0)+71|0;a[ea>>0]=(d[ea>>0]|0)>>>1;break}}while(0);S=S+1|0}while(S>>>0<(c[p>>2]|0)>>>0);g=W>>>24;g=g&1;ea=Y>>>24;ea=ea&1;g=ea+g|0;ea=f+21808|0;c[ea>>2]=g;i=da;return}function mc(f){f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0;P=c[f+224>>2]|0;R=c[f+232>>2]|0;Q=c[f+584>>2]|0;O=c[f+832>>2]|0;K=c[f+4800>>2]|0;N=c[f+5344>>2]|0;D=c[f+5824>>2]|0;h=c[f+6264>>2]|0;S=c[f+6384>>2]|0;T=c[f+6392>>2]|0;a:{if(T){T=0;if(!(N&7)){if(h<<24>>24==4){N=(c[(R+816|0)>>2]|0)==3;h=c[(R+12|0)>>2]|0;m=c[(R+4|0)>>2]|0;g=(MF(d[(m+(h>>>3)|0)>>0]|d[(m+(h>>>3)|0)+1>>0]<<8|d[(m+(h>>>3)|0)+2>>0]<<16|d[(m+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7)>>>23;h=(d[86681+g>>0]|0)+h|0;c[(R+12|0)>>2]=h;g=a[87193+g>>0]|0;b[(R+44376|0)>>1]=g&255;do if(N){if((g&255)>12)g=g&255;else{c[(f+192|0)>>2]=d[44516+((g&255)<<2)+2>>0];l=b[44516+((g&255)<<2)>>1]|0;b[(R+44376|0)>>1]=l;g=(MF(d[(m+(h>>>3)|0)>>0]|d[(m+(h>>>3)|0)+1>>0]<<8|d[(m+(h>>>3)|0)+2>>0]<<16|d[(m+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7)>>>23;h=(d[86681+g>>0]|0)+h|0;c[(R+12|0)>>2]=h;g=a[87193+g>>0]|0;b[(R+44378|0)>>1]=g&255;if((g&255)>12)g=g&255;else{c[(f+192|0)+4>>2]=d[44516+((g&255)<<2)+2>>0];k=b[44516+((g&255)<<2)>>1]|0;b[(R+44378|0)>>1]=k;g=(MF(d[(m+(h>>>3)|0)>>0]|d[(m+(h>>>3)|0)+1>>0]<<8|d[(m+(h>>>3)|0)+2>>0]<<16|d[(m+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7)>>>23;h=(d[86681+g>>0]|0)+h|0;c[(R+12|0)>>2]=h;g=a[87193+g>>0]|0;b[(R+44380|0)>>1]=g&255;if((g&255)>12)g=g&255;else{c[(f+192|0)+8>>2]=d[44516+((g&255)<<2)+2>>0];j=b[44516+((g&255)<<2)>>1]|0;b[(R+44380|0)>>1]=j;g=(MF(d[(m+(h>>>3)|0)>>0]|d[(m+(h>>>3)|0)+1>>0]<<8|d[(m+(h>>>3)|0)+2>>0]<<16|d[(m+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+h;g=a[87193+g>>0]|0;b[(R+44382|0)>>1]=g&255;if((g&255)>12)g=g&255;else{c[(f+192|0)+12>>2]=d[44516+((g&255)<<2)+2>>0];S=b[44516+((g&255)<<2)>>1]|0;b[(R+44382|0)>>1]=S;if(!((k|l|j|S)&256)){S=140;break}rc(P,R,f+156|0);a[R+44166>>0]=-2;a[R+44126>>0]=-2;a[R+44150>>0]=-2;a[R+44110>>0]=-2;S=140;break}}}}N=c[P+4>>2]|0;J=c[(R+21764|0)>>2]|0;K=c[(R+21768|0)>>2]|0;c[(f+32|0)>>2]=g;c[(f+32|0)+4>>2]=J;c[(f+32|0)+8>>2]=K;UC(N,16,61496,f+32|0)}else{if((g&255)>3)g=g&255;else{c[(f+192|0)>>2]=d[44568+((g&255)<<2)+2>>0];b[(R+44376|0)>>1]=b[44568+((g&255)<<2)>>1]|0;N=(MF(d[(m+(h>>>3)|0)>>0]|d[(m+(h>>>3)|0)+1>>0]<<8|d[(m+(h>>>3)|0)+2>>0]<<16|d[(m+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7)>>>23;g=(d[86681+N>>0]|0)+h|0;c[(R+12|0)>>2]=g;h=a[87193+N>>0]|0;b[(R+44378|0)>>1]=h&255;if((h&255)>3)g=h&255;else{c[(f+192|0)+4>>2]=d[44568+((h&255)<<2)+2>>0];b[(R+44378|0)>>1]=b[44568+((h&255)<<2)>>1]|0;h=(MF(d[(m+(g>>>3)|0)>>0]|d[(m+(g>>>3)|0)+1>>0]<<8|d[(m+(g>>>3)|0)+2>>0]<<16|d[(m+(g>>>3)|0)+3>>0]<<24|0)|0)<<(g&7)>>>23;g=(d[86681+h>>0]|0)+g|0;c[(R+12|0)>>2]=g;h=a[87193+h>>0]|0;b[(R+44380|0)>>1]=h&255;if((h&255)>3)g=h&255;else{c[(f+192|0)+8>>2]=d[44568+((h&255)<<2)+2>>0];b[(R+44380|0)>>1]=b[44568+((h&255)<<2)>>1]|0;N=(MF(d[(m+(g>>>3)|0)>>0]|d[(m+(g>>>3)|0)+1>>0]<<8|d[(m+(g>>>3)|0)+2>>0]<<16|d[(m+(g>>>3)|0)+3>>0]<<24|0)|0)<<(g&7)>>>23;c[(R+12|0)>>2]=(d[86681+N>>0]|0)+g;g=a[87193+N>>0]|0;b[(R+44382|0)>>1]=g&255;if((g&255)>3)g=g&255;else{c[(f+192|0)+12>>2]=d[44568+((g&255)<<2)+2>>0];b[(R+44382|0)>>1]=b[44568+((g&255)<<2)>>1]|0;S=140;break}}}}N=c[P+4>>2]|0;J=c[(R+21764|0)>>2]|0;K=c[(R+21768|0)>>2]|0;c[(f+48|0)>>2]=g;c[(f+48|0)+4>>2]=J;c[(f+48|0)+8>>2]=K;UC(N,16,61536,f+48|0)}while(0);b:do if((S|0)==140){m=c[(R+23380|0)>>2]|0;c:do if(m){k=(c[(f+156|0)>>2]&512|0)==0;l=0;d:while(1){e:do if(k){h=c[R+23372+(l<<2)>>2]<<c[(R+21800|0)>>2];j=4096<<(l<<1);switch(h|0){case 1:{r=j;S=148;break e}case 2:{g=e[(R+44376|0)>>1]|0;do if(!(g&256))if(!(g&j)){c[(f+160|0)+(l<<4)>>2]=-1;break}else{K=c[(R+12|0)>>2]|0;N=d[(c[(R+4|0)>>2]|0)+(K>>>3)>>0]<<(K&7)>>>7&1;c[(R+12|0)>>2]=K+1;c[(f+160|0)+(l<<4)>>2]=N^1;break}while(0);g=e[(R+44378|0)>>1]|0;do if(!(g&256))if(!(g&j)){c[(f+160|0)+(l<<4)+4>>2]=-1;break}else{K=c[(R+12|0)>>2]|0;N=d[(c[(R+4|0)>>2]|0)+(K>>>3)>>0]<<(K&7)>>>7&1;c[(R+12|0)>>2]=K+1;c[(f+160|0)+(l<<4)+4>>2]=N^1;break}while(0);g=e[(R+44380|0)>>1]|0;do if(!(g&256))if(!(g&j)){c[(f+160|0)+(l<<4)+8>>2]=-1;break}else{K=c[(R+12|0)>>2]|0;N=d[(c[(R+4|0)>>2]|0)+(K>>>3)>>0]<<(K&7)>>>7&1;c[(R+12|0)>>2]=K+1;c[(f+160|0)+(l<<4)+8>>2]=N^1;break}while(0);g=e[(R+44382|0)>>1]|0;if(g&256|0)break e;if(!(g&j)){c[(f+160|0)+(l<<4)+12>>2]=-1;break e}else{K=c[(R+12|0)>>2]|0;N=d[(c[(R+4|0)>>2]|0)+(K>>>3)>>0]<<(K&7)>>>7&1;c[(R+12|0)>>2]=K+1;c[(f+160|0)+(l<<4)+12>>2]=N^1;break e}}default:{}}g=e[(R+44376|0)>>1]|0;do if(!(g&256)){if(!(g&j)){c[(f+160|0)+(l<<4)>>2]=-1;break}N=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(N>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(N&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+N;g=d[87193+g>>0]|0;if(g>>>0>=h>>>0)break d;c[(f+160|0)+(l<<4)>>2]=g}while(0);g=e[(R+44378|0)>>1]|0;do if(!(g&256)){if(!(g&j)){c[(f+160|0)+(l<<4)+4>>2]=-1;break}N=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(N>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(N&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+N;g=d[87193+g>>0]|0;if(g>>>0>=h>>>0)break d;c[(f+160|0)+(l<<4)+4>>2]=g}while(0);g=e[(R+44380|0)>>1]|0;do if(!(g&256)){if(!(g&j)){c[(f+160|0)+(l<<4)+8>>2]=-1;break}N=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(N>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(N&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+N;g=d[87193+g>>0]|0;if(g>>>0>=h>>>0)break d;c[(f+160|0)+(l<<4)+8>>2]=g}while(0);g=e[(R+44382|0)>>1]|0;if(!(g&256)){if(!(g&j)){c[(f+160|0)+(l<<4)+12>>2]=-1;break}N=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(N>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(N&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+N;g=d[87193+g>>0]|0;if(g>>>0>=h>>>0)break d;c[(f+160|0)+(l<<4)+12>>2]=g}}else{r=4096<<(l<<1);S=148}while(0);do if((S|0)==148){S=0;g=e[(R+44376|0)>>1]|0;do if(!(g&256)){h=(f+160|0)+(l<<4)|0;if(!(g&r)){c[h>>2]=-1;break}else{c[h>>2]=0;break}}while(0);g=e[(R+44378|0)>>1]|0;do if(!(g&256)){h=(f+160|0)+(l<<4)+4|0;if(!(g&r)){c[h>>2]=-1;break}else{c[h>>2]=0;break}}while(0);g=e[(R+44380|0)>>1]|0;do if(!(g&256)){h=(f+160|0)+(l<<4)+8|0;if(!(g&r)){c[h>>2]=-1;break}else{c[h>>2]=0;break}}while(0);g=e[(R+44382|0)>>1]|0;if(!(g&256)){h=(f+160|0)+(l<<4)+12|0;if(!(g&r)){c[h>>2]=-1;break}else{c[h>>2]=0;break}}}while(0);l=l+1|0;if(l>>>0>=m>>>0)break c}R=c[P+4>>2]|0;c[(f+64|0)>>2]=g;UC(R,16,61576,f+64|0);break b}while(0);if(!O)D=0;else{D=(c[P+26732>>2]|0)==0;D=(((D?24117616:7340144)&c[(R+44376|0)>>2]|0)==0?((D?24117616:7340144)&c[(R+44376|0)+4>>2]|0)==0:0)&1}if(m){C=0;do{w=C<<1;x=R+44096+(C*40|0)+11|0;y=R+43776+(C*160|0)+40|0;z=R+43776+(C*160|0)+42|0;B=0;do{g=R+44376+(B<<1)|0;do if(!(b[g>>1]&256)){v=c[(f+160|0)+(C<<4)+(B<<2)>>2]&255;A=B<<2;h=d[96537+A>>0]|0;a[h+9+(R+44096+(C*40|0))>>0]=v;a[h+8+(R+44096+(C*40|0))>>0]=v;a[h+1+(R+44096+(C*40|0))>>0]=v;a[R+44096+(C*40|0)+h>>0]=v;v=e[g>>1]|0;if(!(v&4096<<w)){c[R+43776+(C*160|0)+(h<<2)+36>>2]=0;c[R+43776+(C*160|0)+(h<<2)+32>>2]=0;c[R+43776+(C*160|0)+(h<<2)+4>>2]=0;c[R+43776+(C*160|0)+(h<<2)>>2]=0;break}q=v&24|0?2:1;r=c[(f+192|0)+(B<<2)>>2]|0;if((r|0)<=0)break;u=0;do{s=a[96537+((_(u,q)|0)+A)>>0]|0;t=R+43776+(C*160|0)+((s&255)<<2)|0;l=a[R+44096+(C*40|0)+(s&255)>>0]|0;n=a[R+44096+(C*40|0)+((s&255)+-8|0)>>0]|0;o=a[R+44096+(C*40|0)+((s&255)+-1|0)>>0]|0;m=R+43776+(C*160|0)+(((s&255)+-1|0)<<2)|0;p=R+43776+(C*160|0)+(((s&255)+-8|0)<<2)|0;j=a[R+44096+(C*40|0)+(((s&255)+-8|0)+q|0)>>0]|0;do if((s&255&7|0)==4&((s&255)>19&(j<<24>>24==-2&(c[(P+197608|0)>>2]|0)!=0))){if((a[x>>0]|0)==-2){S=184;break}k=c[(c[(P+24620|0)>>2]|0)+44>>2]|0;c[y>>2]=0;g=(c[(R+21716|0)>>2]&128|0)==0;if(!(c[(R+21796|0)>>2]|0)){if(g){S=184;break}O=c[(P+198236|0)>>2]|0;g=O+(c[(R+21696|0)>>2]|0)|0;h=(c[(R+21768|0)>>2]<<1&2)+((s&255)>>>5)|0;if(!(c[k+((_(h>>>2,O)|0)+g<<2)>>2]&12288<<w)){L=-1;M=y;break}M=(c[(c[(P+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(P+26652|0)>>2]|0,h)|0)|0;L=c[(P+24620|0)>>2]|0;O=c[L+32+(C<<2)>>2]|0;b[y>>1]=b[O+(M<<2)>>1]|0;b[z>>1]=b[O+(M<<2)+2>>1]<<1;L=a[(c[L+64+(C<<2)>>2]|0)+((h&14)+(g<<2|1))>>0]>>1;M=y;break}else{if(!g){S=184;break}g=c[R+21696+(((s&255)>35&1)<<2)>>2]|0;if(!(c[k+(g<<2)>>2]&12288<<w)){L=-1;M=y;break}M=(c[(c[(P+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(P+26652|0)>>2]|0,(s&255)>>>2&3)|0)|0;L=c[(P+24620|0)>>2]|0;O=c[L+32+(C<<2)>>2]|0;b[y>>1]=b[O+(M<<2)>>1]|0;b[z>>1]=(b[O+(M<<2)+2>>1]|0)/2|0;L=a[(c[L+64+(C<<2)>>2]|0)+((s&255)>>>2&2|g<<2|1)>>0]<<1;M=y;break}}else S=184;while(0);do if((S|0)==184){S=0;if(j<<24>>24==-2){L=a[R+44096+(C*40|0)+((s&255)+-9|0)>>0]|0;M=R+43776+(C*160|0)+(((s&255)+-9|0)<<2)|0;break}else{L=j<<24>>24;M=R+43776+(C*160|0)+((((s&255)+-8|0)+q|0)<<2)|0;break}}while(0);g=(o<<24>>24==l<<24>>24&1)+(n<<24>>24==l<<24>>24&1)+((L|0)==(l<<24>>24|0)&1)|0;do if(g>>>0>1){j=b[m>>1]|0;g=b[p>>1]|0;h=b[M>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);h=b[R+43776+(C*160|0)+(((s&255)+-1|0)<<2)+2>>1]|0;j=b[R+43776+(C*160|0)+(((s&255)+-8|0)<<2)+2>>1]|0;k=b[M+2>>1]|0;if(h<<16>>16>j<<16>>16){if(k<<16>>16<=j<<16>>16){m=j<<16>>16;break}m=(k<<16>>16|0)>(h<<16>>16|0)?h<<16>>16:k<<16>>16;break}else{if(j<<16>>16<=k<<16>>16){m=j<<16>>16;break}m=(k<<16>>16|0)>(h<<16>>16|0)?k<<16>>16:h<<16>>16;break}}else{if((g|0)==1){if(o<<24>>24==l<<24>>24){g=b[m>>1]|0;m=b[R+43776+(C*160|0)+(((s&255)+-1|0)<<2)+2>>1]|0;break}if(n<<24>>24==l<<24>>24){g=b[p>>1]|0;m=b[R+43776+(C*160|0)+(((s&255)+-8|0)<<2)+2>>1]|0;break}else{g=b[M>>1]|0;m=b[M+2>>1]|0;break}}j=b[m>>1]|0;if(o<<24>>24!=-2&(n<<24>>24==-2&(L|0)==-2)){g=j<<16>>16;m=b[R+43776+(C*160|0)+(((s&255)+-1|0)<<2)+2>>1]|0;break}g=b[p>>1]|0;h=b[M>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);h=b[R+43776+(C*160|0)+(((s&255)+-1|0)<<2)+2>>1]|0;j=b[R+43776+(C*160|0)+(((s&255)+-8|0)<<2)+2>>1]|0;k=b[M+2>>1]|0;if(h<<16>>16>j<<16>>16){if(k<<16>>16<=j<<16>>16){m=j<<16>>16;break}m=(k<<16>>16|0)>(h<<16>>16|0)?h<<16>>16:k<<16>>16;break}else{if(j<<16>>16<=k<<16>>16){m=j<<16>>16;break}m=(k<<16>>16|0)>(h<<16>>16|0)?k<<16>>16:h<<16>>16;break}}while(0);j=c[(R+12|0)>>2]|0;k=c[(R+4|0)>>2]|0;h=(MF(d[(k+(j>>>3)|0)>>0]|d[(k+(j>>>3)|0)+1>>0]<<8|d[(k+(j>>>3)|0)+2>>0]<<16|d[(k+(j>>>3)|0)+3>>0]<<24|0)|0)<<(j&7);if(h>>>0>134217727){O=(d[86681+(h>>>23)>>0]|0)+j|0;c[(R+12|0)>>2]=O;l=a[87705+(h>>>23)>>0]|0;h=O}else{l=h>>>0>65535?h>>>16:h;h=h>>>0>65535?16:0;h=(d[97868+((l&65280|0)==0?l:l>>>8)>>0]|0)+((l&65280|0)==0?h:h|8)|0;l=k+((31-h+j|0)>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<((31-h+j|0)&7)>>>h;c[(R+12|0)>>2]=32-h+(31-h+j|0)|0;l=(l>>>1^0-(l&1))+(l&1)|0;h=32-h+(31-h+j|0)|0}j=k+(h>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(h&7);if(j>>>0>134217727){c[(R+12|0)>>2]=(d[86681+(j>>>23)>>0]|0)+h;h=a[87705+(j>>>23)>>0]|0}else{O=j>>>0>65535?j>>>16:j;N=j>>>0>65535?16:0;N=(d[97868+((O&65280|0)==0?O:O>>>8)>>0]|0)+((O&65280|0)==0?N:N|8)|0;O=31-N+h|0;h=(MF(d[(k+(O>>>3)|0)>>0]|d[(k+(O>>>3)|0)+1>>0]<<8|d[(k+(O>>>3)|0)+2>>0]<<16|d[(k+(O>>>3)|0)+3>>0]<<24|0)|0)<<(O&7)>>>N;c[(R+12|0)>>2]=32-N+O;h=(h>>>1^0-(h&1))+(h&1)|0}j=h+m|0;h=l+g&65535;do if(!(v&8)){if(v&16){b[t+4>>1]=h;b[t+6>>1]=j&65535;g=j&65535;break}if(!(v&32)){g=j&65535;break}else{b[t+32>>1]=h;b[t+34>>1]=j&65535;g=j&65535;break}}else{b[t+36>>1]=h;b[t+32>>1]=h;b[t+4>>1]=h;b[t+38>>1]=j&65535;b[t+34>>1]=j&65535;b[t+6>>1]=j&65535;g=j&65535}while(0);b[t>>1]=h;b[R+43776+(C*160|0)+((s&255)<<2)+2>>1]=g;u=u+1|0}while((u|0)<(r|0))}else{O=d[96537+(B<<2)>>0]|0;a[R+44096+(C*40|0)+O>>0]=a[O+1+(R+44096+(C*40|0))>>0]|0}while(0);B=B+1|0}while((B|0)!=4);C=C+1|0}while(C>>>0<(c[(R+23380|0)>>2]|0)>>>0)}c[f+6408>>2]=2;c[f+6412>>2]=14;break a}while(0);Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}if(N&256|0){rc(P,R,f+156|0);D=c[P+26732>>2]&O;c[f+6408>>2]=1;break a}if(N&8|0){h=c[(R+23380|0)>>2]|0;if(!h){D=O;c[f+6408>>2]=1;break a}j=0;f:while(1){if(N&4096<<(j<<1)){h=c[R+23372+(j<<2)>>2]<<c[(R+21800|0)>>2];switch(h|0){case 1:{h=0;break}case 2:{M=c[(R+12|0)>>2]|0;h=d[(c[(R+4|0)>>2]|0)+(M>>>3)>>0]<<(M&7)>>>7&1;c[(R+12|0)>>2]=M+1;h=h^1;break}default:{M=c[(R+12|0)>>2]|0;k=(c[(R+4|0)>>2]|0)+(M>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(M&7)>>>23;c[(R+12|0)>>2]=(d[86681+k>>0]|0)+M;k=d[87193+k>>0]|0;if(k>>>0<h>>>0)h=k;else{S=246;break f}}}h=_(h,16843009)|0;c[R+44096+(j*40|0)+12>>2]=h;c[R+44096+(j*40|0)+20>>2]=h;c[R+44096+(j*40|0)+28>>2]=h;c[R+44096+(j*40|0)+36>>2]=h;h=c[(R+23380|0)>>2]|0}j=j+1|0;if(j>>>0>=h>>>0){g=h;break}}if((S|0)==246){Q=c[P+4>>2]|0;c[(f+72|0)>>2]=k;UC(Q,16,61576,f+72|0);Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}if(!g){D=O;c[f+6408>>2]=1;break a}else p=0;while(1){if(N&4096<<(p<<1)){j=a[R+44096+(p*40|0)+12>>0]|0;l=a[R+44096+(p*40|0)+4>>0]|0;m=a[R+44096+(p*40|0)+11>>0]|0;k=R+43776+(p*160|0)+44|0;n=R+43776+(p*160|0)+16|0;g=a[R+44096+(p*40|0)+8>>0]|0;if(g<<24>>24==-2){h=a[R+44096+(p*40|0)+3>>0]|0;o=R+43776+(p*160|0)+12|0}else{h=g<<24>>24;o=R+43776+(p*160|0)+32|0}g=(m<<24>>24==j<<24>>24&1)+(l<<24>>24==j<<24>>24&1)+((h|0)==(j<<24>>24|0)&1)|0;do if(g>>>0>1){j=b[k>>1]|0;g=b[n>>1]|0;h=b[o>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);j=b[R+43776+(p*160|0)+46>>1]|0;k=b[R+43776+(p*160|0)+18>>1]|0;h=b[o+2>>1]|0;if(j<<16>>16>k<<16>>16){if(h<<16>>16<=k<<16>>16){m=g;l=k<<16>>16;break}m=g;l=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16;break}else{if(k<<16>>16<=h<<16>>16){m=g;l=k<<16>>16;break}m=g;l=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16;break}}else{if((g|0)==1){if(m<<24>>24==j<<24>>24){m=b[k>>1]|0;l=b[R+43776+(p*160|0)+46>>1]|0;break}if(l<<24>>24==j<<24>>24){m=b[n>>1]|0;l=b[R+43776+(p*160|0)+18>>1]|0;break}else{m=b[o>>1]|0;l=b[o+2>>1]|0;break}}j=b[k>>1]|0;if(m<<24>>24!=-2&(l<<24>>24==-2&(h|0)==-2)){m=j<<16>>16;l=b[R+43776+(p*160|0)+46>>1]|0;break}g=b[n>>1]|0;h=b[o>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);j=b[R+43776+(p*160|0)+46>>1]|0;k=b[R+43776+(p*160|0)+18>>1]|0;h=b[o+2>>1]|0;if(j<<16>>16>k<<16>>16){if(h<<16>>16<=k<<16>>16){m=g;l=k<<16>>16;break}m=g;l=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16;break}else{if(k<<16>>16<=h<<16>>16){m=g;l=k<<16>>16;break}m=g;l=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16;break}}while(0);h=c[(R+12|0)>>2]|0;k=c[(R+4|0)>>2]|0;g=(MF(d[(k+(h>>>3)|0)>>0]|d[(k+(h>>>3)|0)+1>>0]<<8|d[(k+(h>>>3)|0)+2>>0]<<16|d[(k+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){P=(d[86681+(g>>>23)>>0]|0)+h|0;c[(R+12|0)>>2]=P;j=a[87705+(g>>>23)>>0]|0;g=P}else{j=g>>>0>65535?g>>>16:g;g=g>>>0>65535?16:0;g=(d[97868+((j&65280|0)==0?j:j>>>8)>>0]|0)+((j&65280|0)==0?g:g|8)|0;j=k+((31-g+h|0)>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<((31-g+h|0)&7)>>>g;c[(R+12|0)>>2]=32-g+(31-g+h|0)|0;j=(j>>>1^0-(j&1))+(j&1)|0;g=32-g+(31-g+h|0)|0}h=k+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);if(h>>>0>134217727){c[(R+12|0)>>2]=(d[86681+(h>>>23)>>0]|0)+g;g=a[87705+(h>>>23)>>0]|0}else{P=h>>>0>65535?h>>>16:h;M=h>>>0>65535?16:0;M=(d[97868+((P&65280|0)==0?P:P>>>8)>>0]|0)+((P&65280|0)==0?M:M|8)|0;P=31-M+g|0;g=(MF(d[(k+(P>>>3)|0)>>0]|d[(k+(P>>>3)|0)+1>>0]<<8|d[(k+(P>>>3)|0)+2>>0]<<16|d[(k+(P>>>3)|0)+3>>0]<<24|0)|0)<<(P&7)>>>M;c[(R+12|0)>>2]=32-M+P;g=(g>>>1^0-(g&1))+(g&1)|0}g=g+l<<16|j+m&65535;c[R+43776+(p*160|0)+48>>2]=g;c[R+43776+(p*160|0)+52>>2]=g;c[R+43776+(p*160|0)+56>>2]=g;c[R+43776+(p*160|0)+60>>2]=g;c[R+43776+(p*160|0)+80>>2]=g;c[R+43776+(p*160|0)+84>>2]=g;c[R+43776+(p*160|0)+88>>2]=g;c[R+43776+(p*160|0)+92>>2]=g;c[R+43776+(p*160|0)+112>>2]=g;c[R+43776+(p*160|0)+116>>2]=g;c[R+43776+(p*160|0)+120>>2]=g;c[R+43776+(p*160|0)+124>>2]=g;c[R+43776+(p*160|0)+144>>2]=g;c[R+43776+(p*160|0)+148>>2]=g;c[R+43776+(p*160|0)+152>>2]=g;c[R+43776+(p*160|0)+156>>2]=g;g=c[(R+23380|0)>>2]|0}p=p+1|0;if(p>>>0>=g>>>0){D=O;c[f+6408>>2]=2;c[f+6412>>2]=14;break a}}}g=(c[(R+23380|0)>>2]|0)!=0;if(N&16|0){if(!g){D=O;c[f+6408>>2]=1;break a}l=0;g:do{j=l<<1;k=R+23372+(l<<2)|0;h:do if(!(N&4096<<j))g=255;else{h=c[k>>2]<<c[(R+21800|0)>>2];switch(h|0){case 1:{g=0;break h}case 2:{M=c[(R+12|0)>>2]|0;g=d[(c[(R+4|0)>>2]|0)+(M>>>3)>>0]<<(M&7)>>>7&1;c[(R+12|0)>>2]=M+1;g=g^1;break h}default:{M=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(M>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(M&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+M;g=d[87193+g>>0]|0;if(g>>>0<h>>>0)break h;else{p=g;S=300;break g}}}}while(0);M=_(g,16843009)|0;c[R+44096+(l*40|0)+12>>2]=M;c[R+44096+(l*40|0)+20>>2]=M;i:do if(!(N&4096<<(j|1)))g=255;else{h=c[k>>2]<<c[(R+21800|0)>>2];switch(h|0){case 1:{g=0;break i}case 2:{M=c[(R+12|0)>>2]|0;g=d[(c[(R+4|0)>>2]|0)+(M>>>3)>>0]<<(M&7)>>>7&1;c[(R+12|0)>>2]=M+1;g=g^1;break i}default:{M=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(M>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(M&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+M;g=d[87193+g>>0]|0;if(g>>>0<h>>>0)break i;else{p=g;S=300;break g}}}}while(0);n=_(g,16843009)|0;c[R+44096+(l*40|0)+28>>2]=n;c[R+44096+(l*40|0)+36>>2]=n;l=l+1|0;n=c[(R+23380|0)>>2]|0}while(l>>>0<n>>>0);if((S|0)==300){Q=c[P+4>>2]|0;c[(f+80|0)>>2]=p;UC(Q,16,61576,f+80|0);Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}if(!n){D=O;c[f+6408>>2]=1;break a}D=0;while(1){s=D<<1;t=R+44096+(D*40|0)+4|0;u=R+43776+(D*160|0)+16|0;v=R+43776+(D*160|0)+18|0;w=R+44096+(D*40|0)+11|0;x=R+43776+(D*160|0)+40|0;y=R+43776+(D*160|0)+42|0;z=R+44096+(D*40|0)+27|0;A=R+43776+(D*160|0)+108|0;B=R+43776+(D*160|0)+110|0;C=0;do{if(!(N&4096<<C+s)){h=C<<4|12;g=0}else{g=C<<3;r=C<<4|12;p=a[R+44096+(D*40|0)+r>>0]|0;do if(!C){if((a[t>>0]|0)!=p<<24>>24){S=310;break}E=b[u>>1]|0;q=b[v>>1]|0}else{if((a[z>>0]|0)!=p<<24>>24){S=310;break}E=b[A>>1]|0;q=b[B>>1]|0}while(0);do if((S|0)==310){S=0;q=a[96537+g>>0]|0;m=a[R+44096+(D*40|0)+((q&255)+-8|0)>>0]|0;n=a[R+44096+(D*40|0)+((q&255)+-1|0)>>0]|0;l=R+43776+(D*160|0)+(((q&255)+-1|0)<<2)|0;o=R+43776+(D*160|0)+(((q&255)+-8|0)<<2)|0;j=a[R+44096+(D*40|0)+((q&255)+-4|0)>>0]|0;do if((q&255&7|0)==4&((q&255)>19&(j<<24>>24==-2&(c[(P+197608|0)>>2]|0)!=0))){if((a[w>>0]|0)==-2){S=319;break}k=c[(c[(P+24620|0)>>2]|0)+44>>2]|0;c[x>>2]=0;g=(c[(R+21716|0)>>2]&128|0)==0;if(!(c[(R+21796|0)>>2]|0)){if(g){S=319;break}M=c[(P+198236|0)>>2]|0;g=M+(c[(R+21696|0)>>2]|0)|0;h=(c[(R+21768|0)>>2]<<1&2)+((q&255)>>>5)|0;if(!(c[k+((_(h>>>2,M)|0)+g<<2)>>2]&12288<<s)){G=-1;H=x;break}H=(c[(c[(P+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(P+26652|0)>>2]|0,h)|0)|0;G=c[(P+24620|0)>>2]|0;M=c[G+32+(D<<2)>>2]|0;b[x>>1]=b[M+(H<<2)>>1]|0;b[y>>1]=b[M+(H<<2)+2>>1]<<1;G=a[(c[G+64+(D<<2)>>2]|0)+((h&14)+(g<<2|1))>>0]>>1;H=x;break}else{if(!g){S=319;break}g=c[R+21696+(((q&255)>35&1)<<2)>>2]|0;if(!(c[k+(g<<2)>>2]&12288<<s)){G=-1;H=x;break}H=(c[(c[(P+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(P+26652|0)>>2]|0,(q&255)>>>2&3)|0)|0;G=c[(P+24620|0)>>2]|0;M=c[G+32+(D<<2)>>2]|0;b[x>>1]=b[M+(H<<2)>>1]|0;b[y>>1]=(b[M+(H<<2)+2>>1]|0)/2|0;G=a[(c[G+64+(D<<2)>>2]|0)+((q&255)>>>2&2|g<<2|1)>>0]<<1;H=x;break}}else S=319;while(0);do if((S|0)==319){S=0;if(j<<24>>24==-2){G=a[R+44096+(D*40|0)+((q&255)+-9|0)>>0]|0;H=R+43776+(D*160|0)+(((q&255)+-9|0)<<2)|0;break}else{G=j<<24>>24;H=R+43776+(D*160|0)+(((q&255)+-4|0)<<2)|0;break}}while(0);g=(n<<24>>24==p<<24>>24&1)+(m<<24>>24==p<<24>>24&1)+((G|0)==(p<<24>>24|0)&1)|0;if(g>>>0>1){j=b[l>>1]|0;g=b[o>>1]|0;h=b[H>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[R+43776+(D*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[R+43776+(D*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[H+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){E=g;q=h<<16>>16;break}E=g;q=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){E=g;q=h<<16>>16;break}E=g;q=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}if((g|0)==1){if(n<<24>>24==p<<24>>24){E=b[l>>1]|0;q=b[R+43776+(D*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}if(m<<24>>24==p<<24>>24){E=b[o>>1]|0;q=b[R+43776+(D*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;break}else{E=b[H>>1]|0;q=b[H+2>>1]|0;break}}j=b[l>>1]|0;if(n<<24>>24!=-2&(m<<24>>24==-2&(G|0)==-2)){E=j<<16>>16;q=b[R+43776+(D*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}g=b[o>>1]|0;h=b[H>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[R+43776+(D*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[R+43776+(D*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[H+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){E=g;q=h<<16>>16;break}E=g;q=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){E=g;q=h<<16>>16;break}E=g;q=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}while(0);h=c[(R+12|0)>>2]|0;j=c[(R+4|0)>>2]|0;g=(MF(d[(j+(h>>>3)|0)>>0]|d[(j+(h>>>3)|0)+1>>0]<<8|d[(j+(h>>>3)|0)+2>>0]<<16|d[(j+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){M=(d[86681+(g>>>23)>>0]|0)+h|0;c[(R+12|0)>>2]=M;k=a[87705+(g>>>23)>>0]|0;g=M}else{k=g>>>0>65535?g>>>16:g;g=g>>>0>65535?16:0;g=(d[97868+((k&65280|0)==0?k:k>>>8)>>0]|0)+((k&65280|0)==0?g:g|8)|0;k=j+((31-g+h|0)>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<((31-g+h|0)&7)>>>g;c[(R+12|0)>>2]=32-g+(31-g+h|0)|0;k=(k>>>1^0-(k&1))+(k&1)|0;g=32-g+(31-g+h|0)|0}h=j+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);if(h>>>0>134217727){c[(R+12|0)>>2]=(d[86681+(h>>>23)>>0]|0)+g;g=a[87705+(h>>>23)>>0]|0}else{M=h>>>0>65535?h>>>16:h;L=h>>>0>65535?16:0;L=(d[97868+((M&65280|0)==0?M:M>>>8)>>0]|0)+((M&65280|0)==0?L:L|8)|0;M=31-L+g|0;g=(MF(d[(j+(M>>>3)|0)>>0]|d[(j+(M>>>3)|0)+1>>0]<<8|d[(j+(M>>>3)|0)+2>>0]<<16|d[(j+(M>>>3)|0)+3>>0]<<24|0)|0)<<(M&7)>>>L;c[(R+12|0)>>2]=32-L+M;g=(g>>>1^0-(g&1))+(g&1)|0}h=r;g=g+q<<16|k+E&65535}c[R+43776+(D*160|0)+(h<<2)>>2]=g;c[R+43776+(D*160|0)+(h<<2)+4>>2]=g;c[R+43776+(D*160|0)+(h<<2)+8>>2]=g;c[R+43776+(D*160|0)+(h<<2)+12>>2]=g;c[R+43776+(D*160|0)+(h<<2)+32>>2]=g;c[R+43776+(D*160|0)+(h<<2)+36>>2]=g;c[R+43776+(D*160|0)+(h<<2)+40>>2]=g;c[R+43776+(D*160|0)+(h<<2)+44>>2]=g;C=C+1|0}while((C|0)!=2);D=D+1|0;if(D>>>0>=(c[(R+23380|0)>>2]|0)>>>0){D=O;c[f+6408>>2]=2;c[f+6412>>2]=14;break a}}}if(g){l=0;j:do{j=l<<1;k=R+23372+(l<<2)|0;k:do if(!(N&4096<<j))g=255;else{h=c[k>>2]<<c[(R+21800|0)>>2];switch(h|0){case 1:{g=0;break k}case 2:{M=c[(R+12|0)>>2]|0;g=d[(c[(R+4|0)>>2]|0)+(M>>>3)>>0]<<(M&7)>>>7&1;c[(R+12|0)>>2]=M+1;g=g^1;break k}default:{M=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(M>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(M&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+M;g=d[87193+g>>0]|0;if(g>>>0<h>>>0)break k;else{o=g;S=366;break j}}}}while(0);M=g*257&65535;b[R+44096+(l*40|0)+12>>1]=M;b[R+44096+(l*40|0)+20>>1]=M;b[R+44096+(l*40|0)+28>>1]=M;b[R+44096+(l*40|0)+36>>1]=M;l:do if(!(N&4096<<(j|1)))g=255;else{h=c[k>>2]<<c[(R+21800|0)>>2];switch(h|0){case 1:{g=0;break l}case 2:{M=c[(R+12|0)>>2]|0;g=d[(c[(R+4|0)>>2]|0)+(M>>>3)>>0]<<(M&7)>>>7&1;c[(R+12|0)>>2]=M+1;g=g^1;break l}default:{M=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(M>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(M&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+M;g=d[87193+g>>0]|0;if(g>>>0<h>>>0)break l;else{o=g;S=366;break j}}}}while(0);m=g*257&65535;b[R+44096+(l*40|0)+14>>1]=m;b[R+44096+(l*40|0)+22>>1]=m;b[R+44096+(l*40|0)+30>>1]=m;b[R+44096+(l*40|0)+38>>1]=m;l=l+1|0;m=c[(R+23380|0)>>2]|0}while(l>>>0<m>>>0);if((S|0)==366){Q=c[P+4>>2]|0;c[(f+88|0)>>2]=o;UC(Q,16,61576,f+88|0);Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}if(!m)D=O;else{E=0;do{t=E<<1;u=R+44096+(E*40|0)+11|0;v=R+43776+(E*160|0)+44|0;w=R+43776+(E*160|0)+46|0;x=R+43776+(E*160|0)+40|0;y=R+43776+(E*160|0)+42|0;z=R+44096+(E*40|0)+8|0;A=R+43776+(E*160|0)+20|0;B=R+44096+(E*40|0)+5|0;C=R+43776+(E*160|0)+32|0;D=0;do{if(!(N&4096<<D+t)){h=(D<<1)+12|0;g=0}else{j=D<<2;r=(D<<1)+12|0;p=a[R+44096+(E*40|0)+r>>0]|0;do if(!D)if((a[u>>0]|0)==p<<24>>24){F=b[v>>1]|0;s=b[w>>1]|0}else S=378;else{g=a[z>>0]|0;if(g<<24>>24==-2){g=a[B>>0]|0;h=A}else h=C;if(g<<24>>24!=p<<24>>24){S=378;break}F=b[h>>1]|0;s=b[h+2>>1]|0}while(0);do if((S|0)==378){S=0;q=a[96537+j>>0]|0;m=a[R+44096+(E*40|0)+((q&255)+-8|0)>>0]|0;n=a[R+44096+(E*40|0)+((q&255)+-1|0)>>0]|0;l=R+43776+(E*160|0)+(((q&255)+-1|0)<<2)|0;o=R+43776+(E*160|0)+(((q&255)+-8|0)<<2)|0;j=a[R+44096+(E*40|0)+((q&255)+-6|0)>>0]|0;do if((q&255&7|0)==4&((q&255)>19&(j<<24>>24==-2&(c[(P+197608|0)>>2]|0)!=0))){if((a[u>>0]|0)==-2){S=387;break}k=c[(c[(P+24620|0)>>2]|0)+44>>2]|0;c[x>>2]=0;g=(c[(R+21716|0)>>2]&128|0)==0;if(!(c[(R+21796|0)>>2]|0)){if(g){S=387;break}M=c[(P+198236|0)>>2]|0;g=M+(c[(R+21696|0)>>2]|0)|0;h=(c[(R+21768|0)>>2]<<1&2)+((q&255)>>>5)|0;if(!(c[k+((_(h>>>2,M)|0)+g<<2)>>2]&12288<<t)){I=-1;J=x;break}J=(c[(c[(P+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(P+26652|0)>>2]|0,h)|0)|0;I=c[(P+24620|0)>>2]|0;M=c[I+32+(E<<2)>>2]|0;b[x>>1]=b[M+(J<<2)>>1]|0;b[y>>1]=b[M+(J<<2)+2>>1]<<1;I=a[(c[I+64+(E<<2)>>2]|0)+((h&14)+(g<<2|1))>>0]>>1;J=x;break}else{if(!g){S=387;break}g=c[R+21696+(((q&255)>35&1)<<2)>>2]|0;if(!(c[k+(g<<2)>>2]&12288<<t)){I=-1;J=x;break}J=(c[(c[(P+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(P+26652|0)>>2]|0,(q&255)>>>2&3)|0)|0;I=c[(P+24620|0)>>2]|0;M=c[I+32+(E<<2)>>2]|0;b[x>>1]=b[M+(J<<2)>>1]|0;b[y>>1]=(b[M+(J<<2)+2>>1]|0)/2|0;I=a[(c[I+64+(E<<2)>>2]|0)+((q&255)>>>2&2|g<<2|1)>>0]<<1;J=x;break}}else S=387;while(0);do if((S|0)==387){S=0;if(j<<24>>24==-2){I=a[R+44096+(E*40|0)+((q&255)+-9|0)>>0]|0;J=R+43776+(E*160|0)+(((q&255)+-9|0)<<2)|0;break}else{I=j<<24>>24;J=R+43776+(E*160|0)+(((q&255)+-6|0)<<2)|0;break}}while(0);g=(n<<24>>24==p<<24>>24&1)+(m<<24>>24==p<<24>>24&1)+((I|0)==(p<<24>>24|0)&1)|0;if(g>>>0>1){j=b[l>>1]|0;g=b[o>>1]|0;h=b[J>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[R+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[R+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[J+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){F=g;s=h<<16>>16;break}F=g;s=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){F=g;s=h<<16>>16;break}F=g;s=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}if((g|0)==1){if(n<<24>>24==p<<24>>24){F=b[l>>1]|0;s=b[R+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}if(m<<24>>24==p<<24>>24){F=b[o>>1]|0;s=b[R+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;break}else{F=b[J>>1]|0;s=b[J+2>>1]|0;break}}j=b[l>>1]|0;if(n<<24>>24!=-2&(m<<24>>24==-2&(I|0)==-2)){F=j<<16>>16;s=b[R+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}g=b[o>>1]|0;h=b[J>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[R+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[R+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[J+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){F=g;s=h<<16>>16;break}F=g;s=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){F=g;s=h<<16>>16;break}F=g;s=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}while(0);h=c[(R+12|0)>>2]|0;j=c[(R+4|0)>>2]|0;g=(MF(d[(j+(h>>>3)|0)>>0]|d[(j+(h>>>3)|0)+1>>0]<<8|d[(j+(h>>>3)|0)+2>>0]<<16|d[(j+(h>>>3)|0)+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){M=(d[86681+(g>>>23)>>0]|0)+h|0;c[(R+12|0)>>2]=M;k=a[87705+(g>>>23)>>0]|0;g=M}else{k=g>>>0>65535?g>>>16:g;g=g>>>0>65535?16:0;g=(d[97868+((k&65280|0)==0?k:k>>>8)>>0]|0)+((k&65280|0)==0?g:g|8)|0;k=j+((31-g+h|0)>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<((31-g+h|0)&7)>>>g;c[(R+12|0)>>2]=32-g+(31-g+h|0)|0;k=(k>>>1^0-(k&1))+(k&1)|0;g=32-g+(31-g+h|0)|0}h=j+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);if(h>>>0>134217727){c[(R+12|0)>>2]=(d[86681+(h>>>23)>>0]|0)+g;g=a[87705+(h>>>23)>>0]|0}else{M=h>>>0>65535?h>>>16:h;L=h>>>0>65535?16:0;L=(d[97868+((M&65280|0)==0?M:M>>>8)>>0]|0)+((M&65280|0)==0?L:L|8)|0;M=31-L+g|0;g=(MF(d[(j+(M>>>3)|0)>>0]|d[(j+(M>>>3)|0)+1>>0]<<8|d[(j+(M>>>3)|0)+2>>0]<<16|d[(j+(M>>>3)|0)+3>>0]<<24|0)|0)<<(M&7)>>>L;c[(R+12|0)>>2]=32-L+M;g=(g>>>1^0-(g&1))+(g&1)|0}h=r;g=g+s<<16|k+F&65535}c[R+43776+(E*160|0)+(h<<2)>>2]=g;c[R+43776+(E*160|0)+(h<<2)+4>>2]=g;c[R+43776+(E*160|0)+(h<<2)+32>>2]=g;c[R+43776+(E*160|0)+(h<<2)+36>>2]=g;c[R+43776+(E*160|0)+(h<<2)+64>>2]=g;c[R+43776+(E*160|0)+(h<<2)+68>>2]=g;c[R+43776+(E*160|0)+(h<<2)+96>>2]=g;c[R+43776+(E*160|0)+(h<<2)+100>>2]=g;D=D+1|0}while((D|0)!=2);E=E+1|0}while(E>>>0<(c[(R+23380|0)>>2]|0)>>>0);D=O}}else D=O}else T=1}if(T){T=0;if(1){if(!(N&1)){N=ac(P,R,c[(R+21636|0)>>2]|0,0)|0;c[(R+21636|0)>>2]=N;if((N|0)<0){Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}}else{if(!O)S=119;else{M=c[(R+12|0)>>2]|0;L=d[(c[(R+4|0)>>2]|0)+(M>>>3)>>0]|0;c[(R+12|0)>>2]=M+1;if(!(L&128>>>(M&7)))S=119;else{c[(f+156|0)>>2]=N|16777216;k=c[(R+4|0)>>2]|0;h=c[(R+12|0)>>2]|0;m=0;do{l=d[96537+m>>0]|0;g=a[l+-1+(R+21640)>>0]|0;N=a[l+-8+(R+21640)>>0]|0;g=(g|0)>(N|0)?N:g;g=(g|0)<0?2:g;N=d[k+(h>>>3)>>0]|0;j=h+1|0;c[(R+12|0)>>2]=j;if(!(N&128>>>(h&7))){N=(MF(d[(k+(j>>>3)|0)>>0]|d[(k+(j>>>3)|0)+1>>0]<<8|d[(k+(j>>>3)|0)+2>>0]<<16|d[(k+(j>>>3)|0)+3>>0]<<24|0)|0)<<(j&7)>>>29;h=h+4|0;c[(R+12|0)>>2]=h;g=((N|0)>=(g|0)&1)+N|0}else h=j;N=g*257&65535;b[(R+21640+l|0)>>1]=N;b[(R+21640+l|0)+8>>1]=N;m=m+4|0}while((m|0)<16)}}if((S|0)==119){k=c[(R+4|0)>>2]|0;h=c[(R+12|0)>>2]|0;m=0;do{l=d[96537+m>>0]|0;g=a[l+-1+(R+21640)>>0]|0;N=a[l+-8+(R+21640)>>0]|0;g=(g|0)>(N|0)?N:g;g=(g|0)<0?2:g;N=d[k+(h>>>3)>>0]|0;j=h+1|0;c[(R+12|0)>>2]=j;if(!(N&128>>>(h&7))){N=(MF(d[(k+(j>>>3)|0)>>0]|d[(k+(j>>>3)|0)+1>>0]<<8|d[(k+(j>>>3)|0)+2>>0]<<16|d[(k+(j>>>3)|0)+3>>0]<<24|0)|0)<<(j&7)>>>29;h=h+4|0;c[(R+12|0)>>2]=h;g=((N|0)>=(g|0)&1)+N|0}else h=j;a[R+21640+l>>0]=g;m=m+1|0}while((m|0)<16)}N=(c[R+21680>>2]|0)+(c[(c[P+26648>>2]|0)+(c[(R+21772|0)>>2]<<2)>>2]|0)|0;c[N>>2]=c[R+21676>>2];a[N+4>>0]=a[R+21671>>0]|0;a[N+5>>0]=a[R+21663>>0]|0;a[N+6>>0]=a[R+21655>>0]|0;if(($b(P,R)|0)<=-1){Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}}if(!K){c[R+21632>>2]=6;D=O;c[f+6408>>2]=1;break a}N=c[(R+12|0)>>2]|0;g=(c[(R+4|0)>>2]|0)+(N>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(N&7)>>>23;c[(R+12|0)>>2]=(d[86681+g>>0]|0)+N;g=ac(P,R,d[87193+g>>0]|0,1)|0;if((g|0)<0){Q=-1;i=f;c[f+6408>>2]=6;c[f+6412>>2]=Q|0;break a}else{c[R+21632>>2]=g;D=O;c[f+6408>>2]=1;break a}}else T=1}}c[f+584>>2]=Q;c[f+5824>>2]=D;c[f+6384>>2]=S;c[f+6392>>2]=T}
function Zf(a,e,f){a=a|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;j=i;i=i+64|0;h=j;o=b[e>>1]|0;n=b[e+2>>1]|0;k=n+o|0;n=o-n|0;o=b[e+4>>1]|0;g=b[e+6>>1]|0;m=o-g|0;o=g+o|0;g=o+k|0;c[h>>2]=g;c[h+4>>2]=k-o;c[h+8>>2]=n-m;c[h+12>>2]=m+n;n=b[e+8>>1]|0;m=b[e+10>>1]|0;o=m+n|0;m=n-m|0;n=b[e+12>>1]|0;k=b[e+14>>1]|0;l=n-k|0;n=k+n|0;c[h+16>>2]=n+o;c[h+20>>2]=o-n;c[h+24>>2]=m-l;c[h+28>>2]=l+m;m=b[e+16>>1]|0;l=b[e+18>>1]|0;n=l+m|0;l=m-l|0;m=b[e+20>>1]|0;o=b[e+22>>1]|0;k=m-o|0;m=o+m|0;c[h+32>>2]=m+n;c[h+36>>2]=n-m;c[h+40>>2]=l-k;c[h+44>>2]=k+l;l=b[e+24>>1]|0;k=b[e+26>>1]|0;m=k+l|0;k=l-k|0;l=b[e+28>>1]|0;n=b[e+30>>1]|0;e=l-n|0;l=n+l|0;c[h+48>>2]=l+m;c[h+52>>2]=m-l;c[h+56>>2]=k-e;c[h+60>>2]=e+k;e=0;while(1){o=d[69169+e>>0]|0;k=c[h+(e+8<<2)>>2]|0;m=k+g|0;k=g-k|0;n=c[h+(e+4<<2)>>2]|0;g=c[h+(e+12<<2)>>2]|0;l=n-g|0;n=g+n|0;b[a+(o<<1)>>1]=((_(n+m|0,f)|0)+128|0)>>>8;b[a+(o+16<<1)>>1]=((_(l+k|0,f)|0)+128|0)>>>8;b[a+(o+64<<1)>>1]=((_(k-l|0,f)|0)+128|0)>>>8;b[a+(o+80<<1)>>1]=((_(m-n|0,f)|0)+128|0)>>>8;e=e+1|0;if((e|0)==4)break;g=c[h+(e<<2)>>2]|0}i=j;return}function _f(a,c){a=a|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=b[a>>1]|0;j=a+32|0;g=b[j>>1]|0;p=g+r|0;g=r-g|0;r=a+64|0;o=b[r>>1]|0;i=a+96|0;k=b[i>>1]|0;m=k+o|0;k=o-k|0;o=a+128|0;e=b[o>>1]|0;f=a+160|0;h=b[f>>1]|0;q=h+e|0;h=e-h|0;e=a+192|0;n=b[e>>1]|0;d=a+224|0;l=b[d>>1]|0;s=l+n|0;l=n-l|0;n=q+p|0;q=p-q|0;p=m-s|0;m=s+m|0;b[a>>1]=((_(m+n|0,c)|0)+128|0)>>>8;b[r>>1]=((_(p+q|0,c)|0)+128|0)>>>8;b[o>>1]=((_(q-p|0,c)|0)+128|0)>>>8;b[e>>1]=((_(n-m|0,c)|0)+128|0)>>>8;e=h+g|0;h=g-h|0;g=k-l|0;a=l+k|0;b[j>>1]=((_(a+e|0,c)|0)+128|0)>>>8;b[i>>1]=((_(g+h|0,c)|0)+128|0)>>>8;b[f>>1]=((_(h-g|0,c)|0)+128|0)>>>8;b[d>>1]=((_(e-a|0,c)|0)+128|0)>>>8;return}function $f(a,c){a=a|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;i=b[a>>1]|0;j=a+32|0;e=b[j>>1]|0;g=a+64|0;h=b[g>>1]|0;d=a+96|0;k=b[d>>1]|0;f=i-e|0;i=e+i|0;e=h-k|0;h=k+h|0;b[a>>1]=(_(h+i|0,c)|0)>>>7;b[j>>1]=(_(e+f|0,c)|0)>>>7;b[g>>1]=(_(i-h|0,c)|0)>>>7;b[d>>1]=(_(f-e|0,c)|0)>>>7;return}function ag(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;g=(c[d>>2]|0)+32|0;i=d+32|0;l=c[i>>2]|0;m=l+g|0;l=g-l|0;g=d+16|0;h=c[g>>2]|0;k=d+48|0;n=c[k>>2]|0;j=(h>>1)-n|0;h=(n>>1)+h|0;c[d>>2]=h+m;c[g>>2]=j+l;c[i>>2]=l-j;c[k>>2]=m-h;k=d+4|0;h=c[k>>2]|0;m=d+36|0;i=c[m>>2]|0;j=i+h|0;i=h-i|0;h=d+20|0;l=c[h>>2]|0;g=d+52|0;n=c[g>>2]|0;o=(l>>1)-n|0;l=(n>>1)+l|0;c[k>>2]=l+j;c[h>>2]=o+i;c[m>>2]=i-o;c[g>>2]=j-l;g=d+8|0;l=c[g>>2]|0;j=d+40|0;m=c[j>>2]|0;o=m+l|0;m=l-m|0;l=d+24|0;i=c[l>>2]|0;h=d+56|0;k=c[h>>2]|0;n=(i>>1)-k|0;i=(k>>1)+i|0;c[g>>2]=i+o;c[l>>2]=n+m;c[j>>2]=m-n;c[h>>2]=o-i;h=d+12|0;i=c[h>>2]|0;o=d+44|0;j=c[o>>2]|0;n=j+i|0;j=i-j|0;i=d+28|0;m=c[i>>2]|0;l=d+60|0;g=c[l>>2]|0;k=(m>>1)-g|0;m=(g>>1)+m|0;c[h>>2]=m+n;c[i>>2]=k+j;c[o>>2]=j-k;c[l>>2]=n-m;l=f>>1;m=l<<1;n=l*3|0;o=0;do{g=o<<2;k=c[d+(g<<2)>>2]|0;h=c[d+((g|2)<<2)>>2]|0;j=h+k|0;h=k-h|0;k=c[d+((g|1)<<2)>>2]|0;g=c[d+((g|3)<<2)>>2]|0;i=(k>>1)-g|0;k=(g>>1)+k|0;g=a+(o<<1)|0;f=(k+j>>6)+(e[g>>1]|0)|0;if(f>>>0>=512)f=0-f>>31&511;b[g>>1]=f;g=a+(o+l<<1)|0;f=(e[g>>1]|0)+(i+h>>6)|0;if(f>>>0>=512)f=0-f>>31&511;b[g>>1]=f;g=a+(o+m<<1)|0;f=(e[g>>1]|0)+(h-i>>6)|0;if(f>>>0>=512)f=0-f>>31&511;b[g>>1]=f;g=a+(o+n<<1)|0;f=(e[g>>1]|0)+(j-k>>6)|0;if(f>>>0>=512)f=0-f>>31&511;b[g>>1]=f;o=o+1|0}while((o|0)!=4);f=d+64|0;do{c[d>>2]=0;d=d+4|0}while((d|0)<(f|0));return}function bg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;h=(c[d>>2]|0)+32|0;c[d>>2]=h;g=0;while(1){w=d+(g+32<<2)|0;v=c[w>>2]|0;t=v+h|0;v=h-v|0;p=d+(g+16<<2)|0;u=c[p>>2]|0;o=d+(g+48<<2)|0;i=c[o>>2]|0;q=(u>>1)-i|0;u=(i>>1)+u|0;i=u+t|0;m=q+v|0;q=v-q|0;u=t-u|0;t=d+(g+24<<2)|0;v=c[t>>2]|0;s=d+(g+40<<2)|0;r=c[s>>2]|0;k=d+(g+56<<2)|0;n=c[k>>2]|0;j=r-v-n-(n>>1)|0;l=d+(g+8<<2)|0;h=c[l>>2]|0;x=n-((v>>1)+v)+h|0;n=n+r+(r>>1)-h|0;h=r+v+h+(h>>1)|0;v=(h>>2)+j|0;r=(n>>2)+x|0;n=(x>>2)-n|0;j=h-(j>>2)|0;c[d+(g<<2)>>2]=j+i;c[k>>2]=i-j;c[l>>2]=n+m;c[o>>2]=m-n;c[p>>2]=r+q;c[s>>2]=q-r;c[t>>2]=v+u;c[w>>2]=u-v;g=g+1|0;if((g|0)==8)break;h=c[d+(g<<2)>>2]|0}p=f>>1;q=p<<1;r=p*3|0;s=p<<2;t=p*5|0;u=p*6|0;v=p*7|0;w=0;do{h=w<<3;i=c[d+(h<<2)>>2]|0;j=c[d+((h|4)<<2)>>2]|0;f=j+i|0;j=i-j|0;i=c[d+((h|2)<<2)>>2]|0;o=c[d+((h|6)<<2)>>2]|0;k=(i>>1)-o|0;i=(o>>1)+i|0;o=i+f|0;m=k+j|0;k=j-k|0;i=f-i|0;f=c[d+((h|3)<<2)>>2]|0;j=c[d+((h|5)<<2)>>2]|0;l=c[d+((h|7)<<2)>>2]|0;n=j-f-l-(l>>1)|0;h=c[d+((h|1)<<2)>>2]|0;g=l-((f>>1)+f)+h|0;l=l+j+(j>>1)-h|0;h=j+f+h+(h>>1)|0;f=(h>>2)+n|0;j=(l>>2)+g|0;l=(g>>2)-l|0;n=h-(n>>2)|0;h=a+(w<<1)|0;g=(n+o>>6)+(e[h>>1]|0)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+p<<1)|0;g=(e[h>>1]|0)+(l+m>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+q<<1)|0;g=(e[h>>1]|0)+(j+k>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+r<<1)|0;g=(e[h>>1]|0)+(f+i>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+s<<1)|0;g=(e[h>>1]|0)+(i-f>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+t<<1)|0;g=(e[h>>1]|0)+(k-j>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+u<<1)|0;g=(e[h>>1]|0)+(m-l>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;h=a+(w+v<<1)|0;g=(e[h>>1]|0)+(o-n>>6)|0;if(g>>>0>=512)g=0-g>>31&511;b[h>>1]=g;w=w+1|0}while((w|0)!=8);FF(d|0,0,256)|0;return}function cg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;h=f>>>1;c[d>>2]=0;g=0;while(1){f=(e[a>>1]|0)+i|0;if(f>>>0>=512)f=0-f>>31&511;b[a>>1]=f;d=a+2|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=512)f=0-f>>31&511;b[d>>1]=f;d=a+4|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=512)f=0-f>>31&511;b[d>>1]=f;d=a+6|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=512)f=0-f>>31&511;b[d>>1]=f;g=g+1|0;if((g|0)==4)break;else a=a+(h<<1)|0}return}function dg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;c[d>>2]=0;h=f>>>1;g=0;while(1){d=(e[a>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;f=a+2|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;f=a+4|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;f=a+6|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;f=a+8|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;f=a+10|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;f=a+12|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;f=a+14|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=512)d=0-d>>31&511;b[f>>1]=d;g=g+1|0;if((g|0)==8)break;else a=a+(h<<1)|0}return}function eg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i>>>1;p=0;do{switch(a[j+(d[96537+p>>0]|0)>>0]|0){case 0:break;case 1:{if(!(c[h+(p<<4<<2)>>2]|0))r=14;else{o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}break}default:r=14}if((r|0)==14){r=0;ag(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i)}p=p+1|0}while((p|0)!=16);return}function fg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0;m=i>>>1;p=0;do{if(!(a[j+(d[96537+p>>0]|0)>>0]|0)){if(c[h+(p<<4<<2)>>2]|0){o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=512)k=0-k>>31&511;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}}else ag(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i);p=p+1|0}while((p|0)!=16);return}function gg(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;switch(a[g+12>>0]|0){case 0:break;case 1:{if(!(c[e>>2]|0))h=4;else dg(b+(c[d>>2]|0)|0,e,f);break}default:h=4}if((h|0)==4)bg(b+(c[d>>2]|0)|0,e,f);switch(a[g+14>>0]|0){case 0:break;case 1:{if(!(c[e+256>>2]|0))h=8;else dg(b+(c[d+16>>2]|0)|0,e+256|0,f);break}default:h=8}if((h|0)==8)bg(b+(c[d+16>>2]|0)|0,e+256|0,f);switch(a[g+28>>0]|0){case 0:break;case 1:{if(!(c[e+512>>2]|0))h=12;else dg(b+(c[d+32>>2]|0)|0,e+512|0,f);break}default:h=12}if((h|0)==12)bg(b+(c[d+32>>2]|0)|0,e+512|0,f);switch(a[g+30>>0]|0){case 1:{h=14;break}case 0:return;default:{}}if((h|0)==14)if(c[e+768>>2]|0){dg(b+(c[d+48>>2]|0)|0,e+768|0,f);return}bg(b+(c[d+48>>2]|0)|0,e+768|0,f);return}function hg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;q=i>>>1;s=20;t=1;while(1){k=t<<4;if((k|0)<(k|4|0)){r=f+(t+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(q<<1)|0}}}else ag((c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(s|0))}t=t+1|0;if((t|0)==3)break;else s=s+16|0}return}function ig(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i>>>1;r=20;s=1;while(1){k=s<<4;if((k|0)<(k|4|0)){q=f+(s+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else ag((c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3){r=24;s=1;break}else r=r+16|0}while(1){q=s<<4;k=q|4;if((k|0)<(q|8|0)){q=f+(s+-1<<2)|0;do{l=k+4|0;if(!(a[j+(d[96537+l>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=512)l=0-l>>31&511;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else ag((c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3)break;else r=r+16|0}return}function jg(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;h=i;i=i+64|0;g=h;n=c[b>>2]|0;m=c[b+4>>2]|0;j=m+n|0;m=n-m|0;n=c[b+8>>2]|0;f=c[b+12>>2]|0;l=n-f|0;n=f+n|0;f=n+j|0;c[g>>2]=f;c[g+4>>2]=j-n;c[g+8>>2]=m-l;c[g+12>>2]=l+m;m=c[b+16>>2]|0;l=c[b+20>>2]|0;n=l+m|0;l=m-l|0;m=c[b+24>>2]|0;j=c[b+28>>2]|0;k=m-j|0;m=j+m|0;c[g+16>>2]=m+n;c[g+20>>2]=n-m;c[g+24>>2]=l-k;c[g+28>>2]=k+l;l=c[b+32>>2]|0;k=c[b+36>>2]|0;m=k+l|0;k=l-k|0;l=c[b+40>>2]|0;n=c[b+44>>2]|0;j=l-n|0;l=n+l|0;c[g+32>>2]=l+m;c[g+36>>2]=m-l;c[g+40>>2]=k-j;c[g+44>>2]=j+k;k=c[b+48>>2]|0;j=c[b+52>>2]|0;l=j+k|0;j=k-j|0;k=c[b+56>>2]|0;m=c[b+60>>2]|0;b=k-m|0;k=m+k|0;c[g+48>>2]=k+l;c[g+52>>2]=l-k;c[g+56>>2]=j-b;c[g+60>>2]=b+j;b=0;while(1){n=d[69169+b>>0]|0;j=c[g+(b+8<<2)>>2]|0;l=j+f|0;j=f-j|0;m=c[g+(b+4<<2)>>2]|0;f=c[g+(b+12<<2)>>2]|0;k=m-f|0;m=f+m|0;c[a+(n<<2)>>2]=(_(m+l|0,e)|0)+128>>8;c[a+(n+16<<2)>>2]=(_(k+j|0,e)|0)+128>>8;c[a+(n+64<<2)>>2]=(_(j-k|0,e)|0)+128>>8;c[a+(n+80<<2)>>2]=(_(l-m|0,e)|0)+128>>8;b=b+1|0;if((b|0)==4)break;f=c[g+(b<<2)>>2]|0}i=h;return}function kg(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=c[a>>2]|0;j=a+64|0;g=c[j>>2]|0;p=g+r|0;g=r-g|0;r=a+128|0;o=c[r>>2]|0;i=a+192|0;k=c[i>>2]|0;m=k+o|0;k=o-k|0;o=a+256|0;e=c[o>>2]|0;f=a+320|0;h=c[f>>2]|0;q=h+e|0;h=e-h|0;e=a+384|0;n=c[e>>2]|0;d=a+448|0;l=c[d>>2]|0;s=l+n|0;l=n-l|0;n=q+p|0;q=p-q|0;p=m-s|0;m=s+m|0;c[a>>2]=(_(m+n|0,b)|0)+128>>8;c[r>>2]=(_(p+q|0,b)|0)+128>>8;c[o>>2]=(_(q-p|0,b)|0)+128>>8;c[e>>2]=(_(n-m|0,b)|0)+128>>8;e=h+g|0;h=g-h|0;g=k-l|0;a=l+k|0;c[j>>2]=(_(a+e|0,b)|0)+128>>8;c[i>>2]=(_(g+h|0,b)|0)+128>>8;c[f>>2]=(_(h-g|0,b)|0)+128>>8;c[d>>2]=(_(e-a|0,b)|0)+128>>8;return}function lg(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;i=c[a>>2]|0;j=a+64|0;e=c[j>>2]|0;g=a+128|0;h=c[g>>2]|0;d=a+192|0;k=c[d>>2]|0;f=i-e|0;i=e+i|0;e=h-k|0;h=k+h|0;c[a>>2]=(_(h+i|0,b)|0)>>7;c[j>>2]=(_(e+f|0,b)|0)>>7;c[g>>2]=(_(i-h|0,b)|0)>>7;c[d>>2]=(_(f-e|0,b)|0)>>7;return}function mg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;g=(c[d>>2]|0)+32|0;i=d+32|0;l=c[i>>2]|0;m=l+g|0;l=g-l|0;g=d+16|0;h=c[g>>2]|0;k=d+48|0;n=c[k>>2]|0;j=(h>>1)-n|0;h=(n>>1)+h|0;c[d>>2]=h+m;c[g>>2]=j+l;c[i>>2]=l-j;c[k>>2]=m-h;k=d+4|0;h=c[k>>2]|0;m=d+36|0;i=c[m>>2]|0;j=i+h|0;i=h-i|0;h=d+20|0;l=c[h>>2]|0;g=d+52|0;n=c[g>>2]|0;o=(l>>1)-n|0;l=(n>>1)+l|0;c[k>>2]=l+j;c[h>>2]=o+i;c[m>>2]=i-o;c[g>>2]=j-l;g=d+8|0;l=c[g>>2]|0;j=d+40|0;m=c[j>>2]|0;o=m+l|0;m=l-m|0;l=d+24|0;i=c[l>>2]|0;h=d+56|0;k=c[h>>2]|0;n=(i>>1)-k|0;i=(k>>1)+i|0;c[g>>2]=i+o;c[l>>2]=n+m;c[j>>2]=m-n;c[h>>2]=o-i;h=d+12|0;i=c[h>>2]|0;o=d+44|0;j=c[o>>2]|0;n=j+i|0;j=i-j|0;i=d+28|0;m=c[i>>2]|0;l=d+60|0;g=c[l>>2]|0;k=(m>>1)-g|0;m=(g>>1)+m|0;c[h>>2]=m+n;c[i>>2]=k+j;c[o>>2]=j-k;c[l>>2]=n-m;l=f>>1;m=l<<1;n=l*3|0;o=0;do{g=o<<2;k=c[d+(g<<2)>>2]|0;h=c[d+((g|2)<<2)>>2]|0;j=h+k|0;h=k-h|0;k=c[d+((g|1)<<2)>>2]|0;g=c[d+((g|3)<<2)>>2]|0;i=(k>>1)-g|0;k=(g>>1)+k|0;g=a+(o<<1)|0;f=(k+j>>6)+(e[g>>1]|0)|0;if(f>>>0>=1024)f=0-f>>31&1023;b[g>>1]=f;g=a+(o+l<<1)|0;f=(e[g>>1]|0)+(i+h>>6)|0;if(f>>>0>=1024)f=0-f>>31&1023;b[g>>1]=f;g=a+(o+m<<1)|0;f=(e[g>>1]|0)+(h-i>>6)|0;if(f>>>0>=1024)f=0-f>>31&1023;b[g>>1]=f;g=a+(o+n<<1)|0;f=(e[g>>1]|0)+(j-k>>6)|0;if(f>>>0>=1024)f=0-f>>31&1023;b[g>>1]=f;o=o+1|0}while((o|0)!=4);f=d+64|0;do{c[d>>2]=0;d=d+4|0}while((d|0)<(f|0));return}function ng(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;h=(c[d>>2]|0)+32|0;c[d>>2]=h;g=0;while(1){w=d+(g+32<<2)|0;v=c[w>>2]|0;t=v+h|0;v=h-v|0;p=d+(g+16<<2)|0;u=c[p>>2]|0;o=d+(g+48<<2)|0;i=c[o>>2]|0;q=(u>>1)-i|0;u=(i>>1)+u|0;i=u+t|0;m=q+v|0;q=v-q|0;u=t-u|0;t=d+(g+24<<2)|0;v=c[t>>2]|0;s=d+(g+40<<2)|0;r=c[s>>2]|0;k=d+(g+56<<2)|0;n=c[k>>2]|0;j=r-v-n-(n>>1)|0;l=d+(g+8<<2)|0;h=c[l>>2]|0;x=n-((v>>1)+v)+h|0;n=n+r+(r>>1)-h|0;h=r+v+h+(h>>1)|0;v=(h>>2)+j|0;r=(n>>2)+x|0;n=(x>>2)-n|0;j=h-(j>>2)|0;c[d+(g<<2)>>2]=j+i;c[k>>2]=i-j;c[l>>2]=n+m;c[o>>2]=m-n;c[p>>2]=r+q;c[s>>2]=q-r;c[t>>2]=v+u;c[w>>2]=u-v;g=g+1|0;if((g|0)==8)break;h=c[d+(g<<2)>>2]|0}p=f>>1;q=p<<1;r=p*3|0;s=p<<2;t=p*5|0;u=p*6|0;v=p*7|0;w=0;do{h=w<<3;i=c[d+(h<<2)>>2]|0;j=c[d+((h|4)<<2)>>2]|0;f=j+i|0;j=i-j|0;i=c[d+((h|2)<<2)>>2]|0;o=c[d+((h|6)<<2)>>2]|0;k=(i>>1)-o|0;i=(o>>1)+i|0;o=i+f|0;m=k+j|0;k=j-k|0;i=f-i|0;f=c[d+((h|3)<<2)>>2]|0;j=c[d+((h|5)<<2)>>2]|0;l=c[d+((h|7)<<2)>>2]|0;n=j-f-l-(l>>1)|0;h=c[d+((h|1)<<2)>>2]|0;g=l-((f>>1)+f)+h|0;l=l+j+(j>>1)-h|0;h=j+f+h+(h>>1)|0;f=(h>>2)+n|0;j=(l>>2)+g|0;l=(g>>2)-l|0;n=h-(n>>2)|0;h=a+(w<<1)|0;g=(n+o>>6)+(e[h>>1]|0)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+p<<1)|0;g=(e[h>>1]|0)+(l+m>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+q<<1)|0;g=(e[h>>1]|0)+(j+k>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+r<<1)|0;g=(e[h>>1]|0)+(f+i>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+s<<1)|0;g=(e[h>>1]|0)+(i-f>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+t<<1)|0;g=(e[h>>1]|0)+(k-j>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+u<<1)|0;g=(e[h>>1]|0)+(m-l>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;h=a+(w+v<<1)|0;g=(e[h>>1]|0)+(o-n>>6)|0;if(g>>>0>=1024)g=0-g>>31&1023;b[h>>1]=g;w=w+1|0}while((w|0)!=8);FF(d|0,0,256)|0;return}function og(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;h=f>>>1;c[d>>2]=0;g=0;while(1){f=(e[a>>1]|0)+i|0;if(f>>>0>=1024)f=0-f>>31&1023;b[a>>1]=f;d=a+2|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=1024)f=0-f>>31&1023;b[d>>1]=f;d=a+4|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=1024)f=0-f>>31&1023;b[d>>1]=f;d=a+6|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=1024)f=0-f>>31&1023;b[d>>1]=f;g=g+1|0;if((g|0)==4)break;else a=a+(h<<1)|0}return}function pg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;c[d>>2]=0;h=f>>>1;g=0;while(1){d=(e[a>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;f=a+2|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;f=a+4|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;f=a+6|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;f=a+8|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;f=a+10|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;f=a+12|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;f=a+14|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=1024)d=0-d>>31&1023;b[f>>1]=d;g=g+1|0;if((g|0)==8)break;else a=a+(h<<1)|0}return}function qg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i>>>1;p=0;do{switch(a[j+(d[96537+p>>0]|0)>>0]|0){case 0:break;case 1:{if(!(c[h+(p<<4<<2)>>2]|0))r=14;else{o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}break}default:r=14}if((r|0)==14){r=0;mg(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i)}p=p+1|0}while((p|0)!=16);return}function rg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0;m=i>>>1;p=0;do{if(!(a[j+(d[96537+p>>0]|0)>>0]|0)){if(c[h+(p<<4<<2)>>2]|0){o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=1024)k=0-k>>31&1023;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}}else mg(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i);p=p+1|0}while((p|0)!=16);return}function sg(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;switch(a[g+12>>0]|0){case 0:break;case 1:{if(!(c[e>>2]|0))h=4;else pg(b+(c[d>>2]|0)|0,e,f);break}default:h=4}if((h|0)==4)ng(b+(c[d>>2]|0)|0,e,f);switch(a[g+14>>0]|0){case 0:break;case 1:{if(!(c[e+256>>2]|0))h=8;else pg(b+(c[d+16>>2]|0)|0,e+256|0,f);break}default:h=8}if((h|0)==8)ng(b+(c[d+16>>2]|0)|0,e+256|0,f);switch(a[g+28>>0]|0){case 0:break;case 1:{if(!(c[e+512>>2]|0))h=12;else pg(b+(c[d+32>>2]|0)|0,e+512|0,f);break}default:h=12}if((h|0)==12)ng(b+(c[d+32>>2]|0)|0,e+512|0,f);switch(a[g+30>>0]|0){case 1:{h=14;break}case 0:return;default:{}}if((h|0)==14)if(c[e+768>>2]|0){pg(b+(c[d+48>>2]|0)|0,e+768|0,f);return}ng(b+(c[d+48>>2]|0)|0,e+768|0,f);return}function tg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;q=i>>>1;s=20;t=1;while(1){k=t<<4;if((k|0)<(k|4|0)){r=f+(t+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(q<<1)|0}}}else mg((c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(s|0))}t=t+1|0;if((t|0)==3)break;else s=s+16|0}return}function ug(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i>>>1;r=20;s=1;while(1){k=s<<4;if((k|0)<(k|4|0)){q=f+(s+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else mg((c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3){r=24;s=1;break}else r=r+16|0}while(1){q=s<<4;k=q|4;if((k|0)<(q|8|0)){q=f+(s+-1<<2)|0;do{l=k+4|0;if(!(a[j+(d[96537+l>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=1024)l=0-l>>31&1023;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else mg((c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3)break;else r=r+16|0}return}function vg(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;h=i;i=i+64|0;g=h;n=c[b>>2]|0;m=c[b+4>>2]|0;j=m+n|0;m=n-m|0;n=c[b+8>>2]|0;f=c[b+12>>2]|0;l=n-f|0;n=f+n|0;f=n+j|0;c[g>>2]=f;c[g+4>>2]=j-n;c[g+8>>2]=m-l;c[g+12>>2]=l+m;m=c[b+16>>2]|0;l=c[b+20>>2]|0;n=l+m|0;l=m-l|0;m=c[b+24>>2]|0;j=c[b+28>>2]|0;k=m-j|0;m=j+m|0;c[g+16>>2]=m+n;c[g+20>>2]=n-m;c[g+24>>2]=l-k;c[g+28>>2]=k+l;l=c[b+32>>2]|0;k=c[b+36>>2]|0;m=k+l|0;k=l-k|0;l=c[b+40>>2]|0;n=c[b+44>>2]|0;j=l-n|0;l=n+l|0;c[g+32>>2]=l+m;c[g+36>>2]=m-l;c[g+40>>2]=k-j;c[g+44>>2]=j+k;k=c[b+48>>2]|0;j=c[b+52>>2]|0;l=j+k|0;j=k-j|0;k=c[b+56>>2]|0;m=c[b+60>>2]|0;b=k-m|0;k=m+k|0;c[g+48>>2]=k+l;c[g+52>>2]=l-k;c[g+56>>2]=j-b;c[g+60>>2]=b+j;b=0;while(1){n=d[69169+b>>0]|0;j=c[g+(b+8<<2)>>2]|0;l=j+f|0;j=f-j|0;m=c[g+(b+4<<2)>>2]|0;f=c[g+(b+12<<2)>>2]|0;k=m-f|0;m=f+m|0;c[a+(n<<2)>>2]=(_(m+l|0,e)|0)+128>>8;c[a+(n+16<<2)>>2]=(_(k+j|0,e)|0)+128>>8;c[a+(n+64<<2)>>2]=(_(j-k|0,e)|0)+128>>8;c[a+(n+80<<2)>>2]=(_(l-m|0,e)|0)+128>>8;b=b+1|0;if((b|0)==4)break;f=c[g+(b<<2)>>2]|0}i=h;return}function wg(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=c[a>>2]|0;j=a+64|0;g=c[j>>2]|0;p=g+r|0;g=r-g|0;r=a+128|0;o=c[r>>2]|0;i=a+192|0;k=c[i>>2]|0;m=k+o|0;k=o-k|0;o=a+256|0;e=c[o>>2]|0;f=a+320|0;h=c[f>>2]|0;q=h+e|0;h=e-h|0;e=a+384|0;n=c[e>>2]|0;d=a+448|0;l=c[d>>2]|0;s=l+n|0;l=n-l|0;n=q+p|0;q=p-q|0;p=m-s|0;m=s+m|0;c[a>>2]=(_(m+n|0,b)|0)+128>>8;c[r>>2]=(_(p+q|0,b)|0)+128>>8;c[o>>2]=(_(q-p|0,b)|0)+128>>8;c[e>>2]=(_(n-m|0,b)|0)+128>>8;e=h+g|0;h=g-h|0;g=k-l|0;a=l+k|0;c[j>>2]=(_(a+e|0,b)|0)+128>>8;c[i>>2]=(_(g+h|0,b)|0)+128>>8;c[f>>2]=(_(h-g|0,b)|0)+128>>8;c[d>>2]=(_(e-a|0,b)|0)+128>>8;return}function xg(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;i=c[a>>2]|0;j=a+64|0;e=c[j>>2]|0;g=a+128|0;h=c[g>>2]|0;d=a+192|0;k=c[d>>2]|0;f=i-e|0;i=e+i|0;e=h-k|0;h=k+h|0;c[a>>2]=(_(h+i|0,b)|0)>>7;c[j>>2]=(_(e+f|0,b)|0)>>7;c[g>>2]=(_(i-h|0,b)|0)>>7;c[d>>2]=(_(f-e|0,b)|0)>>7;return}function yg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;g=(c[d>>2]|0)+32|0;i=d+32|0;l=c[i>>2]|0;m=l+g|0;l=g-l|0;g=d+16|0;h=c[g>>2]|0;k=d+48|0;n=c[k>>2]|0;j=(h>>1)-n|0;h=(n>>1)+h|0;c[d>>2]=h+m;c[g>>2]=j+l;c[i>>2]=l-j;c[k>>2]=m-h;k=d+4|0;h=c[k>>2]|0;m=d+36|0;i=c[m>>2]|0;j=i+h|0;i=h-i|0;h=d+20|0;l=c[h>>2]|0;g=d+52|0;n=c[g>>2]|0;o=(l>>1)-n|0;l=(n>>1)+l|0;c[k>>2]=l+j;c[h>>2]=o+i;c[m>>2]=i-o;c[g>>2]=j-l;g=d+8|0;l=c[g>>2]|0;j=d+40|0;m=c[j>>2]|0;o=m+l|0;m=l-m|0;l=d+24|0;i=c[l>>2]|0;h=d+56|0;k=c[h>>2]|0;n=(i>>1)-k|0;i=(k>>1)+i|0;c[g>>2]=i+o;c[l>>2]=n+m;c[j>>2]=m-n;c[h>>2]=o-i;h=d+12|0;i=c[h>>2]|0;o=d+44|0;j=c[o>>2]|0;n=j+i|0;j=i-j|0;i=d+28|0;m=c[i>>2]|0;l=d+60|0;g=c[l>>2]|0;k=(m>>1)-g|0;m=(g>>1)+m|0;c[h>>2]=m+n;c[i>>2]=k+j;c[o>>2]=j-k;c[l>>2]=n-m;l=f>>1;m=l<<1;n=l*3|0;o=0;do{g=o<<2;k=c[d+(g<<2)>>2]|0;h=c[d+((g|2)<<2)>>2]|0;j=h+k|0;h=k-h|0;k=c[d+((g|1)<<2)>>2]|0;g=c[d+((g|3)<<2)>>2]|0;i=(k>>1)-g|0;k=(g>>1)+k|0;g=a+(o<<1)|0;f=(k+j>>6)+(e[g>>1]|0)|0;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=f;g=a+(o+l<<1)|0;f=(e[g>>1]|0)+(i+h>>6)|0;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=f;g=a+(o+m<<1)|0;f=(e[g>>1]|0)+(h-i>>6)|0;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=f;g=a+(o+n<<1)|0;f=(e[g>>1]|0)+(j-k>>6)|0;if(f>>>0>=4096)f=0-f>>31&4095;b[g>>1]=f;o=o+1|0}while((o|0)!=4);f=d+64|0;do{c[d>>2]=0;d=d+4|0}while((d|0)<(f|0));return}function zg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;h=(c[d>>2]|0)+32|0;c[d>>2]=h;g=0;while(1){w=d+(g+32<<2)|0;v=c[w>>2]|0;t=v+h|0;v=h-v|0;p=d+(g+16<<2)|0;u=c[p>>2]|0;o=d+(g+48<<2)|0;i=c[o>>2]|0;q=(u>>1)-i|0;u=(i>>1)+u|0;i=u+t|0;m=q+v|0;q=v-q|0;u=t-u|0;t=d+(g+24<<2)|0;v=c[t>>2]|0;s=d+(g+40<<2)|0;r=c[s>>2]|0;k=d+(g+56<<2)|0;n=c[k>>2]|0;j=r-v-n-(n>>1)|0;l=d+(g+8<<2)|0;h=c[l>>2]|0;x=n-((v>>1)+v)+h|0;n=n+r+(r>>1)-h|0;h=r+v+h+(h>>1)|0;v=(h>>2)+j|0;r=(n>>2)+x|0;n=(x>>2)-n|0;j=h-(j>>2)|0;c[d+(g<<2)>>2]=j+i;c[k>>2]=i-j;c[l>>2]=n+m;c[o>>2]=m-n;c[p>>2]=r+q;c[s>>2]=q-r;c[t>>2]=v+u;c[w>>2]=u-v;g=g+1|0;if((g|0)==8)break;h=c[d+(g<<2)>>2]|0}p=f>>1;q=p<<1;r=p*3|0;s=p<<2;t=p*5|0;u=p*6|0;v=p*7|0;w=0;do{h=w<<3;i=c[d+(h<<2)>>2]|0;j=c[d+((h|4)<<2)>>2]|0;f=j+i|0;j=i-j|0;i=c[d+((h|2)<<2)>>2]|0;o=c[d+((h|6)<<2)>>2]|0;k=(i>>1)-o|0;i=(o>>1)+i|0;o=i+f|0;m=k+j|0;k=j-k|0;i=f-i|0;f=c[d+((h|3)<<2)>>2]|0;j=c[d+((h|5)<<2)>>2]|0;l=c[d+((h|7)<<2)>>2]|0;n=j-f-l-(l>>1)|0;h=c[d+((h|1)<<2)>>2]|0;g=l-((f>>1)+f)+h|0;l=l+j+(j>>1)-h|0;h=j+f+h+(h>>1)|0;f=(h>>2)+n|0;j=(l>>2)+g|0;l=(g>>2)-l|0;n=h-(n>>2)|0;h=a+(w<<1)|0;g=(n+o>>6)+(e[h>>1]|0)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+p<<1)|0;g=(e[h>>1]|0)+(l+m>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+q<<1)|0;g=(e[h>>1]|0)+(j+k>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+r<<1)|0;g=(e[h>>1]|0)+(f+i>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+s<<1)|0;g=(e[h>>1]|0)+(i-f>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+t<<1)|0;g=(e[h>>1]|0)+(k-j>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+u<<1)|0;g=(e[h>>1]|0)+(m-l>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;h=a+(w+v<<1)|0;g=(e[h>>1]|0)+(o-n>>6)|0;if(g>>>0>=4096)g=0-g>>31&4095;b[h>>1]=g;w=w+1|0}while((w|0)!=8);FF(d|0,0,256)|0;return}function Ag(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;h=f>>>1;c[d>>2]=0;g=0;while(1){f=(e[a>>1]|0)+i|0;if(f>>>0>=4096)f=0-f>>31&4095;b[a>>1]=f;d=a+2|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=4096)f=0-f>>31&4095;b[d>>1]=f;d=a+4|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=4096)f=0-f>>31&4095;b[d>>1]=f;d=a+6|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=4096)f=0-f>>31&4095;b[d>>1]=f;g=g+1|0;if((g|0)==4)break;else a=a+(h<<1)|0}return}function Bg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;c[d>>2]=0;h=f>>>1;g=0;while(1){d=(e[a>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;f=a+2|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;f=a+4|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;f=a+6|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;f=a+8|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;f=a+10|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;f=a+12|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;f=a+14|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=4096)d=0-d>>31&4095;b[f>>1]=d;g=g+1|0;if((g|0)==8)break;else a=a+(h<<1)|0}return}function Cg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i>>>1;p=0;do{switch(a[j+(d[96537+p>>0]|0)>>0]|0){case 0:break;case 1:{if(!(c[h+(p<<4<<2)>>2]|0))r=14;else{o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}break}default:r=14}if((r|0)==14){r=0;yg(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i)}p=p+1|0}while((p|0)!=16);return}function Dg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0;m=i>>>1;p=0;do{if(!(a[j+(d[96537+p>>0]|0)>>0]|0)){if(c[h+(p<<4<<2)>>2]|0){o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=4096)k=0-k>>31&4095;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}}else yg(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i);p=p+1|0}while((p|0)!=16);return}function Eg(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;switch(a[g+12>>0]|0){case 0:break;case 1:{if(!(c[e>>2]|0))h=4;else Bg(b+(c[d>>2]|0)|0,e,f);break}default:h=4}if((h|0)==4)zg(b+(c[d>>2]|0)|0,e,f);switch(a[g+14>>0]|0){case 0:break;case 1:{if(!(c[e+256>>2]|0))h=8;else Bg(b+(c[d+16>>2]|0)|0,e+256|0,f);break}default:h=8}if((h|0)==8)zg(b+(c[d+16>>2]|0)|0,e+256|0,f);switch(a[g+28>>0]|0){case 0:break;case 1:{if(!(c[e+512>>2]|0))h=12;else Bg(b+(c[d+32>>2]|0)|0,e+512|0,f);break}default:h=12}if((h|0)==12)zg(b+(c[d+32>>2]|0)|0,e+512|0,f);switch(a[g+30>>0]|0){case 1:{h=14;break}case 0:return;default:{}}if((h|0)==14)if(c[e+768>>2]|0){Bg(b+(c[d+48>>2]|0)|0,e+768|0,f);return}zg(b+(c[d+48>>2]|0)|0,e+768|0,f);return}function Fg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;q=i>>>1;s=20;t=1;while(1){k=t<<4;if((k|0)<(k|4|0)){r=f+(t+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(q<<1)|0}}}else yg((c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(s|0))}t=t+1|0;if((t|0)==3)break;else s=s+16|0}return}function Gg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i>>>1;r=20;s=1;while(1){k=s<<4;if((k|0)<(k|4|0)){q=f+(s+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else yg((c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3){r=24;s=1;break}else r=r+16|0}while(1){q=s<<4;k=q|4;if((k|0)<(q|8|0)){q=f+(s+-1<<2)|0;do{l=k+4|0;if(!(a[j+(d[96537+l>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=4096)l=0-l>>31&4095;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else yg((c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3)break;else r=r+16|0}return}function Hg(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;h=i;i=i+64|0;g=h;n=c[b>>2]|0;m=c[b+4>>2]|0;j=m+n|0;m=n-m|0;n=c[b+8>>2]|0;f=c[b+12>>2]|0;l=n-f|0;n=f+n|0;f=n+j|0;c[g>>2]=f;c[g+4>>2]=j-n;c[g+8>>2]=m-l;c[g+12>>2]=l+m;m=c[b+16>>2]|0;l=c[b+20>>2]|0;n=l+m|0;l=m-l|0;m=c[b+24>>2]|0;j=c[b+28>>2]|0;k=m-j|0;m=j+m|0;c[g+16>>2]=m+n;c[g+20>>2]=n-m;c[g+24>>2]=l-k;c[g+28>>2]=k+l;l=c[b+32>>2]|0;k=c[b+36>>2]|0;m=k+l|0;k=l-k|0;l=c[b+40>>2]|0;n=c[b+44>>2]|0;j=l-n|0;l=n+l|0;c[g+32>>2]=l+m;c[g+36>>2]=m-l;c[g+40>>2]=k-j;c[g+44>>2]=j+k;k=c[b+48>>2]|0;j=c[b+52>>2]|0;l=j+k|0;j=k-j|0;k=c[b+56>>2]|0;m=c[b+60>>2]|0;b=k-m|0;k=m+k|0;c[g+48>>2]=k+l;c[g+52>>2]=l-k;c[g+56>>2]=j-b;c[g+60>>2]=b+j;b=0;while(1){n=d[69169+b>>0]|0;j=c[g+(b+8<<2)>>2]|0;l=j+f|0;j=f-j|0;m=c[g+(b+4<<2)>>2]|0;f=c[g+(b+12<<2)>>2]|0;k=m-f|0;m=f+m|0;c[a+(n<<2)>>2]=(_(m+l|0,e)|0)+128>>8;c[a+(n+16<<2)>>2]=(_(k+j|0,e)|0)+128>>8;c[a+(n+64<<2)>>2]=(_(j-k|0,e)|0)+128>>8;c[a+(n+80<<2)>>2]=(_(l-m|0,e)|0)+128>>8;b=b+1|0;if((b|0)==4)break;f=c[g+(b<<2)>>2]|0}i=h;return}function Ig(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=c[a>>2]|0;j=a+64|0;g=c[j>>2]|0;p=g+r|0;g=r-g|0;r=a+128|0;o=c[r>>2]|0;i=a+192|0;k=c[i>>2]|0;m=k+o|0;k=o-k|0;o=a+256|0;e=c[o>>2]|0;f=a+320|0;h=c[f>>2]|0;q=h+e|0;h=e-h|0;e=a+384|0;n=c[e>>2]|0;d=a+448|0;l=c[d>>2]|0;s=l+n|0;l=n-l|0;n=q+p|0;q=p-q|0;p=m-s|0;m=s+m|0;c[a>>2]=(_(m+n|0,b)|0)+128>>8;c[r>>2]=(_(p+q|0,b)|0)+128>>8;c[o>>2]=(_(q-p|0,b)|0)+128>>8;c[e>>2]=(_(n-m|0,b)|0)+128>>8;e=h+g|0;h=g-h|0;g=k-l|0;a=l+k|0;c[j>>2]=(_(a+e|0,b)|0)+128>>8;c[i>>2]=(_(g+h|0,b)|0)+128>>8;c[f>>2]=(_(h-g|0,b)|0)+128>>8;c[d>>2]=(_(e-a|0,b)|0)+128>>8;return}function Jg(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;i=c[a>>2]|0;j=a+64|0;e=c[j>>2]|0;g=a+128|0;h=c[g>>2]|0;d=a+192|0;k=c[d>>2]|0;f=i-e|0;i=e+i|0;e=h-k|0;h=k+h|0;c[a>>2]=(_(h+i|0,b)|0)>>7;c[j>>2]=(_(e+f|0,b)|0)>>7;c[g>>2]=(_(i-h|0,b)|0)>>7;c[d>>2]=(_(f-e|0,b)|0)>>7;return}function Kg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;g=(c[d>>2]|0)+32|0;i=d+32|0;l=c[i>>2]|0;m=l+g|0;l=g-l|0;g=d+16|0;h=c[g>>2]|0;k=d+48|0;n=c[k>>2]|0;j=(h>>1)-n|0;h=(n>>1)+h|0;c[d>>2]=h+m;c[g>>2]=j+l;c[i>>2]=l-j;c[k>>2]=m-h;k=d+4|0;h=c[k>>2]|0;m=d+36|0;i=c[m>>2]|0;j=i+h|0;i=h-i|0;h=d+20|0;l=c[h>>2]|0;g=d+52|0;n=c[g>>2]|0;o=(l>>1)-n|0;l=(n>>1)+l|0;c[k>>2]=l+j;c[h>>2]=o+i;c[m>>2]=i-o;c[g>>2]=j-l;g=d+8|0;l=c[g>>2]|0;j=d+40|0;m=c[j>>2]|0;o=m+l|0;m=l-m|0;l=d+24|0;i=c[l>>2]|0;h=d+56|0;k=c[h>>2]|0;n=(i>>1)-k|0;i=(k>>1)+i|0;c[g>>2]=i+o;c[l>>2]=n+m;c[j>>2]=m-n;c[h>>2]=o-i;h=d+12|0;i=c[h>>2]|0;o=d+44|0;j=c[o>>2]|0;n=j+i|0;j=i-j|0;i=d+28|0;m=c[i>>2]|0;l=d+60|0;g=c[l>>2]|0;k=(m>>1)-g|0;m=(g>>1)+m|0;c[h>>2]=m+n;c[i>>2]=k+j;c[o>>2]=j-k;c[l>>2]=n-m;l=f>>1;m=l<<1;n=l*3|0;o=0;do{g=o<<2;k=c[d+(g<<2)>>2]|0;h=c[d+((g|2)<<2)>>2]|0;j=h+k|0;h=k-h|0;k=c[d+((g|1)<<2)>>2]|0;g=c[d+((g|3)<<2)>>2]|0;i=(k>>1)-g|0;k=(g>>1)+k|0;g=a+(o<<1)|0;f=(k+j>>6)+(e[g>>1]|0)|0;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=f;g=a+(o+l<<1)|0;f=(e[g>>1]|0)+(i+h>>6)|0;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=f;g=a+(o+m<<1)|0;f=(e[g>>1]|0)+(h-i>>6)|0;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=f;g=a+(o+n<<1)|0;f=(e[g>>1]|0)+(j-k>>6)|0;if(f>>>0>=16384)f=0-f>>31&16383;b[g>>1]=f;o=o+1|0}while((o|0)!=4);f=d+64|0;do{c[d>>2]=0;d=d+4|0}while((d|0)<(f|0));return}function Lg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;h=(c[d>>2]|0)+32|0;c[d>>2]=h;g=0;while(1){w=d+(g+32<<2)|0;v=c[w>>2]|0;t=v+h|0;v=h-v|0;p=d+(g+16<<2)|0;u=c[p>>2]|0;o=d+(g+48<<2)|0;i=c[o>>2]|0;q=(u>>1)-i|0;u=(i>>1)+u|0;i=u+t|0;m=q+v|0;q=v-q|0;u=t-u|0;t=d+(g+24<<2)|0;v=c[t>>2]|0;s=d+(g+40<<2)|0;r=c[s>>2]|0;k=d+(g+56<<2)|0;n=c[k>>2]|0;j=r-v-n-(n>>1)|0;l=d+(g+8<<2)|0;h=c[l>>2]|0;x=n-((v>>1)+v)+h|0;n=n+r+(r>>1)-h|0;h=r+v+h+(h>>1)|0;v=(h>>2)+j|0;r=(n>>2)+x|0;n=(x>>2)-n|0;j=h-(j>>2)|0;c[d+(g<<2)>>2]=j+i;c[k>>2]=i-j;c[l>>2]=n+m;c[o>>2]=m-n;c[p>>2]=r+q;c[s>>2]=q-r;c[t>>2]=v+u;c[w>>2]=u-v;g=g+1|0;if((g|0)==8)break;h=c[d+(g<<2)>>2]|0}p=f>>1;q=p<<1;r=p*3|0;s=p<<2;t=p*5|0;u=p*6|0;v=p*7|0;w=0;do{h=w<<3;i=c[d+(h<<2)>>2]|0;j=c[d+((h|4)<<2)>>2]|0;f=j+i|0;j=i-j|0;i=c[d+((h|2)<<2)>>2]|0;o=c[d+((h|6)<<2)>>2]|0;k=(i>>1)-o|0;i=(o>>1)+i|0;o=i+f|0;m=k+j|0;k=j-k|0;i=f-i|0;f=c[d+((h|3)<<2)>>2]|0;j=c[d+((h|5)<<2)>>2]|0;l=c[d+((h|7)<<2)>>2]|0;n=j-f-l-(l>>1)|0;h=c[d+((h|1)<<2)>>2]|0;g=l-((f>>1)+f)+h|0;l=l+j+(j>>1)-h|0;h=j+f+h+(h>>1)|0;f=(h>>2)+n|0;j=(l>>2)+g|0;l=(g>>2)-l|0;n=h-(n>>2)|0;h=a+(w<<1)|0;g=(n+o>>6)+(e[h>>1]|0)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+p<<1)|0;g=(e[h>>1]|0)+(l+m>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+q<<1)|0;g=(e[h>>1]|0)+(j+k>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+r<<1)|0;g=(e[h>>1]|0)+(f+i>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+s<<1)|0;g=(e[h>>1]|0)+(i-f>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+t<<1)|0;g=(e[h>>1]|0)+(k-j>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+u<<1)|0;g=(e[h>>1]|0)+(m-l>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;h=a+(w+v<<1)|0;g=(e[h>>1]|0)+(o-n>>6)|0;if(g>>>0>=16384)g=0-g>>31&16383;b[h>>1]=g;w=w+1|0}while((w|0)!=8);FF(d|0,0,256)|0;return}function Mg(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;h=f>>>1;c[d>>2]=0;g=0;while(1){f=(e[a>>1]|0)+i|0;if(f>>>0>=16384)f=0-f>>31&16383;b[a>>1]=f;d=a+2|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=16384)f=0-f>>31&16383;b[d>>1]=f;d=a+4|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=16384)f=0-f>>31&16383;b[d>>1]=f;d=a+6|0;f=(e[d>>1]|0)+i|0;if(f>>>0>=16384)f=0-f>>31&16383;b[d>>1]=f;g=g+1|0;if((g|0)==4)break;else a=a+(h<<1)|0}return}function Ng(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;i=(c[d>>2]|0)+32>>6;c[d>>2]=0;h=f>>>1;g=0;while(1){d=(e[a>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;f=a+2|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;f=a+4|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;f=a+6|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;f=a+8|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;f=a+10|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;f=a+12|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;f=a+14|0;d=(e[f>>1]|0)+i|0;if(d>>>0>=16384)d=0-d>>31&16383;b[f>>1]=d;g=g+1|0;if((g|0)==8)break;else a=a+(h<<1)|0}return}function Og(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;m=i>>>1;p=0;do{switch(a[j+(d[96537+p>>0]|0)>>0]|0){case 0:break;case 1:{if(!(c[h+(p<<4<<2)>>2]|0))r=14;else{o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}break}default:r=14}if((r|0)==14){r=0;Kg(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i)}p=p+1|0}while((p|0)!=16);return}function Pg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0;m=i>>>1;p=0;do{if(!(a[j+(d[96537+p>>0]|0)>>0]|0)){if(c[h+(p<<4<<2)>>2]|0){o=f+(c[g+(p<<2)>>2]|0)|0;q=h+(p<<5<<1)|0;n=(c[q>>2]|0)+32>>6;c[q>>2]=0;q=0;while(1){k=(e[o>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[o>>1]=k;l=o+2|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[l>>1]=k;l=o+4|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[l>>1]=k;l=o+6|0;k=(e[l>>1]|0)+n|0;if(k>>>0>=16384)k=0-k>>31&16383;b[l>>1]=k;q=q+1|0;if((q|0)==4)break;else o=o+(m<<1)|0}}}else Kg(f+(c[g+(p<<2)>>2]|0)|0,h+(p<<5<<1)|0,i);p=p+1|0}while((p|0)!=16);return}function Qg(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0;switch(a[g+12>>0]|0){case 0:break;case 1:{if(!(c[e>>2]|0))h=4;else Ng(b+(c[d>>2]|0)|0,e,f);break}default:h=4}if((h|0)==4)Lg(b+(c[d>>2]|0)|0,e,f);switch(a[g+14>>0]|0){case 0:break;case 1:{if(!(c[e+256>>2]|0))h=8;else Ng(b+(c[d+16>>2]|0)|0,e+256|0,f);break}default:h=8}if((h|0)==8)Lg(b+(c[d+16>>2]|0)|0,e+256|0,f);switch(a[g+28>>0]|0){case 0:break;case 1:{if(!(c[e+512>>2]|0))h=12;else Ng(b+(c[d+32>>2]|0)|0,e+512|0,f);break}default:h=12}if((h|0)==12)Lg(b+(c[d+32>>2]|0)|0,e+512|0,f);switch(a[g+30>>0]|0){case 1:{h=14;break}case 0:return;default:{}}if((h|0)==14)if(c[e+768>>2]|0){Ng(b+(c[d+48>>2]|0)|0,e+768|0,f);return}Lg(b+(c[d+48>>2]|0)|0,e+768|0,f);return}function Rg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;q=i>>>1;s=20;t=1;while(1){k=t<<4;if((k|0)<(k|4|0)){r=f+(t+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(q<<1)|0}}}else Kg((c[r>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(s|0))}t=t+1|0;if((t|0)==3)break;else s=s+16|0}return}function Sg(f,g,h,i,j){f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i>>>1;r=20;s=1;while(1){k=s<<4;if((k|0)<(k|4|0)){q=f+(s+-1<<2)|0;do{if(!(a[j+(d[96537+k>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else Kg((c[q>>2]|0)+(c[g+(k<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3){r=24;s=1;break}else r=r+16|0}while(1){q=s<<4;k=q|4;if((k|0)<(q|8|0)){q=f+(s+-1<<2)|0;do{l=k+4|0;if(!(a[j+(d[96537+l>>0]|0)>>0]|0)){if(c[h+(k<<4<<2)>>2]|0){o=(c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0;p=h+(k<<5<<1)|0;n=(c[p>>2]|0)+32>>6;c[p>>2]=0;p=0;while(1){l=(e[o>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[o>>1]=l;m=o+2|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;m=o+4|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;m=o+6|0;l=(e[m>>1]|0)+n|0;if(l>>>0>=16384)l=0-l>>31&16383;b[m>>1]=l;p=p+1|0;if((p|0)==4)break;else o=o+(t<<1)|0}}}else Kg((c[q>>2]|0)+(c[g+(l<<2)>>2]|0)|0,h+(k<<5<<1)|0,i);k=k+1|0}while((k|0)!=(r|0))}s=s+1|0;if((s|0)==3)break;else r=r+16|0}return}function Tg(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;h=i;i=i+64|0;g=h;n=c[b>>2]|0;m=c[b+4>>2]|0;j=m+n|0;m=n-m|0;n=c[b+8>>2]|0;f=c[b+12>>2]|0;l=n-f|0;n=f+n|0;f=n+j|0;c[g>>2]=f;c[g+4>>2]=j-n;c[g+8>>2]=m-l;c[g+12>>2]=l+m;m=c[b+16>>2]|0;l=c[b+20>>2]|0;n=l+m|0;l=m-l|0;m=c[b+24>>2]|0;j=c[b+28>>2]|0;k=m-j|0;m=j+m|0;c[g+16>>2]=m+n;c[g+20>>2]=n-m;c[g+24>>2]=l-k;c[g+28>>2]=k+l;l=c[b+32>>2]|0;k=c[b+36>>2]|0;m=k+l|0;k=l-k|0;l=c[b+40>>2]|0;n=c[b+44>>2]|0;j=l-n|0;l=n+l|0;c[g+32>>2]=l+m;c[g+36>>2]=m-l;c[g+40>>2]=k-j;c[g+44>>2]=j+k;k=c[b+48>>2]|0;j=c[b+52>>2]|0;l=j+k|0;j=k-j|0;k=c[b+56>>2]|0;m=c[b+60>>2]|0;b=k-m|0;k=m+k|0;c[g+48>>2]=k+l;c[g+52>>2]=l-k;c[g+56>>2]=j-b;c[g+60>>2]=b+j;b=0;while(1){n=d[69169+b>>0]|0;j=c[g+(b+8<<2)>>2]|0;l=j+f|0;j=f-j|0;m=c[g+(b+4<<2)>>2]|0;f=c[g+(b+12<<2)>>2]|0;k=m-f|0;m=f+m|0;c[a+(n<<2)>>2]=(_(m+l|0,e)|0)+128>>8;c[a+(n+16<<2)>>2]=(_(k+j|0,e)|0)+128>>8;c[a+(n+64<<2)>>2]=(_(j-k|0,e)|0)+128>>8;c[a+(n+80<<2)>>2]=(_(l-m|0,e)|0)+128>>8;b=b+1|0;if((b|0)==4)break;f=c[g+(b<<2)>>2]|0}i=h;return}function Ug(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=c[a>>2]|0;j=a+64|0;g=c[j>>2]|0;p=g+r|0;g=r-g|0;r=a+128|0;o=c[r>>2]|0;i=a+192|0;k=c[i>>2]|0;m=k+o|0;k=o-k|0;o=a+256|0;e=c[o>>2]|0;f=a+320|0;h=c[f>>2]|0;q=h+e|0;h=e-h|0;e=a+384|0;n=c[e>>2]|0;d=a+448|0;l=c[d>>2]|0;s=l+n|0;l=n-l|0;n=q+p|0;q=p-q|0;p=m-s|0;m=s+m|0;c[a>>2]=(_(m+n|0,b)|0)+128>>8;c[r>>2]=(_(p+q|0,b)|0)+128>>8;c[o>>2]=(_(q-p|0,b)|0)+128>>8;c[e>>2]=(_(n-m|0,b)|0)+128>>8;e=h+g|0;h=g-h|0;g=k-l|0;a=l+k|0;c[j>>2]=(_(a+e|0,b)|0)+128>>8;c[i>>2]=(_(g+h|0,b)|0)+128>>8;c[f>>2]=(_(h-g|0,b)|0)+128>>8;c[d>>2]=(_(e-a|0,b)|0)+128>>8;return}function Vg(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;i=c[a>>2]|0;j=a+64|0;e=c[j>>2]|0;g=a+128|0;h=c[g>>2]|0;d=a+192|0;k=c[d>>2]|0;f=i-e|0;i=e+i|0;e=h-k|0;h=k+h|0;c[a>>2]=(_(h+i|0,b)|0)>>7;c[j>>2]=(_(e+f|0,b)|0)>>7;c[g>>2]=(_(i-h|0,b)|0)>>7;c[d>>2]=(_(f-e|0,b)|0)>>7;return}function Wg(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0;h=i;i=i+16|0;f=h;switch(d|0){case 9:{if((b|0)==70){c[a>>2]=32;c[a+4>>2]=33;c[a+8>>2]=34;c[a+12>>2]=49;c[a+16>>2]=37;c[a+20>>2]=38;c[a+24>>2]=39;c[a+28>>2]=50;c[a+32>>2]=51;c[a+36>>2]=43;c[a+40>>2]=44;c[a+44>>2]=48;c[a+48>>2]=52;c[a+52>>2]=53;c[a+56>>2]=54}else{f=(b|0)==141;d=(b|0)==1448097584|f;if((b|0)<1448097584)switch(b|0){case 141:{g=4;break}default:g=5}else switch(b|0){case 1448097584:{g=4;break}default:g=5}if((g|0)==4){c[a>>2]=30;c[a+4>>2]=31}else if((g|0)==5){c[a>>2]=32;c[a+4>>2]=33}c[a+8>>2]=34;c[a+12>>2]=(b|0)==24?36:35;c[a+16>>2]=37;c[a+20>>2]=38;c[a+24>>2]=39;c[a+28>>2]=d?41:40;c[a+32>>2]=42;d=a+36|0;if((b|0)<1448097584)switch(b|0){case 141:{g=8;break}default:g=7}else switch(b|0){case 1448097584:{g=8;break}default:g=7}if((g|0)==7){c[d>>2]=43;c[a+40>>2]=44}else if((g|0)==8){c[d>>2]=45;c[a+48>>2]=46;c[a+52>>2]=47;c[a+40>>2]=32;c[a+56>>2]=33}if(!f)c[a+44>>2]=48}c[a+60>>2]=41;c[a+64>>2]=42;c[a+68>>2]=43;c[a+72>>2]=44;c[a+76>>2]=45;c[a+80>>2]=46;c[a+84>>2]=47;c[a+88>>2]=48;c[a+92>>2]=49;c[a+96>>2]=50;c[a+100>>2]=51;c[a+104>>2]=52;f=(e|0)<2;e=a+108|0;c[a+116>>2]=f?13:12;c[a+112>>2]=f?15:14;if((b|0)<1448097584)switch(b|0){case 141:{g=16;break}default:g=13}else switch(b|0){case 1448097584:{g=16;break}default:g=13}do if((g|0)==13){d=a+120|0;if(f){c[d>>2]=16;break}else{c[d>>2]=17;break}}else if((g|0)==16)c[a+120>>2]=18;while(0);a:do if((b|0)<141)switch(b|0){case 70:{g=21;break}default:g=18}else if((b|0)<1448097584)switch(b|0){case 141:{g=21;break a}default:{g=18;break a}}else switch(b|0){case 1448097584:{g=21;break a}default:{g=18;break a}}while(0);b:do if((g|0)==18)if(f){c[e>>2]=19;c[a+124>>2]=20;c[a+128>>2]=21;c[a+136>>2]=22;c[a+140>>2]=23;c[a+144>>2]=24;c[a+148>>2]=25;break}else{c[e>>2]=26;c[a+124>>2]=27;c[a+128>>2]=28;c[a+136>>2]=29;c[a+140>>2]=30;c[a+144>>2]=31;c[a+148>>2]=32;break}else if((g|0)==21){c[e>>2]=33;c[a+124>>2]=34;c[a+128>>2]=35;if((b|0)<1448097584)switch(b|0){case 141:break;default:break b}else switch(b|0){case 1448097584:break;default:break b}c[a+136>>2]=36;c[a+140>>2]=37}while(0);c[a+132>>2]=f?39:38;c[a+152>>2]=40;c[a+160>>2]=41;c[a+156>>2]=42;c:do if((b|0)<141)switch(b|0){case 24:{c[a+164>>2]=43;break c}case 70:{c[a+164>>2]=44;break c}default:{g=27;break c}}else{if((b|0)<1448097584)switch(b|0){case 141:break;default:{g=27;break c}}else switch(b|0){case 1448097584:break;default:{g=27;break c}}c[a+164>>2]=45;c[a+180>>2]=46;c[a+184>>2]=47}while(0);if((g|0)==27)c[a+164>>2]=48;c[a+168>>2]=49;c[a+172>>2]=50;c[a+176>>2]=51;c[a+188>>2]=55;c[a+192>>2]=56;c[a+196>>2]=57;c[a+200>>2]=58;c[a+204>>2]=66;c[a+208>>2]=67;c[a+220>>2]=f?54:53;c[a+216>>2]=f?56:55;c[a+232>>2]=57;c[a+228>>2]=58;i=h;return}case 10:{if((b|0)==70){c[a>>2]=59;c[a+4>>2]=60;c[a+8>>2]=61;c[a+12>>2]=49;c[a+16>>2]=63;c[a+20>>2]=64;c[a+24>>2]=65;c[a+28>>2]=50;c[a+32>>2]=51;c[a+36>>2]=68;c[a+40>>2]=69;c[a+44>>2]=72;c[a+48>>2]=52;c[a+52>>2]=53;c[a+56>>2]=54}else{f=(b|0)==141;d=(b|0)==1448097584|f;if((b|0)<1448097584)switch(b|0){case 141:{g=31;break}default:g=32}else switch(b|0){case 1448097584:{g=31;break}default:g=32}if((g|0)==31){c[a>>2]=30;c[a+4>>2]=31}else if((g|0)==32){c[a>>2]=59;c[a+4>>2]=60}c[a+8>>2]=61;c[a+12>>2]=(b|0)==24?36:62;c[a+16>>2]=63;c[a+20>>2]=64;c[a+24>>2]=65;c[a+28>>2]=d?41:66;c[a+32>>2]=67;d=a+36|0;if((b|0)<1448097584)switch(b|0){case 141:{g=35;break}default:g=34}else switch(b|0){case 1448097584:{g=35;break}default:g=34}if((g|0)==34){c[d>>2]=68;c[a+40>>2]=69}else if((g|0)==35){c[d>>2]=45;c[a+48>>2]=70;c[a+52>>2]=71;c[a+40>>2]=59;c[a+56>>2]=60}if(!f)c[a+44>>2]=72}c[a+60>>2]=59;c[a+64>>2]=60;c[a+68>>2]=61;c[a+72>>2]=62;c[a+76>>2]=63;c[a+80>>2]=64;c[a+84>>2]=65;c[a+88>>2]=66;c[a+92>>2]=67;c[a+96>>2]=68;c[a+100>>2]=69;c[a+104>>2]=70;f=(e|0)<2;e=a+108|0;c[a+116>>2]=f?53:52;c[a+112>>2]=f?55:54;if((b|0)<1448097584)switch(b|0){case 141:{g=43;break}default:g=40}else switch(b|0){case 1448097584:{g=43;break}default:g=40}do if((g|0)==40){d=a+120|0;if(f){c[d>>2]=56;break}else{c[d>>2]=57;break}}else if((g|0)==43)c[a+120>>2]=18;while(0);d:do if((b|0)<141)switch(b|0){case 70:{g=48;break}default:g=45}else if((b|0)<1448097584)switch(b|0){case 141:{g=48;break d}default:{g=45;break d}}else switch(b|0){case 1448097584:{g=48;break d}default:{g=45;break d}}while(0);e:do if((g|0)==45)if(f){c[e>>2]=58;c[a+124>>2]=59;c[a+128>>2]=60;c[a+136>>2]=61;c[a+140>>2]=62;c[a+144>>2]=63;c[a+148>>2]=64;break}else{c[e>>2]=65;c[a+124>>2]=66;c[a+128>>2]=67;c[a+136>>2]=68;c[a+140>>2]=69;c[a+144>>2]=70;c[a+148>>2]=71;break}else if((g|0)==48){c[e>>2]=33;c[a+124>>2]=34;c[a+128>>2]=35;if((b|0)<1448097584)switch(b|0){case 141:break;default:break e}else switch(b|0){case 1448097584:break;default:break e}c[a+136>>2]=72;c[a+140>>2]=73}while(0);c[a+132>>2]=f?75:74;c[a+152>>2]=76;c[a+160>>2]=77;c[a+156>>2]=78;f:do if((b|0)<141)switch(b|0){case 24:{c[a+164>>2]=43;break f}case 70:{c[a+164>>2]=44;break f}default:{g=54;break f}}else{if((b|0)<1448097584)switch(b|0){case 141:break;default:{g=54;break f}}else switch(b|0){case 1448097584:break;default:{g=54;break f}}c[a+164>>2]=45;c[a+180>>2]=79;c[a+184>>2]=80}while(0);if((g|0)==54)c[a+164>>2]=81;c[a+168>>2]=82;c[a+172>>2]=83;c[a+176>>2]=84;c[a+188>>2]=73;c[a+192>>2]=74;c[a+196>>2]=75;c[a+200>>2]=76;c[a+204>>2]=68;c[a+208>>2]=69;c[a+220>>2]=f?72:71;c[a+216>>2]=f?74:73;c[a+232>>2]=75;c[a+228>>2]=76;i=h;return}case 12:{if((b|0)==70){c[a>>2]=77;c[a+4>>2]=78;c[a+8>>2]=79;c[a+12>>2]=49;c[a+16>>2]=81;c[a+20>>2]=82;c[a+24>>2]=83;c[a+28>>2]=50;c[a+32>>2]=51;c[a+36>>2]=86;c[a+40>>2]=87;c[a+44>>2]=90;c[a+48>>2]=52;c[a+52>>2]=53;c[a+56>>2]=54}else{f=(b|0)==141;d=(b|0)==1448097584|f;if((b|0)<1448097584)switch(b|0){case 141:{g=58;break}default:g=59}else switch(b|0){case 1448097584:{g=58;break}default:g=59}if((g|0)==58){c[a>>2]=30;c[a+4>>2]=31}else if((g|0)==59){c[a>>2]=77;c[a+4>>2]=78}c[a+8>>2]=79;c[a+12>>2]=(b|0)==24?36:80;c[a+16>>2]=81;c[a+20>>2]=82;c[a+24>>2]=83;c[a+28>>2]=d?41:84;c[a+32>>2]=85;d=a+36|0;if((b|0)<1448097584)switch(b|0){case 141:{g=62;break}default:g=61}else switch(b|0){case 1448097584:{g=62;break}default:g=61}if((g|0)==61){c[d>>2]=86;c[a+40>>2]=87}else if((g|0)==62){c[d>>2]=45;c[a+48>>2]=88;c[a+52>>2]=89;c[a+40>>2]=77;c[a+56>>2]=78}if(!f)c[a+44>>2]=90}c[a+60>>2]=77;c[a+64>>2]=78;c[a+68>>2]=79;c[a+72>>2]=80;c[a+76>>2]=81;c[a+80>>2]=82;c[a+84>>2]=83;c[a+88>>2]=84;c[a+92>>2]=85;c[a+96>>2]=86;c[a+100>>2]=87;c[a+104>>2]=88;f=(e|0)<2;e=a+108|0;c[a+116>>2]=f?86:85;c[a+112>>2]=f?88:87;if((b|0)<1448097584)switch(b|0){case 141:{g=70;break}default:g=67}else switch(b|0){case 1448097584:{g=70;break}default:g=67}do if((g|0)==67){d=a+120|0;if(f){c[d>>2]=89;break}else{c[d>>2]=90;break}}else if((g|0)==70)c[a+120>>2]=18;while(0);g:do if((b|0)<141)switch(b|0){case 70:{g=75;break}default:g=72}else if((b|0)<1448097584)switch(b|0){case 141:{g=75;break g}default:{g=72;break g}}else switch(b|0){case 1448097584:{g=75;break g}default:{g=72;break g}}while(0);h:do if((g|0)==72)if(f){c[e>>2]=91;c[a+124>>2]=92;c[a+128>>2]=93;c[a+136>>2]=94;c[a+140>>2]=95;c[a+144>>2]=96;c[a+148>>2]=97;break}else{c[e>>2]=98;c[a+124>>2]=99;c[a+128>>2]=100;c[a+136>>2]=101;c[a+140>>2]=102;c[a+144>>2]=103;c[a+148>>2]=104;break}else if((g|0)==75){c[e>>2]=33;c[a+124>>2]=34;c[a+128>>2]=35;if((b|0)<1448097584)switch(b|0){case 141:break;default:break h}else switch(b|0){case 1448097584:break;default:break h}c[a+136>>2]=105;c[a+140>>2]=106}while(0);c[a+132>>2]=f?108:107;c[a+152>>2]=109;c[a+160>>2]=110;c[a+156>>2]=111;i:do if((b|0)<141)switch(b|0){case 24:{c[a+164>>2]=43;break i}case 70:{c[a+164>>2]=44;break i}default:{g=81;break i}}else{if((b|0)<1448097584)switch(b|0){case 141:break;default:{g=81;break i}}else switch(b|0){case 1448097584:break;default:{g=81;break i}}c[a+164>>2]=45;c[a+180>>2]=112;c[a+184>>2]=113}while(0);if((g|0)==81)c[a+164>>2]=114;c[a+168>>2]=115;c[a+172>>2]=116;c[a+176>>2]=117;c[a+188>>2]=91;c[a+192>>2]=92;c[a+196>>2]=93;c[a+200>>2]=94;c[a+204>>2]=70;c[a+208>>2]=71;c[a+220>>2]=f?90:89;c[a+216>>2]=f?92:91;c[a+232>>2]=93;c[a+228>>2]=94;i=h;return}case 14:{if((b|0)==70){c[a>>2]=95;c[a+4>>2]=96;c[a+8>>2]=97;c[a+12>>2]=49;c[a+16>>2]=99;c[a+20>>2]=100;c[a+24>>2]=101;c[a+28>>2]=50;c[a+32>>2]=51;c[a+36>>2]=104;c[a+40>>2]=105;c[a+44>>2]=108;c[a+48>>2]=52;c[a+52>>2]=53;c[a+56>>2]=54}else{f=(b|0)==141;d=(b|0)==1448097584|f;if((b|0)<1448097584)switch(b|0){case 141:{g=85;break}default:g=86}else switch(b|0){case 1448097584:{g=85;break}default:g=86}if((g|0)==85){c[a>>2]=30;c[a+4>>2]=31}else if((g|0)==86){c[a>>2]=95;c[a+4>>2]=96}c[a+8>>2]=97;c[a+12>>2]=(b|0)==24?36:98;c[a+16>>2]=99;c[a+20>>2]=100;c[a+24>>2]=101;c[a+28>>2]=d?41:102;c[a+32>>2]=103;d=a+36|0;if((b|0)<1448097584)switch(b|0){case 141:{g=89;break}default:g=88}else switch(b|0){case 1448097584:{g=89;break}default:g=88}if((g|0)==88){c[d>>2]=104;c[a+40>>2]=105}else if((g|0)==89){c[d>>2]=45;c[a+48>>2]=106;c[a+52>>2]=107;c[a+40>>2]=95;c[a+56>>2]=96}if(!f)c[a+44>>2]=108}c[a+60>>2]=95;c[a+64>>2]=96;c[a+68>>2]=97;c[a+72>>2]=98;c[a+76>>2]=99;c[a+80>>2]=100;c[a+84>>2]=101;c[a+88>>2]=102;c[a+92>>2]=103;c[a+96>>2]=104;c[a+100>>2]=105;c[a+104>>2]=106;f=(e|0)<2;e=a+108|0;c[a+116>>2]=f?119:118;c[a+112>>2]=f?121:120;if((b|0)<1448097584)switch(b|0){case 141:{g=97;break}default:g=94}else switch(b|0){case 1448097584:{g=97;break}default:g=94}do if((g|0)==94){d=a+120|0;if(f){c[d>>2]=122;break}else{c[d>>2]=123;break}}else if((g|0)==97)c[a+120>>2]=18;while(0);j:do if((b|0)<141)switch(b|0){case 70:{g=102;break}default:g=99}else if((b|0)<1448097584)switch(b|0){case 141:{g=102;break j}default:{g=99;break j}}else switch(b|0){case 1448097584:{g=102;break j}default:{g=99;break j}}while(0);k:do if((g|0)==99)if(f){c[e>>2]=124;c[a+124>>2]=125;c[a+128>>2]=126;c[a+136>>2]=127;c[a+140>>2]=128;c[a+144>>2]=129;c[a+148>>2]=130;break}else{c[e>>2]=131;c[a+124>>2]=132;c[a+128>>2]=133;c[a+136>>2]=134;c[a+140>>2]=135;c[a+144>>2]=136;c[a+148>>2]=137;break}else if((g|0)==102){c[e>>2]=33;c[a+124>>2]=34;c[a+128>>2]=35;if((b|0)<1448097584)switch(b|0){case 141:break;default:break k}else switch(b|0){case 1448097584:break;default:break k}c[a+136>>2]=138;c[a+140>>2]=139}while(0);c[a+132>>2]=f?141:140;c[a+152>>2]=142;c[a+160>>2]=143;c[a+156>>2]=144;l:do if((b|0)<141)switch(b|0){case 24:{c[a+164>>2]=43;break l}case 70:{c[a+164>>2]=44;break l}default:{g=108;break l}}else{if((b|0)<1448097584)switch(b|0){case 141:break;default:{g=108;break l}}else switch(b|0){case 1448097584:break;default:{g=108;break l}}c[a+164>>2]=45;c[a+180>>2]=145;c[a+184>>2]=146}while(0);if((g|0)==108)c[a+164>>2]=147;c[a+168>>2]=148;c[a+172>>2]=149;c[a+176>>2]=150;c[a+188>>2]=109;c[a+192>>2]=110;c[a+196>>2]=111;c[a+200>>2]=112;c[a+204>>2]=72;c[a+208>>2]=73;c[a+220>>2]=f?108:107;c[a+216>>2]=f?110:109;c[a+232>>2]=111;c[a+228>>2]=112;i=h;return}default:{if((d|0)>=9){c[f>>2]=69173;c[f+4>>2]=69186;c[f+8>>2]=590;UC(0,0,104466,f);Ya()}if((b|0)==70){c[a>>2]=113;c[a+4>>2]=114;c[a+8>>2]=115;c[a+12>>2]=49;c[a+16>>2]=117;c[a+20>>2]=118;c[a+24>>2]=119;c[a+28>>2]=50;c[a+32>>2]=51;c[a+36>>2]=122;c[a+40>>2]=123;c[a+44>>2]=126;c[a+48>>2]=52;c[a+52>>2]=53;c[a+56>>2]=54}else{f=(b|0)==141;d=(b|0)==1448097584|f;if((b|0)<1448097584)switch(b|0){case 141:{g=114;break}default:g=115}else switch(b|0){case 1448097584:{g=114;break}default:g=115}if((g|0)==114){c[a>>2]=30;c[a+4>>2]=31}else if((g|0)==115){c[a>>2]=113;c[a+4>>2]=114}c[a+8>>2]=115;c[a+12>>2]=(b|0)==24?36:116;c[a+16>>2]=117;c[a+20>>2]=118;c[a+24>>2]=119;c[a+28>>2]=d?41:120;c[a+32>>2]=121;d=a+36|0;if((b|0)<1448097584)switch(b|0){case 141:{g=118;break}default:g=117}else switch(b|0){case 1448097584:{g=118;break}default:g=117}if((g|0)==117){c[d>>2]=122;c[a+40>>2]=123}else if((g|0)==118){c[d>>2]=45;c[a+48>>2]=124;c[a+52>>2]=125;c[a+40>>2]=113;c[a+56>>2]=114}if(!f)c[a+44>>2]=126}c[a+60>>2]=113;c[a+64>>2]=114;c[a+68>>2]=115;c[a+72>>2]=116;c[a+76>>2]=117;c[a+80>>2]=118;c[a+84>>2]=119;c[a+88>>2]=120;c[a+92>>2]=121;c[a+96>>2]=122;c[a+100>>2]=123;c[a+104>>2]=124;f=(e|0)<2;e=a+108|0;c[a+116>>2]=f?152:151;c[a+112>>2]=f?154:153;if((b|0)<1448097584)switch(b|0){case 141:{g=126;break}default:g=123}else switch(b|0){case 1448097584:{g=126;break}default:g=123}do if((g|0)==123){d=a+120|0;if(f){c[d>>2]=155;break}else{c[d>>2]=156;break}}else if((g|0)==126)c[a+120>>2]=18;while(0);m:do if((b|0)<141)switch(b|0){case 70:{g=131;break}default:g=128}else if((b|0)<1448097584)switch(b|0){case 141:{g=131;break m}default:{g=128;break m}}else switch(b|0){case 1448097584:{g=131;break m}default:{g=128;break m}}while(0);n:do if((g|0)==128)if(f){c[e>>2]=157;c[a+124>>2]=158;c[a+128>>2]=159;c[a+136>>2]=160;c[a+140>>2]=161;c[a+144>>2]=162;c[a+148>>2]=163;break}else{c[e>>2]=164;c[a+124>>2]=165;c[a+128>>2]=166;c[a+136>>2]=167;c[a+140>>2]=168;c[a+144>>2]=169;c[a+148>>2]=170;break}else if((g|0)==131){c[e>>2]=33;c[a+124>>2]=34;c[a+128>>2]=35;if((b|0)<1448097584)switch(b|0){case 141:break;default:break n}else switch(b|0){case 1448097584:break;default:break n}c[a+136>>2]=171;c[a+140>>2]=172}while(0);c[a+132>>2]=f?174:173;c[a+152>>2]=175;c[a+160>>2]=176;c[a+156>>2]=177;o:do if((b|0)<141)switch(b|0){case 24:{c[a+164>>2]=43;break o}case 70:{c[a+164>>2]=44;break o}default:{g=137;break o}}else{if((b|0)<1448097584)switch(b|0){case 141:break;default:{g=137;break o}}else switch(b|0){case 1448097584:break;default:{g=137;break o}}c[a+164>>2]=45;c[a+180>>2]=178;c[a+184>>2]=179}while(0);if((g|0)==137)c[a+164>>2]=180;c[a+168>>2]=181;c[a+172>>2]=182;c[a+176>>2]=183;c[a+188>>2]=127;c[a+192>>2]=128;c[a+196>>2]=129;c[a+200>>2]=130;c[a+204>>2]=74;c[a+208>>2]=75;c[a+220>>2]=f?126:125;c[a+216>>2]=f?128:127;c[a+232>>2]=129;c[a+228>>2]=130;i=h;return}}}function Xg(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0;i=d[a+(0-e)>>0]|0;h=d[a+(1-e)>>0]|0;g=d[a+(2-e)>>0]|0;f=d[a+(3-e)>>0]|0;b=(i+2+(h<<1)+g|0)>>>2<<8|((d[a+~e>>0]|0)+2+(i<<1)+h|0)>>>2|(h+2+(g<<1)+f|0)>>>2<<16|(g+2+(f<<1)+(d[b>>0]|0)|0)>>>2<<24;c[a>>2]=b;c[a+e>>2]=b;c[a+(e<<1)>>2]=b;c[a+(e*3|0)>>2]=b;return}function Yg(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;j=d[a+-1>>0]|0;i=d[a+(e+-1)>>0]|0;h=e<<1;g=d[a+(h+-1)>>0]|0;b=e*3|0;f=d[a+(b+-1)>>0]|0;c[a>>2]=_(((d[a+~e>>0]|0)+2+(j<<1)+i|0)>>>2,16843009)|0;c[a+e>>2]=_((j+2+(i<<1)+g|0)>>>2,16843009)|0;c[a+h>>2]=_((i+2+(g<<1)+f|0)>>>2,16843009)|0;c[a+b>>2]=_((g+2+f+(f<<1)|0)>>>2,16843009)|0;return}function Zg(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;b=d>>1;d=a+(0-b<<1)|0;e=c[d>>2]|0;d=c[d+4>>2]|0;f=a;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1<<1)|0;c[f>>2]=e;c[f+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=e;c[b+4>>2]=d;return}function _g(a,b,d){a=a|0;b=b|0;d=d|0;var f=0;b=d>>1;d=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;f=a;c[f>>2]=d;c[f+4>>2]=C;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(b<<1)|0;c[d>>2]=f;c[d+4>>2]=C;d=b<<1;f=RF(e[a+(d+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(d<<1)|0;c[d>>2]=f;c[d+4>>2]=C;b=b*3|0;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;b=a+(b<<1)|0;c[b>>2]=d;c[b+4>>2]=C;return}function $g(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function ah(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;i=d[b+(2-e)>>0]|0;c=d[b+(3-e)>>0]|0;f=e<<1;j=d[b+(f+-1)>>0]|0;g=e*3|0;h=d[b+(g+-1)>>0]|0;a[b>>0]=((d[b+(e+-1)>>0]|0)+(d[b+(1-e)>>0]|0)|0)>>>1;i=(j+i|0)>>>1&255;a[b+e>>0]=i;a[b+1>>0]=i;c=(h+c|0)>>>1&255;a[b+(g+3)>>0]=c;a[b+(g+2)>>0]=c;a[b+(f+3)>>0]=c;a[b+(g+1)>>0]=c;a[b+(f+2)>>0]=c;a[b+(e+3)>>0]=c;a[b+g>>0]=c;a[b+(f|1)>>0]=c;a[b+(e+2)>>0]=c;a[b+3>>0]=c;a[b+f>>0]=c;a[b+(e+1)>>0]=c;a[b+2>>0]=c;return}function bh(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=d>>1;l=e[a+(1-i<<1)>>1]|0;g=e[a+(2-i<<1)>>1]|0;k=e[a+(3-i<<1)>>1]|0;j=e[c>>1]|0;h=e[c+2>>1]|0;f=e[c+4>>1]|0;d=e[c+6>>1]|0;b[a>>1]=((e[a+(0-i<<1)>>1]|0)+2+g+(l<<1)|0)>>>2;c=(l+2+k+(g<<1)|0)>>>2&65535;b[a+(i<<1)>>1]=c;b[a+2>>1]=c;c=(g+2+j+(k<<1)|0)>>>2&65535;g=i<<1;b[a+(g<<1)>>1]=c;b[a+(i+1<<1)>>1]=c;b[a+4>>1]=c;k=(k+2+h+(j<<1)|0)>>>2&65535;c=i*3|0;b[a+(c<<1)>>1]=k;b[a+((g|1)<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+f+(h<<1)|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;b[a+(i+3<<1)>>1]=j;h=(h+2+d+(f<<1)|0)>>>2&65535;b[a+(c+2<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(c+3<<1)>>1]=(f+2+(d*3|0)|0)>>>2;return}function ch(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;h=d>>1;m=e[a+(~h<<1)>>1]|0;g=e[a+(0-h<<1)>>1]|0;f=e[a+(1-h<<1)>>1]|0;d=e[a+(2-h<<1)>>1]|0;c=e[a+(3-h<<1)>>1]|0;k=e[a+-2>>1]|0;n=e[a+(h+-1<<1)>>1]|0;j=h<<1;i=e[a+(j+-1<<1)>>1]|0;l=h*3|0;b[a+(l<<1)>>1]=(n+2+(i<<1)+(e[a+(l+-1<<1)>>1]|0)|0)>>>2;i=(k+2+(n<<1)+i|0)>>>2&65535;b[a+(l+1<<1)>>1]=i;b[a+(j<<1)>>1]=i;i=m+2|0;n=(i+(k<<1)+n|0)>>>2&65535;b[a+(l+2<<1)>>1]=n;b[a+((j|1)<<1)>>1]=n;b[a+(h<<1)>>1]=n;k=((m<<1)+2+g+k|0)>>>2&65535;b[a+(l+3<<1)>>1]=k;b[a+(j+2<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;b[a>>1]=k;i=(i+(g<<1)+f|0)>>>2&65535;b[a+(j+3<<1)>>1]=i;b[a+(h+2<<1)>>1]=i;b[a+2>>1]=i;g=(g+2+(f<<1)+d|0)>>>2&65535;b[a+(h+3<<1)>>1]=g;b[a+4>>1]=g;b[a+6>>1]=(f+2+(d<<1)+c|0)>>>2;return}function dh(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;i=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;k=e[a+(3-j<<1)>>1]|0;g=e[a+-2>>1]|0;f=e[a+(j+-1<<1)>>1]|0;h=j<<1;d=e[a+(h+-1<<1)>>1]|0;c=j*3|0;o=(i+1+n|0)>>>1&65535;b[a+((h|1)<<1)>>1]=o;b[a>>1]=o;o=(n+1+m|0)>>>1&65535;b[a+(h+2<<1)>>1]=o;b[a+2>>1]=o;o=(m+1+l|0)>>>1&65535;b[a+(h+3<<1)>>1]=o;b[a+4>>1]=o;b[a+6>>1]=(l+1+k|0)>>>1;o=((i<<1)+2+n+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=o;b[a+(j<<1)>>1]=o;i=i+2|0;o=(i+(n<<1)+m|0)>>>2&65535;b[a+(c+2<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;n=(n+2+(m<<1)+l|0)>>>2&65535;b[a+(c+3<<1)>>1]=n;b[a+(j+2<<1)>>1]=n;b[a+(j+3<<1)>>1]=(m+2+(l<<1)+k|0)>>>2;b[a+(h<<1)>>1]=(i+(g<<1)+f|0)>>>2;b[a+(c<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function eh(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;k=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;i=e[a+-2>>1]|0;g=e[a+(j+-1<<1)>>1]|0;h=j<<1;f=e[a+(h+-1<<1)>>1]|0;c=j*3|0;d=e[a+(c+-1<<1)>>1]|0;o=(k+1+i|0)>>>1&65535;b[a+(j+2<<1)>>1]=o;b[a>>1]=o;o=((k<<1)+2+n+i|0)>>>2&65535;b[a+(j+3<<1)>>1]=o;b[a+2>>1]=o;k=k+2|0;b[a+4>>1]=(k+(n<<1)+m|0)>>>2;b[a+6>>1]=(n+2+(m<<1)+l|0)>>>2;l=(i+1+g|0)>>>1&65535;b[a+(h+2<<1)>>1]=l;b[a+(j<<1)>>1]=l;k=(k+(i<<1)+g|0)>>>2&65535;b[a+(h+3<<1)>>1]=k;b[a+(j+1<<1)>>1]=k;j=(g+1+f|0)>>>1&65535;b[a+(c+2<<1)>>1]=j;b[a+(h<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+3<<1)>>1]=i;b[a+((h|1)<<1)>>1]=i;b[a+(c<<1)>>1]=(f+1+d|0)>>>1;b[a+(c+1<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function fh(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;n=d[b+(0-e)>>0]|0;m=d[b+(1-e)>>0]|0;l=d[b+(2-e)>>0]|0;k=d[b+(3-e)>>0]|0;j=d[c>>0]|0;h=d[c+1>>0]|0;g=d[c+2>>0]|0;f=d[c+3>>0]|0;a[b>>0]=(n+1+m|0)>>>1;c=(m+1+l|0)>>>1&255;i=e<<1;a[b+i>>0]=c;a[b+1>>0]=c;c=(l+1+k|0)>>>1&255;a[b+(i|1)>>0]=c;a[b+2>>0]=c;c=(k+1+j|0)>>>1&255;a[b+(i+2)>>0]=c;a[b+3>>0]=c;a[b+e>>0]=(n+2+(m<<1)+l|0)>>>2;m=(m+2+(l<<1)+k|0)>>>2&255;c=e*3|0;a[b+c>>0]=m;a[b+(e+1)>>0]=m;l=(l+2+(k<<1)+j|0)>>>2&255;a[b+(c+1)>>0]=l;a[b+(e+2)>>0]=l;k=(k+2+(j<<1)+h|0)>>>2&255;a[b+(c+2)>>0]=k;a[b+(e+3)>>0]=k;a[b+(i+3)>>0]=(j+2+(h<<1)+g|0)>>>2;a[b+(c+3)>>0]=(h+2+(g<<1)+f|0)>>>2;return}function gh(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=d>>1;l=e[a+(0-h<<1)>>1]|0;k=e[a+(1-h<<1)>>1]|0;j=e[a+(2-h<<1)>>1]|0;i=e[a+(3-h<<1)>>1]|0;g=e[c>>1]|0;f=e[c+2>>1]|0;d=e[c+4>>1]|0;b[a>>1]=(l+1+k|0)>>>1;m=(k+1+j|0)>>>1&65535;c=h<<1;b[a+(c<<1)>>1]=m;b[a+2>>1]=m;m=(j+1+i|0)>>>1&65535;b[a+((c|1)<<1)>>1]=m;b[a+4>>1]=m;m=(i+1+g|0)>>>1&65535;b[a+(c+2<<1)>>1]=m;b[a+6>>1]=m;b[a+(c+3<<1)>>1]=(g+1+f|0)>>>1;b[a+(h<<1)>>1]=(l+2+(k<<1)+j|0)>>>2;k=(k+2+(j<<1)+i|0)>>>2&65535;c=h*3|0;b[a+(c<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;j=(j+2+(i<<1)+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(h+2<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+2<<1)>>1]=i;b[a+(h+3<<1)>>1]=i;b[a+(c+3<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function hh(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0;g=d>>1;k=e[a+-2>>1]|0;j=e[a+(g+-1<<1)>>1]|0;c=g<<1;i=e[a+(c+-1<<1)>>1]|0;f=g*3|0;d=b[a+(f+-1<<1)>>1]|0;h=d&65535;b[a>>1]=(k+1+j|0)>>>1;b[a+2>>1]=(k+2+(j<<1)+i|0)>>>2;k=(j+1+i|0)>>>1&65535;b[a+(g<<1)>>1]=k;b[a+4>>1]=k;j=(j+2+(i<<1)+h|0)>>>2&65535;b[a+(g+1<<1)>>1]=j;b[a+6>>1]=j;j=(i+1+h|0)>>>1&65535;b[a+(c<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;h=(i+2+h+(h<<1)|0)>>>2&65535;b[a+((c|1)<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(f+3<<1)>>1]=d;b[a+(f+2<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+(f<<1)>>1]=d;b[a+(f+1<<1)>>1]=d;b[a+(c+3<<1)>>1]=d;return}function ih(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=RF(((e[a+-2>>1]|0)+2+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function jh(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0;b=d>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function kh(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;j=70232+(0-(d[b+~e>>0]|0))|0;i=b+(0-e)|0;h=i+1|0;g=i+2|0;f=i+3|0;c=j+(d[b+-1>>0]|0)|0;a[b>>0]=a[c+(d[i>>0]|0)>>0]|0;a[b+1>>0]=a[c+(d[h>>0]|0)>>0]|0;a[b+2>>0]=a[c+(d[g>>0]|0)>>0]|0;a[b+3>>0]=a[c+(d[f>>0]|0)>>0]|0;c=b+e|0;b=j+(d[c+-1>>0]|0)|0;a[c>>0]=a[b+(d[i>>0]|0)>>0]|0;a[c+1>>0]=a[b+(d[h>>0]|0)>>0]|0;a[c+2>>0]=a[b+(d[g>>0]|0)>>0]|0;a[c+3>>0]=a[b+(d[f>>0]|0)>>0]|0;c=c+e|0;b=j+(d[c+-1>>0]|0)|0;a[c>>0]=a[b+(d[i>>0]|0)>>0]|0;a[c+1>>0]=a[b+(d[h>>0]|0)>>0]|0;a[c+2>>0]=a[b+(d[g>>0]|0)>>0]|0;a[c+3>>0]=a[b+(d[f>>0]|0)>>0]|0;c=c+e|0;e=j+(d[c+-1>>0]|0)|0;a[c>>0]=a[e+(d[i>>0]|0)>>0]|0;a[c+1>>0]=a[e+(d[h>>0]|0)>>0]|0;a[c+2>>0]=a[e+(d[g>>0]|0)>>0]|0;a[c+3>>0]=a[e+(d[f>>0]|0)>>0]|0;return}function lh(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b<<1)|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b<<1<<1)|0;c[d>>2]=16711935;c[d+4>>2]=16711935;b=a+(b*3<<1)|0;c[b>>2]=16711935;c[b+4>>2]=16711935;return}function mh(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b<<1)|0;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b<<1<<1)|0;c[d>>2]=16843009;c[d+4>>2]=16843009;b=a+(b*3<<1)|0;c[b>>2]=16843009;c[b+4>>2]=16843009;return}function nh(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b<<1)|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b<<1<<1)|0;c[d>>2]=16777472;c[d+4>>2]=16777472;b=a+(b*3<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function oh(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=d[b+(1-e)>>0]|0;r=d[b+(2-e)>>0]|0;p=d[b+(3-e)>>0]|0;n=d[c>>0]|0;l=d[c+1>>0]|0;i=d[c+2>>0]|0;h=d[c+3>>0]|0;s=d[b+(e+-1)>>0]|0;j=e<<1;q=d[b+(j+-1)>>0]|0;c=e*3|0;o=d[b+(c+-1)>>0]|0;m=d[b+((e<<2)+-1)>>0]|0;k=d[b+((e*5|0)+-1)>>0]|0;g=d[b+((e*6|0)+-1)>>0]|0;f=d[b+((e*7|0)+-1)>>0]|0;a[b>>0]=((d[b+(0-e)>>0]|0)+4+r+(d[b+-1>>0]|0)+q+(s+t<<1)|0)>>>3;s=(t+4+p+s+o+(q+r<<1)|0)>>>3&255;a[b+e>>0]=s;a[b+1>>0]=s;q=(r+4+n+q+m+(o+p<<1)|0)>>>3&255;a[b+j>>0]=q;a[b+(e+1)>>0]=q;a[b+2>>0]=q;o=(p+4+l+o+k+(m+n<<1)|0)>>>3&255;a[b+c>>0]=o;a[b+(j|1)>>0]=o;a[b+(e+2)>>0]=o;a[b+3>>0]=o;m=(n+4+i+m+g+(k+l<<1)|0)>>>3&255;a[b+(c+1)>>0]=m;a[b+(j+2)>>0]=m;a[b+(e+3)>>0]=m;e=(l+4+h+k+f+(g+i<<1)|0)>>>3&255;a[b+(c+2)>>0]=e;a[b+(j+3)>>0]=e;a[b+(c+3)>>0]=(i+2+h+g+f|0)>>>2;return}function ph(b,c,e){b=b|0;c=c|0;e=e|0;qh(b,a[c>>0]|0,a[c+1>>0]|0,a[c+2>>0]|0,e,d[b+(e+-1)>>0]|0,d[b+((e<<1)+-1)>>0]|0,d[b+((e*3|0)+-1)>>0]|0,d[b+((e<<2)+-1)>>0]|0);return}function qh(b,c,e,f,g,h,i,j,k){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0,q=0,r=0;p=d[b+(0-g)>>0]|0;o=d[b+(1-g)>>0]|0;n=d[b+(2-g)>>0]|0;l=d[b+(3-g)>>0]|0;m=c&255;e=e&255;c=o<<1;a[b>>0]=(h+4+j+c+(p+i<<1)|0)>>>3;q=(o+1+n|0)>>>1&255;h=g<<1;a[b+h>>0]=q;a[b+1>>0]=q;q=(n+1+l|0)>>>1&255;a[b+(h|1)>>0]=q;a[b+2>>0]=q;q=m+1|0;r=(q+l|0)>>>1&255;a[b+(h+2)>>0]=r;a[b+3>>0]=r;a[b+(h+3)>>0]=(q+e|0)>>>1;a[b+g>>0]=(i+4+(j<<1)+k+p+c+n|0)>>>3;k=(o+2+(n<<1)+l|0)>>>2&255;c=g*3|0;a[b+c>>0]=k;a[b+(g+1)>>0]=k;k=m+2|0;j=(k+n+(l<<1)|0)>>>2&255;a[b+(c+1)>>0]=j;a[b+(g+2)>>0]=j;j=((m<<1)+2+e+l|0)>>>2&255;a[b+(c+2)>>0]=j;a[b+(g+3)>>0]=j;a[b+(c+3)>>0]=(k+(e<<1)+(f&255)|0)>>>2;return}function rh(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=d[b+-1>>0]|0;q=d[b+(e+-1)>>0]|0;j=e<<1;m=d[b+(j+-1)>>0]|0;f=e*3|0;l=d[b+(f+-1)>>0]|0;i=d[b+((e<<2)+-1)>>0]|0;h=d[b+((e*5|0)+-1)>>0]|0;g=d[b+((e*6|0)+-1)>>0]|0;o=d[b+(2-e)>>0]|0;r=d[b+(3-e)>>0]|0;p=d[c>>0]|0;n=d[c+1>>0]|0;k=d[c+2>>0]|0;c=d[c+3>>0]|0;t=q<<1;s=t+4|0;a[b>>0]=(s+(d[b+(1-e)>>0]|0)+r+(o+u<<1)|0)>>>3;a[b+1>>0]=(u+4+t+m+o+(r<<1)+p|0)>>>3;o=m<<1;r=(s+o+r+(p<<1)+n|0)>>>3&255;a[b+e>>0]=r;a[b+2>>0]=r;p=(q+4+o+l+p+(n<<1)+k|0)>>>3&255;a[b+(e+1)>>0]=p;a[b+3>>0]=p;n=(o+4+n+c+(k+l<<1)|0)>>>3&255;a[b+j>>0]=n;a[b+(e+2)>>0]=n;m=(m+4+k+((c+l|0)*3|0)|0)>>>3&255;a[b+(j|1)>>0]=m;a[b+(e+3)>>0]=m;e=l+2|0;l=(e+(i<<1)+h|0)>>>2&255;a[b+(f+1)>>0]=l;a[b+(j+3)>>0]=l;c=(e+i+k+c|0)>>>2&255;a[b+(j+2)>>0]=c;a[b+f>>0]=c;a[b+(f+2)>>0]=(i+1+h|0)>>>1;a[b+(f+3)>>0]=(i+2+(h<<1)+g|0)>>>2;return}function sh(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=d[b+(1-e)>>0]|0;n=d[b+(2-e)>>0]|0;m=d[b+(3-e)>>0]|0;l=d[c>>0]|0;k=d[c+1>>0]|0;h=d[c+2>>0]|0;g=d[c+3>>0]|0;o=d[b+(e+-1)>>0]|0;i=e<<1;j=d[b+(i+-1)>>0]|0;c=e*3|0;f=d[b+(c+-1)>>0]|0;a[b>>0]=((d[b+(0-e)>>0]|0)+4+n+(d[b+-1>>0]|0)+j+(o+p<<1)|0)>>>3;o=(p+4+m+o+f+(j+n<<1)|0)>>>3&255;a[b+e>>0]=o;a[b+1>>0]=o;j=(n+4+l+(m<<1)+j+(f*3|0)|0)>>>3&255;a[b+i>>0]=j;a[b+(e+1)>>0]=j;a[b+2>>0]=j;j=f<<2;m=(m+4+k+(l<<1)+j|0)>>>3&255;a[b+c>>0]=m;a[b+(i|1)>>0]=m;a[b+(e+2)>>0]=m;a[b+3>>0]=m;l=(l+4+h+(k<<1)+j|0)>>>3&255;a[b+(c+1)>>0]=l;a[b+(i+2)>>0]=l;a[b+(e+3)>>0]=l;e=(k+4+g+(h<<1)+j|0)>>>3&255;a[b+(c+2)>>0]=e;a[b+(i+3)>>0]=e;a[b+(c+3)>>0]=(h+2+g+(f<<1)|0)>>>2;return}function th(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;s=d[b+-1>>0]|0;o=d[b+(e+-1)>>0]|0;h=e<<1;l=d[b+(h+-1)>>0]|0;f=e*3|0;g=a[b+(f+-1)>>0]|0;k=g&255;n=d[b+(2-e)>>0]|0;p=d[b+(3-e)>>0]|0;j=d[c>>0]|0;m=d[c+1>>0]|0;i=d[c+2>>0]|0;c=d[c+3>>0]|0;r=o<<1;q=r+4|0;a[b>>0]=(q+(d[b+(1-e)>>0]|0)+p+(n+s<<1)|0)>>>3;a[b+1>>0]=(s+4+r+l+n+(p<<1)+j|0)>>>3;n=l<<1;p=(q+n+p+(j<<1)+m|0)>>>3&255;a[b+e>>0]=p;a[b+2>>0]=p;j=(o+4+n+k+j+(m<<1)+i|0)>>>3&255;a[b+(e+1)>>0]=j;a[b+3>>0]=j;j=k<<1;m=(n+4+j+m+(i<<1)+c|0)>>>3&255;a[b+h>>0]=m;a[b+(e+2)>>0]=m;k=(l+4+i+((c+k|0)*3|0)|0)>>>3&255;a[b+(h|1)>>0]=k;a[b+(e+3)>>0]=k;a[b+(f+1)>>0]=g;a[b+(h+3)>>0]=g;c=(j+2+i+c|0)>>>2&255;a[b+(h+2)>>0]=c;a[b+f>>0]=c;a[b+(f+3)>>0]=g;a[b+(f+2)>>0]=g;return}function uh(b,c,e){b=b|0;c=c|0;e=e|0;var f=0;f=d[b+((e*3|0)+-1)>>0]|0;qh(b,a[c>>0]|0,a[c+1>>0]|0,a[c+2>>0]|0,e,d[b+(e+-1)>>0]|0,d[b+((e<<1)+-1)>>0]|0,f,f);return}function vh(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;b[a>>1]=((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2;b[a+2>>1]=(q+2+(i<<1)+k|0)>>>2;b[a+4>>1]=(i+2+(k<<1)+l|0)>>>2;b[a+6>>1]=(k+2+(l<<1)+m|0)>>>2;f=a+8|0;b[f>>1]=(l+2+(m<<1)+n|0)>>>2;b[a+10>>1]=(m+2+(n<<1)+o|0)>>>2;b[a+12>>1]=(n+2+(o<<1)+p|0)>>>2;b[a+14>>1]=((g&65535)+2+(p<<1)+o|0)>>>2;p=a;o=c[p>>2]|0;p=c[p+4>>2]|0;q=c[f>>2]|0;f=c[f+4>>2]|0;d=a+(h<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*3<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<2<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*5<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*6<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*7<<1)|0;a=d;c[a>>2]=o;c[a+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function wh(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;p=f>>1;u=a+-2|0;s=e[u>>1]|0;q=e[a+(p+-1<<1)>>1]|0;r=p<<1;o=e[a+(r+-1<<1)>>1]|0;l=p*3|0;m=e[a+(l+-1<<1)>>1]|0;n=p<<2;k=e[a+(n+-1<<1)>>1]|0;h=p*5|0;i=e[a+(h+-1<<1)>>1]|0;j=p*6|0;g=e[a+(j+-1<<1)>>1]|0;d=p*7|0;f=e[a+(d+-1<<1)>>1]|0;u=RF(((e[((b|0)==0?u:a+(~p<<1)|0)>>1]|0)+2+(s<<1)+q|0)>>>2|0,0,65537,65537)|0;t=C;b=a;c[b>>2]=u;c[b+4>>2]=t;b=a+8|0;c[b>>2]=u;c[b+4>>2]=t;s=RF((s+2+(q<<1)+o|0)>>>2|0,0,65537,65537)|0;b=C;p=a+(p<<1)|0;t=p;c[t>>2]=s;c[t+4>>2]=b;p=p+8|0;c[p>>2]=s;c[p+4>>2]=b;q=RF((q+2+(o<<1)+m|0)>>>2|0,0,65537,65537)|0;p=C;b=a+(r<<1)|0;r=b;c[r>>2]=q;c[r+4>>2]=p;b=b+8|0;c[b>>2]=q;c[b+4>>2]=p;o=RF((o+2+(m<<1)+k|0)>>>2|0,0,65537,65537)|0;b=C;l=a+(l<<1)|0;p=l;c[p>>2]=o;c[p+4>>2]=b;l=l+8|0;c[l>>2]=o;c[l+4>>2]=b;m=RF((m+2+(k<<1)+i|0)>>>2|0,0,65537,65537)|0;l=C;b=a+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;k=RF((k+2+(i<<1)+g|0)>>>2|0,0,65537,65537)|0;b=C;h=a+(h<<1)|0;l=h;c[l>>2]=k;c[l+4>>2]=b;h=h+8|0;c[h>>2]=k;c[h+4>>2]=b;i=RF((i+2+(g<<1)+f|0)>>>2|0,0,65537,65537)|0;h=C;b=a+(j<<1)|0;j=b;c[j>>2]=i;c[j+4>>2]=h;b=b+8|0;c[b>>2]=i;c[b+4>>2]=h;f=RF((g+2+(f*3|0)|0)>>>2|0,0,65537,65537)|0;b=C;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function xh(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;A=g>>1;d=(d|0)!=0;n=~A;q=a+-2|0;u=e[q>>1]|0;j=e[a+(A+-1<<1)>>1]|0;o=e[a+((A<<1)+-1<<1)>>1]|0;p=e[a+((A*3|0)+-1<<1)>>1]|0;r=e[a+((A<<2)+-1<<1)>>1]|0;s=e[a+((A*5|0)+-1<<1)>>1]|0;t=e[a+((A*6|0)+-1<<1)>>1]|0;v=e[a+((A*7|0)+-1<<1)>>1]|0;w=0-A|0;x=e[a+(w<<1)>>1]|0;y=e[a+(1-A<<1)>>1]|0;z=e[a+(2-A<<1)>>1]|0;h=e[a+(3-A<<1)>>1]|0;i=e[a+(4-A<<1)>>1]|0;k=e[a+(5-A<<1)>>1]|0;l=e[a+(6-A<<1)>>1]|0;g=b[a+(7-A<<1)>>1]|0;m=g&65535;if(f)g=b[a+(8-A<<1)>>1]|0;z=(((e[(d?a+(n<<1)|0:q)>>1]|0)+2+(u<<1)+j|0)>>>2)+8+((u+2+(j<<1)+o|0)>>>2)+((j+2+(o<<1)+p|0)>>>2)+((o+2+(p<<1)+r|0)>>>2)+((p+2+(r<<1)+s|0)>>>2)+((r+2+(s<<1)+t|0)>>>2)+((s+2+(t<<1)+v|0)>>>2)+((t+2+(v*3|0)|0)>>>2)+(((e[a+((d?n:w)<<1)>>1]|0)+2+(x<<1)+y|0)>>>2)+((x+2+(y<<1)+z|0)>>>2)+((y+2+(z<<1)+h|0)>>>2)+((z+2+(h<<1)+i|0)>>>2)+((h+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+(((g&65535)+2+(m<<1)+l|0)>>>2)>>4;z=RF(z|0,((z|0)<0)<<31>>31|0,65537,65537)|0;f=C;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;A=a;c[A>>2]=z;c[A+4>>2]=f;a=a+8|0;c[a>>2]=z;c[a+4>>2]=f;return}function yh(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;o=f>>1;q=0-o|0;x=e[a+(q<<1)>>1]|0;p=e[a+(1-o<<1)>>1]|0;t=e[a+(2-o<<1)>>1]|0;r=(x+2+(p<<1)+t|0)>>>2;u=e[a+(3-o<<1)>>1]|0;s=(p+2+(t<<1)+u|0)>>>2;w=e[a+(4-o<<1)>>1]|0;t=(t+2+(u<<1)+w|0)>>>2;y=e[a+(5-o<<1)>>1]|0;u=(u+2+(w<<1)+y|0)>>>2;v=e[a+(6-o<<1)>>1]|0;w=(w+2+(y<<1)+v|0)>>>2;f=b[a+(7-o<<1)>>1]|0;n=f&65535;y=(y+2+(v<<1)+n|0)>>>2;if(!d){d=(f&65535)+2+(n<<1)|0;g=n;h=n;i=n;j=n;k=n;l=n;m=n;f=n}else{A=e[a+(8-o<<1)>>1]|0;z=e[a+(9-o<<1)>>1]|0;f=e[a+(10-o<<1)>>1]|0;i=e[a+(11-o<<1)>>1]|0;j=e[a+(12-o<<1)>>1]|0;k=e[a+(13-o<<1)>>1]|0;m=e[a+(14-o<<1)>>1]|0;l=e[a+(15-o<<1)>>1]|0;d=(e[a+(8-o<<1)>>1]|0)+2+(n<<1)|0;g=(z+2+(f<<1)+i|0)>>>2;h=(f+2+(i<<1)+j|0)>>>2;i=(i+2+(j<<1)+k|0)>>>2;j=(j+2+(k<<1)+m|0)>>>2;k=(k+2+(m<<1)+l|0)>>>2;l=(m+2+(l*3|0)|0)>>>2;m=(n+2+(A<<1)+z|0)>>>2;f=(A+2+(z<<1)+f|0)>>>2}z=(d+v|0)>>>2;b[a>>1]=((((e[a+(((c|0)==0?q:~o)<<1)>>1]|0)+2+(x<<1)+p|0)>>>2)+2+(r<<1)+s|0)>>>2;A=(r+2+(s<<1)+t|0)>>>2&65535;b[a+2>>1]=A;b[a+(o<<1)>>1]=A;A=(s+2+(t<<1)+u|0)>>>2&65535;b[a+4>>1]=A;b[a+(o+1<<1)>>1]=A;s=o<<1;b[a+(s<<1)>>1]=A;A=(t+2+(u<<1)+w|0)>>>2&65535;b[a+6>>1]=A;b[a+(o+2<<1)>>1]=A;b[a+((s|1)<<1)>>1]=A;t=o*3|0;b[a+(t<<1)>>1]=A;A=(u+2+(w<<1)+y|0)>>>2&65535;b[a+8>>1]=A;b[a+(o+3<<1)>>1]=A;b[a+(s+2<<1)>>1]=A;b[a+(t+1<<1)>>1]=A;v=o<<2;b[a+(v<<1)>>1]=A;A=(w+2+(y<<1)+z|0)>>>2&65535;b[a+10>>1]=A;b[a+(o+4<<1)>>1]=A;b[a+(s+3<<1)>>1]=A;b[a+(t+2<<1)>>1]=A;b[a+((v|1)<<1)>>1]=A;x=o*5|0;b[a+(x<<1)>>1]=A;A=(y+2+(z<<1)+m|0)>>>2&65535;b[a+12>>1]=A;b[a+(o+5<<1)>>1]=A;b[a+(s+4<<1)>>1]=A;b[a+(t+3<<1)>>1]=A;b[a+((v|2)<<1)>>1]=A;b[a+(x+1<<1)>>1]=A;c=o*6|0;b[a+(c<<1)>>1]=A;z=(z+2+(m<<1)+f|0)>>>2&65535;b[a+14>>1]=z;b[a+(o+6<<1)>>1]=z;b[a+(s+5<<1)>>1]=z;b[a+(t+4<<1)>>1]=z;b[a+((v|3)<<1)>>1]=z;b[a+(x+2<<1)>>1]=z;b[a+((c|1)<<1)>>1]=z;A=o*7|0;b[a+(A<<1)>>1]=z;z=(m+2+(f<<1)+g|0)>>>2&65535;b[a+(o+7<<1)>>1]=z;b[a+(s+6<<1)>>1]=z;b[a+(t+5<<1)>>1]=z;b[a+(v+4<<1)>>1]=z;b[a+(x+3<<1)>>1]=z;b[a+(c+2<<1)>>1]=z;b[a+(A+1<<1)>>1]=z;z=(f+2+(g<<1)+h|0)>>>2&65535;b[a+(s+7<<1)>>1]=z;b[a+(t+6<<1)>>1]=z;b[a+(v+5<<1)>>1]=z;b[a+(x+4<<1)>>1]=z;b[a+(c+3<<1)>>1]=z;b[a+(A+2<<1)>>1]=z;z=(g+2+(h<<1)+i|0)>>>2&65535;b[a+(t+7<<1)>>1]=z;b[a+(v+6<<1)>>1]=z;b[a+(x+5<<1)>>1]=z;b[a+(c+4<<1)>>1]=z;b[a+(A+3<<1)>>1]=z;z=(h+2+(i<<1)+j|0)>>>2&65535;b[a+(v+7<<1)>>1]=z;b[a+(x+6<<1)>>1]=z;b[a+(c+5<<1)>>1]=z;b[a+(A+4<<1)>>1]=z;z=(i+2+(j<<1)+k|0)>>>2&65535;b[a+(x+7<<1)>>1]=z;b[a+(c+6<<1)>>1]=z;b[a+(A+5<<1)>>1]=z;z=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=z;b[a+(A+6<<1)>>1]=z;b[a+(A+7<<1)>>1]=(k+2+(l*3|0)|0)>>>2;return}function zh(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;E=a+(i<<1)|0;v=a+-2|0;F=e[v>>1]|0;x=e[a+(r+-1<<1)>>1]|0;v=((e[(c?E:v)>>1]|0)+2+(F<<1)+x|0)>>>2;i=r<<1;y=e[a+(i+-1<<1)>>1]|0;F=F+2|0;d=(F+(x<<1)+y|0)>>>2;c=r*3|0;z=e[a+(c+-1<<1)>>1]|0;x=(x+2+(y<<1)+z|0)>>>2;s=r<<2;A=e[a+(s+-1<<1)>>1]|0;y=(y+2+(z<<1)+A|0)>>>2;t=r*5|0;B=e[a+(t+-1<<1)>>1]|0;z=(z+2+(A<<1)+B|0)>>>2;u=r*6|0;D=e[a+(u+-1<<1)>>1]|0;A=(A+2+(B<<1)+D|0)>>>2;w=r*7|0;C=e[a+(w+-1<<1)>>1]|0;B=(B+2+(D<<1)+C|0)>>>2;p=(F+((e[E>>1]|0)<<1)+p|0)>>>2;b[a+(w<<1)>>1]=(A+2+((D+2+(C*3|0)|0)>>>2)+(B<<1)|0)>>>2;B=(z+2+(A<<1)+B|0)>>>2&65535;b[a+(w+1<<1)>>1]=B;b[a+(u<<1)>>1]=B;A=(y+2+(z<<1)+A|0)>>>2&65535;b[a+(w+2<<1)>>1]=A;b[a+((u|1)<<1)>>1]=A;b[a+(t<<1)>>1]=A;z=(x+2+(y<<1)+z|0)>>>2&65535;b[a+(w+3<<1)>>1]=z;b[a+(u+2<<1)>>1]=z;b[a+(t+1<<1)>>1]=z;b[a+(s<<1)>>1]=z;y=(d+2+(x<<1)+y|0)>>>2&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+3<<1)>>1]=y;b[a+(t+2<<1)>>1]=y;b[a+((s|1)<<1)>>1]=y;b[a+(c<<1)>>1]=y;x=(v+2+(d<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(t+3<<1)>>1]=x;b[a+((s|2)<<1)>>1]=x;b[a+(c+1<<1)>>1]=x;b[a+(i<<1)>>1]=x;d=((v<<1)+2+d+p|0)>>>2&65535;b[a+(w+6<<1)>>1]=d;b[a+(u+5<<1)>>1]=d;b[a+(t+4<<1)>>1]=d;b[a+((s|3)<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+((i|1)<<1)>>1]=d;b[a+(r<<1)>>1]=d;d=g+2|0;v=(d+v+(p<<1)|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+6<<1)>>1]=v;b[a+(t+5<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+(c+3<<1)>>1]=v;b[a+(i+2<<1)>>1]=v;b[a+(r+1<<1)>>1]=v;b[a>>1]=v;p=((g<<1)+2+h+p|0)>>>2&65535;b[a+(u+7<<1)>>1]=p;b[a+(t+6<<1)>>1]=p;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(i+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;b[a+2>>1]=p;d=(d+(h<<1)+j|0)>>>2&65535;b[a+(t+7<<1)>>1]=d;b[a+(s+6<<1)>>1]=d;b[a+(c+5<<1)>>1]=d;b[a+(i+4<<1)>>1]=d;b[a+(r+3<<1)>>1]=d;b[a+4>>1]=d;d=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=d;b[a+(c+6<<1)>>1]=d;b[a+(i+5<<1)>>1]=d;b[a+(r+4<<1)>>1]=d;b[a+6>>1]=d;d=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=d;b[a+(i+6<<1)>>1]=d;b[a+(r+5<<1)>>1]=d;b[a+8>>1]=d;d=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(i+7<<1)>>1]=d;b[a+(r+6<<1)>>1]=d;b[a+10>>1]=d;d=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(r+7<<1)>>1]=d;b[a+12>>1]=d;b[a+14>>1]=(n+2+(q<<1)+(((f&65535)+2+(o<<1)+m|0)>>>2)|0)>>>2;return}function Ah(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;d=((f&65535)+2+(o<<1)+m|0)>>>2;u=a+(i<<1)|0;t=a+-2|0;B=e[t>>1]|0;v=e[a+(r+-1<<1)>>1]|0;t=((e[(c?u:t)>>1]|0)+2+(B<<1)+v|0)>>>2;o=r<<1;y=e[a+(o+-1<<1)>>1]|0;B=B+2|0;w=(B+(v<<1)+y|0)>>>2;m=r*3|0;x=e[a+(m+-1<<1)>>1]|0;v=(v+2+(y<<1)+x|0)>>>2;i=r<<2;A=e[a+(i+-1<<1)>>1]|0;y=(y+2+(x<<1)+A|0)>>>2;c=r*5|0;C=e[a+(c+-1<<1)>>1]|0;x=(x+2+(A<<1)+C|0)>>>2;f=r*6|0;z=e[a+(f+-1<<1)>>1]|0;A=(A+2+(C<<1)+z|0)>>>2;s=r*7|0;z=(C+2+(z<<1)+(e[a+(s+-1<<1)>>1]|0)|0)>>>2;u=(B+((e[u>>1]|0)<<1)+p|0)>>>2;b[a+(f<<1)>>1]=(y+2+(x<<1)+A|0)>>>2;b[a+(s<<1)>>1]=(x+2+(A<<1)+z|0)>>>2;p=(w+2+(v<<1)+y|0)>>>2&65535;b[a+((f|1)<<1)>>1]=p;b[a+(i<<1)>>1]=p;p=(v+2+(y<<1)+x|0)>>>2&65535;b[a+(s+1<<1)>>1]=p;b[a+(c<<1)>>1]=p;p=((t<<1)+2+w+u|0)>>>2&65535;b[a+(f+2<<1)>>1]=p;b[a+((i|1)<<1)>>1]=p;b[a+(o<<1)>>1]=p;p=(t+2+(w<<1)+v|0)>>>2&65535;b[a+(s+2<<1)>>1]=p;b[a+(c+1<<1)>>1]=p;b[a+(m<<1)>>1]=p;p=g+2|0;t=(p+t+(u<<1)|0)>>>2&65535;b[a+(s+3<<1)>>1]=t;b[a+(c+2<<1)>>1]=t;b[a+(m+1<<1)>>1]=t;b[a+(r<<1)>>1]=t;t=g+1|0;v=(t+u|0)>>>1&65535;b[a+(f+3<<1)>>1]=v;b[a+((i|2)<<1)>>1]=v;b[a+((o|1)<<1)>>1]=v;b[a>>1]=v;g=((g<<1)+2+h+u|0)>>>2&65535;b[a+(s+4<<1)>>1]=g;b[a+(c+3<<1)>>1]=g;b[a+(m+2<<1)>>1]=g;b[a+(r+1<<1)>>1]=g;g=(t+h|0)>>>1&65535;b[a+(f+4<<1)>>1]=g;b[a+((i|3)<<1)>>1]=g;b[a+(o+2<<1)>>1]=g;b[a+2>>1]=g;p=(p+(h<<1)+j|0)>>>2&65535;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(m+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;p=(h+1+j|0)>>>1&65535;b[a+(f+5<<1)>>1]=p;b[a+(i+4<<1)>>1]=p;b[a+(o+3<<1)>>1]=p;b[a+4>>1]=p;p=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+6<<1)>>1]=p;b[a+(c+5<<1)>>1]=p;b[a+(m+4<<1)>>1]=p;b[a+(r+3<<1)>>1]=p;p=(j+1+k|0)>>>1&65535;b[a+(f+6<<1)>>1]=p;b[a+(i+5<<1)>>1]=p;b[a+(o+4<<1)>>1]=p;b[a+6>>1]=p;p=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(s+7<<1)>>1]=p;b[a+(c+6<<1)>>1]=p;b[a+(m+5<<1)>>1]=p;b[a+(r+4<<1)>>1]=p;p=(k+1+l|0)>>>1&65535;b[a+(f+7<<1)>>1]=p;b[a+(i+6<<1)>>1]=p;b[a+(o+5<<1)>>1]=p;b[a+8>>1]=p;p=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(c+7<<1)>>1]=p;b[a+(m+6<<1)>>1]=p;b[a+(r+5<<1)>>1]=p;p=(l+1+n|0)>>>1&65535;b[a+(i+7<<1)>>1]=p;b[a+(o+6<<1)>>1]=p;b[a+10>>1]=p;p=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(m+7<<1)>>1]=p;b[a+(r+6<<1)>>1]=p;p=(n+1+q|0)>>>1&65535;b[a+(o+7<<1)>>1]=p;b[a+12>>1]=p;b[a+(r+7<<1)>>1]=(n+2+(q<<1)+d|0)>>>2;b[a+14>>1]=(q+1+d|0)>>>1;return}function Bh(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;i=f>>1;l=(c|0)!=0;n=0-i|0;m=~i;B=e[a+(n<<1)>>1]|0;h=e[a+(1-i<<1)>>1]|0;n=((e[a+((l?m:n)<<1)>>1]|0)+2+(B<<1)+h|0)>>>2;g=e[a+(2-i<<1)>>1]|0;B=B+2|0;j=(B+(h<<1)+g|0)>>>2;f=e[a+(3-i<<1)>>1]|0;h=(h+2+(g<<1)+f|0)>>>2;c=e[a+(4-i<<1)>>1]|0;g=(g+2+(f<<1)+c|0)>>>2;o=e[a+(5-i<<1)>>1]|0;f=(f+2+(c<<1)+o|0)>>>2;d=e[a+(6-i<<1)>>1]|0;c=(c+2+(o<<1)+d|0)>>>2;d=(o+2+(d<<1)+(e[a+(7-i<<1)>>1]|0)|0)>>>2;m=a+(m<<1)|0;o=a+-2|0;A=e[o>>1]|0;k=e[a+(i+-1<<1)>>1]|0;o=((e[(l?m:o)>>1]|0)+2+(A<<1)+k|0)>>>2;l=i<<1;t=e[a+(l+-1<<1)>>1]|0;q=(A+2+(k<<1)+t|0)>>>2;p=i*3|0;v=e[a+(p+-1<<1)>>1]|0;k=(k+2+(t<<1)+v|0)>>>2;r=i<<2;x=e[a+(r+-1<<1)>>1]|0;t=(t+2+(v<<1)+x|0)>>>2;s=i*5|0;y=e[a+(s+-1<<1)>>1]|0;v=(v+2+(x<<1)+y|0)>>>2;u=i*6|0;C=e[a+(u+-1<<1)>>1]|0;x=(x+2+(y<<1)+C|0)>>>2;w=i*7|0;z=e[a+(w+-1<<1)>>1]|0;y=(y+2+(C<<1)+z|0)>>>2;z=(C+2+(z*3|0)|0)>>>2;m=(B+A+((e[m>>1]|0)<<1)|0)>>>2;b[a+(w<<1)>>1]=(z+1+y|0)>>>1;b[a+(w+1<<1)>>1]=(x+2+z+(y<<1)|0)>>>2;z=(x+1+y|0)>>>1&65535;b[a+(w+2<<1)>>1]=z;b[a+(u<<1)>>1]=z;y=(v+2+(x<<1)+y|0)>>>2&65535;b[a+(w+3<<1)>>1]=y;b[a+((u|1)<<1)>>1]=y;y=(v+1+x|0)>>>1&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+2<<1)>>1]=y;b[a+(s<<1)>>1]=y;x=(t+2+(v<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+3<<1)>>1]=x;b[a+(s+1<<1)>>1]=x;x=(t+1+v|0)>>>1&65535;b[a+(w+6<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(s+2<<1)>>1]=x;b[a+(r<<1)>>1]=x;v=(k+2+(t<<1)+v|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+5<<1)>>1]=v;b[a+(s+3<<1)>>1]=v;b[a+((r|1)<<1)>>1]=v;v=(k+1+t|0)>>>1&65535;b[a+(u+6<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+((r|2)<<1)>>1]=v;b[a+(p<<1)>>1]=v;t=(q+2+(k<<1)+t|0)>>>2&65535;b[a+(u+7<<1)>>1]=t;b[a+(s+5<<1)>>1]=t;b[a+((r|3)<<1)>>1]=t;b[a+(p+1<<1)>>1]=t;t=(q+1+k|0)>>>1&65535;b[a+(s+6<<1)>>1]=t;b[a+(r+4<<1)>>1]=t;b[a+(p+2<<1)>>1]=t;b[a+(l<<1)>>1]=t;k=(o+2+(q<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=k;b[a+(r+5<<1)>>1]=k;b[a+(p+3<<1)>>1]=k;b[a+((l|1)<<1)>>1]=k;k=o+1|0;s=(k+q|0)>>>1&65535;b[a+(r+6<<1)>>1]=s;b[a+(p+4<<1)>>1]=s;b[a+(l+2<<1)>>1]=s;b[a+(i<<1)>>1]=s;q=((o<<1)+2+q+m|0)>>>2&65535;b[a+(r+7<<1)>>1]=q;b[a+(p+5<<1)>>1]=q;b[a+(l+3<<1)>>1]=q;b[a+(i+1<<1)>>1]=q;k=(k+m|0)>>>1&65535;b[a+(p+6<<1)>>1]=k;b[a+(l+4<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a>>1]=k;k=n+2|0;o=(k+o+(m<<1)|0)>>>2&65535;b[a+(p+7<<1)>>1]=o;b[a+(l+5<<1)>>1]=o;b[a+(i+3<<1)>>1]=o;b[a+2>>1]=o;m=((n<<1)+2+j+m|0)>>>2&65535;b[a+(l+6<<1)>>1]=m;b[a+(i+4<<1)>>1]=m;b[a+4>>1]=m;k=(k+(j<<1)+h|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(i+5<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+(h<<1)+g|0)>>>2&65535;b[a+(i+6<<1)>>1]=j;b[a+8>>1]=j;h=(h+2+(g<<1)+f|0)>>>2&65535;b[a+(i+7<<1)>>1]=h;b[a+10>>1]=h;b[a+12>>1]=(g+2+(f<<1)+c|0)>>>2;b[a+14>>1]=(f+2+(c<<1)+d|0)>>>2;return}function Ch(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;s=f>>1;k=0-s|0;l=e[a+(k<<1)>>1]|0;m=e[a+(1-s<<1)>>1]|0;k=((e[a+(((c|0)==0?k:~s)<<1)>>1]|0)+2+(l<<1)+m|0)>>>2;n=e[a+(2-s<<1)>>1]|0;l=(l+2+(m<<1)+n|0)>>>2;o=e[a+(3-s<<1)>>1]|0;m=(m+2+(n<<1)+o|0)>>>2;q=e[a+(4-s<<1)>>1]|0;n=(n+2+(o<<1)+q|0)>>>2;r=e[a+(5-s<<1)>>1]|0;o=(o+2+(q<<1)+r|0)>>>2;p=e[a+(6-s<<1)>>1]|0;q=(q+2+(r<<1)+p|0)>>>2;f=b[a+(7-s<<1)>>1]|0;j=f&65535;r=(r+2+(p<<1)+j|0)>>>2;if(!d){c=(f&65535)+2+(j<<1)|0;d=j;g=j;h=j;i=j;f=j}else{u=e[a+(8-s<<1)>>1]|0;t=e[a+(9-s<<1)>>1]|0;f=e[a+(10-s<<1)>>1]|0;i=e[a+(11-s<<1)>>1]|0;h=e[a+(12-s<<1)>>1]|0;c=(e[a+(8-s<<1)>>1]|0)+2+(j<<1)|0;d=(t+2+(f<<1)+i|0)>>>2;g=(f+2+(i<<1)+h|0)>>>2;h=(i+2+(h<<1)+(e[a+(13-s<<1)>>1]|0)|0)>>>2;i=(j+2+(u<<1)+t|0)>>>2;f=(u+2+(t<<1)+f|0)>>>2}c=(c+p|0)>>>2;b[a>>1]=(k+1+l|0)>>>1;b[a+(s<<1)>>1]=(k+2+(l<<1)+m|0)>>>2;u=(l+1+m|0)>>>1&65535;b[a+2>>1]=u;j=s<<1;b[a+(j<<1)>>1]=u;u=(l+2+(m<<1)+n|0)>>>2&65535;b[a+(s+1<<1)>>1]=u;k=s*3|0;b[a+(k<<1)>>1]=u;u=(m+1+n|0)>>>1&65535;b[a+4>>1]=u;b[a+((j|1)<<1)>>1]=u;l=s<<2;b[a+(l<<1)>>1]=u;u=(m+2+(n<<1)+o|0)>>>2&65535;b[a+(s+2<<1)>>1]=u;b[a+(k+1<<1)>>1]=u;p=s*5|0;b[a+(p<<1)>>1]=u;u=(n+1+o|0)>>>1&65535;b[a+6>>1]=u;b[a+(j+2<<1)>>1]=u;b[a+((l|1)<<1)>>1]=u;t=s*6|0;b[a+(t<<1)>>1]=u;n=(n+2+(o<<1)+q|0)>>>2&65535;b[a+(s+3<<1)>>1]=n;b[a+(k+2<<1)>>1]=n;b[a+(p+1<<1)>>1]=n;u=s*7|0;b[a+(u<<1)>>1]=n;n=(o+1+q|0)>>>1&65535;b[a+8>>1]=n;b[a+(j+3<<1)>>1]=n;b[a+((l|2)<<1)>>1]=n;b[a+((t|1)<<1)>>1]=n;o=(o+2+(q<<1)+r|0)>>>2&65535;b[a+(s+4<<1)>>1]=o;b[a+(k+3<<1)>>1]=o;b[a+(p+2<<1)>>1]=o;b[a+(u+1<<1)>>1]=o;o=(q+1+r|0)>>>1&65535;b[a+10>>1]=o;b[a+(j+4<<1)>>1]=o;b[a+((l|3)<<1)>>1]=o;b[a+(t+2<<1)>>1]=o;q=(q+2+(r<<1)+c|0)>>>2&65535;b[a+(s+5<<1)>>1]=q;b[a+(k+4<<1)>>1]=q;b[a+(p+3<<1)>>1]=q;b[a+(u+2<<1)>>1]=q;q=(r+1+c|0)>>>1&65535;b[a+12>>1]=q;b[a+(j+5<<1)>>1]=q;b[a+(l+4<<1)>>1]=q;b[a+(t+3<<1)>>1]=q;r=(r+2+(c<<1)+i|0)>>>2&65535;b[a+(s+6<<1)>>1]=r;b[a+(k+5<<1)>>1]=r;b[a+(p+4<<1)>>1]=r;b[a+(u+3<<1)>>1]=r;r=(c+1+i|0)>>>1&65535;b[a+14>>1]=r;b[a+(j+6<<1)>>1]=r;b[a+(l+5<<1)>>1]=r;b[a+(t+4<<1)>>1]=r;r=(c+2+(i<<1)+f|0)>>>2&65535;b[a+(s+7<<1)>>1]=r;b[a+(k+6<<1)>>1]=r;b[a+(p+5<<1)>>1]=r;b[a+(u+4<<1)>>1]=r;s=(i+1+f|0)>>>1&65535;b[a+(j+7<<1)>>1]=s;b[a+(l+6<<1)>>1]=s;b[a+(t+5<<1)>>1]=s;s=(i+2+(f<<1)+d|0)>>>2&65535;b[a+(k+7<<1)>>1]=s;b[a+(p+6<<1)>>1]=s;b[a+(u+5<<1)>>1]=s;s=(f+1+d|0)>>>1&65535;b[a+(l+7<<1)>>1]=s;b[a+(t+6<<1)>>1]=s;s=(f+2+(d<<1)+g|0)>>>2&65535;b[a+(p+7<<1)>>1]=s;b[a+(u+6<<1)>>1]=s;b[a+(t+7<<1)>>1]=(d+1+g|0)>>>1;b[a+(u+7<<1)>>1]=(d+2+(g<<1)+h|0)>>>2;return}function Dh(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;n=f>>1;r=a+-2|0;q=e[r>>1]|0;p=e[a+(n+-1<<1)>>1]|0;r=((e[((c|0)==0?r:a+(~n<<1)|0)>>1]|0)+2+(q<<1)+p|0)>>>2;l=n<<1;o=e[a+(l+-1<<1)>>1]|0;q=(q+2+(p<<1)+o|0)>>>2;j=n*3|0;m=e[a+(j+-1<<1)>>1]|0;p=(p+2+(o<<1)+m|0)>>>2;h=n<<2;k=e[a+(h+-1<<1)>>1]|0;o=(o+2+(m<<1)+k|0)>>>2;g=n*5|0;i=e[a+(g+-1<<1)>>1]|0;m=(m+2+(k<<1)+i|0)>>>2;f=n*6|0;s=e[a+(f+-1<<1)>>1]|0;k=(k+2+(i<<1)+s|0)>>>2;d=n*7|0;c=e[a+(d+-1<<1)>>1]|0;i=(i+2+(s<<1)+c|0)>>>2;c=(s+2+(c*3|0)|0)>>>2;b[a>>1]=(r+1+q|0)>>>1;b[a+2>>1]=(r+2+(q<<1)+p|0)>>>2;r=(q+1+p|0)>>>1&65535;b[a+4>>1]=r;b[a+(n<<1)>>1]=r;q=(q+2+(p<<1)+o|0)>>>2&65535;b[a+6>>1]=q;b[a+(n+1<<1)>>1]=q;q=(p+1+o|0)>>>1&65535;b[a+8>>1]=q;b[a+(n+2<<1)>>1]=q;b[a+(l<<1)>>1]=q;p=(p+2+(o<<1)+m|0)>>>2&65535;b[a+10>>1]=p;b[a+(n+3<<1)>>1]=p;b[a+((l|1)<<1)>>1]=p;p=(o+1+m|0)>>>1&65535;b[a+12>>1]=p;b[a+(n+4<<1)>>1]=p;b[a+(l+2<<1)>>1]=p;b[a+(j<<1)>>1]=p;o=(o+2+(m<<1)+k|0)>>>2&65535;b[a+14>>1]=o;b[a+(n+5<<1)>>1]=o;b[a+(l+3<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;o=(m+1+k|0)>>>1&65535;b[a+(n+6<<1)>>1]=o;b[a+(l+4<<1)>>1]=o;b[a+(j+2<<1)>>1]=o;b[a+(h<<1)>>1]=o;m=(m+2+(k<<1)+i|0)>>>2&65535;b[a+(n+7<<1)>>1]=m;b[a+(l+5<<1)>>1]=m;b[a+(j+3<<1)>>1]=m;b[a+((h|1)<<1)>>1]=m;m=(k+1+i|0)>>>1&65535;b[a+(l+6<<1)>>1]=m;b[a+(j+4<<1)>>1]=m;b[a+((h|2)<<1)>>1]=m;b[a+(g<<1)>>1]=m;k=(k+2+c+(i<<1)|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(j+5<<1)>>1]=k;b[a+((h|3)<<1)>>1]=k;b[a+(g+1<<1)>>1]=k;k=(c+1+i|0)>>>1&65535;b[a+(j+6<<1)>>1]=k;b[a+(h+4<<1)>>1]=k;b[a+(g+2<<1)>>1]=k;b[a+(f<<1)>>1]=k;i=(i+2+(c*3|0)|0)>>>2&65535;b[a+(j+7<<1)>>1]=i;b[a+(h+5<<1)>>1]=i;b[a+(g+3<<1)>>1]=i;b[a+((f|1)<<1)>>1]=i;c=c&65535;b[a+(d+7<<1)>>1]=c;b[a+(f+7<<1)>>1]=c;b[a+(g+7<<1)>>1]=c;b[a+(h+7<<1)>>1]=c;b[a+(d+6<<1)>>1]=c;b[a+(f+6<<1)>>1]=c;b[a+(g+6<<1)>>1]=c;b[a+(h+6<<1)>>1]=c;b[a+(d+5<<1)>>1]=c;b[a+(f+5<<1)>>1]=c;b[a+(g+5<<1)>>1]=c;b[a+(d+4<<1)>>1]=c;b[a+(f+4<<1)>>1]=c;b[a+(g+4<<1)>>1]=c;b[a+(d+3<<1)>>1]=c;b[a+(f+3<<1)>>1]=c;b[a+(d+2<<1)>>1]=c;b[a+(f+2<<1)>>1]=c;b[a+(d+1<<1)>>1]=c;b[a+(d<<1)>>1]=c;return}function Eh(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;d=f>>1;n=a+-2|0;m=e[n>>1]|0;l=e[a+(d+-1<<1)>>1]|0;k=e[a+((d<<1)+-1<<1)>>1]|0;j=e[a+((d*3|0)+-1<<1)>>1]|0;i=e[a+((d<<2)+-1<<1)>>1]|0;h=e[a+((d*5|0)+-1<<1)>>1]|0;g=e[a+((d*6|0)+-1<<1)>>1]|0;f=e[a+((d*7|0)+-1<<1)>>1]|0;f=(((e[((b|0)==0?n:a+(~d<<1)|0)>>1]|0)+2+(m<<1)+l|0)>>>2)+4+((m+2+(l<<1)+k|0)>>>2)+((l+2+(k<<1)+j|0)>>>2)+((k+2+(j<<1)+i|0)>>>2)+((j+2+(i<<1)+h|0)>>>2)+((i+2+(h<<1)+g|0)>>>2)+((h+2+(g<<1)+f|0)>>>2)+((g+2+(f*3|0)|0)>>>2)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;b=C;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function Fh(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;q=(((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2)+4+((q+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+((l+2+(m<<1)+n|0)>>>2)+((m+2+(n<<1)+o|0)>>>2)+((n+2+(o<<1)+p|0)>>>2)+(((g&65535)+2+(p<<1)+o|0)>>>2)>>3;q=RF(q|0,((q|0)<0)<<31>>31|0,65537,65537)|0;f=C;d=a;c[d>>2]=q;c[d+4>>2]=f;d=a+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function Gh(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;d=e>>1;b=a;c[b>>2]=16777472;c[b+4>>2]=16777472;b=a+8|0;c[b>>2]=16777472;c[b+4>>2]=16777472;b=a+(d<<1)|0;e=b;c[e>>2]=16777472;c[e+4>>2]=16777472;e=b+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;b=b+(d<<1)|0;e=b;c[e>>2]=16777472;c[e+4>>2]=16777472;e=b+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;b=b+(d<<1)|0;e=b;c[e>>2]=16777472;c[e+4>>2]=16777472;e=b+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;b=b+(d<<1)|0;e=b;c[e>>2]=16777472;c[e+4>>2]=16777472;e=b+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;b=b+(d<<1)|0;e=b;c[e>>2]=16777472;c[e+4>>2]=16777472;e=b+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;b=b+(d<<1)|0;e=b;c[e>>2]=16777472;c[e+4>>2]=16777472;e=b+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;d=b+(d<<1)|0;b=d;c[b>>2]=16777472;c[b+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;return}function Hh(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function Ih(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*7<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*9<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*10<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*11<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*12<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*13<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*14<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function Jh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0;b=b>>1;f=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;g=C;d=a;c[d>>2]=f;c[d+4>>2]=g;d=a+8|0;c[d>>2]=f;c[d+4>>2]=g;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(b<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<1;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*3|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<2;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*5|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b*6|0;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;b=b*7|0;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Kh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;f=f+8|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function Lh(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;d=c>>1;g=0-d|0;h=a+6+(g<<1)|0;j=a+(d<<2<<1)+-2|0;n=d<<1;i=j+(0-n<<1)|0;l=j+(d<<1)|0;k=i+(g<<1)|0;f=l+(d<<1)|0;m=k+(g<<1)|0;h=((((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+((((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+(((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+((e[h+2>>1]|0)-(e[h+-2>>1]|0))))|0)*17|0)+16>>5;i=((((e[f+(d<<1)>>1]|0)-(e[m+(g<<1)>>1]|0)<<2)+((((e[f>>1]|0)-(e[m>>1]|0)|0)*3|0)+(((e[l>>1]|0)-(e[k>>1]|0)<<1)+((e[j>>1]|0)-(e[i>>1]|0))))|0)*17|0)+16>>5;j=h<<1;k=h*3|0;l=h<<2;m=h*5|0;f=h*6|0;g=h*7|0;n=(_(i+h|0,-3)|0)+16+((e[a+(-2-n)+16>>1]|0)+(e[a+((d*14|0)+-2)>>1]|0)<<4)|0;o=8;while(1){c=n>>5;if(c>>>0>=512)c=0-c>>31&511;b[a>>1]=c;c=n+h>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+2>>1]=c;c=n+j>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+4>>1]=c;c=n+k>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+6>>1]=c;c=n+l>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+8>>1]=c;c=n+m>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+10>>1]=c;c=n+f>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+12>>1]=c;c=n+g>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+14>>1]=c;if((o|0)>1){n=n+i|0;o=o+-1|0;a=a+(d<<1)|0}else break}return}function Mh(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;d=c>>1;n=0-d|0;f=a+6+(n<<1)|0;g=a+(d<<3<<1)+-2|0;o=g+(0-(d<<1)<<1)|0;p=a+((d*6|0)+-2)|0;m=a+((d*22|0)+-2)|0;i=g+(d<<1)|0;h=o+(n<<1)|0;k=i+(d<<1)|0;j=h+(n<<1)|0;c=_(d,-3)|0;q=m+(d<<1)|0;l=p+(n<<1)|0;s=q+(d<<1)|0;r=l+(n<<1)|0;u=s+(d<<1)|0;t=r+(n<<1)|0;o=((((e[u+(d<<1)>>1]|0)-(e[t+(n<<1)>>1]|0)<<3)+((((e[u>>1]|0)-(e[t>>1]|0)|0)*7|0)+((((e[s>>1]|0)-(e[r>>1]|0)|0)*6|0)+((((e[q>>1]|0)-(e[l>>1]|0)|0)*5|0)+(((e[k+(d<<1)>>1]|0)-(e[j+(n<<1)>>1]|0)<<2)+((((e[k>>1]|0)-(e[j>>1]|0)|0)*3|0)+(((e[i>>1]|0)-(e[h>>1]|0)<<1)+((e[g>>1]|0)-(e[o>>1]|0))))))))|0)*5|0)+32>>6;f=((((e[f+8>>1]|0)-(e[f+-8>>1]|0)<<2)+((((e[f+6>>1]|0)-(e[f+-6>>1]|0)|0)*3|0)+(((e[f+4>>1]|0)-(e[f+-4>>1]|0)<<1)+((e[f+2>>1]|0)-(e[f+-2>>1]|0))))|0)*17|0)+16>>5;g=f*3|0;h=f<<1;i=f<<2;j=f*5|0;k=f*6|0;l=f*7|0;m=16-g+(_(o,-7)|0)+((e[p+(c<<1)+(n<<1)+16>>1]|0)+(e[m+(d<<3)>>1]|0)<<4)|0;n=16;while(1){c=m>>5;if(c>>>0>=512)c=0-c>>31&511;b[a>>1]=c;c=m+f>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+2>>1]=c;c=m+h>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+4>>1]=c;c=m+g>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+6>>1]=c;c=m+i>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+8>>1]=c;c=m+j>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+10>>1]=c;c=m+k>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+12>>1]=c;c=m+l>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+14>>1]=c;if((n|0)>1){m=m+o|0;n=n+-1|0;a=a+(d<<1)|0}else break}return}function Nh(b,c){b=b|0;c=c|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;k=70232+(0-(d[b+~c>>0]|0))|0;l=b+(0-c)|0;m=l+1|0;n=l+2|0;f=l+3|0;g=l+4|0;h=l+5|0;i=l+6|0;j=l+7|0;e=0;while(1){o=k+(d[b+-1>>0]|0)|0;a[b>>0]=a[o+(d[l>>0]|0)>>0]|0;a[b+1>>0]=a[o+(d[m>>0]|0)>>0]|0;a[b+2>>0]=a[o+(d[n>>0]|0)>>0]|0;a[b+3>>0]=a[o+(d[f>>0]|0)>>0]|0;a[b+4>>0]=a[o+(d[g>>0]|0)>>0]|0;a[b+5>>0]=a[o+(d[h>>0]|0)>>0]|0;a[b+6>>0]=a[o+(d[i>>0]|0)>>0]|0;a[b+7>>0]=a[o+(d[j>>0]|0)>>0]|0;e=e+1|0;if((e|0)==8)break;else b=b+c|0}return}function Oh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=b>>1;k=p<<2;j=p*5|0;m=p<<1;i=p*6|0;h=p*3|0;f=(e[a+(7-p<<1)>>1]|0)+((e[a+(6-p<<1)>>1]|0)+((e[a+(5-p<<1)>>1]|0)+(e[a+(4-p<<1)>>1]|0)))|0;b=p*7|0;d=(e[a+(b+-1<<1)>>1]|0)+((e[a+(i+-1<<1)>>1]|0)+((e[a+(j+-1<<1)>>1]|0)+(e[a+(k+-1<<1)>>1]|0)))|0;o=(e[a+(h+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+((e[a+(p+-1<<1)>>1]|0)+((e[a+-2>>1]|0)+(e[a+(0-p<<1)>>1]|0))+(e[a+(1-p<<1)>>1]|0))+(e[a+(2-p<<1)>>1]|0))+(e[a+(3-p<<1)>>1]|0)+4>>3;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;n=C;l=RF((f+2|0)>>>2|0,0,65537,65537)|0;g=C;q=a;c[q>>2]=o;c[q+4>>2]=n;q=a+8|0;c[q>>2]=l;c[q+4>>2]=g;p=a+(p<<1)|0;q=p;c[q>>2]=o;c[q+4>>2]=n;p=p+8|0;c[p>>2]=l;c[p+4>>2]=g;m=a+(m<<1)|0;p=m;c[p>>2]=o;c[p+4>>2]=n;m=m+8|0;c[m>>2]=l;c[m+4>>2]=g;h=a+(h<<1)|0;m=h;c[m>>2]=o;c[m+4>>2]=n;h=h+8|0;c[h>>2]=l;c[h+4>>2]=g;h=RF((d+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((d+4+f|0)>>>3|0,0,65537,65537)|0;d=C;k=a+(k<<1)|0;l=k;c[l>>2]=h;c[l+4>>2]=g;k=k+8|0;c[k>>2]=f;c[k+4>>2]=d;j=a+(j<<1)|0;k=j;c[k>>2]=h;c[k+4>>2]=g;j=j+8|0;c[j>>2]=f;c[j+4>>2]=d;i=a+(i<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Ph(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;m=b>>1;i=m<<2;h=m*5|0;l=m<<1;g=m*6|0;d=m*3|0;b=m*7|0;f=(e[a+(b+-1<<1)>>1]|0)+((e[a+(g+-1<<1)>>1]|0)+((e[a+(h+-1<<1)>>1]|0)+(e[a+(i+-1<<1)>>1]|0)))|0;k=RF(((e[a+(d+-1<<1)>>1]|0)+((e[a+(l+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;j=C;n=a;c[n>>2]=k;c[n+4>>2]=j;n=a+8|0;c[n>>2]=k;c[n+4>>2]=j;m=a+(m<<1)|0;n=m;c[n>>2]=k;c[n+4>>2]=j;m=m+8|0;c[m>>2]=k;c[m+4>>2]=j;l=a+(l<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;l=l+8|0;c[l>>2]=k;c[l+4>>2]=j;d=a+(d<<1)|0;l=d;c[l>>2]=k;c[l+4>>2]=j;d=d+8|0;c[d>>2]=k;c[d+4>>2]=j;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a+(i<<1)|0;j=i;c[j>>2]=f;c[j+4>>2]=d;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;i=h;c[i>>2]=f;c[i+4>>2]=d;h=h+8|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;h=g;c[h>>2]=f;c[h+4>>2]=d;g=g+8|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}
function gA(a,c,d,e,f,g,h,i,j,k){a=a|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0,q=0,r=0;if(!((j|0)!=0&(k|0)!=0))return;if((i|0)<(k|0)){if((i|0)<=(0-g|0)){r=1-g|0;c=c+(_(0-e|0,i)|0)+(_(r,e)|0)|0;i=r}}else{r=k+-1|0;c=c+(_(0-e|0,i)|0)+(_(r,e)|0)|0;i=r}if((h|0)<(j|0))if((h|0)>(0-f|0))l=h;else{r=1-f|0;l=r;c=c+(r-h<<1)|0}else{r=j+-1|0;l=r;c=c+(r-h<<1)|0}m=0-i|0;m=(m|0)<0?0:m;r=0-l|0;r=(r|0)<0?0:r;i=k-i|0;i=(i|0)<(g|0)?i:g;q=j-l|0;p=(q|0)<(f|0);q=p?q:f;n=q-r|0;o=r<<1;h=c+(o+(_(m,e)|0))|0;c=a+o|0;if((m|0)>0){k=n<<1;l=0;do{KF(c|0,h|0,k|0)|0;c=c+d|0;l=l+1|0}while((l|0)<(m|0));l=m}else l=0;if((l|0)<(i|0)){k=n<<1;do{KF(c|0,h|0,k|0)|0;h=h+e|0;c=c+d|0;l=l+1|0}while((l|0)<(i|0))}else i=l;j=h+(0-e)|0;if((i|0)<(g|0)){l=n<<1;k=_(g-i|0,d)|0;h=c;while(1){KF(h|0,j|0,l|0)|0;i=i+1|0;if((i|0)==(g|0))break;else h=h+d|0}c=c+k|0}l=c+(0-((_(g,d)|0)+o))|0;i=g+-1|0;if(!g)return;k=q+-1|0;if((r|0)<=0){if(!p)return;while(1){c=l+(k<<1)|0;h=q;do{b[l+(h<<1)>>1]=b[c>>1]|0;h=h+1|0}while((h|0)<(f|0));if(!i)break;else{l=l+d|0;i=i+-1|0}}return}if(!p){while(1){c=l+(r<<1)|0;h=0;do{b[l+(h<<1)>>1]=b[c>>1]|0;h=h+1|0}while((h|0)<(r|0));if(!i)break;else{l=l+d|0;i=i+-1|0}}return}while(1){c=l+(r<<1)|0;h=0;do{b[l+(h<<1)>>1]=b[c>>1]|0;h=h+1|0}while((h|0)<(r|0));c=l+(k<<1)|0;h=q;do{b[l+(h<<1)>>1]=b[c>>1]|0;h=h+1|0}while((h|0)<(f|0));if(!i)break;else{l=l+d|0;i=i+-1|0}}return}function hA(a,b){a=a|0;b=b|0;c[a+4>>2]=691;c[a>>2]=(b|0)<9?2:1;return}function iA(a,b,c){a=a|0;b=b|0;c=c|0;return}function jA(f,g,h,j,k,l,m,n,o,p,q,r,s){f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;var t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0;N=i;i=i+12112|0;M=N+72|0;L=N+64|0;G=N+56|0;H=N+48|0;D=N+40|0;E=N+32|0;w=N+16|0;v=N;t=N+104|0;u=N+88|0;c[f>>2]=g;K=(s&4|0)!=0;do if(K)if((h+1|0)>>>0<1501){c[u>>2]=c[f>>2];c[u+4>>2]=c[f+4>>2];c[u+8>>2]=c[f+8>>2];c[u+12>>2]=c[f+12>>2];c[u+8>>2]=0;I=t;J=u;break}else{c[v>>2]=85678;c[v+4>>2]=85655;c[v+8>>2]=288;UC(0,0,104466,v);Ya()}else{c[f+4>>2]=0;c[f+12>>2]=0;c[f+8>>2]=0;t=h+1|0;if(t>>>0>268435454){f=-12;i=N;return f|0}t=YC(t<<3)|0;if(!t){f=-12;i=N;return f|0}else{I=t;J=f}}while(0);F=(p|0)!=0;if(F&(r|0)>2){c[w>>2]=85737;c[w+4>>2]=85655;c[w+8>>2]=304;UC(0,0,104466,w);Ya()}B=(h|0)>0;a:do if(B){y=g*3|0;z=(s&2|0)==0;A=0;x=0;while(1){t=j+(_(A,k)|0)|0;switch(l|0){case 1:{w=a[t>>0]|0;a[I+(x<<3)>>0]=w;break}case 2:{w=b[t>>1]&255;a[I+(x<<3)>>0]=w;break}default:{w=c[t>>2]&255;a[I+(x<<3)>>0]=w}}t=w&255;if((t|0)>(g|0)){if((w&255)>32|(t|0)>(y|0)){v=18;break}u=m+(_(A,n)|0)|0;switch(o|0){case 1:{u=d[u>>0]|0;v=I+(x<<3)+4|0;c[v>>2]=u;break}case 2:{u=e[u>>1]|0;v=I+(x<<3)+4|0;c[v>>2]=u;break}default:{u=c[u>>2]|0;v=I+(x<<3)+4|0;c[v>>2]=u}}w=GF(1,0,w&255|0)|0;O=C;if(!(0<(O|0)|0==(O|0)&u>>>0<w>>>0)){v=25;break}if(z)t=u<<32-t;else t=(d[104547+(u>>>8&255)>>0]|0)<<16|(d[104547+(u&255)>>0]|0)<<24|(d[104547+(u>>>16&255)>>0]|0)<<8|(d[104547+(u>>>24)>>0]|0);c[v>>2]=t;b:do if(F){t=p+(_(A,q)|0)|0;switch(r|0){case 1:{b[I+(x<<3)+2>>1]=d[t>>0]|0;break b}case 2:{b[I+(x<<3)+2>>1]=b[t>>1]|0;break b}default:{b[I+(x<<3)+2>>1]=c[t>>2];break b}}}else b[I+(x<<3)+2>>1]=A;while(0);t=x+1|0}else t=x;A=A+1|0;if((A|0)>=(h|0)){v=38;break}else x=t}if((v|0)==18){c[E>>2]=t;UC(0,16,85767,E);if(K){O=-1;i=N;return O|0}$C(I);O=-1;i=N;return O|0}else if((v|0)==25){UC(0,16,85798,D);if(K){O=-1;i=N;return O|0}$C(I);O=-1;i=N;return O|0}else if((v|0)==38){sE(I,t,8,5);if(!B){u=I;break}y=g*3|0;z=(s&2|0)==0;A=0;x=t;while(1){t=j+(_(A,k)|0)|0;switch(l|0){case 1:{w=a[t>>0]|0;a[I+(x<<3)>>0]=w;break}case 2:{w=b[t>>1]&255;a[I+(x<<3)>>0]=w;break}default:{w=c[t>>2]&255;a[I+(x<<3)>>0]=w}}t=w&255;if(w<<24>>24==0|(t|0)>(g|0))t=x;else{if((w&255)>32|(t|0)>(y|0)){v=46;break}u=m+(_(A,n)|0)|0;switch(o|0){case 1:{u=d[u>>0]|0;v=I+(x<<3)+4|0;c[v>>2]=u;break}case 2:{u=e[u>>1]|0;v=I+(x<<3)+4|0;c[v>>2]=u;break}default:{u=c[u>>2]|0;v=I+(x<<3)+4|0;c[v>>2]=u}}O=GF(1,0,w&255|0)|0;E=C;if(!(0<(E|0)|0==(E|0)&u>>>0<O>>>0)){v=53;break}if(z)t=u<<32-t;else t=(d[104547+(u>>>8&255)>>0]|0)<<16|(d[104547+(u&255)>>0]|0)<<24|(d[104547+(u>>>16&255)>>0]|0)<<8|(d[104547+(u>>>24)>>0]|0);c[v>>2]=t;c:do if(F){t=p+(_(A,q)|0)|0;switch(r|0){case 1:{b[I+(x<<3)+2>>1]=d[t>>0]|0;break c}case 2:{b[I+(x<<3)+2>>1]=b[t>>1]|0;break c}default:{b[I+(x<<3)+2>>1]=c[t>>2];break c}}}else b[I+(x<<3)+2>>1]=A;while(0);t=x+1|0}A=A+1|0;if((A|0)>=(h|0)){u=I;break a}else x=t}if((v|0)==46){c[H>>2]=t;UC(0,16,85767,H);if(K){O=-1;i=N;return O|0}$C(I);O=-1;i=N;return O|0}else if((v|0)==53){UC(0,16,85798,G);if(K){O=-1;i=N;return O|0}$C(I);O=-1;i=N;return O|0}}}else{sE(I,0,8,5);u=I;t=0}while(0);v=lA(J,g,t,I,s)|0;if(!K){$C(u);if((v|0)>=0){O=0;i=N;return O|0}bD(J+4|0);O=v;i=N;return O|0}t=c[J+8>>2]|0;u=c[J+12>>2]|0;if((t|0)!=(u|0)){c[L>>2]=t;c[L+4>>2]=u;UC(0,16,85841,L)}if((v|0)<=-1){c[M>>2]=85859;c[M+4>>2]=85655;c[M+8>>2]=346;UC(0,0,104466,M);Ya()};c[f>>2]=c[J>>2];c[f+4>>2]=c[J+4>>2];c[f+8>>2]=c[J+8>>2];c[f+12>>2]=c[J+12>>2];O=0;i=N;return O|0}function kA(a,b){a=a|0;b=b|0;return ((c[a+4>>2]|0)>>>1)-((c[b+4>>2]|0)>>>1)|0}function lA(e,f,g,h,j){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;D=i;i=i+16|0;y=D;B=D+4|0;z=1<<f;if((f|0)>30){C=-1;i=D;return C|0}l=e+8|0;A=c[l>>2]|0;x=A+z|0;c[l>>2]=x;m=e+12|0;k=c[m>>2]|0;do if((x|0)>(k|0)){if(j&4|0)Ya();k=(1<<c[e>>2])+k|0;c[m>>2]=k;x=e+4|0;k=_C(c[x>>2]|0,k,4)|0;c[x>>2]=k;if(k|0){x=c[e>>2]|0;FF(k+(c[m>>2]<<2)+(-1<<x<<2)|0,0,4<<x|0)|0;break}c[m>>2]=0;c[l>>2]=0;C=-12;i=D;return C|0}while(0);if((A|0)<0){C=A;i=D;return C|0}v=e+4|0;c[B>>2]=(c[v>>2]|0)+(A<<2);a:do if((g|0)>0){w=32-f|0;x=(j&2|0)==0;k=0;b:while(1){u=h+(k<<3)|0;o=a[u>>0]|0;s=o&255;n=h+(k<<3)+4|0;p=c[n>>2]|0;t=b[h+(k<<3)+2>>1]|0;if((s|0)>(f|0)){l=s-f|0;m=p>>>w;a[u>>0]=l;c[n>>2]=p<<f;n=k+1|0;c:do if((n|0)<(g|0)){r=k;s=n;while(1){n=h+(s<<3)|0;o=(d[n>>0]|0)-f|0;if((o|0)<1){n=s;break c}p=h+(s<<3)+4|0;q=c[p>>2]|0;if((q>>>w|0)!=(m|0)){n=s;break c}a[n>>0]=o;c[p>>2]=q<<f;l=(l|0)>(o|0)?l:o;n=s+1|0;if((n|0)<(g|0)){r=s;s=n}else{r=s;break}}}else r=k;while(0);l=(l|0)>(f|0)?f:l;if(!x)m=(d[104547+(m>>>8&255)>>0]<<16|d[104547+(m&255)>>0]<<24|d[104547+(m>>>16&255)>>0]<<8|d[104547+(m>>>24)>>0])>>>w;b[(c[B>>2]|0)+(m<<2)+2>>1]=0-l;k=lA(e,l,n-k|0,u,j)|0;if((k|0)<0){C=32;break}c[B>>2]=(c[v>>2]|0)+(A<<2);b[(c[B>>2]|0)+(m<<2)>>1]=k;k=r}else{m=f-s|0;q=1<<m;if(x){r=1;l=p>>>w}else{r=1<<s;l=d[104547+(p>>>8&255)>>0]<<16|d[104547+(p&255)>>0]<<24|d[104547+(p>>>16&255)>>0]<<8|d[104547+(p>>>24)>>0]}if((m|0)!=31){n=o&255;m=0;while(1){u=b[(c[B>>2]|0)+(l<<2)+2>>1]|0;if(!(u<<16>>16==0|(u<<16>>16|0)==(s|0)))break b;b[(c[B>>2]|0)+(l<<2)+2>>1]=n;b[(c[B>>2]|0)+(l<<2)>>1]=t;m=m+1|0;if((m|0)>=(q|0))break;else l=l+r|0}}}k=k+1|0;if((k|0)>=(g|0))break a}if((C|0)==32){i=D;return k|0}UC(0,16,85824,y);C=-1094995529;i=D;return C|0}while(0);if((f|0)==31){C=A;i=D;return C|0}else k=0;do{if(!(b[(c[B>>2]|0)+(k<<2)+2>>1]|0))b[(c[B>>2]|0)+(k<<2)>>1]=-1;k=k+1|0}while((k|0)<(z|0));k=A;i=D;return k|0}function mA(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0;g=a+16|0;c[a+12>>2]=b;c[a+20>>2]=b+e;h=b+1|0;c[g>>2]=h;e=(d[b>>0]|0)<<18;c[a>>2]=e;f=b+2|0;c[g>>2]=f;e=(d[h>>0]|0)<<10|e;c[a>>2]=e;c[g>>2]=b+3;e=(d[f>>0]|0)<<2|e|2;c[a>>2]=e;c[a+4>>2]=510;return (e>>>0>66846720?-1094995529:0)|0}function nA(){var b=0,e=0,f=0,g=0,h=0;if(!(c[27039]|0))b=0;else return;while(1)if(!b){a[188032]=9;b=1;continue}else{f=(b&65280|0)==0;a[188032+b>>0]=(f?8:0)-(d[97868+(f?b:b>>>8)>>0]|0);b=b+1|0;if((b|0)==512){b=0;break}else continue}while(1){e=b<<1;f=a[85868+(b<<2)>>0]|0;a[188544+(e|1)>>0]=f;a[188544+e>>0]=f;f=a[85868+(b<<2)+1>>0]|0;g=e+128|0;a[188544+(g|1)>>0]=f;a[188544+g>>0]=f;f=a[85868+(b<<2)+2>>0]|0;h=e+256|0;a[188544+(h|1)>>0]=f;a[188544+h>>0]=f;h=a[85868+(b<<2)+3>>0]|0;f=e+384|0;a[188544+(f|1)>>0]=h;a[188544+f>>0]=h;f=(d[86124+b>>0]|0)<<1;a[189056+g>>0]=f;a[189056+(e+129)>>0]=f|1;if(!b){b=128-e|0;a[189056+(b+-1)>>0]=1;a[189056+(b+-2)>>0]=0;b=1;continue}else{g=(d[86188+b>>0]|0)<<1;h=128-e|0;a[189056+(h+-1)>>0]=g;a[189056+(h+-2)>>0]=g|1;b=b+1|0;if((b|0)==64)break;else continue}}b=189312;e=86252;f=b+63|0;do{a[b>>0]=a[e>>0]|0;b=b+1|0;e=e+1|0}while((b|0)<(f|0));c[27039]=1;return}function oA(a){a=a|0;var b=0,d=0;b=c[a>>2]|0;if(!(c[b+684>>2]|0))return;d=a+520|0;if(!(c[d>>2]|0)){LA(a+4|0,b);c[d>>2]=1}FF(c[a+556>>2]|0,127,_(c[a+536>>2]|0,c[a+540>>2]|0)|0)|0;c[a+548>>2]=(c[a+528>>2]|0)*3;c[a+552>>2]=0;return}function pA(b,e,f,g,h,j){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;r=i;i=i+16|0;k=r;q=b+532|0;l=c[q>>2]|0;p=(_(l,f)|0)+e|0;n=b+528|0;o=c[n>>2]|0;m=o+-1|0;p=(p|0)<0?0:(p|0)>(m|0)?m:p;h=(_(l,h)|0)+g|0;h=(h|0)<0?0:(h|0)>(o|0)?o:h;o=b+524|0;l=c[o>>2]|0;m=c[l+(p<<2)>>2]|0;l=c[l+(h<<2)>>2]|0;e=c[b>>2]|0;f=c[e+712>>2]|0;if(f|0)if(c[f+32>>2]|0){i=r;return}if((l|0)<(m|0)|(p|0)>(h|0)){UC(e,16,86315,k);i=r;return}if(!(c[e+684>>2]|0)){i=r;return}if(!(j&18))e=-2;else{e=b+548|0;c[e>>2]=(c[e>>2]|0)+(p+~h);e=-20}if(j&36){k=b+548|0;c[k>>2]=(c[k>>2]|0)+(p+~h);e=e&-38}if(j&72){k=b+548|0;c[k>>2]=(c[k>>2]|0)+(p+~h);e=e&-73}if(j&14|0){c[b+552>>2]=1;c[b+548>>2]=2147483647}if((e|0)==-128)FF((c[b+556>>2]|0)+m|0,0,l-m|0)|0;else if((l|0)>(m|0)){f=b+556|0;g=m;do{k=(c[f>>2]|0)+g|0;a[k>>0]=(d[k>>0]|0)&e;g=g+1|0}while((g|0)!=(l|0))}if((h|0)==(c[n>>2]|0)){c[b+548>>2]=2147483647;g=b+556|0}else{g=b+556|0;n=(c[g>>2]|0)+l|0;a[n>>0]=(d[n>>0]|0)&e;n=(c[g>>2]|0)+l|0;a[n>>0]=d[n>>0]|0|j}j=(c[g>>2]|0)+m|0;a[j>>0]=d[j>>0]|0|1;if((m|0)<=0){i=r;return}e=c[b>>2]|0;if(c[e+816>>2]&2|0){i=r;return}f=c[e+712>>2]|0;if(f|0)if(c[f+32>>2]|0){i=r;return}if(c[(c[e+12>>2]|0)+16>>2]&128|0){i=r;return}if(!(c[b+648>>2]|0)){i=r;return}if(c[b+676>>2]|0){i=r;return}if((_(c[q>>2]|0,c[e+328>>2]|0)|0)>=(p|0)){i=r;return}if((a[(c[g>>2]|0)+(c[(c[o>>2]|0)+(p+-1<<2)>>2]|0)>>0]&-2)<<24>>24==112){i=r;return}c[b+552>>2]=1;c[b+548>>2]=2147483647;i=r;return}function qA(e){e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0;ta=i;i=i+176|0;C=ta+128|0;F=ta+112|0;fa=ta+48|0;w=ta+40|0;v=ta+32|0;s=ta+24|0;r=ta+16|0;q=ta+8|0;o=ta;da=ta+144|0;na=e+544|0;t=c[na>>2]|0;ra=e+536|0;u=c[ra>>2]|0;p=c[e>>2]|0;if(!(c[p+684>>2]|0)){i=ta;return}n=e+548|0;if(!(c[n>>2]|0)){i=ta;return}if(c[p+800>>2]|0){i=ta;return}f=c[p+712>>2]|0;if(f|0)if(c[f+32>>2]|0){i=ta;return}if(c[(c[p+12>>2]|0)+16>>2]&128|0){i=ta;return}ba=e+648|0;pa=c[ba>>2]|0;if(!pa){i=ta;return}if(c[e+676>>2]|0){i=ta;return}qa=e+532|0;j=c[qa>>2]|0;k=j*3|0;l=c[p+328>>2]|0;m=c[p+332>>2]|0;if((c[n>>2]|0)==(_(k,m+l|0)|0)){i=ta;return}sa=e+648|0;oa=pa+32|0;a:do if((j|0)>0){g=_(u+-1|0,c[e+540>>2]|0)|0;h=c[e+556>>2]|0;f=0;do{if((a[h+(g+f)>>0]|0)!=127)break a;f=f+1|0}while((f|0)<(j|0))}else f=0;while(0);if((f|0)==(j|0))if((c[p+48>>2]|0)==2)if(c[p+124>>2]&16|0)if((c[n>>2]|0)==(_(k,l+1+m|0)|0)){UC(p,48,86355,o);i=ta;return}E=e+680|0;f=c[E>>2]|0;do if(f|0){if((c[f+68>>2]|0)==(c[pa+68>>2]|0))if((c[f+72>>2]|0)==(c[pa+72>>2]|0))if((c[f+80>>2]|0)==(c[pa+80>>2]|0))break;UC(p,24,86384,q);c[E>>2]=0;c[E+4>>2]=0;c[E+8>>2]=0;c[E+12>>2]=0;c[E+16>>2]=0;c[E+20>>2]=0;c[E+24>>2]=0;c[E+28>>2]=0}while(0);D=e+712|0;f=c[D>>2]|0;do if(f|0){g=c[sa>>2]|0;if((c[f+68>>2]|0)==(c[g+68>>2]|0))if((c[f+72>>2]|0)==(c[g+72>>2]|0))if((c[f+80>>2]|0)==(c[g+80>>2]|0))break;UC(c[e>>2]|0,24,86434,r);c[D>>2]=0;c[D+4>>2]=0;c[D+8>>2]=0;c[D+12>>2]=0;c[D+16>>2]=0;c[D+20>>2]=0;c[D+24>>2]=0;c[D+28>>2]=0}while(0);ma=e+656|0;if(!(c[ma>>2]|0))ha=31;else if(!(c[e+664>>2]|0))ha=31;do if((ha|0)==31){UC(c[e>>2]|0,16,86480,s);k=e+540|0;j=(_(t<<3,u)|0)+16|0;l=e+744|0;c[l>>2]=LB(_(c[k>>2]<<2,c[ra>>2]|0)|0)|0;f=LB(j)|0;m=e+752|0;c[m>>2]=f;g=c[l>>2]|0;if((f|0)==0|(g|0)==0){h=e+664|0;g=e+748|0;f=e+756|0}else{h=e+664|0;c[h>>2]=c[g+4>>2];c[ma>>2]=(c[f+4>>2]|0)+16;g=e+748|0;c[g>>2]=LB(_(c[k>>2]<<2,c[ra>>2]|0)|0)|0;j=LB(j)|0;f=e+756|0;c[f>>2]=j;k=c[g>>2]|0;if(!((j|0)==0|(k|0)==0)){c[e+668>>2]=c[k+4>>2];c[e+660>>2]=(c[j+4>>2]|0)+16;break}}NB(l);NB(m);c[h>>2]=0;c[ma>>2]=0;NB(g);NB(f);c[e+668>>2]=0;c[e+660>>2]=0;i=ta;return}while(0);if(c[(c[e>>2]|0)+688>>2]&1024|0)if((c[ra>>2]|0)>0){f=e+540|0;g=e+556|0;j=0;do{if((c[qa>>2]|0)>0){h=0;do{ka=(_(c[f>>2]|0,j)|0)+h|0;la=c[e>>2]|0;c[v>>2]=d[(c[g>>2]|0)+ka>>0];UC(la,48,86507,v);h=h+1|0}while((h|0)<(c[qa>>2]|0))}UC(c[e>>2]|0,48,86512,w);j=j+1|0}while((j|0)<(c[ra>>2]|0))}ja=e+528|0;ka=e+524|0;la=e+556|0;f=c[ja>>2]|0;if((f|0)>0){g=0;while(1){k=f;f=f+-1|0;h=(c[la>>2]|0)+(c[(c[ka>>2]|0)+(f<<2)>>2]|0)|0;j=d[h>>0]|0;g=(j&18|0)==0?g:1;if(!g)a[h>>0]=j|2;if((k|0)<=1)break;else g=(j&1|0)==0?g:0}f=c[ja>>2]|0;if((f|0)>0){g=0;while(1){k=f;f=f+-1|0;h=(c[la>>2]|0)+(c[(c[ka>>2]|0)+(f<<2)>>2]|0)|0;j=d[h>>0]|0;g=(j&36|0)==0?g:1;if(!g)a[h>>0]=j|4;if((k|0)<=1)break;else g=(j&1|0)==0?g:0}f=c[ja>>2]|0;if((f|0)>0){g=0;while(1){k=f;f=f+-1|0;h=(c[la>>2]|0)+(c[(c[ka>>2]|0)+(f<<2)>>2]|0)|0;j=d[h>>0]|0;g=(j&72|0)==0?g:1;if(!g)a[h>>0]=j|8;if((k|0)<=1)break;else g=(j&1|0)==0?g:0}}}}ga=e+768|0;if(c[ga>>2]|0){f=c[ja>>2]|0;if((f|0)>0){g=0;while(1){k=f;f=f+-1|0;h=(c[la>>2]|0)+(c[(c[ka>>2]|0)+(f<<2)>>2]|0)|0;j=d[h>>0]|0;g=(j&98|0)==0?((j&16|0)==0?g:0):1;if(!g)a[h>>0]=j|2;if((k|0)<=1)break;else g=(j&1|0)==0?g:0}}}if(c[(c[e>>2]|0)+696>>2]&8|0){g=(c[ja>>2]|0)+-2|0;f=c[qa>>2]|0;if((g|0)>=(f+100|0)){h=1;while(1){ia=c[ka>>2]|0;aa=c[la>>2]|0;j=aa+(c[ia+(g<<2)>>2]|0)|0;$=a[j>>0]|0;k=$&255;h=(k&1|0)==0?h:1;if($<<24>>24!=127?(a[aa+(c[ia+(g+1<<2)>>2]|0)>>0]|0)==127:0){if((k&112|0)!=0|(h|0)==0)ha=56}else if(!h)ha=56;if((ha|0)==56){ha=0;a[j>>0]=k|14;f=c[qa>>2]|0;h=0}if((g|0)>(f+100|0))g=g+-1|0;else break}}}ia=e+576|0;f=c[ja>>2]|0;if((f|0)>0){g=9999999;do{m=f;f=f+-1|0;h=c[(c[ka>>2]|0)+(f<<2)>>2]|0;k=(c[la>>2]|0)+h|0;l=d[k>>0]|0;j=c[ia>>2]|0;if(!j)ha=60;else if(!(a[j+h>>0]|0))ha=60;if((ha|0)==60){ha=0;g=g+1|0}g=(l&2|0)==0?g:0;do if(!(c[ga>>2]|0)){if((g|0)>=50)break;a[k>>0]=l|2}else{if((g|0)>=100)break;a[k>>0]=l|2}while(0);g=(l&1|0)==0?g:9999999}while((m|0)>1);f=g}else f=9999999;g=c[ja>>2]|0;if((g|0)>0)do{m=g;g=g+-1|0;h=c[(c[ka>>2]|0)+(g<<2)>>2]|0;k=(c[la>>2]|0)+h|0;l=d[k>>0]|0;j=c[ia>>2]|0;if(!j)ha=336;else if(!(a[j+h>>0]|0))ha=336;if((ha|0)==336){ha=0;f=f+1|0}f=(l&4|0)==0?f:0;do if(!(c[ga>>2]|0)){if((f|0)>=50)break;a[k>>0]=l|4}else{if((f|0)>=100)break;a[k>>0]=l|4}while(0);f=(l&1|0)==0?f:9999999}while((m|0)>1);g=c[ja>>2]|0;if((g|0)>0)while(1){m=g;g=g+-1|0;h=c[(c[ka>>2]|0)+(g<<2)>>2]|0;k=(c[la>>2]|0)+h|0;l=d[k>>0]|0;j=c[ia>>2]|0;if(!j)ha=346;else if(!(a[j+h>>0]|0))ha=346;if((ha|0)==346){ha=0;f=f+1|0}f=(l&8|0)==0?f:0;do if(!(c[ga>>2]|0)){if((f|0)>=50)break;a[k>>0]=l|8}else{if((f|0)>=100)break;a[k>>0]=l|8}while(0);if((m|0)<=1)break;else f=(l&1|0)==0?f:9999999}f=c[ja>>2]|0;if((f|0)>0){k=0;l=0;while(1){h=(c[la>>2]|0)+(c[(c[ka>>2]|0)+(l<<2)>>2]|0)|0;j=d[h>>0]|0;g=j&14;if(!(j&1)){a[h>>0]=j|k;f=c[ja>>2]|0;g=g|k}l=l+1|0;if((l|0)>=(f|0))break;else k=g}}do if(!(c[ga>>2]|0)){if((f|0)>0)g=0;else{h=0;g=0;f=0;break}do{f=(c[la>>2]|0)+(c[(c[ka>>2]|0)+(g<<2)>>2]|0)|0;ha=d[f>>0]|0;a[f>>0]=(ha&14|0)==0?ha:ha|14;g=g+1|0;f=c[ja>>2]|0}while((g|0)<(f|0));ha=73}else ha=73;while(0);do if((ha|0)==73){if((f|0)<=0){h=0;g=0;f=0;break}k=c[ka>>2]|0;l=c[la>>2]|0;h=0;g=0;m=0;j=0;do{aa=d[l+(c[k+(m<<2)>>2]|0)>>0]|0;g=(aa>>>2&1)+g|0;h=(aa>>>1&1)+h|0;j=(aa>>>3&1)+j|0;m=m+1|0}while((m|0)<(f|0));f=j}while(0);B=c[e>>2]|0;aa=(SD(c[(c[sa>>2]|0)+88>>2]|0)|0)<<24>>24;c[fa>>2]=g;c[fa+4>>2]=h;c[fa+8>>2]=f;c[fa+12>>2]=aa;UC(B,32,86514,fa);B=e+680|0;f=c[B>>2]|0;do if(!f){f=1;ha=100}else{if(!(c[f>>2]|0)){f=1;ha=100;break}if(c[(c[e>>2]|0)+684>>2]&256|0){f=0;ha=100;break}k=c[ja>>2]|0;if((k|0)<=0){g=k;break}g=c[ka>>2]|0;h=c[la>>2]|0;j=0;f=0;do{f=((a[h+(c[g+(j<<2)>>2]|0)>>0]&12)!=12&1)+f|0;j=j+1|0}while((j|0)!=(k|0));if((f|0)<5){f=0;ha=100;break}o=(f|0)>99?(f|0)/50|0:1;if((c[ra>>2]|0)>1){p=e+540|0;q=e+8|0;r=e+684|0;s=e+672|0;h=c[qa>>2]|0;f=0;g=0;t=0;do{if((h|0)>0){n=t<<4;m=0;do{j=(_(c[p>>2]|0,t)|0)+m|0;do if((a[(c[la>>2]|0)+j>>0]&12)!=12){g=g+1|0;if((g|0)%(o|0)|0|0)break;k=c[ba>>2]|0;if((c[k+88>>2]|0)==1){l=k+32|0;aa=m<<4;h=c[l>>2]|0;ha=_(h,n)|0;j=(c[k>>2]|0)+aa+ha|0;k=(c[c[B>>2]>>2]|0)+aa+ha|0;if((c[(c[e>>2]|0)+48>>2]|0)!=28){cA(c[r>>2]|0,t,0);h=c[l>>2]|0}h=(tb[c[q>>2]&63](0,k,j,h,16)|0)+f|0;f=c[l>>2]|0;f=h-(tb[c[q>>2]&63](0,k,k+(f<<4)|0,f,16)|0)|0;h=c[qa>>2]|0;break}else if(!(c[(c[s>>2]|0)+(j<<2)>>2]&7)){f=f+-1|0;break}else{f=f+1|0;break}}while(0);m=m+1|0}while((m|0)<(h|0))}t=t+1|0}while((t|0)<((c[ra>>2]|0)+-1|0))}else f=0;f=(f|0)>0&1;ha=100}while(0);do if((ha|0)==100){g=c[ja>>2]|0;if((g|0)<=0)break;m=e+672|0;j=(f|0)==0?12296:1;k=c[ka>>2]|0;l=c[la>>2]|0;h=0;do{f=c[k+(h<<2)>>2]|0;if((a[l+f>>0]&12)==12){c[(c[m>>2]|0)+(f<<2)>>2]=j;g=c[ja>>2]|0}h=h+1|0}while((h|0)<(g|0))}while(0);f=c[E>>2]|0;if(!f)ha=107;else if(!(c[f>>2]|0))ha=107;do if((ha|0)==107){f=c[D>>2]|0;if(!f){if((g|0)<=0)break}else if(!((c[f>>2]|0)==0&(g|0)>0))break;j=c[ka>>2]|0;k=c[e+672>>2]|0;f=g;h=0;do{g=k+(c[j+(h<<2)>>2]<<2)|0;if(!(c[g>>2]&7)){c[g>>2]=1;f=c[ja>>2]|0}h=h+1|0}while((h|0)<(f|0))}while(0);f=c[ra>>2]|0;if((f|0)>0){n=e+540|0;o=e+672|0;p=e+584|0;q=e+588|0;r=e+776|0;s=e+780|0;t=e+584|0;u=e+592|0;v=e+596|0;w=e+600|0;x=e+604|0;y=e+608|0;z=e+612|0;g=c[qa>>2]|0;A=0;do{if((g|0)>0){m=A<<1;l=0;do{h=(_(c[n>>2]|0,A)|0)+l|0;j=c[(c[o>>2]|0)+(h<<2)>>2]|0;f=c[E>>2]|0;if(!f)f=0;else f=(c[f>>2]|0)!=0;k=f?1:2;aa=d[(c[la>>2]|0)+h>>0]|0;if(!((aa&2|0)==0|(aa&8|j&7|0)!=0)){g=c[na>>2]|0;h=(_(g,m)|0)+(l<<1)|0;f=e+656+((f&1^1)<<2)|0;c[p>>2]=b[(c[f>>2]|0)+(h<<2)>>1];c[q>>2]=b[(c[f>>2]|0)+(h<<2)+2>>1];if(!(j&64))f=0;else{aa=h|1;c[u>>2]=b[(c[f>>2]|0)+(aa<<2)>>1];c[v>>2]=b[(c[f>>2]|0)+(aa<<2)+2>>1];$=g+h|0;c[w>>2]=b[(c[f>>2]|0)+($<<2)>>1];c[x>>2]=b[(c[f>>2]|0)+($<<2)+2>>1];aa=g+aa|0;c[y>>2]=b[(c[f>>2]|0)+(aa<<2)>>1];c[z>>2]=b[(c[f>>2]|0)+(aa<<2)+2>>1];f=1}lb[c[r>>2]&1](c[s>>2]|0,0,k,f,t,l,A,0,0);g=c[qa>>2]|0}l=l+1|0}while((l|0)<(g|0))}A=A+1|0;f=c[ra>>2]|0}while((A|0)<(f|0))}b:do if((c[(c[sa>>2]|0)+88>>2]|0)==3){if((f|0)<=0){ha=261;break}m=e+540|0;n=e+672|0;o=e+760|0;p=e+584|0;q=e+588|0;r=e+616|0;s=e+620|0;t=e+776|0;u=e+780|0;v=e+584|0;w=e+716|0;x=e+720|0;f=c[qa>>2]|0;y=0;c:while(1){if((f|0)>0){l=y<<1;k=0;do{j=(_(l,c[na>>2]|0)|0)+(k<<1)|0;fa=(_(c[m>>2]|0,y)|0)+k|0;if(!((a[(c[la>>2]|0)+fa>>0]&10)!=10?1:(c[(c[n>>2]|0)+(fa<<2)>>2]&7|0)!=0)){f=c[E>>2]|0;if(!f)ha=134;else if(!(c[f>>2]|0))ha=134;else g=3;if((ha|0)==134){ha=0;g=2}f=c[D>>2]|0;if(!f)ha=137;else if(!(c[f>>2]|0))ha=137;if((ha|0)==137){ha=0;g=g&1}f=c[o>>2]|0;if(!((f&65535)<<16>>16)){c[p>>2]=0;c[q>>2]=0;c[r>>2]=0;f=0}else{h=f>>>16;f=f&65535;if((c[(c[e>>2]|0)+48>>2]|0)==28)break c;cA(c[w>>2]|0,y,0);ea=c[x>>2]|0;da=b[ea+(j<<2)>>1]|0;c[p>>2]=(_(da,h)|0)/(f|0)|0;ea=b[ea+(j<<2)+2>>1]|0;c[q>>2]=(_(ea,h)|0)/(f|0)|0;fa=h-f|0;c[r>>2]=(_(da,fa)|0)/(f|0)|0;f=(_(ea,fa)|0)/(f|0)|0}c[s>>2]=f;lb[c[t>>2]&1](c[u>>2]|0,0,g,0,v,k,y,0,0);f=c[qa>>2]|0}k=k+1|0}while((k|0)<(f|0))}y=y+1|0;g=c[ra>>2]|0;if((y|0)>=(g|0)){Q=g;break b}}c[F>>2]=86565;c[F+4>>2]=86604;c[F+8>>2]=1184;UC(0,0,104466,F);Ya()}else{X=c[e+560>>2]|0;aa=e+540|0;Y=c[aa>>2]|0;Z=c[qa>>2]|0;g=c[B>>2]|0;do if(g){if(!(c[g>>2]|0))break;$=(c[g+72>>2]|0)+15>>4;f=(f|0)>($|0)?$:f}while(0);g=c[e+712>>2]|0;do if(!g)$=f;else{if(!(c[g>>2]|0)){$=f;break}$=(c[g+72>>2]|0)+15>>4;$=(f|0)>($|0)?$:f}while(0);f=c[e>>2]|0;do if((c[f+48>>2]|0)==28)if(!(c[e+764>>2]|0)){c[C>>2]=86634;c[C+4>>2]=86604;c[C+8>>2]=48;UC(0,0,104466,C);Ya()}else{ca=4;ea=Z<<2;break}else{ca=2;ea=c[na>>2]|0}while(0);V=_($,Z)|0;W=(V|0)>0;if(W){j=e+672|0;k=e+688|0;l=e+696|0;m=e+664|0;n=0;g=0;do{f=c[(c[ka>>2]|0)+(n<<2)>>2]|0;U=(a[(c[la>>2]|0)+f>>0]&8)==0?3:(c[(c[j>>2]|0)+(f<<2)>>2]&7|0)==0?0:3;a[X+f>>0]=U;do if((U|0)==3)g=g+1|0;else{if(!(c[c[B>>2]>>2]|0))break;h=c[k>>2]|0;if(!h)break;U=c[aa>>2]|0;U=_(((f|0)%(U|0)|0)+(_((f|0)/(U|0)|0,ea)|0)|0,ca)|0;T=c[ma>>2]|0;b[T+(U<<2)>>1]=b[h+(U<<2)>>1]|0;b[T+(U<<2)+2>>1]=b[h+(U<<2)+2>>1]|0;U=f<<2;a[(c[m>>2]|0)+U>>0]=a[(c[l>>2]|0)+U>>0]|0}while(0);n=n+1|0}while((n|0)!=(V|0));f=c[e>>2]|0}else g=0;U=($|0)>0;if(!((g|0)>((Z|0)/2|0|0)?(c[f+684>>2]&1|0)!=0:0)){if(!U){ha=261;break}k=e+672|0;l=e+584|0;m=e+588|0;n=e+776|0;o=e+780|0;f=c[qa>>2]|0;p=0;while(1){if((f|0)>0){j=0;do{h=(_(c[aa>>2]|0,p)|0)+j|0;g=c[B>>2]|0;if(!g)g=2;else g=c[g>>2]|0?1:2;do if(!(c[(c[k>>2]|0)+(h<<2)>>2]&7)){if(!(a[(c[la>>2]|0)+h>>0]&8))break;c[l>>2]=0;c[m>>2]=0;lb[c[n>>2]&1](c[o>>2]|0,0,g,0,l,j,p,0,0);f=c[qa>>2]|0}while(0);j=j+1|0}while((j|0)<(f|0))}p=p+1|0;if((p|0)==($|0)){ha=261;break b}}}N=fa+4|0;O=e+664|0;P=_(ea,ca)|0;Q=e+584|0;R=e+588|0;S=e+776|0;T=e+780|0;f=1;g=1;M=0;d:while(1){if(!((M|0)<10&((f|0)!=0|(M|0)<2))){if(g|0){ha=261;break b}if(W)f=0;else{f=1;g=1;M=0;continue}while(1){g=X+(c[(c[ka>>2]|0)+(f<<2)>>2]|0)|0;if(a[g>>0]|0)a[g>>0]=3;f=f+1|0;if((f|0)==(V|0)){f=1;g=1;M=0;continue d}}}if(U){L=(M|0)>1;h=c[qa>>2]|0;f=0;K=0;while(1){if((h|0)>0){F=_(K,ea)|0;G=K^M;H=(K|0)>0;j=K+1|0;I=(j|0)<($|0);J=K<<4;E=0;do{l=c[aa>>2]|0;z=(_(l,K)|0)+E|0;h=fa;k=h+64|0;do{c[h>>2]=0;h=h+4|0}while((h|0)<(k|0));c[da>>2]=0;c[da+4>>2]=0;c[da+8>>2]=0;c[da+12>>2]=0;c[da+16>>2]=0;c[da+20>>2]=0;c[da+24>>2]=0;c[da+28>>2]=0;C=_(E+F|0,ca)|0;e:do if(!((G^E)&1)){D=X+z|0;if((a[D>>0]|0)==3)break;A=(E|0)>0;if(A)h=(a[X+(z+-1)>>0]|0)==3&1;else h=0;B=(E+1|0)<(Z|0);if(B)h=(a[X+(z+1)>>0]|0)==3?1:h;if(H)h=(a[X+(z-Y)>>0]|0)==3?1:h;if(I){if((h|0)==0?(a[X+(z+Y)>>0]|0)!=3:0)break}else if(!h)break;if(A)h=(a[X+(z+-1)>>0]|0)==2&1;else h=0;if(B)h=(a[X+(z+1)>>0]|0)==2?1:h;if(H)h=(a[X+(z-Y)>>0]|0)==2?1:h;if(I)h=(a[X+(z+Y)>>0]|0)==2?1:h;if(L&(h|0)==0)break;do if(A){g=z+-1|0;if(!(a[X+g>>0]|0)){g=0;break}y=C-ca|0;x=c[ma>>2]|0;c[fa>>2]=b[x+(y<<2)>>1];c[N>>2]=b[x+(y<<2)+2>>1];c[da>>2]=a[(c[O>>2]|0)+(g<<2)>>0];g=1}else g=0;while(0);do if(B){h=z+1|0;if(!(a[X+h>>0]|0))break;y=C+ca|0;x=c[ma>>2]|0;c[fa+(g<<3)>>2]=b[x+(y<<2)>>1];c[fa+(g<<3)+4>>2]=b[x+(y<<2)+2>>1];c[da+(g<<2)>>2]=a[(c[O>>2]|0)+(h<<2)>>0];g=g+1|0}while(0);do if(H){if(!(a[X+(z-Y)>>0]|0))break;y=C-P|0;x=c[ma>>2]|0;c[fa+(g<<3)>>2]=b[x+(y<<2)>>1];c[fa+(g<<3)+4>>2]=b[x+(y<<2)+2>>1];c[da+(g<<2)>>2]=a[(c[O>>2]|0)+(z-l<<2)>>0];g=g+1|0}while(0);do if(I){if(!(a[X+(z+Y)>>0]|0)){ha=216;break}y=C+P|0;x=c[ma>>2]|0;c[fa+(g<<3)>>2]=b[x+(y<<2)>>1];c[fa+(g<<3)+4>>2]=b[x+(y<<2)+2>>1];c[da+(g<<2)>>2]=a[(c[O>>2]|0)+(z+l<<2)>>0];g=g+1|0}else ha=216;while(0);if((ha|0)==216){ha=0;if(!g){g=0;break}}f:do if((g|0)>1){h=0;n=0;k=0;l=0;do{k=(c[fa+(h<<3)>>2]|0)+k|0;l=(c[fa+(h<<3)+4>>2]|0)+l|0;m=c[da+(h<<2)>>2]|0;n=m+n|0;if(h|0)if((m|0)!=(c[da+(h+-1<<2)>>2]|0))break f;h=h+1|0}while((h|0)<(g|0));c[fa+(g<<3)>>2]=(k|0)/(h|0)|0;c[fa+(g<<3)+4>>2]=(l|0)/(h|0)|0;c[da+(g<<2)>>2]=(n|0)/(h|0)|0;q=(g|0)>2;o=q?-99999:0;q=q?99999:0;s=0;r=o;h=o;p=q;m=q;do{w=c[fa+(s<<3)>>2]|0;h=(h|0)>(w|0)?h:w;x=c[fa+(s<<3)+4>>2]|0;o=(o|0)>(x|0)?o:x;y=c[da+(s<<2)>>2]|0;r=(r|0)>(y|0)?r:y;m=(m|0)>(w|0)?w:m;q=(q|0)>(x|0)?x:q;p=(p|0)>(y|0)?y:p;s=s+1|0}while((s|0)!=(g|0));s=o;t=p;o=k-h-m|0;k=g+1|0;p=fa+(k<<3)|0;c[p>>2]=o;l=l-s-q|0;m=fa+(k<<3)+4|0;c[m>>2]=l;h=n-r-t|0;k=da+(k<<2)|0;c[k>>2]=h;if((g|0)==4){c[p>>2]=(o|0)/2|0;c[m>>2]=(l|0)/2|0;c[k>>2]=(h|0)/2|0}g=g+2|0}else g=1;while(0);w=g+1|0;o=c[ma>>2]|0;x=b[o+(C<<2)>>1]|0;y=b[o+(C<<2)+2>>1]|0;v=a[(c[O>>2]|0)+(z<<2)>>0]|0;c[fa+(w<<3)>>2]=x;c[fa+(w<<3)+4>>2]=y;c[da+(w<<2)>>2]=v;if((g|0)>-2){s=E<<4;t=X+(z+-1)|0;u=X+(z+1)|0;v=X+(z-Y)|0;r=X+(z+Y)|0;g=0;h=1073741824;q=0;while(1){p=c[ba>>2]|0;n=p+32|0;p=(c[p>>2]|0)+s+(_(J,c[n>>2]|0)|0)|0;k=c[fa+(q<<3)>>2]|0;c[Q>>2]=k;b[o+(C<<2)>>1]=k;k=c[fa+(q<<3)+4>>2]|0;c[R>>2]=k;b[o+(C<<2)+2>>1]=k;k=c[da+(q<<2)>>2]|0;if((k|0)>=0){lb[c[S>>2]&1](c[T>>2]|0,k,1,0,Q,E,K,0,0);do if(A){if(!(a[t>>0]|0)){k=0;break}l=c[n>>2]|0;m=0;k=0;do{z=_(m,l)|0;z=(d[p+(z+-1)>>0]|0)-(d[p+z>>0]|0)|0;k=((z|0)>-1?z:0-z|0)+k|0;m=m+1|0}while((m|0)!=16)}else k=0;while(0);do if(B){if(!(a[u>>0]|0))break;l=c[n>>2]|0;m=0;do{z=_(m,l)|0;z=(d[p+(z+15)>>0]|0)-(d[p+(z+16)>>0]|0)|0;k=((z|0)>-1?z:0-z|0)+k|0;m=m+1|0}while((m|0)!=16)}while(0);do if(H){if(!(a[v>>0]|0))break;l=c[n>>2]|0;m=0;do{z=(d[p+(m-l)>>0]|0)-(d[p+m>>0]|0)|0;k=((z|0)>-1?z:0-z|0)+k|0;m=m+1|0}while((m|0)!=16)}while(0);do if(I){if(!(a[r>>0]|0))break;m=c[n>>2]|0;l=m*15|0;m=m<<4;n=0;do{z=(d[p+(n+l)>>0]|0)-(d[p+(n+m)>>0]|0)|0;k=((z|0)>-1?z:0-z|0)+k|0;n=n+1|0}while((n|0)!=16)}while(0);z=(k|0)>(h|0);g=z?g:q;h=z?h:k}if((q|0)==(w|0))break;o=c[ma>>2]|0;q=q+1|0}o=c[ma>>2]|0}else g=0;h=c[fa+(g<<3)>>2]|0;c[Q>>2]=h;k=c[fa+(g<<3)+4>>2]|0;c[R>>2]=k;h=h&65535;k=k&65535;m=0;do{l=m+C|0;n=0;do{B=l+(_(n,ea)|0)|0;b[o+(B<<2)>>1]=h;b[o+(B<<2)+2>>1]=k;n=n+1|0}while((n|0)<(ca|0));m=m+1|0}while((m|0)<(ca|0));lb[c[S>>2]&1](c[T>>2]|0,c[da+(g<<2)>>2]|0,1,0,Q,E,K,0,0);do if((c[Q>>2]|0)==(x|0)){if((c[R>>2]|0)!=(y|0))break;a[D>>0]=1;g=0;break e}while(0);a[D>>0]=2;f=f+1|0;g=0}while(0);E=E+1|0;h=c[qa>>2]|0}while((E|0)<(h|0))}else j=K+1|0;if((j|0)==($|0))break;else K=j}}else f=0;M=M+1|0}}while(0);if((ha|0)==261)Q=c[ra>>2]|0;if((Q|0)>0){N=e+540|0;D=e+672|0;E=pa+36|0;F=pa+40|0;P=e+564|0;G=e+568|0;O=e+572|0;M=0;do{f=c[qa>>2]|0;if((f|0)>0){H=M<<4;I=M<<3;C=c[D>>2]|0;J=_(c[N>>2]|0,M)|0;L=0;do{K=J+L|0;if(!(c[C+(K<<2)>>2]&7))ha=273;else if(!(c[ga>>2]|0))ha=273;do if((ha|0)==273){ha=0;B=c[sa>>2]|0;s=c[oa>>2]|0;u=(c[B>>2]|0)+(L<<4)+(_(H,s)|0)|0;v=c[B+4>>2]|0;z=L<<3;w=_(c[E>>2]|0,I)|0;B=c[B+8>>2]|0;A=_(c[F>>2]|0,I)|0;x=c[na>>2]|0;y=(_(x,M)|0)+L<<1;y=(c[P>>2]|0)+(y<<1)|0;t=0;while(1){h=t<<3&8;j=t>>>1<<3;k=h|1;l=h|2;m=h|3;n=h|4;o=h|5;p=h|6;q=h|7;g=0;r=0;do{fa=_(s,r+j|0)|0;g=(d[u+(q+fa)>>0]|0)+((d[u+(p+fa)>>0]|0)+((d[u+(o+fa)>>0]|0)+((d[u+(n+fa)>>0]|0)+((d[u+(m+fa)>>0]|0)+((d[u+(l+fa)>>0]|0)+((d[u+(k+fa)>>0]|0)+((d[u+(h+fa)>>0]|0)+g)))))))|0;r=r+1|0}while((r|0)!=8);b[y+((_(x,t>>1)|0)+(t&1)<<1)>>1]=(g+4|0)>>>3;g=t+1|0;if((g|0)==4)break;s=c[oa>>2]|0;t=g}n=v+z+w|0;m=B+z+A|0;if(!B)break;j=c[E>>2]|0;k=c[F>>2]|0;h=0;g=0;l=0;do{ea=_(j,l)|0;fa=_(k,l)|0;h=(d[n+(ea+7)>>0]|0)+((d[n+(ea+6)>>0]|0)+((d[n+(ea+5)>>0]|0)+((d[n+(ea+4)>>0]|0)+((d[n+(ea+3)>>0]|0)+((d[n+(ea+2)>>0]|0)+((d[n+(ea+1)>>0]|0)+((d[n+ea>>0]|0)+h)))))))|0;g=(d[m+(fa+7)>>0]|0)+((d[m+(fa+6)>>0]|0)+((d[m+(fa+5)>>0]|0)+((d[m+(fa+4)>>0]|0)+((d[m+(fa+3)>>0]|0)+((d[m+(fa+2)>>0]|0)+((d[m+(fa+1)>>0]|0)+((d[m+fa>>0]|0)+g)))))))|0;l=l+1|0}while((l|0)!=8);b[(c[G>>2]|0)+(K<<1)>>1]=(h+4|0)>>>3;b[(c[O>>2]|0)+(K<<1)>>1]=(g+4|0)>>>3}while(0);L=L+1|0}while((L|0)<(f|0))}M=M+1|0}while((M|0)<(Q|0))}else{G=e+568|0;N=e+540|0;O=e+572|0;P=e+564|0;f=c[qa>>2]|0}rA(e,c[P>>2]|0,f<<1,Q<<1,c[na>>2]|0,1);rA(e,c[G>>2]|0,c[qa>>2]|0,c[ra>>2]|0,c[N>>2]|0,0);rA(e,c[O>>2]|0,c[qa>>2]|0,c[ra>>2]|0,c[N>>2]|0,0);m=c[P>>2]|0;f=c[qa>>2]|0;g=c[ra>>2]|0;n=c[na>>2]|0;o=(g<<1)+-1|0;p=(f<<1)+-1|0;do if((p|0)>1&(o|0)>1){l=1;do{h=_(l,n)|0;j=b[m+(h<<1)>>1]|0;k=1;do{ha=m+(k+h<<1)|0;ga=j;j=b[ha>>1]|0;k=k+1|0;b[ha>>1]=((((j<<3)-ga-(b[m+(k+h<<1)>>1]|0)|0)*10923|0)+32768|0)>>>16}while((k|0)!=(p|0));l=l+1|0}while((l|0)!=(o|0));if((p|0)<2)break;else j=1;do{h=b[m+(j<<1)>>1]|0;k=1;do{ha=m+((_(k,n)|0)+j<<1)|0;ga=h;h=b[ha>>1]|0;k=k+1|0;b[ha>>1]=((((h<<3)-ga-(b[m+((_(k,n)|0)+j<<1)>>1]|0)|0)*10923|0)+32768|0)>>>16}while((k|0)!=(o|0));j=j+1|0}while((j|0)!=(p|0))}while(0);if((g|0)>0){H=e+672|0;I=pa+36|0;J=pa+40|0;F=0;do{if((f|0)>0){B=F<<4;C=F<<3;D=F<<1;E=0;do{f=(_(c[N>>2]|0,F)|0)+E|0;do if(!(c[(c[H>>2]|0)+(f<<2)>>2]&120)){if(!(a[(c[la>>2]|0)+f>>0]&2))break;z=c[sa>>2]|0;r=(c[z>>2]|0)+(E<<4)+(_(B,c[oa>>2]|0)|0)|0;s=E<<3;A=(c[z+4>>2]|0)+s+(_(c[I>>2]|0,C)|0)|0;t=c[z+8>>2]|0;u=_(c[J>>2]|0,C)|0;v=(t|0)==0;A=v?0:A;w=z+32|0;x=E<<1;y=0;do{g=y&1;h=y>>1;f=(_(h+D|0,c[na>>2]|0)|0)+(g|x)|0;f=b[(c[P>>2]|0)+(f<<1)>>1]|0;if(f<<16>>16<0)f=0;else f=(f<<16>>16<2040?f:2040)<<16>>16;q=((f|0)/8|0)&255;p=g<<3;f=h<<3;g=p|1;h=p|2;j=p|3;k=p|4;l=p|5;m=p|6;n=p|7;o=0;do{ha=o+f|0;a[r+((_(c[w>>2]|0,ha)|0)+p)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+g)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+h)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+j)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+k)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+l)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+m)>>0]=q;a[r+((_(c[w>>2]|0,ha)|0)+n)>>0]=q;o=o+1|0}while((o|0)!=8);y=y+1|0}while((y|0)!=4);l=v?0:t+s+u|0;g=(_(c[N>>2]|0,F)|0)+E|0;f=b[(c[G>>2]|0)+(g<<1)>>1]|0;g=b[(c[O>>2]|0)+(g<<1)>>1]|0;if(f<<16>>16<0)k=0;else k=(((f<<16>>16<2040?f:2040)<<16>>16|0)/8|0)&255;if(!l)break;j=z+36|0;h=g<<16>>16<0?0:(((g<<16>>16<2040?g:2040)<<16>>16|0)/8|0)&255;f=z+40|0;g=0;do{a[A+(_(c[j>>2]|0,g)|0)>>0]=k;a[l+(_(c[f>>2]|0,g)|0)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+1)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+1)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+2)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+2)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+3)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+3)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+4)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+4)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+5)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+5)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+6)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+6)>>0]=h;a[A+((_(c[j>>2]|0,g)|0)+7)>>0]=k;a[l+((_(c[f>>2]|0,g)|0)+7)>>0]=h;g=g+1|0}while((g|0)!=8)}while(0);E=E+1|0;f=c[qa>>2]|0}while((E|0)<(f|0));g=c[ra>>2]|0}F=F+1|0}while((F|0)<(g|0))}do if(c[(c[e>>2]|0)+684>>2]&2|0){sA(e,c[c[sa>>2]>>2]|0,f<<1,g<<1,c[oa>>2]|0,1);tA(e,c[c[sa>>2]>>2]|0,c[qa>>2]<<1,c[ra>>2]<<1,c[oa>>2]|0,1);f=c[sa>>2]|0;if(!(c[f+8>>2]|0))break;oa=pa+36|0;sA(e,c[f+4>>2]|0,c[qa>>2]|0,c[ra>>2]|0,c[oa>>2]|0,0);pa=pa+40|0;sA(e,c[(c[sa>>2]|0)+8>>2]|0,c[qa>>2]|0,c[ra>>2]|0,c[pa>>2]|0,0);tA(e,c[(c[sa>>2]|0)+4>>2]|0,c[qa>>2]|0,c[ra>>2]|0,c[oa>>2]|0,0);tA(e,c[(c[sa>>2]|0)+8>>2]|0,c[qa>>2]|0,c[ra>>2]|0,c[pa>>2]|0,0)}while(0);if((c[ja>>2]|0)>0){g=e+580|0;j=0;do{h=c[(c[ka>>2]|0)+(j<<2)>>2]|0;f=c[ia>>2]|0;do if(f|0){if((a[(c[la>>2]|0)+h>>0]&14)==0?1:(c[(c[sa>>2]|0)+88>>2]|0)==3)break;a[f+h>>0]=0}while(0);f=c[g>>2]|0;if(f|0)a[f+h>>0]=1;j=j+1|0}while((j|0)<(c[ja>>2]|0))}NB(e+744|0);NB(e+752|0);c[e+664>>2]=0;c[ma>>2]=0;NB(e+748|0);NB(e+756|0);h=sa;k=h+96|0;do{c[h>>2]=0;h=h+4|0}while((h|0)<(k|0));i=ta;return}function rA(d,e,f,g,h,j){d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0;D=i;i=i+16|0;l=D;A=D+8|0;B=D+4|0;k=g<<3;if(!k)y=0;else if((2147483647/(k>>>0)|0)>>>0>h>>>0)y=YC(_(k,h)|0)|0;else y=0;c[A>>2]=y;k=g<<4;if(!k)z=6;else if((2147483647/(k>>>0)|0)>>>0>h>>>0){w=YC(_(k,h)|0)|0;c[B>>2]=w;if((w|0)!=0&(y|0)!=0){x=(g|0)>0;a:do if(x){r=d+540|0;s=d+556|0;if((f|0)<=0){k=0;while(1){k=k+1|0;if((k|0)==(g|0))break a}}q=c[d+672>>2]|0;v=0;do{t=v>>j;u=_(v,h)|0;o=0;n=1024;l=-1;while(1){k=(_(c[r>>2]|0,t)|0)+(o>>j)|0;if(!(c[q+(k<<2)>>2]&7))z=20;else if(!(a[(c[s>>2]|0)+k>>0]&4))z=20;else{m=o+u|0;k=n}if((z|0)==20){z=0;k=o+u|0;m=k;k=b[e+(k<<1)>>1]|0;l=o}b[y+(m<<3)+2>>1]=k;c[w+(m<<4)+4>>2]=(l|0)>-1?o-l|0:9999;o=o+1|0;if((o|0)==(f|0)){o=f;n=1024;l=-1;break}else n=k}while(1){p=o;o=o+-1|0;k=(_(c[r>>2]|0,t)|0)+(o>>j)|0;if(!(c[q+(k<<2)>>2]&7))z=15;else if(!(a[(c[s>>2]|0)+k>>0]&4))z=15;else{m=o+u|0;k=n}if((z|0)==15){z=0;k=o+u|0;m=k;k=b[e+(k<<1)>>1]|0;l=o}b[y+(m<<3)>>1]=k;c[w+(m<<4)>>2]=(l|0)>-1?l-o|0:9999;if((p|0)<=1)break;else n=k}v=v+1|0}while((v|0)!=(g|0))}while(0);v=(f|0)>0;b:do if(v){r=d+540|0;s=d+556|0;if(!x){k=0;while(1){k=k+1|0;if((k|0)==(f|0))break b}}q=c[d+672>>2]|0;u=0;do{t=u>>j;o=0;n=1024;l=-1;while(1){k=(_(c[r>>2]|0,o>>j)|0)+t|0;if(!(c[q+(k<<2)>>2]&7))z=36;else if(!(a[(c[s>>2]|0)+k>>0]&4))z=36;else{m=(_(o,h)|0)+u|0;k=n}if((z|0)==36){z=0;k=(_(o,h)|0)+u|0;m=k;k=b[e+(k<<1)>>1]|0;l=o}b[y+(m<<3)+6>>1]=k;c[w+(m<<4)+12>>2]=(l|0)>-1?o-l|0:9999;o=o+1|0;if((o|0)==(g|0)){o=g;n=1024;l=-1;break}else n=k}while(1){p=o;o=o+-1|0;k=(_(c[r>>2]|0,o>>j)|0)+t|0;if(!(c[q+(k<<2)>>2]&7))z=31;else if(!(a[(c[s>>2]|0)+k>>0]&4))z=31;else{m=(_(o,h)|0)+u|0;k=n}if((z|0)==31){z=0;k=(_(o,h)|0)+u|0;m=k;k=b[e+(k<<1)>>1]|0;l=o}b[y+(m<<3)+4>>1]=k;c[w+(m<<4)+8>>2]=(l|0)>-1?l-o|0:9999;if((p|0)<=1)break;else n=k}u=u+1|0}while((u|0)!=(f|0))}while(0);if(!x){bD(A);bD(B);i=D;return}r=d+556|0;if(!v){bD(A);bD(B);i=D;return}q=c[d+540>>2]|0;k=c[d+672>>2]|0;p=0;do{n=_(p,h)|0;m=_(q,p>>j)|0;o=0;do{l=m+(o>>j)|0;if(!(c[k+(l<<2)>>2]&120))if(a[(c[r>>2]|0)+l>>0]&4){d=o+n|0;v=c[B>>2]|0;u=c[A>>2]|0;y=c[v+(d<<4)>>2]|0;y=268435456/((y>>>0>1?y:1)>>>0)|0;x=b[u+(d<<3)>>1]|0;x=RF(x|0,((x|0)<0)<<31>>31|0,y|0,0)|0;w=C;z=c[v+(d<<4)+4>>2]|0;z=268435456/((z>>>0>1?z:1)>>>0)|0;t=b[u+(d<<3)+2>>1]|0;t=RF(t|0,((t|0)<0)<<31>>31|0,z|0,0)|0;w=HF(t|0,C|0,x|0,w|0)|0;x=C;y=HF(z|0,0,y|0,0)|0;z=C;t=c[v+(d<<4)+8>>2]|0;t=268435456/((t>>>0>1?t:1)>>>0)|0;s=b[u+(d<<3)+4>>1]|0;s=RF(s|0,((s|0)<0)<<31>>31|0,t|0,0)|0;x=HF(s|0,C|0,w|0,x|0)|0;w=C;z=HF(t|0,0,y|0,z|0)|0;y=C;v=c[v+(d<<4)+12>>2]|0;v=268435456/((v>>>0>1?v:1)>>>0)|0;u=b[u+(d<<3)+6>>1]|0;u=RF(u|0,((u|0)<0)<<31>>31|0,v|0,0)|0;w=HF(u|0,C|0,x|0,w|0)|0;x=C;y=HF(v|0,0,z|0,y|0)|0;z=C;v=JF(y|0,z|0,1)|0;x=HF(v|0,C|0,w|0,x|0)|0;z=PF(x|0,C|0,y|0,z|0)|0;b[e+(d<<1)>>1]=z}o=o+1|0}while((o|0)!=(f|0));p=p+1|0}while((p|0)!=(g|0));bD(A);bD(B);i=D;return}}else z=6;if((z|0)==6)c[B>>2]=0;UC(c[d>>2]|0,16,86652,l);bD(A);bD(B);i=D;return}function sA(e,f,g,h,j,k){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0;P=i;i=i+16|0;m=P;do if((c[(c[e>>2]|0)+48>>2]|0)==28)if(!(c[e+764>>2]|0)){c[m>>2]=86634;c[m+4>>2]=86604;c[m+8>>2]=48;UC(0,0,104466,m);Ya()}else{l=4;n=c[e+532>>2]<<2;break}else{l=2;n=c[e+544>>2]|0}while(0);O=l>>>k;M=_(n,O)|0;if((h|0)<=0){i=P;return}N=g+-1|0;I=(g|0)>1;J=e+540|0;K=e+556|0;L=e+672|0;A=e+656|0;H=0;do{if(I){B=H>>k;C=_(H,j)|0;D=_(M,H)|0;G=0;do{m=_(c[J>>2]|0,B)|0;l=m+(G>>k)|0;y=c[K>>2]|0;z=a[y+l>>0]|0;g=G;G=G+1|0;m=m+(G>>k)|0;y=a[y+m>>0]|0;n=c[L>>2]|0;r=g+C<<3;g=(_(g,O)|0)+D|0;e=c[A>>2]|0;o=e+(g<<2)|0;p=(_(G,O)|0)+D|0;q=e+(p<<2)|0;E=(z&14)!=0;F=(y&14)!=0;do if((y|z)&14){if(!((c[n+(l<<2)>>2]|c[n+(m<<2)>>2])&7)){z=(b[o>>1]|0)-(b[q>>1]|0)|0;y=(b[e+(p<<2)+2>>1]|0)+(b[e+(g<<2)+2>>1]|0)|0;if((((y|0)>-1?y:0-y|0)+((z|0)>-1?z:0-z|0)|0)<2)break}s=r|7;t=r|6;u=r+8|0;v=r+9|0;z=E&F;w=r|5;x=r|4;y=r+10|0;n=r+11|0;r=0;do{g=_(r,j)|0;e=f+(g+s)|0;o=d[e>>0]|0;p=f+(g+t)|0;R=o-(d[p>>0]|0)|0;q=f+(u+g)|0;l=d[q>>0]|0;Q=l-o|0;m=f+(v+g)|0;l=(d[m>>0]|0)-l|0;l=((Q|0)>-1?Q:0-Q|0)-(((R|0)>-1?R:0-R|0)+1+((l|0)>-1?l:0-l|0)>>1)|0;l=(l|0)>0?l:0;l=(Q|0)<0?0-l|0:l;if(l|0){if(!z)l=(l<<4|0)/9|0;if(E){a[e>>0]=a[70232+(o+(l*7>>4))>>0]|0;a[p>>0]=a[70232+((d[p>>0]|0)+(l*5>>4))>>0]|0;R=f+(g+w)|0;a[R>>0]=a[70232+((d[R>>0]|0)+(l*3>>4))>>0]|0;R=f+(g+x)|0;a[R>>0]=a[70232+((d[R>>0]|0)+(l>>4))>>0]|0}if(F){a[q>>0]=a[70232+((d[q>>0]|0)-(l*7>>4))>>0]|0;a[m>>0]=a[70232+((d[m>>0]|0)-(l*5>>4))>>0]|0;R=f+(y+g)|0;a[R>>0]=a[70232+((d[R>>0]|0)-(l*3>>4))>>0]|0;R=f+(n+g)|0;a[R>>0]=a[70232+((d[R>>0]|0)-(l>>4))>>0]|0}}r=r+1|0}while((r|0)!=8)}while(0)}while((G|0)!=(N|0))}H=H+1|0}while((H|0)!=(h|0));i=P;return}function tA(e,f,g,h,j,k){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;S=i;i=i+16|0;m=S;do if((c[(c[e>>2]|0)+48>>2]|0)==28)if(!(c[e+764>>2]|0)){c[m>>2]=86634;c[m+4>>2]=86604;c[m+8>>2]=48;UC(0,0,104466,m);Ya()}else{l=4;n=c[e+532>>2]<<2;break}else{l=2;n=c[e+544>>2]|0}while(0);R=l>>>k;M=_(n,R)|0;if((h|0)<=1){i=S;return}N=(g|0)>0;O=e+540|0;P=e+556|0;Q=e+672|0;D=e+656|0;E=j*7|0;F=j*6|0;G=j<<3;H=j*9|0;I=j*5|0;J=j<<2;K=j*10|0;L=j*11|0;C=h+-1|0;m=0;while(1){if(N){z=m>>k;l=m+1|0;A=l>>k;B=_(m,j)|0;t=_(m,M)|0;u=_(l,M)|0;y=0;do{n=y>>k;e=c[O>>2]|0;m=(_(e,z)|0)+n|0;T=c[P>>2]|0;s=a[T+m>>0]|0;n=(_(e,A)|0)+n|0;T=a[T+n>>0]|0;e=c[Q>>2]|0;v=y+B<<3;q=_(y,R)|0;h=q+t|0;o=c[D>>2]|0;p=o+(h<<2)|0;q=q+u|0;r=o+(q<<2)|0;w=(s&14)!=0;x=(T&14)!=0;do if((T|s)&14){if(!((c[e+(m<<2)>>2]|c[e+(n<<2)>>2])&7)){T=(b[p>>1]|0)-(b[r>>1]|0)|0;s=(b[o+(q<<2)+2>>1]|0)+(b[o+(h<<2)+2>>1]|0)|0;if((((s|0)>-1?s:0-s|0)+((T|0)>-1?T:0-T|0)|0)<2)break}r=w&x;s=0;do{h=s+v|0;o=f+(h+E)|0;p=d[o>>0]|0;q=f+(h+F)|0;U=p-(d[q>>0]|0)|0;n=f+(h+G)|0;m=d[n>>0]|0;T=m-p|0;e=f+(h+H)|0;m=(d[e>>0]|0)-m|0;m=((T|0)>-1?T:0-T|0)-(((U|0)>-1?U:0-U|0)+1+((m|0)>-1?m:0-m|0)>>1)|0;m=(m|0)>0?m:0;m=(T|0)<0?0-m|0:m;if(m|0){if(!r)m=(m<<4|0)/9|0;if(w){a[o>>0]=a[70232+(p+(m*7>>4))>>0]|0;a[q>>0]=a[70232+((d[q>>0]|0)+(m*5>>4))>>0]|0;U=f+(h+I)|0;a[U>>0]=a[70232+((d[U>>0]|0)+(m*3>>4))>>0]|0;U=f+(h+J)|0;a[U>>0]=a[70232+((d[U>>0]|0)+(m>>4))>>0]|0}if(x){a[n>>0]=a[70232+((d[n>>0]|0)-(m*7>>4))>>0]|0;a[e>>0]=a[70232+((d[e>>0]|0)-(m*5>>4))>>0]|0;U=f+(h+K)|0;a[U>>0]=a[70232+((d[U>>0]|0)-(m*3>>4))>>0]|0;U=f+(h+L)|0;a[U>>0]=a[70232+((d[U>>0]|0)-(m>>4))>>0]|0}}s=s+1|0}while((s|0)!=8)}while(0);y=y+1|0}while((y|0)!=(g|0))}else l=m+1|0;if((l|0)==(C|0))break;else m=l}i=S;return}function uA(b,d){b=b|0;d=d|0;var e=0,f=0,g=0;b=(c[d+824>>2]|0)+48+(_(c[b+27844>>2]|0,-6)|0)|0;b=(b|0)<0?0:(b|0)>51?51:b;if((c[d+816>>2]|0)==1)f=94361;else f=88217+(c[d+49752>>2]<<11)|0;e=0;do{g=(_(a[f+(e<<1)>>0]|0,b)|0)>>4;g=(g+(a[f+(e<<1)+1>>0]|0)<<1)+-127|0;g=g>>31^g;a[d+48728+e>>0]=(g|0)>124?g&1|124:g;e=e+1|0}while((e|0)!=1024);return}function vA(f,h){f=f|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,u=0,v=0,w=0,x=0,y=0,z=0,C=0,D=0;y=0;D=i;i=i+5440|0;r=c[f+27928>>2]|0;s=((c[(f+26672|0)>>2]|0)+-1|0)>>>0<2;z=c[f+25960>>2]|0;k=c[(h+21764|0)>>2]|0;j=c[(h+21768|0)>>2]|0;C=(_(c[(f+198236|0)>>2]|0,j)|0)+k|0;c[(h+21772|0)>>2]=C;if((c[(h+816|0)>>2]|0)!=1){if((c[(f+197608|0)>>2]|0)==0|(j&1|0)==0)y=5;else if(!(c[h+21624>>2]|0))y=5;else m=c[h+21628>>2]|0;if((y|0)==5)m=wA(f,h,k,j)|0;if(m|0){if(c[(f+197608|0)>>2]|0)if(!(c[(h+21768|0)>>2]&1)){c[(c[(f+24668|0)>>2]|0)+(C<<2)>>2]=2048;z=wA(f,h,c[(h+21764|0)>>2]|0,(c[(h+21768|0)>>2]|0)+1|0)|0;c[h+21628>>2]=z;if(!z){z=(c[(h+21772|0)>>2]|0)-(c[(f+198236|0)>>2]<<1)|0;z=xA(h+48680|0,h+48798+(((e[(c[f+197604>>2]|0)+(z<<1)>>1]|0)==(c[h+808>>2]|0)&(c[(c[(f+24668|0)>>2]|0)+(z<<2)>>2]|0)>>>7)+((c[(h+21764|0)>>2]|0)!=0&c[(h+21796|0)>>2]))|0)|0;c[(h+21796|0)>>2]=z;c[h+21800>>2]=z}}s=c[(h+21772|0)>>2]|0;c[(D+56|0)>>2]=0;k=(c[f+26256>>2]|0)+(s*48|0)|0;m=k+48|0;do{a[k>>0]=0;k=k+1|0}while((k|0)<(m|0));if(!(c[(h+21796|0)>>2]|0))j=0;else{c[(D+56|0)>>2]=128;j=128}if((c[(h+816|0)>>2]|0)==3){j=j|63744;c[(D+56|0)>>2]=j;if(c[h+21812>>2]|0){yA(f,h,j);zA(f,h,j)}rc(f,h,D+56|0);m=c[(D+56|0)>>2]|2048;c[(D+56|0)>>2]=m;j=f+26652|0}else{m=j|14344;c[(D+56|0)>>2]=m;yA(f,h,m);o=c[f+24688>>2]|0;p=c[f+24656>>2]|0;q=c[(f+26652|0)>>2]|0;c[h+44108>>2]=0;c[h+44116>>2]=0;c[h+44124>>2]=0;c[h+44132>>2]=0;l=c[h+21716>>2]|0;if(!(l&12288))if(!l)y=79;else{x=108160;w=-1;y=26}else{k=c[h+21696>>2]|0;z=d[c[h+21724>>2]>>0]|0;j=a[o+((z&254)+(k<<2|1))>>0]|0;k=p+((_(z,q)|0)+3+(c[(c[f+26644>>2]|0)+(k<<2)>>2]|0)<<2)|0;do if(c[(f+197608|0)>>2]|0)if(!(c[(h+21796|0)>>2]|0)){if(!(l&128))break;k=c[k>>2]|0;c[(D+64|0)>>2]=k;b[(D+64|0)+2>>1]=k>>16<<1;k=D+64|0;j=j>>1;break}else{if(l&128)break;k=c[k>>2]|0;c[(D+64|0)>>2]=k;b[(D+64|0)+2>>1]=(k>>16|0)/2|0;k=D+64|0;j=j<<1;break}while(0);if(!(c[k>>2]|j))y=79;else{x=k;w=j;y=26}}do if((y|0)==26){j=c[h+21708>>2]|0;if(!(j&12288))if(!j){y=79;break}else{r=108160;n=-1}else{k=c[h+21688>>2]|0;l=a[o+(k<<2|2)>>0]|0;k=p+((c[(c[f+26644>>2]|0)+(k<<2)>>2]|0)+(q*3|0)<<2)|0;do if(!(c[(f+197608|0)>>2]|0))j=l;else if(!(c[(h+21796|0)>>2]|0)){if(!(j&128)){j=l;break}k=c[k>>2]|0;c[((D+64|0)+4|0)>>2]=k;b[(D+64|0)+6>>1]=k>>16<<1;k=(D+64|0)+4|0;j=l>>1;break}else{if(j&128){j=l;break}k=c[k>>2]|0;c[((D+64|0)+4|0)>>2]=k;b[(D+64|0)+6>>1]=(k>>16|0)/2|0;k=(D+64|0)+4|0;j=l<<1;break}while(0);if(!(c[k>>2]|j)){y=79;break}else{r=k;n=j}}k=c[h+21712>>2]|0;do if(!(k&12288))if(!k){l=c[h+21704>>2]|0;if(!(l&12288)){o=108160;j=(l|0)==0?-2:-1;break}j=c[h+21684>>2]|0;z=c[h+21728>>2]|0;k=a[o+(j<<2|z&2|1)>>0]|0;j=p+(q+3+(z&q<<1)+(c[(c[f+26644>>2]|0)+(j<<2)>>2]|0)<<2)|0;if(!(c[(f+197608|0)>>2]|0)){o=j;j=k}else if(!(c[(h+21796|0)>>2]|0)){if(!(l&128)){o=j;j=k;break}o=c[j>>2]|0;c[((D+64|0)+8|0)>>2]=o;b[(D+64|0)+10>>1]=o>>16<<1;o=(D+64|0)+8|0;j=k>>1;break}else{if(l&128){o=j;j=k;break}o=c[j>>2]|0;c[((D+64|0)+8|0)>>2]=o;b[(D+64|0)+10>>1]=(o>>16|0)/2|0;o=(D+64|0)+8|0;j=k<<1;break}}else{o=108160;j=-1}else{j=c[h+21692>>2]|0;l=a[o+(j<<2|2)>>0]|0;j=p+((c[(c[f+26644>>2]|0)+(j<<2)>>2]|0)+(q*3|0)<<2)|0;if(!(c[(f+197608|0)>>2]|0)){o=j;j=l}else if(!(c[(h+21796|0)>>2]|0)){if(!(k&128)){o=j;j=l;break}o=c[j>>2]|0;c[((D+64|0)+8|0)>>2]=o;b[(D+64|0)+10>>1]=o>>16<<1;o=(D+64|0)+8|0;j=l>>1;break}else{if(k&128){o=j;j=l;break}o=c[j>>2]|0;c[((D+64|0)+8|0)>>2]=o;b[(D+64|0)+10>>1]=(o>>16|0)/2|0;o=(D+64|0)+8|0;j=l<<1;break}}while(0);l=(n|0)!=0;k=(w|0)!=0;j=(l&1^1)+(k&1^1)+((j|0)==0&1)|0;do if(j>>>0>1){j=b[x>>1]|0;k=b[r>>1]|0;l=b[o>>1]|0;if(j<<16>>16>k<<16>>16)if(l<<16>>16>k<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:l<<16>>16;else l=k<<16>>16;else if(k<<16>>16>l<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?l<<16>>16:j<<16>>16;else l=k<<16>>16;n=b[x+2>>1]|0;k=b[r+2>>1]|0;j=b[o+2>>1]|0;if(n<<16>>16>k<<16>>16){if(j<<16>>16<=k<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?n<<16>>16:j<<16>>16;break}else{if(k<<16>>16<=j<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?j<<16>>16:n<<16>>16;break}}else{if((j|0)==1){if(!k){l=b[x>>1]|0;j=b[x+2>>1]|0;break}if(l){l=b[o>>1]|0;j=b[o+2>>1]|0;break}else{l=b[r>>1]|0;j=b[r+2>>1]|0;break}}j=b[x>>1]|0;k=b[r>>1]|0;l=b[o>>1]|0;if(j<<16>>16>k<<16>>16)if(l<<16>>16>k<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:l<<16>>16;else l=k<<16>>16;else if(k<<16>>16>l<<16>>16)l=(l<<16>>16|0)>(j<<16>>16|0)?l<<16>>16:j<<16>>16;else l=k<<16>>16;n=b[x+2>>1]|0;k=b[r+2>>1]|0;j=b[o+2>>1]|0;if(n<<16>>16>k<<16>>16){if(j<<16>>16<=k<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?n<<16>>16:j<<16>>16;break}else{if(k<<16>>16<=j<<16>>16){j=k<<16>>16;break}j=(j<<16>>16|0)>(n<<16>>16|0)?j<<16>>16:n<<16>>16;break}}while(0);z=j<<16|l&65535;c[h+43824>>2]=z;c[h+43828>>2]=z;c[h+43832>>2]=z;c[h+43836>>2]=z;c[h+43856>>2]=z;c[h+43860>>2]=z;c[h+43864>>2]=z;c[h+43868>>2]=z;c[h+43888>>2]=z;c[h+43892>>2]=z;c[h+43896>>2]=z;c[h+43900>>2]=z;c[h+43920>>2]=z;c[h+43924>>2]=z;c[h+43928>>2]=z;c[h+43932>>2]=z}while(0);if((y|0)==79){c[(h+43824|0)>>2]=0;c[(h+43824|0)+4>>2]=0;c[(h+43824|0)+8>>2]=0;c[(h+43824|0)+12>>2]=0;c[(h+43856|0)>>2]=0;c[(h+43856|0)+4>>2]=0;c[(h+43856|0)+8>>2]=0;c[(h+43856|0)+12>>2]=0;c[(h+43888|0)>>2]=0;c[(h+43888|0)+4>>2]=0;c[(h+43888|0)+8>>2]=0;c[(h+43888|0)+12>>2]=0;c[(h+43920|0)>>2]=0;c[(h+43920|0)+4>>2]=0;c[(h+43920|0)+8>>2]=0;c[(h+43920|0)+12>>2]=0}j=f+26652|0}j=c[j>>2]|0;k=(_(j<<2,c[(h+21768|0)>>2]|0)|0)+(c[(h+21764|0)>>2]<<2)|0;l=c[(h+21772|0)>>2]<<2;if(!(m&12288)){z=(c[f+24688>>2]|0)+l|0;b[z>>1]=-1;b[z+2>>1]=-1}else{z=(c[f+24656>>2]|0)+(k<<2)|0;y=c[(h+43824|0)+4>>2]|0;c[z>>2]=c[(h+43824|0)>>2];c[z+4>>2]=y;y=c[(h+43832|0)+4>>2]|0;c[(z+8|0)>>2]=c[(h+43832|0)>>2];c[(z+8|0)+4>>2]=y;y=c[(h+43856|0)+4>>2]|0;c[(z+(j<<2)|0)>>2]=c[(h+43856|0)>>2];c[(z+(j<<2)|0)+4>>2]=y;y=c[(h+43864|0)+4>>2]|0;c[((z+(j<<2)|0)+8|0)>>2]=c[(h+43864|0)>>2];c[((z+(j<<2)|0)+8|0)+4>>2]=y;y=c[(h+43888|0)+4>>2]|0;c[(z+(j<<1<<2)|0)>>2]=c[(h+43888|0)>>2];c[(z+(j<<1<<2)|0)+4>>2]=y;y=c[(h+43896|0)+4>>2]|0;c[((z+(j<<1<<2)|0)+8|0)>>2]=c[(h+43896|0)>>2];c[((z+(j<<1<<2)|0)+8|0)+4>>2]=y;y=c[(h+43920|0)+4>>2]|0;c[(z+(j*3<<2)|0)>>2]=c[(h+43920|0)>>2];c[(z+(j*3<<2)|0)+4>>2]=y;y=c[(h+43928|0)+4>>2]|0;c[((z+(j*3<<2)|0)+8|0)>>2]=c[(h+43928|0)>>2];c[((z+(j*3<<2)|0)+8|0)+4>>2]=y;z=(c[h+48672>>2]|0)+(c[(c[f+26648>>2]|0)+(c[(h+21772|0)>>2]<<2)>>2]<<1)|0;c[z>>2]=0;c[z+4>>2]=0;c[z+8>>2]=0;c[z+12>>2]=0;z=(c[f+24688>>2]|0)+l|0;a[z>>0]=a[h+44108>>0]|0;a[z+1>>0]=a[h+44110>>0]|0;a[z+2>>0]=a[h+44124>>0]|0;a[z+3>>0]=a[h+44126>>0]|0}if(m&49152|0){z=(c[f+24660>>2]|0)+(k<<2)|0;y=c[(h+43984|0)+4>>2]|0;c[z>>2]=c[(h+43984|0)>>2];c[z+4>>2]=y;y=c[(h+43992|0)+4>>2]|0;c[(z+8|0)>>2]=c[(h+43992|0)>>2];c[(z+8|0)+4>>2]=y;y=c[(h+44016|0)+4>>2]|0;c[(z+(j<<2)|0)>>2]=c[(h+44016|0)>>2];c[(z+(j<<2)|0)+4>>2]=y;y=c[(h+44024|0)+4>>2]|0;c[((z+(j<<2)|0)+8|0)>>2]=c[(h+44024|0)>>2];c[((z+(j<<2)|0)+8|0)+4>>2]=y;y=c[(h+44048|0)+4>>2]|0;c[(z+(j<<1<<2)|0)>>2]=c[(h+44048|0)>>2];c[(z+(j<<1<<2)|0)+4>>2]=y;y=c[(h+44056|0)+4>>2]|0;c[((z+(j<<1<<2)|0)+8|0)>>2]=c[(h+44056|0)>>2];c[((z+(j<<1<<2)|0)+8|0)+4>>2]=y;y=c[(h+44080|0)+4>>2]|0;c[(z+(j*3<<2)|0)>>2]=c[(h+44080|0)>>2];c[(z+(j*3<<2)|0)+4>>2]=y;y=c[(h+44088|0)+4>>2]|0;c[((z+(j*3<<2)|0)+8|0)>>2]=c[(h+44088|0)>>2];c[((z+(j*3<<2)|0)+8|0)+4>>2]=y;z=(c[h+48676>>2]|0)+(c[(c[f+26648>>2]|0)+(c[(h+21772|0)>>2]<<2)>>2]<<1)|0;c[z>>2]=0;c[z+4>>2]=0;c[z+8>>2]=0;c[z+12>>2]=0;z=(c[f+24692>>2]|0)+l|0;a[z>>0]=a[h+44148>>0]|0;a[z+1>>0]=a[h+44150>>0]|0;a[z+2>>0]=a[h+44164>>0]|0;a[z+3>>0]=a[h+44166>>0]|0}if(!((m&64|0)==0|(c[(h+816|0)>>2]|0)!=3)){z=(c[f+197640>>2]|0)+(c[(h+21772|0)>>2]<<2)|0;a[z+1>>0]=(e[h+44378>>1]|0)>>>1;a[z+2>>0]=(e[h+44380>>1]|0)>>>1;a[z+3>>0]=(e[h+44382>>1]|0)>>>1}c[(c[f+24668>>2]|0)+(s<<2)>>2]=c[(D+56|0)>>2];a[(c[f+24644>>2]|0)+s>>0]=c[h+824>>2];b[(c[f+197604>>2]|0)+(s<<1)>>1]=c[h+808>>2];c[h+21624>>2]=1;b[(c[f+197624>>2]|0)+(C<<1)>>1]=0;a[(c[f+197628>>2]|0)+C>>0]=0;c[h+840>>2]=0;h=0;i=D;return h|0}}if(c[(f+197608|0)>>2]|0)if(!(c[(h+21768|0)>>2]&1)){x=(c[(h+21772|0)>>2]|0)-(c[(f+198236|0)>>2]<<1)|0;x=xA(h+48680|0,h+48798+(((e[(c[f+197604>>2]|0)+(x<<1)>>1]|0)==(c[h+808>>2]|0)&(c[(c[f+24668>>2]|0)+(x<<2)>>2]|0)>>>7)+((c[(h+21764|0)>>2]|0)!=0&c[(h+21796|0)>>2]))|0)|0;c[(h+21796|0)>>2]=x;c[h+21800>>2]=x}c[h+21624>>2]=0;yA(f,h,0-(c[(h+21796|0)>>2]|0)|0);a:do switch(c[(h+816|0)>>2]|0){case 3:{b:do if(!(xA(h+48680|0,28-(((c[h+21716>>2]|0)+511|0)>>>8&1)+(((c[h+21708>>2]|0)+511|0)>>>8&1^1)+(h+48728)|0)|0)){c[(D+52|0)>>2]=0;j=0}else{if(!(xA(h+48680|0,h+48758|0)|0)){j=(xA(h+48680|0,h+48760|0)|0)+1|0;c[(D+52|0)>>2]=j;break}j=(xA(h+48680|0,h+48759|0)|0)<<3;j=((xA(h+48680|0,h+48760|0)|0)<<2)+j|0;j=j+((xA(h+48680|0,h+48760|0)|0)<<1)|0;j=j+(xA(h+48680|0,h+48760|0)|0)|0;if((j|0)<8){c[(D+52|0)>>2]=j+3|0;j=j+3|0;break}switch(j|0){case 13:{o=AA(h,32,0)|0;c[(D+52|0)>>2]=o;y=113;break a}case 14:{c[(D+52|0)>>2]=11;j=11;break b}case 15:{c[(D+52|0)>>2]=22;j=22;break b}default:{j=(j<<1)+-4+(xA(h+48680|0,h+48760|0)|0)|0;c[(D+52|0)>>2]=j;break b}}}while(0);p=a[44300+(j<<2)+2>>0]|0;n=e[44300+(j<<2)>>1]|0;c[(D+52|0)>>2]=n;l=0;break}case 2:{if(xA(h+48680|0,h+48742|0)|0){o=AA(h,17,0)|0;c[(D+52|0)>>2]=o;y=113;break a}if(!(xA(h+48680|0,h+48743|0)|0))j=(xA(h+48680|0,h+48744|0)|0)*3|0;else j=2-(xA(h+48680|0,h+48745|0)|0)|0;p=a[44392+(j<<2)+2>>0]|0;n=e[44392+(j<<2)>>1]|0;c[(D+52|0)>>2]=n;l=0;break}default:{j=AA(h,3,1)|0;c[(D+52|0)>>2]=j;if(j|0?(c[h+812>>2]|0)==5:0){c[(D+52|0)>>2]=j+-1|0;o=j+-1|0;y=113}else{o=j;y=113}}}while(0);if((y|0)==113){l=d[44412+(o<<2)+3>>0]|0;c[h+21636>>2]=d[44412+(o<<2)+2>>0];n=e[44412+(o<<2)>>1]|0;c[(D+52|0)>>2]=n;p=0}if(!(c[(h+21796|0)>>2]|0))j=n;else{j=n|128;c[(D+52|0)>>2]=j}b[(c[f+197604>>2]|0)+(C<<1)>>1]=c[h+808>>2];if(j&4|0){j=(_(e[44292+(c[(f+26672|0)>>2]<<1)>>1]|0,c[f+27844>>2]|0)|0)>>3;l=c[h+48696>>2]|0;k=c[(h+48680|0)>>2]|0;l=(k&1|0)==0?l:l+-1|0;l=(k&511|0)==0?l:l+-1|0;k=c[h+48700>>2]|0;if((k-l|0)<(j|0)){h=-1;i=D;return h|0}c[h+43608>>2]=l;j=mA(h+48680|0,l+j|0,k-(l+j|0)|0)|0;if((j|0)<0){h=j;i=D;return h|0}b[(c[f+197624>>2]|0)+(C<<1)>>1]=-2065;a[(c[f+197628>>2]|0)+C>>0]=0;a[(c[f+24644>>2]|0)+C>>0]=0;k=(c[f+26256>>2]|0)+(C*48|0)|0;m=k+48|0;do{a[k>>0]=16;k=k+1|0}while((k|0)<(m|0));c[(c[f+24668>>2]|0)+(C<<2)>>2]=c[(D+52|0)>>2];c[h+840>>2]=0;h=0;i=D;return h|0}zA(f,h,j);c:do{x=1;c[D+96>>2]=f;c[D+104>>2]=h;c[D+440>>2]=q;c[D+552>>2]=r;c[D+1120>>2]=C;c[D+3880>>2]=s;c[D+4296>>2]=j;c[D+4760>>2]=u;c[D+5112>>2]=p;c[D+5336>>2]=y;c[D+5344>>2]=x;c[D+5352>>2]=0;c[D+5356>>2]=0;CA(D);u=c[D+4760>>2]|0;y=c[D+5336>>2]|0;A=c[D+5352>>2]|0;t=c[D+5356>>2]|0;B=+g[D+5356>>2];c[D+5352>>2]=0;c[D+5356>>2]=0;if((A|0)==6)return t|0;if((A|0)==1)break;if((A|0)==2)switch(t|0){case 7:break c}}while(0);if(c[(D+52|0)>>2]&120|0){a[(c[f+197628>>2]|0)+C>>0]=0;n=c[(D+52|0)>>2]|0;j=c[f+26652>>2]|0;k=(_(j<<2,c[(h+21768|0)>>2]|0)|0)+(c[(h+21764|0)>>2]<<2)|0;o=c[(h+21772|0)>>2]<<2;if(!(n&12288)){x=(c[f+24688>>2]|0)+o|0;b[x>>1]=-1;b[x+2>>1]=-1}else{m=(c[f+24656>>2]|0)+(k<<2)|0;x=c[(h+43824|0)+4>>2]|0;c[m>>2]=c[(h+43824|0)>>2];c[m+4>>2]=x;x=c[(h+43832|0)+4>>2]|0;c[(m+8|0)>>2]=c[(h+43832|0)>>2];c[(m+8|0)+4>>2]=x;x=c[(h+43856|0)+4>>2]|0;c[(m+(j<<2)|0)>>2]=c[(h+43856|0)>>2];c[(m+(j<<2)|0)+4>>2]=x;x=c[(h+43864|0)+4>>2]|0;c[((m+(j<<2)|0)+8|0)>>2]=c[(h+43864|0)>>2];c[((m+(j<<2)|0)+8|0)+4>>2]=x;x=c[(h+43888|0)+4>>2]|0;c[(m+(j<<1<<2)|0)>>2]=c[(h+43888|0)>>2];c[(m+(j<<1<<2)|0)+4>>2]=x;x=c[(h+43896|0)+4>>2]|0;c[((m+(j<<1<<2)|0)+8|0)>>2]=c[(h+43896|0)>>2];c[((m+(j<<1<<2)|0)+8|0)+4>>2]=x;x=c[(h+43920|0)+4>>2]|0;c[(m+(j*3<<2)|0)>>2]=c[(h+43920|0)>>2];c[(m+(j*3<<2)|0)+4>>2]=x;x=c[(h+43928|0)+4>>2]|0;c[((m+(j*3<<2)|0)+8|0)>>2]=c[(h+43928|0)>>2];c[((m+(j*3<<2)|0)+8|0)+4>>2]=x;m=(c[h+48672>>2]|0)+(c[(c[f+26648>>2]|0)+(c[(h+21772|0)>>2]<<2)>>2]<<1)|0;if(!(n&2048)){x=c[(h+44248|0)+4>>2]|0;c[m>>2]=c[(h+44248|0)>>2];c[m+4>>2]=x;b[(m+6|0)+6>>1]=b[h+44206>>1]|0;b[(m+6|0)+4>>1]=b[h+44222>>1]|0;b[(m+6|0)+2>>1]=b[h+44238>>1]|0}else{c[m>>2]=0;c[m+4>>2]=0;c[m+8>>2]=0;c[m+12>>2]=0}x=(c[f+24688>>2]|0)+o|0;a[x>>0]=a[h+44108>>0]|0;a[x+1>>0]=a[h+44110>>0]|0;a[x+2>>0]=a[h+44124>>0]|0;a[x+3>>0]=a[h+44126>>0]|0}if(n&49152|0){x=(c[f+24660>>2]|0)+(k<<2)|0;w=c[(h+43984|0)+4>>2]|0;c[x>>2]=c[(h+43984|0)>>2];c[x+4>>2]=w;w=c[(h+43992|0)+4>>2]|0;c[(x+8|0)>>2]=c[(h+43992|0)>>2];c[(x+8|0)+4>>2]=w;w=c[(h+44016|0)+4>>2]|0;c[(x+(j<<2)|0)>>2]=c[(h+44016|0)>>2];c[(x+(j<<2)|0)+4>>2]=w;w=c[(h+44024|0)+4>>2]|0;c[((x+(j<<2)|0)+8|0)>>2]=c[(h+44024|0)>>2];c[((x+(j<<2)|0)+8|0)+4>>2]=w;w=c[(h+44048|0)+4>>2]|0;c[(x+(j<<1<<2)|0)>>2]=c[(h+44048|0)>>2];c[(x+(j<<1<<2)|0)+4>>2]=w;w=c[(h+44056|0)+4>>2]|0;c[((x+(j<<1<<2)|0)+8|0)>>2]=c[(h+44056|0)>>2];c[((x+(j<<1<<2)|0)+8|0)+4>>2]=w;w=c[(h+44080|0)+4>>2]|0;c[(x+(j*3<<2)|0)>>2]=c[(h+44080|0)>>2];c[(x+(j*3<<2)|0)+4>>2]=w;w=c[(h+44088|0)+4>>2]|0;c[((x+(j*3<<2)|0)+8|0)>>2]=c[(h+44088|0)>>2];c[((x+(j*3<<2)|0)+8|0)+4>>2]=w;j=(c[h+48676>>2]|0)+(c[(c[f+26648>>2]|0)+(c[(h+21772|0)>>2]<<2)>>2]<<1)|0;if(!(n&2048)){x=c[(h+44328|0)+4>>2]|0;c[j>>2]=c[(h+44328|0)>>2];c[j+4>>2]=x;b[(j+6|0)+6>>1]=b[h+44286>>1]|0;b[(j+6|0)+4>>1]=b[h+44302>>1]|0;b[(j+6|0)+2>>1]=b[h+44318>>1]|0}else{c[j>>2]=0;c[j+4>>2]=0;c[j+8>>2]=0;c[j+12>>2]=0}x=(c[f+24692>>2]|0)+o|0;a[x>>0]=a[h+44148>>0]|0;a[x+1>>0]=a[h+44150>>0]|0;a[x+2>>0]=a[h+44164>>0]|0;a[x+3>>0]=a[h+44166>>0]|0}if(!((n&64|0)==0|(c[(h+816|0)>>2]|0)!=3)){x=(c[f+197640>>2]|0)+(c[(h+21772|0)>>2]<<2)|0;a[x+1>>0]=(e[h+44378>>1]|0)>>>1;a[x+2>>0]=(e[h+44380>>1]|0)>>>1;a[x+3>>0]=(e[h+44382>>1]|0)>>>1}}if(!(c[(D+52|0)>>2]&2)){l=c[(h+21832|0)>>2]|0;w=c[(h+21828|0)>>2]|0;x=xA(h+48680|0,76-(w>>>1&2|l>>>1&1)+(h+48728)|0)|0;x=((xA(h+48680|0,76-(w>>>2&2|x&1)+(h+48728)|0)|0)<<1)+x|0;l=x+((xA(h+48680|0,76-(x<<1&2|l>>>3&1)+(h+48728)|0)|0)<<2)|0;l=l+((xA(h+48680|0,76-(l>>>2&1|l&2)+(h+48728)|0)|0)<<3)|0;if(s){j=(c[(h+21832|0)>>2]|0)>>>4&3;k=(c[(h+21828|0)>>2]|0)>>>4&3;if(!(xA(h+48680|0,((k|0)==0?(j|0)!=0&1:(j|0)!=0&1|2)+77+(h+48728)|0)|0))j=0;else{j=(j|0)==2?5:4;j=(xA(h+48680|0,((k|0)==2?j|2:j)+77+(h+48728)|0)|0)+1|0}l=j<<4|l}}else if(l>>>0>15&(s^1)){UC(c[f+4>>2]|0,16,96641,D+32|0);h=-1094995529;i=D;return h|0}c[h+21824>>2]=l;b[(c[f+197624>>2]|0)+(C<<1)>>1]=l;w=(l&15|0)==0;if(!((u|0)==0|w))if(!(c[(D+52|0)>>2]&7)){x=(xA(h+48680|0,(c[h+21808>>2]|0)+399+(h+48728)|0)|0)<<24;c[(D+52|0)>>2]=c[(D+52|0)>>2]|x}j=c[(D+52|0)>>2]|0;if(!((c[(f+26672|0)>>2]|0)!=3|(j&16777216|0)==0)){k=j&7|0?64:0;x=c[h+21716>>2]|0;if((x|0)!=0&(x&16777216|0)==0){a[h+43747>>0]=k;a[h+43739>>0]=k;a[h+43707>>0]=k;a[h+43699>>0]=k;a[h+43667>>0]=k;a[h+43659>>0]=k}x=c[h+21720>>2]|0;if((x|0)!=0&(x&16777216|0)==0){a[h+43763>>0]=k;a[h+43755>>0]=k;a[h+43723>>0]=k;a[h+43715>>0]=k;a[h+43683>>0]=k;a[h+43675>>0]=k}x=c[h+21708>>2]|0;if((x|0)!=0&(x&16777216|0)==0){x=(j&7|0)==0?0:1077952576;c[h+43652>>2]=x;c[h+43692>>2]=x;c[h+43732>>2]=x}}c[(c[f+24668>>2]|0)+(C<<2)>>2]=j;j=c[(D+52|0)>>2]|0;d:do if(!(l|j&2)){c[h+43660>>2]=0;c[h+43668>>2]=0;c[h+43676>>2]=0;c[h+43684>>2]=0;c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;c[h+840>>2]=0;j=h+824|0}else{k=(c[(h+824|0)>>2]|0)!=0;if(!(j&128)){u=k?f+197660|0:f+197948|0;x=k?f+197644|0:f+197932|0}else{u=k?f+197804|0:f+198092|0;x=k?f+197788|0:f+198076|0}if(!(xA(h+48680|0,(c[(h+840|0)>>2]|0?61:60)+(h+48728)|0)|0)){c[(h+840|0)>>2]=0;j=c[(h+824|0)>>2]|0}else{m=(c[f+27844>>2]|0)*6|0;j=62;k=1;while(1){if(!(xA(h+48680|0,h+48728+j|0)|0)){v=k;break}if((k|0)<((m+3|0)<<1|0)){j=63;k=k+1|0}else{y=592;break}}if((y|0)==592){f=c[f+4>>2]|0;C=c[(h+21768|0)>>2]|0;c[(D+40|0)>>2]=c[(h+21764|0)>>2];c[(D+40|0)+4>>2]=C;UC(f,16,96839,D+40|0);h=-1;i=D;return h|0}j=(v&1|0)==0?0-(v+1>>1)|0:v+1>>1;c[(h+840|0)>>2]=j;j=(c[(h+824|0)>>2]|0)+j|0;c[(h+824|0)>>2]=j;do if(j>>>0>(m+3|0)>>>0)if((j|0)<0){c[(h+824|0)>>2]=j+(m+4|0)|0;j=j+(m+4|0)|0;break}else{c[(h+824|0)>>2]=j-(m+4|0)|0;j=j-(m+4|0)|0;break}while(0);c[h+828>>2]=d[f+28412+j>>0];c[h+832>>2]=d[f+28500+j>>0]}q=c[(D+52|0)>>2]|0;do if(!(q&2)){r=q&7|0?0:3;s=0;do{e:do if(!(1<<s&l)){y=(d[96537+(s<<2)>>0]|0)+(h+43648)|0;b[y>>1]=0;b[y+8>>1]=0}else{if(q&16777216){k=s<<2;m=h+44384+(s<<6<<z<<1)|0;n=(c[(f+197580+(r<<2)|0)>>2]|0)+(j<<8)|0;do if((c[(f+26672|0)>>2]|0)==3){o=d[96537+k>>0]|0;y=(a[o+-1+(h+43648)>>0]|0)!=0&1;if(BA(h+48680|0,((a[o+-8+(h+43648)>>0]|0?y|2:y)|1012)+(h+48728)|0)|0)break;b[(h+43648+o|0)>>1]=0;b[(h+43648+o|0)+8>>1]=0;break e}while(0);FA(f,h,m,5,k,u,n,64);break}k=(c[(f+197556+(r<<2)|0)>>2]|0)+(j<<6)|0;m=s<<2;p=0;do{n=p+m|0;o=d[96537+n>>0]|0;y=(a[o+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[o+-8+(h+43648)>>0]|0?y|2:y)+93+(h+48728)|0)|0))a[h+43648+o>>0]=0;else FA(f,h,h+44384+(n<<4<<z<<1)|0,2,n,x,k,16);p=p+1|0}while((p|0)!=4)}while(0);s=s+1|0}while((s|0)!=4)}else{k=h+47456|0;m=k+64|0;do{c[k>>2]=0;k=k+4|0}while((k|0)<(m|0));if(!(BA(h+48680|0,((c[h+21828>>2]|0)>>>7&2|(c[h+21832>>2]|0)>>>8&1)+85+(h+48728)|0)|0))a[h+43648>>0]=0;else EA(f,h,h+47456|0,0,48,x,16);if(w){c[h+43660>>2]=0;c[h+43668>>2]=0;c[h+43676>>2]=0;c[h+43684>>2]=0;break}j=(c[f+197556>>2]|0)+(j<<6)|0;k=x+1|0;n=0;do{m=d[96537+n>>0]|0;y=(a[m+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[m+-8+(h+43648)>>0]|0?y|2:y)+89+(h+48728)|0)|0))a[h+43648+m>>0]=0;else FA(f,h,h+44384+(n<<4<<z<<1)|0,1,n,k,j,15);n=n+1|0}while((n|0)!=16)}while(0);k=c[(f+26672|0)>>2]|0;if((k|0)!=3){j=(l&48|0)!=0;if((k|0)!=2){do if(j){if(!(BA(h+48680|0,((c[(h+21828|0)>>2]|0)>>>5&2|(c[(h+21832|0)>>2]|0)>>>6&1)+97+(h+48728)|0)|0))a[h+43688>>0]=0;else EA(f,h,h+44384+(256<<z<<1)|0,3,49,96835,4);if(!(BA(h+48680|0,((c[(h+21828|0)>>2]|0)>>>6&2|(c[(h+21832|0)>>2]|0)>>>7&1)+97+(h+48728)|0)|0)){a[h+43728>>0]=0;break}else{EA(f,h,h+44384+(512<<z<<1)|0,3,50,96835,4);break}}while(0);if(!(l&32)){c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;j=h+824|0;break}n=x+1|0;j=(c[f+197556+((c[(D+52|0)>>2]&7|0?1:4)<<2)>>2]|0)+(c[h+828>>2]<<6)|0;m=0;do{k=m+16|0;l=d[96537+k>>0]|0;y=(a[l+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[l+-8+(h+43648)>>0]|0?y|2:y)+101+(h+48728)|0)|0))a[h+43648+l>>0]=0;else FA(f,h,h+44384+(k<<4<<z<<1)|0,4,k,n,j,15);m=m+1|0}while((m|0)!=4);j=(c[f+197556+((c[(D+52|0)>>2]&7|0?2:5)<<2)>>2]|0)+(c[h+832>>2]<<6)|0;m=0;while(1){k=m+32|0;l=d[96537+k>>0]|0;y=(a[l+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[l+-8+(h+43648)>>0]|0?y|2:y)+101+(h+48728)|0)|0))a[h+43648+l>>0]=0;else FA(f,h,h+44384+(k<<4<<z<<1)|0,4,k,n,j,15);m=m+1|0;if((m|0)==4){j=h+824|0;break d}}}do if(j){if(!(BA(h+48680|0,((c[(h+21828|0)>>2]|0)>>>5&2|(c[(h+21832|0)>>2]|0)>>>6&1)+97+(h+48728)|0)|0))a[h+43688>>0]=0;else GA(f,h,h+44384+(256<<z<<1)|0,49);if(!(BA(h+48680|0,((c[(h+21828|0)>>2]|0)>>>6&2|(c[(h+21832|0)>>2]|0)>>>7&1)+97+(h+48728)|0)|0)){a[h+43728>>0]=0;break}else{GA(f,h,h+44384+(512<<z<<1)|0,50);break}}while(0);if(!(l&32)){c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;j=h+824|0;break}l=x+1|0;p=0;while(1){m=(p<<4)+16|0;n=p;p=p+1|0;n=(c[f+197556+((c[(D+52|0)>>2]&7|0?0:3)+p<<2)>>2]|0)+(c[h+828+(n<<2)>>2]<<6)|0;q=0;r=h+44384+(m<<4<<z<<1)|0;while(1){o=(q<<3)+m|0;j=d[96537+o>>0]|0;y=(a[j+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[j+-8+(h+43648)>>0]|0?y|2:y)+101+(h+48728)|0)|0))a[h+43648+j>>0]=0;else FA(f,h,r,4,o,l,n,15);k=r+(16<<z<<1)|0;j=d[96537+(o|1)>>0]|0;y=(a[j+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[j+-8+(h+43648)>>0]|0?y|2:y)+101+(h+48728)|0)|0))a[h+43648+j>>0]=0;else FA(f,h,k,4,o|1,l,n,15);j=d[96537+(o|2)>>0]|0;y=(a[j+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[j+-8+(h+43648)>>0]|0?y|2:y)+101+(h+48728)|0)|0))a[h+43648+j>>0]=0;else FA(f,h,k+(16<<z<<1)|0,4,o|2,l,n,15);j=d[96537+(o|3)>>0]|0;y=(a[j+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,(a[j+-8+(h+43648)>>0]|0?y|2:y)+101+(h+48728)|0)|0))a[h+43648+j>>0]=0;else FA(f,h,(k+(16<<z<<1)|0)+(16<<z<<1)|0,4,o|3,l,n,15);q=q+1|0;if((q|0)==2)break;else r=r+(16<<z<<2<<1)|0}if((p|0)==2){j=h+824|0;break d}}}p=c[(D+52|0)>>2]|0;s=c[h+828>>2]|0;do if(!(p&2)){q=p&7|0?1:4;r=0;do{do if(!(1<<r&l)){y=(d[96537+((r<<2)+16)>>0]|0)+(h+43648)|0;b[y>>1]=0;b[y+8>>1]=0}else{if(p&16777216){j=(r<<2)+16|0;k=c[(f+197580+(q<<2)|0)>>2]|0;m=d[96537+j>>0]|0;y=(a[m+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,((a[m+-8+(h+43648)>>0]|0?y|2:y)|1016)+(h+48728)|0)|0)){b[(h+43648+m|0)>>1]=0;b[(h+43648+m|0)+8>>1]=0;break}else{FA(f,h,h+44384+(j<<4<<z<<1)|0,9,j,u,k+(s<<8)|0,64);break}}j=(c[(f+197556+(q<<2)|0)>>2]|0)+(s<<6)|0;k=(r<<2)+16|0;o=0;do{m=k+o|0;n=d[96537+m>>0]|0;y=(a[n+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,((a[n+-8+(h+43648)>>0]|0?y|2:y)|468)+(h+48728)|0)|0))a[h+43648+n>>0]=0;else FA(f,h,h+44384+(m<<4<<z<<1)|0,8,m,x,j,16);o=o+1|0}while((o|0)!=4)}while(0);r=r+1|0}while((r|0)!=4)}else{k=h+47520|0;m=k+64|0;do{c[k>>2]=0;k=k+4|0}while((k|0)<(m|0));if(!(BA(h+48680|0,((c[h+21832>>2]|0)>>>9&1|(c[h+21828>>2]|0)>>>8&2|460)+(h+48728)|0)|0))a[h+43688>>0]=0;else EA(f,h,h+47520|0,6,49,x,16);if(w){c[h+43700>>2]=0;c[h+43708>>2]=0;c[h+43716>>2]=0;c[h+43724>>2]=0;break}j=(c[f+197560>>2]|0)+(s<<6)|0;k=x+1|0;o=0;do{m=o+16|0;n=d[96537+m>>0]|0;y=(a[n+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,((a[n+-8+(h+43648)>>0]|0?y|2:y)|464)+(h+48728)|0)|0))a[h+43648+n>>0]=0;else FA(f,h,h+44384+(m<<4<<z<<1)|0,7,m,k,j,15);o=o+1|0}while((o|0)!=16)}while(0);r=c[(D+52|0)>>2]|0;s=c[h+832>>2]|0;if(r&2|0){k=h+47584|0;m=k+64|0;do{c[k>>2]=0;k=k+4|0}while((k|0)<(m|0));if(!(BA(h+48680|0,((c[h+21832>>2]|0)>>>10&1|(c[h+21828>>2]|0)>>>9&2|472)+(h+48728)|0)|0))a[h+43728>>0]=0;else EA(f,h,h+47584|0,10,50,x,16);if(w){c[h+43740>>2]=0;c[h+43748>>2]=0;c[h+43756>>2]=0;c[h+43764>>2]=0;j=h+824|0;break}n=(c[f+197564>>2]|0)+(s<<6)|0;j=x+1|0;m=0;while(1){k=m+32|0;l=d[96537+k>>0]|0;y=(a[l+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,((a[l+-8+(h+43648)>>0]|0?y|2:y)|476)+(h+48728)|0)|0))a[h+43648+l>>0]=0;else FA(f,h,h+44384+(k<<4<<z<<1)|0,11,k,j,n,15);m=m+1|0;if((m|0)==16){j=h+824|0;break d}}}p=r&7|0?2:5;q=0;do{do if(!(1<<q&l)){y=(d[96537+((q<<2)+32)>>0]|0)+(h+43648)|0;b[y>>1]=0;b[y+8>>1]=0}else{if(r&16777216){j=(q<<2)+32|0;k=c[(f+197580+(p<<2)|0)>>2]|0;m=d[96537+j>>0]|0;y=(a[m+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,((a[m+-8+(h+43648)>>0]|0?y|2:y)|1020)+(h+48728)|0)|0)){b[(h+43648+m|0)>>1]=0;b[(h+43648+m|0)+8>>1]=0;break}else{FA(f,h,h+44384+(j<<4<<z<<1)|0,13,j,u,k+(s<<8)|0,64);break}}j=(c[(f+197556+(p<<2)|0)>>2]|0)+(s<<6)|0;k=(q<<2)+32|0;o=0;do{m=k+o|0;n=d[96537+m>>0]|0;y=(a[n+-1+(h+43648)>>0]|0)!=0&1;if(!(BA(h+48680|0,((a[n+-8+(h+43648)>>0]|0?y|2:y)|480)+(h+48728)|0)|0))a[h+43648+n>>0]=0;else FA(f,h,h+44384+(m<<4<<z<<1)|0,12,m,x,j,16);o=o+1|0}while((o|0)!=4)}while(0);q=q+1|0}while((q|0)!=4);j=h+824|0}while(0);a[(c[f+24644>>2]|0)+C>>0]=c[j>>2];j=c[(h+21772|0)>>2]|0;k=c[f+26256>>2]|0;c[k+(j*48|0)>>2]=c[h+43660>>2];c[k+(j*48|0)+4>>2]=c[h+43668>>2];c[k+(j*48|0)+8>>2]=c[h+43676>>2];c[k+(j*48|0)+12>>2]=c[h+43684>>2];c[k+(j*48|0)+16>>2]=c[h+43700>>2];c[k+(j*48|0)+20>>2]=c[h+43708>>2];c[k+(j*48|0)+32>>2]=c[h+43740>>2];c[k+(j*48|0)+36>>2]=c[h+43748>>2];if(c[f+25976>>2]|0){h=0;i=D;return h|0}c[k+(j*48|0)+24>>2]=c[h+43716>>2];c[k+(j*48|0)+28>>2]=c[h+43724>>2];c[k+(j*48|0)+40>>2]=c[h+43756>>2];c[k+(j*48|0)+44>>2]=c[h+43764>>2];h=0;i=D;return h|0}function wA(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0;do if(!(c[a+197608>>2]|0)){j=c[b+21772>>2]|0;k=j+-1|0;j=j-(c[a+198236>>2]<<((c[a+197612>>2]|0)!=3&1))|0}else{l=c[a+198236>>2]|0;h=(_(l,f&-2)|0)+d|0;g=h+-1|0;j=(f&1|0)!=0;if(j)if((e[(c[a+197604>>2]|0)+(g<<1)>>1]|0|0)==(c[b+808>>2]|0))i=((c[b+21796>>2]|0)==((c[(c[a+24668>>2]|0)+(g<<2)>>2]|0)>>>7&1|0)?l:0)+g|0;else i=g;else i=g;if(!(c[b+21796>>2]|0)){k=i;j=(_(l,f+-1|0)|0)+d|0;break}g=h-l|0;if(j){k=i;j=g}else if((e[(c[a+197604>>2]|0)+(g<<1)>>1]|0|0)==(c[b+808>>2]|0)){k=i;j=g-((c[(c[a+24668>>2]|0)+(g<<2)>>2]&128|0)==0?0:l)|0}else{k=i;j=g}}while(0);h=c[a+197604>>2]|0;i=c[b+808>>2]|0;if((e[h+(k<<1)>>1]|0|0)==(i|0))g=(c[(c[a+24668>>2]|0)+(k<<2)>>2]|0)>>>11&1^1;else g=0;if((e[h+(j<<1)>>1]|0|0)!=(i|0)){l=g;k=b+816|0;k=c[k>>2]|0;k=(k|0)==3;a=l+13|0;l=k?a:l;a=b+48680|0;l=l+11|0;b=b+48728+l|0;b=xA(a,b)|0;return b|0}l=((c[(c[a+24668>>2]|0)+(j<<2)>>2]|0)>>>11&1^1)+g|0;k=b+816|0;k=c[k>>2]|0;k=(k|0)==3;a=l+13|0;l=k?a:l;a=b+48680|0;l=l+11|0;b=b+48728+l|0;b=xA(a,b)|0;return b|0}function xA(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;h=d[e>>0]|0;g=b+4|0;i=c[g>>2]|0;j=d[188544+((i<<1&384)+h)>>0]|0;i=i-j|0;k=i<<17;l=c[b>>2]|0;f=k-l>>31;c[b>>2]=l-(f&k);c[g>>2]=(f&j-i)+i;h=f^h;a[e>>0]=a[189184+h>>0]|0;e=h&1;h=c[g>>2]|0;f=d[188032+h>>0]|0;c[g>>2]=h<<f;f=c[b>>2]<<f;c[b>>2]=f;if(f&65535|0)return e|0;l=b+16|0;k=c[l>>2]|0;c[b>>2]=(((d[k+1>>0]|0)<<1|(d[k>>0]|0)<<9)+-65535<<7-(d[188032+((f+-1^f)>>>15)>>0]|0))+f;c[l>>2]=k+2;return e|0}function yA(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;f=c[b+21772>>2]|0;g=b+21728|0;c[g>>2]=-1;p=c[a+198236>>2]|0;j=f-(p<<c[b+21796>>2])|0;m=j+-1|0;l=j+1|0;n=f+-1|0;k=b+21724|0;c[k>>2]=96409;r=c[a+24668>>2]|0;do if(!(c[a+197608>>2]|0)){q=n;o=j}else{h=c[r+(n<<2)>>2]&128;i=d&128;if(c[b+21768>>2]&1|0){if((h|0)==(i|0)){q=n;o=j;break}d=f-p+-1|0;if(!i){c[g>>2]=0;c[k>>2]=96441;q=d;n=d;o=j;m=p+m|0;break}else{c[k>>2]=96505;q=d;o=j;break}}d=(i|0)!=0;if(d){f=(((c[r+(j<<2)>>2]|0)>>>7&1)+-1&p)+j|0;g=(((c[r+(m<<2)>>2]|0)>>>7&1)+-1&p)+m|0;l=(((c[r+(l<<2)>>2]|0)>>>7&1)+-1&p)+l|0}else{f=j;g=m}if((h|0)==(i|0)){q=n;o=f;m=g}else if(d){c[k>>2]=96505;q=n;n=p+n|0;o=f;m=g;break}else{c[k>>2]=96473;q=n;o=f;m=g;break}}while(0);c[b+21684>>2]=m;c[b+21688>>2]=o;c[b+21692>>2]=l;c[b+21696>>2]=q;c[b+21700>>2]=n;g=b+21704|0;c[g>>2]=c[r+(m<<2)>>2];h=b+21708|0;c[h>>2]=c[r+(o<<2)>>2];i=b+21712|0;c[i>>2]=c[r+(l<<2)>>2];j=b+21716|0;c[j>>2]=c[r+(q<<2)>>2];k=b+21720|0;c[k>>2]=c[r+(n<<2)>>2];f=c[a+197604>>2]|0;d=c[b+808>>2]|0;if((e[f+(m<<1)>>1]|0|0)!=(d|0)){c[g>>2]=0;if((e[f+(o<<1)>>1]|0|0)!=(d|0))c[h>>2]=0;if((e[f+(q<<1)>>1]|0|0)!=(d|0)){c[k>>2]=0;c[j>>2]=0}}if((e[f+(l<<1)>>1]|0|0)==(d|0))return;c[i>>2]=0;return}function zA(f,g,h){f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0;wa=i;i=i+16|0;v=wa+8|0;u=wa;la=c[g+21724>>2]|0;sa=c[g+21684>>2]|0;ta=c[g+21688>>2]|0;ua=c[g+21692>>2]|0;va=c[g+21696>>2]|0;c[v>>2]=va;ma=c[g+21700>>2]|0;c[v+4>>2]=ma;na=c[g+21704>>2]|0;oa=c[g+21708>>2]|0;pa=c[g+21712>>2]|0;qa=c[g+21716>>2]|0;c[u>>2]=qa;ra=c[g+21720>>2]|0;c[u+4>>2]=ra;do if(!(h&2048)){w=(h&7|0)!=0;if(w){q=c[f+27920>>2]|0?7:-1;l=g+21744|0;c[l>>2]=65535;j=g+21736|0;c[j>>2]=65535;n=g+21732|0;c[n>>2]=65535;o=g+21740|0;c[o>>2]=61162;p=(q&oa|0)!=0;if(p){j=65535;m=60138}else{c[n>>2]=46079;c[j>>2]=13311;c[o>>2]=9962;j=46079;m=8938}k=h&128;do if((k|0)==(qa&128|0)){if(!(qa&q)){j=j&57183;c[n>>2]=j;c[l>>2]=24415}}else{if(!k){if(!((qa&q|0)==0?1:(c[(c[f+24668>>2]|0)+((c[f+198236>>2]|0)+va<<2)>>2]&q|0)==0))break;j=j&57183;c[n>>2]=j;c[l>>2]=24415;break}if(!(qa&q)){j=j&57343;c[n>>2]=j;c[l>>2]=24575;k=24415}else k=65375;if(!(ra&q)){j=j&65375;c[n>>2]=j;c[l>>2]=k}}while(0);if(!(q&na))c[n>>2]=j&32767;if(!(q&pa))c[o>>2]=m;if(h&1|0){if(!(oa&1)){FF(g+21644|0,2-(0-(p&1^1)&3)&255|0,4)|0;j=g+21680|0;k=f+26648|0}else{j=g+21680|0;k=f+26648|0;c[g+21644>>2]=c[(c[j>>2]|0)+(c[(c[k>>2]|0)+(ta<<2)>>2]|0)>>2]}if(!(qa&1)){l=(qa&q|0)==0?-1:2;a[g+21651>>0]=l}else{l=(c[j>>2]|0)+(c[(c[k>>2]|0)+(va<<2)>>2]|0)|0;a[g+21651>>0]=a[l+(6-(d[la>>0]|0))>>0]|0;l=a[l+(6-(d[la+1>>0]|0))>>0]|0}a[g+21659>>0]=l;if(!(ra&1)){j=(ra&q|0)==0?-1:2;a[g+21667>>0]=j}else{j=(c[j>>2]|0)+(c[(c[k>>2]|0)+(ma<<2)>>2]|0)|0;a[g+21667>>0]=a[j+(6-(d[la+2>>0]|0))>>0]|0;j=a[j+(6-(d[la+3>>0]|0))>>0]|0}a[g+21675>>0]=j}}t=(oa|0)!=0;do if(t){j=f+26256|0;k=c[j>>2]|0;c[g+43652>>2]=c[k+(ta*48|0)+12>>2];if(!(c[f+25976>>2]|0)){c[g+43692>>2]=c[k+(ta*48|0)+28>>2];c[g+43732>>2]=c[k+(ta*48|0)+44>>2];break}else{c[g+43692>>2]=c[k+(ta*48|0)+20>>2];c[g+43732>>2]=c[k+(ta*48|0)+36>>2];break}}else{j=w?1077952576:0;c[g+43652>>2]=j;c[g+43692>>2]=j;c[g+43732>>2]=j;j=f+26256|0}while(0);r=w?64:0;s=f+26672|0;l=qa;k=0;while(1){a:do if(!l){ka=k<<4;a[ka+99+(g+43648)>>0]=r;a[ka+91+(g+43648)>>0]=r;a[ka+59+(g+43648)>>0]=r;a[ka+51+(g+43648)>>0]=r;a[ka+19+(g+43648)>>0]=r;a[(ka|11)+(g+43648)>>0]=r}else{l=c[v+(k<<2)>>2]|0;m=c[j>>2]|0;n=k<<1;o=la+(n+8)|0;p=k<<4;a[(p|11)+(g+43648)>>0]=a[(d[o>>0]|0)+(m+(l*48|0))>>0]|0;q=la+(n+9)|0;a[p+19+(g+43648)>>0]=a[(d[q>>0]|0)+(m+(l*48|0))>>0]|0;switch(c[s>>2]|0){case 3:{a[p+51+(g+43648)>>0]=a[(d[o>>0]|0)+16+(m+(l*48|0))>>0]|0;a[p+59+(g+43648)>>0]=a[(d[q>>0]|0)+16+(m+(l*48|0))>>0]|0;a[p+91+(g+43648)>>0]=a[(d[o>>0]|0)+32+(m+(l*48|0))>>0]|0;a[p+99+(g+43648)>>0]=a[(d[q>>0]|0)+32+(m+(l*48|0))>>0]|0;break a}case 2:{a[p+51+(g+43648)>>0]=a[(d[o>>0]|0)+14+(m+(l*48|0))>>0]|0;a[p+59+(g+43648)>>0]=a[(d[q>>0]|0)+14+(m+(l*48|0))>>0]|0;a[p+91+(g+43648)>>0]=a[(d[o>>0]|0)+30+(m+(l*48|0))>>0]|0;a[p+99+(g+43648)>>0]=a[(d[q>>0]|0)+30+(m+(l*48|0))>>0]|0;break a}default:{ka=k<<3;a[ka+51+(g+43648)>>0]=a[(d[la+(n+12)>>0]|0)+(m+(l*48|0))>>0]|0;a[ka+91+(g+43648)>>0]=a[(d[la+(n+13)>>0]|0)+(m+(l*48|0))>>0]|0;break a}}}while(0);k=k+1|0;if((k|0)==2)break;l=c[u+(k<<2)>>2]|0}if(t)j=e[(c[f+197624>>2]|0)+(ta<<1)>>1]|0;else j=w?1999:15;c[g+21828>>2]=j;if(!qa){c[g+21832>>2]=w?1999:15;break}else{ka=c[f+197624>>2]|0;ja=e[ka+(va<<1)>>1]|0;c[g+21832>>2]=ja>>>(d[la>>0]&254)&2|ja&2032|(e[ka+(ma<<1)>>1]|0)>>>(d[la+2>>0]&254)<<2&8;break}}while(0);if(!(h&120)){if(!(h&256)){h=oa>>>24;h=h&1;f=qa>>>24;f=f&1;h=f+h|0;g=g+21808|0;c[g>>2]=h;i=wa;return}if(!(c[g+21812>>2]|0)){h=oa>>>24;h=h&1;f=qa>>>24;f=f&1;h=f+h|0;g=g+21808|0;c[g>>2]=h;i=wa;return}}q=c[f+26652>>2]|0;r=g+23380|0;if(!(c[r>>2]|0)){h=oa>>>24;h=h&1;f=qa>>>24;f=f&1;h=f+h|0;g=g+21808|0;c[g>>2]=h;i=wa;return}s=f+26644|0;t=q*3|0;v=ta<<2;u=v|2;v=v|3;w=(h&80|0)==0;x=qa|0?-1:-2;y=va<<2|1;z=pa|0?-1:-2;A=na|0?-1:-2;B=g+21728|0;C=q<<1;D=q+3|0;E=sa<<2;F=(h&2304|0)==0;G=f+26648|0;H=g+816|0;I=g+44348|0;J=f+197640|0;K=g+44356|0;L=g+44364|0;M=g+44372|0;N=(oa&256|0)==0;O=(oa&64|0)==0;P=g+44340|0;Q=g+44342|0;R=(qa&256|0)==0;S=(qa&64|0)==0;T=g+44347|0;U=(ra&256|0)==0;V=(ra&64|0)==0;W=g+44363|0;X=ma<<2|1;Y=la+2|0;Z=la+3|0;$=la+1|0;aa=f+197608|0;ba=g+21796|0;ca=(na&128|0)!=0;da=(oa&128|0)==0;ea=(pa&128|0)==0;fa=(qa&128|0)==0;ga=(ra&128|0)==0;ha=ua<<2|2;ia=oa|0?-1:-16843010;ja=ra|0?-1:-2;ka=0;do{k=c[f+24688+(ka<<2)>>2]|0;l=c[f+24656+(ka<<2)>>2]|0;m=12288<<(ka<<1);do if(m&h|0){n=(m&oa|0)!=0;if(n){j=(c[(c[s>>2]|0)+(ta<<2)>>2]|0)+t|0;xa=l+(j<<2)|0;p=c[xa+4>>2]|0;o=g+43776+(ka*160|0)+16|0;c[o>>2]=c[xa>>2];c[o+4>>2]=p;j=l+(j<<2)+8|0;o=c[j+4>>2]|0;p=g+43776+(ka*160|0)+24|0;c[p>>2]=c[j>>2];c[p+4>>2]=o;p=a[k+u>>0]|0;a[g+44096+(ka*40|0)+5>>0]=p;a[g+44096+(ka*40|0)+4>>0]=p;p=a[k+v>>0]|0;a[g+44096+(ka*40|0)+7>>0]=p;a[g+44096+(ka*40|0)+6>>0]=p}else{xa=g+43776+(ka*160|0)+16|0;c[xa>>2]=0;c[xa+4>>2]=0;c[xa+8>>2]=0;c[xa+12>>2]=0;c[g+44096+(ka*40|0)+4>>2]=ia}j=(qa&m|0)==0;do if(w)if(j){c[g+43776+(ka*160|0)+44>>2]=0;a[g+44096+(ka*40|0)+11>>0]=x;break}else{c[g+43776+(ka*160|0)+44>>2]=c[l+((c[(c[s>>2]|0)+(va<<2)>>2]|0)+3+(_(d[la>>0]|0,q)|0)<<2)>>2];a[g+44096+(ka*40|0)+11>>0]=a[k+((d[la>>0]&254)+y)>>0]|0;break}else{if(j){c[g+43776+(ka*160|0)+44>>2]=0;c[g+43776+(ka*160|0)+76>>2]=0;a[g+44096+(ka*40|0)+11>>0]=x;j=x}else{j=(c[(c[s>>2]|0)+(va<<2)>>2]|0)+3|0;c[g+43776+(ka*160|0)+44>>2]=c[l+((_(d[la>>0]|0,q)|0)+j<<2)>>2];c[g+43776+(ka*160|0)+76>>2]=c[l+((_(d[$>>0]|0,q)|0)+j<<2)>>2];a[g+44096+(ka*40|0)+11>>0]=a[k+((d[la>>0]&254)+y)>>0]|0;j=a[k+((d[$>>0]&254)+y)>>0]|0}a[g+44096+(ka*40|0)+19>>0]=j;if(!(ra&m)){c[g+43776+(ka*160|0)+108>>2]=0;c[g+43776+(ka*160|0)+140>>2]=0;a[g+44096+(ka*40|0)+27>>0]=ja;j=ja}else{j=(c[(c[s>>2]|0)+(ma<<2)>>2]|0)+3|0;c[g+43776+(ka*160|0)+108>>2]=c[l+((_(d[Y>>0]|0,q)|0)+j<<2)>>2];c[g+43776+(ka*160|0)+140>>2]=c[l+((_(d[Z>>0]|0,q)|0)+j<<2)>>2];a[g+44096+(ka*40|0)+27>>0]=a[k+((d[Y>>0]&254)+X)>>0]|0;j=a[k+((d[Z>>0]&254)+X)>>0]|0}a[g+44096+(ka*40|0)+35>>0]=j}while(0);if(!(m&pa)){c[g+43776+(ka*160|0)+32>>2]=0;j=z}else{c[g+43776+(ka*160|0)+32>>2]=c[l+((c[(c[s>>2]|0)+(ua<<2)>>2]|0)+t<<2)>>2];j=a[k+ha>>0]|0}p=g+44096+(ka*40|0)+8|0;a[p>>0]=j;o=g+44096+(ka*40|0)+6|0;do if((a[o>>0]|j)<<24>>24<0)if(!(m&na)){c[g+43776+(ka*160|0)+12>>2]=0;a[g+44096+(ka*40|0)+3>>0]=A;break}else{xa=c[B>>2]|0;c[g+43776+(ka*160|0)+12>>2]=c[l+(D+(c[(c[s>>2]|0)+(sa<<2)>>2]|0)+(xa&C)<<2)>>2];a[g+44096+(ka*40|0)+3>>0]=a[k+(E|xa&2|1)>>0]|0;break}while(0);if(F){j=c[g+48672+(ka<<2)>>2]|0;a[g+44096+(ka*40|0)+30>>0]=-2;a[g+44096+(ka*40|0)+14>>0]=-2;c[g+43776+(ka*160|0)+56>>2]=0;c[g+43776+(ka*160|0)+120>>2]=0;if(n){l=j+(c[(c[G>>2]|0)+(ta<<2)>>2]<<1)|0;n=c[l+4>>2]|0;xa=g+44176+(ka*80|0)+8|0;c[xa>>2]=c[l>>2];c[xa+4>>2]=n}else{xa=g+44176+(ka*80|0)+8|0;c[xa>>2]=0;c[xa+4>>2]=0}if(!(qa&m)){b[g+44176+(ka*80|0)+22>>1]=0;b[g+44176+(ka*80|0)+38>>1]=0}else{xa=(c[(c[G>>2]|0)+(va<<2)>>2]|0)+6|0;b[g+44176+(ka*80|0)+22>>1]=b[j+(xa-(d[la>>0]|0)<<1)>>1]|0;b[g+44176+(ka*80|0)+38>>1]=b[j+(xa-(d[$>>0]|0)<<1)>>1]|0}if(!(ra&m)){b[g+44176+(ka*80|0)+54>>1]=0;b[g+44176+(ka*80|0)+70>>1]=0}else{xa=(c[(c[G>>2]|0)+(ma<<2)>>2]|0)+6|0;b[g+44176+(ka*80|0)+54>>1]=b[j+(xa-(d[Y>>0]|0)<<1)>>1]|0;b[g+44176+(ka*80|0)+70>>1]=b[j+(xa-(d[Z>>0]|0)<<1)>>1]|0}b[g+44176+(ka*80|0)+28>>1]=0;b[g+44176+(ka*80|0)+60>>1]=0;do if((c[H>>2]|0)==3){j=c[J>>2]|0;c[I>>2]=67372036;c[K>>2]=67372036;c[L>>2]=67372036;c[M>>2]=67372036;do if(N)if(O){c[P>>2]=67372036;break}else{a[P>>0]=a[j+u>>0]|0;a[Q>>0]=a[j+v>>0]|0;break}else c[P>>2]=-2139062144;while(0);do if(R)if(S){a[T>>0]=4;break}else{a[T>>0]=a[j+((d[la>>0]&254)+y)>>0]|0;break}else a[T>>0]=-128;while(0);if(!U){a[W>>0]=-128;break}if(V){a[W>>0]=4;break}else{a[W>>0]=a[j+((d[Y>>0]&254)+X)>>0]|0;break}}while(0);if(!(c[aa>>2]|0))break}else if(!(c[aa>>2]|0))break;if(!(c[ba>>2]|0)){if(ca){j=g+44096+(ka*40|0)+3|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+14|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+7|0;a[xa>>0]=d[xa>>0]<<1}}if(!da){j=g+44096+(ka*40|0)+4|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+18|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+9|0;a[xa>>0]=d[xa>>0]<<1}j=g+44096+(ka*40|0)+5|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+22|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+11|0;a[xa>>0]=d[xa>>0]<<1}j=a[o>>0]|0;if(j<<24>>24>-1){a[o>>0]=j<<24>>24>>1;xa=g+43776+(ka*160|0)+26|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+13|0;a[xa>>0]=d[xa>>0]<<1}j=g+44096+(ka*40|0)+7|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+30|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+15|0;a[xa>>0]=d[xa>>0]<<1}}if(!ea){j=a[p>>0]|0;if(j<<24>>24>-1){a[p>>0]=j<<24>>24>>1;xa=g+43776+(ka*160|0)+34|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+17|0;a[xa>>0]=d[xa>>0]<<1}}if(!fa){j=g+44096+(ka*40|0)+11|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+46|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+23|0;a[xa>>0]=d[xa>>0]<<1}j=g+44096+(ka*40|0)+19|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+78|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+39|0;a[xa>>0]=d[xa>>0]<<1}}if(ga)break;j=g+44096+(ka*40|0)+27|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+110|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+55|0;a[xa>>0]=d[xa>>0]<<1}j=g+44096+(ka*40|0)+35|0;k=a[j>>0]|0;if(k<<24>>24<=-1)break;a[j>>0]=k<<24>>24>>1;xa=g+43776+(ka*160|0)+142|0;b[xa>>1]=b[xa>>1]<<1;xa=g+44176+(ka*80|0)+71|0;a[xa>>0]=d[xa>>0]<<1;break}else{if(!ca){j=g+44096+(ka*40|0)+3|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+14|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+7|0;a[xa>>0]=(d[xa>>0]|0)>>>1}}if(da){j=g+44096+(ka*40|0)+4|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+18|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+9|0;a[xa>>0]=(d[xa>>0]|0)>>>1}j=g+44096+(ka*40|0)+5|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+22|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+11|0;a[xa>>0]=(d[xa>>0]|0)>>>1}j=a[o>>0]|0;if(j<<24>>24>-1){a[o>>0]=j<<24>>24<<1;xa=g+43776+(ka*160|0)+26|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+13|0;a[xa>>0]=(d[xa>>0]|0)>>>1}j=g+44096+(ka*40|0)+7|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+30|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+15|0;a[xa>>0]=(d[xa>>0]|0)>>>1}}if(ea){j=a[p>>0]|0;if(j<<24>>24>-1){a[p>>0]=j<<24>>24<<1;xa=g+43776+(ka*160|0)+34|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+17|0;a[xa>>0]=(d[xa>>0]|0)>>>1}}if(fa){j=g+44096+(ka*40|0)+11|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+46|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+23|0;a[xa>>0]=(d[xa>>0]|0)>>>1}j=g+44096+(ka*40|0)+19|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+78|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+39|0;a[xa>>0]=(d[xa>>0]|0)>>>1}}if(!ga)break;j=g+44096+(ka*40|0)+27|0;k=a[j>>0]|0;if(k<<24>>24>-1){a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+110|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+55|0;a[xa>>0]=(d[xa>>0]|0)>>>1}j=g+44096+(ka*40|0)+35|0;k=a[j>>0]|0;if(k<<24>>24<=-1)break;a[j>>0]=k<<24>>24<<1;xa=g+43776+(ka*160|0)+142|0;b[xa>>1]=(b[xa>>1]|0)/2|0;xa=g+44176+(ka*80|0)+71|0;a[xa>>0]=(d[xa>>0]|0)>>>1;break}}while(0);ka=ka+1|0}while(ka>>>0<(c[r>>2]|0)>>>0);h=oa>>>24;h=h&1;xa=qa>>>24;xa=xa&1;h=xa+h|0;xa=g+21808|0;c[xa>>2]=h;i=wa;return}function AA(a,b,e){a=a|0;b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0;f=a+48728+b|0;if(!e){b=a+48680|0;if(!(xA(b,f)|0)){e=0;return e|0}else k=f}else{k=(c[a+21716>>2]&6|0)!=0;b=a+48680|0;if(!(xA(b,f+((c[a+21708>>2]&6|0)==0?k&1:k?2:1)|0)|0)){e=0;return e|0}else k=f+2|0}f=a+48684|0;g=c[f>>2]|0;h=g+-2|0;c[f>>2]=h;j=a+48680|0;i=c[j>>2]|0;if((i|0)<(h<<17|0)){g=(g+-258|0)>>>31;c[f>>2]=h<<g;f=i<<g;c[j>>2]=f;if(!(f&65535)){a=a+48696|0;i=c[a>>2]|0;c[j>>2]=f+-65535+((d[i+1>>0]|0)<<1|(d[i>>0]|0)<<9);c[a>>2]=i+2}}else if((c[a+48696>>2]|0)!=(c[a+48692>>2]|0)){e=25;return e|0}f=(xA(b,k+1|0)|0)*12|1;if(xA(b,k+2|0)|0)f=f+4+((xA(b,k+(e+2)|0)|0)<<2)|0;a=((xA(b,k+(e+3)|0)|0)<<1)+f|0;e=a+(xA(b,k+((e<<1)+3)|0)|0)|0;return e|0}function BA(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;h=d[e>>0]|0;g=b+4|0;i=c[g>>2]|0;j=d[188544+((i<<1&384)+h)>>0]|0;i=i-j|0;k=i<<17;l=c[b>>2]|0;f=k-l>>31;c[b>>2]=l-(f&k);c[g>>2]=(f&j-i)+i;h=f^h;a[e>>0]=a[189184+h>>0]|0;e=h&1;h=c[g>>2]|0;f=d[188032+h>>0]|0;c[g>>2]=h<<f;f=c[b>>2]<<f;c[b>>2]=f;if(f&65535|0)return e|0;l=b+16|0;k=c[l>>2]|0;c[b>>2]=(((d[k+1>>0]|0)<<1|(d[k>>0]|0)<<9)+-65535<<7-(d[188032+((f+-1^f)>>>15)>>0]|0))+f;c[l>>2]=k+2;return e|0}function CA(f){f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0;U=c[f+96>>2]|0;W=c[f+104>>2]|0;V=c[f+440>>2]|0;T=c[f+552>>2]|0;S=c[f+1120>>2]|0;P=c[f+3880>>2]|0;O=c[f+4296>>2]|0;G=c[f+4760>>2]|0;h=c[f+5112>>2]|0;X=c[f+5336>>2]|0;Y=c[f+5344>>2]|0;a:{if(Y){Y=0;if(!(O&7)){if(h<<24>>24==4){if((c[(W+816|0)>>2]|0)==3){h=0;do{do if(!(BA(W+48680|0,W+48764|0)|0))g=0;else{if(!(BA(W+48680|0,W+48765|0)|0)){g=(BA(W+48680|0,W+48767|0)|0)+1|0;break}if(!(BA(W+48680|0,W+48766|0)|0))g=3;else if(!(BA(W+48680|0,W+48767|0)|0))g=7;else{g=(BA(W+48680|0,W+48767|0)|0)+11|0;break}g=((BA(W+48680|0,W+48767|0)|0)<<1)+g|0;g=g+(BA(W+48680|0,W+48767|0)|0)|0}while(0);c[(f+64|0)+(h<<2)>>2]=d[44516+((g&65535)<<2)+2>>0];b[W+44376+(h<<1)>>1]=b[44516+((g&65535)<<2)>>1]|0;h=h+1|0}while((h|0)!=4);P=c[(W+44376|0)>>2]|0;S=c[(W+44380|0)>>2]|0;if((P>>>16|P|S|S>>>16)&256|0){rc(U,W,f+52|0);a[W+44166>>0]=-2;a[W+44126>>0]=-2;a[W+44150>>0]=-2;a[W+44110>>0]=-2;S=((e[(W+44376|0)>>1]|0)>>>1&255)*257&65535;b[W+44348>>1]=S;b[W+44356>>1]=S;S=((e[W+44378>>1]|0)>>>1&255)*257&65535;b[W+44350>>1]=S;b[W+44358>>1]=S;S=((e[(W+44380|0)>>1]|0)>>>1&255)*257&65535;b[W+44364>>1]=S;b[W+44372>>1]=S;S=((e[W+44382>>1]|0)>>>1&255)*257&65535;b[W+44366>>1]=S;b[W+44374>>1]=S}}else{if(!(BA(W+48680|0,W+48749|0)|0))if(!(BA(W+48680|0,W+48750|0)|0))g=1;else{g=(BA(W+48680|0,W+48751|0)|0)==0;g=g?3:2}else g=0;c[(f+64|0)>>2]=d[44568+(g<<2)+2>>0];b[W+44376>>1]=b[44568+(g<<2)>>1]|0;if(!(BA(W+48680|0,W+48749|0)|0))if(!(BA(W+48680|0,W+48750|0)|0))g=1;else{g=(BA(W+48680|0,W+48751|0)|0)==0;g=g?3:2}else g=0;c[(f+64|0)+4>>2]=d[44568+(g<<2)+2>>0];b[W+44378>>1]=b[44568+(g<<2)>>1]|0;if(!(BA(W+48680|0,W+48749|0)|0))if(!(BA(W+48680|0,W+48750|0)|0))g=1;else{g=(BA(W+48680|0,W+48751|0)|0)==0;g=g?3:2}else g=0;c[(f+64|0)+8>>2]=d[44568+(g<<2)+2>>0];b[W+44380>>1]=b[44568+(g<<2)>>1]|0;if(!(BA(W+48680|0,W+48749|0)|0))if(!(BA(W+48680|0,W+48750|0)|0))g=1;else{g=(BA(W+48680|0,W+48751|0)|0)==0;g=g?3:2}else g=0;c[(f+64|0)+12>>2]=d[44568+(g<<2)+2>>0];b[W+44382>>1]=b[44568+(g<<2)>>1]|0}b:do if(!(c[(W+23380|0)>>2]|0))g=0;else{p=0;c:while(1){m=4096<<(p<<1);n=W+23372+(p<<2)|0;o=0;do{g=e[W+44376+(o<<1)>>1]|0;if(!(g&256)){if(!(g&m))h=-1;else{g=c[n>>2]<<c[(W+21800|0)>>2];if(g>>>0>1){k=d[96537+(o<<2)>>0]|0;l=a[W+44096+(p*40|0)+(k+-8|0)>>0]|0;h=(a[W+44096+(p*40|0)+(k+-1|0)>>0]|0)>0;do if((c[(W+816|0)>>2]|0)==3){if(h)j=(d[W+44336+(k+-1|0)>>0]|0)>>>7&255^1;else j=0;if(l<<24>>24<=0){h=0;break}j=(a[W+44336+(k+-8|0)>>0]|0)<0?j:j|2;h=0}else{j=l<<24>>24>0?h&1|2:h&1;h=0}while(0);while(1){if(!(BA(W+48680|0,j+54+(W+48728)|0)|0))break;if((h|0)>30){h=-1;break c}else{j=(j>>2)+4|0;h=h+1|0}}if(h>>>0>=g>>>0)break c}else h=0}P=h&255;S=d[96537+(o<<2)>>0]|0;a[S+9+(W+44096+(p*40|0))>>0]=P;a[S+8+(W+44096+(p*40|0))>>0]=P;a[S+1+(W+44096+(p*40|0))>>0]=P}o=o+1|0}while((o|0)<4);p=p+1|0;g=c[(W+23380|0)>>2]|0;if(p>>>0>=g>>>0)break b}V=c[U+4>>2]|0;c[f>>2]=h;c[f+4>>2]=g;UC(V,16,96621,f);V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}while(0);if(!T)G=0;else{G=(c[U+26732>>2]|0)==0;G=(((G?24117616:7340144)&c[(W+44376|0)>>2]|0)==0?((G?24117616:7340144)&c[(W+44376|0)+4>>2]|0)==0:0)&1}if(g|0){F=0;do{y=F<<1;z=W+44096+(F*40|0)+11|0;A=W+43776+(F*160|0)+40|0;B=W+43776+(F*160|0)+42|0;E=0;do{C=E<<2;g=d[96537+C>>0]|0;a[W+44096+(F*40|0)+g>>0]=a[g+1+(W+44096+(F*40|0))>>0]|0;D=e[W+44376+(E<<1)>>1]|0;do if(!(D&256)){if(!(D&4096<<y)){c[W+43776+(F*160|0)+(g<<2)>>2]=0;c[W+43776+(F*160|0)+(g<<2)+4>>2]=0;c[W+43776+(F*160|0)+(g<<2)+32>>2]=0;c[W+43776+(F*160|0)+(g<<2)+36>>2]=0;c[W+44176+(F*80|0)+(g<<1)>>2]=0;c[W+44176+(F*80|0)+(g<<1)+16>>2]=0;break}s=D&24|0?2:1;t=c[(f+64|0)+(E<<2)>>2]|0;if((t|0)>0){x=0;do{u=a[96537+((_(x,s)|0)+C)>>0]|0;v=W+43776+(F*160|0)+((u&255)<<2)|0;w=W+44176+(F*80|0)+((u&255)<<1)|0;l=a[W+44096+(F*40|0)+(u&255)>>0]|0;q=(u&255)+-8|0;n=a[W+44096+(F*40|0)+q>>0]|0;r=(u&255)+-1|0;o=a[W+44096+(F*40|0)+r>>0]|0;m=W+43776+(F*160|0)+(r<<2)|0;p=W+43776+(F*160|0)+(q<<2)|0;j=a[W+44096+(F*40|0)+(q+s|0)>>0]|0;do if((u&255&7|0)==4&((u&255)>19&(j<<24>>24==-2&(c[(U+197608|0)>>2]|0)!=0))){if((a[z>>0]|0)==-2){X=200;break}k=c[(c[(U+24620|0)>>2]|0)+44>>2]|0;c[A>>2]=0;g=(c[(W+21716|0)>>2]&128|0)==0;if(!(c[(W+21796|0)>>2]|0)){if(g){X=200;break}T=c[(U+198236|0)>>2]|0;g=T+(c[(W+21696|0)>>2]|0)|0;h=(c[(W+21768|0)>>2]<<1&2)+((u&255)>>>5)|0;if(!(c[k+((_(h>>>2,T)|0)+g<<2)>>2]&12288<<y)){Q=-1;R=A;break}R=(c[(c[(U+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(U+26652|0)>>2]|0,h)|0)|0;Q=c[(U+24620|0)>>2]|0;T=c[Q+32+(F<<2)>>2]|0;b[A>>1]=b[T+(R<<2)>>1]|0;b[B>>1]=b[T+(R<<2)+2>>1]<<1;Q=a[(c[Q+64+(F<<2)>>2]|0)+((h&14)+(g<<2|1))>>0]>>1;R=A;break}else{if(!g){X=200;break}g=c[W+21696+(((u&255)>35&1)<<2)>>2]|0;if(!(c[k+(g<<2)>>2]&12288<<y)){Q=-1;R=A;break}R=(c[(c[(U+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(U+26652|0)>>2]|0,(u&255)>>>2&3)|0)|0;Q=c[(U+24620|0)>>2]|0;T=c[Q+32+(F<<2)>>2]|0;b[A>>1]=b[T+(R<<2)>>1]|0;b[B>>1]=(b[T+(R<<2)+2>>1]|0)/2|0;Q=a[(c[Q+64+(F<<2)>>2]|0)+((u&255)>>>2&2|g<<2|1)>>0]<<1;R=A;break}}else X=200;while(0);do if((X|0)==200){X=0;if(j<<24>>24==-2){Q=a[W+44096+(F*40|0)+((u&255)+-9|0)>>0]|0;R=W+43776+(F*160|0)+(((u&255)+-9|0)<<2)|0;break}else{Q=j<<24>>24;R=W+43776+(F*160|0)+((q+s|0)<<2)|0;break}}while(0);g=(o<<24>>24==l<<24>>24&1)+(n<<24>>24==l<<24>>24&1)+((Q|0)==(l<<24>>24|0)&1)|0;do if(g>>>0>1){j=b[m>>1]|0;g=b[p>>1]|0;h=b[R>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);h=b[W+43776+(F*160|0)+(r<<2)+2>>1]|0;j=b[W+43776+(F*160|0)+(q<<2)+2>>1]|0;k=b[R+2>>1]|0;if(h<<16>>16>j<<16>>16){if(k<<16>>16<=j<<16>>16){h=j<<16>>16;break}h=(k<<16>>16|0)>(h<<16>>16|0)?h<<16>>16:k<<16>>16;break}else{if(j<<16>>16<=k<<16>>16){h=j<<16>>16;break}h=(k<<16>>16|0)>(h<<16>>16|0)?k<<16>>16:h<<16>>16;break}}else{if((g|0)==1){if(o<<24>>24==l<<24>>24){g=b[m>>1]|0;h=b[W+43776+(F*160|0)+(r<<2)+2>>1]|0;break}if(n<<24>>24==l<<24>>24){g=b[p>>1]|0;h=b[W+43776+(F*160|0)+(q<<2)+2>>1]|0;break}else{g=b[R>>1]|0;h=b[R+2>>1]|0;break}}j=b[m>>1]|0;if(o<<24>>24!=-2&(n<<24>>24==-2&(Q|0)==-2)){g=j<<16>>16;h=b[W+43776+(F*160|0)+(r<<2)+2>>1]|0;break}g=b[p>>1]|0;h=b[R>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);h=b[W+43776+(F*160|0)+(r<<2)+2>>1]|0;j=b[W+43776+(F*160|0)+(q<<2)+2>>1]|0;k=b[R+2>>1]|0;if(h<<16>>16>j<<16>>16){if(k<<16>>16<=j<<16>>16){h=j<<16>>16;break}h=(k<<16>>16|0)>(h<<16>>16|0)?h<<16>>16:k<<16>>16;break}else{if(j<<16>>16<=k<<16>>16){h=j<<16>>16;break}h=(k<<16>>16|0)>(h<<16>>16|0)?k<<16>>16:h<<16>>16;break}}while(0);T=(d[W+44176+(F*80|0)+(q<<1)+1>>0]|0)+(d[W+44176+(F*80|0)+(r<<1)+1>>0]|0)|0;g=(DA(W,40,(d[W+44176+(F*80|0)+(q<<1)>>0]|0)+(d[W+44176+(F*80|0)+(r<<1)>>0]|0)|0,f+56|0)|0)+g|0;h=(DA(W,47,T,f+48|0)|0)+h|0;do if(!(D&8)){if(D&16){b[v+4>>1]=g&65535;b[v+6>>1]=h&65535;T=c[(f+56|0)>>2]|0;a[w+2>>0]=T;j=c[(f+48|0)>>2]|0;a[w+3>>0]=j;k=g&65535;g=h&65535;h=T;break}if(!(D&32)){k=g&65535;g=h&65535;h=c[(f+56|0)>>2]|0;j=c[(f+48|0)>>2]|0;break}else{b[v+32>>1]=g&65535;b[v+34>>1]=h&65535;T=c[(f+56|0)>>2]|0;a[w+16>>0]=T;j=c[(f+48|0)>>2]|0;a[w+17>>0]=j;k=g&65535;g=h&65535;h=T;break}}else{b[v+36>>1]=g&65535;b[v+32>>1]=g&65535;b[v+4>>1]=g&65535;b[v+38>>1]=h&65535;b[v+34>>1]=h&65535;b[v+6>>1]=h&65535;T=c[(f+56|0)>>2]|0;a[w+18>>0]=T&255;a[w+16>>0]=T&255;a[w+2>>0]=T&255;j=c[(f+48|0)>>2]|0;a[w+19>>0]=j&255;a[w+17>>0]=j&255;a[w+3>>0]=j&255;k=g&65535;g=h&65535;h=T}while(0);b[v>>1]=k;b[W+43776+(F*160|0)+((u&255)<<2)+2>>1]=g;a[w>>0]=h;a[W+44176+(F*80|0)+((u&255)<<1)+1>>0]=j;x=x+1|0}while((x|0)<(t|0))}}else{c[W+44176+(F*80|0)+(g<<1)>>2]=0;c[W+44176+(F*80|0)+(g<<1)+16>>2]=0}while(0);E=E+1|0}while((E|0)!=4);F=F+1|0}while(F>>>0<(c[(W+23380|0)>>2]|0)>>>0)}c[f+5352>>2]=1;break a}if(O&256|0){rc(U,W,f+52|0);c[W+44200>>2]=0;c[W+44204>>2]=0;c[W+44216>>2]=0;c[W+44220>>2]=0;c[W+44232>>2]=0;c[W+44236>>2]=0;c[W+44248>>2]=0;c[W+44252>>2]=0;c[W+44280>>2]=0;c[W+44284>>2]=0;c[W+44296>>2]=0;c[W+44300>>2]=0;c[W+44312>>2]=0;c[W+44316>>2]=0;c[W+44328>>2]=0;c[W+44332>>2]=0;G=c[U+26732>>2]&T;c[f+5352>>2]=1;break a}if(O&8|0){h=c[(W+23380|0)>>2]|0;if(!h){G=T;c[f+5352>>2]=1;break a}m=c[(f+52|0)>>2]|0;l=0;d:while(1){if(m&4096<<(l<<1)){k=c[W+23372+(l<<2)>>2]<<c[(W+21800|0)>>2];if(k>>>0>1){j=a[W+44096+(l*40|0)+4>>0]|0;h=(a[W+44096+(l*40|0)+11>>0]|0)>0;if((c[(W+816|0)>>2]|0)==3){if(h)h=(d[(W+44347|0)>>0]|0)>>>7&255^1;else h=0;if(j<<24>>24>0){j=(a[(W+44340|0)>>0]|0)<0?h:h|2;h=0}else{j=h;h=0}}else{j=j<<24>>24>0?h&1|2:h&1;h=0}while(1){if(!(BA(W+48680|0,j+54+(W+48728)|0)|0))break;if((h|0)>30){n=-1;o=k;X=263;break d}else{j=(j>>2)+4|0;h=h+1|0}}if(h>>>0>=k>>>0){n=h;o=k;X=263;break}}else h=0;h=_(h,16843009)|0;c[W+44096+(l*40|0)+12>>2]=h;c[W+44096+(l*40|0)+20>>2]=h;c[W+44096+(l*40|0)+28>>2]=h;c[W+44096+(l*40|0)+36>>2]=h;h=c[(W+23380|0)>>2]|0}l=l+1|0;if(l>>>0>=h>>>0){g=h;break}}if((X|0)==263){V=c[U+4>>2]|0;c[(f+8|0)>>2]=n;c[(f+8|0)+4>>2]=o;UC(V,16,96621,f+8|0);V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}if(!g){G=T;c[f+5352>>2]=1;break a}p=0;while(1){if(c[(f+52|0)>>2]&4096<<(p<<1)){j=a[W+44096+(p*40|0)+12>>0]|0;l=a[W+44096+(p*40|0)+4>>0]|0;m=a[W+44096+(p*40|0)+11>>0]|0;k=W+43776+(p*160|0)+44|0;n=W+43776+(p*160|0)+16|0;g=a[W+44096+(p*40|0)+8>>0]|0;if(g<<24>>24==-2){h=a[W+44096+(p*40|0)+3>>0]|0;o=W+43776+(p*160|0)+12|0}else{h=g<<24>>24;o=W+43776+(p*160|0)+32|0}g=(m<<24>>24==j<<24>>24&1)+(l<<24>>24==j<<24>>24&1)+((h|0)==(j<<24>>24|0)&1)|0;do if(g>>>0>1){j=b[k>>1]|0;g=b[n>>1]|0;h=b[o>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);j=b[W+43776+(p*160|0)+46>>1]|0;k=b[W+43776+(p*160|0)+18>>1]|0;h=b[o+2>>1]|0;if(j<<16>>16>k<<16>>16){if(h<<16>>16<=k<<16>>16){l=g;g=k<<16>>16;break}l=g;g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16;break}else{if(k<<16>>16<=h<<16>>16){l=g;g=k<<16>>16;break}l=g;g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16;break}}else{if((g|0)==1){if(m<<24>>24==j<<24>>24){l=b[k>>1]|0;g=b[W+43776+(p*160|0)+46>>1]|0;break}if(l<<24>>24==j<<24>>24){l=b[n>>1]|0;g=b[W+43776+(p*160|0)+18>>1]|0;break}else{l=b[o>>1]|0;g=b[o+2>>1]|0;break}}j=b[k>>1]|0;if(m<<24>>24!=-2&(l<<24>>24==-2&(h|0)==-2)){l=j<<16>>16;g=b[W+43776+(p*160|0)+46>>1]|0;break}g=b[n>>1]|0;h=b[o>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);j=b[W+43776+(p*160|0)+46>>1]|0;k=b[W+43776+(p*160|0)+18>>1]|0;h=b[o+2>>1]|0;if(j<<16>>16>k<<16>>16){if(h<<16>>16<=k<<16>>16){l=g;g=k<<16>>16;break}l=g;g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16;break}else{if(k<<16>>16<=h<<16>>16){l=g;g=k<<16>>16;break}l=g;g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16;break}}while(0);S=(d[W+44176+(p*80|0)+9>>0]|0)+(d[W+44176+(p*80|0)+23>>0]|0)|0;U=(DA(W,40,(d[W+44176+(p*80|0)+8>>0]|0)+(d[W+44176+(p*80|0)+22>>0]|0)|0,f+64|0)|0)+l|0;g=(DA(W,47,S,f+56|0)|0)+g|0;S=(c[(f+56|0)>>2]<<8&65280|c[(f+64|0)>>2]&255)*65537|0;c[W+44176+(p*80|0)+24>>2]=S;c[W+44176+(p*80|0)+28>>2]=S;c[W+44176+(p*80|0)+40>>2]=S;c[W+44176+(p*80|0)+44>>2]=S;c[W+44176+(p*80|0)+56>>2]=S;c[W+44176+(p*80|0)+60>>2]=S;c[W+44176+(p*80|0)+72>>2]=S;c[W+44176+(p*80|0)+76>>2]=S;c[W+43776+(p*160|0)+48>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+52>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+56>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+60>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+80>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+84>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+88>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+92>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+112>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+116>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+120>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+124>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+144>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+148>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+152>>2]=g<<16|U&65535;c[W+43776+(p*160|0)+156>>2]=g<<16|U&65535;g=c[(W+23380|0)>>2]|0}p=p+1|0;if(p>>>0>=g>>>0){G=T;c[f+5352>>2]=2;c[f+5356>>2]=7;break a}}}g=(c[(W+23380|0)>>2]|0)!=0;if(!(O&16)){if(!g){G=T;c[f+5352>>2]=1;break a}m=c[(f+52|0)>>2]|0;q=0;e:do{n=q<<1;o=W+23372+(q<<2)|0;p=0;do{if(!(m&4096<<p+n)){S=(p<<1)+12+(W+44096+(q*40|0))|0;b[S>>1]=-1;b[S+8>>1]=-1;b[S+16>>1]=-1;b[S+24>>1]=-1}else{l=c[o>>2]<<c[(W+21800|0)>>2];if(l>>>0>1){j=d[96537+(p<<2)>>0]|0;k=a[W+44096+(q*40|0)+(j+-8|0)>>0]|0;g=(a[W+44096+(q*40|0)+(j+-1|0)>>0]|0)>0;do if((c[(W+816|0)>>2]|0)==3){if(g)h=(d[W+44336+(j+-1|0)>>0]|0)>>>7&255^1;else h=0;if(k<<24>>24<=0){g=0;break}h=(a[W+44336+(j+-8|0)>>0]|0)<0?h:h|2;g=0}else{h=k<<24>>24>0?g&1|2:g&1;g=0}while(0);while(1){if(!(BA(W+48680|0,h+54+(W+48728)|0)|0))break;if((g|0)>30){r=-1;s=l;X=389;break e}else{h=(h>>2)+4|0;g=g+1|0}}if(g>>>0>=l>>>0){r=g;s=l;X=389;break e}}else g=0;S=(p<<1)+12+(W+44096+(q*40|0))|0;R=g*257&65535;b[S>>1]=R;b[S+8>>1]=R;b[S+16>>1]=R;b[S+24>>1]=R}p=p+1|0}while((p|0)<2);q=q+1|0;t=c[(W+23380|0)>>2]|0}while(q>>>0<t>>>0);if((X|0)==389){V=c[U+4>>2]|0;c[(f+24|0)>>2]=r;c[(f+24|0)+4>>2]=s;UC(V,16,96621,f+24|0);V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}if(!t){G=T;c[f+5352>>2]=1;break a}E=0;while(1){r=E<<1;s=W+44096+(E*40|0)+11|0;t=W+43776+(E*160|0)+44|0;u=W+43776+(E*160|0)+46|0;v=W+43776+(E*160|0)+40|0;w=W+43776+(E*160|0)+42|0;x=W+44096+(E*40|0)+8|0;y=W+43776+(E*160|0)+20|0;z=W+44096+(E*40|0)+5|0;A=W+43776+(E*160|0)+32|0;D=0;do{if(!(c[(f+52|0)>>2]&4096<<D+r)){S=(D<<1)+12|0;c[W+44176+(E*80|0)+(S<<1)>>2]=0;c[W+44176+(E*80|0)+(S<<1)+16>>2]=0;c[W+44176+(E*80|0)+(S<<1)+32>>2]=0;c[W+44176+(E*80|0)+(S<<1)+48>>2]=0;c[W+43776+(E*160|0)+(S<<2)>>2]=0;c[W+43776+(E*160|0)+(S<<2)+4>>2]=0;c[W+43776+(E*160|0)+(S<<2)+32>>2]=0;c[W+43776+(E*160|0)+(S<<2)+36>>2]=0;c[W+43776+(E*160|0)+(S<<2)+64>>2]=0;c[W+43776+(E*160|0)+(S<<2)+68>>2]=0;c[W+43776+(E*160|0)+(S<<2)+96>>2]=0;c[W+43776+(E*160|0)+(S<<2)+100>>2]=0}else{B=D<<2;C=(D<<1)+12|0;p=a[W+44096+(E*40|0)+C>>0]|0;do if(!D)if((a[s>>0]|0)==p<<24>>24){I=b[t>>1]|0;F=b[u>>1]|0}else X=402;else{g=a[x>>0]|0;if(g<<24>>24==-2){g=a[z>>0]|0;h=y}else h=A;if(g<<24>>24!=p<<24>>24){X=402;break}I=b[h>>1]|0;F=b[h+2>>1]|0}while(0);do if((X|0)==402){X=0;q=a[96537+B>>0]|0;m=a[W+44096+(E*40|0)+((q&255)+-8|0)>>0]|0;n=a[W+44096+(E*40|0)+((q&255)+-1|0)>>0]|0;l=W+43776+(E*160|0)+(((q&255)+-1|0)<<2)|0;o=W+43776+(E*160|0)+(((q&255)+-8|0)<<2)|0;j=a[W+44096+(E*40|0)+((q&255)+-6|0)>>0]|0;do if((q&255&7|0)==4&((q&255)>19&(j<<24>>24==-2&(c[(U+197608|0)>>2]|0)!=0))){if((a[s>>0]|0)==-2){X=411;break}k=c[(c[(U+24620|0)>>2]|0)+44>>2]|0;c[v>>2]=0;g=(c[(W+21716|0)>>2]&128|0)==0;if(!(c[(W+21796|0)>>2]|0)){if(g){X=411;break}S=c[(U+198236|0)>>2]|0;g=S+(c[(W+21696|0)>>2]|0)|0;h=(c[(W+21768|0)>>2]<<1&2)+((q&255)>>>5)|0;if(!(c[k+((_(h>>>2,S)|0)+g<<2)>>2]&12288<<r)){K=-1;L=v;break}L=(c[(c[(U+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(U+26652|0)>>2]|0,h)|0)|0;K=c[(U+24620|0)>>2]|0;S=c[K+32+(E<<2)>>2]|0;b[v>>1]=b[S+(L<<2)>>1]|0;b[w>>1]=b[S+(L<<2)+2>>1]<<1;K=a[(c[K+64+(E<<2)>>2]|0)+((h&14)+(g<<2|1))>>0]>>1;L=v;break}else{if(!g){X=411;break}g=c[W+21696+(((q&255)>35&1)<<2)>>2]|0;if(!(c[k+(g<<2)>>2]&12288<<r)){K=-1;L=v;break}L=(c[(c[(U+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(U+26652|0)>>2]|0,(q&255)>>>2&3)|0)|0;K=c[(U+24620|0)>>2]|0;S=c[K+32+(E<<2)>>2]|0;b[v>>1]=b[S+(L<<2)>>1]|0;b[w>>1]=(b[S+(L<<2)+2>>1]|0)/2|0;K=a[(c[K+64+(E<<2)>>2]|0)+((q&255)>>>2&2|g<<2|1)>>0]<<1;L=v;break}}else X=411;while(0);do if((X|0)==411){X=0;if(j<<24>>24==-2){K=a[W+44096+(E*40|0)+((q&255)+-9|0)>>0]|0;L=W+43776+(E*160|0)+(((q&255)+-9|0)<<2)|0;break}else{K=j<<24>>24;L=W+43776+(E*160|0)+(((q&255)+-6|0)<<2)|0;break}}while(0);g=(n<<24>>24==p<<24>>24&1)+(m<<24>>24==p<<24>>24&1)+((K|0)==(p<<24>>24|0)&1)|0;if(g>>>0>1){j=b[l>>1]|0;g=b[o>>1]|0;h=b[L>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[W+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[L+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){I=g;F=h<<16>>16;break}I=g;F=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){I=g;F=h<<16>>16;break}I=g;F=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}if((g|0)==1){if(n<<24>>24==p<<24>>24){I=b[l>>1]|0;F=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}if(m<<24>>24==p<<24>>24){I=b[o>>1]|0;F=b[W+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;break}else{I=b[L>>1]|0;F=b[L+2>>1]|0;break}}j=b[l>>1]|0;if(n<<24>>24!=-2&(m<<24>>24==-2&(K|0)==-2)){I=j<<16>>16;F=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}g=b[o>>1]|0;h=b[L>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[W+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[L+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){I=g;F=h<<16>>16;break}I=g;F=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){I=g;F=h<<16>>16;break}I=g;F=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}while(0);S=d[96537+B>>0]|0;R=(d[W+44176+(E*80|0)+((S+-8|0)<<1)+1>>0]|0)+(d[W+44176+(E*80|0)+((S+-1|0)<<1)+1>>0]|0)|0;S=(DA(W,40,(d[W+44176+(E*80|0)+((S+-8|0)<<1)>>0]|0)+(d[W+44176+(E*80|0)+((S+-1|0)<<1)>>0]|0)|0,f+64|0)|0)+I|0;R=(DA(W,47,R,f+56|0)|0)+F|0;Q=(c[(f+56|0)>>2]<<8&65280|c[(f+64|0)>>2]&255)*65537|0;c[W+44176+(E*80|0)+(C<<1)>>2]=Q;c[W+44176+(E*80|0)+(C<<1)+16>>2]=Q;c[W+44176+(E*80|0)+(C<<1)+32>>2]=Q;c[W+44176+(E*80|0)+(C<<1)+48>>2]=Q;c[W+43776+(E*160|0)+(C<<2)>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+4>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+32>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+36>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+64>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+68>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+96>>2]=R<<16|S&65535;c[W+43776+(E*160|0)+(C<<2)+100>>2]=R<<16|S&65535}D=D+1|0}while((D|0)!=2);E=E+1|0;if(E>>>0>=(c[(W+23380|0)>>2]|0)>>>0){G=T;c[f+5352>>2]=2;c[f+5356>>2]=7;break a}}}if(g){m=c[(f+52|0)>>2]|0;n=0;f:do{k=n<<1;l=W+23372+(n<<2)|0;if(!(m&4096<<k)){c[W+44096+(n*40|0)+12>>2]=-1;c[W+44096+(n*40|0)+20>>2]=-1;h=-1}else{j=c[l>>2]<<c[(W+21800|0)>>2];if(j>>>0>1){h=a[W+44096+(n*40|0)+4>>0]|0;g=(a[W+44096+(n*40|0)+11>>0]|0)>0;if((c[(W+816|0)>>2]|0)==3){if(g)g=(d[(W+44347|0)>>0]|0)>>>7&255^1;else g=0;if(h<<24>>24>0){h=(a[(W+44340|0)>>0]|0)<0?g:g|2;g=0}else{h=g;g=0}}else{h=h<<24>>24>0?g&1|2:g&1;g=0}while(1){if(!(BA(W+48680|0,h+54+(W+48728)|0)|0))break;if((g|0)>30){q=-1;u=j;X=319;break f}else{h=(h>>2)+4|0;g=g+1|0}}if(g>>>0>=j>>>0){q=g;u=j;X=319;break}}else g=0;h=_(g,16843009)|0;c[W+44096+(n*40|0)+12>>2]=h;c[W+44096+(n*40|0)+20>>2]=h;h=h&255}if(!(m&4096<<(k|1))){c[W+44096+(n*40|0)+28>>2]=-1;c[W+44096+(n*40|0)+36>>2]=-1}else{j=c[l>>2]<<c[(W+21800|0)>>2];if(j>>>0>1){g=(a[W+44096+(n*40|0)+27>>0]|0)>0;do if((c[(W+816|0)>>2]|0)==3){if(g)g=(d[(W+44363|0)>>0]|0)>>>7&255^1;else g=0;if(h<<24>>24<=0){h=g;g=0;break}h=(a[(W+44356|0)>>0]|0)<0?g:g|2;g=0}else{h=h<<24>>24>0?g&1|2:g&1;g=0}while(0);while(1){if(!(BA(W+48680|0,h+54+(W+48728)|0)|0))break;if((g|0)>30){q=-1;u=j;X=319;break f}else{h=(h>>2)+4|0;g=g+1|0}}if(g>>>0>=j>>>0){q=g;u=j;X=319;break}}else g=0;R=_(g,16843009)|0;c[W+44096+(n*40|0)+28>>2]=R;c[W+44096+(n*40|0)+36>>2]=R}n=n+1|0;p=c[(W+23380|0)>>2]|0}while(n>>>0<p>>>0);if((X|0)==319){V=c[U+4>>2]|0;c[(f+16|0)>>2]=q;c[(f+16|0)+4>>2]=u;UC(V,16,96621,f+16|0);V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}if(!p)G=T;else{E=0;do{r=E<<1;s=W+44096+(E*40|0)+4|0;t=W+43776+(E*160|0)+16|0;u=W+43776+(E*160|0)+18|0;v=W+44096+(E*40|0)+11|0;w=W+43776+(E*160|0)+40|0;x=W+43776+(E*160|0)+42|0;y=W+44096+(E*40|0)+27|0;z=W+43776+(E*160|0)+108|0;A=W+43776+(E*160|0)+110|0;D=0;do{if(!(c[(f+52|0)>>2]&4096<<D+r)){R=D<<4|12;c[W+44176+(E*80|0)+(R<<1)>>2]=0;c[W+44176+(E*80|0)+(R<<1)+4>>2]=0;c[W+44176+(E*80|0)+(R<<1)+16>>2]=0;c[W+44176+(E*80|0)+(R<<1)+20>>2]=0;Q=W+43776+(E*160|0)+(R<<2)|0;R=W+43776+(E*160|0)+(R<<2)+32|0;c[Q>>2]=0;c[Q+4>>2]=0;c[Q+8>>2]=0;c[Q+12>>2]=0;c[R>>2]=0;c[R+4>>2]=0;c[R+8>>2]=0;c[R+12>>2]=0}else{B=D<<3;C=D<<4|12;o=a[W+44096+(E*40|0)+C>>0]|0;if(!D)if((a[s>>0]|0)==o<<24>>24){J=b[t>>1]|0;H=b[u>>1]|0}else X=329;else if((a[y>>0]|0)==o<<24>>24){J=b[z>>1]|0;H=b[A>>1]|0}else X=329;do if((X|0)==329){X=0;q=a[96537+B>>0]|0;m=a[W+44096+(E*40|0)+((q&255)+-8|0)>>0]|0;n=a[W+44096+(E*40|0)+((q&255)+-1|0)>>0]|0;l=W+43776+(E*160|0)+(((q&255)+-1|0)<<2)|0;p=W+43776+(E*160|0)+(((q&255)+-8|0)<<2)|0;j=a[W+44096+(E*40|0)+((q&255)+-4|0)>>0]|0;do if((q&255&7|0)==4&((q&255)>19&(j<<24>>24==-2&(c[(U+197608|0)>>2]|0)!=0))){if((a[v>>0]|0)==-2){X=338;break}k=c[(c[(U+24620|0)>>2]|0)+44>>2]|0;c[w>>2]=0;g=(c[(W+21716|0)>>2]&128|0)==0;if(!(c[(W+21796|0)>>2]|0)){if(g){X=338;break}R=c[(U+198236|0)>>2]|0;g=R+(c[(W+21696|0)>>2]|0)|0;h=(c[(W+21768|0)>>2]<<1&2)+((q&255)>>>5)|0;if(!(c[k+((_(h>>>2,R)|0)+g<<2)>>2]&12288<<r)){M=-1;N=w;break}N=(c[(c[(U+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(U+26652|0)>>2]|0,h)|0)|0;M=c[(U+24620|0)>>2]|0;R=c[M+32+(E<<2)>>2]|0;b[w>>1]=b[R+(N<<2)>>1]|0;b[x>>1]=b[R+(N<<2)+2>>1]<<1;M=a[(c[M+64+(E<<2)>>2]|0)+((h&14)+(g<<2|1))>>0]>>1;N=w;break}else{if(!g){X=338;break}g=c[W+21696+(((q&255)>35&1)<<2)>>2]|0;if(!(c[k+(g<<2)>>2]&12288<<r)){M=-1;N=w;break}N=(c[(c[(U+26644|0)>>2]|0)+(g<<2)>>2]|0)+3+(_(c[(U+26652|0)>>2]|0,(q&255)>>>2&3)|0)|0;M=c[(U+24620|0)>>2]|0;R=c[M+32+(E<<2)>>2]|0;b[w>>1]=b[R+(N<<2)>>1]|0;b[x>>1]=(b[R+(N<<2)+2>>1]|0)/2|0;M=a[(c[M+64+(E<<2)>>2]|0)+((q&255)>>>2&2|g<<2|1)>>0]<<1;N=w;break}}else X=338;while(0);do if((X|0)==338){X=0;if(j<<24>>24==-2){M=a[W+44096+(E*40|0)+((q&255)+-9|0)>>0]|0;N=W+43776+(E*160|0)+(((q&255)+-9|0)<<2)|0;break}else{M=j<<24>>24;N=W+43776+(E*160|0)+(((q&255)+-4|0)<<2)|0;break}}while(0);g=(n<<24>>24==o<<24>>24&1)+(m<<24>>24==o<<24>>24&1)+((M|0)==(o<<24>>24|0)&1)|0;if(g>>>0>1){j=b[l>>1]|0;g=b[p>>1]|0;h=b[N>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[W+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[N+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){J=g;H=h<<16>>16;break}J=g;H=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){J=g;H=h<<16>>16;break}J=g;H=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}if((g|0)==1){if(n<<24>>24==o<<24>>24){J=b[l>>1]|0;H=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}if(m<<24>>24==o<<24>>24){J=b[p>>1]|0;H=b[W+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;break}else{J=b[N>>1]|0;H=b[N+2>>1]|0;break}}j=b[l>>1]|0;if(n<<24>>24!=-2&(m<<24>>24==-2&(M|0)==-2)){J=j<<16>>16;H=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;break}g=b[p>>1]|0;h=b[N>>1]|0;do if(j<<16>>16>g<<16>>16){if(h<<16>>16<=g<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?j<<16>>16:h<<16>>16}else{if(g<<16>>16<=h<<16>>16){g=g<<16>>16;break}g=(h<<16>>16|0)>(j<<16>>16|0)?h<<16>>16:j<<16>>16}while(0);k=b[W+43776+(E*160|0)+(((q&255)+-1|0)<<2)+2>>1]|0;h=b[W+43776+(E*160|0)+(((q&255)+-8|0)<<2)+2>>1]|0;j=b[N+2>>1]|0;if(k<<16>>16>h<<16>>16){if(j<<16>>16<=h<<16>>16){J=g;H=h<<16>>16;break}J=g;H=(j<<16>>16|0)>(k<<16>>16|0)?k<<16>>16:j<<16>>16;break}else{if(h<<16>>16<=j<<16>>16){J=g;H=h<<16>>16;break}J=g;H=(j<<16>>16|0)>(k<<16>>16|0)?j<<16>>16:k<<16>>16;break}}while(0);R=d[96537+B>>0]|0;Q=(d[W+44176+(E*80|0)+((R+-8|0)<<1)+1>>0]|0)+(d[W+44176+(E*80|0)+((R+-1|0)<<1)+1>>0]|0)|0;R=(DA(W,40,(d[W+44176+(E*80|0)+((R+-8|0)<<1)>>0]|0)+(d[W+44176+(E*80|0)+((R+-1|0)<<1)>>0]|0)|0,f+64|0)|0)+J|0;Q=(DA(W,47,Q,f+56|0)|0)+H|0;L=(c[(f+56|0)>>2]<<8&65280|c[(f+64|0)>>2]&255)*65537|0;c[W+44176+(E*80|0)+(C<<1)>>2]=L;c[W+44176+(E*80|0)+(C<<1)+4>>2]=L;c[W+44176+(E*80|0)+(C<<1)+16>>2]=L;c[W+44176+(E*80|0)+(C<<1)+20>>2]=L;c[W+43776+(E*160|0)+(C<<2)>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+4>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+8>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+12>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+32>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+36>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+40>>2]=Q<<16|R&65535;c[W+43776+(E*160|0)+(C<<2)+44>>2]=Q<<16|R&65535}D=D+1|0}while((D|0)!=2);E=E+1|0}while(E>>>0<(c[(W+23380|0)>>2]|0)>>>0);G=T}}else G=T}else Y=1}if(Y){Y=0;if(1){if(!(O&1)){R=ac(U,W,c[(W+21636|0)>>2]|0,0)|0;c[(W+21636|0)>>2]=R;if((R|0)<0){V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}}else{if(!T)X=123;else if(!(xA(W+48680|0,(c[W+21808>>2]|0)+399+(W+48728)|0)|0))X=123;else{c[(f+52|0)>>2]=O|16777216;j=0;do{h=d[96537+j>>0]|0;g=a[h+-1+(W+21640)>>0]|0;R=a[h+-8+(W+21640)>>0]|0;g=(g|0)>(R|0)?R:g;g=(g|0)<0?2:g;if(!(BA(W+48680|0,W+48796|0)|0)){R=BA(W+48680|0,W+48797|0)|0;R=((BA(W+48680|0,W+48797|0)|0)<<1)+R|0;R=R+((BA(W+48680|0,W+48797|0)|0)<<2)|0;g=((R|0)>=(g|0)&1)+R|0}R=g*257&65535;b[(W+21640+h|0)>>1]=R;b[(W+21640+h|0)+8>>1]=R;j=j+4|0}while((j|0)<16)}if((X|0)==123){j=0;do{h=d[96537+j>>0]|0;g=a[h+-1+(W+21640)>>0]|0;R=a[h+-8+(W+21640)>>0]|0;g=(g|0)>(R|0)?R:g;g=(g|0)<0?2:g;if(!(BA(W+48680|0,W+48796|0)|0)){R=BA(W+48680|0,W+48797|0)|0;R=((BA(W+48680|0,W+48797|0)|0)<<1)+R|0;R=R+((BA(W+48680|0,W+48797|0)|0)<<2)|0;g=((R|0)>=(g|0)&1)+R|0}a[W+21640+h>>0]=g;j=j+1|0}while((j|0)!=16)}R=(c[W+21680>>2]|0)+(c[(c[U+26648>>2]|0)+(c[(W+21772|0)>>2]<<2)>>2]|0)|0;c[R>>2]=c[W+21676>>2];a[R+4>>0]=a[W+21671>>0]|0;a[R+5>>0]=a[W+21663>>0]|0;a[R+6>>0]=a[W+21655>>0]|0;if(($b(U,W)|0)<0){V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}}if(!P){c[W+21632>>2]=6;G=T;c[f+5352>>2]=1;break a}if(!(c[W+21716>>2]|0))g=0;else g=(a[(c[U+197628>>2]|0)+(c[W+21696>>2]|0)>>0]|0)!=0&1;if(c[W+21708>>2]|0)g=((a[(c[U+197628>>2]|0)+(c[W+21688>>2]|0)>>0]|0)!=0&1)+g|0;if(!(xA(W+48680|0,g+64+(W+48728)|0)|0))g=0;else if(!(xA(W+48680|0,W+48795|0)|0))g=1;else{g=(xA(W+48680|0,W+48795|0)|0)==0;g=g?2:3}a[(c[U+197628>>2]|0)+S>>0]=g;g=ac(U,W,g,1)|0;if((g|0)<0){V=-1;i=f;c[f+5352>>2]=6;c[f+5356>>2]=V|0;break a}else{c[W+21632>>2]=g;G=T;c[f+5352>>2]=1;break a}}else Y=1}}c[f+440>>2]=V;c[f+4760>>2]=G;c[f+5336>>2]=X;c[f+5344>>2]=Y}
function Xk(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;a=a+(b<<1)|0;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+16|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+24|0;c[d>>2]=134285313;c[d+4>>2]=134285313;b=a+(b<<1)|0;a=b;c[a>>2]=134285313;c[a+4>>2]=134285313;a=b+8|0;c[a>>2]=134285313;c[a+4>>2]=134285313;a=b+16|0;c[a>>2]=134285313;c[a+4>>2]=134285313;b=b+24|0;c[b>>2]=134285313;c[b+4>>2]=134285313;return}function Yk(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=c>>1;i=0-g|0;h=a+14+(i<<1)|0;u=a+(g<<3<<1)+-2|0;m=g<<1;t=u+(0-m<<1)|0;s=u+(g<<1)|0;r=t+(i<<1)|0;q=s+(g<<1)|0;p=r+(i<<1)|0;o=q+(g<<1)|0;c=p+(i<<1)|0;d=o+(g<<1)|0;f=c+(i<<1)|0;n=d+(g<<1)|0;l=f+(i<<1)|0;k=n+(g<<1)|0;j=l+(i<<1)|0;h=(((e[h+2>>1]|0)-(e[h+-2>>1]|0)+((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+(((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+(((e[h+10>>1]|0)-(e[h+-10>>1]|0)|0)*5|0)+(((e[h+12>>1]|0)-(e[h+-12>>1]|0)|0)*6|0)+(((e[h+14>>1]|0)-(e[h+-14>>1]|0)|0)*7|0)+((e[h+16>>1]|0)-(e[h+-16>>1]|0)<<3)|0)*5|0)+32>>6;i=(((e[u>>1]|0)-(e[t>>1]|0)+((e[s>>1]|0)-(e[r>>1]|0)<<1)+(((e[q>>1]|0)-(e[p>>1]|0)|0)*3|0)+((e[o>>1]|0)-(e[c>>1]|0)<<2)+(((e[d>>1]|0)-(e[f>>1]|0)|0)*5|0)+(((e[n>>1]|0)-(e[l>>1]|0)|0)*6|0)+(((e[k>>1]|0)-(e[j>>1]|0)|0)*7|0)+((e[k+(g<<1)>>1]|0)-(e[j+(i<<1)>>1]|0)<<3)|0)*5|0)+32>>6;j=h<<1;k=h*3|0;l=h<<2;m=((e[a+(-2-m)+32>>1]|0)+(e[a+((g*30|0)+-2)>>1]|0)<<4)+16+(_(i+h|0,-7)|0)|0;n=16;while(1){d=m;f=-16;while(1){c=d>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+(f+16<<1)>>1]=c;c=d+h>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+(f+17<<1)>>1]=c;c=d+j>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+(f+18<<1)>>1]=c;c=d+k>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+(f+19<<1)>>1]=c;if((f|0)<-4){d=d+l|0;f=f+4|0}else break}if((n|0)>1){m=m+i|0;n=n+-1|0;a=a+(g<<1)|0}else break}return}function Zk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function _k(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function $k(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;a=a+(b<<1)|0;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+16|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+24|0;c[d>>2]=134219776;c[d+4>>2]=134219776;b=a+(b<<1)|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+16|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+24|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function al(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0;j=f>>1;f=a+(0-j<<1)|0;i=j<<1;h=j*3|0;a=j<<2;g=(e[f>>1]|0)+(c[d>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+16>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+32>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+48>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+4>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+20>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+36>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+52>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+8>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+24>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+40>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+56>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+12>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+28>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+44>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+60>>2]|0)+g;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function bl(a,d,f){a=a|0;d=d|0;f=f|0;var g=0;f=f>>1;g=(e[a+-2>>1]|0)+(c[d>>2]|0)|0;b[a>>1]=g;g=(c[d+4>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+8>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+12>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+16>>2]|0)|0;b[a>>1]=g;g=(c[d+20>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+24>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+28>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+32>>2]|0)|0;b[a>>1]=g;g=(c[d+36>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+40>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+44>>2]|0)+g;a=a+(f<<1)|0;f=(e[a+-2>>1]|0)+(c[d+48>>2]|0)|0;b[a>>1]=f;f=(c[d+52>>2]|0)+f|0;b[a+2>>1]=f;f=(c[d+56>>2]|0)+f|0;b[a+4>>1]=f;b[a+6>>1]=(c[d+60>>2]|0)+f;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function cl(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;g=f>>1;h=g<<1;i=g*3|0;j=g<<2;k=g*5|0;l=g*6|0;m=g*7|0;n=g<<3;o=d;p=0;f=a+(0-g<<1)|0;while(1){a=(e[f>>1]|0)+(c[o>>2]|0)|0;b[f+(g<<1)>>1]=a;a=(c[o+32>>2]|0)+a|0;b[f+(h<<1)>>1]=a;a=(c[o+64>>2]|0)+a|0;b[f+(i<<1)>>1]=a;a=(c[o+96>>2]|0)+a|0;b[f+(j<<1)>>1]=a;a=(c[o+128>>2]|0)+a|0;b[f+(k<<1)>>1]=a;a=(c[o+160>>2]|0)+a|0;b[f+(l<<1)>>1]=a;a=(c[o+192>>2]|0)+a|0;b[f+(m<<1)>>1]=a;b[f+(n<<1)>>1]=(c[o+224>>2]|0)+a;p=p+1|0;if((p|0)==8)break;else{o=o+4|0;f=f+2|0}}FF(d|0,0,256)|0;return}function dl(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;g=f>>1;h=d;i=0;f=a;while(1){a=(e[f+-2>>1]|0)+(c[h>>2]|0)|0;b[f>>1]=a;a=(c[h+4>>2]|0)+a|0;b[f+2>>1]=a;a=(c[h+8>>2]|0)+a|0;b[f+4>>1]=a;a=(c[h+12>>2]|0)+a|0;b[f+6>>1]=a;a=(c[h+16>>2]|0)+a|0;b[f+8>>1]=a;a=(c[h+20>>2]|0)+a|0;b[f+10>>1]=a;a=(c[h+24>>2]|0)+a|0;b[f+12>>1]=a;b[f+14>>1]=(c[h+28>>2]|0)+a;i=i+1|0;if((i|0)==8)break;else{h=h+32|0;f=f+(g<<1)|0}}FF(d|0,0,256)|0;return}function el(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+16|0;t=u;s=h>>1;k=0-s|0;r=e[a+(k<<1)>>1]|0;j=e[a+(1-s<<1)>>1]|0;l=e[a+(2-s<<1)>>1]|0;m=e[a+(3-s<<1)>>1]|0;n=e[a+(4-s<<1)>>1]|0;o=e[a+(5-s<<1)>>1]|0;p=e[a+(6-s<<1)>>1]|0;h=b[a+(7-s<<1)>>1]|0;q=h&65535;if(g)h=b[a+(8-s<<1)>>1]|0;f=((e[a+(((f|0)==0?k:~s)<<1)>>1]|0)+2+(r<<1)+j|0)>>>2&65535;b[t>>1]=f;b[t+2>>1]=(r+2+(j<<1)+l|0)>>>2;b[t+4>>1]=(j+2+(l<<1)+m|0)>>>2;b[t+6>>1]=(l+2+(m<<1)+n|0)>>>2;b[t+8>>1]=(m+2+(n<<1)+o|0)>>>2;b[t+10>>1]=(n+2+(o<<1)+p|0)>>>2;b[t+12>>1]=(o+2+(p<<1)+q|0)>>>2;b[t+14>>1]=((h&65535)+2+(q<<1)+p|0)>>>2;m=s<<1;n=s*3|0;o=s<<2;p=s*5|0;j=s*6|0;k=s*7|0;l=f;q=d;h=0;while(1){f=(l&65535)+(c[q>>2]|0)|0;b[a>>1]=f;f=(c[q+32>>2]|0)+f|0;b[a+(s<<1)>>1]=f;f=(c[q+64>>2]|0)+f|0;b[a+(m<<1)>>1]=f;f=(c[q+96>>2]|0)+f|0;b[a+(n<<1)>>1]=f;f=(c[q+128>>2]|0)+f|0;b[a+(o<<1)>>1]=f;f=(c[q+160>>2]|0)+f|0;b[a+(p<<1)>>1]=f;f=(c[q+192>>2]|0)+f|0;b[a+(j<<1)>>1]=f;b[a+(k<<1)>>1]=(c[q+224>>2]|0)+f;h=h+1|0;if((h|0)==8)break;l=b[t+(h<<1)>>1]|0;q=q+4|0;a=a+2|0}FF(d|0,0,256)|0;i=u;return}function fl(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;m=i;i=i+16|0;l=m;k=h>>1;s=a+-2|0;r=e[s>>1]|0;q=e[a+(k+-1<<1)>>1]|0;p=e[a+((k<<1)+-1<<1)>>1]|0;o=e[a+((k*3|0)+-1<<1)>>1]|0;n=e[a+((k<<2)+-1<<1)>>1]|0;h=e[a+((k*5|0)+-1<<1)>>1]|0;g=e[a+((k*6|0)+-1<<1)>>1]|0;j=e[a+((k*7|0)+-1<<1)>>1]|0;f=((e[((f|0)==0?s:a+(~k<<1)|0)>>1]|0)+2+(r<<1)+q|0)>>>2&65535;b[l>>1]=f;b[l+2>>1]=(r+2+(q<<1)+p|0)>>>2;b[l+4>>1]=(q+2+(p<<1)+o|0)>>>2;b[l+6>>1]=(p+2+(o<<1)+n|0)>>>2;b[l+8>>1]=(o+2+(n<<1)+h|0)>>>2;b[l+10>>1]=(n+2+(h<<1)+g|0)>>>2;b[l+12>>1]=(h+2+(g<<1)+j|0)>>>2;b[l+14>>1]=(g+2+(j*3|0)|0)>>>2;j=d;g=0;h=a;while(1){s=(f&65535)+(c[j>>2]|0)|0;b[h>>1]=s;s=(c[j+4>>2]|0)+s|0;b[h+2>>1]=s;s=(c[j+8>>2]|0)+s|0;b[h+4>>1]=s;s=(c[j+12>>2]|0)+s|0;b[h+6>>1]=s;s=(c[j+16>>2]|0)+s|0;b[h+8>>1]=s;s=(c[j+20>>2]|0)+s|0;b[h+10>>1]=s;s=(c[j+24>>2]|0)+s|0;b[h+12>>1]=s;b[h+14>>1]=(c[j+28>>2]|0)+s;g=g+1|0;if((g|0)==8)break;f=b[l+(g<<1)>>1]|0;j=j+32|0;h=h+(k<<1)|0}FF(d|0,0,256)|0;i=m;return}function gl(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;al(a+(c[b>>2]|0)|0,d,e);al(a+(c[b+4>>2]|0)|0,d+64|0,e);al(a+(c[b+8>>2]|0)|0,d+128|0,e);al(a+(c[b+12>>2]|0)|0,d+192|0,e);return}function hl(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;al(a+(c[b>>2]|0)|0,d,e);al(a+(c[b+4>>2]|0)|0,d+64|0,e);al(a+(c[b+8>>2]|0)|0,d+128|0,e);al(a+(c[b+12>>2]|0)|0,d+192|0,e);al(a+(c[b+32>>2]|0)|0,d+256|0,e);al(a+(c[b+36>>2]|0)|0,d+320|0,e);al(a+(c[b+40>>2]|0)|0,d+384|0,e);al(a+(c[b+44>>2]|0)|0,d+448|0,e);return}function il(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=4);return}function jl(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;h=g>>1;g=0;do{j=a+(c[d+(g<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=4);g=4;do{j=a+(c[d+(g+4<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=8);return}function kl(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;al(a+(c[b>>2]|0)|0,d,e);al(a+(c[b+4>>2]|0)|0,d+64|0,e);al(a+(c[b+8>>2]|0)|0,d+128|0,e);al(a+(c[b+12>>2]|0)|0,d+192|0,e);al(a+(c[b+16>>2]|0)|0,d+256|0,e);al(a+(c[b+20>>2]|0)|0,d+320|0,e);al(a+(c[b+24>>2]|0)|0,d+384|0,e);al(a+(c[b+28>>2]|0)|0,d+448|0,e);al(a+(c[b+32>>2]|0)|0,d+512|0,e);al(a+(c[b+36>>2]|0)|0,d+576|0,e);al(a+(c[b+40>>2]|0)|0,d+640|0,e);al(a+(c[b+44>>2]|0)|0,d+704|0,e);al(a+(c[b+48>>2]|0)|0,d+768|0,e);al(a+(c[b+52>>2]|0)|0,d+832|0,e);al(a+(c[b+56>>2]|0)|0,d+896|0,e);al(a+(c[b+60>>2]|0)|0,d+960|0,e);return}function ll(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=16);return}function ml(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;b=d>>1;d=a+(0-b<<1)|0;e=c[d>>2]|0;d=c[d+4>>2]|0;f=a;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1<<1)|0;c[f>>2]=e;c[f+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=e;c[b+4>>2]=d;return}function nl(a,b,d){a=a|0;b=b|0;d=d|0;var f=0;b=d>>1;d=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;f=a;c[f>>2]=d;c[f+4>>2]=C;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(b<<1)|0;c[d>>2]=f;c[d+4>>2]=C;d=b<<1;f=RF(e[a+(d+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(d<<1)|0;c[d>>2]=f;c[d+4>>2]=C;b=b*3|0;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;b=a+(b<<1)|0;c[b>>2]=d;c[b+4>>2]=C;return}function ol(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function pl(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=d>>1;l=e[a+(1-i<<1)>>1]|0;g=e[a+(2-i<<1)>>1]|0;k=e[a+(3-i<<1)>>1]|0;j=e[c>>1]|0;h=e[c+2>>1]|0;f=e[c+4>>1]|0;d=e[c+6>>1]|0;b[a>>1]=((e[a+(0-i<<1)>>1]|0)+2+g+(l<<1)|0)>>>2;c=(l+2+k+(g<<1)|0)>>>2&65535;b[a+(i<<1)>>1]=c;b[a+2>>1]=c;c=(g+2+j+(k<<1)|0)>>>2&65535;g=i<<1;b[a+(g<<1)>>1]=c;b[a+(i+1<<1)>>1]=c;b[a+4>>1]=c;k=(k+2+h+(j<<1)|0)>>>2&65535;c=i*3|0;b[a+(c<<1)>>1]=k;b[a+((g|1)<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+f+(h<<1)|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;b[a+(i+3<<1)>>1]=j;h=(h+2+d+(f<<1)|0)>>>2&65535;b[a+(c+2<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(c+3<<1)>>1]=(f+2+(d*3|0)|0)>>>2;return}function ql(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;h=d>>1;m=e[a+(~h<<1)>>1]|0;g=e[a+(0-h<<1)>>1]|0;f=e[a+(1-h<<1)>>1]|0;d=e[a+(2-h<<1)>>1]|0;c=e[a+(3-h<<1)>>1]|0;k=e[a+-2>>1]|0;n=e[a+(h+-1<<1)>>1]|0;j=h<<1;i=e[a+(j+-1<<1)>>1]|0;l=h*3|0;b[a+(l<<1)>>1]=(n+2+(i<<1)+(e[a+(l+-1<<1)>>1]|0)|0)>>>2;i=(k+2+(n<<1)+i|0)>>>2&65535;b[a+(l+1<<1)>>1]=i;b[a+(j<<1)>>1]=i;i=m+2|0;n=(i+(k<<1)+n|0)>>>2&65535;b[a+(l+2<<1)>>1]=n;b[a+((j|1)<<1)>>1]=n;b[a+(h<<1)>>1]=n;k=((m<<1)+2+g+k|0)>>>2&65535;b[a+(l+3<<1)>>1]=k;b[a+(j+2<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;b[a>>1]=k;i=(i+(g<<1)+f|0)>>>2&65535;b[a+(j+3<<1)>>1]=i;b[a+(h+2<<1)>>1]=i;b[a+2>>1]=i;g=(g+2+(f<<1)+d|0)>>>2&65535;b[a+(h+3<<1)>>1]=g;b[a+4>>1]=g;b[a+6>>1]=(f+2+(d<<1)+c|0)>>>2;return}function rl(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;i=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;k=e[a+(3-j<<1)>>1]|0;g=e[a+-2>>1]|0;f=e[a+(j+-1<<1)>>1]|0;h=j<<1;d=e[a+(h+-1<<1)>>1]|0;c=j*3|0;o=(i+1+n|0)>>>1&65535;b[a+((h|1)<<1)>>1]=o;b[a>>1]=o;o=(n+1+m|0)>>>1&65535;b[a+(h+2<<1)>>1]=o;b[a+2>>1]=o;o=(m+1+l|0)>>>1&65535;b[a+(h+3<<1)>>1]=o;b[a+4>>1]=o;b[a+6>>1]=(l+1+k|0)>>>1;o=((i<<1)+2+n+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=o;b[a+(j<<1)>>1]=o;i=i+2|0;o=(i+(n<<1)+m|0)>>>2&65535;b[a+(c+2<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;n=(n+2+(m<<1)+l|0)>>>2&65535;b[a+(c+3<<1)>>1]=n;b[a+(j+2<<1)>>1]=n;b[a+(j+3<<1)>>1]=(m+2+(l<<1)+k|0)>>>2;b[a+(h<<1)>>1]=(i+(g<<1)+f|0)>>>2;b[a+(c<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function sl(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;k=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;i=e[a+-2>>1]|0;g=e[a+(j+-1<<1)>>1]|0;h=j<<1;f=e[a+(h+-1<<1)>>1]|0;c=j*3|0;d=e[a+(c+-1<<1)>>1]|0;o=(k+1+i|0)>>>1&65535;b[a+(j+2<<1)>>1]=o;b[a>>1]=o;o=((k<<1)+2+n+i|0)>>>2&65535;b[a+(j+3<<1)>>1]=o;b[a+2>>1]=o;k=k+2|0;b[a+4>>1]=(k+(n<<1)+m|0)>>>2;b[a+6>>1]=(n+2+(m<<1)+l|0)>>>2;l=(i+1+g|0)>>>1&65535;b[a+(h+2<<1)>>1]=l;b[a+(j<<1)>>1]=l;k=(k+(i<<1)+g|0)>>>2&65535;b[a+(h+3<<1)>>1]=k;b[a+(j+1<<1)>>1]=k;j=(g+1+f|0)>>>1&65535;b[a+(c+2<<1)>>1]=j;b[a+(h<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+3<<1)>>1]=i;b[a+((h|1)<<1)>>1]=i;b[a+(c<<1)>>1]=(f+1+d|0)>>>1;b[a+(c+1<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function tl(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=d>>1;l=e[a+(0-h<<1)>>1]|0;k=e[a+(1-h<<1)>>1]|0;j=e[a+(2-h<<1)>>1]|0;i=e[a+(3-h<<1)>>1]|0;g=e[c>>1]|0;f=e[c+2>>1]|0;d=e[c+4>>1]|0;b[a>>1]=(l+1+k|0)>>>1;m=(k+1+j|0)>>>1&65535;c=h<<1;b[a+(c<<1)>>1]=m;b[a+2>>1]=m;m=(j+1+i|0)>>>1&65535;b[a+((c|1)<<1)>>1]=m;b[a+4>>1]=m;m=(i+1+g|0)>>>1&65535;b[a+(c+2<<1)>>1]=m;b[a+6>>1]=m;b[a+(c+3<<1)>>1]=(g+1+f|0)>>>1;b[a+(h<<1)>>1]=(l+2+(k<<1)+j|0)>>>2;k=(k+2+(j<<1)+i|0)>>>2&65535;c=h*3|0;b[a+(c<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;j=(j+2+(i<<1)+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(h+2<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+2<<1)>>1]=i;b[a+(h+3<<1)>>1]=i;b[a+(c+3<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function ul(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0;g=d>>1;k=e[a+-2>>1]|0;j=e[a+(g+-1<<1)>>1]|0;c=g<<1;i=e[a+(c+-1<<1)>>1]|0;f=g*3|0;d=b[a+(f+-1<<1)>>1]|0;h=d&65535;b[a>>1]=(k+1+j|0)>>>1;b[a+2>>1]=(k+2+(j<<1)+i|0)>>>2;k=(j+1+i|0)>>>1&65535;b[a+(g<<1)>>1]=k;b[a+4>>1]=k;j=(j+2+(i<<1)+h|0)>>>2&65535;b[a+(g+1<<1)>>1]=j;b[a+6>>1]=j;j=(i+1+h|0)>>>1&65535;b[a+(c<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;h=(i+2+h+(h<<1)|0)>>>2&65535;b[a+((c|1)<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(f+3<<1)>>1]=d;b[a+(f+2<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+(f<<1)>>1]=d;b[a+(f+1<<1)>>1]=d;b[a+(c+3<<1)>>1]=d;return}function vl(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=RF(((e[a+-2>>1]|0)+2+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function wl(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0;b=d>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function xl(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b<<1)|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b<<1<<1)|0;c[d>>2]=536813567;c[d+4>>2]=536813567;b=a+(b*3<<1)|0;c[b>>2]=536813567;c[b+4>>2]=536813567;return}function yl(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b<<1)|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b<<1<<1)|0;c[d>>2]=536944641;c[d+4>>2]=536944641;b=a+(b*3<<1)|0;c[b>>2]=536944641;c[b+4>>2]=536944641;return}function zl(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b<<1)|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b<<1<<1)|0;c[d>>2]=536879104;c[d+4>>2]=536879104;b=a+(b*3<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function Al(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;b[a>>1]=((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2;b[a+2>>1]=(q+2+(i<<1)+k|0)>>>2;b[a+4>>1]=(i+2+(k<<1)+l|0)>>>2;b[a+6>>1]=(k+2+(l<<1)+m|0)>>>2;f=a+8|0;b[f>>1]=(l+2+(m<<1)+n|0)>>>2;b[a+10>>1]=(m+2+(n<<1)+o|0)>>>2;b[a+12>>1]=(n+2+(o<<1)+p|0)>>>2;b[a+14>>1]=((g&65535)+2+(p<<1)+o|0)>>>2;p=a;o=c[p>>2]|0;p=c[p+4>>2]|0;q=c[f>>2]|0;f=c[f+4>>2]|0;d=a+(h<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*3<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<2<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*5<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*6<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*7<<1)|0;a=d;c[a>>2]=o;c[a+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function Bl(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;p=f>>1;u=a+-2|0;s=e[u>>1]|0;q=e[a+(p+-1<<1)>>1]|0;r=p<<1;o=e[a+(r+-1<<1)>>1]|0;l=p*3|0;m=e[a+(l+-1<<1)>>1]|0;n=p<<2;k=e[a+(n+-1<<1)>>1]|0;h=p*5|0;i=e[a+(h+-1<<1)>>1]|0;j=p*6|0;g=e[a+(j+-1<<1)>>1]|0;d=p*7|0;f=e[a+(d+-1<<1)>>1]|0;u=RF(((e[((b|0)==0?u:a+(~p<<1)|0)>>1]|0)+2+(s<<1)+q|0)>>>2|0,0,65537,65537)|0;t=C;b=a;c[b>>2]=u;c[b+4>>2]=t;b=a+8|0;c[b>>2]=u;c[b+4>>2]=t;s=RF((s+2+(q<<1)+o|0)>>>2|0,0,65537,65537)|0;b=C;p=a+(p<<1)|0;t=p;c[t>>2]=s;c[t+4>>2]=b;p=p+8|0;c[p>>2]=s;c[p+4>>2]=b;q=RF((q+2+(o<<1)+m|0)>>>2|0,0,65537,65537)|0;p=C;b=a+(r<<1)|0;r=b;c[r>>2]=q;c[r+4>>2]=p;b=b+8|0;c[b>>2]=q;c[b+4>>2]=p;o=RF((o+2+(m<<1)+k|0)>>>2|0,0,65537,65537)|0;b=C;l=a+(l<<1)|0;p=l;c[p>>2]=o;c[p+4>>2]=b;l=l+8|0;c[l>>2]=o;c[l+4>>2]=b;m=RF((m+2+(k<<1)+i|0)>>>2|0,0,65537,65537)|0;l=C;b=a+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;k=RF((k+2+(i<<1)+g|0)>>>2|0,0,65537,65537)|0;b=C;h=a+(h<<1)|0;l=h;c[l>>2]=k;c[l+4>>2]=b;h=h+8|0;c[h>>2]=k;c[h+4>>2]=b;i=RF((i+2+(g<<1)+f|0)>>>2|0,0,65537,65537)|0;h=C;b=a+(j<<1)|0;j=b;c[j>>2]=i;c[j+4>>2]=h;b=b+8|0;c[b>>2]=i;c[b+4>>2]=h;f=RF((g+2+(f*3|0)|0)>>>2|0,0,65537,65537)|0;b=C;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function Cl(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;A=g>>1;d=(d|0)!=0;n=~A;q=a+-2|0;u=e[q>>1]|0;j=e[a+(A+-1<<1)>>1]|0;o=e[a+((A<<1)+-1<<1)>>1]|0;p=e[a+((A*3|0)+-1<<1)>>1]|0;r=e[a+((A<<2)+-1<<1)>>1]|0;s=e[a+((A*5|0)+-1<<1)>>1]|0;t=e[a+((A*6|0)+-1<<1)>>1]|0;v=e[a+((A*7|0)+-1<<1)>>1]|0;w=0-A|0;x=e[a+(w<<1)>>1]|0;y=e[a+(1-A<<1)>>1]|0;z=e[a+(2-A<<1)>>1]|0;h=e[a+(3-A<<1)>>1]|0;i=e[a+(4-A<<1)>>1]|0;k=e[a+(5-A<<1)>>1]|0;l=e[a+(6-A<<1)>>1]|0;g=b[a+(7-A<<1)>>1]|0;m=g&65535;if(f)g=b[a+(8-A<<1)>>1]|0;z=(((e[(d?a+(n<<1)|0:q)>>1]|0)+2+(u<<1)+j|0)>>>2)+8+((u+2+(j<<1)+o|0)>>>2)+((j+2+(o<<1)+p|0)>>>2)+((o+2+(p<<1)+r|0)>>>2)+((p+2+(r<<1)+s|0)>>>2)+((r+2+(s<<1)+t|0)>>>2)+((s+2+(t<<1)+v|0)>>>2)+((t+2+(v*3|0)|0)>>>2)+(((e[a+((d?n:w)<<1)>>1]|0)+2+(x<<1)+y|0)>>>2)+((x+2+(y<<1)+z|0)>>>2)+((y+2+(z<<1)+h|0)>>>2)+((z+2+(h<<1)+i|0)>>>2)+((h+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+(((g&65535)+2+(m<<1)+l|0)>>>2)>>4;z=RF(z|0,((z|0)<0)<<31>>31|0,65537,65537)|0;f=C;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;A=a;c[A>>2]=z;c[A+4>>2]=f;a=a+8|0;c[a>>2]=z;c[a+4>>2]=f;return}function Dl(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;o=f>>1;q=0-o|0;x=e[a+(q<<1)>>1]|0;p=e[a+(1-o<<1)>>1]|0;t=e[a+(2-o<<1)>>1]|0;r=(x+2+(p<<1)+t|0)>>>2;u=e[a+(3-o<<1)>>1]|0;s=(p+2+(t<<1)+u|0)>>>2;w=e[a+(4-o<<1)>>1]|0;t=(t+2+(u<<1)+w|0)>>>2;y=e[a+(5-o<<1)>>1]|0;u=(u+2+(w<<1)+y|0)>>>2;v=e[a+(6-o<<1)>>1]|0;w=(w+2+(y<<1)+v|0)>>>2;f=b[a+(7-o<<1)>>1]|0;n=f&65535;y=(y+2+(v<<1)+n|0)>>>2;if(!d){d=(f&65535)+2+(n<<1)|0;g=n;h=n;i=n;j=n;k=n;l=n;m=n;f=n}else{A=e[a+(8-o<<1)>>1]|0;z=e[a+(9-o<<1)>>1]|0;f=e[a+(10-o<<1)>>1]|0;i=e[a+(11-o<<1)>>1]|0;j=e[a+(12-o<<1)>>1]|0;k=e[a+(13-o<<1)>>1]|0;m=e[a+(14-o<<1)>>1]|0;l=e[a+(15-o<<1)>>1]|0;d=(e[a+(8-o<<1)>>1]|0)+2+(n<<1)|0;g=(z+2+(f<<1)+i|0)>>>2;h=(f+2+(i<<1)+j|0)>>>2;i=(i+2+(j<<1)+k|0)>>>2;j=(j+2+(k<<1)+m|0)>>>2;k=(k+2+(m<<1)+l|0)>>>2;l=(m+2+(l*3|0)|0)>>>2;m=(n+2+(A<<1)+z|0)>>>2;f=(A+2+(z<<1)+f|0)>>>2}z=(d+v|0)>>>2;b[a>>1]=((((e[a+(((c|0)==0?q:~o)<<1)>>1]|0)+2+(x<<1)+p|0)>>>2)+2+(r<<1)+s|0)>>>2;A=(r+2+(s<<1)+t|0)>>>2&65535;b[a+2>>1]=A;b[a+(o<<1)>>1]=A;A=(s+2+(t<<1)+u|0)>>>2&65535;b[a+4>>1]=A;b[a+(o+1<<1)>>1]=A;s=o<<1;b[a+(s<<1)>>1]=A;A=(t+2+(u<<1)+w|0)>>>2&65535;b[a+6>>1]=A;b[a+(o+2<<1)>>1]=A;b[a+((s|1)<<1)>>1]=A;t=o*3|0;b[a+(t<<1)>>1]=A;A=(u+2+(w<<1)+y|0)>>>2&65535;b[a+8>>1]=A;b[a+(o+3<<1)>>1]=A;b[a+(s+2<<1)>>1]=A;b[a+(t+1<<1)>>1]=A;v=o<<2;b[a+(v<<1)>>1]=A;A=(w+2+(y<<1)+z|0)>>>2&65535;b[a+10>>1]=A;b[a+(o+4<<1)>>1]=A;b[a+(s+3<<1)>>1]=A;b[a+(t+2<<1)>>1]=A;b[a+((v|1)<<1)>>1]=A;x=o*5|0;b[a+(x<<1)>>1]=A;A=(y+2+(z<<1)+m|0)>>>2&65535;b[a+12>>1]=A;b[a+(o+5<<1)>>1]=A;b[a+(s+4<<1)>>1]=A;b[a+(t+3<<1)>>1]=A;b[a+((v|2)<<1)>>1]=A;b[a+(x+1<<1)>>1]=A;c=o*6|0;b[a+(c<<1)>>1]=A;z=(z+2+(m<<1)+f|0)>>>2&65535;b[a+14>>1]=z;b[a+(o+6<<1)>>1]=z;b[a+(s+5<<1)>>1]=z;b[a+(t+4<<1)>>1]=z;b[a+((v|3)<<1)>>1]=z;b[a+(x+2<<1)>>1]=z;b[a+((c|1)<<1)>>1]=z;A=o*7|0;b[a+(A<<1)>>1]=z;z=(m+2+(f<<1)+g|0)>>>2&65535;b[a+(o+7<<1)>>1]=z;b[a+(s+6<<1)>>1]=z;b[a+(t+5<<1)>>1]=z;b[a+(v+4<<1)>>1]=z;b[a+(x+3<<1)>>1]=z;b[a+(c+2<<1)>>1]=z;b[a+(A+1<<1)>>1]=z;z=(f+2+(g<<1)+h|0)>>>2&65535;b[a+(s+7<<1)>>1]=z;b[a+(t+6<<1)>>1]=z;b[a+(v+5<<1)>>1]=z;b[a+(x+4<<1)>>1]=z;b[a+(c+3<<1)>>1]=z;b[a+(A+2<<1)>>1]=z;z=(g+2+(h<<1)+i|0)>>>2&65535;b[a+(t+7<<1)>>1]=z;b[a+(v+6<<1)>>1]=z;b[a+(x+5<<1)>>1]=z;b[a+(c+4<<1)>>1]=z;b[a+(A+3<<1)>>1]=z;z=(h+2+(i<<1)+j|0)>>>2&65535;b[a+(v+7<<1)>>1]=z;b[a+(x+6<<1)>>1]=z;b[a+(c+5<<1)>>1]=z;b[a+(A+4<<1)>>1]=z;z=(i+2+(j<<1)+k|0)>>>2&65535;b[a+(x+7<<1)>>1]=z;b[a+(c+6<<1)>>1]=z;b[a+(A+5<<1)>>1]=z;z=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=z;b[a+(A+6<<1)>>1]=z;b[a+(A+7<<1)>>1]=(k+2+(l*3|0)|0)>>>2;return}function El(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;E=a+(i<<1)|0;v=a+-2|0;F=e[v>>1]|0;x=e[a+(r+-1<<1)>>1]|0;v=((e[(c?E:v)>>1]|0)+2+(F<<1)+x|0)>>>2;i=r<<1;y=e[a+(i+-1<<1)>>1]|0;F=F+2|0;d=(F+(x<<1)+y|0)>>>2;c=r*3|0;z=e[a+(c+-1<<1)>>1]|0;x=(x+2+(y<<1)+z|0)>>>2;s=r<<2;A=e[a+(s+-1<<1)>>1]|0;y=(y+2+(z<<1)+A|0)>>>2;t=r*5|0;B=e[a+(t+-1<<1)>>1]|0;z=(z+2+(A<<1)+B|0)>>>2;u=r*6|0;D=e[a+(u+-1<<1)>>1]|0;A=(A+2+(B<<1)+D|0)>>>2;w=r*7|0;C=e[a+(w+-1<<1)>>1]|0;B=(B+2+(D<<1)+C|0)>>>2;p=(F+((e[E>>1]|0)<<1)+p|0)>>>2;b[a+(w<<1)>>1]=(A+2+((D+2+(C*3|0)|0)>>>2)+(B<<1)|0)>>>2;B=(z+2+(A<<1)+B|0)>>>2&65535;b[a+(w+1<<1)>>1]=B;b[a+(u<<1)>>1]=B;A=(y+2+(z<<1)+A|0)>>>2&65535;b[a+(w+2<<1)>>1]=A;b[a+((u|1)<<1)>>1]=A;b[a+(t<<1)>>1]=A;z=(x+2+(y<<1)+z|0)>>>2&65535;b[a+(w+3<<1)>>1]=z;b[a+(u+2<<1)>>1]=z;b[a+(t+1<<1)>>1]=z;b[a+(s<<1)>>1]=z;y=(d+2+(x<<1)+y|0)>>>2&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+3<<1)>>1]=y;b[a+(t+2<<1)>>1]=y;b[a+((s|1)<<1)>>1]=y;b[a+(c<<1)>>1]=y;x=(v+2+(d<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(t+3<<1)>>1]=x;b[a+((s|2)<<1)>>1]=x;b[a+(c+1<<1)>>1]=x;b[a+(i<<1)>>1]=x;d=((v<<1)+2+d+p|0)>>>2&65535;b[a+(w+6<<1)>>1]=d;b[a+(u+5<<1)>>1]=d;b[a+(t+4<<1)>>1]=d;b[a+((s|3)<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+((i|1)<<1)>>1]=d;b[a+(r<<1)>>1]=d;d=g+2|0;v=(d+v+(p<<1)|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+6<<1)>>1]=v;b[a+(t+5<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+(c+3<<1)>>1]=v;b[a+(i+2<<1)>>1]=v;b[a+(r+1<<1)>>1]=v;b[a>>1]=v;p=((g<<1)+2+h+p|0)>>>2&65535;b[a+(u+7<<1)>>1]=p;b[a+(t+6<<1)>>1]=p;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(i+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;b[a+2>>1]=p;d=(d+(h<<1)+j|0)>>>2&65535;b[a+(t+7<<1)>>1]=d;b[a+(s+6<<1)>>1]=d;b[a+(c+5<<1)>>1]=d;b[a+(i+4<<1)>>1]=d;b[a+(r+3<<1)>>1]=d;b[a+4>>1]=d;d=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=d;b[a+(c+6<<1)>>1]=d;b[a+(i+5<<1)>>1]=d;b[a+(r+4<<1)>>1]=d;b[a+6>>1]=d;d=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=d;b[a+(i+6<<1)>>1]=d;b[a+(r+5<<1)>>1]=d;b[a+8>>1]=d;d=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(i+7<<1)>>1]=d;b[a+(r+6<<1)>>1]=d;b[a+10>>1]=d;d=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(r+7<<1)>>1]=d;b[a+12>>1]=d;b[a+14>>1]=(n+2+(q<<1)+(((f&65535)+2+(o<<1)+m|0)>>>2)|0)>>>2;return}function Fl(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;d=((f&65535)+2+(o<<1)+m|0)>>>2;u=a+(i<<1)|0;t=a+-2|0;B=e[t>>1]|0;v=e[a+(r+-1<<1)>>1]|0;t=((e[(c?u:t)>>1]|0)+2+(B<<1)+v|0)>>>2;o=r<<1;y=e[a+(o+-1<<1)>>1]|0;B=B+2|0;w=(B+(v<<1)+y|0)>>>2;m=r*3|0;x=e[a+(m+-1<<1)>>1]|0;v=(v+2+(y<<1)+x|0)>>>2;i=r<<2;A=e[a+(i+-1<<1)>>1]|0;y=(y+2+(x<<1)+A|0)>>>2;c=r*5|0;C=e[a+(c+-1<<1)>>1]|0;x=(x+2+(A<<1)+C|0)>>>2;f=r*6|0;z=e[a+(f+-1<<1)>>1]|0;A=(A+2+(C<<1)+z|0)>>>2;s=r*7|0;z=(C+2+(z<<1)+(e[a+(s+-1<<1)>>1]|0)|0)>>>2;u=(B+((e[u>>1]|0)<<1)+p|0)>>>2;b[a+(f<<1)>>1]=(y+2+(x<<1)+A|0)>>>2;b[a+(s<<1)>>1]=(x+2+(A<<1)+z|0)>>>2;p=(w+2+(v<<1)+y|0)>>>2&65535;b[a+((f|1)<<1)>>1]=p;b[a+(i<<1)>>1]=p;p=(v+2+(y<<1)+x|0)>>>2&65535;b[a+(s+1<<1)>>1]=p;b[a+(c<<1)>>1]=p;p=((t<<1)+2+w+u|0)>>>2&65535;b[a+(f+2<<1)>>1]=p;b[a+((i|1)<<1)>>1]=p;b[a+(o<<1)>>1]=p;p=(t+2+(w<<1)+v|0)>>>2&65535;b[a+(s+2<<1)>>1]=p;b[a+(c+1<<1)>>1]=p;b[a+(m<<1)>>1]=p;p=g+2|0;t=(p+t+(u<<1)|0)>>>2&65535;b[a+(s+3<<1)>>1]=t;b[a+(c+2<<1)>>1]=t;b[a+(m+1<<1)>>1]=t;b[a+(r<<1)>>1]=t;t=g+1|0;v=(t+u|0)>>>1&65535;b[a+(f+3<<1)>>1]=v;b[a+((i|2)<<1)>>1]=v;b[a+((o|1)<<1)>>1]=v;b[a>>1]=v;g=((g<<1)+2+h+u|0)>>>2&65535;b[a+(s+4<<1)>>1]=g;b[a+(c+3<<1)>>1]=g;b[a+(m+2<<1)>>1]=g;b[a+(r+1<<1)>>1]=g;g=(t+h|0)>>>1&65535;b[a+(f+4<<1)>>1]=g;b[a+((i|3)<<1)>>1]=g;b[a+(o+2<<1)>>1]=g;b[a+2>>1]=g;p=(p+(h<<1)+j|0)>>>2&65535;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(m+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;p=(h+1+j|0)>>>1&65535;b[a+(f+5<<1)>>1]=p;b[a+(i+4<<1)>>1]=p;b[a+(o+3<<1)>>1]=p;b[a+4>>1]=p;p=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+6<<1)>>1]=p;b[a+(c+5<<1)>>1]=p;b[a+(m+4<<1)>>1]=p;b[a+(r+3<<1)>>1]=p;p=(j+1+k|0)>>>1&65535;b[a+(f+6<<1)>>1]=p;b[a+(i+5<<1)>>1]=p;b[a+(o+4<<1)>>1]=p;b[a+6>>1]=p;p=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(s+7<<1)>>1]=p;b[a+(c+6<<1)>>1]=p;b[a+(m+5<<1)>>1]=p;b[a+(r+4<<1)>>1]=p;p=(k+1+l|0)>>>1&65535;b[a+(f+7<<1)>>1]=p;b[a+(i+6<<1)>>1]=p;b[a+(o+5<<1)>>1]=p;b[a+8>>1]=p;p=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(c+7<<1)>>1]=p;b[a+(m+6<<1)>>1]=p;b[a+(r+5<<1)>>1]=p;p=(l+1+n|0)>>>1&65535;b[a+(i+7<<1)>>1]=p;b[a+(o+6<<1)>>1]=p;b[a+10>>1]=p;p=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(m+7<<1)>>1]=p;b[a+(r+6<<1)>>1]=p;p=(n+1+q|0)>>>1&65535;b[a+(o+7<<1)>>1]=p;b[a+12>>1]=p;b[a+(r+7<<1)>>1]=(n+2+(q<<1)+d|0)>>>2;b[a+14>>1]=(q+1+d|0)>>>1;return}function Gl(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;i=f>>1;l=(c|0)!=0;n=0-i|0;m=~i;B=e[a+(n<<1)>>1]|0;h=e[a+(1-i<<1)>>1]|0;n=((e[a+((l?m:n)<<1)>>1]|0)+2+(B<<1)+h|0)>>>2;g=e[a+(2-i<<1)>>1]|0;B=B+2|0;j=(B+(h<<1)+g|0)>>>2;f=e[a+(3-i<<1)>>1]|0;h=(h+2+(g<<1)+f|0)>>>2;c=e[a+(4-i<<1)>>1]|0;g=(g+2+(f<<1)+c|0)>>>2;o=e[a+(5-i<<1)>>1]|0;f=(f+2+(c<<1)+o|0)>>>2;d=e[a+(6-i<<1)>>1]|0;c=(c+2+(o<<1)+d|0)>>>2;d=(o+2+(d<<1)+(e[a+(7-i<<1)>>1]|0)|0)>>>2;m=a+(m<<1)|0;o=a+-2|0;A=e[o>>1]|0;k=e[a+(i+-1<<1)>>1]|0;o=((e[(l?m:o)>>1]|0)+2+(A<<1)+k|0)>>>2;l=i<<1;t=e[a+(l+-1<<1)>>1]|0;q=(A+2+(k<<1)+t|0)>>>2;p=i*3|0;v=e[a+(p+-1<<1)>>1]|0;k=(k+2+(t<<1)+v|0)>>>2;r=i<<2;x=e[a+(r+-1<<1)>>1]|0;t=(t+2+(v<<1)+x|0)>>>2;s=i*5|0;y=e[a+(s+-1<<1)>>1]|0;v=(v+2+(x<<1)+y|0)>>>2;u=i*6|0;C=e[a+(u+-1<<1)>>1]|0;x=(x+2+(y<<1)+C|0)>>>2;w=i*7|0;z=e[a+(w+-1<<1)>>1]|0;y=(y+2+(C<<1)+z|0)>>>2;z=(C+2+(z*3|0)|0)>>>2;m=(B+A+((e[m>>1]|0)<<1)|0)>>>2;b[a+(w<<1)>>1]=(z+1+y|0)>>>1;b[a+(w+1<<1)>>1]=(x+2+z+(y<<1)|0)>>>2;z=(x+1+y|0)>>>1&65535;b[a+(w+2<<1)>>1]=z;b[a+(u<<1)>>1]=z;y=(v+2+(x<<1)+y|0)>>>2&65535;b[a+(w+3<<1)>>1]=y;b[a+((u|1)<<1)>>1]=y;y=(v+1+x|0)>>>1&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+2<<1)>>1]=y;b[a+(s<<1)>>1]=y;x=(t+2+(v<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+3<<1)>>1]=x;b[a+(s+1<<1)>>1]=x;x=(t+1+v|0)>>>1&65535;b[a+(w+6<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(s+2<<1)>>1]=x;b[a+(r<<1)>>1]=x;v=(k+2+(t<<1)+v|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+5<<1)>>1]=v;b[a+(s+3<<1)>>1]=v;b[a+((r|1)<<1)>>1]=v;v=(k+1+t|0)>>>1&65535;b[a+(u+6<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+((r|2)<<1)>>1]=v;b[a+(p<<1)>>1]=v;t=(q+2+(k<<1)+t|0)>>>2&65535;b[a+(u+7<<1)>>1]=t;b[a+(s+5<<1)>>1]=t;b[a+((r|3)<<1)>>1]=t;b[a+(p+1<<1)>>1]=t;t=(q+1+k|0)>>>1&65535;b[a+(s+6<<1)>>1]=t;b[a+(r+4<<1)>>1]=t;b[a+(p+2<<1)>>1]=t;b[a+(l<<1)>>1]=t;k=(o+2+(q<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=k;b[a+(r+5<<1)>>1]=k;b[a+(p+3<<1)>>1]=k;b[a+((l|1)<<1)>>1]=k;k=o+1|0;s=(k+q|0)>>>1&65535;b[a+(r+6<<1)>>1]=s;b[a+(p+4<<1)>>1]=s;b[a+(l+2<<1)>>1]=s;b[a+(i<<1)>>1]=s;q=((o<<1)+2+q+m|0)>>>2&65535;b[a+(r+7<<1)>>1]=q;b[a+(p+5<<1)>>1]=q;b[a+(l+3<<1)>>1]=q;b[a+(i+1<<1)>>1]=q;k=(k+m|0)>>>1&65535;b[a+(p+6<<1)>>1]=k;b[a+(l+4<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a>>1]=k;k=n+2|0;o=(k+o+(m<<1)|0)>>>2&65535;b[a+(p+7<<1)>>1]=o;b[a+(l+5<<1)>>1]=o;b[a+(i+3<<1)>>1]=o;b[a+2>>1]=o;m=((n<<1)+2+j+m|0)>>>2&65535;b[a+(l+6<<1)>>1]=m;b[a+(i+4<<1)>>1]=m;b[a+4>>1]=m;k=(k+(j<<1)+h|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(i+5<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+(h<<1)+g|0)>>>2&65535;b[a+(i+6<<1)>>1]=j;b[a+8>>1]=j;h=(h+2+(g<<1)+f|0)>>>2&65535;b[a+(i+7<<1)>>1]=h;b[a+10>>1]=h;b[a+12>>1]=(g+2+(f<<1)+c|0)>>>2;b[a+14>>1]=(f+2+(c<<1)+d|0)>>>2;return}function Hl(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;s=f>>1;k=0-s|0;l=e[a+(k<<1)>>1]|0;m=e[a+(1-s<<1)>>1]|0;k=((e[a+(((c|0)==0?k:~s)<<1)>>1]|0)+2+(l<<1)+m|0)>>>2;n=e[a+(2-s<<1)>>1]|0;l=(l+2+(m<<1)+n|0)>>>2;o=e[a+(3-s<<1)>>1]|0;m=(m+2+(n<<1)+o|0)>>>2;q=e[a+(4-s<<1)>>1]|0;n=(n+2+(o<<1)+q|0)>>>2;r=e[a+(5-s<<1)>>1]|0;o=(o+2+(q<<1)+r|0)>>>2;p=e[a+(6-s<<1)>>1]|0;q=(q+2+(r<<1)+p|0)>>>2;f=b[a+(7-s<<1)>>1]|0;j=f&65535;r=(r+2+(p<<1)+j|0)>>>2;if(!d){c=(f&65535)+2+(j<<1)|0;d=j;g=j;h=j;i=j;f=j}else{u=e[a+(8-s<<1)>>1]|0;t=e[a+(9-s<<1)>>1]|0;f=e[a+(10-s<<1)>>1]|0;i=e[a+(11-s<<1)>>1]|0;h=e[a+(12-s<<1)>>1]|0;c=(e[a+(8-s<<1)>>1]|0)+2+(j<<1)|0;d=(t+2+(f<<1)+i|0)>>>2;g=(f+2+(i<<1)+h|0)>>>2;h=(i+2+(h<<1)+(e[a+(13-s<<1)>>1]|0)|0)>>>2;i=(j+2+(u<<1)+t|0)>>>2;f=(u+2+(t<<1)+f|0)>>>2}c=(c+p|0)>>>2;b[a>>1]=(k+1+l|0)>>>1;b[a+(s<<1)>>1]=(k+2+(l<<1)+m|0)>>>2;u=(l+1+m|0)>>>1&65535;b[a+2>>1]=u;j=s<<1;b[a+(j<<1)>>1]=u;u=(l+2+(m<<1)+n|0)>>>2&65535;b[a+(s+1<<1)>>1]=u;k=s*3|0;b[a+(k<<1)>>1]=u;u=(m+1+n|0)>>>1&65535;b[a+4>>1]=u;b[a+((j|1)<<1)>>1]=u;l=s<<2;b[a+(l<<1)>>1]=u;u=(m+2+(n<<1)+o|0)>>>2&65535;b[a+(s+2<<1)>>1]=u;b[a+(k+1<<1)>>1]=u;p=s*5|0;b[a+(p<<1)>>1]=u;u=(n+1+o|0)>>>1&65535;b[a+6>>1]=u;b[a+(j+2<<1)>>1]=u;b[a+((l|1)<<1)>>1]=u;t=s*6|0;b[a+(t<<1)>>1]=u;n=(n+2+(o<<1)+q|0)>>>2&65535;b[a+(s+3<<1)>>1]=n;b[a+(k+2<<1)>>1]=n;b[a+(p+1<<1)>>1]=n;u=s*7|0;b[a+(u<<1)>>1]=n;n=(o+1+q|0)>>>1&65535;b[a+8>>1]=n;b[a+(j+3<<1)>>1]=n;b[a+((l|2)<<1)>>1]=n;b[a+((t|1)<<1)>>1]=n;o=(o+2+(q<<1)+r|0)>>>2&65535;b[a+(s+4<<1)>>1]=o;b[a+(k+3<<1)>>1]=o;b[a+(p+2<<1)>>1]=o;b[a+(u+1<<1)>>1]=o;o=(q+1+r|0)>>>1&65535;b[a+10>>1]=o;b[a+(j+4<<1)>>1]=o;b[a+((l|3)<<1)>>1]=o;b[a+(t+2<<1)>>1]=o;q=(q+2+(r<<1)+c|0)>>>2&65535;b[a+(s+5<<1)>>1]=q;b[a+(k+4<<1)>>1]=q;b[a+(p+3<<1)>>1]=q;b[a+(u+2<<1)>>1]=q;q=(r+1+c|0)>>>1&65535;b[a+12>>1]=q;b[a+(j+5<<1)>>1]=q;b[a+(l+4<<1)>>1]=q;b[a+(t+3<<1)>>1]=q;r=(r+2+(c<<1)+i|0)>>>2&65535;b[a+(s+6<<1)>>1]=r;b[a+(k+5<<1)>>1]=r;b[a+(p+4<<1)>>1]=r;b[a+(u+3<<1)>>1]=r;r=(c+1+i|0)>>>1&65535;b[a+14>>1]=r;b[a+(j+6<<1)>>1]=r;b[a+(l+5<<1)>>1]=r;b[a+(t+4<<1)>>1]=r;r=(c+2+(i<<1)+f|0)>>>2&65535;b[a+(s+7<<1)>>1]=r;b[a+(k+6<<1)>>1]=r;b[a+(p+5<<1)>>1]=r;b[a+(u+4<<1)>>1]=r;s=(i+1+f|0)>>>1&65535;b[a+(j+7<<1)>>1]=s;b[a+(l+6<<1)>>1]=s;b[a+(t+5<<1)>>1]=s;s=(i+2+(f<<1)+d|0)>>>2&65535;b[a+(k+7<<1)>>1]=s;b[a+(p+6<<1)>>1]=s;b[a+(u+5<<1)>>1]=s;s=(f+1+d|0)>>>1&65535;b[a+(l+7<<1)>>1]=s;b[a+(t+6<<1)>>1]=s;s=(f+2+(d<<1)+g|0)>>>2&65535;b[a+(p+7<<1)>>1]=s;b[a+(u+6<<1)>>1]=s;b[a+(t+7<<1)>>1]=(d+1+g|0)>>>1;b[a+(u+7<<1)>>1]=(d+2+(g<<1)+h|0)>>>2;return}function Il(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;n=f>>1;r=a+-2|0;q=e[r>>1]|0;p=e[a+(n+-1<<1)>>1]|0;r=((e[((c|0)==0?r:a+(~n<<1)|0)>>1]|0)+2+(q<<1)+p|0)>>>2;l=n<<1;o=e[a+(l+-1<<1)>>1]|0;q=(q+2+(p<<1)+o|0)>>>2;j=n*3|0;m=e[a+(j+-1<<1)>>1]|0;p=(p+2+(o<<1)+m|0)>>>2;h=n<<2;k=e[a+(h+-1<<1)>>1]|0;o=(o+2+(m<<1)+k|0)>>>2;g=n*5|0;i=e[a+(g+-1<<1)>>1]|0;m=(m+2+(k<<1)+i|0)>>>2;f=n*6|0;s=e[a+(f+-1<<1)>>1]|0;k=(k+2+(i<<1)+s|0)>>>2;d=n*7|0;c=e[a+(d+-1<<1)>>1]|0;i=(i+2+(s<<1)+c|0)>>>2;c=(s+2+(c*3|0)|0)>>>2;b[a>>1]=(r+1+q|0)>>>1;b[a+2>>1]=(r+2+(q<<1)+p|0)>>>2;r=(q+1+p|0)>>>1&65535;b[a+4>>1]=r;b[a+(n<<1)>>1]=r;q=(q+2+(p<<1)+o|0)>>>2&65535;b[a+6>>1]=q;b[a+(n+1<<1)>>1]=q;q=(p+1+o|0)>>>1&65535;b[a+8>>1]=q;b[a+(n+2<<1)>>1]=q;b[a+(l<<1)>>1]=q;p=(p+2+(o<<1)+m|0)>>>2&65535;b[a+10>>1]=p;b[a+(n+3<<1)>>1]=p;b[a+((l|1)<<1)>>1]=p;p=(o+1+m|0)>>>1&65535;b[a+12>>1]=p;b[a+(n+4<<1)>>1]=p;b[a+(l+2<<1)>>1]=p;b[a+(j<<1)>>1]=p;o=(o+2+(m<<1)+k|0)>>>2&65535;b[a+14>>1]=o;b[a+(n+5<<1)>>1]=o;b[a+(l+3<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;o=(m+1+k|0)>>>1&65535;b[a+(n+6<<1)>>1]=o;b[a+(l+4<<1)>>1]=o;b[a+(j+2<<1)>>1]=o;b[a+(h<<1)>>1]=o;m=(m+2+(k<<1)+i|0)>>>2&65535;b[a+(n+7<<1)>>1]=m;b[a+(l+5<<1)>>1]=m;b[a+(j+3<<1)>>1]=m;b[a+((h|1)<<1)>>1]=m;m=(k+1+i|0)>>>1&65535;b[a+(l+6<<1)>>1]=m;b[a+(j+4<<1)>>1]=m;b[a+((h|2)<<1)>>1]=m;b[a+(g<<1)>>1]=m;k=(k+2+c+(i<<1)|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(j+5<<1)>>1]=k;b[a+((h|3)<<1)>>1]=k;b[a+(g+1<<1)>>1]=k;k=(c+1+i|0)>>>1&65535;b[a+(j+6<<1)>>1]=k;b[a+(h+4<<1)>>1]=k;b[a+(g+2<<1)>>1]=k;b[a+(f<<1)>>1]=k;i=(i+2+(c*3|0)|0)>>>2&65535;b[a+(j+7<<1)>>1]=i;b[a+(h+5<<1)>>1]=i;b[a+(g+3<<1)>>1]=i;b[a+((f|1)<<1)>>1]=i;c=c&65535;b[a+(d+7<<1)>>1]=c;b[a+(f+7<<1)>>1]=c;b[a+(g+7<<1)>>1]=c;b[a+(h+7<<1)>>1]=c;b[a+(d+6<<1)>>1]=c;b[a+(f+6<<1)>>1]=c;b[a+(g+6<<1)>>1]=c;b[a+(h+6<<1)>>1]=c;b[a+(d+5<<1)>>1]=c;b[a+(f+5<<1)>>1]=c;b[a+(g+5<<1)>>1]=c;b[a+(d+4<<1)>>1]=c;b[a+(f+4<<1)>>1]=c;b[a+(g+4<<1)>>1]=c;b[a+(d+3<<1)>>1]=c;b[a+(f+3<<1)>>1]=c;b[a+(d+2<<1)>>1]=c;b[a+(f+2<<1)>>1]=c;b[a+(d+1<<1)>>1]=c;b[a+(d<<1)>>1]=c;return}function Jl(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;d=f>>1;n=a+-2|0;m=e[n>>1]|0;l=e[a+(d+-1<<1)>>1]|0;k=e[a+((d<<1)+-1<<1)>>1]|0;j=e[a+((d*3|0)+-1<<1)>>1]|0;i=e[a+((d<<2)+-1<<1)>>1]|0;h=e[a+((d*5|0)+-1<<1)>>1]|0;g=e[a+((d*6|0)+-1<<1)>>1]|0;f=e[a+((d*7|0)+-1<<1)>>1]|0;f=(((e[((b|0)==0?n:a+(~d<<1)|0)>>1]|0)+2+(m<<1)+l|0)>>>2)+4+((m+2+(l<<1)+k|0)>>>2)+((l+2+(k<<1)+j|0)>>>2)+((k+2+(j<<1)+i|0)>>>2)+((j+2+(i<<1)+h|0)>>>2)+((i+2+(h<<1)+g|0)>>>2)+((h+2+(g<<1)+f|0)>>>2)+((g+2+(f*3|0)|0)>>>2)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;b=C;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function Kl(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;q=(((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2)+4+((q+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+((l+2+(m<<1)+n|0)>>>2)+((m+2+(n<<1)+o|0)>>>2)+((n+2+(o<<1)+p|0)>>>2)+(((g&65535)+2+(p<<1)+o|0)>>>2)>>3;q=RF(q|0,((q|0)<0)<<31>>31|0,65537,65537)|0;f=C;d=a;c[d>>2]=q;c[d+4>>2]=f;d=a+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function Ll(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;d=e>>1;b=a;c[b>>2]=536879104;c[b+4>>2]=536879104;b=a+8|0;c[b>>2]=536879104;c[b+4>>2]=536879104;b=a+(d<<1)|0;e=b;c[e>>2]=536879104;c[e+4>>2]=536879104;e=b+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;b=b+(d<<1)|0;e=b;c[e>>2]=536879104;c[e+4>>2]=536879104;e=b+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;b=b+(d<<1)|0;e=b;c[e>>2]=536879104;c[e+4>>2]=536879104;e=b+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;b=b+(d<<1)|0;e=b;c[e>>2]=536879104;c[e+4>>2]=536879104;e=b+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;b=b+(d<<1)|0;e=b;c[e>>2]=536879104;c[e+4>>2]=536879104;e=b+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;b=b+(d<<1)|0;e=b;c[e>>2]=536879104;c[e+4>>2]=536879104;e=b+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;d=b+(d<<1)|0;b=d;c[b>>2]=536879104;c[b+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;return}function Ml(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function Nl(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*7<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*9<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*10<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*11<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*12<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*13<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*14<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function Ol(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0;b=b>>1;f=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;g=C;d=a;c[d>>2]=f;c[d+4>>2]=g;d=a+8|0;c[d>>2]=f;c[d+4>>2]=g;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(b<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<1;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*3|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<2;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*5|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b*6|0;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;b=b*7|0;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Pl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;f=f+8|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function Ql(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;d=c>>1;g=0-d|0;h=a+6+(g<<1)|0;j=a+(d<<2<<1)+-2|0;n=d<<1;i=j+(0-n<<1)|0;l=j+(d<<1)|0;k=i+(g<<1)|0;f=l+(d<<1)|0;m=k+(g<<1)|0;h=((((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+((((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+(((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+((e[h+2>>1]|0)-(e[h+-2>>1]|0))))|0)*17|0)+16>>5;i=((((e[f+(d<<1)>>1]|0)-(e[m+(g<<1)>>1]|0)<<2)+((((e[f>>1]|0)-(e[m>>1]|0)|0)*3|0)+(((e[l>>1]|0)-(e[k>>1]|0)<<1)+((e[j>>1]|0)-(e[i>>1]|0))))|0)*17|0)+16>>5;j=h<<1;k=h*3|0;l=h<<2;m=h*5|0;f=h*6|0;g=h*7|0;n=(_(i+h|0,-3)|0)+16+((e[a+(-2-n)+16>>1]|0)+(e[a+((d*14|0)+-2)>>1]|0)<<4)|0;o=8;while(1){c=n>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a>>1]=c;c=n+h>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+2>>1]=c;c=n+j>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+4>>1]=c;c=n+k>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+6>>1]=c;c=n+l>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+8>>1]=c;c=n+m>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+10>>1]=c;c=n+f>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+12>>1]=c;c=n+g>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+14>>1]=c;if((o|0)>1){n=n+i|0;o=o+-1|0;a=a+(d<<1)|0}else break}return}function Rl(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;d=c>>1;n=0-d|0;f=a+6+(n<<1)|0;g=a+(d<<3<<1)+-2|0;o=g+(0-(d<<1)<<1)|0;p=a+((d*6|0)+-2)|0;m=a+((d*22|0)+-2)|0;i=g+(d<<1)|0;h=o+(n<<1)|0;k=i+(d<<1)|0;j=h+(n<<1)|0;c=_(d,-3)|0;q=m+(d<<1)|0;l=p+(n<<1)|0;s=q+(d<<1)|0;r=l+(n<<1)|0;u=s+(d<<1)|0;t=r+(n<<1)|0;o=((((e[u+(d<<1)>>1]|0)-(e[t+(n<<1)>>1]|0)<<3)+((((e[u>>1]|0)-(e[t>>1]|0)|0)*7|0)+((((e[s>>1]|0)-(e[r>>1]|0)|0)*6|0)+((((e[q>>1]|0)-(e[l>>1]|0)|0)*5|0)+(((e[k+(d<<1)>>1]|0)-(e[j+(n<<1)>>1]|0)<<2)+((((e[k>>1]|0)-(e[j>>1]|0)|0)*3|0)+(((e[i>>1]|0)-(e[h>>1]|0)<<1)+((e[g>>1]|0)-(e[o>>1]|0))))))))|0)*5|0)+32>>6;f=((((e[f+8>>1]|0)-(e[f+-8>>1]|0)<<2)+((((e[f+6>>1]|0)-(e[f+-6>>1]|0)|0)*3|0)+(((e[f+4>>1]|0)-(e[f+-4>>1]|0)<<1)+((e[f+2>>1]|0)-(e[f+-2>>1]|0))))|0)*17|0)+16>>5;g=f*3|0;h=f<<1;i=f<<2;j=f*5|0;k=f*6|0;l=f*7|0;m=16-g+(_(o,-7)|0)+((e[p+(c<<1)+(n<<1)+16>>1]|0)+(e[m+(d<<3)>>1]|0)<<4)|0;n=16;while(1){c=m>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a>>1]=c;c=m+f>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+2>>1]=c;c=m+h>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+4>>1]=c;c=m+g>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+6>>1]=c;c=m+i>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+8>>1]=c;c=m+j>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+10>>1]=c;c=m+k>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+12>>1]=c;c=m+l>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+14>>1]=c;if((n|0)>1){m=m+o|0;n=n+-1|0;a=a+(d<<1)|0}else break}return}function Sl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=b>>1;k=p<<2;j=p*5|0;m=p<<1;i=p*6|0;h=p*3|0;f=(e[a+(7-p<<1)>>1]|0)+((e[a+(6-p<<1)>>1]|0)+((e[a+(5-p<<1)>>1]|0)+(e[a+(4-p<<1)>>1]|0)))|0;b=p*7|0;d=(e[a+(b+-1<<1)>>1]|0)+((e[a+(i+-1<<1)>>1]|0)+((e[a+(j+-1<<1)>>1]|0)+(e[a+(k+-1<<1)>>1]|0)))|0;o=(e[a+(h+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+((e[a+(p+-1<<1)>>1]|0)+((e[a+-2>>1]|0)+(e[a+(0-p<<1)>>1]|0))+(e[a+(1-p<<1)>>1]|0))+(e[a+(2-p<<1)>>1]|0))+(e[a+(3-p<<1)>>1]|0)+4>>3;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;n=C;l=RF((f+2|0)>>>2|0,0,65537,65537)|0;g=C;q=a;c[q>>2]=o;c[q+4>>2]=n;q=a+8|0;c[q>>2]=l;c[q+4>>2]=g;p=a+(p<<1)|0;q=p;c[q>>2]=o;c[q+4>>2]=n;p=p+8|0;c[p>>2]=l;c[p+4>>2]=g;m=a+(m<<1)|0;p=m;c[p>>2]=o;c[p+4>>2]=n;m=m+8|0;c[m>>2]=l;c[m+4>>2]=g;h=a+(h<<1)|0;m=h;c[m>>2]=o;c[m+4>>2]=n;h=h+8|0;c[h>>2]=l;c[h+4>>2]=g;h=RF((d+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((d+4+f|0)>>>3|0,0,65537,65537)|0;d=C;k=a+(k<<1)|0;l=k;c[l>>2]=h;c[l+4>>2]=g;k=k+8|0;c[k>>2]=f;c[k+4>>2]=d;j=a+(j<<1)|0;k=j;c[k>>2]=h;c[k+4>>2]=g;j=j+8|0;c[j>>2]=f;c[j+4>>2]=d;i=a+(i<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Tl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;m=b>>1;i=m<<2;h=m*5|0;l=m<<1;g=m*6|0;d=m*3|0;b=m*7|0;f=(e[a+(b+-1<<1)>>1]|0)+((e[a+(g+-1<<1)>>1]|0)+((e[a+(h+-1<<1)>>1]|0)+(e[a+(i+-1<<1)>>1]|0)))|0;k=RF(((e[a+(d+-1<<1)>>1]|0)+((e[a+(l+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;j=C;n=a;c[n>>2]=k;c[n+4>>2]=j;n=a+8|0;c[n>>2]=k;c[n+4>>2]=j;m=a+(m<<1)|0;n=m;c[n>>2]=k;c[n+4>>2]=j;m=m+8|0;c[m>>2]=k;c[m+4>>2]=j;l=a+(l<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;l=l+8|0;c[l>>2]=k;c[l+4>>2]=j;d=a+(d<<1)|0;l=d;c[l>>2]=k;c[l+4>>2]=j;d=d+8|0;c[d>>2]=k;c[d+4>>2]=j;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a+(i<<1)|0;j=i;c[j>>2]=f;c[j+4>>2]=d;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;i=h;c[i>>2]=f;c[i+4>>2]=d;h=h+8|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;h=g;c[h>>2]=f;c[h+4>>2]=d;g=g+8|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Ul(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Vl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;i=b>>1;p=4-i|0;m=a+(0-i<<1)|0;d=e[a+(p<<1)>>1]|0;l=a+(1-i<<1)|0;h=e[a+(p+1<<1)>>1]|0;k=a+(2-i<<1)|0;o=e[a+(p+2<<1)>>1]|0;j=a+(3-i<<1)|0;p=e[a+(p+3<<1)>>1]|0;s=RF(((e[m>>1]|0)+2+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;p=RF((d+2+h+o+p|0)>>>2|0,0,65537,65537)|0;o=C;h=a;c[h>>2]=s;c[h+4>>2]=r;h=a+8|0;c[h>>2]=p;c[h+4>>2]=o;h=a+(i<<1)|0;d=h;c[d>>2]=s;c[d+4>>2]=r;d=h+8|0;c[d>>2]=p;c[d+4>>2]=o;d=i<<1;g=a+(d<<1)|0;f=g;c[f>>2]=s;c[f+4>>2]=r;f=g+8|0;c[f>>2]=p;c[f+4>>2]=o;f=i*3|0;b=a+(f<<1)|0;n=b;c[n>>2]=s;c[n+4>>2]=r;n=b+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i<<2<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*5<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*6<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*7<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;f=(e[m>>1]|0)+4+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)+(e[a+-2>>1]|0)+(e[a+(i+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;c[a>>2]=f;c[a+4>>2]=d;a=h;c[a>>2]=f;c[a+4>>2]=d;a=g;c[a>>2]=f;c[a+4>>2]=d;c[b>>2]=f;c[b+4>>2]=d;return}function Wl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;Sl(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Xl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;g=b>>1;m=g<<2;o=e[a+(m+-1<<1)>>1]|0;l=g*5|0;n=e[a+(l+-1<<1)>>1]|0;f=g<<1;k=g*6|0;i=e[a+(k+-1<<1)>>1]|0;d=g*3|0;h=g*7|0;j=e[a+(h+-1<<1)>>1]|0;r=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;q=C;p=a;c[p>>2]=r;c[p+4>>2]=q;p=a+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(g<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(f<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(d<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;j=RF((o+2+n+i+j|0)>>>2|0,0,65537,65537)|0;i=C;m=a+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=a+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;k=a+(k<<1)|0;l=k;c[l>>2]=j;c[l+4>>2]=i;k=k+8|0;c[k>>2]=j;c[k+4>>2]=i;h=a+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(g<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(f<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(d<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+8|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(g<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(f<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+(d<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function Yl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=b>>1;p=g<<2;r=e[a+(p+-1<<1)>>1]|0;o=g*5|0;q=e[a+(o+-1<<1)>>1]|0;f=g<<1;n=g*6|0;l=e[a+(n+-1<<1)>>1]|0;b=g*3|0;k=g*7|0;m=e[a+(k+-1<<1)>>1]|0;u=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;t=C;d=a+8|0;j=a+(g<<1)|0;i=j+8|0;c[i>>2]=u;c[i+4>>2]=t;i=a+(f<<1)|0;h=i+8|0;c[h>>2]=u;c[h+4>>2]=t;h=a+(b<<1)|0;s=h+8|0;c[s>>2]=u;c[s+4>>2]=t;m=RF((r+2+q+l+m|0)>>>2|0,0,65537,65537)|0;l=C;p=a+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=a+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;n=a+(n<<1)|0;o=n;c[o>>2]=m;c[o+4>>2]=l;n=n+8|0;c[n>>2]=m;c[n+4>>2]=l;k=a+(k<<1)|0;n=k;c[n>>2]=m;c[n+4>>2]=l;k=k+8|0;c[k>>2]=m;c[k+4>>2]=l;c[a>>2]=536879104;c[a+4>>2]=536879104;a=j;c[a>>2]=536879104;c[a+4>>2]=536879104;a=i;c[a>>2]=536879104;c[a+4>>2]=536879104;a=h;c[a>>2]=536879104;c[a+4>>2]=536879104;a=d;c[a>>2]=536879104;c[a+4>>2]=536879104;a=d+(g<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=d+(f<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=d+(b<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function Zl(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;j=b>>1;h=0;g=0;f=0;d=0;b=0;i=0;do{k=e[a+((_(i,j)|0)+-1<<1)>>1]|0;h=k+h+(e[a+(i-j<<1)>>1]|0)|0;k=i+4|0;g=(e[a+(k-j<<1)>>1]|0)+g|0;f=(e[a+((_(k,j)|0)+-1<<1)>>1]|0)+f|0;d=(e[a+((_(i+8|0,j)|0)+-1<<1)>>1]|0)+d|0;b=(e[a+((_(i+12|0,j)|0)+-1<<1)>>1]|0)+b|0;i=i+1|0}while((i|0)!=4);k=h+4>>3;k=RF(k|0,((k|0)<0)<<31>>31|0,65537,65537)|0;i=C;m=g+2>>2;m=RF(m|0,((m|0)<0)<<31>>31|0,65537,65537)|0;l=C;r=f+2>>2;o=f+4+g>>3;n=d+2>>2;d=d+4+g>>3;f=b+2>>2;h=b+4+g>>3;q=a;c[q>>2]=k;c[q+4>>2]=i;q=a+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j*3<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;r=RF(r|0,((r|0)<0)<<31>>31|0,65537,65537)|0;q=C;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;l=C;n=RF(n|0,((n|0)<0)<<31>>31|0,65537,65537)|0;m=C;b=RF(d|0,((d|0)<0)<<31>>31|0,65537,65537)|0;d=C;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;g=C;h=RF(h|0,((h|0)<0)<<31>>31|0,65537,65537)|0;i=C;k=a+(j<<2<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*5<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*6<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*7<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j<<3<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*9<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*10<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*11<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*12<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*13<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*14<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*15<<1)|0;a=k;c[a>>2]=f;c[a+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;return}function _l(a,b){a=a|0;b=b|0;Tl(a,b);Tl(a+(b<<3)|0,b);return}function $l(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*7<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*9<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*10<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*11<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*12<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*13<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*14<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function am(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;$l(a,b);h=b>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function bm(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;Zl(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function cm(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;g=b>>1;n=g<<2;p=n+-1|0;v=e[a+(p<<1)>>1]|0;q=g+-1|0;m=g*5|0;o=m+-1|0;k=e[a+(o<<1)>>1]|0;f=g<<1;r=f+-1|0;l=g*6|0;i=l+-1|0;t=e[a+(i<<1)>>1]|0;d=g*3|0;s=d+-1|0;h=g*7|0;j=h+-1|0;u=e[a+(j<<1)>>1]|0;y=RF(((e[a+-2>>1]|0)+2+(e[a+(q<<1)>>1]|0)+(e[a+(r<<1)>>1]|0)+(e[a+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;x=C;w=a;c[w>>2]=y;c[w+4>>2]=x;w=a+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(g<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(f<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(d<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;u=RF((v+2+k+t+u|0)>>>2|0,0,65537,65537)|0;t=C;k=a+(n<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(m<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(l<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(h<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(b<<3)|0;p=e[k+(p<<1)>>1]|0;o=e[k+(o<<1)>>1]|0;i=e[k+(i<<1)>>1]|0;j=e[k+(j<<1)>>1]|0;s=RF(((e[k+-2>>1]|0)+2+(e[k+(q<<1)>>1]|0)+(e[k+(r<<1)>>1]|0)+(e[k+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;q=k;c[q>>2]=s;c[q+4>>2]=r;q=k+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(g<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(f<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(d<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;j=RF((p+2+o+i+j|0)>>>2|0,0,65537,65537)|0;i=C;n=k+(n<<1)|0;o=n;c[o>>2]=j;c[o+4>>2]=i;n=n+8|0;c[n>>2]=j;c[n+4>>2]=i;m=k+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=k+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;h=k+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(g<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(f<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(d<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+8|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(g<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(f<<1)|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+(d<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function dm(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0;h=b>>1;q=h<<2;s=q+-1|0;z=e[a+(s<<1)>>1]|0;t=h+-1|0;p=h*5|0;r=p+-1|0;w=e[a+(r<<1)>>1]|0;g=h<<1;u=g+-1|0;o=h*6|0;l=o+-1|0;x=e[a+(l<<1)>>1]|0;d=h*3|0;v=d+-1|0;n=h*7|0;m=n+-1|0;y=e[a+(m<<1)>>1]|0;D=RF(((e[a+-2>>1]|0)+2+(e[a+(t<<1)>>1]|0)+(e[a+(u<<1)>>1]|0)+(e[a+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;B=C;f=a;c[f>>2]=D;c[f+4>>2]=B;f=a+8|0;k=f;c[k>>2]=D;c[k+4>>2]=B;k=a+(h<<1)|0;j=k;c[j>>2]=D;c[j+4>>2]=B;j=k+8|0;c[j>>2]=D;c[j+4>>2]=B;j=a+(g<<1)|0;i=j;c[i>>2]=D;c[i+4>>2]=B;i=j+8|0;c[i>>2]=D;c[i+4>>2]=B;i=a+(d<<1)|0;A=i;c[A>>2]=D;c[A+4>>2]=B;A=i+8|0;c[A>>2]=D;c[A+4>>2]=B;y=RF((z+2+w+x+y|0)>>>2|0,0,65537,65537)|0;x=C;w=a+(q<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(p<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(o<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(n<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;b=a+(b<<3)|0;s=e[b+(s<<1)>>1]|0;r=e[b+(r<<1)>>1]|0;l=e[b+(l<<1)>>1]|0;m=e[b+(m<<1)>>1]|0;v=RF(((e[b+-2>>1]|0)+2+(e[b+(t<<1)>>1]|0)+(e[b+(u<<1)>>1]|0)+(e[b+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;u=C;t=b;c[t>>2]=v;c[t+4>>2]=u;t=b+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(h<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(g<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(d<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;m=RF((s+2+r+l+m|0)>>>2|0,0,65537,65537)|0;l=C;q=b+(q<<1)|0;r=q;c[r>>2]=m;c[r+4>>2]=l;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;p=b+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=b+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;b=b+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;b=a;c[b>>2]=536879104;c[b+4>>2]=536879104;b=k;c[b>>2]=536879104;c[b+4>>2]=536879104;b=j;c[b>>2]=536879104;c[b+4>>2]=536879104;b=i;c[b>>2]=536879104;c[b+4>>2]=536879104;b=f;c[b>>2]=536879104;c[b+4>>2]=536879104;b=f+(h<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;b=f+(g<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;b=f+(d<<1)|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function em(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b<<1)|0;e=d;c[e>>2]=536813567;c[e+4>>2]=536813567;d=d+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b<<1<<1)|0;e=d;c[e>>2]=536813567;c[e+4>>2]=536813567;d=d+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b*3<<1)|0;e=d;c[e>>2]=536813567;c[e+4>>2]=536813567;d=d+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b<<2<<1)|0;e=d;c[e>>2]=536813567;c[e+4>>2]=536813567;d=d+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b*5<<1)|0;e=d;c[e>>2]=536813567;c[e+4>>2]=536813567;d=d+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+(b*6<<1)|0;e=d;c[e>>2]=536813567;c[e+4>>2]=536813567;d=d+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;b=a+(b*7<<1)|0;a=b;c[a>>2]=536813567;c[a+4>>2]=536813567;b=b+8|0;c[b>>2]=536813567;c[b+4>>2]=536813567;return}function fm(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b<<1)|0;e=d;c[e>>2]=536944641;c[e+4>>2]=536944641;d=d+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b<<1<<1)|0;e=d;c[e>>2]=536944641;c[e+4>>2]=536944641;d=d+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b*3<<1)|0;e=d;c[e>>2]=536944641;c[e+4>>2]=536944641;d=d+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b<<2<<1)|0;e=d;c[e>>2]=536944641;c[e+4>>2]=536944641;d=d+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b*5<<1)|0;e=d;c[e>>2]=536944641;c[e+4>>2]=536944641;d=d+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+(b*6<<1)|0;e=d;c[e>>2]=536944641;c[e+4>>2]=536944641;d=d+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;b=a+(b*7<<1)|0;a=b;c[a>>2]=536944641;c[a+4>>2]=536944641;b=b+8|0;c[b>>2]=536944641;c[b+4>>2]=536944641;return}function gm(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b<<1)|0;e=d;c[e>>2]=536879104;c[e+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b<<1<<1)|0;e=d;c[e>>2]=536879104;c[e+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b*3<<1)|0;e=d;c[e>>2]=536879104;c[e+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b<<2<<1)|0;e=d;c[e>>2]=536879104;c[e+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b*5<<1)|0;e=d;c[e>>2]=536879104;c[e+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+(b*6<<1)|0;e=d;c[e>>2]=536879104;c[e+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;b=a+(b*7<<1)|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+8|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function hm(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;j=b>>1;i=a;c[i>>2]=536879104;c[i+4>>2]=536879104;i=a+8|0;c[i>>2]=536879104;c[i+4>>2]=536879104;i=a+(j<<1)|0;h=i;c[h>>2]=536879104;c[h+4>>2]=536879104;i=i+8|0;c[i>>2]=536879104;c[i+4>>2]=536879104;i=j<<1;h=a+(i<<1)|0;g=h;c[g>>2]=536879104;c[g+4>>2]=536879104;h=h+8|0;c[h>>2]=536879104;c[h+4>>2]=536879104;h=j*3|0;g=a+(h<<1)|0;f=g;c[f>>2]=536879104;c[f+4>>2]=536879104;g=g+8|0;c[g>>2]=536879104;c[g+4>>2]=536879104;g=j<<2;f=a+(g<<1)|0;e=f;c[e>>2]=536879104;c[e+4>>2]=536879104;f=f+8|0;c[f>>2]=536879104;c[f+4>>2]=536879104;f=j*5|0;e=a+(f<<1)|0;d=e;c[d>>2]=536879104;c[d+4>>2]=536879104;e=e+8|0;c[e>>2]=536879104;c[e+4>>2]=536879104;e=j*6|0;d=a+(e<<1)|0;k=d;c[k>>2]=536879104;c[k+4>>2]=536879104;d=d+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=j*7|0;k=a+(d<<1)|0;l=k;c[l>>2]=536879104;c[l+4>>2]=536879104;k=k+8|0;c[k>>2]=536879104;c[k+4>>2]=536879104;b=a+(b<<3)|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(j<<1)|0;j=a;c[j>>2]=536879104;c[j+4>>2]=536879104;a=a+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(i<<1)|0;i=a;c[i>>2]=536879104;c[i+4>>2]=536879104;a=a+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(h<<1)|0;h=a;c[h>>2]=536879104;c[h+4>>2]=536879104;a=a+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(g<<1)|0;g=a;c[g>>2]=536879104;c[g+4>>2]=536879104;a=a+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(f<<1)|0;f=a;c[f>>2]=536879104;c[f+4>>2]=536879104;a=a+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+(e<<1)|0;e=a;c[e>>2]=536879104;c[e+4>>2]=536879104;a=a+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+(d<<1)|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+8|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function im(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+((e[a+(0-b<<1)>>1]|0)+((e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))))))))))))))))))+16>>5;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function jm(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;b=b>>1;d=a+(0-b<<1)|0;j=d;k=c[j>>2]|0;j=c[j+4>>2]|0;h=d+8|0;i=c[h>>2]|0;h=c[h+4>>2]|0;f=d+16|0;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+24|0;e=c[d>>2]|0;d=c[d+4>>2]|0;l=a;c[l>>2]=k;c[l+4>>2]=j;l=a+8|0;c[l>>2]=i;c[l+4>>2]=h;l=a+16|0;c[l>>2]=g;c[l+4>>2]=f;l=a+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<2<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*5<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*6<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*7<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*9<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*10<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*11<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*12<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*13<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*14<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=k;c[a+4>>2]=j;a=b+8|0;c[a>>2]=i;c[a+4>>2]=h;a=b+16|0;c[a>>2]=g;c[a+4>>2]=f;b=b+24|0;c[b>>2]=e;c[b+4>>2]=d;return}function km(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;i=f+8|0;c[i>>2]=h;c[i+4>>2]=g;i=f+16|0;c[i>>2]=h;c[i+4>>2]=g;f=f+24|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function lm(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;a=a+(b<<1)|0;d=a;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+8|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+16|0;c[d>>2]=536813567;c[d+4>>2]=536813567;d=a+24|0;c[d>>2]=536813567;c[d+4>>2]=536813567;b=a+(b<<1)|0;a=b;c[a>>2]=536813567;c[a+4>>2]=536813567;a=b+8|0;c[a>>2]=536813567;c[a+4>>2]=536813567;a=b+16|0;c[a>>2]=536813567;c[a+4>>2]=536813567;b=b+24|0;c[b>>2]=536813567;c[b+4>>2]=536813567;return}function mm(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;a=a+(b<<1)|0;d=a;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+8|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+16|0;c[d>>2]=536944641;c[d+4>>2]=536944641;d=a+24|0;c[d>>2]=536944641;c[d+4>>2]=536944641;b=a+(b<<1)|0;a=b;c[a>>2]=536944641;c[a+4>>2]=536944641;a=b+8|0;c[a>>2]=536944641;c[a+4>>2]=536944641;a=b+16|0;c[a>>2]=536944641;c[a+4>>2]=536944641;b=b+24|0;c[b>>2]=536944641;c[b+4>>2]=536944641;return}function nm(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=c>>1;i=0-g|0;h=a+14+(i<<1)|0;u=a+(g<<3<<1)+-2|0;m=g<<1;t=u+(0-m<<1)|0;s=u+(g<<1)|0;r=t+(i<<1)|0;q=s+(g<<1)|0;p=r+(i<<1)|0;o=q+(g<<1)|0;c=p+(i<<1)|0;d=o+(g<<1)|0;f=c+(i<<1)|0;n=d+(g<<1)|0;l=f+(i<<1)|0;k=n+(g<<1)|0;j=l+(i<<1)|0;h=(((e[h+2>>1]|0)-(e[h+-2>>1]|0)+((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+(((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+(((e[h+10>>1]|0)-(e[h+-10>>1]|0)|0)*5|0)+(((e[h+12>>1]|0)-(e[h+-12>>1]|0)|0)*6|0)+(((e[h+14>>1]|0)-(e[h+-14>>1]|0)|0)*7|0)+((e[h+16>>1]|0)-(e[h+-16>>1]|0)<<3)|0)*5|0)+32>>6;i=(((e[u>>1]|0)-(e[t>>1]|0)+((e[s>>1]|0)-(e[r>>1]|0)<<1)+(((e[q>>1]|0)-(e[p>>1]|0)|0)*3|0)+((e[o>>1]|0)-(e[c>>1]|0)<<2)+(((e[d>>1]|0)-(e[f>>1]|0)|0)*5|0)+(((e[n>>1]|0)-(e[l>>1]|0)|0)*6|0)+(((e[k>>1]|0)-(e[j>>1]|0)|0)*7|0)+((e[k+(g<<1)>>1]|0)-(e[j+(i<<1)>>1]|0)<<3)|0)*5|0)+32>>6;j=h<<1;k=h*3|0;l=h<<2;m=((e[a+(-2-m)+32>>1]|0)+(e[a+((g*30|0)+-2)>>1]|0)<<4)+16+(_(i+h|0,-7)|0)|0;n=16;while(1){d=m;f=-16;while(1){c=d>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+(f+16<<1)>>1]=c;c=d+h>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+(f+17<<1)>>1]=c;c=d+j>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+(f+18<<1)>>1]=c;c=d+k>>5;if(c>>>0>=16384)c=0-c>>31&16383;b[a+(f+19<<1)>>1]=c;if((f|0)<-4){d=d+l|0;f=f+4|0}else break}if((n|0)>1){m=m+i|0;n=n+-1|0;a=a+(g<<1)|0}else break}return}function om(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function pm(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function qm(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;a=a+(b<<1)|0;d=a;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+8|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+16|0;c[d>>2]=536879104;c[d+4>>2]=536879104;d=a+24|0;c[d>>2]=536879104;c[d+4>>2]=536879104;b=a+(b<<1)|0;a=b;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+8|0;c[a>>2]=536879104;c[a+4>>2]=536879104;a=b+16|0;c[a>>2]=536879104;c[a+4>>2]=536879104;b=b+24|0;c[b>>2]=536879104;c[b+4>>2]=536879104;return}function rm(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0;j=f>>1;f=a+(0-j<<1)|0;i=j<<1;h=j*3|0;a=j<<2;g=(e[f>>1]|0)+(c[d>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+16>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+32>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+48>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+4>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+20>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+36>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+52>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+8>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+24>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+40>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+56>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+12>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+28>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+44>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+60>>2]|0)+g;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function sm(a,d,f){a=a|0;d=d|0;f=f|0;var g=0;f=f>>1;g=(e[a+-2>>1]|0)+(c[d>>2]|0)|0;b[a>>1]=g;g=(c[d+4>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+8>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+12>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+16>>2]|0)|0;b[a>>1]=g;g=(c[d+20>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+24>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+28>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+32>>2]|0)|0;b[a>>1]=g;g=(c[d+36>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+40>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+44>>2]|0)+g;a=a+(f<<1)|0;f=(e[a+-2>>1]|0)+(c[d+48>>2]|0)|0;b[a>>1]=f;f=(c[d+52>>2]|0)+f|0;b[a+2>>1]=f;f=(c[d+56>>2]|0)+f|0;b[a+4>>1]=f;b[a+6>>1]=(c[d+60>>2]|0)+f;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function tm(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;g=f>>1;h=g<<1;i=g*3|0;j=g<<2;k=g*5|0;l=g*6|0;m=g*7|0;n=g<<3;o=d;p=0;f=a+(0-g<<1)|0;while(1){a=(e[f>>1]|0)+(c[o>>2]|0)|0;b[f+(g<<1)>>1]=a;a=(c[o+32>>2]|0)+a|0;b[f+(h<<1)>>1]=a;a=(c[o+64>>2]|0)+a|0;b[f+(i<<1)>>1]=a;a=(c[o+96>>2]|0)+a|0;b[f+(j<<1)>>1]=a;a=(c[o+128>>2]|0)+a|0;b[f+(k<<1)>>1]=a;a=(c[o+160>>2]|0)+a|0;b[f+(l<<1)>>1]=a;a=(c[o+192>>2]|0)+a|0;b[f+(m<<1)>>1]=a;b[f+(n<<1)>>1]=(c[o+224>>2]|0)+a;p=p+1|0;if((p|0)==8)break;else{o=o+4|0;f=f+2|0}}FF(d|0,0,256)|0;return}function um(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;g=f>>1;h=d;i=0;f=a;while(1){a=(e[f+-2>>1]|0)+(c[h>>2]|0)|0;b[f>>1]=a;a=(c[h+4>>2]|0)+a|0;b[f+2>>1]=a;a=(c[h+8>>2]|0)+a|0;b[f+4>>1]=a;a=(c[h+12>>2]|0)+a|0;b[f+6>>1]=a;a=(c[h+16>>2]|0)+a|0;b[f+8>>1]=a;a=(c[h+20>>2]|0)+a|0;b[f+10>>1]=a;a=(c[h+24>>2]|0)+a|0;b[f+12>>1]=a;b[f+14>>1]=(c[h+28>>2]|0)+a;i=i+1|0;if((i|0)==8)break;else{h=h+32|0;f=f+(g<<1)|0}}FF(d|0,0,256)|0;return}
function Qh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Rh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;i=b>>1;p=4-i|0;m=a+(0-i<<1)|0;d=e[a+(p<<1)>>1]|0;l=a+(1-i<<1)|0;h=e[a+(p+1<<1)>>1]|0;k=a+(2-i<<1)|0;o=e[a+(p+2<<1)>>1]|0;j=a+(3-i<<1)|0;p=e[a+(p+3<<1)>>1]|0;s=RF(((e[m>>1]|0)+2+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;p=RF((d+2+h+o+p|0)>>>2|0,0,65537,65537)|0;o=C;h=a;c[h>>2]=s;c[h+4>>2]=r;h=a+8|0;c[h>>2]=p;c[h+4>>2]=o;h=a+(i<<1)|0;d=h;c[d>>2]=s;c[d+4>>2]=r;d=h+8|0;c[d>>2]=p;c[d+4>>2]=o;d=i<<1;g=a+(d<<1)|0;f=g;c[f>>2]=s;c[f+4>>2]=r;f=g+8|0;c[f>>2]=p;c[f+4>>2]=o;f=i*3|0;b=a+(f<<1)|0;n=b;c[n>>2]=s;c[n+4>>2]=r;n=b+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i<<2<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*5<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*6<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*7<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;f=(e[m>>1]|0)+4+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)+(e[a+-2>>1]|0)+(e[a+(i+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;c[a>>2]=f;c[a+4>>2]=d;a=h;c[a>>2]=f;c[a+4>>2]=d;a=g;c[a>>2]=f;c[a+4>>2]=d;c[b>>2]=f;c[b+4>>2]=d;return}function Sh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;Oh(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Th(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;g=b>>1;m=g<<2;o=e[a+(m+-1<<1)>>1]|0;l=g*5|0;n=e[a+(l+-1<<1)>>1]|0;f=g<<1;k=g*6|0;i=e[a+(k+-1<<1)>>1]|0;d=g*3|0;h=g*7|0;j=e[a+(h+-1<<1)>>1]|0;r=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;q=C;p=a;c[p>>2]=r;c[p+4>>2]=q;p=a+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(g<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(f<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(d<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;j=RF((o+2+n+i+j|0)>>>2|0,0,65537,65537)|0;i=C;m=a+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=a+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;k=a+(k<<1)|0;l=k;c[l>>2]=j;c[l+4>>2]=i;k=k+8|0;c[k>>2]=j;c[k+4>>2]=i;h=a+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(g<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(f<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(d<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+8|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(g<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(f<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+(d<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function Uh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=b>>1;p=g<<2;r=e[a+(p+-1<<1)>>1]|0;o=g*5|0;q=e[a+(o+-1<<1)>>1]|0;f=g<<1;n=g*6|0;l=e[a+(n+-1<<1)>>1]|0;b=g*3|0;k=g*7|0;m=e[a+(k+-1<<1)>>1]|0;u=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;t=C;d=a+8|0;j=a+(g<<1)|0;i=j+8|0;c[i>>2]=u;c[i+4>>2]=t;i=a+(f<<1)|0;h=i+8|0;c[h>>2]=u;c[h+4>>2]=t;h=a+(b<<1)|0;s=h+8|0;c[s>>2]=u;c[s+4>>2]=t;m=RF((r+2+q+l+m|0)>>>2|0,0,65537,65537)|0;l=C;p=a+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=a+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;n=a+(n<<1)|0;o=n;c[o>>2]=m;c[o+4>>2]=l;n=n+8|0;c[n>>2]=m;c[n+4>>2]=l;k=a+(k<<1)|0;n=k;c[n>>2]=m;c[n+4>>2]=l;k=k+8|0;c[k>>2]=m;c[k+4>>2]=l;c[a>>2]=16777472;c[a+4>>2]=16777472;a=j;c[a>>2]=16777472;c[a+4>>2]=16777472;a=i;c[a>>2]=16777472;c[a+4>>2]=16777472;a=h;c[a>>2]=16777472;c[a+4>>2]=16777472;a=d;c[a>>2]=16777472;c[a+4>>2]=16777472;a=d+(g<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=d+(f<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=d+(b<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function Vh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;j=b>>1;h=0;g=0;f=0;d=0;b=0;i=0;do{k=e[a+((_(i,j)|0)+-1<<1)>>1]|0;h=k+h+(e[a+(i-j<<1)>>1]|0)|0;k=i+4|0;g=(e[a+(k-j<<1)>>1]|0)+g|0;f=(e[a+((_(k,j)|0)+-1<<1)>>1]|0)+f|0;d=(e[a+((_(i+8|0,j)|0)+-1<<1)>>1]|0)+d|0;b=(e[a+((_(i+12|0,j)|0)+-1<<1)>>1]|0)+b|0;i=i+1|0}while((i|0)!=4);k=h+4>>3;k=RF(k|0,((k|0)<0)<<31>>31|0,65537,65537)|0;i=C;m=g+2>>2;m=RF(m|0,((m|0)<0)<<31>>31|0,65537,65537)|0;l=C;r=f+2>>2;o=f+4+g>>3;n=d+2>>2;d=d+4+g>>3;f=b+2>>2;h=b+4+g>>3;q=a;c[q>>2]=k;c[q+4>>2]=i;q=a+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j*3<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;r=RF(r|0,((r|0)<0)<<31>>31|0,65537,65537)|0;q=C;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;l=C;n=RF(n|0,((n|0)<0)<<31>>31|0,65537,65537)|0;m=C;b=RF(d|0,((d|0)<0)<<31>>31|0,65537,65537)|0;d=C;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;g=C;h=RF(h|0,((h|0)<0)<<31>>31|0,65537,65537)|0;i=C;k=a+(j<<2<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*5<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*6<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*7<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j<<3<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*9<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*10<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*11<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*12<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*13<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*14<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*15<<1)|0;a=k;c[a>>2]=f;c[a+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;return}function Wh(a,b){a=a|0;b=b|0;Ph(a,b);Ph(a+(b<<3)|0,b);return}function Xh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*7<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*9<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*10<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*11<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*12<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*13<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*14<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Yh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;Xh(a,b);h=b>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Zh(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;Vh(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function _h(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;g=b>>1;n=g<<2;p=n+-1|0;v=e[a+(p<<1)>>1]|0;q=g+-1|0;m=g*5|0;o=m+-1|0;k=e[a+(o<<1)>>1]|0;f=g<<1;r=f+-1|0;l=g*6|0;i=l+-1|0;t=e[a+(i<<1)>>1]|0;d=g*3|0;s=d+-1|0;h=g*7|0;j=h+-1|0;u=e[a+(j<<1)>>1]|0;y=RF(((e[a+-2>>1]|0)+2+(e[a+(q<<1)>>1]|0)+(e[a+(r<<1)>>1]|0)+(e[a+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;x=C;w=a;c[w>>2]=y;c[w+4>>2]=x;w=a+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(g<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(f<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(d<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;u=RF((v+2+k+t+u|0)>>>2|0,0,65537,65537)|0;t=C;k=a+(n<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(m<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(l<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(h<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(b<<3)|0;p=e[k+(p<<1)>>1]|0;o=e[k+(o<<1)>>1]|0;i=e[k+(i<<1)>>1]|0;j=e[k+(j<<1)>>1]|0;s=RF(((e[k+-2>>1]|0)+2+(e[k+(q<<1)>>1]|0)+(e[k+(r<<1)>>1]|0)+(e[k+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;q=k;c[q>>2]=s;c[q+4>>2]=r;q=k+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(g<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(f<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(d<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;j=RF((p+2+o+i+j|0)>>>2|0,0,65537,65537)|0;i=C;n=k+(n<<1)|0;o=n;c[o>>2]=j;c[o+4>>2]=i;n=n+8|0;c[n>>2]=j;c[n+4>>2]=i;m=k+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=k+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;h=k+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(g<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(f<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(d<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+8|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(g<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(f<<1)|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+(d<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function $h(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0;h=b>>1;q=h<<2;s=q+-1|0;z=e[a+(s<<1)>>1]|0;t=h+-1|0;p=h*5|0;r=p+-1|0;w=e[a+(r<<1)>>1]|0;g=h<<1;u=g+-1|0;o=h*6|0;l=o+-1|0;x=e[a+(l<<1)>>1]|0;d=h*3|0;v=d+-1|0;n=h*7|0;m=n+-1|0;y=e[a+(m<<1)>>1]|0;D=RF(((e[a+-2>>1]|0)+2+(e[a+(t<<1)>>1]|0)+(e[a+(u<<1)>>1]|0)+(e[a+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;B=C;f=a;c[f>>2]=D;c[f+4>>2]=B;f=a+8|0;k=f;c[k>>2]=D;c[k+4>>2]=B;k=a+(h<<1)|0;j=k;c[j>>2]=D;c[j+4>>2]=B;j=k+8|0;c[j>>2]=D;c[j+4>>2]=B;j=a+(g<<1)|0;i=j;c[i>>2]=D;c[i+4>>2]=B;i=j+8|0;c[i>>2]=D;c[i+4>>2]=B;i=a+(d<<1)|0;A=i;c[A>>2]=D;c[A+4>>2]=B;A=i+8|0;c[A>>2]=D;c[A+4>>2]=B;y=RF((z+2+w+x+y|0)>>>2|0,0,65537,65537)|0;x=C;w=a+(q<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(p<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(o<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(n<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;b=a+(b<<3)|0;s=e[b+(s<<1)>>1]|0;r=e[b+(r<<1)>>1]|0;l=e[b+(l<<1)>>1]|0;m=e[b+(m<<1)>>1]|0;v=RF(((e[b+-2>>1]|0)+2+(e[b+(t<<1)>>1]|0)+(e[b+(u<<1)>>1]|0)+(e[b+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;u=C;t=b;c[t>>2]=v;c[t+4>>2]=u;t=b+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(h<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(g<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(d<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;m=RF((s+2+r+l+m|0)>>>2|0,0,65537,65537)|0;l=C;q=b+(q<<1)|0;r=q;c[r>>2]=m;c[r+4>>2]=l;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;p=b+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=b+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;b=b+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;b=a;c[b>>2]=16777472;c[b+4>>2]=16777472;b=k;c[b>>2]=16777472;c[b+4>>2]=16777472;b=j;c[b>>2]=16777472;c[b+4>>2]=16777472;b=i;c[b>>2]=16777472;c[b+4>>2]=16777472;b=f;c[b>>2]=16777472;c[b+4>>2]=16777472;b=f+(h<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;b=f+(g<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;b=f+(d<<1)|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function ai(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;i=b<<2;h=b*5|0;k=b<<1;g=b*6|0;j=b*3|0;f=b*7|0;e=_(((d[a+(j+-1)>>0]|0)+((d[a+(k+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+((d[a+-1>>0]|0)+(d[a+(0-b)>>0]|0)+(d[a+(4-b)>>0]|0)+(d[a+(i+-1)>>0]|0))+(d[a+(1-b)>>0]|0)+(d[a+(5-b)>>0]|0)+(d[a+(h+-1)>>0]|0))+(d[a+(2-b)>>0]|0)+(d[a+(6-b)>>0]|0)+(d[a+(g+-1)>>0]|0))+(d[a+(3-b)>>0]|0)+(d[a+(7-b)>>0]|0)+(d[a+(f+-1)>>0]|0)+8|0)>>>4,16843009)|0;c[a>>2]=e;c[a+4>>2]=e;b=a+b|0;c[b>>2]=e;c[b+4>>2]=e;b=a+k|0;c[b>>2]=e;c[b+4>>2]=e;b=a+j|0;c[b>>2]=e;c[b+4>>2]=e;b=a+i|0;c[b>>2]=e;c[b+4>>2]=e;b=a+h|0;c[b>>2]=e;c[b+4>>2]=e;b=a+g|0;c[b>>2]=e;c[b+4>>2]=e;b=a+f|0;c[b>>2]=e;c[b+4>>2]=e;return}function bi(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;k=b<<1;j=b*3|0;i=b<<2;h=b*5|0;g=b*6|0;f=b*7|0;e=_(((d[a+(f+-1)>>0]|0)+((d[a+(g+-1)>>0]|0)+((d[a+(h+-1)>>0]|0)+((d[a+(i+-1)>>0]|0)+((d[a+(j+-1)>>0]|0)+((d[a+(k+-1)>>0]|0)+((d[a+(b+-1)>>0]|0)+(d[a+-1>>0]|0)))))))+4|0)>>>3,16843009)|0;c[a+4>>2]=e;c[a>>2]=e;b=a+b|0;c[b+4>>2]=e;c[b>>2]=e;b=a+k|0;c[b+4>>2]=e;c[b>>2]=e;b=a+j|0;c[b+4>>2]=e;c[b>>2]=e;b=a+i|0;c[b+4>>2]=e;c[b>>2]=e;b=a+h|0;c[b+4>>2]=e;c[b>>2]=e;b=a+g|0;c[b+4>>2]=e;c[b>>2]=e;b=a+f|0;c[b+4>>2]=e;c[b>>2]=e;return}function ci(a,b){a=a|0;b=b|0;var e=0,f=0;e=_(((d[a+(7-b)>>0]|0)+((d[a+(6-b)>>0]|0)+((d[a+(5-b)>>0]|0)+((d[a+(4-b)>>0]|0)+((d[a+(3-b)>>0]|0)+((d[a+(2-b)>>0]|0)+((d[a+(1-b)>>0]|0)+(d[a+(0-b)>>0]|0)))))))+4|0)>>>3,16843009)|0;c[a+4>>2]=e;c[a>>2]=e;f=a+b|0;c[f+4>>2]=e;c[f>>2]=e;f=a+(b<<1)|0;c[f+4>>2]=e;c[f>>2]=e;f=a+(b*3|0)|0;c[f+4>>2]=e;c[f>>2]=e;f=a+(b<<2)|0;c[f+4>>2]=e;c[f>>2]=e;f=a+(b*5|0)|0;c[f+4>>2]=e;c[f>>2]=e;f=a+(b*6|0)|0;c[f+4>>2]=e;c[f>>2]=e;b=a+(b*7|0)|0;c[b+4>>2]=e;c[b>>2]=e;return}function di(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b<<1)|0;e=d;c[e>>2]=16711935;c[e+4>>2]=16711935;d=d+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b<<1<<1)|0;e=d;c[e>>2]=16711935;c[e+4>>2]=16711935;d=d+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b*3<<1)|0;e=d;c[e>>2]=16711935;c[e+4>>2]=16711935;d=d+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b<<2<<1)|0;e=d;c[e>>2]=16711935;c[e+4>>2]=16711935;d=d+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b*5<<1)|0;e=d;c[e>>2]=16711935;c[e+4>>2]=16711935;d=d+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+(b*6<<1)|0;e=d;c[e>>2]=16711935;c[e+4>>2]=16711935;d=d+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;b=a+(b*7<<1)|0;a=b;c[a>>2]=16711935;c[a+4>>2]=16711935;b=b+8|0;c[b>>2]=16711935;c[b+4>>2]=16711935;return}function ei(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;e=d;c[e>>2]=16843009;c[e+4>>2]=16843009;d=d+8|0;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b<<1<<1)|0;e=d;c[e>>2]=16843009;c[e+4>>2]=16843009;d=d+8|0;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b*3<<1)|0;e=d;c[e>>2]=16843009;c[e+4>>2]=16843009;d=d+8|0;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b<<2<<1)|0;e=d;c[e>>2]=16843009;c[e+4>>2]=16843009;d=d+8|0;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b*5<<1)|0;e=d;c[e>>2]=16843009;c[e+4>>2]=16843009;d=d+8|0;c[d>>2]=16843009;c[d+4>>2]=16843009;d=a+(b*6<<1)|0;e=d;c[e>>2]=16843009;c[e+4>>2]=16843009;d=d+8|0;c[d>>2]=16843009;c[d+4>>2]=16843009;b=a+(b*7<<1)|0;a=b;c[a>>2]=16843009;c[a+4>>2]=16843009;b=b+8|0;c[b>>2]=16843009;c[b+4>>2]=16843009;return}function fi(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b<<1)|0;e=d;c[e>>2]=16777472;c[e+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b<<1<<1)|0;e=d;c[e>>2]=16777472;c[e+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b*3<<1)|0;e=d;c[e>>2]=16777472;c[e+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b<<2<<1)|0;e=d;c[e>>2]=16777472;c[e+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b*5<<1)|0;e=d;c[e>>2]=16777472;c[e+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+(b*6<<1)|0;e=d;c[e>>2]=16777472;c[e+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;b=a+(b*7<<1)|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+8|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function gi(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;j=b>>1;i=a;c[i>>2]=16777472;c[i+4>>2]=16777472;i=a+8|0;c[i>>2]=16777472;c[i+4>>2]=16777472;i=a+(j<<1)|0;h=i;c[h>>2]=16777472;c[h+4>>2]=16777472;i=i+8|0;c[i>>2]=16777472;c[i+4>>2]=16777472;i=j<<1;h=a+(i<<1)|0;g=h;c[g>>2]=16777472;c[g+4>>2]=16777472;h=h+8|0;c[h>>2]=16777472;c[h+4>>2]=16777472;h=j*3|0;g=a+(h<<1)|0;f=g;c[f>>2]=16777472;c[f+4>>2]=16777472;g=g+8|0;c[g>>2]=16777472;c[g+4>>2]=16777472;g=j<<2;f=a+(g<<1)|0;e=f;c[e>>2]=16777472;c[e+4>>2]=16777472;f=f+8|0;c[f>>2]=16777472;c[f+4>>2]=16777472;f=j*5|0;e=a+(f<<1)|0;d=e;c[d>>2]=16777472;c[d+4>>2]=16777472;e=e+8|0;c[e>>2]=16777472;c[e+4>>2]=16777472;e=j*6|0;d=a+(e<<1)|0;k=d;c[k>>2]=16777472;c[k+4>>2]=16777472;d=d+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=j*7|0;k=a+(d<<1)|0;l=k;c[l>>2]=16777472;c[l+4>>2]=16777472;k=k+8|0;c[k>>2]=16777472;c[k+4>>2]=16777472;b=a+(b<<3)|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(j<<1)|0;j=a;c[j>>2]=16777472;c[j+4>>2]=16777472;a=a+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(i<<1)|0;i=a;c[i>>2]=16777472;c[i+4>>2]=16777472;a=a+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(h<<1)|0;h=a;c[h>>2]=16777472;c[h+4>>2]=16777472;a=a+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(g<<1)|0;g=a;c[g>>2]=16777472;c[g+4>>2]=16777472;a=a+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(f<<1)|0;f=a;c[f>>2]=16777472;c[f+4>>2]=16777472;a=a+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+(e<<1)|0;e=a;c[e>>2]=16777472;c[e+4>>2]=16777472;a=a+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+(d<<1)|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+8|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function hi(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+((e[a+(0-b<<1)>>1]|0)+((e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))))))))))))))))))+16>>5;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function ii(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;b=b>>1;d=a+(0-b<<1)|0;j=d;k=c[j>>2]|0;j=c[j+4>>2]|0;h=d+8|0;i=c[h>>2]|0;h=c[h+4>>2]|0;f=d+16|0;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+24|0;e=c[d>>2]|0;d=c[d+4>>2]|0;l=a;c[l>>2]=k;c[l+4>>2]=j;l=a+8|0;c[l>>2]=i;c[l+4>>2]=h;l=a+16|0;c[l>>2]=g;c[l+4>>2]=f;l=a+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<2<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*5<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*6<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*7<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*9<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*10<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*11<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*12<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*13<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*14<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=k;c[a+4>>2]=j;a=b+8|0;c[a>>2]=i;c[a+4>>2]=h;a=b+16|0;c[a>>2]=g;c[a+4>>2]=f;b=b+24|0;c[b>>2]=e;c[b+4>>2]=d;return}function ji(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;i=f+8|0;c[i>>2]=h;c[i+4>>2]=g;i=f+16|0;c[i>>2]=h;c[i+4>>2]=g;f=f+24|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function ki(a,b){a=a|0;b=b|0;li(a,b,1,0);return}function li(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;v=0-c|0;g=b+7+v|0;l=b+(c<<3)+-1|0;h=l+(0-(c<<1))|0;n=l+c|0;m=h+v|0;j=n+c|0;k=m+v|0;o=j+c|0;i=k+v|0;q=o+c|0;p=i+v|0;s=q+c|0;r=p+v|0;u=s+c|0;t=r+v|0;g=((d[g+8>>0]|0)-(d[g+-8>>0]|0)<<3)+((((d[g+7>>0]|0)-(d[g+-7>>0]|0)|0)*7|0)+((((d[g+6>>0]|0)-(d[g+-6>>0]|0)|0)*6|0)+((((d[g+5>>0]|0)-(d[g+-5>>0]|0)|0)*5|0)+(((d[g+4>>0]|0)-(d[g+-4>>0]|0)<<2)+((((d[g+3>>0]|0)-(d[g+-3>>0]|0)|0)*3|0)+(((d[g+2>>0]|0)-(d[g+-2>>0]|0)<<1)+((d[g+1>>0]|0)-(d[g+-1>>0]|0))))))))|0;h=((d[u+c>>0]|0)-(d[t+v>>0]|0)<<3)+((((d[u>>0]|0)-(d[t>>0]|0)|0)*7|0)+((((d[s>>0]|0)-(d[r>>0]|0)|0)*6|0)+((((d[q>>0]|0)-(d[p>>0]|0)|0)*5|0)+(((d[o>>0]|0)-(d[i>>0]|0)<<2)+((((d[j>>0]|0)-(d[k>>0]|0)|0)*3|0)+(((d[n>>0]|0)-(d[m>>0]|0)<<1)+((d[l>>0]|0)-(d[h>>0]|0))))))))|0;l=b+~c|0;m=b+((c*15|0)+-1)|0;do if(!e)if(!f){n=(g*5|0)+32>>6;k=(h*5|0)+32>>6;break}else{n=(g>>2)+g>>4;k=(h>>2)+h>>4;break}else{n=(((h|0)/4|0)*5|0)/16|0;k=(((g|0)/4|0)*5|0)/16|0}while(0);h=n<<1;i=n*3|0;j=n<<2;e=(_(k+n|0,-7)|0)+16+((d[l+16>>0]|0)+(d[m>>0]|0)<<4)|0;g=16;f=b;while(1){v=e>>5;a[f>>0]=v>>>0>255?0-v>>31:v;v=e+n>>5;a[f+1>>0]=v>>>0>255?0-v>>31:v;v=e+h>>5;a[f+2>>0]=v>>>0>255?0-v>>31:v;v=e+i>>5;a[f+3>>0]=v>>>0>255?0-v>>31:v;v=e+j|0;u=v>>5;a[f+4>>0]=u>>>0>255?0-u>>31:u;u=v+n>>5;a[f+5>>0]=u>>>0>255?0-u>>31:u;u=v+h>>5;a[f+6>>0]=u>>>0>255?0-u>>31:u;u=v+i>>5;a[f+7>>0]=u>>>0>255?0-u>>31:u;v=v+j|0;u=v>>5;a[f+8>>0]=u>>>0>255?0-u>>31:u;u=v+n>>5;a[f+9>>0]=u>>>0>255?0-u>>31:u;u=v+h>>5;a[f+10>>0]=u>>>0>255?0-u>>31:u;u=v+i>>5;a[f+11>>0]=u>>>0>255?0-u>>31:u;v=v+j|0;u=v>>5;a[f+12>>0]=u>>>0>255?0-u>>31:u;u=v+n>>5;a[f+13>>0]=u>>>0>255?0-u>>31:u;u=v+h>>5;a[f+14>>0]=u>>>0>255?0-u>>31:u;v=v+i>>5;a[f+15>>0]=v>>>0>255?0-v>>31:v;if((g|0)>1){e=e+k|0;g=g+-1|0;f=f+c|0}else break}return}function mi(a,b){a=a|0;b=b|0;li(a,b,0,1);return}function ni(b,c){b=b|0;c=c|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;s=70232+(0-(d[b+~c>>0]|0))|0;t=b+(0-c)|0;u=t+1|0;v=t+2|0;f=t+3|0;g=t+4|0;h=t+5|0;i=t+6|0;j=t+7|0;k=t+8|0;l=t+9|0;m=t+10|0;n=t+11|0;o=t+12|0;p=t+13|0;q=t+14|0;r=t+15|0;e=0;while(1){w=s+(d[b+-1>>0]|0)|0;a[b>>0]=a[w+(d[t>>0]|0)>>0]|0;a[b+1>>0]=a[w+(d[u>>0]|0)>>0]|0;a[b+2>>0]=a[w+(d[v>>0]|0)>>0]|0;a[b+3>>0]=a[w+(d[f>>0]|0)>>0]|0;a[b+4>>0]=a[w+(d[g>>0]|0)>>0]|0;a[b+5>>0]=a[w+(d[h>>0]|0)>>0]|0;a[b+6>>0]=a[w+(d[i>>0]|0)>>0]|0;a[b+7>>0]=a[w+(d[j>>0]|0)>>0]|0;a[b+8>>0]=a[w+(d[k>>0]|0)>>0]|0;a[b+9>>0]=a[w+(d[l>>0]|0)>>0]|0;a[b+10>>0]=a[w+(d[m>>0]|0)>>0]|0;a[b+11>>0]=a[w+(d[n>>0]|0)>>0]|0;a[b+12>>0]=a[w+(d[o>>0]|0)>>0]|0;a[b+13>>0]=a[w+(d[p>>0]|0)>>0]|0;a[b+14>>0]=a[w+(d[q>>0]|0)>>0]|0;a[b+15>>0]=a[w+(d[r>>0]|0)>>0]|0;e=e+1|0;if((e|0)==16)break;else b=b+c|0}return}function oi(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;a=a+(b<<1)|0;d=a;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+8|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+16|0;c[d>>2]=16711935;c[d+4>>2]=16711935;d=a+24|0;c[d>>2]=16711935;c[d+4>>2]=16711935;b=a+(b<<1)|0;a=b;c[a>>2]=16711935;c[a+4>>2]=16711935;a=b+8|0;c[a>>2]=16711935;c[a+4>>2]=16711935;a=b+16|0;c[a>>2]=16711935;c[a+4>>2]=16711935;b=b+24|0;c[b>>2]=16711935;c[b+4>>2]=16711935;return}function pi(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;c[a+24>>2]=16843009;c[a+28>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;d=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=d;c[a>>2]=16843009;c[a+4>>2]=16843009;a=d+8|0;b=d+(b<<1)|0;c[a>>2]=16843009;c[a+4>>2]=16843009;c[a+8>>2]=16843009;c[a+12>>2]=16843009;c[a+16>>2]=16843009;c[a+20>>2]=16843009;a=b;c[a>>2]=16843009;c[a+4>>2]=16843009;b=b+8|0;c[b>>2]=16843009;c[b+4>>2]=16843009;c[b+8>>2]=16843009;c[b+12>>2]=16843009;c[b+16>>2]=16843009;c[b+20>>2]=16843009;return}function qi(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=c>>1;i=0-g|0;h=a+14+(i<<1)|0;u=a+(g<<3<<1)+-2|0;m=g<<1;t=u+(0-m<<1)|0;s=u+(g<<1)|0;r=t+(i<<1)|0;q=s+(g<<1)|0;p=r+(i<<1)|0;o=q+(g<<1)|0;c=p+(i<<1)|0;d=o+(g<<1)|0;f=c+(i<<1)|0;n=d+(g<<1)|0;l=f+(i<<1)|0;k=n+(g<<1)|0;j=l+(i<<1)|0;h=(((e[h+2>>1]|0)-(e[h+-2>>1]|0)+((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+(((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+(((e[h+10>>1]|0)-(e[h+-10>>1]|0)|0)*5|0)+(((e[h+12>>1]|0)-(e[h+-12>>1]|0)|0)*6|0)+(((e[h+14>>1]|0)-(e[h+-14>>1]|0)|0)*7|0)+((e[h+16>>1]|0)-(e[h+-16>>1]|0)<<3)|0)*5|0)+32>>6;i=(((e[u>>1]|0)-(e[t>>1]|0)+((e[s>>1]|0)-(e[r>>1]|0)<<1)+(((e[q>>1]|0)-(e[p>>1]|0)|0)*3|0)+((e[o>>1]|0)-(e[c>>1]|0)<<2)+(((e[d>>1]|0)-(e[f>>1]|0)|0)*5|0)+(((e[n>>1]|0)-(e[l>>1]|0)|0)*6|0)+(((e[k>>1]|0)-(e[j>>1]|0)|0)*7|0)+((e[k+(g<<1)>>1]|0)-(e[j+(i<<1)>>1]|0)<<3)|0)*5|0)+32>>6;j=h<<1;k=h*3|0;l=h<<2;m=((e[a+(-2-m)+32>>1]|0)+(e[a+((g*30|0)+-2)>>1]|0)<<4)+16+(_(i+h|0,-7)|0)|0;n=16;while(1){d=m;f=-16;while(1){c=d>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+(f+16<<1)>>1]=c;c=d+h>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+(f+17<<1)>>1]=c;c=d+j>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+(f+18<<1)>>1]=c;c=d+k>>5;if(c>>>0>=512)c=0-c>>31&511;b[a+(f+19<<1)>>1]=c;if((f|0)<-4){d=d+l|0;f=f+4|0}else break}if((n|0)>1){m=m+i|0;n=n+-1|0;a=a+(g<<1)|0}else break}return}function ri(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function si(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function ti(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;a=a+(b<<1)|0;d=a;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+8|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+16|0;c[d>>2]=16777472;c[d+4>>2]=16777472;d=a+24|0;c[d>>2]=16777472;c[d+4>>2]=16777472;b=a+(b<<1)|0;a=b;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+8|0;c[a>>2]=16777472;c[a+4>>2]=16777472;a=b+16|0;c[a>>2]=16777472;c[a+4>>2]=16777472;b=b+24|0;c[b>>2]=16777472;c[b+4>>2]=16777472;return}function ui(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0;j=f>>1;f=a+(0-j<<1)|0;i=j<<1;h=j*3|0;a=j<<2;g=(e[f>>1]|0)+(c[d>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+16>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+32>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+48>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+4>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+20>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+36>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+52>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+8>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+24>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+40>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+56>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+12>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+28>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+44>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+60>>2]|0)+g;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function vi(a,d,f){a=a|0;d=d|0;f=f|0;var g=0;f=f>>1;g=(e[a+-2>>1]|0)+(c[d>>2]|0)|0;b[a>>1]=g;g=(c[d+4>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+8>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+12>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+16>>2]|0)|0;b[a>>1]=g;g=(c[d+20>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+24>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+28>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+32>>2]|0)|0;b[a>>1]=g;g=(c[d+36>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+40>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+44>>2]|0)+g;a=a+(f<<1)|0;f=(e[a+-2>>1]|0)+(c[d+48>>2]|0)|0;b[a>>1]=f;f=(c[d+52>>2]|0)+f|0;b[a+2>>1]=f;f=(c[d+56>>2]|0)+f|0;b[a+4>>1]=f;b[a+6>>1]=(c[d+60>>2]|0)+f;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function wi(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;g=f>>1;h=g<<1;i=g*3|0;j=g<<2;k=g*5|0;l=g*6|0;m=g*7|0;n=g<<3;o=d;p=0;f=a+(0-g<<1)|0;while(1){a=(e[f>>1]|0)+(c[o>>2]|0)|0;b[f+(g<<1)>>1]=a;a=(c[o+32>>2]|0)+a|0;b[f+(h<<1)>>1]=a;a=(c[o+64>>2]|0)+a|0;b[f+(i<<1)>>1]=a;a=(c[o+96>>2]|0)+a|0;b[f+(j<<1)>>1]=a;a=(c[o+128>>2]|0)+a|0;b[f+(k<<1)>>1]=a;a=(c[o+160>>2]|0)+a|0;b[f+(l<<1)>>1]=a;a=(c[o+192>>2]|0)+a|0;b[f+(m<<1)>>1]=a;b[f+(n<<1)>>1]=(c[o+224>>2]|0)+a;p=p+1|0;if((p|0)==8)break;else{o=o+4|0;f=f+2|0}}FF(d|0,0,256)|0;return}function xi(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;g=f>>1;h=d;i=0;f=a;while(1){a=(e[f+-2>>1]|0)+(c[h>>2]|0)|0;b[f>>1]=a;a=(c[h+4>>2]|0)+a|0;b[f+2>>1]=a;a=(c[h+8>>2]|0)+a|0;b[f+4>>1]=a;a=(c[h+12>>2]|0)+a|0;b[f+6>>1]=a;a=(c[h+16>>2]|0)+a|0;b[f+8>>1]=a;a=(c[h+20>>2]|0)+a|0;b[f+10>>1]=a;a=(c[h+24>>2]|0)+a|0;b[f+12>>1]=a;b[f+14>>1]=(c[h+28>>2]|0)+a;i=i+1|0;if((i|0)==8)break;else{h=h+32|0;f=f+(g<<1)|0}}FF(d|0,0,256)|0;return}function yi(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+16|0;t=u;s=h>>1;k=0-s|0;r=e[a+(k<<1)>>1]|0;j=e[a+(1-s<<1)>>1]|0;l=e[a+(2-s<<1)>>1]|0;m=e[a+(3-s<<1)>>1]|0;n=e[a+(4-s<<1)>>1]|0;o=e[a+(5-s<<1)>>1]|0;p=e[a+(6-s<<1)>>1]|0;h=b[a+(7-s<<1)>>1]|0;q=h&65535;if(g)h=b[a+(8-s<<1)>>1]|0;f=((e[a+(((f|0)==0?k:~s)<<1)>>1]|0)+2+(r<<1)+j|0)>>>2&65535;b[t>>1]=f;b[t+2>>1]=(r+2+(j<<1)+l|0)>>>2;b[t+4>>1]=(j+2+(l<<1)+m|0)>>>2;b[t+6>>1]=(l+2+(m<<1)+n|0)>>>2;b[t+8>>1]=(m+2+(n<<1)+o|0)>>>2;b[t+10>>1]=(n+2+(o<<1)+p|0)>>>2;b[t+12>>1]=(o+2+(p<<1)+q|0)>>>2;b[t+14>>1]=((h&65535)+2+(q<<1)+p|0)>>>2;m=s<<1;n=s*3|0;o=s<<2;p=s*5|0;j=s*6|0;k=s*7|0;l=f;q=d;h=0;while(1){f=(l&65535)+(c[q>>2]|0)|0;b[a>>1]=f;f=(c[q+32>>2]|0)+f|0;b[a+(s<<1)>>1]=f;f=(c[q+64>>2]|0)+f|0;b[a+(m<<1)>>1]=f;f=(c[q+96>>2]|0)+f|0;b[a+(n<<1)>>1]=f;f=(c[q+128>>2]|0)+f|0;b[a+(o<<1)>>1]=f;f=(c[q+160>>2]|0)+f|0;b[a+(p<<1)>>1]=f;f=(c[q+192>>2]|0)+f|0;b[a+(j<<1)>>1]=f;b[a+(k<<1)>>1]=(c[q+224>>2]|0)+f;h=h+1|0;if((h|0)==8)break;l=b[t+(h<<1)>>1]|0;q=q+4|0;a=a+2|0}FF(d|0,0,256)|0;i=u;return}function zi(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;m=i;i=i+16|0;l=m;k=h>>1;s=a+-2|0;r=e[s>>1]|0;q=e[a+(k+-1<<1)>>1]|0;p=e[a+((k<<1)+-1<<1)>>1]|0;o=e[a+((k*3|0)+-1<<1)>>1]|0;n=e[a+((k<<2)+-1<<1)>>1]|0;h=e[a+((k*5|0)+-1<<1)>>1]|0;g=e[a+((k*6|0)+-1<<1)>>1]|0;j=e[a+((k*7|0)+-1<<1)>>1]|0;f=((e[((f|0)==0?s:a+(~k<<1)|0)>>1]|0)+2+(r<<1)+q|0)>>>2&65535;b[l>>1]=f;b[l+2>>1]=(r+2+(q<<1)+p|0)>>>2;b[l+4>>1]=(q+2+(p<<1)+o|0)>>>2;b[l+6>>1]=(p+2+(o<<1)+n|0)>>>2;b[l+8>>1]=(o+2+(n<<1)+h|0)>>>2;b[l+10>>1]=(n+2+(h<<1)+g|0)>>>2;b[l+12>>1]=(h+2+(g<<1)+j|0)>>>2;b[l+14>>1]=(g+2+(j*3|0)|0)>>>2;j=d;g=0;h=a;while(1){s=(f&65535)+(c[j>>2]|0)|0;b[h>>1]=s;s=(c[j+4>>2]|0)+s|0;b[h+2>>1]=s;s=(c[j+8>>2]|0)+s|0;b[h+4>>1]=s;s=(c[j+12>>2]|0)+s|0;b[h+6>>1]=s;s=(c[j+16>>2]|0)+s|0;b[h+8>>1]=s;s=(c[j+20>>2]|0)+s|0;b[h+10>>1]=s;s=(c[j+24>>2]|0)+s|0;b[h+12>>1]=s;b[h+14>>1]=(c[j+28>>2]|0)+s;g=g+1|0;if((g|0)==8)break;f=b[l+(g<<1)>>1]|0;j=j+32|0;h=h+(k<<1)|0}FF(d|0,0,256)|0;i=m;return}function Ai(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;ui(a+(c[b>>2]|0)|0,d,e);ui(a+(c[b+4>>2]|0)|0,d+64|0,e);ui(a+(c[b+8>>2]|0)|0,d+128|0,e);ui(a+(c[b+12>>2]|0)|0,d+192|0,e);return}function Bi(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;ui(a+(c[b>>2]|0)|0,d,e);ui(a+(c[b+4>>2]|0)|0,d+64|0,e);ui(a+(c[b+8>>2]|0)|0,d+128|0,e);ui(a+(c[b+12>>2]|0)|0,d+192|0,e);ui(a+(c[b+32>>2]|0)|0,d+256|0,e);ui(a+(c[b+36>>2]|0)|0,d+320|0,e);ui(a+(c[b+40>>2]|0)|0,d+384|0,e);ui(a+(c[b+44>>2]|0)|0,d+448|0,e);return}function Ci(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=4);return}function Di(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;h=g>>1;g=0;do{j=a+(c[d+(g<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=4);g=4;do{j=a+(c[d+(g+4<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=8);return}function Ei(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;ui(a+(c[b>>2]|0)|0,d,e);ui(a+(c[b+4>>2]|0)|0,d+64|0,e);ui(a+(c[b+8>>2]|0)|0,d+128|0,e);ui(a+(c[b+12>>2]|0)|0,d+192|0,e);ui(a+(c[b+16>>2]|0)|0,d+256|0,e);ui(a+(c[b+20>>2]|0)|0,d+320|0,e);ui(a+(c[b+24>>2]|0)|0,d+384|0,e);ui(a+(c[b+28>>2]|0)|0,d+448|0,e);ui(a+(c[b+32>>2]|0)|0,d+512|0,e);ui(a+(c[b+36>>2]|0)|0,d+576|0,e);ui(a+(c[b+40>>2]|0)|0,d+640|0,e);ui(a+(c[b+44>>2]|0)|0,d+704|0,e);ui(a+(c[b+48>>2]|0)|0,d+768|0,e);ui(a+(c[b+52>>2]|0)|0,d+832|0,e);ui(a+(c[b+56>>2]|0)|0,d+896|0,e);ui(a+(c[b+60>>2]|0)|0,d+960|0,e);return}function Fi(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=16);return}function Gi(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;b=d>>1;d=a+(0-b<<1)|0;e=c[d>>2]|0;d=c[d+4>>2]|0;f=a;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1<<1)|0;c[f>>2]=e;c[f+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=e;c[b+4>>2]=d;return}function Hi(a,b,d){a=a|0;b=b|0;d=d|0;var f=0;b=d>>1;d=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;f=a;c[f>>2]=d;c[f+4>>2]=C;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(b<<1)|0;c[d>>2]=f;c[d+4>>2]=C;d=b<<1;f=RF(e[a+(d+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(d<<1)|0;c[d>>2]=f;c[d+4>>2]=C;b=b*3|0;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;b=a+(b<<1)|0;c[b>>2]=d;c[b+4>>2]=C;return}function Ii(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Ji(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=d>>1;l=e[a+(1-i<<1)>>1]|0;g=e[a+(2-i<<1)>>1]|0;k=e[a+(3-i<<1)>>1]|0;j=e[c>>1]|0;h=e[c+2>>1]|0;f=e[c+4>>1]|0;d=e[c+6>>1]|0;b[a>>1]=((e[a+(0-i<<1)>>1]|0)+2+g+(l<<1)|0)>>>2;c=(l+2+k+(g<<1)|0)>>>2&65535;b[a+(i<<1)>>1]=c;b[a+2>>1]=c;c=(g+2+j+(k<<1)|0)>>>2&65535;g=i<<1;b[a+(g<<1)>>1]=c;b[a+(i+1<<1)>>1]=c;b[a+4>>1]=c;k=(k+2+h+(j<<1)|0)>>>2&65535;c=i*3|0;b[a+(c<<1)>>1]=k;b[a+((g|1)<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+f+(h<<1)|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;b[a+(i+3<<1)>>1]=j;h=(h+2+d+(f<<1)|0)>>>2&65535;b[a+(c+2<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(c+3<<1)>>1]=(f+2+(d*3|0)|0)>>>2;return}function Ki(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;h=d>>1;m=e[a+(~h<<1)>>1]|0;g=e[a+(0-h<<1)>>1]|0;f=e[a+(1-h<<1)>>1]|0;d=e[a+(2-h<<1)>>1]|0;c=e[a+(3-h<<1)>>1]|0;k=e[a+-2>>1]|0;n=e[a+(h+-1<<1)>>1]|0;j=h<<1;i=e[a+(j+-1<<1)>>1]|0;l=h*3|0;b[a+(l<<1)>>1]=(n+2+(i<<1)+(e[a+(l+-1<<1)>>1]|0)|0)>>>2;i=(k+2+(n<<1)+i|0)>>>2&65535;b[a+(l+1<<1)>>1]=i;b[a+(j<<1)>>1]=i;i=m+2|0;n=(i+(k<<1)+n|0)>>>2&65535;b[a+(l+2<<1)>>1]=n;b[a+((j|1)<<1)>>1]=n;b[a+(h<<1)>>1]=n;k=((m<<1)+2+g+k|0)>>>2&65535;b[a+(l+3<<1)>>1]=k;b[a+(j+2<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;b[a>>1]=k;i=(i+(g<<1)+f|0)>>>2&65535;b[a+(j+3<<1)>>1]=i;b[a+(h+2<<1)>>1]=i;b[a+2>>1]=i;g=(g+2+(f<<1)+d|0)>>>2&65535;b[a+(h+3<<1)>>1]=g;b[a+4>>1]=g;b[a+6>>1]=(f+2+(d<<1)+c|0)>>>2;return}function Li(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;i=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;k=e[a+(3-j<<1)>>1]|0;g=e[a+-2>>1]|0;f=e[a+(j+-1<<1)>>1]|0;h=j<<1;d=e[a+(h+-1<<1)>>1]|0;c=j*3|0;o=(i+1+n|0)>>>1&65535;b[a+((h|1)<<1)>>1]=o;b[a>>1]=o;o=(n+1+m|0)>>>1&65535;b[a+(h+2<<1)>>1]=o;b[a+2>>1]=o;o=(m+1+l|0)>>>1&65535;b[a+(h+3<<1)>>1]=o;b[a+4>>1]=o;b[a+6>>1]=(l+1+k|0)>>>1;o=((i<<1)+2+n+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=o;b[a+(j<<1)>>1]=o;i=i+2|0;o=(i+(n<<1)+m|0)>>>2&65535;b[a+(c+2<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;n=(n+2+(m<<1)+l|0)>>>2&65535;b[a+(c+3<<1)>>1]=n;b[a+(j+2<<1)>>1]=n;b[a+(j+3<<1)>>1]=(m+2+(l<<1)+k|0)>>>2;b[a+(h<<1)>>1]=(i+(g<<1)+f|0)>>>2;b[a+(c<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function Mi(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;k=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;i=e[a+-2>>1]|0;g=e[a+(j+-1<<1)>>1]|0;h=j<<1;f=e[a+(h+-1<<1)>>1]|0;c=j*3|0;d=e[a+(c+-1<<1)>>1]|0;o=(k+1+i|0)>>>1&65535;b[a+(j+2<<1)>>1]=o;b[a>>1]=o;o=((k<<1)+2+n+i|0)>>>2&65535;b[a+(j+3<<1)>>1]=o;b[a+2>>1]=o;k=k+2|0;b[a+4>>1]=(k+(n<<1)+m|0)>>>2;b[a+6>>1]=(n+2+(m<<1)+l|0)>>>2;l=(i+1+g|0)>>>1&65535;b[a+(h+2<<1)>>1]=l;b[a+(j<<1)>>1]=l;k=(k+(i<<1)+g|0)>>>2&65535;b[a+(h+3<<1)>>1]=k;b[a+(j+1<<1)>>1]=k;j=(g+1+f|0)>>>1&65535;b[a+(c+2<<1)>>1]=j;b[a+(h<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+3<<1)>>1]=i;b[a+((h|1)<<1)>>1]=i;b[a+(c<<1)>>1]=(f+1+d|0)>>>1;b[a+(c+1<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function Ni(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=d>>1;l=e[a+(0-h<<1)>>1]|0;k=e[a+(1-h<<1)>>1]|0;j=e[a+(2-h<<1)>>1]|0;i=e[a+(3-h<<1)>>1]|0;g=e[c>>1]|0;f=e[c+2>>1]|0;d=e[c+4>>1]|0;b[a>>1]=(l+1+k|0)>>>1;m=(k+1+j|0)>>>1&65535;c=h<<1;b[a+(c<<1)>>1]=m;b[a+2>>1]=m;m=(j+1+i|0)>>>1&65535;b[a+((c|1)<<1)>>1]=m;b[a+4>>1]=m;m=(i+1+g|0)>>>1&65535;b[a+(c+2<<1)>>1]=m;b[a+6>>1]=m;b[a+(c+3<<1)>>1]=(g+1+f|0)>>>1;b[a+(h<<1)>>1]=(l+2+(k<<1)+j|0)>>>2;k=(k+2+(j<<1)+i|0)>>>2&65535;c=h*3|0;b[a+(c<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;j=(j+2+(i<<1)+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(h+2<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+2<<1)>>1]=i;b[a+(h+3<<1)>>1]=i;b[a+(c+3<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function Oi(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0;g=d>>1;k=e[a+-2>>1]|0;j=e[a+(g+-1<<1)>>1]|0;c=g<<1;i=e[a+(c+-1<<1)>>1]|0;f=g*3|0;d=b[a+(f+-1<<1)>>1]|0;h=d&65535;b[a>>1]=(k+1+j|0)>>>1;b[a+2>>1]=(k+2+(j<<1)+i|0)>>>2;k=(j+1+i|0)>>>1&65535;b[a+(g<<1)>>1]=k;b[a+4>>1]=k;j=(j+2+(i<<1)+h|0)>>>2&65535;b[a+(g+1<<1)>>1]=j;b[a+6>>1]=j;j=(i+1+h|0)>>>1&65535;b[a+(c<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;h=(i+2+h+(h<<1)|0)>>>2&65535;b[a+((c|1)<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(f+3<<1)>>1]=d;b[a+(f+2<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+(f<<1)>>1]=d;b[a+(f+1<<1)>>1]=d;b[a+(c+3<<1)>>1]=d;return}function Pi(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=RF(((e[a+-2>>1]|0)+2+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Qi(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0;b=d>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Ri(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b<<1)|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b<<1<<1)|0;c[d>>2]=33489407;c[d+4>>2]=33489407;b=a+(b*3<<1)|0;c[b>>2]=33489407;c[b+4>>2]=33489407;return}function Si(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b<<1)|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b<<1<<1)|0;c[d>>2]=33620481;c[d+4>>2]=33620481;b=a+(b*3<<1)|0;c[b>>2]=33620481;c[b+4>>2]=33620481;return}function Ti(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b<<1)|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b<<1<<1)|0;c[d>>2]=33554944;c[d+4>>2]=33554944;b=a+(b*3<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function Ui(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;b[a>>1]=((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2;b[a+2>>1]=(q+2+(i<<1)+k|0)>>>2;b[a+4>>1]=(i+2+(k<<1)+l|0)>>>2;b[a+6>>1]=(k+2+(l<<1)+m|0)>>>2;f=a+8|0;b[f>>1]=(l+2+(m<<1)+n|0)>>>2;b[a+10>>1]=(m+2+(n<<1)+o|0)>>>2;b[a+12>>1]=(n+2+(o<<1)+p|0)>>>2;b[a+14>>1]=((g&65535)+2+(p<<1)+o|0)>>>2;p=a;o=c[p>>2]|0;p=c[p+4>>2]|0;q=c[f>>2]|0;f=c[f+4>>2]|0;d=a+(h<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*3<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<2<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*5<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*6<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*7<<1)|0;a=d;c[a>>2]=o;c[a+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function Vi(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;p=f>>1;u=a+-2|0;s=e[u>>1]|0;q=e[a+(p+-1<<1)>>1]|0;r=p<<1;o=e[a+(r+-1<<1)>>1]|0;l=p*3|0;m=e[a+(l+-1<<1)>>1]|0;n=p<<2;k=e[a+(n+-1<<1)>>1]|0;h=p*5|0;i=e[a+(h+-1<<1)>>1]|0;j=p*6|0;g=e[a+(j+-1<<1)>>1]|0;d=p*7|0;f=e[a+(d+-1<<1)>>1]|0;u=RF(((e[((b|0)==0?u:a+(~p<<1)|0)>>1]|0)+2+(s<<1)+q|0)>>>2|0,0,65537,65537)|0;t=C;b=a;c[b>>2]=u;c[b+4>>2]=t;b=a+8|0;c[b>>2]=u;c[b+4>>2]=t;s=RF((s+2+(q<<1)+o|0)>>>2|0,0,65537,65537)|0;b=C;p=a+(p<<1)|0;t=p;c[t>>2]=s;c[t+4>>2]=b;p=p+8|0;c[p>>2]=s;c[p+4>>2]=b;q=RF((q+2+(o<<1)+m|0)>>>2|0,0,65537,65537)|0;p=C;b=a+(r<<1)|0;r=b;c[r>>2]=q;c[r+4>>2]=p;b=b+8|0;c[b>>2]=q;c[b+4>>2]=p;o=RF((o+2+(m<<1)+k|0)>>>2|0,0,65537,65537)|0;b=C;l=a+(l<<1)|0;p=l;c[p>>2]=o;c[p+4>>2]=b;l=l+8|0;c[l>>2]=o;c[l+4>>2]=b;m=RF((m+2+(k<<1)+i|0)>>>2|0,0,65537,65537)|0;l=C;b=a+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;k=RF((k+2+(i<<1)+g|0)>>>2|0,0,65537,65537)|0;b=C;h=a+(h<<1)|0;l=h;c[l>>2]=k;c[l+4>>2]=b;h=h+8|0;c[h>>2]=k;c[h+4>>2]=b;i=RF((i+2+(g<<1)+f|0)>>>2|0,0,65537,65537)|0;h=C;b=a+(j<<1)|0;j=b;c[j>>2]=i;c[j+4>>2]=h;b=b+8|0;c[b>>2]=i;c[b+4>>2]=h;f=RF((g+2+(f*3|0)|0)>>>2|0,0,65537,65537)|0;b=C;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function Wi(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;A=g>>1;d=(d|0)!=0;n=~A;q=a+-2|0;u=e[q>>1]|0;j=e[a+(A+-1<<1)>>1]|0;o=e[a+((A<<1)+-1<<1)>>1]|0;p=e[a+((A*3|0)+-1<<1)>>1]|0;r=e[a+((A<<2)+-1<<1)>>1]|0;s=e[a+((A*5|0)+-1<<1)>>1]|0;t=e[a+((A*6|0)+-1<<1)>>1]|0;v=e[a+((A*7|0)+-1<<1)>>1]|0;w=0-A|0;x=e[a+(w<<1)>>1]|0;y=e[a+(1-A<<1)>>1]|0;z=e[a+(2-A<<1)>>1]|0;h=e[a+(3-A<<1)>>1]|0;i=e[a+(4-A<<1)>>1]|0;k=e[a+(5-A<<1)>>1]|0;l=e[a+(6-A<<1)>>1]|0;g=b[a+(7-A<<1)>>1]|0;m=g&65535;if(f)g=b[a+(8-A<<1)>>1]|0;z=(((e[(d?a+(n<<1)|0:q)>>1]|0)+2+(u<<1)+j|0)>>>2)+8+((u+2+(j<<1)+o|0)>>>2)+((j+2+(o<<1)+p|0)>>>2)+((o+2+(p<<1)+r|0)>>>2)+((p+2+(r<<1)+s|0)>>>2)+((r+2+(s<<1)+t|0)>>>2)+((s+2+(t<<1)+v|0)>>>2)+((t+2+(v*3|0)|0)>>>2)+(((e[a+((d?n:w)<<1)>>1]|0)+2+(x<<1)+y|0)>>>2)+((x+2+(y<<1)+z|0)>>>2)+((y+2+(z<<1)+h|0)>>>2)+((z+2+(h<<1)+i|0)>>>2)+((h+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+(((g&65535)+2+(m<<1)+l|0)>>>2)>>4;z=RF(z|0,((z|0)<0)<<31>>31|0,65537,65537)|0;f=C;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;A=a;c[A>>2]=z;c[A+4>>2]=f;a=a+8|0;c[a>>2]=z;c[a+4>>2]=f;return}function Xi(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;o=f>>1;q=0-o|0;x=e[a+(q<<1)>>1]|0;p=e[a+(1-o<<1)>>1]|0;t=e[a+(2-o<<1)>>1]|0;r=(x+2+(p<<1)+t|0)>>>2;u=e[a+(3-o<<1)>>1]|0;s=(p+2+(t<<1)+u|0)>>>2;w=e[a+(4-o<<1)>>1]|0;t=(t+2+(u<<1)+w|0)>>>2;y=e[a+(5-o<<1)>>1]|0;u=(u+2+(w<<1)+y|0)>>>2;v=e[a+(6-o<<1)>>1]|0;w=(w+2+(y<<1)+v|0)>>>2;f=b[a+(7-o<<1)>>1]|0;n=f&65535;y=(y+2+(v<<1)+n|0)>>>2;if(!d){d=(f&65535)+2+(n<<1)|0;g=n;h=n;i=n;j=n;k=n;l=n;m=n;f=n}else{A=e[a+(8-o<<1)>>1]|0;z=e[a+(9-o<<1)>>1]|0;f=e[a+(10-o<<1)>>1]|0;i=e[a+(11-o<<1)>>1]|0;j=e[a+(12-o<<1)>>1]|0;k=e[a+(13-o<<1)>>1]|0;m=e[a+(14-o<<1)>>1]|0;l=e[a+(15-o<<1)>>1]|0;d=(e[a+(8-o<<1)>>1]|0)+2+(n<<1)|0;g=(z+2+(f<<1)+i|0)>>>2;h=(f+2+(i<<1)+j|0)>>>2;i=(i+2+(j<<1)+k|0)>>>2;j=(j+2+(k<<1)+m|0)>>>2;k=(k+2+(m<<1)+l|0)>>>2;l=(m+2+(l*3|0)|0)>>>2;m=(n+2+(A<<1)+z|0)>>>2;f=(A+2+(z<<1)+f|0)>>>2}z=(d+v|0)>>>2;b[a>>1]=((((e[a+(((c|0)==0?q:~o)<<1)>>1]|0)+2+(x<<1)+p|0)>>>2)+2+(r<<1)+s|0)>>>2;A=(r+2+(s<<1)+t|0)>>>2&65535;b[a+2>>1]=A;b[a+(o<<1)>>1]=A;A=(s+2+(t<<1)+u|0)>>>2&65535;b[a+4>>1]=A;b[a+(o+1<<1)>>1]=A;s=o<<1;b[a+(s<<1)>>1]=A;A=(t+2+(u<<1)+w|0)>>>2&65535;b[a+6>>1]=A;b[a+(o+2<<1)>>1]=A;b[a+((s|1)<<1)>>1]=A;t=o*3|0;b[a+(t<<1)>>1]=A;A=(u+2+(w<<1)+y|0)>>>2&65535;b[a+8>>1]=A;b[a+(o+3<<1)>>1]=A;b[a+(s+2<<1)>>1]=A;b[a+(t+1<<1)>>1]=A;v=o<<2;b[a+(v<<1)>>1]=A;A=(w+2+(y<<1)+z|0)>>>2&65535;b[a+10>>1]=A;b[a+(o+4<<1)>>1]=A;b[a+(s+3<<1)>>1]=A;b[a+(t+2<<1)>>1]=A;b[a+((v|1)<<1)>>1]=A;x=o*5|0;b[a+(x<<1)>>1]=A;A=(y+2+(z<<1)+m|0)>>>2&65535;b[a+12>>1]=A;b[a+(o+5<<1)>>1]=A;b[a+(s+4<<1)>>1]=A;b[a+(t+3<<1)>>1]=A;b[a+((v|2)<<1)>>1]=A;b[a+(x+1<<1)>>1]=A;c=o*6|0;b[a+(c<<1)>>1]=A;z=(z+2+(m<<1)+f|0)>>>2&65535;b[a+14>>1]=z;b[a+(o+6<<1)>>1]=z;b[a+(s+5<<1)>>1]=z;b[a+(t+4<<1)>>1]=z;b[a+((v|3)<<1)>>1]=z;b[a+(x+2<<1)>>1]=z;b[a+((c|1)<<1)>>1]=z;A=o*7|0;b[a+(A<<1)>>1]=z;z=(m+2+(f<<1)+g|0)>>>2&65535;b[a+(o+7<<1)>>1]=z;b[a+(s+6<<1)>>1]=z;b[a+(t+5<<1)>>1]=z;b[a+(v+4<<1)>>1]=z;b[a+(x+3<<1)>>1]=z;b[a+(c+2<<1)>>1]=z;b[a+(A+1<<1)>>1]=z;z=(f+2+(g<<1)+h|0)>>>2&65535;b[a+(s+7<<1)>>1]=z;b[a+(t+6<<1)>>1]=z;b[a+(v+5<<1)>>1]=z;b[a+(x+4<<1)>>1]=z;b[a+(c+3<<1)>>1]=z;b[a+(A+2<<1)>>1]=z;z=(g+2+(h<<1)+i|0)>>>2&65535;b[a+(t+7<<1)>>1]=z;b[a+(v+6<<1)>>1]=z;b[a+(x+5<<1)>>1]=z;b[a+(c+4<<1)>>1]=z;b[a+(A+3<<1)>>1]=z;z=(h+2+(i<<1)+j|0)>>>2&65535;b[a+(v+7<<1)>>1]=z;b[a+(x+6<<1)>>1]=z;b[a+(c+5<<1)>>1]=z;b[a+(A+4<<1)>>1]=z;z=(i+2+(j<<1)+k|0)>>>2&65535;b[a+(x+7<<1)>>1]=z;b[a+(c+6<<1)>>1]=z;b[a+(A+5<<1)>>1]=z;z=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=z;b[a+(A+6<<1)>>1]=z;b[a+(A+7<<1)>>1]=(k+2+(l*3|0)|0)>>>2;return}function Yi(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;E=a+(i<<1)|0;v=a+-2|0;F=e[v>>1]|0;x=e[a+(r+-1<<1)>>1]|0;v=((e[(c?E:v)>>1]|0)+2+(F<<1)+x|0)>>>2;i=r<<1;y=e[a+(i+-1<<1)>>1]|0;F=F+2|0;d=(F+(x<<1)+y|0)>>>2;c=r*3|0;z=e[a+(c+-1<<1)>>1]|0;x=(x+2+(y<<1)+z|0)>>>2;s=r<<2;A=e[a+(s+-1<<1)>>1]|0;y=(y+2+(z<<1)+A|0)>>>2;t=r*5|0;B=e[a+(t+-1<<1)>>1]|0;z=(z+2+(A<<1)+B|0)>>>2;u=r*6|0;D=e[a+(u+-1<<1)>>1]|0;A=(A+2+(B<<1)+D|0)>>>2;w=r*7|0;C=e[a+(w+-1<<1)>>1]|0;B=(B+2+(D<<1)+C|0)>>>2;p=(F+((e[E>>1]|0)<<1)+p|0)>>>2;b[a+(w<<1)>>1]=(A+2+((D+2+(C*3|0)|0)>>>2)+(B<<1)|0)>>>2;B=(z+2+(A<<1)+B|0)>>>2&65535;b[a+(w+1<<1)>>1]=B;b[a+(u<<1)>>1]=B;A=(y+2+(z<<1)+A|0)>>>2&65535;b[a+(w+2<<1)>>1]=A;b[a+((u|1)<<1)>>1]=A;b[a+(t<<1)>>1]=A;z=(x+2+(y<<1)+z|0)>>>2&65535;b[a+(w+3<<1)>>1]=z;b[a+(u+2<<1)>>1]=z;b[a+(t+1<<1)>>1]=z;b[a+(s<<1)>>1]=z;y=(d+2+(x<<1)+y|0)>>>2&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+3<<1)>>1]=y;b[a+(t+2<<1)>>1]=y;b[a+((s|1)<<1)>>1]=y;b[a+(c<<1)>>1]=y;x=(v+2+(d<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(t+3<<1)>>1]=x;b[a+((s|2)<<1)>>1]=x;b[a+(c+1<<1)>>1]=x;b[a+(i<<1)>>1]=x;d=((v<<1)+2+d+p|0)>>>2&65535;b[a+(w+6<<1)>>1]=d;b[a+(u+5<<1)>>1]=d;b[a+(t+4<<1)>>1]=d;b[a+((s|3)<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+((i|1)<<1)>>1]=d;b[a+(r<<1)>>1]=d;d=g+2|0;v=(d+v+(p<<1)|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+6<<1)>>1]=v;b[a+(t+5<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+(c+3<<1)>>1]=v;b[a+(i+2<<1)>>1]=v;b[a+(r+1<<1)>>1]=v;b[a>>1]=v;p=((g<<1)+2+h+p|0)>>>2&65535;b[a+(u+7<<1)>>1]=p;b[a+(t+6<<1)>>1]=p;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(i+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;b[a+2>>1]=p;d=(d+(h<<1)+j|0)>>>2&65535;b[a+(t+7<<1)>>1]=d;b[a+(s+6<<1)>>1]=d;b[a+(c+5<<1)>>1]=d;b[a+(i+4<<1)>>1]=d;b[a+(r+3<<1)>>1]=d;b[a+4>>1]=d;d=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=d;b[a+(c+6<<1)>>1]=d;b[a+(i+5<<1)>>1]=d;b[a+(r+4<<1)>>1]=d;b[a+6>>1]=d;d=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=d;b[a+(i+6<<1)>>1]=d;b[a+(r+5<<1)>>1]=d;b[a+8>>1]=d;d=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(i+7<<1)>>1]=d;b[a+(r+6<<1)>>1]=d;b[a+10>>1]=d;d=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(r+7<<1)>>1]=d;b[a+12>>1]=d;b[a+14>>1]=(n+2+(q<<1)+(((f&65535)+2+(o<<1)+m|0)>>>2)|0)>>>2;return}function Zi(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;d=((f&65535)+2+(o<<1)+m|0)>>>2;u=a+(i<<1)|0;t=a+-2|0;B=e[t>>1]|0;v=e[a+(r+-1<<1)>>1]|0;t=((e[(c?u:t)>>1]|0)+2+(B<<1)+v|0)>>>2;o=r<<1;y=e[a+(o+-1<<1)>>1]|0;B=B+2|0;w=(B+(v<<1)+y|0)>>>2;m=r*3|0;x=e[a+(m+-1<<1)>>1]|0;v=(v+2+(y<<1)+x|0)>>>2;i=r<<2;A=e[a+(i+-1<<1)>>1]|0;y=(y+2+(x<<1)+A|0)>>>2;c=r*5|0;C=e[a+(c+-1<<1)>>1]|0;x=(x+2+(A<<1)+C|0)>>>2;f=r*6|0;z=e[a+(f+-1<<1)>>1]|0;A=(A+2+(C<<1)+z|0)>>>2;s=r*7|0;z=(C+2+(z<<1)+(e[a+(s+-1<<1)>>1]|0)|0)>>>2;u=(B+((e[u>>1]|0)<<1)+p|0)>>>2;b[a+(f<<1)>>1]=(y+2+(x<<1)+A|0)>>>2;b[a+(s<<1)>>1]=(x+2+(A<<1)+z|0)>>>2;p=(w+2+(v<<1)+y|0)>>>2&65535;b[a+((f|1)<<1)>>1]=p;b[a+(i<<1)>>1]=p;p=(v+2+(y<<1)+x|0)>>>2&65535;b[a+(s+1<<1)>>1]=p;b[a+(c<<1)>>1]=p;p=((t<<1)+2+w+u|0)>>>2&65535;b[a+(f+2<<1)>>1]=p;b[a+((i|1)<<1)>>1]=p;b[a+(o<<1)>>1]=p;p=(t+2+(w<<1)+v|0)>>>2&65535;b[a+(s+2<<1)>>1]=p;b[a+(c+1<<1)>>1]=p;b[a+(m<<1)>>1]=p;p=g+2|0;t=(p+t+(u<<1)|0)>>>2&65535;b[a+(s+3<<1)>>1]=t;b[a+(c+2<<1)>>1]=t;b[a+(m+1<<1)>>1]=t;b[a+(r<<1)>>1]=t;t=g+1|0;v=(t+u|0)>>>1&65535;b[a+(f+3<<1)>>1]=v;b[a+((i|2)<<1)>>1]=v;b[a+((o|1)<<1)>>1]=v;b[a>>1]=v;g=((g<<1)+2+h+u|0)>>>2&65535;b[a+(s+4<<1)>>1]=g;b[a+(c+3<<1)>>1]=g;b[a+(m+2<<1)>>1]=g;b[a+(r+1<<1)>>1]=g;g=(t+h|0)>>>1&65535;b[a+(f+4<<1)>>1]=g;b[a+((i|3)<<1)>>1]=g;b[a+(o+2<<1)>>1]=g;b[a+2>>1]=g;p=(p+(h<<1)+j|0)>>>2&65535;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(m+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;p=(h+1+j|0)>>>1&65535;b[a+(f+5<<1)>>1]=p;b[a+(i+4<<1)>>1]=p;b[a+(o+3<<1)>>1]=p;b[a+4>>1]=p;p=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+6<<1)>>1]=p;b[a+(c+5<<1)>>1]=p;b[a+(m+4<<1)>>1]=p;b[a+(r+3<<1)>>1]=p;p=(j+1+k|0)>>>1&65535;b[a+(f+6<<1)>>1]=p;b[a+(i+5<<1)>>1]=p;b[a+(o+4<<1)>>1]=p;b[a+6>>1]=p;p=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(s+7<<1)>>1]=p;b[a+(c+6<<1)>>1]=p;b[a+(m+5<<1)>>1]=p;b[a+(r+4<<1)>>1]=p;p=(k+1+l|0)>>>1&65535;b[a+(f+7<<1)>>1]=p;b[a+(i+6<<1)>>1]=p;b[a+(o+5<<1)>>1]=p;b[a+8>>1]=p;p=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(c+7<<1)>>1]=p;b[a+(m+6<<1)>>1]=p;b[a+(r+5<<1)>>1]=p;p=(l+1+n|0)>>>1&65535;b[a+(i+7<<1)>>1]=p;b[a+(o+6<<1)>>1]=p;b[a+10>>1]=p;p=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(m+7<<1)>>1]=p;b[a+(r+6<<1)>>1]=p;p=(n+1+q|0)>>>1&65535;b[a+(o+7<<1)>>1]=p;b[a+12>>1]=p;b[a+(r+7<<1)>>1]=(n+2+(q<<1)+d|0)>>>2;b[a+14>>1]=(q+1+d|0)>>>1;return}function _i(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;i=f>>1;l=(c|0)!=0;n=0-i|0;m=~i;B=e[a+(n<<1)>>1]|0;h=e[a+(1-i<<1)>>1]|0;n=((e[a+((l?m:n)<<1)>>1]|0)+2+(B<<1)+h|0)>>>2;g=e[a+(2-i<<1)>>1]|0;B=B+2|0;j=(B+(h<<1)+g|0)>>>2;f=e[a+(3-i<<1)>>1]|0;h=(h+2+(g<<1)+f|0)>>>2;c=e[a+(4-i<<1)>>1]|0;g=(g+2+(f<<1)+c|0)>>>2;o=e[a+(5-i<<1)>>1]|0;f=(f+2+(c<<1)+o|0)>>>2;d=e[a+(6-i<<1)>>1]|0;c=(c+2+(o<<1)+d|0)>>>2;d=(o+2+(d<<1)+(e[a+(7-i<<1)>>1]|0)|0)>>>2;m=a+(m<<1)|0;o=a+-2|0;A=e[o>>1]|0;k=e[a+(i+-1<<1)>>1]|0;o=((e[(l?m:o)>>1]|0)+2+(A<<1)+k|0)>>>2;l=i<<1;t=e[a+(l+-1<<1)>>1]|0;q=(A+2+(k<<1)+t|0)>>>2;p=i*3|0;v=e[a+(p+-1<<1)>>1]|0;k=(k+2+(t<<1)+v|0)>>>2;r=i<<2;x=e[a+(r+-1<<1)>>1]|0;t=(t+2+(v<<1)+x|0)>>>2;s=i*5|0;y=e[a+(s+-1<<1)>>1]|0;v=(v+2+(x<<1)+y|0)>>>2;u=i*6|0;C=e[a+(u+-1<<1)>>1]|0;x=(x+2+(y<<1)+C|0)>>>2;w=i*7|0;z=e[a+(w+-1<<1)>>1]|0;y=(y+2+(C<<1)+z|0)>>>2;z=(C+2+(z*3|0)|0)>>>2;m=(B+A+((e[m>>1]|0)<<1)|0)>>>2;b[a+(w<<1)>>1]=(z+1+y|0)>>>1;b[a+(w+1<<1)>>1]=(x+2+z+(y<<1)|0)>>>2;z=(x+1+y|0)>>>1&65535;b[a+(w+2<<1)>>1]=z;b[a+(u<<1)>>1]=z;y=(v+2+(x<<1)+y|0)>>>2&65535;b[a+(w+3<<1)>>1]=y;b[a+((u|1)<<1)>>1]=y;y=(v+1+x|0)>>>1&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+2<<1)>>1]=y;b[a+(s<<1)>>1]=y;x=(t+2+(v<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+3<<1)>>1]=x;b[a+(s+1<<1)>>1]=x;x=(t+1+v|0)>>>1&65535;b[a+(w+6<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(s+2<<1)>>1]=x;b[a+(r<<1)>>1]=x;v=(k+2+(t<<1)+v|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+5<<1)>>1]=v;b[a+(s+3<<1)>>1]=v;b[a+((r|1)<<1)>>1]=v;v=(k+1+t|0)>>>1&65535;b[a+(u+6<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+((r|2)<<1)>>1]=v;b[a+(p<<1)>>1]=v;t=(q+2+(k<<1)+t|0)>>>2&65535;b[a+(u+7<<1)>>1]=t;b[a+(s+5<<1)>>1]=t;b[a+((r|3)<<1)>>1]=t;b[a+(p+1<<1)>>1]=t;t=(q+1+k|0)>>>1&65535;b[a+(s+6<<1)>>1]=t;b[a+(r+4<<1)>>1]=t;b[a+(p+2<<1)>>1]=t;b[a+(l<<1)>>1]=t;k=(o+2+(q<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=k;b[a+(r+5<<1)>>1]=k;b[a+(p+3<<1)>>1]=k;b[a+((l|1)<<1)>>1]=k;k=o+1|0;s=(k+q|0)>>>1&65535;b[a+(r+6<<1)>>1]=s;b[a+(p+4<<1)>>1]=s;b[a+(l+2<<1)>>1]=s;b[a+(i<<1)>>1]=s;q=((o<<1)+2+q+m|0)>>>2&65535;b[a+(r+7<<1)>>1]=q;b[a+(p+5<<1)>>1]=q;b[a+(l+3<<1)>>1]=q;b[a+(i+1<<1)>>1]=q;k=(k+m|0)>>>1&65535;b[a+(p+6<<1)>>1]=k;b[a+(l+4<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a>>1]=k;k=n+2|0;o=(k+o+(m<<1)|0)>>>2&65535;b[a+(p+7<<1)>>1]=o;b[a+(l+5<<1)>>1]=o;b[a+(i+3<<1)>>1]=o;b[a+2>>1]=o;m=((n<<1)+2+j+m|0)>>>2&65535;b[a+(l+6<<1)>>1]=m;b[a+(i+4<<1)>>1]=m;b[a+4>>1]=m;k=(k+(j<<1)+h|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(i+5<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+(h<<1)+g|0)>>>2&65535;b[a+(i+6<<1)>>1]=j;b[a+8>>1]=j;h=(h+2+(g<<1)+f|0)>>>2&65535;b[a+(i+7<<1)>>1]=h;b[a+10>>1]=h;b[a+12>>1]=(g+2+(f<<1)+c|0)>>>2;b[a+14>>1]=(f+2+(c<<1)+d|0)>>>2;return}function $i(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;s=f>>1;k=0-s|0;l=e[a+(k<<1)>>1]|0;m=e[a+(1-s<<1)>>1]|0;k=((e[a+(((c|0)==0?k:~s)<<1)>>1]|0)+2+(l<<1)+m|0)>>>2;n=e[a+(2-s<<1)>>1]|0;l=(l+2+(m<<1)+n|0)>>>2;o=e[a+(3-s<<1)>>1]|0;m=(m+2+(n<<1)+o|0)>>>2;q=e[a+(4-s<<1)>>1]|0;n=(n+2+(o<<1)+q|0)>>>2;r=e[a+(5-s<<1)>>1]|0;o=(o+2+(q<<1)+r|0)>>>2;p=e[a+(6-s<<1)>>1]|0;q=(q+2+(r<<1)+p|0)>>>2;f=b[a+(7-s<<1)>>1]|0;j=f&65535;r=(r+2+(p<<1)+j|0)>>>2;if(!d){c=(f&65535)+2+(j<<1)|0;d=j;g=j;h=j;i=j;f=j}else{u=e[a+(8-s<<1)>>1]|0;t=e[a+(9-s<<1)>>1]|0;f=e[a+(10-s<<1)>>1]|0;i=e[a+(11-s<<1)>>1]|0;h=e[a+(12-s<<1)>>1]|0;c=(e[a+(8-s<<1)>>1]|0)+2+(j<<1)|0;d=(t+2+(f<<1)+i|0)>>>2;g=(f+2+(i<<1)+h|0)>>>2;h=(i+2+(h<<1)+(e[a+(13-s<<1)>>1]|0)|0)>>>2;i=(j+2+(u<<1)+t|0)>>>2;f=(u+2+(t<<1)+f|0)>>>2}c=(c+p|0)>>>2;b[a>>1]=(k+1+l|0)>>>1;b[a+(s<<1)>>1]=(k+2+(l<<1)+m|0)>>>2;u=(l+1+m|0)>>>1&65535;b[a+2>>1]=u;j=s<<1;b[a+(j<<1)>>1]=u;u=(l+2+(m<<1)+n|0)>>>2&65535;b[a+(s+1<<1)>>1]=u;k=s*3|0;b[a+(k<<1)>>1]=u;u=(m+1+n|0)>>>1&65535;b[a+4>>1]=u;b[a+((j|1)<<1)>>1]=u;l=s<<2;b[a+(l<<1)>>1]=u;u=(m+2+(n<<1)+o|0)>>>2&65535;b[a+(s+2<<1)>>1]=u;b[a+(k+1<<1)>>1]=u;p=s*5|0;b[a+(p<<1)>>1]=u;u=(n+1+o|0)>>>1&65535;b[a+6>>1]=u;b[a+(j+2<<1)>>1]=u;b[a+((l|1)<<1)>>1]=u;t=s*6|0;b[a+(t<<1)>>1]=u;n=(n+2+(o<<1)+q|0)>>>2&65535;b[a+(s+3<<1)>>1]=n;b[a+(k+2<<1)>>1]=n;b[a+(p+1<<1)>>1]=n;u=s*7|0;b[a+(u<<1)>>1]=n;n=(o+1+q|0)>>>1&65535;b[a+8>>1]=n;b[a+(j+3<<1)>>1]=n;b[a+((l|2)<<1)>>1]=n;b[a+((t|1)<<1)>>1]=n;o=(o+2+(q<<1)+r|0)>>>2&65535;b[a+(s+4<<1)>>1]=o;b[a+(k+3<<1)>>1]=o;b[a+(p+2<<1)>>1]=o;b[a+(u+1<<1)>>1]=o;o=(q+1+r|0)>>>1&65535;b[a+10>>1]=o;b[a+(j+4<<1)>>1]=o;b[a+((l|3)<<1)>>1]=o;b[a+(t+2<<1)>>1]=o;q=(q+2+(r<<1)+c|0)>>>2&65535;b[a+(s+5<<1)>>1]=q;b[a+(k+4<<1)>>1]=q;b[a+(p+3<<1)>>1]=q;b[a+(u+2<<1)>>1]=q;q=(r+1+c|0)>>>1&65535;b[a+12>>1]=q;b[a+(j+5<<1)>>1]=q;b[a+(l+4<<1)>>1]=q;b[a+(t+3<<1)>>1]=q;r=(r+2+(c<<1)+i|0)>>>2&65535;b[a+(s+6<<1)>>1]=r;b[a+(k+5<<1)>>1]=r;b[a+(p+4<<1)>>1]=r;b[a+(u+3<<1)>>1]=r;r=(c+1+i|0)>>>1&65535;b[a+14>>1]=r;b[a+(j+6<<1)>>1]=r;b[a+(l+5<<1)>>1]=r;b[a+(t+4<<1)>>1]=r;r=(c+2+(i<<1)+f|0)>>>2&65535;b[a+(s+7<<1)>>1]=r;b[a+(k+6<<1)>>1]=r;b[a+(p+5<<1)>>1]=r;b[a+(u+4<<1)>>1]=r;s=(i+1+f|0)>>>1&65535;b[a+(j+7<<1)>>1]=s;b[a+(l+6<<1)>>1]=s;b[a+(t+5<<1)>>1]=s;s=(i+2+(f<<1)+d|0)>>>2&65535;b[a+(k+7<<1)>>1]=s;b[a+(p+6<<1)>>1]=s;b[a+(u+5<<1)>>1]=s;s=(f+1+d|0)>>>1&65535;b[a+(l+7<<1)>>1]=s;b[a+(t+6<<1)>>1]=s;s=(f+2+(d<<1)+g|0)>>>2&65535;b[a+(p+7<<1)>>1]=s;b[a+(u+6<<1)>>1]=s;b[a+(t+7<<1)>>1]=(d+1+g|0)>>>1;b[a+(u+7<<1)>>1]=(d+2+(g<<1)+h|0)>>>2;return}function aj(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;n=f>>1;r=a+-2|0;q=e[r>>1]|0;p=e[a+(n+-1<<1)>>1]|0;r=((e[((c|0)==0?r:a+(~n<<1)|0)>>1]|0)+2+(q<<1)+p|0)>>>2;l=n<<1;o=e[a+(l+-1<<1)>>1]|0;q=(q+2+(p<<1)+o|0)>>>2;j=n*3|0;m=e[a+(j+-1<<1)>>1]|0;p=(p+2+(o<<1)+m|0)>>>2;h=n<<2;k=e[a+(h+-1<<1)>>1]|0;o=(o+2+(m<<1)+k|0)>>>2;g=n*5|0;i=e[a+(g+-1<<1)>>1]|0;m=(m+2+(k<<1)+i|0)>>>2;f=n*6|0;s=e[a+(f+-1<<1)>>1]|0;k=(k+2+(i<<1)+s|0)>>>2;d=n*7|0;c=e[a+(d+-1<<1)>>1]|0;i=(i+2+(s<<1)+c|0)>>>2;c=(s+2+(c*3|0)|0)>>>2;b[a>>1]=(r+1+q|0)>>>1;b[a+2>>1]=(r+2+(q<<1)+p|0)>>>2;r=(q+1+p|0)>>>1&65535;b[a+4>>1]=r;b[a+(n<<1)>>1]=r;q=(q+2+(p<<1)+o|0)>>>2&65535;b[a+6>>1]=q;b[a+(n+1<<1)>>1]=q;q=(p+1+o|0)>>>1&65535;b[a+8>>1]=q;b[a+(n+2<<1)>>1]=q;b[a+(l<<1)>>1]=q;p=(p+2+(o<<1)+m|0)>>>2&65535;b[a+10>>1]=p;b[a+(n+3<<1)>>1]=p;b[a+((l|1)<<1)>>1]=p;p=(o+1+m|0)>>>1&65535;b[a+12>>1]=p;b[a+(n+4<<1)>>1]=p;b[a+(l+2<<1)>>1]=p;b[a+(j<<1)>>1]=p;o=(o+2+(m<<1)+k|0)>>>2&65535;b[a+14>>1]=o;b[a+(n+5<<1)>>1]=o;b[a+(l+3<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;o=(m+1+k|0)>>>1&65535;b[a+(n+6<<1)>>1]=o;b[a+(l+4<<1)>>1]=o;b[a+(j+2<<1)>>1]=o;b[a+(h<<1)>>1]=o;m=(m+2+(k<<1)+i|0)>>>2&65535;b[a+(n+7<<1)>>1]=m;b[a+(l+5<<1)>>1]=m;b[a+(j+3<<1)>>1]=m;b[a+((h|1)<<1)>>1]=m;m=(k+1+i|0)>>>1&65535;b[a+(l+6<<1)>>1]=m;b[a+(j+4<<1)>>1]=m;b[a+((h|2)<<1)>>1]=m;b[a+(g<<1)>>1]=m;k=(k+2+c+(i<<1)|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(j+5<<1)>>1]=k;b[a+((h|3)<<1)>>1]=k;b[a+(g+1<<1)>>1]=k;k=(c+1+i|0)>>>1&65535;b[a+(j+6<<1)>>1]=k;b[a+(h+4<<1)>>1]=k;b[a+(g+2<<1)>>1]=k;b[a+(f<<1)>>1]=k;i=(i+2+(c*3|0)|0)>>>2&65535;b[a+(j+7<<1)>>1]=i;b[a+(h+5<<1)>>1]=i;b[a+(g+3<<1)>>1]=i;b[a+((f|1)<<1)>>1]=i;c=c&65535;b[a+(d+7<<1)>>1]=c;b[a+(f+7<<1)>>1]=c;b[a+(g+7<<1)>>1]=c;b[a+(h+7<<1)>>1]=c;b[a+(d+6<<1)>>1]=c;b[a+(f+6<<1)>>1]=c;b[a+(g+6<<1)>>1]=c;b[a+(h+6<<1)>>1]=c;b[a+(d+5<<1)>>1]=c;b[a+(f+5<<1)>>1]=c;b[a+(g+5<<1)>>1]=c;b[a+(d+4<<1)>>1]=c;b[a+(f+4<<1)>>1]=c;b[a+(g+4<<1)>>1]=c;b[a+(d+3<<1)>>1]=c;b[a+(f+3<<1)>>1]=c;b[a+(d+2<<1)>>1]=c;b[a+(f+2<<1)>>1]=c;b[a+(d+1<<1)>>1]=c;b[a+(d<<1)>>1]=c;return}function bj(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;d=f>>1;n=a+-2|0;m=e[n>>1]|0;l=e[a+(d+-1<<1)>>1]|0;k=e[a+((d<<1)+-1<<1)>>1]|0;j=e[a+((d*3|0)+-1<<1)>>1]|0;i=e[a+((d<<2)+-1<<1)>>1]|0;h=e[a+((d*5|0)+-1<<1)>>1]|0;g=e[a+((d*6|0)+-1<<1)>>1]|0;f=e[a+((d*7|0)+-1<<1)>>1]|0;f=(((e[((b|0)==0?n:a+(~d<<1)|0)>>1]|0)+2+(m<<1)+l|0)>>>2)+4+((m+2+(l<<1)+k|0)>>>2)+((l+2+(k<<1)+j|0)>>>2)+((k+2+(j<<1)+i|0)>>>2)+((j+2+(i<<1)+h|0)>>>2)+((i+2+(h<<1)+g|0)>>>2)+((h+2+(g<<1)+f|0)>>>2)+((g+2+(f*3|0)|0)>>>2)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;b=C;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function cj(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;q=(((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2)+4+((q+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+((l+2+(m<<1)+n|0)>>>2)+((m+2+(n<<1)+o|0)>>>2)+((n+2+(o<<1)+p|0)>>>2)+(((g&65535)+2+(p<<1)+o|0)>>>2)>>3;q=RF(q|0,((q|0)<0)<<31>>31|0,65537,65537)|0;f=C;d=a;c[d>>2]=q;c[d+4>>2]=f;d=a+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function dj(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;d=e>>1;b=a;c[b>>2]=33554944;c[b+4>>2]=33554944;b=a+8|0;c[b>>2]=33554944;c[b+4>>2]=33554944;b=a+(d<<1)|0;e=b;c[e>>2]=33554944;c[e+4>>2]=33554944;e=b+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;b=b+(d<<1)|0;e=b;c[e>>2]=33554944;c[e+4>>2]=33554944;e=b+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;b=b+(d<<1)|0;e=b;c[e>>2]=33554944;c[e+4>>2]=33554944;e=b+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;b=b+(d<<1)|0;e=b;c[e>>2]=33554944;c[e+4>>2]=33554944;e=b+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;b=b+(d<<1)|0;e=b;c[e>>2]=33554944;c[e+4>>2]=33554944;e=b+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;b=b+(d<<1)|0;e=b;c[e>>2]=33554944;c[e+4>>2]=33554944;e=b+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;d=b+(d<<1)|0;b=d;c[b>>2]=33554944;c[b+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;return}function ej(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function fj(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*7<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*9<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*10<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*11<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*12<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*13<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*14<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function gj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0;b=b>>1;f=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;g=C;d=a;c[d>>2]=f;c[d+4>>2]=g;d=a+8|0;c[d>>2]=f;c[d+4>>2]=g;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(b<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<1;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*3|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<2;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*5|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b*6|0;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;b=b*7|0;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function hj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;f=f+8|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function ij(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;d=c>>1;g=0-d|0;h=a+6+(g<<1)|0;j=a+(d<<2<<1)+-2|0;n=d<<1;i=j+(0-n<<1)|0;l=j+(d<<1)|0;k=i+(g<<1)|0;f=l+(d<<1)|0;m=k+(g<<1)|0;h=((((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+((((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+(((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+((e[h+2>>1]|0)-(e[h+-2>>1]|0))))|0)*17|0)+16>>5;i=((((e[f+(d<<1)>>1]|0)-(e[m+(g<<1)>>1]|0)<<2)+((((e[f>>1]|0)-(e[m>>1]|0)|0)*3|0)+(((e[l>>1]|0)-(e[k>>1]|0)<<1)+((e[j>>1]|0)-(e[i>>1]|0))))|0)*17|0)+16>>5;j=h<<1;k=h*3|0;l=h<<2;m=h*5|0;f=h*6|0;g=h*7|0;n=(_(i+h|0,-3)|0)+16+((e[a+(-2-n)+16>>1]|0)+(e[a+((d*14|0)+-2)>>1]|0)<<4)|0;o=8;while(1){c=n>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a>>1]=c;c=n+h>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+2>>1]=c;c=n+j>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+4>>1]=c;c=n+k>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+6>>1]=c;c=n+l>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+8>>1]=c;c=n+m>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+10>>1]=c;c=n+f>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+12>>1]=c;c=n+g>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+14>>1]=c;if((o|0)>1){n=n+i|0;o=o+-1|0;a=a+(d<<1)|0}else break}return}function jj(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;d=c>>1;n=0-d|0;f=a+6+(n<<1)|0;g=a+(d<<3<<1)+-2|0;o=g+(0-(d<<1)<<1)|0;p=a+((d*6|0)+-2)|0;m=a+((d*22|0)+-2)|0;i=g+(d<<1)|0;h=o+(n<<1)|0;k=i+(d<<1)|0;j=h+(n<<1)|0;c=_(d,-3)|0;q=m+(d<<1)|0;l=p+(n<<1)|0;s=q+(d<<1)|0;r=l+(n<<1)|0;u=s+(d<<1)|0;t=r+(n<<1)|0;o=((((e[u+(d<<1)>>1]|0)-(e[t+(n<<1)>>1]|0)<<3)+((((e[u>>1]|0)-(e[t>>1]|0)|0)*7|0)+((((e[s>>1]|0)-(e[r>>1]|0)|0)*6|0)+((((e[q>>1]|0)-(e[l>>1]|0)|0)*5|0)+(((e[k+(d<<1)>>1]|0)-(e[j+(n<<1)>>1]|0)<<2)+((((e[k>>1]|0)-(e[j>>1]|0)|0)*3|0)+(((e[i>>1]|0)-(e[h>>1]|0)<<1)+((e[g>>1]|0)-(e[o>>1]|0))))))))|0)*5|0)+32>>6;f=((((e[f+8>>1]|0)-(e[f+-8>>1]|0)<<2)+((((e[f+6>>1]|0)-(e[f+-6>>1]|0)|0)*3|0)+(((e[f+4>>1]|0)-(e[f+-4>>1]|0)<<1)+((e[f+2>>1]|0)-(e[f+-2>>1]|0))))|0)*17|0)+16>>5;g=f*3|0;h=f<<1;i=f<<2;j=f*5|0;k=f*6|0;l=f*7|0;m=16-g+(_(o,-7)|0)+((e[p+(c<<1)+(n<<1)+16>>1]|0)+(e[m+(d<<3)>>1]|0)<<4)|0;n=16;while(1){c=m>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a>>1]=c;c=m+f>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+2>>1]=c;c=m+h>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+4>>1]=c;c=m+g>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+6>>1]=c;c=m+i>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+8>>1]=c;c=m+j>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+10>>1]=c;c=m+k>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+12>>1]=c;c=m+l>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+14>>1]=c;if((n|0)>1){m=m+o|0;n=n+-1|0;a=a+(d<<1)|0}else break}return}function kj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=b>>1;k=p<<2;j=p*5|0;m=p<<1;i=p*6|0;h=p*3|0;f=(e[a+(7-p<<1)>>1]|0)+((e[a+(6-p<<1)>>1]|0)+((e[a+(5-p<<1)>>1]|0)+(e[a+(4-p<<1)>>1]|0)))|0;b=p*7|0;d=(e[a+(b+-1<<1)>>1]|0)+((e[a+(i+-1<<1)>>1]|0)+((e[a+(j+-1<<1)>>1]|0)+(e[a+(k+-1<<1)>>1]|0)))|0;o=(e[a+(h+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+((e[a+(p+-1<<1)>>1]|0)+((e[a+-2>>1]|0)+(e[a+(0-p<<1)>>1]|0))+(e[a+(1-p<<1)>>1]|0))+(e[a+(2-p<<1)>>1]|0))+(e[a+(3-p<<1)>>1]|0)+4>>3;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;n=C;l=RF((f+2|0)>>>2|0,0,65537,65537)|0;g=C;q=a;c[q>>2]=o;c[q+4>>2]=n;q=a+8|0;c[q>>2]=l;c[q+4>>2]=g;p=a+(p<<1)|0;q=p;c[q>>2]=o;c[q+4>>2]=n;p=p+8|0;c[p>>2]=l;c[p+4>>2]=g;m=a+(m<<1)|0;p=m;c[p>>2]=o;c[p+4>>2]=n;m=m+8|0;c[m>>2]=l;c[m+4>>2]=g;h=a+(h<<1)|0;m=h;c[m>>2]=o;c[m+4>>2]=n;h=h+8|0;c[h>>2]=l;c[h+4>>2]=g;h=RF((d+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((d+4+f|0)>>>3|0,0,65537,65537)|0;d=C;k=a+(k<<1)|0;l=k;c[l>>2]=h;c[l+4>>2]=g;k=k+8|0;c[k>>2]=f;c[k+4>>2]=d;j=a+(j<<1)|0;k=j;c[k>>2]=h;c[k+4>>2]=g;j=j+8|0;c[j>>2]=f;c[j+4>>2]=d;i=a+(i<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function lj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;m=b>>1;i=m<<2;h=m*5|0;l=m<<1;g=m*6|0;d=m*3|0;b=m*7|0;f=(e[a+(b+-1<<1)>>1]|0)+((e[a+(g+-1<<1)>>1]|0)+((e[a+(h+-1<<1)>>1]|0)+(e[a+(i+-1<<1)>>1]|0)))|0;k=RF(((e[a+(d+-1<<1)>>1]|0)+((e[a+(l+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;j=C;n=a;c[n>>2]=k;c[n+4>>2]=j;n=a+8|0;c[n>>2]=k;c[n+4>>2]=j;m=a+(m<<1)|0;n=m;c[n>>2]=k;c[n+4>>2]=j;m=m+8|0;c[m>>2]=k;c[m+4>>2]=j;l=a+(l<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;l=l+8|0;c[l>>2]=k;c[l+4>>2]=j;d=a+(d<<1)|0;l=d;c[l>>2]=k;c[l+4>>2]=j;d=d+8|0;c[d>>2]=k;c[d+4>>2]=j;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a+(i<<1)|0;j=i;c[j>>2]=f;c[j+4>>2]=d;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;i=h;c[i>>2]=f;c[i+4>>2]=d;h=h+8|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;h=g;c[h>>2]=f;c[h+4>>2]=d;g=g+8|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function mj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function nj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;i=b>>1;p=4-i|0;m=a+(0-i<<1)|0;d=e[a+(p<<1)>>1]|0;l=a+(1-i<<1)|0;h=e[a+(p+1<<1)>>1]|0;k=a+(2-i<<1)|0;o=e[a+(p+2<<1)>>1]|0;j=a+(3-i<<1)|0;p=e[a+(p+3<<1)>>1]|0;s=RF(((e[m>>1]|0)+2+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;p=RF((d+2+h+o+p|0)>>>2|0,0,65537,65537)|0;o=C;h=a;c[h>>2]=s;c[h+4>>2]=r;h=a+8|0;c[h>>2]=p;c[h+4>>2]=o;h=a+(i<<1)|0;d=h;c[d>>2]=s;c[d+4>>2]=r;d=h+8|0;c[d>>2]=p;c[d+4>>2]=o;d=i<<1;g=a+(d<<1)|0;f=g;c[f>>2]=s;c[f+4>>2]=r;f=g+8|0;c[f>>2]=p;c[f+4>>2]=o;f=i*3|0;b=a+(f<<1)|0;n=b;c[n>>2]=s;c[n+4>>2]=r;n=b+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i<<2<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*5<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*6<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*7<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;f=(e[m>>1]|0)+4+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)+(e[a+-2>>1]|0)+(e[a+(i+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;c[a>>2]=f;c[a+4>>2]=d;a=h;c[a>>2]=f;c[a+4>>2]=d;a=g;c[a>>2]=f;c[a+4>>2]=d;c[b>>2]=f;c[b+4>>2]=d;return}function oj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;kj(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function pj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;g=b>>1;m=g<<2;o=e[a+(m+-1<<1)>>1]|0;l=g*5|0;n=e[a+(l+-1<<1)>>1]|0;f=g<<1;k=g*6|0;i=e[a+(k+-1<<1)>>1]|0;d=g*3|0;h=g*7|0;j=e[a+(h+-1<<1)>>1]|0;r=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;q=C;p=a;c[p>>2]=r;c[p+4>>2]=q;p=a+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(g<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(f<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(d<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;j=RF((o+2+n+i+j|0)>>>2|0,0,65537,65537)|0;i=C;m=a+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=a+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;k=a+(k<<1)|0;l=k;c[l>>2]=j;c[l+4>>2]=i;k=k+8|0;c[k>>2]=j;c[k+4>>2]=i;h=a+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(g<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(f<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(d<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+8|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(g<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(f<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+(d<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function qj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=b>>1;p=g<<2;r=e[a+(p+-1<<1)>>1]|0;o=g*5|0;q=e[a+(o+-1<<1)>>1]|0;f=g<<1;n=g*6|0;l=e[a+(n+-1<<1)>>1]|0;b=g*3|0;k=g*7|0;m=e[a+(k+-1<<1)>>1]|0;u=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;t=C;d=a+8|0;j=a+(g<<1)|0;i=j+8|0;c[i>>2]=u;c[i+4>>2]=t;i=a+(f<<1)|0;h=i+8|0;c[h>>2]=u;c[h+4>>2]=t;h=a+(b<<1)|0;s=h+8|0;c[s>>2]=u;c[s+4>>2]=t;m=RF((r+2+q+l+m|0)>>>2|0,0,65537,65537)|0;l=C;p=a+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=a+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;n=a+(n<<1)|0;o=n;c[o>>2]=m;c[o+4>>2]=l;n=n+8|0;c[n>>2]=m;c[n+4>>2]=l;k=a+(k<<1)|0;n=k;c[n>>2]=m;c[n+4>>2]=l;k=k+8|0;c[k>>2]=m;c[k+4>>2]=l;c[a>>2]=33554944;c[a+4>>2]=33554944;a=j;c[a>>2]=33554944;c[a+4>>2]=33554944;a=i;c[a>>2]=33554944;c[a+4>>2]=33554944;a=h;c[a>>2]=33554944;c[a+4>>2]=33554944;a=d;c[a>>2]=33554944;c[a+4>>2]=33554944;a=d+(g<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=d+(f<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=d+(b<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function rj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;j=b>>1;h=0;g=0;f=0;d=0;b=0;i=0;do{k=e[a+((_(i,j)|0)+-1<<1)>>1]|0;h=k+h+(e[a+(i-j<<1)>>1]|0)|0;k=i+4|0;g=(e[a+(k-j<<1)>>1]|0)+g|0;f=(e[a+((_(k,j)|0)+-1<<1)>>1]|0)+f|0;d=(e[a+((_(i+8|0,j)|0)+-1<<1)>>1]|0)+d|0;b=(e[a+((_(i+12|0,j)|0)+-1<<1)>>1]|0)+b|0;i=i+1|0}while((i|0)!=4);k=h+4>>3;k=RF(k|0,((k|0)<0)<<31>>31|0,65537,65537)|0;i=C;m=g+2>>2;m=RF(m|0,((m|0)<0)<<31>>31|0,65537,65537)|0;l=C;r=f+2>>2;o=f+4+g>>3;n=d+2>>2;d=d+4+g>>3;f=b+2>>2;h=b+4+g>>3;q=a;c[q>>2]=k;c[q+4>>2]=i;q=a+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j*3<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;r=RF(r|0,((r|0)<0)<<31>>31|0,65537,65537)|0;q=C;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;l=C;n=RF(n|0,((n|0)<0)<<31>>31|0,65537,65537)|0;m=C;b=RF(d|0,((d|0)<0)<<31>>31|0,65537,65537)|0;d=C;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;g=C;h=RF(h|0,((h|0)<0)<<31>>31|0,65537,65537)|0;i=C;k=a+(j<<2<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*5<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*6<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*7<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j<<3<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*9<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*10<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*11<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*12<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*13<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*14<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*15<<1)|0;a=k;c[a>>2]=f;c[a+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;return}function sj(a,b){a=a|0;b=b|0;lj(a,b);lj(a+(b<<3)|0,b);return}function tj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*7<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*9<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*10<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*11<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*12<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*13<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*14<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function uj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;tj(a,b);h=b>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function vj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;rj(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function wj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;g=b>>1;n=g<<2;p=n+-1|0;v=e[a+(p<<1)>>1]|0;q=g+-1|0;m=g*5|0;o=m+-1|0;k=e[a+(o<<1)>>1]|0;f=g<<1;r=f+-1|0;l=g*6|0;i=l+-1|0;t=e[a+(i<<1)>>1]|0;d=g*3|0;s=d+-1|0;h=g*7|0;j=h+-1|0;u=e[a+(j<<1)>>1]|0;y=RF(((e[a+-2>>1]|0)+2+(e[a+(q<<1)>>1]|0)+(e[a+(r<<1)>>1]|0)+(e[a+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;x=C;w=a;c[w>>2]=y;c[w+4>>2]=x;w=a+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(g<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(f<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(d<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;u=RF((v+2+k+t+u|0)>>>2|0,0,65537,65537)|0;t=C;k=a+(n<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(m<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(l<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(h<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(b<<3)|0;p=e[k+(p<<1)>>1]|0;o=e[k+(o<<1)>>1]|0;i=e[k+(i<<1)>>1]|0;j=e[k+(j<<1)>>1]|0;s=RF(((e[k+-2>>1]|0)+2+(e[k+(q<<1)>>1]|0)+(e[k+(r<<1)>>1]|0)+(e[k+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;q=k;c[q>>2]=s;c[q+4>>2]=r;q=k+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(g<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(f<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(d<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;j=RF((p+2+o+i+j|0)>>>2|0,0,65537,65537)|0;i=C;n=k+(n<<1)|0;o=n;c[o>>2]=j;c[o+4>>2]=i;n=n+8|0;c[n>>2]=j;c[n+4>>2]=i;m=k+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=k+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;h=k+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(g<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(f<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(d<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+8|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(g<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(f<<1)|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+(d<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}
function xj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0;h=b>>1;q=h<<2;s=q+-1|0;z=e[a+(s<<1)>>1]|0;t=h+-1|0;p=h*5|0;r=p+-1|0;w=e[a+(r<<1)>>1]|0;g=h<<1;u=g+-1|0;o=h*6|0;l=o+-1|0;x=e[a+(l<<1)>>1]|0;d=h*3|0;v=d+-1|0;n=h*7|0;m=n+-1|0;y=e[a+(m<<1)>>1]|0;D=RF(((e[a+-2>>1]|0)+2+(e[a+(t<<1)>>1]|0)+(e[a+(u<<1)>>1]|0)+(e[a+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;B=C;f=a;c[f>>2]=D;c[f+4>>2]=B;f=a+8|0;k=f;c[k>>2]=D;c[k+4>>2]=B;k=a+(h<<1)|0;j=k;c[j>>2]=D;c[j+4>>2]=B;j=k+8|0;c[j>>2]=D;c[j+4>>2]=B;j=a+(g<<1)|0;i=j;c[i>>2]=D;c[i+4>>2]=B;i=j+8|0;c[i>>2]=D;c[i+4>>2]=B;i=a+(d<<1)|0;A=i;c[A>>2]=D;c[A+4>>2]=B;A=i+8|0;c[A>>2]=D;c[A+4>>2]=B;y=RF((z+2+w+x+y|0)>>>2|0,0,65537,65537)|0;x=C;w=a+(q<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(p<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(o<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(n<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;b=a+(b<<3)|0;s=e[b+(s<<1)>>1]|0;r=e[b+(r<<1)>>1]|0;l=e[b+(l<<1)>>1]|0;m=e[b+(m<<1)>>1]|0;v=RF(((e[b+-2>>1]|0)+2+(e[b+(t<<1)>>1]|0)+(e[b+(u<<1)>>1]|0)+(e[b+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;u=C;t=b;c[t>>2]=v;c[t+4>>2]=u;t=b+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(h<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(g<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(d<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;m=RF((s+2+r+l+m|0)>>>2|0,0,65537,65537)|0;l=C;q=b+(q<<1)|0;r=q;c[r>>2]=m;c[r+4>>2]=l;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;p=b+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=b+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;b=b+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;b=a;c[b>>2]=33554944;c[b+4>>2]=33554944;b=k;c[b>>2]=33554944;c[b+4>>2]=33554944;b=j;c[b>>2]=33554944;c[b+4>>2]=33554944;b=i;c[b>>2]=33554944;c[b+4>>2]=33554944;b=f;c[b>>2]=33554944;c[b+4>>2]=33554944;b=f+(h<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;b=f+(g<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;b=f+(d<<1)|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function yj(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b<<1)|0;e=d;c[e>>2]=33489407;c[e+4>>2]=33489407;d=d+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b<<1<<1)|0;e=d;c[e>>2]=33489407;c[e+4>>2]=33489407;d=d+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b*3<<1)|0;e=d;c[e>>2]=33489407;c[e+4>>2]=33489407;d=d+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b<<2<<1)|0;e=d;c[e>>2]=33489407;c[e+4>>2]=33489407;d=d+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b*5<<1)|0;e=d;c[e>>2]=33489407;c[e+4>>2]=33489407;d=d+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+(b*6<<1)|0;e=d;c[e>>2]=33489407;c[e+4>>2]=33489407;d=d+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;b=a+(b*7<<1)|0;a=b;c[a>>2]=33489407;c[a+4>>2]=33489407;b=b+8|0;c[b>>2]=33489407;c[b+4>>2]=33489407;return}function zj(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b<<1)|0;e=d;c[e>>2]=33620481;c[e+4>>2]=33620481;d=d+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b<<1<<1)|0;e=d;c[e>>2]=33620481;c[e+4>>2]=33620481;d=d+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b*3<<1)|0;e=d;c[e>>2]=33620481;c[e+4>>2]=33620481;d=d+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b<<2<<1)|0;e=d;c[e>>2]=33620481;c[e+4>>2]=33620481;d=d+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b*5<<1)|0;e=d;c[e>>2]=33620481;c[e+4>>2]=33620481;d=d+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+(b*6<<1)|0;e=d;c[e>>2]=33620481;c[e+4>>2]=33620481;d=d+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;b=a+(b*7<<1)|0;a=b;c[a>>2]=33620481;c[a+4>>2]=33620481;b=b+8|0;c[b>>2]=33620481;c[b+4>>2]=33620481;return}function Aj(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b<<1)|0;e=d;c[e>>2]=33554944;c[e+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b<<1<<1)|0;e=d;c[e>>2]=33554944;c[e+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b*3<<1)|0;e=d;c[e>>2]=33554944;c[e+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b<<2<<1)|0;e=d;c[e>>2]=33554944;c[e+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b*5<<1)|0;e=d;c[e>>2]=33554944;c[e+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+(b*6<<1)|0;e=d;c[e>>2]=33554944;c[e+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;b=a+(b*7<<1)|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+8|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function Bj(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;j=b>>1;i=a;c[i>>2]=33554944;c[i+4>>2]=33554944;i=a+8|0;c[i>>2]=33554944;c[i+4>>2]=33554944;i=a+(j<<1)|0;h=i;c[h>>2]=33554944;c[h+4>>2]=33554944;i=i+8|0;c[i>>2]=33554944;c[i+4>>2]=33554944;i=j<<1;h=a+(i<<1)|0;g=h;c[g>>2]=33554944;c[g+4>>2]=33554944;h=h+8|0;c[h>>2]=33554944;c[h+4>>2]=33554944;h=j*3|0;g=a+(h<<1)|0;f=g;c[f>>2]=33554944;c[f+4>>2]=33554944;g=g+8|0;c[g>>2]=33554944;c[g+4>>2]=33554944;g=j<<2;f=a+(g<<1)|0;e=f;c[e>>2]=33554944;c[e+4>>2]=33554944;f=f+8|0;c[f>>2]=33554944;c[f+4>>2]=33554944;f=j*5|0;e=a+(f<<1)|0;d=e;c[d>>2]=33554944;c[d+4>>2]=33554944;e=e+8|0;c[e>>2]=33554944;c[e+4>>2]=33554944;e=j*6|0;d=a+(e<<1)|0;k=d;c[k>>2]=33554944;c[k+4>>2]=33554944;d=d+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=j*7|0;k=a+(d<<1)|0;l=k;c[l>>2]=33554944;c[l+4>>2]=33554944;k=k+8|0;c[k>>2]=33554944;c[k+4>>2]=33554944;b=a+(b<<3)|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(j<<1)|0;j=a;c[j>>2]=33554944;c[j+4>>2]=33554944;a=a+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(i<<1)|0;i=a;c[i>>2]=33554944;c[i+4>>2]=33554944;a=a+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(h<<1)|0;h=a;c[h>>2]=33554944;c[h+4>>2]=33554944;a=a+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(g<<1)|0;g=a;c[g>>2]=33554944;c[g+4>>2]=33554944;a=a+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(f<<1)|0;f=a;c[f>>2]=33554944;c[f+4>>2]=33554944;a=a+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+(e<<1)|0;e=a;c[e>>2]=33554944;c[e+4>>2]=33554944;a=a+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+(d<<1)|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+8|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function Cj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+((e[a+(0-b<<1)>>1]|0)+((e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))))))))))))))))))+16>>5;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function Dj(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;b=b>>1;d=a+(0-b<<1)|0;j=d;k=c[j>>2]|0;j=c[j+4>>2]|0;h=d+8|0;i=c[h>>2]|0;h=c[h+4>>2]|0;f=d+16|0;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+24|0;e=c[d>>2]|0;d=c[d+4>>2]|0;l=a;c[l>>2]=k;c[l+4>>2]=j;l=a+8|0;c[l>>2]=i;c[l+4>>2]=h;l=a+16|0;c[l>>2]=g;c[l+4>>2]=f;l=a+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<2<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*5<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*6<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*7<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*9<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*10<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*11<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*12<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*13<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*14<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=k;c[a+4>>2]=j;a=b+8|0;c[a>>2]=i;c[a+4>>2]=h;a=b+16|0;c[a>>2]=g;c[a+4>>2]=f;b=b+24|0;c[b>>2]=e;c[b+4>>2]=d;return}function Ej(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;i=f+8|0;c[i>>2]=h;c[i+4>>2]=g;i=f+16|0;c[i>>2]=h;c[i+4>>2]=g;f=f+24|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function Fj(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;a=a+(b<<1)|0;d=a;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+8|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+16|0;c[d>>2]=33489407;c[d+4>>2]=33489407;d=a+24|0;c[d>>2]=33489407;c[d+4>>2]=33489407;b=a+(b<<1)|0;a=b;c[a>>2]=33489407;c[a+4>>2]=33489407;a=b+8|0;c[a>>2]=33489407;c[a+4>>2]=33489407;a=b+16|0;c[a>>2]=33489407;c[a+4>>2]=33489407;b=b+24|0;c[b>>2]=33489407;c[b+4>>2]=33489407;return}function Gj(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;a=a+(b<<1)|0;d=a;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+8|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+16|0;c[d>>2]=33620481;c[d+4>>2]=33620481;d=a+24|0;c[d>>2]=33620481;c[d+4>>2]=33620481;b=a+(b<<1)|0;a=b;c[a>>2]=33620481;c[a+4>>2]=33620481;a=b+8|0;c[a>>2]=33620481;c[a+4>>2]=33620481;a=b+16|0;c[a>>2]=33620481;c[a+4>>2]=33620481;b=b+24|0;c[b>>2]=33620481;c[b+4>>2]=33620481;return}function Hj(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=c>>1;i=0-g|0;h=a+14+(i<<1)|0;u=a+(g<<3<<1)+-2|0;m=g<<1;t=u+(0-m<<1)|0;s=u+(g<<1)|0;r=t+(i<<1)|0;q=s+(g<<1)|0;p=r+(i<<1)|0;o=q+(g<<1)|0;c=p+(i<<1)|0;d=o+(g<<1)|0;f=c+(i<<1)|0;n=d+(g<<1)|0;l=f+(i<<1)|0;k=n+(g<<1)|0;j=l+(i<<1)|0;h=(((e[h+2>>1]|0)-(e[h+-2>>1]|0)+((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+(((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+(((e[h+10>>1]|0)-(e[h+-10>>1]|0)|0)*5|0)+(((e[h+12>>1]|0)-(e[h+-12>>1]|0)|0)*6|0)+(((e[h+14>>1]|0)-(e[h+-14>>1]|0)|0)*7|0)+((e[h+16>>1]|0)-(e[h+-16>>1]|0)<<3)|0)*5|0)+32>>6;i=(((e[u>>1]|0)-(e[t>>1]|0)+((e[s>>1]|0)-(e[r>>1]|0)<<1)+(((e[q>>1]|0)-(e[p>>1]|0)|0)*3|0)+((e[o>>1]|0)-(e[c>>1]|0)<<2)+(((e[d>>1]|0)-(e[f>>1]|0)|0)*5|0)+(((e[n>>1]|0)-(e[l>>1]|0)|0)*6|0)+(((e[k>>1]|0)-(e[j>>1]|0)|0)*7|0)+((e[k+(g<<1)>>1]|0)-(e[j+(i<<1)>>1]|0)<<3)|0)*5|0)+32>>6;j=h<<1;k=h*3|0;l=h<<2;m=((e[a+(-2-m)+32>>1]|0)+(e[a+((g*30|0)+-2)>>1]|0)<<4)+16+(_(i+h|0,-7)|0)|0;n=16;while(1){d=m;f=-16;while(1){c=d>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+(f+16<<1)>>1]=c;c=d+h>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+(f+17<<1)>>1]=c;c=d+j>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+(f+18<<1)>>1]=c;c=d+k>>5;if(c>>>0>=1024)c=0-c>>31&1023;b[a+(f+19<<1)>>1]=c;if((f|0)<-4){d=d+l|0;f=f+4|0}else break}if((n|0)>1){m=m+i|0;n=n+-1|0;a=a+(g<<1)|0}else break}return}function Ij(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function Jj(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))))))))))))))+8>>4;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function Kj(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;a=a+(b<<1)|0;d=a;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+8|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+16|0;c[d>>2]=33554944;c[d+4>>2]=33554944;d=a+24|0;c[d>>2]=33554944;c[d+4>>2]=33554944;b=a+(b<<1)|0;a=b;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+8|0;c[a>>2]=33554944;c[a+4>>2]=33554944;a=b+16|0;c[a>>2]=33554944;c[a+4>>2]=33554944;b=b+24|0;c[b>>2]=33554944;c[b+4>>2]=33554944;return}function Lj(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0;j=f>>1;f=a+(0-j<<1)|0;i=j<<1;h=j*3|0;a=j<<2;g=(e[f>>1]|0)+(c[d>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+16>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+32>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+48>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+4>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+20>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+36>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+52>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+8>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+24>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+40>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+56>>2]|0)+g;f=f+2|0;g=(e[f>>1]|0)+(c[d+12>>2]|0)|0;b[f+(j<<1)>>1]=g;g=(c[d+28>>2]|0)+g|0;b[f+(i<<1)>>1]=g;g=(c[d+44>>2]|0)+g|0;b[f+(h<<1)>>1]=g;b[f+(a<<1)>>1]=(c[d+60>>2]|0)+g;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function Mj(a,d,f){a=a|0;d=d|0;f=f|0;var g=0;f=f>>1;g=(e[a+-2>>1]|0)+(c[d>>2]|0)|0;b[a>>1]=g;g=(c[d+4>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+8>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+12>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+16>>2]|0)|0;b[a>>1]=g;g=(c[d+20>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+24>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+28>>2]|0)+g;a=a+(f<<1)|0;g=(e[a+-2>>1]|0)+(c[d+32>>2]|0)|0;b[a>>1]=g;g=(c[d+36>>2]|0)+g|0;b[a+2>>1]=g;g=(c[d+40>>2]|0)+g|0;b[a+4>>1]=g;b[a+6>>1]=(c[d+44>>2]|0)+g;a=a+(f<<1)|0;f=(e[a+-2>>1]|0)+(c[d+48>>2]|0)|0;b[a>>1]=f;f=(c[d+52>>2]|0)+f|0;b[a+2>>1]=f;f=(c[d+56>>2]|0)+f|0;b[a+4>>1]=f;b[a+6>>1]=(c[d+60>>2]|0)+f;a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function Nj(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;g=f>>1;h=g<<1;i=g*3|0;j=g<<2;k=g*5|0;l=g*6|0;m=g*7|0;n=g<<3;o=d;p=0;f=a+(0-g<<1)|0;while(1){a=(e[f>>1]|0)+(c[o>>2]|0)|0;b[f+(g<<1)>>1]=a;a=(c[o+32>>2]|0)+a|0;b[f+(h<<1)>>1]=a;a=(c[o+64>>2]|0)+a|0;b[f+(i<<1)>>1]=a;a=(c[o+96>>2]|0)+a|0;b[f+(j<<1)>>1]=a;a=(c[o+128>>2]|0)+a|0;b[f+(k<<1)>>1]=a;a=(c[o+160>>2]|0)+a|0;b[f+(l<<1)>>1]=a;a=(c[o+192>>2]|0)+a|0;b[f+(m<<1)>>1]=a;b[f+(n<<1)>>1]=(c[o+224>>2]|0)+a;p=p+1|0;if((p|0)==8)break;else{o=o+4|0;f=f+2|0}}FF(d|0,0,256)|0;return}function Oj(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;g=f>>1;h=d;i=0;f=a;while(1){a=(e[f+-2>>1]|0)+(c[h>>2]|0)|0;b[f>>1]=a;a=(c[h+4>>2]|0)+a|0;b[f+2>>1]=a;a=(c[h+8>>2]|0)+a|0;b[f+4>>1]=a;a=(c[h+12>>2]|0)+a|0;b[f+6>>1]=a;a=(c[h+16>>2]|0)+a|0;b[f+8>>1]=a;a=(c[h+20>>2]|0)+a|0;b[f+10>>1]=a;a=(c[h+24>>2]|0)+a|0;b[f+12>>1]=a;b[f+14>>1]=(c[h+28>>2]|0)+a;i=i+1|0;if((i|0)==8)break;else{h=h+32|0;f=f+(g<<1)|0}}FF(d|0,0,256)|0;return}function Pj(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;u=i;i=i+16|0;t=u;s=h>>1;k=0-s|0;r=e[a+(k<<1)>>1]|0;j=e[a+(1-s<<1)>>1]|0;l=e[a+(2-s<<1)>>1]|0;m=e[a+(3-s<<1)>>1]|0;n=e[a+(4-s<<1)>>1]|0;o=e[a+(5-s<<1)>>1]|0;p=e[a+(6-s<<1)>>1]|0;h=b[a+(7-s<<1)>>1]|0;q=h&65535;if(g)h=b[a+(8-s<<1)>>1]|0;f=((e[a+(((f|0)==0?k:~s)<<1)>>1]|0)+2+(r<<1)+j|0)>>>2&65535;b[t>>1]=f;b[t+2>>1]=(r+2+(j<<1)+l|0)>>>2;b[t+4>>1]=(j+2+(l<<1)+m|0)>>>2;b[t+6>>1]=(l+2+(m<<1)+n|0)>>>2;b[t+8>>1]=(m+2+(n<<1)+o|0)>>>2;b[t+10>>1]=(n+2+(o<<1)+p|0)>>>2;b[t+12>>1]=(o+2+(p<<1)+q|0)>>>2;b[t+14>>1]=((h&65535)+2+(q<<1)+p|0)>>>2;m=s<<1;n=s*3|0;o=s<<2;p=s*5|0;j=s*6|0;k=s*7|0;l=f;q=d;h=0;while(1){f=(l&65535)+(c[q>>2]|0)|0;b[a>>1]=f;f=(c[q+32>>2]|0)+f|0;b[a+(s<<1)>>1]=f;f=(c[q+64>>2]|0)+f|0;b[a+(m<<1)>>1]=f;f=(c[q+96>>2]|0)+f|0;b[a+(n<<1)>>1]=f;f=(c[q+128>>2]|0)+f|0;b[a+(o<<1)>>1]=f;f=(c[q+160>>2]|0)+f|0;b[a+(p<<1)>>1]=f;f=(c[q+192>>2]|0)+f|0;b[a+(j<<1)>>1]=f;b[a+(k<<1)>>1]=(c[q+224>>2]|0)+f;h=h+1|0;if((h|0)==8)break;l=b[t+(h<<1)>>1]|0;q=q+4|0;a=a+2|0}FF(d|0,0,256)|0;i=u;return}function Qj(a,d,f,g,h){a=a|0;d=d|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;m=i;i=i+16|0;l=m;k=h>>1;s=a+-2|0;r=e[s>>1]|0;q=e[a+(k+-1<<1)>>1]|0;p=e[a+((k<<1)+-1<<1)>>1]|0;o=e[a+((k*3|0)+-1<<1)>>1]|0;n=e[a+((k<<2)+-1<<1)>>1]|0;h=e[a+((k*5|0)+-1<<1)>>1]|0;g=e[a+((k*6|0)+-1<<1)>>1]|0;j=e[a+((k*7|0)+-1<<1)>>1]|0;f=((e[((f|0)==0?s:a+(~k<<1)|0)>>1]|0)+2+(r<<1)+q|0)>>>2&65535;b[l>>1]=f;b[l+2>>1]=(r+2+(q<<1)+p|0)>>>2;b[l+4>>1]=(q+2+(p<<1)+o|0)>>>2;b[l+6>>1]=(p+2+(o<<1)+n|0)>>>2;b[l+8>>1]=(o+2+(n<<1)+h|0)>>>2;b[l+10>>1]=(n+2+(h<<1)+g|0)>>>2;b[l+12>>1]=(h+2+(g<<1)+j|0)>>>2;b[l+14>>1]=(g+2+(j*3|0)|0)>>>2;j=d;g=0;h=a;while(1){s=(f&65535)+(c[j>>2]|0)|0;b[h>>1]=s;s=(c[j+4>>2]|0)+s|0;b[h+2>>1]=s;s=(c[j+8>>2]|0)+s|0;b[h+4>>1]=s;s=(c[j+12>>2]|0)+s|0;b[h+6>>1]=s;s=(c[j+16>>2]|0)+s|0;b[h+8>>1]=s;s=(c[j+20>>2]|0)+s|0;b[h+10>>1]=s;s=(c[j+24>>2]|0)+s|0;b[h+12>>1]=s;b[h+14>>1]=(c[j+28>>2]|0)+s;g=g+1|0;if((g|0)==8)break;f=b[l+(g<<1)>>1]|0;j=j+32|0;h=h+(k<<1)|0}FF(d|0,0,256)|0;i=m;return}function Rj(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Lj(a+(c[b>>2]|0)|0,d,e);Lj(a+(c[b+4>>2]|0)|0,d+64|0,e);Lj(a+(c[b+8>>2]|0)|0,d+128|0,e);Lj(a+(c[b+12>>2]|0)|0,d+192|0,e);return}function Sj(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Lj(a+(c[b>>2]|0)|0,d,e);Lj(a+(c[b+4>>2]|0)|0,d+64|0,e);Lj(a+(c[b+8>>2]|0)|0,d+128|0,e);Lj(a+(c[b+12>>2]|0)|0,d+192|0,e);Lj(a+(c[b+32>>2]|0)|0,d+256|0,e);Lj(a+(c[b+36>>2]|0)|0,d+320|0,e);Lj(a+(c[b+40>>2]|0)|0,d+384|0,e);Lj(a+(c[b+44>>2]|0)|0,d+448|0,e);return}function Tj(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=4);return}function Uj(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;h=g>>1;g=0;do{j=a+(c[d+(g<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=4);g=4;do{j=a+(c[d+(g+4<<2)>>2]|0)|0;i=f+(g<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(h<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));g=g+1|0}while((g|0)!=8);return}function Vj(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;Lj(a+(c[b>>2]|0)|0,d,e);Lj(a+(c[b+4>>2]|0)|0,d+64|0,e);Lj(a+(c[b+8>>2]|0)|0,d+128|0,e);Lj(a+(c[b+12>>2]|0)|0,d+192|0,e);Lj(a+(c[b+16>>2]|0)|0,d+256|0,e);Lj(a+(c[b+20>>2]|0)|0,d+320|0,e);Lj(a+(c[b+24>>2]|0)|0,d+384|0,e);Lj(a+(c[b+28>>2]|0)|0,d+448|0,e);Lj(a+(c[b+32>>2]|0)|0,d+512|0,e);Lj(a+(c[b+36>>2]|0)|0,d+576|0,e);Lj(a+(c[b+40>>2]|0)|0,d+640|0,e);Lj(a+(c[b+44>>2]|0)|0,d+704|0,e);Lj(a+(c[b+48>>2]|0)|0,d+768|0,e);Lj(a+(c[b+52>>2]|0)|0,d+832|0,e);Lj(a+(c[b+56>>2]|0)|0,d+896|0,e);Lj(a+(c[b+60>>2]|0)|0,d+960|0,e);return}function Wj(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0;g=g>>1;h=0;do{j=a+(c[d+(h<<2)>>2]|0)|0;i=f+(h<<5<<1)|0;k=(e[j+-2>>1]|0)+(c[i>>2]|0)|0;b[j>>1]=k;k=(c[i+4>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+8>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+12>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+16>>2]|0)|0;b[j>>1]=k;k=(c[i+20>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+24>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+28>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+32>>2]|0)|0;b[j>>1]=k;k=(c[i+36>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+40>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+44>>2]|0)+k;j=j+(g<<1)|0;k=(e[j+-2>>1]|0)+(c[i+48>>2]|0)|0;b[j>>1]=k;k=(c[i+52>>2]|0)+k|0;b[j+2>>1]=k;k=(c[i+56>>2]|0)+k|0;b[j+4>>1]=k;b[j+6>>1]=(c[i+60>>2]|0)+k;j=i+64|0;do{b[i>>1]=0;i=i+2|0}while((i|0)<(j|0));h=h+1|0}while((h|0)!=16);return}function Xj(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;b=d>>1;d=a+(0-b<<1)|0;e=c[d>>2]|0;d=c[d+4>>2]|0;f=a;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1)|0;c[f>>2]=e;c[f+4>>2]=d;f=a+(b<<1<<1)|0;c[f>>2]=e;c[f+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=e;c[b+4>>2]=d;return}function Yj(a,b,d){a=a|0;b=b|0;d=d|0;var f=0;b=d>>1;d=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;f=a;c[f>>2]=d;c[f+4>>2]=C;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(b<<1)|0;c[d>>2]=f;c[d+4>>2]=C;d=b<<1;f=RF(e[a+(d+-1<<1)>>1]|0|0,0,65537,65537)|0;d=a+(d<<1)|0;c[d>>2]=f;c[d+4>>2]=C;b=b*3|0;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;b=a+(b<<1)|0;c[b>>2]=d;c[b+4>>2]=C;return}function Zj(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function _j(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=d>>1;l=e[a+(1-i<<1)>>1]|0;g=e[a+(2-i<<1)>>1]|0;k=e[a+(3-i<<1)>>1]|0;j=e[c>>1]|0;h=e[c+2>>1]|0;f=e[c+4>>1]|0;d=e[c+6>>1]|0;b[a>>1]=((e[a+(0-i<<1)>>1]|0)+2+g+(l<<1)|0)>>>2;c=(l+2+k+(g<<1)|0)>>>2&65535;b[a+(i<<1)>>1]=c;b[a+2>>1]=c;c=(g+2+j+(k<<1)|0)>>>2&65535;g=i<<1;b[a+(g<<1)>>1]=c;b[a+(i+1<<1)>>1]=c;b[a+4>>1]=c;k=(k+2+h+(j<<1)|0)>>>2&65535;c=i*3|0;b[a+(c<<1)>>1]=k;b[a+((g|1)<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+f+(h<<1)|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;b[a+(i+3<<1)>>1]=j;h=(h+2+d+(f<<1)|0)>>>2&65535;b[a+(c+2<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(c+3<<1)>>1]=(f+2+(d*3|0)|0)>>>2;return}function $j(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;h=d>>1;m=e[a+(~h<<1)>>1]|0;g=e[a+(0-h<<1)>>1]|0;f=e[a+(1-h<<1)>>1]|0;d=e[a+(2-h<<1)>>1]|0;c=e[a+(3-h<<1)>>1]|0;k=e[a+-2>>1]|0;n=e[a+(h+-1<<1)>>1]|0;j=h<<1;i=e[a+(j+-1<<1)>>1]|0;l=h*3|0;b[a+(l<<1)>>1]=(n+2+(i<<1)+(e[a+(l+-1<<1)>>1]|0)|0)>>>2;i=(k+2+(n<<1)+i|0)>>>2&65535;b[a+(l+1<<1)>>1]=i;b[a+(j<<1)>>1]=i;i=m+2|0;n=(i+(k<<1)+n|0)>>>2&65535;b[a+(l+2<<1)>>1]=n;b[a+((j|1)<<1)>>1]=n;b[a+(h<<1)>>1]=n;k=((m<<1)+2+g+k|0)>>>2&65535;b[a+(l+3<<1)>>1]=k;b[a+(j+2<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;b[a>>1]=k;i=(i+(g<<1)+f|0)>>>2&65535;b[a+(j+3<<1)>>1]=i;b[a+(h+2<<1)>>1]=i;b[a+2>>1]=i;g=(g+2+(f<<1)+d|0)>>>2&65535;b[a+(h+3<<1)>>1]=g;b[a+4>>1]=g;b[a+6>>1]=(f+2+(d<<1)+c|0)>>>2;return}function ak(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;i=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;k=e[a+(3-j<<1)>>1]|0;g=e[a+-2>>1]|0;f=e[a+(j+-1<<1)>>1]|0;h=j<<1;d=e[a+(h+-1<<1)>>1]|0;c=j*3|0;o=(i+1+n|0)>>>1&65535;b[a+((h|1)<<1)>>1]=o;b[a>>1]=o;o=(n+1+m|0)>>>1&65535;b[a+(h+2<<1)>>1]=o;b[a+2>>1]=o;o=(m+1+l|0)>>>1&65535;b[a+(h+3<<1)>>1]=o;b[a+4>>1]=o;b[a+6>>1]=(l+1+k|0)>>>1;o=((i<<1)+2+n+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=o;b[a+(j<<1)>>1]=o;i=i+2|0;o=(i+(n<<1)+m|0)>>>2&65535;b[a+(c+2<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;n=(n+2+(m<<1)+l|0)>>>2&65535;b[a+(c+3<<1)>>1]=n;b[a+(j+2<<1)>>1]=n;b[a+(j+3<<1)>>1]=(m+2+(l<<1)+k|0)>>>2;b[a+(h<<1)>>1]=(i+(g<<1)+f|0)>>>2;b[a+(c<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function bk(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;j=d>>1;k=e[a+(~j<<1)>>1]|0;n=e[a+(0-j<<1)>>1]|0;m=e[a+(1-j<<1)>>1]|0;l=e[a+(2-j<<1)>>1]|0;i=e[a+-2>>1]|0;g=e[a+(j+-1<<1)>>1]|0;h=j<<1;f=e[a+(h+-1<<1)>>1]|0;c=j*3|0;d=e[a+(c+-1<<1)>>1]|0;o=(k+1+i|0)>>>1&65535;b[a+(j+2<<1)>>1]=o;b[a>>1]=o;o=((k<<1)+2+n+i|0)>>>2&65535;b[a+(j+3<<1)>>1]=o;b[a+2>>1]=o;k=k+2|0;b[a+4>>1]=(k+(n<<1)+m|0)>>>2;b[a+6>>1]=(n+2+(m<<1)+l|0)>>>2;l=(i+1+g|0)>>>1&65535;b[a+(h+2<<1)>>1]=l;b[a+(j<<1)>>1]=l;k=(k+(i<<1)+g|0)>>>2&65535;b[a+(h+3<<1)>>1]=k;b[a+(j+1<<1)>>1]=k;j=(g+1+f|0)>>>1&65535;b[a+(c+2<<1)>>1]=j;b[a+(h<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+3<<1)>>1]=i;b[a+((h|1)<<1)>>1]=i;b[a+(c<<1)>>1]=(f+1+d|0)>>>1;b[a+(c+1<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function ck(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=d>>1;l=e[a+(0-h<<1)>>1]|0;k=e[a+(1-h<<1)>>1]|0;j=e[a+(2-h<<1)>>1]|0;i=e[a+(3-h<<1)>>1]|0;g=e[c>>1]|0;f=e[c+2>>1]|0;d=e[c+4>>1]|0;b[a>>1]=(l+1+k|0)>>>1;m=(k+1+j|0)>>>1&65535;c=h<<1;b[a+(c<<1)>>1]=m;b[a+2>>1]=m;m=(j+1+i|0)>>>1&65535;b[a+((c|1)<<1)>>1]=m;b[a+4>>1]=m;m=(i+1+g|0)>>>1&65535;b[a+(c+2<<1)>>1]=m;b[a+6>>1]=m;b[a+(c+3<<1)>>1]=(g+1+f|0)>>>1;b[a+(h<<1)>>1]=(l+2+(k<<1)+j|0)>>>2;k=(k+2+(j<<1)+i|0)>>>2&65535;c=h*3|0;b[a+(c<<1)>>1]=k;b[a+(h+1<<1)>>1]=k;j=(j+2+(i<<1)+g|0)>>>2&65535;b[a+(c+1<<1)>>1]=j;b[a+(h+2<<1)>>1]=j;i=(i+2+(g<<1)+f|0)>>>2&65535;b[a+(c+2<<1)>>1]=i;b[a+(h+3<<1)>>1]=i;b[a+(c+3<<1)>>1]=(g+2+(f<<1)+d|0)>>>2;return}function dk(a,c,d){a=a|0;c=c|0;d=d|0;var f=0,g=0,h=0,i=0,j=0,k=0;g=d>>1;k=e[a+-2>>1]|0;j=e[a+(g+-1<<1)>>1]|0;c=g<<1;i=e[a+(c+-1<<1)>>1]|0;f=g*3|0;d=b[a+(f+-1<<1)>>1]|0;h=d&65535;b[a>>1]=(k+1+j|0)>>>1;b[a+2>>1]=(k+2+(j<<1)+i|0)>>>2;k=(j+1+i|0)>>>1&65535;b[a+(g<<1)>>1]=k;b[a+4>>1]=k;j=(j+2+(i<<1)+h|0)>>>2&65535;b[a+(g+1<<1)>>1]=j;b[a+6>>1]=j;j=(i+1+h|0)>>>1&65535;b[a+(c<<1)>>1]=j;b[a+(g+2<<1)>>1]=j;h=(i+2+h+(h<<1)|0)>>>2&65535;b[a+((c|1)<<1)>>1]=h;b[a+(g+3<<1)>>1]=h;b[a+(f+3<<1)>>1]=d;b[a+(f+2<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+(f<<1)>>1]=d;b[a+(f+1<<1)>>1]=d;b[a+(c+3<<1)>>1]=d;return}function ek(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0,h=0,i=0;h=d>>1;g=h<<1;b=h*3|0;f=RF(((e[a+-2>>1]|0)+2+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function fk(a,b,d){a=a|0;b=b|0;d=d|0;var f=0,g=0;b=d>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function gk(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b<<1)|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b<<1<<1)|0;c[d>>2]=134154239;c[d+4>>2]=134154239;b=a+(b*3<<1)|0;c[b>>2]=134154239;c[b+4>>2]=134154239;return}function hk(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b<<1)|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b<<1<<1)|0;c[d>>2]=134285313;c[d+4>>2]=134285313;b=a+(b*3<<1)|0;c[b>>2]=134285313;c[b+4>>2]=134285313;return}function ik(a,b,d){a=a|0;b=b|0;d=d|0;b=d>>1;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b<<1)|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b<<1<<1)|0;c[d>>2]=134219776;c[d+4>>2]=134219776;b=a+(b*3<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function jk(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;b[a>>1]=((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2;b[a+2>>1]=(q+2+(i<<1)+k|0)>>>2;b[a+4>>1]=(i+2+(k<<1)+l|0)>>>2;b[a+6>>1]=(k+2+(l<<1)+m|0)>>>2;f=a+8|0;b[f>>1]=(l+2+(m<<1)+n|0)>>>2;b[a+10>>1]=(m+2+(n<<1)+o|0)>>>2;b[a+12>>1]=(n+2+(o<<1)+p|0)>>>2;b[a+14>>1]=((g&65535)+2+(p<<1)+o|0)>>>2;p=a;o=c[p>>2]|0;p=c[p+4>>2]|0;q=c[f>>2]|0;f=c[f+4>>2]|0;d=a+(h<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*3<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<2<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*5<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*6<<1)|0;n=d;c[n>>2]=o;c[n+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h*7<<1)|0;a=d;c[a>>2]=o;c[a+4>>2]=p;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function kk(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;p=f>>1;u=a+-2|0;s=e[u>>1]|0;q=e[a+(p+-1<<1)>>1]|0;r=p<<1;o=e[a+(r+-1<<1)>>1]|0;l=p*3|0;m=e[a+(l+-1<<1)>>1]|0;n=p<<2;k=e[a+(n+-1<<1)>>1]|0;h=p*5|0;i=e[a+(h+-1<<1)>>1]|0;j=p*6|0;g=e[a+(j+-1<<1)>>1]|0;d=p*7|0;f=e[a+(d+-1<<1)>>1]|0;u=RF(((e[((b|0)==0?u:a+(~p<<1)|0)>>1]|0)+2+(s<<1)+q|0)>>>2|0,0,65537,65537)|0;t=C;b=a;c[b>>2]=u;c[b+4>>2]=t;b=a+8|0;c[b>>2]=u;c[b+4>>2]=t;s=RF((s+2+(q<<1)+o|0)>>>2|0,0,65537,65537)|0;b=C;p=a+(p<<1)|0;t=p;c[t>>2]=s;c[t+4>>2]=b;p=p+8|0;c[p>>2]=s;c[p+4>>2]=b;q=RF((q+2+(o<<1)+m|0)>>>2|0,0,65537,65537)|0;p=C;b=a+(r<<1)|0;r=b;c[r>>2]=q;c[r+4>>2]=p;b=b+8|0;c[b>>2]=q;c[b+4>>2]=p;o=RF((o+2+(m<<1)+k|0)>>>2|0,0,65537,65537)|0;b=C;l=a+(l<<1)|0;p=l;c[p>>2]=o;c[p+4>>2]=b;l=l+8|0;c[l>>2]=o;c[l+4>>2]=b;m=RF((m+2+(k<<1)+i|0)>>>2|0,0,65537,65537)|0;l=C;b=a+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;k=RF((k+2+(i<<1)+g|0)>>>2|0,0,65537,65537)|0;b=C;h=a+(h<<1)|0;l=h;c[l>>2]=k;c[l+4>>2]=b;h=h+8|0;c[h>>2]=k;c[h+4>>2]=b;i=RF((i+2+(g<<1)+f|0)>>>2|0,0,65537,65537)|0;h=C;b=a+(j<<1)|0;j=b;c[j>>2]=i;c[j+4>>2]=h;b=b+8|0;c[b>>2]=i;c[b+4>>2]=h;f=RF((g+2+(f*3|0)|0)>>>2|0,0,65537,65537)|0;b=C;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function lk(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;A=g>>1;d=(d|0)!=0;n=~A;q=a+-2|0;u=e[q>>1]|0;j=e[a+(A+-1<<1)>>1]|0;o=e[a+((A<<1)+-1<<1)>>1]|0;p=e[a+((A*3|0)+-1<<1)>>1]|0;r=e[a+((A<<2)+-1<<1)>>1]|0;s=e[a+((A*5|0)+-1<<1)>>1]|0;t=e[a+((A*6|0)+-1<<1)>>1]|0;v=e[a+((A*7|0)+-1<<1)>>1]|0;w=0-A|0;x=e[a+(w<<1)>>1]|0;y=e[a+(1-A<<1)>>1]|0;z=e[a+(2-A<<1)>>1]|0;h=e[a+(3-A<<1)>>1]|0;i=e[a+(4-A<<1)>>1]|0;k=e[a+(5-A<<1)>>1]|0;l=e[a+(6-A<<1)>>1]|0;g=b[a+(7-A<<1)>>1]|0;m=g&65535;if(f)g=b[a+(8-A<<1)>>1]|0;z=(((e[(d?a+(n<<1)|0:q)>>1]|0)+2+(u<<1)+j|0)>>>2)+8+((u+2+(j<<1)+o|0)>>>2)+((j+2+(o<<1)+p|0)>>>2)+((o+2+(p<<1)+r|0)>>>2)+((p+2+(r<<1)+s|0)>>>2)+((r+2+(s<<1)+t|0)>>>2)+((s+2+(t<<1)+v|0)>>>2)+((t+2+(v*3|0)|0)>>>2)+(((e[a+((d?n:w)<<1)>>1]|0)+2+(x<<1)+y|0)>>>2)+((x+2+(y<<1)+z|0)>>>2)+((y+2+(z<<1)+h|0)>>>2)+((z+2+(h<<1)+i|0)>>>2)+((h+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+(((g&65535)+2+(m<<1)+l|0)>>>2)>>4;z=RF(z|0,((z|0)<0)<<31>>31|0,65537,65537)|0;f=C;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;y=a;c[y>>2]=z;c[y+4>>2]=f;y=a+8|0;c[y>>2]=z;c[y+4>>2]=f;a=a+(A<<1)|0;A=a;c[A>>2]=z;c[A+4>>2]=f;a=a+8|0;c[a>>2]=z;c[a+4>>2]=f;return}function mk(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;o=f>>1;q=0-o|0;x=e[a+(q<<1)>>1]|0;p=e[a+(1-o<<1)>>1]|0;t=e[a+(2-o<<1)>>1]|0;r=(x+2+(p<<1)+t|0)>>>2;u=e[a+(3-o<<1)>>1]|0;s=(p+2+(t<<1)+u|0)>>>2;w=e[a+(4-o<<1)>>1]|0;t=(t+2+(u<<1)+w|0)>>>2;y=e[a+(5-o<<1)>>1]|0;u=(u+2+(w<<1)+y|0)>>>2;v=e[a+(6-o<<1)>>1]|0;w=(w+2+(y<<1)+v|0)>>>2;f=b[a+(7-o<<1)>>1]|0;n=f&65535;y=(y+2+(v<<1)+n|0)>>>2;if(!d){d=(f&65535)+2+(n<<1)|0;g=n;h=n;i=n;j=n;k=n;l=n;m=n;f=n}else{A=e[a+(8-o<<1)>>1]|0;z=e[a+(9-o<<1)>>1]|0;f=e[a+(10-o<<1)>>1]|0;i=e[a+(11-o<<1)>>1]|0;j=e[a+(12-o<<1)>>1]|0;k=e[a+(13-o<<1)>>1]|0;m=e[a+(14-o<<1)>>1]|0;l=e[a+(15-o<<1)>>1]|0;d=(e[a+(8-o<<1)>>1]|0)+2+(n<<1)|0;g=(z+2+(f<<1)+i|0)>>>2;h=(f+2+(i<<1)+j|0)>>>2;i=(i+2+(j<<1)+k|0)>>>2;j=(j+2+(k<<1)+m|0)>>>2;k=(k+2+(m<<1)+l|0)>>>2;l=(m+2+(l*3|0)|0)>>>2;m=(n+2+(A<<1)+z|0)>>>2;f=(A+2+(z<<1)+f|0)>>>2}z=(d+v|0)>>>2;b[a>>1]=((((e[a+(((c|0)==0?q:~o)<<1)>>1]|0)+2+(x<<1)+p|0)>>>2)+2+(r<<1)+s|0)>>>2;A=(r+2+(s<<1)+t|0)>>>2&65535;b[a+2>>1]=A;b[a+(o<<1)>>1]=A;A=(s+2+(t<<1)+u|0)>>>2&65535;b[a+4>>1]=A;b[a+(o+1<<1)>>1]=A;s=o<<1;b[a+(s<<1)>>1]=A;A=(t+2+(u<<1)+w|0)>>>2&65535;b[a+6>>1]=A;b[a+(o+2<<1)>>1]=A;b[a+((s|1)<<1)>>1]=A;t=o*3|0;b[a+(t<<1)>>1]=A;A=(u+2+(w<<1)+y|0)>>>2&65535;b[a+8>>1]=A;b[a+(o+3<<1)>>1]=A;b[a+(s+2<<1)>>1]=A;b[a+(t+1<<1)>>1]=A;v=o<<2;b[a+(v<<1)>>1]=A;A=(w+2+(y<<1)+z|0)>>>2&65535;b[a+10>>1]=A;b[a+(o+4<<1)>>1]=A;b[a+(s+3<<1)>>1]=A;b[a+(t+2<<1)>>1]=A;b[a+((v|1)<<1)>>1]=A;x=o*5|0;b[a+(x<<1)>>1]=A;A=(y+2+(z<<1)+m|0)>>>2&65535;b[a+12>>1]=A;b[a+(o+5<<1)>>1]=A;b[a+(s+4<<1)>>1]=A;b[a+(t+3<<1)>>1]=A;b[a+((v|2)<<1)>>1]=A;b[a+(x+1<<1)>>1]=A;c=o*6|0;b[a+(c<<1)>>1]=A;z=(z+2+(m<<1)+f|0)>>>2&65535;b[a+14>>1]=z;b[a+(o+6<<1)>>1]=z;b[a+(s+5<<1)>>1]=z;b[a+(t+4<<1)>>1]=z;b[a+((v|3)<<1)>>1]=z;b[a+(x+2<<1)>>1]=z;b[a+((c|1)<<1)>>1]=z;A=o*7|0;b[a+(A<<1)>>1]=z;z=(m+2+(f<<1)+g|0)>>>2&65535;b[a+(o+7<<1)>>1]=z;b[a+(s+6<<1)>>1]=z;b[a+(t+5<<1)>>1]=z;b[a+(v+4<<1)>>1]=z;b[a+(x+3<<1)>>1]=z;b[a+(c+2<<1)>>1]=z;b[a+(A+1<<1)>>1]=z;z=(f+2+(g<<1)+h|0)>>>2&65535;b[a+(s+7<<1)>>1]=z;b[a+(t+6<<1)>>1]=z;b[a+(v+5<<1)>>1]=z;b[a+(x+4<<1)>>1]=z;b[a+(c+3<<1)>>1]=z;b[a+(A+2<<1)>>1]=z;z=(g+2+(h<<1)+i|0)>>>2&65535;b[a+(t+7<<1)>>1]=z;b[a+(v+6<<1)>>1]=z;b[a+(x+5<<1)>>1]=z;b[a+(c+4<<1)>>1]=z;b[a+(A+3<<1)>>1]=z;z=(h+2+(i<<1)+j|0)>>>2&65535;b[a+(v+7<<1)>>1]=z;b[a+(x+6<<1)>>1]=z;b[a+(c+5<<1)>>1]=z;b[a+(A+4<<1)>>1]=z;z=(i+2+(j<<1)+k|0)>>>2&65535;b[a+(x+7<<1)>>1]=z;b[a+(c+6<<1)>>1]=z;b[a+(A+5<<1)>>1]=z;z=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=z;b[a+(A+6<<1)>>1]=z;b[a+(A+7<<1)>>1]=(k+2+(l*3|0)|0)>>>2;return}function nk(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;E=a+(i<<1)|0;v=a+-2|0;F=e[v>>1]|0;x=e[a+(r+-1<<1)>>1]|0;v=((e[(c?E:v)>>1]|0)+2+(F<<1)+x|0)>>>2;i=r<<1;y=e[a+(i+-1<<1)>>1]|0;F=F+2|0;d=(F+(x<<1)+y|0)>>>2;c=r*3|0;z=e[a+(c+-1<<1)>>1]|0;x=(x+2+(y<<1)+z|0)>>>2;s=r<<2;A=e[a+(s+-1<<1)>>1]|0;y=(y+2+(z<<1)+A|0)>>>2;t=r*5|0;B=e[a+(t+-1<<1)>>1]|0;z=(z+2+(A<<1)+B|0)>>>2;u=r*6|0;D=e[a+(u+-1<<1)>>1]|0;A=(A+2+(B<<1)+D|0)>>>2;w=r*7|0;C=e[a+(w+-1<<1)>>1]|0;B=(B+2+(D<<1)+C|0)>>>2;p=(F+((e[E>>1]|0)<<1)+p|0)>>>2;b[a+(w<<1)>>1]=(A+2+((D+2+(C*3|0)|0)>>>2)+(B<<1)|0)>>>2;B=(z+2+(A<<1)+B|0)>>>2&65535;b[a+(w+1<<1)>>1]=B;b[a+(u<<1)>>1]=B;A=(y+2+(z<<1)+A|0)>>>2&65535;b[a+(w+2<<1)>>1]=A;b[a+((u|1)<<1)>>1]=A;b[a+(t<<1)>>1]=A;z=(x+2+(y<<1)+z|0)>>>2&65535;b[a+(w+3<<1)>>1]=z;b[a+(u+2<<1)>>1]=z;b[a+(t+1<<1)>>1]=z;b[a+(s<<1)>>1]=z;y=(d+2+(x<<1)+y|0)>>>2&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+3<<1)>>1]=y;b[a+(t+2<<1)>>1]=y;b[a+((s|1)<<1)>>1]=y;b[a+(c<<1)>>1]=y;x=(v+2+(d<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(t+3<<1)>>1]=x;b[a+((s|2)<<1)>>1]=x;b[a+(c+1<<1)>>1]=x;b[a+(i<<1)>>1]=x;d=((v<<1)+2+d+p|0)>>>2&65535;b[a+(w+6<<1)>>1]=d;b[a+(u+5<<1)>>1]=d;b[a+(t+4<<1)>>1]=d;b[a+((s|3)<<1)>>1]=d;b[a+(c+2<<1)>>1]=d;b[a+((i|1)<<1)>>1]=d;b[a+(r<<1)>>1]=d;d=g+2|0;v=(d+v+(p<<1)|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+6<<1)>>1]=v;b[a+(t+5<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+(c+3<<1)>>1]=v;b[a+(i+2<<1)>>1]=v;b[a+(r+1<<1)>>1]=v;b[a>>1]=v;p=((g<<1)+2+h+p|0)>>>2&65535;b[a+(u+7<<1)>>1]=p;b[a+(t+6<<1)>>1]=p;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(i+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;b[a+2>>1]=p;d=(d+(h<<1)+j|0)>>>2&65535;b[a+(t+7<<1)>>1]=d;b[a+(s+6<<1)>>1]=d;b[a+(c+5<<1)>>1]=d;b[a+(i+4<<1)>>1]=d;b[a+(r+3<<1)>>1]=d;b[a+4>>1]=d;d=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=d;b[a+(c+6<<1)>>1]=d;b[a+(i+5<<1)>>1]=d;b[a+(r+4<<1)>>1]=d;b[a+6>>1]=d;d=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(c+7<<1)>>1]=d;b[a+(i+6<<1)>>1]=d;b[a+(r+5<<1)>>1]=d;b[a+8>>1]=d;d=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(i+7<<1)>>1]=d;b[a+(r+6<<1)>>1]=d;b[a+10>>1]=d;d=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(r+7<<1)>>1]=d;b[a+12>>1]=d;b[a+14>>1]=(n+2+(q<<1)+(((f&65535)+2+(o<<1)+m|0)>>>2)|0)>>>2;return}function ok(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;r=f>>1;c=(c|0)!=0;g=0-r|0;i=~r;p=e[a+(g<<1)>>1]|0;j=e[a+(1-r<<1)>>1]|0;g=((e[a+((c?i:g)<<1)>>1]|0)+2+(p<<1)+j|0)>>>2;k=e[a+(2-r<<1)>>1]|0;h=(p+2+(j<<1)+k|0)>>>2;l=e[a+(3-r<<1)>>1]|0;j=(j+2+(k<<1)+l|0)>>>2;n=e[a+(4-r<<1)>>1]|0;k=(k+2+(l<<1)+n|0)>>>2;q=e[a+(5-r<<1)>>1]|0;l=(l+2+(n<<1)+q|0)>>>2;m=e[a+(6-r<<1)>>1]|0;n=(n+2+(q<<1)+m|0)>>>2;f=b[a+(7-r<<1)>>1]|0;o=f&65535;q=(q+2+(m<<1)+o|0)>>>2;if(d)f=b[a+(8-r<<1)>>1]|0;d=((f&65535)+2+(o<<1)+m|0)>>>2;u=a+(i<<1)|0;t=a+-2|0;B=e[t>>1]|0;v=e[a+(r+-1<<1)>>1]|0;t=((e[(c?u:t)>>1]|0)+2+(B<<1)+v|0)>>>2;o=r<<1;y=e[a+(o+-1<<1)>>1]|0;B=B+2|0;w=(B+(v<<1)+y|0)>>>2;m=r*3|0;x=e[a+(m+-1<<1)>>1]|0;v=(v+2+(y<<1)+x|0)>>>2;i=r<<2;A=e[a+(i+-1<<1)>>1]|0;y=(y+2+(x<<1)+A|0)>>>2;c=r*5|0;C=e[a+(c+-1<<1)>>1]|0;x=(x+2+(A<<1)+C|0)>>>2;f=r*6|0;z=e[a+(f+-1<<1)>>1]|0;A=(A+2+(C<<1)+z|0)>>>2;s=r*7|0;z=(C+2+(z<<1)+(e[a+(s+-1<<1)>>1]|0)|0)>>>2;u=(B+((e[u>>1]|0)<<1)+p|0)>>>2;b[a+(f<<1)>>1]=(y+2+(x<<1)+A|0)>>>2;b[a+(s<<1)>>1]=(x+2+(A<<1)+z|0)>>>2;p=(w+2+(v<<1)+y|0)>>>2&65535;b[a+((f|1)<<1)>>1]=p;b[a+(i<<1)>>1]=p;p=(v+2+(y<<1)+x|0)>>>2&65535;b[a+(s+1<<1)>>1]=p;b[a+(c<<1)>>1]=p;p=((t<<1)+2+w+u|0)>>>2&65535;b[a+(f+2<<1)>>1]=p;b[a+((i|1)<<1)>>1]=p;b[a+(o<<1)>>1]=p;p=(t+2+(w<<1)+v|0)>>>2&65535;b[a+(s+2<<1)>>1]=p;b[a+(c+1<<1)>>1]=p;b[a+(m<<1)>>1]=p;p=g+2|0;t=(p+t+(u<<1)|0)>>>2&65535;b[a+(s+3<<1)>>1]=t;b[a+(c+2<<1)>>1]=t;b[a+(m+1<<1)>>1]=t;b[a+(r<<1)>>1]=t;t=g+1|0;v=(t+u|0)>>>1&65535;b[a+(f+3<<1)>>1]=v;b[a+((i|2)<<1)>>1]=v;b[a+((o|1)<<1)>>1]=v;b[a>>1]=v;g=((g<<1)+2+h+u|0)>>>2&65535;b[a+(s+4<<1)>>1]=g;b[a+(c+3<<1)>>1]=g;b[a+(m+2<<1)>>1]=g;b[a+(r+1<<1)>>1]=g;g=(t+h|0)>>>1&65535;b[a+(f+4<<1)>>1]=g;b[a+((i|3)<<1)>>1]=g;b[a+(o+2<<1)>>1]=g;b[a+2>>1]=g;p=(p+(h<<1)+j|0)>>>2&65535;b[a+(s+5<<1)>>1]=p;b[a+(c+4<<1)>>1]=p;b[a+(m+3<<1)>>1]=p;b[a+(r+2<<1)>>1]=p;p=(h+1+j|0)>>>1&65535;b[a+(f+5<<1)>>1]=p;b[a+(i+4<<1)>>1]=p;b[a+(o+3<<1)>>1]=p;b[a+4>>1]=p;p=(h+2+(j<<1)+k|0)>>>2&65535;b[a+(s+6<<1)>>1]=p;b[a+(c+5<<1)>>1]=p;b[a+(m+4<<1)>>1]=p;b[a+(r+3<<1)>>1]=p;p=(j+1+k|0)>>>1&65535;b[a+(f+6<<1)>>1]=p;b[a+(i+5<<1)>>1]=p;b[a+(o+4<<1)>>1]=p;b[a+6>>1]=p;p=(j+2+(k<<1)+l|0)>>>2&65535;b[a+(s+7<<1)>>1]=p;b[a+(c+6<<1)>>1]=p;b[a+(m+5<<1)>>1]=p;b[a+(r+4<<1)>>1]=p;p=(k+1+l|0)>>>1&65535;b[a+(f+7<<1)>>1]=p;b[a+(i+6<<1)>>1]=p;b[a+(o+5<<1)>>1]=p;b[a+8>>1]=p;p=(k+2+(l<<1)+n|0)>>>2&65535;b[a+(c+7<<1)>>1]=p;b[a+(m+6<<1)>>1]=p;b[a+(r+5<<1)>>1]=p;p=(l+1+n|0)>>>1&65535;b[a+(i+7<<1)>>1]=p;b[a+(o+6<<1)>>1]=p;b[a+10>>1]=p;p=(l+2+(n<<1)+q|0)>>>2&65535;b[a+(m+7<<1)>>1]=p;b[a+(r+6<<1)>>1]=p;p=(n+1+q|0)>>>1&65535;b[a+(o+7<<1)>>1]=p;b[a+12>>1]=p;b[a+(r+7<<1)>>1]=(n+2+(q<<1)+d|0)>>>2;b[a+14>>1]=(q+1+d|0)>>>1;return}function pk(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;i=f>>1;l=(c|0)!=0;n=0-i|0;m=~i;B=e[a+(n<<1)>>1]|0;h=e[a+(1-i<<1)>>1]|0;n=((e[a+((l?m:n)<<1)>>1]|0)+2+(B<<1)+h|0)>>>2;g=e[a+(2-i<<1)>>1]|0;B=B+2|0;j=(B+(h<<1)+g|0)>>>2;f=e[a+(3-i<<1)>>1]|0;h=(h+2+(g<<1)+f|0)>>>2;c=e[a+(4-i<<1)>>1]|0;g=(g+2+(f<<1)+c|0)>>>2;o=e[a+(5-i<<1)>>1]|0;f=(f+2+(c<<1)+o|0)>>>2;d=e[a+(6-i<<1)>>1]|0;c=(c+2+(o<<1)+d|0)>>>2;d=(o+2+(d<<1)+(e[a+(7-i<<1)>>1]|0)|0)>>>2;m=a+(m<<1)|0;o=a+-2|0;A=e[o>>1]|0;k=e[a+(i+-1<<1)>>1]|0;o=((e[(l?m:o)>>1]|0)+2+(A<<1)+k|0)>>>2;l=i<<1;t=e[a+(l+-1<<1)>>1]|0;q=(A+2+(k<<1)+t|0)>>>2;p=i*3|0;v=e[a+(p+-1<<1)>>1]|0;k=(k+2+(t<<1)+v|0)>>>2;r=i<<2;x=e[a+(r+-1<<1)>>1]|0;t=(t+2+(v<<1)+x|0)>>>2;s=i*5|0;y=e[a+(s+-1<<1)>>1]|0;v=(v+2+(x<<1)+y|0)>>>2;u=i*6|0;C=e[a+(u+-1<<1)>>1]|0;x=(x+2+(y<<1)+C|0)>>>2;w=i*7|0;z=e[a+(w+-1<<1)>>1]|0;y=(y+2+(C<<1)+z|0)>>>2;z=(C+2+(z*3|0)|0)>>>2;m=(B+A+((e[m>>1]|0)<<1)|0)>>>2;b[a+(w<<1)>>1]=(z+1+y|0)>>>1;b[a+(w+1<<1)>>1]=(x+2+z+(y<<1)|0)>>>2;z=(x+1+y|0)>>>1&65535;b[a+(w+2<<1)>>1]=z;b[a+(u<<1)>>1]=z;y=(v+2+(x<<1)+y|0)>>>2&65535;b[a+(w+3<<1)>>1]=y;b[a+((u|1)<<1)>>1]=y;y=(v+1+x|0)>>>1&65535;b[a+(w+4<<1)>>1]=y;b[a+(u+2<<1)>>1]=y;b[a+(s<<1)>>1]=y;x=(t+2+(v<<1)+x|0)>>>2&65535;b[a+(w+5<<1)>>1]=x;b[a+(u+3<<1)>>1]=x;b[a+(s+1<<1)>>1]=x;x=(t+1+v|0)>>>1&65535;b[a+(w+6<<1)>>1]=x;b[a+(u+4<<1)>>1]=x;b[a+(s+2<<1)>>1]=x;b[a+(r<<1)>>1]=x;v=(k+2+(t<<1)+v|0)>>>2&65535;b[a+(w+7<<1)>>1]=v;b[a+(u+5<<1)>>1]=v;b[a+(s+3<<1)>>1]=v;b[a+((r|1)<<1)>>1]=v;v=(k+1+t|0)>>>1&65535;b[a+(u+6<<1)>>1]=v;b[a+(s+4<<1)>>1]=v;b[a+((r|2)<<1)>>1]=v;b[a+(p<<1)>>1]=v;t=(q+2+(k<<1)+t|0)>>>2&65535;b[a+(u+7<<1)>>1]=t;b[a+(s+5<<1)>>1]=t;b[a+((r|3)<<1)>>1]=t;b[a+(p+1<<1)>>1]=t;t=(q+1+k|0)>>>1&65535;b[a+(s+6<<1)>>1]=t;b[a+(r+4<<1)>>1]=t;b[a+(p+2<<1)>>1]=t;b[a+(l<<1)>>1]=t;k=(o+2+(q<<1)+k|0)>>>2&65535;b[a+(s+7<<1)>>1]=k;b[a+(r+5<<1)>>1]=k;b[a+(p+3<<1)>>1]=k;b[a+((l|1)<<1)>>1]=k;k=o+1|0;s=(k+q|0)>>>1&65535;b[a+(r+6<<1)>>1]=s;b[a+(p+4<<1)>>1]=s;b[a+(l+2<<1)>>1]=s;b[a+(i<<1)>>1]=s;q=((o<<1)+2+q+m|0)>>>2&65535;b[a+(r+7<<1)>>1]=q;b[a+(p+5<<1)>>1]=q;b[a+(l+3<<1)>>1]=q;b[a+(i+1<<1)>>1]=q;k=(k+m|0)>>>1&65535;b[a+(p+6<<1)>>1]=k;b[a+(l+4<<1)>>1]=k;b[a+(i+2<<1)>>1]=k;b[a>>1]=k;k=n+2|0;o=(k+o+(m<<1)|0)>>>2&65535;b[a+(p+7<<1)>>1]=o;b[a+(l+5<<1)>>1]=o;b[a+(i+3<<1)>>1]=o;b[a+2>>1]=o;m=((n<<1)+2+j+m|0)>>>2&65535;b[a+(l+6<<1)>>1]=m;b[a+(i+4<<1)>>1]=m;b[a+4>>1]=m;k=(k+(j<<1)+h|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(i+5<<1)>>1]=k;b[a+6>>1]=k;j=(j+2+(h<<1)+g|0)>>>2&65535;b[a+(i+6<<1)>>1]=j;b[a+8>>1]=j;h=(h+2+(g<<1)+f|0)>>>2&65535;b[a+(i+7<<1)>>1]=h;b[a+10>>1]=h;b[a+12>>1]=(g+2+(f<<1)+c|0)>>>2;b[a+14>>1]=(f+2+(c<<1)+d|0)>>>2;return}function qk(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;s=f>>1;k=0-s|0;l=e[a+(k<<1)>>1]|0;m=e[a+(1-s<<1)>>1]|0;k=((e[a+(((c|0)==0?k:~s)<<1)>>1]|0)+2+(l<<1)+m|0)>>>2;n=e[a+(2-s<<1)>>1]|0;l=(l+2+(m<<1)+n|0)>>>2;o=e[a+(3-s<<1)>>1]|0;m=(m+2+(n<<1)+o|0)>>>2;q=e[a+(4-s<<1)>>1]|0;n=(n+2+(o<<1)+q|0)>>>2;r=e[a+(5-s<<1)>>1]|0;o=(o+2+(q<<1)+r|0)>>>2;p=e[a+(6-s<<1)>>1]|0;q=(q+2+(r<<1)+p|0)>>>2;f=b[a+(7-s<<1)>>1]|0;j=f&65535;r=(r+2+(p<<1)+j|0)>>>2;if(!d){c=(f&65535)+2+(j<<1)|0;d=j;g=j;h=j;i=j;f=j}else{u=e[a+(8-s<<1)>>1]|0;t=e[a+(9-s<<1)>>1]|0;f=e[a+(10-s<<1)>>1]|0;i=e[a+(11-s<<1)>>1]|0;h=e[a+(12-s<<1)>>1]|0;c=(e[a+(8-s<<1)>>1]|0)+2+(j<<1)|0;d=(t+2+(f<<1)+i|0)>>>2;g=(f+2+(i<<1)+h|0)>>>2;h=(i+2+(h<<1)+(e[a+(13-s<<1)>>1]|0)|0)>>>2;i=(j+2+(u<<1)+t|0)>>>2;f=(u+2+(t<<1)+f|0)>>>2}c=(c+p|0)>>>2;b[a>>1]=(k+1+l|0)>>>1;b[a+(s<<1)>>1]=(k+2+(l<<1)+m|0)>>>2;u=(l+1+m|0)>>>1&65535;b[a+2>>1]=u;j=s<<1;b[a+(j<<1)>>1]=u;u=(l+2+(m<<1)+n|0)>>>2&65535;b[a+(s+1<<1)>>1]=u;k=s*3|0;b[a+(k<<1)>>1]=u;u=(m+1+n|0)>>>1&65535;b[a+4>>1]=u;b[a+((j|1)<<1)>>1]=u;l=s<<2;b[a+(l<<1)>>1]=u;u=(m+2+(n<<1)+o|0)>>>2&65535;b[a+(s+2<<1)>>1]=u;b[a+(k+1<<1)>>1]=u;p=s*5|0;b[a+(p<<1)>>1]=u;u=(n+1+o|0)>>>1&65535;b[a+6>>1]=u;b[a+(j+2<<1)>>1]=u;b[a+((l|1)<<1)>>1]=u;t=s*6|0;b[a+(t<<1)>>1]=u;n=(n+2+(o<<1)+q|0)>>>2&65535;b[a+(s+3<<1)>>1]=n;b[a+(k+2<<1)>>1]=n;b[a+(p+1<<1)>>1]=n;u=s*7|0;b[a+(u<<1)>>1]=n;n=(o+1+q|0)>>>1&65535;b[a+8>>1]=n;b[a+(j+3<<1)>>1]=n;b[a+((l|2)<<1)>>1]=n;b[a+((t|1)<<1)>>1]=n;o=(o+2+(q<<1)+r|0)>>>2&65535;b[a+(s+4<<1)>>1]=o;b[a+(k+3<<1)>>1]=o;b[a+(p+2<<1)>>1]=o;b[a+(u+1<<1)>>1]=o;o=(q+1+r|0)>>>1&65535;b[a+10>>1]=o;b[a+(j+4<<1)>>1]=o;b[a+((l|3)<<1)>>1]=o;b[a+(t+2<<1)>>1]=o;q=(q+2+(r<<1)+c|0)>>>2&65535;b[a+(s+5<<1)>>1]=q;b[a+(k+4<<1)>>1]=q;b[a+(p+3<<1)>>1]=q;b[a+(u+2<<1)>>1]=q;q=(r+1+c|0)>>>1&65535;b[a+12>>1]=q;b[a+(j+5<<1)>>1]=q;b[a+(l+4<<1)>>1]=q;b[a+(t+3<<1)>>1]=q;r=(r+2+(c<<1)+i|0)>>>2&65535;b[a+(s+6<<1)>>1]=r;b[a+(k+5<<1)>>1]=r;b[a+(p+4<<1)>>1]=r;b[a+(u+3<<1)>>1]=r;r=(c+1+i|0)>>>1&65535;b[a+14>>1]=r;b[a+(j+6<<1)>>1]=r;b[a+(l+5<<1)>>1]=r;b[a+(t+4<<1)>>1]=r;r=(c+2+(i<<1)+f|0)>>>2&65535;b[a+(s+7<<1)>>1]=r;b[a+(k+6<<1)>>1]=r;b[a+(p+5<<1)>>1]=r;b[a+(u+4<<1)>>1]=r;s=(i+1+f|0)>>>1&65535;b[a+(j+7<<1)>>1]=s;b[a+(l+6<<1)>>1]=s;b[a+(t+5<<1)>>1]=s;s=(i+2+(f<<1)+d|0)>>>2&65535;b[a+(k+7<<1)>>1]=s;b[a+(p+6<<1)>>1]=s;b[a+(u+5<<1)>>1]=s;s=(f+1+d|0)>>>1&65535;b[a+(l+7<<1)>>1]=s;b[a+(t+6<<1)>>1]=s;s=(f+2+(d<<1)+g|0)>>>2&65535;b[a+(p+7<<1)>>1]=s;b[a+(u+6<<1)>>1]=s;b[a+(t+7<<1)>>1]=(d+1+g|0)>>>1;b[a+(u+7<<1)>>1]=(d+2+(g<<1)+h|0)>>>2;return}function rk(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;n=f>>1;r=a+-2|0;q=e[r>>1]|0;p=e[a+(n+-1<<1)>>1]|0;r=((e[((c|0)==0?r:a+(~n<<1)|0)>>1]|0)+2+(q<<1)+p|0)>>>2;l=n<<1;o=e[a+(l+-1<<1)>>1]|0;q=(q+2+(p<<1)+o|0)>>>2;j=n*3|0;m=e[a+(j+-1<<1)>>1]|0;p=(p+2+(o<<1)+m|0)>>>2;h=n<<2;k=e[a+(h+-1<<1)>>1]|0;o=(o+2+(m<<1)+k|0)>>>2;g=n*5|0;i=e[a+(g+-1<<1)>>1]|0;m=(m+2+(k<<1)+i|0)>>>2;f=n*6|0;s=e[a+(f+-1<<1)>>1]|0;k=(k+2+(i<<1)+s|0)>>>2;d=n*7|0;c=e[a+(d+-1<<1)>>1]|0;i=(i+2+(s<<1)+c|0)>>>2;c=(s+2+(c*3|0)|0)>>>2;b[a>>1]=(r+1+q|0)>>>1;b[a+2>>1]=(r+2+(q<<1)+p|0)>>>2;r=(q+1+p|0)>>>1&65535;b[a+4>>1]=r;b[a+(n<<1)>>1]=r;q=(q+2+(p<<1)+o|0)>>>2&65535;b[a+6>>1]=q;b[a+(n+1<<1)>>1]=q;q=(p+1+o|0)>>>1&65535;b[a+8>>1]=q;b[a+(n+2<<1)>>1]=q;b[a+(l<<1)>>1]=q;p=(p+2+(o<<1)+m|0)>>>2&65535;b[a+10>>1]=p;b[a+(n+3<<1)>>1]=p;b[a+((l|1)<<1)>>1]=p;p=(o+1+m|0)>>>1&65535;b[a+12>>1]=p;b[a+(n+4<<1)>>1]=p;b[a+(l+2<<1)>>1]=p;b[a+(j<<1)>>1]=p;o=(o+2+(m<<1)+k|0)>>>2&65535;b[a+14>>1]=o;b[a+(n+5<<1)>>1]=o;b[a+(l+3<<1)>>1]=o;b[a+(j+1<<1)>>1]=o;o=(m+1+k|0)>>>1&65535;b[a+(n+6<<1)>>1]=o;b[a+(l+4<<1)>>1]=o;b[a+(j+2<<1)>>1]=o;b[a+(h<<1)>>1]=o;m=(m+2+(k<<1)+i|0)>>>2&65535;b[a+(n+7<<1)>>1]=m;b[a+(l+5<<1)>>1]=m;b[a+(j+3<<1)>>1]=m;b[a+((h|1)<<1)>>1]=m;m=(k+1+i|0)>>>1&65535;b[a+(l+6<<1)>>1]=m;b[a+(j+4<<1)>>1]=m;b[a+((h|2)<<1)>>1]=m;b[a+(g<<1)>>1]=m;k=(k+2+c+(i<<1)|0)>>>2&65535;b[a+(l+7<<1)>>1]=k;b[a+(j+5<<1)>>1]=k;b[a+((h|3)<<1)>>1]=k;b[a+(g+1<<1)>>1]=k;k=(c+1+i|0)>>>1&65535;b[a+(j+6<<1)>>1]=k;b[a+(h+4<<1)>>1]=k;b[a+(g+2<<1)>>1]=k;b[a+(f<<1)>>1]=k;i=(i+2+(c*3|0)|0)>>>2&65535;b[a+(j+7<<1)>>1]=i;b[a+(h+5<<1)>>1]=i;b[a+(g+3<<1)>>1]=i;b[a+((f|1)<<1)>>1]=i;c=c&65535;b[a+(d+7<<1)>>1]=c;b[a+(f+7<<1)>>1]=c;b[a+(g+7<<1)>>1]=c;b[a+(h+7<<1)>>1]=c;b[a+(d+6<<1)>>1]=c;b[a+(f+6<<1)>>1]=c;b[a+(g+6<<1)>>1]=c;b[a+(h+6<<1)>>1]=c;b[a+(d+5<<1)>>1]=c;b[a+(f+5<<1)>>1]=c;b[a+(g+5<<1)>>1]=c;b[a+(d+4<<1)>>1]=c;b[a+(f+4<<1)>>1]=c;b[a+(g+4<<1)>>1]=c;b[a+(d+3<<1)>>1]=c;b[a+(f+3<<1)>>1]=c;b[a+(d+2<<1)>>1]=c;b[a+(f+2<<1)>>1]=c;b[a+(d+1<<1)>>1]=c;b[a+(d<<1)>>1]=c;return}function sk(a,b,d,f){a=a|0;b=b|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;d=f>>1;n=a+-2|0;m=e[n>>1]|0;l=e[a+(d+-1<<1)>>1]|0;k=e[a+((d<<1)+-1<<1)>>1]|0;j=e[a+((d*3|0)+-1<<1)>>1]|0;i=e[a+((d<<2)+-1<<1)>>1]|0;h=e[a+((d*5|0)+-1<<1)>>1]|0;g=e[a+((d*6|0)+-1<<1)>>1]|0;f=e[a+((d*7|0)+-1<<1)>>1]|0;f=(((e[((b|0)==0?n:a+(~d<<1)|0)>>1]|0)+2+(m<<1)+l|0)>>>2)+4+((m+2+(l<<1)+k|0)>>>2)+((l+2+(k<<1)+j|0)>>>2)+((k+2+(j<<1)+i|0)>>>2)+((j+2+(i<<1)+h|0)>>>2)+((i+2+(h<<1)+g|0)>>>2)+((h+2+(g<<1)+f|0)>>>2)+((g+2+(f*3|0)|0)>>>2)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;b=C;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;a=a+(d<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=b;g=a+8|0;c[g>>2]=f;c[g+4>>2]=b;d=a+(d<<1)|0;a=d;c[a>>2]=f;c[a+4>>2]=b;d=d+8|0;c[d>>2]=f;c[d+4>>2]=b;return}function tk(a,d,f,g){a=a|0;d=d|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;h=g>>1;j=0-h|0;q=e[a+(j<<1)>>1]|0;i=e[a+(1-h<<1)>>1]|0;k=e[a+(2-h<<1)>>1]|0;l=e[a+(3-h<<1)>>1]|0;m=e[a+(4-h<<1)>>1]|0;n=e[a+(5-h<<1)>>1]|0;o=e[a+(6-h<<1)>>1]|0;g=b[a+(7-h<<1)>>1]|0;p=g&65535;if(f)g=b[a+(8-h<<1)>>1]|0;q=(((e[a+(((d|0)==0?j:~h)<<1)>>1]|0)+2+(q<<1)+i|0)>>>2)+4+((q+2+(i<<1)+k|0)>>>2)+((i+2+(k<<1)+l|0)>>>2)+((k+2+(l<<1)+m|0)>>>2)+((l+2+(m<<1)+n|0)>>>2)+((m+2+(n<<1)+o|0)>>>2)+((n+2+(o<<1)+p|0)>>>2)+(((g&65535)+2+(p<<1)+o|0)>>>2)>>3;q=RF(q|0,((q|0)<0)<<31>>31|0,65537,65537)|0;f=C;d=a;c[d>>2]=q;c[d+4>>2]=f;d=a+8|0;c[d>>2]=q;c[d+4>>2]=f;d=a+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;a=d+8|0;c[a>>2]=q;c[a+4>>2]=f;d=d+(h<<1)|0;a=d;c[a>>2]=q;c[a+4>>2]=f;d=d+8|0;c[d>>2]=q;c[d+4>>2]=f;return}function uk(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;d=e>>1;b=a;c[b>>2]=134219776;c[b+4>>2]=134219776;b=a+8|0;c[b>>2]=134219776;c[b+4>>2]=134219776;b=a+(d<<1)|0;e=b;c[e>>2]=134219776;c[e+4>>2]=134219776;e=b+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;b=b+(d<<1)|0;e=b;c[e>>2]=134219776;c[e+4>>2]=134219776;e=b+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;b=b+(d<<1)|0;e=b;c[e>>2]=134219776;c[e+4>>2]=134219776;e=b+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;b=b+(d<<1)|0;e=b;c[e>>2]=134219776;c[e+4>>2]=134219776;e=b+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;b=b+(d<<1)|0;e=b;c[e>>2]=134219776;c[e+4>>2]=134219776;e=b+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;b=b+(d<<1)|0;e=b;c[e>>2]=134219776;c[e+4>>2]=134219776;e=b+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;d=b+(d<<1)|0;b=d;c[b>>2]=134219776;c[b+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;return}function vk(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function wk(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;b=b>>1;d=a+(0-b<<1)|0;f=d;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+8|0;e=c[d>>2]|0;d=c[d+4>>2]|0;h=a;c[h>>2]=g;c[h+4>>2]=f;h=a+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<1<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<2<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*5<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*6<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*7<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b<<3<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*9<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*10<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*11<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*12<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*13<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;h=a+(b*14<<1)|0;i=h;c[i>>2]=g;c[i+4>>2]=f;h=h+8|0;c[h>>2]=e;c[h+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=g;c[a+4>>2]=f;b=b+8|0;c[b>>2]=e;c[b+4>>2]=d;return}function xk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0;b=b>>1;f=RF(e[a+-2>>1]|0|0,0,65537,65537)|0;g=C;d=a;c[d>>2]=f;c[d+4>>2]=g;d=a+8|0;c[d>>2]=f;c[d+4>>2]=g;d=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(b<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<1;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*3|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b<<2;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;f=b*5|0;d=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;h=f;c[h>>2]=d;c[h+4>>2]=g;f=f+8|0;c[f>>2]=d;c[f+4>>2]=g;f=b*6|0;g=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;f=a+(f<<1)|0;h=f;c[h>>2]=g;c[h+4>>2]=d;f=f+8|0;c[f>>2]=g;c[f+4>>2]=d;b=b*7|0;f=RF(e[a+(b+-1<<1)>>1]|0|0,0,65537,65537)|0;d=C;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function yk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;f=f+8|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function zk(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;d=c>>1;g=0-d|0;h=a+6+(g<<1)|0;j=a+(d<<2<<1)+-2|0;n=d<<1;i=j+(0-n<<1)|0;l=j+(d<<1)|0;k=i+(g<<1)|0;f=l+(d<<1)|0;m=k+(g<<1)|0;h=((((e[h+8>>1]|0)-(e[h+-8>>1]|0)<<2)+((((e[h+6>>1]|0)-(e[h+-6>>1]|0)|0)*3|0)+(((e[h+4>>1]|0)-(e[h+-4>>1]|0)<<1)+((e[h+2>>1]|0)-(e[h+-2>>1]|0))))|0)*17|0)+16>>5;i=((((e[f+(d<<1)>>1]|0)-(e[m+(g<<1)>>1]|0)<<2)+((((e[f>>1]|0)-(e[m>>1]|0)|0)*3|0)+(((e[l>>1]|0)-(e[k>>1]|0)<<1)+((e[j>>1]|0)-(e[i>>1]|0))))|0)*17|0)+16>>5;j=h<<1;k=h*3|0;l=h<<2;m=h*5|0;f=h*6|0;g=h*7|0;n=(_(i+h|0,-3)|0)+16+((e[a+(-2-n)+16>>1]|0)+(e[a+((d*14|0)+-2)>>1]|0)<<4)|0;o=8;while(1){c=n>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a>>1]=c;c=n+h>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+2>>1]=c;c=n+j>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+4>>1]=c;c=n+k>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+6>>1]=c;c=n+l>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+8>>1]=c;c=n+m>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+10>>1]=c;c=n+f>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+12>>1]=c;c=n+g>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+14>>1]=c;if((o|0)>1){n=n+i|0;o=o+-1|0;a=a+(d<<1)|0}else break}return}function Ak(a,c){a=a|0;c=c|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;d=c>>1;n=0-d|0;f=a+6+(n<<1)|0;g=a+(d<<3<<1)+-2|0;o=g+(0-(d<<1)<<1)|0;p=a+((d*6|0)+-2)|0;m=a+((d*22|0)+-2)|0;i=g+(d<<1)|0;h=o+(n<<1)|0;k=i+(d<<1)|0;j=h+(n<<1)|0;c=_(d,-3)|0;q=m+(d<<1)|0;l=p+(n<<1)|0;s=q+(d<<1)|0;r=l+(n<<1)|0;u=s+(d<<1)|0;t=r+(n<<1)|0;o=((((e[u+(d<<1)>>1]|0)-(e[t+(n<<1)>>1]|0)<<3)+((((e[u>>1]|0)-(e[t>>1]|0)|0)*7|0)+((((e[s>>1]|0)-(e[r>>1]|0)|0)*6|0)+((((e[q>>1]|0)-(e[l>>1]|0)|0)*5|0)+(((e[k+(d<<1)>>1]|0)-(e[j+(n<<1)>>1]|0)<<2)+((((e[k>>1]|0)-(e[j>>1]|0)|0)*3|0)+(((e[i>>1]|0)-(e[h>>1]|0)<<1)+((e[g>>1]|0)-(e[o>>1]|0))))))))|0)*5|0)+32>>6;f=((((e[f+8>>1]|0)-(e[f+-8>>1]|0)<<2)+((((e[f+6>>1]|0)-(e[f+-6>>1]|0)|0)*3|0)+(((e[f+4>>1]|0)-(e[f+-4>>1]|0)<<1)+((e[f+2>>1]|0)-(e[f+-2>>1]|0))))|0)*17|0)+16>>5;g=f*3|0;h=f<<1;i=f<<2;j=f*5|0;k=f*6|0;l=f*7|0;m=16-g+(_(o,-7)|0)+((e[p+(c<<1)+(n<<1)+16>>1]|0)+(e[m+(d<<3)>>1]|0)<<4)|0;n=16;while(1){c=m>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a>>1]=c;c=m+f>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+2>>1]=c;c=m+h>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+4>>1]=c;c=m+g>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+6>>1]=c;c=m+i>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+8>>1]=c;c=m+j>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+10>>1]=c;c=m+k>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+12>>1]=c;c=m+l>>5;if(c>>>0>=4096)c=0-c>>31&4095;b[a+14>>1]=c;if((n|0)>1){m=m+o|0;n=n+-1|0;a=a+(d<<1)|0}else break}return}function Bk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=b>>1;k=p<<2;j=p*5|0;m=p<<1;i=p*6|0;h=p*3|0;f=(e[a+(7-p<<1)>>1]|0)+((e[a+(6-p<<1)>>1]|0)+((e[a+(5-p<<1)>>1]|0)+(e[a+(4-p<<1)>>1]|0)))|0;b=p*7|0;d=(e[a+(b+-1<<1)>>1]|0)+((e[a+(i+-1<<1)>>1]|0)+((e[a+(j+-1<<1)>>1]|0)+(e[a+(k+-1<<1)>>1]|0)))|0;o=(e[a+(h+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+((e[a+(p+-1<<1)>>1]|0)+((e[a+-2>>1]|0)+(e[a+(0-p<<1)>>1]|0))+(e[a+(1-p<<1)>>1]|0))+(e[a+(2-p<<1)>>1]|0))+(e[a+(3-p<<1)>>1]|0)+4>>3;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;n=C;l=RF((f+2|0)>>>2|0,0,65537,65537)|0;g=C;q=a;c[q>>2]=o;c[q+4>>2]=n;q=a+8|0;c[q>>2]=l;c[q+4>>2]=g;p=a+(p<<1)|0;q=p;c[q>>2]=o;c[q+4>>2]=n;p=p+8|0;c[p>>2]=l;c[p+4>>2]=g;m=a+(m<<1)|0;p=m;c[p>>2]=o;c[p+4>>2]=n;m=m+8|0;c[m>>2]=l;c[m+4>>2]=g;h=a+(h<<1)|0;m=h;c[m>>2]=o;c[m+4>>2]=n;h=h+8|0;c[h>>2]=l;c[h+4>>2]=g;h=RF((d+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((d+4+f|0)>>>3|0,0,65537,65537)|0;d=C;k=a+(k<<1)|0;l=k;c[l>>2]=h;c[l+4>>2]=g;k=k+8|0;c[k>>2]=f;c[k+4>>2]=d;j=a+(j<<1)|0;k=j;c[k>>2]=h;c[k+4>>2]=g;j=j+8|0;c[j>>2]=f;c[j+4>>2]=d;i=a+(i<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Ck(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;m=b>>1;i=m<<2;h=m*5|0;l=m<<1;g=m*6|0;d=m*3|0;b=m*7|0;f=(e[a+(b+-1<<1)>>1]|0)+((e[a+(g+-1<<1)>>1]|0)+((e[a+(h+-1<<1)>>1]|0)+(e[a+(i+-1<<1)>>1]|0)))|0;k=RF(((e[a+(d+-1<<1)>>1]|0)+((e[a+(l+-1<<1)>>1]|0)+((e[a+(m+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;j=C;n=a;c[n>>2]=k;c[n+4>>2]=j;n=a+8|0;c[n>>2]=k;c[n+4>>2]=j;m=a+(m<<1)|0;n=m;c[n>>2]=k;c[n+4>>2]=j;m=m+8|0;c[m>>2]=k;c[m+4>>2]=j;l=a+(l<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;l=l+8|0;c[l>>2]=k;c[l+4>>2]=j;d=a+(d<<1)|0;l=d;c[l>>2]=k;c[l+4>>2]=j;d=d+8|0;c[d>>2]=k;c[d+4>>2]=j;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a+(i<<1)|0;j=i;c[j>>2]=f;c[j+4>>2]=d;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;i=h;c[i>>2]=f;c[i+4>>2]=d;h=h+8|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;h=g;c[h>>2]=f;c[h+4>>2]=d;g=g+8|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Dk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*7<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Ek(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;i=b>>1;p=4-i|0;m=a+(0-i<<1)|0;d=e[a+(p<<1)>>1]|0;l=a+(1-i<<1)|0;h=e[a+(p+1<<1)>>1]|0;k=a+(2-i<<1)|0;o=e[a+(p+2<<1)>>1]|0;j=a+(3-i<<1)|0;p=e[a+(p+3<<1)>>1]|0;s=RF(((e[m>>1]|0)+2+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;p=RF((d+2+h+o+p|0)>>>2|0,0,65537,65537)|0;o=C;h=a;c[h>>2]=s;c[h+4>>2]=r;h=a+8|0;c[h>>2]=p;c[h+4>>2]=o;h=a+(i<<1)|0;d=h;c[d>>2]=s;c[d+4>>2]=r;d=h+8|0;c[d>>2]=p;c[d+4>>2]=o;d=i<<1;g=a+(d<<1)|0;f=g;c[f>>2]=s;c[f+4>>2]=r;f=g+8|0;c[f>>2]=p;c[f+4>>2]=o;f=i*3|0;b=a+(f<<1)|0;n=b;c[n>>2]=s;c[n+4>>2]=r;n=b+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i<<2<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*5<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*6<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;n=a+(i*7<<1)|0;q=n;c[q>>2]=s;c[q+4>>2]=r;n=n+8|0;c[n>>2]=p;c[n+4>>2]=o;f=(e[m>>1]|0)+4+(e[l>>1]|0)+(e[k>>1]|0)+(e[j>>1]|0)+(e[a+-2>>1]|0)+(e[a+(i+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;c[a>>2]=f;c[a+4>>2]=d;a=h;c[a>>2]=f;c[a+4>>2]=d;a=g;c[a>>2]=f;c[a+4>>2]=d;c[b>>2]=f;c[b+4>>2]=d;return}function Fk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;Bk(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Gk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;g=b>>1;m=g<<2;o=e[a+(m+-1<<1)>>1]|0;l=g*5|0;n=e[a+(l+-1<<1)>>1]|0;f=g<<1;k=g*6|0;i=e[a+(k+-1<<1)>>1]|0;d=g*3|0;h=g*7|0;j=e[a+(h+-1<<1)>>1]|0;r=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(d+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;q=C;p=a;c[p>>2]=r;c[p+4>>2]=q;p=a+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(g<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(f<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;p=a+(d<<1)|0;s=p;c[s>>2]=r;c[s+4>>2]=q;p=p+8|0;c[p>>2]=r;c[p+4>>2]=q;j=RF((o+2+n+i+j|0)>>>2|0,0,65537,65537)|0;i=C;m=a+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=a+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;k=a+(k<<1)|0;l=k;c[l>>2]=j;c[l+4>>2]=i;k=k+8|0;c[k>>2]=j;c[k+4>>2]=i;h=a+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(g<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(f<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(d<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+8|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(g<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(f<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+(d<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function Hk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;g=b>>1;p=g<<2;r=e[a+(p+-1<<1)>>1]|0;o=g*5|0;q=e[a+(o+-1<<1)>>1]|0;f=g<<1;n=g*6|0;l=e[a+(n+-1<<1)>>1]|0;b=g*3|0;k=g*7|0;m=e[a+(k+-1<<1)>>1]|0;u=RF(((e[a+-2>>1]|0)+2+(e[a+(g+-1<<1)>>1]|0)+(e[a+(f+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;t=C;d=a+8|0;j=a+(g<<1)|0;i=j+8|0;c[i>>2]=u;c[i+4>>2]=t;i=a+(f<<1)|0;h=i+8|0;c[h>>2]=u;c[h+4>>2]=t;h=a+(b<<1)|0;s=h+8|0;c[s>>2]=u;c[s+4>>2]=t;m=RF((r+2+q+l+m|0)>>>2|0,0,65537,65537)|0;l=C;p=a+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=a+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;n=a+(n<<1)|0;o=n;c[o>>2]=m;c[o+4>>2]=l;n=n+8|0;c[n>>2]=m;c[n+4>>2]=l;k=a+(k<<1)|0;n=k;c[n>>2]=m;c[n+4>>2]=l;k=k+8|0;c[k>>2]=m;c[k+4>>2]=l;c[a>>2]=134219776;c[a+4>>2]=134219776;a=j;c[a>>2]=134219776;c[a+4>>2]=134219776;a=i;c[a>>2]=134219776;c[a+4>>2]=134219776;a=h;c[a>>2]=134219776;c[a+4>>2]=134219776;a=d;c[a>>2]=134219776;c[a+4>>2]=134219776;a=d+(g<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=d+(f<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=d+(b<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function Ik(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;j=b>>1;h=0;g=0;f=0;d=0;b=0;i=0;do{k=e[a+((_(i,j)|0)+-1<<1)>>1]|0;h=k+h+(e[a+(i-j<<1)>>1]|0)|0;k=i+4|0;g=(e[a+(k-j<<1)>>1]|0)+g|0;f=(e[a+((_(k,j)|0)+-1<<1)>>1]|0)+f|0;d=(e[a+((_(i+8|0,j)|0)+-1<<1)>>1]|0)+d|0;b=(e[a+((_(i+12|0,j)|0)+-1<<1)>>1]|0)+b|0;i=i+1|0}while((i|0)!=4);k=h+4>>3;k=RF(k|0,((k|0)<0)<<31>>31|0,65537,65537)|0;i=C;m=g+2>>2;m=RF(m|0,((m|0)<0)<<31>>31|0,65537,65537)|0;l=C;r=f+2>>2;o=f+4+g>>3;n=d+2>>2;d=d+4+g>>3;f=b+2>>2;h=b+4+g>>3;q=a;c[q>>2]=k;c[q+4>>2]=i;q=a+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j<<1<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;q=a+(j*3<<1)|0;b=q;c[b>>2]=k;c[b+4>>2]=i;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;r=RF(r|0,((r|0)<0)<<31>>31|0,65537,65537)|0;q=C;o=RF(o|0,((o|0)<0)<<31>>31|0,65537,65537)|0;l=C;n=RF(n|0,((n|0)<0)<<31>>31|0,65537,65537)|0;m=C;b=RF(d|0,((d|0)<0)<<31>>31|0,65537,65537)|0;d=C;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;g=C;h=RF(h|0,((h|0)<0)<<31>>31|0,65537,65537)|0;i=C;k=a+(j<<2<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*5<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*6<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j*7<<1)|0;p=k;c[p>>2]=r;c[p+4>>2]=q;k=k+8|0;c[k>>2]=o;c[k+4>>2]=l;k=a+(j<<3<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*9<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*10<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*11<<1)|0;l=k;c[l>>2]=n;c[l+4>>2]=m;k=k+8|0;c[k>>2]=b;c[k+4>>2]=d;k=a+(j*12<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*13<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*14<<1)|0;d=k;c[d>>2]=f;c[d+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;k=a+(j*15<<1)|0;a=k;c[a>>2]=f;c[a+4>>2]=g;k=k+8|0;c[k>>2]=h;c[k+4>>2]=i;return}function Jk(a,b){a=a|0;b=b|0;Ck(a,b);Ck(a+(b<<3)|0,b);return}function Kk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0;b=b>>1;f=4-b|0;f=(e[a+(f+3<<1)>>1]|0)+((e[a+(f+2<<1)>>1]|0)+((e[a+(f+1<<1)>>1]|0)+(e[a+(f<<1)>>1]|0)))|0;h=RF(((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+(e[a+(0-b<<1)>>1]|0)))+2|0)>>>2|0,0,65537,65537)|0;g=C;f=RF((f+2|0)>>>2|0,0,65537,65537)|0;d=C;i=a;c[i>>2]=h;c[i+4>>2]=g;i=a+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<1<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<2<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*5<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*6<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*7<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b<<3<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*9<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*10<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*11<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*12<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*13<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;i=a+(b*14<<1)|0;j=i;c[j>>2]=h;c[j+4>>2]=g;i=i+8|0;c[i>>2]=f;c[i+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=h;c[a+4>>2]=g;b=b+8|0;c[b>>2]=f;c[b+4>>2]=d;return}function Lk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;Kk(a,b);h=b>>1;g=h<<1;b=h*3|0;f=(e[a+(0-h<<1)>>1]|0)+4+(e[a+(1-h<<1)>>1]|0)+(e[a+(2-h<<1)>>1]|0)+(e[a+(3-h<<1)>>1]|0)+(e[a+-2>>1]|0)+(e[a+(h+-1<<1)>>1]|0)+(e[a+(g+-1<<1)>>1]|0)+(e[a+(b+-1<<1)>>1]|0)>>3;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;i=a;c[i>>2]=f;c[i+4>>2]=d;h=a+(h<<1)|0;c[h>>2]=f;c[h+4>>2]=d;g=a+(g<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Mk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;Ik(a,b);b=b>>1;f=RF(((e[a+(0-b<<1)>>1]|0)+2+(e[a+(1-b<<1)>>1]|0)+(e[a+(2-b<<1)>>1]|0)+(e[a+(3-b<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1)|0;c[g>>2]=f;c[g+4>>2]=d;g=a+(b<<1<<1)|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b*3<<1)|0;c[b>>2]=f;c[b+4>>2]=d;return}function Nk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;g=b>>1;n=g<<2;p=n+-1|0;v=e[a+(p<<1)>>1]|0;q=g+-1|0;m=g*5|0;o=m+-1|0;k=e[a+(o<<1)>>1]|0;f=g<<1;r=f+-1|0;l=g*6|0;i=l+-1|0;t=e[a+(i<<1)>>1]|0;d=g*3|0;s=d+-1|0;h=g*7|0;j=h+-1|0;u=e[a+(j<<1)>>1]|0;y=RF(((e[a+-2>>1]|0)+2+(e[a+(q<<1)>>1]|0)+(e[a+(r<<1)>>1]|0)+(e[a+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;x=C;w=a;c[w>>2]=y;c[w+4>>2]=x;w=a+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(g<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(f<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(d<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;u=RF((v+2+k+t+u|0)>>>2|0,0,65537,65537)|0;t=C;k=a+(n<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(m<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(l<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(h<<1)|0;v=k;c[v>>2]=u;c[v+4>>2]=t;k=k+8|0;c[k>>2]=u;c[k+4>>2]=t;k=a+(b<<3)|0;p=e[k+(p<<1)>>1]|0;o=e[k+(o<<1)>>1]|0;i=e[k+(i<<1)>>1]|0;j=e[k+(j<<1)>>1]|0;s=RF(((e[k+-2>>1]|0)+2+(e[k+(q<<1)>>1]|0)+(e[k+(r<<1)>>1]|0)+(e[k+(s<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;r=C;q=k;c[q>>2]=s;c[q+4>>2]=r;q=k+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(g<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(f<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;q=k+(d<<1)|0;t=q;c[t>>2]=s;c[t+4>>2]=r;q=q+8|0;c[q>>2]=s;c[q+4>>2]=r;j=RF((p+2+o+i+j|0)>>>2|0,0,65537,65537)|0;i=C;n=k+(n<<1)|0;o=n;c[o>>2]=j;c[o+4>>2]=i;n=n+8|0;c[n>>2]=j;c[n+4>>2]=i;m=k+(m<<1)|0;n=m;c[n>>2]=j;c[n+4>>2]=i;m=m+8|0;c[m>>2]=j;c[m+4>>2]=i;l=k+(l<<1)|0;m=l;c[m>>2]=j;c[m+4>>2]=i;l=l+8|0;c[l>>2]=j;c[l+4>>2]=i;h=k+(h<<1)|0;k=h;c[k>>2]=j;c[k+4>>2]=i;h=h+8|0;c[h>>2]=j;c[h+4>>2]=i;b=a+(b<<2)|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(g<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(f<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(d<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+8|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(g<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(f<<1)|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+(d<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function Ok(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0;h=b>>1;q=h<<2;s=q+-1|0;z=e[a+(s<<1)>>1]|0;t=h+-1|0;p=h*5|0;r=p+-1|0;w=e[a+(r<<1)>>1]|0;g=h<<1;u=g+-1|0;o=h*6|0;l=o+-1|0;x=e[a+(l<<1)>>1]|0;d=h*3|0;v=d+-1|0;n=h*7|0;m=n+-1|0;y=e[a+(m<<1)>>1]|0;D=RF(((e[a+-2>>1]|0)+2+(e[a+(t<<1)>>1]|0)+(e[a+(u<<1)>>1]|0)+(e[a+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;B=C;f=a;c[f>>2]=D;c[f+4>>2]=B;f=a+8|0;k=f;c[k>>2]=D;c[k+4>>2]=B;k=a+(h<<1)|0;j=k;c[j>>2]=D;c[j+4>>2]=B;j=k+8|0;c[j>>2]=D;c[j+4>>2]=B;j=a+(g<<1)|0;i=j;c[i>>2]=D;c[i+4>>2]=B;i=j+8|0;c[i>>2]=D;c[i+4>>2]=B;i=a+(d<<1)|0;A=i;c[A>>2]=D;c[A+4>>2]=B;A=i+8|0;c[A>>2]=D;c[A+4>>2]=B;y=RF((z+2+w+x+y|0)>>>2|0,0,65537,65537)|0;x=C;w=a+(q<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(p<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(o<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;w=a+(n<<1)|0;z=w;c[z>>2]=y;c[z+4>>2]=x;w=w+8|0;c[w>>2]=y;c[w+4>>2]=x;b=a+(b<<3)|0;s=e[b+(s<<1)>>1]|0;r=e[b+(r<<1)>>1]|0;l=e[b+(l<<1)>>1]|0;m=e[b+(m<<1)>>1]|0;v=RF(((e[b+-2>>1]|0)+2+(e[b+(t<<1)>>1]|0)+(e[b+(u<<1)>>1]|0)+(e[b+(v<<1)>>1]|0)|0)>>>2|0,0,65537,65537)|0;u=C;t=b;c[t>>2]=v;c[t+4>>2]=u;t=b+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(h<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(g<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;t=b+(d<<1)|0;w=t;c[w>>2]=v;c[w+4>>2]=u;t=t+8|0;c[t>>2]=v;c[t+4>>2]=u;m=RF((s+2+r+l+m|0)>>>2|0,0,65537,65537)|0;l=C;q=b+(q<<1)|0;r=q;c[r>>2]=m;c[r+4>>2]=l;q=q+8|0;c[q>>2]=m;c[q+4>>2]=l;p=b+(p<<1)|0;q=p;c[q>>2]=m;c[q+4>>2]=l;p=p+8|0;c[p>>2]=m;c[p+4>>2]=l;o=b+(o<<1)|0;p=o;c[p>>2]=m;c[p+4>>2]=l;o=o+8|0;c[o>>2]=m;c[o+4>>2]=l;b=b+(n<<1)|0;n=b;c[n>>2]=m;c[n+4>>2]=l;b=b+8|0;c[b>>2]=m;c[b+4>>2]=l;b=a;c[b>>2]=134219776;c[b+4>>2]=134219776;b=k;c[b>>2]=134219776;c[b+4>>2]=134219776;b=j;c[b>>2]=134219776;c[b+4>>2]=134219776;b=i;c[b>>2]=134219776;c[b+4>>2]=134219776;b=f;c[b>>2]=134219776;c[b+4>>2]=134219776;b=f+(h<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;b=f+(g<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;b=f+(d<<1)|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function Pk(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b<<1)|0;e=d;c[e>>2]=134154239;c[e+4>>2]=134154239;d=d+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b<<1<<1)|0;e=d;c[e>>2]=134154239;c[e+4>>2]=134154239;d=d+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b*3<<1)|0;e=d;c[e>>2]=134154239;c[e+4>>2]=134154239;d=d+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b<<2<<1)|0;e=d;c[e>>2]=134154239;c[e+4>>2]=134154239;d=d+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b*5<<1)|0;e=d;c[e>>2]=134154239;c[e+4>>2]=134154239;d=d+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+(b*6<<1)|0;e=d;c[e>>2]=134154239;c[e+4>>2]=134154239;d=d+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;b=a+(b*7<<1)|0;a=b;c[a>>2]=134154239;c[a+4>>2]=134154239;b=b+8|0;c[b>>2]=134154239;c[b+4>>2]=134154239;return}function Qk(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b<<1)|0;e=d;c[e>>2]=134285313;c[e+4>>2]=134285313;d=d+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b<<1<<1)|0;e=d;c[e>>2]=134285313;c[e+4>>2]=134285313;d=d+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b*3<<1)|0;e=d;c[e>>2]=134285313;c[e+4>>2]=134285313;d=d+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b<<2<<1)|0;e=d;c[e>>2]=134285313;c[e+4>>2]=134285313;d=d+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b*5<<1)|0;e=d;c[e>>2]=134285313;c[e+4>>2]=134285313;d=d+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;d=a+(b*6<<1)|0;e=d;c[e>>2]=134285313;c[e+4>>2]=134285313;d=d+8|0;c[d>>2]=134285313;c[d+4>>2]=134285313;b=a+(b*7<<1)|0;a=b;c[a>>2]=134285313;c[a+4>>2]=134285313;b=b+8|0;c[b>>2]=134285313;c[b+4>>2]=134285313;return}function Rk(a,b){a=a|0;b=b|0;var d=0,e=0;b=b>>1;d=a;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b<<1)|0;e=d;c[e>>2]=134219776;c[e+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b<<1<<1)|0;e=d;c[e>>2]=134219776;c[e+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b*3<<1)|0;e=d;c[e>>2]=134219776;c[e+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b<<2<<1)|0;e=d;c[e>>2]=134219776;c[e+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b*5<<1)|0;e=d;c[e>>2]=134219776;c[e+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=a+(b*6<<1)|0;e=d;c[e>>2]=134219776;c[e+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;b=a+(b*7<<1)|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+8|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function Sk(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;j=b>>1;i=a;c[i>>2]=134219776;c[i+4>>2]=134219776;i=a+8|0;c[i>>2]=134219776;c[i+4>>2]=134219776;i=a+(j<<1)|0;h=i;c[h>>2]=134219776;c[h+4>>2]=134219776;i=i+8|0;c[i>>2]=134219776;c[i+4>>2]=134219776;i=j<<1;h=a+(i<<1)|0;g=h;c[g>>2]=134219776;c[g+4>>2]=134219776;h=h+8|0;c[h>>2]=134219776;c[h+4>>2]=134219776;h=j*3|0;g=a+(h<<1)|0;f=g;c[f>>2]=134219776;c[f+4>>2]=134219776;g=g+8|0;c[g>>2]=134219776;c[g+4>>2]=134219776;g=j<<2;f=a+(g<<1)|0;e=f;c[e>>2]=134219776;c[e+4>>2]=134219776;f=f+8|0;c[f>>2]=134219776;c[f+4>>2]=134219776;f=j*5|0;e=a+(f<<1)|0;d=e;c[d>>2]=134219776;c[d+4>>2]=134219776;e=e+8|0;c[e>>2]=134219776;c[e+4>>2]=134219776;e=j*6|0;d=a+(e<<1)|0;k=d;c[k>>2]=134219776;c[k+4>>2]=134219776;d=d+8|0;c[d>>2]=134219776;c[d+4>>2]=134219776;d=j*7|0;k=a+(d<<1)|0;l=k;c[l>>2]=134219776;c[l+4>>2]=134219776;k=k+8|0;c[k>>2]=134219776;c[k+4>>2]=134219776;b=a+(b<<3)|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(j<<1)|0;j=a;c[j>>2]=134219776;c[j+4>>2]=134219776;a=a+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(i<<1)|0;i=a;c[i>>2]=134219776;c[i+4>>2]=134219776;a=a+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(h<<1)|0;h=a;c[h>>2]=134219776;c[h+4>>2]=134219776;a=a+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(g<<1)|0;g=a;c[g>>2]=134219776;c[g+4>>2]=134219776;a=a+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(f<<1)|0;f=a;c[f>>2]=134219776;c[f+4>>2]=134219776;a=a+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;a=b+(e<<1)|0;e=a;c[e>>2]=134219776;c[e+4>>2]=134219776;a=a+8|0;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+(d<<1)|0;a=b;c[a>>2]=134219776;c[a+4>>2]=134219776;b=b+8|0;c[b>>2]=134219776;c[b+4>>2]=134219776;return}function Tk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0;b=b>>1;f=(e[a+(15-b<<1)>>1]|0)+((e[a+(14-b<<1)>>1]|0)+((e[a+(13-b<<1)>>1]|0)+((e[a+(12-b<<1)>>1]|0)+((e[a+(11-b<<1)>>1]|0)+((e[a+(10-b<<1)>>1]|0)+((e[a+(9-b<<1)>>1]|0)+((e[a+(8-b<<1)>>1]|0)+((e[a+(7-b<<1)>>1]|0)+((e[a+(6-b<<1)>>1]|0)+((e[a+(5-b<<1)>>1]|0)+((e[a+(4-b<<1)>>1]|0)+((e[a+(3-b<<1)>>1]|0)+((e[a+(2-b<<1)>>1]|0)+((e[a+(1-b<<1)>>1]|0)+((e[a+(0-b<<1)>>1]|0)+((e[a+((b*15|0)+-1<<1)>>1]|0)+((e[a+((b*14|0)+-1<<1)>>1]|0)+((e[a+((b*13|0)+-1<<1)>>1]|0)+((e[a+((b*12|0)+-1<<1)>>1]|0)+((e[a+((b*11|0)+-1<<1)>>1]|0)+((e[a+((b*10|0)+-1<<1)>>1]|0)+((e[a+((b*9|0)+-1<<1)>>1]|0)+((e[a+((b<<3)+-1<<1)>>1]|0)+((e[a+((b*7|0)+-1<<1)>>1]|0)+((e[a+((b*6|0)+-1<<1)>>1]|0)+((e[a+((b*5|0)+-1<<1)>>1]|0)+((e[a+((b<<2)+-1<<1)>>1]|0)+((e[a+((b*3|0)+-1<<1)>>1]|0)+((e[a+((b<<1)+-1<<1)>>1]|0)+((e[a+(b+-1<<1)>>1]|0)+(e[a+-2>>1]|0)))))))))))))))))))))))))))))))+16>>5;f=RF(f|0,((f|0)<0)<<31>>31|0,65537,65537)|0;d=C;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;a=a+(b<<1)|0;g=a;c[g>>2]=f;c[g+4>>2]=d;g=a+8|0;c[g>>2]=f;c[g+4>>2]=d;g=a+16|0;c[g>>2]=f;c[g+4>>2]=d;g=a+24|0;c[g>>2]=f;c[g+4>>2]=d;b=a+(b<<1)|0;a=b;c[a>>2]=f;c[a+4>>2]=d;a=b+8|0;c[a>>2]=f;c[a+4>>2]=d;a=b+16|0;c[a>>2]=f;c[a+4>>2]=d;b=b+24|0;c[b>>2]=f;c[b+4>>2]=d;return}function Uk(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;b=b>>1;d=a+(0-b<<1)|0;j=d;k=c[j>>2]|0;j=c[j+4>>2]|0;h=d+8|0;i=c[h>>2]|0;h=c[h+4>>2]|0;f=d+16|0;g=c[f>>2]|0;f=c[f+4>>2]|0;d=d+24|0;e=c[d>>2]|0;d=c[d+4>>2]|0;l=a;c[l>>2]=k;c[l+4>>2]=j;l=a+8|0;c[l>>2]=i;c[l+4>>2]=h;l=a+16|0;c[l>>2]=g;c[l+4>>2]=f;l=a+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<1<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<2<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*5<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*6<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*7<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b<<3<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*9<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*10<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*11<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*12<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*13<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;l=a+(b*14<<1)|0;m=l;c[m>>2]=k;c[m+4>>2]=j;m=l+8|0;c[m>>2]=i;c[m+4>>2]=h;m=l+16|0;c[m>>2]=g;c[m+4>>2]=f;l=l+24|0;c[l>>2]=e;c[l+4>>2]=d;b=a+(b*15<<1)|0;a=b;c[a>>2]=k;c[a+4>>2]=j;a=b+8|0;c[a>>2]=i;c[a+4>>2]=h;a=b+16|0;c[a>>2]=g;c[a+4>>2]=f;b=b+24|0;c[b>>2]=e;c[b+4>>2]=d;return}function Vk(a,b){a=a|0;b=b|0;var d=0,f=0,g=0,h=0,i=0;b=b>>1;d=0;do{f=_(d,b)|0;h=RF(e[a+(f+-1<<1)>>1]|0|0,0,65537,65537)|0;g=C;f=a+(f<<1)|0;i=f;c[i>>2]=h;c[i+4>>2]=g;i=f+8|0;c[i>>2]=h;c[i+4>>2]=g;i=f+16|0;c[i>>2]=h;c[i+4>>2]=g;f=f+24|0;c[f>>2]=h;c[f+4>>2]=g;d=d+1|0}while((d|0)!=16);return}function Wk(a,b){a=a|0;b=b|0;var d=0;b=b>>1;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;a=a+(b<<1)|0;d=a;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+8|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+16|0;c[d>>2]=134154239;c[d+4>>2]=134154239;d=a+24|0;c[d>>2]=134154239;c[d+4>>2]=134154239;b=a+(b<<1)|0;a=b;c[a>>2]=134154239;c[a+4>>2]=134154239;a=b+8|0;c[a>>2]=134154239;c[a+4>>2]=134154239;a=b+16|0;c[a>>2]=134154239;c[a+4>>2]=134154239;b=b+24|0;c[b>>2]=134154239;c[b+4>>2]=134154239;return}
function $c(e,f){e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0,Aa=0,Ba=0,Ca=0,Da=0,Ea=0,Fa=0;Fa=i;i=i+576|0;Ea=Fa+432|0;Aa=Fa+424|0;wa=Fa+416|0;va=Fa+408|0;ta=Fa+400|0;qa=Fa+392|0;na=Fa+384|0;ja=Fa+376|0;ia=Fa+368|0;fa=Fa+360|0;ca=Fa+352|0;aa=Fa+336|0;$=Fa+320|0;Z=Fa+312|0;X=Fa+304|0;W=Fa+296|0;V=Fa+280|0;U=Fa+264|0;T=Fa+256|0;S=Fa+248|0;R=Fa+240|0;N=Fa+232|0;L=Fa+224|0;K=Fa+216|0;J=Fa+208|0;I=Fa+192|0;H=Fa+168|0;G=Fa+160|0;F=Fa+152|0;E=Fa+136|0;D=Fa+120|0;B=Fa+112|0;A=Fa+104|0;z=Fa+96|0;y=Fa+88|0;x=Fa+80|0;w=Fa+72|0;v=Fa+64|0;u=Fa+56|0;t=Fa+48|0;s=Fa+40|0;r=Fa+32|0;p=Fa+24|0;o=Fa+16|0;g=Fa;Da=Fa+512|0;ua=e+25952|0;q=c[ua>>2]|0;if((q|0)==(f|0))if(!(c[e+203248>>2]|0))if(!(c[e+203396>>2]|0))M=1;else{c[g>>2]=66615;c[g+4>>2]=66634;c[g+8>>2]=1168;UC(0,0,104466,g);Ya()}else M=0;else M=0;c[e+203640>>2]=e+184;c[e+203644>>2]=e+440;la=f+4|0;g=c[la>>2]|0;sa=f+12|0;l=c[sa>>2]|0;ka=f+20|0;h=c[ka>>2]|0;k=g+(l>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(l&7);j=l+16|0;j=h>>>0>j>>>0?j:h;Ca=g+(j>>>3)|0;j=(MF(d[Ca>>0]|d[Ca+1>>0]<<8|d[Ca+2>>0]<<16|d[Ca+3>>0]<<24|0)|0)<<(j&7)>>>16|k&-65536;Ca=j>>>0>65535;j=Ca?k>>>16:j;Ca=Ca?16:0;k=(j&65280|0)==0;j=(k?Ca:Ca|8)+(d[97868+(k?j:j>>>8)>>0]|0)|0;k=31-j|0;Ca=0-l|0;m=h-l|0;l=((k|0)<(Ca|0)?Ca:(m|0)<(k|0)?m:k)+l|0;c[sa>>2]=l;m=k+1|0;if(!m)Ca=-1;else{n=g+(l>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(l&7);if((m|0)<26){g=l+m|0;c[sa>>2]=h>>>0>g>>>0?g:h;g=n>>>j}else{Ba=l+16|0;Ba=h>>>0>Ba>>>0?Ba:h;c[sa>>2]=Ba;Ca=k+-15|0;g=g+(Ba>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(Ba&7)>>>(47-k|0);Ba=Ba+Ca|0;c[sa>>2]=h>>>0>Ba>>>0?Ba:h;g=g|n>>>16<<Ca}g=g+-1|0;if(!g){k=e+203248|0;do if(!(c[k>>2]|0))g=e+197616|0;else{if(c[e+203396>>2]|0){UC(c[e+4>>2]|0,16,66658,o);Ea=-1094995529;i=Fa;return Ea|0}h=e+203252|0;if((c[h>>2]|0)>1){g=e+203260|0;if(!(c[g>>2]|0)){UC(c[e+4>>2]|0,24,66675,p);c[g>>2]=1}c[h>>2]=1;Ea=1;i=Fa;return Ea|0}h=e+24620|0;if(!(c[h>>2]|0)){Ea=-1094995529;i=Fa;return Ea|0}j=e+197616|0;g=(c[j>>2]|0)==0;if((c[e+197612>>2]|0)!=3){if(g){Ea=-1094995529;i=Fa;return Ea|0}g=Jc(e,q,1)|0;c[k>>2]=0;if((g|0)>=0){g=j;break}i=Fa;return g|0}if(!g){Ea=-1094995529;i=Fa;return Ea|0}if((c[e+198248>>2]|0)!=5){Ea=-1094995529;i=Fa;return Ea|0}UC(e,24,66729,r);g=Jc(e,c[ua>>2]|0,1)|0;c[k>>2]=0;bA((c[h>>2]|0)+4|0,2147483647,0);bA((c[h>>2]|0)+4|0,2147483647,1);c[h>>2]=0;if((g|0)<0){Ea=g;i=Fa;return Ea|0}else g=j}while(0);if(!(c[g>>2]|0)){g=e+24620|0;h=c[g>>2]|0;if(h|0)if(!(c[e+25992>>2]|0))bA(h+4|0,2147483647,(c[e+197612>>2]|0)==2&1);c[g>>2]=0;Ca=0}else Ca=0}else Ca=g}m=c[sa>>2]|0;n=c[ka>>2]|0;l=c[la>>2]|0;g=l+(m>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(m&7)>>>23;m=(d[86681+g>>0]|0)+m|0;m=n>>>0>m>>>0?m:n;c[sa>>2]=m;g=a[87193+g>>0]|0;h=g&255;if((g&255)>9){Ea=c[e+4>>2]|0;c[s>>2]=h;c[s+4>>2]=Ca;UC(Ea,16,66755,s);Ea=-1094995529;i=Fa;return Ea|0}oa=(g&255)>4;ea=oa?h+-5|0:h;xa=f+820|0;c[xa>>2]=oa&1;oa=d[66786+ea>>0]|0;Ba=f+812|0;c[Ba>>2]=oa;h=oa&3;ga=f+816|0;c[ga>>2]=h;za=e+198248|0;j=c[za>>2]|0;k=(h|0)==1;if(!(k|(j|0)!=5)){UC(c[e+4>>2]|0,16,66791,t);Ea=-1094995529;i=Fa;return Ea|0}pa=e+203248|0;do if(!(c[pa>>2]|0))if(!(c[e+197616>>2]|0)){g=c[(c[e+4>>2]|0)+856>>2]|0;if((g|0)>7){if(!(c[e+198244>>2]|0)){Ea=2;i=Fa;return Ea|0}if((g|0)>15){if((h|0)==3){Ea=2;i=Fa;return Ea|0}if((g|0)>23){if(!k){Ea=2;i=Fa;return Ea|0}if((g|0)>31)if((j|0)==5){if((g|0)>47)g=2;else break;i=Fa;return g|0}else{if((c[e+203348>>2]|0)<0|(g|0)>47)g=2;else break;i=Fa;return g|0}}}}}while(0);da=e+203396|0;j=(c[da>>2]|0)==0;if(j)c[e+203264>>2]=oa;g=l+(m>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(m&7);do if(g>>>0>134217727){ya=g>>>23;ra=(d[86681+ya>>0]|0)+m|0;c[sa>>2]=n>>>0>ra>>>0?ra:n;ya=d[87193+ya>>0]|0}else{ya=g>>>0>65535;ra=ya?g>>>16:g;ya=ya?16:0;h=(ra&65280|0)==0;ya=(d[97868+(h?ra:ra>>>8)>>0]|0)+(h?ya:ya|8)<<1;h=ya+-31|0;ya=63-ya+m|0;c[sa>>2]=n>>>0>ya>>>0?ya:n;if((h|0)<7){UC(0,16,66830,u);g=-1094995529}else{g=(g>>>h)+-1|0;if(g>>>0<=255){ya=g;break}}Ea=c[e+4>>2]|0;c[v>>2]=g;UC(Ea,16,66854,v);Ea=-1094995529;i=Fa;return Ea|0}while(0);h=c[e+198396+(ya<<2)>>2]|0;if(!h){Ea=c[e+4>>2]|0;c[w>>2]=ya;UC(Ea,16,66878,w);Ea=-1094995529;i=Fa;return Ea|0}ra=e+28592|0;g=c[ra>>2]|0;if(!((g|0)<0|(ya|0)==(g|0))){Ea=c[e+4>>2]|0;c[x>>2]=g;c[x+4>>2]=ya;UC(Ea,16,66910,x);Ea=-1094995529;i=Fa;return Ea|0}g=c[h>>2]|0;if(!(c[e+198268+(g<<2)>>2]|0)){Ea=c[e+4>>2]|0;c[y>>2]=c[e+27864>>2];UC(Ea,16,66946,y);Ea=-1094995529;i=Fa;return Ea|0}if(M){KF(e+27864|0,h|0,728)|0;g=c[h>>2]|0}else if(!j)if((c[e+199420>>2]|0)!=(ya|0)){UC(c[e+4>>2]|0,16,66978,z);Ea=-1094995529;i=Fa;return Ea|0}r=e+26660|0;if((g|0)==(c[r>>2]|0))if((g|0)==(c[e+26656>>2]|0))if(!(c[(c[e+198268+(g<<2)>>2]|0)+1200>>2]|0))h=0;else ha=68;else ha=68;else ha=68;if((ha|0)==68){if(!M){UC(c[e+4>>2]|0,16,67006,A);Ea=-1094995529;i=Fa;return Ea|0}KF(r|0,c[e+198268+(c[e+27864>>2]<<2)>>2]|0,1204)|0;if((c[e+198232>>2]|0)==(c[e+26716>>2]|0))if((c[e+198228>>2]|0)==(_(2-(c[e+26724>>2]|0)|0,c[e+26720>>2]|0)|0)){g=c[e+203536>>2]|0;if((g|0)==(c[e+27844>>2]|0))if((c[e+203532>>2]|0)==(c[e+26672>>2]|0))h=0;else ha=74;else ha=74}else ha=74;else ha=74;if((ha|0)==74){g=c[e+27844>>2]|0;h=1}if((c[e+198260>>2]|0)==(g|0)){if((c[e+198264>>2]|0)!=(c[e+26672>>2]|0))ha=77}else ha=77;if((ha|0)==77)h=1;if(!(c[e+26008>>2]&524288)){if(c[e+27320>>2]|0)if(!(c[e+27324>>2]|0))ha=81}else ha=81;a:do if((ha|0)==81){g=c[e+4>>2]|0;do if((c[g+176>>2]|0)<=1){if(c[e+202296>>2]|0)break;c[e+26e3>>2]=1;break a}while(0);UC(g,24,67046,B)}while(0);g=(c[e+4>>2]|0)+176|0;if((c[g>>2]|0)<2)c[g>>2]=(c[e+26e3>>2]|0)==0&1}s=e+26004|0;if(!(c[s>>2]|0)){g=e+4|0;ma=g;g=c[g>>2]|0;j=0}else{g=c[e+26716>>2]|0;j=e+4|0;k=c[j>>2]|0;if((g<<4|0)==(c[k+128>>2]|0)){l=c[e+26720>>2]|0;m=2-(c[e+26724>>2]|0)|0;ma=_(l<<4,m)|0;if((ma|0)==(c[k+132>>2]|0))if((c[e+203536>>2]|0)==(c[e+27844>>2]|0))if((c[e+203532>>2]|0)==(c[e+26672>>2]|0))if((c[e+198232>>2]|0)==(g|0)){ma=j;g=k;j=(c[e+198228>>2]|0)!=(_(m,l)|0)}else{ma=j;g=k;j=1}else{ma=j;g=k;j=1}else{ma=j;g=k;j=1}else{ma=j;g=k;j=1}}else{ma=j;g=k;j=1}}g=c[g+140>>2]|0;if((g|0)==-1)ha=105;else{k=j&1;switch(g|0){case 12:{j=0;break}case 13:{j=4;break}case 14:{j=5;break}default:j=g}g=ad(e,0)|0;switch(g|0){case 12:{g=0;break}case 13:{g=4;break}case 14:{g=5;break}default:{}}if((j|0)!=(g|0))ha=105}if((ha|0)==105)k=1;if(M){m=c[ma>>2]|0;g=c[e+26760>>2]|0;l=c[e+26764>>2]|0;j=c[m+224>>2]|0;m=c[m+228>>2]|0;n=RF(m|0,((m|0)<0)<<31>>31|0,g|0,((g|0)<0)<<31>>31|0)|0;o=C;p=RF(j|0,((j|0)<0)<<31>>31|0,l|0,((l|0)<0)<<31>>31|0)|0;q=C;if((n|0)==(p|0)&(o|0)==(q|0))if((l|0)==0|(m|0)==0)if((g|0)==0|(j|0)==0)g=-2147483648;else g=(g>>31)-(j>>31)|0;else g=0;else{ba=EF(n|0,o|0,p|0,q|0)|0;g=m^l;g=IF(ba^g|0,C^((g|0)<0)<<31>>31|0,63)|0;g=g|1}q=(g|0)==0?k:1}else q=k;do if(!(c[da>>2]|0)){l=gc(r)|0;m=c[ma>>2]|0;c[m+840>>2]=l;c[m+844>>2]=c[e+26668>>2];c[m+364>>2]=c[e+26708>>2];l=c[e+26716>>2]|0;c[e+198232>>2]=l;k=_(2-(c[e+26724>>2]|0)|0,c[e+26720>>2]|0)|0;c[e+198228>>2]=k;c[e+198240>>2]=_(l,k)|0;c[e+198236>>2]=l+1;c[e+26652>>2]=l<<2;c[e+25976>>2]=(c[e+26672>>2]|0)<2&1;l=l<<4;o=e+25964|0;c[o>>2]=l;k=k<<4;p=e+25968|0;c[p>>2]=k;ba=(c[e+26740>>2]|0)+(c[e+26744>>2]|0)|0;g=l-ba|0;n=(c[e+26752>>2]|0)+(c[e+26748>>2]|0)|0;j=k-n|0;if(l>>>0<=ba>>>0){c[D>>2]=67113;c[D+4>>2]=66634;c[D+8>>2]=985;UC(0,0,104466,D);Ya()}if(k>>>0<=n>>>0){c[E>>2]=67171;c[E+4>>2]=66634;c[E+8>>2]=986;UC(0,0,104466,E);Ya()}n=c[m+120>>2]|0;if((n+15^g+15)>>>0<16){Y=c[m+124>>2]|0;ba=(Y|0)>(j|0)|((n|0)>(g|0)|(Y+15^j+15)>>>0>15);j=ba?j:Y;g=ba?g:n}do if((g|0)<1|(j|0)<1){c[F>>2]=g;c[F+4>>2]=j;UC(m,16,67230,F);g=c[ma>>2]|0;if(!(c[g+696>>2]&8)){UC(g,24,67266,G);g=e+26736|0;c[g>>2]=0;c[g+4>>2]=0;c[g+8>>2]=0;c[g+12>>2]=0;c[g+16>>2]=0;g=c[o>>2]|0;j=c[p>>2]|0;m=c[ma>>2]|0;l=g;k=j;break}else{Ea=-1094995529;i=Fa;return Ea|0}}while(0);c[m+128>>2]=l;c[m+132>>2]=k;c[m+120>>2]=g;c[m+124>>2]=j;if(!(c[e+26768>>2]|0))break;c[m+396>>2]=(c[e+26772>>2]|0)>0?2:1;if(!(c[e+26776>>2]|0))break;ba=m+392|0;Y=c[e+26788>>2]|0;h=(c[ba>>2]|0)==(Y|0)?h:1;c[m+384>>2]=c[e+26780>>2];c[m+388>>2]=c[e+26784>>2];c[ba>>2]=Y}while(0);g=c[s>>2]|0;do if(!((g|0)==0|(h|q|0)==0)){c[s>>2]=0;if((c[ua>>2]|0)!=(f|0)){Ea=c[ma>>2]|0;Ba=c[Ea+128>>2]|0;Ca=c[e+25968>>2]|0;Da=c[Ea+132>>2]|0;f=(c[pa>>2]|0)+1|0;c[H>>2]=c[e+25964>>2];c[H+4>>2]=Ba;c[H+8>>2]=Ca;c[H+12>>2]=Da;c[H+16>>2]=f;UC(Ea,16,67298,H);Ea=-1094995529;i=Fa;return Ea|0}Vb(e);g=ad(e,1)|0;if((g|0)<0){Ea=g;i=Fa;return Ea|0}ba=c[ma>>2]|0;c[ba+140>>2]=g;Q=c[e+25964>>2]|0;Y=c[e+25968>>2]|0;g=wD(g)|0;c[I>>2]=Q;c[I+4>>2]=Y;c[I+8>>2]=g;UC(ba,32,67353,I);g=_c(e)|0;if((g|0)>=0){g=c[s>>2]|0;break}UC(c[ma>>2]|0,16,67391,J);Ea=g;i=Fa;return Ea|0}while(0);do if(!g){if((c[ua>>2]|0)!=(f|0)){UC(c[ma>>2]|0,16,67424,K);Ea=-1163346256;i=Fa;return Ea|0}g=ad(e,1)|0;if((g|0)<0){Ea=g;i=Fa;return Ea|0}c[(c[ma>>2]|0)+140>>2]=g;g=_c(e)|0;if((g|0)>=0)break;UC(c[ma>>2]|0,16,67391,L);Ea=g;i=Fa;return Ea|0}while(0);do if(M){g=e+199420|0;if((c[g>>2]|0)==(ya|0))break;c[g>>2]=ya;Zc(e)}while(0);P=e+26680|0;p=c[P>>2]|0;j=c[sa>>2]|0;k=c[ka>>2]|0;l=c[la>>2]|0;h=l+(j>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(j&7)>>>(32-p|0);j=j+p|0;j=k>>>0>j>>>0?j:k;c[sa>>2]=j;do if(!M){g=c[e+199448>>2]|0;if((g|0)==(h|0))break;Ea=c[ma>>2]|0;c[N>>2]=g;c[N+4>>2]=h;UC(Ea,16,67482,N);Ea=-1094995529;i=Fa;return Ea|0}while(0);g=c[da>>2]|0;o=(g|0)==0;if(o)c[e+199448>>2]=h;c[f+21800>>2]=0;Q=e+197608|0;m=c[Q>>2]|0;ba=e+197612|0;O=c[ba>>2]|0;q=e+25992|0;r=c[q>>2]|0;Y=e+198244|0;n=(c[Y>>2]|0)==0&1;do if(!(c[e+26724>>2]|0)){if((ea|0)==1&(c[e+26732>>2]|0)==0){UC(c[ma>>2]|0,16,67514,R);Ea=-1;i=Fa;return Ea|0}R=d[l+(j>>>3)>>0]|0;h=((j|0)<(k|0)&1)+j|0;c[sa>>2]=h;if(!(R&128>>>(j&7))){l=c[e+26728>>2]|0;h=3;break}else{R=d[l+(h>>>3)>>0]<<(h&7)>>>7&1;c[sa>>2]=((h|0)<(k|0)&1)+h;l=0;h=R+1|0;break}}else{l=0;h=3}while(0);j=c[pa>>2]|0;k=(j|0)==0;do if(!k){if(!((r|0)==(n|0)&(O|0)==(h|0)&(m|0)==(l|0))){Ea=c[ma>>2]|0;c[S>>2]=O;c[S+4>>2]=O;UC(Ea,16,67584,S);Ea=-1094995529;i=Fa;return Ea|0}if(c[e+24620>>2]|0)break;Ea=c[ma>>2]|0;c[T>>2]=j+1;UC(Ea,16,67646,T);Ea=-1094995529;i=Fa;return Ea|0}while(0);c[ba>>2]=h;if(o){c[q>>2]=n;c[ba>>2]=h;c[Q>>2]=l}c[f+21796>>2]=(h|0)!=3&1;if(k){L=e+199448|0;k=c[L>>2]|0;M=e+199468|0;l=c[M>>2]|0;do if((k|0)!=(l|0)){g=1<<p;j=c[e+26708>>2]|0;if((k-l+((k|0)<(l|0)?g:0)|0)<=(j|0))break;ha=k-j|0;c[M>>2]=ha+-1+((ha|0)<1?g:0)}while(0);N=e+197616|0;do if(c[N>>2]|0){j=e+24620|0;g=c[j>>2]|0;if(!g){c[U>>2]=67677;c[U+4>>2]=66634;c[U+8>>2]=1516;UC(0,0,104466,U);Ya()}if(!(c[(c[g>>2]|0)+360>>2]|0)){c[V>>2]=67692;c[V+4>>2]=66634;c[V+8>>2]=1517;UC(0,0,104466,V);Ya()}if((c[g+8>>2]|0)==(c[ma>>2]|0)){bA(g+4|0,2147483647,(O|0)==2&1);h=c[ba>>2]|0}if((h|0)==3|(h|0)==(O|0)){if((O|0)==3)break;bA((c[j>>2]|0)+4|0,2147483647,(O|0)==1&1);break}g=c[j>>2]|0;if((c[g+84>>2]|0)!=(c[L>>2]|0)){if((O|0)==3)break;bA(g+4|0,2147483647,(O|0)==1&1);break}do if(!((O|0)==1&(h|0)==2)){if((O|0)==2&(h|0)==1)break;Ea=c[ma>>2]|0;c[W>>2]=O;c[W+4>>2]=h;UC(Ea,16,67718,W);c[ba>>2]=O;c[q>>2]=r;Ea=-1094995529;i=Fa;return Ea|0}while(0);if((r|0)==(c[q>>2]|0))break;VC(c[ma>>2]|0,67756,X);c[ba>>2]=O;c[q>>2]=r;Ea=-1163346256;i=Fa;return Ea|0}while(0);g=c[L>>2]|0;h=c[M>>2]|0;b:do if((g|0)==(h|0))ha=205;else{p=e+203244|0;q=e+26712|0;l=e+24620|0;r=e+202440|0;s=e+203232|0;t=e+202040|0;u=e+202368|0;v=e+202372|0;w=e+202376|0;x=e+202380|0;y=e+202384|0;z=e+202388|0;A=e+202392|0;B=e+202396|0;D=e+202400|0;E=e+202404|0;F=e+202408|0;G=e+202412|0;H=e+202416|0;I=e+202420|0;J=e+202424|0;K=e+202428|0;while(1){if(c[N>>2]|0){k=g;ha=207;break b}if((g|0)==((h+1|0)%(1<<c[P>>2]|0)|0|0)){ha=205;break b}if(!(c[p>>2]|0))o=0;else o=c[t>>2]|0;ha=c[ma>>2]|0;c[Z>>2]=g;c[Z+4>>2]=h;UC(ha,48,67822,Z);if(!(c[q>>2]|0)){c[u>>2]=-2147483648;c[v>>2]=-2147483648;c[w>>2]=-2147483648;c[x>>2]=-2147483648;c[y>>2]=-2147483648;c[z>>2]=-2147483648;c[A>>2]=-2147483648;c[B>>2]=-2147483648;c[D>>2]=-2147483648;c[E>>2]=-2147483648;c[F>>2]=-2147483648;c[G>>2]=-2147483648;c[H>>2]=-2147483648;c[I>>2]=-2147483648;c[J>>2]=-2147483648;c[K>>2]=-2147483648}g=bd(e)|0;if((g|0)<0){ha=191;break}ha=((c[M>>2]|0)+1|0)%(1<<c[P>>2]|0)|0;c[M>>2]=ha;g=c[l>>2]|0;c[g+84>>2]=ha;c[g+644>>2]=(c[q>>2]|0)==0&1;bA(g+4|0,2147483647,0);bA((c[l>>2]|0)+4|0,2147483647,1);g=Tc(e,1)|0;if((g|0)<0)if(c[(c[ma>>2]|0)+696>>2]&8|0){ha=387;break}g=Uc(e,r,c[s>>2]|0)|0;if((g|0)<0)if(c[(c[ma>>2]|0)+696>>2]&8|0){ha=387;break}if(!(c[p>>2]|0))h=c[M>>2]|0;else{g=c[t>>2]|0;do if(o){h=c[g>>2]|0;j=c[h+68>>2]|0;k=c[o>>2]|0;if((j|0)!=(c[k+68>>2]|0))break;m=c[h+72>>2]|0;if((m|0)!=(c[k+72>>2]|0))break;n=c[h+80>>2]|0;if((n|0)!=(c[k+80>>2]|0))break;PC(h,h+32|0,k,k+32|0,n,j,m);g=c[t>>2]|0;c[g+80>>2]=(c[o+80>>2]|0)+2}while(0);h=c[M>>2]|0;c[g+84>>2]=h}g=c[L>>2]|0;if((g|0)==(h|0)){g=h;ha=205;break b}}if((ha|0)==191){c[N>>2]=0;Ea=g;i=Fa;return Ea|0}else if((ha|0)==387){i=Fa;return g|0}}while(0);do if((ha|0)==205)if(!(c[N>>2]|0)){g=c[ba>>2]|0;h=(g|0)!=3&1;c[N>>2]=h;break}else{l=e+24620|0;k=g;ha=207;break}while(0);do if((ha|0)==207){j=c[l>>2]|0;if(!j){c[$>>2]=67677;c[$+4>>2]=66634;c[$+8>>2]=1622;UC(0,0,104466,$);Ya()}if(!(c[(c[j>>2]|0)+360>>2]|0)){c[aa>>2]=67692;c[aa+4>>2]=66634;c[aa+8>>2]=1623;UC(0,0,104466,aa);Ya()}g=c[ba>>2]|0;h=e+203392|0;if((g|0)==3|(g|0)==(O|0)){c[h>>2]=(c[h>>2]|0)+1;c[l>>2]=0;h=(g|0)!=3&1;c[N>>2]=h;break}c[h>>2]=0;if((c[j+84>>2]|0)==(k|0)){c[N>>2]=0;h=0;break}else{bA(j+4|0,2147483647,(g|0)==2&1);c[N>>2]=1;c[l>>2]=0;g=c[ba>>2]|0;h=1;break}}while(0);do if((g|0)!=3&(h|0)==0){g=e+24620|0;j=0;do{h=e+716+(j*664|0)|0;do if(c[(c[h>>2]|0)+360>>2]|0){if(c[e+716+(j*664|0)+636>>2]|0)break;if((h|0)==(c[g>>2]|0))break;Gc(e,h)}while(0);j=j+1|0}while((j|0)!=36)}else{if((bd(e)|0)>=0)break;c[N>>2]=0;Ea=-1094995529;i=Fa;return Ea|0}while(0);g=c[ba>>2]|0;do if((g|0)==3)FF(c[e+197604>>2]|0,-1,(_(c[e+198228>>2]<<1,c[e+198236>>2]|0)|0)+-2|0)|0;else{h=(g|0)==2&1;j=e+198228|0;g=c[j>>2]|0;if(h>>>0>=g>>>0)break;k=e+197604|0;l=e+198236|0;do{aa=c[l>>2]|0;$=h;h=h+1|0;FF((c[k>>2]|0)+((_(aa,$)|0)<<1)|0,-1,aa-((h|0)==(g|0)&1)<<1|0)|0;g=c[j>>2]|0}while(h>>>0<g>>>0)}while(0);c[e+203268>>2]=-1;g=c[da>>2]|0}if(!g)c[(c[e+24620>>2]|0)+84>>2]=c[e+199448>>2];h=(c[Q>>2]|0)==0;if(h)g=(c[ba>>2]|0)!=3;else g=1;aa=c[e+198240>>2]|0;if(!(Ca>>>0<aa>>>0&Ca<<(g&1)>>>0<aa>>>0)){UC(c[ma>>2]|0,16,67963,ca);Ea=-1094995529;i=Fa;return Ea|0}g=c[e+198232>>2]|0;j=(Ca>>>0)%(g>>>0)|0;c[f+21764>>2]=j;c[f+21776>>2]=j;j=c[ba>>2]|0;g=((Ca>>>0)/(g>>>0)|0)<<(((j|0)!=3|h^1)&1);h=f+21768|0;c[h>>2]=g;q=f+21780|0;c[q>>2]=g;do if((j|0)==2){g=g+1|0;c[h>>2]=g;c[q>>2]=g;g=e+199448|0;h=g;g=c[g>>2]|0;ha=242}else{h=e+199448|0;g=c[h>>2]|0;if((j|0)!=3){ha=242;break}j=c[P>>2]|0;F=h}while(0);if((ha|0)==242){j=(c[P>>2]|0)+1|0;g=g<<1|1;F=h}c[e+199472>>2]=g;c[e+199476>>2]=1<<j;do if((c[za>>2]|0)==5){g=c[sa>>2]|0;h=c[ka>>2]|0;j=(c[la>>2]|0)+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);if(j>>>0>134217727){ha=(d[86681+(j>>>23)>>0]|0)+g|0;c[sa>>2]=h>>>0>ha>>>0?ha:h;break}ha=j>>>0>65535;aa=ha?j>>>16:j;ha=ha?16:0;ca=(aa&65280|0)==0;ha=(d[97868+(ca?aa:aa>>>8)>>0]|0)+(ca?ha:ha|8)<<1;ca=63-ha+g|0;c[sa>>2]=h>>>0>ca>>>0?ca:h;if((ha+-31|0)>=7)break;UC(0,16,66830,fa)}while(0);n=c[e+26684>>2]|0;switch(n|0){case 0:{g=c[e+26688>>2]|0;h=c[sa>>2]|0;l=c[ka>>2]|0;m=c[la>>2]|0;j=m+(h>>>3)|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;k=h+g|0;k=l>>>0>k>>>0?k:l;c[sa>>2]=k;o=(c[da>>2]|0)==0;if(o)c[e+199428>>2]=(MF(j|0)|0)<<(h&7)>>>(32-g|0);do if((c[e+27872>>2]|0)==1){if((c[ba>>2]|0)!=3)break;g=m+(k>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(k&7);if(g>>>0>134217727){g=g>>>23;ha=(d[86681+g>>0]|0)+k|0;c[sa>>2]=l>>>0>ha>>>0?ha:l;g=a[87705+g>>0]|0}else{fa=g>>>0>65535;ha=fa?g>>>16:g;g=fa?16:0;fa=(ha&65280|0)==0;g=(d[97868+(fa?ha:ha>>>8)>>0]|0)+(fa?g:g|8)|0;fa=31-g+k|0;fa=l>>>0>fa>>>0?fa:l;ha=m+(fa>>>3)|0;ha=(MF(d[ha>>0]|d[ha+1>>0]<<8|d[ha+2>>0]<<16|d[ha+3>>0]<<24|0)|0)<<(fa&7)>>>g;g=fa+(32-g)|0;c[sa>>2]=l>>>0>g>>>0?g:l;g=ha&1;g=(ha>>>1^0-g)+g|0}if(!o)break;c[e+199436>>2]=g}while(0);if((n|0)==1)ha=259;else ha=272;break}case 1:{ha=259;break}default:ha=272}do if((ha|0)==259){if(c[e+26692>>2]|0){ha=272;break}g=c[sa>>2]|0;h=c[ka>>2]|0;l=c[la>>2]|0;j=l+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);if(j>>>0>134217727){fa=j>>>23;g=(d[86681+fa>>0]|0)+g|0;c[sa>>2]=h>>>0>g>>>0?g:h;g=a[87705+fa>>0]|0}else{ca=j>>>0>65535;aa=ca?j>>>16:j;ca=ca?16:0;fa=(aa&65280|0)==0;ca=(d[97868+(fa?aa:aa>>>8)>>0]|0)+(fa?ca:ca|8)|0;g=31-ca+g|0;g=h>>>0>g>>>0?g:h;fa=l+(g>>>3)|0;fa=(MF(d[fa>>0]|d[fa+1>>0]<<8|d[fa+2>>0]<<16|d[fa+3>>0]<<24|0)|0)<<(g&7)>>>ca;g=g+(32-ca)|0;c[sa>>2]=h>>>0>g>>>0?g:h;g=fa&1;g=(fa>>>1^0-g)+g|0}k=(c[da>>2]|0)==0;if(k)c[e+199440>>2]=g;if((c[e+27872>>2]|0)!=1){ha=272;break}if((c[ba>>2]|0)!=3){ha=272;break}g=c[sa>>2]|0;h=c[ka>>2]|0;j=l+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);if(j>>>0>134217727){fa=j>>>23;g=(d[86681+fa>>0]|0)+g|0;c[sa>>2]=h>>>0>g>>>0?g:h;g=a[87705+fa>>0]|0}else{ca=j>>>0>65535;aa=ca?j>>>16:j;ca=ca?16:0;fa=(aa&65280|0)==0;ca=(d[97868+(fa?aa:aa>>>8)>>0]|0)+(fa?ca:ca|8)|0;g=31-ca+g|0;g=h>>>0>g>>>0?g:h;fa=l+(g>>>3)|0;fa=(MF(d[fa>>0]|d[fa+1>>0]<<8|d[fa+2>>0]<<16|d[fa+3>>0]<<24|0)|0)<<(g&7)>>>ca;g=g+(32-ca)|0;c[sa>>2]=h>>>0>g>>>0?g:h;g=fa&1;g=(fa>>>1^0-g)+g|0}if(!k)break;c[e+199444>>2]=g;ha=272}while(0);do if((ha|0)==272){if(c[da>>2]|0)break;ha=c[e+24620>>2]|0;fc(e,ha+72|0,ha+80|0)|0}while(0);if(c[e+27924>>2]|0){g=c[sa>>2]|0;h=c[ka>>2]|0;j=(c[la>>2]|0)+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);do if(j>>>0>134217727){ia=j>>>23;g=(d[86681+ia>>0]|0)+g|0;c[sa>>2]=h>>>0>g>>>0?g:h;g=d[87193+ia>>0]|0}else{ha=j>>>0>65535;fa=ha?j>>>16:j;ha=ha?16:0;k=(fa&65280|0)==0;ha=(d[97868+(k?fa:fa>>>8)>>0]|0)+(k?ha:ha|8)<<1;k=ha+-31|0;ha=63-ha+g|0;c[sa>>2]=h>>>0>ha>>>0?ha:h;if((k|0)<7){UC(0,16,66830,ia);g=-1094995529;break}else{g=(j>>>k)+-1|0;break}}while(0);c[f+21804>>2]=g}g=hc(e,f)|0;if((g|0)<0){Ea=g;i=Fa;return Ea|0}c:do if((ea|0)!=2){do if(c[pa>>2]|0){if((oa|0)!=(c[e+203268>>2]|0))break;if(!(bE(e+203272|0,f+23372|0,8)|0))break c}while(0);Oc(e,f)|0}while(0);do if((c[ga>>2]|0)!=1){g=Qc(e,f)|0;if((g|0)>=0)break;c[f+23372>>2]=0;c[f+23376>>2]=0;Ea=g;i=Fa;return Ea|0}while(0);if(!(c[e+27892>>2]|0))ha=292;else if((c[ga>>2]|0)==2)ha=294;else ha=292;d:do if((ha|0)==292){e:do switch(c[e+27896>>2]|0){case 1:{if((c[ga>>2]|0)==3){ha=294;break d}break}case 2:{if((c[ga>>2]|0)!=3)break e;cd(e,f,-1);break d}default:{}}while(0);c[f+856>>2]=0;ia=f+872|0;c[ia>>2]=0;c[ia+4>>2]=0;c[ia+8>>2]=0;c[ia+12>>2]=0}while(0);if((ha|0)==294)ec(e,f)|0;do if(c[Y>>2]|0){if(!(c[(c[ma>>2]|0)+816>>2]&1))g=1;else g=(c[pa>>2]|0)==0;if((Vc(e,la,g&1)|0)>=0)break;if(!(c[(c[ma>>2]|0)+696>>2]&8))break;else g=-1094995529;i=Fa;return g|0}while(0);do if(!(c[Q>>2]|0))ha=307;else{Rc(e,f);if((c[e+27896>>2]|0)!=2){ha=307;break}if((c[ga>>2]|0)!=3)break;cd(e,f,0);cd(e,f,1);ha=307}while(0);do if((ha|0)==307){if((c[ga>>2]|0)!=3)break;if(c[f+21812>>2]|0)break;oc(e,f)}while(0);pc(e,f);o=c[ga>>2]|0;p=(o|0)==1;do if(!p){if(!(c[e+27868>>2]|0))break;ga=c[sa>>2]|0;g=c[ka>>2]|0;ia=(c[la>>2]|0)+(ga>>>3)|0;ia=(MF(d[ia>>0]|d[ia+1>>0]<<8|d[ia+2>>0]<<16|d[ia+3>>0]<<24|0)|0)<<(ga&7)>>>23;ga=(d[86681+ia>>0]|0)+ga|0;c[sa>>2]=g>>>0>ga>>>0?ga:g;ia=a[87193+ia>>0]|0;g=ia&255;if((ia&255)<=2){c[f+49752>>2]=g;break}Ea=c[ma>>2]|0;c[ja>>2]=g;UC(Ea,16,67991,ja);Ea=-1094995529;i=Fa;return Ea|0}while(0);c[f+840>>2]=0;j=c[e+27900>>2]|0;h=c[sa>>2]|0;m=c[ka>>2]|0;l=c[la>>2]|0;g=l+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){g=g>>>23;h=(d[86681+g>>0]|0)+h|0;h=m>>>0>h>>>0?h:m;c[sa>>2]=h;g=a[87705+g>>0]|0}else{la=g>>>0>65535;ka=la?g>>>16:g;g=la?16:0;la=(ka&65280|0)==0;g=(d[97868+(la?ka:ka>>>8)>>0]|0)+(la?g:g|8)|0;h=31-g+h|0;h=m>>>0>h>>>0?h:m;la=l+(h>>>3)|0;la=(MF(d[la>>0]|d[la+1>>0]<<8|d[la+2>>0]<<16|d[la+3>>0]<<24|0)|0)<<(h&7)>>>g;h=h+(32-g)|0;h=m>>>0>h>>>0?h:m;c[sa>>2]=h;g=la&1;g=(la>>>1^0-g)+g|0}g=g+j|0;n=e+27844|0;if(g>>>0>(((c[n>>2]|0)*6|0)+3|0)>>>0){Ea=c[ma>>2]|0;c[na>>2]=g;UC(Ea,16,68019,na);Ea=-1094995529;i=Fa;return Ea|0}E=f+824|0;c[E>>2]=g;c[f+828>>2]=d[e+28412+g>>0];c[f+832>>2]=d[e+28500+g>>0];g=c[Ba>>2]|0;if((g|0)==6){g=((h|0)<(m|0)&1)+h|0;c[sa>>2]=g;ha=323}else if((g+-5|0)>>>0<2){g=h;ha=323}else g=h;do if((ha|0)==323){h=l+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);if(h>>>0>134217727){g=(d[86681+(h>>>23)>>0]|0)+g|0;g=m>>>0>g>>>0?g:m;c[sa>>2]=g;break}else{na=h>>>0>65535;ka=na?h>>>16:h;na=na?16:0;la=(ka&65280|0)==0;na=(d[97868+(la?ka:ka>>>8)>>0]|0)+(la?na:na|8)|0;g=31-na+g|0;g=(m>>>0>g>>>0?g:m)+(32-na)|0;g=m>>>0>g>>>0?g:m;c[sa>>2]=g;break}}while(0);A=f+844|0;c[A>>2]=1;B=f+848|0;c[B>>2]=0;D=f+852|0;c[D>>2]=0;do if(!(c[e+27916>>2]|0)){k=1;g=0;h=0}else{h=l+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7)>>>23;j=(d[86681+h>>0]|0)+g|0;j=m>>>0>j>>>0?j:m;c[sa>>2]=j;g=a[87193+h>>0]|0;h=g&255;if((g&255)>2){Ea=c[ma>>2]|0;c[qa>>2]=h;UC(Ea,16,68039,qa);Ea=-1094995529;i=Fa;return Ea|0}qa=(g&255)<2&1;k=qa^h;c[A>>2]=k;if((h|0)==(qa|0)){g=0;h=0;break}g=l+(j>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(j&7);if(g>>>0>134217727){g=g>>>23;j=(d[86681+g>>0]|0)+j|0;j=m>>>0>j>>>0?j:m;c[sa>>2]=j;g=a[87705+g>>0]|0}else{qa=g>>>0>65535;na=qa?g>>>16:g;g=qa?16:0;qa=(na&65280|0)==0;g=(d[97868+(qa?na:na>>>8)>>0]|0)+(qa?g:g|8)|0;j=31-g+j|0;j=m>>>0>j>>>0?j:m;qa=l+(j>>>3)|0;qa=(MF(d[qa>>0]|d[qa+1>>0]<<8|d[qa+2>>0]<<16|d[qa+3>>0]<<24|0)|0)<<(j&7)>>>g;j=j+(32-g)|0;j=m>>>0>j>>>0?j:m;c[sa>>2]=j;g=qa&1;g=(qa>>>1^0-g)+g|0}h=g<<1;c[B>>2]=h;g=l+(j>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(j&7);if(g>>>0>134217727){g=g>>>23;qa=(d[86681+g>>0]|0)+j|0;c[sa>>2]=m>>>0>qa>>>0?qa:m;g=a[87705+g>>0]|0}else{na=g>>>0>65535;qa=na?g>>>16:g;g=na?16:0;na=(qa&65280|0)==0;g=(d[97868+(na?qa:qa>>>8)>>0]|0)+(na?g:g|8)|0;na=31-g+j|0;na=m>>>0>na>>>0?na:m;qa=l+(na>>>3)|0;qa=(MF(d[qa>>0]|d[qa+1>>0]<<8|d[qa+2>>0]<<16|d[qa+3>>0]<<24|0)|0)<<(na&7)>>>g;g=na+(32-g)|0;c[sa>>2]=m>>>0>g>>>0?g:m;g=qa&1;g=(qa>>>1^0-g)+g|0}g=g<<1;c[D>>2]=g;if(!((g+12|0)>>>0>24|(h+12|0)>>>0>24))break;Ea=c[ma>>2]|0;c[ta>>2]=h;c[ta+4>>2]=g;UC(Ea,16,68078,ta);Ea=-1094995529;i=Fa;return Ea|0}while(0);l=c[ma>>2]|0;j=c[l+848>>2]|0;f:do if((j|0)>47)ha=347;else{do if((j|0)>31)if((c[za>>2]|0)!=5|p^1){ha=347;break f}else ha=346;else{if((j|0)>23)if(p){ha=346;break}else{ha=347;break f}if((j|0)>15)if((o|0)==3){ha=347;break f}else{ha=346;break}else if((j|0)>7){ha=346;break}else break}while(0);if((ha|0)==346)if(!(c[Y>>2]|0)){ha=347;break}if((k|0)!=1)break;j=e+203252|0;if((c[j>>2]|0)<=1)break;if(c[l+92>>2]&1|0){c[A>>2]=2;break}c[j>>2]=1;g=e+203260|0;if(!(c[g>>2]|0)){UC(l,32,68127,va);c[g>>2]=1}if((c[ua>>2]|0)==(f|0)){h=c[B>>2]|0;g=c[D>>2]|0;break}UC(c[ma>>2]|0,16,68562,wa);Ea=1;i=Fa;return Ea|0}while(0);if((ha|0)==347)c[A>>2]=0;v=c[e+27908>>2]|0;v=(v|0)<0?0:v;z=c[e+27912>>2]|0;c[f+836>>2]=-33-((h|0)>(g|0)?g:h)+((c[n>>2]|0)*6|0)-((v|0)>(z|0)?v:z);c[e+203268>>2]=oa;z=f+23372|0;v=z;j=c[v+4>>2]|0;h=e+203272|0;c[h>>2]=c[v>>2];c[h+4>>2]=j;h=c[pa>>2]|0;j=h+1|0;c[pa>>2]=j;v=f+808|0;c[v>>2]=j;g=c[q>>2]|0;if(j|0)b[e+203540+((h&31)<<1)>>1]=g;wa=b[e+203540+((j&31)<<1)>>1]|0;if((h|0)>30&((wa|0)<=(g|0)&(wa+3|0)>=(g|0))){wa=c[ma>>2]|0;c[Aa>>2]=j;c[Aa+4>>2]=32;UC(wa,24,68597,Aa)}q=f+23380|0;r=e+203244|0;s=e+203240|0;u=0;do{t=c[v>>2]&31;m=f+23372+(u<<2)|0;n=u>>>0<(c[q>>2]|0)>>>0;p=0;do{o=Da+(p<<2)|0;c[o>>2]=60;g:do if(n){if(p>>>0>=(c[m>>2]|0)>>>0)break;g=c[(c[c[f+23384+(u*1920|0)+(p*40|0)+36>>2]>>2]|0)+360>>2]|0;if(!g)break;k=c[g>>2]|0;l=c[r>>2]|0;h:do if((l|0)>0){g=0;while(1){if((c[c[(c[c[e+202040+(g<<2)>>2]>>2]|0)+360>>2]>>2]|0)==(k|0))break;g=g+1|0;if((g|0)>=(l|0))break h}c[o>>2]=g}while(0);h=c[s>>2]|0;if((h|0)>0)g=0;else break;while(1){j=c[e+202168+(g<<2)>>2]|0;if(j|0)if((c[c[(c[j>>2]|0)+360>>2]>>2]|0)==(k|0))break;g=g+1|0;if((g|0)>=(h|0))break g}c[o>>2]=l+g}while(0);p=p+1|0}while((p|0)!=16);c[f+27224+(t<<9)+(u<<8)+4>>2]=-1;c[f+27224+(t<<9)+(u<<8)>>2]=-1;g=0;do{c[f+27224+(t<<9)+(u<<8)+(g+2<<2)>>2]=c[f+23384+(u*1920|0)+(g*40|0)+24>>2]&3|c[Da+(g<<2)>>2]<<2;g=g+1|0}while((g|0)!=16);c[f+27224+(t<<9)+(u<<8)+76>>2]=-1;c[f+27224+(t<<9)+(u<<8)+72>>2]=-1;g=16;do{c[f+27224+(t<<9)+(u<<8)+(g+4<<2)>>2]=c[f+23384+(u*1920|0)+(g*40|0)+24>>2]&3|c[Da+((g+-16|0)>>>1<<2)>>2]<<2;g=g+1|0}while((g|0)!=48);u=u+1|0}while((u|0)!=2);c[ra>>2]=ya;y=c[e+27864>>2]|0;c[(c[e+198268+(y<<2)>>2]|0)+1200>>2]=0;c[e+27860>>2]=0;c[e+26656>>2]=y;y=c[ma>>2]|0;if(!(c[y+688>>2]&1)){Ea=0;i=Fa;return Ea|0}x=c[v>>2]|0;v=c[ba>>2]|0;w=(SD(c[Ba>>2]|0)|0)<<24>>24;u=c[xa>>2]|0?68698:190399;t=(c[za>>2]|0)==5?68703:190399;s=c[F>>2]|0;r=c[e+24620>>2]|0;q=c[r+72>>2]|0;r=c[r+76>>2]|0;o=c[z>>2]|0;p=c[f+23376>>2]|0;n=c[E>>2]|0;m=c[A>>2]|0;l=c[B>>2]|0;h=c[D>>2]|0;j=c[f+856>>2]|0;if((j|0)==1)k=c[f+860>>2]|0?68708:190399;else k=190399;if((c[Ba>>2]|0)==3)g=c[f+21812>>2]|0?68710:68715;else g=190399;c[Ea>>2]=x;c[Ea+4>>2]=(v|0)==3?68696:(v|0)==1?68692:68694;c[Ea+8>>2]=Ca;c[Ea+12>>2]=w;c[Ea+16>>2]=u;c[Ea+20>>2]=t;c[Ea+24>>2]=ya;c[Ea+28>>2]=s;c[Ea+32>>2]=q;c[Ea+36>>2]=r;c[Ea+40>>2]=o;c[Ea+44>>2]=p;c[Ea+48>>2]=n;c[Ea+52>>2]=m;c[Ea+56>>2]=l;c[Ea+60>>2]=h;c[Ea+64>>2]=j;c[Ea+68>>2]=k;c[Ea+72>>2]=g;UC(y,48,68720,Ea);Ea=0;i=Fa;return Ea|0}function ad(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;h=i;i=i+16|0;e=h;f=h+8|0;d=c[a+27844>>2]|0;a:do switch(d|0){case 9:{e=c[a+26672>>2]|0;if((e|0)==3){d=f+4|0;if(!(c[(c[a+4>>2]|0)+392>>2]|0)){c[f>>2]=84;g=f;break a}else{c[f>>2]=76;g=f;break a}}else{d=f+4|0;if((e|0)==2){c[f>>2]=80;g=f;break a}else{c[f>>2]=70;g=f;break a}}}case 10:{e=c[a+26672>>2]|0;if((e|0)==3){d=f+4|0;if(!(c[(c[a+4>>2]|0)+392>>2]|0)){c[f>>2]=86;g=f;break a}else{c[f>>2]=78;g=f;break a}}else{d=f+4|0;if((e|0)==2){c[f>>2]=74;g=f;break a}else{c[f>>2]=72;g=f;break a}}}case 12:{e=c[a+26672>>2]|0;if((e|0)==3){d=f+4|0;if(!(c[(c[a+4>>2]|0)+392>>2]|0)){c[f>>2]=314;g=f;break a}else{c[f>>2]=310;g=f;break a}}else{d=f+4|0;if((e|0)==2){c[f>>2]=306;g=f;break a}else{c[f>>2]=302;g=f;break a}}}case 14:{e=c[a+26672>>2]|0;if((e|0)==3){d=f+4|0;if(!(c[(c[a+4>>2]|0)+392>>2]|0)){c[f>>2]=316;g=f;break a}else{c[f>>2]=312;g=f;break a}}else{d=f+4|0;if((e|0)==2){c[f>>2]=308;g=f;break a}else{c[f>>2]=304;g=f;break a}}}case 8:{d=c[a+26672>>2]|0;if((d|0)==3){e=c[a+4>>2]|0;if(!(c[e+392>>2]|0)){c[f>>2]=82;g=f;d=f+4|0;break a}d=f+4|0;if((c[e+396>>2]|0)==2){c[f>>2]=14;g=f;break a}else{c[f>>2]=5;g=f;break a}}e=c[a+4>>2]|0;if((d|0)==2){d=f+4|0;if((c[e+396>>2]|0)==2){c[f>>2]=13;g=f;break a}else{c[f>>2]=4;g=f;break a}}d=c[(c[e+12>>2]|0)+24>>2]|0;if(!d){d=f+4|0;if((c[e+396>>2]|0)==2){c[f>>2]=12;g=f;break a}else{c[f>>2]=0;g=f;break a}}else{g=d;d=f}break}default:{b=c[a+4>>2]|0;c[e>>2]=d;UC(b,16,66521,e);b=-1094995529;i=h;return b|0}}while(0);c[d>>2]=-1;e=c[g>>2]|0;a=c[a+4>>2]|0;b:do if((e|0)!=-1){if(b|0){d=0;while(1){d=d+1|0;if((c[g+(d<<2)>>2]|0)==-1)break b}}d=c[a+140>>2]|0;f=0;while(1){f=f+1|0;if((e|0)==(d|0))break;e=c[g+(f<<2)>>2]|0;if((e|0)==-1)break b}i=h;return d|0}while(0);b=_z(a,g)|0;i=h;return b|0}function bd(a){a=a|0;var b=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;q=i;i=i+64|0;h=q+32|0;g=q+16|0;f=q+8|0;m=q+48|0;p=c[a+25960>>2]|0;c[m>>2]=1<<(c[a+27844>>2]|0)+-1;l=1<<(c[a+27848>>2]|0)+-1;c[m+4>>2]=l;c[m+8>>2]=l;c[m+12>>2]=-1;l=a+4|0;if(!(dA(c[l>>2]|0)|0)){UC(c[l>>2]|0,16,67843,q);a=-1;i=q;return a|0}else e=0;do{b=a+716+(e*664|0)|0;if(c[(c[b>>2]|0)+360>>2]|0)if(!(c[a+716+(e*664|0)+636>>2]|0))Gc(a,b);e=e+1|0}while((e|0)!=36);o=a+24620|0;c[o>>2]=0;b=0;do{if(!(c[(c[a+716+(b*664|0)>>2]|0)+360>>2]|0))break;b=b+1|0}while((b|0)<36);k=(b|0)==36?-1094995529:b;if((k|0)<0){UC(c[l>>2]|0,16,67889,f);a=k;i=q;return a|0}n=a+716+(k*664|0)|0;b=a+197612|0;if(!(c[a+25992>>2]|0))e=c[b>>2]|0;else e=0;c[a+716+(k*664|0)+636>>2]=e;f=a+25996|0;j=c[f>>2]|0;c[f>>2]=j+1;f=c[n>>2]|0;c[f+160>>2]=j;c[a+716+(k*664|0)+632>>2]=(c[b>>2]|0)!=3&1;c[f+84>>2]=0;c[a+716+(k*664|0)+88>>2]=0;c[a+716+(k*664|0)+640>>2]=0;c[a+716+(k*664|0)+644>>2]=0;c[a+716+(k*664|0)+648>>2]=c[a+203348>>2];if(c[f>>2]|0){c[g>>2]=67916;c[g+4>>2]=66634;c[g+8>>2]=227;UC(0,0,104466,g);Ya()}b=a+716+(k*664|0)+4|0;c[b>>2]=f;b=$z(c[l>>2]|0,b,(e|0)!=0&1)|0;a:do if((b|0)>=0){c[a+716+(k*664|0)+652>>2]=c[a+26736>>2];c[a+716+(k*664|0)+660>>2]=c[a+26748>>2];c[a+716+(k*664|0)+656>>2]=c[a+26740>>2];b=c[(c[l>>2]|0)+712>>2]|0;do if(b|0){e=a+716+(k*664|0)+52|0;if(c[e>>2]|0){c[h>>2]=67933;c[h+4>>2]=66634;c[h+8>>2]=241;UC(0,0,104466,h);Ya()}b=c[b+40>>2]|0;if(b|0){b=LB(b)|0;c[a+716+(k*664|0)+48>>2]=b;if(!b){a=-12;i=q;return a|0}else{c[e>>2]=c[b+4>>2];break}}}while(0);j=a+203624|0;b=c[j>>2]|0;do if(!b){r=a+198236|0;h=c[r>>2]|0;g=c[a+198228>>2]|0;f=(_(g+1|0,h)|0)+1|0;b=_(g<<4,c[a+198232>>2]<<2|1)|0;c[j>>2]=PB(f+h|0,8)|0;e=a+203628|0;c[e>>2]=PB((c[r>>2]|0)+f<<2,8)|0;f=a+203632|0;c[f>>2]=PB(b+16|0,8)|0;g=PB(_(h<<2,g)|0,8)|0;h=a+203636|0;c[h>>2]=g;b=c[j>>2]|0;if(b|0)if(c[e>>2]|0)if(!((g|0)==0|(c[f>>2]|0)==0))break;QB(j);QB(e);QB(f);QB(h);b=-12;break a}else e=a+203628|0;while(0);r=a+716+(k*664|0)+16|0;c[r>>2]=RB(b)|0;b=RB(c[e>>2]|0)|0;c[a+716+(k*664|0)+40>>2]=b;e=c[r>>2]|0;if((b|0)==0|(e|0)==0){Gc(a,n);r=-12;i=q;return r|0}g=c[a+198236>>2]<<1;c[a+716+(k*664|0)+44>>2]=(c[b+4>>2]|0)+(g<<2)+4;c[a+716+(k*664|0)+20>>2]=(c[e+4>>2]|0)+g+1;b=a+203632|0;e=a+203636|0;g=a+716+(k*664|0)+24|0;c[g>>2]=RB(c[b>>2]|0)|0;f=RB(c[e>>2]|0)|0;c[a+716+(k*664|0)+56>>2]=f;g=c[g>>2]|0;if(!((f|0)==0|(g|0)==0)){c[a+716+(k*664|0)+32>>2]=(c[g+4>>2]|0)+16;c[a+716+(k*664|0)+64>>2]=c[f+4>>2];r=a+716+(k*664|0)+28|0;c[r>>2]=RB(c[b>>2]|0)|0;b=RB(c[e>>2]|0)|0;c[a+716+(k*664|0)+60>>2]=b;e=c[r>>2]|0;if(!((b|0)==0|(e|0)==0)){c[a+716+(k*664|0)+36>>2]=(c[e+4>>2]|0)+16;c[a+716+(k*664|0)+68>>2]=c[b+4>>2];if(!(c[a+203384>>2]|0)){b=c[l>>2]|0;if(!(c[b+712>>2]|0))if(!(c[(c[b+12>>2]|0)+16>>2]&128))Gz(c[n>>2]|0,m)}c[o>>2]=n;b=a+24624|0;Gc(a,b);j=a+25952|0;Ic((c[j>>2]|0)+672|0,0);b=Hc(a,b,c[o>>2]|0)|0;if((b|0)<0){r=b;i=q;return r|0}b=c[a+25956>>2]|0;if((b|0)>0){g=c[c[o>>2]>>2]|0;e=g+32|0;f=c[j>>2]|0;g=g+36|0;h=0;do{c[f+(h*49776|0)+21748>>2]=c[e>>2];c[f+(h*49776|0)+21752>>2]=c[g>>2];h=h+1|0}while((h|0)<(b|0))}if(!(c[a+203620>>2]|0))b=0;else{oA((c[j>>2]|0)+24|0);Ic((c[j>>2]|0)+704|0,0);Ic((c[j>>2]|0)+736|0,0);b=0}do{r=(d[96537+b>>0]|0)+-12|0;m=(r<<2&28)<<p;r=r>>3;c[a+26260+(b<<2)>>2]=m+(_(c[(c[n>>2]|0)+32>>2]<<2,r)|0);c[a+26260+(b+48<<2)>>2]=m+(_(c[(c[n>>2]|0)+32>>2]<<3,r)|0);b=b+1|0}while((b|0)!=16);b=0;do{r=(d[96537+b>>0]|0)+-12|0;m=(r<<2&28)<<p;r=r>>3;l=m+(_(c[(c[n>>2]|0)+36>>2]<<2,r)|0)|0;c[a+26260+(b+32<<2)>>2]=l;c[a+26260+(b+16<<2)>>2]=l;r=m+(_(c[(c[n>>2]|0)+36>>2]<<3,r)|0)|0;c[a+26260+(b+80<<2)>>2]=r;c[a+26260+(b+64<<2)>>2]=r;b=b+1|0}while((b|0)!=16);r=c[o>>2]|0;c[r+636>>2]=0;c[r+76>>2]=2147483647;c[r+72>>2]=2147483647;c[a+202432>>2]=0;r=0;i=q;return r|0}}Gc(a,n);r=-12;i=q;return r|0}while(0);Gc(a,n);r=b;i=q;return r|0}function cd(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;o=b+872|0;p=(d|0)<0;c[o>>2]=0;c[o+4>>2]=0;c[o+8>>2]=0;c[o+12>>2]=0;if(p){e=c[a+197612>>2]|0;f=c[a+24620>>2]|0;e=c[((e|0)==3?f+80|0:f+72+(e+-1<<2)|0)>>2]|0;f=c[b+23372>>2]|0;g=c[b+23376>>2]|0;if((f|0)==1)if((g|0)==1)if(!(c[a+197608>>2]|0))if(((c[b+25332>>2]|0)+(c[b+23412>>2]|0)|0)==(e<<1|0)){c[b+856>>2]=0;c[b+860>>2]=0;return}else{f=1;g=1;o=0}else{f=1;g=1;o=0}else{f=1;o=0}else o=0}else{e=c[(c[a+24620>>2]|0)+72+(d<<2)>>2]|0;f=(c[b+23372>>2]<<1)+16|0;g=(c[b+23376>>2]<<1)+16|0;o=16}c[b+856>>2]=2;c[b+860>>2]=2;c[b+864>>2]=5;c[b+868>>2]=5;if((o|0)>=(f|0))return;j=(o|0)<(g|0);n=o;do{k=c[b+23384+(n*40|0)+28>>2]|0;if(j){l=b+23384+(n*40|0)+36|0;m=e-k|0;m=((m+128|0)>>>0>255?m>>31^127:m)<<24>>24;if(p){i=o;do{if(!(c[(c[l>>2]|0)+96>>2]|0))if(!(c[(c[b+25304+(i*40|0)+36>>2]|0)+96>>2]|0)){h=(c[b+25304+(i*40|0)+28>>2]|0)-k|0;h=(h+128|0)>>>0>255?h>>31^127:h;a=h&255;h=h<<24>>24;if(!(a<<24>>24))a=32;else{a=(_((((a<<24>>24>-1?h:0-h|0)>>1)+16384|0)/(h|0)|0,m)|0)+32>>8;a=(a+64|0)>>>0<193?64-a|0:32}}else a=32;else a=32;c[b+3192+(n*384|0)+(i<<3)+4>>2]=a;c[b+3192+(n*384|0)+(i<<3)>>2]=a;i=i+1|0}while((i|0)!=(g|0))}else{i=o;do{if(!(c[(c[l>>2]|0)+96>>2]|0))if(!(c[(c[b+25304+(i*40|0)+36>>2]|0)+96>>2]|0)){h=(c[b+25304+(i*40|0)+28>>2]|0)-k|0;h=(h+128|0)>>>0>255?h>>31^127:h;a=h&255;h=h<<24>>24;if(!(a<<24>>24))a=32;else{a=(_((((a<<24>>24>-1?h:0-h|0)>>1)+16384|0)/(h|0)|0,m)|0)+32>>8;a=(a+64|0)>>>0<193?64-a|0:32}}else a=32;else a=32;c[b+3192+(n*384|0)+(i<<3)+(d<<2)>>2]=a;i=i+1|0}while((i|0)!=(g|0))}}n=n+1|0}while((n|0)!=(f|0));return}function dd(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=i;i=i+16|0;e=r;o=c[a+4>>2]|0;if(!b){c[e>>2]=68817;q=e+4|0;c[q>>2]=66634;q=e+8|0;c[q>>2]=2552;UC(0,0,104466,e);Ya()}q=b+-1|0;p=a+25952|0;d=c[p>>2]|0;f=a+198228|0;if((c[d+(q*49776|0)+21768>>2]|0)>=(c[f>>2]|0)){c[e>>2]=68817;n=e+4|0;c[n>>2]=66634;n=e+8|0;c[n>>2]=2552;UC(0,0,104466,e);Ya()}c[d+21784>>2]=2147483647;if(c[o+712>>2]|0){b=0;i=r;return b|0}if(c[(c[o+12>>2]|0)+16>>2]&128|0){b=0;i=r;return b|0}m=a+198232|0;n=c[m>>2]|0;j=_(c[f>>2]|0,n)|0;k=c[p>>2]|0;if((b|0)==1){c[k+21784>>2]=j;b=ed(o,k)|0;c[a+198224>>2]=c[(c[p>>2]|0)+21768>>2];i=r;return b|0}else l=0;do{c[k+(l*49776|0)+572>>2]=0;f=_(n,c[k+(l*49776|0)+21768>>2]|0)|0;f=f+(c[k+(l*49776|0)+21764>>2]|0)|0;d=c[p>>2]|0;g=c[m>>2]|0;h=0;e=j;do{s=_(g,c[d+(h*49776|0)+21768>>2]|0)|0;s=s+(c[d+(h*49776|0)+21764>>2]|0)|0;e=(l|0)==(h|0)|(s|0)<(f|0)?e:(e|0)>(s|0)?s:e;h=h+1|0}while((h|0)!=(b|0));c[k+(l*49776|0)+21784>>2]=e;l=l+1|0}while((l|0)!=(b|0));kb[c[o+824>>2]&1](o,2,d,0,b,49776)|0;f=c[p>>2]|0;c[a+198224>>2]=c[f+(q*49776|0)+21768>>2];if(b>>>0<=1){s=0;i=r;return s|0}d=f+572|0;e=1;do{c[d>>2]=(c[d>>2]|0)+(c[f+(e*49776|0)+572>>2]|0);e=e+1|0}while((e|0)!=(b|0));d=0;i=r;return d|0}function ed(b,f){b=b|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=i;i=i+64|0;C=L+56|0;B=L+48|0;F=L+32|0;D=L+24|0;E=L+16|0;o=L;J=c[f>>2]|0;K=f+21764|0;n=c[K>>2]|0;g=c[c[J+24620>>2]>>2]|0;k=c[g+32>>2]|0;m=f+21748|0;c[m>>2]=k;c[f+21752>>2]=c[g+36>>2];k=((k|0)>-1?k:0-k|0)+63&-32;g=f+43616|0;h=f+43632|0;eD(g,h,k*96|0);j=f+43620|0;eD(j,f+43636|0,k*42|0);k=f+43624|0;H=J+198232|0;eD(k,f+43640|0,(c[H>>2]|0)*96|0);l=f+43628|0;eD(l,f+43644|0,(c[H>>2]|0)*96|0);if(c[g>>2]|0)if(c[j>>2]|0)if(c[k>>2]|0)if(c[l>>2]|0){z=f+21788|0;c[z>>2]=-1;if((c[J+26320>>2]|0)!=(((c[m>>2]|0)*12|0)+(12<<c[J+25960>>2])|0)){c[o>>2]=68886;c[o+4>>2]=66634;c[o+8>>2]=2354;UC(0,0,104466,o);Ya()}G=J+197608|0;if(!(c[G>>2]|0))if((c[J+197612>>2]|0)==3)g=(c[b+48>>2]|0)!=28;else g=1;else g=1;c[f+21792>>2]=g&1;I=J+4|0;g=c[I>>2]|0;if(!(c[g+816>>2]&2))if((c[J+197612>>2]|0)==3){h=c[J+25952>>2]|0;j=c[h+580>>2]|0;if(j|0){A=(_(c[H>>2]|0,c[f+21780>>2]|0)|0)+(c[f+21776>>2]|0)|0;k=(c[J+198240>>2]|0)+-1|0;k=(A|0)>(k|0)?k:A;if(!((A|0)<0|(k|0)==0))if((a[j+(c[(c[h+548>>2]|0)+(k+-1<<2)>>2]|0)>>0]&-2)<<24>>24!=112)c[h+576>>2]=1}}if(!(c[J+27868>>2]|0)){A=f+21768|0;F=(_(c[H>>2]|0,c[A>>2]|0)|0)+(c[K>>2]|0)|0;x=f+21784|0;h=c[x>>2]|0;do if((F|0)<(h|0)){w=f+12|0;y=f+16|0;b=J+197612|0;p=J+198228|0;q=J+198236|0;r=J+197604|0;s=f+808|0;t=J+24668|0;u=f+21796|0;v=f+21800|0;while(1){g=jc(J,f)|0;if((g|0)<=-1){k=74;break}sc(J,f);if(c[G>>2]|0){c[A>>2]=(c[A>>2]|0)+1;g=jc(J,f)|0;if((g|0)<=-1){k=72;break}sc(J,f);c[A>>2]=(c[A>>2]|0)+-1}g=(c[K>>2]|0)+1|0;c[K>>2]=g;if((g|0)>=(c[H>>2]|0)){fd(J,f,n,g);c[K>>2]=0;gd(J,f);g=c[A>>2]|0;j=g+1|0;c[A>>2]=j;if(!(c[G>>2]|0)){if((c[b>>2]|0)!=3){j=g+2|0;c[A>>2]=j}}else{j=g+2|0;c[A>>2]=j;if((j|0)<(c[p>>2]|0)){g=c[q>>2]|0;h=(_(g,j)|0)+(c[K>>2]|0)|0;k=h+-1|0;l=c[r>>2]|0;m=c[s>>2]|0;do if((e[l+(k<<1)>>1]|0|0)==(m|0))g=c[(c[t>>2]|0)+(k<<2)>>2]|0;else{g=h-g|0;if((e[l+(g<<1)>>1]|0|0)!=(m|0)){g=0;break}g=c[(c[t>>2]|0)+(g<<2)>>2]|0}while(0);F=g>>>7&1;c[u>>2]=F;c[v>>2]=F}}if((j|0)<(c[p>>2]|0))n=0;else{k=88;break}}o=c[w>>2]|0;m=c[y>>2]|0;if((m|0)<=(o|0))if((c[z>>2]|0)<1){k=98;break}F=(_(c[H>>2]|0,c[A>>2]|0)|0)+(c[K>>2]|0)|0;g=c[x>>2]|0;if((F|0)>=(g|0)){k=66;break}}if((k|0)==66){h=g;g=c[I>>2]|0;break}else if((k|0)==72){j=(c[A>>2]|0)+-1|0;c[A>>2]=j;h=g}else if((k|0)==74){j=c[A>>2]|0;h=g}else if((k|0)==88){h=c[w>>2]|0;g=c[y>>2]|0;do if((g|0)!=(h|0)){if((g|0)>(h|0))if(!(c[(c[I>>2]|0)+696>>2]&262144))break;g=c[f>>2]|0;if(!(c[g+203620>>2]|0)){f=-1094995529;i=L;return f|0}pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,c[K>>2]|0,j,112);f=-1094995529;i=L;return f|0}while(0);g=c[f>>2]|0;if(!(c[g+203620>>2]|0)){f=0;i=L;return f|0}pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,(c[K>>2]|0)+-1|0,j,112);f=0;i=L;return f|0}else if((k|0)==98){j=c[f+21776>>2]|0;k=c[f+21780>>2]|0;g=c[K>>2]|0;h=c[f>>2]|0;l=(c[h+203620>>2]|0)==0;if((m|0)!=(o|0)){if(l){f=-1094995529;i=L;return f|0}pA((c[h+25952>>2]|0)+24|0,j,k,g,c[A>>2]|0,14);f=-1094995529;i=L;return f|0}if(!l){pA((c[h+25952>>2]|0)+24|0,j,k,g+-1|0,c[A>>2]|0,112);g=c[K>>2]|0}if((g|0)<=(n|0)){f=0;i=L;return f|0}fd(J,f,n,g);f=0;i=L;return f|0}g=c[I>>2]|0;c[C>>2]=c[K>>2];c[C+4>>2]=j;UC(g,16,69117,C);g=c[f>>2]|0;if(!(c[g+203620>>2]|0)){f=h;i=L;return f|0}pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,c[K>>2]|0,c[A>>2]|0,14);f=h;i=L;return f|0}while(0);c[B>>2]=h;UC(g,16,69013,B);g=c[f>>2]|0;if(!(c[g+203620>>2]|0)){f=-1094995529;i=L;return f|0}pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,c[K>>2]|0,c[A>>2]|0,14);f=-1094995529;i=L;return f|0}j=f+12|0;g=c[j>>2]|0;h=0-g&7;if(h){C=c[f+20>>2]|0;g=h+g|0;g=C>>>0>g>>>0?g:C;c[j>>2]=g}A=f+48680|0;g=mA(A,(c[f+4>>2]|0)+((g|0)/8|0)|0,(7-g+(c[f+16>>2]|0)|0)/8|0)|0;if((g|0)<0){f=g;i=L;return f|0}uA(J,f);C=f+21768|0;o=f+21784|0;b=f+48684|0;z=f+48696|0;B=f+48700|0;p=J+26012|0;q=J+198228|0;r=J+197612|0;s=J+198236|0;t=J+197604|0;u=f+808|0;v=J+24668|0;w=f+21796|0;x=f+21800|0;y=f+48692|0;g=c[C>>2]|0;h=n;while(1){n=(_(c[H>>2]|0,g)|0)+(c[K>>2]|0)|0;g=c[o>>2]|0;if((n|0)>=(g|0)){k=25;break}g=vA(J,f)|0;if((g|0)>-1){sc(J,f);if(c[G>>2]|0){c[C>>2]=(c[C>>2]|0)+1;g=vA(J,f)|0;if((g|0)>-1)sc(J,f);c[C>>2]=(c[C>>2]|0)+-1}}j=c[b>>2]|0;k=j+-2|0;c[b>>2]=k;l=c[A>>2]|0;if((l|0)<(k<<17|0)){j=(j+-258|0)>>>31;c[b>>2]=k<<j;j=l<<j;c[A>>2]=j;if(!(j&65535)){k=c[z>>2]|0;c[A>>2]=j+-65535+((d[k+1>>0]|0)<<1|(d[k>>0]|0)<<9);if(k>>>0<(c[B>>2]|0)>>>0){c[z>>2]=k+2;m=0}else m=0}else m=0}else m=(c[z>>2]|0)-(c[y>>2]|0)|0;j=c[z>>2]|0;k=c[B>>2]|0;if((c[p>>2]&16384|0)!=0&j>>>0>(k+2|0)>>>0){k=38;break}if(j>>>0>(k+2|0)>>>0){n=c[I>>2]|0;c[D>>2]=k-j;UC(n,48,69045,D)}if((g|0)<0){k=45;break}j=c[z>>2]|0;g=c[B>>2]|0;if(j>>>0>(g+4|0)>>>0){h=j;k=47;break}g=(c[K>>2]|0)+1|0;c[K>>2]=g;do if((g|0)>=(c[H>>2]|0)){fd(J,f,h,g);c[K>>2]=0;gd(J,f);g=c[C>>2]|0;c[C>>2]=g+1;if(!(c[G>>2]|0)){if((c[r>>2]|0)==3){h=0;break}c[C>>2]=g+2;h=0;break}g=g+2|0;c[C>>2]=g;if((g|0)>=(c[q>>2]|0)){h=0;break}j=c[s>>2]|0;g=(_(j,g)|0)+(c[K>>2]|0)|0;h=g+-1|0;k=c[t>>2]|0;l=c[u>>2]|0;do if((e[k+(h<<1)>>1]|0|0)==(l|0))g=c[(c[v>>2]|0)+(h<<2)>>2]|0;else{g=g-j|0;if((e[k+(g<<1)>>1]|0|0)!=(l|0)){g=0;break}g=c[(c[v>>2]|0)+(g<<2)>>2]|0}while(0);h=g>>>7&1;c[w>>2]=h;c[x>>2]=h;h=0}while(0);if(m|0){k=62;break}g=c[C>>2]|0;if((g|0)>=(c[q>>2]|0)){k=62;break}}if((k|0)==25){J=c[I>>2]|0;c[E>>2]=g;UC(J,16,69013,E);g=c[f>>2]|0;if(!(c[g+203620>>2]|0)){f=-1094995529;i=L;return f|0}pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,c[K>>2]|0,c[C>>2]|0,14);f=-1094995529;i=L;return f|0}else if((k|0)==38){g=c[f>>2]|0;if(c[g+203620>>2]|0)pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,(c[K>>2]|0)+-1|0,c[C>>2]|0,112);g=c[K>>2]|0;if((g|0)<(h|0)){f=0;i=L;return f|0}fd(J,f,h,g+1|0);f=0;i=L;return f|0}else if((k|0)==45){h=c[z>>2]|0;g=c[B>>2]|0}else if((k|0)!=47)if((k|0)==62){g=c[f>>2]|0;if(c[g+203620>>2]|0)pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,(c[K>>2]|0)+-1|0,c[C>>2]|0,112);g=c[K>>2]|0;if((g|0)<=(h|0)){f=0;i=L;return f|0}fd(J,f,h,g);f=0;i=L;return f|0}J=c[I>>2]|0;I=c[C>>2]|0;c[F>>2]=c[K>>2];c[F+4>>2]=I;c[F+8>>2]=g-h;UC(J,16,69070,F);g=c[f>>2]|0;if(!(c[g+203620>>2]|0)){f=-1094995529;i=L;return f|0}pA((c[g+25952>>2]|0)+24|0,c[f+21776>>2]|0,c[f+21780>>2]|0,c[K>>2]|0,c[C>>2]|0,14);f=-1094995529;i=L;return f|0}bD(g);bD(j);bD(k);bD(l);c[h>>2]=0;c[h+4>>2]=0;c[h+8>>2]=0;c[h+12>>2]=0;f=-12;i=L;return f|0}function fd(f,g,h,i){f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0,Aa=0,Ba=0,Ca=0,Da=0,Ea=0,Fa=0,Ga=0,Ha=0,Ia=0,Ja=0,Ka=0,La=0,Ma=0,Na=0,Oa=0,Pa=0,Qa=0,Ra=0,Sa=0,Ta=0,Ua=0,Va=0,Wa=0,Xa=0,Ya=0,Za=0,_a=0,$a=0,ab=0,bb=0,cb=0,db=0,eb=0,fb=0,gb=0,hb=0,ib=0,jb=0,kb=0,lb=0,mb=0,nb=0,ob=0,pb=0,qb=0,rb=0,sb=0,tb=0,ub=0,vb=0,wb=0,xb=0,yb=0,zb=0,Ab=0,Bb=0,Cb=0,Db=0,Eb=0,Fb=0,Gb=0,Hb=0,Ib=0,Jb=0,Kb=0,Lb=0,Mb=0,Nb=0,Ob=0,Pb=0,Qb=0,Rb=0,Sb=0,Tb=0,Ub=0,Vb=0,Wb=0,Xb=0,Yb=0;Ob=g+21768|0;Rb=f+197608|0;j=c[Rb>>2]|0;Sb=j+(c[Ob>>2]|0)|0;Tb=g+812|0;Ub=c[Tb>>2]|0;Vb=f+25960|0;Wb=c[Vb>>2]|0;Pb=16>>>(c[f+25976>>2]|0);Qb=g+844|0;if(!((c[Qb>>2]|0)!=0&(h|0)<(i|0))){Vb=g+828|0;Wb=g+832|0;Qb=g+21764|0;Rb=j;c[Tb>>2]=Ub;c[Qb>>2]=i;i=Sb-Rb|0;c[Ob>>2]=i;g=g+824|0;g=c[g>>2]|0;i=f+28412+g|0;i=a[i>>0]|0;i=i&255;c[Vb>>2]=i;g=f+28500+g|0;g=a[g>>0]|0;g=g&255;c[Wb>>2]=g;return}ka=f+198236|0;la=g+21772|0;ma=f+197604|0;na=g+808|0;oa=f+24668|0;pa=f+197620|0;qa=g+23380|0;ra=g+21764|0;sa=f+24624|0;ta=g+21748|0;ua=f+26672|0;va=g+21752|0;wa=g+21796|0;xa=g+21756|0;ya=g+21760|0;za=g+21688|0;Aa=g+21696|0;Ba=g+21700|0;Ca=g+836|0;Da=f+24644|0;Ea=g+21708|0;Fa=g+21716|0;Ga=g+21720|0;Ha=f+26652|0;Ia=g+43824|0;Ja=g+44108|0;Ka=g+43856|0;La=g+43888|0;Ma=g+43920|0;Na=g+44116|0;Oa=g+44124|0;Pa=g+44132|0;Qa=g+43984|0;Ra=g+44148|0;Sa=g+44016|0;Ta=g+44048|0;Ua=g+44080|0;Va=g+44156|0;Wa=g+44164|0;Xa=g+44172|0;Ya=f+24692|0;Za=g+21800|0;_a=f+24660|0;$a=g+43992|0;ab=g+44024|0;bb=g+44056|0;cb=g+44088|0;db=g+43952|0;eb=g+44140|0;fb=g+43980|0;gb=g+44012|0;hb=g+44044|0;ib=g+44076|0;jb=g+44171|0;kb=g+44163|0;lb=g+44155|0;mb=g+44147|0;nb=f+26644|0;ob=g+43960|0;pb=g+44141|0;qb=g+44143|0;rb=g+44142|0;sb=f+26256|0;tb=g+43660|0;ub=g+43668|0;vb=g+43676|0;wb=g+43684|0;xb=f+197624|0;yb=g+21824|0;zb=f+27868|0;Ab=f+27928|0;Bb=g+43669|0;Cb=g+43661|0;Db=g+43671|0;Eb=g+43670|0;Fb=g+43663|0;Gb=g+43662|0;Hb=g+43685|0;Ib=g+43677|0;Jb=g+43687|0;Kb=g+43686|0;Lb=g+43679|0;Mb=g+43678|0;Nb=g+43683|0;G=g+43675|0;H=g+43667|0;I=g+43659|0;J=g+43653|0;K=g+43652|0;L=g+43655|0;M=g+43654|0;N=f+24688|0;O=f+24656|0;P=g+43832|0;Q=g+43864|0;R=g+43896|0;S=g+43928|0;T=g+43792|0;U=g+44100|0;V=g+43820|0;W=g+43852|0;X=g+43884|0;Y=g+43916|0;Z=g+44131|0;$=g+44123|0;aa=g+44115|0;ba=g+44107|0;ca=g+43800|0;da=g+44101|0;ea=g+44103|0;fa=g+44102|0;ga=g+828|0;ha=g+832|0;ia=g+43624|0;ja=1-Pb|0;do{j=c[Rb>>2]|0;a:do if((j|0)>=0){E=h<<Wb;F=Sb-j|0;while(1){C=(_(c[ka>>2]|0,F)|0)+h|0;c[la>>2]=C;c[na>>2]=e[(c[ma>>2]|0)+(C<<1)>>1];D=c[(c[oa>>2]|0)+(C<<2)>>2]|0;c[qa>>2]=d[(c[pa>>2]|0)+C>>0];r=(j|0)==0;if(r)j=c[wa>>2]|0;else{j=D>>>7&1;c[wa>>2]=j;c[Za>>2]=j}c[ra>>2]=h;c[Ob>>2]=F;A=c[sa>>2]|0;n=c[ta>>2]|0;m=(c[A>>2]|0)+((_(n,F)|0)+E<<4)|0;q=c[ua>>2]|0;x=(q|0)==3;B=_(8<<(x&1),E)|0;p=c[va>>2]|0;l=_(_(F,Pb)|0,p)|0;k=(c[A+4>>2]|0)+B+l|0;l=(c[A+8>>2]|0)+B+l|0;if(!j){c[xa>>2]=n;c[ya>>2]=p;B=n;A=p}else{j=n<<1;c[xa>>2]=j;o=p<<1;c[ya>>2]=o;if(!(F&1)){B=j;A=o}else{B=_(ja,p)|0;k=k+B|0;l=l+B|0;m=m+(_(n,-15)|0)|0;B=j;A=o}}v=c[Vb>>2]|0;u=(q|0)==2;t=m+(0-B)|0;y=0-A|0;w=k+y|0;y=l+y|0;do if(r){j=1;z=30}else{j=(c[Za>>2]|0)!=0;if(!(F&1))if(j){j=0;z=30;break}else break;if(j){j=1;z=30}else{q=c[ia>>2]|0;j=t+(B*15|0)|0;r=j;z=c[r+4>>2]|0;s=q+(h*96|0)|0;c[s>>2]=c[r>>2];c[s+4>>2]=z;s=j+8|0;z=c[s+4>>2]|0;r=q+(h*96|0)+8|0;c[r>>2]=c[s>>2];c[r+4>>2]=z;r=(v|0)!=0;if(r){p=j+16|0;o=p;z=c[o+4>>2]|0;s=q+(h*96|0)+16|0;c[s>>2]=c[o>>2];c[s+4>>2]=z;p=p+8|0;s=c[p+4>>2]|0;z=q+(h*96|0)+24|0;c[z>>2]=c[p>>2];c[z+4>>2]=s}if(x){j=A*15|0;n=w+j|0;p=n;o=c[p>>2]|0;p=c[p+4>>2]|0;if(r){s=q+(h*96|0)+32|0;c[s>>2]=o;c[s+4>>2]=p;s=n+8|0;p=c[s+4>>2]|0;z=q+(h*96|0)+40|0;c[z>>2]=c[s>>2];c[z+4>>2]=p;z=n+16|0;p=z;s=c[p+4>>2]|0;r=q+(h*96|0)+48|0;c[r>>2]=c[p>>2];c[r+4>>2]=s;z=z+8|0;r=c[z+4>>2]|0;s=q+(h*96|0)+56|0;c[s>>2]=c[z>>2];c[s+4>>2]=r;s=y+j|0;r=s;j=c[r+4>>2]|0;z=q+(h*96|0)+64|0;c[z>>2]=c[r>>2];c[z+4>>2]=j;z=s+8|0;j=c[z+4>>2]|0;r=q+(h*96|0)+72|0;c[r>>2]=c[z>>2];c[r+4>>2]=j;s=s+16|0;r=s;j=c[r+4>>2]|0;z=q+(h*96|0)+80|0;c[z>>2]=c[r>>2];c[z+4>>2]=j;s=s+8|0;z=c[s+4>>2]|0;j=q+(h*96|0)+88|0;c[j>>2]=c[s>>2];c[j+4>>2]=z;j=1;z=30;break}else{z=q+(h*96|0)+16|0;c[z>>2]=o;c[z+4>>2]=p;z=n+8|0;r=c[z+4>>2]|0;s=q+(h*96|0)+24|0;c[s>>2]=c[z>>2];c[s+4>>2]=r;s=y+j|0;r=s;j=c[r+4>>2]|0;z=q+(h*96|0)+32|0;c[z>>2]=c[r>>2];c[z+4>>2]=j;s=s+8|0;z=c[s+4>>2]|0;j=q+(h*96|0)+40|0;c[j>>2]=c[s>>2];c[j+4>>2]=z;j=1;z=30;break}}if(u){j=A*15|0;n=w+j|0;p=n;o=c[p>>2]|0;p=c[p+4>>2]|0;if(r){z=q+(h*96|0)+32|0;c[z>>2]=o;c[z+4>>2]=p;z=n+8|0;r=c[z+4>>2]|0;s=q+(h*96|0)+40|0;c[s>>2]=c[z>>2];c[s+4>>2]=r;s=y+j|0;r=s;j=c[r+4>>2]|0;z=q+(h*96|0)+48|0;c[z>>2]=c[r>>2];c[z+4>>2]=j;s=s+8|0;z=c[s+4>>2]|0;j=q+(h*96|0)+56|0;c[j>>2]=c[s>>2];c[j+4>>2]=z;j=1;z=30;break}else{s=q+(h*96|0)+16|0;c[s>>2]=o;c[s+4>>2]=p;s=y+j|0;z=c[s+4>>2]|0;j=q+(h*96|0)+24|0;c[j>>2]=c[s>>2];c[j+4>>2]=z;j=1;z=30;break}}else{j=A*7|0;n=w+j|0;p=n;o=c[p>>2]|0;p=c[p+4>>2]|0;if(r){z=q+(h*96|0)+32|0;c[z>>2]=o;c[z+4>>2]=p;z=n+8|0;r=c[z+4>>2]|0;s=q+(h*96|0)+40|0;c[s>>2]=c[z>>2];c[s+4>>2]=r;s=y+j|0;r=s;j=c[r+4>>2]|0;z=q+(h*96|0)+48|0;c[z>>2]=c[r>>2];c[z+4>>2]=j;s=s+8|0;z=c[s+4>>2]|0;j=q+(h*96|0)+56|0;c[j>>2]=c[s>>2];c[j+4>>2]=z;j=1;z=30;break}else{s=q+(h*96|0)+16|0;c[s>>2]=o;c[s+4>>2]=p;s=y+j|0;z=c[s+4>>2]|0;j=q+(h*96|0)+24|0;c[j>>2]=c[s>>2];c[j+4>>2]=z;j=1;z=30;break}}}}while(0);do if((z|0)==30){z=0;s=c[ra>>2]|0;r=c[g+43624+(j<<2)>>2]|0;p=B<<4;j=t+p|0;q=j;t=c[q+4>>2]|0;o=r+(s*96|0)|0;c[o>>2]=c[q>>2];c[o+4>>2]=t;o=j+8|0;t=c[o+4>>2]|0;q=r+(s*96|0)+8|0;c[q>>2]=c[o>>2];c[q+4>>2]=t;q=(v|0)!=0;if(q){o=j+16|0;n=o;v=c[n+4>>2]|0;t=r+(s*96|0)+16|0;c[t>>2]=c[n>>2];c[t+4>>2]=v;o=o+8|0;t=c[o+4>>2]|0;v=r+(s*96|0)+24|0;c[v>>2]=c[o>>2];c[v+4>>2]=t}if(x){j=w+p|0;o=j;n=c[o>>2]|0;o=c[o+4>>2]|0;if(q){w=r+(s*96|0)+32|0;c[w>>2]=n;c[w+4>>2]=o;w=j+8|0;u=c[w+4>>2]|0;x=r+(s*96|0)+40|0;c[x>>2]=c[w>>2];c[x+4>>2]=u;x=j+16|0;u=x;w=c[u+4>>2]|0;v=r+(s*96|0)+48|0;c[v>>2]=c[u>>2];c[v+4>>2]=w;x=x+8|0;v=c[x+4>>2]|0;w=r+(s*96|0)+56|0;c[w>>2]=c[x>>2];c[w+4>>2]=v;w=y+p|0;v=w;y=c[v+4>>2]|0;x=r+(s*96|0)+64|0;c[x>>2]=c[v>>2];c[x+4>>2]=y;x=w+8|0;y=c[x+4>>2]|0;v=r+(s*96|0)+72|0;c[v>>2]=c[x>>2];c[v+4>>2]=y;w=w+16|0;v=w;y=c[v+4>>2]|0;x=r+(s*96|0)+80|0;c[x>>2]=c[v>>2];c[x+4>>2]=y;w=w+8|0;x=c[w+4>>2]|0;y=r+(s*96|0)+88|0;c[y>>2]=c[w>>2];c[y+4>>2]=x;break}else{x=r+(s*96|0)+16|0;c[x>>2]=n;c[x+4>>2]=o;x=j+8|0;v=c[x+4>>2]|0;w=r+(s*96|0)+24|0;c[w>>2]=c[x>>2];c[w+4>>2]=v;w=y+p|0;v=w;y=c[v+4>>2]|0;x=r+(s*96|0)+32|0;c[x>>2]=c[v>>2];c[x+4>>2]=y;w=w+8|0;x=c[w+4>>2]|0;y=r+(s*96|0)+40|0;c[y>>2]=c[w>>2];c[y+4>>2]=x;break}}if(u){p=A<<4;j=w+p|0;o=j;n=c[o>>2]|0;o=c[o+4>>2]|0;if(q){x=r+(s*96|0)+32|0;c[x>>2]=n;c[x+4>>2]=o;x=j+8|0;v=c[x+4>>2]|0;w=r+(s*96|0)+40|0;c[w>>2]=c[x>>2];c[w+4>>2]=v;w=y+p|0;v=w;y=c[v+4>>2]|0;x=r+(s*96|0)+48|0;c[x>>2]=c[v>>2];c[x+4>>2]=y;w=w+8|0;x=c[w+4>>2]|0;y=r+(s*96|0)+56|0;c[y>>2]=c[w>>2];c[y+4>>2]=x;break}else{w=r+(s*96|0)+16|0;c[w>>2]=n;c[w+4>>2]=o;w=y+p|0;x=c[w+4>>2]|0;y=r+(s*96|0)+24|0;c[y>>2]=c[w>>2];c[y+4>>2]=x;break}}else{p=A<<3;j=w+p|0;o=j;n=c[o>>2]|0;o=c[o+4>>2]|0;if(q){x=r+(s*96|0)+32|0;c[x>>2]=n;c[x+4>>2]=o;x=j+8|0;v=c[x+4>>2]|0;w=r+(s*96|0)+40|0;c[w>>2]=c[x>>2];c[w+4>>2]=v;w=y+p|0;v=w;y=c[v+4>>2]|0;x=r+(s*96|0)+48|0;c[x>>2]=c[v>>2];c[x+4>>2]=y;w=w+8|0;x=c[w+4>>2]|0;y=r+(s*96|0)+56|0;c[y>>2]=c[w>>2];c[y+4>>2]=x;break}else{w=r+(s*96|0)+16|0;c[w>>2]=n;c[w+4>>2]=o;w=y+p|0;x=c[w+4>>2]|0;y=r+(s*96|0)+24|0;c[y>>2]=c[w>>2];c[y+4>>2]=x;break}}}while(0);v=c[la>>2]|0;n=c[ka>>2]|0;o=v-(n<<c[wa>>2])|0;p=v+-1|0;do if(!(c[Rb>>2]|0))w=p;else{j=c[oa>>2]|0;q=c[j+(p<<2)>>2]&128;r=D&128;if(c[Ob>>2]&1|0){w=p-((q|0)==(r|0)?0:n)|0;break}if(!r)j=o;else j=(((c[j+(o<<2)>>2]|0)>>>7&1)+-1&n)+o|0;w=p;p=((q|0)==(r|0)?0:n)+p|0;o=j}while(0);c[za>>2]=o;c[Aa>>2]=w;c[Ba>>2]=p;n=c[Ca>>2]|0;q=c[Da>>2]|0;r=a[q+v>>0]|0;do if((r|0)>(n|0))z=59;else{j=(w|0)<0;if(!j)if((r+1+(a[q+w>>0]|0)>>1|0)>(n|0)){z=59;break}if((o|0)>=0)if((r+1+(a[q+o>>0]|0)>>1|0)>(n|0)){z=59;break}if(c[Rb>>2]|0){if(!j)if((r+1+(a[q+p>>0]|0)>>1|0)>(n|0)){z=59;break}j=c[ka>>2]|0;if((o|0)>=(j|0))if((r+1+(a[q+(o-j)>>0]|0)>>1|0)>(n|0))z=59}}while(0);do if((z|0)==59){z=0;q=c[oa>>2]|0;r=c[q+(o<<2)>>2]|0;j=c[q+(w<<2)>>2]|0;q=c[q+(p<<2)>>2]|0;n=c[ma>>2]|0;t=b[n+(o<<1)>>1]|0;n=b[n+(p<<1)>>1]|0;if((c[Qb>>2]|0)==2){y=c[na>>2]|0;s=(n&65535|0)==(y|0);u=s?j:0;s=s?q:0;q=(t&65535|0)==(y|0)?r:0}else{s=n<<16>>16==-1;u=s?0:j;s=s?0:q;q=t<<16>>16==-1?0:r}c[Ea>>2]=q;c[Fa>>2]=u;c[Ga>>2]=s;do if(!(D&7)){j=c[Ha>>2]|0;n=(D&376|0)==0;do if(!n){if(!(q&12288)){c[T>>2]=0;c[T+4>>2]=0;c[T+8>>2]=0;c[T+12>>2]=0;c[U>>2]=-1}else{Xb=(c[(c[nb>>2]|0)+(o<<2)>>2]|0)+(j*3|0)|0;y=o<<2;x=g+27224+((t&31)<<9)+((c[Za>>2]|0?20:2)<<2)|0;Yb=(c[O>>2]|0)+(Xb<<2)|0;t=c[Yb+4>>2]|0;r=T;c[r>>2]=c[Yb>>2];c[r+4>>2]=t;Xb=(c[O>>2]|0)+(Xb<<2)+8|0;r=c[Xb+4>>2]|0;t=ca;c[t>>2]=c[Xb>>2];c[t+4>>2]=r;t=c[x+(a[(c[N>>2]|0)+(y|2)>>0]<<2)>>2]&255;a[da>>0]=t;a[U>>0]=t;y=c[x+(a[(c[N>>2]|0)+(y|3)>>0]<<2)>>2]&255;a[ea>>0]=y;a[fa>>0]=y}if(!((u^D)&128))if(!(u&12288)){c[V>>2]=0;c[W>>2]=0;c[X>>2]=0;c[Y>>2]=0;a[Z>>0]=-1;a[$>>0]=-1;a[aa>>0]=-1;a[ba>>0]=-1;break}else{y=(c[(c[nb>>2]|0)+(w<<2)>>2]|0)+3|0;Yb=w<<2;Xb=g+27224+((e[(c[ma>>2]|0)+(w<<1)>>1]&31)<<9)+((c[Za>>2]|0?20:2)<<2)|0;c[V>>2]=c[(c[O>>2]|0)+(y<<2)>>2];c[W>>2]=c[(c[O>>2]|0)+(y+j<<2)>>2];c[X>>2]=c[(c[O>>2]|0)+(y+(j<<1)<<2)>>2];c[Y>>2]=c[(c[O>>2]|0)+(y+(j*3|0)<<2)>>2];y=c[Xb+(a[(c[N>>2]|0)+(Yb|1)>>0]<<2)>>2]&255;a[aa>>0]=y;a[ba>>0]=y;Yb=c[Xb+(a[(c[N>>2]|0)+(Yb|3)>>0]<<2)>>2]&255;a[Z>>0]=Yb;a[$>>0]=Yb;break}}while(0);if(!(D&12288)){c[Ia>>2]=0;c[Ia+4>>2]=0;c[Ia+8>>2]=0;c[Ia+12>>2]=0;c[Ka>>2]=0;c[Ka+4>>2]=0;c[Ka+8>>2]=0;c[Ka+12>>2]=0;c[La>>2]=0;c[La+4>>2]=0;c[La+8>>2]=0;c[La+12>>2]=0;c[Ma>>2]=0;c[Ma+4>>2]=0;c[Ma+8>>2]=0;c[Ma+12>>2]=0;c[Ja>>2]=-1;c[Na>>2]=-1;c[Oa>>2]=-1;c[Pa>>2]=-1}else{y=(c[N>>2]|0)+(v<<2)|0;Yb=g+27224+((c[na>>2]&31)<<9)+((c[Za>>2]|0?20:2)<<2)|0;x=(c[Yb+(a[y+1>>0]<<2)>>2]<<16&16711680|c[Yb+(a[y>>0]<<2)>>2]&255)*257|0;y=(c[Yb+(a[y+3>>0]<<2)>>2]<<16&16711680|c[Yb+(a[y+2>>0]<<2)>>2]&255)*257|0;c[Ja>>2]=x;c[Na>>2]=x;c[Oa>>2]=y;c[Pa>>2]=y;y=(_(c[Ob>>2]|0,j)|0)+(c[ra>>2]|0)<<2;y=(c[O>>2]|0)+(y<<2)|0;x=y;Yb=c[x+4>>2]|0;Xb=Ia;c[Xb>>2]=c[x>>2];c[Xb+4>>2]=Yb;Xb=y+8|0;Yb=c[Xb+4>>2]|0;x=P;c[x>>2]=c[Xb>>2];c[x+4>>2]=Yb;x=y+(j<<2)|0;Yb=x;Xb=c[Yb+4>>2]|0;t=Ka;c[t>>2]=c[Yb>>2];c[t+4>>2]=Xb;x=x+8|0;t=c[x+4>>2]|0;Xb=Q;c[Xb>>2]=c[x>>2];c[Xb+4>>2]=t;Xb=y+(j<<1<<2)|0;t=Xb;x=c[t+4>>2]|0;Yb=La;c[Yb>>2]=c[t>>2];c[Yb+4>>2]=x;Xb=Xb+8|0;Yb=c[Xb+4>>2]|0;x=R;c[x>>2]=c[Xb>>2];c[x+4>>2]=Yb;y=y+(j*3<<2)|0;x=y;Yb=c[x+4>>2]|0;Xb=Ma;c[Xb>>2]=c[x>>2];c[Xb+4>>2]=Yb;y=y+8|0;Xb=c[y+4>>2]|0;Yb=S;c[Yb>>2]=c[y>>2];c[Yb+4>>2]=Xb}do if((c[qa>>2]|0)==2){j=c[Ha>>2]|0;do if(!n){if(!(q&49152)){c[db>>2]=0;c[db+4>>2]=0;c[db+8>>2]=0;c[db+12>>2]=0;c[eb>>2]=-1}else{t=(c[(c[nb>>2]|0)+(o<<2)>>2]|0)+(j*3|0)|0;Yb=o<<2;Xb=g+27224+((e[(c[ma>>2]|0)+(o<<1)>>1]&31)<<9)+256+((c[Za>>2]|0?20:2)<<2)|0;r=(c[_a>>2]|0)+(t<<2)|0;y=c[r+4>>2]|0;x=db;c[x>>2]=c[r>>2];c[x+4>>2]=y;t=(c[_a>>2]|0)+(t<<2)+8|0;x=c[t+4>>2]|0;y=ob;c[y>>2]=c[t>>2];c[y+4>>2]=x;y=c[Xb+(a[(c[Ya>>2]|0)+(Yb|2)>>0]<<2)>>2]&255;a[pb>>0]=y;a[eb>>0]=y;Yb=c[Xb+(a[(c[Ya>>2]|0)+(Yb|3)>>0]<<2)>>2]&255;a[qb>>0]=Yb;a[rb>>0]=Yb}if((u^D)&128|0)break;if(!(u&49152)){c[fb>>2]=0;c[gb>>2]=0;c[hb>>2]=0;c[ib>>2]=0;a[jb>>0]=-1;a[kb>>0]=-1;a[lb>>0]=-1;a[mb>>0]=-1;break}else{y=(c[(c[nb>>2]|0)+(w<<2)>>2]|0)+3|0;Yb=w<<2;Xb=g+27224+((e[(c[ma>>2]|0)+(w<<1)>>1]&31)<<9)+256+((c[Za>>2]|0?20:2)<<2)|0;c[fb>>2]=c[(c[_a>>2]|0)+(y<<2)>>2];c[gb>>2]=c[(c[_a>>2]|0)+(y+j<<2)>>2];c[hb>>2]=c[(c[_a>>2]|0)+(y+(j<<1)<<2)>>2];c[ib>>2]=c[(c[_a>>2]|0)+(y+(j*3|0)<<2)>>2];y=c[Xb+(a[(c[Ya>>2]|0)+(Yb|1)>>0]<<2)>>2]&255;a[lb>>0]=y;a[mb>>0]=y;Yb=c[Xb+(a[(c[Ya>>2]|0)+(Yb|3)>>0]<<2)>>2]&255;a[jb>>0]=Yb;a[kb>>0]=Yb;break}}while(0);if(!(D&49152)){c[Qa>>2]=0;c[Qa+4>>2]=0;c[Qa+8>>2]=0;c[Qa+12>>2]=0;c[Sa>>2]=0;c[Sa+4>>2]=0;c[Sa+8>>2]=0;c[Sa+12>>2]=0;c[Ta>>2]=0;c[Ta+4>>2]=0;c[Ta+8>>2]=0;c[Ta+12>>2]=0;c[Ua>>2]=0;c[Ua+4>>2]=0;c[Ua+8>>2]=0;c[Ua+12>>2]=0;c[Ra>>2]=-1;c[Va>>2]=-1;c[Wa>>2]=-1;c[Xa>>2]=-1;break}else{y=(c[Ya>>2]|0)+(v<<2)|0;Yb=g+27224+((c[na>>2]&31)<<9)+256+((c[Za>>2]|0?20:2)<<2)|0;x=(c[Yb+(a[y+1>>0]<<2)>>2]<<16&16711680|c[Yb+(a[y>>0]<<2)>>2]&255)*257|0;y=(c[Yb+(a[y+3>>0]<<2)>>2]<<16&16711680|c[Yb+(a[y+2>>0]<<2)>>2]&255)*257|0;c[Ra>>2]=x;c[Va>>2]=x;c[Wa>>2]=y;c[Xa>>2]=y;y=(_(c[Ob>>2]|0,j)|0)+(c[ra>>2]|0)<<2;y=(c[_a>>2]|0)+(y<<2)|0;x=y;Yb=c[x+4>>2]|0;Xb=Qa;c[Xb>>2]=c[x>>2];c[Xb+4>>2]=Yb;Xb=y+8|0;Yb=c[Xb+4>>2]|0;x=$a;c[x>>2]=c[Xb>>2];c[x+4>>2]=Yb;x=y+(j<<2)|0;Yb=x;Xb=c[Yb+4>>2]|0;t=Sa;c[t>>2]=c[Yb>>2];c[t+4>>2]=Xb;x=x+8|0;t=c[x+4>>2]|0;Xb=ab;c[Xb>>2]=c[x>>2];c[Xb+4>>2]=t;Xb=y+(j<<1<<2)|0;t=Xb;x=c[t+4>>2]|0;Yb=Ta;c[Yb>>2]=c[t>>2];c[Yb+4>>2]=x;Xb=Xb+8|0;Yb=c[Xb+4>>2]|0;x=bb;c[x>>2]=c[Xb>>2];c[x+4>>2]=Yb;y=y+(j*3<<2)|0;x=y;Yb=c[x+4>>2]|0;Xb=Ua;c[Xb>>2]=c[x>>2];c[Xb+4>>2]=Yb;y=y+8|0;Xb=c[y+4>>2]|0;Yb=cb;c[Yb>>2]=c[y>>2];c[Yb+4>>2]=Xb;break}}while(0);j=c[sb>>2]|0;c[tb>>2]=c[j+(v*48|0)>>2];c[ub>>2]=c[j+(v*48|0)+4>>2];c[vb>>2]=c[j+(v*48|0)+8>>2];c[wb>>2]=c[j+(v*48|0)+12>>2];j=e[(c[xb>>2]|0)+(v<<1)>>1]|0;c[yb>>2]=j;if(q|0)c[K>>2]=c[(c[sb>>2]|0)+(o*48|0)+12>>2];if(u|0){Yb=c[sb>>2]|0;a[I>>0]=a[Yb+(w*48|0)+3>>0]|0;a[H>>0]=a[Yb+(w*48|0)+7>>0]|0;a[G>>0]=a[Yb+(w*48|0)+11>>0]|0;a[Nb>>0]=a[Yb+(w*48|0)+15>>0]|0}if(!(c[zb>>2]|0)){if(!(c[Ab>>2]|0))break;if(q&16777216|0){Yb=(e[(c[xb>>2]|0)+(o<<1)>>1]|0)>>>12&4;a[J>>0]=Yb;a[K>>0]=Yb;Yb=(e[(c[xb>>2]|0)+(o<<1)>>1]|0)>>>12&8;a[L>>0]=Yb;a[M>>0]=Yb}if(u&16777216|0){Yb=(e[(c[xb>>2]|0)+(w<<1)>>1]|0)>>>12&2;a[H>>0]=Yb;a[I>>0]=Yb}if(s&16777216|0){Yb=(e[(c[xb>>2]|0)+(p<<1)>>1]|0)>>>12&8;a[Nb>>0]=Yb;a[G>>0]=Yb}if(!(D&16777216))break;Yb=j>>>12;Xb=Yb&1;a[Bb>>0]=Xb;a[ub>>0]=Xb;a[Cb>>0]=Xb;a[tb>>0]=Xb;Xb=Yb&2;a[Db>>0]=Xb;a[Eb>>0]=Xb;a[Fb>>0]=Xb;a[Gb>>0]=Xb;Xb=Yb&4;a[Hb>>0]=Xb;a[wb>>0]=Xb;a[Ib>>0]=Xb;a[vb>>0]=Xb;Yb=Yb&8;a[Jb>>0]=Yb;a[Kb>>0]=Yb;a[Lb>>0]=Yb;a[Mb>>0]=Yb}}while(0);Yb=(c[Da>>2]|0)+C|0;c[ga>>2]=d[(a[Yb>>0]|0)+(f+28412)>>0];c[ha>>2]=d[(a[Yb>>0]|0)+(f+28500)>>0];if(!(c[Rb>>2]|0)){HA(f,g,h,F,m,k,l,B,A);break}else{IA(f,g,h,F,m,k,l,B,A);break}}while(0);if((F|0)>=(Sb|0))break a;F=F+1|0;j=c[Rb>>2]|0}}while(0);h=h+1|0}while((h|0)!=(i|0));Wb=ga;Yb=ha;Vb=ra;Xb=c[Rb>>2]|0;c[Tb>>2]=Ub;c[Vb>>2]=i;Xb=Sb-Xb|0;c[Ob>>2]=Xb;Xb=g+824|0;Xb=c[Xb>>2]|0;g=f+28412+Xb|0;g=a[g>>0]|0;g=g&255;c[Wb>>2]=g;Xb=f+28500+Xb|0;Xb=a[Xb>>0]|0;Xb=Xb&255;c[Yb>>2]=Xb;return}function gd(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;h=a+197612|0;g=(c[h>>2]|0)!=3&1;f=c[b+21768>>2]>>g<<4;g=c[a+198228>>2]<<4>>g;e=c[a+197608>>2]|0;d=16<<e;e=20<<e;if(c[b+844>>2]|0){d=((f+d|0)<(g|0)?0:e)+d|0;f=f-e|0}if((g|0)<=(f|0)|(f+d|0)<0)return;e=g-f|0;g=(f|0)<0;e=((d|0)>(e|0)?e:d)+(g?f:0)|0;d=g?0:f;_b(a,b,d,e);if(c[a+25992>>2]|0)return;if(c[(c[(c[b>>2]|0)+25952>>2]|0)+576>>2]|0)return;bA((c[a+24620>>2]|0)+4|0,d+-1+e|0,(c[h>>2]|0)==2&1);return}function hd(a,b){a=a|0;b=b|0;b=(b+-9|0)>>>0<8;c[a>>2]=b?2:1;c[a+4>>2]=b?4:3;c[a+8>>2]=b?6:5;c[a+12>>2]=b?8:7;c[a+16>>2]=b?10:9;c[a+20>>2]=b?12:11;c[a+24>>2]=b?14:13;c[a+28>>2]=b?16:15;return}function id(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;q=8-g|0;p=8-h|0;s=_(p,q)|0;p=_(p,g)|0;q=_(q,h)|0;o=_(h,g)|0;r=d>>1;if(o|0){if((f|0)<=0)return;n=r+1|0;h=r+2|0;d=r+3|0;i=r+4|0;j=r+5|0;k=r+6|0;l=r+7|0;m=r+8|0;g=0;while(1){v=_(e[c>>1]|0,s)|0;u=c+2|0;x=_(e[u>>1]|0,p)|0;t=c;c=c+(r<<1)|0;y=_(e[c>>1]|0,q)|0;w=t+(n<<1)|0;b[a>>1]=(v+32+x+y+(_(e[w>>1]|0,o)|0)|0)>>>6;u=_(e[u>>1]|0,s)|0;y=t+4|0;x=_(e[y>>1]|0,p)|0;w=_(e[w>>1]|0,q)|0;v=t+(h<<1)|0;b[a+2>>1]=(u+32+x+w+(_(e[v>>1]|0,o)|0)|0)>>>6;y=_(e[y>>1]|0,s)|0;w=t+6|0;x=_(e[w>>1]|0,p)|0;v=_(e[v>>1]|0,q)|0;u=t+(d<<1)|0;b[a+4>>1]=(y+32+x+v+(_(e[u>>1]|0,o)|0)|0)>>>6;w=_(e[w>>1]|0,s)|0;v=t+8|0;x=_(e[v>>1]|0,p)|0;u=_(e[u>>1]|0,q)|0;y=t+(i<<1)|0;b[a+6>>1]=(w+32+x+u+(_(e[y>>1]|0,o)|0)|0)>>>6;v=_(e[v>>1]|0,s)|0;u=t+10|0;x=_(e[u>>1]|0,p)|0;y=_(e[y>>1]|0,q)|0;w=t+(j<<1)|0;b[a+8>>1]=(v+32+x+y+(_(e[w>>1]|0,o)|0)|0)>>>6;u=_(e[u>>1]|0,s)|0;y=t+12|0;x=_(e[y>>1]|0,p)|0;w=_(e[w>>1]|0,q)|0;v=t+(k<<1)|0;b[a+10>>1]=(u+32+x+w+(_(e[v>>1]|0,o)|0)|0)>>>6;y=_(e[y>>1]|0,s)|0;w=t+14|0;x=_(e[w>>1]|0,p)|0;v=_(e[v>>1]|0,q)|0;u=t+(l<<1)|0;b[a+12>>1]=(y+32+x+v+(_(e[u>>1]|0,o)|0)|0)>>>6;w=_(e[w>>1]|0,s)|0;v=_(e[t+16>>1]|0,p)|0;u=_(e[u>>1]|0,q)|0;b[a+14>>1]=(w+32+v+u+(_(e[t+(m<<1)>>1]|0,o)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else a=a+(r<<1)|0}return}o=p+q|0;if(!o){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((_(e[c>>1]|0,s)|0)+32|0)>>>6;b[a+2>>1]=((_(e[c+2>>1]|0,s)|0)+32|0)>>>6;b[a+4>>1]=((_(e[c+4>>1]|0,s)|0)+32|0)>>>6;b[a+6>>1]=((_(e[c+6>>1]|0,s)|0)+32|0)>>>6;b[a+8>>1]=((_(e[c+8>>1]|0,s)|0)+32|0)>>>6;b[a+10>>1]=((_(e[c+10>>1]|0,s)|0)+32|0)>>>6;b[a+12>>1]=((_(e[c+12>>1]|0,s)|0)+32|0)>>>6;b[a+14>>1]=((_(e[c+14>>1]|0,s)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(r<<1)|0;c=c+(r<<1)|0}}return}h=q|0?r:1;if((f|0)<=0)return;d=h+1|0;i=h+2|0;j=h+3|0;k=h+4|0;l=h+5|0;m=h+6|0;n=h+7|0;g=0;while(1){y=_(e[c>>1]|0,s)|0;b[a>>1]=(y+32+(_(e[c+(h<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+2>>1]|0,s)|0;b[a+2>>1]=(y+32+(_(e[c+(d<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+4>>1]|0,s)|0;b[a+4>>1]=(y+32+(_(e[c+(i<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+6>>1]|0,s)|0;b[a+6>>1]=(y+32+(_(e[c+(j<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+8>>1]|0,s)|0;b[a+8>>1]=(y+32+(_(e[c+(k<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+10>>1]|0,s)|0;b[a+10>>1]=(y+32+(_(e[c+(l<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+12>>1]|0,s)|0;b[a+12>>1]=(y+32+(_(e[c+(m<<1)>>1]|0,o)|0)|0)>>>6;y=_(e[c+14>>1]|0,s)|0;b[a+14>>1]=(y+32+(_(e[c+(n<<1)>>1]|0,o)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(r<<1)|0;c=c+(r<<1)|0}}return}function jd(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;r=8-g|0;q=8-h|0;s=_(q,r)|0;q=_(q,g)|0;r=_(r,h)|0;n=_(h,g)|0;if(n|0){if((f|0)<=0)return;o=e+1|0;p=e+2|0;h=e+3|0;i=e+4|0;j=e+5|0;k=e+6|0;l=e+7|0;m=e+8|0;g=0;while(1){v=_(d[c>>0]|0,s)|0;u=c+1|0;x=_(d[u>>0]|0,q)|0;t=c;c=c+e|0;y=_(d[c>>0]|0,r)|0;w=t+o|0;a[b>>0]=(v+32+x+y+(_(d[w>>0]|0,n)|0)|0)>>>6;u=_(d[u>>0]|0,s)|0;y=t+2|0;x=_(d[y>>0]|0,q)|0;w=_(d[w>>0]|0,r)|0;v=t+p|0;a[b+1>>0]=(u+32+x+w+(_(d[v>>0]|0,n)|0)|0)>>>6;y=_(d[y>>0]|0,s)|0;w=t+3|0;x=_(d[w>>0]|0,q)|0;v=_(d[v>>0]|0,r)|0;u=t+h|0;a[b+2>>0]=(y+32+x+v+(_(d[u>>0]|0,n)|0)|0)>>>6;w=_(d[w>>0]|0,s)|0;v=t+4|0;x=_(d[v>>0]|0,q)|0;u=_(d[u>>0]|0,r)|0;y=t+i|0;a[b+3>>0]=(w+32+x+u+(_(d[y>>0]|0,n)|0)|0)>>>6;v=_(d[v>>0]|0,s)|0;u=t+5|0;x=_(d[u>>0]|0,q)|0;y=_(d[y>>0]|0,r)|0;w=t+j|0;a[b+4>>0]=(v+32+x+y+(_(d[w>>0]|0,n)|0)|0)>>>6;u=_(d[u>>0]|0,s)|0;y=t+6|0;x=_(d[y>>0]|0,q)|0;w=_(d[w>>0]|0,r)|0;v=t+k|0;a[b+5>>0]=(u+32+x+w+(_(d[v>>0]|0,n)|0)|0)>>>6;y=_(d[y>>0]|0,s)|0;w=t+7|0;x=_(d[w>>0]|0,q)|0;v=_(d[v>>0]|0,r)|0;u=t+l|0;a[b+6>>0]=(y+32+x+v+(_(d[u>>0]|0,n)|0)|0)>>>6;w=_(d[w>>0]|0,s)|0;v=_(d[t+8>>0]|0,q)|0;u=_(d[u>>0]|0,r)|0;a[b+7>>0]=(w+32+v+u+(_(d[t+m>>0]|0,n)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}p=q+r|0;if(!p){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((_(d[c>>0]|0,s)|0)+32|0)>>>6;a[b+1>>0]=((_(d[c+1>>0]|0,s)|0)+32|0)>>>6;a[b+2>>0]=((_(d[c+2>>0]|0,s)|0)+32|0)>>>6;a[b+3>>0]=((_(d[c+3>>0]|0,s)|0)+32|0)>>>6;a[b+4>>0]=((_(d[c+4>>0]|0,s)|0)+32|0)>>>6;a[b+5>>0]=((_(d[c+5>>0]|0,s)|0)+32|0)>>>6;a[b+6>>0]=((_(d[c+6>>0]|0,s)|0)+32|0)>>>6;a[b+7>>0]=((_(d[c+7>>0]|0,s)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}h=r|0?e:1;if((f|0)<=0)return;i=h+1|0;j=h+2|0;k=h+3|0;l=h+4|0;m=h+5|0;n=h+6|0;o=h+7|0;g=0;while(1){y=_(d[c>>0]|0,s)|0;a[b>>0]=(y+32+(_(d[c+h>>0]|0,p)|0)|0)>>>6;y=_(d[c+1>>0]|0,s)|0;a[b+1>>0]=(y+32+(_(d[c+i>>0]|0,p)|0)|0)>>>6;y=_(d[c+2>>0]|0,s)|0;a[b+2>>0]=(y+32+(_(d[c+j>>0]|0,p)|0)|0)>>>6;y=_(d[c+3>>0]|0,s)|0;a[b+3>>0]=(y+32+(_(d[c+k>>0]|0,p)|0)|0)>>>6;y=_(d[c+4>>0]|0,s)|0;a[b+4>>0]=(y+32+(_(d[c+l>>0]|0,p)|0)|0)>>>6;y=_(d[c+5>>0]|0,s)|0;a[b+5>>0]=(y+32+(_(d[c+m>>0]|0,p)|0)|0)>>>6;y=_(d[c+6>>0]|0,s)|0;a[b+6>>0]=(y+32+(_(d[c+n>>0]|0,p)|0)|0)>>>6;y=_(d[c+7>>0]|0,s)|0;a[b+7>>0]=(y+32+(_(d[c+o>>0]|0,p)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}function kd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;m=8-g|0;l=8-h|0;o=_(l,m)|0;l=_(l,g)|0;m=_(m,h)|0;k=_(h,g)|0;n=d>>1;if(k|0){if((f|0)<=0)return;j=n+1|0;h=n+2|0;d=n+3|0;i=n+4|0;g=0;while(1){r=_(e[c>>1]|0,o)|0;q=c+2|0;t=_(e[q>>1]|0,l)|0;p=c;c=c+(n<<1)|0;u=_(e[c>>1]|0,m)|0;s=p+(j<<1)|0;b[a>>1]=(r+32+t+u+(_(e[s>>1]|0,k)|0)|0)>>>6;q=_(e[q>>1]|0,o)|0;u=p+4|0;t=_(e[u>>1]|0,l)|0;s=_(e[s>>1]|0,m)|0;r=p+(h<<1)|0;b[a+2>>1]=(q+32+t+s+(_(e[r>>1]|0,k)|0)|0)>>>6;u=_(e[u>>1]|0,o)|0;s=p+6|0;t=_(e[s>>1]|0,l)|0;r=_(e[r>>1]|0,m)|0;q=p+(d<<1)|0;b[a+4>>1]=(u+32+t+r+(_(e[q>>1]|0,k)|0)|0)>>>6;s=_(e[s>>1]|0,o)|0;r=_(e[p+8>>1]|0,l)|0;q=_(e[q>>1]|0,m)|0;b[a+6>>1]=(s+32+r+q+(_(e[p+(i<<1)>>1]|0,k)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else a=a+(n<<1)|0}return}k=l+m|0;if(!k){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((_(e[c>>1]|0,o)|0)+32|0)>>>6;b[a+2>>1]=((_(e[c+2>>1]|0,o)|0)+32|0)>>>6;b[a+4>>1]=((_(e[c+4>>1]|0,o)|0)+32|0)>>>6;b[a+6>>1]=((_(e[c+6>>1]|0,o)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(n<<1)|0;c=c+(n<<1)|0}}return}h=m|0?n:1;if((f|0)<=0)return;d=h+1|0;i=h+2|0;j=h+3|0;g=0;while(1){u=_(e[c>>1]|0,o)|0;b[a>>1]=(u+32+(_(e[c+(h<<1)>>1]|0,k)|0)|0)>>>6;u=_(e[c+2>>1]|0,o)|0;b[a+2>>1]=(u+32+(_(e[c+(d<<1)>>1]|0,k)|0)|0)>>>6;u=_(e[c+4>>1]|0,o)|0;b[a+4>>1]=(u+32+(_(e[c+(i<<1)>>1]|0,k)|0)|0)>>>6;u=_(e[c+6>>1]|0,o)|0;b[a+6>>1]=(u+32+(_(e[c+(j<<1)>>1]|0,k)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(n<<1)|0;c=c+(n<<1)|0}}return}function ld(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;n=8-g|0;m=8-h|0;o=_(m,n)|0;m=_(m,g)|0;n=_(n,h)|0;j=_(h,g)|0;if(j|0){if((f|0)<=0)return;k=e+1|0;l=e+2|0;h=e+3|0;i=e+4|0;g=0;while(1){r=_(d[c>>0]|0,o)|0;q=c+1|0;t=_(d[q>>0]|0,m)|0;p=c;c=c+e|0;u=_(d[c>>0]|0,n)|0;s=p+k|0;a[b>>0]=(r+32+t+u+(_(d[s>>0]|0,j)|0)|0)>>>6;q=_(d[q>>0]|0,o)|0;u=p+2|0;t=_(d[u>>0]|0,m)|0;s=_(d[s>>0]|0,n)|0;r=p+l|0;a[b+1>>0]=(q+32+t+s+(_(d[r>>0]|0,j)|0)|0)>>>6;u=_(d[u>>0]|0,o)|0;s=p+3|0;t=_(d[s>>0]|0,m)|0;r=_(d[r>>0]|0,n)|0;q=p+h|0;a[b+2>>0]=(u+32+t+r+(_(d[q>>0]|0,j)|0)|0)>>>6;s=_(d[s>>0]|0,o)|0;r=_(d[p+4>>0]|0,m)|0;q=_(d[q>>0]|0,n)|0;a[b+3>>0]=(s+32+r+q+(_(d[p+i>>0]|0,j)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}l=m+n|0;if(!l){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((_(d[c>>0]|0,o)|0)+32|0)>>>6;a[b+1>>0]=((_(d[c+1>>0]|0,o)|0)+32|0)>>>6;a[b+2>>0]=((_(d[c+2>>0]|0,o)|0)+32|0)>>>6;a[b+3>>0]=((_(d[c+3>>0]|0,o)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}h=n|0?e:1;if((f|0)<=0)return;i=h+1|0;j=h+2|0;k=h+3|0;g=0;while(1){u=_(d[c>>0]|0,o)|0;a[b>>0]=(u+32+(_(d[c+h>>0]|0,l)|0)|0)>>>6;u=_(d[c+1>>0]|0,o)|0;a[b+1>>0]=(u+32+(_(d[c+i>>0]|0,l)|0)|0)>>>6;u=_(d[c+2>>0]|0,o)|0;a[b+2>>0]=(u+32+(_(d[c+j>>0]|0,l)|0)|0)>>>6;u=_(d[c+3>>0]|0,o)|0;a[b+3>>0]=(u+32+(_(d[c+k>>0]|0,l)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}function md(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;k=8-g|0;j=8-h|0;m=_(j,k)|0;j=_(j,g)|0;k=_(k,h)|0;i=_(h,g)|0;l=d>>1;if(i|0){if((f|0)<=0)return;d=l+1|0;h=l+2|0;g=0;while(1){s=_(e[c>>1]|0,m)|0;q=c+2|0;r=_(e[q>>1]|0,j)|0;n=c;c=c+(l<<1)|0;p=_(e[c>>1]|0,k)|0;o=n+(d<<1)|0;b[a>>1]=(s+32+r+p+(_(e[o>>1]|0,i)|0)|0)>>>6;q=_(e[q>>1]|0,m)|0;p=_(e[n+4>>1]|0,j)|0;o=_(e[o>>1]|0,k)|0;b[a+2>>1]=(q+32+p+o+(_(e[n+(h<<1)>>1]|0,i)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else a=a+(l<<1)|0}return}i=j+k|0;if(!i){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((_(e[c>>1]|0,m)|0)+32|0)>>>6;b[a+2>>1]=((_(e[c+2>>1]|0,m)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}h=k|0?l:1;if((f|0)<=0)return;d=h+1|0;g=0;while(1){s=_(e[c>>1]|0,m)|0;b[a>>1]=(s+32+(_(e[c+(h<<1)>>1]|0,i)|0)|0)>>>6;s=_(e[c+2>>1]|0,m)|0;b[a+2>>1]=(s+32+(_(e[c+(d<<1)>>1]|0,i)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}function nd(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;l=8-g|0;k=8-h|0;m=_(k,l)|0;k=_(k,g)|0;l=_(l,h)|0;h=_(h,g)|0;if(h|0){if((f|0)<=0)return;i=e+1|0;j=e+2|0;g=0;while(1){s=_(d[c>>0]|0,m)|0;q=c+1|0;r=_(d[q>>0]|0,k)|0;n=c;c=c+e|0;p=_(d[c>>0]|0,l)|0;o=n+i|0;a[b>>0]=(s+32+r+p+(_(d[o>>0]|0,h)|0)|0)>>>6;q=_(d[q>>0]|0,m)|0;p=_(d[n+2>>0]|0,k)|0;o=_(d[o>>0]|0,l)|0;a[b+1>>0]=(q+32+p+o+(_(d[n+j>>0]|0,h)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}j=k+l|0;if(!j){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((_(d[c>>0]|0,m)|0)+32|0)>>>6;a[b+1>>0]=((_(d[c+1>>0]|0,m)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}h=l|0?e:1;if((f|0)<=0)return;i=h+1|0;g=0;while(1){s=_(d[c>>0]|0,m)|0;a[b>>0]=(s+32+(_(d[c+h>>0]|0,j)|0)|0)>>>6;s=_(d[c+1>>0]|0,m)|0;a[b+1>>0]=(s+32+(_(d[c+i>>0]|0,j)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}function od(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;k=8-g|0;j=8-h|0;m=_(j,k)|0;j=_(j,g)|0;k=_(k,h)|0;i=_(h,g)|0;l=d>>1;if(i|0){if((f|0)<=0)return;h=l+1|0;g=0;while(1){p=_(e[c>>1]|0,m)|0;o=_(e[c+2>>1]|0,j)|0;d=c;c=c+(l<<1)|0;n=_(e[c>>1]|0,k)|0;b[a>>1]=(p+32+o+n+(_(e[d+(h<<1)>>1]|0,i)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else a=a+(l<<1)|0}return}d=j+k|0;if(!d){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((_(e[c>>1]|0,m)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}else{h=k|0?l:1;if((f|0)>0)g=0;else return;while(1){p=_(e[c>>1]|0,m)|0;b[a>>1]=(p+32+(_(e[c+(h<<1)>>1]|0,d)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}}function pd(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;k=8-g|0;j=8-h|0;l=_(j,k)|0;j=_(j,g)|0;k=_(k,h)|0;h=_(h,g)|0;if(h|0){if((f|0)<=0)return;i=e+1|0;g=0;while(1){p=_(d[c>>0]|0,l)|0;o=_(d[c+1>>0]|0,j)|0;m=c;c=c+e|0;n=_(d[c>>0]|0,k)|0;a[b>>0]=(p+32+o+n+(_(d[m+i>>0]|0,h)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}i=j+k|0;if(!i){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((_(d[c>>0]|0,l)|0)+32|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}else{h=k|0?e:1;if((f|0)>0)g=0;else return;while(1){p=_(d[c>>0]|0,l)|0;a[b>>0]=(p+32+(_(d[c+h>>0]|0,i)|0)|0)>>>6;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}}function qd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;q=8-g|0;p=8-h|0;s=_(p,q)|0;p=_(p,g)|0;q=_(q,h)|0;o=_(h,g)|0;r=d>>1;if(o|0){if((f|0)<=0)return;n=r+1|0;h=r+2|0;d=r+3|0;i=r+4|0;j=r+5|0;k=r+6|0;l=r+7|0;m=r+8|0;g=0;while(1){y=_(e[c>>1]|0,s)|0;v=c+2|0;z=_(e[v>>1]|0,p)|0;u=c;c=c+(r<<1)|0;t=_(e[c>>1]|0,q)|0;x=u+(n<<1)|0;b[a>>1]=((e[a>>1]|0)+1+((y+32+z+t+(_(e[x>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+2|0;v=_(e[v>>1]|0,s)|0;z=u+4|0;y=_(e[z>>1]|0,p)|0;x=_(e[x>>1]|0,q)|0;w=u+(h<<1)|0;b[t>>1]=((e[t>>1]|0)+1+((v+32+y+x+(_(e[w>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+4|0;z=_(e[z>>1]|0,s)|0;x=u+6|0;y=_(e[x>>1]|0,p)|0;w=_(e[w>>1]|0,q)|0;v=u+(d<<1)|0;b[t>>1]=((e[t>>1]|0)+1+((z+32+y+w+(_(e[v>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+6|0;x=_(e[x>>1]|0,s)|0;w=u+8|0;y=_(e[w>>1]|0,p)|0;v=_(e[v>>1]|0,q)|0;z=u+(i<<1)|0;b[t>>1]=((e[t>>1]|0)+1+((x+32+y+v+(_(e[z>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+8|0;w=_(e[w>>1]|0,s)|0;v=u+10|0;y=_(e[v>>1]|0,p)|0;z=_(e[z>>1]|0,q)|0;x=u+(j<<1)|0;b[t>>1]=((e[t>>1]|0)+1+((w+32+y+z+(_(e[x>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+10|0;v=_(e[v>>1]|0,s)|0;z=u+12|0;y=_(e[z>>1]|0,p)|0;x=_(e[x>>1]|0,q)|0;w=u+(k<<1)|0;b[t>>1]=((e[t>>1]|0)+1+((v+32+y+x+(_(e[w>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+12|0;z=_(e[z>>1]|0,s)|0;x=u+14|0;y=_(e[x>>1]|0,p)|0;w=_(e[w>>1]|0,q)|0;v=u+(l<<1)|0;b[t>>1]=((e[t>>1]|0)+1+((z+32+y+w+(_(e[v>>1]|0,o)|0)|0)>>>6)|0)>>>1;t=a+14|0;x=_(e[x>>1]|0,s)|0;w=_(e[u+16>>1]|0,p)|0;v=_(e[v>>1]|0,q)|0;b[t>>1]=((e[t>>1]|0)+1+((x+32+w+v+(_(e[u+(m<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else a=a+(r<<1)|0}return}o=p+q|0;if(!o){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((e[a>>1]|0)+1+(((_(e[c>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+2|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+2>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+4|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+4>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+6|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+6>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+8|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+8>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+10|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+10>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+12|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+12>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;z=a+14|0;b[z>>1]=((e[z>>1]|0)+1+(((_(e[c+14>>1]|0,s)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(r<<1)|0;c=c+(r<<1)|0}}return}h=q|0?r:1;if((f|0)<=0)return;d=h+1|0;i=h+2|0;j=h+3|0;k=h+4|0;l=h+5|0;m=h+6|0;n=h+7|0;g=0;while(1){z=_(e[c>>1]|0,s)|0;b[a>>1]=((e[a>>1]|0)+1+((z+32+(_(e[c+(h<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+2|0;y=_(e[c+2>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(d<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+4|0;y=_(e[c+4>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(i<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+6|0;y=_(e[c+6>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(j<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+8|0;y=_(e[c+8>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(k<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+10|0;y=_(e[c+10>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(l<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+12|0;y=_(e[c+12>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(m<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;z=a+14|0;y=_(e[c+14>>1]|0,s)|0;b[z>>1]=((e[z>>1]|0)+1+((y+32+(_(e[c+(n<<1)>>1]|0,o)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(r<<1)|0;c=c+(r<<1)|0}}return}function rd(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;r=8-g|0;q=8-h|0;s=_(q,r)|0;q=_(q,g)|0;r=_(r,h)|0;n=_(h,g)|0;if(n|0){if((f|0)<=0)return;o=e+1|0;p=e+2|0;h=e+3|0;i=e+4|0;j=e+5|0;k=e+6|0;l=e+7|0;m=e+8|0;g=0;while(1){y=_(d[c>>0]|0,s)|0;v=c+1|0;z=_(d[v>>0]|0,q)|0;u=c;c=c+e|0;t=_(d[c>>0]|0,r)|0;x=u+o|0;a[b>>0]=((d[b>>0]|0)+1+((y+32+z+t+(_(d[x>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+1|0;v=_(d[v>>0]|0,s)|0;z=u+2|0;y=_(d[z>>0]|0,q)|0;x=_(d[x>>0]|0,r)|0;w=u+p|0;a[t>>0]=((d[t>>0]|0)+1+((v+32+y+x+(_(d[w>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+2|0;z=_(d[z>>0]|0,s)|0;x=u+3|0;y=_(d[x>>0]|0,q)|0;w=_(d[w>>0]|0,r)|0;v=u+h|0;a[t>>0]=((d[t>>0]|0)+1+((z+32+y+w+(_(d[v>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+3|0;x=_(d[x>>0]|0,s)|0;w=u+4|0;y=_(d[w>>0]|0,q)|0;v=_(d[v>>0]|0,r)|0;z=u+i|0;a[t>>0]=((d[t>>0]|0)+1+((x+32+y+v+(_(d[z>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+4|0;w=_(d[w>>0]|0,s)|0;v=u+5|0;y=_(d[v>>0]|0,q)|0;z=_(d[z>>0]|0,r)|0;x=u+j|0;a[t>>0]=((d[t>>0]|0)+1+((w+32+y+z+(_(d[x>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+5|0;v=_(d[v>>0]|0,s)|0;z=u+6|0;y=_(d[z>>0]|0,q)|0;x=_(d[x>>0]|0,r)|0;w=u+k|0;a[t>>0]=((d[t>>0]|0)+1+((v+32+y+x+(_(d[w>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+6|0;z=_(d[z>>0]|0,s)|0;x=u+7|0;y=_(d[x>>0]|0,q)|0;w=_(d[w>>0]|0,r)|0;v=u+l|0;a[t>>0]=((d[t>>0]|0)+1+((z+32+y+w+(_(d[v>>0]|0,n)|0)|0)>>>6)|0)>>>1;t=b+7|0;x=_(d[x>>0]|0,s)|0;w=_(d[u+8>>0]|0,q)|0;v=_(d[v>>0]|0,r)|0;a[t>>0]=((d[t>>0]|0)+1+((x+32+w+v+(_(d[u+m>>0]|0,n)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}p=q+r|0;if(!p){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((d[b>>0]|0)+1+(((_(d[c>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+1|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+1>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+2|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+2>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+3|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+3>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+4|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+4>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+5|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+5>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+6|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+6>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;z=b+7|0;a[z>>0]=((d[z>>0]|0)+1+(((_(d[c+7>>0]|0,s)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}h=r|0?e:1;if((f|0)<=0)return;i=h+1|0;j=h+2|0;k=h+3|0;l=h+4|0;m=h+5|0;n=h+6|0;o=h+7|0;g=0;while(1){z=_(d[c>>0]|0,s)|0;a[b>>0]=((d[b>>0]|0)+1+((z+32+(_(d[c+h>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+1|0;y=_(d[c+1>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+i>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+2|0;y=_(d[c+2>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+j>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+3|0;y=_(d[c+3>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+k>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+4|0;y=_(d[c+4>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+l>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+5|0;y=_(d[c+5>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+m>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+6|0;y=_(d[c+6>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+n>>0]|0,p)|0)|0)>>>6)|0)>>>1;z=b+7|0;y=_(d[c+7>>0]|0,s)|0;a[z>>0]=((d[z>>0]|0)+1+((y+32+(_(d[c+o>>0]|0,p)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}function sd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;m=8-g|0;l=8-h|0;o=_(l,m)|0;l=_(l,g)|0;m=_(m,h)|0;k=_(h,g)|0;n=d>>1;if(k|0){if((f|0)<=0)return;j=n+1|0;h=n+2|0;d=n+3|0;i=n+4|0;g=0;while(1){u=_(e[c>>1]|0,o)|0;r=c+2|0;v=_(e[r>>1]|0,l)|0;q=c;c=c+(n<<1)|0;p=_(e[c>>1]|0,m)|0;t=q+(j<<1)|0;b[a>>1]=((e[a>>1]|0)+1+((u+32+v+p+(_(e[t>>1]|0,k)|0)|0)>>>6)|0)>>>1;p=a+2|0;r=_(e[r>>1]|0,o)|0;v=q+4|0;u=_(e[v>>1]|0,l)|0;t=_(e[t>>1]|0,m)|0;s=q+(h<<1)|0;b[p>>1]=((e[p>>1]|0)+1+((r+32+u+t+(_(e[s>>1]|0,k)|0)|0)>>>6)|0)>>>1;p=a+4|0;v=_(e[v>>1]|0,o)|0;t=q+6|0;u=_(e[t>>1]|0,l)|0;s=_(e[s>>1]|0,m)|0;r=q+(d<<1)|0;b[p>>1]=((e[p>>1]|0)+1+((v+32+u+s+(_(e[r>>1]|0,k)|0)|0)>>>6)|0)>>>1;p=a+6|0;t=_(e[t>>1]|0,o)|0;s=_(e[q+8>>1]|0,l)|0;r=_(e[r>>1]|0,m)|0;b[p>>1]=((e[p>>1]|0)+1+((t+32+s+r+(_(e[q+(i<<1)>>1]|0,k)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else a=a+(n<<1)|0}return}k=l+m|0;if(!k){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((e[a>>1]|0)+1+(((_(e[c>>1]|0,o)|0)+32|0)>>>6)|0)>>>1;v=a+2|0;b[v>>1]=((e[v>>1]|0)+1+(((_(e[c+2>>1]|0,o)|0)+32|0)>>>6)|0)>>>1;v=a+4|0;b[v>>1]=((e[v>>1]|0)+1+(((_(e[c+4>>1]|0,o)|0)+32|0)>>>6)|0)>>>1;v=a+6|0;b[v>>1]=((e[v>>1]|0)+1+(((_(e[c+6>>1]|0,o)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(n<<1)|0;c=c+(n<<1)|0}}return}h=m|0?n:1;if((f|0)<=0)return;d=h+1|0;i=h+2|0;j=h+3|0;g=0;while(1){v=_(e[c>>1]|0,o)|0;b[a>>1]=((e[a>>1]|0)+1+((v+32+(_(e[c+(h<<1)>>1]|0,k)|0)|0)>>>6)|0)>>>1;v=a+2|0;u=_(e[c+2>>1]|0,o)|0;b[v>>1]=((e[v>>1]|0)+1+((u+32+(_(e[c+(d<<1)>>1]|0,k)|0)|0)>>>6)|0)>>>1;v=a+4|0;u=_(e[c+4>>1]|0,o)|0;b[v>>1]=((e[v>>1]|0)+1+((u+32+(_(e[c+(i<<1)>>1]|0,k)|0)|0)>>>6)|0)>>>1;v=a+6|0;u=_(e[c+6>>1]|0,o)|0;b[v>>1]=((e[v>>1]|0)+1+((u+32+(_(e[c+(j<<1)>>1]|0,k)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(n<<1)|0;c=c+(n<<1)|0}}return}function td(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;n=8-g|0;m=8-h|0;o=_(m,n)|0;m=_(m,g)|0;n=_(n,h)|0;j=_(h,g)|0;if(j|0){if((f|0)<=0)return;k=e+1|0;l=e+2|0;h=e+3|0;i=e+4|0;g=0;while(1){u=_(d[c>>0]|0,o)|0;r=c+1|0;v=_(d[r>>0]|0,m)|0;q=c;c=c+e|0;p=_(d[c>>0]|0,n)|0;t=q+k|0;a[b>>0]=((d[b>>0]|0)+1+((u+32+v+p+(_(d[t>>0]|0,j)|0)|0)>>>6)|0)>>>1;p=b+1|0;r=_(d[r>>0]|0,o)|0;v=q+2|0;u=_(d[v>>0]|0,m)|0;t=_(d[t>>0]|0,n)|0;s=q+l|0;a[p>>0]=((d[p>>0]|0)+1+((r+32+u+t+(_(d[s>>0]|0,j)|0)|0)>>>6)|0)>>>1;p=b+2|0;v=_(d[v>>0]|0,o)|0;t=q+3|0;u=_(d[t>>0]|0,m)|0;s=_(d[s>>0]|0,n)|0;r=q+h|0;a[p>>0]=((d[p>>0]|0)+1+((v+32+u+s+(_(d[r>>0]|0,j)|0)|0)>>>6)|0)>>>1;p=b+3|0;t=_(d[t>>0]|0,o)|0;s=_(d[q+4>>0]|0,m)|0;r=_(d[r>>0]|0,n)|0;a[p>>0]=((d[p>>0]|0)+1+((t+32+s+r+(_(d[q+i>>0]|0,j)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}l=m+n|0;if(!l){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((d[b>>0]|0)+1+(((_(d[c>>0]|0,o)|0)+32|0)>>>6)|0)>>>1;v=b+1|0;a[v>>0]=((d[v>>0]|0)+1+(((_(d[c+1>>0]|0,o)|0)+32|0)>>>6)|0)>>>1;v=b+2|0;a[v>>0]=((d[v>>0]|0)+1+(((_(d[c+2>>0]|0,o)|0)+32|0)>>>6)|0)>>>1;v=b+3|0;a[v>>0]=((d[v>>0]|0)+1+(((_(d[c+3>>0]|0,o)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}h=n|0?e:1;if((f|0)<=0)return;i=h+1|0;j=h+2|0;k=h+3|0;g=0;while(1){v=_(d[c>>0]|0,o)|0;a[b>>0]=((d[b>>0]|0)+1+((v+32+(_(d[c+h>>0]|0,l)|0)|0)>>>6)|0)>>>1;v=b+1|0;u=_(d[c+1>>0]|0,o)|0;a[v>>0]=((d[v>>0]|0)+1+((u+32+(_(d[c+i>>0]|0,l)|0)|0)>>>6)|0)>>>1;v=b+2|0;u=_(d[c+2>>0]|0,o)|0;a[v>>0]=((d[v>>0]|0)+1+((u+32+(_(d[c+j>>0]|0,l)|0)|0)>>>6)|0)>>>1;v=b+3|0;u=_(d[c+3>>0]|0,o)|0;a[v>>0]=((d[v>>0]|0)+1+((u+32+(_(d[c+k>>0]|0,l)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}function ud(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;k=8-g|0;j=8-h|0;m=_(j,k)|0;j=_(j,g)|0;k=_(k,h)|0;i=_(h,g)|0;l=d>>1;if(i|0){if((f|0)<=0)return;d=l+1|0;h=l+2|0;g=0;while(1){s=_(e[c>>1]|0,m)|0;r=c+2|0;q=_(e[r>>1]|0,j)|0;o=c;c=c+(l<<1)|0;n=_(e[c>>1]|0,k)|0;p=o+(d<<1)|0;b[a>>1]=((e[a>>1]|0)+1+((s+32+q+n+(_(e[p>>1]|0,i)|0)|0)>>>6)|0)>>>1;n=a+2|0;r=_(e[r>>1]|0,m)|0;q=_(e[o+4>>1]|0,j)|0;p=_(e[p>>1]|0,k)|0;b[n>>1]=((e[n>>1]|0)+1+((r+32+q+p+(_(e[o+(h<<1)>>1]|0,i)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else a=a+(l<<1)|0}return}i=j+k|0;if(!i){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((e[a>>1]|0)+1+(((_(e[c>>1]|0,m)|0)+32|0)>>>6)|0)>>>1;s=a+2|0;b[s>>1]=((e[s>>1]|0)+1+(((_(e[c+2>>1]|0,m)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}h=k|0?l:1;if((f|0)<=0)return;d=h+1|0;g=0;while(1){s=_(e[c>>1]|0,m)|0;b[a>>1]=((e[a>>1]|0)+1+((s+32+(_(e[c+(h<<1)>>1]|0,i)|0)|0)>>>6)|0)>>>1;s=a+2|0;r=_(e[c+2>>1]|0,m)|0;b[s>>1]=((e[s>>1]|0)+1+((r+32+(_(e[c+(d<<1)>>1]|0,i)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}function vd(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;l=8-g|0;k=8-h|0;m=_(k,l)|0;k=_(k,g)|0;l=_(l,h)|0;h=_(h,g)|0;if(h|0){if((f|0)<=0)return;i=e+1|0;j=e+2|0;g=0;while(1){s=_(d[c>>0]|0,m)|0;r=c+1|0;q=_(d[r>>0]|0,k)|0;o=c;c=c+e|0;n=_(d[c>>0]|0,l)|0;p=o+i|0;a[b>>0]=((d[b>>0]|0)+1+((s+32+q+n+(_(d[p>>0]|0,h)|0)|0)>>>6)|0)>>>1;n=b+1|0;r=_(d[r>>0]|0,m)|0;q=_(d[o+2>>0]|0,k)|0;p=_(d[p>>0]|0,l)|0;a[n>>0]=((d[n>>0]|0)+1+((r+32+q+p+(_(d[o+j>>0]|0,h)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}j=k+l|0;if(!j){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((d[b>>0]|0)+1+(((_(d[c>>0]|0,m)|0)+32|0)>>>6)|0)>>>1;s=b+1|0;a[s>>0]=((d[s>>0]|0)+1+(((_(d[c+1>>0]|0,m)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}h=l|0?e:1;if((f|0)<=0)return;i=h+1|0;g=0;while(1){s=_(d[c>>0]|0,m)|0;a[b>>0]=((d[b>>0]|0)+1+((s+32+(_(d[c+h>>0]|0,j)|0)|0)>>>6)|0)>>>1;s=b+1|0;r=_(d[c+1>>0]|0,m)|0;a[s>>0]=((d[s>>0]|0)+1+((r+32+(_(d[c+i>>0]|0,j)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}function wd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;k=8-g|0;j=8-h|0;m=_(j,k)|0;j=_(j,g)|0;k=_(k,h)|0;i=_(h,g)|0;l=d>>1;if(i|0){if((f|0)<=0)return;h=l+1|0;g=0;while(1){p=_(e[c>>1]|0,m)|0;o=_(e[c+2>>1]|0,j)|0;d=c;c=c+(l<<1)|0;n=_(e[c>>1]|0,k)|0;b[a>>1]=((e[a>>1]|0)+1+((p+32+o+n+(_(e[d+(h<<1)>>1]|0,i)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else a=a+(l<<1)|0}return}d=j+k|0;if(!d){if((f|0)>0)g=0;else return;while(1){b[a>>1]=((e[a>>1]|0)+1+(((_(e[c>>1]|0,m)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}else{h=k|0?l:1;if((f|0)>0)g=0;else return;while(1){p=_(e[c>>1]|0,m)|0;b[a>>1]=((e[a>>1]|0)+1+((p+32+(_(e[c+(h<<1)>>1]|0,d)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(l<<1)|0;c=c+(l<<1)|0}}return}}function xd(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;k=8-g|0;j=8-h|0;l=_(j,k)|0;j=_(j,g)|0;k=_(k,h)|0;h=_(h,g)|0;if(h|0){if((f|0)<=0)return;i=e+1|0;g=0;while(1){p=_(d[c>>0]|0,l)|0;o=_(d[c+1>>0]|0,j)|0;m=c;c=c+e|0;n=_(d[c>>0]|0,k)|0;a[b>>0]=((d[b>>0]|0)+1+((p+32+o+n+(_(d[m+i>>0]|0,h)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return}i=j+k|0;if(!i){if((f|0)>0)g=0;else return;while(1){a[b>>0]=((d[b>>0]|0)+1+(((_(d[c>>0]|0,l)|0)+32|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}else{h=k|0?e:1;if((f|0)>0)g=0;else return;while(1){p=_(d[c>>0]|0,l)|0;a[b>>0]=((d[b>>0]|0)+1+((p+32+(_(d[c+h>>0]|0,i)|0)|0)>>>6)|0)>>>1;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;c=c+e|0}}return}}function yd(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0;f=i;i=i+16|0;e=f;g=(b+-9|0)>>>0<8;c[a+128>>2]=g?2:1;c[a+124>>2]=g?4:3;switch(b|0){case 9:{c[a+84>>2]=5;c[a+88>>2]=6;c[a+92>>2]=7;c[a+96>>2]=8;c[a+100>>2]=1;c[a+104>>2]=2;g=(d|0)<2;c[a+108>>2]=g?4:3;c[a+112>>2]=5;c[a+116>>2]=9;c[a+120>>2]=g?3:2;c[a>>2]=17;c[a+4>>2]=18;c[a+8>>2]=19;c[a+12>>2]=20;c[a+16>>2]=1;c[a+20>>2]=2;c[a+24>>2]=3;c[a+28>>2]=4;c[a+32>>2]=6;c[a+36>>2]=7;c[a+40>>2]=8;c[a+44>>2]=1;c[a+48>>2]=2;c[a+52>>2]=3;c[a+56>>2]=9;c[a+60>>2]=g?11:10;c[a+64>>2]=g?13:12;c[a+68>>2]=4;c[a+72>>2]=g?6:5;c[a+76>>2]=g?8:7;c[a+80>>2]=0;g=a+132|0;c[g>>2]=3;i=f;return}case 10:{c[a+84>>2]=10;c[a+88>>2]=11;c[a+92>>2]=12;c[a+96>>2]=13;c[a+100>>2]=14;c[a+104>>2]=15;g=(d|0)<2;c[a+108>>2]=g?17:16;c[a+112>>2]=18;c[a+116>>2]=14;c[a+120>>2]=g?5:4;c[a>>2]=21;c[a+4>>2]=22;c[a+8>>2]=23;c[a+12>>2]=24;c[a+16>>2]=5;c[a+20>>2]=6;c[a+24>>2]=7;c[a+28>>2]=8;c[a+32>>2]=19;c[a+36>>2]=20;c[a+40>>2]=21;c[a+44>>2]=9;c[a+48>>2]=10;c[a+52>>2]=11;c[a+56>>2]=22;c[a+60>>2]=g?24:23;c[a+64>>2]=g?26:25;c[a+68>>2]=12;c[a+72>>2]=g?14:13;c[a+76>>2]=g?16:15;c[a+80>>2]=0;g=a+132|0;c[g>>2]=3;i=f;return}case 12:{c[a+84>>2]=15;c[a+88>>2]=16;c[a+92>>2]=17;c[a+96>>2]=18;c[a+100>>2]=27;c[a+104>>2]=28;g=(d|0)<2;c[a+108>>2]=g?30:29;c[a+112>>2]=31;c[a+116>>2]=19;c[a+120>>2]=g?7:6;c[a>>2]=25;c[a+4>>2]=26;c[a+8>>2]=27;c[a+12>>2]=28;c[a+16>>2]=9;c[a+20>>2]=10;c[a+24>>2]=11;c[a+28>>2]=12;c[a+32>>2]=32;c[a+36>>2]=33;c[a+40>>2]=34;c[a+44>>2]=17;c[a+48>>2]=18;c[a+52>>2]=19;c[a+56>>2]=35;c[a+60>>2]=g?37:36;c[a+64>>2]=g?39:38;c[a+68>>2]=20;c[a+72>>2]=g?22:21;c[a+76>>2]=g?24:23;c[a+80>>2]=0;g=a+132|0;c[g>>2]=3;i=f;return}case 14:{c[a+84>>2]=20;c[a+88>>2]=21;c[a+92>>2]=22;c[a+96>>2]=23;c[a+100>>2]=40;c[a+104>>2]=41;g=(d|0)<2;c[a+108>>2]=g?43:42;c[a+112>>2]=44;c[a+116>>2]=24;c[a+120>>2]=g?9:8;c[a>>2]=29;c[a+4>>2]=30;c[a+8>>2]=31;c[a+12>>2]=32;c[a+16>>2]=13;c[a+20>>2]=14;c[a+24>>2]=15;c[a+28>>2]=16;c[a+32>>2]=45;c[a+36>>2]=46;c[a+40>>2]=47;c[a+44>>2]=25;c[a+48>>2]=26;c[a+52>>2]=27;c[a+56>>2]=48;c[a+60>>2]=g?50:49;c[a+64>>2]=g?52:51;c[a+68>>2]=28;c[a+72>>2]=g?30:29;c[a+76>>2]=g?32:31;c[a+80>>2]=0;g=a+132|0;c[g>>2]=3;i=f;return}default:{if((b|0)>=9){c[e>>2]=69173;c[e+4>>2]=69148;c[e+8>>2]=150;UC(0,0,104466,e);Ya()}c[a+84>>2]=25;c[a+88>>2]=26;c[a+92>>2]=27;c[a+96>>2]=28;c[a+100>>2]=53;c[a+104>>2]=54;g=(d|0)<2;c[a+108>>2]=g?56:55;c[a+112>>2]=57;c[a+116>>2]=29;c[a+120>>2]=g?11:10;c[a>>2]=33;c[a+4>>2]=34;c[a+8>>2]=35;c[a+12>>2]=36;c[a+16>>2]=17;c[a+20>>2]=18;c[a+24>>2]=19;c[a+28>>2]=20;c[a+32>>2]=58;c[a+36>>2]=59;c[a+40>>2]=60;c[a+44>>2]=33;c[a+48>>2]=34;c[a+52>>2]=35;c[a+56>>2]=61;c[a+60>>2]=g?63:62;c[a+64>>2]=g?65:64;c[a+68>>2]=36;c[a+72>>2]=g?38:37;c[a+76>>2]=g?40:39;c[a+80>>2]=0;g=a+132|0;c[g>>2]=3;i=f;return}}}function zd(a,d,f){a=a|0;d=d|0;f=f|0;var g=0;f=f>>>1;b[a>>1]=(e[a>>1]|0)+(c[d>>2]|0);g=a+2|0;b[g>>1]=(e[g>>1]|0)+(c[d+4>>2]|0);g=a+4|0;b[g>>1]=(e[g>>1]|0)+(c[d+8>>2]|0);g=a+6|0;b[g>>1]=(e[g>>1]|0)+(c[d+12>>2]|0);a=a+(f<<1)|0;b[a>>1]=(e[a>>1]|0)+(c[d+16>>2]|0);g=a+2|0;b[g>>1]=(e[g>>1]|0)+(c[d+20>>2]|0);g=a+4|0;b[g>>1]=(e[g>>1]|0)+(c[d+24>>2]|0);g=a+6|0;b[g>>1]=(e[g>>1]|0)+(c[d+28>>2]|0);a=a+(f<<1)|0;b[a>>1]=(e[a>>1]|0)+(c[d+32>>2]|0);g=a+2|0;b[g>>1]=(e[g>>1]|0)+(c[d+36>>2]|0);g=a+4|0;b[g>>1]=(e[g>>1]|0)+(c[d+40>>2]|0);g=a+6|0;b[g>>1]=(e[g>>1]|0)+(c[d+44>>2]|0);a=a+(f<<1)|0;b[a>>1]=(e[a>>1]|0)+(c[d+48>>2]|0);f=a+2|0;b[f>>1]=(e[f>>1]|0)+(c[d+52>>2]|0);f=a+4|0;b[f>>1]=(e[f>>1]|0)+(c[d+56>>2]|0);a=a+6|0;b[a>>1]=(e[a>>1]|0)+(c[d+60>>2]|0);a=d+64|0;do{b[d>>1]=0;d=d+2|0}while((d|0)<(a|0));return}function Ad(c,f,g){c=c|0;f=f|0;g=g|0;var h=0;a[c>>0]=(d[c>>0]|0)+(e[f>>1]|0);h=c+1|0;a[h>>0]=(d[h>>0]|0)+(e[f+2>>1]|0);h=c+2|0;a[h>>0]=(d[h>>0]|0)+(e[f+4>>1]|0);h=c+3|0;a[h>>0]=(d[h>>0]|0)+(e[f+6>>1]|0);c=c+g|0;a[c>>0]=(d[c>>0]|0)+(e[f+8>>1]|0);h=c+1|0;a[h>>0]=(d[h>>0]|0)+(e[f+10>>1]|0);h=c+2|0;a[h>>0]=(d[h>>0]|0)+(e[f+12>>1]|0);h=c+3|0;a[h>>0]=(d[h>>0]|0)+(e[f+14>>1]|0);c=c+g|0;a[c>>0]=(d[c>>0]|0)+(e[f+16>>1]|0);h=c+1|0;a[h>>0]=(d[h>>0]|0)+(e[f+18>>1]|0);h=c+2|0;a[h>>0]=(d[h>>0]|0)+(e[f+20>>1]|0);h=c+3|0;a[h>>0]=(d[h>>0]|0)+(e[f+22>>1]|0);c=c+g|0;a[c>>0]=(d[c>>0]|0)+(e[f+24>>1]|0);g=c+1|0;a[g>>0]=(d[g>>0]|0)+(e[f+26>>1]|0);g=c+2|0;a[g>>0]=(d[g>>0]|0)+(e[f+28>>1]|0);c=c+3|0;a[c>>0]=(d[c>>0]|0)+(e[f+30>>1]|0);c=f+32|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(c|0));return}function Bd(a,d,f){a=a|0;d=d|0;f=f|0;var g=0,h=0,i=0;h=f>>>1;f=0;g=d;while(1){b[a>>1]=(e[a>>1]|0)+(c[g>>2]|0);i=a+2|0;b[i>>1]=(e[i>>1]|0)+(c[g+4>>2]|0);i=a+4|0;b[i>>1]=(e[i>>1]|0)+(c[g+8>>2]|0);i=a+6|0;b[i>>1]=(e[i>>1]|0)+(c[g+12>>2]|0);i=a+8|0;b[i>>1]=(e[i>>1]|0)+(c[g+16>>2]|0);i=a+10|0;b[i>>1]=(e[i>>1]|0)+(c[g+20>>2]|0);i=a+12|0;b[i>>1]=(e[i>>1]|0)+(c[g+24>>2]|0);i=a+14|0;b[i>>1]=(e[i>>1]|0)+(c[g+28>>2]|0);f=f+1|0;if((f|0)==8)break;else{a=a+(h<<1)|0;g=g+32|0}}FF(d|0,0,256)|0;return}function Cd(c,f,g){c=c|0;f=f|0;g=g|0;var h=0,i=0,j=0;h=0;i=f;while(1){a[c>>0]=(d[c>>0]|0)+(e[i>>1]|0);j=c+1|0;a[j>>0]=(d[j>>0]|0)+(e[i+2>>1]|0);j=c+2|0;a[j>>0]=(d[j>>0]|0)+(e[i+4>>1]|0);j=c+3|0;a[j>>0]=(d[j>>0]|0)+(e[i+6>>1]|0);j=c+4|0;a[j>>0]=(d[j>>0]|0)+(e[i+8>>1]|0);j=c+5|0;a[j>>0]=(d[j>>0]|0)+(e[i+10>>1]|0);j=c+6|0;a[j>>0]=(d[j>>0]|0)+(e[i+12>>1]|0);j=c+7|0;a[j>>0]=(d[j>>0]|0)+(e[i+14>>1]|0);h=h+1|0;if((h|0)==8)break;else{c=c+g|0;i=i+16|0}}c=f+128|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(c|0));return}function Dd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+1;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+16|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+18|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+20|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+22|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+24|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+26|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+28|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+30|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Ed(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+1;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Fd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+1;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Gd(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+1;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=512)h=0-h>>31&511;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Hd(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<1|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+16>>1]|0,i)|0;j=a+16|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+18>>1]|0,i)|0;j=a+18|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+20>>1]|0,i)|0;j=a+20|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+22>>1]|0,i)|0;j=a+22|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+24>>1]|0,i)|0;j=a+24|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+26>>1]|0,i)|0;j=a+26|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+28>>1]|0,i)|0;j=a+28|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+30>>1]|0,i)|0;j=a+30|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Id(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<1|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Jd(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<1|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Kd(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<1|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=512)d=0-d>>31&511;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Ld(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;C=d>>1;B=f<<1;v=g<<1;w=0-C|0;x=_(C,-2)|0;y=_(C,-3)|0;z=C<<1;A=0;while(1){d=a[h+A>>0]|0;u=d<<24>>24<<1;if(d<<24>>24>=0){p=d<<24>>24==0;q=u|1;r=0-u|0;s=0;t=c;while(1){n=t+(w<<1)|0;m=e[n>>1]|0;f=t+(x<<1)|0;k=e[f>>1]|0;g=e[t+(y<<1)>>1]|0;o=e[t>>1]|0;i=t+(C<<1)|0;l=e[i>>1]|0;j=e[t+(z<<1)>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(B|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(p|d)d=d?u:q;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(r|0)?r:(d|0)>(u|0)?u:d)+k;d=q}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!p){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(r|0)?r:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=512)d=0-d>>31&511;b[n>>1]=d;d=o-f|0;if(d>>>0>=512)d=0-d>>31&511;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+2|0}}A=A+1|0;if((A|0)==4)break;else c=c+8|0}return}function Md(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<1;v=g<<1;w=z<<2;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<1;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=512)d=0-d>>31&511;b[n>>1]=d;d=o-f|0;if(d>>>0>=512)d=0-d>>31&511;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function Nd(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<1;v=g<<1;w=z<<1;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<1;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=512)d=0-d>>31&511;b[n>>1]=d;d=o-f|0;if(d>>>0>=512)d=0-d>>31&511;b[t>>1]=d}}}s=s+1|0;if((s|0)==2)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function Od(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;z=c>>1;y=d<<1;q=f<<1;r=_(z,-3)|0;s=_(z,-2)|0;t=0-z|0;u=z<<1;v=(y>>2)+2|0;w=_(z,-4)|0;p=z*3|0;x=0;while(1){c=a+(r<<1)|0;d=e[c>>1]|0;f=a+(s<<1)|0;g=e[f>>1]|0;h=a+(t<<1)|0;i=e[h>>1]|0;j=e[a>>1]|0;k=a+(z<<1)|0;l=e[k>>1]|0;m=a+(u<<1)|0;n=e[m>>1]|0;o=i-j|0;o=(o|0)>-1?o:0-o|0;do if((o|0)<(y|0)){A=g-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=l-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){if((o|0)>=(v|0)){b[h>>1]=(i+2+(g<<1)+l|0)>>>2;b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}A=d-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(w<<1)>>1]|0;b[h>>1]=(d+4+l+(i+g+j<<1)|0)>>>3;b[f>>1]=(d+2+g+i+j|0)>>>2;b[c>>1]=((d*3|0)+4+g+i+j+(A<<1)|0)>>>3}else b[h>>1]=(i+2+(g<<1)+l|0)>>>2;A=n-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(p<<1)>>1]|0;b[a>>1]=(g+4+n+(j+i+l<<1)|0)>>>3;b[k>>1]=(i+2+j+l+n|0)>>>2;b[m>>1]=(i+4+j+l+(n*3|0)+(A<<1)|0)>>>3;break}else{b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}}}}while(0);x=x+1|0;if((x|0)==16)break;else a=a+2|0}return}function Pd(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<1;p=f<<1;q=(s>>2)+2|0;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==16)break;else a=a+(t<<1)|0}return}function Qd(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<1;p=f<<1;q=(s>>2)+2|0;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==8)break;else a=a+(t<<1)|0}return}function Rd(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<1;r=g<<1;n=d>>1;o=0-n|0;p=_(n,-2)|0;q=0;while(1){f=a[h+q>>0]<<1;m=f+-1|0;if((f|0)>=2){l=1-f|0;j=c+(o<<1)|0;d=e[j>>1]|0;f=e[c+(p<<1)>>1]|0;i=e[c>>1]|0;g=e[c+(n<<1)>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(s|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[j>>1]=f;f=i-g|0;if(f>>>0>=512)f=0-f>>31&511;b[c>>1]=f}}}k=c+2|0;i=k+(o<<1)|0;d=e[i>>1]|0;f=e[k+(p<<1)>>1]|0;j=e[k>>1]|0;g=e[k+(n<<1)>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[i>>1]=f;f=j-g|0;if(f>>>0>=512)f=0-f>>31&511;b[k>>1]=f}}}}q=q+1|0;if((q|0)==4)break;else c=c+4|0}return}
function xc(f,g){f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0;W=i;i=i+16|0;T=W+8|0;A=W;J=g+21764|0;h=c[J>>2]|0;H=g+21768|0;k=c[H>>2]|0;D=g+21772|0;j=c[D>>2]|0;t=f+24668|0;L=c[(c[t>>2]|0)+(j<<2)>>2]|0;y=g+824|0;if(!(c[y>>2]|0))N=(c[f+26676>>2]|0)!=0;else N=0;u=16>>>(c[f+25976>>2]|0);s=f+26672|0;O=(c[s>>2]|0)==2;U=c[f+24624>>2]|0;V=f+25960|0;B=c[V>>2]|0;G=h<<B;E=g+21748|0;o=c[E>>2]|0;n=(c[U>>2]|0)+((_(o,k)|0)+G<<4)|0;G=G<<3;F=g+21752|0;m=_(_(u,k)|0,c[F>>2]|0)|0;l=(c[U+4>>2]|0)+G+m|0;m=(c[U+8>>2]|0)+G+m|0;G=f+12|0;B=n+(_(h<<2&12,o)|0)+(64<<B)|0;ob[c[G>>2]&1023](B,o,4);o=l+(_(c[J>>2]&7,c[F>>2]|0)|0)|0;ob[c[G>>2]&1023](o+(64<<c[V>>2])|0,m-l|0,2);o=g+23380|0;a[(c[f+197620>>2]|0)+j>>0]=c[o>>2];B=g+21796|0;j=c[E>>2]|0;a:do if(!(c[B>>2]|0)){c[g+21756>>2]=j;U=c[F>>2]|0;c[g+21760>>2]=U;Q=l;S=f+26260|0;P=m;M=n;K=j}else{q=j<<1;c[g+21756>>2]=q;h=c[F>>2]|0;r=h<<1;c[g+21760>>2]=r;p=f+26452|0;if(k&1){U=_(1-u|0,h)|0;l=l+U|0;m=m+U|0;n=n+(_(j,-15)|0)|0}if(!(c[f+197608>>2]|0)){Q=l;S=p;P=m;M=n;K=q;U=r}else{h=c[o>>2]|0;if(!h){Q=l;S=p;P=m;M=n;K=q;U=r}else{if(!(L&8))k=0;else{j=0;while(1){if(12288<<(j<<1)&L){U=g+44096+(j*40|0)+12|0;h=_((a[U>>0]|0)+16^c[H>>2]&1,16843009)|0;c[U>>2]=h;c[g+44096+(j*40|0)+20>>2]=h;c[g+44096+(j*40|0)+28>>2]=h;c[g+44096+(j*40|0)+36>>2]=h;h=c[o>>2]|0}j=j+1|0;if(j>>>0>=h>>>0){Q=l;S=p;P=m;M=n;K=q;U=r;break a}}}do{if(12288<<(k<<1)&L){h=g+44096+(k*40|0)+12|0;j=a[h>>0]|0;if(j<<24>>24>-1){U=(c[H>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=U;b[g+44096+(k*40|0)+20>>1]=U}h=g+44096+(k*40|0)+14|0;j=a[h>>0]|0;if(j<<24>>24>-1){U=(c[H>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=U;b[g+44096+(k*40|0)+22>>1]=U}h=g+44096+(k*40|0)+28|0;j=a[h>>0]|0;if(j<<24>>24>-1){U=(c[H>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=U;b[g+44096+(k*40|0)+36>>1]=U}h=g+44096+(k*40|0)+30|0;j=a[h>>0]|0;if(j<<24>>24>-1){U=(c[H>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=U;b[g+44096+(k*40|0)+38>>1]=U}h=c[o>>2]|0}k=k+1|0}while(k>>>0<h>>>0);Q=l;S=p;P=m;M=n;K=q;U=r}}}while(0);if(L&4|0){q=c[f+27844>>2]|0;l=g+43608|0;h=c[l>>2]|0;if(!(c[V>>2]|0)){k=M;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+K|0;h=(c[l>>2]|0)+16|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K<<1)|0;h=(c[l>>2]|0)+32|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*3|0)|0;h=(c[l>>2]|0)+48|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K<<2)|0;h=(c[l>>2]|0)+64|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*5|0)|0;h=(c[l>>2]|0)+80|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*6|0)|0;h=(c[l>>2]|0)+96|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*7|0)|0;h=(c[l>>2]|0)+112|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K<<3)|0;h=(c[l>>2]|0)+128|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*9|0)|0;h=(c[l>>2]|0)+144|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*10|0)|0;h=(c[l>>2]|0)+160|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*11|0)|0;h=(c[l>>2]|0)+176|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*12|0)|0;h=(c[l>>2]|0)+192|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*13|0)|0;h=(c[l>>2]|0)+208|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*14|0)|0;h=(c[l>>2]|0)+224|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));k=M+(K*15|0)|0;h=(c[l>>2]|0)+240|0;j=k+16|0;do{a[k>>0]=a[h>>0]|0;k=k+1|0;h=h+1|0}while((k|0)<(j|0));if(!(c[s>>2]|0)){V=1<<q+-1&255;FF(Q|0,V|0,8)|0;FF(P|0,V|0,8)|0;FF(Q+U|0,V|0,8)|0;FF(P+U|0,V|0,8)|0;g=U<<1;FF(Q+g|0,V|0,8)|0;FF(P+g|0,V|0,8)|0;g=U*3|0;FF(Q+g|0,V|0,8)|0;FF(P+g|0,V|0,8)|0;g=U<<2;FF(Q+g|0,V|0,8)|0;FF(P+g|0,V|0,8)|0;g=U*5|0;FF(Q+g|0,V|0,8)|0;FF(P+g|0,V|0,8)|0;g=U*6|0;FF(Q+g|0,V|0,8)|0;FF(P+g|0,V|0,8)|0;U=U*7|0;FF(Q+U|0,V|0,8)|0;FF(P+U|0,V|0,8)|0;i=W;return}h=(c[l>>2]|0)+256|0;j=h+(u<<3)|0;if((u|0)>0)k=0;else{i=W;return}do{V=_(k,U)|0;g=k<<3;T=h+g|0;O=T;O=d[O>>0]|d[O+1>>0]<<8|d[O+2>>0]<<16|d[O+3>>0]<<24;T=T+4|0;T=d[T>>0]|d[T+1>>0]<<8|d[T+2>>0]<<16|d[T+3>>0]<<24;S=Q+V|0;R=S;a[R>>0]=O;a[R+1>>0]=O>>8;a[R+2>>0]=O>>16;a[R+3>>0]=O>>24;S=S+4|0;a[S>>0]=T;a[S+1>>0]=T>>8;a[S+2>>0]=T>>16;a[S+3>>0]=T>>24;g=j+g|0;S=g;S=d[S>>0]|d[S+1>>0]<<8|d[S+2>>0]<<16|d[S+3>>0]<<24;g=g+4|0;g=d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24;V=P+V|0;T=V;a[T>>0]=S;a[T+1>>0]=S>>8;a[T+2>>0]=S>>16;a[T+3>>0]=S>>24;V=V+4|0;a[V>>0]=g;a[V+1>>0]=g>>8;a[V+2>>0]=g>>16;a[V+3>>0]=g>>24;k=k+1|0}while((k|0)!=(u|0));i=W;return}m=c[s>>2]|0;n=_(e[44292+(m<<1)>>1]|0,q)|0;o=(h|0)!=0&n>>>0<2147483640;p=o?h:0;n=o?n+8|0:8;o=32-q|0;h=0;l=0;do{k=M+(_(l,K)|0)|0;j=0;do{V=p+(h>>>3)|0;V=(MF(d[V>>0]|d[V+1>>0]<<8|d[V+2>>0]<<16|d[V+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[k+(j<<1)>>1]=V;j=j+1|0}while((j|0)!=16);l=l+1|0}while((l|0)!=16);k=(u|0)>0;if(!m){if(!k){i=W;return}h=1<<q+-1&65535;j=0;do{g=_(j,U)|0;V=Q+g|0;g=P+g|0;b[g>>1]=h;b[V>>1]=h;b[g+2>>1]=h;b[V+2>>1]=h;b[g+4>>1]=h;b[V+4>>1]=h;b[g+6>>1]=h;b[V+6>>1]=h;b[g+8>>1]=h;b[V+8>>1]=h;b[g+10>>1]=h;b[V+10>>1]=h;b[g+12>>1]=h;b[V+12>>1]=h;b[g+14>>1]=h;b[V+14>>1]=h;j=j+1|0}while((j|0)!=(u|0));i=W;return}if(k)j=0;else{i=W;return}do{V=Q+(_(j,U)|0)|0;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+2>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+4>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+6>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+8>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+10>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+12>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+14>>1]=g;j=j+1|0}while((j|0)!=(u|0));if(k)j=0;else{i=W;return}while(1){V=P+(_(j,U)|0)|0;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+2>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+4>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+6>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+8>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+10>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;h=n>>>0>h>>>0?h:n;b[V+12>>1]=g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7)>>>o;h=h+q|0;b[V+14>>1]=g;j=j+1|0;if((j|0)==(u|0))break;else h=n>>>0>h>>>0?h:n}i=W;return}I=(L&7|0)!=0;b:do if(I){z=g+844|0;j=c[z>>2]|0;c:do if(j|0){w=c[V>>2]|0;do if(!(c[f+197608>>2]|0))h=1;else{h=(c[g+21800>>2]|0)!=0;if(!(c[H>>2]&1)){h=h&1^1;break}else if(h){h=1;break}else break c}while(0);if((j|0)==2){u=c[J>>2]|0;j=c[g+21708>>2]|0;k=(e[(c[f+197604>>2]|0)+((c[D>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1}else{k=c[J>>2]|0;u=k;j=(c[H>>2]|0)>((c[B>>2]|0)!=0|0)&1;k=(k|0)>0&1}p=M+(0-(K+1+w))|0;v=0-(U+1+w)|0;s=Q+v|0;v=P+v|0;r=u+-1|0;q=g+43624+(h<<2)|0;t=c[q>>2]|0;m=t+(u*96|0)|0;if(j|0){o=(k|0)!=0;n=(w|0)==0;do if(o){h=(8<<w)+(t+(r*96|0))|0;G=h;F=c[G>>2]|0;G=c[G+4>>2]|0;j=p+(-7<<w)|0;x=j;E=c[x+4>>2]|0;R=h;c[R>>2]=c[x>>2];c[R+4>>2]=E;R=j;c[R>>2]=F;c[R+4>>2]=G;if(n){h=m;k=p+1|0;j=c[h>>2]|0;h=c[h+4>>2]|0;R=52;break}else{F=h+8|0;k=F;l=c[k>>2]|0;k=c[k+4>>2]|0;h=j+8|0;G=h;R=c[G+4>>2]|0;j=F;c[j>>2]=c[G>>2];c[j+4>>2]=R;c[h>>2]=l;c[h+4>>2]=k;h=m;k=1<<w;j=k;k=p+k|0;l=c[h>>2]|0;h=c[h+4>>2]|0;R=51;break}}else{h=m;l=c[h>>2]|0;h=c[h+4>>2]|0;j=1<<w;k=p+j|0;if(n){j=l;R=52}else R=51}while(0);if((R|0)==51){F=k;G=c[F+4>>2]|0;E=m;c[E>>2]=c[F>>2];c[E+4>>2]=G;E=k;c[E>>2]=l;c[E+4>>2]=h;E=t+(u*96|0)+8|0;G=E;F=c[G>>2]|0;G=c[G+4>>2]|0;k=p+j+8|0;n=k;x=c[n+4>>2]|0;c[E>>2]=c[n>>2];c[E+4>>2]=x;c[k>>2]=F;c[k+4>>2]=G;k=1}else if((R|0)==52){E=k;F=c[E+4>>2]|0;G=m;c[G>>2]=c[E>>2];c[G+4>>2]=F;c[k>>2]=j;c[k+4>>2]=h;k=0}h=(8<<w)+(t+(u*96|0))|0;F=h;E=c[F>>2]|0;F=c[F+4>>2]|0;j=p+(9<<w)|0;n=j;x=c[n+4>>2]|0;G=h;c[G>>2]=c[n>>2];c[G+4>>2]=x;G=j;c[G>>2]=E;c[G+4>>2]=F;if(k){x=h+8|0;F=x;E=c[F>>2]|0;F=c[F+4>>2]|0;G=j+8|0;m=G;n=c[m+4>>2]|0;c[x>>2]=c[m>>2];c[x+4>>2]=n;c[G>>2]=E;c[G+4>>2]=F}h=(c[J>>2]|0)+1|0;if((h|0)<(c[f+198232>>2]|0)){G=(c[q>>2]|0)+(h*96|0)|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;h=p+(17<<w)|0;p=h;x=c[p+4>>2]|0;c[G>>2]=c[p>>2];c[G+4>>2]=x;G=h;c[G>>2]=E;c[G+4>>2]=F;if(k){x=(c[q>>2]|0)+(((c[J>>2]|0)+1|0)*96|0)+8|0;F=x;E=c[F>>2]|0;F=c[F+4>>2]|0;G=h+8|0;p=G;q=c[p+4>>2]|0;c[x>>2]=c[p>>2];c[x+4>>2]=q;c[G>>2]=E;c[G+4>>2]=F}}m=16<<w;if(o){h=t+(r*96|0)+m|0;F=h;E=c[F>>2]|0;F=c[F+4>>2]|0;l=-7<<w;j=s+l|0;q=j;x=c[q+4>>2]|0;G=h;c[G>>2]=c[q>>2];c[G+4>>2]=x;G=j;c[G>>2]=E;c[G+4>>2]=F;if(k){x=h+8|0;F=x;E=c[F>>2]|0;F=c[F+4>>2]|0;G=j+8|0;p=G;q=c[p+4>>2]|0;c[x>>2]=c[p>>2];c[x+4>>2]=q;c[G>>2]=E;c[G+4>>2]=F}j=(24<<w)+(t+(r*96|0))|0;F=j;E=c[F>>2]|0;F=c[F+4>>2]|0;h=v+l|0;r=h;x=c[r+4>>2]|0;G=j;c[G>>2]=c[r>>2];c[G+4>>2]=x;G=h;c[G>>2]=E;c[G+4>>2]=F;if(k){E=j+8|0;G=E;F=c[G>>2]|0;G=c[G+4>>2]|0;k=h+8|0;r=k;x=c[r+4>>2]|0;c[E>>2]=c[r>>2];c[E+4>>2]=x;c[k>>2]=F;c[k+4>>2]=G;k=1}else k=0}j=t+(u*96|0)+m|0;F=j;E=c[F>>2]|0;F=c[F+4>>2]|0;h=s+1+w|0;s=h;x=c[s+4>>2]|0;G=j;c[G>>2]=c[s>>2];c[G+4>>2]=x;G=h;c[G>>2]=E;c[G+4>>2]=F;if(k){x=j+8|0;F=x;E=c[F>>2]|0;F=c[F+4>>2]|0;G=h+8|0;r=G;s=c[r+4>>2]|0;c[x>>2]=c[r>>2];c[x+4>>2]=s;c[G>>2]=E;c[G+4>>2]=F}j=(24<<w)+(t+(u*96|0))|0;F=j;E=c[F>>2]|0;F=c[F+4>>2]|0;h=v+1+w|0;w=h;x=c[w+4>>2]|0;G=j;c[G>>2]=c[w>>2];c[G+4>>2]=x;G=h;c[G>>2]=E;c[G+4>>2]=F;if(k){x=j+8|0;F=x;E=c[F>>2]|0;F=c[F+4>>2]|0;G=h+8|0;v=G;w=c[v+4>>2]|0;c[x>>2]=c[v>>2];c[x+4>>2]=w;c[G>>2]=E;c[G+4>>2]=F}}}while(0);x=g+21632|0;jb[c[f+26128+(c[x>>2]<<2)>>2]&255](Q,U);jb[c[f+26128+(c[x>>2]<<2)>>2]&255](P,U);x=c[V>>2]|0;h=c[y>>2]|0;d:do if(!(L&1)){jb[c[f+26172+(c[g+21636>>2]<<2)>>2]&255](M,K);if(a[g+43648>>0]|0){if(!N){ob[c[f+132>>2]&1023](g+44384|0,g+47456|0,c[(c[f+197556>>2]|0)+(h<<6)>>2]|0);break}h=g+44384|0;j=g+47456|0;if(!x){b[h>>1]=b[j>>1]|0;b[g+44416>>1]=b[g+47458>>1]|0;b[g+44512>>1]=b[g+47460>>1]|0;b[g+44544>>1]=b[g+47462>>1]|0;b[g+44448>>1]=b[g+47464>>1]|0;b[g+44480>>1]=b[g+47466>>1]|0;b[g+44576>>1]=b[g+47468>>1]|0;b[g+44608>>1]=b[g+47470>>1]|0;b[g+44640>>1]=b[g+47472>>1]|0;b[g+44672>>1]=b[g+47474>>1]|0;b[g+44768>>1]=b[g+47476>>1]|0;b[g+44800>>1]=b[g+47478>>1]|0;b[g+44704>>1]=b[g+47480>>1]|0;b[g+44736>>1]=b[g+47482>>1]|0;b[g+44832>>1]=b[g+47484>>1]|0;b[g+44864>>1]=b[g+47486>>1]|0;break}else{c[h>>2]=c[j>>2];c[g+44448>>2]=c[g+47460>>2];c[g+44640>>2]=c[g+47464>>2];c[g+44704>>2]=c[g+47468>>2];c[g+44512>>2]=c[g+47472>>2];c[g+44576>>2]=c[g+47476>>2];c[g+44768>>2]=c[g+47480>>2];c[g+44832>>2]=c[g+47484>>2];c[g+44896>>2]=c[g+47488>>2];c[g+44960>>2]=c[g+47492>>2];c[g+45152>>2]=c[g+47496>>2];c[g+45216>>2]=c[g+47500>>2];c[g+45024>>2]=c[g+47504>>2];c[g+45088>>2]=c[g+47508>>2];c[g+45280>>2]=c[g+47512>>2];c[g+45344>>2]=c[g+47516>>2];break}}}else{if(L&16777216|0){if(!N){l=c[f+112>>2]|0;m=c[f+104>>2]|0;n=g+21732|0;o=g+21740|0;p=(x|0)==0;q=g+44384|0;r=0;while(1){k=M+(c[S+(r<<2)>>2]|0)|0;F=d[96537+r>>0]|0;G=a[g+43648+F>>0]|0;ub[c[f+26080+(a[g+21640+F>>0]<<24>>24<<2)>>2]&255](k,c[n>>2]<<r&32768,c[o>>2]<<r&16384,K);switch(G<<24>>24){case 0:break;case 1:{h=r<<4;if(p)j=e[g+44384+(h<<1)>>1]|0;else j=c[q+(h<<2)>>2]|0;if(!j)R=92;else ob[l&1023](k,g+44384+(h<<x<<1)|0,K);break}default:{h=r<<4;R=92}}if((R|0)==92){R=0;ob[m&1023](k,g+44384+(h<<x<<1)|0,K)}r=r+4|0;if((r|0)>=16)break d}}k=c[f+140>>2]|0;l=f+26664|0;m=f+198220|0;n=g+21732|0;o=g+21740|0;p=(x|0)==0;q=g+44384|0;s=0;while(1){r=M+(c[S+(s<<2)>>2]|0)|0;j=d[96537+s>>0]|0;R=a[g+21640+j>>0]|0;h=R<<24>>24;e:do if(R<<24>>24<2&(c[l>>2]|0)==244)if((c[m>>2]|0)==-1){fb[c[f+26224+(h<<2)>>2]&127](r,g+44384+(s<<4<<x<<1)|0,c[n>>2]<<s&32768,c[o>>2]<<s&16384,K);break}else{ob[c[f+26216+(h<<2)>>2]&1023](r,g+44384+(s<<4<<x<<1)|0,K);break}else{R=a[g+43648+j>>0]|0;ub[c[f+26080+(h<<2)>>2]&255](r,c[n>>2]<<s&32768,c[o>>2]<<s&16384,K);switch(R<<24>>24){case 0:break e;case 1:{h=s<<4;if(p)j=e[g+44384+(h<<1)>>1]|0;else j=c[q+(h<<2)>>2]|0;if(j){ob[k&1023](r,g+44384+(h<<x<<1)|0,K);break e}break}default:h=s<<4}ob[k&1023](r,g+44384+(h<<x<<1)|0,K)}while(0);s=s+4|0;if((s|0)>=16)break d}}if(N){w=c[f+144>>2]|0;v=w}else{v=c[f+100>>2]|0;w=c[f+108>>2]|0}l=f+26664|0;o=g+21740|0;p=(x|0)==0;q=3-K|0;r=3-((K|0)/2|0)|0;s=4<<x;t=0-K|0;u=g+44384|0;if(N)n=0;else{m=0;while(1){l=M+(c[S+(m<<2)>>2]|0)|0;h=d[96537+m>>0]|0;G=a[g+21640+h>>0]|0;j=G<<24>>24;do if((G|4)<<24>>24==7){if(c[o>>2]&32768>>>m|0){k=l+s+t|0;break}if(p){c[T>>2]=_(d[l+q>>0]|0,16843009)|0;k=T;break}else{G=RF(e[l+(r<<1)>>1]|0,0,65537,65537)|0;k=A;c[k>>2]=G;c[k+4>>2]=C;k=A;break}}else k=0;while(0);ob[c[f+26020+(j<<2)>>2]&1023](l,k,K);switch(a[g+43648+h>>0]|0){case 0:break;case 1:{h=m<<4;if(p)j=e[g+44384+(h<<1)>>1]|0;else j=c[u+(h<<2)>>2]|0;if(!j)R=129;else ob[w&1023](l,g+44384+(h<<x<<1)|0,K);break}default:{h=m<<4;R=129}}if((R|0)==129){R=0;ob[v&1023](l,g+44384+(h<<x<<1)|0,K)}m=m+1|0;if((m|0)==16)break d}}do{m=M+(c[S+(n<<2)>>2]|0)|0;j=d[96537+n>>0]|0;h=a[g+21640+j>>0]|0;k=h<<24>>24;if(h<<24>>24<2&(c[l>>2]|0)==244)ob[c[f+26208+(k<<2)>>2]&1023](m,g+44384+(n<<4<<x<<1)|0,K);else{do if((h|4)<<24>>24==7){if(c[o>>2]&32768>>>n|0){h=m+s+t|0;break}if(p){c[T>>2]=_(d[m+q>>0]|0,16843009)|0;h=T;break}else{G=RF(e[m+(r<<1)>>1]|0,0,65537,65537)|0;h=A;c[h>>2]=G;c[h+4>>2]=C;h=A;break}}else h=0;while(0);ob[c[f+26020+(k<<2)>>2]&1023](m,h,K);switch(a[g+43648+j>>0]|0){case 0:break;case 1:{h=n<<4;if(p)j=e[g+44384+(h<<1)>>1]|0;else j=c[u+(h<<2)>>2]|0;if(!j)R=112;else ob[w&1023](m,g+44384+(h<<x<<1)|0,K);break}default:{h=n<<4;R=112}}if((R|0)==112){R=0;ob[v&1023](m,g+44384+(h<<x<<1)|0,K)}}n=n+1|0}while((n|0)!=16)}while(0);j=c[z>>2]|0;if(!j)R=208;else{v=c[V>>2]|0;do if(!(c[f+197608>>2]|0))k=1;else{h=(c[g+21800>>2]|0)!=0;if(!(c[H>>2]&1)){k=h&1^1;break}else if(h){k=1;break}else{R=208;break b}}while(0);if((j|0)==2){u=c[J>>2]|0;j=c[g+21708>>2]|0;h=(e[(c[f+197604>>2]|0)+((c[D>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1}else{h=c[J>>2]|0;u=h;j=(c[H>>2]|0)>((c[B>>2]|0)!=0|0)&1;h=(h|0)>0&1}o=M+(0-(K+1+v))|0;t=0-(U+1+v)|0;r=Q+t|0;t=P+t|0;q=u+-1|0;p=g+43624+(k<<2)|0;s=c[p>>2]|0;k=s+(u*96|0)|0;if(!j)R=208;else{n=(h|0)!=0;m=(v|0)==0;do if(n){h=(8<<v)+(s+(q*96|0))|0;H=h;G=c[H>>2]|0;H=c[H+4>>2]|0;j=o+(-7<<v)|0;E=j;F=c[E+4>>2]|0;R=h;c[R>>2]=c[E>>2];c[R+4>>2]=F;R=j;c[R>>2]=G;c[R+4>>2]=H;if(m){h=k;k=o+1|0;j=c[h>>2]|0;h=c[h+4>>2]|0;R=152;break}else{F=h+8|0;l=F;R=c[l>>2]|0;l=c[l+4>>2]|0;h=j+8|0;G=h;H=c[G+4>>2]|0;j=F;c[j>>2]=c[G>>2];c[j+4>>2]=H;c[h>>2]=R;c[h+4>>2]=l;h=k;k=1<<v;j=k;k=o+k|0;l=c[h>>2]|0;h=c[h+4>>2]|0;R=151;break}}else{h=k;l=c[h>>2]|0;h=c[h+4>>2]|0;j=1<<v;k=o+j|0;if(m){j=l;R=152}else R=151}while(0);if((R|0)==151){H=k;c[H>>2]=l;c[H+4>>2]=h;H=s+(u*96|0)+8|0;R=c[H+4>>2]|0;k=o+j+8|0;c[k>>2]=c[H>>2];c[k+4>>2]=R;k=1}else if((R|0)==152){c[k>>2]=j;c[k+4>>2]=h;k=0}h=(8<<v)+(s+(u*96|0))|0;H=h;G=c[H>>2]|0;H=c[H+4>>2]|0;j=o+(9<<v)|0;E=j;F=c[E+4>>2]|0;R=h;c[R>>2]=c[E>>2];c[R+4>>2]=F;R=j;c[R>>2]=G;c[R+4>>2]=H;if(k){F=h+8|0;H=F;G=c[H>>2]|0;H=c[H+4>>2]|0;R=j+8|0;D=R;E=c[D+4>>2]|0;c[F>>2]=c[D>>2];c[F+4>>2]=E;c[R>>2]=G;c[R+4>>2]=H}h=(c[J>>2]|0)+1|0;if((h|0)<(c[f+198232>>2]|0)){R=(c[p>>2]|0)+(h*96|0)|0;H=R;G=c[H>>2]|0;H=c[H+4>>2]|0;h=o+(17<<v)|0;E=h;F=c[E+4>>2]|0;c[R>>2]=c[E>>2];c[R+4>>2]=F;R=h;c[R>>2]=G;c[R+4>>2]=H;if(k){G=(c[p>>2]|0)+(((c[J>>2]|0)+1|0)*96|0)+8|0;J=G;H=c[J>>2]|0;J=c[J+4>>2]|0;R=h+8|0;E=R;F=c[E+4>>2]|0;c[G>>2]=c[E>>2];c[G+4>>2]=F;c[R>>2]=H;c[R+4>>2]=J}}m=16<<v;if(n){h=s+(q*96|0)+m|0;J=h;H=c[J>>2]|0;J=c[J+4>>2]|0;l=-7<<v;j=r+l|0;F=j;G=c[F+4>>2]|0;R=h;c[R>>2]=c[F>>2];c[R+4>>2]=G;R=j;c[R>>2]=H;c[R+4>>2]=J;if(k){G=h+8|0;J=G;H=c[J>>2]|0;J=c[J+4>>2]|0;R=j+8|0;E=R;F=c[E+4>>2]|0;c[G>>2]=c[E>>2];c[G+4>>2]=F;c[R>>2]=H;c[R+4>>2]=J}j=(24<<v)+(s+(q*96|0))|0;J=j;H=c[J>>2]|0;J=c[J+4>>2]|0;h=t+l|0;F=h;G=c[F+4>>2]|0;R=j;c[R>>2]=c[F>>2];c[R+4>>2]=G;R=h;c[R>>2]=H;c[R+4>>2]=J;if(k){H=j+8|0;R=H;J=c[R>>2]|0;R=c[R+4>>2]|0;k=h+8|0;F=k;G=c[F+4>>2]|0;c[H>>2]=c[F>>2];c[H+4>>2]=G;c[k>>2]=J;c[k+4>>2]=R;k=1}else k=0}j=s+(u*96|0)+m|0;J=j;H=c[J>>2]|0;J=c[J+4>>2]|0;h=r+1+v|0;F=h;G=c[F+4>>2]|0;R=j;c[R>>2]=c[F>>2];c[R+4>>2]=G;R=h;c[R>>2]=H;c[R+4>>2]=J;if(k){G=j+8|0;J=G;H=c[J>>2]|0;J=c[J+4>>2]|0;R=h+8|0;E=R;F=c[E+4>>2]|0;c[G>>2]=c[E>>2];c[G+4>>2]=F;c[R>>2]=H;c[R+4>>2]=J}j=(24<<v)+(s+(u*96|0))|0;J=j;H=c[J>>2]|0;J=c[J+4>>2]|0;h=t+1+v|0;F=h;G=c[F+4>>2]|0;R=j;c[R>>2]=c[F>>2];c[R+4>>2]=G;R=h;c[R>>2]=H;c[R+4>>2]=J;if(k){G=j+8|0;J=G;H=c[J>>2]|0;J=c[J+4>>2]|0;R=h+8|0;E=R;F=c[E+4>>2]|0;c[G>>2]=c[E>>2];c[G+4>>2]=F;c[R>>2]=H;c[R+4>>2]=J;R=208}else R=208}}}else{A=c[f+203640>>2]|0;l=f+152|0;B=c[f+203644>>2]|0;m=f+168|0;n=f+16|0;o=f+32|0;D=c[(c[t>>2]|0)+(c[D>>2]<<2)>>2]|0;h=c[V>>2]|0;j=a[g+44108>>0]|0;k=j<<24>>24>-1;if(O){if(k){y=j<<24>>24;z=c[g+43824>>2]|0;w=c[J>>2]|0;x=(w<<4|8)+(z<<16>>18)|0;z=(c[H>>2]<<4)+(z>>18)|0;w=(x<<h)+(64<<h)+(_(z+(w<<2&12)|0,c[g+21756>>2]|0)|0)|0;ob[c[G>>2]&1023]((c[g+23384+(y*40|0)>>2]|0)+w|0,c[E>>2]|0,4);x=(_((c[J>>2]&7)+(z>>1)|0,c[F>>2]|0)|0)+((x>>1)+64<<h)|0;z=c[g+23384+(y*40|0)+4>>2]|0;ob[c[G>>2]&1023](z+x|0,(c[g+23384+(y*40|0)+8>>2]|0)-z|0,2)}do if(!(D&8)){if(D&16|0){A=A+64|0;B=B+64|0;yc(f,g,0,0,8,8<<c[V>>2],M,Q,P,0,0,A,c[l>>2]|0,B,c[m>>2]|0,n,o,D&4096,D&16384);yc(f,g,8,0,8,8<<c[V>>2],M,Q,P,0,4,A,c[l>>2]|0,B,c[m>>2]|0,n,o,D&8192,D&32768);break}p=g+21756|0;if(D&32|0){w=A+64|0;x=f+156|0;y=B+64|0;z=f+172|0;A=f+20|0;B=f+36|0;yc(f,g,0,0,16,c[p>>2]<<3,M,Q,P,0,0,w,c[x>>2]|0,y,c[z>>2]|0,A,B,D&4096,D&16384);yc(f,g,4,0,16,c[p>>2]<<3,M,Q,P,4,0,w,c[x>>2]|0,y,c[z>>2]|0,A,B,D&8192,D&32768);break}q=A+128|0;r=f+160|0;s=B+128|0;t=f+176|0;u=f+24|0;v=f+40|0;w=f+156|0;x=f+172|0;y=f+20|0;z=f+36|0;o=A+64|0;h=B+64|0;n=0;do{j=e[g+44376+(n<<1)>>1]|0;k=n<<2;l=k&4;m=n<<1&4;do if(!(j&8)){if(j&16|0){A=j&4096;B=j&16384;yc(f,g,k,0,4,4<<c[V>>2],M,Q,P,l,m,q,c[w>>2]|0,s,c[x>>2]|0,y,z,A,B);yc(f,g,k|2,0,4,4<<c[V>>2],M,Q,P,l,m|2,q,c[w>>2]|0,s,c[x>>2]|0,y,z,A,B);break}if(!(j&32)){A=j&4096;B=j&16384;yc(f,g,k,1,4,0,M,Q,P,l,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);j=l|2;yc(f,g,k|1,1,4,0,M,Q,P,j,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);m=m|2;yc(f,g,k|2,1,4,0,M,Q,P,l,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);yc(f,g,k|3,1,4,0,M,Q,P,j,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);break}else{A=j&4096;B=j&16384;yc(f,g,k,0,8,c[p>>2]<<2,M,Q,P,l,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);yc(f,g,k|1,0,8,c[p>>2]<<2,M,Q,P,l|2,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);break}}else yc(f,g,k,1,8,0,M,Q,P,l,m,o,c[w>>2]|0,h,c[x>>2]|0,y,z,j&4096,j&16384);while(0);n=n+1|0}while((n|0)!=4)}else yc(f,g,0,1,16,0,M,Q,P,0,0,A,c[l>>2]|0,B,c[m>>2]|0,n,o,D&4096,D&16384);while(0);if(!(D&49152)){R=208;break}h=c[V>>2]|0;j=a[g+44148>>0]|0;if(j<<24>>24<=-1){m=h;break}D=j<<24>>24;A=c[g+43984>>2]|0;B=c[J>>2]|0;R=(B<<4|8)+(A<<16>>18)|0;H=(c[H>>2]<<4)+(A>>18)|0;B=(R<<h)+(64<<h)+(_(H+(B<<2&12)|0,c[g+21756>>2]|0)|0)|0;ob[c[G>>2]&1023]((c[g+25304+(D*40|0)>>2]|0)+B|0,c[E>>2]|0,4);J=(_((c[J>>2]&7)+(H>>1)|0,c[F>>2]|0)|0)+((R>>1)+64<<h)|0;R=c[g+25304+(D*40|0)+4>>2]|0;ob[c[G>>2]&1023](R+J|0,(c[g+25304+(D*40|0)+8>>2]|0)-R|0,2);R=208;break}else{if(k){y=j<<24>>24;z=c[g+43824>>2]|0;w=c[J>>2]|0;x=(w<<4|8)+(z<<16>>18)|0;z=(c[H>>2]<<4)+(z>>18)|0;w=(x<<h)+(64<<h)+(_(z+(w<<2&12)|0,c[g+21756>>2]|0)|0)|0;ob[c[G>>2]&1023]((c[g+23384+(y*40|0)>>2]|0)+w|0,c[E>>2]|0,4);x=(_((c[J>>2]&7)+(z>>1)|0,c[F>>2]|0)|0)+((x>>1)+64<<h)|0;z=c[g+23384+(y*40|0)+4>>2]|0;ob[c[G>>2]&1023](z+x|0,(c[g+23384+(y*40|0)+8>>2]|0)-z|0,2)}do if(!(D&8)){if(D&16|0){A=A+64|0;B=B+64|0;zc(f,g,0,0,8,8<<c[V>>2],M,Q,P,0,0,A,c[l>>2]|0,B,c[m>>2]|0,n,o,D&4096,D&16384);zc(f,g,8,0,8,8<<c[V>>2],M,Q,P,0,4,A,c[l>>2]|0,B,c[m>>2]|0,n,o,D&8192,D&32768);break}p=g+21756|0;if(D&32|0){w=A+64|0;x=f+156|0;y=B+64|0;z=f+172|0;A=f+20|0;B=f+36|0;zc(f,g,0,0,16,c[p>>2]<<3,M,Q,P,0,0,w,c[x>>2]|0,y,c[z>>2]|0,A,B,D&4096,D&16384);zc(f,g,4,0,16,c[p>>2]<<3,M,Q,P,4,0,w,c[x>>2]|0,y,c[z>>2]|0,A,B,D&8192,D&32768);break}q=A+128|0;r=f+160|0;s=B+128|0;t=f+176|0;u=f+24|0;v=f+40|0;w=f+156|0;x=f+172|0;y=f+20|0;z=f+36|0;o=A+64|0;h=B+64|0;n=0;do{j=e[g+44376+(n<<1)>>1]|0;k=n<<2;l=k&4;m=n<<1&4;do if(!(j&8)){if(j&16|0){A=j&4096;B=j&16384;zc(f,g,k,0,4,4<<c[V>>2],M,Q,P,l,m,q,c[w>>2]|0,s,c[x>>2]|0,y,z,A,B);zc(f,g,k|2,0,4,4<<c[V>>2],M,Q,P,l,m|2,q,c[w>>2]|0,s,c[x>>2]|0,y,z,A,B);break}if(!(j&32)){A=j&4096;B=j&16384;zc(f,g,k,1,4,0,M,Q,P,l,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);j=l|2;zc(f,g,k|1,1,4,0,M,Q,P,j,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);m=m|2;zc(f,g,k|2,1,4,0,M,Q,P,l,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);zc(f,g,k|3,1,4,0,M,Q,P,j,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);break}else{A=j&4096;B=j&16384;zc(f,g,k,0,8,c[p>>2]<<2,M,Q,P,l,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);zc(f,g,k|1,0,8,c[p>>2]<<2,M,Q,P,l|2,m,q,c[r>>2]|0,s,c[t>>2]|0,u,v,A,B);break}}else zc(f,g,k,1,8,0,M,Q,P,l,m,o,c[w>>2]|0,h,c[x>>2]|0,y,z,j&4096,j&16384);while(0);n=n+1|0}while((n|0)!=4)}else zc(f,g,0,1,16,0,M,Q,P,0,0,A,c[l>>2]|0,B,c[m>>2]|0,n,o,D&4096,D&16384);while(0);if(!(D&49152)){R=208;break}h=c[V>>2]|0;j=a[g+44148>>0]|0;if(j<<24>>24<=-1){m=h;break}D=j<<24>>24;A=c[g+43984>>2]|0;B=c[J>>2]|0;R=(B<<4|8)+(A<<16>>18)|0;H=(c[H>>2]<<4)+(A>>18)|0;B=(R<<h)+(64<<h)+(_(H+(B<<2&12)|0,c[g+21756>>2]|0)|0)|0;ob[c[G>>2]&1023]((c[g+25304+(D*40|0)>>2]|0)+B|0,c[E>>2]|0,4);J=(_((c[J>>2]&7)+(H>>1)|0,c[F>>2]|0)|0)+((R>>1)+64<<h)|0;R=c[g+25304+(D*40|0)+4>>2]|0;ob[c[G>>2]&1023](R+J|0,(c[g+25304+(D*40|0)+8>>2]|0)-R|0,2);R=208;break}}while(0);if((R|0)==208)m=c[V>>2]|0;f:do if(!(L&1)){if(!(L&2)){if(!(c[g+21824>>2]&15))break;h=L&16777216;if(!N)if(!h){fb[c[f+116>>2]&127](M,S,g+44384|0,K,g+43648|0);break}else{fb[c[f+120>>2]&127](M,S,g+44384|0,K,g+43648|0);break}j=(h|0)!=0;h=j?4:1;j=c[(j?f+140|0:f+144|0)>>2]|0;k=0;while(1){if(a[(d[96537+k>>0]|0)+(g+43648)>>0]|0)ob[j&1023](M+(c[S+(k<<2)>>2]|0)|0,g+44384+(k<<4<<m<<1)|0,K);k=k+h|0;if((k|0)>=16)break f}}if(!N){fb[c[f+128>>2]&127](M,S,g+44384|0,K,g+43648|0);break}if((c[f+26664>>2]|0)==244){h=c[g+21636>>2]|0;if((h+-1|0)>>>0<2){ub[c[f+26244+(h<<2)>>2]&255](M,S,g+44384|0,K);break}}h=g+44384|0;l=f+144|0;if(!m){j=0;do{h=g+44384+(j<<4<<1)|0;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(b[h>>1]|0)R=217}else R=217;if((R|0)==217){R=0;ob[c[l>>2]&1023](M+(c[S+(j<<2)>>2]|0)|0,h,K)}j=j+1|0}while((j|0)!=16)}else{k=0;do{j=k<<4;if(!(a[(d[96537+k>>0]|0)+(g+43648)>>0]|0)){if(c[h+(j<<2)>>2]|0)R=222}else R=222;if((R|0)==222){R=0;ob[c[l>>2]&1023](M+(c[S+(k<<2)>>2]|0)|0,g+44384+(j<<m<<1)|0,K)}k=k+1|0}while((k|0)!=16)}}while(0);if(!(c[g+21824>>2]&48)){i=W;return}c[T>>2]=Q;k=T+4|0;c[k>>2]=P;g:do if(N){if(I)if((c[f+26664>>2]|0)==244){h=g+21632|0;j=c[h>>2]|0;if((j+-1|0)>>>0<2){ub[c[f+26232+(j<<2)>>2]&255](Q,S+64|0,g+44384+(256<<c[V>>2]<<1)|0,U);ub[c[f+26232+(c[h>>2]<<2)>>2]&255](c[k>>2]|0,S+128|0,g+44384+(512<<c[V>>2]<<1)|0,U);break}}r=c[f+144>>2]|0;s=g+44384|0;if(O)q=1;else{o=1;while(1){m=o<<4;do if((m|0)<(m|4|0)){n=T+(o+-1<<2)|0;j=c[V>>2]|0;k=o<<8;if(!(a[(d[96537+m>>0]|0)+(g+43648)>>0]|0)){if(!j)h=e[g+44384+(k<<1)>>1]|0;else h=c[s+(k<<2)>>2]|0;if(h|0)R=264}else R=264;if((R|0)==264){R=0;ob[r&1023]((c[n>>2]|0)+(c[S+(m<<2)>>2]|0)|0,g+44384+(k<<j<<1)|0,U)}j=m|1;k=c[V>>2]|0;l=j<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(h|0)R=312}else R=312;if((R|0)==312){R=0;ob[r&1023]((c[n>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}j=j+1|0;k=c[V>>2]|0;l=j<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(h|0)R=318}else R=318;if((R|0)==318){R=0;ob[r&1023]((c[n>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}j=m|3;k=c[V>>2]|0;l=j<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(!h)break}ob[r&1023]((c[n>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}while(0);o=o+1|0;if((o|0)==3)break g}}do{p=q<<4;o=p|4;do if((p|0)<(o|0)){m=T+(q+-1<<2)|0;j=c[V>>2]|0;k=q<<8;if(!(a[(d[96537+p>>0]|0)+(g+43648)>>0]|0)){if(!j)h=e[g+44384+(k<<1)>>1]|0;else h=c[s+(k<<2)>>2]|0;if(h|0)R=252}else R=252;if((R|0)==252){R=0;ob[r&1023]((c[m>>2]|0)+(c[S+(p<<2)>>2]|0)|0,g+44384+(k<<j<<1)|0,U)}j=p|1;k=c[V>>2]|0;l=j<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(h|0)R=278}else R=278;if((R|0)==278){R=0;ob[r&1023]((c[m>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}j=j+1|0;k=c[V>>2]|0;l=j<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(h|0)R=284}else R=284;if((R|0)==284){R=0;ob[r&1023]((c[m>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}j=p|3;k=c[V>>2]|0;l=j<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(!h)break}ob[r&1023]((c[m>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}while(0);do if((o|0)<(p|8|0)){n=T+(q+-1<<2)|0;l=o+4|0;k=c[V>>2]|0;j=o<<4;if(!(a[(d[96537+l>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(j<<1)>>1]|0;else h=c[s+(j<<2)>>2]|0;if(h|0)R=246}else R=246;if((R|0)==246){R=0;ob[r&1023]((c[n>>2]|0)+(c[S+(l<<2)>>2]|0)|0,g+44384+(j<<k<<1)|0,U)}m=p|5;j=m+4|0;k=c[V>>2]|0;l=m<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(h|0)R=295}else R=295;if((R|0)==295){R=0;ob[r&1023]((c[n>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}k=m+5|0;l=c[V>>2]|0;j=(m<<4)+16|0;if(!(a[(d[96537+k>>0]|0)+(g+43648)>>0]|0)){if(!l)h=e[g+44384+(j<<1)>>1]|0;else h=c[s+(j<<2)>>2]|0;if(h|0)R=301}else R=301;if((R|0)==301){R=0;ob[r&1023]((c[n>>2]|0)+(c[S+(k<<2)>>2]|0)|0,g+44384+(j<<l<<1)|0,U)}l=p|7;j=l+4|0;k=c[V>>2]|0;l=l<<4;if(!(a[(d[96537+j>>0]|0)+(g+43648)>>0]|0)){if(!k)h=e[g+44384+(l<<1)>>1]|0;else h=c[s+(l<<2)>>2]|0;if(!h)break}ob[r&1023]((c[n>>2]|0)+(c[S+(j<<2)>>2]|0)|0,g+44384+(l<<k<<1)|0,U)}while(0);q=q+1|0}while((q|0)!=3)}else{h=c[g+828>>2]|0;j=c[g+832>>2]|0;if(a[g+43688>>0]|0)jb[c[f+136>>2]&255](g+44384+(256<<c[V>>2]<<1)|0,c[(c[f+197556+((I?1:4)<<2)>>2]|0)+((O?h+3|0:h)<<6)>>2]|0);if(a[g+43728>>0]|0)jb[c[f+136>>2]&255](g+44384+(512<<c[V>>2]<<1)|0,c[(c[f+197556+((I?2:5)<<2)>>2]|0)+((O?j+3|0:j)<<6)>>2]|0);fb[c[f+124>>2]&127](T,S,g+44384|0,U,g+43648|0)}while(0);i=W;return}function yc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0;O=f+856|0;x=c[O>>2]|0;A=(v|0)!=0;B=(w|0)!=0;if(B&(A&(x|0)==2)){y=f+21768|0;z=c[y>>2]|0;U=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+U>>0]|0)*384|0)+(a[f+44136+U>>0]<<3)+((z&1)<<2)>>2]|0)==32)V=3;else N=y}else V=3;do if((V|0)==3){if((x|0)==1){z=f+21768|0;N=z;z=c[z>>2]|0;break}I=c[e+25960>>2]|0;K=o<<1;H=f+21756|0;z=c[H>>2]|0;G=k+((_(z,K)|0)+(n<<1<<I))|0;L=f+21760|0;K=(_(c[L>>2]|0,K)|0)+(n<<I)|0;J=l+K|0;K=m+K|0;D=(c[f+21764>>2]<<3)+n|0;F=f+21796|0;x=c[F>>2]|0;E=(c[f+21768>>2]>>x<<3)+o|0;if(!v){s=q;r=p}else{m=d[96537+g>>0]|0;A=a[f+44096+m>>0]|0;l=(b[f+43776+(m<<2)>>1]|0)+(D<<3)|0;m=(b[f+43776+(m<<2)+2>>1]|0)+(E<<3)|0;k=m<<2&12|l&3;u=l>>2;n=m>>2;y=(_(n,z)|0)+(u<<I)|0;y=(c[f+23384+(A*40|0)>>2]|0)+y|0;o=c[e+198232>>2]|0;v=o<<4;B=c[e+198228>>2]<<4>>x;C=l&7;x=(C|0)==0?0:-3;t=(m&7|0)==0?0:-3;if((u|0)<(0-x|0)|(n|0)<(0-t|0))V=42;else if((u+16|0)>(x+v|0))V=42;else if((n+16|0)>(B+t|0))V=42;else u=0;if((V|0)==42){T=f+43620|0;U=2<<I;eb[c[e+8>>2]&3](c[T>>2]|0,y+(0-U)+(0-(z<<1))|0,z,z,21,21,u+-2|0,n+-2|0,v,B);y=c[H>>2]|0;z=y;u=1;y=(c[T>>2]|0)+U+(y<<1)|0}x=p+(k<<2)|0;ob[c[x>>2]&1023](G,y,z);if(!h)ob[c[x>>2]&1023](G+j|0,y+j|0,c[H>>2]|0);y=l>>3;p=y<<I;z=c[L>>2]|0;x=_(z,n)|0;t=(c[f+23384+(A*40|0)+4>>2]|0)+p+x|0;x=(c[f+23384+(A*40|0)+8>>2]|0)+p+x|0;if(!u){y=m<<1&6;rb[q&63](J,t,z,i,C,y)}else{R=e+8|0;p=f+43620|0;T=o<<3;eb[c[R>>2]&3](c[p>>2]|0,t,z,z,9,17,y,n,T,B);U=m<<1&6;rb[q&63](J,c[p>>2]|0,c[L>>2]|0,i,C,U);S=c[L>>2]|0;eb[c[R>>2]&3](c[p>>2]|0,x,S,S,9,17,y,n,T,B);y=U;x=c[p>>2]|0}rb[q&63](K,x,c[L>>2]|0,i,C,y)}if(!w)return;B=d[96537+g>>0]|0;o=a[f+44136+B>>0]|0;l=(b[f+43936+(B<<2)>>1]|0)+(D<<3)|0;B=(b[f+43936+(B<<2)+2>>1]|0)+(E<<3)|0;k=B<<2&12|l&3;u=l>>2;m=B>>2;z=c[H>>2]|0;y=(_(m,z)|0)+(u<<I)|0;y=(c[f+25304+(o*40|0)>>2]|0)+y|0;n=c[e+198232>>2]|0;v=n<<4;A=c[e+198228>>2]<<4>>c[F>>2];C=l&7;x=(C|0)==0?0:-3;t=(B&7|0)==0?0:-3;if((u|0)<(0-x|0)|(m|0)<(0-t|0))V=53;else if((u+16|0)>(v+x|0))V=53;else if((m+16|0)>(A+t|0))V=53;else u=0;if((V|0)==53){q=f+43620|0;V=2<<I;eb[c[e+8>>2]&3](c[q>>2]|0,y+(0-V)+(0-(z<<1))|0,z,z,21,21,u+-2|0,m+-2|0,v,A);y=c[H>>2]|0;z=y;u=1;y=(c[q>>2]|0)+V+(y<<1)|0}x=r+(k<<2)|0;ob[c[x>>2]&1023](G,y,z);if(!h)ob[c[x>>2]&1023](G+j|0,y+j|0,c[H>>2]|0);t=l>>3;j=t<<I;y=c[L>>2]|0;x=_(y,m)|0;z=(c[f+25304+(o*40|0)+4>>2]|0)+j+x|0;x=(c[f+25304+(o*40|0)+8>>2]|0)+j+x|0;if(!u){f=B<<1&6;rb[s&63](J,z,y,i,C,f);y=f}else{V=e+8|0;f=f+43620|0;e=n<<3;eb[c[V>>2]&3](c[f>>2]|0,z,y,y,9,17,t,m,e,A);y=B<<1&6;rb[s&63](J,c[f>>2]|0,c[L>>2]|0,i,C,y);j=c[L>>2]|0;eb[c[V>>2]&3](c[f>>2]|0,x,j,j,9,17,t,m,e,A);x=c[f>>2]|0}rb[s&63](K,x,c[L>>2]|0,i,C,y);return}while(0);s=c[t>>2]|0;C=c[t+4>>2]|0;M=c[u>>2]|0;L=c[u+4>>2]|0;P=c[e+25960>>2]|0;Q=o<<1;U=f+21756|0;F=c[U>>2]|0;S=k+((_(F,Q)|0)+(n<<1<<P))|0;T=f+21760|0;x=c[T>>2]|0;Q=(_(x,Q)|0)+(n<<P)|0;R=l+Q|0;Q=m+Q|0;y=(c[f+21764>>2]<<3)+n|0;w=f+21796|0;l=c[w>>2]|0;z=(z>>l<<3)+o|0;if(!(A&B)){o=B&1;A=d[96537+g>>0]|0;n=a[f+44096+(o*40|0)+A>>0]|0;k=(b[f+43776+(o*160|0)+(A<<2)>>1]|0)+(y<<3)|0;A=(b[f+43776+(o*160|0)+(A<<2)+2>>1]|0)+(z<<3)|0;v=A<<2&12|k&3;t=k>>2;r=A>>2;y=(_(r,F)|0)+(t<<P)|0;y=(c[f+23384+(o*1920|0)+(n*40|0)>>2]|0)+y|0;B=c[e+198232>>2]|0;u=B<<4;l=c[e+198228>>2]<<4>>l;m=k&7;x=(m|0)==0?0:-3;z=(A&7|0)==0?0:-3;if((t|0)<(0-x|0)|(r|0)<(0-z|0))V=30;else if((t+16|0)>(x+u|0))V=30;else if((r+16|0)>(l+z|0))V=30;else{z=F;u=0}if((V|0)==30){O=f+43620|0;V=2<<P;eb[c[e+8>>2]&3](c[O>>2]|0,y+(0-V)+(0-(F<<1))|0,F,F,21,21,t+-2|0,r+-2|0,u,l);y=c[U>>2]|0;z=y;u=1;y=(c[O>>2]|0)+V+(y<<1)|0}x=p+(v<<2)|0;ob[c[x>>2]&1023](S,y,z);if(!h)ob[c[x>>2]&1023](S+j|0,y+j|0,c[U>>2]|0);t=k>>3;j=t<<P;y=c[T>>2]|0;x=_(y,r)|0;z=(c[f+23384+(o*1920|0)+(n*40|0)+4>>2]|0)+j+x|0;x=(c[f+23384+(o*1920|0)+(n*40|0)+8>>2]|0)+j+x|0;if(!u){e=A<<1&6;rb[q&63](R,z,y,i,m,e);y=e}else{h=e+8|0;e=f+43620|0;j=B<<3;eb[c[h>>2]&3](c[e>>2]|0,z,y,y,9,17,t,r,j,l);y=A<<1&6;rb[q&63](R,c[e>>2]|0,c[T>>2]|0,i,m,y);V=c[T>>2]|0;eb[c[h>>2]&3](c[e>>2]|0,x,V,V,9,17,t,r,j,l);x=c[e>>2]|0}rb[q&63](Q,x,c[T>>2]|0,i,m,y);rb[s&63](S,c[U>>2]|0,i,c[f+864>>2]|0,c[f+888+(n<<4)+(o<<3)>>2]|0,c[f+888+(n<<4)+(o<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;e=f+868|0;rb[C&63](R,c[T>>2]|0,i,c[e>>2]|0,c[f+1656+(n<<5)+(o<<4)>>2]|0,c[f+1656+(n<<5)+(o<<4)+4>>2]|0);rb[C&63](Q,c[T>>2]|0,i,c[e>>2]|0,c[f+1656+(n<<5)+(o<<4)+8>>2]|0,c[f+1656+(n<<5)+(o<<4)+12>>2]|0);return}J=c[f+43616>>2]|0;K=J+(16<<P)|0;I=J+(x<<4)|0;C=d[96537+g>>0]|0;G=a[f+44096+C>>0]|0;H=a[f+44136+C>>0]|0;n=y<<3;k=(b[f+43776+(C<<2)>>1]|0)+n|0;o=z<<3;A=(b[f+43776+(C<<2)+2>>1]|0)+o|0;v=A<<2&12|k&3;t=k>>2;r=A>>2;z=(_(r,F)|0)+(t<<P)|0;z=(c[f+23384+(G*40|0)>>2]|0)+z|0;D=e+198232|0;B=c[D>>2]|0;u=B<<4;E=e+198228|0;l=c[E>>2]<<4>>l;m=k&7;x=(m|0)==0?0:-3;y=(A&7|0)==0?0:-3;if((t|0)<(0-x|0)|(r|0)<(0-y|0))V=9;else if((t+16|0)>(x+u|0))V=9;else if((r+16|0)>(l+y|0))V=9;else{y=F;u=0}if((V|0)==9){s=f+43620|0;g=2<<P;eb[c[e+8>>2]&3](c[s>>2]|0,z+(0-g)+(0-(F<<1))|0,F,F,21,21,t+-2|0,r+-2|0,u,l);z=c[U>>2]|0;y=z;u=1;z=(c[s>>2]|0)+g+(z<<1)|0}x=p+(v<<2)|0;ob[c[x>>2]&1023](S,z,y);s=(h|0)!=0;if(!s)ob[c[x>>2]&1023](S+j|0,z+j|0,c[U>>2]|0);y=k>>3;h=y<<P;z=c[T>>2]|0;x=_(z,r)|0;t=(c[f+23384+(G*40|0)+4>>2]|0)+h+x|0;x=(c[f+23384+(G*40|0)+8>>2]|0)+h+x|0;if(!u){y=A<<1&6;rb[q&63](R,t,z,i,m,y)}else{k=e+8|0;h=f+43620|0;F=B<<3;eb[c[k>>2]&3](c[h>>2]|0,t,z,z,9,17,y,r,F,l);g=A<<1&6;rb[q&63](R,c[h>>2]|0,c[T>>2]|0,i,m,g);B=c[T>>2]|0;eb[c[k>>2]&3](c[h>>2]|0,x,B,B,9,17,y,r,F,l);y=g;x=c[h>>2]|0}rb[q&63](Q,x,c[T>>2]|0,i,m,y);A=(b[f+43936+(C<<2)>>1]|0)+n|0;m=(b[f+43936+(C<<2)+2>>1]|0)+o|0;k=m<<2&12|A&3;v=A>>2;n=m>>2;z=c[U>>2]|0;y=(_(n,z)|0)+(v<<P)|0;y=(c[f+25304+(H*40|0)>>2]|0)+y|0;r=c[D>>2]|0;u=r<<4;l=c[E>>2]<<4>>c[w>>2];B=A&7;x=(B|0)==0?0:-3;t=(m&7|0)==0?0:-3;if((v|0)<(0-x|0)|(n|0)<(0-t|0))V=18;else if((v+16|0)>(u+x|0))V=18;else if((n+16|0)>(l+t|0))V=18;else u=0;if((V|0)==18){h=f+43620|0;V=2<<P;eb[c[e+8>>2]&3](c[h>>2]|0,y+(0-V)+(0-(z<<1))|0,z,z,21,21,v+-2|0,n+-2|0,u,l);y=c[U>>2]|0;z=y;u=1;y=(c[h>>2]|0)+V+(y<<1)|0}x=p+(k<<2)|0;ob[c[x>>2]&1023](I,y,z);if(!s)ob[c[x>>2]&1023](I+j|0,y+j|0,c[U>>2]|0);t=A>>3;j=t<<P;y=c[T>>2]|0;x=_(y,n)|0;z=(c[f+25304+(H*40|0)+4>>2]|0)+j+x|0;x=(c[f+25304+(H*40|0)+8>>2]|0)+j+x|0;if(!u){e=m<<1&6;rb[q&63](J,z,y,i,B,e);y=e}else{h=e+8|0;e=f+43620|0;j=r<<3;eb[c[h>>2]&3](c[e>>2]|0,z,y,y,9,17,t,n,j,l);y=m<<1&6;rb[q&63](J,c[e>>2]|0,c[T>>2]|0,i,B,y);V=c[T>>2]|0;eb[c[h>>2]&3](c[e>>2]|0,x,V,V,9,17,t,n,j,l);x=c[e>>2]|0}rb[q&63](K,x,c[T>>2]|0,i,B,y);if((c[O>>2]|0)==2){e=c[f+3192+(G*384|0)+(H<<3)+((c[N>>2]&1)<<2)>>2]|0;f=64-e|0;pb[M&31](S,I,c[U>>2]|0,i,5,e,f,0);pb[L&31](R,J,c[T>>2]|0,i,5,e,f,0);pb[L&31](Q,K,c[T>>2]|0,i,5,e,f,0);return}else{pb[M&31](S,I,c[U>>2]|0,i,c[f+864>>2]|0,c[f+888+(G<<4)>>2]|0,c[f+888+(H<<4)+8>>2]|0,(c[f+888+(H<<4)+12>>2]|0)+(c[f+888+(G<<4)+4>>2]|0)|0);e=f+868|0;pb[L&31](R,J,c[T>>2]|0,i,c[e>>2]|0,c[f+1656+(G<<5)>>2]|0,c[f+1656+(H<<5)+16>>2]|0,(c[f+1656+(H<<5)+20>>2]|0)+(c[f+1656+(G<<5)+4>>2]|0)|0);pb[L&31](Q,K,c[T>>2]|0,i,c[e>>2]|0,c[f+1656+(G<<5)+8>>2]|0,c[f+1656+(H<<5)+24>>2]|0,(c[f+1656+(H<<5)+28>>2]|0)+(c[f+1656+(G<<5)+12>>2]|0)|0);return}}function zc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0;N=f+856|0;x=c[N>>2]|0;A=(v|0)!=0;B=(w|0)!=0;if(B&(A&(x|0)==2)){y=f+21768|0;z=c[y>>2]|0;V=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+V>>0]|0)*384|0)+(a[f+44136+V>>0]<<3)+((z&1)<<2)>>2]|0)==32)W=3;else P=y}else W=3;do if((W|0)==3){if((x|0)==1){z=f+21768|0;P=z;z=c[z>>2]|0;break}J=c[e+25960>>2]|0;I=f+21756|0;t=c[I>>2]|0;H=k+((_(o<<1,t)|0)+(n<<1<<J))|0;M=f+21760|0;L=(_(c[M>>2]|0,o)|0)+(n<<J)|0;K=l+L|0;L=m+L|0;D=(c[f+21764>>2]<<3)+n|0;F=f+21768|0;G=f+21796|0;x=c[G>>2]|0;E=(c[F>>2]>>x<<3)+o|0;if(!v){s=q;r=p}else{y=d[96537+g>>0]|0;m=a[f+44096+y>>0]|0;B=(b[f+43776+(y<<2)>>1]|0)+(D<<3)|0;y=(b[f+43776+(y<<2)+2>>1]|0)+(E<<3)|0;A=y<<2&12|B&3;k=B>>2;v=y>>2;z=(_(v,t)|0)+(k<<J)|0;z=(c[f+23384+(m*40|0)>>2]|0)+z|0;o=c[e+198232>>2]|0;l=o<<4;n=c[e+198228>>2]<<4>>x;C=B&7;x=(C|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))W=54;else if((k+16|0)>(x+l|0))W=54;else if((v+16|0)>(n+u|0))W=54;else v=0;if((W|0)==54){U=f+43620|0;V=2<<J;eb[c[e+8>>2]&3](c[U>>2]|0,z+(0-V)+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,n);z=c[I>>2]|0;t=z;v=1;z=(c[U>>2]|0)+V+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!h)ob[c[x>>2]&1023](H+j|0,z+j|0,c[I>>2]|0);if(c[G>>2]|0){y=(1-(c[f+23384+(m*40|0)+24>>2]|0)+(c[F>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(n>>1|0);v=x&1|v}z=B>>3;p=z<<J;t=y>>3;u=c[M>>2]|0;x=_(u,t)|0;k=(c[f+23384+(m*40|0)+4>>2]|0)+p+x|0;x=(c[f+23384+(m*40|0)+8>>2]|0)+p+x|0;if(!v){p=i>>1;z=y&7;rb[q&63](K,k,u,p,C,z);y=p}else{R=e+8|0;p=f+43620|0;S=o<<3;T=n>>1;eb[c[R>>2]&3](c[p>>2]|0,k,u,u,9,9,z,t,S,T);U=i>>1;V=y&7;rb[q&63](K,c[p>>2]|0,c[M>>2]|0,U,C,V);y=c[M>>2]|0;eb[c[R>>2]&3](c[p>>2]|0,x,y,y,9,9,z,t,S,T);y=U;z=V;x=c[p>>2]|0}rb[q&63](L,x,c[M>>2]|0,y,C,z)}if(!w)return;y=d[96537+g>>0]|0;o=a[f+44136+y>>0]|0;B=(b[f+43936+(y<<2)>>1]|0)+(D<<3)|0;y=(b[f+43936+(y<<2)+2>>1]|0)+(E<<3)|0;A=y<<2&12|B&3;u=B>>2;k=y>>2;t=c[I>>2]|0;z=(_(k,t)|0)+(u<<J)|0;z=(c[f+25304+(o*40|0)>>2]|0)+z|0;m=c[e+198232>>2]|0;v=m<<4;n=c[e+198228>>2]<<4>>c[G>>2];C=B&7;x=(C|0)==0?0:-3;l=(y&7|0)==0?0:-3;if((u|0)<(0-x|0)|(k|0)<(0-l|0))W=69;else if((u+16|0)>(v+x|0))W=69;else if((k+16|0)>(n+l|0))W=69;else v=0;if((W|0)==69){W=f+43620|0;q=2<<J;eb[c[e+8>>2]&3](c[W>>2]|0,z+(0-q)+(0-(t<<1))|0,t,t,21,21,u+-2|0,k+-2|0,v,n);z=c[I>>2]|0;t=z;v=1;z=(c[W>>2]|0)+q+(z<<1)|0}x=r+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!h)ob[c[x>>2]&1023](H+j|0,z+j|0,c[I>>2]|0);if(c[G>>2]|0){y=(1-(c[f+25304+(o*40|0)+24>>2]|0)+(c[F>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(n>>1|0);v=x&1|v}k=B>>3;q=k<<J;z=y>>3;t=c[M>>2]|0;x=_(t,z)|0;u=(c[f+25304+(o*40|0)+4>>2]|0)+q+x|0;x=(c[f+25304+(o*40|0)+8>>2]|0)+q+x|0;if(!v){f=i>>1;z=y&7;rb[s&63](K,u,t,f,C,z);y=f}else{W=e+8|0;f=f+43620|0;j=m<<3;q=n>>1;eb[c[W>>2]&3](c[f>>2]|0,u,t,t,9,9,k,z,j,q);e=i>>1;i=y&7;rb[s&63](K,c[f>>2]|0,c[M>>2]|0,e,C,i);y=c[M>>2]|0;eb[c[W>>2]&3](c[f>>2]|0,x,y,y,9,9,k,z,j,q);y=e;z=i;x=c[f>>2]|0}rb[s&63](L,x,c[M>>2]|0,y,C,z);return}while(0);D=c[t>>2]|0;s=c[t+4>>2]|0;L=c[u>>2]|0;K=c[u+4>>2]|0;O=c[e+25960>>2]|0;V=f+21756|0;t=c[V>>2]|0;S=k+((_(o<<1,t)|0)+(n<<1<<O))|0;T=i>>1;U=f+21760|0;x=c[U>>2]|0;Q=(_(x,o)|0)+(n<<O)|0;R=l+Q|0;Q=m+Q|0;y=(c[f+21764>>2]<<3)+n|0;M=f+21796|0;u=c[M>>2]|0;z=(z>>u<<3)+o|0;if(!(A&B)){C=B&1;A=d[96537+g>>0]|0;o=a[f+44096+(C*40|0)+A>>0]|0;r=(b[f+43776+(C*160|0)+(A<<2)>>1]|0)+(y<<3)|0;y=(b[f+43776+(C*160|0)+(A<<2)+2>>1]|0)+(z<<3)|0;A=y<<2&12|r&3;k=r>>2;v=y>>2;z=(_(v,t)|0)+(k<<O)|0;z=(c[f+23384+(C*1920|0)+(o*40|0)>>2]|0)+z|0;m=c[e+198232>>2]|0;l=m<<4;B=c[e+198228>>2]<<4>>u;n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))W=38;else if((k+16|0)>(x+l|0))W=38;else if((v+16|0)>(B+u|0))W=38;else v=0;if((W|0)==38){N=f+43620|0;W=2<<O;eb[c[e+8>>2]&3](c[N>>2]|0,z+(0-W)+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,B);z=c[V>>2]|0;t=z;v=1;z=(c[N>>2]|0)+W+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](S,z,t);if(!h)ob[c[x>>2]&1023](S+j|0,z+j|0,c[V>>2]|0);if(c[M>>2]|0){y=(1-(c[f+23384+(C*1920|0)+(o*40|0)+24>>2]|0)+(c[P>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}k=r>>3;j=k<<O;z=y>>3;t=c[U>>2]|0;x=_(t,z)|0;u=(c[f+23384+(C*1920|0)+(o*40|0)+4>>2]|0)+j+x|0;x=(c[f+23384+(C*1920|0)+(o*40|0)+8>>2]|0)+j+x|0;if(!v){y=y&7;rb[q&63](R,u,t,T,n,y)}else{p=e+8|0;e=f+43620|0;W=m<<3;j=B>>1;eb[c[p>>2]&3](c[e>>2]|0,u,t,t,9,9,k,z,W,j);y=y&7;rb[q&63](R,c[e>>2]|0,c[U>>2]|0,T,n,y);h=c[U>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,9,k,z,W,j);x=c[e>>2]|0}rb[q&63](Q,x,c[U>>2]|0,T,n,y);rb[D&63](S,c[V>>2]|0,i,c[f+864>>2]|0,c[f+888+(o<<4)+(C<<3)>>2]|0,c[f+888+(o<<4)+(C<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;i=f+868|0;rb[s&63](R,c[U>>2]|0,T,c[i>>2]|0,c[f+1656+(o<<5)+(C<<4)>>2]|0,c[f+1656+(o<<5)+(C<<4)+4>>2]|0);rb[s&63](Q,c[U>>2]|0,T,c[i>>2]|0,c[f+1656+(o<<5)+(C<<4)+8>>2]|0,c[f+1656+(o<<5)+(C<<4)+12>>2]|0);return}I=c[f+43616>>2]|0;J=I+(16<<O)|0;H=I+(x<<4)|0;s=d[96537+g>>0]|0;F=a[f+44096+s>>0]|0;G=a[f+44136+s>>0]|0;o=y<<3;r=(b[f+43776+(s<<2)>>1]|0)+o|0;C=z<<3;y=(b[f+43776+(s<<2)+2>>1]|0)+C|0;A=y<<2&12|r&3;k=r>>2;v=y>>2;z=(_(v,t)|0)+(k<<O)|0;z=(c[f+23384+(F*40|0)>>2]|0)+z|0;E=e+198232|0;m=c[E>>2]|0;l=m<<4;w=e+198228|0;B=c[w>>2]<<4>>u;n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))W=9;else if((k+16|0)>(x+l|0))W=9;else if((v+16|0)>(B+u|0))W=9;else v=0;if((W|0)==9){D=f+43620|0;g=2<<O;eb[c[e+8>>2]&3](c[D>>2]|0,z+(0-g)+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,B);z=c[V>>2]|0;t=z;v=1;z=(c[D>>2]|0)+g+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](S,z,t);D=(h|0)!=0;if(!D)ob[c[x>>2]&1023](S+j|0,z+j|0,c[V>>2]|0);if(c[M>>2]|0){y=(1-(c[f+23384+(F*40|0)+24>>2]|0)+(c[P>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;h=z<<O;t=y>>3;u=c[U>>2]|0;x=_(u,t)|0;k=(c[f+23384+(F*40|0)+4>>2]|0)+h+x|0;x=(c[f+23384+(F*40|0)+8>>2]|0)+h+x|0;if(!v){y=y&7;rb[q&63](R,k,u,T,n,y)}else{r=e+8|0;h=f+43620|0;m=m<<3;g=B>>1;eb[c[r>>2]&3](c[h>>2]|0,k,u,u,9,9,z,t,m,g);y=y&7;rb[q&63](R,c[h>>2]|0,c[U>>2]|0,T,n,y);B=c[U>>2]|0;eb[c[r>>2]&3](c[h>>2]|0,x,B,B,9,9,z,t,m,g);x=c[h>>2]|0}rb[q&63](Q,x,c[U>>2]|0,T,n,y);r=(b[f+43936+(s<<2)>>1]|0)+o|0;y=(b[f+43936+(s<<2)+2>>1]|0)+C|0;A=y<<2&12|r&3;v=r>>2;l=y>>2;t=c[V>>2]|0;z=(_(l,t)|0)+(v<<O)|0;z=(c[f+25304+(G*40|0)>>2]|0)+z|0;m=c[E>>2]|0;k=m<<4;B=c[w>>2]<<4>>c[M>>2];n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((v|0)<(0-x|0)|(l|0)<(0-u|0))W=22;else if((v+16|0)>(k+x|0))W=22;else if((l+16|0)>(B+u|0))W=22;else v=0;if((W|0)==22){h=f+43620|0;W=2<<O;eb[c[e+8>>2]&3](c[h>>2]|0,z+(0-W)+(0-(t<<1))|0,t,t,21,21,v+-2|0,l+-2|0,k,B);z=c[V>>2]|0;t=z;v=1;z=(c[h>>2]|0)+W+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!D)ob[c[x>>2]&1023](H+j|0,z+j|0,c[V>>2]|0);if(c[M>>2]|0){y=(1-(c[f+25304+(G*40|0)+24>>2]|0)+(c[P>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}k=r>>3;j=k<<O;z=y>>3;t=c[U>>2]|0;x=_(t,z)|0;u=(c[f+25304+(G*40|0)+4>>2]|0)+j+x|0;x=(c[f+25304+(G*40|0)+8>>2]|0)+j+x|0;if(!v){y=y&7;rb[q&63](I,u,t,T,n,y)}else{p=e+8|0;e=f+43620|0;W=m<<3;j=B>>1;eb[c[p>>2]&3](c[e>>2]|0,u,t,t,9,9,k,z,W,j);y=y&7;rb[q&63](I,c[e>>2]|0,c[U>>2]|0,T,n,y);h=c[U>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,9,k,z,W,j);x=c[e>>2]|0}rb[q&63](J,x,c[U>>2]|0,T,n,y);if((c[N>>2]|0)==2){e=c[f+3192+(F*384|0)+(G<<3)+((c[P>>2]&1)<<2)>>2]|0;f=64-e|0;pb[L&31](S,H,c[V>>2]|0,i,5,e,f,0);pb[K&31](R,I,c[U>>2]|0,T,5,e,f,0);pb[K&31](Q,J,c[U>>2]|0,T,5,e,f,0);return}else{pb[L&31](S,H,c[V>>2]|0,i,c[f+864>>2]|0,c[f+888+(F<<4)>>2]|0,c[f+888+(G<<4)+8>>2]|0,(c[f+888+(G<<4)+12>>2]|0)+(c[f+888+(F<<4)+4>>2]|0)|0);i=f+868|0;pb[K&31](R,I,c[U>>2]|0,T,c[i>>2]|0,c[f+1656+(F<<5)>>2]|0,c[f+1656+(G<<5)+16>>2]|0,(c[f+1656+(G<<5)+20>>2]|0)+(c[f+1656+(F<<5)+4>>2]|0)|0);pb[K&31](Q,J,c[U>>2]|0,T,c[i>>2]|0,c[f+1656+(F<<5)+8>>2]|0,c[f+1656+(G<<5)+24>>2]|0,(c[f+1656+(G<<5)+28>>2]|0)+(c[f+1656+(F<<5)+12>>2]|0)|0);return}}function Ac(b,f){b=b|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0;R=i;i=i+16|0;Q=R;G=f+21764|0;O=c[G>>2]|0;F=f+21768|0;J=c[F>>2]|0;u=f+21772|0;K=c[u>>2]|0;g=b+24668|0;L=c[(c[g>>2]|0)+(K<<2)>>2]|0;M=b+26260|0;N=(c[b+26672>>2]|0)==2;P=c[b+24624>>2]|0;A=f+21748|0;E=c[A>>2]|0;H=(c[P>>2]|0)+((_(E,J)|0)+(O<<1)<<4)|0;D=O<<4;B=f+21752|0;J=_(_(16>>>(c[b+25976>>2]|0),J)|0,c[B>>2]|0)|0;I=(c[P+4>>2]|0)+D+J|0;J=(c[P+8>>2]|0)+D+J|0;D=b+12|0;O=H+(_(E,O<<2&12)|0)+128|0;ob[c[D>>2]&1023](O,E,4);E=I+(_(c[G>>2]&7,c[B>>2]|0)|0)+128|0;ob[c[D>>2]&1023](E,J-I|0,2);a[(c[b+197620>>2]|0)+K>>0]=c[f+23380>>2];K=c[A>>2]|0;E=f+21756|0;c[E>>2]=K;O=c[B>>2]|0;c[f+21760>>2]=O;P=(L&7|0)!=0;a:do if(P){s=f+844|0;switch(c[s>>2]|0){case 0:break;case 2:{n=c[G>>2]|0;g=c[f+21708>>2]|0;h=(e[(c[b+197604>>2]|0)+((c[u>>2]|0)+-1-(c[b+198236>>2]|0)<<1)>>1]|0)==(c[f+808>>2]|0)&1;t=5;break}default:{h=c[G>>2]|0;n=h;g=(c[F>>2]|0)>((c[f+21796>>2]|0)!=0|0)&1;h=(h|0)>0&1;t=5}}if((t|0)==5){q=H+(-2-K)|0;l=-2-O|0;k=I+l|0;l=J+l|0;m=n+-1|0;o=f+43628|0;p=c[o>>2]|0;j=p+(n*96|0)|0;if(g|0){h=(h|0)!=0;if(h){A=p+(m*96|0)+16|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=q+-14|0;z=E;y=c[z+4>>2]|0;c[A>>2]=c[z>>2];c[A+4>>2]=y;A=E;c[A>>2]=B;c[A+4>>2]=D;A=p+(m*96|0)+24|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=E+8|0;y=E;z=c[y+4>>2]|0;c[A>>2]=c[y>>2];c[A+4>>2]=z;c[E>>2]=B;c[E+4>>2]=D}E=j;D=c[E>>2]|0;E=c[E+4>>2]|0;B=q+2|0;A=B;z=c[A+4>>2]|0;g=j;c[g>>2]=c[A>>2];c[g+4>>2]=z;g=B;c[g>>2]=D;c[g+4>>2]=E;g=p+(n*96|0)+8|0;E=g;D=c[E>>2]|0;E=c[E+4>>2]|0;B=B+8|0;z=B;A=c[z+4>>2]|0;c[g>>2]=c[z>>2];c[g+4>>2]=A;c[B>>2]=D;c[B+4>>2]=E;B=p+(n*96|0)+16|0;E=B;D=c[E>>2]|0;E=c[E+4>>2]|0;g=q+18|0;A=g;z=c[A+4>>2]|0;c[B>>2]=c[A>>2];c[B+4>>2]=z;B=g;c[B>>2]=D;c[B+4>>2]=E;B=p+(n*96|0)+24|0;E=B;D=c[E>>2]|0;E=c[E+4>>2]|0;g=g+8|0;z=g;A=c[z+4>>2]|0;c[B>>2]=c[z>>2];c[B+4>>2]=A;c[g>>2]=D;c[g+4>>2]=E;g=(c[G>>2]|0)+1|0;if((g|0)<(c[b+198232>>2]|0)){A=(c[o>>2]|0)+(g*96|0)|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=q+34|0;z=E;y=c[z+4>>2]|0;c[A>>2]=c[z>>2];c[A+4>>2]=y;A=E;c[A>>2]=B;c[A+4>>2]=D;A=(c[o>>2]|0)+(((c[G>>2]|0)+1|0)*96|0)+8|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=E+8|0;y=E;z=c[y+4>>2]|0;c[A>>2]=c[y>>2];c[A+4>>2]=z;c[E>>2]=B;c[E+4>>2]=D}if(h){E=p+(m*96|0)+32|0;D=E;B=c[D>>2]|0;D=c[D+4>>2]|0;A=k+-14|0;z=A;y=c[z+4>>2]|0;c[E>>2]=c[z>>2];c[E+4>>2]=y;E=A;c[E>>2]=B;c[E+4>>2]=D;E=p+(m*96|0)+40|0;D=E;B=c[D>>2]|0;D=c[D+4>>2]|0;A=A+8|0;y=A;z=c[y+4>>2]|0;c[E>>2]=c[y>>2];c[E+4>>2]=z;c[A>>2]=B;c[A+4>>2]=D;A=p+(m*96|0)+48|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=l+-14|0;z=E;y=c[z+4>>2]|0;c[A>>2]=c[z>>2];c[A+4>>2]=y;A=E;c[A>>2]=B;c[A+4>>2]=D;A=p+(m*96|0)+56|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=E+8|0;y=E;z=c[y+4>>2]|0;c[A>>2]=c[y>>2];c[A+4>>2]=z;c[E>>2]=B;c[E+4>>2]=D}E=p+(n*96|0)+32|0;D=E;B=c[D>>2]|0;D=c[D+4>>2]|0;A=k+1+1|0;z=A;y=c[z+4>>2]|0;c[E>>2]=c[z>>2];c[E+4>>2]=y;E=A;c[E>>2]=B;c[E+4>>2]=D;E=p+(n*96|0)+40|0;D=E;B=c[D>>2]|0;D=c[D+4>>2]|0;A=A+8|0;y=A;z=c[y+4>>2]|0;c[E>>2]=c[y>>2];c[E+4>>2]=z;c[A>>2]=B;c[A+4>>2]=D;A=p+(n*96|0)+48|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=l+1+1|0;z=E;y=c[z+4>>2]|0;c[A>>2]=c[z>>2];c[A+4>>2]=y;A=E;c[A>>2]=B;c[A+4>>2]=D;A=p+(n*96|0)+56|0;D=A;B=c[D>>2]|0;D=c[D+4>>2]|0;E=E+8|0;y=E;z=c[y+4>>2]|0;c[A>>2]=c[y>>2];c[A+4>>2]=z;c[E>>2]=B;c[E+4>>2]=D}}g=f+21632|0;jb[c[b+26128+(c[g>>2]<<2)>>2]&255](I,O);jb[c[b+26128+(c[g>>2]<<2)>>2]&255](J,O);g=c[f+824>>2]|0;b:do if(!(L&1)){jb[c[b+26172+(c[f+21636>>2]<<2)>>2]&255](H,K);if(a[f+43648>>0]|0)ob[c[b+132>>2]&1023](f+44384|0,f+47456|0,c[(c[b+197556>>2]|0)+(g<<6)>>2]|0)}else{if(L&16777216|0){h=c[b+112>>2]|0;j=c[b+104>>2]|0;k=f+21732|0;l=f+21740|0;m=f+44384|0;o=0;while(1){n=H+(c[b+26260+(o<<2)>>2]|0)|0;D=d[96537+o>>0]|0;E=a[f+43648+D>>0]|0;ub[c[b+26080+(a[f+21640+D>>0]<<24>>24<<2)>>2]&255](n,c[k>>2]<<o&32768,c[l>>2]<<o&16384,K);switch(E<<24>>24){case 0:break;case 1:{g=o<<4;if(!(c[m+(g<<2)>>2]|0))t=20;else ob[h&1023](n,f+44384+(o<<5<<1)|0,K);break}default:{g=o<<4;t=20}}if((t|0)==20){t=0;ob[j&1023](n,f+44384+(g<<1<<1)|0,K)}o=o+4|0;if((o|0)>=16)break b}}k=c[b+108>>2]|0;l=c[b+100>>2]|0;m=f+21740|0;n=3-((K|0)/2|0)|0;o=0-K|0;p=f+44384|0;r=0;while(1){q=H+(c[b+26260+(r<<2)>>2]|0)|0;g=d[96537+r>>0]|0;E=a[f+21640+g>>0]|0;h=E<<24>>24;do if((E|4)<<24>>24==7)if(!(c[m>>2]&32768>>>r)){E=RF(e[q+(n<<1)>>1]|0,0,65537,65537)|0;j=Q;c[j>>2]=E;c[j+4>>2]=C;j=Q;break}else{j=q+8+o|0;break}else j=0;while(0);ob[c[b+26020+(h<<2)>>2]&1023](q,j,K);switch(a[f+43648+g>>0]|0){case 0:break;case 1:{g=r<<4;if(!(c[p+(g<<2)>>2]|0))t=31;else ob[k&1023](q,f+44384+(r<<5<<1)|0,K);break}default:{g=r<<4;t=31}}if((t|0)==31){t=0;ob[l&1023](q,f+44384+(g<<1<<1)|0,K)}r=r+1|0;if((r|0)==16)break b}}while(0);switch(c[s>>2]|0){case 0:break a;case 2:{q=c[G>>2]|0;h=c[f+21708>>2]|0;g=(e[(c[b+197604>>2]|0)+((c[u>>2]|0)+-1-(c[b+198236>>2]|0)<<1)>>1]|0)==(c[f+808>>2]|0)&1;break}default:{g=c[G>>2]|0;q=g;h=(c[F>>2]|0)>((c[f+21796>>2]|0)!=0|0)&1;g=(g|0)>0&1}}k=H+(-2-K)|0;m=-2-O|0;l=I+m|0;m=J+m|0;n=q+-1|0;o=f+43628|0;p=c[o>>2]|0;j=p+(q*96|0)|0;if(h|0){h=(g|0)!=0;if(h){B=p+(n*96|0)+16|0;E=B;D=c[E>>2]|0;E=c[E+4>>2]|0;F=k+-14|0;A=F;z=c[A+4>>2]|0;c[B>>2]=c[A>>2];c[B+4>>2]=z;B=F;c[B>>2]=D;c[B+4>>2]=E;B=p+(n*96|0)+24|0;E=B;D=c[E>>2]|0;E=c[E+4>>2]|0;F=F+8|0;z=F;A=c[z+4>>2]|0;c[B>>2]=c[z>>2];c[B+4>>2]=A;c[F>>2]=D;c[F+4>>2]=E}g=j;F=c[g+4>>2]|0;D=k+2|0;E=D;c[E>>2]=c[g>>2];c[E+4>>2]=F;E=p+(q*96|0)+8|0;F=c[E+4>>2]|0;D=D+8|0;c[D>>2]=c[E>>2];c[D+4>>2]=F;D=p+(q*96|0)+16|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;g=k+18|0;B=g;A=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=A;D=g;c[D>>2]=E;c[D+4>>2]=F;D=p+(q*96|0)+24|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;g=g+8|0;A=g;B=c[A+4>>2]|0;c[D>>2]=c[A>>2];c[D+4>>2]=B;c[g>>2]=E;c[g+4>>2]=F;g=(c[G>>2]|0)+1|0;if((g|0)<(c[b+198232>>2]|0)){D=(c[o>>2]|0)+(g*96|0)|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;A=k+34|0;z=A;B=c[z+4>>2]|0;c[D>>2]=c[z>>2];c[D+4>>2]=B;D=A;c[D>>2]=E;c[D+4>>2]=F;D=(c[o>>2]|0)+(((c[G>>2]|0)+1|0)*96|0)+8|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=A+8|0;A=G;B=c[A+4>>2]|0;c[D>>2]=c[A>>2];c[D+4>>2]=B;c[G>>2]=E;c[G+4>>2]=F}if(h){G=p+(n*96|0)+32|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;D=l+-14|0;B=D;A=c[B+4>>2]|0;c[G>>2]=c[B>>2];c[G+4>>2]=A;G=D;c[G>>2]=E;c[G+4>>2]=F;G=p+(n*96|0)+40|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;D=D+8|0;A=D;B=c[A+4>>2]|0;c[G>>2]=c[A>>2];c[G+4>>2]=B;c[D>>2]=E;c[D+4>>2]=F;D=p+(n*96|0)+48|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=m+-14|0;B=G;A=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=A;D=G;c[D>>2]=E;c[D+4>>2]=F;D=p+(n*96|0)+56|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=G+8|0;A=G;B=c[A+4>>2]|0;c[D>>2]=c[A>>2];c[D+4>>2]=B;c[G>>2]=E;c[G+4>>2]=F}G=p+(q*96|0)+32|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;D=l+1+1|0;B=D;A=c[B+4>>2]|0;c[G>>2]=c[B>>2];c[G+4>>2]=A;G=D;c[G>>2]=E;c[G+4>>2]=F;G=p+(q*96|0)+40|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;D=D+8|0;A=D;B=c[A+4>>2]|0;c[G>>2]=c[A>>2];c[G+4>>2]=B;c[D>>2]=E;c[D+4>>2]=F;D=p+(q*96|0)+48|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=m+1+1|0;B=G;A=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=A;D=G;c[D>>2]=E;c[D+4>>2]=F;D=p+(q*96|0)+56|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=G+8|0;A=G;B=c[A+4>>2]|0;c[D>>2]=c[A>>2];c[D+4>>2]=B;c[G>>2]=E;c[G+4>>2]=F}}else{n=c[b+203640>>2]|0;j=b+152|0;y=c[b+203644>>2]|0;k=b+168|0;l=b+16|0;m=b+32|0;z=c[(c[g>>2]|0)+(c[u>>2]<<2)>>2]|0;g=a[f+44108>>0]|0;h=g<<24>>24>-1;if(N){if(h){w=g<<24>>24;x=c[f+43824>>2]|0;u=c[G>>2]|0;v=(u<<4|8)+(x<<16>>18)|0;x=(c[F>>2]<<4)+(x>>18)|0;u=(v<<1)+128+(_(x+(u<<2&12)|0,K)|0)|0;ob[c[D>>2]&1023]((c[f+23384+(w*40|0)>>2]|0)+u|0,K,4);v=(_((c[G>>2]&7)+(x>>1)|0,c[B>>2]|0)|0)+(v+128&-2)|0;x=c[f+23384+(w*40|0)+4>>2]|0;ob[c[D>>2]&1023](x+v|0,(c[f+23384+(w*40|0)+8>>2]|0)-x|0,2)}do if(!(z&8)){if(z&16|0){x=n+64|0;y=y+64|0;Bc(b,f,0,0,8,16,H,I,J,0,0,x,c[j>>2]|0,y,c[k>>2]|0,l,m,z&4096,z&16384);Bc(b,f,8,0,8,16,H,I,J,0,4,x,c[j>>2]|0,y,c[k>>2]|0,l,m,z&8192,z&32768);break}if(z&32|0){t=n+64|0;u=b+156|0;v=y+64|0;w=b+172|0;x=b+20|0;y=b+36|0;Bc(b,f,0,0,16,c[E>>2]<<3,H,I,J,0,0,t,c[u>>2]|0,v,c[w>>2]|0,x,y,z&4096,z&16384);Bc(b,f,4,0,16,c[E>>2]<<3,H,I,J,4,0,t,c[u>>2]|0,v,c[w>>2]|0,x,y,z&8192,z&32768);break}o=n+128|0;p=b+160|0;q=y+128|0;r=b+176|0;s=b+24|0;t=b+40|0;u=b+156|0;v=b+172|0;w=b+20|0;x=b+36|0;n=n+64|0;g=y+64|0;m=0;do{h=e[f+44376+(m<<1)>>1]|0;j=m<<2;k=j&4;l=m<<1&4;do if(!(h&8)){if(h&16|0){S=h&4096;y=h&16384;Bc(b,f,j,0,4,8,H,I,J,k,l,o,c[u>>2]|0,q,c[v>>2]|0,w,x,S,y);Bc(b,f,j|2,0,4,8,H,I,J,k,l|2,o,c[u>>2]|0,q,c[v>>2]|0,w,x,S,y);break}if(!(h&32)){y=h&4096;S=h&16384;Bc(b,f,j,1,4,0,H,I,J,k,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);h=k|2;Bc(b,f,j|1,1,4,0,H,I,J,h,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);l=l|2;Bc(b,f,j|2,1,4,0,H,I,J,k,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);Bc(b,f,j|3,1,4,0,H,I,J,h,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);break}else{y=h&4096;S=h&16384;Bc(b,f,j,0,8,c[E>>2]<<2,H,I,J,k,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);Bc(b,f,j|1,0,8,c[E>>2]<<2,H,I,J,k|2,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);break}}else Bc(b,f,j,1,8,0,H,I,J,k,l,n,c[u>>2]|0,g,c[v>>2]|0,w,x,h&4096,h&16384);while(0);m=m+1|0}while((m|0)!=4)}else Bc(b,f,0,1,16,0,H,I,J,0,0,n,c[j>>2]|0,y,c[k>>2]|0,l,m,z&4096,z&16384);while(0);if(!(z&49152))break;g=a[f+44148>>0]|0;if(g<<24>>24<=-1)break;z=g<<24>>24;x=c[f+43984>>2]|0;y=c[G>>2]|0;S=(y<<4|8)+(x<<16>>18)|0;F=(c[F>>2]<<4)+(x>>18)|0;E=(S<<1)+128+(_(F+(y<<2&12)|0,c[E>>2]|0)|0)|0;ob[c[D>>2]&1023]((c[f+25304+(z*40|0)>>2]|0)+E|0,c[A>>2]|0,4);G=(_((c[G>>2]&7)+(F>>1)|0,c[B>>2]|0)|0)+(S+128&-2)|0;S=c[f+25304+(z*40|0)+4>>2]|0;ob[c[D>>2]&1023](S+G|0,(c[f+25304+(z*40|0)+8>>2]|0)-S|0,2);break}else{if(h){x=g<<24>>24;S=c[f+43824>>2]|0;v=c[G>>2]|0;w=(v<<4|8)+(S<<16>>18)|0;S=(c[F>>2]<<4)+(S>>18)|0;v=(w<<1)+128+(_(S+(v<<2&12)|0,K)|0)|0;ob[c[D>>2]&1023]((c[f+23384+(x*40|0)>>2]|0)+v|0,K,4);w=(_((c[G>>2]&7)+(S>>1)|0,c[B>>2]|0)|0)+(w+128&-2)|0;S=c[f+23384+(x*40|0)+4>>2]|0;ob[c[D>>2]&1023](S+w|0,(c[f+23384+(x*40|0)+8>>2]|0)-S|0,2)}do if(!(z&8)){if(z&16|0){x=n+64|0;S=y+64|0;Cc(b,f,0,0,8,16,H,I,J,0,0,x,c[j>>2]|0,S,c[k>>2]|0,l,m,z&4096,z&16384);Cc(b,f,8,0,8,16,H,I,J,0,4,x,c[j>>2]|0,S,c[k>>2]|0,l,m,z&8192,z&32768);break}if(z&32|0){u=n+64|0;v=b+156|0;w=y+64|0;x=b+172|0;y=b+20|0;S=b+36|0;Cc(b,f,0,0,16,c[E>>2]<<3,H,I,J,0,0,u,c[v>>2]|0,w,c[x>>2]|0,y,S,z&4096,z&16384);Cc(b,f,4,0,16,c[E>>2]<<3,H,I,J,4,0,u,c[v>>2]|0,w,c[x>>2]|0,y,S,z&8192,z&32768);break}o=n+128|0;p=b+160|0;q=y+128|0;r=b+176|0;s=b+24|0;t=b+40|0;u=b+156|0;v=b+172|0;w=b+20|0;x=b+36|0;n=n+64|0;g=y+64|0;m=0;do{h=e[f+44376+(m<<1)>>1]|0;j=m<<2;k=j&4;l=m<<1&4;do if(!(h&8)){if(h&16|0){y=h&4096;S=h&16384;Cc(b,f,j,0,4,8,H,I,J,k,l,o,c[u>>2]|0,q,c[v>>2]|0,w,x,y,S);Cc(b,f,j|2,0,4,8,H,I,J,k,l|2,o,c[u>>2]|0,q,c[v>>2]|0,w,x,y,S);break}if(!(h&32)){y=h&4096;S=h&16384;Cc(b,f,j,1,4,0,H,I,J,k,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);h=k|2;Cc(b,f,j|1,1,4,0,H,I,J,h,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);l=l|2;Cc(b,f,j|2,1,4,0,H,I,J,k,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);Cc(b,f,j|3,1,4,0,H,I,J,h,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);break}else{y=h&4096;S=h&16384;Cc(b,f,j,0,8,c[E>>2]<<2,H,I,J,k,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);Cc(b,f,j|1,0,8,c[E>>2]<<2,H,I,J,k|2,l,o,c[p>>2]|0,q,c[r>>2]|0,s,t,y,S);break}}else Cc(b,f,j,1,8,0,H,I,J,k,l,n,c[u>>2]|0,g,c[v>>2]|0,w,x,h&4096,h&16384);while(0);m=m+1|0}while((m|0)!=4)}else Cc(b,f,0,1,16,0,H,I,J,0,0,n,c[j>>2]|0,y,c[k>>2]|0,l,m,z&4096,z&16384);while(0);if(!(z&49152))break;g=a[f+44148>>0]|0;if(g<<24>>24<=-1)break;z=g<<24>>24;x=c[f+43984>>2]|0;y=c[G>>2]|0;S=(y<<4|8)+(x<<16>>18)|0;F=(c[F>>2]<<4)+(x>>18)|0;E=(S<<1)+128+(_(F+(y<<2&12)|0,c[E>>2]|0)|0)|0;ob[c[D>>2]&1023]((c[f+25304+(z*40|0)>>2]|0)+E|0,c[A>>2]|0,4);G=(_((c[G>>2]&7)+(F>>1)|0,c[B>>2]|0)|0)+(S+128&-2)|0;S=c[f+25304+(z*40|0)+4>>2]|0;ob[c[D>>2]&1023](S+G|0,(c[f+25304+(z*40|0)+8>>2]|0)-S|0,2);break}}while(0);do if(!(L&1)){if(L&2|0){fb[c[b+128>>2]&127](H,M,f+44384|0,K,f+43648|0);break}if(c[f+21824>>2]&15|0)if(!(L&16777216)){fb[c[b+116>>2]&127](H,M,f+44384|0,K,f+43648|0);break}else{fb[c[b+120>>2]&127](H,M,f+44384|0,K,f+43648|0);break}}while(0);if(!(c[f+21824>>2]&48)){i=R;return}c[Q>>2]=I;c[Q+4>>2]=J;g=c[f+828>>2]|0;h=c[f+832>>2]|0;if(a[f+43688>>0]|0)jb[c[b+136>>2]&255](f+45408|0,c[(c[b+197556+((P?1:4)<<2)>>2]|0)+((N?g+3|0:g)<<6)>>2]|0);if(a[f+43728>>0]|0)jb[c[b+136>>2]&255](f+46432|0,c[(c[b+197556+((P?2:5)<<2)>>2]|0)+((N?h+3|0:h)<<6)>>2]|0);fb[c[b+124>>2]&127](Q,M,f+44384|0,O,f+43648|0);i=R;return}function Bc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;O=f+856|0;x=c[O>>2]|0;A=(v|0)!=0;B=(w|0)!=0;if(B&(A&(x|0)==2)){y=f+21768|0;z=c[y>>2]|0;T=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+T>>0]|0)*384|0)+(a[f+44136+T>>0]<<3)+((z&1)<<2)>>2]|0)==32)U=3;else N=y}else U=3;do if((U|0)==3){if((x|0)==1){z=f+21768|0;N=z;z=c[z>>2]|0;break}J=o<<1;H=f+21756|0;z=c[H>>2]|0;G=k+((_(z,J)|0)+(n<<2))|0;K=f+21760|0;J=(_(c[K>>2]|0,J)|0)+(n<<1)|0;I=l+J|0;J=m+J|0;D=(c[f+21764>>2]<<3)+n|0;F=f+21796|0;x=c[F>>2]|0;E=(c[f+21768>>2]>>x<<3)+o|0;if(!v){s=q;r=p}else{m=d[96537+g>>0]|0;A=a[f+44096+m>>0]|0;l=(b[f+43776+(m<<2)>>1]|0)+(D<<3)|0;m=(b[f+43776+(m<<2)+2>>1]|0)+(E<<3)|0;v=m<<2&12|l&3;u=l>>2;n=m>>2;y=(_(n,z)|0)+(u<<1)|0;y=(c[f+23384+(A*40|0)>>2]|0)+y|0;o=c[e+198232>>2]|0;k=o<<4;B=c[e+198228>>2]<<4>>x;C=l&7;x=(C|0)==0?0:-3;t=(m&7|0)==0?0:-3;if((u|0)<(0-x|0)|(n|0)<(0-t|0))U=42;else if((u+16|0)>(x+k|0))U=42;else if((n+16|0)>(B+t|0))U=42;else u=0;if((U|0)==42){T=f+43620|0;eb[c[e+8>>2]&3](c[T>>2]|0,y+-4+(0-(z<<1))|0,z,z,21,21,u+-2|0,n+-2|0,k,B);y=c[H>>2]|0;z=y;u=1;y=(c[T>>2]|0)+4+(y<<1)|0}x=p+(v<<2)|0;ob[c[x>>2]&1023](G,y,z);if(!h)ob[c[x>>2]&1023](G+j|0,y+j|0,c[H>>2]|0);y=l>>3;p=y<<1;z=c[K>>2]|0;x=_(z,n)|0;t=(c[f+23384+(A*40|0)+4>>2]|0)+p+x|0;x=(c[f+23384+(A*40|0)+8>>2]|0)+p+x|0;if(!u){y=m<<1&6;rb[q&63](I,t,z,i,C,y)}else{Q=e+8|0;p=f+43620|0;S=o<<3;eb[c[Q>>2]&3](c[p>>2]|0,t,z,z,9,17,y,n,S,B);T=m<<1&6;rb[q&63](I,c[p>>2]|0,c[K>>2]|0,i,C,T);R=c[K>>2]|0;eb[c[Q>>2]&3](c[p>>2]|0,x,R,R,9,17,y,n,S,B);y=T;x=c[p>>2]|0}rb[q&63](J,x,c[K>>2]|0,i,C,y)}if(!w)return;B=d[96537+g>>0]|0;o=a[f+44136+B>>0]|0;l=(b[f+43936+(B<<2)>>1]|0)+(D<<3)|0;B=(b[f+43936+(B<<2)+2>>1]|0)+(E<<3)|0;v=B<<2&12|l&3;u=l>>2;m=B>>2;z=c[H>>2]|0;y=(_(m,z)|0)+(u<<1)|0;y=(c[f+25304+(o*40|0)>>2]|0)+y|0;n=c[e+198232>>2]|0;k=n<<4;A=c[e+198228>>2]<<4>>c[F>>2];C=l&7;x=(C|0)==0?0:-3;t=(B&7|0)==0?0:-3;if((u|0)<(0-x|0)|(m|0)<(0-t|0))U=53;else if((u+16|0)>(k+x|0))U=53;else if((m+16|0)>(A+t|0))U=53;else u=0;if((U|0)==53){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,y+-4+(0-(z<<1))|0,z,z,21,21,u+-2|0,m+-2|0,k,A);y=c[H>>2]|0;z=y;u=1;y=(c[U>>2]|0)+4+(y<<1)|0}x=r+(v<<2)|0;ob[c[x>>2]&1023](G,y,z);if(!h)ob[c[x>>2]&1023](G+j|0,y+j|0,c[H>>2]|0);y=l>>3;j=y<<1;z=c[K>>2]|0;x=_(z,m)|0;t=(c[f+25304+(o*40|0)+4>>2]|0)+j+x|0;x=(c[f+25304+(o*40|0)+8>>2]|0)+j+x|0;if(!u){y=B<<1&6;rb[s&63](I,t,z,i,C,y)}else{q=e+8|0;f=f+43620|0;j=n<<3;eb[c[q>>2]&3](c[f>>2]|0,t,z,z,9,17,y,m,j,A);e=B<<1&6;rb[s&63](I,c[f>>2]|0,c[K>>2]|0,i,C,e);U=c[K>>2]|0;eb[c[q>>2]&3](c[f>>2]|0,x,U,U,9,17,y,m,j,A);y=e;x=c[f>>2]|0}rb[s&63](J,x,c[K>>2]|0,i,C,y);return}while(0);s=c[t>>2]|0;C=c[t+4>>2]|0;M=c[u>>2]|0;L=c[u+4>>2]|0;P=o<<1;T=f+21756|0;F=c[T>>2]|0;R=k+((_(F,P)|0)+(n<<2))|0;S=f+21760|0;x=c[S>>2]|0;P=(_(x,P)|0)+(n<<1)|0;Q=l+P|0;P=m+P|0;y=(c[f+21764>>2]<<3)+n|0;w=f+21796|0;l=c[w>>2]|0;z=(z>>l<<3)+o|0;if(!(A&B)){o=B&1;A=d[96537+g>>0]|0;n=a[f+44096+(o*40|0)+A>>0]|0;v=(b[f+43776+(o*160|0)+(A<<2)>>1]|0)+(y<<3)|0;A=(b[f+43776+(o*160|0)+(A<<2)+2>>1]|0)+(z<<3)|0;k=A<<2&12|v&3;t=v>>2;r=A>>2;y=(_(r,F)|0)+(t<<1)|0;y=(c[f+23384+(o*1920|0)+(n*40|0)>>2]|0)+y|0;B=c[e+198232>>2]|0;u=B<<4;l=c[e+198228>>2]<<4>>l;m=v&7;x=(m|0)==0?0:-3;z=(A&7|0)==0?0:-3;if((t|0)<(0-x|0)|(r|0)<(0-z|0))U=30;else if((t+16|0)>(x+u|0))U=30;else if((r+16|0)>(l+z|0))U=30;else{z=F;u=0}if((U|0)==30){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,y+-4+(0-(F<<1))|0,F,F,21,21,t+-2|0,r+-2|0,u,l);y=c[T>>2]|0;z=y;u=1;y=(c[U>>2]|0)+4+(y<<1)|0}x=p+(k<<2)|0;ob[c[x>>2]&1023](R,y,z);if(!h)ob[c[x>>2]&1023](R+j|0,y+j|0,c[T>>2]|0);y=v>>3;j=y<<1;z=c[S>>2]|0;x=_(z,r)|0;t=(c[f+23384+(o*1920|0)+(n*40|0)+4>>2]|0)+j+x|0;x=(c[f+23384+(o*1920|0)+(n*40|0)+8>>2]|0)+j+x|0;if(!u){y=A<<1&6;rb[q&63](Q,t,z,i,m,y)}else{p=e+8|0;e=f+43620|0;U=B<<3;eb[c[p>>2]&3](c[e>>2]|0,t,z,z,9,17,y,r,U,l);j=A<<1&6;rb[q&63](Q,c[e>>2]|0,c[S>>2]|0,i,m,j);h=c[S>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,17,y,r,U,l);y=j;x=c[e>>2]|0}rb[q&63](P,x,c[S>>2]|0,i,m,y);rb[s&63](R,c[T>>2]|0,i,c[f+864>>2]|0,c[f+888+(n<<4)+(o<<3)>>2]|0,c[f+888+(n<<4)+(o<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;e=f+868|0;rb[C&63](Q,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(n<<5)+(o<<4)>>2]|0,c[f+1656+(n<<5)+(o<<4)+4>>2]|0);rb[C&63](P,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(n<<5)+(o<<4)+8>>2]|0,c[f+1656+(n<<5)+(o<<4)+12>>2]|0);return}J=c[f+43616>>2]|0;K=J+32|0;I=J+(x<<4)|0;C=d[96537+g>>0]|0;G=a[f+44096+C>>0]|0;H=a[f+44136+C>>0]|0;n=y<<3;v=(b[f+43776+(C<<2)>>1]|0)+n|0;o=z<<3;A=(b[f+43776+(C<<2)+2>>1]|0)+o|0;k=A<<2&12|v&3;t=v>>2;r=A>>2;z=(_(r,F)|0)+(t<<1)|0;z=(c[f+23384+(G*40|0)>>2]|0)+z|0;D=e+198232|0;B=c[D>>2]|0;u=B<<4;E=e+198228|0;l=c[E>>2]<<4>>l;m=v&7;x=(m|0)==0?0:-3;y=(A&7|0)==0?0:-3;if((t|0)<(0-x|0)|(r|0)<(0-y|0))U=9;else if((t+16|0)>(x+u|0))U=9;else if((r+16|0)>(l+y|0))U=9;else{y=F;u=0}if((U|0)==9){g=f+43620|0;eb[c[e+8>>2]&3](c[g>>2]|0,z+-4+(0-(F<<1))|0,F,F,21,21,t+-2|0,r+-2|0,u,l);z=c[T>>2]|0;y=z;u=1;z=(c[g>>2]|0)+4+(z<<1)|0}x=p+(k<<2)|0;ob[c[x>>2]&1023](R,z,y);s=(h|0)!=0;if(!s)ob[c[x>>2]&1023](R+j|0,z+j|0,c[T>>2]|0);y=v>>3;h=y<<1;z=c[S>>2]|0;x=_(z,r)|0;t=(c[f+23384+(G*40|0)+4>>2]|0)+h+x|0;x=(c[f+23384+(G*40|0)+8>>2]|0)+h+x|0;if(!u){y=A<<1&6;rb[q&63](Q,t,z,i,m,y)}else{v=e+8|0;h=f+43620|0;F=B<<3;eb[c[v>>2]&3](c[h>>2]|0,t,z,z,9,17,y,r,F,l);g=A<<1&6;rb[q&63](Q,c[h>>2]|0,c[S>>2]|0,i,m,g);B=c[S>>2]|0;eb[c[v>>2]&3](c[h>>2]|0,x,B,B,9,17,y,r,F,l);y=g;x=c[h>>2]|0}rb[q&63](P,x,c[S>>2]|0,i,m,y);A=(b[f+43936+(C<<2)>>1]|0)+n|0;m=(b[f+43936+(C<<2)+2>>1]|0)+o|0;v=m<<2&12|A&3;k=A>>2;n=m>>2;z=c[T>>2]|0;y=(_(n,z)|0)+(k<<1)|0;y=(c[f+25304+(H*40|0)>>2]|0)+y|0;r=c[D>>2]|0;u=r<<4;l=c[E>>2]<<4>>c[w>>2];B=A&7;x=(B|0)==0?0:-3;t=(m&7|0)==0?0:-3;if((k|0)<(0-x|0)|(n|0)<(0-t|0))U=18;else if((k+16|0)>(u+x|0))U=18;else if((n+16|0)>(l+t|0))U=18;else u=0;if((U|0)==18){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,y+-4+(0-(z<<1))|0,z,z,21,21,k+-2|0,n+-2|0,u,l);y=c[T>>2]|0;z=y;u=1;y=(c[U>>2]|0)+4+(y<<1)|0}x=p+(v<<2)|0;ob[c[x>>2]&1023](I,y,z);if(!s)ob[c[x>>2]&1023](I+j|0,y+j|0,c[T>>2]|0);y=A>>3;j=y<<1;z=c[S>>2]|0;x=_(z,n)|0;t=(c[f+25304+(H*40|0)+4>>2]|0)+j+x|0;x=(c[f+25304+(H*40|0)+8>>2]|0)+j+x|0;if(!u){y=m<<1&6;rb[q&63](J,t,z,i,B,y)}else{p=e+8|0;e=f+43620|0;U=r<<3;eb[c[p>>2]&3](c[e>>2]|0,t,z,z,9,17,y,n,U,l);j=m<<1&6;rb[q&63](J,c[e>>2]|0,c[S>>2]|0,i,B,j);h=c[S>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,17,y,n,U,l);y=j;x=c[e>>2]|0}rb[q&63](K,x,c[S>>2]|0,i,B,y);if((c[O>>2]|0)==2){e=c[f+3192+(G*384|0)+(H<<3)+((c[N>>2]&1)<<2)>>2]|0;f=64-e|0;pb[M&31](R,I,c[T>>2]|0,i,5,e,f,0);pb[L&31](Q,J,c[S>>2]|0,i,5,e,f,0);pb[L&31](P,K,c[S>>2]|0,i,5,e,f,0);return}else{pb[M&31](R,I,c[T>>2]|0,i,c[f+864>>2]|0,c[f+888+(G<<4)>>2]|0,c[f+888+(H<<4)+8>>2]|0,(c[f+888+(H<<4)+12>>2]|0)+(c[f+888+(G<<4)+4>>2]|0)|0);e=f+868|0;pb[L&31](Q,J,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(G<<5)>>2]|0,c[f+1656+(H<<5)+16>>2]|0,(c[f+1656+(H<<5)+20>>2]|0)+(c[f+1656+(G<<5)+4>>2]|0)|0);pb[L&31](P,K,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(G<<5)+8>>2]|0,c[f+1656+(H<<5)+24>>2]|0,(c[f+1656+(H<<5)+28>>2]|0)+(c[f+1656+(G<<5)+12>>2]|0)|0);return}}function Cc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0;N=f+856|0;x=c[N>>2]|0;A=(v|0)!=0;B=(w|0)!=0;if(B&(A&(x|0)==2)){y=f+21768|0;z=c[y>>2]|0;U=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+U>>0]|0)*384|0)+(a[f+44136+U>>0]<<3)+((z&1)<<2)>>2]|0)==32)V=3;else O=y}else V=3;do if((V|0)==3){if((x|0)==1){z=f+21768|0;O=z;z=c[z>>2]|0;break}I=f+21756|0;t=c[I>>2]|0;H=k+((_(o<<1,t)|0)+(n<<2))|0;L=f+21760|0;K=(_(c[L>>2]|0,o)|0)+(n<<1)|0;J=l+K|0;K=m+K|0;D=(c[f+21764>>2]<<3)+n|0;F=f+21768|0;G=f+21796|0;x=c[G>>2]|0;E=(c[F>>2]>>x<<3)+o|0;if(!v){s=q;r=p}else{y=d[96537+g>>0]|0;m=a[f+44096+y>>0]|0;B=(b[f+43776+(y<<2)>>1]|0)+(D<<3)|0;y=(b[f+43776+(y<<2)+2>>1]|0)+(E<<3)|0;A=y<<2&12|B&3;k=B>>2;v=y>>2;z=(_(v,t)|0)+(k<<1)|0;z=(c[f+23384+(m*40|0)>>2]|0)+z|0;o=c[e+198232>>2]|0;l=o<<4;n=c[e+198228>>2]<<4>>x;C=B&7;x=(C|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))V=54;else if((k+16|0)>(x+l|0))V=54;else if((v+16|0)>(n+u|0))V=54;else v=0;if((V|0)==54){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,z+-4+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,n);z=c[I>>2]|0;t=z;v=1;z=(c[U>>2]|0)+4+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!h)ob[c[x>>2]&1023](H+j|0,z+j|0,c[I>>2]|0);if(c[G>>2]|0){y=(1-(c[f+23384+(m*40|0)+24>>2]|0)+(c[F>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(n>>1|0);v=x&1|v}z=B>>3;p=z<<1;t=y>>3;u=c[L>>2]|0;x=_(u,t)|0;k=(c[f+23384+(m*40|0)+4>>2]|0)+p+x|0;x=(c[f+23384+(m*40|0)+8>>2]|0)+p+x|0;if(!v){p=i>>1;z=y&7;rb[q&63](J,k,u,p,C,z);y=p}else{Q=e+8|0;p=f+43620|0;R=o<<3;S=n>>1;eb[c[Q>>2]&3](c[p>>2]|0,k,u,u,9,9,z,t,R,S);T=i>>1;U=y&7;rb[q&63](J,c[p>>2]|0,c[L>>2]|0,T,C,U);y=c[L>>2]|0;eb[c[Q>>2]&3](c[p>>2]|0,x,y,y,9,9,z,t,R,S);y=T;z=U;x=c[p>>2]|0}rb[q&63](K,x,c[L>>2]|0,y,C,z)}if(!w)return;y=d[96537+g>>0]|0;o=a[f+44136+y>>0]|0;B=(b[f+43936+(y<<2)>>1]|0)+(D<<3)|0;y=(b[f+43936+(y<<2)+2>>1]|0)+(E<<3)|0;A=y<<2&12|B&3;u=B>>2;k=y>>2;t=c[I>>2]|0;z=(_(k,t)|0)+(u<<1)|0;z=(c[f+25304+(o*40|0)>>2]|0)+z|0;m=c[e+198232>>2]|0;v=m<<4;n=c[e+198228>>2]<<4>>c[G>>2];C=B&7;x=(C|0)==0?0:-3;l=(y&7|0)==0?0:-3;if((u|0)<(0-x|0)|(k|0)<(0-l|0))V=69;else if((u+16|0)>(v+x|0))V=69;else if((k+16|0)>(n+l|0))V=69;else v=0;if((V|0)==69){q=f+43620|0;eb[c[e+8>>2]&3](c[q>>2]|0,z+-4+(0-(t<<1))|0,t,t,21,21,u+-2|0,k+-2|0,v,n);z=c[I>>2]|0;t=z;v=1;z=(c[q>>2]|0)+4+(z<<1)|0}x=r+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!h)ob[c[x>>2]&1023](H+j|0,z+j|0,c[I>>2]|0);if(c[G>>2]|0){y=(1-(c[f+25304+(o*40|0)+24>>2]|0)+(c[F>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(n>>1|0);v=x&1|v}z=B>>3;q=z<<1;t=y>>3;u=c[L>>2]|0;x=_(u,t)|0;k=(c[f+25304+(o*40|0)+4>>2]|0)+q+x|0;x=(c[f+25304+(o*40|0)+8>>2]|0)+q+x|0;if(!v){f=i>>1;z=y&7;rb[s&63](J,k,u,f,C,z);y=f}else{V=e+8|0;f=f+43620|0;j=m<<3;q=n>>1;eb[c[V>>2]&3](c[f>>2]|0,k,u,u,9,9,z,t,j,q);e=i>>1;i=y&7;rb[s&63](J,c[f>>2]|0,c[L>>2]|0,e,C,i);y=c[L>>2]|0;eb[c[V>>2]&3](c[f>>2]|0,x,y,y,9,9,z,t,j,q);y=e;z=i;x=c[f>>2]|0}rb[s&63](K,x,c[L>>2]|0,y,C,z);return}while(0);D=c[t>>2]|0;s=c[t+4>>2]|0;L=c[u>>2]|0;K=c[u+4>>2]|0;U=f+21756|0;t=c[U>>2]|0;R=k+((_(o<<1,t)|0)+(n<<2))|0;S=i>>1;T=f+21760|0;x=c[T>>2]|0;P=(_(x,o)|0)+(n<<1)|0;Q=l+P|0;P=m+P|0;y=(c[f+21764>>2]<<3)+n|0;M=f+21796|0;u=c[M>>2]|0;z=(z>>u<<3)+o|0;if(!(A&B)){C=B&1;A=d[96537+g>>0]|0;o=a[f+44096+(C*40|0)+A>>0]|0;r=(b[f+43776+(C*160|0)+(A<<2)>>1]|0)+(y<<3)|0;y=(b[f+43776+(C*160|0)+(A<<2)+2>>1]|0)+(z<<3)|0;A=y<<2&12|r&3;k=r>>2;v=y>>2;z=(_(v,t)|0)+(k<<1)|0;z=(c[f+23384+(C*1920|0)+(o*40|0)>>2]|0)+z|0;m=c[e+198232>>2]|0;l=m<<4;B=c[e+198228>>2]<<4>>u;n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))V=38;else if((k+16|0)>(x+l|0))V=38;else if((v+16|0)>(B+u|0))V=38;else v=0;if((V|0)==38){V=f+43620|0;eb[c[e+8>>2]&3](c[V>>2]|0,z+-4+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,B);z=c[U>>2]|0;t=z;v=1;z=(c[V>>2]|0)+4+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](R,z,t);if(!h)ob[c[x>>2]&1023](R+j|0,z+j|0,c[U>>2]|0);if(c[M>>2]|0){y=(1-(c[f+23384+(C*1920|0)+(o*40|0)+24>>2]|0)+(c[O>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;j=z<<1;t=y>>3;u=c[T>>2]|0;x=_(u,t)|0;k=(c[f+23384+(C*1920|0)+(o*40|0)+4>>2]|0)+j+x|0;x=(c[f+23384+(C*1920|0)+(o*40|0)+8>>2]|0)+j+x|0;if(!v){y=y&7;rb[q&63](Q,k,u,S,n,y)}else{p=e+8|0;e=f+43620|0;V=m<<3;j=B>>1;eb[c[p>>2]&3](c[e>>2]|0,k,u,u,9,9,z,t,V,j);y=y&7;rb[q&63](Q,c[e>>2]|0,c[T>>2]|0,S,n,y);h=c[T>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,9,z,t,V,j);x=c[e>>2]|0}rb[q&63](P,x,c[T>>2]|0,S,n,y);rb[D&63](R,c[U>>2]|0,i,c[f+864>>2]|0,c[f+888+(o<<4)+(C<<3)>>2]|0,c[f+888+(o<<4)+(C<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;i=f+868|0;rb[s&63](Q,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(o<<5)+(C<<4)>>2]|0,c[f+1656+(o<<5)+(C<<4)+4>>2]|0);rb[s&63](P,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(o<<5)+(C<<4)+8>>2]|0,c[f+1656+(o<<5)+(C<<4)+12>>2]|0);return}I=c[f+43616>>2]|0;J=I+32|0;H=I+(x<<4)|0;s=d[96537+g>>0]|0;F=a[f+44096+s>>0]|0;G=a[f+44136+s>>0]|0;o=y<<3;r=(b[f+43776+(s<<2)>>1]|0)+o|0;C=z<<3;y=(b[f+43776+(s<<2)+2>>1]|0)+C|0;A=y<<2&12|r&3;k=r>>2;v=y>>2;z=(_(v,t)|0)+(k<<1)|0;z=(c[f+23384+(F*40|0)>>2]|0)+z|0;E=e+198232|0;m=c[E>>2]|0;l=m<<4;w=e+198228|0;B=c[w>>2]<<4>>u;n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))V=9;else if((k+16|0)>(x+l|0))V=9;else if((v+16|0)>(B+u|0))V=9;else v=0;if((V|0)==9){g=f+43620|0;eb[c[e+8>>2]&3](c[g>>2]|0,z+-4+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,B);z=c[U>>2]|0;t=z;v=1;z=(c[g>>2]|0)+4+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](R,z,t);D=(h|0)!=0;if(!D)ob[c[x>>2]&1023](R+j|0,z+j|0,c[U>>2]|0);if(c[M>>2]|0){y=(1-(c[f+23384+(F*40|0)+24>>2]|0)+(c[O>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;h=z<<1;t=y>>3;u=c[T>>2]|0;x=_(u,t)|0;k=(c[f+23384+(F*40|0)+4>>2]|0)+h+x|0;x=(c[f+23384+(F*40|0)+8>>2]|0)+h+x|0;if(!v){y=y&7;rb[q&63](Q,k,u,S,n,y)}else{r=e+8|0;h=f+43620|0;m=m<<3;g=B>>1;eb[c[r>>2]&3](c[h>>2]|0,k,u,u,9,9,z,t,m,g);y=y&7;rb[q&63](Q,c[h>>2]|0,c[T>>2]|0,S,n,y);B=c[T>>2]|0;eb[c[r>>2]&3](c[h>>2]|0,x,B,B,9,9,z,t,m,g);x=c[h>>2]|0}rb[q&63](P,x,c[T>>2]|0,S,n,y);r=(b[f+43936+(s<<2)>>1]|0)+o|0;y=(b[f+43936+(s<<2)+2>>1]|0)+C|0;A=y<<2&12|r&3;v=r>>2;l=y>>2;t=c[U>>2]|0;z=(_(l,t)|0)+(v<<1)|0;z=(c[f+25304+(G*40|0)>>2]|0)+z|0;m=c[E>>2]|0;k=m<<4;B=c[w>>2]<<4>>c[M>>2];n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((v|0)<(0-x|0)|(l|0)<(0-u|0))V=22;else if((v+16|0)>(k+x|0))V=22;else if((l+16|0)>(B+u|0))V=22;else v=0;if((V|0)==22){V=f+43620|0;eb[c[e+8>>2]&3](c[V>>2]|0,z+-4+(0-(t<<1))|0,t,t,21,21,v+-2|0,l+-2|0,k,B);z=c[U>>2]|0;t=z;v=1;z=(c[V>>2]|0)+4+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!D)ob[c[x>>2]&1023](H+j|0,z+j|0,c[U>>2]|0);if(c[M>>2]|0){y=(1-(c[f+25304+(G*40|0)+24>>2]|0)+(c[O>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;j=z<<1;t=y>>3;u=c[T>>2]|0;x=_(u,t)|0;k=(c[f+25304+(G*40|0)+4>>2]|0)+j+x|0;x=(c[f+25304+(G*40|0)+8>>2]|0)+j+x|0;if(!v){y=y&7;rb[q&63](I,k,u,S,n,y)}else{p=e+8|0;e=f+43620|0;V=m<<3;j=B>>1;eb[c[p>>2]&3](c[e>>2]|0,k,u,u,9,9,z,t,V,j);y=y&7;rb[q&63](I,c[e>>2]|0,c[T>>2]|0,S,n,y);h=c[T>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,9,z,t,V,j);x=c[e>>2]|0}rb[q&63](J,x,c[T>>2]|0,S,n,y);if((c[N>>2]|0)==2){e=c[f+3192+(F*384|0)+(G<<3)+((c[O>>2]&1)<<2)>>2]|0;f=64-e|0;pb[L&31](R,H,c[U>>2]|0,i,5,e,f,0);pb[K&31](Q,I,c[T>>2]|0,S,5,e,f,0);pb[K&31](P,J,c[T>>2]|0,S,5,e,f,0);return}else{pb[L&31](R,H,c[U>>2]|0,i,c[f+864>>2]|0,c[f+888+(F<<4)>>2]|0,c[f+888+(G<<4)+8>>2]|0,(c[f+888+(G<<4)+12>>2]|0)+(c[f+888+(F<<4)+4>>2]|0)|0);i=f+868|0;pb[K&31](Q,I,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(F<<5)>>2]|0,c[f+1656+(G<<5)+16>>2]|0,(c[f+1656+(G<<5)+20>>2]|0)+(c[f+1656+(F<<5)+4>>2]|0)|0);pb[K&31](P,J,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(F<<5)+8>>2]|0,c[f+1656+(G<<5)+24>>2]|0,(c[f+1656+(G<<5)+28>>2]|0)+(c[f+1656+(F<<5)+12>>2]|0)|0);return}}function Dc(f,g){f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0;R=i;i=i+16|0;Q=R;G=g+21764|0;O=c[G>>2]|0;F=g+21768|0;J=c[F>>2]|0;u=g+21772|0;K=c[u>>2]|0;h=f+24668|0;L=c[(c[h>>2]|0)+(K<<2)>>2]|0;M=f+26260|0;N=(c[f+26672>>2]|0)==2;P=c[f+24624>>2]|0;B=g+21748|0;E=c[B>>2]|0;H=(c[P>>2]|0)+((_(E,J)|0)+O<<4)|0;D=O<<3;C=g+21752|0;J=_(_(16>>>(c[f+25976>>2]|0),J)|0,c[C>>2]|0)|0;I=(c[P+4>>2]|0)+D+J|0;J=(c[P+8>>2]|0)+D+J|0;D=f+12|0;O=H+(_(E,O<<2&12)|0)+64|0;ob[c[D>>2]&1023](O,E,4);E=I+(_(c[G>>2]&7,c[C>>2]|0)|0)+64|0;ob[c[D>>2]&1023](E,J-I|0,2);a[(c[f+197620>>2]|0)+K>>0]=c[g+23380>>2];K=c[B>>2]|0;E=g+21756|0;c[E>>2]=K;O=c[C>>2]|0;c[g+21760>>2]=O;P=(L&7|0)!=0;a:do if(P){s=g+844|0;switch(c[s>>2]|0){case 0:break;case 2:{n=c[G>>2]|0;h=c[g+21708>>2]|0;j=(e[(c[f+197604>>2]|0)+((c[u>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1;t=5;break}default:{j=c[G>>2]|0;n=j;h=(c[F>>2]|0)>((c[g+21796>>2]|0)!=0|0)&1;j=(j|0)>0&1;t=5}}if((t|0)==5){q=H+~K|0;l=~O;r=I+l|0;l=J+l|0;m=n+-1|0;o=g+43628|0;p=c[o>>2]|0;k=p+(n*96|0)|0;if(h|0){j=(j|0)!=0;if(j){B=p+(m*96|0)+8|0;D=B;C=c[D>>2]|0;D=c[D+4>>2]|0;E=q+-7|0;z=E;A=c[z+4>>2]|0;c[B>>2]=c[z>>2];c[B+4>>2]=A;c[E>>2]=C;c[E+4>>2]=D}E=k;D=c[E>>2]|0;E=c[E+4>>2]|0;C=q+1|0;B=C;A=c[B+4>>2]|0;h=k;c[h>>2]=c[B>>2];c[h+4>>2]=A;c[C>>2]=D;c[C+4>>2]=E;C=p+(n*96|0)+8|0;E=C;D=c[E>>2]|0;E=c[E+4>>2]|0;h=q+9|0;A=h;B=c[A+4>>2]|0;c[C>>2]=c[A>>2];c[C+4>>2]=B;c[h>>2]=D;c[h+4>>2]=E;h=(c[G>>2]|0)+1|0;if((h|0)<(c[f+198232>>2]|0)){B=(c[o>>2]|0)+(h*96|0)|0;D=B;C=c[D>>2]|0;D=c[D+4>>2]|0;E=q+17|0;z=E;A=c[z+4>>2]|0;c[B>>2]=c[z>>2];c[B+4>>2]=A;c[E>>2]=C;c[E+4>>2]=D}if(j){E=p+(m*96|0)+16|0;D=E;C=c[D>>2]|0;D=c[D+4>>2]|0;B=r+-7|0;A=B;z=c[A+4>>2]|0;c[E>>2]=c[A>>2];c[E+4>>2]=z;c[B>>2]=C;c[B+4>>2]=D;B=p+(m*96|0)+24|0;D=B;C=c[D>>2]|0;D=c[D+4>>2]|0;E=l+-7|0;z=E;A=c[z+4>>2]|0;c[B>>2]=c[z>>2];c[B+4>>2]=A;c[E>>2]=C;c[E+4>>2]=D}E=p+(n*96|0)+16|0;D=E;C=c[D>>2]|0;D=c[D+4>>2]|0;B=r+1|0;A=B;z=c[A+4>>2]|0;c[E>>2]=c[A>>2];c[E+4>>2]=z;c[B>>2]=C;c[B+4>>2]=D;B=p+(n*96|0)+24|0;D=B;C=c[D>>2]|0;D=c[D+4>>2]|0;E=l+1|0;z=E;A=c[z+4>>2]|0;c[B>>2]=c[z>>2];c[B+4>>2]=A;c[E>>2]=C;c[E+4>>2]=D}}h=g+21632|0;jb[c[f+26128+(c[h>>2]<<2)>>2]&255](I,O);jb[c[f+26128+(c[h>>2]<<2)>>2]&255](J,O);h=c[g+824>>2]|0;b:do if(!(L&1)){jb[c[f+26172+(c[g+21636>>2]<<2)>>2]&255](H,K);if(a[g+43648>>0]|0)ob[c[f+132>>2]&1023](g+44384|0,g+47456|0,c[(c[f+197556>>2]|0)+(h<<6)>>2]|0)}else{if(L&16777216|0){k=c[f+112>>2]|0;l=c[f+104>>2]|0;m=g+21732|0;n=g+21740|0;p=0;while(1){o=H+(c[f+26260+(p<<2)>>2]|0)|0;D=d[96537+p>>0]|0;E=a[g+43648+D>>0]|0;ub[c[f+26080+(a[g+21640+D>>0]<<24>>24<<2)>>2]&255](o,c[m>>2]<<p&32768,c[n>>2]<<p&16384,K);switch(E<<24>>24){case 0:break;case 1:{h=p<<4;j=g+44384+(h<<1)|0;if(!(b[j>>1]|0))t=20;else ob[k&1023](o,j,K);break}default:{h=p<<4;t=20}}if((t|0)==20){t=0;ob[l&1023](o,g+44384+(h<<1)|0,K)}p=p+4|0;if((p|0)>=16)break b}}l=c[f+108>>2]|0;m=c[f+100>>2]|0;n=g+21740|0;o=3-K|0;p=0-K|0;r=0;while(1){q=H+(c[f+26260+(r<<2)>>2]|0)|0;h=d[96537+r>>0]|0;E=a[g+21640+h>>0]|0;j=E<<24>>24;do if((E|4)<<24>>24==7)if(!(c[n>>2]&32768>>>r)){c[Q>>2]=_(d[q+o>>0]|0,16843009)|0;k=Q;break}else{k=q+4+p|0;break}else k=0;while(0);ob[c[f+26020+(j<<2)>>2]&1023](q,k,K);switch(a[g+43648+h>>0]|0){case 0:break;case 1:{h=r<<4;j=g+44384+(h<<1)|0;if(!(b[j>>1]|0))t=31;else ob[l&1023](q,j,K);break}default:{h=r<<4;t=31}}if((t|0)==31){t=0;ob[m&1023](q,g+44384+(h<<1)|0,K)}r=r+1|0;if((r|0)==16)break b}}while(0);switch(c[s>>2]|0){case 0:break a;case 2:{r=c[G>>2]|0;j=c[g+21708>>2]|0;h=(e[(c[f+197604>>2]|0)+((c[u>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1;break}default:{h=c[G>>2]|0;r=h;j=(c[F>>2]|0)>((c[g+21796>>2]|0)!=0|0)&1;h=(h|0)>0&1}}l=H+~K|0;n=~O;m=I+n|0;n=J+n|0;o=r+-1|0;p=g+43628|0;q=c[p>>2]|0;k=q+(r*96|0)|0;if(j|0){j=(h|0)!=0;if(j){C=q+(o*96|0)+8|0;E=C;D=c[E>>2]|0;E=c[E+4>>2]|0;F=l+-7|0;A=F;B=c[A+4>>2]|0;c[C>>2]=c[A>>2];c[C+4>>2]=B;c[F>>2]=D;c[F+4>>2]=E}E=k;F=c[E+4>>2]|0;D=l+1|0;c[D>>2]=c[E>>2];c[D+4>>2]=F;D=q+(r*96|0)+8|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;h=l+9|0;B=h;C=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=C;c[h>>2]=E;c[h+4>>2]=F;h=(c[G>>2]|0)+1|0;if((h|0)<(c[f+198232>>2]|0)){D=(c[p>>2]|0)+(h*96|0)|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=l+17|0;B=G;C=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=C;c[G>>2]=E;c[G+4>>2]=F}if(j){G=q+(o*96|0)+16|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;D=m+-7|0;C=D;B=c[C+4>>2]|0;c[G>>2]=c[C>>2];c[G+4>>2]=B;c[D>>2]=E;c[D+4>>2]=F;D=q+(o*96|0)+24|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=n+-7|0;B=G;C=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=C;c[G>>2]=E;c[G+4>>2]=F}G=q+(r*96|0)+16|0;F=G;E=c[F>>2]|0;F=c[F+4>>2]|0;D=m+1|0;C=D;B=c[C+4>>2]|0;c[G>>2]=c[C>>2];c[G+4>>2]=B;c[D>>2]=E;c[D+4>>2]=F;D=q+(r*96|0)+24|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;G=n+1|0;B=G;C=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=C;c[G>>2]=E;c[G+4>>2]=F}}else{o=c[f+203640>>2]|0;k=f+152|0;z=c[f+203644>>2]|0;l=f+168|0;m=f+16|0;n=f+32|0;A=c[(c[h>>2]|0)+(c[u>>2]<<2)>>2]|0;h=a[g+44108>>0]|0;j=h<<24>>24>-1;if(N){if(j){x=h<<24>>24;w=c[g+43824>>2]|0;v=c[G>>2]|0;y=(v<<4|8)+(w<<16>>18)|0;w=(c[F>>2]<<4)+(w>>18)|0;v=y+64+(_(w+(v<<2&12)|0,K)|0)|0;ob[c[D>>2]&1023]((c[g+23384+(x*40|0)>>2]|0)+v|0,K,4);w=(y>>1)+64+(_((c[G>>2]&7)+(w>>1)|0,c[C>>2]|0)|0)|0;y=c[g+23384+(x*40|0)+4>>2]|0;ob[c[D>>2]&1023](y+w|0,(c[g+23384+(x*40|0)+8>>2]|0)-y|0,2)}do if(!(A&8)){if(A&16|0){y=o+64|0;z=z+64|0;Ec(f,g,0,0,8,8,H,I,J,0,0,y,c[k>>2]|0,z,c[l>>2]|0,m,n,A&4096,A&16384);Ec(f,g,8,0,8,8,H,I,J,0,4,y,c[k>>2]|0,z,c[l>>2]|0,m,n,A&8192,A&32768);break}if(A&32|0){u=o+64|0;v=f+156|0;w=z+64|0;x=f+172|0;y=f+20|0;z=f+36|0;Ec(f,g,0,0,16,c[E>>2]<<3,H,I,J,0,0,u,c[v>>2]|0,w,c[x>>2]|0,y,z,A&4096,A&16384);Ec(f,g,4,0,16,c[E>>2]<<3,H,I,J,4,0,u,c[v>>2]|0,w,c[x>>2]|0,y,z,A&8192,A&32768);break}p=o+128|0;q=f+160|0;r=z+128|0;s=f+176|0;t=f+24|0;u=f+40|0;v=f+156|0;w=f+172|0;x=f+20|0;y=f+36|0;o=o+64|0;h=z+64|0;n=0;do{j=e[g+44376+(n<<1)>>1]|0;k=n<<2;l=k&4;m=n<<1&4;do if(!(j&8)){if(j&16|0){S=j&4096;z=j&16384;Ec(f,g,k,0,4,4,H,I,J,l,m,p,c[v>>2]|0,r,c[w>>2]|0,x,y,S,z);Ec(f,g,k|2,0,4,4,H,I,J,l,m|2,p,c[v>>2]|0,r,c[w>>2]|0,x,y,S,z);break}if(!(j&32)){z=j&4096;S=j&16384;Ec(f,g,k,1,4,0,H,I,J,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);j=l|2;Ec(f,g,k|1,1,4,0,H,I,J,j,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);m=m|2;Ec(f,g,k|2,1,4,0,H,I,J,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);Ec(f,g,k|3,1,4,0,H,I,J,j,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);break}else{z=j&4096;S=j&16384;Ec(f,g,k,0,8,c[E>>2]<<2,H,I,J,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);Ec(f,g,k|1,0,8,c[E>>2]<<2,H,I,J,l|2,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);break}}else Ec(f,g,k,1,8,0,H,I,J,l,m,o,c[v>>2]|0,h,c[w>>2]|0,x,y,j&4096,j&16384);while(0);n=n+1|0}while((n|0)!=4)}else Ec(f,g,0,1,16,0,H,I,J,0,0,o,c[k>>2]|0,z,c[l>>2]|0,m,n,A&4096,A&16384);while(0);if(!(A&49152))break;h=a[g+44148>>0]|0;if(h<<24>>24<=-1)break;A=h<<24>>24;S=c[g+43984>>2]|0;y=c[G>>2]|0;z=(y<<4|8)+(S<<16>>18)|0;S=(c[F>>2]<<4)+(S>>18)|0;F=z+64+(_(S+(y<<2&12)|0,c[E>>2]|0)|0)|0;ob[c[D>>2]&1023]((c[g+25304+(A*40|0)>>2]|0)+F|0,c[B>>2]|0,4);G=(z>>1)+64+(_((c[G>>2]&7)+(S>>1)|0,c[C>>2]|0)|0)|0;S=c[g+25304+(A*40|0)+4>>2]|0;ob[c[D>>2]&1023](S+G|0,(c[g+25304+(A*40|0)+8>>2]|0)-S|0,2);break}else{if(j){y=h<<24>>24;x=c[g+43824>>2]|0;w=c[G>>2]|0;S=(w<<4|8)+(x<<16>>18)|0;x=(c[F>>2]<<4)+(x>>18)|0;w=S+64+(_(x+(w<<2&12)|0,K)|0)|0;ob[c[D>>2]&1023]((c[g+23384+(y*40|0)>>2]|0)+w|0,K,4);x=(S>>1)+64+(_((c[G>>2]&7)+(x>>1)|0,c[C>>2]|0)|0)|0;S=c[g+23384+(y*40|0)+4>>2]|0;ob[c[D>>2]&1023](S+x|0,(c[g+23384+(y*40|0)+8>>2]|0)-S|0,2)}do if(!(A&8)){if(A&16|0){y=o+64|0;S=z+64|0;Fc(f,g,0,0,8,8,H,I,J,0,0,y,c[k>>2]|0,S,c[l>>2]|0,m,n,A&4096,A&16384);Fc(f,g,8,0,8,8,H,I,J,0,4,y,c[k>>2]|0,S,c[l>>2]|0,m,n,A&8192,A&32768);break}if(A&32|0){v=o+64|0;w=f+156|0;x=z+64|0;y=f+172|0;z=f+20|0;S=f+36|0;Fc(f,g,0,0,16,c[E>>2]<<3,H,I,J,0,0,v,c[w>>2]|0,x,c[y>>2]|0,z,S,A&4096,A&16384);Fc(f,g,4,0,16,c[E>>2]<<3,H,I,J,4,0,v,c[w>>2]|0,x,c[y>>2]|0,z,S,A&8192,A&32768);break}p=o+128|0;q=f+160|0;r=z+128|0;s=f+176|0;t=f+24|0;u=f+40|0;v=f+156|0;w=f+172|0;x=f+20|0;y=f+36|0;o=o+64|0;h=z+64|0;n=0;do{j=e[g+44376+(n<<1)>>1]|0;k=n<<2;l=k&4;m=n<<1&4;do if(!(j&8)){if(j&16|0){z=j&4096;S=j&16384;Fc(f,g,k,0,4,4,H,I,J,l,m,p,c[v>>2]|0,r,c[w>>2]|0,x,y,z,S);Fc(f,g,k|2,0,4,4,H,I,J,l,m|2,p,c[v>>2]|0,r,c[w>>2]|0,x,y,z,S);break}if(!(j&32)){z=j&4096;S=j&16384;Fc(f,g,k,1,4,0,H,I,J,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);j=l|2;Fc(f,g,k|1,1,4,0,H,I,J,j,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);m=m|2;Fc(f,g,k|2,1,4,0,H,I,J,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);Fc(f,g,k|3,1,4,0,H,I,J,j,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);break}else{z=j&4096;S=j&16384;Fc(f,g,k,0,8,c[E>>2]<<2,H,I,J,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);Fc(f,g,k|1,0,8,c[E>>2]<<2,H,I,J,l|2,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,z,S);break}}else Fc(f,g,k,1,8,0,H,I,J,l,m,o,c[v>>2]|0,h,c[w>>2]|0,x,y,j&4096,j&16384);while(0);n=n+1|0}while((n|0)!=4)}else Fc(f,g,0,1,16,0,H,I,J,0,0,o,c[k>>2]|0,z,c[l>>2]|0,m,n,A&4096,A&16384);while(0);if(!(A&49152))break;h=a[g+44148>>0]|0;if(h<<24>>24<=-1)break;A=h<<24>>24;S=c[g+43984>>2]|0;y=c[G>>2]|0;z=(y<<4|8)+(S<<16>>18)|0;S=(c[F>>2]<<4)+(S>>18)|0;F=z+64+(_(S+(y<<2&12)|0,c[E>>2]|0)|0)|0;ob[c[D>>2]&1023]((c[g+25304+(A*40|0)>>2]|0)+F|0,c[B>>2]|0,4);G=(z>>1)+64+(_((c[G>>2]&7)+(S>>1)|0,c[C>>2]|0)|0)|0;S=c[g+25304+(A*40|0)+4>>2]|0;ob[c[D>>2]&1023](S+G|0,(c[g+25304+(A*40|0)+8>>2]|0)-S|0,2);break}}while(0);do if(!(L&1)){if(L&2|0){fb[c[f+128>>2]&127](H,M,g+44384|0,K,g+43648|0);break}if(c[g+21824>>2]&15|0)if(!(L&16777216)){fb[c[f+116>>2]&127](H,M,g+44384|0,K,g+43648|0);break}else{fb[c[f+120>>2]&127](H,M,g+44384|0,K,g+43648|0);break}}while(0);if(!(c[g+21824>>2]&48)){i=R;return}c[Q>>2]=I;c[Q+4>>2]=J;h=c[g+828>>2]|0;j=c[g+832>>2]|0;if(a[g+43688>>0]|0)jb[c[f+136>>2]&255](g+44896|0,c[(c[f+197556+((P?1:4)<<2)>>2]|0)+((N?h+3|0:h)<<6)>>2]|0);if(a[g+43728>>0]|0)jb[c[f+136>>2]&255](g+45408|0,c[(c[f+197556+((P?2:5)<<2)>>2]|0)+((N?j+3|0:j)<<6)>>2]|0);fb[c[f+124>>2]&127](Q,M,g+44384|0,O,g+43648|0);i=R;return}function Ec(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;O=f+856|0;x=c[O>>2]|0;A=(v|0)!=0;B=(w|0)!=0;if(B&(A&(x|0)==2)){y=f+21768|0;z=c[y>>2]|0;T=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+T>>0]|0)*384|0)+(a[f+44136+T>>0]<<3)+((z&1)<<2)>>2]|0)==32)U=3;else N=y}else U=3;do if((U|0)==3){if((x|0)==1){z=f+21768|0;N=z;z=c[z>>2]|0;break}J=o<<1;H=f+21756|0;z=c[H>>2]|0;G=k+((_(z,J)|0)+(n<<1))|0;K=f+21760|0;J=(_(c[K>>2]|0,J)|0)+n|0;I=l+J|0;J=m+J|0;D=(c[f+21764>>2]<<3)+n|0;F=f+21796|0;x=c[F>>2]|0;E=(c[f+21768>>2]>>x<<3)+o|0;if(!v){s=q;r=p}else{m=d[96537+g>>0]|0;A=a[f+44096+m>>0]|0;l=(b[f+43776+(m<<2)>>1]|0)+(D<<3)|0;m=(b[f+43776+(m<<2)+2>>1]|0)+(E<<3)|0;v=m<<2&12|l&3;u=l>>2;n=m>>2;y=(_(n,z)|0)+u|0;y=(c[f+23384+(A*40|0)>>2]|0)+y|0;o=c[e+198232>>2]|0;k=o<<4;B=c[e+198228>>2]<<4>>x;C=l&7;x=(C|0)==0?0:-3;t=(m&7|0)==0?0:-3;if((u|0)<(0-x|0)|(n|0)<(0-t|0))U=42;else if((u+16|0)>(x+k|0))U=42;else if((n+16|0)>(B+t|0))U=42;else u=0;if((U|0)==42){T=f+43620|0;eb[c[e+8>>2]&3](c[T>>2]|0,y+-2+(0-(z<<1))|0,z,z,21,21,u+-2|0,n+-2|0,k,B);y=c[H>>2]|0;z=y;u=1;y=(c[T>>2]|0)+2+(y<<1)|0}x=p+(v<<2)|0;ob[c[x>>2]&1023](G,y,z);if(!h)ob[c[x>>2]&1023](G+j|0,y+j|0,c[H>>2]|0);y=l>>3;z=c[K>>2]|0;x=_(z,n)|0;t=(c[f+23384+(A*40|0)+4>>2]|0)+y+x|0;x=(c[f+23384+(A*40|0)+8>>2]|0)+y+x|0;if(!u){y=m<<1&6;rb[q&63](I,t,z,i,C,y)}else{Q=e+8|0;p=f+43620|0;S=o<<3;eb[c[Q>>2]&3](c[p>>2]|0,t,z,z,9,17,y,n,S,B);T=m<<1&6;rb[q&63](I,c[p>>2]|0,c[K>>2]|0,i,C,T);R=c[K>>2]|0;eb[c[Q>>2]&3](c[p>>2]|0,x,R,R,9,17,y,n,S,B);y=T;x=c[p>>2]|0}rb[q&63](J,x,c[K>>2]|0,i,C,y)}if(!w)return;B=d[96537+g>>0]|0;o=a[f+44136+B>>0]|0;l=(b[f+43936+(B<<2)>>1]|0)+(D<<3)|0;B=(b[f+43936+(B<<2)+2>>1]|0)+(E<<3)|0;v=B<<2&12|l&3;u=l>>2;m=B>>2;z=c[H>>2]|0;y=(_(m,z)|0)+u|0;y=(c[f+25304+(o*40|0)>>2]|0)+y|0;n=c[e+198232>>2]|0;k=n<<4;A=c[e+198228>>2]<<4>>c[F>>2];C=l&7;x=(C|0)==0?0:-3;t=(B&7|0)==0?0:-3;if((u|0)<(0-x|0)|(m|0)<(0-t|0))U=53;else if((u+16|0)>(k+x|0))U=53;else if((m+16|0)>(A+t|0))U=53;else u=0;if((U|0)==53){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,y+-2+(0-(z<<1))|0,z,z,21,21,u+-2|0,m+-2|0,k,A);y=c[H>>2]|0;z=y;u=1;y=(c[U>>2]|0)+2+(y<<1)|0}x=r+(v<<2)|0;ob[c[x>>2]&1023](G,y,z);if(!h)ob[c[x>>2]&1023](G+j|0,y+j|0,c[H>>2]|0);y=l>>3;z=c[K>>2]|0;x=_(z,m)|0;t=(c[f+25304+(o*40|0)+4>>2]|0)+y+x|0;x=(c[f+25304+(o*40|0)+8>>2]|0)+y+x|0;if(!u){y=B<<1&6;rb[s&63](I,t,z,i,C,y)}else{q=e+8|0;f=f+43620|0;j=n<<3;eb[c[q>>2]&3](c[f>>2]|0,t,z,z,9,17,y,m,j,A);e=B<<1&6;rb[s&63](I,c[f>>2]|0,c[K>>2]|0,i,C,e);U=c[K>>2]|0;eb[c[q>>2]&3](c[f>>2]|0,x,U,U,9,17,y,m,j,A);y=e;x=c[f>>2]|0}rb[s&63](J,x,c[K>>2]|0,i,C,y);return}while(0);s=c[t>>2]|0;C=c[t+4>>2]|0;M=c[u>>2]|0;L=c[u+4>>2]|0;P=o<<1;T=f+21756|0;F=c[T>>2]|0;R=k+((_(F,P)|0)+(n<<1))|0;S=f+21760|0;x=c[S>>2]|0;P=(_(x,P)|0)+n|0;Q=l+P|0;P=m+P|0;y=(c[f+21764>>2]<<3)+n|0;w=f+21796|0;l=c[w>>2]|0;z=(z>>l<<3)+o|0;if(!(A&B)){o=B&1;A=d[96537+g>>0]|0;n=a[f+44096+(o*40|0)+A>>0]|0;v=(b[f+43776+(o*160|0)+(A<<2)>>1]|0)+(y<<3)|0;A=(b[f+43776+(o*160|0)+(A<<2)+2>>1]|0)+(z<<3)|0;k=A<<2&12|v&3;t=v>>2;r=A>>2;y=(_(r,F)|0)+t|0;y=(c[f+23384+(o*1920|0)+(n*40|0)>>2]|0)+y|0;B=c[e+198232>>2]|0;u=B<<4;l=c[e+198228>>2]<<4>>l;m=v&7;x=(m|0)==0?0:-3;z=(A&7|0)==0?0:-3;if((t|0)<(0-x|0)|(r|0)<(0-z|0))U=30;else if((t+16|0)>(x+u|0))U=30;else if((r+16|0)>(l+z|0))U=30;else{z=F;u=0}if((U|0)==30){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,y+-2+(0-(F<<1))|0,F,F,21,21,t+-2|0,r+-2|0,u,l);y=c[T>>2]|0;z=y;u=1;y=(c[U>>2]|0)+2+(y<<1)|0}x=p+(k<<2)|0;ob[c[x>>2]&1023](R,y,z);if(!h)ob[c[x>>2]&1023](R+j|0,y+j|0,c[T>>2]|0);y=v>>3;z=c[S>>2]|0;x=_(z,r)|0;t=(c[f+23384+(o*1920|0)+(n*40|0)+4>>2]|0)+y+x|0;x=(c[f+23384+(o*1920|0)+(n*40|0)+8>>2]|0)+y+x|0;if(!u){y=A<<1&6;rb[q&63](Q,t,z,i,m,y)}else{p=e+8|0;e=f+43620|0;U=B<<3;eb[c[p>>2]&3](c[e>>2]|0,t,z,z,9,17,y,r,U,l);j=A<<1&6;rb[q&63](Q,c[e>>2]|0,c[S>>2]|0,i,m,j);h=c[S>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,17,y,r,U,l);y=j;x=c[e>>2]|0}rb[q&63](P,x,c[S>>2]|0,i,m,y);rb[s&63](R,c[T>>2]|0,i,c[f+864>>2]|0,c[f+888+(n<<4)+(o<<3)>>2]|0,c[f+888+(n<<4)+(o<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;e=f+868|0;rb[C&63](Q,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(n<<5)+(o<<4)>>2]|0,c[f+1656+(n<<5)+(o<<4)+4>>2]|0);rb[C&63](P,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(n<<5)+(o<<4)+8>>2]|0,c[f+1656+(n<<5)+(o<<4)+12>>2]|0);return}J=c[f+43616>>2]|0;K=J+16|0;I=J+(x<<4)|0;C=d[96537+g>>0]|0;G=a[f+44096+C>>0]|0;H=a[f+44136+C>>0]|0;n=y<<3;v=(b[f+43776+(C<<2)>>1]|0)+n|0;o=z<<3;A=(b[f+43776+(C<<2)+2>>1]|0)+o|0;k=A<<2&12|v&3;t=v>>2;r=A>>2;z=(_(r,F)|0)+t|0;z=(c[f+23384+(G*40|0)>>2]|0)+z|0;D=e+198232|0;B=c[D>>2]|0;u=B<<4;E=e+198228|0;l=c[E>>2]<<4>>l;m=v&7;x=(m|0)==0?0:-3;y=(A&7|0)==0?0:-3;if((t|0)<(0-x|0)|(r|0)<(0-y|0))U=9;else if((t+16|0)>(x+u|0))U=9;else if((r+16|0)>(l+y|0))U=9;else{y=F;u=0}if((U|0)==9){g=f+43620|0;eb[c[e+8>>2]&3](c[g>>2]|0,z+-2+(0-(F<<1))|0,F,F,21,21,t+-2|0,r+-2|0,u,l);z=c[T>>2]|0;y=z;u=1;z=(c[g>>2]|0)+2+(z<<1)|0}x=p+(k<<2)|0;ob[c[x>>2]&1023](R,z,y);s=(h|0)!=0;if(!s)ob[c[x>>2]&1023](R+j|0,z+j|0,c[T>>2]|0);y=v>>3;z=c[S>>2]|0;x=_(z,r)|0;t=(c[f+23384+(G*40|0)+4>>2]|0)+y+x|0;x=(c[f+23384+(G*40|0)+8>>2]|0)+y+x|0;if(!u){y=A<<1&6;rb[q&63](Q,t,z,i,m,y)}else{v=e+8|0;h=f+43620|0;F=B<<3;eb[c[v>>2]&3](c[h>>2]|0,t,z,z,9,17,y,r,F,l);g=A<<1&6;rb[q&63](Q,c[h>>2]|0,c[S>>2]|0,i,m,g);B=c[S>>2]|0;eb[c[v>>2]&3](c[h>>2]|0,x,B,B,9,17,y,r,F,l);y=g;x=c[h>>2]|0}rb[q&63](P,x,c[S>>2]|0,i,m,y);A=(b[f+43936+(C<<2)>>1]|0)+n|0;m=(b[f+43936+(C<<2)+2>>1]|0)+o|0;v=m<<2&12|A&3;k=A>>2;n=m>>2;z=c[T>>2]|0;y=(_(n,z)|0)+k|0;y=(c[f+25304+(H*40|0)>>2]|0)+y|0;r=c[D>>2]|0;u=r<<4;l=c[E>>2]<<4>>c[w>>2];B=A&7;x=(B|0)==0?0:-3;t=(m&7|0)==0?0:-3;if((k|0)<(0-x|0)|(n|0)<(0-t|0))U=18;else if((k+16|0)>(u+x|0))U=18;else if((n+16|0)>(l+t|0))U=18;else u=0;if((U|0)==18){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,y+-2+(0-(z<<1))|0,z,z,21,21,k+-2|0,n+-2|0,u,l);y=c[T>>2]|0;z=y;u=1;y=(c[U>>2]|0)+2+(y<<1)|0}x=p+(v<<2)|0;ob[c[x>>2]&1023](I,y,z);if(!s)ob[c[x>>2]&1023](I+j|0,y+j|0,c[T>>2]|0);y=A>>3;z=c[S>>2]|0;x=_(z,n)|0;t=(c[f+25304+(H*40|0)+4>>2]|0)+y+x|0;x=(c[f+25304+(H*40|0)+8>>2]|0)+y+x|0;if(!u){y=m<<1&6;rb[q&63](J,t,z,i,B,y)}else{p=e+8|0;e=f+43620|0;U=r<<3;eb[c[p>>2]&3](c[e>>2]|0,t,z,z,9,17,y,n,U,l);j=m<<1&6;rb[q&63](J,c[e>>2]|0,c[S>>2]|0,i,B,j);h=c[S>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,17,y,n,U,l);y=j;x=c[e>>2]|0}rb[q&63](K,x,c[S>>2]|0,i,B,y);if((c[O>>2]|0)==2){e=c[f+3192+(G*384|0)+(H<<3)+((c[N>>2]&1)<<2)>>2]|0;f=64-e|0;pb[M&31](R,I,c[T>>2]|0,i,5,e,f,0);pb[L&31](Q,J,c[S>>2]|0,i,5,e,f,0);pb[L&31](P,K,c[S>>2]|0,i,5,e,f,0);return}else{pb[M&31](R,I,c[T>>2]|0,i,c[f+864>>2]|0,c[f+888+(G<<4)>>2]|0,c[f+888+(H<<4)+8>>2]|0,(c[f+888+(H<<4)+12>>2]|0)+(c[f+888+(G<<4)+4>>2]|0)|0);e=f+868|0;pb[L&31](Q,J,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(G<<5)>>2]|0,c[f+1656+(H<<5)+16>>2]|0,(c[f+1656+(H<<5)+20>>2]|0)+(c[f+1656+(G<<5)+4>>2]|0)|0);pb[L&31](P,K,c[S>>2]|0,i,c[e>>2]|0,c[f+1656+(G<<5)+8>>2]|0,c[f+1656+(H<<5)+24>>2]|0,(c[f+1656+(H<<5)+28>>2]|0)+(c[f+1656+(G<<5)+12>>2]|0)|0);return}}function Fc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0;N=f+856|0;x=c[N>>2]|0;A=(v|0)!=0;B=(w|0)!=0;if(B&(A&(x|0)==2)){y=f+21768|0;z=c[y>>2]|0;U=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+U>>0]|0)*384|0)+(a[f+44136+U>>0]<<3)+((z&1)<<2)>>2]|0)==32)V=3;else O=y}else V=3;do if((V|0)==3){if((x|0)==1){z=f+21768|0;O=z;z=c[z>>2]|0;break}I=f+21756|0;t=c[I>>2]|0;H=k+((_(t,o)|0)+n<<1)|0;L=f+21760|0;K=(_(c[L>>2]|0,o)|0)+n|0;J=l+K|0;K=m+K|0;D=(c[f+21764>>2]<<3)+n|0;F=f+21768|0;G=f+21796|0;x=c[G>>2]|0;E=(c[F>>2]>>x<<3)+o|0;if(!v){s=q;r=p}else{y=d[96537+g>>0]|0;m=a[f+44096+y>>0]|0;B=(b[f+43776+(y<<2)>>1]|0)+(D<<3)|0;y=(b[f+43776+(y<<2)+2>>1]|0)+(E<<3)|0;A=y<<2&12|B&3;k=B>>2;v=y>>2;z=(_(v,t)|0)+k|0;z=(c[f+23384+(m*40|0)>>2]|0)+z|0;o=c[e+198232>>2]|0;l=o<<4;n=c[e+198228>>2]<<4>>x;C=B&7;x=(C|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))V=54;else if((k+16|0)>(x+l|0))V=54;else if((v+16|0)>(n+u|0))V=54;else v=0;if((V|0)==54){U=f+43620|0;eb[c[e+8>>2]&3](c[U>>2]|0,z+-2+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,n);z=c[I>>2]|0;t=z;v=1;z=(c[U>>2]|0)+2+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!h)ob[c[x>>2]&1023](H+j|0,z+j|0,c[I>>2]|0);if(c[G>>2]|0){y=(1-(c[f+23384+(m*40|0)+24>>2]|0)+(c[F>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(n>>1|0);v=x&1|v}z=B>>3;t=y>>3;u=c[L>>2]|0;x=_(u,t)|0;k=(c[f+23384+(m*40|0)+4>>2]|0)+z+x|0;x=(c[f+23384+(m*40|0)+8>>2]|0)+z+x|0;if(!v){p=i>>1;z=y&7;rb[q&63](J,k,u,p,C,z);y=p}else{Q=e+8|0;p=f+43620|0;R=o<<3;S=n>>1;eb[c[Q>>2]&3](c[p>>2]|0,k,u,u,9,9,z,t,R,S);T=i>>1;U=y&7;rb[q&63](J,c[p>>2]|0,c[L>>2]|0,T,C,U);y=c[L>>2]|0;eb[c[Q>>2]&3](c[p>>2]|0,x,y,y,9,9,z,t,R,S);y=T;z=U;x=c[p>>2]|0}rb[q&63](K,x,c[L>>2]|0,y,C,z)}if(!w)return;y=d[96537+g>>0]|0;o=a[f+44136+y>>0]|0;B=(b[f+43936+(y<<2)>>1]|0)+(D<<3)|0;y=(b[f+43936+(y<<2)+2>>1]|0)+(E<<3)|0;A=y<<2&12|B&3;u=B>>2;k=y>>2;t=c[I>>2]|0;z=(_(k,t)|0)+u|0;z=(c[f+25304+(o*40|0)>>2]|0)+z|0;m=c[e+198232>>2]|0;v=m<<4;n=c[e+198228>>2]<<4>>c[G>>2];C=B&7;x=(C|0)==0?0:-3;l=(y&7|0)==0?0:-3;if((u|0)<(0-x|0)|(k|0)<(0-l|0))V=69;else if((u+16|0)>(v+x|0))V=69;else if((k+16|0)>(n+l|0))V=69;else v=0;if((V|0)==69){q=f+43620|0;eb[c[e+8>>2]&3](c[q>>2]|0,z+-2+(0-(t<<1))|0,t,t,21,21,u+-2|0,k+-2|0,v,n);z=c[I>>2]|0;t=z;v=1;z=(c[q>>2]|0)+2+(z<<1)|0}x=r+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!h)ob[c[x>>2]&1023](H+j|0,z+j|0,c[I>>2]|0);if(c[G>>2]|0){y=(1-(c[f+25304+(o*40|0)+24>>2]|0)+(c[F>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(n>>1|0);v=x&1|v}z=B>>3;t=y>>3;u=c[L>>2]|0;x=_(u,t)|0;k=(c[f+25304+(o*40|0)+4>>2]|0)+z+x|0;x=(c[f+25304+(o*40|0)+8>>2]|0)+z+x|0;if(!v){f=i>>1;z=y&7;rb[s&63](J,k,u,f,C,z);y=f}else{V=e+8|0;f=f+43620|0;j=m<<3;q=n>>1;eb[c[V>>2]&3](c[f>>2]|0,k,u,u,9,9,z,t,j,q);e=i>>1;i=y&7;rb[s&63](J,c[f>>2]|0,c[L>>2]|0,e,C,i);y=c[L>>2]|0;eb[c[V>>2]&3](c[f>>2]|0,x,y,y,9,9,z,t,j,q);y=e;z=i;x=c[f>>2]|0}rb[s&63](K,x,c[L>>2]|0,y,C,z);return}while(0);D=c[t>>2]|0;s=c[t+4>>2]|0;L=c[u>>2]|0;K=c[u+4>>2]|0;U=f+21756|0;t=c[U>>2]|0;R=k+((_(t,o)|0)+n<<1)|0;S=i>>1;T=f+21760|0;x=c[T>>2]|0;P=(_(x,o)|0)+n|0;Q=l+P|0;P=m+P|0;y=(c[f+21764>>2]<<3)+n|0;M=f+21796|0;u=c[M>>2]|0;z=(z>>u<<3)+o|0;if(!(A&B)){C=B&1;A=d[96537+g>>0]|0;o=a[f+44096+(C*40|0)+A>>0]|0;r=(b[f+43776+(C*160|0)+(A<<2)>>1]|0)+(y<<3)|0;y=(b[f+43776+(C*160|0)+(A<<2)+2>>1]|0)+(z<<3)|0;A=y<<2&12|r&3;k=r>>2;v=y>>2;z=(_(v,t)|0)+k|0;z=(c[f+23384+(C*1920|0)+(o*40|0)>>2]|0)+z|0;m=c[e+198232>>2]|0;l=m<<4;B=c[e+198228>>2]<<4>>u;n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))V=38;else if((k+16|0)>(x+l|0))V=38;else if((v+16|0)>(B+u|0))V=38;else v=0;if((V|0)==38){V=f+43620|0;eb[c[e+8>>2]&3](c[V>>2]|0,z+-2+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,B);z=c[U>>2]|0;t=z;v=1;z=(c[V>>2]|0)+2+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](R,z,t);if(!h)ob[c[x>>2]&1023](R+j|0,z+j|0,c[U>>2]|0);if(c[M>>2]|0){y=(1-(c[f+23384+(C*1920|0)+(o*40|0)+24>>2]|0)+(c[O>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;t=y>>3;u=c[T>>2]|0;x=_(u,t)|0;k=(c[f+23384+(C*1920|0)+(o*40|0)+4>>2]|0)+z+x|0;x=(c[f+23384+(C*1920|0)+(o*40|0)+8>>2]|0)+z+x|0;if(!v){y=y&7;rb[q&63](Q,k,u,S,n,y)}else{p=e+8|0;e=f+43620|0;V=m<<3;j=B>>1;eb[c[p>>2]&3](c[e>>2]|0,k,u,u,9,9,z,t,V,j);y=y&7;rb[q&63](Q,c[e>>2]|0,c[T>>2]|0,S,n,y);h=c[T>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,9,z,t,V,j);x=c[e>>2]|0}rb[q&63](P,x,c[T>>2]|0,S,n,y);rb[D&63](R,c[U>>2]|0,i,c[f+864>>2]|0,c[f+888+(o<<4)+(C<<3)>>2]|0,c[f+888+(o<<4)+(C<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;i=f+868|0;rb[s&63](Q,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(o<<5)+(C<<4)>>2]|0,c[f+1656+(o<<5)+(C<<4)+4>>2]|0);rb[s&63](P,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(o<<5)+(C<<4)+8>>2]|0,c[f+1656+(o<<5)+(C<<4)+12>>2]|0);return}I=c[f+43616>>2]|0;J=I+16|0;H=I+(x<<4)|0;s=d[96537+g>>0]|0;F=a[f+44096+s>>0]|0;G=a[f+44136+s>>0]|0;o=y<<3;r=(b[f+43776+(s<<2)>>1]|0)+o|0;C=z<<3;y=(b[f+43776+(s<<2)+2>>1]|0)+C|0;A=y<<2&12|r&3;k=r>>2;v=y>>2;z=(_(v,t)|0)+k|0;z=(c[f+23384+(F*40|0)>>2]|0)+z|0;E=e+198232|0;m=c[E>>2]|0;l=m<<4;w=e+198228|0;B=c[w>>2]<<4>>u;n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((k|0)<(0-x|0)|(v|0)<(0-u|0))V=9;else if((k+16|0)>(x+l|0))V=9;else if((v+16|0)>(B+u|0))V=9;else v=0;if((V|0)==9){g=f+43620|0;eb[c[e+8>>2]&3](c[g>>2]|0,z+-2+(0-(t<<1))|0,t,t,21,21,k+-2|0,v+-2|0,l,B);z=c[U>>2]|0;t=z;v=1;z=(c[g>>2]|0)+2+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](R,z,t);D=(h|0)!=0;if(!D)ob[c[x>>2]&1023](R+j|0,z+j|0,c[U>>2]|0);if(c[M>>2]|0){y=(1-(c[f+23384+(F*40|0)+24>>2]|0)+(c[O>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;t=y>>3;u=c[T>>2]|0;x=_(u,t)|0;k=(c[f+23384+(F*40|0)+4>>2]|0)+z+x|0;x=(c[f+23384+(F*40|0)+8>>2]|0)+z+x|0;if(!v){y=y&7;rb[q&63](Q,k,u,S,n,y)}else{r=e+8|0;h=f+43620|0;m=m<<3;g=B>>1;eb[c[r>>2]&3](c[h>>2]|0,k,u,u,9,9,z,t,m,g);y=y&7;rb[q&63](Q,c[h>>2]|0,c[T>>2]|0,S,n,y);B=c[T>>2]|0;eb[c[r>>2]&3](c[h>>2]|0,x,B,B,9,9,z,t,m,g);x=c[h>>2]|0}rb[q&63](P,x,c[T>>2]|0,S,n,y);r=(b[f+43936+(s<<2)>>1]|0)+o|0;y=(b[f+43936+(s<<2)+2>>1]|0)+C|0;A=y<<2&12|r&3;v=r>>2;l=y>>2;t=c[U>>2]|0;z=(_(l,t)|0)+v|0;z=(c[f+25304+(G*40|0)>>2]|0)+z|0;m=c[E>>2]|0;k=m<<4;B=c[w>>2]<<4>>c[M>>2];n=r&7;x=(n|0)==0?0:-3;u=(y&7|0)==0?0:-3;if((v|0)<(0-x|0)|(l|0)<(0-u|0))V=22;else if((v+16|0)>(k+x|0))V=22;else if((l+16|0)>(B+u|0))V=22;else v=0;if((V|0)==22){V=f+43620|0;eb[c[e+8>>2]&3](c[V>>2]|0,z+-2+(0-(t<<1))|0,t,t,21,21,v+-2|0,l+-2|0,k,B);z=c[U>>2]|0;t=z;v=1;z=(c[V>>2]|0)+2+(z<<1)|0}x=p+(A<<2)|0;ob[c[x>>2]&1023](H,z,t);if(!D)ob[c[x>>2]&1023](H+j|0,z+j|0,c[U>>2]|0);if(c[M>>2]|0){y=(1-(c[f+25304+(G*40|0)+24>>2]|0)+(c[O>>2]&1)<<1)+y|0;x=y>>3;if((x|0)<0)x=1;else x=(x+8|0)>=(B>>1|0);v=x&1|v}z=r>>3;t=y>>3;u=c[T>>2]|0;x=_(u,t)|0;k=(c[f+25304+(G*40|0)+4>>2]|0)+z+x|0;x=(c[f+25304+(G*40|0)+8>>2]|0)+z+x|0;if(!v){y=y&7;rb[q&63](I,k,u,S,n,y)}else{p=e+8|0;e=f+43620|0;V=m<<3;j=B>>1;eb[c[p>>2]&3](c[e>>2]|0,k,u,u,9,9,z,t,V,j);y=y&7;rb[q&63](I,c[e>>2]|0,c[T>>2]|0,S,n,y);h=c[T>>2]|0;eb[c[p>>2]&3](c[e>>2]|0,x,h,h,9,9,z,t,V,j);x=c[e>>2]|0}rb[q&63](J,x,c[T>>2]|0,S,n,y);if((c[N>>2]|0)==2){e=c[f+3192+(F*384|0)+(G<<3)+((c[O>>2]&1)<<2)>>2]|0;f=64-e|0;pb[L&31](R,H,c[U>>2]|0,i,5,e,f,0);pb[K&31](Q,I,c[T>>2]|0,S,5,e,f,0);pb[K&31](P,J,c[T>>2]|0,S,5,e,f,0);return}else{pb[L&31](R,H,c[U>>2]|0,i,c[f+864>>2]|0,c[f+888+(F<<4)>>2]|0,c[f+888+(G<<4)+8>>2]|0,(c[f+888+(G<<4)+12>>2]|0)+(c[f+888+(F<<4)+4>>2]|0)|0);i=f+868|0;pb[K&31](Q,I,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(F<<5)>>2]|0,c[f+1656+(G<<5)+16>>2]|0,(c[f+1656+(G<<5)+20>>2]|0)+(c[f+1656+(F<<5)+4>>2]|0)|0);pb[K&31](P,J,c[T>>2]|0,S,c[i>>2]|0,c[f+1656+(F<<5)+8>>2]|0,c[f+1656+(G<<5)+24>>2]|0,(c[f+1656+(G<<5)+28>>2]|0)+(c[f+1656+(F<<5)+12>>2]|0)|0);return}}function Gc(a,b){a=a|0;b=b|0;var d=0;d=c[b>>2]|0;if(!d)return;if(!(c[d+360>>2]|0))return;Zz(c[a+4>>2]|0,b+4|0);NB(b+48|0);a=b+16|0;NB(a);NB(b+40|0);NB(b+24|0);NB(b+56|0);NB(b+28|0);NB(b+60|0);FF(a|0,0,648)|0;return}function Hc(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;h=i;i=i+32|0;g=h+16|0;e=h;f=c[b>>2]|0;if(c[f+360>>2]|0){c[e>>2]=61897;c[e+4>>2]=61913;c[e+8>>2]=72;UC(0,0,104466,e);Ya()}e=c[d>>2]|0;if(!(c[e+360>>2]|0)){c[g>>2]=61939;c[g+4>>2]=61913;c[g+8>>2]=73;UC(0,0,104466,g);Ya()}g=d+4|0;c[g>>2]=e;e=b+4|0;c[e>>2]=f;e=Yz(e,g)|0;do if((e|0)>=0){g=b+16|0;c[g>>2]=MB(c[d+16>>2]|0)|0;f=MB(c[d+40>>2]|0)|0;c[b+40>>2]=f;if(!((f|0)==0|(c[g>>2]|0)==0)){c[b+20>>2]=c[d+20>>2];c[b+44>>2]=c[d+44>>2];g=b+24|0;c[g>>2]=MB(c[d+24>>2]|0)|0;f=MB(c[d+56>>2]|0)|0;c[b+56>>2]=f;if(!((f|0)==0|(c[g>>2]|0)==0)){c[b+32>>2]=c[d+32>>2];c[b+64>>2]=c[d+64>>2];g=b+28|0;c[g>>2]=MB(c[d+28>>2]|0)|0;f=MB(c[d+60>>2]|0)|0;c[b+60>>2]=f;if(!((f|0)==0|(c[g>>2]|0)==0)){c[b+36>>2]=c[d+36>>2];c[b+68>>2]=c[d+68>>2];if(c[d+52>>2]|0){f=MB(c[d+48>>2]|0)|0;c[b+48>>2]=f;if(!f)break;c[b+52>>2]=c[f+4>>2]}c[b+72>>2]=c[d+72>>2];c[b+76>>2]=c[d+76>>2];KF(b+100|0,d+100|0,512)|0;a=b+612|0;g=d+612|0;c[a>>2]=c[g>>2];c[a+4>>2]=c[g+4>>2];c[a+8>>2]=c[g+8>>2];c[a+12>>2]=c[g+12>>2];c[b+80>>2]=c[d+80>>2];c[b+84>>2]=c[d+84>>2];c[b+88>>2]=c[d+88>>2];c[b+92>>2]=c[d+92>>2];c[b+96>>2]=c[d+96>>2];c[b+628>>2]=c[d+628>>2];c[b+632>>2]=c[d+632>>2];c[b+636>>2]=c[d+636>>2];c[b+652>>2]=c[d+652>>2];c[b+656>>2]=c[d+656>>2];c[b+660>>2]=c[d+660>>2];c[b+640>>2]=c[d+640>>2];c[b+644>>2]=c[d+644>>2];c[b+648>>2]=c[d+648>>2];d=0;i=h;return d|0}}}}while(0);Gc(a,b);d=e;i=h;return d|0}function Ic(a,b){a=a|0;b=b|0;c[a>>2]=0;c[a+4>>2]=0;c[a+8>>2]=0;c[a+12>>2]=0;c[a+16>>2]=0;c[a+20>>2]=0;c[a+24>>2]=0;c[a+28>>2]=0;if(!b)return;c[a>>2]=c[b>>2];c[a+4>>2]=b+4;c[a+8>>2]=c[b+32>>2];c[a+16>>2]=c[b+64>>2];c[a+12>>2]=c[b+36>>2];c[a+20>>2]=c[b+68>>2];c[a+24>>2]=c[b+44>>2];c[a+28>>2]=c[b+632>>2];return}function Jc(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;m=i;i=i+32|0;h=m+8|0;g=m;f=c[a+4>>2]|0;c[a+198224>>2]=0;l=(d|0)!=0;if(l)e=3;else if(!(c[f+816>>2]&1))e=3;else d=0;if((e|0)==3){if(!(c[a+25992>>2]|0)){d=Uc(a,a+202440|0,c[a+203232>>2]|0)|0;c[a+199452>>2]=c[a+199432>>2];c[a+199456>>2]=c[a+199428>>2]}else d=0;c[a+199464>>2]=c[a+199460>>2];c[a+199468>>2]=c[a+199448>>2]}e=c[f+712>>2]|0;if(!e)k=d;else{d=nb[c[e+36>>2]&15](f)|0;if((d|0)<0){UC(f,16,61954,g);k=d}else k=d}if((c[a+25952>>2]|0)!=(b|0)){c[h>>2]=62001;c[h+4>>2]=61913;c[h+8>>2]=190;UC(0,0,104466,h);Ya()}j=a+197612|0;if((c[j>>2]|0)==3)if(c[a+203248>>2]|0)if(!(c[a+27860>>2]|0))if(c[a+203620>>2]|0){d=a+25288|0;if(!(c[(c[d>>2]|0)+360>>2]|0))g=0;else g=(c[b+23372>>2]|0)==0;h=b+24|0;e=b+672|0;f=c[a+24620>>2]|0;c[e>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0;c[e+16>>2]=0;c[e+20>>2]=0;c[e+24>>2]=0;c[e+28>>2]=0;if(f|0){c[e>>2]=c[f>>2];c[b+676>>2]=f+4;c[b+680>>2]=c[f+32>>2];c[b+688>>2]=c[f+64>>2];c[b+684>>2]=c[f+36>>2];c[b+692>>2]=c[f+68>>2];c[b+696>>2]=c[f+44>>2];c[b+700>>2]=c[f+632>>2]}do if(g){f=b+704|0;c[f>>2]=0;c[f+4>>2]=0;c[f+8>>2]=0;c[f+12>>2]=0;c[f+16>>2]=0;c[f+20>>2]=0;c[f+24>>2]=0;c[f>>2]=c[d>>2];c[b+708>>2]=a+25292;c[b+712>>2]=c[a+25320>>2];c[b+720>>2]=c[a+25352>>2];c[b+716>>2]=c[a+25324>>2];c[b+724>>2]=c[a+25356>>2];c[b+728>>2]=c[a+25332>>2];c[b+732>>2]=c[a+25920>>2];f=b+23384|0;c[b+23420>>2]=d;e=c[d>>2]|0;c[f>>2]=c[e>>2];c[f+4>>2]=c[e+4>>2];c[f+8>>2]=c[e+8>>2];f=b+23396|0;e=(c[d>>2]|0)+32|0;c[f>>2]=c[e>>2];c[f+4>>2]=c[e+4>>2];c[f+8>>2]=c[e+8>>2];c[b+23408>>2]=c[a+25924>>2]}else{d=b+704|0;if(!(c[b+23372>>2]|0)){c[d>>2]=0;c[d+4>>2]=0;c[d+8>>2]=0;c[d+12>>2]=0;c[d+16>>2]=0;c[d+20>>2]=0;c[d+24>>2]=0;c[d+28>>2]=0;break}e=c[b+23420>>2]|0;c[d>>2]=0;c[d+4>>2]=0;c[d+8>>2]=0;c[d+12>>2]=0;c[d+16>>2]=0;c[d+20>>2]=0;c[d+24>>2]=0;c[d+28>>2]=0;if(e|0){c[d>>2]=c[e>>2];c[b+708>>2]=e+4;c[b+712>>2]=c[e+32>>2];c[b+720>>2]=c[e+64>>2];c[b+716>>2]=c[e+36>>2];c[b+724>>2]=c[e+68>>2];c[b+728>>2]=c[e+44>>2];c[b+732>>2]=c[e+632>>2]}}while(0);if(c[b+23376>>2]|0){d=b+736|0;e=c[b+25340>>2]|0;c[d>>2]=0;c[d+4>>2]=0;c[d+8>>2]=0;c[d+12>>2]=0;c[d+16>>2]=0;c[d+20>>2]=0;c[d+24>>2]=0;c[d+28>>2]=0;if(e|0){c[d>>2]=c[e>>2];c[b+740>>2]=e+4;c[b+744>>2]=c[e+32>>2];c[b+752>>2]=c[e+64>>2];c[b+748>>2]=c[e+36>>2];c[b+756>>2]=c[e+68>>2];c[b+760>>2]=c[e+44>>2];c[b+764>>2]=c[e+632>>2]}}c[b+796>>2]=c[b+23372>>2];qA(h);if(g){d=b+23384|0;e=d+40|0;do{c[d>>2]=0;d=d+4|0}while((d|0)<(e|0))}}if(l){a=a+203248|0;c[a>>2]=0;i=m;return k|0}if(c[a+25992>>2]|0){a=a+203248|0;c[a>>2]=0;i=m;return k|0}bA((c[a+24620>>2]|0)+4|0,2147483647,(c[j>>2]|0)==2&1);a=a+203248|0;c[a>>2]=0;i=m;return k|0}
function IA(f,g,h,j,k,l,m,n,o){f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;var p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0;ia=i;i=i+48|0;ha=ia+40|0;X=ia+16|0;aa=ia;Z=f+198236|0;ga=(_(c[Z>>2]|0,j)|0)+h|0;Y=f+24668|0;ba=c[(c[Y>>2]|0)+(ga<<2)>>2]|0;ca=(ba>>>6&2^2)+2|0;P=f+26672|0;h=c[P>>2]|0;da=(h|0)!=0;fa=(_(c[f+27844>>2]|0,-6)|0)+48|0;ea=(c[g+848>>2]|0)+52+fa|0;fa=(c[g+852>>2]|0)+52+fa|0;Q=f+197608|0;B=g+21716|0;if(!(c[Q>>2]|0))p=0;else{W=c[B>>2]|0;if((W|0)==0|((W^ba)&128|0)==0)p=0;else{h=X+7&-8;A=h;if(!(ba&7)){r=j&1;h=c[g+21796>>2]|0;s=f+27868|0;t=f+26256|0;u=f+197624|0;v=(h|0)!=0;w=0;do{q=v?w>>2:w&1;p=c[g+21696+(q<<2)>>2]|0;q=c[g+21716+(q<<2)>>2]|0;if(!(q&7)){if((q&16777216|0)==0|(c[s>>2]|0)!=0)p=d[(d[96884+(h<<4)+(r<<3)+w>>0]|0)+((c[t>>2]|0)+(p*48|0))>>0]|0;else p=e[(c[u>>2]|0)+(p<<1)>>1]&((v?w&2:r)|0?32768:8192);b[A+(w<<1)>>1]=p|d[(w>>>1<<3)+12+(g+43648)>>0]|0?2:1}else b[A+(w<<1)>>1]=4;w=w+1|0}while((w|0)!=8)}else{c[h>>2]=262148;c[h+4>>2]=262148;h=A+8|0;c[h>>2]=262148;c[h+4>>2]=262148;h=c[g+21796>>2]|0}z=c[f+24644>>2]|0;W=a[z+ga>>0]|0;y=a[z+(c[g+21696>>2]|0)>>0]|0;z=a[z+(c[g+21700>>2]|0)>>0]|0;u=W+1|0;x=(d[f+28412+W>>0]|0)+1|0;W=(d[f+28500+W>>0]|0)+1|0;p=u+y>>1;v=(x+(d[f+28412+y>>0]|0)|0)>>>1;y=(W+(d[f+28500+y>>0]|0)|0)>>>1;u=u+z>>1;x=(x+(d[f+28412+z>>0]|0)|0)>>>1;z=(W+(d[f+28500+z>>0]|0)|0)>>>1;do if(!h){t=n<<1;q=p+ea|0;r=d[96916+q>>0]|0;W=p+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](k,t,r,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[A+8>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[A+12>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+56>>2]&127](k,t,r,h,ha);break}}while(0);q=k+n|0;w=A+2|0;r=u+ea|0;s=d[96916+r>>0]|0;W=u+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](q,t,s,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(r<<2))>>0]|0;a[ha+1>>0]=a[(b[A+6>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+2>>0]=a[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+3>>0]=a[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[f+56>>2]&127](q,t,s,h,ha);break}}while(0);if(da){t=o<<1;q=v+ea|0;r=d[96916+q>>0]|0;h=v+fa|0;p=d[97072+h>>0]|0;h=q>>>0<68|h>>>0<68;if((c[P>>2]|0)==3){do if(!h){h=b[A>>1]|0;if(h<<16>>16>3){ub[c[f+68>>2]&255](l,t,r,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[A+8>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[A+12>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+56>>2]&127](l,t,r,p,ha);break}}while(0);q=l+o|0;r=x+ea|0;s=d[96916+r>>0]|0;W=x+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](q,t,s,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(r<<2))>>0]|0;a[ha+1>>0]=a[(b[A+6>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+2>>0]=a[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+3>>0]=a[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[f+56>>2]&127](q,t,s,h,ha);break}}while(0);q=y+ea|0;r=d[96916+q>>0]|0;W=y+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](m,t,r,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[A+8>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[A+12>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+56>>2]&127](m,t,r,h,ha);break}}while(0);q=m+o|0;r=z+ea|0;s=d[96916+r>>0]|0;W=z+fa|0;p=d[97072+W>>0]|0;if(r>>>0<68|W>>>0<68)break;h=b[w>>1]|0;if(h<<16>>16>3){ub[c[f+68>>2]&255](q,t,s,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(r<<2))>>0]|0;a[ha+1>>0]=a[(b[A+6>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+2>>0]=a[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+3>>0]=a[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[f+56>>2]&127](q,t,s,p,ha);break}}else{do if(!h){h=b[A>>1]|0;if(h<<16>>16>3){ub[c[f+92>>2]&255](l,t,r,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+8>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+12>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+80>>2]&127](l,t,r,p,ha);break}}while(0);r=l+o|0;s=x+ea|0;q=d[96916+s>>0]|0;W=x+fa|0;h=d[97072+W>>0]|0;do if(!(s>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](r,t,q,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(s<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+6>>1]|0)+(97228+(s<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+10>>1]|0)+(97228+(s<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+14>>1]|0)+(97228+(s<<2))>>0]|0)+1;fb[c[f+80>>2]&127](r,t,q,h,ha);break}}while(0);q=y+ea|0;r=d[96916+q>>0]|0;W=y+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](m,t,r,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+8>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+12>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+80>>2]&127](m,t,r,h,ha);break}}while(0);q=m+o|0;r=z+ea|0;s=d[96916+r>>0]|0;W=z+fa|0;p=d[97072+W>>0]|0;if(r>>>0<68|W>>>0<68)break;h=b[w>>1]|0;if(h<<16>>16>3){ub[c[f+92>>2]&255](q,t,s,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(r<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+6>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[f+80>>2]&127](q,t,s,p,ha);break}}}}else{q=p+ea|0;r=d[96916+q>>0]|0;W=p+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](k,n,r,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[A+2>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[A+6>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+56>>2]&127](k,n,r,h,ha);break}}while(0);q=k+(n<<3)|0;w=A+8|0;r=u+ea|0;s=d[96916+r>>0]|0;W=u+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](q,n,s,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(r<<2))>>0]|0;a[ha+1>>0]=a[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+2>>0]=a[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+3>>0]=a[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[f+56>>2]&127](q,n,s,h,ha);break}}while(0);if(da){h=c[P>>2]|0;if((h|0)==3){q=v+ea|0;r=d[96916+q>>0]|0;W=v+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](l,o,r,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[A+2>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[A+6>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+56>>2]&127](l,o,r,h,ha);break}}while(0);t=o<<3;q=l+t|0;r=x+ea|0;s=d[96916+r>>0]|0;W=x+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](q,o,s,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(r<<2))>>0]|0;a[ha+1>>0]=a[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+2>>0]=a[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+3>>0]=a[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[f+56>>2]&127](q,o,s,h,ha);break}}while(0);q=y+ea|0;r=d[96916+q>>0]|0;W=y+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+68>>2]&255](m,o,r,h);break}else{a[ha>>0]=a[(p<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[A+2>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[A+6>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+56>>2]&127](m,o,r,h,ha);break}}while(0);q=m+t|0;r=z+ea|0;s=d[96916+r>>0]|0;W=z+fa|0;p=d[97072+W>>0]|0;if(r>>>0<68|W>>>0<68)break;h=b[w>>1]|0;if(h<<16>>16>3){ub[c[f+68>>2]&255](q,o,s,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(r<<2))>>0]|0;a[ha+1>>0]=a[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+2>>0]=a[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0;a[ha+3>>0]=a[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[f+56>>2]&127](q,o,s,p,ha);break}}q=(h|0)==2;r=v+ea|0;s=d[96916+r>>0]|0;W=v+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](l,o,s,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(r<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+2>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+4>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+6>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[f+80>>2]&127](l,o,s,h,ha);break}}while(0);if(q){t=o<<3;q=l+t|0;r=x+ea|0;s=d[96916+r>>0]|0;W=x+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](q,o,s,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(r<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[f+80>>2]&127](q,o,s,h,ha);break}}while(0);q=y+ea|0;r=d[96916+q>>0]|0;W=y+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](m,o,r,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+2>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+6>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+80>>2]&127](m,o,r,h,ha);break}}while(0);q=m+t|0;r=z+ea|0;s=d[96916+r>>0]|0;W=z+fa|0;p=d[97072+W>>0]|0;if(r>>>0<68|W>>>0<68)break;h=b[w>>1]|0;if(h<<16>>16>3){ub[c[f+92>>2]&255](q,o,s,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(r<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[f+80>>2]&127](q,o,s,p,ha);break}}else{t=o<<2;q=l+t|0;r=x+ea|0;s=d[96916+r>>0]|0;W=x+fa|0;h=d[97072+W>>0]|0;do if(!(r>>>0<68|W>>>0<68)){p=b[w>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](q,o,s,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(r<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[f+80>>2]&127](q,o,s,h,ha);break}}while(0);q=y+ea|0;r=d[96916+q>>0]|0;W=y+fa|0;h=d[97072+W>>0]|0;do if(!(q>>>0<68|W>>>0<68)){p=b[A>>1]|0;if(p<<16>>16>3){ub[c[f+92>>2]&255](m,o,r,h);break}else{a[ha>>0]=(d[(p<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+2>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+4>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+6>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+80>>2]&127](m,o,r,h,ha);break}}while(0);q=m+t|0;r=z+ea|0;s=d[96916+r>>0]|0;W=z+fa|0;p=d[97072+W>>0]|0;if(r>>>0<68|W>>>0<68)break;h=b[w>>1]|0;if(h<<16>>16>3){ub[c[f+92>>2]&255](q,o,s,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(r<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[A+10>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[A+12>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[A+14>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[f+80>>2]&127](q,o,s,p,ha);break}}}}while(0);h=c[P>>2]|0;p=1}}N=(h|0)==3;t=ga+-1|0;h=c[B>>2]|0;O=ba>>>3&7;W=a[97852+O>>0]|0;L=W&255;if(W<<24>>24==3)M=(c[g+21824>>2]&15|0)==0?1:4;else M=4;z=ba&40;if(!((p|0)!=0|(h|0)==0)){r=aa+7&-8;y=r;p=h|ba;do if(!(p&7)){if(!((z|0)==0|(h&40|0)==0)){W=(JA(g,12,11,ca)|0)&65535;h=y+6|0;b[h>>1]=W;p=y+4|0;b[p>>1]=W;q=y+2|0;b[q>>1]=W;b[y>>1]=W;b[y>>1]=(a[g+43659>>0]|a[g+43660>>0])<<24>>24==0?W:2;if((a[g+43667>>0]|a[g+43668>>0])<<24>>24)b[q>>1]=2;if((a[g+43675>>0]|a[g+43676>>0])<<24>>24)b[p>>1]=2;if(!((a[g+43683>>0]|a[g+43684>>0])<<24>>24))break;b[h>>1]=2;break}if(!((a[g+43659>>0]|a[g+43660>>0])<<24>>24))h=(JA(g,12,11,ca)|0)&65535;else h=2;b[y>>1]=h;if(!((a[g+43667>>0]|a[g+43668>>0])<<24>>24))h=(JA(g,20,19,ca)|0)&65535;else h=2;b[y+2>>1]=h;if(!((a[g+43675>>0]|a[g+43676>>0])<<24>>24))h=(JA(g,28,27,ca)|0)&65535;else h=2;b[y+4>>1]=h;if(!((a[g+43683>>0]|a[g+43684>>0])<<24>>24))h=(JA(g,36,35,ca)|0)&65535;else h=2;b[y+6>>1]=h}else{h=r;W=h;c[W>>2]=196611;c[W+4>>2]=196611;if(p&128|0)if(!(c[Q>>2]|0))if((c[f+197612>>2]|0)==3)break;W=h;c[W>>2]=262148;c[W+4>>2]=262148}while(0);V=c[r>>2]|0;r=V&65535;s=V<<16>>16;v=y+2|0;w=y+4|0;W=c[w>>2]|0;x=y+6|0;do if((s+(V>>16)+(W<<16>>16)|0)!=(0-(W>>16)|0)){W=c[f+24644>>2]|0;u=a[W+t>>0]|0;W=(a[W+ga>>0]|0)+1+u>>1;t=(c[g+828>>2]|0)+1+(d[f+28412+u>>0]|0)>>1;u=(c[g+832>>2]|0)+1+(d[f+28500+u>>0]|0)>>1;h=W+ea|0;p=d[96916+h>>0]|0;W=W+fa|0;q=d[97072+W>>0]|0;do if(!(h>>>0<68|W>>>0<68))if(r<<16>>16>3){ub[c[f+64>>2]&255](k,n,p,q);break}else{a[ha>>0]=a[97228+(h<<2)+s>>0]|0;a[ha+1>>0]=a[(b[v>>1]|0)+(97228+(h<<2))>>0]|0;a[ha+2>>0]=a[(b[w>>1]|0)+(97228+(h<<2))>>0]|0;a[ha+3>>0]=a[(b[x>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[f+52>>2]&127](k,n,p,q,ha);break}while(0);if(da){q=t+ea|0;r=d[96916+q>>0]|0;h=t+fa|0;p=d[97072+h>>0]|0;h=q>>>0<68|h>>>0<68;if(N){do if(!h){h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+64>>2]&255](l,o,r,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[v>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[w>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[x>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+52>>2]&127](l,o,r,p,ha);break}}while(0);q=u+ea|0;r=d[96916+q>>0]|0;W=u+fa|0;p=d[97072+W>>0]|0;if(q>>>0<68|W>>>0<68)break;h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+64>>2]&255](m,o,r,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[v>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[w>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[x>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+52>>2]&127](m,o,r,p,ha);break}}else{do if(!h){h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+88>>2]&255](l,o,r,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[v>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[w>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[x>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+76>>2]&127](l,o,r,p,ha);break}}while(0);q=u+ea|0;r=d[96916+q>>0]|0;W=u+fa|0;p=d[97072+W>>0]|0;if(q>>>0<68|W>>>0<68)break;h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+88>>2]&255](m,o,r,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[v>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[w>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[x>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+76>>2]&127](m,o,r,p,ha);break}}}}while(0)}if(M>>>0>1){G=aa+7&-8;I=G;J=ba&16777216;K=(ba&7|0)==0;s=(z|0)==0;t=I+6|0;u=I+4|0;v=I+2|0;w=G;x=f+24644|0;y=f+25960|0;z=g+828|0;A=g+832|0;B=ha+1|0;C=ha+2|0;D=ha+3|0;E=f+52|0;F=f+76|0;H=1;do{do if(!(J&H<<24)){if(K){if(!(H&L))if(s)r=1;else{r=(JA(g,H+12|0,H+11|0,ca)|0)&65535;b[t>>1]=r;b[u>>1]=r;b[v>>1]=r;b[I>>1]=r;r=0}else{r=w;c[r>>2]=0;c[r+4>>2]=0;r=0}h=H+12|0;p=H+11|0;q=(a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24==0;if(r){if(q)h=(JA(g,h,p,ca)|0)&65535;else h=2;b[I>>1]=h;h=H+20|0;p=H+19|0;if(!((a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24))h=(JA(g,h,p,ca)|0)&65535;else h=2;b[v>>1]=h;h=H+28|0;p=H+27|0;if(!((a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24))h=(JA(g,h,p,ca)|0)&65535;else h=2;b[u>>1]=h;h=H+36|0;p=H+35|0;if(!((a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24))h=(JA(g,h,p,ca)|0)&65535;else h=2;b[t>>1]=h}else{if(!q)b[I>>1]=2;if((a[H+19+(g+43648)>>0]|a[H+20+(g+43648)>>0])<<24>>24)b[v>>1]=2;if((a[H+27+(g+43648)>>0]|a[H+28+(g+43648)>>0])<<24>>24)b[u>>1]=2;if((a[H+35+(g+43648)>>0]|a[H+36+(g+43648)>>0])<<24>>24)b[t>>1]=2}V=c[G>>2]|0;W=c[u>>2]|0;if(((V<<16>>16)+(V>>16)+(W<<16>>16)|0)==(0-(W>>16)|0))break}else{W=w;c[W>>2]=196611;c[W+4>>2]=196611}p=a[(c[x>>2]|0)+ga>>0]|0;q=H<<2;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){W=d[97072+p>>0]|0;V=d[96916+h>>0]|0;U=k+(q<<c[y>>2])|0;a[ha>>0]=a[(b[I>>1]|0)+(97228+(h<<2))>>0]|0;a[B>>0]=a[(b[v>>1]|0)+(97228+(h<<2))>>0]|0;a[C>>0]=a[(b[u>>1]|0)+(97228+(h<<2))>>0]|0;a[D>>0]=a[(b[t>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[E>>2]&127](U,n,V,W,ha)}if(da){if(N){p=c[z>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){W=d[97072+p>>0]|0;V=d[96916+h>>0]|0;U=l+(q<<c[y>>2])|0;a[ha>>0]=a[(b[I>>1]|0)+(97228+(h<<2))>>0]|0;a[B>>0]=a[(b[v>>1]|0)+(97228+(h<<2))>>0]|0;a[C>>0]=a[(b[u>>1]|0)+(97228+(h<<2))>>0]|0;a[D>>0]=a[(b[t>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[E>>2]&127](U,o,V,W,ha)}p=c[A>>2]|0;h=p+ea|0;p=p+fa|0;if(h>>>0<68|p>>>0<68)break;W=d[97072+p>>0]|0;V=d[96916+h>>0]|0;U=m+(q<<c[y>>2])|0;a[ha>>0]=a[(b[I>>1]|0)+(97228+(h<<2))>>0]|0;a[B>>0]=a[(b[v>>1]|0)+(97228+(h<<2))>>0]|0;a[C>>0]=a[(b[u>>1]|0)+(97228+(h<<2))>>0]|0;a[D>>0]=a[(b[t>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[E>>2]&127](U,o,V,W,ha);break}if(!(H&1)){q=H<<1;p=c[z>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){W=d[97072+p>>0]|0;V=d[96916+h>>0]|0;U=l+(q<<c[y>>2])|0;a[ha>>0]=(d[(b[I>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[B>>0]=(d[(b[v>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[C>>0]=(d[(b[u>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[D>>0]=(d[(b[t>>1]|0)+(97228+(h<<2))>>0]|0)+1;fb[c[F>>2]&127](U,o,V,W,ha)}p=c[A>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){W=d[97072+p>>0]|0;V=d[96916+h>>0]|0;U=m+(q<<c[y>>2])|0;a[ha>>0]=(d[(b[I>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[B>>0]=(d[(b[v>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[C>>0]=(d[(b[u>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[D>>0]=(d[(b[t>>1]|0)+(97228+(h<<2))>>0]|0)+1;fb[c[F>>2]&127](U,o,V,W,ha)}}}}while(0);H=H+1|0}while((H|0)<(M|0))}W=c[P>>2]|0;V=(W|0)==3;W=(W|0)==2;t=c[g+21688>>2]|0;r=c[g+21708>>2]|0;U=a[97860+O>>0]|0;T=U&255;if(U<<24>>24==3)U=(c[g+21824>>2]&15|0)==0?1:4;else U=4;S=ba&24;a:do if(r|0){p=c[Q>>2]|0;if((j&1|0)==0&(p|0)!=0)if(r&(ba&128^128)|0){j=n<<1;R=o<<1;O=X+7&-8;v=O;w=f+27868|0;x=f+26256|0;y=f+197624|0;z=g+43660|0;A=g+43661|0;B=v+2|0;C=v+4|0;D=v+6|0;E=g+43663|0;F=g+43662|0;G=f+24644|0;H=g+828|0;I=g+832|0;J=ha+1|0;K=ha+2|0;L=ha+3|0;M=f+48|0;N=f+72|0;P=0;Q=ga-(c[Z>>2]<<1)|0;while(1){h=c[(c[Y>>2]|0)+(Q<<2)>>2]|0;do if(!((h|ba)&7)){if((h&16777216|0)==0|(c[w>>2]|0)!=0){X=c[x>>2]|0;b[v>>1]=(a[X+(Q*48|0)+12>>0]|a[z>>0])<<24>>24?2:1;b[B>>1]=(a[X+(Q*48|0)+13>>0]|a[A>>0])<<24>>24?2:1;b[C>>1]=(a[X+(Q*48|0)+14>>0]|a[F>>0])<<24>>24?2:1;b[D>>1]=(a[X+(Q*48|0)+15>>0]|a[E>>0])<<24>>24?2:1;break}p=(c[y>>2]|0)+(Q<<1)|0;if(!(b[p>>1]&16384))if(!(a[z>>0]|0))h=1;else $=213;else $=213;if(($|0)==213){$=0;h=2}b[v>>1]=h;if(!(b[p>>1]&16384))if(!(a[A>>0]|0))h=1;else $=216;else $=216;if(($|0)==216){$=0;h=2}b[B>>1]=h;if((b[p>>1]|0)<0)$=219;else if(!(a[F>>0]|0))h=1;else $=219;if(($|0)==219){$=0;h=2}b[C>>1]=h;if((b[p>>1]|0)<0)$=222;else if(!(a[E>>0]|0))h=1;else $=222;if(($|0)==222){$=0;h=2}b[D>>1]=h}else{X=O;c[X>>2]=196611;c[X+4>>2]=196611}while(0);q=c[G>>2]|0;h=a[q+Q>>0]|0;q=(a[q+ga>>0]|0)+1+(h<<24>>24)>>1;p=q+ea|0;q=q+fa|0;if(!(p>>>0<68|q>>>0<68)){h=d[97072+q>>0]|0;X=d[96916+p>>0]|0;u=k+(_(P,n)|0)|0;a[ha>>0]=a[(b[v>>1]|0)+(97228+(p<<2))>>0]|0;a[J>>0]=a[(b[B>>1]|0)+(97228+(p<<2))>>0]|0;a[K>>0]=a[(b[C>>1]|0)+(97228+(p<<2))>>0]|0;a[L>>0]=a[(b[D>>1]|0)+(97228+(p<<2))>>0]|0;fb[c[M>>2]&127](u,j,X,h,ha);h=a[(c[G>>2]|0)+Q>>0]|0}t=h<<24>>24;h=(c[H>>2]|0)+1+(d[f+28412+t>>0]|0)>>1;t=(c[I>>2]|0)+1+(d[f+28500+t>>0]|0)>>1;do if(da){u=_(P,o)|0;q=l+u|0;r=h+ea|0;s=d[96916+r>>0]|0;p=h+fa|0;h=d[97072+p>>0]|0;p=r>>>0<68|p>>>0<68;if(V){if(!p){a[ha>>0]=a[(b[v>>1]|0)+(97228+(r<<2))>>0]|0;a[J>>0]=a[(b[B>>1]|0)+(97228+(r<<2))>>0]|0;a[K>>0]=a[(b[C>>1]|0)+(97228+(r<<2))>>0]|0;a[L>>0]=a[(b[D>>1]|0)+(97228+(r<<2))>>0]|0;fb[c[M>>2]&127](q,R,s,h,ha)}p=t+ea|0;h=t+fa|0;if(p>>>0<68|h>>>0<68)break;X=d[97072+h>>0]|0;t=d[96916+p>>0]|0;a[ha>>0]=a[(b[v>>1]|0)+(97228+(p<<2))>>0]|0;a[J>>0]=a[(b[B>>1]|0)+(97228+(p<<2))>>0]|0;a[K>>0]=a[(b[C>>1]|0)+(97228+(p<<2))>>0]|0;a[L>>0]=a[(b[D>>1]|0)+(97228+(p<<2))>>0]|0;fb[c[M>>2]&127](m+u|0,R,t,X,ha);break}else{if(!p){a[ha>>0]=(d[(b[v>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[J>>0]=(d[(b[B>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[K>>0]=(d[(b[C>>1]|0)+(97228+(r<<2))>>0]|0)+1;a[L>>0]=(d[(b[D>>1]|0)+(97228+(r<<2))>>0]|0)+1;fb[c[N>>2]&127](q,R,s,h,ha)}p=t+ea|0;h=t+fa|0;if(p>>>0<68|h>>>0<68)break;X=d[97072+h>>0]|0;t=d[96916+p>>0]|0;a[ha>>0]=(d[(b[v>>1]|0)+(97228+(p<<2))>>0]|0)+1;a[J>>0]=(d[(b[B>>1]|0)+(97228+(p<<2))>>0]|0)+1;a[K>>0]=(d[(b[C>>1]|0)+(97228+(p<<2))>>0]|0)+1;a[L>>0]=(d[(b[D>>1]|0)+(97228+(p<<2))>>0]|0)+1;fb[c[N>>2]&127](m+u|0,R,t,X,ha);break}}while(0);P=P+1|0;if((P|0)==2)break a;else Q=(c[Z>>2]|0)+Q|0}}q=aa+7&-8;y=q;h=r|ba;b:do if(!(h&7)){if(!p)$=243;else if(!((r^ba)&128))$=243;else{Z=q;c[Z>>2]=65537;c[Z+4>>2]=65537}do if(($|0)==243){if(!((S|0)==0|(r&24|0)==0)){$=(JA(g,12,4,ca)|0)&65535;b[y+6>>1]=$;b[y+4>>1]=$;b[y+2>>1]=$;b[y>>1]=$;break}if(!((a[g+43652>>0]|a[g+43660>>0])<<24>>24))h=(JA(g,12,4,ca)|0)&65535;else h=2;b[y>>1]=h;if(!((a[g+43653>>0]|a[g+43661>>0])<<24>>24))h=(JA(g,13,5,ca)|0)&65535;else h=2;b[y+2>>1]=h;if(!((a[g+43654>>0]|a[g+43662>>0])<<24>>24))h=(JA(g,14,6,ca)|0)&65535;else h=2;b[y+4>>1]=h;if(!((a[g+43655>>0]|a[g+43663>>0])<<24>>24))h=(JA(g,15,7,ca)|0)&65535;else h=2;b[y+6>>1]=h;break b}while(0);if((a[g+43652>>0]|a[g+43660>>0])<<24>>24)b[y>>1]=2;if((a[g+43653>>0]|a[g+43661>>0])<<24>>24)b[y+2>>1]=2;if((a[g+43654>>0]|a[g+43662>>0])<<24>>24)b[y+4>>1]=2;if((a[g+43655>>0]|a[g+43663>>0])<<24>>24)b[y+6>>1]=2}else{Z=(h&128|0)==0;$=q;c[$>>2]=Z?262148:196611;c[$+4>>2]=Z?262148:196611}while(0);Z=c[q>>2]|0;r=Z&65535;s=Z<<16>>16;v=y+2|0;w=y+4|0;$=c[w>>2]|0;x=y+6|0;do if((s+(Z>>16)+($<<16>>16)|0)!=(0-($>>16)|0)){$=c[f+24644>>2]|0;u=a[$+t>>0]|0;$=(a[$+ga>>0]|0)+1+u>>1;t=(c[g+828>>2]|0)+1+(d[f+28412+u>>0]|0)>>1;u=(c[g+832>>2]|0)+1+(d[f+28500+u>>0]|0)>>1;h=$+ea|0;p=d[96916+h>>0]|0;$=$+fa|0;q=d[97072+$>>0]|0;do if(!(h>>>0<68|$>>>0<68))if(r<<16>>16>3){ub[c[f+60>>2]&255](k,n,p,q);break}else{a[ha>>0]=a[97228+(h<<2)+s>>0]|0;a[ha+1>>0]=a[(b[v>>1]|0)+(97228+(h<<2))>>0]|0;a[ha+2>>0]=a[(b[w>>1]|0)+(97228+(h<<2))>>0]|0;a[ha+3>>0]=a[(b[x>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[f+48>>2]&127](k,n,p,q,ha);break}while(0);if(da){q=t+ea|0;r=d[96916+q>>0]|0;h=t+fa|0;p=d[97072+h>>0]|0;h=q>>>0<68|h>>>0<68;if(V){do if(!h){h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+60>>2]&255](l,o,r,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[v>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[w>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[x>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+48>>2]&127](l,o,r,p,ha);break}}while(0);q=u+ea|0;r=d[96916+q>>0]|0;$=u+fa|0;p=d[97072+$>>0]|0;if(q>>>0<68|$>>>0<68)break;h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+60>>2]&255](m,o,r,p);break}else{a[ha>>0]=a[(h<<16>>16)+(97228+(q<<2))>>0]|0;a[ha+1>>0]=a[(b[v>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+2>>0]=a[(b[w>>1]|0)+(97228+(q<<2))>>0]|0;a[ha+3>>0]=a[(b[x>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[f+48>>2]&127](m,o,r,p,ha);break}}else{do if(!h){h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+84>>2]&255](l,o,r,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[v>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[w>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[x>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+72>>2]&127](l,o,r,p,ha);break}}while(0);q=u+ea|0;r=d[96916+q>>0]|0;$=u+fa|0;p=d[97072+$>>0]|0;if(q>>>0<68|$>>>0<68)break;h=b[y>>1]|0;if(h<<16>>16>3){ub[c[f+84>>2]&255](m,o,r,p);break}else{a[ha>>0]=(d[(h<<16>>16)+(97228+(q<<2))>>0]|0)+1;a[ha+1>>0]=(d[(b[v>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+2>>0]=(d[(b[w>>1]|0)+(97228+(q<<2))>>0]|0)+1;a[ha+3>>0]=(d[(b[x>>1]|0)+(97228+(q<<2))>>0]|0)+1;fb[c[f+72>>2]&127](m,o,r,p,ha);break}}}}while(0)}while(0);if(U>>>0<=1){i=ia;return}v=aa+7&-8;L=v;M=ba&16777216;K=(ba&7|0)==0;x=(S|0)==0;y=L+6|0;z=L+4|0;A=L+2|0;B=v;C=f+24644|0;D=g+828|0;E=g+832|0;F=ha+1|0;G=ha+2|0;H=ha+3|0;I=n<<2;J=f+48|0;u=f+72|0;w=1;do{t=(M&w<<24|0)==0;do if(W|t){if(K){if(!(w&T))if(x)r=1;else{r=w<<3;r=(JA(g,r+12|0,r|4,ca)|0)&65535;b[y>>1]=r;b[z>>1]=r;b[A>>1]=r;b[L>>1]=r;r=0}else{r=B;c[r>>2]=0;c[r+4>>2]=0;r=0}s=w<<3;h=s+12|0;p=s|4;q=(a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24==0;if(r){if(q)h=(JA(g,h,p,ca)|0)&65535;else h=2;b[L>>1]=h;h=s+13|0;p=s|5;if(!((a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24))h=(JA(g,h,p,ca)|0)&65535;else h=2;b[A>>1]=h;h=s+14|0;p=s|6;if(!((a[g+43648+p>>0]|a[g+43648+h>>0])<<24>>24))h=(JA(g,h,p,ca)|0)&65535;else h=2;b[z>>1]=h;p=s+15|0;h=s|7;if(!((a[g+43648+h>>0]|a[g+43648+p>>0])<<24>>24))h=(JA(g,p,h,ca)|0)&65535;else h=2;b[y>>1]=h}else{if(!q)b[L>>1]=2;if((a[(s|5)+(g+43648)>>0]|a[s+13+(g+43648)>>0])<<24>>24)b[A>>1]=2;if((a[(s|6)+(g+43648)>>0]|a[s+14+(g+43648)>>0])<<24>>24)b[z>>1]=2;if((a[(s|7)+(g+43648)>>0]|a[s+15+(g+43648)>>0])<<24>>24)b[y>>1]=2}ba=c[v>>2]|0;f=c[z>>2]|0;if(((ba<<16>>16)+(ba>>16)+(f<<16>>16)|0)==(0-(f>>16)|0))break}else{f=B;c[f>>2]=196611;c[f+4>>2]=196611}h=a[(c[C>>2]|0)+ga>>0]|0;if(W){if(t){p=h+ea|0;h=h+fa|0;if(!(p>>>0<68|h>>>0<68)){f=d[97072+h>>0]|0;ba=d[96916+p>>0]|0;aa=k+(_(I,w)|0)|0;a[ha>>0]=a[(b[L>>1]|0)+(97228+(p<<2))>>0]|0;a[F>>0]=a[(b[A>>1]|0)+(97228+(p<<2))>>0]|0;a[G>>0]=a[(b[z>>1]|0)+(97228+(p<<2))>>0]|0;a[H>>0]=a[(b[y>>1]|0)+(97228+(p<<2))>>0]|0;fb[c[J>>2]&127](aa,n,ba,f,ha)}}if(!da)break;q=_(w<<2,o)|0;p=c[D>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){f=d[97072+p>>0]|0;ba=d[96916+h>>0]|0;a[ha>>0]=(d[(b[L>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[F>>0]=(d[(b[A>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[G>>0]=(d[(b[z>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[H>>0]=(d[(b[y>>1]|0)+(97228+(h<<2))>>0]|0)+1;fb[c[u>>2]&127](l+q|0,o,ba,f,ha)}p=c[E>>2]|0;h=p+ea|0;p=p+fa|0;if(h>>>0<68|p>>>0<68)break;f=d[97072+p>>0]|0;ba=d[96916+h>>0]|0;a[ha>>0]=(d[(b[L>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[F>>0]=(d[(b[A>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[G>>0]=(d[(b[z>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[H>>0]=(d[(b[y>>1]|0)+(97228+(h<<2))>>0]|0)+1;fb[c[u>>2]&127](m+q|0,o,ba,f,ha);break}p=w<<2;q=h+ea|0;h=h+fa|0;if(!(q>>>0<68|h>>>0<68)){f=d[97072+h>>0]|0;ba=d[96916+q>>0]|0;aa=k+(_(p,n)|0)|0;a[ha>>0]=a[(b[L>>1]|0)+(97228+(q<<2))>>0]|0;a[F>>0]=a[(b[A>>1]|0)+(97228+(q<<2))>>0]|0;a[G>>0]=a[(b[z>>1]|0)+(97228+(q<<2))>>0]|0;a[H>>0]=a[(b[y>>1]|0)+(97228+(q<<2))>>0]|0;fb[c[J>>2]&127](aa,n,ba,f,ha)}if(da){if(V){q=_(p,o)|0;p=c[D>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){f=d[97072+p>>0]|0;ba=d[96916+h>>0]|0;a[ha>>0]=a[(b[L>>1]|0)+(97228+(h<<2))>>0]|0;a[F>>0]=a[(b[A>>1]|0)+(97228+(h<<2))>>0]|0;a[G>>0]=a[(b[z>>1]|0)+(97228+(h<<2))>>0]|0;a[H>>0]=a[(b[y>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[J>>2]&127](l+q|0,o,ba,f,ha)}p=c[E>>2]|0;h=p+ea|0;p=p+fa|0;if(h>>>0<68|p>>>0<68)break;f=d[97072+p>>0]|0;ba=d[96916+h>>0]|0;a[ha>>0]=a[(b[L>>1]|0)+(97228+(h<<2))>>0]|0;a[F>>0]=a[(b[A>>1]|0)+(97228+(h<<2))>>0]|0;a[G>>0]=a[(b[z>>1]|0)+(97228+(h<<2))>>0]|0;a[H>>0]=a[(b[y>>1]|0)+(97228+(h<<2))>>0]|0;fb[c[J>>2]&127](m+q|0,o,ba,f,ha);break}if(!(w&1)){q=_(w<<1,o)|0;p=c[D>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){f=d[97072+p>>0]|0;ba=d[96916+h>>0]|0;a[ha>>0]=(d[(b[L>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[F>>0]=(d[(b[A>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[G>>0]=(d[(b[z>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[H>>0]=(d[(b[y>>1]|0)+(97228+(h<<2))>>0]|0)+1;fb[c[u>>2]&127](l+q|0,o,ba,f,ha)}p=c[E>>2]|0;h=p+ea|0;p=p+fa|0;if(!(h>>>0<68|p>>>0<68)){f=d[97072+p>>0]|0;ba=d[96916+h>>0]|0;a[ha>>0]=(d[(b[L>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[F>>0]=(d[(b[A>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[G>>0]=(d[(b[z>>1]|0)+(97228+(h<<2))>>0]|0)+1;a[H>>0]=(d[(b[y>>1]|0)+(97228+(h<<2))>>0]|0)+1;fb[c[u>>2]&127](m+q|0,o,ba,f,ha)}}}}while(0);w=w+1|0}while((w|0)<(U|0));i=ia;return}function JA(d,e,f,g){d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0;j=a[d+44096+e>>0]|0;k=a[d+44096+f>>0]|0;h=j<<24>>24!=k<<24>>24;if(j<<24>>24==-1|h)h=h&1;else{h=(b[d+43776+(e<<2)+2>>1]|0)-(b[d+43776+(f<<2)+2>>1]|0)|0;h=(((h|0)>-1?h:0-h|0)|0)>=(g|0)?1:((b[d+43776+(e<<2)>>1]|0)+3-(b[d+43776+(f<<2)>>1]|0)|0)>>>0>6}if((c[d+23380>>2]|0)!=2){d=h;return d|0}if(!h){i=a[d+44136+e>>0]|0;h=a[d+44136+f>>0]|0;l=(b[d+43936+(e<<2)+2>>1]|0)-(b[d+43936+(f<<2)+2>>1]|0)|0;if(!((i<<24>>24!=h<<24>>24?1:((b[d+43936+(e<<2)>>1]|0)+3-(b[d+43936+(f<<2)>>1]|0)|0)>>>0>6)|(((l|0)>-1?l:0-l|0)|0)>=(g|0))){l=0;return l|0}}else{h=a[d+44136+f>>0]|0;i=a[d+44136+e>>0]|0}if((i<<24>>24!=k<<24>>24?1:j<<24>>24!=h<<24>>24)|0){l=1;return l|0}k=(b[d+43776+(e<<2)+2>>1]|0)-(b[d+43936+(f<<2)+2>>1]|0)|0;l=(b[d+43936+(e<<2)+2>>1]|0)-(b[d+43776+(f<<2)+2>>1]|0)|0;l=((((k|0)>-1?k:0-k|0)|0)>=(g|0)?1:((b[d+43776+(e<<2)>>1]|0)+3-(b[d+43936+(f<<2)>>1]|0)|0)>>>0>6)|((b[d+43936+(e<<2)>>1]|0)+3-(b[d+43776+(f<<2)>>1]|0)|0)>>>0>6|(((l|0)>-1?l:0-l|0)|0)>=(g|0);return l|0}function KA(){var a=0,b=0;a=0;do{b=a+-256|0;c[108164+(a<<2)>>2]=_(b,b)|0;a=a+1|0}while((a|0)!=512);return}function LA(a,b){a=a|0;b=b|0;c[a>>2]=9;c[a+484>>2]=2;c[a+488>>2]=3;c[a+492>>2]=4;c[a+496>>2]=5;c[a+500>>2]=6;c[a+504>>2]=7;c[a+508>>2]=8;c[a+512>>2]=9;c[a+52>>2]=10;c[a+56>>2]=11;c[a+68>>2]=12;c[a+72>>2]=13;c[a+76>>2]=14;c[a+80>>2]=15;c[a+292>>2]=16;c[a+296>>2]=17;c[a+4>>2]=2;c[a+8>>2]=6;c[a+28>>2]=18;c[a+32>>2]=19;c[a+36>>2]=20;c[a+100>>2]=21;c[a+104>>2]=22;c[a+148>>2]=23;c[a+152>>2]=24;c[a+124>>2]=25;c[a+128>>2]=26;c[a+172>>2]=27;c[a+176>>2]=28;c[a+188>>2]=29;c[a+192>>2]=30;c[a+196>>2]=31;c[a+200>>2]=32;c[a+212>>2]=33;c[a+216>>2]=34;c[a+220>>2]=35;c[a+224>>2]=36;return}function MA(a){a=a|0;var c=0,d=0,e=0,f=0;d=0;c=0;do{f=b[a+(d<<1)>>1]|0;e=f<<16>>16;c=(f<<16>>16>-1?e:0-e|0)+c|0;d=d+1|0}while((d|0)!=64);return c|0}function NA(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;if((f|0)>0){g=c;a=0;c=0}else{e=0;return e|0}while(1){w=(d[b>>0]|0)-(d[g>>0]|0)|0;v=(d[b+1>>0]|0)-(d[g+1>>0]|0)|0;u=(d[b+2>>0]|0)-(d[g+2>>0]|0)|0;t=(d[b+3>>0]|0)-(d[g+3>>0]|0)|0;s=(d[b+4>>0]|0)-(d[g+4>>0]|0)|0;r=(d[b+5>>0]|0)-(d[g+5>>0]|0)|0;q=(d[b+6>>0]|0)-(d[g+6>>0]|0)|0;p=(d[b+7>>0]|0)-(d[g+7>>0]|0)|0;o=(d[b+8>>0]|0)-(d[g+8>>0]|0)|0;n=(d[b+9>>0]|0)-(d[g+9>>0]|0)|0;m=(d[b+10>>0]|0)-(d[g+10>>0]|0)|0;l=(d[b+11>>0]|0)-(d[g+11>>0]|0)|0;k=(d[b+12>>0]|0)-(d[g+12>>0]|0)|0;j=(d[b+13>>0]|0)-(d[g+13>>0]|0)|0;i=(d[b+14>>0]|0)-(d[g+14>>0]|0)|0;h=(d[b+15>>0]|0)-(d[g+15>>0]|0)|0;c=((w|0)>-1?w:0-w|0)+c+((v|0)>-1?v:0-v|0)+((u|0)>-1?u:0-u|0)+((t|0)>-1?t:0-t|0)+((s|0)>-1?s:0-s|0)+((r|0)>-1?r:0-r|0)+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)+((o|0)>-1?o:0-o|0)+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;a=a+1|0;if((a|0)==(f|0))break;else{g=g+e|0;b=b+e|0}}return c|0}function OA(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;if((f|0)>0){g=c;a=0;c=0}else{e=0;return e|0}while(1){v=d[g+1>>0]|0;w=(d[b>>0]|0)-(((d[g>>0]|0)+1+v|0)>>>1)|0;u=d[g+2>>0]|0;v=(d[b+1>>0]|0)-((v+1+u|0)>>>1)|0;t=d[g+3>>0]|0;u=(d[b+2>>0]|0)-((u+1+t|0)>>>1)|0;s=d[g+4>>0]|0;t=(d[b+3>>0]|0)-((t+1+s|0)>>>1)|0;r=d[g+5>>0]|0;s=(d[b+4>>0]|0)-((s+1+r|0)>>>1)|0;q=d[g+6>>0]|0;r=(d[b+5>>0]|0)-((r+1+q|0)>>>1)|0;p=d[g+7>>0]|0;q=(d[b+6>>0]|0)-((q+1+p|0)>>>1)|0;o=d[g+8>>0]|0;p=(d[b+7>>0]|0)-((p+1+o|0)>>>1)|0;n=d[g+9>>0]|0;o=(d[b+8>>0]|0)-((o+1+n|0)>>>1)|0;m=d[g+10>>0]|0;n=(d[b+9>>0]|0)-((n+1+m|0)>>>1)|0;l=d[g+11>>0]|0;m=(d[b+10>>0]|0)-((m+1+l|0)>>>1)|0;k=d[g+12>>0]|0;l=(d[b+11>>0]|0)-((l+1+k|0)>>>1)|0;j=d[g+13>>0]|0;k=(d[b+12>>0]|0)-((k+1+j|0)>>>1)|0;i=d[g+14>>0]|0;j=(d[b+13>>0]|0)-((j+1+i|0)>>>1)|0;h=d[g+15>>0]|0;i=(d[b+14>>0]|0)-((i+1+h|0)>>>1)|0;h=(d[b+15>>0]|0)-((h+1+(d[g+16>>0]|0)|0)>>>1)|0;c=((w|0)>-1?w:0-w|0)+c+((v|0)>-1?v:0-v|0)+((u|0)>-1?u:0-u|0)+((t|0)>-1?t:0-t|0)+((s|0)>-1?s:0-s|0)+((r|0)>-1?r:0-r|0)+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)+((o|0)>-1?o:0-o|0)+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;a=a+1|0;if((a|0)==(f|0))break;else{g=g+e|0;b=b+e|0}}return c|0}function PA(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;if((f|0)>0){a=b;g=0;b=0}else{e=0;return e|0}while(1){h=c;c=c+e|0;w=(d[a>>0]|0)-(((d[h>>0]|0)+1+(d[c>>0]|0)|0)>>>1)|0;v=(d[a+1>>0]|0)-(((d[h+1>>0]|0)+1+(d[c+1>>0]|0)|0)>>>1)|0;u=(d[a+2>>0]|0)-(((d[h+2>>0]|0)+1+(d[c+2>>0]|0)|0)>>>1)|0;t=(d[a+3>>0]|0)-(((d[h+3>>0]|0)+1+(d[c+3>>0]|0)|0)>>>1)|0;s=(d[a+4>>0]|0)-(((d[h+4>>0]|0)+1+(d[c+4>>0]|0)|0)>>>1)|0;r=(d[a+5>>0]|0)-(((d[h+5>>0]|0)+1+(d[c+5>>0]|0)|0)>>>1)|0;q=(d[a+6>>0]|0)-(((d[h+6>>0]|0)+1+(d[c+6>>0]|0)|0)>>>1)|0;p=(d[a+7>>0]|0)-(((d[h+7>>0]|0)+1+(d[c+7>>0]|0)|0)>>>1)|0;o=(d[a+8>>0]|0)-(((d[h+8>>0]|0)+1+(d[c+8>>0]|0)|0)>>>1)|0;n=(d[a+9>>0]|0)-(((d[h+9>>0]|0)+1+(d[c+9>>0]|0)|0)>>>1)|0;m=(d[a+10>>0]|0)-(((d[h+10>>0]|0)+1+(d[c+10>>0]|0)|0)>>>1)|0;l=(d[a+11>>0]|0)-(((d[h+11>>0]|0)+1+(d[c+11>>0]|0)|0)>>>1)|0;k=(d[a+12>>0]|0)-(((d[h+12>>0]|0)+1+(d[c+12>>0]|0)|0)>>>1)|0;j=(d[a+13>>0]|0)-(((d[h+13>>0]|0)+1+(d[c+13>>0]|0)|0)>>>1)|0;i=(d[a+14>>0]|0)-(((d[h+14>>0]|0)+1+(d[c+14>>0]|0)|0)>>>1)|0;h=(d[a+15>>0]|0)-(((d[h+15>>0]|0)+1+(d[c+15>>0]|0)|0)>>>1)|0;b=((w|0)>-1?w:0-w|0)+b+((v|0)>-1?v:0-v|0)+((u|0)>-1?u:0-u|0)+((t|0)>-1?t:0-t|0)+((s|0)>-1?s:0-s|0)+((r|0)>-1?r:0-r|0)+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)+((o|0)>-1?o:0-o|0)+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;g=g+1|0;if((g|0)==(f|0))break;else a=a+e|0}return b|0}function QA(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;if((f|0)>0){a=b;g=0;b=0}else{e=0;return e|0}while(1){h=c;c=c+e|0;y=d[h+1>>0]|0;v=d[c+1>>0]|0;w=(d[a>>0]|0)-(((d[h>>0]|0)+2+y+(d[c>>0]|0)+v|0)>>>2)|0;z=d[h+2>>0]|0;u=d[c+2>>0]|0;v=(d[a+1>>0]|0)-((y+2+v+z+u|0)>>>2)|0;y=d[h+3>>0]|0;t=d[c+3>>0]|0;u=(d[a+2>>0]|0)-((z+2+u+y+t|0)>>>2)|0;z=d[h+4>>0]|0;s=d[c+4>>0]|0;t=(d[a+3>>0]|0)-((y+2+t+z+s|0)>>>2)|0;y=d[h+5>>0]|0;r=d[c+5>>0]|0;s=(d[a+4>>0]|0)-((z+2+s+y+r|0)>>>2)|0;z=d[h+6>>0]|0;q=d[c+6>>0]|0;r=(d[a+5>>0]|0)-((y+2+r+z+q|0)>>>2)|0;y=d[h+7>>0]|0;p=d[c+7>>0]|0;q=(d[a+6>>0]|0)-((z+2+q+y+p|0)>>>2)|0;z=d[h+8>>0]|0;o=d[c+8>>0]|0;p=(d[a+7>>0]|0)-((y+2+p+z+o|0)>>>2)|0;y=d[h+9>>0]|0;n=d[c+9>>0]|0;o=(d[a+8>>0]|0)-((z+2+o+y+n|0)>>>2)|0;z=d[h+10>>0]|0;m=d[c+10>>0]|0;n=(d[a+9>>0]|0)-((y+2+n+z+m|0)>>>2)|0;y=d[h+11>>0]|0;l=d[c+11>>0]|0;m=(d[a+10>>0]|0)-((z+2+m+y+l|0)>>>2)|0;z=d[h+12>>0]|0;k=d[c+12>>0]|0;l=(d[a+11>>0]|0)-((y+2+l+z+k|0)>>>2)|0;y=d[h+13>>0]|0;j=d[c+13>>0]|0;k=(d[a+12>>0]|0)-((z+2+k+y+j|0)>>>2)|0;z=d[h+14>>0]|0;i=d[c+14>>0]|0;j=(d[a+13>>0]|0)-((y+2+j+z+i|0)>>>2)|0;y=d[h+15>>0]|0;x=d[c+15>>0]|0;i=(d[a+14>>0]|0)-((z+2+i+y+x|0)>>>2)|0;h=(d[a+15>>0]|0)-((y+2+x+(d[h+16>>0]|0)+(d[c+16>>0]|0)|0)>>>2)|0;b=((w|0)>-1?w:0-w|0)+b+((v|0)>-1?v:0-v|0)+((u|0)>-1?u:0-u|0)+((t|0)>-1?t:0-t|0)+((s|0)>-1?s:0-s|0)+((r|0)>-1?r:0-r|0)+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)+((o|0)>-1?o:0-o|0)+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;g=g+1|0;if((g|0)==(f|0))break;else a=a+e|0}return b|0}function RA(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;if((f|0)>0){g=c;a=0;c=0}else{e=0;return e|0}while(1){o=(d[b>>0]|0)-(d[g>>0]|0)|0;n=(d[b+1>>0]|0)-(d[g+1>>0]|0)|0;m=(d[b+2>>0]|0)-(d[g+2>>0]|0)|0;l=(d[b+3>>0]|0)-(d[g+3>>0]|0)|0;k=(d[b+4>>0]|0)-(d[g+4>>0]|0)|0;j=(d[b+5>>0]|0)-(d[g+5>>0]|0)|0;i=(d[b+6>>0]|0)-(d[g+6>>0]|0)|0;h=(d[b+7>>0]|0)-(d[g+7>>0]|0)|0;c=((o|0)>-1?o:0-o|0)+c+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;a=a+1|0;if((a|0)==(f|0))break;else{g=g+e|0;b=b+e|0}}return c|0}function SA(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;if((f|0)>0){g=c;a=0;c=0}else{e=0;return e|0}while(1){n=d[g+1>>0]|0;o=(d[b>>0]|0)-(((d[g>>0]|0)+1+n|0)>>>1)|0;m=d[g+2>>0]|0;n=(d[b+1>>0]|0)-((n+1+m|0)>>>1)|0;l=d[g+3>>0]|0;m=(d[b+2>>0]|0)-((m+1+l|0)>>>1)|0;k=d[g+4>>0]|0;l=(d[b+3>>0]|0)-((l+1+k|0)>>>1)|0;j=d[g+5>>0]|0;k=(d[b+4>>0]|0)-((k+1+j|0)>>>1)|0;i=d[g+6>>0]|0;j=(d[b+5>>0]|0)-((j+1+i|0)>>>1)|0;h=d[g+7>>0]|0;i=(d[b+6>>0]|0)-((i+1+h|0)>>>1)|0;h=(d[b+7>>0]|0)-((h+1+(d[g+8>>0]|0)|0)>>>1)|0;c=((o|0)>-1?o:0-o|0)+c+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;a=a+1|0;if((a|0)==(f|0))break;else{g=g+e|0;b=b+e|0}}return c|0}function TA(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;if((g|0)<=0){f=0;return f|0}b=c;h=a[e+2>>0]|0;i=a[e+3>>0]|0;j=a[e+4>>0]|0;k=a[e+5>>0]|0;l=0;c=0;while(1){m=e;e=e+f|0;t=(d[b>>0]|0)-(((d[m>>0]|0)+1+(d[e>>0]|0)|0)>>>1)|0;s=(d[b+1>>0]|0)-(((d[m+1>>0]|0)+1+(d[e+1>>0]|0)|0)>>>1)|0;r=h;h=a[e+2>>0]|0;r=(d[b+2>>0]|0)-(((r&255)+1+(h&255)|0)>>>1)|0;q=i;i=a[e+3>>0]|0;q=(d[b+3>>0]|0)-(((q&255)+1+(i&255)|0)>>>1)|0;p=j;j=a[e+4>>0]|0;p=(d[b+4>>0]|0)-(((p&255)+1+(j&255)|0)>>>1)|0;o=k;k=a[e+5>>0]|0;o=(d[b+5>>0]|0)-(((o&255)+1+(k&255)|0)>>>1)|0;n=(d[b+6>>0]|0)-(((d[m+6>>0]|0)+1+(d[e+6>>0]|0)|0)>>>1)|0;m=(d[b+7>>0]|0)-(((d[m+7>>0]|0)+1+(d[e+7>>0]|0)|0)>>>1)|0;c=((t|0)>-1?t:0-t|0)+c+((s|0)>-1?s:0-s|0)+((r|0)>-1?r:0-r|0)+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)+((o|0)>-1?o:0-o|0)+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)|0;l=l+1|0;if((l|0)==(g|0))break;else b=b+f|0}return c|0}function UA(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;if((g|0)<=0){f=0;return f|0}b=c;h=a[e+4>>0]|0;i=a[e+5>>0]|0;j=0;c=0;while(1){k=e;e=e+f|0;t=d[k+1>>0]|0;q=d[e+1>>0]|0;r=(d[b>>0]|0)-(((d[k>>0]|0)+2+t+(d[e>>0]|0)+q|0)>>>2)|0;u=d[k+2>>0]|0;p=d[e+2>>0]|0;q=(d[b+1>>0]|0)-((t+2+q+u+p|0)>>>2)|0;t=d[k+3>>0]|0;o=d[e+3>>0]|0;p=(d[b+2>>0]|0)-((u+2+p+t+o|0)>>>2)|0;u=h&255;h=a[e+4>>0]|0;n=h&255;o=(d[b+3>>0]|0)-((t+2+o+u+n|0)>>>2)|0;t=i&255;i=a[e+5>>0]|0;m=i&255;n=(d[b+4>>0]|0)-((u+2+n+t+m|0)>>>2)|0;u=d[k+6>>0]|0;l=d[e+6>>0]|0;m=(d[b+5>>0]|0)-((t+2+m+u+l|0)>>>2)|0;t=d[k+7>>0]|0;s=d[e+7>>0]|0;l=(d[b+6>>0]|0)-((u+2+l+t+s|0)>>>2)|0;k=(d[b+7>>0]|0)-((t+2+s+(d[k+8>>0]|0)+(d[e+8>>0]|0)|0)>>>2)|0;c=((r|0)>-1?r:0-r|0)+c+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)+((o|0)>-1?o:0-o|0)+((n|0)>-1?n:0-n|0)+((m|0)>-1?m:0-m|0)+((l|0)>-1?l:0-l|0)+((k|0)>-1?k:0-k|0)|0;j=j+1|0;if((j|0)==(g|0))break;else b=b+f|0}return c|0}function VA(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;f=WA(a,b,c,d,8)|0;f=(WA(a,b+8|0,c+8|0,d,8)|0)+f|0;if((e|0)!=16){d=f;return d|0}g=d<<3;e=b+g|0;c=c+g|0;b=(WA(a,e,c,d,8)|0)+f|0;d=b+(WA(a,e+8|0,c+8|0,d,8)|0)|0;return d|0}function WA(a,b,e,f,g){a=a|0;b=b|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;j=i;i=i+256|0;h=j;g=0;do{k=_(g,f)|0;q=k+1|0;s=d[e+q>>0]|0;q=d[b+q>>0]|0;l=(d[e+k>>0]|0)-(d[b+k>>0]|0)|0;p=l+s-q|0;a=g<<3;q=l-s+q|0;s=k+2|0;l=k+3|0;r=d[e+l>>0]|0;l=d[b+l>>0]|0;s=(d[e+s>>0]|0)-(d[b+s>>0]|0)|0;n=s+r-l|0;l=s-r+l|0;r=k+4|0;s=k+5|0;t=d[e+s>>0]|0;s=d[b+s>>0]|0;r=(d[e+r>>0]|0)-(d[b+r>>0]|0)|0;o=r+t-s|0;s=r-t+s|0;t=k+6|0;k=k+7|0;r=d[e+k>>0]|0;k=d[b+k>>0]|0;t=(d[e+t>>0]|0)-(d[b+t>>0]|0)|0;m=t+r-k|0;k=t-r+k|0;r=n+p|0;n=p-n|0;p=l+q|0;l=q-l|0;q=m+o|0;m=o-m|0;o=k+s|0;k=s-k|0;c[h+(a<<2)>>2]=q+r;c[h+((a|4)<<2)>>2]=r-q;c[h+((a|1)<<2)>>2]=o+p;c[h+((a|5)<<2)>>2]=p-o;c[h+((a|2)<<2)>>2]=m+n;c[h+((a|6)<<2)>>2]=n-m;c[h+((a|3)<<2)>>2]=k+l;c[h+((a|7)<<2)>>2]=l-k;g=g+1|0}while((g|0)!=8);a=0;g=0;do{x=h+(a<<2)|0;r=c[x>>2]|0;v=h+(a+8<<2)|0;u=c[v>>2]|0;w=u+r|0;u=r-u|0;r=h+(a+16<<2)|0;b=c[r>>2]|0;m=h+(a+24<<2)|0;l=c[m>>2]|0;t=l+b|0;l=b-l|0;b=h+(a+32<<2)|0;o=c[b>>2]|0;f=h+(a+40<<2)|0;k=c[f>>2]|0;e=k+o|0;k=o-k|0;o=h+(a+48<<2)|0;p=c[o>>2]|0;n=h+(a+56<<2)|0;s=c[n>>2]|0;q=s+p|0;s=p-s|0;p=t+w|0;c[x>>2]=p;t=w-t|0;c[r>>2]=t;r=l+u|0;c[v>>2]=r;l=u-l|0;c[m>>2]=l;m=q+e|0;c[b>>2]=m;q=e-q|0;c[o>>2]=q;o=s+k|0;c[f>>2]=o;s=k-s|0;c[n>>2]=s;n=m+p|0;m=p-m|0;p=o+r|0;o=r-o|0;r=q+t|0;q=t-q|0;t=l+s|0;s=l-s|0;g=((m|0)>-1?m:0-m|0)+g+((n|0)>-1?n:0-n|0)+((o|0)>-1?o:0-o|0)+((p|0)>-1?p:0-p|0)+((q|0)>-1?q:0-q|0)+((r|0)>-1?r:0-r|0)+((s|0)>-1?s:0-s|0)+((t|0)>-1?t:0-t|0)|0;a=a+1|0}while((a|0)!=8);i=j;return g|0}function XA(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;f=YA(a,b,c,d,8)|0;f=(YA(a,b+8|0,c+8|0,d,8)|0)+f|0;if((e|0)!=16){d=f;return d|0}g=d<<3;e=b+g|0;c=c+g|0;b=(YA(a,e,c,d,8)|0)+f|0;d=b+(YA(a,e+8|0,c+8|0,d,8)|0)|0;return d|0}function YA(a,b,e,f,g){a=a|0;b=b|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;h=i;i=i+256|0;a=h;e=0;do{j=_(e,f)|0;q=d[b+j>>0]|0;p=d[b+(j+1)>>0]|0;o=p+q|0;g=e<<3;p=q-p|0;q=d[b+(j+2)>>0]|0;k=d[b+(j+3)>>0]|0;m=k+q|0;k=q-k|0;q=d[b+(j+4)>>0]|0;r=d[b+(j+5)>>0]|0;n=r+q|0;r=q-r|0;q=d[b+(j+6)>>0]|0;j=d[b+(j+7)>>0]|0;l=j+q|0;j=q-j|0;q=m+o|0;m=o-m|0;o=k+p|0;k=p-k|0;p=l+n|0;l=n-l|0;n=j+r|0;j=r-j|0;c[a+(g<<2)>>2]=p+q;c[a+((g|4)<<2)>>2]=q-p;c[a+((g|1)<<2)>>2]=n+o;c[a+((g|5)<<2)>>2]=o-n;c[a+((g|2)<<2)>>2]=l+m;c[a+((g|6)<<2)>>2]=m-l;c[a+((g|3)<<2)>>2]=j+k;c[a+((g|7)<<2)>>2]=k-j;e=e+1|0}while((e|0)!=8);g=0;e=0;do{x=a+(g<<2)|0;p=c[x>>2]|0;v=a+(g+8<<2)|0;u=c[v>>2]|0;w=u+p|0;u=p-u|0;p=a+(g+16<<2)|0;t=c[p>>2]|0;k=a+(g+24<<2)|0;j=c[k>>2]|0;r=j+t|0;j=t-j|0;t=a+(g+32<<2)|0;m=c[t>>2]|0;b=a+(g+40<<2)|0;f=c[b>>2]|0;s=f+m|0;f=m-f|0;m=a+(g+48<<2)|0;n=c[m>>2]|0;l=a+(g+56<<2)|0;q=c[l>>2]|0;o=q+n|0;q=n-q|0;n=r+w|0;c[x>>2]=n;r=w-r|0;c[p>>2]=r;p=j+u|0;c[v>>2]=p;j=u-j|0;c[k>>2]=j;k=o+s|0;c[t>>2]=k;o=s-o|0;c[m>>2]=o;m=q+f|0;c[b>>2]=m;q=f-q|0;c[l>>2]=q;l=k+n|0;k=n-k|0;n=m+p|0;m=p-m|0;p=o+r|0;o=r-o|0;r=j+q|0;q=j-q|0;e=((k|0)>-1?k:0-k|0)+e+((l|0)>-1?l:0-l|0)+((m|0)>-1?m:0-m|0)+((n|0)>-1?n:0-n|0)+((o|0)>-1?o:0-o|0)+((p|0)>-1?p:0-p|0)+((q|0)>-1?q:0-q|0)+((r|0)>-1?r:0-r|0)|0;g=g+1|0}while((g|0)!=8);x=(c[a+128>>2]|0)+(c[a>>2]|0)|0;i=h;return e-((x|0)>-1?x:0-x|0)|0}function ZA(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0;l=i;i=i+128|0;k=l;h=a+2936|0;ub[c[h>>2]&255](k,b,d,e);j=a+2032|0;hb[c[j>>2]&3](k);g=a+2372|0;a=nb[c[g>>2]&15](k)|0;ub[c[h>>2]&255](k,b+8|0,d+8|0,e);hb[c[j>>2]&3](k);a=(nb[c[g>>2]&15](k)|0)+a|0;if((f|0)!=16){e=a;i=l;return e|0}f=e<<3;b=b+f|0;f=d+f|0;ub[c[h>>2]&255](k,b,f,e);hb[c[j>>2]&3](k);d=(nb[c[g>>2]&15](k)|0)+a|0;ub[c[h>>2]&255](k,b+8|0,f+8|0,e);hb[c[j>>2]&3](k);e=d+(nb[c[g>>2]&15](k)|0)|0;i=l;return e|0}function _A(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0;f=i;i=i+128|0;g=f;ub[c[a+2936>>2]&255](g,b,d,e);hb[c[a+2032>>2]&3](g);e=nb[c[a+2372>>2]&15](g)|0;i=f;return e|0}function $A(a,d,e,f,g){a=a|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;o=i;i=i+128|0;n=o;m=a+2936|0;ub[c[m>>2]&255](n,d,e,f);l=a+2032|0;hb[c[l>>2]&3](n);h=0;a=0;do{j=b[n+(h<<1)>>1]|0;k=j<<16>>16;k=j<<16>>16>-1?k:0-k|0;a=(a|0)>(k|0)?a:k;h=h+1|0}while((h|0)!=64);ub[c[m>>2]&255](n,d+8|0,e+8|0,f);hb[c[l>>2]&3](n);j=0;h=0;do{p=b[n+(j<<1)>>1]|0;k=p<<16>>16;k=p<<16>>16>-1?k:0-k|0;h=(h|0)>(k|0)?h:k;j=j+1|0}while((j|0)!=64);k=h+a|0;if((g|0)!=16){p=k;i=o;return p|0}j=f<<3;d=d+j|0;j=e+j|0;ub[c[m>>2]&255](n,d,j,f);hb[c[l>>2]&3](n);h=0;a=0;do{e=b[n+(h<<1)>>1]|0;p=e<<16>>16;p=e<<16>>16>-1?p:0-p|0;a=(a|0)>(p|0)?a:p;h=h+1|0}while((h|0)!=64);g=a;ub[c[m>>2]&255](n,d+8|0,j+8|0,f);hb[c[l>>2]&3](n);h=0;a=0;do{f=b[n+(h<<1)>>1]|0;p=f<<16>>16;p=f<<16>>16>-1?p:0-p|0;a=(a|0)>(p|0)?a:p;h=h+1|0}while((h|0)!=64);p=g+k+a|0;i=o;return p|0}function aB(a,d,e,f,g){a=a|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0;j=i;i=i+128|0;h=j;ub[c[a+2936>>2]&255](h,d,e,f);hb[c[a+2032>>2]&3](h);a=0;g=0;do{e=b[h+(a<<1)>>1]|0;f=e<<16>>16;f=e<<16>>16>-1?f:0-f|0;g=(g|0)>(f|0)?g:f;a=a+1|0}while((a|0)!=64);i=j;return g|0}function bB(a,b,e,f,g){a=a|0;b=b|0;e=e|0;f=f|0;g=g|0;var h=0;if((g|0)>0){h=e;a=0;e=0}else{f=0;return f|0}while(1){e=(c[109188+((d[b>>0]|0)-(d[h>>0]|0)<<2)>>2]|0)+e+(c[109188+((d[b+1>>0]|0)-(d[h+1>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+2>>0]|0)-(d[h+2>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+3>>0]|0)-(d[h+3>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+4>>0]|0)-(d[h+4>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+5>>0]|0)-(d[h+5>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+6>>0]|0)-(d[h+6>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+7>>0]|0)-(d[h+7>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+8>>0]|0)-(d[h+8>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+9>>0]|0)-(d[h+9>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+10>>0]|0)-(d[h+10>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+11>>0]|0)-(d[h+11>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+12>>0]|0)-(d[h+12>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+13>>0]|0)-(d[h+13>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+14>>0]|0)-(d[h+14>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+15>>0]|0)-(d[h+15>>0]|0)<<2)>>2]|0)|0;a=a+1|0;if((a|0)==(g|0))break;else{h=h+f|0;b=b+f|0}}return e|0}function cB(a,b,e,f,g){a=a|0;b=b|0;e=e|0;f=f|0;g=g|0;var h=0;if((g|0)>0){h=e;a=0;e=0}else{f=0;return f|0}while(1){e=(c[109188+((d[b>>0]|0)-(d[h>>0]|0)<<2)>>2]|0)+e+(c[109188+((d[b+1>>0]|0)-(d[h+1>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+2>>0]|0)-(d[h+2>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+3>>0]|0)-(d[h+3>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+4>>0]|0)-(d[h+4>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+5>>0]|0)-(d[h+5>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+6>>0]|0)-(d[h+6>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+7>>0]|0)-(d[h+7>>0]|0)<<2)>>2]|0)|0;a=a+1|0;if((a|0)==(g|0))break;else{h=h+f|0;b=b+f|0}}return e|0}function dB(a,b,e,f,g){a=a|0;b=b|0;e=e|0;f=f|0;g=g|0;var h=0;if((g|0)>0){h=e;a=0;e=0}else{f=0;return f|0}while(1){e=(c[109188+((d[b>>0]|0)-(d[h>>0]|0)<<2)>>2]|0)+e+(c[109188+((d[b+1>>0]|0)-(d[h+1>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+2>>0]|0)-(d[h+2>>0]|0)<<2)>>2]|0)+(c[109188+((d[b+3>>0]|0)-(d[h+3>>0]|0)<<2)>>2]|0)|0;a=a+1|0;if((a|0)==(g|0))break;else{h=h+f|0;b=b+f|0}}return e|0}function eB(a,d,e,f,g){a=a|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;w=i;i=i+272|0;t=w+256|0;v=w;u=v+128|0;n=a+4272|0;c[n>>2]=0;p=a+2936|0;ub[c[p>>2]&255](v,d,e,f);h=u;j=v;l=h+128|0;do{c[h>>2]=c[j>>2];h=h+4|0;j=j+4|0}while((h|0)<(l|0));q=a+6172|0;r=a+1920|0;s=a+16|0;c[s>>2]=tb[c[q>>2]&63](a,v,0,c[r>>2]|0,t)|0;o=a+6164|0;ub[c[o>>2]&255](a,v,0,c[r>>2]|0);vB(v);c[t>>2]=0;j=0;h=0;do{m=(b[v+(j<<1)>>1]|0)-(b[u+(j<<1)>>1]|0)|0;h=(_(m,m)|0)+h|0;j=j+1|0}while((j|0)!=64);k=h;c[n>>2]=0;ub[c[p>>2]&255](v,d+8|0,e+8|0,f);h=u;j=v;l=h+128|0;do{c[h>>2]=c[j>>2];h=h+4|0;j=j+4|0}while((h|0)<(l|0));c[s>>2]=tb[c[q>>2]&63](a,v,0,c[r>>2]|0,t)|0;ub[c[o>>2]&255](a,v,0,c[r>>2]|0);vB(v);c[t>>2]=0;j=0;h=0;do{m=(b[v+(j<<1)>>1]|0)-(b[u+(j<<1)>>1]|0)|0;h=(_(m,m)|0)+h|0;j=j+1|0}while((j|0)!=64);m=h+k|0;if((g|0)!=16){v=m;i=w;return v|0}k=f<<3;g=d+k|0;k=e+k|0;c[n>>2]=0;ub[c[p>>2]&255](v,g,k,f);h=u;j=v;l=h+128|0;do{c[h>>2]=c[j>>2];h=h+4|0;j=j+4|0}while((h|0)<(l|0));c[s>>2]=tb[c[q>>2]&63](a,v,0,c[r>>2]|0,t)|0;ub[c[o>>2]&255](a,v,0,c[r>>2]|0);vB(v);c[t>>2]=0;j=0;h=0;do{e=(b[v+(j<<1)>>1]|0)-(b[u+(j<<1)>>1]|0)|0;h=(_(e,e)|0)+h|0;j=j+1|0}while((j|0)!=64);d=h;c[n>>2]=0;ub[c[p>>2]&255](v,g+8|0,k+8|0,f);h=u;j=v;l=h+128|0;do{c[h>>2]=c[j>>2];h=h+4|0;j=j+4|0}while((h|0)<(l|0));c[s>>2]=tb[c[q>>2]&63](a,v,0,c[r>>2]|0,t)|0;ub[c[o>>2]&255](a,v,0,c[r>>2]|0);vB(v);c[t>>2]=0;j=0;h=0;do{f=(b[v+(j<<1)>>1]|0)-(b[u+(j<<1)>>1]|0)|0;h=(_(f,f)|0)+h|0;j=j+1|0}while((j|0)!=64);v=d+m+h|0;i=w;return v|0}function fB(a,d,e,f,g){a=a|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0;l=i;i=i+272|0;k=l;h=l+256|0;j=k+128|0;c[a+4272>>2]=0;ub[c[a+2936>>2]&255](k,d,e,f);g=j;d=k;e=g+128|0;do{c[g>>2]=c[d>>2];g=g+4|0;d=d+4|0}while((g|0)<(e|0));d=a+1920|0;c[a+16>>2]=tb[c[a+6172>>2]&63](a,k,0,c[d>>2]|0,h)|0;ub[c[a+6164>>2]&255](a,k,0,c[d>>2]|0);vB(k);c[h>>2]=0;d=0;g=0;do{a=(b[k+(d<<1)>>1]|0)-(b[j+(d<<1)>>1]|0)|0;g=(_(a,a)|0)+g|0;d=d+1|0}while((d|0)!=64);i=l;return g|0}function gB(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;f=hB(a,b,c,d,8)|0;f=(hB(a,b+8|0,c+8|0,d,8)|0)+f|0;if((e|0)!=16){d=f;return d|0}g=d<<3;e=b+g|0;c=c+g|0;b=(hB(a,e,c,d,8)|0)+f|0;d=b+(hB(a,e+8|0,c+8|0,d,8)|0)|0;return d|0}function hB(a,e,f,g,h){a=a|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i;i=i+272|0;s=t+128|0;q=t+64|0;r=t;o=t+256|0;p=c[a+4872>>2]|0;m=e;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=q;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;l=e+g|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=q+8|0;c[k>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[k+4>>2]=m;l=l+g|0;k=l;m=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;n=q+16|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=k;l=l+g|0;n=l;k=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=q+24|0;c[m>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[m+4>>2]=n;l=l+g|0;m=l;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;k=q+32|0;c[k>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[k+4>>2]=m;l=l+g|0;k=l;m=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;n=q+40|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=k;l=l+g|0;n=l;k=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=q+48|0;c[m>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[m+4>>2]=n;l=l+g|0;m=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=q+56|0;c[n>>2]=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;c[n+4>>2]=l;n=f;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;m=r;c[m>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[m+4>>2]=n;m=f+g|0;n=m;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;k=r+8|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+4>>2]=n;m=m+g|0;k=m;n=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=r+16|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=k;m=m+g|0;l=m;k=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=r+24|0;c[n>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[n+4>>2]=l;m=m+g|0;n=m;l=n;n=n+4|0;n=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;k=r+32|0;c[k>>2]=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;c[k+4>>2]=n;m=m+g|0;k=m;n=k;k=k+4|0;k=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;l=r+40|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=k;m=m+g|0;l=m;k=l;l=l+4|0;l=d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24;n=r+48|0;c[n>>2]=d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24;c[n+4>>2]=l;m=m+g|0;n=m;m=m+4|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=r+56|0;c[l>>2]=d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24;c[l+4>>2]=m;ub[c[a+2936>>2]&255](s,q,r,8);l=a+1920|0;m=tb[c[a+6172>>2]&63](a,s,0,c[l>>2]|0,o)|0;c[a+16>>2]=m;n=(c[a+4272>>2]|0)==0;if(n){h=0;e=a+4896|0;f=a+4892|0;g=0}else{h=d[(c[a+4900>>2]|0)+((b[s>>1]|0)+256)>>0]|0;e=a+4880|0;f=a+4876|0;g=1}k=c[e>>2]|0;j=c[f>>2]|0;do if((m|0)>=(g|0)){c[o>>2]=g;if((g|0)<(m|0)){f=0;do{e=b[s+(d[a+204+g>>0]<<1)>>1]|0;if(!(e<<16>>16))f=f+1|0;else{e=(e<<16>>16)+64|0;if(e>>>0<128)e=d[j+(e+(f<<7))>>0]|0;else e=p;h=e+h|0;f=0}g=g+1|0;c[o>>2]=g}while((g|0)<(m|0))}else f=0;e=d[a+204+m>>0]|0;c[o>>2]=e;e=(b[s+(e<<1)>>1]|0)+64|0;if(e>>>0<128){h=(d[k+(e+(f<<7))>>0]|0)+h|0;break}else{h=h+p|0;break}}while(0);if((m|0)<=-1){p=a+2300|0;p=c[p>>2]|0;ob[p&1023](r,8,s);a=a+2404|0;a=c[a>>2]|0;a=tb[a&63](0,r,q,8,8)|0;s=c[l>>2]|0;r=h*109|0;r=_(r,s)|0;s=_(r,s)|0;s=s+64|0;s=s>>7;a=s+a|0;i=t;return a|0}if(n){ub[c[a+6164>>2]&255](a,s,0,c[l>>2]|0);p=a+2300|0;p=c[p>>2]|0;ob[p&1023](r,8,s);a=a+2404|0;a=c[a>>2]|0;a=tb[a&63](0,r,q,8,8)|0;s=c[l>>2]|0;r=h*109|0;r=_(r,s)|0;s=_(r,s)|0;s=s+64|0;s=s>>7;a=s+a|0;i=t;return a|0}else{ub[c[a+6160>>2]&255](a,s,0,c[l>>2]|0);p=a+2300|0;p=c[p>>2]|0;ob[p&1023](r,8,s);a=a+2404|0;a=c[a>>2]|0;a=tb[a&63](0,r,q,8,8)|0;s=c[l>>2]|0;r=h*109|0;r=_(r,s)|0;s=_(r,s)|0;s=s+64|0;s=s>>7;a=s+a|0;i=t;return a|0}return 0}function iB(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;f=jB(a,b,c,d,8)|0;f=(jB(a,b+8|0,c+8|0,d,8)|0)+f|0;if((e|0)!=16){d=f;return d|0}g=d<<3;e=b+g|0;c=c+g|0;b=(jB(a,e,c,d,8)|0)+f|0;d=b+(jB(a,e+8|0,c+8|0,d,8)|0)|0;return d|0}function jB(a,e,f,g,h){a=a|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=i;i=i+144|0;n=p;m=p+128|0;o=c[a+4872>>2]|0;ub[c[a+2936>>2]&255](n,e,f,g);k=tb[c[a+6172>>2]&63](a,n,0,c[a+1920>>2]|0,m)|0;c[a+16>>2]=k;if(!(c[a+4272>>2]|0)){f=0;h=a+4896|0;e=a+4892|0;g=0}else{f=d[(c[a+4900>>2]|0)+((b[n>>1]|0)+256)>>0]|0;h=a+4880|0;e=a+4876|0;g=1}l=c[h>>2]|0;j=c[e>>2]|0;if((k|0)<(g|0)){a=f;i=p;return a|0}c[m>>2]=g;if((g|0)<(k|0)){e=0;do{h=b[n+(d[a+204+g>>0]<<1)>>1]|0;if(!(h<<16>>16))e=e+1|0;else{h=(h<<16>>16)+64|0;if(h>>>0<128)h=d[j+(h+(e<<7))>>0]|0;else h=o;f=h+f|0;e=0}g=g+1|0;c[m>>2]=g}while((g|0)<(k|0))}else e=0;h=d[a+204+k>>0]|0;c[m>>2]=h;h=(b[n+(h<<1)>>1]|0)+64|0;if(h>>>0<128){a=(d[l+(h+(e<<7))>>0]|0)+f|0;i=p;return a|0}else{a=f+o|0;i=p;return a|0}return 0}function kB(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0;if((f|0)>1){a=0;h=1}else{e=0;return e|0}while(1){g=0;do{i=g+e|0;i=(d[b+g>>0]|0)-(d[c+g>>0]|0)-(d[b+i>>0]|0)+(d[c+i>>0]|0)|0;a=((i|0)>-1?i:0-i|0)+a|0;g=g+1|0}while((g|0)!=16);h=h+1|0;if((h|0)==(f|0))break;else{c=c+e|0;b=b+e|0}}return a|0}function lB(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;if((f|0)<=1){e=0;return e|0}h=e+1|0;i=e+2|0;j=e+3|0;k=e+4|0;l=e+5|0;m=e+6|0;n=e+7|0;a=0;g=1;do{w=b;b=b+e|0;v=c;c=c+e|0;o=(d[w>>0]|0)-(d[v>>0]|0)-(d[b>>0]|0)+(d[c>>0]|0)|0;p=(d[w+1>>0]|0)-(d[v+1>>0]|0)-(d[w+h>>0]|0)+(d[v+h>>0]|0)|0;q=(d[w+2>>0]|0)-(d[v+2>>0]|0)-(d[w+i>>0]|0)+(d[v+i>>0]|0)|0;r=(d[w+3>>0]|0)-(d[v+3>>0]|0)-(d[w+j>>0]|0)+(d[v+j>>0]|0)|0;s=(d[w+4>>0]|0)-(d[v+4>>0]|0)-(d[w+k>>0]|0)+(d[v+k>>0]|0)|0;t=(d[w+5>>0]|0)-(d[v+5>>0]|0)-(d[w+l>>0]|0)+(d[v+l>>0]|0)|0;u=(d[w+6>>0]|0)-(d[v+6>>0]|0)-(d[w+m>>0]|0)+(d[v+m>>0]|0)|0;v=(d[w+7>>0]|0)-(d[v+7>>0]|0)-(d[w+n>>0]|0)+(d[v+n>>0]|0)|0;a=((v|0)>-1?v:0-v|0)+(((u|0)>-1?u:0-u|0)+(((t|0)>-1?t:0-t|0)+(((s|0)>-1?s:0-s|0)+(((r|0)>-1?r:0-r|0)+(((q|0)>-1?q:0-q|0)+(((p|0)>-1?p:0-p|0)+(((o|0)>-1?o:0-o|0)+a)))))))|0;g=g+1|0}while((g|0)!=(f|0));return a|0}function mB(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;if((f|0)>1){a=0;g=1}else{e=0;return e|0}while(1){c=0;do{j=c+e|0;k=(d[b+c>>0]|0)-(d[b+j>>0]|0)|0;j=(d[b+(c|1)>>0]|0)-(d[b+(j+1)>>0]|0)|0;i=c|2;i=(d[b+i>>0]|0)-(d[b+(i+e)>>0]|0)|0;h=c|3;h=(d[b+h>>0]|0)-(d[b+(h+e)>>0]|0)|0;a=((k|0)>-1?k:0-k|0)+a+((j|0)>-1?j:0-j|0)+((i|0)>-1?i:0-i|0)+((h|0)>-1?h:0-h|0)|0;c=c+4|0}while((c|0)<16);g=g+1|0;if((g|0)==(f|0))break;else b=b+e|0}return a|0}function nB(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;if((g|0)<=1){f=0;return f|0}i=f+1|0;j=f+2|0;k=f+3|0;l=f+4|0;m=f+5|0;n=f+6|0;o=f+7|0;h=c;e=a[c>>0]|0;b=0;c=1;do{p=h;h=h+f|0;v=e;e=a[h>>0]|0;v=(v&255)-(e&255)|0;u=(d[p+1>>0]|0)-(d[p+i>>0]|0)|0;t=(d[p+2>>0]|0)-(d[p+j>>0]|0)|0;s=(d[p+3>>0]|0)-(d[p+k>>0]|0)|0;w=(d[p+4>>0]|0)-(d[p+l>>0]|0)|0;r=(d[p+5>>0]|0)-(d[p+m>>0]|0)|0;q=(d[p+6>>0]|0)-(d[p+n>>0]|0)|0;p=(d[p+7>>0]|0)-(d[p+o>>0]|0)|0;b=((w|0)>-1?w:0-w|0)+(((v|0)>-1?v:0-v|0)+b+((u|0)>-1?u:0-u|0)+((t|0)>-1?t:0-t|0)+((s|0)>-1?s:0-s|0))+((r|0)>-1?r:0-r|0)+((q|0)>-1?q:0-q|0)+((p|0)>-1?p:0-p|0)|0;c=c+1|0}while((c|0)!=(g|0));return b|0}function oB(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0;if((f|0)>1){a=0;h=1}else{e=0;return e|0}while(1){g=0;do{i=g+e|0;i=(d[b+g>>0]|0)-(d[c+g>>0]|0)-(d[b+i>>0]|0)+(d[c+i>>0]|0)|0;a=(_(i,i)|0)+a|0;g=g+1|0}while((g|0)!=16);h=h+1|0;if((h|0)==(f|0))break;else{c=c+e|0;b=b+e|0}}return a|0}function pB(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;if((f|0)<=1){e=0;return e|0}h=e+1|0;i=e+2|0;j=e+3|0;k=e+4|0;l=e+5|0;m=e+6|0;n=e+7|0;a=0;g=1;do{p=b;b=b+e|0;o=c;c=c+e|0;q=(d[p>>0]|0)-(d[o>>0]|0)-(d[b>>0]|0)+(d[c>>0]|0)|0;a=(_(q,q)|0)+a|0;q=(d[p+1>>0]|0)-(d[o+1>>0]|0)-(d[p+h>>0]|0)+(d[o+h>>0]|0)|0;a=(_(q,q)|0)+a|0;q=(d[p+2>>0]|0)-(d[o+2>>0]|0)-(d[p+i>>0]|0)+(d[o+i>>0]|0)|0;a=(_(q,q)|0)+a|0;q=(d[p+3>>0]|0)-(d[o+3>>0]|0)-(d[p+j>>0]|0)+(d[o+j>>0]|0)|0;a=(_(q,q)|0)+a|0;q=(d[p+4>>0]|0)-(d[o+4>>0]|0)-(d[p+k>>0]|0)+(d[o+k>>0]|0)|0;a=(_(q,q)|0)+a|0;q=(d[p+5>>0]|0)-(d[o+5>>0]|0)-(d[p+l>>0]|0)+(d[o+l>>0]|0)|0;a=(_(q,q)|0)+a|0;q=(d[p+6>>0]|0)-(d[o+6>>0]|0)-(d[p+m>>0]|0)+(d[o+m>>0]|0)|0;a=(_(q,q)|0)+a|0;o=(d[p+7>>0]|0)-(d[o+7>>0]|0)-(d[p+n>>0]|0)+(d[o+n>>0]|0)|0;a=(_(o,o)|0)+a|0;g=g+1|0}while((g|0)!=(f|0));return a|0}function qB(a,b,c,e,f){a=a|0;b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;if((f|0)<=1){e=0;return e|0}g=e+1|0;n=e+2|0;o=e+3|0;p=e+4|0;q=e+5|0;r=e+6|0;s=e+7|0;t=e+8|0;u=e+9|0;h=e+10|0;i=e+11|0;j=e+12|0;k=e+13|0;l=e+14|0;m=e+15|0;a=0;c=1;do{z=b;b=b+e|0;v=(d[z>>0]|0)-(d[b>>0]|0)|0;v=_(v,v)|0;w=(d[z+1>>0]|0)-(d[z+g>>0]|0)|0;w=_(w,w)|0;x=(d[z+2>>0]|0)-(d[z+n>>0]|0)|0;x=_(x,x)|0;y=(d[z+3>>0]|0)-(d[z+o>>0]|0)|0;y=v+a+w+x+(_(y,y)|0)|0;x=(d[z+4>>0]|0)-(d[z+p>>0]|0)|0;x=_(x,x)|0;w=(d[z+5>>0]|0)-(d[z+q>>0]|0)|0;w=_(w,w)|0;a=(d[z+6>>0]|0)-(d[z+r>>0]|0)|0;a=_(a,a)|0;v=(d[z+7>>0]|0)-(d[z+s>>0]|0)|0;v=x+y+w+a+(_(v,v)|0)|0;a=(d[z+8>>0]|0)-(d[z+t>>0]|0)|0;a=_(a,a)|0;w=(d[z+9>>0]|0)-(d[z+u>>0]|0)|0;w=_(w,w)|0;y=(d[z+10>>0]|0)-(d[z+h>>0]|0)|0;y=_(y,y)|0;x=(d[z+11>>0]|0)-(d[z+i>>0]|0)|0;x=a+v+w+y+(_(x,x)|0)|0;y=(d[z+12>>0]|0)-(d[z+j>>0]|0)|0;y=_(y,y)|0;w=(d[z+13>>0]|0)-(d[z+k>>0]|0)|0;w=_(w,w)|0;v=(d[z+14>>0]|0)-(d[z+l>>0]|0)|0;v=_(v,v)|0;a=(d[z+15>>0]|0)-(d[z+m>>0]|0)|0;a=y+x+w+v+(_(a,a)|0)|0;c=c+1|0}while((c|0)!=(f|0));return a|0}function rB(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;if((g|0)<=1){f=0;return f|0}i=f+1|0;j=f+2|0;k=f+3|0;l=f+4|0;m=f+5|0;n=f+6|0;o=f+7|0;h=c;e=a[c>>0]|0;b=0;c=1;do{t=h;h=h+f|0;p=e;e=a[h>>0]|0;p=(p&255)-(e&255)|0;p=_(p,p)|0;q=(d[t+1>>0]|0)-(d[t+i>>0]|0)|0;q=_(q,q)|0;s=(d[t+2>>0]|0)-(d[t+j>>0]|0)|0;s=_(s,s)|0;r=(d[t+3>>0]|0)-(d[t+k>>0]|0)|0;r=p+b+q+s+(_(r,r)|0)|0;s=(d[t+4>>0]|0)-(d[t+l>>0]|0)|0;s=_(s,s)|0;q=(d[t+5>>0]|0)-(d[t+m>>0]|0)|0;q=_(q,q)|0;p=(d[t+6>>0]|0)-(d[t+n>>0]|0)|0;p=_(p,p)|0;b=(d[t+7>>0]|0)-(d[t+o>>0]|0)|0;b=s+r+q+p+(_(b,b)|0)|0;c=c+1|0}while((c|0)!=(g|0));return b|0}function sB(b,e,f,g,h){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;if((h|0)>0){n=e;l=f;f=0;e=0;m=0;while(1){k=(d[n>>0]|0)-(d[l>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+1>>0]|0)-(d[l+1>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+2>>0]|0)-(d[l+2>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+3>>0]|0)-(d[l+3>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+4>>0]|0)-(d[l+4>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+5>>0]|0)-(d[l+5>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+6>>0]|0)-(d[l+6>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+7>>0]|0)-(d[l+7>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+8>>0]|0)-(d[l+8>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+9>>0]|0)-(d[l+9>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+10>>0]|0)-(d[l+10>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+11>>0]|0)-(d[l+11>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+12>>0]|0)-(d[l+12>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+13>>0]|0)-(d[l+13>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+14>>0]|0)-(d[l+14>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+15>>0]|0)-(d[l+15>>0]|0)|0;f=(_(k,k)|0)+f|0;m=m+1|0;if((m|0)<(h|0)){j=a[n>>0]|0;k=a[l>>0]|0;i=0;do{q=i+g|0;i=i+1|0;p=j;j=a[n+i>>0]|0;o=q+1|0;p=(p&255)-(d[n+q>>0]|0)-(j&255)+(d[n+o>>0]|0)|0;r=k;k=a[l+i>>0]|0;o=(r&255)-(d[l+q>>0]|0)-(k&255)+(d[l+o>>0]|0)|0;e=((p|0)>-1?p:0-p|0)+e-((o|0)>-1?o:0-o|0)|0}while((i|0)!=15)}if((m|0)==(h|0))break;else{n=n+g|0;l=l+g|0}}}else{f=0;e=0}e=(e|0)>-1?e:0-e|0;if(!b){r=e<<3;r=r+f|0;return r|0}else{r=_(c[(c[b+596>>2]|0)+836>>2]|0,e)|0;r=r+f|0;return r|0}return 0}function tB(b,e,f,g,h){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;if((h|0)>0){n=e;l=f;f=0;e=0;m=0;while(1){i=a[n>>0]|0;j=a[l>>0]|0;k=(i&255)-(j&255)|0;f=(_(k,k)|0)+f|0;k=(d[n+1>>0]|0)-(d[l+1>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+2>>0]|0)-(d[l+2>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+3>>0]|0)-(d[l+3>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+4>>0]|0)-(d[l+4>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+5>>0]|0)-(d[l+5>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+6>>0]|0)-(d[l+6>>0]|0)|0;f=(_(k,k)|0)+f|0;k=(d[n+7>>0]|0)-(d[l+7>>0]|0)|0;f=(_(k,k)|0)+f|0;m=m+1|0;if((m|0)<(h|0)){k=0;do{q=k+g|0;k=k+1|0;p=i;i=a[n+k>>0]|0;o=q+1|0;p=(p&255)-(d[n+q>>0]|0)-(i&255)+(d[n+o>>0]|0)|0;r=j;j=a[l+k>>0]|0;o=(r&255)-(d[l+q>>0]|0)-(j&255)+(d[l+o>>0]|0)|0;e=((p|0)>-1?p:0-p|0)+e-((o|0)>-1?o:0-o|0)|0}while((k|0)!=7)}if((m|0)==(h|0))break;else{n=n+g|0;l=l+g|0}}}else{f=0;e=0}e=(e|0)>-1?e:0-e|0;if(!b){r=e<<3;r=r+f|0;return r|0}else{r=_(c[(c[b+596>>2]|0)+836>>2]|0,e)|0;r=r+f|0;return r|0}return 0}function uB(a){a=a|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;r=a+4|0;d=c[r>>2]|0;s=a+8|0;t=a+12|0;h=c[t>>2]|0;u=a+2|0;f=b[u>>1]|0;e=b[a>>1]|0;if(!(c[s>>2]|d|h|f)){u=e<<3&65528|e<<19;c[t>>2]=u;c[s>>2]=u;c[r>>2]=u;c[a>>2]=u;return}q=(e*16383|0)+1024|0;k=d<<16>>16;j=k*21407|0;g=j+q|0;k=k*8867|0;i=k+q|0;k=q-k|0;j=q-j|0;q=a+6|0;p=b[q>>1]|0;l=(p*19266|0)+(f*22725|0)|0;o=(_(p,-4520)|0)+(f*19266|0)|0;e=(_(p,-22725)|0)+(f*12873|0)|0;p=(_(p,-12873)|0)+(f*4520|0)|0;n=s;d=c[n>>2]|0;if((d|0)==0&(c[n+4>>2]|0)==0){m=a+10|0;n=a+14|0;h=l;f=o;d=p}else{x=d<<16>>16;f=x*16383|0;w=h<<16>>16;h=w*8867|0;x=_(x,-16383)|0;w=w*21407|0;m=a+10|0;v=b[m>>1]|0;n=a+14|0;d=b[n>>1]|0;g=f+g+h|0;i=x+i-w|0;k=x+k+w|0;j=f+j-h|0;h=(v*12873|0)+l+(d*4520|0)|0;f=(_(v,-22725)|0)+o+(_(d,-12873)|0)|0;e=(v*4520|0)+e+(d*19266|0)|0;d=(v*19266|0)+p+(_(d,-22725)|0)|0}b[a>>1]=(h+g|0)>>>11;b[n>>1]=(g-h|0)>>>11;b[u>>1]=(f+i|0)>>>11;b[t>>1]=(i-f|0)>>>11;b[r>>1]=(e+k|0)>>>11;b[m>>1]=(k-e|0)>>>11;b[q>>1]=(d+j|0)>>>11;b[s>>1]=(j-d|0)>>>11;return}function vB(a){a=a|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;uB(a);uB(a+16|0);uB(a+32|0);uB(a+48|0);uB(a+64|0);uB(a+80|0);uB(a+96|0);uB(a+112|0);t=0;do{s=a+(t<<1)|0;p=((b[s>>1]|0)*16383|0)+524256|0;o=s+32|0;g=b[o>>1]|0;c=p+(g*21407|0)|0;e=p+(g*8867|0)|0;f=p+(_(g,-8867)|0)|0;g=p+(_(g,-21407)|0)|0;p=s+16|0;k=b[p>>1]|0;q=s+48|0;r=b[q>>1]|0;j=(r*19266|0)+(k*22725|0)|0;i=(_(r,-4520)|0)+(k*19266|0)|0;h=(_(r,-22725)|0)+(k*12873|0)|0;k=(_(r,-12873)|0)+(k*4520|0)|0;r=s+64|0;d=b[r>>1]|0;if(d<<16>>16){m=d<<16>>16;n=m*16383|0;m=_(m,-16383)|0;c=n+c|0;e=m+e|0;f=m+f|0;g=n+g|0}n=s+80|0;d=b[n>>1]|0;if(!(d<<16>>16))l=k;else{l=d<<16>>16;j=(l*12873|0)+j|0;i=(_(l,-22725)|0)+i|0;h=(l*4520|0)+h|0;l=(l*19266|0)+k|0}m=s+96|0;d=b[m>>1]|0;if(d<<16>>16){k=d<<16>>16;c=(k*8867|0)+c|0;e=(_(k,-21407)|0)+e|0;f=(k*21407|0)+f|0;g=(_(k,-8867)|0)+g|0}k=s+112|0;d=b[k>>1]|0;if(!(d<<16>>16))d=l;else{d=d<<16>>16;j=(d*4520|0)+j|0;i=(_(d,-12873)|0)+i|0;h=(d*19266|0)+h|0;d=(_(d,-22725)|0)+l|0}b[s>>1]=j+c>>20;b[p>>1]=i+e>>20;b[o>>1]=h+f>>20;b[q>>1]=d+g>>20;b[r>>1]=g-d>>20;b[n>>1]=f-h>>20;b[m>>1]=e-i>>20;b[k>>1]=c-j>>20;t=t+1|0}while((t|0)!=8);return}function wB(b,c,d){b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;a:do if(d>>>0>1){e=1;g=0;while(1){f=a[c>>0]|0;if(!(f<<24>>24)){f=e;e=g;break a}c=c+1|0;g=b+1|0;a[b>>0]=f;f=e+1|0;if(f>>>0<d>>>0){h=e;b=g;e=f;g=h}else{b=g;break}}}else{f=1;e=0}while(0);if(f>>>0<=d>>>0)a[b>>0]=0;return (aE(c)|0)+e|0}function xB(b,c,d){b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;h=aE(b)|0;if((h+1|0)>>>0>=d>>>0){g=aE(c)|0;h=g+h|0;return h|0}b=b+h|0;g=d-h|0;a:do if(g>>>0>1){d=1;f=0;while(1){e=a[c>>0]|0;if(!(e<<24>>24)){e=d;d=f;break a}c=c+1|0;f=b+1|0;a[b>>0]=e;e=d+1|0;if(e>>>0<g>>>0){i=d;b=f;d=e;f=i}else{b=f;break}}}else{e=1;d=0}while(0);if(e>>>0<=g>>>0)a[b>>0]=0;i=(aE(c)|0)+d|0;i=i+h|0;return i|0}function yB(b,c){b=b|0;c=c|0;var d=0,e=0,f=0;e=b;d=c;while(1){c=a[e>>0]|0;b=a[d>>0]|0;b=(b+-65|0)>>>0<26?b^32:b;c=((c+-65|0)>>>0<26?c^32:c)&255;if(!c)break;b=b&255;if((c|0)==(b|0)){e=e+1|0;d=d+1|0}else{f=5;break}}if((f|0)==5){f=c-b|0;return f|0}e=0;f=b&255;f=e-f|0;return f|0}function zB(a){a=a|0;return (a+-48|0)>>>0<10|0}function AB(a){a=a|0;return ((a|0)==11|((a|4|0)==13|((a|0)==10|((a|0)==32|(a|0)==12))))&1|0}function BB(b,c){b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;if(!((b|0)!=0&(c|0)!=0)){k=0;return k|0}j=aE(b)|0;d=cF(c,44)|0;a:do if(d){while(1){i=d-c|0;i=b+((i|0)>(j|0)?i:j)|0;h=b;while(1){e=a[h>>0]|0;h=h+1|0;e=(e+-65|0)>>>0<26?e^32:e;g=a[c>>0]|0;g=(g+-65|0)>>>0<26?g^32:g;if(h>>>0>=i>>>0){c=g;k=6;break}f=e&255;e=g&255;if(!((f|0)!=0&(f|0)==(e|0)))break;else c=c+1|0}if((k|0)==6){k=0;f=e&255;e=c&255}c=d+1|0;if((f|0)==(e|0)){d=1;break}d=cF(c,44)|0;if(!d)break a}return d|0}while(0);while(1){e=a[b>>0]|0;d=a[c>>0]|0;d=(d+-65|0)>>>0<26?d^32:d;e=((e+-65|0)>>>0<26?e^32:e)&255;if(!e){k=10;break}d=d&255;if((e|0)==(d|0)){b=b+1|0;c=c+1|0}else break}if((k|0)==10){e=0;d=d&255}k=(e|0)==(d|0)&1;return k|0}function CB(b,c,d){b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;if(!b){d=0;return d|0}j=d<<24>>24;if(!c){while(1){if(!(a[b>>0]|0)){b=0;e=14;break}i=cF(b,j)|0;b=i+((i|0)!=0&1)|0;if(!b){b=0;e=14;break}}if((e|0)==14)return b|0}else k=b;a:while(1){if(!(a[k>>0]|0)){b=0;e=14;break}else i=c;do{b=a[i>>0]|0;if(!(b<<24>>24))break;else h=0;while(1){e=a[k+h>>0]|0;f=e<<24>>24;g=b<<24>>24;if(e<<24>>24!=b<<24>>24)if(!((_(g,f)|0)==0&(g+f|0)==(j|0)))break;if(h|0)if(e<<24>>24==0?1:e<<24>>24==d<<24>>24){b=1;e=14;break a}g=h+1|0;b=a[i+g>>0]|0;h=g}h=cF(i,j)|0;i=h+((h|0)!=0&1)|0}while((i|0)!=0);i=cF(k,j)|0;k=i+((i|0)!=0&1)|0;if(!k){b=0;e=14;break}}if((e|0)==14)return b|0;return 0}function DB(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0;j=b+16|0;f=(e|0)==1?1008:e;c[b>>2]=j;h=b+4|0;c[h>>2]=0;g=f>>>0<1008?f:1008;i=b+8|0;c[i>>2]=g;c[b+12>>2]=f;a[j>>0]=0;if(g>>>0>=d>>>0)return;e=d+-1|0;if((g|0)==(f|0)|(g|0)==0)return;j=(e>>>0<4294967294?e:-2)+1|0;e=g>>>0>f>>>1>>>0?f:g<<1;f=e>>>0<j>>>0?(f>>>0>j>>>0?j:f):e;e=ZC(0,f)|0;if(!e)return;KF(e|0,c[b>>2]|0,(c[h>>2]|0)+1|0)|0;c[b>>2]=e;c[i>>2]=f;return}function EB(b,d,e){b=b|0;d=d|0;e=e|0;c[b>>2]=d;c[b+4>>2]=0;c[b+8>>2]=e;c[b+12>>2]=e;a[d>>0]=0;return}function FB(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;q=i;i=i+16|0;m=q;o=b+8|0;p=b+4|0;k=b+12|0;l=b+16|0;g=c[o>>2]|0;while(1){f=c[p>>2]|0;j=f>>>0>g>>>0?g:f;h=g-j|0;if((g|0)==(j|0))f=0;else f=(c[b>>2]|0)+f|0;c[m>>2]=e;f=xE(f,h,d,m)|0;if((f|0)<1){n=14;break}if(f>>>0<h>>>0)break;g=c[o>>2]|0;h=c[k>>2]|0;if((g|0)==(h|0))break;j=c[p>>2]|0;if(j>>>0>=g>>>0)break;r=-2-j|0;j=j+1+(r>>>0>f>>>0?f:r)|0;g=g>>>0>h>>>1>>>0?h:g<<1;g=g>>>0<j>>>0?(h>>>0>j>>>0?j:h):g;h=c[b>>2]|0;h=(h|0)==(l|0)?0:h;j=ZC(h,g)|0;if(!j)break;if(!h)KF(j|0,c[b>>2]|0,(c[p>>2]|0)+1|0)|0;c[b>>2]=j;c[o>>2]=g}if((n|0)==14){i=q;return}g=c[p>>2]|0;r=-6-g|0;g=(r>>>0<f>>>0?r:f)+g|0;c[p>>2]=g;f=c[o>>2]|0;if(!f){i=q;return}r=f+-1|0;a[(c[b>>2]|0)+(g>>>0>r>>>0?r:g)>>0]=0;i=q;return}function GB(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;q=i;i=i+16|0;m=q;o=b+8|0;p=b+4|0;k=b+12|0;l=b+16|0;g=c[o>>2]|0;while(1){f=c[p>>2]|0;j=f>>>0>g>>>0?g:f;h=g-j|0;if((g|0)==(j|0))f=0;else f=(c[b>>2]|0)+f|0;c[m>>2]=c[e>>2];f=xE(f,h,d,m)|0;if((f|0)<1){n=14;break}if(f>>>0<h>>>0)break;g=c[o>>2]|0;h=c[k>>2]|0;if((g|0)==(h|0))break;j=c[p>>2]|0;if(j>>>0>=g>>>0)break;r=-2-j|0;j=j+1+(r>>>0>f>>>0?f:r)|0;g=g>>>0>h>>>1>>>0?h:g<<1;g=g>>>0<j>>>0?(h>>>0>j>>>0?j:h):g;h=c[b>>2]|0;h=(h|0)==(l|0)?0:h;j=ZC(h,g)|0;if(!j)break;if(!h)KF(j|0,c[b>>2]|0,(c[p>>2]|0)+1|0)|0;c[b>>2]=j;c[o>>2]=g}if((n|0)==14){i=q;return}g=c[p>>2]|0;r=-6-g|0;g=(r>>>0<f>>>0?r:f)+g|0;c[p>>2]=g;f=c[o>>2]|0;if(!f){i=q;return}r=f+-1|0;a[(c[b>>2]|0)+(g>>>0>r>>>0?r:g)>>0]=0;i=q;return}function HB(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0;f=(c[a+4>>2]|0)+1|0;g=a+8|0;d=c[g>>2]|0;f=f>>>0>d>>>0?d:f;d=c[a>>2]|0;e=(d|0)!=(a+16|0);if(!b){if(!e){b=0;c[g>>2]=f;return b|0}bD(a);b=0;c[g>>2]=f;return b|0}if(e){d=ZC(d,f)|0;if(!d)d=c[a>>2]|0;c[a>>2]=0;e=0}else{d=YC(f)|0;if(!d){e=-12;d=0}else{KF(d|0,c[a>>2]|0,f|0)|0;e=0}}c[b>>2]=d;b=e;c[g>>2]=f;return b|0}function IB(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0;j=i;i=i+16|0;h=j;g=cD(24)|0;c[h>>2]=g;if(!g){b=0;i=j;return b|0}c[g>>2]=a;c[g+4>>2]=b;c[g+12>>2]=d|0?d:1;c[(c[h>>2]|0)+16>>2]=e;c[(c[h>>2]|0)+8>>2]=1;if(f&1|0){e=(c[h>>2]|0)+20|0;c[e>>2]=c[e>>2]|1}g=cD(12)|0;if(!g){bD(h);b=0;i=j;return b|0}else{c[g>>2]=c[h>>2];c[g+4>>2]=a;c[g+8>>2]=b;b=g;i=j;return b|0}return 0}function JB(a,b){a=a|0;b=b|0;$C(b);return}function KB(a){a=a|0;var b=0,d=0,e=0,f=0,g=0;g=i;i=i+16|0;e=g+4|0;f=g;d=YC(a)|0;c[f>>2]=d;if(!d){a=0;i=g;return a|0}b=cD(24)|0;c[e>>2]=b;do if(b|0){c[b>>2]=d;c[b+4>>2]=a;c[b+12>>2]=1;c[b+16>>2]=0;c[(c[e>>2]|0)+8>>2]=1;b=cD(12)|0;if(!b){bD(e);break}c[b>>2]=c[e>>2];c[b+4>>2]=d;c[b+8>>2]=a;a=b;i=g;return a|0}while(0);bD(f);a=0;i=g;return a|0}function LB(a){a=a|0;var b=0,d=0,e=0,f=0,g=0;g=i;i=i+16|0;e=g+4|0;f=g;d=YC(a)|0;c[f>>2]=d;if(d|0){b=cD(24)|0;c[e>>2]=b;do if(b|0){c[b>>2]=d;c[b+4>>2]=a;c[b+12>>2]=1;c[b+16>>2]=0;c[b+8>>2]=1;b=cD(12)|0;if(!b){bD(e);break}c[b>>2]=c[e>>2];c[b+4>>2]=d;c[b+8>>2]=a;FF(d|0,0,a|0)|0;a=b;i=g;return a|0}while(0);bD(f)}a=0;i=g;return a|0}function MB(a){a=a|0;var b=0;b=cD(12)|0;if(!b){a=0;return a|0};c[b>>2]=c[a>>2];c[b+4>>2]=c[a+4>>2];c[b+8>>2]=c[a+8>>2];a=(c[a>>2]|0)+8|0;c[a>>2]=(c[a>>2]|0)+1;a=b;return a|0}function NB(a){a=a|0;var b=0,d=0,e=0;e=i;i=i+16|0;d=e;if(!a){i=e;return}b=c[a>>2]|0;if(!b){i=e;return}b=c[b>>2]|0;c[d>>2]=b;bD(a);b=b+8|0;a=c[b>>2]|0;c[b>>2]=a+-1;if((a|0)==1){a=c[d>>2]|0;jb[c[a+12>>2]&255](c[a+16>>2]|0,c[a>>2]|0);bD(d)}i=e;return}function OB(a){a=a|0;return c[(c[a>>2]|0)+8>>2]|0}function PB(a,b){a=a|0;b=b|0;var d=0;d=cD(24)|0;if(!d){a=0;return a|0}c[d+16>>2]=a;c[d+20>>2]=b|0?b:10;c[d+8>>2]=1;a=d;return a|0}function QB(a){a=a|0;var b=0,d=0,e=0,f=0,g=0;f=i;i=i+16|0;d=f+4|0;e=f;if(!a){i=f;return}b=c[a>>2]|0;if(!b){i=f;return}c[a>>2]=0;g=b+8|0;a=c[g>>2]|0;c[g>>2]=a+-1;if((a|0)!=1){i=f;return}c[d>>2]=b;a=c[b+4>>2]|0;if(a|0)do{c[e>>2]=a;c[b+4>>2]=c[a+16>>2];g=c[e>>2]|0;jb[c[g+8>>2]&255](c[g+4>>2]|0,c[g>>2]|0);bD(e);b=c[d>>2]|0;a=c[b+4>>2]|0}while((a|0)!=0);bD(d);i=f;return}function RB(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;m=i;i=i+16|0;e=m;l=m+8|0;j=m+4|0;h=a+4|0;d=c[h>>2]|0;b=0;while(1){if((d|0)==(b|0)){k=4;break}b=c[h>>2]|0;if((b|0)==(d|0))c[h>>2]=0;if(!b){k=5;break}else{k=d;d=b;b=k}}if((k|0)==4)if(!b)k=5;else j=b;a:do if((k|0)==5){g=a+8|0;f=a+12|0;if((c[g>>2]|0)<=(c[f>>2]|0)){UC(0,48,98124,e);if((c[g>>2]|0)<=(c[f>>2]|0))do{d=c[h>>2]|0;b=0;while(1){if((d|0)==(b|0)){k=7;break}b=c[h>>2]|0;if((b|0)==(d|0))c[h>>2]=0;if(!b)break;else{e=d;d=b;b=e}}if((k|0)==7){k=0;if(b|0){j=b;break a}}}while((c[g>>2]|0)<=(c[f>>2]|0))}b=nb[c[a+20>>2]&15](c[a+16>>2]|0)|0;c[j>>2]=b;do if(!b)b=0;else{d=cD(20)|0;if(d|0){b=c[b>>2]|0;c[d>>2]=c[b>>2];c[d+4>>2]=c[b+16>>2];c[d+8>>2]=c[b+12>>2];c[d+12>>2]=a;c[b+16>>2]=d;c[(c[c[j>>2]>>2]|0)+12>>2]=186;c[g>>2]=(c[g>>2]|0)+1;c[f>>2]=(c[f>>2]|0)+1;b=c[j>>2]|0;break}k=c[b>>2]|0;c[l>>2]=k;bD(j);k=k+8|0;a=c[k>>2]|0;c[k>>2]=a+-1;if((a|0)==1){a=c[l>>2]|0;jb[c[a+12>>2]&255](c[a+16>>2]|0,c[a>>2]|0);bD(l)}b=0}while(0);a=b;i=m;return a|0}while(0);h=j+16|0;f=c[h>>2]|0;if(f|0){e=c[f+12>>2]|0;d=f;while(1){b=c[d+16>>2]|0;if(!b)break;else d=b}g=e+4|0;b=c[g>>2]|0;if(!b)c[g>>2]=f;if(b|0)do{e=c[g>>2]|0;b=0;while(1){if((e|0)==(b|0)){k=29;break}b=c[g>>2]|0;if((b|0)==(e|0))c[g>>2]=0;if(!b){k=28;break}else{k=e;e=b;b=k}}if((k|0)==28)c[d+16>>2]=0;else if((k|0)==29){c[d+16>>2]=b;if(b){d=b;while(1){b=c[d+16>>2]|0;if(!b)break;else d=b}}}b=c[g>>2]|0;if(!b)c[g>>2]=f}while((b|0)!=0)}c[h>>2]=0;d=c[j>>2]|0;e=c[a+16>>2]|0;b=cD(24)|0;c[l>>2]=b;do if(b|0){c[b>>2]=d;c[b+4>>2]=e;c[b+12>>2]=186;c[b+16>>2]=j;c[(c[l>>2]|0)+8>>2]=1;b=cD(12)|0;if(!b){bD(l);break}c[b>>2]=c[l>>2];c[b+4>>2]=d;c[b+8>>2]=e;a=a+8|0;c[a>>2]=(c[a>>2]|0)+1;a=b;i=m;return a|0}while(0);e=c[j+12>>2]|0;d=j;while(1){b=c[d+16>>2]|0;if(!b)break;else d=b}g=e+4|0;f=j;b=c[g>>2]|0;if(!b)c[g>>2]=f;if(!b){a=0;i=m;return a|0}do{e=c[g>>2]|0;b=0;while(1){if((e|0)==(b|0)){k=44;break}b=c[g>>2]|0;if((b|0)==(e|0))c[g>>2]=0;if(!b){k=43;break}else{a=e;e=b;b=a}}if((k|0)==43)c[d+16>>2]=0;else if((k|0)==44){c[d+16>>2]=b;if(b){d=b;while(1){b=c[d+16>>2]|0;if(!b)break;else d=b}}}b=c[g>>2]|0;if(!b)c[g>>2]=f}while((b|0)!=0);b=0;i=m;return b|0}function SB(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0;k=i;i=i+16|0;h=k+4|0;j=k;d=c[a+12>>2]|0;if(a|0){e=a;while(1){b=c[e+16>>2]|0;if(!b)break;else e=b}g=d+4|0;f=a;b=c[g>>2]|0;if(!b)c[g>>2]=f;if(b|0)do{a=c[g>>2]|0;b=0;while(1){if((a|0)==(b|0)){a=11;break}b=c[g>>2]|0;if((b|0)==(a|0))c[g>>2]=0;if(!b){a=10;break}else{l=a;a=b;b=l}}if((a|0)==10)c[e+16>>2]=0;else if((a|0)==11){c[e+16>>2]=b;if(b){e=b;while(1){b=c[e+16>>2]|0;if(!b)break;else e=b}}}b=c[g>>2]|0;if(!b)c[g>>2]=f}while((b|0)!=0)}g=d+8|0;l=c[g>>2]|0;c[g>>2]=l+-1;if((l|0)!=1){i=k;return}c[h>>2]=d;b=c[d+4>>2]|0;if(b|0)do{c[j>>2]=b;c[d+4>>2]=c[b+16>>2];l=c[j>>2]|0;jb[c[l+8>>2]&255](c[l+4>>2]|0,c[l>>2]|0);bD(j);d=c[h>>2]|0;b=c[d+4>>2]|0}while((b|0)!=0);bD(h);i=k;return}function TB(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;s=i;i=i+32|0;r=s;q=s+16|0;o=aE(b)|0;p=b+o|0;if((o|0)<=0){q=0;r=0;C=q;i=s;return r|0}o=(d|0)==0;d=0;e=0;m=b;a:while(1){b:do if(m>>>0<p>>>0){b=m;while(1){switch(a[b>>0]|0){case 124:case 43:break b;default:{}}b=b+1|0;if(b>>>0>=p>>>0)break b}}else b=m;while(0);k=m;l=b-k|0;f=0;while(1){g=c[18968+(f<<4)>>2]|0;if((aE(g)|0)==(l|0))if(!(bE(g,m,l)|0)){n=9;break}f=f+1|0;if(f>>>0>=28){f=0;n=11;break}}c:do if((n|0)==9){g=18968+(f<<4)+8|0;f=c[g>>2]|0;g=c[g+4>>2]|0;n=49}else if((n|0)==11){while(1){if((f+-18|0)>>>0>10){g=c[34340+(f<<3)>>2]|0;if((aE(g)|0)==(l|0))if(!(bE(g,m,l)|0)){n=14;break}}f=f+1|0;if(f>>>0>=36){n=16;break}else n=11}if((n|0)==14){n=0;f=GF(1,0,f|0)|0;g=C}else if((n|0)==16){n=0;h=VD()|0;c[h>>2]=0;g=RE(m,q,10)|0;do if(o){if(!(c[h>>2]|0)){f=c[q>>2]|0;if((f+1-k|0)==(l|0))if((a[f>>0]|0)==99){if((g|0)==1)f=0;else if((g|0)==2)f=1;else if((g|0)==3)f=2;else if((g|0)==4)f=5;else if((g|0)==5)f=9;else if((g|0)==6)f=12;else if((g|0)==7)f=17;else if((g|0)==8)f=22;else if((g|0)==16)f=26;else{n=47;break a}g=18968+(f<<4)+8|0;f=c[g>>2]|0;g=c[g+4>>2]|0;n=49;break c}}}else{f=c[q>>2]|0;j=(f-k|0)==(l|0);if(!j){if((f+1-k|0)!=(l|0))break;if((a[f>>0]|0)!=99)break}if((g|0)==1){f=0;n=21}else if((g|0)==2){f=1;n=21}else if((g|0)==3){f=2;n=21}else if((g|0)==4){f=5;n=21}else if((g|0)==5){f=9;n=21}else if((g|0)==6){f=12;n=21}else if((g|0)==7){f=17;n=21}else if((g|0)==8){f=22;n=21}else if((g|0)==16){f=26;n=21}else{f=0;g=0}if((n|0)==21){g=18968+(f<<4)+8|0;f=c[g>>2]|0;g=c[g+4>>2]|0}if(!j){n=49;break c}c[r>>2]=l;c[r+4>>2]=m;c[r+8>>2]=l;c[r+12>>2]=m;UC(0,24,98843,r);n=49;break c}while(0);c[h>>2]=0;f=PE(m,q,0)|0;g=C;if(c[h>>2]|0){n=47;break a}if(!(((g|0)>0|(g|0)==0&f>>>0>0)&((c[q>>2]|0)-k|0)==(l|0))){n=47;break a}}}while(0);if((n|0)==49)if((f|0)==0&(g|0)==0){e=0;d=0;n=51;break}d=f|d;e=g|e;m=b+1|0;if(m>>>0>=p>>>0){n=51;break}}if((n|0)==47){q=0;r=0;C=q;i=s;return r|0}else if((n|0)==51){C=e;i=s;return d|0}return 0}function UB(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;o=i;i=i+48|0;l=o+40|0;n=o+32|0;m=o+24|0;h=o+16|0;g=o+8|0;j=o;if((b|0)<1){b=d-(d>>>1&1431655765)|0;b=(b>>>2&858993459)+(b&858993459)|0;b=(b>>>4)+b&252645135;b=(b>>>8)+b|0;f=JF(d|0,e|0,33)|0;f=e-(f&1431655765)|0;f=(f>>>2&858993459)+(f&858993459)|0;f=(f>>>4)+f&252645135;f=(f>>>8)+f|0;f=((b>>>16)+b&63)+((f>>>16)+f&63)|0}else f=b;b=0;do{if((f|0)==(c[18968+(b<<4)+4>>2]|0)){p=18968+(b<<4)+8|0;if((c[p>>2]|0)==(d|0)?(c[p+4>>2]|0)==(e|0):0){k=6;break}}b=b+1|0}while(b>>>0<28);if((k|0)==6){c[j>>2]=c[18968+(b<<4)>>2];FB(a,99021,j);i=o;return}c[g>>2]=f;FB(a,99024,g);if((d|0)==0&(e|0)==0){i=o;return}FB(a,99036,h);b=0;g=0;do{p=GF(1,0,g|0)|0;if(!((p&d|0)==0&(C&e|0)==0)){if(g>>>0<=35){f=c[34340+(g<<3)>>2]|0;if((g+-18|0)>>>0>=11){if((b|0)>0)FB(a,99039,m);c[n>>2]=f;FB(a,99021,n)}}b=b+1|0}g=g+1|0}while((g|0)!=64);FB(a,99041,l);i=o;return}function VB(a,b){a=a|0;b=b|0;var c=0;c=a-(a>>>1&1431655765)|0;c=(c>>>2&858993459)+(c&858993459)|0;c=(c>>>4)+c&252645135;c=(c>>>8)+c|0;a=JF(a|0,b|0,33)|0;b=b-(a&1431655765)|0;b=(b>>>2&858993459)+(b&858993459)|0;b=(b>>>4)+b&252645135;b=(b>>>8)+b|0;return ((c>>>16)+c&63)+((b>>>16)+b&63)|0}function WB(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;f=i;i=i+1024|0;g=f;EB(g,a,b);UB(g,c,d,e);i=f;return}function XB(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;if(!b){d=0;return d|0}if(!e)e=0;else e=(e-(c[b+4>>2]|0)>>3)+1|0;k=c[b>>2]|0;if(e>>>0>=k>>>0){d=0;return d|0}j=c[b+4>>2]|0;i=(f&2|0)==0;a:do if(!(f&1)){while(1){g=c[j+(e<<3)>>2]|0;h=0;while(1){f=a[g+h>>0]|0;m=f<<24>>24;b=a[d+h>>0]|0;l=b<<24>>24;b=b<<24>>24==0;if(b|(((m+-97|0)>>>0<26?m^32:m)|0)!=(((l+-97|0)>>>0<26?l^32:l)|0))break;else h=h+1|0}if(!(i&f<<24>>24!=0|b^1))break a;e=e+1|0;if(e>>>0>=k>>>0){e=0;break}}return e|0}else if(i){while(1){g=c[j+(e<<3)>>2]|0;h=0;while(1){f=a[g+h>>0]|0;b=a[d+h>>0]|0;if(b<<24>>24==0?1:f<<24>>24!=b<<24>>24)break;else h=h+1|0}if(!((b|f)<<24>>24))break a;e=e+1|0;if(e>>>0>=k>>>0){e=0;break}}return e|0}else{while(1){b=c[j+(e<<3)>>2]|0;g=0;while(1){m=a[d+g>>0]|0;f=m<<24>>24==0;if((a[b+g>>0]|0)!=m<<24>>24|f)break;else g=g+1|0}if(f)break a;e=e+1|0;if(e>>>0>=k>>>0){e=0;break}}return e|0}while(0);m=j+(e<<3)|0;return m|0}function YB(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=i;i=i+16|0;m=p+8|0;o=p+4|0;l=p;g=c[a>>2]|0;j=XB(g,b,0,e)|0;c[m>>2]=0;c[o>>2]=0;c[l>>2]=0;if(!(e&4))h=dD(b)|0;else h=b;c[o>>2]=h;if(!(e&8))if(!h)f=0;else{f=dD(d)|0;c[l>>2]=f}else{c[l>>2]=d;f=d}if(!g){g=cD(8)|0;c[a>>2]=g;if(g){n=g;k=9}}else{n=g;k=9}if((k|0)==9){do if((b|0)==0|(h|0)!=0)if((d|0)==0|(f|0)!=0){if(!j){g=n+4|0;h=ZC(c[g>>2]|0,(c[n>>2]<<3)+8|0)|0;if(!h)break;c[g>>2]=h}else{if(e&16|0){$C(h);$C(f);a=0;i=p;return a|0}f=c[j+4>>2]|0;if(!(e&32))$C(f);else c[m>>2]=f;$C(c[j>>2]|0);b=(c[n>>2]|0)+-1|0;c[n>>2]=b;b=(c[n+4>>2]|0)+(b<<3)|0;d=c[b+4>>2]|0;k=j;c[k>>2]=c[b>>2];c[k+4>>2]=d}if(!(c[l>>2]|0)){bD(o);f=c[n>>2]|0}else{f=c[n>>2]|0;b=n+4|0;d=c[b>>2]|0;c[d+(f<<3)>>2]=c[o>>2];c[d+(f<<3)+4>>2]=c[l>>2];d=c[m>>2]|0;if(!((e&32|0)==0|(d|0)==0)){g=aE(d)|0;f=c[l>>2]|0;g=g+1+(aE(f)|0)|0;h=cD(g)|0;if(!h)break;xB(h,d,g)|0;bD(m);xB(h,f,g)|0;c[(c[b>>2]|0)+(c[n>>2]<<3)+4>>2]=h;bD(l);f=c[n>>2]|0}f=f+1|0;c[n>>2]=f}if(f|0){a=0;i=p;return a|0}bD(n+4|0);bD(a);a=0;i=p;return a|0}else f=0;while(0);if(!(c[n>>2]|0)){bD(n+4|0);bD(a)}}$C(c[o>>2]|0);$C(f);a=-12;i=p;return a|0}function ZB(a){a=a|0;var b=0,d=0,e=0,f=0;d=c[a>>2]|0;if(!d){bD(a);return}f=c[d>>2]|0;b=f+-1|0;c[d>>2]=b;e=d+4|0;if(f|0)do{bD((c[e>>2]|0)+(b<<3)|0);bD((c[e>>2]|0)+(c[d>>2]<<3)+4|0);f=c[d>>2]|0;b=f+-1|0;c[d>>2]=b}while((f|0)!=0);bD(e);bD(a);return}function _B(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;h=b+4|0;if(!b)return;e=0;while(1){if(!e)g=0;else g=(e-(c[h>>2]|0)>>3)+1|0;if(g>>>0>=(c[b>>2]|0)>>>0){e=8;break}f=c[h>>2]|0;e=f+(g<<3)|0;if(!e){e=8;break}YB(a,c[e>>2]|0,c[f+(g<<3)+4>>2]|0,d)|0}if((e|0)==8)return}function $B(a,b){a=a|0;b=+b;var d=0,e=0.0;b=b*-3.141592653589793/180.0;e=+Q(+b);b=+R(+b);c[a>>2]=0;c[a+4>>2]=0;c[a+8>>2]=0;c[a+12>>2]=0;c[a+16>>2]=0;c[a+20>>2]=0;c[a+24>>2]=0;c[a+28>>2]=0;d=~~(e*65536.0);c[a>>2]=d;c[a+4>>2]=~~(b*-65536.0);c[a+12>>2]=~~(b*65536.0);c[a+16>>2]=d;c[a+32>>2]=1073741824;return}function aC(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;e=1-(((b|0)!=0&1)<<1)|0;f=1-(((d|0)!=0&1)<<1)|0;if(!(d|b))return;c[a>>2]=_(c[a>>2]|0,e)|0;d=a+4|0;c[d>>2]=_(c[d>>2]|0,f)|0;d=a+12|0;c[d>>2]=_(c[d>>2]|0,e)|0;d=a+16|0;c[d>>2]=_(c[d>>2]|0,f)|0;d=a+24|0;c[d>>2]=_(c[d>>2]|0,e)|0;d=a+28|0;c[d>>2]=_(c[d>>2]|0,f)|0;return}function bC(b,d){b=b|0;d=d|0;var e=0.0,f=0,g=0.0,h=0,j=0,k=0,l=0;l=i;i=i+16|0;k=l;if((a[b>>0]|0)==48)if((a[b+1>>0]|32|0)==120)e=+((QE(b,k,16)|0)>>>0);else j=4;else j=4;if((j|0)==4)e=+TE(b,k);h=c[k>>2]|0;f=h;if((h|0)!=(b|0)){b=a[h>>0]|0;if(b<<24>>24==100)if((a[h+1>>0]|0)==66){e=+P(10.0,+(e/20.0));f=h+2|0;c[k>>2]=f;b=f}else j=10;else if((b+-69&255)<54)j=10;else b=h;do if((j|0)==10){b=a[99043+((b<<24>>24)+-69)>>0]|0;if(!(b<<24>>24))b=h;else{f=h+1|0;g=+(b<<24>>24);if((a[f>>0]|0)==105){e=e*+rF(g/.3);f=h+2|0;c[k>>2]=f;b=f;break}else{e=e*+P(10.0,+g);c[k>>2]=f;b=f;break}}}while(0);if((a[b>>0]|0)==66){f=b+1|0;c[k>>2]=f;e=e*8.0}}if(!d){i=l;return +e}c[d>>2]=f;i=l;return +e}function cC(a){a=a|0;var b=0,d=0;d=i;i=i+16|0;b=d;c[b>>2]=a;if(!a){i=d;return}cC(c[a+20>>2]|0);cC(c[a+24>>2]|0);cC(c[a+28>>2]|0);bD(a+32|0);bD(b);i=d;return}function dC(b,d,e,f,g,h,j,k,l){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;var m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i;i=i+64|0;r=t;q=t+12|0;s=t+8|0;m=q;n=m+52|0;do{c[m>>2]=0;m=m+4|0}while((m|0)<(n|0));c[s>>2]=0;p=YC((aE(d)|0)+1|0)|0;if(!p){s=-12;i=t;return s|0}else{n=d;m=p}a:while(1){do{o=a[n>>0]|0;if(!(o<<24>>24))break a;n=n+1|0}while((AB(o<<24>>24)|0)!=0);a[m>>0]=o;m=m+1|0}a[m>>0]=0;c[q>>2]=34628;c[q+4>>2]=100;n=q+8|0;c[n>>2]=p;c[q+16>>2]=e;c[q+20>>2]=g;c[q+24>>2]=f;c[q+28>>2]=j;c[q+32>>2]=h;c[q+40>>2]=k;c[q+44>>2]=l;m=eC(s,q)|0;do if((m|0)>=0){n=c[n>>2]|0;if(a[n>>0]|0){c[r>>2]=n;c[r+4>>2]=d;UC(q,16,99249,r);m=-22;break}n=c[s>>2]|0;if(!(kC(n)|0))m=-22;else{r=cD(80)|0;c[n+32>>2]=r;if(!r)m=-12;else{c[b>>2]=n;c[s>>2]=0}}}while(0);cC(c[s>>2]|0);$C(p);s=m;i=t;return s|0}function eC(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0;n=i;i=i+16|0;m=n+4|0;k=n;l=d+4|0;e=c[l>>2]|0;if((e|0)<1){m=-22;i=n;return m|0}c[l>>2]=e+-1;e=fC(m,d)|0;if((e|0)<0){m=e;i=n;return m|0}j=d+8|0;e=c[j>>2]|0;do if((a[e>>0]|0)==59){f=c[m>>2]|0;while(1){c[j>>2]=e+1;e=fC(k,d)|0;if((e|0)<0){g=7;break}e=c[k>>2]|0;g=cD(40)|0;if(!g){g=9;break}c[g>>2]=22;h[g+8>>3]=1.0;c[g+20>>2]=f;c[g+24>>2]=e;c[m>>2]=g;e=c[j>>2]|0;if((a[e>>0]|0)!=59){e=g;g=11;break}else f=g}if((g|0)==7){cC(f);m=e;i=n;return m|0}else if((g|0)==9){c[m>>2]=0;cC(f);cC(e);m=-12;i=n;return m|0}else if((g|0)==11)break}else e=c[m>>2]|0;while(0);c[l>>2]=(c[l>>2]|0)+1;c[b>>2]=e;m=0;i=n;return m|0}function fC(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0;m=i;i=i+16|0;l=m+4|0;k=m;e=gC(l,d)|0;if((e|0)<0){l=e;i=m;return l|0}j=d+8|0;e=c[l>>2]|0;a:while(1){switch(a[c[j>>2]>>0]|0){case 43:case 45:break;default:{g=9;break a}}f=gC(k,d)|0;if((f|0)<0){g=5;break}f=c[k>>2]|0;g=cD(40)|0;if(!g){g=7;break}c[g>>2]=21;h[g+8>>3]=1.0;c[g+20>>2]=e;c[g+24>>2]=f;c[l>>2]=g;e=g}if((g|0)==5){cC(e);l=f;i=m;return l|0}else if((g|0)==7){c[l>>2]=0;cC(e);cC(f);l=-12;i=m;return l|0}else if((g|0)==9){c[b>>2]=e;l=0;i=m;return l|0}return 0}function gC(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0;n=i;i=i+16|0;m=n+4|0;l=n;e=hC(m,d)|0;if((e|0)<0){m=e;i=n;return m|0}k=d+8|0;a:while(1){e=c[k>>2]|0;switch(a[e>>0]|0){case 47:case 42:break;default:{g=9;break a}}c[k>>2]=e+1;j=a[e>>0]|0;f=c[m>>2]|0;e=hC(l,d)|0;if((e|0)<0){g=5;break}e=c[l>>2]|0;g=cD(40)|0;if(!g){g=7;break}c[g>>2]=j<<24>>24==42?19:20;h[g+8>>3]=1.0;c[g+20>>2]=f;c[g+24>>2]=e;c[m>>2]=g}if((g|0)==5){cC(f);m=e;i=n;return m|0}else if((g|0)==7){c[m>>2]=0;cC(f);cC(e);m=-12;i=n;return m|0}else if((g|0)==9){c[b>>2]=c[m>>2];m=0;i=n;return m|0}return 0}function hC(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0.0,m=0,n=0,o=0,p=0,q=0,r=0;r=i;i=i+16|0;o=r+8|0;p=r+4|0;n=r;m=d+8|0;f=c[m>>2]|0;e=a[f>>0]|0;do if(e<<24>>24==45){+TE(f,o);e=c[o>>2]|0;f=c[m>>2]|0;if((e|0)!=(f|0))if((a[e>>0]|0)==100)if((a[e+1>>0]|0)==66){e=iC(p,d)|0;l=1.0;break}e=a[f>>0]|0;q=7}else q=7;while(0);if((q|0)==7){k=(e<<24>>24==43&1)-(e<<24>>24==45&1)|0;c[m>>2]=f+(k&1);e=iC(p,d)|0;l=+(k|1|0)}if((e|0)<0){q=e;i=r;return q|0}g=c[m>>2]|0;e=c[p>>2]|0;f=e;do if((a[g>>0]|0)==94){while(1){g=g+1|0;c[m>>2]=g;f=a[g>>0]|0;do if(f<<24>>24==45){+TE(g,o);f=c[o>>2]|0;g=c[m>>2]|0;if((f|0)!=(g|0))if((a[f>>0]|0)==100)if((a[f+1>>0]|0)==66){f=iC(n,d)|0;j=0;break}f=a[g>>0]|0;q=17}else q=17;while(0);if((q|0)==17){q=0;j=(f<<24>>24==43&1)-(f<<24>>24==45&1)|0;c[m>>2]=g+(j&1);f=iC(n,d)|0}if((f|0)<0){q=19;break}g=c[n>>2]|0;k=cD(40)|0;f=g;if(!k){q=21;break}c[k>>2]=18;h[k+8>>3]=1.0;c[k+20>>2]=e;c[k+24>>2]=g;c[p>>2]=k;if(g|0){g=f+8|0;h[g>>3]=+(j|1|0)*+h[g>>3]}g=c[m>>2]|0;if((a[g>>0]|0)!=94){f=k;e=k;q=25;break}else e=k}if((q|0)==19){cC(e);q=f;i=r;return q|0}else if((q|0)==21){c[p>>2]=0;cC(e);cC(f);q=-12;i=r;return q|0}else if((q|0)==25)break}while(0);if(e|0){q=e+8|0;h[q>>3]=l*+h[q>>3]}c[b>>2]=f;q=0;i=r;return q|0}function iC(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0.0;A=i;i=i+48|0;w=A+24|0;t=A+16|0;p=A+8|0;n=A;z=A+32|0;u=A+28|0;s=cD(40)|0;c[z>>2]=s;v=d+8|0;x=c[v>>2]|0;c[u>>2]=x;if(!s){b=-12;i=A;return b|0}B=+bC(x,u);r=c[z>>2]|0;o=r+8|0;h[o>>3]=B;s=c[u>>2]|0;q=r;if((s|0)!=(c[v>>2]|0)){c[r>>2]=0;c[v>>2]=s;c[b>>2]=q;b=0;i=A;return b|0}h[o>>3]=1.0;l=c[d+16>>2]|0;a:do if(l|0){f=c[l>>2]|0;if(f|0){m=a[s>>0]|0;e=0;while(1){g=a[f>>0]|0;b:do if(!(g<<24>>24)){g=m;y=10}else{j=g;g=m;k=0;while(1){k=k+1|0;if(j<<24>>24!=g<<24>>24)break b;j=a[f+k>>0]|0;g=a[s+k>>0]|0;if(!(j<<24>>24)){y=10;break}}}while(0);if((y|0)==10){y=0;k=g<<24>>24;if(g<<24>>24!=95&((k+-48|0)>>>0>9&((k&-33)+-65|0)>>>0>25))break}e=e+1|0;f=c[l+(e<<2)>>2]|0;if(!f)break a}c[v>>2]=s+(aE(f)|0);c[r>>2]=1;c[r+16>>2]=e;c[b>>2]=q;b=0;i=A;return b|0}}while(0);f=a[s>>0]|0;if(f<<24>>24==69){l=a[s+1>>0]|0;m=l<<24>>24;if(l<<24>>24!=95&((m+-48|0)>>>0>9&((m&-33)+-65|0)>>>0>25)){e=19416;f=0}else{e=69;y=301}}else{e=f;y=301}c:do if((y|0)==301){if(e<<24>>24==80)if((a[s+1>>0]|0)==73){l=a[s+2>>0]|0;m=l<<24>>24;if(l<<24>>24!=95&((m+-48|0)>>>0>9&((m&-33)+-65|0)>>>0>25)){e=19432;f=1;break}}switch(f<<24>>24){case 80:{if((a[s+1>>0]|0)==72)if((a[s+2>>0]|0)==73){l=a[s+3>>0]|0;m=l<<24>>24;if(l<<24>>24!=95&((m+-48|0)>>>0>9&((m&-33)+-65|0)>>>0>25)){e=19448;f=2;break c}}if(f<<24>>24==81)y=306;break}case 81:{y=306;break}default:{}}if((y|0)==306)if((a[s+1>>0]|0)==80)if((a[s+2>>0]|0)==50)if((a[s+3>>0]|0)==76)if((a[s+4>>0]|0)==65)if((a[s+5>>0]|0)==77)if((a[s+6>>0]|0)==66)if((a[s+7>>0]|0)==68)if((a[s+8>>0]|0)==65){l=a[s+9>>0]|0;m=l<<24>>24;if(l<<24>>24!=95&((m+-48|0)>>>0>9&((m&-33)+-65|0)>>>0>25)){e=19464;f=3;break}}e=cF(s,40)|0;c[v>>2]=e;if(!e){c[n>>2]=x;UC(d,16,99121,n);c[v>>2]=c[u>>2];cC(c[z>>2]|0);b=-22;i=A;return b|0}c[v>>2]=e+1;if((a[s>>0]|0)==40){bD(z);e=eC(z,d)|0;if((e|0)<0){b=e;i=A;return b|0}e=c[v>>2]|0;if((a[e>>0]|0)==41){c[v>>2]=e+1;c[b>>2]=c[z>>2];b=0;i=A;return b|0}else{c[p>>2]=x;UC(d,16,99164,p);cC(c[z>>2]|0);b=-22;i=A;return b|0}}e=eC(r+20|0,d)|0;if((e|0)<0){cC(c[z>>2]|0);b=e;i=A;return b|0}f=c[v>>2]|0;e=a[f>>0]|0;if(e<<24>>24==44){c[v>>2]=f+1;eC((c[z>>2]|0)+24|0,d)|0;f=c[v>>2]|0;e=a[f>>0]|0;if(e<<24>>24==44){c[v>>2]=f+1;eC((c[z>>2]|0)+28|0,d)|0;f=c[v>>2]|0;e=a[f>>0]|0}}if(e<<24>>24!=41){c[t>>2]=x;UC(d,16,99185,t);cC(c[z>>2]|0);b=-22;i=A;return b|0}c[v>>2]=f+1;p=c[z>>2]|0;c[p>>2]=2;n=c[u>>2]|0;o=a[n>>0]|0;k=o<<24>>24==115;d:do if(k){if((a[n+1>>0]|0)==105)if((a[n+2>>0]|0)==110)if((a[n+3>>0]|0)==104){u=a[n+4>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=1;break}}if((a[n+1>>0]|0)==105)if((a[n+2>>0]|0)==110){u=a[n+3>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))c[p+16>>2]=4;else{l=0;m=0;y=43}}else{l=0;m=0;y=43}else{l=0;m=0;y=43}}else switch(o<<24>>24){case 99:{if((a[n+1>>0]|0)==111)if((a[n+2>>0]|0)==115)if((a[n+3>>0]|0)==104){u=a[n+4>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=2;break d}}if((a[n+1>>0]|0)!=111){l=1;m=0;y=43;break d}if((a[n+2>>0]|0)!=115){l=1;m=0;y=43;break d}u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))){l=1;m=0;y=43;break d}c[p+16>>2]=5;break d}case 116:{if((a[n+1>>0]|0)==97)if((a[n+2>>0]|0)==110)if((a[n+3>>0]|0)==104){u=a[n+4>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=3;break d}}if((a[n+1>>0]|0)!=97){l=0;m=1;y=43;break d}if((a[n+2>>0]|0)!=110){l=0;m=1;y=43;break d}u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))){l=0;m=1;y=43;break d}c[p+16>>2]=6;break d}default:{l=0;m=0;y=43;break d}}while(0);e:do if((y|0)==43){e=o<<24>>24==97;do if(e){if((a[n+1>>0]|0)==116)if((a[n+2>>0]|0)==97)if((a[n+3>>0]|0)==110){u=a[n+4>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=7;break e}}do if((a[n+1>>0]|0)==115)if((a[n+2>>0]|0)==105)if((a[n+3>>0]|0)==110){u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p+16>>2]=8;break e}while(0);if((a[n+1>>0]|0)==99)if((a[n+2>>0]|0)==111)if((a[n+3>>0]|0)==115){u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p+16>>2]=9;break e}}while(0);f=o<<24>>24==101;if(f)if((a[n+1>>0]|0)==120)if((a[n+2>>0]|0)==112){u=a[n+3>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=10;break}else g=0}else g=0;else g=0;else if(o<<24>>24==108)if((a[n+1>>0]|0)==111)if((a[n+2>>0]|0)==103){u=a[n+3>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=11;break}else g=1}else g=1;else g=1;else g=0;if(e)if((a[n+1>>0]|0)==98)if((a[n+2>>0]|0)==115){u=a[n+3>>0]|0;v=u<<24>>24;if(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)){c[p+16>>2]=12;break}}do if(m)if((a[n+1>>0]|0)==105)if((a[n+2>>0]|0)==109){if((a[n+3>>0]|0)!=101)break;u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p+16>>2]=13;break e}while(0);f:do if(k)if((a[n+1>>0]|0)==113){if((a[n+2>>0]|0)!=117){j=0;break}if((a[n+3>>0]|0)!=105){j=0;break}if((a[n+4>>0]|0)!=115){j=0;break}if((a[n+5>>0]|0)!=104){j=0;break}u=a[n+6>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))){j=0;break}c[p>>2]=5;break e}else j=0;else{switch(o<<24>>24){case 103:{if((a[n+1>>0]|0)!=97){j=1;break f}if((a[n+2>>0]|0)!=117){j=1;break f}if((a[n+3>>0]|0)!=115){j=1;break f}if((a[n+4>>0]|0)!=115){j=1;break f}u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))){j=1;break f}c[p>>2]=6;break e}case 109:break;default:{j=0;break f}}do if((a[n+1>>0]|0)==111){if((a[n+2>>0]|0)!=100)break;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=10;break e}while(0);do if((a[n+1>>0]|0)==97){if((a[n+2>>0]|0)!=120)break;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=11;break e}while(0);if((a[n+1>>0]|0)!=105){j=0;break}if((a[n+2>>0]|0)!=110){j=0;break}u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))){j=0;break}c[p>>2]=12;break e}while(0);do if(f){if((a[n+1>>0]|0)!=113)break;u=a[n+2>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=13;break e}while(0);do if(j){do if((a[n+1>>0]|0)==116){if((a[n+2>>0]|0)!=101)break;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=15;break e}while(0);if((a[n+1>>0]|0)!=116)break;u=a[n+2>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=14;break e}while(0);do if(g){do if((a[n+1>>0]|0)==116){if((a[n+2>>0]|0)!=101)break;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=16;break e}while(0);do if((a[n+1>>0]|0)==116){u=a[n+2>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=17;break e}while(0);if((a[n+1>>0]|0)!=100)break;u=a[n+2>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=7;break e}while(0);e=o<<24>>24==105;do if(e){do if((a[n+1>>0]|0)==115){if((a[n+2>>0]|0)!=110)break;if((a[n+3>>0]|0)!=97)break;if((a[n+4>>0]|0)!=110)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=8;break e}while(0);if((a[n+1>>0]|0)!=115)break;if((a[n+2>>0]|0)!=105)break;if((a[n+3>>0]|0)!=110)break;if((a[n+4>>0]|0)!=102)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=9;break e}while(0);do if(k){if((a[n+1>>0]|0)!=116)break;u=a[n+2>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=23;break e}else{if(o<<24>>24!=119)break;if((a[n+1>>0]|0)!=104)break;if((a[n+2>>0]|0)!=105)break;if((a[n+3>>0]|0)!=108)break;if((a[n+4>>0]|0)!=101)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=24;break e}while(0);do if(m){if((a[n+1>>0]|0)!=97)break;if((a[n+2>>0]|0)!=121)break;if((a[n+3>>0]|0)!=108)break;if((a[n+4>>0]|0)!=111)break;if((a[n+5>>0]|0)!=114)break;u=a[n+6>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=25;break e}while(0);f=o<<24>>24==114;do if(f){if((a[n+1>>0]|0)!=111)break;if((a[n+2>>0]|0)!=111)break;if((a[n+3>>0]|0)!=116)break;u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=26;break e}else{if(o<<24>>24!=102)break;if((a[n+1>>0]|0)!=108)break;if((a[n+2>>0]|0)!=111)break;if((a[n+3>>0]|0)!=111)break;if((a[n+4>>0]|0)!=114)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=27;break e}while(0);do if(l){if((a[n+1>>0]|0)!=101)break;if((a[n+2>>0]|0)!=105)break;if((a[n+3>>0]|0)!=108)break;u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=28;break e}while(0);do if(m){if((a[n+1>>0]|0)!=114)break;if((a[n+2>>0]|0)!=117)break;if((a[n+3>>0]|0)!=110)break;if((a[n+4>>0]|0)!=99)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=29;break e}while(0);g:do if(k){if((a[n+1>>0]|0)!=113)break;if((a[n+2>>0]|0)!=114)break;if((a[n+3>>0]|0)!=116)break;u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=30;break e}else{switch(o<<24>>24){case 110:{if((a[n+1>>0]|0)!=111)break g;if((a[n+2>>0]|0)!=116)break g;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break g;c[p>>2]=31;break e}case 112:break;default:break g}do if((a[n+1>>0]|0)==111){if((a[n+2>>0]|0)!=119)break;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=18;break e}while(0);if((a[n+1>>0]|0)!=114)break;if((a[n+2>>0]|0)!=105)break;if((a[n+3>>0]|0)!=110)break;if((a[n+4>>0]|0)!=116)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=37;break e}while(0);do if(f){if((a[n+1>>0]|0)!=97)break;if((a[n+2>>0]|0)!=110)break;if((a[n+3>>0]|0)!=100)break;if((a[n+4>>0]|0)!=111)break;if((a[n+5>>0]|0)!=109)break;u=a[n+6>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=32;break e}else{if(o<<24>>24!=104)break;if((a[n+1>>0]|0)!=121)break;if((a[n+2>>0]|0)!=112)break;if((a[n+3>>0]|0)!=111)break;if((a[n+4>>0]|0)!=116)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=33;break e}while(0);do if(j){if((a[n+1>>0]|0)!=99)break;if((a[n+2>>0]|0)!=100)break;u=a[n+3>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=34;break e}while(0);do if(e){do if((a[n+1>>0]|0)==102){u=a[n+2>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=35;break e}while(0);if((a[n+1>>0]|0)!=102)break;if((a[n+2>>0]|0)!=110)break;if((a[n+3>>0]|0)!=111)break;if((a[n+4>>0]|0)!=116)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=36;break e}else{if(o<<24>>24!=98)break;do if((a[n+1>>0]|0)==105){if((a[n+2>>0]|0)!=116)break;if((a[n+3>>0]|0)!=97)break;if((a[n+4>>0]|0)!=110)break;if((a[n+5>>0]|0)!=100)break;u=a[n+6>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=38;break e}while(0);do if((a[n+1>>0]|0)==105){if((a[n+2>>0]|0)!=116)break;if((a[n+3>>0]|0)!=111)break;if((a[n+4>>0]|0)!=114)break;u=a[n+5>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=39;break e}while(0);if((a[n+1>>0]|0)!=101)break;if((a[n+2>>0]|0)!=116)break;if((a[n+3>>0]|0)!=119)break;if((a[n+4>>0]|0)!=101)break;if((a[n+5>>0]|0)!=101)break;if((a[n+6>>0]|0)!=110)break;u=a[n+7>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=40;break e}while(0);do if(l){if((a[n+1>>0]|0)!=108)break;if((a[n+2>>0]|0)!=105)break;if((a[n+3>>0]|0)!=112)break;u=a[n+4>>0]|0;v=u<<24>>24;if(!(u<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25)))break;c[p>>2]=41;break e}while(0);l=c[d+24>>2]|0;h:do if(l|0){e=c[l>>2]|0;if(!e)break;else{k=e;e=0}while(1){f=a[k>>0]|0;i:do if(!(f<<24>>24)){f=o;y=156}else{g=f;f=o;j=0;while(1){j=j+1|0;if(g<<24>>24!=f<<24>>24)break i;g=a[k+j>>0]|0;f=a[n+j>>0]|0;if(!(g<<24>>24)){y=156;break}}}while(0);if((y|0)==156){y=0;v=f<<24>>24;if(f<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))break}e=e+1|0;k=c[l+(e<<2)>>2]|0;if(!k)break h}c[p+16>>2]=c[(c[d+20>>2]|0)+(e<<2)>>2];d=c[z>>2]|0;c[d>>2]=3;c[b>>2]=d;b=0;i=A;return b|0}while(0);l=c[d+32>>2]|0;j:do if(l|0){e=c[l>>2]|0;if(!e)break;else{k=e;e=0}while(1){f=a[k>>0]|0;k:do if(!(f<<24>>24)){f=o;y=162}else{g=f;f=o;j=0;while(1){j=j+1|0;if(g<<24>>24!=f<<24>>24)break k;g=a[k+j>>0]|0;f=a[n+j>>0]|0;if(!(g<<24>>24)){y=162;break}}}while(0);if((y|0)==162){y=0;v=f<<24>>24;if(f<<24>>24!=95&((v+-48|0)>>>0>9&((v&-33)+-65|0)>>>0>25))break}e=e+1|0;k=c[l+(e<<2)>>2]|0;if(!k)break j}c[p+16>>2]=c[(c[d+28>>2]|0)+(e<<2)>>2];d=c[z>>2]|0;c[d>>2]=4;c[b>>2]=d;b=0;i=A;return b|0}while(0);c[w>>2]=x;UC(d,16,99223,w);cC(c[z>>2]|0);b=-22;i=A;return b|0}while(0);c[b>>2]=c[z>>2];b=0;i=A;return b|0}while(0);c[v>>2]=s+(aE(c[e>>2]|0)|0);c[r>>2]=0;h[o>>3]=+h[19416+(f<<4)+8>>3];c[b>>2]=q;b=0;i=A;return b|0}function jC(a){a=+a;var b=0;b=RD()|0;return +((+(b>>>0)+4294967296.0*+(C|0))*1.0e-06)}function kC(a){a=a|0;a:do if(!a)a=0;else switch(c[a>>2]|0){case 1:case 0:{a=1;break a}case 32:case 31:case 30:case 29:case 28:case 27:case 9:case 8:case 6:case 7:case 5:case 3:case 2:{if(!(kC(c[a+20>>2]|0)|0)){a=0;break a}a=(c[a+24>>2]|0)==0;break a}case 37:{if(!(kC(c[a+20>>2]|0)|0)){a=0;break a}a=c[a+24>>2]|0;if(!a){a=1;break a}a=(kC(a)|0)!=0;break a}case 25:case 36:case 35:{if(!(kC(c[a+20>>2]|0)|0)){a=0;break a}if(!(kC(c[a+24>>2]|0)|0)){a=0;break a}a=c[a+28>>2]|0;if(!a){a=1;break a}a=(kC(a)|0)!=0;break a}case 41:case 40:{if(!(kC(c[a+20>>2]|0)|0)){a=0;break a}if(!(kC(c[a+24>>2]|0)|0)){a=0;break a}a=(kC(c[a+28>>2]|0)|0)!=0;break a}default:{if(!(kC(c[a+20>>2]|0)|0)){a=0;break a}if(!(kC(c[a+24>>2]|0)|0)){a=0;break a}a=(c[a+28>>2]|0)==0;break a}}while(0);return a&1|0}
function Kc(e,f){e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0;ka=i;i=i+416|0;ia=ka+328|0;ea=ka+320|0;da=ka+312|0;ca=ka+304|0;Z=ka+296|0;Y=ka+288|0;X=ka+280|0;W=ka+272|0;V=ka+264|0;U=ka+256|0;T=ka+248|0;S=ka+240|0;Q=ka+232|0;O=ka+224|0;M=ka+200|0;L=ka+192|0;K=ka+176|0;J=ka+168|0;I=ka+160|0;H=ka+152|0;G=ka+144|0;F=ka+136|0;E=ka+128|0;D=ka+120|0;C=ka+112|0;x=ka+104|0;B=ka+96|0;A=ka+88|0;z=ka+80|0;y=ka+72|0;w=ka+64|0;v=ka+56|0;u=ka+48|0;t=ka+40|0;s=ka+32|0;p=ka+24|0;n=ka+16|0;m=ka+8|0;g=ka;ba=e+704|0;ha=c[ba>>2]|0;aa=e+712|0;ga=c[aa>>2]|0;$=e+696|0;j=c[$>>2]|0;k=j+(ha>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(ha&7)>>>24;ha=ha+8|0;ha=ga>>>0>ha>>>0?ha:ga;c[ba>>2]=ha;P=d[j+(ha>>>3)>>0]<<(ha&7)>>>7&1;ha=((ha|0)<(ga|0)&1)+ha|0;c[ba>>2]=ha;N=d[j+(ha>>>3)>>0]<<(ha&7);ha=((ha|0)<(ga|0)&1)+ha|0;c[ba>>2]=ha;fa=d[j+(ha>>>3)>>0]<<(ha&7);ha=((ha|0)<(ga|0)&1)+ha|0;c[ba>>2]=ha;ja=d[j+(ha>>>3)>>0]<<(ha&7);ha=((ha|0)<(ga|0)&1)+ha|0;c[ba>>2]=ha;h=d[j+(ha>>>3)>>0]<<(ha&7);ha=((ha|0)<(ga|0)&1)+ha|0;c[ba>>2]=ha;h=N>>>6&2|P|fa>>>5&4|ja>>>4&8|h>>>3&16|d[j+(ha>>>3)>>0]<<(ha&7)>>>2&32;ha=((ha|0)<(ga|0)&1)+ha+2|0;ha=ga>>>0>ha>>>0?ha:ga;c[ba>>2]=ha;j=j+(ha>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(ha&7)>>>24;ha=ha+8|0;ga=ga>>>0>ha>>>0?ha:ga;c[ba>>2]=ga;ha=c[aa>>2]|0;ja=(c[$>>2]|0)+(ga>>>3)|0;ja=(MF(d[ja>>0]|d[ja+1>>0]<<8|d[ja+2>>0]<<16|d[ja+3>>0]<<24|0)|0)<<(ga&7)>>>23;ga=(d[86681+ja>>0]|0)+ga|0;c[ba>>2]=ha>>>0>ga>>>0?ga:ha;ja=a[87193+ja>>0]|0;ha=ja&255;if((ja&255)>31){e=c[e+4>>2]|0;c[g>>2]=ha;UC(e,16,62636,g);e=-1094995529;i=ka;return e|0}ja=cD(1204)|0;if(!ja){e=-12;i=ka;return e|0}c[ja>>2]=ha;c[ja+1164>>2]=24;fa=ja+4|0;c[fa>>2]=k;c[ja+1196>>2]=h;ga=ja+8|0;c[ga>>2]=j;N=ja+112|0;c[N>>2]=-1;q=ja+672|0;r=ja+768|0;FF(q|0,16,480)|0;c[ja+668>>2]=0;P=ja+128|0;c[P>>2]=2;a:do switch(k|0){case 144:case 138:case 128:case 118:case 86:case 83:case 44:case 244:case 122:case 110:case 100:{g=c[ba>>2]|0;l=c[aa>>2]|0;o=c[$>>2]|0;h=o+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7)>>>23;g=(d[86681+h>>0]|0)+g|0;g=l>>>0>g>>>0?g:l;c[ba>>2]=g;h=a[87193+h>>0]|0;j=h&255;c[ja+12>>2]=j;if((h&255)>3){e=c[e+4>>2]|0;c[m>>2]=j;VC(e,62660,m);break a}if(h<<24>>24==3){m=d[o+(g>>>3)>>0]<<(g&7)>>>7&1;g=((g|0)<(l|0)&1)+g|0;c[ba>>2]=g;c[ja+1192>>2]=m;if(m){UC(c[e+4>>2]|0,16,62681,n);break a}}j=o+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);do if(j>>>0>134217727){h=j>>>23;m=(d[86681+h>>0]|0)+g|0;m=l>>>0>m>>>0?m:l;c[ba>>2]=m;g=d[87193+h>>0]|0;h=o}else{h=j>>>0>65535;n=h?j>>>16:j;h=h?16:0;k=(n&65280|0)==0;h=(d[97868+(k?n:n>>>8)>>0]|0)+(k?h:h|8)<<1;k=h+-31|0;h=63-h+g|0;h=l>>>0>h>>>0?h:l;c[ba>>2]=h;if((k|0)<7){UC(0,16,66830,p);g=-1094995529;m=c[ba>>2]|0;h=c[$>>2]|0;l=c[aa>>2]|0;break}else{g=(j>>>k)+-1|0;m=h;h=o;break}}while(0);g=g+8|0;k=ja+1184|0;c[k>>2]=g;h=h+(m>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(m&7);do if(h>>>0>134217727){h=h>>>23;s=(d[86681+h>>0]|0)+m|0;c[ba>>2]=l>>>0>s>>>0?s:l;h=d[87193+h>>0]|0}else{p=h>>>0>65535;o=p?h>>>16:h;p=p?16:0;j=(o&65280|0)==0;p=(d[97868+(j?o:o>>>8)>>0]|0)+(j?p:p|8)<<1;j=p+-31|0;p=63-p+m|0;c[ba>>2]=l>>>0>p>>>0?p:l;if((j|0)<7){UC(0,16,66830,s);h=-1094995529;g=c[k>>2]|0;break}else{h=(h>>>j)+-1|0;break}}while(0);s=h+8|0;c[ja+1188>>2]=s;if((s|0)!=(g|0)){VC(c[e+4>>2]|0,62722,t);break a}if((g|0)>14|((h|0)<0|h>>>0>6)){e=c[e+4>>2]|0;c[u>>2]=g;c[u+4>>2]=g;UC(e,16,62758,u);break a}else{u=c[ba>>2]|0;R=d[(c[$>>2]|0)+(u>>>3)>>0]<<(u&7)>>>7&1;c[ba>>2]=((u|0)<(c[aa>>2]|0)&1)+u;c[ja+16>>2]=R;Lc(e,ja,0,1,q,r);R=26;break a}}default:{c[ja+12>>2]=1;c[ja+1184>>2]=8;c[ja+1188>>2]=8;R=26}}while(0);b:do if((R|0)==26){g=c[ba>>2]|0;n=c[aa>>2]|0;m=c[$>>2]|0;j=m+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);do if(j>>>0>134217727){R=j>>>23;h=(d[86681+R>>0]|0)+g|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=d[87193+R>>0]|0;R=31}else{h=j>>>0>65535;u=h?j>>>16:j;h=h?16:0;k=(u&65280|0)==0;h=(d[97868+(k?u:u>>>8)>>0]|0)+(k?h:h|8)<<1;k=h+-31|0;h=63-h+g|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;if((k|0)<7){UC(0,16,66830,v);g=-1094995529;break}else{g=(j>>>k)+-1|0;R=31;break}}while(0);if((R|0)==31)if(g>>>0<=12){c[ja+20>>2]=g+4;w=m+(h>>>3)|0;w=(MF(d[w>>0]|d[w+1>>0]<<8|d[w+2>>0]<<16|d[w+3>>0]<<24|0)|0)<<(h&7)>>>23;g=(d[86681+w>>0]|0)+h|0;g=n>>>0>g>>>0?g:n;c[ba>>2]=g;h=d[87193+w>>0]|0;w=ja+24|0;c[w>>2]=h;c:do switch(h|0){case 0:{j=m+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);do if(j>>>0>134217727){R=j>>>23;h=(d[86681+R>>0]|0)+g|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=d[87193+R>>0]|0;R=39}else{h=j>>>0>65535;B=h?j>>>16:j;h=h?16:0;k=(B&65280|0)==0;h=(d[97868+(k?B:B>>>8)>>0]|0)+(k?h:h|8)<<1;k=h+-31|0;h=63-h+g|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;if((k|0)<7){UC(0,16,66830,y);g=-1094995529;break}else{g=(j>>>k)+-1|0;R=39;break}}while(0);if((R|0)==39)if(g>>>0<=12){c[ja+28>>2]=g+4;g=h;break c}e=c[e+4>>2]|0;c[z>>2]=g;UC(e,16,63003,z);break b}case 1:{h=d[m+(g>>>3)>>0]<<(g&7)>>>7&1;g=((g|0)<(n|0)&1)+g|0;c[ba>>2]=g;c[ja+32>>2]=h;h=m+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);if(h>>>0>134217727){R=h>>>23;h=(d[86681+R>>0]|0)+g|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=a[87705+R>>0]|0}else{R=h>>>0>65535;z=R?h>>>16:h;h=R?16:0;R=(z&65280|0)==0;h=(d[97868+(R?z:z>>>8)>>0]|0)+(R?h:h|8)|0;g=31-h+g|0;g=n>>>0>g>>>0?g:n;R=m+(g>>>3)|0;R=(MF(d[R>>0]|d[R+1>>0]<<8|d[R+2>>0]<<16|d[R+3>>0]<<24|0)|0)<<(g&7)>>>h;h=g+(32-h)|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=R&1;g=(R>>>1^0-g)+g|0}c[ja+36>>2]=g;g=m+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){g=g>>>23;h=(d[86681+g>>0]|0)+h|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=a[87705+g>>0]|0}else{R=g>>>0>65535;z=R?g>>>16:g;g=R?16:0;R=(z&65280|0)==0;g=(d[97868+(R?z:z>>>8)>>0]|0)+(R?g:g|8)|0;h=31-g+h|0;h=n>>>0>h>>>0?h:n;R=m+(h>>>3)|0;R=(MF(d[R>>0]|d[R+1>>0]<<8|d[R+2>>0]<<16|d[R+3>>0]<<24|0)|0)<<(h&7)>>>g;h=h+(32-g)|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=R&1;g=(R>>>1^0-g)+g|0}c[ja+40>>2]=g;g=m+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7);do if(g>>>0>134217727){g=g>>>23;h=(d[86681+g>>0]|0)+h|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;g=d[87193+g>>0]|0;c[ja+44>>2]=g}else{R=g>>>0>65535;z=R?g>>>16:g;R=R?16:0;j=(z&65280|0)==0;R=(d[97868+(j?z:z>>>8)>>0]|0)+(j?R:R|8)<<1;j=R+-31|0;h=63-R+h|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;if((j|0)<7){UC(0,16,66830,A);c[ja+44>>2]=-1094995529;g=-1094995529}else{g=(g>>>j)+-1|0;c[ja+44>>2]=g;if(g>>>0<=255)break}e=c[e+4>>2]|0;c[B>>2]=g;UC(e,16,63042,B);break b}while(0);if((g|0)>0){l=ja+148|0;k=0;do{j=m+(h>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(h&7);if(j>>>0>134217727){j=j>>>23;h=(d[86681+j>>0]|0)+h|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;j=a[87705+j>>0]|0}else{R=j>>>0>65535;B=R?j>>>16:j;j=R?16:0;R=(B&65280|0)==0;j=(d[97868+(R?B:B>>>8)>>0]|0)+(R?j:j|8)|0;h=31-j+h|0;h=n>>>0>h>>>0?h:n;R=m+(h>>>3)|0;R=(MF(d[R>>0]|d[R+1>>0]<<8|d[R+2>>0]<<16|d[R+3>>0]<<24|0)|0)<<(h&7)>>>j;h=h+(32-j)|0;h=n>>>0>h>>>0?h:n;c[ba>>2]=h;j=R&1;j=(R>>>1^0-j)+j|0}b[l+(k<<1)>>1]=j;k=k+1|0}while((k|0)<(g|0));g=h}else g=h;break}case 2:break;default:{e=c[e+4>>2]|0;c[x>>2]=h;UC(e,16,63072,x);break b}}while(0);h=m+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7)>>>23;j=(d[86681+h>>0]|0)+g|0;j=n>>>0>j>>>0?j:n;c[ba>>2]=j;h=d[87193+h>>0]|0;v=ja+48|0;c[v>>2]=h;g=e+4|0;k=c[g>>2]|0;if((c[k+52>>2]|0)==844516691){h=h>>>0<2?2:h;c[v>>2]=h}if((h|0)>34|h>>>0>16){c[C>>2]=h;UC(k,16,63093,C);break}k=d[m+(j>>>3)>>0]<<(j&7)>>>7&1;h=((j|0)<(n|0)&1)+j|0;c[ba>>2]=h;c[ja+52>>2]=k;k=m+(h>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(h&7);do if(k>>>0>134217727){j=k>>>23;l=(d[86681+j>>0]|0)+h|0;l=n>>>0>l>>>0?l:n;c[ba>>2]=l;h=d[87193+j>>0]|0;j=m;k=n}else{j=k>>>0>65535;R=j?k>>>16:k;j=j?16:0;l=(R&65280|0)==0;j=(d[97868+(l?R:R>>>8)>>0]|0)+(l?j:j|8)<<1;l=j+-31|0;j=63-j+h|0;j=n>>>0>j>>>0?j:n;c[ba>>2]=j;if((l|0)<7){UC(0,16,66830,D);h=-1094995529;l=c[ba>>2]|0;j=c[$>>2]|0;k=c[aa>>2]|0;break}else{h=(k>>>l)+-1|0;l=j;j=m;k=n;break}}while(0);m=h+1|0;u=ja+56|0;c[u>>2]=m;h=j+(l>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(l&7);do if(h>>>0>134217727){h=h>>>23;R=(d[86681+h>>0]|0)+l|0;c[ba>>2]=k>>>0>R>>>0?R:k;h=d[87193+h>>0]|0;R=75}else{R=h>>>0>65535;D=R?h>>>16:h;R=R?16:0;j=(D&65280|0)==0;R=(d[97868+(j?D:D>>>8)>>0]|0)+(j?R:R|8)<<1;j=R+-31|0;R=63-R+l|0;c[ba>>2]=k>>>0>R>>>0?R:k;if((j|0)<7){UC(0,16,66830,E);c[ja+60>>2]=-1094995528;R=76;break}else{h=(h>>>j)+-1|0;R=75;break}}while(0);if((R|0)==75){h=h+1|0;t=ja+60|0;c[t>>2]=h;if(h>>>0>134217726|m>>>0>134217726)R=76;else if(!(NC(m<<4,h<<4,0,c[g>>2]|0)|0)){h=c[ba>>2]|0;j=c[$>>2]|0;F=d[j+(h>>>3)>>0]<<(h&7)>>>7&1;l=c[aa>>2]|0;h=((h|0)<(l|0)&1)+h|0;c[ba>>2]=h;s=ja+64|0;c[s>>2]=F;if(!F){F=d[j+(h>>>3)>>0]<<(h&7)>>>7&1;h=((h|0)<(l|0)&1)+h|0;c[ba>>2]=h;c[ja+68>>2]=F}else c[ja+68>>2]=0;F=d[j+(h>>>3)>>0]<<(h&7)>>>7&1;k=((h|0)<(l|0)&1)+h|0;c[ba>>2]=k;B=ja+72|0;c[B>>2]=F;F=d[j+(k>>>3)>>0]<<(k&7)>>>7&1;k=((k|0)<(l|0)&1)+k|0;c[ba>>2]=k;h=ja+76|0;c[h>>2]=F;d:do if(!F){c[h>>2]=0;c[h+4>>2]=0;c[h+8>>2]=0;c[h+12>>2]=0;c[h+16>>2]=0}else{h=j+(k>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(k&7);do if(h>>>0>134217727){h=h>>>23;k=(d[86681+h>>0]|0)+k|0;k=l>>>0>k>>>0?k:l;c[ba>>2]=k;h=d[87193+h>>0]|0}else{F=h>>>0>65535;E=F?h>>>16:h;F=F?16:0;m=(E&65280|0)==0;F=(d[97868+(m?E:E>>>8)>>0]|0)+(m?F:F|8)<<1;m=F+-31|0;k=63-F+k|0;k=l>>>0>k>>>0?k:l;c[ba>>2]=k;if((m|0)<7){UC(0,16,66830,G);h=-1094995529;k=c[ba>>2]|0;j=c[$>>2]|0;l=c[aa>>2]|0;break}else{h=(h>>>m)+-1|0;break}}while(0);m=j+(k>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(k&7);do if(m>>>0>134217727){r=m>>>23;k=(d[86681+r>>0]|0)+k|0;k=l>>>0>k>>>0?k:l;c[ba>>2]=k;r=d[87193+r>>0]|0}else{G=m>>>0>65535;F=G?m>>>16:m;G=G?16:0;n=(F&65280|0)==0;G=(d[97868+(n?F:F>>>8)>>0]|0)+(n?G:G|8)<<1;n=G+-31|0;k=63-G+k|0;k=l>>>0>k>>>0?k:l;c[ba>>2]=k;if((n|0)<7){UC(0,16,66830,H);r=-1094995529;k=c[ba>>2]|0;j=c[$>>2]|0;l=c[aa>>2]|0;break}else{r=(m>>>n)+-1|0;break}}while(0);m=j+(k>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(k&7);do if(m>>>0>134217727){q=m>>>23;m=(d[86681+q>>0]|0)+k|0;m=l>>>0>m>>>0?m:l;c[ba>>2]=m;q=d[87193+q>>0]|0}else{H=m>>>0>65535;G=H?m>>>16:m;H=H?16:0;n=(G&65280|0)==0;H=(d[97868+(n?G:G>>>8)>>0]|0)+(n?H:H|8)<<1;n=H+-31|0;k=63-H+k|0;k=l>>>0>k>>>0?k:l;c[ba>>2]=k;if((n|0)<7){UC(0,16,66830,I);q=-1094995529;m=c[ba>>2]|0;j=c[$>>2]|0;l=c[aa>>2]|0;break}else{q=(m>>>n)+-1|0;m=k;break}}while(0);j=j+(m>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(m&7);do if(j>>>0>134217727){n=j>>>23;J=(d[86681+n>>0]|0)+m|0;c[ba>>2]=l>>>0>J>>>0?J:l;n=d[87193+n>>0]|0}else{I=j>>>0>65535;H=I?j>>>16:j;I=I?16:0;k=(H&65280|0)==0;I=(d[97868+(k?H:H>>>8)>>0]|0)+(k?I:I|8)<<1;k=I+-31|0;I=63-I+m|0;c[ba>>2]=l>>>0>I>>>0?I:l;if((k|0)<7){UC(0,16,66830,J);n=-1094995529;break}else{n=(j>>>k)+-1|0;break}}while(0);o=c[u>>2]<<4;j=2-(c[s>>2]|0)|0;p=_(j,c[t>>2]<<4)|0;l=c[g>>2]|0;if(c[l+92>>2]&65536|0){c[K>>2]=h;c[K+4>>2]=r;c[K+8>>2]=q;c[K+12>>2]=n;UC(l,48,63149,K);M=ja+80|0;c[M>>2]=0;c[M+4>>2]=0;c[M+8>>2]=0;c[M+12>>2]=0;break}k=c[ja+12>>2]|0;m=(k+-1|0)>>>0<2&1;k=j<<((k|0)==1&1);j=31>>>((c[ja+1184>>2]|0)>8&1);do if(j&h){if(c[l+88>>2]&1|0)break;h=h&~j;c[L>>2]=h;UC(l,24,63215,L)}while(0);L=536870911>>>m;do if(!(r>>>0>L>>>0|h>>>0>L>>>0)){L=536870911/(k>>>0)|0;if(q>>>0>L>>>0|n>>>0>L>>>0)break;if(h+r<<m>>>0>=o>>>0)break;if((_(k,n+q|0)|0)>>>0>=p>>>0)break;c[ja+80>>2]=h<<m;c[ja+84>>2]=r<<m;c[ja+88>>2]=_(k,q)|0;c[ja+92>>2]=_(k,n)|0;break d}while(0);e=c[g>>2]|0;c[M>>2]=h;c[M+4>>2]=r;c[M+8>>2]=q;c[M+12>>2]=n;c[M+16>>2]=o;c[M+20>>2]=p;UC(e,16,63283,M);break b}while(0);j=c[ba>>2]|0;k=c[$>>2]|0;M=d[k+(j>>>3)>>0]<<(j&7)>>>7&1;o=c[aa>>2]|0;j=((j|0)<(o|0)&1)+j|0;c[ba>>2]=j;A=ja+96|0;c[A>>2]=M;e:do if(!M)h=e+708|0;else{M=d[k+(j>>>3)>>0]|0;h=((j|0)<(o|0)&1)+j|0;c[ba>>2]=h;do if(!(M&128>>>(j&7))){c[ja+104>>2]=0;c[ja+100>>2]=0}else{j=k+(h>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(h&7);l=j>>>24;h=h+8|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;if((l|0)==255){O=k+(h>>>3)|0;O=(MF(d[O>>0]|d[O+1>>0]<<8|d[O+2>>0]<<16|d[O+3>>0]<<24|0)|0)<<(h&7)>>>16;h=h+16|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;c[ja+100>>2]=O;O=k+(h>>>3)|0;O=(MF(d[O>>0]|d[O+1>>0]<<8|d[O+2>>0]<<16|d[O+3>>0]<<24|0)|0)<<(h&7)>>>16;h=h+16|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;c[ja+104>>2]=O;break}if(j>>>0<285212672){L=33876+(l<<3)|0;M=c[L+4>>2]|0;O=ja+100|0;c[O>>2]=c[L>>2];c[O+4>>2]=M;break}else{UC(c[g>>2]|0,16,63324,O);break b}}while(0);O=((h|0)<(o|0)&1)+h|0;h=((d[k+(h>>>3)>>0]&128>>>(h&7)|0)==0?0:(O|0)<(o|0)&1)+O|0;c[ba>>2]=h;O=d[k+(h>>>3)>>0]<<(h&7)>>>7&1;h=((h|0)<(o|0)&1)+h|0;c[ba>>2]=h;c[ja+108>>2]=O;do if(O){h=h+3|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;O=d[k+(h>>>3)>>0]<<(h&7)>>>7&1;h=((h|0)<(o|0)&1)+h|0;c[ba>>2]=h;c[N>>2]=O;O=d[k+(h>>>3)>>0]<<(h&7)>>>7&1;h=((h|0)<(o|0)&1)+h|0;c[ba>>2]=h;c[ja+116>>2]=O;if(!O)break;O=k+(h>>>3)|0;O=(MF(d[O>>0]|d[O+1>>0]<<8|d[O+2>>0]<<16|d[O+3>>0]<<24|0)|0)<<(h&7);h=h+8|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;j=ja+120|0;c[j>>2]=O>>>24;l=k+(h>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(h&7);h=h+8|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;m=ja+124|0;c[m>>2]=l>>>24;n=k+(h>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(h&7);h=h+8|0;h=o>>>0>h>>>0?h:o;c[ba>>2]=h;c[P>>2]=n>>>24;if(O>>>0>167772159)c[j>>2]=2;if(l>>>0>268435455)c[m>>2]=2;if(n>>>0<=184549375)break;c[P>>2]=2}while(0);P=d[k+(h>>>3)>>0]|0;j=((h|0)<(o|0)&1)+h|0;c[ba>>2]=j;do if(!(P&128>>>(h&7)))m=j;else{h=k+(j>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(j&7);do if(h>>>0>134217727){h=h>>>23;l=(d[86681+h>>0]|0)+j|0;l=o>>>0>l>>>0?l:o;c[ba>>2]=l;h=d[87193+h>>0]|0;j=o}else{P=h>>>0>65535;O=P?h>>>16:h;P=P?16:0;l=(O&65280|0)==0;P=(d[97868+(l?O:O>>>8)>>0]|0)+(l?P:P|8)<<1;l=P+-31|0;j=63-P+j|0;j=o>>>0>j>>>0?j:o;c[ba>>2]=j;if((l|0)<7){UC(0,16,66830,Q);h=-1094995529;l=c[ba>>2]|0;k=c[$>>2]|0;j=c[aa>>2]|0;break}else{h=(h>>>l)+-1|0;l=j;j=o;break}}while(0);c[(c[g>>2]|0)+400>>2]=h+1;h=k+(l>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(l&7);if(h>>>0>134217727){m=(d[86681+(h>>>23)>>0]|0)+l|0;m=j>>>0>m>>>0?m:j;c[ba>>2]=m;break}Q=h>>>0>65535;P=Q?h>>>16:h;Q=Q?16:0;h=(P&65280|0)==0;Q=(d[97868+(h?P:P>>>8)>>0]|0)+(h?Q:Q|8)<<1;h=63-Q+l|0;h=j>>>0>h>>>0?h:j;c[ba>>2]=h;if((Q+-31|0)>=7){m=h;break}UC(0,16,66830,S);m=c[ba>>2]|0;k=c[$>>2]|0}while(0);j=k+(m>>>3)|0;j=d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24;l=m&7;do if((MF(j|0)|0)&-2147483648>>>l|0){h=e+708|0;if(((c[h>>2]|0)-m|0)>=10)break;UC(c[g>>2]|0,24,63346,T);j=c[ba>>2]|0;break e}while(0);T=(j&255)<<l>>>7&1;j=c[aa>>2]|0;h=((m|0)<(j|0)&1)+m|0;c[ba>>2]=h;n=ja+132|0;c[n>>2]=T;if(T){l=k+(h>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(h&7)&-65536;h=h+16|0;h=j>>>0>h>>>0?h:j;c[ba>>2]=h;m=k+(h>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(h&7)>>>16;h=h+16|0;h=j>>>0>h>>>0?h:j;c[ba>>2]=h;l=m|l;m=k+(h>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(h&7)&-65536;h=h+16|0;h=j>>>0>h>>>0?h:j;c[ba>>2]=h;T=k+(h>>>3)|0;T=(MF(d[T>>0]|d[T+1>>0]<<8|d[T+2>>0]<<16|d[T+3>>0]<<24|0)|0)<<(h&7)>>>16;h=h+16|0;h=j>>>0>h>>>0?h:j;c[ba>>2]=h;m=T|m;if((l|0)!=0&(m|0)!=0){c[ja+136>>2]=l;c[ja+140>>2]=m}else{h=c[g>>2]|0;c[U>>2]=m;c[U+4>>2]=l;UC(h,16,63361,U);c[n>>2]=0;h=c[ba>>2]|0;k=c[$>>2]|0;j=c[aa>>2]|0}U=d[k+(h>>>3)>>0]<<(h&7)>>>7&1;h=((h|0)<(j|0)&1)+h|0;c[ba>>2]=h;c[ja+144>>2]=U}U=d[k+(h>>>3)>>0]<<(h&7)>>>7&1;h=((h|0)<(j|0)&1)+h|0;c[ba>>2]=h;l=ja+1152|0;c[l>>2]=U;if(U){if((Mc(e,ja)|0)<0)break b;h=c[ba>>2]|0;k=c[$>>2]|0;j=c[aa>>2]|0}U=d[k+(h>>>3)>>0]<<(h&7)>>>7&1;c[ba>>2]=((h|0)<(j|0)&1)+h;h=ja+1156|0;c[h>>2]=U;if(U|0)if((Mc(e,ja)|0)<0)break b;do if(!(c[l>>2]|0)){if(c[h>>2]|0){R=158;break}h=c[ba>>2]|0;k=c[aa>>2]|0}else R=158;while(0);if((R|0)==158){h=c[ba>>2]|0;k=c[aa>>2]|0;h=((h|0)<(k|0)&1)+h|0;c[ba>>2]=h}m=c[$>>2]|0;U=d[m+(h>>>3)>>0]<<(h&7)>>>7&1;j=((h|0)<(k|0)&1)+h|0;c[ba>>2]=j;c[ja+1160>>2]=U;h=e+708|0;if((c[h>>2]|0)==(j|0))break;U=d[m+(j>>>3)>>0]<<(j&7)>>>7&1;j=((j|0)<(k|0)&1)+j|0;c[ba>>2]=j;p=ja+660|0;c[p>>2]=U;if(!U)break;j=((j|0)<(k|0)&1)+j|0;c[ba>>2]=j;l=m+(j>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(j&7);do if(l>>>0>134217727){j=(d[86681+(l>>>23)>>0]|0)+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j}else{U=l>>>0>65535;S=U?l>>>16:l;U=U?16:0;T=(S&65280|0)==0;U=(d[97868+(T?S:S>>>8)>>0]|0)+(T?U:U|8)<<1;j=63-U+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;if((U+-31|0)>=7)break;UC(0,16,66830,V);j=c[ba>>2]|0;m=c[$>>2]|0;k=c[aa>>2]|0}while(0);l=m+(j>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(j&7);do if(l>>>0>134217727){j=(d[86681+(l>>>23)>>0]|0)+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j}else{V=l>>>0>65535;T=V?l>>>16:l;V=V?16:0;U=(T&65280|0)==0;V=(d[97868+(U?T:T>>>8)>>0]|0)+(U?V:V|8)<<1;j=63-V+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;if((V+-31|0)>=7)break;UC(0,16,66830,W);j=c[ba>>2]|0;m=c[$>>2]|0;k=c[aa>>2]|0}while(0);l=m+(j>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(j&7);do if(l>>>0>134217727){j=(d[86681+(l>>>23)>>0]|0)+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j}else{W=l>>>0>65535;U=W?l>>>16:l;W=W?16:0;V=(U&65280|0)==0;W=(d[97868+(V?U:U>>>8)>>0]|0)+(V?W:W|8)<<1;j=63-W+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;if((W+-31|0)>=7)break;UC(0,16,66830,X);j=c[ba>>2]|0;m=c[$>>2]|0;k=c[aa>>2]|0}while(0);l=m+(j>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(j&7);do if(l>>>0>134217727){j=(d[86681+(l>>>23)>>0]|0)+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;o=m}else{X=l>>>0>65535;V=X?l>>>16:l;X=X?16:0;W=(V&65280|0)==0;X=(d[97868+(W?V:V>>>8)>>0]|0)+(W?X:X|8)<<1;j=63-X+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;if((X+-31|0)>=7){o=m;break}UC(0,16,66830,Y);j=c[ba>>2]|0;o=c[$>>2]|0;k=c[aa>>2]|0}while(0);l=o+(j>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(j&7);do if(l>>>0>134217727){m=l>>>23;n=(d[86681+m>>0]|0)+j|0;n=k>>>0>n>>>0?n:k;c[ba>>2]=n;m=d[87193+m>>0]|0;j=o}else{Y=l>>>0>65535;X=Y?l>>>16:l;Y=Y?16:0;m=(X&65280|0)==0;Y=(d[97868+(m?X:X>>>8)>>0]|0)+(m?Y:Y|8)<<1;m=Y+-31|0;j=63-Y+j|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;if((m|0)<7){UC(0,16,66830,Z);m=-1094995529;n=c[ba>>2]|0;j=c[$>>2]|0;k=c[aa>>2]|0;break}else{m=(l>>>m)+-1|0;n=j;j=o;break}}while(0);l=ja+664|0;c[l>>2]=m;j=j+(n>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(n&7);do if(j>>>0>134217727){j=(d[86681+(j>>>23)>>0]|0)+n|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j}else{aa=j>>>0>65535;$=aa?j>>>16:j;aa=aa?16:0;j=($&65280|0)==0;aa=(d[97868+(j?$:$>>>8)>>0]|0)+(j?aa:aa|8)<<1;j=63-aa+n|0;j=k>>>0>j>>>0?j:k;c[ba>>2]=j;if((aa+-31|0)>=7)break;UC(0,16,66830,ca);j=c[ba>>2]|0}while(0);if((c[h>>2]|0)<(j|0)){c[l>>2]=0;c[p>>2]=0;break}k=c[l>>2]|0;if(k>>>0<=16)break;e=c[g>>2]|0;c[da>>2]=k;UC(e,16,63444,da);c[l>>2]=16;break b}while(0);h=(c[h>>2]|0)-j|0;if((h|0)<0){da=c[g>>2]|0;f=(f|0)!=0;c[ea>>2]=c[A>>2]|0?63484:63488;c[ea+4>>2]=0-h;UC(da,f?24:16,63492,ea);if(!f)break}h=ja+104|0;if(!(c[h>>2]|0))c[h>>2]=1;z=c[g>>2]|0;if(c[z+688>>2]&1|0){y=c[fa>>2]|0;x=c[ga>>2]|0;w=c[w>>2]|0;v=c[v>>2]|0;u=c[u>>2]|0;t=c[t>>2]|0;if(!(c[s>>2]|0))s=c[ja+68>>2]|0?63516:63523;else s=63531;n=c[B>>2]|0?63535:190399;o=c[ja+80>>2]|0;p=c[ja+84>>2]|0;q=c[ja+88>>2]|0;r=c[ja+92>>2]|0;g=c[A>>2]|0?63484:190399;h=63539+((c[ja+12>>2]|0)*5|0)|0;if(!(c[ja+132>>2]|0)){k=0;l=0}else{k=c[ja+136>>2]|0;l=c[ja+140>>2]|0}j=c[ja+1184>>2]|0;if(!(c[ja+660>>2]|0))m=-1;else m=c[ja+664>>2]|0;c[ia>>2]=ha;c[ia+4>>2]=y;c[ia+8>>2]=x;c[ia+12>>2]=w;c[ia+16>>2]=v;c[ia+20>>2]=u;c[ia+24>>2]=t;c[ia+28>>2]=s;c[ia+32>>2]=n;c[ia+36>>2]=o;c[ia+40>>2]=p;c[ia+44>>2]=q;c[ia+48>>2]=r;c[ia+52>>2]=g;c[ia+56>>2]=h;c[ia+60>>2]=k;c[ia+64>>2]=l;c[ia+68>>2]=j;c[ia+72>>2]=m;UC(z,48,63559,ia)}c[ja+1200>>2]=1;e=e+198268+(ha<<2)|0;$C(c[e>>2]|0);c[e>>2]=ja;e=0;i=ka;return e|0}}UC(c[g>>2]|0,16,63123,F);break}e=c[e+4>>2]|0;c[w>>2]=g;UC(e,16,62952,w)}while(0);$C(ja);e=-1094995529;i=ka;return e|0}function Lc(b,e,f,g,h,i){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;q=(g|0)!=0;if(q)l=0;else l=(c[e+668>>2]|0)!=0;r=l?e+768|0:62792;s=l?e+960|0:62856;v=b+704|0;p=c[v>>2]|0;u=b+696|0;k=c[u>>2]|0;o=d[k+(p>>>3)>>0]|0;t=b+712|0;j=c[t>>2]|0;b=((p|0)<(j|0)&1)+p|0;c[v>>2]=b;if(!(o&128>>>(p&7)))return;p=l?e+720|0:62936;o=e+668|0;c[o>>2]=c[o>>2]|g;o=d[k+(b>>>3)>>0]|0;c[v>>2]=((b|0)<(j|0)&1)+b;a:do if(!(o&128>>>(b&7))){n=h;b=l?e+672|0:62920;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){o=g>>>23;b=(d[86681+o>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+o>>0]|0}else{n=g>>>0>65535;g=n?g>>>16:g;n=n?16:0;o=(g&65280|0)==0;n=(d[97868+(o?g:g>>>8)>>0]|0)+(o?n:n|8)|0;b=31-n+b|0;b=j>>>0>b>>>0?b:j;o=k+(b>>>3)|0;o=(MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0)<<(b&7)>>>n;b=b+(32-n)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=o&1;b=(o>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[h+(d[66214+l>>0]|0)>>0]=m;l=l+1|0;if((l|0)>=16)break a}n=h;b=62920;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);o=h+16|0;n=c[v>>2]|0;m=d[(c[u>>2]|0)+(n>>>3)>>0]|0;c[v>>2]=((n|0)<(c[t>>2]|0)&1)+n;b:do if(!(m&128>>>(n&7))){n=o;b=h;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){n=g>>>23;b=(d[86681+n>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+n>>0]|0}else{n=g>>>0>65535;x=n?g>>>16:g;g=n?16:0;n=(x&65280|0)==0;g=(d[97868+(n?x:x>>>8)>>0]|0)+(n?g:g|8)|0;b=31-g+b|0;b=j>>>0>b>>>0?b:j;n=k+(b>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(b&7)>>>g;b=b+(32-g)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=n&1;b=(n>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[66214+l>>0]|0)+(h+16)>>0]=m;l=l+1|0;if((l|0)>=16)break b}n=o;b=62920;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);n=h+32|0;x=c[v>>2]|0;m=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;c:do if(!(m&128>>>(x&7))){b=o;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{o=g>>>0>65535;g=o?g>>>16:g;o=o?16:0;x=(g&65280|0)==0;o=(d[97868+(x?g:g>>>8)>>0]|0)+(x?o:o|8)|0;b=31-o+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>o;b=b+(32-o)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[66214+l>>0]|0)+(h+32)>>0]=m;l=l+1|0;if((l|0)>=16)break c}b=62920;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);o=h+48|0;x=c[v>>2]|0;n=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;d:do if(!(n&128>>>(x&7))){n=o;b=p;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{p=g>>>0>65535;n=p?g>>>16:g;p=p?16:0;x=(n&65280|0)==0;p=(d[97868+(x?n:n>>>8)>>0]|0)+(x?p:p|8)|0;b=31-p+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>p;b=b+(32-p)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[66214+l>>0]|0)+(h+48)>>0]=m;l=l+1|0;if((l|0)>=16)break d}n=o;b=62936;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);p=h+64|0;x=c[v>>2]|0;n=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;e:do if(!(n&128>>>(x&7))){n=p;b=o;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{o=g>>>0>65535;n=o?g>>>16:g;o=o?16:0;x=(n&65280|0)==0;o=(d[97868+(x?n:n>>>8)>>0]|0)+(x?o:o|8)|0;b=31-o+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>o;b=b+(32-o)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[66214+l>>0]|0)+(h+64)>>0]=m;l=l+1|0;if((l|0)>=16)break e}n=p;b=62936;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);n=h+80|0;x=c[v>>2]|0;o=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;f:do if(!(o&128>>>(x&7))){b=p;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{p=g>>>0>65535;o=p?g>>>16:g;p=p?16:0;x=(o&65280|0)==0;p=(d[97868+(x?o:o>>>8)>>0]|0)+(x?p:p|8)|0;b=31-p+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>p;b=b+(32-p)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[66214+l>>0]|0)+(h+80)>>0]=m;l=l+1|0;if((l|0)>=16)break f}b=62936;j=n+16|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);if(!q)if(!(c[f+64>>2]|0))return;x=c[v>>2]|0;h=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;g:do if(!(h&128>>>(x&7))){n=i;b=r;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{h=g>>>0>65535;f=h?g>>>16:g;h=h?16:0;x=(f&65280|0)==0;h=(d[97868+(x?f:f>>>8)>>0]|0)+(x?h:h|8)|0;b=31-h+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>h;b=b+(32-h)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[i+(d[71512+l>>0]|0)>>0]=m;l=l+1|0;if((l|0)>=64)break g}n=i;b=62792;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);p=i+192|0;x=c[v>>2]|0;h=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;h:do if(!(h&128>>>(x&7))){n=p;b=s;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{s=g>>>0>65535;h=s?g>>>16:g;s=s?16:0;x=(h&65280|0)==0;s=(d[97868+(x?h:h>>>8)>>0]|0)+(x?s:s|8)|0;b=31-s+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>s;b=b+(32-s)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[71512+l>>0]|0)+(i+192)>>0]=m;l=l+1|0;if((l|0)>=64)break h}n=p;b=62856;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);if((c[e+12>>2]|0)!=3)return;o=i+64|0;x=c[v>>2]|0;e=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;i:do if(!(e&128>>>(x&7))){n=o;b=i;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{e=g>>>0>65535;s=e?g>>>16:g;e=e?16:0;x=(s&65280|0)==0;e=(d[97868+(x?s:s>>>8)>>0]|0)+(x?e:e|8)|0;b=31-e+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>e;b=b+(32-e)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[71512+l>>0]|0)+(i+64)>>0]=m;l=l+1|0;if((l|0)>=64)break i}n=o;b=62792;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);q=i+256|0;x=c[v>>2]|0;e=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;j:do if(!(e&128>>>(x&7))){n=q;b=p;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{e=g>>>0>65535;s=e?g>>>16:g;e=e?16:0;x=(s&65280|0)==0;e=(d[97868+(x?s:s>>>8)>>0]|0)+(x?e:e|8)|0;b=31-e+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>e;b=b+(32-e)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[71512+l>>0]|0)+(i+256)>>0]=m;l=l+1|0;if((l|0)>=64)break j}n=q;b=62856;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);n=i+128|0;x=c[v>>2]|0;e=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;k:do if(!(e&128>>>(x&7))){b=o;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}else{l=0;m=8;b=8;while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{e=g>>>0>65535;s=e?g>>>16:g;e=e?16:0;x=(s&65280|0)==0;e=(d[97868+(x?s:s>>>8)>>0]|0)+(x?e:e|8)|0;b=31-e+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>e;b=b+(32-e)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[71512+l>>0]|0)+(i+128)>>0]=m;l=l+1|0;if((l|0)>=64)break k}b=62792;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0))}while(0);n=i+320|0;x=c[v>>2]|0;e=d[(c[u>>2]|0)+(x>>>3)>>0]|0;c[v>>2]=((x|0)<(c[t>>2]|0)&1)+x;if(!(e&128>>>(x&7))){b=q;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0));return}else{l=0;m=8;b=8}while(1){if(!b)b=0;else{b=c[v>>2]|0;j=c[t>>2]|0;k=c[u>>2]|0;g=k+(b>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(b&7);if(g>>>0>134217727){x=g>>>23;b=(d[86681+x>>0]|0)+b|0;c[v>>2]=j>>>0>b>>>0?b:j;b=a[87705+x>>0]|0}else{e=g>>>0>65535;s=e?g>>>16:g;e=e?16:0;x=(s&65280|0)==0;e=(d[97868+(x?s:s>>>8)>>0]|0)+(x?e:e|8)|0;b=31-e+b|0;b=j>>>0>b>>>0?b:j;x=k+(b>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(b&7)>>>e;b=b+(32-e)|0;c[v>>2]=j>>>0>b>>>0?b:j;b=x&1;b=(x>>>1^0-b)+b|0}b=b+m&255}if(!(b|l))break;m=b|0?b:m;a[(d[71512+l>>0]|0)+(i+320)>>0]=m;l=l+1|0;if((l|0)>=64){w=127;break}}if((w|0)==127)return;b=62856;j=n+64|0;do{a[n>>0]=a[b>>0]|0;n=n+1|0;b=b+1|0}while((n|0)<(j|0));return}function Mc(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;m=i;i=i+16|0;f=m;h=a+704|0;e=c[h>>2]|0;k=c[a+712>>2]|0;l=c[a+696>>2]|0;j=l+(e>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(e&7)>>>23;e=(d[86681+j>>0]|0)+e|0;e=k>>>0>e>>>0?e:k;c[h>>2]=e;j=(d[87193+j>>0]|0)+1|0;if(j>>>0>32){b=c[a+4>>2]|0;c[f>>2]=j;UC(b,16,63422,f);b=-1094995529;i=m;return b|0}e=e+4|0;e=(k>>>0>e>>>0?e:k)+4|0;e=k>>>0>e>>>0?e:k;c[h>>2]=e;g=0;do{n=l+(e>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(e&7);a=e+16|0;a=k>>>0>a>>>0?a:k;f=l+(a>>>3)|0;a=(MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0)<<(a&7)>>>16|n&-65536;f=a>>>0>65535;a=f?n>>>16:a;f=f?16:0;n=(a&65280|0)==0;f=31-(d[97868+(n?a:a>>>8)>>0]|0)-(n?f:f|8)|0;n=0-e|0;a=k-e|0;e=((f|0)<(n|0)?n:(a|0)<(f|0)?a:f)+e|0;c[h>>2]=e;a=f+1|0;do if(a)if((a|0)<26){e=e+a|0;e=k>>>0>e>>>0?e:k;c[h>>2]=e;break}else{e=e+16|0;e=f+-15+(k>>>0>e>>>0?e:k)|0;e=k>>>0>e>>>0?e:k;c[h>>2]=e;break}while(0);n=l+(e>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(e&7);a=e+16|0;a=k>>>0>a>>>0?a:k;f=l+(a>>>3)|0;a=(MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0)<<(a&7)>>>16|n&-65536;f=a>>>0>65535;a=f?n>>>16:a;f=f?16:0;n=(a&65280|0)==0;f=31-(d[97868+(n?a:a>>>8)>>0]|0)-(n?f:f|8)|0;n=0-e|0;a=k-e|0;e=((f|0)<(n|0)?n:(a|0)<(f|0)?a:f)+e|0;c[h>>2]=e;a=f+1|0;do if(a)if((a|0)<26){e=e+a|0;e=k>>>0>e>>>0?e:k;c[h>>2]=e;break}else{e=e+16|0;e=f+-15+(k>>>0>e>>>0?e:k)|0;e=k>>>0>e>>>0?e:k;c[h>>2]=e;break}while(0);e=((e|0)<(k|0)&1)+e|0;c[h>>2]=e;g=g+1|0}while((g|0)!=(j|0));n=l+(e>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(e&7)>>>27;g=e+5|0;g=k>>>0>g>>>0?g:k;c[h>>2]=g;c[b+1172>>2]=n+1;n=l+(g>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(g&7)>>>27;g=g+5|0;g=k>>>0>g>>>0?g:k;c[h>>2]=g;c[b+1176>>2]=n+1;n=l+(g>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(g&7)>>>27;g=g+5|0;g=k>>>0>g>>>0?g:k;c[h>>2]=g;c[b+1180>>2]=n+1;n=l+(g>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(g&7)>>>27;l=g+5|0;c[h>>2]=k>>>0>l>>>0?l:k;c[b+1164>>2]=n;c[b+1168>>2]=j;n=0;i=m;return n|0}function Nc(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;L=i;i=i+160|0;K=L+96|0;C=L+88|0;v=L+80|0;u=L+72|0;t=L+64|0;s=L+56|0;r=L+48|0;q=L+40|0;o=L+32|0;n=L+24|0;m=L+16|0;l=L+8|0;k=L;G=b+704|0;f=c[G>>2]|0;D=b+712|0;h=c[D>>2]|0;E=b+696|0;g=(c[E>>2]|0)+(f>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(f&7);do if(g>>>0>134217727){J=g>>>23;f=(d[86681+J>>0]|0)+f|0;c[G>>2]=h>>>0>f>>>0?f:h;f=d[87193+J>>0]|0}else{J=g>>>0>65535;H=J?g>>>16:g;J=J?16:0;j=(H&65280|0)==0;J=(d[97868+(j?H:H>>>8)>>0]|0)+(j?J:J|8)<<1;j=J+-31|0;J=63-J+f|0;c[G>>2]=h>>>0>J>>>0?J:h;if((j|0)<7){UC(0,16,66830,k);f=-1094995529}else{f=(g>>>j)+-1|0;if(f>>>0<=255)break}b=c[b+4>>2]|0;c[l>>2]=f;UC(b,16,66854,l);b=-1094995529;i=L;return b|0}while(0);J=cD(728)|0;if(!J){b=-12;i=L;return b|0}j=c[G>>2]|0;p=c[D>>2]|0;l=c[E>>2]|0;H=l+(j>>>3)|0;H=(MF(d[H>>0]|d[H+1>>0]<<8|d[H+2>>0]<<16|d[H+3>>0]<<24|0)|0)<<(j&7)>>>23;j=(d[86681+H>>0]|0)+j|0;j=p>>>0>j>>>0?j:p;c[G>>2]=j;H=a[87193+H>>0]|0;g=H&255;c[J>>2]=g;a:do if((H&255)>31)I=10;else{h=c[b+198268+(g<<2)>>2]|0;if(!h)I=10;else{F=h+1184|0;g=c[F>>2]|0;if((g|0)>14){f=c[b+4>>2]|0;c[n>>2]=g;UC(f,16,63647,n);f=-1094995529;break}switch(g|0){case 13:case 11:{f=c[b+4>>2]|0;c[o>>2]=g;UC(f,16,63674,o);f=-1163346256;break a}default:{}}B=d[l+(j>>>3)>>0]<<(j&7)>>>7&1;g=((j|0)<(p|0)&1)+j|0;c[G>>2]=g;H=J+4|0;c[H>>2]=B;j=d[l+(g>>>3)>>0]<<(g&7)>>>7&1;g=((g|0)<(p|0)&1)+g|0;c[G>>2]=g;c[J+8>>2]=j;j=l+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);do if(j>>>0>134217727){I=j>>>23;j=(d[86681+I>>0]|0)+g|0;j=p>>>0>j>>>0?j:p;c[G>>2]=j;g=d[87193+I>>0]|0;I=20}else{h=j>>>0>65535;B=h?j>>>16:j;h=h?16:0;k=(B&65280|0)==0;h=(d[97868+(k?B:B>>>8)>>0]|0)+(k?h:h|8)<<1;k=h+-31|0;h=63-h+g|0;h=p>>>0>h>>>0?h:p;c[G>>2]=h;if((k|0)<7){UC(0,16,66830,q);B=J+12|0;c[B>>2]=-1094995528;break}else{g=(j>>>k)+-1|0;j=h;I=20;break}}while(0);if((I|0)==20){k=J+12|0;c[k>>2]=g+1;if((g|0)>0){g=l+(j>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(j&7);do if(g>>>0>134217727){g=g>>>23;B=(d[86681+g>>0]|0)+j|0;c[G>>2]=p>>>0>B>>>0?B:p;g=d[87193+g>>0]|0}else{B=g>>>0>65535;A=B?g>>>16:g;B=B?16:0;h=(A&65280|0)==0;B=(d[97868+(h?A:A>>>8)>>0]|0)+(h?B:B|8)<<1;h=B+-31|0;B=63-B+j|0;c[G>>2]=p>>>0>B>>>0?B:p;if((h|0)<7){UC(0,16,66830,r);g=-1094995529;break}else{g=(g>>>h)+-1|0;break}}while(0);c[J+16>>2]=g;UC(c[b+4>>2]|0,16,63707,s);B=k}else B=k}g=c[G>>2]|0;m=c[D>>2]|0;l=c[E>>2]|0;j=l+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7);do if(j>>>0>134217727){h=j>>>23;n=(d[86681+h>>0]|0)+g|0;n=m>>>0>n>>>0?n:m;c[G>>2]=n;g=d[87193+h>>0]|0;h=l;k=m}else{h=j>>>0>65535;A=h?j>>>16:j;h=h?16:0;k=(A&65280|0)==0;h=(d[97868+(k?A:A>>>8)>>0]|0)+(k?h:h|8)<<1;k=h+-31|0;h=63-h+g|0;h=m>>>0>h>>>0?h:m;c[G>>2]=h;if((k|0)<7){UC(0,16,66830,t);g=-1094995529;n=c[G>>2]|0;h=c[E>>2]|0;k=c[D>>2]|0;break}else{g=(j>>>k)+-1|0;n=h;h=l;k=m;break}}while(0);g=g+1|0;A=J+20|0;c[A>>2]=g;h=h+(n>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(n&7);do if(h>>>0>134217727){h=h>>>23;z=(d[86681+h>>0]|0)+n|0;c[G>>2]=k>>>0>z>>>0?z:k;h=d[87193+h>>0]|0}else{z=h>>>0>65535;y=z?h>>>16:h;z=z?16:0;j=(y&65280|0)==0;z=(d[97868+(j?y:y>>>8)>>0]|0)+(j?z:z|8)<<1;j=z+-31|0;z=63-z+n|0;c[G>>2]=k>>>0>z>>>0?z:k;if((j|0)<7){UC(0,16,66830,u);h=-1094995529;g=c[A>>2]|0;break}else{h=(h>>>j)+-1|0;break}}while(0);z=J+24|0;c[z>>2]=h+1;if((g+-1|h)>>>0>31){UC(c[b+4>>2]|0,16,63726,v);f=-1094995529;break}j=(c[F>>2]|0)*6|0;g=c[G>>2]|0;p=c[E>>2]|0;h=d[p+(g>>>3)>>0]<<(g&7)>>>7&1;q=c[D>>2]|0;g=((g|0)<(q|0)&1)+g|0;c[G>>2]=g;y=J+28|0;c[y>>2]=h;h=p+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7)>>>30;g=g+2|0;g=q>>>0>g>>>0?g:q;c[G>>2]=g;c[J+32>>2]=h;h=p+(g>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(g&7);if(h>>>0>134217727){x=h>>>23;h=(d[86681+x>>0]|0)+g|0;h=q>>>0>h>>>0?h:q;c[G>>2]=h;g=a[87705+x>>0]|0}else{x=h>>>0>65535;w=x?h>>>16:h;h=x?16:0;x=(w&65280|0)==0;h=(d[97868+(x?w:w>>>8)>>0]|0)+(x?h:h|8)|0;g=31-h+g|0;g=q>>>0>g>>>0?g:q;x=p+(g>>>3)|0;x=(MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0)<<(g&7)>>>h;h=g+(32-h)|0;h=q>>>0>h>>>0?h:q;c[G>>2]=h;g=x&1;g=(x>>>1^0-g)+g|0}j=j+-22|0;x=J+36|0;c[x>>2]=j+g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){g=g>>>23;h=(d[86681+g>>0]|0)+h|0;h=q>>>0>h>>>0?h:q;c[G>>2]=h;g=a[87705+g>>0]|0}else{w=g>>>0>65535;v=w?g>>>16:g;g=w?16:0;w=(v&65280|0)==0;g=(d[97868+(w?v:v>>>8)>>0]|0)+(w?g:g|8)|0;h=31-g+h|0;h=q>>>0>h>>>0?h:q;w=p+(h>>>3)|0;w=(MF(d[w>>0]|d[w+1>>0]<<8|d[w+2>>0]<<16|d[w+3>>0]<<24|0)|0)<<(h&7)>>>g;h=h+(32-g)|0;h=q>>>0>h>>>0?h:q;c[G>>2]=h;g=w&1;g=(w>>>1^0-g)+g|0}w=J+40|0;c[w>>2]=j+g;g=p+(h>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(h&7);if(g>>>0>134217727){g=g>>>23;h=(d[86681+g>>0]|0)+h|0;h=q>>>0>h>>>0?h:q;c[G>>2]=h;g=a[87705+g>>0]|0}else{v=g>>>0>65535;u=v?g>>>16:g;g=v?16:0;v=(u&65280|0)==0;g=(d[97868+(v?u:u>>>8)>>0]|0)+(v?g:g|8)|0;h=31-g+h|0;h=q>>>0>h>>>0?h:q;v=p+(h>>>3)|0;v=(MF(d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24|0)|0)<<(h&7)>>>g;h=h+(32-g)|0;h=q>>>0>h>>>0?h:q;c[G>>2]=h;g=v&1;g=(v>>>1^0-g)+g|0}r=J+44|0;c[r>>2]=g;v=d[p+(h>>>3)>>0]<<(h&7)>>>7&1;l=((h|0)<(q|0)&1)+h|0;c[G>>2]=l;s=J+52|0;c[s>>2]=v;v=d[p+(l>>>3)>>0]<<(l&7)>>>7&1;l=((l|0)<(q|0)&1)+l|0;c[G>>2]=l;t=J+56|0;c[t>>2]=v;v=d[p+(l>>>3)>>0]<<(l&7)>>>7&1;l=((l|0)<(q|0)&1)+l|0;c[G>>2]=l;u=J+60|0;c[u>>2]=v;v=J+64|0;c[v>>2]=0;c[b+199420>>2]=-1;m=J+68|0;n=b+198268+(c[J>>2]<<2)|0;o=c[n>>2]|0;h=m;j=o+672|0;k=h+96|0;do{c[h>>2]=c[j>>2];h=h+4|0;j=j+4|0}while((h|0)<(k|0));h=J+164|0;KF(h|0,o+768|0,384)|0;b:do if((l|0)<(e|0)){switch(c[o+4>>2]|0){case 66:case 77:case 88:{if(c[o+1196>>2]&7|0){UC(c[b+4>>2]|0,40,63752,C);g=c[r>>2]|0;I=56;break b}break}default:{}}k=d[p+(l>>>3)>>0]<<(l&7)>>>7&1;c[G>>2]=((l|0)<(q|0)&1)+l;c[v>>2]=k;Lc(b,c[n>>2]|0,J,0,m,h);k=c[G>>2]|0;j=c[D>>2]|0;g=c[E>>2]|0;h=g+(k>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(k&7);if(h>>>0>134217727){g=h>>>23;E=(d[86681+g>>0]|0)+k|0;c[G>>2]=j>>>0>E>>>0?E:j;g=a[87705+g>>0]|0}else{D=h>>>0>65535;E=D?h>>>16:h;D=D?16:0;C=(E&65280|0)==0;D=(d[97868+(C?E:E>>>8)>>0]|0)+(C?D:D|8)|0;C=31-D+k|0;C=j>>>0>C>>>0?C:j;E=g+(C>>>3)|0;E=(MF(d[E>>0]|d[E+1>>0]<<8|d[E+2>>0]<<16|d[E+3>>0]<<24|0)|0)<<(C&7)>>>D;g=C+(32-D)|0;c[G>>2]=j>>>0>g>>>0?g:j;g=E&1;g=(E>>>1^0-g)+g|0}c[J+48>>2]=g;l=c[r>>2]|0}else I=56;while(0);if((I|0)==56){c[J+48>>2]=g;l=g}h=c[F>>2]|0;j=h+-8|0;I=j*6|0;k=I+51|0;if((I|0)>-52){g=0;while(1){I=g+l|0;a[J+548+g>>0]=a[((I|0)<0?0:(I|0)>(k|0)?k:I)+(62020+(j*88|0))>>0]|0;if((g|0)<(k|0))g=g+1|0;else break}g=J+48|0;l=g;h=c[F>>2]|0;g=c[g>>2]|0}else l=J+48|0;h=h+-8|0;I=h*6|0;j=I+51|0;if((I|0)>-52){k=0;while(1){I=k+g|0;a[J+636+k>>0]=a[((I|0)<0?0:(I|0)>(j|0)?j:I)+(62020+(h*88|0))>>0]|0;if((k|0)<(j|0))k=k+1|0;else break}}j=c[r>>2]|0;g=c[l>>2]|0;if((j|0)!=(g|0))c[J+724>>2]=1;h=c[b+4>>2]|0;if(c[h+688>>2]&1|0){p=c[J>>2]|0;q=c[H>>2]|0?63817:63823;r=c[B>>2]|0;B=c[A>>2]|0;e=c[z>>2]|0;C=c[y>>2]|0?63829:190399;D=c[x>>2]|0;E=c[w>>2]|0;F=c[s>>2]|0?63838:190399;G=c[t>>2]|0?63843:190399;H=c[u>>2]|0?63850:190399;I=c[v>>2]|0?63855:190399;c[K>>2]=f;c[K+4>>2]=p;c[K+8>>2]=q;c[K+12>>2]=r;c[K+16>>2]=B;c[K+20>>2]=e;c[K+24>>2]=C;c[K+28>>2]=D;c[K+32>>2]=E;c[K+36>>2]=j;c[K+40>>2]=g;c[K+44>>2]=F;c[K+48>>2]=G;c[K+52>>2]=H;c[K+56>>2]=I;UC(h,48,63862,K)}b=b+198396+(f<<2)|0;$C(c[b>>2]|0);c[b>>2]=J;b=0;i=L;return b|0}}while(0);if((I|0)==10){f=c[b+4>>2]|0;c[m>>2]=g;UC(f,16,62636,m);f=-1094995529}$C(J);b=f;i=L;return b|0}function Oc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0;B=i;i=i+240|0;A=B+48|0;d=B+32|0;w=B+16|0;v=B;u=B+104|0;x=B+96|0;y=B+56|0;z=b+816|0;do if((c[z>>2]|0)==3){r=a+197612|0;s=c[r>>2]|0;p=c[a+24620>>2]|0;s=c[((s|0)==3?p+80|0:p+72+(((s|0)==2&1)<<2)|0)>>2]|0;p=a+203244|0;q=a+202168|0;t=0;while(1){o=c[p>>2]|0;k=t^1;l=(t|0)!=1?-2147483648:2147483647;a:do if((o|0)>0){h=s;e=0;while(1){m=u+(e<<2)|0;g=l;j=0;while(1){f=c[a+202040+(j<<2)>>2]|0;d=c[f+80>>2]|0;if(((d|0)>(h|0)|0)==(k|0))d=g;else if(((d|0)<(g|0)|0)==(k|0))d=g;else c[m>>2]=f;j=j+1|0;if((j|0)==(o|0))break;else g=d}if((d|0)==(l|0))break;h=(c[(c[m>>2]|0)+80>>2]|0)-k|0;e=e+1|0}l=t|0?-2147483648:2147483647;j=s;d=0;while(1){n=m+(d<<2)|0;h=l;k=0;while(1){g=c[a+202040+(k<<2)>>2]|0;f=c[g+80>>2]|0;if(((f|0)>(j|0)|0)==(t|0))f=h;else if(((f|0)<(h|0)|0)==(t|0))f=h;else c[n>>2]=g;k=k+1|0;if((k|0)==(o|0))break;else h=f}if((f|0)==(l|0))break a;j=(c[(c[n>>2]|0)+80>>2]|0)-t|0;d=d+1|0}}else{e=0;d=0}while(0);d=d+e|0;if((d|0)>=33){d=20;break}d=Pc(a+199480+(t*1280|0)|0,32,u,d,0,c[r>>2]|0)|0;d=(Pc(a+199480+(t*1280|0)+(d*40|0)|0,32-d|0,q,16,1,c[r>>2]|0)|0)+d|0;if((d|0)>=33){d=22;break}e=c[b+23372+(t<<2)>>2]|0;if(e>>>0>d>>>0)FF(a+199480+(t*1280|0)+(d*40|0)|0,0,(e-d|0)*40|0)|0;c[x+(t<<2)>>2]=d;t=t+1|0;if((t|0)>=2){d=26;break}}if((d|0)==20){c[v>>2]=63936;c[v+4>>2]=63946;c[v+8>>2]=151;UC(0,0,104466,v);Ya()}else if((d|0)==22){c[w>>2]=63936;c[w+4>>2]=63946;c[w+8>>2]=158;UC(0,0,104466,w);Ya()}else if((d|0)==26){e=c[x>>2]|0;x=c[x+4>>2]|0;if((e|0)==(x|0)&(x|0)>1){b:do if((e|0)>0){d=0;do{if((c[c[(c[c[a+199480+(d*40|0)+36>>2]>>2]|0)+360>>2]>>2]|0)!=(c[c[(c[c[a+200760+(d*40|0)+36>>2]>>2]|0)+360>>2]>>2]|0))break b;d=d+1|0}while((d|0)<(e|0))}else d=0;while(0);if((d|0)==(e|0)){e=a+200800|0;g=y;f=e;h=g+40|0;do{c[g>>2]=c[f>>2];g=g+4|0;f=f+4|0}while((g|0)<(h|0));d=a+200760|0;g=e;f=d;h=g+40|0;do{c[g>>2]=c[f>>2];g=g+4|0;f=f+4|0}while((g|0)<(h|0));g=d;f=y;h=g+40|0;do{c[g>>2]=c[f>>2];g=g+4|0;f=f+4|0}while((g|0)<(h|0))}}break}}else{y=a+197612|0;e=Pc(a+199480|0,32,a+202040|0,c[a+203244>>2]|0,0,c[y>>2]|0)|0;e=(Pc(a+199480+(e*40|0)|0,32-e|0,a+202168|0,16,1,c[y>>2]|0)|0)+e|0;if((e|0)>=33){c[d>>2]=63936;c[d+4>>2]=63946;c[d+8>>2]=179;UC(0,0,104466,d);Ya()}d=c[b+23372>>2]|0;if(d>>>0>e>>>0)FF(a+199480+(e*40|0)|0,0,(d-e|0)*40|0)|0}while(0);m=a+24620|0;k=a+4|0;n=0;do{l=b+23372+(n<<2)|0;d=c[l>>2]|0;if(d|0){j=0;do{g=a+199480+(n*1280|0)+(j*40|0)|0;e=c[a+199480+(n*1280|0)+(j*40|0)+36>>2]|0;do if(e){e=c[e>>2]|0;f=c[c[m>>2]>>2]|0;if((c[f+68>>2]|0)==(c[e+68>>2]|0))if((c[f+72>>2]|0)==(c[e+72>>2]|0))if((c[f+80>>2]|0)==(c[e+80>>2]|0))break;UC(c[k>>2]|0,16,63985,A);h=g+40|0;do{c[g>>2]=0;g=g+4|0}while((g|0)<(h|0));d=c[l>>2]|0}while(0);j=j+1|0}while(j>>>0<d>>>0)}n=n+1|0}while((n|0)<(((c[z>>2]|0)==3?2:1)|0));i=B;return 0}function Pc(a,b,d,e,f,g){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0;E=i;i=i+32|0;D=E+16|0;C=E;if((e|0)<=0){D=0;i=E;return D|0}u=(f|0)==0;v=(g|0)==3;w=(g|0)==2;x=w&1;y=g^3;z=(g|0)==0;A=(g|0)==1;B=A&1;h=0;j=0;f=0;while(1){a:do if((h|0)<(e|0)){k=h;while(1){h=c[d+(k<<2)>>2]|0;if(h|0)if(c[h+636>>2]&g|0){l=1;h=k;break a}h=k+1|0;if((h|0)<(e|0))k=h;else{l=0;break}}}else l=0;while(0);b:do if((j|0)<(e|0)){k=j;while(1){j=c[d+(k<<2)>>2]|0;if(j|0)if(c[j+636>>2]&y|0){t=1;j=k;break b}j=k+1|0;if((j|0)<(e|0))k=j;else{t=0;break}}}else t=0;while(0);if(l){if((f|0)>=(b|0)){h=13;break}r=c[d+(h<<2)>>2]|0;if(u)k=c[r+84>>2]|0;else k=h;l=r+92|0;c[l>>2]=k;s=f+1|0;q=a+(f*40|0)|0;h=h+1|0;k=r+636|0;if(c[k>>2]&g){o=c[r>>2]|0;c[q>>2]=c[o>>2];c[q+4>>2]=c[o+4>>2];c[q+8>>2]=c[o+8>>2];o=a+(f*40|0)+12|0;p=(c[r>>2]|0)+32|0;c[o>>2]=c[p>>2];c[o+4>>2]=c[p+4>>2];c[o+8>>2]=c[p+8>>2];p=a+(f*40|0)+24|0;c[p>>2]=c[k>>2];n=a+(f*40|0)+28|0;c[n>>2]=c[r+80>>2];k=c[l>>2]|0;l=a+(f*40|0)+32|0;c[l>>2]=k;c[a+(f*40|0)+36>>2]=r;if(!v){m=c[o>>2]|0;if(w){c[q>>2]=(c[q>>2]|0)+m;c[o>>2]=m<<1;q=a+(f*40|0)+16|0;o=c[q>>2]|0;m=a+(f*40|0)+4|0;c[m>>2]=(c[m>>2]|0)+o;c[q>>2]=o<<1;q=a+(f*40|0)+20|0;o=c[q>>2]|0;m=a+(f*40|0)+8|0;c[m>>2]=(c[m>>2]|0)+o;c[p>>2]=2;c[q>>2]=o<<1}else{c[o>>2]=m<<1;q=a+(f*40|0)+16|0;c[q>>2]=c[q>>2]<<1;c[p>>2]=g;q=a+(f*40|0)+20|0;c[q>>2]=c[q>>2]<<1}c[n>>2]=c[r+72+(x<<2)>>2];c[l>>2]=k<<1|1}}}else s=f;if(t){if((s|0)>=(b|0)){h=25;break}r=c[d+(j<<2)>>2]|0;if(u)f=c[r+84>>2]|0;else f=j;l=r+92|0;c[l>>2]=f;f=s+1|0;q=a+(s*40|0)|0;j=j+1|0;k=r+636|0;if(c[k>>2]&y){p=c[r>>2]|0;c[q>>2]=c[p>>2];c[q+4>>2]=c[p+4>>2];c[q+8>>2]=c[p+8>>2];p=a+(s*40|0)+12|0;o=(c[r>>2]|0)+32|0;c[p>>2]=c[o>>2];c[p+4>>2]=c[o+4>>2];c[p+8>>2]=c[o+8>>2];o=a+(s*40|0)+24|0;c[o>>2]=c[k>>2];n=a+(s*40|0)+28|0;c[n>>2]=c[r+80>>2];k=c[l>>2]|0;l=a+(s*40|0)+32|0;c[l>>2]=k;c[a+(s*40|0)+36>>2]=r;if(!z){m=c[p>>2]|0;if(A){c[q>>2]=(c[q>>2]|0)+m;c[p>>2]=m<<1;t=a+(s*40|0)+16|0;q=c[t>>2]|0;p=a+(s*40|0)+4|0;c[p>>2]=(c[p>>2]|0)+q;c[t>>2]=q<<1;t=a+(s*40|0)+20|0;q=c[t>>2]|0;s=a+(s*40|0)+8|0;c[s>>2]=(c[s>>2]|0)+q;c[o>>2]=2;c[t>>2]=q<<1}else{c[p>>2]=m<<1;t=a+(s*40|0)+16|0;c[t>>2]=c[t>>2]<<1;c[o>>2]=y;t=a+(s*40|0)+20|0;c[t>>2]=c[t>>2]<<1}c[n>>2]=c[r+72+(B<<2)>>2];c[l>>2]=k<<1}}}else f=s;if(!((j|0)<(e|0)|(h|0)<(e|0))){h=34;break}}if((h|0)==13){c[C>>2]=63969;c[C+4>>2]=63946;c[C+8>>2]=89;UC(0,0,104466,C);Ya()}else if((h|0)==25){c[D>>2]=63969;c[D+4>>2]=63946;c[D+8>>2]=94;UC(0,0,104466,D);Ya()}else if((h|0)==34){i=E;return f|0}return 0}function Qc(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0;R=i;i=i+128|0;P=R+112|0;O=R+104|0;K=R+96|0;L=R+88|0;G=R+80|0;F=R+72|0;E=R+64|0;D=R+56|0;C=R+48|0;k=R+32|0;j=R+24|0;h=R+8|0;Q=b+4|0;f=c[Q>>2]|0;if(c[f+688>>2]&2048|0){UC(f,48,64019,R);f=b+203244|0;if(c[f>>2]|0){g=0;do{J=c[b+202040+(g<<2)>>2]|0;M=c[Q>>2]|0;H=c[J+84>>2]|0;I=c[J+80>>2]|0;J=c[c[J>>2]>>2]|0;c[h>>2]=g;c[h+4>>2]=H;c[h+8>>2]=I;c[h+12>>2]=J;UC(M,48,64037,h);g=g+1|0}while(g>>>0<(c[f>>2]|0)>>>0)}}f=c[Q>>2]|0;if(c[f+688>>2]&2048|0){UC(f,48,64057,j);g=0;do{f=c[b+202168+(g<<2)>>2]|0;if(f|0){M=c[Q>>2]|0;H=c[f+84>>2]|0;I=c[f+80>>2]|0;J=c[c[f>>2]>>2]|0;c[k>>2]=g;c[k+4>>2]=H;c[k+8>>2]=I;c[k+12>>2]=J;UC(M,48,64037,k)}g=g+1|0}while((g|0)!=16)}M=e+23380|0;if(!(c[M>>2]|0)){Q=0;i=R;return Q|0}t=e+12|0;u=e+4|0;v=e+20|0;w=b+199472|0;x=b+199476|0;y=b+197612|0;z=b+203244|0;A=b+24620|0;B=0;a:while(1){s=e+23372+(B<<2)|0;KF(e+23384+(B*1920|0)|0,b+199480+(B*1280|0)|0,(c[s>>2]|0)*40|0)|0;J=c[t>>2]|0;g=c[u>>2]|0;I=d[g+(J>>>3)>>0]|0;h=c[v>>2]|0;f=((J|0)<(h|0)&1)+J|0;c[t>>2]=f;b:do if(I&128>>>(J&7)|0){r=0;j=c[w>>2]|0;while(1){l=g+(f>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(f&7)>>>23;k=(d[86681+l>>0]|0)+f|0;k=h>>>0>k>>>0?k:h;c[t>>2]=k;l=a[87193+l>>0]|0;f=l&255;if(l<<24>>24==3)break b;if(r>>>0>=(c[s>>2]|0)>>>0){N=18;break a}switch(f|0){case 1:case 0:{f=g+(k>>>3)|0;f=(MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0)<<(k&7);do if(f>>>0>134217727){f=f>>>23;J=(d[86681+f>>0]|0)+k|0;c[t>>2]=h>>>0>J>>>0?J:h;f=d[87193+f>>0]|0}else{J=f>>>0>65535;I=J?f>>>16:f;J=J?16:0;g=(I&65280|0)==0;J=(d[97868+(g?I:I>>>8)>>0]|0)+(g?J:J|8)<<1;g=J+-31|0;J=63-J+k|0;c[t>>2]=h>>>0>J>>>0?J:h;if((g|0)<7){UC(0,16,66830,D);f=-1094995529;break}else{f=(f>>>g)+-1|0;break}}while(0);g=f+1|0;h=c[x>>2]|0;if(g>>>0>h>>>0){N=33;break a}j=(l<<24>>24==0?~f:g)+j&h+-1;f=c[y>>2]|0;if((f|0)==3){k=j;l=3}else{k=j>>1;l=(j&1|0)==0?f^3:f}f=c[z>>2]|0;if((f|0)>0){while(1){h=f+-1|0;g=c[b+202040+(h<<2)>>2]|0;if((c[g+84>>2]|0)==(k|0))if(c[g+636>>2]&l|0){N=32;break}if((f|0)>1)f=h;else{f=h;break}}if((N|0)==32)c[g+92>>2]=j;if((f|0)<1){f=j;N=53}else N=48}else{f=j;N=53}break}case 2:{f=g+(k>>>3)|0;f=(MF(d[f>>0]|d[f+1>>0]<<8|d[f+2>>0]<<16|d[f+3>>0]<<24|0)|0)<<(k&7);do if(f>>>0>134217727){N=f>>>23;k=(d[86681+N>>0]|0)+k|0;c[t>>2]=h>>>0>k>>>0?k:h;k=d[87193+N>>0]|0}else{N=f>>>0>65535;J=N?f>>>16:f;N=N?16:0;g=(J&65280|0)==0;N=(d[97868+(g?J:J>>>8)>>0]|0)+(g?N:N|8)<<1;g=N+-31|0;N=63-N+k|0;c[t>>2]=h>>>0>N>>>0?N:h;if((g|0)<7){UC(0,16,66830,F);k=-1094995529;break}else{k=(f>>>g)+-1|0;break}}while(0);f=c[y>>2]|0;if((f|0)==3){g=k;l=3}else{g=k>>1;l=(k&1|0)==0?f^3:f}if(g>>>0>31){N=50;break a}g=c[b+202168+(g<<2)>>2]|0;if(!g){f=j;N=53}else if(!(c[g+636>>2]&l)){f=j;N=53}else{f=c[g>>2]|0;h=c[c[A>>2]>>2]|0;if((c[h+68>>2]|0)==(c[f+68>>2]|0))if((c[h+72>>2]|0)==(c[f+72>>2]|0))if((c[h+80>>2]|0)==(c[f+80>>2]|0)){c[g+92>>2]=k;N=48}else{f=j;N=53}else{f=j;N=53}else{f=j;N=53}}break}default:{N=51;break a}}if((N|0)==48){N=0;q=g+92|0;k=r+1|0;n=c[s>>2]|0;if(k>>>0<n>>>0){o=g+96|0;f=k;m=r;while(1){h=c[e+23384+(B*1920|0)+(m*40|0)+36>>2]|0;if(h|0)if((c[o>>2]|0)==(c[h+96>>2]|0))if((c[q>>2]|0)==(c[e+23384+(B*1920|0)+(m*40|0)+32>>2]|0)){f=m;break}h=f+1|0;if(h>>>0<n>>>0){m=f;f=h}else break}if((f|0)>(r|0))do{I=e+23384+(B*1920|0)+(f*40|0)|0;f=f+-1|0;H=e+23384+(B*1920|0)+(f*40|0)|0;J=I+40|0;do{c[I>>2]=c[H>>2];I=I+4|0;H=H+4|0}while((I|0)<(J|0))}while((f|0)>(r|0))}m=e+23384+(B*1920|0)+(r*40|0)|0;n=c[g>>2]|0;c[m>>2]=c[n>>2];c[m+4>>2]=c[n+4>>2];c[m+8>>2]=c[n+8>>2];n=e+23384+(B*1920|0)+(r*40|0)+12|0;o=(c[g>>2]|0)+32|0;c[n>>2]=c[o>>2];c[n+4>>2]=c[o+4>>2];c[n+8>>2]=c[o+8>>2];o=e+23384+(B*1920|0)+(r*40|0)+24|0;c[o>>2]=c[g+636>>2];p=e+23384+(B*1920|0)+(r*40|0)+28|0;c[p>>2]=c[g+80>>2];c[e+23384+(B*1920|0)+(r*40|0)+32>>2]=c[q>>2];c[e+23384+(B*1920|0)+(r*40|0)+36>>2]=g;if((c[y>>2]|0)!=3){f=(l|0)==2;h=c[n>>2]|0;if(f){c[m>>2]=(c[m>>2]|0)+h;c[n>>2]=h<<1;J=e+23384+(B*1920|0)+(r*40|0)+16|0;I=c[J>>2]|0;H=e+23384+(B*1920|0)+(r*40|0)+4|0;c[H>>2]=(c[H>>2]|0)+I;c[J>>2]=I<<1;J=e+23384+(B*1920|0)+(r*40|0)+20|0;I=c[J>>2]|0;H=e+23384+(B*1920|0)+(r*40|0)+8|0;c[H>>2]=(c[H>>2]|0)+I;c[o>>2]=2;c[J>>2]=I<<1}else{c[n>>2]=h<<1;J=e+23384+(B*1920|0)+(r*40|0)+16|0;c[J>>2]=c[J>>2]<<1;c[o>>2]=l;J=e+23384+(B*1920|0)+(r*40|0)+20|0;c[J>>2]=c[J>>2]<<1}c[p>>2]=c[g+72+((f&1)<<2)>>2]}}else if((N|0)==53){N=0;UC(c[Q>>2]|0,16,64196,K);I=e+23384+(B*1920|0)+(r*40|0)|0;J=I+40|0;do{c[I>>2]=0;I=I+4|0}while((I|0)<(J|0));k=r+1|0;j=f}f=c[t>>2]|0;g=c[u>>2]|0;h=c[v>>2]|0;r=k}}while(0);B=B+1|0;f=c[M>>2]|0;if(B>>>0>=f>>>0){N=12;break}}if((N|0)==12){if(!f){Q=0;i=R;return Q|0}q=b+197612|0;r=b+202368|0;s=b+202372|0;t=b+202376|0;u=b+202380|0;v=b+202384|0;w=b+202388|0;x=b+202392|0;y=b+202396|0;z=b+202400|0;A=b+202404|0;B=b+202408|0;C=b+202412|0;D=b+202416|0;E=b+202420|0;F=b+202424|0;G=b+202428|0;p=0;c:while(1){o=e+23372+(p<<2)|0;if(c[o>>2]|0){j=b+199480+(p*1280|0)|0;k=b+199480+(p*1280|0)+28|0;l=b+199480+(p*1280|0)+36|0;m=b+199480+(p*1280|0)+24|0;n=0;do{g=e+23384+(p*1920|0)+(n*40|0)|0;h=e+23384+(p*1920|0)+(n*40|0)+36|0;f=c[h>>2]|0;if(!f)N=73;else if((c[q>>2]|0)==3)if((c[e+23384+(p*1920|0)+(n*40|0)+24>>2]&3|0)!=3)N=73;if((N|0)==73){N=0;L=c[Q>>2]|0;c[O>>2]=c[k>>2];UC(L,16,64238,O);c[r>>2]=-2147483648;c[s>>2]=-2147483648;c[t>>2]=-2147483648;c[u>>2]=-2147483648;c[v>>2]=-2147483648;c[w>>2]=-2147483648;c[x>>2]=-2147483648;c[y>>2]=-2147483648;c[z>>2]=-2147483648;c[A>>2]=-2147483648;c[B>>2]=-2147483648;c[C>>2]=-2147483648;c[D>>2]=-2147483648;c[E>>2]=-2147483648;c[F>>2]=-2147483648;c[G>>2]=-2147483648;if(!(c[l>>2]|0)){f=-1;N=81;break c}if((c[q>>2]|0)==3)if((c[m>>2]&3|0)!=3){f=-1;N=81;break c}I=g;H=j;J=I+40|0;do{c[I>>2]=c[H>>2];I=I+4|0;H=H+4|0}while((I|0)<(J|0));f=c[h>>2]|0}n=n+1|0;if((OB(c[(c[f>>2]|0)+360>>2]|0)|0)<=0){N=78;break c}}while(n>>>0<(c[o>>2]|0)>>>0);f=c[M>>2]|0}p=p+1|0;if(p>>>0>=f>>>0){f=0;N=81;break}}if((N|0)==78){c[P>>2]=64280;c[P+4>>2]=63946;c[P+8>>2]=365;UC(0,0,104466,P);Ya()}else if((N|0)==81){i=R;return f|0}}else if((N|0)==18){UC(c[Q>>2]|0,16,64074,C);Q=-1;i=R;return Q|0}else if((N|0)==33){UC(c[Q>>2]|0,16,64100,E);Q=-1094995529;i=R;return Q|0}else if((N|0)==50){UC(c[Q>>2]|0,16,64127,G);Q=-1094995529;i=R;return Q|0}else if((N|0)==51){Q=c[Q>>2]|0;c[L>>2]=f;UC(Q,16,64155,L);Q=-1094995529;i=R;return Q|0}return 0}function Rc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=b+23380|0;a=c[l>>2]|0;if(!a)return;else k=0;do{j=b+23372+(k<<2)|0;if(c[j>>2]|0){f=0;do{a=f<<1;d=a+16|0;e=b+23384+(k*1920|0)+(d*40|0)|0;g=e;h=b+23384+(k*1920|0)+(f*40|0)|0;i=g+40|0;do{c[g>>2]=c[h>>2];g=g+4|0;h=h+4|0}while((g|0)<(i|0));g=b+23384+(k*1920|0)+(d*40|0)+12|0;c[g>>2]=c[g>>2]<<1;g=b+23384+(k*1920|0)+(d*40|0)+16|0;c[g>>2]=c[g>>2]<<1;g=b+23384+(k*1920|0)+(d*40|0)+20|0;c[g>>2]=c[g>>2]<<1;c[b+23384+(k*1920|0)+(d*40|0)+24>>2]=1;c[b+23384+(k*1920|0)+(d*40|0)+28>>2]=c[(c[b+23384+(k*1920|0)+(d*40|0)+36>>2]|0)+72>>2];g=e+40|0;h=e;i=g+40|0;do{c[g>>2]=c[h>>2];g=g+4|0;h=h+4|0}while((g|0)<(i|0));h=b+23384+(k*1920|0)+(f*40|0)+36|0;g=c[h>>2]|0;i=e+40|0;c[i>>2]=(c[i>>2]|0)+(c[(c[g>>2]|0)+32>>2]|0);i=e+44|0;c[i>>2]=(c[i>>2]|0)+(c[(c[g>>2]|0)+36>>2]|0);i=e+48|0;c[i>>2]=(c[i>>2]|0)+(c[(c[c[h>>2]>>2]|0)+40>>2]|0);c[e+64>>2]=2;c[e+68>>2]=c[(c[e+76>>2]|0)+76>>2];i=c[b+888+(f<<4)+(k<<3)>>2]|0;h=a+17|0;c[b+888+(h<<4)+(k<<3)>>2]=i;c[b+888+(d<<4)+(k<<3)>>2]=i;i=c[b+888+(f<<4)+(k<<3)+4>>2]|0;c[b+888+(h<<4)+(k<<3)+4>>2]=i;c[b+888+(d<<4)+(k<<3)+4>>2]=i;i=c[b+1656+(f<<5)+(k<<4)>>2]|0;c[b+1656+(h<<5)+(k<<4)>>2]=i;c[b+1656+(d<<5)+(k<<4)>>2]=i;i=c[b+1656+(f<<5)+(k<<4)+4>>2]|0;c[b+1656+(h<<5)+(k<<4)+4>>2]=i;c[b+1656+(d<<5)+(k<<4)+4>>2]=i;i=c[b+1656+(f<<5)+(k<<4)+8>>2]|0;c[b+1656+(h<<5)+(k<<4)+8>>2]=i;c[b+1656+(d<<5)+(k<<4)+8>>2]=i;i=c[b+1656+(f<<5)+(k<<4)+12>>2]|0;c[b+1656+(h<<5)+(k<<4)+12>>2]=i;c[b+1656+(d<<5)+(k<<4)+12>>2]=i;f=f+1|0}while(f>>>0<(c[j>>2]|0)>>>0);a=c[l>>2]|0}k=k+1|0}while(k>>>0<a>>>0);return}function Sc(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;j=a+202296|0;e=a+203240|0;i=0;do{f=a+202168+(i<<2)|0;g=c[f>>2]|0;if(g|0){h=g+636|0;c[h>>2]=0;b=c[j>>2]|0;a:do if(b|0){d=0;while(1){d=d+1|0;if((b|0)==(g|0))break;b=c[a+202296+(d<<2)>>2]|0;if(!b)break a}c[h>>2]=4}while(0);c[g+96>>2]=0;c[f>>2]=0;c[e>>2]=(c[e>>2]|0)+-1}i=i+1|0}while((i|0)!=16);i=a+203244|0;b=c[i>>2]|0;if(b|0){d=a+25288|0;if(!(c[c[d>>2]>>2]|0)){Gc(a,d);Hc(a,d,c[a+202040>>2]|0)|0;b=c[i>>2]|0}if((b|0)>0){h=0;do{e=a+202040+(h<<2)|0;f=c[e>>2]|0;g=f+636|0;c[g>>2]=0;b=c[j>>2]|0;b:do if(b|0){d=0;while(1){d=d+1|0;if((b|0)==(f|0))break;b=c[a+202296+(d<<2)>>2]|0;if(!b)break b}c[g>>2]=4}while(0);c[e>>2]=0;h=h+1|0}while((h|0)<(c[i>>2]|0))}}c[i>>2]=0;FF(a+199480|0,0,2560)|0;e=a+25956|0;if((c[e>>2]|0)<=0)return;b=a+25952|0;d=0;do{FF((c[b>>2]|0)+(d*49776|0)+23372|0,0,3852)|0;d=d+1|0}while((d|0)<(c[e>>2]|0));return}function Tc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0;n=i;i=i+816|0;m=n+16|0;l=n;k=n+24|0;g=(b|0)!=0;e=g?a+202440|0:k;b=c[a+203244>>2]|0;do if(!b)j=0;else if(((c[a+203240>>2]|0)+b|0)<(c[a+26708>>2]|0))j=0;else{f=(c[a+197612>>2]|0)==3;if(!f)if(!(c[a+197616>>2]|0))if(c[(c[a+24620>>2]|0)+636>>2]|0){j=0;break}c[e>>2]=1;b=c[(c[a+202040+(b+-1<<2)>>2]|0)+84>>2]|0;d=e+4|0;c[d>>2]=b;if(f)j=1;else{j=b<<1;c[d>>2]=j;c[e+12>>2]=1;c[e+16>>2]=j|1;j=2}}while(0);h=a+203232|0;if(g){c[h>>2]=j;m=0;i=n;return m|0}b=c[h>>2]|0;do if((j|0)==(b|0)){if(!j){m=0;i=n;return m|0}else d=0;while(1){b=c[a+202440+(d*12|0)>>2]|0;e=c[k+(d*12|0)>>2]|0;if((b|0)!=(e|0)){f=14;break}d=d+1|0;if((d|0)>=(j|0)){b=0;f=16;break}}if((f|0)==14){c[l>>2]=b;c[l+4>>2]=e;c[l+8>>2]=d;UC(0,16,64353,l);b=c[h>>2]|0;break}else if((f|0)==16){i=n;return b|0}}while(0);a=c[a+4>>2]|0;c[m>>2]=j;c[m+4>>2]=b;UC(a,16,64407,m);m=-1094995529;i=n;return m|0}function Uc(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0;ta=i;i=i+304|0;ra=ta+280|0;qa=ta+272|0;pa=ta+256|0;oa=ta+248|0;na=ta+232|0;ma=ta+224|0;ka=ta+208|0;ja=ta+200|0;ia=ta+184|0;ha=ta+176|0;ga=ta+160|0;fa=ta+152|0;ea=ta+144|0;ca=ta+128|0;ba=ta+120|0;aa=ta+104|0;$=ta+96|0;_=ta+88|0;Z=ta+80|0;Y=ta+64|0;X=ta+56|0;W=ta+48|0;V=ta+40|0;da=ta+24|0;U=ta+8|0;sa=a+4|0;f=c[sa>>2]|0;if((d|0)==0?(c[f+688>>2]&2048|0)!=0:0)UC(f,48,64456,ta);if((d|0)>0){Q=a+203244|0;R=a+202296|0;t=a+203240|0;u=a+197612|0;v=a+202040|0;w=a+24620|0;x=a+202044|0;y=a+199448|0;z=a+203236|0;A=a+202368|0;B=a+202372|0;C=a+202376|0;D=a+202380|0;E=a+202384|0;F=a+202388|0;G=a+202392|0;H=a+202396|0;I=a+202400|0;J=a+202404|0;K=a+202408|0;L=a+202412|0;M=a+202416|0;N=a+202420|0;O=a+202424|0;P=a+202428|0;n=0;o=0;h=0;S=0;l=0;j=0;g=0;a:while(1){f=c[sa>>2]|0;if(c[f+688>>2]&2048|0){r=c[a+202440+(S*12|0)+4>>2]|0;s=c[a+202440+(S*12|0)+8>>2]|0;c[U>>2]=c[a+202440+(S*12|0)>>2];c[U+4>>2]=r;c[U+8>>2]=s;UC(f,48,64470,U)}k=b+(S*12|0)|0;f=c[k>>2]|0;b:do switch(f|0){case 3:case 1:{f=c[b+(S*12|0)+4>>2]|0;g=c[u>>2]|0;if((g|0)==3){j=f;m=3}else{j=f>>1;m=(f&1|0)==0?g^3:g}c:do if((c[Q>>2]|0)>0){g=0;while(1){h=c[a+202040+(g<<2)>>2]|0;f=c[sa>>2]|0;if(!(c[f+688>>2]&2048))f=h+84|0;else{s=h+84|0;r=c[s>>2]|0;c[da>>2]=g;c[da+4>>2]=r;c[da+8>>2]=h;UC(f,48,64485,da);f=s}if((c[f>>2]|0)==(j|0))break;g=g+1|0;if((g|0)>=(c[Q>>2]|0)){g=l;break c}}if(h){f=c[k>>2]|0;s=j;p=g;r=h;q=m;la=22;break b}}else g=l;while(0);if((c[k>>2]|0)==3){f=c[a+202168+(c[b+(S*12|0)+8>>2]<<2)>>2]|0;if(f|0)if((c[f+84>>2]|0)==(j|0)){h=j;f=g;j=0;g=m;break b}}UC(c[sa>>2]|0,c[Q>>2]|0?16:48,64495,V);o=-1094995529;h=j;f=g;j=0;g=m;break}default:{s=h;p=l;r=j;q=g;la=22}}while(0);d:do if((la|0)==22){la=0;switch(f|0){case 1:{f=c[sa>>2]|0;g=c[f+688>>2]|0;if(g&2048){g=c[Q>>2]|0;c[W>>2]=c[a+202440+(S*12|0)+4>>2];c[W+4>>2]=g;UC(f,48,64522,W);g=c[sa>>2]|0;f=g;g=c[g+688>>2]|0}m=q^3;if(g&2048|0){l=c[Q>>2]|0;c[X>>2]=s;c[X+4>>2]=l;UC(f,48,64553,X)}if((c[Q>>2]|0)>0)j=0;else{h=s;f=p;j=r;g=q;break d}while(1){g=a+202040+(j<<2)|0;h=c[g>>2]|0;f=c[sa>>2]|0;if(!(c[f+688>>2]&2048))f=h+84|0;else{l=h+84|0;k=c[l>>2]|0;c[Y>>2]=j;c[Y+4>>2]=k;c[Y+8>>2]=h;UC(f,48,64485,Y);f=l}if((c[f>>2]|0)==(s|0)){k=h;l=j;break}j=j+1|0;if((j|0)>=(c[Q>>2]|0)){h=s;f=p;j=r;g=q;break d}}if(!k){h=s;f=p;j=r;g=q;break d}j=k+636|0;m=c[j>>2]&m;c[j>>2]=m;if(m|0){h=s;f=p;j=r;g=q;break d}f=c[R>>2]|0;e:do if(f|0){h=0;while(1){h=h+1|0;if((f|0)==(k|0))break;f=c[a+202296+(h<<2)>>2]|0;if(!f)break e}c[j>>2]=4}while(0);c[g>>2]=0;f=(c[Q>>2]|0)+-1|0;c[Q>>2]=f;if(!f){h=s;f=p;j=r;g=q;break d}LF(g|0,a+202040+(l+1<<2)|0,f-l<<2|0)|0;h=s;f=p;j=r;g=q;break d}case 3:{m=b+(S*12|0)+8|0;f=c[m>>2]|0;j=a+202168+(f<<2)|0;k=c[j>>2]|0;if(!((k|0)==(r|0)|(k|0)==0)){l=k+636|0;c[l>>2]=0;g=c[R>>2]|0;f:do if(g|0){h=0;while(1){h=h+1|0;if((g|0)==(k|0))break;g=c[a+202296+(h<<2)>>2]|0;if(!g)break f}c[l>>2]=4}while(0);c[k+96>>2]=0;c[j>>2]=0;c[t>>2]=(c[t>>2]|0)+-1}g=a+202040+(p<<2)|0;c[g>>2]=0;h=(c[Q>>2]|0)+-1|0;c[Q>>2]=h;if(h){LF(g|0,a+202040+(p+1<<2)|0,h-p<<2|0)|0;f=c[m>>2]|0}c[a+202168+(f<<2)>>2]=r;if(!r){h=s;f=p;j=0;g=q;break d}c[r+96>>2]=1;c[t>>2]=(c[t>>2]|0)+1;h=s;f=p;j=r;g=q;break d}case 2:{f=c[b+(S*12|0)+8>>2]|0;g=c[u>>2]|0;if((g|0)==3){l=f;m=3}else{l=f>>1;m=(f&1|0)==0?g^3:g}h=a+202168+(l<<2)|0;k=c[h>>2]|0;if(!k){f=c[sa>>2]|0;if(!(c[f+688>>2]&2048)){h=s;f=l;j=0;g=m;break d}UC(f,48,64579,Z);h=s;f=l;j=0;g=m;break d}j=k+636|0;r=c[j>>2]&(m^3);c[j>>2]=r;if(r|0){h=s;f=l;j=k;g=m;break d}f=c[R>>2]|0;g:do if(f|0){g=0;while(1){g=g+1|0;if((f|0)==(k|0))break;f=c[a+202296+(g<<2)>>2]|0;if(!f)break g}c[j>>2]=4}while(0);c[k+96>>2]=0;c[h>>2]=0;c[t>>2]=(c[t>>2]|0)+-1;h=s;f=l;j=k;g=m;break d}case 6:{if((c[v>>2]|0)==(c[w>>2]|0)){UC(c[sa>>2]|0,16,64605,_);c[v>>2]=0;f=(c[Q>>2]|0)+-1|0;c[Q>>2]=f;if(f|0)LF(v|0,x|0,f<<2|0)|0}f=c[w>>2]|0;m=b+(S*12|0)+8|0;if(!(c[f+96>>2]|0))l=p;else{l=0;while(1){k=a+202168+(l<<2)|0;if((c[k>>2]|0)==(f|0)){if((l|0)!=(c[m>>2]|0)){UC(c[sa>>2]|0,16,64677,$);f=c[k>>2]|0}if(f|0){j=f+636|0;c[j>>2]=0;g=c[R>>2]|0;h:do if(g|0){h=0;while(1){h=h+1|0;if((g|0)==(f|0))break;g=c[a+202296+(h<<2)>>2]|0;if(!g)break h}c[j>>2]=4}while(0);c[f+96>>2]=0;c[k>>2]=0;c[t>>2]=(c[t>>2]|0)+-1}}l=l+1|0;f=c[w>>2]|0;if((l|0)==32){l=32;break}}}g=f;k=a+202168+(c[m>>2]<<2)|0;j=c[k>>2]|0;if((j|0)!=(f|0)){if(c[f+96>>2]|0){la=79;break a}if(!j)f=g;else{h=j+636|0;c[h>>2]=0;f=c[R>>2]|0;i:do if(f|0){g=0;while(1){g=g+1|0;if((f|0)==(j|0))break;f=c[a+202296+(g<<2)>>2]|0;if(!f)break i}c[h>>2]=4}while(0);c[j+96>>2]=0;c[k>>2]=0;c[t>>2]=(c[t>>2]|0)+-1;f=c[w>>2]|0}c[k>>2]=f;c[f+96>>2]=1;c[t>>2]=(c[t>>2]|0)+1;f=c[w>>2]|0}n=f+636|0;c[n>>2]=c[n>>2]|c[u>>2];n=1;h=s;f=l;j=r;g=q;break d}case 4:{f=c[b+(S*12|0)+8>>2]|0;if((f|0)>=16){h=s;j=r;g=q;break d}while(1){j=a+202168+(f<<2)|0;k=c[j>>2]|0;if(k|0){l=k+636|0;c[l>>2]=0;g=c[R>>2]|0;j:do if(g|0){h=0;while(1){h=h+1|0;if((g|0)==(k|0))break;g=c[a+202296+(h<<2)>>2]|0;if(!g)break j}c[l>>2]=4}while(0);c[k+96>>2]=0;c[j>>2]=0;c[t>>2]=(c[t>>2]|0)+-1}f=f+1|0;if((f|0)==16){h=s;f=16;j=r;g=q;break}}break}case 5:{f=c[Q>>2]|0;k:do if(!f)l=0;else l:while(1){j=c[(c[v>>2]|0)+84>>2]|0;g=c[sa>>2]|0;if(c[g+688>>2]&2048){c[ba>>2]=j;c[ba+4>>2]=f;UC(g,48,64553,ba);f=c[Q>>2]|0}m:do if((f|0)>0){l=0;while(1){g=a+202040+(l<<2)|0;h=c[g>>2]|0;f=c[sa>>2]|0;if(!(c[f+688>>2]&2048))f=h+84|0;else{p=h+84|0;m=c[p>>2]|0;c[ca>>2]=l;c[ca+4>>2]=m;c[ca+8>>2]=h;UC(f,48,64485,ca);f=p}if((c[f>>2]|0)==(j|0)){k=h;break}l=l+1|0;f=c[Q>>2]|0;if((l|0)>=(f|0))break m}if(k|0){j=k+636|0;c[j>>2]=0;f=c[R>>2]|0;n:do if(f|0){h=0;while(1){h=h+1|0;if((f|0)==(k|0))break;f=c[a+202296+(h<<2)>>2]|0;if(!f)break n}c[j>>2]=4}while(0);c[g>>2]=0;f=(c[Q>>2]|0)+-1|0;c[Q>>2]=f;if(!f){l=0;break k}LF(g|0,a+202040+(l+1<<2)|0,f-l<<2|0)|0}f=c[Q>>2]|0;if(!f){l=0;break k}else continue l}while(0);if(!f){l=0;break}}while(0);do{h=a+202168+(l<<2)|0;j=c[h>>2]|0;if(j|0){k=j+636|0;c[k>>2]=0;f=c[R>>2]|0;o:do if(f|0){g=0;while(1){g=g+1|0;if((f|0)==(j|0))break;f=c[a+202296+(g<<2)>>2]|0;if(!f)break o}c[k>>2]=4}while(0);c[j+96>>2]=0;c[h>>2]=0;c[t>>2]=(c[t>>2]|0)+-1}l=l+1|0}while((l|0)!=16);h=c[w>>2]|0;c[h+84>>2]=0;c[y>>2]=0;c[z>>2]=1;c[h+88>>2]=1;c[A>>2]=-2147483648;c[B>>2]=-2147483648;c[C>>2]=-2147483648;c[D>>2]=-2147483648;c[E>>2]=-2147483648;c[F>>2]=-2147483648;c[G>>2]=-2147483648;c[H>>2]=-2147483648;c[I>>2]=-2147483648;c[J>>2]=-2147483648;c[K>>2]=-2147483648;c[L>>2]=-2147483648;c[M>>2]=-2147483648;c[N>>2]=-2147483648;c[O>>2]=-2147483648;c[P>>2]=-2147483648;h=s;f=16;j=r;g=q;break d}default:{h=s;f=p;j=r;g=q;break d}}}while(0);S=S+1|0;if((S|0)>=(d|0)){T=n;e=o;break}else l=f}if((la|0)==79){c[aa>>2]=64740;c[aa+4>>2]=63946;c[aa+8>>2]=704;UC(0,0,104466,aa);Ya()}if(!T)la=124;else s=a+203244|0}else{e=0;la=124}do if((la|0)==124){m=a+203244|0;f=c[m>>2]|0;if(!f){g=a+24620|0;j=g;g=c[g>>2]|0}else{h=c[a+202040>>2]|0;j=a+24620|0;g=c[j>>2]|0;if((h|0)==(g|0)){s=h+636|0;c[s>>2]=c[s>>2]|c[a+197612>>2];s=m;break}}if(c[g+96>>2]|0){UC(c[sa>>2]|0,16,64766,ea);s=m;e=-1094995529;break}l=c[g+84>>2]|0;g=c[sa>>2]|0;if(c[g+688>>2]&2048){c[fa>>2]=l;c[fa+4>>2]=f;UC(g,48,64553,fa);f=c[m>>2]|0}p:do if((f|0)>0){k=0;while(1){g=a+202040+(k<<2)|0;h=c[g>>2]|0;f=c[sa>>2]|0;if(!(c[f+688>>2]&2048))f=h+84|0;else{fa=h+84|0;ea=c[fa>>2]|0;c[ga>>2]=k;c[ga+4>>2]=ea;c[ga+8>>2]=h;UC(f,48,64485,ga);f=fa}if((c[f>>2]|0)==(l|0)){l=g;break}k=k+1|0;f=c[m>>2]|0;if((k|0)>=(f|0))break p}if(h){g=h+636|0;c[g>>2]=0;e=c[a+202296>>2]|0;q:do if(e|0){f=0;while(1){f=f+1|0;if((e|0)==(h|0))break;e=c[a+202296+(f<<2)>>2]|0;if(!e)break q}c[g>>2]=4}while(0);c[l>>2]=0;e=(c[m>>2]|0)+-1|0;c[m>>2]=e;if(e|0)LF(l|0,a+202040+(k+1<<2)|0,e-k<<2|0)|0;UC(c[sa>>2]|0,16,64879,ha);e=-1094995529}f=c[m>>2]|0}while(0);if(!f)f=0;else{LF(a+202044|0,a+202040|0,f<<2|0)|0;f=c[m>>2]|0}s=c[j>>2]|0;c[a+202040>>2]=s;c[m>>2]=f+1;s=s+636|0;c[s>>2]=c[s>>2]|c[a+197612>>2];s=m}while(0);r=a+203240|0;f=c[r>>2]|0;g=c[s>>2]|0;q=a+26708|0;h=c[q>>2]|0;r:do if((g+f|0)>(((h|0)>1?h:1)|0)){e=c[sa>>2]|0;c[ia>>2]=f;c[ia+4>>2]=g;c[ia+8>>2]=h;UC(e,16,64921,ia);e=c[s>>2]|0;if(!((c[r>>2]|0)!=0&(e|0)==0)){g=c[(c[a+202040+(e+-1<<2)>>2]|0)+84>>2]|0;f=c[sa>>2]|0;if(c[f+688>>2]&2048){c[ja>>2]=g;c[ja+4>>2]=e;UC(f,48,64553,ja);e=c[s>>2]|0}if((e|0)>0)j=0;else{e=-1094995529;la=176;break}while(1){f=a+202040+(j<<2)|0;h=c[f>>2]|0;e=c[sa>>2]|0;if(!(c[e+688>>2]&2048))e=h+84|0;else{ja=h+84|0;ia=c[ja>>2]|0;c[ka>>2]=j;c[ka+4>>2]=ia;c[ka+8>>2]=h;UC(e,48,64485,ka);e=ja}if((c[e>>2]|0)==(g|0)){k=f;break}j=j+1|0;e=c[s>>2]|0;if((j|0)>=(e|0)){f=e;e=-1094995529;la=177;break r}}if(!h){e=-1094995529;la=176;break}g=h+636|0;c[g>>2]=0;e=c[a+202296>>2]|0;s:do if(e|0){f=0;while(1){f=f+1|0;if((e|0)==(h|0))break;e=c[a+202296+(f<<2)>>2]|0;if(!e)break s}c[g>>2]=4}while(0);c[k>>2]=0;e=(c[s>>2]|0)+-1|0;c[s>>2]=e;if(!e){k=-1094995529;break}LF(k|0,a+202040+(j+1<<2)|0,e-j<<2|0)|0;e=-1094995529;la=176;break}e=c[a+202168>>2]|0;do if(!e){e=c[a+202172>>2]|0;if(!e){e=c[a+202176>>2]|0;if(!e){e=c[a+202180>>2]|0;if(!e){e=c[a+202184>>2]|0;if(!e){e=c[a+202188>>2]|0;if(!e){e=c[a+202192>>2]|0;if(!e){e=c[a+202196>>2]|0;if(!e){e=c[a+202200>>2]|0;if(!e){e=c[a+202204>>2]|0;if(!e){e=c[a+202208>>2]|0;if(!e){e=c[a+202212>>2]|0;if(!e){e=c[a+202216>>2]|0;if(!e){e=c[a+202220>>2]|0;if(!e){e=c[a+202224>>2]|0;if(e|0){f=14;la=154;break}e=c[a+202228>>2]|0;if(e|0){f=15;la=154;break}e=c[a+202232>>2]|0;if(!e){e=-1094995529;la=176;break r}else j=a+202232|0}else{f=13;la=154}}else{f=12;la=154}}else{f=11;la=154}}else{f=10;la=154}}else{f=9;la=154}}else{f=8;la=154}}else{f=7;la=154}}else{f=6;la=154}}else{f=5;la=154}}else{f=4;la=154}}else{f=3;la=154}}else{f=2;la=154}}else{f=1;la=154}}else{f=0;la=154}while(0);if((la|0)==154)j=a+202168+(f<<2)|0;h=e+636|0;c[h>>2]=0;f=c[a+202296>>2]|0;t:do if(f|0){g=0;while(1){g=g+1|0;if((f|0)==(e|0))break;f=c[a+202296+(g<<2)>>2]|0;if(!f)break t}c[h>>2]=4}while(0);c[e+96>>2]=0;c[j>>2]=0;c[r>>2]=(c[r>>2]|0)+-1;e=-1094995529;la=176}else la=176;while(0);if((la|0)==176){f=c[s>>2]|0;la=177}if((la|0)==177)if((f|0)>0){n=a+24620|0;o=a+26680|0;p=a+202296|0;m=0;while(1){g=c[a+202040+(m<<2)>>2]|0;u:do if(c[g+644>>2]|0){k=c[g+84>>2]|0;if(((1<<c[o>>2])+-1&(c[(c[n>>2]|0)+84>>2]|0)-k|0)>(c[q>>2]|0)){g=c[sa>>2]|0;if(c[g+688>>2]&2048){c[ma>>2]=k;c[ma+4>>2]=f;UC(g,48,64553,ma);f=c[s>>2]|0}if((f|0)>0){j=0;while(1){g=a+202040+(j<<2)|0;h=c[g>>2]|0;f=c[sa>>2]|0;if(!(c[f+688>>2]&2048))f=h+84|0;else{la=h+84|0;ka=c[la>>2]|0;c[na>>2]=j;c[na+4>>2]=ka;c[na+8>>2]=h;UC(f,48,64485,na);f=la}if((c[f>>2]|0)==(k|0)){k=h;l=j;break}j=j+1|0;if((j|0)>=(c[s>>2]|0))break u}if(k|0){j=k+636|0;c[j>>2]=0;f=c[p>>2]|0;v:do if(f|0){h=0;while(1){h=h+1|0;if((f|0)==(k|0))break;f=c[a+202296+(h<<2)>>2]|0;if(!f)break v}c[j>>2]=4}while(0);c[g>>2]=0;f=(c[s>>2]|0)+-1|0;c[s>>2]=f;if(f|0)LF(g|0,a+202040+(l+1<<2)|0,f-l<<2|0)|0}}}}while(0);m=m+1|0;f=c[s>>2]|0;if((m|0)>=(f|0)){k=e;break}}}else k=e;e=c[sa>>2]|0;if(c[e+688>>2]&2048|0){UC(e,48,64019,oa);if(c[s>>2]|0){e=0;do{na=c[a+202040+(e<<2)>>2]|0;oa=c[sa>>2]|0;la=c[na+84>>2]|0;ma=c[na+80>>2]|0;na=c[c[na>>2]>>2]|0;c[pa>>2]=e;c[pa+4>>2]=la;c[pa+8>>2]=ma;c[pa+12>>2]=na;UC(oa,48,64037,pa);e=e+1|0}while(e>>>0<(c[s>>2]|0)>>>0)}}e=c[sa>>2]|0;if(c[e+688>>2]&2048|0){UC(e,48,64057,qa);f=0;do{e=c[a+202168+(f<<2)>>2]|0;if(e|0){qa=c[sa>>2]|0;na=c[e+84>>2]|0;oa=c[e+80>>2]|0;pa=c[c[e>>2]>>2]|0;c[ra>>2]=f;c[ra+4>>2]=na;c[ra+8>>2]=oa;c[ra+12>>2]=pa;UC(qa,48,64037,ra)}f=f+1|0}while((f|0)!=16)}e=c[a+27884>>2]|0;h=c[a+27888>>2]|0;j=0;f=0;g=0;do{f=f>>>0>e>>>0?f:e;g=g>>>0>h>>>0?g:h;j=j+1|0}while((j|0)!=256);if((k|0)<=-1){a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}if(c[r>>2]|0){a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}e=c[a+197612>>2]|0;if((c[s>>2]|0)>=3)if(!((g|0)<2&(f|0)<=(((e|0)!=3?2:1)|0))){a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}if((f|0)>((((c[a+203388>>2]|0)==0&1)<<1)+((e|0)!=3?3:2)|0)){a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}e=c[a+24620>>2]|0;if((c[(c[e>>2]|0)+88>>2]|0)!=1){a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}ra=e+640|0;c[ra>>2]=c[ra>>2]|1;if(c[(c[sa>>2]|0)+176>>2]|0){a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}a=a+203384|0;c[a>>2]=c[a>>2]|2;a=c[sa>>2]|0;a=a+696|0;a=c[a>>2]|0;a=a&8;a=(a|0)!=0;a=a?k:0;i=ta;return a|0}function Vc(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;B=i;i=i+848|0;A=B+40|0;z=B+24|0;x=B+16|0;w=B+8|0;v=B;y=B+48|0;u=e+8|0;g=c[u>>2]|0;a:do if((c[b+198248>>2]|0)==5){v=c[e+16>>2]|0;x=g+1|0;x=v>>>0>x>>>0?x:v;c[u>>2]=x;w=d[(c[e>>2]|0)+(x>>>3)>>0]|0;c[u>>2]=((x|0)<(v|0)&1)+x;if(!(w&128>>>(x&7)))l=0;else{c[y>>2]=6;c[y+8>>2]=0;l=1}}else{j=c[e>>2]|0;s=d[j+(g>>>3)>>0]|0;t=e+16|0;k=c[t>>2]|0;h=((g|0)<(k|0)&1)+g|0;c[u>>2]=h;if(!(s&128>>>(g&7))){if(f|0){g=Tc(b,f)|0;if((g|0)<0)if(c[(c[b+4>>2]|0)+696>>2]&8|0){b=g;i=B;return b|0}}b=0;i=B;return b|0}q=b+199472|0;r=b+199476|0;s=b+197612|0;l=0;while(1){p=j+(h>>>3)|0;p=(MF(d[p>>0]|d[p+1>>0]<<8|d[p+2>>0]<<16|d[p+3>>0]<<24|0)|0)<<(h&7)>>>23;m=(d[86681+p>>0]|0)+h|0;m=k>>>0>m>>>0?m:k;c[u>>2]=m;p=a[87193+p>>0]|0;g=p&255;c[y+(l*12|0)>>2]=g;o=g|2;if((o|0)==3){n=c[q>>2]|0;h=j+(m>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(m&7);do if(h>>>0>134217727){h=h>>>23;m=(d[86681+h>>0]|0)+m|0;c[u>>2]=k>>>0>m>>>0?m:k;h=d[87193+h>>0]|0}else{C=h>>>0>65535;D=C?h>>>16:h;C=C?16:0;j=(D&65280|0)==0;C=(d[97868+(j?D:D>>>8)>>0]|0)+(j?C:C|8)<<1;j=C+-31|0;m=63-C+m|0;c[u>>2]=k>>>0>m>>>0?m:k;if((j|0)<7){UC(0,16,66830,v);h=-1094995529;break}else{h=(h>>>j)+-1|0;break}}while(0);c[y+(l*12|0)+4>>2]=n+-1-h&(c[r>>2]|0)+-1}m=p<<24>>24==2;k=p<<24>>24==4;if((o|0)==6|(g|1|0)==3){D=c[u>>2]|0;j=c[t>>2]|0;h=(c[e>>2]|0)+(D>>>3)|0;h=(MF(d[h>>0]|d[h+1>>0]<<8|d[h+2>>0]<<16|d[h+3>>0]<<24|0)|0)<<(D&7)>>>23;D=(d[86681+h>>0]|0)+D|0;c[u>>2]=j>>>0>D>>>0?D:j;h=a[87193+h>>0]|0;j=h&255;if((h&255)>31){m=19;break}if(!((h&255)<16|k&h<<24>>24==16)){if(!m){m=19;break}if((c[s>>2]|0)==3){m=19;break}}c[y+(l*12|0)+8>>2]=j}if((p&255)>6){m=21;break}if(!(p<<24>>24))break a;l=l+1|0;if((l|0)>=66)break a;h=c[u>>2]|0;j=c[e>>2]|0;k=c[t>>2]|0}if((m|0)==19){D=c[b+4>>2]|0;c[w>>2]=g;UC(D,16,65014,w);D=-1;i=B;return D|0}else if((m|0)==21){D=c[b+4>>2]|0;c[x>>2]=g;UC(D,16,65074,x);D=-1;i=B;return D|0}}while(0);if((f|0)!=0&(l|0)!=-1){KF(b+202440|0,y|0,792)|0;c[b+203232>>2]=l;D=0;i=B;return D|0}if(!((f|0)==0&(l|0)>-1)){D=0;i=B;return D|0}k=b+203232|0;g=c[k>>2]|0;do if((l|0)==(g|0)){if((l|0)>0)h=0;else{D=0;i=B;return D|0}while(1){g=c[b+202440+(h*12|0)>>2]|0;j=c[y+(h*12|0)>>2]|0;if((g|0)!=(j|0)){m=36;break}h=h+1|0;if((h|0)>=(l|0)){g=0;m=38;break}}if((m|0)==36){c[z>>2]=g;c[z+4>>2]=j;c[z+8>>2]=h;UC(0,16,64353,z);g=c[k>>2]|0;break}else if((m|0)==38){i=B;return g|0}}while(0);D=c[b+4>>2]|0;c[A>>2]=l;c[A+4>>2]=g;UC(D,16,64407,A);D=-1094995529;i=B;return D|0}function Wc(a){a=a|0;c[a+203348>>2]=-1;c[a+203340>>2]=0;c[a+203344>>2]=-1;c[a+203400>>2]=0;c[a+203288>>2]=0;c[a+203304>>2]=0;c[a+203320>>2]=0;c[a+203328>>2]=0;bD(a+203332|0);return}function Xc(f){f=f|0;var g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,na=0,oa=0,pa=0,qa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0,Aa=0,Ba=0,Ca=0,Da=0,Ea=0,Fa=0,Ga=0,Ha=0,Ia=0,Ja=0;Ia=i;i=i+496|0;Ca=Ia+208|0;Ba=Ia+200|0;Aa=Ia+192|0;za=Ia+160|0;ya=Ia+152|0;xa=Ia+144|0;wa=Ia+136|0;va=Ia+128|0;ua=Ia+120|0;ta=Ia+112|0;sa=Ia+88|0;ra=Ia+80|0;qa=Ia+72|0;Ea=Ia+64|0;pa=Ia+56|0;oa=Ia+48|0;na=Ia+40|0;ma=Ia+32|0;Da=Ia+24|0;Fa=Ia+8|0;la=Ia;ka=Ia+216|0;ja=Ia+212|0;t=f+704|0;j=c[t>>2]|0;u=f+708|0;g=c[u>>2]|0;if((g-j|0)<=16){Ha=0;i=Ia;return Ha|0}v=f+696|0;H=f+712|0;Ga=f+4|0;aa=f+26660|0;ea=f+696|0;fa=f+203280|0;ga=f+203336|0;ha=f+203344|0;ia=f+203340|0;w=f+203324|0;x=f+203320|0;y=f+203328|0;z=f+203332|0;A=ka+16|0;B=f+198220|0;D=f+203348|0;E=f+203388|0;F=f+203400|0;G=f+203356|0;I=f+203360|0;J=f+203288|0;K=f+203292|0;L=f+203364|0;M=f+203300|0;N=f+203376|0;O=f+203296|0;P=f+203372|0;Q=f+203368|0;R=f+203304|0;S=f+203312|0;T=f+203316|0;U=f+203308|0;V=f+203648|0;W=f+203649|0;X=f+203652|0;Y=f+203654|0;Z=f+203655|0;_=f+203656|0;$=f+203657|0;ba=f+203650|0;ca=f+203658|0;da=f+203660|0;a:while(1){n=c[v>>2]|0;s=n+(j>>>3)|0;if(!((MF(d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24|0)|0)&-65536>>>(j&7))){g=0;Ha=154;break}else k=0;while(1){if((g-j|0)<8){g=-1094995529;Ha=154;break a}s=n+(j>>>3)|0;s=(MF(d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24|0)|0)<<(j&7);k=(s>>>24)+k|0;l=c[H>>2]|0;j=j+8|0;j=l>>>0>j>>>0?j:l;c[t>>2]=j;if(s>>>0<=4278190079){m=l;break}}l=j;j=0;while(1){if((g-l|0)<8){g=-1094995529;Ha=154;break a}s=n+(l>>>3)|0;s=(MF(d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24|0)|0)<<(l&7);j=(s>>>24)+j|0;l=l+8|0;l=m>>>0>l>>>0?l:m;c[t>>2]=l;if(s>>>0<=4278190079){m=l;break}}l=c[Ga>>2]|0;if(c[l+688>>2]&256){c[la>>2]=k;c[la+4>>2]=j;UC(l,48,65122,la);g=c[u>>2]|0;m=c[t>>2]|0}g=g-m|0;if(j>>>0>((g|0)/8|0)>>>0){Ha=12;break}s=m+(j<<3)|0;b:do switch(k|0){case 1:{j=0;g=aa;do{if(!(c[g+20>>2]|0)){r=c[f+198268+(j<<2)>>2]|0;g=(r|0)==0?g:r}j=j+1|0}while((j|0)!=32);o=g;if(!(c[o+1152>>2]|0))if(!(c[o+1156>>2]|0))g=m;else Ha=19;else Ha=19;if((Ha|0)==19){Ha=0;g=c[o+1176>>2]|0;do if(!g){j=0;g=m}else{j=c[H>>2]|0;k=c[v>>2]|0;l=k+(m>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(m&7);if((g|0)<26){r=m+g|0;r=j>>>0>r>>>0?r:j;c[t>>2]=r;j=l>>>(32-g|0);g=r;break}else{p=m+16|0;p=j>>>0>p>>>0?p:j;c[t>>2]=p;r=g+-16|0;q=k+(p>>>3)|0;q=(MF(d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24|0)|0)<<(p&7)>>>(48-g|0);g=p+r|0;g=j>>>0>g>>>0?g:j;c[t>>2]=g;j=q|l>>>16<<r;break}}while(0);c[ha>>2]=j;j=c[o+1180>>2]|0;do if(!j)j=0;else{k=c[H>>2]|0;l=c[v>>2]|0;m=l+(g>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(g&7);if((j|0)<26){g=g+j|0;g=k>>>0>g>>>0?g:k;c[t>>2]=g;j=m>>>(32-j|0);break}else{g=g+16|0;g=k>>>0>g>>>0?g:k;c[t>>2]=g;r=j+-16|0;q=l+(g>>>3)|0;j=(MF(d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24|0)|0)<<(g&7)>>>(48-j|0);g=g+r|0;g=k>>>0>g>>>0?g:k;c[t>>2]=g;j=j|m>>>16<<r;break}}while(0);c[ia>>2]=j}if(c[o+1160>>2]|0){p=c[H>>2]|0;q=c[ea>>2]|0;n=q+(g>>>3)|0;n=(MF(d[n>>0]|d[n+1>>0]<<8|d[n+2>>0]<<16|d[n+3>>0]<<24|0)|0)<<(g&7);r=n>>>28;g=g+4|0;g=p>>>0>g>>>0?g:p;c[t>>2]=g;c[fa>>2]=r;c[ga>>2]=0;if(n>>>0>2415919103){g=-1094995529;Ha=154;break a}n=d[65174+r>>0]|0;m=o+1164|0;k=g;g=0;l=0;while(1){o=q+(k>>>3)|0;o=MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0;j=k+1|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j;if(o&-2147483648>>>(k&7)){k=q+(j>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(j&7)>>>30;o=j+2|0;g=1<<k|g;c[ga>>2]=g;o=(p>>>0>o>>>0?o:p)+1|0;o=(p>>>0>o>>>0?o:p)+5|0;o=p>>>0>o>>>0?o:p;c[t>>2]=o;j=q+(o>>>3)|0;j=MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0;k=o+1|0;k=(p>>>0>k>>>0?k:p)+1|0;k=(p>>>0>k>>>0?k:p)+1|0;k=(p>>>0>k>>>0?k:p)+8|0;k=p>>>0>k>>>0?k:p;c[t>>2]=k;if(!(j&-2147483648>>>(o&7))){o=q+(k>>>3)|0;o=MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0;j=k+1|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j;if(o&-2147483648>>>(k&7)){o=j+6|0;o=p>>>0>o>>>0?o:p;c[t>>2]=o;k=q+(o>>>3)|0;k=MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0;j=o+1|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j;if(k&-2147483648>>>(o&7)){o=j+6|0;o=p>>>0>o>>>0?o:p;c[t>>2]=o;k=q+(o>>>3)|0;k=MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0;j=o+1|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j;if(k&-2147483648>>>(o&7)){j=j+5|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j}}}}else{j=k+6|0;j=(p>>>0>j>>>0?j:p)+6|0;j=(p>>>0>j>>>0?j:p)+5|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j}k=c[m>>2]|0;if((k|0)>0){j=k+j|0;j=p>>>0>j>>>0?j:p;c[t>>2]=j}}l=l+1|0;if(l>>>0>=n>>>0)break;else k=j}j=c[Ga>>2]|0;if(c[j+688>>2]&1|0){c[Da>>2]=g;c[Da+4>>2]=r;UC(j,48,65183,Da)}}break}case 4:{if((j|0)<7){g=-1094995529;Ha=154;break a}n=c[H>>2]|0;l=c[ea>>2]|0;r=l+(m>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(m&7);g=m+8|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;if(r>>>0>4278190079){g=g+8|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;k=j+-8|0}else k=j+-7|0;g=g+8|0;g=(n>>>0>g>>>0?g:n)+8|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;j=l+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7)&-65536;g=g+16|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;r=l+(g>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(g&7)>>>16;g=g+16|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;j=r|j;c:do if((j|0)<1195456820){switch(j|0){case 1146373937:break;default:break c}if((k|0)<1){g=-1094995529;Ha=154;break a}r=g+1|0;r=n>>>0>r>>>0?r:n;c[t>>2]=r;q=l+(r>>>3)|0;q=MF(d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24|0)|0;g=r+1|0;g=(n>>>0>g>>>0?g:n)+6|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;if(!(q&-2147483648>>>(r&7)))break b;if((k|0)<2){g=-1094995529;Ha=154;break a}q=g+4|0;q=n>>>0>q>>>0?q:n;c[t>>2]=q;r=l+(q>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(q&7)>>>28;q=q+4|0;c[t>>2]=n>>>0>q>>>0?q:n;a[w>>0]=r;c[x>>2]=1;c[(c[Ga>>2]|0)+272>>2]=r;break b}else{switch(j|0){case 1195456820:break;default:break c}if((k|0)<3){g=-22;Ha=154;break a}r=l+(g>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(g&7);g=g+8|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;if((r&-16777216|0)!=50331648){k=k+-1|0;j=0;do{g=g+8|0;g=n>>>0>g>>>0?g:n;j=j+1|0}while((j|0)!=(k|0));c[t>>2]=g;break b}r=g+1|0;r=n>>>0>r>>>0?r:n;c[t>>2]=r;q=l+(r>>>3)|0;q=MF(d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24|0)|0;g=r+1|0;g=n>>>0>g>>>0?g:n;c[t>>2]=g;if(!(q&-2147483648>>>(r&7)))break b;r=g+1|0;r=n>>>0>r>>>0?r:n;c[t>>2]=r;l=l+(r>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(r&7)>>>27;r=r+5|0;r=(n>>>0>r>>>0?r:n)+8|0;c[t>>2]=n>>>0>r>>>0?r:n;if(!l)break b;if((k+-2|0)<(l*3|0))break b;r=c[y>>2]|0;g=RF(l|0,0,3,0)|0;g=HF(r|0,((r|0)<0)<<31>>31|0,g|0,C|0)|0;r=C;if(r>>>0>0|(r|0)==0&g>>>0>2147483647){g=-22;Ha=154;break a}g=aD(z,g)|0;if((g|0)<0){Ha=154;break a}j=c[t>>2]|0;g=c[H>>2]|0;k=0;do{r=(c[ea>>2]|0)+(j>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(j&7)>>>24;j=j+8|0;c[t>>2]=g>>>0>j>>>0?j:g;g=c[y>>2]|0;c[y>>2]=g+1;a[(c[z>>2]|0)+g>>0]=r;g=c[t>>2]|0;r=c[H>>2]|0;j=(c[ea>>2]|0)+(g>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(g&7)>>>24;g=g+8|0;c[t>>2]=r>>>0>g>>>0?g:r;r=c[y>>2]|0;c[y>>2]=r+1;a[(c[z>>2]|0)+r>>0]=j;r=c[t>>2]|0;j=c[H>>2]|0;g=(c[ea>>2]|0)+(r>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(r&7)>>>24;r=r+8|0;c[t>>2]=j>>>0>r>>>0?r:j;j=c[y>>2]|0;c[y>>2]=j+1;a[(c[z>>2]|0)+j>>0]=g;k=k+1|0;j=c[t>>2]|0;g=c[H>>2]|0}while((k|0)!=(l|0));r=j+8|0;c[t>>2]=g>>>0>r>>>0?r:g;break b}while(0);r=g+(k<<3)|0;c[t>>2]=n>>>0>r>>>0?r:n;break}case 5:{if((j|0)<16){Ha=150;break a}k=c[H>>2]|0;n=c[ea>>2]|0;g=0;do{l=n+(m>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(m&7)>>>24;r=m+8|0;m=k>>>0>r>>>0?r:k;c[t>>2]=m;a[ka+g>>0]=l;g=g+1|0;l=(g|0)<(j|0)}while(g>>>0<271&l);m=g;a[ka+m>>0]=0;c[ma>>2]=ja;g=(dF(A,65209,ma)|0)==1;k=c[ja>>2]|0;if(g&(k|0)>0)c[B>>2]=k;if(g&(k|0)==1)if(!(kF(A,65224,16)|0))c[B>>2]=67;g=c[Ga>>2]|0;if(c[g+688>>2]&4096|0){c[na>>2]=A;UC(g,48,65241,na)}if(l){l=c[H>>2]|0;g=c[t>>2]|0;k=m;do{g=g+8|0;g=l>>>0>g>>>0?g:l;k=k+1|0}while((k|0)!=(j|0));c[t>>2]=g}break}case 6:{g=c[H>>2]|0;k=(c[ea>>2]|0)+(m>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(m&7);do if(k>>>0>134217727){k=k>>>23;j=(d[86681+k>>0]|0)+m|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;k=d[87193+k>>0]|0}else{j=k>>>0>65535;r=j?k>>>16:k;j=j?16:0;l=(r&65280|0)==0;j=(d[97868+(l?r:r>>>8)>>0]|0)+(l?j:j|8)<<1;l=j+-31|0;j=63-j+m|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;if((l|0)<7){UC(0,16,66830,oa);k=-1094995529;j=c[t>>2]|0;g=c[H>>2]|0;break}else{k=(k>>>l)+-1|0;break}}while(0);c[D>>2]=k;r=j+4|0;c[t>>2]=g>>>0>r>>>0?r:g;g=c[Ga>>2]|0;if(c[g+688>>2]&1|0){c[pa>>2]=k;UC(g,48,65257,pa)}c[E>>2]=1;break}case 0:{l=c[H>>2]|0;r=(c[ea>>2]|0)+(m>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(m&7)>>>23;j=(d[86681+r>>0]|0)+m|0;j=l>>>0>j>>>0?j:l;c[t>>2]=j;r=a[87193+r>>0]|0;g=r&255;if((r&255)>31){Ha=89;break a}q=c[f+198268+(g<<2)>>2]|0;if(!q){Ha=89;break a}if(!(c[q+1152>>2]|0))g=j;else{o=q+1168|0;if((c[o>>2]|0)>0){p=q+1172|0;n=c[p>>2]|0;g=j;j=l;m=0;do{do if(!n)g=0;else{k=c[ea>>2]|0;l=k+(g>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(g&7);if((n|0)<26){g=n+g|0;c[t>>2]=j>>>0>g>>>0?g:j;g=l>>>(32-n|0);break}else{Ja=g+16|0;Ja=j>>>0>Ja>>>0?Ja:j;c[t>>2]=Ja;g=n+-16|0;r=k+(Ja>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(Ja&7)>>>(48-n|0);n=Ja+g|0;c[t>>2]=j>>>0>n>>>0?n:j;g=r|l>>>16<<g;break}}while(0);c[f+203404+(m<<2)>>2]=g;n=c[p>>2]|0;j=c[H>>2]|0;g=(c[t>>2]|0)+n|0;g=j>>>0>g>>>0?g:j;c[t>>2]=g;m=m+1|0}while((m|0)<(c[o>>2]|0));l=j}else g=j}if(c[q+1156>>2]|0){p=q+1168|0;if((c[p>>2]|0)>0){o=q+1172|0;n=c[o>>2]|0;m=0;do{do if(!n)g=0;else{j=c[ea>>2]|0;k=j+(g>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(g&7);if((n|0)<26){g=n+g|0;c[t>>2]=l>>>0>g>>>0?g:l;g=k>>>(32-n|0);break}else{r=g+16|0;r=l>>>0>r>>>0?r:l;c[t>>2]=r;g=n+-16|0;Ja=j+(r>>>3)|0;Ja=(MF(d[Ja>>0]|d[Ja+1>>0]<<8|d[Ja+2>>0]<<16|d[Ja+3>>0]<<24|0)|0)<<(r&7)>>>(48-n|0);r=r+g|0;c[t>>2]=l>>>0>r>>>0?r:l;g=Ja|k>>>16<<g;break}}while(0);c[f+203404+(m<<2)>>2]=g;n=c[o>>2]|0;l=c[H>>2]|0;g=(c[t>>2]|0)+n|0;g=l>>>0>g>>>0?g:l;c[t>>2]=g;m=m+1|0}while((m|0)<(c[p>>2]|0))}}c[F>>2]=1;break}case 45:{g=c[H>>2]|0;n=c[ea>>2]|0;k=n+(m>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(m&7);do if(k>>>0>134217727){k=k>>>23;j=(d[86681+k>>0]|0)+m|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;k=d[87193+k>>0]|0}else{j=k>>>0>65535;Ja=j?k>>>16:k;j=j?16:0;l=(Ja&65280|0)==0;j=(d[97868+(l?Ja:Ja>>>8)>>0]|0)+(l?j:j|8)<<1;l=j+-31|0;j=63-j+m|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;if((l|0)<7){UC(0,16,66830,qa);k=-1094995529;j=c[t>>2]|0;n=c[ea>>2]|0;g=c[H>>2]|0;break}else{k=(k>>>l)+-1|0;break}}while(0);c[G>>2]=k;Ja=(d[n+(j>>>3)>>0]|0)<<(j&7)>>>7&1;j=((j|0)<(g|0)&1)+j|0;c[t>>2]=j;c[I>>2]=Ja;c[J>>2]=Ja^1;if(!Ja){r=n+(j>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(j&7)>>>25;j=j+7|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;c[K>>2]=r;c[L>>2]=r;Ja=(d[n+(j>>>3)>>0]|0)<<(j&7)>>>7&1;j=((j|0)<(g|0)&1)+j|0;c[t>>2]=j;c[M>>2]=Ja;c[N>>2]=Ja;q=n+(j>>>3)|0;q=(MF(d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24|0)|0)<<(j&7)>>>26;j=j+6|0;c[O>>2]=q;c[P>>2]=q;j=(g>>>0>j>>>0?j:g)+6|0;j=g>>>0>j>>>0?j:g;if(!((r|0)==5|(Ja|0)!=0)){j=j+16|0;j=g>>>0>j>>>0?j:g}j=j+8|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;k=n+(j>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(j&7);do if(k>>>0>134217727){k=k>>>23;j=(d[86681+k>>0]|0)+j|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;k=d[87193+k>>0]|0}else{Ja=k>>>0>65535;r=Ja?k>>>16:k;Ja=Ja?16:0;l=(r&65280|0)==0;Ja=(d[97868+(l?r:r>>>8)>>0]|0)+(l?Ja:Ja|8)<<1;l=Ja+-31|0;j=63-Ja+j|0;j=g>>>0>j>>>0?j:g;c[t>>2]=j;if((l|0)<7){UC(0,16,66830,ra);k=-1094995529;j=c[t>>2]|0;g=c[H>>2]|0;break}else{k=(k>>>l)+-1|0;break}}while(0);c[Q>>2]=k}Ja=j+1|0;c[t>>2]=g>>>0>Ja>>>0?Ja:g;g=c[Ga>>2]|0;if(c[g+688>>2]&1|0){o=c[I>>2]|0;p=c[L>>2]|0;q=c[N>>2]|0;r=c[P>>2]|0;Ja=c[Q>>2]|0;c[sa>>2]=c[G>>2];c[sa+4>>2]=o;c[sa+8>>2]=p;c[sa+12>>2]=q;c[sa+16>>2]=r;c[sa+20>>2]=Ja;UC(g,48,65337,sa)}break}case 47:{l=c[ea>>2]|0;Ja=(d[l+(m>>>3)>>0]|0)<<(m&7)&128;j=c[H>>2]|0;g=((m|0)<(j|0)&1)+m|0;c[t>>2]=g;c[R>>2]=Ja>>>7^1;if(!Ja){Ja=(d[l+(g>>>3)>>0]|0)<<(g&7)>>>7&1;k=((g|0)<(j|0)&1)+g|0;c[t>>2]=k;c[S>>2]=Ja;g=(d[l+(k>>>3)>>0]|0)<<(k&7)>>>7&1;k=((k|0)<(j|0)&1)+k|0;c[t>>2]=k;c[T>>2]=g;g=l+(k>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(k&7)>>>16;k=k+16|0;k=j>>>0>k>>>0?k:j;c[t>>2]=k;c[U>>2]=g;g=l+(k>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(k&7);if(g>>>0>134217727){g=(d[86681+(g>>>23)>>0]|0)+k|0;g=j>>>0>g>>>0?g:j;c[t>>2]=g}else{Ja=g>>>0>65535;r=Ja?g>>>16:g;Ja=Ja?16:0;g=(r&65280|0)==0;Ja=(d[97868+(g?r:r>>>8)>>0]|0)+(g?Ja:Ja|8)<<1;g=63-Ja+k|0;g=j>>>0>g>>>0?g:j;c[t>>2]=g;if((Ja+-31|0)<7){UC(0,16,66830,ta);g=c[t>>2]|0;j=c[H>>2]|0}}Ja=g+1|0;c[t>>2]=j>>>0>Ja>>>0?Ja:j}break}case 56:{j=c[Ga>>2]|0;g=c[j+688>>2]|0;if(!(g&8388608))l=g;else{UC(j,48,65364,ua);j=c[Ga>>2]|0;m=c[t>>2]|0;l=c[j+688>>2]|0}g=c[H>>2]|0;k=(c[ea>>2]|0)+(m>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(m&7)>>>24;Ja=m+8|0;c[t>>2]=g>>>0>Ja>>>0?Ja:g;g=k&255;a[V>>0]=g;if(l&8388608){c[va>>2]=k;UC(j,48,65397,va);g=a[V>>0]|0}switch(g<<24>>24){case 0:break;case 1:{r=c[t>>2]|0;Ja=c[H>>2]|0;k=c[ea>>2]|0;j=k+(r>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(r&7)>>>24;r=r+8|0;r=Ja>>>0>r>>>0?r:Ja;c[t>>2]=r;g=j&255;a[ca>>0]=g;k=k+(r>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(r&7)>>>16;r=r+16|0;c[t>>2]=Ja>>>0>r>>>0?r:Ja;b[da>>1]=k;k=c[Ga>>2]|0;if(c[k+688>>2]&8388608){c[Aa>>2]=j;UC(k,48,65671,Aa);g=a[ca>>0]|0}if(g<<24>>24)break b;g=c[Ga>>2]|0;if(!(c[g+688>>2]&8388608))break b;h[Ba>>3]=+(e[da>>1]|0)/100.0;UC(g,48,65724,Ba);break b}default:break b}k=c[t>>2]|0;g=c[H>>2]|0;j=(c[ea>>2]|0)+(k>>>3)|0;j=(MF(d[j>>0]|d[j+1>>0]<<8|d[j+2>>0]<<16|d[j+3>>0]<<24|0)|0)<<(k&7)>>>24;k=k+8|0;c[t>>2]=g>>>0>k>>>0?k:g;g=j&255;a[W>>0]=g;k=c[Ga>>2]|0;if(!(c[k+688>>2]&8388608))Ha=138;else{c[wa>>2]=j;UC(k,48,65450,wa);Ja=b[V>>1]|0;g=(Ja&65535)>>>8&255;if((Ja&255)<<24>>24==2){Ja=c[t>>2]|0;j=c[H>>2]|0;g=(c[ea>>2]|0)+(Ja>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(Ja&7)>>>16;Ja=Ja+16|0;c[t>>2]=j>>>0>Ja>>>0?Ja:j;b[ba>>1]=g;j=c[Ga>>2]|0;if(c[j+688>>2]&8388608|0){c[xa>>2]=g;UC(j,48,65503,xa)}}else Ha=138}if((Ha|0)==138){Ha=0;if(g<<24>>24==3){Ja=c[t>>2]|0;j=c[H>>2]|0;g=(c[ea>>2]|0)+(Ja>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(Ja&7)>>>16;Ja=Ja+16|0;c[t>>2]=j>>>0>Ja>>>0?Ja:j;b[X>>1]=g;j=c[Ga>>2]|0;if(c[j+688>>2]&8388608|0){c[ya>>2]=g;UC(j,48,65556,ya)}}}Ja=c[t>>2]|0;j=c[H>>2]|0;g=c[ea>>2]|0;k=g+(Ja>>>3)|0;k=(MF(d[k>>0]|d[k+1>>0]<<8|d[k+2>>0]<<16|d[k+3>>0]<<24|0)|0)<<(Ja&7)>>>24;Ja=Ja+8|0;Ja=j>>>0>Ja>>>0?Ja:j;c[t>>2]=Ja;k=k&255;a[Y>>0]=k;l=g+(Ja>>>3)|0;l=(MF(d[l>>0]|d[l+1>>0]<<8|d[l+2>>0]<<16|d[l+3>>0]<<24|0)|0)<<(Ja&7)>>>24;Ja=Ja+8|0;Ja=j>>>0>Ja>>>0?Ja:j;c[t>>2]=Ja;l=l&255;a[Z>>0]=l;m=g+(Ja>>>3)|0;m=(MF(d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24|0)|0)<<(Ja&7)>>>24;Ja=Ja+8|0;Ja=j>>>0>Ja>>>0?Ja:j;c[t>>2]=Ja;m=m&255;a[_>>0]=m;g=g+(Ja>>>3)|0;g=(MF(d[g>>0]|d[g+1>>0]<<8|d[g+2>>0]<<16|d[g+3>>0]<<24|0)|0)<<(Ja&7)>>>24;Ja=Ja+8|0;c[t>>2]=j>>>0>Ja>>>0?Ja:j;g=g&255;a[$>>0]=g;j=c[Ga>>2]|0;if(c[j+688>>2]&8388608|0){h[za>>3]=+(k&255)/255.0;h[za+8>>3]=+(l&255)/255.0;h[za+16>>3]=+(m&255)/255.0;h[za+24>>3]=+(g&255)/255.0;UC(j,48,65609,za)}break}default:{Ja=c[Ga>>2]|0;c[Ca>>2]=k;UC(Ja,48,65777,Ca)}}while(0);j=c[t>>2]|0;r=0-j|0;k=s-j|0;g=c[H>>2]|0;Ja=g-j|0;j=((k|0)<(r|0)?r:(Ja|0)<(k|0)?Ja:k)+j|0;k=0-j&7;if(k){j=k+j|0;j=g>>>0>j>>>0?j:g}c[t>>2]=j;g=c[u>>2]|0;if((g-j|0)<=16){g=0;Ha=154;break}}if((Ha|0)==12){Ja=c[Ga>>2]|0;c[Fa>>2]=k;c[Fa+4>>2]=j<<3;c[Fa+8>>2]=g;UC(Ja,16,65137,Fa);Ja=-1094995529;i=Ia;return Ja|0}else if((Ha|0)==89){Ja=c[Ga>>2]|0;c[Ea>>2]=g;UC(Ja,16,65285,Ea);Ja=-1094995529;i=Ia;return Ja|0}else if((Ha|0)==150){Ja=-1094995529;i=Ia;return Ja|0}else if((Ha|0)==154){i=Ia;return g|0}return 0}function Yc(a){a=a|0;switch(c[a+203360>>2]|0){case 0:switch(c[a+203364>>2]|0){case 0:{a=(c[a+203372>>2]|0)==2?65798:65814;return a|0}case 1:{a=(c[a+203372>>2]|0)==2?65830:65849;return a|0}case 2:{a=(c[a+203372>>2]|0)==2?65868:65887;return a|0}case 3:{a=(c[a+203372>>2]|0)==2?65906:65917;return a|0}case 4:{a=(c[a+203372>>2]|0)==2?65928:65939;return a|0}case 5:{a=(c[a+203372>>2]|0)==2?65950:65959;return a|0}default:{a=98195;return a|0}}case 1:{a=98195;return a|0}default:{a=0;return a|0}}return 0}function Zc(a){a=a|0;var b=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;k=a+27844|0;h=((c[k>>2]|0)*6|0)+4|0;do if((h|0)>0){i=0;do{f=a+197556+(i<<2)|0;c[f>>2]=a+28596+(i*5632|0);a:do if((i|0)>0){e=a+27932+(i<<4)|0;b=0;while(1){if(!(bE(a+27932+(b<<4)|0,e,16)|0))break;b=b+1|0;if((b|0)>=(i|0)){f=0;m=3;break a}}c[f>>2]=a+28596+(b*5632|0)}else{f=0;m=3}while(0);if((m|0)==3)while(1){m=0;b=(d[65968+f>>0]|0)+2|0;e=d[66056+f>>0]|0;g=0;do{l=g>>2;c[a+28596+(i*5632|0)+(f<<6)+((g<<2&12|l)<<2)>>2]=(_(d[a+27932+(i<<4)+g>>0]|0,d[(l&1)+(g&1)+(66144+(e*3|0))>>0]|0)|0)<<b;g=g+1|0}while((g|0)!=16);f=f+1|0;if((f|0)==(h|0))break;else m=3}i=i+1|0}while((i|0)!=6)}else{l=a+28596|0;c[a+197556>>2]=l;j=a+34228|0;b=a+197560|0;c[b>>2]=j;h=a+27948|0;i=a+27932|0;g=(bE(i,h,16)|0)==0;c[b>>2]=g?l:j;b=a+197564|0;c[b>>2]=a+39860;j=a+27964|0;if(!(bE(i,j,16)|0)){e=0;m=13}else if(!(bE(h,j,16)|0)){e=1;m=13}if((m|0)==13)c[b>>2]=a+28596+(e*5632|0);b=a+197568|0;c[b>>2]=a+45492;g=a+27980|0;if(!(bE(i,g,16)|0)){e=0;m=16}else if(!(bE(h,g,16)|0)){e=1;m=16}else if(!(bE(j,g,16)|0)){e=2;m=16}if((m|0)==16)c[b>>2]=a+28596+(e*5632|0);b=a+197572|0;c[b>>2]=a+51124;f=a+27996|0;if(!(bE(i,f,16)|0)){e=0;m=19}else if(!(bE(h,f,16)|0)){e=1;m=19}else if(!(bE(j,f,16)|0)){e=2;m=19}else if(!(bE(g,f,16)|0)){e=3;m=19}if((m|0)==19)c[b>>2]=a+28596+(e*5632|0);e=a+197576|0;c[e>>2]=a+56756;b=a+28012|0;if(!(bE(i,b,16)|0))b=0;else if(!(bE(h,b,16)|0))b=1;else if(!(bE(j,b,16)|0))b=2;else if(!(bE(g,b,16)|0))b=3;else if(!(bE(f,b,16)|0))b=4;else break;c[e>>2]=a+28596+(b*5632|0)}while(0);l=a+197580|0;c[l>>2]=0;c[l+4>>2]=0;c[l+8>>2]=0;c[l+12>>2]=0;c[l+16>>2]=0;c[l+20>>2]=0;l=a+27928|0;b:do if(c[l>>2]|0){h=((c[k>>2]|0)*6|0)+4|0;if((h|0)>0){i=0;while(1){f=a+197580+(i<<2)|0;c[f>>2]=a+62388+(i*22528|0);c:do if((i|0)>0){e=a+28028+(i<<6)|0;b=0;while(1){if(!(bE(a+28028+(b<<6)|0,e,64)|0))break;b=b+1|0;if((b|0)>=(i|0)){f=0;m=26;break c}}c[f>>2]=a+62388+(b*22528|0)}else{f=0;m=26}while(0);if((m|0)==26)while(1){m=0;b=d[65968+f>>0]|0;e=d[66056+f>>0]|0;g=0;do{c[a+62388+(i*22528|0)+(f<<8)+((g<<3&56|g>>3)<<2)>>2]=(_(d[a+28028+(i<<6)+g>>0]|0,d[(d[66162+(g>>>1&12|g&3)>>0]|0)+(66178+(e*6|0))>>0]|0)|0)<<b;g=g+1|0}while((g|0)!=64);f=f+1|0;if((f|0)==(h|0))break;else m=26}i=i+1|0;if((i|0)==6)break b}}k=a+62388|0;c[a+197580>>2]=k;j=a+84916|0;b=a+197584|0;c[b>>2]=j;h=a+28092|0;i=a+28028|0;g=(bE(i,h,64)|0)==0;c[b>>2]=g?k:j;b=a+197588|0;c[b>>2]=a+107444;j=a+28156|0;if(!(bE(i,j,64)|0)){e=0;m=36}else if(!(bE(h,j,64)|0)){e=1;m=36}if((m|0)==36)c[b>>2]=a+62388+(e*22528|0);b=a+197592|0;c[b>>2]=a+129972;g=a+28220|0;if(!(bE(i,g,64)|0)){e=0;m=39}else if(!(bE(h,g,64)|0)){e=1;m=39}else if(!(bE(j,g,64)|0)){e=2;m=39}if((m|0)==39)c[b>>2]=a+62388+(e*22528|0);b=a+197596|0;c[b>>2]=a+152500;f=a+28284|0;if(!(bE(i,f,64)|0)){e=0;m=42}else if(!(bE(h,f,64)|0)){e=1;m=42}else if(!(bE(j,f,64)|0)){e=2;m=42}else if(!(bE(g,f,64)|0)){e=3;m=42}if((m|0)==42)c[b>>2]=a+62388+(e*22528|0);e=a+197600|0;c[e>>2]=a+175028;b=a+28348|0;if(!(bE(i,b,64)|0))b=0;else if(!(bE(h,b,64)|0))b=1;else if(!(bE(j,b,64)|0))b=2;else if(!(bE(g,b,64)|0))b=3;else if(!(bE(f,b,64)|0))b=4;else break;c[e>>2]=a+62388+(b*22528|0)}while(0);if(!(c[a+26676>>2]|0))return;m=c[a+197556>>2]|0;c[m>>2]=64;c[m+4>>2]=64;c[m+8>>2]=64;c[m+12>>2]=64;c[m+16>>2]=64;c[m+20>>2]=64;c[m+24>>2]=64;c[m+28>>2]=64;c[m+32>>2]=64;c[m+36>>2]=64;c[m+40>>2]=64;c[m+44>>2]=64;c[m+48>>2]=64;c[m+52>>2]=64;c[m+56>>2]=64;c[m+60>>2]=64;m=c[a+197560>>2]|0;c[m>>2]=64;c[m+4>>2]=64;c[m+8>>2]=64;c[m+12>>2]=64;c[m+16>>2]=64;c[m+20>>2]=64;c[m+24>>2]=64;c[m+28>>2]=64;c[m+32>>2]=64;c[m+36>>2]=64;c[m+40>>2]=64;c[m+44>>2]=64;c[m+48>>2]=64;c[m+52>>2]=64;c[m+56>>2]=64;c[m+60>>2]=64;m=c[a+197564>>2]|0;c[m>>2]=64;c[m+4>>2]=64;c[m+8>>2]=64;c[m+12>>2]=64;c[m+16>>2]=64;c[m+20>>2]=64;c[m+24>>2]=64;c[m+28>>2]=64;c[m+32>>2]=64;c[m+36>>2]=64;c[m+40>>2]=64;c[m+44>>2]=64;c[m+48>>2]=64;c[m+52>>2]=64;c[m+56>>2]=64;c[m+60>>2]=64;m=c[a+197568>>2]|0;c[m>>2]=64;c[m+4>>2]=64;c[m+8>>2]=64;c[m+12>>2]=64;c[m+16>>2]=64;c[m+20>>2]=64;c[m+24>>2]=64;c[m+28>>2]=64;c[m+32>>2]=64;c[m+36>>2]=64;c[m+40>>2]=64;c[m+44>>2]=64;c[m+48>>2]=64;c[m+52>>2]=64;c[m+56>>2]=64;c[m+60>>2]=64;m=c[a+197572>>2]|0;c[m>>2]=64;c[m+4>>2]=64;c[m+8>>2]=64;c[m+12>>2]=64;c[m+16>>2]=64;c[m+20>>2]=64;c[m+24>>2]=64;c[m+28>>2]=64;c[m+32>>2]=64;c[m+36>>2]=64;c[m+40>>2]=64;c[m+44>>2]=64;c[m+48>>2]=64;c[m+52>>2]=64;c[m+56>>2]=64;c[m+60>>2]=64;m=c[a+197576>>2]|0;c[m>>2]=64;c[m+4>>2]=64;c[m+8>>2]=64;c[m+12>>2]=64;c[m+16>>2]=64;c[m+20>>2]=64;c[m+24>>2]=64;c[m+28>>2]=64;c[m+32>>2]=64;c[m+36>>2]=64;c[m+40>>2]=64;c[m+44>>2]=64;c[m+48>>2]=64;c[m+52>>2]=64;c[m+56>>2]=64;c[m+60>>2]=64;if(!(c[l>>2]|0))return;b=c[a+197580>>2]|0;e=0;do{c[b+(e<<2)>>2]=64;e=e+1|0}while((e|0)!=64);b=c[a+197584>>2]|0;e=0;do{c[b+(e<<2)>>2]=64;e=e+1|0}while((e|0)!=64);b=c[a+197588>>2]|0;e=0;do{c[b+(e<<2)>>2]=64;e=e+1|0}while((e|0)!=64);b=c[a+197592>>2]|0;e=0;do{c[b+(e<<2)>>2]=64;e=e+1|0}while((e|0)!=64);b=c[a+197596>>2]|0;e=0;do{c[b+(e<<2)>>2]=64;e=e+1|0}while((e|0)!=64);b=c[a+197600>>2]|0;e=0;do{c[b+(e<<2)>>2]=64;e=e+1|0}while((e|0)!=64);return}function _c(b){b=b|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;o=i;i=i+48|0;g=o+40|0;n=o+32|0;l=o+24|0;k=o+16|0;j=o+8|0;h=o;m=b+4|0;f=c[m>>2]|0;e=b+26760|0;c[g>>2]=c[e>>2];c[g+4>>2]=c[e+4>>2];Ez(f,g)|0;zD(c[(c[m>>2]|0)+140>>2]|0,b+25972|0,b+25976|0)|0;if(c[b+26792>>2]|0){g=GF(c[b+26800>>2]|0,0,(c[b+198220>>2]|0)>>>0<44|0)|0;f=c[m>>2]|0;CD(f+900|0,f+896|0,_(c[f+112>>2]|0,c[b+26796>>2]|0)|0,0,g,C,1073741824,0)|0}Ub(b);c[b+197616>>2]=0;c[b+203284>>2]=1;e=0;do{g=d[66214+e>>0]|0;a[b+197644+e>>0]=g<<2&12|g>>>2;g=d[66231+e>>0]|0;a[b+197788+e>>0]=g<<2&12|g>>>2;e=e+1|0}while((e|0)!=16);e=0;do{g=d[71512+e>>0]|0;a[b+197660+e>>0]=g<<3&56|g>>>3;g=d[66248+e>>0]|0;a[b+197724+e>>0]=g<<3&56|g>>>3;g=d[66313+e>>0]|0;a[b+197804+e>>0]=g<<3&56|g>>>3;g=d[66378+e>>0]|0;a[b+197868+e>>0]=g<<3&56|g>>>3;e=e+1|0}while((e|0)!=64);e=b+197932|0;if(!(c[b+26676>>2]|0)){f=b+197644|0;c[e>>2]=c[f>>2];c[e+4>>2]=c[f+4>>2];c[e+8>>2]=c[f+8>>2];c[e+12>>2]=c[f+12>>2];e=b+197948|0;f=b+197660|0;g=e+64|0;do{c[e>>2]=c[f>>2];e=e+4|0;f=f+4|0}while((e|0)<(g|0));e=b+198012|0;f=b+197724|0;g=e+64|0;do{c[e>>2]=c[f>>2];e=e+4|0;f=f+4|0}while((e|0)<(g|0));e=b+198076|0;f=b+197788|0;c[e>>2]=c[f>>2];c[e+4>>2]=c[f+4>>2];c[e+8>>2]=c[f+8>>2];c[e+12>>2]=c[f+12>>2];e=b+198092|0;f=b+197804|0;g=e+64|0;do{c[e>>2]=c[f>>2];e=e+4|0;f=f+4|0}while((e|0)<(g|0));e=b+198156|0;f=b+197868|0;g=e+64|0;do{c[e>>2]=c[f>>2];e=e+4|0;f=f+4|0}while((e|0)<(g|0))}else{f=66214;g=e+16|0;do{a[e>>0]=a[f>>0]|0;e=e+1|0;f=f+1|0}while((e|0)<(g|0));e=b+197948|0;f=71512;g=e+64|0;do{a[e>>0]=a[f>>0]|0;e=e+1|0;f=f+1|0}while((e|0)<(g|0));e=b+198012|0;f=66248;g=e+64|0;do{a[e>>0]=a[f>>0]|0;e=e+1|0;f=f+1|0}while((e|0)<(g|0));e=b+198076|0;f=66231;g=e+16|0;do{a[e>>0]=a[f>>0]|0;e=e+1|0;f=f+1|0}while((e|0)<(g|0));e=b+198092|0;f=66313;g=e+64|0;do{a[e>>0]=a[f>>0]|0;e=e+1|0;f=f+1|0}while((e|0)<(g|0));e=b+198156|0;f=66378;g=e+64|0;do{a[e>>0]=a[f>>0]|0;e=e+1|0;f=f+1|0}while((e|0)<(g|0))}e=bc(b)|0;g=c[m>>2]|0;a:do if((e|0)<0)UC(g,16,66443,h);else{e=c[g+12>>2]|0;do if(e|0)if(c[e+16>>2]&128|0){if((c[b+27844>>2]|0)==8)if((c[b+26672>>2]|0)<=1)break;UC(g,16,66470,j);e=-1094995529;break a}while(0);e=b+27844|0;f=c[e>>2]|0;b:do if((f+-8|0)>>>0<=6){switch(f|0){case 11:case 13:break b;default:{}}c[g+796>>2]=f;c[b+203536>>2]=f;k=b+26672|0;j=c[k>>2]|0;c[b+203532>>2]=j;c[b+25960>>2]=(f|0)>8&1;c[b+198264>>2]=j;c[b+198260>>2]=f;yd(b+16|0,f,j);hd(b+152|0,c[b+27848>>2]|0);Vn(b+184|0,c[e>>2]|0);Wg(b+26020|0,c[(c[m>>2]|0)+48>>2]|0,c[e>>2]|0,c[k>>2]|0);hA(b+8|0,c[e>>2]|0);e=c[b+198228>>2]|0;if((e|0)>-1)e=1;else{k=c[m>>2]|0;c[l>>2]=1;c[l+4>>2]=e;UC(k,24,66547,l)}c[b+203256>>2]=e;l=b+203252|0;k=c[l>>2]|0;c[l>>2]=(k|0)>(e|0)?e:k;e=cc(b,c[b+25952>>2]|0)|0;if((e|0)<0){UC(c[m>>2]|0,16,66591,n);break a}c[b+26004>>2]=1;b=0;i=o;return b|0}while(0);c[k>>2]=f;UC(g,16,66521,k);e=-1094995529}while(0);Ub(b);c[b+26004>>2]=0;b=e;i=o;return b|0}
function lC(a,b){a=a|0;b=b|0;var e=0.0,f=0,g=0.0,j=0,l=0.0,m=0.0,n=0,o=0,p=0.0,q=0.0,s=0,t=0.0,u=0,v=0.0,w=0.0;u=i;i=i+16|0;j=u;do switch(c[b>>2]|0){case 0:{t=+h[b+8>>3];i=u;return +t}case 1:{t=+h[b+8>>3]*+h[(c[a+12>>2]|0)+(c[b+16>>2]<<3)>>3];i=u;return +t}case 2:{q=+h[b+8>>3];s=c[b+16>>2]|0;t=+lC(a,c[b+20>>2]|0);t=q*+ib[s&15](t);i=u;return +t}case 3:{q=+h[b+8>>3];o=c[b+16>>2]|0;s=c[a+36>>2]|0;t=+lC(a,c[b+20>>2]|0);t=q*+gb[o&0](s,t);i=u;return +t}case 4:{p=+h[b+8>>3];o=c[b+16>>2]|0;s=c[a+36>>2]|0;q=+lC(a,c[b+20>>2]|0);t=+lC(a,c[b+24>>2]|0);t=p*+db[o&0](s,q,t);i=u;return +t}case 5:{t=1.0/(+X(+(+lC(a,c[b+20>>2]|0)*4.0))+1.0);i=u;return +t}case 6:{t=+lC(a,c[b+20>>2]|0);t=+X(+(t*t*-.5))/2.5066282746310002;i=u;return +t}case 7:{t=+h[b+8>>3];s=~~+lC(a,c[b+20>>2]|0);i=u;return +(t*+h[(c[a+48>>2]|0)+(((s|0)<0?0:(s|0)>9?9:s)<<3)>>3])}case 8:{t=+h[b+8>>3];q=+N(+(+lC(a,c[b+20>>2]|0)));h[k>>3]=q;a=c[k+4>>2]|0;t=t*+((a>>>0>2146435072|(a|0)==2146435072&(c[k>>2]|0)>>>0>0)&1);i=u;return +t}case 9:{t=+h[b+8>>3];q=+N(+(+lC(a,c[b+20>>2]|0)));h[k>>3]=q;t=t*+(((c[k>>2]|0)==0?(c[k+4>>2]|0)==2146435072:0)&1);i=u;return +t}case 27:{t=+h[b+8>>3]*+M(+(+lC(a,c[b+20>>2]|0)));i=u;return +t}case 28:{t=+h[b+8>>3]*+Z(+(+lC(a,c[b+20>>2]|0)));i=u;return +t}case 29:{t=+h[b+8>>3]*+vF(+lC(a,c[b+20>>2]|0));i=u;return +t}case 30:{t=+h[b+8>>3]*+O(+(+lC(a,c[b+20>>2]|0)));i=u;return +t}case 31:{t=+h[b+8>>3]*+(+lC(a,c[b+20>>2]|0)==0.0&1);i=u;return +t}case 35:{g=+h[b+8>>3];if(+lC(a,c[b+20>>2]|0)!=0.0)e=+lC(a,c[b+24>>2]|0);else{f=c[b+28>>2]|0;if(!f)e=0.0;else e=+lC(a,f)}t=g*e;i=u;return +t}case 36:{g=+h[b+8>>3];if(+lC(a,c[b+20>>2]|0)!=0.0){f=c[b+28>>2]|0;if(!f)e=0.0;else e=+lC(a,f)}else e=+lC(a,c[b+24>>2]|0);t=g*e;i=u;return +t}case 41:{f=b+20|0;e=+lC(a,c[f>>2]|0);g=+lC(a,c[b+24>>2]|0);l=+lC(a,c[b+28>>2]|0);t=+N(+g);h[k>>3]=t;s=c[k+4>>2]|0;if(s>>>0>2146435072|(s|0)==2146435072&(c[k>>2]|0)>>>0>0){t=r;i=u;return +t}t=+N(+l);h[k>>3]=t;s=c[k+4>>2]|0;if(s>>>0>2146435072|(s|0)==2146435072&(c[k>>2]|0)>>>0>0){t=r;i=u;return +t}t=+N(+e);h[k>>3]=t;s=c[k+4>>2]|0;if(s>>>0>2146435072|(s|0)==2146435072&(c[k>>2]|0)>>>0>0|g>l){t=r;i=u;return +t}else{q=+h[b+8>>3];t=+lC(a,c[f>>2]|0);i=u;return +(q*(t<g?g:t>l?l:t))}}case 40:{e=+lC(a,c[b+20>>2]|0);g=+h[b+8>>3];if(!(e>=+lC(a,c[b+24>>2]|0)))e=0.0;else e=+(e<=+lC(a,c[b+28>>2]|0)&1);t=g*e;i=u;return +t}case 37:{e=+lC(a,c[b+20>>2]|0);f=c[b+24>>2]|0;if(!f)f=32;else f=~~+lC(a,f);h[j>>3]=e;UC(a,f,99299,j);t=e;i=u;return +t}case 32:{f=~~+lC(a,c[b+20>>2]|0);f=(c[a+48>>2]|0)+(((f|0)<0?0:(f|0)>9?9:f)<<3)|0;e=+h[f>>3];t=+N(+e);h[k>>3]=t;a=c[k+4>>2]|0;if(a>>>0>2146435072|(a|0)==2146435072&(c[k>>2]|0)>>>0>0)e=1013904223.0;else{a=RF(~~e>>>0|0,(+N(e)>=1.0?(e>0.0?~~+$(+M(e/4294967296.0),4294967295.0)>>>0:~~+Z((e-+(~~e>>>0))/4294967296.0)>>>0):0)|0,1664525,0)|0;a=HF(a|0,C|0,1013904223,0)|0;e=+(a>>>0)+4294967296.0*+(C>>>0)}h[f>>3]=e;t=e*5.421010862427522e-20*+h[b+8>>3];i=u;return +t}case 24:{j=b+20|0;if(!(+lC(a,c[j>>2]|0)!=0.0)){t=r;i=u;return +t}f=b+24|0;do e=+lC(a,c[f>>2]|0);while(+lC(a,c[j>>2]|0)!=0.0);i=u;return +e}case 25:{m=+lC(a,c[b+24>>2]|0);f=c[b+28>>2]|0;if(!f)o=0;else{o=~~+lC(a,f);o=(o|0)<0?0:(o|0)>9?9:o}n=a+48|0;s=c[n>>2]|0;p=+h[s+(o<<3)>>3];j=b+20|0;h[s+(o<<3)>>3]=0.0;e=+lC(a,c[j>>2]|0);a:do if(!(e!=0.0&e==0.0)){f=0;l=1.0;while(1){f=f+1|0;if((f|0)>=1e3)break a;l=l*(m/+(f|0));h[(c[n>>2]|0)+(o<<3)>>3]=+(f|0);t=+lC(a,c[j>>2]|0);g=e+l*t;if(t!=0.0&e==g){e=g;break}else e=g}}while(0);h[(c[n>>2]|0)+(o<<3)>>3]=p;t=e;i=u;return +t}case 26:{s=a+48|0;t=+h[c[s>>2]>>3];q=+lC(a,c[b+24>>2]|0);n=b+20|0;l=-1.0;p=1797693134862315708145274.0e284;j=-1;g=-1.0;e=-1797693134862315708145274.0e284;while(1){do if((j|0)<255)h[c[s>>2]>>3]=q*+(d[104547+(j&255)>>0]|0)/255.0;else{w=q*+P(.9,+(+(j+-255|0)));f=c[s>>2]|0;b=(j&1|0)==0;v=-w;m=b?w:v;h[f>>3]=b?w:v;if(!(j&2)){h[f>>3]=l+m;break}else{h[f>>3]=g+m;break}}while(0);m=+lC(a,c[n>>2]|0);if(m<=0.0&m>e){g=+h[c[s>>2]>>3];e=m}if(m>=0.0&m<p)l=+h[c[s>>2]>>3];else m=p;j=j+1|0;if(g>=0.0&l>=0.0){o=61;break}if((j|0)>=1024)break;else p=m}b:do if((o|0)==61){f=0;do{w=(g+l)*.5;h[c[s>>2]>>3]=w;if(g==w|l==w)break b;p=+lC(a,c[n>>2]|0);if(p<=0.0)g=+h[c[s>>2]>>3];if(p>=0.0)l=+h[c[s>>2]>>3];w=+N(+p);h[k>>3]=w;b=c[k+4>>2]|0;f=f+1|0;if(b>>>0>2146435072|(b|0)==2146435072&(c[k>>2]|0)>>>0>0){l=p;g=p;break b}}while((f|0)<1e3)}while(0);h[c[s>>2]>>3]=t;w=m>-e?g:l;i=u;return +w}default:{e=+lC(a,c[b+20>>2]|0);g=+lC(a,c[b+24>>2]|0);do switch(c[b>>2]|0){case 10:{w=+h[b+8>>3]*(e-g*+M(+(e/g)));i=u;return +w}case 34:{w=+h[b+8>>3]*(+((WC(~~e>>>0,+N(e)>=1.0?(e>0.0?~~+$(+M(e/4294967296.0),4294967295.0)>>>0:~~+Z((e-+(~~e>>>0))/4294967296.0)>>>0):0,~~g>>>0,+N(g)>=1.0?(g>0.0?~~+$(+M(g/4294967296.0),4294967295.0)>>>0:~~+Z((g-+(~~g>>>0))/4294967296.0)>>>0):0)|0)>>>0)+4294967296.0*+(C|0));i=u;return +w}case 11:{w=(e>g?e:g)*+h[b+8>>3];i=u;return +w}case 12:{w=(e<g?e:g)*+h[b+8>>3];i=u;return +w}case 13:{w=(e==g?1.0:0.0)*+h[b+8>>3];i=u;return +w}case 14:{w=(e>g?1.0:0.0)*+h[b+8>>3];i=u;return +w}case 15:{w=(e>=g?1.0:0.0)*+h[b+8>>3];i=u;return +w}case 17:{w=(e<g?1.0:0.0)*+h[b+8>>3];i=u;return +w}case 16:{w=(e<=g?1.0:0.0)*+h[b+8>>3];i=u;return +w}case 18:{w=+h[b+8>>3]*+P(+e,+g);i=u;return +w}case 19:{w=e*g*+h[b+8>>3];i=u;return +w}case 20:{w=e/g*+h[b+8>>3];i=u;return +w}case 21:{w=(e+g)*+h[b+8>>3];i=u;return +w}case 22:{w=g*+h[b+8>>3];i=u;return +w}case 23:{w=+h[b+8>>3];s=~~e;h[(c[a+48>>2]|0)+(((s|0)<0?0:(s|0)>9?9:s)<<3)>>3]=g;w=g*w;i=u;return +w}case 33:{w=+h[b+8>>3]*+O(+(e*e+g*g));i=u;return +w}case 38:{w=+N(+e);h[k>>3]=w;a=c[k+4>>2]|0;if(a>>>0>2146435072|(a|0)==2146435072&(c[k>>2]|0)>>>0>0){w=r;i=u;return +w}w=+N(+g);h[k>>3]=w;a=c[k+4>>2]|0;if(a>>>0>2146435072|(a|0)==2146435072&(c[k>>2]|0)>>>0>0){w=r;i=u;return +w}w=+(~~g&~~e|0)*+h[b+8>>3];i=u;return +w}case 39:{w=+N(+e);h[k>>3]=w;a=c[k+4>>2]|0;if(a>>>0>2146435072|(a|0)==2146435072&(c[k>>2]|0)>>>0>0){w=r;i=u;return +w}w=+N(+g);h[k>>3]=w;a=c[k+4>>2]|0;if(a>>>0>2146435072|(a|0)==2146435072&(c[k>>2]|0)>>>0>0){w=r;i=u;return +w}w=+(~~g|~~e|0)*+h[b+8>>3];i=u;return +w}default:{w=r;i=u;return +w}}while(0)}}while(0);return 0.0}function mC(a,b,d,e,f,g,j,k,l,m,n){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;var o=0,p=0,q=0;q=i;i=i+64|0;p=q+4|0;o=q;c[o>>2]=0;d=dC(o,b,d,f,g,j,k,m,n)|0;if((d|0)<0){h[a>>3]=r;p=d;i=q;return p|0}else{d=c[o>>2]|0;f=p;j=f+48|0;do{c[f>>2]=0;f=f+4|0}while((f|0)<(j|0));c[p+48>>2]=c[d+32>>2];c[p+12>>2]=e;c[p+36>>2]=l;h[a>>3]=+lC(p,d);cC(d);p=a;l=c[p+4>>2]&2147483647;p=l>>>0>2146435072|(l|0)==2146435072&(c[p>>2]|0)>>>0>0?-22:0;i=q;return p|0}return 0}function nC(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0;h=i;i=i+48|0;g=h+8|0;f=h;e=h+24|0;c[e>>2]=d;if(!(b&64))e=0;else{j=(c[e>>2]|0)+(4-1)&~(4-1);d=c[j>>2]|0;c[e>>2]=j+4;e=d}c[f>>2]=e;e=cE(a,b|524288,f)|0;if((e|0)==-1){i=h;return e|0}c[g>>2]=1;if((rE(e,2,g)|0)!=-1){i=h;return e|0}UC(0,48,99303,h+16|0);i=h;return e|0}function oC(a,b,d){a=a|0;b=b|0;d=d|0;a=a+432|0;c[a>>2]=b;c[a+4>>2]=d;return}function pC(a,b,d){a=a|0;b=b|0;d=d|0;a=a+448|0;c[a>>2]=b;c[a+4>>2]=d;return}function qC(a,b,d){a=a|0;b=b|0;d=d|0;a=a+440|0;c[a>>2]=b;c[a+4>>2]=d;return}function rC(a){a=a|0;return c[a+464>>2]|0}function sC(a,b){a=a|0;b=b|0;c[a+464>>2]=b;return}function tC(a,b){a=a|0;b=b|0;c[a+468>>2]=b;return}function uC(a){a=a|0;return c[a+424>>2]|0}function vC(a,b){a=a|0;b=b|0;c[a+424>>2]=b;return}function wC(a){a=a|0;return c[a+412>>2]|0}function xC(a,b){a=a|0;b=b|0;c[a+412>>2]=b;return}function yC(a){a=a|0;return a+456|0}function zC(){var a=0,b=0,d=0;a=cD(480)|0;if(!a){a=0;return a|0}b=a+64|0;c[b>>2]=0;bD(b);FF(a|0,0,480)|0;d=a+144|0;c[d>>2]=0;c[d+4>>2]=-2147483648;d=a+152|0;c[d>>2]=0;c[d+4>>2]=-2147483648;d=a+136|0;c[d>>2]=0;c[d+4>>2]=-2147483648;d=a+432|0;c[d>>2]=0;c[d+4>>2]=-2147483648;d=a+448|0;c[d>>2]=0;c[d+4>>2]=0;d=a+440|0;c[d>>2]=-1;c[d+4>>2]=-1;c[a+468>>2]=-1;c[a+84>>2]=1;c[a+124>>2]=0;c[a+128>>2]=1;c[a+80>>2]=-1;c[b>>2]=a;c[a+416>>2]=2;c[a+420>>2]=2;c[a+424>>2]=2;c[a+412>>2]=0;c[a+428>>2]=0;return a|0}function AC(a){a=a|0;var b=0;if(!a)return;b=c[a>>2]|0;if(!b)return;BC(b);bD(a);return}function BC(a){a=a|0;var b=0,d=0,e=0,f=0,g=0;if(!a)return;b=a+404|0;d=a+400|0;if((c[b>>2]|0)>0){e=0;do{f=(c[d>>2]|0)+(e<<2)|0;g=c[f>>2]|0;NB(g+16|0);ZB(g+12|0);bD(f);e=e+1|0}while((e|0)<(c[b>>2]|0))}c[b>>2]=0;bD(d);NB(a+360|0);NB(a+364|0);NB(a+368|0);NB(a+372|0);NB(a+376|0);NB(a+380|0);NB(a+384|0);NB(a+388|0);b=a+396|0;d=a+392|0;if((c[b>>2]|0)>0){e=0;do{NB((c[d>>2]|0)+(e<<2)|0);e=e+1|0}while((e|0)<(c[b>>2]|0))}bD(d);ZB(a+456|0);NB(a+472|0);b=a+64|0;if((c[b>>2]|0)!=(a|0))bD(b);FF(a|0,0,480)|0;g=a+144|0;c[g>>2]=0;c[g+4>>2]=-2147483648;g=a+152|0;c[g>>2]=0;c[g+4>>2]=-2147483648;g=a+136|0;c[g>>2]=0;c[g+4>>2]=-2147483648;g=a+432|0;c[g>>2]=0;c[g+4>>2]=-2147483648;g=a+448|0;c[g>>2]=0;c[g+4>>2]=0;g=a+440|0;c[g>>2]=-1;c[g+4>>2]=-1;c[a+468>>2]=-1;c[a+84>>2]=1;c[a+124>>2]=0;c[a+128>>2]=1;c[a+80>>2]=-1;c[b>>2]=a;c[a+416>>2]=2;c[a+420>>2]=2;c[a+424>>2]=2;c[a+412>>2]=0;c[a+428>>2]=0;return}function CC(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;n=b+80|0;f=c[n>>2]|0;if((f|0)<0){b=-22;return b|0}h=b+68|0;if((c[h>>2]|0)>0){o=b+72|0;if((c[o>>2]|0)>0){p=yD(f)|0;if(!p){b=-22;return b|0}f=NC(c[h>>2]|0,c[o>>2]|0,0,0)|0;if((f|0)<0){b=f;return b|0}j=b+32|0;f=c[j>>2]|0;if(!f)if((e|0)<1)r=16;else{l=e+-1|0;g=1;while(1){f=KC(j,c[n>>2]|0,g+-1+(c[h>>2]|0)&0-g)|0;if((f|0)<0){q=f;r=55;break}f=c[j>>2]|0;g=g<<1;if((g|0)>(e|0)|(f&l|0)==0){k=f;break}}if((r|0)==55)return q|0;i=0-e|0;if(!k)r=16;else{h=k+l&i;c[j>>2]=h;f=b+36|0;g=c[f>>2]|0;if(g|0){c[f>>2]=g+l&i;f=b+40|0;g=c[f>>2]|0;if(g|0){c[f>>2]=g+l&i;f=b+44|0;g=c[f>>2]|0;if(g|0)c[f>>2]=g+l&i}}if(!h)r=16;else{m=h;r=14}}}else{m=f;r=14}if((r|0)==14){g=p+6|0;f=KB(_((c[o>>2]|0)+31&-32,m)|0|31)|0;c[b+360>>2]=f;if(f|0){c[b>>2]=c[f+4>>2];f=c[b+36>>2]|0;if(!f)r=16;else{f=KB(31-(_(0-((c[o>>2]|0)+31&-32)>>(d[g>>0]|0),f)|0)|0)|0;c[b+364>>2]=f;if(f|0){c[b+4>>2]=c[f+4>>2];f=c[b+40>>2]|0;if(!f)r=16;else{f=KB(31-(_(0-((c[o>>2]|0)+31&-32)>>(d[g>>0]|0),f)|0)|0)|0;c[b+368>>2]=f;if(f|0){c[b+8>>2]=c[f+4>>2];f=c[b+44>>2]|0;if(!f)r=16;else{f=KB(_((c[o>>2]|0)+31&-32,f)|0|31)|0;c[b+372>>2]=f;if(f|0){c[b+12>>2]=c[f+4>>2];r=16}}}}}}}}do if((r|0)==16){if(a[p+7>>0]&66){r=b+364|0;NB(r);f=KB(1024)|0;c[r>>2]=f;if(!f)break;c[b+4>>2]=c[f+4>>2]}c[b+64>>2]=b;b=0;return b|0}while(0);BC(b);b=-12;return b|0}}j=b+76|0;if((c[j>>2]|0)<=0){b=-22;return b|0}g=b+352|0;p=g;h=b+464|0;if((c[p>>2]|0)==0&(c[p+4>>2]|0)==0)if((c[h>>2]|0)<=0){b=-22;return b|0}i=ID(f)|0;f=c[h>>2]|0;if(!f){f=g;f=VB(c[f>>2]|0,c[f+4>>2]|0)|0;c[h>>2]=f}l=i|0?f:1;m=b+32|0;if(!(c[m>>2]|0)){f=JD(m,f,c[j>>2]|0,c[n>>2]|0,e)|0;if((f|0)<0){b=f;return b|0}}k=(l|0)>8;do if(k){if(l>>>0<536870911)f=cD(l<<2)|0;else f=0;h=b+64|0;c[h>>2]=f;g=l+-8|0;if(g>>>0<536870911){p=cD(g<<2)|0;f=b+392|0;c[f>>2]=p;if(!((p|0)==0|(c[h>>2]|0)==0)){c[b+396>>2]=g;f=h;h=8;break}}else{f=b+392|0;c[f>>2]=0}bD(h);bD(f);b=-12;return b|0}else{f=b+64|0;c[f>>2]=b;if((l|0)>0)h=l;else{b=0;return b|0}}while(0);i=0;do{g=KB(c[m>>2]|0)|0;c[b+360+(i<<2)>>2]=g;if(!g){r=50;break}p=c[g+4>>2]|0;c[b+(i<<2)>>2]=p;c[(c[f>>2]|0)+(i<<2)>>2]=p;i=i+1|0}while((i|0)<(h|0));if((r|0)==50){BC(b);b=-12;return b|0}j=l+-8|0;if(!k){b=0;return b|0}g=b+392|0;i=0;while(1){h=KB(c[m>>2]|0)|0;c[(c[g>>2]|0)+(i<<2)>>2]=h;h=c[(c[g>>2]|0)+(i<<2)>>2]|0;if(!h)break;c[(c[f>>2]|0)+(i+8<<2)>>2]=c[h+4>>2];i=i+1|0;if((i|0)>=(j|0)){q=0;r=55;break}}if((r|0)==55)return q|0;BC(b);b=-12;return b|0}function DC(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0;c[a+80>>2]=c[b+80>>2];c[a+68>>2]=c[b+68>>2];c[a+72>>2]=c[b+72>>2];h=b+464|0;c[a+464>>2]=c[h>>2];f=b+352|0;g=c[f+4>>2]|0;d=a+352|0;c[d>>2]=c[f>>2];c[d+4>>2]=g;c[a+76>>2]=c[b+76>>2];d=EC(a,b,0)|0;if((d|0)<0){a=d;return a|0}d=c[b+360>>2]|0;if(!d){d=CC(a,32)|0;if((d|0)<0){a=d;return a|0}d=GC(a,b)|0;if((d|0)>=0){a=d;return a|0}BC(a);a=d;return a|0}g=MB(d)|0;c[a+360>>2]=g;a:do if(!g)d=-12;else{d=c[b+364>>2]|0;if(d|0){g=MB(d)|0;c[a+364>>2]=g;if(!g){d=-12;break}}d=c[b+368>>2]|0;if(d|0){g=MB(d)|0;c[a+368>>2]=g;if(!g){d=-12;break}}d=c[b+372>>2]|0;if(d|0){g=MB(d)|0;c[a+372>>2]=g;if(!g){d=-12;break}}d=c[b+376>>2]|0;if(d|0){g=MB(d)|0;c[a+376>>2]=g;if(!g){d=-12;break}}d=c[b+380>>2]|0;if(d|0){g=MB(d)|0;c[a+380>>2]=g;if(!g){d=-12;break}}d=c[b+384>>2]|0;if(d|0){g=MB(d)|0;c[a+384>>2]=g;if(!g){d=-12;break}}d=c[b+388>>2]|0;if(d|0){g=MB(d)|0;c[a+388>>2]=g;if(!g){d=-12;break}}g=b+392|0;b:do if(c[g>>2]|0){f=b+396|0;d=c[f>>2]|0;if(d|0)if((2147483647/(d>>>0)|0)>>>0>4){e=cD(d<<2)|0;d=a+392|0;c[d>>2]=e;if(!e){d=-12;break a}c[a+396>>2]=c[f>>2];if((c[f>>2]|0)>0)e=0;else break;while(1){i=MB(c[(c[g>>2]|0)+(e<<2)>>2]|0)|0;c[(c[d>>2]|0)+(e<<2)>>2]=i;if(!(c[(c[d>>2]|0)+(e<<2)>>2]|0)){d=-12;break a}e=e+1|0;if((e|0)>=(c[f>>2]|0))break b}}c[a+392>>2]=0;d=-12;break a}while(0);f=b+64|0;if((c[f>>2]|0)==(b|0))c[a+64>>2]=a;else{d=c[h>>2]|0;if(!d){d=-22;break}if((2147483647/(d>>>0)|0)>>>0<5){c[a+64>>2]=0;d=-12;break}d=d<<2;e=YC(d)|0;c[a+64>>2]=e;if(!e){d=-12;break}KF(e|0,c[f>>2]|0,d|0)|0};c[a>>2]=c[b>>2];c[a+4>>2]=c[b+4>>2];c[a+8>>2]=c[b+8>>2];c[a+12>>2]=c[b+12>>2];c[a+16>>2]=c[b+16>>2];c[a+20>>2]=c[b+20>>2];c[a+24>>2]=c[b+24>>2];c[a+28>>2]=c[b+28>>2];i=a+32|0;a=b+32|0;c[i>>2]=c[a>>2];c[i+4>>2]=c[a+4>>2];c[i+8>>2]=c[a+8>>2];c[i+12>>2]=c[a+12>>2];c[i+16>>2]=c[a+16>>2];c[i+20>>2]=c[a+20>>2];c[i+24>>2]=c[a+24>>2];c[i+28>>2]=c[a+28>>2];i=0;return i|0}while(0);BC(a);i=d;return i|0}function EC(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;c[a+84>>2]=c[b+84>>2];c[a+88>>2]=c[b+88>>2];e=b+124|0;f=c[e+4>>2]|0;g=a+124|0;c[g>>2]=c[e>>2];c[g+4>>2]=f;g=b+136|0;f=c[g+4>>2]|0;e=a+136|0;c[e>>2]=c[g>>2];c[e+4>>2]=f;c[a+292>>2]=c[b+292>>2];c[a+296>>2]=c[b+296>>2];c[a+300>>2]=c[b+300>>2];c[a+304>>2]=c[b+304>>2];c[a+344>>2]=c[b+344>>2];c[a+216>>2]=c[b+216>>2];c[a+288>>2]=c[b+288>>2];e=b+144|0;f=c[e+4>>2]|0;g=a+144|0;c[g>>2]=c[e>>2];c[g+4>>2]=f;g=b+152|0;f=c[g+4>>2]|0;e=a+152|0;c[e>>2]=c[g>>2];c[e+4>>2]=f;e=b+440|0;f=c[e+4>>2]|0;g=a+440|0;c[g>>2]=c[e>>2];c[g+4>>2]=f;c[a+468>>2]=c[b+468>>2];g=b+448|0;f=c[g+4>>2]|0;e=a+448|0;c[e>>2]=c[g>>2];c[e+4>>2]=f;e=b+320|0;f=c[e+4>>2]|0;g=a+320|0;c[g>>2]=c[e>>2];c[g+4>>2]=f;c[a+168>>2]=c[b+168>>2];g=b+432|0;f=c[g+4>>2]|0;e=a+432|0;c[e>>2]=c[g>>2];c[e+4>>2]=f;c[a+160>>2]=c[b+160>>2];c[a+164>>2]=c[b+164>>2];c[a+408>>2]=c[b+408>>2];c[a+460>>2]=c[b+460>>2];c[a+416>>2]=c[b+416>>2];c[a+420>>2]=c[b+420>>2];c[a+424>>2]=c[b+424>>2];c[a+412>>2]=c[b+412>>2];c[a+428>>2]=c[b+428>>2];_B(a+456|0,c[b+456>>2]|0,0);e=a+224|0;f=b+224|0;g=e+64|0;do{c[e>>2]=c[f>>2];e=e+4|0;f=f+4|0}while((e|0)<(g|0));i=b+404|0;e=c[i>>2]|0;a:do if((e|0)>0){j=b+400|0;k=b+68|0;l=a+68|0;m=b+72|0;n=a+72|0;o=a+404|0;p=a+400|0;if(!d)h=0;else{h=0;while(1){d=c[(c[j>>2]|0)+(h<<2)>>2]|0;f=c[d>>2]|0;if(!f){if((c[k>>2]|0)==(c[l>>2]|0))if((c[m>>2]|0)==(c[n>>2]|0))q=15}else q=15;if((q|0)==15){q=0;g=d+8|0;e=FC(a,f,c[g>>2]|0)|0;if(!e)break;KF(c[e+4>>2]|0,c[d+4>>2]|0,c[g>>2]|0)|0;_B(e+12|0,c[d+12>>2]|0,0);e=c[i>>2]|0}h=h+1|0;if((h|0)>=(e|0))break a}if((c[o>>2]|0)>0){e=0;do{b=(c[p>>2]|0)+(e<<2)|0;a=c[b>>2]|0;NB(a+16|0);ZB(a+12|0);bD(b);e=e+1|0}while((e|0)<(c[o>>2]|0))}c[o>>2]=0;bD(p);b=-12;return b|0}while(1){d=c[(c[j>>2]|0)+(h<<2)>>2]|0;g=c[d>>2]|0;if(!g){if((c[k>>2]|0)==(c[l>>2]|0))if((c[m>>2]|0)==(c[n>>2]|0))q=6}else q=6;if((q|0)==6){q=0;e=c[o>>2]|0;if(e>>>0>536870910)break;e=ZC(c[p>>2]|0,(e<<2)+4|0)|0;if(!e){q=20;break}c[p>>2]=e;f=cD(20)|0;if(!f){q=20;break}c[f>>2]=g;e=c[o>>2]|0;c[o>>2]=e+1;c[(c[p>>2]|0)+(e<<2)>>2]=f;e=MB(c[d+16>>2]|0)|0;c[f+16>>2]=e;if(!e){q=24;break}c[f+4>>2]=c[e+4>>2];c[f+8>>2]=c[e+8>>2];_B(f+12|0,c[d+12>>2]|0,0);e=c[i>>2]|0}h=h+1|0;if((h|0)>=(e|0))break a}if((q|0)==20)e=c[o>>2]|0;else if((q|0)==24){if((c[o>>2]|0)>0){e=0;do{b=(c[p>>2]|0)+(e<<2)|0;a=c[b>>2]|0;NB(a+16|0);ZB(a+12|0);bD(b);e=e+1|0}while((e|0)<(c[o>>2]|0))}c[o>>2]=0;bD(p);b=-12;return b|0}if((e|0)>0){e=0;do{b=(c[p>>2]|0)+(e<<2)|0;a=c[b>>2]|0;NB(a+16|0);ZB(a+12|0);bD(b);e=e+1|0}while((e|0)<(c[o>>2]|0))}c[o>>2]=0;bD(p);b=-12;return b|0}while(0);f=a+176|0;c[f>>2]=0;g=a+180|0;c[g>>2]=0;d=a+184|0;c[d>>2]=0;e=c[b+472>>2]|0;if(!e){b=0;return b|0}e=MB(e)|0;c[a+472>>2]=e;if(!e){b=0;return b|0}c[f>>2]=c[e+4>>2];c[g>>2]=c[b+180>>2];c[d>>2]=c[b+184>>2];b=0;return b|0}function FC(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0;j=i;i=i+16|0;h=j;g=a+404|0;e=c[g>>2]|0;if(e>>>0>536870910){b=0;i=j;return b|0}f=a+400|0;a=ZC(c[f>>2]|0,(e<<2)+4|0)|0;if(!a){b=0;i=j;return b|0}c[f>>2]=a;e=cD(20)|0;c[h>>2]=e;if(!e){b=0;i=j;return b|0}a=e;do if((d|0)>0){c[e+16>>2]=KB(d)|0;a=c[h>>2]|0;e=c[a+16>>2]|0;if(e|0){c[a+4>>2]=c[e+4>>2];a=c[h>>2]|0;c[a+8>>2]=d;e=a;break}bD(h);b=0;i=j;return b|0}while(0);c[e>>2]=b;b=c[g>>2]|0;c[g>>2]=b+1;c[(c[f>>2]|0)+(b<<2)>>2]=a;b=c[h>>2]|0;i=j;return b|0}function GC(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0;m=i;i=i+16|0;j=m;l=a+80|0;g=c[l>>2]|0;if((g|0)<0?1:(g|0)!=(c[b+80>>2]|0)){l=-22;i=m;return l|0}d=c[a+68>>2]|0;if((d|0)>0){e=c[a+72>>2]|0;if((e|0)>0){h=b+68|0;a:do if((d|0)<(c[h>>2]|0))d=-22;else{f=b+72|0;if((e|0)<(c[f>>2]|0))d=-22;else{d=AD(g)|0;if((d|0)>0){e=0;do{if(!(c[a+(e<<2)>>2]|0)){d=-22;break a}if(!(c[b+(e<<2)>>2]|0)){d=-22;break a}e=e+1|0}while((e|0)<(d|0))};c[j>>2]=c[b>>2];c[j+4>>2]=c[b+4>>2];c[j+8>>2]=c[b+8>>2];c[j+12>>2]=c[b+12>>2];PC(a,a+32|0,j,b+32|0,c[l>>2]|0,c[h>>2]|0,c[f>>2]|0);d=0}}while(0);l=d;i=m;return l|0}}d=a+76|0;if((c[d>>2]|0)<=0){l=-22;i=m;return l|0}e=a+352|0;k=e;if((c[k>>2]|0)==0&(c[k+4>>2]|0)==0){l=-22;i=m;return l|0}h=ID(g)|0;k=c[a+464>>2]|0;h=h|0?k:1;j=c[d>>2]|0;if((j|0)!=(c[b+76>>2]|0)){l=-22;i=m;return l|0}if((k|0)!=(c[b+464>>2]|0)){l=-22;i=m;return l|0}f=e;g=b+352|0;if(!((c[f>>2]|0)==(c[g>>2]|0)?(c[f+4>>2]|0)==(c[g+4>>2]|0):0)){l=-22;i=m;return l|0}g=c[a+64>>2]|0;e=b+64|0;b:do if((h|0)>0){f=0;while(1){if(!(c[g+(f<<2)>>2]|0)){d=-22;e=23;break}d=c[e>>2]|0;if(!(c[d+(f<<2)>>2]|0)){d=-22;e=23;break}f=f+1|0;if((f|0)>=(h|0))break b}if((e|0)==23){i=m;return d|0}}else d=c[e>>2]|0;while(0);KD(g,d,0,0,j,k,c[l>>2]|0)|0;l=0;i=m;return l|0}function HC(a,b){a=a|0;b=b|0;var d=0;KF(a|0,b|0,480)|0;d=b+64|0;if((c[d>>2]|0)==(b|0))c[a+64>>2]=a;FF(b|0,0,480)|0;bD(d);FF(b|0,0,480)|0;a=b+144|0;c[a>>2]=0;c[a+4>>2]=-2147483648;a=b+152|0;c[a>>2]=0;c[a+4>>2]=-2147483648;a=b+136|0;c[a>>2]=0;c[a+4>>2]=-2147483648;a=b+432|0;c[a>>2]=0;c[a+4>>2]=-2147483648;a=b+448|0;c[a>>2]=0;c[a+4>>2]=0;a=b+440|0;c[a>>2]=-1;c[a+4>>2]=-1;c[b+468>>2]=-1;c[b+84>>2]=1;c[b+124>>2]=0;c[b+128>>2]=1;c[b+80>>2]=-1;c[d>>2]=b;c[b+416>>2]=2;c[b+420>>2]=2;c[b+424>>2]=2;c[b+412>>2]=0;c[b+428>>2]=0;return}function IC(a,b){a=a|0;b=b|0;return EC(a,b,1)|0}function JC(a,d,e){a=a|0;d=d|0;e=e|0;var f=0,g=0,h=0;c[a>>2]=0;c[a+4>>2]=0;c[a+8>>2]=0;c[a+12>>2]=0;if(!d){g=b[e+8>>1]|0;f=(g&65535)>>>2&7;g=a+((g&3)<<2)|0;if((f|0)>=(c[g>>2]|0))c[g>>2]=f+1;g=b[e+10>>1]|0;f=(g&65535)>>>2&7;g=a+((g&3)<<2)|0;if((f|0)>=(c[g>>2]|0))c[g>>2]=f+1;g=b[e+12>>1]|0;f=(g&65535)>>>2&7;g=a+((g&3)<<2)|0;if((f|0)>=(c[g>>2]|0))c[g>>2]=f+1;f=b[e+14>>1]|0;g=(f&65535)>>>2&7;f=a+((f&3)<<2)|0;if((g|0)<(c[f>>2]|0))return;c[f>>2]=g+1;return}else{c[d>>2]=0;c[d+4>>2]=0;c[d+8>>2]=0;c[d+12>>2]=0;f=e+8|0;h=b[f>>1]|0;g=(h&65535)>>>2&7;h=a+((h&3)<<2)|0;if((g|0)>=(c[h>>2]|0)){c[h>>2]=g+1;c[d+((b[f>>1]&3)<<2)>>2]=0}f=e+10|0;h=b[f>>1]|0;g=(h&65535)>>>2&7;h=a+((h&3)<<2)|0;if((g|0)>=(c[h>>2]|0)){c[h>>2]=g+1;c[d+((b[f>>1]&3)<<2)>>2]=1}f=e+12|0;h=b[f>>1]|0;g=(h&65535)>>>2&7;h=a+((h&3)<<2)|0;if((g|0)>=(c[h>>2]|0)){c[h>>2]=g+1;c[d+((b[f>>1]&3)<<2)>>2]=2}g=e+14|0;f=b[g>>1]|0;h=(f&65535)>>>2&7;f=a+((f&3)<<2)|0;if((h|0)<(c[f>>2]|0))return;c[f>>2]=h+1;c[d+((b[g>>1]&3)<<2)>>2]=3;return}}function KC(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0;m=i;i=i+32|0;k=m+16|0;l=m;e=yD(e)|0;c[b>>2]=0;c[b+4>>2]=0;c[b+8>>2]=0;c[b+12>>2]=0;if(!e){b=-22;i=m;return b|0}g=e+7|0;if(a[g>>0]&8){b=-22;i=m;return b|0}JC(k,l,e);h=e+5|0;j=f+-1|0;if((f|0)<0){b=-22;i=m;return b|0}f=c[k>>2]|0;if(((c[l>>2]|0)+-1|0)>>>0<2)e=d[h>>0]|0;else e=0;e=j+(1<<e)>>e;if(e|0)if((2147483647/(e|0)|0|0)<(f|0)){b=-22;i=m;return b|0}e=_(e,f)|0;g=(a[g>>0]&4)==0;e=g?e:e+7>>3;if((e|0)<0){b=e;i=m;return b|0}c[b>>2]=e;f=c[k+4>>2]|0;if(((c[l+4>>2]|0)+-1|0)>>>0<2)e=d[h>>0]|0;else e=0;e=j+(1<<e)>>e;if(e|0)if((2147483647/(e|0)|0|0)<(f|0)){b=-22;i=m;return b|0}e=_(e,f)|0;e=g?e:e+7>>3;if((e|0)<0){b=e;i=m;return b|0}c[b+4>>2]=e;f=c[k+8>>2]|0;if(((c[l+8>>2]|0)+-1|0)>>>0<2)e=d[h>>0]|0;else e=0;e=j+(1<<e)>>e;if(e|0)if((2147483647/(e|0)|0|0)<(f|0)){b=-22;i=m;return b|0}e=_(e,f)|0;e=g?e:e+7>>3;if((e|0)<0){b=e;i=m;return b|0}c[b+8>>2]=e;f=c[k+12>>2]|0;if(((c[l+12>>2]|0)+-1|0)>>>0<2)e=d[h>>0]|0;else e=0;e=j+(1<<e)>>e;if(e|0)if((2147483647/(e|0)|0|0)<(f|0)){b=-22;i=m;return b|0}e=_(e,f)|0;e=g?e:e+7>>3;if((e|0)<0){b=e;i=m;return b|0}c[b+12>>2]=e;b=0;i=m;return b|0}function LC(e,f,g,h,j){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=i;i=i+16|0;p=q;c[p>>2]=0;c[p+4>>2]=0;c[p+8>>2]=0;c[p+12>>2]=0;l=yD(f)|0;c[e>>2]=0;c[e+4>>2]=0;c[e+8>>2]=0;c[e+12>>2]=0;if(!l){g=-22;i=q;return g|0}k=a[l+7>>0]|0;if(k&8){g=-22;i=q;return g|0}c[e>>2]=h;f=c[j>>2]|0;if((f|0)>(2147482623/(g|0)|0|0)){g=-22;i=q;return g|0}n=_(f,g)|0;if(k&66){g=n+3&-4;c[e+4>>2]=h+g;g=g+1024|0;i=q;return g|0}c[p+((b[l+8>>1]&3)<<2)>>2]=1;c[p+((b[l+10>>1]&3)<<2)>>2]=1;c[p+((b[l+12>>1]&3)<<2)>>2]=1;c[p+((b[l+14>>1]&3)<<2)>>2]=1;o=g+-1|0;m=l+6|0;if(!(c[p+4>>2]|0)){g=n;i=q;return g|0}f=d[m>>0]|0;l=h+n|0;c[e+4>>2]=l;f=o+(1<<f)>>f;k=c[j+4>>2]|0;if((k|0)>(2147483647/(f|0)|0|0)){g=-22;i=q;return g|0}f=_(f,k)|0;if((n|0)>(2147483647-f|0)){g=-22;i=q;return g|0}h=f+n|0;if(!(c[p+8>>2]|0)){g=h;i=q;return g|0}k=d[m>>0]|0;l=l+f|0;c[e+8>>2]=l;f=o+(1<<k)>>k;k=c[j+8>>2]|0;if((k|0)>(2147483647/(f|0)|0|0)){g=-22;i=q;return g|0}f=_(f,k)|0;if((h|0)>(2147483647-f|0)){g=-22;i=q;return g|0}k=f+h|0;if(!(c[p+12>>2]|0)){g=k;i=q;return g|0}c[e+12>>2]=l+f;f=c[j+12>>2]|0;if((f|0)>(2147483647/(g|0)|0|0)){g=-22;i=q;return g|0}g=_(f,g)|0;g=(k|0)>(2147483647-g|0)?-22:g+k|0;i=q;return g|0}function MC(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0;switch(b|0){case 22:{d=0;do{c[a+(d<<2)>>2]=(_(d>>5,2359296)|0)+-16777216|(d>>>2&7)*9216|(d&3)*85;d=d+1|0}while((d|0)!=256);d=0;return d|0}case 19:{d=0;do{c[a+(d<<2)>>2]=((_(d&7,2359296)|0)+-16777216|(d>>>3&7)*9216)+((d>>6)*85|0);d=d+1|0}while((d|0)!=256);d=0;return d|0}default:{f=0;a:while(1){switch(b|0){case 8:{d=f;e=f;g=f;break}case 21:{d=(f>>3)*255|0;e=(f>>>1&3)*85|0;g=0-(f&1)&255;break}case 24:{d=0-(f&1)&255;e=(f>>>1&3)*85|0;g=(f>>3)*255|0;break}default:{d=-22;e=8;break a}}c[a+(f<<2)>>2]=(g<<16)+-16777216+(e<<8)+d;f=f+1|0;if((f|0)>=256){d=0;e=8;break}}if((e|0)==8)return d|0}}return 0}function NC(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0;h=i;i=i+32|0;g=h;f=h+8|0;c[f>>2]=34672;c[f+4>>2]=d;c[f+8>>2]=e;if((a|0)>0&(b|0)>0){d=RF(b+128|0,0,a+128|0,0)|0;e=C;if(e>>>0<0|(e|0)==0&d>>>0<268435455){a=0;i=h;return a|0}}c[g>>2]=a;c[g+4>>2]=b;UC(f,16,99341,g);a=-22;i=h;return a|0}function OC(a,b,d){a=a|0;b=b|0;d=d|0;var e=0;e=c[d+4>>2]|0;if((e|0)<1){a=-22;return a|0}d=c[d>>2]|0;if((d|0)<0){a=-22;return a|0}if((d|0)==0|(d|0)==(e|0)){a=0;return a|0}if((d|0)<(e|0)){e=XC(a,0,d,((d|0)<0)<<31>>31,e,((e|0)<0)<<31>>31,0)|0;d=C}else{e=XC(b,0,e,((e|0)<0)<<31>>31,d,((d|0)<0)<<31>>31,0)|0;d=C}a=(d|0)>0|(d|0)==0&e>>>0>0?0:-22;return a|0}function PC(e,f,g,h,j,k,l){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;var m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0;B=i;i=i+112|0;A=B+64|0;y=B+48|0;z=B+32|0;r=B+16|0;q=B;w=B+88|0;x=B+72|0;p=yD(j)|0;if(!p){i=B;return}m=d[p+7>>0]|0;if(m&8|0){i=B;return}if(m&66|0){n=c[e>>2]|0;p=c[f>>2]|0;m=c[g>>2]|0;o=c[h>>2]|0;if((n|0)!=0&(m|0)!=0){if((((o|0)>-1?o:0-o|0)|0)<(k|0)){c[q>>2]=99372;c[q+4>>2]=99403;c[q+8>>2]=279;UC(0,0,104466,q);Ya()}if((((p|0)>-1?p:0-p|0)|0)<(k|0)){c[r>>2]=99424;c[r+4>>2]=99403;c[r+8>>2]=280;UC(0,0,104466,r);Ya()}if((l|0)>0)while(1){KF(n|0,m|0,k|0)|0;if((l|0)>1){n=n+p|0;m=m+o|0;l=l+-1|0}else break}}KF(c[e+4>>2]|0,c[g+4>>2]|0,1024)|0;i=B;return}m=a[p+4>>0]|0;if(!(m<<24>>24)){i=B;return}n=m&255;o=0;m=0;do{v=(b[p+8+(o<<1)>>1]&3)+1|0;m=(m|0)>(v|0)?m:v;o=o+1|0}while((o|0)<(n|0));if((m|0)<=0){i=B;return}u=k+-1|0;v=0-l|0;t=p+6|0;a:do if((k|0)<0){l=yD(j)|0;if(l)if(!(a[l+7>>0]&8))JC(w,x,l)}else{s=0;while(1){n=yD(j)|0;if(!n)break a;o=n+7|0;if(a[o>>0]&8)break a;JC(w,x,n);p=c[w+(s<<2)>>2]|0;if(((c[x+(s<<2)>>2]|0)+-1|0)>>>0<2)n=d[n+5>>0]|0;else n=0;n=u+(1<<n)>>n;if(n|0)if((2147483647/(n|0)|0|0)<(p|0))break a;k=_(n,p)|0;k=(a[o>>0]&4)==0?k:k+7>>3;if((k|0)<0)break a;if((s+-1|0)>>>0<2)n=0-(v>>(d[t>>0]|0))|0;else n=l;o=c[e+(s<<2)>>2]|0;q=c[f+(s<<2)>>2]|0;p=c[g+(s<<2)>>2]|0;r=c[h+(s<<2)>>2]|0;if((o|0)!=0&(p|0)!=0){if((((r|0)>-1?r:0-r|0)|0)<(k|0)){l=32;break}if((((q|0)>-1?q:0-q|0)|0)<(k|0)){l=35;break}if((n|0)>0)while(1){KF(o|0,p|0,k|0)|0;if((n|0)>1){o=o+q|0;p=p+r|0;n=n+-1|0}else break}}s=s+1|0;if((s|0)>=(m|0)){l=39;break}}if((l|0)==32){c[z>>2]=99372;c[z+4>>2]=99403;c[z+8>>2]=279;UC(0,0,104466,z);Ya()}else if((l|0)==35){c[y>>2]=99424;c[y+4>>2]=99403;c[y+8>>2]=280;UC(0,0,104466,y);Ya()}else if((l|0)==39){i=B;return}}while(0);UC(0,16,99455,A);i=B;return}function QC(a){a=a|0;return c[c[a>>2]>>2]|0}function RC(b,d,e,f,g,h,j){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;w=i;i=i+32|0;u=w+24|0;m=w+16|0;r=w+8|0;l=w;if(!b)q=0;else q=c[b>>2]|0;DB(g,0,1);s=g+1024|0;DB(s,0,1);t=g+2048|0;DB(t,0,1);v=g+3072|0;DB(v,0,65536);p=(j|0)!=0;if(p){c[j+4>>2]=16;c[j>>2]=16}if((q|0)!=0&(c[h>>2]|0)!=0){k=c[q+20>>2]|0;do if(!k)n=17;else{o=c[b+k>>2]|0;if(!o)n=17;else{k=c[o>>2]|0;if(!k)n=17;else{c[l>>2]=nb[c[k+4>>2]&15](o)|0;c[l+4>>2]=o;FB(g,99485,l);if(!p){c[m>>2]=nb[c[q+4>>2]&15](b)|0;c[m+4>>2]=b;FB(s,99485,m);break}k=c[o>>2]|0;do if(!k)k=16;else{n=c[k+12>>2]|0;if((n|0)<3357440|(n&252)>>>0<100)k=16;else{l=c[k+32>>2]|0;if(l>>>0>45)k=16;else{k=c[k+36>>2]|0;if(!k){k=l+16|0;break}else{k=(nb[k&15](o)|0)+16|0;break}}}}while(0);c[j>>2]=k;n=17}}}while(0);if((n|0)==17){c[r>>2]=nb[c[q+4>>2]&15](b)|0;c[r+4>>2]=b;FB(s,99485,r);if(p){k=c[b>>2]|0;do if(!k)k=16;else{r=c[k+12>>2]|0;if((r|0)<3357440|(r&252)>>>0<100)k=16;else{l=c[k+32>>2]|0;if(l>>>0>45)k=16;else{k=c[k+36>>2]|0;if(!k){k=l+16|0;break}else{k=(nb[k&15](b)|0)+16|0;break}}}}while(0);c[j+4>>2]=k}}if(0){switch(d|0){case -8:{k=99541;break}case 48:{k=99535;break}case 40:{k=99527;break}case 32:{k=99522;break}case 24:{k=99514;break}case 16:{k=99508;break}case 8:{k=99502;break}case 0:{k=99496;break}default:k=190399}c[u>>2]=k;FB(t,99547,u)}}GB(v,e,f);if(!(a[c[g>>2]>>0]|0))if(!(a[c[s>>2]>>0]|0))if(!(a[c[t>>2]>>0]|0))if(!(a[c[v>>2]>>0]|0)){i=w;return}k=c[g+3076>>2]|0;if(!k)k=0;else if(k>>>0>(c[g+3080>>2]|0)>>>0)k=0;else k=a[(c[v>>2]|0)+(k+-1)>>0]|0;c[h>>2]=(k<<24>>24==10|k<<24>>24==13)&1;i=w;return}function SC(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=i;i=i+5168|0;k=r+24|0;j=r+16|0;g=r;q=r+40|0;h=r+4136|0;m=r+32|0;if((d|0)>-1){n=d&255;p=d>>>8&255}else{n=d;p=0}if((n|0)>32){i=r;return}RC(b,n,e,f,q,34716,m);b=q+1024|0;s=c[b>>2]|0;f=q+2048|0;e=c[f>>2]|0;o=q+3072|0;d=c[o>>2]|0;c[g>>2]=c[q>>2];c[g+4>>2]=s;c[g+8>>2]=e;c[g+12>>2]=d;$E(h,1024,99553,g)|0;d=c[27553]|0;if(!d){e=(qF(2)|0)!=0;e=e?1:-1;c[27553]=e}else e=d;if((c[8679]|0)==0|0==0)l=11;else{s=(_D(h,189375)|0)!=0;if(s|(a[h>>0]|0)==0)l=11;else if((a[h+((aE(h)|0)+-1)>>0]|0)==13)l=11;else{d=(c[27554]|0)+1|0;c[27554]=d;if((e|0)==1){s=c[10957]|0;c[j>>2]=d;ZE(s,99562,j)|0}}}if((l|0)==11){d=c[27554]|0;if((d|0)>0){s=c[10957]|0;c[k>>2]=d;ZE(s,99598,k)|0;c[27554]=0}aF(189375,h)|0;d=c[q>>2]|0;e=a[d>>0]|0;if(e<<24>>24){do{if((e&255)<8|(e+-14&255)<18)a[d>>0]=63;d=d+1|0;e=a[d>>0]|0}while(e<<24>>24!=0);d=c[q>>2]|0}TC(c[m>>2]|0,0,d);d=c[b>>2]|0;e=a[d>>0]|0;if(e<<24>>24){do{if((e&255)<8|(e+-14&255)<18)a[d>>0]=63;d=d+1|0;e=a[d>>0]|0}while(e<<24>>24!=0);d=c[b>>2]|0}TC(c[m+4>>2]|0,0,d);d=c[f>>2]|0;e=a[d>>0]|0;if(e<<24>>24){do{if((e&255)<8|(e+-14&255)<18)a[d>>0]=63;d=d+1|0;e=a[d>>0]|0}while(e<<24>>24!=0);d=c[f>>2]|0}b=n>>3;b=(b|0)<0?0:(b|0)>7?7:b;TC(b,p,d);d=c[o>>2]|0;e=a[d>>0]|0;if(e<<24>>24){do{if((e&255)<8|(e+-14&255)<18)a[d>>0]=63;d=d+1|0;e=a[d>>0]|0}while(e<<24>>24!=0);d=c[o>>2]|0}TC(b,p,d)}HB(q+3072|0,0)|0;i=r;return}function TC(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;l=i;i=i+48|0;k=l+32|0;j=l+16|0;h=l;if(!(a[e>>0]|0)){i=l;return}f=c[8680]|0;do if((f|0)<0){g=Ta(99634)|0;do if(!(Ta(99639)|0))if(!(Ta(99648)|0)){if(g|0)if(qF(2)|0){f=1;break}f=(Ta(99669)|0)!=0}else f=0;else f=0;while(0);f=f&1;c[8680]=f;if(!(Ta(99688)|0)){if(!g)break;if(!(mF(g,99710)|0))break}f=f<<8;c[8680]=f}while(0);g=(b|0)==4?0:f;if((g|0)==1){k=c[10957]|0;b=c[34724+(b<<2)>>2]|0;c[h>>2]=b>>>4&15;c[h+4>>2]=b&15;c[h+8>>2]=e;ZE(k,99719,h)|0;i=l;return}if((d|0)!=0&(f|0)==256){k=c[10957]|0;c[j>>2]=(c[34724+(b<<2)>>2]|0)>>>16&255;c[j+4>>2]=d;c[j+8>>2]=e;ZE(k,99735,j)|0;i=l;return}f=c[10957]|0;if((g|0)==256){b=c[34724+(b<<2)>>2]|0;c[k>>2]=b>>>16&255;c[k+4>>2]=b>>>8&255;c[k+8>>2]=e;ZE(f,99735,k)|0;i=l;return}else{nF(e,f)|0;i=l;return}}function UC(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0;h=i;i=i+16|0;f=h;if(!a)c[f>>2]=e;else{g=c[a>>2]|0;c[f>>2]=e;if(g)if((c[g+12>>2]|0)>3280641){g=c[g+16>>2]|0;if((b|0)>7&(g|0)!=0)b=(c[a+g>>2]|0)+b|0}}if(!131){i=h;return}SC(a,b,d,f);i=h;return}function VC(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;f=i;i=i+32|0;e=f+16|0;c[e>>2]=d;if(131)SC(a,24,b,e);UC(a,24,99762,f);UC(a,24,99938,f+8|0);i=f;return}function WC(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;if((c|0)==0&(d|0)==0){d=b;b=a;C=d;return b|0}while(1){a=QF(a|0,b|0,c|0,d|0)|0;b=C;if((a|0)==0&(b|0)==0)break;else{f=d;e=c;d=b;c=a;b=f;a=e}}C=d;return c|0}function XC(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0;if((d|0)<0|((f|0)<0|(f|0)==0&e>>>0<1)){e=-2147483648;f=0;C=e;return f|0}j=g&-8193;if(j>>>0>5|(j|0)==4){e=-2147483648;f=0;C=e;return f|0}do if(g&8192){j=HF(a|0,b|0,1,-2147483648)|0;i=C;if(i>>>0<0|(i|0)==0&j>>>0<2){e=b;f=a;C=e;return f|0}else{g=g+-8192|0;break}}while(0);if((b|0)<0){j=(b|0)>-2147483648|(b|0)==-2147483648&a>>>0>1;j=EF(0,0,(j?a:1)|0,(j?b:-2147483648)|0)|0;f=XC(j,C,c,d,e,f,g>>>1&1^g)|0;f=EF(0,0,f|0,C|0)|0;return f|0}if((g|0)==5){g=PF(e|0,f|0,2,0)|0;h=C}else{h=(g&1|0)==0;g=HF(e|0,f|0,-1,-1)|0;g=h?0:g;h=h?0:C}if(!(((d|0)<0|(d|0)==0&c>>>0<2147483648)&((f|0)<0|(f|0)==0&e>>>0<2147483648))){k=IF(a|0,b|0,32)|0;b=C;m=IF(c|0,d|0,32)|0;l=C;j=RF(m|0,l|0,a|0,0)|0;i=C;d=RF(c|0,0,k|0,b|0)|0;d=HF(j|0,i|0,d|0,C|0)|0;i=C;c=RF(c|0,0,a|0,0)|0;c=HF(0,d|0,c|0,C|0)|0;j=C;a=RF(m|0,l|0,k|0,b|0)|0;a=HF(i|0,0,a|0,C|0)|0;a=HF(a|0,C|0,(j>>>0<d>>>0|(j|0)==(d|0)&c>>>0<0)&1|0,0)|0;b=C;j=HF(g|0,h|0,c|0,j|0)|0;c=C;b=HF(a|0,b|0,(c>>>0<h>>>0|(c|0)==(h|0)&j>>>0<g>>>0)&1|0,0)|0;h=C;a=d;g=i;i=63;while(1){m=JF(j|0,c|0,i|0)|0;k=GF(b|0,h|0,1)|0;l=C;k=m&1|k;a=GF(a|0,g|0,1)|0;g=C;m=l>>>0<f>>>0|(l|0)==(f|0)&k>>>0<e>>>0;b=EF(k|0,l|0,(m?0:e)|0,(m?0:f)|0)|0;a=(m&1|a)^1;if((i|0)<=0){b=g;break}else{h=C;i=i+-1|0}}m=(b|0)<0;l=m?-2147483648:g;m=m?0:a;C=l;return m|0}if((b|0)<0|(b|0)==0&a>>>0<2147483648){m=RF(c|0,d|0,a|0,b|0)|0;m=HF(g|0,h|0,m|0,C|0)|0;m=PF(m|0,C|0,e|0,f|0)|0;l=C;C=l;return m|0}i=PF(a|0,b|0,e|0,f|0)|0;j=C;a=QF(a|0,b|0,e|0,f|0)|0;a=RF(a|0,C|0,c|0,d|0)|0;g=HF(a|0,C|0,g|0,h|0)|0;g=PF(g|0,C|0,e|0,f|0)|0;a=C;if(((c|0)!=0|(d|0)!=0)&((j|0)>0|(j|0)==0&i>>>0>2147483646)){m=EF(-1,2147483647,g|0,a|0)|0;m=PF(m|0,C|0,c|0,d|0)|0;l=C;if((j|0)>(l|0)|(j|0)==(l|0)&i>>>0>m>>>0){l=-2147483648;m=0;C=l;return m|0}}m=RF(i|0,j|0,c|0,d|0)|0;m=HF(g|0,a|0,m|0,C|0)|0;l=C;C=l;return m|0}function YC(a){a=a|0;var b=0,d=0,e=0;e=i;i=i+16|0;b=e+4|0;d=e;c[d>>2]=0;do if((2147483647+-32|0)>>>0<a>>>0)b=0;else{if(a|0)if(!(CF(d,16,a)|0)){b=c[d>>2]|0;break}else{c[d>>2]=0;b=0;break}c[b>>2]=0;do if(2147483647==32)b=0;else if(!(CF(b,16,1)|0)){b=c[b>>2]|0;break}else{c[b>>2]=0;b=0;break}while(0);c[d>>2]=b}while(0);i=e;return b|0}function ZC(a,b){a=a|0;b=b|0;if((2147483647+-32|0)>>>0<b>>>0){b=0;return b|0}b=yF(a,((b|0)==0&1)+b|0)|0;return b|0}function _C(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=_(c,b)|0;if((c|0)!=0&(c|b)>>>0>65535)if(((d>>>0)/(c>>>0)|0|0)!=(b|0)){xF(a);a=0;return a|0}if((2147483647+-32|0)>>>0<d>>>0)c=0;else c=yF(a,((d|0)==0&1)+d|0)|0;if(!((d|0)!=0&(c|0)==0)){a=c;return a|0}xF(a);a=0;return a|0}function $C(a){a=a|0;xF(a);return}function aD(b,c){b=b|0;c=c|0;var e=0;e=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;if(!c){a[b>>0]=0;a[b+1>>0]=0;a[b+2>>0]=0;a[b+3>>0]=0;xF(e);b=0;return b|0}do if((2147483647+-32|0)>>>0>=c>>>0){e=yF(e,c)|0;if(!e){e=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;break}a[b>>0]=e;a[b+1>>0]=e>>8;a[b+2>>0]=e>>16;a[b+3>>0]=e>>24;b=0;return b|0}while(0);a[b>>0]=0;a[b+1>>0]=0;a[b+2>>0]=0;a[b+3>>0]=0;xF(e);b=-12;return b|0}function bD(b){b=b|0;var c=0;c=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;a[b>>0]=0;a[b+1>>0]=0;a[b+2>>0]=0;a[b+3>>0]=0;xF(c);return}function cD(a){a=a|0;var b=0,d=0,e=0;e=i;i=i+16|0;b=e+4|0;d=e;c[d>>2]=0;a:do if((2147483647+-32|0)>>>0>=a>>>0){do if(!a){c[b>>2]=0;do if(2147483647==32)b=0;else if(!(CF(b,16,1)|0)){b=c[b>>2]|0;break}else{c[b>>2]=0;b=0;break}while(0);c[d>>2]=b}else if(!(CF(d,16,a)|0)){b=c[d>>2]|0;break}else{c[d>>2]=0;break a}while(0);if(!b){a=0;i=e;return a|0}FF(b|0,0,a|0)|0;a=b;i=e;return a|0}while(0);a=0;i=e;return a|0}function dD(a){a=a|0;var b=0,c=0;if(!a)b=0;else{c=(aE(a)|0)+1|0;if((2147483647+-32|0)>>>0<c>>>0)b=0;else{b=yF(0,((c|0)==0&1)+c|0)|0;if(!b)b=0;else KF(b|0,a|0,c|0)|0}}return b|0}function eD(b,e,f){b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0;k=i;i=i+32|0;g=k;h=k+16|0;j=k+12|0;if((c[e>>2]|0)>>>0>=f>>>0)if(f|0?(d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24|0)==0:0){c[g>>2]=100095;c[g+4>>2]=100112;c[g+8>>2]=33;UC(0,0,104466,g);Ya()}else{i=k;return}g=f+32+(f>>>4)|0;g=g>>>0>f>>>0?g:f;f=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;a[b>>0]=0;a[b+1>>0]=0;a[b+2>>0]=0;a[b+3>>0]=0;xF(f);c[j>>2]=0;do if((2147483647+-32|0)>>>0<g>>>0)f=0;else{if(g|0)if(!(CF(j,16,g)|0)){f=c[j>>2]|0;break}else{c[j>>2]=0;f=0;break}c[h>>2]=0;do if(2147483647==32)f=0;else if(!(CF(h,16,1)|0)){f=c[h>>2]|0;break}else{c[h>>2]=0;f=0;break}while(0);c[j>>2]=f}while(0);a[b>>0]=f;a[b+1>>0]=f>>8;a[b+2>>0]=f>>16;a[b+3>>0]=f>>24;c[e>>2]=(f|0)==0?0:g;i=k;return}function fD(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0.0;w=i;i=i+128|0;j=w+104|0;p=w+96|0;m=w+88|0;l=w+80|0;s=w+56|0;o=w+48|0;t=w+24|0;q=w+16|0;n=w+8|0;k=w;r=w+112|0;v=w+108|0;u=gD(b,d,0,0,f,v)|0;f=c[v>>2]|0;if(!((u|0)!=0&(f|0)!=0)){b=-1414549496;i=w;return b|0}g=(e|0)!=0;a:do if(!g){d=c[u+12>>2]|0;if((d|0)<1346784596)if((d|0)<1129270354)if((d|0)<1128811585){switch(d|0){case 5:break a;default:d=-22}i=w;return d|0}else{switch(d|0){case 1128811585:break a;default:d=-22}i=w;return d|0}else if((d|0)<1146442272){switch(d|0){case 1129270354:break a;default:d=-22}i=w;return d|0}else{switch(d|0){case 1146442272:break a;default:d=-22}i=w;return d|0}else if((d|0)<1397316165)if((d|0)<1397116244){switch(d|0){case 1346784596:break a;default:d=-22}i=w;return d|0}else{switch(d|0){case 1397116244:break a;default:d=-22}i=w;return d|0}else if((d|0)<1448231252){switch(d|0){case 1397316165:break a;default:d=-22}i=w;return d|0}else{switch(d|0){case 1448231252:break a;default:d=-22}i=w;return d|0}}while(0);if(c[u+40>>2]&128|0){b=-22;i=w;return b|0}v=f+(c[u+8>>2]|0)|0;d=c[u+12>>2]|0;b:do if((d|0)<7)switch(d|0){case 5:{bD(v);b=dD(e)|0;c[v>>2]=b;b=b|0?0:-12;i=w;return b|0}case 6:case 3:case 4:case 2:case 1:case 0:{b=iD(b,f,u,e,v)|0;i=w;return b|0}default:break b}else{if((d|0)<1346784596){if((d|0)<1129270354){if((d|0)<1128811585){switch(d|0){case 7:break;default:break b}b=hD(e,v)|0;i=w;return b|0}switch(d|0){case 1128811585:break;default:break b}if(g)if(_D(e,100327)|0){f=TB(e,0)|0;g=C;if((f|0)==0&(g|0)==0){c[p>>2]=e;UC(b,16,100690,p);d=-22}else d=0;b=v;c[b>>2]=f;c[b+4>>2]=g;b=d;i=w;return b|0}e=v;c[e>>2]=0;c[e+4>>2]=0;break}if((d|0)<1146442272){switch(d|0){case 1129270354:break;default:break b}if(!e){b=0;i=w;return b|0}d=sD(v,e,-1,b)|0;if((d|0)>=0){b=d;i=w;return b|0}c[m>>2]=e;UC(b,16,100646,m);b=d;i=w;return b|0}switch(d|0){case 1146442272:break;default:break b}if(!g){b=v;c[b>>2]=0;c[b+4>>2]=0;b=0;i=w;return b|0}d=vD(v,e,1)|0;if((d|0)>=0){b=d;i=w;return b|0}c[l>>2]=e;UC(b,16,100599,l);b=d;i=w;return b|0}if((d|0)>=1397316165){if((d|0)>=1448231252){switch(d|0){case 1448231252:break;default:break b}if(!e)d=-22;else{d=rD(v,e)|0;if((d|0)>=0){b=d;i=w;return b|0}}c[n>>2]=e;UC(b,16,100420,n);b=d;i=w;return b|0}switch(d|0){case 1397316165:break;default:break b}if(e|0)if(_D(e,100327)|0){d=qD(v,v+4|0,e)|0;if((d|0)>=0){b=d;i=w;return b|0}c[k>>2]=e;UC(b,16,100371,k);b=d;i=w;return b|0}c[v+4>>2]=0;c[v>>2]=0;b=0;i=w;return b|0}if((d|0)<1397116244){switch(d|0){case 1346784596:break;default:break b}do if(!e)d=-1;else if(!(_D(e,100327)|0))d=-1;else{d=xD(e)|0;if((d|0)==-1){d=RE(e,r,0)|0;if(d>>>0<340?(a[c[r>>2]>>0]|0)==0:0)break;c[q>>2]=e;c[q+4>>2]=100510;UC(b,16,100469,q);b=-22;i=w;return b|0}}while(0);x=+h[u+24>>3];g=~~(x>-1.0?x:-1.0);x=+h[u+32>>3];f=~~(x>339.0?339.0:x);e=(f|g|0)==0;g=e?-1:g;f=e?339:f;if((d|0)<(g|0)|(d|0)>(f|0)){e=c[u>>2]|0;c[t>>2]=d;c[t+4>>2]=e;c[t+8>>2]=100510;c[t+12>>2]=g;c[t+16>>2]=f;UC(b,16,100523,t);b=-34;i=w;return b|0}else{c[v>>2]=d;b=0;i=w;return b|0}}else{switch(d|0){case 1397116244:break;default:break b}do if(!e)d=-1;else if(!(_D(e,100327)|0))d=-1;else{d=GD(e)|0;if((d|0)==-1){d=RE(e,r,0)|0;if(d>>>0<10?(a[c[r>>2]>>0]|0)==0:0)break;c[o>>2]=e;c[o+4>>2]=100585;UC(b,16,100469,o);b=-22;i=w;return b|0}}while(0);x=+h[u+24>>3];g=~~(x>-1.0?x:-1.0);x=+h[u+32>>3];f=~~(x>9.0?9.0:x);e=(f|g|0)==0;g=e?-1:g;f=e?9:f;if((d|0)<(g|0)|(d|0)>(f|0)){e=c[u>>2]|0;c[s>>2]=d;c[s+4>>2]=e;c[s+8>>2]=100585;c[s+12>>2]=g;c[s+16>>2]=f;UC(b,16,100523,s);b=-34;i=w;return b|0}else{c[v>>2]=d;b=0;i=w;return b|0}}}while(0);UC(b,16,100743,j);b=-22;i=w;return b|0}function gD(a,b,d,e,f,g){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0;m=i;i=i+16|0;k=m;if(!a){g=0;i=m;return g|0}h=c[a>>2]|0;if(!h){g=0;i=m;return g|0}a:do if(f&1|0){if(!(f&2)){h=c[h+24>>2]|0;if(!h)break;else j=0;while(1){j=sb[h&7](a,j)|0;if(!j)break a;h=gD(j,b,d,e,f,g)|0;if(h|0)break;h=c[(c[a>>2]|0)+24>>2]|0;if(!h)break a}i=m;return h|0}c[k>>2]=0;j=h+28|0;while(1){h=c[j>>2]|0;if(!h){l=8;break}h=nb[h&15](c[k>>2]|0)|0;c[k>>2]=h;if(!h)break;h=gD(k,b,d,e,f,0)|0;if(h|0){l=12;break}}if((l|0)==8)c[k>>2]=0;else if((l|0)==12){g=h;i=m;return g|0}}while(0);j=c[a>>2]|0;k=j+8|0;l=(d|0)==0;if(!j){g=0;i=m;return g|0}else h=0;while(1){if(!h){h=c[k>>2]|0;if(!h){h=0;l=32;break}j=c[h>>2]|0;if(!j){h=0;l=32;break}}else{h=h+48|0;j=c[h>>2]|0;if(!j){h=0;l=32;break}}if(_D(j,b)|0)continue;if((c[h+40>>2]&e|0)!=(e|0))continue;j=(c[h+12>>2]|0)==128;if(l)if(j)continue;else{l=28;break}if(!j)continue;j=c[h+44>>2]|0;if(!j)continue;if(!(_D(j,d)|0)){l=28;break}}if((l|0)==28){if(!g){g=h;i=m;return g|0}if(!(f&2)){c[g>>2]=a;g=h;i=m;return g|0}else{c[g>>2]=0;g=h;i=m;return g|0}}else if((l|0)==32){i=m;return h|0}return 0}function hD(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;l=d+4|0;bD(d);c[l>>2]=0;if(!b){d=0;return d|0}e=aE(b)|0;if(!e){d=0;return d|0}if(e&1|0){d=-22;return d|0}j=(e|0)/2|0;k=YC(j)|0;if(!k){d=-12;return d|0}e=a[b>>0]|0;a:do if(e<<24>>24){i=k;while(1){g=b+1|0;f=e<<24>>24;do if((e+-48&255)<10)h=f+-48|0;else if((e+-97&255)<6){h=f+-87|0;break}else{h=(e+-65&255)<6?f+-55|0:-1;break}while(0);b=b+2|0;e=a[g>>0]|0;f=e<<24>>24;do if((e+-48&255)<10)e=f+-48|0;else if((e+-97&255)<6){e=f+-87|0;break}else{e=(e+-65&255)<6?f+-55|0:-1;break}while(0);if((e|h|0)<0)break;a[i>>0]=e|h<<4;e=a[b>>0]|0;if(!(e<<24>>24))break a;else i=i+1|0}$C(k);d=-22;return d|0}while(0);c[d>>2]=k;c[l>>2]=j;d=0;return d|0}function iD(b,d,e,f,g){b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;var j=0,k=0,l=0.0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0;K=i;i=i+1072|0;J=K+784|0;I=K+776|0;H=K+520|0;k=K+1052|0;j=K+1048|0;G=K+512|0;F=K;E=K+792|0;c[H>>2]=k;c[H+4>>2]=j;c[H+8>>2]=K+1056;if((dF(f,100137,H)|0)==2){C=c[k>>2]|0;j=jD(b,e,g,1.0,c[j>>2]|0,C,((C|0)<0)<<31>>31)|0;if((j|0)>-1){b=j;i=K;return b|0}}B=e+12|0;C=e+44|0;w=(d|0)==0;x=e+16|0;y=e+32|0;z=e+24|0;A=e+16|0;a:while(1){v=c[B>>2]|0;if(!v){j=a[f>>0]|0;switch(j<<24>>24){case 45:case 43:{f=f+1|0;m=j<<24>>24;break}default:m=0}j=0;b:do{k=a[f+j>>0]|0;switch(k<<24>>24){case 45:case 43:case 0:break b;default:{}}a[H+j>>0]=k;j=j+1|0}while(j>>>0<255);a[H+j>>0]=0;t=j}else{m=0;t=0}u=(t|0)!=0;s=u?H:f;j=c[C>>2]|0;c:do if(w)D=28;else{k=c[d>>2]|0;if(!k)D=28;else{o=k+8|0;if(!j){k=0;while(1){if(!k){k=c[o>>2]|0;if(!k){D=28;break c}n=c[k>>2]|0;if(!n){D=28;break c}}else{k=k+48|0;n=c[k>>2]|0;if(!n){D=28;break c}}if(_D(n,s)|0)continue;if((c[k+12>>2]|0)!=128)break}D=31;break}else k=0;while(1){if(!k){k=c[o>>2]|0;if(!k){D=28;break c}n=c[k>>2]|0;if(!n){D=28;break c}}else{k=k+48|0;n=c[k>>2]|0;if(!n){D=28;break c}}if(_D(n,s)|0)continue;if((c[k+12>>2]|0)!=128)continue;n=c[k+44>>2]|0;if(!n)continue;if(!(_D(n,j)|0)){j=k;break}}j=j+16|0;h[G>>3]=+((c[j>>2]|0)>>>0)+4294967296.0*+(c[j+4>>2]|0);j=v}}while(0);if((D|0)==28)D=31;if((D|0)==31){D=0;d:do if((j|0)==0|w)j=0;else{q=c[d>>2]|0;p=(q|0)==0;q=q+8|0;r=0;k=0;while(1){if(p){if(!k){j=r;break d}else o=k;while(1){k=o+48|0;if(!(c[k>>2]|0)){j=r;break d}n=o+60|0;if((c[n>>2]|0)!=128){o=k;continue}o=c[o+92>>2]|0;if(!o){o=k;continue}if(!(_D(o,j)|0))break;else o=k}}else while(1){if(!k){k=c[q>>2]|0;if(!k){j=r;break d}if(!(c[k>>2]|0)){j=r;break d}}else{k=k+48|0;if(!(c[k>>2]|0)){j=r;break d}}n=k+12|0;if((c[n>>2]|0)!=128)continue;o=c[k+44>>2]|0;if(!o)continue;if(!(_D(o,j)|0))break}if((r+6|0)>>>0>63){D=47;break a}c[E+(r<<2)>>2]=c[k>>2];switch(c[n>>2]|0){case 1:case 0:case 128:case 2:{o=k+16|0;l=+((c[o>>2]|0)>>>0)+4294967296.0*+(c[o+4>>2]|0);break}default:l=+h[k+16>>3]}h[F+(r<<3)>>3]=l;r=r+1|0}}while(0);c[E+(j<<2)>>2]=100311;switch(v|0){case 1:case 0:case 128:case 2:{v=A;l=+((c[v>>2]|0)>>>0)+4294967296.0*+(c[v+4>>2]|0);break}default:l=+h[x>>3]}r=j+1|0;h[F+(j<<3)>>3]=l;c[E+(r<<2)>>2]=100319;v=j+2|0;h[F+(r<<3)>>3]=+h[y>>3];c[E+(v<<2)>>2]=100323;r=j+3|0;h[F+(v<<3)>>3]=+h[z>>3];c[E+(r<<2)>>2]=100327;v=j+4|0;h[F+(r<<3)>>3]=0.0;c[E+(v<<2)>>2]=100332;j=j+5|0;h[F+(v<<3)>>3]=-1.0;c[E+(j<<2)>>2]=0;h[F+(j<<3)>>3]=0.0;j=mC(G,s,E,F,0,0,0,0,0,0,b)|0;if((j|0)<0){D=57;break}j=c[B>>2]|0}e:do if(!j){j=c[g>>2]|0;switch(m|0){case 43:{l=+h[G>>3];h[G>>3]=+((~~l>>>0|j)>>>0)+4294967296.0*+((+N(l)>=1.0?(l>0.0?~~+$(+M(l/4294967296.0),4294967295.0)>>>0:~~+Z((l-+(~~l>>>0))/4294967296.0)>>>0):0)|0);break e}case 45:{l=+h[G>>3];h[G>>3]=+((j&~(~~l>>>0))>>>0)+4294967296.0*0.0;break e}default:break e}}while(0);j=jD(b,e,g,+h[G>>3],1,1,0)|0;if((j|0)<0)break;f=f+t|0;if(!u){j=0;break}if(!(a[f>>0]|0)){j=0;break}}if((D|0)==47){c[I>>2]=j;UC(b,16,100274,I);j=-1163346256;D=58}else if((D|0)==57){c[J>>2]=f;UC(b,16,100336,J);D=58}b=j;i=K;return b|0}function jD(a,b,d,e,f,j,k){a=a|0;b=b|0;d=d|0;e=+e;f=f|0;j=j|0;k=k|0;var l=0,m=0.0,n=0.0,o=0.0,p=0.0,q=0,t=0,u=0,v=0;v=i;i=i+64|0;l=v+40|0;t=v+8|0;u=v;q=c[b+12>>2]|0;a:do if(!q){m=(+(j>>>0)+4294967296.0*+(k|0))*e/+(f|0);if(!(m<-1.5|m>4294967295.5)){u=uF(m*256.0)|0;if((u&255|0)==0&0==0)break}d=c[b>>2]|0;h[l>>3]=m;c[l+8>>2]=d;UC(a,16,100203,l);d=-34;i=v;return d|0}else{o=+h[b+32>>3];p=+(f|0);n=(+(j>>>0)+4294967296.0*+(k|0))*e;m=+h[b+24>>3];if(p*o<n|p*m>n){d=c[b>>2]|0;h[t>>3]=(f|0)==0?(n!=0.0?s:r):n/p;c[t+8>>2]=d;h[t+16>>3]=m;h[t+24>>3]=o;UC(a,16,100151,t);d=-34;i=v;return d|0}b:do if((q|0)<6)switch(q|0){case 1:break a;case 2:break b;case 4:{g[d>>2]=(+(j>>>0)+4294967296.0*+(k|0))*e/+(f|0);d=0;i=v;return d|0}case 3:{h[d>>3]=(+(j>>>0)+4294967296.0*+(k|0))*e/+(f|0);d=0;i=v;return d|0}default:{d=-22;i=v;return d|0}}else{if((q|0)<1146442272){if((q|0)>=1128811585){switch(q|0){case 1128811585:break b;default:l=-22}i=v;return l|0}switch(q|0){case 6:break;default:{d=-22;i=v;return d|0}}m=(+(j>>>0)+4294967296.0*+(k|0))*e;if(+(~~e|0)==e){c[d>>2]=~~m;c[d+4>>2]=f;d=0;i=v;return d|0}else{ED(u,m/+(f|0),16777216);a=u;f=c[a+4>>2]|0;c[d>>2]=c[a>>2];c[d+4>>2]=f;d=0;i=v;return d|0}}if((q|0)<1346784596){switch(q|0){case 1146442272:break b;default:l=-22}i=v;return l|0}if((q|0)<1397116244){switch(q|0){case 1346784596:break;default:{d=-22;i=v;return d|0}}f=uF(e/+(f|0))|0;f=RF(f|0,C|0,j|0,k|0)|0;c[d>>2]=f;d=0;i=v;return d|0}else{switch(q|0){case 1397116244:break;default:{d=-22;i=v;return d|0}}f=uF(e/+(f|0))|0;f=RF(f|0,C|0,j|0,k|0)|0;c[d>>2]=f;d=0;i=v;return d|0}}while(0);f=uF(e/+(f|0))|0;f=RF(f|0,C|0,j|0,k|0)|0;c[d>>2]=f;c[d+4>>2]=C;d=0;i=v;return d|0}while(0);f=uF(e/+(f|0))|0;f=RF(f|0,C|0,j|0,k|0)|0;c[d>>2]=f;d=0;i=v;return d|0}function kD(a){a=a|0;lD(a,0,0);return}function lD(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;r=i;i=i+48|0;p=r+24|0;o=r+16|0;n=r+8|0;m=r;k=r+32|0;if(!a){i=r;return}l=k+4|0;j=0;a:while(1){e=c[a>>2]|0;g=(j|0)==0;if(g&(e|0)!=0){e=c[e+8>>2]|0;if(!e)q=6;else{f=c[e>>2]|0;if(!f)q=6}}else q=6;if((q|0)==6){q=0;if(g){q=29;break}e=j+48|0;f=c[e>>2]|0;if(!f){q=29;break}}j=a+(c[e+8>>2]|0)|0;g=c[e+40>>2]|0;if(!((g&b|0)==(d|0)&(g&128|0)==0)){j=e;continue}g=c[e+12>>2]|0;b:do if((g|0)<8)switch(g|0){case 2:case 1:case 0:{q=10;break b}case 4:case 3:{jD(a,e,j,+h[e+16>>3],1,1,0)|0;j=e;continue a}case 6:{ED(k,+h[e+16>>3],2147483647);g=c[k>>2]|0;jD(a,e,j,1.0,c[l>>2]|0,g,((g|0)<0)<<31>>31)|0;j=e;continue a}case 5:{g=c[e+16>>2]|0;bD(j);c[j>>2]=dD(g)|0;j=e;continue a}case 7:{hD(c[e+16>>2]|0,j)|0;j=e;continue a}default:{q=28;break b}}else{if((g|0)<1146442272){if((g|0)<1128811585)switch(g|0){case 8:case 128:{j=e;continue a}default:{q=28;break b}}if((g|0)<1129270354)switch(g|0){case 1128811585:{q=10;break b}default:{q=28;break b}}switch(g|0){case 1129270354:break;default:{q=28;break b}}f=c[e+16>>2]|0;if(!f){j=e;continue a}if((sD(j,f,-1,a)|0)>=0){j=e;continue a}c[m>>2]=f;UC(a,16,100646,m);j=e;continue a}if((g|0)<1397116244){if((g|0)<1346784596)switch(g|0){case 1146442272:{q=10;break b}default:{q=28;break b}}switch(g|0){case 1346784596:break;default:{q=28;break b}}g=e+16|0;jD(a,e,j,1.0,1,c[g>>2]|0,c[g+4>>2]|0)|0;j=e;continue a}if((g|0)<1397316165){switch(g|0){case 1397116244:break;default:{q=28;break b}}g=e+16|0;jD(a,e,j,1.0,1,c[g>>2]|0,c[g+4>>2]|0)|0;j=e;continue a}if((g|0)>=1448231252){switch(g|0){case 1448231252:break;default:{q=28;break b}}f=c[e+16>>2]|0;if(f|0)if((rD(j,f)|0)>=0){j=e;continue a}c[o>>2]=f;UC(a,16,100420,o);j=e;continue a}switch(g|0){case 1397316165:break;default:{q=28;break b}}f=c[e+16>>2]|0;if(f|0)if(_D(f,100327)|0){if((qD(j,j+4|0,f)|0)>=0){j=e;continue a}c[n>>2]=f;UC(a,16,100371,n);j=e;continue a}c[j+4>>2]=0;c[j>>2]=0;j=e;continue a}while(0);if((q|0)==10){q=0;g=e+16|0;jD(a,e,j,1.0,1,c[g>>2]|0,c[g+4>>2]|0)|0;j=e;continue}else if((q|0)==28){q=0;c[p>>2]=g;c[p+4>>2]=f;UC(a,48,100765,p);j=e;continue}}if((q|0)==29){i=r;return}}function mD(a){a=a|0;var b=0,d=0,e=0,f=0;if(!a)return;e=0;a:while(1){b=c[a>>2]|0;d=(e|0)==0;if(d&(b|0)!=0){b=c[b+8>>2]|0;if(!b)f=6;else if(!(c[b>>2]|0))f=6}else f=6;if((f|0)==6){f=0;if(d){f=11;break}b=e+48|0;if(!(c[b>>2]|0)){f=11;break}}switch(c[b+12>>2]|0){case 7:case 5:{bD(a+(c[b+8>>2]|0)|0);e=b;continue a}case 8:{ZB(a+(c[b+8>>2]|0)|0);e=b;continue a}default:{e=b;continue a}}}if((f|0)==11)return}function nD(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0;k=i;i=i+16|0;h=k;j=k+8|0;c[j>>2]=0;if(!b){j=0;i=k;return j|0}e=XB(c[b>>2]|0,190399,0,2)|0;a:do if(!e)e=0;else{g=e;while(1){f=g+4|0;e=fD(a,c[g>>2]|0,c[f>>2]|0,d)|0;if((e|0)==-1414549496)YB(j,c[g>>2]|0,c[f>>2]|0,0)|0;else if((e|0)<0)break;g=XB(c[b>>2]|0,190399,g,2)|0;if(!g){e=0;break a}}d=c[f>>2]|0;c[h>>2]=c[g>>2];c[h+4>>2]=d;UC(a,16,100816,h)}while(0);ZB(b);c[b>>2]=c[j>>2];j=e;i=k;return j|0}function oD(a,b){a=a|0;b=b|0;return nD(a,b,0)|0}function pD(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,j=0,k=0,l=0;l=i;i=i+32|0;k=l+8|0;j=l;g=a+4|0;c[k>>2]=a;c[k+4>>2]=g;c[k+8>>2]=l+20;if((dF(b,100854,k)|0)==2){j=c[a>>2]|0;k=c[g>>2]|0;CD(a,g,j,((j|0)<0)<<31>>31,k,((k|0)<0)<<31>>31,d,((d|0)<0)<<31>>31)|0;k=0;i=l;return k|0}g=mC(k,b,0,0,0,0,0,0,0,e,f)|0;if((g|0)<0){k=g;i=l;return k|0}else{ED(j,+h[k>>3],d);d=c[j+4>>2]|0;k=a;c[k>>2]=c[j>>2];c[k+4>>2]=d;k=0;i=l;return k|0}return 0}function qD(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0;j=i;i=i+16|0;h=j;f=0;while(1){if(!(_D(c[34972+(f*12|0)>>2]|0,e)|0)){e=f;g=3;break}f=f+1|0;if((f|0)>=53){g=5;break}}if((g|0)==3){f=c[34972+(e*12|0)+8>>2]|0;e=c[34972+(e*12|0)+4>>2]|0}else if((g|0)==5){if((f|0)!=53){b=-22;i=j;return b|0}e=RE(e,h,10)|0;f=c[h>>2]|0;if(a[f>>0]|0){f=f+1|0;c[h>>2]=f}f=RE(f,h,10)|0;if(a[c[h>>2]>>0]|0){b=-22;i=j;return b|0}}if((e|0)<1|(f|0)<1){b=-22;i=j;return b|0}c[b>>2]=e;c[d>>2]=f;b=0;i=j;return b|0}function rD(a,b){a=a|0;b=b|0;var d=0;if(!(_D(100862,b)|0))b=0;else if(!(_D(100867,b)|0))b=1;else if(!(_D(100871,b)|0))b=2;else if(!(_D(100877,b)|0))b=3;else if(!(_D(100882,b)|0))b=4;else if(!(_D(100888,b)|0))b=5;else if(!(_D(100893,b)|0))b=6;else if(!(_D(100898,b)|0))b=7;else{b=pD(a,b,1001e3,64,0)|0;if((b|0)<0){a=b;return a|0}if((c[a>>2]|0)<1){a=-22;return a|0}else return ((c[a+4>>2]|0)<1?-22:0)|0}d=35608+(b*12|0)+4|0;b=c[d+4>>2]|0;c[a>>2]=c[d>>2];c[a+4>>2]=b;a=0;return a|0}function sD(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0.0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;t=i;i=i+160|0;s=t+16|0;m=t+8|0;l=t;r=t+28|0;p=t+32|0;k=t+24|0;if((a[d>>0]|0)==35)j=1;else{j=(kF(d,101164,2)|0)==0;j=j?2:0}if((e|0)<0)e=aE(d)|0;e=e-j+1|0;wB(p,d+j|0,e>>>0>128?128:e)|0;e=cF(p,64)|0;c[r>>2]=e;if(!e)o=0;else{o=e+1|0;c[r>>2]=o;a[e>>0]=0}h=aE(p)|0;n=b+3|0;a[n>>0]=-1;a:do if(!(yB(p,101167)|0))q=9;else if(!(yB(p,101174)|0))q=9;else{if(!j)if((ME(p,101183)|0)!=(h|0)){e=UE(p,35704,140,8,6)|0;if(e|0){p=e+4|0;a[b>>0]=a[p>>0]|0;a[b+1>>0]=a[p+1>>0]|0;a[b+2>>0]=a[p+2>>0]|0;break}c[m>>2]=p;UC(f,16,102632,m);s=-22;i=t;return s|0}e=QE(p,k,16)|0;b:do if(!(a[c[k>>2]>>0]|0)){switch(h|0){case 8:{a[n>>0]=e;e=e>>>8;break}case 6:break;default:break b}a[b>>0]=e>>>16;a[b+1>>0]=e>>>8;a[b+2>>0]=e;break a}while(0);c[l>>2]=p;UC(f,16,101206,l);s=-22;i=t;return s|0}while(0);if((q|0)==9){q=BD()|0;a[b>>0]=q>>>24;a[b+1>>0]=q>>>16;a[b+2>>0]=q>>>8;a[n>>0]=q}if(!o){s=0;i=t;return s|0}if(!(kF(o,101164,2)|0))g=+((QE(o,r,16)|0)>>>0);else{g=+TE(o,r);g=g<0.0|g>1.0?256.0:g*255.0}e=c[r>>2]|0;if((e|0)!=(o|0))if(!(g<0.0|(g>255.0|(a[e>>0]|0)!=0))){a[n>>0]=~~g;s=0;i=t;return s|0}c[s>>2]=o;c[s+4>>2]=d;UC(f,16,102656,s);s=-22;i=t;return s|0}function tD(a,b){a=a|0;b=b|0;return yB(a,c[b>>2]|0)|0}function uD(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;j=e+8|0;k=e+4|0;l=e+20|0;m=e+16|0;n=e+12|0;a:while(1){g=d+1|0;f=a[d>>0]|0;switch(f<<24>>24){case 0:{d=36;break a}case 37:break;default:{i=(AB(f<<24>>24)|0)==0;d=a[b>>0]|0;if(i){if(d<<24>>24!=f<<24>>24){b=0;d=36;break a}d=g;b=b+1|0;continue a}if(!(d<<24>>24)){d=g;continue a}while(1){if(!(AB(d<<24>>24)|0)){d=g;continue a}b=b+1|0;d=a[b>>0]|0;if(!(d<<24>>24)){d=g;continue a}}}}d=d+2|0;i=a[g>>0]|0;switch(i<<24>>24|0){case 74:case 72:{f=a[b>>0]|0;if(!(zB(f)|0)){b=0;d=36;break a}f=f+-48|0;g=b+1|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{b=b+2|0;f=(f*10|0)+-48+h|0}if((f|0)==-1?1:f>>>0>(i<<24>>24==72?23:2147483647)>>>0){b=0;d=36;break a}c[j>>2]=f;continue a}case 77:{f=a[b>>0]|0;if(!(zB(f)|0)){b=0;d=36;break a}f=f+-48|0;g=b+1|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{b=b+2|0;f=(f*10|0)+-48+h|0}if(f>>>0>59){b=0;d=36;break a}c[k>>2]=f;continue a}case 83:{f=a[b>>0]|0;if(!(zB(f)|0)){b=0;d=36;break a}f=f+-48|0;g=b+1|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{b=b+2|0;f=(f*10|0)+-48+h|0}if(f>>>0>59){b=0;d=36;break a}c[e>>2]=f;continue a}case 89:{f=a[b>>0]|0;if(!(zB(f)|0)){b=0;d=36;break a}f=f+-48|0;g=b+1|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{f=(f*10|0)+-48+h|0;g=b+2|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{f=(f*10|0)+-48+h|0;g=b+3|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{b=b+4|0;f=(f*10|0)+-48+h|0}}}if(f>>>0>9999){b=0;d=36;break a}c[l>>2]=f+-1900;continue a}case 109:{f=a[b>>0]|0;if(!(zB(f)|0)){b=0;d=36;break a}g=f+-48|0;f=b+1|0;h=a[f>>0]|0;if(!(zB(h)|0))b=f;else{b=b+2|0;g=(g*10|0)+-48+h|0}f=g+-1|0;if((g|0)==-1|f>>>0>11){b=0;d=36;break a}c[m>>2]=f;continue a}case 100:{f=a[b>>0]|0;if(!(zB(f)|0)){b=0;d=36;break a}f=f+-48|0;g=b+1|0;h=a[g>>0]|0;if(!(zB(h)|0))b=g;else{b=b+2|0;f=(f*10|0)+-48+h|0}if((f|0)==-1|(f+-1|0)>>>0>30){b=0;d=36;break a}c[n>>2]=f;continue a}case 84:{b=uD(b,102700,e)|0;if(!b){b=0;d=36;break a}else continue a}case 37:if((a[b>>0]|0)==37){b=b+1|0;continue a}else{b=0;d=36;break a}default:{b=0;d=36;break a}}}if((d|0)==36)return b|0;return 0}function vD(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=i;i=i+128|0;m=q+120|0;p=q+76|0;o=q+32|0;n=q;f=p;g=f+44|0;do{c[f>>2]=0;f=f+4|0}while((f|0)<(g|0));l=b;c[l>>2]=0;c[l+4>>2]=-2147483648;l=(e|0)!=0;do if(l){g=(a[d>>0]|0)==45;d=g?d+1|0:d;g=g&1;f=uD(d,102736,p)|0;if(!f){f=uD(d,102745,p)|0;e=p+8|0;c[e>>2]=0;if(!f){c[p>>2]=RE(d,n,10)|0;f=c[n>>2]|0;if((f|0)==(d|0)){b=-22;i=q;return b|0}else{c[p+4>>2]=0;c[e>>2]=0;if(!f)e=-22;else{k=0;break}i=q;return e|0}}else k=0}else k=0}else{e=_a(0)|0;c[m>>2]=e;if(!(yB(d,102709)|0)){p=RF(e|0,((e|0)<0)<<31>>31|0,1e6,0)|0;c[b>>2]=p;c[b+4>>2]=C;b=0;i=q;return b|0}e=uD(d,102713,p)|0;if(!e)e=uD(d,102722,p)|0;k=(e|0)==0;e=k?d:e;d=k&1;switch(a[e>>0]|0){case 32:case 116:case 84:{e=e+1|0;break}default:{}}f=uD(e,102700,p)|0;if(!f){f=uD(e,102729,p)|0;if(!f){b=-22;i=q;return b|0}else{g=0;k=d}}else{g=0;k=d}}while(0);e=a[f>>0]|0;if(e<<24>>24==46){d=f+1|0;e=a[d>>0]|0;if(!(zB(e)|0)){j=0;f=d}else{e=(e*1e5|0)+-48e5|0;h=f+2|0;d=a[h>>0]|0;if(!(zB(d)|0)){j=e;f=h}else{e=(d*1e4|0)+-48e4+e|0;h=f+3|0;d=a[h>>0]|0;if(!(zB(d)|0)){j=e;f=h}else{e=(d*1e3|0)+-48e3+e|0;h=f+4|0;d=a[h>>0]|0;if(!(zB(d)|0)){j=e;f=h}else{e=(d*100|0)+-4800+e|0;h=f+5|0;d=a[h>>0]|0;if(!(zB(d)|0)){j=e;f=h}else{e=(d*10|0)+-480+e|0;h=f+6|0;d=a[h>>0]|0;if(!(zB(d)|0)){j=e;f=h}else{j=d+-48+e|0;f=f+7|0}}}}}}while(1){e=a[f>>0]|0;if(!(zB(e<<24>>24)|0)){d=j;break}else f=f+1|0}}else d=0;do if(l)e=((c[p+4>>2]|0)*60|0)+((c[p+8>>2]|0)*3600|0)+(c[p>>2]|0)|0;else{e=(e|32)<<24>>24==122;f=f+(e&1)|0;if(k|0){if(e){o=(Wa(m|0,o|0)|0)+12|0;c[n>>2]=c[o>>2];c[n+4>>2]=c[o+4>>2];c[n+8>>2]=c[o+8>>2];c[n+12>>2]=c[o+12>>2];c[n+16>>2]=c[o+16>>2];c[n+20>>2]=c[o+20>>2];c[n+24>>2]=c[o+24>>2];c[n+28>>2]=c[o+28>>2]}else{o=(Ha(m|0,o|0)|0)+12|0;c[n>>2]=c[o>>2];c[n+4>>2]=c[o+4>>2];c[n+8>>2]=c[o+8>>2];c[n+12>>2]=c[o+12>>2];c[n+16>>2]=c[o+16>>2];c[n+20>>2]=c[o+20>>2];c[n+24>>2]=c[o+24>>2];c[n+28>>2]=c[o+28>>2]}o=p+12|0;c[o>>2]=c[n>>2];c[o+4>>2]=c[n+4>>2];c[o+8>>2]=c[n+8>>2];c[o+12>>2]=c[n+12>>2];c[o+16>>2]=c[n+16>>2];c[o+20>>2]=c[n+20>>2];c[o+24>>2]=c[n+24>>2];c[o+28>>2]=c[n+28>>2]}if(e){o=c[p+16>>2]|0;e=o+1|0;n=(e|0)<3;m=(n?1899:1900)+(c[p+20>>2]|0)|0;e=((c[p+8>>2]|0)*3600|0)+-2032579456+((c[p+4>>2]|0)*60|0)+(c[p>>2]|0)+(((m*365|0)+(c[p+12>>2]|0)+((m|0)/4|0)+((m|0)/-100|0)+((m|0)/400|0)+((((n?o+13|0:e)*153|0)+-457|0)/5|0)|0)*86400|0)|0;break}else{e=Oa(p|0)|0;break}}while(0);if(a[f>>0]|0){b=-22;i=q;return b|0}n=RF(e|0,((e|0)<0)<<31>>31|0,1e6,0)|0;n=HF(n|0,C|0,d|0,((d|0)<0)<<31>>31|0)|0;p=C;o=(g|0)!=0;m=EF(0,0,n|0,p|0)|0;c[b>>2]=o?m:n;c[b+4>>2]=o?C:p;b=0;i=q;return b|0}function wD(a){a=a|0;if(a>>>0>=340){a=0;return a|0}a=c[36824+(a*20|0)>>2]|0;return a|0}function xD(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0;h=i;i=i+48|0;e=h;g=h+8|0;if(!(_D(a,104421)|0))d=102968;else{d=(_D(a,104427)|0)==0;d=d?102958:a}a=0;do{b=c[36824+(a*20|0)>>2]|0;if(b|0){if(!(_D(b,d)|0)){f=8;break}if(BB(d,c[36824+(a*20|0)+16>>2]|0)|0){f=8;break}}a=a+1|0}while((a|0)<340);if((f|0)==8)if((a|0)!=-1){g=a;i=h;return g|0}c[e>>2]=d;c[e+4>>2]=104438;$E(g,32,104433,e)|0;a=0;while(1){b=c[36824+(a*20|0)>>2]|0;if(b|0){if(!(_D(b,g)|0))break;if(BB(g,c[36824+(a*20|0)+16>>2]|0)|0)break}a=a+1|0;if((a|0)>=340){a=-1;break}}g=a;i=h;return g|0}function yD(a){a=a|0;return (a>>>0>339?0:36824+(a*20|0)|0)|0}function zD(a,b,e){a=a|0;b=b|0;e=e|0;if(a>>>0>339|(36824+(a*20|0)|0)==0){e=-38;return e|0}c[b>>2]=d[36824+(a*20|0)+5>>0];c[e>>2]=d[36824+(a*20|0)+6>>0];e=0;return e|0}function AD(d){d=d|0;var e=0,f=0,g=0,h=0,j=0;j=i;i=i+16|0;h=j;c[h>>2]=0;c[h+4>>2]=0;c[h+8>>2]=0;c[h+12>>2]=0;if(d>>>0>339|(36824+(d*20|0)|0)==0){h=-22;i=j;return h|0}e=a[36824+(d*20|0)+4>>0]|0;if(!(e<<24>>24)){f=0;d=0;g=0;e=0}else{e=e&255;f=0;do{c[h+((b[36824+(d*20|0)+8+(f<<1)>>1]&3)<<2)>>2]=1;f=f+1|0}while((f|0)<(e|0));f=c[h>>2]|0;d=c[h+4>>2]|0;g=c[h+8>>2]|0;e=c[h+12>>2]|0}h=e+(g+(d+f))|0;i=j;return h|0}function BD(){var a=0,b=0,d=0,e=0,f=0,g=0,h=0,j=0,k=0,l=0;j=i;i=i+160|0;f=j+144|0;h=j+24|0;g=j;b=j+156|0;a=nC(104441,0,g)|0;if((a|0)!=-1){e=pF(a,b,4)|0;qE(a)|0;if((e|0)==4){h=c[b>>2]|0;i=j;return h|0}}a=nC(104454,0,h)|0;if((a|0)!=-1){e=pF(a,b,4)|0;qE(a)|0;if((e|0)==4){h=c[b>>2]|0;i=j;return h|0}}e=107512;d=c[e>>2]|0;e=c[e+4>>2]|0;if((c[10956]|0)>>>0>=121){c[f>>2]=104496;c[f+4>>2]=104523;c[f+8>>2]=75;UC(0,0,104466,f);Ya()}if((d|0)==0&(e|0)==0){b=0;while(1){a=Pa()|0;if((a|0)==(b|0)){b=110220+((c[26878]&511)<<2)|0;c[b>>2]=(c[b>>2]|0)+1;b=a;continue}else{f=107512;f=HF(c[f>>2]|0,c[f+4>>2]|0,1,0)|0;e=C;d=107512;c[d>>2]=f;c[d+4>>2]=e;d=110220+((f&511)<<2)|0;c[d>>2]=(c[d>>2]|0)+(((a-b|0)>>>0)%3294638521|0);if(e>>>0>0|(e|0)==0&f>>>0>64)break;else{b=a;continue}}}}else{b=0;while(1){a=Pa()|0;if((a|0)==(b|0)){b=110220+((c[26878]&511)<<2)|0;c[b>>2]=(c[b>>2]|0)+1;b=a;continue}else{k=107512;k=HF(c[k>>2]|0,c[k+4>>2]|0,1,0)|0;f=C;l=107512;c[l>>2]=k;c[l+4>>2]=f;l=110220+((k&511)<<2)|0;c[l>>2]=(c[l>>2]|0)+(((a-b|0)>>>0)%3294638521|0);f=EF(k|0,f|0,d|0,e|0)|0;b=C;if(b>>>0>0|(b|0)==0&f>>>0>4)break;else{b=a;continue}}}}LD(h,160)|0;OD(h,110220,2048);PD(h,g);l=MF(c[g>>2]|0)|0;l=(MF(c[g+16>>2]|0)|0)+l|0;i=j;return l|0}function CD(a,b,d,e,f,g,h,i){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0;B=g^e;k=(e|0)>-1|(e|0)==-1&d>>>0>4294967295;j=EF(0,0,d|0,e|0)|0;j=k?d:j;k=k?e:C;m=(g|0)>-1|(g|0)==-1&f>>>0>4294967295;l=EF(0,0,f|0,g|0)|0;l=m?f:l;m=m?g:C;n=WC(j,k,l,m)|0;o=C;if((n|0)==0&(o|0)==0)j=g;else{d=PF(j|0,k|0,n|0,o|0)|0;e=C;f=PF(l|0,m|0,n|0,o|0)|0;j=C}z=(e|0)>(i|0)|(e|0)==(i|0)&d>>>0>h>>>0;y=(j|0)>(i|0)|(j|0)==(i|0)&f>>>0>h>>>0;l=y|z;k=l?1:d;l=l?0:f;if((f|0)==0&(j|0)==0|(y|z)^1){i=0;A=0;z=k;h=l;y=(B|0)<0;B=0-z|0;B=y?B:z;c[a>>2]=B;c[b>>2]=h;b=(i|0)==0;a=(A|0)==0;a=b&a;a=a&1;return a|0}else{w=d;v=e;p=f;q=j;t=0;z=1}while(1){s=PF(w|0,v|0,p|0,q|0)|0;u=C;m=RF(s|0,u|0,p|0,q|0)|0;n=C;j=EF(w|0,v|0,m|0,n|0)|0;e=C;g=((k|0)<0)<<31>>31;f=RF(s|0,u|0,k|0,g|0)|0;r=((t|0)<0)<<31>>31;f=HF(f|0,C|0,t|0,r|0)|0;D=C;o=((l|0)<0)<<31>>31;d=RF(s|0,u|0,l|0,o|0)|0;x=((z|0)<0)<<31>>31;d=HF(d|0,C|0,z|0,x|0)|0;y=C;if((D|0)>(i|0)|(D|0)==(i|0)&f>>>0>h>>>0|((y|0)>(i|0)|(y|0)==(i|0)&d>>>0>h>>>0)){y=l;f=s;d=u;s=k;n=z;m=x;j=k;e=l;break}if((w|0)==(m|0)&(v|0)==(n|0)){A=12;break}else{z=l;t=k;v=q;w=p;p=j;q=e;k=f;l=d}}if((A|0)==12){B=(B|0)<0;D=0-f|0;D=B?D:f;c[a>>2]=D;c[b>>2]=d;a=(j|0)==0;D=(e|0)==0;D=a&D;D=D&1;return D|0}if(j){f=EF(h|0,i|0,t|0,r|0)|0;f=PF(f|0,C|0,s|0,g|0)|0;d=C}if(e){i=EF(h|0,i|0,n|0,m|0)|0;i=PF(i|0,C|0,y|0,o|0)|0;D=C;A=d>>>0>D>>>0|(d|0)==(D|0)&f>>>0>i>>>0;f=A?i:f;d=A?D:d}A=GF(y|0,o|0,1)|0;A=RF(A|0,C|0,f|0,d|0)|0;A=HF(A|0,C|0,n|0,m|0)|0;A=RF(A|0,C|0,p|0,q|0)|0;h=C;D=RF(w|0,v|0,y|0,o|0)|0;i=C;if(!(h>>>0>i>>>0|(h|0)==(i|0)&A>>>0>D>>>0)){A=p;D=q;h=j;i=e;z=(B|0)<0;B=0-h|0;B=z?B:h;c[a>>2]=B;c[b>>2]=i;a=(A|0)==0;D=(D|0)==0;D=a&D;D=D&1;return D|0}h=RF(f|0,d|0,s|0,g|0)|0;h=HF(h|0,C|0,t|0,r|0)|0;i=RF(f|0,d|0,y|0,o|0)|0;i=HF(i|0,C|0,n|0,m|0)|0;A=p;D=q;z=(B|0)<0;B=0-h|0;B=z?B:h;c[a>>2]=B;c[b>>2]=i;a=(A|0)==0;D=(D|0)==0;D=a&D;D=D&1;return D|0}function DD(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;h=b+4|0;g=c[b>>2]|0;f=c[d>>2]|0;g=RF(f|0,((f|0)<0)<<31>>31|0,g|0,((g|0)<0)<<31>>31|0)|0;f=C;e=c[h>>2]|0;d=c[d+4>>2]|0;e=RF(d|0,((d|0)<0)<<31>>31|0,e|0,((e|0)<0)<<31>>31|0)|0;CD(b,h,g,f,e,C,2147483647,0)|0;e=b;b=c[e+4>>2]|0;d=a;c[d>>2]=c[e>>2];c[d+4>>2]=b;return}function ED(a,b,d){a=a|0;b=+b;d=d|0;var e=0.0,f=0,g=0,j=0,l=0,m=0,n=0,o=0,p=0;p=i;i=i+16|0;n=p;e=+N(+b);h[k>>3]=e;m=c[k+4>>2]|0;if(m>>>0>2146435072|(m|0)==2146435072&(c[k>>2]|0)>>>0>0){c[a>>2]=0;c[a+4>>2]=0;i=p;return}e=+N(+b);if(e>2147483650.0){c[a>>2]=b<0.0?-1:1;c[a+4>>2]=0;i=p;return}f=~~(+Y(+(e+1.0e-20))/.6931471805599453);f=GF(1,0,61-((f|0)>0?f:0)|0)|0;g=C;j=n+4|0;e=+M(+((+(f>>>0)+4294967296.0*+(g|0))*b+.5));l=~~e>>>0;m=+N(e)>=1.0?(e>0.0?~~+$(+M(e/4294967296.0),4294967295.0)>>>0:~~+Z((e-+(~~e>>>0))/4294967296.0)>>>0):0;CD(n,j,l,m,f,g,d,((d|0)<0)<<31>>31)|0;if(!(c[n>>2]|0)){if((d|0)!=2147483647&(b!=0.0&(d|0)>0))o=8}else if((d|0)!=2147483647&((d|0)>0&(b!=0.0&(c[j>>2]|0)==0)))o=8;if((o|0)==8)CD(n,j,l,m,f,g,2147483647,0)|0;d=c[n+4>>2]|0;o=a;c[o>>2]=c[n>>2];c[o+4>>2]=d;i=p;return}function FD(a){a=a|0;return (a>>>0>9?0:43624+(a*20|0)|0)|0}function GD(a){a=a|0;if(!(_D(43624,a)|0))a=0;else if(!(_D(43644,a)|0))a=1;else if(!(_D(43664,a)|0))a=2;else if(!(_D(43684,a)|0))a=3;else if(!(_D(43704,a)|0))a=4;else if(!(_D(43724,a)|0))a=5;else if(!(_D(43744,a)|0))a=6;else if(!(_D(43764,a)|0))a=7;else if(!(_D(43784,a)|0))a=8;else{a=(_D(43804,a)|0)==0;return (a?9:-1)|0}return a|0}function HD(a){a=a|0;if(a>>>0>9){a=-1;return a|0}if((a+-5|0)>>>0<5)return a|0;a=c[43624+(a*20|0)+16>>2]|0;return a|0}function ID(a){a=a|0;if(a>>>0>9){a=0;return a|0}a=c[43624+(a*20|0)+12>>2]|0;return a|0}function JD(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0;if(e>>>0>9){b=-22;return b|0}g=c[43624+(e*20|0)+8>>2]>>3;if((b|0)<1|((d|0)<1|(g|0)==0)){b=-22;return b|0}do if(!f)if((d|0)>2147483616){b=-22;return b|0}else{d=d+31&-32;f=1;break}while(0);if((2147483647/(f|0)|0|0)<(b|0)){b=-22;return b|0}i=RF(d|0,((d|0)<0)<<31>>31|0,b|0,((b|0)<0)<<31>>31|0)|0;k=C;h=(2147483647-(_(f,b)|0)|0)/(g|0)|0;j=((h|0)<0)<<31>>31;if((k|0)>(j|0)|(k|0)==(j|0)&i>>>0>h>>>0){k=-22;return k|0}e=(e+-5|0)>>>0<5;d=_(d,g)|0;if(e)d=f+-1+d&0-f;else d=f+-1+(_(d,b)|0)&0-f;if(a|0)c[a>>2]=d;k=_(d,e?b:1)|0;return k|0}function KD(a,b,d,e,f,g,h){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0;if(h>>>0>9){k=1;h=0}else{j=(h+-5|0)>>>0<5;k=j?g:1;h=c[43624+(h*20|0)+8>>2]>>3;g=j?1:g}g=_(g,h)|0;j=_(g,f)|0;i=_(g,d)|0;d=_(g,e)|0;g=c[a>>2]|0;h=c[b>>2]|0;e=h;l=g;f=(k|0)>0;if(((g>>>0<h>>>0?e-l|0:l-e|0)|0)<(j|0)){if(!f)return 0;LF(g+i|0,h+d|0,j|0)|0;if((k|0)==1)return 0;else g=1;do{LF((c[a+(g<<2)>>2]|0)+i|0,(c[b+(g<<2)>>2]|0)+d|0,j|0)|0;g=g+1|0}while((g|0)!=(k|0));return 0}else{if(!f)return 0;KF(g+i|0,h+d|0,j|0)|0;if((k|0)==1)return 0;else g=1;do{KF((c[a+(g<<2)>>2]|0)+i|0,(c[b+(g<<2)>>2]|0)+d|0,j|0)|0;g=g+1|0}while((g|0)!=(k|0));return 0}return 0}function LD(b,d){b=b|0;d=d|0;a[b>>0]=d>>>5;switch(d|0){case 160:{c[b+80>>2]=1732584193;c[b+84>>2]=-271733879;c[b+88>>2]=-1732584194;c[b+92>>2]=271733878;c[b+96>>2]=-1009589776;c[b+112>>2]=187;break}case 224:{c[b+80>>2]=-1056596264;c[b+84>>2]=914150663;c[b+88>>2]=812702999;c[b+92>>2]=-150054599;c[b+96>>2]=-4191439;c[b+100>>2]=1750603025;c[b+104>>2]=1694076839;c[b+108>>2]=-1090891868;c[b+112>>2]=188;break}case 256:{c[b+80>>2]=1779033703;c[b+84>>2]=-1150833019;c[b+88>>2]=1013904242;c[b+92>>2]=-1521486534;c[b+96>>2]=1359893119;c[b+100>>2]=-1694144372;c[b+104>>2]=528734635;c[b+108>>2]=1541459225;c[b+112>>2]=188;break}default:{b=-1;return b|0}}b=b+8|0;c[b>>2]=0;c[b+4>>2]=0;b=0;return b|0}function MD(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;n=c[a>>2]|0;j=a+4|0;k=c[j>>2]|0;h=a+8|0;i=c[h>>2]|0;f=a+12|0;g=c[f>>2]|0;e=a+16|0;F=c[e>>2]|0;u=d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24;F=(n<<5|n>>>27)+1518500249+F+(MF(u|0)|0)+((g^i)&k^g)|0;k=k<<30|k>>>2;q=b+4|0;q=d[q>>0]|d[q+1>>0]<<8|d[q+2>>0]<<16|d[q+3>>0]<<24;g=g+1518500249+((k^i)&n^i)+(MF(q|0)|0)+(F<<5|F>>>27)|0;l=n<<30|n>>>2;v=b+8|0;v=d[v>>0]|d[v+1>>0]<<8|d[v+2>>0]<<16|d[v+3>>0]<<24;i=i+1518500249+(MF(v|0)|0)+(F&(k^l)^k)+(g<<5|g>>>27)|0;F=F<<30|F>>>2;A=b+12|0;A=d[A>>0]|d[A+1>>0]<<8|d[A+2>>0]<<16|d[A+3>>0]<<24;k=k+1518500249+(MF(A|0)|0)+(g&(F^l)^l)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;y=b+16|0;y=d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24;l=l+1518500249+(MF(y|0)|0)+(i&(g^F)^F)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;w=b+20|0;w=d[w>>0]|d[w+1>>0]<<8|d[w+2>>0]<<16|d[w+3>>0]<<24;F=(MF(w|0)|0)+1518500249+F+(k&(i^g)^g)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;s=b+24|0;s=d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24;g=(MF(s|0)|0)+1518500249+g+(l&(k^i)^i)+(F<<5|F>>>27)|0;l=l<<30|l>>>2;o=b+28|0;o=d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24;i=(MF(o|0)|0)+1518500249+i+(F&(l^k)^k)+(g<<5|g>>>27)|0;F=F<<30|F>>>2;r=b+32|0;r=d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24;k=(MF(r|0)|0)+1518500249+k+(g&(F^l)^l)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;m=b+36|0;m=d[m>>0]|d[m+1>>0]<<8|d[m+2>>0]<<16|d[m+3>>0]<<24;l=(MF(m|0)|0)+1518500249+l+(i&(g^F)^F)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;C=b+40|0;C=d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24;F=(MF(C|0)|0)+1518500249+F+(k&(i^g)^g)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;B=b+44|0;B=d[B>>0]|d[B+1>>0]<<8|d[B+2>>0]<<16|d[B+3>>0]<<24;g=(MF(B|0)|0)+1518500249+g+(l&(k^i)^i)+(F<<5|F>>>27)|0;l=l<<30|l>>>2;z=b+48|0;z=d[z>>0]|d[z+1>>0]<<8|d[z+2>>0]<<16|d[z+3>>0]<<24;i=(MF(z|0)|0)+1518500249+i+(F&(l^k)^k)+(g<<5|g>>>27)|0;F=F<<30|F>>>2;x=b+52|0;x=d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24;k=(MF(x|0)|0)+1518500249+k+(g&(F^l)^l)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;E=b+56|0;E=d[E>>0]|d[E+1>>0]<<8|d[E+2>>0]<<16|d[E+3>>0]<<24;t=MF(E|0)|0;l=t+1518500249+l+(i&(g^F)^F)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;D=b+60|0;D=d[D>>0]|d[D+1>>0]<<8|d[D+2>>0]<<16|d[D+3>>0]<<24;p=MF(D|0)|0;b=p+1518500249+F+(k&(i^g)^g)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;u=MF(v^u^r^x|0)|0;u=u<<1|u>>>31;g=u+1518500249+g+(l&(k^i)^i)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;q=MF(A^q^m^E|0)|0;q=q<<1|q>>>31;i=q+1518500249+i+(b&(l^k)^k)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;v=MF(y^v^C^D|0)|0;v=v<<1|v>>>31;k=v+1518500249+k+(g&(b^l)^l)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;A=(MF(w^A^B|0)|0)^u;A=A<<1|A>>>31;l=A+1518500249+l+(i&(g^b)^b)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;y=(MF(s^y^z|0)|0)^q;y=y<<1|y>>>31;b=y+1859775393+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;w=(MF(o^w^x|0)|0)^v;w=w<<1|w>>>31;g=w+1859775393+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;s=(MF(r^s^E|0)|0)^A;s=s<<1|s>>>31;i=s+1859775393+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;o=(MF(m^o^D|0)|0)^y;o=o<<1|o>>>31;k=o+1859775393+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;r=(MF(C^r|0)|0)^u^w;r=r<<1|r>>>31;l=r+1859775393+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;m=(MF(B^m|0)|0)^q^s;m=m<<1|m>>>31;b=m+1859775393+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;C=(MF(z^C|0)|0)^v^o;C=C<<1|C>>>31;g=C+1859775393+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;B=(MF(x^B|0)|0)^A^r;B=B<<1|B>>>31;i=B+1859775393+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;z=(MF(E^z|0)|0)^y^m;z=z<<1|z>>>31;k=z+1859775393+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;x=(MF(D^x|0)|0)^w^C;x=x<<1|x>>>31;l=x+1859775393+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;t=u^t^s^B;t=t<<1|t>>>31;b=t+1859775393+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;p=q^p^o^z;p=p<<1|p>>>31;g=p+1859775393+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;u=v^u^r^x;u=u<<1|u>>>31;i=u+1859775393+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;q=A^q^m^t;q=q<<1|q>>>31;k=q+1859775393+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;v=y^v^C^p;v=v<<1|v>>>31;l=v+1859775393+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;A=w^A^B^u;A=A<<1|A>>>31;b=A+1859775393+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;y=s^y^z^q;y=y<<1|y>>>31;g=y+1859775393+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;w=o^w^x^v;w=w<<1|w>>>31;i=w+1859775393+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;s=r^s^t^A;s=s<<1|s>>>31;k=s+1859775393+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;o=m^o^p^y;o=o<<1|o>>>31;l=o+1859775393+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;r=C^r^u^w;r=r<<1|r>>>31;b=r+-1894007588+b+((k|i)&g|k&i)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;m=B^m^q^s;m=m<<1|m>>>31;g=m+-1894007588+g+((l|k)&i|l&k)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;C=z^C^v^o;C=C<<1|C>>>31;i=C+-1894007588+i+((b|l)&k|b&l)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;B=x^B^A^r;B=B<<1|B>>>31;k=B+-1894007588+k+((g|b)&l|g&b)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;z=t^z^y^m;z=z<<1|z>>>31;l=z+-1894007588+l+((i|g)&b|i&g)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;x=p^x^w^C;x=x<<1|x>>>31;b=x+-1894007588+b+((k|i)&g|k&i)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;t=u^t^s^B;t=t<<1|t>>>31;g=t+-1894007588+g+((l|k)&i|l&k)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;p=q^p^o^z;p=p<<1|p>>>31;i=p+-1894007588+i+((b|l)&k|b&l)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;u=v^u^r^x;u=u<<1|u>>>31;k=u+-1894007588+k+((g|b)&l|g&b)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;q=A^q^m^t;q=q<<1|q>>>31;l=q+-1894007588+l+((i|g)&b|i&g)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;v=y^v^C^p;v=v<<1|v>>>31;b=v+-1894007588+b+((k|i)&g|k&i)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;A=w^A^B^u;A=A<<1|A>>>31;g=A+-1894007588+g+((l|k)&i|l&k)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;y=s^y^z^q;y=y<<1|y>>>31;i=y+-1894007588+i+((b|l)&k|b&l)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;w=o^w^x^v;w=w<<1|w>>>31;k=w+-1894007588+k+((g|b)&l|g&b)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;s=r^s^t^A;s=s<<1|s>>>31;l=s+-1894007588+l+((i|g)&b|i&g)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;o=m^o^p^y;o=o<<1|o>>>31;b=o+-1894007588+b+((k|i)&g|k&i)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;r=C^r^u^w;r=r<<1|r>>>31;g=r+-1894007588+g+((l|k)&i|l&k)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;m=B^m^q^s;m=m<<1|m>>>31;i=m+-1894007588+i+((b|l)&k|b&l)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;C=z^C^v^o;C=C<<1|C>>>31;k=C+-1894007588+k+((g|b)&l|g&b)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;B=x^B^A^r;B=B<<1|B>>>31;l=B+-1894007588+l+((i|g)&b|i&g)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;z=t^z^y^m;z=z<<1|z>>>31;b=z+-899497514+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;x=p^x^w^C;x=x<<1|x>>>31;g=x+-899497514+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;t=u^t^s^B;t=t<<1|t>>>31;i=t+-899497514+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;p=q^p^o^z;p=p<<1|p>>>31;k=p+-899497514+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;u=v^u^r^x;u=u<<1|u>>>31;l=u+-899497514+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;q=A^q^m^t;q=q<<1|q>>>31;b=q+-899497514+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;v=y^v^C^p;v=v<<1|v>>>31;g=v+-899497514+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;A=w^A^B^u;A=A<<1|A>>>31;i=A+-899497514+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;y=s^y^z^q;y=y<<1|y>>>31;k=y+-899497514+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;w=o^w^x^v;w=w<<1|w>>>31;l=w+-899497514+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;s=r^s^t^A;s=s<<1|s>>>31;b=s+-899497514+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;o=m^o^p^y;o=o<<1|o>>>31;g=o+-899497514+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;r=C^r^u^w;r=r<<1|r>>>31;i=r+-899497514+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;m=B^m^q^s;m=m<<1|m>>>31;k=m+-899497514+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;v=z^C^v^o;v=v<<1|v>>>31;l=v+-899497514+l+(g^b^i)+(k<<5|k>>>27)|0;i=i<<30|i>>>2;r=x^B^A^r;r=r<<1|r>>>31;b=r+-899497514+b+(i^g^k)+(l<<5|l>>>27)|0;k=k<<30|k>>>2;m=t^z^y^m;m=m<<1|m>>>31;g=m+-899497514+g+(k^i^l)+(b<<5|b>>>27)|0;l=l<<30|l>>>2;v=p^x^w^v;i=(v<<1|v>>>31)+-899497514+i+(l^k^b)+(g<<5|g>>>27)|0;b=b<<30|b>>>2;r=u^t^s^r;k=(r<<1|r>>>31)+-899497514+k+(b^l^g)+(i<<5|i>>>27)|0;g=g<<30|g>>>2;m=q^p^o^m;c[a>>2]=n+-899497514+(m<<1|m>>>31)+l+(g^b^i)+(k<<5|k>>>27);c[j>>2]=k+(c[j>>2]|0);c[h>>2]=(i<<30|i>>>2)+(c[h>>2]|0);c[f>>2]=g+(c[f>>2]|0);c[e>>2]=b+(c[e>>2]|0);return}function ND(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0;s=c[a>>2]|0;q=a+4|0;r=c[q>>2]|0;o=a+8|0;p=c[o>>2]|0;m=a+12|0;n=c[m>>2]|0;j=a+16|0;l=c[j>>2]|0;h=a+20|0;i=c[h>>2]|0;f=a+24|0;g=c[f>>2]|0;e=a+28|0;J=c[e>>2]|0;v=MF(d[b>>0]|d[b+1>>0]<<8|d[b+2>>0]<<16|d[b+3>>0]<<24|0)|0;J=J+1116352408+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))+((g^i)&l^g)+v|0;n=J+n|0;J=((p|r)&s|p&r)+((s<<30|s>>>2)^(s<<19|s>>>13)^(s<<10|s>>>22))+J|0;u=b+4|0;u=MF(d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24|0)|0;g=g+1899447441+u+(n&(i^l)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((J<<30|J>>>2)^(J<<19|J>>>13)^(J<<10|J>>>22))+(J&(r|s)|r&s)+g|0;t=b+8|0;t=MF(d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24|0)|0;i=i+-1245643825+t+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(J|s)|J&s)+i|0;x=b+12|0;x=MF(d[x>>0]|d[x+1>>0]<<8|d[x+2>>0]<<16|d[x+3>>0]<<24|0)|0;l=l+-373957723+x+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+s|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|J)|g&J)+l|0;E=b+16|0;E=MF(d[E>>0]|d[E+1>>0]<<8|d[E+2>>0]<<16|d[E+3>>0]<<24|0)|0;n=n+961987163+E+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;J=n+J|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;C=b+20|0;C=MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0;p=C+1508970993+p+(J&(k^r)^r)+((J<<26|J>>>6)^(J<<21|J>>>11)^(J<<7|J>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;A=b+24|0;A=MF(d[A>>0]|d[A+1>>0]<<8|d[A+2>>0]<<16|d[A+3>>0]<<24|0)|0;r=A+-1841331548+r+(g&(J^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;y=b+28|0;y=MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0;k=y+-1424204075+k+(i&(g^J)^J)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;I=b+32|0;I=MF(d[I>>0]|d[I+1>>0]<<8|d[I+2>>0]<<16|d[I+3>>0]<<24|0)|0;J=I+-670586216+J+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=J+n|0;J=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+J|0;H=b+36|0;H=MF(d[H>>0]|d[H+1>>0]<<8|d[H+2>>0]<<16|d[H+3>>0]<<24|0)|0;g=H+310598401+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((J<<30|J>>>2)^(J<<19|J>>>13)^(J<<10|J>>>22))+(J&(k|r)|k&r)+g|0;G=b+40|0;G=MF(d[G>>0]|d[G+1>>0]<<8|d[G+2>>0]<<16|d[G+3>>0]<<24|0)|0;i=G+607225278+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(J|k)|J&k)+i|0;F=b+44|0;F=MF(d[F>>0]|d[F+1>>0]<<8|d[F+2>>0]<<16|d[F+3>>0]<<24|0)|0;l=F+1426881987+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|J)|g&J)+l|0;D=b+48|0;D=MF(d[D>>0]|d[D+1>>0]<<8|d[D+2>>0]<<16|d[D+3>>0]<<24|0)|0;n=D+1925078388+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;J=n+J|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;B=b+52|0;B=MF(d[B>>0]|d[B+1>>0]<<8|d[B+2>>0]<<16|d[B+3>>0]<<24|0)|0;p=B+-2132889090+p+(J&(k^r)^r)+((J<<26|J>>>6)^(J<<21|J>>>11)^(J<<7|J>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;z=b+56|0;z=MF(d[z>>0]|d[z+1>>0]<<8|d[z+2>>0]<<16|d[z+3>>0]<<24|0)|0;r=z+-1680079193+r+(g&(J^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;w=b+60|0;w=MF(d[w>>0]|d[w+1>>0]<<8|d[w+2>>0]<<16|d[w+3>>0]<<24|0)|0;k=w+-1046744716+k+(i&(g^J)^J)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;v=((u<<14|u>>>18)^u>>>3^(u<<25|u>>>7))+v+H+((z<<13|z>>>19)^z>>>10^(z<<15|z>>>17))|0;b=v+-459576895+J+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=b+n|0;b=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+b|0;u=((t<<14|t>>>18)^t>>>3^(t<<25|t>>>7))+u+G+((w<<13|w>>>19)^w>>>10^(w<<15|w>>>17))|0;g=u+-272742522+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((b<<30|b>>>2)^(b<<19|b>>>13)^(b<<10|b>>>22))+(b&(k|r)|k&r)+g|0;t=((x<<14|x>>>18)^x>>>3^(x<<25|x>>>7))+t+F+((v<<13|v>>>19)^v>>>10^(v<<15|v>>>17))|0;i=t+264347078+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(b|k)|b&k)+i|0;x=((E<<14|E>>>18)^E>>>3^(E<<25|E>>>7))+x+D+((u<<13|u>>>19)^u>>>10^(u<<15|u>>>17))|0;l=x+604807628+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|b)|g&b)+l|0;E=((C<<14|C>>>18)^C>>>3^(C<<25|C>>>7))+E+B+((t<<13|t>>>19)^t>>>10^(t<<15|t>>>17))|0;n=E+770255983+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;b=n+b|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;C=((A<<14|A>>>18)^A>>>3^(A<<25|A>>>7))+C+z+((x<<13|x>>>19)^x>>>10^(x<<15|x>>>17))|0;p=C+1249150122+p+(b&(k^r)^r)+((b<<26|b>>>6)^(b<<21|b>>>11)^(b<<7|b>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;A=((y<<14|y>>>18)^y>>>3^(y<<25|y>>>7))+A+w+((E<<13|E>>>19)^E>>>10^(E<<15|E>>>17))|0;r=A+1555081692+r+(g&(b^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;y=((I<<14|I>>>18)^I>>>3^(I<<25|I>>>7))+y+v+((C<<13|C>>>19)^C>>>10^(C<<15|C>>>17))|0;k=y+1996064986+k+(i&(g^b)^b)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;I=((H<<14|H>>>18)^H>>>3^(H<<25|H>>>7))+I+u+((A<<13|A>>>19)^A>>>10^(A<<15|A>>>17))|0;b=I+-1740746414+b+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=b+n|0;b=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+b|0;H=((G<<14|G>>>18)^G>>>3^(G<<25|G>>>7))+H+t+((y<<13|y>>>19)^y>>>10^(y<<15|y>>>17))|0;g=H+-1473132947+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((b<<30|b>>>2)^(b<<19|b>>>13)^(b<<10|b>>>22))+(b&(k|r)|k&r)+g|0;G=((F<<14|F>>>18)^F>>>3^(F<<25|F>>>7))+G+x+((I<<13|I>>>19)^I>>>10^(I<<15|I>>>17))|0;i=G+-1341970488+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(b|k)|b&k)+i|0;F=((D<<14|D>>>18)^D>>>3^(D<<25|D>>>7))+F+E+((H<<13|H>>>19)^H>>>10^(H<<15|H>>>17))|0;l=F+-1084653625+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|b)|g&b)+l|0;D=((B<<14|B>>>18)^B>>>3^(B<<25|B>>>7))+D+C+((G<<13|G>>>19)^G>>>10^(G<<15|G>>>17))|0;n=D+-958395405+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;b=n+b|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;B=((z<<14|z>>>18)^z>>>3^(z<<25|z>>>7))+B+A+((F<<13|F>>>19)^F>>>10^(F<<15|F>>>17))|0;p=B+-710438585+p+(b&(k^r)^r)+((b<<26|b>>>6)^(b<<21|b>>>11)^(b<<7|b>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;z=((w<<14|w>>>18)^w>>>3^(w<<25|w>>>7))+z+y+((D<<13|D>>>19)^D>>>10^(D<<15|D>>>17))|0;r=z+113926993+r+(g&(b^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;w=((v<<14|v>>>18)^v>>>3^(v<<25|v>>>7))+w+I+((B<<13|B>>>19)^B>>>10^(B<<15|B>>>17))|0;k=w+338241895+k+(i&(g^b)^b)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;v=((u<<14|u>>>18)^u>>>3^(u<<25|u>>>7))+v+H+((z<<13|z>>>19)^z>>>10^(z<<15|z>>>17))|0;b=v+666307205+b+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=b+n|0;b=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+b|0;u=((t<<14|t>>>18)^t>>>3^(t<<25|t>>>7))+u+G+((w<<13|w>>>19)^w>>>10^(w<<15|w>>>17))|0;g=u+773529912+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((b<<30|b>>>2)^(b<<19|b>>>13)^(b<<10|b>>>22))+(b&(k|r)|k&r)+g|0;t=((x<<14|x>>>18)^x>>>3^(x<<25|x>>>7))+t+F+((v<<13|v>>>19)^v>>>10^(v<<15|v>>>17))|0;i=t+1294757372+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(b|k)|b&k)+i|0;x=((E<<14|E>>>18)^E>>>3^(E<<25|E>>>7))+x+D+((u<<13|u>>>19)^u>>>10^(u<<15|u>>>17))|0;l=x+1396182291+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|b)|g&b)+l|0;E=((C<<14|C>>>18)^C>>>3^(C<<25|C>>>7))+E+B+((t<<13|t>>>19)^t>>>10^(t<<15|t>>>17))|0;n=E+1695183700+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;b=n+b|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;C=((A<<14|A>>>18)^A>>>3^(A<<25|A>>>7))+C+z+((x<<13|x>>>19)^x>>>10^(x<<15|x>>>17))|0;p=C+1986661051+p+(b&(k^r)^r)+((b<<26|b>>>6)^(b<<21|b>>>11)^(b<<7|b>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;A=((y<<14|y>>>18)^y>>>3^(y<<25|y>>>7))+A+w+((E<<13|E>>>19)^E>>>10^(E<<15|E>>>17))|0;r=A+-2117940946+r+(g&(b^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;y=((I<<14|I>>>18)^I>>>3^(I<<25|I>>>7))+y+v+((C<<13|C>>>19)^C>>>10^(C<<15|C>>>17))|0;k=y+-1838011259+k+(i&(g^b)^b)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;I=((H<<14|H>>>18)^H>>>3^(H<<25|H>>>7))+I+u+((A<<13|A>>>19)^A>>>10^(A<<15|A>>>17))|0;b=I+-1564481375+b+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=b+n|0;b=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+b|0;H=((G<<14|G>>>18)^G>>>3^(G<<25|G>>>7))+H+t+((y<<13|y>>>19)^y>>>10^(y<<15|y>>>17))|0;g=H+-1474664885+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((b<<30|b>>>2)^(b<<19|b>>>13)^(b<<10|b>>>22))+(b&(k|r)|k&r)+g|0;G=((F<<14|F>>>18)^F>>>3^(F<<25|F>>>7))+G+x+((I<<13|I>>>19)^I>>>10^(I<<15|I>>>17))|0;i=G+-1035236496+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(b|k)|b&k)+i|0;F=((D<<14|D>>>18)^D>>>3^(D<<25|D>>>7))+F+E+((H<<13|H>>>19)^H>>>10^(H<<15|H>>>17))|0;l=F+-949202525+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|b)|g&b)+l|0;D=((B<<14|B>>>18)^B>>>3^(B<<25|B>>>7))+D+C+((G<<13|G>>>19)^G>>>10^(G<<15|G>>>17))|0;n=D+-778901479+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;b=n+b|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;B=((z<<14|z>>>18)^z>>>3^(z<<25|z>>>7))+B+A+((F<<13|F>>>19)^F>>>10^(F<<15|F>>>17))|0;p=B+-694614492+p+(b&(k^r)^r)+((b<<26|b>>>6)^(b<<21|b>>>11)^(b<<7|b>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;z=((w<<14|w>>>18)^w>>>3^(w<<25|w>>>7))+z+y+((D<<13|D>>>19)^D>>>10^(D<<15|D>>>17))|0;r=z+-200395387+r+(g&(b^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;w=((v<<14|v>>>18)^v>>>3^(v<<25|v>>>7))+w+I+((B<<13|B>>>19)^B>>>10^(B<<15|B>>>17))|0;k=w+275423344+k+(i&(g^b)^b)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;v=((u<<14|u>>>18)^u>>>3^(u<<25|u>>>7))+v+H+((z<<13|z>>>19)^z>>>10^(z<<15|z>>>17))|0;b=v+430227734+b+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=b+n|0;b=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+b|0;u=((t<<14|t>>>18)^t>>>3^(t<<25|t>>>7))+u+G+((w<<13|w>>>19)^w>>>10^(w<<15|w>>>17))|0;g=u+506948616+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((b<<30|b>>>2)^(b<<19|b>>>13)^(b<<10|b>>>22))+(b&(k|r)|k&r)+g|0;t=((x<<14|x>>>18)^x>>>3^(x<<25|x>>>7))+t+F+((v<<13|v>>>19)^v>>>10^(v<<15|v>>>17))|0;i=t+659060556+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(b|k)|b&k)+i|0;x=((E<<14|E>>>18)^E>>>3^(E<<25|E>>>7))+x+D+((u<<13|u>>>19)^u>>>10^(u<<15|u>>>17))|0;l=x+883997877+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|b)|g&b)+l|0;E=((C<<14|C>>>18)^C>>>3^(C<<25|C>>>7))+E+B+((t<<13|t>>>19)^t>>>10^(t<<15|t>>>17))|0;n=E+958139571+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;b=n+b|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;C=((A<<14|A>>>18)^A>>>3^(A<<25|A>>>7))+C+z+((x<<13|x>>>19)^x>>>10^(x<<15|x>>>17))|0;p=C+1322822218+p+(b&(k^r)^r)+((b<<26|b>>>6)^(b<<21|b>>>11)^(b<<7|b>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;A=((y<<14|y>>>18)^y>>>3^(y<<25|y>>>7))+A+w+((E<<13|E>>>19)^E>>>10^(E<<15|E>>>17))|0;r=A+1537002063+r+(g&(b^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;y=((I<<14|I>>>18)^I>>>3^(I<<25|I>>>7))+y+v+((C<<13|C>>>19)^C>>>10^(C<<15|C>>>17))|0;k=y+1747873779+k+(i&(g^b)^b)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;l=k+l|0;k=((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+(r&(p|n)|p&n)+k|0;u=((H<<14|H>>>18)^H>>>3^(H<<25|H>>>7))+I+u+((A<<13|A>>>19)^A>>>10^(A<<15|A>>>17))|0;b=u+1955562222+b+(l&(i^g)^g)+((l<<26|l>>>6)^(l<<21|l>>>11)^(l<<7|l>>>25))|0;n=b+n|0;b=((k<<30|k>>>2)^(k<<19|k>>>13)^(k<<10|k>>>22))+(k&(r|p)|r&p)+b|0;t=((G<<14|G>>>18)^G>>>3^(G<<25|G>>>7))+H+t+((y<<13|y>>>19)^y>>>10^(y<<15|y>>>17))|0;g=t+2024104815+g+(n&(l^i)^i)+((n<<26|n>>>6)^(n<<21|n>>>11)^(n<<7|n>>>25))|0;p=g+p|0;g=((b<<30|b>>>2)^(b<<19|b>>>13)^(b<<10|b>>>22))+(b&(k|r)|k&r)+g|0;x=((F<<14|F>>>18)^F>>>3^(F<<25|F>>>7))+G+x+((u<<13|u>>>19)^u>>>10^(u<<15|u>>>17))|0;i=x+-2067236844+i+(p&(n^l)^l)+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))|0;r=i+r|0;i=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&(b|k)|b&k)+i|0;t=((D<<14|D>>>18)^D>>>3^(D<<25|D>>>7))+F+E+((t<<13|t>>>19)^t>>>10^(t<<15|t>>>17))|0;l=t+-1933114872+l+(r&(p^n)^n)+((r<<26|r>>>6)^(r<<21|r>>>11)^(r<<7|r>>>25))|0;k=l+k|0;l=((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+(i&(g|b)|g&b)+l|0;x=((B<<14|B>>>18)^B>>>3^(B<<25|B>>>7))+D+C+((x<<13|x>>>19)^x>>>10^(x<<15|x>>>17))|0;n=x+-1866530822+n+(k&(r^p)^p)+((k<<26|k>>>6)^(k<<21|k>>>11)^(k<<7|k>>>25))|0;b=n+b|0;n=((l<<30|l>>>2)^(l<<19|l>>>13)^(l<<10|l>>>22))+(l&(i|g)|i&g)+n|0;t=((z<<14|z>>>18)^z>>>3^(z<<25|z>>>7))+B+A+((t<<13|t>>>19)^t>>>10^(t<<15|t>>>17))|0;p=t+-1538233109+p+(b&(k^r)^r)+((b<<26|b>>>6)^(b<<21|b>>>11)^(b<<7|b>>>25))|0;g=p+g|0;p=((n<<30|n>>>2)^(n<<19|n>>>13)^(n<<10|n>>>22))+(n&(l|i)|l&i)+p|0;r=z+-1090935817+((w<<14|w>>>18)^w>>>3^(w<<25|w>>>7))+y+((x<<13|x>>>19)^x>>>10^(x<<15|x>>>17))+r+(g&(b^k)^k)+((g<<26|g>>>6)^(g<<21|g>>>11)^(g<<7|g>>>25))|0;i=r+i|0;r=((p<<30|p>>>2)^(p<<19|p>>>13)^(p<<10|p>>>22))+(p&(n|l)|n&l)+r|0;k=w+-965641998+((v<<14|v>>>18)^v>>>3^(v<<25|v>>>7))+u+((t<<13|t>>>19)^t>>>10^(t<<15|t>>>17))+k+(i&(g^b)^b)+((i<<26|i>>>6)^(i<<21|i>>>11)^(i<<7|i>>>25))|0;c[a>>2]=(r&(p|n)|p&n)+s+((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+k;c[q>>2]=r+(c[q>>2]|0);c[o>>2]=p+(c[o>>2]|0);c[m>>2]=n+(c[m>>2]|0);c[j>>2]=l+(c[j>>2]|0)+k;c[h>>2]=i+(c[h>>2]|0);c[f>>2]=g+(c[f>>2]|0);c[e>>2]=b+(c[e>>2]|0);return}function OD(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;h=a+8|0;g=h;f=c[g>>2]|0;e=f&63;g=HF(f|0,c[g+4>>2]|0,d|0,0)|0;c[h>>2]=g;c[h+4>>2]=C;if((e+d|0)>>>0>63){f=64-e|0;KF(a+16+e|0,b|0,f|0)|0;g=a+112|0;h=a+80|0;jb[c[g>>2]&255](h,a+16|0);if((f+63|0)>>>0<d>>>0)while(1){jb[c[g>>2]&255](h,b+f|0);e=f+64|0;if((f+127|0)>>>0<d>>>0)f=e;else{f=e;e=0;break}}else e=0}else f=0;KF(a+16+e|0,b+f|0,d-f|0)|0;return}function PD(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;o=i;i=i+16|0;n=o;m=b+8|0;h=m;f=c[h>>2]|0;h=c[h+4>>2]|0;l=GF(f|0,h|0,3)|0;l=MF(l|0)|0;j=JF(f|0,h|0,29)|0;k=n;c[k>>2]=MF(j|0)|0;c[k+4>>2]=l;k=f&63;h=HF(f|0,h|0,1,0)|0;f=C;l=m;c[l>>2]=h;c[l+4>>2]=f;if((k+1|0)>>>0>63){j=64-k|0;KF(b+16+k|0,104803,j|0)|0;jb[c[b+112>>2]&255](b+80|0,b+16|0);h=m;g=c[h+4>>2]|0;h=c[h>>2]|0;f=0}else{g=f;j=0;f=k}KF(b+16+f|0,104803+j|0,1-j|0)|0;f=h&63;if(!((h&63|0)==56&0==0)){j=b+112|0;k=b+80|0;l=b+16|0;do{g=HF(h|0,g|0,1,0)|0;h=m;c[h>>2]=g;c[h+4>>2]=C;if((f+1|0)>>>0>63){g=64-f|0;FF(b+16+f|0,0,g|0)|0;jb[c[j>>2]&255](k,l);f=0}else g=0;KF(b+16+f|0,190399+g|0,1-g|0)|0;g=m;h=c[g>>2]|0;g=c[g+4>>2]|0;f=h&63}while(!((h&63|0)==56&0==0))}l=HF(h|0,g|0,8,0)|0;c[m>>2]=l;c[m+4>>2]=C;if((f+8|0)>>>0>63){g=64-f|0;KF(b+16+f|0,n|0,g|0)|0;jb[c[b+112>>2]&255](b+80|0,b+16|0);f=0}else g=0;KF(b+16+f|0,n+g|0,8-g|0)|0;if(!(a[b>>0]|0)){i=o;return}else f=0;do{m=MF(c[b+80+(f<<2)>>2]|0)|0;n=e+(f<<2)|0;a[n>>0]=m;a[n+1>>0]=m>>8;a[n+2>>0]=m>>16;a[n+3>>0]=m>>24;f=f+1|0}while((f|0)<(d[b>>0]|0));i=o;return}function QD(b){b=b|0;var d=0,e=0;b=FC(b,2,8)|0;if(!b){b=0;return b|0}b=b+4|0;d=c[b>>2]|0;e=d;a[e>>0]=0;a[e+1>>0]=0;a[e+2>>0]=0;a[e+3>>0]=0;d=d+4|0;a[d>>0]=0;a[d+1>>0]=0;a[d+2>>0]=0;a[d+3>>0]=0;b=c[b>>2]|0;return b|0}function RD(){var a=0,b=0,d=0;b=i;i=i+16|0;a=b;$a(a|0,0)|0;d=c[a>>2]|0;d=RF(d|0,((d|0)<0)<<31>>31|0,1e6,0)|0;a=c[a+4>>2]|0;a=HF(d|0,C|0,a|0,((a|0)<0)<<31>>31|0)|0;i=b;return a|0}function SD(a){a=a|0;switch(a|0){case 1:{a=73;break}case 2:{a=80;break}case 3:{a=66;break}case 4:{a=83;break}case 5:{a=105;break}case 6:{a=112;break}case 7:{a=98;break}default:a=63}return a|0}function TD(a){a=a|0;var b=0,d=0;b=i;i=i+16|0;d=b;c[d>>2]=c[a+60>>2];a=UD(Ca(6,d|0)|0)|0;i=b;return a|0}function UD(a){a=a|0;if(a>>>0>4294963200){c[(VD()|0)>>2]=0-a;a=-1}return a|0}function VD(){var a=0;if(!0)a=112312;else a=c[(Sa()|0)+64>>2]|0;return a|0}function WD(a){a=a|0;return}function XD(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0;f=i;i=i+32|0;g=f;e=f+20|0;c[g>>2]=c[a+60>>2];c[g+4>>2]=0;c[g+8>>2]=b;c[g+12>>2]=e;c[g+16>>2]=d;if((UD(ab(140,g|0)|0)|0)<0){c[e>>2]=-1;a=-1}else a=c[e>>2]|0;i=f;return a|0}function YD(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;q=i;i=i+48|0;n=q+16|0;m=q;e=q+32|0;o=a+28|0;f=c[o>>2]|0;c[e>>2]=f;p=a+20|0;f=(c[p>>2]|0)-f|0;c[e+4>>2]=f;c[e+8>>2]=b;c[e+12>>2]=d;k=a+60|0;l=a+44|0;b=2;f=f+d|0;while(1){if(!(c[28067]|0)){c[n>>2]=c[k>>2];c[n+4>>2]=e;c[n+8>>2]=b;h=UD(bb(146,n|0)|0)|0}else{Ea(3,a|0);c[m>>2]=c[k>>2];c[m+4>>2]=e;c[m+8>>2]=b;h=UD(bb(146,m|0)|0)|0;wa(0)}if((f|0)==(h|0)){f=6;break}if((h|0)<0){f=8;break}f=f-h|0;g=c[e+4>>2]|0;if(h>>>0>g>>>0){j=c[l>>2]|0;c[o>>2]=j;c[p>>2]=j;j=c[e+12>>2]|0;h=h-g|0;e=e+8|0;b=b+-1|0}else if((b|0)==2){c[o>>2]=(c[o>>2]|0)+h;j=g;b=2}else j=g;c[e>>2]=(c[e>>2]|0)+h;c[e+4>>2]=j-h}if((f|0)==6){n=c[l>>2]|0;c[a+16>>2]=n+(c[a+48>>2]|0);a=n;c[o>>2]=a;c[p>>2]=a}else if((f|0)==8){c[a+16>>2]=0;c[o>>2]=0;c[p>>2]=0;c[a>>2]=c[a>>2]|32;if((b|0)==2)d=0;else d=d-(c[e+4>>2]|0)|0}i=q;return d|0}function ZD(a){a=a|0;if(!(c[a+68>>2]|0))WD(a);return}function _D(b,c){b=b|0;c=c|0;var d=0,e=0;e=a[b>>0]|0;d=a[c>>0]|0;if(e<<24>>24==0?1:e<<24>>24!=d<<24>>24)c=e;else{do{b=b+1|0;c=c+1|0;e=a[b>>0]|0;d=a[c>>0]|0}while(!(e<<24>>24==0?1:e<<24>>24!=d<<24>>24));c=e}return (c&255)-(d&255)|0}function $D(a){a=a|0;return ((a|0)==32|(a+-9|0)>>>0<5)&1|0}function aE(b){b=b|0;var d=0,e=0,f=0;f=b;a:do if(!(f&3))e=4;else{d=b;b=f;while(1){if(!(a[d>>0]|0))break a;d=d+1|0;b=d;if(!(b&3)){b=d;e=4;break}}}while(0);if((e|0)==4){while(1){d=c[b>>2]|0;if(!((d&-2139062144^-2139062144)&d+-16843009))b=b+4|0;else break}if((d&255)<<24>>24)do b=b+1|0;while((a[b>>0]|0)!=0)}return b-f|0}function bE(b,c,d){b=b|0;c=c|0;d=d|0;var e=0,f=0;a:do if(!d)d=0;else{f=d;e=b;while(1){b=a[e>>0]|0;d=a[c>>0]|0;if(b<<24>>24!=d<<24>>24)break;f=f+-1|0;if(!f){d=0;break a}else{e=e+1|0;c=c+1|0}}d=(b&255)-(d&255)|0}while(0);return d|0}function cE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+32|0;f=e;g=e+16|0;c[g>>2]=d;h=(c[g>>2]|0)+(4-1)&~(4-1);d=c[h>>2]|0;c[g>>2]=h+4;c[f>>2]=a;c[f+4>>2]=b|32768;c[f+8>>2]=d;d=UD(Za(5,f|0)|0)|0;i=e;return d|0}function dE(b,e,f,g,h){b=b|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;a:do if(e>>>0>36){c[(VD()|0)>>2]=22;h=0;g=0}else{r=b+4|0;q=b+100|0;do{i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0}while(($D(i)|0)!=0);b:do switch(i|0){case 43:case 45:{j=((i|0)==45)<<31>>31;i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0;p=j;break b}else{i=eE(b)|0;p=j;break b}}default:p=0}while(0);j=(e|0)==0;do if((e|16|0)==16&(i|0)==48){i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0;if((i|32|0)!=120)if(j){e=8;n=46;break}else{n=32;break}e=c[r>>2]|0;if(e>>>0<(c[q>>2]|0)>>>0){c[r>>2]=e+1;i=d[e>>0]|0}else i=eE(b)|0;if((d[104806+i>>0]|0)>15){g=(c[q>>2]|0)==0;if(!g)c[r>>2]=(c[r>>2]|0)+-1;if(!f){hE(b,0);h=0;g=0;break a}if(g){h=0;g=0;break a}c[r>>2]=(c[r>>2]|0)+-1;h=0;g=0;break a}else{e=16;n=46}}else{e=j?10:e;if((d[104806+i>>0]|0)>>>0<e>>>0)n=32;else{if(c[q>>2]|0)c[r>>2]=(c[r>>2]|0)+-1;hE(b,0);c[(VD()|0)>>2]=22;h=0;g=0;break a}}while(0);if((n|0)==32)if((e|0)==10){e=i+-48|0;if(e>>>0<10){i=0;while(1){j=(i*10|0)+e|0;e=c[r>>2]|0;if(e>>>0<(c[q>>2]|0)>>>0){c[r>>2]=e+1;i=d[e>>0]|0}else i=eE(b)|0;e=i+-48|0;if(!(e>>>0<10&j>>>0<429496729)){e=j;break}else i=j}j=0}else{e=0;j=0}f=i+-48|0;if(f>>>0<10){while(1){k=RF(e|0,j|0,10,0)|0;l=C;m=((f|0)<0)<<31>>31;o=~m;if(l>>>0>o>>>0|(l|0)==(o|0)&k>>>0>~f>>>0){k=e;break}e=HF(k|0,l|0,f|0,m|0)|0;j=C;i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0;f=i+-48|0;if(!(f>>>0<10&(j>>>0<429496729|(j|0)==429496729&e>>>0<2576980378))){k=e;break}}if(f>>>0>9){i=k;e=p}else{e=10;n=72}}else{i=e;e=p}}else n=46;c:do if((n|0)==46){if(!(e+-1&e)){n=a[105062+((e*23|0)>>>5&7)>>0]|0;j=a[104806+i>>0]|0;f=j&255;if(f>>>0<e>>>0){i=0;while(1){k=f|i<<n;i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0;j=a[104806+i>>0]|0;f=j&255;if(!(k>>>0<134217728&f>>>0<e>>>0))break;else i=k}f=0}else{f=0;k=0}l=JF(-1,-1,n|0)|0;m=C;if((j&255)>>>0>=e>>>0|(f>>>0>m>>>0|(f|0)==(m|0)&k>>>0>l>>>0)){j=f;n=72;break}else i=f;while(1){k=GF(k|0,i|0,n|0)|0;f=C;k=j&255|k;i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0;j=a[104806+i>>0]|0;if((j&255)>>>0>=e>>>0|(f>>>0>m>>>0|(f|0)==(m|0)&k>>>0>l>>>0)){j=f;n=72;break c}else i=f}}j=a[104806+i>>0]|0;f=j&255;if(f>>>0<e>>>0){i=0;while(1){k=f+(_(i,e)|0)|0;i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0;j=a[104806+i>>0]|0;f=j&255;if(!(k>>>0<119304647&f>>>0<e>>>0))break;else i=k}f=0}else{k=0;f=0}if((j&255)>>>0<e>>>0){n=SF(-1,-1,e|0,0)|0;o=C;m=f;while(1){if(m>>>0>o>>>0|(m|0)==(o|0)&k>>>0>n>>>0){j=m;n=72;break c}f=RF(k|0,m|0,e|0,0)|0;l=C;j=j&255;if(l>>>0>4294967295|(l|0)==-1&f>>>0>~j>>>0){j=m;n=72;break c}k=HF(j|0,0,f|0,l|0)|0;f=C;i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0;j=a[104806+i>>0]|0;if((j&255)>>>0>=e>>>0){j=f;n=72;break}else m=f}}else{j=f;n=72}}while(0);if((n|0)==72)if((d[104806+i>>0]|0)>>>0<e>>>0){do{i=c[r>>2]|0;if(i>>>0<(c[q>>2]|0)>>>0){c[r>>2]=i+1;i=d[i>>0]|0}else i=eE(b)|0}while((d[104806+i>>0]|0)>>>0<e>>>0);c[(VD()|0)>>2]=34;j=h;i=g;e=(g&1|0)==0&0==0?p:0}else{i=k;e=p}if(c[q>>2]|0)c[r>>2]=(c[r>>2]|0)+-1;if(!(j>>>0<h>>>0|(j|0)==(h|0)&i>>>0<g>>>0)){if(!((g&1|0)!=0|0!=0|(e|0)!=0)){c[(VD()|0)>>2]=34;g=HF(g|0,h|0,-1,-1)|0;h=C;break}if(j>>>0>h>>>0|(j|0)==(h|0)&i>>>0>g>>>0){c[(VD()|0)>>2]=34;break}}g=((e|0)<0)<<31>>31;g=EF(i^e|0,j^g|0,e|0,g|0)|0;h=C}while(0);C=h;return g|0}function eE(b){b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0;f=b+104|0;e=c[f>>2]|0;if(!e)j=3;else if((c[b+108>>2]|0)<(e|0))j=3;else j=4;if((j|0)==3){e=fE(b)|0;if((e|0)<0)j=4;else{f=c[f>>2]|0;i=c[b+8>>2]|0;if(!f){g=i;j=9}else{h=c[b+4>>2]|0;f=f-(c[b+108>>2]|0)|0;g=i;if((i-h|0)<(f|0))j=9;else c[b+100>>2]=h+(f+-1)}if((j|0)==9)c[b+100>>2]=i;f=b+4|0;if(!g)f=c[f>>2]|0;else{f=c[f>>2]|0;b=b+108|0;c[b>>2]=g+1-f+(c[b>>2]|0)}f=f+-1|0;if((d[f>>0]|0|0)!=(e|0))a[f>>0]=e}}if((j|0)==4){c[b+100>>2]=0;e=-1}return e|0}function fE(a){a=a|0;var b=0,e=0,f=0;f=i;i=i+16|0;b=f;if(!(c[a+8>>2]|0))if(!(gE(a)|0))e=3;else b=-1;else e=3;if((e|0)==3)if((mb[c[a+32>>2]&7](a,b,1)|0)==1)b=d[b>>0]|0;else b=-1;i=f;return b|0}function gE(b){b=b|0;var d=0,e=0;d=b+74|0;e=a[d>>0]|0;a[d>>0]=e+255|e;d=b+20|0;e=b+44|0;if((c[d>>2]|0)>>>0>(c[e>>2]|0)>>>0)mb[c[b+36>>2]&7](b,0,0)|0;c[b+16>>2]=0;c[b+28>>2]=0;c[d>>2]=0;d=c[b>>2]|0;if(!(d&20)){d=c[e>>2]|0;c[b+8>>2]=d;c[b+4>>2]=d;d=0}else if(!(d&4))d=-1;else{c[b>>2]=d|32;d=-1}return d|0}function hE(a,b){a=a|0;b=b|0;var d=0,e=0,f=0;c[a+104>>2]=b;d=c[a+8>>2]|0;e=c[a+4>>2]|0;f=d-e|0;c[a+108>>2]=f;if((b|0)!=0&(f|0)>(b|0))c[a+100>>2]=e+b;else c[a+100>>2]=d;return}
function Sd(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<1;q=g<<1;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<1;m=f+-1|0;if((f|0)>=2){l=1-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[j>>1]=f;f=i-g|0;if(f>>>0>=512)f=0-f>>31&511;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[i>>1]=f;f=j-g|0;if(f>>>0>=512)f=0-f>>31&511;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function Td(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<1;r=g<<1;o=d>>1;p=o<<2;q=0;while(1){f=a[h+q>>0]<<1;n=f+-1|0;if((f|0)>=2){k=1-f|0;l=0;m=c;while(1){i=m+-2|0;d=e[i>>1]|0;f=e[m+-4>>1]|0;j=e[m>>1]|0;g=e[m+2>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(k|0)?k:(g|0)>(n|0)?n:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[i>>1]=f;f=j-g|0;if(f>>>0>=512)f=0-f>>31&511;b[m>>1]=f}}}l=l+1|0;if((l|0)==4)break;else m=m+(o<<1)|0}}q=q+1|0;if((q|0)==4)break;else c=c+(p<<1)|0}return}function Ud(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=f<<1;o=g<<1;m=d>>1;n=0;while(1){f=a[h+n>>0]<<1;j=f+-1|0;if((f|0)>=2){d=1-f|0;l=c+-2|0;i=e[l>>1]|0;f=e[c+-4>>1]|0;k=e[c>>1]|0;g=e[c+2>>1]|0;q=i-k|0;if((((q|0)>-1?q:0-q|0)|0)<(p|0)){q=f-i|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){q=g-k|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){g=f+4-g+(k-i<<2)>>3;g=(g|0)<(d|0)?d:(g|0)>(j|0)?j:g;f=g+i|0;if(f>>>0>=512)f=0-f>>31&511;b[l>>1]=f;f=k-g|0;if(f>>>0>=512)f=0-f>>31&511;b[c>>1]=f}}}}n=n+1|0;if((n|0)==4)break;else c=c+(m<<1)|0}return}function Vd(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<1;q=g<<1;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<1;m=f+-1|0;if((f|0)>=2){l=1-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[j>>1]=f;f=i-g|0;if(f>>>0>=512)f=0-f>>31&511;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=512)f=0-f>>31&511;b[i>>1]=f;f=j-g|0;if(f>>>0>=512)f=0-f>>31&511;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function Wd(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;n=c>>1;m=d<<1;i=f<<1;j=0-n|0;k=_(n,-2)|0;l=0;while(1){g=a+(j<<1)|0;h=e[g>>1]|0;c=e[a+(k<<1)>>1]|0;d=e[a>>1]|0;f=e[a+(n<<1)>>1]|0;o=h-d|0;if((((o|0)>-1?o:0-o|0)|0)<(m|0)){o=c-h|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){o=f-d|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){b[g>>1]=(h+2+(c<<1)+f|0)>>>2;b[a>>1]=(c+2+d+(f<<1)|0)>>>2}}}l=l+1|0;if((l|0)==8)break;else a=a+2|0}return}function Xd(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<1;i=f<<1;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function Yd(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<1;i=f<<1;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==16)break;else a=a+(l<<1)|0}return}function Zd(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<1;i=f<<1;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==4)break;else a=a+(l<<1)|0}return}function _d(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<1;i=f<<1;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function $d(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+2;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+16|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+18|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+20|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+22|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+24|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+26|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+28|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+30|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function ae(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+2;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function be(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+2;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function ce(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+2;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=1024)h=0-h>>31&1023;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function de(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<2|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+16>>1]|0,i)|0;j=a+16|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+18>>1]|0,i)|0;j=a+18|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+20>>1]|0,i)|0;j=a+20|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+22>>1]|0,i)|0;j=a+22|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+24>>1]|0,i)|0;j=a+24|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+26>>1]|0,i)|0;j=a+26|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+28>>1]|0,i)|0;j=a+28|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+30>>1]|0,i)|0;j=a+30|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function ee(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<2|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function fe(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<2|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function ge(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<2|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=1024)d=0-d>>31&1023;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function he(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;C=d>>1;B=f<<2;v=g<<2;w=0-C|0;x=_(C,-2)|0;y=_(C,-3)|0;z=C<<1;A=0;while(1){d=a[h+A>>0]|0;u=d<<24>>24<<2;if(d<<24>>24>=0){p=d<<24>>24==0;q=u|1;r=0-u|0;s=0;t=c;while(1){n=t+(w<<1)|0;m=e[n>>1]|0;f=t+(x<<1)|0;k=e[f>>1]|0;g=e[t+(y<<1)>>1]|0;o=e[t>>1]|0;i=t+(C<<1)|0;l=e[i>>1]|0;j=e[t+(z<<1)>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(B|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(p|d)d=d?u:q;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(r|0)?r:(d|0)>(u|0)?u:d)+k;d=q}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!p){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(r|0)?r:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=1024)d=0-d>>31&1023;b[n>>1]=d;d=o-f|0;if(d>>>0>=1024)d=0-d>>31&1023;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+2|0}}A=A+1|0;if((A|0)==4)break;else c=c+8|0}return}function ie(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<2;v=g<<2;w=z<<2;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<2;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=1024)d=0-d>>31&1023;b[n>>1]=d;d=o-f|0;if(d>>>0>=1024)d=0-d>>31&1023;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function je(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<2;v=g<<2;w=z<<1;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<2;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=1024)d=0-d>>31&1023;b[n>>1]=d;d=o-f|0;if(d>>>0>=1024)d=0-d>>31&1023;b[t>>1]=d}}}s=s+1|0;if((s|0)==2)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function ke(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;z=c>>1;y=d<<2;q=f<<2;r=_(z,-3)|0;s=_(z,-2)|0;t=0-z|0;u=z<<1;v=(y>>2)+2|0;w=_(z,-4)|0;p=z*3|0;x=0;while(1){c=a+(r<<1)|0;d=e[c>>1]|0;f=a+(s<<1)|0;g=e[f>>1]|0;h=a+(t<<1)|0;i=e[h>>1]|0;j=e[a>>1]|0;k=a+(z<<1)|0;l=e[k>>1]|0;m=a+(u<<1)|0;n=e[m>>1]|0;o=i-j|0;o=(o|0)>-1?o:0-o|0;do if((o|0)<(y|0)){A=g-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=l-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){if((o|0)>=(v|0)){b[h>>1]=(i+2+(g<<1)+l|0)>>>2;b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}A=d-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(w<<1)>>1]|0;b[h>>1]=(d+4+l+(i+g+j<<1)|0)>>>3;b[f>>1]=(d+2+g+i+j|0)>>>2;b[c>>1]=((d*3|0)+4+g+i+j+(A<<1)|0)>>>3}else b[h>>1]=(i+2+(g<<1)+l|0)>>>2;A=n-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(p<<1)>>1]|0;b[a>>1]=(g+4+n+(j+i+l<<1)|0)>>>3;b[k>>1]=(i+2+j+l+n|0)>>>2;b[m>>1]=(i+4+j+l+(n*3|0)+(A<<1)|0)>>>3;break}else{b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}}}}while(0);x=x+1|0;if((x|0)==16)break;else a=a+2|0}return}function le(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<2;p=f<<2;q=(s>>2)+2|0;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==16)break;else a=a+(t<<1)|0}return}function me(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<2;p=f<<2;q=(s>>2)+2|0;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==8)break;else a=a+(t<<1)|0}return}function ne(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<2;r=g<<2;n=d>>1;o=0-n|0;p=_(n,-2)|0;q=0;while(1){f=a[h+q>>0]<<2;m=f+-3|0;if((m|0)>=1){l=3-f|0;j=c+(o<<1)|0;d=e[j>>1]|0;f=e[c+(p<<1)>>1]|0;i=e[c>>1]|0;g=e[c+(n<<1)>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(s|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[j>>1]=f;f=i-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[c>>1]=f}}}k=c+2|0;i=k+(o<<1)|0;d=e[i>>1]|0;f=e[k+(p<<1)>>1]|0;j=e[k>>1]|0;g=e[k+(n<<1)>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[i>>1]=f;f=j-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[k>>1]=f}}}}q=q+1|0;if((q|0)==4)break;else c=c+4|0}return}function oe(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<2;q=g<<2;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<2;m=f+-3|0;if((m|0)>=1){l=3-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[j>>1]=f;f=i-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[i>>1]=f;f=j-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function pe(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<2;r=g<<2;o=d>>1;p=o<<2;q=0;while(1){f=a[h+q>>0]<<2;n=f+-3|0;if((n|0)>=1){k=3-f|0;l=0;m=c;while(1){i=m+-2|0;d=e[i>>1]|0;f=e[m+-4>>1]|0;j=e[m>>1]|0;g=e[m+2>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(k|0)?k:(g|0)>(n|0)?n:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[i>>1]=f;f=j-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[m>>1]=f}}}l=l+1|0;if((l|0)==4)break;else m=m+(o<<1)|0}}q=q+1|0;if((q|0)==4)break;else c=c+(p<<1)|0}return}function qe(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=f<<2;o=g<<2;m=d>>1;n=0;while(1){f=a[h+n>>0]<<2;j=f+-3|0;if((j|0)>=1){d=3-f|0;l=c+-2|0;i=e[l>>1]|0;f=e[c+-4>>1]|0;k=e[c>>1]|0;g=e[c+2>>1]|0;q=i-k|0;if((((q|0)>-1?q:0-q|0)|0)<(p|0)){q=f-i|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){q=g-k|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){g=f+4-g+(k-i<<2)>>3;g=(g|0)<(d|0)?d:(g|0)>(j|0)?j:g;f=g+i|0;if(f>>>0>=1024)f=0-f>>31&1023;b[l>>1]=f;f=k-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[c>>1]=f}}}}n=n+1|0;if((n|0)==4)break;else c=c+(m<<1)|0}return}function re(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<2;q=g<<2;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<2;m=f+-3|0;if((m|0)>=1){l=3-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[j>>1]=f;f=i-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=1024)f=0-f>>31&1023;b[i>>1]=f;f=j-g|0;if(f>>>0>=1024)f=0-f>>31&1023;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function se(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;n=c>>1;m=d<<2;i=f<<2;j=0-n|0;k=_(n,-2)|0;l=0;while(1){g=a+(j<<1)|0;h=e[g>>1]|0;c=e[a+(k<<1)>>1]|0;d=e[a>>1]|0;f=e[a+(n<<1)>>1]|0;o=h-d|0;if((((o|0)>-1?o:0-o|0)|0)<(m|0)){o=c-h|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){o=f-d|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){b[g>>1]=(h+2+(c<<1)+f|0)>>>2;b[a>>1]=(c+2+d+(f<<1)|0)>>>2}}}l=l+1|0;if((l|0)==8)break;else a=a+2|0}return}function te(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<2;i=f<<2;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function ue(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<2;i=f<<2;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==16)break;else a=a+(l<<1)|0}return}function ve(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<2;i=f<<2;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==4)break;else a=a+(l<<1)|0}return}function we(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<2;i=f<<2;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function xe(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+4;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+16|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+18|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+20|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+22|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+24|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+26|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+28|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+30|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function ye(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+4;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function ze(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+4;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Ae(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+4;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=4096)h=0-h>>31&4095;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Be(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<4|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+16>>1]|0,i)|0;j=a+16|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+18>>1]|0,i)|0;j=a+18|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+20>>1]|0,i)|0;j=a+20|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+22>>1]|0,i)|0;j=a+22|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+24>>1]|0,i)|0;j=a+24|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+26>>1]|0,i)|0;j=a+26|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+28>>1]|0,i)|0;j=a+28|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+30>>1]|0,i)|0;j=a+30|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Ce(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<4|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function De(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<4|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Ee(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<4|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=4096)d=0-d>>31&4095;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function Fe(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;C=d>>1;B=f<<4;v=g<<4;w=0-C|0;x=_(C,-2)|0;y=_(C,-3)|0;z=C<<1;A=0;while(1){d=a[h+A>>0]|0;u=d<<24>>24<<4;if(d<<24>>24>=0){p=d<<24>>24==0;q=u|1;r=0-u|0;s=0;t=c;while(1){n=t+(w<<1)|0;m=e[n>>1]|0;f=t+(x<<1)|0;k=e[f>>1]|0;g=e[t+(y<<1)>>1]|0;o=e[t>>1]|0;i=t+(C<<1)|0;l=e[i>>1]|0;j=e[t+(z<<1)>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(B|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(p|d)d=d?u:q;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(r|0)?r:(d|0)>(u|0)?u:d)+k;d=q}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!p){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(r|0)?r:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=4096)d=0-d>>31&4095;b[n>>1]=d;d=o-f|0;if(d>>>0>=4096)d=0-d>>31&4095;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+2|0}}A=A+1|0;if((A|0)==4)break;else c=c+8|0}return}function Ge(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<4;v=g<<4;w=z<<2;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<4;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=4096)d=0-d>>31&4095;b[n>>1]=d;d=o-f|0;if(d>>>0>=4096)d=0-d>>31&4095;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function He(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<4;v=g<<4;w=z<<1;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<4;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=4096)d=0-d>>31&4095;b[n>>1]=d;d=o-f|0;if(d>>>0>=4096)d=0-d>>31&4095;b[t>>1]=d}}}s=s+1|0;if((s|0)==2)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function Ie(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;z=c>>1;y=d<<4;q=f<<4;r=_(z,-3)|0;s=_(z,-2)|0;t=0-z|0;u=z<<1;v=y>>2|2;w=_(z,-4)|0;p=z*3|0;x=0;while(1){c=a+(r<<1)|0;d=e[c>>1]|0;f=a+(s<<1)|0;g=e[f>>1]|0;h=a+(t<<1)|0;i=e[h>>1]|0;j=e[a>>1]|0;k=a+(z<<1)|0;l=e[k>>1]|0;m=a+(u<<1)|0;n=e[m>>1]|0;o=i-j|0;o=(o|0)>-1?o:0-o|0;do if((o|0)<(y|0)){A=g-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=l-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){if((o|0)>=(v|0)){b[h>>1]=(i+2+(g<<1)+l|0)>>>2;b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}A=d-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(w<<1)>>1]|0;b[h>>1]=(d+4+l+(i+g+j<<1)|0)>>>3;b[f>>1]=(d+2+g+i+j|0)>>>2;b[c>>1]=((d*3|0)+4+g+i+j+(A<<1)|0)>>>3}else b[h>>1]=(i+2+(g<<1)+l|0)>>>2;A=n-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(p<<1)>>1]|0;b[a>>1]=(g+4+n+(j+i+l<<1)|0)>>>3;b[k>>1]=(i+2+j+l+n|0)>>>2;b[m>>1]=(i+4+j+l+(n*3|0)+(A<<1)|0)>>>3;break}else{b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}}}}while(0);x=x+1|0;if((x|0)==16)break;else a=a+2|0}return}function Je(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<4;p=f<<4;q=s>>2|2;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==16)break;else a=a+(t<<1)|0}return}function Ke(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<4;p=f<<4;q=s>>2|2;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==8)break;else a=a+(t<<1)|0}return}function Le(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<4;r=g<<4;n=d>>1;o=0-n|0;p=_(n,-2)|0;q=0;while(1){f=a[h+q>>0]<<4;m=f+-15|0;if((m|0)>=1){l=15-f|0;j=c+(o<<1)|0;d=e[j>>1]|0;f=e[c+(p<<1)>>1]|0;i=e[c>>1]|0;g=e[c+(n<<1)>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(s|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[j>>1]=f;f=i-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[c>>1]=f}}}k=c+2|0;i=k+(o<<1)|0;d=e[i>>1]|0;f=e[k+(p<<1)>>1]|0;j=e[k>>1]|0;g=e[k+(n<<1)>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[i>>1]=f;f=j-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[k>>1]=f}}}}q=q+1|0;if((q|0)==4)break;else c=c+4|0}return}function Me(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<4;q=g<<4;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<4;m=f+-15|0;if((m|0)>=1){l=15-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[j>>1]=f;f=i-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[i>>1]=f;f=j-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function Ne(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<4;r=g<<4;o=d>>1;p=o<<2;q=0;while(1){f=a[h+q>>0]<<4;n=f+-15|0;if((n|0)>=1){k=15-f|0;l=0;m=c;while(1){i=m+-2|0;d=e[i>>1]|0;f=e[m+-4>>1]|0;j=e[m>>1]|0;g=e[m+2>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(k|0)?k:(g|0)>(n|0)?n:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[i>>1]=f;f=j-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[m>>1]=f}}}l=l+1|0;if((l|0)==4)break;else m=m+(o<<1)|0}}q=q+1|0;if((q|0)==4)break;else c=c+(p<<1)|0}return}function Oe(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=f<<4;o=g<<4;m=d>>1;n=0;while(1){f=a[h+n>>0]<<4;j=f+-15|0;if((j|0)>=1){d=15-f|0;l=c+-2|0;i=e[l>>1]|0;f=e[c+-4>>1]|0;k=e[c>>1]|0;g=e[c+2>>1]|0;q=i-k|0;if((((q|0)>-1?q:0-q|0)|0)<(p|0)){q=f-i|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){q=g-k|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){g=f+4-g+(k-i<<2)>>3;g=(g|0)<(d|0)?d:(g|0)>(j|0)?j:g;f=g+i|0;if(f>>>0>=4096)f=0-f>>31&4095;b[l>>1]=f;f=k-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[c>>1]=f}}}}n=n+1|0;if((n|0)==4)break;else c=c+(m<<1)|0}return}function Pe(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<4;q=g<<4;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<4;m=f+-15|0;if((m|0)>=1){l=15-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[j>>1]=f;f=i-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=4096)f=0-f>>31&4095;b[i>>1]=f;f=j-g|0;if(f>>>0>=4096)f=0-f>>31&4095;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function Qe(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;n=c>>1;m=d<<4;i=f<<4;j=0-n|0;k=_(n,-2)|0;l=0;while(1){g=a+(j<<1)|0;h=e[g>>1]|0;c=e[a+(k<<1)>>1]|0;d=e[a>>1]|0;f=e[a+(n<<1)>>1]|0;o=h-d|0;if((((o|0)>-1?o:0-o|0)|0)<(m|0)){o=c-h|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){o=f-d|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){b[g>>1]=(h+2+(c<<1)+f|0)>>>2;b[a>>1]=(c+2+d+(f<<1)|0)>>>2}}}l=l+1|0;if((l|0)==8)break;else a=a+2|0}return}function Re(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<4;i=f<<4;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function Se(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<4;i=f<<4;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==16)break;else a=a+(l<<1)|0}return}function Te(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<4;i=f<<4;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==4)break;else a=a+(l<<1)|0}return}function Ue(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<4;i=f<<4;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function Ve(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+6;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+16|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+18|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+20|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+22|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+24|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+26|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+28|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+30|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function We(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+6;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+8|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+10|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+12|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+14|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Xe(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+6;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+4|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;i=a+6|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Ye(a,c,d,f,g,h){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;k=c>>1;c=h<<f+6;if(f)c=c+(1<<f+-1)|0;if((d|0)>0)j=0;else return;while(1){h=(_(e[a>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[a>>1]=h;i=a+2|0;h=(_(e[i>>1]|0,g)|0)+c>>f;if(h>>>0>=16384)h=0-h>>31&16383;b[i>>1]=h;j=j+1|0;if((j|0)==(d|0))break;else a=a+(k<<1)|0}return}function Ze(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<6|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+16>>1]|0,i)|0;j=a+16|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+18>>1]|0,i)|0;j=a+18|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+20>>1]|0,i)|0;j=a+20|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+22>>1]|0,i)|0;j=a+22|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+24>>1]|0,i)|0;j=a+24|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+26>>1]|0,i)|0;j=a+26|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+28>>1]|0,i)|0;j=a+28|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+30>>1]|0,i)|0;j=a+30|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function _e(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<6|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+8>>1]|0,i)|0;j=a+8|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+10>>1]|0,i)|0;j=a+10|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+12>>1]|0,i)|0;j=a+12|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+14>>1]|0,i)|0;j=a+14|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function $e(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<6|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+4>>1]|0,i)|0;j=a+4|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;d=_(e[c+6>>1]|0,i)|0;j=a+6|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function af(a,c,d,f,g,h,i,j){a=a|0;c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;m=d>>1;l=(j<<6|1)<<g;if((f|0)<=0)return;k=g+1|0;g=0;while(1){d=_(e[c>>1]|0,i)|0;d=d+l+(_(e[a>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[a>>1]=d;d=_(e[c+2>>1]|0,i)|0;j=a+2|0;d=d+l+(_(e[j>>1]|0,h)|0)>>k;if(d>>>0>=16384)d=0-d>>31&16383;b[j>>1]=d;g=g+1|0;if((g|0)==(f|0))break;else{a=a+(m<<1)|0;c=c+(m<<1)|0}}return}function bf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;C=d>>1;B=f<<6;v=g<<6;w=0-C|0;x=_(C,-2)|0;y=_(C,-3)|0;z=C<<1;A=0;while(1){d=a[h+A>>0]|0;u=d<<24>>24<<6;if(d<<24>>24>=0){p=d<<24>>24==0;q=u|1;r=0-u|0;s=0;t=c;while(1){n=t+(w<<1)|0;m=e[n>>1]|0;f=t+(x<<1)|0;k=e[f>>1]|0;g=e[t+(y<<1)>>1]|0;o=e[t>>1]|0;i=t+(C<<1)|0;l=e[i>>1]|0;j=e[t+(z<<1)>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(B|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(p|d)d=d?u:q;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(r|0)?r:(d|0)>(u|0)?u:d)+k;d=q}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!p){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(r|0)?r:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=16384)d=0-d>>31&16383;b[n>>1]=d;d=o-f|0;if(d>>>0>=16384)d=0-d>>31&16383;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+2|0}}A=A+1|0;if((A|0)==4)break;else c=c+8|0}return}function cf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<6;v=g<<6;w=z<<2;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<6;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=16384)d=0-d>>31&16383;b[n>>1]=d;d=o-f|0;if(d>>>0>=16384)d=0-d>>31&16383;b[t>>1]=d}}}s=s+1|0;if((s|0)==4)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function df(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;z=d>>1;y=f<<6;v=g<<6;w=z<<1;x=0;while(1){d=a[h+x>>0]|0;u=d<<24>>24<<6;if(d<<24>>24>=0){r=d<<24>>24==0;p=u|1;q=0-u|0;s=0;t=c;while(1){n=t+-2|0;m=e[n>>1]|0;f=t+-4|0;k=e[f>>1]|0;g=e[t+-6>>1]|0;o=e[t>>1]|0;i=t+2|0;l=e[i>>1]|0;j=e[t+4>>1]|0;d=m-o|0;if((((d|0)>-1?d:0-d|0)|0)<(y|0)){d=k-m|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=l-o|0;if((((d|0)>-1?d:0-d|0)|0)<(v|0)){d=g-m|0;d=(((d|0)>-1?d:0-d|0)|0)>=(v|0);if(r|d)d=d?u:p;else{d=((((m+1+o|0)>>>1)+g|0)>>>1)-k|0;b[f>>1]=((d|0)<(q|0)?q:(d|0)>(u|0)?u:d)+k;d=p}g=j-o|0;if((((g|0)>-1?g:0-g|0)|0)<(v|0)){if(!r){j=((((m+1+o|0)>>>1)+j|0)>>>1)-l|0;b[i>>1]=((j|0)<(q|0)?q:(j|0)>(u|0)?u:j)+l}d=d+1|0}f=k+4-l+(o-m<<2)>>3;l=0-d|0;f=(f|0)<(l|0)?l:(f|0)>(d|0)?d:f;d=f+m|0;if(d>>>0>=16384)d=0-d>>31&16383;b[n>>1]=d;d=o-f|0;if(d>>>0>=16384)d=0-d>>31&16383;b[t>>1]=d}}}s=s+1|0;if((s|0)==2)break;else t=t+(z<<1)|0}}x=x+1|0;if((x|0)==4)break;else c=c+(w<<1)|0}return}function ef(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;z=c>>1;y=d<<6;q=f<<6;r=_(z,-3)|0;s=_(z,-2)|0;t=0-z|0;u=z<<1;v=y>>2|2;w=_(z,-4)|0;p=z*3|0;x=0;while(1){c=a+(r<<1)|0;d=e[c>>1]|0;f=a+(s<<1)|0;g=e[f>>1]|0;h=a+(t<<1)|0;i=e[h>>1]|0;j=e[a>>1]|0;k=a+(z<<1)|0;l=e[k>>1]|0;m=a+(u<<1)|0;n=e[m>>1]|0;o=i-j|0;o=(o|0)>-1?o:0-o|0;do if((o|0)<(y|0)){A=g-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=l-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){if((o|0)>=(v|0)){b[h>>1]=(i+2+(g<<1)+l|0)>>>2;b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}A=d-i|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(w<<1)>>1]|0;b[h>>1]=(d+4+l+(i+g+j<<1)|0)>>>3;b[f>>1]=(d+2+g+i+j|0)>>>2;b[c>>1]=((d*3|0)+4+g+i+j+(A<<1)|0)>>>3}else b[h>>1]=(i+2+(g<<1)+l|0)>>>2;A=n-j|0;if((((A|0)>-1?A:0-A|0)|0)<(q|0)){A=e[a+(p<<1)>>1]|0;b[a>>1]=(g+4+n+(j+i+l<<1)|0)>>>3;b[k>>1]=(i+2+j+l+n|0)>>>2;b[m>>1]=(i+4+j+l+(n*3|0)+(A<<1)|0)>>>3;break}else{b[a>>1]=(g+2+j+(l<<1)|0)>>>2;break}}}}while(0);x=x+1|0;if((x|0)==16)break;else a=a+2|0}return}function ff(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<6;p=f<<6;q=s>>2|2;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==16)break;else a=a+(t<<1)|0}return}function gf(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c>>1;s=d<<6;p=f<<6;q=s>>2|2;r=0;while(1){m=a+-6|0;n=e[m>>1]|0;o=a+-4|0;c=e[o>>1]|0;d=a+-2|0;f=e[d>>1]|0;g=e[a>>1]|0;h=a+2|0;i=e[h>>1]|0;j=a+4|0;k=e[j>>1]|0;l=f-g|0;l=(l|0)>-1?l:0-l|0;do if((l|0)<(s|0)){u=c-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=i-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){if((l|0)>=(q|0)){b[d>>1]=(f+2+(c<<1)+i|0)>>>2;b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}u=n-f|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+-8>>1]|0;b[d>>1]=(n+4+i+(f+c+g<<1)|0)>>>3;b[o>>1]=(n+2+c+f+g|0)>>>2;b[m>>1]=((n*3|0)+4+c+f+g+(u<<1)|0)>>>3}else b[d>>1]=(f+2+(c<<1)+i|0)>>>2;u=k-g|0;if((((u|0)>-1?u:0-u|0)|0)<(p|0)){u=e[a+6>>1]|0;b[a>>1]=(c+4+k+(g+f+i<<1)|0)>>>3;b[h>>1]=(f+2+g+i+k|0)>>>2;b[j>>1]=(f+4+g+i+(k*3|0)+(u<<1)|0)>>>3;break}else{b[a>>1]=(c+2+g+(i<<1)|0)>>>2;break}}}}while(0);r=r+1|0;if((r|0)==8)break;else a=a+(t<<1)|0}return}function hf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<6;r=g<<6;n=d>>1;o=0-n|0;p=_(n,-2)|0;q=0;while(1){f=a[h+q>>0]<<6;m=f+-63|0;if((m|0)>=1){l=63-f|0;j=c+(o<<1)|0;d=e[j>>1]|0;f=e[c+(p<<1)>>1]|0;i=e[c>>1]|0;g=e[c+(n<<1)>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(s|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[j>>1]=f;f=i-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[c>>1]=f}}}k=c+2|0;i=k+(o<<1)|0;d=e[i>>1]|0;f=e[k+(p<<1)>>1]|0;j=e[k>>1]|0;g=e[k+(n<<1)>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[i>>1]=f;f=j-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[k>>1]=f}}}}q=q+1|0;if((q|0)==4)break;else c=c+4|0}return}function jf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<6;q=g<<6;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<6;m=f+-63|0;if((m|0)>=1){l=63-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[j>>1]=f;f=i-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[i>>1]=f;f=j-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function kf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=f<<6;r=g<<6;o=d>>1;p=o<<2;q=0;while(1){f=a[h+q>>0]<<6;n=f+-63|0;if((n|0)>=1){k=63-f|0;l=0;m=c;while(1){i=m+-2|0;d=e[i>>1]|0;f=e[m+-4>>1]|0;j=e[m>>1]|0;g=e[m+2>>1]|0;t=d-j|0;if((((t|0)>-1?t:0-t|0)|0)<(s|0)){t=f-d|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){t=g-j|0;if((((t|0)>-1?t:0-t|0)|0)<(r|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(k|0)?k:(g|0)>(n|0)?n:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[i>>1]=f;f=j-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[m>>1]=f}}}l=l+1|0;if((l|0)==4)break;else m=m+(o<<1)|0}}q=q+1|0;if((q|0)==4)break;else c=c+(p<<1)|0}return}function lf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;p=f<<6;o=g<<6;m=d>>1;n=0;while(1){f=a[h+n>>0]<<6;j=f+-63|0;if((j|0)>=1){d=63-f|0;l=c+-2|0;i=e[l>>1]|0;f=e[c+-4>>1]|0;k=e[c>>1]|0;g=e[c+2>>1]|0;q=i-k|0;if((((q|0)>-1?q:0-q|0)|0)<(p|0)){q=f-i|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){q=g-k|0;if((((q|0)>-1?q:0-q|0)|0)<(o|0)){g=f+4-g+(k-i<<2)>>3;g=(g|0)<(d|0)?d:(g|0)>(j|0)?j:g;f=g+i|0;if(f>>>0>=16384)f=0-f>>31&16383;b[l>>1]=f;f=k-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[c>>1]=f}}}}n=n+1|0;if((n|0)==4)break;else c=c+(m<<1)|0}return}function mf(c,d,f,g,h){c=c|0;d=d|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;r=f<<6;q=g<<6;n=d>>1;o=n<<1;p=0;while(1){f=a[h+p>>0]<<6;m=f+-63|0;if((m|0)>=1){l=63-f|0;j=c+-2|0;d=e[j>>1]|0;f=e[c+-4>>1]|0;i=e[c>>1]|0;g=e[c+2>>1]|0;k=d-i|0;if((((k|0)>-1?k:0-k|0)|0)<(r|0)){k=f-d|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){k=g-i|0;if((((k|0)>-1?k:0-k|0)|0)<(q|0)){g=f+4-g+(i-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[j>>1]=f;f=i-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[c>>1]=f}}}k=c+(n<<1)|0;i=k+-2|0;d=e[i>>1]|0;f=e[k+-4>>1]|0;j=e[k>>1]|0;g=e[k+2>>1]|0;s=d-j|0;if((((s|0)>-1?s:0-s|0)|0)<(r|0)){s=f-d|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){s=g-j|0;if((((s|0)>-1?s:0-s|0)|0)<(q|0)){g=f+4-g+(j-d<<2)>>3;g=(g|0)<(l|0)?l:(g|0)>(m|0)?m:g;f=g+d|0;if(f>>>0>=16384)f=0-f>>31&16383;b[i>>1]=f;f=j-g|0;if(f>>>0>=16384)f=0-f>>31&16383;b[k>>1]=f}}}}p=p+1|0;if((p|0)==4)break;else c=c+(o<<1)|0}return}function nf(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;n=c>>1;m=d<<6;i=f<<6;j=0-n|0;k=_(n,-2)|0;l=0;while(1){g=a+(j<<1)|0;h=e[g>>1]|0;c=e[a+(k<<1)>>1]|0;d=e[a>>1]|0;f=e[a+(n<<1)>>1]|0;o=h-d|0;if((((o|0)>-1?o:0-o|0)|0)<(m|0)){o=c-h|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){o=f-d|0;if((((o|0)>-1?o:0-o|0)|0)<(i|0)){b[g>>1]=(h+2+(c<<1)+f|0)>>>2;b[a>>1]=(c+2+d+(f<<1)|0)>>>2}}}l=l+1|0;if((l|0)==8)break;else a=a+2|0}return}function of(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<6;i=f<<6;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function pf(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<6;i=f<<6;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==16)break;else a=a+(l<<1)|0}return}function qf(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<6;i=f<<6;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==4)break;else a=a+(l<<1)|0}return}function rf(a,c,d,f){a=a|0;c=c|0;d=d|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=c>>1;k=d<<6;i=f<<6;j=0;while(1){f=a+-2|0;g=e[f>>1]|0;h=e[a+-4>>1]|0;c=e[a>>1]|0;d=e[a+2>>1]|0;m=g-c|0;if((((m|0)>-1?m:0-m|0)|0)<(k|0)){m=h-g|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){m=d-c|0;if((((m|0)>-1?m:0-m|0)|0)<(i|0)){b[f>>1]=(g+2+(h<<1)+d|0)>>>2;b[a>>1]=(h+2+c+(d<<1)|0)>>>2}}}j=j+1|0;if((j|0)==8)break;else a=a+(l<<1)|0}return}function sf(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;h=h<<f;if(f)h=h+(1<<f+-1)|0;if((e|0)>0)i=0;else return;while(1){j=(_(d[b>>0]|0,g)|0)+h>>f;a[b>>0]=j>>>0>255?0-j>>31:j;j=b+1|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+2|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+3|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+4|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+5|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+6|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+7|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+8|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+9|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+10|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+11|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+12|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+13|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+14|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+15|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;i=i+1|0;if((i|0)==(e|0))break;else b=b+c|0}return}function tf(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;h=h<<f;if(f)h=h+(1<<f+-1)|0;if((e|0)>0)i=0;else return;while(1){j=(_(d[b>>0]|0,g)|0)+h>>f;a[b>>0]=j>>>0>255?0-j>>31:j;j=b+1|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+2|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+3|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+4|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+5|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+6|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+7|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;i=i+1|0;if((i|0)==(e|0))break;else b=b+c|0}return}function uf(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;h=h<<f;if(f)h=h+(1<<f+-1)|0;if((e|0)>0)i=0;else return;while(1){j=(_(d[b>>0]|0,g)|0)+h>>f;a[b>>0]=j>>>0>255?0-j>>31:j;j=b+1|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+2|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;j=b+3|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;i=i+1|0;if((i|0)==(e|0))break;else b=b+c|0}return}function vf(b,c,e,f,g,h){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0;h=h<<f;if(f)h=h+(1<<f+-1)|0;if((e|0)>0)i=0;else return;while(1){j=(_(d[b>>0]|0,g)|0)+h>>f;a[b>>0]=j>>>0>255?0-j>>31:j;j=b+1|0;k=(_(d[j>>0]|0,g)|0)+h>>f;a[j>>0]=k>>>0>255?0-k>>31:k;i=i+1|0;if((i|0)==(e|0))break;else b=b+c|0}return}function wf(b,c,e,f,g,h,i,j){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;l=(j+1|1)<<g;if((f|0)<=0)return;k=g+1|0;j=c;g=0;while(1){m=_(d[j>>0]|0,i)|0;m=m+l+(_(d[b>>0]|0,h)|0)>>k;a[b>>0]=m>>>0>255?0-m>>31:m;m=_(d[j+1>>0]|0,i)|0;c=b+1|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+2>>0]|0,i)|0;m=b+2|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+3>>0]|0,i)|0;c=b+3|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+4>>0]|0,i)|0;m=b+4|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+5>>0]|0,i)|0;c=b+5|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+6>>0]|0,i)|0;m=b+6|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+7>>0]|0,i)|0;c=b+7|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+8>>0]|0,i)|0;m=b+8|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+9>>0]|0,i)|0;c=b+9|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+10>>0]|0,i)|0;m=b+10|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+11>>0]|0,i)|0;c=b+11|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+12>>0]|0,i)|0;m=b+12|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+13>>0]|0,i)|0;c=b+13|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+14>>0]|0,i)|0;m=b+14|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+15>>0]|0,i)|0;c=b+15|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;j=j+e|0}}return}function xf(b,c,e,f,g,h,i,j){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;l=(j+1|1)<<g;if((f|0)<=0)return;k=g+1|0;j=c;g=0;while(1){m=_(d[j>>0]|0,i)|0;m=m+l+(_(d[b>>0]|0,h)|0)>>k;a[b>>0]=m>>>0>255?0-m>>31:m;m=_(d[j+1>>0]|0,i)|0;c=b+1|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+2>>0]|0,i)|0;m=b+2|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+3>>0]|0,i)|0;c=b+3|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+4>>0]|0,i)|0;m=b+4|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+5>>0]|0,i)|0;c=b+5|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+6>>0]|0,i)|0;m=b+6|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+7>>0]|0,i)|0;c=b+7|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;j=j+e|0}}return}function yf(b,c,e,f,g,h,i,j){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;l=(j+1|1)<<g;if((f|0)<=0)return;k=g+1|0;j=c;g=0;while(1){m=_(d[j>>0]|0,i)|0;m=m+l+(_(d[b>>0]|0,h)|0)>>k;a[b>>0]=m>>>0>255?0-m>>31:m;m=_(d[j+1>>0]|0,i)|0;c=b+1|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;c=_(d[j+2>>0]|0,i)|0;m=b+2|0;c=c+l+(_(d[m>>0]|0,h)|0)>>k;a[m>>0]=c>>>0>255?0-c>>31:c;m=_(d[j+3>>0]|0,i)|0;c=b+3|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;j=j+e|0}}return}function zf(b,c,e,f,g,h,i,j){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0,m=0;l=(j+1|1)<<g;if((f|0)<=0)return;k=g+1|0;j=c;g=0;while(1){m=_(d[j>>0]|0,i)|0;m=m+l+(_(d[b>>0]|0,h)|0)>>k;a[b>>0]=m>>>0>255?0-m>>31:m;m=_(d[j+1>>0]|0,i)|0;c=b+1|0;m=m+l+(_(d[c>>0]|0,h)|0)>>k;a[c>>0]=m>>>0>255?0-m>>31:m;g=g+1|0;if((g|0)==(f|0))break;else{b=b+e|0;j=j+e|0}}return}function Af(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0;x=0-c|0;y=_(c,-2)|0;z=_(c,-3)|0;A=c<<1;B=0;while(1){h=a[g+B>>0]|0;w=h<<24>>24;if(h<<24>>24>=0){s=h<<24>>24==0;t=w+1|0;m=0-w|0;u=0;v=b;while(1){n=v+x|0;o=d[n>>0]|0;i=v+y|0;p=d[i>>0]|0;j=d[v+z>>0]|0;q=d[v>>0]|0;k=v+c|0;r=d[k>>0]|0;l=d[v+A>>0]|0;h=o-q|0;if((((h|0)>-1?h:0-h|0)|0)<(e|0)){h=p-o|0;if((((h|0)>-1?h:0-h|0)|0)<(f|0)){h=r-q|0;if((((h|0)>-1?h:0-h|0)|0)<(f|0)){h=j-o|0;h=(((h|0)>-1?h:0-h|0)|0)>=(f|0);if(s|h)h=h?w:t;else{h=((((o+1+q|0)>>>1)+j|0)>>>1)-p|0;a[i>>0]=((h|0)<(m|0)?m:(h|0)>(w|0)?w:h)+p;h=t}j=l-q|0;if((((j|0)>-1?j:0-j|0)|0)<(f|0)){if(!s){l=((((o+1+q|0)>>>1)+l|0)>>>1)-r|0;a[k>>0]=((l|0)<(m|0)?m:(l|0)>(w|0)?w:l)+r}h=h+1|0}r=p+4-r+(q-o<<2)>>3;p=0-h|0;r=(r|0)<(p|0)?p:(r|0)>(h|0)?h:r;p=r+o|0;a[n>>0]=p>>>0>255?0-p>>31:p;r=q-r|0;a[v>>0]=r>>>0>255?0-r>>31:r}}}u=u+1|0;if((u|0)==4)break;else v=v+1|0}}B=B+1|0;if((B|0)==4)break;else b=b+4|0}return}function Bf(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;x=c<<2;y=0;while(1){h=a[g+y>>0]|0;w=h<<24>>24;if(h<<24>>24>=0){q=h<<24>>24==0;r=w+1|0;s=0-w|0;u=0;v=b;while(1){t=v+-1|0;m=d[t>>0]|0;i=v+-2|0;n=d[i>>0]|0;j=d[v+-3>>0]|0;o=d[v>>0]|0;k=v+1|0;p=d[k>>0]|0;l=d[v+2>>0]|0;h=m-o|0;if((((h|0)>-1?h:0-h|0)|0)<(e|0)){h=n-m|0;if((((h|0)>-1?h:0-h|0)|0)<(f|0)){h=p-o|0;if((((h|0)>-1?h:0-h|0)|0)<(f|0)){h=j-m|0;h=(((h|0)>-1?h:0-h|0)|0)>=(f|0);if(q|h)h=h?w:r;else{h=((((m+1+o|0)>>>1)+j|0)>>>1)-n|0;a[i>>0]=((h|0)<(s|0)?s:(h|0)>(w|0)?w:h)+n;h=r}j=l-o|0;if((((j|0)>-1?j:0-j|0)|0)<(f|0)){if(!q){l=((((m+1+o|0)>>>1)+l|0)>>>1)-p|0;a[k>>0]=((l|0)<(s|0)?s:(l|0)>(w|0)?w:l)+p}h=h+1|0}p=n+4-p+(o-m<<2)>>3;n=0-h|0;p=(p|0)<(n|0)?n:(p|0)>(h|0)?h:p;n=p+m|0;a[t>>0]=n>>>0>255?0-n>>31:n;t=o-p|0;a[v>>0]=t>>>0>255?0-t>>31:t}}}u=u+1|0;if((u|0)==4)break;else v=v+c|0}}y=y+1|0;if((y|0)==4)break;else b=b+x|0}return}function Cf(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;x=c<<1;y=0;while(1){h=a[g+y>>0]|0;w=h<<24>>24;if(h<<24>>24>=0){q=h<<24>>24==0;r=w+1|0;s=0-w|0;u=0;v=b;while(1){t=v+-1|0;m=d[t>>0]|0;i=v+-2|0;n=d[i>>0]|0;j=d[v+-3>>0]|0;o=d[v>>0]|0;k=v+1|0;p=d[k>>0]|0;l=d[v+2>>0]|0;h=m-o|0;if((((h|0)>-1?h:0-h|0)|0)<(e|0)){h=n-m|0;if((((h|0)>-1?h:0-h|0)|0)<(f|0)){h=p-o|0;if((((h|0)>-1?h:0-h|0)|0)<(f|0)){h=j-m|0;h=(((h|0)>-1?h:0-h|0)|0)>=(f|0);if(q|h)h=h?w:r;else{h=((((m+1+o|0)>>>1)+j|0)>>>1)-n|0;a[i>>0]=((h|0)<(s|0)?s:(h|0)>(w|0)?w:h)+n;h=r}j=l-o|0;if((((j|0)>-1?j:0-j|0)|0)<(f|0)){if(!q){l=((((m+1+o|0)>>>1)+l|0)>>>1)-p|0;a[k>>0]=((l|0)<(s|0)?s:(l|0)>(w|0)?w:l)+p}h=h+1|0}p=n+4-p+(o-m<<2)>>3;n=0-h|0;p=(p|0)<(n|0)?n:(p|0)>(h|0)?h:p;n=p+m|0;a[t>>0]=n>>>0>255?0-n>>31:n;t=o-p|0;a[v>>0]=t>>>0>255?0-t>>31:t}}}u=u+1|0;if((u|0)==2)break;else v=v+c|0}}y=y+1|0;if((y|0)==4)break;else b=b+x|0}return}function Df(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;s=_(c,-3)|0;t=_(c,-2)|0;u=0-c|0;v=c<<1;w=(e>>2)+2|0;x=_(c,-4)|0;y=c*3|0;z=0;while(1){r=b+s|0;g=d[r>>0]|0;h=b+t|0;i=d[h>>0]|0;j=b+u|0;k=d[j>>0]|0;l=d[b>>0]|0;m=b+c|0;n=d[m>>0]|0;o=b+v|0;p=d[o>>0]|0;q=k-l|0;q=(q|0)>-1?q:0-q|0;do if((q|0)<(e|0)){A=i-k|0;if((((A|0)>-1?A:0-A|0)|0)<(f|0)){A=n-l|0;if((((A|0)>-1?A:0-A|0)|0)<(f|0)){if((q|0)>=(w|0)){a[j>>0]=(k+2+(i<<1)+n|0)>>>2;a[b>>0]=(i+2+l+(n<<1)|0)>>>2;break}A=g-k|0;if((((A|0)>-1?A:0-A|0)|0)<(f|0)){A=d[b+x>>0]|0;a[j>>0]=(g+4+n+(k+i+l<<1)|0)>>>3;a[h>>0]=(g+2+i+k+l|0)>>>2;a[r>>0]=((g*3|0)+4+i+k+l+(A<<1)|0)>>>3}else a[j>>0]=(k+2+(i<<1)+n|0)>>>2;A=p-l|0;if((((A|0)>-1?A:0-A|0)|0)<(f|0)){A=d[b+y>>0]|0;a[b>>0]=(i+4+p+(l+k+n<<1)|0)>>>3;a[m>>0]=(k+2+l+n+p|0)>>>2;a[o>>0]=(k+4+l+n+(p*3|0)+(A<<1)|0)>>>3;break}else{a[b>>0]=(i+2+l+(n<<1)|0)>>>2;break}}}}while(0);z=z+1|0;if((z|0)==16)break;else b=b+1|0}return}function Ef(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;s=(e>>2)+2|0;t=0;while(1){m=b+-3|0;o=d[m>>0]|0;p=b+-2|0;q=d[p>>0]|0;r=b+-1|0;g=d[r>>0]|0;h=d[b>>0]|0;i=b+1|0;j=d[i>>0]|0;k=b+2|0;l=d[k>>0]|0;n=g-h|0;n=(n|0)>-1?n:0-n|0;do if((n|0)<(e|0)){u=q-g|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){u=j-h|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){if((n|0)>=(s|0)){a[r>>0]=(g+2+(q<<1)+j|0)>>>2;a[b>>0]=(q+2+h+(j<<1)|0)>>>2;break}u=o-g|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){u=d[b+-4>>0]|0;a[r>>0]=(o+4+j+(g+q+h<<1)|0)>>>3;a[p>>0]=(o+2+q+g+h|0)>>>2;a[m>>0]=((o*3|0)+4+q+g+h+(u<<1)|0)>>>3}else a[r>>0]=(g+2+(q<<1)+j|0)>>>2;u=l-h|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){u=d[b+3>>0]|0;a[b>>0]=(q+4+l+(h+g+j<<1)|0)>>>3;a[i>>0]=(g+2+h+j+l|0)>>>2;a[k>>0]=(g+4+h+j+(l*3|0)+(u<<1)|0)>>>3;break}else{a[b>>0]=(q+2+h+(j<<1)|0)>>>2;break}}}}while(0);t=t+1|0;if((t|0)==16)break;else b=b+c|0}return}function Ff(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;s=(e>>2)+2|0;t=0;while(1){m=b+-3|0;o=d[m>>0]|0;p=b+-2|0;q=d[p>>0]|0;r=b+-1|0;g=d[r>>0]|0;h=d[b>>0]|0;i=b+1|0;j=d[i>>0]|0;k=b+2|0;l=d[k>>0]|0;n=g-h|0;n=(n|0)>-1?n:0-n|0;do if((n|0)<(e|0)){u=q-g|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){u=j-h|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){if((n|0)>=(s|0)){a[r>>0]=(g+2+(q<<1)+j|0)>>>2;a[b>>0]=(q+2+h+(j<<1)|0)>>>2;break}u=o-g|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){u=d[b+-4>>0]|0;a[r>>0]=(o+4+j+(g+q+h<<1)|0)>>>3;a[p>>0]=(o+2+q+g+h|0)>>>2;a[m>>0]=((o*3|0)+4+q+g+h+(u<<1)|0)>>>3}else a[r>>0]=(g+2+(q<<1)+j|0)>>>2;u=l-h|0;if((((u|0)>-1?u:0-u|0)|0)<(f|0)){u=d[b+3>>0]|0;a[b>>0]=(q+4+l+(h+g+j<<1)|0)>>>3;a[i>>0]=(g+2+h+j+l|0)>>>2;a[k>>0]=(g+4+h+j+(l*3|0)+(u<<1)|0)>>>3;break}else{a[b>>0]=(q+2+h+(j<<1)|0)>>>2;break}}}}while(0);t=t+1|0;if((t|0)==8)break;else b=b+c|0}return}function Gf(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;p=0-c|0;q=_(c,-2)|0;r=0;while(1){n=a[g+r>>0]|0;o=n<<24>>24;if(n<<24>>24>=1){n=0-o|0;j=b+p|0;k=d[j>>0]|0;l=d[b+q>>0]|0;h=d[b>>0]|0;i=d[b+c>>0]|0;m=k-h|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=l-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=i-h|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=l+4-i+(h-k<<2)>>3;m=(m|0)<(n|0)?n:(m|0)>(o|0)?o:m;l=m+k|0;a[j>>0]=l>>>0>255?0-l>>31:l;m=h-m|0;a[b>>0]=m>>>0>255?0-m>>31:m}}}h=b+1|0;i=h+p|0;j=d[i>>0]|0;k=d[h+q>>0]|0;l=d[h>>0]|0;m=d[h+c>>0]|0;s=j-l|0;if((((s|0)>-1?s:0-s|0)|0)<(e|0)){s=k-j|0;if((((s|0)>-1?s:0-s|0)|0)<(f|0)){s=m-l|0;if((((s|0)>-1?s:0-s|0)|0)<(f|0)){s=k+4-m+(l-j<<2)>>3;s=(s|0)<(n|0)?n:(s|0)>(o|0)?o:s;o=s+j|0;a[i>>0]=o>>>0>255?0-o>>31:o;s=l-s|0;a[h>>0]=s>>>0>255?0-s>>31:s}}}}r=r+1|0;if((r|0)==4)break;else b=b+2|0}return}function Hf(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;p=c<<1;q=0;while(1){n=a[g+q>>0]|0;o=n<<24>>24;if(n<<24>>24>=1){n=0-o|0;j=b+-1|0;k=d[j>>0]|0;l=d[b+-2>>0]|0;h=d[b>>0]|0;i=d[b+1>>0]|0;m=k-h|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=l-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=i-h|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=l+4-i+(h-k<<2)>>3;m=(m|0)<(n|0)?n:(m|0)>(o|0)?o:m;l=m+k|0;a[j>>0]=l>>>0>255?0-l>>31:l;m=h-m|0;a[b>>0]=m>>>0>255?0-m>>31:m}}}h=b+c|0;i=h+-1|0;j=d[i>>0]|0;k=d[h+-2>>0]|0;l=d[h>>0]|0;m=d[h+1>>0]|0;r=j-l|0;if((((r|0)>-1?r:0-r|0)|0)<(e|0)){r=k-j|0;if((((r|0)>-1?r:0-r|0)|0)<(f|0)){r=m-l|0;if((((r|0)>-1?r:0-r|0)|0)<(f|0)){r=k+4-m+(l-j<<2)>>3;r=(r|0)<(n|0)?n:(r|0)>(o|0)?o:r;o=r+j|0;a[i>>0]=o>>>0>255?0-o>>31:o;r=l-r|0;a[h>>0]=r>>>0>255?0-r>>31:r}}}}q=q+1|0;if((q|0)==4)break;else b=b+p|0}return}function If(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;q=c<<2;r=0;while(1){p=a[g+r>>0]|0;k=p<<24>>24;if(p<<24>>24>=1){l=0-k|0;o=0;p=b;while(1){m=p+-1|0;n=d[m>>0]|0;h=d[p+-2>>0]|0;i=d[p>>0]|0;j=d[p+1>>0]|0;s=n-i|0;if((((s|0)>-1?s:0-s|0)|0)<(e|0)){s=h-n|0;if((((s|0)>-1?s:0-s|0)|0)<(f|0)){s=j-i|0;if((((s|0)>-1?s:0-s|0)|0)<(f|0)){s=h+4-j+(i-n<<2)>>3;s=(s|0)<(l|0)?l:(s|0)>(k|0)?k:s;n=s+n|0;a[m>>0]=n>>>0>255?0-n>>31:n;s=i-s|0;a[p>>0]=s>>>0>255?0-s>>31:s}}}o=o+1|0;if((o|0)==4)break;else p=p+c|0}}r=r+1|0;if((r|0)==4)break;else b=b+q|0}return}function Jf(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;o=0;while(1){n=a[g+o>>0]|0;j=n<<24>>24;if(n<<24>>24>=1){k=0-j|0;l=b+-1|0;m=d[l>>0]|0;n=d[b+-2>>0]|0;h=d[b>>0]|0;i=d[b+1>>0]|0;p=m-h|0;if((((p|0)>-1?p:0-p|0)|0)<(e|0)){p=n-m|0;if((((p|0)>-1?p:0-p|0)|0)<(f|0)){p=i-h|0;if((((p|0)>-1?p:0-p|0)|0)<(f|0)){p=n+4-i+(h-m<<2)>>3;p=(p|0)<(k|0)?k:(p|0)>(j|0)?j:p;n=p+m|0;a[l>>0]=n>>>0>255?0-n>>31:n;p=h-p|0;a[b>>0]=p>>>0>255?0-p>>31:p}}}}o=o+1|0;if((o|0)==4)break;else b=b+c|0}return}function Kf(b,c,e,f,g){b=b|0;c=c|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;p=c<<1;q=0;while(1){n=a[g+q>>0]|0;o=n<<24>>24;if(n<<24>>24>=1){n=0-o|0;j=b+-1|0;k=d[j>>0]|0;l=d[b+-2>>0]|0;h=d[b>>0]|0;i=d[b+1>>0]|0;m=k-h|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=l-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=i-h|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=l+4-i+(h-k<<2)>>3;m=(m|0)<(n|0)?n:(m|0)>(o|0)?o:m;l=m+k|0;a[j>>0]=l>>>0>255?0-l>>31:l;m=h-m|0;a[b>>0]=m>>>0>255?0-m>>31:m}}}h=b+c|0;i=h+-1|0;j=d[i>>0]|0;k=d[h+-2>>0]|0;l=d[h>>0]|0;m=d[h+1>>0]|0;r=j-l|0;if((((r|0)>-1?r:0-r|0)|0)<(e|0)){r=k-j|0;if((((r|0)>-1?r:0-r|0)|0)<(f|0)){r=m-l|0;if((((r|0)>-1?r:0-r|0)|0)<(f|0)){r=k+4-m+(l-j<<2)>>3;r=(r|0)<(n|0)?n:(r|0)>(o|0)?o:r;o=r+j|0;a[i>>0]=o>>>0>255?0-o>>31:o;r=l-r|0;a[h>>0]=r>>>0>255?0-r>>31:r}}}}q=q+1|0;if((q|0)==4)break;else b=b+p|0}return}function Lf(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;l=0-c|0;m=_(c,-2)|0;n=0;while(1){h=b+l|0;i=d[h>>0]|0;j=d[b+m>>0]|0;k=d[b>>0]|0;g=d[b+c>>0]|0;o=i-k|0;if((((o|0)>-1?o:0-o|0)|0)<(e|0)){o=j-i|0;if((((o|0)>-1?o:0-o|0)|0)<(f|0)){o=g-k|0;if((((o|0)>-1?o:0-o|0)|0)<(f|0)){a[h>>0]=(i+2+(j<<1)+g|0)>>>2;a[b>>0]=(j+2+k+(g<<1)|0)>>>2}}}n=n+1|0;if((n|0)==8)break;else b=b+1|0}return}function Mf(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=0;while(1){g=b+-1|0;i=d[g>>0]|0;j=d[b+-2>>0]|0;k=d[b>>0]|0;h=d[b+1>>0]|0;m=i-k|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=j-i|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=h-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){a[g>>0]=(i+2+(j<<1)+h|0)>>>2;a[b>>0]=(j+2+k+(h<<1)|0)>>>2}}}l=l+1|0;if((l|0)==8)break;else b=b+c|0}return}function Nf(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=0;while(1){g=b+-1|0;i=d[g>>0]|0;j=d[b+-2>>0]|0;k=d[b>>0]|0;h=d[b+1>>0]|0;m=i-k|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=j-i|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=h-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){a[g>>0]=(i+2+(j<<1)+h|0)>>>2;a[b>>0]=(j+2+k+(h<<1)|0)>>>2}}}l=l+1|0;if((l|0)==16)break;else b=b+c|0}return}function Of(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=0;while(1){g=b+-1|0;i=d[g>>0]|0;j=d[b+-2>>0]|0;k=d[b>>0]|0;h=d[b+1>>0]|0;m=i-k|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=j-i|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=h-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){a[g>>0]=(i+2+(j<<1)+h|0)>>>2;a[b>>0]=(j+2+k+(h<<1)|0)>>>2}}}l=l+1|0;if((l|0)==4)break;else b=b+c|0}return}function Pf(b,c,e,f){b=b|0;c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=0;while(1){g=b+-1|0;i=d[g>>0]|0;j=d[b+-2>>0]|0;k=d[b>>0]|0;h=d[b+1>>0]|0;m=i-k|0;if((((m|0)>-1?m:0-m|0)|0)<(e|0)){m=j-i|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){m=h-k|0;if((((m|0)>-1?m:0-m|0)|0)<(f|0)){a[g>>0]=(i+2+(j<<1)+h|0)>>>2;a[b>>0]=(j+2+k+(h<<1)|0)>>>2}}}l=l+1|0;if((l|0)==8)break;else b=b+c|0}return}function Qf(c,f,g){c=c|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=(e[f>>1]<<16)+2097152>>16;n=f+16|0;h=b[n>>1]|0;i=h+p|0;h=p-h|0;p=f+8|0;o=b[p>>1]|0;l=f+24|0;j=b[l>>1]|0;m=(o>>1)-j|0;o=(j>>1)+o|0;b[f>>1]=o+i;b[p>>1]=m+h;b[n>>1]=h-m;b[l>>1]=i-o;l=f+2|0;o=b[l>>1]|0;i=f+18|0;n=b[i>>1]|0;m=n+o|0;n=o-n|0;o=f+10|0;h=b[o>>1]|0;p=f+26|0;j=b[p>>1]|0;k=(h>>1)-j|0;h=(j>>1)+h|0;b[l>>1]=h+m;b[o>>1]=k+n;b[i>>1]=n-k;b[p>>1]=m-h;p=f+4|0;h=b[p>>1]|0;m=f+20|0;i=b[m>>1]|0;k=i+h|0;i=h-i|0;h=f+12|0;n=b[h>>1]|0;o=f+28|0;l=b[o>>1]|0;j=(n>>1)-l|0;n=(l>>1)+n|0;b[p>>1]=n+k;b[h>>1]=j+i;b[m>>1]=i-j;b[o>>1]=k-n;o=f+6|0;n=b[o>>1]|0;k=f+22|0;m=b[k>>1]|0;j=m+n|0;m=n-m|0;n=f+14|0;i=b[n>>1]|0;h=f+30|0;p=b[h>>1]|0;l=(i>>1)-p|0;i=(p>>1)+i|0;b[o>>1]=i+j;b[n>>1]=l+m;b[k>>1]=m-l;b[h>>1]=j-i;h=g<<1;i=g*3|0;j=0;do{p=j<<2;o=b[f+(p<<1)>>1]|0;l=b[f+((p|2)<<1)>>1]|0;n=l+o|0;l=o-l|0;o=b[f+((p|1)<<1)>>1]|0;p=b[f+((p|3)<<1)>>1]|0;m=(o>>1)-p|0;o=(p>>1)+o|0;p=c+j|0;k=(o+n>>6)+(d[p>>0]|0)|0;a[p>>0]=k>>>0>255?0-k>>31:k;p=c+(j+g)|0;k=(d[p>>0]|0)+(m+l>>6)|0;a[p>>0]=k>>>0>255?0-k>>31:k;p=c+(j+h)|0;m=(d[p>>0]|0)+(l-m>>6)|0;a[p>>0]=m>>>0>255?0-m>>31:m;p=c+(j+i)|0;o=(d[p>>0]|0)+(n-o>>6)|0;a[p>>0]=o>>>0>255?0-o>>31:o;j=j+1|0}while((j|0)!=4);h=f+32|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(h|0));return}function Rf(c,f,g){c=c|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;i=(e[f>>1]|0)+32&65535;b[f>>1]=i;h=0;while(1){p=i<<16>>16;n=f+(h+32<<1)|0;m=b[n>>1]|0;k=m+p|0;m=p-m|0;p=f+(h+16<<1)|0;l=b[p>>1]|0;q=f+(h+48<<1)|0;w=b[q>>1]|0;o=(l>>1)-w|0;l=(w>>1)+l|0;w=l+k|0;s=o+m|0;o=m-o|0;l=k-l|0;k=f+(h+24<<1)|0;m=b[k>>1]|0;j=f+(h+40<<1)|0;i=b[j>>1]|0;u=f+(h+56<<1)|0;r=b[u>>1]|0;v=i-m-r-(r>>1)|0;t=f+(h+8<<1)|0;x=b[t>>1]|0;y=r-((m>>1)+m)+x|0;r=r+i+(i>>1)-x|0;x=i+m+x+(x>>1)|0;m=(x>>2)+v|0;i=(r>>2)+y|0;r=(y>>2)-r|0;v=x-(v>>2)|0;b[f+(h<<1)>>1]=v+w;b[u>>1]=w-v;b[t>>1]=r+s;b[q>>1]=s-r;b[p>>1]=i+o;b[j>>1]=o-i;b[k>>1]=m+l;b[n>>1]=l-m;h=h+1|0;if((h|0)==8)break;i=b[f+(h<<1)>>1]|0}h=g<<1;i=g*3|0;j=g<<2;k=g*5|0;l=g*6|0;m=g*7|0;n=0;do{y=n<<3;q=b[f+(y<<1)>>1]|0;t=b[f+((y|4)<<1)>>1]|0;r=t+q|0;t=q-t|0;q=b[f+((y|2)<<1)>>1]|0;w=b[f+((y|6)<<1)>>1]|0;s=(q>>1)-w|0;q=(w>>1)+q|0;w=q+r|0;u=s+t|0;s=t-s|0;q=r-q|0;r=b[f+((y|3)<<1)>>1]|0;t=b[f+((y|5)<<1)>>1]|0;v=b[f+((y|7)<<1)>>1]|0;x=t-r-v-(v>>1)|0;y=b[f+((y|1)<<1)>>1]|0;p=v-((r>>1)+r)+y|0;v=v+t+(t>>1)-y|0;y=t+r+y+(y>>1)|0;r=(y>>2)+x|0;t=(v>>2)+p|0;v=(p>>2)-v|0;x=y-(x>>2)|0;y=c+n|0;p=(x+w>>6)+(d[y>>0]|0)|0;a[y>>0]=p>>>0>255?0-p>>31:p;y=c+(n+g)|0;p=(v+u>>6)+(d[y>>0]|0)|0;a[y>>0]=p>>>0>255?0-p>>31:p;y=c+(n+h)|0;p=(t+s>>6)+(d[y>>0]|0)|0;a[y>>0]=p>>>0>255?0-p>>31:p;y=c+(n+i)|0;p=(d[y>>0]|0)+(r+q>>6)|0;a[y>>0]=p>>>0>255?0-p>>31:p;y=c+(n+j)|0;r=(d[y>>0]|0)+(q-r>>6)|0;a[y>>0]=r>>>0>255?0-r>>31:r;y=c+(n+k)|0;t=(d[y>>0]|0)+(s-t>>6)|0;a[y>>0]=t>>>0>255?0-t>>31:t;y=c+(n+l)|0;v=(d[y>>0]|0)+(u-v>>6)|0;a[y>>0]=v>>>0>255?0-v>>31:v;y=c+(n+m)|0;x=(d[y>>0]|0)+(w-x>>6)|0;a[y>>0]=x>>>0>255?0-x>>31:x;n=n+1|0}while((n|0)!=8);h=f+128|0;do{b[f>>1]=0;f=f+2|0}while((f|0)<(h|0));return}function Sf(c,e,f){c=c|0;e=e|0;f=f|0;var g=0,h=0;g=(b[e>>1]|0)+32>>6;b[e>>1]=0;e=(d[c>>0]|0)+g|0;a[c>>0]=e>>>0>255?0-e>>31:e;e=c+1|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;e=c+2|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;e=c+3|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;c=c+f|0;e=(d[c>>0]|0)+g|0;a[c>>0]=e>>>0>255?0-e>>31:e;e=c+1|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;e=c+2|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;e=c+3|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;c=c+f|0;e=(d[c>>0]|0)+g|0;a[c>>0]=e>>>0>255?0-e>>31:e;e=c+1|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;e=c+2|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;e=c+3|0;h=(d[e>>0]|0)+g|0;a[e>>0]=h>>>0>255?0-h>>31:h;f=c+f|0;c=(d[f>>0]|0)+g|0;a[f>>0]=c>>>0>255?0-c>>31:c;c=f+1|0;e=(d[c>>0]|0)+g|0;a[c>>0]=e>>>0>255?0-e>>31:e;c=f+2|0;e=(d[c>>0]|0)+g|0;a[c>>0]=e>>>0>255?0-e>>31:e;f=f+3|0;c=(d[f>>0]|0)+g|0;a[f>>0]=c>>>0>255?0-c>>31:c;return}function Tf(c,e,f){c=c|0;e=e|0;f=f|0;var g=0,h=0,i=0;g=(b[e>>1]|0)+32>>6;b[e>>1]=0;e=0;while(1){h=(d[c>>0]|0)+g|0;a[c>>0]=h>>>0>255?0-h>>31:h;h=c+1|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;h=c+2|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;h=c+3|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;h=c+4|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;h=c+5|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;h=c+6|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;h=c+7|0;i=(d[h>>0]|0)+g|0;a[h>>0]=i>>>0>255?0-i>>31:i;e=e+1|0;if((e|0)==8)break;else c=c+f|0}return}function Uf(e,f,g,h,i){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0;l=0;do{switch(a[i+(d[96537+l>>0]|0)>>0]|0){case 0:break;case 1:{j=g+(l<<4<<1)|0;k=b[j>>1]|0;if(!(k<<16>>16))m=6;else{o=e+(c[f+(l<<2)>>2]|0)|0;n=(k<<16>>16)+32>>6;b[j>>1]=0;k=(d[o>>0]|0)+n|0;a[o>>0]=k>>>0>255?0-k>>31:k;k=o+1|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j;k=o+2|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j;k=o+3|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j;k=o+h|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j;j=k+1|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;j=k+2|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;j=k+3|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;k=k+h|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j;j=k+1|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;j=k+2|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;j=k+3|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;k=k+h|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j;j=k+1|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;j=k+2|0;o=(d[j>>0]|0)+n|0;a[j>>0]=o>>>0>255?0-o>>31:o;k=k+3|0;j=(d[k>>0]|0)+n|0;a[k>>0]=j>>>0>255?0-j>>31:j}break}default:{j=g+(l<<4<<1)|0;m=6}}if((m|0)==6){m=0;Qf(e+(c[f+(l<<2)>>2]|0)|0,j,h)}l=l+1|0}while((l|0)!=16);return}function Vf(e,f,g,h,i){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0;l=0;do{if(!(a[i+(d[96537+l>>0]|0)>>0]|0)){j=g+(l<<4<<1)|0;k=b[j>>1]|0;if(k<<16>>16){n=e+(c[f+(l<<2)>>2]|0)|0;m=(k<<16>>16)+32>>6;b[j>>1]=0;k=(d[n>>0]|0)+m|0;a[n>>0]=k>>>0>255?0-k>>31:k;k=n+1|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j;k=n+2|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j;k=n+3|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j;k=n+h|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j;j=k+1|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;j=k+2|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;j=k+3|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;k=k+h|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j;j=k+1|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;j=k+2|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;j=k+3|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;k=k+h|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j;j=k+1|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;j=k+2|0;n=(d[j>>0]|0)+m|0;a[j>>0]=n>>>0>255?0-n>>31:n;k=k+3|0;j=(d[k>>0]|0)+m|0;a[k>>0]=j>>>0>255?0-j>>31:j}}else Qf(e+(c[f+(l<<2)>>2]|0)|0,g+(l<<4<<1)|0,h);l=l+1|0}while((l|0)!=16);return}function Wf(d,e,f,g,h){d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0;switch(a[h+12>>0]|0){case 0:break;case 1:{if(!(b[f>>1]|0))j=4;else Tf(d+(c[e>>2]|0)|0,f,g);break}default:j=4}if((j|0)==4)Rf(d+(c[e>>2]|0)|0,f,g);switch(a[h+14>>0]|0){case 0:break;case 1:{i=f+128|0;if(!(b[i>>1]|0))j=9;else Tf(d+(c[e+16>>2]|0)|0,i,g);break}default:{i=f+128|0;j=9}}if((j|0)==9)Rf(d+(c[e+16>>2]|0)|0,i,g);switch(a[h+28>>0]|0){case 0:break;case 1:{i=f+256|0;if(!(b[i>>1]|0))j=14;else Tf(d+(c[e+32>>2]|0)|0,i,g);break}default:{i=f+256|0;j=14}}if((j|0)==14)Rf(d+(c[e+32>>2]|0)|0,i,g);switch(a[h+30>>0]|0){case 1:{i=f+384|0;if(b[i>>1]|0){Tf(d+(c[e+48>>2]|0)|0,i,g);return}break}case 0:return;default:i=f+384|0}Rf(d+(c[e+48>>2]|0)|0,i,g);return}function Xf(e,f,g,h,i){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;n=20;o=1;while(1){j=o<<4;if((j|0)<(j|4|0)){m=e+(o+-1<<2)|0;do{if(!(a[i+(d[96537+j>>0]|0)>>0]|0)){k=g+(j<<4<<1)|0;l=b[k>>1]|0;if(l<<16>>16){q=(c[m>>2]|0)+(c[f+(j<<2)>>2]|0)|0;p=(l<<16>>16)+32>>6;b[k>>1]=0;l=(d[q>>0]|0)+p|0;a[q>>0]=l>>>0>255?0-l>>31:l;l=q+1|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+2|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+3|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+h|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k;k=l+1|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=l+2|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=l+3|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;l=l+h|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k;k=l+1|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=l+2|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=l+3|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;l=l+h|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k;k=l+1|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=l+2|0;q=(d[k>>0]|0)+p|0;a[k>>0]=q>>>0>255?0-q>>31:q;l=l+3|0;k=(d[l>>0]|0)+p|0;a[l>>0]=k>>>0>255?0-k>>31:k}}else Qf((c[m>>2]|0)+(c[f+(j<<2)>>2]|0)|0,g+(j<<4<<1)|0,h);j=j+1|0}while((j|0)!=(n|0))}o=o+1|0;if((o|0)==3)break;else n=n+16|0}return}function Yf(e,f,g,h,i){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;n=20;o=1;while(1){j=o<<4;if((j|0)<(j|4|0)){m=e+(o+-1<<2)|0;do{if(!(a[i+(d[96537+j>>0]|0)>>0]|0)){k=g+(j<<4<<1)|0;l=b[k>>1]|0;if(l<<16>>16){p=(c[m>>2]|0)+(c[f+(j<<2)>>2]|0)|0;l=(l<<16>>16)+32>>6;b[k>>1]=0;k=(d[p>>0]|0)+l|0;a[p>>0]=k>>>0>255?0-k>>31:k;k=p+1|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+2|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+3|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;p=p+h|0;k=(d[p>>0]|0)+l|0;a[p>>0]=k>>>0>255?0-k>>31:k;k=p+1|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+2|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+3|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;p=p+h|0;k=(d[p>>0]|0)+l|0;a[p>>0]=k>>>0>255?0-k>>31:k;k=p+1|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+2|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+3|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;p=p+h|0;k=(d[p>>0]|0)+l|0;a[p>>0]=k>>>0>255?0-k>>31:k;k=p+1|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;k=p+2|0;q=(d[k>>0]|0)+l|0;a[k>>0]=q>>>0>255?0-q>>31:q;p=p+3|0;l=(d[p>>0]|0)+l|0;a[p>>0]=l>>>0>255?0-l>>31:l}}else Qf((c[m>>2]|0)+(c[f+(j<<2)>>2]|0)|0,g+(j<<4<<1)|0,h);j=j+1|0}while((j|0)!=(n|0))}o=o+1|0;if((o|0)==3){o=24;p=1;break}else n=n+16|0}while(1){q=p<<4;j=q|4;if((j|0)<(q|8|0)){n=e+(p+-1<<2)|0;do{k=j+4|0;if(!(a[i+(d[96537+k>>0]|0)>>0]|0)){l=g+(j<<4<<1)|0;m=b[l>>1]|0;if(m<<16>>16){q=(c[n>>2]|0)+(c[f+(k<<2)>>2]|0)|0;m=(m<<16>>16)+32>>6;b[l>>1]=0;l=(d[q>>0]|0)+m|0;a[q>>0]=l>>>0>255?0-l>>31:l;l=q+1|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+2|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+3|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;q=q+h|0;l=(d[q>>0]|0)+m|0;a[q>>0]=l>>>0>255?0-l>>31:l;l=q+1|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+2|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+3|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;q=q+h|0;l=(d[q>>0]|0)+m|0;a[q>>0]=l>>>0>255?0-l>>31:l;l=q+1|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+2|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+3|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;q=q+h|0;l=(d[q>>0]|0)+m|0;a[q>>0]=l>>>0>255?0-l>>31:l;l=q+1|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;l=q+2|0;k=(d[l>>0]|0)+m|0;a[l>>0]=k>>>0>255?0-k>>31:k;q=q+3|0;m=(d[q>>0]|0)+m|0;a[q>>0]=m>>>0>255?0-m>>31:m}}else Qf((c[n>>2]|0)+(c[f+(k<<2)>>2]|0)|0,g+(j<<4<<1)|0,h);j=j+1|0}while((j|0)!=(o|0))}p=p+1|0;if((p|0)==3)break;else o=o+16|0}return}
function iE(b,e,f){b=b|0;e=e|0;f=f|0;var g=0.0,h=0,j=0.0,k=0,l=0,m=0.0,n=0,o=0,p=0,q=0.0,t=0,u=0,v=0.0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0.0;L=i;i=i+512|0;H=L;switch(e|0){case 0:{K=24;I=-149;z=4;break}case 1:{K=53;I=-1074;z=4;break}case 2:{K=53;I=-1074;z=4;break}default:g=0.0}a:do if((z|0)==4){E=b+4|0;D=b+100|0;do{e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0}while(($D(e)|0)!=0);b:do switch(e|0){case 43:case 45:{h=1-(((e|0)==45&1)<<1)|0;e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0;J=h;break b}else{e=eE(b)|0;J=h;break b}}default:J=1}while(0);h=e;e=0;do{if((h|32|0)!=(a[105071+e>>0]|0))break;do if(e>>>0<7){h=c[E>>2]|0;if(h>>>0<(c[D>>2]|0)>>>0){c[E>>2]=h+1;h=d[h>>0]|0;break}else{h=eE(b)|0;break}}while(0);e=e+1|0}while(e>>>0<8);c:do switch(e|0){case 8:break;case 3:{z=23;break}default:{k=(f|0)!=0;if(k&e>>>0>3)if((e|0)==8)break c;else{z=23;break c}d:do if(!e){e=0;do{if((h|32|0)!=(a[107496+e>>0]|0))break d;do if(e>>>0<2){h=c[E>>2]|0;if(h>>>0<(c[D>>2]|0)>>>0){c[E>>2]=h+1;h=d[h>>0]|0;break}else{h=eE(b)|0;break}}while(0);e=e+1|0}while(e>>>0<3)}while(0);switch(e|0){case 3:{e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;if((e|0)==40)e=1;else{if(!(c[D>>2]|0)){g=r;break a}c[E>>2]=(c[E>>2]|0)+-1;g=r;break a}while(1){h=c[E>>2]|0;if(h>>>0<(c[D>>2]|0)>>>0){c[E>>2]=h+1;h=d[h>>0]|0}else h=eE(b)|0;if(!((h+-48|0)>>>0<10|(h+-65|0)>>>0<26))if(!((h|0)==95|(h+-97|0)>>>0<26))break;e=e+1|0}if((h|0)==41){g=r;break a}h=(c[D>>2]|0)==0;if(!h)c[E>>2]=(c[E>>2]|0)+-1;if(!k){c[(VD()|0)>>2]=22;hE(b,0);g=0.0;break a}if(!e){g=r;break a}while(1){e=e+-1|0;if(!h)c[E>>2]=(c[E>>2]|0)+-1;if(!e){g=r;break a}}}case 0:{do if((h|0)==48){e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;if((e|32|0)!=120){if(!(c[D>>2]|0)){e=48;break}c[E>>2]=(c[E>>2]|0)+-1;e=48;break}e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0;k=0}else{e=eE(b)|0;k=0}e:while(1){switch(e|0){case 46:{z=74;break e}case 48:break;default:{y=0;l=0;x=0;h=0;n=k;o=0;w=0;m=1.0;k=0;g=0.0;break e}}e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0;k=1;continue}else{e=eE(b)|0;k=1;continue}}if((z|0)==74){e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;if((e|0)==48){k=0;h=0;do{e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;k=HF(k|0,h|0,-1,-1)|0;h=C}while((e|0)==48);y=0;l=0;x=k;n=1;o=1;w=0;m=1.0;k=0;g=0.0}else{y=0;l=0;x=0;h=0;n=k;o=1;w=0;m=1.0;k=0;g=0.0}}while(1){t=e+-48|0;p=e|32;if(t>>>0<10)z=86;else{u=(e|0)==46;if(!(u|(p+-97|0)>>>0<6)){p=x;t=y;break}if(u)if(!o){u=l;h=y;t=y;o=1;p=w;j=m}else{p=x;t=y;e=46;break}else z=86}if((z|0)==86){z=0;e=(e|0)>57?p+-87|0:t;do if((y|0)<0|(y|0)==0&l>>>0<8){p=w;j=m;k=e+(k<<4)|0}else{if((y|0)<0|(y|0)==0&l>>>0<14){v=m*.0625;p=w;j=v;g=g+v*+(e|0);break}if((w|0)!=0|(e|0)==0){p=w;j=m}else{p=1;j=m;g=g+m*.5}}while(0);l=HF(l|0,y|0,1,0)|0;u=x;t=C;n=1}e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;y=t;x=u;e=d[e>>0]|0;w=p;m=j;continue}else{y=t;x=u;e=eE(b)|0;w=p;m=j;continue}}if(!n){e=(c[D>>2]|0)==0;if(!e)c[E>>2]=(c[E>>2]|0)+-1;if(!f)hE(b,0);else if(!e){e=c[E>>2]|0;c[E>>2]=e+-1;if(o|0)c[E>>2]=e+-2}g=+(J|0)*0.0;break a}n=(o|0)==0;o=n?l:p;n=n?t:h;if((t|0)<0|(t|0)==0&l>>>0<8){h=t;do{k=k<<4;l=HF(l|0,h|0,1,0)|0;h=C}while((h|0)<0|(h|0)==0&l>>>0<8)}if((e|32|0)==112){h=jE(b,f)|0;e=C;if((h|0)==0&(e|0)==-2147483648){if(!f){hE(b,0);g=0.0;break a}if(!(c[D>>2]|0)){h=0;e=0}else{c[E>>2]=(c[E>>2]|0)+-1;h=0;e=0}}}else if(!(c[D>>2]|0)){h=0;e=0}else{c[E>>2]=(c[E>>2]|0)+-1;h=0;e=0}H=GF(o|0,n|0,2)|0;H=HF(H|0,C|0,-32,-1)|0;e=HF(H|0,C|0,h|0,e|0)|0;h=C;if(!k){g=+(J|0)*0.0;break a}if((h|0)>0|(h|0)==0&e>>>0>(0-I|0)>>>0){c[(VD()|0)>>2]=34;g=+(J|0)*1797693134862315708145274.0e284*1797693134862315708145274.0e284;break a}H=I+-106|0;G=((H|0)<0)<<31>>31;if((h|0)<(G|0)|(h|0)==(G|0)&e>>>0<H>>>0){c[(VD()|0)>>2]=34;g=+(J|0)*2.2250738585072014e-308*2.2250738585072014e-308;break a}if((k|0)>-1){do{G=!(g>=.5);H=G&1|k<<1;k=H^1;g=g+(G?g:g+-1.0);e=HF(e|0,h|0,-1,-1)|0;h=C}while((H|0)>-1);l=e;m=g}else{l=e;m=g}e=EF(32,0,I|0,((I|0)<0)<<31>>31|0)|0;e=HF(l|0,h|0,e|0,C|0)|0;I=C;if(0>(I|0)|0==(I|0)&K>>>0>e>>>0)if((e|0)<0){e=0;z=127}else z=125;else{e=K;z=125}if((z|0)==125)if((e|0)<53)z=127;else{h=e;j=+(J|0);g=0.0}if((z|0)==127){g=+(J|0);h=e;j=g;g=+lE(+kE(1.0,84-e|0),g)}K=(k&1|0)==0&(m!=0.0&(h|0)<32);g=j*(K?0.0:m)+(g+j*+(((K&1)+k|0)>>>0))-g;if(!(g!=0.0))c[(VD()|0)>>2]=34;g=+nE(g,l);break a}else e=h;while(0);F=I+K|0;G=0-F|0;k=0;f:while(1){switch(e|0){case 46:{z=138;break f}case 48:break;default:{h=0;p=0;o=0;break f}}e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0;k=1;continue}else{e=eE(b)|0;k=1;continue}}if((z|0)==138){e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;if((e|0)==48){h=0;e=0;while(1){h=HF(h|0,e|0,-1,-1)|0;k=C;e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;if((e|0)==48)e=k;else{p=k;k=1;o=1;break}}}else{h=0;p=0;o=1}}c[H>>2]=0;n=e+-48|0;l=(e|0)==46;g:do if(l|n>>>0<10){B=H+496|0;y=0;u=0;w=l;A=p;t=k;z=o;k=0;l=0;o=0;h:while(1){do if(w)if(!z){h=y;p=u;z=1}else{p=A;e=y;n=u;break h}else{w=HF(y|0,u|0,1,0)|0;u=C;x=(e|0)!=48;if((l|0)>=125){if(!x){p=A;y=w;break}c[B>>2]=c[B>>2]|1;p=A;y=w;break}p=H+(l<<2)|0;if(k)n=e+-48+((c[p>>2]|0)*10|0)|0;c[p>>2]=n;k=k+1|0;n=(k|0)==9;p=A;y=w;t=1;k=n?0:k;l=(n&1)+l|0;o=x?w:o}while(0);e=c[E>>2]|0;if(e>>>0<(c[D>>2]|0)>>>0){c[E>>2]=e+1;e=d[e>>0]|0}else e=eE(b)|0;n=e+-48|0;w=(e|0)==46;if(!(w|n>>>0<10)){n=z;z=161;break g}else A=p}t=(t|0)!=0;z=169}else{y=0;u=0;t=k;n=o;k=0;l=0;o=0;z=161}while(0);do if((z|0)==161){B=(n|0)==0;h=B?y:h;p=B?u:p;t=(t|0)!=0;if(!((e|32|0)==101&t))if((e|0)>-1){e=y;n=u;z=169;break}else{e=y;n=u;z=171;break}n=jE(b,f)|0;e=C;if((n|0)==0&(e|0)==-2147483648){if(!f){hE(b,0);g=0.0;break}if(!(c[D>>2]|0)){n=0;e=0}else{c[E>>2]=(c[E>>2]|0)+-1;n=0;e=0}}h=HF(n|0,e|0,h|0,p|0)|0;t=y;p=C;n=u;z=173}while(0);if((z|0)==169)if(!(c[D>>2]|0))z=171;else{c[E>>2]=(c[E>>2]|0)+-1;if(t){t=e;z=173}else z=172}if((z|0)==171)if(t){t=e;z=173}else z=172;do if((z|0)==172){c[(VD()|0)>>2]=22;hE(b,0);g=0.0}else if((z|0)==173){e=c[H>>2]|0;if(!e){g=+(J|0)*0.0;break}if(((n|0)<0|(n|0)==0&t>>>0<10)&((h|0)==(t|0)&(p|0)==(n|0)))if(K>>>0>30|(e>>>K|0)==0){g=+(J|0)*+(e>>>0);break}b=(I|0)/-2|0;E=((b|0)<0)<<31>>31;if((p|0)>(E|0)|(p|0)==(E|0)&h>>>0>b>>>0){c[(VD()|0)>>2]=34;g=+(J|0)*1797693134862315708145274.0e284*1797693134862315708145274.0e284;break}b=I+-106|0;E=((b|0)<0)<<31>>31;if((p|0)<(E|0)|(p|0)==(E|0)&h>>>0<b>>>0){c[(VD()|0)>>2]=34;g=+(J|0)*2.2250738585072014e-308*2.2250738585072014e-308;break}if(k){if((k|0)<9){n=H+(l<<2)|0;e=c[n>>2]|0;do{e=e*10|0;k=k+1|0}while((k|0)!=9);c[n>>2]=e}l=l+1|0}if((o|0)<9)if((o|0)<=(h|0)&(h|0)<18){if((h|0)==9){g=+(J|0)*+((c[H>>2]|0)>>>0);break}if((h|0)<9){g=+(J|0)*+((c[H>>2]|0)>>>0)/+(c[44148+(8-h<<2)>>2]|0);break}b=K+27+(_(h,-3)|0)|0;e=c[H>>2]|0;if((b|0)>30|(e>>>b|0)==0){g=+(J|0)*+(e>>>0)*+(c[44148+(h+-10<<2)>>2]|0);break}}e=(h|0)%9|0;if(!e){k=0;e=0}else{t=(h|0)>-1?e:e+9|0;n=c[44148+(8-t<<2)>>2]|0;if(!l){k=0;l=0}else{o=1e9/(n|0)|0;k=0;e=0;p=0;do{D=H+(p<<2)|0;E=c[D>>2]|0;b=((E>>>0)/(n>>>0)|0)+e|0;c[D>>2]=b;e=_((E>>>0)%(n>>>0)|0,o)|0;b=(p|0)==(k|0)&(b|0)==0;p=p+1|0;h=b?h+-9|0:h;k=b?p&127:k}while((p|0)!=(l|0));if(e){c[H+(l<<2)>>2]=e;l=l+1|0}}e=0;h=9-t+h|0}i:while(1){u=(h|0)<18;w=(h|0)==18;x=H+(k<<2)|0;do{if(!u){if(!w){y=l;break i}if((c[x>>2]|0)>>>0>=9007199){h=18;y=l;break i}}n=0;o=l+127|0;while(1){t=o&127;p=H+(t<<2)|0;o=GF(c[p>>2]|0,0,29)|0;o=HF(o|0,C|0,n|0,0)|0;n=C;if(n>>>0>0|(n|0)==0&o>>>0>1e9){b=SF(o|0,n|0,1e9,0)|0;o=TF(o|0,n|0,1e9,0)|0;n=b}else n=0;c[p>>2]=o;b=(t|0)==(k|0);l=(t|0)!=(l+127&127|0)|b?l:(o|0)==0?t:l;if(b)break;else o=t+-1|0}e=e+-29|0}while((n|0)==0);k=k+127&127;if((k|0)==(l|0)){b=l+127&127;l=H+((l+126&127)<<2)|0;c[l>>2]=c[l>>2]|c[H+(b<<2)>>2];l=b}c[H+(k<<2)>>2]=n;h=h+9|0}j:while(1){l=y+1&127;x=H+((y+127&127)<<2)|0;while(1){u=(h|0)==18;w=(h|0)>27?9:1;t=u^1;while(1){o=k&127;p=(o|0)==(y|0);do if(p)z=219;else{n=c[H+(o<<2)>>2]|0;if(n>>>0<9007199){z=219;break}if(n>>>0>9007199)break;n=k+1&127;if((n|0)==(y|0)){z=219;break}n=c[H+(n<<2)>>2]|0;if(n>>>0<254740991){z=219;break}if(!(n>>>0>254740991|t)){h=o;l=y;break j}}while(0);if((z|0)==219){z=0;if(u){z=220;break j}}e=e+w|0;if((k|0)==(y|0))k=y;else break}t=(1<<w)+-1|0;u=1e9>>>w;o=k;n=0;p=k;while(1){E=H+(p<<2)|0;b=c[E>>2]|0;k=(b>>>w)+n|0;c[E>>2]=k;n=_(b&t,u)|0;k=(p|0)==(o|0)&(k|0)==0;p=p+1&127;h=k?h+-9|0:h;k=k?p:o;if((p|0)==(y|0))break;else o=k}if(!n)continue;if((l|0)!=(k|0))break;c[x>>2]=c[x>>2]|1}c[H+(y<<2)>>2]=n;y=l}if((z|0)==220)if(p){c[H+(l+-1<<2)>>2]=0;h=y}else{h=o;l=y}g=+((c[H+(h<<2)>>2]|0)>>>0);h=k+1&127;if((h|0)==(l|0)){l=k+2&127;c[H+(l+-1<<2)>>2]=0}v=+(J|0);j=v*(g*1.0e9+ +((c[H+(h<<2)>>2]|0)>>>0));u=e+53|0;p=u-I|0;t=(p|0)<(K|0);h=t&1;o=t?((p|0)<0?0:p):K;if((o|0)<53){M=+lE(+kE(1.0,105-o|0),j);m=+oE(j,+kE(1.0,53-o|0));q=M;g=m;m=M+(j-m)}else{q=0.0;g=0.0;m=j}n=k+2&127;do if((n|0)==(l|0))j=g;else{n=c[H+(n<<2)>>2]|0;do if(n>>>0<5e8){if(!n)if((k+3&127|0)==(l|0))break;g=v*.25+g}else{if(n>>>0>5e8){g=v*.75+g;break}if((k+3&127|0)==(l|0)){g=v*.5+g;break}else{g=v*.75+g;break}}while(0);if((53-o|0)<=1){j=g;break}if(+oE(g,1.0)!=0.0){j=g;break}j=g+1.0}while(0);g=m+j-q;do if((u&2147483647|0)>(-2-F|0)){if(+N(+g)>=9007199254740992.0){h=t&(o|0)==(p|0)?0:h;e=e+1|0;g=g*.5}if((e+50|0)<=(G|0))if(!(j!=0.0&(h|0)!=0))break;c[(VD()|0)>>2]=34}while(0);g=+nE(g,e)}while(0);break a}default:{if(c[D>>2]|0)c[E>>2]=(c[E>>2]|0)+-1;c[(VD()|0)>>2]=22;hE(b,0);g=0.0;break a}}}}while(0);if((z|0)==23){h=(c[D>>2]|0)==0;if(!h)c[E>>2]=(c[E>>2]|0)+-1;if((f|0)!=0&e>>>0>3)do{if(!h)c[E>>2]=(c[E>>2]|0)+-1;e=e+-1|0}while(e>>>0>3)}g=+(J|0)*s}while(0);i=L;return +g}function jE(a,b){a=a|0;b=b|0;var e=0,f=0,g=0,h=0,i=0,j=0;i=a+4|0;e=c[i>>2]|0;j=a+100|0;if(e>>>0<(c[j>>2]|0)>>>0){c[i>>2]=e+1;e=d[e>>0]|0}else e=eE(a)|0;switch(e|0){case 43:case 45:{f=(e|0)==45&1;e=c[i>>2]|0;if(e>>>0<(c[j>>2]|0)>>>0){c[i>>2]=e+1;e=d[e>>0]|0}else e=eE(a)|0;if((b|0)!=0&(e+-48|0)>>>0>9)if(!(c[j>>2]|0))h=f;else{c[i>>2]=(c[i>>2]|0)+-1;h=f}else h=f;break}default:h=0}if((e+-48|0)>>>0>9)if(!(c[j>>2]|0)){f=-2147483648;e=0}else{c[i>>2]=(c[i>>2]|0)+-1;f=-2147483648;e=0}else{f=0;do{f=e+-48+(f*10|0)|0;e=c[i>>2]|0;if(e>>>0<(c[j>>2]|0)>>>0){c[i>>2]=e+1;e=d[e>>0]|0}else e=eE(a)|0}while((e+-48|0)>>>0<10&(f|0)<214748364);b=((f|0)<0)<<31>>31;if((e+-48|0)>>>0<10){do{b=RF(f|0,b|0,10,0)|0;f=C;e=HF(e|0,((e|0)<0)<<31>>31|0,-48,-1)|0;f=HF(e|0,C|0,b|0,f|0)|0;b=C;e=c[i>>2]|0;if(e>>>0<(c[j>>2]|0)>>>0){c[i>>2]=e+1;e=d[e>>0]|0}else e=eE(a)|0}while((e+-48|0)>>>0<10&((b|0)<21474836|(b|0)==21474836&f>>>0<2061584302));g=f}else g=f;if((e+-48|0)>>>0<10)do{e=c[i>>2]|0;if(e>>>0<(c[j>>2]|0)>>>0){c[i>>2]=e+1;e=d[e>>0]|0}else e=eE(a)|0}while((e+-48|0)>>>0<10);if(c[j>>2]|0)c[i>>2]=(c[i>>2]|0)+-1;a=(h|0)!=0;e=EF(0,0,g|0,b|0)|0;f=a?C:b;e=a?e:g}C=f;return e|0}function kE(a,b){a=+a;b=b|0;var d=0;if((b|0)>1023){a=a*8988465674311579538646525.0e283;d=b+-1023|0;if((d|0)>1023){d=b+-2046|0;d=(d|0)>1023?1023:d;a=a*8988465674311579538646525.0e283}}else if((b|0)<-1022){a=a*2.2250738585072014e-308;d=b+1022|0;if((d|0)<-1022){d=b+2044|0;d=(d|0)<-1022?-1022:d;a=a*2.2250738585072014e-308}}else d=b;d=GF(d+1023|0,0,52)|0;b=C;c[k>>2]=d;c[k+4>>2]=b;return +(a*+h[k>>3])}function lE(a,b){a=+a;b=+b;return +(+mE(a,b))}function mE(a,b){a=+a;b=+b;var d=0;h[k>>3]=b;d=c[k+4>>2]|0;b=+N(+a);h[k>>3]=b;d=d&-2147483648|c[k+4>>2];c[k>>2]=c[k>>2];c[k+4>>2]=d;return +(+h[k>>3])}function nE(a,b){a=+a;b=b|0;return +(+kE(a,b))}function oE(a,b){a=+a;b=+b;return +(+pE(a,b))}function pE(a,b){a=+a;b=+b;var d=0,e=0,f=0,g=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0.0;h[k>>3]=a;d=c[k>>2]|0;m=c[k+4>>2]|0;h[k>>3]=b;n=c[k>>2]|0;o=c[k+4>>2]|0;e=JF(d|0,m|0,52)|0;e=e&2047;j=JF(n|0,o|0,52)|0;j=j&2047;p=m&-2147483648;i=GF(n|0,o|0,1)|0;l=C;a:do if((i|0)==0&(l|0)==0)q=3;else{r=+N(+b);h[k>>3]=r;g=c[k+4>>2]|0;if(g>>>0>2146435072|(g|0)==2146435072&(c[k>>2]|0)>>>0>0|(e|0)==2047)q=3;else{f=GF(d|0,m|0,1)|0;g=C;if(!(g>>>0>l>>>0|(g|0)==(l|0)&f>>>0>i>>>0))return +((f|0)==(i|0)&(g|0)==(l|0)?a*0.0:a);if(!e){e=GF(d|0,m|0,12)|0;f=C;if((f|0)>-1|(f|0)==-1&e>>>0>4294967295){g=e;e=0;do{e=e+-1|0;g=GF(g|0,f|0,1)|0;f=C}while((f|0)>-1|(f|0)==-1&g>>>0>4294967295)}else e=0;d=GF(d|0,m|0,1-e|0)|0;f=C}else f=m&1048575|1048576;if(!j){g=GF(n|0,o|0,12)|0;i=C;if((i|0)>-1|(i|0)==-1&g>>>0>4294967295){j=0;do{j=j+-1|0;g=GF(g|0,i|0,1)|0;i=C}while((i|0)>-1|(i|0)==-1&g>>>0>4294967295)}else j=0;n=GF(n|0,o|0,1-j|0)|0;m=C}else m=o&1048575|1048576;l=EF(d|0,f|0,n|0,m|0)|0;i=C;g=(i|0)>-1|(i|0)==-1&l>>>0>4294967295;b:do if((e|0)>(j|0)){while(1){if(g)if((d|0)==(n|0)&(f|0)==(m|0))break;else{d=l;f=i}d=GF(d|0,f|0,1)|0;f=C;e=e+-1|0;l=EF(d|0,f|0,n|0,m|0)|0;i=C;g=(i|0)>-1|(i|0)==-1&l>>>0>4294967295;if((e|0)<=(j|0))break b}b=a*0.0;break a}while(0);if(g)if((d|0)==(n|0)&(f|0)==(m|0)){b=a*0.0;break}else{f=i;d=l}if(f>>>0<1048576|(f|0)==1048576&d>>>0<0)do{d=GF(d|0,f|0,1)|0;f=C;e=e+-1|0}while(f>>>0<1048576|(f|0)==1048576&d>>>0<0);if((e|0)>0){o=HF(d|0,f|0,0,-1048576)|0;d=C;e=GF(e|0,0,52)|0;d=d|C;e=o|e}else{e=JF(d|0,f|0,1-e|0)|0;d=C}c[k>>2]=e;c[k+4>>2]=d|p;b=+h[k>>3]}}while(0);if((q|0)==3){b=a*b;b=b/b}return +b}function qE(a){a=a|0;var b=0,d=0;b=i;i=i+16|0;d=b;c[d>>2]=a;a=Ca(6,d|0)|0;a=UD((a|0)==-4?-115:a)|0;i=b;return a|0}function rE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;s=i;i=i+192|0;j=s+152|0;h=s+136|0;r=s+120|0;q=s+104|0;p=s+96|0;m=s+80|0;l=s+64|0;f=s+48|0;n=s+32|0;g=s+16|0;e=s;t=s+176|0;k=s+168|0;c[t>>2]=d;d=(c[t>>2]|0)+(4-1)&~(4-1);o=c[d>>2]|0;c[t>>2]=d+4;o=(b|0)==4?o|32768:o;a:do switch(b|0){case 14:{c[e>>2]=a;c[e+4>>2]=14;c[e+8>>2]=o;d=UD(Qa(221,e|0)|0)|0;break}case 9:{c[g>>2]=a;c[g+4>>2]=16;c[g+8>>2]=k;d=Qa(221,g|0)|0;switch(d|0){case -22:{c[n>>2]=a;c[n+4>>2]=9;c[n+8>>2]=o;d=Qa(221,n|0)|0;break}case 0:{d=c[k+4>>2]|0;d=(c[k>>2]|0)==2?0-d|0:d;break}default:d=UD(d)|0}break}case 1030:{c[f>>2]=a;c[f+4>>2]=1030;c[f+8>>2]=o;d=Qa(221,f|0)|0;if((d|0)!=-22){if((d|0)>-1){c[l>>2]=d;c[l+4>>2]=2;c[l+8>>2]=1;Qa(221,l|0)|0}d=UD(d)|0;break a}c[m>>2]=a;c[m+4>>2]=1030;c[m+8>>2]=0;d=Qa(221,m|0)|0;if((d|0)==-22){c[q>>2]=a;c[q+4>>2]=0;c[q+8>>2]=o;d=Qa(221,q|0)|0;if((d|0)>-1){c[r>>2]=d;c[r+4>>2]=2;c[r+8>>2]=1;Qa(221,r|0)|0}d=UD(d)|0;break a}else{if((d|0)>-1){c[p>>2]=d;Ca(6,p|0)|0}d=UD(-22)|0;break a}}default:if((b+-12|0)>>>0<5){c[h>>2]=a;c[h+4>>2]=b;c[h+8>>2]=o;d=UD(Qa(221,h|0)|0)|0;break a}else{c[j>>2]=a;c[j+4>>2]=b;c[j+8>>2]=o;d=UD(Qa(221,j|0)|0)|0;break a}}while(0);i=s;return d|0}function sE(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;r=i;i=i+208|0;o=r+8|0;p=r;f=_(d,b)|0;n=p;c[n>>2]=1;c[n+4>>2]=0;if(f|0){n=0-d|0;c[o+4>>2]=d;c[o>>2]=d;g=d;b=d;h=2;while(1){b=b+d+g|0;c[o+(h<<2)>>2]=b;if(b>>>0<f>>>0){m=g;g=b;h=h+1|0;b=m}else break}h=a+f+n|0;m=p+4|0;if(h>>>0>a>>>0){j=h;g=1;f=1;while(1){do if((g&3|0)==3){tE(a,d,e,f,o);l=c[m>>2]|0;b=l<<30|(c[p>>2]|0)>>>2;c[p>>2]=b;c[m>>2]=l>>>2;f=f+2|0}else{b=f+-1|0;if((c[o+(b<<2)>>2]|0)>>>0<(j-a|0)>>>0)tE(a,d,e,f,o);else vE(a,d,e,p,f,0,o);if((f|0)==1){b=c[p>>2]|0;c[m>>2]=b>>>31|c[m>>2]<<1;b=b<<1;c[p>>2]=b;f=0;break}if(b>>>0>31){g=c[p>>2]|0;c[m>>2]=g;c[p>>2]=0;b=f+-33|0;f=g;g=0}else{f=c[m>>2]|0;g=c[p>>2]|0}c[m>>2]=g>>>(32-b|0)|f<<b;b=g<<b;c[p>>2]=b;f=1}while(0);g=b|1;c[p>>2]=g;b=a+d|0;if(b>>>0>=h>>>0){a=f;break}else a=b}}else{b=a;a=1}vE(b,d,e,p,a,0,o);l=p+4|0;g=c[p>>2]|0;h=c[l>>2]|0;f=(h|0)==0;if(!((a|0)==1&(g|0)==1&f)){k=h;while(1){if((a|0)<2){h=g+-1|0;do if(!h){f=32;q=28}else{if(!(h&1)){f=0;do{f=f+1|0;h=h>>>1}while(!(h&1|0))}else{if(f)f=32;else{if(!(k&1)){h=k;f=0}else{j=0;h=k;f=0;break}do{f=f+1|0;h=h>>>1}while(!(h&1|0))}f=f+32|0}if(f>>>0>31)q=28;else{j=f;h=k}}while(0);if((q|0)==28){q=0;c[p>>2]=k;c[m>>2]=0;j=f+-32|0;g=k;h=0}c[p>>2]=h<<32-j|g>>>j;c[m>>2]=h>>>j;b=b+n|0;h=f+a|0}else{j=g>>>30;h=a+-2|0;c[p>>2]=(g<<1&2147483646|j<<31)^3;c[m>>2]=(j|k<<2)>>>1;vE(b+(0-(c[o+(h<<2)>>2]|0))+n|0,d,e,p,a+-1|0,1,o);k=c[p>>2]|0;c[m>>2]=k>>>31|c[m>>2]<<1;c[p>>2]=k<<1|1;b=b+n|0;vE(b,d,e,p,h,1,o)}g=c[p>>2]|0;k=c[l>>2]|0;f=(k|0)==0;if((h|0)==1&(g|0)==1&f)break;else a=h}}}i=r;return}function tE(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;p=i;i=i+240|0;o=p;c[o>>2]=a;a:do if((e|0)>1){n=0-b|0;g=e;h=a;k=a;l=1;while(1){a=h+n|0;m=g+-2|0;j=a+(0-(c[f+(m<<2)>>2]|0))|0;if((sb[d&7](k,j)|0)>-1)if((sb[d&7](k,a)|0)>-1){e=l;break a}e=l+1|0;h=o+(l<<2)|0;if((sb[d&7](j,a)|0)>-1){c[h>>2]=j;a=j;g=g+-1|0}else{c[h>>2]=a;g=m}if((g|0)<=1)break a;h=a;k=c[o>>2]|0;l=e}}else e=1;while(0);uE(b,o,e);i=p;return}function uE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0,j=0;h=i;i=i+256|0;e=h;a:do if((d|0)>=2){g=b+(d<<2)|0;c[g>>2]=e;if(a|0)while(1){f=a>>>0>256?256:a;KF(e|0,c[b>>2]|0,f|0)|0;e=0;do{j=b+(e<<2)|0;e=e+1|0;KF(c[j>>2]|0,c[b+(e<<2)>>2]|0,f|0)|0;c[j>>2]=(c[j>>2]|0)+f}while((e|0)!=(d|0));if((a|0)==(f|0))break a;a=a-f|0;e=c[g>>2]|0}}while(0);i=h;return}function vE(a,b,d,e,f,g,h){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;r=i;i=i+240|0;p=r;k=c[e>>2]|0;j=c[e+4>>2]|0;c[p>>2]=a;o=0-b|0;a:do if((j|0)!=0|(k|0)!=1){e=a+(0-(c[h+(f<<2)>>2]|0))|0;if((sb[d&7](e,a)|0)<1){e=1;q=18}else{g=(g|0)==0;n=e;l=1;m=k;while(1){if(g&(f|0)>1){e=a+o|0;g=c[h+(f+-2<<2)>>2]|0;if((sb[d&7](e,n)|0)>-1){g=f;e=l;q=19;break a}if((sb[d&7](e+(0-g)|0,n)|0)>-1){g=f;e=l;q=19;break a}}e=l+1|0;c[p+(l<<2)>>2]=n;g=m+-1|0;do if(!g){a=32;q=15}else{if(!(g&1)){a=0;do{a=a+1|0;g=g>>>1}while(!(g&1|0))}else{if(!j)g=32;else{if(!(j&1)){a=j;g=0}else{k=0;g=m;a=0;break}do{g=g+1|0;a=a>>>1}while(!(a&1|0))}a=g+32|0}if(a>>>0>31)q=15;else{k=a;g=m}}while(0);if((q|0)==15){q=0;k=a+-32|0;g=j;j=0}m=j<<32-k|g>>>k;j=j>>>k;g=a+f|0;if(!((j|0)!=0|(m|0)!=1)){a=n;q=19;break a}a=n+(0-(c[h+(g<<2)>>2]|0))|0;if((sb[d&7](a,c[p>>2]|0)|0)<1){a=n;f=g;g=0;q=18;break}else{k=n;f=g;g=1;n=a;l=e;a=k}}}}else{e=1;q=18}while(0);if((q|0)==18)if(!g){g=f;q=19}if((q|0)==19){uE(b,p,e);tE(a,b,d,g,h)}i=r;return}function wE(b){b=b|0;var c=0,e=0;c=0;while(1){if((d[105080+c>>0]|0)==(b|0)){e=2;break}c=c+1|0;if((c|0)==87){c=87;b=105168;e=5;break}}if((e|0)==2)if(!c)b=105168;else{b=105168;e=5}if((e|0)==5)while(1){e=b;while(1){b=e+1|0;if(!(a[e>>0]|0))break;else e=b}c=c+-1|0;if(!c)break;else e=5}return b|0}function xE(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0;n=i;i=i+128|0;g=n+112|0;m=n;h=m;j=44180;k=h+112|0;do{c[h>>2]=c[j>>2];h=h+4|0;j=j+4|0}while((h|0)<(k|0));if((d+-1|0)>>>0>2147483646)if(!d){d=1;l=4}else{c[(VD()|0)>>2]=75;d=-1}else{g=b;l=4}if((l|0)==4){l=-2-g|0;l=d>>>0>l>>>0?l:d;c[m+48>>2]=l;b=m+20|0;c[b>>2]=g;c[m+44>>2]=g;d=g+l|0;g=m+16|0;c[g>>2]=d;c[m+28>>2]=d;d=zE(m,e,f)|0;if(l){e=c[b>>2]|0;a[e+(((e|0)==(c[g>>2]|0))<<31>>31)>>0]=0}}i=n;return d|0}function yE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;e=a+20|0;f=c[e>>2]|0;a=(c[a+16>>2]|0)-f|0;a=a>>>0>d>>>0?d:a;KF(f|0,b|0,a|0)|0;c[e>>2]=(c[e>>2]|0)+a;return d|0}function zE(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;s=i;i=i+224|0;o=s+120|0;r=s+80|0;q=s;p=s+136|0;f=r;g=f+40|0;do{c[f>>2]=0;f=f+4|0}while((f|0)<(g|0));c[o>>2]=c[e>>2];if((AE(0,d,o,q,r)|0)<0)e=-1;else{if((c[b+76>>2]|0)>-1)m=LE(b)|0;else m=0;e=c[b>>2]|0;n=e&32;if((a[b+74>>0]|0)<1)c[b>>2]=e&-33;e=b+48|0;if(!(c[e>>2]|0)){g=b+44|0;h=c[g>>2]|0;c[g>>2]=p;j=b+28|0;c[j>>2]=p;k=b+20|0;c[k>>2]=p;c[e>>2]=80;l=b+16|0;c[l>>2]=p+80;f=AE(b,d,o,q,r)|0;if(h){mb[c[b+36>>2]&7](b,0,0)|0;f=(c[k>>2]|0)==0?-1:f;c[g>>2]=h;c[e>>2]=0;c[l>>2]=0;c[j>>2]=0;c[k>>2]=0}}else f=AE(b,d,o,q,r)|0;e=c[b>>2]|0;c[b>>2]=e|n;if(m|0)WD(b);e=(e&32|0)==0?f:-1}i=s;return e|0}function AE(e,f,g,j,l){e=e|0;f=f|0;g=g|0;j=j|0;l=l|0;var m=0,n=0,o=0,p=0,q=0.0,r=0,s=0,t=0,u=0,v=0,w=0,x=0.0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0;ia=i;i=i+624|0;da=ia+24|0;fa=ia+16|0;ea=ia+588|0;aa=ia+576|0;ca=ia;W=ia+536|0;ha=ia+8|0;ga=ia+528|0;M=(e|0)!=0;N=W+40|0;V=N;W=W+39|0;X=ha+4|0;Y=ea;Z=0-Y|0;$=aa+12|0;aa=aa+11|0;ba=$;O=ba-Y|0;P=-2-Y|0;Q=ba+2|0;R=da+288|0;S=ea+9|0;T=S;U=ea+8|0;m=0;o=0;n=0;y=f;a:while(1){do if((m|0)>-1)if((o|0)>(2147483647-m|0)){c[(VD()|0)>>2]=75;m=-1;break}else{m=o+m|0;break}while(0);f=a[y>>0]|0;if(!(f<<24>>24)){L=244;break}else o=y;b:while(1){switch(f<<24>>24){case 37:{f=o;L=9;break b}case 0:{f=o;break b}default:{}}K=o+1|0;f=a[K>>0]|0;o=K}c:do if((L|0)==9)while(1){L=0;if((a[f+1>>0]|0)!=37)break c;o=o+1|0;f=f+2|0;if((a[f>>0]|0)==37)L=9;else break}while(0);w=o-y|0;if(M)if(!(c[e>>2]&32))BE(y,w,e)|0;if((o|0)!=(y|0)){o=w;y=f;continue}r=f+1|0;o=a[r>>0]|0;p=(o<<24>>24)+-48|0;if(p>>>0<10){K=(a[f+2>>0]|0)==36;r=K?f+3|0:r;o=a[r>>0]|0;u=K?p:-1;n=K?1:n}else u=-1;f=o<<24>>24;d:do if((f&-32|0)==32){p=0;while(1){if(!(1<<f+-32&75913)){s=p;break d}p=1<<(o<<24>>24)+-32|p;r=r+1|0;o=a[r>>0]|0;f=o<<24>>24;if((f&-32|0)!=32){s=p;break}}}else s=0;while(0);do if(o<<24>>24==42){o=r+1|0;f=(a[o>>0]|0)+-48|0;if(f>>>0<10)if((a[r+2>>0]|0)==36){c[l+(f<<2)>>2]=10;n=1;r=r+3|0;f=c[j+((a[o>>0]|0)+-48<<3)>>2]|0}else L=24;else L=24;if((L|0)==24){L=0;if(n|0){m=-1;break a}if(!M){v=s;n=0;r=o;K=0;break}n=(c[g>>2]|0)+(4-1)&~(4-1);f=c[n>>2]|0;c[g>>2]=n+4;n=0;r=o}if((f|0)<0){v=s|8192;K=0-f|0}else{v=s;K=f}}else{p=(o<<24>>24)+-48|0;if(p>>>0<10){f=r;o=0;do{o=(o*10|0)+p|0;f=f+1|0;p=(a[f>>0]|0)+-48|0}while(p>>>0<10);if((o|0)<0){m=-1;break a}else{v=s;r=f;K=o}}else{v=s;K=0}}while(0);e:do if((a[r>>0]|0)==46){f=r+1|0;o=a[f>>0]|0;if(o<<24>>24!=42){p=(o<<24>>24)+-48|0;if(p>>>0<10)o=0;else{s=0;break}while(1){o=(o*10|0)+p|0;f=f+1|0;p=(a[f>>0]|0)+-48|0;if(p>>>0>=10){s=o;break e}}}f=r+2|0;o=(a[f>>0]|0)+-48|0;if(o>>>0<10)if((a[r+3>>0]|0)==36){c[l+(o<<2)>>2]=10;s=c[j+((a[f>>0]|0)+-48<<3)>>2]|0;f=r+4|0;break}if(n|0){m=-1;break a}if(M){J=(c[g>>2]|0)+(4-1)&~(4-1);s=c[J>>2]|0;c[g>>2]=J+4}else s=0}else{s=-1;f=r}while(0);t=0;while(1){o=(a[f>>0]|0)+-65|0;if(o>>>0>57){m=-1;break a}p=f+1|0;o=a[106972+(t*58|0)+o>>0]|0;r=o&255;if((r+-1|0)>>>0<8){f=p;t=r}else{J=p;break}}if(!(o<<24>>24)){m=-1;break}p=(u|0)>-1;do if(o<<24>>24==19)if(p){m=-1;break a}else L=52;else{if(p){c[l+(u<<2)>>2]=r;H=j+(u<<3)|0;I=c[H+4>>2]|0;L=ca;c[L>>2]=c[H>>2];c[L+4>>2]=I;L=52;break}if(!M){m=0;break a}DE(ca,r,g)}while(0);if((L|0)==52){L=0;if(!M){o=w;y=J;continue}}u=a[f>>0]|0;u=(t|0)!=0&(u&15|0)==3?u&-33:u;p=v&-65537;I=(v&8192|0)==0?v:p;f:do switch(u|0){case 110:switch(t|0){case 0:{c[c[ca>>2]>>2]=m;o=w;y=J;continue a}case 1:{c[c[ca>>2]>>2]=m;o=w;y=J;continue a}case 2:{o=c[ca>>2]|0;c[o>>2]=m;c[o+4>>2]=((m|0)<0)<<31>>31;o=w;y=J;continue a}case 3:{b[c[ca>>2]>>1]=m;o=w;y=J;continue a}case 4:{a[c[ca>>2]>>0]=m;o=w;y=J;continue a}case 6:{c[c[ca>>2]>>2]=m;o=w;y=J;continue a}case 7:{o=c[ca>>2]|0;c[o>>2]=m;c[o+4>>2]=((m|0)<0)<<31>>31;o=w;y=J;continue a}default:{o=w;y=J;continue a}}case 112:{t=I|8;s=s>>>0>8?s:8;u=120;L=64;break}case 88:case 120:{t=I;L=64;break}case 111:{p=ca;o=c[p>>2]|0;p=c[p+4>>2]|0;if((o|0)==0&(p|0)==0)f=N;else{f=N;do{f=f+-1|0;a[f>>0]=o&7|48;o=JF(o|0,p|0,3)|0;p=C}while(!((o|0)==0&(p|0)==0))}if(!(I&8)){o=I;t=0;r=107452;L=77}else{t=V-f|0;o=I;s=(s|0)>(t|0)?s:t+1|0;t=0;r=107452;L=77}break}case 105:case 100:{o=ca;f=c[o>>2]|0;o=c[o+4>>2]|0;if((o|0)<0){f=EF(0,0,f|0,o|0)|0;o=C;p=ca;c[p>>2]=f;c[p+4>>2]=o;p=1;r=107452;L=76;break f}if(!(I&2048)){r=I&1;p=r;r=(r|0)==0?107452:107454;L=76}else{p=1;r=107453;L=76}break}case 117:{o=ca;f=c[o>>2]|0;o=c[o+4>>2]|0;p=0;r=107452;L=76;break}case 99:{a[W>>0]=c[ca>>2];f=W;u=1;w=0;v=107452;o=N;break}case 109:{o=wE(c[(VD()|0)>>2]|0)|0;L=82;break}case 115:{o=c[ca>>2]|0;o=o|0?o:107462;L=82;break}case 67:{c[ha>>2]=c[ca>>2];c[X>>2]=0;c[ca>>2]=ha;f=ha;s=-1;L=86;break}case 83:{f=c[ca>>2]|0;if(!s){GE(e,32,K,0,I);f=0;L=97}else L=86;break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{q=+h[ca>>3];c[fa>>2]=0;h[k>>3]=q;if((c[k+4>>2]|0)<0){q=-q;G=1;H=107469}else if(!(I&2048)){H=I&1;G=H;H=(H|0)==0?107470:107475}else{G=1;H=107472}h[k>>3]=q;F=c[k+4>>2]&2146435072;do if(F>>>0<2146435072|(F|0)==2146435072&0<0){x=+JE(q,fa)*2.0;o=x!=0.0;if(o)c[fa>>2]=(c[fa>>2]|0)+-1;D=u|32;if((D|0)==97){v=u&32;y=(v|0)==0?H:H+9|0;w=G|2;f=12-s|0;do if(s>>>0>11|(f|0)==0)q=x;else{q=8.0;do{f=f+-1|0;q=q*16.0}while((f|0)!=0);if((a[y>>0]|0)==45){q=-(q+(-x-q));break}else{q=x+q-q;break}}while(0);o=c[fa>>2]|0;f=(o|0)<0?0-o|0:o;f=EE(f,((f|0)<0)<<31>>31,$)|0;if((f|0)==($|0)){a[aa>>0]=48;f=aa}a[f+-1>>0]=(o>>31&2)+43;t=f+-2|0;a[t>>0]=u+15;r=(s|0)<1;p=(I&8|0)==0;o=ea;while(1){H=~~q;f=o+1|0;a[o>>0]=d[107436+H>>0]|v;q=(q-+(H|0))*16.0;do if((f-Y|0)==1){if(p&(r&q==0.0))break;a[f>>0]=46;f=o+2|0}while(0);if(!(q!=0.0))break;else o=f}p=t;s=(s|0)!=0&(P+f|0)<(s|0)?Q+s-p|0:O-p+f|0;r=s+w|0;GE(e,32,K,r,I);if(!(c[e>>2]&32))BE(y,w,e)|0;GE(e,48,K,r,I^65536);o=f-Y|0;if(!(c[e>>2]&32))BE(ea,o,e)|0;f=ba-p|0;GE(e,48,s-(o+f)|0,0,0);if(!(c[e>>2]&32))BE(t,f,e)|0;GE(e,32,K,r,I^8192);f=(r|0)<(K|0)?K:r;break}f=(s|0)<0?6:s;if(o){o=(c[fa>>2]|0)+-28|0;c[fa>>2]=o;q=x*268435456.0}else{q=x;o=c[fa>>2]|0}F=(o|0)<0?da:R;E=F;o=F;do{B=~~q>>>0;c[o>>2]=B;o=o+4|0;q=(q-+(B>>>0))*1.0e9}while(q!=0.0);p=o;o=c[fa>>2]|0;if((o|0)>0){s=F;while(1){t=(o|0)>29?29:o;r=p+-4|0;do if(r>>>0<s>>>0)r=s;else{o=0;do{B=GF(c[r>>2]|0,0,t|0)|0;B=HF(B|0,C|0,o|0,0)|0;o=C;A=TF(B|0,o|0,1e9,0)|0;c[r>>2]=A;o=SF(B|0,o|0,1e9,0)|0;r=r+-4|0}while(r>>>0>=s>>>0);if(!o){r=s;break}r=s+-4|0;c[r>>2]=o}while(0);while(1){if(p>>>0<=r>>>0)break;o=p+-4|0;if(!(c[o>>2]|0))p=o;else break}o=(c[fa>>2]|0)-t|0;c[fa>>2]=o;if((o|0)>0)s=r;else break}}else r=F;if((o|0)<0){y=((f+25|0)/9|0)+1|0;z=(D|0)==102;v=r;while(1){w=0-o|0;w=(w|0)>9?9:w;do if(v>>>0<p>>>0){o=(1<<w)+-1|0;s=1e9>>>w;r=0;t=v;do{B=c[t>>2]|0;c[t>>2]=(B>>>w)+r;r=_(B&o,s)|0;t=t+4|0}while(t>>>0<p>>>0);o=(c[v>>2]|0)==0?v+4|0:v;if(!r){r=o;break}c[p>>2]=r;r=o;p=p+4|0}else r=(c[v>>2]|0)==0?v+4|0:v;while(0);o=z?F:r;p=(p-o>>2|0)>(y|0)?o+(y<<2)|0:p;o=(c[fa>>2]|0)+w|0;c[fa>>2]=o;if((o|0)>=0){z=r;break}else v=r}}else z=r;do if(z>>>0<p>>>0){o=(E-z>>2)*9|0;s=c[z>>2]|0;if(s>>>0<10)break;else r=10;do{r=r*10|0;o=o+1|0}while(s>>>0>=r>>>0)}else o=0;while(0);A=(D|0)==103;B=(f|0)!=0;r=f-((D|0)!=102?o:0)+((B&A)<<31>>31)|0;if((r|0)<(((p-E>>2)*9|0)+-9|0)){t=r+9216|0;r=F+4+(((t|0)/9|0)+-1024<<2)|0;t=((t|0)%9|0)+1|0;if((t|0)<9){s=10;do{s=s*10|0;t=t+1|0}while((t|0)!=9)}else s=10;w=c[r>>2]|0;y=(w>>>0)%(s>>>0)|0;t=(r+4|0)==(p|0);do if(t&(y|0)==0)s=z;else{x=(((w>>>0)/(s>>>0)|0)&1|0)==0?9007199254740992.0:9007199254740994.0;v=(s|0)/2|0;if(y>>>0<v>>>0)q=.5;else q=t&(y|0)==(v|0)?1.0:1.5;do if(G){if((a[H>>0]|0)!=45)break;x=-x;q=-q}while(0);t=w-y|0;c[r>>2]=t;if(!(x+q!=x)){s=z;break}D=t+s|0;c[r>>2]=D;if(D>>>0>999999999){o=z;while(1){s=r+-4|0;c[r>>2]=0;if(s>>>0<o>>>0){o=o+-4|0;c[o>>2]=0}D=(c[s>>2]|0)+1|0;c[s>>2]=D;if(D>>>0>999999999)r=s;else{v=o;r=s;break}}}else v=z;o=(E-v>>2)*9|0;t=c[v>>2]|0;if(t>>>0<10){s=v;break}else s=10;do{s=s*10|0;o=o+1|0}while(t>>>0>=s>>>0);s=v}while(0);D=r+4|0;z=s;p=p>>>0>D>>>0?D:p}w=0-o|0;while(1){if(p>>>0<=z>>>0){y=0;D=p;break}r=p+-4|0;if(!(c[r>>2]|0))p=r;else{y=1;D=p;break}}do if(A){f=(B&1^1)+f|0;if((f|0)>(o|0)&(o|0)>-5){u=u+-1|0;f=f+-1-o|0}else{u=u+-2|0;f=f+-1|0}p=I&8;if(p|0)break;do if(y){p=c[D+-4>>2]|0;if(!p){r=9;break}if(!((p>>>0)%10|0)){s=10;r=0}else{r=0;break}do{s=s*10|0;r=r+1|0}while(!((p>>>0)%(s>>>0)|0|0))}else r=9;while(0);p=((D-E>>2)*9|0)+-9|0;if((u|32|0)==102){p=p-r|0;p=(p|0)<0?0:p;f=(f|0)<(p|0)?f:p;p=0;break}else{p=p+o-r|0;p=(p|0)<0?0:p;f=(f|0)<(p|0)?f:p;p=0;break}}else p=I&8;while(0);v=f|p;s=(v|0)!=0&1;t=(u|32|0)==102;if(t){o=(o|0)>0?o:0;u=0}else{r=(o|0)<0?w:o;r=EE(r,((r|0)<0)<<31>>31,$)|0;if((ba-r|0)<2)do{r=r+-1|0;a[r>>0]=48}while((ba-r|0)<2);a[r+-1>>0]=(o>>31&2)+43;E=r+-2|0;a[E>>0]=u;o=ba-E|0;u=E}w=G+1+f+s+o|0;GE(e,32,K,w,I);if(!(c[e>>2]&32))BE(H,G,e)|0;GE(e,48,K,w,I^65536);do if(t){r=z>>>0>F>>>0?F:z;o=r;do{p=EE(c[o>>2]|0,0,S)|0;do if((o|0)==(r|0)){if((p|0)!=(S|0))break;a[U>>0]=48;p=U}else{if(p>>>0<=ea>>>0)break;FF(ea|0,48,p-Y|0)|0;do p=p+-1|0;while(p>>>0>ea>>>0)}while(0);if(!(c[e>>2]&32))BE(p,T-p|0,e)|0;o=o+4|0}while(o>>>0<=F>>>0);do if(v|0){if(c[e>>2]&32|0)break;BE(107504,1,e)|0}while(0);if((f|0)>0&o>>>0<D>>>0){p=o;while(1){o=EE(c[p>>2]|0,0,S)|0;if(o>>>0>ea>>>0){FF(ea|0,48,o-Y|0)|0;do o=o+-1|0;while(o>>>0>ea>>>0)}if(!(c[e>>2]&32))BE(o,(f|0)>9?9:f,e)|0;p=p+4|0;o=f+-9|0;if(!((f|0)>9&p>>>0<D>>>0)){f=o;break}else f=o}}GE(e,48,f+9|0,9,0)}else{t=y?D:z+4|0;if((f|0)>-1){s=(p|0)==0;r=z;do{o=EE(c[r>>2]|0,0,S)|0;if((o|0)==(S|0)){a[U>>0]=48;o=U}do if((r|0)==(z|0)){p=o+1|0;if(!(c[e>>2]&32))BE(o,1,e)|0;if(s&(f|0)<1){o=p;break}if(c[e>>2]&32|0){o=p;break}BE(107504,1,e)|0;o=p}else{if(o>>>0<=ea>>>0)break;FF(ea|0,48,o+Z|0)|0;do o=o+-1|0;while(o>>>0>ea>>>0)}while(0);p=T-o|0;if(!(c[e>>2]&32))BE(o,(f|0)>(p|0)?p:f,e)|0;f=f-p|0;r=r+4|0}while(r>>>0<t>>>0&(f|0)>-1)}GE(e,48,f+18|0,18,0);if(c[e>>2]&32|0)break;BE(u,ba-u|0,e)|0}while(0);GE(e,32,K,w,I^8192);f=(w|0)<(K|0)?K:w}else{t=(u&32|0)!=0;s=q!=q|0.0!=0.0;o=s?0:G;r=o+3|0;GE(e,32,K,r,p);f=c[e>>2]|0;if(!(f&32)){BE(H,o,e)|0;f=c[e>>2]|0}if(!(f&32))BE(s?(t?107496:107500):t?107488:107492,3,e)|0;GE(e,32,K,r,I^8192);f=(r|0)<(K|0)?K:r}while(0);o=f;y=J;continue a}default:{f=y;p=I;u=s;w=0;v=107452;o=N}}while(0);g:do if((L|0)==64){p=ca;o=c[p>>2]|0;p=c[p+4>>2]|0;r=u&32;if((o|0)==0&(p|0)==0){f=N;o=t;t=0;r=107452;L=77}else{f=N;do{f=f+-1|0;a[f>>0]=d[107436+(o&15)>>0]|r;o=JF(o|0,p|0,4)|0;p=C}while(!((o|0)==0&(p|0)==0));L=ca;if((t&8|0)==0|(c[L>>2]|0)==0&(c[L+4>>2]|0)==0){o=t;t=0;r=107452;L=77}else{o=t;t=2;r=107452+(u>>4)|0;L=77}}}else if((L|0)==76){f=EE(f,o,N)|0;o=I;t=p;L=77}else if((L|0)==82){L=0;I=FE(o,0,s)|0;H=(I|0)==0;f=o;u=H?s:I-o|0;w=0;v=107452;o=H?o+s|0:I}else if((L|0)==86){L=0;p=0;o=0;t=f;while(1){r=c[t>>2]|0;if(!r)break;o=HE(ga,r)|0;if((o|0)<0|o>>>0>(s-p|0)>>>0)break;p=o+p|0;if(s>>>0>p>>>0)t=t+4|0;else break}if((o|0)<0){m=-1;break a}GE(e,32,K,p,I);if(!p){f=0;L=97}else{r=0;while(1){o=c[f>>2]|0;if(!o){f=p;L=97;break g}o=HE(ga,o)|0;r=o+r|0;if((r|0)>(p|0)){f=p;L=97;break g}if(!(c[e>>2]&32))BE(ga,o,e)|0;if(r>>>0>=p>>>0){f=p;L=97;break}else f=f+4|0}}}while(0);if((L|0)==97){L=0;GE(e,32,K,f,I^8192);o=(K|0)>(f|0)?K:f;y=J;continue}if((L|0)==77){L=0;p=(s|0)>-1?o&-65537:o;o=ca;o=(c[o>>2]|0)!=0|(c[o+4>>2]|0)!=0;if((s|0)!=0|o){u=(o&1^1)+(V-f)|0;u=(s|0)>(u|0)?s:u;w=t;v=r;o=N}else{f=N;u=0;w=t;v=r;o=N}}t=o-f|0;r=(u|0)<(t|0)?t:u;s=w+r|0;o=(K|0)<(s|0)?s:K;GE(e,32,o,s,p);if(!(c[e>>2]&32))BE(v,w,e)|0;GE(e,48,o,s,p^65536);GE(e,48,r,t,0);if(!(c[e>>2]&32))BE(f,t,e)|0;GE(e,32,o,s,p^8192);y=J}h:do if((L|0)==244)if(!e)if(!n)m=0;else{m=1;while(1){n=c[l+(m<<2)>>2]|0;if(!n)break;DE(j+(m<<3)|0,n,g);m=m+1|0;if((m|0)>=10){m=1;break h}}if((m|0)<10)while(1){if(c[l+(m<<2)>>2]|0){m=-1;break h}m=m+1|0;if((m|0)>=10){m=1;break}}else m=1}while(0);i=ia;return m|0}function BE(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0;f=e+16|0;g=c[f>>2]|0;if(!g)if(!(CE(e)|0)){g=c[f>>2]|0;h=5}else f=0;else h=5;a:do if((h|0)==5){i=e+20|0;f=c[i>>2]|0;h=f;if((g-f|0)>>>0<d>>>0){f=mb[c[e+36>>2]&7](e,b,d)|0;break}b:do if((a[e+75>>0]|0)>-1){f=d;while(1){if(!f){g=h;f=0;break b}g=f+-1|0;if((a[b+g>>0]|0)==10)break;else f=g}if((mb[c[e+36>>2]&7](e,b,f)|0)>>>0<f>>>0)break a;d=d-f|0;b=b+f|0;g=c[i>>2]|0}else{g=h;f=0}while(0);KF(g|0,b|0,d|0)|0;c[i>>2]=(c[i>>2]|0)+d;f=f+d|0}while(0);return f|0}function CE(b){b=b|0;var d=0,e=0;d=b+74|0;e=a[d>>0]|0;a[d>>0]=e+255|e;d=c[b>>2]|0;if(!(d&8)){c[b+8>>2]=0;c[b+4>>2]=0;d=c[b+44>>2]|0;c[b+28>>2]=d;c[b+20>>2]=d;c[b+16>>2]=d+(c[b+48>>2]|0);d=0}else{c[b>>2]=d|32;d=-1}return d|0}function DE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0.0;a:do if(b>>>0<=20)do switch(b|0){case 9:{e=(c[d>>2]|0)+(4-1)&~(4-1);b=c[e>>2]|0;c[d>>2]=e+4;c[a>>2]=b;break a}case 10:{e=(c[d>>2]|0)+(4-1)&~(4-1);b=c[e>>2]|0;c[d>>2]=e+4;e=a;c[e>>2]=b;c[e+4>>2]=((b|0)<0)<<31>>31;break a}case 11:{e=(c[d>>2]|0)+(4-1)&~(4-1);b=c[e>>2]|0;c[d>>2]=e+4;e=a;c[e>>2]=b;c[e+4>>2]=0;break a}case 12:{e=(c[d>>2]|0)+(8-1)&~(8-1);b=e;f=c[b>>2]|0;b=c[b+4>>2]|0;c[d>>2]=e+8;e=a;c[e>>2]=f;c[e+4>>2]=b;break a}case 13:{f=(c[d>>2]|0)+(4-1)&~(4-1);e=c[f>>2]|0;c[d>>2]=f+4;e=(e&65535)<<16>>16;f=a;c[f>>2]=e;c[f+4>>2]=((e|0)<0)<<31>>31;break a}case 14:{f=(c[d>>2]|0)+(4-1)&~(4-1);e=c[f>>2]|0;c[d>>2]=f+4;f=a;c[f>>2]=e&65535;c[f+4>>2]=0;break a}case 15:{f=(c[d>>2]|0)+(4-1)&~(4-1);e=c[f>>2]|0;c[d>>2]=f+4;e=(e&255)<<24>>24;f=a;c[f>>2]=e;c[f+4>>2]=((e|0)<0)<<31>>31;break a}case 16:{f=(c[d>>2]|0)+(4-1)&~(4-1);e=c[f>>2]|0;c[d>>2]=f+4;f=a;c[f>>2]=e&255;c[f+4>>2]=0;break a}case 17:{f=(c[d>>2]|0)+(8-1)&~(8-1);g=+h[f>>3];c[d>>2]=f+8;h[a>>3]=g;break a}case 18:{f=(c[d>>2]|0)+(8-1)&~(8-1);g=+h[f>>3];c[d>>2]=f+8;h[a>>3]=g;break a}default:break a}while(0);while(0);return}function EE(b,c,d){b=b|0;c=c|0;d=d|0;var e=0;if(c>>>0>0|(c|0)==0&b>>>0>4294967295)while(1){e=TF(b|0,c|0,10,0)|0;d=d+-1|0;a[d>>0]=e|48;e=SF(b|0,c|0,10,0)|0;if(c>>>0>9|(c|0)==9&b>>>0>4294967295){b=e;c=C}else{b=e;break}}if(b)while(1){d=d+-1|0;a[d>>0]=(b>>>0)%10|0|48;if(b>>>0<10)break;else b=(b>>>0)/10|0}return d|0}function FE(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0;h=d&255;f=(e|0)!=0;a:do if(f&(b&3|0)!=0){g=d&255;while(1){if((a[b>>0]|0)==g<<24>>24){i=6;break a}b=b+1|0;e=e+-1|0;f=(e|0)!=0;if(!(f&(b&3|0)!=0)){i=5;break}}}else i=5;while(0);if((i|0)==5)if(f)i=6;else e=0;b:do if((i|0)==6){g=d&255;if((a[b>>0]|0)!=g<<24>>24){f=_(h,16843009)|0;c:do if(e>>>0>3)while(1){h=c[b>>2]^f;if((h&-2139062144^-2139062144)&h+-16843009|0)break;b=b+4|0;e=e+-4|0;if(e>>>0<=3){i=11;break c}}else i=11;while(0);if((i|0)==11)if(!e){e=0;break}while(1){if((a[b>>0]|0)==g<<24>>24)break b;b=b+1|0;e=e+-1|0;if(!e){e=0;break}}}}while(0);return (e|0?b:0)|0}function GE(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0;j=i;i=i+256|0;h=j;do if((d|0)>(e|0)&(f&73728|0)==0){f=d-e|0;FF(h|0,b|0,(f>>>0>256?256:f)|0)|0;b=c[a>>2]|0;g=(b&32|0)==0;if(f>>>0>255){e=d-e|0;do{if(g){BE(h,256,a)|0;b=c[a>>2]|0}f=f+-256|0;g=(b&32|0)==0}while(f>>>0>255);if(g)f=e&255;else break}else if(!g)break;BE(h,f,a)|0}while(0);i=j;return}function HE(a,b){a=a|0;b=b|0;if(!a)a=0;else a=IE(a,b,0)|0;return a|0}function IE(b,d,e){b=b|0;d=d|0;e=e|0;do if(!b)b=1;else{if(d>>>0<128){a[b>>0]=d;b=1;break}if(d>>>0<2048){a[b>>0]=d>>>6|192;a[b+1>>0]=d&63|128;b=2;break}if(d>>>0<55296|(d&-8192|0)==57344){a[b>>0]=d>>>12|224;a[b+1>>0]=d>>>6&63|128;a[b+2>>0]=d&63|128;b=3;break}if((d+-65536|0)>>>0<1048576){a[b>>0]=d>>>18|240;a[b+1>>0]=d>>>12&63|128;a[b+2>>0]=d>>>6&63|128;a[b+3>>0]=d&63|128;b=4;break}else{c[(VD()|0)>>2]=84;b=-1;break}}while(0);return b|0}function JE(a,b){a=+a;b=b|0;return +(+KE(a,b))}function KE(a,b){a=+a;b=b|0;var d=0,e=0,f=0;h[k>>3]=a;d=c[k>>2]|0;e=c[k+4>>2]|0;f=JF(d|0,e|0,52)|0;f=f&2047;switch(f|0){case 0:{if(a!=0.0){a=+KE(a*18446744073709551616.0,b);d=(c[b>>2]|0)+-64|0}else d=0;c[b>>2]=d;break}case 2047:break;default:{c[b>>2]=f+-1022;c[k>>2]=d;c[k+4>>2]=e&-2146435073|1071644672;a=+h[k>>3]}}return +a}function LE(a){a=a|0;return 0}function ME(b,d){b=b|0;d=d|0;var e=0,f=0,g=0,h=0;h=i;i=i+32|0;g=h;c[g>>2]=0;c[g+4>>2]=0;c[g+8>>2]=0;c[g+12>>2]=0;c[g+16>>2]=0;c[g+20>>2]=0;c[g+24>>2]=0;c[g+28>>2]=0;f=a[d>>0]|0;do if(!(f<<24>>24))d=0;else{if(!(a[d+1>>0]|0)){d=b;while(1)if((a[d>>0]|0)==f<<24>>24)d=d+1|0;else break;d=d-b|0;break}else{e=d;d=f}do{f=g+(((d&255)>>>5&255)<<2)|0;c[f>>2]=c[f>>2]|1<<(d&31);e=e+1|0;d=a[e>>0]|0}while(d<<24>>24!=0);e=a[b>>0]|0;a:do if(!(e<<24>>24))d=b;else{d=b;do{if(!(c[g+(((e&255)>>>5&255)<<2)>>2]&1<<(e&31)))break a;d=d+1|0;e=a[d>>0]|0}while(e<<24>>24!=0)}while(0);d=d-b|0}while(0);i=h;return d|0}function NE(b,d){b=b|0;d=d|0;var e=0,f=0,g=0;f=d&255;a:do if(!f)b=b+(aE(b)|0)|0;else{if(b&3){e=d&255;do{g=a[b>>0]|0;if(g<<24>>24==0?1:g<<24>>24==e<<24>>24)break a;b=b+1|0}while((b&3|0)!=0)}f=_(f,16843009)|0;e=c[b>>2]|0;b:do if(!((e&-2139062144^-2139062144)&e+-16843009))do{g=e^f;if((g&-2139062144^-2139062144)&g+-16843009|0)break b;b=b+4|0;e=c[b>>2]|0}while(!((e&-2139062144^-2139062144)&e+-16843009|0));while(0);e=d&255;while(1){g=a[b>>0]|0;if(g<<24>>24==0?1:g<<24>>24==e<<24>>24)break;else b=b+1|0}}while(0);return b|0}function OE(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0;k=i;i=i+112|0;j=k;c[j>>2]=0;g=j+4|0;c[g>>2]=a;c[j+44>>2]=a;h=j+8|0;c[h>>2]=(a|0)<0?-1:a+2147483647|0;c[j+76>>2]=-1;hE(j,0);e=dE(j,d,1,e,f)|0;if(b|0)c[b>>2]=a+((c[g>>2]|0)+(c[j+108>>2]|0)-(c[h>>2]|0));i=k;return e|0}function PE(a,b,c){a=a|0;b=b|0;c=c|0;a=OE(a,b,c,0,-2147483648)|0;return a|0}function QE(a,b,c){a=a|0;b=b|0;c=c|0;a=OE(a,b,c,-1,0)|0;return a|0}function RE(a,b,c){a=a|0;b=b|0;c=c|0;a=OE(a,b,c,-2147483648,0)|0;return a|0}function SE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0.0,f=0,g=0,h=0,j=0;j=i;i=i+112|0;h=j;f=h;g=f+112|0;do{c[f>>2]=0;f=f+4|0}while((f|0)<(g|0));f=h+4|0;c[f>>2]=a;g=h+8|0;c[g>>2]=-1;c[h+44>>2]=a;c[h+76>>2]=-1;hE(h,0);e=+iE(h,d,1);d=(c[f>>2]|0)-(c[g>>2]|0)+(c[h+108>>2]|0)|0;if(b|0)c[b>>2]=d|0?a+d|0:a;i=j;return +e}function TE(a,b){a=a|0;b=b|0;return +(+SE(a,b,1))}function UE(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0;a:do if(!c)b=0;else{h=b;while(1){g=c>>>1;b=h+(_(g,d)|0)|0;f=sb[e&7](a,b)|0;if(!f)break a;if((c|0)==1){b=0;break a}f=(f|0)<0;c=f?g:c-g|0;if(!c){b=0;break}else h=f?h:b}}while(0);return b|0}function VE(a){a=+a;var b=0.0,d=0.0,e=0;h[k>>3]=a;d=(c[k+4>>2]|0)<0?-.5:.5;b=+N(+a);h[k>>3]=b;e=c[k+4>>2]|0;do if(e>>>0<1082535490){b=+WE(b);if(e>>>0>=1072693248){a=d*(b+b/(b+1.0));break}if(e>>>0>=1045430272)a=d*(b*2.0-b*b/(b+1.0))}else a=d*2.0*+XE(b);while(0);return +a}function WE(a){a=+a;var b=0,d=0,e=0.0,f=0.0,g=0.0,i=0,j=0,l=0.0;h[k>>3]=a;d=c[k+4>>2]|0;b=d&2147483647;d=JF(c[k>>2]|0,d|0,63)|0;do if(b>>>0>1078159481){g=+N(+a);h[k>>3]=g;i=c[k+4>>2]|0;if(!(i>>>0>2146435072|(i|0)==2146435072&(c[k>>2]|0)>>>0>0))if(!d)if(a>709.782712893384)a=a*8988465674311579538646525.0e283;else j=11;else a=-1.0}else{if(b>>>0<=1071001154)if(b>>>0<1016070144)break;else{g=0.0;i=0;j=14;break}if(b>>>0<1072734898)if(!d){e=a+-.6931471803691238;b=1;f=1.9082149292705877e-10;j=12;break}else{e=a+.6931471803691238;b=-1;f=-1.9082149292705877e-10;j=12;break}else j=11}while(0);if((j|0)==11){b=~~(a*1.4426950408889634+(d|0?-.5:.5));f=+(b|0);e=a-f*.6931471803691238;f=f*1.9082149292705877e-10;j=12}if((j|0)==12){g=e-f;a=g;g=e-g-f;i=b;j=14}a:do if((j|0)==14){f=a*.5;e=a*f;l=e*(e*(e*(e*(4.008217827329362e-06-e*2.0109921818362437e-07)+-7.93650757867488e-05)+1.5873015872548146e-03)+-.03333333333333313)+1.0;f=3.0-f*l;f=e*((l-f)/(6.0-a*f));if(!i){a=a-(a*f-e);break}e=a*(f-g)-g-e;switch(i|0){case -1:{a=(a-e)*.5+-.5;break a}case 1:if(a<-.25){a=(e-(a+.5))*-2.0;break a}else{a=(a-e)*2.0+1.0;break a}default:{d=GF(i+1023|0,0,52)|0;j=C;c[k>>2]=d;c[k+4>>2]=j;f=+h[k>>3];if(i>>>0>56){a=a-e+1.0;a=((i|0)==1024?a*2.0*8988465674311579538646525.0e283:f*a)+-1.0;break a}b=GF(1023-i|0,0,52)|0;d=C;if((i|0)<20){c[k>>2]=b;c[k+4>>2]=d;a=1.0-+h[k>>3]+(a-e)}else{c[k>>2]=b;c[k+4>>2]=d;a=a-(+h[k>>3]+e)+1.0}a=f*a;break a}}}while(0);return +a}function XE(a){a=+a;return +(+X(+(a+-1416.0996898839683))*2247116418577894884661631.0e283*2247116418577894884661631.0e283)}function YE(a){a=+a;var b=0;a=+N(+a);h[k>>3]=a;b=c[k+4>>2]|0;do if(b>>>0<1072049730)if(b>>>0<1045430272)a=1.0;else{a=+WE(a);a=a*a/((a+1.0)*2.0)+1.0}else if(b>>>0<1082535490){a=+X(+a);a=(a+1.0/a)*.5;break}else{a=+XE(a);break}while(0);return +a}function ZE(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;e=i;i=i+16|0;f=e;c[f>>2]=d;d=zE(a,b,f)|0;i=e;return d|0}function _E(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=i;i=i+32|0;f=e;g=e+16|0;c[g>>2]=d;h=(c[g>>2]|0)+(4-1)&~(4-1);d=c[h>>2]|0;c[g>>2]=h+4;c[f>>2]=a;c[f+4>>2]=b;c[f+8>>2]=d;d=UD(Ua(54,f|0)|0)|0;i=e;return d|0}function $E(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0;f=i;i=i+16|0;g=f;c[g>>2]=e;e=xE(a,b,d,g)|0;i=f;return e|0}function aF(a,b){a=a|0;b=b|0;bF(a,b)|0;return a|0}function bF(b,d){b=b|0;d=d|0;var e=0,f=0;e=d;a:do if(!((e^b)&3)){if(e&3)do{e=a[d>>0]|0;a[b>>0]=e;if(!(e<<24>>24))break a;d=d+1|0;b=b+1|0}while((d&3|0)!=0);e=c[d>>2]|0;if(!((e&-2139062144^-2139062144)&e+-16843009)){f=b;while(1){d=d+4|0;b=f+4|0;c[f>>2]=e;e=c[d>>2]|0;if((e&-2139062144^-2139062144)&e+-16843009|0)break;else f=b}}f=8}else f=8;while(0);if((f|0)==8){f=a[d>>0]|0;a[b>>0]=f;if(f<<24>>24)do{d=d+1|0;b=b+1|0;f=a[d>>0]|0;a[b>>0]=f}while(f<<24>>24!=0)}return b|0}function cF(b,c){b=b|0;c=c|0;b=NE(b,c)|0;return ((a[b>>0]|0)==(c&255)<<24>>24?b:0)|0}function dF(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;e=i;i=i+16|0;f=e;c[f>>2]=d;d=eF(a,b,f)|0;i=e;return d|0}function eF(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;g=i;i=i+112|0;e=g;f=e;h=f+112|0;do{c[f>>2]=0;f=f+4|0}while((f|0)<(h|0));c[e+32>>2]=5;c[e+44>>2]=a;c[e+76>>2]=-1;c[e+84>>2]=a;h=hF(e,b,d)|0;i=g;return h|0}function fF(a,b,c){a=a|0;b=b|0;c=c|0;return gF(a,b,c)|0}function gF(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0,g=0,h=0;e=a+84|0;g=c[e>>2]|0;h=d+256|0;f=FE(g,0,h)|0;f=(f|0)==0?h:f-g|0;d=f>>>0<d>>>0?f:d;KF(b|0,g|0,d|0)|0;c[a+4>>2]=g+d;b=g+f|0;c[a+8>>2]=b;c[e>>2]=b;return d|0}function hF(e,f,j){e=e|0;f=f|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0.0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0;P=i;i=i+304|0;H=P+16|0;J=P+8|0;I=P+33|0;K=P;y=P+32|0;if((c[e+76>>2]|0)>-1)O=LE(e)|0;else O=0;k=a[f>>0]|0;a:do if(!(k<<24>>24))k=0;else{L=e+4|0;M=e+100|0;G=e+108|0;z=e+8|0;A=I+10|0;B=I+33|0;D=J+4|0;E=I+46|0;F=I+94|0;m=k;k=0;n=f;s=0;l=0;f=0;b:while(1){c:do if(!($D(m&255)|0)){m=(a[n>>0]|0)==37;d:do if(m){q=n+1|0;o=a[q>>0]|0;e:do switch(o<<24>>24){case 37:break d;case 42:{x=0;o=n+2|0;break}default:{o=(o&255)+-48|0;if(o>>>0<10)if((a[n+2>>0]|0)==36){c[H>>2]=c[j>>2];while(1){x=(c[H>>2]|0)+(4-1)&~(4-1);m=c[x>>2]|0;c[H>>2]=x+4;if(o>>>0>1)o=o+-1|0;else break}x=m;o=n+3|0;break e}o=(c[j>>2]|0)+(4-1)&~(4-1);x=c[o>>2]|0;c[j>>2]=o+4;o=q}}while(0);m=a[o>>0]|0;n=m&255;if((n+-48|0)>>>0<10){m=0;while(1){q=(m*10|0)+-48+n|0;o=o+1|0;m=a[o>>0]|0;n=m&255;if((n+-48|0)>>>0>=10)break;else m=q}}else q=0;if(m<<24>>24==109){o=o+1|0;r=a[o>>0]|0;m=(x|0)!=0&1;l=0;f=0}else{r=m;m=0}n=o+1|0;switch(r&255|0){case 104:{w=(a[n>>0]|0)==104;n=w?o+2|0:n;o=w?-2:-1;break}case 108:{w=(a[n>>0]|0)==108;n=w?o+2|0:n;o=w?3:1;break}case 106:{o=3;break}case 116:case 122:{o=1;break}case 76:{o=2;break}case 110:case 112:case 67:case 83:case 91:case 99:case 115:case 88:case 71:case 70:case 69:case 65:case 103:case 102:case 101:case 97:case 120:case 117:case 111:case 105:case 100:{n=o;o=0;break}default:{N=154;break b}}r=d[n>>0]|0;t=(r&47|0)==3;r=t?r|32:r;t=t?1:o;switch(r|0){case 99:{w=s;v=(q|0)<1?1:q;break}case 91:{w=s;v=q;break}case 110:{if(!x){o=s;break c}switch(t|0){case -2:{a[x>>0]=s;o=s;break c}case -1:{b[x>>1]=s;o=s;break c}case 0:{c[x>>2]=s;o=s;break c}case 1:{c[x>>2]=s;o=s;break c}case 3:{o=x;c[o>>2]=s;c[o+4>>2]=((s|0)<0)<<31>>31;o=s;break c}default:{o=s;break c}}}default:{hE(e,0);do{o=c[L>>2]|0;if(o>>>0<(c[M>>2]|0)>>>0){c[L>>2]=o+1;o=d[o>>0]|0}else o=eE(e)|0}while(($D(o)|0)!=0);if(!(c[M>>2]|0))o=c[L>>2]|0;else{o=(c[L>>2]|0)+-1|0;c[L>>2]=o}w=(c[G>>2]|0)+s+o-(c[z>>2]|0)|0;v=q}}hE(e,v);o=c[L>>2]|0;q=c[M>>2]|0;if(o>>>0<q>>>0)c[L>>2]=o+1;else{if((eE(e)|0)<0){N=154;break b}q=c[M>>2]|0}if(q|0)c[L>>2]=(c[L>>2]|0)+-1;f:do switch(r|0){case 91:case 99:case 115:{u=(r|0)==99;g:do if((r|16|0)==115){FF(I|0,-1,257)|0;a[I>>0]=0;if((r|0)==115){a[B>>0]=0;a[A>>0]=0;a[A+1>>0]=0;a[A+2>>0]=0;a[A+3>>0]=0;a[A+4>>0]=0}}else{r=n+1|0;s=(a[r>>0]|0)==94;o=s&1;n=s?n+2|0:r;FF(I|0,s&1|0,257)|0;a[I>>0]=0;switch(a[n>>0]|0){case 45:{s=(o^1)&255;a[E>>0]=s;n=n+1|0;break}case 93:{s=(o^1)&255;a[F>>0]=s;n=n+1|0;break}default:s=(o^1)&255}while(1){o=a[n>>0]|0;h:do switch(o<<24>>24){case 0:{N=154;break b}case 93:break g;case 45:{r=n+1|0;o=a[r>>0]|0;switch(o<<24>>24){case 93:case 0:{o=45;break h}default:{}}n=a[n+-1>>0]|0;if((n&255)<(o&255)){n=n&255;do{n=n+1|0;a[I+n>>0]=s;o=a[r>>0]|0}while((n|0)<(o&255|0));n=r}else n=r;break}default:{}}while(0);a[I+((o&255)+1)>>0]=s;n=n+1|0}}while(0);r=u?v+1|0:31;s=(t|0)==1;t=(m|0)!=0;i:do if(s){if(t){f=wF(r<<2)|0;if(!f){l=0;N=154;break b}}else f=x;c[J>>2]=0;c[D>>2]=0;l=0;j:while(1){q=(f|0)==0;do{k:while(1){o=c[L>>2]|0;if(o>>>0<(c[M>>2]|0)>>>0){c[L>>2]=o+1;o=d[o>>0]|0}else o=eE(e)|0;if(!(a[I+(o+1)>>0]|0))break j;a[y>>0]=o;switch(iF(K,y,1,J)|0){case -1:{l=0;N=154;break b}case -2:break;default:break k}}if(!q){c[f+(l<<2)>>2]=c[K>>2];l=l+1|0}}while(!(t&(l|0)==(r|0)));l=r<<1|1;o=yF(f,l<<2)|0;if(!o){l=0;N=154;break b}else{q=r;r=l;f=o;l=q}}if(!(jF(J)|0)){l=0;N=154;break b}else{q=l;l=0}}else{if(t){l=wF(r)|0;if(!l){l=0;f=0;N=154;break b}else o=0;while(1){do{f=c[L>>2]|0;if(f>>>0<(c[M>>2]|0)>>>0){c[L>>2]=f+1;f=d[f>>0]|0}else f=eE(e)|0;if(!(a[I+(f+1)>>0]|0)){q=o;f=0;break i}a[l+o>>0]=f;o=o+1|0}while((o|0)!=(r|0));f=r<<1|1;o=yF(l,f)|0;if(!o){f=0;N=154;break b}else{q=r;r=f;l=o;o=q}}}if(!x){l=q;while(1){f=c[L>>2]|0;if(f>>>0<l>>>0){c[L>>2]=f+1;f=d[f>>0]|0}else f=eE(e)|0;if(!(a[I+(f+1)>>0]|0)){q=0;l=0;f=0;break i}l=c[M>>2]|0}}else{l=0;while(1){f=c[L>>2]|0;if(f>>>0<q>>>0){c[L>>2]=f+1;f=d[f>>0]|0}else f=eE(e)|0;if(!(a[I+(f+1)>>0]|0)){q=l;l=x;f=0;break i}a[x+l>>0]=f;q=c[M>>2]|0;l=l+1|0}}}while(0);if(!(c[M>>2]|0))o=c[L>>2]|0;else{o=(c[L>>2]|0)+-1|0;c[L>>2]=o}o=o-(c[z>>2]|0)+(c[G>>2]|0)|0;if(!o)break b;if(!((o|0)==(v|0)|u^1))break b;do if(t)if(s){c[x>>2]=f;break}else{c[x>>2]=l;break}while(0);if(!u){if(f|0)c[f+(q<<2)>>2]=0;if(!l){l=0;break f}a[l+q>>0]=0}break}case 120:case 88:case 112:{o=16;N=136;break}case 111:{o=8;N=136;break}case 117:case 100:{o=10;N=136;break}case 105:{o=0;N=136;break}case 71:case 103:case 70:case 102:case 69:case 101:case 65:case 97:{p=+iE(e,t,0);if((c[G>>2]|0)==((c[z>>2]|0)-(c[L>>2]|0)|0))break b;if(x)switch(t|0){case 0:{g[x>>2]=p;break f}case 1:{h[x>>3]=p;break f}case 2:{h[x>>3]=p;break f}default:break f}break}default:{}}while(0);l:do if((N|0)==136){N=0;o=dE(e,o,0,-1,-1)|0;if((c[G>>2]|0)==((c[z>>2]|0)-(c[L>>2]|0)|0))break b;if((x|0)!=0&(r|0)==112){c[x>>2]=o;break}if(x)switch(t|0){case -2:{a[x>>0]=o;break l}case -1:{b[x>>1]=o;break l}case 0:{c[x>>2]=o;break l}case 1:{c[x>>2]=o;break l}case 3:{v=x;c[v>>2]=o;c[v+4>>2]=C;break l}default:break l}}while(0);k=((x|0)!=0&1)+k|0;o=(c[G>>2]|0)+w+(c[L>>2]|0)-(c[z>>2]|0)|0;break c}while(0);n=n+(m&1)|0;hE(e,0);m=c[L>>2]|0;if(m>>>0<(c[M>>2]|0)>>>0){c[L>>2]=m+1;m=d[m>>0]|0}else m=eE(e)|0;if((m|0)!=(d[n>>0]|0)){N=22;break b}o=s+1|0}else{while(1){m=n+1|0;if(!($D(d[m>>0]|0)|0))break;else n=m}hE(e,0);do{m=c[L>>2]|0;if(m>>>0<(c[M>>2]|0)>>>0){c[L>>2]=m+1;m=d[m>>0]|0}else m=eE(e)|0}while(($D(m)|0)!=0);if(!(c[M>>2]|0))m=c[L>>2]|0;else{m=(c[L>>2]|0)+-1|0;c[L>>2]=m}o=(c[G>>2]|0)+s+m-(c[z>>2]|0)|0}while(0);n=n+1|0;m=a[n>>0]|0;if(!(m<<24>>24))break a;else s=o}if((N|0)==22){if(c[M>>2]|0)c[L>>2]=(c[L>>2]|0)+-1;if((k|0)!=0|(m|0)>-1)break;else{k=0;N=155}}else if((N|0)==154)if(!k){k=m;N=155}if((N|0)==155){m=k;k=-1}if(m){xF(l);xF(f)}}while(0);if(O|0)WD(e);i=P;return k|0}function iF(b,d,e,f){b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0;l=i;i=i+16|0;g=l;j=(f|0)==0?112316:f;f=c[j>>2]|0;a:do if(!d)if(!f)f=0;else k=15;else{h=(b|0)==0?g:b;if(!e)f=-2;else{if(!f){f=a[d>>0]|0;g=f&255;if(f<<24>>24>-1){c[h>>2]=g;f=f<<24>>24!=0&1;break}f=g+-194|0;if(f>>>0>50){k=15;break}f=c[43944+(f<<2)>>2]|0;g=e+-1|0;if(g){d=d+1|0;k=9}}else{g=e;k=9}b:do if((k|0)==9){b=a[d>>0]|0;m=(b&255)>>>3;if((m+-16|m+(f>>26))>>>0>7){k=15;break a}while(1){d=d+1|0;f=(b&255)+-128|f<<6;g=g+-1|0;if((f|0)>=0)break;if(!g)break b;b=a[d>>0]|0;if((b&-64)<<24>>24!=-128){k=15;break a}}c[j>>2]=0;c[h>>2]=f;f=e-g|0;break a}while(0);c[j>>2]=f;f=-2}}while(0);if((k|0)==15){c[j>>2]=0;c[(VD()|0)>>2]=84;f=-1}i=l;return f|0}function jF(a){a=a|0;if(!a)a=1;else a=(c[a>>2]|0)==0;return a&1|0}function kF(b,c,e){b=b|0;c=c|0;e=e|0;var f=0,g=0;if(!e)c=0;else{f=a[b>>0]|0;a:do if(!(f<<24>>24))f=0;else while(1){e=e+-1|0;g=a[c>>0]|0;if(!(f<<24>>24==g<<24>>24&((e|0)!=0&g<<24>>24!=0)))break a;b=b+1|0;c=c+1|0;f=a[b>>0]|0;if(!(f<<24>>24)){f=0;break}}while(0);c=(f&255)-(d[c>>0]|0)|0}return c|0}function lF(a,b){a=a|0;b=b|0;var d=0,e=0;d=i;i=i+16|0;e=d;c[e>>2]=b;b=((_E(a,21505,e)|0)!=0)<<31>>31;i=d;return b|0}function mF(b,e){b=b|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;w=i;i=i+1056|0;u=w+1024|0;v=w;k=a[e>>0]|0;do if(k<<24>>24){t=cF(b,k<<24>>24)|0;if(!t)b=0;else{j=a[e+1>>0]|0;if(!(j<<24>>24))b=t;else{f=t+1|0;m=a[f>>0]|0;if(!(m<<24>>24))b=0;else{h=a[e+2>>0]|0;if(!(h<<24>>24)){j=j&255|(k&255)<<8;b=m;g=d[t>>0]<<8|m&255;while(1){g=g&65535;if((g|0)==(j|0))break;f=f+1|0;h=a[f>>0]|0;if(!(h<<24>>24)){b=0;break}else{b=h;g=h&255|g<<8}}b=b<<24>>24?f+-1|0:0;break}f=t+2|0;b=a[f>>0]|0;if(!(b<<24>>24))b=0;else{g=a[e+3>>0]|0;if(!(g<<24>>24)){h=(j&255)<<16|(k&255)<<24|(h&255)<<8;g=(b&255)<<8|(m&255)<<16|d[t>>0]<<24;if((g|0)!=(h|0))do{f=f+1|0;b=a[f>>0]|0;g=(b&255|g)<<8}while(!(b<<24>>24==0|(g|0)==(h|0)));b=b<<24>>24?f+-2|0:0;break}f=t+3|0;l=a[f>>0]|0;if(!(l<<24>>24))b=0;else{if(!(a[e+4>>0]|0)){h=(j&255)<<16|(k&255)<<24|(h&255)<<8|g&255;b=(b&255)<<8|(m&255)<<16|l&255|d[t>>0]<<24;if((b|0)==(h|0))b=l;else{g=b;do{f=f+1|0;b=a[f>>0]|0;g=b&255|g<<8}while(!(b<<24>>24==0|(g|0)==(h|0)))}b=b<<24>>24?f+-3|0:0;break};c[u>>2]=0;c[u+4>>2]=0;c[u+8>>2]=0;c[u+12>>2]=0;c[u+16>>2]=0;c[u+20>>2]=0;c[u+24>>2]=0;c[u+28>>2]=0;b=k;g=0;while(1){if(!(a[t+g>>0]|0)){b=0;break}f=u+(((b&255)>>>5&255)<<2)|0;c[f>>2]=c[f>>2]|1<<(b&31);f=g+1|0;c[v+((b&255)<<2)>>2]=f;b=a[e+f>>0]|0;if(!(b<<24>>24)){n=23;break}else g=f}a:do if((n|0)==23){b:do if(f>>>0>1){h=1;n=-1;b=0;c:while(1){m=1;while(1){d:while(1){j=1;while(1){k=a[e+(j+n)>>0]|0;l=a[e+h>>0]|0;if(k<<24>>24!=l<<24>>24){m=h;j=k;h=l;break d}if((j|0)==(m|0))break;j=j+1|0;h=j+b|0;if(h>>>0>=f>>>0){b=n;p=m;break c}}b=b+m|0;h=b+1|0;if(h>>>0>=f>>>0){b=n;p=m;break c}}k=m-n|0;if((j&255)<=(h&255))break;b=m+1|0;if(b>>>0<f>>>0){h=b;b=m;m=k}else{b=n;p=k;break c}}h=b+2|0;if(h>>>0>=f>>>0){p=1;break}else{n=b;b=b+1|0}}j=1;k=-1;h=0;while(1){l=h;h=1;while(1){o=l;e:while(1){l=1;while(1){n=a[e+(l+k)>>0]|0;m=a[e+j>>0]|0;if(n<<24>>24!=m<<24>>24){l=j;j=o;break e}if((l|0)==(h|0))break;l=l+1|0;j=l+o|0;if(j>>>0>=f>>>0){j=p;break b}}o=o+h|0;j=o+1|0;if(j>>>0>=f>>>0){j=p;break b}}h=l-k|0;if((n&255)>=(m&255)){h=j;break}j=l+1|0;if(j>>>0>=f>>>0){j=p;break b}}j=h+2|0;if(j>>>0>=f>>>0){k=h;j=p;h=1;break}else{k=h;h=h+1|0}}}else{b=-1;k=-1;j=1;h=1}while(0);r=(k+1|0)>>>0>(b+1|0)>>>0;h=r?h:j;r=r?k:b;q=r+1|0;if(!(bE(e,e+h|0,q)|0))s=f-h|0;else{h=f-r+-1|0;s=0;h=(r>>>0>h>>>0?r:h)+1|0}n=f|63;o=(s|0)!=0;p=f-h|0;b=t;m=0;l=t;f:while(1){j=b;do if((l-j|0)>>>0<f>>>0){k=FE(l,0,n)|0;if(!k){k=l+n|0;break}else if((k-j|0)>>>0<f>>>0){b=0;break a}else break}else k=l;while(0);j=a[b+g>>0]|0;if(!(1<<(j&31)&c[u+(((j&255)>>>5&255)<<2)>>2])){b=b+f|0;m=0;l=k;continue}t=c[v+((j&255)<<2)>>2]|0;j=f-t|0;if((f|0)!=(t|0)){b=b+(o&(m|0)!=0&j>>>0<h>>>0?p:j)|0;m=0;l=k;continue}j=q>>>0>m>>>0?q:m;l=a[e+j>>0]|0;g:do if(!(l<<24>>24))j=q;else{while(1){if(l<<24>>24!=(a[b+j>>0]|0))break;j=j+1|0;l=a[e+j>>0]|0;if(!(l<<24>>24)){j=q;break g}}b=b+(j-r)|0;m=0;l=k;continue f}while(0);do{if(j>>>0<=m>>>0)break a;j=j+-1|0}while((a[e+j>>0]|0)==(a[b+j>>0]|0));b=b+h|0;m=s;l=k}}while(0)}}}}}}while(0);i=w;return b|0}function nF(a,b){a=a|0;b=b|0;return (oF(a,aE(a)|0,1,b)|0)+-1|0}function oF(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0;f=_(d,b)|0;if((c[e+76>>2]|0)>-1){g=(LE(e)|0)==0;a=BE(a,f,e)|0;if(!g)WD(e)}else a=BE(a,f,e)|0;if((a|0)!=(f|0))d=(a>>>0)/(b>>>0)|0;return d|0}function pF(a,b,d){a=a|0;b=b|0;d=d|0;var e=0,f=0;e=i;i=i+16|0;f=e;c[f>>2]=a;c[f+4>>2]=b;c[f+8>>2]=d;a=UD(Xa(3,f|0)|0)|0;i=e;return a|0}function qF(a){a=a|0;var b=0;b=i;i=i+64|0;a=(lF(a,b)|0)==0&1;i=b;return a|0}function rF(a){a=+a;var b=0,d=0,e=0,f=0.0,g=0.0;h[k>>3]=a;b=c[k+4>>2]|0;d=b&2147483647;do if(d>>>0>1083174911){b=(b|0)>-1|(b|0)==-1&(c[k>>2]|0)>>>0>4294967295;if(b&d>>>0>1083179007){a=a*8988465674311579538646525.0e283;break}if(d>>>0>2146435071){a=-1.0/a;break}else if(!(a<=-1075.0)|b){e=9;break}else{a=0.0;break}}else if(d>>>0<1016070144)a=a+1.0;else e=9;while(0);if((e|0)==9){g=a+26388279066624.0;h[k>>3]=g;e=(c[k>>2]|0)+128|0;d=e<<1&510;f=+h[19480+(d<<3)>>3];a=a-(g+-26388279066624.0)-+h[19480+((d|1)<<3)>>3];a=+kE(f+f*a*(a*(a*(a*(a*1.3333559164630223e-03+.009618129842126066)+.0555041086648214)+.2402265069591)+.6931471805599453),(e&-256|0)/256|0)}return +a}function sF(a){a=+a;var b=0,d=0;h[k>>3]=a;d=c[k+4>>2]|0;a=+N(+a);h[k>>3]=a;b=c[k+4>>2]|0;do if(b>>>0>1071748074)if(b>>>0>1077149696){a=1.0-0.0/a;break}else{a=1.0-2.0/(+WE(a*2.0)+2.0);break}else{if(b>>>0>1070618798){a=+WE(a*2.0);a=a/(a+2.0);break}if(b>>>0>1048575){a=+WE(a*-2.0);a=-a/(a+2.0)}}while(0);return +((d|0)<0?-a:a)}function tF(a){a=+a;var b=0,d=0;h[k>>3]=a;b=c[k+4>>2]|0;d=b&2146435072;if(!(d>>>0>1126170624|(d|0)==1126170624&0>0)){b=(b|0)<0;a=b?a+-4503599627370496.0+4503599627370496.0:a+4503599627370496.0+-4503599627370496.0;if(a==0.0)a=b?-0.0:0.0}return +a}function uF(a){a=+a;a=+tF(a);C=+N(a)>=1.0?(a>0.0?~~+$(+M(a/4294967296.0),4294967295.0)>>>0:~~+Z((a-+(~~a>>>0))/4294967296.0)>>>0):0;return ~~a>>>0|0}function vF(a){a=+a;var b=0,d=0,e=0,f=0;h[k>>3]=a;e=c[k>>2]|0;f=c[k+4>>2]|0;b=JF(e|0,f|0,52)|0;b=(b&2047)+-1011|0;if((b|0)<=63){d=(b|0)<12;b=JF(-1,-1,b|0)|0;b=d?-1:b;d=d?2147483647:C;if(!((b&e|0)==0&(d&f|0)==0)){c[k>>2]=e&~b;c[k+4>>2]=f&~d;a=+h[k>>3]}}return +a}function wF(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;do if(a>>>0<245){o=a>>>0<11?16:a+11&-8;a=o>>>3;j=c[28080]|0;b=j>>>a;if(b&3|0){b=(b&1^1)+a|0;d=112360+(b<<1<<2)|0;e=d+8|0;f=c[e>>2]|0;g=f+8|0;h=c[g>>2]|0;do if((d|0)==(h|0))c[28080]=j&~(1<<b);else{if(h>>>0<(c[28084]|0)>>>0)Ya();a=h+12|0;if((c[a>>2]|0)==(f|0)){c[a>>2]=d;c[e>>2]=h;break}else Ya()}while(0);A=b<<3;c[f+4>>2]=A|3;A=f+A+4|0;c[A>>2]=c[A>>2]|1;A=g;return A|0}h=c[28082]|0;if(o>>>0>h>>>0){if(b|0){d=2<<a;d=b<<a&(d|0-d);d=(d&0-d)+-1|0;i=d>>>12&16;d=d>>>i;f=d>>>5&8;d=d>>>f;g=d>>>2&4;d=d>>>g;e=d>>>1&2;d=d>>>e;b=d>>>1&1;b=(f|i|g|e|b)+(d>>>b)|0;d=112360+(b<<1<<2)|0;e=d+8|0;g=c[e>>2]|0;i=g+8|0;f=c[i>>2]|0;do if((d|0)==(f|0)){c[28080]=j&~(1<<b);k=h}else{if(f>>>0<(c[28084]|0)>>>0)Ya();a=f+12|0;if((c[a>>2]|0)==(g|0)){c[a>>2]=d;c[e>>2]=f;k=c[28082]|0;break}else Ya()}while(0);h=(b<<3)-o|0;c[g+4>>2]=o|3;e=g+o|0;c[e+4>>2]=h|1;c[e+h>>2]=h;if(k|0){f=c[28085]|0;b=k>>>3;d=112360+(b<<1<<2)|0;a=c[28080]|0;b=1<<b;if(!(a&b)){c[28080]=a|b;l=d+8|0;m=d}else{a=d+8|0;b=c[a>>2]|0;if(b>>>0<(c[28084]|0)>>>0)Ya();else{l=a;m=b}}c[l>>2]=f;c[m+12>>2]=f;c[f+8>>2]=m;c[f+12>>2]=d}c[28082]=h;c[28085]=e;A=i;return A|0}a=c[28081]|0;if(a){d=(a&0-a)+-1|0;z=d>>>12&16;d=d>>>z;y=d>>>5&8;d=d>>>y;A=d>>>2&4;d=d>>>A;b=d>>>1&2;d=d>>>b;e=d>>>1&1;e=c[112624+((y|z|A|b|e)+(d>>>e)<<2)>>2]|0;d=(c[e+4>>2]&-8)-o|0;b=e;while(1){a=c[b+16>>2]|0;if(!a){a=c[b+20>>2]|0;if(!a){j=e;break}}b=(c[a+4>>2]&-8)-o|0;A=b>>>0<d>>>0;d=A?b:d;b=a;e=A?a:e}g=c[28084]|0;if(j>>>0<g>>>0)Ya();i=j+o|0;if(j>>>0>=i>>>0)Ya();h=c[j+24>>2]|0;e=c[j+12>>2]|0;do if((e|0)==(j|0)){b=j+20|0;a=c[b>>2]|0;if(!a){b=j+16|0;a=c[b>>2]|0;if(!a){n=0;break}}while(1){e=a+20|0;f=c[e>>2]|0;if(f|0){a=f;b=e;continue}e=a+16|0;f=c[e>>2]|0;if(!f)break;else{a=f;b=e}}if(b>>>0<g>>>0)Ya();else{c[b>>2]=0;n=a;break}}else{f=c[j+8>>2]|0;if(f>>>0<g>>>0)Ya();a=f+12|0;if((c[a>>2]|0)!=(j|0))Ya();b=e+8|0;if((c[b>>2]|0)==(j|0)){c[a>>2]=e;c[b>>2]=f;n=e;break}else Ya()}while(0);do if(h|0){a=c[j+28>>2]|0;b=112624+(a<<2)|0;if((j|0)==(c[b>>2]|0)){c[b>>2]=n;if(!n){c[28081]=c[28081]&~(1<<a);break}}else{if(h>>>0<(c[28084]|0)>>>0)Ya();a=h+16|0;if((c[a>>2]|0)==(j|0))c[a>>2]=n;else c[h+20>>2]=n;if(!n)break}b=c[28084]|0;if(n>>>0<b>>>0)Ya();c[n+24>>2]=h;a=c[j+16>>2]|0;do if(a|0)if(a>>>0<b>>>0)Ya();else{c[n+16>>2]=a;c[a+24>>2]=n;break}while(0);a=c[j+20>>2]|0;if(a|0)if(a>>>0<(c[28084]|0)>>>0)Ya();else{c[n+20>>2]=a;c[a+24>>2]=n;break}}while(0);if(d>>>0<16){A=d+o|0;c[j+4>>2]=A|3;A=j+A+4|0;c[A>>2]=c[A>>2]|1}else{c[j+4>>2]=o|3;c[i+4>>2]=d|1;c[i+d>>2]=d;a=c[28082]|0;if(a|0){f=c[28085]|0;b=a>>>3;e=112360+(b<<1<<2)|0;a=c[28080]|0;b=1<<b;if(!(a&b)){c[28080]=a|b;p=e+8|0;q=e}else{a=e+8|0;b=c[a>>2]|0;if(b>>>0<(c[28084]|0)>>>0)Ya();else{p=a;q=b}}c[p>>2]=f;c[q+12>>2]=f;c[f+8>>2]=q;c[f+12>>2]=e}c[28082]=d;c[28085]=i}A=j+8|0;return A|0}}}else if(a>>>0>4294967231)o=-1;else{a=a+11|0;o=a&-8;j=c[28081]|0;if(j){d=0-o|0;a=a>>>8;if(!a)i=0;else if(o>>>0>16777215)i=31;else{q=(a+1048320|0)>>>16&8;u=a<<q;p=(u+520192|0)>>>16&4;u=u<<p;i=(u+245760|0)>>>16&2;i=14-(p|q|i)+(u<<i>>>15)|0;i=o>>>(i+7|0)&1|i<<1}b=c[112624+(i<<2)>>2]|0;a:do if(!b){a=0;b=0;u=86}else{f=d;a=0;g=o<<((i|0)==31?0:25-(i>>>1)|0);h=b;b=0;while(1){e=c[h+4>>2]&-8;d=e-o|0;if(d>>>0<f>>>0)if((e|0)==(o|0)){a=h;b=h;u=90;break a}else b=h;else d=f;e=c[h+20>>2]|0;h=c[h+16+(g>>>31<<2)>>2]|0;a=(e|0)==0|(e|0)==(h|0)?a:e;e=(h|0)==0;if(e){u=86;break}else{f=d;g=g<<(e&1^1)}}}while(0);if((u|0)==86){if((a|0)==0&(b|0)==0){a=2<<i;a=j&(a|0-a);if(!a)break;q=(a&0-a)+-1|0;m=q>>>12&16;q=q>>>m;l=q>>>5&8;q=q>>>l;n=q>>>2&4;q=q>>>n;p=q>>>1&2;q=q>>>p;a=q>>>1&1;a=c[112624+((l|m|n|p|a)+(q>>>a)<<2)>>2]|0}if(!a){h=d;j=b}else u=90}if((u|0)==90)while(1){u=0;q=(c[a+4>>2]&-8)-o|0;e=q>>>0<d>>>0;d=e?q:d;b=e?a:b;e=c[a+16>>2]|0;if(e|0){a=e;u=90;continue}a=c[a+20>>2]|0;if(!a){h=d;j=b;break}else u=90}if(j)if(h>>>0<((c[28082]|0)-o|0)>>>0){f=c[28084]|0;if(j>>>0<f>>>0)Ya();i=j+o|0;if(j>>>0>=i>>>0)Ya();g=c[j+24>>2]|0;d=c[j+12>>2]|0;do if((d|0)==(j|0)){b=j+20|0;a=c[b>>2]|0;if(!a){b=j+16|0;a=c[b>>2]|0;if(!a){r=0;break}}while(1){d=a+20|0;e=c[d>>2]|0;if(e|0){a=e;b=d;continue}d=a+16|0;e=c[d>>2]|0;if(!e)break;else{a=e;b=d}}if(b>>>0<f>>>0)Ya();else{c[b>>2]=0;r=a;break}}else{e=c[j+8>>2]|0;if(e>>>0<f>>>0)Ya();a=e+12|0;if((c[a>>2]|0)!=(j|0))Ya();b=d+8|0;if((c[b>>2]|0)==(j|0)){c[a>>2]=d;c[b>>2]=e;r=d;break}else Ya()}while(0);do if(g|0){a=c[j+28>>2]|0;b=112624+(a<<2)|0;if((j|0)==(c[b>>2]|0)){c[b>>2]=r;if(!r){c[28081]=c[28081]&~(1<<a);break}}else{if(g>>>0<(c[28084]|0)>>>0)Ya();a=g+16|0;if((c[a>>2]|0)==(j|0))c[a>>2]=r;else c[g+20>>2]=r;if(!r)break}b=c[28084]|0;if(r>>>0<b>>>0)Ya();c[r+24>>2]=g;a=c[j+16>>2]|0;do if(a|0)if(a>>>0<b>>>0)Ya();else{c[r+16>>2]=a;c[a+24>>2]=r;break}while(0);a=c[j+20>>2]|0;if(a|0)if(a>>>0<(c[28084]|0)>>>0)Ya();else{c[r+20>>2]=a;c[a+24>>2]=r;break}}while(0);do if(h>>>0<16){A=h+o|0;c[j+4>>2]=A|3;A=j+A+4|0;c[A>>2]=c[A>>2]|1}else{c[j+4>>2]=o|3;c[i+4>>2]=h|1;c[i+h>>2]=h;a=h>>>3;if(h>>>0<256){d=112360+(a<<1<<2)|0;b=c[28080]|0;a=1<<a;if(!(b&a)){c[28080]=b|a;s=d+8|0;t=d}else{a=d+8|0;b=c[a>>2]|0;if(b>>>0<(c[28084]|0)>>>0)Ya();else{s=a;t=b}}c[s>>2]=i;c[t+12>>2]=i;c[i+8>>2]=t;c[i+12>>2]=d;break}a=h>>>8;if(!a)d=0;else if(h>>>0>16777215)d=31;else{z=(a+1048320|0)>>>16&8;A=a<<z;y=(A+520192|0)>>>16&4;A=A<<y;d=(A+245760|0)>>>16&2;d=14-(y|z|d)+(A<<d>>>15)|0;d=h>>>(d+7|0)&1|d<<1}e=112624+(d<<2)|0;c[i+28>>2]=d;a=i+16|0;c[a+4>>2]=0;c[a>>2]=0;a=c[28081]|0;b=1<<d;if(!(a&b)){c[28081]=a|b;c[e>>2]=i;c[i+24>>2]=e;c[i+12>>2]=i;c[i+8>>2]=i;break}f=h<<((d|0)==31?0:25-(d>>>1)|0);a=c[e>>2]|0;while(1){if((c[a+4>>2]&-8|0)==(h|0)){d=a;u=148;break}b=a+16+(f>>>31<<2)|0;d=c[b>>2]|0;if(!d){u=145;break}else{f=f<<1;a=d}}if((u|0)==145)if(b>>>0<(c[28084]|0)>>>0)Ya();else{c[b>>2]=i;c[i+24>>2]=a;c[i+12>>2]=i;c[i+8>>2]=i;break}else if((u|0)==148){a=d+8|0;b=c[a>>2]|0;A=c[28084]|0;if(b>>>0>=A>>>0&d>>>0>=A>>>0){c[b+12>>2]=i;c[a>>2]=i;c[i+8>>2]=b;c[i+12>>2]=d;c[i+24>>2]=0;break}else Ya()}}while(0);A=j+8|0;return A|0}}}while(0);d=c[28082]|0;if(d>>>0>=o>>>0){a=d-o|0;b=c[28085]|0;if(a>>>0>15){A=b+o|0;c[28085]=A;c[28082]=a;c[A+4>>2]=a|1;c[A+a>>2]=a;c[b+4>>2]=o|3}else{c[28082]=0;c[28085]=0;c[b+4>>2]=d|3;A=b+d+4|0;c[A>>2]=c[A>>2]|1}A=b+8|0;return A|0}a=c[28083]|0;if(a>>>0>o>>>0){y=a-o|0;c[28083]=y;A=c[28086]|0;z=A+o|0;c[28086]=z;c[z+4>>2]=y|1;c[A+4>>2]=o|3;A=A+8|0;return A|0}do if(!(c[28198]|0)){a=Na(30)|0;if(!(a+-1&a)){c[28200]=a;c[28199]=a;c[28201]=-1;c[28202]=-1;c[28203]=0;c[28191]=0;c[28198]=(_a(0)|0)&-16^1431655768;break}else Ya()}while(0);h=o+48|0;g=c[28200]|0;i=o+47|0;f=g+i|0;g=0-g|0;j=f&g;if(j>>>0<=o>>>0){A=0;return A|0}a=c[28190]|0;if(a|0){s=c[28188]|0;t=s+j|0;if(t>>>0<=s>>>0|t>>>0>a>>>0){A=0;return A|0}}b:do if(!(c[28191]&4)){a=c[28086]|0;c:do if(!a)u=173;else{e=112768;while(1){b=c[e>>2]|0;if(b>>>0<=a>>>0){d=e+4|0;if((b+(c[d>>2]|0)|0)>>>0>a>>>0)break}e=c[e+8>>2]|0;if(!e){u=173;break c}}a=f-(c[28083]|0)&g;if(a>>>0<2147483647){b=Ka(a|0)|0;if((b|0)==((c[e>>2]|0)+(c[d>>2]|0)|0)){if((b|0)!=(-1|0)){f=b;h=a;u=193;break b}}else{e=b;u=183}}}while(0);do if((u|0)==173){f=Ka(0)|0;if((f|0)!=(-1|0)){a=f;b=c[28199]|0;d=b+-1|0;if(!(d&a))a=j;else a=j-a+(d+a&0-b)|0;b=c[28188]|0;d=b+a|0;if(a>>>0>o>>>0&a>>>0<2147483647){e=c[28190]|0;if(e|0)if(d>>>0<=b>>>0|d>>>0>e>>>0)break;b=Ka(a|0)|0;if((b|0)==(f|0)){h=a;u=193;break b}else{e=b;u=183}}}}while(0);d:do if((u|0)==183){d=0-a|0;do if(h>>>0>a>>>0&(a>>>0<2147483647&(e|0)!=(-1|0))){b=c[28200]|0;b=i-a+b&0-b;if(b>>>0<2147483647)if((Ka(b|0)|0)==(-1|0)){Ka(d|0)|0;break d}else{a=b+a|0;break}}while(0);if((e|0)!=(-1|0)){f=e;h=a;u=193;break b}}while(0);c[28191]=c[28191]|4;u=190}else u=190;while(0);if((u|0)==190)if(j>>>0<2147483647){b=Ka(j|0)|0;a=Ka(0)|0;if(b>>>0<a>>>0&((b|0)!=(-1|0)&(a|0)!=(-1|0))){a=a-b|0;if(a>>>0>(o+40|0)>>>0){f=b;h=a;u=193}}}if((u|0)==193){a=(c[28188]|0)+h|0;c[28188]=a;if(a>>>0>(c[28189]|0)>>>0)c[28189]=a;i=c[28086]|0;do if(!i){A=c[28084]|0;if((A|0)==0|f>>>0<A>>>0)c[28084]=f;c[28192]=f;c[28193]=h;c[28195]=0;c[28089]=c[28198];c[28088]=-1;a=0;do{A=112360+(a<<1<<2)|0;c[A+12>>2]=A;c[A+8>>2]=A;a=a+1|0}while((a|0)!=32);A=f+8|0;A=(A&7|0)==0?0:0-A&7;z=f+A|0;A=h+-40-A|0;c[28086]=z;c[28083]=A;c[z+4>>2]=A|1;c[z+A+4>>2]=40;c[28087]=c[28202]}else{b=112768;do{a=c[b>>2]|0;e=b+4|0;d=c[e>>2]|0;if((f|0)==(a+d|0)){u=203;break}b=c[b+8>>2]|0}while((b|0)!=0);if((u|0)==203)if(!(c[b+12>>2]&8))if(i>>>0<f>>>0&i>>>0>=a>>>0){c[e>>2]=d+h;A=i+8|0;A=(A&7|0)==0?0:0-A&7;z=i+A|0;A=h-A+(c[28083]|0)|0;c[28086]=z;c[28083]=A;c[z+4>>2]=A|1;c[z+A+4>>2]=40;c[28087]=c[28202];break}a=c[28084]|0;if(f>>>0<a>>>0){c[28084]=f;j=f}else j=a;d=f+h|0;a=112768;while(1){if((c[a>>2]|0)==(d|0)){b=a;u=211;break}a=c[a+8>>2]|0;if(!a){b=112768;break}}if((u|0)==211)if(!(c[a+12>>2]&8)){c[b>>2]=f;l=a+4|0;c[l>>2]=(c[l>>2]|0)+h;l=f+8|0;l=f+((l&7|0)==0?0:0-l&7)|0;a=d+8|0;a=d+((a&7|0)==0?0:0-a&7)|0;k=l+o|0;g=a-l-o|0;c[l+4>>2]=o|3;do if((a|0)==(i|0)){A=(c[28083]|0)+g|0;c[28083]=A;c[28086]=k;c[k+4>>2]=A|1}else{if((a|0)==(c[28085]|0)){A=(c[28082]|0)+g|0;c[28082]=A;c[28085]=k;c[k+4>>2]=A|1;c[k+A>>2]=A;break}b=c[a+4>>2]|0;if((b&3|0)==1){i=b&-8;f=b>>>3;e:do if(b>>>0<256){d=c[a+8>>2]|0;e=c[a+12>>2]|0;b=112360+(f<<1<<2)|0;do if((d|0)!=(b|0)){if(d>>>0<j>>>0)Ya();if((c[d+12>>2]|0)==(a|0))break;Ya()}while(0);if((e|0)==(d|0)){c[28080]=c[28080]&~(1<<f);break}do if((e|0)==(b|0))v=e+8|0;else{if(e>>>0<j>>>0)Ya();b=e+8|0;if((c[b>>2]|0)==(a|0)){v=b;break}Ya()}while(0);c[d+12>>2]=e;c[v>>2]=d}else{h=c[a+24>>2]|0;e=c[a+12>>2]|0;do if((e|0)==(a|0)){d=a+16|0;e=d+4|0;b=c[e>>2]|0;if(!b){b=c[d>>2]|0;if(!b){y=0;break}}else d=e;while(1){e=b+20|0;f=c[e>>2]|0;if(f|0){b=f;d=e;continue}e=b+16|0;f=c[e>>2]|0;if(!f)break;else{b=f;d=e}}if(d>>>0<j>>>0)Ya();else{c[d>>2]=0;y=b;break}}else{f=c[a+8>>2]|0;if(f>>>0<j>>>0)Ya();b=f+12|0;if((c[b>>2]|0)!=(a|0))Ya();d=e+8|0;if((c[d>>2]|0)==(a|0)){c[b>>2]=e;c[d>>2]=f;y=e;break}else Ya()}while(0);if(!h)break;b=c[a+28>>2]|0;d=112624+(b<<2)|0;do if((a|0)==(c[d>>2]|0)){c[d>>2]=y;if(y|0)break;c[28081]=c[28081]&~(1<<b);break e}else{if(h>>>0<(c[28084]|0)>>>0)Ya();b=h+16|0;if((c[b>>2]|0)==(a|0))c[b>>2]=y;else c[h+20>>2]=y;if(!y)break e}while(0);e=c[28084]|0;if(y>>>0<e>>>0)Ya();c[y+24>>2]=h;b=a+16|0;d=c[b>>2]|0;do if(d|0)if(d>>>0<e>>>0)Ya();else{c[y+16>>2]=d;c[d+24>>2]=y;break}while(0);b=c[b+4>>2]|0;if(!b)break;if(b>>>0<(c[28084]|0)>>>0)Ya();else{c[y+20>>2]=b;c[b+24>>2]=y;break}}while(0);a=a+i|0;g=i+g|0}a=a+4|0;c[a>>2]=c[a>>2]&-2;c[k+4>>2]=g|1;c[k+g>>2]=g;a=g>>>3;if(g>>>0<256){d=112360+(a<<1<<2)|0;b=c[28080]|0;a=1<<a;do if(!(b&a)){c[28080]=b|a;z=d+8|0;A=d}else{a=d+8|0;b=c[a>>2]|0;if(b>>>0>=(c[28084]|0)>>>0){z=a;A=b;break}Ya()}while(0);c[z>>2]=k;c[A+12>>2]=k;c[k+8>>2]=A;c[k+12>>2]=d;break}a=g>>>8;do if(!a)d=0;else{if(g>>>0>16777215){d=31;break}z=(a+1048320|0)>>>16&8;A=a<<z;y=(A+520192|0)>>>16&4;A=A<<y;d=(A+245760|0)>>>16&2;d=14-(y|z|d)+(A<<d>>>15)|0;d=g>>>(d+7|0)&1|d<<1}while(0);e=112624+(d<<2)|0;c[k+28>>2]=d;a=k+16|0;c[a+4>>2]=0;c[a>>2]=0;a=c[28081]|0;b=1<<d;if(!(a&b)){c[28081]=a|b;c[e>>2]=k;c[k+24>>2]=e;c[k+12>>2]=k;c[k+8>>2]=k;break}f=g<<((d|0)==31?0:25-(d>>>1)|0);a=c[e>>2]|0;while(1){if((c[a+4>>2]&-8|0)==(g|0)){d=a;u=281;break}b=a+16+(f>>>31<<2)|0;d=c[b>>2]|0;if(!d){u=278;break}else{f=f<<1;a=d}}if((u|0)==278)if(b>>>0<(c[28084]|0)>>>0)Ya();else{c[b>>2]=k;c[k+24>>2]=a;c[k+12>>2]=k;c[k+8>>2]=k;break}else if((u|0)==281){a=d+8|0;b=c[a>>2]|0;A=c[28084]|0;if(b>>>0>=A>>>0&d>>>0>=A>>>0){c[b+12>>2]=k;c[a>>2]=k;c[k+8>>2]=b;c[k+12>>2]=d;c[k+24>>2]=0;break}else Ya()}}while(0);A=l+8|0;return A|0}else b=112768;while(1){a=c[b>>2]|0;if(a>>>0<=i>>>0){a=a+(c[b+4>>2]|0)|0;if(a>>>0>i>>>0)break}b=c[b+8>>2]|0}g=a+-47|0;d=g+8|0;d=g+((d&7|0)==0?0:0-d&7)|0;g=i+16|0;d=d>>>0<g>>>0?i:d;b=d+8|0;e=f+8|0;e=(e&7|0)==0?0:0-e&7;A=f+e|0;e=h+-40-e|0;c[28086]=A;c[28083]=e;c[A+4>>2]=e|1;c[A+e+4>>2]=40;c[28087]=c[28202];e=d+4|0;c[e>>2]=27;c[b>>2]=c[28192];c[b+4>>2]=c[28193];c[b+8>>2]=c[28194];c[b+12>>2]=c[28195];c[28192]=f;c[28193]=h;c[28195]=0;c[28194]=b;b=d+24|0;do{b=b+4|0;c[b>>2]=7}while((b+4|0)>>>0<a>>>0);if((d|0)!=(i|0)){h=d-i|0;c[e>>2]=c[e>>2]&-2;c[i+4>>2]=h|1;c[d>>2]=h;a=h>>>3;if(h>>>0<256){d=112360+(a<<1<<2)|0;b=c[28080]|0;a=1<<a;if(!(b&a)){c[28080]=b|a;w=d+8|0;x=d}else{a=d+8|0;b=c[a>>2]|0;if(b>>>0<(c[28084]|0)>>>0)Ya();else{w=a;x=b}}c[w>>2]=i;c[x+12>>2]=i;c[i+8>>2]=x;c[i+12>>2]=d;break}a=h>>>8;if(!a)d=0;else if(h>>>0>16777215)d=31;else{z=(a+1048320|0)>>>16&8;A=a<<z;y=(A+520192|0)>>>16&4;A=A<<y;d=(A+245760|0)>>>16&2;d=14-(y|z|d)+(A<<d>>>15)|0;d=h>>>(d+7|0)&1|d<<1}f=112624+(d<<2)|0;c[i+28>>2]=d;c[i+20>>2]=0;c[g>>2]=0;a=c[28081]|0;b=1<<d;if(!(a&b)){c[28081]=a|b;c[f>>2]=i;c[i+24>>2]=f;c[i+12>>2]=i;c[i+8>>2]=i;break}e=h<<((d|0)==31?0:25-(d>>>1)|0);a=c[f>>2]|0;while(1){if((c[a+4>>2]&-8|0)==(h|0)){d=a;u=307;break}b=a+16+(e>>>31<<2)|0;d=c[b>>2]|0;if(!d){u=304;break}else{e=e<<1;a=d}}if((u|0)==304)if(b>>>0<(c[28084]|0)>>>0)Ya();else{c[b>>2]=i;c[i+24>>2]=a;c[i+12>>2]=i;c[i+8>>2]=i;break}else if((u|0)==307){a=d+8|0;b=c[a>>2]|0;A=c[28084]|0;if(b>>>0>=A>>>0&d>>>0>=A>>>0){c[b+12>>2]=i;c[a>>2]=i;c[i+8>>2]=b;c[i+12>>2]=d;c[i+24>>2]=0;break}else Ya()}}}while(0);a=c[28083]|0;if(a>>>0>o>>>0){y=a-o|0;c[28083]=y;A=c[28086]|0;z=A+o|0;c[28086]=z;c[z+4>>2]=y|1;c[A+4>>2]=o|3;A=A+8|0;return A|0}}c[(VD()|0)>>2]=12;A=0;return A|0}function xF(a){a=a|0;var b=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;if(!a)return;d=a+-8|0;h=c[28084]|0;if(d>>>0<h>>>0)Ya();a=c[a+-4>>2]|0;b=a&3;if((b|0)==1)Ya();e=a&-8;m=d+e|0;do if(!(a&1)){a=c[d>>2]|0;if(!b)return;k=d+(0-a)|0;j=a+e|0;if(k>>>0<h>>>0)Ya();if((k|0)==(c[28085]|0)){a=m+4|0;b=c[a>>2]|0;if((b&3|0)!=3){q=k;g=j;break}c[28082]=j;c[a>>2]=b&-2;c[k+4>>2]=j|1;c[k+j>>2]=j;return}e=a>>>3;if(a>>>0<256){b=c[k+8>>2]|0;d=c[k+12>>2]|0;a=112360+(e<<1<<2)|0;if((b|0)!=(a|0)){if(b>>>0<h>>>0)Ya();if((c[b+12>>2]|0)!=(k|0))Ya()}if((d|0)==(b|0)){c[28080]=c[28080]&~(1<<e);q=k;g=j;break}if((d|0)==(a|0))f=d+8|0;else{if(d>>>0<h>>>0)Ya();a=d+8|0;if((c[a>>2]|0)==(k|0))f=a;else Ya()}c[b+12>>2]=d;c[f>>2]=b;q=k;g=j;break}f=c[k+24>>2]|0;d=c[k+12>>2]|0;do if((d|0)==(k|0)){b=k+16|0;d=b+4|0;a=c[d>>2]|0;if(!a){a=c[b>>2]|0;if(!a){i=0;break}}else b=d;while(1){d=a+20|0;e=c[d>>2]|0;if(e|0){a=e;b=d;continue}d=a+16|0;e=c[d>>2]|0;if(!e)break;else{a=e;b=d}}if(b>>>0<h>>>0)Ya();else{c[b>>2]=0;i=a;break}}else{e=c[k+8>>2]|0;if(e>>>0<h>>>0)Ya();a=e+12|0;if((c[a>>2]|0)!=(k|0))Ya();b=d+8|0;if((c[b>>2]|0)==(k|0)){c[a>>2]=d;c[b>>2]=e;i=d;break}else Ya()}while(0);if(!f){q=k;g=j}else{a=c[k+28>>2]|0;b=112624+(a<<2)|0;if((k|0)==(c[b>>2]|0)){c[b>>2]=i;if(!i){c[28081]=c[28081]&~(1<<a);q=k;g=j;break}}else{if(f>>>0<(c[28084]|0)>>>0)Ya();a=f+16|0;if((c[a>>2]|0)==(k|0))c[a>>2]=i;else c[f+20>>2]=i;if(!i){q=k;g=j;break}}d=c[28084]|0;if(i>>>0<d>>>0)Ya();c[i+24>>2]=f;a=k+16|0;b=c[a>>2]|0;do if(b|0)if(b>>>0<d>>>0)Ya();else{c[i+16>>2]=b;c[b+24>>2]=i;break}while(0);a=c[a+4>>2]|0;if(!a){q=k;g=j}else if(a>>>0<(c[28084]|0)>>>0)Ya();else{c[i+20>>2]=a;c[a+24>>2]=i;q=k;g=j;break}}}else{q=d;g=e}while(0);if(q>>>0>=m>>>0)Ya();a=m+4|0;b=c[a>>2]|0;if(!(b&1))Ya();if(!(b&2)){if((m|0)==(c[28086]|0)){p=(c[28083]|0)+g|0;c[28083]=p;c[28086]=q;c[q+4>>2]=p|1;if((q|0)!=(c[28085]|0))return;c[28085]=0;c[28082]=0;return}if((m|0)==(c[28085]|0)){p=(c[28082]|0)+g|0;c[28082]=p;c[28085]=q;c[q+4>>2]=p|1;c[q+p>>2]=p;return}g=(b&-8)+g|0;e=b>>>3;do if(b>>>0<256){b=c[m+8>>2]|0;d=c[m+12>>2]|0;a=112360+(e<<1<<2)|0;if((b|0)!=(a|0)){if(b>>>0<(c[28084]|0)>>>0)Ya();if((c[b+12>>2]|0)!=(m|0))Ya()}if((d|0)==(b|0)){c[28080]=c[28080]&~(1<<e);break}if((d|0)==(a|0))l=d+8|0;else{if(d>>>0<(c[28084]|0)>>>0)Ya();a=d+8|0;if((c[a>>2]|0)==(m|0))l=a;else Ya()}c[b+12>>2]=d;c[l>>2]=b}else{f=c[m+24>>2]|0;a=c[m+12>>2]|0;do if((a|0)==(m|0)){b=m+16|0;d=b+4|0;a=c[d>>2]|0;if(!a){a=c[b>>2]|0;if(!a){n=0;break}}else b=d;while(1){d=a+20|0;e=c[d>>2]|0;if(e|0){a=e;b=d;continue}d=a+16|0;e=c[d>>2]|0;if(!e)break;else{a=e;b=d}}if(b>>>0<(c[28084]|0)>>>0)Ya();else{c[b>>2]=0;n=a;break}}else{b=c[m+8>>2]|0;if(b>>>0<(c[28084]|0)>>>0)Ya();d=b+12|0;if((c[d>>2]|0)!=(m|0))Ya();e=a+8|0;if((c[e>>2]|0)==(m|0)){c[d>>2]=a;c[e>>2]=b;n=a;break}else Ya()}while(0);if(f|0){a=c[m+28>>2]|0;b=112624+(a<<2)|0;if((m|0)==(c[b>>2]|0)){c[b>>2]=n;if(!n){c[28081]=c[28081]&~(1<<a);break}}else{if(f>>>0<(c[28084]|0)>>>0)Ya();a=f+16|0;if((c[a>>2]|0)==(m|0))c[a>>2]=n;else c[f+20>>2]=n;if(!n)break}d=c[28084]|0;if(n>>>0<d>>>0)Ya();c[n+24>>2]=f;a=m+16|0;b=c[a>>2]|0;do if(b|0)if(b>>>0<d>>>0)Ya();else{c[n+16>>2]=b;c[b+24>>2]=n;break}while(0);a=c[a+4>>2]|0;if(a|0)if(a>>>0<(c[28084]|0)>>>0)Ya();else{c[n+20>>2]=a;c[a+24>>2]=n;break}}}while(0);c[q+4>>2]=g|1;c[q+g>>2]=g;if((q|0)==(c[28085]|0)){c[28082]=g;return}}else{c[a>>2]=b&-2;c[q+4>>2]=g|1;c[q+g>>2]=g}a=g>>>3;if(g>>>0<256){d=112360+(a<<1<<2)|0;b=c[28080]|0;a=1<<a;if(!(b&a)){c[28080]=b|a;o=d+8|0;p=d}else{a=d+8|0;b=c[a>>2]|0;if(b>>>0<(c[28084]|0)>>>0)Ya();else{o=a;p=b}}c[o>>2]=q;c[p+12>>2]=q;c[q+8>>2]=p;c[q+12>>2]=d;return}a=g>>>8;if(!a)d=0;else if(g>>>0>16777215)d=31;else{o=(a+1048320|0)>>>16&8;p=a<<o;n=(p+520192|0)>>>16&4;p=p<<n;d=(p+245760|0)>>>16&2;d=14-(n|o|d)+(p<<d>>>15)|0;d=g>>>(d+7|0)&1|d<<1}e=112624+(d<<2)|0;c[q+28>>2]=d;c[q+20>>2]=0;c[q+16>>2]=0;a=c[28081]|0;b=1<<d;do if(!(a&b)){c[28081]=a|b;c[e>>2]=q;c[q+24>>2]=e;c[q+12>>2]=q;c[q+8>>2]=q}else{f=g<<((d|0)==31?0:25-(d>>>1)|0);a=c[e>>2]|0;while(1){if((c[a+4>>2]&-8|0)==(g|0)){d=a;e=130;break}b=a+16+(f>>>31<<2)|0;d=c[b>>2]|0;if(!d){e=127;break}else{f=f<<1;a=d}}if((e|0)==127)if(b>>>0<(c[28084]|0)>>>0)Ya();else{c[b>>2]=q;c[q+24>>2]=a;c[q+12>>2]=q;c[q+8>>2]=q;break}else if((e|0)==130){a=d+8|0;b=c[a>>2]|0;p=c[28084]|0;if(b>>>0>=p>>>0&d>>>0>=p>>>0){c[b+12>>2]=q;c[a>>2]=q;c[q+8>>2]=b;c[q+12>>2]=d;c[q+24>>2]=0;break}else Ya()}}while(0);q=(c[28088]|0)+-1|0;c[28088]=q;if(!q)a=112776;else return;while(1){a=c[a>>2]|0;if(!a)break;else a=a+8|0}c[28088]=-1;return}function yF(a,b){a=a|0;b=b|0;var d=0,e=0;if(!a){a=wF(b)|0;return a|0}if(b>>>0>4294967231){c[(VD()|0)>>2]=12;a=0;return a|0}d=zF(a+-8|0,b>>>0<11?16:b+11&-8)|0;if(d|0){a=d+8|0;return a|0}d=wF(b)|0;if(!d){a=0;return a|0}e=c[a+-4>>2]|0;e=(e&-8)-((e&3|0)==0?8:4)|0;KF(d|0,a|0,(e>>>0<b>>>0?e:b)|0)|0;xF(a);a=d;return a|0}function zF(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;n=a+4|0;o=c[n>>2]|0;d=o&-8;k=a+d|0;i=c[28084]|0;e=o&3;if(!((e|0)!=1&a>>>0>=i>>>0&a>>>0<k>>>0))Ya();f=c[k+4>>2]|0;if(!(f&1))Ya();if(!e){if(b>>>0<256){a=0;return a|0}if(d>>>0>=(b+4|0)>>>0)if((d-b|0)>>>0<=c[28200]<<1>>>0)return a|0;a=0;return a|0}if(d>>>0>=b>>>0){d=d-b|0;if(d>>>0<=15)return a|0;m=a+b|0;c[n>>2]=o&1|b|2;c[m+4>>2]=d|3;b=m+d+4|0;c[b>>2]=c[b>>2]|1;AF(m,d);return a|0}if((k|0)==(c[28086]|0)){d=(c[28083]|0)+d|0;if(d>>>0<=b>>>0){a=0;return a|0}m=d-b|0;l=a+b|0;c[n>>2]=o&1|b|2;c[l+4>>2]=m|1;c[28086]=l;c[28083]=m;return a|0}if((k|0)==(c[28085]|0)){e=(c[28082]|0)+d|0;if(e>>>0<b>>>0){a=0;return a|0}d=e-b|0;if(d>>>0>15){e=a+b|0;m=e+d|0;c[n>>2]=o&1|b|2;c[e+4>>2]=d|1;c[m>>2]=d;b=m+4|0;c[b>>2]=c[b>>2]&-2}else{c[n>>2]=o&1|e|2;e=a+e+4|0;c[e>>2]=c[e>>2]|1;e=0;d=0}c[28082]=d;c[28085]=e;return a|0}if(f&2|0){a=0;return a|0}l=(f&-8)+d|0;if(l>>>0<b>>>0){a=0;return a|0}m=l-b|0;g=f>>>3;do if(f>>>0<256){e=c[k+8>>2]|0;f=c[k+12>>2]|0;d=112360+(g<<1<<2)|0;if((e|0)!=(d|0)){if(e>>>0<i>>>0)Ya();if((c[e+12>>2]|0)!=(k|0))Ya()}if((f|0)==(e|0)){c[28080]=c[28080]&~(1<<g);break}if((f|0)==(d|0))h=f+8|0;else{if(f>>>0<i>>>0)Ya();d=f+8|0;if((c[d>>2]|0)==(k|0))h=d;else Ya()}c[e+12>>2]=f;c[h>>2]=e}else{h=c[k+24>>2]|0;f=c[k+12>>2]|0;do if((f|0)==(k|0)){e=k+16|0;f=e+4|0;d=c[f>>2]|0;if(!d){d=c[e>>2]|0;if(!d){j=0;break}}else e=f;while(1){f=d+20|0;g=c[f>>2]|0;if(g|0){d=g;e=f;continue}f=d+16|0;g=c[f>>2]|0;if(!g)break;else{d=g;e=f}}if(e>>>0<i>>>0)Ya();else{c[e>>2]=0;j=d;break}}else{g=c[k+8>>2]|0;if(g>>>0<i>>>0)Ya();d=g+12|0;if((c[d>>2]|0)!=(k|0))Ya();e=f+8|0;if((c[e>>2]|0)==(k|0)){c[d>>2]=f;c[e>>2]=g;j=f;break}else Ya()}while(0);if(h|0){d=c[k+28>>2]|0;e=112624+(d<<2)|0;if((k|0)==(c[e>>2]|0)){c[e>>2]=j;if(!j){c[28081]=c[28081]&~(1<<d);break}}else{if(h>>>0<(c[28084]|0)>>>0)Ya();d=h+16|0;if((c[d>>2]|0)==(k|0))c[d>>2]=j;else c[h+20>>2]=j;if(!j)break}f=c[28084]|0;if(j>>>0<f>>>0)Ya();c[j+24>>2]=h;d=k+16|0;e=c[d>>2]|0;do if(e|0)if(e>>>0<f>>>0)Ya();else{c[j+16>>2]=e;c[e+24>>2]=j;break}while(0);d=c[d+4>>2]|0;if(d|0)if(d>>>0<(c[28084]|0)>>>0)Ya();else{c[j+20>>2]=d;c[d+24>>2]=j;break}}}while(0);if(m>>>0<16){c[n>>2]=l|o&1|2;b=a+l+4|0;c[b>>2]=c[b>>2]|1;return a|0}else{l=a+b|0;c[n>>2]=o&1|b|2;c[l+4>>2]=m|3;b=l+m+4|0;c[b>>2]=c[b>>2]|1;AF(l,m);return a|0}return 0}function AF(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;o=a+b|0;d=c[a+4>>2]|0;do if(!(d&1)){f=c[a>>2]|0;if(!(d&3))return;l=a+(0-f)|0;k=f+b|0;i=c[28084]|0;if(l>>>0<i>>>0)Ya();if((l|0)==(c[28085]|0)){a=o+4|0;d=c[a>>2]|0;if((d&3|0)!=3){r=l;g=k;break}c[28082]=k;c[a>>2]=d&-2;c[l+4>>2]=k|1;c[l+k>>2]=k;return}e=f>>>3;if(f>>>0<256){a=c[l+8>>2]|0;b=c[l+12>>2]|0;d=112360+(e<<1<<2)|0;if((a|0)!=(d|0)){if(a>>>0<i>>>0)Ya();if((c[a+12>>2]|0)!=(l|0))Ya()}if((b|0)==(a|0)){c[28080]=c[28080]&~(1<<e);r=l;g=k;break}if((b|0)==(d|0))h=b+8|0;else{if(b>>>0<i>>>0)Ya();d=b+8|0;if((c[d>>2]|0)==(l|0))h=d;else Ya()}c[a+12>>2]=b;c[h>>2]=a;r=l;g=k;break}f=c[l+24>>2]|0;b=c[l+12>>2]|0;do if((b|0)==(l|0)){a=l+16|0;b=a+4|0;d=c[b>>2]|0;if(!d){d=c[a>>2]|0;if(!d){j=0;break}}else a=b;while(1){b=d+20|0;e=c[b>>2]|0;if(e|0){d=e;a=b;continue}b=d+16|0;e=c[b>>2]|0;if(!e)break;else{d=e;a=b}}if(a>>>0<i>>>0)Ya();else{c[a>>2]=0;j=d;break}}else{e=c[l+8>>2]|0;if(e>>>0<i>>>0)Ya();d=e+12|0;if((c[d>>2]|0)!=(l|0))Ya();a=b+8|0;if((c[a>>2]|0)==(l|0)){c[d>>2]=b;c[a>>2]=e;j=b;break}else Ya()}while(0);if(!f){r=l;g=k}else{d=c[l+28>>2]|0;a=112624+(d<<2)|0;if((l|0)==(c[a>>2]|0)){c[a>>2]=j;if(!j){c[28081]=c[28081]&~(1<<d);r=l;g=k;break}}else{if(f>>>0<(c[28084]|0)>>>0)Ya();d=f+16|0;if((c[d>>2]|0)==(l|0))c[d>>2]=j;else c[f+20>>2]=j;if(!j){r=l;g=k;break}}b=c[28084]|0;if(j>>>0<b>>>0)Ya();c[j+24>>2]=f;d=l+16|0;a=c[d>>2]|0;do if(a|0)if(a>>>0<b>>>0)Ya();else{c[j+16>>2]=a;c[a+24>>2]=j;break}while(0);d=c[d+4>>2]|0;if(!d){r=l;g=k}else if(d>>>0<(c[28084]|0)>>>0)Ya();else{c[j+20>>2]=d;c[d+24>>2]=j;r=l;g=k;break}}}else{r=a;g=b}while(0);h=c[28084]|0;if(o>>>0<h>>>0)Ya();d=o+4|0;a=c[d>>2]|0;if(!(a&2)){if((o|0)==(c[28086]|0)){q=(c[28083]|0)+g|0;c[28083]=q;c[28086]=r;c[r+4>>2]=q|1;if((r|0)!=(c[28085]|0))return;c[28085]=0;c[28082]=0;return}if((o|0)==(c[28085]|0)){q=(c[28082]|0)+g|0;c[28082]=q;c[28085]=r;c[r+4>>2]=q|1;c[r+q>>2]=q;return}g=(a&-8)+g|0;e=a>>>3;do if(a>>>0<256){a=c[o+8>>2]|0;b=c[o+12>>2]|0;d=112360+(e<<1<<2)|0;if((a|0)!=(d|0)){if(a>>>0<h>>>0)Ya();if((c[a+12>>2]|0)!=(o|0))Ya()}if((b|0)==(a|0)){c[28080]=c[28080]&~(1<<e);break}if((b|0)==(d|0))m=b+8|0;else{if(b>>>0<h>>>0)Ya();d=b+8|0;if((c[d>>2]|0)==(o|0))m=d;else Ya()}c[a+12>>2]=b;c[m>>2]=a}else{f=c[o+24>>2]|0;b=c[o+12>>2]|0;do if((b|0)==(o|0)){a=o+16|0;b=a+4|0;d=c[b>>2]|0;if(!d){d=c[a>>2]|0;if(!d){n=0;break}}else a=b;while(1){b=d+20|0;e=c[b>>2]|0;if(e|0){d=e;a=b;continue}b=d+16|0;e=c[b>>2]|0;if(!e)break;else{d=e;a=b}}if(a>>>0<h>>>0)Ya();else{c[a>>2]=0;n=d;break}}else{e=c[o+8>>2]|0;if(e>>>0<h>>>0)Ya();d=e+12|0;if((c[d>>2]|0)!=(o|0))Ya();a=b+8|0;if((c[a>>2]|0)==(o|0)){c[d>>2]=b;c[a>>2]=e;n=b;break}else Ya()}while(0);if(f|0){d=c[o+28>>2]|0;a=112624+(d<<2)|0;if((o|0)==(c[a>>2]|0)){c[a>>2]=n;if(!n){c[28081]=c[28081]&~(1<<d);break}}else{if(f>>>0<(c[28084]|0)>>>0)Ya();d=f+16|0;if((c[d>>2]|0)==(o|0))c[d>>2]=n;else c[f+20>>2]=n;if(!n)break}b=c[28084]|0;if(n>>>0<b>>>0)Ya();c[n+24>>2]=f;d=o+16|0;a=c[d>>2]|0;do if(a|0)if(a>>>0<b>>>0)Ya();else{c[n+16>>2]=a;c[a+24>>2]=n;break}while(0);d=c[d+4>>2]|0;if(d|0)if(d>>>0<(c[28084]|0)>>>0)Ya();else{c[n+20>>2]=d;c[d+24>>2]=n;break}}}while(0);c[r+4>>2]=g|1;c[r+g>>2]=g;if((r|0)==(c[28085]|0)){c[28082]=g;return}}else{c[d>>2]=a&-2;c[r+4>>2]=g|1;c[r+g>>2]=g}d=g>>>3;if(g>>>0<256){b=112360+(d<<1<<2)|0;a=c[28080]|0;d=1<<d;if(!(a&d)){c[28080]=a|d;p=b+8|0;q=b}else{d=b+8|0;a=c[d>>2]|0;if(a>>>0<(c[28084]|0)>>>0)Ya();else{p=d;q=a}}c[p>>2]=r;c[q+12>>2]=r;c[r+8>>2]=q;c[r+12>>2]=b;return}d=g>>>8;if(!d)b=0;else if(g>>>0>16777215)b=31;else{p=(d+1048320|0)>>>16&8;q=d<<p;o=(q+520192|0)>>>16&4;q=q<<o;b=(q+245760|0)>>>16&2;b=14-(o|p|b)+(q<<b>>>15)|0;b=g>>>(b+7|0)&1|b<<1}e=112624+(b<<2)|0;c[r+28>>2]=b;c[r+20>>2]=0;c[r+16>>2]=0;d=c[28081]|0;a=1<<b;if(!(d&a)){c[28081]=d|a;c[e>>2]=r;c[r+24>>2]=e;c[r+12>>2]=r;c[r+8>>2]=r;return}f=g<<((b|0)==31?0:25-(b>>>1)|0);d=c[e>>2]|0;while(1){if((c[d+4>>2]&-8|0)==(g|0)){b=d;e=127;break}a=d+16+(f>>>31<<2)|0;b=c[a>>2]|0;if(!b){e=124;break}else{f=f<<1;d=b}}if((e|0)==124){if(a>>>0<(c[28084]|0)>>>0)Ya();c[a>>2]=r;c[r+24>>2]=d;c[r+12>>2]=r;c[r+8>>2]=r;return}else if((e|0)==127){d=b+8|0;a=c[d>>2]|0;q=c[28084]|0;if(!(a>>>0>=q>>>0&b>>>0>=q>>>0))Ya();c[a+12>>2]=r;c[d>>2]=r;c[r+8>>2]=a;c[r+12>>2]=b;c[r+24>>2]=0;return}}function BF(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0;a=a>>>0<16?16:a;if(a+-1&a){d=16;while(1)if(d>>>0<a>>>0)d=d<<1;else{a=d;break}}if((-64-a|0)>>>0<=b>>>0){c[(VD()|0)>>2]=12;g=0;return g|0}g=b>>>0<11?16:b+11&-8;d=wF(g+12+a|0)|0;if(!d){g=0;return g|0}e=d+-8|0;do if(d&a+-1){b=(d+a+-1&0-a)+-8|0;f=e;b=(b-f|0)>>>0>15?b:b+a|0;f=b-f|0;a=d+-4|0;h=c[a>>2]|0;d=(h&-8)-f|0;if(!(h&3)){c[b>>2]=(c[e>>2]|0)+f;c[b+4>>2]=d;e=b;break}else{h=b+4|0;c[h>>2]=d|c[h>>2]&1|2;d=b+d+4|0;c[d>>2]=c[d>>2]|1;c[a>>2]=f|c[a>>2]&1|2;c[h>>2]=c[h>>2]|1;AF(e,f);e=b;break}}while(0);a=e+4|0;d=c[a>>2]|0;if(d&3|0){b=d&-8;if(b>>>0>(g+16|0)>>>0){h=b-g|0;f=e+g|0;c[a>>2]=g|d&1|2;c[f+4>>2]=h|3;g=f+h+4|0;c[g>>2]=c[g>>2]|1;AF(f,h)}}h=e+8|0;return h|0}function CF(a,b,d){a=a|0;b=b|0;d=d|0;var e=0;do if((b|0)==8)e=wF(d)|0;else{e=b>>>2;if((b&3|0)!=0|(e|0)==0){a=22;return a|0}if(e+1073741823&e|0){a=22;return a|0}if((-64-b|0)>>>0<d>>>0){a=12;return a|0}else{e=BF(b>>>0<16?16:b,d)|0;break}}while(0);if(!e){a=12;return a|0}c[a>>2]=e;a=0;return a|0}function DF(){}function EF(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;d=b-d-(c>>>0>a>>>0|0)>>>0;return (C=d,a-c>>>0|0)|0}function FF(b,d,e){b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0;f=b+e|0;if((e|0)>=20){d=d&255;h=b&3;i=d|d<<8|d<<16|d<<24;g=f&~3;if(h){h=b+4-h|0;while((b|0)<(h|0)){a[b>>0]=d;b=b+1|0}}while((b|0)<(g|0)){c[b>>2]=i;b=b+4|0}}while((b|0)<(f|0)){a[b>>0]=d;b=b+1|0}return b-e|0}function GF(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){C=b<<c|(a&(1<<c)-1<<32-c)>>>32-c;return a<<c}C=a<<c-32;return 0}function HF(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;c=a+c>>>0;return (C=b+d+(c>>>0<a>>>0|0)>>>0,c|0)|0}function IF(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){C=b>>c;return a>>>c|(b&(1<<c)-1)<<32-c}C=(b|0)<0?-1:0;return b>>c-32|0}function JF(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){C=b>>>c;return a>>>c|(b&(1<<c)-1)<<32-c}C=0;return b>>>c-32|0}function KF(b,d,e){b=b|0;d=d|0;e=e|0;var f=0;if((e|0)>=4096)return Ma(b|0,d|0,e|0)|0;f=b|0;if((b&3)==(d&3)){while(b&3){if(!e)return f|0;a[b>>0]=a[d>>0]|0;b=b+1|0;d=d+1|0;e=e-1|0}while((e|0)>=4){c[b>>2]=c[d>>2];b=b+4|0;d=d+4|0;e=e-4|0}}while((e|0)>0){a[b>>0]=a[d>>0]|0;b=b+1|0;d=d+1|0;e=e-1|0}return f|0}function LF(b,c,d){b=b|0;c=c|0;d=d|0;var e=0;if((c|0)<(b|0)&(b|0)<(c+d|0)){e=b;c=c+d|0;b=b+d|0;while((d|0)>0){b=b-1|0;c=c-1|0;d=d-1|0;a[b>>0]=a[c>>0]|0}b=e}else KF(b,c,d)|0;return b|0}function MF(a){a=a|0;return (a&255)<<24|(a>>8&255)<<16|(a>>16&255)<<8|a>>>24|0}function NF(b){b=b|0;var c=0;c=a[m+(b&255)>>0]|0;if((c|0)<8)return c|0;c=a[m+(b>>8&255)>>0]|0;if((c|0)<8)return c+8|0;c=a[m+(b>>16&255)>>0]|0;if((c|0)<8)return c+16|0;return (a[m+(b>>>24)>>0]|0)+24|0}function OF(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;f=a&65535;e=b&65535;c=_(e,f)|0;d=a>>>16;a=(c>>>16)+(_(e,d)|0)|0;e=b>>>16;b=_(e,f)|0;return (C=(a>>>16)+(_(e,d)|0)+(((a&65535)+b|0)>>>16)|0,a+b<<16|c&65535|0)|0}function PF(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;j=b>>31|((b|0)<0?-1:0)<<1;i=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;f=d>>31|((d|0)<0?-1:0)<<1;e=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;h=EF(j^a|0,i^b|0,j|0,i|0)|0;g=C;a=f^j;b=e^i;return EF((UF(h,g,EF(f^c|0,e^d|0,f|0,e|0)|0,C,0)|0)^a|0,C^b|0,a|0,b|0)|0}function QF(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,k=0,l=0;f=i;i=i+16|0;j=f|0;h=b>>31|((b|0)<0?-1:0)<<1;g=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;l=e>>31|((e|0)<0?-1:0)<<1;k=((e|0)<0?-1:0)>>31|((e|0)<0?-1:0)<<1;a=EF(h^a|0,g^b|0,h|0,g|0)|0;b=C;UF(a,b,EF(l^d|0,k^e|0,l|0,k|0)|0,C,j)|0;e=EF(c[j>>2]^h|0,c[j+4>>2]^g|0,h|0,g|0)|0;d=C;i=f;return (C=d,e)|0}function RF(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;e=a;f=c;c=OF(e,f)|0;a=C;return (C=(_(b,f)|0)+(_(d,e)|0)+a|a&0,c|0|0)|0}function SF(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return UF(a,b,c,d,0)|0}function TF(a,b,d,e){a=a|0;b=b|0;d=d|0;e=e|0;var f=0,g=0;g=i;i=i+16|0;f=g|0;UF(a,b,d,e,f)|0;i=g;return (C=c[f+4>>2]|0,c[f>>2]|0)|0}function UF(a,b,d,e,f){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;l=a;j=b;k=j;h=d;n=e;i=n;if(!k){g=(f|0)!=0;if(!i){if(g){c[f>>2]=(l>>>0)%(h>>>0);c[f+4>>2]=0}n=0;f=(l>>>0)/(h>>>0)>>>0;return (C=n,f)|0}else{if(!g){n=0;f=0;return (C=n,f)|0}c[f>>2]=a|0;c[f+4>>2]=b&0;n=0;f=0;return (C=n,f)|0}}g=(i|0)==0;do if(!h){if(g){if(f|0){c[f>>2]=(k>>>0)%(h>>>0);c[f+4>>2]=0}n=0;f=(k>>>0)/(h>>>0)>>>0;return (C=n,f)|0}if(!l){if(f|0){c[f>>2]=0;c[f+4>>2]=(k>>>0)%(i>>>0)}n=0;f=(k>>>0)/(i>>>0)>>>0;return (C=n,f)|0}g=i-1|0;if(!(g&i)){if(f|0){c[f>>2]=a|0;c[f+4>>2]=g&k|b&0}n=0;f=k>>>((NF(i|0)|0)>>>0);return (C=n,f)|0}g=(aa(i|0)|0)-(aa(k|0)|0)|0;if(g>>>0<=30){b=g+1|0;i=31-g|0;h=b;a=k<<i|l>>>(b>>>0);b=k>>>(b>>>0);g=0;i=l<<i;break}if(!f){n=0;f=0;return (C=n,f)|0}c[f>>2]=a|0;c[f+4>>2]=j|b&0;n=0;f=0;return (C=n,f)|0}else{if(!g){g=(aa(i|0)|0)-(aa(k|0)|0)|0;if(g>>>0<=31){m=g+1|0;i=31-g|0;b=g-31>>31;h=m;a=l>>>(m>>>0)&b|k<<i;b=k>>>(m>>>0)&b;g=0;i=l<<i;break}if(!f){n=0;f=0;return (C=n,f)|0}c[f>>2]=a|0;c[f+4>>2]=j|b&0;n=0;f=0;return (C=n,f)|0}g=h-1|0;if(g&h|0){i=(aa(h|0)|0)+33-(aa(k|0)|0)|0;p=64-i|0;m=32-i|0;j=m>>31;o=i-32|0;b=o>>31;h=i;a=m-1>>31&k>>>(o>>>0)|(k<<m|l>>>(i>>>0))&b;b=b&k>>>(i>>>0);g=l<<p&j;i=(k<<p|l>>>(o>>>0))&j|l<<m&i-33>>31;break}if(f|0){c[f>>2]=g&l;c[f+4>>2]=0}if((h|0)==1){o=j|b&0;p=a|0|0;return (C=o,p)|0}else{p=NF(h|0)|0;o=k>>>(p>>>0)|0;p=k<<32-p|l>>>(p>>>0)|0;return (C=o,p)|0}}while(0);if(!h){k=i;j=0;i=0}else{m=d|0|0;l=n|e&0;k=HF(m|0,l|0,-1,-1)|0;d=C;j=i;i=0;do{e=j;j=g>>>31|j<<1;g=i|g<<1;e=a<<1|e>>>31|0;n=a>>>31|b<<1|0;EF(k|0,d|0,e|0,n|0)|0;p=C;o=p>>31|((p|0)<0?-1:0)<<1;i=o&1;a=EF(e|0,n|0,o&m|0,(((p|0)<0?-1:0)>>31|((p|0)<0?-1:0)<<1)&l|0)|0;b=C;h=h-1|0}while((h|0)!=0);k=j;j=0}h=0;if(f|0){c[f>>2]=a;c[f+4>>2]=b}o=(g|0)>>>31|(k|h)<<1|(h<<1|g>>>31)&0|j;p=(g<<1|0>>>31)&-2|i;return (C=o,p)|0}function VF(a,b,c,d){a=a|0;b=b|0;c=+c;d=+d;return +db[a&0](b|0,+c,+d)}function WF(a,b,c,d,e,f,g,h,i,j,k){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;eb[a&3](b|0,c|0,d|0,e|0,f|0,g|0,h|0,i|0,j|0,k|0)}function XF(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;fb[a&127](b|0,c|0,d|0,e|0,f|0)}function YF(a,b,c){a=a|0;b=b|0;c=+c;return +gb[a&0](b|0,+c)}function ZF(a,b){a=a|0;b=b|0;hb[a&3](b|0)}function _F(a,b){a=a|0;b=+b;return +ib[a&15](+b)}function $F(a,b,c){a=a|0;b=b|0;c=c|0;jb[a&255](b|0,c|0)}function aG(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;return kb[a&1](b|0,c|0,d|0,e|0,f|0,g|0)|0}function bG(a,b,c,d,e,f,g,h,i,j){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;lb[a&1](b|0,c|0,d|0,e|0,f|0,g|0,h|0,i|0,j|0)}function cG(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return mb[a&7](b|0,c|0,d|0)|0}function dG(a,b){a=a|0;b=b|0;return nb[a&15](b|0)|0}function eG(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ob[a&1023](b|0,c|0,d|0)}function fG(a,b,c,d,e,f,g,h,i){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;pb[a&31](b|0,c|0,d|0,e|0,f|0,g|0,h|0,i|0)}function gG(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;return qb[a&1](b|0,c|0,d|0,e|0)|0}function hG(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;rb[a&63](b|0,c|0,d|0,e|0,f|0,g|0)}function iG(a,b,c){a=a|0;b=b|0;c=c|0;return sb[a&7](b|0,c|0)|0}function jG(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;return tb[a&63](b|0,c|0,d|0,e|0,f|0)|0}function kG(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ub[a&255](b|0,c|0,d|0,e|0)}function lG(a,b,c){a=a|0;b=+b;c=+c;ba(0);return 0.0}function mG(a,b,c,d,e,f,g,h,i,j){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;ba(1)}function nG(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ba(2)}function oG(a,b){a=a|0;b=+b;ba(3);return 0.0}function pG(a){a=a|0;ba(4)}function qG(a){a=+a;ba(5);return 0.0}function rG(a){a=+a;return +xa(+a)}function sG(a){a=+a;return +Ra(+a)}function tG(a){a=+a;return +Va(+a)}function uG(a){a=+a;return +La(+a)}function vG(a){a=+a;return +Fa(+a)}function wG(a){a=+a;return +Aa(+a)}function xG(a){a=+a;return +ya(+a)}function yG(a){a=+a;return +Ba(+a)}function zG(a){a=+a;return +va(+a)}function AG(a,b){a=a|0;b=b|0;ba(6)}function BG(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;ba(7);return 0}function CG(a,b,c,d,e,f,g,h,i){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;ba(8)}function DG(a,b,c){a=a|0;b=b|0;c=c|0;ba(9);return 0}function EG(a){a=a|0;ba(10);return 0}function FG(a,b,c){a=a|0;b=b|0;c=c|0;ba(11)}function GG(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;ba(12)}function HG(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ba(13);return 0}function IG(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;ba(14)}function JG(a,b){a=a|0;b=b|0;ba(15);return 0}function KG(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ba(16);return 0}function LG(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ba(17)}
function DA(a,b,e,f){a=a|0;b=b|0;e=e|0;f=f|0;var g=0,h=0,j=0,k=0,l=0,m=0,n=0,o=0;o=i;i=i+16|0;k=o;m=a+48680|0;if(!(BA(m,b+2+(e+-3>>31)+(e+-33>>31)+(a+48728)|0)|0)){c[f>>2]=0;a=0;i=o;return a|0}if(!(BA(m,b+3+(a+48728)|0)|0)){e=1;n=17}else if(!(BA(m,b+4+(a+48728)|0)|0)){e=2;n=17}else if(!(BA(m,b+5+(a+48728)|0)|0)){e=3;n=17}else{e=b+6+(a+48728)|0;if(!(BA(m,e)|0)){e=4;n=17}else if(!(BA(m,e)|0)){e=5;n=17}else if(!(BA(m,e)|0)){e=6;n=17}else if(!(BA(m,e)|0)){e=7;n=17}else if(!(BA(m,e)|0)){e=8;n=17}else{l=a+48696|0;j=a+48684|0;b=c[m>>2]|0;g=3;e=9;while(1){b=b<<1;c[m>>2]=b;if(!(b&65534)){h=c[l>>2]|0;b=b+-65535+((d[h+1>>0]|0)<<1|(d[h>>0]|0)<<9)|0;c[m>>2]=b;c[l>>2]=h+2}h=c[j>>2]<<17;if((b|0)<(h|0)){j=h;break}b=b-h|0;c[m>>2]=b;e=(1<<g)+e|0;if((g|0)>23){n=16;break}else g=g+1|0}if((n|0)==16){UC(c[(c[a>>2]|0)+4>>2]|0,16,96588,k);a=-2147483648;i=o;return a|0}do{g=g+-1|0;b=b<<1;c[m>>2]=b;if(!(b&65534)){k=c[l>>2]|0;b=b+-65535+((d[k+1>>0]|0)<<1|(d[k>>0]|0)<<9)|0;c[m>>2]=b;c[l>>2]=k+2}if((b|0)<(j|0))h=0;else{b=b-j|0;c[m>>2]=b;h=1}e=(h<<g)+e|0}while((g|0)!=0);b=m;g=e;e=(e|0)<70?e:70}}if((n|0)==17){b=m;g=e}c[f>>2]=e;e=c[b>>2]<<1;c[b>>2]=e;if(!(e&65534)){n=a+48696|0;f=c[n>>2]|0;e=e+-65535+((d[f+1>>0]|0)<<1|(d[f>>0]|0)<<9)|0;c[b>>2]=e;c[n>>2]=f+2}f=c[a+48684>>2]<<17;n=e-f|0;a=n>>31;c[b>>2]=(a&f)+n;a=(a^0-g)-a|0;i=o;return a|0}function EA(f,g,h,j,k,l,m){f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;m=m|0;var n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;B=i;i=i+256|0;x=B;y=g+48684|0;n=c[y>>2]|0;z=g+48680|0;p=c[z>>2]|0;A=g+48696|0;o=c[A>>2]|0;t=c[g+21796>>2]|0;s=(c[34060+(t*56|0)+(j<<2)>>2]|0)+(g+48728)|0;t=(c[34172+(t*56|0)+(j<<2)>>2]|0)+(g+48728)|0;w=(c[34284+(j<<2)>>2]|0)+(g+48728)|0;u=m+-1|0;a:do if((m|0)>1){r=0;m=0;while(1){C=s+m|0;q=d[C>>0]|0;F=d[188544+(q+(n<<1&384))>>0]|0;n=n-F|0;D=n<<17;E=D-p>>31;n=(E&F-n)+n|0;q=E^q;a[C>>0]=a[189184+q>>0]|0;C=d[188032+n>>0]|0;n=n<<C;p=p-(E&D)<<C;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(q&1))q=r;else{F=t+m|0;q=r+1|0;c[x+(r<<2)>>2]=m;r=d[F>>0]|0;C=d[188544+(r+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;r=D^r;a[F>>0]=a[189184+r>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(r&1){r=q;break a}}m=m+1|0;if((m|0)<(u|0))r=q;else{v=9;break}}}else{q=0;m=0;v=9}while(0);if((v|0)==9)if((m|0)==(u|0)){c[x+(q<<2)>>2]=u;r=q+1|0}else r=q;m=c[g+21772>>2]|0;q=c[f+197624>>2]|0;if((j|0)==3){F=q+(m<<1)|0;b[F>>1]=e[F>>1]|0|64<<k+-49}else{F=q+(m<<1)|0;b[F>>1]=e[F>>1]|0|256<<k+-48}a[(d[96537+k>>0]|0)+(g+43648)>>0]=r;if(!(c[f+25960>>2]|0)){j=r;m=0;do{F=w+(d[96654+m>>0]|0)|0;j=j+-1|0;u=d[l+(c[x+(j<<2)>>2]|0)>>0]|0;q=d[F>>0]|0;C=d[188544+(q+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;q=D^q;a[F>>0]=a[189184+q>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(q&1)){m=d[96662+m>>0]|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;b[h+(u<<1)>>1]=(E^65535)-E;p=(E&F)+p|0}else{t=w+(d[96678+m>>0]|0)|0;m=d[96670+m>>0]|0;s=a[t>>0]|0;q=2;while(1){r=s&255;F=d[188544+((n<<1&384)+r)>>0]|0;n=n-F|0;E=n<<17;D=E-p>>31;n=(D&F-n)+n|0;r=D^r;s=a[189184+r>>0]|0;a[t>>0]=s;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(r&1))break;q=q+1|0;if((q|0)>=15){v=53;break}}if((v|0)==53){s=n<<17;r=0;while(1){p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}if((p|0)<(s|0)){v=57;break}p=p-s|0;if((r|0)>=30){q=1;v=60;break}r=r+1|0}if((v|0)==57){v=0;if(!r)q=1;else{q=1;v=60}}if((v|0)==60)while(1){v=0;r=r+-1|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=(p|0)<(s|0);p=p-(F?0:s)|0;q=(F&1|q<<1)^1;if(!r)break;else v=60}q=q+14|0}p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;b[h+(u<<1)>>1]=(E^0-q)-E;p=(E&F)+p|0}}while((j|0)!=0);c[y>>2]=n;c[z>>2]=p;c[A>>2]=o;i=B;return}j=r;m=0;do{F=w+(d[96654+m>>0]|0)|0;j=j+-1|0;u=d[l+(c[x+(j<<2)>>2]|0)>>0]|0;q=d[F>>0]|0;C=d[188544+(q+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;q=D^q;a[F>>0]=a[189184+q>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(q&1)){m=d[96662+m>>0]|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;c[h+(u<<2)>>2]=~E-E;p=(E&F)+p|0}else{t=w+(d[96678+m>>0]|0)|0;m=d[96670+m>>0]|0;s=a[t>>0]|0;q=2;while(1){r=s&255;F=d[188544+((n<<1&384)+r)>>0]|0;n=n-F|0;E=n<<17;D=E-p>>31;n=(D&F-n)+n|0;r=D^r;s=a[189184+r>>0]|0;a[t>>0]=s;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(r&1))break;q=q+1|0;if((q|0)>=15){v=27;break}}if((v|0)==27){s=n<<17;r=0;while(1){p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}if((p|0)<(s|0)){v=31;break}p=p-s|0;if((r|0)>=30){q=1;v=34;break}r=r+1|0}if((v|0)==31){v=0;if(!r)q=1;else{q=1;v=34}}if((v|0)==34)while(1){v=0;r=r+-1|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=(p|0)<(s|0);p=p-(F?0:s)|0;q=(F&1|q<<1)^1;if(!r)break;else v=34}q=q+14|0}p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;c[h+(u<<2)>>2]=(E^0-q)-E;p=(E&F)+p|0}}while((j|0)!=0);c[y>>2]=n;c[z>>2]=p;c[A>>2]=o;i=B;return}function FA(e,f,g,h,j,k,l,m){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;m=m|0;var n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;B=i;i=i+256|0;x=B;y=f+48684|0;n=c[y>>2]|0;z=f+48680|0;p=c[z>>2]|0;A=f+48696|0;o=c[A>>2]|0;r=c[f+21796>>2]|0;s=(c[34060+(r*56|0)+(h<<2)>>2]|0)+(f+48728)|0;t=(c[34172+(r*56|0)+(h<<2)>>2]|0)+(f+48728)|0;w=(c[34284+(h<<2)>>2]|0)+(f+48728)|0;u=(m|0)==64;a:do if(u){q=0;m=0;while(1){C=s+(d[96694+(r*63|0)+m>>0]|0)|0;h=d[C>>0]|0;F=d[188544+(h+(n<<1&384))>>0]|0;n=n-F|0;D=n<<17;E=D-p>>31;n=(E&F-n)+n|0;h=E^h;a[C>>0]=a[189184+h>>0]|0;C=d[188032+n>>0]|0;n=n<<C;p=p-(E&D)<<C;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(h&1))h=q;else{F=t+(d[189312+m>>0]|0)|0;h=q+1|0;c[x+(q<<2)>>2]=m;q=d[F>>0]|0;C=d[188544+(q+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;q=D^q;a[F>>0]=a[189184+q>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(q&1){m=h;break a}}m=m+1|0;if((m|0)<63)q=h;else break}if((m|0)==63){c[x+(h<<2)>>2]=63;m=h+1|0}else m=h}else{r=m+-1|0;if((m|0)>1){q=0;h=0;while(1){F=s+h|0;m=d[F>>0]|0;C=d[188544+(m+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;m=D^m;a[F>>0]=a[189184+m>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(m&1))m=q;else{F=t+h|0;m=q+1|0;c[x+(q<<2)>>2]=h;q=d[F>>0]|0;C=d[188544+(q+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;q=D^q;a[F>>0]=a[189184+q>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(q&1)break a}h=h+1|0;if((h|0)<(r|0))q=m;else break}}else{m=0;h=0}if((h|0)==(r|0)){c[x+(m<<2)>>2]=r;m=m+1|0}}while(0);h=a[96537+j>>0]|0;if(u){F=(h&255)+(f+43648)|0;E=m*257&65535;b[F>>1]=E;b[F+8>>1]=E}else a[(h&255)+(f+43648)>>0]=m;if(!(c[e+25960>>2]|0)){u=m;h=0;do{F=w+(d[96654+h>>0]|0)|0;u=u+-1|0;t=d[k+(c[x+(u<<2)>>2]|0)>>0]|0;m=d[F>>0]|0;C=d[188544+(m+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;m=D^m;a[F>>0]=a[189184+m>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(m&1)){h=d[96662+h>>0]|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;b[g+(t<<1)>>1]=((p>>>31|32)+(E^0-(c[l+(t<<2)>>2]|0))|0)>>>6;p=(E&F)+p|0}else{s=w+(d[96678+h>>0]|0)|0;h=d[96670+h>>0]|0;r=a[s>>0]|0;m=2;while(1){q=r&255;F=d[188544+((n<<1&384)+q)>>0]|0;n=n-F|0;E=n<<17;D=E-p>>31;n=(D&F-n)+n|0;q=D^q;r=a[189184+q>>0]|0;a[s>>0]=r;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(q&1))break;m=m+1|0;if((m|0)>=15){v=63;break}}if((v|0)==63){r=n<<17;q=0;while(1){p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}if((p|0)<(r|0)){v=67;break}p=p-r|0;if((q|0)>=30){m=1;v=70;break}q=q+1|0}if((v|0)==67){v=0;if(!q)m=1;else{m=1;v=70}}if((v|0)==70)while(1){v=0;q=q+-1|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=(p|0)<(r|0);p=p-(F?0:r)|0;m=(F&1|m<<1)^1;if(!q)break;else v=70}m=m+14|0}p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;b[g+(t<<1)>>1]=((_((E^0-m)-E|0,c[l+(t<<2)>>2]|0)|0)+32|0)>>>6;p=(E&F)+p|0}}while((u|0)!=0);c[y>>2]=n;c[z>>2]=p;c[A>>2]=o;i=B;return}u=m;h=0;do{F=w+(d[96654+h>>0]|0)|0;u=u+-1|0;t=d[k+(c[x+(u<<2)>>2]|0)>>0]|0;m=d[F>>0]|0;C=d[188544+(m+(n<<1&384))>>0]|0;n=n-C|0;E=n<<17;D=E-p>>31;n=(D&C-n)+n|0;m=D^m;a[F>>0]=a[189184+m>>0]|0;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(m&1)){h=d[96662+h>>0]|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;c[g+(t<<2)>>2]=(p>>>31|32)+(E^0-(c[l+(t<<2)>>2]|0))>>6;p=(E&F)+p|0}else{s=w+(d[96678+h>>0]|0)|0;h=d[96670+h>>0]|0;r=a[s>>0]|0;m=2;while(1){q=r&255;F=d[188544+((n<<1&384)+q)>>0]|0;n=n-F|0;E=n<<17;D=E-p>>31;n=(D&F-n)+n|0;q=D^q;r=a[189184+q>>0]|0;a[s>>0]=r;F=d[188032+n>>0]|0;n=n<<F;p=p-(D&E)<<F;if(!(p&65535)){p=(((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)+-65535<<7-(d[188032+((p+-1^p)>>>15)>>0]|0))+p|0;o=o+2|0}if(!(q&1))break;m=m+1|0;if((m|0)>=15){v=37;break}}if((v|0)==37){r=n<<17;q=0;while(1){p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}if((p|0)<(r|0)){v=41;break}p=p-r|0;if((q|0)>=30){m=1;v=44;break}q=q+1|0}if((v|0)==41){v=0;if(!q)m=1;else{m=1;v=44}}if((v|0)==44)while(1){v=0;q=q+-1|0;p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=(p|0)<(r|0);p=p-(F?0:r)|0;m=(F&1|m<<1)^1;if(!q)break;else v=44}m=m+14|0}p=p<<1;if(!(p&65534)){p=p+-65535+((d[o+1>>0]|0)<<1|(d[o>>0]|0)<<9)|0;o=o+2|0}F=n<<17;p=p-F|0;E=p>>31;c[g+(t<<2)>>2]=(_((E^0-m)-E|0,c[l+(t<<2)>>2]|0)|0)+32>>6;p=(E&F)+p|0}}while((u|0)!=0);c[y>>2]=n;c[z>>2]=p;c[A>>2]=o;i=B;return}function GA(f,g,h,j){f=f|0;g=g|0;h=h|0;j=j|0;var k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;z=i;i=i+256|0;v=z;w=g+48684|0;x=g+48680|0;y=g+48696|0;s=c[g+21796>>2]|0;r=(c[34060+(s*56|0)+12>>2]|0)+(g+48728)|0;s=(c[34172+(s*56|0)+12>>2]|0)+(g+48728)|0;u=g+48985|0;m=c[x>>2]|0;l=c[y>>2]|0;k=c[w>>2]|0;q=0;o=0;while(1){p=d[96820+o>>0]|0;A=r+p|0;n=d[A>>0]|0;D=d[188544+(n+(k<<1&384))>>0]|0;k=k-D|0;B=k<<17;C=B-m>>31;k=(C&D-k)+k|0;n=C^n;a[A>>0]=a[189184+n>>0]|0;A=d[188032+k>>0]|0;k=k<<A;m=m-(C&B)<<A;if(!(m&65535)){m=(((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)+-65535<<7-(d[188032+((m+-1^m)>>>15)>>0]|0))+m|0;l=l+2|0}if(!(n&1))n=q;else{D=s+p|0;n=q+1|0;c[v+(q<<2)>>2]=o;p=d[D>>0]|0;A=d[188544+(p+(k<<1&384))>>0]|0;k=k-A|0;C=k<<17;B=C-m>>31;k=(B&A-k)+k|0;p=B^p;a[D>>0]=a[189184+p>>0]|0;D=d[188032+k>>0]|0;k=k<<D;m=m-(B&C)<<D;if(!(m&65535)){m=(((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)+-65535<<7-(d[188032+((m+-1^m)>>>15)>>0]|0))+m|0;l=l+2|0}if(p&1)break}o=o+1|0;if((o|0)<7)q=n;else{t=9;break}}if((t|0)==9)if((o|0)==7){c[v+(n<<2)>>2]=7;n=n+1|0}D=(c[f+197624>>2]|0)+(c[g+21772>>2]<<1)|0;b[D>>1]=e[D>>1]|0|64<<j+-49;a[(d[96537+j>>0]|0)+(g+43648)>>0]=n;if(!(c[f+25960>>2]|0)){f=n;n=0;do{D=u+(d[96654+n>>0]|0)|0;f=f+-1|0;s=d[96827+(c[v+(f<<2)>>2]|0)>>0]|0;o=d[D>>0]|0;A=d[188544+(o+(k<<1&384))>>0]|0;k=k-A|0;C=k<<17;B=C-m>>31;k=(B&A-k)+k|0;o=B^o;a[D>>0]=a[189184+o>>0]|0;D=d[188032+k>>0]|0;k=k<<D;m=m-(B&C)<<D;if(!(m&65535)){m=(((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)+-65535<<7-(d[188032+((m+-1^m)>>>15)>>0]|0))+m|0;l=l+2|0}if(!(o&1)){n=d[96662+n>>0]|0;m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}D=k<<17;m=m-D|0;C=m>>31;b[h+(s<<1)>>1]=(C^65535)-C;m=(C&D)+m|0}else{r=u+(d[96686+n>>0]|0)|0;n=d[96670+n>>0]|0;q=a[r>>0]|0;o=2;while(1){p=q&255;D=d[188544+((k<<1&384)+p)>>0]|0;k=k-D|0;C=k<<17;B=C-m>>31;k=(B&D-k)+k|0;p=B^p;q=a[189184+p>>0]|0;a[r>>0]=q;D=d[188032+k>>0]|0;k=k<<D;m=m-(B&C)<<D;if(!(m&65535)){m=(((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)+-65535<<7-(d[188032+((m+-1^m)>>>15)>>0]|0))+m|0;l=l+2|0}if(!(p&1))break;o=o+1|0;if((o|0)>=15){t=50;break}}if((t|0)==50){q=k<<17;p=0;while(1){m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}if((m|0)<(q|0)){t=54;break}m=m-q|0;if((p|0)>=30){o=1;t=57;break}p=p+1|0}if((t|0)==54){t=0;if(!p)o=1;else{o=1;t=57}}if((t|0)==57)while(1){t=0;p=p+-1|0;m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}D=(m|0)<(q|0);m=m-(D?0:q)|0;o=(D&1|o<<1)^1;if(!p)break;else t=57}o=o+14|0}m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}D=k<<17;m=m-D|0;C=m>>31;b[h+(s<<1)>>1]=(C^0-o)-C;m=(C&D)+m|0}}while((f|0)!=0);c[w>>2]=k;c[x>>2]=m;c[y>>2]=l;i=z;return}f=n;n=0;do{D=u+(d[96654+n>>0]|0)|0;f=f+-1|0;s=d[96827+(c[v+(f<<2)>>2]|0)>>0]|0;o=d[D>>0]|0;A=d[188544+(o+(k<<1&384))>>0]|0;k=k-A|0;C=k<<17;B=C-m>>31;k=(B&A-k)+k|0;o=B^o;a[D>>0]=a[189184+o>>0]|0;D=d[188032+k>>0]|0;k=k<<D;m=m-(B&C)<<D;if(!(m&65535)){m=(((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)+-65535<<7-(d[188032+((m+-1^m)>>>15)>>0]|0))+m|0;l=l+2|0}if(!(o&1)){n=d[96662+n>>0]|0;m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}D=k<<17;m=m-D|0;C=m>>31;c[h+(s<<2)>>2]=~C-C;m=(C&D)+m|0}else{r=u+(d[96686+n>>0]|0)|0;n=d[96670+n>>0]|0;q=a[r>>0]|0;o=2;while(1){p=q&255;D=d[188544+((k<<1&384)+p)>>0]|0;k=k-D|0;C=k<<17;B=C-m>>31;k=(B&D-k)+k|0;p=B^p;q=a[189184+p>>0]|0;a[r>>0]=q;D=d[188032+k>>0]|0;k=k<<D;m=m-(B&C)<<D;if(!(m&65535)){m=(((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)+-65535<<7-(d[188032+((m+-1^m)>>>15)>>0]|0))+m|0;l=l+2|0}if(!(p&1))break;o=o+1|0;if((o|0)>=15){t=24;break}}if((t|0)==24){q=k<<17;p=0;while(1){m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}if((m|0)<(q|0)){t=28;break}m=m-q|0;if((p|0)>=30){o=1;t=31;break}p=p+1|0}if((t|0)==28){t=0;if(!p)o=1;else{o=1;t=31}}if((t|0)==31)while(1){t=0;p=p+-1|0;m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}D=(m|0)<(q|0);m=m-(D?0:q)|0;o=(D&1|o<<1)^1;if(!p)break;else t=31}o=o+14|0}m=m<<1;if(!(m&65534)){m=m+-65535+((d[l+1>>0]|0)<<1|(d[l>>0]|0)<<9)|0;l=l+2|0}D=k<<17;m=m-D|0;C=m>>31;c[h+(s<<2)>>2]=(C^0-o)-C;m=(C&D)+m|0}}while((f|0)!=0);c[w>>2]=k;c[x>>2]=m;c[y>>2]=l;i=z;return}function HA(e,f,g,h,j,k,l,m,n){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;var o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0;q=0;F=i;i=i+80|0;p=c[e+96>>2]|0;if(p|0)if(!(c[e+28588>>2]|0)){A=c[e+26672>>2]|0;v=c[f+21772>>2]|0;u=c[f+21716>>2]|0;x=c[f+21708>>2]|0;C=(_(c[e+27844>>2]|0,-6)|0)+48|0;E=(c[f+848>>2]|0)+52+C|0;C=(c[f+852>>2]|0)+52+C|0;w=c[(c[e+24668>>2]|0)+(v<<2)>>2]|0;D=c[e+24644>>2]|0;z=a[D+v>>0]|0;v=a[D+(v+-1)>>0]|0;D=a[D+(c[f+21688>>2]|0)>>0]|0;B=d[e+28412+z>>0]|0;r=(z+1|0)+v>>1;y=D+(z+1|0)>>1;v=((B+1|0)+(d[e+28412+v>>0]|0)|0)>>>1;D=((d[e+28412+D>>0]|0)+(B+1|0)|0)>>>1;o=1;if(o)if(!(c[e+25960>>2]|0)){if(w&7){f=(c[e+197612>>2]|0)!=3?44584:44592;if(u)if(!((r+E|0)>>>0<68|(r+C|0)>>>0<68))ub[c[e+64>>2]&255](j,m,d[96916+(r+E|0)>>0]|0,d[97072+(r+C|0)>>0]|0);p=(z+E|0)>>>0<68|(z+C|0)>>>0<68;if(w&16777216){if(!p){t=d[(97072+(z+C|0)|0)>>0]|0;s=d[(96916+(z+E|0)|0)>>0]|0;FF(F+72|0|0,a[97228+((z+E|0)<<2)+3>>0]|0,4)|0;fb[c[e+52>>2]&127](j+8|0,m,s,t,F+72|0)}do if(x|0){g=d[96916+(y+E|0)>>0]|0;h=d[97072+(y+C|0)>>0]|0;if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[e+60>>2]&255](j,m,g,h);break}else{a[(F+72|0)>>0]=a[(o<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[f+2>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[f+4>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[f+6>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,g,h,F+72|0);break}}}while(0);if(!p){y=d[(97072+(z+C|0)|0)>>0]|0;t=d[(96916+(z+E|0)|0)>>0]|0;FF(F+72|0|0,a[97228+((z+E|0)<<2)+3>>0]|0,4)|0;fb[c[e+48>>2]&127](j+(m<<3)|0,m,t,y,F+72|0)}}else{if(!p){t=d[(97072+(z+C|0)|0)>>0]|0;s=d[(96916+(z+E|0)|0)>>0]|0;r=a[97228+((z+E|0)<<2)+3>>0]|0;FF(F+72|0|0,r|0,4)|0;fb[c[(e+52|0)>>2]&127](j+4|0,m,s,t,F+72|0);FF(F+72|0|0,r|0,4)|0;fb[c[(e+52|0)>>2]&127](j+8|0,m,s,t,F+72|0);FF(F+72|0|0,r|0,4)|0;fb[c[(e+52|0)>>2]&127](j+12|0,m,s,t,F+72|0)}do if(x|0){g=d[96916+(y+E|0)>>0]|0;h=d[97072+(y+C|0)>>0]|0;if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[e+60>>2]&255](j,m,g,h);break}else{a[(F+72|0)>>0]=a[(o<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[f+2>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[f+4>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[f+6>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,g,h,F+72|0);break}}}while(0);if(!p){y=d[(97072+(z+C|0)|0)>>0]|0;t=d[(96916+(z+E|0)|0)>>0]|0;z=a[97228+((z+E|0)<<2)+3>>0]|0;FF(F+72|0|0,z|0,4)|0;fb[c[(e+48|0)>>2]&127](j+(m<<2)|0,m,t,y,F+72|0);FF(F+72|0|0,z|0,4)|0;fb[c[(e+48|0)>>2]&127](j+(m<<3)|0,m,t,y,F+72|0);FF(F+72|0|0,z|0,4)|0;fb[c[(e+48|0)>>2]&127](j+(m*12|0)|0,m,t,y,F+72|0)}}if(!A){i=F;return}if((A|0)==3){if(u)if(!((v+E|0)>>>0<68|(v+C|0)>>>0<68)){n=d[97072+(v+C|0)>>0]|0;A=d[96916+(v+E|0)>>0]|0;ub[c[(e+64|0)>>2]&255](k,m,A,n);ub[c[(e+64|0)>>2]&255](l,m,A,n)}p=(B+E|0)>>>0<68|(B+C|0)>>>0<68;if(w&16777216){if(!p){n=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+8|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+8|0,m,A,n,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+60|0)>>2]&255](k,m,g,h);ub[c[(e+60|0)>>2]&255](l,m,g,h);break}else{j=a[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)>>0]=j;A=a[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=A;n=a[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=n;D=a[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](k,m,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=n;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](l,m,g,h,F+72|0);break}}}while(0);if(p){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,C,D,F+72|0);i=F;return}else{if(!p){n=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+4|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+4|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+8|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+8|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+12|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+12|0,m,A,n,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+60|0)>>2]&255](k,m,g,h);ub[c[(e+60|0)>>2]&255](l,m,g,h);break}else{j=a[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)>>0]=j;A=a[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=A;n=a[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=n;D=a[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](k,m,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=n;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](l,m,g,h,F+72|0);break}}}while(0);if(p){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m<<2)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m<<2)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m*12|0)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m*12|0)|0,m,C,D,F+72|0);i=F;return}}if((A|0)==2){if(u)if(!((v+E|0)>>>0<68|(v+C|0)>>>0<68)){m=d[97072+(v+C|0)>>0]|0;A=d[96916+(v+E|0)>>0]|0;ub[c[(e+88|0)>>2]&255](k,n,A,m);ub[c[(e+88|0)>>2]&255](l,n,A,m)}if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){m=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](k+4|0,n,A,m,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](l+4|0,n,A,m,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+84|0)>>2]&255](k,n,g,h);ub[c[(e+84|0)>>2]&255](l,n,g,h);break}else{j=(d[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[(F+72|0)>>0]=j;A=(d[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+1|0)>>0]=A;m=(d[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+2|0)>>0]=m;D=(d[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](k,n,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=m;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](l,n,g,h,F+72|0);break}}}while(0);if((B+E|0)>>>0<68|(B+C|0)>>>0<68){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n<<3)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n<<3)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n*12|0)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n*12|0)|0,n,C,D,F+72|0);i=F;return}else{if(u)if(!((v+E|0)>>>0<68|(v+C|0)>>>0<68)){m=d[97072+(v+C|0)>>0]|0;A=d[96916+(v+E|0)>>0]|0;ub[c[(e+88|0)>>2]&255](k,n,A,m);ub[c[(e+88|0)>>2]&255](l,n,A,m)}if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){m=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](k+4|0,n,A,m,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](l+4|0,n,A,m,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+84|0)>>2]&255](k,n,g,h);ub[c[(e+84|0)>>2]&255](l,n,g,h);break}else{j=(d[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[(F+72|0)>>0]=j;A=(d[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+1|0)>>0]=A;m=(d[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+2|0)>>0]=m;D=(d[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](k,n,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=m;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](l,n,g,h,F+72|0);break}}}while(0);if((B+E|0)>>>0<68|(B+C|0)>>>0<68){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,C,D,F+72|0);i=F;return}}s=F+7&-8;g=c[f+21824>>2]|0;if(w&16777216)if((A|0)==3|(g&7|0)!=7)q=311;else{c[(F+7&-8)>>2]=131074;c[(F+7&-8)+4>>2]=131074;c[(s+16|0)>>2]=131074;c[(s+16|0)+4>>2]=131074;c[(s+32|0)>>2]=131074;c[(s+32|0)+4>>2]=131074;c[(s+48|0)>>2]=131074;c[(s+48|0)+4>>2]=131074;t=4}else q=311;if((q|0)==311){eb[p&3](s,f+43648|0,f+44096|0,f+43776|0,(c[f+23380>>2]|0)==2&1,4-(0-((g&15|0)==0&w>>>3)&3)|0,(w>>24)+1|0,0-((u*5|0)>>>5&1&(0-((w*5|0)>>>5&1)&3|w>>4)>>>1)&3,0-((w*5|0)>>>5&1)&3|w>>4,(c[e+197612>>2]|0)!=3&1);t=4-(0-((g&15|0)==0&w>>>3)&3)|0}if(u&7|0){c[(F+7&-8)>>2]=262148;c[(F+7&-8)+4>>2]=262148}if(x&7|0){q=(c[e+197612>>2]|0)!=3;c[(s+32|0)>>2]=q?196611:262148;c[(s+32|0)+4>>2]=q?196611:262148}do if(u|0)if(!((c[(F+7&-8)>>2]|0)==0&(c[(F+7&-8)+4>>2]|0)==0)){g=d[96916+(r+E|0)>>0]|0;h=d[97072+(r+C|0)>>0]|0;do if(!((r+E|0)>>>0<68|(r+C|0)>>>0<68)){o=b[s>>1]|0;if(o<<16>>16>3){ub[c[e+64>>2]&255](j,m,g,h);break}else{a[(F+72|0)>>0]=a[(o<<16>>16)+(97228+((r+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+2>>1]|0)+(97228+((r+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+4>>1]|0)+(97228+((r+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+6>>1]|0)+(97228+((r+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j,m,g,h,F+72|0);break}}while(0);if(A){h=d[96916+(v+E|0)>>0]|0;o=d[97072+(v+C|0)>>0]|0;if((A|0)==3){if((v+E|0)>>>0<68|(v+C|0)>>>0<68)break;g=b[s>>1]|0;if(g<<16>>16>3)ub[c[e+64>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](k,m,h,o,F+72|0)}g=b[s>>1]|0;if(g<<16>>16>3){ub[c[e+64>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](l,m,h,o,F+72|0);break}}else{if((v+E|0)>>>0<68|(v+C|0)>>>0<68)break;g=b[s>>1]|0;if(g<<16>>16>3)ub[c[e+88>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;fb[c[e+76>>2]&127](k,n,h,o,F+72|0)}g=b[s>>1]|0;if(g<<16>>16>3){ub[c[e+88>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;fb[c[e+76>>2]&127](l,n,h,o,F+72|0);break}}}}while(0);do{p=1;if(p){p=0;if((t|0)==1){if(x|0){g=c[(s+32|0)>>2]|0;if(!((g|0)==0&(c[(s+32|0)+4>>2]|0)==0)){h=d[96916+(y+E|0)>>0]|0;o=d[97072+(y+C|0)>>0]|0;do if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68))if((g&65535)<<16>>16>3){ub[c[e+60>>2]&255](j,m,h,o);break}else{a[(F+72|0)>>0]=a[((g&65535)<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,h,o,F+72|0);break}while(0);if(A){h=d[96916+(D+E|0)>>0]|0;o=d[97072+(D+C|0)>>0]|0;if((A|0)==3){if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(s+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+60>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](k,m,h,o,F+72|0)}g=b[(s+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+60>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](l,m,h,o,F+72|0);break}}else{if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(s+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+84>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](k,n,h,o,F+72|0)}g=b[(s+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+84>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](l,n,h,o,F+72|0);break}}}}}}else p=1}if(p)if(1){if(w&16777216){g=c[(s+16|0)>>2]|0;do if(!((g|0)==0&(c[(s+16|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+18>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+20>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+22>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+8|0,m,v,w,F+72|0)}if(A)if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+8|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+8|0,m,v,w,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](k+4|0,n,v,w,F+72|0);a[(F+72|0)>>0]=(d[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](l+4|0,n,v,w,F+72|0);break}}while(0);do if(x|0){g=c[(s+32|0)>>2]|0;if(!((g|0)==0&(c[(s+32|0)+4>>2]|0)==0)){h=d[96916+(y+E|0)>>0]|0;o=d[97072+(y+C|0)>>0]|0;do if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68))if((g&65535)<<16>>16>3){ub[c[e+60>>2]&255](j,m,h,o);break}else{a[(F+72|0)>>0]=a[((g&65535)<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,h,o,F+72|0);break}while(0);if(A){h=d[96916+(D+E|0)>>0]|0;o=d[97072+(D+C|0)>>0]|0;if((A|0)==3){if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(s+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+60>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](k,m,h,o,F+72|0)}g=b[(s+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+60>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](l,m,h,o,F+72|0);break}}else{if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(s+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+84>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](k,n,h,o,F+72|0)}g=b[(s+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+84>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](l,n,h,o,F+72|0);break}}}}}while(0);g=c[(s+48|0)>>2]|0;if((g|0)==0&(c[(s+48|0)+4>>2]|0)==0)break;if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;y=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+50>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+52>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+54>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m<<3)|0,m,y,D,F+72|0)}if(!A)break;if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;C=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,C,D,F+72|0);a[(F+72|0)>>0]=a[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,C,D,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;C=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,C,D,F+72|0);a[(F+72|0)>>0]=(d[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,C,D,F+72|0);break}}g=c[(s+8|0)>>2]|0;if(!((g|0)==0&(c[(s+8|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+10>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+12>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+14>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+4|0,m,v,w,F+72|0)}if((A|0)==3)if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+8|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+10|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+12|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+14|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+4|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(s+8|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+10|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+12|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+14|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+4|0,m,v,w,F+72|0)}}g=c[(s+16|0)>>2]|0;do if(!((g|0)==0&(c[(s+16|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+18>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+20>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+22>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+8|0,m,v,w,F+72|0)}if(A)if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+8|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+8|0,m,v,w,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](k+4|0,n,v,w,F+72|0);a[(F+72|0)>>0]=(d[(b[(s+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](l+4|0,n,v,w,F+72|0);break}}while(0);g=c[(s+24|0)>>2]|0;if(!((g|0)==0&(c[(s+24|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+26>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+28>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+30>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+12|0,m,v,w,F+72|0)}if((A|0)==3)if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+24|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+26|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+28|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+30|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+12|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(s+24|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+26|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+28|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+30|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+12|0,m,v,w,F+72|0)}}do if(x|0){g=c[(s+32|0)>>2]|0;if(!((g|0)==0&(c[(s+32|0)+4>>2]|0)==0)){h=d[96916+(y+E|0)>>0]|0;o=d[97072+(y+C|0)>>0]|0;do if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68))if((g&65535)<<16>>16>3){ub[c[e+60>>2]&255](j,m,h,o);break}else{a[(F+72|0)>>0]=a[((g&65535)<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,h,o,F+72|0);break}while(0);if(!A)break;h=d[96916+(D+E|0)>>0]|0;o=d[97072+(D+C|0)>>0]|0;if((A|0)==3){if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(s+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+60>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](k,m,h,o,F+72|0)}g=b[(s+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+60>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](l,m,h,o,F+72|0);break}}else{if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(s+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+84>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](k,n,h,o,F+72|0)}g=b[(s+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+84>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[s+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[s+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[s+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](l,n,h,o,F+72|0);break}}}}while(0);g=c[(s+40|0)>>2]|0;do if(!((g|0)==0&(c[(s+40|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;y=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+42>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+44>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+46>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m<<2)|0,m,y,D,F+72|0)}if((A|0)!=3)break;if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;y=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+40|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+42|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+44|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+46|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m<<2)|0,m,y,D,F+72|0);a[(F+72|0)>>0]=a[(b[(s+40|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+42|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+44|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+46|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m<<2)|0,m,y,D,F+72|0)}while(0);g=c[(s+48|0)>>2]|0;do if(!((g|0)==0&(c[(s+48|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;y=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+50>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+52>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+54>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m<<3)|0,m,y,D,F+72|0)}if(!A)break;if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;n=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,n,D,F+72|0);a[(F+72|0)>>0]=a[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,n,D,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;y=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,y,D,F+72|0);a[(F+72|0)>>0]=(d[(b[(s+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(s+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(s+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(s+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,y,D,F+72|0);break}}while(0);g=c[(s+56|0)>>2]|0;if(!((g|0)==0&(c[(s+56|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;n=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[s+58>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[s+60>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[s+62>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m*12|0)|0,m,n,D,F+72|0)}if((A|0)!=3)break;if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;C=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(s+56|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+58|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+60|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+62|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m*12|0)|0,m,C,D,F+72|0);a[(F+72|0)>>0]=a[(b[(s+56|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(s+58|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(s+60|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(s+62|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m*12|0)|0,m,C,D,F+72|0)}}}while(0);i=F;return}else o=1;if(o)if(1){if(w&7){f=(c[e+197612>>2]|0)!=3?44584:44592;if(u)if(!((r+E|0)>>>0<68|(r+C|0)>>>0<68))ub[c[e+64>>2]&255](j,m,d[96916+(r+E|0)>>0]|0,d[97072+(r+C|0)>>0]|0);p=(z+E|0)>>>0<68|(z+C|0)>>>0<68;if(w&16777216){if(!p){t=d[(97072+(z+C|0)|0)>>0]|0;s=d[(96916+(z+E|0)|0)>>0]|0;FF(F+72|0|0,a[97228+((z+E|0)<<2)+3>>0]|0,4)|0;fb[c[e+52>>2]&127](j+16|0,m,s,t,F+72|0)}do if(x|0){g=d[96916+(y+E|0)>>0]|0;h=d[97072+(y+C|0)>>0]|0;if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[e+60>>2]&255](j,m,g,h);break}else{a[(F+72|0)>>0]=a[(o<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[f+2>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[f+4>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[f+6>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,g,h,F+72|0);break}}}while(0);if(!p){y=d[(97072+(z+C|0)|0)>>0]|0;t=d[(96916+(z+E|0)|0)>>0]|0;FF(F+72|0|0,a[97228+((z+E|0)<<2)+3>>0]|0,4)|0;fb[c[e+48>>2]&127](j+(m<<3)|0,m,t,y,F+72|0)}}else{if(!p){t=d[(97072+(z+C|0)|0)>>0]|0;s=d[(96916+(z+E|0)|0)>>0]|0;r=a[97228+((z+E|0)<<2)+3>>0]|0;FF(F+72|0|0,r|0,4)|0;fb[c[(e+52|0)>>2]&127](j+8|0,m,s,t,F+72|0);FF(F+72|0|0,r|0,4)|0;fb[c[(e+52|0)>>2]&127](j+16|0,m,s,t,F+72|0);FF(F+72|0|0,r|0,4)|0;fb[c[(e+52|0)>>2]&127](j+24|0,m,s,t,F+72|0)}do if(x|0){g=d[96916+(y+E|0)>>0]|0;h=d[97072+(y+C|0)>>0]|0;if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[e+60>>2]&255](j,m,g,h);break}else{a[(F+72|0)>>0]=a[(o<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[f+2>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[f+4>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[f+6>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,g,h,F+72|0);break}}}while(0);if(!p){y=d[(97072+(z+C|0)|0)>>0]|0;t=d[(96916+(z+E|0)|0)>>0]|0;z=a[97228+((z+E|0)<<2)+3>>0]|0;FF(F+72|0|0,z|0,4)|0;fb[c[(e+48|0)>>2]&127](j+(m<<2)|0,m,t,y,F+72|0);FF(F+72|0|0,z|0,4)|0;fb[c[(e+48|0)>>2]&127](j+(m<<3)|0,m,t,y,F+72|0);FF(F+72|0|0,z|0,4)|0;fb[c[(e+48|0)>>2]&127](j+(m*12|0)|0,m,t,y,F+72|0)}}if(!A){i=F;return}if((A|0)==3){if(u)if(!((v+E|0)>>>0<68|(v+C|0)>>>0<68)){n=d[97072+(v+C|0)>>0]|0;A=d[96916+(v+E|0)>>0]|0;ub[c[(e+64|0)>>2]&255](k,m,A,n);ub[c[(e+64|0)>>2]&255](l,m,A,n)}p=(B+E|0)>>>0<68|(B+C|0)>>>0<68;if(w&16777216){if(!p){n=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+16|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+16|0,m,A,n,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+60|0)>>2]&255](k,m,g,h);ub[c[(e+60|0)>>2]&255](l,m,g,h);break}else{j=a[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)>>0]=j;A=a[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=A;n=a[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=n;D=a[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](k,m,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=n;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](l,m,g,h,F+72|0);break}}}while(0);if(p){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,C,D,F+72|0);i=F;return}else{if(!p){n=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+8|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+8|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+16|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+16|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](k+24|0,m,A,n,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+52|0)>>2]&127](l+24|0,m,A,n,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+60|0)>>2]&255](k,m,g,h);ub[c[(e+60|0)>>2]&255](l,m,g,h);break}else{j=a[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)>>0]=j;A=a[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=A;n=a[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=n;D=a[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](k,m,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=n;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+48|0)>>2]&127](l,m,g,h,F+72|0);break}}}while(0);if(p){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=a[97228+((B+E|0)<<2)+3>>0]|0;FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m<<2)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m<<2)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](k+(m*12|0)|0,m,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+48|0)>>2]&127](l+(m*12|0)|0,m,C,D,F+72|0);i=F;return}}if((A|0)==2){if(u)if(!((v+E|0)>>>0<68|(v+C|0)>>>0<68)){m=d[97072+(v+C|0)>>0]|0;A=d[96916+(v+E|0)>>0]|0;ub[c[(e+88|0)>>2]&255](k,n,A,m);ub[c[(e+88|0)>>2]&255](l,n,A,m)}if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){m=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](k+8|0,n,A,m,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](l+8|0,n,A,m,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+84|0)>>2]&255](k,n,g,h);ub[c[(e+84|0)>>2]&255](l,n,g,h);break}else{j=(d[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[(F+72|0)>>0]=j;A=(d[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+1|0)>>0]=A;m=(d[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+2|0)>>0]=m;D=(d[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](k,n,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=m;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](l,n,g,h,F+72|0);break}}}while(0);if((B+E|0)>>>0<68|(B+C|0)>>>0<68){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n<<3)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n<<3)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n*12|0)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n*12|0)|0,n,C,D,F+72|0);i=F;return}else{if(u)if(!((v+E|0)>>>0<68|(v+C|0)>>>0<68)){m=d[97072+(v+C|0)>>0]|0;A=d[96916+(v+E|0)>>0]|0;ub[c[(e+88|0)>>2]&255](k,n,A,m);ub[c[(e+88|0)>>2]&255](l,n,A,m)}if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){m=d[(97072+(B+C|0)|0)>>0]|0;A=d[(96916+(B+E|0)|0)>>0]|0;j=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](k+8|0,n,A,m,F+72|0);FF(F+72|0|0,j|0,4)|0;fb[c[(e+76|0)>>2]&127](l+8|0,n,A,m,F+72|0)}do if(x|0){g=d[96916+(D+E|0)>>0]|0;h=d[97072+(D+C|0)>>0]|0;if(!((D+E|0)>>>0<68|(D+C|0)>>>0<68)){o=b[f>>1]|0;if(o<<16>>16>3){ub[c[(e+84|0)>>2]&255](k,n,g,h);ub[c[(e+84|0)>>2]&255](l,n,g,h);break}else{j=(d[(o<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[(F+72|0)>>0]=j;A=(d[(b[f+2>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+1|0)>>0]=A;m=(d[(b[f+4>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+2|0)>>0]=m;D=(d[(b[f+6>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1&255;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](k,n,g,h,F+72|0);a[(F+72|0)>>0]=j;a[((F+72|0)+1|0)>>0]=A;a[((F+72|0)+2|0)>>0]=m;a[((F+72|0)+3|0)>>0]=D;fb[c[(e+72|0)>>2]&127](l,n,g,h,F+72|0);break}}}while(0);if((B+E|0)>>>0<68|(B+C|0)>>>0<68){i=F;return}D=d[(97072+(B+C|0)|0)>>0]|0;C=d[(96916+(B+E|0)|0)>>0]|0;E=(d[97228+((B+E|0)<<2)+3>>0]|0)+1&255;FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,C,D,F+72|0);FF(F+72|0|0,E|0,4)|0;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,C,D,F+72|0);i=F;return}}t=F+7&-8;g=c[f+21824>>2]|0;if(w&16777216)if((A|0)==3|(g&7|0)!=7)q=80;else{c[(F+7&-8)>>2]=131074;c[(F+7&-8)+4>>2]=131074;c[(t+16|0)>>2]=131074;c[(t+16|0)+4>>2]=131074;c[(t+32|0)>>2]=131074;c[(t+32|0)+4>>2]=131074;c[(t+48|0)>>2]=131074;c[(t+48|0)+4>>2]=131074;s=4}else q=80;if((q|0)==80){eb[p&3](t,f+43648|0,f+44096|0,f+43776|0,(c[f+23380>>2]|0)==2&1,4-(0-((g&15|0)==0&w>>>3)&3)|0,(w>>24)+1|0,0-((u*5|0)>>>5&1&(0-((w*5|0)>>>5&1)&3|w>>4)>>>1)&3,0-((w*5|0)>>>5&1)&3|w>>4,(c[e+197612>>2]|0)!=3&1);s=4-(0-((g&15|0)==0&w>>>3)&3)|0}if(u&7|0){c[(F+7&-8)>>2]=262148;c[(F+7&-8)+4>>2]=262148}if(x&7|0){q=(c[e+197612>>2]|0)!=3;c[(t+32|0)>>2]=q?196611:262148;c[(t+32|0)+4>>2]=q?196611:262148}do if(u|0)if(!((c[(F+7&-8)>>2]|0)==0&(c[(F+7&-8)+4>>2]|0)==0)){g=d[96916+(r+E|0)>>0]|0;h=d[97072+(r+C|0)>>0]|0;do if(!((r+E|0)>>>0<68|(r+C|0)>>>0<68)){o=b[t>>1]|0;if(o<<16>>16>3){ub[c[e+64>>2]&255](j,m,g,h);break}else{a[(F+72|0)>>0]=a[(o<<16>>16)+(97228+((r+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+2>>1]|0)+(97228+((r+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+4>>1]|0)+(97228+((r+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+6>>1]|0)+(97228+((r+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j,m,g,h,F+72|0);break}}while(0);if(A){h=d[96916+(v+E|0)>>0]|0;o=d[97072+(v+C|0)>>0]|0;if((A|0)==3){if((v+E|0)>>>0<68|(v+C|0)>>>0<68)break;g=b[t>>1]|0;if(g<<16>>16>3)ub[c[e+64>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](k,m,h,o,F+72|0)}g=b[t>>1]|0;if(g<<16>>16>3){ub[c[e+64>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](l,m,h,o,F+72|0);break}}else{if((v+E|0)>>>0<68|(v+C|0)>>>0<68)break;g=b[t>>1]|0;if(g<<16>>16>3)ub[c[e+88>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;fb[c[e+76>>2]&127](k,n,h,o,F+72|0)}g=b[t>>1]|0;if(g<<16>>16>3){ub[c[e+88>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+2>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+4>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+6>>1]|0)+(97228+((v+E|0)<<2))>>0]|0)+1;fb[c[e+76>>2]&127](l,n,h,o,F+72|0);break}}}}while(0);do{p=1;if(p){p=0;if((s|0)==1){if(x|0){g=c[(t+32|0)>>2]|0;if(!((g|0)==0&(c[(t+32|0)+4>>2]|0)==0)){h=d[96916+(y+E|0)>>0]|0;o=d[97072+(y+C|0)>>0]|0;do if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68))if((g&65535)<<16>>16>3){ub[c[e+60>>2]&255](j,m,h,o);break}else{a[(F+72|0)>>0]=a[((g&65535)<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,h,o,F+72|0);break}while(0);if(A){h=d[96916+(D+E|0)>>0]|0;o=d[97072+(D+C|0)>>0]|0;if((A|0)==3){if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(t+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+60>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](k,m,h,o,F+72|0)}g=b[(t+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+60>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](l,m,h,o,F+72|0);break}}else{if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(t+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+84>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](k,n,h,o,F+72|0)}g=b[(t+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+84>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](l,n,h,o,F+72|0);break}}}}}}else p=1}if(p)if(1){if(w&16777216){g=c[(t+16|0)>>2]|0;do if(!((g|0)==0&(c[(t+16|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+18>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+20>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+22>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+16|0,m,v,w,F+72|0)}if(A)if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+16|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+16|0,m,v,w,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](k+8|0,n,v,w,F+72|0);a[(F+72|0)>>0]=(d[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](l+8|0,n,v,w,F+72|0);break}}while(0);do if(x|0){g=c[(t+32|0)>>2]|0;if(!((g|0)==0&(c[(t+32|0)+4>>2]|0)==0)){h=d[96916+(y+E|0)>>0]|0;o=d[97072+(y+C|0)>>0]|0;do if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68))if((g&65535)<<16>>16>3){ub[c[e+60>>2]&255](j,m,h,o);break}else{a[(F+72|0)>>0]=a[((g&65535)<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,h,o,F+72|0);break}while(0);if(A){h=d[96916+(D+E|0)>>0]|0;o=d[97072+(D+C|0)>>0]|0;if((A|0)==3){if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(t+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+60>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](k,m,h,o,F+72|0)}g=b[(t+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+60>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](l,m,h,o,F+72|0);break}}else{if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(t+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+84>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](k,n,h,o,F+72|0)}g=b[(t+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+84>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](l,n,h,o,F+72|0);break}}}}}while(0);g=c[(t+48|0)>>2]|0;if((g|0)==0&(c[(t+48|0)+4>>2]|0)==0)break;if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;y=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+50>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+52>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+54>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m<<3)|0,m,y,D,F+72|0)}if(!A)break;if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;C=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,C,D,F+72|0);a[(F+72|0)>>0]=a[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,C,D,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;C=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,C,D,F+72|0);a[(F+72|0)>>0]=(d[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,C,D,F+72|0);break}}g=c[(t+8|0)>>2]|0;if(!((g|0)==0&(c[(t+8|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+10>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+12>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+14>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+8|0,m,v,w,F+72|0)}if((A|0)==3)if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+8|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+10|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+12|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+14|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+8|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(t+8|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+10|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+12|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+14|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+8|0,m,v,w,F+72|0)}}g=c[(t+16|0)>>2]|0;do if(!((g|0)==0&(c[(t+16|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+18>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+20>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+22>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+16|0,m,v,w,F+72|0)}if(A)if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+16|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+16|0,m,v,w,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](k+8|0,n,v,w,F+72|0);a[(F+72|0)>>0]=(d[(b[(t+16|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+18|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+20|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+22|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+76|0)>>2]&127](l+8|0,n,v,w,F+72|0);break}}while(0);g=c[(t+24|0)>>2]|0;if(!((g|0)==0&(c[(t+24|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){w=d[97072+(z+C|0)>>0]|0;v=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+26>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+28>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+30>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+52>>2]&127](j+24|0,m,v,w,F+72|0)}if((A|0)==3)if(!((B+E|0)>>>0<68|(B+C|0)>>>0<68)){w=d[97072+(B+C|0)>>0]|0;v=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+24|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+26|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+28|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+30|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](k+24|0,m,v,w,F+72|0);a[(F+72|0)>>0]=a[(b[(t+24|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+26|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+28|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+30|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+52|0)>>2]&127](l+24|0,m,v,w,F+72|0)}}do if(x|0){g=c[(t+32|0)>>2]|0;if(!((g|0)==0&(c[(t+32|0)+4>>2]|0)==0)){h=d[96916+(y+E|0)>>0]|0;o=d[97072+(y+C|0)>>0]|0;do if(!((y+E|0)>>>0<68|(y+C|0)>>>0<68))if((g&65535)<<16>>16>3){ub[c[e+60>>2]&255](j,m,h,o);break}else{a[(F+72|0)>>0]=a[((g&65535)<<16>>16)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((y+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j,m,h,o,F+72|0);break}while(0);if(!A)break;h=d[96916+(D+E|0)>>0]|0;o=d[97072+(D+C|0)>>0]|0;if((A|0)==3){if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(t+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+60>>2]&255](k,m,h,o);else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](k,m,h,o,F+72|0)}g=b[(t+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+60>>2]&255](l,m,h,o);break}else{a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](l,m,h,o,F+72|0);break}}else{if((D+E|0)>>>0<68|(D+C|0)>>>0<68)break;g=b[(t+32|0)>>1]|0;if(g<<16>>16>3)ub[c[e+84>>2]&255](k,n,h,o);else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](k,n,h,o,F+72|0)}g=b[(t+32|0)>>1]|0;if(g<<16>>16>3){ub[c[e+84>>2]&255](l,n,h,o);break}else{a[(F+72|0)>>0]=(d[(g<<16>>16)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+1>>0]=(d[(b[t+34>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+2>>0]=(d[(b[t+36>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;a[(F+72|0)+3>>0]=(d[(b[t+38>>1]|0)+(97228+((D+E|0)<<2))>>0]|0)+1;fb[c[e+72>>2]&127](l,n,h,o,F+72|0);break}}}}while(0);g=c[(t+40|0)>>2]|0;do if(!((g|0)==0&(c[(t+40|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;y=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+42>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+44>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+46>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m<<2)|0,m,y,D,F+72|0)}if((A|0)!=3)break;if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;y=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+40|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+42|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+44|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+46|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m<<2)|0,m,y,D,F+72|0);a[(F+72|0)>>0]=a[(b[(t+40|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+42|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+44|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+46|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m<<2)|0,m,y,D,F+72|0)}while(0);g=c[(t+48|0)>>2]|0;do if(!((g|0)==0&(c[(t+48|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;y=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+50>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+52>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+54>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m<<3)|0,m,y,D,F+72|0)}if(!A)break;if((A|0)==3){if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;n=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m<<3)|0,m,n,D,F+72|0);a[(F+72|0)>>0]=a[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m<<3)|0,m,n,D,F+72|0);break}else{if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;y=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=(d[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](k+(n<<2)|0,n,y,D,F+72|0);a[(F+72|0)>>0]=(d[(b[(t+48|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+1|0)>>0]=(d[(b[(t+50|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+2|0)>>0]=(d[(b[(t+52|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;a[((F+72|0)+3|0)>>0]=(d[(b[(t+54|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0)+1;fb[c[(e+72|0)>>2]&127](l+(n<<2)|0,n,y,D,F+72|0);break}}while(0);g=c[(t+56|0)>>2]|0;if(!((g|0)==0&(c[(t+56|0)+4>>2]|0)==0)){if(!((z+E|0)>>>0<68|(z+C|0)>>>0<68)){D=d[97072+(z+C|0)>>0]|0;n=d[96916+(z+E|0)>>0]|0;a[(F+72|0)>>0]=a[(g<<16>>16)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+1>>0]=a[(b[t+58>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+2>>0]=a[(b[t+60>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;a[(F+72|0)+3>>0]=a[(b[t+62>>1]|0)+(97228+((z+E|0)<<2))>>0]|0;fb[c[e+48>>2]&127](j+(m*12|0)|0,m,n,D,F+72|0)}if((A|0)!=3)break;if((B+E|0)>>>0<68|(B+C|0)>>>0<68)break;D=d[97072+(B+C|0)>>0]|0;C=d[96916+(B+E|0)>>0]|0;a[(F+72|0)>>0]=a[(b[(t+56|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+58|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+60|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+62|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](k+(m*12|0)|0,m,C,D,F+72|0);a[(F+72|0)>>0]=a[(b[(t+56|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+1|0)>>0]=a[(b[(t+58|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+2|0)>>0]=a[(b[(t+60|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;a[((F+72|0)+3|0)>>0]=a[(b[(t+62|0)>>1]|0)+(97228+((B+E|0)<<2))>>0]|0;fb[c[(e+48|0)>>2]&127](l+(m*12|0)|0,m,C,D,F+72|0)}}}while(0);i=F;return}}IA(e,f,g,h,j,k,l,m,n);i=F;return}
function nc(e,f,g,h,j,k,l,m){e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;k=k|0;l=l|0;m=m|0;var n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;B=i;i=i+112|0;A=B+32|0;v=B+24|0;u=B+16|0;t=B;z=B+40|0;w=(m|0)<9;do if(w)if((m|0)==4){r=c[26883]|0;y=g+8|0;x=c[y>>2]|0;s=(c[g>>2]|0)+(x>>>3)|0;s=(MF(d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24|0)|0)<<(x&7)>>>24;n=b[r+(s<<2)>>1]|0;c[y>>2]=(b[r+(s<<2)+2>>1]|0)+x;break}else{r=c[26887]|0;y=g+8|0;x=c[y>>2]|0;s=(c[g>>2]|0)+(x>>>3)|0;s=(MF(d[s>>0]|d[s+1>>0]<<8|d[s+2>>0]<<16|d[s+3>>0]<<24|0)|0)<<(x&7)>>>19;n=b[r+(s<<2)>>1]|0;c[y>>2]=(b[r+(s<<2)+2>>1]|0)+x;break}else if((j|0)>47){r=d[96537+((j<<4)+-768)>>0]|0;r=(d[r+-8+(f+43648)>>0]|0)+(d[r+-1+(f+43648)>>0]|0)|0;r=c[107560+(c[33780+(((r>>>0<64?(r+1|0)>>>1:r)&31)<<2)>>2]<<4)+4>>2]|0;s=g+8|0;o=c[s>>2]|0;q=c[g>>2]|0;y=q+(o>>>3)|0;y=(MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0)<<(o&7)>>>24;n=b[r+(y<<2)>>1]|0;y=b[r+(y<<2)+2>>1]|0;p=y<<16>>16;if(y<<16>>16<0){o=o+8|0;y=q+(o>>>3)|0;p=((MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0)<<(o&7)>>>(p+32|0))+n|0;n=b[r+(p<<2)>>1]|0;p=b[r+(p<<2)+2>>1]|0}c[s>>2]=p+o;break}else{r=d[96537+j>>0]|0;r=(d[r+-8+(f+43648)>>0]|0)+(d[r+-1+(f+43648)>>0]|0)|0;r=c[107560+(c[33780+(((r>>>0<64?(r+1|0)>>>1:r)&31)<<2)>>2]<<4)+4>>2]|0;s=g+8|0;o=c[s>>2]|0;q=c[g>>2]|0;y=q+(o>>>3)|0;y=(MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0)<<(o&7)>>>24;n=b[r+(y<<2)>>1]|0;y=b[r+(y<<2)+2>>1]|0;p=y<<16>>16;if(y<<16>>16<0){o=o+8|0;y=q+(o>>>3)|0;p=((MF(d[y>>0]|d[y+1>>0]<<8|d[y+2>>0]<<16|d[y+3>>0]<<24|0)|0)<<(o&7)>>>(p+32|0))+n|0;n=b[r+(p<<2)>>1]|0;p=b[r+(p<<2)+2>>1]|0}c[s>>2]=p+o;break}while(0);y=n>>2;a[(d[96537+j>>0]|0)+(f+43648)>>0]=y;if(!y){A=0;i=B;return A|0}if(y>>>0>m>>>0){A=c[e+4>>2]|0;e=c[f+21768>>2]|0;c[t>>2]=c[f+21764>>2];c[t+4>>2]=e;c[t+8>>2]=y;UC(A,16,61785,t);A=-1;i=B;return A|0}s=n&3;x=c[g>>2]|0;g=g+8|0;n=c[g>>2]|0;r=x+(n>>>3)|0;r=(MF(d[r>>0]|d[r+1>>0]<<8|d[r+2>>0]<<16|d[r+3>>0]<<24|0)|0)<<(n&7);t=r>>>29;n=n+s|0;c[g>>2]=n;c[z>>2]=1-(r>>>30&2);c[z+4>>2]=1-(t&2);c[z+8>>2]=1-(t<<1&2);a:do if((s|0)<(y|0)){r=(s|0)!=3;q=(y|0)>10&r;C=q&1;o=x+(n>>>3)|0;o=(MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0)<<(n&7)>>>24;t=a[184448+(C<<9)+(o<<1)>>0]|0;p=t<<24>>24;n=n+(a[184448+(C<<9)+(o<<1)+1>>0]|0)|0;c[g>>2]=n;if(t<<24>>24>99){o=p+-100|0;if((o|0)==8){t=x+(n>>>3)|0;t=(MF(d[t>>0]|d[t+1>>0]<<8|d[t+2>>0]<<16|d[t+3>>0]<<24|0)|0)<<(n&7);o=t>>>0>65535;t=o?t>>>16:t;o=o?16:0;C=(t&65280|0)==0;o=32-(d[97868+(C?t:t>>>8)>>0]|0)-(C?o:o|8)|0;n=o+n|0;c[g>>2]=n;o=p+-101+o|0}do if((o|0)<14){if(q){C=d[x+(n>>>3)>>0]<<(n&7)>>>7&1;n=n+1|0;c[g>>2]=n;o=C|o<<1}}else{if((o|0)==14){o=x+(n>>>3)|0;if(q){o=d[o>>0]<<(n&7)>>>7&1;n=n+1|0;c[g>>2]=n;o=o|28;break}else{o=(MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0)<<(n&7)>>>28;n=n+4|0;c[g>>2]=n;o=o+14|0;break}}do if((o|0)>15){if((o|0)<=28){q=o+-3|0;p=q;q=(1<<q)+-4066|0;break}UC(c[e+4>>2]|0,16,61830,u);C=-1;i=B;return C|0}else{p=12;q=30}while(0);C=x+(n>>>3)|0;o=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(n&7)>>>(35-o|0);n=p+n|0;c[g>>2]=n;o=o+q|0}while(0);C=r?o+2|0:o;p=C&1;c[z+(s<<2)>>2]=(C+2>>1^0-p)+p;p=2}else{p=(r?p>>31|1:0)+p|0;c[z+(s<<2)>>2]=p;p=(p+3|0)>>>0>6?2:1}o=s+1|0;if((o|0)<(y|0)){s=p;while(1){u=x+(n>>>3)|0;u=(MF(d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24|0)|0)<<(n&7)>>>24;C=a[184448+(s<<9)+(u<<1)>>0]|0;q=C<<24>>24;n=n+(a[184448+(s<<9)+(u<<1)+1>>0]|0)|0;c[g>>2]=n;if(C<<24>>24>99){p=q+-100|0;if((p|0)==8){u=x+(n>>>3)|0;u=(MF(d[u>>0]|d[u+1>>0]<<8|d[u+2>>0]<<16|d[u+3>>0]<<24|0)|0)<<(n&7);p=u>>>0>65535;u=p?u>>>16:u;p=p?16:0;C=(u&65280|0)==0;p=32-(d[97868+(C?u:u>>>8)>>0]|0)-(C?p:p|8)|0;n=p+n|0;c[g>>2]=n;p=q+-101+p|0}if((p|0)<15){C=x+(n>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(n&7)>>>(32-s|0);n=n+s|0;c[g>>2]=n;p=C+(p<<s)|0}else{q=15<<s;if((p|0)>15){if((p|0)>28)break;C=p+-3|0;r=C;q=q+-4096+(1<<C)|0}else r=12;C=x+(n>>>3)|0;p=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(n&7)>>>(35-p|0);n=r+n|0;c[g>>2]=n;p=p+q|0}q=p&1;q=(p+2>>1^0-q)+q|0}c[z+(o<<2)>>2]=q;p=c[33848+(s<<2)>>2]|0;o=o+1|0;if((o|0)>=(y|0))break a;else s=((p+q|0)>>>0>p<<1>>>0&1)+s|0}UC(c[e+4>>2]|0,16,61830,v);C=-1094995529;i=B;return C|0}}while(0);do if((y|0)==(m|0)){o=n;n=0}else{if(!w){m=c[107768+(y<<4)+4>>2]|0;o=x+(n>>>3)|0;o=(MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0)<<(n&7)>>>23;C=b[m+(o<<2)>>1]|0;o=(b[m+(o<<2)+2>>1]|0)+n|0;c[g>>2]=o;n=C;break}if((m|0)==4){m=c[107608+(y<<4)+4>>2]|0;o=x+(n>>>3)|0;o=(MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0)<<(n&7)>>>29;C=b[m+(o<<2)>>1]|0;o=(b[m+(o<<2)+2>>1]|0)+n|0;c[g>>2]=o;n=C;break}else{m=c[107656+(y<<4)+4>>2]|0;o=x+(n>>>3)|0;o=(MF(d[o>>0]|d[o+1>>0]<<8|d[o+2>>0]<<16|d[o+3>>0]<<24|0)|0)<<(n&7)>>>27;C=b[m+(o<<2)>>1]|0;o=(b[m+(o<<2)+2>>1]|0)+n|0;c[g>>2]=o;n=C;break}}while(0);p=k+(y+-1+n)|0;q=(j|0)>47;r=c[z>>2]|0;do if(!(c[e+25960>>2]|0))if(q){b[h+(d[p>>0]<<1)>>1]=r;if((y|0)>1&(n|0)>0){r=o;o=1;do{if((n|0)<7){l=c[108008+(n<<4)+4>>2]|0;C=x+(r>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>29;q=b[l+(C<<2)>>1]|0;r=(b[l+(C<<2)+2>>1]|0)+r|0}else{s=c[27031]|0;C=x+(r>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>26;q=b[s+(C<<2)>>1]|0;C=b[s+(C<<2)+2>>1]|0;t=C<<16>>16;if(C<<16>>16<0){r=r+6|0;C=x+(r>>>3)|0;t=((MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>(t+32|0))+q|0;q=b[s+(t<<2)>>1]|0;t=b[s+(t<<2)+2>>1]|0}r=t+r|0}c[g>>2]=r;n=n-q|0;p=p+~q|0;b[h+(d[p>>0]<<1)>>1]=c[z+(o<<2)>>2];o=o+1|0}while((o|0)<(y|0)&(n|0)>0)}else o=1;if((o|0)>=(y|0))break;do{p=p+-1|0;b[h+(d[p>>0]<<1)>>1]=c[z+(o<<2)>>2];o=o+1|0}while((o|0)!=(y|0))}else{C=d[p>>0]|0;b[h+(C<<1)>>1]=((_(c[l+(C<<2)>>2]|0,r)|0)+32|0)>>>6;if((y|0)>1&(n|0)>0){r=o;o=1;do{if((n|0)<7){j=c[108008+(n<<4)+4>>2]|0;C=x+(r>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>29;q=b[j+(C<<2)>>1]|0;r=(b[j+(C<<2)+2>>1]|0)+r|0}else{s=c[27031]|0;C=x+(r>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>26;q=b[s+(C<<2)>>1]|0;C=b[s+(C<<2)+2>>1]|0;t=C<<16>>16;if(C<<16>>16<0){r=r+6|0;C=x+(r>>>3)|0;t=((MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>(t+32|0))+q|0;q=b[s+(t<<2)>>1]|0;t=b[s+(t<<2)+2>>1]|0}r=t+r|0}c[g>>2]=r;n=n-q|0;p=p+~q|0;C=d[p>>0]|0;b[h+(C<<1)>>1]=((_(c[l+(C<<2)>>2]|0,c[z+(o<<2)>>2]|0)|0)+32|0)>>>6;o=o+1|0}while((o|0)<(y|0)&(n|0)>0)}else o=1;if((o|0)>=(y|0))break;do{p=p+-1|0;C=d[p>>0]|0;b[h+(C<<1)>>1]=((_(c[l+(C<<2)>>2]|0,c[z+(o<<2)>>2]|0)|0)+32|0)>>>6;o=o+1|0}while((o|0)!=(y|0))}else{o=d[p>>0]|0;if(q){c[h+(o<<2)>>2]=r;if((y|0)>1&(n|0)>0){o=1;do{if((n|0)<7){l=c[108008+(n<<4)+4>>2]|0;q=c[g>>2]|0;C=x+(q>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(q&7)>>>29;s=b[l+(C<<2)>>1]|0;q=(b[l+(C<<2)+2>>1]|0)+q|0}else{s=c[27031]|0;r=c[g>>2]|0;C=x+(r>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>26;q=b[s+(C<<2)>>1]|0;C=b[s+(C<<2)+2>>1]|0;t=C<<16>>16;if(C<<16>>16<0){r=r+6|0;C=x+(r>>>3)|0;t=((MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>(t+32|0))+q|0;q=b[s+(t<<2)>>1]|0;t=b[s+(t<<2)+2>>1]|0}s=q;q=t+r|0}c[g>>2]=q;n=n-s|0;p=p+~s|0;c[h+(d[p>>0]<<2)>>2]=c[z+(o<<2)>>2];o=o+1|0}while((o|0)<(y|0)&(n|0)>0)}else o=1;if((o|0)>=(y|0))break;do{p=p+-1|0;c[h+(d[p>>0]<<2)>>2]=c[z+(o<<2)>>2];o=o+1|0}while((o|0)!=(y|0))}else{c[h+(o<<2)>>2]=(_(c[l+(o<<2)>>2]|0,r)|0)+32>>6;if((y|0)>1&(n|0)>0){o=1;do{if((n|0)<7){j=c[108008+(n<<4)+4>>2]|0;q=c[g>>2]|0;C=x+(q>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(q&7)>>>29;s=b[j+(C<<2)>>1]|0;q=(b[j+(C<<2)+2>>1]|0)+q|0}else{s=c[27031]|0;r=c[g>>2]|0;C=x+(r>>>3)|0;C=(MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>26;q=b[s+(C<<2)>>1]|0;C=b[s+(C<<2)+2>>1]|0;t=C<<16>>16;if(C<<16>>16<0){r=r+6|0;C=x+(r>>>3)|0;t=((MF(d[C>>0]|d[C+1>>0]<<8|d[C+2>>0]<<16|d[C+3>>0]<<24|0)|0)<<(r&7)>>>(t+32|0))+q|0;q=b[s+(t<<2)>>1]|0;t=b[s+(t<<2)+2>>1]|0}s=q;q=t+r|0}c[g>>2]=q;n=n-s|0;p=p+~s|0;C=d[p>>0]|0;c[h+(C<<2)>>2]=(_(c[l+(C<<2)>>2]|0,c[z+(o<<2)>>2]|0)|0)+32>>6;o=o+1|0}while((o|0)<(y|0)&(n|0)>0)}else o=1;if((o|0)>=(y|0))break;do{p=p+-1|0;C=d[p>>0]|0;c[h+(C<<2)>>2]=(_(c[l+(C<<2)>>2]|0,c[z+(o<<2)>>2]|0)|0)+32>>6;o=o+1|0}while((o|0)!=(y|0))}}while(0);if((n|0)>=0){C=0;i=B;return C|0}C=c[e+4>>2]|0;e=c[f+21768>>2]|0;c[A>>2]=c[f+21764>>2];c[A+4>>2]=e;UC(C,16,61852,A);C=-1;i=B;return C|0}function oc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;n=c[a+197612>>2]|0;l=c[a+24620>>2]|0;n=c[((n|0)==3?l+80|0:l+72+(((n|0)==2&1)<<2)|0)>>2]|0;m=c[b+25332>>2]|0;if(!(c[a+197608>>2]|0)){a=b+23372|0;g=a;a=c[a>>2]|0}else{k=b+25340|0;g=b+23372|0;i=c[l+72>>2]|0;d=c[k>>2]|0;j=c[d+72>>2]|0;a=c[g>>2]|0;if(a&2147483647){h=0;do{d=h+16|0;e=c[b+23384+(d*40|0)+28>>2]|0;f=j-e|0;f=(f+128|0)>>>0>255?f>>31^127:f;a=f&255;f=f<<24>>24;if(!(a<<24>>24))a=256;else if(!(c[(c[b+23384+(d*40|0)+36>>2]|0)+96>>2]|0)){e=i-e|0;f=(_((((a<<24>>24>-1?f:0-f|0)>>1)+16384|0)/(f|0)|0,((e+128|0)>>>0>255?e>>31^127:e)<<24>>24)|0)+32|0;a=f>>6;a=(a+1024|0)>>>0>2047?f>>31^1023:a}else a=256;c[b+21964+(h<<2)>>2]=a;h=h+1|0;a=c[g>>2]|0}while(h>>>0<a<<1>>>0);d=c[k>>2]|0}j=c[l+76>>2]|0;i=c[d+76>>2]|0;if(a&2147483647){h=0;do{d=h+16|0;e=c[b+23384+(d*40|0)+28>>2]|0;f=i-e|0;f=(f+128|0)>>>0>255?f>>31^127:f;a=f&255;f=f<<24>>24;if(!(a<<24>>24))a=256;else if(!(c[(c[b+23384+(d*40|0)+36>>2]|0)+96>>2]|0)){l=j-e|0;l=(_((((a<<24>>24>-1?f:0-f|0)>>1)+16384|0)/(f|0)|0,((l+128|0)>>>0>255?l>>31^127:l)<<24>>24)|0)+32|0;a=l>>6;a=(a+1024|0)>>>0>2047?l>>31^1023:a}else a=256;c[b+22092+((h^1)<<2)>>2]=a;h=h+1|0;a=c[g>>2]|0}while(h>>>0<a<<1>>>0)}}if(!a)return;else f=0;do{d=c[b+23384+(f*40|0)+28>>2]|0;e=m-d|0;e=(e+128|0)>>>0>255?e>>31^127:e;a=e&255;e=e<<24>>24;if(!(a<<24>>24))a=256;else if(!(c[(c[b+23384+(f*40|0)+36>>2]|0)+96>>2]|0)){l=n-d|0;l=(_((((a<<24>>24>-1?e:0-e|0)>>1)+16384|0)/(e|0)|0,((l+128|0)>>>0>255?l>>31^127:l)<<24>>24)|0)+32|0;a=l>>6;a=(a+1024|0)>>>0>2047?l>>31^1023:a}else a=256;c[b+21836+(f<<2)>>2]=a;f=f+1|0}while(f>>>0<(c[g>>2]|0)>>>0);return}function pc(a,b){a=a|0;b=b|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;l=a+24620|0;j=c[l>>2]|0;k=a+197612|0;g=c[k>>2]|0;e=g&1^1;m=b+25328|0;f=c[m>>2]&1^1;i=b+23380|0;if(!(c[i>>2]|0))d=0;else{h=0;do{d=b+23372+(h<<2)|0;c[j+612+(e<<3)+(h<<2)>>2]=c[d>>2];if(c[d>>2]|0){g=0;do{c[j+100+(e<<8)+(h<<7)+(g<<2)>>2]=c[b+23384+(h*1920|0)+(g*40|0)+24>>2]&3|c[(c[b+23384+(h*1920|0)+(g*40|0)+36>>2]|0)+84>>2]<<2;g=g+1|0}while(g>>>0<(c[d>>2]|0)>>>0)}h=h+1|0;d=c[i>>2]|0}while(h>>>0<d>>>0);g=c[k>>2]|0}if((g|0)==3){g=j+612|0;h=c[g+4>>2]|0;d=j+620|0;c[d>>2]=c[g>>2];c[d+4>>2]=h;KF(j+356|0,j+100|0,256)|0;d=c[i>>2]|0}i=a+197608|0;c[j+628>>2]=c[i>>2];h=b+21820|0;c[h>>2]=0;if((d|0)!=2)return;if(!(c[b+23376>>2]|0))return;g=c[k>>2]|0;if((g|0)==3){e=c[(c[l>>2]|0)+80>>2]|0;m=c[b+25340>>2]|0;f=(c[m+72>>2]|0)-e|0;e=(c[m+76>>2]|0)-e|0;e=(((f|0)>-1?f:0-f|0)|0)>=(((e|0)>-1?e:0-e|0)|0)&1;c[b+21816>>2]=e;f=e}else{d=c[m>>2]|0;if(!(d&g))if(!(c[(c[b+25340>>2]|0)+628>>2]|0))c[h>>2]=(d<<1)+-3}if((c[b+816>>2]|0)!=3)return;if(c[b+21812>>2]|0)return;d=b+22220|0;qc(a,b,d,0,e,f,0);if(c[i>>2]|0){qc(a,b,b+22604|0,0,0,0,1);qc(a,b,b+22988|0,0,1,1,1)}qc(a,b,d,1,e,f,0);if(!(c[i>>2]|0))return;qc(a,b,b+22604|0,1,0,0,1);qc(a,b,b+22988|0,1,1,1,1);return}function qc(a,b,d,e,f,g,h){a=a|0;b=b|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;t=c[b+25340>>2]|0;q=(h|0)!=0;r=q?16:0;s=c[b+23372>>2]|0;s=q?(s<<1)+16|0:s;if(q)o=1;else o=(c[a+197612>>2]|0)!=3;FF(d+(e*192|0)|0,0,192)|0;l=t+612+(g<<3)+(e<<2)|0;m=(r|0)<(s|0);n=t+628|0;h=c[l>>2]|0;p=0;do{a=p;p=p+1|0;a:do if((h|0)>0){k=o&(a|0)!=(f|0);j=(a^f)+16|0;if(o){i=0;while(1){a=c[t+100+(g<<8)+(e<<7)+(i<<2)>>2]|0;a=(a&3|0)==3?p+(a&-4)|0:a;b:do if(m){h=r;while(1){if((c[b+23384+(h*40|0)+24>>2]&3|c[(c[b+23384+(h*40|0)+36>>2]|0)+84>>2]<<2|0)==(a|0))break;h=h+1|0;if((h|0)>=(s|0))break b}h=q?h+-16^f:h;if(c[n>>2]|0)c[d+(e*192|0)+(j+(i<<1)<<2)>>2]=h;if(!k)c[d+(e*192|0)+(i<<2)>>2]=h}while(0);i=i+1|0;h=c[l>>2]|0;if((i|0)>=(h|0))break a}}if(m)i=0;else{a=0;while(1){a=a+1|0;if((a|0)>=(h|0))break a}}do{a=c[t+100+(g<<8)+(e<<7)+(i<<2)>>2]|3;h=r;do{if((c[b+23384+(h*40|0)+24>>2]&3|c[(c[b+23384+(h*40|0)+36>>2]|0)+84>>2]<<2|0)==(a|0)){u=18;break}h=h+1|0}while((h|0)<(s|0));if((u|0)==18){u=0;h=q?h+-16^f:h;if(c[n>>2]|0)c[d+(e*192|0)+(j+(i<<1)<<2)>>2]=h;if(!k)c[d+(e*192|0)+(i<<2)>>2]=h}i=i+1|0;h=c[l>>2]|0}while((i|0)<(h|0))}while(0)}while((p|0)!=2);return}function rc(f,g,h){f=f|0;g=g|0;h=h|0;var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;U=i;i=i+32|0;B=U+16|0;A=U+8|0;v=U;D=c[f+26652>>2]|0;x=c[g+21772>>2]|0;if(!(c[g+21812>>2]|0)){u=g+21768|0;p=c[h>>2]|0;w=p&64;v=c[g+25340>>2]|0;m=c[v+44>>2]|0;j=(p&128|0)!=0;do if(!(c[m+(x<<2)>>2]&128))if(j){n=c[f+198236>>2]|0;o=(_(n,c[u>>2]&-2)|0)+(c[g+21764>>2]|0)|0;l=c[m+(o<<2)>>2]|0;c[B>>2]=l;j=c[m+(o+n<<2)>>2]|0;k=B+4|0;c[k>>2]=j;n=n<<2|2;m=D*6|0;if(!((j^l)&128))k=l;else{S=l&-129;c[B>>2]=S;j=j&-129;c[k>>2]=j;k=S}if(k&15|0)if(!((w|0)!=0|(j&15|0)==0)){t=p|61712;c[h>>2]=t;D=m;A=n;j=o;s=20744;break}t=p|61504;c[h>>2]=t;D=m;A=n;j=o;s=20744}else{n=2;l=x;T=161}else{k=c[f+198236>>2]|0;if(j){n=2;l=(_(c[g+21820>>2]|0,k)|0)+x|0;T=161;break}else{n=0;l=(_((c[u>>2]&-2)+(c[g+21816>>2]|0)|0,k)|0)+(c[g+21764>>2]|0)|0;T=161;break}}while(0);do if((T|0)==161){k=c[m+(l<<2)>>2]|0;c[B+4>>2]=k;c[B>>2]=k;j=(w|0)!=0;if(!(j|(k&15|0)==0)){t=p|20744;c[h>>2]=t;A=n;j=l;s=20744;break}if(!j){j=k&48;if(j|0){t=p|j|61696;c[h>>2]=t;A=n;j=l;s=20744;break}}s=(c[f+26732>>2]|0)==0?20800:20744;t=p|61504;c[h>>2]=t;A=n;j=l}while(0);m=c[(c[f+26644>>2]|0)+(j<<2)>>2]|0;l=(c[v+32>>2]|0)+(m<<2)|0;m=(c[v+36>>2]|0)+(m<<2)|0;n=j<<2;j=(c[v+64>>2]|0)+n|0;n=(c[v+68>>2]|0)+n|0;if(!A)if(c[u>>2]&1){h=D<<1;l=l+(h<<2)|0;m=m+(h<<2)|0;j=j+2|0;n=n+2|0}q=g+22220|0;r=g+22412|0;p=g+21836|0;o=t&128;if(c[f+197608>>2]|0)if(!o)o=0;else{r=c[u>>2]&1;p=g+21964+(r<<7)|0;q=g+22604+(r*384|0)|0;r=g+22604+(r*384|0)+192|0}C=c[v+628>>2]<<4&k>>3;do if((o|0)==(k&128|0)){if(t&8|0){c[g+44148>>2]=0;c[g+44156>>2]=0;c[g+44164>>2]=0;c[g+44172>>2]=0;if(!(k&7)){j=a[j>>0]|0;k=j<<24>>24>-1;if(k)j=q+((j<<24>>24)+C<<2)|0;else j=r+((a[n>>0]|0)+C<<2)|0;T=c[j>>2]|0;S=c[p+(T<<2)>>2]|0;h=k?l:m;k=b[h>>1]|0;l=(_(k,S)|0)+128>>8;h=b[h+2>>1]|0;S=(_(h,S)|0)+128>>8;j=S<<16|l&65535;k=S-h<<16|l-k&65535;l=_(T,16843009)|0}else{j=0;k=0;l=0}c[g+44108>>2]=l;c[g+44116>>2]=l;c[g+44124>>2]=l;c[g+44132>>2]=l;c[g+43824>>2]=j;c[g+43828>>2]=j;c[g+43832>>2]=j;c[g+43836>>2]=j;c[g+43856>>2]=j;c[g+43860>>2]=j;c[g+43864>>2]=j;c[g+43868>>2]=j;c[g+43888>>2]=j;c[g+43892>>2]=j;c[g+43896>>2]=j;c[g+43900>>2]=j;c[g+43920>>2]=j;c[g+43924>>2]=j;c[g+43928>>2]=j;c[g+43932>>2]=j;c[g+43984>>2]=k;c[g+43988>>2]=k;c[g+43992>>2]=k;c[g+43996>>2]=k;c[g+44016>>2]=k;c[g+44020>>2]=k;c[g+44024>>2]=k;c[g+44028>>2]=k;c[g+44048>>2]=k;c[g+44052>>2]=k;c[g+44056>>2]=k;c[g+44060>>2]=k;c[g+44080>>2]=k;c[g+44084>>2]=k;c[g+44088>>2]=k;c[g+44092>>2]=k;break}f=(w|0)==0;B=s&65535;A=(k&7|0)==0;x=(s&8|0)==0;y=D*3|0;z=0;do{s=z&1;u=z>>1;k=g+44376+(z<<1)|0;if(f)T=194;else if(b[k>>1]&256)T=194;do if((T|0)==194){T=0;b[k>>1]=B;v=z<<2;o=d[96537+v>>0]|0;h=g+44136+o|0;b[h>>1]=0;b[h+8>>1]=0;if(!A){h=g+44096+o|0;b[h>>1]=0;b[h+8>>1]=0;h=g+43776+(o<<2)|0;c[h>>2]=0;c[h+4>>2]=0;c[h+32>>2]=0;c[h+36>>2]=0;h=g+43936+(o<<2)|0;c[h>>2]=0;c[h+4>>2]=0;c[h+32>>2]=0;c[h+36>>2]=0;break}k=a[j+z>>0]|0;if(k<<24>>24>-1){w=l;k=q+((k<<24>>24)+C<<2)|0}else{w=m;k=r+((a[n+z>>0]|0)+C<<2)|0}S=c[k>>2]|0;t=c[p+(S<<2)>>2]|0;h=g+44096+o|0;S=S*257&65535;b[h>>1]=S;b[h+8>>1]=S;if(!x){Q=(_(y,u)|0)+(0-s&3)|0;S=b[w+(Q<<2)>>1]|0;R=(_(S<<16>>16,t)|0)+128>>8;Q=b[w+(Q<<2)+2>>1]|0;P=(_(Q<<16>>16,t)|0)+128>>8;h=g+43776+(o<<2)|0;O=P<<16|R&65535;c[h>>2]=O;c[h+4>>2]=O;c[h+32>>2]=O;c[h+36>>2]=O;h=g+43936+(o<<2)|0;S=P-(Q&65535)<<16|R-(S&65535)&65535;c[h>>2]=S;c[h+4>>2]=S;c[h+32>>2]=S;c[h+36>>2]=S;break}s=s<<1;k=u<<1;o=0;do{Q=(_((o>>1)+k|0,D)|0)+(o&1|s)|0;S=w+(Q<<2)|0;h=d[96537+(o+v)>>0]|0;R=((_(b[S>>1]|0,t)|0)+128|0)>>>8;b[g+43776+(h<<2)>>1]=R;Q=w+(Q<<2)+2|0;P=((_(b[Q>>1]|0,t)|0)+128|0)>>>8;b[g+43776+(h<<2)+2>>1]=P;c[g+43936+(h<<2)>>2]=P-(e[Q>>1]|0)<<16|R-(e[S>>1]|0)&65535;o=o+1|0}while((o|0)!=4)}while(0);z=z+1|0}while((z|0)!=4)}else{z=o>>>6^2;y=(w|0)==0;u=s&65535;x=0;do{v=x&1;w=x>>1;k=g+44376+(x<<1)|0;if(y)T=177;else if(b[k>>1]&256)T=177;do if((T|0)==177){T=0;b[k>>1]=u;s=d[96537+(x<<2)>>0]|0;h=g+44136+s|0;b[h>>1]=0;b[h+8>>1]=0;if(c[B+(w<<2)>>2]&7|0){h=g+44096+s|0;b[h>>1]=0;b[h+8>>1]=0;h=g+43776+(s<<2)|0;c[h>>2]=0;c[h+4>>2]=0;c[h+32>>2]=0;c[h+36>>2]=0;h=g+43936+(s<<2)|0;c[h>>2]=0;c[h+4>>2]=0;c[h+32>>2]=0;c[h+36>>2]=0;break}k=(_(w,A)|0)+v|0;o=a[j+k>>0]|0;if(o<<24>>24>-1){t=l;k=q+((o<<24>>24)+C<<2)|0}else{t=m;k=r+((a[n+k>>0]|0)+C<<2)|0}Q=c[k>>2]|0;P=c[p+(Q<<2)>>2]|0;S=g+44096+s|0;Q=Q*257&65535;b[S>>1]=Q;b[S+8>>1]=Q;S=(0-v&3)+(_(w,D)|0)|0;Q=(b[t+(S<<2)+2>>1]<<z|0)/2|0;S=b[t+(S<<2)>>1]|0;R=(_(S<<16>>16,P)|0)+128>>8;P=(_(Q,P)|0)+128>>8;h=g+43776+(s<<2)|0;O=P<<16|R&65535;c[h>>2]=O;c[h+4>>2]=O;c[h+32>>2]=O;c[h+36>>2]=O;h=g+43936+(s<<2)|0;S=P-Q<<16|R-(S&65535)&65535;c[h>>2]=S;c[h+4>>2]=S;c[h+32>>2]=S;c[h+36>>2]=S}while(0);x=x+1|0}while((x|0)!=4)}while(0);i=U;return}j=c[h>>2]|0;S=j&64;R=(S|0)==0;u=0;t=61440;while(1){m=a[g+44096+(u*40|0)+11>>0]|0;o=a[g+44096+(u*40|0)+4>>0]|0;k=a[g+44096+(u*40|0)+8>>0]|0;if(k<<24>>24==-2){s=g+43776+(u*160|0)+12|0;r=a[g+44096+(u*40|0)+3>>0]|0}else{s=g+43776+(u*160|0)+32|0;r=k<<24>>24}q=m>>>0>o>>>0?o:m;q=q>>>0>r>>>0?r:q;l=A+(u<<2)|0;c[l>>2]=q;do if((q|0)>-1){n=g+43776+(u*160|0)+44|0;p=g+43776+(u*160|0)+16|0;l=(m|0)==(q|0);k=(o|0)==(q|0);if(((l&1)+(k&1)+((r|0)==(q|0)&1)|0)>>>0<=1){if(l){c[v+(u<<2)>>2]=c[n>>2];k=t;break}if(k){c[v+(u<<2)>>2]=c[p>>2];k=t;break}else{c[v+(u<<2)>>2]=c[s>>2];k=t;break}}Q=b[n>>1]|0;o=Q<<16>>16;k=b[p>>1]|0;l=k<<16>>16;m=b[s>>1]|0;n=m<<16>>16;if(Q<<16>>16>k<<16>>16){if(m<<16>>16>k<<16>>16)l=(n|0)>(o|0)?o:n}else if(k<<16>>16>m<<16>>16)l=(n|0)>(o|0)?n:o;Q=b[g+43776+(u*160|0)+46>>1]|0;o=Q<<16>>16;p=b[g+43776+(u*160|0)+18>>1]|0;k=p<<16>>16;m=b[s+2>>1]|0;n=m<<16>>16;if(Q<<16>>16>p<<16>>16){if(m<<16>>16>p<<16>>16)k=(n|0)>(o|0)?o:n}else if(p<<16>>16>m<<16>>16)k=(n|0)>(o|0)?n:o;c[v+(u<<2)>>2]=k<<16|l&65535;k=t}else{k=~(12288<<(u<<1));c[v+(u<<2)>>2]=0;c[l>>2]=-1;if(R){j=j&k;c[h>>2]=j}k=t&k}while(0);u=u+1|0;if((u|0)==2){m=k;break}else t=k}u=g+21768|0;k=c[A>>2]|0;if((k|0)<0){l=A+4|0;if((c[l>>2]|0)<0){c[l>>2]=0;c[A>>2]=0;if(R){j=j|61440;c[h>>2]=j}z=0;s=j;m=m|61440}else{z=k;s=j}}else{z=k;s=j}k=c[v>>2]|0;j=c[v+4>>2]|0;do if(!(k|S|j)){N=_(z&255,16843009)|0;c[g+44108>>2]=N;c[g+44116>>2]=N;c[g+44124>>2]=N;c[g+44132>>2]=N;N=_(c[A+4>>2]&255,16843009)|0;c[g+44148>>2]=N;c[g+44156>>2]=N;c[g+44164>>2]=N;c[g+44172>>2]=N;N=g+43824|0;O=g+43856|0;P=g+43888|0;Q=g+43920|0;R=g+43984|0;S=g+44016|0;T=g+44048|0;g=g+44080|0;c[N>>2]=0;c[N+4>>2]=0;c[N+8>>2]=0;c[N+12>>2]=0;c[O>>2]=0;c[O+4>>2]=0;c[O+8>>2]=0;c[O+12>>2]=0;c[P>>2]=0;c[P+4>>2]=0;c[P+8>>2]=0;c[P+12>>2]=0;c[Q>>2]=0;c[Q+4>>2]=0;c[Q+8>>2]=0;c[Q+12>>2]=0;c[R>>2]=0;c[R+4>>2]=0;c[R+8>>2]=0;c[R+12>>2]=0;c[S>>2]=0;c[S+4>>2]=0;c[S+8>>2]=0;c[S+12>>2]=0;c[T>>2]=0;c[T+4>>2]=0;c[T+8>>2]=0;c[T+12>>2]=0;c[g>>2]=0;c[g+4>>2]=0;c[g+8>>2]=0;c[g+12>>2]=0;c[h>>2]=c[h>>2]&-41337|264}else{Q=g+25340|0;t=c[Q>>2]|0;p=c[t+44>>2]|0;l=(s&128|0)!=0;do if(!(c[p+(x<<2)>>2]&128))if(l){q=c[f+198236>>2]|0;r=_(c[u>>2]&-2,q)|0;r=r+(c[g+21764>>2]|0)|0;o=c[p+(r<<2)>>2]|0;c[B>>2]=o;l=c[p+(r+q<<2)>>2]|0;n=B+4|0;c[n>>2]=l;q=q<<2|2;p=D*6|0;if(!((l^o)&128))n=o;else{P=o&-129;c[B>>2]=P;l=l&-129;c[n>>2]=l;n=P}o=m|264;if(n&15|0)if(!((S|0)!=0|(l&15|0)==0)){s=s|272;c[h>>2]=s;D=p;y=q;m=r;q=o;break}s=s|64;c[h>>2]=s;D=p;y=q;m=r;q=o}else{r=2;q=x;T=45}else{n=c[f+198236>>2]|0;if(l){r=2;q=(_(c[g+21820>>2]|0,n)|0)+x|0;T=45;break}else{r=0;q=(_((c[u>>2]&-2)+(c[g+21816>>2]|0)|0,n)|0)+(c[g+21764>>2]|0)|0;T=45;break}}while(0);do if((T|0)==45){n=c[p+(q<<2)>>2]|0;c[B+4>>2]=n;c[B>>2]=n;o=m|264;l=(S|0)!=0;if(!(l|(n&15|0)==0)){s=s|264;c[h>>2]=s;y=r;m=q;q=o;break}if(!l){l=n&48;if(l|0){s=s|l|256;c[h>>2]=s;y=r;m=q;q=o;break}}P=(c[f+26732>>2]|0)==0?o+56|0:o;s=s|64;c[h>>2]=s;y=r;m=q;q=P}while(0);o=c[(c[f+26644>>2]|0)+(m<<2)>>2]|0;l=(c[t+32>>2]|0)+(o<<2)|0;o=(c[t+36>>2]|0)+(o<<2)|0;p=m<<2;m=(c[t+64>>2]|0)+p|0;p=(c[t+68>>2]|0)+p|0;if(!y)if(!(c[u>>2]&1)){P=l;O=o;N=m}else{O=D<<1;P=l+(O<<2)|0;O=o+(O<<2)|0;N=m+2|0;p=p+2|0}else{P=l;O=o;N=m}if((s^n)&128|0){w=q&65535;x=(z&255)*257&65535;u=c[A+4>>2]|0;v=(u&255)*257&65535;t=(z|0)>0?k:0;u=(u|0)>0?j:0;if(R){r=0;l=0;do{n=r&1;T=r>>1;m=(_(T,y)|0)+n|0;n=(0-n&3)+(_(T,D)|0)|0;b[g+44376+(r<<1)>>1]=w;q=d[96537+(r<<2)>>0]|0;R=g+44096+q|0;b[R>>1]=x;b[R+8>>1]=x;R=g+44136+q|0;b[R>>1]=v;b[R+8>>1]=v;do if(!(c[B+(T<<2)>>2]&7))if(!(c[(c[Q>>2]|0)+96>>2]|0)){o=a[N+m>>0]|0;if(!(o<<24>>24)){R=b[P+(n<<2)>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}R=b[P+(n<<2)+2>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}}else{if(o<<24>>24>=0){m=k;n=j;break}if(a[p+m>>0]|0){m=k;n=j;break}R=b[O+(n<<2)>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}R=b[O+(n<<2)+2>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}}m=t;n=u;l=l+1|0}else{m=k;n=j}else{m=k;n=j}while(0);T=g+43776+(q<<2)|0;c[T>>2]=m;c[T+4>>2]=m;c[T+32>>2]=m;c[T+36>>2]=m;T=g+43936+(q<<2)|0;c[T>>2]=n;c[T+4>>2]=n;c[T+32>>2]=n;c[T+36>>2]=n;r=r+1|0}while((r|0)!=4);j=l}else{s=0;l=0;do{q=s&1;n=s>>1;o=(_(n,y)|0)+q|0;q=(0-q&3)+(_(n,D)|0)|0;m=g+44376+(s<<1)|0;if(b[m>>1]&256){b[m>>1]=w;r=d[96537+(s<<2)>>0]|0;T=g+44096+r|0;b[T>>1]=x;b[T+8>>1]=x;T=g+44136+r|0;b[T>>1]=v;b[T+8>>1]=v;do if(!(c[B+(n<<2)>>2]&7))if(!(c[(c[Q>>2]|0)+96>>2]|0)){m=a[N+o>>0]|0;if(!(m<<24>>24)){R=b[P+(q<<2)>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}R=b[P+(q<<2)+2>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}}else{if(m<<24>>24>=0){m=k;n=j;break}if(a[p+o>>0]|0){m=k;n=j;break}R=b[O+(q<<2)>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}R=b[O+(q<<2)+2>>1]|0;T=R<<16>>16;if(((R<<16>>16>-1?T:0-T|0)|0)>=2){m=k;n=j;break}}m=t;n=u;l=l+1|0}else{m=k;n=j}else{m=k;n=j}while(0);T=g+43776+(r<<2)|0;c[T>>2]=m;c[T+4>>2]=m;c[T+32>>2]=m;c[T+36>>2]=m;T=g+43936+(r<<2)|0;c[T>>2]=n;c[T+4>>2]=n;c[T+32>>2]=n;c[T+36>>2]=n}s=s+1|0}while((s|0)!=4);j=l}if(j&3|S|0)break;c[h>>2]=c[h>>2]&-41337|264;break}if(s&8|0){m=_(z&255,16843009)|0;c[g+44108>>2]=m;c[g+44116>>2]=m;c[g+44124>>2]=m;c[g+44132>>2]=m;m=c[A+4>>2]|0;T=_(m&255,16843009)|0;c[g+44148>>2]=T;c[g+44156>>2]=T;c[g+44164>>2]=T;c[g+44172>>2]=T;do if(!(n&7))if(!(c[t+96>>2]|0)){l=a[N>>0]|0;if(!(l<<24>>24)){h=b[P>>1]|0;T=h<<16>>16;if(((h<<16>>16>-1?T:0-T|0)|0)>=2)break;h=b[P+2>>1]|0;T=h<<16>>16;if(((h<<16>>16>-1?T:0-T|0)|0)>=2)break}else{if(l<<24>>24>=0)break;if(a[p>>0]|0)break;h=b[O>>1]|0;T=h<<16>>16;if(((h<<16>>16>-1?T:0-T|0)|0)>=2)break;h=b[O+2>>1]|0;T=h<<16>>16;if(((h<<16>>16>-1?T:0-T|0)|0)>=2)break;if((c[f+198220>>2]|0)>>>0<=33)break}k=(z|0)>0?k:0;j=(m|0)>0?j:0}while(0);c[g+43824>>2]=k;c[g+43828>>2]=k;c[g+43832>>2]=k;c[g+43836>>2]=k;c[g+43856>>2]=k;c[g+43860>>2]=k;c[g+43864>>2]=k;c[g+43868>>2]=k;c[g+43888>>2]=k;c[g+43892>>2]=k;c[g+43896>>2]=k;c[g+43900>>2]=k;c[g+43920>>2]=k;c[g+43924>>2]=k;c[g+43928>>2]=k;c[g+43932>>2]=k;c[g+43984>>2]=j;c[g+43988>>2]=j;c[g+43992>>2]=j;c[g+43996>>2]=j;c[g+44016>>2]=j;c[g+44020>>2]=j;c[g+44024>>2]=j;c[g+44028>>2]=j;c[g+44048>>2]=j;c[g+44052>>2]=j;c[g+44056>>2]=j;c[g+44060>>2]=j;c[g+44080>>2]=j;c[g+44084>>2]=j;c[g+44088>>2]=j;c[g+44092>>2]=j;break}L=q&65535;M=(z&255)*257&65535;G=c[A+4>>2]|0;K=(G&255)*257&65535;J=(n&7|0)==0;H=(q&8|0)==0;I=D*3|0;F=(z|0)==0;G=(G|0)==0;C=f+198220|0;E=0;l=0;do{o=E&1;q=E>>1;B=g+44376+(E<<1)|0;if(R)T=98;else if(b[B>>1]&256)T=98;do if((T|0)==98){T=0;b[B>>1]=L;A=E<<2;z=d[96537+A>>0]|0;r=g+43776+(z<<2)|0;c[r>>2]=k;s=r+4|0;c[s>>2]=k;t=r+32|0;c[t>>2]=k;u=r+36|0;c[u>>2]=k;v=g+43936+(z<<2)|0;c[v>>2]=j;w=v+4|0;c[w>>2]=j;x=v+32|0;c[x>>2]=j;y=v+36|0;c[y>>2]=j;f=g+44096+z|0;b[f>>1]=M;b[f+8>>1]=M;f=g+44136+z|0;b[f>>1]=K;b[f+8>>1]=K;if(J)if(!(c[(c[Q>>2]|0)+96>>2]|0)){m=a[N+E>>0]|0;n=m<<24>>24==0;if(!n){if(m<<24>>24>=0)break;if(a[p+E>>0]|0)break;if((c[C>>2]|0)>>>0<=33)break}f=n?P:O;if(!H){m=(_(I,q)|0)+(0-o&3)|0;A=b[f+(m<<2)>>1]|0;B=A<<16>>16;if(((A<<16>>16>-1?B:0-B|0)|0)>=2)break;f=b[f+(m<<2)+2>>1]|0;B=f<<16>>16;if(((f<<16>>16>-1?B:0-B|0)|0)>=2)break;if(F){c[r>>2]=0;c[s>>2]=0;c[t>>2]=0;c[u>>2]=0}if(G){c[v>>2]=0;c[w>>2]=0;c[x>>2]=0;c[y>>2]=0}l=l+4|0;break}s=o<<1;r=q<<1;o=_(r,D)|0;m=o+s|0;y=b[f+(m<<2)>>1]|0;n=y<<16>>16;n=((y<<16>>16>-1?n:0-n|0)|0)<2;do if(F)if(G){do if(n){x=b[f+(m<<2)+2>>1]|0;y=x<<16>>16;if(((x<<16>>16>-1?y:0-y|0)|0)>=2){m=0;break}c[g+43776+(z<<2)>>2]=0;c[g+43936+(z<<2)>>2]=0;m=1}else m=0;while(0);q=s|1;n=o+q|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;do if(((y<<16>>16>-1?z:0-z|0)|0)<2){y=b[f+(n<<2)+2>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;z=d[96537+(A|1)>>0]|0;c[g+43776+(z<<2)>>2]=0;c[g+43936+(z<<2)>>2]=0;m=m+1|0}while(0);o=_(r|1,D)|0;n=o+s|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;do if(((y<<16>>16>-1?z:0-z|0)|0)<2){y=b[f+(n<<2)+2>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;z=d[96537+(A|2)>>0]|0;c[g+43776+(z<<2)>>2]=0;c[g+43936+(z<<2)>>2]=0;m=m+1|0}while(0);n=o+q|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;z=b[f+(n<<2)+2>>1]|0;f=z<<16>>16;if(((z<<16>>16>-1?f:0-f|0)|0)>=2)break;f=d[96537+(A|3)>>0]|0;c[g+43776+(f<<2)>>2]=0;c[g+43936+(f<<2)>>2]=0;m=m+1|0;break}else{do if(n){x=b[f+(m<<2)+2>>1]|0;y=x<<16>>16;if(((x<<16>>16>-1?y:0-y|0)|0)>=2){m=0;break}c[g+43776+(z<<2)>>2]=0;m=1}else m=0;while(0);q=s|1;n=o+q|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;do if(((y<<16>>16>-1?z:0-z|0)|0)<2){y=b[f+(n<<2)+2>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;c[g+43776+(d[96537+(A|1)>>0]<<2)>>2]=0;m=m+1|0}while(0);o=_(r|1,D)|0;n=o+s|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;do if(((y<<16>>16>-1?z:0-z|0)|0)<2){y=b[f+(n<<2)+2>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;c[g+43776+(d[96537+(A|2)>>0]<<2)>>2]=0;m=m+1|0}while(0);n=o+q|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;z=b[f+(n<<2)+2>>1]|0;f=z<<16>>16;if(((z<<16>>16>-1?f:0-f|0)|0)>=2)break;c[g+43776+(d[96537+(A|3)>>0]<<2)>>2]=0;m=m+1|0;break}else{if(!G){if(n){A=b[f+(m<<2)+2>>1]|0;m=A<<16>>16;m=((A<<16>>16>-1?m:0-m|0)|0)<2&1}else m=0;q=s|1;n=o+q|0;z=b[f+(n<<2)>>1]|0;A=z<<16>>16;if(((z<<16>>16>-1?A:0-A|0)|0)<2){z=b[f+(n<<2)+2>>1]|0;A=z<<16>>16;m=(((z<<16>>16>-1?A:0-A|0)|0)<2&1)+m|0}o=_(r|1,D)|0;n=o+s|0;z=b[f+(n<<2)>>1]|0;A=z<<16>>16;if(((z<<16>>16>-1?A:0-A|0)|0)<2){z=b[f+(n<<2)+2>>1]|0;A=z<<16>>16;m=(((z<<16>>16>-1?A:0-A|0)|0)<2&1)+m|0}n=o+q|0;z=b[f+(n<<2)>>1]|0;A=z<<16>>16;if(((z<<16>>16>-1?A:0-A|0)|0)>=2)break;A=b[f+(n<<2)+2>>1]|0;f=A<<16>>16;m=(((A<<16>>16>-1?f:0-f|0)|0)<2&1)+m|0;break}do if(n){x=b[f+(m<<2)+2>>1]|0;y=x<<16>>16;if(((x<<16>>16>-1?y:0-y|0)|0)>=2){m=0;break}c[g+43936+(z<<2)>>2]=0;m=1}else m=0;while(0);q=s|1;n=o+q|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;do if(((y<<16>>16>-1?z:0-z|0)|0)<2){y=b[f+(n<<2)+2>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;c[g+43936+(d[96537+(A|1)>>0]<<2)>>2]=0;m=m+1|0}while(0);o=_(r|1,D)|0;n=o+s|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;do if(((y<<16>>16>-1?z:0-z|0)|0)<2){y=b[f+(n<<2)+2>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;c[g+43936+(d[96537+(A|2)>>0]<<2)>>2]=0;m=m+1|0}while(0);n=o+q|0;y=b[f+(n<<2)>>1]|0;z=y<<16>>16;if(((y<<16>>16>-1?z:0-z|0)|0)>=2)break;z=b[f+(n<<2)+2>>1]|0;f=z<<16>>16;if(((z<<16>>16>-1?f:0-f|0)|0)>=2)break;c[g+43936+(d[96537+(A|3)>>0]<<2)>>2]=0;m=m+1|0}while(0);if(!(m&3))b[B>>1]=(e[B>>1]|0)+65480;l=m+l|0}}while(0);E=E+1|0}while((E|0)!=4);j=l;if(!(j&15|S))c[h>>2]=c[h>>2]&-41337|264}while(0);i=U;return}function sc(a,b){a=a|0;b=b|0;var d=0;if(!(c[b+21792>>2]|0))if(!(c[(c[a+24668>>2]|0)+(c[b+21772>>2]<<2)>>2]&4))d=(c[b+824>>2]|0)==0;else d=1;else d=1;if((c[a+26672>>2]|0)==3){if(!d)if(!(c[a+25960>>2]|0)){vc(a,b);return}tc(a,b);return}if(d){xc(a,b);return}if(!(c[a+25960>>2]|0)){Dc(a,b);return}else{Ac(a,b);return}}function tc(f,g){f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0;fa=i;i=i+32|0;R=fa+20|0;S=fa;da=fa+8|0;Z=g+21764|0;h=c[Z>>2]|0;X=g+21768|0;k=c[X>>2]|0;V=g+21772|0;j=c[V>>2]|0;r=f+24668|0;aa=c[(c[r>>2]|0)+(j<<2)>>2]|0;Q=g+824|0;if(!(c[Q>>2]|0))ca=(c[f+26676>>2]|0)!=0;else ca=0;W=f+24624|0;ba=f+25960|0;G=g+21748|0;H=f+12|0;T=c[ba>>2]|0;o=c[G>>2]|0;n=(c[c[W>>2]>>2]|0)+((_(o,k)|0)+(h<<T)<<4)|0;c[da>>2]=n;T=n+(_(h<<2&12,o)|0)+(64<<T)|0;ob[c[H>>2]&1023](T,o,4);o=c[ba>>2]|0;T=c[G>>2]|0;l=(c[(c[W>>2]|0)+4>>2]|0)+((_(T,k)|0)+(h<<o)<<4)|0;U=da+4|0;c[U>>2]=l;o=l+(_(c[Z>>2]<<2&12,T)|0)+(64<<o)|0;ob[c[H>>2]&1023](o,T,4);T=c[ba>>2]|0;o=c[G>>2]|0;h=(c[(c[W>>2]|0)+8>>2]|0)+((_(o,k)|0)+(h<<T)<<4)|0;W=da+8|0;c[W>>2]=h;T=h+(_(c[Z>>2]<<2&12,o)|0)+(64<<T)|0;ob[c[H>>2]&1023](T,o,4);o=g+23380|0;a[(c[f+197620>>2]|0)+j>>0]=c[o>>2];T=g+21796|0;j=c[G>>2]|0;a:do if(!(c[T>>2]|0)){c[g+21760>>2]=j;c[g+21756>>2]=j;F=l;E=h;$=f+26260|0;ea=j}else{q=j<<1;c[g+21760>>2]=q;c[g+21756>>2]=q;p=f+26452|0;if(!(k&1))m=h;else{m=_(j,-15)|0;n=n+m|0;c[da>>2]=n;l=l+m|0;c[U>>2]=l;m=h+m|0;c[W>>2]=m}if(!(c[f+197608>>2]|0)){F=l;E=m;$=p;ea=q}else{h=c[o>>2]|0;if(!h){F=l;E=m;$=p;ea=q}else{if(!(aa&8))k=0;else{j=0;while(1){if(12288<<(j<<1)&aa){ea=g+44096+(j*40|0)+12|0;h=_((a[ea>>0]|0)+16^c[X>>2]&1,16843009)|0;c[ea>>2]=h;c[g+44096+(j*40|0)+20>>2]=h;c[g+44096+(j*40|0)+28>>2]=h;c[g+44096+(j*40|0)+36>>2]=h;h=c[o>>2]|0}j=j+1|0;if(j>>>0>=h>>>0){F=l;E=m;$=p;ea=q;break a}}}do{if(12288<<(k<<1)&aa){h=g+44096+(k*40|0)+12|0;j=a[h>>0]|0;if(j<<24>>24>-1){ea=(c[X>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=ea;b[g+44096+(k*40|0)+20>>1]=ea}h=g+44096+(k*40|0)+14|0;j=a[h>>0]|0;if(j<<24>>24>-1){ea=(c[X>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=ea;b[g+44096+(k*40|0)+22>>1]=ea}h=g+44096+(k*40|0)+28|0;j=a[h>>0]|0;if(j<<24>>24>-1){ea=(c[X>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=ea;b[g+44096+(k*40|0)+36>>1]=ea}h=g+44096+(k*40|0)+30|0;j=a[h>>0]|0;if(j<<24>>24>-1){ea=(c[X>>2]&1^(j<<24>>24)+16)*257&65535;b[h>>1]=ea;b[g+44096+(k*40|0)+38>>1]=ea}h=c[o>>2]|0}k=k+1|0}while(k>>>0<h>>>0);F=l;E=m;$=p;ea=q}}}while(0);if(aa&4|0){if(!(c[ba>>2]|0)){o=g+43608|0;p=ea<<1;q=ea*3|0;r=ea<<2;s=ea*5|0;t=ea*6|0;u=ea*7|0;v=ea<<3;w=ea*9|0;x=ea*10|0;y=ea*11|0;z=ea*12|0;A=ea*13|0;B=ea*14|0;D=ea*15|0;h=0;while(1){j=h<<8;k=n;l=(c[o>>2]|0)+j|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+ea|0;l=(c[o>>2]|0)+j+16|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+p|0;l=(c[o>>2]|0)+j+32|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+q|0;l=(c[o>>2]|0)+j+48|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+r|0;l=(c[o>>2]|0)+j+64|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+s|0;l=(c[o>>2]|0)+j+80|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+t|0;l=(c[o>>2]|0)+j+96|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+u|0;l=(c[o>>2]|0)+j+112|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+v|0;l=(c[o>>2]|0)+j+128|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+w|0;l=(c[o>>2]|0)+j+144|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+x|0;l=(c[o>>2]|0)+j+160|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+y|0;l=(c[o>>2]|0)+j+176|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+z|0;l=(c[o>>2]|0)+j+192|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+A|0;l=(c[o>>2]|0)+j+208|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+B|0;l=(c[o>>2]|0)+j+224|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));k=n+D|0;l=(c[o>>2]|0)+j+240|0;m=k+16|0;do{a[k>>0]=a[l>>0]|0;k=k+1|0;l=l+1|0}while((k|0)<(m|0));h=h+1|0;if((h|0)==3)break;n=c[da+(h<<2)>>2]|0}i=fa;return}r=c[f+27844>>2]|0;q=c[g+43608>>2]|0;o=r*768|0;p=(q|0)!=0&o>>>0<2147483640;q=p?q:0;o=p?o|8:8;p=32-r|0;h=0;l=0;do{k=n+(_(l,ea)|0)|0;j=0;do{da=q+(h>>>3)|0;da=(MF(d[da>>0]|d[da+1>>0]<<8|d[da+2>>0]<<16|d[da+3>>0]<<24|0)|0)<<(h&7)>>>p;h=h+r|0;h=o>>>0>h>>>0?h:o;b[k+(j<<1)>>1]=da;j=j+1|0}while((j|0)!=16);l=l+1|0}while((l|0)!=16);m=c[U>>2]|0;l=0;do{k=m+(_(l,ea)|0)|0;j=0;do{da=q+(h>>>3)|0;da=(MF(d[da>>0]|d[da+1>>0]<<8|d[da+2>>0]<<16|d[da+3>>0]<<24|0)|0)<<(h&7)>>>p;h=h+r|0;h=o>>>0>h>>>0?h:o;b[k+(j<<1)>>1]=da;j=j+1|0}while((j|0)!=16);l=l+1|0}while((l|0)!=16);m=c[W>>2]|0;j=0;do{l=m+(_(j,ea)|0)|0;k=0;do{da=q+(h>>>3)|0;da=(MF(d[da>>0]|d[da+1>>0]<<8|d[da+2>>0]<<16|d[da+3>>0]<<24|0)|0)<<(h&7)>>>p;h=h+r|0;h=o>>>0>h>>>0?h:o;b[l+(k<<1)>>1]=da;k=k+1|0}while((k|0)!=16);j=j+1|0}while((j|0)!=16);i=fa;return}b:do if(!(aa&7)){p=c[f+203640>>2]|0;k=f+152|0;B=c[f+203644>>2]|0;l=f+168|0;m=f+16|0;o=f+32|0;D=c[(c[r>>2]|0)+(c[V>>2]<<2)>>2]|0;h=c[ba>>2]|0;j=a[g+44108>>0]|0;if(j<<24>>24>-1){V=j<<24>>24;W=c[g+43824>>2]|0;U=c[Z>>2]|0;W=((U<<4|8)+(W<<16>>18)<<h)+(64<<h)+(_((c[X>>2]<<4|U<<2&12)+(W>>18)|0,c[g+21756>>2]|0)|0)|0;ob[c[H>>2]&1023]((c[g+23384+(V*40|0)>>2]|0)+W|0,c[G>>2]|0,4);ob[c[H>>2]&1023]((c[g+23384+(V*40|0)+4>>2]|0)+W|0,c[G>>2]|0,4);ob[c[H>>2]&1023]((c[g+23384+(V*40|0)+8>>2]|0)+W|0,c[G>>2]|0,4)}do if(!(D&8)){if(D&16|0){V=p+64|0;W=B+64|0;uc(f,g,0,0,8,8<<c[ba>>2],n,F,E,0,0,V,c[k>>2]|0,W,c[l>>2]|0,m,o,D&4096,D&16384);uc(f,g,8,0,8,8<<c[ba>>2],n,F,E,0,4,V,c[k>>2]|0,W,c[l>>2]|0,m,o,D&8192,D&32768);break}q=g+21756|0;if(D&32|0){R=p+64|0;S=f+156|0;T=B+64|0;U=f+172|0;V=f+20|0;W=f+36|0;uc(f,g,0,0,16,c[q>>2]<<3,n,F,E,0,0,R,c[S>>2]|0,T,c[U>>2]|0,V,W,D&4096,D&16384);uc(f,g,4,0,16,c[q>>2]<<3,n,F,E,4,0,R,c[S>>2]|0,T,c[U>>2]|0,V,W,D&8192,D&32768);break}r=p+128|0;s=f+160|0;t=B+128|0;u=f+176|0;v=f+24|0;w=f+40|0;x=f+156|0;y=f+172|0;z=f+20|0;A=f+36|0;p=p+64|0;h=B+64|0;o=0;do{j=e[g+44376+(o<<1)>>1]|0;k=o<<2;l=k&4;m=o<<1&4;do if(!(j&8)){if(j&16|0){V=j&4096;W=j&16384;uc(f,g,k,0,4,4<<c[ba>>2],n,F,E,l,m,r,c[x>>2]|0,t,c[y>>2]|0,z,A,V,W);uc(f,g,k|2,0,4,4<<c[ba>>2],n,F,E,l,m|2,r,c[x>>2]|0,t,c[y>>2]|0,z,A,V,W);break}if(!(j&32)){V=j&4096;W=j&16384;uc(f,g,k,1,4,0,n,F,E,l,m,r,c[s>>2]|0,t,c[u>>2]|0,v,w,V,W);T=l|2;uc(f,g,k|1,1,4,0,n,F,E,T,m,r,c[s>>2]|0,t,c[u>>2]|0,v,w,V,W);U=m|2;uc(f,g,k|2,1,4,0,n,F,E,l,U,r,c[s>>2]|0,t,c[u>>2]|0,v,w,V,W);uc(f,g,k|3,1,4,0,n,F,E,T,U,r,c[s>>2]|0,t,c[u>>2]|0,v,w,V,W);break}else{V=j&4096;W=j&16384;uc(f,g,k,0,8,c[q>>2]<<2,n,F,E,l,m,r,c[s>>2]|0,t,c[u>>2]|0,v,w,V,W);uc(f,g,k|1,0,8,c[q>>2]<<2,n,F,E,l|2,m,r,c[s>>2]|0,t,c[u>>2]|0,v,w,V,W);break}}else uc(f,g,k,1,8,0,n,F,E,l,m,p,c[x>>2]|0,h,c[y>>2]|0,z,A,j&4096,j&16384);while(0);o=o+1|0}while((o|0)!=4)}else uc(f,g,0,1,16,0,n,F,E,0,0,p,c[k>>2]|0,B,c[l>>2]|0,m,o,D&4096,D&16384);while(0);if(D&49152|0){h=c[ba>>2]|0;j=a[g+44148>>0]|0;if(j<<24>>24>-1){W=j<<24>>24;V=c[g+43984>>2]|0;Z=c[Z>>2]|0;Z=((Z<<4|8)+(V<<16>>18)<<h)+(64<<h)+(_((c[X>>2]<<4|Z<<2&12)+(V>>18)|0,c[g+21756>>2]|0)|0)|0;ob[c[H>>2]&1023]((c[g+25304+(W*40|0)>>2]|0)+Z|0,c[G>>2]|0,4);ob[c[H>>2]&1023]((c[g+25304+(W*40|0)+4>>2]|0)+Z|0,c[G>>2]|0,4);ob[c[H>>2]&1023]((c[g+25304+(W*40|0)+8>>2]|0)+Z|0,c[G>>2]|0,4)}}}else{P=g+844|0;j=c[P>>2]|0;c:do if(j|0){x=c[ba>>2]|0;do if(!(c[f+197608>>2]|0))h=1;else{h=(c[g+21800>>2]|0)!=0;if(!(c[X>>2]&1)){h=h&1^1;break}else if(h){h=1;break}else break c}while(0);if((j|0)==2){t=c[Z>>2]|0;j=c[g+21708>>2]|0;k=(e[(c[f+197604>>2]|0)+((c[V>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1}else{k=c[Z>>2]|0;t=k;j=(c[X>>2]|0)>((c[T>>2]|0)!=0|0)&1;k=(k|0)>0&1}v=0-(ea+1+x)|0;q=n+v|0;u=F+v|0;v=E+v|0;r=t+-1|0;w=g+43624+(h<<2)|0;s=c[w>>2]|0;n=s+(t*96|0)|0;if(j|0){p=(k|0)!=0;l=(x|0)==0;do if(p){h=(8<<x)+(s+(r*96|0))|0;O=h;N=c[O>>2]|0;O=c[O+4>>2]|0;j=q+(-7<<x)|0;L=j;M=c[L+4>>2]|0;Y=h;c[Y>>2]=c[L>>2];c[Y+4>>2]=M;Y=j;c[Y>>2]=N;c[Y+4>>2]=O;if(l){j=n;h=1;m=q+1|0;l=n;k=c[j>>2]|0;j=c[j+4>>2]|0;Y=44;break}else{l=h+8|0;m=l;h=c[m>>2]|0;m=c[m+4>>2]|0;j=j+8|0;Y=j;k=c[Y+4>>2]|0;c[l>>2]=c[Y>>2];c[l+4>>2]=k;c[j>>2]=h;c[j+4>>2]=m;j=n;m=1<<x;h=m;m=q+m|0;l=n;k=c[j>>2]|0;j=c[j+4>>2]|0;Y=43;break}}else{j=n;k=c[j>>2]|0;j=c[j+4>>2]|0;h=1<<x;m=q+h|0;if(l){l=n;Y=44}else{l=n;Y=43}}while(0);if((Y|0)==43){M=m;Y=c[M+4>>2]|0;O=l;c[O>>2]=c[M>>2];c[O+4>>2]=Y;O=m;c[O>>2]=k;c[O+4>>2]=j;O=s+(t*96|0)+8|0;m=O;Y=c[m>>2]|0;m=c[m+4>>2]|0;l=q+h+8|0;M=l;N=c[M+4>>2]|0;c[O>>2]=c[M>>2];c[O+4>>2]=N;c[l>>2]=Y;c[l+4>>2]=m;l=1;m=h}else if((Y|0)==44){O=m;Y=c[O+4>>2]|0;c[l>>2]=c[O>>2];c[l+4>>2]=Y;l=m;c[l>>2]=k;c[l+4>>2]=j;l=0;m=h}h=(8<<x)+(s+(t*96|0))|0;O=h;N=c[O>>2]|0;O=c[O+4>>2]|0;n=9<<x;j=q+n|0;L=j;M=c[L+4>>2]|0;Y=h;c[Y>>2]=c[L>>2];c[Y+4>>2]=M;Y=j;c[Y>>2]=N;c[Y+4>>2]=O;if(l){M=h+8|0;O=M;N=c[O>>2]|0;O=c[O+4>>2]|0;Y=j+8|0;K=Y;L=c[K+4>>2]|0;c[M>>2]=c[K>>2];c[M+4>>2]=L;c[Y>>2]=N;c[Y+4>>2]=O}h=(c[Z>>2]|0)+1|0;o=f+198232|0;if((h|0)<(c[o>>2]|0)){Y=(c[w>>2]|0)+(h*96|0)|0;O=Y;N=c[O>>2]|0;O=c[O+4>>2]|0;h=q+(17<<x)|0;L=h;M=c[L+4>>2]|0;c[Y>>2]=c[L>>2];c[Y+4>>2]=M;Y=h;c[Y>>2]=N;c[Y+4>>2]=O;if(l){M=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+8|0;O=M;N=c[O>>2]|0;O=c[O+4>>2]|0;Y=h+8|0;K=Y;L=c[K+4>>2]|0;c[M>>2]=c[K>>2];c[M+4>>2]=L;c[Y>>2]=N;c[Y+4>>2]=O}}if(p){h=(24<<x)+(s+(r*96|0))|0;O=h;N=c[O>>2]|0;O=c[O+4>>2]|0;k=-7<<x;j=u+k|0;L=j;M=c[L+4>>2]|0;Y=h;c[Y>>2]=c[L>>2];c[Y+4>>2]=M;Y=j;c[Y>>2]=N;c[Y+4>>2]=O;if(l){M=h+8|0;O=M;N=c[O>>2]|0;O=c[O+4>>2]|0;Y=j+8|0;K=Y;L=c[K+4>>2]|0;c[M>>2]=c[K>>2];c[M+4>>2]=L;c[Y>>2]=N;c[Y+4>>2]=O}j=(40<<x)+(s+(r*96|0))|0;O=j;N=c[O>>2]|0;O=c[O+4>>2]|0;h=v+k|0;L=h;M=c[L+4>>2]|0;Y=j;c[Y>>2]=c[L>>2];c[Y+4>>2]=M;Y=h;c[Y>>2]=N;c[Y+4>>2]=O;if(l){M=j+8|0;O=M;N=c[O>>2]|0;O=c[O+4>>2]|0;Y=h+8|0;K=Y;L=c[K+4>>2]|0;c[M>>2]=c[K>>2];c[M+4>>2]=L;c[Y>>2]=N;c[Y+4>>2]=O;Y=56}else Y=57}else if(l)Y=56;else Y=57;if((Y|0)==56){M=(16<<x)+(s+(t*96|0))|0;N=M;K=c[N>>2]|0;N=c[N+4>>2]|0;l=u+m|0;J=l;L=c[J+4>>2]|0;O=M;c[O>>2]=c[J>>2];c[O+4>>2]=L;O=l;c[O>>2]=K;c[O+4>>2]=N;M=M+8|0;O=M;N=c[O>>2]|0;O=c[O+4>>2]|0;l=l+8|0;K=l;L=c[K+4>>2]|0;c[M>>2]=c[K>>2];c[M+4>>2]=L;c[l>>2]=N;c[l+4>>2]=O;l=1}else if((Y|0)==57){M=(16<<x)+(s+(t*96|0))|0;O=M;N=c[O>>2]|0;O=c[O+4>>2]|0;l=u+m|0;K=l;L=c[K+4>>2]|0;c[M>>2]=c[K>>2];c[M+4>>2]=L;c[l>>2]=N;c[l+4>>2]=O;l=0}h=(24<<x)+(s+(t*96|0))|0;N=h;M=c[N>>2]|0;N=c[N+4>>2]|0;j=u+n|0;K=j;L=c[K+4>>2]|0;O=h;c[O>>2]=c[K>>2];c[O+4>>2]=L;O=j;c[O>>2]=M;c[O+4>>2]=N;if(l){O=h+8|0;M=O;J=c[M>>2]|0;M=c[M+4>>2]|0;L=j+8|0;K=L;I=c[K+4>>2]|0;c[O>>2]=c[K>>2];c[O+4>>2]=I;c[L>>2]=J;c[L+4>>2]=M;L=(32<<x)+(s+(t*96|0))|0;M=L;J=c[M>>2]|0;M=c[M+4>>2]|0;O=v+m|0;I=O;K=c[I+4>>2]|0;N=L;c[N>>2]=c[I>>2];c[N+4>>2]=K;N=O;c[N>>2]=J;c[N+4>>2]=M;L=L+8|0;N=L;M=c[N>>2]|0;N=c[N+4>>2]|0;O=O+8|0;J=O;K=c[J+4>>2]|0;c[L>>2]=c[J>>2];c[L+4>>2]=K;c[O>>2]=M;c[O+4>>2]=N}else{L=(32<<x)+(s+(t*96|0))|0;N=L;M=c[N>>2]|0;N=c[N+4>>2]|0;O=v+m|0;J=O;K=c[J+4>>2]|0;c[L>>2]=c[J>>2];c[L+4>>2]=K;c[O>>2]=M;c[O+4>>2]=N}j=(40<<x)+(s+(t*96|0))|0;N=j;M=c[N>>2]|0;N=c[N+4>>2]|0;h=v+n|0;K=h;L=c[K+4>>2]|0;O=j;c[O>>2]=c[K>>2];c[O+4>>2]=L;O=h;c[O>>2]=M;c[O+4>>2]=N;if(l){L=j+8|0;N=L;M=c[N>>2]|0;N=c[N+4>>2]|0;O=h+8|0;J=O;K=c[J+4>>2]|0;c[L>>2]=c[J>>2];c[L+4>>2]=K;c[O>>2]=M;c[O+4>>2]=N}h=(c[Z>>2]|0)+1|0;if((h|0)<(c[o>>2]|0)){j=16<<x;O=(c[w>>2]|0)+(h*96|0)+j|0;N=O;M=c[N>>2]|0;N=c[N+4>>2]|0;k=17<<x;h=u+k|0;K=h;L=c[K+4>>2]|0;c[O>>2]=c[K>>2];c[O+4>>2]=L;O=h;c[O>>2]=M;c[O+4>>2]=N;if(l){L=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+j+8|0;N=L;M=c[N>>2]|0;N=c[N+4>>2]|0;O=h+8|0;J=O;K=c[J+4>>2]|0;c[L>>2]=c[J>>2];c[L+4>>2]=K;c[O>>2]=M;c[O+4>>2]=N}j=32<<x;O=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+j|0;N=O;M=c[N>>2]|0;N=c[N+4>>2]|0;h=v+k|0;K=h;L=c[K+4>>2]|0;c[O>>2]=c[K>>2];c[O+4>>2]=L;O=h;c[O>>2]=M;c[O+4>>2]=N;if(l){L=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+j+8|0;N=L;M=c[N>>2]|0;N=c[N+4>>2]|0;O=h+8|0;J=O;K=c[J+4>>2]|0;c[L>>2]=c[J>>2];c[L+4>>2]=K;c[O>>2]=M;c[O+4>>2]=N}}}}while(0);u=(aa&1|0)==0;v=g+21636|0;w=f+132|0;x=(aa&16777216|0)==0;y=f+144|0;z=f+26664|0;A=g+21740|0;B=3-ea|0;D=3-((ea|0)/2|0)|0;E=0-ea|0;F=g+44384|0;G=f+108|0;H=f+100|0;I=f+140|0;J=f+198220|0;K=g+21732|0;L=f+112|0;M=f+104|0;O=0;do{N=c[ba>>2]|0;r=c[da+(O<<2)>>2]|0;h=c[((O|0)==0?Q:g+828+(O+-1<<2)|0)>>2]|0;s=O<<4;t=$+(s<<2)|0;d:do if(u){jb[c[f+26172+(c[v>>2]<<2)>>2]&255](r,ea);if(a[(d[96537+(O+48)>>0]|0)+(g+43648)>>0]|0){if(!ca){ob[c[w>>2]&1023](g+44384+(O<<8<<N<<1)|0,g+47456+(O<<6)|0,c[(c[f+197556+(O<<2)>>2]|0)+(h<<6)>>2]|0);break}h=g+44384+(O<<8<<N<<1)|0;j=g+47456+(O<<6)|0;if(!N){b[h>>1]=b[j>>1]|0;b[h+32>>1]=b[g+47456+(O<<6)+2>>1]|0;b[h+128>>1]=b[g+47456+(O<<6)+4>>1]|0;b[h+160>>1]=b[g+47456+(O<<6)+6>>1]|0;b[h+64>>1]=b[g+47456+(O<<6)+8>>1]|0;b[h+96>>1]=b[g+47456+(O<<6)+10>>1]|0;b[h+192>>1]=b[g+47456+(O<<6)+12>>1]|0;b[h+224>>1]=b[g+47456+(O<<6)+14>>1]|0;b[h+256>>1]=b[g+47456+(O<<6)+16>>1]|0;b[h+288>>1]=b[g+47456+(O<<6)+18>>1]|0;b[h+384>>1]=b[g+47456+(O<<6)+20>>1]|0;b[h+416>>1]=b[g+47456+(O<<6)+22>>1]|0;b[h+320>>1]=b[g+47456+(O<<6)+24>>1]|0;b[h+352>>1]=b[g+47456+(O<<6)+26>>1]|0;b[h+448>>1]=b[g+47456+(O<<6)+28>>1]|0;b[h+480>>1]=b[g+47456+(O<<6)+30>>1]|0;break}else{c[h>>2]=c[j>>2];c[h+64>>2]=c[g+47456+(O<<6)+4>>2];c[h+256>>2]=c[g+47456+(O<<6)+8>>2];c[h+320>>2]=c[g+47456+(O<<6)+12>>2];c[h+128>>2]=c[g+47456+(O<<6)+16>>2];c[h+192>>2]=c[g+47456+(O<<6)+20>>2];c[h+384>>2]=c[g+47456+(O<<6)+24>>2];c[h+448>>2]=c[g+47456+(O<<6)+28>>2];c[h+512>>2]=c[g+47456+(O<<6)+32>>2];c[h+576>>2]=c[g+47456+(O<<6)+36>>2];c[h+768>>2]=c[g+47456+(O<<6)+40>>2];c[h+832>>2]=c[g+47456+(O<<6)+44>>2];c[h+640>>2]=c[g+47456+(O<<6)+48>>2];c[h+704>>2]=c[g+47456+(O<<6)+52>>2];c[h+896>>2]=c[g+47456+(O<<6)+56>>2];c[h+960>>2]=c[g+47456+(O<<6)+60>>2];break}}}else{if(!x){if(!ca){l=c[L>>2]|0;m=c[M>>2]|0;n=O<<8;o=(N|0)==0;p=0;while(1){k=r+(c[t+(p<<2)>>2]|0)|0;q=a[(d[96537+(p+s)>>0]|0)+(g+43648)>>0]|0;ub[c[f+26080+(a[(a[96537+p>>0]&255)+(g+21640)>>0]<<24>>24<<2)>>2]&255](k,c[K>>2]<<p&32768,c[A>>2]<<p&16384,ea);switch(q<<24>>24){case 0:break;case 1:{h=(p<<4)+n|0;if(o)j=e[g+44384+(h<<1)>>1]|0;else j=c[F+(h<<2)>>2]|0;if(!j)Y=94;else ob[l&1023](k,g+44384+(h<<N<<1)|0,ea);break}default:{h=(p<<4)+n|0;Y=94}}if((Y|0)==94){Y=0;ob[m&1023](k,g+44384+(h<<N<<1)|0,ea)}p=p+4|0;if((p|0)>=16)break d}}n=c[I>>2]|0;k=O<<8;l=(N|0)==0;o=0;while(1){m=r+(c[t+(o<<2)>>2]|0)|0;q=a[(d[96537+o>>0]|0)+(g+21640)>>0]|0;h=q<<24>>24;e:do if(q<<24>>24<2&(c[z>>2]|0)==244)if((c[J>>2]|0)==-1){fb[c[f+26224+(h<<2)>>2]&127](m,g+44384+((o<<4)+k<<N<<1)|0,c[K>>2]<<o&32768,c[A>>2]<<o&16384,ea);break}else{ob[c[f+26216+(h<<2)>>2]&1023](m,g+44384+((o<<4)+k<<N<<1)|0,ea);break}else{q=a[(d[96537+(o+s)>>0]|0)+(g+43648)>>0]|0;ub[c[f+26080+(h<<2)>>2]&255](m,c[K>>2]<<o&32768,c[A>>2]<<o&16384,ea);switch(q<<24>>24){case 0:break e;case 1:{h=(o<<4)+k|0;if(l)j=e[g+44384+(h<<1)>>1]|0;else j=c[F+(h<<2)>>2]|0;if(j){ob[n&1023](m,g+44384+(h<<N<<1)|0,ea);break e}break}default:h=(o<<4)+k|0}ob[n&1023](m,g+44384+(h<<N<<1)|0,ea)}while(0);o=o+4|0;if((o|0)>=16)break d}}if(!ca){k=c[G>>2]|0;l=c[H>>2]|0;m=O<<8;n=(N|0)==0;o=4<<N;q=0;while(1){p=r+(c[t+(q<<2)>>2]|0)|0;j=a[(d[96537+q>>0]|0)+(g+21640)>>0]|0;h=j<<24>>24;do if((j|4)<<24>>24==7){if(c[A>>2]&32768>>>q|0){j=p+o+E|0;break}if(n){c[R>>2]=_(d[p+B>>0]|0,16843009)|0;j=R;break}else{ga=RF(e[p+(D<<1)>>1]|0,0,65537,65537)|0;j=S;c[j>>2]=ga;c[j+4>>2]=C;j=S;break}}else j=0;while(0);ob[c[f+26020+(h<<2)>>2]&1023](p,j,ea);switch(a[(d[96537+(q+s)>>0]|0)+(g+43648)>>0]|0){case 0:break;case 1:{h=(q<<4)+m|0;if(n)j=e[g+44384+(h<<1)>>1]|0;else j=c[F+(h<<2)>>2]|0;if(!j)Y=130;else ob[k&1023](p,g+44384+(h<<N<<1)|0,ea);break}default:{h=(q<<4)+m|0;Y=130}}if((Y|0)==130){Y=0;ob[l&1023](p,g+44384+(h<<N<<1)|0,ea)}q=q+1|0;if((q|0)==16)break d}}k=c[y>>2]|0;l=O<<8;m=(N|0)==0;n=4<<N;p=0;do{o=r+(c[t+(p<<2)>>2]|0)|0;h=a[(d[96537+p>>0]|0)+(g+21640)>>0]|0;j=h<<24>>24;if(h<<24>>24<2&(c[z>>2]|0)==244)ob[c[f+26208+(j<<2)>>2]&1023](o,g+44384+((p<<4)+l<<N<<1)|0,ea);else{do if((h|4)<<24>>24==7){if(c[A>>2]&32768>>>p|0){h=o+n+E|0;break}if(m){c[R>>2]=_(d[o+B>>0]|0,16843009)|0;h=R;break}else{ga=RF(e[o+(D<<1)>>1]|0,0,65537,65537)|0;h=S;c[h>>2]=ga;c[h+4>>2]=C;h=S;break}}else h=0;while(0);ob[c[f+26020+(j<<2)>>2]&1023](o,h,ea);switch(a[(d[96537+(p+s)>>0]|0)+(g+43648)>>0]|0){case 0:break;case 1:{h=(p<<4)+l|0;if(m)j=e[g+44384+(h<<1)>>1]|0;else j=c[F+(h<<2)>>2]|0;if(!j)Y=113;else ob[k&1023](o,g+44384+(h<<N<<1)|0,ea);break}default:{h=(p<<4)+l|0;Y=113}}if((Y|0)==113){Y=0;ob[k&1023](o,g+44384+(h<<N<<1)|0,ea)}}p=p+1|0}while((p|0)!=16)}while(0);O=O+1|0}while((O|0)!=3);j=c[P>>2]|0;if(j|0){k=c[da>>2]|0;l=c[U>>2]|0;m=c[W>>2]|0;x=c[ba>>2]|0;do if(!(c[f+197608>>2]|0))n=1;else{h=(c[g+21800>>2]|0)!=0;if(!(c[X>>2]&1)){n=h&1^1;break}else if(h){n=1;break}else break b}while(0);if((j|0)==2){u=c[Z>>2]|0;j=c[g+21708>>2]|0;h=(e[(c[f+197604>>2]|0)+((c[V>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1}else{h=c[Z>>2]|0;u=h;j=(c[X>>2]|0)>((c[T>>2]|0)!=0|0)&1;h=(h|0)>0&1}v=0-(ea+1+x)|0;q=k+v|0;t=l+v|0;v=m+v|0;r=u+-1|0;w=g+43624+(n<<2)|0;s=c[w>>2]|0;k=s+(u*96|0)|0;if(j|0){p=(h|0)!=0;m=(x|0)==0;do if(p){h=(8<<x)+(s+(r*96|0))|0;Y=h;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;j=q+(-7<<x)|0;V=j;W=c[V+4>>2]|0;ga=h;c[ga>>2]=c[V>>2];c[ga+4>>2]=W;ga=j;c[ga>>2]=X;c[ga+4>>2]=Y;if(m){h=k;k=q+1|0;l=c[h>>2]|0;h=c[h+4>>2]|0;j=1;Y=154;break}else{W=h+8|0;l=W;Y=c[l>>2]|0;l=c[l+4>>2]|0;h=j+8|0;X=h;ga=c[X+4>>2]|0;j=W;c[j>>2]=c[X>>2];c[j+4>>2]=ga;c[h>>2]=Y;c[h+4>>2]=l;h=k;k=1<<x;j=k;k=q+k|0;l=c[h>>2]|0;h=c[h+4>>2]|0;Y=153;break}}else{h=k;l=c[h>>2]|0;h=c[h+4>>2]|0;j=1<<x;k=q+j|0;if(m)Y=154;else Y=153}while(0);if((Y|0)==153){ga=k;c[ga>>2]=l;c[ga+4>>2]=h;ga=s+(u*96|0)+8|0;l=c[ga+4>>2]|0;m=q+j+8|0;c[m>>2]=c[ga>>2];c[m+4>>2]=l;m=j;l=1}else if((Y|0)==154){m=k;c[m>>2]=l;c[m+4>>2]=h;m=j;l=0}h=(8<<x)+(s+(u*96|0))|0;Y=h;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;n=9<<x;j=q+n|0;V=j;W=c[V+4>>2]|0;ga=h;c[ga>>2]=c[V>>2];c[ga+4>>2]=W;ga=j;c[ga>>2]=X;c[ga+4>>2]=Y;if(l){W=h+8|0;Y=W;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;ga=j+8|0;U=ga;V=c[U+4>>2]|0;c[W>>2]=c[U>>2];c[W+4>>2]=V;c[ga>>2]=X;c[ga+4>>2]=Y}h=(c[Z>>2]|0)+1|0;o=f+198232|0;if((h|0)<(c[o>>2]|0)){ga=(c[w>>2]|0)+(h*96|0)|0;Y=ga;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;h=q+(17<<x)|0;V=h;W=c[V+4>>2]|0;c[ga>>2]=c[V>>2];c[ga+4>>2]=W;ga=h;c[ga>>2]=X;c[ga+4>>2]=Y;if(l){W=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+8|0;Y=W;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;ga=h+8|0;U=ga;V=c[U+4>>2]|0;c[W>>2]=c[U>>2];c[W+4>>2]=V;c[ga>>2]=X;c[ga+4>>2]=Y}}if(p){h=(24<<x)+(s+(r*96|0))|0;Y=h;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;k=-7<<x;j=t+k|0;V=j;W=c[V+4>>2]|0;ga=h;c[ga>>2]=c[V>>2];c[ga+4>>2]=W;ga=j;c[ga>>2]=X;c[ga+4>>2]=Y;if(l){W=h+8|0;Y=W;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;ga=j+8|0;U=ga;V=c[U+4>>2]|0;c[W>>2]=c[U>>2];c[W+4>>2]=V;c[ga>>2]=X;c[ga+4>>2]=Y}j=(40<<x)+(s+(r*96|0))|0;Y=j;X=c[Y>>2]|0;Y=c[Y+4>>2]|0;h=v+k|0;V=h;W=c[V+4>>2]|0;ga=j;c[ga>>2]=c[V>>2];c[ga+4>>2]=W;ga=h;c[ga>>2]=X;c[ga+4>>2]=Y;if(l){W=j+8|0;ga=W;X=c[ga>>2]|0;ga=c[ga+4>>2]|0;Y=h+8|0;U=Y;V=c[U+4>>2]|0;c[W>>2]=c[U>>2];c[W+4>>2]=V;c[Y>>2]=X;c[Y+4>>2]=ga;Y=166}else Y=167}else if(l)Y=166;else Y=167;if((Y|0)==166){X=(16<<x)+(s+(u*96|0))|0;V=X;W=c[V+4>>2]|0;l=t+m|0;ga=l;c[ga>>2]=c[V>>2];c[ga+4>>2]=W;X=X+8|0;ga=c[X+4>>2]|0;l=l+8|0;c[l>>2]=c[X>>2];c[l+4>>2]=ga;l=1}else if((Y|0)==167){X=(16<<x)+(s+(u*96|0))|0;ga=c[X+4>>2]|0;l=t+m|0;c[l>>2]=c[X>>2];c[l+4>>2]=ga;l=0}h=(24<<x)+(s+(u*96|0))|0;X=h;W=c[X>>2]|0;X=c[X+4>>2]|0;j=t+n|0;U=j;V=c[U+4>>2]|0;ga=h;c[ga>>2]=c[U>>2];c[ga+4>>2]=V;ga=j;c[ga>>2]=W;c[ga+4>>2]=X;if(l){ga=h+8|0;U=ga;V=c[U>>2]|0;U=c[U+4>>2]|0;W=j+8|0;T=W;X=c[T+4>>2]|0;c[ga>>2]=c[T>>2];c[ga+4>>2]=X;c[W>>2]=V;c[W+4>>2]=U;W=(32<<x)+(s+(u*96|0))|0;U=W;V=c[U+4>>2]|0;ga=v+m|0;X=ga;c[X>>2]=c[U>>2];c[X+4>>2]=V;W=W+8|0;X=c[W+4>>2]|0;ga=ga+8|0;c[ga>>2]=c[W>>2];c[ga+4>>2]=X}else{W=(32<<x)+(s+(u*96|0))|0;X=c[W+4>>2]|0;ga=v+m|0;c[ga>>2]=c[W>>2];c[ga+4>>2]=X}j=(40<<x)+(s+(u*96|0))|0;X=j;W=c[X>>2]|0;X=c[X+4>>2]|0;h=v+n|0;U=h;V=c[U+4>>2]|0;ga=j;c[ga>>2]=c[U>>2];c[ga+4>>2]=V;ga=h;c[ga>>2]=W;c[ga+4>>2]=X;if(l){V=j+8|0;X=V;W=c[X>>2]|0;X=c[X+4>>2]|0;ga=h+8|0;T=ga;U=c[T+4>>2]|0;c[V>>2]=c[T>>2];c[V+4>>2]=U;c[ga>>2]=W;c[ga+4>>2]=X}h=(c[Z>>2]|0)+1|0;if((h|0)<(c[o>>2]|0)){j=16<<x;ga=(c[w>>2]|0)+(h*96|0)+j|0;X=ga;W=c[X>>2]|0;X=c[X+4>>2]|0;k=17<<x;h=t+k|0;U=h;V=c[U+4>>2]|0;c[ga>>2]=c[U>>2];c[ga+4>>2]=V;ga=h;c[ga>>2]=W;c[ga+4>>2]=X;if(l){V=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+j+8|0;X=V;W=c[X>>2]|0;X=c[X+4>>2]|0;ga=h+8|0;T=ga;U=c[T+4>>2]|0;c[V>>2]=c[T>>2];c[V+4>>2]=U;c[ga>>2]=W;c[ga+4>>2]=X}j=32<<x;ga=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+j|0;X=ga;W=c[X>>2]|0;X=c[X+4>>2]|0;h=v+k|0;U=h;V=c[U+4>>2]|0;c[ga>>2]=c[U>>2];c[ga+4>>2]=V;ga=h;c[ga>>2]=W;c[ga+4>>2]=X;if(l){W=(c[w>>2]|0)+(((c[Z>>2]|0)+1|0)*96|0)+j+8|0;Z=W;X=c[Z>>2]|0;Z=c[Z+4>>2]|0;ga=h+8|0;U=ga;V=c[U+4>>2]|0;c[W>>2]=c[U>>2];c[W+4>>2]=V;c[ga>>2]=X;c[ga+4>>2]=Z}}}}}while(0);A=(aa&1|0)==0;B=(aa&2|0)==0;D=g+21824|0;p=aa&16777216;m=(p|0)!=0;n=m?4:1;o=f+144|0;m=m?f+140|0:o;p=(p|0)==0;q=f+116|0;r=f+120|0;s=f+26664|0;t=g+21636|0;u=g+44384|0;v=f+128|0;z=0;do{l=c[ba>>2]|0;w=c[da+(z<<2)>>2]|0;x=z<<4;y=$+(x<<2)|0;f:do if(A){if(B){if(!(c[D>>2]&15))break;if(!ca)if(p){fb[c[q>>2]&127](w,y,g+44384+(z<<8<<l<<1)|0,ea,(z*40|0)+(g+43648)|0);break}else{fb[c[r>>2]&127](w,y,g+44384+(z<<8<<l<<1)|0,ea,(z*40|0)+(g+43648)|0);break}h=c[m>>2]|0;j=z<<8;k=0;while(1){if(a[(d[96537+(k+x)>>0]|0)+(g+43648)>>0]|0)ob[h&1023](w+(c[y+(k<<2)>>2]|0)|0,g+44384+((k<<4)+j<<l<<1)|0,ea);k=k+n|0;if((k|0)>=16)break f}}if(!ca){fb[c[v>>2]&127](w,y,g+44384+(z<<8<<l<<1)|0,ea,(z*40|0)+(g+43648)|0);break}if((c[s>>2]|0)==244){h=c[t>>2]|0;if((h+-1|0)>>>0<2){ub[c[f+26244+(h<<2)>>2]&255](w,y,g+44384+(z<<8<<l<<1)|0,ea);break}}k=z<<8;if(!l){j=0;do{h=g+44384+((j<<4)+k<<1)|0;if(!(a[(d[96537+(j+x)>>0]|0)+(g+43648)>>0]|0)){if(b[h>>1]|0)Y=207}else Y=207;if((Y|0)==207){Y=0;ob[c[o>>2]&1023](w+(c[y+(j<<2)>>2]|0)|0,h,ea)}j=j+1|0}while((j|0)!=16)}else{j=0;do{h=(j<<4)+k|0;if(!(a[(d[96537+(j+x)>>0]|0)+(g+43648)>>0]|0)){if(c[u+(h<<2)>>2]|0)Y=212}else Y=212;if((Y|0)==212){Y=0;ob[c[o>>2]&1023](w+(c[y+(j<<2)>>2]|0)|0,g+44384+(h<<l<<1)|0,ea)}j=j+1|0}while((j|0)!=16)}}while(0);z=z+1|0}while((z|0)!=3);i=fa;return}function uc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0;L=f+856|0;s=c[L>>2]|0;y=(v|0)!=0;z=(w|0)!=0;if(z&(y&(s|0)==2)){q=f+21768|0;x=c[q>>2]|0;Q=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+Q>>0]|0)*384|0)+(a[f+44136+Q>>0]<<3)+((x&1)<<2)>>2]|0)==32)R=3;else K=q}else R=3;do if((R|0)==3){if((s|0)==1){x=f+21768|0;K=x;x=c[x>>2]|0;break}F=c[e+25960>>2]|0;G=f+21756|0;t=c[G>>2]|0;E=(_(o<<1,t)|0)+(n<<1<<F)|0;C=k+E|0;D=l+E|0;E=m+E|0;A=(c[f+21764>>2]<<3)+n|0;B=f+21796|0;s=c[B>>2]|0;o=(c[f+21768>>2]>>s<<3)+o|0;if(!v)r=p;else{q=d[96537+g>>0]|0;v=a[f+44096+q>>0]|0;i=(b[f+43776+(q<<2)>>1]|0)+(A<<3)|0;q=(b[f+43776+(q<<2)+2>>1]|0)+(o<<3)|0;u=q<<2&12|i&3;l=i>>2;m=q>>2;y=(_(m,t)|0)+(l<<F)|0;x=(c[f+23384+(v*40|0)>>2]|0)+y|0;n=c[e+198232>>2]<<4;z=c[e+198228>>2]<<4>>s;s=(i&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((l|0)<(0-s|0)|(m|0)<(0-q|0))R=63;else if((l+16|0)>(s+n|0))R=63;else if((m+16|0)>(z+q|0))R=63;else{s=t;t=0;q=x}if((R|0)==63){Q=f+43620|0;i=2<<F;eb[c[e+8>>2]&3](c[Q>>2]|0,x+(0-i)+(0-(t<<1))|0,t,t,21,21,l+-2|0,m+-2|0,n,z);q=c[G>>2]|0;s=q;t=1;q=(c[Q>>2]|0)+i+(q<<1)|0}k=p+(u<<2)|0;ob[c[k>>2]&1023](C,q,s);u=(h|0)!=0;if(!u)ob[c[k>>2]&1023](C+j|0,q+j|0,c[G>>2]|0);s=(c[f+23384+(v*40|0)+4>>2]|0)+y|0;x=(t|0)!=0;if(x){i=f+43620|0;p=2<<F;q=c[G>>2]|0;eb[c[e+8>>2]&3](c[i>>2]|0,s+(0-p)+(0-(q<<1))|0,q,q,21,21,l+-2|0,m+-2|0,n,z);s=c[G>>2]|0;q=s;s=(c[i>>2]|0)+p+(s<<1)|0}else q=c[G>>2]|0;ob[c[k>>2]&1023](D,s,q);if(!u)ob[c[k>>2]&1023](D+j|0,s+j|0,c[G>>2]|0);s=(c[f+23384+(v*40|0)+8>>2]|0)+y|0;if(x){i=f+43620|0;p=2<<F;q=c[G>>2]|0;eb[c[e+8>>2]&3](c[i>>2]|0,s+(0-p)+(0-(q<<1))|0,q,q,21,21,l+-2|0,m+-2|0,n,z);s=c[G>>2]|0;q=s;s=(c[i>>2]|0)+p+(s<<1)|0}else q=c[G>>2]|0;ob[c[k>>2]&1023](E,s,q);if(!u)ob[c[k>>2]&1023](E+j|0,s+j|0,c[G>>2]|0)}if(!w)return;q=d[96537+g>>0]|0;m=a[f+44136+q>>0]|0;s=(b[f+43936+(q<<2)>>1]|0)+(A<<3)|0;q=(b[f+43936+(q<<2)+2>>1]|0)+(o<<3)|0;u=q<<2&12|s&3;y=s>>2;z=q>>2;x=c[G>>2]|0;k=(_(z,x)|0)+(y<<F)|0;t=(c[f+25304+(m*40|0)>>2]|0)+k|0;l=c[e+198232>>2]<<4;v=c[e+198228>>2]<<4>>c[B>>2];s=(s&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((y|0)<(0-s|0)|(z|0)<(0-q|0))R=80;else if((y+16|0)>(l+s|0))R=80;else if((z+16|0)>(v+q|0))R=80;else{q=0;s=t}if((R|0)==80){p=f+43620|0;R=2<<F;eb[c[e+8>>2]&3](c[p>>2]|0,t+(0-R)+(0-(x<<1))|0,x,x,21,21,y+-2|0,z+-2|0,l,v);s=c[G>>2]|0;x=s;q=1;s=(c[p>>2]|0)+R+(s<<1)|0}u=r+(u<<2)|0;ob[c[u>>2]&1023](C,s,x);t=(h|0)!=0;if(!t)ob[c[u>>2]&1023](C+j|0,s+j|0,c[G>>2]|0);s=(c[f+25304+(m*40|0)+4>>2]|0)+k|0;x=(q|0)!=0;if(x){h=f+43620|0;R=2<<F;q=c[G>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+(0-R)+(0-(q<<1))|0,q,q,21,21,y+-2|0,z+-2|0,l,v);s=c[G>>2]|0;q=s;s=(c[h>>2]|0)+R+(s<<1)|0}else q=c[G>>2]|0;ob[c[u>>2]&1023](D,s,q);if(!t)ob[c[u>>2]&1023](D+j|0,s+j|0,c[G>>2]|0);s=(c[f+25304+(m*40|0)+8>>2]|0)+k|0;if(x){R=f+43620|0;f=2<<F;q=c[G>>2]|0;eb[c[e+8>>2]&3](c[R>>2]|0,s+(0-f)+(0-(q<<1))|0,q,q,21,21,y+-2|0,z+-2|0,l,v);s=c[G>>2]|0;q=s;s=(c[R>>2]|0)+f+(s<<1)|0}else q=c[G>>2]|0;ob[c[u>>2]&1023](E,s,q);if(t)return;ob[c[u>>2]&1023](E+j|0,s+j|0,c[G>>2]|0);return}while(0);B=c[t>>2]|0;J=c[u>>2]|0;M=c[e+25960>>2]|0;Q=f+21756|0;w=c[Q>>2]|0;N=(_(o<<1,w)|0)+(n<<1<<M)|0;P=k+N|0;O=l+N|0;N=m+N|0;q=(c[f+21764>>2]<<3)+n|0;C=f+21796|0;A=c[C>>2]|0;s=(x>>A<<3)+o|0;if(!(y&z)){n=z&1;t=d[96537+g>>0]|0;m=a[f+44096+(n*40|0)+t>>0]|0;L=(b[f+43776+(n*160|0)+(t<<2)>>1]|0)+(q<<3)|0;q=(b[f+43776+(n*160|0)+(t<<2)+2>>1]|0)+(s<<3)|0;t=q<<2&12|L&3;y=L>>2;z=q>>2;k=(_(z,w)|0)+(y<<M)|0;x=(c[f+23384+(n*1920|0)+(m*40|0)>>2]|0)+k|0;l=c[e+198232>>2]<<4;v=c[e+198228>>2]<<4>>A;s=(L&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((y|0)<(0-s|0)|(z|0)<(0-q|0))R=44;else if((y+16|0)>(s+l|0))R=44;else if((z+16|0)>(v+q|0))R=44;else{s=w;u=0;q=x}if((R|0)==44){L=f+43620|0;R=2<<M;eb[c[e+8>>2]&3](c[L>>2]|0,x+(0-R)+(0-(w<<1))|0,w,w,21,21,y+-2|0,z+-2|0,l,v);q=c[Q>>2]|0;s=q;u=1;q=(c[L>>2]|0)+R+(q<<1)|0}r=p+(t<<2)|0;ob[c[r>>2]&1023](P,q,s);t=(h|0)!=0;if(!t)ob[c[r>>2]&1023](P+j|0,q+j|0,c[Q>>2]|0);s=(c[f+23384+(n*1920|0)+(m*40|0)+4>>2]|0)+k|0;x=(u|0)!=0;if(x){h=f+43620|0;R=2<<M;q=c[Q>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+(0-R)+(0-(q<<1))|0,q,q,21,21,y+-2|0,z+-2|0,l,v);s=c[Q>>2]|0;q=s;s=(c[h>>2]|0)+R+(s<<1)|0}else q=c[Q>>2]|0;ob[c[r>>2]&1023](O,s,q);if(!t)ob[c[r>>2]&1023](O+j|0,s+j|0,c[Q>>2]|0);s=(c[f+23384+(n*1920|0)+(m*40|0)+8>>2]|0)+k|0;if(x){h=f+43620|0;R=2<<M;q=c[Q>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+(0-R)+(0-(q<<1))|0,q,q,21,21,y+-2|0,z+-2|0,l,v);s=c[Q>>2]|0;q=s;s=(c[h>>2]|0)+R+(s<<1)|0}else q=c[Q>>2]|0;ob[c[r>>2]&1023](N,s,q);if(!t)ob[c[r>>2]&1023](N+j|0,s+j|0,c[Q>>2]|0);rb[B&63](P,c[Q>>2]|0,i,c[f+864>>2]|0,c[f+888+(m<<4)+(n<<3)>>2]|0,c[f+888+(m<<4)+(n<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;e=f+21760|0;j=f+868|0;rb[B&63](O,c[e>>2]|0,i,c[j>>2]|0,c[f+1656+(m<<5)+(n<<4)>>2]|0,c[f+1656+(m<<5)+(n<<4)+4>>2]|0);rb[B&63](N,c[e>>2]|0,i,c[j>>2]|0,c[f+1656+(m<<5)+(n<<4)+8>>2]|0,c[f+1656+(m<<5)+(n<<4)+12>>2]|0);return}F=c[f+43616>>2]|0;G=F+(16<<M)|0;H=f+21760|0;I=F+(c[H>>2]<<4)|0;n=d[96537+g>>0]|0;D=a[f+44096+n>>0]|0;E=a[f+44136+n>>0]|0;m=q<<3;g=(b[f+43776+(n<<2)>>1]|0)+m|0;l=s<<3;q=(b[f+43776+(n<<2)+2>>1]|0)+l|0;u=q<<2&12|g&3;v=g>>2;y=q>>2;r=(_(y,w)|0)+(v<<M)|0;x=(c[f+23384+(D*40|0)>>2]|0)+r|0;o=e+198232|0;z=c[o>>2]<<4;B=e+198228|0;k=c[B>>2]<<4>>A;s=(g&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((v|0)<(0-s|0)|(y|0)<(0-q|0))R=9;else if((v+16|0)>(s+z|0))R=9;else if((y+16|0)>(k+q|0))R=9;else{s=w;t=0;q=x}if((R|0)==9){A=f+43620|0;g=2<<M;eb[c[e+8>>2]&3](c[A>>2]|0,x+(0-g)+(0-(w<<1))|0,w,w,21,21,v+-2|0,y+-2|0,z,k);q=c[Q>>2]|0;s=q;t=1;q=(c[A>>2]|0)+g+(q<<1)|0}u=p+(u<<2)|0;ob[c[u>>2]&1023](P,q,s);A=(h|0)!=0;if(!A)ob[c[u>>2]&1023](P+j|0,q+j|0,c[Q>>2]|0);s=(c[f+23384+(D*40|0)+4>>2]|0)+r|0;x=(t|0)!=0;if(x){g=f+43620|0;h=2<<M;q=c[Q>>2]|0;eb[c[e+8>>2]&3](c[g>>2]|0,s+(0-h)+(0-(q<<1))|0,q,q,21,21,v+-2|0,y+-2|0,z,k);s=c[Q>>2]|0;q=s;s=(c[g>>2]|0)+h+(s<<1)|0}else q=c[Q>>2]|0;ob[c[u>>2]&1023](O,s,q);if(!A)ob[c[u>>2]&1023](O+j|0,s+j|0,c[Q>>2]|0);s=(c[f+23384+(D*40|0)+8>>2]|0)+r|0;if(x){g=f+43620|0;h=2<<M;q=c[Q>>2]|0;eb[c[e+8>>2]&3](c[g>>2]|0,s+(0-h)+(0-(q<<1))|0,q,q,21,21,v+-2|0,y+-2|0,z,k);s=c[Q>>2]|0;q=s;s=(c[g>>2]|0)+h+(s<<1)|0}else q=c[Q>>2]|0;ob[c[u>>2]&1023](N,s,q);if(!A)ob[c[u>>2]&1023](N+j|0,s+j|0,c[Q>>2]|0);s=(b[f+43936+(n<<2)>>1]|0)+m|0;q=(b[f+43936+(n<<2)+2>>1]|0)+l|0;u=q<<2&12|s&3;y=s>>2;z=q>>2;x=c[Q>>2]|0;k=(_(z,x)|0)+(y<<M)|0;t=(c[f+25304+(E*40|0)>>2]|0)+k|0;v=c[o>>2]<<4;r=c[B>>2]<<4>>c[C>>2];s=(s&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((y|0)<(0-s|0)|(z|0)<(0-q|0))R=25;else if((y+16|0)>(v+s|0))R=25;else if((z+16|0)>(r+q|0))R=25;else{q=x;x=0;s=t}if((R|0)==25){h=f+43620|0;R=2<<M;eb[c[e+8>>2]&3](c[h>>2]|0,t+(0-R)+(0-(x<<1))|0,x,x,21,21,y+-2|0,z+-2|0,v,r);s=c[Q>>2]|0;q=s;x=1;s=(c[h>>2]|0)+R+(s<<1)|0}t=p+(u<<2)|0;ob[c[t>>2]&1023](I,s,q);if(!A)ob[c[t>>2]&1023](I+j|0,s+j|0,c[Q>>2]|0);s=(c[f+25304+(E*40|0)+4>>2]|0)+k|0;x=(x|0)!=0;if(x){h=f+43620|0;R=2<<M;q=c[Q>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+(0-R)+(0-(q<<1))|0,q,q,21,21,y+-2|0,z+-2|0,v,r);s=c[Q>>2]|0;q=s;s=(c[h>>2]|0)+R+(s<<1)|0}else q=c[Q>>2]|0;ob[c[t>>2]&1023](F,s,q);if(!A)ob[c[t>>2]&1023](F+j|0,s+j|0,c[Q>>2]|0);s=(c[f+25304+(E*40|0)+8>>2]|0)+k|0;if(x){h=f+43620|0;R=2<<M;q=c[Q>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+(0-R)+(0-(q<<1))|0,q,q,21,21,y+-2|0,z+-2|0,v,r);s=c[Q>>2]|0;q=s;s=(c[h>>2]|0)+R+(s<<1)|0}else q=c[Q>>2]|0;ob[c[t>>2]&1023](G,s,q);if(!A)ob[c[t>>2]&1023](G+j|0,s+j|0,c[Q>>2]|0);if((c[L>>2]|0)==2){f=c[f+3192+(D*384|0)+(E<<3)+((c[K>>2]&1)<<2)>>2]|0;j=64-f|0;pb[J&31](P,I,c[Q>>2]|0,i,5,f,j,0);pb[J&31](O,F,c[H>>2]|0,i,5,f,j,0);pb[J&31](N,G,c[H>>2]|0,i,5,f,j,0);return}else{pb[J&31](P,I,c[Q>>2]|0,i,c[f+864>>2]|0,c[f+888+(D<<4)>>2]|0,c[f+888+(E<<4)+8>>2]|0,(c[f+888+(E<<4)+12>>2]|0)+(c[f+888+(D<<4)+4>>2]|0)|0);j=f+868|0;pb[J&31](O,F,c[H>>2]|0,i,c[j>>2]|0,c[f+1656+(D<<5)>>2]|0,c[f+1656+(E<<5)+16>>2]|0,(c[f+1656+(E<<5)+20>>2]|0)+(c[f+1656+(D<<5)+4>>2]|0)|0);pb[J&31](N,G,c[H>>2]|0,i,c[j>>2]|0,c[f+1656+(D<<5)+8>>2]|0,c[f+1656+(E<<5)+24>>2]|0,(c[f+1656+(E<<5)+28>>2]|0)+(c[f+1656+(D<<5)+12>>2]|0)|0);return}}function vc(f,g){f=f|0;g=g|0;var h=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0;S=i;i=i+16|0;I=S+12|0;H=S;M=g+21764|0;Q=c[M>>2]|0;L=g+21768|0;G=c[L>>2]|0;K=g+21772|0;R=c[K>>2]|0;h=f+24668|0;O=c[(c[h>>2]|0)+(R<<2)>>2]|0;F=f+24624|0;B=g+21748|0;C=f+12|0;D=c[B>>2]|0;N=(c[c[F>>2]>>2]|0)+((_(D,G)|0)+Q<<4)|0;c[H>>2]=N;P=N+(_(Q<<2&12,D)|0)+64|0;ob[c[C>>2]&1023](P,D,4);D=c[B>>2]|0;P=(c[(c[F>>2]|0)+4>>2]|0)+((_(D,G)|0)+Q<<4)|0;c[H+4>>2]=P;E=P+(_(c[M>>2]<<2&12,D)|0)+64|0;ob[c[C>>2]&1023](E,D,4);D=c[B>>2]|0;Q=(c[(c[F>>2]|0)+8>>2]|0)+((_(D,G)|0)+Q<<4)|0;c[H+8>>2]=Q;G=Q+(_(c[M>>2]<<2&12,D)|0)+64|0;ob[c[C>>2]&1023](G,D,4);a[(c[f+197620>>2]|0)+R>>0]=c[g+23380>>2];R=c[B>>2]|0;c[g+21760>>2]=R;D=g+21756|0;c[D>>2]=R;a:do if(!(O&7)){n=c[f+203640>>2]|0;j=f+152|0;z=c[f+203644>>2]|0;k=f+168|0;l=f+16|0;m=f+32|0;A=c[(c[h>>2]|0)+(c[K>>2]<<2)>>2]|0;h=a[g+44108>>0]|0;if(h<<24>>24>-1){J=h<<24>>24;K=c[g+43824>>2]|0;I=c[M>>2]|0;K=(K<<16>>18)+64+(I<<4|8)+(_((c[L>>2]<<4|I<<2&12)+(K>>18)|0,R)|0)|0;ob[c[C>>2]&1023]((c[g+23384+(J*40|0)>>2]|0)+K|0,R,4);ob[c[C>>2]&1023]((c[g+23384+(J*40|0)+4>>2]|0)+K|0,c[B>>2]|0,4);ob[c[C>>2]&1023]((c[g+23384+(J*40|0)+8>>2]|0)+K|0,c[B>>2]|0,4)}do if(!(A&8)){if(A&16|0){J=n+64|0;K=z+64|0;wc(f,g,0,0,8,8,N,P,Q,0,0,J,c[j>>2]|0,K,c[k>>2]|0,l,m,A&4096,A&16384);wc(f,g,8,0,8,8,N,P,Q,0,4,J,c[j>>2]|0,K,c[k>>2]|0,l,m,A&8192,A&32768);break}if(A&32|0){F=n+64|0;G=f+156|0;H=z+64|0;I=f+172|0;J=f+20|0;K=f+36|0;wc(f,g,0,0,16,c[D>>2]<<3,N,P,Q,0,0,F,c[G>>2]|0,H,c[I>>2]|0,J,K,A&4096,A&16384);wc(f,g,4,0,16,c[D>>2]<<3,N,P,Q,4,0,F,c[G>>2]|0,H,c[I>>2]|0,J,K,A&8192,A&32768);break}p=n+128|0;q=f+160|0;r=z+128|0;s=f+176|0;t=f+24|0;u=f+40|0;v=f+156|0;w=f+172|0;x=f+20|0;y=f+36|0;o=n+64|0;h=z+64|0;n=0;do{j=e[g+44376+(n<<1)>>1]|0;k=n<<2;l=k&4;m=n<<1&4;do if(!(j&8)){if(j&16|0){J=j&4096;K=j&16384;wc(f,g,k,0,4,4,N,P,Q,l,m,p,c[v>>2]|0,r,c[w>>2]|0,x,y,J,K);wc(f,g,k|2,0,4,4,N,P,Q,l,m|2,p,c[v>>2]|0,r,c[w>>2]|0,x,y,J,K);break}if(!(j&32)){J=j&4096;K=j&16384;wc(f,g,k,1,4,0,N,P,Q,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,J,K);H=l|2;wc(f,g,k|1,1,4,0,N,P,Q,H,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,J,K);I=m|2;wc(f,g,k|2,1,4,0,N,P,Q,l,I,p,c[q>>2]|0,r,c[s>>2]|0,t,u,J,K);wc(f,g,k|3,1,4,0,N,P,Q,H,I,p,c[q>>2]|0,r,c[s>>2]|0,t,u,J,K);break}else{J=j&4096;K=j&16384;wc(f,g,k,0,8,c[D>>2]<<2,N,P,Q,l,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,J,K);wc(f,g,k|1,0,8,c[D>>2]<<2,N,P,Q,l|2,m,p,c[q>>2]|0,r,c[s>>2]|0,t,u,J,K);break}}else wc(f,g,k,1,8,0,N,P,Q,l,m,o,c[v>>2]|0,h,c[w>>2]|0,x,y,j&4096,j&16384);while(0);n=n+1|0}while((n|0)!=4)}else wc(f,g,0,1,16,0,N,P,Q,0,0,n,c[j>>2]|0,z,c[k>>2]|0,l,m,A&4096,A&16384);while(0);if(A&49152|0){h=a[g+44148>>0]|0;if(h<<24>>24>-1){K=h<<24>>24;J=c[g+43984>>2]|0;M=c[M>>2]|0;M=(J<<16>>18)+64+(M<<4|8)+(_((c[L>>2]<<4|M<<2&12)+(J>>18)|0,c[D>>2]|0)|0)|0;ob[c[C>>2]&1023]((c[g+25304+(K*40|0)>>2]|0)+M|0,c[B>>2]|0,4);ob[c[C>>2]&1023]((c[g+25304+(K*40|0)+4>>2]|0)+M|0,c[B>>2]|0,4);ob[c[C>>2]&1023]((c[g+25304+(K*40|0)+8>>2]|0)+M|0,c[B>>2]|0,4)}}}else{G=g+844|0;switch(c[G>>2]|0){case 0:break;case 2:{m=c[M>>2]|0;h=c[g+21708>>2]|0;j=(e[(c[f+197604>>2]|0)+((c[K>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1;J=5;break}default:{j=c[M>>2]|0;m=j;h=(c[L>>2]|0)>((c[g+21796>>2]|0)!=0|0)&1;j=(j|0)>0&1;J=5}}if((J|0)==5){p=~R;o=N+p|0;r=P+p|0;p=Q+p|0;l=m+-1|0;q=g+43628|0;n=c[q>>2]|0;k=n+(m*96|0)|0;if(h|0){j=(j|0)!=0;if(j){C=n+(l*96|0)+8|0;E=C;D=c[E>>2]|0;E=c[E+4>>2]|0;F=o+-7|0;A=F;B=c[A+4>>2]|0;c[C>>2]=c[A>>2];c[C+4>>2]=B;c[F>>2]=D;c[F+4>>2]=E}F=k;h=c[F>>2]|0;F=c[F+4>>2]|0;E=o+1|0;D=E;C=c[D+4>>2]|0;c[k>>2]=c[D>>2];c[k+4>>2]=C;c[E>>2]=h;c[E+4>>2]=F;E=n+(m*96|0)+8|0;k=E;F=c[k>>2]|0;k=c[k+4>>2]|0;h=o+9|0;C=h;D=c[C+4>>2]|0;c[E>>2]=c[C>>2];c[E+4>>2]=D;c[h>>2]=F;c[h+4>>2]=k;h=(c[M>>2]|0)+1|0;k=f+198232|0;if((h|0)<(c[k>>2]|0)){C=(c[q>>2]|0)+(h*96|0)|0;E=C;D=c[E>>2]|0;E=c[E+4>>2]|0;F=o+17|0;A=F;B=c[A+4>>2]|0;c[C>>2]=c[A>>2];c[C+4>>2]=B;c[F>>2]=D;c[F+4>>2]=E}if(j){F=n+(l*96|0)+24|0;E=F;D=c[E>>2]|0;E=c[E+4>>2]|0;C=r+-7|0;B=C;A=c[B+4>>2]|0;c[F>>2]=c[B>>2];c[F+4>>2]=A;c[C>>2]=D;c[C+4>>2]=E;C=n+(l*96|0)+40|0;E=C;D=c[E>>2]|0;E=c[E+4>>2]|0;F=p+-7|0;A=F;B=c[A+4>>2]|0;c[C>>2]=c[A>>2];c[C+4>>2]=B;c[F>>2]=D;c[F+4>>2]=E}h=n+(m*96|0)+16|0;F=h;E=c[F>>2]|0;F=c[F+4>>2]|0;D=r+1|0;C=D;B=c[C+4>>2]|0;c[h>>2]=c[C>>2];c[h+4>>2]=B;c[D>>2]=E;c[D+4>>2]=F;D=n+(m*96|0)+24|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;h=r+9|0;B=h;C=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=C;c[h>>2]=E;c[h+4>>2]=F;h=n+(m*96|0)+32|0;F=h;E=c[F>>2]|0;F=c[F+4>>2]|0;D=p+1|0;C=D;B=c[C+4>>2]|0;c[h>>2]=c[C>>2];c[h+4>>2]=B;c[D>>2]=E;c[D+4>>2]=F;D=n+(m*96|0)+40|0;F=D;E=c[F>>2]|0;F=c[F+4>>2]|0;h=p+9|0;B=h;C=c[B+4>>2]|0;c[D>>2]=c[B>>2];c[D+4>>2]=C;c[h>>2]=E;c[h+4>>2]=F;h=(c[M>>2]|0)+1|0;if((h|0)<(c[k>>2]|0)){F=(c[q>>2]|0)+(h*96|0)+16|0;E=F;D=c[E>>2]|0;E=c[E+4>>2]|0;C=r+17|0;B=C;A=c[B+4>>2]|0;c[F>>2]=c[B>>2];c[F+4>>2]=A;c[C>>2]=D;c[C+4>>2]=E;C=(c[q>>2]|0)+(((c[M>>2]|0)+1|0)*96|0)+32|0;E=C;D=c[E>>2]|0;E=c[E+4>>2]|0;F=p+17|0;A=F;B=c[A+4>>2]|0;c[C>>2]=c[A>>2];c[C+4>>2]=B;c[F>>2]=D;c[F+4>>2]=E}}}s=g+824|0;t=(O&1|0)==0;u=g+21636|0;v=f+132|0;w=(O&16777216|0)==0;x=f+108|0;y=f+100|0;z=g+21740|0;A=3-R|0;B=0-R|0;C=f+112|0;D=f+104|0;E=g+21732|0;F=N;h=0;while(1){j=c[((h|0)==0?s:g+828+(h+-1<<2)|0)>>2]|0;q=h<<4;r=f+26260+(q<<2)|0;b:do if(t){jb[c[f+26172+(c[u>>2]<<2)>>2]&255](F,R);if(a[(d[96537+(h+48)>>0]|0)+(g+43648)>>0]|0)ob[c[v>>2]&1023](g+44384+(h<<8<<1)|0,g+47456+(h<<6)|0,c[(c[f+197556+(h<<2)>>2]|0)+(j<<6)>>2]|0)}else{if(!w){l=c[C>>2]|0;m=c[D>>2]|0;n=h<<8;p=0;while(1){o=F+(c[r+(p<<2)>>2]|0)|0;k=a[(d[96537+(p+q)>>0]|0)+(g+43648)>>0]|0;ub[c[f+26080+(a[(a[96537+p>>0]&255)+(g+21640)>>0]<<24>>24<<2)>>2]&255](o,c[E>>2]<<p&32768,c[z>>2]<<p&16384,R);switch(k<<24>>24){case 0:break;case 1:{j=(p<<4)+n|0;k=g+44384+(j<<1)|0;if(!(b[k>>1]|0))J=22;else ob[l&1023](o,k,R);break}default:{j=(p<<4)+n|0;J=22}}if((J|0)==22){J=0;ob[m&1023](o,g+44384+(j<<1)|0,R)}p=p+4|0;if((p|0)>=16)break b}}l=c[x>>2]|0;m=c[y>>2]|0;n=h<<8;p=0;while(1){o=F+(c[r+(p<<2)>>2]|0)|0;k=a[(d[96537+p>>0]|0)+(g+21640)>>0]|0;j=k<<24>>24;do if((k|4)<<24>>24==7)if(!(c[z>>2]&32768>>>p)){c[I>>2]=_(d[o+A>>0]|0,16843009)|0;k=I;break}else{k=o+4+B|0;break}else k=0;while(0);ob[c[f+26020+(j<<2)>>2]&1023](o,k,R);switch(a[(d[96537+(p+q)>>0]|0)+(g+43648)>>0]|0){case 0:break;case 1:{j=(p<<4)+n|0;k=g+44384+(j<<1)|0;if(!(b[k>>1]|0))J=33;else ob[l&1023](o,k,R);break}default:{j=(p<<4)+n|0;J=33}}if((J|0)==33){J=0;ob[m&1023](o,g+44384+(j<<1)|0,R)}p=p+1|0;if((p|0)==16)break b}}while(0);h=h+1|0;if((h|0)==3)break;F=c[H+(h<<2)>>2]|0}switch(c[G>>2]|0){case 0:break a;case 2:{r=c[M>>2]|0;k=c[g+21708>>2]|0;h=(e[(c[f+197604>>2]|0)+((c[K>>2]|0)+-1-(c[f+198236>>2]|0)<<1)>>1]|0)==(c[g+808>>2]|0)&1;break}default:{h=c[M>>2]|0;r=h;k=(c[L>>2]|0)>((c[g+21796>>2]|0)!=0|0)&1;h=(h|0)>0&1}}p=~R;l=N+p|0;o=P+p|0;p=Q+p|0;m=r+-1|0;q=g+43628|0;n=c[q>>2]|0;j=n+(r*96|0)|0;if(k|0){k=(h|0)!=0;if(k){I=n+(m*96|0)+8|0;K=I;J=c[K>>2]|0;K=c[K+4>>2]|0;L=l+-7|0;G=L;H=c[G+4>>2]|0;c[I>>2]=c[G>>2];c[I+4>>2]=H;c[L>>2]=J;c[L+4>>2]=K}L=j;j=c[L+4>>2]|0;K=l+1|0;c[K>>2]=c[L>>2];c[K+4>>2]=j;K=n+(r*96|0)+8|0;j=K;L=c[j>>2]|0;j=c[j+4>>2]|0;h=l+9|0;I=h;J=c[I+4>>2]|0;c[K>>2]=c[I>>2];c[K+4>>2]=J;c[h>>2]=L;c[h+4>>2]=j;h=(c[M>>2]|0)+1|0;j=f+198232|0;if((h|0)<(c[j>>2]|0)){I=(c[q>>2]|0)+(h*96|0)|0;K=I;J=c[K>>2]|0;K=c[K+4>>2]|0;L=l+17|0;G=L;H=c[G+4>>2]|0;c[I>>2]=c[G>>2];c[I+4>>2]=H;c[L>>2]=J;c[L+4>>2]=K}if(k){L=n+(m*96|0)+24|0;K=L;J=c[K>>2]|0;K=c[K+4>>2]|0;I=o+-7|0;H=I;G=c[H+4>>2]|0;c[L>>2]=c[H>>2];c[L+4>>2]=G;c[I>>2]=J;c[I+4>>2]=K;I=n+(m*96|0)+40|0;K=I;J=c[K>>2]|0;K=c[K+4>>2]|0;L=p+-7|0;G=L;H=c[G+4>>2]|0;c[I>>2]=c[G>>2];c[I+4>>2]=H;c[L>>2]=J;c[L+4>>2]=K}J=n+(r*96|0)+16|0;L=c[J+4>>2]|0;h=o+1|0;c[h>>2]=c[J>>2];c[h+4>>2]=L;h=n+(r*96|0)+24|0;L=h;J=c[L>>2]|0;L=c[L+4>>2]|0;K=o+9|0;I=K;H=c[I+4>>2]|0;c[h>>2]=c[I>>2];c[h+4>>2]=H;c[K>>2]=J;c[K+4>>2]=L;K=n+(r*96|0)+32|0;L=c[K+4>>2]|0;J=p+1|0;c[J>>2]=c[K>>2];c[J+4>>2]=L;J=n+(r*96|0)+40|0;L=J;K=c[L>>2]|0;L=c[L+4>>2]|0;h=p+9|0;H=h;I=c[H+4>>2]|0;c[J>>2]=c[H>>2];c[J+4>>2]=I;c[h>>2]=K;c[h+4>>2]=L;h=(c[M>>2]|0)+1|0;if((h|0)<(c[j>>2]|0)){H=(c[q>>2]|0)+(h*96|0)+16|0;L=H;K=c[L>>2]|0;L=c[L+4>>2]|0;J=o+17|0;G=J;I=c[G+4>>2]|0;c[H>>2]=c[G>>2];c[H+4>>2]=I;c[J>>2]=K;c[J+4>>2]=L;J=(c[q>>2]|0)+(((c[M>>2]|0)+1|0)*96|0)+32|0;L=J;K=c[L>>2]|0;L=c[L+4>>2]|0;M=p+17|0;H=M;I=c[H+4>>2]|0;c[J>>2]=c[H>>2];c[J+4>>2]=I;c[M>>2]=K;c[M+4>>2]=L}}}while(0);k=g+21824|0;l=(O&16777216|0)==0;m=f+116|0;n=f+120|0;h=f+128|0;if(O&1|0){i=S;return}j=f+26260|0;if(O&2|0){fb[c[h>>2]&127](N,j,g+44384|0,R,g+43648|0);fb[c[h>>2]&127](P,f+26324|0,g+44896|0,R,g+43688|0);fb[c[h>>2]&127](Q,f+26388|0,g+45408|0,R,g+43728|0);i=S;return}do if(c[k>>2]&15|0)if(l){fb[c[m>>2]&127](N,j,g+44384|0,R,g+43648|0);break}else{fb[c[n>>2]&127](N,j,g+44384|0,R,g+43648|0);break}while(0);h=f+26324|0;do if(c[k>>2]&15|0)if(l){fb[c[m>>2]&127](P,h,g+44896|0,R,g+43688|0);break}else{fb[c[n>>2]&127](P,h,g+44896|0,R,g+43688|0);break}while(0);h=f+26388|0;if(!(c[k>>2]&15)){i=S;return}if(l){fb[c[m>>2]&127](Q,h,g+45408|0,R,g+43728|0);i=S;return}else{fb[c[n>>2]&127](Q,h,g+45408|0,R,g+43728|0);i=S;return}}function wc(e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w){e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;l=l|0;m=m|0;n=n|0;o=o|0;p=p|0;q=q|0;r=r|0;s=s|0;t=t|0;u=u|0;v=v|0;w=w|0;var x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0;L=f+856|0;s=c[L>>2]|0;y=(v|0)!=0;z=(w|0)!=0;if(z&(y&(s|0)==2)){q=f+21768|0;x=c[q>>2]|0;P=d[96537+g>>0]|0;if((c[f+3192+((a[f+44096+P>>0]|0)*384|0)+(a[f+44136+P>>0]<<3)+((x&1)<<2)>>2]|0)==32)Q=3;else K=q}else Q=3;do if((Q|0)==3){if((s|0)==1){x=f+21768|0;K=x;x=c[x>>2]|0;break}F=f+21756|0;t=c[F>>2]|0;E=(_(o<<1,t)|0)+(n<<1)|0;C=k+E|0;D=l+E|0;E=m+E|0;A=(c[f+21764>>2]<<3)+n|0;B=f+21796|0;s=c[B>>2]|0;o=(c[f+21768>>2]>>s<<3)+o|0;if(!v)r=p;else{q=d[96537+g>>0]|0;v=a[f+44096+q>>0]|0;i=(b[f+43776+(q<<2)>>1]|0)+(A<<3)|0;q=(b[f+43776+(q<<2)+2>>1]|0)+(o<<3)|0;u=q<<2&12|i&3;z=i>>2;m=q>>2;l=(_(m,t)|0)+z|0;x=(c[f+23384+(v*40|0)>>2]|0)+l|0;n=c[e+198232>>2]<<4;y=c[e+198228>>2]<<4>>s;s=(i&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((z|0)<(0-s|0)|(m|0)<(0-q|0))Q=63;else if((z+16|0)>(s+n|0))Q=63;else if((m+16|0)>(y+q|0))Q=63;else{s=t;t=0;q=x}if((Q|0)==63){i=f+43620|0;eb[c[e+8>>2]&3](c[i>>2]|0,x+-2+(0-(t<<1))|0,t,t,21,21,z+-2|0,m+-2|0,n,y);q=c[F>>2]|0;s=q;t=1;q=(c[i>>2]|0)+2+(q<<1)|0}k=p+(u<<2)|0;ob[c[k>>2]&1023](C,q,s);u=(h|0)!=0;if(!u)ob[c[k>>2]&1023](C+j|0,q+j|0,c[F>>2]|0);s=(c[f+23384+(v*40|0)+4>>2]|0)+l|0;x=(t|0)!=0;if(x){p=f+43620|0;q=c[F>>2]|0;eb[c[e+8>>2]&3](c[p>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,z+-2|0,m+-2|0,n,y);s=c[F>>2]|0;q=s;s=(c[p>>2]|0)+2+(s<<1)|0}else q=c[F>>2]|0;ob[c[k>>2]&1023](D,s,q);if(!u)ob[c[k>>2]&1023](D+j|0,s+j|0,c[F>>2]|0);s=(c[f+23384+(v*40|0)+8>>2]|0)+l|0;if(x){p=f+43620|0;q=c[F>>2]|0;eb[c[e+8>>2]&3](c[p>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,z+-2|0,m+-2|0,n,y);s=c[F>>2]|0;q=s;s=(c[p>>2]|0)+2+(s<<1)|0}else q=c[F>>2]|0;ob[c[k>>2]&1023](E,s,q);if(!u)ob[c[k>>2]&1023](E+j|0,s+j|0,c[F>>2]|0)}if(!w)return;q=d[96537+g>>0]|0;m=a[f+44136+q>>0]|0;s=(b[f+43936+(q<<2)>>1]|0)+(A<<3)|0;q=(b[f+43936+(q<<2)+2>>1]|0)+(o<<3)|0;u=q<<2&12|s&3;l=s>>2;y=q>>2;x=c[F>>2]|0;k=(_(y,x)|0)+l|0;t=(c[f+25304+(m*40|0)>>2]|0)+k|0;z=c[e+198232>>2]<<4;v=c[e+198228>>2]<<4>>c[B>>2];s=(s&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((l|0)<(0-s|0)|(y|0)<(0-q|0))Q=80;else if((l+16|0)>(z+s|0))Q=80;else if((y+16|0)>(v+q|0))Q=80;else{q=0;s=t}if((Q|0)==80){Q=f+43620|0;eb[c[e+8>>2]&3](c[Q>>2]|0,t+-2+(0-(x<<1))|0,x,x,21,21,l+-2|0,y+-2|0,z,v);s=c[F>>2]|0;x=s;q=1;s=(c[Q>>2]|0)+2+(s<<1)|0}u=r+(u<<2)|0;ob[c[u>>2]&1023](C,s,x);t=(h|0)!=0;if(!t)ob[c[u>>2]&1023](C+j|0,s+j|0,c[F>>2]|0);s=(c[f+25304+(m*40|0)+4>>2]|0)+k|0;x=(q|0)!=0;if(x){Q=f+43620|0;q=c[F>>2]|0;eb[c[e+8>>2]&3](c[Q>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,l+-2|0,y+-2|0,z,v);s=c[F>>2]|0;q=s;s=(c[Q>>2]|0)+2+(s<<1)|0}else q=c[F>>2]|0;ob[c[u>>2]&1023](D,s,q);if(!t)ob[c[u>>2]&1023](D+j|0,s+j|0,c[F>>2]|0);s=(c[f+25304+(m*40|0)+8>>2]|0)+k|0;if(x){f=f+43620|0;q=c[F>>2]|0;eb[c[e+8>>2]&3](c[f>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,l+-2|0,y+-2|0,z,v);s=c[F>>2]|0;q=s;s=(c[f>>2]|0)+2+(s<<1)|0}else q=c[F>>2]|0;ob[c[u>>2]&1023](E,s,q);if(t)return;ob[c[u>>2]&1023](E+j|0,s+j|0,c[F>>2]|0);return}while(0);B=c[t>>2]|0;J=c[u>>2]|0;P=f+21756|0;w=c[P>>2]|0;M=(_(o<<1,w)|0)+(n<<1)|0;O=k+M|0;N=l+M|0;M=m+M|0;q=(c[f+21764>>2]<<3)+n|0;C=f+21796|0;A=c[C>>2]|0;s=(x>>A<<3)+o|0;if(!(y&z)){n=z&1;t=d[96537+g>>0]|0;m=a[f+44096+(n*40|0)+t>>0]|0;L=(b[f+43776+(n*160|0)+(t<<2)>>1]|0)+(q<<3)|0;q=(b[f+43776+(n*160|0)+(t<<2)+2>>1]|0)+(s<<3)|0;t=q<<2&12|L&3;l=L>>2;y=q>>2;k=(_(y,w)|0)+l|0;x=(c[f+23384+(n*1920|0)+(m*40|0)>>2]|0)+k|0;z=c[e+198232>>2]<<4;v=c[e+198228>>2]<<4>>A;s=(L&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((l|0)<(0-s|0)|(y|0)<(0-q|0))Q=44;else if((l+16|0)>(s+z|0))Q=44;else if((y+16|0)>(v+q|0))Q=44;else{s=w;u=0;q=x}if((Q|0)==44){Q=f+43620|0;eb[c[e+8>>2]&3](c[Q>>2]|0,x+-2+(0-(w<<1))|0,w,w,21,21,l+-2|0,y+-2|0,z,v);q=c[P>>2]|0;s=q;u=1;q=(c[Q>>2]|0)+2+(q<<1)|0}r=p+(t<<2)|0;ob[c[r>>2]&1023](O,q,s);t=(h|0)!=0;if(!t)ob[c[r>>2]&1023](O+j|0,q+j|0,c[P>>2]|0);s=(c[f+23384+(n*1920|0)+(m*40|0)+4>>2]|0)+k|0;x=(u|0)!=0;if(x){Q=f+43620|0;q=c[P>>2]|0;eb[c[e+8>>2]&3](c[Q>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,l+-2|0,y+-2|0,z,v);s=c[P>>2]|0;q=s;s=(c[Q>>2]|0)+2+(s<<1)|0}else q=c[P>>2]|0;ob[c[r>>2]&1023](N,s,q);if(!t)ob[c[r>>2]&1023](N+j|0,s+j|0,c[P>>2]|0);s=(c[f+23384+(n*1920|0)+(m*40|0)+8>>2]|0)+k|0;if(x){Q=f+43620|0;q=c[P>>2]|0;eb[c[e+8>>2]&3](c[Q>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,l+-2|0,y+-2|0,z,v);s=c[P>>2]|0;q=s;s=(c[Q>>2]|0)+2+(s<<1)|0}else q=c[P>>2]|0;ob[c[r>>2]&1023](M,s,q);if(!t)ob[c[r>>2]&1023](M+j|0,s+j|0,c[P>>2]|0);rb[B&63](O,c[P>>2]|0,i,c[f+864>>2]|0,c[f+888+(m<<4)+(n<<3)>>2]|0,c[f+888+(m<<4)+(n<<3)+4>>2]|0);if(!(c[f+860>>2]|0))return;e=f+21760|0;j=f+868|0;rb[B&63](N,c[e>>2]|0,i,c[j>>2]|0,c[f+1656+(m<<5)+(n<<4)>>2]|0,c[f+1656+(m<<5)+(n<<4)+4>>2]|0);rb[B&63](M,c[e>>2]|0,i,c[j>>2]|0,c[f+1656+(m<<5)+(n<<4)+8>>2]|0,c[f+1656+(m<<5)+(n<<4)+12>>2]|0);return}F=c[f+43616>>2]|0;G=F+16|0;H=f+21760|0;I=F+(c[H>>2]<<4)|0;n=d[96537+g>>0]|0;D=a[f+44096+n>>0]|0;E=a[f+44136+n>>0]|0;m=q<<3;g=(b[f+43776+(n<<2)>>1]|0)+m|0;z=s<<3;q=(b[f+43776+(n<<2)+2>>1]|0)+z|0;u=q<<2&12|g&3;v=g>>2;l=q>>2;r=(_(l,w)|0)+v|0;x=(c[f+23384+(D*40|0)>>2]|0)+r|0;o=e+198232|0;y=c[o>>2]<<4;B=e+198228|0;k=c[B>>2]<<4>>A;s=(g&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((v|0)<(0-s|0)|(l|0)<(0-q|0))Q=9;else if((v+16|0)>(s+y|0))Q=9;else if((l+16|0)>(k+q|0))Q=9;else{s=w;t=0;q=x}if((Q|0)==9){g=f+43620|0;eb[c[e+8>>2]&3](c[g>>2]|0,x+-2+(0-(w<<1))|0,w,w,21,21,v+-2|0,l+-2|0,y,k);q=c[P>>2]|0;s=q;t=1;q=(c[g>>2]|0)+2+(q<<1)|0}u=p+(u<<2)|0;ob[c[u>>2]&1023](O,q,s);A=(h|0)!=0;if(!A)ob[c[u>>2]&1023](O+j|0,q+j|0,c[P>>2]|0);s=(c[f+23384+(D*40|0)+4>>2]|0)+r|0;x=(t|0)!=0;if(x){h=f+43620|0;q=c[P>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,v+-2|0,l+-2|0,y,k);s=c[P>>2]|0;q=s;s=(c[h>>2]|0)+2+(s<<1)|0}else q=c[P>>2]|0;ob[c[u>>2]&1023](N,s,q);if(!A)ob[c[u>>2]&1023](N+j|0,s+j|0,c[P>>2]|0);s=(c[f+23384+(D*40|0)+8>>2]|0)+r|0;if(x){h=f+43620|0;q=c[P>>2]|0;eb[c[e+8>>2]&3](c[h>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,v+-2|0,l+-2|0,y,k);s=c[P>>2]|0;q=s;s=(c[h>>2]|0)+2+(s<<1)|0}else q=c[P>>2]|0;ob[c[u>>2]&1023](M,s,q);if(!A)ob[c[u>>2]&1023](M+j|0,s+j|0,c[P>>2]|0);s=(b[f+43936+(n<<2)>>1]|0)+m|0;q=(b[f+43936+(n<<2)+2>>1]|0)+z|0;u=q<<2&12|s&3;l=s>>2;y=q>>2;x=c[P>>2]|0;k=(_(y,x)|0)+l|0;t=(c[f+25304+(E*40|0)>>2]|0)+k|0;v=c[o>>2]<<4;r=c[B>>2]<<4>>c[C>>2];s=(s&7|0)==0?0:-3;q=(q&7|0)==0?0:-3;if((l|0)<(0-s|0)|(y|0)<(0-q|0))Q=25;else if((l+16|0)>(v+s|0))Q=25;else if((y+16|0)>(r+q|0))Q=25;else{q=x;x=0;s=t}if((Q|0)==25){Q=f+43620|0;eb[c[e+8>>2]&3](c[Q>>2]|0,t+-2+(0-(x<<1))|0,x,x,21,21,l+-2|0,y+-2|0,v,r);s=c[P>>2]|0;q=s;x=1;s=(c[Q>>2]|0)+2+(s<<1)|0}t=p+(u<<2)|0;ob[c[t>>2]&1023](I,s,q);if(!A)ob[c[t>>2]&1023](I+j|0,s+j|0,c[P>>2]|0);s=(c[f+25304+(E*40|0)+4>>2]|0)+k|0;x=(x|0)!=0;if(x){Q=f+43620|0;q=c[P>>2]|0;eb[c[e+8>>2]&3](c[Q>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,l+-2|0,y+-2|0,v,r);s=c[P>>2]|0;q=s;s=(c[Q>>2]|0)+2+(s<<1)|0}else q=c[P>>2]|0;ob[c[t>>2]&1023](F,s,q);if(!A)ob[c[t>>2]&1023](F+j|0,s+j|0,c[P>>2]|0);s=(c[f+25304+(E*40|0)+8>>2]|0)+k|0;if(x){Q=f+43620|0;q=c[P>>2]|0;eb[c[e+8>>2]&3](c[Q>>2]|0,s+-2+(0-(q<<1))|0,q,q,21,21,l+-2|0,y+-2|0,v,r);s=c[P>>2]|0;q=s;s=(c[Q>>2]|0)+2+(s<<1)|0}else q=c[P>>2]|0;ob[c[t>>2]&1023](G,s,q);if(!A)ob[c[t>>2]&1023](G+j|0,s+j|0,c[P>>2]|0);if((c[L>>2]|0)==2){f=c[f+3192+(D*384|0)+(E<<3)+((c[K>>2]&1)<<2)>>2]|0;j=64-f|0;pb[J&31](O,I,c[P>>2]|0,i,5,f,j,0);pb[J&31](N,F,c[H>>2]|0,i,5,f,j,0);pb[J&31](M,G,c[H>>2]|0,i,5,f,j,0);return}else{pb[J&31](O,I,c[P>>2]|0,i,c[f+864>>2]|0,c[f+888+(D<<4)>>2]|0,c[f+888+(E<<4)+8>>2]|0,(c[f+888+(E<<4)+12>>2]|0)+(c[f+888+(D<<4)+4>>2]|0)|0);j=f+868|0;pb[J&31](N,F,c[H>>2]|0,i,c[j>>2]|0,c[f+1656+(D<<5)>>2]|0,c[f+1656+(E<<5)+16>>2]|0,(c[f+1656+(E<<5)+20>>2]|0)+(c[f+1656+(D<<5)+4>>2]|0)|0);pb[J&31](M,G,c[H>>2]|0,i,c[j>>2]|0,c[f+1656+(D<<5)+8>>2]|0,c[f+1656+(E<<5)+24>>2]|0,(c[f+1656+(E<<5)+28>>2]|0)+(c[f+1656+(D<<5)+12>>2]|0)|0);return}}

// EMSCRIPTEN_END_FUNCS
var db=[lG];var eb=[mG,gA,fA,mG];var fb=[nG,eg,gg,ig,hg,fg,Ld,Md,Nd,Rd,Td,Sd,Vd,Ud,qg,sg,ug,tg,rg,he,ie,je,ne,pe,oe,re,qe,Cg,Eg,Gg,Fg,Dg,Fe,Ge,He,Le,Ne,Me,Pe,Oe,Og,Qg,Sg,Rg,Pg,bf,cf,df,hf,kf,jf,mf,lf,Uf,Wf,Yf,Xf,Vf,Af,Bf,Cf,Gf,If,Hf,Kf,Jf,yi,zi,Pj,Qj,el,fl,vm,wm,Nn,On,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG,nG];var gb=[oG];var hb=[pG,Zb,Gb,ZD];var ib=[qG,VE,YE,sF,rG,sG,tG,uG,vG,wG,xG,yG,zG,jC,qG,qG];var jb=[AG,JB,kg,lg,wg,xg,Ig,Jg,Ug,Vg,_f,$f,Ih,Hh,Kh,Jh,Lh,Mh,Nh,Oh,Ph,Qh,Rh,Sh,Th,Uh,Vh,Wh,Xh,Yh,Zh,_h,$h,ai,bi,ci,di,ei,gi,fi,hi,ii,ji,ki,mi,ni,oi,pi,qi,ri,si,ti,fj,ej,hj,gj,ij,jj,kj,lj,mj,nj,oj,pj,qj,rj,sj,tj,uj,vj,wj,xj,yj,zj,Bj,Aj,Cj,Dj,Ej,Fj,Gj,Hj,Ij,Jj,Kj,wk,vk,yk,xk,zk,Ak,Bk,Ck,Dk,Ek,Fk,Gk,Hk,Ik,Jk,Kk,Lk,Mk,Nk,Ok,Pk,Qk,Sk,Rk,Tk,Uk,Vk,Wk,Xk,Yk,Zk,_k,$k,Nl,Ml,Pl,Ol,Ql,Rl,Sl,Tl,Ul,Vl,Wl,Xl,Yl,Zl,_l,$l,am,bm,cm,dm,em,fm,hm,gm,im,jm,km,lm,mm,nm,om,pm,qm,cn,bn,en,dn,fn,gn,hn,jn,kn,ln,mn,nn,on,pn,qn,rn,sn,tn,un,vn,wn,xn,zn,yn,An,Bn,Cn,Dn,En,Fn,Gn,Hn,In,Kz,Lz,SB,MD,ND,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG,AG];var kb=[BG,Mz];var lb=[CG,dc];var mb=[DG,YD,XD,yE,Hz,fF,DG,DG];var nb=[EG,Nb,Yb,QC,vz,xz,yz,TD,LB,MA,KB,EG,EG,EG,EG,EG];var ob=[FG,Ad,zd,Cd,Bd,ag,bg,cg,dg,jg,mg,ng,og,pg,vg,yg,zg,Ag,Bg,Hg,Kg,Lg,Mg,Ng,Tg,Qf,Rf,Sf,Tf,Zf,Xg,Yg,Zg,_g,$g,bh,ah,ch,dh,eh,gh,fh,hh,ih,jh,kh,lh,mh,nh,oh,ph,rh,sh,th,uh,ui,vi,wi,xi,Gi,Hi,Ii,Ji,Ki,Li,Mi,Ni,Oi,Pi,Qi,Ri,Si,Ti,Lj,Mj,Nj,Oj,Xj,Yj,Zj,_j,$j,ak,bk,ck,dk,ek,fk,gk,hk,ik,al,bl,cl,dl,ml,nl,ol,pl,ql,rl,sl,tl,ul,vl,wl,xl,yl,zl,rm,sm,tm,um,Dm,Em,Fm,Gm,Hm,Im,Jm,Km,Lm,Mm,Nm,Om,Pm,Qm,Jn,Kn,Ln,Mn,Wn,Xn,Zn,_n,$n,bo,co,fo,go,ho,io,jo,ko,lo,mo,no,oo,po,qo,ro,so,to,uo,vo,wo,xo,yo,zo,Ao,Bo,Co,Do,Eo,Fo,Ho,Io,Jo,Lo,Mo,Oo,Po,Qo,Ro,So,To,Uo,Vo,Wo,Xo,Yo,_o,$o,ap,bp,cp,ep,fp,gp,hp,ip,jp,kp,lp,mp,np,op,pp,rp,sp,tp,up,vp,wp,yp,zp,Bp,Cp,Dp,Ep,Fp,Gp,Hp,Ip,Jp,Kp,Lp,Mp,Np,Op,Pp,Qp,Rp,Sp,Tp,Up,Vp,Wp,Xp,Yp,Zp,_p,$p,aq,bq,cq,dq,eq,fq,gq,hq,iq,jq,kq,lq,nq,oq,pq,rq,sq,uq,vq,wq,xq,yq,zq,Aq,Bq,Cq,Dq,Eq,Fq,Gq,Hq,Iq,Jq,Kq,Lq,Mq,Nq,Oq,Pq,Qq,Rq,Sq,Tq,Uq,Wq,Xq,Yq,_q,$q,br,cr,dr,er,fr,gr,hr,ir,jr,kr,lr,nr,or,pr,rr,sr,ur,vr,wr,xr,yr,zr,Ar,Br,Cr,Dr,Er,Fr,Hr,Ir,Jr,Kr,Lr,Mr,Or,Pr,Rr,Sr,Tr,Ur,Vr,Wr,Xr,Yr,Zr,_r,$r,as,bs,cs,ds,es,fs,gs,hs,is,js,ks,ls,ms,ns,os,ps,qs,rs,ss,ts,us,vs,ws,xs,ys,zs,As,Bs,Ds,Es,Fs,Hs,Is,Ks,Ls,Ms,Ns,Os,Ps,Qs,Rs,Ss,Ts,Us,Vs,Ws,Xs,Ys,Zs,_s,$s,at,bt,ct,dt,et,ft,gt,ht,it,kt,lt,mt,ot,pt,rt,st,tt,ut,vt,wt,xt,yt,zt,At,Bt,Dt,Et,Ft,Ht,It,Kt,Lt,Mt,Nt,Ot,Pt,Qt,Rt,St,Tt,Ut,Vt,Xt,Yt,Zt,_t,$t,au,cu,du,fu,gu,hu,iu,ju,ku,lu,mu,nu,ou,pu,qu,ru,su,tu,uu,vu,wu,xu,yu,zu,Au,Bu,Cu,Du,Eu,Fu,Gu,Hu,Iu,Ju,Ku,Lu,Mu,Nu,Ou,Pu,Qu,Ru,Tu,Uu,Vu,Xu,Yu,_u,$u,av,bv,cv,dv,ev,fv,gv,hv,iv,jv,kv,lv,mv,nv,ov,pv,qv,rv,sv,tv,uv,vv,wv,xv,yv,Av,Bv,Cv,Ev,Fv,Hv,Iv,Jv,Kv,Lv,Mv,Nv,Ov,Pv,Qv,Rv,Tv,Uv,Vv,Xv,Yv,_v,$v,aw,bw,cw,dw,ew,fw,gw,hw,iw,jw,lw,mw,nw,ow,pw,qw,sw,tw,vw,ww,xw,yw,zw,Aw,Bw,Cw,Dw,Ew,Fw,Gw,Hw,Iw,Jw,Kw,Lw,Mw,Nw,Ow,Pw,Qw,Rw,Sw,Tw,Uw,Vw,Ww,Xw,Yw,Zw,_w,$w,ax,bx,cx,dx,ex,fx,hx,ix,jx,lx,mx,ox,px,qx,rx,sx,tx,ux,vx,wx,xx,yx,zx,Ax,Bx,Cx,Dx,Ex,Fx,Gx,Hx,Ix,Jx,Kx,Lx,Mx,Nx,Ox,Qx,Rx,Sx,Ux,Vx,Xx,Yx,Zx,_x,$x,ay,by,cy,dy,ey,fy,hy,iy,jy,ly,my,oy,py,qy,ry,sy,ty,uy,vy,wy,xy,yy,zy,By,Cy,Dy,Ey,Fy,Gy,Iy,Jy,Ly,My,Ny,Oy,Py,Qy,Ry,Sy,Ty,Uy,Vy,Wy,Xy,Yy,Zy,_y,$y,az,bz,cz,dz,ez,fz,gz,hz,iz,jz,kz,lz,mz,nz,oz,pz,qz,rz,sz,tz,iA,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG,FG];var pb=[GG,Hd,Id,Jd,Kd,de,ee,fe,ge,Be,Ce,De,Ee,Ze,_e,$e,af,wf,xf,yf,zf,GG,GG,GG,GG,GG,GG,GG,GG,GG,GG,GG];var qb=[HG,Wb];var rb=[IG,jd,id,ld,kd,nd,md,pd,od,rd,qd,td,sd,vd,ud,xd,wd,Dd,Ed,Fd,Gd,$d,ae,be,ce,xe,ye,ze,Ae,Ve,We,Xe,Ye,sf,tf,uf,vf,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG,IG];var sb=[JG,wz,ed,Az,Oz,kA,tD,JG];var tb=[KG,Nz,NA,OA,PA,QA,RA,SA,TA,UA,VA,WA,XA,YA,ZA,_A,$A,aB,bB,cB,dB,eB,fB,gB,hB,iB,jB,kB,lB,mB,nB,oB,pB,qB,rB,sB,tB,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG,KG];var ub=[LG,Od,Pd,Qd,Wd,Yd,Xd,_d,Zd,ke,le,me,se,ue,te,we,ve,Ie,Je,Ke,Qe,Se,Re,Ue,Te,ef,ff,gf,nf,pf,of,rf,qf,Df,Ef,Ff,Lf,Nf,Mf,Pf,Of,vh,wh,xh,yh,zh,Ah,Bh,Ch,Dh,Eh,Fh,Gh,Bi,Ai,Di,Ci,Ei,Fi,Ui,Vi,Wi,Xi,Yi,Zi,_i,$i,aj,bj,cj,dj,Sj,Rj,Uj,Tj,Vj,Wj,jk,kk,lk,mk,nk,ok,pk,qk,rk,sk,tk,uk,hl,gl,jl,il,kl,ll,Al,Bl,Cl,Dl,El,Fl,Gl,Hl,Il,Jl,Kl,Ll,ym,xm,Am,zm,Bm,Cm,Rm,Sm,Tm,Um,Vm,Wm,Xm,Ym,Zm,_m,$m,an,Qn,Pn,Sn,Rn,Tn,Un,SC,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG,LG];return{_i64Add:HF,_i64Subtract:EF,_av_packet_from_data:Hb,_free:xF,_avcodec_flush_buffers:Xz,_avcodec_alloc_context3:zz,_avcodec_find_decoder_by_name:Wz,_memmove:LF,_bitshift64Ashr:IF,_memset:FF,_malloc:wF,_avcodec_register_all:Eb,_memcpy:KF,_avcodec_alloc_frame:Qz,_avcodec_open2:Rz,_avcodec_decode_video2:Tz,_bitshift64Lshr:JF,_av_init_packet:Fb,_llvm_bswap_i32:MF,_bitshift64Shl:GF,runPostSets:DF,stackAlloc:vb,stackSave:wb,stackRestore:xb,establishStackSpace:yb,setThrew:zb,setTempRet0:Cb,getTempRet0:Db,dynCall_didd:VF,dynCall_viiiiiiiiii:WF,dynCall_viiiii:XF,dynCall_did:YF,dynCall_vi:ZF,dynCall_dd:_F,dynCall_vii:$F,dynCall_iiiiiii:aG,dynCall_viiiiiiiii:bG,dynCall_iiii:cG,dynCall_ii:dG,dynCall_viii:eG,dynCall_viiiiiiii:fG,dynCall_iiiii:gG,dynCall_viiiiii:hG,dynCall_iii:iG,dynCall_iiiiii:jG,dynCall_viiii:kG}})


// EMSCRIPTEN_END_ASM
(Module.asmGlobalArg,Module.asmLibraryArg,buffer);var _i64Subtract=Module["_i64Subtract"]=asm["_i64Subtract"];var _av_packet_from_data=Module["_av_packet_from_data"]=asm["_av_packet_from_data"];var _free=Module["_free"]=asm["_free"];var _avcodec_flush_buffers=Module["_avcodec_flush_buffers"]=asm["_avcodec_flush_buffers"];var _avcodec_alloc_context3=Module["_avcodec_alloc_context3"]=asm["_avcodec_alloc_context3"];var _avcodec_register_all=Module["_avcodec_register_all"]=asm["_avcodec_register_all"];var _i64Add=Module["_i64Add"]=asm["_i64Add"];var _memmove=Module["_memmove"]=asm["_memmove"];var _bitshift64Ashr=Module["_bitshift64Ashr"]=asm["_bitshift64Ashr"];var _memset=Module["_memset"]=asm["_memset"];var runPostSets=Module["runPostSets"]=asm["runPostSets"];var _malloc=Module["_malloc"]=asm["_malloc"];var _avcodec_find_decoder_by_name=Module["_avcodec_find_decoder_by_name"]=asm["_avcodec_find_decoder_by_name"];var _memcpy=Module["_memcpy"]=asm["_memcpy"];var _avcodec_alloc_frame=Module["_avcodec_alloc_frame"]=asm["_avcodec_alloc_frame"];var _avcodec_open2=Module["_avcodec_open2"]=asm["_avcodec_open2"];var _avcodec_decode_video2=Module["_avcodec_decode_video2"]=asm["_avcodec_decode_video2"];var _bitshift64Lshr=Module["_bitshift64Lshr"]=asm["_bitshift64Lshr"];var _av_init_packet=Module["_av_init_packet"]=asm["_av_init_packet"];var _llvm_bswap_i32=Module["_llvm_bswap_i32"]=asm["_llvm_bswap_i32"];var _bitshift64Shl=Module["_bitshift64Shl"]=asm["_bitshift64Shl"];var dynCall_didd=Module["dynCall_didd"]=asm["dynCall_didd"];var dynCall_viiiiiiiiii=Module["dynCall_viiiiiiiiii"]=asm["dynCall_viiiiiiiiii"];var dynCall_viiiii=Module["dynCall_viiiii"]=asm["dynCall_viiiii"];var dynCall_did=Module["dynCall_did"]=asm["dynCall_did"];var dynCall_vi=Module["dynCall_vi"]=asm["dynCall_vi"];var dynCall_dd=Module["dynCall_dd"]=asm["dynCall_dd"];var dynCall_vii=Module["dynCall_vii"]=asm["dynCall_vii"];var dynCall_iiiiiii=Module["dynCall_iiiiiii"]=asm["dynCall_iiiiiii"];var dynCall_viiiiiiiii=Module["dynCall_viiiiiiiii"]=asm["dynCall_viiiiiiiii"];var dynCall_iiii=Module["dynCall_iiii"]=asm["dynCall_iiii"];var dynCall_ii=Module["dynCall_ii"]=asm["dynCall_ii"];var dynCall_viii=Module["dynCall_viii"]=asm["dynCall_viii"];var dynCall_viiiiiiii=Module["dynCall_viiiiiiii"]=asm["dynCall_viiiiiiii"];var dynCall_iiiii=Module["dynCall_iiiii"]=asm["dynCall_iiiii"];var dynCall_viiiiii=Module["dynCall_viiiiii"]=asm["dynCall_viiiiii"];var dynCall_iii=Module["dynCall_iii"]=asm["dynCall_iii"];var dynCall_iiiiii=Module["dynCall_iiiiii"]=asm["dynCall_iiiiii"];var dynCall_viiii=Module["dynCall_viiii"]=asm["dynCall_viiii"];Runtime.stackAlloc=asm["stackAlloc"];Runtime.stackSave=asm["stackSave"];Runtime.stackRestore=asm["stackRestore"];Runtime.establishStackSpace=asm["establishStackSpace"];Runtime.setTempRet0=asm["setTempRet0"];Runtime.getTempRet0=asm["getTempRet0"];function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}ExitStatus.prototype=new Error;ExitStatus.prototype.constructor=ExitStatus;var initialStackTop;var preloadStartTime=null;var calledMain=false;dependenciesFulfilled=function runCaller(){if(!Module["calledRun"])run();if(!Module["calledRun"])dependenciesFulfilled=runCaller};Module["callMain"]=Module.callMain=function callMain(args){args=args||[];ensureInitRuntime();var argc=args.length+1;function pad(){for(var i=0;i<4-1;i++){argv.push(0)}}var argv=[allocate(intArrayFromString(Module["thisProgram"]),"i8",ALLOC_NORMAL)];pad();for(var i=0;i<argc-1;i=i+1){argv.push(allocate(intArrayFromString(args[i]),"i8",ALLOC_NORMAL));pad()}argv.push(0);argv=allocate(argv,"i32",ALLOC_NORMAL);try{var ret=Module["_main"](argc,argv,0);exit(ret,true)}catch(e){if(e instanceof ExitStatus){return}else if(e=="SimulateInfiniteLoop"){Module["noExitRuntime"]=true;return}else{if(e&&typeof e==="object"&&e.stack)Module.printErr("exception thrown: "+[e,e.stack]);throw e}}finally{calledMain=true}};function run(args){args=args||Module["arguments"];if(preloadStartTime===null)preloadStartTime=Date.now();if(runDependencies>0){return}preRun();if(runDependencies>0)return;if(Module["calledRun"])return;function doRun(){if(Module["calledRun"])return;Module["calledRun"]=true;if(ABORT)return;ensureInitRuntime();preMain();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(Module["_main"]&&shouldRunNow)Module["callMain"](args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout((function(){setTimeout((function(){Module["setStatus"]("")}),1);doRun()}),1)}else{doRun()}}Module["run"]=Module.run=run;function exit(status,implicit){if(implicit&&Module["noExitRuntime"]){return}if(Module["noExitRuntime"]){}else{ABORT=true;EXITSTATUS=status;STACKTOP=initialStackTop;exitRuntime();if(Module["onExit"])Module["onExit"](status)}if(ENVIRONMENT_IS_NODE){process["exit"](status)}else if(ENVIRONMENT_IS_SHELL&&typeof quit==="function"){quit(status)}throw new ExitStatus(status)}Module["exit"]=Module.exit=exit;var abortDecorators=[];function abort(what){if(what!==undefined){Module.print(what);Module.printErr(what);what=JSON.stringify(what)}else{what=""}ABORT=true;EXITSTATUS=1;var extra="\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.";var output="abort("+what+") at "+stackTrace()+extra;if(abortDecorators){abortDecorators.forEach((function(decorator){output=decorator(output,what)}))}throw output}Module["abort"]=Module.abort=abort;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"]){shouldRunNow=false}Module["noExitRuntime"]=true;run()





!function() {
  var d3 = {
    version: "3.5.17"
  };
  var d3_arraySlice = [].slice, d3_array = function(list) {
    return d3_arraySlice.call(list);
  };
  var d3_document = this.document;
  function d3_documentElement(node) {
    return node && (node.ownerDocument || node.document || node).documentElement;
  }
  function d3_window(node) {
    return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
  }
  if (d3_document) {
    try {
      d3_array(d3_document.documentElement.childNodes)[0].nodeType;
    } catch (e) {
      d3_array = function(list) {
        var i = list.length, array = new Array(i);
        while (i--) array[i] = list[i];
        return array;
      };
    }
  }
  if (!Date.now) Date.now = function() {
    return +new Date();
  };
  if (d3_document) {
    try {
      d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
    } catch (error) {
      var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
      d3_element_prototype.setAttribute = function(name, value) {
        d3_element_setAttribute.call(this, name, value + "");
      };
      d3_element_prototype.setAttributeNS = function(space, local, value) {
        d3_element_setAttributeNS.call(this, space, local, value + "");
      };
      d3_style_prototype.setProperty = function(name, value, priority) {
        d3_style_setProperty.call(this, name, value + "", priority);
      };
    }
  }
  d3.ascending = d3_ascending;
  function d3_ascending(a, b) {
    return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
  }
  d3.descending = function(a, b) {
    return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
  };
  d3.min = function(array, f) {
    var i = -1, n = array.length, a, b;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null && a > b) a = b;
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
    }
    return a;
  };
  d3.max = function(array, f) {
    var i = -1, n = array.length, a, b;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null && b > a) a = b;
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
    }
    return a;
  };
  d3.extent = function(array, f) {
    var i = -1, n = array.length, a, b, c;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = c = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null) {
        if (a > b) a = b;
        if (c < b) c = b;
      }
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = c = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
        if (a > b) a = b;
        if (c < b) c = b;
      }
    }
    return [ a, c ];
  };
  function d3_number(x) {
    return x === null ? NaN : +x;
  }
  function d3_numeric(x) {
    return !isNaN(x);
  }
  d3.sum = function(array, f) {
    var s = 0, n = array.length, a, i = -1;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = +array[i])) s += a;
    } else {
      while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
    }
    return s;
  };
  d3.mean = function(array, f) {
    var s = 0, n = array.length, a, i = -1, j = n;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
    } else {
      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
    }
    if (j) return s / j;
  };
  d3.quantile = function(values, p) {
    var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
    return e ? v + e * (values[h] - v) : v;
  };
  d3.median = function(array, f) {
    var numbers = [], n = array.length, a, i = -1;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
    } else {
      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
    }
    if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
  };
  d3.variance = function(array, f) {
    var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0;
    if (arguments.length === 1) {
      while (++i < n) {
        if (d3_numeric(a = d3_number(array[i]))) {
          d = a - m;
          m += d / ++j;
          s += d * (a - m);
        }
      }
    } else {
      while (++i < n) {
        if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
          d = a - m;
          m += d / ++j;
          s += d * (a - m);
        }
      }
    }
    if (j > 1) return s / (j - 1);
  };
  d3.deviation = function() {
    var v = d3.variance.apply(this, arguments);
    return v ? Math.sqrt(v) : v;
  };
  function d3_bisector(compare) {
    return {
      left: function(a, x, lo, hi) {
        if (arguments.length < 3) lo = 0;
        if (arguments.length < 4) hi = a.length;
        while (lo < hi) {
          var mid = lo + hi >>> 1;
          if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid;
        }
        return lo;
      },
      right: function(a, x, lo, hi) {
        if (arguments.length < 3) lo = 0;
        if (arguments.length < 4) hi = a.length;
        while (lo < hi) {
          var mid = lo + hi >>> 1;
          if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1;
        }
        return lo;
      }
    };
  }
  var d3_bisect = d3_bisector(d3_ascending);
  d3.bisectLeft = d3_bisect.left;
  d3.bisect = d3.bisectRight = d3_bisect.right;
  d3.bisector = function(f) {
    return d3_bisector(f.length === 1 ? function(d, x) {
      return d3_ascending(f(d), x);
    } : f);
  };
  d3.shuffle = function(array, i0, i1) {
    if ((m = arguments.length) < 3) {
      i1 = array.length;
      if (m < 2) i0 = 0;
    }
    var m = i1 - i0, t, i;
    while (m) {
      i = Math.random() * m-- | 0;
      t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
    }
    return array;
  };
  d3.permute = function(array, indexes) {
    var i = indexes.length, permutes = new Array(i);
    while (i--) permutes[i] = array[indexes[i]];
    return permutes;
  };
  d3.pairs = function(array) {
    var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n);
    while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
    return pairs;
  };
  d3.transpose = function(matrix) {
    if (!(n = matrix.length)) return [];
    for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) {
      for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
        row[j] = matrix[j][i];
      }
    }
    return transpose;
  };
  function d3_transposeLength(d) {
    return d.length;
  }
  d3.zip = function() {
    return d3.transpose(arguments);
  };
  d3.keys = function(map) {
    var keys = [];
    for (var key in map) keys.push(key);
    return keys;
  };
  d3.values = function(map) {
    var values = [];
    for (var key in map) values.push(map[key]);
    return values;
  };
  d3.entries = function(map) {
    var entries = [];
    for (var key in map) entries.push({
      key: key,
      value: map[key]
    });
    return entries;
  };
  d3.merge = function(arrays) {
    var n = arrays.length, m, i = -1, j = 0, merged, array;
    while (++i < n) j += arrays[i].length;
    merged = new Array(j);
    while (--n >= 0) {
      array = arrays[n];
      m = array.length;
      while (--m >= 0) {
        merged[--j] = array[m];
      }
    }
    return merged;
  };
  var abs = Math.abs;
  d3.range = function(start, stop, step) {
    if (arguments.length < 3) {
      step = 1;
      if (arguments.length < 2) {
        stop = start;
        start = 0;
      }
    }
    if ((stop - start) / step === Infinity) throw new Error("infinite range");
    var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
    start *= k, stop *= k, step *= k;
    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
    return range;
  };
  function d3_range_integerScale(x) {
    var k = 1;
    while (x * k % 1) k *= 10;
    return k;
  }
  function d3_class(ctor, properties) {
    for (var key in properties) {
      Object.defineProperty(ctor.prototype, key, {
        value: properties[key],
        enumerable: false
      });
    }
  }
  d3.map = function(object, f) {
    var map = new d3_Map();
    if (object instanceof d3_Map) {
      object.forEach(function(key, value) {
        map.set(key, value);
      });
    } else if (Array.isArray(object)) {
      var i = -1, n = object.length, o;
      if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
    } else {
      for (var key in object) map.set(key, object[key]);
    }
    return map;
  };
  function d3_Map() {
    this._ = Object.create(null);
  }
  var d3_map_proto = "__proto__", d3_map_zero = "\x00";
  d3_class(d3_Map, {
    has: d3_map_has,
    get: function(key) {
      return this._[d3_map_escape(key)];
    },
    set: function(key, value) {
      return this._[d3_map_escape(key)] = value;
    },
    remove: d3_map_remove,
    keys: d3_map_keys,
    values: function() {
      var values = [];
      for (var key in this._) values.push(this._[key]);
      return values;
    },
    entries: function() {
      var entries = [];
      for (var key in this._) entries.push({
        key: d3_map_unescape(key),
        value: this._[key]
      });
      return entries;
    },
    size: d3_map_size,
    empty: d3_map_empty,
    forEach: function(f) {
      for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
    }
  });
  function d3_map_escape(key) {
    return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
  }
  function d3_map_unescape(key) {
    return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
  }
  function d3_map_has(key) {
    return d3_map_escape(key) in this._;
  }
  function d3_map_remove(key) {
    return (key = d3_map_escape(key)) in this._ && delete this._[key];
  }
  function d3_map_keys() {
    var keys = [];
    for (var key in this._) keys.push(d3_map_unescape(key));
    return keys;
  }
  function d3_map_size() {
    var size = 0;
    for (var key in this._) ++size;
    return size;
  }
  function d3_map_empty() {
    for (var key in this._) return false;
    return true;
  }
  d3.nest = function() {
    var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
    function map(mapType, array, depth) {
      if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
      var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
      while (++i < n) {
        if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
          values.push(object);
        } else {
          valuesByKey.set(keyValue, [ object ]);
        }
      }
      if (mapType) {
        object = mapType();
        setter = function(keyValue, values) {
          object.set(keyValue, map(mapType, values, depth));
        };
      } else {
        object = {};
        setter = function(keyValue, values) {
          object[keyValue] = map(mapType, values, depth);
        };
      }
      valuesByKey.forEach(setter);
      return object;
    }
    function entries(map, depth) {
      if (depth >= keys.length) return map;
      var array = [], sortKey = sortKeys[depth++];
      map.forEach(function(key, keyMap) {
        array.push({
          key: key,
          values: entries(keyMap, depth)
        });
      });
      return sortKey ? array.sort(function(a, b) {
        return sortKey(a.key, b.key);
      }) : array;
    }
    nest.map = function(array, mapType) {
      return map(mapType, array, 0);
    };
    nest.entries = function(array) {
      return entries(map(d3.map, array, 0), 0);
    };
    nest.key = function(d) {
      keys.push(d);
      return nest;
    };
    nest.sortKeys = function(order) {
      sortKeys[keys.length - 1] = order;
      return nest;
    };
    nest.sortValues = function(order) {
      sortValues = order;
      return nest;
    };
    nest.rollup = function(f) {
      rollup = f;
      return nest;
    };
    return nest;
  };
  d3.set = function(array) {
    var set = new d3_Set();
    if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
    return set;
  };
  function d3_Set() {
    this._ = Object.create(null);
  }
  d3_class(d3_Set, {
    has: d3_map_has,
    add: function(key) {
      this._[d3_map_escape(key += "")] = true;
      return key;
    },
    remove: d3_map_remove,
    values: d3_map_keys,
    size: d3_map_size,
    empty: d3_map_empty,
    forEach: function(f) {
      for (var key in this._) f.call(this, d3_map_unescape(key));
    }
  });
  d3.behavior = {};
  function d3_identity(d) {
    return d;
  }
  d3.rebind = function(target, source) {
    var i = 1, n = arguments.length, method;
    while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
    return target;
  };
  function d3_rebind(target, source, method) {
    return function() {
      var value = method.apply(source, arguments);
      return value === source ? target : value;
    };
  }
  function d3_vendorSymbol(object, name) {
    if (name in object) return name;
    name = name.charAt(0).toUpperCase() + name.slice(1);
    for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
      var prefixName = d3_vendorPrefixes[i] + name;
      if (prefixName in object) return prefixName;
    }
  }
  var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
  function d3_noop() {}
  d3.dispatch = function() {
    var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
    return dispatch;
  };
  function d3_dispatch() {}
  d3_dispatch.prototype.on = function(type, listener) {
    var i = type.indexOf("."), name = "";
    if (i >= 0) {
      name = type.slice(i + 1);
      type = type.slice(0, i);
    }
    if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
    if (arguments.length === 2) {
      if (listener == null) for (type in this) {
        if (this.hasOwnProperty(type)) this[type].on(name, null);
      }
      return this;
    }
  };
  function d3_dispatch_event(dispatch) {
    var listeners = [], listenerByName = new d3_Map();
    function event() {
      var z = listeners, i = -1, n = z.length, l;
      while (++i < n) if (l = z[i].on) l.apply(this, arguments);
      return dispatch;
    }
    event.on = function(name, listener) {
      var l = listenerByName.get(name), i;
      if (arguments.length < 2) return l && l.on;
      if (l) {
        l.on = null;
        listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
        listenerByName.remove(name);
      }
      if (listener) listeners.push(listenerByName.set(name, {
        on: listener
      }));
      return dispatch;
    };
    return event;
  }
  d3.event = null;
  function d3_eventPreventDefault() {
    d3.event.preventDefault();
  }
  function d3_eventSource() {
    var e = d3.event, s;
    while (s = e.sourceEvent) e = s;
    return e;
  }
  function d3_eventDispatch(target) {
    var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
    dispatch.of = function(thiz, argumentz) {
      return function(e1) {
        try {
          var e0 = e1.sourceEvent = d3.event;
          e1.target = target;
          d3.event = e1;
          dispatch[e1.type].apply(thiz, argumentz);
        } finally {
          d3.event = e0;
        }
      };
    };
    return dispatch;
  }
  d3.requote = function(s) {
    return s.replace(d3_requote_re, "\\$&");
  };
  var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
  var d3_subclass = {}.__proto__ ? function(object, prototype) {
    object.__proto__ = prototype;
  } : function(object, prototype) {
    for (var property in prototype) object[property] = prototype[property];
  };
  function d3_selection(groups) {
    d3_subclass(groups, d3_selectionPrototype);
    return groups;
  }
  var d3_select = function(s, n) {
    return n.querySelector(s);
  }, d3_selectAll = function(s, n) {
    return n.querySelectorAll(s);
  }, d3_selectMatches = function(n, s) {
    var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
    d3_selectMatches = function(n, s) {
      return d3_selectMatcher.call(n, s);
    };
    return d3_selectMatches(n, s);
  };
  if (typeof Sizzle === "function") {
    d3_select = function(s, n) {
      return Sizzle(s, n)[0] || null;
    };
    d3_selectAll = Sizzle;
    d3_selectMatches = Sizzle.matchesSelector;
  }
  d3.selection = function() {
    return d3.select(d3_document.documentElement);
  };
  var d3_selectionPrototype = d3.selection.prototype = [];
  d3_selectionPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, group, node;
    selector = d3_selection_selector(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      subgroup.parentNode = (group = this[j]).parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(subnode = selector.call(node, node.__data__, i, j));
          if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_selector(selector) {
    return typeof selector === "function" ? selector : function() {
      return d3_select(selector, this);
    };
  }
  d3_selectionPrototype.selectAll = function(selector) {
    var subgroups = [], subgroup, node;
    selector = d3_selection_selectorAll(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
          subgroup.parentNode = node;
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_selectorAll(selector) {
    return typeof selector === "function" ? selector : function() {
      return d3_selectAll(selector, this);
    };
  }
  var d3_nsXhtml = "http://www.w3.org/1999/xhtml";
  var d3_nsPrefix = {
    svg: "http://www.w3.org/2000/svg",
    xhtml: d3_nsXhtml,
    xlink: "http://www.w3.org/1999/xlink",
    xml: "http://www.w3.org/XML/1998/namespace",
    xmlns: "http://www.w3.org/2000/xmlns/"
  };
  d3.ns = {
    prefix: d3_nsPrefix,
    qualify: function(name) {
      var i = name.indexOf(":"), prefix = name;
      if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
      return d3_nsPrefix.hasOwnProperty(prefix) ? {
        space: d3_nsPrefix[prefix],
        local: name
      } : name;
    }
  };
  d3_selectionPrototype.attr = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node();
        name = d3.ns.qualify(name);
        return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
      }
      for (value in name) this.each(d3_selection_attr(value, name[value]));
      return this;
    }
    return this.each(d3_selection_attr(name, value));
  };
  function d3_selection_attr(name, value) {
    name = d3.ns.qualify(name);
    function attrNull() {
      this.removeAttribute(name);
    }
    function attrNullNS() {
      this.removeAttributeNS(name.space, name.local);
    }
    function attrConstant() {
      this.setAttribute(name, value);
    }
    function attrConstantNS() {
      this.setAttributeNS(name.space, name.local, value);
    }
    function attrFunction() {
      var x = value.apply(this, arguments);
      if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
    }
    function attrFunctionNS() {
      var x = value.apply(this, arguments);
      if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
    }
    return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
  }
  function d3_collapse(s) {
    return s.trim().replace(/\s+/g, " ");
  }
  d3_selectionPrototype.classed = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
        if (value = node.classList) {
          while (++i < n) if (!value.contains(name[i])) return false;
        } else {
          value = node.getAttribute("class");
          while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
        }
        return true;
      }
      for (value in name) this.each(d3_selection_classed(value, name[value]));
      return this;
    }
    return this.each(d3_selection_classed(name, value));
  };
  function d3_selection_classedRe(name) {
    return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
  }
  function d3_selection_classes(name) {
    return (name + "").trim().split(/^|\s+/);
  }
  function d3_selection_classed(name, value) {
    name = d3_selection_classes(name).map(d3_selection_classedName);
    var n = name.length;
    function classedConstant() {
      var i = -1;
      while (++i < n) name[i](this, value);
    }
    function classedFunction() {
      var i = -1, x = value.apply(this, arguments);
      while (++i < n) name[i](this, x);
    }
    return typeof value === "function" ? classedFunction : classedConstant;
  }
  function d3_selection_classedName(name) {
    var re = d3_selection_classedRe(name);
    return function(node, value) {
      if (c = node.classList) return value ? c.add(name) : c.remove(name);
      var c = node.getAttribute("class") || "";
      if (value) {
        re.lastIndex = 0;
        if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
      } else {
        node.setAttribute("class", d3_collapse(c.replace(re, " ")));
      }
    };
  }
  d3_selectionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
        return this;
      }
      if (n < 2) {
        var node = this.node();
        return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
      }
      priority = "";
    }
    return this.each(d3_selection_style(name, value, priority));
  };
  function d3_selection_style(name, value, priority) {
    function styleNull() {
      this.style.removeProperty(name);
    }
    function styleConstant() {
      this.style.setProperty(name, value, priority);
    }
    function styleFunction() {
      var x = value.apply(this, arguments);
      if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
    }
    return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
  }
  d3_selectionPrototype.property = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") return this.node()[name];
      for (value in name) this.each(d3_selection_property(value, name[value]));
      return this;
    }
    return this.each(d3_selection_property(name, value));
  };
  function d3_selection_property(name, value) {
    function propertyNull() {
      delete this[name];
    }
    function propertyConstant() {
      this[name] = value;
    }
    function propertyFunction() {
      var x = value.apply(this, arguments);
      if (x == null) delete this[name]; else this[name] = x;
    }
    return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
  }
  d3_selectionPrototype.text = function(value) {
    return arguments.length ? this.each(typeof value === "function" ? function() {
      var v = value.apply(this, arguments);
      this.textContent = v == null ? "" : v;
    } : value == null ? function() {
      this.textContent = "";
    } : function() {
      this.textContent = value;
    }) : this.node().textContent;
  };
  d3_selectionPrototype.html = function(value) {
    return arguments.length ? this.each(typeof value === "function" ? function() {
      var v = value.apply(this, arguments);
      this.innerHTML = v == null ? "" : v;
    } : value == null ? function() {
      this.innerHTML = "";
    } : function() {
      this.innerHTML = value;
    }) : this.node().innerHTML;
  };
  d3_selectionPrototype.append = function(name) {
    name = d3_selection_creator(name);
    return this.select(function() {
      return this.appendChild(name.apply(this, arguments));
    });
  };
  function d3_selection_creator(name) {
    function create() {
      var document = this.ownerDocument, namespace = this.namespaceURI;
      return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name);
    }
    function createNS() {
      return this.ownerDocument.createElementNS(name.space, name.local);
    }
    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
  }
  d3_selectionPrototype.insert = function(name, before) {
    name = d3_selection_creator(name);
    before = d3_selection_selector(before);
    return this.select(function() {
      return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
    });
  };
  d3_selectionPrototype.remove = function() {
    return this.each(d3_selectionRemove);
  };
  function d3_selectionRemove() {
    var parent = this.parentNode;
    if (parent) parent.removeChild(this);
  }
  d3_selectionPrototype.data = function(value, key) {
    var i = -1, n = this.length, group, node;
    if (!arguments.length) {
      value = new Array(n = (group = this[0]).length);
      while (++i < n) {
        if (node = group[i]) {
          value[i] = node.__data__;
        }
      }
      return value;
    }
    function bind(group, groupData) {
      var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
      if (key) {
        var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
        for (i = -1; ++i < n; ) {
          if (node = group[i]) {
            if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
              exitNodes[i] = node;
            } else {
              nodeByKeyValue.set(keyValue, node);
            }
            keyValues[i] = keyValue;
          }
        }
        for (i = -1; ++i < m; ) {
          if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          } else if (node !== true) {
            updateNodes[i] = node;
            node.__data__ = nodeData;
          }
          nodeByKeyValue.set(keyValue, true);
        }
        for (i = -1; ++i < n; ) {
          if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
            exitNodes[i] = group[i];
          }
        }
      } else {
        for (i = -1; ++i < n0; ) {
          node = group[i];
          nodeData = groupData[i];
          if (node) {
            node.__data__ = nodeData;
            updateNodes[i] = node;
          } else {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          }
        }
        for (;i < m; ++i) {
          enterNodes[i] = d3_selection_dataNode(groupData[i]);
        }
        for (;i < n; ++i) {
          exitNodes[i] = group[i];
        }
      }
      enterNodes.update = updateNodes;
      enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
      enter.push(enterNodes);
      update.push(updateNodes);
      exit.push(exitNodes);
    }
    var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
    if (typeof value === "function") {
      while (++i < n) {
        bind(group = this[i], value.call(group, group.parentNode.__data__, i));
      }
    } else {
      while (++i < n) {
        bind(group = this[i], value);
      }
    }
    update.enter = function() {
      return enter;
    };
    update.exit = function() {
      return exit;
    };
    return update;
  };
  function d3_selection_dataNode(data) {
    return {
      __data__: data
    };
  }
  d3_selectionPrototype.datum = function(value) {
    return arguments.length ? this.property("__data__", value) : this.property("__data__");
  };
  d3_selectionPrototype.filter = function(filter) {
    var subgroups = [], subgroup, group, node;
    if (typeof filter !== "function") filter = d3_selection_filter(filter);
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      subgroup.parentNode = (group = this[j]).parentNode;
      for (var i = 0, n = group.length; i < n; i++) {
        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
          subgroup.push(node);
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_filter(selector) {
    return function() {
      return d3_selectMatches(this, selector);
    };
  }
  d3_selectionPrototype.order = function() {
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
        if (node = group[i]) {
          if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
          next = node;
        }
      }
    }
    return this;
  };
  d3_selectionPrototype.sort = function(comparator) {
    comparator = d3_selection_sortComparator.apply(this, arguments);
    for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
    return this.order();
  };
  function d3_selection_sortComparator(comparator) {
    if (!arguments.length) comparator = d3_ascending;
    return function(a, b) {
      return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
    };
  }
  d3_selectionPrototype.each = function(callback) {
    return d3_selection_each(this, function(node, i, j) {
      callback.call(node, node.__data__, i, j);
    });
  };
  function d3_selection_each(groups, callback) {
    for (var j = 0, m = groups.length; j < m; j++) {
      for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
        if (node = group[i]) callback(node, i, j);
      }
    }
    return groups;
  }
  d3_selectionPrototype.call = function(callback) {
    var args = d3_array(arguments);
    callback.apply(args[0] = this, args);
    return this;
  };
  d3_selectionPrototype.empty = function() {
    return !this.node();
  };
  d3_selectionPrototype.node = function() {
    for (var j = 0, m = this.length; j < m; j++) {
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        var node = group[i];
        if (node) return node;
      }
    }
    return null;
  };
  d3_selectionPrototype.size = function() {
    var n = 0;
    d3_selection_each(this, function() {
      ++n;
    });
    return n;
  };
  function d3_selection_enter(selection) {
    d3_subclass(selection, d3_selection_enterPrototype);
    return selection;
  }
  var d3_selection_enterPrototype = [];
  d3.selection.enter = d3_selection_enter;
  d3.selection.enter.prototype = d3_selection_enterPrototype;
  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
  d3_selection_enterPrototype.call = d3_selectionPrototype.call;
  d3_selection_enterPrototype.size = d3_selectionPrototype.size;
  d3_selection_enterPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, upgroup, group, node;
    for (var j = -1, m = this.length; ++j < m; ) {
      upgroup = (group = this[j]).update;
      subgroups.push(subgroup = []);
      subgroup.parentNode = group.parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
          subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  d3_selection_enterPrototype.insert = function(name, before) {
    if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
    return d3_selectionPrototype.insert.call(this, name, before);
  };
  function d3_selection_enterInsertBefore(enter) {
    var i0, j0;
    return function(d, i, j) {
      var group = enter[j].update, n = group.length, node;
      if (j != j0) j0 = j, i0 = 0;
      if (i >= i0) i0 = i + 1;
      while (!(node = group[i0]) && ++i0 < n) ;
      return node;
    };
  }
  d3.select = function(node) {
    var group;
    if (typeof node === "string") {
      group = [ d3_select(node, d3_document) ];
      group.parentNode = d3_document.documentElement;
    } else {
      group = [ node ];
      group.parentNode = d3_documentElement(node);
    }
    return d3_selection([ group ]);
  };
  d3.selectAll = function(nodes) {
    var group;
    if (typeof nodes === "string") {
      group = d3_array(d3_selectAll(nodes, d3_document));
      group.parentNode = d3_document.documentElement;
    } else {
      group = d3_array(nodes);
      group.parentNode = null;
    }
    return d3_selection([ group ]);
  };
  d3_selectionPrototype.on = function(type, listener, capture) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof type !== "string") {
        if (n < 2) listener = false;
        for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
        return this;
      }
      if (n < 2) return (n = this.node()["__on" + type]) && n._;
      capture = false;
    }
    return this.each(d3_selection_on(type, listener, capture));
  };
  function d3_selection_on(type, listener, capture) {
    var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
    if (i > 0) type = type.slice(0, i);
    var filter = d3_selection_onFilters.get(type);
    if (filter) type = filter, wrap = d3_selection_onFilter;
    function onRemove() {
      var l = this[name];
      if (l) {
        this.removeEventListener(type, l, l.$);
        delete this[name];
      }
    }
    function onAdd() {
      var l = wrap(listener, d3_array(arguments));
      onRemove.call(this);
      this.addEventListener(type, this[name] = l, l.$ = capture);
      l._ = listener;
    }
    function removeAll() {
      var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
      for (var name in this) {
        if (match = name.match(re)) {
          var l = this[name];
          this.removeEventListener(match[1], l, l.$);
          delete this[name];
        }
      }
    }
    return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
  }
  var d3_selection_onFilters = d3.map({
    mouseenter: "mouseover",
    mouseleave: "mouseout"
  });
  if (d3_document) {
    d3_selection_onFilters.forEach(function(k) {
      if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
    });
  }
  function d3_selection_onListener(listener, argumentz) {
    return function(e) {
      var o = d3.event;
      d3.event = e;
      argumentz[0] = this.__data__;
      try {
        listener.apply(this, argumentz);
      } finally {
        d3.event = o;
      }
    };
  }
  function d3_selection_onFilter(listener, argumentz) {
    var l = d3_selection_onListener(listener, argumentz);
    return function(e) {
      var target = this, related = e.relatedTarget;
      if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
        l.call(target, e);
      }
    };
  }
  var d3_event_dragSelect, d3_event_dragId = 0;
  function d3_event_dragSuppress(node) {
    var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
    if (d3_event_dragSelect == null) {
      d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
    }
    if (d3_event_dragSelect) {
      var style = d3_documentElement(node).style, select = style[d3_event_dragSelect];
      style[d3_event_dragSelect] = "none";
    }
    return function(suppressClick) {
      w.on(name, null);
      if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
      if (suppressClick) {
        var off = function() {
          w.on(click, null);
        };
        w.on(click, function() {
          d3_eventPreventDefault();
          off();
        }, true);
        setTimeout(off, 0);
      }
    };
  }
  d3.mouse = function(container) {
    return d3_mousePoint(container, d3_eventSource());
  };
  var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
  function d3_mousePoint(container, e) {
    if (e.changedTouches) e = e.changedTouches[0];
    var svg = container.ownerSVGElement || container;
    if (svg.createSVGPoint) {
      var point = svg.createSVGPoint();
      if (d3_mouse_bug44083 < 0) {
        var window = d3_window(container);
        if (window.scrollX || window.scrollY) {
          svg = d3.select("body").append("svg").style({
            position: "absolute",
            top: 0,
            left: 0,
            margin: 0,
            padding: 0,
            border: "none"
          }, "important");
          var ctm = svg[0][0].getScreenCTM();
          d3_mouse_bug44083 = !(ctm.f || ctm.e);
          svg.remove();
        }
      }
      if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, 
      point.y = e.clientY;
      point = point.matrixTransform(container.getScreenCTM().inverse());
      return [ point.x, point.y ];
    }
    var rect = container.getBoundingClientRect();
    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
  }
  d3.touch = function(container, touches, identifier) {
    if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
    if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
      if ((touch = touches[i]).identifier === identifier) {
        return d3_mousePoint(container, touch);
      }
    }
  };
  d3.behavior.drag = function() {
    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
    function drag() {
      this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
    }
    function dragstart(id, position, subject, move, end) {
      return function() {
        var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId);
        if (origin) {
          dragOffset = origin.apply(that, arguments);
          dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
        } else {
          dragOffset = [ 0, 0 ];
        }
        dispatch({
          type: "dragstart"
        });
        function moved() {
          var position1 = position(parent, dragId), dx, dy;
          if (!position1) return;
          dx = position1[0] - position0[0];
          dy = position1[1] - position0[1];
          dragged |= dx | dy;
          position0 = position1;
          dispatch({
            type: "drag",
            x: position1[0] + dragOffset[0],
            y: position1[1] + dragOffset[1],
            dx: dx,
            dy: dy
          });
        }
        function ended() {
          if (!position(parent, dragId)) return;
          dragSubject.on(move + dragName, null).on(end + dragName, null);
          dragRestore(dragged);
          dispatch({
            type: "dragend"
          });
        }
      };
    }
    drag.origin = function(x) {
      if (!arguments.length) return origin;
      origin = x;
      return drag;
    };
    return d3.rebind(drag, event, "on");
  };
  function d3_behavior_dragTouchId() {
    return d3.event.changedTouches[0].identifier;
  }
  d3.touches = function(container, touches) {
    if (arguments.length < 2) touches = d3_eventSource().touches;
    return touches ? d3_array(touches).map(function(touch) {
      var point = d3_mousePoint(container, touch);
      point.identifier = touch.identifier;
      return point;
    }) : [];
  };
  var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
  function d3_sgn(x) {
    return x > 0 ? 1 : x < 0 ? -1 : 0;
  }
  function d3_cross2d(a, b, c) {
    return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
  }
  function d3_acos(x) {
    return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
  }
  function d3_asin(x) {
    return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
  }
  function d3_sinh(x) {
    return ((x = Math.exp(x)) - 1 / x) / 2;
  }
  function d3_cosh(x) {
    return ((x = Math.exp(x)) + 1 / x) / 2;
  }
  function d3_tanh(x) {
    return ((x = Math.exp(2 * x)) - 1) / (x + 1);
  }
  function d3_haversin(x) {
    return (x = Math.sin(x / 2)) * x;
  }
  var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
  d3.interpolateZoom = function(p0, p1) {
    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S;
    if (d2 < ε2) {
      S = Math.log(w1 / w0) / ρ;
      i = function(t) {
        return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ];
      };
    } else {
      var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
      S = (r1 - r0) / ρ;
      i = function(t) {
        var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
        return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
      };
    }
    i.duration = S * 1e3;
    return i;
  };
  d3.behavior.zoom = function() {
    var view = {
      x: 0,
      y: 0,
      k: 1
    }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
    if (!d3_behavior_zoomWheel) {
      d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
        return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
      }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
        return d3.event.wheelDelta;
      }, "mousewheel") : (d3_behavior_zoomDelta = function() {
        return -d3.event.detail;
      }, "MozMousePixelScroll");
    }
    function zoom(g) {
      g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
    }
    zoom.event = function(g) {
      g.each(function() {
        var dispatch = event.of(this, arguments), view1 = view;
        if (d3_transitionInheritId) {
          d3.select(this).transition().each("start.zoom", function() {
            view = this.__chart__ || {
              x: 0,
              y: 0,
              k: 1
            };
            zoomstarted(dispatch);
          }).tween("zoom:zoom", function() {
            var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
            return function(t) {
              var l = i(t), k = dx / l[2];
              this.__chart__ = view = {
                x: cx - l[0] * k,
                y: cy - l[1] * k,
                k: k
              };
              zoomed(dispatch);
            };
          }).each("interrupt.zoom", function() {
            zoomended(dispatch);
          }).each("end.zoom", function() {
            zoomended(dispatch);
          });
        } else {
          this.__chart__ = view;
          zoomstarted(dispatch);
          zoomed(dispatch);
          zoomended(dispatch);
        }
      });
    };
    zoom.translate = function(_) {
      if (!arguments.length) return [ view.x, view.y ];
      view = {
        x: +_[0],
        y: +_[1],
        k: view.k
      };
      rescale();
      return zoom;
    };
    zoom.scale = function(_) {
      if (!arguments.length) return view.k;
      view = {
        x: view.x,
        y: view.y,
        k: null
      };
      scaleTo(+_);
      rescale();
      return zoom;
    };
    zoom.scaleExtent = function(_) {
      if (!arguments.length) return scaleExtent;
      scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.center = function(_) {
      if (!arguments.length) return center;
      center = _ && [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.size = function(_) {
      if (!arguments.length) return size;
      size = _ && [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.duration = function(_) {
      if (!arguments.length) return duration;
      duration = +_;
      return zoom;
    };
    zoom.x = function(z) {
      if (!arguments.length) return x1;
      x1 = z;
      x0 = z.copy();
      view = {
        x: 0,
        y: 0,
        k: 1
      };
      return zoom;
    };
    zoom.y = function(z) {
      if (!arguments.length) return y1;
      y1 = z;
      y0 = z.copy();
      view = {
        x: 0,
        y: 0,
        k: 1
      };
      return zoom;
    };
    function location(p) {
      return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ];
    }
    function point(l) {
      return [ l[0] * view.k + view.x, l[1] * view.k + view.y ];
    }
    function scaleTo(s) {
      view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
    }
    function translateTo(p, l) {
      l = point(l);
      view.x += p[0] - l[0];
      view.y += p[1] - l[1];
    }
    function zoomTo(that, p, l, k) {
      that.__chart__ = {
        x: view.x,
        y: view.y,
        k: view.k
      };
      scaleTo(Math.pow(2, k));
      translateTo(center0 = p, l);
      that = d3.select(that);
      if (duration > 0) that = that.transition().duration(duration);
      that.call(zoom.event);
    }
    function rescale() {
      if (x1) x1.domain(x0.range().map(function(x) {
        return (x - view.x) / view.k;
      }).map(x0.invert));
      if (y1) y1.domain(y0.range().map(function(y) {
        return (y - view.y) / view.k;
      }).map(y0.invert));
    }
    function zoomstarted(dispatch) {
      if (!zooming++) dispatch({
        type: "zoomstart"
      });
    }
    function zoomed(dispatch) {
      rescale();
      dispatch({
        type: "zoom",
        scale: view.k,
        translate: [ view.x, view.y ]
      });
    }
    function zoomended(dispatch) {
      if (!--zooming) dispatch({
        type: "zoomend"
      }), center0 = null;
    }
    function mousedowned() {
      var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
      d3_selection_interrupt.call(that);
      zoomstarted(dispatch);
      function moved() {
        dragged = 1;
        translateTo(d3.mouse(that), location0);
        zoomed(dispatch);
      }
      function ended() {
        subject.on(mousemove, null).on(mouseup, null);
        dragRestore(dragged);
        zoomended(dispatch);
      }
    }
    function touchstarted() {
      var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that);
      started();
      zoomstarted(dispatch);
      subject.on(mousedown, null).on(touchstart, started);
      function relocate() {
        var touches = d3.touches(that);
        scale0 = view.k;
        touches.forEach(function(t) {
          if (t.identifier in locations0) locations0[t.identifier] = location(t);
        });
        return touches;
      }
      function started() {
        var target = d3.event.target;
        d3.select(target).on(touchmove, moved).on(touchend, ended);
        targets.push(target);
        var changed = d3.event.changedTouches;
        for (var i = 0, n = changed.length; i < n; ++i) {
          locations0[changed[i].identifier] = null;
        }
        var touches = relocate(), now = Date.now();
        if (touches.length === 1) {
          if (now - touchtime < 500) {
            var p = touches[0];
            zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
            d3_eventPreventDefault();
          }
          touchtime = now;
        } else if (touches.length > 1) {
          var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
          distance0 = dx * dx + dy * dy;
        }
      }
      function moved() {
        var touches = d3.touches(that), p0, l0, p1, l1;
        d3_selection_interrupt.call(that);
        for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
          p1 = touches[i];
          if (l1 = locations0[p1.identifier]) {
            if (l0) break;
            p0 = p1, l0 = l1;
          }
        }
        if (l1) {
          var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0);
          p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
          l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
          scaleTo(scale1 * scale0);
        }
        touchtime = null;
        translateTo(p0, l0);
        zoomed(dispatch);
      }
      function ended() {
        if (d3.event.touches.length) {
          var changed = d3.event.changedTouches;
          for (var i = 0, n = changed.length; i < n; ++i) {
            delete locations0[changed[i].identifier];
          }
          for (var identifier in locations0) {
            return void relocate();
          }
        }
        d3.selectAll(targets).on(zoomName, null);
        subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
        dragRestore();
        zoomended(dispatch);
      }
    }
    function mousewheeled() {
      var dispatch = event.of(this, arguments);
      if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), 
      translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
      mousewheelTimer = setTimeout(function() {
        mousewheelTimer = null;
        zoomended(dispatch);
      }, 50);
      d3_eventPreventDefault();
      scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
      translateTo(center0, translate0);
      zoomed(dispatch);
    }
    function dblclicked() {
      var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2;
      zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
    }
    return d3.rebind(zoom, event, "on");
  };
  var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel;
  d3.color = d3_color;
  function d3_color() {}
  d3_color.prototype.toString = function() {
    return this.rgb() + "";
  };
  d3.hsl = d3_hsl;
  function d3_hsl(h, s, l) {
    return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
  }
  var d3_hslPrototype = d3_hsl.prototype = new d3_color();
  d3_hslPrototype.brighter = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_hsl(this.h, this.s, this.l / k);
  };
  d3_hslPrototype.darker = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_hsl(this.h, this.s, k * this.l);
  };
  d3_hslPrototype.rgb = function() {
    return d3_hsl_rgb(this.h, this.s, this.l);
  };
  function d3_hsl_rgb(h, s, l) {
    var m1, m2;
    h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
    s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
    l = l < 0 ? 0 : l > 1 ? 1 : l;
    m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
    m1 = 2 * l - m2;
    function v(h) {
      if (h > 360) h -= 360; else if (h < 0) h += 360;
      if (h < 60) return m1 + (m2 - m1) * h / 60;
      if (h < 180) return m2;
      if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
      return m1;
    }
    function vv(h) {
      return Math.round(v(h) * 255);
    }
    return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
  }
  d3.hcl = d3_hcl;
  function d3_hcl(h, c, l) {
    return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
  }
  var d3_hclPrototype = d3_hcl.prototype = new d3_color();
  d3_hclPrototype.brighter = function(k) {
    return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.darker = function(k) {
    return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.rgb = function() {
    return d3_hcl_lab(this.h, this.c, this.l).rgb();
  };
  function d3_hcl_lab(h, c, l) {
    if (isNaN(h)) h = 0;
    if (isNaN(c)) c = 0;
    return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
  }
  d3.lab = d3_lab;
  function d3_lab(l, a, b) {
    return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
  }
  var d3_lab_K = 18;
  var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
  var d3_labPrototype = d3_lab.prototype = new d3_color();
  d3_labPrototype.brighter = function(k) {
    return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.darker = function(k) {
    return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.rgb = function() {
    return d3_lab_rgb(this.l, this.a, this.b);
  };
  function d3_lab_rgb(l, a, b) {
    var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
    x = d3_lab_xyz(x) * d3_lab_X;
    y = d3_lab_xyz(y) * d3_lab_Y;
    z = d3_lab_xyz(z) * d3_lab_Z;
    return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
  }
  function d3_lab_hcl(l, a, b) {
    return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
  }
  function d3_lab_xyz(x) {
    return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
  }
  function d3_xyz_lab(x) {
    return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
  }
  function d3_xyz_rgb(r) {
    return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
  }
  d3.rgb = d3_rgb;
  function d3_rgb(r, g, b) {
    return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
  }
  function d3_rgbNumber(value) {
    return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
  }
  function d3_rgbString(value) {
    return d3_rgbNumber(value) + "";
  }
  var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
  d3_rgbPrototype.brighter = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    var r = this.r, g = this.g, b = this.b, i = 30;
    if (!r && !g && !b) return new d3_rgb(i, i, i);
    if (r && r < i) r = i;
    if (g && g < i) g = i;
    if (b && b < i) b = i;
    return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
  };
  d3_rgbPrototype.darker = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_rgb(k * this.r, k * this.g, k * this.b);
  };
  d3_rgbPrototype.hsl = function() {
    return d3_rgb_hsl(this.r, this.g, this.b);
  };
  d3_rgbPrototype.toString = function() {
    return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
  };
  function d3_rgb_hex(v) {
    return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
  }
  function d3_rgb_parse(format, rgb, hsl) {
    var r = 0, g = 0, b = 0, m1, m2, color;
    m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase());
    if (m1) {
      m2 = m1[2].split(",");
      switch (m1[1]) {
       case "hsl":
        {
          return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
        }

       case "rgb":
        {
          return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
        }
      }
    }
    if (color = d3_rgb_names.get(format)) {
      return rgb(color.r, color.g, color.b);
    }
    if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
      if (format.length === 4) {
        r = (color & 3840) >> 4;
        r = r >> 4 | r;
        g = color & 240;
        g = g >> 4 | g;
        b = color & 15;
        b = b << 4 | b;
      } else if (format.length === 7) {
        r = (color & 16711680) >> 16;
        g = (color & 65280) >> 8;
        b = color & 255;
      }
    }
    return rgb(r, g, b);
  }
  function d3_rgb_hsl(r, g, b) {
    var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
    if (d) {
      s = l < .5 ? d / (max + min) : d / (2 - max - min);
      if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
      h *= 60;
    } else {
      h = NaN;
      s = l > 0 && l < 1 ? 0 : h;
    }
    return new d3_hsl(h, s, l);
  }
  function d3_rgb_lab(r, g, b) {
    r = d3_rgb_xyz(r);
    g = d3_rgb_xyz(g);
    b = d3_rgb_xyz(b);
    var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
    return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
  }
  function d3_rgb_xyz(r) {
    return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
  }
  function d3_rgb_parseNumber(c) {
    var f = parseFloat(c);
    return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
  }
  var d3_rgb_names = d3.map({
    aliceblue: 15792383,
    antiquewhite: 16444375,
    aqua: 65535,
    aquamarine: 8388564,
    azure: 15794175,
    beige: 16119260,
    bisque: 16770244,
    black: 0,
    blanchedalmond: 16772045,
    blue: 255,
    blueviolet: 9055202,
    brown: 10824234,
    burlywood: 14596231,
    cadetblue: 6266528,
    chartreuse: 8388352,
    chocolate: 13789470,
    coral: 16744272,
    cornflowerblue: 6591981,
    cornsilk: 16775388,
    crimson: 14423100,
    cyan: 65535,
    darkblue: 139,
    darkcyan: 35723,
    darkgoldenrod: 12092939,
    darkgray: 11119017,
    darkgreen: 25600,
    darkgrey: 11119017,
    darkkhaki: 12433259,
    darkmagenta: 9109643,
    darkolivegreen: 5597999,
    darkorange: 16747520,
    darkorchid: 10040012,
    darkred: 9109504,
    darksalmon: 15308410,
    darkseagreen: 9419919,
    darkslateblue: 4734347,
    darkslategray: 3100495,
    darkslategrey: 3100495,
    darkturquoise: 52945,
    darkviolet: 9699539,
    deeppink: 16716947,
    deepskyblue: 49151,
    dimgray: 6908265,
    dimgrey: 6908265,
    dodgerblue: 2003199,
    firebrick: 11674146,
    floralwhite: 16775920,
    forestgreen: 2263842,
    fuchsia: 16711935,
    gainsboro: 14474460,
    ghostwhite: 16316671,
    gold: 16766720,
    goldenrod: 14329120,
    gray: 8421504,
    green: 32768,
    greenyellow: 11403055,
    grey: 8421504,
    honeydew: 15794160,
    hotpink: 16738740,
    indianred: 13458524,
    indigo: 4915330,
    ivory: 16777200,
    khaki: 15787660,
    lavender: 15132410,
    lavenderblush: 16773365,
    lawngreen: 8190976,
    lemonchiffon: 16775885,
    lightblue: 11393254,
    lightcoral: 15761536,
    lightcyan: 14745599,
    lightgoldenrodyellow: 16448210,
    lightgray: 13882323,
    lightgreen: 9498256,
    lightgrey: 13882323,
    lightpink: 16758465,
    lightsalmon: 16752762,
    lightseagreen: 2142890,
    lightskyblue: 8900346,
    lightslategray: 7833753,
    lightslategrey: 7833753,
    lightsteelblue: 11584734,
    lightyellow: 16777184,
    lime: 65280,
    limegreen: 3329330,
    linen: 16445670,
    magenta: 16711935,
    maroon: 8388608,
    mediumaquamarine: 6737322,
    mediumblue: 205,
    mediumorchid: 12211667,
    mediumpurple: 9662683,
    mediumseagreen: 3978097,
    mediumslateblue: 8087790,
    mediumspringgreen: 64154,
    mediumturquoise: 4772300,
    mediumvioletred: 13047173,
    midnightblue: 1644912,
    mintcream: 16121850,
    mistyrose: 16770273,
    moccasin: 16770229,
    navajowhite: 16768685,
    navy: 128,
    oldlace: 16643558,
    olive: 8421376,
    olivedrab: 7048739,
    orange: 16753920,
    orangered: 16729344,
    orchid: 14315734,
    palegoldenrod: 15657130,
    palegreen: 10025880,
    paleturquoise: 11529966,
    palevioletred: 14381203,
    papayawhip: 16773077,
    peachpuff: 16767673,
    peru: 13468991,
    pink: 16761035,
    plum: 14524637,
    powderblue: 11591910,
    purple: 8388736,
    rebeccapurple: 6697881,
    red: 16711680,
    rosybrown: 12357519,
    royalblue: 4286945,
    saddlebrown: 9127187,
    salmon: 16416882,
    sandybrown: 16032864,
    seagreen: 3050327,
    seashell: 16774638,
    sienna: 10506797,
    silver: 12632256,
    skyblue: 8900331,
    slateblue: 6970061,
    slategray: 7372944,
    slategrey: 7372944,
    snow: 16775930,
    springgreen: 65407,
    steelblue: 4620980,
    tan: 13808780,
    teal: 32896,
    thistle: 14204888,
    tomato: 16737095,
    turquoise: 4251856,
    violet: 15631086,
    wheat: 16113331,
    white: 16777215,
    whitesmoke: 16119285,
    yellow: 16776960,
    yellowgreen: 10145074
  });
  d3_rgb_names.forEach(function(key, value) {
    d3_rgb_names.set(key, d3_rgbNumber(value));
  });
  function d3_functor(v) {
    return typeof v === "function" ? v : function() {
      return v;
    };
  }
  d3.functor = d3_functor;
  d3.xhr = d3_xhrType(d3_identity);
  function d3_xhrType(response) {
    return function(url, mimeType, callback) {
      if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, 
      mimeType = null;
      return d3_xhr(url, mimeType, response, callback);
    };
  }
  function d3_xhr(url, mimeType, response, callback) {
    var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
    if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
    "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
      request.readyState > 3 && respond();
    };
    function respond() {
      var status = request.status, result;
      if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
        try {
          result = response.call(xhr, request);
        } catch (e) {
          dispatch.error.call(xhr, e);
          return;
        }
        dispatch.load.call(xhr, result);
      } else {
        dispatch.error.call(xhr, request);
      }
    }
    request.onprogress = function(event) {
      var o = d3.event;
      d3.event = event;
      try {
        dispatch.progress.call(xhr, request);
      } finally {
        d3.event = o;
      }
    };
    xhr.header = function(name, value) {
      name = (name + "").toLowerCase();
      if (arguments.length < 2) return headers[name];
      if (value == null) delete headers[name]; else headers[name] = value + "";
      return xhr;
    };
    xhr.mimeType = function(value) {
      if (!arguments.length) return mimeType;
      mimeType = value == null ? null : value + "";
      return xhr;
    };
    xhr.responseType = function(value) {
      if (!arguments.length) return responseType;
      responseType = value;
      return xhr;
    };
    xhr.response = function(value) {
      response = value;
      return xhr;
    };
    [ "get", "post" ].forEach(function(method) {
      xhr[method] = function() {
        return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
      };
    });
    xhr.send = function(method, data, callback) {
      if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
      request.open(method, url, true);
      if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
      if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
      if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
      if (responseType != null) request.responseType = responseType;
      if (callback != null) xhr.on("error", callback).on("load", function(request) {
        callback(null, request);
      });
      dispatch.beforesend.call(xhr, request);
      request.send(data == null ? null : data);
      return xhr;
    };
    xhr.abort = function() {
      request.abort();
      return xhr;
    };
    d3.rebind(xhr, dispatch, "on");
    return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
  }
  function d3_xhr_fixCallback(callback) {
    return callback.length === 1 ? function(error, request) {
      callback(error == null ? request : null);
    } : callback;
  }
  function d3_xhrHasResponse(request) {
    var type = request.responseType;
    return type && type !== "text" ? request.response : request.responseText;
  }
  d3.dsv = function(delimiter, mimeType) {
    var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
    function dsv(url, row, callback) {
      if (arguments.length < 3) callback = row, row = null;
      var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
      xhr.row = function(_) {
        return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
      };
      return xhr;
    }
    function response(request) {
      return dsv.parse(request.responseText);
    }
    function typedResponse(f) {
      return function(request) {
        return dsv.parse(request.responseText, f);
      };
    }
    dsv.parse = function(text, f) {
      var o;
      return dsv.parseRows(text, function(row, i) {
        if (o) return o(row, i - 1);
        var a = new Function("d", "return {" + row.map(function(name, i) {
          return JSON.stringify(name) + ": d[" + i + "]";
        }).join(",") + "}");
        o = f ? function(row, i) {
          return f(a(row), i);
        } : a;
      });
    };
    dsv.parseRows = function(text, f) {
      var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
      function token() {
        if (I >= N) return EOF;
        if (eol) return eol = false, EOL;
        var j = I;
        if (text.charCodeAt(j) === 34) {
          var i = j;
          while (i++ < N) {
            if (text.charCodeAt(i) === 34) {
              if (text.charCodeAt(i + 1) !== 34) break;
              ++i;
            }
          }
          I = i + 2;
          var c = text.charCodeAt(i + 1);
          if (c === 13) {
            eol = true;
            if (text.charCodeAt(i + 2) === 10) ++I;
          } else if (c === 10) {
            eol = true;
          }
          return text.slice(j + 1, i).replace(/""/g, '"');
        }
        while (I < N) {
          var c = text.charCodeAt(I++), k = 1;
          if (c === 10) eol = true; else if (c === 13) {
            eol = true;
            if (text.charCodeAt(I) === 10) ++I, ++k;
          } else if (c !== delimiterCode) continue;
          return text.slice(j, I - k);
        }
        return text.slice(j);
      }
      while ((t = token()) !== EOF) {
        var a = [];
        while (t !== EOL && t !== EOF) {
          a.push(t);
          t = token();
        }
        if (f && (a = f(a, n++)) == null) continue;
        rows.push(a);
      }
      return rows;
    };
    dsv.format = function(rows) {
      if (Array.isArray(rows[0])) return dsv.formatRows(rows);
      var fieldSet = new d3_Set(), fields = [];
      rows.forEach(function(row) {
        for (var field in row) {
          if (!fieldSet.has(field)) {
            fields.push(fieldSet.add(field));
          }
        }
      });
      return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
        return fields.map(function(field) {
          return formatValue(row[field]);
        }).join(delimiter);
      })).join("\n");
    };
    dsv.formatRows = function(rows) {
      return rows.map(formatRow).join("\n");
    };
    function formatRow(row) {
      return row.map(formatValue).join(delimiter);
    }
    function formatValue(text) {
      return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
    }
    return dsv;
  };
  d3.csv = d3.dsv(",", "text/csv");
  d3.tsv = d3.dsv("	", "text/tab-separated-values");
  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
    setTimeout(callback, 17);
  };
  d3.timer = function() {
    d3_timer.apply(this, arguments);
  };
  function d3_timer(callback, delay, then) {
    var n = arguments.length;
    if (n < 2) delay = 0;
    if (n < 3) then = Date.now();
    var time = then + delay, timer = {
      c: callback,
      t: time,
      n: null
    };
    if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
    d3_timer_queueTail = timer;
    if (!d3_timer_interval) {
      d3_timer_timeout = clearTimeout(d3_timer_timeout);
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
    return timer;
  }
  function d3_timer_step() {
    var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
    if (delay > 24) {
      if (isFinite(delay)) {
        clearTimeout(d3_timer_timeout);
        d3_timer_timeout = setTimeout(d3_timer_step, delay);
      }
      d3_timer_interval = 0;
    } else {
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
  }
  d3.timer.flush = function() {
    d3_timer_mark();
    d3_timer_sweep();
  };
  function d3_timer_mark() {
    var now = Date.now(), timer = d3_timer_queueHead;
    while (timer) {
      if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
      timer = timer.n;
    }
    return now;
  }
  function d3_timer_sweep() {
    var t0, t1 = d3_timer_queueHead, time = Infinity;
    while (t1) {
      if (t1.c) {
        if (t1.t < time) time = t1.t;
        t1 = (t0 = t1).n;
      } else {
        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
      }
    }
    d3_timer_queueTail = t0;
    return time;
  }
  function d3_format_precision(x, p) {
    return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
  }
  d3.round = function(x, n) {
    return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
  };
  var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
  d3.formatPrefix = function(value, precision) {
    var i = 0;
    if (value = +value) {
      if (value < 0) value *= -1;
      if (precision) value = d3.round(value, d3_format_precision(value, precision));
      i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
      i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
    }
    return d3_formatPrefixes[8 + i / 3];
  };
  function d3_formatPrefix(d, i) {
    var k = Math.pow(10, abs(8 - i) * 3);
    return {
      scale: i > 8 ? function(d) {
        return d / k;
      } : function(d) {
        return d * k;
      },
      symbol: d
    };
  }
  function d3_locale_numberFormat(locale) {
    var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
      var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
      while (i > 0 && g > 0) {
        if (length + g + 1 > width) g = Math.max(1, width - length);
        t.push(value.substring(i -= g, i + g));
        if ((length += g + 1) > width) break;
        g = locale_grouping[j = (j + 1) % locale_grouping.length];
      }
      return t.reverse().join(locale_thousands);
    } : d3_identity;
    return function(specifier) {
      var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
      if (precision) precision = +precision.substring(1);
      if (zfill || fill === "0" && align === "=") {
        zfill = fill = "0";
        align = "=";
      }
      switch (type) {
       case "n":
        comma = true;
        type = "g";
        break;

       case "%":
        scale = 100;
        suffix = "%";
        type = "f";
        break;

       case "p":
        scale = 100;
        suffix = "%";
        type = "r";
        break;

       case "b":
       case "o":
       case "x":
       case "X":
        if (symbol === "#") prefix = "0" + type.toLowerCase();

       case "c":
        exponent = false;

       case "d":
        integer = true;
        precision = 0;
        break;

       case "s":
        scale = -1;
        type = "r";
        break;
      }
      if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
      if (type == "r" && !precision) type = "g";
      if (precision != null) {
        if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
      }
      type = d3_format_types.get(type) || d3_format_typeDefault;
      var zcomma = zfill && comma;
      return function(value) {
        var fullSuffix = suffix;
        if (integer && value % 1) return "";
        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
        if (scale < 0) {
          var unit = d3.formatPrefix(value, precision);
          value = unit.scale(value);
          fullSuffix = unit.symbol + suffix;
        } else {
          value *= scale;
        }
        value = type(value, precision);
        var i = value.lastIndexOf("."), before, after;
        if (i < 0) {
          var j = exponent ? value.lastIndexOf("e") : -1;
          if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
        } else {
          before = value.substring(0, i);
          after = locale_decimal + value.substring(i + 1);
        }
        if (!zfill && comma) before = formatGroup(before, Infinity);
        var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
        if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
        negative += prefix;
        value = before + after;
        return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
      };
    };
  }
  var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
  var d3_format_types = d3.map({
    b: function(x) {
      return x.toString(2);
    },
    c: function(x) {
      return String.fromCharCode(x);
    },
    o: function(x) {
      return x.toString(8);
    },
    x: function(x) {
      return x.toString(16);
    },
    X: function(x) {
      return x.toString(16).toUpperCase();
    },
    g: function(x, p) {
      return x.toPrecision(p);
    },
    e: function(x, p) {
      return x.toExponential(p);
    },
    f: function(x, p) {
      return x.toFixed(p);
    },
    r: function(x, p) {
      return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
    }
  });
  function d3_format_typeDefault(x) {
    return x + "";
  }
  var d3_time = d3.time = {}, d3_date = Date;
  function d3_date_utc() {
    this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
  }
  d3_date_utc.prototype = {
    getDate: function() {
      return this._.getUTCDate();
    },
    getDay: function() {
      return this._.getUTCDay();
    },
    getFullYear: function() {
      return this._.getUTCFullYear();
    },
    getHours: function() {
      return this._.getUTCHours();
    },
    getMilliseconds: function() {
      return this._.getUTCMilliseconds();
    },
    getMinutes: function() {
      return this._.getUTCMinutes();
    },
    getMonth: function() {
      return this._.getUTCMonth();
    },
    getSeconds: function() {
      return this._.getUTCSeconds();
    },
    getTime: function() {
      return this._.getTime();
    },
    getTimezoneOffset: function() {
      return 0;
    },
    valueOf: function() {
      return this._.valueOf();
    },
    setDate: function() {
      d3_time_prototype.setUTCDate.apply(this._, arguments);
    },
    setDay: function() {
      d3_time_prototype.setUTCDay.apply(this._, arguments);
    },
    setFullYear: function() {
      d3_time_prototype.setUTCFullYear.apply(this._, arguments);
    },
    setHours: function() {
      d3_time_prototype.setUTCHours.apply(this._, arguments);
    },
    setMilliseconds: function() {
      d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
    },
    setMinutes: function() {
      d3_time_prototype.setUTCMinutes.apply(this._, arguments);
    },
    setMonth: function() {
      d3_time_prototype.setUTCMonth.apply(this._, arguments);
    },
    setSeconds: function() {
      d3_time_prototype.setUTCSeconds.apply(this._, arguments);
    },
    setTime: function() {
      d3_time_prototype.setTime.apply(this._, arguments);
    }
  };
  var d3_time_prototype = Date.prototype;
  function d3_time_interval(local, step, number) {
    function round(date) {
      var d0 = local(date), d1 = offset(d0, 1);
      return date - d0 < d1 - date ? d0 : d1;
    }
    function ceil(date) {
      step(date = local(new d3_date(date - 1)), 1);
      return date;
    }
    function offset(date, k) {
      step(date = new d3_date(+date), k);
      return date;
    }
    function range(t0, t1, dt) {
      var time = ceil(t0), times = [];
      if (dt > 1) {
        while (time < t1) {
          if (!(number(time) % dt)) times.push(new Date(+time));
          step(time, 1);
        }
      } else {
        while (time < t1) times.push(new Date(+time)), step(time, 1);
      }
      return times;
    }
    function range_utc(t0, t1, dt) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = t0;
        return range(utc, t1, dt);
      } finally {
        d3_date = Date;
      }
    }
    local.floor = local;
    local.round = round;
    local.ceil = ceil;
    local.offset = offset;
    local.range = range;
    var utc = local.utc = d3_time_interval_utc(local);
    utc.floor = utc;
    utc.round = d3_time_interval_utc(round);
    utc.ceil = d3_time_interval_utc(ceil);
    utc.offset = d3_time_interval_utc(offset);
    utc.range = range_utc;
    return local;
  }
  function d3_time_interval_utc(method) {
    return function(date, k) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = date;
        return method(utc, k)._;
      } finally {
        d3_date = Date;
      }
    };
  }
  d3_time.year = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setMonth(0, 1);
    return date;
  }, function(date, offset) {
    date.setFullYear(date.getFullYear() + offset);
  }, function(date) {
    return date.getFullYear();
  });
  d3_time.years = d3_time.year.range;
  d3_time.years.utc = d3_time.year.utc.range;
  d3_time.day = d3_time_interval(function(date) {
    var day = new d3_date(2e3, 0);
    day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
    return day;
  }, function(date, offset) {
    date.setDate(date.getDate() + offset);
  }, function(date) {
    return date.getDate() - 1;
  });
  d3_time.days = d3_time.day.range;
  d3_time.days.utc = d3_time.day.utc.range;
  d3_time.dayOfYear = function(date) {
    var year = d3_time.year(date);
    return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
  };
  [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) {
    i = 7 - i;
    var interval = d3_time[day] = d3_time_interval(function(date) {
      (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
      return date;
    }, function(date, offset) {
      date.setDate(date.getDate() + Math.floor(offset) * 7);
    }, function(date) {
      var day = d3_time.year(date).getDay();
      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
    });
    d3_time[day + "s"] = interval.range;
    d3_time[day + "s"].utc = interval.utc.range;
    d3_time[day + "OfYear"] = function(date) {
      var day = d3_time.year(date).getDay();
      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
    };
  });
  d3_time.week = d3_time.sunday;
  d3_time.weeks = d3_time.sunday.range;
  d3_time.weeks.utc = d3_time.sunday.utc.range;
  d3_time.weekOfYear = d3_time.sundayOfYear;
  function d3_locale_timeFormat(locale) {
    var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths;
    function d3_time_format(template) {
      var n = template.length;
      function format(date) {
        var string = [], i = -1, j = 0, c, p, f;
        while (++i < n) {
          if (template.charCodeAt(i) === 37) {
            string.push(template.slice(j, i));
            if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
            if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
            string.push(c);
            j = i + 1;
          }
        }
        string.push(template.slice(j, i));
        return string.join("");
      }
      format.parse = function(string) {
        var d = {
          y: 1900,
          m: 0,
          d: 1,
          H: 0,
          M: 0,
          S: 0,
          L: 0,
          Z: null
        }, i = d3_time_parse(d, template, string, 0);
        if (i != string.length) return null;
        if ("p" in d) d.H = d.H % 12 + d.p * 12;
        var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
        if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) {
          if (!("w" in d)) d.w = "W" in d ? 1 : 0;
          date.setFullYear(d.y, 0, 1);
          date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
        } else date.setFullYear(d.y, d.m, d.d);
        date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
        return localZ ? date._ : date;
      };
      format.toString = function() {
        return template;
      };
      return format;
    }
    function d3_time_parse(date, template, string, j) {
      var c, p, t, i = 0, n = template.length, m = string.length;
      while (i < n) {
        if (j >= m) return -1;
        c = template.charCodeAt(i++);
        if (c === 37) {
          t = template.charAt(i++);
          p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
          if (!p || (j = p(date, string, j)) < 0) return -1;
        } else if (c != string.charCodeAt(j++)) {
          return -1;
        }
      }
      return j;
    }
    d3_time_format.utc = function(template) {
      var local = d3_time_format(template);
      function format(date) {
        try {
          d3_date = d3_date_utc;
          var utc = new d3_date();
          utc._ = date;
          return local(utc);
        } finally {
          d3_date = Date;
        }
      }
      format.parse = function(string) {
        try {
          d3_date = d3_date_utc;
          var date = local.parse(string);
          return date && date._;
        } finally {
          d3_date = Date;
        }
      };
      format.toString = local.toString;
      return format;
    };
    d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
    var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
    locale_periods.forEach(function(p, i) {
      d3_time_periodLookup.set(p.toLowerCase(), i);
    });
    var d3_time_formats = {
      a: function(d) {
        return locale_shortDays[d.getDay()];
      },
      A: function(d) {
        return locale_days[d.getDay()];
      },
      b: function(d) {
        return locale_shortMonths[d.getMonth()];
      },
      B: function(d) {
        return locale_months[d.getMonth()];
      },
      c: d3_time_format(locale_dateTime),
      d: function(d, p) {
        return d3_time_formatPad(d.getDate(), p, 2);
      },
      e: function(d, p) {
        return d3_time_formatPad(d.getDate(), p, 2);
      },
      H: function(d, p) {
        return d3_time_formatPad(d.getHours(), p, 2);
      },
      I: function(d, p) {
        return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
      },
      j: function(d, p) {
        return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
      },
      L: function(d, p) {
        return d3_time_formatPad(d.getMilliseconds(), p, 3);
      },
      m: function(d, p) {
        return d3_time_formatPad(d.getMonth() + 1, p, 2);
      },
      M: function(d, p) {
        return d3_time_formatPad(d.getMinutes(), p, 2);
      },
      p: function(d) {
        return locale_periods[+(d.getHours() >= 12)];
      },
      S: function(d, p) {
        return d3_time_formatPad(d.getSeconds(), p, 2);
      },
      U: function(d, p) {
        return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
      },
      w: function(d) {
        return d.getDay();
      },
      W: function(d, p) {
        return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
      },
      x: d3_time_format(locale_date),
      X: d3_time_format(locale_time),
      y: function(d, p) {
        return d3_time_formatPad(d.getFullYear() % 100, p, 2);
      },
      Y: function(d, p) {
        return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
      },
      Z: d3_time_zone,
      "%": function() {
        return "%";
      }
    };
    var d3_time_parsers = {
      a: d3_time_parseWeekdayAbbrev,
      A: d3_time_parseWeekday,
      b: d3_time_parseMonthAbbrev,
      B: d3_time_parseMonth,
      c: d3_time_parseLocaleFull,
      d: d3_time_parseDay,
      e: d3_time_parseDay,
      H: d3_time_parseHour24,
      I: d3_time_parseHour24,
      j: d3_time_parseDayOfYear,
      L: d3_time_parseMilliseconds,
      m: d3_time_parseMonthNumber,
      M: d3_time_parseMinutes,
      p: d3_time_parseAmPm,
      S: d3_time_parseSeconds,
      U: d3_time_parseWeekNumberSunday,
      w: d3_time_parseWeekdayNumber,
      W: d3_time_parseWeekNumberMonday,
      x: d3_time_parseLocaleDate,
      X: d3_time_parseLocaleTime,
      y: d3_time_parseYear,
      Y: d3_time_parseFullYear,
      Z: d3_time_parseZone,
      "%": d3_time_parseLiteralPercent
    };
    function d3_time_parseWeekdayAbbrev(date, string, i) {
      d3_time_dayAbbrevRe.lastIndex = 0;
      var n = d3_time_dayAbbrevRe.exec(string.slice(i));
      return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseWeekday(date, string, i) {
      d3_time_dayRe.lastIndex = 0;
      var n = d3_time_dayRe.exec(string.slice(i));
      return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseMonthAbbrev(date, string, i) {
      d3_time_monthAbbrevRe.lastIndex = 0;
      var n = d3_time_monthAbbrevRe.exec(string.slice(i));
      return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseMonth(date, string, i) {
      d3_time_monthRe.lastIndex = 0;
      var n = d3_time_monthRe.exec(string.slice(i));
      return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseLocaleFull(date, string, i) {
      return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
    }
    function d3_time_parseLocaleDate(date, string, i) {
      return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
    }
    function d3_time_parseLocaleTime(date, string, i) {
      return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
    }
    function d3_time_parseAmPm(date, string, i) {
      var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
      return n == null ? -1 : (date.p = n, i);
    }
    return d3_time_format;
  }
  var d3_time_formatPads = {
    "-": "",
    _: " ",
    "0": "0"
  }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/;
  function d3_time_formatPad(value, fill, width) {
    var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
    return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
  }
  function d3_time_formatRe(names) {
    return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
  }
  function d3_time_formatLookup(names) {
    var map = new d3_Map(), i = -1, n = names.length;
    while (++i < n) map.set(names[i].toLowerCase(), i);
    return map;
  }
  function d3_time_parseWeekdayNumber(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 1));
    return n ? (date.w = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseWeekNumberSunday(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i));
    return n ? (date.U = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseWeekNumberMonday(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i));
    return n ? (date.W = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseFullYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 4));
    return n ? (date.y = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
  }
  function d3_time_parseZone(date, string, i) {
    return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, 
    i + 5) : -1;
  }
  function d3_time_expandYear(d) {
    return d + (d > 68 ? 1900 : 2e3);
  }
  function d3_time_parseMonthNumber(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
  }
  function d3_time_parseDay(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.d = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseDayOfYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
    return n ? (date.j = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseHour24(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.H = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseMinutes(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.M = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseSeconds(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.S = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseMilliseconds(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
    return n ? (date.L = +n[0], i + n[0].length) : -1;
  }
  function d3_time_zone(d) {
    var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60;
    return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
  }
  function d3_time_parseLiteralPercent(date, string, i) {
    d3_time_percentRe.lastIndex = 0;
    var n = d3_time_percentRe.exec(string.slice(i, i + 1));
    return n ? i + n[0].length : -1;
  }
  function d3_time_formatMulti(formats) {
    var n = formats.length, i = -1;
    while (++i < n) formats[i][0] = this(formats[i][0]);
    return function(date) {
      var i = 0, f = formats[i];
      while (!f[1](date)) f = formats[++i];
      return f[0](date);
    };
  }
  d3.locale = function(locale) {
    return {
      numberFormat: d3_locale_numberFormat(locale),
      timeFormat: d3_locale_timeFormat(locale)
    };
  };
  var d3_locale_enUS = d3.locale({
    decimal: ".",
    thousands: ",",
    grouping: [ 3 ],
    currency: [ "$", "" ],
    dateTime: "%a %b %e %X %Y",
    date: "%m/%d/%Y",
    time: "%H:%M:%S",
    periods: [ "AM", "PM" ],
    days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
    shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
    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" ]
  });
  d3.format = d3_locale_enUS.numberFormat;
  d3.geo = {};
  function d3_adder() {}
  d3_adder.prototype = {
    s: 0,
    t: 0,
    add: function(y) {
      d3_adderSum(y, this.t, d3_adderTemp);
      d3_adderSum(d3_adderTemp.s, this.s, this);
      if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
    },
    reset: function() {
      this.s = this.t = 0;
    },
    valueOf: function() {
      return this.s;
    }
  };
  var d3_adderTemp = new d3_adder();
  function d3_adderSum(a, b, o) {
    var x = o.s = a + b, bv = x - a, av = x - bv;
    o.t = a - av + (b - bv);
  }
  d3.geo.stream = function(object, listener) {
    if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
      d3_geo_streamObjectType[object.type](object, listener);
    } else {
      d3_geo_streamGeometry(object, listener);
    }
  };
  function d3_geo_streamGeometry(geometry, listener) {
    if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
      d3_geo_streamGeometryType[geometry.type](geometry, listener);
    }
  }
  var d3_geo_streamObjectType = {
    Feature: function(feature, listener) {
      d3_geo_streamGeometry(feature.geometry, listener);
    },
    FeatureCollection: function(object, listener) {
      var features = object.features, i = -1, n = features.length;
      while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
    }
  };
  var d3_geo_streamGeometryType = {
    Sphere: function(object, listener) {
      listener.sphere();
    },
    Point: function(object, listener) {
      object = object.coordinates;
      listener.point(object[0], object[1], object[2]);
    },
    MultiPoint: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
    },
    LineString: function(object, listener) {
      d3_geo_streamLine(object.coordinates, listener, 0);
    },
    MultiLineString: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
    },
    Polygon: function(object, listener) {
      d3_geo_streamPolygon(object.coordinates, listener);
    },
    MultiPolygon: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
    },
    GeometryCollection: function(object, listener) {
      var geometries = object.geometries, i = -1, n = geometries.length;
      while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
    }
  };
  function d3_geo_streamLine(coordinates, listener, closed) {
    var i = -1, n = coordinates.length - closed, coordinate;
    listener.lineStart();
    while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
    listener.lineEnd();
  }
  function d3_geo_streamPolygon(coordinates, listener) {
    var i = -1, n = coordinates.length;
    listener.polygonStart();
    while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
    listener.polygonEnd();
  }
  d3.geo.area = function(object) {
    d3_geo_areaSum = 0;
    d3.geo.stream(object, d3_geo_area);
    return d3_geo_areaSum;
  };
  var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
  var d3_geo_area = {
    sphere: function() {
      d3_geo_areaSum += 4 * π;
    },
    point: d3_noop,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: function() {
      d3_geo_areaRingSum.reset();
      d3_geo_area.lineStart = d3_geo_areaRingStart;
    },
    polygonEnd: function() {
      var area = 2 * d3_geo_areaRingSum;
      d3_geo_areaSum += area < 0 ? 4 * π + area : area;
      d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
    }
  };
  function d3_geo_areaRingStart() {
    var λ00, φ00, λ0, cosφ0, sinφ0;
    d3_geo_area.point = function(λ, φ) {
      d3_geo_area.point = nextPoint;
      λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), 
      sinφ0 = Math.sin(φ);
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      φ = φ * d3_radians / 2 + π / 4;
      var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ);
      d3_geo_areaRingSum.add(Math.atan2(v, u));
      λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
    }
    d3_geo_area.lineEnd = function() {
      nextPoint(λ00, φ00);
    };
  }
  function d3_geo_cartesian(spherical) {
    var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ);
    return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ];
  }
  function d3_geo_cartesianDot(a, b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  }
  function d3_geo_cartesianCross(a, b) {
    return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
  }
  function d3_geo_cartesianAdd(a, b) {
    a[0] += b[0];
    a[1] += b[1];
    a[2] += b[2];
  }
  function d3_geo_cartesianScale(vector, k) {
    return [ vector[0] * k, vector[1] * k, vector[2] * k ];
  }
  function d3_geo_cartesianNormalize(d) {
    var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
    d[0] /= l;
    d[1] /= l;
    d[2] /= l;
  }
  function d3_geo_spherical(cartesian) {
    return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
  }
  function d3_geo_sphericalEqual(a, b) {
    return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
  }
  d3.geo.bounds = function() {
    var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
    var bound = {
      point: point,
      lineStart: lineStart,
      lineEnd: lineEnd,
      polygonStart: function() {
        bound.point = ringPoint;
        bound.lineStart = ringStart;
        bound.lineEnd = ringEnd;
        dλSum = 0;
        d3_geo_area.polygonStart();
      },
      polygonEnd: function() {
        d3_geo_area.polygonEnd();
        bound.point = point;
        bound.lineStart = lineStart;
        bound.lineEnd = lineEnd;
        if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90;
        range[0] = λ0, range[1] = λ1;
      }
    };
    function point(λ, φ) {
      ranges.push(range = [ λ0 = λ, λ1 = λ ]);
      if (φ < φ0) φ0 = φ;
      if (φ > φ1) φ1 = φ;
    }
    function linePoint(λ, φ) {
      var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]);
      if (p0) {
        var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
        d3_geo_cartesianNormalize(inflection);
        inflection = d3_geo_spherical(inflection);
        var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180;
        if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
          var φi = inflection[1] * d3_degrees;
          if (φi > φ1) φ1 = φi;
        } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
          var φi = -inflection[1] * d3_degrees;
          if (φi < φ0) φ0 = φi;
        } else {
          if (φ < φ0) φ0 = φ;
          if (φ > φ1) φ1 = φ;
        }
        if (antimeridian) {
          if (λ < λ_) {
            if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
          } else {
            if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
          }
        } else {
          if (λ1 >= λ0) {
            if (λ < λ0) λ0 = λ;
            if (λ > λ1) λ1 = λ;
          } else {
            if (λ > λ_) {
              if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
            } else {
              if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
            }
          }
        }
      } else {
        point(λ, φ);
      }
      p0 = p, λ_ = λ;
    }
    function lineStart() {
      bound.point = linePoint;
    }
    function lineEnd() {
      range[0] = λ0, range[1] = λ1;
      bound.point = point;
      p0 = null;
    }
    function ringPoint(λ, φ) {
      if (p0) {
        var dλ = λ - λ_;
        dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
      } else λ__ = λ, φ__ = φ;
      d3_geo_area.point(λ, φ);
      linePoint(λ, φ);
    }
    function ringStart() {
      d3_geo_area.lineStart();
    }
    function ringEnd() {
      ringPoint(λ__, φ__);
      d3_geo_area.lineEnd();
      if (abs(dλSum) > ε) λ0 = -(λ1 = 180);
      range[0] = λ0, range[1] = λ1;
      p0 = null;
    }
    function angle(λ0, λ1) {
      return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
    }
    function compareRanges(a, b) {
      return a[0] - b[0];
    }
    function withinRange(x, range) {
      return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
    }
    return function(feature) {
      φ1 = λ1 = -(λ0 = φ0 = Infinity);
      ranges = [];
      d3.geo.stream(feature, bound);
      var n = ranges.length;
      if (n) {
        ranges.sort(compareRanges);
        for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
          b = ranges[i];
          if (withinRange(b[0], a) || withinRange(b[1], a)) {
            if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
            if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
          } else {
            merged.push(a = b);
          }
        }
        var best = -Infinity, dλ;
        for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
          b = merged[i];
          if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
        }
      }
      ranges = range = null;
      return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ];
    };
  }();
  d3.geo.centroid = function(object) {
    d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
    d3.geo.stream(object, d3_geo_centroid);
    var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
    if (m < ε2) {
      x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
      if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
      m = x * x + y * y + z * z;
      if (m < ε2) return [ NaN, NaN ];
    }
    return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
  };
  var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
  var d3_geo_centroid = {
    sphere: d3_noop,
    point: d3_geo_centroidPoint,
    lineStart: d3_geo_centroidLineStart,
    lineEnd: d3_geo_centroidLineEnd,
    polygonStart: function() {
      d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
    },
    polygonEnd: function() {
      d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
    }
  };
  function d3_geo_centroidPoint(λ, φ) {
    λ *= d3_radians;
    var cosφ = Math.cos(φ *= d3_radians);
    d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
  }
  function d3_geo_centroidPointXYZ(x, y, z) {
    ++d3_geo_centroidW0;
    d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
    d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
    d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
  }
  function d3_geo_centroidLineStart() {
    var x0, y0, z0;
    d3_geo_centroid.point = function(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians);
      x0 = cosφ * Math.cos(λ);
      y0 = cosφ * Math.sin(λ);
      z0 = Math.sin(φ);
      d3_geo_centroid.point = nextPoint;
      d3_geo_centroidPointXYZ(x0, y0, z0);
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
      d3_geo_centroidW1 += w;
      d3_geo_centroidX1 += w * (x0 + (x0 = x));
      d3_geo_centroidY1 += w * (y0 + (y0 = y));
      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
      d3_geo_centroidPointXYZ(x0, y0, z0);
    }
  }
  function d3_geo_centroidLineEnd() {
    d3_geo_centroid.point = d3_geo_centroidPoint;
  }
  function d3_geo_centroidRingStart() {
    var λ00, φ00, x0, y0, z0;
    d3_geo_centroid.point = function(λ, φ) {
      λ00 = λ, φ00 = φ;
      d3_geo_centroid.point = nextPoint;
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians);
      x0 = cosφ * Math.cos(λ);
      y0 = cosφ * Math.sin(λ);
      z0 = Math.sin(φ);
      d3_geo_centroidPointXYZ(x0, y0, z0);
    };
    d3_geo_centroid.lineEnd = function() {
      nextPoint(λ00, φ00);
      d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
      d3_geo_centroid.point = d3_geo_centroidPoint;
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
      d3_geo_centroidX2 += v * cx;
      d3_geo_centroidY2 += v * cy;
      d3_geo_centroidZ2 += v * cz;
      d3_geo_centroidW1 += w;
      d3_geo_centroidX1 += w * (x0 + (x0 = x));
      d3_geo_centroidY1 += w * (y0 + (y0 = y));
      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
      d3_geo_centroidPointXYZ(x0, y0, z0);
    }
  }
  function d3_geo_compose(a, b) {
    function compose(x, y) {
      return x = a(x, y), b(x[0], x[1]);
    }
    if (a.invert && b.invert) compose.invert = function(x, y) {
      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
    };
    return compose;
  }
  function d3_true() {
    return true;
  }
  function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
    var subject = [], clip = [];
    segments.forEach(function(segment) {
      if ((n = segment.length - 1) <= 0) return;
      var n, p0 = segment[0], p1 = segment[n];
      if (d3_geo_sphericalEqual(p0, p1)) {
        listener.lineStart();
        for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
        listener.lineEnd();
        return;
      }
      var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
      a.o = b;
      subject.push(a);
      clip.push(b);
      a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
      b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
      a.o = b;
      subject.push(a);
      clip.push(b);
    });
    clip.sort(compare);
    d3_geo_clipPolygonLinkCircular(subject);
    d3_geo_clipPolygonLinkCircular(clip);
    if (!subject.length) return;
    for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
      clip[i].e = entry = !entry;
    }
    var start = subject[0], points, point;
    while (1) {
      var current = start, isSubject = true;
      while (current.v) if ((current = current.n) === start) return;
      points = current.z;
      listener.lineStart();
      do {
        current.v = current.o.v = true;
        if (current.e) {
          if (isSubject) {
            for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
          } else {
            interpolate(current.x, current.n.x, 1, listener);
          }
          current = current.n;
        } else {
          if (isSubject) {
            points = current.p.z;
            for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
          } else {
            interpolate(current.x, current.p.x, -1, listener);
          }
          current = current.p;
        }
        current = current.o;
        points = current.z;
        isSubject = !isSubject;
      } while (!current.v);
      listener.lineEnd();
    }
  }
  function d3_geo_clipPolygonLinkCircular(array) {
    if (!(n = array.length)) return;
    var n, i = 0, a = array[0], b;
    while (++i < n) {
      a.n = b = array[i];
      b.p = a;
      a = b;
    }
    a.n = b = array[0];
    b.p = a;
  }
  function d3_geo_clipPolygonIntersection(point, points, other, entry) {
    this.x = point;
    this.z = points;
    this.o = other;
    this.e = entry;
    this.v = false;
    this.n = this.p = null;
  }
  function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
    return function(rotate, listener) {
      var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          clip.point = pointRing;
          clip.lineStart = ringStart;
          clip.lineEnd = ringEnd;
          segments = [];
          polygon = [];
        },
        polygonEnd: function() {
          clip.point = point;
          clip.lineStart = lineStart;
          clip.lineEnd = lineEnd;
          segments = d3.merge(segments);
          var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
          if (segments.length) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
          } else if (clipStartInside) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            listener.lineStart();
            interpolate(null, null, 1, listener);
            listener.lineEnd();
          }
          if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
          segments = polygon = null;
        },
        sphere: function() {
          listener.polygonStart();
          listener.lineStart();
          interpolate(null, null, 1, listener);
          listener.lineEnd();
          listener.polygonEnd();
        }
      };
      function point(λ, φ) {
        var point = rotate(λ, φ);
        if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
      }
      function pointLine(λ, φ) {
        var point = rotate(λ, φ);
        line.point(point[0], point[1]);
      }
      function lineStart() {
        clip.point = pointLine;
        line.lineStart();
      }
      function lineEnd() {
        clip.point = point;
        line.lineEnd();
      }
      var segments;
      var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring;
      function pointRing(λ, φ) {
        ring.push([ λ, φ ]);
        var point = rotate(λ, φ);
        ringListener.point(point[0], point[1]);
      }
      function ringStart() {
        ringListener.lineStart();
        ring = [];
      }
      function ringEnd() {
        pointRing(ring[0][0], ring[0][1]);
        ringListener.lineEnd();
        var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
        ring.pop();
        polygon.push(ring);
        ring = null;
        if (!n) return;
        if (clean & 1) {
          segment = ringSegments[0];
          var n = segment.length - 1, i = -1, point;
          if (n > 0) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            listener.lineStart();
            while (++i < n) listener.point((point = segment[i])[0], point[1]);
            listener.lineEnd();
          }
          return;
        }
        if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
        segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
      }
      return clip;
    };
  }
  function d3_geo_clipSegmentLength1(segment) {
    return segment.length > 1;
  }
  function d3_geo_clipBufferListener() {
    var lines = [], line;
    return {
      lineStart: function() {
        lines.push(line = []);
      },
      point: function(λ, φ) {
        line.push([ λ, φ ]);
      },
      lineEnd: d3_noop,
      buffer: function() {
        var buffer = lines;
        lines = [];
        line = null;
        return buffer;
      },
      rejoin: function() {
        if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
      }
    };
  }
  function d3_geo_clipSort(a, b) {
    return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
  }
  var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]);
  function d3_geo_clipAntimeridianLine(listener) {
    var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean;
    return {
      lineStart: function() {
        listener.lineStart();
        clean = 1;
      },
      point: function(λ1, φ1) {
        var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0);
        if (abs(dλ - π) < ε) {
          listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
          listener.point(sλ0, φ0);
          listener.lineEnd();
          listener.lineStart();
          listener.point(sλ1, φ0);
          listener.point(λ1, φ0);
          clean = 0;
        } else if (sλ0 !== sλ1 && dλ >= π) {
          if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
          if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
          φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
          listener.point(sλ0, φ0);
          listener.lineEnd();
          listener.lineStart();
          listener.point(sλ1, φ0);
          clean = 0;
        }
        listener.point(λ0 = λ1, φ0 = φ1);
        sλ0 = sλ1;
      },
      lineEnd: function() {
        listener.lineEnd();
        λ0 = φ0 = NaN;
      },
      clean: function() {
        return 2 - clean;
      }
    };
  }
  function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
    var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
    return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
  }
  function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
    var φ;
    if (from == null) {
      φ = direction * halfπ;
      listener.point(-π, φ);
      listener.point(0, φ);
      listener.point(π, φ);
      listener.point(π, 0);
      listener.point(π, -φ);
      listener.point(0, -φ);
      listener.point(-π, -φ);
      listener.point(-π, 0);
      listener.point(-π, φ);
    } else if (abs(from[0] - to[0]) > ε) {
      var s = from[0] < to[0] ? π : -π;
      φ = direction * s / 2;
      listener.point(-s, φ);
      listener.point(0, φ);
      listener.point(s, φ);
    } else {
      listener.point(to[0], to[1]);
    }
  }
  function d3_geo_pointInPolygon(point, polygon) {
    var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0;
    d3_geo_areaRingSum.reset();
    for (var i = 0, n = polygon.length; i < n; ++i) {
      var ring = polygon[i], m = ring.length;
      if (!m) continue;
      var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1;
      while (true) {
        if (j === m) j = 0;
        point = ring[j];
        var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ;
        d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
        polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
        if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
          var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
          d3_geo_cartesianNormalize(arc);
          var intersection = d3_geo_cartesianCross(meridianNormal, arc);
          d3_geo_cartesianNormalize(intersection);
          var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
          if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
            winding += antimeridian ^ dλ >= 0 ? 1 : -1;
          }
        }
        if (!j++) break;
        λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
      }
    }
    return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1;
  }
  function d3_geo_clipCircle(radius) {
    var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
    return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]);
    function visible(λ, φ) {
      return Math.cos(λ) * Math.cos(φ) > cr;
    }
    function clipLine(listener) {
      var point0, c0, v0, v00, clean;
      return {
        lineStart: function() {
          v00 = v0 = false;
          clean = 1;
        },
        point: function(λ, φ) {
          var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
          if (!point0 && (v00 = v0 = v)) listener.lineStart();
          if (v !== v0) {
            point2 = intersect(point0, point1);
            if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
              point1[0] += ε;
              point1[1] += ε;
              v = visible(point1[0], point1[1]);
            }
          }
          if (v !== v0) {
            clean = 0;
            if (v) {
              listener.lineStart();
              point2 = intersect(point1, point0);
              listener.point(point2[0], point2[1]);
            } else {
              point2 = intersect(point0, point1);
              listener.point(point2[0], point2[1]);
              listener.lineEnd();
            }
            point0 = point2;
          } else if (notHemisphere && point0 && smallRadius ^ v) {
            var t;
            if (!(c & c0) && (t = intersect(point1, point0, true))) {
              clean = 0;
              if (smallRadius) {
                listener.lineStart();
                listener.point(t[0][0], t[0][1]);
                listener.point(t[1][0], t[1][1]);
                listener.lineEnd();
              } else {
                listener.point(t[1][0], t[1][1]);
                listener.lineEnd();
                listener.lineStart();
                listener.point(t[0][0], t[0][1]);
              }
            }
          }
          if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
            listener.point(point1[0], point1[1]);
          }
          point0 = point1, v0 = v, c0 = c;
        },
        lineEnd: function() {
          if (v0) listener.lineEnd();
          point0 = null;
        },
        clean: function() {
          return clean | (v00 && v0) << 1;
        }
      };
    }
    function intersect(a, b, two) {
      var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
      var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
      if (!determinant) return !two && a;
      var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
      d3_geo_cartesianAdd(A, B);
      var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
      if (t2 < 0) return;
      var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
      d3_geo_cartesianAdd(q, A);
      q = d3_geo_spherical(q);
      if (!two) return q;
      var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z;
      if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
      var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε;
      if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
      if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
        var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
        d3_geo_cartesianAdd(q1, A);
        return [ q, d3_geo_spherical(q1) ];
      }
    }
    function code(λ, φ) {
      var r = smallRadius ? radius : π - radius, code = 0;
      if (λ < -r) code |= 1; else if (λ > r) code |= 2;
      if (φ < -r) code |= 4; else if (φ > r) code |= 8;
      return code;
    }
  }
  function d3_geom_clipLine(x0, y0, x1, y1) {
    return function(line) {
      var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r;
      r = x0 - ax;
      if (!dx && r > 0) return;
      r /= dx;
      if (dx < 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      } else if (dx > 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      }
      r = x1 - ax;
      if (!dx && r < 0) return;
      r /= dx;
      if (dx < 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      } else if (dx > 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      }
      r = y0 - ay;
      if (!dy && r > 0) return;
      r /= dy;
      if (dy < 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      } else if (dy > 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      }
      r = y1 - ay;
      if (!dy && r < 0) return;
      r /= dy;
      if (dy < 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      } else if (dy > 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      }
      if (t0 > 0) line.a = {
        x: ax + t0 * dx,
        y: ay + t0 * dy
      };
      if (t1 < 1) line.b = {
        x: ax + t1 * dx,
        y: ay + t1 * dy
      };
      return line;
    };
  }
  var d3_geo_clipExtentMAX = 1e9;
  d3.geo.clipExtent = function() {
    var x0, y0, x1, y1, stream, clip, clipExtent = {
      stream: function(output) {
        if (stream) stream.valid = false;
        stream = clip(output);
        stream.valid = true;
        return stream;
      },
      extent: function(_) {
        if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
        clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
        if (stream) stream.valid = false, stream = null;
        return clipExtent;
      }
    };
    return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]);
  };
  function d3_geo_clipExtent(x0, y0, x1, y1) {
    return function(listener) {
      var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring;
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          listener = bufferListener;
          segments = [];
          polygon = [];
          clean = true;
        },
        polygonEnd: function() {
          listener = listener_;
          segments = d3.merge(segments);
          var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
          if (inside || visible) {
            listener.polygonStart();
            if (inside) {
              listener.lineStart();
              interpolate(null, null, 1, listener);
              listener.lineEnd();
            }
            if (visible) {
              d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
            }
            listener.polygonEnd();
          }
          segments = polygon = ring = null;
        }
      };
      function insidePolygon(p) {
        var wn = 0, n = polygon.length, y = p[1];
        for (var i = 0; i < n; ++i) {
          for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
            b = v[j];
            if (a[1] <= y) {
              if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
            } else {
              if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
            }
            a = b;
          }
        }
        return wn !== 0;
      }
      function interpolate(from, to, direction, listener) {
        var a = 0, a1 = 0;
        if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
          do {
            listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
          } while ((a = (a + direction + 4) % 4) !== a1);
        } else {
          listener.point(to[0], to[1]);
        }
      }
      function pointVisible(x, y) {
        return x0 <= x && x <= x1 && y0 <= y && y <= y1;
      }
      function point(x, y) {
        if (pointVisible(x, y)) listener.point(x, y);
      }
      var x__, y__, v__, x_, y_, v_, first, clean;
      function lineStart() {
        clip.point = linePoint;
        if (polygon) polygon.push(ring = []);
        first = true;
        v_ = false;
        x_ = y_ = NaN;
      }
      function lineEnd() {
        if (segments) {
          linePoint(x__, y__);
          if (v__ && v_) bufferListener.rejoin();
          segments.push(bufferListener.buffer());
        }
        clip.point = point;
        if (v_) listener.lineEnd();
      }
      function linePoint(x, y) {
        x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
        y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
        var v = pointVisible(x, y);
        if (polygon) ring.push([ x, y ]);
        if (first) {
          x__ = x, y__ = y, v__ = v;
          first = false;
          if (v) {
            listener.lineStart();
            listener.point(x, y);
          }
        } else {
          if (v && v_) listener.point(x, y); else {
            var l = {
              a: {
                x: x_,
                y: y_
              },
              b: {
                x: x,
                y: y
              }
            };
            if (clipLine(l)) {
              if (!v_) {
                listener.lineStart();
                listener.point(l.a.x, l.a.y);
              }
              listener.point(l.b.x, l.b.y);
              if (!v) listener.lineEnd();
              clean = false;
            } else if (v) {
              listener.lineStart();
              listener.point(x, y);
              clean = false;
            }
          }
        }
        x_ = x, y_ = y, v_ = v;
      }
      return clip;
    };
    function corner(p, direction) {
      return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
    }
    function compare(a, b) {
      return comparePoints(a.x, b.x);
    }
    function comparePoints(a, b) {
      var ca = corner(a, 1), cb = corner(b, 1);
      return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
    }
  }
  function d3_geo_conic(projectAt) {
    var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
    p.parallels = function(_) {
      if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ];
      return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
    };
    return p;
  }
  function d3_geo_conicEqualArea(φ0, φ1) {
    var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n;
    function forward(λ, φ) {
      var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
      return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = ρ0 - y;
      return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ];
    };
    return forward;
  }
  (d3.geo.conicEqualArea = function() {
    return d3_geo_conic(d3_geo_conicEqualArea);
  }).raw = d3_geo_conicEqualArea;
  d3.geo.albers = function() {
    return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
  };
  d3.geo.albersUsa = function() {
    var lower48 = d3.geo.albers();
    var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
    var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
    var point, pointStream = {
      point: function(x, y) {
        point = [ x, y ];
      }
    }, lower48Point, alaskaPoint, hawaiiPoint;
    function albersUsa(coordinates) {
      var x = coordinates[0], y = coordinates[1];
      point = null;
      (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
      return point;
    }
    albersUsa.invert = function(coordinates) {
      var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
      return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
    };
    albersUsa.stream = function(stream) {
      var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
      return {
        point: function(x, y) {
          lower48Stream.point(x, y);
          alaskaStream.point(x, y);
          hawaiiStream.point(x, y);
        },
        sphere: function() {
          lower48Stream.sphere();
          alaskaStream.sphere();
          hawaiiStream.sphere();
        },
        lineStart: function() {
          lower48Stream.lineStart();
          alaskaStream.lineStart();
          hawaiiStream.lineStart();
        },
        lineEnd: function() {
          lower48Stream.lineEnd();
          alaskaStream.lineEnd();
          hawaiiStream.lineEnd();
        },
        polygonStart: function() {
          lower48Stream.polygonStart();
          alaskaStream.polygonStart();
          hawaiiStream.polygonStart();
        },
        polygonEnd: function() {
          lower48Stream.polygonEnd();
          alaskaStream.polygonEnd();
          hawaiiStream.polygonEnd();
        }
      };
    };
    albersUsa.precision = function(_) {
      if (!arguments.length) return lower48.precision();
      lower48.precision(_);
      alaska.precision(_);
      hawaii.precision(_);
      return albersUsa;
    };
    albersUsa.scale = function(_) {
      if (!arguments.length) return lower48.scale();
      lower48.scale(_);
      alaska.scale(_ * .35);
      hawaii.scale(_);
      return albersUsa.translate(lower48.translate());
    };
    albersUsa.translate = function(_) {
      if (!arguments.length) return lower48.translate();
      var k = lower48.scale(), x = +_[0], y = +_[1];
      lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
      alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
      hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
      return albersUsa;
    };
    return albersUsa.scale(1070);
  };
  var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
    point: d3_noop,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: function() {
      d3_geo_pathAreaPolygon = 0;
      d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
    },
    polygonEnd: function() {
      d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
      d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
    }
  };
  function d3_geo_pathAreaRingStart() {
    var x00, y00, x0, y0;
    d3_geo_pathArea.point = function(x, y) {
      d3_geo_pathArea.point = nextPoint;
      x00 = x0 = x, y00 = y0 = y;
    };
    function nextPoint(x, y) {
      d3_geo_pathAreaPolygon += y0 * x - x0 * y;
      x0 = x, y0 = y;
    }
    d3_geo_pathArea.lineEnd = function() {
      nextPoint(x00, y00);
    };
  }
  var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
  var d3_geo_pathBounds = {
    point: d3_geo_pathBoundsPoint,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: d3_noop,
    polygonEnd: d3_noop
  };
  function d3_geo_pathBoundsPoint(x, y) {
    if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
    if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
    if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
    if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
  }
  function d3_geo_pathBuffer() {
    var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
    var stream = {
      point: point,
      lineStart: function() {
        stream.point = pointLineStart;
      },
      lineEnd: lineEnd,
      polygonStart: function() {
        stream.lineEnd = lineEndPolygon;
      },
      polygonEnd: function() {
        stream.lineEnd = lineEnd;
        stream.point = point;
      },
      pointRadius: function(_) {
        pointCircle = d3_geo_pathBufferCircle(_);
        return stream;
      },
      result: function() {
        if (buffer.length) {
          var result = buffer.join("");
          buffer = [];
          return result;
        }
      }
    };
    function point(x, y) {
      buffer.push("M", x, ",", y, pointCircle);
    }
    function pointLineStart(x, y) {
      buffer.push("M", x, ",", y);
      stream.point = pointLine;
    }
    function pointLine(x, y) {
      buffer.push("L", x, ",", y);
    }
    function lineEnd() {
      stream.point = point;
    }
    function lineEndPolygon() {
      buffer.push("Z");
    }
    return stream;
  }
  function d3_geo_pathBufferCircle(radius) {
    return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
  }
  var d3_geo_pathCentroid = {
    point: d3_geo_pathCentroidPoint,
    lineStart: d3_geo_pathCentroidLineStart,
    lineEnd: d3_geo_pathCentroidLineEnd,
    polygonStart: function() {
      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
    },
    polygonEnd: function() {
      d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
      d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
    }
  };
  function d3_geo_pathCentroidPoint(x, y) {
    d3_geo_centroidX0 += x;
    d3_geo_centroidY0 += y;
    ++d3_geo_centroidZ0;
  }
  function d3_geo_pathCentroidLineStart() {
    var x0, y0;
    d3_geo_pathCentroid.point = function(x, y) {
      d3_geo_pathCentroid.point = nextPoint;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    };
    function nextPoint(x, y) {
      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
      d3_geo_centroidX1 += z * (x0 + x) / 2;
      d3_geo_centroidY1 += z * (y0 + y) / 2;
      d3_geo_centroidZ1 += z;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    }
  }
  function d3_geo_pathCentroidLineEnd() {
    d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
  }
  function d3_geo_pathCentroidRingStart() {
    var x00, y00, x0, y0;
    d3_geo_pathCentroid.point = function(x, y) {
      d3_geo_pathCentroid.point = nextPoint;
      d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
    };
    function nextPoint(x, y) {
      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
      d3_geo_centroidX1 += z * (x0 + x) / 2;
      d3_geo_centroidY1 += z * (y0 + y) / 2;
      d3_geo_centroidZ1 += z;
      z = y0 * x - x0 * y;
      d3_geo_centroidX2 += z * (x0 + x);
      d3_geo_centroidY2 += z * (y0 + y);
      d3_geo_centroidZ2 += z * 3;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    }
    d3_geo_pathCentroid.lineEnd = function() {
      nextPoint(x00, y00);
    };
  }
  function d3_geo_pathContext(context) {
    var pointRadius = 4.5;
    var stream = {
      point: point,
      lineStart: function() {
        stream.point = pointLineStart;
      },
      lineEnd: lineEnd,
      polygonStart: function() {
        stream.lineEnd = lineEndPolygon;
      },
      polygonEnd: function() {
        stream.lineEnd = lineEnd;
        stream.point = point;
      },
      pointRadius: function(_) {
        pointRadius = _;
        return stream;
      },
      result: d3_noop
    };
    function point(x, y) {
      context.moveTo(x + pointRadius, y);
      context.arc(x, y, pointRadius, 0, τ);
    }
    function pointLineStart(x, y) {
      context.moveTo(x, y);
      stream.point = pointLine;
    }
    function pointLine(x, y) {
      context.lineTo(x, y);
    }
    function lineEnd() {
      stream.point = point;
    }
    function lineEndPolygon() {
      context.closePath();
    }
    return stream;
  }
  function d3_geo_resample(project) {
    var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
    function resample(stream) {
      return (maxDepth ? resampleRecursive : resampleNone)(stream);
    }
    function resampleNone(stream) {
      return d3_geo_transformPoint(stream, function(x, y) {
        x = project(x, y);
        stream.point(x[0], x[1]);
      });
    }
    function resampleRecursive(stream) {
      var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
      var resample = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          stream.polygonStart();
          resample.lineStart = ringStart;
        },
        polygonEnd: function() {
          stream.polygonEnd();
          resample.lineStart = lineStart;
        }
      };
      function point(x, y) {
        x = project(x, y);
        stream.point(x[0], x[1]);
      }
      function lineStart() {
        x0 = NaN;
        resample.point = linePoint;
        stream.lineStart();
      }
      function linePoint(λ, φ) {
        var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ);
        resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
        stream.point(x0, y0);
      }
      function lineEnd() {
        resample.point = point;
        stream.lineEnd();
      }
      function ringStart() {
        lineStart();
        resample.point = ringPoint;
        resample.lineEnd = ringEnd;
      }
      function ringPoint(λ, φ) {
        linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
        resample.point = linePoint;
      }
      function ringEnd() {
        resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
        resample.lineEnd = lineEnd;
        lineEnd();
      }
      return resample;
    }
    function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
      var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
      if (d2 > 4 * δ2 && depth--) {
        var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
        if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
          resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
          stream.point(x2, y2);
          resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
        }
      }
    }
    resample.precision = function(_) {
      if (!arguments.length) return Math.sqrt(δ2);
      maxDepth = (δ2 = _ * _) > 0 && 16;
      return resample;
    };
    return resample;
  }
  d3.geo.path = function() {
    var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
    function path(object) {
      if (object) {
        if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
        if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
        d3.geo.stream(object, cacheStream);
      }
      return contextStream.result();
    }
    path.area = function(object) {
      d3_geo_pathAreaSum = 0;
      d3.geo.stream(object, projectStream(d3_geo_pathArea));
      return d3_geo_pathAreaSum;
    };
    path.centroid = function(object) {
      d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
      d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
      return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
    };
    path.bounds = function(object) {
      d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
      d3.geo.stream(object, projectStream(d3_geo_pathBounds));
      return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
    };
    path.projection = function(_) {
      if (!arguments.length) return projection;
      projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
      return reset();
    };
    path.context = function(_) {
      if (!arguments.length) return context;
      contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
      if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
      return reset();
    };
    path.pointRadius = function(_) {
      if (!arguments.length) return pointRadius;
      pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
      return path;
    };
    function reset() {
      cacheStream = null;
      return path;
    }
    return path.projection(d3.geo.albersUsa()).context(null);
  };
  function d3_geo_pathProjectStream(project) {
    var resample = d3_geo_resample(function(x, y) {
      return project([ x * d3_degrees, y * d3_degrees ]);
    });
    return function(stream) {
      return d3_geo_projectionRadians(resample(stream));
    };
  }
  d3.geo.transform = function(methods) {
    return {
      stream: function(stream) {
        var transform = new d3_geo_transform(stream);
        for (var k in methods) transform[k] = methods[k];
        return transform;
      }
    };
  };
  function d3_geo_transform(stream) {
    this.stream = stream;
  }
  d3_geo_transform.prototype = {
    point: function(x, y) {
      this.stream.point(x, y);
    },
    sphere: function() {
      this.stream.sphere();
    },
    lineStart: function() {
      this.stream.lineStart();
    },
    lineEnd: function() {
      this.stream.lineEnd();
    },
    polygonStart: function() {
      this.stream.polygonStart();
    },
    polygonEnd: function() {
      this.stream.polygonEnd();
    }
  };
  function d3_geo_transformPoint(stream, point) {
    return {
      point: point,
      sphere: function() {
        stream.sphere();
      },
      lineStart: function() {
        stream.lineStart();
      },
      lineEnd: function() {
        stream.lineEnd();
      },
      polygonStart: function() {
        stream.polygonStart();
      },
      polygonEnd: function() {
        stream.polygonEnd();
      }
    };
  }
  d3.geo.projection = d3_geo_projection;
  d3.geo.projectionMutator = d3_geo_projectionMutator;
  function d3_geo_projection(project) {
    return d3_geo_projectionMutator(function() {
      return project;
    })();
  }
  function d3_geo_projectionMutator(projectAt) {
    var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
      x = project(x, y);
      return [ x[0] * k + δx, δy - x[1] * k ];
    }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
    function projection(point) {
      point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
      return [ point[0] * k + δx, δy - point[1] * k ];
    }
    function invert(point) {
      point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
      return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
    }
    projection.stream = function(output) {
      if (stream) stream.valid = false;
      stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
      stream.valid = true;
      return stream;
    };
    projection.clipAngle = function(_) {
      if (!arguments.length) return clipAngle;
      preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
      return invalidate();
    };
    projection.clipExtent = function(_) {
      if (!arguments.length) return clipExtent;
      clipExtent = _;
      postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
      return invalidate();
    };
    projection.scale = function(_) {
      if (!arguments.length) return k;
      k = +_;
      return reset();
    };
    projection.translate = function(_) {
      if (!arguments.length) return [ x, y ];
      x = +_[0];
      y = +_[1];
      return reset();
    };
    projection.center = function(_) {
      if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ];
      λ = _[0] % 360 * d3_radians;
      φ = _[1] % 360 * d3_radians;
      return reset();
    };
    projection.rotate = function(_) {
      if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ];
      δλ = _[0] % 360 * d3_radians;
      δφ = _[1] % 360 * d3_radians;
      δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
      return reset();
    };
    d3.rebind(projection, projectResample, "precision");
    function reset() {
      projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
      var center = project(λ, φ);
      δx = x - center[0] * k;
      δy = y + center[1] * k;
      return invalidate();
    }
    function invalidate() {
      if (stream) stream.valid = false, stream = null;
      return projection;
    }
    return function() {
      project = projectAt.apply(this, arguments);
      projection.invert = project.invert && invert;
      return reset();
    };
  }
  function d3_geo_projectionRadians(stream) {
    return d3_geo_transformPoint(stream, function(x, y) {
      stream.point(x * d3_radians, y * d3_radians);
    });
  }
  function d3_geo_equirectangular(λ, φ) {
    return [ λ, φ ];
  }
  (d3.geo.equirectangular = function() {
    return d3_geo_projection(d3_geo_equirectangular);
  }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
  d3.geo.rotation = function(rotate) {
    rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
    function forward(coordinates) {
      coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
    }
    forward.invert = function(coordinates) {
      coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
    };
    return forward;
  };
  function d3_geo_identityRotation(λ, φ) {
    return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
  }
  d3_geo_identityRotation.invert = d3_geo_equirectangular;
  function d3_geo_rotation(δλ, δφ, δγ) {
    return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
  }
  function d3_geo_forwardRotationλ(δλ) {
    return function(λ, φ) {
      return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
    };
  }
  function d3_geo_rotationλ(δλ) {
    var rotation = d3_geo_forwardRotationλ(δλ);
    rotation.invert = d3_geo_forwardRotationλ(-δλ);
    return rotation;
  }
  function d3_geo_rotationφγ(δφ, δγ) {
    var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ);
    function rotation(λ, φ) {
      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ;
      return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ];
    }
    rotation.invert = function(λ, φ) {
      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ;
      return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ];
    };
    return rotation;
  }
  d3.geo.circle = function() {
    var origin = [ 0, 0 ], angle, precision = 6, interpolate;
    function circle() {
      var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
      interpolate(null, null, 1, {
        point: function(x, y) {
          ring.push(x = rotate(x, y));
          x[0] *= d3_degrees, x[1] *= d3_degrees;
        }
      });
      return {
        type: "Polygon",
        coordinates: [ ring ]
      };
    }
    circle.origin = function(x) {
      if (!arguments.length) return origin;
      origin = x;
      return circle;
    };
    circle.angle = function(x) {
      if (!arguments.length) return angle;
      interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
      return circle;
    };
    circle.precision = function(_) {
      if (!arguments.length) return precision;
      interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
      return circle;
    };
    return circle.angle(90);
  };
  function d3_geo_circleInterpolate(radius, precision) {
    var cr = Math.cos(radius), sr = Math.sin(radius);
    return function(from, to, direction, listener) {
      var step = direction * precision;
      if (from != null) {
        from = d3_geo_circleAngle(cr, from);
        to = d3_geo_circleAngle(cr, to);
        if (direction > 0 ? from < to : from > to) from += direction * τ;
      } else {
        from = radius + direction * τ;
        to = radius - .5 * step;
      }
      for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
        listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
      }
    };
  }
  function d3_geo_circleAngle(cr, point) {
    var a = d3_geo_cartesian(point);
    a[0] -= cr;
    d3_geo_cartesianNormalize(a);
    var angle = d3_acos(-a[1]);
    return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
  }
  d3.geo.distance = function(a, b) {
    var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t;
    return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
  };
  d3.geo.graticule = function() {
    var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
    function graticule() {
      return {
        type: "MultiLineString",
        coordinates: lines()
      };
    }
    function lines() {
      return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
        return abs(x % DX) > ε;
      }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
        return abs(y % DY) > ε;
      }).map(y));
    }
    graticule.lines = function() {
      return lines().map(function(coordinates) {
        return {
          type: "LineString",
          coordinates: coordinates
        };
      });
    };
    graticule.outline = function() {
      return {
        type: "Polygon",
        coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
      };
    };
    graticule.extent = function(_) {
      if (!arguments.length) return graticule.minorExtent();
      return graticule.majorExtent(_).minorExtent(_);
    };
    graticule.majorExtent = function(_) {
      if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
      X0 = +_[0][0], X1 = +_[1][0];
      Y0 = +_[0][1], Y1 = +_[1][1];
      if (X0 > X1) _ = X0, X0 = X1, X1 = _;
      if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
      return graticule.precision(precision);
    };
    graticule.minorExtent = function(_) {
      if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
      x0 = +_[0][0], x1 = +_[1][0];
      y0 = +_[0][1], y1 = +_[1][1];
      if (x0 > x1) _ = x0, x0 = x1, x1 = _;
      if (y0 > y1) _ = y0, y0 = y1, y1 = _;
      return graticule.precision(precision);
    };
    graticule.step = function(_) {
      if (!arguments.length) return graticule.minorStep();
      return graticule.majorStep(_).minorStep(_);
    };
    graticule.majorStep = function(_) {
      if (!arguments.length) return [ DX, DY ];
      DX = +_[0], DY = +_[1];
      return graticule;
    };
    graticule.minorStep = function(_) {
      if (!arguments.length) return [ dx, dy ];
      dx = +_[0], dy = +_[1];
      return graticule;
    };
    graticule.precision = function(_) {
      if (!arguments.length) return precision;
      precision = +_;
      x = d3_geo_graticuleX(y0, y1, 90);
      y = d3_geo_graticuleY(x0, x1, precision);
      X = d3_geo_graticuleX(Y0, Y1, 90);
      Y = d3_geo_graticuleY(X0, X1, precision);
      return graticule;
    };
    return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]);
  };
  function d3_geo_graticuleX(y0, y1, dy) {
    var y = d3.range(y0, y1 - ε, dy).concat(y1);
    return function(x) {
      return y.map(function(y) {
        return [ x, y ];
      });
    };
  }
  function d3_geo_graticuleY(x0, x1, dx) {
    var x = d3.range(x0, x1 - ε, dx).concat(x1);
    return function(y) {
      return x.map(function(x) {
        return [ x, y ];
      });
    };
  }
  function d3_source(d) {
    return d.source;
  }
  function d3_target(d) {
    return d.target;
  }
  d3.geo.greatArc = function() {
    var source = d3_source, source_, target = d3_target, target_;
    function greatArc() {
      return {
        type: "LineString",
        coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
      };
    }
    greatArc.distance = function() {
      return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
    };
    greatArc.source = function(_) {
      if (!arguments.length) return source;
      source = _, source_ = typeof _ === "function" ? null : _;
      return greatArc;
    };
    greatArc.target = function(_) {
      if (!arguments.length) return target;
      target = _, target_ = typeof _ === "function" ? null : _;
      return greatArc;
    };
    greatArc.precision = function() {
      return arguments.length ? greatArc : 0;
    };
    return greatArc;
  };
  d3.geo.interpolate = function(source, target) {
    return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
  };
  function d3_geo_interpolate(x0, y0, x1, y1) {
    var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
    var interpolate = d ? function(t) {
      var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
      return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
    } : function() {
      return [ x0 * d3_degrees, y0 * d3_degrees ];
    };
    interpolate.distance = d;
    return interpolate;
  }
  d3.geo.length = function(object) {
    d3_geo_lengthSum = 0;
    d3.geo.stream(object, d3_geo_length);
    return d3_geo_lengthSum;
  };
  var d3_geo_lengthSum;
  var d3_geo_length = {
    sphere: d3_noop,
    point: d3_noop,
    lineStart: d3_geo_lengthLineStart,
    lineEnd: d3_noop,
    polygonStart: d3_noop,
    polygonEnd: d3_noop
  };
  function d3_geo_lengthLineStart() {
    var λ0, sinφ0, cosφ0;
    d3_geo_length.point = function(λ, φ) {
      λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
      d3_geo_length.point = nextPoint;
    };
    d3_geo_length.lineEnd = function() {
      d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
    };
    function nextPoint(λ, φ) {
      var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t);
      d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
      λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
    }
  }
  function d3_geo_azimuthal(scale, angle) {
    function azimuthal(λ, φ) {
      var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ);
      return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ];
    }
    azimuthal.invert = function(x, y) {
      var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c);
      return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ];
    };
    return azimuthal;
  }
  var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
    return Math.sqrt(2 / (1 + cosλcosφ));
  }, function(ρ) {
    return 2 * Math.asin(ρ / 2);
  });
  (d3.geo.azimuthalEqualArea = function() {
    return d3_geo_projection(d3_geo_azimuthalEqualArea);
  }).raw = d3_geo_azimuthalEqualArea;
  var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
    var c = Math.acos(cosλcosφ);
    return c && c / Math.sin(c);
  }, d3_identity);
  (d3.geo.azimuthalEquidistant = function() {
    return d3_geo_projection(d3_geo_azimuthalEquidistant);
  }).raw = d3_geo_azimuthalEquidistant;
  function d3_geo_conicConformal(φ0, φ1) {
    var cosφ0 = Math.cos(φ0), t = function(φ) {
      return Math.tan(π / 4 + φ / 2);
    }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n;
    if (!n) return d3_geo_mercator;
    function forward(λ, φ) {
      if (F > 0) {
        if (φ < -halfπ + ε) φ = -halfπ + ε;
      } else {
        if (φ > halfπ - ε) φ = halfπ - ε;
      }
      var ρ = F / Math.pow(t(φ), n);
      return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
      return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ];
    };
    return forward;
  }
  (d3.geo.conicConformal = function() {
    return d3_geo_conic(d3_geo_conicConformal);
  }).raw = d3_geo_conicConformal;
  function d3_geo_conicEquidistant(φ0, φ1) {
    var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0;
    if (abs(n) < ε) return d3_geo_equirectangular;
    function forward(λ, φ) {
      var ρ = G - φ;
      return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = G - y;
      return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ];
    };
    return forward;
  }
  (d3.geo.conicEquidistant = function() {
    return d3_geo_conic(d3_geo_conicEquidistant);
  }).raw = d3_geo_conicEquidistant;
  var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
    return 1 / cosλcosφ;
  }, Math.atan);
  (d3.geo.gnomonic = function() {
    return d3_geo_projection(d3_geo_gnomonic);
  }).raw = d3_geo_gnomonic;
  function d3_geo_mercator(λ, φ) {
    return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ];
  }
  d3_geo_mercator.invert = function(x, y) {
    return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ];
  };
  function d3_geo_mercatorProjection(project) {
    var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
    m.scale = function() {
      var v = scale.apply(m, arguments);
      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
    };
    m.translate = function() {
      var v = translate.apply(m, arguments);
      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
    };
    m.clipExtent = function(_) {
      var v = clipExtent.apply(m, arguments);
      if (v === m) {
        if (clipAuto = _ == null) {
          var k = π * scale(), t = translate();
          clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
        }
      } else if (clipAuto) {
        v = null;
      }
      return v;
    };
    return m.clipExtent(null);
  }
  (d3.geo.mercator = function() {
    return d3_geo_mercatorProjection(d3_geo_mercator);
  }).raw = d3_geo_mercator;
  var d3_geo_orthographic = d3_geo_azimuthal(function() {
    return 1;
  }, Math.asin);
  (d3.geo.orthographic = function() {
    return d3_geo_projection(d3_geo_orthographic);
  }).raw = d3_geo_orthographic;
  var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
    return 1 / (1 + cosλcosφ);
  }, function(ρ) {
    return 2 * Math.atan(ρ);
  });
  (d3.geo.stereographic = function() {
    return d3_geo_projection(d3_geo_stereographic);
  }).raw = d3_geo_stereographic;
  function d3_geo_transverseMercator(λ, φ) {
    return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ];
  }
  d3_geo_transverseMercator.invert = function(x, y) {
    return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ];
  };
  (d3.geo.transverseMercator = function() {
    var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate;
    projection.center = function(_) {
      return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]);
    };
    projection.rotate = function(_) {
      return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), 
      [ _[0], _[1], _[2] - 90 ]);
    };
    return rotate([ 0, 0, 90 ]);
  }).raw = d3_geo_transverseMercator;
  d3.geom = {};
  function d3_geom_pointX(d) {
    return d[0];
  }
  function d3_geom_pointY(d) {
    return d[1];
  }
  d3.geom.hull = function(vertices) {
    var x = d3_geom_pointX, y = d3_geom_pointY;
    if (arguments.length) return hull(vertices);
    function hull(data) {
      if (data.length < 3) return [];
      var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = [];
      for (i = 0; i < n; i++) {
        points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]);
      }
      points.sort(d3_geom_hullOrder);
      for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]);
      var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints);
      var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = [];
      for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
      for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
      return polygon;
    }
    hull.x = function(_) {
      return arguments.length ? (x = _, hull) : x;
    };
    hull.y = function(_) {
      return arguments.length ? (y = _, hull) : y;
    };
    return hull;
  };
  function d3_geom_hullUpper(points) {
    var n = points.length, hull = [ 0, 1 ], hs = 2;
    for (var i = 2; i < n; i++) {
      while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
      hull[hs++] = i;
    }
    return hull.slice(0, hs);
  }
  function d3_geom_hullOrder(a, b) {
    return a[0] - b[0] || a[1] - b[1];
  }
  d3.geom.polygon = function(coordinates) {
    d3_subclass(coordinates, d3_geom_polygonPrototype);
    return coordinates;
  };
  var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
  d3_geom_polygonPrototype.area = function() {
    var i = -1, n = this.length, a, b = this[n - 1], area = 0;
    while (++i < n) {
      a = b;
      b = this[i];
      area += a[1] * b[0] - a[0] * b[1];
    }
    return area * .5;
  };
  d3_geom_polygonPrototype.centroid = function(k) {
    var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
    if (!arguments.length) k = -1 / (6 * this.area());
    while (++i < n) {
      a = b;
      b = this[i];
      c = a[0] * b[1] - b[0] * a[1];
      x += (a[0] + b[0]) * c;
      y += (a[1] + b[1]) * c;
    }
    return [ x * k, y * k ];
  };
  d3_geom_polygonPrototype.clip = function(subject) {
    var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
    while (++i < n) {
      input = subject.slice();
      subject.length = 0;
      b = this[i];
      c = input[(m = input.length - closed) - 1];
      j = -1;
      while (++j < m) {
        d = input[j];
        if (d3_geom_polygonInside(d, a, b)) {
          if (!d3_geom_polygonInside(c, a, b)) {
            subject.push(d3_geom_polygonIntersect(c, d, a, b));
          }
          subject.push(d);
        } else if (d3_geom_polygonInside(c, a, b)) {
          subject.push(d3_geom_polygonIntersect(c, d, a, b));
        }
        c = d;
      }
      if (closed) subject.push(subject[0]);
      a = b;
    }
    return subject;
  };
  function d3_geom_polygonInside(p, a, b) {
    return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
  }
  function d3_geom_polygonIntersect(c, d, a, b) {
    var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
    return [ x1 + ua * x21, y1 + ua * y21 ];
  }
  function d3_geom_polygonClosed(coordinates) {
    var a = coordinates[0], b = coordinates[coordinates.length - 1];
    return !(a[0] - b[0] || a[1] - b[1]);
  }
  var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];
  function d3_geom_voronoiBeach() {
    d3_geom_voronoiRedBlackNode(this);
    this.edge = this.site = this.circle = null;
  }
  function d3_geom_voronoiCreateBeach(site) {
    var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
    beach.site = site;
    return beach;
  }
  function d3_geom_voronoiDetachBeach(beach) {
    d3_geom_voronoiDetachCircle(beach);
    d3_geom_voronoiBeaches.remove(beach);
    d3_geom_voronoiBeachPool.push(beach);
    d3_geom_voronoiRedBlackNode(beach);
  }
  function d3_geom_voronoiRemoveBeach(beach) {
    var circle = beach.circle, x = circle.x, y = circle.cy, vertex = {
      x: x,
      y: y
    }, previous = beach.P, next = beach.N, disappearing = [ beach ];
    d3_geom_voronoiDetachBeach(beach);
    var lArc = previous;
    while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
      previous = lArc.P;
      disappearing.unshift(lArc);
      d3_geom_voronoiDetachBeach(lArc);
      lArc = previous;
    }
    disappearing.unshift(lArc);
    d3_geom_voronoiDetachCircle(lArc);
    var rArc = next;
    while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
      next = rArc.N;
      disappearing.push(rArc);
      d3_geom_voronoiDetachBeach(rArc);
      rArc = next;
    }
    disappearing.push(rArc);
    d3_geom_voronoiDetachCircle(rArc);
    var nArcs = disappearing.length, iArc;
    for (iArc = 1; iArc < nArcs; ++iArc) {
      rArc = disappearing[iArc];
      lArc = disappearing[iArc - 1];
      d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
    }
    lArc = disappearing[0];
    rArc = disappearing[nArcs - 1];
    rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
    d3_geom_voronoiAttachCircle(lArc);
    d3_geom_voronoiAttachCircle(rArc);
  }
  function d3_geom_voronoiAddBeach(site) {
    var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
    while (node) {
      dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
      if (dxl > ε) node = node.L; else {
        dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
        if (dxr > ε) {
          if (!node.R) {
            lArc = node;
            break;
          }
          node = node.R;
        } else {
          if (dxl > -ε) {
            lArc = node.P;
            rArc = node;
          } else if (dxr > -ε) {
            lArc = node;
            rArc = node.N;
          } else {
            lArc = rArc = node;
          }
          break;
        }
      }
    }
    var newArc = d3_geom_voronoiCreateBeach(site);
    d3_geom_voronoiBeaches.insert(lArc, newArc);
    if (!lArc && !rArc) return;
    if (lArc === rArc) {
      d3_geom_voronoiDetachCircle(lArc);
      rArc = d3_geom_voronoiCreateBeach(lArc.site);
      d3_geom_voronoiBeaches.insert(newArc, rArc);
      newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
      d3_geom_voronoiAttachCircle(lArc);
      d3_geom_voronoiAttachCircle(rArc);
      return;
    }
    if (!rArc) {
      newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
      return;
    }
    d3_geom_voronoiDetachCircle(lArc);
    d3_geom_voronoiDetachCircle(rArc);
    var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = {
      x: (cy * hb - by * hc) / d + ax,
      y: (bx * hc - cx * hb) / d + ay
    };
    d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
    newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
    rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
    d3_geom_voronoiAttachCircle(lArc);
    d3_geom_voronoiAttachCircle(rArc);
  }
  function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
    var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix;
    if (!pby2) return rfocx;
    var lArc = arc.P;
    if (!lArc) return -Infinity;
    site = lArc.site;
    var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix;
    if (!plby2) return lfocx;
    var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2;
    if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
    return (rfocx + lfocx) / 2;
  }
  function d3_geom_voronoiRightBreakPoint(arc, directrix) {
    var rArc = arc.N;
    if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
    var site = arc.site;
    return site.y === directrix ? site.x : Infinity;
  }
  function d3_geom_voronoiCell(site) {
    this.site = site;
    this.edges = [];
  }
  d3_geom_voronoiCell.prototype.prepare = function() {
    var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge;
    while (iHalfEdge--) {
      edge = halfEdges[iHalfEdge].edge;
      if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
    }
    halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
    return halfEdges.length;
  };
  function d3_geom_voronoiCloseCells(extent) {
    var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
    while (iCell--) {
      cell = cells[iCell];
      if (!cell || !cell.prepare()) continue;
      halfEdges = cell.edges;
      nHalfEdges = halfEdges.length;
      iHalfEdge = 0;
      while (iHalfEdge < nHalfEdges) {
        end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
        start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
        if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
          halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
            x: x0,
            y: abs(x2 - x0) < ε ? y2 : y1
          } : abs(y3 - y1) < ε && x1 - x3 > ε ? {
            x: abs(y2 - y1) < ε ? x2 : x1,
            y: y1
          } : abs(x3 - x1) < ε && y3 - y0 > ε ? {
            x: x1,
            y: abs(x2 - x1) < ε ? y2 : y0
          } : abs(y3 - y0) < ε && x3 - x0 > ε ? {
            x: abs(y2 - y0) < ε ? x2 : x0,
            y: y0
          } : null), cell.site, null));
          ++nHalfEdges;
        }
      }
    }
  }
  function d3_geom_voronoiHalfEdgeOrder(a, b) {
    return b.angle - a.angle;
  }
  function d3_geom_voronoiCircle() {
    d3_geom_voronoiRedBlackNode(this);
    this.x = this.y = this.arc = this.site = this.cy = null;
  }
  function d3_geom_voronoiAttachCircle(arc) {
    var lArc = arc.P, rArc = arc.N;
    if (!lArc || !rArc) return;
    var lSite = lArc.site, cSite = arc.site, rSite = rArc.site;
    if (lSite === rSite) return;
    var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by;
    var d = 2 * (ax * cy - ay * cx);
    if (d >= -ε2) return;
    var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by;
    var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
    circle.arc = arc;
    circle.site = cSite;
    circle.x = x + bx;
    circle.y = cy + Math.sqrt(x * x + y * y);
    circle.cy = cy;
    arc.circle = circle;
    var before = null, node = d3_geom_voronoiCircles._;
    while (node) {
      if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
        if (node.L) node = node.L; else {
          before = node.P;
          break;
        }
      } else {
        if (node.R) node = node.R; else {
          before = node;
          break;
        }
      }
    }
    d3_geom_voronoiCircles.insert(before, circle);
    if (!before) d3_geom_voronoiFirstCircle = circle;
  }
  function d3_geom_voronoiDetachCircle(arc) {
    var circle = arc.circle;
    if (circle) {
      if (!circle.P) d3_geom_voronoiFirstCircle = circle.N;
      d3_geom_voronoiCircles.remove(circle);
      d3_geom_voronoiCirclePool.push(circle);
      d3_geom_voronoiRedBlackNode(circle);
      arc.circle = null;
    }
  }
  function d3_geom_voronoiClipEdges(extent) {
    var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e;
    while (i--) {
      e = edges[i];
      if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
        e.a = e.b = null;
        edges.splice(i, 1);
      }
    }
  }
  function d3_geom_voronoiConnectEdge(edge, extent) {
    var vb = edge.b;
    if (vb) return true;
    var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb;
    if (ry === ly) {
      if (fx < x0 || fx >= x1) return;
      if (lx > rx) {
        if (!va) va = {
          x: fx,
          y: y0
        }; else if (va.y >= y1) return;
        vb = {
          x: fx,
          y: y1
        };
      } else {
        if (!va) va = {
          x: fx,
          y: y1
        }; else if (va.y < y0) return;
        vb = {
          x: fx,
          y: y0
        };
      }
    } else {
      fm = (lx - rx) / (ry - ly);
      fb = fy - fm * fx;
      if (fm < -1 || fm > 1) {
        if (lx > rx) {
          if (!va) va = {
            x: (y0 - fb) / fm,
            y: y0
          }; else if (va.y >= y1) return;
          vb = {
            x: (y1 - fb) / fm,
            y: y1
          };
        } else {
          if (!va) va = {
            x: (y1 - fb) / fm,
            y: y1
          }; else if (va.y < y0) return;
          vb = {
            x: (y0 - fb) / fm,
            y: y0
          };
        }
      } else {
        if (ly < ry) {
          if (!va) va = {
            x: x0,
            y: fm * x0 + fb
          }; else if (va.x >= x1) return;
          vb = {
            x: x1,
            y: fm * x1 + fb
          };
        } else {
          if (!va) va = {
            x: x1,
            y: fm * x1 + fb
          }; else if (va.x < x0) return;
          vb = {
            x: x0,
            y: fm * x0 + fb
          };
        }
      }
    }
    edge.a = va;
    edge.b = vb;
    return true;
  }
  function d3_geom_voronoiEdge(lSite, rSite) {
    this.l = lSite;
    this.r = rSite;
    this.a = this.b = null;
  }
  function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
    var edge = new d3_geom_voronoiEdge(lSite, rSite);
    d3_geom_voronoiEdges.push(edge);
    if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
    if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
    d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
    d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
    return edge;
  }
  function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
    var edge = new d3_geom_voronoiEdge(lSite, null);
    edge.a = va;
    edge.b = vb;
    d3_geom_voronoiEdges.push(edge);
    return edge;
  }
  function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
    if (!edge.a && !edge.b) {
      edge.a = vertex;
      edge.l = lSite;
      edge.r = rSite;
    } else if (edge.l === rSite) {
      edge.b = vertex;
    } else {
      edge.a = vertex;
    }
  }
  function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
    var va = edge.a, vb = edge.b;
    this.edge = edge;
    this.site = lSite;
    this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
  }
  d3_geom_voronoiHalfEdge.prototype = {
    start: function() {
      return this.edge.l === this.site ? this.edge.a : this.edge.b;
    },
    end: function() {
      return this.edge.l === this.site ? this.edge.b : this.edge.a;
    }
  };
  function d3_geom_voronoiRedBlackTree() {
    this._ = null;
  }
  function d3_geom_voronoiRedBlackNode(node) {
    node.U = node.C = node.L = node.R = node.P = node.N = null;
  }
  d3_geom_voronoiRedBlackTree.prototype = {
    insert: function(after, node) {
      var parent, grandpa, uncle;
      if (after) {
        node.P = after;
        node.N = after.N;
        if (after.N) after.N.P = node;
        after.N = node;
        if (after.R) {
          after = after.R;
          while (after.L) after = after.L;
          after.L = node;
        } else {
          after.R = node;
        }
        parent = after;
      } else if (this._) {
        after = d3_geom_voronoiRedBlackFirst(this._);
        node.P = null;
        node.N = after;
        after.P = after.L = node;
        parent = after;
      } else {
        node.P = node.N = null;
        this._ = node;
        parent = null;
      }
      node.L = node.R = null;
      node.U = parent;
      node.C = true;
      after = node;
      while (parent && parent.C) {
        grandpa = parent.U;
        if (parent === grandpa.L) {
          uncle = grandpa.R;
          if (uncle && uncle.C) {
            parent.C = uncle.C = false;
            grandpa.C = true;
            after = grandpa;
          } else {
            if (after === parent.R) {
              d3_geom_voronoiRedBlackRotateLeft(this, parent);
              after = parent;
              parent = after.U;
            }
            parent.C = false;
            grandpa.C = true;
            d3_geom_voronoiRedBlackRotateRight(this, grandpa);
          }
        } else {
          uncle = grandpa.L;
          if (uncle && uncle.C) {
            parent.C = uncle.C = false;
            grandpa.C = true;
            after = grandpa;
          } else {
            if (after === parent.L) {
              d3_geom_voronoiRedBlackRotateRight(this, parent);
              after = parent;
              parent = after.U;
            }
            parent.C = false;
            grandpa.C = true;
            d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
          }
        }
        parent = after.U;
      }
      this._.C = false;
    },
    remove: function(node) {
      if (node.N) node.N.P = node.P;
      if (node.P) node.P.N = node.N;
      node.N = node.P = null;
      var parent = node.U, sibling, left = node.L, right = node.R, next, red;
      if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right);
      if (parent) {
        if (parent.L === node) parent.L = next; else parent.R = next;
      } else {
        this._ = next;
      }
      if (left && right) {
        red = next.C;
        next.C = node.C;
        next.L = left;
        left.U = next;
        if (next !== right) {
          parent = next.U;
          next.U = node.U;
          node = next.R;
          parent.L = node;
          next.R = right;
          right.U = next;
        } else {
          next.U = parent;
          parent = next;
          node = next.R;
        }
      } else {
        red = node.C;
        node = next;
      }
      if (node) node.U = parent;
      if (red) return;
      if (node && node.C) {
        node.C = false;
        return;
      }
      do {
        if (node === this._) break;
        if (node === parent.L) {
          sibling = parent.R;
          if (sibling.C) {
            sibling.C = false;
            parent.C = true;
            d3_geom_voronoiRedBlackRotateLeft(this, parent);
            sibling = parent.R;
          }
          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
            if (!sibling.R || !sibling.R.C) {
              sibling.L.C = false;
              sibling.C = true;
              d3_geom_voronoiRedBlackRotateRight(this, sibling);
              sibling = parent.R;
            }
            sibling.C = parent.C;
            parent.C = sibling.R.C = false;
            d3_geom_voronoiRedBlackRotateLeft(this, parent);
            node = this._;
            break;
          }
        } else {
          sibling = parent.L;
          if (sibling.C) {
            sibling.C = false;
            parent.C = true;
            d3_geom_voronoiRedBlackRotateRight(this, parent);
            sibling = parent.L;
          }
          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
            if (!sibling.L || !sibling.L.C) {
              sibling.R.C = false;
              sibling.C = true;
              d3_geom_voronoiRedBlackRotateLeft(this, sibling);
              sibling = parent.L;
            }
            sibling.C = parent.C;
            parent.C = sibling.L.C = false;
            d3_geom_voronoiRedBlackRotateRight(this, parent);
            node = this._;
            break;
          }
        }
        sibling.C = true;
        node = parent;
        parent = parent.U;
      } while (!node.C);
      if (node) node.C = false;
    }
  };
  function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
    var p = node, q = node.R, parent = p.U;
    if (parent) {
      if (parent.L === p) parent.L = q; else parent.R = q;
    } else {
      tree._ = q;
    }
    q.U = parent;
    p.U = q;
    p.R = q.L;
    if (p.R) p.R.U = p;
    q.L = p;
  }
  function d3_geom_voronoiRedBlackRotateRight(tree, node) {
    var p = node, q = node.L, parent = p.U;
    if (parent) {
      if (parent.L === p) parent.L = q; else parent.R = q;
    } else {
      tree._ = q;
    }
    q.U = parent;
    p.U = q;
    p.L = q.R;
    if (p.L) p.L.U = p;
    q.R = p;
  }
  function d3_geom_voronoiRedBlackFirst(node) {
    while (node.L) node = node.L;
    return node;
  }
  function d3_geom_voronoi(sites, bbox) {
    var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle;
    d3_geom_voronoiEdges = [];
    d3_geom_voronoiCells = new Array(sites.length);
    d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
    d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
    while (true) {
      circle = d3_geom_voronoiFirstCircle;
      if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
        if (site.x !== x0 || site.y !== y0) {
          d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
          d3_geom_voronoiAddBeach(site);
          x0 = site.x, y0 = site.y;
        }
        site = sites.pop();
      } else if (circle) {
        d3_geom_voronoiRemoveBeach(circle.arc);
      } else {
        break;
      }
    }
    if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
    var diagram = {
      cells: d3_geom_voronoiCells,
      edges: d3_geom_voronoiEdges
    };
    d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
    return diagram;
  }
  function d3_geom_voronoiVertexOrder(a, b) {
    return b.y - a.y || b.x - a.x;
  }
  d3.geom.voronoi = function(points) {
    var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent;
    if (points) return voronoi(points);
    function voronoi(data) {
      var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1];
      d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
        var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) {
          var s = e.start();
          return [ s.x, s.y ];
        }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : [];
        polygon.point = data[i];
      });
      return polygons;
    }
    function sites(data) {
      return data.map(function(d, i) {
        return {
          x: Math.round(fx(d, i) / ε) * ε,
          y: Math.round(fy(d, i) / ε) * ε,
          i: i
        };
      });
    }
    voronoi.links = function(data) {
      return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
        return edge.l && edge.r;
      }).map(function(edge) {
        return {
          source: data[edge.l.i],
          target: data[edge.r.i]
        };
      });
    };
    voronoi.triangles = function(data) {
      var triangles = [];
      d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
        var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l;
        while (++j < m) {
          e0 = e1;
          s0 = s1;
          e1 = edges[j].edge;
          s1 = e1.l === site ? e1.r : e1.l;
          if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
            triangles.push([ data[i], data[s0.i], data[s1.i] ]);
          }
        }
      });
      return triangles;
    };
    voronoi.x = function(_) {
      return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
    };
    voronoi.y = function(_) {
      return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
    };
    voronoi.clipExtent = function(_) {
      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
      clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
      return voronoi;
    };
    voronoi.size = function(_) {
      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
      return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
    };
    return voronoi;
  };
  var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ];
  function d3_geom_voronoiTriangleArea(a, b, c) {
    return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
  }
  d3.geom.delaunay = function(vertices) {
    return d3.geom.voronoi().triangles(vertices);
  };
  d3.geom.quadtree = function(points, x1, y1, x2, y2) {
    var x = d3_geom_pointX, y = d3_geom_pointY, compat;
    if (compat = arguments.length) {
      x = d3_geom_quadtreeCompatX;
      y = d3_geom_quadtreeCompatY;
      if (compat === 3) {
        y2 = y1;
        x2 = x1;
        y1 = x1 = 0;
      }
      return quadtree(points);
    }
    function quadtree(data) {
      var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
      if (x1 != null) {
        x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
      } else {
        x2_ = y2_ = -(x1_ = y1_ = Infinity);
        xs = [], ys = [];
        n = data.length;
        if (compat) for (i = 0; i < n; ++i) {
          d = data[i];
          if (d.x < x1_) x1_ = d.x;
          if (d.y < y1_) y1_ = d.y;
          if (d.x > x2_) x2_ = d.x;
          if (d.y > y2_) y2_ = d.y;
          xs.push(d.x);
          ys.push(d.y);
        } else for (i = 0; i < n; ++i) {
          var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
          if (x_ < x1_) x1_ = x_;
          if (y_ < y1_) y1_ = y_;
          if (x_ > x2_) x2_ = x_;
          if (y_ > y2_) y2_ = y_;
          xs.push(x_);
          ys.push(y_);
        }
      }
      var dx = x2_ - x1_, dy = y2_ - y1_;
      if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
      function insert(n, d, x, y, x1, y1, x2, y2) {
        if (isNaN(x) || isNaN(y)) return;
        if (n.leaf) {
          var nx = n.x, ny = n.y;
          if (nx != null) {
            if (abs(nx - x) + abs(ny - y) < .01) {
              insertChild(n, d, x, y, x1, y1, x2, y2);
            } else {
              var nPoint = n.point;
              n.x = n.y = n.point = null;
              insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
              insertChild(n, d, x, y, x1, y1, x2, y2);
            }
          } else {
            n.x = x, n.y = y, n.point = d;
          }
        } else {
          insertChild(n, d, x, y, x1, y1, x2, y2);
        }
      }
      function insertChild(n, d, x, y, x1, y1, x2, y2) {
        var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right;
        n.leaf = false;
        n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
        if (right) x1 = xm; else x2 = xm;
        if (below) y1 = ym; else y2 = ym;
        insert(n, d, x, y, x1, y1, x2, y2);
      }
      var root = d3_geom_quadtreeNode();
      root.add = function(d) {
        insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
      };
      root.visit = function(f) {
        d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
      };
      root.find = function(point) {
        return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
      };
      i = -1;
      if (x1 == null) {
        while (++i < n) {
          insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
        }
        --i;
      } else data.forEach(root.add);
      xs = ys = data = d = null;
      return root;
    }
    quadtree.x = function(_) {
      return arguments.length ? (x = _, quadtree) : x;
    };
    quadtree.y = function(_) {
      return arguments.length ? (y = _, quadtree) : y;
    };
    quadtree.extent = function(_) {
      if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], 
      y2 = +_[1][1];
      return quadtree;
    };
    quadtree.size = function(_) {
      if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
      return quadtree;
    };
    return quadtree;
  };
  function d3_geom_quadtreeCompatX(d) {
    return d.x;
  }
  function d3_geom_quadtreeCompatY(d) {
    return d.y;
  }
  function d3_geom_quadtreeNode() {
    return {
      leaf: true,
      nodes: [],
      point: null,
      x: null,
      y: null
    };
  }
  function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
    if (!f(node, x1, y1, x2, y2)) {
      var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
      if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
      if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
      if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
      if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
    }
  }
  function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
    var minDistance2 = Infinity, closestPoint;
    (function find(node, x1, y1, x2, y2) {
      if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
      if (point = node.point) {
        var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy;
        if (distance2 < minDistance2) {
          var distance = Math.sqrt(minDistance2 = distance2);
          x0 = x - distance, y0 = y - distance;
          x3 = x + distance, y3 = y + distance;
          closestPoint = point;
        }
      }
      var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym;
      for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
        if (node = children[i & 3]) switch (i & 3) {
         case 0:
          find(node, x1, y1, xm, ym);
          break;

         case 1:
          find(node, xm, y1, x2, ym);
          break;

         case 2:
          find(node, x1, ym, xm, y2);
          break;

         case 3:
          find(node, xm, ym, x2, y2);
          break;
        }
      }
    })(root, x0, y0, x3, y3);
    return closestPoint;
  }
  d3.interpolateRgb = d3_interpolateRgb;
  function d3_interpolateRgb(a, b) {
    a = d3.rgb(a);
    b = d3.rgb(b);
    var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
    return function(t) {
      return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
    };
  }
  d3.interpolateObject = d3_interpolateObject;
  function d3_interpolateObject(a, b) {
    var i = {}, c = {}, k;
    for (k in a) {
      if (k in b) {
        i[k] = d3_interpolate(a[k], b[k]);
      } else {
        c[k] = a[k];
      }
    }
    for (k in b) {
      if (!(k in a)) {
        c[k] = b[k];
      }
    }
    return function(t) {
      for (k in i) c[k] = i[k](t);
      return c;
    };
  }
  d3.interpolateNumber = d3_interpolateNumber;
  function d3_interpolateNumber(a, b) {
    a = +a, b = +b;
    return function(t) {
      return a * (1 - t) + b * t;
    };
  }
  d3.interpolateString = d3_interpolateString;
  function d3_interpolateString(a, b) {
    var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = [];
    a = a + "", b = b + "";
    while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
      if ((bs = bm.index) > bi) {
        bs = b.slice(bi, bs);
        if (s[i]) s[i] += bs; else s[++i] = bs;
      }
      if ((am = am[0]) === (bm = bm[0])) {
        if (s[i]) s[i] += bm; else s[++i] = bm;
      } else {
        s[++i] = null;
        q.push({
          i: i,
          x: d3_interpolateNumber(am, bm)
        });
      }
      bi = d3_interpolate_numberB.lastIndex;
    }
    if (bi < b.length) {
      bs = b.slice(bi);
      if (s[i]) s[i] += bs; else s[++i] = bs;
    }
    return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
      return b(t) + "";
    }) : function() {
      return b;
    } : (b = q.length, function(t) {
      for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
      return s.join("");
    });
  }
  var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
  d3.interpolate = d3_interpolate;
  function d3_interpolate(a, b) {
    var i = d3.interpolators.length, f;
    while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
    return f;
  }
  d3.interpolators = [ function(a, b) {
    var t = typeof b;
    return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
  } ];
  d3.interpolateArray = d3_interpolateArray;
  function d3_interpolateArray(a, b) {
    var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
    for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
    for (;i < na; ++i) c[i] = a[i];
    for (;i < nb; ++i) c[i] = b[i];
    return function(t) {
      for (i = 0; i < n0; ++i) c[i] = x[i](t);
      return c;
    };
  }
  var d3_ease_default = function() {
    return d3_identity;
  };
  var d3_ease = d3.map({
    linear: d3_ease_default,
    poly: d3_ease_poly,
    quad: function() {
      return d3_ease_quad;
    },
    cubic: function() {
      return d3_ease_cubic;
    },
    sin: function() {
      return d3_ease_sin;
    },
    exp: function() {
      return d3_ease_exp;
    },
    circle: function() {
      return d3_ease_circle;
    },
    elastic: d3_ease_elastic,
    back: d3_ease_back,
    bounce: function() {
      return d3_ease_bounce;
    }
  });
  var d3_ease_mode = d3.map({
    "in": d3_identity,
    out: d3_ease_reverse,
    "in-out": d3_ease_reflect,
    "out-in": function(f) {
      return d3_ease_reflect(d3_ease_reverse(f));
    }
  });
  d3.ease = function(name) {
    var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in";
    t = d3_ease.get(t) || d3_ease_default;
    m = d3_ease_mode.get(m) || d3_identity;
    return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
  };
  function d3_ease_clamp(f) {
    return function(t) {
      return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
    };
  }
  function d3_ease_reverse(f) {
    return function(t) {
      return 1 - f(1 - t);
    };
  }
  function d3_ease_reflect(f) {
    return function(t) {
      return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
    };
  }
  function d3_ease_quad(t) {
    return t * t;
  }
  function d3_ease_cubic(t) {
    return t * t * t;
  }
  function d3_ease_cubicInOut(t) {
    if (t <= 0) return 0;
    if (t >= 1) return 1;
    var t2 = t * t, t3 = t2 * t;
    return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
  }
  function d3_ease_poly(e) {
    return function(t) {
      return Math.pow(t, e);
    };
  }
  function d3_ease_sin(t) {
    return 1 - Math.cos(t * halfπ);
  }
  function d3_ease_exp(t) {
    return Math.pow(2, 10 * (t - 1));
  }
  function d3_ease_circle(t) {
    return 1 - Math.sqrt(1 - t * t);
  }
  function d3_ease_elastic(a, p) {
    var s;
    if (arguments.length < 2) p = .45;
    if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4;
    return function(t) {
      return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p);
    };
  }
  function d3_ease_back(s) {
    if (!s) s = 1.70158;
    return function(t) {
      return t * t * ((s + 1) * t - s);
    };
  }
  function d3_ease_bounce(t) {
    return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
  }
  d3.interpolateHcl = d3_interpolateHcl;
  function d3_interpolateHcl(a, b) {
    a = d3.hcl(a);
    b = d3.hcl(b);
    var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
    if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
    return function(t) {
      return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
    };
  }
  d3.interpolateHsl = d3_interpolateHsl;
  function d3_interpolateHsl(a, b) {
    a = d3.hsl(a);
    b = d3.hsl(b);
    var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
    if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
    return function(t) {
      return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
    };
  }
  d3.interpolateLab = d3_interpolateLab;
  function d3_interpolateLab(a, b) {
    a = d3.lab(a);
    b = d3.lab(b);
    var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
    return function(t) {
      return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
    };
  }
  d3.interpolateRound = d3_interpolateRound;
  function d3_interpolateRound(a, b) {
    b -= a;
    return function(t) {
      return Math.round(a + b * t);
    };
  }
  d3.transform = function(string) {
    var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
    return (d3.transform = function(string) {
      if (string != null) {
        g.setAttribute("transform", string);
        var t = g.transform.baseVal.consolidate();
      }
      return new d3_transform(t ? t.matrix : d3_transformIdentity);
    })(string);
  };
  function d3_transform(m) {
    var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
    if (r0[0] * r1[1] < r1[0] * r0[1]) {
      r0[0] *= -1;
      r0[1] *= -1;
      kx *= -1;
      kz *= -1;
    }
    this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
    this.translate = [ m.e, m.f ];
    this.scale = [ kx, ky ];
    this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
  }
  d3_transform.prototype.toString = function() {
    return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
  };
  function d3_transformDot(a, b) {
    return a[0] * b[0] + a[1] * b[1];
  }
  function d3_transformNormalize(a) {
    var k = Math.sqrt(d3_transformDot(a, a));
    if (k) {
      a[0] /= k;
      a[1] /= k;
    }
    return k;
  }
  function d3_transformCombine(a, b, k) {
    a[0] += k * b[0];
    a[1] += k * b[1];
    return a;
  }
  var d3_transformIdentity = {
    a: 1,
    b: 0,
    c: 0,
    d: 1,
    e: 0,
    f: 0
  };
  d3.interpolateTransform = d3_interpolateTransform;
  function d3_interpolateTransformPop(s) {
    return s.length ? s.pop() + "," : "";
  }
  function d3_interpolateTranslate(ta, tb, s, q) {
    if (ta[0] !== tb[0] || ta[1] !== tb[1]) {
      var i = s.push("translate(", null, ",", null, ")");
      q.push({
        i: i - 4,
        x: d3_interpolateNumber(ta[0], tb[0])
      }, {
        i: i - 2,
        x: d3_interpolateNumber(ta[1], tb[1])
      });
    } else if (tb[0] || tb[1]) {
      s.push("translate(" + tb + ")");
    }
  }
  function d3_interpolateRotate(ra, rb, s, q) {
    if (ra !== rb) {
      if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
      q.push({
        i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2,
        x: d3_interpolateNumber(ra, rb)
      });
    } else if (rb) {
      s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")");
    }
  }
  function d3_interpolateSkew(wa, wb, s, q) {
    if (wa !== wb) {
      q.push({
        i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2,
        x: d3_interpolateNumber(wa, wb)
      });
    } else if (wb) {
      s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")");
    }
  }
  function d3_interpolateScale(ka, kb, s, q) {
    if (ka[0] !== kb[0] || ka[1] !== kb[1]) {
      var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")");
      q.push({
        i: i - 4,
        x: d3_interpolateNumber(ka[0], kb[0])
      }, {
        i: i - 2,
        x: d3_interpolateNumber(ka[1], kb[1])
      });
    } else if (kb[0] !== 1 || kb[1] !== 1) {
      s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")");
    }
  }
  function d3_interpolateTransform(a, b) {
    var s = [], q = [];
    a = d3.transform(a), b = d3.transform(b);
    d3_interpolateTranslate(a.translate, b.translate, s, q);
    d3_interpolateRotate(a.rotate, b.rotate, s, q);
    d3_interpolateSkew(a.skew, b.skew, s, q);
    d3_interpolateScale(a.scale, b.scale, s, q);
    a = b = null;
    return function(t) {
      var i = -1, n = q.length, o;
      while (++i < n) s[(o = q[i]).i] = o.x(t);
      return s.join("");
    };
  }
  function d3_uninterpolateNumber(a, b) {
    b = (b -= a = +a) || 1 / b;
    return function(x) {
      return (x - a) / b;
    };
  }
  function d3_uninterpolateClamp(a, b) {
    b = (b -= a = +a) || 1 / b;
    return function(x) {
      return Math.max(0, Math.min(1, (x - a) / b));
    };
  }
  d3.layout = {};
  d3.layout.bundle = function() {
    return function(links) {
      var paths = [], i = -1, n = links.length;
      while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
      return paths;
    };
  };
  function d3_layout_bundlePath(link) {
    var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
    while (start !== lca) {
      start = start.parent;
      points.push(start);
    }
    var k = points.length;
    while (end !== lca) {
      points.splice(k, 0, end);
      end = end.parent;
    }
    return points;
  }
  function d3_layout_bundleAncestors(node) {
    var ancestors = [], parent = node.parent;
    while (parent != null) {
      ancestors.push(node);
      node = parent;
      parent = parent.parent;
    }
    ancestors.push(node);
    return ancestors;
  }
  function d3_layout_bundleLeastCommonAncestor(a, b) {
    if (a === b) return a;
    var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
    while (aNode === bNode) {
      sharedNode = aNode;
      aNode = aNodes.pop();
      bNode = bNodes.pop();
    }
    return sharedNode;
  }
  d3.layout.chord = function() {
    var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
    function relayout() {
      var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
      chords = [];
      groups = [];
      k = 0, i = -1;
      while (++i < n) {
        x = 0, j = -1;
        while (++j < n) {
          x += matrix[i][j];
        }
        groupSums.push(x);
        subgroupIndex.push(d3.range(n));
        k += x;
      }
      if (sortGroups) {
        groupIndex.sort(function(a, b) {
          return sortGroups(groupSums[a], groupSums[b]);
        });
      }
      if (sortSubgroups) {
        subgroupIndex.forEach(function(d, i) {
          d.sort(function(a, b) {
            return sortSubgroups(matrix[i][a], matrix[i][b]);
          });
        });
      }
      k = (τ - padding * n) / k;
      x = 0, i = -1;
      while (++i < n) {
        x0 = x, j = -1;
        while (++j < n) {
          var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
          subgroups[di + "-" + dj] = {
            index: di,
            subindex: dj,
            startAngle: a0,
            endAngle: a1,
            value: v
          };
        }
        groups[di] = {
          index: di,
          startAngle: x0,
          endAngle: x,
          value: groupSums[di]
        };
        x += padding;
      }
      i = -1;
      while (++i < n) {
        j = i - 1;
        while (++j < n) {
          var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
          if (source.value || target.value) {
            chords.push(source.value < target.value ? {
              source: target,
              target: source
            } : {
              source: source,
              target: target
            });
          }
        }
      }
      if (sortChords) resort();
    }
    function resort() {
      chords.sort(function(a, b) {
        return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
      });
    }
    chord.matrix = function(x) {
      if (!arguments.length) return matrix;
      n = (matrix = x) && matrix.length;
      chords = groups = null;
      return chord;
    };
    chord.padding = function(x) {
      if (!arguments.length) return padding;
      padding = x;
      chords = groups = null;
      return chord;
    };
    chord.sortGroups = function(x) {
      if (!arguments.length) return sortGroups;
      sortGroups = x;
      chords = groups = null;
      return chord;
    };
    chord.sortSubgroups = function(x) {
      if (!arguments.length) return sortSubgroups;
      sortSubgroups = x;
      chords = null;
      return chord;
    };
    chord.sortChords = function(x) {
      if (!arguments.length) return sortChords;
      sortChords = x;
      if (chords) resort();
      return chord;
    };
    chord.chords = function() {
      if (!chords) relayout();
      return chords;
    };
    chord.groups = function() {
      if (!groups) relayout();
      return groups;
    };
    return chord;
  };
  d3.layout.force = function() {
    var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
    function repulse(node) {
      return function(quad, x1, _, x2) {
        if (quad.point !== node) {
          var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy;
          if (dw * dw / theta2 < dn) {
            if (dn < chargeDistance2) {
              var k = quad.charge / dn;
              node.px -= dx * k;
              node.py -= dy * k;
            }
            return true;
          }
          if (quad.point && dn && dn < chargeDistance2) {
            var k = quad.pointCharge / dn;
            node.px -= dx * k;
            node.py -= dy * k;
          }
        }
        return !quad.charge;
      };
    }
    force.tick = function() {
      if ((alpha *= .99) < .005) {
        timer = null;
        event.end({
          type: "end",
          alpha: alpha = 0
        });
        return true;
      }
      var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
      for (i = 0; i < m; ++i) {
        o = links[i];
        s = o.source;
        t = o.target;
        x = t.x - s.x;
        y = t.y - s.y;
        if (l = x * x + y * y) {
          l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
          x *= l;
          y *= l;
          t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5);
          t.y -= y * k;
          s.x += x * (k = 1 - k);
          s.y += y * k;
        }
      }
      if (k = alpha * gravity) {
        x = size[0] / 2;
        y = size[1] / 2;
        i = -1;
        if (k) while (++i < n) {
          o = nodes[i];
          o.x += (x - o.x) * k;
          o.y += (y - o.y) * k;
        }
      }
      if (charge) {
        d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
        i = -1;
        while (++i < n) {
          if (!(o = nodes[i]).fixed) {
            q.visit(repulse(o));
          }
        }
      }
      i = -1;
      while (++i < n) {
        o = nodes[i];
        if (o.fixed) {
          o.x = o.px;
          o.y = o.py;
        } else {
          o.x -= (o.px - (o.px = o.x)) * friction;
          o.y -= (o.py - (o.py = o.y)) * friction;
        }
      }
      event.tick({
        type: "tick",
        alpha: alpha
      });
    };
    force.nodes = function(x) {
      if (!arguments.length) return nodes;
      nodes = x;
      return force;
    };
    force.links = function(x) {
      if (!arguments.length) return links;
      links = x;
      return force;
    };
    force.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return force;
    };
    force.linkDistance = function(x) {
      if (!arguments.length) return linkDistance;
      linkDistance = typeof x === "function" ? x : +x;
      return force;
    };
    force.distance = force.linkDistance;
    force.linkStrength = function(x) {
      if (!arguments.length) return linkStrength;
      linkStrength = typeof x === "function" ? x : +x;
      return force;
    };
    force.friction = function(x) {
      if (!arguments.length) return friction;
      friction = +x;
      return force;
    };
    force.charge = function(x) {
      if (!arguments.length) return charge;
      charge = typeof x === "function" ? x : +x;
      return force;
    };
    force.chargeDistance = function(x) {
      if (!arguments.length) return Math.sqrt(chargeDistance2);
      chargeDistance2 = x * x;
      return force;
    };
    force.gravity = function(x) {
      if (!arguments.length) return gravity;
      gravity = +x;
      return force;
    };
    force.theta = function(x) {
      if (!arguments.length) return Math.sqrt(theta2);
      theta2 = x * x;
      return force;
    };
    force.alpha = function(x) {
      if (!arguments.length) return alpha;
      x = +x;
      if (alpha) {
        if (x > 0) {
          alpha = x;
        } else {
          timer.c = null, timer.t = NaN, timer = null;
          event.end({
            type: "end",
            alpha: alpha = 0
          });
        }
      } else if (x > 0) {
        event.start({
          type: "start",
          alpha: alpha = x
        });
        timer = d3_timer(force.tick);
      }
      return force;
    };
    force.start = function() {
      var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
      for (i = 0; i < n; ++i) {
        (o = nodes[i]).index = i;
        o.weight = 0;
      }
      for (i = 0; i < m; ++i) {
        o = links[i];
        if (typeof o.source == "number") o.source = nodes[o.source];
        if (typeof o.target == "number") o.target = nodes[o.target];
        ++o.source.weight;
        ++o.target.weight;
      }
      for (i = 0; i < n; ++i) {
        o = nodes[i];
        if (isNaN(o.x)) o.x = position("x", w);
        if (isNaN(o.y)) o.y = position("y", h);
        if (isNaN(o.px)) o.px = o.x;
        if (isNaN(o.py)) o.py = o.y;
      }
      distances = [];
      if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
      strengths = [];
      if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
      charges = [];
      if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
      function position(dimension, size) {
        if (!neighbors) {
          neighbors = new Array(n);
          for (j = 0; j < n; ++j) {
            neighbors[j] = [];
          }
          for (j = 0; j < m; ++j) {
            var o = links[j];
            neighbors[o.source.index].push(o.target);
            neighbors[o.target.index].push(o.source);
          }
        }
        var candidates = neighbors[i], j = -1, l = candidates.length, x;
        while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x;
        return Math.random() * size;
      }
      return force.resume();
    };
    force.resume = function() {
      return force.alpha(.1);
    };
    force.stop = function() {
      return force.alpha(0);
    };
    force.drag = function() {
      if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
      if (!arguments.length) return drag;
      this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
    };
    function dragmove(d) {
      d.px = d3.event.x, d.py = d3.event.y;
      force.resume();
    }
    return d3.rebind(force, event, "on");
  };
  function d3_layout_forceDragstart(d) {
    d.fixed |= 2;
  }
  function d3_layout_forceDragend(d) {
    d.fixed &= ~6;
  }
  function d3_layout_forceMouseover(d) {
    d.fixed |= 4;
    d.px = d.x, d.py = d.y;
  }
  function d3_layout_forceMouseout(d) {
    d.fixed &= ~4;
  }
  function d3_layout_forceAccumulate(quad, alpha, charges) {
    var cx = 0, cy = 0;
    quad.charge = 0;
    if (!quad.leaf) {
      var nodes = quad.nodes, n = nodes.length, i = -1, c;
      while (++i < n) {
        c = nodes[i];
        if (c == null) continue;
        d3_layout_forceAccumulate(c, alpha, charges);
        quad.charge += c.charge;
        cx += c.charge * c.cx;
        cy += c.charge * c.cy;
      }
    }
    if (quad.point) {
      if (!quad.leaf) {
        quad.point.x += Math.random() - .5;
        quad.point.y += Math.random() - .5;
      }
      var k = alpha * charges[quad.point.index];
      quad.charge += quad.pointCharge = k;
      cx += k * quad.point.x;
      cy += k * quad.point.y;
    }
    quad.cx = cx / quad.charge;
    quad.cy = cy / quad.charge;
  }
  var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity;
  d3.layout.hierarchy = function() {
    var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
    function hierarchy(root) {
      var stack = [ root ], nodes = [], node;
      root.depth = 0;
      while ((node = stack.pop()) != null) {
        nodes.push(node);
        if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
          var n, childs, child;
          while (--n >= 0) {
            stack.push(child = childs[n]);
            child.parent = node;
            child.depth = node.depth + 1;
          }
          if (value) node.value = 0;
          node.children = childs;
        } else {
          if (value) node.value = +value.call(hierarchy, node, node.depth) || 0;
          delete node.children;
        }
      }
      d3_layout_hierarchyVisitAfter(root, function(node) {
        var childs, parent;
        if (sort && (childs = node.children)) childs.sort(sort);
        if (value && (parent = node.parent)) parent.value += node.value;
      });
      return nodes;
    }
    hierarchy.sort = function(x) {
      if (!arguments.length) return sort;
      sort = x;
      return hierarchy;
    };
    hierarchy.children = function(x) {
      if (!arguments.length) return children;
      children = x;
      return hierarchy;
    };
    hierarchy.value = function(x) {
      if (!arguments.length) return value;
      value = x;
      return hierarchy;
    };
    hierarchy.revalue = function(root) {
      if (value) {
        d3_layout_hierarchyVisitBefore(root, function(node) {
          if (node.children) node.value = 0;
        });
        d3_layout_hierarchyVisitAfter(root, function(node) {
          var parent;
          if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
          if (parent = node.parent) parent.value += node.value;
        });
      }
      return root;
    };
    return hierarchy;
  };
  function d3_layout_hierarchyRebind(object, hierarchy) {
    d3.rebind(object, hierarchy, "sort", "children", "value");
    object.nodes = object;
    object.links = d3_layout_hierarchyLinks;
    return object;
  }
  function d3_layout_hierarchyVisitBefore(node, callback) {
    var nodes = [ node ];
    while ((node = nodes.pop()) != null) {
      callback(node);
      if ((children = node.children) && (n = children.length)) {
        var n, children;
        while (--n >= 0) nodes.push(children[n]);
      }
    }
  }
  function d3_layout_hierarchyVisitAfter(node, callback) {
    var nodes = [ node ], nodes2 = [];
    while ((node = nodes.pop()) != null) {
      nodes2.push(node);
      if ((children = node.children) && (n = children.length)) {
        var i = -1, n, children;
        while (++i < n) nodes.push(children[i]);
      }
    }
    while ((node = nodes2.pop()) != null) {
      callback(node);
    }
  }
  function d3_layout_hierarchyChildren(d) {
    return d.children;
  }
  function d3_layout_hierarchyValue(d) {
    return d.value;
  }
  function d3_layout_hierarchySort(a, b) {
    return b.value - a.value;
  }
  function d3_layout_hierarchyLinks(nodes) {
    return d3.merge(nodes.map(function(parent) {
      return (parent.children || []).map(function(child) {
        return {
          source: parent,
          target: child
        };
      });
    }));
  }
  d3.layout.partition = function() {
    var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
    function position(node, x, dx, dy) {
      var children = node.children;
      node.x = x;
      node.y = node.depth * dy;
      node.dx = dx;
      node.dy = dy;
      if (children && (n = children.length)) {
        var i = -1, n, c, d;
        dx = node.value ? dx / node.value : 0;
        while (++i < n) {
          position(c = children[i], x, d = c.value * dx, dy);
          x += d;
        }
      }
    }
    function depth(node) {
      var children = node.children, d = 0;
      if (children && (n = children.length)) {
        var i = -1, n;
        while (++i < n) d = Math.max(d, depth(children[i]));
      }
      return 1 + d;
    }
    function partition(d, i) {
      var nodes = hierarchy.call(this, d, i);
      position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
      return nodes;
    }
    partition.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return partition;
    };
    return d3_layout_hierarchyRebind(partition, hierarchy);
  };
  d3.layout.pie = function() {
    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0;
    function pie(data) {
      var n = data.length, values = data.map(function(d, i) {
        return +value.call(pie, d, i);
      }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v;
      if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
        return values[j] - values[i];
      } : function(i, j) {
        return sort(data[i], data[j]);
      });
      index.forEach(function(i) {
        arcs[i] = {
          data: data[i],
          value: v = values[i],
          startAngle: a,
          endAngle: a += v * k + pa,
          padAngle: p
        };
      });
      return arcs;
    }
    pie.value = function(_) {
      if (!arguments.length) return value;
      value = _;
      return pie;
    };
    pie.sort = function(_) {
      if (!arguments.length) return sort;
      sort = _;
      return pie;
    };
    pie.startAngle = function(_) {
      if (!arguments.length) return startAngle;
      startAngle = _;
      return pie;
    };
    pie.endAngle = function(_) {
      if (!arguments.length) return endAngle;
      endAngle = _;
      return pie;
    };
    pie.padAngle = function(_) {
      if (!arguments.length) return padAngle;
      padAngle = _;
      return pie;
    };
    return pie;
  };
  var d3_layout_pieSortByValue = {};
  d3.layout.stack = function() {
    var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
    function stack(data, index) {
      if (!(n = data.length)) return data;
      var series = data.map(function(d, i) {
        return values.call(stack, d, i);
      });
      var points = series.map(function(d) {
        return d.map(function(v, i) {
          return [ x.call(stack, v, i), y.call(stack, v, i) ];
        });
      });
      var orders = order.call(stack, points, index);
      series = d3.permute(series, orders);
      points = d3.permute(points, orders);
      var offsets = offset.call(stack, points, index);
      var m = series[0].length, n, i, j, o;
      for (j = 0; j < m; ++j) {
        out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
        for (i = 1; i < n; ++i) {
          out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
        }
      }
      return data;
    }
    stack.values = function(x) {
      if (!arguments.length) return values;
      values = x;
      return stack;
    };
    stack.order = function(x) {
      if (!arguments.length) return order;
      order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
      return stack;
    };
    stack.offset = function(x) {
      if (!arguments.length) return offset;
      offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
      return stack;
    };
    stack.x = function(z) {
      if (!arguments.length) return x;
      x = z;
      return stack;
    };
    stack.y = function(z) {
      if (!arguments.length) return y;
      y = z;
      return stack;
    };
    stack.out = function(z) {
      if (!arguments.length) return out;
      out = z;
      return stack;
    };
    return stack;
  };
  function d3_layout_stackX(d) {
    return d.x;
  }
  function d3_layout_stackY(d) {
    return d.y;
  }
  function d3_layout_stackOut(d, y0, y) {
    d.y0 = y0;
    d.y = y;
  }
  var d3_layout_stackOrders = d3.map({
    "inside-out": function(data) {
      var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
        return max[a] - max[b];
      }), top = 0, bottom = 0, tops = [], bottoms = [];
      for (i = 0; i < n; ++i) {
        j = index[i];
        if (top < bottom) {
          top += sums[j];
          tops.push(j);
        } else {
          bottom += sums[j];
          bottoms.push(j);
        }
      }
      return bottoms.reverse().concat(tops);
    },
    reverse: function(data) {
      return d3.range(data.length).reverse();
    },
    "default": d3_layout_stackOrderDefault
  });
  var d3_layout_stackOffsets = d3.map({
    silhouette: function(data) {
      var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
      for (j = 0; j < m; ++j) {
        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
        if (o > max) max = o;
        sums.push(o);
      }
      for (j = 0; j < m; ++j) {
        y0[j] = (max - sums[j]) / 2;
      }
      return y0;
    },
    wiggle: function(data) {
      var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
      y0[0] = o = o0 = 0;
      for (j = 1; j < m; ++j) {
        for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
        for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
          for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
            s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
          }
          s2 += s3 * data[i][j][1];
        }
        y0[j] = o -= s1 ? s2 / s1 * dx : 0;
        if (o < o0) o0 = o;
      }
      for (j = 0; j < m; ++j) y0[j] -= o0;
      return y0;
    },
    expand: function(data) {
      var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
      for (j = 0; j < m; ++j) {
        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
        if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
      }
      for (j = 0; j < m; ++j) y0[j] = 0;
      return y0;
    },
    zero: d3_layout_stackOffsetZero
  });
  function d3_layout_stackOrderDefault(data) {
    return d3.range(data.length);
  }
  function d3_layout_stackOffsetZero(data) {
    var j = -1, m = data[0].length, y0 = [];
    while (++j < m) y0[j] = 0;
    return y0;
  }
  function d3_layout_stackMaxIndex(array) {
    var i = 1, j = 0, v = array[0][1], k, n = array.length;
    for (;i < n; ++i) {
      if ((k = array[i][1]) > v) {
        j = i;
        v = k;
      }
    }
    return j;
  }
  function d3_layout_stackReduceSum(d) {
    return d.reduce(d3_layout_stackSum, 0);
  }
  function d3_layout_stackSum(p, d) {
    return p + d[1];
  }
  d3.layout.histogram = function() {
    var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
    function histogram(data, i) {
      var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
      while (++i < m) {
        bin = bins[i] = [];
        bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
        bin.y = 0;
      }
      if (m > 0) {
        i = -1;
        while (++i < n) {
          x = values[i];
          if (x >= range[0] && x <= range[1]) {
            bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
            bin.y += k;
            bin.push(data[i]);
          }
        }
      }
      return bins;
    }
    histogram.value = function(x) {
      if (!arguments.length) return valuer;
      valuer = x;
      return histogram;
    };
    histogram.range = function(x) {
      if (!arguments.length) return ranger;
      ranger = d3_functor(x);
      return histogram;
    };
    histogram.bins = function(x) {
      if (!arguments.length) return binner;
      binner = typeof x === "number" ? function(range) {
        return d3_layout_histogramBinFixed(range, x);
      } : d3_functor(x);
      return histogram;
    };
    histogram.frequency = function(x) {
      if (!arguments.length) return frequency;
      frequency = !!x;
      return histogram;
    };
    return histogram;
  };
  function d3_layout_histogramBinSturges(range, values) {
    return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
  }
  function d3_layout_histogramBinFixed(range, n) {
    var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
    while (++x <= n) f[x] = m * x + b;
    return f;
  }
  function d3_layout_histogramRange(values) {
    return [ d3.min(values), d3.max(values) ];
  }
  d3.layout.pack = function() {
    var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
    function pack(d, i) {
      var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
        return radius;
      };
      root.x = root.y = 0;
      d3_layout_hierarchyVisitAfter(root, function(d) {
        d.r = +r(d.value);
      });
      d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
      if (padding) {
        var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
        d3_layout_hierarchyVisitAfter(root, function(d) {
          d.r += dr;
        });
        d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
        d3_layout_hierarchyVisitAfter(root, function(d) {
          d.r -= dr;
        });
      }
      d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
      return nodes;
    }
    pack.size = function(_) {
      if (!arguments.length) return size;
      size = _;
      return pack;
    };
    pack.radius = function(_) {
      if (!arguments.length) return radius;
      radius = _ == null || typeof _ === "function" ? _ : +_;
      return pack;
    };
    pack.padding = function(_) {
      if (!arguments.length) return padding;
      padding = +_;
      return pack;
    };
    return d3_layout_hierarchyRebind(pack, hierarchy);
  };
  function d3_layout_packSort(a, b) {
    return a.value - b.value;
  }
  function d3_layout_packInsert(a, b) {
    var c = a._pack_next;
    a._pack_next = b;
    b._pack_prev = a;
    b._pack_next = c;
    c._pack_prev = b;
  }
  function d3_layout_packSplice(a, b) {
    a._pack_next = b;
    b._pack_prev = a;
  }
  function d3_layout_packIntersects(a, b) {
    var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
    return .999 * dr * dr > dx * dx + dy * dy;
  }
  function d3_layout_packSiblings(node) {
    if (!(nodes = node.children) || !(n = nodes.length)) return;
    var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
    function bound(node) {
      xMin = Math.min(node.x - node.r, xMin);
      xMax = Math.max(node.x + node.r, xMax);
      yMin = Math.min(node.y - node.r, yMin);
      yMax = Math.max(node.y + node.r, yMax);
    }
    nodes.forEach(d3_layout_packLink);
    a = nodes[0];
    a.x = -a.r;
    a.y = 0;
    bound(a);
    if (n > 1) {
      b = nodes[1];
      b.x = b.r;
      b.y = 0;
      bound(b);
      if (n > 2) {
        c = nodes[2];
        d3_layout_packPlace(a, b, c);
        bound(c);
        d3_layout_packInsert(a, c);
        a._pack_prev = c;
        d3_layout_packInsert(c, b);
        b = a._pack_next;
        for (i = 3; i < n; i++) {
          d3_layout_packPlace(a, b, c = nodes[i]);
          var isect = 0, s1 = 1, s2 = 1;
          for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
            if (d3_layout_packIntersects(j, c)) {
              isect = 1;
              break;
            }
          }
          if (isect == 1) {
            for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
              if (d3_layout_packIntersects(k, c)) {
                break;
              }
            }
          }
          if (isect) {
            if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
            i--;
          } else {
            d3_layout_packInsert(a, c);
            b = c;
            bound(c);
          }
        }
      }
    }
    var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
    for (i = 0; i < n; i++) {
      c = nodes[i];
      c.x -= cx;
      c.y -= cy;
      cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
    }
    node.r = cr;
    nodes.forEach(d3_layout_packUnlink);
  }
  function d3_layout_packLink(node) {
    node._pack_next = node._pack_prev = node;
  }
  function d3_layout_packUnlink(node) {
    delete node._pack_next;
    delete node._pack_prev;
  }
  function d3_layout_packTransform(node, x, y, k) {
    var children = node.children;
    node.x = x += k * node.x;
    node.y = y += k * node.y;
    node.r *= k;
    if (children) {
      var i = -1, n = children.length;
      while (++i < n) d3_layout_packTransform(children[i], x, y, k);
    }
  }
  function d3_layout_packPlace(a, b, c) {
    var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
    if (db && (dx || dy)) {
      var da = b.r + c.r, dc = dx * dx + dy * dy;
      da *= da;
      db *= db;
      var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
      c.x = a.x + x * dx + y * dy;
      c.y = a.y + x * dy - y * dx;
    } else {
      c.x = a.x + db;
      c.y = a.y;
    }
  }
  d3.layout.tree = function() {
    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null;
    function tree(d, i) {
      var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0);
      d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
      d3_layout_hierarchyVisitBefore(root1, secondWalk);
      if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else {
        var left = root0, right = root0, bottom = root0;
        d3_layout_hierarchyVisitBefore(root0, function(node) {
          if (node.x < left.x) left = node;
          if (node.x > right.x) right = node;
          if (node.depth > bottom.depth) bottom = node;
        });
        var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1);
        d3_layout_hierarchyVisitBefore(root0, function(node) {
          node.x = (node.x + tx) * kx;
          node.y = node.depth * ky;
        });
      }
      return nodes;
    }
    function wrapTree(root0) {
      var root1 = {
        A: null,
        children: [ root0 ]
      }, queue = [ root1 ], node1;
      while ((node1 = queue.pop()) != null) {
        for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
          queue.push((children[i] = child = {
            _: children[i],
            parent: node1,
            children: (child = children[i].children) && child.slice() || [],
            A: null,
            a: null,
            z: 0,
            m: 0,
            c: 0,
            s: 0,
            t: null,
            i: i
          }).a = child);
        }
      }
      return root1.children[0];
    }
    function firstWalk(v) {
      var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null;
      if (children.length) {
        d3_layout_treeShift(v);
        var midpoint = (children[0].z + children[children.length - 1].z) / 2;
        if (w) {
          v.z = w.z + separation(v._, w._);
          v.m = v.z - midpoint;
        } else {
          v.z = midpoint;
        }
      } else if (w) {
        v.z = w.z + separation(v._, w._);
      }
      v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
    }
    function secondWalk(v) {
      v._.x = v.z + v.parent.m;
      v.m += v.parent.m;
    }
    function apportion(v, w, ancestor) {
      if (w) {
        var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift;
        while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
          vom = d3_layout_treeLeft(vom);
          vop = d3_layout_treeRight(vop);
          vop.a = v;
          shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
          if (shift > 0) {
            d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
            sip += shift;
            sop += shift;
          }
          sim += vim.m;
          sip += vip.m;
          som += vom.m;
          sop += vop.m;
        }
        if (vim && !d3_layout_treeRight(vop)) {
          vop.t = vim;
          vop.m += sim - sop;
        }
        if (vip && !d3_layout_treeLeft(vom)) {
          vom.t = vip;
          vom.m += sip - som;
          ancestor = v;
        }
      }
      return ancestor;
    }
    function sizeNode(node) {
      node.x *= size[0];
      node.y = node.depth * size[1];
    }
    tree.separation = function(x) {
      if (!arguments.length) return separation;
      separation = x;
      return tree;
    };
    tree.size = function(x) {
      if (!arguments.length) return nodeSize ? null : size;
      nodeSize = (size = x) == null ? sizeNode : null;
      return tree;
    };
    tree.nodeSize = function(x) {
      if (!arguments.length) return nodeSize ? size : null;
      nodeSize = (size = x) == null ? null : sizeNode;
      return tree;
    };
    return d3_layout_hierarchyRebind(tree, hierarchy);
  };
  function d3_layout_treeSeparation(a, b) {
    return a.parent == b.parent ? 1 : 2;
  }
  function d3_layout_treeLeft(v) {
    var children = v.children;
    return children.length ? children[0] : v.t;
  }
  function d3_layout_treeRight(v) {
    var children = v.children, n;
    return (n = children.length) ? children[n - 1] : v.t;
  }
  function d3_layout_treeMove(wm, wp, shift) {
    var change = shift / (wp.i - wm.i);
    wp.c -= change;
    wp.s += shift;
    wm.c += change;
    wp.z += shift;
    wp.m += shift;
  }
  function d3_layout_treeShift(v) {
    var shift = 0, change = 0, children = v.children, i = children.length, w;
    while (--i >= 0) {
      w = children[i];
      w.z += shift;
      w.m += shift;
      shift += w.s + (change += w.c);
    }
  }
  function d3_layout_treeAncestor(vim, v, ancestor) {
    return vim.a.parent === v.parent ? vim.a : ancestor;
  }
  d3.layout.cluster = function() {
    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
    function cluster(d, i) {
      var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
      d3_layout_hierarchyVisitAfter(root, function(node) {
        var children = node.children;
        if (children && children.length) {
          node.x = d3_layout_clusterX(children);
          node.y = d3_layout_clusterY(children);
        } else {
          node.x = previousNode ? x += separation(node, previousNode) : 0;
          node.y = 0;
          previousNode = node;
        }
      });
      var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
      d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
        node.x = (node.x - root.x) * size[0];
        node.y = (root.y - node.y) * size[1];
      } : function(node) {
        node.x = (node.x - x0) / (x1 - x0) * size[0];
        node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
      });
      return nodes;
    }
    cluster.separation = function(x) {
      if (!arguments.length) return separation;
      separation = x;
      return cluster;
    };
    cluster.size = function(x) {
      if (!arguments.length) return nodeSize ? null : size;
      nodeSize = (size = x) == null;
      return cluster;
    };
    cluster.nodeSize = function(x) {
      if (!arguments.length) return nodeSize ? size : null;
      nodeSize = (size = x) != null;
      return cluster;
    };
    return d3_layout_hierarchyRebind(cluster, hierarchy);
  };
  function d3_layout_clusterY(children) {
    return 1 + d3.max(children, function(child) {
      return child.y;
    });
  }
  function d3_layout_clusterX(children) {
    return children.reduce(function(x, child) {
      return x + child.x;
    }, 0) / children.length;
  }
  function d3_layout_clusterLeft(node) {
    var children = node.children;
    return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
  }
  function d3_layout_clusterRight(node) {
    var children = node.children, n;
    return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
  }
  d3.layout.treemap = function() {
    var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
    function scale(children, k) {
      var i = -1, n = children.length, child, area;
      while (++i < n) {
        area = (child = children[i]).value * (k < 0 ? 0 : k);
        child.area = isNaN(area) || area <= 0 ? 0 : area;
      }
    }
    function squarify(node) {
      var children = node.children;
      if (children && children.length) {
        var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
        scale(remaining, rect.dx * rect.dy / node.value);
        row.area = 0;
        while ((n = remaining.length) > 0) {
          row.push(child = remaining[n - 1]);
          row.area += child.area;
          if (mode !== "squarify" || (score = worst(row, u)) <= best) {
            remaining.pop();
            best = score;
          } else {
            row.area -= row.pop().area;
            position(row, u, rect, false);
            u = Math.min(rect.dx, rect.dy);
            row.length = row.area = 0;
            best = Infinity;
          }
        }
        if (row.length) {
          position(row, u, rect, true);
          row.length = row.area = 0;
        }
        children.forEach(squarify);
      }
    }
    function stickify(node) {
      var children = node.children;
      if (children && children.length) {
        var rect = pad(node), remaining = children.slice(), child, row = [];
        scale(remaining, rect.dx * rect.dy / node.value);
        row.area = 0;
        while (child = remaining.pop()) {
          row.push(child);
          row.area += child.area;
          if (child.z != null) {
            position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
            row.length = row.area = 0;
          }
        }
        children.forEach(stickify);
      }
    }
    function worst(row, u) {
      var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
      while (++i < n) {
        if (!(r = row[i].area)) continue;
        if (r < rmin) rmin = r;
        if (r > rmax) rmax = r;
      }
      s *= s;
      u *= u;
      return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
    }
    function position(row, u, rect, flush) {
      var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
      if (u == rect.dx) {
        if (flush || v > rect.dy) v = rect.dy;
        while (++i < n) {
          o = row[i];
          o.x = x;
          o.y = y;
          o.dy = v;
          x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
        }
        o.z = true;
        o.dx += rect.x + rect.dx - x;
        rect.y += v;
        rect.dy -= v;
      } else {
        if (flush || v > rect.dx) v = rect.dx;
        while (++i < n) {
          o = row[i];
          o.x = x;
          o.y = y;
          o.dx = v;
          y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
        }
        o.z = false;
        o.dy += rect.y + rect.dy - y;
        rect.x += v;
        rect.dx -= v;
      }
    }
    function treemap(d) {
      var nodes = stickies || hierarchy(d), root = nodes[0];
      root.x = root.y = 0;
      if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0;
      if (stickies) hierarchy.revalue(root);
      scale([ root ], root.dx * root.dy / root.value);
      (stickies ? stickify : squarify)(root);
      if (sticky) stickies = nodes;
      return nodes;
    }
    treemap.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return treemap;
    };
    treemap.padding = function(x) {
      if (!arguments.length) return padding;
      function padFunction(node) {
        var p = x.call(treemap, node, node.depth);
        return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
      }
      function padConstant(node) {
        return d3_layout_treemapPad(node, x);
      }
      var type;
      pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], 
      padConstant) : padConstant;
      return treemap;
    };
    treemap.round = function(x) {
      if (!arguments.length) return round != Number;
      round = x ? Math.round : Number;
      return treemap;
    };
    treemap.sticky = function(x) {
      if (!arguments.length) return sticky;
      sticky = x;
      stickies = null;
      return treemap;
    };
    treemap.ratio = function(x) {
      if (!arguments.length) return ratio;
      ratio = x;
      return treemap;
    };
    treemap.mode = function(x) {
      if (!arguments.length) return mode;
      mode = x + "";
      return treemap;
    };
    return d3_layout_hierarchyRebind(treemap, hierarchy);
  };
  function d3_layout_treemapPadNull(node) {
    return {
      x: node.x,
      y: node.y,
      dx: node.dx,
      dy: node.dy
    };
  }
  function d3_layout_treemapPad(node, padding) {
    var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
    if (dx < 0) {
      x += dx / 2;
      dx = 0;
    }
    if (dy < 0) {
      y += dy / 2;
      dy = 0;
    }
    return {
      x: x,
      y: y,
      dx: dx,
      dy: dy
    };
  }
  d3.random = {
    normal: function(µ, σ) {
      var n = arguments.length;
      if (n < 2) σ = 1;
      if (n < 1) µ = 0;
      return function() {
        var x, y, r;
        do {
          x = Math.random() * 2 - 1;
          y = Math.random() * 2 - 1;
          r = x * x + y * y;
        } while (!r || r > 1);
        return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
      };
    },
    logNormal: function() {
      var random = d3.random.normal.apply(d3, arguments);
      return function() {
        return Math.exp(random());
      };
    },
    bates: function(m) {
      var random = d3.random.irwinHall(m);
      return function() {
        return random() / m;
      };
    },
    irwinHall: function(m) {
      return function() {
        for (var s = 0, j = 0; j < m; j++) s += Math.random();
        return s;
      };
    }
  };
  d3.scale = {};
  function d3_scaleExtent(domain) {
    var start = domain[0], stop = domain[domain.length - 1];
    return start < stop ? [ start, stop ] : [ stop, start ];
  }
  function d3_scaleRange(scale) {
    return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
  }
  function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
    var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
    return function(x) {
      return i(u(x));
    };
  }
  function d3_scale_nice(domain, nice) {
    var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
    if (x1 < x0) {
      dx = i0, i0 = i1, i1 = dx;
      dx = x0, x0 = x1, x1 = dx;
    }
    domain[i0] = nice.floor(x0);
    domain[i1] = nice.ceil(x1);
    return domain;
  }
  function d3_scale_niceStep(step) {
    return step ? {
      floor: function(x) {
        return Math.floor(x / step) * step;
      },
      ceil: function(x) {
        return Math.ceil(x / step) * step;
      }
    } : d3_scale_niceIdentity;
  }
  var d3_scale_niceIdentity = {
    floor: d3_identity,
    ceil: d3_identity
  };
  function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
    var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
    if (domain[k] < domain[0]) {
      domain = domain.slice().reverse();
      range = range.slice().reverse();
    }
    while (++j <= k) {
      u.push(uninterpolate(domain[j - 1], domain[j]));
      i.push(interpolate(range[j - 1], range[j]));
    }
    return function(x) {
      var j = d3.bisect(domain, x, 1, k) - 1;
      return i[j](u[j](x));
    };
  }
  d3.scale.linear = function() {
    return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
  };
  function d3_scale_linear(domain, range, interpolate, clamp) {
    var output, input;
    function rescale() {
      var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
      output = linear(domain, range, uninterpolate, interpolate);
      input = linear(range, domain, uninterpolate, d3_interpolate);
      return scale;
    }
    function scale(x) {
      return output(x);
    }
    scale.invert = function(y) {
      return input(y);
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(Number);
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.rangeRound = function(x) {
      return scale.range(x).interpolate(d3_interpolateRound);
    };
    scale.clamp = function(x) {
      if (!arguments.length) return clamp;
      clamp = x;
      return rescale();
    };
    scale.interpolate = function(x) {
      if (!arguments.length) return interpolate;
      interpolate = x;
      return rescale();
    };
    scale.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    scale.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    scale.nice = function(m) {
      d3_scale_linearNice(domain, m);
      return rescale();
    };
    scale.copy = function() {
      return d3_scale_linear(domain, range, interpolate, clamp);
    };
    return rescale();
  }
  function d3_scale_linearRebind(scale, linear) {
    return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
  }
  function d3_scale_linearNice(domain, m) {
    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
    return domain;
  }
  function d3_scale_linearTickRange(domain, m) {
    if (m == null) m = 10;
    var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
    if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
    extent[0] = Math.ceil(extent[0] / step) * step;
    extent[1] = Math.floor(extent[1] / step) * step + step * .5;
    extent[2] = step;
    return extent;
  }
  function d3_scale_linearTicks(domain, m) {
    return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
  }
  function d3_scale_linearTickFormat(domain, m, format) {
    var range = d3_scale_linearTickRange(domain, m);
    if (format) {
      var match = d3_format_re.exec(format);
      match.shift();
      if (match[8] === "s") {
        var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
        if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
        match[8] = "f";
        format = d3.format(match.join(""));
        return function(d) {
          return format(prefix.scale(d)) + prefix.symbol;
        };
      }
      if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
      format = match.join("");
    } else {
      format = ",." + d3_scale_linearPrecision(range[2]) + "f";
    }
    return d3.format(format);
  }
  var d3_scale_linearFormatSignificant = {
    s: 1,
    g: 1,
    p: 1,
    r: 1,
    e: 1
  };
  function d3_scale_linearPrecision(value) {
    return -Math.floor(Math.log(value) / Math.LN10 + .01);
  }
  function d3_scale_linearFormatPrecision(type, range) {
    var p = d3_scale_linearPrecision(range[2]);
    return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
  }
  d3.scale.log = function() {
    return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
  };
  function d3_scale_log(linear, base, positive, domain) {
    function log(x) {
      return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
    }
    function pow(x) {
      return positive ? Math.pow(base, x) : -Math.pow(base, -x);
    }
    function scale(x) {
      return linear(log(x));
    }
    scale.invert = function(x) {
      return pow(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      positive = x[0] >= 0;
      linear.domain((domain = x.map(Number)).map(log));
      return scale;
    };
    scale.base = function(_) {
      if (!arguments.length) return base;
      base = +_;
      linear.domain(domain.map(log));
      return scale;
    };
    scale.nice = function() {
      var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
      linear.domain(niced);
      domain = niced.map(pow);
      return scale;
    };
    scale.ticks = function() {
      var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
      if (isFinite(j - i)) {
        if (positive) {
          for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
          ticks.push(pow(i));
        } else {
          ticks.push(pow(i));
          for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
        }
        for (i = 0; ticks[i] < u; i++) {}
        for (j = ticks.length; ticks[j - 1] > v; j--) {}
        ticks = ticks.slice(i, j);
      }
      return ticks;
    };
    scale.tickFormat = function(n, format) {
      if (!arguments.length) return d3_scale_logFormat;
      if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
      var k = Math.max(1, base * n / scale.ticks().length);
      return function(d) {
        var i = d / pow(Math.round(log(d)));
        if (i * base < base - .5) i *= base;
        return i <= k ? format(d) : "";
      };
    };
    scale.copy = function() {
      return d3_scale_log(linear.copy(), base, positive, domain);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
    floor: function(x) {
      return -Math.ceil(-x);
    },
    ceil: function(x) {
      return -Math.floor(-x);
    }
  };
  d3.scale.pow = function() {
    return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
  };
  function d3_scale_pow(linear, exponent, domain) {
    var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
    function scale(x) {
      return linear(powp(x));
    }
    scale.invert = function(x) {
      return powb(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      linear.domain((domain = x.map(Number)).map(powp));
      return scale;
    };
    scale.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    scale.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    scale.nice = function(m) {
      return scale.domain(d3_scale_linearNice(domain, m));
    };
    scale.exponent = function(x) {
      if (!arguments.length) return exponent;
      powp = d3_scale_powPow(exponent = x);
      powb = d3_scale_powPow(1 / exponent);
      linear.domain(domain.map(powp));
      return scale;
    };
    scale.copy = function() {
      return d3_scale_pow(linear.copy(), exponent, domain);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  function d3_scale_powPow(e) {
    return function(x) {
      return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
    };
  }
  d3.scale.sqrt = function() {
    return d3.scale.pow().exponent(.5);
  };
  d3.scale.ordinal = function() {
    return d3_scale_ordinal([], {
      t: "range",
      a: [ [] ]
    });
  };
  function d3_scale_ordinal(domain, ranger) {
    var index, range, rangeBand;
    function scale(x) {
      return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
    }
    function steps(start, step) {
      return d3.range(domain.length).map(function(i) {
        return start + step * i;
      });
    }
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = [];
      index = new d3_Map();
      var i = -1, n = x.length, xi;
      while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
      return scale[ranger.t].apply(scale, ranger.a);
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      rangeBand = 0;
      ranger = {
        t: "range",
        a: arguments
      };
      return scale;
    };
    scale.rangePoints = function(x, padding) {
      if (arguments.length < 2) padding = 0;
      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, 
      0) : (stop - start) / (domain.length - 1 + padding);
      range = steps(start + step * padding / 2, step);
      rangeBand = 0;
      ranger = {
        t: "rangePoints",
        a: arguments
      };
      return scale;
    };
    scale.rangeRoundPoints = function(x, padding) {
      if (arguments.length < 2) padding = 0;
      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), 
      0) : (stop - start) / (domain.length - 1 + padding) | 0;
      range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
      rangeBand = 0;
      ranger = {
        t: "rangeRoundPoints",
        a: arguments
      };
      return scale;
    };
    scale.rangeBands = function(x, padding, outerPadding) {
      if (arguments.length < 2) padding = 0;
      if (arguments.length < 3) outerPadding = padding;
      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
      range = steps(start + step * outerPadding, step);
      if (reverse) range.reverse();
      rangeBand = step * (1 - padding);
      ranger = {
        t: "rangeBands",
        a: arguments
      };
      return scale;
    };
    scale.rangeRoundBands = function(x, padding, outerPadding) {
      if (arguments.length < 2) padding = 0;
      if (arguments.length < 3) outerPadding = padding;
      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
      range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
      if (reverse) range.reverse();
      rangeBand = Math.round(step * (1 - padding));
      ranger = {
        t: "rangeRoundBands",
        a: arguments
      };
      return scale;
    };
    scale.rangeBand = function() {
      return rangeBand;
    };
    scale.rangeExtent = function() {
      return d3_scaleExtent(ranger.a[0]);
    };
    scale.copy = function() {
      return d3_scale_ordinal(domain, ranger);
    };
    return scale.domain(domain);
  }
  d3.scale.category10 = function() {
    return d3.scale.ordinal().range(d3_category10);
  };
  d3.scale.category20 = function() {
    return d3.scale.ordinal().range(d3_category20);
  };
  d3.scale.category20b = function() {
    return d3.scale.ordinal().range(d3_category20b);
  };
  d3.scale.category20c = function() {
    return d3.scale.ordinal().range(d3_category20c);
  };
  var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
  var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
  var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
  var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
  d3.scale.quantile = function() {
    return d3_scale_quantile([], []);
  };
  function d3_scale_quantile(domain, range) {
    var thresholds;
    function rescale() {
      var k = 0, q = range.length;
      thresholds = [];
      while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
      return scale;
    }
    function scale(x) {
      if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
    }
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.quantiles = function() {
      return thresholds;
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
    };
    scale.copy = function() {
      return d3_scale_quantile(domain, range);
    };
    return rescale();
  }
  d3.scale.quantize = function() {
    return d3_scale_quantize(0, 1, [ 0, 1 ]);
  };
  function d3_scale_quantize(x0, x1, range) {
    var kx, i;
    function scale(x) {
      return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
    }
    function rescale() {
      kx = range.length / (x1 - x0);
      i = range.length - 1;
      return scale;
    }
    scale.domain = function(x) {
      if (!arguments.length) return [ x0, x1 ];
      x0 = +x[0];
      x1 = +x[x.length - 1];
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      y = y < 0 ? NaN : y / kx + x0;
      return [ y, y + 1 / kx ];
    };
    scale.copy = function() {
      return d3_scale_quantize(x0, x1, range);
    };
    return rescale();
  }
  d3.scale.threshold = function() {
    return d3_scale_threshold([ .5 ], [ 0, 1 ]);
  };
  function d3_scale_threshold(domain, range) {
    function scale(x) {
      if (x <= x) return range[d3.bisect(domain, x)];
    }
    scale.domain = function(_) {
      if (!arguments.length) return domain;
      domain = _;
      return scale;
    };
    scale.range = function(_) {
      if (!arguments.length) return range;
      range = _;
      return scale;
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      return [ domain[y - 1], domain[y] ];
    };
    scale.copy = function() {
      return d3_scale_threshold(domain, range);
    };
    return scale;
  }
  d3.scale.identity = function() {
    return d3_scale_identity([ 0, 1 ]);
  };
  function d3_scale_identity(domain) {
    function identity(x) {
      return +x;
    }
    identity.invert = identity;
    identity.domain = identity.range = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(identity);
      return identity;
    };
    identity.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    identity.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    identity.copy = function() {
      return d3_scale_identity(domain);
    };
    return identity;
  }
  d3.svg = {};
  function d3_zero() {
    return 0;
  }
  d3.svg.arc = function() {
    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle;
    function arc() {
      var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1;
      if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
      if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
      var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];
      if (ap = (+padAngle.apply(this, arguments) || 0) / 2) {
        rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
        if (!cw) p1 *= -1;
        if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
        if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
      }
      if (r1) {
        x0 = r1 * Math.cos(a0 + p1);
        y0 = r1 * Math.sin(a0 + p1);
        x1 = r1 * Math.cos(a1 - p1);
        y1 = r1 * Math.sin(a1 - p1);
        var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
        if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
          var h1 = (a0 + a1) / 2;
          x0 = r1 * Math.cos(h1);
          y0 = r1 * Math.sin(h1);
          x1 = y1 = null;
        }
      } else {
        x0 = y0 = 0;
      }
      if (r0) {
        x2 = r0 * Math.cos(a1 - p0);
        y2 = r0 * Math.sin(a1 - p0);
        x3 = r0 * Math.cos(a0 + p0);
        y3 = r0 * Math.sin(a0 + p0);
        var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
        if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
          var h0 = (a0 + a1) / 2;
          x2 = r0 * Math.cos(h0);
          y2 = r0 * Math.sin(h0);
          x3 = y3 = null;
        }
      } else {
        x2 = y2 = 0;
      }
      if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
        cr = r0 < r1 ^ cw ? 0 : 1;
        var rc1 = rc, rc0 = rc;
        if (da < π) {
          var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
          rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
          rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
        }
        if (x1 != null) {
          var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw);
          if (rc === rc1) {
            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
          } else {
            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
          }
        } else {
          path.push("M", x0, ",", y0);
        }
        if (x3 != null) {
          var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw);
          if (rc === rc0) {
            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
          } else {
            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
          }
        } else {
          path.push("L", x2, ",", y2);
        }
      } else {
        path.push("M", x0, ",", y0);
        if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
        path.push("L", x2, ",", y2);
        if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
      }
      path.push("Z");
      return path.join("");
    }
    function circleSegment(r1, cw) {
      return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
    }
    arc.innerRadius = function(v) {
      if (!arguments.length) return innerRadius;
      innerRadius = d3_functor(v);
      return arc;
    };
    arc.outerRadius = function(v) {
      if (!arguments.length) return outerRadius;
      outerRadius = d3_functor(v);
      return arc;
    };
    arc.cornerRadius = function(v) {
      if (!arguments.length) return cornerRadius;
      cornerRadius = d3_functor(v);
      return arc;
    };
    arc.padRadius = function(v) {
      if (!arguments.length) return padRadius;
      padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
      return arc;
    };
    arc.startAngle = function(v) {
      if (!arguments.length) return startAngle;
      startAngle = d3_functor(v);
      return arc;
    };
    arc.endAngle = function(v) {
      if (!arguments.length) return endAngle;
      endAngle = d3_functor(v);
      return arc;
    };
    arc.padAngle = function(v) {
      if (!arguments.length) return padAngle;
      padAngle = d3_functor(v);
      return arc;
    };
    arc.centroid = function() {
      var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
      return [ Math.cos(a) * r, Math.sin(a) * r ];
    };
    return arc;
  };
  var d3_svg_arcAuto = "auto";
  function d3_svg_arcInnerRadius(d) {
    return d.innerRadius;
  }
  function d3_svg_arcOuterRadius(d) {
    return d.outerRadius;
  }
  function d3_svg_arcStartAngle(d) {
    return d.startAngle;
  }
  function d3_svg_arcEndAngle(d) {
    return d.endAngle;
  }
  function d3_svg_arcPadAngle(d) {
    return d && d.padAngle;
  }
  function d3_svg_arcSweep(x0, y0, x1, y1) {
    return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
  }
  function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
    var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3;
    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
    return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ];
  }
  function d3_svg_line(projection) {
    var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
    function line(data) {
      var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
      function segment() {
        segments.push("M", interpolate(projection(points), tension));
      }
      while (++i < n) {
        if (defined.call(this, d = data[i], i)) {
          points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
        } else if (points.length) {
          segment();
          points = [];
        }
      }
      if (points.length) segment();
      return segments.length ? segments.join("") : null;
    }
    line.x = function(_) {
      if (!arguments.length) return x;
      x = _;
      return line;
    };
    line.y = function(_) {
      if (!arguments.length) return y;
      y = _;
      return line;
    };
    line.defined = function(_) {
      if (!arguments.length) return defined;
      defined = _;
      return line;
    };
    line.interpolate = function(_) {
      if (!arguments.length) return interpolateKey;
      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
      return line;
    };
    line.tension = function(_) {
      if (!arguments.length) return tension;
      tension = _;
      return line;
    };
    return line;
  }
  d3.svg.line = function() {
    return d3_svg_line(d3_identity);
  };
  var d3_svg_lineInterpolators = d3.map({
    linear: d3_svg_lineLinear,
    "linear-closed": d3_svg_lineLinearClosed,
    step: d3_svg_lineStep,
    "step-before": d3_svg_lineStepBefore,
    "step-after": d3_svg_lineStepAfter,
    basis: d3_svg_lineBasis,
    "basis-open": d3_svg_lineBasisOpen,
    "basis-closed": d3_svg_lineBasisClosed,
    bundle: d3_svg_lineBundle,
    cardinal: d3_svg_lineCardinal,
    "cardinal-open": d3_svg_lineCardinalOpen,
    "cardinal-closed": d3_svg_lineCardinalClosed,
    monotone: d3_svg_lineMonotone
  });
  d3_svg_lineInterpolators.forEach(function(key, value) {
    value.key = key;
    value.closed = /-closed$/.test(key);
  });
  function d3_svg_lineLinear(points) {
    return points.length > 1 ? points.join("L") : points + "Z";
  }
  function d3_svg_lineLinearClosed(points) {
    return points.join("L") + "Z";
  }
  function d3_svg_lineStep(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
    if (n > 1) path.push("H", p[0]);
    return path.join("");
  }
  function d3_svg_lineStepBefore(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
    return path.join("");
  }
  function d3_svg_lineStepAfter(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
    return path.join("");
  }
  function d3_svg_lineCardinalOpen(points, tension) {
    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineCardinalClosed(points, tension) {
    return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
    points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
  }
  function d3_svg_lineCardinal(points, tension) {
    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineHermite(points, tangents) {
    if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
      return d3_svg_lineLinear(points);
    }
    var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
    if (quad) {
      path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
      p0 = points[1];
      pi = 2;
    }
    if (tangents.length > 1) {
      t = tangents[1];
      p = points[pi];
      pi++;
      path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
      for (var i = 2; i < tangents.length; i++, pi++) {
        p = points[pi];
        t = tangents[i];
        path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
      }
    }
    if (quad) {
      var lp = points[pi];
      path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
    }
    return path;
  }
  function d3_svg_lineCardinalTangents(points, tension) {
    var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
    while (++i < n) {
      p0 = p1;
      p1 = p2;
      p2 = points[i];
      tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
    }
    return tangents;
  }
  function d3_svg_lineBasis(points) {
    if (points.length < 3) return d3_svg_lineLinear(points);
    var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
    points.push(points[n - 1]);
    while (++i <= n) {
      pi = points[i];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    points.pop();
    path.push("L", pi);
    return path.join("");
  }
  function d3_svg_lineBasisOpen(points) {
    if (points.length < 4) return d3_svg_lineLinear(points);
    var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
    while (++i < 3) {
      pi = points[i];
      px.push(pi[0]);
      py.push(pi[1]);
    }
    path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
    --i;
    while (++i < n) {
      pi = points[i];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    return path.join("");
  }
  function d3_svg_lineBasisClosed(points) {
    var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
    while (++i < 4) {
      pi = points[i % n];
      px.push(pi[0]);
      py.push(pi[1]);
    }
    path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
    --i;
    while (++i < m) {
      pi = points[i % n];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    return path.join("");
  }
  function d3_svg_lineBundle(points, tension) {
    var n = points.length - 1;
    if (n) {
      var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
      while (++i <= n) {
        p = points[i];
        t = i / n;
        p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
        p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
      }
    }
    return d3_svg_lineBasis(points);
  }
  function d3_svg_lineDot4(a, b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
  }
  var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
  function d3_svg_lineBasisBezier(path, x, y) {
    path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
  }
  function d3_svg_lineSlope(p0, p1) {
    return (p1[1] - p0[1]) / (p1[0] - p0[0]);
  }
  function d3_svg_lineFiniteDifferences(points) {
    var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
    while (++i < j) {
      m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
    }
    m[i] = d;
    return m;
  }
  function d3_svg_lineMonotoneTangents(points) {
    var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
    while (++i < j) {
      d = d3_svg_lineSlope(points[i], points[i + 1]);
      if (abs(d) < ε) {
        m[i] = m[i + 1] = 0;
      } else {
        a = m[i] / d;
        b = m[i + 1] / d;
        s = a * a + b * b;
        if (s > 9) {
          s = d * 3 / Math.sqrt(s);
          m[i] = s * a;
          m[i + 1] = s * b;
        }
      }
    }
    i = -1;
    while (++i <= j) {
      s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
      tangents.push([ s || 0, m[i] * s || 0 ]);
    }
    return tangents;
  }
  function d3_svg_lineMonotone(points) {
    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
  }
  d3.svg.line.radial = function() {
    var line = d3_svg_line(d3_svg_lineRadial);
    line.radius = line.x, delete line.x;
    line.angle = line.y, delete line.y;
    return line;
  };
  function d3_svg_lineRadial(points) {
    var point, i = -1, n = points.length, r, a;
    while (++i < n) {
      point = points[i];
      r = point[0];
      a = point[1] - halfπ;
      point[0] = r * Math.cos(a);
      point[1] = r * Math.sin(a);
    }
    return points;
  }
  function d3_svg_area(projection) {
    var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
    function area(data) {
      var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
        return x;
      } : d3_functor(x1), fy1 = y0 === y1 ? function() {
        return y;
      } : d3_functor(y1), x, y;
      function segment() {
        segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
      }
      while (++i < n) {
        if (defined.call(this, d = data[i], i)) {
          points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
          points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
        } else if (points0.length) {
          segment();
          points0 = [];
          points1 = [];
        }
      }
      if (points0.length) segment();
      return segments.length ? segments.join("") : null;
    }
    area.x = function(_) {
      if (!arguments.length) return x1;
      x0 = x1 = _;
      return area;
    };
    area.x0 = function(_) {
      if (!arguments.length) return x0;
      x0 = _;
      return area;
    };
    area.x1 = function(_) {
      if (!arguments.length) return x1;
      x1 = _;
      return area;
    };
    area.y = function(_) {
      if (!arguments.length) return y1;
      y0 = y1 = _;
      return area;
    };
    area.y0 = function(_) {
      if (!arguments.length) return y0;
      y0 = _;
      return area;
    };
    area.y1 = function(_) {
      if (!arguments.length) return y1;
      y1 = _;
      return area;
    };
    area.defined = function(_) {
      if (!arguments.length) return defined;
      defined = _;
      return area;
    };
    area.interpolate = function(_) {
      if (!arguments.length) return interpolateKey;
      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
      interpolateReverse = interpolate.reverse || interpolate;
      L = interpolate.closed ? "M" : "L";
      return area;
    };
    area.tension = function(_) {
      if (!arguments.length) return tension;
      tension = _;
      return area;
    };
    return area;
  }
  d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
  d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
  d3.svg.area = function() {
    return d3_svg_area(d3_identity);
  };
  d3.svg.area.radial = function() {
    var area = d3_svg_area(d3_svg_lineRadial);
    area.radius = area.x, delete area.x;
    area.innerRadius = area.x0, delete area.x0;
    area.outerRadius = area.x1, delete area.x1;
    area.angle = area.y, delete area.y;
    area.startAngle = area.y0, delete area.y0;
    area.endAngle = area.y1, delete area.y1;
    return area;
  };
  d3.svg.chord = function() {
    var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
    function chord(d, i) {
      var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
      return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
    }
    function subgroup(self, f, d, i) {
      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
      return {
        r: r,
        a0: a0,
        a1: a1,
        p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
        p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
      };
    }
    function equals(a, b) {
      return a.a0 == b.a0 && a.a1 == b.a1;
    }
    function arc(r, p, a) {
      return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
    }
    function curve(r0, p0, r1, p1) {
      return "Q 0,0 " + p1;
    }
    chord.radius = function(v) {
      if (!arguments.length) return radius;
      radius = d3_functor(v);
      return chord;
    };
    chord.source = function(v) {
      if (!arguments.length) return source;
      source = d3_functor(v);
      return chord;
    };
    chord.target = function(v) {
      if (!arguments.length) return target;
      target = d3_functor(v);
      return chord;
    };
    chord.startAngle = function(v) {
      if (!arguments.length) return startAngle;
      startAngle = d3_functor(v);
      return chord;
    };
    chord.endAngle = function(v) {
      if (!arguments.length) return endAngle;
      endAngle = d3_functor(v);
      return chord;
    };
    return chord;
  };
  function d3_svg_chordRadius(d) {
    return d.radius;
  }
  d3.svg.diagonal = function() {
    var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
    function diagonal(d, i) {
      var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
        x: p0.x,
        y: m
      }, {
        x: p3.x,
        y: m
      }, p3 ];
      p = p.map(projection);
      return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
    }
    diagonal.source = function(x) {
      if (!arguments.length) return source;
      source = d3_functor(x);
      return diagonal;
    };
    diagonal.target = function(x) {
      if (!arguments.length) return target;
      target = d3_functor(x);
      return diagonal;
    };
    diagonal.projection = function(x) {
      if (!arguments.length) return projection;
      projection = x;
      return diagonal;
    };
    return diagonal;
  };
  function d3_svg_diagonalProjection(d) {
    return [ d.x, d.y ];
  }
  d3.svg.diagonal.radial = function() {
    var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
    diagonal.projection = function(x) {
      return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
    };
    return diagonal;
  };
  function d3_svg_diagonalRadialProjection(projection) {
    return function() {
      var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ;
      return [ r * Math.cos(a), r * Math.sin(a) ];
    };
  }
  d3.svg.symbol = function() {
    var type = d3_svg_symbolType, size = d3_svg_symbolSize;
    function symbol(d, i) {
      return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
    }
    symbol.type = function(x) {
      if (!arguments.length) return type;
      type = d3_functor(x);
      return symbol;
    };
    symbol.size = function(x) {
      if (!arguments.length) return size;
      size = d3_functor(x);
      return symbol;
    };
    return symbol;
  };
  function d3_svg_symbolSize() {
    return 64;
  }
  function d3_svg_symbolType() {
    return "circle";
  }
  function d3_svg_symbolCircle(size) {
    var r = Math.sqrt(size / π);
    return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
  }
  var d3_svg_symbols = d3.map({
    circle: d3_svg_symbolCircle,
    cross: function(size) {
      var r = Math.sqrt(size / 5) / 2;
      return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
    },
    diamond: function(size) {
      var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
      return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
    },
    square: function(size) {
      var r = Math.sqrt(size) / 2;
      return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
    },
    "triangle-down": function(size) {
      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
      return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
    },
    "triangle-up": function(size) {
      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
      return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
    }
  });
  d3.svg.symbolTypes = d3_svg_symbols.keys();
  var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
  d3_selectionPrototype.transition = function(name) {
    var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || {
      time: Date.now(),
      ease: d3_ease_cubicInOut,
      delay: 0,
      duration: 250
    };
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) d3_transitionNode(node, i, ns, id, transition);
        subgroup.push(node);
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_selectionPrototype.interrupt = function(name) {
    return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
  };
  var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());
  function d3_selection_interruptNS(ns) {
    return function() {
      var lock, activeId, active;
      if ((lock = this[ns]) && (active = lock[activeId = lock.active])) {
        active.timer.c = null;
        active.timer.t = NaN;
        if (--lock.count) delete lock[activeId]; else delete this[ns];
        lock.active += .5;
        active.event && active.event.interrupt.call(this, this.__data__, active.index);
      }
    };
  }
  function d3_transition(groups, ns, id) {
    d3_subclass(groups, d3_transitionPrototype);
    groups.namespace = ns;
    groups.id = id;
    return groups;
  }
  var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
  d3_transitionPrototype.call = d3_selectionPrototype.call;
  d3_transitionPrototype.empty = d3_selectionPrototype.empty;
  d3_transitionPrototype.node = d3_selectionPrototype.node;
  d3_transitionPrototype.size = d3_selectionPrototype.size;
  d3.transition = function(selection, name) {
    return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
  };
  d3.transition.prototype = d3_transitionPrototype;
  d3_transitionPrototype.select = function(selector) {
    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node;
    selector = d3_selection_selector(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
          if ("__data__" in node) subnode.__data__ = node.__data__;
          d3_transitionNode(subnode, i, ns, id, node[ns][id]);
          subgroup.push(subnode);
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_transitionPrototype.selectAll = function(selector) {
    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition;
    selector = d3_selection_selectorAll(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          transition = node[ns][id];
          subnodes = selector.call(node, node.__data__, i, j);
          subgroups.push(subgroup = []);
          for (var k = -1, o = subnodes.length; ++k < o; ) {
            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
            subgroup.push(subnode);
          }
        }
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_transitionPrototype.filter = function(filter) {
    var subgroups = [], subgroup, group, node;
    if (typeof filter !== "function") filter = d3_selection_filter(filter);
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
          subgroup.push(node);
        }
      }
    }
    return d3_transition(subgroups, this.namespace, this.id);
  };
  d3_transitionPrototype.tween = function(name, tween) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 2) return this.node()[ns][id].tween.get(name);
    return d3_selection_each(this, tween == null ? function(node) {
      node[ns][id].tween.remove(name);
    } : function(node) {
      node[ns][id].tween.set(name, tween);
    });
  };
  function d3_transition_tween(groups, name, value, tween) {
    var id = groups.id, ns = groups.namespace;
    return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
      node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
    } : (value = tween(value), function(node) {
      node[ns][id].tween.set(name, value);
    }));
  }
  d3_transitionPrototype.attr = function(nameNS, value) {
    if (arguments.length < 2) {
      for (value in nameNS) this.attr(value, nameNS[value]);
      return this;
    }
    var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
    function attrNull() {
      this.removeAttribute(name);
    }
    function attrNullNS() {
      this.removeAttributeNS(name.space, name.local);
    }
    function attrTween(b) {
      return b == null ? attrNull : (b += "", function() {
        var a = this.getAttribute(name), i;
        return a !== b && (i = interpolate(a, b), function(t) {
          this.setAttribute(name, i(t));
        });
      });
    }
    function attrTweenNS(b) {
      return b == null ? attrNullNS : (b += "", function() {
        var a = this.getAttributeNS(name.space, name.local), i;
        return a !== b && (i = interpolate(a, b), function(t) {
          this.setAttributeNS(name.space, name.local, i(t));
        });
      });
    }
    return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.attrTween = function(nameNS, tween) {
    var name = d3.ns.qualify(nameNS);
    function attrTween(d, i) {
      var f = tween.call(this, d, i, this.getAttribute(name));
      return f && function(t) {
        this.setAttribute(name, f(t));
      };
    }
    function attrTweenNS(d, i) {
      var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
      return f && function(t) {
        this.setAttributeNS(name.space, name.local, f(t));
      };
    }
    return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.style(priority, name[priority], value);
        return this;
      }
      priority = "";
    }
    function styleNull() {
      this.style.removeProperty(name);
    }
    function styleString(b) {
      return b == null ? styleNull : (b += "", function() {
        var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
        return a !== b && (i = d3_interpolate(a, b), function(t) {
          this.style.setProperty(name, i(t), priority);
        });
      });
    }
    return d3_transition_tween(this, "style." + name, value, styleString);
  };
  d3_transitionPrototype.styleTween = function(name, tween, priority) {
    if (arguments.length < 3) priority = "";
    function styleTween(d, i) {
      var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
      return f && function(t) {
        this.style.setProperty(name, f(t), priority);
      };
    }
    return this.tween("style." + name, styleTween);
  };
  d3_transitionPrototype.text = function(value) {
    return d3_transition_tween(this, "text", value, d3_transition_text);
  };
  function d3_transition_text(b) {
    if (b == null) b = "";
    return function() {
      this.textContent = b;
    };
  }
  d3_transitionPrototype.remove = function() {
    var ns = this.namespace;
    return this.each("end.transition", function() {
      var p;
      if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
    });
  };
  d3_transitionPrototype.ease = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].ease;
    if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
    return d3_selection_each(this, function(node) {
      node[ns][id].ease = value;
    });
  };
  d3_transitionPrototype.delay = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].delay;
    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
      node[ns][id].delay = +value.call(node, node.__data__, i, j);
    } : (value = +value, function(node) {
      node[ns][id].delay = value;
    }));
  };
  d3_transitionPrototype.duration = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].duration;
    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
      node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
    } : (value = Math.max(1, value), function(node) {
      node[ns][id].duration = value;
    }));
  };
  d3_transitionPrototype.each = function(type, listener) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 2) {
      var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
      try {
        d3_transitionInheritId = id;
        d3_selection_each(this, function(node, i, j) {
          d3_transitionInherit = node[ns][id];
          type.call(node, node.__data__, i, j);
        });
      } finally {
        d3_transitionInherit = inherit;
        d3_transitionInheritId = inheritId;
      }
    } else {
      d3_selection_each(this, function(node) {
        var transition = node[ns][id];
        (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
      });
    }
    return this;
  };
  d3_transitionPrototype.transition = function() {
    var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition;
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        if (node = group[i]) {
          transition = node[ns][id0];
          d3_transitionNode(node, i, ns, id1, {
            time: transition.time,
            ease: transition.ease,
            delay: transition.delay + transition.duration,
            duration: transition.duration
          });
        }
        subgroup.push(node);
      }
    }
    return d3_transition(subgroups, ns, id1);
  };
  function d3_transitionNamespace(name) {
    return name == null ? "__transition__" : "__transition_" + name + "__";
  }
  function d3_transitionNode(node, i, ns, id, inherit) {
    var lock = node[ns] || (node[ns] = {
      active: 0,
      count: 0
    }), transition = lock[id], time, timer, duration, ease, tweens;
    function schedule(elapsed) {
      var delay = transition.delay;
      timer.t = delay + time;
      if (delay <= elapsed) return start(elapsed - delay);
      timer.c = start;
    }
    function start(elapsed) {
      var activeId = lock.active, active = lock[activeId];
      if (active) {
        active.timer.c = null;
        active.timer.t = NaN;
        --lock.count;
        delete lock[activeId];
        active.event && active.event.interrupt.call(node, node.__data__, active.index);
      }
      for (var cancelId in lock) {
        if (+cancelId < id) {
          var cancel = lock[cancelId];
          cancel.timer.c = null;
          cancel.timer.t = NaN;
          --lock.count;
          delete lock[cancelId];
        }
      }
      timer.c = tick;
      d3_timer(function() {
        if (timer.c && tick(elapsed || 1)) {
          timer.c = null;
          timer.t = NaN;
        }
        return 1;
      }, 0, time);
      lock.active = id;
      transition.event && transition.event.start.call(node, node.__data__, i);
      tweens = [];
      transition.tween.forEach(function(key, value) {
        if (value = value.call(node, node.__data__, i)) {
          tweens.push(value);
        }
      });
      ease = transition.ease;
      duration = transition.duration;
    }
    function tick(elapsed) {
      var t = elapsed / duration, e = ease(t), n = tweens.length;
      while (n > 0) {
        tweens[--n].call(node, e);
      }
      if (t >= 1) {
        transition.event && transition.event.end.call(node, node.__data__, i);
        if (--lock.count) delete lock[id]; else delete node[ns];
        return 1;
      }
    }
    if (!transition) {
      time = inherit.time;
      timer = d3_timer(schedule, 0, time);
      transition = lock[id] = {
        tween: new d3_Map(),
        time: time,
        timer: timer,
        delay: inherit.delay,
        duration: inherit.duration,
        ease: inherit.ease,
        index: i
      };
      inherit = null;
      ++lock.count;
    }
  }
  d3.svg.axis = function() {
    var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_;
    function axis(g) {
      g.each(function() {
        var g = d3.select(this);
        var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
        var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
        var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), 
        d3.transition(path));
        tickEnter.append("line");
        tickEnter.append("text");
        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
        if (orient === "bottom" || orient === "top") {
          tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
          text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
          pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
        } else {
          tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
          text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
          pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
        }
        lineEnter.attr(y2, sign * innerTickSize);
        textEnter.attr(y1, sign * tickSpacing);
        lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
        textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
        if (scale1.rangeBand) {
          var x = scale1, dx = x.rangeBand() / 2;
          scale0 = scale1 = function(d) {
            return x(d) + dx;
          };
        } else if (scale0.rangeBand) {
          scale0 = scale1;
        } else {
          tickExit.call(tickTransform, scale1, scale0);
        }
        tickEnter.call(tickTransform, scale0, scale1);
        tickUpdate.call(tickTransform, scale1, scale1);
      });
    }
    axis.scale = function(x) {
      if (!arguments.length) return scale;
      scale = x;
      return axis;
    };
    axis.orient = function(x) {
      if (!arguments.length) return orient;
      orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
      return axis;
    };
    axis.ticks = function() {
      if (!arguments.length) return tickArguments_;
      tickArguments_ = d3_array(arguments);
      return axis;
    };
    axis.tickValues = function(x) {
      if (!arguments.length) return tickValues;
      tickValues = x;
      return axis;
    };
    axis.tickFormat = function(x) {
      if (!arguments.length) return tickFormat_;
      tickFormat_ = x;
      return axis;
    };
    axis.tickSize = function(x) {
      var n = arguments.length;
      if (!n) return innerTickSize;
      innerTickSize = +x;
      outerTickSize = +arguments[n - 1];
      return axis;
    };
    axis.innerTickSize = function(x) {
      if (!arguments.length) return innerTickSize;
      innerTickSize = +x;
      return axis;
    };
    axis.outerTickSize = function(x) {
      if (!arguments.length) return outerTickSize;
      outerTickSize = +x;
      return axis;
    };
    axis.tickPadding = function(x) {
      if (!arguments.length) return tickPadding;
      tickPadding = +x;
      return axis;
    };
    axis.tickSubdivide = function() {
      return arguments.length && axis;
    };
    return axis;
  };
  var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
    top: 1,
    right: 1,
    bottom: 1,
    left: 1
  };
  function d3_svg_axisX(selection, x0, x1) {
    selection.attr("transform", function(d) {
      var v0 = x0(d);
      return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
    });
  }
  function d3_svg_axisY(selection, y0, y1) {
    selection.attr("transform", function(d) {
      var v0 = y0(d);
      return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
    });
  }
  d3.svg.brush = function() {
    var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0];
    function brush(g) {
      g.each(function() {
        var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
        var background = g.selectAll(".background").data([ 0 ]);
        background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
        g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move");
        var resize = g.selectAll(".resize").data(resizes, d3_identity);
        resize.exit().remove();
        resize.enter().append("g").attr("class", function(d) {
          return "resize " + d;
        }).style("cursor", function(d) {
          return d3_svg_brushCursor[d];
        }).append("rect").attr("x", function(d) {
          return /[ew]$/.test(d) ? -3 : null;
        }).attr("y", function(d) {
          return /^[ns]/.test(d) ? -3 : null;
        }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
        resize.style("display", brush.empty() ? "none" : null);
        var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range;
        if (x) {
          range = d3_scaleRange(x);
          backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
          redrawX(gUpdate);
        }
        if (y) {
          range = d3_scaleRange(y);
          backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
          redrawY(gUpdate);
        }
        redraw(gUpdate);
      });
    }
    brush.event = function(g) {
      g.each(function() {
        var event_ = event.of(this, arguments), extent1 = {
          x: xExtent,
          y: yExtent,
          i: xExtentDomain,
          j: yExtentDomain
        }, extent0 = this.__chart__ || extent1;
        this.__chart__ = extent1;
        if (d3_transitionInheritId) {
          d3.select(this).transition().each("start.brush", function() {
            xExtentDomain = extent0.i;
            yExtentDomain = extent0.j;
            xExtent = extent0.x;
            yExtent = extent0.y;
            event_({
              type: "brushstart"
            });
          }).tween("brush:brush", function() {
            var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y);
            xExtentDomain = yExtentDomain = null;
            return function(t) {
              xExtent = extent1.x = xi(t);
              yExtent = extent1.y = yi(t);
              event_({
                type: "brush",
                mode: "resize"
              });
            };
          }).each("end.brush", function() {
            xExtentDomain = extent1.i;
            yExtentDomain = extent1.j;
            event_({
              type: "brush",
              mode: "resize"
            });
            event_({
              type: "brushend"
            });
          });
        } else {
          event_({
            type: "brushstart"
          });
          event_({
            type: "brush",
            mode: "resize"
          });
          event_({
            type: "brushend"
          });
        }
      });
    };
    function redraw(g) {
      g.selectAll(".resize").attr("transform", function(d) {
        return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
      });
    }
    function redrawX(g) {
      g.select(".extent").attr("x", xExtent[0]);
      g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
    }
    function redrawY(g) {
      g.select(".extent").attr("y", yExtent[0]);
      g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
    }
    function brushstart() {
      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset;
      var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
      if (d3.event.changedTouches) {
        w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
      } else {
        w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
      }
      g.interrupt().selectAll("*").interrupt();
      if (dragging) {
        origin[0] = xExtent[0] - origin[0];
        origin[1] = yExtent[0] - origin[1];
      } else if (resizing) {
        var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
        offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ];
        origin[0] = xExtent[ex];
        origin[1] = yExtent[ey];
      } else if (d3.event.altKey) center = origin.slice();
      g.style("pointer-events", "none").selectAll(".resize").style("display", null);
      d3.select("body").style("cursor", eventTarget.style("cursor"));
      event_({
        type: "brushstart"
      });
      brushmove();
      function keydown() {
        if (d3.event.keyCode == 32) {
          if (!dragging) {
            center = null;
            origin[0] -= xExtent[1];
            origin[1] -= yExtent[1];
            dragging = 2;
          }
          d3_eventPreventDefault();
        }
      }
      function keyup() {
        if (d3.event.keyCode == 32 && dragging == 2) {
          origin[0] += xExtent[1];
          origin[1] += yExtent[1];
          dragging = 0;
          d3_eventPreventDefault();
        }
      }
      function brushmove() {
        var point = d3.mouse(target), moved = false;
        if (offset) {
          point[0] += offset[0];
          point[1] += offset[1];
        }
        if (!dragging) {
          if (d3.event.altKey) {
            if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ];
            origin[0] = xExtent[+(point[0] < center[0])];
            origin[1] = yExtent[+(point[1] < center[1])];
          } else center = null;
        }
        if (resizingX && move1(point, x, 0)) {
          redrawX(g);
          moved = true;
        }
        if (resizingY && move1(point, y, 1)) {
          redrawY(g);
          moved = true;
        }
        if (moved) {
          redraw(g);
          event_({
            type: "brush",
            mode: dragging ? "move" : "resize"
          });
        }
      }
      function move1(point, scale, i) {
        var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max;
        if (dragging) {
          r0 -= position;
          r1 -= size + position;
        }
        min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
        if (dragging) {
          max = (min += position) + size;
        } else {
          if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
          if (position < min) {
            max = min;
            min = position;
          } else {
            max = position;
          }
        }
        if (extent[0] != min || extent[1] != max) {
          if (i) yExtentDomain = null; else xExtentDomain = null;
          extent[0] = min;
          extent[1] = max;
          return true;
        }
      }
      function brushend() {
        brushmove();
        g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
        d3.select("body").style("cursor", null);
        w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
        dragRestore();
        event_({
          type: "brushend"
        });
      }
    }
    brush.x = function(z) {
      if (!arguments.length) return x;
      x = z;
      resizes = d3_svg_brushResizes[!x << 1 | !y];
      return brush;
    };
    brush.y = function(z) {
      if (!arguments.length) return y;
      y = z;
      resizes = d3_svg_brushResizes[!x << 1 | !y];
      return brush;
    };
    brush.clamp = function(z) {
      if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null;
      if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z;
      return brush;
    };
    brush.extent = function(z) {
      var x0, x1, y0, y1, t;
      if (!arguments.length) {
        if (x) {
          if (xExtentDomain) {
            x0 = xExtentDomain[0], x1 = xExtentDomain[1];
          } else {
            x0 = xExtent[0], x1 = xExtent[1];
            if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
            if (x1 < x0) t = x0, x0 = x1, x1 = t;
          }
        }
        if (y) {
          if (yExtentDomain) {
            y0 = yExtentDomain[0], y1 = yExtentDomain[1];
          } else {
            y0 = yExtent[0], y1 = yExtent[1];
            if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
            if (y1 < y0) t = y0, y0 = y1, y1 = t;
          }
        }
        return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
      }
      if (x) {
        x0 = z[0], x1 = z[1];
        if (y) x0 = x0[0], x1 = x1[0];
        xExtentDomain = [ x0, x1 ];
        if (x.invert) x0 = x(x0), x1 = x(x1);
        if (x1 < x0) t = x0, x0 = x1, x1 = t;
        if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ];
      }
      if (y) {
        y0 = z[0], y1 = z[1];
        if (x) y0 = y0[1], y1 = y1[1];
        yExtentDomain = [ y0, y1 ];
        if (y.invert) y0 = y(y0), y1 = y(y1);
        if (y1 < y0) t = y0, y0 = y1, y1 = t;
        if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ];
      }
      return brush;
    };
    brush.clear = function() {
      if (!brush.empty()) {
        xExtent = [ 0, 0 ], yExtent = [ 0, 0 ];
        xExtentDomain = yExtentDomain = null;
      }
      return brush;
    };
    brush.empty = function() {
      return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
    };
    return d3.rebind(brush, event, "on");
  };
  var d3_svg_brushCursor = {
    n: "ns-resize",
    e: "ew-resize",
    s: "ns-resize",
    w: "ew-resize",
    nw: "nwse-resize",
    ne: "nesw-resize",
    se: "nwse-resize",
    sw: "nesw-resize"
  };
  var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
  var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
  var d3_time_formatUtc = d3_time_format.utc;
  var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
  d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
  function d3_time_formatIsoNative(date) {
    return date.toISOString();
  }
  d3_time_formatIsoNative.parse = function(string) {
    var date = new Date(string);
    return isNaN(date) ? null : date;
  };
  d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
  d3_time.second = d3_time_interval(function(date) {
    return new d3_date(Math.floor(date / 1e3) * 1e3);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 1e3);
  }, function(date) {
    return date.getSeconds();
  });
  d3_time.seconds = d3_time.second.range;
  d3_time.seconds.utc = d3_time.second.utc.range;
  d3_time.minute = d3_time_interval(function(date) {
    return new d3_date(Math.floor(date / 6e4) * 6e4);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 6e4);
  }, function(date) {
    return date.getMinutes();
  });
  d3_time.minutes = d3_time.minute.range;
  d3_time.minutes.utc = d3_time.minute.utc.range;
  d3_time.hour = d3_time_interval(function(date) {
    var timezone = date.getTimezoneOffset() / 60;
    return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 36e5);
  }, function(date) {
    return date.getHours();
  });
  d3_time.hours = d3_time.hour.range;
  d3_time.hours.utc = d3_time.hour.utc.range;
  d3_time.month = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setDate(1);
    return date;
  }, function(date, offset) {
    date.setMonth(date.getMonth() + offset);
  }, function(date) {
    return date.getMonth();
  });
  d3_time.months = d3_time.month.range;
  d3_time.months.utc = d3_time.month.utc.range;
  function d3_time_scale(linear, methods, format) {
    function scale(x) {
      return linear(x);
    }
    scale.invert = function(x) {
      return d3_time_scaleDate(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
      linear.domain(x);
      return scale;
    };
    function tickMethod(extent, count) {
      var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target);
      return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) {
        return d / 31536e6;
      }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
    }
    scale.nice = function(interval, skip) {
      var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
      if (method) interval = method[0], skip = method[1];
      function skipped(date) {
        return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
      }
      return scale.domain(d3_scale_nice(domain, skip > 1 ? {
        floor: function(date) {
          while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
          return date;
        },
        ceil: function(date) {
          while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
          return date;
        }
      } : interval));
    };
    scale.ticks = function(interval, skip) {
      var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
        range: interval
      }, skip ];
      if (method) interval = method[0], skip = method[1];
      return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
    };
    scale.tickFormat = function() {
      return format;
    };
    scale.copy = function() {
      return d3_time_scale(linear.copy(), methods, format);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  function d3_time_scaleDate(t) {
    return new Date(t);
  }
  var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
  var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ];
  var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) {
    return d.getMilliseconds();
  } ], [ ":%S", function(d) {
    return d.getSeconds();
  } ], [ "%I:%M", function(d) {
    return d.getMinutes();
  } ], [ "%I %p", function(d) {
    return d.getHours();
  } ], [ "%a %d", function(d) {
    return d.getDay() && d.getDate() != 1;
  } ], [ "%b %d", function(d) {
    return d.getDate() != 1;
  } ], [ "%B", function(d) {
    return d.getMonth();
  } ], [ "%Y", d3_true ] ]);
  var d3_time_scaleMilliseconds = {
    range: function(start, stop, step) {
      return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
    },
    floor: d3_identity,
    ceil: d3_identity
  };
  d3_time_scaleLocalMethods.year = d3_time.year;
  d3_time.scale = function() {
    return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
  };
  var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
    return [ m[0].utc, m[1] ];
  });
  var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) {
    return d.getUTCMilliseconds();
  } ], [ ":%S", function(d) {
    return d.getUTCSeconds();
  } ], [ "%I:%M", function(d) {
    return d.getUTCMinutes();
  } ], [ "%I %p", function(d) {
    return d.getUTCHours();
  } ], [ "%a %d", function(d) {
    return d.getUTCDay() && d.getUTCDate() != 1;
  } ], [ "%b %d", function(d) {
    return d.getUTCDate() != 1;
  } ], [ "%B", function(d) {
    return d.getUTCMonth();
  } ], [ "%Y", d3_true ] ]);
  d3_time_scaleUtcMethods.year = d3_time.year.utc;
  d3_time.scale.utc = function() {
    return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
  };
  d3.text = d3_xhrType(function(request) {
    return request.responseText;
  });
  d3.json = function(url, callback) {
    return d3_xhr(url, "application/json", d3_json, callback);
  };
  function d3_json(request) {
    return JSON.parse(request.responseText);
  }
  d3.html = function(url, callback) {
    return d3_xhr(url, "text/html", d3_html, callback);
  };
  function d3_html(request) {
    var range = d3_document.createRange();
    range.selectNode(d3_document.body);
    return range.createContextualFragment(request.responseText);
  }
  d3.xml = d3_xhrType(function(request) {
    return request.responseXML;
  });
  if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3;
}();
/* nvd3 version 1.8.5 (https://github.com/novus/nvd3) 2016-12-01 */
(function(){

// set up main nv object
var nv = {};

// the major global objects under the nv namespace
nv.dev = false; //set false when in production
nv.tooltip = nv.tooltip || {}; // For the tooltip system
nv.utils = nv.utils || {}; // Utility subsystem
nv.models = nv.models || {}; //stores all the possible models/components
nv.charts = {}; //stores all the ready to use charts
nv.logs = {}; //stores some statistics and potential error messages
nv.dom = {}; //DOM manipulation functions

// Node/CommonJS - require D3
if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && typeof(d3) == 'undefined') {
    d3 = require('d3');
}

nv.dispatch = d3.dispatch('render_start', 'render_end');

// Function bind polyfill
// Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
// https://github.com/ariya/phantomjs/issues/10522
// http://kangax.github.io/compat-table/es5/#Function.prototype.bind
// phantomJS is used for running the test suite
if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function () {},
            fBound = function () {
                return fToBind.apply(this instanceof fNOP && oThis
                        ? this
                        : oThis,
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    };
}

//  Development render timers - disabled if dev = false
if (nv.dev) {
    nv.dispatch.on('render_start', function(e) {
        nv.logs.startTime = +new Date();
    });

    nv.dispatch.on('render_end', function(e) {
        nv.logs.endTime = +new Date();
        nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
        nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
    });
}

// Logs all arguments, and returns the last so you can test things in place
// Note: in IE8 console.log is an object not a function, and if modernizr is used
// then calling Function.prototype.bind with with anything other than a function
// causes a TypeError to be thrown.
nv.log = function() {
    if (nv.dev && window.console && console.log && console.log.apply)
        console.log.apply(console, arguments);
    else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
        var log = Function.prototype.bind.call(console.log, console);
        log.apply(console, arguments);
    }
    return arguments[arguments.length - 1];
};

// print console warning, should be used by deprecated functions
nv.deprecated = function(name, info) {
    if (console && console.warn) {
        console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
    }
};

// The nv.render function is used to queue up chart rendering
// in non-blocking async functions.
// When all queued charts are done rendering, nv.dispatch.render_end is invoked.
nv.render = function render(step) {
    // number of graphs to generate in each timeout loop
    step = step || 1;

    nv.render.active = true;
    nv.dispatch.render_start();

    var renderLoop = function() {
        var chart, graph;

        for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
            chart = graph.generate();
            if (typeof graph.callback == typeof(Function)) graph.callback(chart);
        }

        nv.render.queue.splice(0, i);

        if (nv.render.queue.length) {
            setTimeout(renderLoop);
        }
        else {
            nv.dispatch.render_end();
            nv.render.active = false;
        }
    };

    setTimeout(renderLoop);
};

nv.render.active = false;
nv.render.queue = [];

/*
Adds a chart to the async rendering queue. This method can take arguments in two forms:
nv.addGraph({
    generate: <Function>
    callback: <Function>
})

or

nv.addGraph(<generate Function>, <callback Function>)

The generate function should contain code that creates the NVD3 model, sets options
on it, adds data to an SVG element, and invokes the chart model. The generate function
should return the chart model.  See examples/lineChart.html for a usage example.

The callback function is optional, and it is called when the generate function completes.
*/
nv.addGraph = function(obj) {
    if (typeof arguments[0] === typeof(Function)) {
        obj = {generate: arguments[0], callback: arguments[1]};
    }

    nv.render.queue.push(obj);

    if (!nv.render.active) {
        nv.render();
    }
};

// Node/CommonJS exports
if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
  module.exports = nv;
}

if (typeof(window) !== 'undefined') {
  window.nv = nv;
}
/* Facade for queueing DOM write operations
 * with Fastdom (https://github.com/wilsonpage/fastdom)
 * if available.
 * This could easily be extended to support alternate
 * implementations in the future.
 */
nv.dom.write = function(callback) {
	if (window.fastdom !== undefined) {
		return fastdom.mutate(callback);
	}
	return callback();
};

/* Facade for queueing DOM read operations
 * with Fastdom (https://github.com/wilsonpage/fastdom)
 * if available.
 * This could easily be extended to support alternate
 * implementations in the future.
 */
nv.dom.read = function(callback) {
	if (window.fastdom !== undefined) {
		return fastdom.measure(callback);
	}
	return callback();
};
/* Utility class to handle creation of an interactive layer.
 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
 containing the X-coordinate. It can also render a vertical line where the mouse is located.

 dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
 */
nv.interactiveGuideline = function() {
    "use strict";

    var margin = { left: 0, top: 0 } //Pass the chart's top and left magins. Used to calculate the mouseX/Y.
        ,   width = null
        ,   height = null
        ,   xScale = d3.scale.linear()
        ,   dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick', 'elementMouseDown', 'elementMouseUp')
        ,   showGuideLine = true
        ,   svgContainer = null // Must pass the chart's svg, we'll use its mousemove event.
        ,   tooltip = nv.models.tooltip()
        ,   isMSIE =  window.ActiveXObject// Checkt if IE by looking for activeX. (excludes IE11)
    ;

    tooltip
        .duration(0)
        .hideDelay(0)
        .hidden(false);

    function layer(selection) {
        selection.each(function(data) {
            var container = d3.select(this);
            var availableWidth = (width || 960), availableHeight = (height || 400);
            var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
                .data([data]);
            var wrapEnter = wrap.enter()
                .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
            wrapEnter.append("g").attr("class","nv-interactiveGuideLine");

            if (!svgContainer) {
                return;
            }

            function mouseHandler() {
                var d3mouse = d3.mouse(this);
                var mouseX = d3mouse[0];
                var mouseY = d3mouse[1];
                var subtractMargin = true;
                var mouseOutAnyReason = false;
                if (isMSIE) {
                    /*
                     D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
                     d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
                     over a rect in IE 10.
                     However, d3.event.offsetX/Y also returns the mouse coordinates
                     relative to the triggering <rect>. So we use offsetX/Y on IE.
                     */
                    mouseX = d3.event.offsetX;
                    mouseY = d3.event.offsetY;

                    /*
                     On IE, if you attach a mouse event listener to the <svg> container,
                     it will actually trigger it for all the child elements (like <path>, <circle>, etc).
                     When this happens on IE, the offsetX/Y is set to where ever the child element
                     is located.
                     As a result, we do NOT need to subtract margins to figure out the mouse X/Y
                     position under this scenario. Removing the line below *will* cause
                     the interactive layer to not work right on IE.
                     */
                    if(d3.event.target.tagName !== "svg") {
                        subtractMargin = false;
                    }

                    if (d3.event.target.className.baseVal.match("nv-legend")) {
                        mouseOutAnyReason = true;
                    }

                }

                if(subtractMargin) {
                    mouseX -= margin.left;
                    mouseY -= margin.top;
                }

                /* If mouseX/Y is outside of the chart's bounds,
                 trigger a mouseOut event.
                 */
                if (d3.event.type === 'mouseout'
                    || mouseX < 0 || mouseY < 0
                    || mouseX > availableWidth || mouseY > availableHeight
                    || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
                    || mouseOutAnyReason
                    ) {

                    if (isMSIE) {
                        if (d3.event.relatedTarget
                            && d3.event.relatedTarget.ownerSVGElement === undefined
                            && (d3.event.relatedTarget.className === undefined
                                || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {

                            return;
                        }
                    }
                    dispatch.elementMouseout({
                        mouseX: mouseX,
                        mouseY: mouseY
                    });
                    layer.renderGuideLine(null); //hide the guideline
                    tooltip.hidden(true);
                    return;
                } else {
                    tooltip.hidden(false);
                }


                var scaleIsOrdinal = typeof xScale.rangeBands === 'function';
                var pointXValue = undefined;

                // Ordinal scale has no invert method
                if (scaleIsOrdinal) {
                    var elementIndex = d3.bisect(xScale.range(), mouseX) - 1;
                    // Check if mouseX is in the range band
                    if (xScale.range()[elementIndex] + xScale.rangeBand() >= mouseX) {
                        pointXValue = xScale.domain()[d3.bisect(xScale.range(), mouseX) - 1];
                    }
                    else {
                        dispatch.elementMouseout({
                            mouseX: mouseX,
                            mouseY: mouseY
                        });
                        layer.renderGuideLine(null); //hide the guideline
                        tooltip.hidden(true);
                        return;
                    }
                }
                else {
                    pointXValue = xScale.invert(mouseX);
                }

                dispatch.elementMousemove({
                    mouseX: mouseX,
                    mouseY: mouseY,
                    pointXValue: pointXValue
                });

                //If user double clicks the layer, fire a elementDblclick
                if (d3.event.type === "dblclick") {
                    dispatch.elementDblclick({
                        mouseX: mouseX,
                        mouseY: mouseY,
                        pointXValue: pointXValue
                    });
                }

                // if user single clicks the layer, fire elementClick
                if (d3.event.type === 'click') {
                    dispatch.elementClick({
                        mouseX: mouseX,
                        mouseY: mouseY,
                        pointXValue: pointXValue
                    });
                }

                // if user presses mouse down the layer, fire elementMouseDown
                if (d3.event.type === 'mousedown') {
                	dispatch.elementMouseDown({
                		mouseX: mouseX,
                		mouseY: mouseY,
                		pointXValue: pointXValue
                	});
                }

                // if user presses mouse down the layer, fire elementMouseUp
                if (d3.event.type === 'mouseup') {
                	dispatch.elementMouseUp({
                		mouseX: mouseX,
                		mouseY: mouseY,
                		pointXValue: pointXValue
                	});
                }
            }

            svgContainer
                .on("touchmove",mouseHandler)
                .on("mousemove",mouseHandler, true)
                .on("mouseout" ,mouseHandler,true)
                .on("mousedown" ,mouseHandler,true)
                .on("mouseup" ,mouseHandler,true)
                .on("dblclick" ,mouseHandler)
                .on("click", mouseHandler)
            ;

            layer.guideLine = null;
            //Draws a vertical guideline at the given X postion.
            layer.renderGuideLine = function(x) {
                if (!showGuideLine) return;
                if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
                nv.dom.write(function() {
                    var line = wrap.select(".nv-interactiveGuideLine")
                        .selectAll("line")
                        .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
                    line.enter()
                        .append("line")
                        .attr("class", "nv-guideline")
                        .attr("x1", function(d) { return d;})
                        .attr("x2", function(d) { return d;})
                        .attr("y1", availableHeight)
                        .attr("y2",0);
                    line.exit().remove();
                });
            }
        });
    }

    layer.dispatch = dispatch;
    layer.tooltip = tooltip;

    layer.margin = function(_) {
        if (!arguments.length) return margin;
        margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
        margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
        return layer;
    };

    layer.width = function(_) {
        if (!arguments.length) return width;
        width = _;
        return layer;
    };

    layer.height = function(_) {
        if (!arguments.length) return height;
        height = _;
        return layer;
    };

    layer.xScale = function(_) {
        if (!arguments.length) return xScale;
        xScale = _;
        return layer;
    };

    layer.showGuideLine = function(_) {
        if (!arguments.length) return showGuideLine;
        showGuideLine = _;
        return layer;
    };

    layer.svgContainer = function(_) {
        if (!arguments.length) return svgContainer;
        svgContainer = _;
        return layer;
    };

    return layer;
};

/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.

 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
 because 28 is closer to 30 than 10.

 Unit tests can be found in: interactiveBisectTest.html

 Has the following known issues:
 * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
 * Won't work if there are duplicate x coordinate values.
 */
nv.interactiveBisect = function (values, searchVal, xAccessor) {
    "use strict";
    if (! (values instanceof Array)) {
        return null;
    }
    var _xAccessor;
    if (typeof xAccessor !== 'function') {
        _xAccessor = function(d) {
            return d.x;
        }
    } else {
        _xAccessor = xAccessor;
    }
    var _cmp = function(d, v) {
        // Accessors are no longer passed the index of the element along with
        // the element itself when invoked by d3.bisector.
        //
        // Starting at D3 v3.4.4, d3.bisector() started inspecting the
        // function passed to determine if it should consider it an accessor
        // or a comparator. This meant that accessors that take two arguments
        // (expecting an index as the second parameter) are treated as
        // comparators where the second argument is the search value against
        // which the first argument is compared.
        return _xAccessor(d) - v;
    };

    var bisect = d3.bisector(_cmp).left;
    var index = d3.max([0, bisect(values,searchVal) - 1]);
    var currentValue = _xAccessor(values[index]);

    if (typeof currentValue === 'undefined') {
        currentValue = index;
    }

    if (currentValue === searchVal) {
        return index; //found exact match
    }

    var nextIndex = d3.min([index+1, values.length - 1]);
    var nextValue = _xAccessor(values[nextIndex]);

    if (typeof nextValue === 'undefined') {
        nextValue = nextIndex;
    }

    if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
        return index;
    } else {
        return nextIndex
    }
};

/*
 Returns the index in the array "values" that is closest to searchVal.
 Only returns an index if searchVal is within some "threshold".
 Otherwise, returns null.
 */
nv.nearestValueIndex = function (values, searchVal, threshold) {
    "use strict";
    var yDistMax = Infinity, indexToHighlight = null;
    values.forEach(function(d,i) {
        var delta = Math.abs(searchVal - d);
        if ( d != null && delta <= yDistMax && delta < threshold) {
            yDistMax = delta;
            indexToHighlight = i;
        }
    });
    return indexToHighlight;
};

/* Model which can be instantiated to handle tooltip rendering.
 Example usage:
 var tip = nv.models.tooltip().gravity('w').distance(23)
 .data(myDataObject);

 tip();    //just invoke the returned function to render tooltip.
 */
nv.models.tooltip = function() {
    "use strict";

    /*
    Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
    Example Format of data:
    {
        key: "Date",
        value: "August 2009",
        series: [
            {key: "Series 1", value: "Value 1", color: "#000"},
            {key: "Series 2", value: "Value 2", color: "#00f"}
        ]
    }
    */
    var id = "nvtooltip-" + Math.floor(Math.random() * 100000) // Generates a unique id when you create a new tooltip() object.
        ,   data = null
        ,   gravity = 'w'   // Can be 'n','s','e','w'. Determines how tooltip is positioned.
        ,   distance = 25 // Distance to offset tooltip from the mouse location.
        ,   snapDistance = 0   // Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
        ,   classes = null  // Attaches additional CSS classes to the tooltip DIV that is created.
        ,   hidden = true  // Start off hidden, toggle with hide/show functions below.
        ,   hideDelay = 200  // Delay (in ms) before the tooltip hides after calling hide().
        ,   tooltip = null // d3 select of the tooltip div.
        ,   lastPosition = { left: null, top: null } // Last position the tooltip was in.
        ,   enabled = true  // True -> tooltips are rendered. False -> don't render tooltips.
        ,   duration = 100 // Tooltip movement duration, in ms.
        ,   headerEnabled = true // If is to show the tooltip header.
        ,   nvPointerEventsClass = "nv-pointer-events-none" // CSS class to specify whether element should not have mouse events.
    ;

    // Format function for the tooltip values column.
    var valueFormatter = function(d, i) {
        return d;
    };

    // Format function for the tooltip header value.
    var headerFormatter = function(d) {
        return d;
    };

    var keyFormatter = function(d, i) {
        return d;
    };

    // By default, the tooltip model renders a beautiful table inside a DIV.
    // You can override this function if a custom tooltip is desired.
    var contentGenerator = function(d) {
        if (d === null) {
            return '';
        }

        var table = d3.select(document.createElement("table"));
        if (headerEnabled) {
            var theadEnter = table.selectAll("thead")
                .data([d])
                .enter().append("thead");

            theadEnter.append("tr")
                .append("td")
                .attr("colspan", 3)
                .append("strong")
                .classed("x-value", true)
                .html(headerFormatter(d.value));
        }

        var tbodyEnter = table.selectAll("tbody")
            .data([d])
            .enter().append("tbody");

        var trowEnter = tbodyEnter.selectAll("tr")
                .data(function(p) { return p.series})
                .enter()
                .append("tr")
                .classed("highlight", function(p) { return p.highlight});

        trowEnter.append("td")
            .classed("legend-color-guide",true)
            .append("div")
            .style("background-color", function(p) { return p.color});

        trowEnter.append("td")
            .classed("key",true)
            .classed("total",function(p) { return !!p.total})
            .html(function(p, i) { return keyFormatter(p.key, i)});

        trowEnter.append("td")
            .classed("value",true)
            .html(function(p, i) { return valueFormatter(p.value, i) });

        trowEnter.filter(function (p,i) { return p.percent !== undefined }).append("td")
            .classed("percent", true)
            .html(function(p, i) { return "(" + d3.format('%')(p.percent) + ")" });

        trowEnter.selectAll("td").each(function(p) {
            if (p.highlight) {
                var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
                var opacity = 0.6;
                d3.select(this)
                    .style("border-bottom-color", opacityScale(opacity))
                    .style("border-top-color", opacityScale(opacity))
                ;
            }
        });

        var html = table.node().outerHTML;
        if (d.footer !== undefined)
            html += "<div class='footer'>" + d.footer + "</div>";
        return html;

    };

    /*
     Function that returns the position (relative to the viewport/document.body)
     the tooltip should be placed in.
     Should return: {
        left: <leftPos>,
        top: <topPos>
     }
     */
    var position = function() {
        var pos = {
            left: d3.event !== null ? d3.event.clientX : 0,
            top: d3.event !== null ? d3.event.clientY : 0
        };

        if(getComputedStyle(document.body).transform != 'none') {
            // Take the offset into account, as now the tooltip is relative
            // to document.body.
            var client = document.body.getBoundingClientRect();
            pos.left -= client.left;
            pos.top -= client.top;
        }

        return pos;
    };

    var dataSeriesExists = function(d) {
        if (d && d.series) {
            if (nv.utils.isArray(d.series)) {
                return true;
            }
            // if object, it's okay just convert to array of the object
            if (nv.utils.isObject(d.series)) {
                d.series = [d.series];
                return true;
            }
        }
        return false;
    };

    // Calculates the gravity offset of the tooltip. Parameter is position of tooltip
    // relative to the viewport.
    var calcGravityOffset = function(pos) {
        var height = tooltip.node().offsetHeight,
            width = tooltip.node().offsetWidth,
            clientWidth = document.documentElement.clientWidth, // Don't want scrollbars.
            clientHeight = document.documentElement.clientHeight, // Don't want scrollbars.
            left, top, tmp;

        // calculate position based on gravity
        switch (gravity) {
            case 'e':
                left = - width - distance;
                top = - (height / 2);
                if(pos.left + left < 0) left = distance;
                if((tmp = pos.top + top) < 0) top -= tmp;
                if((tmp = pos.top + top + height) > clientHeight) top -= tmp - clientHeight;
                break;
            case 'w':
                left = distance;
                top = - (height / 2);
                if (pos.left + left + width > clientWidth) left = - width - distance;
                if ((tmp = pos.top + top) < 0) top -= tmp;
                if ((tmp = pos.top + top + height) > clientHeight) top -= tmp - clientHeight;
                break;
            case 'n':
                left = - (width / 2) - 5; // - 5 is an approximation of the mouse's height.
                top = distance;
                if (pos.top + top + height > clientHeight) top = - height - distance;
                if ((tmp = pos.left + left) < 0) left -= tmp;
                if ((tmp = pos.left + left + width) > clientWidth) left -= tmp - clientWidth;
                break;
            case 's':
                left = - (width / 2);
                top = - height - distance;
                if (pos.top + top < 0) top = distance;
                if ((tmp = pos.left + left) < 0) left -= tmp;
                if ((tmp = pos.left + left + width) > clientWidth) left -= tmp - clientWidth;
                break;
            case 'center':
                left = - (width / 2);
                top = - (height / 2);
                break;
            default:
                left = 0;
                top = 0;
                break;
        }

        return { 'left': left, 'top': top };
    };

    /*
     Positions the tooltip in the correct place, as given by the position() function.
     */
    var positionTooltip = function() {
        nv.dom.read(function() {
            var pos = position(),
                gravityOffset = calcGravityOffset(pos),
                left = pos.left + gravityOffset.left,
                top = pos.top + gravityOffset.top;

            // delay hiding a bit to avoid flickering
            if (hidden) {
                tooltip
                    .interrupt()
                    .transition()
                    .delay(hideDelay)
                    .duration(0)
                    .style('opacity', 0);
            } else {
                // using tooltip.style('transform') returns values un-usable for tween
                var old_translate = 'translate(' + lastPosition.left + 'px, ' + lastPosition.top + 'px)';
                var new_translate = 'translate(' + Math.round(left) + 'px, ' + Math.round(top) + 'px)';
                var translateInterpolator = d3.interpolateString(old_translate, new_translate);
                var is_hidden = tooltip.style('opacity') < 0.1;

                tooltip
                    .interrupt() // cancel running transitions
                    .transition()
                    .duration(is_hidden ? 0 : duration)
                    // using tween since some versions of d3 can't auto-tween a translate on a div
                    .styleTween('transform', function (d) {
                        return translateInterpolator;
                    }, 'important')
                    // Safari has its own `-webkit-transform` and does not support `transform`
                    .styleTween('-webkit-transform', function (d) {
                        return translateInterpolator;
                    })
                    .style('-ms-transform', new_translate)
                    .style('opacity', 1);
            }

            lastPosition.left = left;
            lastPosition.top = top;
        });
    };

    // Creates new tooltip container, or uses existing one on DOM.
    function initTooltip() {
        if (!tooltip || !tooltip.node()) {
            // Create new tooltip div if it doesn't exist on DOM.

            var data = [1];
            tooltip = d3.select(document.body).select('#'+id).data(data);

            tooltip.enter().append('div')
                   .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
                   .attr("id", id)
                   .style("top", 0).style("left", 0)
                   .style('opacity', 0)
                   .style('position', 'fixed')
                   .selectAll("div, table, td, tr").classed(nvPointerEventsClass, true)
                   .classed(nvPointerEventsClass, true);

            tooltip.exit().remove()
        }
    }

    // Draw the tooltip onto the DOM.
    function nvtooltip() {
        if (!enabled) return;
        if (!dataSeriesExists(data)) return;

        nv.dom.write(function () {
            initTooltip();
            // Generate data and set it into tooltip.
            // Bonus - If you override contentGenerator and return falsey you can use something like
            //         React or Knockout to bind the data for your tooltip.
            var newContent = contentGenerator(data);
            if (newContent) {
                tooltip.node().innerHTML = newContent;
            }

            positionTooltip();
        });

        return nvtooltip;
    }

    nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
    nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);

    nvtooltip._options = Object.create({}, {
        // simple read/write options
        duration: {get: function(){return duration;}, set: function(_){duration=_;}},
        gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
        distance: {get: function(){return distance;}, set: function(_){distance=_;}},
        snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
        classes: {get: function(){return classes;}, set: function(_){classes=_;}},
        enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
        hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
        contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
        valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
        headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
        keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
        headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
        position: {get: function(){return position;}, set: function(_){position=_;}},

        // Deprecated options
        chartContainer: {get: function(){return document.body;}, set: function(_){
            // deprecated after 1.8.3
            nv.deprecated('chartContainer', 'feature removed after 1.8.3');
        }},
        fixedTop: {get: function(){return null;}, set: function(_){
            // deprecated after 1.8.1
            nv.deprecated('fixedTop', 'feature removed after 1.8.1');
        }},
        offset: {get: function(){return {left: 0, top: 0};}, set: function(_){
            // deprecated after 1.8.1
            nv.deprecated('offset', 'use chart.tooltip.distance() instead');
        }},

        // options with extra logic
        hidden: {get: function(){return hidden;}, set: function(_){
            if (hidden != _) {
                hidden = !!_;
                nvtooltip();
            }
        }},
        data: {get: function(){return data;}, set: function(_){
            // if showing a single data point, adjust data format with that
            if (_.point) {
                _.value = _.point.x;
                _.series = _.series || {};
                _.series.value = _.point.y;
                _.series.color = _.point.color || _.series.color;
            }
            data = _;
        }},

        // read only properties
        node: {get: function(){return tooltip.node();}, set: function(_){}},
        id: {get: function(){return id;}, set: function(_){}}
    });

    nv.utils.initOptions(nvtooltip);
    return nvtooltip;
};


/*
Gets the browser window size

Returns object with height and width properties
 */
nv.utils.windowSize = function() {
    // Sane defaults
    var size = {width: 640, height: 480};

    // Most recent browsers use
    if (window.innerWidth && window.innerHeight) {
        size.width = window.innerWidth;
        size.height = window.innerHeight;
        return (size);
    }

    // IE can use depending on mode it is in
    if (document.compatMode=='CSS1Compat' &&
        document.documentElement &&
        document.documentElement.offsetWidth ) {

        size.width = document.documentElement.offsetWidth;
        size.height = document.documentElement.offsetHeight;
        return (size);
    }

    // Earlier IE uses Doc.body
    if (document.body && document.body.offsetWidth) {
        size.width = document.body.offsetWidth;
        size.height = document.body.offsetHeight;
        return (size);
    }

    return (size);
};


/* handle dumb browser quirks...  isinstance breaks if you use frames
typeof returns 'object' for null, NaN is a number, etc.
 */
nv.utils.isArray = Array.isArray;
nv.utils.isObject = function(a) {
    return a !== null && typeof a === 'object';
};
nv.utils.isFunction = function(a) {
    return typeof a === 'function';
};
nv.utils.isDate = function(a) {
    return toString.call(a) === '[object Date]';
};
nv.utils.isNumber = function(a) {
    return !isNaN(a) && typeof a === 'number';
};


/*
Binds callback function to run when window is resized
 */
nv.utils.windowResize = function(handler) {
    if (window.addEventListener) {
        window.addEventListener('resize', handler);
    } else {
        nv.log("ERROR: Failed to bind to window.resize with: ", handler);
    }
    // return object with clear function to remove the single added callback.
    return {
        callback: handler,
        clear: function() {
            window.removeEventListener('resize', handler);
        }
    }
};


/*
Backwards compatible way to implement more d3-like coloring of graphs.
Can take in nothing, an array, or a function/scale
To use a normal scale, get the range and pass that because we must be able
to take two arguments and use the index to keep backward compatibility
*/
nv.utils.getColor = function(color) {
    //if you pass in nothing, get default colors back
    if (color === undefined) {
        return nv.utils.defaultColor();

    //if passed an array, turn it into a color scale
    } else if(nv.utils.isArray(color)) {
        var color_scale = d3.scale.ordinal().range(color);
        return function(d, i) {
            var key = i === undefined ? d : i;
            return d.color || color_scale(key);
        };

    //if passed a function or scale, return it, or whatever it may be
    //external libs, such as angularjs-nvd3-directives use this
    } else {
        //can't really help it if someone passes rubbish as color
        return color;
    }
};


/*
Default color chooser uses a color scale of 20 colors from D3
 https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
 */
nv.utils.defaultColor = function() {
    // get range of the scale so we'll turn it into our own function.
    return nv.utils.getColor(d3.scale.category20().range());
};


/*
Returns a color function that takes the result of 'getKey' for each series and
looks for a corresponding color from the dictionary
*/
nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
    // use default series.key if getKey is undefined
    getKey = getKey || function(series) { return series.key };
    defaultColors = defaultColors || d3.scale.category20().range();

    // start at end of default color list and walk back to index 0
    var defIndex = defaultColors.length;

    return function(series, index) {
        var key = getKey(series);
        if (nv.utils.isFunction(dictionary[key])) {
            return dictionary[key]();
        } else if (dictionary[key] !== undefined) {
            return dictionary[key];
        } else {
            // no match in dictionary, use a default color
            if (!defIndex) {
                // used all the default colors, start over
                defIndex = defaultColors.length;
            }
            defIndex = defIndex - 1;
            return defaultColors[defIndex];
        }
    };
};


/*
From the PJAX example on d3js.org, while this is not really directly needed
it's a very cool method for doing pjax, I may expand upon it a little bit,
open to suggestions on anything that may be useful
*/
nv.utils.pjax = function(links, content) {

    var load = function(href) {
        d3.html(href, function(fragment) {
            var target = d3.select(content).node();
            target.parentNode.replaceChild(
                d3.select(fragment).select(content).node(),
                target);
            nv.utils.pjax(links, content);
        });
    };

    d3.selectAll(links).on("click", function() {
        history.pushState(this.href, this.textContent, this.href);
        load(this.href);
        d3.event.preventDefault();
    });

    d3.select(window).on("popstate", function() {
        if (d3.event.state) {
            load(d3.event.state);
        }
    });
};


/*
For when we want to approximate the width in pixels for an SVG:text element.
Most common instance is when the element is in a display:none; container.
Forumla is : text.length * font-size * constant_factor
*/
nv.utils.calcApproxTextWidth = function (svgTextElem) {
    if (nv.utils.isFunction(svgTextElem.style) && nv.utils.isFunction(svgTextElem.text)) {
        var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
        var textLength = svgTextElem.text().length;
        return nv.utils.NaNtoZero(textLength * fontSize * 0.5);
    }
    return 0;
};


/*
Numbers that are undefined, null or NaN, convert them to zeros.
*/
nv.utils.NaNtoZero = function(n) {
    if (!nv.utils.isNumber(n)
        || isNaN(n)
        || n === null
        || n === Infinity
        || n === -Infinity) {

        return 0;
    }
    return n;
};

/*
Add a way to watch for d3 transition ends to d3
*/
d3.selection.prototype.watchTransition = function(renderWatch){
    var args = [this].concat([].slice.call(arguments, 1));
    return renderWatch.transition.apply(renderWatch, args);
};


/*
Helper object to watch when d3 has rendered something
*/
nv.utils.renderWatch = function(dispatch, duration) {
    if (!(this instanceof nv.utils.renderWatch)) {
        return new nv.utils.renderWatch(dispatch, duration);
    }

    var _duration = duration !== undefined ? duration : 250;
    var renderStack = [];
    var self = this;

    this.models = function(models) {
        models = [].slice.call(arguments, 0);
        models.forEach(function(model){
            model.__rendered = false;
            (function(m){
                m.dispatch.on('renderEnd', function(arg){
                    m.__rendered = true;
                    self.renderEnd('model');
                });
            })(model);

            if (renderStack.indexOf(model) < 0) {
                renderStack.push(model);
            }
        });
    return this;
    };

    this.reset = function(duration) {
        if (duration !== undefined) {
            _duration = duration;
        }
        renderStack = [];
    };

    this.transition = function(selection, args, duration) {
        args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];

        if (args.length > 1) {
            duration = args.pop();
        } else {
            duration = _duration !== undefined ? _duration : 250;
        }
        selection.__rendered = false;

        if (renderStack.indexOf(selection) < 0) {
            renderStack.push(selection);
        }

        if (duration === 0) {
            selection.__rendered = true;
            selection.delay = function() { return this; };
            selection.duration = function() { return this; };
            return selection;
        } else {
            if (selection.length === 0) {
                selection.__rendered = true;
            } else if (selection.every( function(d){ return !d.length; } )) {
                selection.__rendered = true;
            } else {
                selection.__rendered = false;
            }

            var n = 0;
            return selection
                .transition()
                .duration(duration)
                .each(function(){ ++n; })
                .each('end', function(d, i) {
                    if (--n === 0) {
                        selection.__rendered = true;
                        self.renderEnd.apply(this, args);
                    }
                });
        }
    };

    this.renderEnd = function() {
        if (renderStack.every( function(d){ return d.__rendered; } )) {
            renderStack.forEach( function(d){ d.__rendered = false; });
            dispatch.renderEnd.apply(this, arguments);
        }
    }

};


/*
Takes multiple objects and combines them into the first one (dst)
example:  nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
gives:  {a: 2, b: 3, c: 4}
*/
nv.utils.deepExtend = function(dst){
    var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
    sources.forEach(function(source) {
        for (var key in source) {
            var isArray = nv.utils.isArray(dst[key]);
            var isObject = nv.utils.isObject(dst[key]);
            var srcObj = nv.utils.isObject(source[key]);

            if (isObject && !isArray && srcObj) {
                nv.utils.deepExtend(dst[key], source[key]);
            } else {
                dst[key] = source[key];
            }
        }
    });
};


/*
state utility object, used to track d3 states in the models
*/
nv.utils.state = function(){
    if (!(this instanceof nv.utils.state)) {
        return new nv.utils.state();
    }
    var state = {};
    var _self = this;
    var _setState = function(){};
    var _getState = function(){ return {}; };
    var init = null;
    var changed = null;

    this.dispatch = d3.dispatch('change', 'set');

    this.dispatch.on('set', function(state){
        _setState(state, true);
    });

    this.getter = function(fn){
        _getState = fn;
        return this;
    };

    this.setter = function(fn, callback) {
        if (!callback) {
            callback = function(){};
        }
        _setState = function(state, update){
            fn(state);
            if (update) {
                callback();
            }
        };
        return this;
    };

    this.init = function(state){
        init = init || {};
        nv.utils.deepExtend(init, state);
    };

    var _set = function(){
        var settings = _getState();

        if (JSON.stringify(settings) === JSON.stringify(state)) {
            return false;
        }

        for (var key in settings) {
            if (state[key] === undefined) {
                state[key] = {};
            }
            state[key] = settings[key];
            changed = true;
        }
        return true;
    };

    this.update = function(){
        if (init) {
            _setState(init, false);
            init = null;
        }
        if (_set.call(this)) {
            this.dispatch.change(state);
        }
    };

};


/*
Snippet of code you can insert into each nv.models.* to give you the ability to
do things like:
chart.options({
  showXAxis: true,
  tooltips: true
});

To enable in the chart:
chart.options = nv.utils.optionsFunc.bind(chart);
*/
nv.utils.optionsFunc = function(args) {
    if (args) {
        d3.map(args).forEach((function(key,value) {
            if (nv.utils.isFunction(this[key])) {
                this[key](value);
            }
        }).bind(this));
    }
    return this;
};


/*
numTicks:  requested number of ticks
data:  the chart data

returns the number of ticks to actually use on X axis, based on chart data
to avoid duplicate ticks with the same value
*/
nv.utils.calcTicksX = function(numTicks, data) {
    // find max number of values from all data streams
    var numValues = 1;
    var i = 0;
    for (i; i < data.length; i += 1) {
        var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
        numValues = stream_len > numValues ? stream_len : numValues;
    }
    nv.log("Requested number of ticks: ", numTicks);
    nv.log("Calculated max values to be: ", numValues);
    // make sure we don't have more ticks than values to avoid duplicates
    numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
    // make sure we have at least one tick
    numTicks = numTicks < 1 ? 1 : numTicks;
    // make sure it's an integer
    numTicks = Math.floor(numTicks);
    nv.log("Calculating tick count as: ", numTicks);
    return numTicks;
};


/*
returns number of ticks to actually use on Y axis, based on chart data
*/
nv.utils.calcTicksY = function(numTicks, data) {
    // currently uses the same logic but we can adjust here if needed later
    return nv.utils.calcTicksX(numTicks, data);
};


/*
Add a particular option from an options object onto chart
Options exposed on a chart are a getter/setter function that returns chart
on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');

option objects should be generated via Object.create() to provide
the option of manipulating data via get/set functions.
*/
nv.utils.initOption = function(chart, name) {
    // if it's a call option, just call it directly, otherwise do get/set
    if (chart._calls && chart._calls[name]) {
        chart[name] = chart._calls[name];
    } else {
        chart[name] = function (_) {
            if (!arguments.length) return chart._options[name];
            chart._overrides[name] = true;
            chart._options[name] = _;
            return chart;
        };
        // calling the option as _option will ignore if set by option already
        // so nvd3 can set options internally but the stop if set manually
        chart['_' + name] = function(_) {
            if (!arguments.length) return chart._options[name];
            if (!chart._overrides[name]) {
                chart._options[name] = _;
            }
            return chart;
        }
    }
};


/*
Add all options in an options object to the chart
*/
nv.utils.initOptions = function(chart) {
    chart._overrides = chart._overrides || {};
    var ops = Object.getOwnPropertyNames(chart._options || {});
    var calls = Object.getOwnPropertyNames(chart._calls || {});
    ops = ops.concat(calls);
    for (var i in ops) {
        nv.utils.initOption(chart, ops[i]);
    }
};


/*
Inherit options from a D3 object
d3.rebind makes calling the function on target actually call it on source
Also use _d3options so we can track what we inherit for documentation and chained inheritance
*/
nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
    target._d3options = oplist.concat(target._d3options || []);
    oplist.unshift(d3_source);
    oplist.unshift(target);
    d3.rebind.apply(this, oplist);
};


/*
Remove duplicates from an array
*/
nv.utils.arrayUnique = function(a) {
    return a.sort().filter(function(item, pos) {
        return !pos || item != a[pos - 1];
    });
};


/*
Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
Necessary since d3 doesn't let you extend its list -_-
Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
*/
nv.utils.symbolMap = d3.map();


/*
Replaces d3.svg.symbol so that we can look both there and our own map
 */
nv.utils.symbol = function() {
    var type,
        size = 64;
    function symbol(d,i) {
        var t = type.call(this,d,i);
        var s = size.call(this,d,i);
        if (d3.svg.symbolTypes.indexOf(t) !== -1) {
            return d3.svg.symbol().type(t).size(s)();
        } else {
            return nv.utils.symbolMap.get(t)(s);
        }
    }
    symbol.type = function(_) {
        if (!arguments.length) return type;
        type = d3.functor(_);
        return symbol;
    };
    symbol.size = function(_) {
        if (!arguments.length) return size;
        size = d3.functor(_);
        return symbol;
    };
    return symbol;
};


/*
Inherit option getter/setter functions from source to target
d3.rebind makes calling the function on target actually call it on source
Also track via _inherited and _d3options so we can track what we inherit
for documentation generation purposes and chained inheritance
*/
nv.utils.inheritOptions = function(target, source) {
    // inherit all the things
    var ops = Object.getOwnPropertyNames(source._options || {});
    var calls = Object.getOwnPropertyNames(source._calls || {});
    var inherited = source._inherited || [];
    var d3ops = source._d3options || [];
    var args = ops.concat(calls).concat(inherited).concat(d3ops);
    args.unshift(source);
    args.unshift(target);
    d3.rebind.apply(this, args);
    // pass along the lists to keep track of them, don't allow duplicates
    target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
    target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
};


/*
Runs common initialize code on the svg before the chart builds
*/
nv.utils.initSVG = function(svg) {
    svg.classed({'nvd3-svg':true});
};


/*
Sanitize and provide default for the container height.
*/
nv.utils.sanitizeHeight = function(height, container) {
    return (height || parseInt(container.style('height'), 10) || 400);
};


/*
Sanitize and provide default for the container width.
*/
nv.utils.sanitizeWidth = function(width, container) {
    return (width || parseInt(container.style('width'), 10) || 960);
};


/*
Calculate the available height for a chart.
*/
nv.utils.availableHeight = function(height, container, margin) {
    return Math.max(0,nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom);
};

/*
Calculate the available width for a chart.
*/
nv.utils.availableWidth = function(width, container, margin) {
    return Math.max(0,nv.utils.sanitizeWidth(width, container) - margin.left - margin.right);
};

/*
Clear any rendered chart components and display a chart's 'noData' message
*/
nv.utils.noData = function(chart, container) {
    var opt = chart.options(),
        margin = opt.margin(),
        noData = opt.noData(),
        data = (noData == null) ? ["No Data Available."] : [noData],
        height = nv.utils.availableHeight(null, container, margin),
        width = nv.utils.availableWidth(null, container, margin),
        x = margin.left + width/2,
        y = margin.top + height/2;

    //Remove any previously created chart components
    container.selectAll('g').remove();

    var noDataText = container.selectAll('.nv-noData').data(data);

    noDataText.enter().append('text')
        .attr('class', 'nvd3 nv-noData')
        .attr('dy', '-.7em')
        .style('text-anchor', 'middle');

    noDataText
        .attr('x', x)
        .attr('y', y)
        .text(function(t){ return t; });
};

/*
 Wrap long labels.
 */
nv.utils.wrapTicks = function (text, width) {
    text.each(function() {
        var text = d3.select(this),
            words = text.text().split(/\s+/).reverse(),
            word,
            line = [],
            lineNumber = 0,
            lineHeight = 1.1,
            y = text.attr("y"),
            dy = parseFloat(text.attr("dy")),
            tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
        while (word = words.pop()) {
            line.push(word);
            tspan.text(line.join(" "));
            if (tspan.node().getComputedTextLength() > width) {
                line.pop();
                tspan.text(line.join(" "));
                line = [word];
                tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
            }
        }
    });
};

/*
Check equality of 2 array
*/
nv.utils.arrayEquals = function (array1, array2) {
    if (array1 === array2)
        return true;

    if (!array1 || !array2)
        return false;

    // compare lengths - can save a lot of time
    if (array1.length != array2.length)
        return false;

    for (var i = 0,
        l = array1.length; i < l; i++) {
        // Check if we have nested arrays
        if (array1[i] instanceof Array && array2[i] instanceof Array) {
            // recurse into the nested arrays
            if (!nv.arrayEquals(array1[i], array2[i]))
                return false;
        } else if (array1[i] != array2[i]) {
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;
        }
    }
    return true;
};
nv.models.axis = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var axis = d3.svg.axis();
    var scale = d3.scale.linear();

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 75 //only used for tickLabel currently
        , height = 60 //only used for tickLabel currently
        , axisLabelText = null
        , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
        , rotateLabels = 0
        , rotateYLabel = true
        , staggerLabels = false
        , isOrdinal = false
        , ticks = null
        , axisLabelDistance = 0
        , fontSize = undefined
        , duration = 250
        , dispatch = d3.dispatch('renderEnd')
        ;
    axis
        .scale(scale)
        .orient('bottom')
        .tickFormat(function(d) { return d })
    ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var scale0;
    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var container = d3.select(this);
            nv.utils.initSVG(container);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            if (ticks !== null)
                axis.ticks(ticks);
            else if (axis.orient() == 'top' || axis.orient() == 'bottom')
                axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);

            //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
            g.watchTransition(renderWatch, 'axis').call(axis);

            scale0 = scale0 || axis.scale();

            var fmt = axis.tickFormat();
            if (fmt == null) {
                fmt = scale0.tickFormat();
            }

            var axisLabel = g.selectAll('text.nv-axislabel')
                .data([axisLabelText || null]);
            axisLabel.exit().remove();

            //only skip when fontSize is undefined so it can be cleared with a null or blank string
            if (fontSize !== undefined) {
                g.selectAll('g').select("text").style('font-size', fontSize);
            }

            var xLabelMargin;
            var axisMaxMin;
            var w;
            switch (axis.orient()) {
                case 'top':
                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
                  w = 0;
                  if (scale.range().length === 1) {
                    w = isOrdinal ? scale.range()[0] * 2 + scale.rangeBand() : 0;
                  } else if (scale.range().length === 2) {
                    w = isOrdinal ? scale.range()[0] + scale.range()[1] + scale.rangeBand() : scale.range()[1];
                  } else if ( scale.range().length > 2){
                    w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
                  };
                    axisLabel
                        .attr('text-anchor', 'middle')
                        .attr('y', 0)
                        .attr('x', w/2);
                    if (showMaxMin) {
                        axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
                            .data(scale.domain());
                        axisMaxMin.enter().append('g').attr('class',function(d,i){
                                return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
                        }).append('text');
                        axisMaxMin.exit().remove();
                        axisMaxMin
                            .attr('transform', function(d,i) {
                                return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
                            })
                            .select('text')
                            .attr('dy', '-0.5em')
                            .attr('y', -axis.tickPadding())
                            .attr('text-anchor', 'middle')
                            .text(function(d,i) {
                                var v = fmt(d);
                                return ('' + v).match('NaN') ? '' : v;
                            });
                        axisMaxMin.watchTransition(renderWatch, 'min-max top')
                            .attr('transform', function(d,i) {
                                return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
                            });
                    }
                    break;
                case 'bottom':
                    xLabelMargin = axisLabelDistance + 36;
                    var maxTextWidth = 30;
                    var textHeight = 0;
                    var xTicks = g.selectAll('g').select("text");
                    var rotateLabelsRule = '';
                    if (rotateLabels%360) {
                        //Reset transform on ticks so textHeight can be calculated correctly
                        xTicks.attr('transform', ''); 
                        //Calculate the longest xTick width
                        xTicks.each(function(d,i){
                            var box = this.getBoundingClientRect();
                            var width = box.width;
                            textHeight = box.height;
                            if(width > maxTextWidth) maxTextWidth = width;
                        });
                        rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
                        //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
                        var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
                        xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
                        //Rotate all xTicks
                        xTicks
                            .attr('transform', rotateLabelsRule)
                            .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
                    } else {
                        if (staggerLabels) {
                            xTicks
                                .attr('transform', function(d,i) {
                                    return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
                                });
                        } else {
                            xTicks.attr('transform', "translate(0,0)");
                        }
                    }
                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
                    w = 0;
                    if (scale.range().length === 1) {
                        w = isOrdinal ? scale.range()[0] * 2 + scale.rangeBand() : 0;
                    } else if (scale.range().length === 2) {
                        w = isOrdinal ? scale.range()[0] + scale.range()[1] + scale.rangeBand() : scale.range()[1];
                    } else if ( scale.range().length > 2){
                        w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
                    };
                    axisLabel
                        .attr('text-anchor', 'middle')
                        .attr('y', xLabelMargin)
                        .attr('x', w/2);
                    if (showMaxMin) {
                        //if (showMaxMin && !isOrdinal) {
                        axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
                            //.data(scale.domain())
                            .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
                        axisMaxMin.enter().append('g').attr('class',function(d,i){
                                return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
                        }).append('text');
                        axisMaxMin.exit().remove();
                        axisMaxMin
                            .attr('transform', function(d,i) {
                                return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
                            })
                            .select('text')
                            .attr('dy', '.71em')
                            .attr('y', axis.tickPadding())
                            .attr('transform', rotateLabelsRule)
                            .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
                            .text(function(d,i) {
                                var v = fmt(d);
                                return ('' + v).match('NaN') ? '' : v;
                            });
                        axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
                            .attr('transform', function(d,i) {
                                return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
                            });
                    }

                    break;
                case 'right':
                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
                    axisLabel
                        .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
                        .attr('transform', rotateYLabel ? 'rotate(90)' : '')
                        .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12 - (axisLabelDistance || 0)) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
                        .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
                    if (showMaxMin) {
                        axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
                            .data(scale.domain());
                       	axisMaxMin.enter().append('g').attr('class',function(d,i){
                                return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
                        }).append('text')
                            .style('opacity', 0);
                        axisMaxMin.exit().remove();
                        axisMaxMin
                            .attr('transform', function(d,i) {
                                return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
                            })
                            .select('text')
                            .attr('dy', '.32em')
                            .attr('y', 0)
                            .attr('x', axis.tickPadding())
                            .style('text-anchor', 'start')
                            .text(function(d, i) {
                                var v = fmt(d);
                                return ('' + v).match('NaN') ? '' : v;
                            });
                        axisMaxMin.watchTransition(renderWatch, 'min-max right')
                            .attr('transform', function(d,i) {
                                return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
                            })
                            .select('text')
                            .style('opacity', 1);
                    }
                    break;
                case 'left':
                    /*
                     //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
                     var yTicks = g.selectAll('g').select("text");
                     yTicks.each(function(d,i){
                     var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
                     if(labelPadding > width) width = labelPadding;
                     });
                     */
                    axisLabel.enter().append('text').attr('class', 'nv-axislabel');
                    axisLabel
                        .style('text-anchor', rotateYLabel ? 'middle' : 'end')
                        .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
                        .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
                        .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
                    if (showMaxMin) {
                        axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
                            .data(scale.domain());
                        axisMaxMin.enter().append('g').attr('class',function(d,i){
                                return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
                        }).append('text')
                            .style('opacity', 0);
                        axisMaxMin.exit().remove();
                        axisMaxMin
                            .attr('transform', function(d,i) {
                                return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
                            })
                            .select('text')
                            .attr('dy', '.32em')
                            .attr('y', 0)
                            .attr('x', -axis.tickPadding())
                            .attr('text-anchor', 'end')
                            .text(function(d,i) {
                                var v = fmt(d);
                                return ('' + v).match('NaN') ? '' : v;
                            });
                        axisMaxMin.watchTransition(renderWatch, 'min-max right')
                            .attr('transform', function(d,i) {
                                return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
                            })
                            .select('text')
                            .style('opacity', 1);
                    }
                    break;
            }
            axisLabel.text(function(d) { return d });

            if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
                //check if max and min overlap other values, if so, hide the values that overlap
                g.selectAll('g') // the g's wrapping each tick
                    .each(function(d,i) {
                        d3.select(this).select('text').attr('opacity', 1);
                        if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
                            if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
                                d3.select(this).attr('opacity', 0);

                            d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
                        }
                    });

                //if Max and Min = 0 only show min, Issue #281
                if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
                    wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
                        return !i ? 1 : 0
                    });
                }
            }

            if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
                var maxMinRange = [];
                wrap.selectAll('g.nv-axisMaxMin')
                    .each(function(d,i) {
                        try {
                            if (i) // i== 1, max position
                                maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4);  //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
                            else // i==0, min position
                                maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
                        }catch (err) {
                            if (i) // i== 1, max position
                                maxMinRange.push(scale(d) - 4);  //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
                            else // i==0, min position
                                maxMinRange.push(scale(d) + 4);
                        }
                    });
                // the g's wrapping each tick
                g.selectAll('g').each(function(d, i) {
                    if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
                        if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
                            d3.select(this).remove();
                        else
                            d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
                    }
                });
            }

            //Highlight zero tick line
            g.selectAll('.tick')
                .filter(function (d) {
                    /*
                    The filter needs to return only ticks at or near zero.
                    Numbers like 0.00001 need to count as zero as well,
                    and the arithmetic trick below solves that.
                    */
                    return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
                }) 
                .classed('zero', true);
            
            //store old scales for use in transitions on update
            scale0 = scale.copy();

        });

        renderWatch.renderEnd('axis immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.axis = axis;
    chart.dispatch = dispatch;

    chart.options = nv.utils.optionsFunc.bind(chart);
    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
        staggerLabels:     {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
        rotateLabels:      {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
        rotateYLabel:      {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
        showMaxMin:        {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
        axisLabel:         {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
        height:            {get: function(){return height;}, set: function(_){height=_;}},
        ticks:             {get: function(){return ticks;}, set: function(_){ticks=_;}},
        width:             {get: function(){return width;}, set: function(_){width=_;}},
        fontSize:          {get: function(){return fontSize;}, set: function(_){fontSize=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top !== undefined    ? _.top    : margin.top;
            margin.right  = _.right !== undefined  ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left !== undefined   ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration=_;
            renderWatch.reset(duration);
        }},
        scale: {get: function(){return scale;}, set: function(_){
            scale = _;
            axis.scale(scale);
            isOrdinal = typeof scale.rangeBands === 'function';
            nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
        }}
    });

    nv.utils.initOptions(chart);
    nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
    nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);

    return chart;
};
nv.models.boxPlot = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0},
        width = 960,
        height = 500,
        id = Math.floor(Math.random() * 10000), // Create semi-unique ID in case user doesn't select one
        xScale = d3.scale.ordinal(),
        yScale = d3.scale.linear(),
        getX  = function(d) { return d.label }, // Default data model selectors.
        getQ1 = function(d) { return d.values.Q1 },
        getQ2 = function(d) { return d.values.Q2 },
        getQ3 = function(d) { return d.values.Q3 },
        getWl = function(d) { return d.values.whisker_low },
        getWh = function(d) { return d.values.whisker_high },
        getColor = function(d) { return d.color },
        getOlItems  = function(d) { return d.values.outliers },
        getOlValue = function(d, i, j) { return d },
        getOlLabel = function(d, i, j) { return d },
        getOlColor = function(d, i, j) { return undefined },
        color = nv.utils.defaultColor(),
        container = null,
        xDomain, xRange,
        yDomain, yRange,
        dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd'),
        duration = 250,
        maxBoxWidth = null;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var xScale0, yScale0;
    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);

            // Setup Scales
            xScale.domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
                .rangeBands(xRange || [0, availableWidth], 0.1);

            // if we know yDomain, no need to calculate
            var yData = []
            if (!yDomain) {
                // (y-range is based on quartiles, whiskers and outliers)
                var values = [], yMin, yMax;
                data.forEach(function (d, i) {
                    var q1 = getQ1(d), q3 = getQ3(d), wl = getWl(d), wh = getWh(d);
                    var olItems = getOlItems(d);
                    if (olItems) {
                        olItems.forEach(function (e, i) {
                            values.push(getOlValue(e, i, undefined));
                        });
                    }
                    if (wl) { values.push(wl) }
                    if (q1) { values.push(q1) }
                    if (q3) { values.push(q3) }
                    if (wh) { values.push(wh) }
                });
                yMin = d3.min(values);
                yMax = d3.max(values);
                yData = [ yMin, yMax ] ;
            }

            yScale.domain(yDomain || yData);
            yScale.range(yRange || [availableHeight, 0]);

            //store old scales if they exist
            xScale0 = xScale0 || xScale;
            yScale0 = yScale0 || yScale.copy().range([yScale(0),yScale(0)]);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
            var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
            boxplots
                .attr('class', 'nv-boxplot')
                .attr('transform', function(d,i,j) { return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)'; })
                .classed('hover', function(d) { return d.hover });
            boxplots
                .watchTransition(renderWatch, 'nv-boxplot: boxplots')
                .style('stroke-opacity', 1)
                .style('fill-opacity', 0.75)
                .delay(function(d,i) { return i * duration / data.length })
                .attr('transform', function(d,i) {
                    return 'translate(' + (xScale(getX(d,i)) + xScale.rangeBand() * 0.05) + ', 0)';
                });
            boxplots.exit().remove();

            // ----- add the SVG elements for each boxPlot -----

            // conditionally append whisker lines
            boxEnter.each(function(d,i) {
                var box = d3.select(this);
                [getWl, getWh].forEach(function (f) {
                    if (f(d) !== undefined && f(d) !== null) {
                        var key = (f === getWl) ? 'low' : 'high';
                        box.append('line')
                          .style('stroke', getColor(d) || color(d,i))
                          .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
                        box.append('line')
                          .style('stroke', getColor(d) || color(d,i))
                          .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
                    }
                });
            });

            var box_width = function() { return (maxBoxWidth === null ? xScale.rangeBand() * 0.9 : Math.min(75, xScale.rangeBand() * 0.9)); };
            var box_left  = function() { return xScale.rangeBand() * 0.45 - box_width()/2; };
            var box_right = function() { return xScale.rangeBand() * 0.45 + box_width()/2; };

            // update whisker lines and ticks
            [getWl, getWh].forEach(function (f) {
                var key = (f === getWl) ? 'low' : 'high';
                var endpoint = (f === getWl) ? getQ1 : getQ3;
                boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
                  .watchTransition(renderWatch, 'nv-boxplot: boxplots')
                    .attr('x1', xScale.rangeBand() * 0.45 )
                    .attr('y1', function(d,i) { return yScale(f(d)); })
                    .attr('x2', xScale.rangeBand() * 0.45 )
                    .attr('y2', function(d,i) { return yScale(endpoint(d)); });
                boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
                  .watchTransition(renderWatch, 'nv-boxplot: boxplots')
                    .attr('x1', box_left )
                    .attr('y1', function(d,i) { return yScale(f(d)); })
                    .attr('x2', box_right )
                    .attr('y2', function(d,i) { return yScale(f(d)); });
            });

            [getWl, getWh].forEach(function (f) {
                var key = (f === getWl) ? 'low' : 'high';
                boxEnter.selectAll('.nv-boxplot-' + key)
                  .on('mouseover', function(d,i,j) {
                      d3.select(this).classed('hover', true);
                      dispatch.elementMouseover({
                          series: { key: f(d), color: getColor(d) || color(d,j) },
                          e: d3.event
                      });
                  })
                  .on('mouseout', function(d,i,j) {
                      d3.select(this).classed('hover', false);
                      dispatch.elementMouseout({
                          series: { key: f(d), color: getColor(d) || color(d,j) },
                          e: d3.event
                      });
                  })
                  .on('mousemove', function(d,i) {
                      dispatch.elementMousemove({e: d3.event});
                  });
            });

            // boxes
            boxEnter.append('rect')
                .attr('class', 'nv-boxplot-box')
                // tooltip events
                .on('mouseover', function(d,i) {
                    d3.select(this).classed('hover', true);
                    dispatch.elementMouseover({
                        key: getX(d),
                        value: getX(d),
                        series: [
                            { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
                            { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
                            { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
                        ],
                        data: d,
                        index: i,
                        e: d3.event
                    });
                })
                .on('mouseout', function(d,i) {
                    d3.select(this).classed('hover', false);
                    dispatch.elementMouseout({
                        key: getX(d),
                        value: getX(d),
                        series: [
                            { key: 'Q3', value: getQ3(d), color: getColor(d) || color(d,i) },
                            { key: 'Q2', value: getQ2(d), color: getColor(d) || color(d,i) },
                            { key: 'Q1', value: getQ1(d), color: getColor(d) || color(d,i) }
                        ],
                        data: d,
                        index: i,
                        e: d3.event
                    });
                })
                .on('mousemove', function(d,i) {
                    dispatch.elementMousemove({e: d3.event});
                });

            // box transitions
            boxplots.select('rect.nv-boxplot-box')
              .watchTransition(renderWatch, 'nv-boxplot: boxes')
                .attr('y', function(d,i) { return yScale(getQ3(d)); })
                .attr('width', box_width)
                .attr('x', box_left )
                .attr('height', function(d,i) { return Math.abs(yScale(getQ3(d)) - yScale(getQ1(d))) || 1 })
                .style('fill', function(d,i) { return getColor(d) || color(d,i) })
                .style('stroke', function(d,i) { return getColor(d) || color(d,i) });

            // median line
            boxEnter.append('line').attr('class', 'nv-boxplot-median');

            boxplots.select('line.nv-boxplot-median')
              .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
                .attr('x1', box_left)
                .attr('y1', function(d,i) { return yScale(getQ2(d)); })
                .attr('x2', box_right)
                .attr('y2', function(d,i) { return yScale(getQ2(d)); });

            // outliers
            var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
                return getOlItems(d) || [];
            });
            outliers.enter().append('circle')
                .style('fill', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
                .style('stroke', function(d,i,j) { return getOlColor(d,i,j) || color(d,j) })
                .style('z-index', 9000)
                .on('mouseover', function(d,i,j) {
                    d3.select(this).classed('hover', true);
                    dispatch.elementMouseover({
                        series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
                        e: d3.event
                    });
                })
                .on('mouseout', function(d,i,j) {
                    d3.select(this).classed('hover', false);
                    dispatch.elementMouseout({
                        series: { key: getOlLabel(d,i,j), color: getOlColor(d,i,j) || color(d,j) },
                        e: d3.event
                    });
                })
                .on('mousemove', function(d,i) {
                    dispatch.elementMousemove({e: d3.event});
                });
            outliers.attr('class', 'nv-boxplot-outlier');
            outliers
              .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
                .attr('cx', xScale.rangeBand() * 0.45)
                .attr('cy', function(d,i,j) { return yScale(getOlValue(d,i,j)); })
                .attr('r', '3');
            outliers.exit().remove();

            //store old scales for use in transitions on update
            xScale0 = xScale.copy();
            yScale0 = yScale.copy();
        });

        renderWatch.renderEnd('nv-boxplot immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:       {get: function(){return width;}, set: function(_){width=_;}},
        height:      {get: function(){return height;}, set: function(_){height=_;}},
        maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
        x:           {get: function(){return getX;}, set: function(_){getX=_;}},
        q1: {get: function(){return getQ1;}, set: function(_){getQ1=_;}},
        q2: {get: function(){return getQ2;}, set: function(_){getQ2=_;}},
        q3: {get: function(){return getQ3;}, set: function(_){getQ3=_;}},
        wl: {get: function(){return getWl;}, set: function(_){getWl=_;}},
        wh: {get: function(){return getWh;}, set: function(_){getWh=_;}},
        itemColor:    {get: function(){return getColor;}, set: function(_){getColor=_;}},
        outliers:     {get: function(){return getOlItems;}, set: function(_){getOlItems=_;}},
        outlierValue: {get: function(){return getOlValue;}, set: function(_){getOlValue=_;}},
        outlierLabel: {get: function(){return getOlLabel;}, set: function(_){getOlLabel=_;}},
        outlierColor: {get: function(){return getOlColor;}, set: function(_){getOlColor=_;}},
        xScale:  {get: function(){return xScale;}, set: function(_){xScale=_;}},
        yScale:  {get: function(){return yScale;}, set: function(_){yScale=_;}},
        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
        id:          {get: function(){return id;}, set: function(_){id=_;}},
        // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
        y: {
            get: function() {
                console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
                return {};
            },
            set: function(_) {
                console.warn('BoxPlot \'y\' chart option is deprecated. Please use model overrides instead.');
            }
        },
        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};
nv.models.boxPlotChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var boxplot = nv.models.boxPlot(),
        xAxis = nv.models.axis(),
        yAxis = nv.models.axis();

    var margin = {top: 15, right: 10, bottom: 50, left: 60},
        width = null,
        height = null,
        color = nv.utils.getColor(),
        showXAxis = true,
        showYAxis = true,
        rightAlignYAxis = false,
        staggerLabels = false,
        tooltip = nv.models.tooltip(),
        x, y,
        noData = 'No Data Available.',
        dispatch = d3.dispatch('beforeUpdate', 'renderEnd'),
        duration = 250;

    xAxis
        .orient('bottom')
        .showMaxMin(false)
        .tickFormat(function(d) { return d })
    ;
    yAxis
        .orient((rightAlignYAxis) ? 'right' : 'left')
        .tickFormat(d3.format(',.1f'))
    ;

    tooltip.duration(0);

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(boxplot);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this), that = this;
            nv.utils.initSVG(container);
            var availableWidth = (width  || parseInt(container.style('width')) || 960) - margin.left - margin.right;
            var availableHeight = (height || parseInt(container.style('height')) || 400) - margin.top - margin.bottom;

            chart.update = function() {
                dispatch.beforeUpdate();
                container.transition().duration(duration).call(chart);
            };
            chart.container = this;

            // TODO still need to find a way to validate quartile data presence using boxPlot callbacks.
            // Display No Data message if there's nothing to show. (quartiles required at minimum).
            if (!data || !data.length) {
                var noDataText = container.selectAll('.nv-noData').data([noData]);

                noDataText.enter().append('text')
                    .attr('class', 'nvd3 nv-noData')
                    .attr('dy', '-.7em')
                    .style('text-anchor', 'middle');

                noDataText
                    .attr('x', margin.left + availableWidth / 2)
                    .attr('y', margin.top + availableHeight / 2)
                    .text(function(d) { return d });

                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = boxplot.xScale();
            y = boxplot.yScale().clamp(true);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
            var defsEnter = gEnter.append('defs');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis')
                .append('g').attr('class', 'nv-zeroLine')
                .append('line');

            gEnter.append('g').attr('class', 'nv-barsWrap');
            g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            if (rightAlignYAxis) {
                g.select('.nv-y.nv-axis')
                    .attr('transform', 'translate(' + availableWidth + ',0)');
            }

            // Main Chart Component(s)
            boxplot.width(availableWidth).height(availableHeight);

            var barsWrap = g.select('.nv-barsWrap')
                .datum(data.filter(function(d) { return !d.disabled }))

            barsWrap.transition().call(boxplot);

            defsEnter.append('clipPath')
                .attr('id', 'nv-x-label-clip-' + boxplot.id())
                .append('rect');

            g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
                .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
                .attr('height', 16)
                .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight, 0);

                g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
                g.select('.nv-x.nv-axis').call(xAxis);

                var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
                if (staggerLabels) {
                    xTicks
                        .selectAll('text')
                        .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 === 0 ? '5' : '17') + ')' })
                }
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
                    .tickSize( -availableWidth, 0);

                g.select('.nv-y.nv-axis').call(yAxis);
            }

            // Zero line
            g.select('.nv-zeroLine line')
                .attr('x1',0)
                .attr('x2',availableWidth)
                .attr('y1', y(0))
                .attr('y2', y(0))
            ;

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------
        });

        renderWatch.renderEnd('nv-boxplot chart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
        tooltip.data(evt).hidden(false);
    });

    boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.data(evt).hidden(true);
    });

    boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.boxplot = boxplot;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
        showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        tooltipContent:    {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            boxplot.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            boxplot.color(color);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( (_) ? 'right' : 'left');
        }}
    });

    nv.utils.inheritOptions(chart, boxplot);
    nv.utils.initOptions(chart);

    return chart;
}

// Chart design based on the recommendations of Stephen Few. Implementation
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
// http://projects.instantcognition.com/protovis/bulletchart/

nv.models.bullet = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , orient = 'left' // TODO top & bottom
        , reverse = false
        , ranges = function(d) { return d.ranges }
        , markers = function(d) { return d.markers ? d.markers : [] }
        , markerLines = function(d) { return d.markerLines ? d.markerLines : [0] }
        , measures = function(d) { return d.measures }
        , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
        , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : []  }
        , markerLineLabels = function(d) { return d.markerLineLabels ? d.markerLineLabels : []  }
        , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : []  }
        , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
        , width = 380
        , height = 30
        , container = null
        , tickFormat = null
        , color = nv.utils.getColor(['#1f77b4'])
        , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
        , defaultRangeLabels = ["Maximum", "Mean", "Minimum"]
        , legacyRangeClassNames = ["Max", "Avg", "Min"]
        , duration = 1000
        ;

    function sortLabels(labels, values){
        var lz = labels.slice();
        labels.sort(function(a, b){
            var iA = lz.indexOf(a);
            var iB = lz.indexOf(b);
            return d3.descending(values[iA], values[iB]);
        });
    };

    function chart(selection) {
        selection.each(function(d, i) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);

            var rangez = ranges.call(this, d, i).slice(),
                markerz = markers.call(this, d, i).slice(),
                markerLinez = markerLines.call(this, d, i).slice(),
                measurez = measures.call(this, d, i).slice(),
                rangeLabelz = rangeLabels.call(this, d, i).slice(),
                markerLabelz = markerLabels.call(this, d, i).slice(),
                markerLineLabelz = markerLineLabels.call(this, d, i).slice(),
                measureLabelz = measureLabels.call(this, d, i).slice();

            // Sort labels according to their sorted values
            sortLabels(rangeLabelz, rangez);
            sortLabels(markerLabelz, markerz);
            sortLabels(markerLineLabelz, markerLinez);
            sortLabels(measureLabelz, measurez);

            // sort values descending
            rangez.sort(d3.descending);
            markerz.sort(d3.descending);
            markerLinez.sort(d3.descending);
            measurez.sort(d3.descending);

            // Setup Scales
            // Compute the new x-scale.
            var x1 = d3.scale.linear()
                .domain( d3.extent(d3.merge([forceX, rangez])) )
                .range(reverse ? [availableWidth, 0] : [0, availableWidth]);

            // Retrieve the old x-scale, if this is an update.
            var x0 = this.__chart__ || d3.scale.linear()
                .domain([0, Infinity])
                .range(x1.range());

            // Stash the new scale.
            this.__chart__ = x1;

            var rangeMin = d3.min(rangez), //rangez[2]
                rangeMax = d3.max(rangez), //rangez[0]
                rangeAvg = rangez[1];

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            for(var i=0,il=rangez.length; i<il; i++){
                var rangeClassNames = 'nv-range nv-range'+i;
                if(i <= 2){
                    rangeClassNames = rangeClassNames + ' nv-range'+legacyRangeClassNames[i];
                }
                gEnter.append('rect').attr('class', rangeClassNames);
            }

            gEnter.append('rect').attr('class', 'nv-measure');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
                w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
            var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
                xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };

            for(var i=0,il=rangez.length; i<il; i++){
                var range = rangez[i];
                g.select('rect.nv-range'+i)
                    .datum(range)
                    .attr('height', availableHeight)
                    .transition()
                    .duration(duration)
                    .attr('width', w1(range))
                    .attr('x', xp1(range))
            }

            g.select('rect.nv-measure')
                .style('fill', color)
                .attr('height', availableHeight / 3)
                .attr('y', availableHeight / 3)
                .on('mouseover', function() {
                    dispatch.elementMouseover({
                        value: measurez[0],
                        label: measureLabelz[0] || 'Current',
                        color: d3.select(this).style("fill")
                    })
                })
                .on('mousemove', function() {
                    dispatch.elementMousemove({
                        value: measurez[0],
                        label: measureLabelz[0] || 'Current',
                        color: d3.select(this).style("fill")
                    })
                })
                .on('mouseout', function() {
                    dispatch.elementMouseout({
                        value: measurez[0],
                        label: measureLabelz[0] || 'Current',
                        color: d3.select(this).style("fill")
                    })
                })
                .transition()
                .duration(duration)
                .attr('width', measurez < 0 ?
                    x1(0) - x1(measurez[0])
                    : x1(measurez[0]) - x1(0))
                .attr('x', xp1(measurez));

            var h3 =  availableHeight / 6;

            var markerData = markerz.map( function(marker, index) {
                return {value: marker, label: markerLabelz[index]}
            });
            gEnter
              .selectAll("path.nv-markerTriangle")
              .data(markerData)
              .enter()
              .append('path')
              .attr('class', 'nv-markerTriangle')
              .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
              .on('mouseover', function(d) {
                dispatch.elementMouseover({
                  value: d.value,
                  label: d.label || 'Previous',
                  color: d3.select(this).style("fill"),
                  pos: [x1(d.value), availableHeight/2]
                })

              })
              .on('mousemove', function(d) {
                  dispatch.elementMousemove({
                      value: d.value,
                      label: d.label || 'Previous',
                      color: d3.select(this).style("fill")
                  })
              })
              .on('mouseout', function(d, i) {
                  dispatch.elementMouseout({
                      value: d.value,
                      label: d.label || 'Previous',
                      color: d3.select(this).style("fill")
                  })
              });

            g.selectAll("path.nv-markerTriangle")
              .data(markerData)
              .transition()
              .duration(duration)
              .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' });

            var markerLinesData = markerLinez.map( function(marker, index) {
                return {value: marker, label: markerLineLabelz[index]}
            });
            gEnter
              .selectAll("line.nv-markerLine")
              .data(markerLinesData)
              .enter()
              .append('line')
              .attr('cursor', '')
              .attr('class', 'nv-markerLine')
              .attr('x1', function(d) { return x1(d.value) })
              .attr('y1', '2')
              .attr('x2', function(d) { return x1(d.value) })
              .attr('y2', availableHeight - 2)
              .on('mouseover', function(d) {
                dispatch.elementMouseover({
                  value: d.value,
                  label: d.label || 'Previous',
                  color: d3.select(this).style("fill"),
                  pos: [x1(d.value), availableHeight/2]
                })

              })
              .on('mousemove', function(d) {
                  dispatch.elementMousemove({
                      value: d.value,
                      label: d.label || 'Previous',
                      color: d3.select(this).style("fill")
                  })
              })
              .on('mouseout', function(d, i) {
                  dispatch.elementMouseout({
                      value: d.value,
                      label: d.label || 'Previous',
                      color: d3.select(this).style("fill")
                  })
              });

            g.selectAll("line.nv-markerLine")
              .data(markerLinesData)
              .transition()
              .duration(duration)
              .attr('x1', function(d) { return x1(d.value) })
              .attr('x2', function(d) { return x1(d.value) });

            wrap.selectAll('.nv-range')
                .on('mouseover', function(d,i) {
                    var label = rangeLabelz[i] || defaultRangeLabels[i];
                    dispatch.elementMouseover({
                        value: d,
                        label: label,
                        color: d3.select(this).style("fill")
                    })
                })
                .on('mousemove', function() {
                    dispatch.elementMousemove({
                        value: measurez[0],
                        label: measureLabelz[0] || 'Previous',
                        color: d3.select(this).style("fill")
                    })
                })
                .on('mouseout', function(d,i) {
                    var label = rangeLabelz[i] || defaultRangeLabels[i];
                    dispatch.elementMouseout({
                        value: d,
                        label: label,
                        color: d3.select(this).style("fill")
                    })
                });
        });

        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
        markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
        measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
        forceX:      {get: function(){return forceX;}, set: function(_){forceX=_;}},
        width:    {get: function(){return width;}, set: function(_){width=_;}},
        height:    {get: function(){return height;}, set: function(_){height=_;}},
        tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
        duration:    {get: function(){return duration;}, set: function(_){duration=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
            orient = _;
            reverse = orient == 'right' || orient == 'bottom';
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);
    return chart;
};



// Chart design based on the recommendations of Stephen Few. Implementation
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
// http://projects.instantcognition.com/protovis/bulletchart/
nv.models.bulletChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var bullet = nv.models.bullet();
    var tooltip = nv.models.tooltip();

    var orient = 'left' // TODO top & bottom
        , reverse = false
        , margin = {top: 5, right: 40, bottom: 20, left: 120}
        , ranges = function(d) { return d.ranges }
        , markers = function(d) { return d.markers ? d.markers : [] }
        , measures = function(d) { return d.measures }
        , width = null
        , height = 55
        , tickFormat = null
        , ticks = null
        , noData = null
        , dispatch = d3.dispatch()
        ;

    tooltip
        .duration(0)
        .headerEnabled(false);

    function chart(selection) {
        selection.each(function(d, i) {
            var container = d3.select(this);
            nv.utils.initSVG(container);

            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = height - margin.top - margin.bottom,
                that = this;

            chart.update = function() { chart(selection) };
            chart.container = this;

            // Display No Data message if there's nothing to show.
            if (!d || !ranges.call(this, d, i)) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
                markerz = markers.call(this, d, i).slice().sort(d3.descending),
                measurez = measures.call(this, d, i).slice().sort(d3.descending);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-bulletWrap');
            gEnter.append('g').attr('class', 'nv-titles');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // Compute the new x-scale.
            var x1 = d3.scale.linear()
                .domain([0, Math.max(rangez[0], (markerz[0] || 0), measurez[0])])  // TODO: need to allow forceX and forceY, and xDomain, yDomain
                .range(reverse ? [availableWidth, 0] : [0, availableWidth]);

            // Retrieve the old x-scale, if this is an update.
            var x0 = this.__chart__ || d3.scale.linear()
                .domain([0, Infinity])
                .range(x1.range());

            // Stash the new scale.
            this.__chart__ = x1;

            var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
                w1 = function(d) { return Math.abs(x1(d) - x1(0)) };

            var title = gEnter.select('.nv-titles').append('g')
                .attr('text-anchor', 'end')
                .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
            title.append('text')
                .attr('class', 'nv-title')
                .text(function(d) { return d.title; });

            title.append('text')
                .attr('class', 'nv-subtitle')
                .attr('dy', '1em')
                .text(function(d) { return d.subtitle; });

            bullet
                .width(availableWidth)
                .height(availableHeight);

            var bulletWrap = g.select('.nv-bulletWrap');
            d3.transition(bulletWrap).call(bullet);

            // Compute the tick format.
            var format = tickFormat || x1.tickFormat( availableWidth / 100 );

            // Update the tick groups.
            var tick = g.selectAll('g.nv-tick')
                .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
                    return this.textContent || format(d);
                });

            // Initialize the ticks with the old scale, x0.
            var tickEnter = tick.enter().append('g')
                .attr('class', 'nv-tick')
                .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
                .style('opacity', 1e-6);

            tickEnter.append('line')
                .attr('y1', availableHeight)
                .attr('y2', availableHeight * 7 / 6);

            tickEnter.append('text')
                .attr('text-anchor', 'middle')
                .attr('dy', '1em')
                .attr('y', availableHeight * 7 / 6)
                .text(format);

            // Transition the updating ticks to the new scale, x1.
            var tickUpdate = d3.transition(tick)
                .transition()
                .duration(bullet.duration())
                .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
                .style('opacity', 1);

            tickUpdate.select('line')
                .attr('y1', availableHeight)
                .attr('y2', availableHeight * 7 / 6);

            tickUpdate.select('text')
                .attr('y', availableHeight * 7 / 6);

            // Transition the exiting ticks to the new scale, x1.
            d3.transition(tick.exit())
                .transition()
                .duration(bullet.duration())
                .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
                .style('opacity', 1e-6)
                .remove();
        });

        d3.timer.flush();
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt['series'] = {
            key: evt.label,
            value: evt.value,
            color: evt.color
        };
        tooltip.data(evt).hidden(false);
    });

    bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.bullet = bullet;
    chart.dispatch = dispatch;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        ranges:      {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
        markers:     {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
        measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
        width:    {get: function(){return width;}, set: function(_){width=_;}},
        height:    {get: function(){return height;}, set: function(_){height=_;}},
        tickFormat:    {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
        ticks:    {get: function(){return ticks;}, set: function(_){ticks=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
            orient = _;
            reverse = orient == 'right' || orient == 'bottom';
        }}
    });

    nv.utils.inheritOptions(chart, bullet);
    nv.utils.initOptions(chart);

    return chart;
};



nv.models.candlestickBar = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = null
        , height = null
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container
        , x = d3.scale.linear()
        , y = d3.scale.linear()
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , getOpen = function(d) { return d.open }
        , getClose = function(d) { return d.close }
        , getHigh = function(d) { return d.high }
        , getLow = function(d) { return d.low }
        , forceX = []
        , forceY = []
        , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
        , clipEdge = true
        , color = nv.utils.defaultColor()
        , interactive = false
        , xDomain
        , yDomain
        , xRange
        , yRange
        , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    function chart(selection) {
        selection.each(function(data) {
            container = d3.select(this);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            nv.utils.initSVG(container);

            // Width of the candlestick bars.
            var barWidth = (availableWidth / data[0].values.length) * .45;

            // Setup Scales
            x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));

            if (padData)
                x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
            else
                x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);

            y.domain(yDomain || [
                    d3.min(data[0].values.map(getLow).concat(forceY)),
                    d3.max(data[0].values.map(getHigh).concat(forceY))
                ]
            ).range(yRange || [availableHeight, 0]);

            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
            if (x.domain()[0] === x.domain()[1])
                x.domain()[0] ?
                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
                    : x.domain([-1,1]);

            if (y.domain()[0] === y.domain()[1])
                y.domain()[0] ?
                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
                    : y.domain([-1,1]);

            // Setup containers and skeleton of chart
            var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-ticks');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            container
                .on('click', function(d,i) {
                    dispatch.chartClick({
                        data: d,
                        index: i,
                        pos: d3.event,
                        id: id
                    });
                });

            defsEnter.append('clipPath')
                .attr('id', 'nv-chart-clip-path-' + id)
                .append('rect');

            wrap.select('#nv-chart-clip-path-' + id + ' rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');

            var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
                .data(function(d) { return d });
            ticks.exit().remove();

            var tickGroups = ticks.enter().append('g');

            // The colors are currently controlled by CSS.
            ticks
                .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});

            var lines = tickGroups.append('line')
                .attr('class', 'nv-candlestick-lines')
                .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
                .attr('x1', 0)
                .attr('y1', function(d, i) { return y(getHigh(d, i)); })
                .attr('x2', 0)
                .attr('y2', function(d, i) { return y(getLow(d, i)); });

            var rects = tickGroups.append('rect')
                .attr('class', 'nv-candlestick-rects nv-bars')
                .attr('transform', function(d, i) {
                    return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
                    + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
                    + ')';
                })
                .attr('x', 0)
                .attr('y', 0)
                .attr('width', barWidth)
                .attr('height', function(d, i) {
                    var open = getOpen(d, i);
                    var close = getClose(d, i);
                    return open > close ? y(close) - y(open) : y(open) - y(close);
                });

            ticks.select('.nv-candlestick-lines').transition()
                .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
                .attr('x1', 0)
                .attr('y1', function(d, i) { return y(getHigh(d, i)); })
                .attr('x2', 0)
                .attr('y2', function(d, i) { return y(getLow(d, i)); });

            ticks.select('.nv-candlestick-rects').transition()
                .attr('transform', function(d, i) {
                    return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
                    + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
                    + ')';
                })
                .attr('x', 0)
                .attr('y', 0)
                .attr('width', barWidth)
                .attr('height', function(d, i) {
                    var open = getOpen(d, i);
                    var close = getClose(d, i);
                    return open > close ? y(close) - y(open) : y(open) - y(close);
                });
        });

        return chart;
    }


    //Create methods to allow outside functions to highlight a specific bar.
    chart.highlightPoint = function(pointIndex, isHoverOver) {
        chart.clearHighlights();
        container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
            .classed("hover", isHoverOver)
        ;
    };

    chart.clearHighlights = function() {
        container.select(".nv-candlestickBar .nv-tick.hover")
            .classed("hover", false)
        ;
    };

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:    {get: function(){return width;}, set: function(_){width=_;}},
        height:   {get: function(){return height;}, set: function(_){height=_;}},
        xScale:   {get: function(){return x;}, set: function(_){x=_;}},
        yScale:   {get: function(){return y;}, set: function(_){y=_;}},
        xDomain:  {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain:  {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:   {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:   {get: function(){return yRange;}, set: function(_){yRange=_;}},
        forceX:   {get: function(){return forceX;}, set: function(_){forceX=_;}},
        forceY:   {get: function(){return forceY;}, set: function(_){forceY=_;}},
        padData:  {get: function(){return padData;}, set: function(_){padData=_;}},
        clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
        id:       {get: function(){return id;}, set: function(_){id=_;}},
        interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},

        x:     {get: function(){return getX;}, set: function(_){getX=_;}},
        y:     {get: function(){return getY;}, set: function(_){getY=_;}},
        open:  {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
        close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
        high:  {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
        low:   {get: function(){return getLow;}, set: function(_){getLow=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    != undefined ? _.top    : margin.top;
            margin.right  = _.right  != undefined ? _.right  : margin.right;
            margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   != undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);
    return chart;
};

nv.models.cumulativeLineChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var lines = nv.models.line()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , legend = nv.models.legend()
        , controls = nv.models.legend()
        , interactiveLayer = nv.interactiveGuideline()
        , tooltip = nv.models.tooltip()
        ;

    var margin = {top: 30, right: 30, bottom: 50, left: 60}
        , marginTop = null
        , color = nv.utils.defaultColor()
        , width = null
        , height = null
        , showLegend = true
        , showXAxis = true
        , showYAxis = true
        , rightAlignYAxis = false
        , showControls = true
        , useInteractiveGuideline = false
        , rescaleY = true
        , x //can be accessed via chart.xScale()
        , y //can be accessed via chart.yScale()
        , id = lines.id()
        , state = nv.utils.state()
        , defaultState = null
        , noData = null
        , average = function(d) { return d.average }
        , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
        , transitionDuration = 250
        , duration = 250
        , noErrorCheck = false  //if set to TRUE, will bypass an error check in the indexify function.
        ;

    state.index = 0;
    state.rescaleY = rescaleY;

    xAxis.orient('bottom').tickPadding(7);
    yAxis.orient((rightAlignYAxis) ? 'right' : 'left');

    tooltip.valueFormatter(function(d, i) {
        return yAxis.tickFormat()(d, i);
    }).headerFormatter(function(d, i) {
        return xAxis.tickFormat()(d, i);
    });

    controls.updateState(false);

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var dx = d3.scale.linear()
        , index = {i: 0, x: 0}
        , renderWatch = nv.utils.renderWatch(dispatch, duration)
        ;

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled }),
                index: index.i,
                rescaleY: rescaleY
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.index !== undefined)
                index.i = state.index;
            if (state.rescaleY !== undefined)
                rescaleY = state.rescaleY;
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        }
    };

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(lines);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);
        selection.each(function(data) {
            var container = d3.select(this);
            nv.utils.initSVG(container);
            container.classed('nv-chart-' + id, true);
            var that = this;

            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() {
                if (duration === 0)
                    container.call(chart);
                else
                    container.transition().duration(duration).call(chart)
            };
            chart.container = this;

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disableddisabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            var indexDrag = d3.behavior.drag()
                .on('dragstart', dragStart)
                .on('drag', dragMove)
                .on('dragend', dragEnd);


            function dragStart(d,i) {
                d3.select(chart.container)
                    .style('cursor', 'ew-resize');
            }

            function dragMove(d,i) {
                index.x = d3.event.x;
                index.i = Math.round(dx.invert(index.x));
                updateZero();
            }

            function dragEnd(d,i) {
                d3.select(chart.container)
                    .style('cursor', 'auto');

                // update state and send stateChange with new index
                state.index = index.i;
                dispatch.stateChange(state);
            }

            // Display No Data message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = lines.xScale();
            y = lines.yScale();

            if (!rescaleY) {
                var seriesDomains = data
                    .filter(function(series) { return !series.disabled })
                    .map(function(series,i) {
                        var initialDomain = d3.extent(series.values, lines.y());

                        //account for series being disabled when losing 95% or more
                        if (initialDomain[0] < -.95) initialDomain[0] = -.95;

                        return [
                                (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
                                (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
                        ];
                    });

                var completeDomain = [
                    d3.min(seriesDomains, function(d) { return d[0] }),
                    d3.max(seriesDomains, function(d) { return d[1] })
                ];

                lines.yDomain(completeDomain);
            } else {
                lines.yDomain(null);
            }

            dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
                .range([0, availableWidth])
                .clamp(true);

            var data = indexify(index.i, data);

            // Setup containers and skeleton of chart
            var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
            var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-interactive');
            gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
            gEnter.append('g').attr('class', 'nv-y nv-axis');
            gEnter.append('g').attr('class', 'nv-background');
            gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
            gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
            gEnter.append('g').attr('class', 'nv-legendWrap');
            gEnter.append('g').attr('class', 'nv-controlsWrap');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                legend.width(availableWidth);

                g.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                g.select('.nv-legendWrap')
                    .attr('transform', 'translate(0,' + (-margin.top) +')')
            }

            // Controls
            if (!showControls) {
                 g.select('.nv-controlsWrap').selectAll('*').remove();
            } else {
                var controlsData = [
                    { key: 'Re-scale y-axis', disabled: !rescaleY }
                ];

                controls
                    .width(140)
                    .color(['#444', '#444', '#444'])
                    .rightAlign(false)
                    .margin({top: 5, right: 0, bottom: 5, left: 20})
                ;

                g.select('.nv-controlsWrap')
                    .datum(controlsData)
                    .attr('transform', 'translate(0,' + (-margin.top) +')')
                    .call(controls);
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            // Show error if series goes below 100%
            var tempDisabled = data.filter(function(d) { return d.tempDisabled });

            wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
            if (tempDisabled.length) {
                wrap.append('text').attr('class', 'tempDisabled')
                    .attr('x', availableWidth / 2)
                    .attr('y', '-.71em')
                    .style('text-anchor', 'end')
                    .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
            }

            //Set up interactive layer
            if (useInteractiveGuideline) {
                interactiveLayer
                    .width(availableWidth)
                    .height(availableHeight)
                    .margin({left:margin.left,top:margin.top})
                    .svgContainer(container)
                    .xScale(x);
                wrap.select(".nv-interactive").call(interactiveLayer);
            }

            gEnter.select('.nv-background')
                .append('rect');

            g.select('.nv-background rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            lines
                //.x(function(d) { return d.x })
                .y(function(d) { return d.display.y })
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));

            var linesWrap = g.select('.nv-linesWrap')
                .datum(data.filter(function(d) { return  !d.disabled && !d.tempDisabled }));

            linesWrap.call(lines);

            //Store a series index number in the data array.
            data.forEach(function(d,i) {
                d.seriesIndex = i;
            });

            var avgLineData = data.filter(function(d) {
                return !d.disabled && !!average(d);
            });

            var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
                .data(avgLineData, function(d) { return d.key; });

            var getAvgLineY = function(d) {
                //If average lines go off the svg element, clamp them to the svg bounds.
                var yVal = y(average(d));
                if (yVal < 0) return 0;
                if (yVal > availableHeight) return availableHeight;
                return yVal;
            };

            avgLines.enter()
                .append('line')
                .style('stroke-width',2)
                .style('stroke-dasharray','10,10')
                .style('stroke',function (d,i) {
                    return lines.color()(d,d.seriesIndex);
                })
                .attr('x1',0)
                .attr('x2',availableWidth)
                .attr('y1', getAvgLineY)
                .attr('y2', getAvgLineY);

            avgLines
                .style('stroke-opacity',function(d){
                    //If average lines go offscreen, make them transparent
                    var yVal = y(average(d));
                    if (yVal < 0 || yVal > availableHeight) return 0;
                    return 1;
                })
                .attr('x1',0)
                .attr('x2',availableWidth)
                .attr('y1', getAvgLineY)
                .attr('y2', getAvgLineY);

            avgLines.exit().remove();

            //Create index line
            var indexLine = linesWrap.selectAll('.nv-indexLine')
                .data([index]);
            indexLine.enter().append('rect').attr('class', 'nv-indexLine')
                .attr('width', 3)
                .attr('x', -2)
                .attr('fill', 'red')
                .attr('fill-opacity', .5)
                .style("pointer-events","all")
                .call(indexDrag);

            indexLine
                .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
                .attr('height', availableHeight);

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
                    .tickSize(-availableHeight, 0);

                g.select('.nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
                g.select('.nv-x.nv-axis')
                    .call(xAxis);
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);

                g.select('.nv-y.nv-axis')
                    .call(yAxis);
            }

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            function updateZero() {
                indexLine
                    .data([index]);

                //When dragging the index line, turn off line transitions.
                // Then turn them back on when done dragging.
                var oldDuration = chart.duration();
                chart.duration(0);
                chart.update();
                chart.duration(oldDuration);
            }

            g.select('.nv-background rect')
                .on('click', function() {
                    index.x = d3.mouse(this)[0];
                    index.i = Math.round(dx.invert(index.x));

                    // update state and send stateChange with new index
                    state.index = index.i;
                    dispatch.stateChange(state);

                    updateZero();
                });

            lines.dispatch.on('elementClick', function(e) {
                index.i = e.pointIndex;
                index.x = dx(index.i);

                // update state and send stateChange with new index
                state.index = index.i;
                dispatch.stateChange(state);

                updateZero();
            });

            controls.dispatch.on('legendClick', function(d,i) {
                d.disabled = !d.disabled;
                rescaleY = !d.disabled;

                state.rescaleY = rescaleY;
                dispatch.stateChange(state);
                chart.update();
            });

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            interactiveLayer.dispatch.on('elementMousemove', function(e) {
                lines.clearHighlights();
                var singlePoint, pointIndex, pointXLocation, allData = [];

                data
                    .filter(function(series, i) {
                        series.seriesIndex = i;
                        return !series.disabled;
                    })
                    .forEach(function(series,i) {
                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
                        lines.highlightPoint(i, pointIndex, true);
                        var point = series.values[pointIndex];
                        if (typeof point === 'undefined') return;
                        if (typeof singlePoint === 'undefined') singlePoint = point;
                        if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
                        allData.push({
                            key: series.key,
                            value: chart.y()(point, pointIndex),
                            color: color(series,series.seriesIndex)
                        });
                    });

                //Highlight the tooltip entry based on which point the mouse is closest to.
                if (allData.length > 2) {
                    var yValue = chart.yScale().invert(e.mouseY);
                    var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
                    var threshold = 0.03 * domainExtent;
                    var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
                    if (indexToHighlight !== null)
                        allData[indexToHighlight].highlight = true;
                }

                var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
                interactiveLayer.tooltip
                    .valueFormatter(function(d,i) {
                        return yAxis.tickFormat()(d);
                    })
                    .data(
                    {
                        value: xValue,
                        series: allData
                    }
                )();

                interactiveLayer.renderGuideLine(pointXLocation);
            });

            interactiveLayer.dispatch.on("elementMouseout",function(e) {
                lines.clearHighlights();
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });

                    state.disabled = e.disabled;
                }

                if (typeof e.index !== 'undefined') {
                    index.i = e.index;
                    index.x = dx(index.i);

                    state.index = e.index;

                    indexLine
                        .data([index]);
                }

                if (typeof e.rescaleY !== 'undefined') {
                    rescaleY = e.rescaleY;
                }

                chart.update();
            });

        });

        renderWatch.renderEnd('cumulativeLineChart immediate');

        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    lines.dispatch.on('elementMouseover.tooltip', function(evt) {
        var point = {
            x: chart.x()(evt.point),
            y: chart.y()(evt.point),
            color: evt.point.color
        };
        evt.point = point;
        tooltip.data(evt).hidden(false);
    });

    lines.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true)
    });

    //============================================================
    // Functions
    //------------------------------------------------------------

    var indexifyYGetter = null;
    /* Normalize the data according to an index point. */
    function indexify(idx, data) {
        if (!indexifyYGetter) indexifyYGetter = lines.y();
        return data.map(function(line, i) {
            if (!line.values) {
                return line;
            }
            var indexValue = line.values[idx];
            if (indexValue == null) {
                return line;
            }
            var v = indexifyYGetter(indexValue, idx);

            //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
            if (v < -.95 && !noErrorCheck) {
                //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)

                line.tempDisabled = true;
                return line;
            }

            line.tempDisabled = false;

            line.values = line.values.map(function(point, pointIndex) {
                point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
                return point;
            });

            return line;
        })
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.lines = lines;
    chart.legend = legend;
    chart.controls = controls;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.interactiveLayer = interactiveLayer;
    chart.state = state;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        rescaleY:     {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
        showControls:     {get: function(){return showControls;}, set: function(_){showControls=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        average: {get: function(){return average;}, set: function(_){average=_;}},
        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        showXAxis:    {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        noErrorCheck:    {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
        }},
        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
            useInteractiveGuideline = _;
            if (_ === true) {
                chart.interactive(false);
                chart.useVoronoi(false);
            }
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( (_) ? 'right' : 'left');
        }},
        duration:    {get: function(){return duration;}, set: function(_){
            duration = _;
            lines.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
            renderWatch.reset(duration);
        }}
    });

    nv.utils.inheritOptions(chart, lines);
    nv.utils.initOptions(chart);

    return chart;
};
//TODO: consider deprecating by adding necessary features to multiBar model
nv.models.discreteBar = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 960
        , height = 500
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container
        , x = d3.scale.ordinal()
        , y = d3.scale.linear()
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
        , color = nv.utils.defaultColor()
        , showValues = false
        , valueFormat = d3.format(',.2f')
        , xDomain
        , yDomain
        , xRange
        , yRange
        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
        , rectClass = 'discreteBar'
        , duration = 250
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x0, y0;
    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);

            //add series index to each data point for reference
            data.forEach(function(series, i) {
                series.values.forEach(function(point) {
                    point.series = i;
                });
            });

            // Setup Scales
            // remap and flatten the data for use in calculating the scales' domains
            var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
                data.map(function(d) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
                    })
                });

            x   .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
                .rangeBands(xRange || [0, availableWidth], .1);
            y   .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));

            // If showValues, pad the Y axis range to account for label height
            if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
            else y.range(yRange || [availableHeight, 0]);

            //store old scales if they exist
            x0 = x0 || x;
            y0 = y0 || y.copy().range([y(0),y(0)]);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-groups');
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
                .data(function(d) { return d }, function(d) { return d.key });
            groups.enter().append('g')
                .style('stroke-opacity', 1e-6)
                .style('fill-opacity', 1e-6);
            groups.exit()
                .watchTransition(renderWatch, 'discreteBar: exit groups')
                .style('stroke-opacity', 1e-6)
                .style('fill-opacity', 1e-6)
                .remove();
            groups
                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
                .classed('hover', function(d) { return d.hover });
            groups
                .watchTransition(renderWatch, 'discreteBar: groups')
                .style('stroke-opacity', 1)
                .style('fill-opacity', .75);

            var bars = groups.selectAll('g.nv-bar')
                .data(function(d) { return d.values });
            bars.exit().remove();

            var barsEnter = bars.enter().append('g')
                .attr('transform', function(d,i,j) {
                    return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
                })
                .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
                    d3.select(this).classed('hover', true);
                    dispatch.elementMouseover({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mouseout', function(d,i) {
                    d3.select(this).classed('hover', false);
                    dispatch.elementMouseout({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mousemove', function(d,i) {
                    dispatch.elementMousemove({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('click', function(d,i) {
                    var element = this;
                    dispatch.elementClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill"),
                        event: d3.event,
                        element: element
                    });
                    d3.event.stopPropagation();
                })
                .on('dblclick', function(d,i) {
                    dispatch.elementDblClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                    d3.event.stopPropagation();
                });

            barsEnter.append('rect')
                .attr('height', 0)
                .attr('width', x.rangeBand() * .9 / data.length )

            if (showValues) {
                barsEnter.append('text')
                    .attr('text-anchor', 'middle')
                ;

                bars.select('text')
                    .text(function(d,i) { return valueFormat(getY(d,i)) })
                    .watchTransition(renderWatch, 'discreteBar: bars text')
                    .attr('x', x.rangeBand() * .9 / 2)
                    .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })

                ;
            } else {
                bars.selectAll('text').remove();
            }

            bars
                .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
                .style('fill', function(d,i) { return d.color || color(d,i) })
                .style('stroke', function(d,i) { return d.color || color(d,i) })
                .select('rect')
                .attr('class', rectClass)
                .watchTransition(renderWatch, 'discreteBar: bars rect')
                .attr('width', x.rangeBand() * .9 / data.length);
            bars.watchTransition(renderWatch, 'discreteBar: bars')
                //.delay(function(d,i) { return i * 1200 / data[0].values.length })
                .attr('transform', function(d,i) {
                    var left = x(getX(d,i)) + x.rangeBand() * .05,
                        top = getY(d,i) < 0 ?
                            y(0) :
                                y(0) - y(getY(d,i)) < 1 ?
                            y(0) - 1 : //make 1 px positive bars show up above y=0
                            y(getY(d,i));

                    return 'translate(' + left + ', ' + top + ')'
                })
                .select('rect')
                .attr('height', function(d,i) {
                    return  Math.max(Math.abs(y(getY(d,i)) - y(0)), 1)
                });


            //store old scales for use in transitions on update
            x0 = x.copy();
            y0 = y.copy();

        });

        renderWatch.renderEnd('discreteBar immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:   {get: function(){return width;}, set: function(_){width=_;}},
        height:  {get: function(){return height;}, set: function(_){height=_;}},
        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
        showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
        valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
        id:          {get: function(){return id;}, set: function(_){id=_;}},
        rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};

nv.models.discreteBarChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var discretebar = nv.models.discreteBar()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
	, legend = nv.models.legend()
        , tooltip = nv.models.tooltip()
        ;

    var margin = {top: 15, right: 10, bottom: 50, left: 60}
        , marginTop = null
        , width = null
        , height = null
        , color = nv.utils.getColor()
	, showLegend = false
        , showXAxis = true
        , showYAxis = true
        , rightAlignYAxis = false
        , staggerLabels = false
        , wrapLabels = false
        , rotateLabels = 0
        , x
        , y
        , noData = null
        , dispatch = d3.dispatch('beforeUpdate','renderEnd')
        , duration = 250
        ;

    xAxis
        .orient('bottom')
        .showMaxMin(false)
        .tickFormat(function(d) { return d })
    ;
    yAxis
        .orient((rightAlignYAxis) ? 'right' : 'left')
        .tickFormat(d3.format(',.1f'))
    ;

    tooltip
        .duration(0)
        .headerEnabled(false)
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        })
        .keyFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        });

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(discretebar);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() {
                dispatch.beforeUpdate();
                container.transition().duration(duration).call(chart);
            };
            chart.container = this;

            // Display No Data message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container);
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = discretebar.xScale();
            y = discretebar.yScale().clamp(true);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
            var defsEnter = gEnter.append('defs');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis')
                .append('g').attr('class', 'nv-zeroLine')
                .append('line');

            gEnter.append('g').attr('class', 'nv-barsWrap');
	    gEnter.append('g').attr('class', 'nv-legendWrap');

            g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                legend.width(availableWidth);

                g.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                wrap.select('.nv-legendWrap')
                    .attr('transform', 'translate(0,' + (-margin.top) +')')
            }

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            // Main Chart Component(s)
            discretebar
                .width(availableWidth)
                .height(availableHeight);

            var barsWrap = g.select('.nv-barsWrap')
                .datum(data.filter(function(d) { return !d.disabled }));

            barsWrap.transition().call(discretebar);


            defsEnter.append('clipPath')
                .attr('id', 'nv-x-label-clip-' + discretebar.id())
                .append('rect');

            g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
                .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
                .attr('height', 16)
                .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight, 0);

                g.select('.nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
                g.select('.nv-x.nv-axis').call(xAxis);

                var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
                if (staggerLabels) {
                    xTicks
                        .selectAll('text')
                        .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
                }

                if (rotateLabels) {
                    xTicks
                        .selectAll('.tick text')
                        .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
                        .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
                }

                if (wrapLabels) {
                    g.selectAll('.tick text')
                        .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
                }
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);

                g.select('.nv-y.nv-axis').call(yAxis);
            }

            // Zero line
            g.select(".nv-zeroLine line")
                .attr("x1",0)
                .attr("x2",(rightAlignYAxis) ? -availableWidth : availableWidth)
                .attr("y1", y(0))
                .attr("y2", y(0))
            ;
        });

        renderWatch.renderEnd('discreteBar chart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt['series'] = {
            key: chart.x()(evt.data),
            value: chart.y()(evt.data),
            color: evt.color
        };
        tooltip.data(evt).hidden(false);
    });

    discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.discretebar = discretebar;
    chart.legend = legend;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
	showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
        rotateLabels:  {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
        wrapLabels:  {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},
        showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            discretebar.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            discretebar.color(color);
	    legend.color(color);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( (_) ? 'right' : 'left');
        }}
    });

    nv.utils.inheritOptions(chart, discretebar);
    nv.utils.initOptions(chart);

    return chart;
}

nv.models.distribution = function() {
    "use strict";
    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 400 //technically width or height depending on x or y....
        , size = 8
        , axis = 'x' // 'x' or 'y'... horizontal or vertical
        , getData = function(d) { return d[axis] }  // defaults d.x or d.y
        , color = nv.utils.defaultColor()
        , scale = d3.scale.linear()
        , domain
        , duration = 250
        , dispatch = d3.dispatch('renderEnd')
        ;

    //============================================================


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var scale0;
    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    //============================================================


    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
                naxis = axis == 'x' ? 'y' : 'x',
                container = d3.select(this);
            nv.utils.initSVG(container);

            //------------------------------------------------------------
            // Setup Scales

            scale0 = scale0 || scale;

            //------------------------------------------------------------


            //------------------------------------------------------------
            // Setup containers and skeleton of chart

            var wrap = container.selectAll('g.nv-distribution').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

            //------------------------------------------------------------


            var distWrap = g.selectAll('g.nv-dist')
                .data(function(d) { return d }, function(d) { return d.key });

            distWrap.enter().append('g');
            distWrap
                .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
                .style('stroke', function(d,i) { return color(d, i) });

            var dist = distWrap.selectAll('line.nv-dist' + axis)
                .data(function(d) { return d.values })
            dist.enter().append('line')
                .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
                .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
            renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
                // .transition()
                .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
                .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
                .style('stroke-opacity', 0)
                .remove();
            dist
                .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
                .attr(naxis + '1', 0)
                .attr(naxis + '2', size);
            renderWatch.transition(dist, 'dist')
                // .transition()
                .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
                .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })


            scale0 = scale.copy();

        });
        renderWatch.renderEnd('distribution immediate');
        return chart;
    }


    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------
    chart.options = nv.utils.optionsFunc.bind(chart);
    chart.dispatch = dispatch;

    chart.margin = function(_) {
        if (!arguments.length) return margin;
        margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
        margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
        margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
        margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
        return chart;
    };

    chart.width = function(_) {
        if (!arguments.length) return width;
        width = _;
        return chart;
    };

    chart.axis = function(_) {
        if (!arguments.length) return axis;
        axis = _;
        return chart;
    };

    chart.size = function(_) {
        if (!arguments.length) return size;
        size = _;
        return chart;
    };

    chart.getData = function(_) {
        if (!arguments.length) return getData;
        getData = d3.functor(_);
        return chart;
    };

    chart.scale = function(_) {
        if (!arguments.length) return scale;
        scale = _;
        return chart;
    };

    chart.color = function(_) {
        if (!arguments.length) return color;
        color = nv.utils.getColor(_);
        return chart;
    };

    chart.duration = function(_) {
        if (!arguments.length) return duration;
        duration = _;
        renderWatch.reset(duration);
        return chart;
    };
    //============================================================


    return chart;
}
nv.models.focus = function(content) {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var content = content || nv.models.line()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , brush = d3.svg.brush()
        ;

    var margin = {top: 10, right: 0, bottom: 30, left: 0}
        , color = nv.utils.defaultColor()
        , width = null
        , height = 70
        , showXAxis = true
        , showYAxis = false
        , rightAlignYAxis = false
        , ticks = null
        , x
        , y
        , brushExtent = null
        , duration = 250
        , dispatch = d3.dispatch('brush', 'onBrush', 'renderEnd')
        , syncBrushing = true
        ;

    content.interactive(false);
    content.pointActive(function(d) { return false; });

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(content);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this);
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = height - margin.top - margin.bottom;

            chart.update = function() { 
                if( duration === 0 ) {
                    container.call( chart );
                } else {
                    container.transition().duration(duration).call(chart);
                }
            };
            chart.container = this;

            // Setup Scales
            x = content.xScale();
            y = content.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-focus').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-focus').append('g');
            var g = wrap.select('g');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            gEnter.append('g').attr('class', 'nv-background').append('rect');
            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis');
            gEnter.append('g').attr('class', 'nv-contentWrap');
            gEnter.append('g').attr('class', 'nv-brushBackground');
            gEnter.append('g').attr('class', 'nv-x nv-brush');

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            g.select('.nv-background rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);
                
            content
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled; }));

            var contentWrap = g.select('.nv-contentWrap')
                .datum(data.filter(function(d) { return !d.disabled; }));

            d3.transition(contentWrap).call(content);
            
            // Setup Brush
            brush
                .x(x)
                .on('brush', function() {
                    onBrush(syncBrushing);
                });

            brush.on('brushend', function () {
                if (!syncBrushing) {
                    dispatch.onBrush(brush.empty() ? x.domain() : brush.extent());
                }
            });

            if (brushExtent) brush.extent(brushExtent);

            var brushBG = g.select('.nv-brushBackground').selectAll('g')
                .data([brushExtent || brush.extent()]);
    
            var brushBGenter = brushBG.enter()
                .append('g');

            brushBGenter.append('rect')
                .attr('class', 'left')
                .attr('x', 0)
                .attr('y', 0)
                .attr('height', availableHeight);

            brushBGenter.append('rect')
                .attr('class', 'right')
                .attr('x', 0)
                .attr('y', 0)
                .attr('height', availableHeight);

            var gBrush = g.select('.nv-x.nv-brush')
                .call(brush);
            gBrush.selectAll('rect')
                .attr('height', availableHeight);
            gBrush.selectAll('.resize').append('path').attr('d', resizePath);

            onBrush(true);

            g.select('.nv-background rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            if (showXAxis) {
                xAxis.scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight, 0);
  
                g.select('.nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
                d3.transition(g.select('.nv-x.nv-axis'))
                    .call(xAxis);
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);

                d3.transition(g.select('.nv-y.nv-axis'))
                    .call(yAxis);
            }
            
            g.select('.nv-x.nv-axis')
                .attr('transform', 'translate(0,' + y.range()[0] + ')');

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            //============================================================
            // Functions
            //------------------------------------------------------------
    
            // Taken from crossfilter (http://square.github.com/crossfilter/)
            function resizePath(d) {
                var e = +(d == 'e'),
                    x = e ? 1 : -1,
                    y = availableHeight / 3;
                return 'M' + (0.5 * x) + ',' + y
                    + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
                    + 'V' + (2 * y - 6)
                    + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
                    + 'Z'
                    + 'M' + (2.5 * x) + ',' + (y + 8)
                    + 'V' + (2 * y - 8)
                    + 'M' + (4.5 * x) + ',' + (y + 8)
                    + 'V' + (2 * y - 8);
            }
    
    
            function updateBrushBG() {
                if (!brush.empty()) brush.extent(brushExtent);
                brushBG
                    .data([brush.empty() ? x.domain() : brushExtent])
                    .each(function(d,i) {
                        var leftWidth = x(d[0]) - x.range()[0],
                            rightWidth = availableWidth - x(d[1]);
                        d3.select(this).select('.left')
                            .attr('width',  leftWidth < 0 ? 0 : leftWidth);
    
                        d3.select(this).select('.right')
                            .attr('x', x(d[1]))
                            .attr('width', rightWidth < 0 ? 0 : rightWidth);
                    });
            }


            function onBrush(shouldDispatch) {
                brushExtent = brush.empty() ? null : brush.extent();
                var extent = brush.empty() ? x.domain() : brush.extent();
                dispatch.brush({extent: extent, brush: brush});
                updateBrushBG();
                if (shouldDispatch) {
                    dispatch.onBrush(extent);
                }
            }
        });

        renderWatch.renderEnd('focus immediate');
        return chart;
    }


    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.content = content;
    chart.brush = brush;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
        syncBrushing: {get: function(){return syncBrushing;}, set: function(_){syncBrushing=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            content.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            content.color(color);
        }},
        interpolate: {get: function(){return content.interpolate();}, set: function(_){
            content.interpolate(_);
        }},
        xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
            xAxis.tickFormat(_);
        }},
        yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
            yAxis.tickFormat(_);
        }},
        x: {get: function(){return content.x();}, set: function(_){
            content.x(_);
        }},
        y: {get: function(){return content.y();}, set: function(_){
            content.y(_);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
        }}
    });

    nv.utils.inheritOptions(chart, content);
    nv.utils.initOptions(chart);

    return chart;
};
nv.models.forceDirectedGraph = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------
    var margin = {top: 2, right: 0, bottom: 2, left: 0}
        , width = 400
        , height = 32
        , container = null
        , dispatch = d3.dispatch('renderEnd')
        , color = nv.utils.getColor(['#000'])
        , tooltip      = nv.models.tooltip()
        , noData = null
        // Force directed graph specific parameters [default values]
        , linkStrength = 0.1
        , friction = 0.9
        , linkDist = 30
        , charge = -120
        , gravity = 0.1
        , theta = 0.8
        , alpha = 0.1
        , radius = 5
        // These functions allow to add extra attributes to ndes and links
        ,nodeExtras = function(nodes) { /* Do nothing */ }
        ,linkExtras = function(links) { /* Do nothing */ }
        ;


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);

    function chart(selection) {
        renderWatch.reset();

        selection.each(function(data) {
          container = d3.select(this);
          nv.utils.initSVG(container);

          var availableWidth = nv.utils.availableWidth(width, container, margin),
              availableHeight = nv.utils.availableHeight(height, container, margin);

          container
                  .attr("width", availableWidth)
                  .attr("height", availableHeight);

          // Display No Data message if there's nothing to show.
          if (!data || !data.links || !data.nodes) {
              nv.utils.noData(chart, container)
              return chart;
          } else {
              container.selectAll('.nv-noData').remove();
          }
          container.selectAll('*').remove();

          // Collect names of all fields in the nodes
          var nodeFieldSet = new Set();
          data.nodes.forEach(function(node) {
            var keys = Object.keys(node);
            keys.forEach(function(key) {
              nodeFieldSet.add(key);
            });
          });

          var force = d3.layout.force()
                .nodes(data.nodes)
                .links(data.links)
                .size([availableWidth, availableHeight])
                .linkStrength(linkStrength)
                .friction(friction)
                .linkDistance(linkDist)
                .charge(charge)
                .gravity(gravity)
                .theta(theta)
                .alpha(alpha)
                .start();

          var link = container.selectAll(".link")
                .data(data.links)
                .enter().append("line")
                .attr("class", "nv-force-link")
                .style("stroke-width", function(d) { return Math.sqrt(d.value); });

          var node = container.selectAll(".node")
                .data(data.nodes)
                .enter()
                .append("g")
                .attr("class", "nv-force-node")
                .call(force.drag);

          node
            .append("circle")
            .attr("r", radius)
            .style("fill", function(d) { return color(d) } )
            .on("mouseover", function(evt) {
              container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
                  .attr('y1', evt.py);
              container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
                  .attr('x2', evt.px);

              // Add 'series' object to
              var nodeColor = color(evt);
              evt.series = [];
              nodeFieldSet.forEach(function(field) {
                evt.series.push({
                  color: nodeColor,
                  key:   field,
                  value: evt[field]
                });
              });
              tooltip.data(evt).hidden(false);
            })
            .on("mouseout",  function(d) {
              tooltip.hidden(true);
            });

          tooltip.headerFormatter(function(d) {return "Node";});

          // Apply extra attributes to nodes and links (if any)
          linkExtras(link);
          nodeExtras(node);

          force.on("tick", function() {
              link.attr("x1", function(d) { return d.source.x; })
                  .attr("y1", function(d) { return d.source.y; })
                  .attr("x2", function(d) { return d.target.x; })
                  .attr("y2", function(d) { return d.target.y; });

              node.attr("transform", function(d) {
                return "translate(" + d.x + ", " + d.y + ")";
              });
            });
        });

        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:     {get: function(){return width;}, set: function(_){width=_;}},
        height:    {get: function(){return height;}, set: function(_){height=_;}},

        // Force directed graph specific parameters
        linkStrength:{get: function(){return linkStrength;}, set: function(_){linkStrength=_;}},
        friction:    {get: function(){return friction;}, set: function(_){friction=_;}},
        linkDist:    {get: function(){return linkDist;}, set: function(_){linkDist=_;}},
        charge:      {get: function(){return charge;}, set: function(_){charge=_;}},
        gravity:     {get: function(){return gravity;}, set: function(_){gravity=_;}},
        theta:       {get: function(){return theta;}, set: function(_){theta=_;}},
        alpha:       {get: function(){return alpha;}, set: function(_){alpha=_;}},
        radius:      {get: function(){return radius;}, set: function(_){radius=_;}},

        //functor options
        x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
        y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        nodeExtras: {get: function(){return nodeExtras;}, set: function(_){
            nodeExtras = _;
        }},
        linkExtras: {get: function(){return linkExtras;}, set: function(_){
            linkExtras = _;
        }}
    });

    chart.dispatch = dispatch;
    chart.tooltip = tooltip;
    nv.utils.initOptions(chart);
    return chart;
};
nv.models.furiousLegend = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 5, right: 0, bottom: 5, left: 0}
        , width = 400
        , height = 20
        , getKey = function(d) { return d.key }
        , keyFormatter = function (d) { return d }
        , color = nv.utils.getColor()
        , maxKeyLength = 20 //default value for key lengths
        , align = true
        , padding = 28 //define how much space between legend items. - recommend 32 for furious version
        , rightAlign = true
        , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
        , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
        , expanded = false
        , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
        , vers = 'classic' //Options are "classic" and "furious"
        ;

    function chart(selection) {
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                container = d3.select(this);
            nv.utils.initSVG(container);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-legend').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
            var g = wrap.select('g');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            var series = g.selectAll('.nv-series')
                .data(function(d) {
                    if(vers != 'furious') return d;

                    return d.filter(function(n) {
                        return expanded ? true : !n.disengaged;
                    });
                });
            var seriesEnter = series.enter().append('g').attr('class', 'nv-series')

            var seriesShape;

            if(vers == 'classic') {
                seriesEnter.append('circle')
                    .style('stroke-width', 2)
                    .attr('class','nv-legend-symbol')
                    .attr('r', 5);

                seriesShape = series.select('circle');
            } else if (vers == 'furious') {
                seriesEnter.append('rect')
                    .style('stroke-width', 2)
                    .attr('class','nv-legend-symbol')
                    .attr('rx', 3)
                    .attr('ry', 3);

                seriesShape = series.select('rect');

                seriesEnter.append('g')
                    .attr('class', 'nv-check-box')
                    .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
                    .attr('transform', 'translate(-10,-8)scale(0.5)');

                var seriesCheckbox = series.select('.nv-check-box');

                seriesCheckbox.each(function(d,i) {
                    d3.select(this).selectAll('path')
                        .attr('stroke', setTextColor(d,i));
                });
            }

            seriesEnter.append('text')
                .attr('text-anchor', 'start')
                .attr('class','nv-legend-text')
                .attr('dy', '.32em')
                .attr('dx', '8');

            var seriesText = series.select('text.nv-legend-text');

            series
                .on('mouseover', function(d,i) {
                    dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
                })
                .on('mouseout', function(d,i) {
                    dispatch.legendMouseout(d,i);
                })
                .on('click', function(d,i) {
                    dispatch.legendClick(d,i);
                    // make sure we re-get data in case it was modified
                    var data = series.data();
                    if (updateState) {
                        if(vers =='classic') {
                            if (radioButtonMode) {
                                //Radio button mode: set every series to disabled,
                                //  and enable the clicked series.
                                data.forEach(function(series) { series.disabled = true});
                                d.disabled = false;
                            }
                            else {
                                d.disabled = !d.disabled;
                                if (data.every(function(series) { return series.disabled})) {
                                    //the default behavior of NVD3 legends is, if every single series
                                    // is disabled, turn all series' back on.
                                    data.forEach(function(series) { series.disabled = false});
                                }
                            }
                        } else if(vers == 'furious') {
                            if(expanded) {
                                d.disengaged = !d.disengaged;
                                d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
                                d.disabled = d.disengaged || d.userDisabled;
                            } else if (!expanded) {
                                d.disabled = !d.disabled;
                                d.userDisabled = d.disabled;
                                var engaged = data.filter(function(d) { return !d.disengaged; });
                                if (engaged.every(function(series) { return series.userDisabled })) {
                                    //the default behavior of NVD3 legends is, if every single series
                                    // is disabled, turn all series' back on.
                                    data.forEach(function(series) {
                                        series.disabled = series.userDisabled = false;
                                    });
                                }
                            }
                        }
                        dispatch.stateChange({
                            disabled: data.map(function(d) { return !!d.disabled }),
                            disengaged: data.map(function(d) { return !!d.disengaged })
                        });

                    }
                })
                .on('dblclick', function(d,i) {
                    if(vers == 'furious' && expanded) return;
                    dispatch.legendDblclick(d,i);
                    if (updateState) {
                        // make sure we re-get data in case it was modified
                        var data = series.data();
                        //the default behavior of NVD3 legends, when double clicking one,
                        // is to set all other series' to false, and make the double clicked series enabled.
                        data.forEach(function(series) {
                            series.disabled = true;
                            if(vers == 'furious') series.userDisabled = series.disabled;
                        });
                        d.disabled = false;
                        if(vers == 'furious') d.userDisabled = d.disabled;
                        dispatch.stateChange({
                            disabled: data.map(function(d) { return !!d.disabled })
                        });
                    }
                });

            series.classed('nv-disabled', function(d) { return d.userDisabled });
            series.exit().remove();

            seriesText
                .attr('fill', setTextColor)
                .text(function (d) { return keyFormatter(getKey(d)) });

            //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
            // NEW ALIGNING CODE, TODO: clean up

            var versPadding;
            switch(vers) {
                case 'furious' :
                    versPadding = 23;
                    break;
                case 'classic' :
                    versPadding = 20;
            }

            if (align) {

                var seriesWidths = [];
                series.each(function(d,i) {
                    var legendText;
                    if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
                        var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
                        legendText = d3.select(this).select('text').text(trimmedKey + "...");
                        d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
                    } else {
                        legendText = d3.select(this).select('text');
                    }
                    var nodeTextLength;
                    try {
                        nodeTextLength = legendText.node().getComputedTextLength();
                        // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
                        if(nodeTextLength <= 0) throw Error();
                    }
                    catch(e) {
                        nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
                    }

                    seriesWidths.push(nodeTextLength + padding);
                });

                var seriesPerRow = 0;
                var legendWidth = 0;
                var columnWidths = [];

                while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
                    columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
                    legendWidth += seriesWidths[seriesPerRow++];
                }
                if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row

                while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
                    columnWidths = [];
                    seriesPerRow--;

                    for (var k = 0; k < seriesWidths.length; k++) {
                        if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
                            columnWidths[k % seriesPerRow] = seriesWidths[k];
                    }

                    legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
                        return prev + cur;
                    });
                }

                var xPositions = [];
                for (var i = 0, curX = 0; i < seriesPerRow; i++) {
                    xPositions[i] = curX;
                    curX += columnWidths[i];
                }

                series
                    .attr('transform', function(d, i) {
                        return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
                    });

                //position legend as far right as possible within the total width
                if (rightAlign) {
                    g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
                }
                else {
                    g.attr('transform', 'translate(0' + ',' + margin.top + ')');
                }

                height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);

            } else {

                var ypos = 5,
                    newxpos = 5,
                    maxwidth = 0,
                    xpos;
                series
                    .attr('transform', function(d, i) {
                        var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
                        xpos = newxpos;

                        if (width < margin.left + margin.right + xpos + length) {
                            newxpos = xpos = 5;
                            ypos += versPadding;
                        }

                        newxpos += length;
                        if (newxpos > maxwidth) maxwidth = newxpos;

                        return 'translate(' + xpos + ',' + ypos + ')';
                    });

                //position legend as far right as possible within the total width
                g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');

                height = margin.top + margin.bottom + ypos + 15;
            }

            if(vers == 'furious') {
                // Size rectangles after text is placed
                seriesShape
                    .attr('width', function(d,i) {
                        return seriesText[0][i].getComputedTextLength() + 27;
                    })
                    .attr('height', 18)
                    .attr('y', -9)
                    .attr('x', -15)
            }

            seriesShape
                .style('fill', setBGColor)
                .style('stroke', function(d,i) { return d.color || color(d, i) });
        });

        function setTextColor(d,i) {
            if(vers != 'furious') return '#000';
            if(expanded) {
                return d.disengaged ? color(d,i) : '#fff';
            } else if (!expanded) {
                return !!d.disabled ? color(d,i) : '#fff';
            }
        }

        function setBGColor(d,i) {
            if(expanded && vers == 'furious') {
                return d.disengaged ? '#fff' : color(d,i);
            } else {
                return !!d.disabled ? '#fff' : color(d,i);
            }
        }

        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:          {get: function(){return width;}, set: function(_){width=_;}},
        height:         {get: function(){return height;}, set: function(_){height=_;}},
        key:            {get: function(){return getKey;}, set: function(_){getKey=_;}},
        keyFormatter:   {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
        align:          {get: function(){return align;}, set: function(_){align=_;}},
        rightAlign:     {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
        maxKeyLength:   {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
        padding:        {get: function(){return padding;}, set: function(_){padding=_;}},
        updateState:    {get: function(){return updateState;}, set: function(_){updateState=_;}},
        radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
        expanded:       {get: function(){return expanded;}, set: function(_){expanded=_;}},
        vers:           {get: function(){return vers;}, set: function(_){vers=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};
//TODO: consider deprecating and using multibar with single series for this
nv.models.historicalBar = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = null
        , height = null
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container = null
        , x = d3.scale.linear()
        , y = d3.scale.linear()
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , forceX = []
        , forceY = [0]
        , padData = false
        , clipEdge = true
        , color = nv.utils.defaultColor()
        , xDomain
        , yDomain
        , xRange
        , yRange
        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
        , interactive = true
        ;

    var renderWatch = nv.utils.renderWatch(dispatch, 0);

    function chart(selection) {
        selection.each(function(data) {
            renderWatch.reset();

            container = d3.select(this);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            nv.utils.initSVG(container);

            // Setup Scales
            x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));

            if (padData)
                x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
            else
                x.range(xRange || [0, availableWidth]);

            y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
                .range(yRange || [availableHeight, 0]);

            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
            if (x.domain()[0] === x.domain()[1])
                x.domain()[0] ?
                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
                    : x.domain([-1,1]);

            if (y.domain()[0] === y.domain()[1])
                y.domain()[0] ?
                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
                    : y.domain([-1,1]);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-bars');
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            container
                .on('click', function(d,i) {
                    dispatch.chartClick({
                        data: d,
                        index: i,
                        pos: d3.event,
                        id: id
                    });
                });

            defsEnter.append('clipPath')
                .attr('id', 'nv-chart-clip-path-' + id)
                .append('rect');

            wrap.select('#nv-chart-clip-path-' + id + ' rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');

            var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
                .data(function(d) { return d }, function(d,i) {return getX(d,i)});
            bars.exit().remove();

            bars.enter().append('rect')
                .attr('x', 0 )
                .attr('y', function(d,i) {  return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
                .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
                .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
                .on('mouseover', function(d,i) {
                    if (!interactive) return;
                    d3.select(this).classed('hover', true);
                    dispatch.elementMouseover({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });

                })
                .on('mouseout', function(d,i) {
                    if (!interactive) return;
                    d3.select(this).classed('hover', false);
                    dispatch.elementMouseout({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mousemove', function(d,i) {
                    if (!interactive) return;
                    dispatch.elementMousemove({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('click', function(d,i) {
                    if (!interactive) return;
                    var element = this;
                    dispatch.elementClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill"),
                        event: d3.event,
                        element: element
                    });
                    d3.event.stopPropagation();
                })
                .on('dblclick', function(d,i) {
                    if (!interactive) return;
                    dispatch.elementDblClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                    d3.event.stopPropagation();
                });

            bars
                .attr('fill', function(d,i) { return color(d, i); })
                .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
                .watchTransition(renderWatch, 'bars')
                .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
                //TODO: better width calculations that don't assume always uniform data spacing;w
                .attr('width', (availableWidth / data[0].values.length) * .9 );

            bars.watchTransition(renderWatch, 'bars')
                .attr('y', function(d,i) {
                    var rval = getY(d,i) < 0 ?
                        y(0) :
                            y(0) - y(getY(d,i)) < 1 ?
                        y(0) - 1 :
                        y(getY(d,i));
                    return nv.utils.NaNtoZero(rval);
                })
                .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });

        });

        renderWatch.renderEnd('historicalBar immediate');
        return chart;
    }

    //Create methods to allow outside functions to highlight a specific bar.
    chart.highlightPoint = function(pointIndex, isHoverOver) {
        container
            .select(".nv-bars .nv-bar-0-" + pointIndex)
            .classed("hover", isHoverOver)
        ;
    };

    chart.clearHighlights = function() {
        container
            .select(".nv-bars .nv-bar.hover")
            .classed("hover", false)
        ;
    };

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:   {get: function(){return width;}, set: function(_){width=_;}},
        height:  {get: function(){return height;}, set: function(_){height=_;}},
        forceX:  {get: function(){return forceX;}, set: function(_){forceX=_;}},
        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
        padData: {get: function(){return padData;}, set: function(_){padData=_;}},
        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
        clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
        id:          {get: function(){return id;}, set: function(_){id=_;}},
        interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};

nv.models.historicalBarChart = function(bar_model) {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var bars = bar_model || nv.models.historicalBar()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , legend = nv.models.legend()
        , interactiveLayer = nv.interactiveGuideline()
        , tooltip = nv.models.tooltip()
        ;


    var margin = {top: 30, right: 90, bottom: 50, left: 90}
        , marginTop = null
        , color = nv.utils.defaultColor()
        , width = null
        , height = null
        , showLegend = false
        , showXAxis = true
        , showYAxis = true
        , rightAlignYAxis = false
        , useInteractiveGuideline = false
        , x
        , y
        , state = {}
        , defaultState = null
        , noData = null
        , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
        , transitionDuration = 250
        ;

    xAxis.orient('bottom').tickPadding(7);
    yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
    tooltip
        .duration(0)
        .headerEnabled(false)
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        })
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        });


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch, 0);

    function chart(selection) {
        selection.each(function(data) {
            renderWatch.reset();
            renderWatch.models(bars);
            if (showXAxis) renderWatch.models(xAxis);
            if (showYAxis) renderWatch.models(yAxis);

            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
            chart.container = this;

            //set state.disabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display noData message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = bars.xScale();
            y = bars.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis');
            gEnter.append('g').attr('class', 'nv-barsWrap');
            gEnter.append('g').attr('class', 'nv-legendWrap');
            gEnter.append('g').attr('class', 'nv-interactive');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                legend.width(availableWidth);

                g.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                wrap.select('.nv-legendWrap')
                    .attr('transform', 'translate(0,' + (-margin.top) +')')
            }
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            //Set up interactive layer
            if (useInteractiveGuideline) {
                interactiveLayer
                    .width(availableWidth)
                    .height(availableHeight)
                    .margin({left:margin.left, top:margin.top})
                    .svgContainer(container)
                    .xScale(x);
                wrap.select(".nv-interactive").call(interactiveLayer);
            }
            bars
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled }));

            var barsWrap = g.select('.nv-barsWrap')
                .datum(data.filter(function(d) { return !d.disabled }));
            barsWrap.transition().call(bars);

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight, 0);

                g.select('.nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
                g.select('.nv-x.nv-axis')
                    .transition()
                    .call(xAxis);
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);

                g.select('.nv-y.nv-axis')
                    .transition()
                    .call(yAxis);
            }

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            interactiveLayer.dispatch.on('elementMousemove', function(e) {
                bars.clearHighlights();

                var singlePoint, pointIndex, pointXLocation, allData = [];
                data
                    .filter(function(series, i) {
                        series.seriesIndex = i;
                        return !series.disabled;
                    })
                    .forEach(function(series,i) {
                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
                        bars.highlightPoint(pointIndex,true);
                        var point = series.values[pointIndex];
                        if (point === undefined) return;
                        if (singlePoint === undefined) singlePoint = point;
                        if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
                        allData.push({
                            key: series.key,
                            value: chart.y()(point, pointIndex),
                            color: color(series,series.seriesIndex),
                            data: series.values[pointIndex]
                        });
                    });

                var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
                interactiveLayer.tooltip
                    .valueFormatter(function(d,i) {
                        return yAxis.tickFormat()(d);
                    })
                    .data({
                        value: xValue,
                        index: pointIndex,
                        series: allData
                    })();

                interactiveLayer.renderGuideLine(pointXLocation);

            });

            interactiveLayer.dispatch.on("elementMouseout",function(e) {
                dispatch.tooltipHide();
                bars.clearHighlights();
            });

            legend.dispatch.on('legendClick', function(d,i) {
                d.disabled = !d.disabled;

                if (!data.filter(function(d) { return !d.disabled }).length) {
                    data.map(function(d) {
                        d.disabled = false;
                        wrap.selectAll('.nv-series').classed('disabled', false);
                        return d;
                    });
                }

                state.disabled = data.map(function(d) { return !!d.disabled });
                dispatch.stateChange(state);

                selection.transition().call(chart);
            });

            legend.dispatch.on('legendDblclick', function(d) {
                //Double clicking should always enable current series, and disabled all others.
                data.forEach(function(d) {
                    d.disabled = true;
                });
                d.disabled = false;

                state.disabled = data.map(function(d) { return !!d.disabled });
                dispatch.stateChange(state);
                chart.update();
            });

            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });

                    state.disabled = e.disabled;
                }

                chart.update();
            });
        });

        renderWatch.renderEnd('historicalBarChart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    bars.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt['series'] = {
            key: chart.x()(evt.data),
            value: chart.y()(evt.data),
            color: evt.color
        };
        tooltip.data(evt).hidden(false);
    });

    bars.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    bars.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.bars = bars;
    chart.legend = legend;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.interactiveLayer = interactiveLayer;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
            bars.color(color);
        }},
        duration:    {get: function(){return transitionDuration;}, set: function(_){
            transitionDuration=_;
            renderWatch.reset(transitionDuration);
            yAxis.duration(transitionDuration);
            xAxis.duration(transitionDuration);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( (_) ? 'right' : 'left');
        }},
        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
            useInteractiveGuideline = _;
            if (_ === true) {
                chart.interactive(false);
            }
        }}
    });

    nv.utils.inheritOptions(chart, bars);
    nv.utils.initOptions(chart);

    return chart;
};


// ohlcChart is just a historical chart with ohlc bars and some tweaks
nv.models.ohlcBarChart = function() {
    var chart = nv.models.historicalBarChart(nv.models.ohlcBar());

    // special default tooltip since we show multiple values per x
    chart.useInteractiveGuideline(true);
    chart.interactiveLayer.tooltip.contentGenerator(function(data) {
        // we assume only one series exists for this chart
        var d = data.series[0].data;
        // match line colors as defined in nv.d3.css
        var color = d.open < d.close ? "2ca02c" : "d62728";
        return '' +
            '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
            '<table>' +
            '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
            '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
            '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
            '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
            '</table>';
    });
    return chart;
};

// candlestickChart is just a historical chart with candlestick bars and some tweaks
nv.models.candlestickBarChart = function() {
    var chart = nv.models.historicalBarChart(nv.models.candlestickBar());

    // special default tooltip since we show multiple values per x
    chart.useInteractiveGuideline(true);
    chart.interactiveLayer.tooltip.contentGenerator(function(data) {
        // we assume only one series exists for this chart
        var d = data.series[0].data;
        // match line colors as defined in nv.d3.css
        var color = d.open < d.close ? "2ca02c" : "d62728";
        return '' +
            '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
            '<table>' +
            '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
            '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
            '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
            '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
            '</table>';
    });
    return chart;
};
nv.models.legend = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 5, right: 0, bottom: 5, left: 0}
        , width = 400
        , height = 20
        , getKey = function(d) { return d.key }
        , keyFormatter = function (d) { return d }
        , color = nv.utils.getColor()
        , maxKeyLength = 20 //default value for key lengths
        , align = true
        , padding = 32 //define how much space between legend items. - recommend 32 for furious version
        , rightAlign = true
        , updateState = true   //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
        , radioButtonMode = false   //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
        , expanded = false
        , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
        , vers = 'classic' //Options are "classic" and "furious"
        ;

    function chart(selection) {
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                container = d3.select(this);
            nv.utils.initSVG(container);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-legend').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
            var g = wrap.select('g');

            if (rightAlign)
                wrap.attr('transform', 'translate(' + (- margin.right) + ',' + margin.top + ')');
            else
                wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            var series = g.selectAll('.nv-series')
                .data(function(d) {
                    if(vers != 'furious') return d;

                    return d.filter(function(n) {
                        return expanded ? true : !n.disengaged;
                    });
                });

            var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
            var seriesShape;

            var versPadding;
            switch(vers) {
                case 'furious' :
                    versPadding = 23;
                    break;
                case 'classic' :
                    versPadding = 20;
            }

            if(vers == 'classic') {
                seriesEnter.append('circle')
                    .style('stroke-width', 2)
                    .attr('class','nv-legend-symbol')
                    .attr('r', 5);

                seriesShape = series.select('.nv-legend-symbol');
            } else if (vers == 'furious') {
                seriesEnter.append('rect')
                    .style('stroke-width', 2)
                    .attr('class','nv-legend-symbol')
                    .attr('rx', 3)
                    .attr('ry', 3);
                seriesShape = series.select('.nv-legend-symbol');

                seriesEnter.append('g')
                    .attr('class', 'nv-check-box')
                    .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
                    .attr('transform', 'translate(-10,-8)scale(0.5)');

                var seriesCheckbox = series.select('.nv-check-box');

                seriesCheckbox.each(function(d,i) {
                    d3.select(this).selectAll('path')
                        .attr('stroke', setTextColor(d,i));
                });
            }

            seriesEnter.append('text')
                .attr('text-anchor', 'start')
                .attr('class','nv-legend-text')
                .attr('dy', '.32em')
                .attr('dx', '8');

            var seriesText = series.select('text.nv-legend-text');

            series
                .on('mouseover', function(d,i) {
                    dispatch.legendMouseover(d,i);  //TODO: Make consistent with other event objects
                })
                .on('mouseout', function(d,i) {
                    dispatch.legendMouseout(d,i);
                })
                .on('click', function(d,i) {
                    dispatch.legendClick(d,i);
                    // make sure we re-get data in case it was modified
                    var data = series.data();
                    if (updateState) {
                        if(vers =='classic') {
                            if (radioButtonMode) {
                                //Radio button mode: set every series to disabled,
                                //  and enable the clicked series.
                                data.forEach(function(series) { series.disabled = true});
                                d.disabled = false;
                            }
                            else {
                                d.disabled = !d.disabled;
                                if (data.every(function(series) { return series.disabled})) {
                                    //the default behavior of NVD3 legends is, if every single series
                                    // is disabled, turn all series' back on.
                                    data.forEach(function(series) { series.disabled = false});
                                }
                            }
                        } else if(vers == 'furious') {
                            if(expanded) {
                                d.disengaged = !d.disengaged;
                                d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
                                d.disabled = d.disengaged || d.userDisabled;
                            } else if (!expanded) {
                                d.disabled = !d.disabled;
                                d.userDisabled = d.disabled;
                                var engaged = data.filter(function(d) { return !d.disengaged; });
                                if (engaged.every(function(series) { return series.userDisabled })) {
                                    //the default behavior of NVD3 legends is, if every single series
                                    // is disabled, turn all series' back on.
                                    data.forEach(function(series) {
                                        series.disabled = series.userDisabled = false;
                                    });
                                }
                            }
                        }
                        dispatch.stateChange({
                            disabled: data.map(function(d) { return !!d.disabled }),
                            disengaged: data.map(function(d) { return !!d.disengaged })
                        });

                    }
                })
                .on('dblclick', function(d,i) {
                    if(vers == 'furious' && expanded) return;
                    dispatch.legendDblclick(d,i);
                    if (updateState) {
                        // make sure we re-get data in case it was modified
                        var data = series.data();
                        //the default behavior of NVD3 legends, when double clicking one,
                        // is to set all other series' to false, and make the double clicked series enabled.
                        data.forEach(function(series) {
                            series.disabled = true;
                            if(vers == 'furious') series.userDisabled = series.disabled;
                        });
                        d.disabled = false;
                        if(vers == 'furious') d.userDisabled = d.disabled;
                        dispatch.stateChange({
                            disabled: data.map(function(d) { return !!d.disabled })
                        });
                    }
                });

            series.classed('nv-disabled', function(d) { return d.userDisabled });
            series.exit().remove();

            seriesText
                .attr('fill', setTextColor)
                .text(function (d) { return keyFormatter(getKey(d)) });

            //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
            // NEW ALIGNING CODE, TODO: clean up
            var legendWidth = 0;
            if (align) {

                var seriesWidths = [];
                series.each(function(d,i) {
                    var legendText;
                    if (keyFormatter(getKey(d)) && keyFormatter(getKey(d)).length > maxKeyLength) {
                        var trimmedKey = keyFormatter(getKey(d)).substring(0, maxKeyLength);
                        legendText = d3.select(this).select('text').text(trimmedKey + "...");
                        d3.select(this).append("svg:title").text(keyFormatter(getKey(d)));
                    } else {
                        legendText = d3.select(this).select('text');
                    }
                    var nodeTextLength;
                    try {
                        nodeTextLength = legendText.node().getComputedTextLength();
                        // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
                        if(nodeTextLength <= 0) throw Error();
                    }
                    catch(e) {
                        nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
                    }

                    seriesWidths.push(nodeTextLength + padding);
                });

                var seriesPerRow = 0;
                var columnWidths = [];
                legendWidth = 0;

                while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
                    columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
                    legendWidth += seriesWidths[seriesPerRow++];
                }
                if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row

                while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
                    columnWidths = [];
                    seriesPerRow--;

                    for (var k = 0; k < seriesWidths.length; k++) {
                        if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
                            columnWidths[k % seriesPerRow] = seriesWidths[k];
                    }

                    legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
                        return prev + cur;
                    });
                }

                var xPositions = [];
                for (var i = 0, curX = 0; i < seriesPerRow; i++) {
                    xPositions[i] = curX;
                    curX += columnWidths[i];
                }

                series
                    .attr('transform', function(d, i) {
                        return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
                    });

                //position legend as far right as possible within the total width
                if (rightAlign) {
                    g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
                }
                else {
                    g.attr('transform', 'translate(0' + ',' + margin.top + ')');
                }

                height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);

            } else {

                var ypos = 5,
                    newxpos = 5,
                    maxwidth = 0,
                    xpos;
                series
                    .attr('transform', function(d, i) {
                        var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
                        xpos = newxpos;

                        if (width < margin.left + margin.right + xpos + length) {
                            newxpos = xpos = 5;
                            ypos += versPadding;
                        }

                        newxpos += length;
                        if (newxpos > maxwidth) maxwidth = newxpos;

                        if(legendWidth < xpos + maxwidth) {
                            legendWidth = xpos + maxwidth;
                        }
                        return 'translate(' + xpos + ',' + ypos + ')';
                    });

                //position legend as far right as possible within the total width
                g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');

                height = margin.top + margin.bottom + ypos + 15;
            }

            if(vers == 'furious') {
                // Size rectangles after text is placed
                seriesShape
                    .attr('width', function(d,i) {
                        return seriesText[0][i].getComputedTextLength() + 27;
                    })
                    .attr('height', 18)
                    .attr('y', -9)
                    .attr('x', -15);

                // The background for the expanded legend (UI)
                gEnter.insert('rect',':first-child')
                    .attr('class', 'nv-legend-bg')
                    .attr('fill', '#eee')
                    // .attr('stroke', '#444')
                    .attr('opacity',0);

                var seriesBG = g.select('.nv-legend-bg');

                seriesBG
                .transition().duration(300)
                    .attr('x', -versPadding )
                    .attr('width', legendWidth + versPadding - 12)
                    .attr('height', height + 10)
                    .attr('y', -margin.top - 10)
                    .attr('opacity', expanded ? 1 : 0);


            }

            seriesShape
                .style('fill', setBGColor)
                .style('fill-opacity', setBGOpacity)
                .style('stroke', setBGColor);
        });

        function setTextColor(d,i) {
            if(vers != 'furious') return '#000';
            if(expanded) {
                return d.disengaged ? '#000' : '#fff';
            } else if (!expanded) {
                if(!d.color) d.color = color(d,i);
                return !!d.disabled ? d.color : '#fff';
            }
        }

        function setBGColor(d,i) {
            if(expanded && vers == 'furious') {
                return d.disengaged ? '#eee' : d.color || color(d,i);
            } else {
                return d.color || color(d,i);
            }
        }


        function setBGOpacity(d,i) {
            if(expanded && vers == 'furious') {
                return 1;
            } else {
                return !!d.disabled ? 0 : 1;
            }
        }

        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:          {get: function(){return width;}, set: function(_){width=_;}},
        height:         {get: function(){return height;}, set: function(_){height=_;}},
        key:            {get: function(){return getKey;}, set: function(_){getKey=_;}},
        keyFormatter:   {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
        align:          {get: function(){return align;}, set: function(_){align=_;}},
        maxKeyLength:   {get: function(){return maxKeyLength;}, set: function(_){maxKeyLength=_;}},
        rightAlign:     {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
        padding:        {get: function(){return padding;}, set: function(_){padding=_;}},
        updateState:    {get: function(){return updateState;}, set: function(_){updateState=_;}},
        radioButtonMode:{get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
        expanded:       {get: function(){return expanded;}, set: function(_){expanded=_;}},
        vers:           {get: function(){return vers;}, set: function(_){vers=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};

nv.models.line = function() {
    "use strict";
    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var  scatter = nv.models.scatter()
        ;

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 960
        , height = 500
        , container = null
        , strokeWidth = 1.5
        , color = nv.utils.defaultColor() // a function that returns a color
        , getX = function(d) { return d.x } // accessor to get the x value from a data point
        , getY = function(d) { return d.y } // accessor to get the y value from a data point
        , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
        , isArea = function(d) { return d.area } // decides if a line is an area or just a line
        , clipEdge = false // if true, masks lines within x and y scale
        , x //can be accessed via chart.xScale()
        , y //can be accessed via chart.yScale()
        , interpolate = "linear" // controls the line interpolation
        , duration = 250
        , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
        ;

    scatter
        .pointSize(16) // default size
        .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
    ;

    //============================================================


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x0, y0 //used to store previous scales
        , renderWatch = nv.utils.renderWatch(dispatch, duration)
        ;

    //============================================================


    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(scatter);
        selection.each(function(data) {
            container = d3.select(this);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);
            nv.utils.initSVG(container);

            // Setup Scales
            x = scatter.xScale();
            y = scatter.yScale();

            x0 = x0 || x;
            y0 = y0 || y;

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-groups');
            gEnter.append('g').attr('class', 'nv-scatterWrap');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            scatter
                .width(availableWidth)
                .height(availableHeight);

            var scatterWrap = wrap.select('.nv-scatterWrap');
            scatterWrap.call(scatter);

            defsEnter.append('clipPath')
                .attr('id', 'nv-edge-clip-' + scatter.id())
                .append('rect');

            wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
                .attr('width', availableWidth)
                .attr('height', (availableHeight > 0) ? availableHeight : 0);

            g   .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
            scatterWrap
                .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');

            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
                .data(function(d) { return d }, function(d) { return d.key });
            groups.enter().append('g')
                .style('stroke-opacity', 1e-6)
                .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
                .style('fill-opacity', 1e-6);

            groups.exit().remove();

            groups
                .attr('class', function(d,i) {
                    return (d.classed || '') + ' nv-group nv-series-' + i;
                })
                .classed('hover', function(d) { return d.hover })
                .style('fill', function(d,i){ return color(d, i) })
                .style('stroke', function(d,i){ return color(d, i)});
            groups.watchTransition(renderWatch, 'line: groups')
                .style('stroke-opacity', 1)
                .style('fill-opacity', function(d) { return d.fillOpacity || .5});

            var areaPaths = groups.selectAll('path.nv-area')
                .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
            areaPaths.enter().append('path')
                .attr('class', 'nv-area')
                .attr('d', function(d) {
                    return d3.svg.area()
                        .interpolate(interpolate)
                        .defined(defined)
                        .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
                        .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
                        .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
                        //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
                        .apply(this, [d.values])
                });
            groups.exit().selectAll('path.nv-area')
                .remove();

            areaPaths.watchTransition(renderWatch, 'line: areaPaths')
                .attr('d', function(d) {
                    return d3.svg.area()
                        .interpolate(interpolate)
                        .defined(defined)
                        .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
                        .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
                        .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
                        //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
                        .apply(this, [d.values])
                });

            var linePaths = groups.selectAll('path.nv-line')
                .data(function(d) { return [d.values] });

            linePaths.enter().append('path')
                .attr('class', 'nv-line')
                .attr('d',
                    d3.svg.line()
                    .interpolate(interpolate)
                    .defined(defined)
                    .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
                    .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
            );

            linePaths.watchTransition(renderWatch, 'line: linePaths')
                .attr('d',
                    d3.svg.line()
                    .interpolate(interpolate)
                    .defined(defined)
                    .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
                    .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
            );

            //store old scales for use in transitions on update
            x0 = x.copy();
            y0 = y.copy();
        });
        renderWatch.renderEnd('line immediate');
        return chart;
    }


    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.scatter = scatter;
    // Pass through events
    scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
    scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
    scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        defined: {get: function(){return defined;}, set: function(_){defined=_;}},
        interpolate:      {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
        clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            scatter.duration(duration);
        }},
        isArea: {get: function(){return isArea;}, set: function(_){
            isArea = d3.functor(_);
        }},
        x: {get: function(){return getX;}, set: function(_){
            getX = _;
            scatter.x(_);
        }},
        y: {get: function(){return getY;}, set: function(_){
            getY = _;
            scatter.y(_);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            scatter.color(color);
        }}
    });

    nv.utils.inheritOptions(chart, scatter);
    nv.utils.initOptions(chart);

    return chart;
};
nv.models.lineChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var lines = nv.models.line()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , legend = nv.models.legend()
        , interactiveLayer = nv.interactiveGuideline()
        , tooltip = nv.models.tooltip()
        , focus = nv.models.focus(nv.models.line())
        ;

    var margin = {top: 30, right: 20, bottom: 50, left: 60}
        , marginTop = null
        , color = nv.utils.defaultColor()
        , width = null
        , height = null
        , showLegend = true
        , legendPosition = 'top'
        , showXAxis = true
        , showYAxis = true
        , rightAlignYAxis = false
        , useInteractiveGuideline = false
        , x
        , y
        , focusEnable = false
        , state = nv.utils.state()
        , defaultState = null
        , noData = null
        , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
        , duration = 250
        ;

    // set options on sub-objects for this chart
    xAxis.orient('bottom').tickPadding(7);
    yAxis.orient(rightAlignYAxis ? 'right' : 'left');

    lines.clipEdge(true).duration(0);

    tooltip.valueFormatter(function(d, i) {
        return yAxis.tickFormat()(d, i);
    }).headerFormatter(function(d, i) {
        return xAxis.tickFormat()(d, i);
    });

    interactiveLayer.tooltip.valueFormatter(function(d, i) {
        return yAxis.tickFormat()(d, i);
    }).headerFormatter(function(d, i) {
        return xAxis.tickFormat()(d, i);
    });


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled; })
            };
        };
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        };
    };

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(lines);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this);
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
            chart.update = function() {
                if( duration === 0 ) {
                    container.call( chart );
                } else {
                    container.transition().duration(duration).call(chart);
                }
            };
            chart.container = this;

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disabled
            state.disabled = data.map(function(d) { return !!d.disabled; });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display noData message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length; }).length) {
                nv.utils.noData(chart, container);
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            /* Update `main' graph on brush update. */
            focus.dispatch.on("onBrush", function(extent) {
                onBrush(extent);
            });

            // Setup Scales
            x = lines.xScale();
            y = lines.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-legendWrap');

            var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
            focusEnter.append('g').attr('class', 'nv-background').append('rect');
            focusEnter.append('g').attr('class', 'nv-x nv-axis');
            focusEnter.append('g').attr('class', 'nv-y nv-axis');
            focusEnter.append('g').attr('class', 'nv-linesWrap');
            focusEnter.append('g').attr('class', 'nv-interactive');

            var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                legend.width(availableWidth);

                g.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (legendPosition === 'bottom') {
                    wrap.select('.nv-legendWrap')
                        .attr('transform', 'translate(0,' + availableHeight +')');
                } else if (legendPosition === 'top') {
                    if (!marginTop && legend.height() !== margin.top) {
                        margin.top = legend.height();
                        availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
                    }

                    wrap.select('.nv-legendWrap')
                        .attr('transform', 'translate(0,' + (-margin.top) +')');
                }
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            //Set up interactive layer
            if (useInteractiveGuideline) {
                interactiveLayer
                    .width(availableWidth)
                    .height(availableHeight)
                    .margin({left:margin.left, top:margin.top})
                    .svgContainer(container)
                    .xScale(x);
                wrap.select(".nv-interactive").call(interactiveLayer);
            }

            g.select('.nv-focus .nv-background rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            lines
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled; }));

            var linesWrap = g.select('.nv-linesWrap')
                .datum(data.filter(function(d) { return !d.disabled; }));


            // Setup Main (Focus) Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight, 0);
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);
            }

            //============================================================
            // Update Axes
            //============================================================
            function updateXAxis() {
              if(showXAxis) {
                g.select('.nv-focus .nv-x.nv-axis')
                  .transition()
                  .duration(duration)
                  .call(xAxis)
                ;
              }
            }

            function updateYAxis() {
              if(showYAxis) {
                g.select('.nv-focus .nv-y.nv-axis')
                  .transition()
                  .duration(duration)
                  .call(yAxis)
                ;
              }
            }

            g.select('.nv-focus .nv-x.nv-axis')
                .attr('transform', 'translate(0,' + availableHeight + ')');

            //============================================================
            // Update Focus
            //============================================================
            if(!focusEnable) {
                linesWrap.call(lines);
                updateXAxis();
                updateYAxis();
            } else {
                focus.width(availableWidth);
                g.select('.nv-focusWrap')
                    .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
                    .datum(data.filter(function(d) { return !d.disabled; }))
                    .call(focus);
                var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
                if(extent !== null){
                    onBrush(extent);
                }
            }
            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            interactiveLayer.dispatch.on('elementMousemove', function(e) {
                lines.clearHighlights();
                var singlePoint, pointIndex, pointXLocation, allData = [];
                data
                    .filter(function(series, i) {
                        series.seriesIndex = i;
                        return !series.disabled && !series.disableTooltip;
                    })
                    .forEach(function(series,i) {
                        var extent = focusEnable ? (focus.brush.empty() ? focus.xScale().domain() : focus.brush.extent()) : x.domain();
                        var currentValues = series.values.filter(function(d,i) {
                            // Checks if the x point is between the extents, handling case where extent[0] is greater than extent[1]
                            // (e.g. x domain is manually set to reverse the x-axis)
                            if(extent[0] <= extent[1]) {
                                return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
                            } else {
                                return lines.x()(d,i) >= extent[1] && lines.x()(d,i) <= extent[0];
                            }
                        });

                        pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
                        var point = currentValues[pointIndex];
                        var pointYValue = chart.y()(point, pointIndex);
                        if (pointYValue !== null) {
                            lines.highlightPoint(i, pointIndex, true);
                        }
                        if (point === undefined) return;
                        if (singlePoint === undefined) singlePoint = point;
                        if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
                        allData.push({
                            key: series.key,
                            value: pointYValue,
                            color: color(series,series.seriesIndex),
                            data: point
                        });
                    });
                //Highlight the tooltip entry based on which point the mouse is closest to.
                if (allData.length > 2) {
                    var yValue = chart.yScale().invert(e.mouseY);
                    var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
                    var threshold = 0.03 * domainExtent;
                    var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value;}),yValue,threshold);
                    if (indexToHighlight !== null)
                        allData[indexToHighlight].highlight = true;
                }

                var defaultValueFormatter = function(d,i) {
                    return d == null ? "N/A" : yAxis.tickFormat()(d);
                };

                interactiveLayer.tooltip
                    .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
                    .data({
                        value: chart.x()( singlePoint,pointIndex ),
                        index: pointIndex,
                        series: allData
                    })();

                interactiveLayer.renderGuideLine(pointXLocation);

            });

            interactiveLayer.dispatch.on('elementClick', function(e) {
                var pointXLocation, allData = [];

                data.filter(function(series, i) {
                    series.seriesIndex = i;
                    return !series.disabled;
                }).forEach(function(series) {
                    var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
                    var point = series.values[pointIndex];
                    if (typeof point === 'undefined') return;
                    if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
                    var yPos = chart.yScale()(chart.y()(point,pointIndex));
                    allData.push({
                        point: point,
                        pointIndex: pointIndex,
                        pos: [pointXLocation, yPos],
                        seriesIndex: series.seriesIndex,
                        series: series
                    });
                });

                lines.dispatch.elementClick(allData);
            });

            interactiveLayer.dispatch.on("elementMouseout",function(e) {
                lines.clearHighlights();
            });

            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });

                    state.disabled = e.disabled;
                }
                chart.update();
            });

            //============================================================
            // Functions
            //------------------------------------------------------------

            // Taken from crossfilter (http://square.github.com/crossfilter/)
            function resizePath(d) {
                var e = +(d == 'e'),
                    x = e ? 1 : -1,
                    y = availableHeight / 3;
                return 'M' + (0.5 * x) + ',' + y
                    + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
                    + 'V' + (2 * y - 6)
                    + 'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y)
                    + 'Z'
                    + 'M' + (2.5 * x) + ',' + (y + 8)
                    + 'V' + (2 * y - 8)
                    + 'M' + (4.5 * x) + ',' + (y + 8)
                    + 'V' + (2 * y - 8);
            }

            function onBrush(extent) {
                // Update Main (Focus)
                var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
                    .datum(
                    data.filter(function(d) { return !d.disabled; })
                        .map(function(d,i) {
                            return {
                                key: d.key,
                                area: d.area,
                                classed: d.classed,
                                values: d.values.filter(function(d,i) {
                                    return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
                                }),
                                disableTooltip: d.disableTooltip
                            };
                        })
                );
                focusLinesWrap.transition().duration(duration).call(lines);

                // Update Main (Focus) Axes
                updateXAxis();
                updateYAxis();
            }
        });

        renderWatch.renderEnd('lineChart immediate');
        return chart;
    }


    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    lines.dispatch.on('elementMouseover.tooltip', function(evt) {
        if(!evt.series.disableTooltip){
            tooltip.data(evt).hidden(false);
        }
    });

    lines.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.lines = lines;
    chart.legend = legend;
    chart.focus = focus;
    chart.xAxis = xAxis;
    chart.x2Axis = focus.xAxis
    chart.yAxis = yAxis;
    chart.y2Axis = focus.yAxis
    chart.interactiveLayer = interactiveLayer;
    chart.tooltip = tooltip;
    chart.state = state;
    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        // Focus options, mostly passed onto focus model.
        focusEnable:    {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
        focusHeight:     {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
        focusShowAxisX:    {get: function(){return focus.showXAxis();}, set: function(_){focus.showXAxis(_);}},
        focusShowAxisY:    {get: function(){return focus.showYAxis();}, set: function(_){focus.showYAxis(_);}},
        brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},

        // options that require extra logic in the setter
        focusMargin: {get: function(){return focus.margin}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            focus.margin.right  = _.right  !== undefined ? _.right  : focus.margin.right;
            focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
            focus.margin.left   = _.left   !== undefined ? _.left   : focus.margin.left;
        }},
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            lines.duration(duration);
            focus.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
            lines.color(color);
            focus.color(color);
        }},
        interpolate: {get: function(){return lines.interpolate();}, set: function(_){
            lines.interpolate(_);
            focus.interpolate(_);
        }},
        xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
            xAxis.tickFormat(_);
            focus.xTickFormat(_);
        }},
        yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
            yAxis.tickFormat(_);
            focus.yTickFormat(_);
        }},
        x: {get: function(){return lines.x();}, set: function(_){
            lines.x(_);
            focus.x(_);
        }},
        y: {get: function(){return lines.y();}, set: function(_){
            lines.y(_);
            focus.y(_);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
        }},
        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
            useInteractiveGuideline = _;
            if (useInteractiveGuideline) {
                lines.interactive(false);
                lines.useVoronoi(false);
            }
        }}
    });

    nv.utils.inheritOptions(chart, lines);
    nv.utils.initOptions(chart);

    return chart;
};

nv.models.lineWithFocusChart = function() {
  return nv.models.lineChart()
    .margin({ bottom: 30 })
    .focusEnable( true );
};
nv.models.linePlusBarChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var lines = nv.models.line()
        , lines2 = nv.models.line()
        , bars = nv.models.historicalBar()
        , bars2 = nv.models.historicalBar()
        , xAxis = nv.models.axis()
        , x2Axis = nv.models.axis()
        , y1Axis = nv.models.axis()
        , y2Axis = nv.models.axis()
        , y3Axis = nv.models.axis()
        , y4Axis = nv.models.axis()
        , legend = nv.models.legend()
        , brush = d3.svg.brush()
        , tooltip = nv.models.tooltip()
        ;

    var margin = {top: 30, right: 30, bottom: 30, left: 60}
        , marginTop = null
        , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
        , width = null
        , height = null
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , color = nv.utils.defaultColor()
        , showLegend = true
        , focusEnable = true
        , focusShowAxisY = false
        , focusShowAxisX = true
        , focusHeight = 50
        , extent
        , brushExtent = null
        , x
        , x2
        , y1
        , y2
        , y3
        , y4
        , noData = null
        , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
        , transitionDuration = 0
        , state = nv.utils.state()
        , defaultState = null
        , legendLeftAxisHint = ' (left axis)'
        , legendRightAxisHint = ' (right axis)'
        , switchYAxisOrder = false
        ;

    lines.clipEdge(true);
    lines2.interactive(false);
    // We don't want any points emitted for the focus chart's scatter graph.
    lines2.pointActive(function(d) { return false });
    xAxis.orient('bottom').tickPadding(5);
    y1Axis.orient('left');
    y2Axis.orient('right');
    x2Axis.orient('bottom').tickPadding(5);
    y3Axis.orient('left');
    y4Axis.orient('right');

    tooltip.headerEnabled(true).headerFormatter(function(d, i) {
        return xAxis.tickFormat()(d, i);
    });

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var getBarsAxis = function() {
        return switchYAxisOrder
            ? { main: y2Axis, focus: y4Axis }
            : { main: y1Axis, focus: y3Axis }
    }

    var getLinesAxis = function() {
        return switchYAxisOrder
            ? { main: y1Axis, focus: y3Axis }
            : { main: y2Axis, focus: y4Axis }
    }

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled })
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        }
    };

    var allDisabled = function(data) {
      return data.every(function(series) {
        return series.disabled;
      });
    }

    function chart(selection) {
        selection.each(function(data) {
            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight1 = nv.utils.availableHeight(height, container, margin)
                    - (focusEnable ? focusHeight : 0),
                availableHeight2 = focusHeight - margin2.top - margin2.bottom;

            chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
            chart.container = this;

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disableddisabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display No Data message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
            var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240

            if (dataBars.length && !switchYAxisOrder) {
                x = bars.xScale();
            } else {
                x = lines.xScale();
            }

            x2 = x2Axis.scale();

            // select the scales and series based on the position of the yAxis
            y1 = switchYAxisOrder ? lines.yScale() : bars.yScale();
            y2 = switchYAxisOrder ? bars.yScale() : lines.yScale();
            y3 = switchYAxisOrder ? lines2.yScale() : bars2.yScale();
            y4 = switchYAxisOrder ? bars2.yScale() : lines2.yScale();

            var series1 = data
                .filter(function(d) { return !d.disabled && (switchYAxisOrder ? !d.bar : d.bar) })
                .map(function(d) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d,i), y: getY(d,i) }
                    })
                });

            var series2 = data
                .filter(function(d) { return !d.disabled && (switchYAxisOrder ? d.bar : !d.bar) })
                .map(function(d) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d,i), y: getY(d,i) }
                    })
                });

            x.range([0, availableWidth]);

            x2  .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
                .range([0, availableWidth]);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-legendWrap');

            // this is the main chart
            var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
            focusEnter.append('g').attr('class', 'nv-x nv-axis');
            focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
            focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
            focusEnter.append('g').attr('class', 'nv-barsWrap');
            focusEnter.append('g').attr('class', 'nv-linesWrap');

            // context chart is where you can focus in
            var contextEnter = gEnter.append('g').attr('class', 'nv-context');
            contextEnter.append('g').attr('class', 'nv-x nv-axis');
            contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
            contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
            contextEnter.append('g').attr('class', 'nv-barsWrap');
            contextEnter.append('g').attr('class', 'nv-linesWrap');
            contextEnter.append('g').attr('class', 'nv-brushBackground');
            contextEnter.append('g').attr('class', 'nv-x nv-brush');

            //============================================================
            // Legend
            //------------------------------------------------------------

            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
                var legendXPosition = legend.align() ? legendWidth : 0;

                legend.width(legendWidth);

                g.select('.nv-legendWrap')
                    .datum(data.map(function(series) {
                        series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
                        if(switchYAxisOrder) {
                            series.key = series.originalKey + (series.bar ? legendRightAxisHint : legendLeftAxisHint);
                        } else {
                            series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
                        }
                        return series;
                    }))
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
                    availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
                }

                g.select('.nv-legendWrap')
                    .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            //============================================================
            // Context chart (focus chart) components
            //------------------------------------------------------------

            // hide or show the focus context chart
            g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');

            bars2
                .width(availableWidth)
                .height(availableHeight2)
                .color(data.map(function (d, i) {
                    return d.color || color(d, i);
                }).filter(function (d, i) {
                    return !data[i].disabled && data[i].bar
                }));
            lines2
                .width(availableWidth)
                .height(availableHeight2)
                .color(data.map(function (d, i) {
                    return d.color || color(d, i);
                }).filter(function (d, i) {
                    return !data[i].disabled && !data[i].bar
                }));

            var bars2Wrap = g.select('.nv-context .nv-barsWrap')
                .datum(dataBars.length ? dataBars : [
                    {values: []}
                ]);
            var lines2Wrap = g.select('.nv-context .nv-linesWrap')
                .datum(allDisabled(dataLines) ?
                       [{values: []}] :
                       dataLines.filter(function(dataLine) {
                         return !dataLine.disabled;
                       }));

            g.select('.nv-context')
                .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');

            bars2Wrap.transition().call(bars2);
            lines2Wrap.transition().call(lines2);

            // context (focus chart) axis controls
            if (focusShowAxisX) {
                x2Axis
                    ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
                    .tickSize(-availableHeight2, 0);
                g.select('.nv-context .nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y3.range()[0] + ')');
                g.select('.nv-context .nv-x.nv-axis').transition()
                    .call(x2Axis);
            }

            if (focusShowAxisY) {
                y3Axis
                    .scale(y3)
                    ._ticks( availableHeight2 / 36 )
                    .tickSize( -availableWidth, 0);
                y4Axis
                    .scale(y4)
                    ._ticks( availableHeight2 / 36 )
                    .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none

                g.select('.nv-context .nv-y3.nv-axis')
                    .style('opacity', dataBars.length ? 1 : 0)
                    .attr('transform', 'translate(0,' + x2.range()[0] + ')');
                g.select('.nv-context .nv-y2.nv-axis')
                    .style('opacity', dataLines.length ? 1 : 0)
                    .attr('transform', 'translate(' + x2.range()[1] + ',0)');

                g.select('.nv-context .nv-y1.nv-axis').transition()
                    .call(y3Axis);
                g.select('.nv-context .nv-y2.nv-axis').transition()
                    .call(y4Axis);
            }

            // Setup Brush
            brush.x(x2).on('brush', onBrush);

            if (brushExtent) brush.extent(brushExtent);

            var brushBG = g.select('.nv-brushBackground').selectAll('g')
                .data([brushExtent || brush.extent()]);

            var brushBGenter = brushBG.enter()
                .append('g');

            brushBGenter.append('rect')
                .attr('class', 'left')
                .attr('x', 0)
                .attr('y', 0)
                .attr('height', availableHeight2);

            brushBGenter.append('rect')
                .attr('class', 'right')
                .attr('x', 0)
                .attr('y', 0)
                .attr('height', availableHeight2);

            var gBrush = g.select('.nv-x.nv-brush')
                .call(brush);
            gBrush.selectAll('rect')
                //.attr('y', -5)
                .attr('height', availableHeight2);
            gBrush.selectAll('.resize').append('path').attr('d', resizePath);

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });
                    state.disabled = e.disabled;
                }
                chart.update();
            });

            //============================================================
            // Functions
            //------------------------------------------------------------

            // Taken from crossfilter (http://square.github.com/crossfilter/)
            function resizePath(d) {
                var e = +(d == 'e'),
                    x = e ? 1 : -1,
                    y = availableHeight2 / 3;
                return 'M' + (.5 * x) + ',' + y
                    + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
                    + 'V' + (2 * y - 6)
                    + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
                    + 'Z'
                    + 'M' + (2.5 * x) + ',' + (y + 8)
                    + 'V' + (2 * y - 8)
                    + 'M' + (4.5 * x) + ',' + (y + 8)
                    + 'V' + (2 * y - 8);
            }


            function updateBrushBG() {
                if (!brush.empty()) brush.extent(brushExtent);
                brushBG
                    .data([brush.empty() ? x2.domain() : brushExtent])
                    .each(function(d,i) {
                        var leftWidth = x2(d[0]) - x2.range()[0],
                            rightWidth = x2.range()[1] - x2(d[1]);
                        d3.select(this).select('.left')
                            .attr('width',  leftWidth < 0 ? 0 : leftWidth);

                        d3.select(this).select('.right')
                            .attr('x', x2(d[1]))
                            .attr('width', rightWidth < 0 ? 0 : rightWidth);
                    });
            }

            function onBrush() {
                brushExtent = brush.empty() ? null : brush.extent();
                extent = brush.empty() ? x2.domain() : brush.extent();
                dispatch.brush({extent: extent, brush: brush});
                updateBrushBG();

                // Prepare Main (Focus) Bars and Lines
                bars
                    .width(availableWidth)
                    .height(availableHeight1)
                    .color(data.map(function(d,i) {
                        return d.color || color(d, i);
                    }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));

                lines
                    .width(availableWidth)
                    .height(availableHeight1)
                    .color(data.map(function(d,i) {
                        return d.color || color(d, i);
                    }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));

                var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
                    .datum(!dataBars.length ? [{values:[]}] :
                        dataBars
                            .map(function(d,i) {
                                return {
                                    key: d.key,
                                    values: d.values.filter(function(d,i) {
                                        return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
                                    })
                                }
                            })
                );

                var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
                    .datum(allDisabled(dataLines) ? [{values:[]}] :
                           dataLines
                           .filter(function(dataLine) { return !dataLine.disabled; })
                           .map(function(d,i) {
                                return {
                                    area: d.area,
                                    fillOpacity: d.fillOpacity,
                                    strokeWidth: d.strokeWidth,
                                    key: d.key,
                                    values: d.values.filter(function(d,i) {
                                        return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
                                    })
                                }
                            })
                );

                // Update Main (Focus) X Axis
                if (dataBars.length && !switchYAxisOrder) {
                    x = bars.xScale();
                } else {
                    x = lines.xScale();
                }

                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight1, 0);

                xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);

                g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
                    .call(xAxis);

                // Update Main (Focus) Bars and Lines
                focusBarsWrap.transition().duration(transitionDuration).call(bars);
                focusLinesWrap.transition().duration(transitionDuration).call(lines);

                // Setup and Update Main (Focus) Y Axes
                g.select('.nv-focus .nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y1.range()[0] + ')');

                y1Axis
                    .scale(y1)
                    ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
                    .tickSize(-availableWidth, 0);
                y2Axis
                    .scale(y2)
                    ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) );

                // Show the y2 rules only if y1 has none
                if(!switchYAxisOrder) {
                    y2Axis.tickSize(dataBars.length ? 0 : -availableWidth, 0);
                } else {
                    y2Axis.tickSize(dataLines.length ? 0 : -availableWidth, 0);
                }

                // Calculate opacity of the axis
                var barsOpacity = dataBars.length ? 1 : 0;
                var linesOpacity = dataLines.length && !allDisabled(dataLines) ? 1 : 0;

                var y1Opacity = switchYAxisOrder ? linesOpacity : barsOpacity;
                var y2Opacity = switchYAxisOrder ? barsOpacity : linesOpacity;

                g.select('.nv-focus .nv-y1.nv-axis')
                    .style('opacity', y1Opacity);
                g.select('.nv-focus .nv-y2.nv-axis')
                    .style('opacity', y2Opacity)
                    .attr('transform', 'translate(' + x.range()[1] + ',0)');

                g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
                    .call(y1Axis);
                g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
                    .call(y2Axis);
            }

            onBrush();

        });

        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    lines.dispatch.on('elementMouseover.tooltip', function(evt) {
        tooltip
            .duration(100)
            .valueFormatter(function(d, i) {
                return getLinesAxis().main.tickFormat()(d, i);
            })
            .data(evt)
            .hidden(false);
    });

    lines.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true)
    });

    bars.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt.value = chart.x()(evt.data);
        evt['series'] = {
            value: chart.y()(evt.data),
            color: evt.color
        };
        tooltip
            .duration(0)
            .valueFormatter(function(d, i) {
                return getBarsAxis().main.tickFormat()(d, i);
            })
            .data(evt)
            .hidden(false);
    });

    bars.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    bars.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================


    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.legend = legend;
    chart.lines = lines;
    chart.lines2 = lines2;
    chart.bars = bars;
    chart.bars2 = bars2;
    chart.xAxis = xAxis;
    chart.x2Axis = x2Axis;
    chart.y1Axis = y1Axis;
    chart.y2Axis = y2Axis;
    chart.y3Axis = y3Axis;
    chart.y4Axis = y4Axis;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        brushExtent:    {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        focusEnable:    {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
        focusHeight:    {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
        focusShowAxisX:    {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
        focusShowAxisY:    {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
        legendLeftAxisHint:    {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
        legendRightAxisHint:    {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        focusMargin: {get: function(){return margin2;}, set: function(_){
            margin2.top    = _.top    !== undefined ? _.top    : margin2.top;
            margin2.right  = _.right  !== undefined ? _.right  : margin2.right;
            margin2.bottom = _.bottom !== undefined ? _.bottom : margin2.bottom;
            margin2.left   = _.left   !== undefined ? _.left   : margin2.left;
        }},
        duration: {get: function(){return transitionDuration;}, set: function(_){
            transitionDuration = _;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
        }},
        x: {get: function(){return getX;}, set: function(_){
            getX = _;
            lines.x(_);
            lines2.x(_);
            bars.x(_);
            bars2.x(_);
        }},
        y: {get: function(){return getY;}, set: function(_){
            getY = _;
            lines.y(_);
            lines2.y(_);
            bars.y(_);
            bars2.y(_);
        }},
        switchYAxisOrder:    {get: function(){return switchYAxisOrder;}, set: function(_){
            // Switch the tick format for the yAxis
            if(switchYAxisOrder !== _) {
                var y1 = y1Axis;
                y1Axis = y2Axis;
                y2Axis = y1;

                var y3 = y3Axis;
                y3Axis = y4Axis;
                y4Axis = y3;
            }
            switchYAxisOrder=_;

            y1Axis.orient('left');
            y2Axis.orient('right');
            y3Axis.orient('left');
            y4Axis.orient('right');
        }}
    });

    nv.utils.inheritOptions(chart, lines);
    nv.utils.initOptions(chart);

    return chart;
};

nv.models.multiBar = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 960
        , height = 500
        , x = d3.scale.ordinal()
        , y = d3.scale.linear()
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container = null
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
        , clipEdge = true
        , stacked = false
        , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
        , color = nv.utils.defaultColor()
        , hideable = false
        , barColor = null // adding the ability to set the color for each rather than the whole group
        , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
        , duration = 500
        , xDomain
        , yDomain
        , xRange
        , yRange
        , groupSpacing = 0.1
        , fillOpacity = 0.75
        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x0, y0 //used to store previous scales
        , renderWatch = nv.utils.renderWatch(dispatch, duration)
        ;

    var last_datalength = 0;

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);
            var nonStackableCount = 0;
            // This function defines the requirements for render complete
            var endFn = function(d, i) {
                if (d.series === data.length - 1 && i === data[0].values.length - 1)
                    return true;
                return false;
            };

            if(hideable && data.length) hideable = [{
                values: data[0].values.map(function(d) {
                        return {
                            x: d.x,
                            y: 0,
                            series: d.series,
                            size: 0.01
                        };}
                )}];

            if (stacked) {
                var parsed = d3.layout.stack()
                    .offset(stackOffset)
                    .values(function(d){ return d.values })
                    .y(getY)
                (!data.length && hideable ? hideable : data);

                parsed.forEach(function(series, i){
                    // if series is non-stackable, use un-parsed data
                    if (series.nonStackable) {
                        data[i].nonStackableSeries = nonStackableCount++;
                        parsed[i] = data[i];
                    } else {
                        // don't stack this seires on top of the nonStackable seriees
                        if (i > 0 && parsed[i - 1].nonStackable){
                            parsed[i].values.map(function(d,j){
                                d.y0 -= parsed[i - 1].values[j].y;
                                d.y1 = d.y0 + d.y;
                            });
                        }
                    }
                });
                data = parsed;
            }
            //add series index and key to each data point for reference
            data.forEach(function(series, i) {
                series.values.forEach(function(point) {
                    point.series = i;
                    point.key = series.key;
                });
            });

            // HACK for negative value stacking
            if (stacked && data.length > 0) {
                data[0].values.map(function(d,i) {
                    var posBase = 0, negBase = 0;
                    data.map(function(d, idx) {
                        if (!data[idx].nonStackable) {
                            var f = d.values[i]
                            f.size = Math.abs(f.y);
                            if (f.y<0)  {
                                f.y1 = negBase;
                                negBase = negBase - f.size;
                            } else
                            {
                                f.y1 = f.size + posBase;
                                posBase = posBase + f.size;
                            }
                        }

                    });
                });
            }
            // Setup Scales
            // remap and flatten the data for use in calculating the scales' domains
            var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
                data.map(function(d, idx) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
                    })
                });

            x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
                .rangeBands(xRange || [0, availableWidth], groupSpacing);

            y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
                var domain = d.y;
                // increase the domain range if this series is stackable
                if (stacked && !data[d.idx].nonStackable) {
                    if (d.y > 0){
                        domain = d.y1
                    } else {
                        domain = d.y1 + d.y
                    }
                }
                return domain;
            }).concat(forceY)))
            .range(yRange || [availableHeight, 0]);

            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
            if (x.domain()[0] === x.domain()[1])
                x.domain()[0] ?
                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
                    : x.domain([-1,1]);

            if (y.domain()[0] === y.domain()[1])
                y.domain()[0] ?
                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
                    : y.domain([-1,1]);

            x0 = x0 || x;
            y0 = y0 || y;

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-groups');
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            defsEnter.append('clipPath')
                .attr('id', 'nv-edge-clip-' + id)
                .append('rect');
            wrap.select('#nv-edge-clip-' + id + ' rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');

            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
                .data(function(d) { return d }, function(d,i) { return i });
            groups.enter().append('g')
                .style('stroke-opacity', 1e-6)
                .style('fill-opacity', 1e-6);

            var exitTransition = renderWatch
                .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
                .attr('y', function(d, i, j) {
                    var yVal = y0(0) || 0;
                    if (stacked) {
                        if (data[d.series] && !data[d.series].nonStackable) {
                            yVal = y0(d.y0);
                        }
                    }
                    return yVal;
                })
                .attr('height', 0)
                .remove();
            if (exitTransition.delay)
                exitTransition.delay(function(d,i) {
                    var delay = i * (duration / (last_datalength + 1)) - i;
                    return delay;
                });
            groups
                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
                .classed('hover', function(d) { return d.hover })
                .style('fill', function(d,i){ return color(d, i) })
                .style('stroke', function(d,i){ return color(d, i) });
            groups
                .style('stroke-opacity', 1)
                .style('fill-opacity', fillOpacity);

            var bars = groups.selectAll('rect.nv-bar')
                .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
            bars.exit().remove();

            var barsEnter = bars.enter().append('rect')
                    .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
                    .attr('x', function(d,i,j) {
                        return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
                    })
                    .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
                    .attr('height', 0)
                    .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
                    .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
                ;
            bars
                .style('fill', function(d,i,j){ return color(d, j, i);  })
                .style('stroke', function(d,i,j){ return color(d, j, i); })
                .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
                    d3.select(this).classed('hover', true);
                    dispatch.elementMouseover({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mouseout', function(d,i) {
                    d3.select(this).classed('hover', false);
                    dispatch.elementMouseout({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mousemove', function(d,i) {
                    dispatch.elementMousemove({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('click', function(d,i) {
                    var element = this;
                    dispatch.elementClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill"),
                        event: d3.event,
                        element: element
                    });
                    d3.event.stopPropagation();
                })
                .on('dblclick', function(d,i) {
                    dispatch.elementDblClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                    d3.event.stopPropagation();
                });
            bars
                .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
                .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })

            if (barColor) {
                if (!disabled) disabled = data.map(function() { return true });
                bars
                    .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); })
                    .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); });
            }

            var barSelection =
                bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
                    .delay(function(d,i) {
                        return i * duration / data[0].values.length;
                    });
            if (stacked){
                barSelection
                    .attr('y', function(d,i,j) {
                        var yVal = 0;
                        // if stackable, stack it on top of the previous series
                        if (!data[j].nonStackable) {
                            yVal = y(d.y1);
                        } else {
                            if (getY(d,i) < 0){
                                yVal = y(0);
                            } else {
                                if (y(0) - y(getY(d,i)) < -1){
                                    yVal = y(0) - 1;
                                } else {
                                    yVal = y(getY(d, i)) || 0;
                                }
                            }
                        }
                        return yVal;
                    })
                    .attr('height', function(d,i,j) {
                        if (!data[j].nonStackable) {
                            return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 0);
                        } else {
                            return Math.max(Math.abs(y(getY(d,i)) - y(0)), 0) || 0;
                        }
                    })
                    .attr('x', function(d,i,j) {
                        var width = 0;
                        if (data[j].nonStackable) {
                            width = d.series * x.rangeBand() / data.length;
                            if (data.length !== nonStackableCount){
                                width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
                            }
                        }
                        return width;
                    })
                    .attr('width', function(d,i,j){
                        if (!data[j].nonStackable) {
                            return x.rangeBand();
                        } else {
                            // if all series are nonStacable, take the full width
                            var width = (x.rangeBand() / nonStackableCount);
                            // otherwise, nonStackable graph will be only taking the half-width
                            // of the x rangeBand
                            if (data.length !== nonStackableCount) {
                                width = x.rangeBand()/(nonStackableCount*2);
                            }
                            return width;
                        }
                    });
            }
            else {
                barSelection
                    .attr('x', function(d,i) {
                        return d.series * x.rangeBand() / data.length;
                    })
                    .attr('width', x.rangeBand() / data.length)
                    .attr('y', function(d,i) {
                        return getY(d,i) < 0 ?
                            y(0) :
                                y(0) - y(getY(d,i)) < 1 ?
                            y(0) - 1 :
                            y(getY(d,i)) || 0;
                    })
                    .attr('height', function(d,i) {
                        return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
                    });
            }

            //store old scales for use in transitions on update
            x0 = x.copy();
            y0 = y.copy();

            // keep track of the last data value length for transition calculations
            if (data[0] && data[0].values) {
                last_datalength = data[0].values.length;
            }

        });

        renderWatch.renderEnd('multibar immediate');

        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:   {get: function(){return width;}, set: function(_){width=_;}},
        height:  {get: function(){return height;}, set: function(_){height=_;}},
        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
        stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
        stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
        clipEdge:    {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
        disabled:    {get: function(){return disabled;}, set: function(_){disabled=_;}},
        id:          {get: function(){return id;}, set: function(_){id=_;}},
        hideable:    {get: function(){return hideable;}, set: function(_){hideable=_;}},
        groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
        fillOpacity: {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        barColor:  {get: function(){return barColor;}, set: function(_){
            barColor = _ ? nv.utils.getColor(_) : null;
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};
nv.models.multiBarChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var multibar = nv.models.multiBar()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , interactiveLayer = nv.interactiveGuideline()
        , legend = nv.models.legend()
        , controls = nv.models.legend()
        , tooltip = nv.models.tooltip()
        ;

    var margin = {top: 30, right: 20, bottom: 50, left: 60}
        , marginTop = null
        , width = null
        , height = null
        , color = nv.utils.defaultColor()
        , showControls = true
        , controlLabels = {}
        , showLegend = true
        , showXAxis = true
        , showYAxis = true
        , rightAlignYAxis = false
        , reduceXTicks = true // if false a tick will show for every data point
        , staggerLabels = false
        , wrapLabels = false
        , rotateLabels = 0
        , x //can be accessed via chart.xScale()
        , y //can be accessed via chart.yScale()
        , state = nv.utils.state()
        , defaultState = null
        , noData = null
        , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
        , controlWidth = function() { return showControls ? 180 : 0 }
        , duration = 250
        , useInteractiveGuideline = false
        ;

    state.stacked = false // DEPRECATED Maintained for backward compatibility

    multibar.stacked(false);
    xAxis
        .orient('bottom')
        .tickPadding(7)
        .showMaxMin(false)
        .tickFormat(function(d) { return d })
    ;
    yAxis
        .orient((rightAlignYAxis) ? 'right' : 'left')
        .tickFormat(d3.format(',.1f'))
    ;

    tooltip
        .duration(0)
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        })
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        });

    interactiveLayer.tooltip
        .valueFormatter(function(d, i) {
            return d == null ? "N/A" : yAxis.tickFormat()(d, i);
        })
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        });

    interactiveLayer.tooltip
        .valueFormatter(function (d, i) {
            return d == null ? "N/A" : yAxis.tickFormat()(d, i);
        })
        .headerFormatter(function (d, i) {
            return xAxis.tickFormat()(d, i);
        });

    interactiveLayer.tooltip
        .duration(0)
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        })
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        });

    controls.updateState(false);

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);
    var stacked = false;

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled }),
                stacked: stacked
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.stacked !== undefined)
                stacked = state.stacked;
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        }
    };

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(multibar);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() {
                if (duration === 0)
                    container.call(chart);
                else
                    container.transition()
                        .duration(duration)
                        .call(chart);
            };
            chart.container = this;

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disableddisabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display noData message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = multibar.xScale();
            y = multibar.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis');
            gEnter.append('g').attr('class', 'nv-barsWrap');
            gEnter.append('g').attr('class', 'nv-legendWrap');
            gEnter.append('g').attr('class', 'nv-controlsWrap');
            gEnter.append('g').attr('class', 'nv-interactive');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                legend.width(availableWidth - controlWidth());

                g.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                g.select('.nv-legendWrap')
                    .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
            }

            // Controls
            if (!showControls) {
                 g.select('.nv-controlsWrap').selectAll('*').remove();
            } else {
                var controlsData = [
                    { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
                    { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
                ];

                controls.width(controlWidth()).color(['#444', '#444', '#444']);
                g.select('.nv-controlsWrap')
                    .datum(controlsData)
                    .attr('transform', 'translate(0,' + (-margin.top) +')')
                    .call(controls);
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            // Main Chart Component(s)
            multibar
                .disabled(data.map(function(series) { return series.disabled }))
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled }));


            var barsWrap = g.select('.nv-barsWrap')
                .datum(data.filter(function(d) { return !d.disabled }));

            barsWrap.call(multibar);

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize(-availableHeight, 0);

                g.select('.nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y.range()[0] + ')');
                g.select('.nv-x.nv-axis')
                    .call(xAxis);

                var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');

                xTicks
                    .selectAll('line, text')
                    .style('opacity', 1)

                if (staggerLabels) {
                    var getTranslate = function(x,y) {
                        return "translate(" + x + "," + y + ")";
                    };

                    var staggerUp = 5, staggerDown = 17;  //pixels to stagger by
                    // Issue #140
                    xTicks
                        .selectAll("text")
                        .attr('transform', function(d,i,j) {
                            return  getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
                        });

                    var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
                    g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
                        .attr("transform", function(d,i) {
                            return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
                        });
                }

                if (wrapLabels) {
                    g.selectAll('.tick text')
                        .call(nv.utils.wrapTicks, chart.xAxis.rangeBand())
                }

                if (reduceXTicks)
                    xTicks
                        .filter(function(d,i) {
                            return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
                        })
                        .selectAll('text, line')
                        .style('opacity', 0);

                if(rotateLabels)
                    xTicks
                        .selectAll('.tick text')
                        .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
                        .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');

                g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
                    .style('opacity', 1);
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);

                g.select('.nv-y.nv-axis')
                    .call(yAxis);
            }

            //Set up interactive layer
            if (useInteractiveGuideline) {
                interactiveLayer
                    .width(availableWidth)
                    .height(availableHeight)
                    .margin({left:margin.left, top:margin.top})
                    .svgContainer(container)
                    .xScale(x);
                wrap.select(".nv-interactive").call(interactiveLayer);
            }

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            controls.dispatch.on('legendClick', function(d,i) {
                if (!d.disabled) return;
                controlsData = controlsData.map(function(s) {
                    s.disabled = true;
                    return s;
                });
                d.disabled = false;

                switch (d.key) {
                    case 'Grouped':
                    case controlLabels.grouped:
                        multibar.stacked(false);
                        break;
                    case 'Stacked':
                    case controlLabels.stacked:
                        multibar.stacked(true);
                        break;
                }

                state.stacked = multibar.stacked();
                dispatch.stateChange(state);
                chart.update();
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });
                    state.disabled = e.disabled;
                }
                if (typeof e.stacked !== 'undefined') {
                    multibar.stacked(e.stacked);
                    state.stacked = e.stacked;
                    stacked = e.stacked;
                }
                chart.update();
            });

            if (useInteractiveGuideline) {
                interactiveLayer.dispatch.on('elementMousemove', function(e) {
                    if (e.pointXValue == undefined) return;

                    var singlePoint, pointIndex, pointXLocation, xValue, allData = [];
                    data
                        .filter(function(series, i) {
                            series.seriesIndex = i;
                            return !series.disabled;
                        })
                        .forEach(function(series,i) {
                            pointIndex = x.domain().indexOf(e.pointXValue)

                            var point = series.values[pointIndex];
                            if (point === undefined) return;

                            xValue = point.x;
                            if (singlePoint === undefined) singlePoint = point;
                            if (pointXLocation === undefined) pointXLocation = e.mouseX
                            allData.push({
                                key: series.key,
                                value: chart.y()(point, pointIndex),
                                color: color(series,series.seriesIndex),
                                data: series.values[pointIndex]
                            });
                        });

                    interactiveLayer.tooltip
                        .data({
                            value: xValue,
                            index: pointIndex,
                            series: allData
                        })();

                    interactiveLayer.renderGuideLine(pointXLocation);
                });

                interactiveLayer.dispatch.on("elementMouseout",function(e) {
                    interactiveLayer.tooltip.hidden(true);
                });
            }
            else {
                multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
                    evt.value = chart.x()(evt.data);
                    evt['series'] = {
                        key: evt.data.key,
                        value: chart.y()(evt.data),
                        color: evt.color
                    };
                    tooltip.data(evt).hidden(false);
                });

                multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true);
                });

                multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
                    tooltip();
                });
            }
        });

        renderWatch.renderEnd('multibarchart immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.multibar = multibar;
    chart.legend = legend;
    chart.controls = controls;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.state = state;
    chart.tooltip = tooltip;
    chart.interactiveLayer = interactiveLayer;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
        controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        reduceXTicks:    {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
        rotateLabels:    {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
        staggerLabels:    {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
        wrapLabels:   {get: function(){return wrapLabels;}, set: function(_){wrapLabels=!!_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            multibar.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
            renderWatch.reset(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
        }},
        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
            useInteractiveGuideline = _;
        }},
        barColor:  {get: function(){return multibar.barColor;}, set: function(_){
            multibar.barColor(_);
            legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
        }}
    });

    nv.utils.inheritOptions(chart, multibar);
    nv.utils.initOptions(chart);

    return chart;
};

nv.models.multiBarHorizontal = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 960
        , height = 500
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container = null
        , x = d3.scale.ordinal()
        , y = d3.scale.linear()
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , getYerr = function(d) { return d.yErr }
        , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
        , color = nv.utils.defaultColor()
        , barColor = null // adding the ability to set the color for each rather than the whole group
        , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
        , stacked = false
        , showValues = false
        , showBarLabels = false
        , valuePadding = 60
        , groupSpacing = 0.1
        , fillOpacity = 0.75
        , valueFormat = d3.format(',.2f')
        , delay = 1200
        , xDomain
        , yDomain
        , xRange
        , yRange
        , duration = 250
        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x0, y0; //used to store previous scales
    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);

            if (stacked)
                data = d3.layout.stack()
                    .offset('zero')
                    .values(function(d){ return d.values })
                    .y(getY)
                (data);

            //add series index and key to each data point for reference
            data.forEach(function(series, i) {
                series.values.forEach(function(point) {
                    point.series = i;
                    point.key = series.key;
                });
            });

            // HACK for negative value stacking
            if (stacked)
                data[0].values.map(function(d,i) {
                    var posBase = 0, negBase = 0;
                    data.map(function(d) {
                        var f = d.values[i]
                        f.size = Math.abs(f.y);
                        if (f.y<0)  {
                            f.y1 = negBase - f.size;
                            negBase = negBase - f.size;
                        } else
                        {
                            f.y1 = posBase;
                            posBase = posBase + f.size;
                        }
                    });
                });

            // Setup Scales
            // remap and flatten the data for use in calculating the scales' domains
            var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
                data.map(function(d) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
                    })
                });

            x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
                .rangeBands(xRange || [0, availableHeight], groupSpacing);

            y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY)))

            if (showValues && !stacked)
                y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
            else
                y.range(yRange || [0, availableWidth]);

            x0 = x0 || x;
            y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);

            // Setup containers and skeleton of chart
            var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-groups');
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
                .data(function(d) { return d }, function(d,i) { return i });
            groups.enter().append('g')
                .style('stroke-opacity', 1e-6)
                .style('fill-opacity', 1e-6);
            groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
                .style('stroke-opacity', 1e-6)
                .style('fill-opacity', 1e-6)
                .remove();
            groups
                .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
                .classed('hover', function(d) { return d.hover })
                .style('fill', function(d,i){ return color(d, i) })
                .style('stroke', function(d,i){ return color(d, i) });
            groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
                .style('stroke-opacity', 1)
                .style('fill-opacity', fillOpacity);

            var bars = groups.selectAll('g.nv-bar')
                .data(function(d) { return d.values });
            bars.exit().remove();

            var barsEnter = bars.enter().append('g')
                .attr('transform', function(d,i,j) {
                    return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
                });

            barsEnter.append('rect')
                .attr('width', 0)
                .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )

            bars
                .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
                    d3.select(this).classed('hover', true);
                    dispatch.elementMouseover({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mouseout', function(d,i) {
                    d3.select(this).classed('hover', false);
                    dispatch.elementMouseout({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mouseout', function(d,i) {
                    dispatch.elementMouseout({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('mousemove', function(d,i) {
                    dispatch.elementMousemove({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                })
                .on('click', function(d,i) {
                    var element = this;
                    dispatch.elementClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill"),
                        event: d3.event,
                        element: element
                    });
                    d3.event.stopPropagation();
                })
                .on('dblclick', function(d,i) {
                    dispatch.elementDblClick({
                        data: d,
                        index: i,
                        color: d3.select(this).style("fill")
                    });
                    d3.event.stopPropagation();
                });

            if (getYerr(data[0],0)) {
                barsEnter.append('polyline');

                bars.select('polyline')
                    .attr('fill', 'none')
                    .attr('points', function(d,i) {
                        var xerr = getYerr(d,i)
                            , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
                        xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
                        xerr = xerr.map(function(e) { return y(e) - y(0); });
                        var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
                        return a.map(function (path) { return path.join(',') }).join(' ');
                    })
                    .attr('transform', function(d,i) {
                        var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
                        return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
                    });
            }

            barsEnter.append('text');

            if (showValues && !stacked) {
                bars.select('text')
                    .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
                    .attr('y', x.rangeBand() / (data.length * 2))
                    .attr('dy', '.32em')
                    .text(function(d,i) {
                        var t = valueFormat(getY(d,i))
                            , yerr = getYerr(d,i);
                        if (yerr === undefined)
                            return t;
                        if (!yerr.length)
                            return t + '±' + valueFormat(Math.abs(yerr));
                        return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
                    });
                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
                    .select('text')
                    .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
            } else {
                bars.selectAll('text').text('');
            }

            if (showBarLabels && !stacked) {
                barsEnter.append('text').classed('nv-bar-label',true);
                bars.select('text.nv-bar-label')
                    .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
                    .attr('y', x.rangeBand() / (data.length * 2))
                    .attr('dy', '.32em')
                    .text(function(d,i) { return getX(d,i) });
                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
                    .select('text.nv-bar-label')
                    .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
            }
            else {
                bars.selectAll('text.nv-bar-label').text('');
            }

            bars
                .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})

            if (barColor) {
                if (!disabled) disabled = data.map(function() { return true });
                bars
                    .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); })
                    .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker(  disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i]  })[j]   ).toString(); });
            }

            if (stacked)
                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
                    .attr('transform', function(d,i) {
                        return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
                    })
                    .select('rect')
                    .attr('width', function(d,i) {
                        return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) || 0
                    })
                    .attr('height', x.rangeBand() );
            else
                bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
                    .attr('transform', function(d,i) {
                        //TODO: stacked must be all positive or all negative, not both?
                        return 'translate(' +
                            (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
                            + ',' +
                            (d.series * x.rangeBand() / data.length
                                +
                                x(getX(d,i)) )
                            + ')'
                    })
                    .select('rect')
                    .attr('height', x.rangeBand() / data.length )
                    .attr('width', function(d,i) {
                        return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0
                    });

            //store old scales for use in transitions on update
            x0 = x.copy();
            y0 = y.copy();

        });

        renderWatch.renderEnd('multibarHorizontal immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:   {get: function(){return width;}, set: function(_){width=_;}},
        height:  {get: function(){return height;}, set: function(_){height=_;}},
        x:       {get: function(){return getX;}, set: function(_){getX=_;}},
        y:       {get: function(){return getY;}, set: function(_){getY=_;}},
        yErr:       {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
        xScale:  {get: function(){return x;}, set: function(_){x=_;}},
        yScale:  {get: function(){return y;}, set: function(_){y=_;}},
        xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:  {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:  {get: function(){return yRange;}, set: function(_){yRange=_;}},
        forceY:  {get: function(){return forceY;}, set: function(_){forceY=_;}},
        stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
        showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
        // this shows the group name, seems pointless?
        //showBarLabels:    {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
        disabled:     {get: function(){return disabled;}, set: function(_){disabled=_;}},
        id:           {get: function(){return id;}, set: function(_){id=_;}},
        valueFormat:  {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
        valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
        groupSpacing: {get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
        fillOpacity:  {get: function(){return fillOpacity;}, set: function(_){fillOpacity=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        barColor:  {get: function(){return barColor;}, set: function(_){
            barColor = _ ? nv.utils.getColor(_) : null;
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};

nv.models.multiBarHorizontalChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var multibar = nv.models.multiBarHorizontal()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , legend = nv.models.legend().height(30)
        , controls = nv.models.legend().height(30)
        , tooltip = nv.models.tooltip()
        ;

    var margin = {top: 30, right: 20, bottom: 50, left: 60}
        , marginTop = null
        , width = null
        , height = null
        , color = nv.utils.defaultColor()
        , showControls = true
        , controlLabels = {}
        , showLegend = true
        , showXAxis = true
        , showYAxis = true
        , stacked = false
        , x //can be accessed via chart.xScale()
        , y //can be accessed via chart.yScale()
        , state = nv.utils.state()
        , defaultState = null
        , noData = null
        , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
        , controlWidth = function() { return showControls ? 180 : 0 }
        , duration = 250
        ;

    state.stacked = false; // DEPRECATED Maintained for backward compatibility

    multibar.stacked(stacked);

    xAxis
        .orient('left')
        .tickPadding(5)
        .showMaxMin(false)
        .tickFormat(function(d) { return d })
    ;
    yAxis
        .orient('bottom')
        .tickFormat(d3.format(',.1f'))
    ;

    tooltip
        .duration(0)
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        })
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        });

    controls.updateState(false);

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled }),
                stacked: stacked
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.stacked !== undefined)
                stacked = state.stacked;
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        }
    };

    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(multibar);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() { container.transition().duration(duration).call(chart) };
            chart.container = this;

            stacked = multibar.stacked();

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disableddisabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display No Data message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = multibar.xScale();
            y = multibar.yScale().clamp(true);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis')
                .append('g').attr('class', 'nv-zeroLine')
                .append('line');
            gEnter.append('g').attr('class', 'nv-barsWrap');
            gEnter.append('g').attr('class', 'nv-legendWrap');
            gEnter.append('g').attr('class', 'nv-controlsWrap');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                legend.width(availableWidth - controlWidth());

                g.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                g.select('.nv-legendWrap')
                    .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
            }

            // Controls
            if (!showControls) {
                 g.select('.nv-controlsWrap').selectAll('*').remove();
            } else {
                var controlsData = [
                    { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
                    { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
                ];

                controls.width(controlWidth()).color(['#444', '#444', '#444']);
                g.select('.nv-controlsWrap')
                    .datum(controlsData)
                    .attr('transform', 'translate(0,' + (-margin.top) +')')
                    .call(controls);
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // Main Chart Component(s)
            multibar
                .disabled(data.map(function(series) { return series.disabled }))
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled }));

            var barsWrap = g.select('.nv-barsWrap')
                .datum(data.filter(function(d) { return !d.disabled }));

            barsWrap.transition().call(multibar);

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
                    .tickSize(-availableWidth, 0);

                g.select('.nv-x.nv-axis').call(xAxis);

                var xTicks = g.select('.nv-x.nv-axis').selectAll('g');

                xTicks
                    .selectAll('line, text');
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize( -availableHeight, 0);

                g.select('.nv-y.nv-axis')
                    .attr('transform', 'translate(0,' + availableHeight + ')');
                g.select('.nv-y.nv-axis').call(yAxis);
            }

            // Zero line
            g.select(".nv-zeroLine line")
                .attr("x1", y(0))
                .attr("x2", y(0))
                .attr("y1", 0)
                .attr("y2", -availableHeight)
            ;

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            controls.dispatch.on('legendClick', function(d,i) {
                if (!d.disabled) return;
                controlsData = controlsData.map(function(s) {
                    s.disabled = true;
                    return s;
                });
                d.disabled = false;

                switch (d.key) {
                    case 'Grouped':
                    case controlLabels.grouped:
                        multibar.stacked(false);
                        break;
                    case 'Stacked':
                    case controlLabels.stacked:
                        multibar.stacked(true);
                        break;
                }

                state.stacked = multibar.stacked();
                dispatch.stateChange(state);
                stacked = multibar.stacked();

                chart.update();
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {

                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });

                    state.disabled = e.disabled;
                }

                if (typeof e.stacked !== 'undefined') {
                    multibar.stacked(e.stacked);
                    state.stacked = e.stacked;
                    stacked = e.stacked;
                }

                chart.update();
            });
        });
        renderWatch.renderEnd('multibar horizontal chart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt.value = chart.x()(evt.data);
        evt['series'] = {
            key: evt.data.key,
            value: chart.y()(evt.data),
            color: evt.color
        };
        tooltip.data(evt).hidden(false);
    });

    multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.multibar = multibar;
    chart.legend = legend;
    chart.controls = controls;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.state = state;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
        controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            multibar.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
        }},
        barColor:  {get: function(){return multibar.barColor;}, set: function(_){
            multibar.barColor(_);
            legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
        }}
    });

    nv.utils.inheritOptions(chart, multibar);
    nv.utils.initOptions(chart);

    return chart;
};
nv.models.multiChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 30, right: 20, bottom: 50, left: 60},
        marginTop = null,
        color = nv.utils.defaultColor(),
        width = null,
        height = null,
        showLegend = true,
        noData = null,
        yDomain1,
        yDomain2,
        getX = function(d) { return d.x },
        getY = function(d) { return d.y},
        interpolate = 'linear',
        useVoronoi = true,
        interactiveLayer = nv.interactiveGuideline(),
        useInteractiveGuideline = false,
        legendRightAxisHint = ' (right axis)',
        duration = 250
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x = d3.scale.linear(),
        yScale1 = d3.scale.linear(),
        yScale2 = d3.scale.linear(),

        lines1 = nv.models.line().yScale(yScale1).duration(duration),
        lines2 = nv.models.line().yScale(yScale2).duration(duration),

        scatters1 = nv.models.scatter().yScale(yScale1).duration(duration),
        scatters2 = nv.models.scatter().yScale(yScale2).duration(duration),

        bars1 = nv.models.multiBar().stacked(false).yScale(yScale1).duration(duration),
        bars2 = nv.models.multiBar().stacked(false).yScale(yScale2).duration(duration),

        stack1 = nv.models.stackedArea().yScale(yScale1).duration(duration),
        stack2 = nv.models.stackedArea().yScale(yScale2).duration(duration),

        xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5).duration(duration),
        yAxis1 = nv.models.axis().scale(yScale1).orient('left').duration(duration),
        yAxis2 = nv.models.axis().scale(yScale2).orient('right').duration(duration),

        legend = nv.models.legend().height(30),
        tooltip = nv.models.tooltip(),
        dispatch = d3.dispatch();

    var charts = [lines1, lines2, scatters1, scatters2, bars1, bars2, stack1, stack2];

    function chart(selection) {
        selection.each(function(data) {
            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);

            chart.update = function() { container.transition().call(chart); };
            chart.container = this;

            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
            var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
            var dataScatters1 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 1});
            var dataScatters2 = data.filter(function(d) {return d.type == 'scatter' && d.yAxis == 2});
            var dataBars1 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 1});
            var dataBars2 =  data.filter(function(d) {return d.type == 'bar'  && d.yAxis == 2});
            var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
            var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});

            // Display noData message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container);
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
                .map(function(d) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d), y: getY(d) }
                    })
                });

            var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
                .map(function(d) {
                    return d.values.map(function(d,i) {
                        return { x: getX(d), y: getY(d) }
                    })
                });

            x   .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x }))
                .range([0, availableWidth]);

            var wrap = container.selectAll('g.wrap.multiChart').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y1 nv-axis');
            gEnter.append('g').attr('class', 'nv-y2 nv-axis');
            gEnter.append('g').attr('class', 'stack1Wrap');
            gEnter.append('g').attr('class', 'stack2Wrap');
            gEnter.append('g').attr('class', 'bars1Wrap');
            gEnter.append('g').attr('class', 'bars2Wrap');
            gEnter.append('g').attr('class', 'scatters1Wrap');
            gEnter.append('g').attr('class', 'scatters2Wrap');
            gEnter.append('g').attr('class', 'lines1Wrap');
            gEnter.append('g').attr('class', 'lines2Wrap');
            gEnter.append('g').attr('class', 'legendWrap');
            gEnter.append('g').attr('class', 'nv-interactive');

            var g = wrap.select('g');

            var color_array = data.map(function(d,i) {
                return data[i].color || color(d, i);
            });

            // Legend
            if (!showLegend) {
                g.select('.legendWrap').selectAll('*').remove();
            } else {
                var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
                var legendXPosition = legend.align() ? legendWidth : 0;

                legend.width(legendWidth);
                legend.color(color_array);

                g.select('.legendWrap')
                    .datum(data.map(function(series) {
                        series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
                        series.key = series.originalKey + (series.yAxis == 1 ? '' : legendRightAxisHint);
                        return series;
                    }))
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                g.select('.legendWrap')
                    .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
            }

            lines1
                .width(availableWidth)
                .height(availableHeight)
                .interpolate(interpolate)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
            lines2
                .width(availableWidth)
                .height(availableHeight)
                .interpolate(interpolate)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
            scatters1
                .width(availableWidth)
                .height(availableHeight)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'scatter'}));
            scatters2
                .width(availableWidth)
                .height(availableHeight)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'scatter'}));
            bars1
                .width(availableWidth)
                .height(availableHeight)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
            bars2
                .width(availableWidth)
                .height(availableHeight)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
            stack1
                .width(availableWidth)
                .height(availableHeight)
                .interpolate(interpolate)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
            stack2
                .width(availableWidth)
                .height(availableHeight)
                .interpolate(interpolate)
                .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));

            g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            var lines1Wrap = g.select('.lines1Wrap')
                .datum(dataLines1.filter(function(d){return !d.disabled}));
            var scatters1Wrap = g.select('.scatters1Wrap')
                .datum(dataScatters1.filter(function(d){return !d.disabled}));
            var bars1Wrap = g.select('.bars1Wrap')
                .datum(dataBars1.filter(function(d){return !d.disabled}));
            var stack1Wrap = g.select('.stack1Wrap')
                .datum(dataStack1.filter(function(d){return !d.disabled}));
            var lines2Wrap = g.select('.lines2Wrap')
                .datum(dataLines2.filter(function(d){return !d.disabled}));
            var scatters2Wrap = g.select('.scatters2Wrap')
                .datum(dataScatters2.filter(function(d){return !d.disabled}));
            var bars2Wrap = g.select('.bars2Wrap')
                .datum(dataBars2.filter(function(d){return !d.disabled}));
            var stack2Wrap = g.select('.stack2Wrap')
                .datum(dataStack2.filter(function(d){return !d.disabled}));

            var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
                return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
            }).concat([{x:0, y:0}]) : [];
            var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
                return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
            }).concat([{x:0, y:0}]) : [];

            yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
                .range([0, availableHeight]);

            yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
                .range([0, availableHeight]);

            lines1.yDomain(yScale1.domain());
            scatters1.yDomain(yScale1.domain());
            bars1.yDomain(yScale1.domain());
            stack1.yDomain(yScale1.domain());

            lines2.yDomain(yScale2.domain());
            scatters2.yDomain(yScale2.domain());
            bars2.yDomain(yScale2.domain());
            stack2.yDomain(yScale2.domain());

            if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
            if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}

            if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
            if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}

            if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
            if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}

            if(dataScatters1.length){d3.transition(scatters1Wrap).call(scatters1);}
            if(dataScatters2.length){d3.transition(scatters2Wrap).call(scatters2);}

            xAxis
                ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                .tickSize(-availableHeight, 0);

            g.select('.nv-x.nv-axis')
                .attr('transform', 'translate(0,' + availableHeight + ')');
            d3.transition(g.select('.nv-x.nv-axis'))
                .call(xAxis);

            yAxis1
                ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                .tickSize( -availableWidth, 0);


            d3.transition(g.select('.nv-y1.nv-axis'))
                .call(yAxis1);

            yAxis2
                ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                .tickSize( -availableWidth, 0);

            d3.transition(g.select('.nv-y2.nv-axis'))
                .call(yAxis2);

            g.select('.nv-y1.nv-axis')
                .classed('nv-disabled', series1.length ? false : true)
                .attr('transform', 'translate(' + x.range()[0] + ',0)');

            g.select('.nv-y2.nv-axis')
                .classed('nv-disabled', series2.length ? false : true)
                .attr('transform', 'translate(' + x.range()[1] + ',0)');

            legend.dispatch.on('stateChange', function(newState) {
                chart.update();
            });

            if(useInteractiveGuideline){
                interactiveLayer
                    .width(availableWidth)
                    .height(availableHeight)
                    .margin({left:margin.left, top:margin.top})
                    .svgContainer(container)
                    .xScale(x);
                wrap.select(".nv-interactive").call(interactiveLayer);
            }

            //============================================================
            // Event Handling/Dispatching
            //------------------------------------------------------------

            function mouseover_line(evt) {
                var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
                evt.value = evt.point.x;
                evt.series = {
                    value: evt.point.y,
                    color: evt.point.color,
                    key: evt.series.key
                };
                tooltip
                    .duration(0)
                    .headerFormatter(function(d, i) {
                    	return xAxis.tickFormat()(d, i);
                    })
                    .valueFormatter(function(d, i) {
                        return yaxis.tickFormat()(d, i);
                    })
                    .data(evt)
                    .hidden(false);
            }

            function mouseover_scatter(evt) {
                var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
                evt.value = evt.point.x;
                evt.series = {
                    value: evt.point.y,
                    color: evt.point.color,
                    key: evt.series.key
                };
                tooltip
                    .duration(100)
                    .headerFormatter(function(d, i) {
                    	return xAxis.tickFormat()(d, i);
                    })
                    .valueFormatter(function(d, i) {
                        return yaxis.tickFormat()(d, i);
                    })
                    .data(evt)
                    .hidden(false);
            }

            function mouseover_stack(evt) {
                var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
                evt.point['x'] = stack1.x()(evt.point);
                evt.point['y'] = stack1.y()(evt.point);
                tooltip
                    .duration(0)
                    .headerFormatter(function(d, i) {
                    	return xAxis.tickFormat()(d, i);
                    })
                    .valueFormatter(function(d, i) {
                        return yaxis.tickFormat()(d, i);
                    })
                    .data(evt)
                    .hidden(false);
            }

            function mouseover_bar(evt) {
                var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;

                evt.value = bars1.x()(evt.data);
                evt['series'] = {
                    value: bars1.y()(evt.data),
                    color: evt.color,
                    key: evt.data.key
                };
                tooltip
                    .duration(0)
                    .headerFormatter(function(d, i) {
                    	return xAxis.tickFormat()(d, i);
                    })
                    .valueFormatter(function(d, i) {
                        return yaxis.tickFormat()(d, i);
                    })
                    .data(evt)
                    .hidden(false);
            }



            function clearHighlights() {
              for(var i=0, il=charts.length; i < il; i++){
                var chart = charts[i];
                try {
                  chart.clearHighlights();
                } catch(e){}
              }
            }

            function highlightPoint(serieIndex, pointIndex, b){
              for(var i=0, il=charts.length; i < il; i++){
                var chart = charts[i];
                try {
                  chart.highlightPoint(serieIndex, pointIndex, b);
                } catch(e){}
              }
            }

            if(useInteractiveGuideline){
                interactiveLayer.dispatch.on('elementMousemove', function(e) {
                    clearHighlights();
                    var singlePoint, pointIndex, pointXLocation, allData = [];
                    data
                    .filter(function(series, i) {
                        series.seriesIndex = i;
                        return !series.disabled;
                    })
                    .forEach(function(series,i) {
                        var extent = x.domain();
                        var currentValues = series.values.filter(function(d,i) {
                            return chart.x()(d,i) >= extent[0] && chart.x()(d,i) <= extent[1];
                        });

                        pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, chart.x());
                        var point = currentValues[pointIndex];
                        var pointYValue = chart.y()(point, pointIndex);
                        if (pointYValue !== null) {
                            highlightPoint(i, pointIndex, true);
                        }
                        if (point === undefined) return;
                        if (singlePoint === undefined) singlePoint = point;
                        if (pointXLocation === undefined) pointXLocation = x(chart.x()(point,pointIndex));
                        allData.push({
                            key: series.key,
                            value: pointYValue,
                            color: color(series,series.seriesIndex),
                            data: point,
                            yAxis: series.yAxis == 2 ? yAxis2 : yAxis1
                        });
                    });

                    var defaultValueFormatter = function(d,i) {
                        var yAxis = allData[i].yAxis;
                        return d == null ? "N/A" : yAxis.tickFormat()(d);
                    };

                    interactiveLayer.tooltip
                        .headerFormatter(function(d, i) {
                            return xAxis.tickFormat()(d, i);
                        })
                        .valueFormatter(interactiveLayer.tooltip.valueFormatter() || defaultValueFormatter)
                        .data({
                            value: chart.x()( singlePoint,pointIndex ),
                            index: pointIndex,
                            series: allData
                        })();

                    interactiveLayer.renderGuideLine(pointXLocation);
                });

                interactiveLayer.dispatch.on("elementMouseout",function(e) {
                    clearHighlights();
                });
            } else {
                lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
                lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
                lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true)
                });
                lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true)
                });

                scatters1.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
                scatters2.dispatch.on('elementMouseover.tooltip', mouseover_scatter);
                scatters1.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true)
                });
                scatters2.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true)
                });

                stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
                stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
                stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true)
                });
                stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true)
                });

                bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
                bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);

                bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true);
                });
                bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
                    tooltip.hidden(true);
                });
                bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
                    tooltip();
                });
                bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
                    tooltip();
                });
            }
        });

        return chart;
    }

    //============================================================
    // Global getters and setters
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.legend = legend;
    chart.lines1 = lines1;
    chart.lines2 = lines2;
    chart.scatters1 = scatters1;
    chart.scatters2 = scatters2;
    chart.bars1 = bars1;
    chart.bars2 = bars2;
    chart.stack1 = stack1;
    chart.stack2 = stack2;
    chart.xAxis = xAxis;
    chart.yAxis1 = yAxis1;
    chart.yAxis2 = yAxis2;
    chart.tooltip = tooltip;
    chart.interactiveLayer = interactiveLayer;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        yDomain1:      {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
        yDomain2:    {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
        legendRightAxisHint:    {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        x: {get: function(){return getX;}, set: function(_){
            getX = _;
            lines1.x(_);
            lines2.x(_);
            scatters1.x(_);
            scatters2.x(_);
            bars1.x(_);
            bars2.x(_);
            stack1.x(_);
            stack2.x(_);
        }},
        y: {get: function(){return getY;}, set: function(_){
            getY = _;
            lines1.y(_);
            lines2.y(_);
            scatters1.y(_);
            scatters2.y(_);
            stack1.y(_);
            stack2.y(_);
            bars1.y(_);
            bars2.y(_);
        }},
        useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
            useVoronoi=_;
            lines1.useVoronoi(_);
            lines2.useVoronoi(_);
            stack1.useVoronoi(_);
            stack2.useVoronoi(_);
        }},

        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
            useInteractiveGuideline = _;
            if (useInteractiveGuideline) {
                lines1.interactive(false);
                lines1.useVoronoi(false);
                lines2.interactive(false);
                lines2.useVoronoi(false);
                stack1.interactive(false);
                stack1.useVoronoi(false);
                stack2.interactive(false);
                stack2.useVoronoi(false);
                scatters1.interactive(false);
                scatters2.interactive(false);
            }
        }},

        duration: {get: function(){return duration;}, set: function(_) {
            duration = _;
            [lines1, lines2, stack1, stack2, scatters1, scatters2, xAxis, yAxis1, yAxis2].forEach(function(model){
              model.duration(duration);
            });
        }}
    });

    nv.utils.initOptions(chart);

    return chart;
};

nv.models.ohlcBar = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = null
        , height = null
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container = null
        , x = d3.scale.linear()
        , y = d3.scale.linear()
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , getOpen = function(d) { return d.open }
        , getClose = function(d) { return d.close }
        , getHigh = function(d) { return d.high }
        , getLow = function(d) { return d.low }
        , forceX = []
        , forceY = []
        , padData     = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
        , clipEdge = true
        , color = nv.utils.defaultColor()
        , interactive = false
        , xDomain
        , yDomain
        , xRange
        , yRange
        , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    function chart(selection) {
        selection.each(function(data) {
            container = d3.select(this);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            nv.utils.initSVG(container);

            // ohlc bar width.
            var w = (availableWidth / data[0].values.length) * .9;

            // Setup Scales
            x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));

            if (padData)
                x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
            else
                x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);

            y.domain(yDomain || [
                    d3.min(data[0].values.map(getLow).concat(forceY)),
                    d3.max(data[0].values.map(getHigh).concat(forceY))
                ]
            ).range(yRange || [availableHeight, 0]);

            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
            if (x.domain()[0] === x.domain()[1])
                x.domain()[0] ?
                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
                    : x.domain([-1,1]);

            if (y.domain()[0] === y.domain()[1])
                y.domain()[0] ?
                    y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
                    : y.domain([-1,1]);

            // Setup containers and skeleton of chart
            var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-ticks');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            container
                .on('click', function(d,i) {
                    dispatch.chartClick({
                        data: d,
                        index: i,
                        pos: d3.event,
                        id: id
                    });
                });

            defsEnter.append('clipPath')
                .attr('id', 'nv-chart-clip-path-' + id)
                .append('rect');

            wrap.select('#nv-chart-clip-path-' + id + ' rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            g   .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');

            var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
                .data(function(d) { return d });
            ticks.exit().remove();

            ticks.enter().append('path')
                .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
                .attr('d', function(d,i) {
                    return 'm0,0l0,'
                        + (y(getOpen(d,i))
                            - y(getHigh(d,i)))
                        + 'l'
                        + (-w/2)
                        + ',0l'
                        + (w/2)
                        + ',0l0,'
                        + (y(getLow(d,i)) - y(getOpen(d,i)))
                        + 'l0,'
                        + (y(getClose(d,i))
                            - y(getLow(d,i)))
                        + 'l'
                        + (w/2)
                        + ',0l'
                        + (-w/2)
                        + ',0z';
                })
                .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
                .attr('fill', function(d,i) { return color[0]; })
                .attr('stroke', function(d,i) { return color[0]; })
                .attr('x', 0 )
                .attr('y', function(d,i) {  return y(Math.max(0, getY(d,i))) })
                .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });

            // the bar colors are controlled by CSS currently
            ticks.attr('class', function(d,i,j) {
                return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
            });

            d3.transition(ticks)
                .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
                .attr('d', function(d,i) {
                    var w = (availableWidth / data[0].values.length) * .9;
                    return 'm0,0l0,'
                        + (y(getOpen(d,i))
                            - y(getHigh(d,i)))
                        + 'l'
                        + (-w/2)
                        + ',0l'
                        + (w/2)
                        + ',0l0,'
                        + (y(getLow(d,i))
                            - y(getOpen(d,i)))
                        + 'l0,'
                        + (y(getClose(d,i))
                            - y(getLow(d,i)))
                        + 'l'
                        + (w/2)
                        + ',0l'
                        + (-w/2)
                        + ',0z';
                });
        });

        return chart;
    }


    //Create methods to allow outside functions to highlight a specific bar.
    chart.highlightPoint = function(pointIndex, isHoverOver) {
        chart.clearHighlights();
        container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
            .classed("hover", isHoverOver)
        ;
    };

    chart.clearHighlights = function() {
        container.select(".nv-ohlcBar .nv-tick.hover")
            .classed("hover", false)
        ;
    };

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:    {get: function(){return width;}, set: function(_){width=_;}},
        height:   {get: function(){return height;}, set: function(_){height=_;}},
        xScale:   {get: function(){return x;}, set: function(_){x=_;}},
        yScale:   {get: function(){return y;}, set: function(_){y=_;}},
        xDomain:  {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain:  {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:   {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:   {get: function(){return yRange;}, set: function(_){yRange=_;}},
        forceX:   {get: function(){return forceX;}, set: function(_){forceX=_;}},
        forceY:   {get: function(){return forceY;}, set: function(_){forceY=_;}},
        padData:  {get: function(){return padData;}, set: function(_){padData=_;}},
        clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
        id:       {get: function(){return id;}, set: function(_){id=_;}},
        interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},

        x:     {get: function(){return getX;}, set: function(_){getX=_;}},
        y:     {get: function(){return getY;}, set: function(_){getY=_;}},
        open:  {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
        close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
        high:  {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
        low:   {get: function(){return getLow;}, set: function(_){getLow=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    != undefined ? _.top    : margin.top;
            margin.right  = _.right  != undefined ? _.right  : margin.right;
            margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   != undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);
    return chart;
};
// Code adapted from Jason Davies' "Parallel Coordinates"
// http://bl.ocks.org/jasondavies/1341281
nv.models.parallelCoordinates = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 30, right: 0, bottom: 10, left: 0}
        , width = null
        , height = null
        , availableWidth = null
        , availableHeight = null
        , x = d3.scale.ordinal()
        , y = {}
        , undefinedValuesLabel = "undefined values"
        , dimensionData = []
        , enabledDimensions = []
        , dimensionNames = []
        , displayBrush = true
        , color = nv.utils.defaultColor()
        , filters = []
        , active = []
        , dragging = []
        , axisWithUndefinedValues = []
        , lineTension = 1
        , foreground
        , background
        , dimensions
        , line = d3.svg.line()
        , axis = d3.svg.axis()
        , dispatch = d3.dispatch('brushstart', 'brush', 'brushEnd', 'dimensionsOrder', "stateChange", 'elementClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd', 'activeChanged')
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var container = d3.select(this);
            availableWidth = nv.utils.availableWidth(width, container, margin);
            availableHeight = nv.utils.availableHeight(height, container, margin);

            nv.utils.initSVG(container);

           //Convert old data to new format (name, values)
            if (data[0].values === undefined) {
                var newData = [];
                data.forEach(function (d) {
                        var val = {};
                        var key = Object.keys(d);
                        key.forEach(function (k) { if (k !== "name") val[k] = d[k] });
                        newData.push({ key: d.name, values: val });
                });
                data = newData;
            }

            var dataValues = data.map(function (d) {return d.values});
            if (active.length === 0) {
                active = data;
            }; //set all active before first brush call
            
            dimensionNames = dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; }).map(function (d) { return d.key });
            enabledDimensions = dimensionData.filter(function (d) { return !d.disabled; });
            
            // Setup Scales
            x.rangePoints([0, availableWidth], 1).domain(enabledDimensions.map(function (d) { return d.key; }));

            //Set as true if all values on an axis are missing.
            // Extract the list of dimensions and create a scale for each.
            var oldDomainMaxValue = {};
            var displayMissingValuesline = false;
            var currentTicks = [];
            
            dimensionNames.forEach(function(d) {
                var extent = d3.extent(dataValues, function (p) { return +p[d]; });
                var min = extent[0];
                var max = extent[1];
                var onlyUndefinedValues = false;
                //If there is no values to display on an axis, set the extent to 0
                if (isNaN(min) || isNaN(max)) {
                    onlyUndefinedValues = true;
                    min = 0;
                    max = 0;
                }
                //Scale axis if there is only one value
                if (min === max) {
                    min = min - 1;
                    max = max + 1;
                }
                var f = filters.filter(function (k) { return k.dimension == d; });
                if (f.length !== 0) {
                    //If there is only NaN values, keep the existing domain.
                    if (onlyUndefinedValues) {
                        min = y[d].domain()[0];
                        max = y[d].domain()[1];
                    }
                        //If the brush extent is > max (< min), keep the extent value.
                    else if (!f[0].hasOnlyNaN && displayBrush) {
                        min = min > f[0].extent[0] ? f[0].extent[0] : min;
                        max = max < f[0].extent[1] ? f[0].extent[1] : max;
                    }
                        //If there is NaN values brushed be sure the brush extent is on the domain.
                    else if (f[0].hasNaN) {
                        max = max < f[0].extent[1] ? f[0].extent[1] : max;
                        oldDomainMaxValue[d] = y[d].domain()[1];
                        displayMissingValuesline = true;
                    }
                }
                //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
                //The remaining 10% are used to display the missingValue line.
                y[d] = d3.scale.linear()
                    .domain([min, max])
                    .range([(availableHeight - 12) * 0.9, 0]);

                axisWithUndefinedValues = [];
                y[d].brush = d3.svg.brush().y(y[d]).on('brushstart', brushstart).on('brush', brush).on('brushend', brushend);
            });

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
            gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
            gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            line.interpolate('cardinal').tension(lineTension);
            axis.orient('left');
            var axisDrag = d3.behavior.drag()
                        .on('dragstart', dragStart)
                        .on('drag', dragMove)
                        .on('dragend', dragEnd);

            //Add missing value line at the bottom of the chart
            var missingValuesline, missingValueslineText;
            var step = x.range()[1] - x.range()[0];
            step = isNaN(step) ? x.range()[0] : step;
            if (!isNaN(step)) {
                var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
                missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
                missingValuesline.enter().append('line');
                missingValuesline.exit().remove();
                missingValuesline.attr("x1", function(d) { return d[0]; })
                        .attr("y1", function(d) { return d[1]; })
                        .attr("x2", function(d) { return d[2]; })
                        .attr("y2", function(d) { return d[3]; });
    
                //Add the text "undefined values" under the missing value line
                missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data([undefinedValuesLabel]);
                missingValueslineText.append('text').data([undefinedValuesLabel]);
                missingValueslineText.enter().append('text');
                missingValueslineText.exit().remove();
                missingValueslineText.attr("y", availableHeight)
                        //To have the text right align with the missingValues line, substract 92 representing the text size.
                        .attr("x", availableWidth - 92 - step / 2)
                        .text(function(d) { return d; });
            }
            // Add grey background lines for context.
            background = wrap.select('.background').selectAll('path').data(data);
            background.enter().append('path');
            background.exit().remove();
            background.attr('d', path);

            // Add blue foreground lines for focus.
            foreground = wrap.select('.foreground').selectAll('path').data(data);
            foreground.enter().append('path')
            foreground.exit().remove();
            foreground.attr('d', path)
                .style("stroke-width", function (d, i) {
                if (isNaN(d.strokeWidth)) { d.strokeWidth = 1;} return d.strokeWidth;})
                .attr('stroke', function (d, i) { return d.color || color(d, i); });
            foreground.on("mouseover", function (d, i) {
                d3.select(this).classed('hover', true).style("stroke-width", d.strokeWidth + 2 + "px").style("stroke-opacity", 1);
                dispatch.elementMouseover({
                    label: d.name,
                    color: d.color || color(d, i),
                    values: d.values,
                    dimensions: enabledDimensions
                });

            });
            foreground.on("mouseout", function (d, i) {
                d3.select(this).classed('hover', false).style("stroke-width", d.strokeWidth + "px").style("stroke-opacity", 0.7);
                dispatch.elementMouseout({
                    label: d.name,
                    index: i
                });
            });
            foreground.on('mousemove', function (d, i) {
                dispatch.elementMousemove();
            });
            foreground.on('click', function (d) {
                dispatch.elementClick({
                    id: d.id
                });
            });
            // Add a group element for each dimension.
            dimensions = g.selectAll('.dimension').data(enabledDimensions);
            var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');

            dimensions.attr('transform', function(d) { return 'translate(' + x(d.key) + ',0)'; });
            dimensionsEnter.append('g').attr('class', 'nv-axis');

            // Add an axis and title.
            dimensionsEnter.append('text')
                .attr('class', 'nv-label')
                .style("cursor", "move")
                .attr('dy', '-1em')
                .attr('text-anchor', 'middle')
                .on("mouseover", function(d, i) {
                    dispatch.elementMouseover({
                        label: d.tooltip || d.key,
                        color: d.color 
                    });
                })
                .on("mouseout", function(d, i) {
                    dispatch.elementMouseout({
                        label: d.tooltip
                    });
                })
                .on('mousemove', function (d, i) {
                    dispatch.elementMousemove();
                })
                .call(axisDrag);

            dimensionsEnter.append('g').attr('class', 'nv-brushBackground');
            dimensions.exit().remove();
            dimensions.select('.nv-label').text(function (d) { return d.key });

            // Add and store a brush for each axis.
            restoreBrush(displayBrush);

            var actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); }),
                    extents = actives.map(function (p) { return y[p].brush.extent(); });
            var formerActive = active.slice(0);

            //Restore active values
            active = [];
            foreground.style("display", function (d) {
                var isActive = actives.every(function (p, i) {
                    if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) {
                        return true;
                    }
                    return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
                });
                if (isActive)
                    active.push(d);
                return !isActive ? "none" : null;

            });

            if (filters.length > 0 || !nv.utils.arrayEquals(active, formerActive)) {
               dispatch.activeChanged(active);
            }

            // Returns the path for a given data point.
            function path(d) {
                return line(enabledDimensions.map(function (p) {
                    //If value if missing, put the value on the missing value line
                    if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key])) || displayMissingValuesline) {
                        var domain = y[p.key].domain();
                        var range = y[p.key].range();
                        var min = domain[0] - (domain[1] - domain[0]) / 9;

                        //If it's not already the case, allow brush to select undefined values
                        if (axisWithUndefinedValues.indexOf(p.key) < 0) {

                            var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
                            y[p.key].brush.y(newscale);
                            axisWithUndefinedValues.push(p.key);
                        }
                        if (isNaN(d.values[p.key]) || isNaN(parseFloat(d.values[p.key]))) {
                            return [x(p.key), y[p.key](min)];
                        }
                    }

                    //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
                    if (missingValuesline !== undefined) {
                        if (axisWithUndefinedValues.length > 0 || displayMissingValuesline) {
                            missingValuesline.style("display", "inline");
                            missingValueslineText.style("display", "inline");
                        } else {
                            missingValuesline.style("display", "none");
                            missingValueslineText.style("display", "none");
                        }
                    }
                    return [x(p.key), y[p.key](d.values[p.key])];
                }));
            }

            function restoreBrush(visible) {
                filters.forEach(function (f) {
                    //If filter brushed NaN values, keep the brush on the bottom of the axis.
                    var brushDomain = y[f.dimension].brush.y().domain();
                    if (f.hasOnlyNaN) {
                        f.extent[1] = (y[f.dimension].domain()[1] - brushDomain[0]) * (f.extent[1] - f.extent[0]) / (oldDomainMaxValue[f.dimension] - f.extent[0]) + brushDomain[0];
                    }
                    if (f.hasNaN) {
                        f.extent[0] = brushDomain[0];
                    }
                    if (visible)
                        y[f.dimension].brush.extent(f.extent);
                });
                
                dimensions.select('.nv-brushBackground')
                    .each(function (d) {
                        d3.select(this).call(y[d.key].brush);

                    })
                    .selectAll('rect')
                    .attr('x', -8)
                    .attr('width', 16);
                
                updateTicks();
            }
            
            // Handles a brush event, toggling the display of foreground lines.
            function brushstart() {
                //If brush aren't visible, show it before brushing again.
                if (displayBrush === false) {
                    displayBrush = true;
                    restoreBrush(true);
                }
            }
            
            // Handles a brush event, toggling the display of foreground lines.
            function brush() {
                actives = dimensionNames.filter(function (p) { return !y[p].brush.empty(); });
                extents = actives.map(function(p) { return y[p].brush.extent(); });

                filters = []; //erase current filters
                actives.forEach(function(d,i) {
                    filters[i] = {
                        dimension: d,
                        extent: extents[i],
                        hasNaN: false,
                        hasOnlyNaN: false
                    }
                });

                active = []; //erase current active list
                foreground.style('display', function(d) {
                    var isActive = actives.every(function(p, i) {
                        if ((isNaN(d.values[p]) || isNaN(parseFloat(d.values[p]))) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
                        return (extents[i][0] <= d.values[p] && d.values[p] <= extents[i][1]) && !isNaN(parseFloat(d.values[p]));
                    });
                    if (isActive) active.push(d);
                    return isActive ? null : 'none';
                });
                
                updateTicks();
                
                dispatch.brush({
                    filters: filters,
                    active: active
                });
            }
            function brushend() {
                var hasActiveBrush = actives.length > 0 ? true : false;
                filters.forEach(function (f) {
                    if (f.extent[0] === y[f.dimension].brush.y().domain()[0] && axisWithUndefinedValues.indexOf(f.dimension) >= 0)
                        f.hasNaN = true;
                    if (f.extent[1] < y[f.dimension].domain()[0])
                        f.hasOnlyNaN = true;
                });
                dispatch.brushEnd(active, hasActiveBrush);
            }           
            function updateTicks() {
                dimensions.select('.nv-axis')
                    .each(function (d, i) {
                        var f = filters.filter(function (k) { return k.dimension == d.key; });
                        currentTicks[d.key] = y[d.key].domain();
                        
                        //If brush are available, display brush extent
                        if (f.length != 0 && displayBrush)
                        {
                            currentTicks[d.key] = [];
                            if (f[0].extent[1] > y[d.key].domain()[0]) 
                                currentTicks[d.key] = [f[0].extent[1]];
                            if (f[0].extent[0] >= y[d.key].domain()[0])
                                currentTicks[d.key].push(f[0].extent[0]);    
                        }
                            
                        d3.select(this).call(axis.scale(y[d.key]).tickFormat(d.format).tickValues(currentTicks[d.key]));
                });
            }
            function dragStart(d) {
                dragging[d.key] = this.parentNode.__origin__ = x(d.key);
                background.attr("visibility", "hidden");
            }
            function dragMove(d) {
                dragging[d.key] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
                foreground.attr("d", path);
                enabledDimensions.sort(function (a, b) { return dimensionPosition(a.key) - dimensionPosition(b.key); });
                enabledDimensions.forEach(function (d, i) { return d.currentPosition = i; });
                x.domain(enabledDimensions.map(function (d) { return d.key; }));
                dimensions.attr("transform", function(d) { return "translate(" + dimensionPosition(d.key) + ")"; });
            }
            function dragEnd(d, i) {
                delete this.parentNode.__origin__;
                delete dragging[d.key];
                d3.select(this.parentNode).attr("transform", "translate(" + x(d.key) + ")");
                foreground
                  .attr("d", path);
                background
                  .attr("d", path)
                  .attr("visibility", null);

                dispatch.dimensionsOrder(enabledDimensions);
            }
            function dimensionPosition(d) {
                var v = dragging[d];
                return v == null ? x(d) : v;
            }
        });
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:         {get: function(){return width;},           set: function(_){width= _;}},
        height:        {get: function(){return height;},          set: function(_){height= _;}},
        dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
        displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
        filters: { get: function () { return filters; }, set: function (_) { filters = _; } },
        active: { get: function () { return active; }, set: function (_) { active = _; } },
        lineTension:   {get: function(){return lineTension;},     set: function(_){lineTension = _;}},
        undefinedValuesLabel : {get: function(){return undefinedValuesLabel;}, set: function(_){undefinedValuesLabel=_;}},
        
        // deprecated options
        dimensions: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
            // deprecated after 1.8.1
            nv.deprecated('dimensions', 'use dimensionData instead');
            if (dimensionData.length === 0) {
                _.forEach(function (k) { dimensionData.push({ key: k }) })
            } else {
                _.forEach(function (k, i) { dimensionData[i].key= k })
            }
        }},
        dimensionNames: {get: function () { return dimensionData.map(function (d){return d.key}); }, set: function (_) {
            // deprecated after 1.8.1
            nv.deprecated('dimensionNames', 'use dimensionData instead');
            dimensionNames = [];
            if (dimensionData.length === 0) {
                _.forEach(function (k) { dimensionData.push({ key: k }) })
            } else {
                _.forEach(function (k, i) { dimensionData[i].key = k })
            }
 
        }},
        dimensionFormats: {get: function () { return dimensionData.map(function (d) { return d.format }); }, set: function (_) {
            // deprecated after 1.8.1
            nv.deprecated('dimensionFormats', 'use dimensionData instead');
            if (dimensionData.length === 0) {
                _.forEach(function (f) { dimensionData.push({ format: f }) })
            } else {
                _.forEach(function (f, i) { dimensionData[i].format = f })
            }

        }},
        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    =  _.top    !== undefined ? _.top    : margin.top;
            margin.right  =  _.right  !== undefined ? _.right  : margin.right;
            margin.bottom =  _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   =  _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });
    nv.utils.initOptions(chart);
    return chart;
};
nv.models.parallelCoordinatesChart = function () {
        "use strict";
        //============================================================
        // Public Variables with Default Settings
        //------------------------------------------------------------

        var parallelCoordinates = nv.models.parallelCoordinates()
        var legend = nv.models.legend()
        var tooltip = nv.models.tooltip();
        var dimensionTooltip = nv.models.tooltip();

        var margin = { top: 0, right: 0, bottom: 0, left: 0 }
        , marginTop = null
        , width = null
        , height = null
        , showLegend = true
        , color = nv.utils.defaultColor()
        , state = nv.utils.state()
        , dimensionData = []
        , displayBrush = true
        , defaultState = null
        , noData = null
        , nanValue = "undefined"
        , dispatch = d3.dispatch('dimensionsOrder', 'brushEnd', 'stateChange', 'changeState', 'renderEnd')
        , controlWidth = function () { return showControls ? 180 : 0 }
        ;

	    //============================================================

		//============================================================
        // Private Variables
        //------------------------------------------------------------

        var renderWatch = nv.utils.renderWatch(dispatch);

        var stateGetter = function(data) {
            return function() {
                return {
                    active: data.map(function(d) { return !d.disabled })
                };
            }
        };

        var stateSetter = function(data) {
            return function(state) {
                if(state.active !== undefined) {
                    data.forEach(function(series, i) {
                        series.disabled = !state.active[i];
                    });
                }
            }
        };

        tooltip.contentGenerator(function(data) {
            var str = '<table><thead><tr><td class="legend-color-guide"><div style="background-color:' + data.color + '"></div></td><td><strong>' + data.key + '</strong></td></tr></thead>';
            if(data.series.length !== 0)
            {
                str = str + '<tbody><tr><td height ="10px"></td></tr>';
                data.series.forEach(function(d){
                    str = str + '<tr><td class="legend-color-guide"><div style="background-color:' + d.color + '"></div></td><td class="key">' + d.key + '</td><td class="value">' + d.value + '</td></tr>';
                });
                str = str + '</tbody>';
            }
            str = str + '</table>';
            return str;
        });

        //============================================================
        // Chart function
        //------------------------------------------------------------

        function chart(selection) {
            renderWatch.reset();
            renderWatch.models(parallelCoordinates);

            selection.each(function(data) {
                var container = d3.select(this);
                nv.utils.initSVG(container);

                var that = this;

                var availableWidth = nv.utils.availableWidth(width, container, margin),
                    availableHeight = nv.utils.availableHeight(height, container, margin);

                chart.update = function() { container.call(chart); };
                chart.container = this;

                state.setter(stateSetter(dimensionData), chart.update)
                    .getter(stateGetter(dimensionData))
                    .update();

                //set state.disabled
                state.disabled = dimensionData.map(function (d) { return !!d.disabled });

                //Keep dimensions position in memory
                dimensionData = dimensionData.map(function (d) {d.disabled = !!d.disabled; return d});
                dimensionData.forEach(function (d, i) {
                    d.originalPosition = isNaN(d.originalPosition) ? i : d.originalPosition;
                    d.currentPosition = isNaN(d.currentPosition) ? i : d.currentPosition;
                });

               if (!defaultState) {
                    var key;
                    defaultState = {};
                    for(key in state) {
                        if(state[key] instanceof Array)
                            defaultState[key] = state[key].slice(0);
                        else
                            defaultState[key] = state[key];
                    }
                }

                // Display No Data message if there's nothing to show.
                if(!data || !data.length) {
                    nv.utils.noData(chart, container);
                    return chart;
                } else {
                    container.selectAll('.nv-noData').remove();
                }

                //------------------------------------------------------------
                // Setup containers and skeleton of chart

                var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinatesChart').data([data]);
                var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinatesChart').append('g');

                var g = wrap.select('g');

                gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
                gEnter.append('g').attr('class', 'nv-legendWrap');

                g.select("rect")
                    .attr("width", availableWidth)
                    .attr("height", (availableHeight > 0) ? availableHeight : 0);

                // Legend
                if (!showLegend) {
                    g.select('.nv-legendWrap').selectAll('*').remove();
                } else {
                    legend.width(availableWidth)
                        .color(function (d) { return "rgb(188,190,192)"; });

                    g.select('.nv-legendWrap')
                        .datum(dimensionData.sort(function (a, b) { return a.originalPosition - b.originalPosition; }))
                        .call(legend);

                    if (!marginTop && legend.height() !== margin.top) {
                        margin.top = legend.height();
                        availableHeight = nv.utils.availableHeight(height, container, margin);
                    }
                    wrap.select('.nv-legendWrap')
                       .attr('transform', 'translate( 0 ,' + (-margin.top) + ')');
                }
                wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

                // Main Chart Component(s)
                parallelCoordinates
                    .width(availableWidth)
                    .height(availableHeight)
                    .dimensionData(dimensionData)
                    .displayBrush(displayBrush);

		        var parallelCoordinatesWrap = g.select('.nv-parallelCoordinatesWrap ')
                  .datum(data);

		        parallelCoordinatesWrap.transition().call(parallelCoordinates);

				//============================================================
                // Event Handling/Dispatching (in chart's scope)
                //------------------------------------------------------------
                //Display reset brush button
		        parallelCoordinates.dispatch.on('brushEnd', function (active, hasActiveBrush) {
		            if (hasActiveBrush) {
		                displayBrush = true;
		                dispatch.brushEnd(active);
		            } else {

		                displayBrush = false;
		            }
		        });

		        legend.dispatch.on('stateChange', function(newState) {
		            for(var key in newState) {
		                state[key] = newState[key];
		            }
		            dispatch.stateChange(state);
		            chart.update();
		        });

                //Update dimensions order and display reset sorting button
		        parallelCoordinates.dispatch.on('dimensionsOrder', function (e) {
		            dimensionData.sort(function (a, b) { return a.currentPosition - b.currentPosition; });
		            var isSorted = false;
		            dimensionData.forEach(function (d, i) {
		                d.currentPosition = i;
		                if (d.currentPosition !== d.originalPosition)
		                    isSorted = true;
		            });
		            dispatch.dimensionsOrder(dimensionData, isSorted);
		        });

				// Update chart from a state object passed to event handler
                dispatch.on('changeState', function (e) {

                    if (typeof e.disabled !== 'undefined') {
                        dimensionData.forEach(function (series, i) {
                            series.disabled = e.disabled[i];
                        });
                        state.disabled = e.disabled;
                    }
                    chart.update();
                });
            });

            renderWatch.renderEnd('parraleleCoordinateChart immediate');
            return chart;
        }

		//============================================================
        // Event Handling/Dispatching (out of chart's scope)
        //------------------------------------------------------------

        parallelCoordinates.dispatch.on('elementMouseover.tooltip', function (evt) {
            var tp = {
                key: evt.label,
                color: evt.color,
                series: []
             }
            if(evt.values){
                Object.keys(evt.values).forEach(function (d) {
                    var dim = evt.dimensions.filter(function (dd) {return dd.key === d;})[0];
                    if(dim){
                        var v;
                        if (isNaN(evt.values[d]) || isNaN(parseFloat(evt.values[d]))) {
                            v = nanValue;
                        } else {
                            v = dim.format(evt.values[d]);
                        }
                        tp.series.push({ idx: dim.currentPosition, key: d, value: v, color: dim.color });
                    }
                });
                tp.series.sort(function(a,b) {return a.idx - b.idx});
             }
            tooltip.data(tp).hidden(false);
        });

        parallelCoordinates.dispatch.on('elementMouseout.tooltip', function(evt) {
            tooltip.hidden(true)
        });

        parallelCoordinates.dispatch.on('elementMousemove.tooltip', function () {
            tooltip();
        });
		 //============================================================
        // Expose Public Variables
        //------------------------------------------------------------

		// expose chart's sub-components
        chart.dispatch = dispatch;
        chart.parallelCoordinates = parallelCoordinates;
        chart.legend = legend;
        chart.tooltip = tooltip;
        chart.options = nv.utils.optionsFunc.bind(chart);

        chart._options = Object.create({}, {
            // simple options, just get/set the necessary values
            width: { get: function () { return width; }, set: function (_) { width = _; } },
            height: { get: function () { return height; }, set: function (_) { height = _; } },
            showLegend: { get: function () { return showLegend; }, set: function (_) { showLegend = _; } },
            defaultState: { get: function () { return defaultState; }, set: function (_) { defaultState = _; } },
            dimensionData: { get: function () { return dimensionData; }, set: function (_) { dimensionData = _; } },
            displayBrush: { get: function () { return displayBrush; }, set: function (_) { displayBrush = _; } },
            noData: { get: function () { return noData; }, set: function (_) { noData = _; } },
            nanValue: { get: function () { return nanValue; }, set: function (_) { nanValue = _; } },

            // options that require extra logic in the setter
            margin: {
                get: function () { return margin; },
                set: function (_) {
                    if (_.top !== undefined) {
                        margin.top = _.top;
                        marginTop = _.top;
                    }
                    margin.right = _.right !== undefined ? _.right : margin.right;
                    margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
                    margin.left = _.left !== undefined ? _.left : margin.left;
                }
            },
            color: {get: function(){return color;}, set: function(_){
                    color = nv.utils.getColor(_);
                    legend.color(color);
                    parallelCoordinates.color(color);
                }}
        });

        nv.utils.inheritOptions(chart, parallelCoordinates);
        nv.utils.initOptions(chart);

        return chart;
    };
nv.models.pie = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 500
        , height = 500
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container = null
        , color = nv.utils.defaultColor()
        , valueFormat = d3.format(',.2f')
        , showLabels = true
        , labelsOutside = false
        , labelType = "key"
        , labelThreshold = .02 //if slice percentage is under this, don't show label
        , donut = false
        , title = false
        , growOnHover = true
        , titleOffset = 0
        , labelSunbeamLayout = false
        , startAngle = false
        , padAngle = false
        , endAngle = false
        , cornerRadius = 0
        , donutRatio = 0.5
        , duration = 250
        , arcsRadius = []
        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
        ;

    var arcs = [];
    var arcsOver = [];

    //============================================================
    // chart function
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right
                , availableHeight = height - margin.top - margin.bottom
                , radius = Math.min(availableWidth, availableHeight) / 2
                , arcsRadiusOuter = []
                , arcsRadiusInner = []
                ;

            container = d3.select(this)
            if (arcsRadius.length === 0) {
                var outer = radius - radius / 5;
                var inner = donutRatio * radius;
                for (var i = 0; i < data[0].length; i++) {
                    arcsRadiusOuter.push(outer);
                    arcsRadiusInner.push(inner);
                }
            } else {
                if(growOnHover){
                    arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
                    arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
                    donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
                } else {
                    arcsRadiusOuter = arcsRadius.map(function (d) { return d.outer * radius; });
                    arcsRadiusInner = arcsRadius.map(function (d) { return d.inner * radius; });
                    donutRatio = d3.min(arcsRadius.map(function (d) { return d.inner; }));
                }
            }
            nv.utils.initSVG(container);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
            var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');
            var g_pie = gEnter.append('g').attr('class', 'nv-pie');
            gEnter.append('g').attr('class', 'nv-pieLabels');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
            g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
            g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');

            //
            container.on('click', function(d,i) {
                dispatch.chartClick({
                    data: d,
                    index: i,
                    pos: d3.event,
                    id: id
                });
            });

            arcs = [];
            arcsOver = [];
            for (var i = 0; i < data[0].length; i++) {

                var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
                var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);

                if (startAngle !== false) {
                    arc.startAngle(startAngle);
                    arcOver.startAngle(startAngle);
                }
                if (endAngle !== false) {
                    arc.endAngle(endAngle);
                    arcOver.endAngle(endAngle);
                }
                if (donut) {
                    arc.innerRadius(arcsRadiusInner[i]);
                    arcOver.innerRadius(arcsRadiusInner[i]);
                }

                if (arc.cornerRadius && cornerRadius) {
                    arc.cornerRadius(cornerRadius);
                    arcOver.cornerRadius(cornerRadius);
                }

                arcs.push(arc);
                arcsOver.push(arcOver);
            }

            // Setup the Pie chart and choose the data element
            var pie = d3.layout.pie()
                .sort(null)
                .value(function(d) { return d.disabled ? 0 : getY(d) });

            // padAngle added in d3 3.5
            if (pie.padAngle && padAngle) {
                pie.padAngle(padAngle);
            }

            // if title is specified and donut, put it in the middle
            if (donut && title) {
                g_pie.append("text").attr('class', 'nv-pie-title');

                wrap.select('.nv-pie-title')
                    .style("text-anchor", "middle")
                    .text(function (d) {
                        return title;
                    })
                    .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
                    .attr("dy", "0.35em") // trick to vertically center text
                    .attr('transform', function(d, i) {
                        return 'translate(0, '+ titleOffset + ')';
                    });
            }

            var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
            var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);

            slices.exit().remove();
            pieLabels.exit().remove();

            var ae = slices.enter().append('g');
            ae.attr('class', 'nv-slice');
            ae.on('mouseover', function(d, i) {
                d3.select(this).classed('hover', true);
                if (growOnHover) {
                    d3.select(this).select("path").transition()
                        .duration(70)
                        .attr("d", arcsOver[i]);
                }
                dispatch.elementMouseover({
                    data: d.data,
                    index: i,
                    color: d3.select(this).style("fill"),
                    percent: (d.endAngle - d.startAngle) / (2 * Math.PI)
                });
            });
            ae.on('mouseout', function(d, i) {
                d3.select(this).classed('hover', false);
                if (growOnHover) {
                    d3.select(this).select("path").transition()
                        .duration(50)
                        .attr("d", arcs[i]);
                }
                dispatch.elementMouseout({data: d.data, index: i});
            });
            ae.on('mousemove', function(d, i) {
                dispatch.elementMousemove({data: d.data, index: i});
            });
            ae.on('click', function(d, i) {
                var element = this;
                dispatch.elementClick({
                    data: d.data,
                    index: i,
                    color: d3.select(this).style("fill"),
                    event: d3.event,
                    element: element
                });
            });
            ae.on('dblclick', function(d, i) {
                dispatch.elementDblClick({
                    data: d.data,
                    index: i,
                    color: d3.select(this).style("fill")
                });
            });

            slices.attr('fill', function(d,i) { return color(d.data, i); });
            slices.attr('stroke', function(d,i) { return color(d.data, i); });

            var paths = ae.append('path').each(function(d) {
                this._current = d;
            });

            slices.select('path')
                .transition()
                .duration(duration)
                .attr('d', function (d, i) { return arcs[i](d); })
                .attrTween('d', arcTween);

            if (showLabels) {
                // This does the normal label
                var labelsArc = [];
                for (var i = 0; i < data[0].length; i++) {
                    labelsArc.push(arcs[i]);

                    if (labelsOutside) {
                        if (donut) {
                            labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
                            if (startAngle !== false) labelsArc[i].startAngle(startAngle);
                            if (endAngle !== false) labelsArc[i].endAngle(endAngle);
                        }
                    } else if (!donut) {
                            labelsArc[i].innerRadius(0);
                    }
                }

                pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
                    var group = d3.select(this);

                    group.attr('transform', function (d, i) {
                        if (labelSunbeamLayout) {
                            d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
                            d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
                            var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
                            if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
                                rotateAngle -= 90;
                            } else {
                                rotateAngle += 90;
                            }
                            return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
                        } else {
                            d.outerRadius = radius + 10; // Set Outer Coordinate
                            d.innerRadius = radius + 15; // Set Inner Coordinate
                            return 'translate(' + labelsArc[i].centroid(d) + ')'
                        }
                    });

                    group.append('rect')
                        .style('stroke', '#fff')
                        .style('fill', '#fff')
                        .attr("rx", 3)
                        .attr("ry", 3);

                    group.append('text')
                        .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
                        .style('fill', '#000')
                });

                var labelLocationHash = {};
                var avgHeight = 14;
                var avgWidth = 140;
                var createHashKey = function(coordinates) {
                    return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
                };
                var getSlicePercentage = function(d) {
                    return (d.endAngle - d.startAngle) / (2 * Math.PI);
                };

                pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
                    if (labelSunbeamLayout) {
                        d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
                        d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
                        var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
                        if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
                            rotateAngle -= 90;
                        } else {
                            rotateAngle += 90;
                        }
                        return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
                    } else {
                        d.outerRadius = radius + 10; // Set Outer Coordinate
                        d.innerRadius = radius + 15; // Set Inner Coordinate

                        /*
                        Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
                        Each label location is hashed, and if a hash collision occurs, we assume an overlap.
                        Adjust the label's y-position to remove the overlap.
                        */
                        var center = labelsArc[i].centroid(d);
                        var percent = getSlicePercentage(d);
                        if (d.value && percent >= labelThreshold) {
                            var hashKey = createHashKey(center);
                            if (labelLocationHash[hashKey]) {
                                center[1] -= avgHeight;
                            }
                            labelLocationHash[createHashKey(center)] = true;
                        }
                        return 'translate(' + center + ')'
                    }
                });

                pieLabels.select(".nv-label text")
                    .style('text-anchor', function(d,i) {
                        //center the text on it's origin or begin/end if orthogonal aligned
                        return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
                    })
                    .text(function(d, i) {
                        var percent = getSlicePercentage(d);
                        var label = '';
                        if (!d.value || percent < labelThreshold) return '';

                        if(typeof labelType === 'function') {
                            label = labelType(d, i, {
                                'key': getX(d.data),
                                'value': getY(d.data),
                                'percent': valueFormat(percent)
                            });
                        } else {
                            switch (labelType) {
                                case 'key':
                                    label = getX(d.data);
                                    break;
                                case 'value':
                                    label = valueFormat(getY(d.data));
                                    break;
                                case 'percent':
                                    label = d3.format('%')(percent);
                                    break;
                            }
                        }
                        return label;
                    })
                ;
            }


            // Computes the angle of an arc, converting from radians to degrees.
            function angle(d) {
                var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
                return a > 90 ? a - 180 : a;
            }

            function arcTween(a, idx) {
                a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
                a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
                if (!donut) a.innerRadius = 0;
                var i = d3.interpolate(this._current, a);
                this._current = i(0);
                return function (t) {
                    return arcs[idx](i(t));
                };
            }
        });

        renderWatch.renderEnd('pie immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
        title:      {get: function(){return title;}, set: function(_){title=_;}},
        titleOffset:    {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
        labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
        valueFormat:    {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
        x:          {get: function(){return getX;}, set: function(_){getX=_;}},
        id:         {get: function(){return id;}, set: function(_){id=_;}},
        endAngle:   {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
        startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
        padAngle:   {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
        cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
        donutRatio:   {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
        labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
        labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
        donut:              {get: function(){return donut;}, set: function(_){donut=_;}},
        growOnHover:        {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},

        // depreciated after 1.7.1
        pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
            labelsOutside=_;
            nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
        }},
        // depreciated after 1.7.1
        donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
            labelsOutside=_;
            nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
        }},
        // deprecated after 1.7.1
        labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
            valueFormat=_;
            nv.deprecated('labelFormat','use valueFormat instead');
        }},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
            margin.right  = typeof _.right  != 'undefined' ? _.right  : margin.right;
            margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
            margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
        }},
        y: {get: function(){return getY;}, set: function(_){
            getY=d3.functor(_);
        }},
        color: {get: function(){return color;}, set: function(_){
            color=nv.utils.getColor(_);
        }},
        labelType:          {get: function(){return labelType;}, set: function(_){
            labelType= _ || 'key';
        }}
    });

    nv.utils.initOptions(chart);
    return chart;
};
nv.models.pieChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var pie = nv.models.pie();
    var legend = nv.models.legend();
    var tooltip = nv.models.tooltip();

    var margin = {top: 30, right: 20, bottom: 20, left: 20}
        , marginTop = null
        , width = null
        , height = null
        , showTooltipPercent = false
        , showLegend = true
        , legendPosition = "top"
        , color = nv.utils.defaultColor()
        , state = nv.utils.state()
        , defaultState = null
        , noData = null
        , duration = 250
        , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
        ;

    tooltip
        .duration(0)
        .headerEnabled(false)
        .valueFormatter(function(d, i) {
            return pie.valueFormat()(d, i);
        });

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled })
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.active !== undefined) {
                data.forEach(function (series, i) {
                    series.disabled = !state.active[i];
                });
            }
        }
    };

    //============================================================
    // Chart function
    //------------------------------------------------------------

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(pie);

        selection.each(function(data) {
            var container = d3.select(this);
            nv.utils.initSVG(container);

            var that = this;
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() { container.transition().call(chart); };
            chart.container = this;

            state.setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            //set state.disabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display No Data message if there's nothing to show.
            if (!data || !data.length) {
                nv.utils.noData(chart, container);
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-pieWrap');
            gEnter.append('g').attr('class', 'nv-legendWrap');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                if (legendPosition === "top") {
                    legend.width( availableWidth ).key(pie.x());

                    wrap.select('.nv-legendWrap')
                        .datum(data)
                        .call(legend);

                    if (!marginTop && legend.height() !== margin.top) {
                        margin.top = legend.height();
                        availableHeight = nv.utils.availableHeight(height, container, margin);
                    }

                    wrap.select('.nv-legendWrap')
                        .attr('transform', 'translate(0,' + (-margin.top) +')');
                } else if (legendPosition === "right") {
                    var legendWidth = nv.models.legend().width();
                    if (availableWidth / 2 < legendWidth) {
                        legendWidth = (availableWidth / 2)
                    }
                    legend.height(availableHeight).key(pie.x());
                    legend.width(legendWidth);
                    availableWidth -= legend.width();

                    wrap.select('.nv-legendWrap')
                        .datum(data)
                        .call(legend)
                        .attr('transform', 'translate(' + (availableWidth) +',0)');
                }
            }
            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // Main Chart Component(s)
            pie.width(availableWidth).height(availableHeight);
            var pieWrap = g.select('.nv-pieWrap').datum([data]);
            d3.transition(pieWrap).call(pie);

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState) {
                    state[key] = newState[key];
                }
                dispatch.stateChange(state);
                chart.update();
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });
                    state.disabled = e.disabled;
                }
                chart.update();
            });
        });

        renderWatch.renderEnd('pieChart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    pie.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt['series'] = {
            key: chart.x()(evt.data),
            value: chart.y()(evt.data),
            color: evt.color,
            percent: evt.percent
        };
        if (!showTooltipPercent) {
            delete evt.percent;
            delete evt.series.percent;
        }
        tooltip.data(evt).hidden(false);
    });

    pie.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    pie.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.legend = legend;
    chart.dispatch = dispatch;
    chart.pie = pie;
    chart.tooltip = tooltip;
    chart.options = nv.utils.optionsFunc.bind(chart);

    // use Object get/set functionality to map between vars and chart functions
    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:              {get: function(){return width;},                set: function(_){width=_;}},
        height:             {get: function(){return height;},               set: function(_){height=_;}},
        noData:             {get: function(){return noData;},               set: function(_){noData=_;}},
        showTooltipPercent: {get: function(){return showTooltipPercent;},   set: function(_){showTooltipPercent=_;}},
        showLegend:         {get: function(){return showLegend;},           set: function(_){showLegend=_;}},
        legendPosition:     {get: function(){return legendPosition;},       set: function(_){legendPosition=_;}},
        defaultState:       {get: function(){return defaultState;},         set: function(_){defaultState=_;}},

        // options that require extra logic in the setter
        color: {get: function(){return color;}, set: function(_){
            color = _;
            legend.color(color);
            pie.color(color);
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            pie.duration(duration);
        }},
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }}
    });
    nv.utils.inheritOptions(chart, pie);
    nv.utils.initOptions(chart);
    return chart;
};
nv.models.sankey = function() {
    'use strict';

    // Sources:
    // - https://bost.ocks.org/mike/sankey/
    // - https://github.com/soxofaan/d3-plugin-captain-sankey

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var sankey = {},
        nodeWidth = 24,
        nodePadding = 8,
        size = [1, 1],
        nodes = [],
        links = [],
        sinksRight = true;

    var layout = function(iterations) {
        computeNodeLinks();
        computeNodeValues();
        computeNodeBreadths();
        computeNodeDepths(iterations);
    };

    var relayout = function() {
        computeLinkDepths();
    };

    // SVG path data generator, to be used as 'd' attribute on 'path' element selection.
    var link = function() {
        var curvature = .5;

        function link(d) {

            var x0 = d.source.x + d.source.dx,
                x1 = d.target.x,
                xi = d3.interpolateNumber(x0, x1),
                x2 = xi(curvature),
                x3 = xi(1 - curvature),
                y0 = d.source.y + d.sy + d.dy / 2,
                y1 = d.target.y + d.ty + d.dy / 2;
            var linkPath = 'M' + x0 + ',' + y0
                + 'C' + x2 + ',' + y0
                + ' ' + x3 + ',' + y1
                + ' ' + x1 + ',' + y1;
            return linkPath;
        }

        link.curvature = function(_) {
            if (!arguments.length) return curvature;
            curvature = +_;
            return link;
        };

        return link;
    };

    // Y-position of the middle of a node.
    var center = function(node) {
        return node.y + node.dy / 2;
    };

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    // Populate the sourceLinks and targetLinks for each node.
    // Also, if the source and target are not objects, assume they are indices.
    function computeNodeLinks() {
        nodes.forEach(function(node) {
            // Links that have this node as source.
            node.sourceLinks = [];
            // Links that have this node as target.
            node.targetLinks = [];
        });
        links.forEach(function(link) {
            var source = link.source,
                target = link.target;
            if (typeof source === 'number') source = link.source = nodes[link.source];
            if (typeof target === 'number') target = link.target = nodes[link.target];
            source.sourceLinks.push(link);
            target.targetLinks.push(link);
        });
    }

    // Compute the value (size) of each node by summing the associated links.
    function computeNodeValues() {
        nodes.forEach(function(node) {
            node.value = Math.max(
                d3.sum(node.sourceLinks, value),
                d3.sum(node.targetLinks, value)
            );
        });
    }

    // Iteratively assign the breadth (x-position) for each node.
    // Nodes are assigned the maximum breadth of incoming neighbors plus one;
    // nodes with no incoming links are assigned breadth zero, while
    // nodes with no outgoing links are assigned the maximum breadth.
    function computeNodeBreadths() {
        //
        var remainingNodes = nodes,
            nextNodes,
            x = 0;

        // Work from left to right.
        // Keep updating the breath (x-position) of nodes that are target of recently updated nodes.
        //
        while (remainingNodes.length && x < nodes.length) {
            nextNodes = [];
            remainingNodes.forEach(function(node) {
                node.x = x;
                node.dx = nodeWidth;
                node.sourceLinks.forEach(function(link) {
                    if (nextNodes.indexOf(link.target) < 0) {
                        nextNodes.push(link.target);
                    }
                });
            });
            remainingNodes = nextNodes;
            ++x;
            //
        }

        // Optionally move pure sinks always to the right.
        if (sinksRight) {
            moveSinksRight(x);
        }

        scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
    }

    function moveSourcesRight() {
        nodes.forEach(function(node) {
            if (!node.targetLinks.length) {
                node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
            }
        });
    }

    function moveSinksRight(x) {
        nodes.forEach(function(node) {
            if (!node.sourceLinks.length) {
                node.x = x - 1;
            }
        });
    }

    function scaleNodeBreadths(kx) {
        nodes.forEach(function(node) {
            node.x *= kx;
        });
    }

    // Compute the depth (y-position) for each node.
    function computeNodeDepths(iterations) {
        // Group nodes by breath.
        var nodesByBreadth = d3.nest()
            .key(function(d) { return d.x; })
            .sortKeys(d3.ascending)
            .entries(nodes)
            .map(function(d) { return d.values; });

        //
        initializeNodeDepth();
        resolveCollisions();
        computeLinkDepths();
        for (var alpha = 1; iterations > 0; --iterations) {
            relaxRightToLeft(alpha *= .99);
            resolveCollisions();
            computeLinkDepths();
            relaxLeftToRight(alpha);
            resolveCollisions();
            computeLinkDepths();
        }

        function initializeNodeDepth() {
            // Calculate vertical scaling factor.
            var ky = d3.min(nodesByBreadth, function(nodes) {
                return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
            });

            nodesByBreadth.forEach(function(nodes) {
                nodes.forEach(function(node, i) {
                    node.y = i;
                    node.dy = node.value * ky;
                });
            });

            links.forEach(function(link) {
                link.dy = link.value * ky;
            });
        }

        function relaxLeftToRight(alpha) {
            nodesByBreadth.forEach(function(nodes, breadth) {
                nodes.forEach(function(node) {
                    if (node.targetLinks.length) {
                        // Value-weighted average of the y-position of source node centers linked to this node.
                        var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
                        node.y += (y - center(node)) * alpha;
                    }
                });
            });

            function weightedSource(link) {
                return (link.source.y + link.sy + link.dy / 2) * link.value;
            }
        }

        function relaxRightToLeft(alpha) {
            nodesByBreadth.slice().reverse().forEach(function(nodes) {
                nodes.forEach(function(node) {
                    if (node.sourceLinks.length) {
                        // Value-weighted average of the y-positions of target nodes linked to this node.
                        var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
                        node.y += (y - center(node)) * alpha;
                    }
                });
            });

            function weightedTarget(link) {
                return (link.target.y + link.ty + link.dy / 2) * link.value;
            }
        }

        function resolveCollisions() {
            nodesByBreadth.forEach(function(nodes) {
                var node,
                    dy,
                    y0 = 0,
                    n = nodes.length,
                    i;

                // Push any overlapping nodes down.
                nodes.sort(ascendingDepth);
                for (i = 0; i < n; ++i) {
                    node = nodes[i];
                    dy = y0 - node.y;
                    if (dy > 0) node.y += dy;
                    y0 = node.y + node.dy + nodePadding;
                }

                // If the bottommost node goes outside the bounds, push it back up.
                dy = y0 - nodePadding - size[1];
                if (dy > 0) {
                    y0 = node.y -= dy;

                    // Push any overlapping nodes back up.
                    for (i = n - 2; i >= 0; --i) {
                        node = nodes[i];
                        dy = node.y + node.dy + nodePadding - y0;
                        if (dy > 0) node.y -= dy;
                        y0 = node.y;
                    }
                }
            });
        }

        function ascendingDepth(a, b) {
            return a.y - b.y;
        }
    }

    // Compute y-offset of the source endpoint (sy) and target endpoints (ty) of links,
    // relative to the source/target node's y-position.
    function computeLinkDepths() {
        nodes.forEach(function(node) {
            node.sourceLinks.sort(ascendingTargetDepth);
            node.targetLinks.sort(ascendingSourceDepth);
        });
        nodes.forEach(function(node) {
            var sy = 0, ty = 0;
            node.sourceLinks.forEach(function(link) {
                link.sy = sy;
                sy += link.dy;
            });
            node.targetLinks.forEach(function(link) {
                link.ty = ty;
                ty += link.dy;
            });
        });

        function ascendingSourceDepth(a, b) {
            return a.source.y - b.source.y;
        }

        function ascendingTargetDepth(a, b) {
            return a.target.y - b.target.y;
        }
    }

    // Value property accessor.
    function value(x) {
        return x.value;
    }

    sankey.options = nv.utils.optionsFunc.bind(sankey);
    sankey._options = Object.create({}, {
        nodeWidth:    {get: function(){return nodeWidth;},   set: function(_){nodeWidth=+_;}},
        nodePadding:  {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}},
        nodes:        {get: function(){return nodes;},       set: function(_){nodes=_;}},
        links:        {get: function(){return links ;},      set: function(_){links=_;}},
        size:         {get: function(){return size;},        set: function(_){size=_;}},
        sinksRight:   {get: function(){return sinksRight;},  set: function(_){sinksRight=_;}},

        layout:       {get: function(){layout(32);},         set: function(_){layout(_);}},
        relayout:     {get: function(){relayout();},         set: function(_){}},
        center:       {get: function(){return center();},    set: function(_){
            if(typeof _ === 'function'){
                center=_;
            }
        }},
        link:         {get: function(){return link();},      set: function(_){
            if(typeof _ === 'function'){
                link=_;
            }
            return link();
        }}
    });

    nv.utils.initOptions(sankey);

    return sankey;
};
nv.models.sankeyChart = function() {
    "use strict";

    // Sources:
    // - https://bost.ocks.org/mike/sankey/
    // - https://github.com/soxofaan/d3-plugin-captain-sankey

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 5, right: 0, bottom: 5, left: 0}
        , sankey = nv.models.sankey()
        , width = 600
        , height = 400
        , nodeWidth = 36
        , nodePadding =  40
        , units = 'units'
        , center = undefined
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var formatNumber = d3.format(',.0f');    // zero decimal places
    var format = function(d) {
        return formatNumber(d) + ' ' + units;
    };
    var color = d3.scale.category20();
    var linkTitle = function(d){
        return d.source.name + ' → ' + d.target.name + '\n' + format(d.value);
    };
    var nodeFillColor = function(d){
        return d.color = color(d.name.replace(/ .*/, ''));
    };
    var nodeStrokeColor = function(d){
        return d3.rgb(d.color).darker(2);
    };
    var nodeTitle = function(d){
        return d.name + '\n' + format(d.value);
    };

    var showError = function(element, message) {
        element.append('text')
            .attr('x', 0)
            .attr('y', 0)
            .attr('class', 'nvd3-sankey-chart-error')
            .attr('text-anchor', 'middle')
            .text(message);
    };

    function chart(selection) {
        selection.each(function(data) {

            var testData = {
                nodes:
                    [
                        {'node': 1, 'name': 'Test 1'},
                        {'node': 2, 'name': 'Test 2'},
                        {'node': 3, 'name': 'Test 3'},
                        {'node': 4, 'name': 'Test 4'},
                        {'node': 5, 'name': 'Test 5'},
                        {'node': 6, 'name': 'Test 6'}
                    ],
                links:
                    [
                        {'source': 0, 'target': 1, 'value': 2295},
                        {'source': 0, 'target': 5, 'value': 1199},
                        {'source': 1, 'target': 2, 'value': 1119},
                        {'source': 1, 'target': 5, 'value': 1176},
                        {'source': 2, 'target': 3, 'value': 487},
                        {'source': 2, 'target': 5, 'value': 632},
                        {'source': 3, 'target': 4, 'value': 301},
                        {'source': 3, 'target': 5, 'value': 186}
                    ]
            };

            // Error handling
            var isDataValid = false;
            var dataAvailable = false;

            // check if data is valid
            if(
                (typeof data['nodes'] === 'object' && data['nodes'].length) >= 0 &&
                (typeof data['links'] === 'object' && data['links'].length) >= 0
            ){
                isDataValid = true;
            }

            // check if data is available
            if(
                data['nodes'] && data['nodes'].length > 0 &&
                data['links'] && data['links'].length > 0
            ) {
                dataAvailable = true;
            }

            // show error
            if(!isDataValid) {
                console.error('NVD3 Sankey chart error:', 'invalid data format for', data);
                console.info('Valid data format is: ', testData, JSON.stringify(testData));
                showError(selection, 'Error loading chart, data is invalid');
                return false;
            }

            // TODO use nv.utils.noData
            if(!dataAvailable) {
                showError(selection, 'No data available');
                return false;
            }

            // No errors, continue

            // append the svg canvas to the page
            var svg = selection.append('svg')
                .attr('width', width)
                .attr('height', height)
                .append('g')
                .attr('class', 'nvd3 nv-wrap nv-sankeyChart');

            // Set the sankey diagram properties
            sankey
                .nodeWidth(nodeWidth)
                .nodePadding(nodePadding)
                .size([width, height]);

            var path = sankey.link();

            sankey
                .nodes(data.nodes)
                .links(data.links)
                .layout(32)
                .center(center);

            // add in the links
            var link = svg.append('g').selectAll('.link')
                .data(data.links)
                .enter().append('path')
                .attr('class', 'link')
                .attr('d', path)
                .style('stroke-width', function(d) { return Math.max(1, d.dy); })
            .sort(function(a,b) { return b.dy - a.dy; });

            // add the link titles
            link.append('title')
                .text(linkTitle);

            // add in the nodes
            var node = svg.append('g').selectAll('.node')
                .data(data.nodes)
                .enter().append('g')
                .attr('class', 'node')
                .attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
                .call(
                    d3.behavior
                        .drag()
                        .origin(function(d) { return d; })
                        .on('dragstart', function() {
                            this.parentNode.appendChild(this);
                        })
                        .on('drag', dragmove)
                );

            // add the rectangles for the nodes
            node.append('rect')
                .attr('height', function(d) { return d.dy; })
                .attr('width', sankey.nodeWidth())
                .style('fill', nodeFillColor)
                .style('stroke', nodeStrokeColor)
                .append('title')
                .text(nodeTitle);

            // add in the title for the nodes
            node.append('text')
                .attr('x', -6)
                .attr('y', function(d) { return d.dy / 2; })
                .attr('dy', '.35em')
                .attr('text-anchor', 'end')
                .attr('transform', null)
                .text(function(d) { return d.name; })
                .filter(function(d) { return d.x < width / 2; })
                .attr('x', 6 + sankey.nodeWidth())
                .attr('text-anchor', 'start');

            // the function for moving the nodes
            function dragmove(d) {
                d3.select(this).attr('transform',
                'translate(' + d.x + ',' + (
                    d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
                ) + ')');
                sankey.relayout();
                link.attr('d', path);
            }
        });

        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        units:           {get: function(){return units;},       set: function(_){units=_;}},
        width:           {get: function(){return width;},       set: function(_){width=_;}},
        height:          {get: function(){return height;},      set: function(_){height=_;}},
        format:          {get: function(){return format;},      set: function(_){format=_;}},
        linkTitle:       {get: function(){return linkTitle;},   set: function(_){linkTitle=_;}},
        nodeWidth:       {get: function(){return nodeWidth;},   set: function(_){nodeWidth=_;}},
        nodePadding:     {get: function(){return nodePadding;}, set: function(_){nodePadding=_;}},
        center:          {get: function(){return center},       set: function(_){center=_}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        nodeStyle: {get: function(){return {};}, set: function(_){
            nodeFillColor   = _.fillColor   !== undefined ? _.fillColor   : nodeFillColor;
            nodeStrokeColor = _.strokeColor !== undefined ? _.strokeColor : nodeStrokeColor;
            nodeTitle       = _.title       !== undefined ? _.title       : nodeTitle;
        }}

    });

    nv.utils.initOptions(chart);

    return chart;
};

nv.models.scatter = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin       = {top: 0, right: 0, bottom: 0, left: 0}
        , width        = null
        , height       = null
        , color        = nv.utils.defaultColor() // chooses color
        , pointBorderColor = null
        , id           = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
        , container    = null
        , x            = d3.scale.linear()
        , y            = d3.scale.linear()
        , z            = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
        , getX         = function(d) { return d.x } // accessor to get the x value
        , getY         = function(d) { return d.y } // accessor to get the y value
        , getSize      = function(d) { return d.size || 1} // accessor to get the point size
        , getShape     = function(d) { return d.shape || 'circle' } // accessor to get point shape
        , forceX       = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
        , forceY       = [] // List of numbers to Force into the Y scale
        , forceSize    = [] // List of numbers to Force into the Size scale
        , interactive  = true // If true, plots a voronoi overlay for advanced point intersection
        , pointActive  = function(d) { return !d.notActive } // any points that return false will be filtered out
        , padData      = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
        , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
        , clipEdge     = false // if true, masks points within x and y scale
        , clipVoronoi  = true // if true, masks each point with a circle... can turn off to slightly increase performance
        , showVoronoi  = false // display the voronoi areas
        , clipRadius   = function() { return 25 } // function to get the radius for voronoi point clips
        , xDomain      = null // Override x domain (skips the calculation from data)
        , yDomain      = null // Override y domain
        , xRange       = null // Override x range
        , yRange       = null // Override y range
        , sizeDomain   = null // Override point size domain
        , sizeRange    = null
        , singlePoint  = false
        , dispatch     = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
        , useVoronoi   = true
        , duration     = 250
        , interactiveUpdateDelay = 300
        , showLabels    = false
        ;


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x0, y0, z0 // used to store previous scales
        , width0
        , height0
        , timeoutID
        , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
        , renderWatch = nv.utils.renderWatch(dispatch, duration)
        , _sizeRange_def = [16, 256]
        , _cache = {}
        ;

    function getCache(d) {
        var key, val;
        key = d[0].series + ':' + d[1];
        val = _cache[key] = _cache[key] || {};
        return val;
    }

    function delCache(d) {
        var key, val;
        key = d[0].series + ':' + d[1];
        delete _cache[key];
    }

    function getDiffs(d) {
        var i, key, val,
            cache = getCache(d),
            diffs = false;
        for (i = 1; i < arguments.length; i += 2) {
            key = arguments[i];
            val = arguments[i + 1](d[0], d[1]);
            if (cache[key] !== val || !cache.hasOwnProperty(key)) {
                cache[key] = val;
                diffs = true;
            }
        }
        return diffs;
    }

    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            container = d3.select(this);
            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            nv.utils.initSVG(container);

            //add series index to each data point for reference
            data.forEach(function(series, i) {
                series.values.forEach(function(point) {
                    point.series = i;
                });
            });

            // Setup Scales
            var logScale = chart.yScale().name === d3.scale.log().name ? true : false;
            // remap and flatten the data for use in calculating the scales' domains
            var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
                d3.merge(
                    data.map(function(d) {
                        return d.values.map(function(d,i) {
                            return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
                        })
                    })
                );

            x   .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))

            if (padData && data[0])
                x.range(xRange || [(availableWidth * padDataOuter +  availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length)  ]);
            //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5)  / data[0].values.length ]);
            else
                x.range(xRange || [0, availableWidth]);

             if (logScale) {
                    var min = d3.min(seriesData.map(function(d) { if (d.y !== 0) return d.y; }));
                    y.clamp(true)
                        .domain(yDomain || d3.extent(seriesData.map(function(d) {
                            if (d.y !== 0) return d.y;
                            else return min * 0.1;
                        }).concat(forceY)))
                        .range(yRange || [availableHeight, 0]);
                } else {
                        y.domain(yDomain || d3.extent(seriesData.map(function (d) { return d.y;}).concat(forceY)))
                        .range(yRange || [availableHeight, 0]);
                }

            z   .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
                .range(sizeRange || _sizeRange_def);

            // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
            singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];

            if (x.domain()[0] === x.domain()[1])
                x.domain()[0] ?
                    x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
                    : x.domain([-1,1]);

            if (y.domain()[0] === y.domain()[1])
                y.domain()[0] ?
                    y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
                    : y.domain([-1,1]);

            if ( isNaN(x.domain()[0])) {
                x.domain([-1,1]);
            }

            if ( isNaN(y.domain()[0])) {
                y.domain([-1,1]);
            }

            x0 = x0 || x;
            y0 = y0 || y;
            z0 = z0 || z;

            var scaleDiff = x(1) !== x0(1) || y(1) !== y0(1) || z(1) !== z0(1);

            width0 = width0 || width;
            height0 = height0 || height;

            var sizeDiff = width0 !== width || height0 !== height;

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            wrap.classed('nv-single-point', singlePoint);
            gEnter.append('g').attr('class', 'nv-groups');
            gEnter.append('g').attr('class', 'nv-point-paths');
            wrapEnter.append('g').attr('class', 'nv-point-clips');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            defsEnter.append('clipPath')
                .attr('id', 'nv-edge-clip-' + id)
                .append('rect')
                .attr('transform', 'translate( -10, -10)');
                
            wrap.select('#nv-edge-clip-' + id + ' rect')
                .attr('width', availableWidth + 20)
                .attr('height', (availableHeight > 0) ? availableHeight + 20 : 0);

            g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');

            function updateInteractiveLayer() {
                // Always clear needs-update flag regardless of whether or not
                // we will actually do anything (avoids needless invocations).
                needsUpdate = false;

                if (!interactive) return false;

                // inject series and point index for reference into voronoi
                if (useVoronoi === true) {
                    var vertices = d3.merge(data.map(function(group, groupIndex) {
                            return group.values
                                .map(function(point, pointIndex) {
                                    // *Adding noise to make duplicates very unlikely
                                    // *Injecting series and point index for reference
                                    /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
                                     */
                                    var pX = getX(point,pointIndex);
                                    var pY = getY(point,pointIndex);

                                    return [nv.utils.NaNtoZero(x(pX))+ Math.random() * 1e-4,
                                            nv.utils.NaNtoZero(y(pY))+ Math.random() * 1e-4,
                                        groupIndex,
                                        pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
                                })
                                .filter(function(pointArray, pointIndex) {
                                    return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
                                })
                        })
                    );

                    if (vertices.length == 0) return false;  // No active points, we're done
                    if (vertices.length < 3) {
                        // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
                        vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
                        vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
                        vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
                        vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
                    }

                    // keep voronoi sections from going more than 10 outside of graph
                    // to avoid overlap with other things like legend etc
                    var bounds = d3.geom.polygon([
                        [-10,-10],
                        [-10,height + 10],
                        [width + 10,height + 10],
                        [width + 10,-10]
                    ]);

                    var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
                        return {
                            'data': bounds.clip(d),
                            'series': vertices[i][2],
                            'point': vertices[i][3]
                        }
                    });

                    // nuke all voronoi paths on reload and recreate them
                    wrap.select('.nv-point-paths').selectAll('path').remove();
                    var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
                    var vPointPaths = pointPaths
                        .enter().append("svg:path")
                        .attr("d", function(d) {
                            if (!d || !d.data || d.data.length === 0)
                                return 'M 0 0';
                            else
                                return "M" + d.data.join(",") + "Z";
                        })
                        .attr("id", function(d,i) {
                            return "nv-path-"+i; })
                        .attr("clip-path", function(d,i) { return "url(#nv-clip-"+id+"-"+i+")"; })
                        ;

                    // good for debugging point hover issues
                    if (showVoronoi) {
                        vPointPaths.style("fill", d3.rgb(230, 230, 230))
                            .style('fill-opacity', 0.4)
                            .style('stroke-opacity', 1)
                            .style("stroke", d3.rgb(200,200,200));
                    }

                    if (clipVoronoi) {
                        // voronoi sections are already set to clip,
                        // just create the circles with the IDs they expect
                        wrap.select('.nv-point-clips').selectAll('*').remove(); // must do * since it has sub-dom
                        var pointClips = wrap.select('.nv-point-clips').selectAll('clipPath').data(vertices);
                        var vPointClips = pointClips
                            .enter().append("svg:clipPath")
                            .attr("id", function(d, i) { return "nv-clip-"+id+"-"+i;})
                            .append("svg:circle")
                            .attr('cx', function(d) { return d[0]; })
                            .attr('cy', function(d) { return d[1]; })
                            .attr('r', clipRadius);
                    }

                    var mouseEventCallback = function(el, d, mDispatch) {
                        if (needsUpdate) return 0;
                        var series = data[d.series];
                        if (series === undefined) return;
                        var point  = series.values[d.point];
                        point['color'] = color(series, d.series);

                        // standardize attributes for tooltip.
                        point['x'] = getX(point);
                        point['y'] = getY(point);

                        // can't just get box of event node since it's actually a voronoi polygon
                        var box = container.node().getBoundingClientRect();
                        var scrollTop  = window.pageYOffset || document.documentElement.scrollTop;
                        var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;

                        var pos = {
                            left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
                            top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
                        };

                        mDispatch({
                            point: point,
                            series: series,
                            pos: pos,
                            relativePos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
                            seriesIndex: d.series,
                            pointIndex: d.point,
                            event: d3.event,
                            element: el
                        });
                    };

                    pointPaths
                        .on('click', function(d) {
                            mouseEventCallback(this, d, dispatch.elementClick);
                        })
                        .on('dblclick', function(d) {
                            mouseEventCallback(this, d, dispatch.elementDblClick);
                        })
                        .on('mouseover', function(d) {
                            mouseEventCallback(this, d, dispatch.elementMouseover);
                        })
                        .on('mouseout', function(d, i) {
                            mouseEventCallback(this, d, dispatch.elementMouseout);
                        });

                } else {
                    // add event handlers to points instead voronoi paths
                    wrap.select('.nv-groups').selectAll('.nv-group')
                        .selectAll('.nv-point')
                        //.data(dataWithPoints)
                        //.style('pointer-events', 'auto') // recativate events, disabled by css
                        .on('click', function(d,i) {
                            //nv.log('test', d, i);
                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
                            var series = data[d.series],
                                point  = series.values[i];
                            var element = this;
                            dispatch.elementClick({
                                point: point,
                                series: series,
                                pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], //TODO: make this pos base on the page
                                relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
                                seriesIndex: d.series,
                                pointIndex: i,
                                event: d3.event,
                                element: element
                            });
                        })
                        .on('dblclick', function(d,i) {
                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
                            var series = data[d.series],
                                point  = series.values[i];

                            dispatch.elementDblClick({
                                point: point,
                                series: series,
                                pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
                                relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
                                seriesIndex: d.series,
                                pointIndex: i
                            });
                        })
                        .on('mouseover', function(d,i) {
                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
                            var series = data[d.series],
                                point  = series.values[i];

                            dispatch.elementMouseover({
                                point: point,
                                series: series,
                                pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
                                relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
                                seriesIndex: d.series,
                                pointIndex: i,
                                color: color(d, i)
                            });
                        })
                        .on('mouseout', function(d,i) {
                            if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
                            var series = data[d.series],
                                point  = series.values[i];

                            dispatch.elementMouseout({
                                point: point,
                                series: series,
                                pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],//TODO: make this pos base on the page
                                relativePos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
                                seriesIndex: d.series,
                                pointIndex: i,
                                color: color(d, i)
                            });
                        });
                }
            }

            needsUpdate = true;
            var groups = wrap.select('.nv-groups').selectAll('.nv-group')
                .data(function(d) { return d }, function(d) { return d.key });
            groups.enter().append('g')
                .style('stroke-opacity', 1e-6)
                .style('fill-opacity', 1e-6);
            groups.exit()
                .remove();
            groups
                .attr('class', function(d,i) {
                    return (d.classed || '') + ' nv-group nv-series-' + i;
                })
                .classed('nv-noninteractive', !interactive)
                .classed('hover', function(d) { return d.hover });
            groups.watchTransition(renderWatch, 'scatter: groups')
                .style('fill', function(d,i) { return color(d, i) })
                .style('stroke', function(d,i) { return d.pointBorderColor || pointBorderColor || color(d, i) })
                .style('stroke-opacity', 1)
                .style('fill-opacity', .5);

            // create the points, maintaining their IDs from the original data set
            var points = groups.selectAll('path.nv-point')
                .data(function(d) {
                    return d.values.map(
                        function (point, pointIndex) {
                            return [point, pointIndex]
                        }).filter(
                            function(pointArray, pointIndex) {
                                return pointActive(pointArray[0], pointIndex)
                            })
                    });
            points.enter().append('path')
                .attr('class', function (d) {
                    return 'nv-point nv-point-' + d[1];
                })
                .style('fill', function (d) { return d.color })
                .style('stroke', function (d) { return d.color })
                .attr('transform', function(d) {
                    return 'translate(' + nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')'
                })
                .attr('d',
                    nv.utils.symbol()
                    .type(function(d) { return getShape(d[0]); })
                    .size(function(d) { return z(getSize(d[0],d[1])) })
            );
            points.exit().each(delCache).remove();
            groups.exit().selectAll('path.nv-point')
                .watchTransition(renderWatch, 'scatter exit')
                .attr('transform', function(d) {
                    return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
                })
                .remove();
            // Update points position only if "x" or "y" have changed
            points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'x', getX, 'y', getY); })
                .watchTransition(renderWatch, 'scatter points')
                .attr('transform', function(d) {
                    //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
                    return 'translate(' + nv.utils.NaNtoZero(x(getX(d[0],d[1]))) + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
                });
            // Update points appearance only if "shape" or "size" have changed
            points.filter(function (d) { return scaleDiff || sizeDiff || getDiffs(d, 'shape', getShape, 'size', getSize); })
                .watchTransition(renderWatch, 'scatter points')
                .attr('d',
                    nv.utils.symbol()
                    .type(function(d) { return getShape(d[0]); })
                    .size(function(d) { return z(getSize(d[0],d[1])) })
            );

            // add label a label to scatter chart
            if(showLabels)
            {
                var titles =  groups.selectAll('.nv-label')
                    .data(function(d) {
                        return d.values.map(
                            function (point, pointIndex) {
                                return [point, pointIndex]
                            }).filter(
                                function(pointArray, pointIndex) {
                                    return pointActive(pointArray[0], pointIndex)
                                })
                        });

                titles.enter().append('text')
                    .style('fill', function (d,i) {
                        return d.color })
                    .style('stroke-opacity', 0)
                    .style('fill-opacity', 1)
                    .attr('transform', function(d) {
                        var dx = nv.utils.NaNtoZero(x0(getX(d[0],d[1]))) + Math.sqrt(z(getSize(d[0],d[1]))/Math.PI) + 2;
                        return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y0(getY(d[0],d[1]))) + ')';
                    })
                    .text(function(d,i){
                        return d[0].label;});

                titles.exit().remove();
                groups.exit().selectAll('path.nv-label')
                    .watchTransition(renderWatch, 'scatter exit')
                    .attr('transform', function(d) {
                        var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
                        return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')';
                    })
                    .remove();
               titles.each(function(d) {
                  d3.select(this)
                    .classed('nv-label', true)
                    .classed('nv-label-' + d[1], false)
                    .classed('hover',false);
                });
                titles.watchTransition(renderWatch, 'scatter labels')
                    .attr('transform', function(d) {
                        var dx = nv.utils.NaNtoZero(x(getX(d[0],d[1])))+ Math.sqrt(z(getSize(d[0],d[1]))/Math.PI)+2;
                        return 'translate(' + dx + ',' + nv.utils.NaNtoZero(y(getY(d[0],d[1]))) + ')'
                    });
            }

            // Delay updating the invisible interactive layer for smoother animation
            if( interactiveUpdateDelay )
            {
                clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
                timeoutID = setTimeout(updateInteractiveLayer, interactiveUpdateDelay );
            }
            else
            {
                updateInteractiveLayer();
            }

            //store old scales for use in transitions on update
            x0 = x.copy();
            y0 = y.copy();
            z0 = z.copy();

            width0 = width;
            height0 = height;

        });
        renderWatch.renderEnd('scatter immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    // utility function calls provided by this chart
    chart._calls = new function() {
        this.clearHighlights = function () {
            nv.dom.write(function() {
                container.selectAll(".nv-point.hover").classed("hover", false);
            });
            return null;
        };
        this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
            nv.dom.write(function() {
                container.select('.nv-groups')
                  .selectAll(".nv-series-" + seriesIndex)
                  .selectAll(".nv-point-" + pointIndex)
                  .classed("hover", isHoverOver);
            });
        };
    };

    // trigger calls from events too
    dispatch.on('elementMouseover.point', function(d) {
        if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
    });

    dispatch.on('elementMouseout.point', function(d) {
        if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
    });

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:        {get: function(){return width;}, set: function(_){width=_;}},
        height:       {get: function(){return height;}, set: function(_){height=_;}},
        xScale:       {get: function(){return x;}, set: function(_){x=_;}},
        yScale:       {get: function(){return y;}, set: function(_){y=_;}},
        pointScale:   {get: function(){return z;}, set: function(_){z=_;}},
        xDomain:      {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain:      {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        pointDomain:  {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
        xRange:       {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:       {get: function(){return yRange;}, set: function(_){yRange=_;}},
        pointRange:   {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
        forceX:       {get: function(){return forceX;}, set: function(_){forceX=_;}},
        forceY:       {get: function(){return forceY;}, set: function(_){forceY=_;}},
        forcePoint:   {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
        interactive:  {get: function(){return interactive;}, set: function(_){interactive=_;}},
        pointActive:  {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
        padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
        padData:      {get: function(){return padData;}, set: function(_){padData=_;}},
        clipEdge:     {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
        clipVoronoi:  {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
        clipRadius:   {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
        showVoronoi:   {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
        id:           {get: function(){return id;}, set: function(_){id=_;}},
        interactiveUpdateDelay: {get:function(){return interactiveUpdateDelay;}, set: function(_){interactiveUpdateDelay=_;}},
        showLabels: {get: function(){return showLabels;}, set: function(_){ showLabels = _;}},
        pointBorderColor: {get: function(){return pointBorderColor;}, set: function(_){pointBorderColor=_;}},

        // simple functor options
        x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
        y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
        pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
        pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
        }},
        color: {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
            useVoronoi = _;
            if (useVoronoi === false) {
                clipVoronoi = false;
            }
        }}
    });

    nv.utils.initOptions(chart);
    return chart;
};

nv.models.scatterChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var scatter      = nv.models.scatter()
        , xAxis        = nv.models.axis()
        , yAxis        = nv.models.axis()
        , legend       = nv.models.legend()
        , distX        = nv.models.distribution()
        , distY        = nv.models.distribution()
        , tooltip      = nv.models.tooltip()
        ;

    var margin       = {top: 30, right: 20, bottom: 50, left: 75}
        , marginTop = null
        , width        = null
        , height       = null
        , container    = null
        , color        = nv.utils.defaultColor()
        , x            = scatter.xScale()
        , y            = scatter.yScale()
        , showDistX    = false
        , showDistY    = false
        , showLegend   = true
        , showXAxis    = true
        , showYAxis    = true
        , rightAlignYAxis = false
        , state = nv.utils.state()
        , defaultState = null
        , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
        , noData       = null
        , duration = 250
        , showLabels    = false
        ;

    scatter.xScale(x).yScale(y);
    xAxis.orient('bottom').tickPadding(10);
    yAxis
        .orient((rightAlignYAxis) ? 'right' : 'left')
        .tickPadding(10)
    ;
    distX.axis('x');
    distY.axis('y');
    tooltip
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        })
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        });

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var x0, y0
        , renderWatch = nv.utils.renderWatch(dispatch, duration);

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled })
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        }
    };

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(scatter);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);
        if (showDistX) renderWatch.models(distX);
        if (showDistY) renderWatch.models(distY);

        selection.each(function(data) {
            var that = this;

            container = d3.select(this);
            nv.utils.initSVG(container);

            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() {
                if (duration === 0)
                    container.call(chart);
                else
                    container.transition().duration(duration).call(chart);
            };
            chart.container = this;

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disableddisabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display noData message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container);
                renderWatch.renderEnd('scatter immediate');
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            // Setup Scales
            x = scatter.xScale();
            y = scatter.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            // background for pointer events
            gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");

            gEnter.append('g').attr('class', 'nv-x nv-axis');
            gEnter.append('g').attr('class', 'nv-y nv-axis');
            gEnter.append('g').attr('class', 'nv-scatterWrap');
            gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
            gEnter.append('g').attr('class', 'nv-distWrap');
            gEnter.append('g').attr('class', 'nv-legendWrap');

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                var legendWidth = availableWidth;
                legend.width(legendWidth);

                wrap.select('.nv-legendWrap')
                    .datum(data)
                    .call(legend);

                if (!marginTop && legend.height() !== margin.top) {
                    margin.top = legend.height();
                    availableHeight = nv.utils.availableHeight(height, container, margin);
                }

                wrap.select('.nv-legendWrap')
                    .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // Main Chart Component(s)
            scatter
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    d.color = d.color || color(d, i);
                    return d.color;
                }).filter(function(d,i) { return !data[i].disabled }))
                .showLabels(showLabels);

            wrap.select('.nv-scatterWrap')
                .datum(data.filter(function(d) { return !d.disabled }))
                .call(scatter);


            wrap.select('.nv-regressionLinesWrap')
                .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');

            var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
                .data(function (d) {
                    return d;
                });

            regWrap.enter().append('g').attr('class', 'nv-regLines');

            var regLine = regWrap.selectAll('.nv-regLine')
                .data(function (d) {
                    return [d]
                });

            regLine.enter()
                .append('line').attr('class', 'nv-regLine')
                .style('stroke-opacity', 0);

            // don't add lines unless we have slope and intercept to use
            regLine.filter(function(d) {
                return d.intercept && d.slope;
            })
                .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
                .attr('x1', x.range()[0])
                .attr('x2', x.range()[1])
                .attr('y1', function (d, i) {
                    return y(x.domain()[0] * d.slope + d.intercept)
                })
                .attr('y2', function (d, i) {
                    return y(x.domain()[1] * d.slope + d.intercept)
                })
                .style('stroke', function (d, i, j) {
                    return color(d, j)
                })
                .style('stroke-opacity', function (d, i) {
                    return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
                });

            // Setup Axes
            if (showXAxis) {
                xAxis
                    .scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize( -availableHeight , 0);

                g.select('.nv-x.nv-axis')
                    .attr('transform', 'translate(0,' + y.range()[0] + ')')
                    .call(xAxis);
            }

            if (showYAxis) {
                yAxis
                    .scale(y)
                    ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
                    .tickSize( -availableWidth, 0);

                g.select('.nv-y.nv-axis')
                    .call(yAxis);
            }

            // Setup Distribution
            if (showDistX) {
                distX
                    .getData(scatter.x())
                    .scale(x)
                    .width(availableWidth)
                    .color(data.map(function(d,i) {
                        return d.color || color(d, i);
                    }).filter(function(d,i) { return !data[i].disabled }));
                gEnter.select('.nv-distWrap').append('g')
                    .attr('class', 'nv-distributionX');
                g.select('.nv-distributionX')
                    .attr('transform', 'translate(0,' + y.range()[0] + ')')
                    .datum(data.filter(function(d) { return !d.disabled }))
                    .call(distX);
            }

            if (showDistY) {
                distY
                    .getData(scatter.y())
                    .scale(y)
                    .width(availableHeight)
                    .color(data.map(function(d,i) {
                        return d.color || color(d, i);
                    }).filter(function(d,i) { return !data[i].disabled }));
                gEnter.select('.nv-distWrap').append('g')
                    .attr('class', 'nv-distributionY');
                g.select('.nv-distributionY')
                    .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
                    .datum(data.filter(function(d) { return !d.disabled }))
                    .call(distY);
            }

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {
                if (typeof e.disabled !== 'undefined') {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });
                    state.disabled = e.disabled;
                }
                chart.update();
            });

            // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
            scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
                tooltip.hidden(true);
                container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
                    .attr('y1', 0);
                container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
                    .attr('x2', distY.size());
            });

            scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
                container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
                    .attr('y1', evt.relativePos[1] - availableHeight);
                container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
                    .attr('x2', evt.relativePos[0] + distX.size());
                tooltip.data(evt).hidden(false);
            });

            //store old scales for use in transitions on update
            x0 = x.copy();
            y0 = y.copy();

        });

        renderWatch.renderEnd('scatter with line immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.scatter = scatter;
    chart.legend = legend;
    chart.xAxis = xAxis;
    chart.yAxis = yAxis;
    chart.distX = distX;
    chart.distY = distY;
    chart.tooltip = tooltip;

    chart.options = nv.utils.optionsFunc.bind(chart);
    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        container:  {get: function(){return container;}, set: function(_){container=_;}},
        showDistX:  {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
        showDistY:  {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        showXAxis:  {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:  {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        defaultState:     {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:     {get: function(){return noData;}, set: function(_){noData=_;}},
        duration:   {get: function(){return duration;}, set: function(_){duration=_;}},
        showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( (_) ? 'right' : 'left');
        }},
        color: {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
            distX.color(color);
            distY.color(color);
        }}
    });

    nv.utils.inheritOptions(chart, scatter);
    nv.utils.initOptions(chart);
    return chart;
};

nv.models.sparkline = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 2, right: 0, bottom: 2, left: 0}
        , width = 400
        , height = 32
        , container = null
        , animate = true
        , x = d3.scale.linear()
        , y = d3.scale.linear()
        , getX = function(d) { return d.x }
        , getY = function(d) { return d.y }
        , color = nv.utils.getColor(['#000'])
        , xDomain
        , yDomain
        , xRange
        , yRange
        , showMinMaxPoints = true
        , showCurrentPoint = true
        , dispatch = d3.dispatch('renderEnd')
        ;

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);
    
    function chart(selection) {
        renderWatch.reset();
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);

            // Setup Scales
            x   .domain(xDomain || d3.extent(data, getX ))
                .range(xRange || [0, availableWidth]);

            y   .domain(yDomain || d3.extent(data, getY ))
                .range(yRange || [availableHeight, 0]);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

            var paths = wrap.selectAll('path')
                .data(function(d) { return [d] });
            paths.enter().append('path');
            paths.exit().remove();
            paths
                .style('stroke', function(d,i) { return d.color || color(d, i) })
                .attr('d', d3.svg.line()
                    .x(function(d,i) { return x(getX(d,i)) })
                    .y(function(d,i) { return y(getY(d,i)) })
            );

            // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
            var points = wrap.selectAll('circle.nv-point')
                .data(function(data) {
                    var yValues = data.map(function(d, i) { return getY(d,i); });
                    function pointIndex(index) {
                        if (index != -1) {
                            var result = data[index];
                            result.pointIndex = index;
                            return result;
                        } else {
                            return null;
                        }
                    }
                    var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
                        minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
                        currentPoint = pointIndex(yValues.length - 1);
                    return [(showMinMaxPoints ? minPoint : null), (showMinMaxPoints ? maxPoint : null), (showCurrentPoint ? currentPoint : null)].filter(function (d) {return d != null;});
                });
            points.enter().append('circle');
            points.exit().remove();
            points
                .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
                .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
                .attr('r', 2)
                .attr('class', function(d,i) {
                    return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
                            getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
                });
        });
        
        renderWatch.renderEnd('sparkline immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:            {get: function(){return width;}, set: function(_){width=_;}},
        height:           {get: function(){return height;}, set: function(_){height=_;}},
        xDomain:          {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
        yDomain:          {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
        xRange:           {get: function(){return xRange;}, set: function(_){xRange=_;}},
        yRange:           {get: function(){return yRange;}, set: function(_){yRange=_;}},
        xScale:           {get: function(){return x;}, set: function(_){x=_;}},
        yScale:           {get: function(){return y;}, set: function(_){y=_;}},
        animate:          {get: function(){return animate;}, set: function(_){animate=_;}},
        showMinMaxPoints: {get: function(){return showMinMaxPoints;}, set: function(_){showMinMaxPoints=_;}},
        showCurrentPoint: {get: function(){return showCurrentPoint;}, set: function(_){showCurrentPoint=_;}},

        //functor options
        x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
        y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }}
    });

    chart.dispatch = dispatch;
    nv.utils.initOptions(chart);
    return chart;
};

nv.models.sparklinePlus = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var sparkline = nv.models.sparkline();

    var margin = {top: 15, right: 100, bottom: 10, left: 50}
        , width = null
        , height = null
        , x
        , y
        , index = []
        , paused = false
        , xTickFormat = d3.format(',r')
        , yTickFormat = d3.format(',.2f')
        , showLastValue = true
        , alignValue = true
        , rightAlignValue = false
        , noData = null
        , dispatch = d3.dispatch('renderEnd')
        ;
        
    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(sparkline);
        selection.each(function(data) {
            var container = d3.select(this);
            nv.utils.initSVG(container);

            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() { container.call(chart); };
            chart.container = this;

            // Display No Data message if there's nothing to show.
            if (!data || !data.length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            var currentValue = sparkline.y()(data[data.length-1], data.length-1);

            // Setup Scales
            x = sparkline.xScale();
            y = sparkline.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-sparklineWrap');
            gEnter.append('g').attr('class', 'nv-valueWrap');
            gEnter.append('g').attr('class', 'nv-hoverArea');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            // Main Chart Component(s)
            var sparklineWrap = g.select('.nv-sparklineWrap');

            sparkline.width(availableWidth).height(availableHeight);
            sparklineWrap.call(sparkline);

            if (showLastValue) {
                var valueWrap = g.select('.nv-valueWrap');
                var value = valueWrap.selectAll('.nv-currentValue')
                    .data([currentValue]);

                value.enter().append('text').attr('class', 'nv-currentValue')
                    .attr('dx', rightAlignValue ? -8 : 8)
                    .attr('dy', '.9em')
                    .style('text-anchor', rightAlignValue ? 'end' : 'start');

                value
                    .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
                    .attr('y', alignValue ? function (d) {
                        return y(d)
                    } : 0)
                    .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
                    .text(yTickFormat(currentValue));
            }

            gEnter.select('.nv-hoverArea').append('rect')
                .on('mousemove', sparklineHover)
                .on('click', function() { paused = !paused })
                .on('mouseout', function() { index = []; updateValueLine(); });

            g.select('.nv-hoverArea rect')
                .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
                .attr('width', availableWidth + margin.left + margin.right)
                .attr('height', availableHeight + margin.top);

            //index is currently global (within the chart), may or may not keep it that way
            function updateValueLine() {
                if (paused) return;

                var hoverValue = g.selectAll('.nv-hoverValue').data(index);

                var hoverEnter = hoverValue.enter()
                    .append('g').attr('class', 'nv-hoverValue')
                    .style('stroke-opacity', 0)
                    .style('fill-opacity', 0);

                hoverValue.exit()
                    .transition().duration(250)
                    .style('stroke-opacity', 0)
                    .style('fill-opacity', 0)
                    .remove();

                hoverValue
                    .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
                    .transition().duration(250)
                    .style('stroke-opacity', 1)
                    .style('fill-opacity', 1);

                if (!index.length) return;

                hoverEnter.append('line')
                    .attr('x1', 0)
                    .attr('y1', -margin.top)
                    .attr('x2', 0)
                    .attr('y2', availableHeight);

                hoverEnter.append('text').attr('class', 'nv-xValue')
                    .attr('x', -6)
                    .attr('y', -margin.top)
                    .attr('text-anchor', 'end')
                    .attr('dy', '.9em');

                g.select('.nv-hoverValue .nv-xValue')
                    .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));

                hoverEnter.append('text').attr('class', 'nv-yValue')
                    .attr('x', 6)
                    .attr('y', -margin.top)
                    .attr('text-anchor', 'start')
                    .attr('dy', '.9em');

                g.select('.nv-hoverValue .nv-yValue')
                    .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
            }

            function sparklineHover() {
                if (paused) return;

                var pos = d3.mouse(this)[0] - margin.left;

                function getClosestIndex(data, x) {
                    var distance = Math.abs(sparkline.x()(data[0], 0) - x);
                    var closestIndex = 0;
                    for (var i = 0; i < data.length; i++){
                        if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
                            distance = Math.abs(sparkline.x()(data[i], i) - x);
                            closestIndex = i;
                        }
                    }
                    return closestIndex;
                }

                index = [getClosestIndex(data, Math.round(x.invert(pos)))];
                updateValueLine();
            }

        });
        renderWatch.renderEnd('sparklinePlus immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.sparkline = sparkline;

    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:           {get: function(){return width;}, set: function(_){width=_;}},
        height:          {get: function(){return height;}, set: function(_){height=_;}},
        xTickFormat:     {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
        yTickFormat:     {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
        showLastValue:   {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
        alignValue:      {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
        rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
        noData:          {get: function(){return noData;}, set: function(_){noData=_;}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }}
    });

    nv.utils.inheritOptions(chart, sparkline);
    nv.utils.initOptions(chart);

    return chart;
};

nv.models.stackedArea = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 960
        , height = 500
        , color = nv.utils.defaultColor() // a function that computes the color
        , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
        , container = null
        , getX = function(d) { return d.x } // accessor to get the x value from a data point
        , getY = function(d) { return d.y } // accessor to get the y value from a data point
        , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
        , style = 'stack'
        , offset = 'zero'
        , order = 'default'
        , interpolate = 'linear'  // controls the line interpolation
        , clipEdge = false // if true, masks lines within x and y scale
        , x //can be accessed via chart.xScale()
        , y //can be accessed via chart.yScale()
        , scatter = nv.models.scatter()
        , duration = 250
        , dispatch =  d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
        ;

    scatter
        .pointSize(2.2) // default size
        .pointDomain([2.2, 2.2]) // all the same size by default
    ;

    /************************************
     * offset:
     *   'wiggle' (stream)
     *   'zero' (stacked)
     *   'expand' (normalize to 100%)
     *   'silhouette' (simple centered)
     *
     * order:
     *   'inside-out' (stream)
     *   'default' (input order)
     ************************************/

    var renderWatch = nv.utils.renderWatch(dispatch, duration);

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(scatter);
        selection.each(function(data) {
            var availableWidth = width - margin.left - margin.right,
                availableHeight = height - margin.top - margin.bottom;

            container = d3.select(this);
            nv.utils.initSVG(container);

            // Setup Scales
            x = scatter.xScale();
            y = scatter.yScale();

            var dataRaw = data;
            // Injecting point index into each point because d3.layout.stack().out does not give index
            data.forEach(function(aseries, i) {
                aseries.seriesIndex = i;
                aseries.values = aseries.values.map(function(d, j) {
                    d.index = j;
                    d.seriesIndex = i;
                    return d;
                });
            });

            var dataFiltered = data.filter(function(series) {
                return !series.disabled;
            });

            data = d3.layout.stack()
                .order(order)
                .offset(offset)
                .values(function(d) { return d.values })  //TODO: make values customizeable in EVERY model in this fashion
                .x(getX)
                .y(getY)
                .out(function(d, y0, y) {
                    d.display = {
                        y: y,
                        y0: y0
                    };
                })
            (dataFiltered);

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
            var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
            var defsEnter = wrapEnter.append('defs');
            var gEnter = wrapEnter.append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-areaWrap');
            gEnter.append('g').attr('class', 'nv-scatterWrap');

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
            
            // If the user has not specified forceY, make sure 0 is included in the domain
            // Otherwise, use user-specified values for forceY
            if (scatter.forceY().length == 0) {
                scatter.forceY().push(0);
            }
            
            scatter
                .width(availableWidth)
                .height(availableHeight)
                .x(getX)
                .y(function(d) {
                    if (d.display !== undefined) { return d.display.y + d.display.y0; }
                })
                .color(data.map(function(d,i) {
                    d.color = d.color || color(d, d.seriesIndex);
                    return d.color;
                }));

            var scatterWrap = g.select('.nv-scatterWrap')
                .datum(data);

            scatterWrap.call(scatter);

            defsEnter.append('clipPath')
                .attr('id', 'nv-edge-clip-' + id)
                .append('rect');

            wrap.select('#nv-edge-clip-' + id + ' rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');

            var area = d3.svg.area()
                .defined(defined)
                .x(function(d,i)  { return x(getX(d,i)) })
                .y0(function(d) {
                    return y(d.display.y0)
                })
                .y1(function(d) {
                    return y(d.display.y + d.display.y0)
                })
                .interpolate(interpolate);

            var zeroArea = d3.svg.area()
                .defined(defined)
                .x(function(d,i)  { return x(getX(d,i)) })
                .y0(function(d) { return y(d.display.y0) })
                .y1(function(d) { return y(d.display.y0) });

            var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
                .data(function(d) { return d });

            path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
                .attr('d', function(d,i){
                    return zeroArea(d.values, d.seriesIndex);
                })
                .on('mouseover', function(d,i) {
                    d3.select(this).classed('hover', true);
                    dispatch.areaMouseover({
                        point: d,
                        series: d.key,
                        pos: [d3.event.pageX, d3.event.pageY],
                        seriesIndex: d.seriesIndex
                    });
                })
                .on('mouseout', function(d,i) {
                    d3.select(this).classed('hover', false);
                    dispatch.areaMouseout({
                        point: d,
                        series: d.key,
                        pos: [d3.event.pageX, d3.event.pageY],
                        seriesIndex: d.seriesIndex
                    });
                })
                .on('click', function(d,i) {
                    d3.select(this).classed('hover', false);
                    dispatch.areaClick({
                        point: d,
                        series: d.key,
                        pos: [d3.event.pageX, d3.event.pageY],
                        seriesIndex: d.seriesIndex
                    });
                });

            path.exit().remove();
            path.style('fill', function(d,i){
                    return d.color || color(d, d.seriesIndex)
                })
                .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
            path.watchTransition(renderWatch,'stackedArea path')
                .attr('d', function(d,i) {
                    return area(d.values,i)
                });

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            scatter.dispatch.on('elementMouseover.area', function(e) {
                g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
            });
            scatter.dispatch.on('elementMouseout.area', function(e) {
                g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
            });

            //Special offset functions
            chart.d3_stackedOffset_stackPercent = function(stackData) {
                var n = stackData.length,    //How many series
                    m = stackData[0].length,     //how many points per series
                    i,
                    j,
                    o,
                    y0 = [];

                for (j = 0; j < m; ++j) { //Looping through all points
                    for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
                        o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
                    }

                    if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
                        stackData[i][j][1] /= o;
                    } else { //(total y value of all series at point in time i) == 0
                        for (i = 0; i < n; i++) {
                            stackData[i][j][1] = 0;
                        }
                    }
                }
                for (j = 0; j < m; ++j) y0[j] = 0;
                return y0;
            };

        });

        renderWatch.renderEnd('stackedArea immediate');
        return chart;
    }

    //============================================================
    // Global getters and setters
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.scatter = scatter;

    scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
    scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
    scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });

    chart.interpolate = function(_) {
        if (!arguments.length) return interpolate;
        interpolate = _;
        return chart;
    };

    chart.duration = function(_) {
        if (!arguments.length) return duration;
        duration = _;
        renderWatch.reset(duration);
        scatter.duration(duration);
        return chart;
    };

    chart.dispatch = dispatch;
    chart.scatter = scatter;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        defined: {get: function(){return defined;}, set: function(_){defined=_;}},
        clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
        offset:      {get: function(){return offset;}, set: function(_){offset=_;}},
        order:    {get: function(){return order;}, set: function(_){order=_;}},
        interpolate:    {get: function(){return interpolate;}, set: function(_){interpolate=_;}},

        // simple functor options
        x:     {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
        y:     {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
        }},
        style: {get: function(){return style;}, set: function(_){
            style = _;
            switch (style) {
                case 'stack':
                    chart.offset('zero');
                    chart.order('default');
                    break;
                case 'stream':
                    chart.offset('wiggle');
                    chart.order('inside-out');
                    break;
                case 'stream-center':
                    chart.offset('silhouette');
                    chart.order('inside-out');
                    break;
                case 'expand':
                    chart.offset('expand');
                    chart.order('default');
                    break;
                case 'stack_percent':
                    chart.offset(chart.d3_stackedOffset_stackPercent);
                    chart.order('default');
                    break;
            }
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            scatter.duration(duration);
        }}
    });

    nv.utils.inheritOptions(chart, scatter);
    nv.utils.initOptions(chart);

    return chart;
};

nv.models.stackedAreaChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var stacked = nv.models.stackedArea()
        , xAxis = nv.models.axis()
        , yAxis = nv.models.axis()
        , legend = nv.models.legend()
        , controls = nv.models.legend()
        , interactiveLayer = nv.interactiveGuideline()
        , tooltip = nv.models.tooltip()
        , focus = nv.models.focus(nv.models.stackedArea())
        ;

    var margin = {top: 10, right: 25, bottom: 50, left: 60}
        , marginTop = null
        , width = null
        , height = null
        , color = nv.utils.defaultColor()
        , showControls = true
        , showLegend = true
        , legendPosition = 'top'
        , showXAxis = true
        , showYAxis = true
        , rightAlignYAxis = false
        , focusEnable = false
        , useInteractiveGuideline = false
        , showTotalInTooltip = true
        , totalLabel = 'TOTAL'
        , x //can be accessed via chart.xScale()
        , y //can be accessed via chart.yScale()
        , state = nv.utils.state()
        , defaultState = null
        , noData = null
        , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
        , controlWidth = 250
        , controlOptions = ['Stacked','Stream','Expanded']
        , controlLabels = {}
        , duration = 250
        ;

    state.style = stacked.style();
    xAxis.orient('bottom').tickPadding(7);
    yAxis.orient((rightAlignYAxis) ? 'right' : 'left');

    tooltip
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        })
        .valueFormatter(function(d, i) {
            return yAxis.tickFormat()(d, i);
        });

    interactiveLayer.tooltip
        .headerFormatter(function(d, i) {
            return xAxis.tickFormat()(d, i);
        })
        .valueFormatter(function(d, i) {
            return d == null ? "N/A" : yAxis.tickFormat()(d, i);
        });

    var oldYTickFormat = null,
        oldValueFormatter = null;

    controls.updateState(false);

    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);
    var style = stacked.style();

    var stateGetter = function(data) {
        return function(){
            return {
                active: data.map(function(d) { return !d.disabled }),
                style: stacked.style()
            };
        }
    };

    var stateSetter = function(data) {
        return function(state) {
            if (state.style !== undefined)
                style = state.style;
            if (state.active !== undefined)
                data.forEach(function(series,i) {
                    series.disabled = !state.active[i];
                });
        }
    };

    var percentFormatter = d3.format('%');

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(stacked);
        if (showXAxis) renderWatch.models(xAxis);
        if (showYAxis) renderWatch.models(yAxis);

        selection.each(function(data) {
            var container = d3.select(this),
                that = this;
            nv.utils.initSVG(container);

            var availableWidth = nv.utils.availableWidth(width, container, margin),
                availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);

            chart.update = function() { container.transition().duration(duration).call(chart); };
            chart.container = this;

            state
                .setter(stateSetter(data), chart.update)
                .getter(stateGetter(data))
                .update();

            // DEPRECATED set state.disabled
            state.disabled = data.map(function(d) { return !!d.disabled });

            if (!defaultState) {
                var key;
                defaultState = {};
                for (key in state) {
                    if (state[key] instanceof Array)
                        defaultState[key] = state[key].slice(0);
                    else
                        defaultState[key] = state[key];
                }
            }

            // Display No Data message if there's nothing to show.
            if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
                nv.utils.noData(chart, container)
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }
            // Setup Scales
            x = stacked.xScale();
            y = stacked.yScale();

            // Setup containers and skeleton of chart
            var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
            var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
            var g = wrap.select('g');

            gEnter.append('g').attr('class', 'nv-legendWrap');
            gEnter.append('g').attr('class', 'nv-controlsWrap');

            var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
            focusEnter.append('g').attr('class', 'nv-background').append('rect');
            focusEnter.append('g').attr('class', 'nv-x nv-axis');
            focusEnter.append('g').attr('class', 'nv-y nv-axis');
            focusEnter.append('g').attr('class', 'nv-stackedWrap');
            focusEnter.append('g').attr('class', 'nv-interactive');

            // g.select("rect").attr("width",availableWidth).attr("height",availableHeight);

            var contextEnter = gEnter.append('g').attr('class', 'nv-focusWrap');

            // Legend
            if (!showLegend) {
                g.select('.nv-legendWrap').selectAll('*').remove();
            } else {
                var legendWidth = (showControls && legendPosition === 'top') ? availableWidth - controlWidth : availableWidth;

                legend.width(legendWidth);
                g.select('.nv-legendWrap').datum(data).call(legend);

                if (legendPosition === 'bottom') {
                	// constant from axis.js, plus some margin for better layout
                	var xAxisHeight = (showXAxis ? 12 : 0) + 10;
                   	margin.bottom = Math.max(legend.height() + xAxisHeight, margin.bottom);
                   	availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
                	var legendTop = availableHeight + xAxisHeight;
                    g.select('.nv-legendWrap')
                        .attr('transform', 'translate(0,' + legendTop +')');
                } else if (legendPosition === 'top') {
                    if (!marginTop && margin.top != legend.height()) {
                        margin.top = legend.height();
                        availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
                    }

                    g.select('.nv-legendWrap')
                    	.attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
                }
            }

            // Controls
            if (!showControls) {
                 g.select('.nv-controlsWrap').selectAll('*').remove();
            } else {
                var controlsData = [
                    {
                        key: controlLabels.stacked || 'Stacked',
                        metaKey: 'Stacked',
                        disabled: stacked.style() != 'stack',
                        style: 'stack'
                    },
                    {
                        key: controlLabels.stream || 'Stream',
                        metaKey: 'Stream',
                        disabled: stacked.style() != 'stream',
                        style: 'stream'
                    },
                    {
                        key: controlLabels.expanded || 'Expanded',
                        metaKey: 'Expanded',
                        disabled: stacked.style() != 'expand',
                        style: 'expand'
                    },
                    {
                        key: controlLabels.stack_percent || 'Stack %',
                        metaKey: 'Stack_Percent',
                        disabled: stacked.style() != 'stack_percent',
                        style: 'stack_percent'
                    }
                ];

                controlWidth = (controlOptions.length/3) * 260;
                controlsData = controlsData.filter(function(d) {
                    return controlOptions.indexOf(d.metaKey) !== -1;
                });

                controls
                    .width( controlWidth )
                    .color(['#444', '#444', '#444']);

                g.select('.nv-controlsWrap')
                    .datum(controlsData)
                    .call(controls);

                var requiredTop = Math.max(controls.height(), showLegend && (legendPosition === 'top') ? legend.height() : 0);

                if ( margin.top != requiredTop ) {
                    margin.top = requiredTop;
                    availableHeight = nv.utils.availableHeight(height, container, margin) - (focusEnable ? focus.height() : 0);
                }

                g.select('.nv-controlsWrap')
                    .attr('transform', 'translate(0,' + (-margin.top) +')');
            }

            wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

            if (rightAlignYAxis) {
                g.select(".nv-y.nv-axis")
                    .attr("transform", "translate(" + availableWidth + ",0)");
            }

            //Set up interactive layer
            if (useInteractiveGuideline) {
                interactiveLayer
                    .width(availableWidth)
                    .height(availableHeight)
                    .margin({left: margin.left, top: margin.top})
                    .svgContainer(container)
                    .xScale(x);
                wrap.select(".nv-interactive").call(interactiveLayer);
            }

            g.select('.nv-focus .nv-background rect')
                .attr('width', availableWidth)
                .attr('height', availableHeight);

            stacked
                .width(availableWidth)
                .height(availableHeight)
                .color(data.map(function(d,i) {
                    return d.color || color(d, i);
                }).filter(function(d,i) { return !data[i].disabled; }));

            var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
                .datum(data.filter(function(d) { return !d.disabled; }));

            // Setup Axes
            if (showXAxis) {
                xAxis.scale(x)
                    ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
                    .tickSize( -availableHeight, 0);
            }

            if (showYAxis) {
                var ticks;
                if (stacked.offset() === 'wiggle') {
                    ticks = 0;
                }
                else {
                    ticks = nv.utils.calcTicksY(availableHeight/36, data);
                }
                yAxis.scale(y)
                    ._ticks(ticks)
                    .tickSize(-availableWidth, 0);
            }

            //============================================================
            // Update Axes
            //============================================================
            function updateXAxis() {
                if(showXAxis) {
                    g.select('.nv-focus .nv-x.nv-axis')
                        .attr('transform', 'translate(0,' + availableHeight + ')')
                        .transition()
                        .duration(duration)
                        .call(xAxis)
                        ;
                }
            }

            function updateYAxis() {
                if(showYAxis) {
                    if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
                        var currentFormat = yAxis.tickFormat();

                        if ( !oldYTickFormat || currentFormat !== percentFormatter )
                            oldYTickFormat = currentFormat;

                        //Forces the yAxis to use percentage in 'expand' mode.
                        yAxis.tickFormat(percentFormatter);
                    }
                    else {
                        if (oldYTickFormat) {
                            yAxis.tickFormat(oldYTickFormat);
                            oldYTickFormat = null;
                        }
                    }

                    g.select('.nv-focus .nv-y.nv-axis')
                    .transition().duration(0)
                    .call(yAxis);
                }
            }

            //============================================================
            // Update Focus
            //============================================================
            if(!focusEnable) {
                stackedWrap.transition().call(stacked);
                updateXAxis();
                updateYAxis();
            } else {
                focus.width(availableWidth);
                g.select('.nv-focusWrap')
                    .attr('transform', 'translate(0,' + ( availableHeight + margin.bottom + focus.margin().top) + ')')
                    .datum(data.filter(function(d) { return !d.disabled; }))
                    .call(focus);
                var extent = focus.brush.empty() ? focus.xDomain() : focus.brush.extent();
                if(extent !== null){
                    onBrush(extent);
                }
            }

            //============================================================
            // Event Handling/Dispatching (in chart's scope)
            //------------------------------------------------------------

            stacked.dispatch.on('areaClick.toggle', function(e) {
                if (data.filter(function(d) { return !d.disabled }).length === 1)
                    data.forEach(function(d) {
                        d.disabled = false;
                    });
                else
                    data.forEach(function(d,i) {
                        d.disabled = (i != e.seriesIndex);
                    });

                state.disabled = data.map(function(d) { return !!d.disabled });
                dispatch.stateChange(state);

                chart.update();
            });

            legend.dispatch.on('stateChange', function(newState) {
                for (var key in newState)
                    state[key] = newState[key];
                dispatch.stateChange(state);
                chart.update();
            });

            controls.dispatch.on('legendClick', function(d,i) {
                if (!d.disabled) return;

                controlsData = controlsData.map(function(s) {
                    s.disabled = true;
                    return s;
                });
                d.disabled = false;

                stacked.style(d.style);


                state.style = stacked.style();
                dispatch.stateChange(state);

                chart.update();
            });

            interactiveLayer.dispatch.on('elementMousemove', function(e) {
                stacked.clearHighlights();
                var singlePoint, pointIndex, pointXLocation, allData = [], valueSum = 0, allNullValues = true;
                data
                    .filter(function(series, i) {
                        series.seriesIndex = i;
                        return !series.disabled;
                    })
                    .forEach(function(series,i) {
                        pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
                        var point = series.values[pointIndex];
                        var pointYValue = chart.y()(point, pointIndex);
                        if (pointYValue != null) {
                            stacked.highlightPoint(i, pointIndex, true);
                        }
                        if (typeof point === 'undefined') return;
                        if (typeof singlePoint === 'undefined') singlePoint = point;
                        if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));

                        //If we are in 'expand' mode, use the stacked percent value instead of raw value.
                        var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
                        allData.push({
                            key: series.key,
                            value: tooltipValue,
                            color: color(series,series.seriesIndex),
                            point: point
                        });

                        if (showTotalInTooltip && stacked.style() != 'expand' && tooltipValue != null) {
                          valueSum += tooltipValue;
                          allNullValues = false;
                        };
                    });

                allData.reverse();

                //Highlight the tooltip entry based on which stack the mouse is closest to.
                if (allData.length > 2) {
                    var yValue = chart.yScale().invert(e.mouseY);
                    var yDistMax = Infinity, indexToHighlight = null;
                    allData.forEach(function(series,i) {

                        //To handle situation where the stacked area chart is negative, we need to use absolute values
                        //when checking if the mouse Y value is within the stack area.
                        yValue = Math.abs(yValue);
                        var stackedY0 = Math.abs(series.point.display.y0);
                        var stackedY = Math.abs(series.point.display.y);
                        if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
                        {
                            indexToHighlight = i;
                            return;
                        }
                    });
                    if (indexToHighlight != null)
                        allData[indexToHighlight].highlight = true;
                }

                //If we are not in 'expand' mode, add a 'Total' row to the tooltip.
                if (showTotalInTooltip && stacked.style() != 'expand' && allData.length >= 2 && !allNullValues) {
                    allData.push({
                        key: totalLabel,
                        value: valueSum,
                        total: true
                    });
                }

                var xValue = chart.x()(singlePoint,pointIndex);

                var valueFormatter = interactiveLayer.tooltip.valueFormatter();
                // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
                if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
                    if ( !oldValueFormatter ) {
                        oldValueFormatter = valueFormatter;
                    }
                    //Forces the tooltip to use percentage in 'expand' mode.
                    valueFormatter = d3.format(".1%");
                }
                else {
                    if (oldValueFormatter) {
                        valueFormatter = oldValueFormatter;
                        oldValueFormatter = null;
                    }
                }

                interactiveLayer.tooltip
                    .valueFormatter(valueFormatter)
                    .data(
                    {
                        value: xValue,
                        series: allData
                    }
                )();

                interactiveLayer.renderGuideLine(pointXLocation);

            });

            interactiveLayer.dispatch.on("elementMouseout",function(e) {
                stacked.clearHighlights();
            });

            /* Update `main' graph on brush update. */
            focus.dispatch.on("onBrush", function(extent) {
                onBrush(extent);
            });

            // Update chart from a state object passed to event handler
            dispatch.on('changeState', function(e) {

                if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
                    data.forEach(function(series,i) {
                        series.disabled = e.disabled[i];
                    });

                    state.disabled = e.disabled;
                }

                if (typeof e.style !== 'undefined') {
                    stacked.style(e.style);
                    style = e.style;
                }

                chart.update();
            });

            //============================================================
            // Functions
            //------------------------------------------------------------

            function onBrush(extent) {
                // Update Main (Focus)
                var stackedWrap = g.select('.nv-focus .nv-stackedWrap')
                    .datum(
                    data.filter(function(d) { return !d.disabled; })
                        .map(function(d,i) {
                            return {
                                key: d.key,
                                area: d.area,
                                classed: d.classed,
                                values: d.values.filter(function(d,i) {
                                    return stacked.x()(d,i) >= extent[0] && stacked.x()(d,i) <= extent[1];
                                }),
                                disableTooltip: d.disableTooltip
                            };
                        })
                );
                stackedWrap.transition().duration(duration).call(stacked);

                // Update Main (Focus) Axes
                updateXAxis();
                updateYAxis();
            }

        });

        renderWatch.renderEnd('stacked Area chart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt.point['x'] = stacked.x()(evt.point);
        evt.point['y'] = stacked.y()(evt.point);
        tooltip.data(evt).hidden(false);
    });

    stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true)
    });
    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.stacked = stacked;
    chart.legend = legend;
    chart.controls = controls;
    chart.xAxis = xAxis;
    chart.x2Axis = focus.xAxis;
    chart.yAxis = yAxis;
    chart.y2Axis = focus.yAxis;
    chart.interactiveLayer = interactiveLayer;
    chart.tooltip = tooltip;
    chart.focus = focus;

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
        legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
        showXAxis:      {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
        showYAxis:    {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
        defaultState:    {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
        noData:    {get: function(){return noData;}, set: function(_){noData=_;}},
        showControls:    {get: function(){return showControls;}, set: function(_){showControls=_;}},
        controlLabels:    {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
        controlOptions:    {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
        showTotalInTooltip:      {get: function(){return showTotalInTooltip;}, set: function(_){showTotalInTooltip=_;}},
        totalLabel:      {get: function(){return totalLabel;}, set: function(_){totalLabel=_;}},
        focusEnable:    {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
        focusHeight:     {get: function(){return focus.height();}, set: function(_){focus.height(_);}},
        brushExtent: {get: function(){return focus.brushExtent();}, set: function(_){focus.brushExtent(_);}},

        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            if (_.top !== undefined) {
                margin.top = _.top;
                marginTop = _.top;
            }
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
        }},
        focusMargin: {get: function(){return focus.margin}, set: function(_){
            focus.margin.top    = _.top    !== undefined ? _.top    : focus.margin.top;
            focus.margin.right  = _.right  !== undefined ? _.right  : focus.margin.right;
            focus.margin.bottom = _.bottom !== undefined ? _.bottom : focus.margin.bottom;
            focus.margin.left   = _.left   !== undefined ? _.left   : focus.margin.left;
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            stacked.duration(duration);
            xAxis.duration(duration);
            yAxis.duration(duration);
        }},
        color:  {get: function(){return color;}, set: function(_){
            color = nv.utils.getColor(_);
            legend.color(color);
            stacked.color(color);
            focus.color(color);
        }},
        x: {get: function(){return stacked.x();}, set: function(_){
            stacked.x(_);
            focus.x(_);
        }},
        y: {get: function(){return stacked.y();}, set: function(_){
            stacked.y(_);
            focus.y(_);
        }},
        rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
            rightAlignYAxis = _;
            yAxis.orient( rightAlignYAxis ? 'right' : 'left');
        }},
        useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
            useInteractiveGuideline = !!_;
            chart.interactive(!_);
            chart.useVoronoi(!_);
            stacked.scatter.interactive(!_);
        }}
    });

    nv.utils.inheritOptions(chart, stacked);
    nv.utils.initOptions(chart);

    return chart;
};

nv.models.stackedAreaWithFocusChart = function() {
  return nv.models.stackedAreaChart()
    .margin({ bottom: 30 })
    .focusEnable( true );
};
// based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
nv.models.sunburst = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var margin = {top: 0, right: 0, bottom: 0, left: 0}
        , width = 600
        , height = 600
        , mode = "count"
        , modes = {count: function(d) { return 1; }, value: function(d) { return d.value || d.size }, size: function(d) { return d.value || d.size }}
        , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
        , container = null
        , color = nv.utils.defaultColor()
        , showLabels = false
        , labelFormat = function(d){if(mode === 'count'){return d.name + ' #' + d.value}else{return d.name + ' ' + (d.value || d.size)}}
        , labelThreshold = 0.02
        , sort = function(d1, d2){return d1.name > d2.name;}
        , key = function(d,i){return d.name;}
        , groupColorByParent = true
        , duration = 500
        , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd');

    //============================================================
    // aux functions and setup
    //------------------------------------------------------------

    var x = d3.scale.linear().range([0, 2 * Math.PI]);
    var y = d3.scale.sqrt();

    var partition = d3.layout.partition().sort(sort);

    var node, availableWidth, availableHeight, radius;
    var prevPositions = {};

    var arc = d3.svg.arc()
        .startAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x))) })
        .endAngle(function(d) {return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))) })
        .innerRadius(function(d) {return Math.max(0, y(d.y)) })
        .outerRadius(function(d) {return Math.max(0, y(d.y + d.dy)) });

    function rotationToAvoidUpsideDown(d) {
        var centerAngle = computeCenterAngle(d);
        if(centerAngle > 90){
            return 180;
        }
        else {
            return 0;
        }
    }

    function computeCenterAngle(d) {
        var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
        var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
        var centerAngle = (((startAngle + endAngle) / 2) * (180 / Math.PI)) - 90;
        return centerAngle;
    }

    function computeNodePercentage(d) {
        var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
        var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
        return (endAngle - startAngle) / (2 * Math.PI);
    }

    function labelThresholdMatched(d) {
        var startAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x)));
        var endAngle = Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));

        var size = endAngle - startAngle;
        return size > labelThreshold;
    }

    // When zooming: interpolate the scales.
    function arcTweenZoom(e,i) {
        var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]),
        yd = d3.interpolate(y.domain(), [node.y, 1]),
        yr = d3.interpolate(y.range(), [node.y ? 20 : 0, radius]);

        if (i === 0) {
            return function() {return arc(e);}
        }
        else {
            return function (t) {
                x.domain(xd(t));
                y.domain(yd(t)).range(yr(t));
                return arc(e);
            }
        };
    }

    function arcTweenUpdate(d) {
        var ipo = d3.interpolate({x: d.x0, dx: d.dx0, y: d.y0, dy: d.dy0}, d);

        return function (t) {
            var b = ipo(t);

            d.x0 = b.x;
            d.dx0 = b.dx;
            d.y0 = b.y;
            d.dy0 = b.dy;

            return arc(b);
        };
    }

    function updatePrevPosition(node) {
        var k = key(node);
        if(! prevPositions[k]) prevPositions[k] = {};
        var pP = prevPositions[k];
        pP.dx = node.dx;
        pP.x = node.x;
        pP.dy = node.dy;
        pP.y = node.y;
    }

    function storeRetrievePrevPositions(nodes) {
        nodes.forEach(function(n){
            var k = key(n);
            var pP = prevPositions[k];
            //console.log(k,n,pP);
            if( pP ){
                n.dx0 = pP.dx;
                n.x0 = pP.x;
                n.dy0 = pP.dy;
                n.y0 = pP.y;
            }
            else {
                n.dx0 = n.dx;
                n.x0 = n.x;
                n.dy0 = n.dy;
                n.y0 = n.y;
            }
            updatePrevPosition(n);
        });
    }

    function zoomClick(d) {
        var labels = container.selectAll('text')
        var path = container.selectAll('path')

        // fade out all text elements
        labels.transition().attr("opacity",0);

        // to allow reference to the new center node
        node = d;

        path.transition()
            .duration(duration)
            .attrTween("d", arcTweenZoom)
            .each('end', function(e) {
                // partially taken from here: http://bl.ocks.org/metmajer/5480307
                // check if the animated element's data e lies within the visible angle span given in d
                if(e.x >= d.x && e.x < (d.x + d.dx) ){
                    if(e.depth >= d.depth){
                        // get a selection of the associated text element
                        var parentNode = d3.select(this.parentNode);
                        var arcText = parentNode.select('text');

                        // fade in the text element and recalculate positions
                        arcText.transition().duration(duration)
                        .text( function(e){return labelFormat(e) })
                        .attr("opacity", function(d){
                            if(labelThresholdMatched(d)) {
                                return 1;
                            }
                            else {
                                return 0;
                            }
                        })
                        .attr("transform", function() {
                            var width = this.getBBox().width;
                            if(e.depth === 0)
                            return "translate(" + (width / 2 * - 1) + ",0)";
                            else if(e.depth === d.depth){
                                return "translate(" + (y(e.y) + 5) + ",0)";
                            }
                            else {
                                var centerAngle = computeCenterAngle(e);
                                var rotation = rotationToAvoidUpsideDown(e);
                                if (rotation === 0) {
                                    return 'rotate('+ centerAngle +')translate(' + (y(e.y) + 5) + ',0)';
                                }
                                else {
                                    return 'rotate('+ centerAngle +')translate(' + (y(e.y) + width + 5) + ',0)rotate(' + rotation + ')';
                                }
                            }
                        });
                    }
                }
            })
    }

    //============================================================
    // chart function
    //------------------------------------------------------------
    var renderWatch = nv.utils.renderWatch(dispatch);

    function chart(selection) {
        renderWatch.reset();

        selection.each(function(data) {
            container = d3.select(this);
            availableWidth = nv.utils.availableWidth(width, container, margin);
            availableHeight = nv.utils.availableHeight(height, container, margin);
            radius = Math.min(availableWidth, availableHeight) / 2;

            y.range([0, radius]);

            // Setup containers and skeleton of chart
            var wrap = container.select('g.nvd3.nv-wrap.nv-sunburst');
            if( !wrap[0][0] ) {
                wrap = container.append('g')
                    .attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id)
                    .attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
            } else {
                wrap.attr('transform', 'translate(' + ((availableWidth / 2) + margin.left + margin.right) + ',' + ((availableHeight / 2) + margin.top + margin.bottom) + ')');
            }

            container.on('click', function (d, i) {
                dispatch.chartClick({
                    data: d,
                    index: i,
                    pos: d3.event,
                    id: id
                });
            });

            partition.value(modes[mode] || modes["count"]);

            //reverse the drawing order so that the labels of inner
            //arcs are drawn on top of the outer arcs.
            var nodes = partition.nodes(data[0]).reverse()

            storeRetrievePrevPositions(nodes);
            var cG = wrap.selectAll('.arc-container').data(nodes, key)

            //handle new datapoints
            var cGE = cG.enter()
                .append("g")
                .attr("class",'arc-container')

            cGE.append("path")
                .attr("d", arc)
                .style("fill", function (d) {
                    if (d.color) {
                        return d.color;
                    }
                    else if (groupColorByParent) {
                        return color((d.children ? d : d.parent).name);
                    }
                    else {
                        return color(d.name);
                    }
                })
                .style("stroke", "#FFF")
                .on("click", function(d,i){
                    zoomClick(d);
                    dispatch.elementClick({
                        data: d,
                        index: i
                    })
                })
                .on('mouseover', function(d,i){
                    d3.select(this).classed('hover', true).style('opacity', 0.8);
                    dispatch.elementMouseover({
                        data: d,
                        color: d3.select(this).style("fill"),
                        percent: computeNodePercentage(d)
                    });
                })
                .on('mouseout', function(d,i){
                    d3.select(this).classed('hover', false).style('opacity', 1);
                    dispatch.elementMouseout({
                        data: d
                    });
                })
                .on('mousemove', function(d,i){
                    dispatch.elementMousemove({
                        data: d
                    });
                });

            ///Iterating via each and selecting based on the this
            ///makes it work ... a cG.selectAll('path') doesn't.
            ///Without iteration the data (in the element) didn't update.
            cG.each(function(d){
                d3.select(this).select('path')
                    .transition()
                    .duration(duration)
                    .attrTween('d', arcTweenUpdate);
            });

            if(showLabels){
                //remove labels first and add them back
                cG.selectAll('text').remove();

                //this way labels are on top of newly added arcs
                cG.append('text')
                    .text( function(e){ return labelFormat(e)})
                    .transition()
                    .duration(duration)
                    .attr("opacity", function(d){
                        if(labelThresholdMatched(d)) {
                            return 1;
                        }
                        else {
                            return 0;
                        }
                    })
                    .attr("transform", function(d) {
                        var width = this.getBBox().width;
                        if(d.depth === 0){
                            return "rotate(0)translate(" + (width / 2 * -1) + ",0)";
                        }
                        else {
                            var centerAngle = computeCenterAngle(d);
                            var rotation = rotationToAvoidUpsideDown(d);
                            if (rotation === 0) {
                                return 'rotate('+ centerAngle +')translate(' + (y(d.y) + 5) + ',0)';
                            }
                            else {
                                return 'rotate('+ centerAngle +')translate(' + (y(d.y) + width + 5) + ',0)rotate(' + rotation + ')';
                            }
                        }
                    });
            }

            //zoom out to the center when the data is updated.
            zoomClick(nodes[nodes.length - 1])


            //remove unmatched elements ...
            cG.exit()
                .transition()
                .duration(duration)
                .attr('opacity',0)
                .each('end',function(d){
                    var k = key(d);
                    prevPositions[k] = undefined;
                })
                .remove();
        });


        renderWatch.renderEnd('sunburst immediate');
        return chart;
    }

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    chart.dispatch = dispatch;
    chart.options = nv.utils.optionsFunc.bind(chart);

    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        width:      {get: function(){return width;}, set: function(_){width=_;}},
        height:     {get: function(){return height;}, set: function(_){height=_;}},
        mode:       {get: function(){return mode;}, set: function(_){mode=_;}},
        id:         {get: function(){return id;}, set: function(_){id=_;}},
        duration:   {get: function(){return duration;}, set: function(_){duration=_;}},
        groupColorByParent: {get: function(){return groupColorByParent;}, set: function(_){groupColorByParent=!!_;}},
        showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=!!_}},
        labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_}},
        labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_}},
        sort: {get: function(){return sort;}, set: function(_){sort=_}},
        key: {get: function(){return key;}, set: function(_){key=_}},
        // options that require extra logic in the setter
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    != undefined ? _.top    : margin.top;
            margin.right  = _.right  != undefined ? _.right  : margin.right;
            margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   != undefined ? _.left   : margin.left;
        }},
        color: {get: function(){return color;}, set: function(_){
            color=nv.utils.getColor(_);
        }}
    });

    nv.utils.initOptions(chart);
    return chart;
};
nv.models.sunburstChart = function() {
    "use strict";

    //============================================================
    // Public Variables with Default Settings
    //------------------------------------------------------------

    var sunburst = nv.models.sunburst();
    var tooltip = nv.models.tooltip();

    var margin = {top: 30, right: 20, bottom: 20, left: 20}
        , width = null
        , height = null
        , color = nv.utils.defaultColor()
        , showTooltipPercent = false
        , id = Math.round(Math.random() * 100000)
        , defaultState = null
        , noData = null
        , duration = 250
        , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd');


    //============================================================
    // Private Variables
    //------------------------------------------------------------

    var renderWatch = nv.utils.renderWatch(dispatch);

    tooltip
        .duration(0)
        .headerEnabled(false)
        .valueFormatter(function(d){return d;});

    //============================================================
    // Chart function
    //------------------------------------------------------------

    function chart(selection) {
        renderWatch.reset();
        renderWatch.models(sunburst);

        selection.each(function(data) {
            var container = d3.select(this);

            nv.utils.initSVG(container);

            var availableWidth = nv.utils.availableWidth(width, container, margin);
            var availableHeight = nv.utils.availableHeight(height, container, margin);

            chart.update = function() {
                if (duration === 0) {
                    container.call(chart);
                } else {
                    container.transition().duration(duration).call(chart);
                }
            };
            chart.container = container;

            // Display No Data message if there's nothing to show.
            if (!data || !data.length) {
                nv.utils.noData(chart, container);
                return chart;
            } else {
                container.selectAll('.nv-noData').remove();
            }

            sunburst.width(availableWidth).height(availableHeight).margin(margin);
            container.call(sunburst);
        });

        renderWatch.renderEnd('sunburstChart immediate');
        return chart;
    }

    //============================================================
    // Event Handling/Dispatching (out of chart's scope)
    //------------------------------------------------------------

    sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
        evt.series = {
            key: evt.data.name,
            value: (evt.data.value || evt.data.size),
            color: evt.color,
            percent: evt.percent
        };
        if (!showTooltipPercent) {
            delete evt.percent;
            delete evt.series.percent;
        }
        tooltip.data(evt).hidden(false);
    });

    sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
        tooltip.hidden(true);
    });

    sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
        tooltip();
    });

    //============================================================
    // Expose Public Variables
    //------------------------------------------------------------

    // expose chart's sub-components
    chart.dispatch = dispatch;
    chart.sunburst = sunburst;
    chart.tooltip = tooltip;
    chart.options = nv.utils.optionsFunc.bind(chart);

    // use Object get/set functionality to map between vars and chart functions
    chart._options = Object.create({}, {
        // simple options, just get/set the necessary values
        noData:             {get: function(){return noData;},               set: function(_){noData=_;}},
        defaultState:       {get: function(){return defaultState;},         set: function(_){defaultState=_;}},
        showTooltipPercent: {get: function(){return showTooltipPercent;},   set: function(_){showTooltipPercent=_;}},

        // options that require extra logic in the setter
        color: {get: function(){return color;}, set: function(_){
            color = _;
            sunburst.color(color);
        }},
        duration: {get: function(){return duration;}, set: function(_){
            duration = _;
            renderWatch.reset(duration);
            sunburst.duration(duration);
        }},
        margin: {get: function(){return margin;}, set: function(_){
            margin.top    = _.top    !== undefined ? _.top    : margin.top;
            margin.right  = _.right  !== undefined ? _.right  : margin.right;
            margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
            margin.left   = _.left   !== undefined ? _.left   : margin.left;
            sunburst.margin(margin);
        }}
    });
    nv.utils.inheritOptions(chart, sunburst);
    nv.utils.initOptions(chart);
    return chart;

};

nv.version = "1.8.5";
})();
//# sourceMappingURL=nv.d3.js.map
/*! nouislider - 9.2.0 - 2017-01-11 10:35:34 */

(function (factory) {

    if ( typeof define === 'function' && define.amd ) {

        // AMD. Register as an anonymous module.
        define([], factory);

    } else if ( typeof exports === 'object' ) {

        // Node/CommonJS
        module.exports = factory();

    } else {

        // Browser globals
        window.noUiSlider = factory();
    }

}(function( ){

	'use strict';

	var VERSION = '9.2.0';


	// Creates a node, adds it to target, returns the new node.
	function addNodeTo ( target, className ) {
		var div = document.createElement('div');
		addClass(div, className);
		target.appendChild(div);
		return div;
	}

	// Removes duplicates from an array.
	function unique ( array ) {
		return array.filter(function(a){
			return !this[a] ? this[a] = true : false;
		}, {});
	}

	// Round a value to the closest 'to'.
	function closest ( value, to ) {
		return Math.round(value / to) * to;
	}

	// Current position of an element relative to the document.
	function offset ( elem, orientation ) {

	var rect = elem.getBoundingClientRect(),
		doc = elem.ownerDocument,
		docElem = doc.documentElement,
		pageOffset = getPageOffset();

		// getBoundingClientRect contains left scroll in Chrome on Android.
		// I haven't found a feature detection that proves this. Worst case
		// scenario on mis-match: the 'tap' feature on horizontal sliders breaks.
		if ( /webkit.*Chrome.*Mobile/i.test(navigator.userAgent) ) {
			pageOffset.x = 0;
		}

		return orientation ? (rect.top + pageOffset.y - docElem.clientTop) : (rect.left + pageOffset.x - docElem.clientLeft);
	}

	// Checks whether a value is numerical.
	function isNumeric ( a ) {
		return typeof a === 'number' && !isNaN( a ) && isFinite( a );
	}

	// Sets a class and removes it after [duration] ms.
	function addClassFor ( element, className, duration ) {
		if (duration > 0) {
		addClass(element, className);
			setTimeout(function(){
				removeClass(element, className);
			}, duration);
		}
	}

	// Limits a value to 0 - 100
	function limit ( a ) {
		return Math.max(Math.min(a, 100), 0);
	}

	// Wraps a variable as an array, if it isn't one yet.
	// Note that an input array is returned by reference!
	function asArray ( a ) {
		return Array.isArray(a) ? a : [a];
	}

	// Counts decimals
	function countDecimals ( numStr ) {
		numStr = String(numStr);
		var pieces = numStr.split(".");
		return pieces.length > 1 ? pieces[1].length : 0;
	}

	// http://youmightnotneedjquery.com/#add_class
	function addClass ( el, className ) {
		if ( el.classList ) {
			el.classList.add(className);
		} else {
			el.className += ' ' + className;
		}
	}

	// http://youmightnotneedjquery.com/#remove_class
	function removeClass ( el, className ) {
		if ( el.classList ) {
			el.classList.remove(className);
		} else {
			el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
		}
	}

	// https://plainjs.com/javascript/attributes/adding-removing-and-testing-for-classes-9/
	function hasClass ( el, className ) {
		return el.classList ? el.classList.contains(className) : new RegExp('\\b' + className + '\\b').test(el.className);
	}

	// https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollY#Notes
	function getPageOffset ( ) {

		var supportPageOffset = window.pageXOffset !== undefined,
			isCSS1Compat = ((document.compatMode || "") === "CSS1Compat"),
			x = supportPageOffset ? window.pageXOffset : isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft,
			y = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop;

		return {
			x: x,
			y: y
		};
	}

	// we provide a function to compute constants instead
	// of accessing window.* as soon as the module needs it
	// so that we do not compute anything if not needed
	function getActions ( ) {

		// Determine the events to bind. IE11 implements pointerEvents without
		// a prefix, which breaks compatibility with the IE10 implementation.
		return window.navigator.pointerEnabled ? {
			start: 'pointerdown',
			move: 'pointermove',
			end: 'pointerup'
		} : window.navigator.msPointerEnabled ? {
			start: 'MSPointerDown',
			move: 'MSPointerMove',
			end: 'MSPointerUp'
		} : {
			start: 'mousedown touchstart',
			move: 'mousemove touchmove',
			end: 'mouseup touchend'
		};
	}


// Value calculation

	// Determine the size of a sub-range in relation to a full range.
	function subRangeRatio ( pa, pb ) {
		return (100 / (pb - pa));
	}

	// (percentage) How many percent is this value of this range?
	function fromPercentage ( range, value ) {
		return (value * 100) / ( range[1] - range[0] );
	}

	// (percentage) Where is this value on this range?
	function toPercentage ( range, value ) {
		return fromPercentage( range, range[0] < 0 ?
			value + Math.abs(range[0]) :
				value - range[0] );
	}

	// (value) How much is this percentage on this range?
	function isPercentage ( range, value ) {
		return ((value * ( range[1] - range[0] )) / 100) + range[0];
	}


// Range conversion

	function getJ ( value, arr ) {

		var j = 1;

		while ( value >= arr[j] ){
			j += 1;
		}

		return j;
	}

	// (percentage) Input a value, find where, on a scale of 0-100, it applies.
	function toStepping ( xVal, xPct, value ) {

		if ( value >= xVal.slice(-1)[0] ){
			return 100;
		}

		var j = getJ( value, xVal ), va, vb, pa, pb;

		va = xVal[j-1];
		vb = xVal[j];
		pa = xPct[j-1];
		pb = xPct[j];

		return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb));
	}

	// (value) Input a percentage, find where it is on the specified range.
	function fromStepping ( xVal, xPct, value ) {

		// There is no range group that fits 100
		if ( value >= 100 ){
			return xVal.slice(-1)[0];
		}

		var j = getJ( value, xPct ), va, vb, pa, pb;

		va = xVal[j-1];
		vb = xVal[j];
		pa = xPct[j-1];
		pb = xPct[j];

		return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb));
	}

	// (percentage) Get the step that applies at a certain value.
	function getStep ( xPct, xSteps, snap, value ) {

		if ( value === 100 ) {
			return value;
		}

		var j = getJ( value, xPct ), a, b;

		// If 'snap' is set, steps are used as fixed points on the slider.
		if ( snap ) {

			a = xPct[j-1];
			b = xPct[j];

			// Find the closest position, a or b.
			if ((value - a) > ((b-a)/2)){
				return b;
			}

			return a;
		}

		if ( !xSteps[j-1] ){
			return value;
		}

		return xPct[j-1] + closest(
			value - xPct[j-1],
			xSteps[j-1]
		);
	}


// Entry parsing

	function handleEntryPoint ( index, value, that ) {

		var percentage;

		// Wrap numerical input in an array.
		if ( typeof value === "number" ) {
			value = [value];
		}

		// Reject any invalid input, by testing whether value is an array.
		if ( Object.prototype.toString.call( value ) !== '[object Array]' ){
			throw new Error("noUiSlider (" + VERSION + "): 'range' contains invalid value.");
		}

		// Covert min/max syntax to 0 and 100.
		if ( index === 'min' ) {
			percentage = 0;
		} else if ( index === 'max' ) {
			percentage = 100;
		} else {
			percentage = parseFloat( index );
		}

		// Check for correct input.
		if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) {
			throw new Error("noUiSlider (" + VERSION + "): 'range' value isn't numeric.");
		}

		// Store values.
		that.xPct.push( percentage );
		that.xVal.push( value[0] );

		// NaN will evaluate to false too, but to keep
		// logging clear, set step explicitly. Make sure
		// not to override the 'step' setting with false.
		if ( !percentage ) {
			if ( !isNaN( value[1] ) ) {
				that.xSteps[0] = value[1];
			}
		} else {
			that.xSteps.push( isNaN(value[1]) ? false : value[1] );
		}

		that.xHighestCompleteStep.push(0);
	}

	function handleStepPoint ( i, n, that ) {

		// Ignore 'false' stepping.
		if ( !n ) {
			return true;
		}

		// Factor to range ratio
		that.xSteps[i] = fromPercentage([
			 that.xVal[i]
			,that.xVal[i+1]
		], n) / subRangeRatio (
			that.xPct[i],
			that.xPct[i+1] );

		var totalSteps = (that.xVal[i+1] - that.xVal[i]) / that.xNumSteps[i];
		var highestStep = Math.ceil(Number(totalSteps.toFixed(3)) - 1);
		var step = that.xVal[i] + (that.xNumSteps[i] * highestStep);

		that.xHighestCompleteStep[i] = step;
	}


// Interface

	// The interface to Spectrum handles all direction-based
	// conversions, so the above values are unaware.

	function Spectrum ( entry, snap, direction, singleStep ) {

		this.xPct = [];
		this.xVal = [];
		this.xSteps = [ singleStep || false ];
		this.xNumSteps = [ false ];
		this.xHighestCompleteStep = [];

		this.snap = snap;
		this.direction = direction;

		var index, ordered = [ /* [0, 'min'], [1, '50%'], [2, 'max'] */ ];

		// Map the object keys to an array.
		for ( index in entry ) {
			if ( entry.hasOwnProperty(index) ) {
				ordered.push([entry[index], index]);
			}
		}

		// Sort all entries by value (numeric sort).
		if ( ordered.length && typeof ordered[0][0] === "object" ) {
			ordered.sort(function(a, b) { return a[0][0] - b[0][0]; });
		} else {
			ordered.sort(function(a, b) { return a[0] - b[0]; });
		}


		// Convert all entries to subranges.
		for ( index = 0; index < ordered.length; index++ ) {
			handleEntryPoint(ordered[index][1], ordered[index][0], this);
		}

		// Store the actual step values.
		// xSteps is sorted in the same order as xPct and xVal.
		this.xNumSteps = this.xSteps.slice(0);

		// Convert all numeric steps to the percentage of the subrange they represent.
		for ( index = 0; index < this.xNumSteps.length; index++ ) {
			handleStepPoint(index, this.xNumSteps[index], this);
		}
	}

	Spectrum.prototype.getMargin = function ( value ) {

		var step = this.xNumSteps[0];

		if ( step && ((value / step) % 1) !== 0 ) {
			throw new Error("noUiSlider (" + VERSION + "): 'limit', 'margin' and 'padding' must be divisible by step.");
		}

		return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false;
	};

	Spectrum.prototype.toStepping = function ( value ) {

		value = toStepping( this.xVal, this.xPct, value );

		return value;
	};

	Spectrum.prototype.fromStepping = function ( value ) {

		return fromStepping( this.xVal, this.xPct, value );
	};

	Spectrum.prototype.getStep = function ( value ) {

		value = getStep(this.xPct, this.xSteps, this.snap, value );

		return value;
	};

	Spectrum.prototype.getNearbySteps = function ( value ) {

		var j = getJ(value, this.xPct);

		return {
			stepBefore: { startValue: this.xVal[j-2], step: this.xNumSteps[j-2], highestStep: this.xHighestCompleteStep[j-2] },
			thisStep: { startValue: this.xVal[j-1], step: this.xNumSteps[j-1], highestStep: this.xHighestCompleteStep[j-1] },
			stepAfter: { startValue: this.xVal[j-0], step: this.xNumSteps[j-0], highestStep: this.xHighestCompleteStep[j-0] }
		};
	};

	Spectrum.prototype.countStepDecimals = function () {
		var stepDecimals = this.xNumSteps.map(countDecimals);
		return Math.max.apply(null, stepDecimals);
 	};

	// Outside testing
	Spectrum.prototype.convert = function ( value ) {
		return this.getStep(this.toStepping(value));
	};

/*	Every input option is tested and parsed. This'll prevent
	endless validation in internal methods. These tests are
	structured with an item for every option available. An
	option can be marked as required by setting the 'r' flag.
	The testing function is provided with three arguments:
		- The provided value for the option;
		- A reference to the options object;
		- The name for the option;

	The testing function returns false when an error is detected,
	or true when everything is OK. It can also modify the option
	object, to make sure all values can be correctly looped elsewhere. */

	var defaultFormatter = { 'to': function( value ){
		return value !== undefined && value.toFixed(2);
	}, 'from': Number };

	function testStep ( parsed, entry ) {

		if ( !isNumeric( entry ) ) {
			throw new Error("noUiSlider (" + VERSION + "): 'step' is not numeric.");
		}

		// The step option can still be used to set stepping
		// for linear sliders. Overwritten if set in 'range'.
		parsed.singleStep = entry;
	}

	function testRange ( parsed, entry ) {

		// Filter incorrect input.
		if ( typeof entry !== 'object' || Array.isArray(entry) ) {
			throw new Error("noUiSlider (" + VERSION + "): 'range' is not an object.");
		}

		// Catch missing start or end.
		if ( entry.min === undefined || entry.max === undefined ) {
			throw new Error("noUiSlider (" + VERSION + "): Missing 'min' or 'max' in 'range'.");
		}

		// Catch equal start or end.
		if ( entry.min === entry.max ) {
			throw new Error("noUiSlider (" + VERSION + "): 'range' 'min' and 'max' cannot be equal.");
		}

		parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.dir, parsed.singleStep);
	}

	function testStart ( parsed, entry ) {

		entry = asArray(entry);

		// Validate input. Values aren't tested, as the public .val method
		// will always provide a valid location.
		if ( !Array.isArray( entry ) || !entry.length ) {
			throw new Error("noUiSlider (" + VERSION + "): 'start' option is incorrect.");
		}

		// Store the number of handles.
		parsed.handles = entry.length;

		// When the slider is initialized, the .val method will
		// be called with the start options.
		parsed.start = entry;
	}

	function testSnap ( parsed, entry ) {

		// Enforce 100% stepping within subranges.
		parsed.snap = entry;

		if ( typeof entry !== 'boolean' ){
			throw new Error("noUiSlider (" + VERSION + "): 'snap' option must be a boolean.");
		}
	}

	function testAnimate ( parsed, entry ) {

		// Enforce 100% stepping within subranges.
		parsed.animate = entry;

		if ( typeof entry !== 'boolean' ){
			throw new Error("noUiSlider (" + VERSION + "): 'animate' option must be a boolean.");
		}
	}

	function testAnimationDuration ( parsed, entry ) {

		parsed.animationDuration = entry;

		if ( typeof entry !== 'number' ){
			throw new Error("noUiSlider (" + VERSION + "): 'animationDuration' option must be a number.");
		}
	}

	function testConnect ( parsed, entry ) {

		var connect = [false];
		var i;

		// Map legacy options
		if ( entry === 'lower' ) {
			entry = [true, false];
		}

		else if ( entry === 'upper' ) {
			entry = [false, true];
		}

		// Handle boolean options
		if ( entry === true || entry === false ) {

			for ( i = 1; i < parsed.handles; i++ ) {
				connect.push(entry);
			}

			connect.push(false);
		}

		// Reject invalid input
		else if ( !Array.isArray( entry ) || !entry.length || entry.length !== parsed.handles + 1 ) {
			throw new Error("noUiSlider (" + VERSION + "): 'connect' option doesn't match handle count.");
		}

		else {
			connect = entry;
		}

		parsed.connect = connect;
	}

	function testOrientation ( parsed, entry ) {

		// Set orientation to an a numerical value for easy
		// array selection.
		switch ( entry ){
		  case 'horizontal':
			parsed.ort = 0;
			break;
		  case 'vertical':
			parsed.ort = 1;
			break;
		  default:
			throw new Error("noUiSlider (" + VERSION + "): 'orientation' option is invalid.");
		}
	}

	function testMargin ( parsed, entry ) {

		if ( !isNumeric(entry) ){
			throw new Error("noUiSlider (" + VERSION + "): 'margin' option must be numeric.");
		}

		// Issue #582
		if ( entry === 0 ) {
			return;
		}

		parsed.margin = parsed.spectrum.getMargin(entry);

		if ( !parsed.margin ) {
			throw new Error("noUiSlider (" + VERSION + "): 'margin' option is only supported on linear sliders.");
		}
	}

	function testLimit ( parsed, entry ) {

		if ( !isNumeric(entry) ){
			throw new Error("noUiSlider (" + VERSION + "): 'limit' option must be numeric.");
		}

		parsed.limit = parsed.spectrum.getMargin(entry);

		if ( !parsed.limit || parsed.handles < 2 ) {
			throw new Error("noUiSlider (" + VERSION + "): 'limit' option is only supported on linear sliders with 2 or more handles.");
		}
	}

	function testPadding ( parsed, entry ) {

		if ( !isNumeric(entry) ){
			throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric.");
		}

		if ( entry === 0 ) {
			return;
		}

		parsed.padding = parsed.spectrum.getMargin(entry);

		if ( !parsed.padding ) {
			throw new Error("noUiSlider (" + VERSION + "): 'padding' option is only supported on linear sliders.");
		}

		if ( parsed.padding < 0 ) {
			throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number.");
		}

		if ( parsed.padding >= 50 ) {
			throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be less than half the range.");
		}
	}

	function testDirection ( parsed, entry ) {

		// Set direction as a numerical value for easy parsing.
		// Invert connection for RTL sliders, so that the proper
		// handles get the connect/background classes.
		switch ( entry ) {
		  case 'ltr':
			parsed.dir = 0;
			break;
		  case 'rtl':
			parsed.dir = 1;
			break;
		  default:
			throw new Error("noUiSlider (" + VERSION + "): 'direction' option was not recognized.");
		}
	}

	function testBehaviour ( parsed, entry ) {

		// Make sure the input is a string.
		if ( typeof entry !== 'string' ) {
			throw new Error("noUiSlider (" + VERSION + "): 'behaviour' must be a string containing options.");
		}

		// Check if the string contains any keywords.
		// None are required.
		var tap = entry.indexOf('tap') >= 0;
		var drag = entry.indexOf('drag') >= 0;
		var fixed = entry.indexOf('fixed') >= 0;
		var snap = entry.indexOf('snap') >= 0;
		var hover = entry.indexOf('hover') >= 0;

		if ( fixed ) {

			if ( parsed.handles !== 2 ) {
				throw new Error("noUiSlider (" + VERSION + "): 'fixed' behaviour must be used with 2 handles");
			}

			// Use margin to enforce fixed state
			testMargin(parsed, parsed.start[1] - parsed.start[0]);
		}

		parsed.events = {
			tap: tap || snap,
			drag: drag,
			fixed: fixed,
			snap: snap,
			hover: hover
		};
	}

	function testTooltips ( parsed, entry ) {

		if ( entry === false ) {
			return;
		}

		else if ( entry === true ) {

			parsed.tooltips = [];

			for ( var i = 0; i < parsed.handles; i++ ) {
				parsed.tooltips.push(true);
			}
		}

		else {

			parsed.tooltips = asArray(entry);

			if ( parsed.tooltips.length !== parsed.handles ) {
				throw new Error("noUiSlider (" + VERSION + "): must pass a formatter for all handles.");
			}

			parsed.tooltips.forEach(function(formatter){
				if ( typeof formatter !== 'boolean' && (typeof formatter !== 'object' || typeof formatter.to !== 'function') ) {
					throw new Error("noUiSlider (" + VERSION + "): 'tooltips' must be passed a formatter or 'false'.");
				}
			});
		}
	}

	function testFormat ( parsed, entry ) {

		parsed.format = entry;

		// Any object with a to and from method is supported.
		if ( typeof entry.to === 'function' && typeof entry.from === 'function' ) {
			return true;
		}

		throw new Error("noUiSlider (" + VERSION + "): 'format' requires 'to' and 'from' methods.");
	}

	function testCssPrefix ( parsed, entry ) {

		if ( entry !== undefined && typeof entry !== 'string' && entry !== false ) {
			throw new Error("noUiSlider (" + VERSION + "): 'cssPrefix' must be a string or `false`.");
		}

		parsed.cssPrefix = entry;
	}

	function testCssClasses ( parsed, entry ) {

		if ( entry !== undefined && typeof entry !== 'object' ) {
			throw new Error("noUiSlider (" + VERSION + "): 'cssClasses' must be an object.");
		}

		if ( typeof parsed.cssPrefix === 'string' ) {
			parsed.cssClasses = {};

			for ( var key in entry ) {
				if ( !entry.hasOwnProperty(key) ) { continue; }

				parsed.cssClasses[key] = parsed.cssPrefix + entry[key];
			}
		} else {
			parsed.cssClasses = entry;
		}
	}

	function testUseRaf ( parsed, entry ) {
		if ( entry === true || entry === false ) {
			parsed.useRequestAnimationFrame = entry;
		} else {
			throw new Error("noUiSlider (" + VERSION + "): 'useRequestAnimationFrame' option should be true (default) or false.");
		}
	}

	// Test all developer settings and parse to assumption-safe values.
	function testOptions ( options ) {

		// To prove a fix for #537, freeze options here.
		// If the object is modified, an error will be thrown.
		// Object.freeze(options);

		var parsed = {
			margin: 0,
			limit: 0,
			padding: 0,
			animate: true,
			animationDuration: 300,
			format: defaultFormatter
		};

		// Tests are executed in the order they are presented here.
		var tests = {
			'step': { r: false, t: testStep },
			'start': { r: true, t: testStart },
			'connect': { r: true, t: testConnect },
			'direction': { r: true, t: testDirection },
			'snap': { r: false, t: testSnap },
			'animate': { r: false, t: testAnimate },
			'animationDuration': { r: false, t: testAnimationDuration },
			'range': { r: true, t: testRange },
			'orientation': { r: false, t: testOrientation },
			'margin': { r: false, t: testMargin },
			'limit': { r: false, t: testLimit },
			'padding': { r: false, t: testPadding },
			'behaviour': { r: true, t: testBehaviour },
			'format': { r: false, t: testFormat },
			'tooltips': { r: false, t: testTooltips },
			'cssPrefix': { r: false, t: testCssPrefix },
			'cssClasses': { r: false, t: testCssClasses },
			'useRequestAnimationFrame': { r: false, t: testUseRaf }
		};

		var defaults = {
			'connect': false,
			'direction': 'ltr',
			'behaviour': 'tap',
			'orientation': 'horizontal',
			'cssPrefix' : 'noUi-',
			'cssClasses': {
				target: 'target',
				base: 'base',
				origin: 'origin',
				handle: 'handle',
				handleLower: 'handle-lower',
				handleUpper: 'handle-upper',
				horizontal: 'horizontal',
				vertical: 'vertical',
				background: 'background',
				connect: 'connect',
				ltr: 'ltr',
				rtl: 'rtl',
				draggable: 'draggable',
				drag: 'state-drag',
				tap: 'state-tap',
				active: 'active',
				tooltip: 'tooltip',
				pips: 'pips',
				pipsHorizontal: 'pips-horizontal',
				pipsVertical: 'pips-vertical',
				marker: 'marker',
				markerHorizontal: 'marker-horizontal',
				markerVertical: 'marker-vertical',
				markerNormal: 'marker-normal',
				markerLarge: 'marker-large',
				markerSub: 'marker-sub',
				value: 'value',
				valueHorizontal: 'value-horizontal',
				valueVertical: 'value-vertical',
				valueNormal: 'value-normal',
				valueLarge: 'value-large',
				valueSub: 'value-sub'
			},
			'useRequestAnimationFrame': true
		};

		// Run all options through a testing mechanism to ensure correct
		// input. It should be noted that options might get modified to
		// be handled properly. E.g. wrapping integers in arrays.
		Object.keys(tests).forEach(function( name ){

			// If the option isn't set, but it is required, throw an error.
			if ( options[name] === undefined && defaults[name] === undefined ) {

				if ( tests[name].r ) {
					throw new Error("noUiSlider (" + VERSION + "): '" + name + "' is required.");
				}

				return true;
			}

			tests[name].t( parsed, options[name] === undefined ? defaults[name] : options[name] );
		});

		// Forward pips options
		parsed.pips = options.pips;

		var styles = [['left', 'top'], ['right', 'bottom']];

		// Pre-define the styles.
		parsed.style = styles[parsed.dir][parsed.ort];
		parsed.styleOposite = styles[parsed.dir?0:1][parsed.ort];

		return parsed;
	}


function closure ( target, options, originalOptions ){

	var actions = getActions( );

	// All variables local to 'closure' are prefixed with 'scope_'
	var scope_Target = target;
	var scope_Locations = [];
	var scope_Base;
	var scope_Handles;
	var scope_HandleNumbers = [];
	var scope_ActiveHandle = false;
	var scope_Connects;
	var scope_Spectrum = options.spectrum;
	var scope_Values = [];
	var scope_Events = {};
	var scope_Self;


	// Append a origin to the base
	function addOrigin ( base, handleNumber ) {

		var origin = addNodeTo(base, options.cssClasses.origin);
		var handle = addNodeTo(origin, options.cssClasses.handle);

		handle.setAttribute('data-handle', handleNumber);

		if ( handleNumber === 0 ) {
			addClass(handle, options.cssClasses.handleLower);
		}

		else if ( handleNumber === options.handles - 1 ) {
			addClass(handle, options.cssClasses.handleUpper);
		}

		return origin;
	}

	// Insert nodes for connect elements
	function addConnect ( base, add ) {

		if ( !add ) {
			return false;
		}

		return addNodeTo(base, options.cssClasses.connect);
	}

	// Add handles to the slider base.
	function addElements ( connectOptions, base ) {

		scope_Handles = [];
		scope_Connects = [];

		scope_Connects.push(addConnect(base, connectOptions[0]));

		// [::::O====O====O====]
		// connectOptions = [0, 1, 1, 1]

		for ( var i = 0; i < options.handles; i++ ) {
			// Keep a list of all added handles.
			scope_Handles.push(addOrigin(base, i));
			scope_HandleNumbers[i] = i;
			scope_Connects.push(addConnect(base, connectOptions[i + 1]));
		}
	}

	// Initialize a single slider.
	function addSlider ( target ) {

		// Apply classes and data to the target.
		addClass(target, options.cssClasses.target);

		if ( options.dir === 0 ) {
			addClass(target, options.cssClasses.ltr);
		} else {
			addClass(target, options.cssClasses.rtl);
		}

		if ( options.ort === 0 ) {
			addClass(target, options.cssClasses.horizontal);
		} else {
			addClass(target, options.cssClasses.vertical);
		}

		scope_Base = addNodeTo(target, options.cssClasses.base);
	}


	function addTooltip ( handle, handleNumber ) {

		if ( !options.tooltips[handleNumber] ) {
			return false;
		}

		return addNodeTo(handle.firstChild, options.cssClasses.tooltip);
	}

	// The tooltips option is a shorthand for using the 'update' event.
	function tooltips ( ) {

		// Tooltips are added with options.tooltips in original order.
		var tips = scope_Handles.map(addTooltip);

		bindEvent('update', function(values, handleNumber, unencoded) {

			if ( !tips[handleNumber] ) {
				return;
			}

			var formattedValue = values[handleNumber];

			if ( options.tooltips[handleNumber] !== true ) {
				formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]);
			}

			tips[handleNumber].innerHTML = formattedValue;
		});
	}


	function getGroup ( mode, values, stepped ) {

		// Use the range.
		if ( mode === 'range' || mode === 'steps' ) {
			return scope_Spectrum.xVal;
		}

		if ( mode === 'count' ) {

			if ( !values ) {
				throw new Error("noUiSlider (" + VERSION + "): 'values' required for mode 'count'.");
			}

			// Divide 0 - 100 in 'count' parts.
			var spread = ( 100 / (values - 1) );
			var v;
			var i = 0;

			values = [];

			// List these parts and have them handled as 'positions'.
			while ( (v = i++ * spread) <= 100 ) {
				values.push(v);
			}

			mode = 'positions';
		}

		if ( mode === 'positions' ) {

			// Map all percentages to on-range values.
			return values.map(function( value ){
				return scope_Spectrum.fromStepping( stepped ? scope_Spectrum.getStep( value ) : value );
			});
		}

		if ( mode === 'values' ) {

			// If the value must be stepped, it needs to be converted to a percentage first.
			if ( stepped ) {

				return values.map(function( value ){

					// Convert to percentage, apply step, return to value.
					return scope_Spectrum.fromStepping( scope_Spectrum.getStep( scope_Spectrum.toStepping( value ) ) );
				});

			}

			// Otherwise, we can simply use the values.
			return values;
		}
	}

	function generateSpread ( density, mode, group ) {

		function safeIncrement(value, increment) {
			// Avoid floating point variance by dropping the smallest decimal places.
			return (value + increment).toFixed(7) / 1;
		}

		var indexes = {};
		var firstInRange = scope_Spectrum.xVal[0];
		var lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length-1];
		var ignoreFirst = false;
		var ignoreLast = false;
		var prevPct = 0;

		// Create a copy of the group, sort it and filter away all duplicates.
		group = unique(group.slice().sort(function(a, b){ return a - b; }));

		// Make sure the range starts with the first element.
		if ( group[0] !== firstInRange ) {
			group.unshift(firstInRange);
			ignoreFirst = true;
		}

		// Likewise for the last one.
		if ( group[group.length - 1] !== lastInRange ) {
			group.push(lastInRange);
			ignoreLast = true;
		}

		group.forEach(function ( current, index ) {

			// Get the current step and the lower + upper positions.
			var step;
			var i;
			var q;
			var low = current;
			var high = group[index+1];
			var newPct;
			var pctDifference;
			var pctPos;
			var type;
			var steps;
			var realSteps;
			var stepsize;

			// When using 'steps' mode, use the provided steps.
			// Otherwise, we'll step on to the next subrange.
			if ( mode === 'steps' ) {
				step = scope_Spectrum.xNumSteps[ index ];
			}

			// Default to a 'full' step.
			if ( !step ) {
				step = high-low;
			}

			// Low can be 0, so test for false. If high is undefined,
			// we are at the last subrange. Index 0 is already handled.
			if ( low === false || high === undefined ) {
				return;
			}

			// Make sure step isn't 0, which would cause an infinite loop (#654)
			step = Math.max(step, 0.0000001);

			// Find all steps in the subrange.
			for ( i = low; i <= high; i = safeIncrement(i, step) ) {

				// Get the percentage value for the current step,
				// calculate the size for the subrange.
				newPct = scope_Spectrum.toStepping( i );
				pctDifference = newPct - prevPct;

				steps = pctDifference / density;
				realSteps = Math.round(steps);

				// This ratio represents the ammount of percentage-space a point indicates.
				// For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-devided.
				// Round the percentage offset to an even number, then divide by two
				// to spread the offset on both sides of the range.
				stepsize = pctDifference/realSteps;

				// Divide all points evenly, adding the correct number to this subrange.
				// Run up to <= so that 100% gets a point, event if ignoreLast is set.
				for ( q = 1; q <= realSteps; q += 1 ) {

					// The ratio between the rounded value and the actual size might be ~1% off.
					// Correct the percentage offset by the number of points
					// per subrange. density = 1 will result in 100 points on the
					// full range, 2 for 50, 4 for 25, etc.
					pctPos = prevPct + ( q * stepsize );
					indexes[pctPos.toFixed(5)] = ['x', 0];
				}

				// Determine the point type.
				type = (group.indexOf(i) > -1) ? 1 : ( mode === 'steps' ? 2 : 0 );

				// Enforce the 'ignoreFirst' option by overwriting the type for 0.
				if ( !index && ignoreFirst ) {
					type = 0;
				}

				if ( !(i === high && ignoreLast)) {
					// Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value.
					indexes[newPct.toFixed(5)] = [i, type];
				}

				// Update the percentage count.
				prevPct = newPct;
			}
		});

		return indexes;
	}

	function addMarking ( spread, filterFunc, formatter ) {

		var element = document.createElement('div');
		var out = '';
		var valueSizeClasses = [
			options.cssClasses.valueNormal,
			options.cssClasses.valueLarge,
			options.cssClasses.valueSub
		];
		var markerSizeClasses = [
			options.cssClasses.markerNormal,
			options.cssClasses.markerLarge,
			options.cssClasses.markerSub
		];
		var valueOrientationClasses = [
			options.cssClasses.valueHorizontal,
			options.cssClasses.valueVertical
		];
		var markerOrientationClasses = [
			options.cssClasses.markerHorizontal,
			options.cssClasses.markerVertical
		];

		addClass(element, options.cssClasses.pips);
		addClass(element, options.ort === 0 ? options.cssClasses.pipsHorizontal : options.cssClasses.pipsVertical);

		function getClasses( type, source ){
			var a = source === options.cssClasses.value;
			var orientationClasses = a ? valueOrientationClasses : markerOrientationClasses;
			var sizeClasses = a ? valueSizeClasses : markerSizeClasses;

			return source + ' ' + orientationClasses[options.ort] + ' ' + sizeClasses[type];
		}

		function getTags( offset, source, values ) {
			return 'class="' + getClasses(values[1], source) + '" style="' + options.style + ': ' + offset + '%"';
		}

		function addSpread ( offset, values ){

			// Apply the filter function, if it is set.
			values[1] = (values[1] && filterFunc) ? filterFunc(values[0], values[1]) : values[1];

			// Add a marker for every point
			out += '<div ' + getTags(offset, options.cssClasses.marker, values) + '></div>';

			// Values are only appended for points marked '1' or '2'.
			if ( values[1] ) {
				out += '<div ' + getTags(offset, options.cssClasses.value, values) + '>' + formatter.to(values[0]) + '</div>';
			}
		}

		// Append all points.
		Object.keys(spread).forEach(function(a){
			addSpread(a, spread[a]);
		});

		element.innerHTML = out;

		return element;
	}

	function pips ( grid ) {

		var mode = grid.mode;
		var density = grid.density || 1;
		var filter = grid.filter || false;
		var values = grid.values || false;
		var stepped = grid.stepped || false;
		var group = getGroup( mode, values, stepped );
		var spread = generateSpread( density, mode, group );
		var format = grid.format || {
			to: Math.round
		};

		return scope_Target.appendChild(addMarking(
			spread,
			filter,
			format
		));
	}


	// Shorthand for base dimensions.
	function baseSize ( ) {
		var rect = scope_Base.getBoundingClientRect(), alt = 'offset' + ['Width', 'Height'][options.ort];
		return options.ort === 0 ? (rect.width||scope_Base[alt]) : (rect.height||scope_Base[alt]);
	}

	// Handler for attaching events trough a proxy.
	function attachEvent ( events, element, callback, data ) {

		// This function can be used to 'filter' events to the slider.
		// element is a node, not a nodeList

		var method = function ( e ){

			if ( scope_Target.hasAttribute('disabled') ) {
				return false;
			}

			// Stop if an active 'tap' transition is taking place.
			if ( hasClass(scope_Target, options.cssClasses.tap) ) {
				return false;
			}

			e = fixEvent(e, data.pageOffset);

			// Handle reject of multitouch
			if ( !e ) {
				return false;
			}

			// Ignore right or middle clicks on start #454
			if ( events === actions.start && e.buttons !== undefined && e.buttons > 1 ) {
				return false;
			}

			// Ignore right or middle clicks on start #454
			if ( data.hover && e.buttons ) {
				return false;
			}

			e.calcPoint = e.points[ options.ort ];

			// Call the event handler with the event [ and additional data ].
			callback ( e, data );
		};

		var methods = [];

		// Bind a closure on the target for every event type.
		events.split(' ').forEach(function( eventName ){
			element.addEventListener(eventName, method, false);
			methods.push([eventName, method]);
		});

		return methods;
	}

	// Provide a clean event with standardized offset values.
	function fixEvent ( e, pageOffset ) {

		// Prevent scrolling and panning on touch events, while
		// attempting to slide. The tap event also depends on this.
		e.preventDefault();

		// Filter the event to register the type, which can be
		// touch, mouse or pointer. Offset changes need to be
		// made on an event specific basis.
		var touch = e.type.indexOf('touch') === 0;
		var mouse = e.type.indexOf('mouse') === 0;
		var pointer = e.type.indexOf('pointer') === 0;
		var x;
		var y;

		// IE10 implemented pointer events with a prefix;
		if ( e.type.indexOf('MSPointer') === 0 ) {
			pointer = true;
		}

		if ( touch ) {

			// Fix bug when user touches with two or more fingers on mobile devices.
			// It's useful when you have two or more sliders on one page,
			// that can be touched simultaneously.
			// #649, #663, #668
			if ( e.touches.length > 1 ) {
				return false;
			}

			// noUiSlider supports one movement at a time,
			// so we can select the first 'changedTouch'.
			x = e.changedTouches[0].pageX;
			y = e.changedTouches[0].pageY;
		}

		pageOffset = pageOffset || getPageOffset();

		if ( mouse || pointer ) {
			x = e.clientX + pageOffset.x;
			y = e.clientY + pageOffset.y;
		}

		e.pageOffset = pageOffset;
		e.points = [x, y];
		e.cursor = mouse || pointer; // Fix #435

		return e;
	}

	// Translate a coordinate in the document to a percentage on the slider
	function calcPointToPercentage ( calcPoint ) {
		var location = calcPoint - offset(scope_Base, options.ort);
		var proposal = ( location * 100 ) / baseSize();
		return options.dir ? 100 - proposal : proposal;
	}

	// Find handle closest to a certain percentage on the slider
	function getClosestHandle ( proposal ) {

		var closest = 100;
		var handleNumber = false;

		scope_Handles.forEach(function(handle, index){

			// Disabled handles are ignored
			if ( handle.hasAttribute('disabled') ) {
				return;
			}

			var pos = Math.abs(scope_Locations[index] - proposal);

			if ( pos < closest ) {
				handleNumber = index;
				closest = pos;
			}
		});

		return handleNumber;
	}

	// Moves handle(s) by a percentage
	// (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...])
	function moveHandles ( upward, proposal, locations, handleNumbers ) {

		var proposals = locations.slice();

		var b = [!upward, upward];
		var f = [upward, !upward];

		// Copy handleNumbers so we don't change the dataset
		handleNumbers = handleNumbers.slice();

		// Check to see which handle is 'leading'.
		// If that one can't move the second can't either.
		if ( upward ) {
			handleNumbers.reverse();
		}

		// Step 1: get the maximum percentage that any of the handles can move
		if ( handleNumbers.length > 1 ) {

			handleNumbers.forEach(function(handleNumber, o) {

				var to = checkHandlePosition(proposals, handleNumber, proposals[handleNumber] + proposal, b[o], f[o]);

				// Stop if one of the handles can't move.
				if ( to === false ) {
					proposal = 0;
				} else {
					proposal = to - proposals[handleNumber];
					proposals[handleNumber] = to;
				}
			});
		}

		// If using one handle, check backward AND forward
		else {
			b = f = [true];
		}

		var state = false;

		// Step 2: Try to set the handles with the found percentage
		handleNumbers.forEach(function(handleNumber, o) {
			state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state;
		});

		// Step 3: If a handle moved, fire events
		if ( state ) {
			handleNumbers.forEach(function(handleNumber){
				fireEvent('update', handleNumber);
				fireEvent('slide', handleNumber);
			});
		}
	}

	// External event handling
	function fireEvent ( eventName, handleNumber, tap ) {

		Object.keys(scope_Events).forEach(function( targetEvent ) {

			var eventType = targetEvent.split('.')[0];

			if ( eventName === eventType ) {
				scope_Events[targetEvent].forEach(function( callback ) {

					callback.call(
						// Use the slider public API as the scope ('this')
						scope_Self,
						// Return values as array, so arg_1[arg_2] is always valid.
						scope_Values.map(options.format.to),
						// Handle index, 0 or 1
						handleNumber,
						// Unformatted slider values
						scope_Values.slice(),
						// Event is fired by tap, true or false
						tap || false,
						// Left offset of the handle, in relation to the slider
						scope_Locations.slice()
					);
				});
			}
		});
	}


	// Fire 'end' when a mouse or pen leaves the document.
	function documentLeave ( event, data ) {
		if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){
			eventEnd (event, data);
		}
	}

	// Handle movement on document for handle and range drag.
	function eventMove ( event, data ) {

		// Fix #498
		// Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty).
		// https://connect.microsoft.com/IE/feedback/details/927005/mobile-ie10-windows-phone-buttons-property-of-pointermove-event-always-zero
		// IE9 has .buttons and .which zero on mousemove.
		// Firefox breaks the spec MDN defines.
		if ( navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0 ) {
			return eventEnd(event, data);
		}

		// Check if we are moving up or down
		var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint);

		// Convert the movement into a percentage of the slider width/height
		var proposal = (movement * 100) / data.baseSize;

		moveHandles(movement > 0, proposal, data.locations, data.handleNumbers);
	}

	// Unbind move events on document, call callbacks.
	function eventEnd ( event, data ) {

		// The handle is no longer active, so remove the class.
		if ( scope_ActiveHandle ) {
			removeClass(scope_ActiveHandle, options.cssClasses.active);
			scope_ActiveHandle = false;
		}

		// Remove cursor styles and text-selection events bound to the body.
		if ( event.cursor ) {
			document.body.style.cursor = '';
			document.body.removeEventListener('selectstart', document.body.noUiListener);
		}

		// Unbind the move and end events, which are added on 'start'.
		document.documentElement.noUiListeners.forEach(function( c ) {
			document.documentElement.removeEventListener(c[0], c[1]);
		});

		// Remove dragging class.
		removeClass(scope_Target, options.cssClasses.drag);

		setZindex();

		data.handleNumbers.forEach(function(handleNumber){
			fireEvent('set', handleNumber);
			fireEvent('change', handleNumber);
			fireEvent('end', handleNumber);
		});
	}

	// Bind move events on document.
	function eventStart ( event, data ) {

		if ( data.handleNumbers.length === 1 ) {

			var handle = scope_Handles[data.handleNumbers[0]];

			// Ignore 'disabled' handles
			if ( handle.hasAttribute('disabled') ) {
				return false;
			}

			// Mark the handle as 'active' so it can be styled.
			scope_ActiveHandle = handle.children[0];
			addClass(scope_ActiveHandle, options.cssClasses.active);
		}

		// Fix #551, where a handle gets selected instead of dragged.
		event.preventDefault();

		// A drag should never propagate up to the 'tap' event.
		event.stopPropagation();

		// Attach the move and end events.
		var moveEvent = attachEvent(actions.move, document.documentElement, eventMove, {
			startCalcPoint: event.calcPoint,
			baseSize: baseSize(),
			pageOffset: event.pageOffset,
			handleNumbers: data.handleNumbers,
			buttonsProperty: event.buttons,
			locations: scope_Locations.slice()
		});

		var endEvent = attachEvent(actions.end, document.documentElement, eventEnd, {
			handleNumbers: data.handleNumbers
		});

		var outEvent = attachEvent("mouseout", document.documentElement, documentLeave, {
			handleNumbers: data.handleNumbers
		});

		document.documentElement.noUiListeners = moveEvent.concat(endEvent, outEvent);

		// Text selection isn't an issue on touch devices,
		// so adding cursor styles can be skipped.
		if ( event.cursor ) {

			// Prevent the 'I' cursor and extend the range-drag cursor.
			document.body.style.cursor = getComputedStyle(event.target).cursor;

			// Mark the target with a dragging state.
			if ( scope_Handles.length > 1 ) {
				addClass(scope_Target, options.cssClasses.drag);
			}

			var f = function(){
				return false;
			};

			document.body.noUiListener = f;

			// Prevent text selection when dragging the handles.
			document.body.addEventListener('selectstart', f, false);
		}

		data.handleNumbers.forEach(function(handleNumber){
			fireEvent('start', handleNumber);
		});
	}

	// Move closest handle to tapped location.
	function eventTap ( event ) {

		// The tap event shouldn't propagate up
		event.stopPropagation();

		var proposal = calcPointToPercentage(event.calcPoint);
		var handleNumber = getClosestHandle(proposal);

		// Tackle the case that all handles are 'disabled'.
		if ( handleNumber === false ) {
			return false;
		}

		// Flag the slider as it is now in a transitional state.
		// Transition takes a configurable amount of ms (default 300). Re-enable the slider after that.
		if ( !options.events.snap ) {
			addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
		}

		setHandle(handleNumber, proposal, true, true);

		setZindex();

		fireEvent('slide', handleNumber, true);
		fireEvent('set', handleNumber, true);
		fireEvent('change', handleNumber, true);
		fireEvent('update', handleNumber, true);

		if ( options.events.snap ) {
			eventStart(event, { handleNumbers: [handleNumber] });
		}
	}

	// Fires a 'hover' event for a hovered mouse/pen position.
	function eventHover ( event ) {

		var proposal = calcPointToPercentage(event.calcPoint);

		var to = scope_Spectrum.getStep(proposal);
		var value = scope_Spectrum.fromStepping(to);

		Object.keys(scope_Events).forEach(function( targetEvent ) {
			if ( 'hover' === targetEvent.split('.')[0] ) {
				scope_Events[targetEvent].forEach(function( callback ) {
					callback.call( scope_Self, value );
				});
			}
		});
	}

	// Attach events to several slider parts.
	function bindSliderEvents ( behaviour ) {

		// Attach the standard drag event to the handles.
		if ( !behaviour.fixed ) {

			scope_Handles.forEach(function( handle, index ){

				// These events are only bound to the visual handle
				// element, not the 'real' origin element.
				attachEvent ( actions.start, handle.children[0], eventStart, {
					handleNumbers: [index]
				});
			});
		}

		// Attach the tap event to the slider base.
		if ( behaviour.tap ) {
			attachEvent (actions.start, scope_Base, eventTap, {});
		}

		// Fire hover events
		if ( behaviour.hover ) {
			attachEvent (actions.move, scope_Base, eventHover, { hover: true });
		}

		// Make the range draggable.
		if ( behaviour.drag ){

			scope_Connects.forEach(function( connect, index ){

				if ( connect === false || index === 0 || index === scope_Connects.length - 1 ) {
					return;
				}

				var handleBefore = scope_Handles[index - 1];
				var handleAfter = scope_Handles[index];
				var eventHolders = [connect];

				addClass(connect, options.cssClasses.draggable);

				// When the range is fixed, the entire range can
				// be dragged by the handles. The handle in the first
				// origin will propagate the start event upward,
				// but it needs to be bound manually on the other.
				if ( behaviour.fixed ) {
					eventHolders.push(handleBefore.children[0]);
					eventHolders.push(handleAfter.children[0]);
				}

				eventHolders.forEach(function( eventHolder ) {
					attachEvent ( actions.start, eventHolder, eventStart, {
						handles: [handleBefore, handleAfter],
						handleNumbers: [index - 1, index]
					});
				});
			});
		}
	}


	// Split out the handle positioning logic so the Move event can use it, too
	function checkHandlePosition ( reference, handleNumber, to, lookBackward, lookForward ) {

		// For sliders with multiple handles, limit movement to the other handle.
		// Apply the margin option by adding it to the handle positions.
		if ( scope_Handles.length > 1 ) {

			if ( lookBackward && handleNumber > 0 ) {
				to = Math.max(to, reference[handleNumber - 1] + options.margin);
			}

			if ( lookForward && handleNumber < scope_Handles.length - 1 ) {
				to = Math.min(to, reference[handleNumber + 1] - options.margin);
			}
		}

		// The limit option has the opposite effect, limiting handles to a
		// maximum distance from another. Limit must be > 0, as otherwise
		// handles would be unmoveable.
		if ( scope_Handles.length > 1 && options.limit ) {

			if ( lookBackward && handleNumber > 0 ) {
				to = Math.min(to, reference[handleNumber - 1] + options.limit);
			}

			if ( lookForward && handleNumber < scope_Handles.length - 1 ) {
				to = Math.max(to, reference[handleNumber + 1] - options.limit);
			}
		}

		// The padding option keeps the handles a certain distance from the
		// edges of the slider. Padding must be > 0.
		if ( options.padding ) {

			if ( handleNumber === 0 ) {
				to = Math.max(to, options.padding);
			}

			if ( handleNumber === scope_Handles.length - 1 ) {
				to = Math.min(to, 100 - options.padding);
			}
		}

		to = scope_Spectrum.getStep(to);

		// Limit percentage to the 0 - 100 range
		to = limit(to);

		// Return false if handle can't move
		if ( to === reference[handleNumber] ) {
			return false;
		}

		return to;
	}

	function toPct ( pct ) {
		return pct + '%';
	}

	// Updates scope_Locations and scope_Values, updates visual state
	function updateHandlePosition ( handleNumber, to ) {

		// Update locations.
		scope_Locations[handleNumber] = to;

		// Convert the value to the slider stepping/range.
		scope_Values[handleNumber] = scope_Spectrum.fromStepping(to);

		// Called synchronously or on the next animationFrame
		var stateUpdate = function() {
			scope_Handles[handleNumber].style[options.style] = toPct(to);
			updateConnect(handleNumber);
			updateConnect(handleNumber + 1);
		};

		// Set the handle to the new position.
		// Use requestAnimationFrame for efficient painting.
		// No significant effect in Chrome, Edge sees dramatic performace improvements.
		// Option to disable is useful for unit tests, and single-step debugging.
		if ( window.requestAnimationFrame && options.useRequestAnimationFrame ) {
			window.requestAnimationFrame(stateUpdate);
		} else {
			stateUpdate();
		}
	}

	function setZindex ( ) {

		scope_HandleNumbers.forEach(function(handleNumber){
			// Handles before the slider middle are stacked later = higher,
			// Handles after the middle later is lower
			// [[7] [8] .......... | .......... [5] [4]
			var dir = (scope_Locations[handleNumber] > 50 ? -1 : 1);
			var zIndex = 3 + (scope_Handles.length + (dir * handleNumber));
			scope_Handles[handleNumber].childNodes[0].style.zIndex = zIndex;
		});
	}

	// Test suggested values and apply margin, step.
	function setHandle ( handleNumber, to, lookBackward, lookForward ) {

		to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward);

		if ( to === false ) {
			return false;
		}

		updateHandlePosition(handleNumber, to);

		return true;
	}

	// Updates style attribute for connect nodes
	function updateConnect ( index ) {

		// Skip connects set to false
		if ( !scope_Connects[index] ) {
			return;
		}

		var l = 0;
		var h = 100;

		if ( index !== 0 ) {
			l = scope_Locations[index - 1];
		}

		if ( index !== scope_Connects.length - 1 ) {
			h = scope_Locations[index];
		}

		scope_Connects[index].style[options.style] = toPct(l);
		scope_Connects[index].style[options.styleOposite] = toPct(100 - h);
	}

	// ...
	function setValue ( to, handleNumber ) {

		// Setting with null indicates an 'ignore'.
		// Inputting 'false' is invalid.
		if ( to === null || to === false ) {
			return;
		}

		// If a formatted number was passed, attemt to decode it.
		if ( typeof to === 'number' ) {
			to = String(to);
		}

		to = options.format.from(to);

		// Request an update for all links if the value was invalid.
		// Do so too if setting the handle fails.
		if ( to !== false && !isNaN(to) ) {
			setHandle(handleNumber, scope_Spectrum.toStepping(to), false, false);
		}
	}

	// Set the slider value.
	function valueSet ( input, fireSetEvent ) {

		var values = asArray(input);
		var isInit = scope_Locations[0] === undefined;

		// Event fires by default
		fireSetEvent = (fireSetEvent === undefined ? true : !!fireSetEvent);

		values.forEach(setValue);

		// Animation is optional.
		// Make sure the initial values were set before using animated placement.
		if ( options.animate && !isInit ) {
			addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration);
		}

		// Now that all base values are set, apply constraints
		scope_HandleNumbers.forEach(function(handleNumber){
			setHandle(handleNumber, scope_Locations[handleNumber], true, false);
		});

		setZindex();

		scope_HandleNumbers.forEach(function(handleNumber){

			fireEvent('update', handleNumber);

			// Fire the event only for handles that received a new value, as per #579
			if ( values[handleNumber] !== null && fireSetEvent ) {
				fireEvent('set', handleNumber);
			}
		});
	}

	// Reset slider to initial values
	function valueReset ( fireSetEvent ) {
		valueSet(options.start, fireSetEvent);
	}

	// Get the slider value.
	function valueGet ( ) {

		var values = scope_Values.map(options.format.to);

		// If only one handle is used, return a single value.
		if ( values.length === 1 ){
			return values[0];
		}

		return values;
	}

	// Removes classes from the root and empties it.
	function destroy ( ) {

		for ( var key in options.cssClasses ) {
			if ( !options.cssClasses.hasOwnProperty(key) ) { continue; }
			removeClass(scope_Target, options.cssClasses[key]);
		}

		while (scope_Target.firstChild) {
			scope_Target.removeChild(scope_Target.firstChild);
		}

		delete scope_Target.noUiSlider;
	}

	// Get the current step size for the slider.
	function getCurrentStep ( ) {

		// Check all locations, map them to their stepping point.
		// Get the step point, then find it in the input list.
		return scope_Locations.map(function( location, index ){

			var nearbySteps = scope_Spectrum.getNearbySteps( location );
			var value = scope_Values[index];
			var increment = nearbySteps.thisStep.step;
			var decrement = null;

			// If the next value in this step moves into the next step,
			// the increment is the start of the next step - the current value
			if ( increment !== false ) {
				if ( value + increment > nearbySteps.stepAfter.startValue ) {
					increment = nearbySteps.stepAfter.startValue - value;
				}
			}


			// If the value is beyond the starting point
			if ( value > nearbySteps.thisStep.startValue ) {
				decrement = nearbySteps.thisStep.step;
			}

			else if ( nearbySteps.stepBefore.step === false ) {
				decrement = false;
			}

			// If a handle is at the start of a step, it always steps back into the previous step first
			else {
				decrement = value - nearbySteps.stepBefore.highestStep;
			}


			// Now, if at the slider edges, there is not in/decrement
			if ( location === 100 ) {
				increment = null;
			}

			else if ( location === 0 ) {
				decrement = null;
			}

			// As per #391, the comparison for the decrement step can have some rounding issues.
			var stepDecimals = scope_Spectrum.countStepDecimals();

			// Round per #391
			if ( increment !== null && increment !== false ) {
				increment = Number(increment.toFixed(stepDecimals));
			}

			if ( decrement !== null && decrement !== false ) {
				decrement = Number(decrement.toFixed(stepDecimals));
			}

			return [decrement, increment];
		});
	}

	// Attach an event to this slider, possibly including a namespace
	function bindEvent ( namespacedEvent, callback ) {
		scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || [];
		scope_Events[namespacedEvent].push(callback);

		// If the event bound is 'update,' fire it immediately for all handles.
		if ( namespacedEvent.split('.')[0] === 'update' ) {
			scope_Handles.forEach(function(a, index){
				fireEvent('update', index);
			});
		}
	}

	// Undo attachment of event
	function removeEvent ( namespacedEvent ) {

		var event = namespacedEvent && namespacedEvent.split('.')[0];
		var namespace = event && namespacedEvent.substring(event.length);

		Object.keys(scope_Events).forEach(function( bind ){

			var tEvent = bind.split('.')[0],
				tNamespace = bind.substring(tEvent.length);

			if ( (!event || event === tEvent) && (!namespace || namespace === tNamespace) ) {
				delete scope_Events[bind];
			}
		});
	}

	// Updateable: margin, limit, padding, step, range, animate, snap
	function updateOptions ( optionsToUpdate, fireSetEvent ) {

		// Spectrum is created using the range, snap, direction and step options.
		// 'snap' and 'step' can be updated, 'direction' cannot, due to event binding.
		// If 'snap' and 'step' are not passed, they should remain unchanged.
		var v = valueGet();

		var updateAble = ['margin', 'limit', 'padding', 'range', 'animate', 'snap', 'step', 'format'];

		// Only change options that we're actually passed to update.
		updateAble.forEach(function(name){
			if ( optionsToUpdate[name] !== undefined ) {
				originalOptions[name] = optionsToUpdate[name];
			}
		});

		var newOptions = testOptions(originalOptions);

		// Load new options into the slider state
		updateAble.forEach(function(name){
			if ( optionsToUpdate[name] !== undefined ) {
				options[name] = newOptions[name];
			}
		});

		// Save current spectrum direction as testOptions in testRange call
		// doesn't rely on current direction
		newOptions.spectrum.direction = scope_Spectrum.direction;
		scope_Spectrum = newOptions.spectrum;

		// Limit, margin and padding depend on the spectrum but are stored outside of it. (#677)
		options.margin = newOptions.margin;
		options.limit = newOptions.limit;
		options.padding = newOptions.padding;

		// Invalidate the current positioning so valueSet forces an update.
		scope_Locations = [];
		valueSet(optionsToUpdate.start || v, fireSetEvent);
	}

	// Throw an error if the slider was already initialized.
	if ( scope_Target.noUiSlider ) {
		throw new Error("noUiSlider (" + VERSION + "): Slider was already initialized.");
	}

	// Create the base element, initialise HTML and set classes.
	// Add handles and connect elements.
	addSlider(scope_Target);
	addElements(options.connect, scope_Base);

	scope_Self = {
		destroy: destroy,
		steps: getCurrentStep,
		on: bindEvent,
		off: removeEvent,
		get: valueGet,
		set: valueSet,
		reset: valueReset,
		// Exposed for unit testing, don't use this in your application.
		__moveHandles: function(a, b, c) { moveHandles(a, b, scope_Locations, c); },
		options: originalOptions, // Issue #600, #678
		updateOptions: updateOptions,
		target: scope_Target, // Issue #597
		pips: pips // Issue #594
	};

	// Attach user events.
	bindSliderEvents(options.events);

	// Use the public value method to set the start values.
	valueSet(options.start);

	if ( options.pips ) {
		pips(options.pips);
	}

	if ( options.tooltips ) {
		tooltips();
	}

	return scope_Self;

}


	// Run the standard initializer
	function initialize ( target, originalOptions ) {

		if ( !target.nodeName ) {
			throw new Error("noUiSlider (" + VERSION + "): create requires a single element.");
		}

		// Test the options and create the slider environment;
		var options = testOptions( originalOptions, target );
		var api = closure( target, options, originalOptions );

		target.noUiSlider = api;

		return api;
	}

	// Use an object instead of a function for future expansibility;
	return {
		version: VERSION,
		create: initialize
	};

}));
(function (factory) {

    if ( typeof define === 'function' && define.amd ) {

        // AMD. Register as an anonymous module.
        define([], factory);

    } else if ( typeof exports === 'object' ) {

        // Node/CommonJS
        module.exports = factory();

    } else {

        // Browser globals
        window.wNumb = factory();
    }

}(function(){

	'use strict';

var FormatOptions = [
	'decimals',
	'thousand',
	'mark',
	'prefix',
	'suffix',
	'encoder',
	'decoder',
	'negativeBefore',
	'negative',
	'edit',
	'undo'
];

// General

	// Reverse a string
	function strReverse ( a ) {
		return a.split('').reverse().join('');
	}

	// Check if a string starts with a specified prefix.
	function strStartsWith ( input, match ) {
		return input.substring(0, match.length) === match;
	}

	// Check is a string ends in a specified suffix.
	function strEndsWith ( input, match ) {
		return input.slice(-1 * match.length) === match;
	}

	// Throw an error if formatting options are incompatible.
	function throwEqualError( F, a, b ) {
		if ( (F[a] || F[b]) && (F[a] === F[b]) ) {
			throw new Error(a);
		}
	}

	// Check if a number is finite and not NaN
	function isValidNumber ( input ) {
		return typeof input === 'number' && isFinite( input );
	}

	// Provide rounding-accurate toFixed method.
	// Borrowed: http://stackoverflow.com/a/21323330/775265
	function toFixed ( value, exp ) {
		value = value.toString().split('e');
		value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));
		value = value.toString().split('e');
		return (+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))).toFixed(exp);
	}


// Formatting

	// Accept a number as input, output formatted string.
	function formatTo ( decimals, thousand, mark, prefix, suffix, encoder, decoder, negativeBefore, negative, edit, undo, input ) {

		var originalInput = input, inputIsNegative, inputPieces, inputBase, inputDecimals = '', output = '';

		// Apply user encoder to the input.
		// Expected outcome: number.
		if ( encoder ) {
			input = encoder(input);
		}

		// Stop if no valid number was provided, the number is infinite or NaN.
		if ( !isValidNumber(input) ) {
			return false;
		}

		// Rounding away decimals might cause a value of -0
		// when using very small ranges. Remove those cases.
		if ( decimals !== false && parseFloat(input.toFixed(decimals)) === 0 ) {
			input = 0;
		}

		// Formatting is done on absolute numbers,
		// decorated by an optional negative symbol.
		if ( input < 0 ) {
			inputIsNegative = true;
			input = Math.abs(input);
		}

		// Reduce the number of decimals to the specified option.
		if ( decimals !== false ) {
			input = toFixed( input, decimals );
		}

		// Transform the number into a string, so it can be split.
		input = input.toString();

		// Break the number on the decimal separator.
		if ( input.indexOf('.') !== -1 ) {
			inputPieces = input.split('.');

			inputBase = inputPieces[0];

			if ( mark ) {
				inputDecimals = mark + inputPieces[1];
			}

		} else {

		// If it isn't split, the entire number will do.
			inputBase = input;
		}

		// Group numbers in sets of three.
		if ( thousand ) {
			inputBase = strReverse(inputBase).match(/.{1,3}/g);
			inputBase = strReverse(inputBase.join( strReverse( thousand ) ));
		}

		// If the number is negative, prefix with negation symbol.
		if ( inputIsNegative && negativeBefore ) {
			output += negativeBefore;
		}

		// Prefix the number
		if ( prefix ) {
			output += prefix;
		}

		// Normal negative option comes after the prefix. Defaults to '-'.
		if ( inputIsNegative && negative ) {
			output += negative;
		}

		// Append the actual number.
		output += inputBase;
		output += inputDecimals;

		// Apply the suffix.
		if ( suffix ) {
			output += suffix;
		}

		// Run the output through a user-specified post-formatter.
		if ( edit ) {
			output = edit ( output, originalInput );
		}

		// All done.
		return output;
	}

	// Accept a sting as input, output decoded number.
	function formatFrom ( decimals, thousand, mark, prefix, suffix, encoder, decoder, negativeBefore, negative, edit, undo, input ) {

		var originalInput = input, inputIsNegative, output = '';

		// User defined pre-decoder. Result must be a non empty string.
		if ( undo ) {
			input = undo(input);
		}

		// Test the input. Can't be empty.
		if ( !input || typeof input !== 'string' ) {
			return false;
		}

		// If the string starts with the negativeBefore value: remove it.
		// Remember is was there, the number is negative.
		if ( negativeBefore && strStartsWith(input, negativeBefore) ) {
			input = input.replace(negativeBefore, '');
			inputIsNegative = true;
		}

		// Repeat the same procedure for the prefix.
		if ( prefix && strStartsWith(input, prefix) ) {
			input = input.replace(prefix, '');
		}

		// And again for negative.
		if ( negative && strStartsWith(input, negative) ) {
			input = input.replace(negative, '');
			inputIsNegative = true;
		}

		// Remove the suffix.
		// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice
		if ( suffix && strEndsWith(input, suffix) ) {
			input = input.slice(0, -1 * suffix.length);
		}

		// Remove the thousand grouping.
		if ( thousand ) {
			input = input.split(thousand).join('');
		}

		// Set the decimal separator back to period.
		if ( mark ) {
			input = input.replace(mark, '.');
		}

		// Prepend the negative symbol.
		if ( inputIsNegative ) {
			output += '-';
		}

		// Add the number
		output += input;

		// Trim all non-numeric characters (allow '.' and '-');
		output = output.replace(/[^0-9\.\-.]/g, '');

		// The value contains no parse-able number.
		if ( output === '' ) {
			return false;
		}

		// Covert to number.
		output = Number(output);

		// Run the user-specified post-decoder.
		if ( decoder ) {
			output = decoder(output);
		}

		// Check is the output is valid, otherwise: return false.
		if ( !isValidNumber(output) ) {
			return false;
		}

		return output;
	}


// Framework

	// Validate formatting options
	function validate ( inputOptions ) {

		var i, optionName, optionValue,
			filteredOptions = {};

		if ( inputOptions['suffix'] === undefined ) {
			inputOptions['suffix'] = inputOptions['postfix'];
		}

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

			optionName = FormatOptions[i];
			optionValue = inputOptions[optionName];

			if ( optionValue === undefined ) {

				// Only default if negativeBefore isn't set.
				if ( optionName === 'negative' && !filteredOptions.negativeBefore ) {
					filteredOptions[optionName] = '-';
				// Don't set a default for mark when 'thousand' is set.
				} else if ( optionName === 'mark' && filteredOptions.thousand !== '.' ) {
					filteredOptions[optionName] = '.';
				} else {
					filteredOptions[optionName] = false;
				}

			// Floating points in JS are stable up to 7 decimals.
			} else if ( optionName === 'decimals' ) {
				if ( optionValue >= 0 && optionValue < 8 ) {
					filteredOptions[optionName] = optionValue;
				} else {
					throw new Error(optionName);
				}

			// These options, when provided, must be functions.
			} else if ( optionName === 'encoder' || optionName === 'decoder' || optionName === 'edit' || optionName === 'undo' ) {
				if ( typeof optionValue === 'function' ) {
					filteredOptions[optionName] = optionValue;
				} else {
					throw new Error(optionName);
				}

			// Other options are strings.
			} else {

				if ( typeof optionValue === 'string' ) {
					filteredOptions[optionName] = optionValue;
				} else {
					throw new Error(optionName);
				}
			}
		}

		// Some values can't be extracted from a
		// string if certain combinations are present.
		throwEqualError(filteredOptions, 'mark', 'thousand');
		throwEqualError(filteredOptions, 'prefix', 'negative');
		throwEqualError(filteredOptions, 'prefix', 'negativeBefore');

		return filteredOptions;
	}

	// Pass all options as function arguments
	function passAll ( options, method, input ) {
		var i, args = [];

		// Add all options in order of FormatOptions
		for ( i = 0; i < FormatOptions.length; i+=1 ) {
			args.push(options[FormatOptions[i]]);
		}

		// Append the input, then call the method, presenting all
		// options as arguments.
		args.push(input);
		return method.apply('', args);
	}

	function wNumb ( options ) {

		if ( !(this instanceof wNumb) ) {
			return new wNumb ( options );
		}

		if ( typeof options !== "object" ) {
			return;
		}

		options = validate(options);

		// Call 'formatTo' with proper arguments.
		this.to = function ( input ) {
			return passAll(options, formatTo, input);
		};

		// Call 'formatFrom' with proper arguments.
		this.from = function ( input ) {
			return passAll(options, formatFrom, input);
		};
	}

	return wNumb;

}));

// OpenLayers 3. See http://openlayers.org/
// License: https://raw.githubusercontent.com/openlayers/ol3/master/LICENSE.md
// Version: v3.18.2
;(function (root, factory) {
  if (typeof exports === "object") {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define([], factory);
  } else {
    root.ol = factory();
  }
}(this, function () {
  var OPENLAYERS = {};
  var goog = this.goog = {};
this.CLOSURE_NO_DEPS = true;
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Bootstrap for the Google JS Library (Closure).
 *
 * In uncompiled mode base.js will write out Closure's deps file, unless the
 * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
 * include their own deps file(s) from different locations.
 *
 * @author arv@google.com (Erik Arvidsson)
 *
 * @provideGoog
 */


/**
 * @define {boolean} Overridden to true by the compiler when
 *     --process_closure_primitives is specified.
 */
var COMPILED = false;


/**
 * Base namespace for the Closure library.  Checks to see goog is already
 * defined in the current scope before assigning to prevent clobbering if
 * base.js is loaded more than once.
 *
 * @const
 */
var goog = goog || {};


/**
 * Reference to the global context.  In most cases this will be 'window'.
 */
goog.global = this;


/**
 * A hook for overriding the define values in uncompiled mode.
 *
 * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before
 * loading base.js.  If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES},
 * {@code goog.define} will use the value instead of the default value.  This
 * allows flags to be overwritten without compilation (this is normally
 * accomplished with the compiler's "define" flag).
 *
 * Example:
 * <pre>
 *   var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
 * </pre>
 *
 * @type {Object<string, (string|number|boolean)>|undefined}
 */
goog.global.CLOSURE_UNCOMPILED_DEFINES;


/**
 * A hook for overriding the define values in uncompiled or compiled mode,
 * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code.  In
 * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
 *
 * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
 * string literals or the compiler will emit an error.
 *
 * While any @define value may be set, only those set with goog.define will be
 * effective for uncompiled code.
 *
 * Example:
 * <pre>
 *   var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
 * </pre>
 *
 * @type {Object<string, (string|number|boolean)>|undefined}
 */
goog.global.CLOSURE_DEFINES;


/**
 * Returns true if the specified value is not undefined.
 * WARNING: Do not use this to test if an object has a property. Use the in
 * operator instead.
 *
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is defined.
 */
goog.isDef = function(val) {
  // void 0 always evaluates to undefined and hence we do not need to depend on
  // the definition of the global variable named 'undefined'.
  return val !== void 0;
};


/**
 * Builds an object structure for the provided namespace path, ensuring that
 * names that already exist are not overwritten. For example:
 * "a.b.c" -> a = {};a.b={};a.b.c={};
 * Used by goog.provide and goog.exportSymbol.
 * @param {string} name name of the object that this file defines.
 * @param {*=} opt_object the object to expose at the end of the path.
 * @param {Object=} opt_objectToExportTo The object to add the path to; default
 *     is |goog.global|.
 * @private
 */
goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
  var parts = name.split('.');
  var cur = opt_objectToExportTo || goog.global;

  // Internet Explorer exhibits strange behavior when throwing errors from
  // methods externed in this manner.  See the testExportSymbolExceptions in
  // base_test.html for an example.
  if (!(parts[0] in cur) && cur.execScript) {
    cur.execScript('var ' + parts[0]);
  }

  // Certain browsers cannot parse code in the form for((a in b); c;);
  // This pattern is produced by the JSCompiler when it collapses the
  // statement above into the conditional loop below. To prevent this from
  // happening, use a for-loop and reserve the init logic as below.

  // Parentheses added to eliminate strict JS warning in Firefox.
  for (var part; parts.length && (part = parts.shift());) {
    if (!parts.length && goog.isDef(opt_object)) {
      // last part and we have an object; use it
      cur[part] = opt_object;
    } else if (cur[part]) {
      cur = cur[part];
    } else {
      cur = cur[part] = {};
    }
  }
};


/**
 * Defines a named value. In uncompiled mode, the value is retrieved from
 * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
 * has the property specified, and otherwise used the defined defaultValue.
 * When compiled the default can be overridden using the compiler
 * options or the value set in the CLOSURE_DEFINES object.
 *
 * @param {string} name The distinguished name to provide.
 * @param {string|number|boolean} defaultValue
 */
goog.define = function(name, defaultValue) {
  var value = defaultValue;
  if (!COMPILED) {
    if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
        Object.prototype.hasOwnProperty.call(
            goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
      value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
    } else if (
        goog.global.CLOSURE_DEFINES &&
        Object.prototype.hasOwnProperty.call(
            goog.global.CLOSURE_DEFINES, name)) {
      value = goog.global.CLOSURE_DEFINES[name];
    }
  }
  goog.exportPath_(name, value);
};


/**
 * @define {boolean} DEBUG is provided as a convenience so that debugging code
 * that should not be included in a production js_binary can be easily stripped
 * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
 * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
 * because they are generally used for debugging purposes and it is difficult
 * for the JSCompiler to statically determine whether they are used.
 */
goog.define('goog.DEBUG', true);


/**
 * @define {string} LOCALE defines the locale being used for compilation. It is
 * used to select locale specific data to be compiled in js binary. BUILD rule
 * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
 * option.
 *
 * Take into account that the locale code format is important. You should use
 * the canonical Unicode format with hyphen as a delimiter. Language must be
 * lowercase, Language Script - Capitalized, Region - UPPERCASE.
 * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
 *
 * See more info about locale codes here:
 * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
 *
 * For language codes you should use values defined by ISO 693-1. See it here
 * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
 * this rule: the Hebrew language. For legacy reasons the old code (iw) should
 * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
 */
goog.define('goog.LOCALE', 'en');  // default to en


/**
 * @define {boolean} Whether this code is running on trusted sites.
 *
 * On untrusted sites, several native functions can be defined or overridden by
 * external libraries like Prototype, Datejs, and JQuery and setting this flag
 * to false forces closure to use its own implementations when possible.
 *
 * If your JavaScript can be loaded by a third party site and you are wary about
 * relying on non-standard implementations, specify
 * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
 */
goog.define('goog.TRUSTED_SITE', true);


/**
 * @define {boolean} Whether a project is expected to be running in strict mode.
 *
 * This define can be used to trigger alternate implementations compatible with
 * running in EcmaScript Strict mode or warn about unavailable functionality.
 * @see https://goo.gl/PudQ4y
 *
 */
goog.define('goog.STRICT_MODE_COMPATIBLE', false);


/**
 * @define {boolean} Whether code that calls {@link goog.setTestOnly} should
 *     be disallowed in the compilation unit.
 */
goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG);


/**
 * @define {boolean} Whether to use a Chrome app CSP-compliant method for
 *     loading scripts via goog.require. @see appendScriptSrcNode_.
 */
goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);


/**
 * Defines a namespace in Closure.
 *
 * A namespace may only be defined once in a codebase. It may be defined using
 * goog.provide() or goog.module().
 *
 * The presence of one or more goog.provide() calls in a file indicates
 * that the file defines the given objects/namespaces.
 * Provided symbols must not be null or undefined.
 *
 * In addition, goog.provide() creates the object stubs for a namespace
 * (for example, goog.provide("goog.foo.bar") will create the object
 * goog.foo.bar if it does not already exist).
 *
 * Build tools also scan for provide/require/module statements
 * to discern dependencies, build dependency files (see deps.js), etc.
 *
 * @see goog.require
 * @see goog.module
 * @param {string} name Namespace provided by this file in the form
 *     "goog.package.part".
 */
goog.provide = function(name) {
  if (goog.isInModuleLoader_()) {
    throw Error('goog.provide can not be used within a goog.module.');
  }
  if (!COMPILED) {
    // Ensure that the same namespace isn't provided twice.
    // A goog.module/goog.provide maps a goog.require to a specific file
    if (goog.isProvided_(name)) {
      throw Error('Namespace "' + name + '" already declared.');
    }
  }

  goog.constructNamespace_(name);
};


/**
 * @param {string} name Namespace provided by this file in the form
 *     "goog.package.part".
 * @param {Object=} opt_obj The object to embed in the namespace.
 * @private
 */
goog.constructNamespace_ = function(name, opt_obj) {
  if (!COMPILED) {
    delete goog.implicitNamespaces_[name];

    var namespace = name;
    while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
      if (goog.getObjectByName(namespace)) {
        break;
      }
      goog.implicitNamespaces_[namespace] = true;
    }
  }

  goog.exportPath_(name, opt_obj);
};


/**
 * Module identifier validation regexp.
 * Note: This is a conservative check, it is very possible to be more lenient,
 *   the primary exclusion here is "/" and "\" and a leading ".", these
 *   restrictions are intended to leave the door open for using goog.require
 *   with relative file paths rather than module identifiers.
 * @private
 */
goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;


/**
 * Defines a module in Closure.
 *
 * Marks that this file must be loaded as a module and claims the namespace.
 *
 * A namespace may only be defined once in a codebase. It may be defined using
 * goog.provide() or goog.module().
 *
 * goog.module() has three requirements:
 * - goog.module may not be used in the same file as goog.provide.
 * - goog.module must be the first statement in the file.
 * - only one goog.module is allowed per file.
 *
 * When a goog.module annotated file is loaded, it is enclosed in
 * a strict function closure. This means that:
 * - any variables declared in a goog.module file are private to the file
 * (not global), though the compiler is expected to inline the module.
 * - The code must obey all the rules of "strict" JavaScript.
 * - the file will be marked as "use strict"
 *
 * NOTE: unlike goog.provide, goog.module does not declare any symbols by
 * itself. If declared symbols are desired, use
 * goog.module.declareLegacyNamespace().
 *
 *
 * See the public goog.module proposal: http://goo.gl/Va1hin
 *
 * @param {string} name Namespace provided by this file in the form
 *     "goog.package.part", is expected but not required.
 */
goog.module = function(name) {
  if (!goog.isString(name) || !name ||
      name.search(goog.VALID_MODULE_RE_) == -1) {
    throw Error('Invalid module identifier');
  }
  if (!goog.isInModuleLoader_()) {
    throw Error('Module ' + name + ' has been loaded incorrectly.');
  }
  if (goog.moduleLoaderState_.moduleName) {
    throw Error('goog.module may only be called once per module.');
  }

  // Store the module name for the loader.
  goog.moduleLoaderState_.moduleName = name;
  if (!COMPILED) {
    // Ensure that the same namespace isn't provided twice.
    // A goog.module/goog.provide maps a goog.require to a specific file
    if (goog.isProvided_(name)) {
      throw Error('Namespace "' + name + '" already declared.');
    }
    delete goog.implicitNamespaces_[name];
  }
};


/**
 * @param {string} name The module identifier.
 * @return {?} The module exports for an already loaded module or null.
 *
 * Note: This is not an alternative to goog.require, it does not
 * indicate a hard dependency, instead it is used to indicate
 * an optional dependency or to access the exports of a module
 * that has already been loaded.
 * @suppress {missingProvide}
 */
goog.module.get = function(name) {
  return goog.module.getInternal_(name);
};


/**
 * @param {string} name The module identifier.
 * @return {?} The module exports for an already loaded module or null.
 * @private
 */
goog.module.getInternal_ = function(name) {
  if (!COMPILED) {
    if (goog.isProvided_(name)) {
      // goog.require only return a value with-in goog.module files.
      return name in goog.loadedModules_ ? goog.loadedModules_[name] :
                                           goog.getObjectByName(name);
    } else {
      return null;
    }
  }
};


/**
 * @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}}
 */
goog.moduleLoaderState_ = null;


/**
 * @private
 * @return {boolean} Whether a goog.module is currently being initialized.
 */
goog.isInModuleLoader_ = function() {
  return goog.moduleLoaderState_ != null;
};


/**
 * Provide the module's exports as a globally accessible object under the
 * module's declared name.  This is intended to ease migration to goog.module
 * for files that have existing usages.
 * @suppress {missingProvide}
 */
goog.module.declareLegacyNamespace = function() {
  if (!COMPILED && !goog.isInModuleLoader_()) {
    throw new Error(
        'goog.module.declareLegacyNamespace must be called from ' +
        'within a goog.module');
  }
  if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
    throw Error(
        'goog.module must be called prior to ' +
        'goog.module.declareLegacyNamespace.');
  }
  goog.moduleLoaderState_.declareLegacyNamespace = true;
};


/**
 * Marks that the current file should only be used for testing, and never for
 * live code in production.
 *
 * In the case of unit tests, the message may optionally be an exact namespace
 * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
 * provide (if not explicitly defined in the code).
 *
 * @param {string=} opt_message Optional message to add to the error that's
 *     raised when used in production code.
 */
goog.setTestOnly = function(opt_message) {
  if (goog.DISALLOW_TEST_ONLY_CODE) {
    opt_message = opt_message || '';
    throw Error(
        'Importing test-only code into non-debug environment' +
        (opt_message ? ': ' + opt_message : '.'));
  }
};


/**
 * Forward declares a symbol. This is an indication to the compiler that the
 * symbol may be used in the source yet is not required and may not be provided
 * in compilation.
 *
 * The most common usage of forward declaration is code that takes a type as a
 * function parameter but does not need to require it. By forward declaring
 * instead of requiring, no hard dependency is made, and (if not required
 * elsewhere) the namespace may never be required and thus, not be pulled
 * into the JavaScript binary. If it is required elsewhere, it will be type
 * checked as normal.
 *
 *
 * @param {string} name The namespace to forward declare in the form of
 *     "goog.package.part".
 */
goog.forwardDeclare = function(name) {};


/**
 * Forward declare type information. Used to assign types to goog.global
 * referenced object that would otherwise result in unknown type references
 * and thus block property disambiguation.
 */
goog.forwardDeclare('Document');
goog.forwardDeclare('HTMLScriptElement');
goog.forwardDeclare('XMLHttpRequest');


if (!COMPILED) {
  /**
   * Check if the given name has been goog.provided. This will return false for
   * names that are available only as implicit namespaces.
   * @param {string} name name of the object to look for.
   * @return {boolean} Whether the name has been provided.
   * @private
   */
  goog.isProvided_ = function(name) {
    return (name in goog.loadedModules_) ||
        (!goog.implicitNamespaces_[name] &&
         goog.isDefAndNotNull(goog.getObjectByName(name)));
  };

  /**
   * Namespaces implicitly defined by goog.provide. For example,
   * goog.provide('goog.events.Event') implicitly declares that 'goog' and
   * 'goog.events' must be namespaces.
   *
   * @type {!Object<string, (boolean|undefined)>}
   * @private
   */
  goog.implicitNamespaces_ = {'goog.module': true};

  // NOTE: We add goog.module as an implicit namespace as goog.module is defined
  // here and because the existing module package has not been moved yet out of
  // the goog.module namespace. This satisifies both the debug loader and
  // ahead-of-time dependency management.
}


/**
 * Returns an object based on its fully qualified external name.  The object
 * is not found if null or undefined.  If you are using a compilation pass that
 * renames property names beware that using this function will not find renamed
 * properties.
 *
 * @param {string} name The fully qualified name.
 * @param {Object=} opt_obj The object within which to look; default is
 *     |goog.global|.
 * @return {?} The value (object or primitive) or, if not found, null.
 */
goog.getObjectByName = function(name, opt_obj) {
  var parts = name.split('.');
  var cur = opt_obj || goog.global;
  for (var part; part = parts.shift();) {
    if (goog.isDefAndNotNull(cur[part])) {
      cur = cur[part];
    } else {
      return null;
    }
  }
  return cur;
};


/**
 * Globalizes a whole namespace, such as goog or goog.lang.
 *
 * @param {!Object} obj The namespace to globalize.
 * @param {Object=} opt_global The object to add the properties to.
 * @deprecated Properties may be explicitly exported to the global scope, but
 *     this should no longer be done in bulk.
 */
goog.globalize = function(obj, opt_global) {
  var global = opt_global || goog.global;
  for (var x in obj) {
    global[x] = obj[x];
  }
};


/**
 * Adds a dependency from a file to the files it requires.
 * @param {string} relPath The path to the js file.
 * @param {!Array<string>} provides An array of strings with
 *     the names of the objects this file provides.
 * @param {!Array<string>} requires An array of strings with
 *     the names of the objects this file requires.
 * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating
 *     how the file must be loaded.  The boolean 'true' is equivalent
 *     to {'module': 'goog'} for backwards-compatibility.  Valid properties
 *     and values include {'module': 'goog'} and {'lang': 'es6'}.
 */
goog.addDependency = function(relPath, provides, requires, opt_loadFlags) {
  if (goog.DEPENDENCIES_ENABLED) {
    var provide, require;
    var path = relPath.replace(/\\/g, '/');
    var deps = goog.dependencies_;
    if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') {
      opt_loadFlags = opt_loadFlags ? {'module': 'goog'} : {};
    }
    for (var i = 0; provide = provides[i]; i++) {
      deps.nameToPath[provide] = path;
      deps.loadFlags[path] = opt_loadFlags;
    }
    for (var j = 0; require = requires[j]; j++) {
      if (!(path in deps.requires)) {
        deps.requires[path] = {};
      }
      deps.requires[path][require] = true;
    }
  }
};




// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
// to do "debug-mode" development.  The dependency system can sometimes be
// confusing, as can the debug DOM loader's asynchronous nature.
//
// With the DOM loader, a call to goog.require() is not blocking -- the script
// will not load until some point after the current script.  If a namespace is
// needed at runtime, it needs to be defined in a previous script, or loaded via
// require() with its registered dependencies.
//
// User-defined namespaces may need their own deps file. For a reference on
// creating a deps file, see:
// Externally: https://developers.google.com/closure/library/docs/depswriter
//
// Because of legacy clients, the DOM loader can't be easily removed from
// base.js.  Work is being done to make it disableable or replaceable for
// different environments (DOM-less JavaScript interpreters like Rhino or V8,
// for example). See bootstrap/ for more information.


/**
 * @define {boolean} Whether to enable the debug loader.
 *
 * If enabled, a call to goog.require() will attempt to load the namespace by
 * appending a script tag to the DOM (if the namespace has been registered).
 *
 * If disabled, goog.require() will simply assert that the namespace has been
 * provided (and depend on the fact that some outside tool correctly ordered
 * the script).
 */
goog.define('goog.ENABLE_DEBUG_LOADER', true);


/**
 * @param {string} msg
 * @private
 */
goog.logToConsole_ = function(msg) {
  if (goog.global.console) {
    goog.global.console['error'](msg);
  }
};


/**
 * Implements a system for the dynamic resolution of dependencies that works in
 * parallel with the BUILD system. Note that all calls to goog.require will be
 * stripped by the JSCompiler when the --process_closure_primitives option is
 * used.
 * @see goog.provide
 * @param {string} name Namespace to include (as was given in goog.provide()) in
 *     the form "goog.package.part".
 * @return {?} If called within a goog.module file, the associated namespace or
 *     module otherwise null.
 */
goog.require = function(name) {
  // If the object already exists we do not need do do anything.
  if (!COMPILED) {
    if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
      goog.maybeProcessDeferredDep_(name);
    }

    if (goog.isProvided_(name)) {
      if (goog.isInModuleLoader_()) {
        return goog.module.getInternal_(name);
      } else {
        return null;
      }
    }

    if (goog.ENABLE_DEBUG_LOADER) {
      var path = goog.getPathFromDeps_(name);
      if (path) {
        goog.writeScripts_(path);
        return null;
      }
    }

    var errorMessage = 'goog.require could not find: ' + name;
    goog.logToConsole_(errorMessage);

    throw Error(errorMessage);
  }
};


/**
 * Path for included scripts.
 * @type {string}
 */
goog.basePath = '';


/**
 * A hook for overriding the base path.
 * @type {string|undefined}
 */
goog.global.CLOSURE_BASE_PATH;


/**
 * Whether to write out Closure's deps file. By default, the deps are written.
 * @type {boolean|undefined}
 */
goog.global.CLOSURE_NO_DEPS;


/**
 * A function to import a single script. This is meant to be overridden when
 * Closure is being run in non-HTML contexts, such as web workers. It's defined
 * in the global scope so that it can be set before base.js is loaded, which
 * allows deps.js to be imported properly.
 *
 * The function is passed the script source, which is a relative URI. It should
 * return true if the script was imported, false otherwise.
 * @type {(function(string): boolean)|undefined}
 */
goog.global.CLOSURE_IMPORT_SCRIPT;


/**
 * Null function used for default values of callbacks, etc.
 * @return {void} Nothing.
 */
goog.nullFunction = function() {};


/**
 * When defining a class Foo with an abstract method bar(), you can do:
 * Foo.prototype.bar = goog.abstractMethod
 *
 * Now if a subclass of Foo fails to override bar(), an error will be thrown
 * when bar() is invoked.
 *
 * Note: This does not take the name of the function to override as an argument
 * because that would make it more difficult to obfuscate our JavaScript code.
 *
 * @type {!Function}
 * @throws {Error} when invoked to indicate the method should be overridden.
 */
goog.abstractMethod = function() {
  throw Error('unimplemented abstract method');
};


/**
 * Adds a {@code getInstance} static method that always returns the same
 * instance object.
 * @param {!Function} ctor The constructor for the class to add the static
 *     method to.
 */
goog.addSingletonGetter = function(ctor) {
  ctor.getInstance = function() {
    if (ctor.instance_) {
      return ctor.instance_;
    }
    if (goog.DEBUG) {
      // NOTE: JSCompiler can't optimize away Array#push.
      goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
    }
    return ctor.instance_ = new ctor;
  };
};


/**
 * All singleton classes that have been instantiated, for testing. Don't read
 * it directly, use the {@code goog.testing.singleton} module. The compiler
 * removes this variable if unused.
 * @type {!Array<!Function>}
 * @private
 */
goog.instantiatedSingletons_ = [];


/**
 * @define {boolean} Whether to load goog.modules using {@code eval} when using
 * the debug loader.  This provides a better debugging experience as the
 * source is unmodified and can be edited using Chrome Workspaces or similar.
 * However in some environments the use of {@code eval} is banned
 * so we provide an alternative.
 */
goog.define('goog.LOAD_MODULE_USING_EVAL', true);


/**
 * @define {boolean} Whether the exports of goog.modules should be sealed when
 * possible.
 */
goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG);


/**
 * The registry of initialized modules:
 * the module identifier to module exports map.
 * @private @const {!Object<string, ?>}
 */
goog.loadedModules_ = {};


/**
 * True if goog.dependencies_ is available.
 * @const {boolean}
 */
goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;


/**
 * @define {string} How to decide whether to transpile.  Valid values
 * are 'always', 'never', and 'detect'.  The default ('detect') is to
 * use feature detection to determine which language levels need
 * transpilation.
 */
// NOTE(user): we could expand this to accept a language level to bypass
// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but
// would leave ES3 and ES5 files alone.
goog.define('goog.TRANSPILE', 'detect');


/**
 * @define {string} Path to the transpiler.  Executing the script at this
 * path (relative to base.js) should define a function $jscomp.transpile.
 */
goog.define('goog.TRANSPILER', 'transpile.js');


if (goog.DEPENDENCIES_ENABLED) {
  /**
   * This object is used to keep track of dependencies and other data that is
   * used for loading scripts.
   * @private
   * @type {{
   *   loadFlags: !Object<string, !Object<string, string>>,
   *   nameToPath: !Object<string, string>,
   *   requires: !Object<string, !Object<string, boolean>>,
   *   visited: !Object<string, boolean>,
   *   written: !Object<string, boolean>,
   *   deferred: !Object<string, string>
   * }}
   */
  goog.dependencies_ = {
    loadFlags: {},  // 1 to 1

    nameToPath: {},  // 1 to 1

    requires: {},  // 1 to many

    // Used when resolving dependencies to prevent us from visiting file twice.
    visited: {},

    written: {},  // Used to keep track of script files we have written.

    deferred: {}  // Used to track deferred module evaluations in old IEs
  };


  /**
   * Tries to detect whether is in the context of an HTML document.
   * @return {boolean} True if it looks like HTML document.
   * @private
   */
  goog.inHtmlDocument_ = function() {
    /** @type {Document} */
    var doc = goog.global.document;
    return doc != null && 'write' in doc;  // XULDocument misses write.
  };


  /**
   * Tries to detect the base path of base.js script that bootstraps Closure.
   * @private
   */
  goog.findBasePath_ = function() {
    if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) {
      goog.basePath = goog.global.CLOSURE_BASE_PATH;
      return;
    } else if (!goog.inHtmlDocument_()) {
      return;
    }
    /** @type {Document} */
    var doc = goog.global.document;
    var scripts = doc.getElementsByTagName('SCRIPT');
    // Search backwards since the current script is in almost all cases the one
    // that has base.js.
    for (var i = scripts.length - 1; i >= 0; --i) {
      var script = /** @type {!HTMLScriptElement} */ (scripts[i]);
      var src = script.src;
      var qmark = src.lastIndexOf('?');
      var l = qmark == -1 ? src.length : qmark;
      if (src.substr(l - 7, 7) == 'base.js') {
        goog.basePath = src.substr(0, l - 7);
        return;
      }
    }
  };


  /**
   * Imports a script if, and only if, that script hasn't already been imported.
   * (Must be called at execution time)
   * @param {string} src Script source.
   * @param {string=} opt_sourceText The optionally source text to evaluate
   * @private
   */
  goog.importScript_ = function(src, opt_sourceText) {
    var importScript =
        goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_;
    if (importScript(src, opt_sourceText)) {
      goog.dependencies_.written[src] = true;
    }
  };


  /**
   * Whether the browser is IE9 or earlier, which needs special handling
   * for deferred modules.
   * @const @private {boolean}
   */
  goog.IS_OLD_IE_ =
      !!(!goog.global.atob && goog.global.document && goog.global.document.all);


  /**
   * Given a URL initiate retrieval and execution of a script that needs
   * pre-processing.
   * @param {string} src Script source URL.
   * @param {boolean} isModule Whether this is a goog.module.
   * @param {boolean} needsTranspile Whether this source needs transpilation.
   * @private
   */
  goog.importProcessedScript_ = function(src, isModule, needsTranspile) {
    // In an attempt to keep browsers from timing out loading scripts using
    // synchronous XHRs, put each load in its own script block.
    var bootstrap = 'goog.retrieveAndExec_("' + src + '", ' + isModule + ', ' +
        needsTranspile + ');';

    goog.importScript_('', bootstrap);
  };


  /** @private {!Array<string>} */
  goog.queuedModules_ = [];


  /**
   * Return an appropriate module text. Suitable to insert into
   * a script tag (that is unescaped).
   * @param {string} srcUrl
   * @param {string} scriptText
   * @return {string}
   * @private
   */
  goog.wrapModule_ = function(srcUrl, scriptText) {
    if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
      return '' +
          'goog.loadModule(function(exports) {' +
          '"use strict";' + scriptText +
          '\n' +  // terminate any trailing single line comment.
          ';return exports' +
          '});' +
          '\n//# sourceURL=' + srcUrl + '\n';
    } else {
      return '' +
          'goog.loadModule(' +
          goog.global.JSON.stringify(
              scriptText + '\n//# sourceURL=' + srcUrl + '\n') +
          ');';
    }
  };

  // On IE9 and earlier, it is necessary to handle
  // deferred module loads. In later browsers, the
  // code to be evaluated is simply inserted as a script
  // block in the correct order. To eval deferred
  // code at the right time, we piggy back on goog.require to call
  // goog.maybeProcessDeferredDep_.
  //
  // The goog.requires are used both to bootstrap
  // the loading process (when no deps are available) and
  // declare that they should be available.
  //
  // Here we eval the sources, if all the deps are available
  // either already eval'd or goog.require'd.  This will
  // be the case when all the dependencies have already
  // been loaded, and the dependent module is loaded.
  //
  // But this alone isn't sufficient because it is also
  // necessary to handle the case where there is no root
  // that is not deferred.  For that there we register for an event
  // and trigger goog.loadQueuedModules_ handle any remaining deferred
  // evaluations.

  /**
   * Handle any remaining deferred goog.module evals.
   * @private
   */
  goog.loadQueuedModules_ = function() {
    var count = goog.queuedModules_.length;
    if (count > 0) {
      var queue = goog.queuedModules_;
      goog.queuedModules_ = [];
      for (var i = 0; i < count; i++) {
        var path = queue[i];
        goog.maybeProcessDeferredPath_(path);
      }
    }
  };


  /**
   * Eval the named module if its dependencies are
   * available.
   * @param {string} name The module to load.
   * @private
   */
  goog.maybeProcessDeferredDep_ = function(name) {
    if (goog.isDeferredModule_(name) && goog.allDepsAreAvailable_(name)) {
      var path = goog.getPathFromDeps_(name);
      goog.maybeProcessDeferredPath_(goog.basePath + path);
    }
  };

  /**
   * @param {string} name The module to check.
   * @return {boolean} Whether the name represents a
   *     module whose evaluation has been deferred.
   * @private
   */
  goog.isDeferredModule_ = function(name) {
    var path = goog.getPathFromDeps_(name);
    var loadFlags = path && goog.dependencies_.loadFlags[path] || {};
    if (path && (loadFlags['module'] == 'goog' ||
                 goog.needsTranspile_(loadFlags['lang']))) {
      var abspath = goog.basePath + path;
      return (abspath) in goog.dependencies_.deferred;
    }
    return false;
  };

  /**
   * @param {string} name The module to check.
   * @return {boolean} Whether the name represents a
   *     module whose declared dependencies have all been loaded
   *     (eval'd or a deferred module load)
   * @private
   */
  goog.allDepsAreAvailable_ = function(name) {
    var path = goog.getPathFromDeps_(name);
    if (path && (path in goog.dependencies_.requires)) {
      for (var requireName in goog.dependencies_.requires[path]) {
        if (!goog.isProvided_(requireName) &&
            !goog.isDeferredModule_(requireName)) {
          return false;
        }
      }
    }
    return true;
  };


  /**
   * @param {string} abspath
   * @private
   */
  goog.maybeProcessDeferredPath_ = function(abspath) {
    if (abspath in goog.dependencies_.deferred) {
      var src = goog.dependencies_.deferred[abspath];
      delete goog.dependencies_.deferred[abspath];
      goog.globalEval(src);
    }
  };


  /**
   * Load a goog.module from the provided URL.  This is not a general purpose
   * code loader and does not support late loading code, that is it should only
   * be used during page load. This method exists to support unit tests and
   * "debug" loaders that would otherwise have inserted script tags. Under the
   * hood this needs to use a synchronous XHR and is not recommeneded for
   * production code.
   *
   * The module's goog.requires must have already been satisified; an exception
   * will be thrown if this is not the case. This assumption is that no
   * "deps.js" file exists, so there is no way to discover and locate the
   * module-to-be-loaded's dependencies and no attempt is made to do so.
   *
   * There should only be one attempt to load a module.  If
   * "goog.loadModuleFromUrl" is called for an already loaded module, an
   * exception will be throw.
   *
   * @param {string} url The URL from which to attempt to load the goog.module.
   */
  goog.loadModuleFromUrl = function(url) {
    // Because this executes synchronously, we don't need to do any additional
    // bookkeeping. When "goog.loadModule" the namespace will be marked as
    // having been provided which is sufficient.
    goog.retrieveAndExec_(url, true, false);
  };


  /**
   * Writes a new script pointing to {@code src} directly into the DOM.
   *
   * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for
   * the fallback mechanism.
   *
   * @param {string} src The script URL.
   * @private
   */
  goog.writeScriptSrcNode_ = function(src) {
    goog.global.document.write(
        '<script type="text/javascript" src="' + src + '"></' +
        'script>');
  };


  /**
   * Appends a new script node to the DOM using a CSP-compliant mechanism. This
   * method exists as a fallback for document.write (which is not allowed in a
   * strict CSP context, e.g., Chrome apps).
   *
   * NOTE: This method is not analogous to using document.write to insert a
   * <script> tag; specifically, the user agent will execute a script added by
   * document.write immediately after the current script block finishes
   * executing, whereas the DOM-appended script node will not be executed until
   * the entire document is parsed and executed. That is to say, this script is
   * added to the end of the script execution queue.
   *
   * The page must not attempt to call goog.required entities until after the
   * document has loaded, e.g., in or after the window.onload callback.
   *
   * @param {string} src The script URL.
   * @private
   */
  goog.appendScriptSrcNode_ = function(src) {
    /** @type {Document} */
    var doc = goog.global.document;
    var scriptEl =
        /** @type {HTMLScriptElement} */ (doc.createElement('script'));
    scriptEl.type = 'text/javascript';
    scriptEl.src = src;
    scriptEl.defer = false;
    scriptEl.async = false;
    doc.head.appendChild(scriptEl);
  };


  /**
   * The default implementation of the import function. Writes a script tag to
   * import the script.
   *
   * @param {string} src The script url.
   * @param {string=} opt_sourceText The optionally source text to evaluate
   * @return {boolean} True if the script was imported, false otherwise.
   * @private
   */
  goog.writeScriptTag_ = function(src, opt_sourceText) {
    if (goog.inHtmlDocument_()) {
      /** @type {!HTMLDocument} */
      var doc = goog.global.document;

      // If the user tries to require a new symbol after document load,
      // something has gone terribly wrong. Doing a document.write would
      // wipe out the page. This does not apply to the CSP-compliant method
      // of writing script tags.
      if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING &&
          doc.readyState == 'complete') {
        // Certain test frameworks load base.js multiple times, which tries
        // to write deps.js each time. If that happens, just fail silently.
        // These frameworks wipe the page between each load of base.js, so this
        // is OK.
        var isDeps = /\bdeps.js$/.test(src);
        if (isDeps) {
          return false;
        } else {
          throw Error('Cannot write "' + src + '" after document load');
        }
      }

      if (opt_sourceText === undefined) {
        if (!goog.IS_OLD_IE_) {
          if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) {
            goog.appendScriptSrcNode_(src);
          } else {
            goog.writeScriptSrcNode_(src);
          }
        } else {
          var state = " onreadystatechange='goog.onScriptLoad_(this, " +
              ++goog.lastNonModuleScriptIndex_ + ")' ";
          doc.write(
              '<script type="text/javascript" src="' + src + '"' + state +
              '></' +
              'script>');
        }
      } else {
        doc.write(
            '<script type="text/javascript">' + opt_sourceText + '</' +
            'script>');
      }
      return true;
    } else {
      return false;
    }
  };


  /**
   * Determines whether the given language needs to be transpiled.
   * @param {string} lang
   * @return {boolean}
   * @private
   */
  goog.needsTranspile_ = function(lang) {
    if (goog.TRANSPILE == 'always') {
      return true;
    } else if (goog.TRANSPILE == 'never') {
      return false;
    } else if (!goog.transpiledLanguages_) {
      goog.transpiledLanguages_ = {'es5': true, 'es6': true, 'es6-impl': true};
      /** @preserveTry */
      try {
        // Perform some quick conformance checks, to distinguish
        // between browsers that support es5, es6-impl, or es6.

        // Identify ES3-only browsers by their incorrect treatment of commas.
        goog.transpiledLanguages_['es5'] = eval('[1,].length!=1');

        // As browsers mature, features will be moved from the full test
        // into the impl test.  This must happen before the corresponding
        // features are changed in the Closure Compiler's FeatureSet object.

        // Test 1: es6-impl [FF49, Edge 13, Chrome 49]
        //   (a) let/const keyword, (b) class expressions, (c) Map object,
        //   (d) iterable arguments, (e) spread operator
        var es6implTest =
            'let a={};const X=class{constructor(){}x(z){return new Map([' +
            '...arguments]).get(z[0])==3}};return new X().x([a,3])';

        // Test 2: es6 [FF50 (?), Edge 14 (?), Chrome 50]
        //   (a) default params (specifically shadowing locals),
        //   (b) destructuring, (c) block-scoped functions,
        //   (d) for-of (const), (e) new.target/Reflect.construct
        var es6fullTest =
            'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' +
            'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' +
            'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' +
            'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' +
            '==3}';

        if (eval('(()=>{"use strict";' + es6implTest + '})()')) {
          goog.transpiledLanguages_['es6-impl'] = false;
        }
        if (eval('(()=>{"use strict";' + es6fullTest + '})()')) {
          goog.transpiledLanguages_['es6'] = false;
        }
      } catch (err) {
      }
    }
    return !!goog.transpiledLanguages_[lang];
  };


  /** @private {?Object<string, boolean>} */
  goog.transpiledLanguages_ = null;


  /** @private {number} */
  goog.lastNonModuleScriptIndex_ = 0;


  /**
   * A readystatechange handler for legacy IE
   * @param {!HTMLScriptElement} script
   * @param {number} scriptIndex
   * @return {boolean}
   * @private
   */
  goog.onScriptLoad_ = function(script, scriptIndex) {
    // for now load the modules when we reach the last script,
    // later allow more inter-mingling.
    if (script.readyState == 'complete' &&
        goog.lastNonModuleScriptIndex_ == scriptIndex) {
      goog.loadQueuedModules_();
    }
    return true;
  };

  /**
   * Resolves dependencies based on the dependencies added using addDependency
   * and calls importScript_ in the correct order.
   * @param {string} pathToLoad The path from which to start discovering
   *     dependencies.
   * @private
   */
  goog.writeScripts_ = function(pathToLoad) {
    /** @type {!Array<string>} The scripts we need to write this time. */
    var scripts = [];
    var seenScript = {};
    var deps = goog.dependencies_;

    /** @param {string} path */
    function visitNode(path) {
      if (path in deps.written) {
        return;
      }

      // We have already visited this one. We can get here if we have cyclic
      // dependencies.
      if (path in deps.visited) {
        return;
      }

      deps.visited[path] = true;

      if (path in deps.requires) {
        for (var requireName in deps.requires[path]) {
          // If the required name is defined, we assume that it was already
          // bootstrapped by other means.
          if (!goog.isProvided_(requireName)) {
            if (requireName in deps.nameToPath) {
              visitNode(deps.nameToPath[requireName]);
            } else {
              throw Error('Undefined nameToPath for ' + requireName);
            }
          }
        }
      }

      if (!(path in seenScript)) {
        seenScript[path] = true;
        scripts.push(path);
      }
    }

    visitNode(pathToLoad);

    // record that we are going to load all these scripts.
    for (var i = 0; i < scripts.length; i++) {
      var path = scripts[i];
      goog.dependencies_.written[path] = true;
    }

    // If a module is loaded synchronously then we need to
    // clear the current inModuleLoader value, and restore it when we are
    // done loading the current "requires".
    var moduleState = goog.moduleLoaderState_;
    goog.moduleLoaderState_ = null;

    for (var i = 0; i < scripts.length; i++) {
      var path = scripts[i];
      if (path) {
        var loadFlags = deps.loadFlags[path] || {};
        var needsTranspile = goog.needsTranspile_(loadFlags['lang']);
        if (loadFlags['module'] == 'goog' || needsTranspile) {
          goog.importProcessedScript_(
              goog.basePath + path, loadFlags['module'] == 'goog',
              needsTranspile);
        } else {
          goog.importScript_(goog.basePath + path);
        }
      } else {
        goog.moduleLoaderState_ = moduleState;
        throw Error('Undefined script input');
      }
    }

    // restore the current "module loading state"
    goog.moduleLoaderState_ = moduleState;
  };


  /**
   * Looks at the dependency rules and tries to determine the script file that
   * fulfills a particular rule.
   * @param {string} rule In the form goog.namespace.Class or project.script.
   * @return {?string} Url corresponding to the rule, or null.
   * @private
   */
  goog.getPathFromDeps_ = function(rule) {
    if (rule in goog.dependencies_.nameToPath) {
      return goog.dependencies_.nameToPath[rule];
    } else {
      return null;
    }
  };

  goog.findBasePath_();

  // Allow projects to manage the deps files themselves.
  if (!goog.global.CLOSURE_NO_DEPS) {
    goog.importScript_(goog.basePath + 'deps.js');
  }
}


/**
 * @param {function(?):?|string} moduleDef The module definition.
 */
goog.loadModule = function(moduleDef) {
  // NOTE: we allow function definitions to be either in the from
  // of a string to eval (which keeps the original source intact) or
  // in a eval forbidden environment (CSP) we allow a function definition
  // which in its body must call {@code goog.module}, and return the exports
  // of the module.
  var previousState = goog.moduleLoaderState_;
  try {
    goog.moduleLoaderState_ = {
      moduleName: undefined,
      declareLegacyNamespace: false
    };
    var exports;
    if (goog.isFunction(moduleDef)) {
      exports = moduleDef.call(undefined, {});
    } else if (goog.isString(moduleDef)) {
      exports = goog.loadModuleFromSource_.call(undefined, moduleDef);
    } else {
      throw Error('Invalid module definition');
    }

    var moduleName = goog.moduleLoaderState_.moduleName;
    if (!goog.isString(moduleName) || !moduleName) {
      throw Error('Invalid module name \"' + moduleName + '\"');
    }

    // Don't seal legacy namespaces as they may be uses as a parent of
    // another namespace
    if (goog.moduleLoaderState_.declareLegacyNamespace) {
      goog.constructNamespace_(moduleName, exports);
    } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) {
      Object.seal(exports);
    }

    goog.loadedModules_[moduleName] = exports;
  } finally {
    goog.moduleLoaderState_ = previousState;
  }
};


/**
 * @private @const {function(string):?}
 *
 * The new type inference warns because this function has no formal
 * parameters, but its jsdoc says that it takes one argument.
 * (The argument is used via arguments[0], but NTI does not detect this.)
 * @suppress {newCheckTypes}
 */
goog.loadModuleFromSource_ = function() {
  // NOTE: we avoid declaring parameters or local variables here to avoid
  // masking globals or leaking values into the module definition.
  'use strict';
  var exports = {};
  eval(arguments[0]);
  return exports;
};


/**
 * Normalize a file path by removing redundant ".." and extraneous "." file
 * path components.
 * @param {string} path
 * @return {string}
 * @private
 */
goog.normalizePath_ = function(path) {
  var components = path.split('/');
  var i = 0;
  while (i < components.length) {
    if (components[i] == '.') {
      components.splice(i, 1);
    } else if (
        i && components[i] == '..' && components[i - 1] &&
        components[i - 1] != '..') {
      components.splice(--i, 2);
    } else {
      i++;
    }
  }
  return components.join('/');
};


/**
 * Loads file by synchronous XHR. Should not be used in production environments.
 * @param {string} src Source URL.
 * @return {?string} File contents, or null if load failed.
 * @private
 */
goog.loadFileSync_ = function(src) {
  if (goog.global.CLOSURE_LOAD_FILE_SYNC) {
    return goog.global.CLOSURE_LOAD_FILE_SYNC(src);
  } else {
    try {
      /** @type {XMLHttpRequest} */
      var xhr = new goog.global['XMLHttpRequest']();
      xhr.open('get', src, false);
      xhr.send();
      // NOTE: Successful http: requests have a status of 200, but successful
      // file: requests may have a status of zero.  Any other status, or a
      // thrown exception (particularly in case of file: requests) indicates
      // some sort of error, which we treat as a missing or unavailable file.
      return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null;
    } catch (err) {
      // No need to rethrow or log, since errors should show up on their own.
      return null;
    }
  }
};


/**
 * Retrieve and execute a script that needs some sort of wrapping.
 * @param {string} src Script source URL.
 * @param {boolean} isModule Whether to load as a module.
 * @param {boolean} needsTranspile Whether to transpile down to ES3.
 * @private
 */
goog.retrieveAndExec_ = function(src, isModule, needsTranspile) {
  if (!COMPILED) {
    // The full but non-canonicalized URL for later use.
    var originalPath = src;
    // Canonicalize the path, removing any /./ or /../ since Chrome's debugging
    // console doesn't auto-canonicalize XHR loads as it does <script> srcs.
    src = goog.normalizePath_(src);

    var importScript =
        goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_;

    var scriptText = goog.loadFileSync_(src);
    if (scriptText == null) {
      throw new Error('Load of "' + src + '" failed');
    }

    if (needsTranspile) {
      scriptText = goog.transpile_.call(goog.global, scriptText, src);
    }

    if (isModule) {
      scriptText = goog.wrapModule_(src, scriptText);
    } else {
      scriptText += '\n//# sourceURL=' + src;
    }
    var isOldIE = goog.IS_OLD_IE_;
    if (isOldIE) {
      goog.dependencies_.deferred[originalPath] = scriptText;
      goog.queuedModules_.push(originalPath);
    } else {
      importScript(src, scriptText);
    }
  }
};


/**
 * Lazily retrieves the transpiler and applies it to the source.
 * @param {string} code JS code.
 * @param {string} path Path to the code.
 * @return {string} The transpiled code.
 * @private
 */
goog.transpile_ = function(code, path) {
  var jscomp = goog.global['$jscomp'];
  if (!jscomp) {
    goog.global['$jscomp'] = jscomp = {};
  }
  var transpile = jscomp.transpile;
  if (!transpile) {
    var transpilerPath = goog.basePath + goog.TRANSPILER;
    var transpilerCode = goog.loadFileSync_(transpilerPath);
    if (transpilerCode) {
      // This must be executed synchronously, since by the time we know we
      // need it, we're about to load and write the ES6 code synchronously,
      // so a normal script-tag load will be too slow.
      eval(transpilerCode + '\n//# sourceURL=' + transpilerPath);
      // Note: transpile.js reassigns goog.global['$jscomp'] so pull it again.
      jscomp = goog.global['$jscomp'];
      transpile = jscomp.transpile;
    }
  }
  if (!transpile) {
    // The transpiler is an optional component.  If it's not available then
    // replace it with a pass-through function that simply logs.
    var suffix = ' requires transpilation but no transpiler was found.';
    transpile = jscomp.transpile = function(code, path) {
      // TODO(user): figure out some way to get this error to show up
      // in test results, noting that the failure may occur in many
      // different ways, including in loadModule() before the test
      // runner even comes up.
      goog.logToConsole_(path + suffix);
      return code;
    };
  }
  // Note: any transpilation errors/warnings will be logged to the console.
  return transpile(code, path);
};


//==============================================================================
// Language Enhancements
//==============================================================================


/**
 * This is a "fixed" version of the typeof operator.  It differs from the typeof
 * operator in such a way that null returns 'null' and arrays return 'array'.
 * @param {?} value The value to get the type of.
 * @return {string} The name of the type.
 */
goog.typeOf = function(value) {
  var s = typeof value;
  if (s == 'object') {
    if (value) {
      // Check these first, so we can avoid calling Object.prototype.toString if
      // possible.
      //
      // IE improperly marshals typeof across execution contexts, but a
      // cross-context object will still return false for "instanceof Object".
      if (value instanceof Array) {
        return 'array';
      } else if (value instanceof Object) {
        return s;
      }

      // HACK: In order to use an Object prototype method on the arbitrary
      //   value, the compiler requires the value be cast to type Object,
      //   even though the ECMA spec explicitly allows it.
      var className = Object.prototype.toString.call(
          /** @type {!Object} */ (value));
      // In Firefox 3.6, attempting to access iframe window objects' length
      // property throws an NS_ERROR_FAILURE, so we need to special-case it
      // here.
      if (className == '[object Window]') {
        return 'object';
      }

      // We cannot always use constructor == Array or instanceof Array because
      // different frames have different Array objects. In IE6, if the iframe
      // where the array was created is destroyed, the array loses its
      // prototype. Then dereferencing val.splice here throws an exception, so
      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
      // so that will work. In this case, this function will return false and
      // most array functions will still work because the array is still
      // array-like (supports length and []) even though it has lost its
      // prototype.
      // Mark Miller noticed that Object.prototype.toString
      // allows access to the unforgeable [[Class]] property.
      //  15.2.4.2 Object.prototype.toString ( )
      //  When the toString method is called, the following steps are taken:
      //      1. Get the [[Class]] property of this object.
      //      2. Compute a string value by concatenating the three strings
      //         "[object ", Result(1), and "]".
      //      3. Return Result(2).
      // and this behavior survives the destruction of the execution context.
      if ((className == '[object Array]' ||
           // In IE all non value types are wrapped as objects across window
           // boundaries (not iframe though) so we have to do object detection
           // for this edge case.
           typeof value.length == 'number' &&
               typeof value.splice != 'undefined' &&
               typeof value.propertyIsEnumerable != 'undefined' &&
               !value.propertyIsEnumerable('splice')

               )) {
        return 'array';
      }
      // HACK: There is still an array case that fails.
      //     function ArrayImpostor() {}
      //     ArrayImpostor.prototype = [];
      //     var impostor = new ArrayImpostor;
      // this can be fixed by getting rid of the fast path
      // (value instanceof Array) and solely relying on
      // (value && Object.prototype.toString.vall(value) === '[object Array]')
      // but that would require many more function calls and is not warranted
      // unless closure code is receiving objects from untrusted sources.

      // IE in cross-window calls does not correctly marshal the function type
      // (it appears just as an object) so we cannot use just typeof val ==
      // 'function'. However, if the object has a call property, it is a
      // function.
      if ((className == '[object Function]' ||
           typeof value.call != 'undefined' &&
               typeof value.propertyIsEnumerable != 'undefined' &&
               !value.propertyIsEnumerable('call'))) {
        return 'function';
      }

    } else {
      return 'null';
    }

  } else if (s == 'function' && typeof value.call == 'undefined') {
    // In Safari typeof nodeList returns 'function', and on Firefox typeof
    // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
    // would like to return object for those and we can detect an invalid
    // function by making sure that the function object has a call method.
    return 'object';
  }
  return s;
};


/**
 * Returns true if the specified value is null.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is null.
 */
goog.isNull = function(val) {
  return val === null;
};


/**
 * Returns true if the specified value is defined and not null.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is defined and not null.
 */
goog.isDefAndNotNull = function(val) {
  // Note that undefined == null.
  return val != null;
};


/**
 * Returns true if the specified value is an array.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is an array.
 */
goog.isArray = function(val) {
  return goog.typeOf(val) == 'array';
};


/**
 * Returns true if the object looks like an array. To qualify as array like
 * the value needs to be either a NodeList or an object with a Number length
 * property. As a special case, a function value is not array like, because its
 * length property is fixed to correspond to the number of expected arguments.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is an array.
 */
goog.isArrayLike = function(val) {
  var type = goog.typeOf(val);
  // We do not use goog.isObject here in order to exclude function values.
  return type == 'array' || type == 'object' && typeof val.length == 'number';
};


/**
 * Returns true if the object looks like a Date. To qualify as Date-like the
 * value needs to be an object and have a getFullYear() function.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is a like a Date.
 */
goog.isDateLike = function(val) {
  return goog.isObject(val) && typeof val.getFullYear == 'function';
};


/**
 * Returns true if the specified value is a string.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is a string.
 */
goog.isString = function(val) {
  return typeof val == 'string';
};


/**
 * Returns true if the specified value is a boolean.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is boolean.
 */
goog.isBoolean = function(val) {
  return typeof val == 'boolean';
};


/**
 * Returns true if the specified value is a number.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is a number.
 */
goog.isNumber = function(val) {
  return typeof val == 'number';
};


/**
 * Returns true if the specified value is a function.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is a function.
 */
goog.isFunction = function(val) {
  return goog.typeOf(val) == 'function';
};


/**
 * Returns true if the specified value is an object.  This includes arrays and
 * functions.
 * @param {?} val Variable to test.
 * @return {boolean} Whether variable is an object.
 */
goog.isObject = function(val) {
  var type = typeof val;
  return type == 'object' && val != null || type == 'function';
  // return Object(val) === val also works, but is slower, especially if val is
  // not an object.
};


/**
 * Gets a unique ID for an object. This mutates the object so that further calls
 * with the same object as a parameter returns the same value. The unique ID is
 * guaranteed to be unique across the current session amongst objects that are
 * passed into {@code getUid}. There is no guarantee that the ID is unique or
 * consistent across sessions. It is unsafe to generate unique ID for function
 * prototypes.
 *
 * @param {Object} obj The object to get the unique ID for.
 * @return {number} The unique ID for the object.
 */
goog.getUid = function(obj) {
  // TODO(arv): Make the type stricter, do not accept null.

  // In Opera window.hasOwnProperty exists but always returns false so we avoid
  // using it. As a consequence the unique ID generated for BaseClass.prototype
  // and SubClass.prototype will be the same.
  return obj[goog.UID_PROPERTY_] ||
      (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
};


/**
 * Whether the given object is already assigned a unique ID.
 *
 * This does not modify the object.
 *
 * @param {!Object} obj The object to check.
 * @return {boolean} Whether there is an assigned unique id for the object.
 */
goog.hasUid = function(obj) {
  return !!obj[goog.UID_PROPERTY_];
};


/**
 * Removes the unique ID from an object. This is useful if the object was
 * previously mutated using {@code goog.getUid} in which case the mutation is
 * undone.
 * @param {Object} obj The object to remove the unique ID field from.
 */
goog.removeUid = function(obj) {
  // TODO(arv): Make the type stricter, do not accept null.

  // In IE, DOM nodes are not instances of Object and throw an exception if we
  // try to delete.  Instead we try to use removeAttribute.
  if (obj !== null && 'removeAttribute' in obj) {
    obj.removeAttribute(goog.UID_PROPERTY_);
  }
  /** @preserveTry */
  try {
    delete obj[goog.UID_PROPERTY_];
  } catch (ex) {
  }
};


/**
 * Name for unique ID property. Initialized in a way to help avoid collisions
 * with other closure JavaScript on the same page.
 * @type {string}
 * @private
 */
goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);


/**
 * Counter for UID.
 * @type {number}
 * @private
 */
goog.uidCounter_ = 0;


/**
 * Adds a hash code field to an object. The hash code is unique for the
 * given object.
 * @param {Object} obj The object to get the hash code for.
 * @return {number} The hash code for the object.
 * @deprecated Use goog.getUid instead.
 */
goog.getHashCode = goog.getUid;


/**
 * Removes the hash code field from an object.
 * @param {Object} obj The object to remove the field from.
 * @deprecated Use goog.removeUid instead.
 */
goog.removeHashCode = goog.removeUid;


/**
 * Clones a value. The input may be an Object, Array, or basic type. Objects and
 * arrays will be cloned recursively.
 *
 * WARNINGS:
 * <code>goog.cloneObject</code> does not detect reference loops. Objects that
 * refer to themselves will cause infinite recursion.
 *
 * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
 * UIDs created by <code>getUid</code> into cloned results.
 *
 * @param {*} obj The value to clone.
 * @return {*} A clone of the input value.
 * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
 */
goog.cloneObject = function(obj) {
  var type = goog.typeOf(obj);
  if (type == 'object' || type == 'array') {
    if (obj.clone) {
      return obj.clone();
    }
    var clone = type == 'array' ? [] : {};
    for (var key in obj) {
      clone[key] = goog.cloneObject(obj[key]);
    }
    return clone;
  }

  return obj;
};


/**
 * A native implementation of goog.bind.
 * @param {Function} fn A function to partially apply.
 * @param {Object|undefined} selfObj Specifies the object which this should
 *     point to when the function is run.
 * @param {...*} var_args Additional arguments that are partially applied to the
 *     function.
 * @return {!Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 * @private
 * @suppress {deprecated} The compiler thinks that Function.prototype.bind is
 *     deprecated because some people have declared a pure-JS version.
 *     Only the pure-JS version is truly deprecated.
 */
goog.bindNative_ = function(fn, selfObj, var_args) {
  return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
};


/**
 * A pure-JS implementation of goog.bind.
 * @param {Function} fn A function to partially apply.
 * @param {Object|undefined} selfObj Specifies the object which this should
 *     point to when the function is run.
 * @param {...*} var_args Additional arguments that are partially applied to the
 *     function.
 * @return {!Function} A partially-applied form of the function bind() was
 *     invoked as a method of.
 * @private
 */
goog.bindJs_ = function(fn, selfObj, var_args) {
  if (!fn) {
    throw new Error();
  }

  if (arguments.length > 2) {
    var boundArgs = Array.prototype.slice.call(arguments, 2);
    return function() {
      // Prepend the bound arguments to the current arguments.
      var newArgs = Array.prototype.slice.call(arguments);
      Array.prototype.unshift.apply(newArgs, boundArgs);
      return fn.apply(selfObj, newArgs);
    };

  } else {
    return function() { return fn.apply(selfObj, arguments); };
  }
};


/**
 * Partially applies this function to a particular 'this object' and zero or
 * more arguments. The result is a new function with some arguments of the first
 * function pre-filled and the value of this 'pre-specified'.
 *
 * Remaining arguments specified at call-time are appended to the pre-specified
 * ones.
 *
 * Also see: {@link #partial}.
 *
 * Usage:
 * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2');
 * barMethBound('arg3', 'arg4');</pre>
 *
 * @param {?function(this:T, ...)} fn A function to partially apply.
 * @param {T} selfObj Specifies the object which this should point to when the
 *     function is run.
 * @param {...*} var_args Additional arguments that are partially applied to the
 *     function.
 * @return {!Function} A partially-applied form of the function goog.bind() was
 *     invoked as a method of.
 * @template T
 * @suppress {deprecated} See above.
 */
goog.bind = function(fn, selfObj, var_args) {
  // TODO(nicksantos): narrow the type signature.
  if (Function.prototype.bind &&
      // NOTE(nicksantos): Somebody pulled base.js into the default Chrome
      // extension environment. This means that for Chrome extensions, they get
      // the implementation of Function.prototype.bind that calls goog.bind
      // instead of the native one. Even worse, we don't want to introduce a
      // circular dependency between goog.bind and Function.prototype.bind, so
      // we have to hack this to make sure it works correctly.
      Function.prototype.bind.toString().indexOf('native code') != -1) {
    goog.bind = goog.bindNative_;
  } else {
    goog.bind = goog.bindJs_;
  }
  return goog.bind.apply(null, arguments);
};


/**
 * Like goog.bind(), except that a 'this object' is not required. Useful when
 * the target function is already bound.
 *
 * Usage:
 * var g = goog.partial(f, arg1, arg2);
 * g(arg3, arg4);
 *
 * @param {Function} fn A function to partially apply.
 * @param {...*} var_args Additional arguments that are partially applied to fn.
 * @return {!Function} A partially-applied form of the function goog.partial()
 *     was invoked as a method of.
 */
goog.partial = function(fn, var_args) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    // Clone the array (with slice()) and append additional arguments
    // to the existing arguments.
    var newArgs = args.slice();
    newArgs.push.apply(newArgs, arguments);
    return fn.apply(this, newArgs);
  };
};


/**
 * Copies all the members of a source object to a target object. This method
 * does not work on all browsers for all objects that contain keys such as
 * toString or hasOwnProperty. Use goog.object.extend for this purpose.
 * @param {Object} target Target.
 * @param {Object} source Source.
 */
goog.mixin = function(target, source) {
  for (var x in source) {
    target[x] = source[x];
  }

  // For IE7 or lower, the for-in-loop does not contain any properties that are
  // not enumerable on the prototype object (for example, isPrototypeOf from
  // Object.prototype) but also it will not include 'replace' on objects that
  // extend String and change 'replace' (not that it is common for anyone to
  // extend anything except Object).
};


/**
 * @return {number} An integer value representing the number of milliseconds
 *     between midnight, January 1, 1970 and the current time.
 */
goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
             // Unary plus operator converts its operand to a number which in
             // the case of
             // a date is done by calling getTime().
             return +new Date();
           });


/**
 * Evals JavaScript in the global scope.  In IE this uses execScript, other
 * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
 * global scope (for example, in Safari), appends a script tag instead.
 * Throws an exception if neither execScript or eval is defined.
 * @param {string} script JavaScript string.
 */
goog.globalEval = function(script) {
  if (goog.global.execScript) {
    goog.global.execScript(script, 'JavaScript');
  } else if (goog.global.eval) {
    // Test to see if eval works
    if (goog.evalWorksForGlobals_ == null) {
      goog.global.eval('var _evalTest_ = 1;');
      if (typeof goog.global['_evalTest_'] != 'undefined') {
        try {
          delete goog.global['_evalTest_'];
        } catch (ignore) {
          // Microsoft edge fails the deletion above in strict mode.
        }
        goog.evalWorksForGlobals_ = true;
      } else {
        goog.evalWorksForGlobals_ = false;
      }
    }

    if (goog.evalWorksForGlobals_) {
      goog.global.eval(script);
    } else {
      /** @type {Document} */
      var doc = goog.global.document;
      var scriptElt =
          /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT'));
      scriptElt.type = 'text/javascript';
      scriptElt.defer = false;
      // Note(user): can't use .innerHTML since "t('<test>')" will fail and
      // .text doesn't work in Safari 2.  Therefore we append a text node.
      scriptElt.appendChild(doc.createTextNode(script));
      doc.body.appendChild(scriptElt);
      doc.body.removeChild(scriptElt);
    }
  } else {
    throw Error('goog.globalEval not available');
  }
};


/**
 * Indicates whether or not we can call 'eval' directly to eval code in the
 * global scope. Set to a Boolean by the first call to goog.globalEval (which
 * empirically tests whether eval works for globals). @see goog.globalEval
 * @type {?boolean}
 * @private
 */
goog.evalWorksForGlobals_ = null;


/**
 * Optional map of CSS class names to obfuscated names used with
 * goog.getCssName().
 * @private {!Object<string, string>|undefined}
 * @see goog.setCssNameMapping
 */
goog.cssNameMapping_;


/**
 * Optional obfuscation style for CSS class names. Should be set to either
 * 'BY_WHOLE' or 'BY_PART' if defined.
 * @type {string|undefined}
 * @private
 * @see goog.setCssNameMapping
 */
goog.cssNameMappingStyle_;


/**
 * Handles strings that are intended to be used as CSS class names.
 *
 * This function works in tandem with @see goog.setCssNameMapping.
 *
 * Without any mapping set, the arguments are simple joined with a hyphen and
 * passed through unaltered.
 *
 * When there is a mapping, there are two possible styles in which these
 * mappings are used. In the BY_PART style, each part (i.e. in between hyphens)
 * of the passed in css name is rewritten according to the map. In the BY_WHOLE
 * style, the full css name is looked up in the map directly. If a rewrite is
 * not specified by the map, the compiler will output a warning.
 *
 * When the mapping is passed to the compiler, it will replace calls to
 * goog.getCssName with the strings from the mapping, e.g.
 *     var x = goog.getCssName('foo');
 *     var y = goog.getCssName(this.baseClass, 'active');
 *  becomes:
 *     var x = 'foo';
 *     var y = this.baseClass + '-active';
 *
 * If one argument is passed it will be processed, if two are passed only the
 * modifier will be processed, as it is assumed the first argument was generated
 * as a result of calling goog.getCssName.
 *
 * @param {string} className The class name.
 * @param {string=} opt_modifier A modifier to be appended to the class name.
 * @return {string} The class name or the concatenation of the class name and
 *     the modifier.
 */
goog.getCssName = function(className, opt_modifier) {
  var getMapping = function(cssName) {
    return goog.cssNameMapping_[cssName] || cssName;
  };

  var renameByParts = function(cssName) {
    // Remap all the parts individually.
    var parts = cssName.split('-');
    var mapped = [];
    for (var i = 0; i < parts.length; i++) {
      mapped.push(getMapping(parts[i]));
    }
    return mapped.join('-');
  };

  var rename;
  if (goog.cssNameMapping_) {
    rename =
        goog.cssNameMappingStyle_ == 'BY_WHOLE' ? getMapping : renameByParts;
  } else {
    rename = function(a) { return a; };
  }

  if (opt_modifier) {
    return className + '-' + rename(opt_modifier);
  } else {
    return rename(className);
  }
};


/**
 * Sets the map to check when returning a value from goog.getCssName(). Example:
 * <pre>
 * goog.setCssNameMapping({
 *   "goog": "a",
 *   "disabled": "b",
 * });
 *
 * var x = goog.getCssName('goog');
 * // The following evaluates to: "a a-b".
 * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
 * </pre>
 * When declared as a map of string literals to string literals, the JSCompiler
 * will replace all calls to goog.getCssName() using the supplied map if the
 * --process_closure_primitives flag is set.
 *
 * @param {!Object} mapping A map of strings to strings where keys are possible
 *     arguments to goog.getCssName() and values are the corresponding values
 *     that should be returned.
 * @param {string=} opt_style The style of css name mapping. There are two valid
 *     options: 'BY_PART', and 'BY_WHOLE'.
 * @see goog.getCssName for a description.
 */
goog.setCssNameMapping = function(mapping, opt_style) {
  goog.cssNameMapping_ = mapping;
  goog.cssNameMappingStyle_ = opt_style;
};


/**
 * To use CSS renaming in compiled mode, one of the input files should have a
 * call to goog.setCssNameMapping() with an object literal that the JSCompiler
 * can extract and use to replace all calls to goog.getCssName(). In uncompiled
 * mode, JavaScript code should be loaded before this base.js file that declares
 * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
 * to ensure that the mapping is loaded before any calls to goog.getCssName()
 * are made in uncompiled mode.
 *
 * A hook for overriding the CSS name mapping.
 * @type {!Object<string, string>|undefined}
 */
goog.global.CLOSURE_CSS_NAME_MAPPING;


if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
  // This does not call goog.setCssNameMapping() because the JSCompiler
  // requires that goog.setCssNameMapping() be called with an object literal.
  goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
}


/**
 * Gets a localized message.
 *
 * This function is a compiler primitive. If you give the compiler a localized
 * message bundle, it will replace the string at compile-time with a localized
 * version, and expand goog.getMsg call to a concatenated string.
 *
 * Messages must be initialized in the form:
 * <code>
 * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
 * </code>
 *
 * This function produces a string which should be treated as plain text. Use
 * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to
 * produce SafeHtml.
 *
 * @param {string} str Translatable string, places holders in the form {$foo}.
 * @param {Object<string, string>=} opt_values Maps place holder name to value.
 * @return {string} message with placeholders filled.
 */
goog.getMsg = function(str, opt_values) {
  if (opt_values) {
    str = str.replace(/\{\$([^}]+)}/g, function(match, key) {
      return (opt_values != null && key in opt_values) ? opt_values[key] :
                                                         match;
    });
  }
  return str;
};


/**
 * Gets a localized message. If the message does not have a translation, gives a
 * fallback message.
 *
 * This is useful when introducing a new message that has not yet been
 * translated into all languages.
 *
 * This function is a compiler primitive. Must be used in the form:
 * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
 * where MSG_A and MSG_B were initialized with goog.getMsg.
 *
 * @param {string} a The preferred message.
 * @param {string} b The fallback message.
 * @return {string} The best translated message.
 */
goog.getMsgWithFallback = function(a, b) {
  return a;
};


/**
 * Exposes an unobfuscated global namespace path for the given object.
 * Note that fields of the exported object *will* be obfuscated, unless they are
 * exported in turn via this function or goog.exportProperty.
 *
 * Also handy for making public items that are defined in anonymous closures.
 *
 * ex. goog.exportSymbol('public.path.Foo', Foo);
 *
 * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction);
 *     public.path.Foo.staticFunction();
 *
 * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
 *                       Foo.prototype.myMethod);
 *     new public.path.Foo().myMethod();
 *
 * @param {string} publicPath Unobfuscated name to export.
 * @param {*} object Object the name should point to.
 * @param {Object=} opt_objectToExportTo The object to add the path to; default
 *     is goog.global.
 */
goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
  goog.exportPath_(publicPath, object, opt_objectToExportTo);
};


/**
 * Exports a property unobfuscated into the object's namespace.
 * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
 * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
 * @param {Object} object Object whose static property is being exported.
 * @param {string} publicName Unobfuscated name to export.
 * @param {*} symbol Object the name should point to.
 */
goog.exportProperty = function(object, publicName, symbol) {
  object[publicName] = symbol;
};


/**
 * Inherit the prototype methods from one constructor into another.
 *
 * Usage:
 * <pre>
 * function ParentClass(a, b) { }
 * ParentClass.prototype.foo = function(a) { };
 *
 * function ChildClass(a, b, c) {
 *   ChildClass.base(this, 'constructor', a, b);
 * }
 * goog.inherits(ChildClass, ParentClass);
 *
 * var child = new ChildClass('a', 'b', 'see');
 * child.foo(); // This works.
 * </pre>
 *
 * @param {!Function} childCtor Child class.
 * @param {!Function} parentCtor Parent class.
 */
goog.inherits = function(childCtor, parentCtor) {
  /** @constructor */
  function tempCtor() {}
  tempCtor.prototype = parentCtor.prototype;
  childCtor.superClass_ = parentCtor.prototype;
  childCtor.prototype = new tempCtor();
  /** @override */
  childCtor.prototype.constructor = childCtor;

  /**
   * Calls superclass constructor/method.
   *
   * This function is only available if you use goog.inherits to
   * express inheritance relationships between classes.
   *
   * NOTE: This is a replacement for goog.base and for superClass_
   * property defined in childCtor.
   *
   * @param {!Object} me Should always be "this".
   * @param {string} methodName The method name to call. Calling
   *     superclass constructor can be done with the special string
   *     'constructor'.
   * @param {...*} var_args The arguments to pass to superclass
   *     method/constructor.
   * @return {*} The return value of the superclass method/constructor.
   */
  childCtor.base = function(me, methodName, var_args) {
    // Copying using loop to avoid deop due to passing arguments object to
    // function. This is faster in many JS engines as of late 2014.
    var args = new Array(arguments.length - 2);
    for (var i = 2; i < arguments.length; i++) {
      args[i - 2] = arguments[i];
    }
    return parentCtor.prototype[methodName].apply(me, args);
  };
};


/**
 * Call up to the superclass.
 *
 * If this is called from a constructor, then this calls the superclass
 * constructor with arguments 1-N.
 *
 * If this is called from a prototype method, then you must pass the name of the
 * method as the second argument to this function. If you do not, you will get a
 * runtime error. This calls the superclass' method with arguments 2-N.
 *
 * This function only works if you use goog.inherits to express inheritance
 * relationships between your classes.
 *
 * This function is a compiler primitive. At compile-time, the compiler will do
 * macro expansion to remove a lot of the extra overhead that this function
 * introduces. The compiler will also enforce a lot of the assumptions that this
 * function makes, and treat it as a compiler error if you break them.
 *
 * @param {!Object} me Should always be "this".
 * @param {*=} opt_methodName The method name if calling a super method.
 * @param {...*} var_args The rest of the arguments.
 * @return {*} The return value of the superclass method.
 * @suppress {es5Strict} This method can not be used in strict mode, but
 *     all Closure Library consumers must depend on this file.
 */
goog.base = function(me, opt_methodName, var_args) {
  var caller = arguments.callee.caller;

  if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
    throw Error(
        'arguments.caller not defined.  goog.base() cannot be used ' +
        'with strict mode code. See ' +
        'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
  }

  if (caller.superClass_) {
    // Copying using loop to avoid deop due to passing arguments object to
    // function. This is faster in many JS engines as of late 2014.
    var ctorArgs = new Array(arguments.length - 1);
    for (var i = 1; i < arguments.length; i++) {
      ctorArgs[i - 1] = arguments[i];
    }
    // This is a constructor. Call the superclass constructor.
    return caller.superClass_.constructor.apply(me, ctorArgs);
  }

  // Copying using loop to avoid deop due to passing arguments object to
  // function. This is faster in many JS engines as of late 2014.
  var args = new Array(arguments.length - 2);
  for (var i = 2; i < arguments.length; i++) {
    args[i - 2] = arguments[i];
  }
  var foundCaller = false;
  for (var ctor = me.constructor; ctor;
       ctor = ctor.superClass_ && ctor.superClass_.constructor) {
    if (ctor.prototype[opt_methodName] === caller) {
      foundCaller = true;
    } else if (foundCaller) {
      return ctor.prototype[opt_methodName].apply(me, args);
    }
  }

  // If we did not find the caller in the prototype chain, then one of two
  // things happened:
  // 1) The caller is an instance method.
  // 2) This method was not called by the right caller.
  if (me[opt_methodName] === caller) {
    return me.constructor.prototype[opt_methodName].apply(me, args);
  } else {
    throw Error(
        'goog.base called from a method of one name ' +
        'to a method of a different name');
  }
};


/**
 * Allow for aliasing within scope functions.  This function exists for
 * uncompiled code - in compiled code the calls will be inlined and the aliases
 * applied.  In uncompiled code the function is simply run since the aliases as
 * written are valid JavaScript.
 *
 *
 * @param {function()} fn Function to call.  This function can contain aliases
 *     to namespaces (e.g. "var dom = goog.dom") or classes
 *     (e.g. "var Timer = goog.Timer").
 */
goog.scope = function(fn) {
  if (goog.isInModuleLoader_()) {
    throw Error('goog.scope is not supported within a goog.module.');
  }
  fn.call(goog.global);
};


/*
 * To support uncompiled, strict mode bundles that use eval to divide source
 * like so:
 *    eval('someSource;//# sourceUrl sourcefile.js');
 * We need to export the globally defined symbols "goog" and "COMPILED".
 * Exporting "goog" breaks the compiler optimizations, so we required that
 * be defined externally.
 * NOTE: We don't use goog.exportSymbol here because we don't want to trigger
 * extern generation when that compiler option is enabled.
 */
if (!COMPILED) {
  goog.global['COMPILED'] = COMPILED;
}


//==============================================================================
// goog.defineClass implementation
//==============================================================================


/**
 * Creates a restricted form of a Closure "class":
 *   - from the compiler's perspective, the instance returned from the
 *     constructor is sealed (no new properties may be added).  This enables
 *     better checks.
 *   - the compiler will rewrite this definition to a form that is optimal
 *     for type checking and optimization (initially this will be a more
 *     traditional form).
 *
 * @param {Function} superClass The superclass, Object or null.
 * @param {goog.defineClass.ClassDescriptor} def
 *     An object literal describing
 *     the class.  It may have the following properties:
 *     "constructor": the constructor function
 *     "statics": an object literal containing methods to add to the constructor
 *        as "static" methods or a function that will receive the constructor
 *        function as its only parameter to which static properties can
 *        be added.
 *     all other properties are added to the prototype.
 * @return {!Function} The class constructor.
 */
goog.defineClass = function(superClass, def) {
  // TODO(johnlenz): consider making the superClass an optional parameter.
  var constructor = def.constructor;
  var statics = def.statics;
  // Wrap the constructor prior to setting up the prototype and static methods.
  if (!constructor || constructor == Object.prototype.constructor) {
    constructor = function() {
      throw Error('cannot instantiate an interface (no constructor defined).');
    };
  }

  var cls = goog.defineClass.createSealingConstructor_(constructor, superClass);
  if (superClass) {
    goog.inherits(cls, superClass);
  }

  // Remove all the properties that should not be copied to the prototype.
  delete def.constructor;
  delete def.statics;

  goog.defineClass.applyProperties_(cls.prototype, def);
  if (statics != null) {
    if (statics instanceof Function) {
      statics(cls);
    } else {
      goog.defineClass.applyProperties_(cls, statics);
    }
  }

  return cls;
};


/**
 * @typedef {{
 *   constructor: (!Function|undefined),
 *   statics: (Object|undefined|function(Function):void)
 * }}
 * @suppress {missingProvide}
 */
goog.defineClass.ClassDescriptor;


/**
 * @define {boolean} Whether the instances returned by goog.defineClass should
 *     be sealed when possible.
 *
 * When sealing is disabled the constructor function will not be wrapped by
 * goog.defineClass, making it incompatible with ES6 class methods.
 */
goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);


/**
 * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is
 * defined, this function will wrap the constructor in a function that seals the
 * results of the provided constructor function.
 *
 * @param {!Function} ctr The constructor whose results maybe be sealed.
 * @param {Function} superClass The superclass constructor.
 * @return {!Function} The replacement constructor.
 * @private
 */
goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
  if (!goog.defineClass.SEAL_CLASS_INSTANCES) {
    // Do now wrap the constructor when sealing is disabled. Angular code
    // depends on this for injection to work properly.
    return ctr;
  }

  // Compute whether the constructor is sealable at definition time, rather
  // than when the instance is being constructed.
  var superclassSealable = !goog.defineClass.isUnsealable_(superClass);

  /**
   * @this {Object}
   * @return {?}
   */
  var wrappedCtr = function() {
    // Don't seal an instance of a subclass when it calls the constructor of
    // its super class as there is most likely still setup to do.
    var instance = ctr.apply(this, arguments) || this;
    instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_];

    if (this.constructor === wrappedCtr && superclassSealable &&
        Object.seal instanceof Function) {
      Object.seal(instance);
    }
    return instance;
  };

  return wrappedCtr;
};


/**
 * @param {Function} ctr The constructor to test.
 * @returns {boolean} Whether the constructor has been tagged as unsealable
 *     using goog.tagUnsealableClass.
 * @private
 */
goog.defineClass.isUnsealable_ = function(ctr) {
  return ctr && ctr.prototype &&
      ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_];
};


// TODO(johnlenz): share these values with the goog.object
/**
 * The names of the fields that are defined on Object.prototype.
 * @type {!Array<string>}
 * @private
 * @const
 */
goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  'toLocaleString', 'toString', 'valueOf'
];


// TODO(johnlenz): share this function with the goog.object
/**
 * @param {!Object} target The object to add properties to.
 * @param {!Object} source The object to copy properties from.
 * @private
 */
goog.defineClass.applyProperties_ = function(target, source) {
  // TODO(johnlenz): update this to support ES5 getters/setters

  var key;
  for (key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      target[key] = source[key];
    }
  }

  // For IE the for-in-loop does not contain any properties that are not
  // enumerable on the prototype object (for example isPrototypeOf from
  // Object.prototype) and it will also not include 'replace' on objects that
  // extend String and change 'replace' (not that it is common for anyone to
  // extend anything except Object).
  for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
    key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i];
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      target[key] = source[key];
    }
  }
};


/**
 * Sealing classes breaks the older idiom of assigning properties on the
 * prototype rather than in the constructor. As such, goog.defineClass
 * must not seal subclasses of these old-style classes until they are fixed.
 * Until then, this marks a class as "broken", instructing defineClass
 * not to seal subclasses.
 * @param {!Function} ctr The legacy constructor to tag as unsealable.
 */
goog.tagUnsealableClass = function(ctr) {
  if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) {
    ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true;
  }
};


/**
 * Name for unsealable tag property.
 * @const @private {string}
 */
goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';

goog.provide('ol');


/**
 * Constants defined with the define tag cannot be changed in application
 * code, but can be set at compile time.
 * Some reduce the size of the build in advanced compile mode.
 */


/**
 * @define {boolean} Assume touch.  Default is `false`.
 */
ol.ASSUME_TOUCH = false;


/**
 * TODO: rename this to something having to do with tile grids
 * see https://github.com/openlayers/ol3/issues/2076
 * @define {number} Default maximum zoom for default tile grids.
 */
ol.DEFAULT_MAX_ZOOM = 42;


/**
 * @define {number} Default min zoom level for the map view.  Default is `0`.
 */
ol.DEFAULT_MIN_ZOOM = 0;


/**
 * @define {number} Default maximum allowed threshold  (in pixels) for
 *     reprojection triangulation. Default is `0.5`.
 */
ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD = 0.5;


/**
 * @define {number} Default tile size.
 */
ol.DEFAULT_TILE_SIZE = 256;


/**
 * @define {string} Default WMS version.
 */
ol.DEFAULT_WMS_VERSION = '1.3.0';


/**
 * @define {number} Hysteresis pixels.
 */
ol.DRAG_BOX_HYSTERESIS_PIXELS = 8;


/**
 * @define {boolean} Enable the Canvas renderer.  Default is `true`. Setting
 *     this to false at compile time in advanced mode removes all code
 *     supporting the Canvas renderer from the build.
 */
ol.ENABLE_CANVAS = true;


/**
 * @define {boolean} Enable the DOM renderer (used as a fallback where Canvas is
 *     not available).  Default is `true`. Setting this to false at compile time
 *     in advanced mode removes all code supporting the DOM renderer from the
 *     build.
 */
ol.ENABLE_DOM = true;


/**
 * @define {boolean} Enable rendering of ol.layer.Image based layers.  Default
 *     is `true`. Setting this to false at compile time in advanced mode removes
 *     all code supporting Image layers from the build.
 */
ol.ENABLE_IMAGE = true;


/**
 * @define {boolean} Enable integration with the Proj4js library.  Default is
 *     `true`.
 */
ol.ENABLE_PROJ4JS = true;


/**
 * @define {boolean} Enable automatic reprojection of raster sources. Default is
 *     `true`.
 */
ol.ENABLE_RASTER_REPROJECTION = true;


/**
 * @define {boolean} Enable rendering of ol.layer.Tile based layers.  Default is
 *     `true`. Setting this to false at compile time in advanced mode removes
 *     all code supporting Tile layers from the build.
 */
ol.ENABLE_TILE = true;


/**
 * @define {boolean} Enable rendering of ol.layer.Vector based layers.  Default
 *     is `true`. Setting this to false at compile time in advanced mode removes
 *     all code supporting Vector layers from the build.
 */
ol.ENABLE_VECTOR = true;


/**
 * @define {boolean} Enable rendering of ol.layer.VectorTile based layers.
 *     Default is `true`. Setting this to false at compile time in advanced mode
 *     removes all code supporting VectorTile layers from the build.
 */
ol.ENABLE_VECTOR_TILE = true;


/**
 * @define {boolean} Enable the WebGL renderer.  Default is `true`. Setting
 *     this to false at compile time in advanced mode removes all code
 *     supporting the WebGL renderer from the build.
 */
ol.ENABLE_WEBGL = true;


/**
 * @define {number} The size in pixels of the first atlas image. Default is
 * `256`.
 */
ol.INITIAL_ATLAS_SIZE = 256;


/**
 * @define {number} The maximum size in pixels of atlas images. Default is
 * `-1`, meaning it is not used (and `ol.WEBGL_MAX_TEXTURE_SIZE` is
 * used instead).
 */
ol.MAX_ATLAS_SIZE = -1;


/**
 * @define {number} Maximum mouse wheel delta.
 */
ol.MOUSEWHEELZOOM_MAXDELTA = 1;


/**
 * @define {number} Mouse wheel timeout duration.
 */
ol.MOUSEWHEELZOOM_TIMEOUT_DURATION = 80;


/**
 * @define {number} Maximum width and/or height extent ratio that determines
 * when the overview map should be zoomed out.
 */
ol.OVERVIEWMAP_MAX_RATIO = 0.75;


/**
 * @define {number} Minimum width and/or height extent ratio that determines
 * when the overview map should be zoomed in.
 */
ol.OVERVIEWMAP_MIN_RATIO = 0.1;


/**
 * @define {number} Maximum number of source tiles for raster reprojection of
 *     a single tile.
 *     If too many source tiles are determined to be loaded to create a single
 *     reprojected tile the browser can become unresponsive or even crash.
 *     This can happen if the developer defines projections improperly and/or
 *     with unlimited extents.
 *     If too many tiles are required, no tiles are loaded and
 *     `ol.Tile.State.ERROR` state is set. Default is `100`.
 */
ol.RASTER_REPROJECTION_MAX_SOURCE_TILES = 100;


/**
 * @define {number} Maximum number of subdivision steps during raster
 *     reprojection triangulation. Prevents high memory usage and large
 *     number of proj4 calls (for certain transformations and areas).
 *     At most `2*(2^this)` triangles are created for each triangulated
 *     extent (tile/image). Default is `10`.
 */
ol.RASTER_REPROJECTION_MAX_SUBDIVISION = 10;


/**
 * @define {number} Maximum allowed size of triangle relative to world width.
 *     When transforming corners of world extent between certain projections,
 *     the resulting triangulation seems to have zero error and no subdivision
 *     is performed.
 *     If the triangle width is more than this (relative to world width; 0-1),
 *     subdivison is forced (up to `ol.RASTER_REPROJECTION_MAX_SUBDIVISION`).
 *     Default is `0.25`.
 */
ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH = 0.25;


/**
 * @define {number} Tolerance for geometry simplification in device pixels.
 */
ol.SIMPLIFY_TOLERANCE = 0.5;


/**
 * @define {number} Texture cache high water mark.
 */
ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;


/**
 * @define {string} OpenLayers version.
 */
ol.VERSION = '';


/**
 * The maximum supported WebGL texture size in pixels. If WebGL is not
 * supported, the value is set to `undefined`.
 * @const
 * @type {number|undefined}
 */
ol.WEBGL_MAX_TEXTURE_SIZE; // value is set in `ol.has`


/**
 * List of supported WebGL extensions.
 * @const
 * @type {Array.<string>}
 */
ol.WEBGL_EXTENSIONS; // value is set in `ol.has`


/**
 * Inherit the prototype methods from one constructor into another.
 *
 * Usage:
 *
 *     function ParentClass(a, b) { }
 *     ParentClass.prototype.foo = function(a) { }
 *
 *     function ChildClass(a, b, c) {
 *       // Call parent constructor
 *       ParentClass.call(this, a, b);
 *     }
 *     ol.inherits(ChildClass, ParentClass);
 *
 *     var child = new ChildClass('a', 'b', 'see');
 *     child.foo(); // This works.
 *
 * @param {!Function} childCtor Child constructor.
 * @param {!Function} parentCtor Parent constructor.
 * @function
 * @api
 */
ol.inherits = function(childCtor, parentCtor) {
  childCtor.prototype = Object.create(parentCtor.prototype);
  childCtor.prototype.constructor = childCtor;
};


/**
 * A reusable function, used e.g. as a default for callbacks.
 *
 * @return {undefined} Nothing.
 */
ol.nullFunction = function() {};


/**
 * Gets a unique ID for an object. This mutates the object so that further calls
 * with the same object as a parameter returns the same value. Adapted from
 * goog.getUid.
 *
 * @param {Object} obj The object to get the unique ID for.
 * @return {number} The unique ID for the object.
 */
ol.getUid = function(obj) {
  return obj.ol_uid ||
      (obj.ol_uid = ++ol.uidCounter_);
};


/**
 * Counter for getUid.
 * @type {number}
 * @private
 */
ol.uidCounter_ = 0;


/**
 * @see https://github.com/tc39/proposal-global
 */
if (typeof window !== 'undefined') {
  ol.global = window;
} else if (typeof global !== 'undefined') {
  ol.global = global;
} else if (typeof self !== 'undefined') {
  ol.global = self;
}

goog.provide('ol.AssertionError');

goog.require('ol');

/**
 * Error object thrown when an assertion failed. This is an ECMA-262 Error,
 * extended with a `code` property.
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error}
 * @constructor
 * @extends {Error}
 * @implements {oli.AssertionError}
 * @param {number} code Error code.
 */
ol.AssertionError = function(code) {

  /**
   * @type {string}
   */
  this.message = 'Assertion failed. See ' +
      (ol.VERSION ? 'http://openlayers.org/en/' + ol.VERSION.split('-')[0] : '') +
      '/doc/errors/#' + code + ' for details.';

  /**
   * Error code. The meaning of the code can be found on
   * {@link http://openlayers.org/en/latest/errors.html} (replace `latest` with
   * the version found in the OpenLayers script's header comment if a version
   * other than the latest is used).
   * @type {number}
   * @api
   */
  this.code = code;

  this.name = 'AssertionError';

};
ol.inherits(ol.AssertionError, Error);

goog.provide('ol.asserts');

goog.require('ol');
goog.require('ol.AssertionError');


/**
 * @param {*} assertion Assertion we expected to be truthy.
 * @param {number} errorCode Error code.
 */
ol.asserts.assert = function(assertion, errorCode) {
  if (!assertion) {
    throw new ol.AssertionError(errorCode);
  }
};

goog.provide('ol.math');

goog.require('ol.asserts');


/**
 * Takes a number and clamps it to within the provided bounds.
 * @param {number} value The input number.
 * @param {number} min The minimum value to return.
 * @param {number} max The maximum value to return.
 * @return {number} The input number if it is within bounds, or the nearest
 *     number within the bounds.
 */
ol.math.clamp = function(value, min, max) {
  return Math.min(Math.max(value, min), max);
};


/**
 * Return the hyperbolic cosine of a given number. The method will use the
 * native `Math.cosh` function if it is available, otherwise the hyperbolic
 * cosine will be calculated via the reference implementation of the Mozilla
 * developer network.
 *
 * @param {number} x X.
 * @return {number} Hyperbolic cosine of x.
 */
ol.math.cosh = (function() {
  // Wrapped in a iife, to save the overhead of checking for the native
  // implementation on every invocation.
  var cosh;
  if ('cosh' in Math) {
    // The environment supports the native Math.cosh function, use it…
    cosh = Math.cosh;
  } else {
    // … else, use the reference implementation of MDN:
    cosh = function(x) {
      var y = Math.exp(x);
      return (y + 1 / y) / 2;
    };
  }
  return cosh;
}());


/**
 * @param {number} x X.
 * @return {number} The smallest power of two greater than or equal to x.
 */
ol.math.roundUpToPowerOfTwo = function(x) {
  ol.asserts.assert(0 < x, 29); // `x` must be greater than `0`
  return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
};


/**
 * Returns the square of the closest distance between the point (x, y) and the
 * line segment (x1, y1) to (x2, y2).
 * @param {number} x X.
 * @param {number} y Y.
 * @param {number} x1 X1.
 * @param {number} y1 Y1.
 * @param {number} x2 X2.
 * @param {number} y2 Y2.
 * @return {number} Squared distance.
 */
ol.math.squaredSegmentDistance = function(x, y, x1, y1, x2, y2) {
  var dx = x2 - x1;
  var dy = y2 - y1;
  if (dx !== 0 || dy !== 0) {
    var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
    if (t > 1) {
      x1 = x2;
      y1 = y2;
    } else if (t > 0) {
      x1 += dx * t;
      y1 += dy * t;
    }
  }
  return ol.math.squaredDistance(x, y, x1, y1);
};


/**
 * Returns the square of the distance between the points (x1, y1) and (x2, y2).
 * @param {number} x1 X1.
 * @param {number} y1 Y1.
 * @param {number} x2 X2.
 * @param {number} y2 Y2.
 * @return {number} Squared distance.
 */
ol.math.squaredDistance = function(x1, y1, x2, y2) {
  var dx = x2 - x1;
  var dy = y2 - y1;
  return dx * dx + dy * dy;
};


/**
 * Solves system of linear equations using Gaussian elimination method.
 *
 * @param {Array.<Array.<number>>} mat Augmented matrix (n x n + 1 column)
 *                                     in row-major order.
 * @return {Array.<number>} The resulting vector.
 */
ol.math.solveLinearSystem = function(mat) {
  var n = mat.length;

  if (goog.DEBUG) {
    for (var row = 0; row < n; row++) {
      console.assert(mat[row].length == n + 1,
                          'every row should have correct number of columns');
    }
  }

  for (var i = 0; i < n; i++) {
    // Find max in the i-th column (ignoring i - 1 first rows)
    var maxRow = i;
    var maxEl = Math.abs(mat[i][i]);
    for (var r = i + 1; r < n; r++) {
      var absValue = Math.abs(mat[r][i]);
      if (absValue > maxEl) {
        maxEl = absValue;
        maxRow = r;
      }
    }

    if (maxEl === 0) {
      return null; // matrix is singular
    }

    // Swap max row with i-th (current) row
    var tmp = mat[maxRow];
    mat[maxRow] = mat[i];
    mat[i] = tmp;

    // Subtract the i-th row to make all the remaining rows 0 in the i-th column
    for (var j = i + 1; j < n; j++) {
      var coef = -mat[j][i] / mat[i][i];
      for (var k = i; k < n + 1; k++) {
        if (i == k) {
          mat[j][k] = 0;
        } else {
          mat[j][k] += coef * mat[i][k];
        }
      }
    }
  }

  // Solve Ax=b for upper triangular matrix A (mat)
  var x = new Array(n);
  for (var l = n - 1; l >= 0; l--) {
    x[l] = mat[l][n] / mat[l][l];
    for (var m = l - 1; m >= 0; m--) {
      mat[m][n] -= mat[m][l] * x[l];
    }
  }
  return x;
};


/**
 * Converts radians to to degrees.
 *
 * @param {number} angleInRadians Angle in radians.
 * @return {number} Angle in degrees.
 */
ol.math.toDegrees = function(angleInRadians) {
  return angleInRadians * 180 / Math.PI;
};


/**
 * Converts degrees to radians.
 *
 * @param {number} angleInDegrees Angle in degrees.
 * @return {number} Angle in radians.
 */
ol.math.toRadians = function(angleInDegrees) {
  return angleInDegrees * Math.PI / 180;
};

/**
 * Returns the modulo of a / b, depending on the sign of b.
 *
 * @param {number} a Dividend.
 * @param {number} b Divisor.
 * @return {number} Modulo.
 */
ol.math.modulo = function(a, b) {
  var r = a % b;
  return r * b < 0 ? r + b : r;
};

/**
 * Calculates the linearly interpolated value of x between a and b.
 *
 * @param {number} a Number
 * @param {number} b Number
 * @param {number} x Value to be interpolated.
 * @return {number} Interpolated value.
 */
ol.math.lerp = function(a, b, x) {
  return a + x * (b - a);
};

goog.provide('ol.CenterConstraint');

goog.require('ol.math');


/**
 * @param {ol.Extent} extent Extent.
 * @return {ol.CenterConstraintType} The constraint.
 */
ol.CenterConstraint.createExtent = function(extent) {
  return (
      /**
       * @param {ol.Coordinate|undefined} center Center.
       * @return {ol.Coordinate|undefined} Center.
       */
      function(center) {
        if (center) {
          return [
            ol.math.clamp(center[0], extent[0], extent[2]),
            ol.math.clamp(center[1], extent[1], extent[3])
          ];
        } else {
          return undefined;
        }
      });
};


/**
 * @param {ol.Coordinate|undefined} center Center.
 * @return {ol.Coordinate|undefined} Center.
 */
ol.CenterConstraint.none = function(center) {
  return center;
};

goog.provide('ol.Constraints');


/**
 * @constructor
 * @param {ol.CenterConstraintType} centerConstraint Center constraint.
 * @param {ol.ResolutionConstraintType} resolutionConstraint
 *     Resolution constraint.
 * @param {ol.RotationConstraintType} rotationConstraint
 *     Rotation constraint.
 */
ol.Constraints = function(centerConstraint, resolutionConstraint, rotationConstraint) {

  /**
   * @type {ol.CenterConstraintType}
   */
  this.center = centerConstraint;

  /**
   * @type {ol.ResolutionConstraintType}
   */
  this.resolution = resolutionConstraint;

  /**
   * @type {ol.RotationConstraintType}
   */
  this.rotation = rotationConstraint;

};

goog.provide('ol.obj');


/**
 * Polyfill for Object.assign().  Assigns enumerable and own properties from
 * one or more source objects to a target object.
 *
 * @see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
 * @param {!Object} target The target object.
 * @param {...Object} var_sources The source object(s).
 * @return {!Object} The modified target object.
 */
ol.obj.assign = (typeof Object.assign === 'function') ? Object.assign : function(target, var_sources) {
  if (target === undefined || target === null) {
    throw new TypeError('Cannot convert undefined or null to object');
  }

  var output = Object(target);
  for (var i = 1, ii = arguments.length; i < ii; ++i) {
    var source = arguments[i];
    if (source !== undefined && source !== null) {
      for (var key in source) {
        if (source.hasOwnProperty(key)) {
          output[key] = source[key];
        }
      }
    }
  }
  return output;
};


/**
 * Removes all properties from an object.
 * @param {Object} object The object to clear.
 */
ol.obj.clear = function(object) {
  for (var property in object) {
    delete object[property];
  }
};


/**
 * Get an array of property values from an object.
 * @param {Object<K,V>} object The object from which to get the values.
 * @return {!Array<V>} The property values.
 * @template K,V
 */
ol.obj.getValues = function(object) {
  var values = [];
  for (var property in object) {
    values.push(object[property]);
  }
  return values;
};


/**
 * Determine if an object has any properties.
 * @param {Object} object The object to check.
 * @return {boolean} The object is empty.
 */
ol.obj.isEmpty = function(object) {
  var property;
  for (property in object) {
    return false;
  }
  return !property;
};

goog.provide('ol.events');

goog.require('ol.obj');


/**
 * @param {ol.EventsKey} listenerObj Listener object.
 * @return {ol.EventsListenerFunctionType} Bound listener.
 */
ol.events.bindListener_ = function(listenerObj) {
  var boundListener = function(evt) {
    var listener = listenerObj.listener;
    var bindTo = listenerObj.bindTo || listenerObj.target;
    if (listenerObj.callOnce) {
      ol.events.unlistenByKey(listenerObj);
    }
    return listener.call(bindTo, evt);
  };
  listenerObj.boundListener = boundListener;
  return boundListener;
};


/**
 * Finds the matching {@link ol.EventsKey} in the given listener
 * array.
 *
 * @param {!Array<!ol.EventsKey>} listeners Array of listeners.
 * @param {!Function} listener The listener function.
 * @param {Object=} opt_this The `this` value inside the listener.
 * @param {boolean=} opt_setDeleteIndex Set the deleteIndex on the matching
 *     listener, for {@link ol.events.unlistenByKey}.
 * @return {ol.EventsKey|undefined} The matching listener object.
 * @private
 */
ol.events.findListener_ = function(listeners, listener, opt_this,
    opt_setDeleteIndex) {
  var listenerObj;
  for (var i = 0, ii = listeners.length; i < ii; ++i) {
    listenerObj = listeners[i];
    if (listenerObj.listener === listener &&
        listenerObj.bindTo === opt_this) {
      if (opt_setDeleteIndex) {
        listenerObj.deleteIndex = i;
      }
      return listenerObj;
    }
  }
  return undefined;
};


/**
 * @param {ol.EventTargetLike} target Target.
 * @param {string} type Type.
 * @return {Array.<ol.EventsKey>|undefined} Listeners.
 */
ol.events.getListeners = function(target, type) {
  var listenerMap = target.ol_lm;
  return listenerMap ? listenerMap[type] : undefined;
};


/**
 * Get the lookup of listeners.  If one does not exist on the target, it is
 * created.
 * @param {ol.EventTargetLike} target Target.
 * @return {!Object.<string, Array.<ol.EventsKey>>} Map of
 *     listeners by event type.
 * @private
 */
ol.events.getListenerMap_ = function(target) {
  var listenerMap = target.ol_lm;
  if (!listenerMap) {
    listenerMap = target.ol_lm = {};
  }
  return listenerMap;
};


/**
 * Clean up all listener objects of the given type.  All properties on the
 * listener objects will be removed, and if no listeners remain in the listener
 * map, it will be removed from the target.
 * @param {ol.EventTargetLike} target Target.
 * @param {string} type Type.
 * @private
 */
ol.events.removeListeners_ = function(target, type) {
  var listeners = ol.events.getListeners(target, type);
  if (listeners) {
    for (var i = 0, ii = listeners.length; i < ii; ++i) {
      target.removeEventListener(type, listeners[i].boundListener);
      ol.obj.clear(listeners[i]);
    }
    listeners.length = 0;
    var listenerMap = target.ol_lm;
    if (listenerMap) {
      delete listenerMap[type];
      if (Object.keys(listenerMap).length === 0) {
        delete target.ol_lm;
      }
    }
  }
};


/**
 * Registers an event listener on an event target. Inspired by
 * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
 *
 * This function efficiently binds a `listener` to a `this` object, and returns
 * a key for use with {@link ol.events.unlistenByKey}.
 *
 * @param {ol.EventTargetLike} target Event target.
 * @param {string} type Event type.
 * @param {ol.EventsListenerFunctionType} listener Listener.
 * @param {Object=} opt_this Object referenced by the `this` keyword in the
 *     listener. Default is the `target`.
 * @param {boolean=} opt_once If true, add the listener as one-off listener.
 * @return {ol.EventsKey} Unique key for the listener.
 */
ol.events.listen = function(target, type, listener, opt_this, opt_once) {
  var listenerMap = ol.events.getListenerMap_(target);
  var listeners = listenerMap[type];
  if (!listeners) {
    listeners = listenerMap[type] = [];
  }
  var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
      false);
  if (listenerObj) {
    if (!opt_once) {
      // Turn one-off listener into a permanent one.
      listenerObj.callOnce = false;
    }
  } else {
    listenerObj = /** @type {ol.EventsKey} */ ({
      bindTo: opt_this,
      callOnce: !!opt_once,
      listener: listener,
      target: target,
      type: type
    });
    target.addEventListener(type, ol.events.bindListener_(listenerObj));
    listeners.push(listenerObj);
  }

  return listenerObj;
};


/**
 * Registers a one-off event listener on an event target. Inspired by
 * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
 *
 * This function efficiently binds a `listener` as self-unregistering listener
 * to a `this` object, and returns a key for use with
 * {@link ol.events.unlistenByKey} in case the listener needs to be unregistered
 * before it is called.
 *
 * When {@link ol.events.listen} is called with the same arguments after this
 * function, the self-unregistering listener will be turned into a permanent
 * listener.
 *
 * @param {ol.EventTargetLike} target Event target.
 * @param {string} type Event type.
 * @param {ol.EventsListenerFunctionType} listener Listener.
 * @param {Object=} opt_this Object referenced by the `this` keyword in the
 *     listener. Default is the `target`.
 * @return {ol.EventsKey} Key for unlistenByKey.
 */
ol.events.listenOnce = function(target, type, listener, opt_this) {
  return ol.events.listen(target, type, listener, opt_this, true);
};


/**
 * Unregisters an event listener on an event target. Inspired by
 * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
 *
 * To return a listener, this function needs to be called with the exact same
 * arguments that were used for a previous {@link ol.events.listen} call.
 *
 * @param {ol.EventTargetLike} target Event target.
 * @param {string} type Event type.
 * @param {ol.EventsListenerFunctionType} listener Listener.
 * @param {Object=} opt_this Object referenced by the `this` keyword in the
 *     listener. Default is the `target`.
 */
ol.events.unlisten = function(target, type, listener, opt_this) {
  var listeners = ol.events.getListeners(target, type);
  if (listeners) {
    var listenerObj = ol.events.findListener_(listeners, listener, opt_this,
        true);
    if (listenerObj) {
      ol.events.unlistenByKey(listenerObj);
    }
  }
};


/**
 * Unregisters event listeners on an event target. Inspired by
 * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
 *
 * The argument passed to this function is the key returned from
 * {@link ol.events.listen} or {@link ol.events.listenOnce}.
 *
 * @param {ol.EventsKey} key The key.
 */
ol.events.unlistenByKey = function(key) {
  if (key && key.target) {
    key.target.removeEventListener(key.type, key.boundListener);
    var listeners = ol.events.getListeners(key.target, key.type);
    if (listeners) {
      var i = 'deleteIndex' in key ? key.deleteIndex : listeners.indexOf(key);
      if (i !== -1) {
        listeners.splice(i, 1);
      }
      if (listeners.length === 0) {
        ol.events.removeListeners_(key.target, key.type);
      }
    }
    ol.obj.clear(key);
  }
};


/**
 * Unregisters all event listeners on an event target. Inspired by
 * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html}
 *
 * @param {ol.EventTargetLike} target Target.
 */
ol.events.unlistenAll = function(target) {
  var listenerMap = ol.events.getListenerMap_(target);
  for (var type in listenerMap) {
    ol.events.removeListeners_(target, type);
  }
};

goog.provide('ol.Disposable');

goog.require('ol');

/**
 * Objects that need to clean up after themselves.
 * @constructor
 */
ol.Disposable = function() {};

/**
 * The object has already been disposed.
 * @type {boolean}
 * @private
 */
ol.Disposable.prototype.disposed_ = false;

/**
 * Clean up.
 */
ol.Disposable.prototype.dispose = function() {
  if (!this.disposed_) {
    this.disposed_ = true;
    this.disposeInternal();
  }
};

/**
 * Extension point for disposable objects.
 * @protected
 */
ol.Disposable.prototype.disposeInternal = ol.nullFunction;

goog.provide('ol.events.Event');


/**
 * @classdesc
 * Stripped down implementation of the W3C DOM Level 2 Event interface.
 * @see {@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-interface}
 *
 * This implementation only provides `type` and `target` properties, and
 * `stopPropagation` and `preventDefault` methods. It is meant as base class
 * for higher level events defined in the library, and works with
 * {@link ol.events.EventTarget}.
 *
 * @constructor
 * @implements {oli.events.Event}
 * @param {string} type Type.
 */
ol.events.Event = function(type) {

  /**
   * @type {boolean}
   */
  this.propagationStopped;

  /**
   * The event type.
   * @type {string}
   * @api stable
   */
  this.type = type;

  /**
   * The event target.
   * @type {Object}
   * @api stable
   */
  this.target = null;

};


/**
 * Stop event propagation.
 * @function
 * @api stable
 */
ol.events.Event.prototype.preventDefault =

/**
 * Stop event propagation.
 * @function
 * @api stable
 */
ol.events.Event.prototype.stopPropagation = function() {
  this.propagationStopped = true;
};


/**
 * @param {Event|ol.events.Event} evt Event
 */
ol.events.Event.stopPropagation = function(evt) {
  evt.stopPropagation();
};


/**
 * @param {Event|ol.events.Event} evt Event
 */
ol.events.Event.preventDefault = function(evt) {
  evt.preventDefault();
};

goog.provide('ol.events.EventTarget');

goog.require('ol');
goog.require('ol.Disposable');
goog.require('ol.events');
goog.require('ol.events.Event');


/**
 * @classdesc
 * A simplified implementation of the W3C DOM Level 2 EventTarget interface.
 * @see {@link https://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-EventTarget}
 *
 * There are two important simplifications compared to the specification:
 *
 * 1. The handling of `useCapture` in `addEventListener` and
 *    `removeEventListener`. There is no real capture model.
 * 2. The handling of `stopPropagation` and `preventDefault` on `dispatchEvent`.
 *    There is no event target hierarchy. When a listener calls
 *    `stopPropagation` or `preventDefault` on an event object, it means that no
 *    more listeners after this one will be called. Same as when the listener
 *    returns false.
 *
 * @constructor
 * @extends {ol.Disposable}
 */
ol.events.EventTarget = function() {

  ol.Disposable.call(this);

  /**
   * @private
   * @type {!Object.<string, number>}
   */
  this.pendingRemovals_ = {};

  /**
   * @private
   * @type {!Object.<string, number>}
   */
  this.dispatching_ = {};

  /**
   * @private
   * @type {!Object.<string, Array.<ol.EventsListenerFunctionType>>}
   */
  this.listeners_ = {};

};
ol.inherits(ol.events.EventTarget, ol.Disposable);


/**
 * @param {string} type Type.
 * @param {ol.EventsListenerFunctionType} listener Listener.
 */
ol.events.EventTarget.prototype.addEventListener = function(type, listener) {
  var listeners = this.listeners_[type];
  if (!listeners) {
    listeners = this.listeners_[type] = [];
  }
  if (listeners.indexOf(listener) === -1) {
    listeners.push(listener);
  }
};


/**
 * @param {{type: string,
 *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
 *     string} event Event or event type.
 * @return {boolean|undefined} `false` if anyone called preventDefault on the
 *     event object or if any of the listeners returned false.
 */
ol.events.EventTarget.prototype.dispatchEvent = function(event) {
  var evt = typeof event === 'string' ? new ol.events.Event(event) : event;
  var type = evt.type;
  evt.target = this;
  var listeners = this.listeners_[type];
  var propagate;
  if (listeners) {
    if (!(type in this.dispatching_)) {
      this.dispatching_[type] = 0;
      this.pendingRemovals_[type] = 0;
    }
    ++this.dispatching_[type];
    for (var i = 0, ii = listeners.length; i < ii; ++i) {
      if (listeners[i].call(this, evt) === false || evt.propagationStopped) {
        propagate = false;
        break;
      }
    }
    --this.dispatching_[type];
    if (this.dispatching_[type] === 0) {
      var pendingRemovals = this.pendingRemovals_[type];
      delete this.pendingRemovals_[type];
      while (pendingRemovals--) {
        this.removeEventListener(type, ol.nullFunction);
      }
      delete this.dispatching_[type];
    }
    return propagate;
  }
};


/**
 * @inheritDoc
 */
ol.events.EventTarget.prototype.disposeInternal = function() {
  ol.events.unlistenAll(this);
};


/**
 * Get the listeners for a specified event type. Listeners are returned in the
 * order that they will be called in.
 *
 * @param {string} type Type.
 * @return {Array.<ol.EventsListenerFunctionType>} Listeners.
 */
ol.events.EventTarget.prototype.getListeners = function(type) {
  return this.listeners_[type];
};


/**
 * @param {string=} opt_type Type. If not provided,
 *     `true` will be returned if this EventTarget has any listeners.
 * @return {boolean} Has listeners.
 */
ol.events.EventTarget.prototype.hasListener = function(opt_type) {
  return opt_type ?
      opt_type in this.listeners_ :
      Object.keys(this.listeners_).length > 0;
};


/**
 * @param {string} type Type.
 * @param {ol.EventsListenerFunctionType} listener Listener.
 */
ol.events.EventTarget.prototype.removeEventListener = function(type, listener) {
  var listeners = this.listeners_[type];
  if (listeners) {
    var index = listeners.indexOf(listener);
    goog.DEBUG && console.assert(index != -1, 'listener not found');
    if (type in this.pendingRemovals_) {
      // make listener a no-op, and remove later in #dispatchEvent()
      listeners[index] = ol.nullFunction;
      ++this.pendingRemovals_[type];
    } else {
      listeners.splice(index, 1);
      if (listeners.length === 0) {
        delete this.listeners_[type];
      }
    }
  }
};

goog.provide('ol.events.EventType');

/**
 * @enum {string}
 * @const
 */
ol.events.EventType = {
  /**
   * Generic change event. Triggered when the revision counter is increased.
   * @event ol.events.Event#change
   * @api
   */
  CHANGE: 'change',

  CLICK: 'click',
  DBLCLICK: 'dblclick',
  DRAGENTER: 'dragenter',
  DRAGOVER: 'dragover',
  DROP: 'drop',
  ERROR: 'error',
  KEYDOWN: 'keydown',
  KEYPRESS: 'keypress',
  LOAD: 'load',
  MOUSEDOWN: 'mousedown',
  MOUSEMOVE: 'mousemove',
  MOUSEOUT: 'mouseout',
  MOUSEUP: 'mouseup',
  MOUSEWHEEL: 'mousewheel',
  MSPOINTERDOWN: 'mspointerdown',
  RESIZE: 'resize',
  TOUCHSTART: 'touchstart',
  TOUCHMOVE: 'touchmove',
  TOUCHEND: 'touchend',
  WHEEL: 'wheel'
};

goog.provide('ol.Observable');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventTarget');
goog.require('ol.events.EventType');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * An event target providing convenient methods for listener registration
 * and unregistration. A generic `change` event is always available through
 * {@link ol.Observable#changed}.
 *
 * @constructor
 * @extends {ol.events.EventTarget}
 * @fires ol.events.Event
 * @struct
 * @api stable
 */
ol.Observable = function() {

  ol.events.EventTarget.call(this);

  /**
   * @private
   * @type {number}
   */
  this.revision_ = 0;

};
ol.inherits(ol.Observable, ol.events.EventTarget);


/**
 * Removes an event listener using the key returned by `on()` or `once()`.
 * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()`
 *     or `once()` (or an array of keys).
 * @api stable
 */
ol.Observable.unByKey = function(key) {
  if (Array.isArray(key)) {
    for (var i = 0, ii = key.length; i < ii; ++i) {
      ol.events.unlistenByKey(key[i]);
    }
  } else {
    ol.events.unlistenByKey(/** @type {ol.EventsKey} */ (key));
  }
};


/**
 * Increases the revision counter and dispatches a 'change' event.
 * @api
 */
ol.Observable.prototype.changed = function() {
  ++this.revision_;
  this.dispatchEvent(ol.events.EventType.CHANGE);
};


/**
 * Dispatches an event and calls all listeners listening for events
 * of this type. The event parameter can either be a string or an
 * Object with a `type` property.
 *
 * @param {{type: string,
 *     target: (EventTarget|ol.events.EventTarget|undefined)}|ol.events.Event|
 *     string} event Event object.
 * @function
 * @api
 */
ol.Observable.prototype.dispatchEvent;


/**
 * Get the version number for this object.  Each time the object is modified,
 * its version number will be incremented.
 * @return {number} Revision.
 * @api
 */
ol.Observable.prototype.getRevision = function() {
  return this.revision_;
};


/**
 * Listen for a certain type of event.
 * @param {string|Array.<string>} type The event type or array of event types.
 * @param {function(?): ?} listener The listener function.
 * @param {Object=} opt_this The object to use as `this` in `listener`.
 * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
 *     called with an array of event types as the first argument, the return
 *     will be an array of keys.
 * @api stable
 */
ol.Observable.prototype.on = function(type, listener, opt_this) {
  if (Array.isArray(type)) {
    var len = type.length;
    var keys = new Array(len);
    for (var i = 0; i < len; ++i) {
      keys[i] = ol.events.listen(this, type[i], listener, opt_this);
    }
    return keys;
  } else {
    return ol.events.listen(
        this, /** @type {string} */ (type), listener, opt_this);
  }
};


/**
 * Listen once for a certain type of event.
 * @param {string|Array.<string>} type The event type or array of event types.
 * @param {function(?): ?} listener The listener function.
 * @param {Object=} opt_this The object to use as `this` in `listener`.
 * @return {ol.EventsKey|Array.<ol.EventsKey>} Unique key for the listener. If
 *     called with an array of event types as the first argument, the return
 *     will be an array of keys.
 * @api stable
 */
ol.Observable.prototype.once = function(type, listener, opt_this) {
  if (Array.isArray(type)) {
    var len = type.length;
    var keys = new Array(len);
    for (var i = 0; i < len; ++i) {
      keys[i] = ol.events.listenOnce(this, type[i], listener, opt_this);
    }
    return keys;
  } else {
    return ol.events.listenOnce(
        this, /** @type {string} */ (type), listener, opt_this);
  }
};


/**
 * Unlisten for a certain type of event.
 * @param {string|Array.<string>} type The event type or array of event types.
 * @param {function(?): ?} listener The listener function.
 * @param {Object=} opt_this The object which was used as `this` by the
 * `listener`.
 * @api stable
 */
ol.Observable.prototype.un = function(type, listener, opt_this) {
  if (Array.isArray(type)) {
    for (var i = 0, ii = type.length; i < ii; ++i) {
      ol.events.unlisten(this, type[i], listener, opt_this);
    }
    return;
  } else {
    ol.events.unlisten(this, /** @type {string} */ (type), listener, opt_this);
  }
};


/**
 * Removes an event listener using the key returned by `on()` or `once()`.
 * Note that using the {@link ol.Observable.unByKey} static function is to
 * be preferred.
 * @param {ol.EventsKey|Array.<ol.EventsKey>} key The key returned by `on()`
 *     or `once()` (or an array of keys).
 * @function
 * @api stable
 */
ol.Observable.prototype.unByKey = ol.Observable.unByKey;

goog.provide('ol.Object');
goog.provide('ol.ObjectEvent');
goog.provide('ol.ObjectEventType');

goog.require('ol');
goog.require('ol.Observable');
goog.require('ol.events.Event');
goog.require('ol.obj');


/**
 * @enum {string}
 */
ol.ObjectEventType = {
  /**
   * Triggered when a property is changed.
   * @event ol.ObjectEvent#propertychange
   * @api stable
   */
  PROPERTYCHANGE: 'propertychange'
};


/**
 * @classdesc
 * Events emitted by {@link ol.Object} instances are instances of this type.
 *
 * @param {string} type The event type.
 * @param {string} key The property name.
 * @param {*} oldValue The old value for `key`.
 * @extends {ol.events.Event}
 * @implements {oli.ObjectEvent}
 * @constructor
 */
ol.ObjectEvent = function(type, key, oldValue) {
  ol.events.Event.call(this, type);

  /**
   * The name of the property whose value is changing.
   * @type {string}
   * @api stable
   */
  this.key = key;

  /**
   * The old value. To get the new value use `e.target.get(e.key)` where
   * `e` is the event object.
   * @type {*}
   * @api stable
   */
  this.oldValue = oldValue;

};
ol.inherits(ol.ObjectEvent, ol.events.Event);


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Most non-trivial classes inherit from this.
 *
 * This extends {@link ol.Observable} with observable properties, where each
 * property is observable as well as the object as a whole.
 *
 * Classes that inherit from this have pre-defined properties, to which you can
 * add your owns. The pre-defined properties are listed in this documentation as
 * 'Observable Properties', and have their own accessors; for example,
 * {@link ol.Map} has a `target` property, accessed with `getTarget()`  and
 * changed with `setTarget()`. Not all properties are however settable. There
 * are also general-purpose accessors `get()` and `set()`. For example,
 * `get('target')` is equivalent to `getTarget()`.
 *
 * The `set` accessors trigger a change event, and you can monitor this by
 * registering a listener. For example, {@link ol.View} has a `center`
 * property, so `view.on('change:center', function(evt) {...});` would call the
 * function whenever the value of the center property changes. Within the
 * function, `evt.target` would be the view, so `evt.target.getCenter()` would
 * return the new center.
 *
 * You can add your own observable properties with
 * `object.set('prop', 'value')`, and retrieve that with `object.get('prop')`.
 * You can listen for changes on that property value with
 * `object.on('change:prop', listener)`. You can get a list of all
 * properties with {@link ol.Object#getProperties object.getProperties()}.
 *
 * Note that the observable properties are separate from standard JS properties.
 * You can, for example, give your map object a title with
 * `map.title='New title'` and with `map.set('title', 'Another title')`. The
 * first will be a `hasOwnProperty`; the second will appear in
 * `getProperties()`. Only the second is observable.
 *
 * Properties can be deleted by using the unset method. E.g.
 * object.unset('foo').
 *
 * @constructor
 * @extends {ol.Observable}
 * @param {Object.<string, *>=} opt_values An object with key-value pairs.
 * @fires ol.ObjectEvent
 * @api
 */
ol.Object = function(opt_values) {
  ol.Observable.call(this);

  // Call ol.getUid to ensure that the order of objects' ids is the same as
  // the order in which they were created.  This also helps to ensure that
  // object properties are always added in the same order, which helps many
  // JavaScript engines generate faster code.
  ol.getUid(this);

  /**
   * @private
   * @type {!Object.<string, *>}
   */
  this.values_ = {};

  if (opt_values !== undefined) {
    this.setProperties(opt_values);
  }
};
ol.inherits(ol.Object, ol.Observable);


/**
 * @private
 * @type {Object.<string, string>}
 */
ol.Object.changeEventTypeCache_ = {};


/**
 * @param {string} key Key name.
 * @return {string} Change name.
 */
ol.Object.getChangeEventType = function(key) {
  return ol.Object.changeEventTypeCache_.hasOwnProperty(key) ?
      ol.Object.changeEventTypeCache_[key] :
      (ol.Object.changeEventTypeCache_[key] = 'change:' + key);
};


/**
 * Gets a value.
 * @param {string} key Key name.
 * @return {*} Value.
 * @api stable
 */
ol.Object.prototype.get = function(key) {
  var value;
  if (this.values_.hasOwnProperty(key)) {
    value = this.values_[key];
  }
  return value;
};


/**
 * Get a list of object property names.
 * @return {Array.<string>} List of property names.
 * @api stable
 */
ol.Object.prototype.getKeys = function() {
  return Object.keys(this.values_);
};


/**
 * Get an object of all property names and values.
 * @return {Object.<string, *>} Object.
 * @api stable
 */
ol.Object.prototype.getProperties = function() {
  return ol.obj.assign({}, this.values_);
};


/**
 * @param {string} key Key name.
 * @param {*} oldValue Old value.
 */
ol.Object.prototype.notify = function(key, oldValue) {
  var eventType;
  eventType = ol.Object.getChangeEventType(key);
  this.dispatchEvent(new ol.ObjectEvent(eventType, key, oldValue));
  eventType = ol.ObjectEventType.PROPERTYCHANGE;
  this.dispatchEvent(new ol.ObjectEvent(eventType, key, oldValue));
};


/**
 * Sets a value.
 * @param {string} key Key name.
 * @param {*} value Value.
 * @param {boolean=} opt_silent Update without triggering an event.
 * @api stable
 */
ol.Object.prototype.set = function(key, value, opt_silent) {
  if (opt_silent) {
    this.values_[key] = value;
  } else {
    var oldValue = this.values_[key];
    this.values_[key] = value;
    if (oldValue !== value) {
      this.notify(key, oldValue);
    }
  }
};


/**
 * Sets a collection of key-value pairs.  Note that this changes any existing
 * properties and adds new ones (it does not remove any existing properties).
 * @param {Object.<string, *>} values Values.
 * @param {boolean=} opt_silent Update without triggering an event.
 * @api stable
 */
ol.Object.prototype.setProperties = function(values, opt_silent) {
  var key;
  for (key in values) {
    this.set(key, values[key], opt_silent);
  }
};


/**
 * Unsets a property.
 * @param {string} key Key name.
 * @param {boolean=} opt_silent Unset without triggering an event.
 * @api stable
 */
ol.Object.prototype.unset = function(key, opt_silent) {
  if (key in this.values_) {
    var oldValue = this.values_[key];
    delete this.values_[key];
    if (!opt_silent) {
      this.notify(key, oldValue);
    }
  }
};

goog.provide('ol.array');


/**
 * Performs a binary search on the provided sorted list and returns the index of the item if found. If it can't be found it'll return -1.
 * https://github.com/darkskyapp/binary-search
 *
 * @param {Array.<*>} haystack Items to search through.
 * @param {*} needle The item to look for.
 * @param {Function=} opt_comparator Comparator function.
 * @return {number} The index of the item if found, -1 if not.
 */
ol.array.binarySearch = function(haystack, needle, opt_comparator) {
  var mid, cmp;
  var comparator = opt_comparator || ol.array.numberSafeCompareFunction;
  var low = 0;
  var high = haystack.length;
  var found = false;

  while (low < high) {
    /* Note that "(low + high) >>> 1" may overflow, and results in a typecast
     * to double (which gives the wrong results). */
    mid = low + (high - low >> 1);
    cmp = +comparator(haystack[mid], needle);

    if (cmp < 0.0) { /* Too low. */
      low  = mid + 1;

    } else { /* Key found or too high */
      high = mid;
      found = !cmp;
    }
  }

  /* Key not found. */
  return found ? low : ~low;
};

/**
 * @param {Array.<number>} arr Array.
 * @param {number} target Target.
 * @return {number} Index.
 */
ol.array.binaryFindNearest = function(arr, target) {
  var index = ol.array.binarySearch(arr, target,
      /**
       * @param {number} a A.
       * @param {number} b B.
       * @return {number} b minus a.
       */
      function(a, b) {
        return b - a;
      });
  if (index >= 0) {
    return index;
  } else if (index == -1) {
    return 0;
  } else if (index == -arr.length - 1) {
    return arr.length - 1;
  } else {
    var left = -index - 2;
    var right = -index - 1;
    if (arr[left] - target < target - arr[right]) {
      return left;
    } else {
      return right;
    }
  }
};


/**
 * Compare function for array sort that is safe for numbers.
 * @param {*} a The first object to be compared.
 * @param {*} b The second object to be compared.
 * @return {number} A negative number, zero, or a positive number as the first
 *     argument is less than, equal to, or greater than the second.
 */
ol.array.numberSafeCompareFunction = function(a, b) {
  return a > b ? 1 : a < b ? -1 : 0;
};


/**
 * Whether the array contains the given object.
 * @param {Array.<*>} arr The array to test for the presence of the element.
 * @param {*} obj The object for which to test.
 * @return {boolean} The object is in the array.
 */
ol.array.includes = function(arr, obj) {
  return arr.indexOf(obj) >= 0;
};


/**
 * @param {Array.<number>} arr Array.
 * @param {number} target Target.
 * @param {number} direction 0 means return the nearest, > 0
 *    means return the largest nearest, < 0 means return the
 *    smallest nearest.
 * @return {number} Index.
 */
ol.array.linearFindNearest = function(arr, target, direction) {
  var n = arr.length;
  if (arr[0] <= target) {
    return 0;
  } else if (target <= arr[n - 1]) {
    return n - 1;
  } else {
    var i;
    if (direction > 0) {
      for (i = 1; i < n; ++i) {
        if (arr[i] < target) {
          return i - 1;
        }
      }
    } else if (direction < 0) {
      for (i = 1; i < n; ++i) {
        if (arr[i] <= target) {
          return i;
        }
      }
    } else {
      for (i = 1; i < n; ++i) {
        if (arr[i] == target) {
          return i;
        } else if (arr[i] < target) {
          if (arr[i - 1] - target < target - arr[i]) {
            return i - 1;
          } else {
            return i;
          }
        }
      }
    }
    return n - 1;
  }
};


/**
 * @param {Array.<*>} arr Array.
 * @param {number} begin Begin index.
 * @param {number} end End index.
 */
ol.array.reverseSubArray = function(arr, begin, end) {
  goog.DEBUG && console.assert(begin >= 0,
      'Array begin index should be equal to or greater than 0');
  goog.DEBUG && console.assert(end < arr.length,
      'Array end index should be less than the array length');
  while (begin < end) {
    var tmp = arr[begin];
    arr[begin] = arr[end];
    arr[end] = tmp;
    ++begin;
    --end;
  }
};


/**
 * @param {Array.<*>} arr Array.
 * @return {!Array.<?>} Flattened Array.
 */
ol.array.flatten = function(arr) {
  var data = arr.reduce(function(flattened, value) {
    if (Array.isArray(value)) {
      return flattened.concat(ol.array.flatten(value));
    } else {
      return flattened.concat(value);
    }
  }, []);
  return data;
};


/**
 * @param {Array.<VALUE>} arr The array to modify.
 * @param {Array.<VALUE>|VALUE} data The elements or arrays of elements
 *     to add to arr.
 * @template VALUE
 */
ol.array.extend = function(arr, data) {
  var i;
  var extension = Array.isArray(data) ? data : [data];
  var length = extension.length;
  for (i = 0; i < length; i++) {
    arr[arr.length] = extension[i];
  }
};


/**
 * @param {Array.<VALUE>} arr The array to modify.
 * @param {VALUE} obj The element to remove.
 * @template VALUE
 * @return {boolean} If the element was removed.
 */
ol.array.remove = function(arr, obj) {
  var i = arr.indexOf(obj);
  var found = i > -1;
  if (found) {
    arr.splice(i, 1);
  }
  return found;
};


/**
 * @param {Array.<VALUE>} arr The array to search in.
 * @param {function(VALUE, number, ?) : boolean} func The function to compare.
 * @template VALUE
 * @return {VALUE} The element found.
 */
ol.array.find = function(arr, func) {
  var length = arr.length >>> 0;
  var value;

  for (var i = 0; i < length; i++) {
    value = arr[i];
    if (func(value, i, arr)) {
      return value;
    }
  }
  return null;
};


/**
 * @param {Array|Uint8ClampedArray} arr1 The first array to compare.
 * @param {Array|Uint8ClampedArray} arr2 The second array to compare.
 * @return {boolean} Whether the two arrays are equal.
 */
ol.array.equals = function(arr1, arr2) {
  var len1 = arr1.length;
  if (len1 !== arr2.length) {
    return false;
  }
  for (var i = 0; i < len1; i++) {
    if (arr1[i] !== arr2[i]) {
      return false;
    }
  }
  return true;
};


/**
 * @param {Array.<*>} arr The array to sort (modifies original).
 * @param {Function} compareFnc Comparison function.
 */
ol.array.stableSort = function(arr, compareFnc) {
  var length = arr.length;
  var tmp = Array(arr.length);
  var i;
  for (i = 0; i < length; i++) {
    tmp[i] = {index: i, value: arr[i]};
  }
  tmp.sort(function(a, b) {
    return compareFnc(a.value, b.value) || a.index - b.index;
  });
  for (i = 0; i < arr.length; i++) {
    arr[i] = tmp[i].value;
  }
};


/**
 * @param {Array.<*>} arr The array to search in.
 * @param {Function} func Comparison function.
 * @return {number} Return index.
 */
ol.array.findIndex = function(arr, func) {
  var index;
  var found = !arr.every(function(el, idx) {
    index = idx;
    return !func(el, idx, arr);
  });
  return found ? index : -1;
};


/**
 * @param {Array.<*>} arr The array to test.
 * @param {Function=} opt_func Comparison function.
 * @param {boolean=} opt_strict Strictly sorted (default false).
 * @return {boolean} Return index.
 */
ol.array.isSorted = function(arr, opt_func, opt_strict) {
  var compare = opt_func || ol.array.numberSafeCompareFunction;
  return arr.every(function(currentVal, index) {
    if (index === 0) {
      return true;
    }
    var res = compare(arr[index - 1], currentVal);
    return !(res > 0 || opt_strict && res === 0);
  });
};

goog.provide('ol.ResolutionConstraint');

goog.require('ol.array');
goog.require('ol.math');


/**
 * @param {Array.<number>} resolutions Resolutions.
 * @return {ol.ResolutionConstraintType} Zoom function.
 */
ol.ResolutionConstraint.createSnapToResolutions = function(resolutions) {
  return (
      /**
       * @param {number|undefined} resolution Resolution.
       * @param {number} delta Delta.
       * @param {number} direction Direction.
       * @return {number|undefined} Resolution.
       */
      function(resolution, delta, direction) {
        if (resolution !== undefined) {
          var z =
              ol.array.linearFindNearest(resolutions, resolution, direction);
          z = ol.math.clamp(z + delta, 0, resolutions.length - 1);
          var index = Math.floor(z);
          if (z != index && index < resolutions.length - 1) {
            var power = resolutions[index] / resolutions[index + 1];
            return resolutions[index] / Math.pow(power, z - index);
          } else {
            return resolutions[index];
          }
        } else {
          return undefined;
        }
      });
};


/**
 * @param {number} power Power.
 * @param {number} maxResolution Maximum resolution.
 * @param {number=} opt_maxLevel Maximum level.
 * @return {ol.ResolutionConstraintType} Zoom function.
 */
ol.ResolutionConstraint.createSnapToPower = function(power, maxResolution, opt_maxLevel) {
  return (
      /**
       * @param {number|undefined} resolution Resolution.
       * @param {number} delta Delta.
       * @param {number} direction Direction.
       * @return {number|undefined} Resolution.
       */
      function(resolution, delta, direction) {
        if (resolution !== undefined) {
          var offset = -direction / 2 + 0.5;
          var oldLevel = Math.floor(
              Math.log(maxResolution / resolution) / Math.log(power) + offset);
          var newLevel = Math.max(oldLevel + delta, 0);
          if (opt_maxLevel !== undefined) {
            newLevel = Math.min(newLevel, opt_maxLevel);
          }
          return maxResolution / Math.pow(power, newLevel);
        } else {
          return undefined;
        }
      });
};

goog.provide('ol.RotationConstraint');

goog.require('ol.math');


/**
 * @param {number|undefined} rotation Rotation.
 * @param {number} delta Delta.
 * @return {number|undefined} Rotation.
 */
ol.RotationConstraint.disable = function(rotation, delta) {
  if (rotation !== undefined) {
    return 0;
  } else {
    return undefined;
  }
};


/**
 * @param {number|undefined} rotation Rotation.
 * @param {number} delta Delta.
 * @return {number|undefined} Rotation.
 */
ol.RotationConstraint.none = function(rotation, delta) {
  if (rotation !== undefined) {
    return rotation + delta;
  } else {
    return undefined;
  }
};


/**
 * @param {number} n N.
 * @return {ol.RotationConstraintType} Rotation constraint.
 */
ol.RotationConstraint.createSnapToN = function(n) {
  var theta = 2 * Math.PI / n;
  return (
      /**
       * @param {number|undefined} rotation Rotation.
       * @param {number} delta Delta.
       * @return {number|undefined} Rotation.
       */
      function(rotation, delta) {
        if (rotation !== undefined) {
          rotation = Math.floor((rotation + delta) / theta + 0.5) * theta;
          return rotation;
        } else {
          return undefined;
        }
      });
};


/**
 * @param {number=} opt_tolerance Tolerance.
 * @return {ol.RotationConstraintType} Rotation constraint.
 */
ol.RotationConstraint.createSnapToZero = function(opt_tolerance) {
  var tolerance = opt_tolerance || ol.math.toRadians(5);
  return (
      /**
       * @param {number|undefined} rotation Rotation.
       * @param {number} delta Delta.
       * @return {number|undefined} Rotation.
       */
      function(rotation, delta) {
        if (rotation !== undefined) {
          if (Math.abs(rotation + delta) <= tolerance) {
            return 0;
          } else {
            return rotation + delta;
          }
        } else {
          return undefined;
        }
      });
};

goog.provide('ol.string');

/**
 * @param {number} number Number to be formatted
 * @param {number} width The desired width
 * @param {number=} opt_precision Precision of the output string (i.e. number of decimal places)
 * @returns {string} Formatted string
*/
ol.string.padNumber = function(number, width, opt_precision) {
  var numberString = opt_precision !== undefined ? number.toFixed(opt_precision) : '' + number;
  var decimal = numberString.indexOf('.');
  decimal = decimal === -1 ? numberString.length : decimal;
  return decimal > width ? numberString : new Array(1 + width - decimal).join('0') + numberString;
};

/**
 * Adapted from https://github.com/omichelsen/compare-versions/blob/master/index.js
 * @param {string|number} v1 First version
 * @param {string|number} v2 Second version
 * @returns {number} Value
 */
ol.string.compareVersions = function(v1, v2) {
  var s1 = ('' + v1).split('.');
  var s2 = ('' + v2).split('.');

  for (var i = 0; i < Math.max(s1.length, s2.length); i++) {
    var n1 = parseInt(s1[i] || '0', 10);
    var n2 = parseInt(s2[i] || '0', 10);

    if (n1 > n2) return 1;
    if (n2 > n1) return -1;
  }

  return 0;
};

goog.provide('ol.coordinate');

goog.require('ol.math');
goog.require('ol.string');


/**
 * Add `delta` to `coordinate`. `coordinate` is modified in place and returned
 * by the function.
 *
 * Example:
 *
 *     var coord = [7.85, 47.983333];
 *     ol.coordinate.add(coord, [-2, 4]);
 *     // coord is now [5.85, 51.983333]
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.Coordinate} delta Delta.
 * @return {ol.Coordinate} The input coordinate adjusted by the given delta.
 * @api stable
 */
ol.coordinate.add = function(coordinate, delta) {
  coordinate[0] += delta[0];
  coordinate[1] += delta[1];
  return coordinate;
};


/**
 * Calculates the point closest to the passed coordinate on the passed segment.
 * This is the foot of the perpendicular of the coordinate to the segment when
 * the foot is on the segment, or the closest segment coordinate when the foot
 * is outside the segment.
 *
 * @param {ol.Coordinate} coordinate The coordinate.
 * @param {Array.<ol.Coordinate>} segment The two coordinates of the segment.
 * @return {ol.Coordinate} The foot of the perpendicular of the coordinate to
 *     the segment.
 */
ol.coordinate.closestOnSegment = function(coordinate, segment) {
  var x0 = coordinate[0];
  var y0 = coordinate[1];
  var start = segment[0];
  var end = segment[1];
  var x1 = start[0];
  var y1 = start[1];
  var x2 = end[0];
  var y2 = end[1];
  var dx = x2 - x1;
  var dy = y2 - y1;
  var along = (dx === 0 && dy === 0) ? 0 :
      ((dx * (x0 - x1)) + (dy * (y0 - y1))) / ((dx * dx + dy * dy) || 0);
  var x, y;
  if (along <= 0) {
    x = x1;
    y = y1;
  } else if (along >= 1) {
    x = x2;
    y = y2;
  } else {
    x = x1 + along * dx;
    y = y1 + along * dy;
  }
  return [x, y];
};


/**
 * Returns a {@link ol.CoordinateFormatType} function that can be used to format
 * a {ol.Coordinate} to a string.
 *
 * Example without specifying the fractional digits:
 *
 *     var coord = [7.85, 47.983333];
 *     var stringifyFunc = ol.coordinate.createStringXY();
 *     var out = stringifyFunc(coord);
 *     // out is now '8, 48'
 *
 * Example with explicitly specifying 2 fractional digits:
 *
 *     var coord = [7.85, 47.983333];
 *     var stringifyFunc = ol.coordinate.createStringXY(2);
 *     var out = stringifyFunc(coord);
 *     // out is now '7.85, 47.98'
 *
 * @param {number=} opt_fractionDigits The number of digits to include
 *    after the decimal point. Default is `0`.
 * @return {ol.CoordinateFormatType} Coordinate format.
 * @api stable
 */
ol.coordinate.createStringXY = function(opt_fractionDigits) {
  return (
      /**
       * @param {ol.Coordinate|undefined} coordinate Coordinate.
       * @return {string} String XY.
       */
      function(coordinate) {
        return ol.coordinate.toStringXY(coordinate, opt_fractionDigits);
      });
};


/**
 * @private
 * @param {number} degrees Degrees.
 * @param {string} hemispheres Hemispheres.
 * @param {number=} opt_fractionDigits The number of digits to include
 *    after the decimal point. Default is `0`.
 * @return {string} String.
 */
ol.coordinate.degreesToStringHDMS_ = function(degrees, hemispheres, opt_fractionDigits) {
  var normalizedDegrees = ol.math.modulo(degrees + 180, 360) - 180;
  var x = Math.abs(3600 * normalizedDegrees);
  var dflPrecision = opt_fractionDigits || 0;
  return Math.floor(x / 3600) + '\u00b0 ' +
      ol.string.padNumber(Math.floor((x / 60) % 60), 2) + '\u2032 ' +
      ol.string.padNumber((x % 60), 2, dflPrecision) + '\u2033 ' +
      hemispheres.charAt(normalizedDegrees < 0 ? 1 : 0);
};


/**
 * Transforms the given {@link ol.Coordinate} to a string using the given string
 * template. The strings `{x}` and `{y}` in the template will be replaced with
 * the first and second coordinate values respectively.
 *
 * Example without specifying the fractional digits:
 *
 *     var coord = [7.85, 47.983333];
 *     var template = 'Coordinate is ({x}|{y}).';
 *     var out = ol.coordinate.format(coord, template);
 *     // out is now 'Coordinate is (8|48).'
 *
 * Example explicitly specifying the fractional digits:
 *
 *     var coord = [7.85, 47.983333];
 *     var template = 'Coordinate is ({x}|{y}).';
 *     var out = ol.coordinate.format(coord, template, 2);
 *     // out is now 'Coordinate is (7.85|47.98).'
 *
 * @param {ol.Coordinate|undefined} coordinate Coordinate.
 * @param {string} template A template string with `{x}` and `{y}` placeholders
 *     that will be replaced by first and second coordinate values.
 * @param {number=} opt_fractionDigits The number of digits to include
 *    after the decimal point. Default is `0`.
 * @return {string} Formatted coordinate.
 * @api stable
 */
ol.coordinate.format = function(coordinate, template, opt_fractionDigits) {
  if (coordinate) {
    return template
      .replace('{x}', coordinate[0].toFixed(opt_fractionDigits))
      .replace('{y}', coordinate[1].toFixed(opt_fractionDigits));
  } else {
    return '';
  }
};


/**
 * @param {ol.Coordinate} coordinate1 First coordinate.
 * @param {ol.Coordinate} coordinate2 Second coordinate.
 * @return {boolean} Whether the passed coordinates are equal.
 */
ol.coordinate.equals = function(coordinate1, coordinate2) {
  var equals = true;
  for (var i = coordinate1.length - 1; i >= 0; --i) {
    if (coordinate1[i] != coordinate2[i]) {
      equals = false;
      break;
    }
  }
  return equals;
};


/**
 * Rotate `coordinate` by `angle`. `coordinate` is modified in place and
 * returned by the function.
 *
 * Example:
 *
 *     var coord = [7.85, 47.983333];
 *     var rotateRadians = Math.PI / 2; // 90 degrees
 *     ol.coordinate.rotate(coord, rotateRadians);
 *     // coord is now [-47.983333, 7.85]
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} angle Angle in radian.
 * @return {ol.Coordinate} Coordinate.
 * @api stable
 */
ol.coordinate.rotate = function(coordinate, angle) {
  var cosAngle = Math.cos(angle);
  var sinAngle = Math.sin(angle);
  var x = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
  var y = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
  coordinate[0] = x;
  coordinate[1] = y;
  return coordinate;
};


/**
 * Scale `coordinate` by `scale`. `coordinate` is modified in place and returned
 * by the function.
 *
 * Example:
 *
 *     var coord = [7.85, 47.983333];
 *     var scale = 1.2;
 *     ol.coordinate.scale(coord, scale);
 *     // coord is now [9.42, 57.5799996]
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} scale Scale factor.
 * @return {ol.Coordinate} Coordinate.
 */
ol.coordinate.scale = function(coordinate, scale) {
  coordinate[0] *= scale;
  coordinate[1] *= scale;
  return coordinate;
};


/**
 * Subtract `delta` to `coordinate`. `coordinate` is modified in place and
 * returned by the function.
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.Coordinate} delta Delta.
 * @return {ol.Coordinate} Coordinate.
 */
ol.coordinate.sub = function(coordinate, delta) {
  coordinate[0] -= delta[0];
  coordinate[1] -= delta[1];
  return coordinate;
};


/**
 * @param {ol.Coordinate} coord1 First coordinate.
 * @param {ol.Coordinate} coord2 Second coordinate.
 * @return {number} Squared distance between coord1 and coord2.
 */
ol.coordinate.squaredDistance = function(coord1, coord2) {
  var dx = coord1[0] - coord2[0];
  var dy = coord1[1] - coord2[1];
  return dx * dx + dy * dy;
};


/**
 * Calculate the squared distance from a coordinate to a line segment.
 *
 * @param {ol.Coordinate} coordinate Coordinate of the point.
 * @param {Array.<ol.Coordinate>} segment Line segment (2 coordinates).
 * @return {number} Squared distance from the point to the line segment.
 */
ol.coordinate.squaredDistanceToSegment = function(coordinate, segment) {
  return ol.coordinate.squaredDistance(coordinate,
      ol.coordinate.closestOnSegment(coordinate, segment));
};


/**
 * Format a geographic coordinate with the hemisphere, degrees, minutes, and
 * seconds.
 *
 * Example without specifying fractional digits:
 *
 *     var coord = [7.85, 47.983333];
 *     var out = ol.coordinate.toStringHDMS(coord);
 *     // out is now '47° 58′ 60″ N 7° 50′ 60″ E'
 *
 * Example explicitly specifying 1 fractional digit:
 *
 *     var coord = [7.85, 47.983333];
 *     var out = ol.coordinate.toStringHDMS(coord, 1);
 *     // out is now '47° 58′ 60.0″ N 7° 50′ 60.0″ E'
 *
 * @param {ol.Coordinate|undefined} coordinate Coordinate.
 * @param {number=} opt_fractionDigits The number of digits to include
 *    after the decimal point. Default is `0`.
 * @return {string} Hemisphere, degrees, minutes and seconds.
 * @api stable
 */
ol.coordinate.toStringHDMS = function(coordinate, opt_fractionDigits) {
  if (coordinate) {
    return ol.coordinate.degreesToStringHDMS_(coordinate[1], 'NS', opt_fractionDigits) + ' ' +
        ol.coordinate.degreesToStringHDMS_(coordinate[0], 'EW', opt_fractionDigits);
  } else {
    return '';
  }
};


/**
 * Format a coordinate as a comma delimited string.
 *
 * Example without specifying fractional digits:
 *
 *     var coord = [7.85, 47.983333];
 *     var out = ol.coordinate.toStringXY(coord);
 *     // out is now '8, 48'
 *
 * Example explicitly specifying 1 fractional digit:
 *
 *     var coord = [7.85, 47.983333];
 *     var out = ol.coordinate.toStringXY(coord, 1);
 *     // out is now '7.8, 48.0'
 *
 * @param {ol.Coordinate|undefined} coordinate Coordinate.
 * @param {number=} opt_fractionDigits The number of digits to include
 *    after the decimal point. Default is `0`.
 * @return {string} XY.
 * @api stable
 */
ol.coordinate.toStringXY = function(coordinate, opt_fractionDigits) {
  return ol.coordinate.format(coordinate, '{x}, {y}', opt_fractionDigits);
};


/**
 * Create an ol.Coordinate from an Array and take into account axis order.
 *
 * Examples:
 *
 *     var northCoord = ol.coordinate.fromProjectedArray([1, 2], 'n');
 *     // northCoord is now [2, 1]
 *
 *     var eastCoord = ol.coordinate.fromProjectedArray([1, 2], 'e');
 *     // eastCoord is now [1, 2]
 *
 * @param {Array} array The array with coordinates.
 * @param {string} axis the axis info.
 * @return {ol.Coordinate} The coordinate created.
 */
ol.coordinate.fromProjectedArray = function(array, axis) {
  var firstAxis = axis.charAt(0);
  if (firstAxis === 'n' || firstAxis === 's') {
    return [array[1], array[0]];
  } else {
    return array;
  }
};

goog.provide('ol.extent');
goog.provide('ol.extent.Corner');
goog.provide('ol.extent.Relationship');

goog.require('ol.asserts');


/**
 * Extent corner.
 * @enum {string}
 */
ol.extent.Corner = {
  BOTTOM_LEFT: 'bottom-left',
  BOTTOM_RIGHT: 'bottom-right',
  TOP_LEFT: 'top-left',
  TOP_RIGHT: 'top-right'
};


/**
 * Relationship to an extent.
 * @enum {number}
 */
ol.extent.Relationship = {
  UNKNOWN: 0,
  INTERSECTING: 1,
  ABOVE: 2,
  RIGHT: 4,
  BELOW: 8,
  LEFT: 16
};


/**
 * Build an extent that includes all given coordinates.
 *
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @return {ol.Extent} Bounding extent.
 * @api stable
 */
ol.extent.boundingExtent = function(coordinates) {
  var extent = ol.extent.createEmpty();
  for (var i = 0, ii = coordinates.length; i < ii; ++i) {
    ol.extent.extendCoordinate(extent, coordinates[i]);
  }
  return extent;
};


/**
 * @param {Array.<number>} xs Xs.
 * @param {Array.<number>} ys Ys.
 * @param {ol.Extent=} opt_extent Destination extent.
 * @private
 * @return {ol.Extent} Extent.
 */
ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) {
  goog.DEBUG && console.assert(xs.length > 0, 'xs length should be larger than 0');
  goog.DEBUG && console.assert(ys.length > 0, 'ys length should be larger than 0');
  var minX = Math.min.apply(null, xs);
  var minY = Math.min.apply(null, ys);
  var maxX = Math.max.apply(null, xs);
  var maxY = Math.max.apply(null, ys);
  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
};


/**
 * Return extent increased by the provided value.
 * @param {ol.Extent} extent Extent.
 * @param {number} value The amount by which the extent should be buffered.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 * @api stable
 */
ol.extent.buffer = function(extent, value, opt_extent) {
  if (opt_extent) {
    opt_extent[0] = extent[0] - value;
    opt_extent[1] = extent[1] - value;
    opt_extent[2] = extent[2] + value;
    opt_extent[3] = extent[3] + value;
    return opt_extent;
  } else {
    return [
      extent[0] - value,
      extent[1] - value,
      extent[2] + value,
      extent[3] + value
    ];
  }
};


/**
 * Creates a clone of an extent.
 *
 * @param {ol.Extent} extent Extent to clone.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} The clone.
 */
ol.extent.clone = function(extent, opt_extent) {
  if (opt_extent) {
    opt_extent[0] = extent[0];
    opt_extent[1] = extent[1];
    opt_extent[2] = extent[2];
    opt_extent[3] = extent[3];
    return opt_extent;
  } else {
    return extent.slice();
  }
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} x X.
 * @param {number} y Y.
 * @return {number} Closest squared distance.
 */
ol.extent.closestSquaredDistanceXY = function(extent, x, y) {
  var dx, dy;
  if (x < extent[0]) {
    dx = extent[0] - x;
  } else if (extent[2] < x) {
    dx = x - extent[2];
  } else {
    dx = 0;
  }
  if (y < extent[1]) {
    dy = extent[1] - y;
  } else if (extent[3] < y) {
    dy = y - extent[3];
  } else {
    dy = 0;
  }
  return dx * dx + dy * dy;
};


/**
 * Check if the passed coordinate is contained or on the edge of the extent.
 *
 * @param {ol.Extent} extent Extent.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @return {boolean} The coordinate is contained in the extent.
 * @api stable
 */
ol.extent.containsCoordinate = function(extent, coordinate) {
  return ol.extent.containsXY(extent, coordinate[0], coordinate[1]);
};


/**
 * Check if one extent contains another.
 *
 * An extent is deemed contained if it lies completely within the other extent,
 * including if they share one or more edges.
 *
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent 2.
 * @return {boolean} The second extent is contained by or on the edge of the
 *     first.
 * @api stable
 */
ol.extent.containsExtent = function(extent1, extent2) {
  return extent1[0] <= extent2[0] && extent2[2] <= extent1[2] &&
      extent1[1] <= extent2[1] && extent2[3] <= extent1[3];
};


/**
 * Check if the passed coordinate is contained or on the edge of the extent.
 *
 * @param {ol.Extent} extent Extent.
 * @param {number} x X coordinate.
 * @param {number} y Y coordinate.
 * @return {boolean} The x, y values are contained in the extent.
 * @api stable
 */
ol.extent.containsXY = function(extent, x, y) {
  return extent[0] <= x && x <= extent[2] && extent[1] <= y && y <= extent[3];
};


/**
 * Get the relationship between a coordinate and extent.
 * @param {ol.Extent} extent The extent.
 * @param {ol.Coordinate} coordinate The coordinate.
 * @return {number} The relationship (bitwise compare with
 *     ol.extent.Relationship).
 */
ol.extent.coordinateRelationship = function(extent, coordinate) {
  var minX = extent[0];
  var minY = extent[1];
  var maxX = extent[2];
  var maxY = extent[3];
  var x = coordinate[0];
  var y = coordinate[1];
  var relationship = ol.extent.Relationship.UNKNOWN;
  if (x < minX) {
    relationship = relationship | ol.extent.Relationship.LEFT;
  } else if (x > maxX) {
    relationship = relationship | ol.extent.Relationship.RIGHT;
  }
  if (y < minY) {
    relationship = relationship | ol.extent.Relationship.BELOW;
  } else if (y > maxY) {
    relationship = relationship | ol.extent.Relationship.ABOVE;
  }
  if (relationship === ol.extent.Relationship.UNKNOWN) {
    relationship = ol.extent.Relationship.INTERSECTING;
  }
  return relationship;
};


/**
 * Create an empty extent.
 * @return {ol.Extent} Empty extent.
 * @api stable
 */
ol.extent.createEmpty = function() {
  return [Infinity, Infinity, -Infinity, -Infinity];
};


/**
 * Create a new extent or update the provided extent.
 * @param {number} minX Minimum X.
 * @param {number} minY Minimum Y.
 * @param {number} maxX Maximum X.
 * @param {number} maxY Maximum Y.
 * @param {ol.Extent=} opt_extent Destination extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.createOrUpdate = function(minX, minY, maxX, maxY, opt_extent) {
  if (opt_extent) {
    opt_extent[0] = minX;
    opt_extent[1] = minY;
    opt_extent[2] = maxX;
    opt_extent[3] = maxY;
    return opt_extent;
  } else {
    return [minX, minY, maxX, maxY];
  }
};


/**
 * Create a new empty extent or make the provided one empty.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.createOrUpdateEmpty = function(opt_extent) {
  return ol.extent.createOrUpdate(
      Infinity, Infinity, -Infinity, -Infinity, opt_extent);
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.createOrUpdateFromCoordinate = function(coordinate, opt_extent) {
  var x = coordinate[0];
  var y = coordinate[1];
  return ol.extent.createOrUpdate(x, y, x, y, opt_extent);
};


/**
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.createOrUpdateFromCoordinates = function(coordinates, opt_extent) {
  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
  return ol.extent.extendCoordinates(extent, coordinates);
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.createOrUpdateFromFlatCoordinates = function(flatCoordinates, offset, end, stride, opt_extent) {
  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
  return ol.extent.extendFlatCoordinates(
      extent, flatCoordinates, offset, end, stride);
};


/**
 * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.createOrUpdateFromRings = function(rings, opt_extent) {
  var extent = ol.extent.createOrUpdateEmpty(opt_extent);
  return ol.extent.extendRings(extent, rings);
};


/**
 * Empty an extent in place.
 * @param {ol.Extent} extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.empty = function(extent) {
  extent[0] = extent[1] = Infinity;
  extent[2] = extent[3] = -Infinity;
  return extent;
};


/**
 * Determine if two extents are equivalent.
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent 2.
 * @return {boolean} The two extents are equivalent.
 * @api stable
 */
ol.extent.equals = function(extent1, extent2) {
  return extent1[0] == extent2[0] && extent1[2] == extent2[2] &&
      extent1[1] == extent2[1] && extent1[3] == extent2[3];
};


/**
 * Modify an extent to include another extent.
 * @param {ol.Extent} extent1 The extent to be modified.
 * @param {ol.Extent} extent2 The extent that will be included in the first.
 * @return {ol.Extent} A reference to the first (extended) extent.
 * @api stable
 */
ol.extent.extend = function(extent1, extent2) {
  if (extent2[0] < extent1[0]) {
    extent1[0] = extent2[0];
  }
  if (extent2[2] > extent1[2]) {
    extent1[2] = extent2[2];
  }
  if (extent2[1] < extent1[1]) {
    extent1[1] = extent2[1];
  }
  if (extent2[3] > extent1[3]) {
    extent1[3] = extent2[3];
  }
  return extent1;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.Coordinate} coordinate Coordinate.
 */
ol.extent.extendCoordinate = function(extent, coordinate) {
  if (coordinate[0] < extent[0]) {
    extent[0] = coordinate[0];
  }
  if (coordinate[0] > extent[2]) {
    extent[2] = coordinate[0];
  }
  if (coordinate[1] < extent[1]) {
    extent[1] = coordinate[1];
  }
  if (coordinate[1] > extent[3]) {
    extent[3] = coordinate[1];
  }
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @return {ol.Extent} Extent.
 */
ol.extent.extendCoordinates = function(extent, coordinates) {
  var i, ii;
  for (i = 0, ii = coordinates.length; i < ii; ++i) {
    ol.extent.extendCoordinate(extent, coordinates[i]);
  }
  return extent;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @return {ol.Extent} Extent.
 */
ol.extent.extendFlatCoordinates = function(extent, flatCoordinates, offset, end, stride) {
  for (; offset < end; offset += stride) {
    ol.extent.extendXY(
        extent, flatCoordinates[offset], flatCoordinates[offset + 1]);
  }
  return extent;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {Array.<Array.<ol.Coordinate>>} rings Rings.
 * @return {ol.Extent} Extent.
 */
ol.extent.extendRings = function(extent, rings) {
  var i, ii;
  for (i = 0, ii = rings.length; i < ii; ++i) {
    ol.extent.extendCoordinates(extent, rings[i]);
  }
  return extent;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} x X.
 * @param {number} y Y.
 */
ol.extent.extendXY = function(extent, x, y) {
  extent[0] = Math.min(extent[0], x);
  extent[1] = Math.min(extent[1], y);
  extent[2] = Math.max(extent[2], x);
  extent[3] = Math.max(extent[3], y);
};


/**
 * This function calls `callback` for each corner of the extent. If the
 * callback returns a truthy value the function returns that value
 * immediately. Otherwise the function returns `false`.
 * @param {ol.Extent} extent Extent.
 * @param {function(this:T, ol.Coordinate): S} callback Callback.
 * @param {T=} opt_this Value to use as `this` when executing `callback`.
 * @return {S|boolean} Value.
 * @template S, T
 */
ol.extent.forEachCorner = function(extent, callback, opt_this) {
  var val;
  val = callback.call(opt_this, ol.extent.getBottomLeft(extent));
  if (val) {
    return val;
  }
  val = callback.call(opt_this, ol.extent.getBottomRight(extent));
  if (val) {
    return val;
  }
  val = callback.call(opt_this, ol.extent.getTopRight(extent));
  if (val) {
    return val;
  }
  val = callback.call(opt_this, ol.extent.getTopLeft(extent));
  if (val) {
    return val;
  }
  return false;
};


/**
 * @param {ol.Extent} extent Extent.
 * @return {number} Area.
 */
ol.extent.getArea = function(extent) {
  var area = 0;
  if (!ol.extent.isEmpty(extent)) {
    area = ol.extent.getWidth(extent) * ol.extent.getHeight(extent);
  }
  return area;
};


/**
 * Get the bottom left coordinate of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {ol.Coordinate} Bottom left coordinate.
 * @api stable
 */
ol.extent.getBottomLeft = function(extent) {
  return [extent[0], extent[1]];
};


/**
 * Get the bottom right coordinate of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {ol.Coordinate} Bottom right coordinate.
 * @api stable
 */
ol.extent.getBottomRight = function(extent) {
  return [extent[2], extent[1]];
};


/**
 * Get the center coordinate of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {ol.Coordinate} Center.
 * @api stable
 */
ol.extent.getCenter = function(extent) {
  return [(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2];
};


/**
 * Get a corner coordinate of an extent.
 * @param {ol.Extent} extent Extent.
 * @param {ol.extent.Corner} corner Corner.
 * @return {ol.Coordinate} Corner coordinate.
 */
ol.extent.getCorner = function(extent, corner) {
  var coordinate;
  if (corner === ol.extent.Corner.BOTTOM_LEFT) {
    coordinate = ol.extent.getBottomLeft(extent);
  } else if (corner === ol.extent.Corner.BOTTOM_RIGHT) {
    coordinate = ol.extent.getBottomRight(extent);
  } else if (corner === ol.extent.Corner.TOP_LEFT) {
    coordinate = ol.extent.getTopLeft(extent);
  } else if (corner === ol.extent.Corner.TOP_RIGHT) {
    coordinate = ol.extent.getTopRight(extent);
  } else {
    ol.asserts.assert(false, 13); // Invalid corner
  }
  return /** @type {!ol.Coordinate} */ (coordinate);
};


/**
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent 2.
 * @return {number} Enlarged area.
 */
ol.extent.getEnlargedArea = function(extent1, extent2) {
  var minX = Math.min(extent1[0], extent2[0]);
  var minY = Math.min(extent1[1], extent2[1]);
  var maxX = Math.max(extent1[2], extent2[2]);
  var maxY = Math.max(extent1[3], extent2[3]);
  return (maxX - minX) * (maxY - minY);
};


/**
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {ol.Extent=} opt_extent Destination extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.getForViewAndSize = function(center, resolution, rotation, size, opt_extent) {
  var dx = resolution * size[0] / 2;
  var dy = resolution * size[1] / 2;
  var cosRotation = Math.cos(rotation);
  var sinRotation = Math.sin(rotation);
  var xCos = dx * cosRotation;
  var xSin = dx * sinRotation;
  var yCos = dy * cosRotation;
  var ySin = dy * sinRotation;
  var x = center[0];
  var y = center[1];
  var x0 = x - xCos + ySin;
  var x1 = x - xCos - ySin;
  var x2 = x + xCos - ySin;
  var x3 = x + xCos + ySin;
  var y0 = y - xSin - yCos;
  var y1 = y - xSin + yCos;
  var y2 = y + xSin + yCos;
  var y3 = y + xSin - yCos;
  return ol.extent.createOrUpdate(
      Math.min(x0, x1, x2, x3), Math.min(y0, y1, y2, y3),
      Math.max(x0, x1, x2, x3), Math.max(y0, y1, y2, y3),
      opt_extent);
};


/**
 * Get the height of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {number} Height.
 * @api stable
 */
ol.extent.getHeight = function(extent) {
  return extent[3] - extent[1];
};


/**
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent 2.
 * @return {number} Intersection area.
 */
ol.extent.getIntersectionArea = function(extent1, extent2) {
  var intersection = ol.extent.getIntersection(extent1, extent2);
  return ol.extent.getArea(intersection);
};


/**
 * Get the intersection of two extents.
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent 2.
 * @param {ol.Extent=} opt_extent Optional extent to populate with intersection.
 * @return {ol.Extent} Intersecting extent.
 * @api stable
 */
ol.extent.getIntersection = function(extent1, extent2, opt_extent) {
  var intersection = opt_extent ? opt_extent : ol.extent.createEmpty();
  if (ol.extent.intersects(extent1, extent2)) {
    if (extent1[0] > extent2[0]) {
      intersection[0] = extent1[0];
    } else {
      intersection[0] = extent2[0];
    }
    if (extent1[1] > extent2[1]) {
      intersection[1] = extent1[1];
    } else {
      intersection[1] = extent2[1];
    }
    if (extent1[2] < extent2[2]) {
      intersection[2] = extent1[2];
    } else {
      intersection[2] = extent2[2];
    }
    if (extent1[3] < extent2[3]) {
      intersection[3] = extent1[3];
    } else {
      intersection[3] = extent2[3];
    }
  }
  return intersection;
};


/**
 * @param {ol.Extent} extent Extent.
 * @return {number} Margin.
 */
ol.extent.getMargin = function(extent) {
  return ol.extent.getWidth(extent) + ol.extent.getHeight(extent);
};


/**
 * Get the size (width, height) of an extent.
 * @param {ol.Extent} extent The extent.
 * @return {ol.Size} The extent size.
 * @api stable
 */
ol.extent.getSize = function(extent) {
  return [extent[2] - extent[0], extent[3] - extent[1]];
};


/**
 * Get the top left coordinate of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {ol.Coordinate} Top left coordinate.
 * @api stable
 */
ol.extent.getTopLeft = function(extent) {
  return [extent[0], extent[3]];
};


/**
 * Get the top right coordinate of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {ol.Coordinate} Top right coordinate.
 * @api stable
 */
ol.extent.getTopRight = function(extent) {
  return [extent[2], extent[3]];
};


/**
 * Get the width of an extent.
 * @param {ol.Extent} extent Extent.
 * @return {number} Width.
 * @api stable
 */
ol.extent.getWidth = function(extent) {
  return extent[2] - extent[0];
};


/**
 * Determine if one extent intersects another.
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent.
 * @return {boolean} The two extents intersect.
 * @api stable
 */
ol.extent.intersects = function(extent1, extent2) {
  return extent1[0] <= extent2[2] &&
      extent1[2] >= extent2[0] &&
      extent1[1] <= extent2[3] &&
      extent1[3] >= extent2[1];
};


/**
 * Determine if an extent is empty.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} Is empty.
 * @api stable
 */
ol.extent.isEmpty = function(extent) {
  return extent[2] < extent[0] || extent[3] < extent[1];
};


/**
 * @param {ol.Extent} extent Extent.
 * @return {boolean} Is infinite.
 */
ol.extent.isInfinite = function(extent) {
  return extent[0] == -Infinity || extent[1] == -Infinity ||
      extent[2] == Infinity || extent[3] == Infinity;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @return {ol.Coordinate} Coordinate.
 */
ol.extent.normalize = function(extent, coordinate) {
  return [
    (coordinate[0] - extent[0]) / (extent[2] - extent[0]),
    (coordinate[1] - extent[1]) / (extent[3] - extent[1])
  ];
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} Extent.
 */
ol.extent.returnOrUpdate = function(extent, opt_extent) {
  if (opt_extent) {
    opt_extent[0] = extent[0];
    opt_extent[1] = extent[1];
    opt_extent[2] = extent[2];
    opt_extent[3] = extent[3];
    return opt_extent;
  } else {
    return extent;
  }
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} value Value.
 */
ol.extent.scaleFromCenter = function(extent, value) {
  var deltaX = ((extent[2] - extent[0]) / 2) * (value - 1);
  var deltaY = ((extent[3] - extent[1]) / 2) * (value - 1);
  extent[0] -= deltaX;
  extent[2] += deltaX;
  extent[1] -= deltaY;
  extent[3] += deltaY;
};


/**
 * Determine if the segment between two coordinates intersects (crosses,
 * touches, or is contained by) the provided extent.
 * @param {ol.Extent} extent The extent.
 * @param {ol.Coordinate} start Segment start coordinate.
 * @param {ol.Coordinate} end Segment end coordinate.
 * @return {boolean} The segment intersects the extent.
 */
ol.extent.intersectsSegment = function(extent, start, end) {
  var intersects = false;
  var startRel = ol.extent.coordinateRelationship(extent, start);
  var endRel = ol.extent.coordinateRelationship(extent, end);
  if (startRel === ol.extent.Relationship.INTERSECTING ||
      endRel === ol.extent.Relationship.INTERSECTING) {
    intersects = true;
  } else {
    var minX = extent[0];
    var minY = extent[1];
    var maxX = extent[2];
    var maxY = extent[3];
    var startX = start[0];
    var startY = start[1];
    var endX = end[0];
    var endY = end[1];
    var slope = (endY - startY) / (endX - startX);
    var x, y;
    if (!!(endRel & ol.extent.Relationship.ABOVE) &&
        !(startRel & ol.extent.Relationship.ABOVE)) {
      // potentially intersects top
      x = endX - ((endY - maxY) / slope);
      intersects = x >= minX && x <= maxX;
    }
    if (!intersects && !!(endRel & ol.extent.Relationship.RIGHT) &&
        !(startRel & ol.extent.Relationship.RIGHT)) {
      // potentially intersects right
      y = endY - ((endX - maxX) * slope);
      intersects = y >= minY && y <= maxY;
    }
    if (!intersects && !!(endRel & ol.extent.Relationship.BELOW) &&
        !(startRel & ol.extent.Relationship.BELOW)) {
      // potentially intersects bottom
      x = endX - ((endY - minY) / slope);
      intersects = x >= minX && x <= maxX;
    }
    if (!intersects && !!(endRel & ol.extent.Relationship.LEFT) &&
        !(startRel & ol.extent.Relationship.LEFT)) {
      // potentially intersects left
      y = endY - ((endX - minX) * slope);
      intersects = y >= minY && y <= maxY;
    }

  }
  return intersects;
};


/**
 * @param {ol.Extent} extent1 Extent 1.
 * @param {ol.Extent} extent2 Extent 2.
 * @return {boolean} Touches.
 */
ol.extent.touches = function(extent1, extent2) {
  var intersects = ol.extent.intersects(extent1, extent2);
  return intersects &&
      (extent1[0] == extent2[2] || extent1[2] == extent2[0] ||
       extent1[1] == extent2[3] || extent1[3] == extent2[1]);
};


/**
 * Apply a transform function to the extent.
 * @param {ol.Extent} extent Extent.
 * @param {ol.TransformFunction} transformFn Transform function.  Called with
 * [minX, minY, maxX, maxY] extent coordinates.
 * @param {ol.Extent=} opt_extent Destination extent.
 * @return {ol.Extent} Extent.
 * @api stable
 */
ol.extent.applyTransform = function(extent, transformFn, opt_extent) {
  var coordinates = [
    extent[0], extent[1],
    extent[0], extent[3],
    extent[2], extent[1],
    extent[2], extent[3]
  ];
  transformFn(coordinates, coordinates, 2);
  var xs = [coordinates[0], coordinates[2], coordinates[4], coordinates[6]];
  var ys = [coordinates[1], coordinates[3], coordinates[5], coordinates[7]];
  return ol.extent.boundingExtentXYs_(xs, ys, opt_extent);
};

goog.provide('ol.functions');

/**
 * Always returns true.
 * @returns {boolean} true.
 */
ol.functions.TRUE = function() {
  return true;
};

/**
 * Always returns false.
 * @returns {boolean} false.
 */
ol.functions.FALSE = function() {
  return false;
};

/**
 * @license
 * Latitude/longitude spherical geodesy formulae taken from
 * http://www.movable-type.co.uk/scripts/latlong.html
 * Licensed under CC-BY-3.0.
 */

goog.provide('ol.Sphere');

goog.require('ol.math');


/**
 * @classdesc
 * Class to create objects that can be used with {@link
 * ol.geom.Polygon.circular}.
 *
 * For example to create a sphere whose radius is equal to the semi-major
 * axis of the WGS84 ellipsoid:
 *
 * ```js
 * var wgs84Sphere= new ol.Sphere(6378137);
 * ```
 *
 * @constructor
 * @param {number} radius Radius.
 * @api
 */
ol.Sphere = function(radius) {

  /**
   * @type {number}
   */
  this.radius = radius;

};


/**
 * Returns the geodesic area for a list of coordinates.
 *
 * [Reference](http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409)
 * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
 * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
 * Laboratory, Pasadena, CA, June 2007
 *
 * @param {Array.<ol.Coordinate>} coordinates List of coordinates of a linear
 * ring. If the ring is oriented clockwise, the area will be positive,
 * otherwise it will be negative.
 * @return {number} Area.
 * @api
 */
ol.Sphere.prototype.geodesicArea = function(coordinates) {
  var area = 0, len = coordinates.length;
  var x1 = coordinates[len - 1][0];
  var y1 = coordinates[len - 1][1];
  for (var i = 0; i < len; i++) {
    var x2 = coordinates[i][0], y2 = coordinates[i][1];
    area += ol.math.toRadians(x2 - x1) *
        (2 + Math.sin(ol.math.toRadians(y1)) +
        Math.sin(ol.math.toRadians(y2)));
    x1 = x2;
    y1 = y2;
  }
  return area * this.radius * this.radius / 2.0;
};


/**
 * Returns the distance from c1 to c2 using the haversine formula.
 *
 * @param {ol.Coordinate} c1 Coordinate 1.
 * @param {ol.Coordinate} c2 Coordinate 2.
 * @return {number} Haversine distance.
 * @api
 */
ol.Sphere.prototype.haversineDistance = function(c1, c2) {
  var lat1 = ol.math.toRadians(c1[1]);
  var lat2 = ol.math.toRadians(c2[1]);
  var deltaLatBy2 = (lat2 - lat1) / 2;
  var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2;
  var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) +
      Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) *
      Math.cos(lat1) * Math.cos(lat2);
  return 2 * this.radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};


/**
 * Returns the coordinate at the given distance and bearing from `c1`.
 *
 * @param {ol.Coordinate} c1 The origin point (`[lon, lat]` in degrees).
 * @param {number} distance The great-circle distance between the origin
 *     point and the target point.
 * @param {number} bearing The bearing (in radians).
 * @return {ol.Coordinate} The target point.
 */
ol.Sphere.prototype.offset = function(c1, distance, bearing) {
  var lat1 = ol.math.toRadians(c1[1]);
  var lon1 = ol.math.toRadians(c1[0]);
  var dByR = distance / this.radius;
  var lat = Math.asin(
      Math.sin(lat1) * Math.cos(dByR) +
      Math.cos(lat1) * Math.sin(dByR) * Math.cos(bearing));
  var lon = lon1 + Math.atan2(
      Math.sin(bearing) * Math.sin(dByR) * Math.cos(lat1),
      Math.cos(dByR) - Math.sin(lat1) * Math.sin(lat));
  return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
};

goog.provide('ol.sphere.NORMAL');

goog.require('ol.Sphere');


/**
 * The normal sphere.
 * @const
 * @type {ol.Sphere}
 */
ol.sphere.NORMAL = new ol.Sphere(6370997);

goog.provide('ol.proj');
goog.provide('ol.proj.METERS_PER_UNIT');
goog.provide('ol.proj.Projection');
goog.provide('ol.proj.Units');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.sphere.NORMAL');


/**
 * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or
 * `'us-ft'`.
 * @enum {string}
 */
ol.proj.Units = {
  DEGREES: 'degrees',
  FEET: 'ft',
  METERS: 'm',
  PIXELS: 'pixels',
  TILE_PIXELS: 'tile-pixels',
  USFEET: 'us-ft'
};


/**
 * Meters per unit lookup table.
 * @const
 * @type {Object.<ol.proj.Units, number>}
 * @api stable
 */
ol.proj.METERS_PER_UNIT = {};
ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] =
    2 * Math.PI * ol.sphere.NORMAL.radius / 360;
ol.proj.METERS_PER_UNIT[ol.proj.Units.FEET] = 0.3048;
ol.proj.METERS_PER_UNIT[ol.proj.Units.METERS] = 1;
ol.proj.METERS_PER_UNIT[ol.proj.Units.USFEET] = 1200 / 3937;


/**
 * @classdesc
 * Projection definition class. One of these is created for each projection
 * supported in the application and stored in the {@link ol.proj} namespace.
 * You can use these in applications, but this is not required, as API params
 * and options use {@link ol.ProjectionLike} which means the simple string
 * code will suffice.
 *
 * You can use {@link ol.proj.get} to retrieve the object for a particular
 * projection.
 *
 * The library includes definitions for `EPSG:4326` and `EPSG:3857`, together
 * with the following aliases:
 * * `EPSG:4326`: CRS:84, urn:ogc:def:crs:EPSG:6.6:4326,
 *     urn:ogc:def:crs:OGC:1.3:CRS84, urn:ogc:def:crs:OGC:2:84,
 *     http://www.opengis.net/gml/srs/epsg.xml#4326,
 *     urn:x-ogc:def:crs:EPSG:4326
 * * `EPSG:3857`: EPSG:102100, EPSG:102113, EPSG:900913,
 *     urn:ogc:def:crs:EPSG:6.18:3:3857,
 *     http://www.opengis.net/gml/srs/epsg.xml#3857
 *
 * If you use proj4js, aliases can be added using `proj4.defs()`; see
 * [documentation](https://github.com/proj4js/proj4js). To set an alternative
 * namespace for proj4, use {@link ol.proj.setProj4}.
 *
 * @constructor
 * @param {olx.ProjectionOptions} options Projection options.
 * @struct
 * @api stable
 */
ol.proj.Projection = function(options) {

  /**
   * @private
   * @type {string}
   */
  this.code_ = options.code;

  /**
   * @private
   * @type {ol.proj.Units}
   */
  this.units_ = /** @type {ol.proj.Units} */ (options.units);

  /**
   * @private
   * @type {ol.Extent}
   */
  this.extent_ = options.extent !== undefined ? options.extent : null;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.worldExtent_ = options.worldExtent !== undefined ?
      options.worldExtent : null;

  /**
   * @private
   * @type {string}
   */
  this.axisOrientation_ = options.axisOrientation !== undefined ?
      options.axisOrientation : 'enu';

  /**
   * @private
   * @type {boolean}
   */
  this.global_ = options.global !== undefined ? options.global : false;


  /**
   * @private
   * @type {boolean}
   */
  this.canWrapX_ = !!(this.global_ && this.extent_);

  /**
  * @private
  * @type {function(number, ol.Coordinate):number}
  */
  this.getPointResolutionFunc_ = options.getPointResolution !== undefined ?
      options.getPointResolution : this.getPointResolution_;

  /**
   * @private
   * @type {ol.tilegrid.TileGrid}
   */
  this.defaultTileGrid_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.metersPerUnit_ = options.metersPerUnit;

  var projections = ol.proj.projections_;
  var code = options.code;
  goog.DEBUG && console.assert(code !== undefined,
      'Option "code" is required for constructing instance');
  if (ol.ENABLE_PROJ4JS) {
    var proj4js = ol.proj.proj4_ || ol.global['proj4'];
    if (typeof proj4js == 'function' && projections[code] === undefined) {
      var def = proj4js.defs(code);
      if (def !== undefined) {
        if (def.axis !== undefined && options.axisOrientation === undefined) {
          this.axisOrientation_ = def.axis;
        }
        if (options.metersPerUnit === undefined) {
          this.metersPerUnit_ = def.to_meter;
        }
        if (options.units === undefined) {
          this.units_ = def.units;
        }
        var currentCode, currentDef, currentProj, proj4Transform;
        for (currentCode in projections) {
          currentDef = proj4js.defs(currentCode);
          if (currentDef !== undefined) {
            currentProj = ol.proj.get(currentCode);
            if (currentDef === def) {
              ol.proj.addEquivalentProjections([currentProj, this]);
            } else {
              proj4Transform = proj4js(currentCode, code);
              ol.proj.addCoordinateTransforms(currentProj, this,
                  proj4Transform.forward, proj4Transform.inverse);
            }
          }
        }
      }
    }
  }

};


/**
 * @return {boolean} The projection is suitable for wrapping the x-axis
 */
ol.proj.Projection.prototype.canWrapX = function() {
  return this.canWrapX_;
};


/**
 * Get the code for this projection, e.g. 'EPSG:4326'.
 * @return {string} Code.
 * @api stable
 */
ol.proj.Projection.prototype.getCode = function() {
  return this.code_;
};


/**
 * Get the validity extent for this projection.
 * @return {ol.Extent} Extent.
 * @api stable
 */
ol.proj.Projection.prototype.getExtent = function() {
  return this.extent_;
};


/**
 * Get the units of this projection.
 * @return {ol.proj.Units} Units.
 * @api stable
 */
ol.proj.Projection.prototype.getUnits = function() {
  return this.units_;
};


/**
 * Get the amount of meters per unit of this projection.  If the projection is
 * not configured with `metersPerUnit` or a units identifier, the return is
 * `undefined`.
 * @return {number|undefined} Meters.
 * @api stable
 */
ol.proj.Projection.prototype.getMetersPerUnit = function() {
  return this.metersPerUnit_ || ol.proj.METERS_PER_UNIT[this.units_];
};


/**
 * Get the world extent for this projection.
 * @return {ol.Extent} Extent.
 * @api
 */
ol.proj.Projection.prototype.getWorldExtent = function() {
  return this.worldExtent_;
};


/**
 * Get the axis orientation of this projection.
 * Example values are:
 * enu - the default easting, northing, elevation.
 * neu - northing, easting, up - useful for "lat/long" geographic coordinates,
 *     or south orientated transverse mercator.
 * wnu - westing, northing, up - some planetary coordinate systems have
 *     "west positive" coordinate systems
 * @return {string} Axis orientation.
 */
ol.proj.Projection.prototype.getAxisOrientation = function() {
  return this.axisOrientation_;
};


/**
 * Is this projection a global projection which spans the whole world?
 * @return {boolean} Whether the projection is global.
 * @api stable
 */
ol.proj.Projection.prototype.isGlobal = function() {
  return this.global_;
};


/**
* Set if the projection is a global projection which spans the whole world
* @param {boolean} global Whether the projection is global.
* @api stable
*/
ol.proj.Projection.prototype.setGlobal = function(global) {
  this.global_ = global;
  this.canWrapX_ = !!(global && this.extent_);
};


/**
 * @return {ol.tilegrid.TileGrid} The default tile grid.
 */
ol.proj.Projection.prototype.getDefaultTileGrid = function() {
  return this.defaultTileGrid_;
};


/**
 * @param {ol.tilegrid.TileGrid} tileGrid The default tile grid.
 */
ol.proj.Projection.prototype.setDefaultTileGrid = function(tileGrid) {
  this.defaultTileGrid_ = tileGrid;
};


/**
 * Set the validity extent for this projection.
 * @param {ol.Extent} extent Extent.
 * @api stable
 */
ol.proj.Projection.prototype.setExtent = function(extent) {
  this.extent_ = extent;
  this.canWrapX_ = !!(this.global_ && extent);
};


/**
 * Set the world extent for this projection.
 * @param {ol.Extent} worldExtent World extent
 *     [minlon, minlat, maxlon, maxlat].
 * @api
 */
ol.proj.Projection.prototype.setWorldExtent = function(worldExtent) {
  this.worldExtent_ = worldExtent;
};


/**
* Set the getPointResolution function for this projection.
* @param {function(number, ol.Coordinate):number} func Function
* @api
*/
ol.proj.Projection.prototype.setGetPointResolution = function(func) {
  this.getPointResolutionFunc_ = func;
};


/**
* Default version.
* Get the resolution of the point in degrees or distance units.
* For projections with degrees as the unit this will simply return the
* provided resolution. For other projections the point resolution is
* estimated by transforming the 'point' pixel to EPSG:4326,
* measuring its width and height on the normal sphere,
* and taking the average of the width and height.
* @param {number} resolution Nominal resolution in projection units.
* @param {ol.Coordinate} point Point to find adjusted resolution at.
* @return {number} Point resolution at point in projection units.
* @private
*/
ol.proj.Projection.prototype.getPointResolution_ = function(resolution, point) {
  var units = this.getUnits();
  if (units == ol.proj.Units.DEGREES) {
    return resolution;
  } else {
    // Estimate point resolution by transforming the center pixel to EPSG:4326,
    // measuring its width and height on the normal sphere, and taking the
    // average of the width and height.
    var toEPSG4326 = ol.proj.getTransformFromProjections(
        this, ol.proj.get('EPSG:4326'));
    var vertices = [
      point[0] - resolution / 2, point[1],
      point[0] + resolution / 2, point[1],
      point[0], point[1] - resolution / 2,
      point[0], point[1] + resolution / 2
    ];
    vertices = toEPSG4326(vertices, vertices, 2);
    var width = ol.sphere.NORMAL.haversineDistance(
        vertices.slice(0, 2), vertices.slice(2, 4));
    var height = ol.sphere.NORMAL.haversineDistance(
        vertices.slice(4, 6), vertices.slice(6, 8));
    var pointResolution = (width + height) / 2;
    var metersPerUnit = this.getMetersPerUnit();
    if (metersPerUnit !== undefined) {
      pointResolution /= metersPerUnit;
    }
    return pointResolution;
  }
};


/**
 * Get the resolution of the point in degrees or distance units.
 * For projections with degrees as the unit this will simply return the
 * provided resolution. The default for other projections is to estimate
 * the point resolution by transforming the 'point' pixel to EPSG:4326,
 * measuring its width and height on the normal sphere,
 * and taking the average of the width and height.
 * An alternative implementation may be given when constructing a
 * projection. For many local projections,
 * such a custom function will return the resolution unchanged.
 * @param {number} resolution Resolution in projection units.
 * @param {ol.Coordinate} point Point.
 * @return {number} Point resolution in projection units.
 * @api
 */
ol.proj.Projection.prototype.getPointResolution = function(resolution, point) {
  return this.getPointResolutionFunc_(resolution, point);
};


/**
 * @private
 * @type {Object.<string, ol.proj.Projection>}
 */
ol.proj.projections_ = {};


/**
 * @private
 * @type {Object.<string, Object.<string, ol.TransformFunction>>}
 */
ol.proj.transforms_ = {};


/**
 * @private
 * @type {proj4}
 */
ol.proj.proj4_ = null;


if (ol.ENABLE_PROJ4JS) {
  /**
   * Register proj4. If not explicitly registered, it will be assumed that
   * proj4js will be loaded in the global namespace. For example in a
   * browserify ES6 environment you could use:
   *
   *     import ol from 'openlayers';
   *     import proj4 from 'proj4';
   *     ol.proj.setProj4(proj4);
   *
   * @param {proj4} proj4 Proj4.
   * @api
   */
  ol.proj.setProj4 = function(proj4) {
    goog.DEBUG && console.assert(typeof proj4 == 'function',
        'proj4 argument should be a function');
    ol.proj.proj4_ = proj4;
  };
}


/**
 * Registers transformation functions that don't alter coordinates. Those allow
 * to transform between projections with equal meaning.
 *
 * @param {Array.<ol.proj.Projection>} projections Projections.
 * @api
 */
ol.proj.addEquivalentProjections = function(projections) {
  ol.proj.addProjections(projections);
  projections.forEach(function(source) {
    projections.forEach(function(destination) {
      if (source !== destination) {
        ol.proj.addTransform(source, destination, ol.proj.cloneTransform);
      }
    });
  });
};


/**
 * Registers transformation functions to convert coordinates in any projection
 * in projection1 to any projection in projection2.
 *
 * @param {Array.<ol.proj.Projection>} projections1 Projections with equal
 *     meaning.
 * @param {Array.<ol.proj.Projection>} projections2 Projections with equal
 *     meaning.
 * @param {ol.TransformFunction} forwardTransform Transformation from any
 *   projection in projection1 to any projection in projection2.
 * @param {ol.TransformFunction} inverseTransform Transform from any projection
 *   in projection2 to any projection in projection1..
 */
ol.proj.addEquivalentTransforms = function(projections1, projections2, forwardTransform, inverseTransform) {
  projections1.forEach(function(projection1) {
    projections2.forEach(function(projection2) {
      ol.proj.addTransform(projection1, projection2, forwardTransform);
      ol.proj.addTransform(projection2, projection1, inverseTransform);
    });
  });
};


/**
 * Add a Projection object to the list of supported projections that can be
 * looked up by their code.
 *
 * @param {ol.proj.Projection} projection Projection instance.
 * @api stable
 */
ol.proj.addProjection = function(projection) {
  ol.proj.projections_[projection.getCode()] = projection;
  ol.proj.addTransform(projection, projection, ol.proj.cloneTransform);
};


/**
 * @param {Array.<ol.proj.Projection>} projections Projections.
 */
ol.proj.addProjections = function(projections) {
  var addedProjections = [];
  projections.forEach(function(projection) {
    addedProjections.push(ol.proj.addProjection(projection));
  });
};


/**
 * FIXME empty description for jsdoc
 */
ol.proj.clearAllProjections = function() {
  ol.proj.projections_ = {};
  ol.proj.transforms_ = {};
};


/**
 * @param {ol.proj.Projection|string|undefined} projection Projection.
 * @param {string} defaultCode Default code.
 * @return {ol.proj.Projection} Projection.
 */
ol.proj.createProjection = function(projection, defaultCode) {
  if (!projection) {
    return ol.proj.get(defaultCode);
  } else if (typeof projection === 'string') {
    return ol.proj.get(projection);
  } else {
    return /** @type {ol.proj.Projection} */ (projection);
  }
};


/**
 * Registers a conversion function to convert coordinates from the source
 * projection to the destination projection.
 *
 * @param {ol.proj.Projection} source Source.
 * @param {ol.proj.Projection} destination Destination.
 * @param {ol.TransformFunction} transformFn Transform.
 */
ol.proj.addTransform = function(source, destination, transformFn) {
  var sourceCode = source.getCode();
  var destinationCode = destination.getCode();
  var transforms = ol.proj.transforms_;
  if (!(sourceCode in transforms)) {
    transforms[sourceCode] = {};
  }
  transforms[sourceCode][destinationCode] = transformFn;
};


/**
 * Registers coordinate transform functions to convert coordinates between the
 * source projection and the destination projection.
 * The forward and inverse functions convert coordinate pairs; this function
 * converts these into the functions used internally which also handle
 * extents and coordinate arrays.
 *
 * @param {ol.ProjectionLike} source Source projection.
 * @param {ol.ProjectionLike} destination Destination projection.
 * @param {function(ol.Coordinate): ol.Coordinate} forward The forward transform
 *     function (that is, from the source projection to the destination
 *     projection) that takes a {@link ol.Coordinate} as argument and returns
 *     the transformed {@link ol.Coordinate}.
 * @param {function(ol.Coordinate): ol.Coordinate} inverse The inverse transform
 *     function (that is, from the destination projection to the source
 *     projection) that takes a {@link ol.Coordinate} as argument and returns
 *     the transformed {@link ol.Coordinate}.
 * @api stable
 */
ol.proj.addCoordinateTransforms = function(source, destination, forward, inverse) {
  var sourceProj = ol.proj.get(source);
  var destProj = ol.proj.get(destination);
  ol.proj.addTransform(sourceProj, destProj,
      ol.proj.createTransformFromCoordinateTransform(forward));
  ol.proj.addTransform(destProj, sourceProj,
      ol.proj.createTransformFromCoordinateTransform(inverse));
};


/**
 * Creates a {@link ol.TransformFunction} from a simple 2D coordinate transform
 * function.
 * @param {function(ol.Coordinate): ol.Coordinate} transform Coordinate
 *     transform.
 * @return {ol.TransformFunction} Transform function.
 */
ol.proj.createTransformFromCoordinateTransform = function(transform) {
  return (
      /**
       * @param {Array.<number>} input Input.
       * @param {Array.<number>=} opt_output Output.
       * @param {number=} opt_dimension Dimension.
       * @return {Array.<number>} Output.
       */
      function(input, opt_output, opt_dimension) {
        var length = input.length;
        var dimension = opt_dimension !== undefined ? opt_dimension : 2;
        var output = opt_output !== undefined ? opt_output : new Array(length);
        var point, i, j;
        for (i = 0; i < length; i += dimension) {
          point = transform([input[i], input[i + 1]]);
          output[i] = point[0];
          output[i + 1] = point[1];
          for (j = dimension - 1; j >= 2; --j) {
            output[i + j] = input[i + j];
          }
        }
        return output;
      });
};


/**
 * Unregisters the conversion function to convert coordinates from the source
 * projection to the destination projection.  This method is used to clean up
 * cached transforms during testing.
 *
 * @param {ol.proj.Projection} source Source projection.
 * @param {ol.proj.Projection} destination Destination projection.
 * @return {ol.TransformFunction} transformFn The unregistered transform.
 */
ol.proj.removeTransform = function(source, destination) {
  var sourceCode = source.getCode();
  var destinationCode = destination.getCode();
  var transforms = ol.proj.transforms_;
  goog.DEBUG && console.assert(sourceCode in transforms,
      'sourceCode should be in transforms');
  goog.DEBUG && console.assert(destinationCode in transforms[sourceCode],
      'destinationCode should be in transforms of sourceCode');
  var transform = transforms[sourceCode][destinationCode];
  delete transforms[sourceCode][destinationCode];
  if (ol.obj.isEmpty(transforms[sourceCode])) {
    delete transforms[sourceCode];
  }
  return transform;
};


/**
 * Transforms a coordinate from longitude/latitude to a different projection.
 * @param {ol.Coordinate} coordinate Coordinate as longitude and latitude, i.e.
 *     an array with longitude as 1st and latitude as 2nd element.
 * @param {ol.ProjectionLike=} opt_projection Target projection. The
 *     default is Web Mercator, i.e. 'EPSG:3857'.
 * @return {ol.Coordinate} Coordinate projected to the target projection.
 * @api stable
 */
ol.proj.fromLonLat = function(coordinate, opt_projection) {
  return ol.proj.transform(coordinate, 'EPSG:4326',
      opt_projection !== undefined ? opt_projection : 'EPSG:3857');
};


/**
 * Transforms a coordinate to longitude/latitude.
 * @param {ol.Coordinate} coordinate Projected coordinate.
 * @param {ol.ProjectionLike=} opt_projection Projection of the coordinate.
 *     The default is Web Mercator, i.e. 'EPSG:3857'.
 * @return {ol.Coordinate} Coordinate as longitude and latitude, i.e. an array
 *     with longitude as 1st and latitude as 2nd element.
 * @api stable
 */
ol.proj.toLonLat = function(coordinate, opt_projection) {
  return ol.proj.transform(coordinate,
      opt_projection !== undefined ? opt_projection : 'EPSG:3857', 'EPSG:4326');
};


/**
 * Fetches a Projection object for the code specified.
 *
 * @param {ol.ProjectionLike} projectionLike Either a code string which is
 *     a combination of authority and identifier such as "EPSG:4326", or an
 *     existing projection object, or undefined.
 * @return {ol.proj.Projection} Projection object, or null if not in list.
 * @api stable
 */
ol.proj.get = function(projectionLike) {
  var projection;
  if (projectionLike instanceof ol.proj.Projection) {
    projection = projectionLike;
  } else if (typeof projectionLike === 'string') {
    var code = projectionLike;
    projection = ol.proj.projections_[code];
    if (ol.ENABLE_PROJ4JS) {
      var proj4js = ol.proj.proj4_ || ol.global['proj4'];
      if (projection === undefined && typeof proj4js == 'function' &&
          proj4js.defs(code) !== undefined) {
        projection = new ol.proj.Projection({code: code});
        ol.proj.addProjection(projection);
      }
    }
  }
  return projection || null;
};


/**
 * Checks if two projections are the same, that is every coordinate in one
 * projection does represent the same geographic point as the same coordinate in
 * the other projection.
 *
 * @param {ol.proj.Projection} projection1 Projection 1.
 * @param {ol.proj.Projection} projection2 Projection 2.
 * @return {boolean} Equivalent.
 * @api
 */
ol.proj.equivalent = function(projection1, projection2) {
  if (projection1 === projection2) {
    return true;
  }
  var equalUnits = projection1.getUnits() === projection2.getUnits();
  if (projection1.getCode() === projection2.getCode()) {
    return equalUnits;
  } else {
    var transformFn = ol.proj.getTransformFromProjections(
        projection1, projection2);
    return transformFn === ol.proj.cloneTransform && equalUnits;
  }
};


/**
 * Given the projection-like objects, searches for a transformation
 * function to convert a coordinates array from the source projection to the
 * destination projection.
 *
 * @param {ol.ProjectionLike} source Source.
 * @param {ol.ProjectionLike} destination Destination.
 * @return {ol.TransformFunction} Transform function.
 * @api stable
 */
ol.proj.getTransform = function(source, destination) {
  var sourceProjection = ol.proj.get(source);
  var destinationProjection = ol.proj.get(destination);
  return ol.proj.getTransformFromProjections(
      sourceProjection, destinationProjection);
};


/**
 * Searches in the list of transform functions for the function for converting
 * coordinates from the source projection to the destination projection.
 *
 * @param {ol.proj.Projection} sourceProjection Source Projection object.
 * @param {ol.proj.Projection} destinationProjection Destination Projection
 *     object.
 * @return {ol.TransformFunction} Transform function.
 */
ol.proj.getTransformFromProjections = function(sourceProjection, destinationProjection) {
  var transforms = ol.proj.transforms_;
  var sourceCode = sourceProjection.getCode();
  var destinationCode = destinationProjection.getCode();
  var transform;
  if (sourceCode in transforms && destinationCode in transforms[sourceCode]) {
    transform = transforms[sourceCode][destinationCode];
  }
  if (transform === undefined) {
    goog.DEBUG && console.assert(transform !== undefined, 'transform should be defined');
    transform = ol.proj.identityTransform;
  }
  return transform;
};


/**
 * @param {Array.<number>} input Input coordinate array.
 * @param {Array.<number>=} opt_output Output array of coordinate values.
 * @param {number=} opt_dimension Dimension.
 * @return {Array.<number>} Input coordinate array (same array as input).
 */
ol.proj.identityTransform = function(input, opt_output, opt_dimension) {
  if (opt_output !== undefined && input !== opt_output) {
    // TODO: consider making this a warning instead
    goog.DEBUG && console.assert(false, 'This should not be used internally.');
    for (var i = 0, ii = input.length; i < ii; ++i) {
      opt_output[i] = input[i];
    }
    input = opt_output;
  }
  return input;
};


/**
 * @param {Array.<number>} input Input coordinate array.
 * @param {Array.<number>=} opt_output Output array of coordinate values.
 * @param {number=} opt_dimension Dimension.
 * @return {Array.<number>} Output coordinate array (new array, same coordinate
 *     values).
 */
ol.proj.cloneTransform = function(input, opt_output, opt_dimension) {
  var output;
  if (opt_output !== undefined) {
    for (var i = 0, ii = input.length; i < ii; ++i) {
      opt_output[i] = input[i];
    }
    output = opt_output;
  } else {
    output = input.slice();
  }
  return output;
};


/**
 * Transforms a coordinate from source projection to destination projection.
 * This returns a new coordinate (and does not modify the original).
 *
 * See {@link ol.proj.transformExtent} for extent transformation.
 * See the transform method of {@link ol.geom.Geometry} and its subclasses for
 * geometry transforms.
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.ProjectionLike} source Source projection-like.
 * @param {ol.ProjectionLike} destination Destination projection-like.
 * @return {ol.Coordinate} Coordinate.
 * @api stable
 */
ol.proj.transform = function(coordinate, source, destination) {
  var transformFn = ol.proj.getTransform(source, destination);
  return transformFn(coordinate, undefined, coordinate.length);
};


/**
 * Transforms an extent from source projection to destination projection.  This
 * returns a new extent (and does not modify the original).
 *
 * @param {ol.Extent} extent The extent to transform.
 * @param {ol.ProjectionLike} source Source projection-like.
 * @param {ol.ProjectionLike} destination Destination projection-like.
 * @return {ol.Extent} The transformed extent.
 * @api stable
 */
ol.proj.transformExtent = function(extent, source, destination) {
  var transformFn = ol.proj.getTransform(source, destination);
  return ol.extent.applyTransform(extent, transformFn);
};


/**
 * Transforms the given point to the destination projection.
 *
 * @param {ol.Coordinate} point Point.
 * @param {ol.proj.Projection} sourceProjection Source projection.
 * @param {ol.proj.Projection} destinationProjection Destination projection.
 * @return {ol.Coordinate} Point.
 */
ol.proj.transformWithProjections = function(point, sourceProjection, destinationProjection) {
  var transformFn = ol.proj.getTransformFromProjections(
      sourceProjection, destinationProjection);
  return transformFn(point);
};

goog.provide('ol.geom.Geometry');
goog.provide('ol.geom.GeometryLayout');
goog.provide('ol.geom.GeometryType');

goog.require('ol');
goog.require('ol.functions');
goog.require('ol.Object');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.proj.Units');


/**
 * The geometry type. One of `'Point'`, `'LineString'`, `'LinearRing'`,
 * `'Polygon'`, `'MultiPoint'`, `'MultiLineString'`, `'MultiPolygon'`,
 * `'GeometryCollection'`, `'Circle'`.
 * @enum {string}
 */
ol.geom.GeometryType = {
  POINT: 'Point',
  LINE_STRING: 'LineString',
  LINEAR_RING: 'LinearRing',
  POLYGON: 'Polygon',
  MULTI_POINT: 'MultiPoint',
  MULTI_LINE_STRING: 'MultiLineString',
  MULTI_POLYGON: 'MultiPolygon',
  GEOMETRY_COLLECTION: 'GeometryCollection',
  CIRCLE: 'Circle'
};


/**
 * The coordinate layout for geometries, indicating whether a 3rd or 4th z ('Z')
 * or measure ('M') coordinate is available. Supported values are `'XY'`,
 * `'XYZ'`, `'XYM'`, `'XYZM'`.
 * @enum {string}
 */
ol.geom.GeometryLayout = {
  XY: 'XY',
  XYZ: 'XYZ',
  XYM: 'XYM',
  XYZM: 'XYZM'
};


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for vector geometries.
 *
 * To get notified of changes to the geometry, register a listener for the
 * generic `change` event on your geometry instance.
 *
 * @constructor
 * @extends {ol.Object}
 * @api stable
 */
ol.geom.Geometry = function() {

  ol.Object.call(this);

  /**
   * @private
   * @type {ol.Extent}
   */
  this.extent_ = ol.extent.createEmpty();

  /**
   * @private
   * @type {number}
   */
  this.extentRevision_ = -1;

  /**
   * @protected
   * @type {Object.<string, ol.geom.Geometry>}
   */
  this.simplifiedGeometryCache = {};

  /**
   * @protected
   * @type {number}
   */
  this.simplifiedGeometryMaxMinSquaredTolerance = 0;

  /**
   * @protected
   * @type {number}
   */
  this.simplifiedGeometryRevision = 0;

};
ol.inherits(ol.geom.Geometry, ol.Object);


/**
 * Make a complete copy of the geometry.
 * @abstract
 * @return {!ol.geom.Geometry} Clone.
 */
ol.geom.Geometry.prototype.clone = function() {};


/**
 * @abstract
 * @param {number} x X.
 * @param {number} y Y.
 * @param {ol.Coordinate} closestPoint Closest point.
 * @param {number} minSquaredDistance Minimum squared distance.
 * @return {number} Minimum squared distance.
 */
ol.geom.Geometry.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {};


/**
 * Return the closest point of the geometry to the passed point as
 * {@link ol.Coordinate coordinate}.
 * @param {ol.Coordinate} point Point.
 * @param {ol.Coordinate=} opt_closestPoint Closest point.
 * @return {ol.Coordinate} Closest point.
 * @api stable
 */
ol.geom.Geometry.prototype.getClosestPoint = function(point, opt_closestPoint) {
  var closestPoint = opt_closestPoint ? opt_closestPoint : [NaN, NaN];
  this.closestPointXY(point[0], point[1], closestPoint, Infinity);
  return closestPoint;
};


/**
 * Returns true if this geometry includes the specified coordinate. If the
 * coordinate is on the boundary of the geometry, returns false.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @return {boolean} Contains coordinate.
 * @api
 */
ol.geom.Geometry.prototype.intersectsCoordinate = function(coordinate) {
  return this.containsXY(coordinate[0], coordinate[1]);
};


/**
 * @abstract
 * @param {ol.Extent} extent Extent.
 * @protected
 * @return {ol.Extent} extent Extent.
 */
ol.geom.Geometry.prototype.computeExtent = function(extent) {};


/**
 * @param {number} x X.
 * @param {number} y Y.
 * @return {boolean} Contains (x, y).
 */
ol.geom.Geometry.prototype.containsXY = ol.functions.FALSE;


/**
 * Get the extent of the geometry.
 * @param {ol.Extent=} opt_extent Extent.
 * @return {ol.Extent} extent Extent.
 * @api stable
 */
ol.geom.Geometry.prototype.getExtent = function(opt_extent) {
  if (this.extentRevision_ != this.getRevision()) {
    this.extent_ = this.computeExtent(this.extent_);
    this.extentRevision_ = this.getRevision();
  }
  return ol.extent.returnOrUpdate(this.extent_, opt_extent);
};


/**
 * Rotate the geometry around a given coordinate. This modifies the geometry
 * coordinates in place.
 * @abstract
 * @param {number} angle Rotation angle in radians.
 * @param {ol.Coordinate} anchor The rotation center.
 * @api
 */
ol.geom.Geometry.prototype.rotate = function(angle, anchor) {};


/**
 * Scale the geometry (with an optional origin).  This modifies the geometry
 * coordinates in place.
 * @abstract
 * @param {number} sx The scaling factor in the x-direction.
 * @param {number=} opt_sy The scaling factor in the y-direction (defaults to
 *     sx).
 * @param {ol.Coordinate=} opt_anchor The scale origin (defaults to the center
 *     of the geometry extent).
 * @api
 */
ol.geom.Geometry.prototype.scale = function(sx, opt_sy, opt_anchor) {};


/**
 * Create a simplified version of this geometry.  For linestrings, this uses
 * the the {@link
 * https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
 * Douglas Peucker} algorithm.  For polygons, a quantization-based
 * simplification is used to preserve topology.
 * @function
 * @param {number} tolerance The tolerance distance for simplification.
 * @return {ol.geom.Geometry} A new, simplified version of the original
 *     geometry.
 * @api
 */
ol.geom.Geometry.prototype.simplify = function(tolerance) {
  return this.getSimplifiedGeometry(tolerance * tolerance);
};


/**
 * Create a simplified version of this geometry using the Douglas Peucker
 * algorithm.
 * @see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
 * @abstract
 * @param {number} squaredTolerance Squared tolerance.
 * @return {ol.geom.Geometry} Simplified geometry.
 */
ol.geom.Geometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {};


/**
 * Get the type of this geometry.
 * @abstract
 * @return {ol.geom.GeometryType} Geometry type.
 */
ol.geom.Geometry.prototype.getType = function() {};


/**
 * Apply a transform function to each coordinate of the geometry.
 * The geometry is modified in place.
 * If you do not want the geometry modified in place, first `clone()` it and
 * then use this function on the clone.
 * @abstract
 * @param {ol.TransformFunction} transformFn Transform.
 */
ol.geom.Geometry.prototype.applyTransform = function(transformFn) {};


/**
 * Test if the geometry and the passed extent intersect.
 * @abstract
 * @param {ol.Extent} extent Extent.
 * @return {boolean} `true` if the geometry and the extent intersect.
 */
ol.geom.Geometry.prototype.intersectsExtent = function(extent) {};


/**
 * Translate the geometry.  This modifies the geometry coordinates in place.  If
 * instead you want a new geometry, first `clone()` this geometry.
 * @abstract
 * @param {number} deltaX Delta X.
 * @param {number} deltaY Delta Y.
 */
ol.geom.Geometry.prototype.translate = function(deltaX, deltaY) {};


/**
 * Transform each coordinate of the geometry from one coordinate reference
 * system to another. The geometry is modified in place.
 * For example, a line will be transformed to a line and a circle to a circle.
 * If you do not want the geometry modified in place, first `clone()` it and
 * then use this function on the clone.
 *
 * @param {ol.ProjectionLike} source The current projection.  Can be a
 *     string identifier or a {@link ol.proj.Projection} object.
 * @param {ol.ProjectionLike} destination The desired projection.  Can be a
 *     string identifier or a {@link ol.proj.Projection} object.
 * @return {ol.geom.Geometry} This geometry.  Note that original geometry is
 *     modified in place.
 * @api stable
 */
ol.geom.Geometry.prototype.transform = function(source, destination) {
  goog.DEBUG && console.assert(
      ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS &&
      ol.proj.get(destination).getUnits() !== ol.proj.Units.TILE_PIXELS,
      'cannot transform geometries with TILE_PIXELS units');
  this.applyTransform(ol.proj.getTransform(source, destination));
  return this;
};

goog.provide('ol.geom.flat.transform');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {ol.Transform} transform Transform.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Transformed coordinates.
 */
ol.geom.flat.transform.transform2D = function(flatCoordinates, offset, end, stride, transform, opt_dest) {
  var dest = opt_dest ? opt_dest : [];
  var i = 0;
  var j;
  for (j = offset; j < end; j += stride) {
    var x = flatCoordinates[j];
    var y = flatCoordinates[j + 1];
    dest[i++] = transform[0] * x + transform[2] * y + transform[4];
    dest[i++] = transform[1] * x + transform[3] * y + transform[5];
  }
  if (opt_dest && dest.length != i) {
    dest.length = i;
  }
  return dest;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} angle Angle.
 * @param {Array.<number>} anchor Rotation anchor point.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Transformed coordinates.
 */
ol.geom.flat.transform.rotate = function(flatCoordinates, offset, end, stride, angle, anchor, opt_dest) {
  var dest = opt_dest ? opt_dest : [];
  var cos = Math.cos(angle);
  var sin = Math.sin(angle);
  var anchorX = anchor[0];
  var anchorY = anchor[1];
  var i = 0;
  for (var j = offset; j < end; j += stride) {
    var deltaX = flatCoordinates[j] - anchorX;
    var deltaY = flatCoordinates[j + 1] - anchorY;
    dest[i++] = anchorX + deltaX * cos - deltaY * sin;
    dest[i++] = anchorY + deltaX * sin + deltaY * cos;
    for (var k = j + 2; k < j + stride; ++k) {
      dest[i++] = flatCoordinates[k];
    }
  }
  if (opt_dest && dest.length != i) {
    dest.length = i;
  }
  return dest;
};


/**
 * Scale the coordinates.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} sx Scale factor in the x-direction.
 * @param {number} sy Scale factor in the y-direction.
 * @param {Array.<number>} anchor Scale anchor point.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Transformed coordinates.
 */
ol.geom.flat.transform.scale = function(flatCoordinates, offset, end, stride, sx, sy, anchor, opt_dest) {
  var dest = opt_dest ? opt_dest : [];
  var anchorX = anchor[0];
  var anchorY = anchor[1];
  var i = 0;
  for (var j = offset; j < end; j += stride) {
    var deltaX = flatCoordinates[j] - anchorX;
    var deltaY = flatCoordinates[j + 1] - anchorY;
    dest[i++] = anchorX + sx * deltaX;
    dest[i++] = anchorY + sy * deltaY;
    for (var k = j + 2; k < j + stride; ++k) {
      dest[i++] = flatCoordinates[k];
    }
  }
  if (opt_dest && dest.length != i) {
    dest.length = i;
  }
  return dest;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} deltaX Delta X.
 * @param {number} deltaY Delta Y.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Transformed coordinates.
 */
ol.geom.flat.transform.translate = function(flatCoordinates, offset, end, stride, deltaX, deltaY, opt_dest) {
  var dest = opt_dest ? opt_dest : [];
  var i = 0;
  var j, k;
  for (j = offset; j < end; j += stride) {
    dest[i++] = flatCoordinates[j] + deltaX;
    dest[i++] = flatCoordinates[j + 1] + deltaY;
    for (k = j + 2; k < j + stride; ++k) {
      dest[i++] = flatCoordinates[k];
    }
  }
  if (opt_dest && dest.length != i) {
    dest.length = i;
  }
  return dest;
};

goog.provide('ol.geom.SimpleGeometry');

goog.require('ol');
goog.require('ol.functions');
goog.require('ol.extent');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.flat.transform');
goog.require('ol.obj');


/**
 * @classdesc
 * Abstract base class; only used for creating subclasses; do not instantiate
 * in apps, as cannot be rendered.
 *
 * @constructor
 * @extends {ol.geom.Geometry}
 * @api stable
 */
ol.geom.SimpleGeometry = function() {

  ol.geom.Geometry.call(this);

  /**
   * @protected
   * @type {ol.geom.GeometryLayout}
   */
  this.layout = ol.geom.GeometryLayout.XY;

  /**
   * @protected
   * @type {number}
   */
  this.stride = 2;

  /**
   * @protected
   * @type {Array.<number>}
   */
  this.flatCoordinates = null;

};
ol.inherits(ol.geom.SimpleGeometry, ol.geom.Geometry);


/**
 * @param {number} stride Stride.
 * @private
 * @return {ol.geom.GeometryLayout} layout Layout.
 */
ol.geom.SimpleGeometry.getLayoutForStride_ = function(stride) {
  var layout;
  if (stride == 2) {
    layout = ol.geom.GeometryLayout.XY;
  } else if (stride == 3) {
    layout = ol.geom.GeometryLayout.XYZ;
  } else if (stride == 4) {
    layout = ol.geom.GeometryLayout.XYZM;
  }
  goog.DEBUG && console.assert(layout, 'unsupported stride: ' + stride);
  return /** @type {ol.geom.GeometryLayout} */ (layout);
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @return {number} Stride.
 */
ol.geom.SimpleGeometry.getStrideForLayout = function(layout) {
  var stride;
  if (layout == ol.geom.GeometryLayout.XY) {
    stride = 2;
  } else if (layout == ol.geom.GeometryLayout.XYZ || layout == ol.geom.GeometryLayout.XYM) {
    stride = 3;
  } else if (layout == ol.geom.GeometryLayout.XYZM) {
    stride = 4;
  }
  goog.DEBUG && console.assert(stride, 'unsupported layout: ' + layout);
  return /** @type {number} */ (stride);
};


/**
 * @inheritDoc
 */
ol.geom.SimpleGeometry.prototype.containsXY = ol.functions.FALSE;


/**
 * @inheritDoc
 */
ol.geom.SimpleGeometry.prototype.computeExtent = function(extent) {
  return ol.extent.createOrUpdateFromFlatCoordinates(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      extent);
};


/**
 * @abstract
 * @return {Array} Coordinates.
 */
ol.geom.SimpleGeometry.prototype.getCoordinates = function() {};


/**
 * Return the first coordinate of the geometry.
 * @return {ol.Coordinate} First coordinate.
 * @api stable
 */
ol.geom.SimpleGeometry.prototype.getFirstCoordinate = function() {
  return this.flatCoordinates.slice(0, this.stride);
};


/**
 * @return {Array.<number>} Flat coordinates.
 */
ol.geom.SimpleGeometry.prototype.getFlatCoordinates = function() {
  return this.flatCoordinates;
};


/**
 * Return the last coordinate of the geometry.
 * @return {ol.Coordinate} Last point.
 * @api stable
 */
ol.geom.SimpleGeometry.prototype.getLastCoordinate = function() {
  return this.flatCoordinates.slice(this.flatCoordinates.length - this.stride);
};


/**
 * Return the {@link ol.geom.GeometryLayout layout} of the geometry.
 * @return {ol.geom.GeometryLayout} Layout.
 * @api stable
 */
ol.geom.SimpleGeometry.prototype.getLayout = function() {
  return this.layout;
};


/**
 * @inheritDoc
 */
ol.geom.SimpleGeometry.prototype.getSimplifiedGeometry = function(squaredTolerance) {
  if (this.simplifiedGeometryRevision != this.getRevision()) {
    ol.obj.clear(this.simplifiedGeometryCache);
    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
    this.simplifiedGeometryRevision = this.getRevision();
  }
  // If squaredTolerance is negative or if we know that simplification will not
  // have any effect then just return this.
  if (squaredTolerance < 0 ||
      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
       squaredTolerance <= this.simplifiedGeometryMaxMinSquaredTolerance)) {
    return this;
  }
  var key = squaredTolerance.toString();
  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
    return this.simplifiedGeometryCache[key];
  } else {
    var simplifiedGeometry =
        this.getSimplifiedGeometryInternal(squaredTolerance);
    var simplifiedFlatCoordinates = simplifiedGeometry.getFlatCoordinates();
    if (simplifiedFlatCoordinates.length < this.flatCoordinates.length) {
      this.simplifiedGeometryCache[key] = simplifiedGeometry;
      return simplifiedGeometry;
    } else {
      // Simplification did not actually remove any coordinates.  We now know
      // that any calls to getSimplifiedGeometry with a squaredTolerance less
      // than or equal to the current squaredTolerance will also not have any
      // effect.  This allows us to short circuit simplification (saving CPU
      // cycles) and prevents the cache of simplified geometries from filling
      // up with useless identical copies of this geometry (saving memory).
      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
      return this;
    }
  }
};


/**
 * @param {number} squaredTolerance Squared tolerance.
 * @return {ol.geom.SimpleGeometry} Simplified geometry.
 * @protected
 */
ol.geom.SimpleGeometry.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
  return this;
};


/**
 * @return {number} Stride.
 */
ol.geom.SimpleGeometry.prototype.getStride = function() {
  return this.stride;
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @protected
 */
ol.geom.SimpleGeometry.prototype.setFlatCoordinatesInternal = function(layout, flatCoordinates) {
  this.stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
  this.layout = layout;
  this.flatCoordinates = flatCoordinates;
};


/**
 * @abstract
 * @param {Array} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 */
ol.geom.SimpleGeometry.prototype.setCoordinates = function(coordinates, opt_layout) {};


/**
 * @param {ol.geom.GeometryLayout|undefined} layout Layout.
 * @param {Array} coordinates Coordinates.
 * @param {number} nesting Nesting.
 * @protected
 */
ol.geom.SimpleGeometry.prototype.setLayout = function(layout, coordinates, nesting) {
  /** @type {number} */
  var stride;
  if (layout) {
    stride = ol.geom.SimpleGeometry.getStrideForLayout(layout);
  } else {
    var i;
    for (i = 0; i < nesting; ++i) {
      if (coordinates.length === 0) {
        this.layout = ol.geom.GeometryLayout.XY;
        this.stride = 2;
        return;
      } else {
        coordinates = /** @type {Array} */ (coordinates[0]);
      }
    }
    stride = coordinates.length;
    layout = ol.geom.SimpleGeometry.getLayoutForStride_(stride);
  }
  this.layout = layout;
  this.stride = stride;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.SimpleGeometry.prototype.applyTransform = function(transformFn) {
  if (this.flatCoordinates) {
    transformFn(this.flatCoordinates, this.flatCoordinates, this.stride);
    this.changed();
  }
};


/**
 * @inheritDoc
 * @api
 */
ol.geom.SimpleGeometry.prototype.rotate = function(angle, anchor) {
  var flatCoordinates = this.getFlatCoordinates();
  if (flatCoordinates) {
    var stride = this.getStride();
    ol.geom.flat.transform.rotate(
        flatCoordinates, 0, flatCoordinates.length,
        stride, angle, anchor, flatCoordinates);
    this.changed();
  }
};


/**
 * @inheritDoc
 * @api
 */
ol.geom.SimpleGeometry.prototype.scale = function(sx, opt_sy, opt_anchor) {
  var sy = opt_sy;
  if (sy === undefined) {
    sy = sx;
  }
  var anchor = opt_anchor;
  if (!anchor) {
    anchor = ol.extent.getCenter(this.getExtent());
  }
  var flatCoordinates = this.getFlatCoordinates();
  if (flatCoordinates) {
    var stride = this.getStride();
    ol.geom.flat.transform.scale(
        flatCoordinates, 0, flatCoordinates.length,
        stride, sx, sy, anchor, flatCoordinates);
    this.changed();
  }
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.SimpleGeometry.prototype.translate = function(deltaX, deltaY) {
  var flatCoordinates = this.getFlatCoordinates();
  if (flatCoordinates) {
    var stride = this.getStride();
    ol.geom.flat.transform.translate(
        flatCoordinates, 0, flatCoordinates.length, stride,
        deltaX, deltaY, flatCoordinates);
    this.changed();
  }
};


/**
 * @param {ol.geom.SimpleGeometry} simpleGeometry Simple geometry.
 * @param {ol.Transform} transform Transform.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Transformed flat coordinates.
 */
ol.geom.SimpleGeometry.transform2D = function(simpleGeometry, transform, opt_dest) {
  var flatCoordinates = simpleGeometry.getFlatCoordinates();
  if (!flatCoordinates) {
    return null;
  } else {
    var stride = simpleGeometry.getStride();
    return ol.geom.flat.transform.transform2D(
        flatCoordinates, 0, flatCoordinates.length, stride,
        transform, opt_dest);
  }
};

goog.provide('ol.geom.flat.area');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @return {number} Area.
 */
ol.geom.flat.area.linearRing = function(flatCoordinates, offset, end, stride) {
  var twiceArea = 0;
  var x1 = flatCoordinates[end - stride];
  var y1 = flatCoordinates[end - stride + 1];
  for (; offset < end; offset += stride) {
    var x2 = flatCoordinates[offset];
    var y2 = flatCoordinates[offset + 1];
    twiceArea += y1 * x2 - x1 * y2;
    x1 = x2;
    y1 = y2;
  }
  return twiceArea / 2;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @return {number} Area.
 */
ol.geom.flat.area.linearRings = function(flatCoordinates, offset, ends, stride) {
  var area = 0;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    area += ol.geom.flat.area.linearRing(flatCoordinates, offset, end, stride);
    offset = end;
  }
  return area;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @return {number} Area.
 */
ol.geom.flat.area.linearRingss = function(flatCoordinates, offset, endss, stride) {
  var area = 0;
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    area +=
        ol.geom.flat.area.linearRings(flatCoordinates, offset, ends, stride);
    offset = ends[ends.length - 1];
  }
  return area;
};

goog.provide('ol.geom.flat.closest');

goog.require('ol.math');


/**
 * Returns the point on the 2D line segment flatCoordinates[offset1] to
 * flatCoordinates[offset2] that is closest to the point (x, y).  Extra
 * dimensions are linearly interpolated.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset1 Offset 1.
 * @param {number} offset2 Offset 2.
 * @param {number} stride Stride.
 * @param {number} x X.
 * @param {number} y Y.
 * @param {Array.<number>} closestPoint Closest point.
 */
ol.geom.flat.closest.point = function(flatCoordinates, offset1, offset2, stride, x, y, closestPoint) {
  var x1 = flatCoordinates[offset1];
  var y1 = flatCoordinates[offset1 + 1];
  var dx = flatCoordinates[offset2] - x1;
  var dy = flatCoordinates[offset2 + 1] - y1;
  var i, offset;
  if (dx === 0 && dy === 0) {
    offset = offset1;
  } else {
    var t = ((x - x1) * dx + (y - y1) * dy) / (dx * dx + dy * dy);
    if (t > 1) {
      offset = offset2;
    } else if (t > 0) {
      for (i = 0; i < stride; ++i) {
        closestPoint[i] = ol.math.lerp(flatCoordinates[offset1 + i],
            flatCoordinates[offset2 + i], t);
      }
      closestPoint.length = stride;
      return;
    } else {
      offset = offset1;
    }
  }
  for (i = 0; i < stride; ++i) {
    closestPoint[i] = flatCoordinates[offset + i];
  }
  closestPoint.length = stride;
};


/**
 * Return the squared of the largest distance between any pair of consecutive
 * coordinates.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} maxSquaredDelta Max squared delta.
 * @return {number} Max squared delta.
 */
ol.geom.flat.closest.getMaxSquaredDelta = function(flatCoordinates, offset, end, stride, maxSquaredDelta) {
  var x1 = flatCoordinates[offset];
  var y1 = flatCoordinates[offset + 1];
  for (offset += stride; offset < end; offset += stride) {
    var x2 = flatCoordinates[offset];
    var y2 = flatCoordinates[offset + 1];
    var squaredDelta = ol.math.squaredDistance(x1, y1, x2, y2);
    if (squaredDelta > maxSquaredDelta) {
      maxSquaredDelta = squaredDelta;
    }
    x1 = x2;
    y1 = y2;
  }
  return maxSquaredDelta;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {number} maxSquaredDelta Max squared delta.
 * @return {number} Max squared delta.
 */
ol.geom.flat.closest.getsMaxSquaredDelta = function(flatCoordinates, offset, ends, stride, maxSquaredDelta) {
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    maxSquaredDelta = ol.geom.flat.closest.getMaxSquaredDelta(
        flatCoordinates, offset, end, stride, maxSquaredDelta);
    offset = end;
  }
  return maxSquaredDelta;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {number} maxSquaredDelta Max squared delta.
 * @return {number} Max squared delta.
 */
ol.geom.flat.closest.getssMaxSquaredDelta = function(flatCoordinates, offset, endss, stride, maxSquaredDelta) {
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    maxSquaredDelta = ol.geom.flat.closest.getsMaxSquaredDelta(
        flatCoordinates, offset, ends, stride, maxSquaredDelta);
    offset = ends[ends.length - 1];
  }
  return maxSquaredDelta;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} maxDelta Max delta.
 * @param {boolean} isRing Is ring.
 * @param {number} x X.
 * @param {number} y Y.
 * @param {Array.<number>} closestPoint Closest point.
 * @param {number} minSquaredDistance Minimum squared distance.
 * @param {Array.<number>=} opt_tmpPoint Temporary point object.
 * @return {number} Minimum squared distance.
 */
ol.geom.flat.closest.getClosestPoint = function(flatCoordinates, offset, end,
    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
    opt_tmpPoint) {
  if (offset == end) {
    return minSquaredDistance;
  }
  var i, squaredDistance;
  if (maxDelta === 0) {
    // All points are identical, so just test the first point.
    squaredDistance = ol.math.squaredDistance(
        x, y, flatCoordinates[offset], flatCoordinates[offset + 1]);
    if (squaredDistance < minSquaredDistance) {
      for (i = 0; i < stride; ++i) {
        closestPoint[i] = flatCoordinates[offset + i];
      }
      closestPoint.length = stride;
      return squaredDistance;
    } else {
      return minSquaredDistance;
    }
  }
  goog.DEBUG && console.assert(maxDelta > 0, 'maxDelta should be larger than 0');
  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
  var index = offset + stride;
  while (index < end) {
    ol.geom.flat.closest.point(
        flatCoordinates, index - stride, index, stride, x, y, tmpPoint);
    squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]);
    if (squaredDistance < minSquaredDistance) {
      minSquaredDistance = squaredDistance;
      for (i = 0; i < stride; ++i) {
        closestPoint[i] = tmpPoint[i];
      }
      closestPoint.length = stride;
      index += stride;
    } else {
      // Skip ahead multiple points, because we know that all the skipped
      // points cannot be any closer than the closest point we have found so
      // far.  We know this because we know how close the current point is, how
      // close the closest point we have found so far is, and the maximum
      // distance between consecutive points.  For example, if we're currently
      // at distance 10, the best we've found so far is 3, and that the maximum
      // distance between consecutive points is 2, then we'll need to skip at
      // least (10 - 3) / 2 == 3 (rounded down) points to have any chance of
      // finding a closer point.  We use Math.max(..., 1) to ensure that we
      // always advance at least one point, to avoid an infinite loop.
      index += stride * Math.max(
          ((Math.sqrt(squaredDistance) -
            Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1);
    }
  }
  if (isRing) {
    // Check the closing segment.
    ol.geom.flat.closest.point(
        flatCoordinates, end - stride, offset, stride, x, y, tmpPoint);
    squaredDistance = ol.math.squaredDistance(x, y, tmpPoint[0], tmpPoint[1]);
    if (squaredDistance < minSquaredDistance) {
      minSquaredDistance = squaredDistance;
      for (i = 0; i < stride; ++i) {
        closestPoint[i] = tmpPoint[i];
      }
      closestPoint.length = stride;
    }
  }
  return minSquaredDistance;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {number} maxDelta Max delta.
 * @param {boolean} isRing Is ring.
 * @param {number} x X.
 * @param {number} y Y.
 * @param {Array.<number>} closestPoint Closest point.
 * @param {number} minSquaredDistance Minimum squared distance.
 * @param {Array.<number>=} opt_tmpPoint Temporary point object.
 * @return {number} Minimum squared distance.
 */
ol.geom.flat.closest.getsClosestPoint = function(flatCoordinates, offset, ends,
    stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
    opt_tmpPoint) {
  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    minSquaredDistance = ol.geom.flat.closest.getClosestPoint(
        flatCoordinates, offset, end, stride,
        maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint);
    offset = end;
  }
  return minSquaredDistance;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {number} maxDelta Max delta.
 * @param {boolean} isRing Is ring.
 * @param {number} x X.
 * @param {number} y Y.
 * @param {Array.<number>} closestPoint Closest point.
 * @param {number} minSquaredDistance Minimum squared distance.
 * @param {Array.<number>=} opt_tmpPoint Temporary point object.
 * @return {number} Minimum squared distance.
 */
ol.geom.flat.closest.getssClosestPoint = function(flatCoordinates, offset,
    endss, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance,
    opt_tmpPoint) {
  var tmpPoint = opt_tmpPoint ? opt_tmpPoint : [NaN, NaN];
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    minSquaredDistance = ol.geom.flat.closest.getsClosestPoint(
        flatCoordinates, offset, ends, stride,
        maxDelta, isRing, x, y, closestPoint, minSquaredDistance, tmpPoint);
    offset = ends[ends.length - 1];
  }
  return minSquaredDistance;
};

goog.provide('ol.geom.flat.deflate');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} stride Stride.
 * @return {number} offset Offset.
 */
ol.geom.flat.deflate.coordinate = function(flatCoordinates, offset, coordinate, stride) {
  goog.DEBUG && console.assert(coordinate.length == stride,
      'length of the coordinate array should match stride');
  var i, ii;
  for (i = 0, ii = coordinate.length; i < ii; ++i) {
    flatCoordinates[offset++] = coordinate[i];
  }
  return offset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {number} stride Stride.
 * @return {number} offset Offset.
 */
ol.geom.flat.deflate.coordinates = function(flatCoordinates, offset, coordinates, stride) {
  var i, ii;
  for (i = 0, ii = coordinates.length; i < ii; ++i) {
    var coordinate = coordinates[i];
    goog.DEBUG && console.assert(coordinate.length == stride,
        'length of coordinate array should match stride');
    var j;
    for (j = 0; j < stride; ++j) {
      flatCoordinates[offset++] = coordinate[j];
    }
  }
  return offset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<ol.Coordinate>>} coordinatess Coordinatess.
 * @param {number} stride Stride.
 * @param {Array.<number>=} opt_ends Ends.
 * @return {Array.<number>} Ends.
 */
ol.geom.flat.deflate.coordinatess = function(flatCoordinates, offset, coordinatess, stride, opt_ends) {
  var ends = opt_ends ? opt_ends : [];
  var i = 0;
  var j, jj;
  for (j = 0, jj = coordinatess.length; j < jj; ++j) {
    var end = ol.geom.flat.deflate.coordinates(
        flatCoordinates, offset, coordinatess[j], stride);
    ends[i++] = end;
    offset = end;
  }
  ends.length = i;
  return ends;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinatesss Coordinatesss.
 * @param {number} stride Stride.
 * @param {Array.<Array.<number>>=} opt_endss Endss.
 * @return {Array.<Array.<number>>} Endss.
 */
ol.geom.flat.deflate.coordinatesss = function(flatCoordinates, offset, coordinatesss, stride, opt_endss) {
  var endss = opt_endss ? opt_endss : [];
  var i = 0;
  var j, jj;
  for (j = 0, jj = coordinatesss.length; j < jj; ++j) {
    var ends = ol.geom.flat.deflate.coordinatess(
        flatCoordinates, offset, coordinatesss[j], stride, endss[i]);
    endss[i++] = ends;
    offset = ends[ends.length - 1];
  }
  endss.length = i;
  return endss;
};

goog.provide('ol.geom.flat.inflate');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {Array.<ol.Coordinate>=} opt_coordinates Coordinates.
 * @return {Array.<ol.Coordinate>} Coordinates.
 */
ol.geom.flat.inflate.coordinates = function(flatCoordinates, offset, end, stride, opt_coordinates) {
  var coordinates = opt_coordinates !== undefined ? opt_coordinates : [];
  var i = 0;
  var j;
  for (j = offset; j < end; j += stride) {
    coordinates[i++] = flatCoordinates.slice(j, j + stride);
  }
  coordinates.length = i;
  return coordinates;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {Array.<Array.<ol.Coordinate>>=} opt_coordinatess Coordinatess.
 * @return {Array.<Array.<ol.Coordinate>>} Coordinatess.
 */
ol.geom.flat.inflate.coordinatess = function(flatCoordinates, offset, ends, stride, opt_coordinatess) {
  var coordinatess = opt_coordinatess !== undefined ? opt_coordinatess : [];
  var i = 0;
  var j, jj;
  for (j = 0, jj = ends.length; j < jj; ++j) {
    var end = ends[j];
    coordinatess[i++] = ol.geom.flat.inflate.coordinates(
        flatCoordinates, offset, end, stride, coordinatess[i]);
    offset = end;
  }
  coordinatess.length = i;
  return coordinatess;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {Array.<Array.<Array.<ol.Coordinate>>>=} opt_coordinatesss
 *     Coordinatesss.
 * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinatesss.
 */
ol.geom.flat.inflate.coordinatesss = function(flatCoordinates, offset, endss, stride, opt_coordinatesss) {
  var coordinatesss = opt_coordinatesss !== undefined ? opt_coordinatesss : [];
  var i = 0;
  var j, jj;
  for (j = 0, jj = endss.length; j < jj; ++j) {
    var ends = endss[j];
    coordinatesss[i++] = ol.geom.flat.inflate.coordinatess(
        flatCoordinates, offset, ends, stride, coordinatesss[i]);
    offset = ends[ends.length - 1];
  }
  coordinatesss.length = i;
  return coordinatesss;
};

// Based on simplify-js https://github.com/mourner/simplify-js
// Copyright (c) 2012, Vladimir Agafonkin
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    1. Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//
//    2. Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.geom.flat.simplify');

goog.require('ol.math');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {boolean} highQuality Highest quality.
 * @param {Array.<number>=} opt_simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @return {Array.<number>} Simplified line string.
 */
ol.geom.flat.simplify.lineString = function(flatCoordinates, offset, end,
    stride, squaredTolerance, highQuality, opt_simplifiedFlatCoordinates) {
  var simplifiedFlatCoordinates = opt_simplifiedFlatCoordinates !== undefined ?
      opt_simplifiedFlatCoordinates : [];
  if (!highQuality) {
    end = ol.geom.flat.simplify.radialDistance(flatCoordinates, offset, end,
        stride, squaredTolerance,
        simplifiedFlatCoordinates, 0);
    flatCoordinates = simplifiedFlatCoordinates;
    offset = 0;
    stride = 2;
  }
  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
      flatCoordinates, offset, end, stride, squaredTolerance,
      simplifiedFlatCoordinates, 0);
  return simplifiedFlatCoordinates;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.douglasPeucker = function(flatCoordinates, offset, end,
    stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
  var n = (end - offset) / stride;
  if (n < 3) {
    for (; offset < end; offset += stride) {
      simplifiedFlatCoordinates[simplifiedOffset++] =
          flatCoordinates[offset];
      simplifiedFlatCoordinates[simplifiedOffset++] =
          flatCoordinates[offset + 1];
    }
    return simplifiedOffset;
  }
  /** @type {Array.<number>} */
  var markers = new Array(n);
  markers[0] = 1;
  markers[n - 1] = 1;
  /** @type {Array.<number>} */
  var stack = [offset, end - stride];
  var index = 0;
  var i;
  while (stack.length > 0) {
    var last = stack.pop();
    var first = stack.pop();
    var maxSquaredDistance = 0;
    var x1 = flatCoordinates[first];
    var y1 = flatCoordinates[first + 1];
    var x2 = flatCoordinates[last];
    var y2 = flatCoordinates[last + 1];
    for (i = first + stride; i < last; i += stride) {
      var x = flatCoordinates[i];
      var y = flatCoordinates[i + 1];
      var squaredDistance = ol.math.squaredSegmentDistance(
          x, y, x1, y1, x2, y2);
      if (squaredDistance > maxSquaredDistance) {
        index = i;
        maxSquaredDistance = squaredDistance;
      }
    }
    if (maxSquaredDistance > squaredTolerance) {
      markers[(index - offset) / stride] = 1;
      if (first + stride < index) {
        stack.push(first, index);
      }
      if (index + stride < last) {
        stack.push(index, last);
      }
    }
  }
  for (i = 0; i < n; ++i) {
    if (markers[i]) {
      simplifiedFlatCoordinates[simplifiedOffset++] =
          flatCoordinates[offset + i * stride];
      simplifiedFlatCoordinates[simplifiedOffset++] =
          flatCoordinates[offset + i * stride + 1];
    }
  }
  return simplifiedOffset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @param {Array.<number>} simplifiedEnds Simplified ends.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.douglasPeuckers = function(flatCoordinates, offset,
    ends, stride, squaredTolerance, simplifiedFlatCoordinates,
    simplifiedOffset, simplifiedEnds) {
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    simplifiedOffset = ol.geom.flat.simplify.douglasPeucker(
        flatCoordinates, offset, end, stride, squaredTolerance,
        simplifiedFlatCoordinates, simplifiedOffset);
    simplifiedEnds.push(simplifiedOffset);
    offset = end;
  }
  return simplifiedOffset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.douglasPeuckerss = function(
    flatCoordinates, offset, endss, stride, squaredTolerance,
    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    var simplifiedEnds = [];
    simplifiedOffset = ol.geom.flat.simplify.douglasPeuckers(
        flatCoordinates, offset, ends, stride, squaredTolerance,
        simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
    simplifiedEndss.push(simplifiedEnds);
    offset = ends[ends.length - 1];
  }
  return simplifiedOffset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.radialDistance = function(flatCoordinates, offset, end,
    stride, squaredTolerance, simplifiedFlatCoordinates, simplifiedOffset) {
  if (end <= offset + stride) {
    // zero or one point, no simplification possible, so copy and return
    for (; offset < end; offset += stride) {
      simplifiedFlatCoordinates[simplifiedOffset++] = flatCoordinates[offset];
      simplifiedFlatCoordinates[simplifiedOffset++] =
          flatCoordinates[offset + 1];
    }
    return simplifiedOffset;
  }
  var x1 = flatCoordinates[offset];
  var y1 = flatCoordinates[offset + 1];
  // copy first point
  simplifiedFlatCoordinates[simplifiedOffset++] = x1;
  simplifiedFlatCoordinates[simplifiedOffset++] = y1;
  var x2 = x1;
  var y2 = y1;
  for (offset += stride; offset < end; offset += stride) {
    x2 = flatCoordinates[offset];
    y2 = flatCoordinates[offset + 1];
    if (ol.math.squaredDistance(x1, y1, x2, y2) > squaredTolerance) {
      // copy point at offset
      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
      x1 = x2;
      y1 = y2;
    }
  }
  if (x2 != x1 || y2 != y1) {
    // copy last point
    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
  }
  return simplifiedOffset;
};


/**
 * @param {number} value Value.
 * @param {number} tolerance Tolerance.
 * @return {number} Rounded value.
 */
ol.geom.flat.simplify.snap = function(value, tolerance) {
  return tolerance * Math.round(value / tolerance);
};


/**
 * Simplifies a line string using an algorithm designed by Tim Schaub.
 * Coordinates are snapped to the nearest value in a virtual grid and
 * consecutive duplicate coordinates are discarded.  This effectively preserves
 * topology as the simplification of any subsection of a line string is
 * independent of the rest of the line string.  This means that, for examples,
 * the common edge between two polygons will be simplified to the same line
 * string independently in both polygons.  This implementation uses a single
 * pass over the coordinates and eliminates intermediate collinear points.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} tolerance Tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.quantize = function(flatCoordinates, offset, end, stride,
    tolerance, simplifiedFlatCoordinates, simplifiedOffset) {
  // do nothing if the line is empty
  if (offset == end) {
    return simplifiedOffset;
  }
  // snap the first coordinate (P1)
  var x1 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
  var y1 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
  offset += stride;
  // add the first coordinate to the output
  simplifiedFlatCoordinates[simplifiedOffset++] = x1;
  simplifiedFlatCoordinates[simplifiedOffset++] = y1;
  // find the next coordinate that does not snap to the same value as the first
  // coordinate (P2)
  var x2, y2;
  do {
    x2 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
    y2 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
    offset += stride;
    if (offset == end) {
      // all coordinates snap to the same value, the line collapses to a point
      // push the last snapped value anyway to ensure that the output contains
      // at least two points
      // FIXME should we really return at least two points anyway?
      simplifiedFlatCoordinates[simplifiedOffset++] = x2;
      simplifiedFlatCoordinates[simplifiedOffset++] = y2;
      return simplifiedOffset;
    }
  } while (x2 == x1 && y2 == y1);
  while (offset < end) {
    var x3, y3;
    // snap the next coordinate (P3)
    x3 = ol.geom.flat.simplify.snap(flatCoordinates[offset], tolerance);
    y3 = ol.geom.flat.simplify.snap(flatCoordinates[offset + 1], tolerance);
    offset += stride;
    // skip P3 if it is equal to P2
    if (x3 == x2 && y3 == y2) {
      continue;
    }
    // calculate the delta between P1 and P2
    var dx1 = x2 - x1;
    var dy1 = y2 - y1;
    // calculate the delta between P3 and P1
    var dx2 = x3 - x1;
    var dy2 = y3 - y1;
    // if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from
    // P1 in the same direction then P2 is on the straight line between P1 and
    // P3
    if ((dx1 * dy2 == dy1 * dx2) &&
        ((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) &&
        ((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) {
      // discard P2 and set P2 = P3
      x2 = x3;
      y2 = y3;
      continue;
    }
    // either P1, P2, and P3 are not colinear, or they are colinear but P3 is
    // between P3 and P1 or on the opposite half of the line to P2.  add P2,
    // and continue with P1 = P2 and P2 = P3
    simplifiedFlatCoordinates[simplifiedOffset++] = x2;
    simplifiedFlatCoordinates[simplifiedOffset++] = y2;
    x1 = x2;
    y1 = y2;
    x2 = x3;
    y2 = y3;
  }
  // add the last point (P2)
  simplifiedFlatCoordinates[simplifiedOffset++] = x2;
  simplifiedFlatCoordinates[simplifiedOffset++] = y2;
  return simplifiedOffset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {number} tolerance Tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @param {Array.<number>} simplifiedEnds Simplified ends.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.quantizes = function(
    flatCoordinates, offset, ends, stride,
    tolerance,
    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) {
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    simplifiedOffset = ol.geom.flat.simplify.quantize(
        flatCoordinates, offset, end, stride,
        tolerance,
        simplifiedFlatCoordinates, simplifiedOffset);
    simplifiedEnds.push(simplifiedOffset);
    offset = end;
  }
  return simplifiedOffset;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {number} tolerance Tolerance.
 * @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
 *     coordinates.
 * @param {number} simplifiedOffset Simplified offset.
 * @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
 * @return {number} Simplified offset.
 */
ol.geom.flat.simplify.quantizess = function(
    flatCoordinates, offset, endss, stride,
    tolerance,
    simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    var simplifiedEnds = [];
    simplifiedOffset = ol.geom.flat.simplify.quantizes(
        flatCoordinates, offset, ends, stride,
        tolerance,
        simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
    simplifiedEndss.push(simplifiedEnds);
    offset = ends[ends.length - 1];
  }
  return simplifiedOffset;
};

goog.provide('ol.geom.LinearRing');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.area');
goog.require('ol.geom.flat.closest');
goog.require('ol.geom.flat.deflate');
goog.require('ol.geom.flat.inflate');
goog.require('ol.geom.flat.simplify');


/**
 * @classdesc
 * Linear ring geometry. Only used as part of polygon; cannot be rendered
 * on its own.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.LinearRing = function(coordinates, opt_layout) {

  ol.geom.SimpleGeometry.call(this);

  /**
   * @private
   * @type {number}
   */
  this.maxDelta_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.maxDeltaRevision_ = -1;

  this.setCoordinates(coordinates, opt_layout);

};
ol.inherits(ol.geom.LinearRing, ol.geom.SimpleGeometry);


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.LinearRing} Clone.
 * @api stable
 */
ol.geom.LinearRing.prototype.clone = function() {
  var linearRing = new ol.geom.LinearRing(null);
  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
  return linearRing;
};


/**
 * @inheritDoc
 */
ol.geom.LinearRing.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  if (this.maxDeltaRevision_ != this.getRevision()) {
    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta(
        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0));
    this.maxDeltaRevision_ = this.getRevision();
  }
  return ol.geom.flat.closest.getClosestPoint(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
};


/**
 * Return the area of the linear ring on projected plane.
 * @return {number} Area (on projected plane).
 * @api stable
 */
ol.geom.LinearRing.prototype.getArea = function() {
  return ol.geom.flat.area.linearRing(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
};


/**
 * Return the coordinates of the linear ring.
 * @return {Array.<ol.Coordinate>} Coordinates.
 * @api stable
 */
ol.geom.LinearRing.prototype.getCoordinates = function() {
  return ol.geom.flat.inflate.coordinates(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
};


/**
 * @inheritDoc
 */
ol.geom.LinearRing.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
  var simplifiedFlatCoordinates = [];
  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      squaredTolerance, simplifiedFlatCoordinates, 0);
  var simplifiedLinearRing = new ol.geom.LinearRing(null);
  simplifiedLinearRing.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates);
  return simplifiedLinearRing;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.LinearRing.prototype.getType = function() {
  return ol.geom.GeometryType.LINEAR_RING;
};


/**
 * Set the coordinates of the linear ring.
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.LinearRing.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
  } else {
    this.setLayout(opt_layout, coordinates, 1);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
        this.flatCoordinates, 0, coordinates, this.stride);
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 */
ol.geom.LinearRing.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.changed();
};

goog.provide('ol.geom.Point');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.deflate');
goog.require('ol.math');


/**
 * @classdesc
 * Point geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {ol.Coordinate} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.Point = function(coordinates, opt_layout) {
  ol.geom.SimpleGeometry.call(this);
  this.setCoordinates(coordinates, opt_layout);
};
ol.inherits(ol.geom.Point, ol.geom.SimpleGeometry);


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.Point} Clone.
 * @api stable
 */
ol.geom.Point.prototype.clone = function() {
  var point = new ol.geom.Point(null);
  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
  return point;
};


/**
 * @inheritDoc
 */
ol.geom.Point.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  var flatCoordinates = this.flatCoordinates;
  var squaredDistance = ol.math.squaredDistance(
      x, y, flatCoordinates[0], flatCoordinates[1]);
  if (squaredDistance < minSquaredDistance) {
    var stride = this.stride;
    var i;
    for (i = 0; i < stride; ++i) {
      closestPoint[i] = flatCoordinates[i];
    }
    closestPoint.length = stride;
    return squaredDistance;
  } else {
    return minSquaredDistance;
  }
};


/**
 * Return the coordinate of the point.
 * @return {ol.Coordinate} Coordinates.
 * @api stable
 */
ol.geom.Point.prototype.getCoordinates = function() {
  return !this.flatCoordinates ? [] : this.flatCoordinates.slice();
};


/**
 * @inheritDoc
 */
ol.geom.Point.prototype.computeExtent = function(extent) {
  return ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates, extent);
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.Point.prototype.getType = function() {
  return ol.geom.GeometryType.POINT;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.Point.prototype.intersectsExtent = function(extent) {
  return ol.extent.containsXY(extent,
      this.flatCoordinates[0], this.flatCoordinates[1]);
};


/**
 * Set the coordinate of the point.
 * @param {ol.Coordinate} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.Point.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
  } else {
    this.setLayout(opt_layout, coordinates, 0);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    this.flatCoordinates.length = ol.geom.flat.deflate.coordinate(
        this.flatCoordinates, 0, coordinates, this.stride);
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 */
ol.geom.Point.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.changed();
};

goog.provide('ol.geom.flat.contains');

goog.require('ol.extent');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} Contains extent.
 */
ol.geom.flat.contains.linearRingContainsExtent = function(flatCoordinates, offset, end, stride, extent) {
  var outside = ol.extent.forEachCorner(extent,
      /**
       * @param {ol.Coordinate} coordinate Coordinate.
       * @return {boolean} Contains (x, y).
       */
      function(coordinate) {
        return !ol.geom.flat.contains.linearRingContainsXY(flatCoordinates,
            offset, end, stride, coordinate[0], coordinate[1]);
      });
  return !outside;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} x X.
 * @param {number} y Y.
 * @return {boolean} Contains (x, y).
 */
ol.geom.flat.contains.linearRingContainsXY = function(flatCoordinates, offset, end, stride, x, y) {
  // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
  var contains = false;
  var x1 = flatCoordinates[end - stride];
  var y1 = flatCoordinates[end - stride + 1];
  for (; offset < end; offset += stride) {
    var x2 = flatCoordinates[offset];
    var y2 = flatCoordinates[offset + 1];
    var intersect = ((y1 > y) != (y2 > y)) &&
        (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1);
    if (intersect) {
      contains = !contains;
    }
    x1 = x2;
    y1 = y2;
  }
  return contains;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {number} x X.
 * @param {number} y Y.
 * @return {boolean} Contains (x, y).
 */
ol.geom.flat.contains.linearRingsContainsXY = function(flatCoordinates, offset, ends, stride, x, y) {
  goog.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array');
  if (ends.length === 0) {
    return false;
  }
  if (!ol.geom.flat.contains.linearRingContainsXY(
      flatCoordinates, offset, ends[0], stride, x, y)) {
    return false;
  }
  var i, ii;
  for (i = 1, ii = ends.length; i < ii; ++i) {
    if (ol.geom.flat.contains.linearRingContainsXY(
        flatCoordinates, ends[i - 1], ends[i], stride, x, y)) {
      return false;
    }
  }
  return true;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {number} x X.
 * @param {number} y Y.
 * @return {boolean} Contains (x, y).
 */
ol.geom.flat.contains.linearRingssContainsXY = function(flatCoordinates, offset, endss, stride, x, y) {
  goog.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array');
  if (endss.length === 0) {
    return false;
  }
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    if (ol.geom.flat.contains.linearRingsContainsXY(
        flatCoordinates, offset, ends, stride, x, y)) {
      return true;
    }
    offset = ends[ends.length - 1];
  }
  return false;
};

goog.provide('ol.geom.flat.interiorpoint');

goog.require('ol.array');
goog.require('ol.geom.flat.contains');


/**
 * Calculates a point that is likely to lie in the interior of the linear rings.
 * Inspired by JTS's com.vividsolutions.jts.geom.Geometry#getInteriorPoint.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {Array.<number>} flatCenters Flat centers.
 * @param {number} flatCentersOffset Flat center offset.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Destination.
 */
ol.geom.flat.interiorpoint.linearRings = function(flatCoordinates, offset,
    ends, stride, flatCenters, flatCentersOffset, opt_dest) {
  var i, ii, x, x1, x2, y1, y2;
  var y = flatCenters[flatCentersOffset + 1];
  /** @type {Array.<number>} */
  var intersections = [];
  // Calculate intersections with the horizontal line
  var end = ends[0];
  x1 = flatCoordinates[end - stride];
  y1 = flatCoordinates[end - stride + 1];
  for (i = offset; i < end; i += stride) {
    x2 = flatCoordinates[i];
    y2 = flatCoordinates[i + 1];
    if ((y <= y1 && y2 <= y) || (y1 <= y && y <= y2)) {
      x = (y - y1) / (y2 - y1) * (x2 - x1) + x1;
      intersections.push(x);
    }
    x1 = x2;
    y1 = y2;
  }
  // Find the longest segment of the horizontal line that has its center point
  // inside the linear ring.
  var pointX = NaN;
  var maxSegmentLength = -Infinity;
  intersections.sort(ol.array.numberSafeCompareFunction);
  x1 = intersections[0];
  for (i = 1, ii = intersections.length; i < ii; ++i) {
    x2 = intersections[i];
    var segmentLength = Math.abs(x2 - x1);
    if (segmentLength > maxSegmentLength) {
      x = (x1 + x2) / 2;
      if (ol.geom.flat.contains.linearRingsContainsXY(
          flatCoordinates, offset, ends, stride, x, y)) {
        pointX = x;
        maxSegmentLength = segmentLength;
      }
    }
    x1 = x2;
  }
  if (isNaN(pointX)) {
    // There is no horizontal line that has its center point inside the linear
    // ring.  Use the center of the the linear ring's extent.
    pointX = flatCenters[flatCentersOffset];
  }
  if (opt_dest) {
    opt_dest.push(pointX, y);
    return opt_dest;
  } else {
    return [pointX, y];
  }
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {Array.<number>} flatCenters Flat centers.
 * @return {Array.<number>} Interior points.
 */
ol.geom.flat.interiorpoint.linearRingss = function(flatCoordinates, offset, endss, stride, flatCenters) {
  goog.DEBUG && console.assert(2 * endss.length == flatCenters.length,
      'endss.length times 2 should be flatCenters.length');
  var interiorPoints = [];
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    interiorPoints = ol.geom.flat.interiorpoint.linearRings(flatCoordinates,
        offset, ends, stride, flatCenters, 2 * i, interiorPoints);
    offset = ends[ends.length - 1];
  }
  return interiorPoints;
};

goog.provide('ol.geom.flat.segments');


/**
 * This function calls `callback` for each segment of the flat coordinates
 * array. If the callback returns a truthy value the function returns that
 * value immediately. Otherwise the function returns `false`.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
 *     called for each segment.
 * @param {S=} opt_this The object to be used as the value of 'this'
 *     within callback.
 * @return {T|boolean} Value.
 * @template T,S
 */
ol.geom.flat.segments.forEach = function(flatCoordinates, offset, end, stride, callback, opt_this) {
  var point1 = [flatCoordinates[offset], flatCoordinates[offset + 1]];
  var point2 = [];
  var ret;
  for (; (offset + stride) < end; offset += stride) {
    point2[0] = flatCoordinates[offset + stride];
    point2[1] = flatCoordinates[offset + stride + 1];
    ret = callback.call(opt_this, point1, point2);
    if (ret) {
      return ret;
    }
    point1[0] = point2[0];
    point1[1] = point2[1];
  }
  return false;
};

goog.provide('ol.geom.flat.intersectsextent');

goog.require('ol.extent');
goog.require('ol.geom.flat.contains');
goog.require('ol.geom.flat.segments');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} True if the geometry and the extent intersect.
 */
ol.geom.flat.intersectsextent.lineString = function(flatCoordinates, offset, end, stride, extent) {
  var coordinatesExtent = ol.extent.extendFlatCoordinates(
      ol.extent.createEmpty(), flatCoordinates, offset, end, stride);
  if (!ol.extent.intersects(extent, coordinatesExtent)) {
    return false;
  }
  if (ol.extent.containsExtent(extent, coordinatesExtent)) {
    return true;
  }
  if (coordinatesExtent[0] >= extent[0] &&
      coordinatesExtent[2] <= extent[2]) {
    return true;
  }
  if (coordinatesExtent[1] >= extent[1] &&
      coordinatesExtent[3] <= extent[3]) {
    return true;
  }
  return ol.geom.flat.segments.forEach(flatCoordinates, offset, end, stride,
      /**
       * @param {ol.Coordinate} point1 Start point.
       * @param {ol.Coordinate} point2 End point.
       * @return {boolean} `true` if the segment and the extent intersect,
       *     `false` otherwise.
       */
      function(point1, point2) {
        return ol.extent.intersectsSegment(extent, point1, point2);
      });
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} True if the geometry and the extent intersect.
 */
ol.geom.flat.intersectsextent.lineStrings = function(flatCoordinates, offset, ends, stride, extent) {
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    if (ol.geom.flat.intersectsextent.lineString(
        flatCoordinates, offset, ends[i], stride, extent)) {
      return true;
    }
    offset = ends[i];
  }
  return false;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} True if the geometry and the extent intersect.
 */
ol.geom.flat.intersectsextent.linearRing = function(flatCoordinates, offset, end, stride, extent) {
  if (ol.geom.flat.intersectsextent.lineString(
      flatCoordinates, offset, end, stride, extent)) {
    return true;
  }
  if (ol.geom.flat.contains.linearRingContainsXY(
      flatCoordinates, offset, end, stride, extent[0], extent[1])) {
    return true;
  }
  if (ol.geom.flat.contains.linearRingContainsXY(
      flatCoordinates, offset, end, stride, extent[0], extent[3])) {
    return true;
  }
  if (ol.geom.flat.contains.linearRingContainsXY(
      flatCoordinates, offset, end, stride, extent[2], extent[1])) {
    return true;
  }
  if (ol.geom.flat.contains.linearRingContainsXY(
      flatCoordinates, offset, end, stride, extent[2], extent[3])) {
    return true;
  }
  return false;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} True if the geometry and the extent intersect.
 */
ol.geom.flat.intersectsextent.linearRings = function(flatCoordinates, offset, ends, stride, extent) {
  goog.DEBUG && console.assert(ends.length > 0, 'ends should not be an empty array');
  if (!ol.geom.flat.intersectsextent.linearRing(
      flatCoordinates, offset, ends[0], stride, extent)) {
    return false;
  }
  if (ends.length === 1) {
    return true;
  }
  var i, ii;
  for (i = 1, ii = ends.length; i < ii; ++i) {
    if (ol.geom.flat.contains.linearRingContainsExtent(
        flatCoordinates, ends[i - 1], ends[i], stride, extent)) {
      return false;
    }
  }
  return true;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @param {ol.Extent} extent Extent.
 * @return {boolean} True if the geometry and the extent intersect.
 */
ol.geom.flat.intersectsextent.linearRingss = function(flatCoordinates, offset, endss, stride, extent) {
  goog.DEBUG && console.assert(endss.length > 0, 'endss should not be an empty array');
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    if (ol.geom.flat.intersectsextent.linearRings(
        flatCoordinates, offset, ends, stride, extent)) {
      return true;
    }
    offset = ends[ends.length - 1];
  }
  return false;
};

goog.provide('ol.geom.flat.reverse');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 */
ol.geom.flat.reverse.coordinates = function(flatCoordinates, offset, end, stride) {
  while (offset < end - stride) {
    var i;
    for (i = 0; i < stride; ++i) {
      var tmp = flatCoordinates[offset + i];
      flatCoordinates[offset + i] = flatCoordinates[end - stride + i];
      flatCoordinates[end - stride + i] = tmp;
    }
    offset += stride;
    end -= stride;
  }
};

goog.provide('ol.geom.flat.orient');

goog.require('ol');
goog.require('ol.geom.flat.reverse');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @return {boolean} Is clockwise.
 */
ol.geom.flat.orient.linearRingIsClockwise = function(flatCoordinates, offset, end, stride) {
  // http://tinyurl.com/clockwise-method
  // https://github.com/OSGeo/gdal/blob/trunk/gdal/ogr/ogrlinearring.cpp
  var edge = 0;
  var x1 = flatCoordinates[end - stride];
  var y1 = flatCoordinates[end - stride + 1];
  for (; offset < end; offset += stride) {
    var x2 = flatCoordinates[offset];
    var y2 = flatCoordinates[offset + 1];
    edge += (x2 - x1) * (y2 + y1);
    x1 = x2;
    y1 = y2;
  }
  return edge > 0;
};


/**
 * Determines if linear rings are oriented.  By default, left-hand orientation
 * is tested (first ring must be clockwise, remaining rings counter-clockwise).
 * To test for right-hand orientation, use the `opt_right` argument.
 *
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Array of end indexes.
 * @param {number} stride Stride.
 * @param {boolean=} opt_right Test for right-hand orientation
 *     (counter-clockwise exterior ring and clockwise interior rings).
 * @return {boolean} Rings are correctly oriented.
 */
ol.geom.flat.orient.linearRingsAreOriented = function(flatCoordinates, offset, ends, stride, opt_right) {
  var right = opt_right !== undefined ? opt_right : false;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
        flatCoordinates, offset, end, stride);
    if (i === 0) {
      if ((right && isClockwise) || (!right && !isClockwise)) {
        return false;
      }
    } else {
      if ((right && !isClockwise) || (!right && isClockwise)) {
        return false;
      }
    }
    offset = end;
  }
  return true;
};


/**
 * Determines if linear rings are oriented.  By default, left-hand orientation
 * is tested (first ring must be clockwise, remaining rings counter-clockwise).
 * To test for right-hand orientation, use the `opt_right` argument.
 *
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Array of array of end indexes.
 * @param {number} stride Stride.
 * @param {boolean=} opt_right Test for right-hand orientation
 *     (counter-clockwise exterior ring and clockwise interior rings).
 * @return {boolean} Rings are correctly oriented.
 */
ol.geom.flat.orient.linearRingssAreOriented = function(flatCoordinates, offset, endss, stride, opt_right) {
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    if (!ol.geom.flat.orient.linearRingsAreOriented(
        flatCoordinates, offset, endss[i], stride, opt_right)) {
      return false;
    }
  }
  return true;
};


/**
 * Orient coordinates in a flat array of linear rings.  By default, rings
 * are oriented following the left-hand rule (clockwise for exterior and
 * counter-clockwise for interior rings).  To orient according to the
 * right-hand rule, use the `opt_right` argument.
 *
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {boolean=} opt_right Follow the right-hand rule for orientation.
 * @return {number} End.
 */
ol.geom.flat.orient.orientLinearRings = function(flatCoordinates, offset, ends, stride, opt_right) {
  var right = opt_right !== undefined ? opt_right : false;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(
        flatCoordinates, offset, end, stride);
    var reverse = i === 0 ?
        (right && isClockwise) || (!right && !isClockwise) :
        (right && !isClockwise) || (!right && isClockwise);
    if (reverse) {
      ol.geom.flat.reverse.coordinates(flatCoordinates, offset, end, stride);
    }
    offset = end;
  }
  return offset;
};


/**
 * Orient coordinates in a flat array of linear rings.  By default, rings
 * are oriented following the left-hand rule (clockwise for exterior and
 * counter-clockwise for interior rings).  To orient according to the
 * right-hand rule, use the `opt_right` argument.
 *
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Array of array of end indexes.
 * @param {number} stride Stride.
 * @param {boolean=} opt_right Follow the right-hand rule for orientation.
 * @return {number} End.
 */
ol.geom.flat.orient.orientLinearRingss = function(flatCoordinates, offset, endss, stride, opt_right) {
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    offset = ol.geom.flat.orient.orientLinearRings(
        flatCoordinates, offset, endss[i], stride, opt_right);
  }
  return offset;
};

goog.provide('ol.geom.Polygon');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.Point');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.area');
goog.require('ol.geom.flat.closest');
goog.require('ol.geom.flat.contains');
goog.require('ol.geom.flat.deflate');
goog.require('ol.geom.flat.inflate');
goog.require('ol.geom.flat.interiorpoint');
goog.require('ol.geom.flat.intersectsextent');
goog.require('ol.geom.flat.orient');
goog.require('ol.geom.flat.simplify');
goog.require('ol.math');


/**
 * @classdesc
 * Polygon geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.Polygon = function(coordinates, opt_layout) {

  ol.geom.SimpleGeometry.call(this);

  /**
   * @type {Array.<number>}
   * @private
   */
  this.ends_ = [];

  /**
   * @private
   * @type {number}
   */
  this.flatInteriorPointRevision_ = -1;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.flatInteriorPoint_ = null;

  /**
   * @private
   * @type {number}
   */
  this.maxDelta_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.maxDeltaRevision_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.orientedRevision_ = -1;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.orientedFlatCoordinates_ = null;

  this.setCoordinates(coordinates, opt_layout);

};
ol.inherits(ol.geom.Polygon, ol.geom.SimpleGeometry);


/**
 * Append the passed linear ring to this polygon.
 * @param {ol.geom.LinearRing} linearRing Linear ring.
 * @api stable
 */
ol.geom.Polygon.prototype.appendLinearRing = function(linearRing) {
  goog.DEBUG && console.assert(linearRing.getLayout() == this.layout,
      'layout of linearRing should match layout');
  if (!this.flatCoordinates) {
    this.flatCoordinates = linearRing.getFlatCoordinates().slice();
  } else {
    ol.array.extend(this.flatCoordinates, linearRing.getFlatCoordinates());
  }
  this.ends_.push(this.flatCoordinates.length);
  this.changed();
};


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.Polygon} Clone.
 * @api stable
 */
ol.geom.Polygon.prototype.clone = function() {
  var polygon = new ol.geom.Polygon(null);
  polygon.setFlatCoordinates(
      this.layout, this.flatCoordinates.slice(), this.ends_.slice());
  return polygon;
};


/**
 * @inheritDoc
 */
ol.geom.Polygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  if (this.maxDeltaRevision_ != this.getRevision()) {
    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
        this.flatCoordinates, 0, this.ends_, this.stride, 0));
    this.maxDeltaRevision_ = this.getRevision();
  }
  return ol.geom.flat.closest.getsClosestPoint(
      this.flatCoordinates, 0, this.ends_, this.stride,
      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
};


/**
 * @inheritDoc
 */
ol.geom.Polygon.prototype.containsXY = function(x, y) {
  return ol.geom.flat.contains.linearRingsContainsXY(
      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, x, y);
};


/**
 * Return the area of the polygon on projected plane.
 * @return {number} Area (on projected plane).
 * @api stable
 */
ol.geom.Polygon.prototype.getArea = function() {
  return ol.geom.flat.area.linearRings(
      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride);
};


/**
 * Get the coordinate array for this geometry.  This array has the structure
 * of a GeoJSON coordinate array for polygons.
 *
 * @param {boolean=} opt_right Orient coordinates according to the right-hand
 *     rule (counter-clockwise for exterior and clockwise for interior rings).
 *     If `false`, coordinates will be oriented according to the left-hand rule
 *     (clockwise for exterior and counter-clockwise for interior rings).
 *     By default, coordinate orientation will depend on how the geometry was
 *     constructed.
 * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
 * @api stable
 */
ol.geom.Polygon.prototype.getCoordinates = function(opt_right) {
  var flatCoordinates;
  if (opt_right !== undefined) {
    flatCoordinates = this.getOrientedFlatCoordinates().slice();
    ol.geom.flat.orient.orientLinearRings(
        flatCoordinates, 0, this.ends_, this.stride, opt_right);
  } else {
    flatCoordinates = this.flatCoordinates;
  }

  return ol.geom.flat.inflate.coordinatess(
      flatCoordinates, 0, this.ends_, this.stride);
};


/**
 * @return {Array.<number>} Ends.
 */
ol.geom.Polygon.prototype.getEnds = function() {
  return this.ends_;
};


/**
 * @return {Array.<number>} Interior point.
 */
ol.geom.Polygon.prototype.getFlatInteriorPoint = function() {
  if (this.flatInteriorPointRevision_ != this.getRevision()) {
    var flatCenter = ol.extent.getCenter(this.getExtent());
    this.flatInteriorPoint_ = ol.geom.flat.interiorpoint.linearRings(
        this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride,
        flatCenter, 0);
    this.flatInteriorPointRevision_ = this.getRevision();
  }
  return this.flatInteriorPoint_;
};


/**
 * Return an interior point of the polygon.
 * @return {ol.geom.Point} Interior point.
 * @api stable
 */
ol.geom.Polygon.prototype.getInteriorPoint = function() {
  return new ol.geom.Point(this.getFlatInteriorPoint());
};


/**
 * Return the number of rings of the polygon,  this includes the exterior
 * ring and any interior rings.
 *
 * @return {number} Number of rings.
 * @api
 */
ol.geom.Polygon.prototype.getLinearRingCount = function() {
  return this.ends_.length;
};


/**
 * Return the Nth linear ring of the polygon geometry. Return `null` if the
 * given index is out of range.
 * The exterior linear ring is available at index `0` and the interior rings
 * at index `1` and beyond.
 *
 * @param {number} index Index.
 * @return {ol.geom.LinearRing} Linear ring.
 * @api stable
 */
ol.geom.Polygon.prototype.getLinearRing = function(index) {
  goog.DEBUG && console.assert(0 <= index && index < this.ends_.length,
      'index should be in between 0 and and length of this.ends_');
  if (index < 0 || this.ends_.length <= index) {
    return null;
  }
  var linearRing = new ol.geom.LinearRing(null);
  linearRing.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
      index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
  return linearRing;
};


/**
 * Return the linear rings of the polygon.
 * @return {Array.<ol.geom.LinearRing>} Linear rings.
 * @api stable
 */
ol.geom.Polygon.prototype.getLinearRings = function() {
  var layout = this.layout;
  var flatCoordinates = this.flatCoordinates;
  var ends = this.ends_;
  var linearRings = [];
  var offset = 0;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    var linearRing = new ol.geom.LinearRing(null);
    linearRing.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
    linearRings.push(linearRing);
    offset = end;
  }
  return linearRings;
};


/**
 * @return {Array.<number>} Oriented flat coordinates.
 */
ol.geom.Polygon.prototype.getOrientedFlatCoordinates = function() {
  if (this.orientedRevision_ != this.getRevision()) {
    var flatCoordinates = this.flatCoordinates;
    if (ol.geom.flat.orient.linearRingsAreOriented(
        flatCoordinates, 0, this.ends_, this.stride)) {
      this.orientedFlatCoordinates_ = flatCoordinates;
    } else {
      this.orientedFlatCoordinates_ = flatCoordinates.slice();
      this.orientedFlatCoordinates_.length =
          ol.geom.flat.orient.orientLinearRings(
              this.orientedFlatCoordinates_, 0, this.ends_, this.stride);
    }
    this.orientedRevision_ = this.getRevision();
  }
  return this.orientedFlatCoordinates_;
};


/**
 * @inheritDoc
 */
ol.geom.Polygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
  var simplifiedFlatCoordinates = [];
  var simplifiedEnds = [];
  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizes(
      this.flatCoordinates, 0, this.ends_, this.stride,
      Math.sqrt(squaredTolerance),
      simplifiedFlatCoordinates, 0, simplifiedEnds);
  var simplifiedPolygon = new ol.geom.Polygon(null);
  simplifiedPolygon.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
  return simplifiedPolygon;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.Polygon.prototype.getType = function() {
  return ol.geom.GeometryType.POLYGON;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.Polygon.prototype.intersectsExtent = function(extent) {
  return ol.geom.flat.intersectsextent.linearRings(
      this.getOrientedFlatCoordinates(), 0, this.ends_, this.stride, extent);
};


/**
 * Set the coordinates of the polygon.
 * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.Polygon.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
  } else {
    this.setLayout(opt_layout, coordinates, 2);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    var ends = ol.geom.flat.deflate.coordinatess(
        this.flatCoordinates, 0, coordinates, this.stride, this.ends_);
    this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {Array.<number>} ends Ends.
 */
ol.geom.Polygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
  if (!flatCoordinates) {
    goog.DEBUG && console.assert(ends && ends.length === 0,
        'ends must be an empty array');
  } else if (ends.length === 0) {
    goog.DEBUG && console.assert(flatCoordinates.length === 0,
        'flatCoordinates should be an empty array');
  } else {
    goog.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1],
        'the length of flatCoordinates should be the last entry of ends');
  }
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.ends_ = ends;
  this.changed();
};


/**
 * Create an approximation of a circle on the surface of a sphere.
 * @param {ol.Sphere} sphere The sphere.
 * @param {ol.Coordinate} center Center (`[lon, lat]` in degrees).
 * @param {number} radius The great-circle distance from the center to
 *     the polygon vertices.
 * @param {number=} opt_n Optional number of vertices for the resulting
 *     polygon. Default is `32`.
 * @return {ol.geom.Polygon} The "circular" polygon.
 * @api stable
 */
ol.geom.Polygon.circular = function(sphere, center, radius, opt_n) {
  var n = opt_n ? opt_n : 32;
  /** @type {Array.<number>} */
  var flatCoordinates = [];
  var i;
  for (i = 0; i < n; ++i) {
    ol.array.extend(
        flatCoordinates, sphere.offset(center, radius, 2 * Math.PI * i / n));
  }
  flatCoordinates.push(flatCoordinates[0], flatCoordinates[1]);
  var polygon = new ol.geom.Polygon(null);
  polygon.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
  return polygon;
};


/**
 * Create a polygon from an extent. The layout used is `XY`.
 * @param {ol.Extent} extent The extent.
 * @return {ol.geom.Polygon} The polygon.
 * @api
 */
ol.geom.Polygon.fromExtent = function(extent) {
  var minX = extent[0];
  var minY = extent[1];
  var maxX = extent[2];
  var maxY = extent[3];
  var flatCoordinates =
      [minX, minY, minX, maxY, maxX, maxY, maxX, minY, minX, minY];
  var polygon = new ol.geom.Polygon(null);
  polygon.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, flatCoordinates, [flatCoordinates.length]);
  return polygon;
};


/**
 * Create a regular polygon from a circle.
 * @param {ol.geom.Circle} circle Circle geometry.
 * @param {number=} opt_sides Number of sides of the polygon. Default is 32.
 * @param {number=} opt_angle Start angle for the first vertex of the polygon in
 *     radians. Default is 0.
 * @return {ol.geom.Polygon} Polygon geometry.
 * @api
 */
ol.geom.Polygon.fromCircle = function(circle, opt_sides, opt_angle) {
  var sides = opt_sides ? opt_sides : 32;
  var stride = circle.getStride();
  var layout = circle.getLayout();
  var polygon = new ol.geom.Polygon(null, layout);
  var arrayLength = stride * (sides + 1);
  var flatCoordinates = new Array(arrayLength);
  for (var i = 0; i < arrayLength; i++) {
    flatCoordinates[i] = 0;
  }
  var ends = [flatCoordinates.length];
  polygon.setFlatCoordinates(layout, flatCoordinates, ends);
  ol.geom.Polygon.makeRegular(
      polygon, circle.getCenter(), circle.getRadius(), opt_angle);
  return polygon;
};


/**
 * Modify the coordinates of a polygon to make it a regular polygon.
 * @param {ol.geom.Polygon} polygon Polygon geometry.
 * @param {ol.Coordinate} center Center of the regular polygon.
 * @param {number} radius Radius of the regular polygon.
 * @param {number=} opt_angle Start angle for the first vertex of the polygon in
 *     radians. Default is 0.
 */
ol.geom.Polygon.makeRegular = function(polygon, center, radius, opt_angle) {
  var flatCoordinates = polygon.getFlatCoordinates();
  var layout = polygon.getLayout();
  var stride = polygon.getStride();
  var ends = polygon.getEnds();
  goog.DEBUG && console.assert(ends.length === 1, 'only 1 ring is supported');
  var sides = flatCoordinates.length / stride - 1;
  var startAngle = opt_angle ? opt_angle : 0;
  var angle, offset;
  for (var i = 0; i <= sides; ++i) {
    offset = i * stride;
    angle = startAngle + (ol.math.modulo(i, sides) * 2 * Math.PI / sides);
    flatCoordinates[offset] = center[0] + (radius * Math.cos(angle));
    flatCoordinates[offset + 1] = center[1] + (radius * Math.sin(angle));
  }
  polygon.setFlatCoordinates(layout, flatCoordinates, ends);
};

goog.provide('ol.View');

goog.require('ol');
goog.require('ol.CenterConstraint');
goog.require('ol.Constraints');
goog.require('ol.Object');
goog.require('ol.ResolutionConstraint');
goog.require('ol.RotationConstraint');
goog.require('ol.array');
goog.require('ol.asserts');
goog.require('ol.coordinate');
goog.require('ol.extent');
goog.require('ol.geom.Polygon');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.proj');
goog.require('ol.proj.METERS_PER_UNIT');
goog.require('ol.proj.Units');


/**
 * @classdesc
 * An ol.View object represents a simple 2D view of the map.
 *
 * This is the object to act upon to change the center, resolution,
 * and rotation of the map.
 *
 * ### The view states
 *
 * An `ol.View` is determined by three states: `center`, `resolution`,
 * and `rotation`. Each state has a corresponding getter and setter, e.g.
 * `getCenter` and `setCenter` for the `center` state.
 *
 * An `ol.View` has a `projection`. The projection determines the
 * coordinate system of the center, and its units determine the units of the
 * resolution (projection units per pixel). The default projection is
 * Spherical Mercator (EPSG:3857).
 *
 * ### The constraints
 *
 * `setCenter`, `setResolution` and `setRotation` can be used to change the
 * states of the view. Any value can be passed to the setters. And the value
 * that is passed to a setter will effectively be the value set in the view,
 * and returned by the corresponding getter.
 *
 * But an `ol.View` object also has a *resolution constraint*, a
 * *rotation constraint* and a *center constraint*.
 *
 * As said above, no constraints are applied when the setters are used to set
 * new states for the view. Applying constraints is done explicitly through
 * the use of the `constrain*` functions (`constrainResolution` and
 * `constrainRotation` and `constrainCenter`).
 *
 * The main users of the constraints are the interactions and the
 * controls. For example, double-clicking on the map changes the view to
 * the "next" resolution. And releasing the fingers after pinch-zooming
 * snaps to the closest resolution (with an animation).
 *
 * The *resolution constraint* snaps to specific resolutions. It is
 * determined by the following options: `resolutions`, `maxResolution`,
 * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three
 * options are ignored. See documentation for each option for more
 * information.
 *
 * The *rotation constraint* snaps to specific angles. It is determined
 * by the following options: `enableRotation` and `constrainRotation`.
 * By default the rotation value is snapped to zero when approaching the
 * horizontal.
 *
 * The *center constraint* is determined by the `extent` option. By
 * default the center is not constrained at all.
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.ViewOptions=} opt_options View options.
 * @api stable
 */
ol.View = function(opt_options) {
  ol.Object.call(this);
  var options = opt_options || {};

  /**
   * @private
   * @type {Array.<number>}
   */
  this.hints_ = [0, 0];

  /**
   * @type {Object.<string, *>}
   */
  var properties = {};
  properties[ol.View.Property.CENTER] = options.center !== undefined ?
      options.center : null;

  /**
   * @private
   * @const
   * @type {ol.proj.Projection}
   */
  this.projection_ = ol.proj.createProjection(options.projection, 'EPSG:3857');

  var resolutionConstraintInfo = ol.View.createResolutionConstraint_(
      options);

  /**
   * @private
   * @type {number}
   */
  this.maxResolution_ = resolutionConstraintInfo.maxResolution;

  /**
   * @private
   * @type {number}
   */
  this.minResolution_ = resolutionConstraintInfo.minResolution;

  /**
   * @private
   * @type {number}
   */
  this.zoomFactor_ = resolutionConstraintInfo.zoomFactor;

  /**
   * @private
   * @type {Array.<number>|undefined}
   */
  this.resolutions_ = options.resolutions;

  /**
   * @private
   * @type {number}
   */
  this.minZoom_ = resolutionConstraintInfo.minZoom;

  var centerConstraint = ol.View.createCenterConstraint_(options);
  var resolutionConstraint = resolutionConstraintInfo.constraint;
  var rotationConstraint = ol.View.createRotationConstraint_(options);

  /**
   * @private
   * @type {ol.Constraints}
   */
  this.constraints_ = new ol.Constraints(
      centerConstraint, resolutionConstraint, rotationConstraint);

  if (options.resolution !== undefined) {
    properties[ol.View.Property.RESOLUTION] = options.resolution;
  } else if (options.zoom !== undefined) {
    properties[ol.View.Property.RESOLUTION] = this.constrainResolution(
        this.maxResolution_, options.zoom - this.minZoom_);
  }
  properties[ol.View.Property.ROTATION] =
      options.rotation !== undefined ? options.rotation : 0;
  this.setProperties(properties);
};
ol.inherits(ol.View, ol.Object);


/**
 * @param {number} rotation Target rotation.
 * @param {ol.Coordinate} anchor Rotation anchor.
 * @return {ol.Coordinate|undefined} Center for rotation and anchor.
 */
ol.View.prototype.calculateCenterRotate = function(rotation, anchor) {
  var center;
  var currentCenter = this.getCenter();
  if (currentCenter !== undefined) {
    center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]];
    ol.coordinate.rotate(center, rotation - this.getRotation());
    ol.coordinate.add(center, anchor);
  }
  return center;
};


/**
 * @param {number} resolution Target resolution.
 * @param {ol.Coordinate} anchor Zoom anchor.
 * @return {ol.Coordinate|undefined} Center for resolution and anchor.
 */
ol.View.prototype.calculateCenterZoom = function(resolution, anchor) {
  var center;
  var currentCenter = this.getCenter();
  var currentResolution = this.getResolution();
  if (currentCenter !== undefined && currentResolution !== undefined) {
    var x = anchor[0] -
        resolution * (anchor[0] - currentCenter[0]) / currentResolution;
    var y = anchor[1] -
        resolution * (anchor[1] - currentCenter[1]) / currentResolution;
    center = [x, y];
  }
  return center;
};


/**
 * Get the constrained center of this view.
 * @param {ol.Coordinate|undefined} center Center.
 * @return {ol.Coordinate|undefined} Constrained center.
 * @api
 */
ol.View.prototype.constrainCenter = function(center) {
  return this.constraints_.center(center);
};


/**
 * Get the constrained resolution of this view.
 * @param {number|undefined} resolution Resolution.
 * @param {number=} opt_delta Delta. Default is `0`.
 * @param {number=} opt_direction Direction. Default is `0`.
 * @return {number|undefined} Constrained resolution.
 * @api
 */
ol.View.prototype.constrainResolution = function(
    resolution, opt_delta, opt_direction) {
  var delta = opt_delta || 0;
  var direction = opt_direction || 0;
  return this.constraints_.resolution(resolution, delta, direction);
};


/**
 * Get the constrained rotation of this view.
 * @param {number|undefined} rotation Rotation.
 * @param {number=} opt_delta Delta. Default is `0`.
 * @return {number|undefined} Constrained rotation.
 * @api
 */
ol.View.prototype.constrainRotation = function(rotation, opt_delta) {
  var delta = opt_delta || 0;
  return this.constraints_.rotation(rotation, delta);
};


/**
 * Get the view center.
 * @return {ol.Coordinate|undefined} The center of the view.
 * @observable
 * @api stable
 */
ol.View.prototype.getCenter = function() {
  return /** @type {ol.Coordinate|undefined} */ (
      this.get(ol.View.Property.CENTER));
};


/**
 * @param {Array.<number>=} opt_hints Destination array.
 * @return {Array.<number>} Hint.
 */
ol.View.prototype.getHints = function(opt_hints) {
  if (opt_hints !== undefined) {
    opt_hints[0] = this.hints_[0];
    opt_hints[1] = this.hints_[1];
    return opt_hints;
  } else {
    return this.hints_.slice();
  }
};


/**
 * Calculate the extent for the current view state and the passed size.
 * The size is the pixel dimensions of the box into which the calculated extent
 * should fit. In most cases you want to get the extent of the entire map,
 * that is `map.getSize()`.
 * @param {ol.Size} size Box pixel size.
 * @return {ol.Extent} Extent.
 * @api stable
 */
ol.View.prototype.calculateExtent = function(size) {
  var center = /** @type {!ol.Coordinate} */ (this.getCenter());
  ol.asserts.assert(center, 1); // The view center is not defined
  var resolution = /** @type {!number} */ (this.getResolution());
  ol.asserts.assert(resolution !== undefined, 2); // The view resolution is not defined
  var rotation = /** @type {!number} */ (this.getRotation());
  ol.asserts.assert(rotation !== undefined, 3); // The view rotation is not defined

  return ol.extent.getForViewAndSize(center, resolution, rotation, size);
};


/**
 * Get the maximum resolution of the view.
 * @return {number} The maximum resolution of the view.
 * @api
 */
ol.View.prototype.getMaxResolution = function() {
  return this.maxResolution_;
};


/**
 * Get the minimum resolution of the view.
 * @return {number} The minimum resolution of the view.
 * @api
 */
ol.View.prototype.getMinResolution = function() {
  return this.minResolution_;
};


/**
 * Get the view projection.
 * @return {ol.proj.Projection} The projection of the view.
 * @api stable
 */
ol.View.prototype.getProjection = function() {
  return this.projection_;
};


/**
 * Get the view resolution.
 * @return {number|undefined} The resolution of the view.
 * @observable
 * @api stable
 */
ol.View.prototype.getResolution = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.View.Property.RESOLUTION));
};


/**
 * Get the resolutions for the view. This returns the array of resolutions
 * passed to the constructor of the {ol.View}, or undefined if none were given.
 * @return {Array.<number>|undefined} The resolutions of the view.
 * @api stable
 */
ol.View.prototype.getResolutions = function() {
  return this.resolutions_;
};


/**
 * Get the resolution for a provided extent (in map units) and size (in pixels).
 * @param {ol.Extent} extent Extent.
 * @param {ol.Size} size Box pixel size.
 * @return {number} The resolution at which the provided extent will render at
 *     the given size.
 */
ol.View.prototype.getResolutionForExtent = function(extent, size) {
  var xResolution = ol.extent.getWidth(extent) / size[0];
  var yResolution = ol.extent.getHeight(extent) / size[1];
  return Math.max(xResolution, yResolution);
};


/**
 * Return a function that returns a value between 0 and 1 for a
 * resolution. Exponential scaling is assumed.
 * @param {number=} opt_power Power.
 * @return {function(number): number} Resolution for value function.
 */
ol.View.prototype.getResolutionForValueFunction = function(opt_power) {
  var power = opt_power || 2;
  var maxResolution = this.maxResolution_;
  var minResolution = this.minResolution_;
  var max = Math.log(maxResolution / minResolution) / Math.log(power);
  return (
      /**
       * @param {number} value Value.
       * @return {number} Resolution.
       */
      function(value) {
        var resolution = maxResolution / Math.pow(power, value * max);
        goog.DEBUG && console.assert(resolution >= minResolution &&
            resolution <= maxResolution,
            'calculated resolution outside allowed bounds (%s <= %s <= %s)',
            minResolution, resolution, maxResolution);
        return resolution;
      });
};


/**
 * Get the view rotation.
 * @return {number} The rotation of the view in radians.
 * @observable
 * @api stable
 */
ol.View.prototype.getRotation = function() {
  return /** @type {number} */ (this.get(ol.View.Property.ROTATION));
};


/**
 * Return a function that returns a resolution for a value between
 * 0 and 1. Exponential scaling is assumed.
 * @param {number=} opt_power Power.
 * @return {function(number): number} Value for resolution function.
 */
ol.View.prototype.getValueForResolutionFunction = function(opt_power) {
  var power = opt_power || 2;
  var maxResolution = this.maxResolution_;
  var minResolution = this.minResolution_;
  var max = Math.log(maxResolution / minResolution) / Math.log(power);
  return (
      /**
       * @param {number} resolution Resolution.
       * @return {number} Value.
       */
      function(resolution) {
        var value =
            (Math.log(maxResolution / resolution) / Math.log(power)) / max;
        goog.DEBUG && console.assert(value >= 0 && value <= 1,
            'calculated value (%s) ouside allowed range (0-1)', value);
        return value;
      });
};


/**
 * @return {olx.ViewState} View state.
 */
ol.View.prototype.getState = function() {
  goog.DEBUG && console.assert(this.isDef(),
      'the view was not defined (had no center and/or resolution)');
  var center = /** @type {ol.Coordinate} */ (this.getCenter());
  var projection = this.getProjection();
  var resolution = /** @type {number} */ (this.getResolution());
  var rotation = this.getRotation();
  return /** @type {olx.ViewState} */ ({
    center: center.slice(),
    projection: projection !== undefined ? projection : null,
    resolution: resolution,
    rotation: rotation
  });
};


/**
 * Get the current zoom level. Return undefined if the current
 * resolution is undefined or not within the "resolution constraints".
 * @return {number|undefined} Zoom.
 * @api stable
 */
ol.View.prototype.getZoom = function() {
  var zoom;
  var resolution = this.getResolution();
  if (resolution !== undefined &&
      resolution >= this.minResolution_ && resolution <= this.maxResolution_) {
    var offset = this.minZoom_ || 0;
    var max, zoomFactor;
    if (this.resolutions_) {
      var nearest = ol.array.linearFindNearest(this.resolutions_, resolution, 1);
      offset += nearest;
      if (nearest == this.resolutions_.length - 1) {
        return offset;
      }
      max = this.resolutions_[nearest];
      zoomFactor = max / this.resolutions_[nearest + 1];
    } else {
      max = this.maxResolution_;
      zoomFactor = this.zoomFactor_;
    }
    zoom = offset + Math.log(max / resolution) / Math.log(zoomFactor);
  }
  return zoom;
};


/**
 * Fit the given geometry or extent based on the given map size and border.
 * The size is pixel dimensions of the box to fit the extent into.
 * In most cases you will want to use the map size, that is `map.getSize()`.
 * Takes care of the map angle.
 * @param {ol.geom.SimpleGeometry|ol.Extent} geometry Geometry.
 * @param {ol.Size} size Box pixel size.
 * @param {olx.view.FitOptions=} opt_options Options.
 * @api
 */
ol.View.prototype.fit = function(geometry, size, opt_options) {
  if (!(geometry instanceof ol.geom.SimpleGeometry)) {
    ol.asserts.assert(Array.isArray(geometry),
        24); // Invalid extent or geometry provided as `geometry`
    ol.asserts.assert(!ol.extent.isEmpty(geometry),
        25); // Cannot fit empty extent provided as `geometry`
    geometry = ol.geom.Polygon.fromExtent(geometry);
  }

  var options = opt_options || {};

  var padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0];
  var constrainResolution = options.constrainResolution !== undefined ?
      options.constrainResolution : true;
  var nearest = options.nearest !== undefined ? options.nearest : false;
  var minResolution;
  if (options.minResolution !== undefined) {
    minResolution = options.minResolution;
  } else if (options.maxZoom !== undefined) {
    minResolution = this.constrainResolution(
        this.maxResolution_, options.maxZoom - this.minZoom_, 0);
  } else {
    minResolution = 0;
  }
  var coords = geometry.getFlatCoordinates();

  // calculate rotated extent
  var rotation = this.getRotation();
  goog.DEBUG && console.assert(rotation !== undefined, 'rotation was not defined');
  var cosAngle = Math.cos(-rotation);
  var sinAngle = Math.sin(-rotation);
  var minRotX = +Infinity;
  var minRotY = +Infinity;
  var maxRotX = -Infinity;
  var maxRotY = -Infinity;
  var stride = geometry.getStride();
  for (var i = 0, ii = coords.length; i < ii; i += stride) {
    var rotX = coords[i] * cosAngle - coords[i + 1] * sinAngle;
    var rotY = coords[i] * sinAngle + coords[i + 1] * cosAngle;
    minRotX = Math.min(minRotX, rotX);
    minRotY = Math.min(minRotY, rotY);
    maxRotX = Math.max(maxRotX, rotX);
    maxRotY = Math.max(maxRotY, rotY);
  }

  // calculate resolution
  var resolution = this.getResolutionForExtent(
      [minRotX, minRotY, maxRotX, maxRotY],
      [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]);
  resolution = isNaN(resolution) ? minResolution :
      Math.max(resolution, minResolution);
  if (constrainResolution) {
    var constrainedResolution = this.constrainResolution(resolution, 0, 0);
    if (!nearest && constrainedResolution < resolution) {
      constrainedResolution = this.constrainResolution(
          constrainedResolution, -1, 0);
    }
    resolution = constrainedResolution;
  }
  this.setResolution(resolution);

  // calculate center
  sinAngle = -sinAngle; // go back to original rotation
  var centerRotX = (minRotX + maxRotX) / 2;
  var centerRotY = (minRotY + maxRotY) / 2;
  centerRotX += (padding[1] - padding[3]) / 2 * resolution;
  centerRotY += (padding[0] - padding[2]) / 2 * resolution;
  var centerX = centerRotX * cosAngle - centerRotY * sinAngle;
  var centerY = centerRotY * cosAngle + centerRotX * sinAngle;

  this.setCenter([centerX, centerY]);
};


/**
 * Center on coordinate and view position.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.Size} size Box pixel size.
 * @param {ol.Pixel} position Position on the view to center on.
 * @api
 */
ol.View.prototype.centerOn = function(coordinate, size, position) {
  // calculate rotated position
  var rotation = this.getRotation();
  var cosAngle = Math.cos(-rotation);
  var sinAngle = Math.sin(-rotation);
  var rotX = coordinate[0] * cosAngle - coordinate[1] * sinAngle;
  var rotY = coordinate[1] * cosAngle + coordinate[0] * sinAngle;
  var resolution = this.getResolution();
  rotX += (size[0] / 2 - position[0]) * resolution;
  rotY += (position[1] - size[1] / 2) * resolution;

  // go back to original angle
  sinAngle = -sinAngle; // go back to original rotation
  var centerX = rotX * cosAngle - rotY * sinAngle;
  var centerY = rotY * cosAngle + rotX * sinAngle;

  this.setCenter([centerX, centerY]);
};


/**
 * @return {boolean} Is defined.
 */
ol.View.prototype.isDef = function() {
  return !!this.getCenter() && this.getResolution() !== undefined;
};


/**
 * Rotate the view around a given coordinate.
 * @param {number} rotation New rotation value for the view.
 * @param {ol.Coordinate=} opt_anchor The rotation center.
 * @api stable
 */
ol.View.prototype.rotate = function(rotation, opt_anchor) {
  if (opt_anchor !== undefined) {
    var center = this.calculateCenterRotate(rotation, opt_anchor);
    this.setCenter(center);
  }
  this.setRotation(rotation);
};


/**
 * Set the center of the current view.
 * @param {ol.Coordinate|undefined} center The center of the view.
 * @observable
 * @api stable
 */
ol.View.prototype.setCenter = function(center) {
  this.set(ol.View.Property.CENTER, center);
};


/**
 * @param {ol.View.Hint} hint Hint.
 * @param {number} delta Delta.
 * @return {number} New value.
 */
ol.View.prototype.setHint = function(hint, delta) {
  goog.DEBUG && console.assert(0 <= hint && hint < this.hints_.length,
      'illegal hint (%s), must be between 0 and %s', hint, this.hints_.length);
  this.hints_[hint] += delta;
  goog.DEBUG && console.assert(this.hints_[hint] >= 0,
      'Hint at %s must be positive, was %s', hint, this.hints_[hint]);
  return this.hints_[hint];
};


/**
 * Set the resolution for this view.
 * @param {number|undefined} resolution The resolution of the view.
 * @observable
 * @api stable
 */
ol.View.prototype.setResolution = function(resolution) {
  this.set(ol.View.Property.RESOLUTION, resolution);
};


/**
 * Set the rotation for this view.
 * @param {number} rotation The rotation of the view in radians.
 * @observable
 * @api stable
 */
ol.View.prototype.setRotation = function(rotation) {
  this.set(ol.View.Property.ROTATION, rotation);
};


/**
 * Zoom to a specific zoom level.
 * @param {number} zoom Zoom level.
 * @api stable
 */
ol.View.prototype.setZoom = function(zoom) {
  var resolution = this.constrainResolution(
      this.maxResolution_, zoom - this.minZoom_, 0);
  this.setResolution(resolution);
};


/**
 * @param {olx.ViewOptions} options View options.
 * @private
 * @return {ol.CenterConstraintType} The constraint.
 */
ol.View.createCenterConstraint_ = function(options) {
  if (options.extent !== undefined) {
    return ol.CenterConstraint.createExtent(options.extent);
  } else {
    return ol.CenterConstraint.none;
  }
};


/**
 * @private
 * @param {olx.ViewOptions} options View options.
 * @return {{constraint: ol.ResolutionConstraintType, maxResolution: number,
 *     minResolution: number, zoomFactor: number}} The constraint.
 */
ol.View.createResolutionConstraint_ = function(options) {
  var resolutionConstraint;
  var maxResolution;
  var minResolution;

  // TODO: move these to be ol constants
  // see https://github.com/openlayers/ol3/issues/2076
  var defaultMaxZoom = 28;
  var defaultZoomFactor = 2;

  var minZoom = options.minZoom !== undefined ?
      options.minZoom : ol.DEFAULT_MIN_ZOOM;

  var maxZoom = options.maxZoom !== undefined ?
      options.maxZoom : defaultMaxZoom;

  var zoomFactor = options.zoomFactor !== undefined ?
      options.zoomFactor : defaultZoomFactor;

  if (options.resolutions !== undefined) {
    var resolutions = options.resolutions;
    maxResolution = resolutions[0];
    minResolution = resolutions[resolutions.length - 1];
    resolutionConstraint = ol.ResolutionConstraint.createSnapToResolutions(
        resolutions);
  } else {
    // calculate the default min and max resolution
    var projection = ol.proj.createProjection(options.projection, 'EPSG:3857');
    var extent = projection.getExtent();
    var size = !extent ?
        // use an extent that can fit the whole world if need be
        360 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] /
            projection.getMetersPerUnit() :
        Math.max(ol.extent.getWidth(extent), ol.extent.getHeight(extent));

    var defaultMaxResolution = size / ol.DEFAULT_TILE_SIZE / Math.pow(
        defaultZoomFactor, ol.DEFAULT_MIN_ZOOM);

    var defaultMinResolution = defaultMaxResolution / Math.pow(
        defaultZoomFactor, defaultMaxZoom - ol.DEFAULT_MIN_ZOOM);

    // user provided maxResolution takes precedence
    maxResolution = options.maxResolution;
    if (maxResolution !== undefined) {
      minZoom = 0;
    } else {
      maxResolution = defaultMaxResolution / Math.pow(zoomFactor, minZoom);
    }

    // user provided minResolution takes precedence
    minResolution = options.minResolution;
    if (minResolution === undefined) {
      if (options.maxZoom !== undefined) {
        if (options.maxResolution !== undefined) {
          minResolution = maxResolution / Math.pow(zoomFactor, maxZoom);
        } else {
          minResolution = defaultMaxResolution / Math.pow(zoomFactor, maxZoom);
        }
      } else {
        minResolution = defaultMinResolution;
      }
    }

    // given discrete zoom levels, minResolution may be different than provided
    maxZoom = minZoom + Math.floor(
        Math.log(maxResolution / minResolution) / Math.log(zoomFactor));
    minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom);

    resolutionConstraint = ol.ResolutionConstraint.createSnapToPower(
        zoomFactor, maxResolution, maxZoom - minZoom);
  }
  return {constraint: resolutionConstraint, maxResolution: maxResolution,
    minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor};
};


/**
 * @private
 * @param {olx.ViewOptions} options View options.
 * @return {ol.RotationConstraintType} Rotation constraint.
 */
ol.View.createRotationConstraint_ = function(options) {
  var enableRotation = options.enableRotation !== undefined ?
      options.enableRotation : true;
  if (enableRotation) {
    var constrainRotation = options.constrainRotation;
    if (constrainRotation === undefined || constrainRotation === true) {
      return ol.RotationConstraint.createSnapToZero();
    } else if (constrainRotation === false) {
      return ol.RotationConstraint.none;
    } else if (typeof constrainRotation === 'number') {
      return ol.RotationConstraint.createSnapToN(constrainRotation);
    } else {
      goog.DEBUG && console.assert(false,
          'illegal option for constrainRotation (%s)', constrainRotation);
      return ol.RotationConstraint.none;
    }
  } else {
    return ol.RotationConstraint.disable;
  }
};


/**
 * @enum {string}
 */
ol.View.Property = {
  CENTER: 'center',
  RESOLUTION: 'resolution',
  ROTATION: 'rotation'
};


/**
 * @enum {number}
 */
ol.View.Hint = {
  ANIMATING: 0,
  INTERACTING: 1
};

goog.provide('ol.easing');


/**
 * Start slow and speed up.
 * @param {number} t Input between 0 and 1.
 * @return {number} Output between 0 and 1.
 * @api
 */
ol.easing.easeIn = function(t) {
  return Math.pow(t, 3);
};


/**
 * Start fast and slow down.
 * @param {number} t Input between 0 and 1.
 * @return {number} Output between 0 and 1.
 * @api
 */
ol.easing.easeOut = function(t) {
  return 1 - ol.easing.easeIn(1 - t);
};


/**
 * Start slow, speed up, and then slow down again.
 * @param {number} t Input between 0 and 1.
 * @return {number} Output between 0 and 1.
 * @api
 */
ol.easing.inAndOut = function(t) {
  return 3 * t * t - 2 * t * t * t;
};


/**
 * Maintain a constant speed over time.
 * @param {number} t Input between 0 and 1.
 * @return {number} Output between 0 and 1.
 * @api
 */
ol.easing.linear = function(t) {
  return t;
};


/**
 * Start slow, speed up, and at the very end slow down again.  This has the
 * same general behavior as {@link ol.easing.inAndOut}, but the final slowdown
 * is delayed.
 * @param {number} t Input between 0 and 1.
 * @return {number} Output between 0 and 1.
 * @api
 */
ol.easing.upAndDown = function(t) {
  if (t < 0.5) {
    return ol.easing.inAndOut(2 * t);
  } else {
    return 1 - ol.easing.inAndOut(2 * (t - 0.5));
  }
};

goog.provide('ol.animation');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.coordinate');
goog.require('ol.easing');


/**
 * Generate an animated transition that will "bounce" the resolution as it
 * approaches the final value.
 * @param {olx.animation.BounceOptions} options Bounce options.
 * @return {ol.PreRenderFunction} Pre-render function.
 * @api
 */
ol.animation.bounce = function(options) {
  var resolution = options.resolution;
  var start = options.start ? options.start : Date.now();
  var duration = options.duration !== undefined ? options.duration : 1000;
  var easing = options.easing ?
      options.easing : ol.easing.upAndDown;
  return (
      /**
       * @param {ol.Map} map Map.
       * @param {?olx.FrameState} frameState Frame state.
       * @return {boolean} Run this function in the next frame.
       */
      function(map, frameState) {
        if (frameState.time < start) {
          frameState.animate = true;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else if (frameState.time < start + duration) {
          var delta = easing((frameState.time - start) / duration);
          var deltaResolution = resolution - frameState.viewState.resolution;
          frameState.animate = true;
          frameState.viewState.resolution += delta * deltaResolution;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else {
          return false;
        }
      });
};


/**
 * Generate an animated transition while updating the view center.
 * @param {olx.animation.PanOptions} options Pan options.
 * @return {ol.PreRenderFunction} Pre-render function.
 * @api
 */
ol.animation.pan = function(options) {
  var source = options.source;
  var start = options.start ? options.start : Date.now();
  var sourceX = source[0];
  var sourceY = source[1];
  var duration = options.duration !== undefined ? options.duration : 1000;
  var easing = options.easing ?
      options.easing : ol.easing.inAndOut;
  return (
      /**
       * @param {ol.Map} map Map.
       * @param {?olx.FrameState} frameState Frame state.
       * @return {boolean} Run this function in the next frame.
       */
      function(map, frameState) {
        if (frameState.time < start) {
          frameState.animate = true;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else if (frameState.time < start + duration) {
          var delta = 1 - easing((frameState.time - start) / duration);
          var deltaX = sourceX - frameState.viewState.center[0];
          var deltaY = sourceY - frameState.viewState.center[1];
          frameState.animate = true;
          frameState.viewState.center[0] += delta * deltaX;
          frameState.viewState.center[1] += delta * deltaY;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else {
          return false;
        }
      });
};


/**
 * Generate an animated transition while updating the view rotation.
 * @param {olx.animation.RotateOptions} options Rotate options.
 * @return {ol.PreRenderFunction} Pre-render function.
 * @api
 */
ol.animation.rotate = function(options) {
  var sourceRotation = options.rotation ? options.rotation : 0;
  var start = options.start ? options.start : Date.now();
  var duration = options.duration !== undefined ? options.duration : 1000;
  var easing = options.easing ?
      options.easing : ol.easing.inAndOut;
  var anchor = options.anchor ?
      options.anchor : null;

  return (
      /**
       * @param {ol.Map} map Map.
       * @param {?olx.FrameState} frameState Frame state.
       * @return {boolean} Run this function in the next frame.
       */
      function(map, frameState) {
        if (frameState.time < start) {
          frameState.animate = true;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else if (frameState.time < start + duration) {
          var delta = 1 - easing((frameState.time - start) / duration);
          var deltaRotation =
              (sourceRotation - frameState.viewState.rotation) * delta;
          frameState.animate = true;
          frameState.viewState.rotation += deltaRotation;
          if (anchor) {
            var center = frameState.viewState.center;
            ol.coordinate.sub(center, anchor);
            ol.coordinate.rotate(center, deltaRotation);
            ol.coordinate.add(center, anchor);
          }
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else {
          return false;
        }
      });
};


/**
 * Generate an animated transition while updating the view resolution.
 * @param {olx.animation.ZoomOptions} options Zoom options.
 * @return {ol.PreRenderFunction} Pre-render function.
 * @api
 */
ol.animation.zoom = function(options) {
  var sourceResolution = options.resolution;
  var start = options.start ? options.start : Date.now();
  var duration = options.duration !== undefined ? options.duration : 1000;
  var easing = options.easing ?
      options.easing : ol.easing.inAndOut;
  return (
      /**
       * @param {ol.Map} map Map.
       * @param {?olx.FrameState} frameState Frame state.
       * @return {boolean} Run this function in the next frame.
       */
      function(map, frameState) {
        if (frameState.time < start) {
          frameState.animate = true;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else if (frameState.time < start + duration) {
          var delta = 1 - easing((frameState.time - start) / duration);
          var deltaResolution =
              sourceResolution - frameState.viewState.resolution;
          frameState.animate = true;
          frameState.viewState.resolution += delta * deltaResolution;
          frameState.viewHints[ol.View.Hint.ANIMATING] += 1;
          return true;
        } else {
          return false;
        }
      });
};

goog.provide('ol.TileRange');

goog.require('ol.asserts');


/**
 * A representation of a contiguous block of tiles.  A tile range is specified
 * by its min/max tile coordinates and is inclusive of coordinates.
 *
 * @constructor
 * @param {number} minX Minimum X.
 * @param {number} maxX Maximum X.
 * @param {number} minY Minimum Y.
 * @param {number} maxY Maximum Y.
 * @struct
 */
ol.TileRange = function(minX, maxX, minY, maxY) {

  /**
   * @type {number}
   */
  this.minX = minX;

  /**
   * @type {number}
   */
  this.maxX = maxX;

  /**
   * @type {number}
   */
  this.minY = minY;

  /**
   * @type {number}
   */
  this.maxY = maxY;

};


/**
 * @param {...ol.TileCoord} var_args Tile coordinates.
 * @return {!ol.TileRange} Bounding tile box.
 */
ol.TileRange.boundingTileRange = function(var_args) {
  var tileCoord0 = /** @type {ol.TileCoord} */ (arguments[0]);
  var tileCoord0Z = tileCoord0[0];
  var tileCoord0X = tileCoord0[1];
  var tileCoord0Y = tileCoord0[2];
  var tileRange = new ol.TileRange(tileCoord0X, tileCoord0X,
                                   tileCoord0Y, tileCoord0Y);
  var i, ii, tileCoord, tileCoordX, tileCoordY, tileCoordZ;
  for (i = 1, ii = arguments.length; i < ii; ++i) {
    tileCoord = /** @type {ol.TileCoord} */ (arguments[i]);
    tileCoordZ = tileCoord[0];
    tileCoordX = tileCoord[1];
    tileCoordY = tileCoord[2];
    ol.asserts.assert(tileCoordZ == tileCoord0Z,
        23); // The passed `ol.TileCoord`s must all have the same `z` value
    tileRange.minX = Math.min(tileRange.minX, tileCoordX);
    tileRange.maxX = Math.max(tileRange.maxX, tileCoordX);
    tileRange.minY = Math.min(tileRange.minY, tileCoordY);
    tileRange.maxY = Math.max(tileRange.maxY, tileCoordY);
  }
  return tileRange;
};


/**
 * @param {number} minX Minimum X.
 * @param {number} maxX Maximum X.
 * @param {number} minY Minimum Y.
 * @param {number} maxY Maximum Y.
 * @param {ol.TileRange|undefined} tileRange TileRange.
 * @return {ol.TileRange} Tile range.
 */
ol.TileRange.createOrUpdate = function(minX, maxX, minY, maxY, tileRange) {
  if (tileRange !== undefined) {
    tileRange.minX = minX;
    tileRange.maxX = maxX;
    tileRange.minY = minY;
    tileRange.maxY = maxY;
    return tileRange;
  } else {
    return new ol.TileRange(minX, maxX, minY, maxY);
  }
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {boolean} Contains tile coordinate.
 */
ol.TileRange.prototype.contains = function(tileCoord) {
  return this.containsXY(tileCoord[1], tileCoord[2]);
};


/**
 * @param {ol.TileRange} tileRange Tile range.
 * @return {boolean} Contains.
 */
ol.TileRange.prototype.containsTileRange = function(tileRange) {
  return this.minX <= tileRange.minX && tileRange.maxX <= this.maxX &&
      this.minY <= tileRange.minY && tileRange.maxY <= this.maxY;
};


/**
 * @param {number} x Tile coordinate x.
 * @param {number} y Tile coordinate y.
 * @return {boolean} Contains coordinate.
 */
ol.TileRange.prototype.containsXY = function(x, y) {
  return this.minX <= x && x <= this.maxX && this.minY <= y && y <= this.maxY;
};


/**
 * @param {ol.TileRange} tileRange Tile range.
 * @return {boolean} Equals.
 */
ol.TileRange.prototype.equals = function(tileRange) {
  return this.minX == tileRange.minX && this.minY == tileRange.minY &&
      this.maxX == tileRange.maxX && this.maxY == tileRange.maxY;
};


/**
 * @param {ol.TileRange} tileRange Tile range.
 */
ol.TileRange.prototype.extend = function(tileRange) {
  if (tileRange.minX < this.minX) {
    this.minX = tileRange.minX;
  }
  if (tileRange.maxX > this.maxX) {
    this.maxX = tileRange.maxX;
  }
  if (tileRange.minY < this.minY) {
    this.minY = tileRange.minY;
  }
  if (tileRange.maxY > this.maxY) {
    this.maxY = tileRange.maxY;
  }
};


/**
 * @return {number} Height.
 */
ol.TileRange.prototype.getHeight = function() {
  return this.maxY - this.minY + 1;
};


/**
 * @return {ol.Size} Size.
 */
ol.TileRange.prototype.getSize = function() {
  return [this.getWidth(), this.getHeight()];
};


/**
 * @return {number} Width.
 */
ol.TileRange.prototype.getWidth = function() {
  return this.maxX - this.minX + 1;
};


/**
 * @param {ol.TileRange} tileRange Tile range.
 * @return {boolean} Intersects.
 */
ol.TileRange.prototype.intersects = function(tileRange) {
  return this.minX <= tileRange.maxX &&
      this.maxX >= tileRange.minX &&
      this.minY <= tileRange.maxY &&
      this.maxY >= tileRange.minY;
};

goog.provide('ol.size');


/**
 * Returns a buffered size.
 * @param {ol.Size} size Size.
 * @param {number} buffer Buffer.
 * @param {ol.Size=} opt_size Optional reusable size array.
 * @return {ol.Size} The buffered size.
 */
ol.size.buffer = function(size, buffer, opt_size) {
  if (opt_size === undefined) {
    opt_size = [0, 0];
  }
  opt_size[0] = size[0] + 2 * buffer;
  opt_size[1] = size[1] + 2 * buffer;
  return opt_size;
};


/**
 * Compares sizes for equality.
 * @param {ol.Size} a Size.
 * @param {ol.Size} b Size.
 * @return {boolean} Equals.
 */
ol.size.equals = function(a, b) {
  return a[0] == b[0] && a[1] == b[1];
};


/**
 * Determines if a size has a positive area.
 * @param {ol.Size} size The size to test.
 * @return {boolean} The size has a positive area.
 */
ol.size.hasArea = function(size) {
  return size[0] > 0 && size[1] > 0;
};


/**
 * Returns a size scaled by a ratio. The result will be an array of integers.
 * @param {ol.Size} size Size.
 * @param {number} ratio Ratio.
 * @param {ol.Size=} opt_size Optional reusable size array.
 * @return {ol.Size} The scaled size.
 */
ol.size.scale = function(size, ratio, opt_size) {
  if (opt_size === undefined) {
    opt_size = [0, 0];
  }
  opt_size[0] = (size[0] * ratio + 0.5) | 0;
  opt_size[1] = (size[1] * ratio + 0.5) | 0;
  return opt_size;
};


/**
 * Returns an `ol.Size` array for the passed in number (meaning: square) or
 * `ol.Size` array.
 * (meaning: non-square),
 * @param {number|ol.Size} size Width and height.
 * @param {ol.Size=} opt_size Optional reusable size array.
 * @return {ol.Size} Size.
 * @api stable
 */
ol.size.toSize = function(size, opt_size) {
  if (Array.isArray(size)) {
    return size;
  } else {
    if (opt_size === undefined) {
      opt_size = [size, size];
    } else {
      opt_size[0] = opt_size[1] = /** @type {number} */ (size);
    }
    return opt_size;
  }
};

goog.provide('ol.tilecoord');


/**
 * @enum {number}
 */
ol.QuadKeyCharCode = {
  ZERO: '0'.charCodeAt(0),
  ONE: '1'.charCodeAt(0),
  TWO: '2'.charCodeAt(0),
  THREE: '3'.charCodeAt(0)
};


/**
 * @param {string} str String that follows pattern “z/x/y” where x, y and z are
 *   numbers.
 * @return {ol.TileCoord} Tile coord.
 */
ol.tilecoord.createFromString = function(str) {
  var v = str.split('/');
  goog.DEBUG && console.assert(v.length === 3,
      'must provide a string in "z/x/y" format, got "%s"', str);
  return v.map(function(e) {
    return parseInt(e, 10);
  });
};


/**
 * @param {number} z Z.
 * @param {number} x X.
 * @param {number} y Y.
 * @param {ol.TileCoord=} opt_tileCoord Tile coordinate.
 * @return {ol.TileCoord} Tile coordinate.
 */
ol.tilecoord.createOrUpdate = function(z, x, y, opt_tileCoord) {
  if (opt_tileCoord !== undefined) {
    opt_tileCoord[0] = z;
    opt_tileCoord[1] = x;
    opt_tileCoord[2] = y;
    return opt_tileCoord;
  } else {
    return [z, x, y];
  }
};


/**
 * @param {number} z Z.
 * @param {number} x X.
 * @param {number} y Y.
 * @return {string} Key.
 */
ol.tilecoord.getKeyZXY = function(z, x, y) {
  return z + '/' + x + '/' + y;
};


/**
 * @param {ol.TileCoord} tileCoord Tile coord.
 * @return {number} Hash.
 */
ol.tilecoord.hash = function(tileCoord) {
  return (tileCoord[1] << tileCoord[0]) + tileCoord[2];
};


/**
 * @param {ol.TileCoord} tileCoord Tile coord.
 * @return {string} Quad key.
 */
ol.tilecoord.quadKey = function(tileCoord) {
  var z = tileCoord[0];
  var digits = new Array(z);
  var mask = 1 << (z - 1);
  var i, charCode;
  for (i = 0; i < z; ++i) {
    charCode = ol.QuadKeyCharCode.ZERO;
    if (tileCoord[1] & mask) {
      charCode += 1;
    }
    if (tileCoord[2] & mask) {
      charCode += 2;
    }
    digits[i] = String.fromCharCode(charCode);
    mask >>= 1;
  }
  return digits.join('');
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @return {boolean} Tile coordinate is within extent and zoom level range.
 */
ol.tilecoord.withinExtentAndZ = function(tileCoord, tileGrid) {
  var z = tileCoord[0];
  var x = tileCoord[1];
  var y = tileCoord[2];

  if (tileGrid.getMinZoom() > z || z > tileGrid.getMaxZoom()) {
    return false;
  }
  var extent = tileGrid.getExtent();
  var tileRange;
  if (!extent) {
    tileRange = tileGrid.getFullTileRange(z);
  } else {
    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
  }
  if (!tileRange) {
    return true;
  } else {
    return tileRange.containsXY(x, y);
  }
};

goog.provide('ol.tilegrid.TileGrid');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.TileRange');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.size');
goog.require('ol.tilecoord');


/**
 * @classdesc
 * Base class for setting the grid pattern for sources accessing tiled-image
 * servers.
 *
 * @constructor
 * @param {olx.tilegrid.TileGridOptions} options Tile grid options.
 * @struct
 * @api stable
 */
ol.tilegrid.TileGrid = function(options) {

  /**
   * @protected
   * @type {number}
   */
  this.minZoom = options.minZoom !== undefined ? options.minZoom : 0;

  /**
   * @private
   * @type {!Array.<number>}
   */
  this.resolutions_ = options.resolutions;
  ol.asserts.assert(ol.array.isSorted(this.resolutions_, function(a, b) {
    return b - a;
  }, true), 17); // `resolutions` must be sorted in descending order

  /**
   * @protected
   * @type {number}
   */
  this.maxZoom = this.resolutions_.length - 1;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.origin_ = options.origin !== undefined ? options.origin : null;

  /**
   * @private
   * @type {Array.<ol.Coordinate>}
   */
  this.origins_ = null;
  if (options.origins !== undefined) {
    this.origins_ = options.origins;
    ol.asserts.assert(this.origins_.length == this.resolutions_.length,
        20); // Number of `origins` and `resolutions` must be equal
  }

  var extent = options.extent;

  if (extent !== undefined &&
      !this.origin_ && !this.origins_) {
    this.origin_ = ol.extent.getTopLeft(extent);
  }

  ol.asserts.assert(
      (!this.origin_ && this.origins_) || (this.origin_ && !this.origins_),
      18); // Either `origin` or `origins` must be configured, never both

  /**
   * @private
   * @type {Array.<number|ol.Size>}
   */
  this.tileSizes_ = null;
  if (options.tileSizes !== undefined) {
    this.tileSizes_ = options.tileSizes;
    ol.asserts.assert(this.tileSizes_.length == this.resolutions_.length,
        19); // Number of `tileSizes` and `resolutions` must be equal
  }

  /**
   * @private
   * @type {number|ol.Size}
   */
  this.tileSize_ = options.tileSize !== undefined ?
      options.tileSize :
      !this.tileSizes_ ? ol.DEFAULT_TILE_SIZE : null;
  ol.asserts.assert(
      (!this.tileSize_ && this.tileSizes_) ||
      (this.tileSize_ && !this.tileSizes_),
      22); // Either `tileSize` or `tileSizes` must be configured, never both

  /**
   * @private
   * @type {ol.Extent}
   */
  this.extent_ = extent !== undefined ? extent : null;


  /**
   * @private
   * @type {Array.<ol.TileRange>}
   */
  this.fullTileRanges_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.tmpSize_ = [0, 0];

  if (options.sizes !== undefined) {
    goog.DEBUG && console.assert(options.sizes.length == this.resolutions_.length,
        'number of sizes and resolutions must be equal');
    this.fullTileRanges_ = options.sizes.map(function(size, z) {
      goog.DEBUG && console.assert(size[0] !== 0, 'width must not be 0');
      goog.DEBUG && console.assert(size[1] !== 0, 'height must not be 0');
      var tileRange = new ol.TileRange(
          Math.min(0, size[0]), Math.max(size[0] - 1, -1),
          Math.min(0, size[1]), Math.max(size[1] - 1, -1));
      return tileRange;
    }, this);
  } else if (extent) {
    this.calculateTileRanges_(extent);
  }

};


/**
 * @private
 * @type {ol.TileCoord}
 */
ol.tilegrid.TileGrid.tmpTileCoord_ = [0, 0, 0];


/**
 * Call a function with each tile coordinate for a given extent and zoom level.
 *
 * @param {ol.Extent} extent Extent.
 * @param {number} zoom Zoom level.
 * @param {function(ol.TileCoord)} callback Function called with each tile coordinate.
 * @api
 */
ol.tilegrid.TileGrid.prototype.forEachTileCoord = function(extent, zoom, callback) {
  var tileRange = this.getTileRangeForExtentAndZ(extent, zoom);
  for (var i = tileRange.minX, ii = tileRange.maxX; i <= ii; ++i) {
    for (var j = tileRange.minY, jj = tileRange.maxY; j <= jj; ++j) {
      callback([zoom, i, j]);
    }
  }
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {function(this: T, number, ol.TileRange): boolean} callback Callback.
 * @param {T=} opt_this The object to use as `this` in `callback`.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {boolean} Callback succeeded.
 * @template T
 */
ol.tilegrid.TileGrid.prototype.forEachTileCoordParentTileRange = function(tileCoord, callback, opt_this, opt_tileRange, opt_extent) {
  var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
  var z = tileCoord[0] - 1;
  while (z >= this.minZoom) {
    if (callback.call(opt_this, z,
        this.getTileRangeForExtentAndZ(tileCoordExtent, z, opt_tileRange))) {
      return true;
    }
    --z;
  }
  return false;
};


/**
 * Get the extent for this tile grid, if it was configured.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.TileGrid.prototype.getExtent = function() {
  return this.extent_;
};


/**
 * Get the maximum zoom level for the grid.
 * @return {number} Max zoom.
 * @api
 */
ol.tilegrid.TileGrid.prototype.getMaxZoom = function() {
  return this.maxZoom;
};


/**
 * Get the minimum zoom level for the grid.
 * @return {number} Min zoom.
 * @api
 */
ol.tilegrid.TileGrid.prototype.getMinZoom = function() {
  return this.minZoom;
};


/**
 * Get the origin for the grid at the given zoom level.
 * @param {number} z Z.
 * @return {ol.Coordinate} Origin.
 * @api stable
 */
ol.tilegrid.TileGrid.prototype.getOrigin = function(z) {
  if (this.origin_) {
    return this.origin_;
  } else {
    goog.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
        'given z is not in allowed range (%s <= %s <= %s)',
        this.minZoom, z, this.maxZoom);
    return this.origins_[z];
  }
};


/**
 * Get the resolution for the given zoom level.
 * @param {number} z Z.
 * @return {number} Resolution.
 * @api stable
 */
ol.tilegrid.TileGrid.prototype.getResolution = function(z) {
  goog.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
      'given z is not in allowed range (%s <= %s <= %s)',
      this.minZoom, z, this.maxZoom);
  return this.resolutions_[z];
};


/**
 * Get the list of resolutions for the tile grid.
 * @return {Array.<number>} Resolutions.
 * @api stable
 */
ol.tilegrid.TileGrid.prototype.getResolutions = function() {
  return this.resolutions_;
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordChildTileRange = function(tileCoord, opt_tileRange, opt_extent) {
  if (tileCoord[0] < this.maxZoom) {
    var tileCoordExtent = this.getTileCoordExtent(tileCoord, opt_extent);
    return this.getTileRangeForExtentAndZ(
        tileCoordExtent, tileCoord[0] + 1, opt_tileRange);
  } else {
    return null;
  }
};


/**
 * @param {number} z Z.
 * @param {ol.TileRange} tileRange Tile range.
 * @param {ol.Extent=} opt_extent Temporary ol.Extent object.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeExtent = function(z, tileRange, opt_extent) {
  var origin = this.getOrigin(z);
  var resolution = this.getResolution(z);
  var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_);
  var minX = origin[0] + tileRange.minX * tileSize[0] * resolution;
  var maxX = origin[0] + (tileRange.maxX + 1) * tileSize[0] * resolution;
  var minY = origin[1] + tileRange.minY * tileSize[1] * resolution;
  var maxY = origin[1] + (tileRange.maxY + 1) * tileSize[1] * resolution;
  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(extent, resolution, opt_tileRange) {
  var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_;
  this.getTileCoordForXYAndResolution_(
      extent[0], extent[1], resolution, false, tileCoord);
  var minX = tileCoord[1];
  var minY = tileCoord[2];
  this.getTileCoordForXYAndResolution_(
      extent[2], extent[3], resolution, true, tileCoord);
  return ol.TileRange.createOrUpdate(
      minX, tileCoord[1], minY, tileCoord[2], opt_tileRange);
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} z Z.
 * @param {ol.TileRange=} opt_tileRange Temporary tile range object.
 * @return {ol.TileRange} Tile range.
 */
ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndZ = function(extent, z, opt_tileRange) {
  var resolution = this.getResolution(z);
  return this.getTileRangeForExtentAndResolution(
      extent, resolution, opt_tileRange);
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {ol.Coordinate} Tile center.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordCenter = function(tileCoord) {
  var origin = this.getOrigin(tileCoord[0]);
  var resolution = this.getResolution(tileCoord[0]);
  var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_);
  return [
    origin[0] + (tileCoord[1] + 0.5) * tileSize[0] * resolution,
    origin[1] + (tileCoord[2] + 0.5) * tileSize[1] * resolution
  ];
};


/**
 * Get the extent of a tile coordinate.
 *
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Extent=} opt_extent Temporary extent object.
 * @return {ol.Extent} Extent.
 * @api
 */
ol.tilegrid.TileGrid.prototype.getTileCoordExtent = function(tileCoord, opt_extent) {
  var origin = this.getOrigin(tileCoord[0]);
  var resolution = this.getResolution(tileCoord[0]);
  var tileSize = ol.size.toSize(this.getTileSize(tileCoord[0]), this.tmpSize_);
  var minX = origin[0] + tileCoord[1] * tileSize[0] * resolution;
  var minY = origin[1] + tileCoord[2] * tileSize[1] * resolution;
  var maxX = minX + tileSize[0] * resolution;
  var maxY = minY + tileSize[1] * resolution;
  return ol.extent.createOrUpdate(minX, minY, maxX, maxY, opt_extent);
};


/**
 * Get the tile coordinate for the given map coordinate and resolution.  This
 * method considers that coordinates that intersect tile boundaries should be
 * assigned the higher tile coordinate.
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 * @api
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution = function(coordinate, resolution, opt_tileCoord) {
  return this.getTileCoordForXYAndResolution_(
      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
};


/**
 * @param {number} x X.
 * @param {number} y Y.
 * @param {number} resolution Resolution.
 * @param {boolean} reverseIntersectionPolicy Instead of letting edge
 *     intersections go to the higher tile coordinate, let edge intersections
 *     go to the lower tile coordinate.
 * @param {ol.TileCoord=} opt_tileCoord Temporary ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 * @private
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
    x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) {
  var z = this.getZForResolution(resolution);
  var scale = resolution / this.getResolution(z);
  var origin = this.getOrigin(z);
  var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_);

  var adjustX = reverseIntersectionPolicy ? 0.5 : 0;
  var adjustY = reverseIntersectionPolicy ? 0 : 0.5;
  var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX);
  var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY);
  var tileCoordX = scale * xFromOrigin / tileSize[0];
  var tileCoordY = scale * yFromOrigin / tileSize[1];

  if (reverseIntersectionPolicy) {
    tileCoordX = Math.ceil(tileCoordX) - 1;
    tileCoordY = Math.ceil(tileCoordY) - 1;
  } else {
    tileCoordX = Math.floor(tileCoordX);
    tileCoordY = Math.floor(tileCoordY);
  }

  return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
};


/**
 * Get a tile coordinate given a map coordinate and zoom level.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} z Zoom level.
 * @param {ol.TileCoord=} opt_tileCoord Destination ol.TileCoord object.
 * @return {ol.TileCoord} Tile coordinate.
 * @api
 */
ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ = function(coordinate, z, opt_tileCoord) {
  var resolution = this.getResolution(z);
  return this.getTileCoordForXYAndResolution_(
      coordinate[0], coordinate[1], resolution, false, opt_tileCoord);
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @return {number} Tile resolution.
 */
ol.tilegrid.TileGrid.prototype.getTileCoordResolution = function(tileCoord) {
  goog.DEBUG && console.assert(
      this.minZoom <= tileCoord[0] && tileCoord[0] <= this.maxZoom,
      'z of given tilecoord is not in allowed range (%s <= %s <= %s',
      this.minZoom, tileCoord[0], this.maxZoom);
  return this.resolutions_[tileCoord[0]];
};


/**
 * Get the tile size for a zoom level. The type of the return value matches the
 * `tileSize` or `tileSizes` that the tile grid was configured with. To always
 * get an `ol.Size`, run the result through `ol.size.toSize()`.
 * @param {number} z Z.
 * @return {number|ol.Size} Tile size.
 * @api stable
 */
ol.tilegrid.TileGrid.prototype.getTileSize = function(z) {
  if (this.tileSize_) {
    return this.tileSize_;
  } else {
    goog.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
        'z is not in allowed range (%s <= %s <= %s',
        this.minZoom, z, this.maxZoom);
    return this.tileSizes_[z];
  }
};


/**
 * @param {number} z Zoom level.
 * @return {ol.TileRange} Extent tile range for the specified zoom level.
 */
ol.tilegrid.TileGrid.prototype.getFullTileRange = function(z) {
  if (!this.fullTileRanges_) {
    return null;
  } else {
    goog.DEBUG && console.assert(this.minZoom <= z && z <= this.maxZoom,
        'z is not in allowed range (%s <= %s <= %s',
        this.minZoom, z, this.maxZoom);
    return this.fullTileRanges_[z];
  }
};


/**
 * @param {number} resolution Resolution.
 * @param {number=} opt_direction If 0, the nearest resolution will be used.
 *     If 1, the nearest lower resolution will be used. If -1, the nearest
 *     higher resolution will be used. Default is 0.
 * @return {number} Z.
 * @api
 */
ol.tilegrid.TileGrid.prototype.getZForResolution = function(
    resolution, opt_direction) {
  var z = ol.array.linearFindNearest(this.resolutions_, resolution,
      opt_direction || 0);
  return ol.math.clamp(z, this.minZoom, this.maxZoom);
};


/**
 * @param {!ol.Extent} extent Extent for this tile grid.
 * @private
 */
ol.tilegrid.TileGrid.prototype.calculateTileRanges_ = function(extent) {
  var length = this.resolutions_.length;
  var fullTileRanges = new Array(length);
  for (var z = this.minZoom; z < length; ++z) {
    fullTileRanges[z] = this.getTileRangeForExtentAndZ(extent, z);
  }
  this.fullTileRanges_ = fullTileRanges;
};

goog.provide('ol.tilegrid');

goog.require('ol');
goog.require('ol.size');
goog.require('ol.extent');
goog.require('ol.extent.Corner');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.proj.METERS_PER_UNIT');
goog.require('ol.proj.Units');
goog.require('ol.tilegrid.TileGrid');


/**
 * @param {ol.proj.Projection} projection Projection.
 * @return {!ol.tilegrid.TileGrid} Default tile grid for the passed projection.
 */
ol.tilegrid.getForProjection = function(projection) {
  var tileGrid = projection.getDefaultTileGrid();
  if (!tileGrid) {
    tileGrid = ol.tilegrid.createForProjection(projection);
    projection.setDefaultTileGrid(tileGrid);
  }
  return tileGrid;
};


/**
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.proj.Projection} projection Projection.
 * @return {ol.TileCoord} Tile coordinate.
 */
ol.tilegrid.wrapX = function(tileGrid, tileCoord, projection) {
  var z = tileCoord[0];
  var center = tileGrid.getTileCoordCenter(tileCoord);
  var projectionExtent = ol.tilegrid.extentFromProjection(projection);
  if (!ol.extent.containsCoordinate(projectionExtent, center)) {
    var worldWidth = ol.extent.getWidth(projectionExtent);
    var worldsAway = Math.ceil((projectionExtent[0] - center[0]) / worldWidth);
    center[0] += worldWidth * worldsAway;
    return tileGrid.getTileCoordForCoordAndZ(center, z);
  } else {
    return tileCoord;
  }
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number=} opt_maxZoom Maximum zoom level (default is
 *     ol.DEFAULT_MAX_ZOOM).
 * @param {number|ol.Size=} opt_tileSize Tile size (default uses
 *     ol.DEFAULT_TILE_SIZE).
 * @param {ol.extent.Corner=} opt_corner Extent corner (default is
 *     ol.extent.Corner.TOP_LEFT).
 * @return {!ol.tilegrid.TileGrid} TileGrid instance.
 */
ol.tilegrid.createForExtent = function(extent, opt_maxZoom, opt_tileSize, opt_corner) {
  var corner = opt_corner !== undefined ?
      opt_corner : ol.extent.Corner.TOP_LEFT;

  var resolutions = ol.tilegrid.resolutionsFromExtent(
      extent, opt_maxZoom, opt_tileSize);

  return new ol.tilegrid.TileGrid({
    extent: extent,
    origin: ol.extent.getCorner(extent, corner),
    resolutions: resolutions,
    tileSize: opt_tileSize
  });
};


/**
 * Creates a tile grid with a standard XYZ tiling scheme.
 * @param {olx.tilegrid.XYZOptions=} opt_options Tile grid options.
 * @return {ol.tilegrid.TileGrid} Tile grid instance.
 * @api
 */
ol.tilegrid.createXYZ = function(opt_options) {
  var options = /** @type {olx.tilegrid.TileGridOptions} */ ({});
  ol.obj.assign(options, opt_options !== undefined ?
      opt_options : /** @type {olx.tilegrid.XYZOptions} */ ({}));
  if (options.extent === undefined) {
    options.extent = ol.proj.get('EPSG:3857').getExtent();
  }
  options.resolutions = ol.tilegrid.resolutionsFromExtent(
      options.extent, options.maxZoom, options.tileSize);
  delete options.maxZoom;

  return new ol.tilegrid.TileGrid(options);
};


/**
 * Create a resolutions array from an extent.  A zoom factor of 2 is assumed.
 * @param {ol.Extent} extent Extent.
 * @param {number=} opt_maxZoom Maximum zoom level (default is
 *     ol.DEFAULT_MAX_ZOOM).
 * @param {number|ol.Size=} opt_tileSize Tile size (default uses
 *     ol.DEFAULT_TILE_SIZE).
 * @return {!Array.<number>} Resolutions array.
 */
ol.tilegrid.resolutionsFromExtent = function(extent, opt_maxZoom, opt_tileSize) {
  var maxZoom = opt_maxZoom !== undefined ?
      opt_maxZoom : ol.DEFAULT_MAX_ZOOM;

  var height = ol.extent.getHeight(extent);
  var width = ol.extent.getWidth(extent);

  var tileSize = ol.size.toSize(opt_tileSize !== undefined ?
      opt_tileSize : ol.DEFAULT_TILE_SIZE);
  var maxResolution = Math.max(
      width / tileSize[0], height / tileSize[1]);

  var length = maxZoom + 1;
  var resolutions = new Array(length);
  for (var z = 0; z < length; ++z) {
    resolutions[z] = maxResolution / Math.pow(2, z);
  }
  return resolutions;
};


/**
 * @param {ol.ProjectionLike} projection Projection.
 * @param {number=} opt_maxZoom Maximum zoom level (default is
 *     ol.DEFAULT_MAX_ZOOM).
 * @param {ol.Size=} opt_tileSize Tile size (default uses ol.DEFAULT_TILE_SIZE).
 * @param {ol.extent.Corner=} opt_corner Extent corner (default is
 *     ol.extent.Corner.BOTTOM_LEFT).
 * @return {!ol.tilegrid.TileGrid} TileGrid instance.
 */
ol.tilegrid.createForProjection = function(projection, opt_maxZoom, opt_tileSize, opt_corner) {
  var extent = ol.tilegrid.extentFromProjection(projection);
  return ol.tilegrid.createForExtent(
      extent, opt_maxZoom, opt_tileSize, opt_corner);
};


/**
 * Generate a tile grid extent from a projection.  If the projection has an
 * extent, it is used.  If not, a global extent is assumed.
 * @param {ol.ProjectionLike} projection Projection.
 * @return {ol.Extent} Extent.
 */
ol.tilegrid.extentFromProjection = function(projection) {
  projection = ol.proj.get(projection);
  var extent = projection.getExtent();
  if (!extent) {
    var half = 180 * ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES] /
        projection.getMetersPerUnit();
    extent = ol.extent.createOrUpdate(-half, -half, half, half);
  }
  return extent;
};

goog.provide('ol.Attribution');

goog.require('ol.TileRange');
goog.require('ol.math');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * An attribution for a layer source.
 *
 * Example:
 *
 *     source: new ol.source.OSM({
 *       attributions: [
 *         new ol.Attribution({
 *           html: 'All maps &copy; ' +
 *               '<a href="http://www.opencyclemap.org/">OpenCycleMap</a>'
 *         }),
 *         ol.source.OSM.ATTRIBUTION
 *       ],
 *     ..
 *
 * @constructor
 * @param {olx.AttributionOptions} options Attribution options.
 * @struct
 * @api stable
 */
ol.Attribution = function(options) {

  /**
   * @private
   * @type {string}
   */
  this.html_ = options.html;

  /**
   * @private
   * @type {Object.<string, Array.<ol.TileRange>>}
   */
  this.tileRanges_ = options.tileRanges ? options.tileRanges : null;

};


/**
 * Get the attribution markup.
 * @return {string} The attribution HTML.
 * @api stable
 */
ol.Attribution.prototype.getHTML = function() {
  return this.html_;
};


/**
 * @param {Object.<string, ol.TileRange>} tileRanges Tile ranges.
 * @param {!ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @param {!ol.proj.Projection} projection Projection.
 * @return {boolean} Intersects any tile range.
 */
ol.Attribution.prototype.intersectsAnyTileRange = function(tileRanges, tileGrid, projection) {
  if (!this.tileRanges_) {
    return true;
  }
  var i, ii, tileRange, zKey;
  for (zKey in tileRanges) {
    if (!(zKey in this.tileRanges_)) {
      continue;
    }
    tileRange = tileRanges[zKey];
    var testTileRange;
    for (i = 0, ii = this.tileRanges_[zKey].length; i < ii; ++i) {
      testTileRange = this.tileRanges_[zKey][i];
      if (testTileRange.intersects(tileRange)) {
        return true;
      }
      var extentTileRange = tileGrid.getTileRangeForExtentAndZ(
          ol.tilegrid.extentFromProjection(projection), parseInt(zKey, 10));
      var width = extentTileRange.getWidth();
      if (tileRange.minX < extentTileRange.minX ||
          tileRange.maxX > extentTileRange.maxX) {
        if (testTileRange.intersects(new ol.TileRange(
            ol.math.modulo(tileRange.minX, width),
            ol.math.modulo(tileRange.maxX, width),
            tileRange.minY, tileRange.maxY))) {
          return true;
        }
        if (tileRange.getWidth() > width &&
            testTileRange.intersects(extentTileRange)) {
          return true;
        }
      }
    }
  }
  return false;
};

/**
 * An implementation of Google Maps' MVCArray.
 * @see https://developers.google.com/maps/documentation/javascript/reference
 */

goog.provide('ol.Collection');

goog.require('ol');
goog.require('ol.events.Event');
goog.require('ol.Object');


/**
 * @enum {string}
 */
ol.CollectionProperty = {
  LENGTH: 'length'
};


/**
 * @classdesc
 * An expanded version of standard JS Array, adding convenience methods for
 * manipulation. Add and remove changes to the Collection trigger a Collection
 * event. Note that this does not cover changes to the objects _within_ the
 * Collection; they trigger events on the appropriate object, not on the
 * Collection as a whole.
 *
 * @constructor
 * @extends {ol.Object}
 * @fires ol.Collection.Event
 * @param {!Array.<T>=} opt_array Array.
 * @template T
 * @api stable
 */
ol.Collection = function(opt_array) {

  ol.Object.call(this);

  /**
   * @private
   * @type {!Array.<T>}
   */
  this.array_ = opt_array ? opt_array : [];

  this.updateLength_();

};
ol.inherits(ol.Collection, ol.Object);


/**
 * Remove all elements from the collection.
 * @api stable
 */
ol.Collection.prototype.clear = function() {
  while (this.getLength() > 0) {
    this.pop();
  }
};


/**
 * Add elements to the collection.  This pushes each item in the provided array
 * to the end of the collection.
 * @param {!Array.<T>} arr Array.
 * @return {ol.Collection.<T>} This collection.
 * @api stable
 */
ol.Collection.prototype.extend = function(arr) {
  var i, ii;
  for (i = 0, ii = arr.length; i < ii; ++i) {
    this.push(arr[i]);
  }
  return this;
};


/**
 * Iterate over each element, calling the provided callback.
 * @param {function(this: S, T, number, Array.<T>): *} f The function to call
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array). The return value is ignored.
 * @param {S=} opt_this The object to use as `this` in `f`.
 * @template S
 * @api stable
 */
ol.Collection.prototype.forEach = function(f, opt_this) {
  this.array_.forEach(f, opt_this);
};


/**
 * Get a reference to the underlying Array object. Warning: if the array
 * is mutated, no events will be dispatched by the collection, and the
 * collection's "length" property won't be in sync with the actual length
 * of the array.
 * @return {!Array.<T>} Array.
 * @api stable
 */
ol.Collection.prototype.getArray = function() {
  return this.array_;
};


/**
 * Get the element at the provided index.
 * @param {number} index Index.
 * @return {T} Element.
 * @api stable
 */
ol.Collection.prototype.item = function(index) {
  return this.array_[index];
};


/**
 * Get the length of this collection.
 * @return {number} The length of the array.
 * @observable
 * @api stable
 */
ol.Collection.prototype.getLength = function() {
  return /** @type {number} */ (this.get(ol.CollectionProperty.LENGTH));
};


/**
 * Insert an element at the provided index.
 * @param {number} index Index.
 * @param {T} elem Element.
 * @api stable
 */
ol.Collection.prototype.insertAt = function(index, elem) {
  this.array_.splice(index, 0, elem);
  this.updateLength_();
  this.dispatchEvent(
      new ol.Collection.Event(ol.Collection.EventType.ADD, elem));
};


/**
 * Remove the last element of the collection and return it.
 * Return `undefined` if the collection is empty.
 * @return {T|undefined} Element.
 * @api stable
 */
ol.Collection.prototype.pop = function() {
  return this.removeAt(this.getLength() - 1);
};


/**
 * Insert the provided element at the end of the collection.
 * @param {T} elem Element.
 * @return {number} Length.
 * @api stable
 */
ol.Collection.prototype.push = function(elem) {
  var n = this.array_.length;
  this.insertAt(n, elem);
  return n;
};


/**
 * Remove the first occurrence of an element from the collection.
 * @param {T} elem Element.
 * @return {T|undefined} The removed element or undefined if none found.
 * @api stable
 */
ol.Collection.prototype.remove = function(elem) {
  var arr = this.array_;
  var i, ii;
  for (i = 0, ii = arr.length; i < ii; ++i) {
    if (arr[i] === elem) {
      return this.removeAt(i);
    }
  }
  return undefined;
};


/**
 * Remove the element at the provided index and return it.
 * Return `undefined` if the collection does not contain this index.
 * @param {number} index Index.
 * @return {T|undefined} Value.
 * @api stable
 */
ol.Collection.prototype.removeAt = function(index) {
  var prev = this.array_[index];
  this.array_.splice(index, 1);
  this.updateLength_();
  this.dispatchEvent(
      new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev));
  return prev;
};


/**
 * Set the element at the provided index.
 * @param {number} index Index.
 * @param {T} elem Element.
 * @api stable
 */
ol.Collection.prototype.setAt = function(index, elem) {
  var n = this.getLength();
  if (index < n) {
    var prev = this.array_[index];
    this.array_[index] = elem;
    this.dispatchEvent(
        new ol.Collection.Event(ol.Collection.EventType.REMOVE, prev));
    this.dispatchEvent(
        new ol.Collection.Event(ol.Collection.EventType.ADD, elem));
  } else {
    var j;
    for (j = n; j < index; ++j) {
      this.insertAt(j, undefined);
    }
    this.insertAt(index, elem);
  }
};


/**
 * @private
 */
ol.Collection.prototype.updateLength_ = function() {
  this.set(ol.CollectionProperty.LENGTH, this.array_.length);
};


/**
 * @enum {string}
 */
ol.Collection.EventType = {
  /**
   * Triggered when an item is added to the collection.
   * @event ol.Collection.Event#add
   * @api stable
   */
  ADD: 'add',
  /**
   * Triggered when an item is removed from the collection.
   * @event ol.Collection.Event#remove
   * @api stable
   */
  REMOVE: 'remove'
};


/**
 * @classdesc
 * Events emitted by {@link ol.Collection} instances are instances of this
 * type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.Collection.Event}
 * @param {ol.Collection.EventType} type Type.
 * @param {*=} opt_element Element.
 */
ol.Collection.Event = function(type, opt_element) {

  ol.events.Event.call(this, type);

  /**
   * The element that is added to or removed from the collection.
   * @type {*}
   * @api stable
   */
  this.element = opt_element;

};
ol.inherits(ol.Collection.Event, ol.events.Event);

goog.provide('ol.color');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.math');


/**
 * This RegExp matches # followed by 3 or 6 hex digits.
 * @const
 * @type {RegExp}
 * @private
 */
ol.color.hexColorRe_ = /^#(?:[0-9a-f]{3}){1,2}$/i;


/**
 * Regular expression for matching and capturing RGB style strings.
 * @const
 * @type {RegExp}
 * @private
 */
ol.color.rgbColorRe_ =
    /^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;


/**
 * Regular expression for matching and capturing RGBA style strings.
 * @const
 * @type {RegExp}
 * @private
 */
ol.color.rgbaColorRe_ =
    /^(?:rgba)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|1|0\.\d{0,10})\)$/i;


/**
 * Regular expression for matching potential named color style strings.
 * @const
 * @type {RegExp}
 * @private
 */
ol.color.namedColorRe_ =
    /^([a-z]*)$/i;


/**
 * Return the color as an array. This function maintains a cache of calculated
 * arrays which means the result should not be modified.
 * @param {ol.Color|string} color Color.
 * @return {ol.Color} Color.
 * @api
 */
ol.color.asArray = function(color) {
  if (Array.isArray(color)) {
    return color;
  } else {
    return ol.color.fromString(/** @type {string} */ (color));
  }
};


/**
 * Return the color as an rgba string.
 * @param {ol.Color|string} color Color.
 * @return {string} Rgba string.
 * @api
 */
ol.color.asString = function(color) {
  if (typeof color === 'string') {
    return color;
  } else {
    return ol.color.toString(color);
  }
};

/**
 * Return named color as an rgba string.
 * @param {string} color Named color.
 * @return {string} Rgb string.
 */
ol.color.fromNamed = function(color) {
  var el = document.createElement('div');
  el.style.color = color;
  document.body.appendChild(el);
  var rgb = window.getComputedStyle(el).color;
  document.body.removeChild(el);
  return rgb;
};


/**
 * @param {string} s String.
 * @return {ol.Color} Color.
 */
ol.color.fromString = (
    function() {

      // We maintain a small cache of parsed strings.  To provide cheap LRU-like
      // semantics, whenever the cache grows too large we simply delete an
      // arbitrary 25% of the entries.

      /**
       * @const
       * @type {number}
       */
      var MAX_CACHE_SIZE = 1024;

      /**
       * @type {Object.<string, ol.Color>}
       */
      var cache = {};

      /**
       * @type {number}
       */
      var cacheSize = 0;

      return (
          /**
           * @param {string} s String.
           * @return {ol.Color} Color.
           */
          function(s) {
            var color;
            if (cache.hasOwnProperty(s)) {
              color = cache[s];
            } else {
              if (cacheSize >= MAX_CACHE_SIZE) {
                var i = 0;
                var key;
                for (key in cache) {
                  if ((i++ & 3) === 0) {
                    delete cache[key];
                    --cacheSize;
                  }
                }
              }
              color = ol.color.fromStringInternal_(s);
              cache[s] = color;
              ++cacheSize;
            }
            return color;
          });

    })();


/**
 * @param {string} s String.
 * @private
 * @return {ol.Color} Color.
 */
ol.color.fromStringInternal_ = function(s) {
  var r, g, b, a, color, match;

  if (ol.color.namedColorRe_.exec(s)) {
    s = ol.color.fromNamed(s);
  }

  if (ol.color.hexColorRe_.exec(s)) { // hex
    var n = s.length - 1; // number of hex digits
    ol.asserts.assert(n == 3 || n == 6, 54); // Hex color should have 3 or 6 digits
    var d = n == 3 ? 1 : 2; // number of digits per channel
    r = parseInt(s.substr(1 + 0 * d, d), 16);
    g = parseInt(s.substr(1 + 1 * d, d), 16);
    b = parseInt(s.substr(1 + 2 * d, d), 16);
    if (d == 1) {
      r = (r << 4) + r;
      g = (g << 4) + g;
      b = (b << 4) + b;
    }
    a = 1;
    color = [r, g, b, a];
  } else if ((match = ol.color.rgbaColorRe_.exec(s))) { // rgba()
    r = Number(match[1]);
    g = Number(match[2]);
    b = Number(match[3]);
    a = Number(match[4]);
    color = ol.color.normalize([r, g, b, a]);
  } else if ((match = ol.color.rgbColorRe_.exec(s))) { // rgb()
    r = Number(match[1]);
    g = Number(match[2]);
    b = Number(match[3]);
    color = ol.color.normalize([r, g, b, 1]);
  } else {
    ol.asserts.assert(false, 14); // Invalid color
  }
  return /** @type {ol.Color} */ (color);
};


/**
 * @param {ol.Color} color Color.
 * @return {boolean} Is valid.
 */
ol.color.isValid = function(color) {
  return 0 <= color[0] && color[0] < 256 &&
      0 <= color[1] && color[1] < 256 &&
      0 <= color[2] && color[2] < 256 &&
      0 <= color[3] && color[3] <= 1;
};


/**
 * @param {ol.Color} color Color.
 * @param {ol.Color=} opt_color Color.
 * @return {ol.Color} Clamped color.
 */
ol.color.normalize = function(color, opt_color) {
  var result = opt_color || [];
  result[0] = ol.math.clamp((color[0] + 0.5) | 0, 0, 255);
  result[1] = ol.math.clamp((color[1] + 0.5) | 0, 0, 255);
  result[2] = ol.math.clamp((color[2] + 0.5) | 0, 0, 255);
  result[3] = ol.math.clamp(color[3], 0, 1);
  return result;
};


/**
 * @param {ol.Color} color Color.
 * @return {string} String.
 */
ol.color.toString = function(color) {
  var r = color[0];
  if (r != (r | 0)) {
    r = (r + 0.5) | 0;
  }
  var g = color[1];
  if (g != (g | 0)) {
    g = (g + 0.5) | 0;
  }
  var b = color[2];
  if (b != (b | 0)) {
    b = (b + 0.5) | 0;
  }
  var a = color[3] === undefined ? 1 : color[3];
  return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
};

goog.provide('ol.colorlike');

goog.require('ol.color');


/**
 * @param {ol.Color|ol.ColorLike} color Color.
 * @return {ol.ColorLike} The color as an ol.ColorLike
 * @api
 */
ol.colorlike.asColorLike = function(color) {
  if (ol.colorlike.isColorLike(color)) {
    return /** @type {string|CanvasPattern|CanvasGradient} */ (color);
  } else {
    return ol.color.asString(/** @type {ol.Color} */ (color));
  }
};


/**
 * @param {?} color The value that is potentially an ol.ColorLike
 * @return {boolean} Whether the color is an ol.ColorLike
 */
ol.colorlike.isColorLike = function(color) {
  return (
      typeof color === 'string' ||
      color instanceof CanvasPattern ||
      color instanceof CanvasGradient
  );
};

// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Provides a base class for custom Error objects such that the
 * stack is correctly maintained.
 *
 * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
 * sufficient.
 *
 */

goog.provide('goog.debug.Error');



/**
 * Base class for custom error objects.
 * @param {*=} opt_msg The message associated with the error.
 * @constructor
 * @extends {Error}
 */
goog.debug.Error = function(opt_msg) {

  // Attempt to ensure there is a stack trace.
  if (Error.captureStackTrace) {
    Error.captureStackTrace(this, goog.debug.Error);
  } else {
    var stack = new Error().stack;
    if (stack) {
      this.stack = stack;
    }
  }

  if (opt_msg) {
    this.message = String(opt_msg);
  }

  /**
   * Whether to report this error to the server. Setting this to false will
   * cause the error reporter to not report the error back to the server,
   * which can be useful if the client knows that the error has already been
   * logged on the server.
   * @type {boolean}
   */
  this.reportErrorToServer = true;
};
goog.inherits(goog.debug.Error, Error);


/** @override */
goog.debug.Error.prototype.name = 'CustomError';

// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Definition of goog.dom.NodeType.
 */

goog.provide('goog.dom.NodeType');


/**
 * Constants for the nodeType attribute in the Node interface.
 *
 * These constants match those specified in the Node interface. These are
 * usually present on the Node object in recent browsers, but not in older
 * browsers (specifically, early IEs) and thus are given here.
 *
 * In some browsers (early IEs), these are not defined on the Node object,
 * so they are provided here.
 *
 * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247
 * @enum {number}
 */
goog.dom.NodeType = {
  ELEMENT: 1,
  ATTRIBUTE: 2,
  TEXT: 3,
  CDATA_SECTION: 4,
  ENTITY_REFERENCE: 5,
  ENTITY: 6,
  PROCESSING_INSTRUCTION: 7,
  COMMENT: 8,
  DOCUMENT: 9,
  DOCUMENT_TYPE: 10,
  DOCUMENT_FRAGMENT: 11,
  NOTATION: 12
};

// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Utilities for string manipulation.
 * @author arv@google.com (Erik Arvidsson)
 */


/**
 * Namespace for string utilities
 */
goog.provide('goog.string');
goog.provide('goog.string.Unicode');


/**
 * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps
 * with detection of double-escaping as this letter is frequently used.
 */
goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false);


/**
 * @define {boolean} Whether to force non-dom html unescaping.
 */
goog.define('goog.string.FORCE_NON_DOM_HTML_UNESCAPING', false);


/**
 * Common Unicode string characters.
 * @enum {string}
 */
goog.string.Unicode = {
  NBSP: '\xa0'
};


/**
 * Fast prefix-checker.
 * @param {string} str The string to check.
 * @param {string} prefix A string to look for at the start of {@code str}.
 * @return {boolean} True if {@code str} begins with {@code prefix}.
 */
goog.string.startsWith = function(str, prefix) {
  return str.lastIndexOf(prefix, 0) == 0;
};


/**
 * Fast suffix-checker.
 * @param {string} str The string to check.
 * @param {string} suffix A string to look for at the end of {@code str}.
 * @return {boolean} True if {@code str} ends with {@code suffix}.
 */
goog.string.endsWith = function(str, suffix) {
  var l = str.length - suffix.length;
  return l >= 0 && str.indexOf(suffix, l) == l;
};


/**
 * Case-insensitive prefix-checker.
 * @param {string} str The string to check.
 * @param {string} prefix  A string to look for at the end of {@code str}.
 * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
 *     case).
 */
goog.string.caseInsensitiveStartsWith = function(str, prefix) {
  return goog.string.caseInsensitiveCompare(
             prefix, str.substr(0, prefix.length)) == 0;
};


/**
 * Case-insensitive suffix-checker.
 * @param {string} str The string to check.
 * @param {string} suffix A string to look for at the end of {@code str}.
 * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
 *     case).
 */
goog.string.caseInsensitiveEndsWith = function(str, suffix) {
  return goog.string.caseInsensitiveCompare(
             suffix, str.substr(str.length - suffix.length, suffix.length)) ==
      0;
};


/**
 * Case-insensitive equality checker.
 * @param {string} str1 First string to check.
 * @param {string} str2 Second string to check.
 * @return {boolean} True if {@code str1} and {@code str2} are the same string,
 *     ignoring case.
 */
goog.string.caseInsensitiveEquals = function(str1, str2) {
  return str1.toLowerCase() == str2.toLowerCase();
};


/**
 * Does simple python-style string substitution.
 * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
 * @param {string} str The string containing the pattern.
 * @param {...*} var_args The items to substitute into the pattern.
 * @return {string} A copy of {@code str} in which each occurrence of
 *     {@code %s} has been replaced an argument from {@code var_args}.
 */
goog.string.subs = function(str, var_args) {
  var splitParts = str.split('%s');
  var returnString = '';

  var subsArguments = Array.prototype.slice.call(arguments, 1);
  while (subsArguments.length &&
         // Replace up to the last split part. We are inserting in the
         // positions between split parts.
         splitParts.length > 1) {
    returnString += splitParts.shift() + subsArguments.shift();
  }

  return returnString + splitParts.join('%s');  // Join unused '%s'
};


/**
 * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
 * and tabs) to a single space, and strips leading and trailing whitespace.
 * @param {string} str Input string.
 * @return {string} A copy of {@code str} with collapsed whitespace.
 */
goog.string.collapseWhitespace = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
};


/**
 * Checks if a string is empty or contains only whitespaces.
 * @param {string} str The string to check.
 * @return {boolean} Whether {@code str} is empty or whitespace only.
 */
goog.string.isEmptyOrWhitespace = function(str) {
  // testing length == 0 first is actually slower in all browsers (about the
  // same in Opera).
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return /^[\s\xa0]*$/.test(str);
};


/**
 * Checks if a string is empty.
 * @param {string} str The string to check.
 * @return {boolean} Whether {@code str} is empty.
 */
goog.string.isEmptyString = function(str) {
  return str.length == 0;
};


/**
 * Checks if a string is empty or contains only whitespaces.
 *
 * TODO(user): Deprecate this when clients have been switched over to
 * goog.string.isEmptyOrWhitespace.
 *
 * @param {string} str The string to check.
 * @return {boolean} Whether {@code str} is empty or whitespace only.
 */
goog.string.isEmpty = goog.string.isEmptyOrWhitespace;


/**
 * Checks if a string is null, undefined, empty or contains only whitespaces.
 * @param {*} str The string to check.
 * @return {boolean} Whether {@code str} is null, undefined, empty, or
 *     whitespace only.
 * @deprecated Use goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str))
 *     instead.
 */
goog.string.isEmptyOrWhitespaceSafe = function(str) {
  return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str));
};


/**
 * Checks if a string is null, undefined, empty or contains only whitespaces.
 *
 * TODO(user): Deprecate this when clients have been switched over to
 * goog.string.isEmptyOrWhitespaceSafe.
 *
 * @param {*} str The string to check.
 * @return {boolean} Whether {@code str} is null, undefined, empty, or
 *     whitespace only.
 */
goog.string.isEmptySafe = goog.string.isEmptyOrWhitespaceSafe;


/**
 * Checks if a string is all breaking whitespace.
 * @param {string} str The string to check.
 * @return {boolean} Whether the string is all breaking whitespace.
 */
goog.string.isBreakingWhitespace = function(str) {
  return !/[^\t\n\r ]/.test(str);
};


/**
 * Checks if a string contains all letters.
 * @param {string} str string to check.
 * @return {boolean} True if {@code str} consists entirely of letters.
 */
goog.string.isAlpha = function(str) {
  return !/[^a-zA-Z]/.test(str);
};


/**
 * Checks if a string contains only numbers.
 * @param {*} str string to check. If not a string, it will be
 *     casted to one.
 * @return {boolean} True if {@code str} is numeric.
 */
goog.string.isNumeric = function(str) {
  return !/[^0-9]/.test(str);
};


/**
 * Checks if a string contains only numbers or letters.
 * @param {string} str string to check.
 * @return {boolean} True if {@code str} is alphanumeric.
 */
goog.string.isAlphaNumeric = function(str) {
  return !/[^a-zA-Z0-9]/.test(str);
};


/**
 * Checks if a character is a space character.
 * @param {string} ch Character to check.
 * @return {boolean} True if {@code ch} is a space.
 */
goog.string.isSpace = function(ch) {
  return ch == ' ';
};


/**
 * Checks if a character is a valid unicode character.
 * @param {string} ch Character to check.
 * @return {boolean} True if {@code ch} is a valid unicode character.
 */
goog.string.isUnicodeChar = function(ch) {
  return ch.length == 1 && ch >= ' ' && ch <= '~' ||
      ch >= '\u0080' && ch <= '\uFFFD';
};


/**
 * Takes a string and replaces newlines with a space. Multiple lines are
 * replaced with a single space.
 * @param {string} str The string from which to strip newlines.
 * @return {string} A copy of {@code str} stripped of newlines.
 */
goog.string.stripNewlines = function(str) {
  return str.replace(/(\r\n|\r|\n)+/g, ' ');
};


/**
 * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
 * @param {string} str The string to in which to canonicalize newlines.
 * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
 */
goog.string.canonicalizeNewlines = function(str) {
  return str.replace(/(\r\n|\r|\n)/g, '\n');
};


/**
 * Normalizes whitespace in a string, replacing all whitespace chars with
 * a space.
 * @param {string} str The string in which to normalize whitespace.
 * @return {string} A copy of {@code str} with all whitespace normalized.
 */
goog.string.normalizeWhitespace = function(str) {
  return str.replace(/\xa0|\s/g, ' ');
};


/**
 * Normalizes spaces in a string, replacing all consecutive spaces and tabs
 * with a single space. Replaces non-breaking space with a space.
 * @param {string} str The string in which to normalize spaces.
 * @return {string} A copy of {@code str} with all consecutive spaces and tabs
 *    replaced with a single space.
 */
goog.string.normalizeSpaces = function(str) {
  return str.replace(/\xa0|[ \t]+/g, ' ');
};


/**
 * Removes the breaking spaces from the left and right of the string and
 * collapses the sequences of breaking spaces in the middle into single spaces.
 * The original and the result strings render the same way in HTML.
 * @param {string} str A string in which to collapse spaces.
 * @return {string} Copy of the string with normalized breaking spaces.
 */
goog.string.collapseBreakingSpaces = function(str) {
  return str.replace(/[\t\r\n ]+/g, ' ')
      .replace(/^[\t\r\n ]+|[\t\r\n ]+$/g, '');
};


/**
 * Trims white spaces to the left and right of a string.
 * @param {string} str The string to trim.
 * @return {string} A trimmed copy of {@code str}.
 */
goog.string.trim =
    (goog.TRUSTED_SITE && String.prototype.trim) ? function(str) {
      return str.trim();
    } : function(str) {
      // Since IE doesn't include non-breaking-space (0xa0) in their \s
      // character class (as required by section 7.2 of the ECMAScript spec),
      // we explicitly include it in the regexp to enforce consistent
      // cross-browser behavior.
      return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
    };


/**
 * Trims whitespaces at the left end of a string.
 * @param {string} str The string to left trim.
 * @return {string} A trimmed copy of {@code str}.
 */
goog.string.trimLeft = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/^[\s\xa0]+/, '');
};


/**
 * Trims whitespaces at the right end of a string.
 * @param {string} str The string to right trim.
 * @return {string} A trimmed copy of {@code str}.
 */
goog.string.trimRight = function(str) {
  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
  // include it in the regexp to enforce consistent cross-browser behavior.
  return str.replace(/[\s\xa0]+$/, '');
};


/**
 * A string comparator that ignores case.
 * -1 = str1 less than str2
 *  0 = str1 equals str2
 *  1 = str1 greater than str2
 *
 * @param {string} str1 The string to compare.
 * @param {string} str2 The string to compare {@code str1} to.
 * @return {number} The comparator result, as described above.
 */
goog.string.caseInsensitiveCompare = function(str1, str2) {
  var test1 = String(str1).toLowerCase();
  var test2 = String(str2).toLowerCase();

  if (test1 < test2) {
    return -1;
  } else if (test1 == test2) {
    return 0;
  } else {
    return 1;
  }
};


/**
 * Compares two strings interpreting their numeric substrings as numbers.
 *
 * @param {string} str1 First string.
 * @param {string} str2 Second string.
 * @param {!RegExp} tokenizerRegExp Splits a string into substrings of
 *     non-negative integers, non-numeric characters and optionally fractional
 *     numbers starting with a decimal point.
 * @return {number} Negative if str1 < str2, 0 is str1 == str2, positive if
 *     str1 > str2.
 * @private
 */
goog.string.numberAwareCompare_ = function(str1, str2, tokenizerRegExp) {
  if (str1 == str2) {
    return 0;
  }
  if (!str1) {
    return -1;
  }
  if (!str2) {
    return 1;
  }

  // Using match to split the entire string ahead of time turns out to be faster
  // for most inputs than using RegExp.exec or iterating over each character.
  var tokens1 = str1.toLowerCase().match(tokenizerRegExp);
  var tokens2 = str2.toLowerCase().match(tokenizerRegExp);

  var count = Math.min(tokens1.length, tokens2.length);

  for (var i = 0; i < count; i++) {
    var a = tokens1[i];
    var b = tokens2[i];

    // Compare pairs of tokens, returning if one token sorts before the other.
    if (a != b) {
      // Only if both tokens are integers is a special comparison required.
      // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
      var num1 = parseInt(a, 10);
      if (!isNaN(num1)) {
        var num2 = parseInt(b, 10);
        if (!isNaN(num2) && num1 - num2) {
          return num1 - num2;
        }
      }
      return a < b ? -1 : 1;
    }
  }

  // If one string is a substring of the other, the shorter string sorts first.
  if (tokens1.length != tokens2.length) {
    return tokens1.length - tokens2.length;
  }

  // The two strings must be equivalent except for case (perfect equality is
  // tested at the head of the function.) Revert to default ASCII string
  // comparison to stabilize the sort.
  return str1 < str2 ? -1 : 1;
};


/**
 * String comparison function that handles non-negative integer numbers in a
 * way humans might expect. Using this function, the string 'File 2.jpg' sorts
 * before 'File 10.jpg', and 'Version 1.9' before 'Version 1.10'. The comparison
 * is mostly case-insensitive, though strings that are identical except for case
 * are sorted with the upper-case strings before lower-case.
 *
 * This comparison function is up to 50x slower than either the default or the
 * case-insensitive compare. It should not be used in time-critical code, but
 * should be fast enough to sort several hundred short strings (like filenames)
 * with a reasonable delay.
 *
 * @param {string} str1 The string to compare in a numerically sensitive way.
 * @param {string} str2 The string to compare {@code str1} to.
 * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
 *     0 if str1 > str2.
 */
goog.string.intAwareCompare = function(str1, str2) {
  return goog.string.numberAwareCompare_(str1, str2, /\d+|\D+/g);
};


/**
 * String comparison function that handles non-negative integer and fractional
 * numbers in a way humans might expect. Using this function, the string
 * 'File 2.jpg' sorts before 'File 10.jpg', and '3.14' before '3.2'. Equivalent
 * to {@link goog.string.intAwareCompare} apart from the way how it interprets
 * dots.
 *
 * @param {string} str1 The string to compare in a numerically sensitive way.
 * @param {string} str2 The string to compare {@code str1} to.
 * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
 *     0 if str1 > str2.
 */
goog.string.floatAwareCompare = function(str1, str2) {
  return goog.string.numberAwareCompare_(str1, str2, /\d+|\.\d+|\D+/g);
};


/**
 * Alias for {@link goog.string.floatAwareCompare}.
 *
 * @param {string} str1
 * @param {string} str2
 * @return {number}
 */
goog.string.numerateCompare = goog.string.floatAwareCompare;


/**
 * URL-encodes a string
 * @param {*} str The string to url-encode.
 * @return {string} An encoded copy of {@code str} that is safe for urls.
 *     Note that '#', ':', and other characters used to delimit portions
 *     of URLs *will* be encoded.
 */
goog.string.urlEncode = function(str) {
  return encodeURIComponent(String(str));
};


/**
 * URL-decodes the string. We need to specially handle '+'s because
 * the javascript library doesn't convert them to spaces.
 * @param {string} str The string to url decode.
 * @return {string} The decoded {@code str}.
 */
goog.string.urlDecode = function(str) {
  return decodeURIComponent(str.replace(/\+/g, ' '));
};


/**
 * Converts \n to <br>s or <br />s.
 * @param {string} str The string in which to convert newlines.
 * @param {boolean=} opt_xml Whether to use XML compatible tags.
 * @return {string} A copy of {@code str} with converted newlines.
 */
goog.string.newLineToBr = function(str, opt_xml) {
  return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
};


/**
 * Escapes double quote '"' and single quote '\'' characters in addition to
 * '&', '<', and '>' so that a string can be included in an HTML tag attribute
 * value within double or single quotes.
 *
 * It should be noted that > doesn't need to be escaped for the HTML or XML to
 * be valid, but it has been decided to escape it for consistency with other
 * implementations.
 *
 * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the
 * lowercase letter "e".
 *
 * NOTE(user):
 * HtmlEscape is often called during the generation of large blocks of HTML.
 * Using statics for the regular expressions and strings is an optimization
 * that can more than half the amount of time IE spends in this function for
 * large apps, since strings and regexes both contribute to GC allocations.
 *
 * Testing for the presence of a character before escaping increases the number
 * of function calls, but actually provides a speed increase for the average
 * case -- since the average case often doesn't require the escaping of all 4
 * characters and indexOf() is much cheaper than replace().
 * The worst case does suffer slightly from the additional calls, therefore the
 * opt_isLikelyToContainHtmlChars option has been included for situations
 * where all 4 HTML entities are very likely to be present and need escaping.
 *
 * Some benchmarks (times tended to fluctuate +-0.05ms):
 *                                     FireFox                     IE6
 * (no chars / average (mix of cases) / all 4 chars)
 * no checks                     0.13 / 0.22 / 0.22         0.23 / 0.53 / 0.80
 * indexOf                       0.08 / 0.17 / 0.26         0.22 / 0.54 / 0.84
 * indexOf + re test             0.07 / 0.17 / 0.28         0.19 / 0.50 / 0.85
 *
 * An additional advantage of checking if replace actually needs to be called
 * is a reduction in the number of object allocations, so as the size of the
 * application grows the difference between the various methods would increase.
 *
 * @param {string} str string to be escaped.
 * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
 *     if the character needs replacing - use this option if you expect each of
 *     the characters to appear often. Leave false if you expect few html
 *     characters to occur in your strings, such as if you are escaping HTML.
 * @return {string} An escaped copy of {@code str}.
 */
goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {

  if (opt_isLikelyToContainHtmlChars) {
    str = str.replace(goog.string.AMP_RE_, '&amp;')
              .replace(goog.string.LT_RE_, '&lt;')
              .replace(goog.string.GT_RE_, '&gt;')
              .replace(goog.string.QUOT_RE_, '&quot;')
              .replace(goog.string.SINGLE_QUOTE_RE_, '&#39;')
              .replace(goog.string.NULL_RE_, '&#0;');
    if (goog.string.DETECT_DOUBLE_ESCAPING) {
      str = str.replace(goog.string.E_RE_, '&#101;');
    }
    return str;

  } else {
    // quick test helps in the case when there are no chars to replace, in
    // worst case this makes barely a difference to the time taken
    if (!goog.string.ALL_RE_.test(str)) return str;

    // str.indexOf is faster than regex.test in this case
    if (str.indexOf('&') != -1) {
      str = str.replace(goog.string.AMP_RE_, '&amp;');
    }
    if (str.indexOf('<') != -1) {
      str = str.replace(goog.string.LT_RE_, '&lt;');
    }
    if (str.indexOf('>') != -1) {
      str = str.replace(goog.string.GT_RE_, '&gt;');
    }
    if (str.indexOf('"') != -1) {
      str = str.replace(goog.string.QUOT_RE_, '&quot;');
    }
    if (str.indexOf('\'') != -1) {
      str = str.replace(goog.string.SINGLE_QUOTE_RE_, '&#39;');
    }
    if (str.indexOf('\x00') != -1) {
      str = str.replace(goog.string.NULL_RE_, '&#0;');
    }
    if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) {
      str = str.replace(goog.string.E_RE_, '&#101;');
    }
    return str;
  }
};


/**
 * Regular expression that matches an ampersand, for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.AMP_RE_ = /&/g;


/**
 * Regular expression that matches a less than sign, for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.LT_RE_ = /</g;


/**
 * Regular expression that matches a greater than sign, for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.GT_RE_ = />/g;


/**
 * Regular expression that matches a double quote, for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.QUOT_RE_ = /"/g;


/**
 * Regular expression that matches a single quote, for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.SINGLE_QUOTE_RE_ = /'/g;


/**
 * Regular expression that matches null character, for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.NULL_RE_ = /\x00/g;


/**
 * Regular expression that matches a lowercase letter "e", for use in escaping.
 * @const {!RegExp}
 * @private
 */
goog.string.E_RE_ = /e/g;


/**
 * Regular expression that matches any character that needs to be escaped.
 * @const {!RegExp}
 * @private
 */
goog.string.ALL_RE_ =
    (goog.string.DETECT_DOUBLE_ESCAPING ? /[\x00&<>"'e]/ : /[\x00&<>"']/);


/**
 * Unescapes an HTML string.
 *
 * @param {string} str The string to unescape.
 * @return {string} An unescaped copy of {@code str}.
 */
goog.string.unescapeEntities = function(str) {
  if (goog.string.contains(str, '&')) {
    // We are careful not to use a DOM if we do not have one or we explicitly
    // requested non-DOM html unescaping.
    if (!goog.string.FORCE_NON_DOM_HTML_UNESCAPING &&
        'document' in goog.global) {
      return goog.string.unescapeEntitiesUsingDom_(str);
    } else {
      // Fall back on pure XML entities
      return goog.string.unescapePureXmlEntities_(str);
    }
  }
  return str;
};


/**
 * Unescapes a HTML string using the provided document.
 *
 * @param {string} str The string to unescape.
 * @param {!Document} document A document to use in escaping the string.
 * @return {string} An unescaped copy of {@code str}.
 */
goog.string.unescapeEntitiesWithDocument = function(str, document) {
  if (goog.string.contains(str, '&')) {
    return goog.string.unescapeEntitiesUsingDom_(str, document);
  }
  return str;
};


/**
 * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
 * entities. This function is XSS-safe and whitespace-preserving.
 * @private
 * @param {string} str The string to unescape.
 * @param {Document=} opt_document An optional document to use for creating
 *     elements. If this is not specified then the default window.document
 *     will be used.
 * @return {string} The unescaped {@code str} string.
 */
goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) {
  /** @type {!Object<string, string>} */
  var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
  var div;
  if (opt_document) {
    div = opt_document.createElement('div');
  } else {
    div = goog.global.document.createElement('div');
  }
  // Match as many valid entity characters as possible. If the actual entity
  // happens to be shorter, it will still work as innerHTML will return the
  // trailing characters unchanged. Since the entity characters do not include
  // open angle bracket, there is no chance of XSS from the innerHTML use.
  // Since no whitespace is passed to innerHTML, whitespace is preserved.
  return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
    // Check for cached entity.
    var value = seen[s];
    if (value) {
      return value;
    }
    // Check for numeric entity.
    if (entity.charAt(0) == '#') {
      // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
      var n = Number('0' + entity.substr(1));
      if (!isNaN(n)) {
        value = String.fromCharCode(n);
      }
    }
    // Fall back to innerHTML otherwise.
    if (!value) {
      // Append a non-entity character to avoid a bug in Webkit that parses
      // an invalid entity at the end of innerHTML text as the empty string.
      div.innerHTML = s + ' ';
      // Then remove the trailing character from the result.
      value = div.firstChild.nodeValue.slice(0, -1);
    }
    // Cache and return.
    return seen[s] = value;
  });
};


/**
 * Unescapes XML entities.
 * @private
 * @param {string} str The string to unescape.
 * @return {string} An unescaped copy of {@code str}.
 */
goog.string.unescapePureXmlEntities_ = function(str) {
  return str.replace(/&([^;]+);/g, function(s, entity) {
    switch (entity) {
      case 'amp':
        return '&';
      case 'lt':
        return '<';
      case 'gt':
        return '>';
      case 'quot':
        return '"';
      default:
        if (entity.charAt(0) == '#') {
          // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
          var n = Number('0' + entity.substr(1));
          if (!isNaN(n)) {
            return String.fromCharCode(n);
          }
        }
        // For invalid entities we just return the entity
        return s;
    }
  });
};


/**
 * Regular expression that matches an HTML entity.
 * See also HTML5: Tokenization / Tokenizing character references.
 * @private
 * @type {!RegExp}
 */
goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;


/**
 * Do escaping of whitespace to preserve spatial formatting. We use character
 * entity #160 to make it safer for xml.
 * @param {string} str The string in which to escape whitespace.
 * @param {boolean=} opt_xml Whether to use XML compatible tags.
 * @return {string} An escaped copy of {@code str}.
 */
goog.string.whitespaceEscape = function(str, opt_xml) {
  // This doesn't use goog.string.preserveSpaces for backwards compatibility.
  return goog.string.newLineToBr(str.replace(/  /g, ' &#160;'), opt_xml);
};


/**
 * Preserve spaces that would be otherwise collapsed in HTML by replacing them
 * with non-breaking space Unicode characters.
 * @param {string} str The string in which to preserve whitespace.
 * @return {string} A copy of {@code str} with preserved whitespace.
 */
goog.string.preserveSpaces = function(str) {
  return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP);
};


/**
 * Strip quote characters around a string.  The second argument is a string of
 * characters to treat as quotes.  This can be a single character or a string of
 * multiple character and in that case each of those are treated as possible
 * quote characters. For example:
 *
 * <pre>
 * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
 * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
 * </pre>
 *
 * @param {string} str The string to strip.
 * @param {string} quoteChars The quote characters to strip.
 * @return {string} A copy of {@code str} without the quotes.
 */
goog.string.stripQuotes = function(str, quoteChars) {
  var length = quoteChars.length;
  for (var i = 0; i < length; i++) {
    var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
    if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
      return str.substring(1, str.length - 1);
    }
  }
  return str;
};


/**
 * Truncates a string to a certain length and adds '...' if necessary.  The
 * length also accounts for the ellipsis, so a maximum length of 10 and a string
 * 'Hello World!' produces 'Hello W...'.
 * @param {string} str The string to truncate.
 * @param {number} chars Max number of characters.
 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
 *     characters from being cut off in the middle.
 * @return {string} The truncated {@code str} string.
 */
goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
  if (opt_protectEscapedCharacters) {
    str = goog.string.unescapeEntities(str);
  }

  if (str.length > chars) {
    str = str.substring(0, chars - 3) + '...';
  }

  if (opt_protectEscapedCharacters) {
    str = goog.string.htmlEscape(str);
  }

  return str;
};


/**
 * Truncate a string in the middle, adding "..." if necessary,
 * and favoring the beginning of the string.
 * @param {string} str The string to truncate the middle of.
 * @param {number} chars Max number of characters.
 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
 *     characters from being cutoff in the middle.
 * @param {number=} opt_trailingChars Optional number of trailing characters to
 *     leave at the end of the string, instead of truncating as close to the
 *     middle as possible.
 * @return {string} A truncated copy of {@code str}.
 */
goog.string.truncateMiddle = function(
    str, chars, opt_protectEscapedCharacters, opt_trailingChars) {
  if (opt_protectEscapedCharacters) {
    str = goog.string.unescapeEntities(str);
  }

  if (opt_trailingChars && str.length > chars) {
    if (opt_trailingChars > chars) {
      opt_trailingChars = chars;
    }
    var endPoint = str.length - opt_trailingChars;
    var startPoint = chars - opt_trailingChars;
    str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
  } else if (str.length > chars) {
    // Favor the beginning of the string:
    var half = Math.floor(chars / 2);
    var endPos = str.length - half;
    half += chars % 2;
    str = str.substring(0, half) + '...' + str.substring(endPos);
  }

  if (opt_protectEscapedCharacters) {
    str = goog.string.htmlEscape(str);
  }

  return str;
};


/**
 * Special chars that need to be escaped for goog.string.quote.
 * @private {!Object<string, string>}
 */
goog.string.specialEscapeChars_ = {
  '\0': '\\0',
  '\b': '\\b',
  '\f': '\\f',
  '\n': '\\n',
  '\r': '\\r',
  '\t': '\\t',
  '\x0B': '\\x0B',  // '\v' is not supported in JScript
  '"': '\\"',
  '\\': '\\\\',
  // To support the use case of embedding quoted strings inside of script
  // tags, we have to make sure HTML comments and opening/closing script tags do
  // not appear in the resulting string. The specific strings that must be
  // escaped are documented at:
  // http://www.w3.org/TR/html51/semantics.html#restrictions-for-contents-of-script-elements
  '<': '\x3c'
};


/**
 * Character mappings used internally for goog.string.escapeChar.
 * @private {!Object<string, string>}
 */
goog.string.jsEscapeCache_ = {
  '\'': '\\\''
};


/**
 * Encloses a string in double quotes and escapes characters so that the
 * string is a valid JS string. The resulting string is safe to embed in
 * `<script>` tags as "<" is escaped.
 * @param {string} s The string to quote.
 * @return {string} A copy of {@code s} surrounded by double quotes.
 */
goog.string.quote = function(s) {
  s = String(s);
  var sb = ['"'];
  for (var i = 0; i < s.length; i++) {
    var ch = s.charAt(i);
    var cc = ch.charCodeAt(0);
    sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
        ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
  }
  sb.push('"');
  return sb.join('');
};


/**
 * Takes a string and returns the escaped string for that character.
 * @param {string} str The string to escape.
 * @return {string} An escaped string representing {@code str}.
 */
goog.string.escapeString = function(str) {
  var sb = [];
  for (var i = 0; i < str.length; i++) {
    sb[i] = goog.string.escapeChar(str.charAt(i));
  }
  return sb.join('');
};


/**
 * Takes a character and returns the escaped string for that character. For
 * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
 * @param {string} c The character to escape.
 * @return {string} An escaped string representing {@code c}.
 */
goog.string.escapeChar = function(c) {
  if (c in goog.string.jsEscapeCache_) {
    return goog.string.jsEscapeCache_[c];
  }

  if (c in goog.string.specialEscapeChars_) {
    return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
  }

  var rv = c;
  var cc = c.charCodeAt(0);
  if (cc > 31 && cc < 127) {
    rv = c;
  } else {
    // tab is 9 but handled above
    if (cc < 256) {
      rv = '\\x';
      if (cc < 16 || cc > 256) {
        rv += '0';
      }
    } else {
      rv = '\\u';
      if (cc < 4096) {  // \u1000
        rv += '0';
      }
    }
    rv += cc.toString(16).toUpperCase();
  }

  return goog.string.jsEscapeCache_[c] = rv;
};


/**
 * Determines whether a string contains a substring.
 * @param {string} str The string to search.
 * @param {string} subString The substring to search for.
 * @return {boolean} Whether {@code str} contains {@code subString}.
 */
goog.string.contains = function(str, subString) {
  return str.indexOf(subString) != -1;
};


/**
 * Determines whether a string contains a substring, ignoring case.
 * @param {string} str The string to search.
 * @param {string} subString The substring to search for.
 * @return {boolean} Whether {@code str} contains {@code subString}.
 */
goog.string.caseInsensitiveContains = function(str, subString) {
  return goog.string.contains(str.toLowerCase(), subString.toLowerCase());
};


/**
 * Returns the non-overlapping occurrences of ss in s.
 * If either s or ss evalutes to false, then returns zero.
 * @param {string} s The string to look in.
 * @param {string} ss The string to look for.
 * @return {number} Number of occurrences of ss in s.
 */
goog.string.countOf = function(s, ss) {
  return s && ss ? s.split(ss).length - 1 : 0;
};


/**
 * Removes a substring of a specified length at a specific
 * index in a string.
 * @param {string} s The base string from which to remove.
 * @param {number} index The index at which to remove the substring.
 * @param {number} stringLength The length of the substring to remove.
 * @return {string} A copy of {@code s} with the substring removed or the full
 *     string if nothing is removed or the input is invalid.
 */
goog.string.removeAt = function(s, index, stringLength) {
  var resultStr = s;
  // If the index is greater or equal to 0 then remove substring
  if (index >= 0 && index < s.length && stringLength > 0) {
    resultStr = s.substr(0, index) +
        s.substr(index + stringLength, s.length - index - stringLength);
  }
  return resultStr;
};


/**
 *  Removes the first occurrence of a substring from a string.
 *  @param {string} s The base string from which to remove.
 *  @param {string} ss The string to remove.
 *  @return {string} A copy of {@code s} with {@code ss} removed or the full
 *      string if nothing is removed.
 */
goog.string.remove = function(s, ss) {
  var re = new RegExp(goog.string.regExpEscape(ss), '');
  return s.replace(re, '');
};


/**
 *  Removes all occurrences of a substring from a string.
 *  @param {string} s The base string from which to remove.
 *  @param {string} ss The string to remove.
 *  @return {string} A copy of {@code s} with {@code ss} removed or the full
 *      string if nothing is removed.
 */
goog.string.removeAll = function(s, ss) {
  var re = new RegExp(goog.string.regExpEscape(ss), 'g');
  return s.replace(re, '');
};


/**
 * Escapes characters in the string that are not safe to use in a RegExp.
 * @param {*} s The string to escape. If not a string, it will be casted
 *     to one.
 * @return {string} A RegExp safe, escaped copy of {@code s}.
 */
goog.string.regExpEscape = function(s) {
  return String(s)
      .replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1')
      .replace(/\x08/g, '\\x08');
};


/**
 * Repeats a string n times.
 * @param {string} string The string to repeat.
 * @param {number} length The number of times to repeat.
 * @return {string} A string containing {@code length} repetitions of
 *     {@code string}.
 */
goog.string.repeat = (String.prototype.repeat) ? function(string, length) {
  // The native method is over 100 times faster than the alternative.
  return string.repeat(length);
} : function(string, length) {
  return new Array(length + 1).join(string);
};


/**
 * Pads number to given length and optionally rounds it to a given precision.
 * For example:
 * <pre>padNumber(1.25, 2, 3) -> '01.250'
 * padNumber(1.25, 2) -> '01.25'
 * padNumber(1.25, 2, 1) -> '01.3'
 * padNumber(1.25, 0) -> '1.25'</pre>
 *
 * @param {number} num The number to pad.
 * @param {number} length The desired length.
 * @param {number=} opt_precision The desired precision.
 * @return {string} {@code num} as a string with the given options.
 */
goog.string.padNumber = function(num, length, opt_precision) {
  var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
  var index = s.indexOf('.');
  if (index == -1) {
    index = s.length;
  }
  return goog.string.repeat('0', Math.max(0, length - index)) + s;
};


/**
 * Returns a string representation of the given object, with
 * null and undefined being returned as the empty string.
 *
 * @param {*} obj The object to convert.
 * @return {string} A string representation of the {@code obj}.
 */
goog.string.makeSafe = function(obj) {
  return obj == null ? '' : String(obj);
};


/**
 * Concatenates string expressions. This is useful
 * since some browsers are very inefficient when it comes to using plus to
 * concat strings. Be careful when using null and undefined here since
 * these will not be included in the result. If you need to represent these
 * be sure to cast the argument to a String first.
 * For example:
 * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
 * buildString(null, undefined) -> ''
 * </pre>
 * @param {...*} var_args A list of strings to concatenate. If not a string,
 *     it will be casted to one.
 * @return {string} The concatenation of {@code var_args}.
 */
goog.string.buildString = function(var_args) {
  return Array.prototype.join.call(arguments, '');
};


/**
 * Returns a string with at least 64-bits of randomness.
 *
 * Doesn't trust Javascript's random function entirely. Uses a combination of
 * random and current timestamp, and then encodes the string in base-36 to
 * make it shorter.
 *
 * @return {string} A random string, e.g. sn1s7vb4gcic.
 */
goog.string.getRandomString = function() {
  var x = 2147483648;
  return Math.floor(Math.random() * x).toString(36) +
      Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
};


/**
 * Compares two version numbers.
 *
 * @param {string|number} version1 Version of first item.
 * @param {string|number} version2 Version of second item.
 *
 * @return {number}  1 if {@code version1} is higher.
 *                   0 if arguments are equal.
 *                  -1 if {@code version2} is higher.
 */
goog.string.compareVersions = function(version1, version2) {
  var order = 0;
  // Trim leading and trailing whitespace and split the versions into
  // subversions.
  var v1Subs = goog.string.trim(String(version1)).split('.');
  var v2Subs = goog.string.trim(String(version2)).split('.');
  var subCount = Math.max(v1Subs.length, v2Subs.length);

  // Iterate over the subversions, as long as they appear to be equivalent.
  for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
    var v1Sub = v1Subs[subIdx] || '';
    var v2Sub = v2Subs[subIdx] || '';

    // Split the subversions into pairs of numbers and qualifiers (like 'b').
    // Two different RegExp objects are needed because they are both using
    // the 'g' flag.
    var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
    var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
    do {
      var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
      var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
      // Break if there are no more matches.
      if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
        break;
      }

      // Parse the numeric part of the subversion. A missing number is
      // equivalent to 0.
      var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
      var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);

      // Compare the subversion components. The number has the highest
      // precedence. Next, if the numbers are equal, a subversion without any
      // qualifier is always higher than a subversion with any qualifier. Next,
      // the qualifiers are compared as strings.
      order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
          goog.string.compareElements_(
              v1Comp[2].length == 0, v2Comp[2].length == 0) ||
          goog.string.compareElements_(v1Comp[2], v2Comp[2]);
      // Stop as soon as an inequality is discovered.
    } while (order == 0);
  }

  return order;
};


/**
 * Compares elements of a version number.
 *
 * @param {string|number|boolean} left An element from a version number.
 * @param {string|number|boolean} right An element from a version number.
 *
 * @return {number}  1 if {@code left} is higher.
 *                   0 if arguments are equal.
 *                  -1 if {@code right} is higher.
 * @private
 */
goog.string.compareElements_ = function(left, right) {
  if (left < right) {
    return -1;
  } else if (left > right) {
    return 1;
  }
  return 0;
};


/**
 * String hash function similar to java.lang.String.hashCode().
 * The hash code for a string is computed as
 * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
 * where s[i] is the ith character of the string and n is the length of
 * the string. We mod the result to make it between 0 (inclusive) and 2^32
 * (exclusive).
 * @param {string} str A string.
 * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
 *  (exclusive). The empty string returns 0.
 */
goog.string.hashCode = function(str) {
  var result = 0;
  for (var i = 0; i < str.length; ++i) {
    // Normalize to 4 byte range, 0 ... 2^32.
    result = (31 * result + str.charCodeAt(i)) >>> 0;
  }
  return result;
};


/**
 * The most recent unique ID. |0 is equivalent to Math.floor in this case.
 * @type {number}
 * @private
 */
goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;


/**
 * Generates and returns a string which is unique in the current document.
 * This is useful, for example, to create unique IDs for DOM elements.
 * @return {string} A unique id.
 */
goog.string.createUniqueString = function() {
  return 'goog_' + goog.string.uniqueStringCounter_++;
};


/**
 * Converts the supplied string to a number, which may be Infinity or NaN.
 * This function strips whitespace: (toNumber(' 123') === 123)
 * This function accepts scientific notation: (toNumber('1e1') === 10)
 *
 * This is better than Javascript's built-in conversions because, sadly:
 *     (Number(' ') === 0) and (parseFloat('123a') === 123)
 *
 * @param {string} str The string to convert.
 * @return {number} The number the supplied string represents, or NaN.
 */
goog.string.toNumber = function(str) {
  var num = Number(str);
  if (num == 0 && goog.string.isEmptyOrWhitespace(str)) {
    return NaN;
  }
  return num;
};


/**
 * Returns whether the given string is lower camel case (e.g. "isFooBar").
 *
 * Note that this assumes the string is entirely letters.
 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
 *
 * @param {string} str String to test.
 * @return {boolean} Whether the string is lower camel case.
 */
goog.string.isLowerCamelCase = function(str) {
  return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
};


/**
 * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
 *
 * Note that this assumes the string is entirely letters.
 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
 *
 * @param {string} str String to test.
 * @return {boolean} Whether the string is upper camel case.
 */
goog.string.isUpperCamelCase = function(str) {
  return /^([A-Z][a-z]*)+$/.test(str);
};


/**
 * Converts a string from selector-case to camelCase (e.g. from
 * "multi-part-string" to "multiPartString"), useful for converting
 * CSS selectors and HTML dataset keys to their equivalent JS properties.
 * @param {string} str The string in selector-case form.
 * @return {string} The string in camelCase form.
 */
goog.string.toCamelCase = function(str) {
  return String(str).replace(
      /\-([a-z])/g, function(all, match) { return match.toUpperCase(); });
};


/**
 * Converts a string from camelCase to selector-case (e.g. from
 * "multiPartString" to "multi-part-string"), useful for converting JS
 * style and dataset properties to equivalent CSS selectors and HTML keys.
 * @param {string} str The string in camelCase form.
 * @return {string} The string in selector-case form.
 */
goog.string.toSelectorCase = function(str) {
  return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
};


/**
 * Converts a string into TitleCase. First character of the string is always
 * capitalized in addition to the first letter of every subsequent word.
 * Words are delimited by one or more whitespaces by default. Custom delimiters
 * can optionally be specified to replace the default, which doesn't preserve
 * whitespace delimiters and instead must be explicitly included if needed.
 *
 * Default delimiter => " ":
 *    goog.string.toTitleCase('oneTwoThree')    => 'OneTwoThree'
 *    goog.string.toTitleCase('one two three')  => 'One Two Three'
 *    goog.string.toTitleCase('  one   two   ') => '  One   Two   '
 *    goog.string.toTitleCase('one_two_three')  => 'One_two_three'
 *    goog.string.toTitleCase('one-two-three')  => 'One-two-three'
 *
 * Custom delimiter => "_-.":
 *    goog.string.toTitleCase('oneTwoThree', '_-.')       => 'OneTwoThree'
 *    goog.string.toTitleCase('one two three', '_-.')     => 'One two three'
 *    goog.string.toTitleCase('  one   two   ', '_-.')    => '  one   two   '
 *    goog.string.toTitleCase('one_two_three', '_-.')     => 'One_Two_Three'
 *    goog.string.toTitleCase('one-two-three', '_-.')     => 'One-Two-Three'
 *    goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
 *    goog.string.toTitleCase('one. two. three', '_-.')   => 'One. two. three'
 *    goog.string.toTitleCase('one-two.three', '_-.')     => 'One-Two.Three'
 *
 * @param {string} str String value in camelCase form.
 * @param {string=} opt_delimiters Custom delimiter character set used to
 *      distinguish words in the string value. Each character represents a
 *      single delimiter. When provided, default whitespace delimiter is
 *      overridden and must be explicitly included if needed.
 * @return {string} String value in TitleCase form.
 */
goog.string.toTitleCase = function(str, opt_delimiters) {
  var delimiters = goog.isString(opt_delimiters) ?
      goog.string.regExpEscape(opt_delimiters) :
      '\\s';

  // For IE8, we need to prevent using an empty character set. Otherwise,
  // incorrect matching will occur.
  delimiters = delimiters ? '|[' + delimiters + ']+' : '';

  var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
  return str.replace(
      regexp, function(all, p1, p2) { return p1 + p2.toUpperCase(); });
};


/**
 * Capitalizes a string, i.e. converts the first letter to uppercase
 * and all other letters to lowercase, e.g.:
 *
 * goog.string.capitalize('one')     => 'One'
 * goog.string.capitalize('ONE')     => 'One'
 * goog.string.capitalize('one two') => 'One two'
 *
 * Note that this function does not trim initial whitespace.
 *
 * @param {string} str String value to capitalize.
 * @return {string} String value with first letter in uppercase.
 */
goog.string.capitalize = function(str) {
  return String(str.charAt(0)).toUpperCase() +
      String(str.substr(1)).toLowerCase();
};


/**
 * Parse a string in decimal or hexidecimal ('0xFFFF') form.
 *
 * To parse a particular radix, please use parseInt(string, radix) directly. See
 * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
 *
 * This is a wrapper for the built-in parseInt function that will only parse
 * numbers as base 10 or base 16.  Some JS implementations assume strings
 * starting with "0" are intended to be octal. ES3 allowed but discouraged
 * this behavior. ES5 forbids it.  This function emulates the ES5 behavior.
 *
 * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
 *
 * @param {string|number|null|undefined} value The value to be parsed.
 * @return {number} The number, parsed. If the string failed to parse, this
 *     will be NaN.
 */
goog.string.parseInt = function(value) {
  // Force finite numbers to strings.
  if (isFinite(value)) {
    value = String(value);
  }

  if (goog.isString(value)) {
    // If the string starts with '0x' or '-0x', parse as hex.
    return /^\s*-?0x/i.test(value) ? parseInt(value, 16) : parseInt(value, 10);
  }

  return NaN;
};


/**
 * Splits a string on a separator a limited number of times.
 *
 * This implementation is more similar to Python or Java, where the limit
 * parameter specifies the maximum number of splits rather than truncating
 * the number of results.
 *
 * See http://docs.python.org/2/library/stdtypes.html#str.split
 * See JavaDoc: http://goo.gl/F2AsY
 * See Mozilla reference: http://goo.gl/dZdZs
 *
 * @param {string} str String to split.
 * @param {string} separator The separator.
 * @param {number} limit The limit to the number of splits. The resulting array
 *     will have a maximum length of limit+1.  Negative numbers are the same
 *     as zero.
 * @return {!Array<string>} The string, split.
 */
goog.string.splitLimit = function(str, separator, limit) {
  var parts = str.split(separator);
  var returnVal = [];

  // Only continue doing this while we haven't hit the limit and we have
  // parts left.
  while (limit > 0 && parts.length) {
    returnVal.push(parts.shift());
    limit--;
  }

  // If there are remaining parts, append them to the end.
  if (parts.length) {
    returnVal.push(parts.join(separator));
  }

  return returnVal;
};


/**
 * Finds the characters to the right of the last instance of any separator
 *
 * This function is similar to goog.string.path.baseName, except it can take a
 * list of characters to split the string on. It will return the rightmost
 * grouping of characters to the right of any separator as a left-to-right
 * oriented string.
 *
 * @see goog.string.path.baseName
 * @param {string} str The string
 * @param {string|!Array<string>} separators A list of separator characters
 * @return {string} The last part of the string with respect to the separators
 */
goog.string.lastComponent = function(str, separators) {
  if (!separators) {
    return str;
  } else if (typeof separators == 'string') {
    separators = [separators];
  }

  var lastSeparatorIndex = -1;
  for (var i = 0; i < separators.length; i++) {
    if (separators[i] == '') {
      continue;
    }
    var currentSeparatorIndex = str.lastIndexOf(separators[i]);
    if (currentSeparatorIndex > lastSeparatorIndex) {
      lastSeparatorIndex = currentSeparatorIndex;
    }
  }
  if (lastSeparatorIndex == -1) {
    return str;
  }
  return str.slice(lastSeparatorIndex + 1);
};


/**
 * Computes the Levenshtein edit distance between two strings.
 * @param {string} a
 * @param {string} b
 * @return {number} The edit distance between the two strings.
 */
goog.string.editDistance = function(a, b) {
  var v0 = [];
  var v1 = [];

  if (a == b) {
    return 0;
  }

  if (!a.length || !b.length) {
    return Math.max(a.length, b.length);
  }

  for (var i = 0; i < b.length + 1; i++) {
    v0[i] = i;
  }

  for (var i = 0; i < a.length; i++) {
    v1[0] = i + 1;

    for (var j = 0; j < b.length; j++) {
      var cost = Number(a[i] != b[j]);
      // Cost for the substring is the minimum of adding one character, removing
      // one character, or a swap.
      v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
    }

    for (var j = 0; j < v0.length; j++) {
      v0[j] = v1[j];
    }
  }

  return v1[b.length];
};

// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Utilities to check the preconditions, postconditions and
 * invariants runtime.
 *
 * Methods in this package should be given special treatment by the compiler
 * for type-inference. For example, <code>goog.asserts.assert(foo)</code>
 * will restrict <code>foo</code> to a truthy value.
 *
 * The compiler has an option to disable asserts. So code like:
 * <code>
 * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
 * </code>
 * will be transformed into:
 * <code>
 * var x = foo();
 * </code>
 * The compiler will leave in foo() (because its return value is used),
 * but it will remove bar() because it assumes it does not have side-effects.
 *
 * @author agrieve@google.com (Andrew Grieve)
 */

goog.provide('goog.asserts');
goog.provide('goog.asserts.AssertionError');

goog.require('goog.debug.Error');
goog.require('goog.dom.NodeType');
goog.require('goog.string');


/**
 * @define {boolean} Whether to strip out asserts or to leave them in.
 */
goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);



/**
 * Error object for failed assertions.
 * @param {string} messagePattern The pattern that was used to form message.
 * @param {!Array<*>} messageArgs The items to substitute into the pattern.
 * @constructor
 * @extends {goog.debug.Error}
 * @final
 */
goog.asserts.AssertionError = function(messagePattern, messageArgs) {
  messageArgs.unshift(messagePattern);
  goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
  // Remove the messagePattern afterwards to avoid permanently modifying the
  // passed in array.
  messageArgs.shift();

  /**
   * The message pattern used to format the error message. Error handlers can
   * use this to uniquely identify the assertion.
   * @type {string}
   */
  this.messagePattern = messagePattern;
};
goog.inherits(goog.asserts.AssertionError, goog.debug.Error);


/** @override */
goog.asserts.AssertionError.prototype.name = 'AssertionError';


/**
 * The default error handler.
 * @param {!goog.asserts.AssertionError} e The exception to be handled.
 */
goog.asserts.DEFAULT_ERROR_HANDLER = function(e) {
  throw e;
};


/**
 * The handler responsible for throwing or logging assertion errors.
 * @private {function(!goog.asserts.AssertionError)}
 */
goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;


/**
 * Throws an exception with the given message and "Assertion failed" prefixed
 * onto it.
 * @param {string} defaultMessage The message to use if givenMessage is empty.
 * @param {Array<*>} defaultArgs The substitution arguments for defaultMessage.
 * @param {string|undefined} givenMessage Message supplied by the caller.
 * @param {Array<*>} givenArgs The substitution arguments for givenMessage.
 * @throws {goog.asserts.AssertionError} When the value is not a number.
 * @private
 */
goog.asserts.doAssertFailure_ = function(
    defaultMessage, defaultArgs, givenMessage, givenArgs) {
  var message = 'Assertion failed';
  if (givenMessage) {
    message += ': ' + givenMessage;
    var args = givenArgs;
  } else if (defaultMessage) {
    message += ': ' + defaultMessage;
    args = defaultArgs;
  }
  // The '' + works around an Opera 10 bug in the unit tests. Without it,
  // a stack trace is added to var message above. With this, a stack trace is
  // not added until this line (it causes the extra garbage to be added after
  // the assertion message instead of in the middle of it).
  var e = new goog.asserts.AssertionError('' + message, args || []);
  goog.asserts.errorHandler_(e);
};


/**
 * Sets a custom error handler that can be used to customize the behavior of
 * assertion failures, for example by turning all assertion failures into log
 * messages.
 * @param {function(!goog.asserts.AssertionError)} errorHandler
 */
goog.asserts.setErrorHandler = function(errorHandler) {
  if (goog.asserts.ENABLE_ASSERTS) {
    goog.asserts.errorHandler_ = errorHandler;
  }
};


/**
 * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
 * true.
 * @template T
 * @param {T} condition The condition to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {T} The value of the condition.
 * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
 */
goog.asserts.assert = function(condition, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !condition) {
    goog.asserts.doAssertFailure_(
        '', null, opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return condition;
};


/**
 * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
 * when we want to add a check in the unreachable area like switch-case
 * statement:
 *
 * <pre>
 *  switch(type) {
 *    case FOO: doSomething(); break;
 *    case BAR: doSomethingElse(); break;
 *    default: goog.asserts.fail('Unrecognized type: ' + type);
 *      // We have only 2 types - "default:" section is unreachable code.
 *  }
 * </pre>
 *
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @throws {goog.asserts.AssertionError} Failure.
 */
goog.asserts.fail = function(opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS) {
    goog.asserts.errorHandler_(
        new goog.asserts.AssertionError(
            'Failure' + (opt_message ? ': ' + opt_message : ''),
            Array.prototype.slice.call(arguments, 1)));
  }
};


/**
 * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {number} The value, guaranteed to be a number when asserts enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a number.
 */
goog.asserts.assertNumber = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
    goog.asserts.doAssertFailure_(
        'Expected number but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {number} */ (value);
};


/**
 * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {string} The value, guaranteed to be a string when asserts enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a string.
 */
goog.asserts.assertString = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
    goog.asserts.doAssertFailure_(
        'Expected string but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {string} */ (value);
};


/**
 * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Function} The value, guaranteed to be a function when asserts
 *     enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a function.
 */
goog.asserts.assertFunction = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
    goog.asserts.doAssertFailure_(
        'Expected function but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Function} */ (value);
};


/**
 * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Object} The value, guaranteed to be a non-null object.
 * @throws {goog.asserts.AssertionError} When the value is not an object.
 */
goog.asserts.assertObject = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
    goog.asserts.doAssertFailure_(
        'Expected object but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Object} */ (value);
};


/**
 * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Array<?>} The value, guaranteed to be a non-null array.
 * @throws {goog.asserts.AssertionError} When the value is not an array.
 */
goog.asserts.assertArray = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
    goog.asserts.doAssertFailure_(
        'Expected array but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Array<?>} */ (value);
};


/**
 * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {boolean} The value, guaranteed to be a boolean when asserts are
 *     enabled.
 * @throws {goog.asserts.AssertionError} When the value is not a boolean.
 */
goog.asserts.assertBoolean = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
    goog.asserts.doAssertFailure_(
        'Expected boolean but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {boolean} */ (value);
};


/**
 * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.
 * @param {*} value The value to check.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @return {!Element} The value, likely to be a DOM Element when asserts are
 *     enabled.
 * @throws {goog.asserts.AssertionError} When the value is not an Element.
 */
goog.asserts.assertElement = function(value, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS &&
      (!goog.isObject(value) || value.nodeType != goog.dom.NodeType.ELEMENT)) {
    goog.asserts.doAssertFailure_(
        'Expected Element but got %s: %s.', [goog.typeOf(value), value],
        opt_message, Array.prototype.slice.call(arguments, 2));
  }
  return /** @type {!Element} */ (value);
};


/**
 * Checks if the value is an instance of the user-defined type if
 * goog.asserts.ENABLE_ASSERTS is true.
 *
 * The compiler may tighten the type returned by this function.
 *
 * @param {?} value The value to check.
 * @param {function(new: T, ...)} type A user-defined constructor.
 * @param {string=} opt_message Error message in case of failure.
 * @param {...*} var_args The items to substitute into the failure message.
 * @throws {goog.asserts.AssertionError} When the value is not an instance of
 *     type.
 * @return {T}
 * @template T
 */
goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
  if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
    goog.asserts.doAssertFailure_(
        'Expected instanceof %s but got %s.',
        [goog.asserts.getType_(type), goog.asserts.getType_(value)],
        opt_message, Array.prototype.slice.call(arguments, 3));
  }
  return value;
};


/**
 * Checks that no enumerable keys are present in Object.prototype. Such keys
 * would break most code that use {@code for (var ... in ...)} loops.
 */
goog.asserts.assertObjectPrototypeIsIntact = function() {
  for (var key in Object.prototype) {
    goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
  }
};


/**
 * Returns the type of a value. If a constructor is passed, and a suitable
 * string cannot be found, 'unknown type name' will be returned.
 * @param {*} value A constructor, object, or primitive.
 * @return {string} The best display name for the value, or 'unknown type name'.
 * @private
 */
goog.asserts.getType_ = function(value) {
  if (value instanceof Function) {
    return value.displayName || value.name || 'unknown type name';
  } else if (value instanceof Object) {
    return value.constructor.displayName || value.constructor.name ||
        Object.prototype.toString.call(value);
  } else {
    return value === null ? 'null' : typeof value;
  }
};

// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Utilities for manipulating arrays.
 *
 * @author arv@google.com (Erik Arvidsson)
 */


goog.provide('goog.array');

goog.require('goog.asserts');


/**
 * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
 * rely on Array.prototype functions, if available.
 *
 * The Array.prototype functions can be defined by external libraries like
 * Prototype and setting this flag to false forces closure to use its own
 * goog.array implementation.
 *
 * If your javascript can be loaded by a third party site and you are wary about
 * relying on the prototype functions, specify
 * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
 *
 * Setting goog.TRUSTED_SITE to false will automatically set
 * NATIVE_ARRAY_PROTOTYPES to false.
 */
goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);


/**
 * @define {boolean} If true, JSCompiler will use the native implementation of
 * array functions where appropriate (e.g., {@code Array#filter}) and remove the
 * unused pure JS implementation.
 */
goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false);


/**
 * Returns the last element in an array without removing it.
 * Same as goog.array.last.
 * @param {IArrayLike<T>|string} array The array.
 * @return {T} Last item in array.
 * @template T
 */
goog.array.peek = function(array) {
  return array[array.length - 1];
};


/**
 * Returns the last element in an array without removing it.
 * Same as goog.array.peek.
 * @param {IArrayLike<T>|string} array The array.
 * @return {T} Last item in array.
 * @template T
 */
goog.array.last = goog.array.peek;

// NOTE(arv): Since most of the array functions are generic it allows you to
// pass an array-like object. Strings have a length and are considered array-
// like. However, the 'in' operator does not work on strings so we cannot just
// use the array path even if the browser supports indexing into strings. We
// therefore end up splitting the string.


/**
 * Returns the index of the first element of an array with a specified value, or
 * -1 if the element is not present in the array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
 *
 * @param {IArrayLike<T>|string} arr The array to be searched.
 * @param {T} obj The object for which we are searching.
 * @param {number=} opt_fromIndex The index at which to start the search. If
 *     omitted the search starts at index 0.
 * @return {number} The index of the first matching array element.
 * @template T
 */
goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.indexOf) ?
    function(arr, obj, opt_fromIndex) {
      goog.asserts.assert(arr.length != null);

      return Array.prototype.indexOf.call(arr, obj, opt_fromIndex);
    } :
    function(arr, obj, opt_fromIndex) {
      var fromIndex = opt_fromIndex == null ?
          0 :
          (opt_fromIndex < 0 ? Math.max(0, arr.length + opt_fromIndex) :
                               opt_fromIndex);

      if (goog.isString(arr)) {
        // Array.prototype.indexOf uses === so only strings should be found.
        if (!goog.isString(obj) || obj.length != 1) {
          return -1;
        }
        return arr.indexOf(obj, fromIndex);
      }

      for (var i = fromIndex; i < arr.length; i++) {
        if (i in arr && arr[i] === obj) return i;
      }
      return -1;
    };


/**
 * Returns the index of the last element of an array with a specified value, or
 * -1 if the element is not present in the array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
 *
 * @param {!IArrayLike<T>|string} arr The array to be searched.
 * @param {T} obj The object for which we are searching.
 * @param {?number=} opt_fromIndex The index at which to start the search. If
 *     omitted the search starts at the end of the array.
 * @return {number} The index of the last matching array element.
 * @template T
 */
goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.lastIndexOf) ?
    function(arr, obj, opt_fromIndex) {
      goog.asserts.assert(arr.length != null);

      // Firefox treats undefined and null as 0 in the fromIndex argument which
      // leads it to always return -1
      var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
      return Array.prototype.lastIndexOf.call(arr, obj, fromIndex);
    } :
    function(arr, obj, opt_fromIndex) {
      var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;

      if (fromIndex < 0) {
        fromIndex = Math.max(0, arr.length + fromIndex);
      }

      if (goog.isString(arr)) {
        // Array.prototype.lastIndexOf uses === so only strings should be found.
        if (!goog.isString(obj) || obj.length != 1) {
          return -1;
        }
        return arr.lastIndexOf(obj, fromIndex);
      }

      for (var i = fromIndex; i >= 0; i--) {
        if (i in arr && arr[i] === obj) return i;
      }
      return -1;
    };


/**
 * Calls a function for each element in an array. Skips holes in the array.
 * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
 *
 * @param {IArrayLike<T>|string} arr Array or array like object over
 *     which to iterate.
 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
 *     element. This function takes 3 arguments (the element, the index and the
 *     array). The return value is ignored.
 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
 * @template T,S
 */
goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.forEach) ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      Array.prototype.forEach.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2) {
          f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr);
        }
      }
    };


/**
 * Calls a function for each element in an array, starting from the last
 * element rather than the first.
 *
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
 *     element. This function
 *     takes 3 arguments (the element, the index and the array). The return
 *     value is ignored.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @template T,S
 */
goog.array.forEachRight = function(arr, f, opt_obj) {
  var l = arr.length;  // must be fixed during loop... see docs
  var arr2 = goog.isString(arr) ? arr.split('') : arr;
  for (var i = l - 1; i >= 0; --i) {
    if (i in arr2) {
      f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr);
    }
  }
};


/**
 * Calls a function for each element in an array, and if the function returns
 * true adds the element to a new array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
 *
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?):boolean} f The function to call for
 *     every element. This function
 *     takes 3 arguments (the element, the index and the array) and must
 *     return a Boolean. If the return value is true the element is added to the
 *     result array. If it is false the element is not included.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {!Array<T>} a new array in which only elements that passed the test
 *     are present.
 * @template T,S
 */
goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.filter) ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return Array.prototype.filter.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var res = [];
      var resLength = 0;
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2) {
          var val = arr2[i];  // in case f mutates arr2
          if (f.call(/** @type {?} */ (opt_obj), val, i, arr)) {
            res[resLength++] = val;
          }
        }
      }
      return res;
    };


/**
 * Calls a function for each element in an array and inserts the result into a
 * new array.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
 *
 * @param {IArrayLike<VALUE>|string} arr Array or array like object
 *     over which to iterate.
 * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call
 *     for every element. This function takes 3 arguments (the element,
 *     the index and the array) and should return something. The result will be
 *     inserted into a new array.
 * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
 * @return {!Array<RESULT>} a new array with the results from f.
 * @template THIS, VALUE, RESULT
 */
goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.map) ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return Array.prototype.map.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var res = new Array(l);
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2) {
          res[i] = f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr);
        }
      }
      return res;
    };


/**
 * Passes every element of an array into a function and accumulates the result.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
 *
 * For example:
 * var a = [1, 2, 3, 4];
 * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
 * returns 10
 *
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {function(this:S, R, T, number, ?) : R} f The function to call for
 *     every element. This function
 *     takes 4 arguments (the function's previous result or the initial value,
 *     the value of the current array element, the current array index, and the
 *     array itself)
 *     function(previousValue, currentValue, index, array).
 * @param {?} val The initial value to pass into the function on the first call.
 * @param {S=} opt_obj  The object to be used as the value of 'this'
 *     within f.
 * @return {R} Result of evaluating f repeatedly across the values of the array.
 * @template T,S,R
 */
goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.reduce) ?
    function(arr, f, val, opt_obj) {
      goog.asserts.assert(arr.length != null);
      if (opt_obj) {
        f = goog.bind(f, opt_obj);
      }
      return Array.prototype.reduce.call(arr, f, val);
    } :
    function(arr, f, val, opt_obj) {
      var rval = val;
      goog.array.forEach(arr, function(val, index) {
        rval = f.call(/** @type {?} */ (opt_obj), rval, val, index, arr);
      });
      return rval;
    };


/**
 * Passes every element of an array into a function and accumulates the result,
 * starting from the last element and working towards the first.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
 *
 * For example:
 * var a = ['a', 'b', 'c'];
 * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
 * returns 'cba'
 *
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
 *     every element. This function
 *     takes 4 arguments (the function's previous result or the initial value,
 *     the value of the current array element, the current array index, and the
 *     array itself)
 *     function(previousValue, currentValue, index, array).
 * @param {?} val The initial value to pass into the function on the first call.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {R} Object returned as a result of evaluating f repeatedly across the
 *     values of the array.
 * @template T,S,R
 */
goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.reduceRight) ?
    function(arr, f, val, opt_obj) {
      goog.asserts.assert(arr.length != null);
      goog.asserts.assert(f != null);
      if (opt_obj) {
        f = goog.bind(f, opt_obj);
      }
      return Array.prototype.reduceRight.call(arr, f, val);
    } :
    function(arr, f, val, opt_obj) {
      var rval = val;
      goog.array.forEachRight(arr, function(val, index) {
        rval = f.call(/** @type {?} */ (opt_obj), rval, val, index, arr);
      });
      return rval;
    };


/**
 * Calls f for each element of an array. If any call returns true, some()
 * returns true (without checking the remaining elements). If all calls
 * return false, some() returns false.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
 *
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a boolean.
 * @param {S=} opt_obj  The object to be used as the value of 'this'
 *     within f.
 * @return {boolean} true if any element passes the test.
 * @template T,S
 */
goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.some) ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return Array.prototype.some.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2 && f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) {
          return true;
        }
      }
      return false;
    };


/**
 * Call f for each element of an array. If all calls return true, every()
 * returns true. If any call returns false, every() returns false and
 * does not continue to check the remaining elements.
 *
 * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
 *
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a boolean.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within f.
 * @return {boolean} false if any element fails the test.
 * @template T,S
 */
goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
        (goog.array.ASSUME_NATIVE_FUNCTIONS || Array.prototype.every) ?
    function(arr, f, opt_obj) {
      goog.asserts.assert(arr.length != null);

      return Array.prototype.every.call(arr, f, opt_obj);
    } :
    function(arr, f, opt_obj) {
      var l = arr.length;  // must be fixed during loop... see docs
      var arr2 = goog.isString(arr) ? arr.split('') : arr;
      for (var i = 0; i < l; i++) {
        if (i in arr2 && !f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) {
          return false;
        }
      }
      return true;
    };


/**
 * Counts the array elements that fulfill the predicate, i.e. for which the
 * callback function returns true. Skips holes in the array.
 *
 * @param {!IArrayLike<T>|string} arr Array or array like object
 *     over which to iterate.
 * @param {function(this: S, T, number, ?): boolean} f The function to call for
 *     every element. Takes 3 arguments (the element, the index and the array).
 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
 * @return {number} The number of the matching elements.
 * @template T,S
 */
goog.array.count = function(arr, f, opt_obj) {
  var count = 0;
  goog.array.forEach(arr, function(element, index, arr) {
    if (f.call(/** @type {?} */ (opt_obj), element, index, arr)) {
      ++count;
    }
  }, opt_obj);
  return count;
};


/**
 * Search an array for the first element that satisfies a given condition and
 * return that element.
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {T|null} The first array element that passes the test, or null if no
 *     element is found.
 * @template T,S
 */
goog.array.find = function(arr, f, opt_obj) {
  var i = goog.array.findIndex(arr, f, opt_obj);
  return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
};


/**
 * Search an array for the first element that satisfies a given condition and
 * return its index.
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
 *     every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {number} The index of the first array element that passes the test,
 *     or -1 if no element is found.
 * @template T,S
 */
goog.array.findIndex = function(arr, f, opt_obj) {
  var l = arr.length;  // must be fixed during loop... see docs
  var arr2 = goog.isString(arr) ? arr.split('') : arr;
  for (var i = 0; i < l; i++) {
    if (i in arr2 && f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) {
      return i;
    }
  }
  return -1;
};


/**
 * Search an array (in reverse order) for the last element that satisfies a
 * given condition and return that element.
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {T|null} The last array element that passes the test, or null if no
 *     element is found.
 * @template T,S
 */
goog.array.findRight = function(arr, f, opt_obj) {
  var i = goog.array.findIndexRight(arr, f, opt_obj);
  return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
};


/**
 * Search an array (in reverse order) for the last element that satisfies a
 * given condition and return its index.
 * @param {IArrayLike<T>|string} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {number} The index of the last array element that passes the test,
 *     or -1 if no element is found.
 * @template T,S
 */
goog.array.findIndexRight = function(arr, f, opt_obj) {
  var l = arr.length;  // must be fixed during loop... see docs
  var arr2 = goog.isString(arr) ? arr.split('') : arr;
  for (var i = l - 1; i >= 0; i--) {
    if (i in arr2 && f.call(/** @type {?} */ (opt_obj), arr2[i], i, arr)) {
      return i;
    }
  }
  return -1;
};


/**
 * Whether the array contains the given object.
 * @param {IArrayLike<?>|string} arr The array to test for the presence of the
 *     element.
 * @param {*} obj The object for which to test.
 * @return {boolean} true if obj is present.
 */
goog.array.contains = function(arr, obj) {
  return goog.array.indexOf(arr, obj) >= 0;
};


/**
 * Whether the array is empty.
 * @param {IArrayLike<?>|string} arr The array to test.
 * @return {boolean} true if empty.
 */
goog.array.isEmpty = function(arr) {
  return arr.length == 0;
};


/**
 * Clears the array.
 * @param {IArrayLike<?>} arr Array or array like object to clear.
 */
goog.array.clear = function(arr) {
  // For non real arrays we don't have the magic length so we delete the
  // indices.
  if (!goog.isArray(arr)) {
    for (var i = arr.length - 1; i >= 0; i--) {
      delete arr[i];
    }
  }
  arr.length = 0;
};


/**
 * Pushes an item into an array, if it's not already in the array.
 * @param {Array<T>} arr Array into which to insert the item.
 * @param {T} obj Value to add.
 * @template T
 */
goog.array.insert = function(arr, obj) {
  if (!goog.array.contains(arr, obj)) {
    arr.push(obj);
  }
};


/**
 * Inserts an object at the given index of the array.
 * @param {IArrayLike<?>} arr The array to modify.
 * @param {*} obj The object to insert.
 * @param {number=} opt_i The index at which to insert the object. If omitted,
 *      treated as 0. A negative index is counted from the end of the array.
 */
goog.array.insertAt = function(arr, obj, opt_i) {
  goog.array.splice(arr, opt_i, 0, obj);
};


/**
 * Inserts at the given index of the array, all elements of another array.
 * @param {IArrayLike<?>} arr The array to modify.
 * @param {IArrayLike<?>} elementsToAdd The array of elements to add.
 * @param {number=} opt_i The index at which to insert the object. If omitted,
 *      treated as 0. A negative index is counted from the end of the array.
 */
goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
  goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
};


/**
 * Inserts an object into an array before a specified object.
 * @param {Array<T>} arr The array to modify.
 * @param {T} obj The object to insert.
 * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
 *     is omitted or not found, obj is inserted at the end of the array.
 * @template T
 */
goog.array.insertBefore = function(arr, obj, opt_obj2) {
  var i;
  if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
    arr.push(obj);
  } else {
    goog.array.insertAt(arr, obj, i);
  }
};


/**
 * Removes the first occurrence of a particular value from an array.
 * @param {IArrayLike<T>} arr Array from which to remove
 *     value.
 * @param {T} obj Object to remove.
 * @return {boolean} True if an element was removed.
 * @template T
 */
goog.array.remove = function(arr, obj) {
  var i = goog.array.indexOf(arr, obj);
  var rv;
  if ((rv = i >= 0)) {
    goog.array.removeAt(arr, i);
  }
  return rv;
};


/**
 * Removes the last occurrence of a particular value from an array.
 * @param {!IArrayLike<T>} arr Array from which to remove value.
 * @param {T} obj Object to remove.
 * @return {boolean} True if an element was removed.
 * @template T
 */
goog.array.removeLast = function(arr, obj) {
  var i = goog.array.lastIndexOf(arr, obj);
  if (i >= 0) {
    goog.array.removeAt(arr, i);
    return true;
  }
  return false;
};


/**
 * Removes from an array the element at index i
 * @param {IArrayLike<?>} arr Array or array like object from which to
 *     remove value.
 * @param {number} i The index to remove.
 * @return {boolean} True if an element was removed.
 */
goog.array.removeAt = function(arr, i) {
  goog.asserts.assert(arr.length != null);

  // use generic form of splice
  // splice returns the removed items and if successful the length of that
  // will be 1
  return Array.prototype.splice.call(arr, i, 1).length == 1;
};


/**
 * Removes the first value that satisfies the given condition.
 * @param {IArrayLike<T>} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {boolean} True if an element was removed.
 * @template T,S
 */
goog.array.removeIf = function(arr, f, opt_obj) {
  var i = goog.array.findIndex(arr, f, opt_obj);
  if (i >= 0) {
    goog.array.removeAt(arr, i);
    return true;
  }
  return false;
};


/**
 * Removes all values that satisfy the given condition.
 * @param {IArrayLike<T>} arr Array or array
 *     like object over which to iterate.
 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the element, the index and the array) and should
 *     return a boolean.
 * @param {S=} opt_obj An optional "this" context for the function.
 * @return {number} The number of items removed
 * @template T,S
 */
goog.array.removeAllIf = function(arr, f, opt_obj) {
  var removedCount = 0;
  goog.array.forEachRight(arr, function(val, index) {
    if (f.call(/** @type {?} */ (opt_obj), val, index, arr)) {
      if (goog.array.removeAt(arr, index)) {
        removedCount++;
      }
    }
  });
  return removedCount;
};


/**
 * Returns a new array that is the result of joining the arguments.  If arrays
 * are passed then their items are added, however, if non-arrays are passed they
 * will be added to the return array as is.
 *
 * Note that ArrayLike objects will be added as is, rather than having their
 * items added.
 *
 * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
 * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
 * goog.array.concat([1, 2], null) -> [1, 2, null]
 *
 * There is bug in all current versions of IE (6, 7 and 8) where arrays created
 * in an iframe become corrupted soon (not immediately) after the iframe is
 * destroyed. This is common if loading data via goog.net.IframeIo, for example.
 * This corruption only affects the concat method which will start throwing
 * Catastrophic Errors (#-2147418113).
 *
 * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
 *
 * Internally goog.array should use this, so that all methods will continue to
 * work on these broken array objects.
 *
 * @param {...*} var_args Items to concatenate.  Arrays will have each item
 *     added, while primitives and objects will be added as is.
 * @return {!Array<?>} The new resultant array.
 */
goog.array.concat = function(var_args) {
  return Array.prototype.concat.apply(Array.prototype, arguments);
};


/**
 * Returns a new array that contains the contents of all the arrays passed.
 * @param {...!Array<T>} var_args
 * @return {!Array<T>}
 * @template T
 */
goog.array.join = function(var_args) {
  return Array.prototype.concat.apply(Array.prototype, arguments);
};


/**
 * Converts an object to an array.
 * @param {IArrayLike<T>|string} object  The object to convert to an
 *     array.
 * @return {!Array<T>} The object converted into an array. If object has a
 *     length property, every property indexed with a non-negative number
 *     less than length will be included in the result. If object does not
 *     have a length property, an empty array will be returned.
 * @template T
 */
goog.array.toArray = function(object) {
  var length = object.length;

  // If length is not a number the following it false. This case is kept for
  // backwards compatibility since there are callers that pass objects that are
  // not array like.
  if (length > 0) {
    var rv = new Array(length);
    for (var i = 0; i < length; i++) {
      rv[i] = object[i];
    }
    return rv;
  }
  return [];
};


/**
 * Does a shallow copy of an array.
 * @param {IArrayLike<T>|string} arr  Array or array-like object to
 *     clone.
 * @return {!Array<T>} Clone of the input array.
 * @template T
 */
goog.array.clone = goog.array.toArray;


/**
 * Extends an array with another array, element, or "array like" object.
 * This function operates 'in-place', it does not create a new Array.
 *
 * Example:
 * var a = [];
 * goog.array.extend(a, [0, 1]);
 * a; // [0, 1]
 * goog.array.extend(a, 2);
 * a; // [0, 1, 2]
 *
 * @param {Array<VALUE>} arr1  The array to modify.
 * @param {...(Array<VALUE>|VALUE)} var_args The elements or arrays of elements
 *     to add to arr1.
 * @template VALUE
 */
goog.array.extend = function(arr1, var_args) {
  for (var i = 1; i < arguments.length; i++) {
    var arr2 = arguments[i];
    if (goog.isArrayLike(arr2)) {
      var len1 = arr1.length || 0;
      var len2 = arr2.length || 0;
      arr1.length = len1 + len2;
      for (var j = 0; j < len2; j++) {
        arr1[len1 + j] = arr2[j];
      }
    } else {
      arr1.push(arr2);
    }
  }
};


/**
 * Adds or removes elements from an array. This is a generic version of Array
 * splice. This means that it might work on other objects similar to arrays,
 * such as the arguments object.
 *
 * @param {IArrayLike<T>} arr The array to modify.
 * @param {number|undefined} index The index at which to start changing the
 *     array. If not defined, treated as 0.
 * @param {number} howMany How many elements to remove (0 means no removal. A
 *     value below 0 is treated as zero and so is any other non number. Numbers
 *     are floored).
 * @param {...T} var_args Optional, additional elements to insert into the
 *     array.
 * @return {!Array<T>} the removed elements.
 * @template T
 */
goog.array.splice = function(arr, index, howMany, var_args) {
  goog.asserts.assert(arr.length != null);

  return Array.prototype.splice.apply(arr, goog.array.slice(arguments, 1));
};


/**
 * Returns a new array from a segment of an array. This is a generic version of
 * Array slice. This means that it might work on other objects similar to
 * arrays, such as the arguments object.
 *
 * @param {IArrayLike<T>|string} arr The array from
 * which to copy a segment.
 * @param {number} start The index of the first element to copy.
 * @param {number=} opt_end The index after the last element to copy.
 * @return {!Array<T>} A new array containing the specified segment of the
 *     original array.
 * @template T
 */
goog.array.slice = function(arr, start, opt_end) {
  goog.asserts.assert(arr.length != null);

  // passing 1 arg to slice is not the same as passing 2 where the second is
  // null or undefined (in that case the second argument is treated as 0).
  // we could use slice on the arguments object and then use apply instead of
  // testing the length
  if (arguments.length <= 2) {
    return Array.prototype.slice.call(arr, start);
  } else {
    return Array.prototype.slice.call(arr, start, opt_end);
  }
};


/**
 * Removes all duplicates from an array (retaining only the first
 * occurrence of each array element).  This function modifies the
 * array in place and doesn't change the order of the non-duplicate items.
 *
 * For objects, duplicates are identified as having the same unique ID as
 * defined by {@link goog.getUid}.
 *
 * Alternatively you can specify a custom hash function that returns a unique
 * value for each item in the array it should consider unique.
 *
 * Runtime: N,
 * Worstcase space: 2N (no dupes)
 *
 * @param {IArrayLike<T>} arr The array from which to remove
 *     duplicates.
 * @param {Array=} opt_rv An optional array in which to return the results,
 *     instead of performing the removal inplace.  If specified, the original
 *     array will remain unchanged.
 * @param {function(T):string=} opt_hashFn An optional function to use to
 *     apply to every item in the array. This function should return a unique
 *     value for each item in the array it should consider unique.
 * @template T
 */
goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) {
  var returnArray = opt_rv || arr;
  var defaultHashFn = function(item) {
    // Prefix each type with a single character representing the type to
    // prevent conflicting keys (e.g. true and 'true').
    return goog.isObject(item) ? 'o' + goog.getUid(item) :
                                 (typeof item).charAt(0) + item;
  };
  var hashFn = opt_hashFn || defaultHashFn;

  var seen = {}, cursorInsert = 0, cursorRead = 0;
  while (cursorRead < arr.length) {
    var current = arr[cursorRead++];
    var key = hashFn(current);
    if (!Object.prototype.hasOwnProperty.call(seen, key)) {
      seen[key] = true;
      returnArray[cursorInsert++] = current;
    }
  }
  returnArray.length = cursorInsert;
};


/**
 * Searches the specified array for the specified target using the binary
 * search algorithm.  If no opt_compareFn is specified, elements are compared
 * using <code>goog.array.defaultCompare</code>, which compares the elements
 * using the built in < and > operators.  This will produce the expected
 * behavior for homogeneous arrays of String(s) and Number(s). The array
 * specified <b>must</b> be sorted in ascending order (as defined by the
 * comparison function).  If the array is not sorted, results are undefined.
 * If the array contains multiple instances of the specified target value, any
 * of these instances may be found.
 *
 * Runtime: O(log n)
 *
 * @param {IArrayLike<VALUE>} arr The array to be searched.
 * @param {TARGET} target The sought value.
 * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison
 *     function by which the array is ordered. Should take 2 arguments to
 *     compare, and return a negative number, zero, or a positive number
 *     depending on whether the first argument is less than, equal to, or
 *     greater than the second.
 * @return {number} Lowest index of the target value if found, otherwise
 *     (-(insertion point) - 1). The insertion point is where the value should
 *     be inserted into arr to preserve the sorted property.  Return value >= 0
 *     iff target is found.
 * @template TARGET, VALUE
 */
goog.array.binarySearch = function(arr, target, opt_compareFn) {
  return goog.array.binarySearch_(
      arr, opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
      target);
};


/**
 * Selects an index in the specified array using the binary search algorithm.
 * The evaluator receives an element and determines whether the desired index
 * is before, at, or after it.  The evaluator must be consistent (formally,
 * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
 * must be monotonically non-increasing).
 *
 * Runtime: O(log n)
 *
 * @param {IArrayLike<VALUE>} arr The array to be searched.
 * @param {function(this:THIS, VALUE, number, ?): number} evaluator
 *     Evaluator function that receives 3 arguments (the element, the index and
 *     the array). Should return a negative number, zero, or a positive number
 *     depending on whether the desired index is before, at, or after the
 *     element passed to it.
 * @param {THIS=} opt_obj The object to be used as the value of 'this'
 *     within evaluator.
 * @return {number} Index of the leftmost element matched by the evaluator, if
 *     such exists; otherwise (-(insertion point) - 1). The insertion point is
 *     the index of the first element for which the evaluator returns negative,
 *     or arr.length if no such element exists. The return value is non-negative
 *     iff a match is found.
 * @template THIS, VALUE
 */
goog.array.binarySelect = function(arr, evaluator, opt_obj) {
  return goog.array.binarySearch_(
      arr, evaluator, true /* isEvaluator */, undefined /* opt_target */,
      opt_obj);
};


/**
 * Implementation of a binary search algorithm which knows how to use both
 * comparison functions and evaluators. If an evaluator is provided, will call
 * the evaluator with the given optional data object, conforming to the
 * interface defined in binarySelect. Otherwise, if a comparison function is
 * provided, will call the comparison function against the given data object.
 *
 * This implementation purposefully does not use goog.bind or goog.partial for
 * performance reasons.
 *
 * Runtime: O(log n)
 *
 * @param {IArrayLike<?>} arr The array to be searched.
 * @param {function(?, ?, ?): number | function(?, ?): number} compareFn
 *     Either an evaluator or a comparison function, as defined by binarySearch
 *     and binarySelect above.
 * @param {boolean} isEvaluator Whether the function is an evaluator or a
 *     comparison function.
 * @param {?=} opt_target If the function is a comparison function, then
 *     this is the target to binary search for.
 * @param {Object=} opt_selfObj If the function is an evaluator, this is an
 *     optional this object for the evaluator.
 * @return {number} Lowest index of the target value if found, otherwise
 *     (-(insertion point) - 1). The insertion point is where the value should
 *     be inserted into arr to preserve the sorted property.  Return value >= 0
 *     iff target is found.
 * @private
 */
goog.array.binarySearch_ = function(
    arr, compareFn, isEvaluator, opt_target, opt_selfObj) {
  var left = 0;            // inclusive
  var right = arr.length;  // exclusive
  var found;
  while (left < right) {
    var middle = (left + right) >> 1;
    var compareResult;
    if (isEvaluator) {
      compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
    } else {
      // NOTE(dimvar): To avoid this cast, we'd have to use function overloading
      // for the type of binarySearch_, which the type system can't express yet.
      compareResult = /** @type {function(?, ?): number} */ (compareFn)(
          opt_target, arr[middle]);
    }
    if (compareResult > 0) {
      left = middle + 1;
    } else {
      right = middle;
      // We are looking for the lowest index so we can't return immediately.
      found = !compareResult;
    }
  }
  // left is the index if found, or the insertion point otherwise.
  // ~left is a shorthand for -left - 1.
  return found ? left : ~left;
};


/**
 * Sorts the specified array into ascending order.  If no opt_compareFn is
 * specified, elements are compared using
 * <code>goog.array.defaultCompare</code>, which compares the elements using
 * the built in < and > operators.  This will produce the expected behavior
 * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
 * but will give unpredictable results for heterogeneous lists of strings and
 * numbers with different numbers of digits.
 *
 * This sort is not guaranteed to be stable.
 *
 * Runtime: Same as <code>Array.prototype.sort</code>
 *
 * @param {Array<T>} arr The array to be sorted.
 * @param {?function(T,T):number=} opt_compareFn Optional comparison
 *     function by which the
 *     array is to be ordered. Should take 2 arguments to compare, and return a
 *     negative number, zero, or a positive number depending on whether the
 *     first argument is less than, equal to, or greater than the second.
 * @template T
 */
goog.array.sort = function(arr, opt_compareFn) {
  // TODO(arv): Update type annotation since null is not accepted.
  arr.sort(opt_compareFn || goog.array.defaultCompare);
};


/**
 * Sorts the specified array into ascending order in a stable way.  If no
 * opt_compareFn is specified, elements are compared using
 * <code>goog.array.defaultCompare</code>, which compares the elements using
 * the built in < and > operators.  This will produce the expected behavior
 * for homogeneous arrays of String(s) and Number(s).
 *
 * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
 * O(n) overhead of copying the array twice.
 *
 * @param {Array<T>} arr The array to be sorted.
 * @param {?function(T, T): number=} opt_compareFn Optional comparison function
 *     by which the array is to be ordered. Should take 2 arguments to compare,
 *     and return a negative number, zero, or a positive number depending on
 *     whether the first argument is less than, equal to, or greater than the
 *     second.
 * @template T
 */
goog.array.stableSort = function(arr, opt_compareFn) {
  var compArr = new Array(arr.length);
  for (var i = 0; i < arr.length; i++) {
    compArr[i] = {index: i, value: arr[i]};
  }
  var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
  function stableCompareFn(obj1, obj2) {
    return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
  }
  goog.array.sort(compArr, stableCompareFn);
  for (var i = 0; i < arr.length; i++) {
    arr[i] = compArr[i].value;
  }
};


/**
 * Sort the specified array into ascending order based on item keys
 * returned by the specified key function.
 * If no opt_compareFn is specified, the keys are compared in ascending order
 * using <code>goog.array.defaultCompare</code>.
 *
 * Runtime: O(S(f(n)), where S is runtime of <code>goog.array.sort</code>
 * and f(n) is runtime of the key function.
 *
 * @param {Array<T>} arr The array to be sorted.
 * @param {function(T): K} keyFn Function taking array element and returning
 *     a key used for sorting this element.
 * @param {?function(K, K): number=} opt_compareFn Optional comparison function
 *     by which the keys are to be ordered. Should take 2 arguments to compare,
 *     and return a negative number, zero, or a positive number depending on
 *     whether the first argument is less than, equal to, or greater than the
 *     second.
 * @template T,K
 */
goog.array.sortByKey = function(arr, keyFn, opt_compareFn) {
  var keyCompareFn = opt_compareFn || goog.array.defaultCompare;
  goog.array.sort(
      arr, function(a, b) { return keyCompareFn(keyFn(a), keyFn(b)); });
};


/**
 * Sorts an array of objects by the specified object key and compare
 * function. If no compare function is provided, the key values are
 * compared in ascending order using <code>goog.array.defaultCompare</code>.
 * This won't work for keys that get renamed by the compiler. So use
 * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
 * @param {Array<Object>} arr An array of objects to sort.
 * @param {string} key The object key to sort by.
 * @param {Function=} opt_compareFn The function to use to compare key
 *     values.
 */
goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
  goog.array.sortByKey(arr, function(obj) { return obj[key]; }, opt_compareFn);
};


/**
 * Tells if the array is sorted.
 * @param {!Array<T>} arr The array.
 * @param {?function(T,T):number=} opt_compareFn Function to compare the
 *     array elements.
 *     Should take 2 arguments to compare, and return a negative number, zero,
 *     or a positive number depending on whether the first argument is less
 *     than, equal to, or greater than the second.
 * @param {boolean=} opt_strict If true no equal elements are allowed.
 * @return {boolean} Whether the array is sorted.
 * @template T
 */
goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
  var compare = opt_compareFn || goog.array.defaultCompare;
  for (var i = 1; i < arr.length; i++) {
    var compareResult = compare(arr[i - 1], arr[i]);
    if (compareResult > 0 || compareResult == 0 && opt_strict) {
      return false;
    }
  }
  return true;
};


/**
 * Compares two arrays for equality. Two arrays are considered equal if they
 * have the same length and their corresponding elements are equal according to
 * the comparison function.
 *
 * @param {IArrayLike<?>} arr1 The first array to compare.
 * @param {IArrayLike<?>} arr2 The second array to compare.
 * @param {Function=} opt_equalsFn Optional comparison function.
 *     Should take 2 arguments to compare, and return true if the arguments
 *     are equal. Defaults to {@link goog.array.defaultCompareEquality} which
 *     compares the elements using the built-in '===' operator.
 * @return {boolean} Whether the two arrays are equal.
 */
goog.array.equals = function(arr1, arr2, opt_equalsFn) {
  if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
      arr1.length != arr2.length) {
    return false;
  }
  var l = arr1.length;
  var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
  for (var i = 0; i < l; i++) {
    if (!equalsFn(arr1[i], arr2[i])) {
      return false;
    }
  }
  return true;
};


/**
 * 3-way array compare function.
 * @param {!IArrayLike<VALUE>} arr1 The first array to
 *     compare.
 * @param {!IArrayLike<VALUE>} arr2 The second array to
 *     compare.
 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
 *     function by which the array is to be ordered. Should take 2 arguments to
 *     compare, and return a negative number, zero, or a positive number
 *     depending on whether the first argument is less than, equal to, or
 *     greater than the second.
 * @return {number} Negative number, zero, or a positive number depending on
 *     whether the first argument is less than, equal to, or greater than the
 *     second.
 * @template VALUE
 */
goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
  var compare = opt_compareFn || goog.array.defaultCompare;
  var l = Math.min(arr1.length, arr2.length);
  for (var i = 0; i < l; i++) {
    var result = compare(arr1[i], arr2[i]);
    if (result != 0) {
      return result;
    }
  }
  return goog.array.defaultCompare(arr1.length, arr2.length);
};


/**
 * Compares its two arguments for order, using the built in < and >
 * operators.
 * @param {VALUE} a The first object to be compared.
 * @param {VALUE} b The second object to be compared.
 * @return {number} A negative number, zero, or a positive number as the first
 *     argument is less than, equal to, or greater than the second,
 *     respectively.
 * @template VALUE
 */
goog.array.defaultCompare = function(a, b) {
  return a > b ? 1 : a < b ? -1 : 0;
};


/**
 * Compares its two arguments for inverse order, using the built in < and >
 * operators.
 * @param {VALUE} a The first object to be compared.
 * @param {VALUE} b The second object to be compared.
 * @return {number} A negative number, zero, or a positive number as the first
 *     argument is greater than, equal to, or less than the second,
 *     respectively.
 * @template VALUE
 */
goog.array.inverseDefaultCompare = function(a, b) {
  return -goog.array.defaultCompare(a, b);
};


/**
 * Compares its two arguments for equality, using the built in === operator.
 * @param {*} a The first object to compare.
 * @param {*} b The second object to compare.
 * @return {boolean} True if the two arguments are equal, false otherwise.
 */
goog.array.defaultCompareEquality = function(a, b) {
  return a === b;
};


/**
 * Inserts a value into a sorted array. The array is not modified if the
 * value is already present.
 * @param {IArrayLike<VALUE>} array The array to modify.
 * @param {VALUE} value The object to insert.
 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
 *     function by which the array is ordered. Should take 2 arguments to
 *     compare, and return a negative number, zero, or a positive number
 *     depending on whether the first argument is less than, equal to, or
 *     greater than the second.
 * @return {boolean} True if an element was inserted.
 * @template VALUE
 */
goog.array.binaryInsert = function(array, value, opt_compareFn) {
  var index = goog.array.binarySearch(array, value, opt_compareFn);
  if (index < 0) {
    goog.array.insertAt(array, value, -(index + 1));
    return true;
  }
  return false;
};


/**
 * Removes a value from a sorted array.
 * @param {!IArrayLike<VALUE>} array The array to modify.
 * @param {VALUE} value The object to remove.
 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
 *     function by which the array is ordered. Should take 2 arguments to
 *     compare, and return a negative number, zero, or a positive number
 *     depending on whether the first argument is less than, equal to, or
 *     greater than the second.
 * @return {boolean} True if an element was removed.
 * @template VALUE
 */
goog.array.binaryRemove = function(array, value, opt_compareFn) {
  var index = goog.array.binarySearch(array, value, opt_compareFn);
  return (index >= 0) ? goog.array.removeAt(array, index) : false;
};


/**
 * Splits an array into disjoint buckets according to a splitting function.
 * @param {Array<T>} array The array.
 * @param {function(this:S, T,number,Array<T>):?} sorter Function to call for
 *     every element.  This takes 3 arguments (the element, the index and the
 *     array) and must return a valid object key (a string, number, etc), or
 *     undefined, if that object should not be placed in a bucket.
 * @param {S=} opt_obj The object to be used as the value of 'this' within
 *     sorter.
 * @return {!Object} An object, with keys being all of the unique return values
 *     of sorter, and values being arrays containing the items for
 *     which the splitter returned that key.
 * @template T,S
 */
goog.array.bucket = function(array, sorter, opt_obj) {
  var buckets = {};

  for (var i = 0; i < array.length; i++) {
    var value = array[i];
    var key = sorter.call(/** @type {?} */ (opt_obj), value, i, array);
    if (goog.isDef(key)) {
      // Push the value to the right bucket, creating it if necessary.
      var bucket = buckets[key] || (buckets[key] = []);
      bucket.push(value);
    }
  }

  return buckets;
};


/**
 * Creates a new object built from the provided array and the key-generation
 * function.
 * @param {IArrayLike<T>} arr Array or array like object over
 *     which to iterate whose elements will be the values in the new object.
 * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
 *     call for every element. This function takes 3 arguments (the element, the
 *     index and the array) and should return a string that will be used as the
 *     key for the element in the new object. If the function returns the same
 *     key for more than one element, the value for that key is
 *     implementation-defined.
 * @param {S=} opt_obj The object to be used as the value of 'this'
 *     within keyFunc.
 * @return {!Object<T>} The new object.
 * @template T,S
 */
goog.array.toObject = function(arr, keyFunc, opt_obj) {
  var ret = {};
  goog.array.forEach(arr, function(element, index) {
    ret[keyFunc.call(/** @type {?} */ (opt_obj), element, index, arr)] =
        element;
  });
  return ret;
};


/**
 * Creates a range of numbers in an arithmetic progression.
 *
 * Range takes 1, 2, or 3 arguments:
 * <pre>
 * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
 * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
 * range(-2, -5, -1) produces [-2, -3, -4]
 * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
 * </pre>
 *
 * @param {number} startOrEnd The starting value of the range if an end argument
 *     is provided. Otherwise, the start value is 0, and this is the end value.
 * @param {number=} opt_end The optional end value of the range.
 * @param {number=} opt_step The step size between range values. Defaults to 1
 *     if opt_step is undefined or 0.
 * @return {!Array<number>} An array of numbers for the requested range. May be
 *     an empty array if adding the step would not converge toward the end
 *     value.
 */
goog.array.range = function(startOrEnd, opt_end, opt_step) {
  var array = [];
  var start = 0;
  var end = startOrEnd;
  var step = opt_step || 1;
  if (opt_end !== undefined) {
    start = startOrEnd;
    end = opt_end;
  }

  if (step * (end - start) < 0) {
    // Sign mismatch: start + step will never reach the end value.
    return [];
  }

  if (step > 0) {
    for (var i = start; i < end; i += step) {
      array.push(i);
    }
  } else {
    for (var i = start; i > end; i += step) {
      array.push(i);
    }
  }
  return array;
};


/**
 * Returns an array consisting of the given value repeated N times.
 *
 * @param {VALUE} value The value to repeat.
 * @param {number} n The repeat count.
 * @return {!Array<VALUE>} An array with the repeated value.
 * @template VALUE
 */
goog.array.repeat = function(value, n) {
  var array = [];
  for (var i = 0; i < n; i++) {
    array[i] = value;
  }
  return array;
};


/**
 * Returns an array consisting of every argument with all arrays
 * expanded in-place recursively.
 *
 * @param {...*} var_args The values to flatten.
 * @return {!Array<?>} An array containing the flattened values.
 */
goog.array.flatten = function(var_args) {
  var CHUNK_SIZE = 8192;

  var result = [];
  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (goog.isArray(element)) {
      for (var c = 0; c < element.length; c += CHUNK_SIZE) {
        var chunk = goog.array.slice(element, c, c + CHUNK_SIZE);
        var recurseResult = goog.array.flatten.apply(null, chunk);
        for (var r = 0; r < recurseResult.length; r++) {
          result.push(recurseResult[r]);
        }
      }
    } else {
      result.push(element);
    }
  }
  return result;
};


/**
 * Rotates an array in-place. After calling this method, the element at
 * index i will be the element previously at index (i - n) %
 * array.length, for all values of i between 0 and array.length - 1,
 * inclusive.
 *
 * For example, suppose list comprises [t, a, n, k, s]. After invoking
 * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
 *
 * @param {!Array<T>} array The array to rotate.
 * @param {number} n The amount to rotate.
 * @return {!Array<T>} The array.
 * @template T
 */
goog.array.rotate = function(array, n) {
  goog.asserts.assert(array.length != null);

  if (array.length) {
    n %= array.length;
    if (n > 0) {
      Array.prototype.unshift.apply(array, array.splice(-n, n));
    } else if (n < 0) {
      Array.prototype.push.apply(array, array.splice(0, -n));
    }
  }
  return array;
};


/**
 * Moves one item of an array to a new position keeping the order of the rest
 * of the items. Example use case: keeping a list of JavaScript objects
 * synchronized with the corresponding list of DOM elements after one of the
 * elements has been dragged to a new position.
 * @param {!IArrayLike<?>} arr The array to modify.
 * @param {number} fromIndex Index of the item to move between 0 and
 *     {@code arr.length - 1}.
 * @param {number} toIndex Target index between 0 and {@code arr.length - 1}.
 */
goog.array.moveItem = function(arr, fromIndex, toIndex) {
  goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length);
  goog.asserts.assert(toIndex >= 0 && toIndex < arr.length);
  // Remove 1 item at fromIndex.
  var removedItems = Array.prototype.splice.call(arr, fromIndex, 1);
  // Insert the removed item at toIndex.
  Array.prototype.splice.call(arr, toIndex, 0, removedItems[0]);
  // We don't use goog.array.insertAt and goog.array.removeAt, because they're
  // significantly slower than splice.
};


/**
 * Creates a new array for which the element at position i is an array of the
 * ith element of the provided arrays.  The returned array will only be as long
 * as the shortest array provided; additional values are ignored.  For example,
 * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
 *
 * This is similar to the zip() function in Python.  See {@link
 * http://docs.python.org/library/functions.html#zip}
 *
 * @param {...!IArrayLike<?>} var_args Arrays to be combined.
 * @return {!Array<!Array<?>>} A new array of arrays created from
 *     provided arrays.
 */
goog.array.zip = function(var_args) {
  if (!arguments.length) {
    return [];
  }
  var result = [];
  var minLen = arguments[0].length;
  for (var i = 1; i < arguments.length; i++) {
    if (arguments[i].length < minLen) {
      minLen = arguments[i].length;
    }
  }
  for (var i = 0; i < minLen; i++) {
    var value = [];
    for (var j = 0; j < arguments.length; j++) {
      value.push(arguments[j][i]);
    }
    result.push(value);
  }
  return result;
};


/**
 * Shuffles the values in the specified array using the Fisher-Yates in-place
 * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
 * and so resets the state of that random number generator. Similarly, may reset
 * the state of the any other specified random number generator.
 *
 * Runtime: O(n)
 *
 * @param {!Array<?>} arr The array to be shuffled.
 * @param {function():number=} opt_randFn Optional random function to use for
 *     shuffling.
 *     Takes no arguments, and returns a random number on the interval [0, 1).
 *     Defaults to Math.random() using JavaScript's built-in Math library.
 */
goog.array.shuffle = function(arr, opt_randFn) {
  var randFn = opt_randFn || Math.random;

  for (var i = arr.length - 1; i > 0; i--) {
    // Choose a random array index in [0, i] (inclusive with i).
    var j = Math.floor(randFn() * (i + 1));

    var tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
  }
};


/**
 * Returns a new array of elements from arr, based on the indexes of elements
 * provided by index_arr. For example, the result of index copying
 * ['a', 'b', 'c'] with index_arr [1,0,0,2] is ['b', 'a', 'a', 'c'].
 *
 * @param {!Array<T>} arr The array to get a indexed copy from.
 * @param {!Array<number>} index_arr An array of indexes to get from arr.
 * @return {!Array<T>} A new array of elements from arr in index_arr order.
 * @template T
 */
goog.array.copyByIndex = function(arr, index_arr) {
  var result = [];
  goog.array.forEach(index_arr, function(index) { result.push(arr[index]); });
  return result;
};


/**
 * Maps each element of the input array into zero or more elements of the output
 * array.
 *
 * @param {!IArrayLike<VALUE>|string} arr Array or array like object
 *     over which to iterate.
 * @param {function(this:THIS, VALUE, number, ?): !Array<RESULT>} f The function
 *     to call for every element. This function takes 3 arguments (the element,
 *     the index and the array) and should return an array. The result will be
 *     used to extend a new array.
 * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
 * @return {!Array<RESULT>} a new array with the concatenation of all arrays
 *     returned from f.
 * @template THIS, VALUE, RESULT
 */
goog.array.concatMap = function(arr, f, opt_obj) {
  return goog.array.concat.apply([], goog.array.map(arr, f, opt_obj));
};

// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Utilities used by goog.labs.userAgent tools. These functions
 * should not be used outside of goog.labs.userAgent.*.
 *
 *
 * @author nnaze@google.com (Nathan Naze)
 */

goog.provide('goog.labs.userAgent.util');

goog.require('goog.string');


/**
 * Gets the native userAgent string from navigator if it exists.
 * If navigator or navigator.userAgent string is missing, returns an empty
 * string.
 * @return {string}
 * @private
 */
goog.labs.userAgent.util.getNativeUserAgentString_ = function() {
  var navigator = goog.labs.userAgent.util.getNavigator_();
  if (navigator) {
    var userAgent = navigator.userAgent;
    if (userAgent) {
      return userAgent;
    }
  }
  return '';
};


/**
 * Getter for the native navigator.
 * This is a separate function so it can be stubbed out in testing.
 * @return {Navigator}
 * @private
 */
goog.labs.userAgent.util.getNavigator_ = function() {
  return goog.global.navigator;
};


/**
 * A possible override for applications which wish to not check
 * navigator.userAgent but use a specified value for detection instead.
 * @private {string}
 */
goog.labs.userAgent.util.userAgent_ =
    goog.labs.userAgent.util.getNativeUserAgentString_();


/**
 * Applications may override browser detection on the built in
 * navigator.userAgent object by setting this string. Set to null to use the
 * browser object instead.
 * @param {?string=} opt_userAgent The User-Agent override.
 */
goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
  goog.labs.userAgent.util.userAgent_ =
      opt_userAgent || goog.labs.userAgent.util.getNativeUserAgentString_();
};


/**
 * @return {string} The user agent string.
 */
goog.labs.userAgent.util.getUserAgent = function() {
  return goog.labs.userAgent.util.userAgent_;
};


/**
 * @param {string} str
 * @return {boolean} Whether the user agent contains the given string, ignoring
 *     case.
 */
goog.labs.userAgent.util.matchUserAgent = function(str) {
  var userAgent = goog.labs.userAgent.util.getUserAgent();
  return goog.string.contains(userAgent, str);
};


/**
 * @param {string} str
 * @return {boolean} Whether the user agent contains the given string.
 */
goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
  var userAgent = goog.labs.userAgent.util.getUserAgent();
  return goog.string.caseInsensitiveContains(userAgent, str);
};


/**
 * Parses the user agent into tuples for each section.
 * @param {string} userAgent
 * @return {!Array<!Array<string>>} Tuples of key, version, and the contents
 *     of the parenthetical.
 */
goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
  // Matches each section of a user agent string.
  // Example UA:
  // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
  // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
  // This has three version tuples: Mozilla, AppleWebKit, and Mobile.

  var versionRegExp = new RegExp(
      // Key. Note that a key may have a space.
      // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
      '(\\w[\\w ]+)' +

          '/' +                // slash
          '([^\\s]+)' +        // version (i.e. '5.0b')
          '\\s*' +             // whitespace
          '(?:\\((.*?)\\))?',  // parenthetical info. parentheses not matched.
      'g');

  var data = [];
  var match;

  // Iterate and collect the version tuples.  Each iteration will be the
  // next regex match.
  while (match = versionRegExp.exec(userAgent)) {
    data.push([
      match[1],  // key
      match[2],  // value
      // || undefined as this is not undefined in IE7 and IE8
      match[3] || undefined  // info
    ]);
  }

  return data;
};

// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Utilities for manipulating objects/maps/hashes.
 * @author arv@google.com (Erik Arvidsson)
 */

goog.provide('goog.object');


/**
 * Whether two values are not observably distinguishable. This
 * correctly detects that 0 is not the same as -0 and two NaNs are
 * practically equivalent.
 *
 * The implementation is as suggested by harmony:egal proposal.
 *
 * @param {*} v The first value to compare.
 * @param {*} v2 The second value to compare.
 * @return {boolean} Whether two values are not observably distinguishable.
 * @see http://wiki.ecmascript.org/doku.php?id=harmony:egal
 */
goog.object.is = function(v, v2) {
  if (v === v2) {
    // 0 === -0, but they are not identical.
    // We need the cast because the compiler requires that v2 is a
    // number (although 1/v2 works with non-number). We cast to ? to
    // stop the compiler from type-checking this statement.
    return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2);
  }

  // NaN is non-reflexive: NaN !== NaN, although they are identical.
  return v !== v && v2 !== v2;
};


/**
 * Calls a function for each element in an object/map/hash.
 *
 * @param {Object<K,V>} obj The object over which to iterate.
 * @param {function(this:T,V,?,Object<K,V>):?} f The function to call
 *     for every element. This function takes 3 arguments (the value, the
 *     key and the object) and the return value is ignored.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @template T,K,V
 */
goog.object.forEach = function(obj, f, opt_obj) {
  for (var key in obj) {
    f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
  }
};


/**
 * Calls a function for each element in an object/map/hash. If that call returns
 * true, adds the element to a new object.
 *
 * @param {Object<K,V>} obj The object over which to iterate.
 * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
 *     for every element. This
 *     function takes 3 arguments (the value, the key and the object)
 *     and should return a boolean. If the return value is true the
 *     element is added to the result object. If it is false the
 *     element is not included.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {!Object<K,V>} a new object in which only elements that passed the
 *     test are present.
 * @template T,K,V
 */
goog.object.filter = function(obj, f, opt_obj) {
  var res = {};
  for (var key in obj) {
    if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
      res[key] = obj[key];
    }
  }
  return res;
};


/**
 * For every element in an object/map/hash calls a function and inserts the
 * result into a new object.
 *
 * @param {Object<K,V>} obj The object over which to iterate.
 * @param {function(this:T,V,?,Object<K,V>):R} f The function to call
 *     for every element. This function
 *     takes 3 arguments (the value, the key and the object)
 *     and should return something. The result will be inserted
 *     into a new object.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {!Object<K,R>} a new object with the results from f.
 * @template T,K,V,R
 */
goog.object.map = function(obj, f, opt_obj) {
  var res = {};
  for (var key in obj) {
    res[key] = f.call(/** @type {?} */ (opt_obj), obj[key], key, obj);
  }
  return res;
};


/**
 * Calls a function for each element in an object/map/hash. If any
 * call returns true, returns true (without checking the rest). If
 * all calls return false, returns false.
 *
 * @param {Object<K,V>} obj The object to check.
 * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
 *     call for every element. This function
 *     takes 3 arguments (the value, the key and the object) and should
 *     return a boolean.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {boolean} true if any element passes the test.
 * @template T,K,V
 */
goog.object.some = function(obj, f, opt_obj) {
  for (var key in obj) {
    if (f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
      return true;
    }
  }
  return false;
};


/**
 * Calls a function for each element in an object/map/hash. If
 * all calls return true, returns true. If any call returns false, returns
 * false at this point and does not continue to check the remaining elements.
 *
 * @param {Object<K,V>} obj The object to check.
 * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
 *     call for every element. This function
 *     takes 3 arguments (the value, the key and the object) and should
 *     return a boolean.
 * @param {T=} opt_obj This is used as the 'this' object within f.
 * @return {boolean} false if any element fails the test.
 * @template T,K,V
 */
goog.object.every = function(obj, f, opt_obj) {
  for (var key in obj) {
    if (!f.call(/** @type {?} */ (opt_obj), obj[key], key, obj)) {
      return false;
    }
  }
  return true;
};


/**
 * Returns the number of key-value pairs in the object map.
 *
 * @param {Object} obj The object for which to get the number of key-value
 *     pairs.
 * @return {number} The number of key-value pairs in the object map.
 */
goog.object.getCount = function(obj) {
  var rv = 0;
  for (var key in obj) {
    rv++;
  }
  return rv;
};


/**
 * Returns one key from the object map, if any exists.
 * For map literals the returned key will be the first one in most of the
 * browsers (a know exception is Konqueror).
 *
 * @param {Object} obj The object to pick a key from.
 * @return {string|undefined} The key or undefined if the object is empty.
 */
goog.object.getAnyKey = function(obj) {
  for (var key in obj) {
    return key;
  }
};


/**
 * Returns one value from the object map, if any exists.
 * For map literals the returned value will be the first one in most of the
 * browsers (a know exception is Konqueror).
 *
 * @param {Object<K,V>} obj The object to pick a value from.
 * @return {V|undefined} The value or undefined if the object is empty.
 * @template K,V
 */
goog.object.getAnyValue = function(obj) {
  for (var key in obj) {
    return obj[key];
  }
};


/**
 * Whether the object/hash/map contains the given object as a value.
 * An alias for goog.object.containsValue(obj, val).
 *
 * @param {Object<K,V>} obj The object in which to look for val.
 * @param {V} val The object for which to check.
 * @return {boolean} true if val is present.
 * @template K,V
 */
goog.object.contains = function(obj, val) {
  return goog.object.containsValue(obj, val);
};


/**
 * Returns the values of the object/map/hash.
 *
 * @param {Object<K,V>} obj The object from which to get the values.
 * @return {!Array<V>} The values in the object/map/hash.
 * @template K,V
 */
goog.object.getValues = function(obj) {
  var res = [];
  var i = 0;
  for (var key in obj) {
    res[i++] = obj[key];
  }
  return res;
};


/**
 * Returns the keys of the object/map/hash.
 *
 * @param {Object} obj The object from which to get the keys.
 * @return {!Array<string>} Array of property keys.
 */
goog.object.getKeys = function(obj) {
  var res = [];
  var i = 0;
  for (var key in obj) {
    res[i++] = key;
  }
  return res;
};


/**
 * Get a value from an object multiple levels deep.  This is useful for
 * pulling values from deeply nested objects, such as JSON responses.
 * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
 *
 * @param {!Object} obj An object to get the value from.  Can be array-like.
 * @param {...(string|number|!IArrayLike<number|string>)}
 *     var_args A number of keys
 *     (as strings, or numbers, for array-like objects).  Can also be
 *     specified as a single array of keys.
 * @return {*} The resulting value.  If, at any point, the value for a key
 *     is undefined, returns undefined.
 */
goog.object.getValueByKeys = function(obj, var_args) {
  var isArrayLike = goog.isArrayLike(var_args);
  var keys = isArrayLike ? var_args : arguments;

  // Start with the 2nd parameter for the variable parameters syntax.
  for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
    obj = obj[keys[i]];
    if (!goog.isDef(obj)) {
      break;
    }
  }

  return obj;
};


/**
 * Whether the object/map/hash contains the given key.
 *
 * @param {Object} obj The object in which to look for key.
 * @param {?} key The key for which to check.
 * @return {boolean} true If the map contains the key.
 */
goog.object.containsKey = function(obj, key) {
  return obj !== null && key in obj;
};


/**
 * Whether the object/map/hash contains the given value. This is O(n).
 *
 * @param {Object<K,V>} obj The object in which to look for val.
 * @param {V} val The value for which to check.
 * @return {boolean} true If the map contains the value.
 * @template K,V
 */
goog.object.containsValue = function(obj, val) {
  for (var key in obj) {
    if (obj[key] == val) {
      return true;
    }
  }
  return false;
};


/**
 * Searches an object for an element that satisfies the given condition and
 * returns its key.
 * @param {Object<K,V>} obj The object to search in.
 * @param {function(this:T,V,string,Object<K,V>):boolean} f The
 *      function to call for every element. Takes 3 arguments (the value,
 *     the key and the object) and should return a boolean.
 * @param {T=} opt_this An optional "this" context for the function.
 * @return {string|undefined} The key of an element for which the function
 *     returns true or undefined if no such element is found.
 * @template T,K,V
 */
goog.object.findKey = function(obj, f, opt_this) {
  for (var key in obj) {
    if (f.call(/** @type {?} */ (opt_this), obj[key], key, obj)) {
      return key;
    }
  }
  return undefined;
};


/**
 * Searches an object for an element that satisfies the given condition and
 * returns its value.
 * @param {Object<K,V>} obj The object to search in.
 * @param {function(this:T,V,string,Object<K,V>):boolean} f The function
 *     to call for every element. Takes 3 arguments (the value, the key
 *     and the object) and should return a boolean.
 * @param {T=} opt_this An optional "this" context for the function.
 * @return {V} The value of an element for which the function returns true or
 *     undefined if no such element is found.
 * @template T,K,V
 */
goog.object.findValue = function(obj, f, opt_this) {
  var key = goog.object.findKey(obj, f, opt_this);
  return key && obj[key];
};


/**
 * Whether the object/map/hash is empty.
 *
 * @param {Object} obj The object to test.
 * @return {boolean} true if obj is empty.
 */
goog.object.isEmpty = function(obj) {
  for (var key in obj) {
    return false;
  }
  return true;
};


/**
 * Removes all key value pairs from the object/map/hash.
 *
 * @param {Object} obj The object to clear.
 */
goog.object.clear = function(obj) {
  for (var i in obj) {
    delete obj[i];
  }
};


/**
 * Removes a key-value pair based on the key.
 *
 * @param {Object} obj The object from which to remove the key.
 * @param {?} key The key to remove.
 * @return {boolean} Whether an element was removed.
 */
goog.object.remove = function(obj, key) {
  var rv;
  if (rv = key in /** @type {!Object} */ (obj)) {
    delete obj[key];
  }
  return rv;
};


/**
 * Adds a key-value pair to the object. Throws an exception if the key is
 * already in use. Use set if you want to change an existing pair.
 *
 * @param {Object<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {V} val The value to add.
 * @template K,V
 */
goog.object.add = function(obj, key, val) {
  if (obj !== null && key in obj) {
    throw Error('The object already contains the key "' + key + '"');
  }
  goog.object.set(obj, key, val);
};


/**
 * Returns the value for the given key.
 *
 * @param {Object<K,V>} obj The object from which to get the value.
 * @param {string} key The key for which to get the value.
 * @param {R=} opt_val The value to return if no item is found for the given
 *     key (default is undefined).
 * @return {V|R|undefined} The value for the given key.
 * @template K,V,R
 */
goog.object.get = function(obj, key, opt_val) {
  if (obj !== null && key in obj) {
    return obj[key];
  }
  return opt_val;
};


/**
 * Adds a key-value pair to the object/map/hash.
 *
 * @param {Object<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {V} value The value to add.
 * @template K,V
 */
goog.object.set = function(obj, key, value) {
  obj[key] = value;
};


/**
 * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
 *
 * @param {Object<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {V} value The value to add if the key wasn't present.
 * @return {V} The value of the entry at the end of the function.
 * @template K,V
 */
goog.object.setIfUndefined = function(obj, key, value) {
  return key in /** @type {!Object} */ (obj) ? obj[key] : (obj[key] = value);
};


/**
 * Sets a key and value to an object if the key is not set. The value will be
 * the return value of the given function. If the key already exists, the
 * object will not be changed and the function will not be called (the function
 * will be lazily evaluated -- only called if necessary).
 *
 * This function is particularly useful for use with a map used a as a cache.
 *
 * @param {!Object<K,V>} obj The object to which to add the key-value pair.
 * @param {string} key The key to add.
 * @param {function():V} f The value to add if the key wasn't present.
 * @return {V} The value of the entry at the end of the function.
 * @template K,V
 */
goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
  if (key in obj) {
    return obj[key];
  }

  var val = f();
  obj[key] = val;
  return val;
};


/**
 * Compares two objects for equality using === on the values.
 *
 * @param {!Object<K,V>} a
 * @param {!Object<K,V>} b
 * @return {boolean}
 * @template K,V
 */
goog.object.equals = function(a, b) {
  for (var k in a) {
    if (!(k in b) || a[k] !== b[k]) {
      return false;
    }
  }
  for (var k in b) {
    if (!(k in a)) {
      return false;
    }
  }
  return true;
};


/**
 * Returns a shallow clone of the object.
 *
 * @param {Object<K,V>} obj Object to clone.
 * @return {!Object<K,V>} Clone of the input object.
 * @template K,V
 */
goog.object.clone = function(obj) {
  // We cannot use the prototype trick because a lot of methods depend on where
  // the actual key is set.

  var res = {};
  for (var key in obj) {
    res[key] = obj[key];
  }
  return res;
  // We could also use goog.mixin but I wanted this to be independent from that.
};


/**
 * Clones a value. The input may be an Object, Array, or basic type. Objects and
 * arrays will be cloned recursively.
 *
 * WARNINGS:
 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
 * that refer to themselves will cause infinite recursion.
 *
 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
 * copies UIDs created by <code>getUid</code> into cloned results.
 *
 * @param {*} obj The value to clone.
 * @return {*} A clone of the input value.
 */
goog.object.unsafeClone = function(obj) {
  var type = goog.typeOf(obj);
  if (type == 'object' || type == 'array') {
    if (goog.isFunction(obj.clone)) {
      return obj.clone();
    }
    var clone = type == 'array' ? [] : {};
    for (var key in obj) {
      clone[key] = goog.object.unsafeClone(obj[key]);
    }
    return clone;
  }

  return obj;
};


/**
 * Returns a new object in which all the keys and values are interchanged
 * (keys become values and values become keys). If multiple keys map to the
 * same value, the chosen transposed value is implementation-dependent.
 *
 * @param {Object} obj The object to transpose.
 * @return {!Object} The transposed object.
 */
goog.object.transpose = function(obj) {
  var transposed = {};
  for (var key in obj) {
    transposed[obj[key]] = key;
  }
  return transposed;
};


/**
 * The names of the fields that are defined on Object.prototype.
 * @type {Array<string>}
 * @private
 */
goog.object.PROTOTYPE_FIELDS_ = [
  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
  'toLocaleString', 'toString', 'valueOf'
];


/**
 * Extends an object with another object.
 * This operates 'in-place'; it does not create a new Object.
 *
 * Example:
 * var o = {};
 * goog.object.extend(o, {a: 0, b: 1});
 * o; // {a: 0, b: 1}
 * goog.object.extend(o, {b: 2, c: 3});
 * o; // {a: 0, b: 2, c: 3}
 *
 * @param {Object} target The object to modify. Existing properties will be
 *     overwritten if they are also present in one of the objects in
 *     {@code var_args}.
 * @param {...Object} var_args The objects from which values will be copied.
 */
goog.object.extend = function(target, var_args) {
  var key, source;
  for (var i = 1; i < arguments.length; i++) {
    source = arguments[i];
    for (key in source) {
      target[key] = source[key];
    }

    // For IE the for-in-loop does not contain any properties that are not
    // enumerable on the prototype object (for example isPrototypeOf from
    // Object.prototype) and it will also not include 'replace' on objects that
    // extend String and change 'replace' (not that it is common for anyone to
    // extend anything except Object).

    for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
      key = goog.object.PROTOTYPE_FIELDS_[j];
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }
};


/**
 * Creates a new object built from the key-value pairs provided as arguments.
 * @param {...*} var_args If only one argument is provided and it is an array
 *     then this is used as the arguments,  otherwise even arguments are used as
 *     the property names and odd arguments are used as the property values.
 * @return {!Object} The new object.
 * @throws {Error} If there are uneven number of arguments or there is only one
 *     non array argument.
 */
goog.object.create = function(var_args) {
  var argLength = arguments.length;
  if (argLength == 1 && goog.isArray(arguments[0])) {
    return goog.object.create.apply(null, arguments[0]);
  }

  if (argLength % 2) {
    throw Error('Uneven number of arguments');
  }

  var rv = {};
  for (var i = 0; i < argLength; i += 2) {
    rv[arguments[i]] = arguments[i + 1];
  }
  return rv;
};


/**
 * Creates a new object where the property names come from the arguments but
 * the value is always set to true
 * @param {...*} var_args If only one argument is provided and it is an array
 *     then this is used as the arguments,  otherwise the arguments are used
 *     as the property names.
 * @return {!Object} The new object.
 */
goog.object.createSet = function(var_args) {
  var argLength = arguments.length;
  if (argLength == 1 && goog.isArray(arguments[0])) {
    return goog.object.createSet.apply(null, arguments[0]);
  }

  var rv = {};
  for (var i = 0; i < argLength; i++) {
    rv[arguments[i]] = true;
  }
  return rv;
};


/**
 * Creates an immutable view of the underlying object, if the browser
 * supports immutable objects.
 *
 * In default mode, writes to this view will fail silently. In strict mode,
 * they will throw an error.
 *
 * @param {!Object<K,V>} obj An object.
 * @return {!Object<K,V>} An immutable view of that object, or the
 *     original object if this browser does not support immutables.
 * @template K,V
 */
goog.object.createImmutableView = function(obj) {
  var result = obj;
  if (Object.isFrozen && !Object.isFrozen(obj)) {
    result = Object.create(obj);
    Object.freeze(result);
  }
  return result;
};


/**
 * @param {!Object} obj An object.
 * @return {boolean} Whether this is an immutable view of the object.
 */
goog.object.isImmutableView = function(obj) {
  return !!Object.isFrozen && Object.isFrozen(obj);
};

// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Closure user agent detection (Browser).
 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
 * For more information on rendering engine, platform, or device see the other
 * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
 * goog.labs.userAgent.device respectively.)
 *
 * @author martone@google.com (Andy Martone)
 */

goog.provide('goog.labs.userAgent.browser');

goog.require('goog.array');
goog.require('goog.labs.userAgent.util');
goog.require('goog.object');
goog.require('goog.string');


// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
// functions.


/**
 * @return {boolean} Whether the user's browser is Opera.  Note: Chromium
 *     based Opera (Opera 15+) is detected as Chrome to avoid unnecessary
 *     special casing.
 * @private
 */
goog.labs.userAgent.browser.matchOpera_ = function() {
  return goog.labs.userAgent.util.matchUserAgent('Opera');
};


/**
 * @return {boolean} Whether the user's browser is IE.
 * @private
 */
goog.labs.userAgent.browser.matchIE_ = function() {
  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
      goog.labs.userAgent.util.matchUserAgent('MSIE');
};


/**
 * @return {boolean} Whether the user's browser is Edge.
 * @private
 */
goog.labs.userAgent.browser.matchEdge_ = function() {
  return goog.labs.userAgent.util.matchUserAgent('Edge');
};


/**
 * @return {boolean} Whether the user's browser is Firefox.
 * @private
 */
goog.labs.userAgent.browser.matchFirefox_ = function() {
  return goog.labs.userAgent.util.matchUserAgent('Firefox');
};


/**
 * @return {boolean} Whether the user's browser is Safari.
 * @private
 */
goog.labs.userAgent.browser.matchSafari_ = function() {
  return goog.labs.userAgent.util.matchUserAgent('Safari') &&
      !(goog.labs.userAgent.browser.matchChrome_() ||
        goog.labs.userAgent.browser.matchCoast_() ||
        goog.labs.userAgent.browser.matchOpera_() ||
        goog.labs.userAgent.browser.matchEdge_() ||
        goog.labs.userAgent.browser.isSilk() ||
        goog.labs.userAgent.util.matchUserAgent('Android'));
};


/**
 * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
 *     iOS browser).
 * @private
 */
goog.labs.userAgent.browser.matchCoast_ = function() {
  return goog.labs.userAgent.util.matchUserAgent('Coast');
};


/**
 * @return {boolean} Whether the user's browser is iOS Webview.
 * @private
 */
goog.labs.userAgent.browser.matchIosWebview_ = function() {
  // iOS Webview does not show up as Chrome or Safari. Also check for Opera's
  // WebKit-based iOS browser, Coast.
  return (goog.labs.userAgent.util.matchUserAgent('iPad') ||
          goog.labs.userAgent.util.matchUserAgent('iPhone')) &&
      !goog.labs.userAgent.browser.matchSafari_() &&
      !goog.labs.userAgent.browser.matchChrome_() &&
      !goog.labs.userAgent.browser.matchCoast_() &&
      goog.labs.userAgent.util.matchUserAgent('AppleWebKit');
};


/**
 * @return {boolean} Whether the user's browser is Chrome.
 * @private
 */
goog.labs.userAgent.browser.matchChrome_ = function() {
  return (goog.labs.userAgent.util.matchUserAgent('Chrome') ||
          goog.labs.userAgent.util.matchUserAgent('CriOS')) &&
      !goog.labs.userAgent.browser.matchEdge_();
};


/**
 * @return {boolean} Whether the user's browser is the Android browser.
 * @private
 */
goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
  // Android can appear in the user agent string for Chrome on Android.
  // This is not the Android standalone browser if it does.
  return goog.labs.userAgent.util.matchUserAgent('Android') &&
      !(goog.labs.userAgent.browser.isChrome() ||
        goog.labs.userAgent.browser.isFirefox() ||
        goog.labs.userAgent.browser.isOpera() ||
        goog.labs.userAgent.browser.isSilk());
};


/**
 * @return {boolean} Whether the user's browser is Opera.
 */
goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;


/**
 * @return {boolean} Whether the user's browser is IE.
 */
goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;


/**
 * @return {boolean} Whether the user's browser is Edge.
 */
goog.labs.userAgent.browser.isEdge = goog.labs.userAgent.browser.matchEdge_;


/**
 * @return {boolean} Whether the user's browser is Firefox.
 */
goog.labs.userAgent.browser.isFirefox =
    goog.labs.userAgent.browser.matchFirefox_;


/**
 * @return {boolean} Whether the user's browser is Safari.
 */
goog.labs.userAgent.browser.isSafari = goog.labs.userAgent.browser.matchSafari_;


/**
 * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
 *     iOS browser).
 */
goog.labs.userAgent.browser.isCoast = goog.labs.userAgent.browser.matchCoast_;


/**
 * @return {boolean} Whether the user's browser is iOS Webview.
 */
goog.labs.userAgent.browser.isIosWebview =
    goog.labs.userAgent.browser.matchIosWebview_;


/**
 * @return {boolean} Whether the user's browser is Chrome.
 */
goog.labs.userAgent.browser.isChrome = goog.labs.userAgent.browser.matchChrome_;


/**
 * @return {boolean} Whether the user's browser is the Android browser.
 */
goog.labs.userAgent.browser.isAndroidBrowser =
    goog.labs.userAgent.browser.matchAndroidBrowser_;


/**
 * For more information, see:
 * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
 * @return {boolean} Whether the user's browser is Silk.
 */
goog.labs.userAgent.browser.isSilk = function() {
  return goog.labs.userAgent.util.matchUserAgent('Silk');
};


/**
 * @return {string} The browser version or empty string if version cannot be
 *     determined. Note that for Internet Explorer, this returns the version of
 *     the browser, not the version of the rendering engine. (IE 8 in
 *     compatibility mode will return 8.0 rather than 7.0. To determine the
 *     rendering engine version, look at document.documentMode instead. See
 *     http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
 *     details.)
 */
goog.labs.userAgent.browser.getVersion = function() {
  var userAgentString = goog.labs.userAgent.util.getUserAgent();
  // Special case IE since IE's version is inside the parenthesis and
  // without the '/'.
  if (goog.labs.userAgent.browser.isIE()) {
    return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
  }

  var versionTuples =
      goog.labs.userAgent.util.extractVersionTuples(userAgentString);

  // Construct a map for easy lookup.
  var versionMap = {};
  goog.array.forEach(versionTuples, function(tuple) {
    // Note that the tuple is of length three, but we only care about the
    // first two.
    var key = tuple[0];
    var value = tuple[1];
    versionMap[key] = value;
  });

  var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap);

  // Gives the value with the first key it finds, otherwise empty string.
  function lookUpValueWithKeys(keys) {
    var key = goog.array.find(keys, versionMapHasKey);
    return versionMap[key] || '';
  }

  // Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
  // See
  // http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
  if (goog.labs.userAgent.browser.isOpera()) {
    // Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
    // Opera uses 'OPR' for more recent UAs.
    return lookUpValueWithKeys(['Version', 'Opera']);
  }

  // Check Edge before Chrome since it has Chrome in the string.
  if (goog.labs.userAgent.browser.isEdge()) {
    return lookUpValueWithKeys(['Edge']);
  }

  if (goog.labs.userAgent.browser.isChrome()) {
    return lookUpValueWithKeys(['Chrome', 'CriOS']);
  }

  // Usually products browser versions are in the third tuple after "Mozilla"
  // and the engine.
  var tuple = versionTuples[2];
  return tuple && tuple[1] || '';
};


/**
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the browser version is higher or the same as the
 *     given version.
 */
goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
  return goog.string.compareVersions(
             goog.labs.userAgent.browser.getVersion(), version) >= 0;
};


/**
 * Determines IE version. More information:
 * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
 * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
 * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
 * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
 *
 * @param {string} userAgent the User-Agent.
 * @return {string}
 * @private
 */
goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
  // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
  // bug. Example UA:
  // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
  // like Gecko.
  // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
  var rv = /rv: *([\d\.]*)/.exec(userAgent);
  if (rv && rv[1]) {
    return rv[1];
  }

  var version = '';
  var msie = /MSIE +([\d\.]+)/.exec(userAgent);
  if (msie && msie[1]) {
    // IE in compatibility mode usually identifies itself as MSIE 7.0; in this
    // case, use the Trident version to determine the version of IE. For more
    // details, see the links above.
    var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
    if (msie[1] == '7.0') {
      if (tridentVersion && tridentVersion[1]) {
        switch (tridentVersion[1]) {
          case '4.0':
            version = '8.0';
            break;
          case '5.0':
            version = '9.0';
            break;
          case '6.0':
            version = '10.0';
            break;
          case '7.0':
            version = '11.0';
            break;
        }
      } else {
        version = '7.0';
      }
    } else {
      version = msie[1];
    }
  }
  return version;
};

// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Closure user agent detection.
 * @see http://en.wikipedia.org/wiki/User_agent
 * For more information on browser brand, platform, or device see the other
 * sub-namespaces in goog.labs.userAgent (browser, platform, and device).
 *
 */

goog.provide('goog.labs.userAgent.engine');

goog.require('goog.array');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');


/**
 * @return {boolean} Whether the rendering engine is Presto.
 */
goog.labs.userAgent.engine.isPresto = function() {
  return goog.labs.userAgent.util.matchUserAgent('Presto');
};


/**
 * @return {boolean} Whether the rendering engine is Trident.
 */
goog.labs.userAgent.engine.isTrident = function() {
  // IE only started including the Trident token in IE8.
  return goog.labs.userAgent.util.matchUserAgent('Trident') ||
      goog.labs.userAgent.util.matchUserAgent('MSIE');
};


/**
 * @return {boolean} Whether the rendering engine is Edge.
 */
goog.labs.userAgent.engine.isEdge = function() {
  return goog.labs.userAgent.util.matchUserAgent('Edge');
};


/**
 * @return {boolean} Whether the rendering engine is WebKit.
 */
goog.labs.userAgent.engine.isWebKit = function() {
  return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') &&
      !goog.labs.userAgent.engine.isEdge();
};


/**
 * @return {boolean} Whether the rendering engine is Gecko.
 */
goog.labs.userAgent.engine.isGecko = function() {
  return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
      !goog.labs.userAgent.engine.isWebKit() &&
      !goog.labs.userAgent.engine.isTrident() &&
      !goog.labs.userAgent.engine.isEdge();
};


/**
 * @return {string} The rendering engine's version or empty string if version
 *     can't be determined.
 */
goog.labs.userAgent.engine.getVersion = function() {
  var userAgentString = goog.labs.userAgent.util.getUserAgent();
  if (userAgentString) {
    var tuples = goog.labs.userAgent.util.extractVersionTuples(userAgentString);

    var engineTuple = goog.labs.userAgent.engine.getEngineTuple_(tuples);
    if (engineTuple) {
      // In Gecko, the version string is either in the browser info or the
      // Firefox version.  See Gecko user agent string reference:
      // http://goo.gl/mULqa
      if (engineTuple[0] == 'Gecko') {
        return goog.labs.userAgent.engine.getVersionForKey_(tuples, 'Firefox');
      }

      return engineTuple[1];
    }

    // MSIE has only one version identifier, and the Trident version is
    // specified in the parenthetical. IE Edge is covered in the engine tuple
    // detection.
    var browserTuple = tuples[0];
    var info;
    if (browserTuple && (info = browserTuple[2])) {
      var match = /Trident\/([^\s;]+)/.exec(info);
      if (match) {
        return match[1];
      }
    }
  }
  return '';
};


/**
 * @param {!Array<!Array<string>>} tuples Extracted version tuples.
 * @return {!Array<string>|undefined} The engine tuple or undefined if not
 *     found.
 * @private
 */
goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) {
  if (!goog.labs.userAgent.engine.isEdge()) {
    return tuples[1];
  }
  for (var i = 0; i < tuples.length; i++) {
    var tuple = tuples[i];
    if (tuple[0] == 'Edge') {
      return tuple;
    }
  }
};


/**
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the rendering engine version is higher or the same
 *     as the given version.
 */
goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
  return goog.string.compareVersions(
             goog.labs.userAgent.engine.getVersion(), version) >= 0;
};


/**
 * @param {!Array<!Array<string>>} tuples Version tuples.
 * @param {string} key The key to look for.
 * @return {string} The version string of the given key, if present.
 *     Otherwise, the empty string.
 * @private
 */
goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
  // TODO(nnaze): Move to util if useful elsewhere.

  var pair = goog.array.find(tuples, function(pair) { return key == pair[0]; });

  return pair && pair[1] || '';
};

// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Closure user agent platform detection.
 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
 * For more information on browser brand, rendering engine, or device see the
 * other sub-namespaces in goog.labs.userAgent (browser, engine, and device
 * respectively).
 *
 */

goog.provide('goog.labs.userAgent.platform');

goog.require('goog.labs.userAgent.util');
goog.require('goog.string');


/**
 * @return {boolean} Whether the platform is Android.
 */
goog.labs.userAgent.platform.isAndroid = function() {
  return goog.labs.userAgent.util.matchUserAgent('Android');
};


/**
 * @return {boolean} Whether the platform is iPod.
 */
goog.labs.userAgent.platform.isIpod = function() {
  return goog.labs.userAgent.util.matchUserAgent('iPod');
};


/**
 * @return {boolean} Whether the platform is iPhone.
 */
goog.labs.userAgent.platform.isIphone = function() {
  return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
      !goog.labs.userAgent.util.matchUserAgent('iPod') &&
      !goog.labs.userAgent.util.matchUserAgent('iPad');
};


/**
 * @return {boolean} Whether the platform is iPad.
 */
goog.labs.userAgent.platform.isIpad = function() {
  return goog.labs.userAgent.util.matchUserAgent('iPad');
};


/**
 * @return {boolean} Whether the platform is iOS.
 */
goog.labs.userAgent.platform.isIos = function() {
  return goog.labs.userAgent.platform.isIphone() ||
      goog.labs.userAgent.platform.isIpad() ||
      goog.labs.userAgent.platform.isIpod();
};


/**
 * @return {boolean} Whether the platform is Mac.
 */
goog.labs.userAgent.platform.isMacintosh = function() {
  return goog.labs.userAgent.util.matchUserAgent('Macintosh');
};


/**
 * Note: ChromeOS is not considered to be Linux as it does not report itself
 * as Linux in the user agent string.
 * @return {boolean} Whether the platform is Linux.
 */
goog.labs.userAgent.platform.isLinux = function() {
  return goog.labs.userAgent.util.matchUserAgent('Linux');
};


/**
 * @return {boolean} Whether the platform is Windows.
 */
goog.labs.userAgent.platform.isWindows = function() {
  return goog.labs.userAgent.util.matchUserAgent('Windows');
};


/**
 * @return {boolean} Whether the platform is ChromeOS.
 */
goog.labs.userAgent.platform.isChromeOS = function() {
  return goog.labs.userAgent.util.matchUserAgent('CrOS');
};


/**
 * The version of the platform. We only determine the version for Windows,
 * Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
 * look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
 * version 0.0.
 *
 * @return {string} The platform version or empty string if version cannot be
 *     determined.
 */
goog.labs.userAgent.platform.getVersion = function() {
  var userAgentString = goog.labs.userAgent.util.getUserAgent();
  var version = '', re;
  if (goog.labs.userAgent.platform.isWindows()) {
    re = /Windows (?:NT|Phone) ([0-9.]+)/;
    var match = re.exec(userAgentString);
    if (match) {
      version = match[1];
    } else {
      version = '0.0';
    }
  } else if (goog.labs.userAgent.platform.isIos()) {
    re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
    var match = re.exec(userAgentString);
    // Report the version as x.y.z and not x_y_z
    version = match && match[1].replace(/_/g, '.');
  } else if (goog.labs.userAgent.platform.isMacintosh()) {
    re = /Mac OS X ([0-9_.]+)/;
    var match = re.exec(userAgentString);
    // Note: some old versions of Camino do not report an OSX version.
    // Default to 10.
    version = match ? match[1].replace(/_/g, '.') : '10';
  } else if (goog.labs.userAgent.platform.isAndroid()) {
    re = /Android\s+([^\);]+)(\)|;)/;
    var match = re.exec(userAgentString);
    version = match && match[1];
  } else if (goog.labs.userAgent.platform.isChromeOS()) {
    re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
    var match = re.exec(userAgentString);
    version = match && match[1];
  }
  return version || '';
};


/**
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the browser version is higher or the same as the
 *     given version.
 */
goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
  return goog.string.compareVersions(
             goog.labs.userAgent.platform.getVersion(), version) >= 0;
};

// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Rendering engine detection.
 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
 * For information on the browser brand (such as Safari versus Chrome), see
 * goog.userAgent.product.
 * @author arv@google.com (Erik Arvidsson)
 * @see ../demos/useragent.html
 */

goog.provide('goog.userAgent');

goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.engine');
goog.require('goog.labs.userAgent.platform');
goog.require('goog.labs.userAgent.util');
goog.require('goog.string');


/**
 * @define {boolean} Whether we know at compile-time that the browser is IE.
 */
goog.define('goog.userAgent.ASSUME_IE', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is EDGE.
 */
goog.define('goog.userAgent.ASSUME_EDGE', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is GECKO.
 */
goog.define('goog.userAgent.ASSUME_GECKO', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
 */
goog.define('goog.userAgent.ASSUME_WEBKIT', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is a
 *     mobile device running WebKit e.g. iPhone or Android.
 */
goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);


/**
 * @define {boolean} Whether we know at compile-time that the browser is OPERA.
 */
goog.define('goog.userAgent.ASSUME_OPERA', false);


/**
 * @define {boolean} Whether the
 *     {@code goog.userAgent.isVersionOrHigher}
 *     function will return true for any version.
 */
goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);


/**
 * Whether we know the browser engine at compile-time.
 * @type {boolean}
 * @private
 */
goog.userAgent.BROWSER_KNOWN_ = goog.userAgent.ASSUME_IE ||
    goog.userAgent.ASSUME_EDGE || goog.userAgent.ASSUME_GECKO ||
    goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.ASSUME_WEBKIT ||
    goog.userAgent.ASSUME_OPERA;


/**
 * Returns the userAgent string for the current browser.
 *
 * @return {string} The userAgent string.
 */
goog.userAgent.getUserAgentString = function() {
  return goog.labs.userAgent.util.getUserAgent();
};


/**
 * TODO(nnaze): Change type to "Navigator" and update compilation targets.
 * @return {Object} The native navigator object.
 */
goog.userAgent.getNavigator = function() {
  // Need a local navigator reference instead of using the global one,
  // to avoid the rare case where they reference different objects.
  // (in a WorkerPool, for example).
  return goog.global['navigator'] || null;
};


/**
 * Whether the user agent is Opera.
 * @type {boolean}
 */
goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_OPERA :
    goog.labs.userAgent.browser.isOpera();


/**
 * Whether the user agent is Internet Explorer.
 * @type {boolean}
 */
goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_IE :
    goog.labs.userAgent.browser.isIE();


/**
 * Whether the user agent is Microsoft Edge.
 * @type {boolean}
 */
goog.userAgent.EDGE = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_EDGE :
    goog.labs.userAgent.engine.isEdge();


/**
 * Whether the user agent is MS Internet Explorer or MS Edge.
 * @type {boolean}
 */
goog.userAgent.EDGE_OR_IE = goog.userAgent.EDGE || goog.userAgent.IE;


/**
 * Whether the user agent is Gecko. Gecko is the rendering engine used by
 * Mozilla, Firefox, and others.
 * @type {boolean}
 */
goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_GECKO :
    goog.labs.userAgent.engine.isGecko();


/**
 * Whether the user agent is WebKit. WebKit is the rendering engine that
 * Safari, Android and others use.
 * @type {boolean}
 */
goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
    goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
    goog.labs.userAgent.engine.isWebKit();


/**
 * Whether the user agent is running on a mobile device.
 *
 * This is a separate function so that the logic can be tested.
 *
 * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
 *
 * @return {boolean} Whether the user agent is running on a mobile device.
 * @private
 */
goog.userAgent.isMobile_ = function() {
  return goog.userAgent.WEBKIT &&
      goog.labs.userAgent.util.matchUserAgent('Mobile');
};


/**
 * Whether the user agent is running on a mobile device.
 *
 * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
 *   is promoted as the gecko/webkit logic is likely inaccurate.
 *
 * @type {boolean}
 */
goog.userAgent.MOBILE =
    goog.userAgent.ASSUME_MOBILE_WEBKIT || goog.userAgent.isMobile_();


/**
 * Used while transitioning code to use WEBKIT instead.
 * @type {boolean}
 * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
 * TODO(nicksantos): Delete this from goog.userAgent.
 */
goog.userAgent.SAFARI = goog.userAgent.WEBKIT;


/**
 * @return {string} the platform (operating system) the user agent is running
 *     on. Default to empty string because navigator.platform may not be defined
 *     (on Rhino, for example).
 * @private
 */
goog.userAgent.determinePlatform_ = function() {
  var navigator = goog.userAgent.getNavigator();
  return navigator && navigator.platform || '';
};


/**
 * The platform (operating system) the user agent is running on. Default to
 * empty string because navigator.platform may not be defined (on Rhino, for
 * example).
 * @type {string}
 */
goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();


/**
 * @define {boolean} Whether the user agent is running on a Macintosh operating
 *     system.
 */
goog.define('goog.userAgent.ASSUME_MAC', false);


/**
 * @define {boolean} Whether the user agent is running on a Windows operating
 *     system.
 */
goog.define('goog.userAgent.ASSUME_WINDOWS', false);


/**
 * @define {boolean} Whether the user agent is running on a Linux operating
 *     system.
 */
goog.define('goog.userAgent.ASSUME_LINUX', false);


/**
 * @define {boolean} Whether the user agent is running on a X11 windowing
 *     system.
 */
goog.define('goog.userAgent.ASSUME_X11', false);


/**
 * @define {boolean} Whether the user agent is running on Android.
 */
goog.define('goog.userAgent.ASSUME_ANDROID', false);


/**
 * @define {boolean} Whether the user agent is running on an iPhone.
 */
goog.define('goog.userAgent.ASSUME_IPHONE', false);


/**
 * @define {boolean} Whether the user agent is running on an iPad.
 */
goog.define('goog.userAgent.ASSUME_IPAD', false);


/**
 * @define {boolean} Whether the user agent is running on an iPod.
 */
goog.define('goog.userAgent.ASSUME_IPOD', false);


/**
 * @type {boolean}
 * @private
 */
goog.userAgent.PLATFORM_KNOWN_ = goog.userAgent.ASSUME_MAC ||
    goog.userAgent.ASSUME_WINDOWS || goog.userAgent.ASSUME_LINUX ||
    goog.userAgent.ASSUME_X11 || goog.userAgent.ASSUME_ANDROID ||
    goog.userAgent.ASSUME_IPHONE || goog.userAgent.ASSUME_IPAD ||
    goog.userAgent.ASSUME_IPOD;


/**
 * Whether the user agent is running on a Macintosh operating system.
 * @type {boolean}
 */
goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_MAC :
    goog.labs.userAgent.platform.isMacintosh();


/**
 * Whether the user agent is running on a Windows operating system.
 * @type {boolean}
 */
goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_WINDOWS :
    goog.labs.userAgent.platform.isWindows();


/**
 * Whether the user agent is Linux per the legacy behavior of
 * goog.userAgent.LINUX, which considered ChromeOS to also be
 * Linux.
 * @return {boolean}
 * @private
 */
goog.userAgent.isLegacyLinux_ = function() {
  return goog.labs.userAgent.platform.isLinux() ||
      goog.labs.userAgent.platform.isChromeOS();
};


/**
 * Whether the user agent is running on a Linux operating system.
 *
 * Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
 * while goog.labs.userAgent.platform considers ChromeOS and
 * Linux to be different OSes.
 *
 * @type {boolean}
 */
goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_LINUX :
    goog.userAgent.isLegacyLinux_();


/**
 * @return {boolean} Whether the user agent is an X11 windowing system.
 * @private
 */
goog.userAgent.isX11_ = function() {
  var navigator = goog.userAgent.getNavigator();
  return !!navigator &&
      goog.string.contains(navigator['appVersion'] || '', 'X11');
};


/**
 * Whether the user agent is running on a X11 windowing system.
 * @type {boolean}
 */
goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_X11 :
    goog.userAgent.isX11_();


/**
 * Whether the user agent is running on Android.
 * @type {boolean}
 */
goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_ANDROID :
    goog.labs.userAgent.platform.isAndroid();


/**
 * Whether the user agent is running on an iPhone.
 * @type {boolean}
 */
goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_IPHONE :
    goog.labs.userAgent.platform.isIphone();


/**
 * Whether the user agent is running on an iPad.
 * @type {boolean}
 */
goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_IPAD :
    goog.labs.userAgent.platform.isIpad();


/**
 * Whether the user agent is running on an iPod.
 * @type {boolean}
 */
goog.userAgent.IPOD = goog.userAgent.PLATFORM_KNOWN_ ?
    goog.userAgent.ASSUME_IPOD :
    goog.labs.userAgent.platform.isIpod();


/**
 * @return {string} The string that describes the version number of the user
 *     agent.
 * @private
 */
goog.userAgent.determineVersion_ = function() {
  // All browsers have different ways to detect the version and they all have
  // different naming schemes.
  // version is a string rather than a number because it may contain 'b', 'a',
  // and so on.
  var version = '';
  var arr = goog.userAgent.getVersionRegexResult_();
  if (arr) {
    version = arr ? arr[1] : '';
  }

  if (goog.userAgent.IE) {
    // IE9 can be in document mode 9 but be reporting an inconsistent user agent
    // version.  If it is identifying as a version lower than 9 we take the
    // documentMode as the version instead.  IE8 has similar behavior.
    // It is recommended to set the X-UA-Compatible header to ensure that IE9
    // uses documentMode 9.
    var docMode = goog.userAgent.getDocumentMode_();
    if (docMode != null && docMode > parseFloat(version)) {
      return String(docMode);
    }
  }

  return version;
};


/**
 * @return {?Array|undefined} The version regex matches from parsing the user
 *     agent string. These regex statements must be executed inline so they can
 *     be compiled out by the closure compiler with the rest of the useragent
 *     detection logic when ASSUME_* is specified.
 * @private
 */
goog.userAgent.getVersionRegexResult_ = function() {
  var userAgent = goog.userAgent.getUserAgentString();
  if (goog.userAgent.GECKO) {
    return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
  }
  if (goog.userAgent.EDGE) {
    return /Edge\/([\d\.]+)/.exec(userAgent);
  }
  if (goog.userAgent.IE) {
    return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
  }
  if (goog.userAgent.WEBKIT) {
    // WebKit/125.4
    return /WebKit\/(\S+)/.exec(userAgent);
  }
  if (goog.userAgent.OPERA) {
    // If none of the above browsers were detected but the browser is Opera, the
    // only string that is of interest is 'Version/<number>'.
    return /(?:Version)[ \/]?(\S+)/.exec(userAgent);
  }
  return undefined;
};


/**
 * @return {number|undefined} Returns the document mode (for testing).
 * @private
 */
goog.userAgent.getDocumentMode_ = function() {
  // NOTE(user): goog.userAgent may be used in context where there is no DOM.
  var doc = goog.global['document'];
  return doc ? doc['documentMode'] : undefined;
};


/**
 * The version of the user agent. This is a string because it might contain
 * 'b' (as in beta) as well as multiple dots.
 * @type {string}
 */
goog.userAgent.VERSION = goog.userAgent.determineVersion_();


/**
 * Compares two version numbers.
 *
 * @param {string} v1 Version of first item.
 * @param {string} v2 Version of second item.
 *
 * @return {number}  1 if first argument is higher
 *                   0 if arguments are equal
 *                  -1 if second argument is higher.
 * @deprecated Use goog.string.compareVersions.
 */
goog.userAgent.compare = function(v1, v2) {
  return goog.string.compareVersions(v1, v2);
};


/**
 * Cache for {@link goog.userAgent.isVersionOrHigher}.
 * Calls to compareVersions are surprisingly expensive and, as a browser's
 * version number is unlikely to change during a session, we cache the results.
 * @const
 * @private
 */
goog.userAgent.isVersionOrHigherCache_ = {};


/**
 * Whether the user agent version is higher or the same as the given version.
 * NOTE: When checking the version numbers for Firefox and Safari, be sure to
 * use the engine's version, not the browser's version number.  For example,
 * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
 * Opera and Internet Explorer versions match the product release number.<br>
 * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
 *     Webkit</a>
 * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
 *
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the user agent version is higher or the same as
 *     the given version.
 */
goog.userAgent.isVersionOrHigher = function(version) {
  return goog.userAgent.ASSUME_ANY_VERSION ||
      goog.userAgent.isVersionOrHigherCache_[version] ||
      (goog.userAgent.isVersionOrHigherCache_[version] =
           goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
};


/**
 * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
 * @param {string|number} version The version to check.
 * @return {boolean} Whether the user agent version is higher or the same as
 *     the given version.
 * @deprecated Use goog.userAgent.isVersionOrHigher().
 */
goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;


/**
 * Whether the IE effective document mode is higher or the same as the given
 * document mode version.
 * NOTE: Only for IE, return false for another browser.
 *
 * @param {number} documentMode The document mode version to check.
 * @return {boolean} Whether the IE effective document mode is higher or the
 *     same as the given version.
 */
goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
  return Number(goog.userAgent.DOCUMENT_MODE) >= documentMode;
};


/**
 * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
 * @param {number} version The version to check.
 * @return {boolean} Whether the IE effective document mode is higher or the
 *      same as the given version.
 * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
 */
goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;


/**
 * For IE version < 7, documentMode is undefined, so attempt to use the
 * CSS1Compat property to see if we are in standards mode. If we are in
 * standards mode, treat the browser version as the document mode. Otherwise,
 * IE is emulating version 5.
 * @type {number|undefined}
 * @const
 */
goog.userAgent.DOCUMENT_MODE = (function() {
  var doc = goog.global['document'];
  var mode = goog.userAgent.getDocumentMode_();
  if (!doc || !goog.userAgent.IE) {
    return undefined;
  }
  return mode || (doc['compatMode'] == 'CSS1Compat' ?
                      parseInt(goog.userAgent.VERSION, 10) :
                      5);
})();

goog.provide('ol.vec.Mat4');


/**
 * @return {Array.<number>} 4x4 matrix representing a 3D identity transform.
 */
ol.vec.Mat4.create = function() {
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
};


/**
 * @param {Array.<number>} mat4 Flattened 4x4 matrix receiving the result.
 * @param {ol.Transform} transform Transformation matrix.
 * @return {Array.<number>} 2D transformation matrix as flattened 4x4 matrix.
 */
ol.vec.Mat4.fromTransform = function(mat4, transform) {
  mat4[0] = transform[0];
  mat4[1] = transform[1];
  mat4[4] = transform[2];
  mat4[5] = transform[3];
  mat4[12] = transform[4];
  mat4[13] = transform[5];
  return mat4;
};

goog.provide('ol.dom');

goog.require('goog.userAgent');
goog.require('ol');
goog.require('ol.vec.Mat4');


/**
 * @type {Array.<number>}
 * @private
 */
ol.dom.tmpMat4_ = ol.vec.Mat4.create();


/**
 * Create an html canvas element and returns its 2d context.
 * @param {number=} opt_width Canvas width.
 * @param {number=} opt_height Canvas height.
 * @return {CanvasRenderingContext2D} The context.
 */
ol.dom.createCanvasContext2D = function(opt_width, opt_height) {
  var canvas = document.createElement('CANVAS');
  if (opt_width) {
    canvas.width = opt_width;
  }
  if (opt_height) {
    canvas.height = opt_height;
  }
  return canvas.getContext('2d');
};


/**
 * Detect 2d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms2d
 * @return {boolean}
 */
ol.dom.canUseCssTransform = (function() {
  var canUseCssTransform;
  return function() {
    if (canUseCssTransform === undefined) {
      var el = document.createElement('P'),
          has2d,
          transforms = {
            'webkitTransform': '-webkit-transform',
            'OTransform': '-o-transform',
            'msTransform': '-ms-transform',
            'MozTransform': '-moz-transform',
            'transform': 'transform'
          };
      document.body.appendChild(el);
      for (var t in transforms) {
        if (t in el.style) {
          el.style[t] = 'translate(1px,1px)';
          has2d = ol.global.getComputedStyle(el).getPropertyValue(
              transforms[t]);
        }
      }
      document.body.removeChild(el);

      canUseCssTransform = (has2d && has2d !== 'none');
    }
    return canUseCssTransform;
  };
}());


/**
 * Detect 3d transform.
 * Adapted from http://stackoverflow.com/q/5661671/130442
 * http://caniuse.com/#feat=transforms3d
 * @return {boolean}
 */
ol.dom.canUseCssTransform3D = (function() {
  var canUseCssTransform3D;
  return function() {
    if (canUseCssTransform3D === undefined) {
      var el = document.createElement('P'),
          has3d,
          transforms = {
            'webkitTransform': '-webkit-transform',
            'OTransform': '-o-transform',
            'msTransform': '-ms-transform',
            'MozTransform': '-moz-transform',
            'transform': 'transform'
          };
      document.body.appendChild(el);
      for (var t in transforms) {
        if (t in el.style) {
          el.style[t] = 'translate3d(1px,1px,1px)';
          has3d = ol.global.getComputedStyle(el).getPropertyValue(
              transforms[t]);
        }
      }
      document.body.removeChild(el);

      canUseCssTransform3D = (has3d && has3d !== 'none');
    }
    return canUseCssTransform3D;
  };
}());


/**
 * @param {Element} element Element.
 * @param {string} value Value.
 */
ol.dom.setTransform = function(element, value) {
  var style = element.style;
  style.WebkitTransform = value;
  style.MozTransform = value;
  style.OTransform = value;
  style.msTransform = value;
  style.transform = value;

  // IE 9+ seems to assume transform-origin: 100% 100%; for some unknown reason
  if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('9.0')) {
    element.style.transformOrigin = '0 0';
  }
};


/**
 * @param {!Element} element Element.
 * @param {ol.Transform} transform Matrix.
 * @param {number=} opt_precision Precision.
 */
ol.dom.transformElement2D = function(element, transform, opt_precision) {
  // using matrix() causes gaps in Chrome and Firefox on Mac OS X, so prefer
  // matrix3d()
  var i;
  if (ol.dom.canUseCssTransform3D()) {
    var value3D;
    var transform3D = ol.vec.Mat4.fromTransform(ol.dom.tmpMat4_, transform);
    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings3D = new Array(16);
      for (i = 0; i < 16; ++i) {
        strings3D[i] = transform3D[i].toFixed(opt_precision);
      }
      value3D = strings3D.join(',');
    } else {
      value3D = transform3D.join(',');
    }
    ol.dom.setTransform(element, 'matrix3d(' + value3D + ')');
  } else if (ol.dom.canUseCssTransform()) {
    /** @type {string} */
    var value2D;
    if (opt_precision !== undefined) {
      /** @type {Array.<string>} */
      var strings2D = new Array(6);
      for (i = 0; i < 6; ++i) {
        strings2D[i] = transform[i].toFixed(opt_precision);
      }
      value2D = strings2D.join(',');
    } else {
      value2D = transform.join(',');
    }
    ol.dom.setTransform(element, 'matrix(' + value2D + ')');
  } else {
    element.style.left = Math.round(transform[4]) + 'px';
    element.style.top = Math.round(transform[5]) + 'px';

    // TODO: Add scaling here. This isn't quite as simple as multiplying
    // width/height, because that only changes the container size, not the
    // content size.
  }
};


/**
 * Get the current computed width for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerWidth(true)`.
 * @param {!Element} element Element.
 * @return {number} The width.
 */
ol.dom.outerWidth = function(element) {
  var width = element.offsetWidth;
  var style = element.currentStyle || ol.global.getComputedStyle(element);
  width += parseInt(style.marginLeft, 10) + parseInt(style.marginRight, 10);

  return width;
};


/**
 * Get the current computed height for the given element including margin,
 * padding and border.
 * Equivalent to jQuery's `$(el).outerHeight(true)`.
 * @param {!Element} element Element.
 * @return {number} The height.
 */
ol.dom.outerHeight = function(element) {
  var height = element.offsetHeight;
  var style = element.currentStyle || ol.global.getComputedStyle(element);
  height += parseInt(style.marginTop, 10) + parseInt(style.marginBottom, 10);

  return height;
};

/**
 * @param {Node} newNode Node to replace old node
 * @param {Node} oldNode The node to be replaced
 */
ol.dom.replaceNode = function(newNode, oldNode) {
  var parent = oldNode.parentNode;
  if (parent) {
    parent.replaceChild(newNode, oldNode);
  }
};

/**
 * @param {Node} node The node to remove.
 * @returns {Node} The node that was removed or null.
 */
ol.dom.removeNode = function(node) {
  return node && node.parentNode ? node.parentNode.removeChild(node) : null;
};

/**
 * @param {Node} node The node to remove the children from.
 */
ol.dom.removeChildren = function(node) {
  while (node.lastChild) {
    node.removeChild(node.lastChild);
  }
};

goog.provide('ol.MapEvent');
goog.provide('ol.MapEventType');

goog.require('ol');
goog.require('ol.events.Event');


/**
 * @enum {string}
 */
ol.MapEventType = {

  /**
   * Triggered after a map frame is rendered.
   * @event ol.MapEvent#postrender
   * @api
   */
  POSTRENDER: 'postrender',

  /**
   * Triggered after the map is moved.
   * @event ol.MapEvent#moveend
   * @api stable
   */
  MOVEEND: 'moveend'

};


/**
 * @classdesc
 * Events emitted as map events are instances of this type.
 * See {@link ol.Map} for which events trigger a map event.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.MapEvent}
 * @param {string} type Event type.
 * @param {ol.Map} map Map.
 * @param {?olx.FrameState=} opt_frameState Frame state.
 */
ol.MapEvent = function(type, map, opt_frameState) {

  ol.events.Event.call(this, type);

  /**
   * The map where the event occurred.
   * @type {ol.Map}
   * @api stable
   */
  this.map = map;

  /**
   * The frame state at the time of the event.
   * @type {?olx.FrameState}
   * @api
   */
  this.frameState = opt_frameState !== undefined ? opt_frameState : null;

};
ol.inherits(ol.MapEvent, ol.events.Event);

goog.provide('ol.control.Control');

goog.require('ol.events');
goog.require('ol');
goog.require('ol.MapEventType');
goog.require('ol.Object');
goog.require('ol.dom');


/**
 * @classdesc
 * A control is a visible widget with a DOM element in a fixed position on the
 * screen. They can involve user input (buttons), or be informational only;
 * the position is determined using CSS. By default these are placed in the
 * container with CSS class name `ol-overlaycontainer-stopevent`, but can use
 * any outside DOM element.
 *
 * This is the base class for controls. You can use it for simple custom
 * controls by creating the element with listeners, creating an instance:
 * ```js
 * var myControl = new ol.control.Control({element: myElement});
 * ```
 * and then adding this to the map.
 *
 * The main advantage of having this as a control rather than a simple separate
 * DOM element is that preventing propagation is handled for you. Controls
 * will also be `ol.Object`s in a `ol.Collection`, so you can use their
 * methods.
 *
 * You can also extend this base for your own control class. See
 * examples/custom-controls for an example of how to do this.
 *
 * @constructor
 * @extends {ol.Object}
 * @implements {oli.control.Control}
 * @param {olx.control.ControlOptions} options Control options.
 * @api stable
 */
ol.control.Control = function(options) {

  ol.Object.call(this);

  /**
   * @protected
   * @type {Element}
   */
  this.element = options.element ? options.element : null;

  /**
   * @private
   * @type {Element}
   */
  this.target_ = null;

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  /**
   * @protected
   * @type {!Array.<ol.EventsKey>}
   */
  this.listenerKeys = [];

  /**
   * @type {function(ol.MapEvent)}
   */
  this.render = options.render ? options.render : ol.nullFunction;

  if (options.target) {
    this.setTarget(options.target);
  }

};
ol.inherits(ol.control.Control, ol.Object);


/**
 * @inheritDoc
 */
ol.control.Control.prototype.disposeInternal = function() {
  ol.dom.removeNode(this.element);
  ol.Object.prototype.disposeInternal.call(this);
};


/**
 * Get the map associated with this control.
 * @return {ol.Map} Map.
 * @api stable
 */
ol.control.Control.prototype.getMap = function() {
  return this.map_;
};


/**
 * Remove the control from its current map and attach it to the new map.
 * Subclasses may set up event handlers to get notified about changes to
 * the map here.
 * @param {ol.Map} map Map.
 * @api stable
 */
ol.control.Control.prototype.setMap = function(map) {
  if (this.map_) {
    ol.dom.removeNode(this.element);
  }
  for (var i = 0, ii = this.listenerKeys.length; i < ii; ++i) {
    ol.events.unlistenByKey(this.listenerKeys[i]);
  }
  this.listenerKeys.length = 0;
  this.map_ = map;
  if (this.map_) {
    var target = this.target_ ?
        this.target_ : map.getOverlayContainerStopEvent();
    target.appendChild(this.element);
    if (this.render !== ol.nullFunction) {
      this.listenerKeys.push(ol.events.listen(map,
          ol.MapEventType.POSTRENDER, this.render, this));
    }
    map.render();
  }
};


/**
 * This function is used to set a target element for the control. It has no
 * effect if it is called after the control has been added to the map (i.e.
 * after `setMap` is called on the control). If no `target` is set in the
 * options passed to the control constructor and if `setTarget` is not called
 * then the control is added to the map's overlay container.
 * @param {Element|string} target Target.
 * @api
 */
ol.control.Control.prototype.setTarget = function(target) {
  this.target_ = typeof target === 'string' ?
    document.getElementById(target) :
    target;
};

goog.provide('ol.css');


/**
 * The CSS class for hidden feature.
 *
 * @const
 * @type {string}
 */
ol.css.CLASS_HIDDEN = 'ol-hidden';


/**
 * The CSS class that we'll give the DOM elements to have them unselectable.
 *
 * @const
 * @type {string}
 */
ol.css.CLASS_UNSELECTABLE = 'ol-unselectable';


/**
 * The CSS class for unsupported feature.
 *
 * @const
 * @type {string}
 */
ol.css.CLASS_UNSUPPORTED = 'ol-unsupported';


/**
 * The CSS class for controls.
 *
 * @const
 * @type {string}
 */
ol.css.CLASS_CONTROL = 'ol-control';

// FIXME handle date line wrap

goog.provide('ol.control.Attribution');

goog.require('ol');
goog.require('ol.dom');
goog.require('ol.control.Control');
goog.require('ol.css');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.obj');


/**
 * @classdesc
 * Control to show all the attributions associated with the layer sources
 * in the map. This control is one of the default controls included in maps.
 * By default it will show in the bottom right portion of the map, but this can
 * be changed by using a css selector for `.ol-attribution`.
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.AttributionOptions=} opt_options Attribution options.
 * @api stable
 */
ol.control.Attribution = function(opt_options) {

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {Element}
   */
  this.ulElement_ = document.createElement('UL');

  /**
   * @private
   * @type {Element}
   */
  this.logoLi_ = document.createElement('LI');

  this.ulElement_.appendChild(this.logoLi_);
  this.logoLi_.style.display = 'none';

  /**
   * @private
   * @type {boolean}
   */
  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;

  /**
   * @private
   * @type {boolean}
   */
  this.collapsible_ = options.collapsible !== undefined ?
      options.collapsible : true;

  if (!this.collapsible_) {
    this.collapsed_ = false;
  }

  var className = options.className !== undefined ? options.className : 'ol-attribution';

  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Attributions';

  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00BB';

  if (typeof collapseLabel === 'string') {
    /**
     * @private
     * @type {Node}
     */
    this.collapseLabel_ = document.createElement('span');
    this.collapseLabel_.textContent = collapseLabel;
  } else {
    this.collapseLabel_ = collapseLabel;
  }

  var label = options.label !== undefined ? options.label : 'i';

  if (typeof label === 'string') {
    /**
     * @private
     * @type {Node}
     */
    this.label_ = document.createElement('span');
    this.label_.textContent = label;
  } else {
    this.label_ = label;
  }


  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
      this.collapseLabel_ : this.label_;
  var button = document.createElement('button');
  button.setAttribute('type', 'button');
  button.title = tipLabel;
  button.appendChild(activeLabel);

  ol.events.listen(button, ol.events.EventType.CLICK, this.handleClick_, this);

  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
      ol.css.CLASS_CONTROL +
      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
      (this.collapsible_ ? '' : ' ol-uncollapsible');
  var element = document.createElement('div');
  element.className = cssClasses;
  element.appendChild(this.ulElement_);
  element.appendChild(button);

  var render = options.render ? options.render : ol.control.Attribution.render;

  ol.control.Control.call(this, {
    element: element,
    render: render,
    target: options.target
  });

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = true;

  /**
   * @private
   * @type {Object.<string, Element>}
   */
  this.attributionElements_ = {};

  /**
   * @private
   * @type {Object.<string, boolean>}
   */
  this.attributionElementRenderedVisible_ = {};

  /**
   * @private
   * @type {Object.<string, Element>}
   */
  this.logoElements_ = {};

};
ol.inherits(ol.control.Attribution, ol.control.Control);


/**
 * @param {?olx.FrameState} frameState Frame state.
 * @return {Array.<Object.<string, ol.Attribution>>} Attributions.
 */
ol.control.Attribution.prototype.getSourceAttributions = function(frameState) {
  var i, ii, j, jj, tileRanges, source, sourceAttribution,
      sourceAttributionKey, sourceAttributions, sourceKey;
  var intersectsTileRange;
  var layerStatesArray = frameState.layerStatesArray;
  /** @type {Object.<string, ol.Attribution>} */
  var attributions = ol.obj.assign({}, frameState.attributions);
  /** @type {Object.<string, ol.Attribution>} */
  var hiddenAttributions = {};
  var projection = /** @type {!ol.proj.Projection} */ (frameState.viewState.projection);
  for (i = 0, ii = layerStatesArray.length; i < ii; i++) {
    source = layerStatesArray[i].layer.getSource();
    if (!source) {
      continue;
    }
    sourceKey = ol.getUid(source).toString();
    sourceAttributions = source.getAttributions();
    if (!sourceAttributions) {
      continue;
    }
    for (j = 0, jj = sourceAttributions.length; j < jj; j++) {
      sourceAttribution = sourceAttributions[j];
      sourceAttributionKey = ol.getUid(sourceAttribution).toString();
      if (sourceAttributionKey in attributions) {
        continue;
      }
      tileRanges = frameState.usedTiles[sourceKey];
      if (tileRanges) {
        var tileGrid = /** @type {ol.source.Tile} */ (source).getTileGridForProjection(projection);
        intersectsTileRange = sourceAttribution.intersectsAnyTileRange(
            tileRanges, tileGrid, projection);
      } else {
        intersectsTileRange = false;
      }
      if (intersectsTileRange) {
        if (sourceAttributionKey in hiddenAttributions) {
          delete hiddenAttributions[sourceAttributionKey];
        }
        attributions[sourceAttributionKey] = sourceAttribution;
      } else {
        hiddenAttributions[sourceAttributionKey] = sourceAttribution;
      }
    }
  }
  return [attributions, hiddenAttributions];
};


/**
 * Update the attribution element.
 * @param {ol.MapEvent} mapEvent Map event.
 * @this {ol.control.Attribution}
 * @api
 */
ol.control.Attribution.render = function(mapEvent) {
  this.updateElement_(mapEvent.frameState);
};


/**
 * @private
 * @param {?olx.FrameState} frameState Frame state.
 */
ol.control.Attribution.prototype.updateElement_ = function(frameState) {

  if (!frameState) {
    if (this.renderedVisible_) {
      this.element.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return;
  }

  var attributions = this.getSourceAttributions(frameState);
  /** @type {Object.<string, ol.Attribution>} */
  var visibleAttributions = attributions[0];
  /** @type {Object.<string, ol.Attribution>} */
  var hiddenAttributions = attributions[1];

  var attributionElement, attributionKey;
  for (attributionKey in this.attributionElements_) {
    if (attributionKey in visibleAttributions) {
      if (!this.attributionElementRenderedVisible_[attributionKey]) {
        this.attributionElements_[attributionKey].style.display = '';
        this.attributionElementRenderedVisible_[attributionKey] = true;
      }
      delete visibleAttributions[attributionKey];
    } else if (attributionKey in hiddenAttributions) {
      if (this.attributionElementRenderedVisible_[attributionKey]) {
        this.attributionElements_[attributionKey].style.display = 'none';
        delete this.attributionElementRenderedVisible_[attributionKey];
      }
      delete hiddenAttributions[attributionKey];
    } else {
      ol.dom.removeNode(this.attributionElements_[attributionKey]);
      delete this.attributionElements_[attributionKey];
      delete this.attributionElementRenderedVisible_[attributionKey];
    }
  }
  for (attributionKey in visibleAttributions) {
    attributionElement = document.createElement('LI');
    attributionElement.innerHTML =
        visibleAttributions[attributionKey].getHTML();
    this.ulElement_.appendChild(attributionElement);
    this.attributionElements_[attributionKey] = attributionElement;
    this.attributionElementRenderedVisible_[attributionKey] = true;
  }
  for (attributionKey in hiddenAttributions) {
    attributionElement = document.createElement('LI');
    attributionElement.innerHTML =
        hiddenAttributions[attributionKey].getHTML();
    attributionElement.style.display = 'none';
    this.ulElement_.appendChild(attributionElement);
    this.attributionElements_[attributionKey] = attributionElement;
  }

  var renderVisible =
      !ol.obj.isEmpty(this.attributionElementRenderedVisible_) ||
      !ol.obj.isEmpty(frameState.logos);
  if (this.renderedVisible_ != renderVisible) {
    this.element.style.display = renderVisible ? '' : 'none';
    this.renderedVisible_ = renderVisible;
  }
  if (renderVisible &&
      ol.obj.isEmpty(this.attributionElementRenderedVisible_)) {
    this.element.classList.add('ol-logo-only');
  } else {
    this.element.classList.remove('ol-logo-only');
  }

  this.insertLogos_(frameState);

};


/**
 * @param {?olx.FrameState} frameState Frame state.
 * @private
 */
ol.control.Attribution.prototype.insertLogos_ = function(frameState) {

  var logo;
  var logos = frameState.logos;
  var logoElements = this.logoElements_;

  for (logo in logoElements) {
    if (!(logo in logos)) {
      ol.dom.removeNode(logoElements[logo]);
      delete logoElements[logo];
    }
  }

  var image, logoElement, logoKey;
  for (logoKey in logos) {
    var logoValue = logos[logoKey];
    if (logoValue instanceof HTMLElement) {
      this.logoLi_.appendChild(logoValue);
      logoElements[logoKey] = logoValue;
    }
    if (!(logoKey in logoElements)) {
      image = new Image();
      image.src = logoKey;
      if (logoValue === '') {
        logoElement = image;
      } else {
        logoElement = document.createElement('a');
        logoElement.href = logoValue;
        logoElement.appendChild(image);
      }
      this.logoLi_.appendChild(logoElement);
      logoElements[logoKey] = logoElement;
    }
  }

  this.logoLi_.style.display = !ol.obj.isEmpty(logos) ? '' : 'none';

};


/**
 * @param {Event} event The event to handle
 * @private
 */
ol.control.Attribution.prototype.handleClick_ = function(event) {
  event.preventDefault();
  this.handleToggle_();
};


/**
 * @private
 */
ol.control.Attribution.prototype.handleToggle_ = function() {
  this.element.classList.toggle('ol-collapsed');
  if (this.collapsed_) {
    ol.dom.replaceNode(this.collapseLabel_, this.label_);
  } else {
    ol.dom.replaceNode(this.label_, this.collapseLabel_);
  }
  this.collapsed_ = !this.collapsed_;
};


/**
 * Return `true` if the attribution is collapsible, `false` otherwise.
 * @return {boolean} True if the widget is collapsible.
 * @api stable
 */
ol.control.Attribution.prototype.getCollapsible = function() {
  return this.collapsible_;
};


/**
 * Set whether the attribution should be collapsible.
 * @param {boolean} collapsible True if the widget is collapsible.
 * @api stable
 */
ol.control.Attribution.prototype.setCollapsible = function(collapsible) {
  if (this.collapsible_ === collapsible) {
    return;
  }
  this.collapsible_ = collapsible;
  this.element.classList.toggle('ol-uncollapsible');
  if (!collapsible && this.collapsed_) {
    this.handleToggle_();
  }
};


/**
 * Collapse or expand the attribution according to the passed parameter. Will
 * not do anything if the attribution isn't collapsible or if the current
 * collapsed state is already the one requested.
 * @param {boolean} collapsed True if the widget is collapsed.
 * @api stable
 */
ol.control.Attribution.prototype.setCollapsed = function(collapsed) {
  if (!this.collapsible_ || this.collapsed_ === collapsed) {
    return;
  }
  this.handleToggle_();
};


/**
 * Return `true` when the attribution is currently collapsed or `false`
 * otherwise.
 * @return {boolean} True if the widget is collapsed.
 * @api stable
 */
ol.control.Attribution.prototype.getCollapsed = function() {
  return this.collapsed_;
};

goog.provide('ol.control.FullScreen');

goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol');
goog.require('ol.control.Control');
goog.require('ol.dom');
goog.require('ol.css');


/**
 * @classdesc
 * Provides a button that when clicked fills up the full screen with the map.
 * The full screen source element is by default the element containing the map viewport unless
 * overriden by providing the `source` option. In which case, the dom
 * element introduced using this parameter will be displayed in full screen.
 *
 * When in full screen mode, a close button is shown to exit full screen mode.
 * The [Fullscreen API](http://www.w3.org/TR/fullscreen/) is used to
 * toggle the map in full screen mode.
 *
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.FullScreenOptions=} opt_options Options.
 * @api stable
 */
ol.control.FullScreen = function(opt_options) {

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {string}
   */
  this.cssClassName_ = options.className !== undefined ? options.className :
      'ol-full-screen';

  var label = options.label !== undefined ? options.label : '\u2922';

  /**
   * @private
   * @type {Node}
   */
  this.labelNode_ = typeof label === 'string' ?
      document.createTextNode(label) : label;

  var labelActive = options.labelActive !== undefined ? options.labelActive : '\u00d7';

  /**
   * @private
   * @type {Node}
   */
  this.labelActiveNode_ = typeof labelActive === 'string' ?
      document.createTextNode(labelActive) : labelActive;

  var tipLabel = options.tipLabel ? options.tipLabel : 'Toggle full-screen';
  var button = document.createElement('button');
  button.className = this.cssClassName_ + '-' + ol.control.FullScreen.isFullScreen();
  button.setAttribute('type', 'button');
  button.title = tipLabel;
  button.appendChild(this.labelNode_);

  ol.events.listen(button, ol.events.EventType.CLICK,
      this.handleClick_, this);

  var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE +
      ' ' + ol.css.CLASS_CONTROL + ' ' +
      (!ol.control.FullScreen.isFullScreenSupported() ? ol.css.CLASS_UNSUPPORTED : '');
  var element = document.createElement('div');
  element.className = cssClasses;
  element.appendChild(button);

  ol.control.Control.call(this, {
    element: element,
    target: options.target
  });

  /**
   * @private
   * @type {boolean}
   */
  this.keys_ = options.keys !== undefined ? options.keys : false;

  /**
   * @private
   * @type {Element|string|undefined}
   */
  this.source_ = options.source;

};
ol.inherits(ol.control.FullScreen, ol.control.Control);


/**
 * @param {Event} event The event to handle
 * @private
 */
ol.control.FullScreen.prototype.handleClick_ = function(event) {
  event.preventDefault();
  this.handleFullScreen_();
};


/**
 * @private
 */
ol.control.FullScreen.prototype.handleFullScreen_ = function() {
  if (!ol.control.FullScreen.isFullScreenSupported()) {
    return;
  }
  var map = this.getMap();
  if (!map) {
    return;
  }
  if (ol.control.FullScreen.isFullScreen()) {
    ol.control.FullScreen.exitFullScreen();
  } else {
    var element;
    if (this.source_) {
      element = typeof this.source_ === 'string' ?
        document.getElementById(this.source_) :
        this.source_;
    } else {
      element = map.getTargetElement();
    }
    if (this.keys_) {
      ol.control.FullScreen.requestFullScreenWithKeys(element);

    } else {
      ol.control.FullScreen.requestFullScreen(element);
    }
  }
};


/**
 * @private
 */
ol.control.FullScreen.prototype.handleFullScreenChange_ = function() {
  var button = this.element.firstElementChild;
  var map = this.getMap();
  if (ol.control.FullScreen.isFullScreen()) {
    button.className = this.cssClassName_ + '-true';
    ol.dom.replaceNode(this.labelActiveNode_, this.labelNode_);
  } else {
    button.className = this.cssClassName_ + '-false';
    ol.dom.replaceNode(this.labelNode_, this.labelActiveNode_);
  }
  if (map) {
    map.updateSize();
  }
};


/**
 * @inheritDoc
 * @api stable
 */
ol.control.FullScreen.prototype.setMap = function(map) {
  ol.control.Control.prototype.setMap.call(this, map);
  if (map) {
    this.listenerKeys.push(ol.events.listen(ol.global.document,
        ol.control.FullScreen.getChangeType_(),
        this.handleFullScreenChange_, this)
    );
  }
};

/**
 * @return {boolean} Fullscreen is supported by the current platform.
 */
ol.control.FullScreen.isFullScreenSupported = function() {
  var body = document.body;
  return !!(
    body.webkitRequestFullscreen ||
    (body.mozRequestFullScreen && document.mozFullScreenEnabled) ||
    (body.msRequestFullscreen && document.msFullscreenEnabled) ||
    (body.requestFullscreen && document.fullscreenEnabled)
  );
};

/**
 * @return {boolean} Element is currently in fullscreen.
 */
ol.control.FullScreen.isFullScreen = function() {
  return !!(
    document.webkitIsFullScreen || document.mozFullScreen ||
    document.msFullscreenElement || document.fullscreenElement
  );
};

/**
 * Request to fullscreen an element.
 * @param {Node} element Element to request fullscreen
 */
ol.control.FullScreen.requestFullScreen = function(element) {
  if (element.requestFullscreen) {
    element.requestFullscreen();
  } else if (element.msRequestFullscreen) {
    element.msRequestFullscreen();
  } else if (element.mozRequestFullScreen) {
    element.mozRequestFullScreen();
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullscreen();
  }
};

/**
 * Request to fullscreen an element with keyboard input.
 * @param {Node} element Element to request fullscreen
 */
ol.control.FullScreen.requestFullScreenWithKeys = function(element) {
  if (element.mozRequestFullScreenWithKeys) {
    element.mozRequestFullScreenWithKeys();
  } else if (element.webkitRequestFullscreen) {
    element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  } else {
    ol.control.FullScreen.requestFullScreen(element);
  }
};

/**
 * Exit fullscreen.
 */
ol.control.FullScreen.exitFullScreen = function() {
  if (document.exitFullscreen) {
    document.exitFullscreen();
  } else if (document.msExitFullscreen) {
    document.msExitFullscreen();
  } else if (document.mozCancelFullScreen) {
    document.mozCancelFullScreen();
  } else if (document.webkitExitFullscreen) {
    document.webkitExitFullscreen();
  }
};

/**
 * @return {string} Change type.
 * @private
 */
ol.control.FullScreen.getChangeType_ = (function() {
  var changeType;
  return function() {
    if (!changeType) {
      var body = document.body;
      if (body.webkitRequestFullscreen) {
        changeType = 'webkitfullscreenchange';
      } else if (body.mozRequestFullScreen) {
        changeType = 'mozfullscreenchange';
      } else if (body.msRequestFullscreen) {
        changeType = 'MSFullscreenChange';
      } else if (body.requestFullscreen) {
        changeType = 'fullscreenchange';
      }
    }
    return changeType;
  };
})();

goog.provide('ol.control.Rotate');

goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol');
goog.require('ol.animation');
goog.require('ol.control.Control');
goog.require('ol.css');
goog.require('ol.easing');


/**
 * @classdesc
 * A button control to reset rotation to 0.
 * To style this control use css selector `.ol-rotate`. A `.ol-hidden` css
 * selector is added to the button when the rotation is 0.
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.RotateOptions=} opt_options Rotate options.
 * @api stable
 */
ol.control.Rotate = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var className = options.className !== undefined ? options.className : 'ol-rotate';

  var label = options.label !== undefined ? options.label : '\u21E7';

  /**
   * @type {Element}
   * @private
   */
  this.label_ = null;

  if (typeof label === 'string') {
    this.label_ = document.createElement('span');
    this.label_.className = 'ol-compass';
    this.label_.textContent = label;
  } else {
    this.label_ = label;
    this.label_.classList.add('ol-compass');
  }

  var tipLabel = options.tipLabel ? options.tipLabel : 'Reset rotation';

  var button = document.createElement('button');
  button.className = className + '-reset';
  button.setAttribute('type', 'button');
  button.title = tipLabel;
  button.appendChild(this.label_);

  ol.events.listen(button, ol.events.EventType.CLICK,
      ol.control.Rotate.prototype.handleClick_, this);

  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
      ol.css.CLASS_CONTROL;
  var element = document.createElement('div');
  element.className = cssClasses;
  element.appendChild(button);

  var render = options.render ? options.render : ol.control.Rotate.render;

  this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;

  ol.control.Control.call(this, {
    element: element,
    render: render,
    target: options.target
  });

  /**
   * @type {number}
   * @private
   */
  this.duration_ = options.duration !== undefined ? options.duration : 250;

  /**
   * @type {boolean}
   * @private
   */
  this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;

  /**
   * @private
   * @type {number|undefined}
   */
  this.rotation_ = undefined;

  if (this.autoHide_) {
    this.element.classList.add(ol.css.CLASS_HIDDEN);
  }

};
ol.inherits(ol.control.Rotate, ol.control.Control);


/**
 * @param {Event} event The event to handle
 * @private
 */
ol.control.Rotate.prototype.handleClick_ = function(event) {
  event.preventDefault();
  if (this.callResetNorth_ !== undefined) {
    this.callResetNorth_();
  } else {
    this.resetNorth_();
  }
};


/**
 * @private
 */
ol.control.Rotate.prototype.resetNorth_ = function() {
  var map = this.getMap();
  var view = map.getView();
  if (!view) {
    // the map does not have a view, so we can't act
    // upon it
    return;
  }
  var currentRotation = view.getRotation();
  if (currentRotation !== undefined) {
    if (this.duration_ > 0) {
      currentRotation = currentRotation % (2 * Math.PI);
      if (currentRotation < -Math.PI) {
        currentRotation += 2 * Math.PI;
      }
      if (currentRotation > Math.PI) {
        currentRotation -= 2 * Math.PI;
      }
      map.beforeRender(ol.animation.rotate({
        rotation: currentRotation,
        duration: this.duration_,
        easing: ol.easing.easeOut
      }));
    }
    view.setRotation(0);
  }
};


/**
 * Update the rotate control element.
 * @param {ol.MapEvent} mapEvent Map event.
 * @this {ol.control.Rotate}
 * @api
 */
ol.control.Rotate.render = function(mapEvent) {
  var frameState = mapEvent.frameState;
  if (!frameState) {
    return;
  }
  var rotation = frameState.viewState.rotation;
  if (rotation != this.rotation_) {
    var transform = 'rotate(' + rotation + 'rad)';
    if (this.autoHide_) {
      var contains = this.element.classList.contains(ol.css.CLASS_HIDDEN);
      if (!contains && rotation === 0) {
        this.element.classList.add(ol.css.CLASS_HIDDEN);
      } else if (contains && rotation !== 0) {
        this.element.classList.remove(ol.css.CLASS_HIDDEN);
      }
    }
    this.label_.style.msTransform = transform;
    this.label_.style.webkitTransform = transform;
    this.label_.style.transform = transform;
  }
  this.rotation_ = rotation;
};

goog.provide('ol.control.Zoom');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.animation');
goog.require('ol.control.Control');
goog.require('ol.css');
goog.require('ol.easing');


/**
 * @classdesc
 * A control with 2 buttons, one for zoom in and one for zoom out.
 * This control is one of the default controls of a map. To style this control
 * use css selectors `.ol-zoom-in` and `.ol-zoom-out`.
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.ZoomOptions=} opt_options Zoom options.
 * @api stable
 */
ol.control.Zoom = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var className = options.className !== undefined ? options.className : 'ol-zoom';

  var delta = options.delta !== undefined ? options.delta : 1;

  var zoomInLabel = options.zoomInLabel !== undefined ? options.zoomInLabel : '+';
  var zoomOutLabel = options.zoomOutLabel !== undefined ? options.zoomOutLabel : '\u2212';

  var zoomInTipLabel = options.zoomInTipLabel !== undefined ?
      options.zoomInTipLabel : 'Zoom in';
  var zoomOutTipLabel = options.zoomOutTipLabel !== undefined ?
      options.zoomOutTipLabel : 'Zoom out';

  var inElement = document.createElement('button');
  inElement.className = className + '-in';
  inElement.setAttribute('type', 'button');
  inElement.title = zoomInTipLabel;
  inElement.appendChild(
    typeof zoomInLabel === 'string' ? document.createTextNode(zoomInLabel) : zoomInLabel
  );

  ol.events.listen(inElement, ol.events.EventType.CLICK,
      ol.control.Zoom.prototype.handleClick_.bind(this, delta));

  var outElement = document.createElement('button');
  outElement.className = className + '-out';
  outElement.setAttribute('type', 'button');
  outElement.title = zoomOutTipLabel;
  outElement.appendChild(
    typeof zoomOutLabel === 'string' ? document.createTextNode(zoomOutLabel) : zoomOutLabel
  );

  ol.events.listen(outElement, ol.events.EventType.CLICK,
      ol.control.Zoom.prototype.handleClick_.bind(this, -delta));

  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
      ol.css.CLASS_CONTROL;
  var element = document.createElement('div');
  element.className = cssClasses;
  element.appendChild(inElement);
  element.appendChild(outElement);

  ol.control.Control.call(this, {
    element: element,
    target: options.target
  });

  /**
   * @type {number}
   * @private
   */
  this.duration_ = options.duration !== undefined ? options.duration : 250;

};
ol.inherits(ol.control.Zoom, ol.control.Control);


/**
 * @param {number} delta Zoom delta.
 * @param {Event} event The event to handle
 * @private
 */
ol.control.Zoom.prototype.handleClick_ = function(delta, event) {
  event.preventDefault();
  this.zoomByDelta_(delta);
};


/**
 * @param {number} delta Zoom delta.
 * @private
 */
ol.control.Zoom.prototype.zoomByDelta_ = function(delta) {
  var map = this.getMap();
  var view = map.getView();
  if (!view) {
    // the map does not have a view, so we can't act
    // upon it
    return;
  }
  var currentResolution = view.getResolution();
  if (currentResolution) {
    if (this.duration_ > 0) {
      map.beforeRender(ol.animation.zoom({
        resolution: currentResolution,
        duration: this.duration_,
        easing: ol.easing.easeOut
      }));
    }
    var newResolution = view.constrainResolution(currentResolution, delta);
    view.setResolution(newResolution);
  }
};

goog.provide('ol.control');

goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.control.Attribution');
goog.require('ol.control.Rotate');
goog.require('ol.control.Zoom');


/**
 * Set of controls included in maps by default. Unless configured otherwise,
 * this returns a collection containing an instance of each of the following
 * controls:
 * * {@link ol.control.Zoom}
 * * {@link ol.control.Rotate}
 * * {@link ol.control.Attribution}
 *
 * @param {olx.control.DefaultsOptions=} opt_options Defaults options.
 * @return {ol.Collection.<ol.control.Control>} Controls.
 * @api stable
 */
ol.control.defaults = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var controls = new ol.Collection();

  var zoomControl = options.zoom !== undefined ? options.zoom : true;
  if (zoomControl) {
    controls.push(new ol.control.Zoom(options.zoomOptions));
  }

  var rotateControl = options.rotate !== undefined ? options.rotate : true;
  if (rotateControl) {
    controls.push(new ol.control.Rotate(options.rotateOptions));
  }

  var attributionControl = options.attribution !== undefined ?
      options.attribution : true;
  if (attributionControl) {
    controls.push(new ol.control.Attribution(options.attributionOptions));
  }

  return controls;

};

// FIXME should listen on appropriate pane, once it is defined

goog.provide('ol.control.MousePosition');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.Object');
goog.require('ol.control.Control');
goog.require('ol.proj');


/**
 * @enum {string}
 */
ol.control.MousePositionProperty = {
  PROJECTION: 'projection',
  COORDINATE_FORMAT: 'coordinateFormat'
};


/**
 * @classdesc
 * A control to show the 2D coordinates of the mouse cursor. By default, these
 * are in the view projection, but can be in any supported projection.
 * By default the control is shown in the top right corner of the map, but this
 * can be changed by using the css selector `.ol-mouse-position`.
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.MousePositionOptions=} opt_options Mouse position
 *     options.
 * @api stable
 */
ol.control.MousePosition = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var element = document.createElement('DIV');
  element.className = options.className !== undefined ? options.className : 'ol-mouse-position';

  var render = options.render ?
      options.render : ol.control.MousePosition.render;

  ol.control.Control.call(this, {
    element: element,
    render: render,
    target: options.target
  });

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.control.MousePositionProperty.PROJECTION),
      this.handleProjectionChanged_, this);

  if (options.coordinateFormat) {
    this.setCoordinateFormat(options.coordinateFormat);
  }
  if (options.projection) {
    this.setProjection(ol.proj.get(options.projection));
  }

  /**
   * @private
   * @type {string}
   */
  this.undefinedHTML_ = options.undefinedHTML !== undefined ? options.undefinedHTML : '';

  /**
   * @private
   * @type {string}
   */
  this.renderedHTML_ = element.innerHTML;

  /**
   * @private
   * @type {ol.proj.Projection}
   */
  this.mapProjection_ = null;

  /**
   * @private
   * @type {?ol.TransformFunction}
   */
  this.transform_ = null;

  /**
   * @private
   * @type {ol.Pixel}
   */
  this.lastMouseMovePixel_ = null;

};
ol.inherits(ol.control.MousePosition, ol.control.Control);


/**
 * Update the mouseposition element.
 * @param {ol.MapEvent} mapEvent Map event.
 * @this {ol.control.MousePosition}
 * @api
 */
ol.control.MousePosition.render = function(mapEvent) {
  var frameState = mapEvent.frameState;
  if (!frameState) {
    this.mapProjection_ = null;
  } else {
    if (this.mapProjection_ != frameState.viewState.projection) {
      this.mapProjection_ = frameState.viewState.projection;
      this.transform_ = null;
    }
  }
  this.updateHTML_(this.lastMouseMovePixel_);
};


/**
 * @private
 */
ol.control.MousePosition.prototype.handleProjectionChanged_ = function() {
  this.transform_ = null;
};


/**
 * Return the coordinate format type used to render the current position or
 * undefined.
 * @return {ol.CoordinateFormatType|undefined} The format to render the current
 *     position in.
 * @observable
 * @api stable
 */
ol.control.MousePosition.prototype.getCoordinateFormat = function() {
  return /** @type {ol.CoordinateFormatType|undefined} */ (
      this.get(ol.control.MousePositionProperty.COORDINATE_FORMAT));
};


/**
 * Return the projection that is used to report the mouse position.
 * @return {ol.proj.Projection|undefined} The projection to report mouse
 *     position in.
 * @observable
 * @api stable
 */
ol.control.MousePosition.prototype.getProjection = function() {
  return /** @type {ol.proj.Projection|undefined} */ (
      this.get(ol.control.MousePositionProperty.PROJECTION));
};


/**
 * @param {Event} event Browser event.
 * @protected
 */
ol.control.MousePosition.prototype.handleMouseMove = function(event) {
  var map = this.getMap();
  this.lastMouseMovePixel_ = map.getEventPixel(event);
  this.updateHTML_(this.lastMouseMovePixel_);
};


/**
 * @param {Event} event Browser event.
 * @protected
 */
ol.control.MousePosition.prototype.handleMouseOut = function(event) {
  this.updateHTML_(null);
  this.lastMouseMovePixel_ = null;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.control.MousePosition.prototype.setMap = function(map) {
  ol.control.Control.prototype.setMap.call(this, map);
  if (map) {
    var viewport = map.getViewport();
    this.listenerKeys.push(
        ol.events.listen(viewport, ol.events.EventType.MOUSEMOVE,
            this.handleMouseMove, this),
        ol.events.listen(viewport, ol.events.EventType.MOUSEOUT,
            this.handleMouseOut, this)
    );
  }
};


/**
 * Set the coordinate format type used to render the current position.
 * @param {ol.CoordinateFormatType} format The format to render the current
 *     position in.
 * @observable
 * @api stable
 */
ol.control.MousePosition.prototype.setCoordinateFormat = function(format) {
  this.set(ol.control.MousePositionProperty.COORDINATE_FORMAT, format);
};


/**
 * Set the projection that is used to report the mouse position.
 * @param {ol.proj.Projection} projection The projection to report mouse
 *     position in.
 * @observable
 * @api stable
 */
ol.control.MousePosition.prototype.setProjection = function(projection) {
  this.set(ol.control.MousePositionProperty.PROJECTION, projection);
};


/**
 * @param {?ol.Pixel} pixel Pixel.
 * @private
 */
ol.control.MousePosition.prototype.updateHTML_ = function(pixel) {
  var html = this.undefinedHTML_;
  if (pixel && this.mapProjection_) {
    if (!this.transform_) {
      var projection = this.getProjection();
      if (projection) {
        this.transform_ = ol.proj.getTransformFromProjections(
            this.mapProjection_, projection);
      } else {
        this.transform_ = ol.proj.identityTransform;
      }
    }
    var map = this.getMap();
    var coordinate = map.getCoordinateFromPixel(pixel);
    if (coordinate) {
      this.transform_(coordinate, coordinate);
      var coordinateFormat = this.getCoordinateFormat();
      if (coordinateFormat) {
        html = coordinateFormat(coordinate);
      } else {
        html = coordinate.toString();
      }
    }
  }
  if (!this.renderedHTML_ || html != this.renderedHTML_) {
    this.element.innerHTML = html;
    this.renderedHTML_ = html;
  }
};

// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview A global registry for entry points into a program,
 * so that they can be instrumented. Each module should register their
 * entry points with this registry. Designed to be compiled out
 * if no instrumentation is requested.
 *
 * Entry points may be registered before or after a call to
 * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
 * later, the existing monitor will instrument the new entry point.
 *
 * @author nicksantos@google.com (Nick Santos)
 */

goog.provide('goog.debug.EntryPointMonitor');
goog.provide('goog.debug.entryPointRegistry');

goog.require('goog.asserts');



/**
 * @interface
 */
goog.debug.EntryPointMonitor = function() {};


/**
 * Instruments a function.
 *
 * @param {!Function} fn A function to instrument.
 * @return {!Function} The instrumented function.
 */
goog.debug.EntryPointMonitor.prototype.wrap;


/**
 * Try to remove an instrumentation wrapper created by this monitor.
 * If the function passed to unwrap is not a wrapper created by this
 * monitor, then we will do nothing.
 *
 * Notice that some wrappers may not be unwrappable. For example, if other
 * monitors have applied their own wrappers, then it will be impossible to
 * unwrap them because their wrappers will have captured our wrapper.
 *
 * So it is important that entry points are unwrapped in the reverse
 * order that they were wrapped.
 *
 * @param {!Function} fn A function to unwrap.
 * @return {!Function} The unwrapped function, or {@code fn} if it was not
 *     a wrapped function created by this monitor.
 */
goog.debug.EntryPointMonitor.prototype.unwrap;


/**
 * An array of entry point callbacks.
 * @type {!Array<function(!Function)>}
 * @private
 */
goog.debug.entryPointRegistry.refList_ = [];


/**
 * Monitors that should wrap all the entry points.
 * @type {!Array<!goog.debug.EntryPointMonitor>}
 * @private
 */
goog.debug.entryPointRegistry.monitors_ = [];


/**
 * Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
 * Checking this allows the compiler to optimize out the registrations.
 * @type {boolean}
 * @private
 */
goog.debug.entryPointRegistry.monitorsMayExist_ = false;


/**
 * Register an entry point with this module.
 *
 * The entry point will be instrumented when a monitor is passed to
 * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
 * entry point is instrumented immediately.
 *
 * @param {function(!Function)} callback A callback function which is called
 *     with a transforming function to instrument the entry point. The callback
 *     is responsible for wrapping the relevant entry point with the
 *     transforming function.
 */
goog.debug.entryPointRegistry.register = function(callback) {
  // Don't use push(), so that this can be compiled out.
  goog.debug.entryPointRegistry
      .refList_[goog.debug.entryPointRegistry.refList_.length] = callback;
  // If no one calls monitorAll, this can be compiled out.
  if (goog.debug.entryPointRegistry.monitorsMayExist_) {
    var monitors = goog.debug.entryPointRegistry.monitors_;
    for (var i = 0; i < monitors.length; i++) {
      callback(goog.bind(monitors[i].wrap, monitors[i]));
    }
  }
};


/**
 * Configures a monitor to wrap all entry points.
 *
 * Entry points that have already been registered are immediately wrapped by
 * the monitor. When an entry point is registered in the future, it will also
 * be wrapped by the monitor when it is registered.
 *
 * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
 */
goog.debug.entryPointRegistry.monitorAll = function(monitor) {
  goog.debug.entryPointRegistry.monitorsMayExist_ = true;
  var transformer = goog.bind(monitor.wrap, monitor);
  for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
    goog.debug.entryPointRegistry.refList_[i](transformer);
  }
  goog.debug.entryPointRegistry.monitors_.push(monitor);
};


/**
 * Try to unmonitor all the entry points that have already been registered. If
 * an entry point is registered in the future, it will not be wrapped by the
 * monitor when it is registered. Note that this may fail if the entry points
 * have additional wrapping.
 *
 * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
 *     the entry points.
 * @throws {Error} If the monitor is not the most recently configured monitor.
 */
goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
  var monitors = goog.debug.entryPointRegistry.monitors_;
  goog.asserts.assert(
      monitor == monitors[monitors.length - 1],
      'Only the most recent monitor can be unwrapped.');
  var transformer = goog.bind(monitor.unwrap, monitor);
  for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
    goog.debug.entryPointRegistry.refList_[i](transformer);
  }
  monitors.length--;
};

// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Defines the goog.dom.TagName enum.  This enumerates
 * all HTML tag names specified in either the the W3C HTML 4.01 index of
 * elements or the HTML5 draft specification.
 *
 * References:
 * http://www.w3.org/TR/html401/index/elements.html
 * http://dev.w3.org/html5/spec/section-index.html
 *
 */
goog.provide('goog.dom.TagName');


/**
 * Enum of all html tag names specified by the W3C HTML4.01 and HTML5
 * specifications.
 * @enum {string}
 */
goog.dom.TagName = {
  A: 'A',
  ABBR: 'ABBR',
  ACRONYM: 'ACRONYM',
  ADDRESS: 'ADDRESS',
  APPLET: 'APPLET',
  AREA: 'AREA',
  ARTICLE: 'ARTICLE',
  ASIDE: 'ASIDE',
  AUDIO: 'AUDIO',
  B: 'B',
  BASE: 'BASE',
  BASEFONT: 'BASEFONT',
  BDI: 'BDI',
  BDO: 'BDO',
  BIG: 'BIG',
  BLOCKQUOTE: 'BLOCKQUOTE',
  BODY: 'BODY',
  BR: 'BR',
  BUTTON: 'BUTTON',
  CANVAS: 'CANVAS',
  CAPTION: 'CAPTION',
  CENTER: 'CENTER',
  CITE: 'CITE',
  CODE: 'CODE',
  COL: 'COL',
  COLGROUP: 'COLGROUP',
  COMMAND: 'COMMAND',
  DATA: 'DATA',
  DATALIST: 'DATALIST',
  DD: 'DD',
  DEL: 'DEL',
  DETAILS: 'DETAILS',
  DFN: 'DFN',
  DIALOG: 'DIALOG',
  DIR: 'DIR',
  DIV: 'DIV',
  DL: 'DL',
  DT: 'DT',
  EM: 'EM',
  EMBED: 'EMBED',
  FIELDSET: 'FIELDSET',
  FIGCAPTION: 'FIGCAPTION',
  FIGURE: 'FIGURE',
  FONT: 'FONT',
  FOOTER: 'FOOTER',
  FORM: 'FORM',
  FRAME: 'FRAME',
  FRAMESET: 'FRAMESET',
  H1: 'H1',
  H2: 'H2',
  H3: 'H3',
  H4: 'H4',
  H5: 'H5',
  H6: 'H6',
  HEAD: 'HEAD',
  HEADER: 'HEADER',
  HGROUP: 'HGROUP',
  HR: 'HR',
  HTML: 'HTML',
  I: 'I',
  IFRAME: 'IFRAME',
  IMG: 'IMG',
  INPUT: 'INPUT',
  INS: 'INS',
  ISINDEX: 'ISINDEX',
  KBD: 'KBD',
  KEYGEN: 'KEYGEN',
  LABEL: 'LABEL',
  LEGEND: 'LEGEND',
  LI: 'LI',
  LINK: 'LINK',
  MAP: 'MAP',
  MARK: 'MARK',
  MATH: 'MATH',
  MENU: 'MENU',
  META: 'META',
  METER: 'METER',
  NAV: 'NAV',
  NOFRAMES: 'NOFRAMES',
  NOSCRIPT: 'NOSCRIPT',
  OBJECT: 'OBJECT',
  OL: 'OL',
  OPTGROUP: 'OPTGROUP',
  OPTION: 'OPTION',
  OUTPUT: 'OUTPUT',
  P: 'P',
  PARAM: 'PARAM',
  PRE: 'PRE',
  PROGRESS: 'PROGRESS',
  Q: 'Q',
  RP: 'RP',
  RT: 'RT',
  RUBY: 'RUBY',
  S: 'S',
  SAMP: 'SAMP',
  SCRIPT: 'SCRIPT',
  SECTION: 'SECTION',
  SELECT: 'SELECT',
  SMALL: 'SMALL',
  SOURCE: 'SOURCE',
  SPAN: 'SPAN',
  STRIKE: 'STRIKE',
  STRONG: 'STRONG',
  STYLE: 'STYLE',
  SUB: 'SUB',
  SUMMARY: 'SUMMARY',
  SUP: 'SUP',
  SVG: 'SVG',
  TABLE: 'TABLE',
  TBODY: 'TBODY',
  TD: 'TD',
  TEMPLATE: 'TEMPLATE',
  TEXTAREA: 'TEXTAREA',
  TFOOT: 'TFOOT',
  TH: 'TH',
  THEAD: 'THEAD',
  TIME: 'TIME',
  TITLE: 'TITLE',
  TR: 'TR',
  TRACK: 'TRACK',
  TT: 'TT',
  U: 'U',
  UL: 'UL',
  VAR: 'VAR',
  VIDEO: 'VIDEO',
  WBR: 'WBR'
};

// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Utilities for creating functions. Loosely inspired by the
 * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8.
 *
 * @author nicksantos@google.com (Nick Santos)
 */


goog.provide('goog.functions');


/**
 * Creates a function that always returns the same value.
 * @param {T} retValue The value to return.
 * @return {function():T} The new function.
 * @template T
 */
goog.functions.constant = function(retValue) {
  return function() { return retValue; };
};


/**
 * Always returns false.
 * @type {function(...): boolean}
 */
goog.functions.FALSE = goog.functions.constant(false);


/**
 * Always returns true.
 * @type {function(...): boolean}
 */
goog.functions.TRUE = goog.functions.constant(true);


/**
 * Always returns NULL.
 * @type {function(...): null}
 */
goog.functions.NULL = goog.functions.constant(null);


/**
 * A simple function that returns the first argument of whatever is passed
 * into it.
 * @param {T=} opt_returnValue The single value that will be returned.
 * @param {...*} var_args Optional trailing arguments. These are ignored.
 * @return {T} The first argument passed in, or undefined if nothing was passed.
 * @template T
 */
goog.functions.identity = function(opt_returnValue, var_args) {
  return opt_returnValue;
};


/**
 * Creates a function that always throws an error with the given message.
 * @param {string} message The error message.
 * @return {!Function} The error-throwing function.
 */
goog.functions.error = function(message) {
  return function() { throw Error(message); };
};


/**
 * Creates a function that throws the given object.
 * @param {*} err An object to be thrown.
 * @return {!Function} The error-throwing function.
 */
goog.functions.fail = function(err) {
  return function() { throw err; };
};


/**
 * Given a function, create a function that keeps opt_numArgs arguments and
 * silently discards all additional arguments.
 * @param {Function} f The original function.
 * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
 * @return {!Function} A version of f that only keeps the first opt_numArgs
 *     arguments.
 */
goog.functions.lock = function(f, opt_numArgs) {
  opt_numArgs = opt_numArgs || 0;
  return function() {
    return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs));
  };
};


/**
 * Creates a function that returns its nth argument.
 * @param {number} n The position of the return argument.
 * @return {!Function} A new function.
 */
goog.functions.nth = function(n) {
  return function() { return arguments[n]; };
};


/**
 * Like goog.partial(), except that arguments are added after arguments to the
 * returned function.
 *
 * Usage:
 * function f(arg1, arg2, arg3, arg4) { ... }
 * var g = goog.functions.partialRight(f, arg3, arg4);
 * g(arg1, arg2);
 *
 * @param {!Function} fn A function to partially apply.
 * @param {...*} var_args Additional arguments that are partially applied to fn
 *     at the end.
 * @return {!Function} A partially-applied form of the function goog.partial()
 *     was invoked as a method of.
 */
goog.functions.partialRight = function(fn, var_args) {
  var rightArgs = Array.prototype.slice.call(arguments, 1);
  return function() {
    var newArgs = Array.prototype.slice.call(arguments);
    newArgs.push.apply(newArgs, rightArgs);
    return fn.apply(this, newArgs);
  };
};


/**
 * Given a function, create a new function that swallows its return value
 * and replaces it with a new one.
 * @param {Function} f A function.
 * @param {T} retValue A new return value.
 * @return {function(...?):T} A new function.
 * @template T
 */
goog.functions.withReturnValue = function(f, retValue) {
  return goog.functions.sequence(f, goog.functions.constant(retValue));
};


/**
 * Creates a function that returns whether its argument equals the given value.
 *
 * Example:
 * var key = goog.object.findKey(obj, goog.functions.equalTo('needle'));
 *
 * @param {*} value The value to compare to.
 * @param {boolean=} opt_useLooseComparison Whether to use a loose (==)
 *     comparison rather than a strict (===) one. Defaults to false.
 * @return {function(*):boolean} The new function.
 */
goog.functions.equalTo = function(value, opt_useLooseComparison) {
  return function(other) {
    return opt_useLooseComparison ? (value == other) : (value === other);
  };
};


/**
 * Creates the composition of the functions passed in.
 * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
 * @param {function(...?):T} fn The final function.
 * @param {...Function} var_args A list of functions.
 * @return {function(...?):T} The composition of all inputs.
 * @template T
 */
goog.functions.compose = function(fn, var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    var result;
    if (length) {
      result = functions[length - 1].apply(this, arguments);
    }

    for (var i = length - 2; i >= 0; i--) {
      result = functions[i].call(this, result);
    }
    return result;
  };
};


/**
 * Creates a function that calls the functions passed in in sequence, and
 * returns the value of the last function. For example,
 * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
 * @param {...Function} var_args A list of functions.
 * @return {!Function} A function that calls all inputs in sequence.
 */
goog.functions.sequence = function(var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    var result;
    for (var i = 0; i < length; i++) {
      result = functions[i].apply(this, arguments);
    }
    return result;
  };
};


/**
 * Creates a function that returns true if each of its components evaluates
 * to true. The components are evaluated in order, and the evaluation will be
 * short-circuited as soon as a function returns false.
 * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
 * @param {...Function} var_args A list of functions.
 * @return {function(...?):boolean} A function that ANDs its component
 *      functions.
 */
goog.functions.and = function(var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    for (var i = 0; i < length; i++) {
      if (!functions[i].apply(this, arguments)) {
        return false;
      }
    }
    return true;
  };
};


/**
 * Creates a function that returns true if any of its components evaluates
 * to true. The components are evaluated in order, and the evaluation will be
 * short-circuited as soon as a function returns true.
 * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
 * @param {...Function} var_args A list of functions.
 * @return {function(...?):boolean} A function that ORs its component
 *    functions.
 */
goog.functions.or = function(var_args) {
  var functions = arguments;
  var length = functions.length;
  return function() {
    for (var i = 0; i < length; i++) {
      if (functions[i].apply(this, arguments)) {
        return true;
      }
    }
    return false;
  };
};


/**
 * Creates a function that returns the Boolean opposite of a provided function.
 * For example, (goog.functions.not(f))(x) is equivalent to !f(x).
 * @param {!Function} f The original function.
 * @return {function(...?):boolean} A function that delegates to f and returns
 * opposite.
 */
goog.functions.not = function(f) {
  return function() { return !f.apply(this, arguments); };
};


/**
 * Generic factory function to construct an object given the constructor
 * and the arguments. Intended to be bound to create object factories.
 *
 * Example:
 *
 * var factory = goog.partial(goog.functions.create, Class);
 *
 * @param {function(new:T, ...)} constructor The constructor for the Object.
 * @param {...*} var_args The arguments to be passed to the constructor.
 * @return {T} A new instance of the class given in {@code constructor}.
 * @template T
 */
goog.functions.create = function(constructor, var_args) {
  /**
   * @constructor
   * @final
   */
  var temp = function() {};
  temp.prototype = constructor.prototype;

  // obj will have constructor's prototype in its chain and
  // 'obj instanceof constructor' will be true.
  var obj = new temp();

  // obj is initialized by constructor.
  // arguments is only array-like so lacks shift(), but can be used with
  // the Array prototype function.
  constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
  return obj;
};


/**
 * @define {boolean} Whether the return value cache should be used.
 *    This should only be used to disable caches when testing.
 */
goog.define('goog.functions.CACHE_RETURN_VALUE', true);


/**
 * Gives a wrapper function that caches the return value of a parameterless
 * function when first called.
 *
 * When called for the first time, the given function is called and its
 * return value is cached (thus this is only appropriate for idempotent
 * functions).  Subsequent calls will return the cached return value. This
 * allows the evaluation of expensive functions to be delayed until first used.
 *
 * To cache the return values of functions with parameters, see goog.memoize.
 *
 * @param {!function():T} fn A function to lazily evaluate.
 * @return {!function():T} A wrapped version the function.
 * @template T
 */
goog.functions.cacheReturnValue = function(fn) {
  var called = false;
  var value;

  return function() {
    if (!goog.functions.CACHE_RETURN_VALUE) {
      return fn();
    }

    if (!called) {
      value = fn();
      called = true;
    }

    return value;
  };
};


/**
 * Wraps a function to allow it to be called, at most, once. All
 * additional calls are no-ops.
 *
 * This is particularly useful for initialization functions
 * that should be called, at most, once.
 *
 * @param {function():*} f Function to call.
 * @return {function():undefined} Wrapped function.
 */
goog.functions.once = function(f) {
  // Keep a reference to the function that we null out when we're done with
  // it -- that way, the function can be GC'd when we're done with it.
  var inner = f;
  return function() {
    if (inner) {
      var tmp = inner;
      inner = null;
      tmp();
    }
  };
};


/**
 * Wraps a function to allow it to be called, at most, once for each sequence of
 * calls fired repeatedly so long as they are fired less than a specified
 * interval apart (in milliseconds). Whether it receives one signal or multiple,
 * it will always wait until a full interval has elapsed since the last signal
 * before performing the action, passing the arguments from the last call of the
 * debouncing decorator into the decorated function.
 *
 * This is particularly useful for bulking up repeated user actions (e.g. only
 * refreshing a view once a user finishes typing rather than updating with every
 * keystroke). For more stateful debouncing with support for pausing, resuming,
 * and canceling debounced actions, use {@code goog.async.Debouncer}.
 *
 * @param {function(this:SCOPE, ...?)} f Function to call.
 * @param {number} interval Interval over which to debounce. The function will
 *     only be called after the full interval has elapsed since the last call.
 * @param {SCOPE=} opt_scope Object in whose scope to call the function.
 * @return {function(...?): undefined} Wrapped function.
 * @template SCOPE
 */
goog.functions.debounce = function(f, interval, opt_scope) {
  if (opt_scope) {
    f = goog.bind(f, opt_scope);
  }
  var timeout = null;
  return /** @type {function(...?)} */ (function(var_args) {
    goog.global.clearTimeout(timeout);
    var args = arguments;
    timeout =
        goog.global.setTimeout(function() { f.apply(null, args); }, interval);
  });
};


/**
 * Wraps a function to allow it to be called, at most, once per interval
 * (specified in milliseconds). If it is called multiple times while it is
 * waiting, it will only perform the action once at the end of the interval,
 * passing the arguments from the last call of the throttling decorator into the
 * decorated function.
 *
 * This is particularly useful for limiting repeated user requests (e.g.
 * preventing a user from spamming a server with frequent view refreshes). For
 * more stateful throttling with support for pausing, resuming, and canceling
 * throttled actions, use {@code goog.async.Throttle}.
 *
 * @param {function(this:SCOPE, ...?)} f Function to call.
 * @param {number} interval Interval over which to throttle. The function can
 *     only be called once per interval.
 * @param {SCOPE=} opt_scope Object in whose scope to call the function.
 * @return {function(...?): undefined} Wrapped function.
 * @template SCOPE
 */
goog.functions.throttle = function(f, interval, opt_scope) {
  if (opt_scope) {
    f = goog.bind(f, opt_scope);
  }
  var timeout = null;
  var shouldFire = false;
  var args = [];

  var handleTimeout = function() {
    timeout = null;
    if (shouldFire) {
      shouldFire = false;
      fire();
    }
  };

  var fire = function() {
    timeout = goog.global.setTimeout(handleTimeout, interval);
    f.apply(null, args);
  };

  return /** @type {function(...?)} */ (function(var_args) {
    args = arguments;
    if (!timeout) {
      fire();
    } else {
      shouldFire = true;
    }
  });
};

// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Provides a function to schedule running a function as soon
 * as possible after the current JS execution stops and yields to the event
 * loop.
 *
 */

goog.provide('goog.async.nextTick');
goog.provide('goog.async.throwException');

goog.require('goog.debug.entryPointRegistry');
goog.require('goog.dom.TagName');
goog.require('goog.functions');
goog.require('goog.labs.userAgent.browser');
goog.require('goog.labs.userAgent.engine');


/**
 * Throw an item without interrupting the current execution context.  For
 * example, if processing a group of items in a loop, sometimes it is useful
 * to report an error while still allowing the rest of the batch to be
 * processed.
 * @param {*} exception
 */
goog.async.throwException = function(exception) {
  // Each throw needs to be in its own context.
  goog.global.setTimeout(function() { throw exception; }, 0);
};


/**
 * Fires the provided callbacks as soon as possible after the current JS
 * execution context. setTimeout(…, 0) takes at least 4ms when called from
 * within another setTimeout(…, 0) for legacy reasons.
 *
 * This will not schedule the callback as a microtask (i.e. a task that can
 * preempt user input or networking callbacks). It is meant to emulate what
 * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
 * behavior, use {@see goog.Promise} instead.
 *
 * @param {function(this:SCOPE)} callback Callback function to fire as soon as
 *     possible.
 * @param {SCOPE=} opt_context Object in whose scope to call the listener.
 * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
 *     ensures correctness at the cost of speed. See comments for details.
 * @template SCOPE
 */
goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
  var cb = callback;
  if (opt_context) {
    cb = goog.bind(callback, opt_context);
  }
  cb = goog.async.nextTick.wrapCallback_(cb);
  // Note we do allow callers to also request setImmediate if they are willing
  // to accept the possible tradeoffs of incorrectness in exchange for speed.
  // The IE fallback of readystate change is much slower. See useSetImmediate_
  // for details.
  if (goog.isFunction(goog.global.setImmediate) &&
      (opt_useSetImmediate || goog.async.nextTick.useSetImmediate_())) {
    goog.global.setImmediate(cb);
    return;
  }

  // Look for and cache the custom fallback version of setImmediate.
  if (!goog.async.nextTick.setImmediate_) {
    goog.async.nextTick.setImmediate_ =
        goog.async.nextTick.getSetImmediateEmulator_();
  }
  goog.async.nextTick.setImmediate_(cb);
};


/**
 * Returns whether should use setImmediate implementation currently on window.
 *
 * window.setImmediate was introduced and currently only supported by IE10+,
 * but due to a bug in the implementation it is not guaranteed that
 * setImmediate is faster than setTimeout nor that setImmediate N is before
 * setImmediate N+1. That is why we do not use the native version if
 * available. We do, however, call setImmediate if it is a non-native function
 * because that indicates that it has been replaced by goog.testing.MockClock
 * which we do want to support.
 * See
 * http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
 *
 * @return {boolean} Whether to use the implementation of setImmediate defined
 *     on Window.
 * @private
 */
goog.async.nextTick.useSetImmediate_ = function() {
  // Not a browser environment.
  if (!goog.global.Window || !goog.global.Window.prototype) {
    return true;
  }

  // MS Edge has window.setImmediate natively, but it's not on Window.prototype.
  // Also, there's no clean way to detect if the goog.global.setImmediate has
  // been replaced by mockClock as its replacement also shows up as "[native
  // code]" when using toString. Therefore, just always use
  // goog.global.setImmediate for Edge. It's unclear if it suffers the same
  // issues as IE10/11, but based on
  // https://dev.modern.ie/testdrive/demos/setimmediatesorting/
  // it seems they've been working to ensure it's WAI.
  if (goog.labs.userAgent.browser.isEdge() ||
      goog.global.Window.prototype.setImmediate != goog.global.setImmediate) {
    // Something redefined setImmediate in which case we decide to use it (This
    // is so that we use the mockClock setImmediate).
    return true;
  }

  return false;
};


/**
 * Cache for the setImmediate implementation.
 * @type {function(function())}
 * @private
 */
goog.async.nextTick.setImmediate_;


/**
 * Determines the best possible implementation to run a function as soon as
 * the JS event loop is idle.
 * @return {function(function())} The "setImmediate" implementation.
 * @private
 */
goog.async.nextTick.getSetImmediateEmulator_ = function() {
  // Create a private message channel and use it to postMessage empty messages
  // to ourselves.
  var Channel = goog.global['MessageChannel'];
  // If MessageChannel is not available and we are in a browser, implement
  // an iframe based polyfill in browsers that have postMessage and
  // document.addEventListener. The latter excludes IE8 because it has a
  // synchronous postMessage implementation.
  if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
      window.postMessage && window.addEventListener &&
      // Presto (The old pre-blink Opera engine) has problems with iframes
      // and contentWindow.
      !goog.labs.userAgent.engine.isPresto()) {
    /** @constructor */
    Channel = function() {
      // Make an empty, invisible iframe.
      var iframe = /** @type {!HTMLIFrameElement} */ (
          document.createElement(goog.dom.TagName.IFRAME));
      iframe.style.display = 'none';
      iframe.src = '';
      document.documentElement.appendChild(iframe);
      var win = iframe.contentWindow;
      var doc = win.document;
      doc.open();
      doc.write('');
      doc.close();
      // Do not post anything sensitive over this channel, as the workaround for
      // pages with file: origin could allow that information to be modified or
      // intercepted.
      var message = 'callImmediate' + Math.random();
      // The same origin policy rejects attempts to postMessage from file: urls
      // unless the origin is '*'.
      // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
      var origin = win.location.protocol == 'file:' ?
          '*' :
          win.location.protocol + '//' + win.location.host;
      var onmessage = goog.bind(function(e) {
        // Validate origin and message to make sure that this message was
        // intended for us. If the origin is set to '*' (see above) only the
        // message needs to match since, for example, '*' != 'file://'. Allowing
        // the wildcard is ok, as we are not concerned with security here.
        if ((origin != '*' && e.origin != origin) || e.data != message) {
          return;
        }
        this['port1'].onmessage();
      }, this);
      win.addEventListener('message', onmessage, false);
      this['port1'] = {};
      this['port2'] = {
        postMessage: function() { win.postMessage(message, origin); }
      };
    };
  }
  if (typeof Channel !== 'undefined' && (!goog.labs.userAgent.browser.isIE())) {
    // Exclude all of IE due to
    // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
    // which allows starving postMessage with a busy setTimeout loop.
    // This currently affects IE10 and IE11 which would otherwise be able
    // to use the postMessage based fallbacks.
    var channel = new Channel();
    // Use a fifo linked list to call callbacks in the right order.
    var head = {};
    var tail = head;
    channel['port1'].onmessage = function() {
      if (goog.isDef(head.next)) {
        head = head.next;
        var cb = head.cb;
        head.cb = null;
        cb();
      }
    };
    return function(cb) {
      tail.next = {cb: cb};
      tail = tail.next;
      channel['port2'].postMessage(0);
    };
  }
  // Implementation for IE6 to IE10: Script elements fire an asynchronous
  // onreadystatechange event when inserted into the DOM.
  if (typeof document !== 'undefined' &&
      'onreadystatechange' in document.createElement(goog.dom.TagName.SCRIPT)) {
    return function(cb) {
      var script = document.createElement(goog.dom.TagName.SCRIPT);
      script.onreadystatechange = function() {
        // Clean up and call the callback.
        script.onreadystatechange = null;
        script.parentNode.removeChild(script);
        script = null;
        cb();
        cb = null;
      };
      document.documentElement.appendChild(script);
    };
  }
  // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
  // or more.
  // NOTE(user): This fallback is used for IE11.
  return function(cb) { goog.global.setTimeout(cb, 0); };
};


/**
 * Helper function that is overrided to protect callbacks with entry point
 * monitor if the application monitors entry points.
 * @param {function()} callback Callback function to fire as soon as possible.
 * @return {function()} The wrapped callback.
 * @private
 */
goog.async.nextTick.wrapCallback_ = goog.functions.identity;


// Register the callback function as an entry point, so that it can be
// monitored for exception handling, etc. This has to be done in this file
// since it requires special code to handle all browsers.
goog.debug.entryPointRegistry.register(
    /**
     * @param {function(!Function): !Function} transformer The transforming
     *     function.
     */
    function(transformer) { goog.async.nextTick.wrapCallback_ = transformer; });

goog.provide('ol.webgl');


/** Constants taken from goog.webgl
 */


/**
 * @const
 * @type {number}
 */
ol.webgl.ONE = 1;


/**
 * @const
 * @type {number}
 */
ol.webgl.SRC_ALPHA = 0x0302;


/**
 * @const
 * @type {number}
 */
ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0;


/**
 * @const
 * @type {number}
 */
ol.webgl.COLOR_BUFFER_BIT = 0x00004000;


/**
 * @const
 * @type {number}
 */
ol.webgl.TRIANGLES = 0x0004;


/**
 * @const
 * @type {number}
 */
ol.webgl.TRIANGLE_STRIP = 0x0005;


/**
 * @const
 * @type {number}
 */
ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303;


/**
 * @const
 * @type {number}
 */
ol.webgl.ARRAY_BUFFER = 0x8892;


/**
 * @const
 * @type {number}
 */
ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893;


/**
 * @const
 * @type {number}
 */
ol.webgl.STREAM_DRAW = 0x88E0;


/**
 * @const
 * @type {number}
 */
ol.webgl.STATIC_DRAW = 0x88E4;


/**
 * @const
 * @type {number}
 */
ol.webgl.DYNAMIC_DRAW = 0x88E8;


/**
 * @const
 * @type {number}
 */
ol.webgl.CULL_FACE = 0x0B44;


/**
 * @const
 * @type {number}
 */
ol.webgl.BLEND = 0x0BE2;


/**
 * @const
 * @type {number}
 */
ol.webgl.STENCIL_TEST = 0x0B90;


/**
 * @const
 * @type {number}
 */
ol.webgl.DEPTH_TEST = 0x0B71;


/**
 * @const
 * @type {number}
 */
ol.webgl.SCISSOR_TEST = 0x0C11;


/**
 * @const
 * @type {number}
 */
ol.webgl.UNSIGNED_BYTE = 0x1401;


/**
 * @const
 * @type {number}
 */
ol.webgl.UNSIGNED_SHORT = 0x1403;


/**
 * @const
 * @type {number}
 */
ol.webgl.UNSIGNED_INT = 0x1405;


/**
 * @const
 * @type {number}
 */
ol.webgl.FLOAT = 0x1406;


/**
 * @const
 * @type {number}
 */
ol.webgl.RGBA = 0x1908;


/**
 * @const
 * @type {number}
 */
ol.webgl.FRAGMENT_SHADER = 0x8B30;


/**
 * @const
 * @type {number}
 */
ol.webgl.VERTEX_SHADER = 0x8B31;


/**
 * @const
 * @type {number}
 */
ol.webgl.LINK_STATUS = 0x8B82;


/**
 * @const
 * @type {number}
 */
ol.webgl.LINEAR = 0x2601;


/**
 * @const
 * @type {number}
 */
ol.webgl.TEXTURE_MAG_FILTER = 0x2800;


/**
 * @const
 * @type {number}
 */
ol.webgl.TEXTURE_MIN_FILTER = 0x2801;


/**
 * @const
 * @type {number}
 */
ol.webgl.TEXTURE_WRAP_S = 0x2802;


/**
 * @const
 * @type {number}
 */
ol.webgl.TEXTURE_WRAP_T = 0x2803;


/**
 * @const
 * @type {number}
 */
ol.webgl.TEXTURE_2D = 0x0DE1;


/**
 * @const
 * @type {number}
 */
ol.webgl.TEXTURE0 = 0x84C0;


/**
 * @const
 * @type {number}
 */
ol.webgl.CLAMP_TO_EDGE = 0x812F;


/**
 * @const
 * @type {number}
 */
ol.webgl.COMPILE_STATUS = 0x8B81;


/**
 * @const
 * @type {number}
 */
ol.webgl.FRAMEBUFFER = 0x8D40;


/** end of goog.webgl constants
 */


/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.webgl.CONTEXT_IDS_ = [
  'experimental-webgl',
  'webgl',
  'webkit-3d',
  'moz-webgl'
];


/**
 * @param {HTMLCanvasElement} canvas Canvas.
 * @param {Object=} opt_attributes Attributes.
 * @return {WebGLRenderingContext} WebGL rendering context.
 */
ol.webgl.getContext = function(canvas, opt_attributes) {
  var context, i, ii = ol.webgl.CONTEXT_IDS_.length;
  for (i = 0; i < ii; ++i) {
    try {
      context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes);
      if (context) {
        return /** @type {!WebGLRenderingContext} */ (context);
      }
    } catch (e) {
      // pass
    }
  }
  return null;
};

goog.provide('ol.has');

goog.require('ol');
goog.require('ol.dom');
goog.require('ol.webgl');


var ua = typeof navigator !== 'undefined' ?
    navigator.userAgent.toLowerCase() : '';

/**
 * User agent string says we are dealing with Firefox as browser.
 * @type {boolean}
 */
ol.has.FIREFOX = ua.indexOf('firefox') !== -1;

/**
 * User agent string says we are dealing with Safari as browser.
 * @type {boolean}
 */
ol.has.SAFARI = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') === -1;

/**
 * User agent string says we are dealing with a Mac as platform.
 * @type {boolean}
 */
ol.has.MAC = ua.indexOf('macintosh') !== -1;


/**
 * The ratio between physical pixels and device-independent pixels
 * (dips) on the device (`window.devicePixelRatio`).
 * @const
 * @type {number}
 * @api stable
 */
ol.has.DEVICE_PIXEL_RATIO = ol.global.devicePixelRatio || 1;


/**
 * True if the browser's Canvas implementation implements {get,set}LineDash.
 * @type {boolean}
 */
ol.has.CANVAS_LINE_DASH = false;


/**
 * True if both the library and browser support Canvas.  Always `false`
 * if `ol.ENABLE_CANVAS` is set to `false` at compile time.
 * @const
 * @type {boolean}
 * @api stable
 */
ol.has.CANVAS = ol.ENABLE_CANVAS && (
    /**
     * @return {boolean} Canvas supported.
     */
    function() {
      if (!('HTMLCanvasElement' in ol.global)) {
        return false;
      }
      try {
        var context = ol.dom.createCanvasContext2D();
        if (!context) {
          return false;
        } else {
          if (context.setLineDash !== undefined) {
            ol.has.CANVAS_LINE_DASH = true;
          }
          return true;
        }
      } catch (e) {
        return false;
      }
    })();


/**
 * Indicates if DeviceOrientation is supported in the user's browser.
 * @const
 * @type {boolean}
 * @api stable
 */
ol.has.DEVICE_ORIENTATION = 'DeviceOrientationEvent' in ol.global;


/**
 * True if `ol.ENABLE_DOM` is set to `true` at compile time.
 * @const
 * @type {boolean}
 */
ol.has.DOM = ol.ENABLE_DOM;


/**
 * Is HTML5 geolocation supported in the current browser?
 * @const
 * @type {boolean}
 * @api stable
 */
ol.has.GEOLOCATION = 'geolocation' in ol.global.navigator;


/**
 * True if browser supports touch events.
 * @const
 * @type {boolean}
 * @api stable
 */
ol.has.TOUCH = ol.ASSUME_TOUCH || 'ontouchstart' in ol.global;


/**
 * True if browser supports pointer events.
 * @const
 * @type {boolean}
 */
ol.has.POINTER = 'PointerEvent' in ol.global;


/**
 * True if browser supports ms pointer events (IE 10).
 * @const
 * @type {boolean}
 */
ol.has.MSPOINTER = !!(ol.global.navigator.msPointerEnabled);


/**
 * True if both OpenLayers and browser support WebGL.  Always `false`
 * if `ol.ENABLE_WEBGL` is set to `false` at compile time.
 * @const
 * @type {boolean}
 * @api stable
 */
ol.has.WEBGL;


(function() {
  if (ol.ENABLE_WEBGL) {
    var hasWebGL = false;
    var textureSize;
    var /** @type {Array.<string>} */ extensions = [];

    if ('WebGLRenderingContext' in ol.global) {
      try {
        var canvas = /** @type {HTMLCanvasElement} */
            (document.createElement('CANVAS'));
        var gl = ol.webgl.getContext(canvas, {
          failIfMajorPerformanceCaveat: true
        });
        if (gl) {
          hasWebGL = true;
          textureSize = /** @type {number} */
              (gl.getParameter(gl.MAX_TEXTURE_SIZE));
          extensions = gl.getSupportedExtensions();
        }
      } catch (e) {
        // pass
      }
    }
    ol.has.WEBGL = hasWebGL;
    ol.WEBGL_EXTENSIONS = extensions;
    ol.WEBGL_MAX_TEXTURE_SIZE = textureSize;
  }
})();

goog.provide('ol.pointer.EventSource');


/**
 * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
 * @param {!Object.<string, function(Event)>} mapping Event
 *     mapping.
 * @constructor
 */
ol.pointer.EventSource = function(dispatcher, mapping) {
  /**
   * @type {ol.pointer.PointerEventHandler}
   */
  this.dispatcher = dispatcher;

  /**
   * @private
   * @const
   * @type {!Object.<string, function(Event)>}
   */
  this.mapping_ = mapping;
};


/**
 * List of events supported by this source.
 * @return {Array.<string>} Event names
 */
ol.pointer.EventSource.prototype.getEvents = function() {
  return Object.keys(this.mapping_);
};


/**
 * Returns a mapping between the supported event types and
 * the handlers that should handle an event.
 * @return {Object.<string, function(Event)>}
 *         Event/Handler mapping
 */
ol.pointer.EventSource.prototype.getMapping = function() {
  return this.mapping_;
};


/**
 * Returns the handler that should handle a given event type.
 * @param {string} eventType The event type.
 * @return {function(Event)} Handler
 */
ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
  return this.mapping_[eventType];
};

// Based on https://github.com/Polymer/PointerEvents

// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.pointer.MouseSource');

goog.require('ol');
goog.require('ol.pointer.EventSource');


/**
 * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
 * @constructor
 * @extends {ol.pointer.EventSource}
 */
ol.pointer.MouseSource = function(dispatcher) {
  var mapping = {
    'mousedown': this.mousedown,
    'mousemove': this.mousemove,
    'mouseup': this.mouseup,
    'mouseover': this.mouseover,
    'mouseout': this.mouseout
  };
  ol.pointer.EventSource.call(this, dispatcher, mapping);

  /**
   * @const
   * @type {!Object.<string, Event|Object>}
   */
  this.pointerMap = dispatcher.pointerMap;

  /**
   * @const
   * @type {Array.<ol.Pixel>}
   */
  this.lastTouches = [];
};
ol.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);


/**
 * @const
 * @type {number}
 */
ol.pointer.MouseSource.POINTER_ID = 1;


/**
 * @const
 * @type {string}
 */
ol.pointer.MouseSource.POINTER_TYPE = 'mouse';


/**
 * Radius around touchend that swallows mouse events.
 *
 * @const
 * @type {number}
 */
ol.pointer.MouseSource.DEDUP_DIST = 25;


/**
 * Detect if a mouse event was simulated from a touch by
 * checking if previously there was a touch event at the
 * same position.
 *
 * FIXME - Known problem with the native Android browser on
 * Samsung GT-I9100 (Android 4.1.2):
 * In case the page is scrolled, this function does not work
 * correctly when a canvas is used (WebGL or canvas renderer).
 * Mouse listeners on canvas elements (for this browser), create
 * two mouse events: One 'good' and one 'bad' one (on other browsers or
 * when a div is used, there is only one event). For the 'bad' one,
 * clientX/clientY and also pageX/pageY are wrong when the page
 * is scrolled. Because of that, this function can not detect if
 * the events were simulated from a touch event. As result, a
 * pointer event at a wrong position is dispatched, which confuses
 * the map interactions.
 * It is unclear, how one can get the correct position for the event
 * or detect that the positions are invalid.
 *
 * @private
 * @param {Event} inEvent The in event.
 * @return {boolean} True, if the event was generated by a touch.
 */
ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ = function(inEvent) {
  var lts = this.lastTouches;
  var x = inEvent.clientX, y = inEvent.clientY;
  for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
    // simulated mouse events will be swallowed near a primary touchend
    var dx = Math.abs(x - t[0]), dy = Math.abs(y - t[1]);
    if (dx <= ol.pointer.MouseSource.DEDUP_DIST &&
        dy <= ol.pointer.MouseSource.DEDUP_DIST) {
      return true;
    }
  }
  return false;
};


/**
 * Creates a copy of the original event that will be used
 * for the fake pointer event.
 *
 * @param {Event} inEvent The in event.
 * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
 * @return {Object} The copied event.
 */
ol.pointer.MouseSource.prepareEvent = function(inEvent, dispatcher) {
  var e = dispatcher.cloneEvent(inEvent, inEvent);

  // forward mouse preventDefault
  var pd = e.preventDefault;
  e.preventDefault = function() {
    inEvent.preventDefault();
    pd();
  };

  e.pointerId = ol.pointer.MouseSource.POINTER_ID;
  e.isPrimary = true;
  e.pointerType = ol.pointer.MouseSource.POINTER_TYPE;

  return e;
};


/**
 * Handler for `mousedown`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
  if (!this.isEventSimulatedFromTouch_(inEvent)) {
    // TODO(dfreedman) workaround for some elements not sending mouseup
    // http://crbug/149091
    if (ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap) {
      this.cancel(inEvent);
    }
    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
    this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()] = inEvent;
    this.dispatcher.down(e, inEvent);
  }
};


/**
 * Handler for `mousemove`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MouseSource.prototype.mousemove = function(inEvent) {
  if (!this.isEventSimulatedFromTouch_(inEvent)) {
    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
    this.dispatcher.move(e, inEvent);
  }
};


/**
 * Handler for `mouseup`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
  if (!this.isEventSimulatedFromTouch_(inEvent)) {
    var p = this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];

    if (p && p.button === inEvent.button) {
      var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
      this.dispatcher.up(e, inEvent);
      this.cleanupMouse();
    }
  }
};


/**
 * Handler for `mouseover`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MouseSource.prototype.mouseover = function(inEvent) {
  if (!this.isEventSimulatedFromTouch_(inEvent)) {
    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
    this.dispatcher.enterOver(e, inEvent);
  }
};


/**
 * Handler for `mouseout`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MouseSource.prototype.mouseout = function(inEvent) {
  if (!this.isEventSimulatedFromTouch_(inEvent)) {
    var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
    this.dispatcher.leaveOut(e, inEvent);
  }
};


/**
 * Dispatches a `pointercancel` event.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
  var e = ol.pointer.MouseSource.prepareEvent(inEvent, this.dispatcher);
  this.dispatcher.cancel(e, inEvent);
  this.cleanupMouse();
};


/**
 * Remove the mouse from the list of active pointers.
 */
ol.pointer.MouseSource.prototype.cleanupMouse = function() {
  delete this.pointerMap[ol.pointer.MouseSource.POINTER_ID.toString()];
};

// Based on https://github.com/Polymer/PointerEvents

// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.pointer.MsSource');

goog.require('ol');
goog.require('ol.pointer.EventSource');


/**
 * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
 * @constructor
 * @extends {ol.pointer.EventSource}
 */
ol.pointer.MsSource = function(dispatcher) {
  var mapping = {
    'MSPointerDown': this.msPointerDown,
    'MSPointerMove': this.msPointerMove,
    'MSPointerUp': this.msPointerUp,
    'MSPointerOut': this.msPointerOut,
    'MSPointerOver': this.msPointerOver,
    'MSPointerCancel': this.msPointerCancel,
    'MSGotPointerCapture': this.msGotPointerCapture,
    'MSLostPointerCapture': this.msLostPointerCapture
  };
  ol.pointer.EventSource.call(this, dispatcher, mapping);

  /**
   * @const
   * @type {!Object.<string, Event|Object>}
   */
  this.pointerMap = dispatcher.pointerMap;

  /**
   * @const
   * @type {Array.<string>}
   */
  this.POINTER_TYPES = [
    '',
    'unavailable',
    'touch',
    'pen',
    'mouse'
  ];
};
ol.inherits(ol.pointer.MsSource, ol.pointer.EventSource);


/**
 * Creates a copy of the original event that will be used
 * for the fake pointer event.
 *
 * @private
 * @param {Event} inEvent The in event.
 * @return {Object} The copied event.
 */
ol.pointer.MsSource.prototype.prepareEvent_ = function(inEvent) {
  var e = inEvent;
  if (typeof inEvent.pointerType === 'number') {
    e = this.dispatcher.cloneEvent(inEvent, inEvent);
    e.pointerType = this.POINTER_TYPES[inEvent.pointerType];
  }

  return e;
};


/**
 * Remove this pointer from the list of active pointers.
 * @param {number} pointerId Pointer identifier.
 */
ol.pointer.MsSource.prototype.cleanup = function(pointerId) {
  delete this.pointerMap[pointerId.toString()];
};


/**
 * Handler for `msPointerDown`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msPointerDown = function(inEvent) {
  this.pointerMap[inEvent.pointerId.toString()] = inEvent;
  var e = this.prepareEvent_(inEvent);
  this.dispatcher.down(e, inEvent);
};


/**
 * Handler for `msPointerMove`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msPointerMove = function(inEvent) {
  var e = this.prepareEvent_(inEvent);
  this.dispatcher.move(e, inEvent);
};


/**
 * Handler for `msPointerUp`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msPointerUp = function(inEvent) {
  var e = this.prepareEvent_(inEvent);
  this.dispatcher.up(e, inEvent);
  this.cleanup(inEvent.pointerId);
};


/**
 * Handler for `msPointerOut`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msPointerOut = function(inEvent) {
  var e = this.prepareEvent_(inEvent);
  this.dispatcher.leaveOut(e, inEvent);
};


/**
 * Handler for `msPointerOver`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msPointerOver = function(inEvent) {
  var e = this.prepareEvent_(inEvent);
  this.dispatcher.enterOver(e, inEvent);
};


/**
 * Handler for `msPointerCancel`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msPointerCancel = function(inEvent) {
  var e = this.prepareEvent_(inEvent);
  this.dispatcher.cancel(e, inEvent);
  this.cleanup(inEvent.pointerId);
};


/**
 * Handler for `msLostPointerCapture`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msLostPointerCapture = function(inEvent) {
  var e = this.dispatcher.makeEvent('lostpointercapture',
      inEvent, inEvent);
  this.dispatcher.dispatchEvent(e);
};


/**
 * Handler for `msGotPointerCapture`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.MsSource.prototype.msGotPointerCapture = function(inEvent) {
  var e = this.dispatcher.makeEvent('gotpointercapture',
      inEvent, inEvent);
  this.dispatcher.dispatchEvent(e);
};

// Based on https://github.com/Polymer/PointerEvents

// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.pointer.NativeSource');

goog.require('ol');
goog.require('ol.pointer.EventSource');


/**
 * @param {ol.pointer.PointerEventHandler} dispatcher Event handler.
 * @constructor
 * @extends {ol.pointer.EventSource}
 */
ol.pointer.NativeSource = function(dispatcher) {
  var mapping = {
    'pointerdown': this.pointerDown,
    'pointermove': this.pointerMove,
    'pointerup': this.pointerUp,
    'pointerout': this.pointerOut,
    'pointerover': this.pointerOver,
    'pointercancel': this.pointerCancel,
    'gotpointercapture': this.gotPointerCapture,
    'lostpointercapture': this.lostPointerCapture
  };
  ol.pointer.EventSource.call(this, dispatcher, mapping);
};
ol.inherits(ol.pointer.NativeSource, ol.pointer.EventSource);


/**
 * Handler for `pointerdown`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.pointerDown = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `pointermove`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.pointerMove = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `pointerup`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.pointerUp = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `pointerout`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.pointerOut = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `pointerover`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.pointerOver = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `pointercancel`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.pointerCancel = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `lostpointercapture`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.lostPointerCapture = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};


/**
 * Handler for `gotpointercapture`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.NativeSource.prototype.gotPointerCapture = function(inEvent) {
  this.dispatcher.fireNativeEvent(inEvent);
};

// Based on https://github.com/Polymer/PointerEvents

// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.pointer.PointerEvent');


goog.require('ol');
goog.require('ol.events.Event');


/**
 * A class for pointer events.
 *
 * This class is used as an abstraction for mouse events,
 * touch events and even native pointer events.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @param {string} type The type of the event to create.
 * @param {Event} originalEvent The event.
 * @param {Object.<string, ?>=} opt_eventDict An optional dictionary of
 *    initial event properties.
 */
ol.pointer.PointerEvent = function(type, originalEvent, opt_eventDict) {
  ol.events.Event.call(this, type);

  /**
   * @const
   * @type {Event}
   */
  this.originalEvent = originalEvent;

  var eventDict = opt_eventDict ? opt_eventDict : {};

  /**
   * @type {number}
   */
  this.buttons = this.getButtons_(eventDict);

  /**
   * @type {number}
   */
  this.pressure = this.getPressure_(eventDict, this.buttons);

  // MouseEvent related properties

  /**
   * @type {boolean}
   */
  this.bubbles = 'bubbles' in eventDict ? eventDict['bubbles'] : false;

  /**
   * @type {boolean}
   */
  this.cancelable = 'cancelable' in eventDict ? eventDict['cancelable'] : false;

  /**
   * @type {Object}
   */
  this.view = 'view' in eventDict ? eventDict['view'] : null;

  /**
   * @type {number}
   */
  this.detail = 'detail' in eventDict ? eventDict['detail'] : null;

  /**
   * @type {number}
   */
  this.screenX = 'screenX' in eventDict ? eventDict['screenX'] : 0;

  /**
   * @type {number}
   */
  this.screenY = 'screenY' in eventDict ? eventDict['screenY'] : 0;

  /**
   * @type {number}
   */
  this.clientX = 'clientX' in eventDict ? eventDict['clientX'] : 0;

  /**
   * @type {number}
   */
  this.clientY = 'clientY' in eventDict ? eventDict['clientY'] : 0;

  /**
   * @type {boolean}
   */
  this.ctrlKey = 'ctrlKey' in eventDict ? eventDict['ctrlKey'] : false;

  /**
   * @type {boolean}
   */
  this.altKey = 'altKey' in eventDict ? eventDict['altKey'] : false;

  /**
   * @type {boolean}
   */
  this.shiftKey = 'shiftKey' in eventDict ? eventDict['shiftKey'] : false;

  /**
   * @type {boolean}
   */
  this.metaKey = 'metaKey' in eventDict ? eventDict['metaKey'] : false;

  /**
   * @type {number}
   */
  this.button = 'button' in eventDict ? eventDict['button'] : 0;

  /**
   * @type {Node}
   */
  this.relatedTarget = 'relatedTarget' in eventDict ?
      eventDict['relatedTarget'] : null;

  // PointerEvent related properties

  /**
   * @const
   * @type {number}
   */
  this.pointerId = 'pointerId' in eventDict ? eventDict['pointerId'] : 0;

  /**
   * @type {number}
   */
  this.width = 'width' in eventDict ? eventDict['width'] : 0;

  /**
   * @type {number}
   */
  this.height = 'height' in eventDict ? eventDict['height'] : 0;

  /**
   * @type {number}
   */
  this.tiltX = 'tiltX' in eventDict ? eventDict['tiltX'] : 0;

  /**
   * @type {number}
   */
  this.tiltY = 'tiltY' in eventDict ? eventDict['tiltY'] : 0;

  /**
   * @type {string}
   */
  this.pointerType = 'pointerType' in eventDict ? eventDict['pointerType'] : '';

  /**
   * @type {number}
   */
  this.hwTimestamp = 'hwTimestamp' in eventDict ? eventDict['hwTimestamp'] : 0;

  /**
   * @type {boolean}
   */
  this.isPrimary = 'isPrimary' in eventDict ? eventDict['isPrimary'] : false;

  // keep the semantics of preventDefault
  if (originalEvent.preventDefault) {
    this.preventDefault = function() {
      originalEvent.preventDefault();
    };
  }
};
ol.inherits(ol.pointer.PointerEvent, ol.events.Event);


/**
 * @private
 * @param {Object.<string, ?>} eventDict The event dictionary.
 * @return {number} Button indicator.
 */
ol.pointer.PointerEvent.prototype.getButtons_ = function(eventDict) {
  // According to the w3c spec,
  // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
  // MouseEvent.button == 0 can mean either no mouse button depressed, or the
  // left mouse button depressed.
  //
  // As of now, the only way to distinguish between the two states of
  // MouseEvent.button is by using the deprecated MouseEvent.which property, as
  // this maps mouse buttons to positive integers > 0, and uses 0 to mean that
  // no mouse button is held.
  //
  // MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
  // but initMouseEvent does not expose an argument with which to set
  // MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
  // MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
  // of app developers.
  //
  // The only way to propagate the correct state of MouseEvent.which and
  // MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
  // is to call initMouseEvent with a buttonArg value of -1.
  //
  // This is fixed with DOM Level 4's use of buttons
  var buttons;
  if (eventDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) {
    buttons = eventDict.buttons;
  } else {
    switch (eventDict.which) {
      case 1: buttons = 1; break;
      case 2: buttons = 4; break;
      case 3: buttons = 2; break;
      default: buttons = 0;
    }
  }
  return buttons;
};


/**
 * @private
 * @param {Object.<string, ?>} eventDict The event dictionary.
 * @param {number} buttons Button indicator.
 * @return {number} The pressure.
 */
ol.pointer.PointerEvent.prototype.getPressure_ = function(eventDict, buttons) {
  // Spec requires that pointers without pressure specified use 0.5 for down
  // state and 0 for up state.
  var pressure = 0;
  if (eventDict.pressure) {
    pressure = eventDict.pressure;
  } else {
    pressure = buttons ? 0.5 : 0;
  }
  return pressure;
};


/**
 * Is the `buttons` property supported?
 * @type {boolean}
 */
ol.pointer.PointerEvent.HAS_BUTTONS = false;


/**
 * Checks if the `buttons` property is supported.
 */
(function() {
  try {
    var ev = new MouseEvent('click', {buttons: 1});
    ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
  } catch (e) {
    // pass
  }
})();

// Based on https://github.com/Polymer/PointerEvents

// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.pointer.TouchSource');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.pointer.EventSource');
goog.require('ol.pointer.MouseSource');


/**
 * @constructor
 * @param {ol.pointer.PointerEventHandler} dispatcher The event handler.
 * @param {ol.pointer.MouseSource} mouseSource Mouse source.
 * @extends {ol.pointer.EventSource}
 */
ol.pointer.TouchSource = function(dispatcher, mouseSource) {
  var mapping = {
    'touchstart': this.touchstart,
    'touchmove': this.touchmove,
    'touchend': this.touchend,
    'touchcancel': this.touchcancel
  };
  ol.pointer.EventSource.call(this, dispatcher, mapping);

  /**
   * @const
   * @type {!Object.<string, Event|Object>}
   */
  this.pointerMap = dispatcher.pointerMap;

  /**
   * @const
   * @type {ol.pointer.MouseSource}
   */
  this.mouseSource = mouseSource;

  /**
   * @private
   * @type {number|undefined}
   */
  this.firstTouchId_ = undefined;

  /**
   * @private
   * @type {number}
   */
  this.clickCount_ = 0;

  /**
   * @private
   * @type {number|undefined}
   */
  this.resetId_ = undefined;
};
ol.inherits(ol.pointer.TouchSource, ol.pointer.EventSource);


/**
 * Mouse event timeout: This should be long enough to
 * ignore compat mouse events made by touch.
 * @const
 * @type {number}
 */
ol.pointer.TouchSource.DEDUP_TIMEOUT = 2500;


/**
 * @const
 * @type {number}
 */
ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT = 200;


/**
 * @const
 * @type {string}
 */
ol.pointer.TouchSource.POINTER_TYPE = 'touch';


/**
 * @private
 * @param {Touch} inTouch The in touch.
 * @return {boolean} True, if this is the primary touch.
 */
ol.pointer.TouchSource.prototype.isPrimaryTouch_ = function(inTouch) {
  return this.firstTouchId_ === inTouch.identifier;
};


/**
 * Set primary touch if there are no pointers, or the only pointer is the mouse.
 * @param {Touch} inTouch The in touch.
 * @private
 */
ol.pointer.TouchSource.prototype.setPrimaryTouch_ = function(inTouch) {
  var count = Object.keys(this.pointerMap).length;
  if (count === 0 || (count === 1 &&
      ol.pointer.MouseSource.POINTER_ID.toString() in this.pointerMap)) {
    this.firstTouchId_ = inTouch.identifier;
    this.cancelResetClickCount_();
  }
};


/**
 * @private
 * @param {Object} inPointer The in pointer object.
 */
ol.pointer.TouchSource.prototype.removePrimaryPointer_ = function(inPointer) {
  if (inPointer.isPrimary) {
    this.firstTouchId_ = undefined;
    this.resetClickCount_();
  }
};


/**
 * @private
 */
ol.pointer.TouchSource.prototype.resetClickCount_ = function() {
  var global = ol.global;
  this.resetId_ = global.setTimeout(
      this.resetClickCountHandler_.bind(this),
      ol.pointer.TouchSource.CLICK_COUNT_TIMEOUT);
};


/**
 * @private
 */
ol.pointer.TouchSource.prototype.resetClickCountHandler_ = function() {
  this.clickCount_ = 0;
  this.resetId_ = undefined;
};


/**
 * @private
 */
ol.pointer.TouchSource.prototype.cancelResetClickCount_ = function() {
  var global = ol.global;
  if (this.resetId_ !== undefined) {
    global.clearTimeout(this.resetId_);
  }
};


/**
 * @private
 * @param {Event} browserEvent Browser event
 * @param {Touch} inTouch Touch event
 * @return {Object} A pointer object.
 */
ol.pointer.TouchSource.prototype.touchToPointer_ = function(browserEvent, inTouch) {
  var e = this.dispatcher.cloneEvent(browserEvent, inTouch);
  // Spec specifies that pointerId 1 is reserved for Mouse.
  // Touch identifiers can start at 0.
  // Add 2 to the touch identifier for compatibility.
  e.pointerId = inTouch.identifier + 2;
  // TODO: check if this is necessary?
  //e.target = findTarget(e);
  e.bubbles = true;
  e.cancelable = true;
  e.detail = this.clickCount_;
  e.button = 0;
  e.buttons = 1;
  e.width = inTouch.webkitRadiusX || inTouch.radiusX || 0;
  e.height = inTouch.webkitRadiusY || inTouch.radiusY || 0;
  e.pressure = inTouch.webkitForce || inTouch.force || 0.5;
  e.isPrimary = this.isPrimaryTouch_(inTouch);
  e.pointerType = ol.pointer.TouchSource.POINTER_TYPE;

  // make sure that the properties that are different for
  // each `Touch` object are not copied from the BrowserEvent object
  e.clientX = inTouch.clientX;
  e.clientY = inTouch.clientY;
  e.screenX = inTouch.screenX;
  e.screenY = inTouch.screenY;

  return e;
};


/**
 * @private
 * @param {Event} inEvent Touch event
 * @param {function(Event, Object)} inFunction In function.
 */
ol.pointer.TouchSource.prototype.processTouches_ = function(inEvent, inFunction) {
  var touches = Array.prototype.slice.call(
      inEvent.changedTouches);
  var count = touches.length;
  function preventDefault() {
    inEvent.preventDefault();
  }
  var i, pointer;
  for (i = 0; i < count; ++i) {
    pointer = this.touchToPointer_(inEvent, touches[i]);
    // forward touch preventDefaults
    pointer.preventDefault = preventDefault;
    inFunction.call(this, inEvent, pointer);
  }
};


/**
 * @private
 * @param {TouchList} touchList The touch list.
 * @param {number} searchId Search identifier.
 * @return {boolean} True, if the `Touch` with the given id is in the list.
 */
ol.pointer.TouchSource.prototype.findTouch_ = function(touchList, searchId) {
  var l = touchList.length;
  var touch;
  for (var i = 0; i < l; i++) {
    touch = touchList[i];
    if (touch.identifier === searchId) {
      return true;
    }
  }
  return false;
};


/**
 * In some instances, a touchstart can happen without a touchend. This
 * leaves the pointermap in a broken state.
 * Therefore, on every touchstart, we remove the touches that did not fire a
 * touchend event.
 * To keep state globally consistent, we fire a pointercancel for
 * this "abandoned" touch
 *
 * @private
 * @param {Event} inEvent The in event.
 */
ol.pointer.TouchSource.prototype.vacuumTouches_ = function(inEvent) {
  var touchList = inEvent.touches;
  // pointerMap.getCount() should be < touchList.length here,
  // as the touchstart has not been processed yet.
  var keys = Object.keys(this.pointerMap);
  var count = keys.length;
  if (count >= touchList.length) {
    var d = [];
    var i, key, value;
    for (i = 0; i < count; ++i) {
      key = keys[i];
      value = this.pointerMap[key];
      // Never remove pointerId == 1, which is mouse.
      // Touch identifiers are 2 smaller than their pointerId, which is the
      // index in pointermap.
      if (key != ol.pointer.MouseSource.POINTER_ID &&
          !this.findTouch_(touchList, key - 2)) {
        d.push(value.out);
      }
    }
    for (i = 0; i < d.length; ++i) {
      this.cancelOut_(inEvent, d[i]);
    }
  }
};


/**
 * Handler for `touchstart`, triggers `pointerover`,
 * `pointerenter` and `pointerdown` events.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.TouchSource.prototype.touchstart = function(inEvent) {
  this.vacuumTouches_(inEvent);
  this.setPrimaryTouch_(inEvent.changedTouches[0]);
  this.dedupSynthMouse_(inEvent);
  this.clickCount_++;
  this.processTouches_(inEvent, this.overDown_);
};


/**
 * @private
 * @param {Event} browserEvent The event.
 * @param {Object} inPointer The in pointer object.
 */
ol.pointer.TouchSource.prototype.overDown_ = function(browserEvent, inPointer) {
  this.pointerMap[inPointer.pointerId] = {
    target: inPointer.target,
    out: inPointer,
    outTarget: inPointer.target
  };
  this.dispatcher.over(inPointer, browserEvent);
  this.dispatcher.enter(inPointer, browserEvent);
  this.dispatcher.down(inPointer, browserEvent);
};


/**
 * Handler for `touchmove`.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.TouchSource.prototype.touchmove = function(inEvent) {
  inEvent.preventDefault();
  this.processTouches_(inEvent, this.moveOverOut_);
};


/**
 * @private
 * @param {Event} browserEvent The event.
 * @param {Object} inPointer The in pointer.
 */
ol.pointer.TouchSource.prototype.moveOverOut_ = function(browserEvent, inPointer) {
  var event = inPointer;
  var pointer = this.pointerMap[event.pointerId];
  // a finger drifted off the screen, ignore it
  if (!pointer) {
    return;
  }
  var outEvent = pointer.out;
  var outTarget = pointer.outTarget;
  this.dispatcher.move(event, browserEvent);
  if (outEvent && outTarget !== event.target) {
    outEvent.relatedTarget = event.target;
    event.relatedTarget = outTarget;
    // recover from retargeting by shadow
    outEvent.target = outTarget;
    if (event.target) {
      this.dispatcher.leaveOut(outEvent, browserEvent);
      this.dispatcher.enterOver(event, browserEvent);
    } else {
      // clean up case when finger leaves the screen
      event.target = outTarget;
      event.relatedTarget = null;
      this.cancelOut_(browserEvent, event);
    }
  }
  pointer.out = event;
  pointer.outTarget = event.target;
};


/**
 * Handler for `touchend`, triggers `pointerup`,
 * `pointerout` and `pointerleave` events.
 *
 * @param {Event} inEvent The event.
 */
ol.pointer.TouchSource.prototype.touchend = function(inEvent) {
  this.dedupSynthMouse_(inEvent);
  this.processTouches_(inEvent, this.upOut_);
};


/**
 * @private
 * @param {Event} browserEvent An event.
 * @param {Object} inPointer The inPointer object.
 */
ol.pointer.TouchSource.prototype.upOut_ = function(browserEvent, inPointer) {
  this.dispatcher.up(inPointer, browserEvent);
  this.dispatcher.out(inPointer, browserEvent);
  this.dispatcher.leave(inPointer, browserEvent);
  this.cleanUpPointer_(inPointer);
};


/**
 * Handler for `touchcancel`, triggers `pointercancel`,
 * `pointerout` and `pointerleave` events.
 *
 * @param {Event} inEvent The in event.
 */
ol.pointer.TouchSource.prototype.touchcancel = function(inEvent) {
  this.processTouches_(inEvent, this.cancelOut_);
};


/**
 * @private
 * @param {Event} browserEvent The event.
 * @param {Object} inPointer The in pointer.
 */
ol.pointer.TouchSource.prototype.cancelOut_ = function(browserEvent, inPointer) {
  this.dispatcher.cancel(inPointer, browserEvent);
  this.dispatcher.out(inPointer, browserEvent);
  this.dispatcher.leave(inPointer, browserEvent);
  this.cleanUpPointer_(inPointer);
};


/**
 * @private
 * @param {Object} inPointer The inPointer object.
 */
ol.pointer.TouchSource.prototype.cleanUpPointer_ = function(inPointer) {
  delete this.pointerMap[inPointer.pointerId];
  this.removePrimaryPointer_(inPointer);
};


/**
 * Prevent synth mouse events from creating pointer events.
 *
 * @private
 * @param {Event} inEvent The in event.
 */
ol.pointer.TouchSource.prototype.dedupSynthMouse_ = function(inEvent) {
  var lts = this.mouseSource.lastTouches;
  var t = inEvent.changedTouches[0];
  // only the primary finger will synth mouse events
  if (this.isPrimaryTouch_(t)) {
    // remember x/y of last touch
    var lt = [t.clientX, t.clientY];
    lts.push(lt);

    var global = ol.global;
    global.setTimeout(function() {
      // remove touch after timeout
      ol.array.remove(lts, lt);
    }, ol.pointer.TouchSource.DEDUP_TIMEOUT);
  }
};

// Based on https://github.com/Polymer/PointerEvents

// Copyright (c) 2013 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

goog.provide('ol.pointer.PointerEventHandler');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventTarget');

goog.require('ol.has');
goog.require('ol.pointer.MouseSource');
goog.require('ol.pointer.MsSource');
goog.require('ol.pointer.NativeSource');
goog.require('ol.pointer.PointerEvent');
goog.require('ol.pointer.TouchSource');


/**
 * @constructor
 * @extends {ol.events.EventTarget}
 * @param {Element|HTMLDocument} element Viewport element.
 */
ol.pointer.PointerEventHandler = function(element) {
  ol.events.EventTarget.call(this);

  /**
   * @const
   * @private
   * @type {Element|HTMLDocument}
   */
  this.element_ = element;

  /**
   * @const
   * @type {!Object.<string, Event|Object>}
   */
  this.pointerMap = {};

  /**
   * @type {Object.<string, function(Event)>}
   * @private
   */
  this.eventMap_ = {};

  /**
   * @type {Array.<ol.pointer.EventSource>}
   * @private
   */
  this.eventSourceList_ = [];

  this.registerSources();
};
ol.inherits(ol.pointer.PointerEventHandler, ol.events.EventTarget);


/**
 * Set up the event sources (mouse, touch and native pointers)
 * that generate pointer events.
 */
ol.pointer.PointerEventHandler.prototype.registerSources = function() {
  if (ol.has.POINTER) {
    this.registerSource('native', new ol.pointer.NativeSource(this));
  } else if (ol.has.MSPOINTER) {
    this.registerSource('ms', new ol.pointer.MsSource(this));
  } else {
    var mouseSource = new ol.pointer.MouseSource(this);
    this.registerSource('mouse', mouseSource);

    if (ol.has.TOUCH) {
      this.registerSource('touch',
          new ol.pointer.TouchSource(this, mouseSource));
    }
  }

  // register events on the viewport element
  this.register_();
};


/**
 * Add a new event source that will generate pointer events.
 *
 * @param {string} name A name for the event source
 * @param {ol.pointer.EventSource} source The source event.
 */
ol.pointer.PointerEventHandler.prototype.registerSource = function(name, source) {
  var s = source;
  var newEvents = s.getEvents();

  if (newEvents) {
    newEvents.forEach(function(e) {
      var handler = s.getHandlerForEvent(e);

      if (handler) {
        this.eventMap_[e] = handler.bind(s);
      }
    }, this);
    this.eventSourceList_.push(s);
  }
};


/**
 * Set up the events for all registered event sources.
 * @private
 */
ol.pointer.PointerEventHandler.prototype.register_ = function() {
  var l = this.eventSourceList_.length;
  var eventSource;
  for (var i = 0; i < l; i++) {
    eventSource = this.eventSourceList_[i];
    this.addEvents_(eventSource.getEvents());
  }
};


/**
 * Remove all registered events.
 * @private
 */
ol.pointer.PointerEventHandler.prototype.unregister_ = function() {
  var l = this.eventSourceList_.length;
  var eventSource;
  for (var i = 0; i < l; i++) {
    eventSource = this.eventSourceList_[i];
    this.removeEvents_(eventSource.getEvents());
  }
};


/**
 * Calls the right handler for a new event.
 * @private
 * @param {Event} inEvent Browser event.
 */
ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
  var type = inEvent.type;
  var handler = this.eventMap_[type];
  if (handler) {
    handler(inEvent);
  }
};


/**
 * Setup listeners for the given events.
 * @private
 * @param {Array.<string>} events List of events.
 */
ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
  events.forEach(function(eventName) {
    ol.events.listen(this.element_, eventName, this.eventHandler_, this);
  }, this);
};


/**
 * Unregister listeners for the given events.
 * @private
 * @param {Array.<string>} events List of events.
 */
ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
  events.forEach(function(e) {
    ol.events.unlisten(this.element_, e, this.eventHandler_, this);
  }, this);
};


/**
 * Returns a snapshot of inEvent, with writable properties.
 *
 * @param {Event} event Browser event.
 * @param {Event|Touch} inEvent An event that contains
 *    properties to copy.
 * @return {Object} An object containing shallow copies of
 *    `inEvent`'s properties.
 */
ol.pointer.PointerEventHandler.prototype.cloneEvent = function(event, inEvent) {
  var eventCopy = {}, p;
  for (var i = 0, ii = ol.pointer.CLONE_PROPS.length; i < ii; i++) {
    p = ol.pointer.CLONE_PROPS[i][0];
    eventCopy[p] = event[p] || inEvent[p] || ol.pointer.CLONE_PROPS[i][1];
  }

  return eventCopy;
};


// EVENTS


/**
 * Triggers a 'pointerdown' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.down = function(data, event) {
  this.fireEvent(ol.pointer.EventType.POINTERDOWN, data, event);
};


/**
 * Triggers a 'pointermove' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.move = function(data, event) {
  this.fireEvent(ol.pointer.EventType.POINTERMOVE, data, event);
};


/**
 * Triggers a 'pointerup' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.up = function(data, event) {
  this.fireEvent(ol.pointer.EventType.POINTERUP, data, event);
};


/**
 * Triggers a 'pointerenter' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.enter = function(data, event) {
  data.bubbles = false;
  this.fireEvent(ol.pointer.EventType.POINTERENTER, data, event);
};


/**
 * Triggers a 'pointerleave' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.leave = function(data, event) {
  data.bubbles = false;
  this.fireEvent(ol.pointer.EventType.POINTERLEAVE, data, event);
};


/**
 * Triggers a 'pointerover' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.over = function(data, event) {
  data.bubbles = true;
  this.fireEvent(ol.pointer.EventType.POINTEROVER, data, event);
};


/**
 * Triggers a 'pointerout' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.out = function(data, event) {
  data.bubbles = true;
  this.fireEvent(ol.pointer.EventType.POINTEROUT, data, event);
};


/**
 * Triggers a 'pointercancel' event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.cancel = function(data, event) {
  this.fireEvent(ol.pointer.EventType.POINTERCANCEL, data, event);
};


/**
 * Triggers a combination of 'pointerout' and 'pointerleave' events.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.leaveOut = function(data, event) {
  this.out(data, event);
  if (!this.contains_(data.target, data.relatedTarget)) {
    this.leave(data, event);
  }
};


/**
 * Triggers a combination of 'pointerover' and 'pointerevents' events.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.enterOver = function(data, event) {
  this.over(data, event);
  if (!this.contains_(data.target, data.relatedTarget)) {
    this.enter(data, event);
  }
};


/**
 * @private
 * @param {Element} container The container element.
 * @param {Element} contained The contained element.
 * @return {boolean} Returns true if the container element
 *   contains the other element.
 */
ol.pointer.PointerEventHandler.prototype.contains_ = function(container, contained) {
  if (!container || !contained) {
    return false;
  }
  return container.contains(contained);
};


// EVENT CREATION AND TRACKING
/**
 * Creates a new Event of type `inType`, based on the information in
 * `data`.
 *
 * @param {string} inType A string representing the type of event to create.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 * @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
 */
ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, data, event) {
  return new ol.pointer.PointerEvent(inType, event, data);
};


/**
 * Make and dispatch an event in one call.
 * @param {string} inType A string representing the type of event.
 * @param {Object} data Pointer event data.
 * @param {Event} event The event.
 */
ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, data, event) {
  var e = this.makeEvent(inType, data, event);
  this.dispatchEvent(e);
};


/**
 * Creates a pointer event from a native pointer event
 * and dispatches this event.
 * @param {Event} event A platform event with a target.
 */
ol.pointer.PointerEventHandler.prototype.fireNativeEvent = function(event) {
  var e = this.makeEvent(event.type, event, event);
  this.dispatchEvent(e);
};


/**
 * Wrap a native mouse event into a pointer event.
 * This proxy method is required for the legacy IE support.
 * @param {string} eventType The pointer event type.
 * @param {Event} event The event.
 * @return {ol.pointer.PointerEvent} The wrapped event.
 */
ol.pointer.PointerEventHandler.prototype.wrapMouseEvent = function(eventType, event) {
  var pointerEvent = this.makeEvent(
      eventType, ol.pointer.MouseSource.prepareEvent(event, this), event);
  return pointerEvent;
};


/**
 * @inheritDoc
 */
ol.pointer.PointerEventHandler.prototype.disposeInternal = function() {
  this.unregister_();
  ol.events.EventTarget.prototype.disposeInternal.call(this);
};


/**
 * Constants for event names.
 * @enum {string}
 */
ol.pointer.EventType = {
  POINTERMOVE: 'pointermove',
  POINTERDOWN: 'pointerdown',
  POINTERUP: 'pointerup',
  POINTEROVER: 'pointerover',
  POINTEROUT: 'pointerout',
  POINTERENTER: 'pointerenter',
  POINTERLEAVE: 'pointerleave',
  POINTERCANCEL: 'pointercancel'
};


/**
 * Properties to copy when cloning an event, with default values.
 * @type {Array.<Array>}
 */
ol.pointer.CLONE_PROPS = [
  // MouseEvent
  ['bubbles', false],
  ['cancelable', false],
  ['view', null],
  ['detail', null],
  ['screenX', 0],
  ['screenY', 0],
  ['clientX', 0],
  ['clientY', 0],
  ['ctrlKey', false],
  ['altKey', false],
  ['shiftKey', false],
  ['metaKey', false],
  ['button', 0],
  ['relatedTarget', null],
  // DOM Level 3
  ['buttons', 0],
  // PointerEvent
  ['pointerId', 0],
  ['width', 0],
  ['height', 0],
  ['pressure', 0],
  ['tiltX', 0],
  ['tiltY', 0],
  ['pointerType', ''],
  ['hwTimestamp', 0],
  ['isPrimary', false],
  // event instance
  ['type', ''],
  ['target', null],
  ['currentTarget', null],
  ['which', 0]
];

goog.provide('ol.MapBrowserEvent');
goog.provide('ol.MapBrowserEvent.EventType');
goog.provide('ol.MapBrowserEventHandler');
goog.provide('ol.MapBrowserPointerEvent');

goog.require('ol');
goog.require('ol.MapEvent');
goog.require('ol.events');
goog.require('ol.events.EventTarget');
goog.require('ol.events.EventType');
goog.require('ol.pointer.PointerEventHandler');


/**
 * @classdesc
 * Events emitted as map browser events are instances of this type.
 * See {@link ol.Map} for which events trigger a map browser event.
 *
 * @constructor
 * @extends {ol.MapEvent}
 * @implements {oli.MapBrowserEvent}
 * @param {string} type Event type.
 * @param {ol.Map} map Map.
 * @param {Event} browserEvent Browser event.
 * @param {boolean=} opt_dragging Is the map currently being dragged?
 * @param {?olx.FrameState=} opt_frameState Frame state.
 */
ol.MapBrowserEvent = function(type, map, browserEvent, opt_dragging,
    opt_frameState) {

  ol.MapEvent.call(this, type, map, opt_frameState);

  /**
   * The original browser event.
   * @const
   * @type {Event}
   * @api stable
   */
  this.originalEvent = browserEvent;

  /**
   * The pixel of the original browser event.
   * @type {ol.Pixel}
   * @api stable
   */
  this.pixel = map.getEventPixel(browserEvent);

  /**
   * The coordinate of the original browser event.
   * @type {ol.Coordinate}
   * @api stable
   */
  this.coordinate = map.getCoordinateFromPixel(this.pixel);

  /**
   * Indicates if the map is currently being dragged. Only set for
   * `POINTERDRAG` and `POINTERMOVE` events. Default is `false`.
   *
   * @type {boolean}
   * @api stable
   */
  this.dragging = opt_dragging !== undefined ? opt_dragging : false;

};
ol.inherits(ol.MapBrowserEvent, ol.MapEvent);


/**
 * Prevents the default browser action.
 * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
 * @override
 * @api stable
 */
ol.MapBrowserEvent.prototype.preventDefault = function() {
  ol.MapEvent.prototype.preventDefault.call(this);
  this.originalEvent.preventDefault();
};


/**
 * Prevents further propagation of the current event.
 * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
 * @override
 * @api stable
 */
ol.MapBrowserEvent.prototype.stopPropagation = function() {
  ol.MapEvent.prototype.stopPropagation.call(this);
  this.originalEvent.stopPropagation();
};


/**
 * @constructor
 * @extends {ol.MapBrowserEvent}
 * @param {string} type Event type.
 * @param {ol.Map} map Map.
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @param {boolean=} opt_dragging Is the map currently being dragged?
 * @param {?olx.FrameState=} opt_frameState Frame state.
 */
ol.MapBrowserPointerEvent = function(type, map, pointerEvent, opt_dragging,
    opt_frameState) {

  ol.MapBrowserEvent.call(this, type, map, pointerEvent.originalEvent, opt_dragging,
      opt_frameState);

  /**
   * @const
   * @type {ol.pointer.PointerEvent}
   */
  this.pointerEvent = pointerEvent;

};
ol.inherits(ol.MapBrowserPointerEvent, ol.MapBrowserEvent);


/**
 * @param {ol.Map} map The map with the viewport to listen to events on.
 * @constructor
 * @extends {ol.events.EventTarget}
 */
ol.MapBrowserEventHandler = function(map) {

  ol.events.EventTarget.call(this);

  /**
   * This is the element that we will listen to the real events on.
   * @type {ol.Map}
   * @private
   */
  this.map_ = map;

  /**
   * @type {number}
   * @private
   */
  this.clickTimeoutId_ = 0;

  /**
   * @type {boolean}
   * @private
   */
  this.dragging_ = false;

  /**
   * @type {!Array.<ol.EventsKey>}
   * @private
   */
  this.dragListenerKeys_ = [];

  /**
   * The most recent "down" type event (or null if none have occurred).
   * Set on pointerdown.
   * @type {ol.pointer.PointerEvent}
   * @private
   */
  this.down_ = null;

  var element = this.map_.getViewport();

  /**
   * @type {number}
   * @private
   */
  this.activePointers_ = 0;

  /**
   * @type {!Object.<number, boolean>}
   * @private
   */
  this.trackedTouches_ = {};

  /**
   * Event handler which generates pointer events for
   * the viewport element.
   *
   * @type {ol.pointer.PointerEventHandler}
   * @private
   */
  this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element);

  /**
   * Event handler which generates pointer events for
   * the document (used when dragging).
   *
   * @type {ol.pointer.PointerEventHandler}
   * @private
   */
  this.documentPointerEventHandler_ = null;

  /**
   * @type {?ol.EventsKey}
   * @private
   */
  this.pointerdownListenerKey_ = ol.events.listen(this.pointerEventHandler_,
      ol.pointer.EventType.POINTERDOWN,
      this.handlePointerDown_, this);

  /**
   * @type {?ol.EventsKey}
   * @private
   */
  this.relayedListenerKey_ = ol.events.listen(this.pointerEventHandler_,
      ol.pointer.EventType.POINTERMOVE,
      this.relayEvent_, this);

};
ol.inherits(ol.MapBrowserEventHandler, ol.events.EventTarget);


/**
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.emulateClick_ = function(pointerEvent) {
  var newEvent;
  newEvent = new ol.MapBrowserPointerEvent(
      ol.MapBrowserEvent.EventType.CLICK, this.map_, pointerEvent);
  this.dispatchEvent(newEvent);
  if (this.clickTimeoutId_ !== 0) {
    // double-click
    ol.global.clearTimeout(this.clickTimeoutId_);
    this.clickTimeoutId_ = 0;
    newEvent = new ol.MapBrowserPointerEvent(
        ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, pointerEvent);
    this.dispatchEvent(newEvent);
  } else {
    // click
    this.clickTimeoutId_ = ol.global.setTimeout(function() {
      this.clickTimeoutId_ = 0;
      var newEvent = new ol.MapBrowserPointerEvent(
          ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, pointerEvent);
      this.dispatchEvent(newEvent);
    }.bind(this), 250);
  }
};


/**
 * Keeps track on how many pointers are currently active.
 *
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.updateActivePointers_ = function(pointerEvent) {
  var event = pointerEvent;

  if (event.type == ol.MapBrowserEvent.EventType.POINTERUP ||
      event.type == ol.MapBrowserEvent.EventType.POINTERCANCEL) {
    delete this.trackedTouches_[event.pointerId];
  } else if (event.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
    this.trackedTouches_[event.pointerId] = true;
  }
  this.activePointers_ = Object.keys(this.trackedTouches_).length;
};


/**
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(pointerEvent) {
  this.updateActivePointers_(pointerEvent);
  var newEvent = new ol.MapBrowserPointerEvent(
      ol.MapBrowserEvent.EventType.POINTERUP, this.map_, pointerEvent);
  this.dispatchEvent(newEvent);

  // We emulate click events on left mouse button click, touch contact, and pen
  // contact. isMouseActionButton returns true in these cases (evt.button is set
  // to 0).
  // See http://www.w3.org/TR/pointerevents/#button-states
  if (!this.dragging_ && this.isMouseActionButton_(pointerEvent)) {
    goog.DEBUG && console.assert(this.down_, 'this.down_ must be truthy');
    this.emulateClick_(this.down_);
  }

  goog.DEBUG && console.assert(this.activePointers_ >= 0,
      'this.activePointers_ should be equal to or larger than 0');
  if (this.activePointers_ === 0) {
    this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
    this.dragListenerKeys_.length = 0;
    this.dragging_ = false;
    this.down_ = null;
    this.documentPointerEventHandler_.dispose();
    this.documentPointerEventHandler_ = null;
  }
};


/**
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @return {boolean} If the left mouse button was pressed.
 * @private
 */
ol.MapBrowserEventHandler.prototype.isMouseActionButton_ = function(pointerEvent) {
  return pointerEvent.button === 0;
};


/**
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(pointerEvent) {
  this.updateActivePointers_(pointerEvent);
  var newEvent = new ol.MapBrowserPointerEvent(
      ol.MapBrowserEvent.EventType.POINTERDOWN, this.map_, pointerEvent);
  this.dispatchEvent(newEvent);

  this.down_ = pointerEvent;

  if (this.dragListenerKeys_.length === 0) {
    /* Set up a pointer event handler on the `document`,
     * which is required when the pointer is moved outside
     * the viewport when dragging.
     */
    this.documentPointerEventHandler_ =
        new ol.pointer.PointerEventHandler(document);

    this.dragListenerKeys_.push(
      ol.events.listen(this.documentPointerEventHandler_,
          ol.MapBrowserEvent.EventType.POINTERMOVE,
          this.handlePointerMove_, this),
      ol.events.listen(this.documentPointerEventHandler_,
          ol.MapBrowserEvent.EventType.POINTERUP,
          this.handlePointerUp_, this),
      /* Note that the listener for `pointercancel is set up on
       * `pointerEventHandler_` and not `documentPointerEventHandler_` like
       * the `pointerup` and `pointermove` listeners.
       *
       * The reason for this is the following: `TouchSource.vacuumTouches_()`
       * issues `pointercancel` events, when there was no `touchend` for a
       * `touchstart`. Now, let's say a first `touchstart` is registered on
       * `pointerEventHandler_`. The `documentPointerEventHandler_` is set up.
       * But `documentPointerEventHandler_` doesn't know about the first
       * `touchstart`. If there is no `touchend` for the `touchstart`, we can
       * only receive a `touchcancel` from `pointerEventHandler_`, because it is
       * only registered there.
       */
      ol.events.listen(this.pointerEventHandler_,
          ol.MapBrowserEvent.EventType.POINTERCANCEL,
          this.handlePointerUp_, this)
    );
  }
};


/**
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(pointerEvent) {
  // Fix IE10 on windows Surface : When you tap the tablet, it triggers
  // multiple pointermove events between pointerdown and pointerup with
  // the exact same coordinates of the pointerdown event. To avoid a
  // 'false' touchmove event to be dispatched , we test if the pointer
  // effectively moved.
  if (this.isMoving_(pointerEvent)) {
    this.dragging_ = true;
    var newEvent = new ol.MapBrowserPointerEvent(
        ol.MapBrowserEvent.EventType.POINTERDRAG, this.map_, pointerEvent,
        this.dragging_);
    this.dispatchEvent(newEvent);
  }

  // Some native android browser triggers mousemove events during small period
  // of time. See: https://code.google.com/p/android/issues/detail?id=5491 or
  // https://code.google.com/p/android/issues/detail?id=19827
  // ex: Galaxy Tab P3110 + Android 4.1.1
  pointerEvent.preventDefault();
};


/**
 * Wrap and relay a pointer event.  Note that this requires that the type
 * string for the MapBrowserPointerEvent matches the PointerEvent type.
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.relayEvent_ = function(pointerEvent) {
  var dragging = !!(this.down_ && this.isMoving_(pointerEvent));
  this.dispatchEvent(new ol.MapBrowserPointerEvent(
      pointerEvent.type, this.map_, pointerEvent, dragging));
};


/**
 * @param {ol.pointer.PointerEvent} pointerEvent Pointer event.
 * @return {boolean} Is moving.
 * @private
 */
ol.MapBrowserEventHandler.prototype.isMoving_ = function(pointerEvent) {
  return pointerEvent.clientX != this.down_.clientX ||
      pointerEvent.clientY != this.down_.clientY;
};


/**
 * @inheritDoc
 */
ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
  if (this.relayedListenerKey_) {
    ol.events.unlistenByKey(this.relayedListenerKey_);
    this.relayedListenerKey_ = null;
  }
  if (this.pointerdownListenerKey_) {
    ol.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;
  }

  this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
  this.dragListenerKeys_.length = 0;

  if (this.documentPointerEventHandler_) {
    this.documentPointerEventHandler_.dispose();
    this.documentPointerEventHandler_ = null;
  }
  if (this.pointerEventHandler_) {
    this.pointerEventHandler_.dispose();
    this.pointerEventHandler_ = null;
  }
  ol.events.EventTarget.prototype.disposeInternal.call(this);
};


/**
 * Constants for event names.
 * @enum {string}
 */
ol.MapBrowserEvent.EventType = {

  /**
   * A true single click with no dragging and no double click. Note that this
   * event is delayed by 250 ms to ensure that it is not a double click.
   * @event ol.MapBrowserEvent#singleclick
   * @api stable
   */
  SINGLECLICK: 'singleclick',

  /**
   * A click with no dragging. A double click will fire two of this.
   * @event ol.MapBrowserEvent#click
   * @api stable
   */
  CLICK: ol.events.EventType.CLICK,

  /**
   * A true double click, with no dragging.
   * @event ol.MapBrowserEvent#dblclick
   * @api stable
   */
  DBLCLICK: ol.events.EventType.DBLCLICK,

  /**
   * Triggered when a pointer is dragged.
   * @event ol.MapBrowserEvent#pointerdrag
   * @api
   */
  POINTERDRAG: 'pointerdrag',

  /**
   * Triggered when a pointer is moved. Note that on touch devices this is
   * triggered when the map is panned, so is not the same as mousemove.
   * @event ol.MapBrowserEvent#pointermove
   * @api stable
   */
  POINTERMOVE: 'pointermove',

  POINTERDOWN: 'pointerdown',
  POINTERUP: 'pointerup',
  POINTEROVER: 'pointerover',
  POINTEROUT: 'pointerout',
  POINTERENTER: 'pointerenter',
  POINTERLEAVE: 'pointerleave',
  POINTERCANCEL: 'pointercancel'
};

goog.provide('ol.layer.Base');
goog.provide('ol.layer.LayerProperty');

goog.require('ol');
goog.require('ol.Object');
goog.require('ol.math');
goog.require('ol.obj');


/**
 * @enum {string}
 */
ol.layer.LayerProperty = {
  OPACITY: 'opacity',
  VISIBLE: 'visible',
  EXTENT: 'extent',
  Z_INDEX: 'zIndex',
  MAX_RESOLUTION: 'maxResolution',
  MIN_RESOLUTION: 'minResolution',
  SOURCE: 'source'
};


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Note that with `ol.layer.Base` and all its subclasses, any property set in
 * the options is set as a {@link ol.Object} property on the layer object, so
 * is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.layer.BaseOptions} options Layer options.
 * @api stable
 */
ol.layer.Base = function(options) {

  ol.Object.call(this);

  /**
   * @type {Object.<string, *>}
   */
  var properties = ol.obj.assign({}, options);
  properties[ol.layer.LayerProperty.OPACITY] =
      options.opacity !== undefined ? options.opacity : 1;
  properties[ol.layer.LayerProperty.VISIBLE] =
      options.visible !== undefined ? options.visible : true;
  properties[ol.layer.LayerProperty.Z_INDEX] =
      options.zIndex !== undefined ? options.zIndex : 0;
  properties[ol.layer.LayerProperty.MAX_RESOLUTION] =
      options.maxResolution !== undefined ? options.maxResolution : Infinity;
  properties[ol.layer.LayerProperty.MIN_RESOLUTION] =
      options.minResolution !== undefined ? options.minResolution : 0;

  this.setProperties(properties);
};
ol.inherits(ol.layer.Base, ol.Object);


/**
 * @return {ol.LayerState} Layer state.
 */
ol.layer.Base.prototype.getLayerState = function() {
  var opacity = this.getOpacity();
  var sourceState = this.getSourceState();
  var visible = this.getVisible();
  var extent = this.getExtent();
  var zIndex = this.getZIndex();
  var maxResolution = this.getMaxResolution();
  var minResolution = this.getMinResolution();
  return {
    layer: /** @type {ol.layer.Layer} */ (this),
    opacity: ol.math.clamp(opacity, 0, 1),
    sourceState: sourceState,
    visible: visible,
    managed: true,
    extent: extent,
    zIndex: zIndex,
    maxResolution: maxResolution,
    minResolution: Math.max(minResolution, 0)
  };
};


/**
 * @abstract
 * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
 *     modified in place).
 * @return {Array.<ol.layer.Layer>} Array of layers.
 */
ol.layer.Base.prototype.getLayersArray = function(opt_array) {};


/**
 * @abstract
 * @param {Array.<ol.LayerState>=} opt_states Optional list of layer
 *     states (to be modified in place).
 * @return {Array.<ol.LayerState>} List of layer states.
 */
ol.layer.Base.prototype.getLayerStatesArray = function(opt_states) {};


/**
 * Return the {@link ol.Extent extent} of the layer or `undefined` if it
 * will be visible regardless of extent.
 * @return {ol.Extent|undefined} The layer extent.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getExtent = function() {
  return /** @type {ol.Extent|undefined} */ (
      this.get(ol.layer.LayerProperty.EXTENT));
};


/**
 * Return the maximum resolution of the layer.
 * @return {number} The maximum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getMaxResolution = function() {
  return /** @type {number} */ (
      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
};


/**
 * Return the minimum resolution of the layer.
 * @return {number} The minimum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getMinResolution = function() {
  return /** @type {number} */ (
      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
};


/**
 * Return the opacity of the layer (between 0 and 1).
 * @return {number} The opacity of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getOpacity = function() {
  return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
};


/**
 * @abstract
 * @return {ol.source.State} Source state.
 */
ol.layer.Base.prototype.getSourceState = function() {};


/**
 * Return the visibility of the layer (`true` or `false`).
 * @return {boolean} The visibility of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getVisible = function() {
  return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
};


/**
 * Return the Z-index of the layer, which is used to order layers before
 * rendering. The default Z-index is 0.
 * @return {number} The Z-index of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getZIndex = function() {
  return /** @type {number} */ (this.get(ol.layer.LayerProperty.Z_INDEX));
};


/**
 * Set the extent at which the layer is visible.  If `undefined`, the layer
 * will be visible at all extents.
 * @param {ol.Extent|undefined} extent The extent of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setExtent = function(extent) {
  this.set(ol.layer.LayerProperty.EXTENT, extent);
};


/**
 * Set the maximum resolution at which the layer is visible.
 * @param {number} maxResolution The maximum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
  this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution);
};


/**
 * Set the minimum resolution at which the layer is visible.
 * @param {number} minResolution The minimum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setMinResolution = function(minResolution) {
  this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution);
};


/**
 * Set the opacity of the layer, allowed values range from 0 to 1.
 * @param {number} opacity The opacity of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setOpacity = function(opacity) {
  this.set(ol.layer.LayerProperty.OPACITY, opacity);
};


/**
 * Set the visibility of the layer (`true` or `false`).
 * @param {boolean} visible The visibility of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setVisible = function(visible) {
  this.set(ol.layer.LayerProperty.VISIBLE, visible);
};


/**
 * Set Z-index of the layer, which is used to order layers before rendering.
 * The default Z-index is 0.
 * @param {number} zindex The z-index of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setZIndex = function(zindex) {
  this.set(ol.layer.LayerProperty.Z_INDEX, zindex);
};

goog.provide('ol.render.Event');
goog.provide('ol.render.EventType');

goog.require('ol');
goog.require('ol.events.Event');


/**
 * @enum {string}
 */
ol.render.EventType = {
  /**
   * @event ol.render.Event#postcompose
   * @api
   */
  POSTCOMPOSE: 'postcompose',
  /**
   * @event ol.render.Event#precompose
   * @api
   */
  PRECOMPOSE: 'precompose',
  /**
   * @event ol.render.Event#render
   * @api
   */
  RENDER: 'render'
};


/**
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.render.Event}
 * @param {ol.render.EventType} type Type.
 * @param {ol.render.VectorContext=} opt_vectorContext Vector context.
 * @param {olx.FrameState=} opt_frameState Frame state.
 * @param {?CanvasRenderingContext2D=} opt_context Context.
 * @param {?ol.webgl.Context=} opt_glContext WebGL Context.
 */
ol.render.Event = function(
    type, opt_vectorContext, opt_frameState, opt_context,
    opt_glContext) {

  ol.events.Event.call(this, type);

  /**
   * For canvas, this is an instance of {@link ol.render.canvas.Immediate}.
   * @type {ol.render.VectorContext|undefined}
   * @api
   */
  this.vectorContext = opt_vectorContext;

  /**
   * An object representing the current render frame state.
   * @type {olx.FrameState|undefined}
   * @api
   */
  this.frameState = opt_frameState;

  /**
   * Canvas context. Only available when a Canvas renderer is used, null
   * otherwise.
   * @type {CanvasRenderingContext2D|null|undefined}
   * @api
   */
  this.context = opt_context;

  /**
   * WebGL context. Only available when a WebGL renderer is used, null
   * otherwise.
   * @type {ol.webgl.Context|null|undefined}
   * @api
   */
  this.glContext = opt_glContext;

};
ol.inherits(ol.render.Event, ol.events.Event);

goog.provide('ol.source.Source');
goog.provide('ol.source.State');

goog.require('ol');
goog.require('ol.Attribution');
goog.require('ol.Object');
goog.require('ol.proj');


/**
 * State of the source, one of 'undefined', 'loading', 'ready' or 'error'.
 * @enum {string}
 */
ol.source.State = {
  UNDEFINED: 'undefined',
  LOADING: 'loading',
  READY: 'ready',
  ERROR: 'error'
};


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for {@link ol.layer.Layer} sources.
 *
 * A generic `change` event is triggered when the state of the source changes.
 *
 * @constructor
 * @extends {ol.Object}
 * @param {ol.SourceSourceOptions} options Source options.
 * @api stable
 */
ol.source.Source = function(options) {

  ol.Object.call(this);

  /**
   * @private
   * @type {ol.proj.Projection}
   */
  this.projection_ = ol.proj.get(options.projection);

  /**
   * @private
   * @type {Array.<ol.Attribution>}
   */
  this.attributions_ = ol.source.Source.toAttributionsArray_(options.attributions);

  /**
   * @private
   * @type {string|olx.LogoOptions|undefined}
   */
  this.logo_ = options.logo;

  /**
   * @private
   * @type {ol.source.State}
   */
  this.state_ = options.state !== undefined ?
      options.state : ol.source.State.READY;

  /**
   * @private
   * @type {boolean}
   */
  this.wrapX_ = options.wrapX !== undefined ? options.wrapX : false;

};
ol.inherits(ol.source.Source, ol.Object);

/**
 * Turns various ways of defining an attribution to an array of `ol.Attributions`.
 *
 * @param {ol.AttributionLike|undefined}
 *     attributionLike The attributions as string, array of strings,
 *     `ol.Attribution`, array of `ol.Attribution` or undefined.
 * @return {Array.<ol.Attribution>} The array of `ol.Attribution` or null if
 *     `undefined` was given.
 */
ol.source.Source.toAttributionsArray_ = function(attributionLike) {
  if (typeof attributionLike === 'string') {
    return [new ol.Attribution({html: attributionLike})];
  } else if (attributionLike instanceof ol.Attribution) {
    return [attributionLike];
  } else if (Array.isArray(attributionLike)) {
    var len = attributionLike.length;
    var attributions = new Array(len);
    for (var i = 0; i < len; i++) {
      var item = attributionLike[i];
      if (typeof item === 'string') {
        attributions[i] = new ol.Attribution({html: item});
      } else {
        attributions[i] = item;
      }
    }
    return attributions;
  } else {
    return null;
  }
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
 * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
 *     callback.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.source.Source.prototype.forEachFeatureAtCoordinate = ol.nullFunction;


/**
 * Get the attributions of the source.
 * @return {Array.<ol.Attribution>} Attributions.
 * @api stable
 */
ol.source.Source.prototype.getAttributions = function() {
  return this.attributions_;
};


/**
 * Get the logo of the source.
 * @return {string|olx.LogoOptions|undefined} Logo.
 * @api stable
 */
ol.source.Source.prototype.getLogo = function() {
  return this.logo_;
};


/**
 * Get the projection of the source.
 * @return {ol.proj.Projection} Projection.
 * @api
 */
ol.source.Source.prototype.getProjection = function() {
  return this.projection_;
};


/**
 * @abstract
 * @return {Array.<number>|undefined} Resolutions.
 */
ol.source.Source.prototype.getResolutions = function() {};


/**
 * Get the state of the source, see {@link ol.source.State} for possible states.
 * @return {ol.source.State} State.
 * @api
 */
ol.source.Source.prototype.getState = function() {
  return this.state_;
};


/**
 * @return {boolean|undefined} Wrap X.
 */
ol.source.Source.prototype.getWrapX = function() {
  return this.wrapX_;
};


/**
 * Refreshes the source and finally dispatches a 'change' event.
 * @api
 */
ol.source.Source.prototype.refresh = function() {
  this.changed();
};


/**
 * Set the attributions of the source.
 * @param {ol.AttributionLike|undefined} attributions Attributions.
 *     Can be passed as `string`, `Array<string>`, `{@link ol.Attribution}`,
 *     `Array<{@link ol.Attribution}>` or `undefined`.
 * @api
 */
ol.source.Source.prototype.setAttributions = function(attributions) {
  this.attributions_ = ol.source.Source.toAttributionsArray_(attributions);
  this.changed();
};


/**
 * Set the logo of the source.
 * @param {string|olx.LogoOptions|undefined} logo Logo.
 */
ol.source.Source.prototype.setLogo = function(logo) {
  this.logo_ = logo;
};


/**
 * Set the state of the source.
 * @param {ol.source.State} state State.
 * @protected
 */
ol.source.Source.prototype.setState = function(state) {
  this.state_ = state;
  this.changed();
};

goog.provide('ol.layer.Layer');

goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol');
goog.require('ol.Object');
goog.require('ol.layer.Base');
goog.require('ol.layer.LayerProperty');
goog.require('ol.obj');
goog.require('ol.render.EventType');
goog.require('ol.source.State');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * A visual representation of raster or vector map data.
 * Layers group together those properties that pertain to how the data is to be
 * displayed, irrespective of the source of that data.
 *
 * Layers are usually added to a map with {@link ol.Map#addLayer}. Components
 * like {@link ol.interaction.Select} use unmanaged layers internally. These
 * unmanaged layers are associated with the map using
 * {@link ol.layer.Layer#setMap} instead.
 *
 * A generic `change` event is fired when the state of the source changes.
 *
 * @constructor
 * @extends {ol.layer.Base}
 * @fires ol.render.Event
 * @param {olx.layer.LayerOptions} options Layer options.
 * @api stable
 */
ol.layer.Layer = function(options) {

  var baseOptions = ol.obj.assign({}, options);
  delete baseOptions.source;

  ol.layer.Base.call(this, /** @type {olx.layer.BaseOptions} */ (baseOptions));

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.mapPrecomposeKey_ = null;

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.mapRenderKey_ = null;

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.sourceChangeKey_ = null;

  if (options.map) {
    this.setMap(options.map);
  }

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.layer.LayerProperty.SOURCE),
      this.handleSourcePropertyChange_, this);

  var source = options.source ? options.source : null;
  this.setSource(source);
};
ol.inherits(ol.layer.Layer, ol.layer.Base);


/**
 * Return `true` if the layer is visible, and if the passed resolution is
 * between the layer's minResolution and maxResolution. The comparison is
 * inclusive for `minResolution` and exclusive for `maxResolution`.
 * @param {ol.LayerState} layerState Layer state.
 * @param {number} resolution Resolution.
 * @return {boolean} The layer is visible at the given resolution.
 */
ol.layer.Layer.visibleAtResolution = function(layerState, resolution) {
  return layerState.visible && resolution >= layerState.minResolution &&
      resolution < layerState.maxResolution;
};


/**
 * @inheritDoc
 */
ol.layer.Layer.prototype.getLayersArray = function(opt_array) {
  var array = opt_array ? opt_array : [];
  array.push(this);
  return array;
};


/**
 * @inheritDoc
 */
ol.layer.Layer.prototype.getLayerStatesArray = function(opt_states) {
  var states = opt_states ? opt_states : [];
  states.push(this.getLayerState());
  return states;
};


/**
 * Get the layer source.
 * @return {ol.source.Source} The layer source (or `null` if not yet set).
 * @observable
 * @api stable
 */
ol.layer.Layer.prototype.getSource = function() {
  var source = this.get(ol.layer.LayerProperty.SOURCE);
  return /** @type {ol.source.Source} */ (source) || null;
};


/**
  * @inheritDoc
  */
ol.layer.Layer.prototype.getSourceState = function() {
  var source = this.getSource();
  return !source ? ol.source.State.UNDEFINED : source.getState();
};


/**
 * @private
 */
ol.layer.Layer.prototype.handleSourceChange_ = function() {
  this.changed();
};


/**
 * @private
 */
ol.layer.Layer.prototype.handleSourcePropertyChange_ = function() {
  if (this.sourceChangeKey_) {
    ol.events.unlistenByKey(this.sourceChangeKey_);
    this.sourceChangeKey_ = null;
  }
  var source = this.getSource();
  if (source) {
    this.sourceChangeKey_ = ol.events.listen(source,
        ol.events.EventType.CHANGE, this.handleSourceChange_, this);
  }
  this.changed();
};


/**
 * Sets the layer to be rendered on top of other layers on a map. The map will
 * not manage this layer in its layers collection, and the callback in
 * {@link ol.Map#forEachLayerAtPixel} will receive `null` as layer. This
 * is useful for temporary layers. To remove an unmanaged layer from the map,
 * use `#setMap(null)`.
 *
 * To add the layer to a map and have it managed by the map, use
 * {@link ol.Map#addLayer} instead.
 * @param {ol.Map} map Map.
 * @api
 */
ol.layer.Layer.prototype.setMap = function(map) {
  if (this.mapPrecomposeKey_) {
    ol.events.unlistenByKey(this.mapPrecomposeKey_);
    this.mapPrecomposeKey_ = null;
  }
  if (!map) {
    this.changed();
  }
  if (this.mapRenderKey_) {
    ol.events.unlistenByKey(this.mapRenderKey_);
    this.mapRenderKey_ = null;
  }
  if (map) {
    this.mapPrecomposeKey_ = ol.events.listen(
        map, ol.render.EventType.PRECOMPOSE, function(evt) {
          var layerState = this.getLayerState();
          layerState.managed = false;
          layerState.zIndex = Infinity;
          evt.frameState.layerStatesArray.push(layerState);
          evt.frameState.layerStates[ol.getUid(this)] = layerState;
        }, this);
    this.mapRenderKey_ = ol.events.listen(
        this, ol.events.EventType.CHANGE, map.render, map);
    this.changed();
  }
};


/**
 * Set the layer source.
 * @param {ol.source.Source} source The layer source.
 * @observable
 * @api stable
 */
ol.layer.Layer.prototype.setSource = function(source) {
  this.set(ol.layer.LayerProperty.SOURCE, source);
};

goog.provide('ol.style.IconImageCache');

goog.require('ol.color');


/**
 * @constructor
 */
ol.style.IconImageCache = function() {

  /**
   * @type {Object.<string, ol.style.IconImage>}
   * @private
   */
  this.cache_ = {};

  /**
   * @type {number}
   * @private
   */
  this.cacheSize_ = 0;

  /**
   * @const
   * @type {number}
   * @private
   */
  this.maxCacheSize_ = 32;
};


/**
 * @param {string} src Src.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.Color} color Color.
 * @return {string} Cache key.
 */
ol.style.IconImageCache.getKey = function(src, crossOrigin, color) {
  goog.DEBUG && console.assert(crossOrigin !== undefined,
      'argument crossOrigin must be defined');
  var colorString = color ? ol.color.asString(color) : 'null';
  return crossOrigin + ':' + src + ':' + colorString;
};


/**
 * FIXME empty description for jsdoc
 */
ol.style.IconImageCache.prototype.clear = function() {
  this.cache_ = {};
  this.cacheSize_ = 0;
};


/**
 * FIXME empty description for jsdoc
 */
ol.style.IconImageCache.prototype.expire = function() {
  if (this.cacheSize_ > this.maxCacheSize_) {
    var i = 0;
    var key, iconImage;
    for (key in this.cache_) {
      iconImage = this.cache_[key];
      if ((i++ & 3) === 0 && !iconImage.hasListener()) {
        delete this.cache_[key];
        --this.cacheSize_;
      }
    }
  }
};


/**
 * @param {string} src Src.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.Color} color Color.
 * @return {ol.style.IconImage} Icon image.
 */
ol.style.IconImageCache.prototype.get = function(src, crossOrigin, color) {
  var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
  return key in this.cache_ ? this.cache_[key] : null;
};


/**
 * @param {string} src Src.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.Color} color Color.
 * @param {ol.style.IconImage} iconImage Icon image.
 */
ol.style.IconImageCache.prototype.set = function(src, crossOrigin, color,
                                                 iconImage) {
  var key = ol.style.IconImageCache.getKey(src, crossOrigin, color);
  this.cache_[key] = iconImage;
  ++this.cacheSize_;
};

goog.provide('ol.style');

goog.require('ol.style.IconImageCache');

ol.style.iconImageCache = new ol.style.IconImageCache();

goog.provide('ol.transform');

goog.require('ol.asserts');


/**
 * Collection of affine 2d transformation functions. The functions work on an
 * array of 6 elements. The element order is compatible with the [SVGMatrix
 * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is
 * a subset (elements a to f) of a 3x3 martrix:
 * ```
 * [ a c e ]
 * [ b d f ]
 * [ 0 0 1 ]
 * ```
 */


/**
 * @private
 * @type {ol.Transform}
 */
ol.transform.tmp_ = new Array(6);


/**
 * Create an identity transform.
 * @return {!ol.Transform} Identity transform.
 */
ol.transform.create = function() {
  return [1, 0, 0, 1, 0, 0];
};


/**
 * Resets the given transform to an identity transform.
 * @param {!ol.Transform} transform Transform.
 * @return {!ol.Transform} Transform.
 */
ol.transform.reset = function(transform) {
  return ol.transform.set(transform, 1, 0, 0, 1, 0, 0);
};


/**
 * Multiply the underlying matrices of two transforms and return the result in
 * the first transform.
 * @param {!ol.Transform} transform1 Transform parameters of matrix 1.
 * @param {!ol.Transform} transform2 Transform parameters of matrix 2.
 * @return {!ol.Transform} transform1 multiplied with transform2.
 */
ol.transform.multiply = function(transform1, transform2) {
  var a1 = transform1[0];
  var b1 = transform1[1];
  var c1 = transform1[2];
  var d1 = transform1[3];
  var e1 = transform1[4];
  var f1 = transform1[5];
  var a2 = transform2[0];
  var b2 = transform2[1];
  var c2 = transform2[2];
  var d2 = transform2[3];
  var e2 = transform2[4];
  var f2 = transform2[5];

  transform1[0] = a1 * a2 + c1 * b2;
  transform1[1] = b1 * a2 + d1 * b2;
  transform1[2] = a1 * c2 + c1 * d2;
  transform1[3] = b1 * c2 + d1 * d2;
  transform1[4] = a1 * e2 + c1 * f2 + e1;
  transform1[5] = b1 * e2 + d1 * f2 + f1;

  return transform1;
};

/**
 * Set the transform components a-f on a given transform.
 * @param {!ol.Transform} transform Transform.
 * @param {number} a The a component of the transform.
 * @param {number} b The b component of the transform.
 * @param {number} c The c component of the transform.
 * @param {number} d The d component of the transform.
 * @param {number} e The e component of the transform.
 * @param {number} f The f component of the transform.
 * @return {!ol.Transform} Matrix with transform applied.
 */
ol.transform.set = function(transform, a, b, c, d, e, f) {
  transform[0] = a;
  transform[1] = b;
  transform[2] = c;
  transform[3] = d;
  transform[4] = e;
  transform[5] = f;
  return transform;
};


/**
 * Set transform on one matrix from another matrix.
 * @param {!ol.Transform} transform1 Matrix to set transform to.
 * @param {!ol.Transform} transform2 Matrix to set transform from.
 * @return {!ol.Transform} transform1 with transform from transform2 applied.
 */
ol.transform.setFromArray = function(transform1, transform2) {
  transform1[0] = transform2[0];
  transform1[1] = transform2[1];
  transform1[2] = transform2[2];
  transform1[3] = transform2[3];
  transform1[4] = transform2[4];
  transform1[5] = transform2[5];
  return transform1;
};


/**
 * Transforms the given coordinate with the given transform returning the
 * resulting, transformed coordinate. The coordinate will be modified in-place.
 *
 * @param {ol.Transform} transform The transformation.
 * @param {ol.Coordinate|ol.Pixel} coordinate The coordinate to transform.
 * @return {ol.Coordinate|ol.Pixel} return coordinate so that operations can be
 *     chained together.
 */
ol.transform.apply = function(transform, coordinate) {
  var x = coordinate[0], y = coordinate[1];
  coordinate[0] = transform[0] * x + transform[2] * y + transform[4];
  coordinate[1] = transform[1] * x + transform[3] * y + transform[5];
  return coordinate;
};


/**
 * Applies rotation to the given transform.
 * @param {!ol.Transform} transform Transform.
 * @param {number} angle Angle in radians.
 * @return {!ol.Transform} The rotated transform.
 */
ol.transform.rotate = function(transform, angle) {
  var cos = Math.cos(angle);
  var sin = Math.sin(angle);
  return ol.transform.multiply(transform,
      ol.transform.set(ol.transform.tmp_, cos, sin, -sin, cos, 0, 0));
};


/**
 * Applies scale to a given transform.
 * @param {!ol.Transform} transform Transform.
 * @param {number} x Scale factor x.
 * @param {number} y Scale factor y.
 * @return {!ol.Transform} The scaled transform.
 */
ol.transform.scale = function(transform, x, y) {
  return ol.transform.multiply(transform,
      ol.transform.set(ol.transform.tmp_, x, 0, 0, y, 0, 0));
};


/**
 * Applies translation to the given transform.
 * @param {!ol.Transform} transform Transform.
 * @param {number} dx Translation x.
 * @param {number} dy Translation y.
 * @return {!ol.Transform} The translated transform.
 */
ol.transform.translate = function(transform, dx, dy) {
  return ol.transform.multiply(transform,
      ol.transform.set(ol.transform.tmp_, 1, 0, 0, 1, dx, dy));
};


/**
 * Creates a composite transform given an initial translation, scale, rotation, and
 * final translation (in that order only, not commutative).
 * @param {!ol.Transform} transform The transform (will be modified in place).
 * @param {number} dx1 Initial translation x.
 * @param {number} dy1 Initial translation y.
 * @param {number} sx Scale factor x.
 * @param {number} sy Scale factor y.
 * @param {number} angle Rotation (in counter-clockwise radians).
 * @param {number} dx2 Final translation x.
 * @param {number} dy2 Final translation y.
 * @return {!ol.Transform} The composite transform.
 */
ol.transform.compose = function(transform, dx1, dy1, sx, sy, angle, dx2, dy2) {
  var sin = Math.sin(angle);
  var cos = Math.cos(angle);
  transform[0] = sx * cos;
  transform[1] = sy * sin;
  transform[2] = -sx * sin;
  transform[3] = sy * cos;
  transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1;
  transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1;
  return transform;
};


/**
 * Invert the given transform.
 * @param {!ol.Transform} transform Transform.
 * @return {!ol.Transform} Inverse of the transform.
 */
ol.transform.invert = function(transform) {
  var det = ol.transform.determinant(transform);
  ol.asserts.assert(det !== 0, 32); // Transformation matrix cannot be inverted

  var a = transform[0];
  var b = transform[1];
  var c = transform[2];
  var d = transform[3];
  var e = transform[4];
  var f = transform[5];

  transform[0] = d / det;
  transform[1] = -b / det;
  transform[2] = -c / det;
  transform[3] = a / det;
  transform[4] = (c * f - d * e) / det;
  transform[5] = -(a * f - b * e) / det;

  return transform;
};


/**
 * Returns the determinant of the given matrix.
 * @param {!ol.Transform} mat Matrix.
 * @return {number} Determinant.
 */
ol.transform.determinant = function(mat) {
  return mat[0] * mat[3] - mat[1] * mat[2];
};

goog.provide('ol.renderer.Map');
goog.provide('ol.RendererType');

goog.require('ol');
goog.require('ol.Disposable');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.functions');
goog.require('ol.layer.Layer');
goog.require('ol.style');
goog.require('ol.transform');


/**
 * Available renderers: `'canvas'`, `'dom'` or `'webgl'`.
 * @enum {string}
 */
ol.RendererType = {
  CANVAS: 'canvas',
  DOM: 'dom',
  WEBGL: 'webgl'
};


/**
 * @constructor
 * @extends {ol.Disposable}
 * @param {Element} container Container.
 * @param {ol.Map} map Map.
 * @struct
 */
ol.renderer.Map = function(container, map) {

  ol.Disposable.call(this);


  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = map;

  /**
   * @private
   * @type {Object.<string, ol.renderer.Layer>}
   */
  this.layerRenderers_ = {};

  /**
   * @private
   * @type {Object.<string, ol.EventsKey>}
   */
  this.layerRendererListeners_ = {};

};
ol.inherits(ol.renderer.Map, ol.Disposable);


/**
 * @param {olx.FrameState} frameState FrameState.
 * @protected
 */
ol.renderer.Map.prototype.calculateMatrices2D = function(frameState) {
  var viewState = frameState.viewState;
  var coordinateToPixelTransform = frameState.coordinateToPixelTransform;
  var pixelToCoordinateTransform = frameState.pixelToCoordinateTransform;
  goog.DEBUG && console.assert(coordinateToPixelTransform,
      'frameState has a coordinateToPixelTransform');

  ol.transform.compose(coordinateToPixelTransform,
      frameState.size[0] / 2, frameState.size[1] / 2,
      1 / viewState.resolution, -1 / viewState.resolution,
      -viewState.rotation,
      -viewState.center[0], -viewState.center[1]);

  ol.transform.invert(
      ol.transform.setFromArray(pixelToCoordinateTransform, coordinateToPixelTransform));
};


/**
 * @abstract
 * @param {ol.layer.Layer} layer Layer.
 * @protected
 * @return {ol.renderer.Layer} layerRenderer Layer renderer.
 */
ol.renderer.Map.prototype.createLayerRenderer = function(layer) {};


/**
 * @inheritDoc
 */
ol.renderer.Map.prototype.disposeInternal = function() {
  for (var id in this.layerRenderers_) {
    this.layerRenderers_[id].dispose();
  }
};


/**
 * @param {ol.Map} map Map.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.Map.expireIconCache_ = function(map, frameState) {
  var cache = ol.style.iconImageCache;
  cache.expire();
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, (ol.Feature|ol.render.Feature),
 *     ol.layer.Layer): T} callback Feature callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
 *     function, only layers which are visible and for which this function
 *     returns `true` will be tested for features.  By default, all visible
 *     layers will be tested.
 * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg,
        layerFilter, thisArg2) {
  var result;
  var viewState = frameState.viewState;
  var viewResolution = viewState.resolution;

  /**
   * @param {ol.Feature|ol.render.Feature} feature Feature.
   * @param {ol.layer.Layer} layer Layer.
   * @return {?} Callback result.
   */
  function forEachFeatureAtCoordinate(feature, layer) {
    var key = ol.getUid(feature).toString();
    var managed = frameState.layerStates[ol.getUid(layer)].managed;
    if (!(key in frameState.skippedFeatureUids && !managed)) {
      return callback.call(thisArg, feature, managed ? layer : null);
    }
  }

  var projection = viewState.projection;

  var translatedCoordinate = coordinate;
  if (projection.canWrapX()) {
    var projectionExtent = projection.getExtent();
    var worldWidth = ol.extent.getWidth(projectionExtent);
    var x = coordinate[0];
    if (x < projectionExtent[0] || x > projectionExtent[2]) {
      var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
      translatedCoordinate = [x + worldWidth * worldsAway, coordinate[1]];
    }
  }

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
        layerFilter.call(thisArg2, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      if (layer.getSource()) {
        result = layerRenderer.forEachFeatureAtCoordinate(
            layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
            frameState, forEachFeatureAtCoordinate, thisArg);
      }
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
 *     function, only layers which are visible and for which this function
 *     returns `true` will be tested for features.  By default, all visible
 *     layers will be tested.
 * @param {U} thisArg2 Value to use as `this` when executing `layerFilter`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
        layerFilter, thisArg2) {
  var result;
  var viewState = frameState.viewState;
  var viewResolution = viewState.resolution;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
        layerFilter.call(thisArg2, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      result = layerRenderer.forEachLayerAtPixel(
          pixel, frameState, callback, thisArg);
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
 *     function, only layers which are visible and for which this function
 *     returns `true` will be tested for features.  By default, all visible
 *     layers will be tested.
 * @param {U} thisArg Value to use as `this` when executing `layerFilter`.
 * @return {boolean} Is there a feature at the given coordinate?
 * @template U
 */
ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) {
  var hasFeature = this.forEachFeatureAtCoordinate(
      coordinate, frameState, ol.functions.TRUE, this, layerFilter, thisArg);

  return hasFeature !== undefined;
};


/**
 * @param {ol.layer.Layer} layer Layer.
 * @protected
 * @return {ol.renderer.Layer} Layer renderer.
 */
ol.renderer.Map.prototype.getLayerRenderer = function(layer) {
  var layerKey = ol.getUid(layer).toString();
  if (layerKey in this.layerRenderers_) {
    return this.layerRenderers_[layerKey];
  } else {
    var layerRenderer = this.createLayerRenderer(layer);
    this.layerRenderers_[layerKey] = layerRenderer;
    this.layerRendererListeners_[layerKey] = ol.events.listen(layerRenderer,
        ol.events.EventType.CHANGE, this.handleLayerRendererChange_, this);

    return layerRenderer;
  }
};


/**
 * @param {string} layerKey Layer key.
 * @protected
 * @return {ol.renderer.Layer} Layer renderer.
 */
ol.renderer.Map.prototype.getLayerRendererByKey = function(layerKey) {
  goog.DEBUG && console.assert(layerKey in this.layerRenderers_,
      'given layerKey (%s) exists in layerRenderers', layerKey);
  return this.layerRenderers_[layerKey];
};


/**
 * @protected
 * @return {Object.<string, ol.renderer.Layer>} Layer renderers.
 */
ol.renderer.Map.prototype.getLayerRenderers = function() {
  return this.layerRenderers_;
};


/**
 * @return {ol.Map} Map.
 */
ol.renderer.Map.prototype.getMap = function() {
  return this.map_;
};


/**
 * @abstract
 * @return {string} Type
 */
ol.renderer.Map.prototype.getType = function() {};


/**
 * Handle changes in a layer renderer.
 * @private
 */
ol.renderer.Map.prototype.handleLayerRendererChange_ = function() {
  this.map_.render();
};


/**
 * @param {string} layerKey Layer key.
 * @return {ol.renderer.Layer} Layer renderer.
 * @private
 */
ol.renderer.Map.prototype.removeLayerRendererByKey_ = function(layerKey) {
  goog.DEBUG && console.assert(layerKey in this.layerRenderers_,
      'given layerKey (%s) exists in layerRenderers', layerKey);
  var layerRenderer = this.layerRenderers_[layerKey];
  delete this.layerRenderers_[layerKey];

  goog.DEBUG && console.assert(layerKey in this.layerRendererListeners_,
      'given layerKey (%s) exists in layerRendererListeners', layerKey);
  ol.events.unlistenByKey(this.layerRendererListeners_[layerKey]);
  delete this.layerRendererListeners_[layerKey];

  return layerRenderer;
};


/**
 * Render.
 * @param {?olx.FrameState} frameState Frame state.
 */
ol.renderer.Map.prototype.renderFrame = ol.nullFunction;


/**
 * @param {ol.Map} map Map.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.Map.prototype.removeUnusedLayerRenderers_ = function(map, frameState) {
  var layerKey;
  for (layerKey in this.layerRenderers_) {
    if (!frameState || !(layerKey in frameState.layerStates)) {
      this.removeLayerRendererByKey_(layerKey).dispose();
    }
  }
};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @protected
 */
ol.renderer.Map.prototype.scheduleExpireIconCache = function(frameState) {
  frameState.postRenderFunctions.push(
    /** @type {ol.PostRenderFunction} */ (ol.renderer.Map.expireIconCache_)
  );
};


/**
 * @param {!olx.FrameState} frameState Frame state.
 * @protected
 */
ol.renderer.Map.prototype.scheduleRemoveUnusedLayerRenderers = function(frameState) {
  var layerKey;
  for (layerKey in this.layerRenderers_) {
    if (!(layerKey in frameState.layerStates)) {
      frameState.postRenderFunctions.push(
        /** @type {ol.PostRenderFunction} */ (this.removeUnusedLayerRenderers_.bind(this))
      );
      return;
    }
  }
};


/**
 * @param {ol.LayerState} state1 First layer state.
 * @param {ol.LayerState} state2 Second layer state.
 * @return {number} The zIndex difference.
 */
ol.renderer.Map.sortByZIndex = function(state1, state2) {
  return state1.zIndex - state2.zIndex;
};

goog.provide('ol.Tile');

goog.require('ol');
goog.require('ol.events.EventTarget');
goog.require('ol.events.EventType');


/**
 * @classdesc
 * Base class for tiles.
 *
 * @constructor
 * @extends {ol.events.EventTarget}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Tile.State} state State.
 */
ol.Tile = function(tileCoord, state) {

  ol.events.EventTarget.call(this);

  /**
   * @type {ol.TileCoord}
   */
  this.tileCoord = tileCoord;

  /**
   * @protected
   * @type {ol.Tile.State}
   */
  this.state = state;

  /**
   * An "interim" tile for this tile. The interim tile may be used while this
   * one is loading, for "smooth" transitions when changing params/dimensions
   * on the source.
   * @type {ol.Tile}
   */
  this.interimTile = null;

  /**
   * A key assigned to the tile. This is used by the tile source to determine
   * if this tile can effectively be used, or if a new tile should be created
   * and this one be used as an interim tile for this new tile.
   * @type {string}
   */
  this.key = '';

};
ol.inherits(ol.Tile, ol.events.EventTarget);


/**
 * @protected
 */
ol.Tile.prototype.changed = function() {
  this.dispatchEvent(ol.events.EventType.CHANGE);
};


/**
 * Get the HTML image element for this tile (may be a Canvas, Image, or Video).
 * @abstract
 * @param {Object=} opt_context Object.
 * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image.
 */
ol.Tile.prototype.getImage = function(opt_context) {};


/**
 * @return {string} Key.
 */
ol.Tile.prototype.getKey = function() {
  return this.key + '/' + this.tileCoord;
};


/**
 * Get the tile coordinate for this tile.
 * @return {ol.TileCoord} The tile coordinate.
 * @api
 */
ol.Tile.prototype.getTileCoord = function() {
  return this.tileCoord;
};


/**
 * @return {ol.Tile.State} State.
 */
ol.Tile.prototype.getState = function() {
  return this.state;
};


/**
 * Load the image or retry if loading previously failed.
 * Loading is taken care of by the tile queue, and calling this method is
 * only needed for preloading or for reloading in case of an error.
 * @abstract
 * @api
 */
ol.Tile.prototype.load = function() {};


/**
 * @enum {number}
 */
ol.Tile.State = {
  IDLE: 0,
  LOADING: 1,
  LOADED: 2,
  ERROR: 3,
  EMPTY: 4,
  ABORT: 5
};

goog.provide('ol.structs.PriorityQueue');

goog.require('ol.asserts');
goog.require('ol.obj');


/**
 * Priority queue.
 *
 * The implementation is inspired from the Closure Library's Heap class and
 * Python's heapq module.
 *
 * @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html
 * @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py
 *
 * @constructor
 * @param {function(T): number} priorityFunction Priority function.
 * @param {function(T): string} keyFunction Key function.
 * @struct
 * @template T
 */
ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {

  /**
   * @type {function(T): number}
   * @private
   */
  this.priorityFunction_ = priorityFunction;

  /**
   * @type {function(T): string}
   * @private
   */
  this.keyFunction_ = keyFunction;

  /**
   * @type {Array.<T>}
   * @private
   */
  this.elements_ = [];

  /**
   * @type {Array.<number>}
   * @private
   */
  this.priorities_ = [];

  /**
   * @type {Object.<string, boolean>}
   * @private
   */
  this.queuedElements_ = {};

};


/**
 * @const
 * @type {number}
 */
ol.structs.PriorityQueue.DROP = Infinity;


if (goog.DEBUG) {
  /**
   * FIXME empty description for jsdoc
   */
  ol.structs.PriorityQueue.prototype.assertValid = function() {
    var elements = this.elements_;
    var priorities = this.priorities_;
    var n = elements.length;
    console.assert(priorities.length == n);
    var i, priority;
    for (i = 0; i < (n >> 1) - 1; ++i) {
      priority = priorities[i];
      console.assert(priority <= priorities[this.getLeftChildIndex_(i)],
          'priority smaller than or equal to priority of left child (%s <= %s)',
          priority, priorities[this.getLeftChildIndex_(i)]);
      console.assert(priority <= priorities[this.getRightChildIndex_(i)],
          'priority smaller than or equal to priority of right child (%s <= %s)',
          priority, priorities[this.getRightChildIndex_(i)]);
    }
  };
}


/**
 * FIXME empty description for jsdoc
 */
ol.structs.PriorityQueue.prototype.clear = function() {
  this.elements_.length = 0;
  this.priorities_.length = 0;
  ol.obj.clear(this.queuedElements_);
};


/**
 * Remove and return the highest-priority element. O(log N).
 * @return {T} Element.
 */
ol.structs.PriorityQueue.prototype.dequeue = function() {
  var elements = this.elements_;
  goog.DEBUG && console.assert(elements.length > 0,
      'must have elements in order to be able to dequeue');
  var priorities = this.priorities_;
  var element = elements[0];
  if (elements.length == 1) {
    elements.length = 0;
    priorities.length = 0;
  } else {
    elements[0] = elements.pop();
    priorities[0] = priorities.pop();
    this.siftUp_(0);
  }
  var elementKey = this.keyFunction_(element);
  goog.DEBUG && console.assert(elementKey in this.queuedElements_,
      'key %s is not listed as queued', elementKey);
  delete this.queuedElements_[elementKey];
  return element;
};


/**
 * Enqueue an element. O(log N).
 * @param {T} element Element.
 * @return {boolean} The element was added to the queue.
 */
ol.structs.PriorityQueue.prototype.enqueue = function(element) {
  ol.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_),
      31); // Tried to enqueue an `element` that was already added to the queue
  var priority = this.priorityFunction_(element);
  if (priority != ol.structs.PriorityQueue.DROP) {
    this.elements_.push(element);
    this.priorities_.push(priority);
    this.queuedElements_[this.keyFunction_(element)] = true;
    this.siftDown_(0, this.elements_.length - 1);
    return true;
  }
  return false;
};


/**
 * @return {number} Count.
 */
ol.structs.PriorityQueue.prototype.getCount = function() {
  return this.elements_.length;
};


/**
 * Gets the index of the left child of the node at the given index.
 * @param {number} index The index of the node to get the left child for.
 * @return {number} The index of the left child.
 * @private
 */
ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
  return index * 2 + 1;
};


/**
 * Gets the index of the right child of the node at the given index.
 * @param {number} index The index of the node to get the right child for.
 * @return {number} The index of the right child.
 * @private
 */
ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
  return index * 2 + 2;
};


/**
 * Gets the index of the parent of the node at the given index.
 * @param {number} index The index of the node to get the parent for.
 * @return {number} The index of the parent.
 * @private
 */
ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
  return (index - 1) >> 1;
};


/**
 * Make this a heap. O(N).
 * @private
 */
ol.structs.PriorityQueue.prototype.heapify_ = function() {
  var i;
  for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
    this.siftUp_(i);
  }
};


/**
 * @return {boolean} Is empty.
 */
ol.structs.PriorityQueue.prototype.isEmpty = function() {
  return this.elements_.length === 0;
};


/**
 * @param {string} key Key.
 * @return {boolean} Is key queued.
 */
ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
  return key in this.queuedElements_;
};


/**
 * @param {T} element Element.
 * @return {boolean} Is queued.
 */
ol.structs.PriorityQueue.prototype.isQueued = function(element) {
  return this.isKeyQueued(this.keyFunction_(element));
};


/**
 * @param {number} index The index of the node to move down.
 * @private
 */
ol.structs.PriorityQueue.prototype.siftUp_ = function(index) {
  var elements = this.elements_;
  var priorities = this.priorities_;
  var count = elements.length;
  var element = elements[index];
  var priority = priorities[index];
  var startIndex = index;

  while (index < (count >> 1)) {
    var lIndex = this.getLeftChildIndex_(index);
    var rIndex = this.getRightChildIndex_(index);

    var smallerChildIndex = rIndex < count &&
        priorities[rIndex] < priorities[lIndex] ?
        rIndex : lIndex;

    elements[index] = elements[smallerChildIndex];
    priorities[index] = priorities[smallerChildIndex];
    index = smallerChildIndex;
  }

  elements[index] = element;
  priorities[index] = priority;
  this.siftDown_(startIndex, index);
};


/**
 * @param {number} startIndex The index of the root.
 * @param {number} index The index of the node to move up.
 * @private
 */
ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) {
  var elements = this.elements_;
  var priorities = this.priorities_;
  var element = elements[index];
  var priority = priorities[index];

  while (index > startIndex) {
    var parentIndex = this.getParentIndex_(index);
    if (priorities[parentIndex] > priority) {
      elements[index] = elements[parentIndex];
      priorities[index] = priorities[parentIndex];
      index = parentIndex;
    } else {
      break;
    }
  }
  elements[index] = element;
  priorities[index] = priority;
};


/**
 * FIXME empty description for jsdoc
 */
ol.structs.PriorityQueue.prototype.reprioritize = function() {
  var priorityFunction = this.priorityFunction_;
  var elements = this.elements_;
  var priorities = this.priorities_;
  var index = 0;
  var n = elements.length;
  var element, i, priority;
  for (i = 0; i < n; ++i) {
    element = elements[i];
    priority = priorityFunction(element);
    if (priority == ol.structs.PriorityQueue.DROP) {
      delete this.queuedElements_[this.keyFunction_(element)];
    } else {
      priorities[index] = priority;
      elements[index++] = element;
    }
  }
  elements.length = index;
  priorities.length = index;
  this.heapify_();
};

goog.provide('ol.TileQueue');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.structs.PriorityQueue');


/**
 * @constructor
 * @extends {ol.structs.PriorityQueue.<Array>}
 * @param {ol.TilePriorityFunction} tilePriorityFunction
 *     Tile priority function.
 * @param {function(): ?} tileChangeCallback
 *     Function called on each tile change event.
 * @struct
 */
ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {

  ol.structs.PriorityQueue.call(
      this,
      /**
       * @param {Array} element Element.
       * @return {number} Priority.
       */
      function(element) {
        return tilePriorityFunction.apply(null, element);
      },
      /**
       * @param {Array} element Element.
       * @return {string} Key.
       */
      function(element) {
        return /** @type {ol.Tile} */ (element[0]).getKey();
      });

  /**
   * @private
   * @type {function(): ?}
   */
  this.tileChangeCallback_ = tileChangeCallback;

  /**
   * @private
   * @type {number}
   */
  this.tilesLoading_ = 0;

  /**
   * @private
   * @type {!Object.<string,boolean>}
   */
  this.tilesLoadingKeys_ = {};

};
ol.inherits(ol.TileQueue, ol.structs.PriorityQueue);


/**
 * @inheritDoc
 */
ol.TileQueue.prototype.enqueue = function(element) {
  var added = ol.structs.PriorityQueue.prototype.enqueue.call(this, element);
  if (added) {
    var tile = element[0];
    ol.events.listen(tile, ol.events.EventType.CHANGE,
        this.handleTileChange, this);
  }
  return added;
};


/**
 * @return {number} Number of tiles loading.
 */
ol.TileQueue.prototype.getTilesLoading = function() {
  return this.tilesLoading_;
};


/**
 * @param {ol.events.Event} event Event.
 * @protected
 */
ol.TileQueue.prototype.handleTileChange = function(event) {
  var tile = /** @type {ol.Tile} */ (event.target);
  var state = tile.getState();
  if (state === ol.Tile.State.LOADED || state === ol.Tile.State.ERROR ||
      state === ol.Tile.State.EMPTY || state === ol.Tile.State.ABORT) {
    ol.events.unlisten(tile, ol.events.EventType.CHANGE,
        this.handleTileChange, this);
    var tileKey = tile.getKey();
    if (tileKey in this.tilesLoadingKeys_) {
      delete this.tilesLoadingKeys_[tileKey];
      --this.tilesLoading_;
    }
    this.tileChangeCallback_();
  }
  goog.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_);
};


/**
 * @param {number} maxTotalLoading Maximum number tiles to load simultaneously.
 * @param {number} maxNewLoads Maximum number of new tiles to load.
 */
ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) {
  var newLoads = 0;
  var tile, tileKey;
  while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
         this.getCount() > 0) {
    tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
    tileKey = tile.getKey();
    if (tile.getState() === ol.Tile.State.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
      this.tilesLoadingKeys_[tileKey] = true;
      ++this.tilesLoading_;
      ++newLoads;
      tile.load();
    }
    goog.DEBUG && console.assert(Object.keys(this.tilesLoadingKeys_).length === this.tilesLoading_);
  }
};

goog.provide('ol.Kinetic');

goog.require('ol.animation');


/**
 * @classdesc
 * Implementation of inertial deceleration for map movement.
 *
 * @constructor
 * @param {number} decay Rate of decay (must be negative).
 * @param {number} minVelocity Minimum velocity (pixels/millisecond).
 * @param {number} delay Delay to consider to calculate the kinetic
 *     initial values (milliseconds).
 * @struct
 * @api
 */
ol.Kinetic = function(decay, minVelocity, delay) {

  /**
   * @private
   * @type {number}
   */
  this.decay_ = decay;

  /**
   * @private
   * @type {number}
   */
  this.minVelocity_ = minVelocity;

  /**
   * @private
   * @type {number}
   */
  this.delay_ = delay;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.points_ = [];

  /**
   * @private
   * @type {number}
   */
  this.angle_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.initialVelocity_ = 0;
};


/**
 * FIXME empty description for jsdoc
 */
ol.Kinetic.prototype.begin = function() {
  this.points_.length = 0;
  this.angle_ = 0;
  this.initialVelocity_ = 0;
};


/**
 * @param {number} x X.
 * @param {number} y Y.
 */
ol.Kinetic.prototype.update = function(x, y) {
  this.points_.push(x, y, Date.now());
};


/**
 * @return {boolean} Whether we should do kinetic animation.
 */
ol.Kinetic.prototype.end = function() {
  if (this.points_.length < 6) {
    // at least 2 points are required (i.e. there must be at least 6 elements
    // in the array)
    return false;
  }
  var delay = Date.now() - this.delay_;
  var lastIndex = this.points_.length - 3;
  if (this.points_[lastIndex + 2] < delay) {
    // the last tracked point is too old, which means that the user stopped
    // panning before releasing the map
    return false;
  }

  // get the first point which still falls into the delay time
  var firstIndex = lastIndex - 3;
  while (firstIndex > 0 && this.points_[firstIndex + 2] > delay) {
    firstIndex -= 3;
  }
  var duration = this.points_[lastIndex + 2] - this.points_[firstIndex + 2];
  var dx = this.points_[lastIndex] - this.points_[firstIndex];
  var dy = this.points_[lastIndex + 1] - this.points_[firstIndex + 1];
  this.angle_ = Math.atan2(dy, dx);
  this.initialVelocity_ = Math.sqrt(dx * dx + dy * dy) / duration;
  return this.initialVelocity_ > this.minVelocity_;
};


/**
 * @param {ol.Coordinate} source Source coordinate for the animation.
 * @return {ol.PreRenderFunction} Pre-render function for kinetic animation.
 */
ol.Kinetic.prototype.pan = function(source) {
  var decay = this.decay_;
  var initialVelocity = this.initialVelocity_;
  var velocity = this.minVelocity_ - initialVelocity;
  var duration = this.getDuration_();
  var easingFunction = (
      /**
       * @param {number} t T.
       * @return {number} Easing.
       */
      function(t) {
        return initialVelocity * (Math.exp((decay * t) * duration) - 1) /
            velocity;
      });
  return ol.animation.pan({
    source: source,
    duration: duration,
    easing: easingFunction
  });
};


/**
 * @private
 * @return {number} Duration of animation (milliseconds).
 */
ol.Kinetic.prototype.getDuration_ = function() {
  return Math.log(this.minVelocity_ / this.initialVelocity_) / this.decay_;
};


/**
 * @return {number} Total distance travelled (pixels).
 */
ol.Kinetic.prototype.getDistance = function() {
  return (this.minVelocity_ - this.initialVelocity_) / this.decay_;
};


/**
 * @return {number} Angle of the kinetic panning animation (radians).
 */
ol.Kinetic.prototype.getAngle = function() {
  return this.angle_;
};

// FIXME factor out key precondition (shift et. al)

goog.provide('ol.interaction.Interaction');
goog.provide('ol.interaction.InteractionProperty');

goog.require('ol');
goog.require('ol.Object');
goog.require('ol.animation');
goog.require('ol.easing');


/**
 * @enum {string}
 */
ol.interaction.InteractionProperty = {
  ACTIVE: 'active'
};


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * User actions that change the state of the map. Some are similar to controls,
 * but are not associated with a DOM element.
 * For example, {@link ol.interaction.KeyboardZoom} is functionally the same as
 * {@link ol.control.Zoom}, but triggered by a keyboard event not a button
 * element event.
 * Although interactions do not have a DOM element, some of them do render
 * vectors and so are visible on the screen.
 *
 * @constructor
 * @param {olx.interaction.InteractionOptions} options Options.
 * @extends {ol.Object}
 * @api
 */
ol.interaction.Interaction = function(options) {

  ol.Object.call(this);

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  this.setActive(true);

  /**
   * @type {function(ol.MapBrowserEvent):boolean}
   */
  this.handleEvent = options.handleEvent;

};
ol.inherits(ol.interaction.Interaction, ol.Object);


/**
 * Return whether the interaction is currently active.
 * @return {boolean} `true` if the interaction is active, `false` otherwise.
 * @observable
 * @api
 */
ol.interaction.Interaction.prototype.getActive = function() {
  return /** @type {boolean} */ (
      this.get(ol.interaction.InteractionProperty.ACTIVE));
};


/**
 * Get the map associated with this interaction.
 * @return {ol.Map} Map.
 * @api
 */
ol.interaction.Interaction.prototype.getMap = function() {
  return this.map_;
};


/**
 * Activate or deactivate the interaction.
 * @param {boolean} active Active.
 * @observable
 * @api
 */
ol.interaction.Interaction.prototype.setActive = function(active) {
  this.set(ol.interaction.InteractionProperty.ACTIVE, active);
};


/**
 * Remove the interaction from its current map and attach it to the new map.
 * Subclasses may set up event handlers to get notified about changes to
 * the map here.
 * @param {ol.Map} map Map.
 */
ol.interaction.Interaction.prototype.setMap = function(map) {
  this.map_ = map;
};


/**
 * @param {ol.Map} map Map.
 * @param {ol.View} view View.
 * @param {ol.Coordinate} delta Delta.
 * @param {number=} opt_duration Duration.
 */
ol.interaction.Interaction.pan = function(map, view, delta, opt_duration) {
  var currentCenter = view.getCenter();
  if (currentCenter) {
    if (opt_duration && opt_duration > 0) {
      map.beforeRender(ol.animation.pan({
        source: currentCenter,
        duration: opt_duration,
        easing: ol.easing.linear
      }));
    }
    var center = view.constrainCenter(
        [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]);
    view.setCenter(center);
  }
};


/**
 * @param {ol.Map} map Map.
 * @param {ol.View} view View.
 * @param {number|undefined} rotation Rotation.
 * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
 * @param {number=} opt_duration Duration.
 */
ol.interaction.Interaction.rotate = function(map, view, rotation, opt_anchor, opt_duration) {
  rotation = view.constrainRotation(rotation, 0);
  ol.interaction.Interaction.rotateWithoutConstraints(
      map, view, rotation, opt_anchor, opt_duration);
};


/**
 * @param {ol.Map} map Map.
 * @param {ol.View} view View.
 * @param {number|undefined} rotation Rotation.
 * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
 * @param {number=} opt_duration Duration.
 */
ol.interaction.Interaction.rotateWithoutConstraints = function(map, view, rotation, opt_anchor, opt_duration) {
  if (rotation !== undefined) {
    var currentRotation = view.getRotation();
    var currentCenter = view.getCenter();
    if (currentRotation !== undefined && currentCenter &&
        opt_duration && opt_duration > 0) {
      map.beforeRender(ol.animation.rotate({
        rotation: currentRotation,
        duration: opt_duration,
        easing: ol.easing.easeOut
      }));
      if (opt_anchor) {
        map.beforeRender(ol.animation.pan({
          source: currentCenter,
          duration: opt_duration,
          easing: ol.easing.easeOut
        }));
      }
    }
    view.rotate(rotation, opt_anchor);
  }
};


/**
 * @param {ol.Map} map Map.
 * @param {ol.View} view View.
 * @param {number|undefined} resolution Resolution to go to.
 * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
 * @param {number=} opt_duration Duration.
 * @param {number=} opt_direction Zooming direction; > 0 indicates
 *     zooming out, in which case the constraints system will select
 *     the largest nearest resolution; < 0 indicates zooming in, in
 *     which case the constraints system will select the smallest
 *     nearest resolution; == 0 indicates that the zooming direction
 *     is unknown/not relevant, in which case the constraints system
 *     will select the nearest resolution. If not defined 0 is
 *     assumed.
 */
ol.interaction.Interaction.zoom = function(map, view, resolution, opt_anchor, opt_duration, opt_direction) {
  resolution = view.constrainResolution(resolution, 0, opt_direction);
  ol.interaction.Interaction.zoomWithoutConstraints(
      map, view, resolution, opt_anchor, opt_duration);
};


/**
 * @param {ol.Map} map Map.
 * @param {ol.View} view View.
 * @param {number} delta Delta from previous zoom level.
 * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
 * @param {number=} opt_duration Duration.
 */
ol.interaction.Interaction.zoomByDelta = function(map, view, delta, opt_anchor, opt_duration) {
  var currentResolution = view.getResolution();
  var resolution = view.constrainResolution(currentResolution, delta, 0);
  ol.interaction.Interaction.zoomWithoutConstraints(
      map, view, resolution, opt_anchor, opt_duration);
};


/**
 * @param {ol.Map} map Map.
 * @param {ol.View} view View.
 * @param {number|undefined} resolution Resolution to go to.
 * @param {ol.Coordinate=} opt_anchor Anchor coordinate.
 * @param {number=} opt_duration Duration.
 */
ol.interaction.Interaction.zoomWithoutConstraints = function(map, view, resolution, opt_anchor, opt_duration) {
  if (resolution) {
    var currentResolution = view.getResolution();
    var currentCenter = view.getCenter();
    if (currentResolution !== undefined && currentCenter &&
        resolution !== currentResolution &&
        opt_duration && opt_duration > 0) {
      map.beforeRender(ol.animation.zoom({
        resolution: currentResolution,
        duration: opt_duration,
        easing: ol.easing.easeOut
      }));
      if (opt_anchor) {
        map.beforeRender(ol.animation.pan({
          source: currentCenter,
          duration: opt_duration,
          easing: ol.easing.easeOut
        }));
      }
    }
    if (opt_anchor) {
      var center = view.calculateCenterZoom(resolution, opt_anchor);
      view.setCenter(center);
    }
    view.setResolution(resolution);
  }
};

goog.provide('ol.interaction.DoubleClickZoom');

goog.require('ol');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.interaction.Interaction');


/**
 * @classdesc
 * Allows the user to zoom by double-clicking on the map.
 *
 * @constructor
 * @extends {ol.interaction.Interaction}
 * @param {olx.interaction.DoubleClickZoomOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DoubleClickZoom = function(opt_options) {

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {number}
   */
  this.delta_ = options.delta ? options.delta : 1;

  ol.interaction.Interaction.call(this, {
    handleEvent: ol.interaction.DoubleClickZoom.handleEvent
  });

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 250;

};
ol.inherits(ol.interaction.DoubleClickZoom, ol.interaction.Interaction);


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a
 * doubleclick) and eventually zooms the map.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.DoubleClickZoom}
 * @api
 */
ol.interaction.DoubleClickZoom.handleEvent = function(mapBrowserEvent) {
  var stopEvent = false;
  var browserEvent = mapBrowserEvent.originalEvent;
  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK) {
    var map = mapBrowserEvent.map;
    var anchor = mapBrowserEvent.coordinate;
    var delta = browserEvent.shiftKey ? -this.delta_ : this.delta_;
    var view = map.getView();
    ol.interaction.Interaction.zoomByDelta(
        map, view, delta, anchor, this.duration_);
    mapBrowserEvent.preventDefault();
    stopEvent = true;
  }
  return !stopEvent;
};

goog.provide('ol.events.condition');

goog.require('ol.asserts');
goog.require('ol.functions');
goog.require('ol.MapBrowserEvent.EventType');


/**
 * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when
 * additionally the shift-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt key is pressed.
 * @api stable
 */
ol.events.condition.altKeyOnly = function(mapBrowserEvent) {
  var originalEvent = mapBrowserEvent.originalEvent;
  return (
      originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};


/**
 * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise
 * (e.g. when additionally the platform-modifier-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt and shift keys are pressed.
 * @api stable
 */
ol.events.condition.altShiftKeysOnly = function(mapBrowserEvent) {
  var originalEvent = mapBrowserEvent.originalEvent;
  return (
      originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      originalEvent.shiftKey);
};


/**
 * Return always true.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True.
 * @function
 * @api stable
 */
ol.events.condition.always = ol.functions.TRUE;


/**
 * Return `true` if the event is a `click` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `click` event.
 * @api stable
 */
ol.events.condition.click = function(mapBrowserEvent) {
  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.CLICK;
};


/**
 * Return `true` if the event has an "action"-producing mouse button.
 *
 * By definition, this includes left-click on windows/linux, and left-click
 * without the ctrl key on Macs.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} The result.
 */
ol.events.condition.mouseActionButton = function(mapBrowserEvent) {
  var originalEvent = mapBrowserEvent.originalEvent;
  return originalEvent.button == 0 &&
      !(goog.userAgent.WEBKIT && ol.has.MAC && originalEvent.ctrlKey);
};


/**
 * Return always false.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} False.
 * @function
 * @api stable
 */
ol.events.condition.never = ol.functions.FALSE;


/**
 * Return `true` if the browser event is a `pointermove` event, `false`
 * otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the browser event is a `pointermove` event.
 * @api
 */
ol.events.condition.pointerMove = function(mapBrowserEvent) {
  return mapBrowserEvent.type == 'pointermove';
};


/**
 * Return `true` if the event is a map `singleclick` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `singleclick` event.
 * @api stable
 */
ol.events.condition.singleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK;
};


/**
 * Return `true` if the event is a map `dblclick` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `dblclick` event.
 * @api stable
 */
ol.events.condition.doubleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == ol.MapBrowserEvent.EventType.DBLCLICK;
};


/**
 * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is
 * pressed.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if there no modifier keys are pressed.
 * @api stable
 */
ol.events.condition.noModifierKeys = function(mapBrowserEvent) {
  var originalEvent = mapBrowserEvent.originalEvent;
  return (
      !originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};


/**
 * Return `true` if only the platform-modifier-key (the meta-key on Mac,
 * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally
 * the shift-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the platform modifier key is pressed.
 * @api stable
 */
ol.events.condition.platformModifierKeyOnly = function(mapBrowserEvent) {
  var originalEvent = mapBrowserEvent.originalEvent;
  return (
      !originalEvent.altKey &&
      (ol.has.MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};


/**
 * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when
 * additionally the alt-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the shift key is pressed.
 * @api stable
 */
ol.events.condition.shiftKeyOnly = function(mapBrowserEvent) {
  var originalEvent = mapBrowserEvent.originalEvent;
  return (
      !originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      originalEvent.shiftKey);
};


/**
 * Return `true` if the target element is not editable, i.e. not a `<input>`-,
 * `<select>`- or `<textarea>`-element, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if the target element is not editable.
 * @api
 */
ol.events.condition.targetNotEditable = function(mapBrowserEvent) {
  var target = mapBrowserEvent.originalEvent.target;
  var tagName = target.tagName;
  return (
      tagName !== 'INPUT' &&
      tagName !== 'SELECT' &&
      tagName !== 'TEXTAREA');
};


/**
 * Return `true` if the event originates from a mouse device.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a mouse device.
 * @api stable
 */
ol.events.condition.mouseOnly = function(mapBrowserEvent) {
  ol.asserts.assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event
  // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType
  return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse';
};


/**
 * Return `true` if the event originates from a primary pointer in
 * contact with the surface or if the left mouse button is pressed.
 * @see http://www.w3.org/TR/pointerevents/#button-states
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a primary pointer.
 * @api
 */
ol.events.condition.primaryAction = function(mapBrowserEvent) {
  var pointerEvent = mapBrowserEvent.pointerEvent;
  return pointerEvent.isPrimary && pointerEvent.button === 0;
};

goog.provide('ol.interaction.Pointer');

goog.require('ol');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.MapBrowserPointerEvent');
goog.require('ol.interaction.Interaction');
goog.require('ol.obj');


/**
 * @classdesc
 * Base class that calls user-defined functions on `down`, `move` and `up`
 * events. This class also manages "drag sequences".
 *
 * When the `handleDownEvent` user function returns `true` a drag sequence is
 * started. During a drag sequence the `handleDragEvent` user function is
 * called on `move` events. The drag sequence ends when the `handleUpEvent`
 * user function is called and returns `false`.
 *
 * @constructor
 * @param {olx.interaction.PointerOptions=} opt_options Options.
 * @extends {ol.interaction.Interaction}
 * @api
 */
ol.interaction.Pointer = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var handleEvent = options.handleEvent ?
      options.handleEvent : ol.interaction.Pointer.handleEvent;

  ol.interaction.Interaction.call(this, {
    handleEvent: handleEvent
  });

  /**
   * @type {function(ol.MapBrowserPointerEvent):boolean}
   * @private
   */
  this.handleDownEvent_ = options.handleDownEvent ?
      options.handleDownEvent : ol.interaction.Pointer.handleDownEvent;

  /**
   * @type {function(ol.MapBrowserPointerEvent)}
   * @private
   */
  this.handleDragEvent_ = options.handleDragEvent ?
      options.handleDragEvent : ol.interaction.Pointer.handleDragEvent;

  /**
   * @type {function(ol.MapBrowserPointerEvent)}
   * @private
   */
  this.handleMoveEvent_ = options.handleMoveEvent ?
      options.handleMoveEvent : ol.interaction.Pointer.handleMoveEvent;

  /**
   * @type {function(ol.MapBrowserPointerEvent):boolean}
   * @private
   */
  this.handleUpEvent_ = options.handleUpEvent ?
      options.handleUpEvent : ol.interaction.Pointer.handleUpEvent;

  /**
   * @type {boolean}
   * @protected
   */
  this.handlingDownUpSequence = false;

  /**
   * @type {Object.<number, ol.pointer.PointerEvent>}
   * @private
   */
  this.trackedPointers_ = {};

  /**
   * @type {Array.<ol.pointer.PointerEvent>}
   * @protected
   */
  this.targetPointers = [];

};
ol.inherits(ol.interaction.Pointer, ol.interaction.Interaction);


/**
 * @param {Array.<ol.pointer.PointerEvent>} pointerEvents List of events.
 * @return {ol.Pixel} Centroid pixel.
 */
ol.interaction.Pointer.centroid = function(pointerEvents) {
  var length = pointerEvents.length;
  var clientX = 0;
  var clientY = 0;
  for (var i = 0; i < length; i++) {
    clientX += pointerEvents[i].clientX;
    clientY += pointerEvents[i].clientY;
  }
  return [clientX / length, clientY / length];
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Whether the event is a pointerdown, pointerdrag
 *     or pointerup event.
 * @private
 */
ol.interaction.Pointer.prototype.isPointerDraggingEvent_ = function(mapBrowserEvent) {
  var type = mapBrowserEvent.type;
  return (
      type === ol.MapBrowserEvent.EventType.POINTERDOWN ||
      type === ol.MapBrowserEvent.EventType.POINTERDRAG ||
      type === ol.MapBrowserEvent.EventType.POINTERUP);
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @private
 */
ol.interaction.Pointer.prototype.updateTrackedPointers_ = function(mapBrowserEvent) {
  if (this.isPointerDraggingEvent_(mapBrowserEvent)) {
    var event = mapBrowserEvent.pointerEvent;

    if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) {
      delete this.trackedPointers_[event.pointerId];
    } else if (mapBrowserEvent.type ==
        ol.MapBrowserEvent.EventType.POINTERDOWN) {
      this.trackedPointers_[event.pointerId] = event;
    } else if (event.pointerId in this.trackedPointers_) {
      // update only when there was a pointerdown event for this pointer
      this.trackedPointers_[event.pointerId] = event;
    }
    this.targetPointers = ol.obj.getValues(this.trackedPointers_);
  }
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.Pointer}
 */
ol.interaction.Pointer.handleDragEvent = ol.nullFunction;


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Capture dragging.
 * @this {ol.interaction.Pointer}
 */
ol.interaction.Pointer.handleUpEvent = ol.functions.FALSE;


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Capture dragging.
 * @this {ol.interaction.Pointer}
 */
ol.interaction.Pointer.handleDownEvent = ol.functions.FALSE;


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.Pointer}
 */
ol.interaction.Pointer.handleMoveEvent = ol.nullFunction;


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} and may call into
 * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are
 * detected.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.Pointer}
 * @api
 */
ol.interaction.Pointer.handleEvent = function(mapBrowserEvent) {
  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
    return true;
  }

  var stopEvent = false;
  this.updateTrackedPointers_(mapBrowserEvent);
  if (this.handlingDownUpSequence) {
    if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDRAG) {
      this.handleDragEvent_(mapBrowserEvent);
    } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERUP) {
      this.handlingDownUpSequence = this.handleUpEvent_(mapBrowserEvent);
    }
  }
  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERDOWN) {
    var handled = this.handleDownEvent_(mapBrowserEvent);
    this.handlingDownUpSequence = handled;
    stopEvent = this.shouldStopEvent(handled);
  } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE) {
    this.handleMoveEvent_(mapBrowserEvent);
  }
  return !stopEvent;
};


/**
 * This method is used to determine if "down" events should be propagated to
 * other interactions or should be stopped.
 *
 * The method receives the return code of the "handleDownEvent" function.
 *
 * By default this function is the "identity" function. It's overidden in
 * child classes.
 *
 * @param {boolean} handled Was the event handled by the interaction?
 * @return {boolean} Should the event be stopped?
 * @protected
 */
ol.interaction.Pointer.prototype.shouldStopEvent = function(handled) {
  return handled;
};

goog.provide('ol.interaction.DragPan');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.coordinate');
goog.require('ol.events.condition');
goog.require('ol.functions');
goog.require('ol.interaction.Pointer');


/**
 * @classdesc
 * Allows the user to pan the map by dragging the map.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.DragPanOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DragPan = function(opt_options) {

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.DragPan.handleDownEvent_,
    handleDragEvent: ol.interaction.DragPan.handleDragEvent_,
    handleUpEvent: ol.interaction.DragPan.handleUpEvent_
  });

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {ol.Kinetic|undefined}
   */
  this.kinetic_ = options.kinetic;

  /**
   * @private
   * @type {?ol.PreRenderFunction}
   */
  this.kineticPreRenderFn_ = null;

  /**
   * @type {ol.Pixel}
   */
  this.lastCentroid = null;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.noModifierKeys;

  /**
   * @private
   * @type {boolean}
   */
  this.noKinetic_ = false;

};
ol.inherits(ol.interaction.DragPan, ol.interaction.Pointer);


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.DragPan}
 * @private
 */
ol.interaction.DragPan.handleDragEvent_ = function(mapBrowserEvent) {
  goog.DEBUG && console.assert(this.targetPointers.length >= 1,
      'the length of this.targetPointers should be more than 1');
  var centroid =
      ol.interaction.Pointer.centroid(this.targetPointers);
  if (this.kinetic_) {
    this.kinetic_.update(centroid[0], centroid[1]);
  }
  if (this.lastCentroid) {
    var deltaX = this.lastCentroid[0] - centroid[0];
    var deltaY = centroid[1] - this.lastCentroid[1];
    var map = mapBrowserEvent.map;
    var view = map.getView();
    var viewState = view.getState();
    var center = [deltaX, deltaY];
    ol.coordinate.scale(center, viewState.resolution);
    ol.coordinate.rotate(center, viewState.rotation);
    ol.coordinate.add(center, viewState.center);
    center = view.constrainCenter(center);
    view.setCenter(center);
  }
  this.lastCentroid = centroid;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.DragPan}
 * @private
 */
ol.interaction.DragPan.handleUpEvent_ = function(mapBrowserEvent) {
  var map = mapBrowserEvent.map;
  var view = map.getView();
  if (this.targetPointers.length === 0) {
    if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) {
      var distance = this.kinetic_.getDistance();
      var angle = this.kinetic_.getAngle();
      var center = /** @type {!ol.Coordinate} */ (view.getCenter());
      this.kineticPreRenderFn_ = this.kinetic_.pan(center);
      map.beforeRender(this.kineticPreRenderFn_);
      var centerpx = map.getPixelFromCoordinate(center);
      var dest = map.getCoordinateFromPixel([
        centerpx[0] - distance * Math.cos(angle),
        centerpx[1] - distance * Math.sin(angle)
      ]);
      dest = view.constrainCenter(dest);
      view.setCenter(dest);
    } else {
      // the view is not updated, force a render
      map.render();
    }
    view.setHint(ol.View.Hint.INTERACTING, -1);
    return false;
  } else {
    this.lastCentroid = null;
    return true;
  }
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.DragPan}
 * @private
 */
ol.interaction.DragPan.handleDownEvent_ = function(mapBrowserEvent) {
  if (this.targetPointers.length > 0 && this.condition_(mapBrowserEvent)) {
    var map = mapBrowserEvent.map;
    var view = map.getView();
    this.lastCentroid = null;
    if (!this.handlingDownUpSequence) {
      view.setHint(ol.View.Hint.INTERACTING, 1);
    }
    if (this.kineticPreRenderFn_ &&
        map.removePreRenderFunction(this.kineticPreRenderFn_)) {
      view.setCenter(mapBrowserEvent.frameState.viewState.center);
      this.kineticPreRenderFn_ = null;
    }
    if (this.kinetic_) {
      this.kinetic_.begin();
    }
    // No kinetic as soon as more than one pointer on the screen is
    // detected. This is to prevent nasty pans after pinch.
    this.noKinetic_ = this.targetPointers.length > 1;
    return true;
  } else {
    return false;
  }
};


/**
 * @inheritDoc
 */
ol.interaction.DragPan.prototype.shouldStopEvent = ol.functions.FALSE;

goog.provide('ol.interaction.DragRotate');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.events.condition');
goog.require('ol.functions');
goog.require('ol.interaction.Interaction');
goog.require('ol.interaction.Pointer');


/**
 * @classdesc
 * Allows the user to rotate the map by clicking and dragging on the map,
 * normally combined with an {@link ol.events.condition} that limits
 * it to when the alt and shift keys are held down.
 *
 * This interaction is only supported for mouse devices.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.DragRotateOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DragRotate = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.DragRotate.handleDownEvent_,
    handleDragEvent: ol.interaction.DragRotate.handleDragEvent_,
    handleUpEvent: ol.interaction.DragRotate.handleUpEvent_
  });

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.altShiftKeysOnly;

  /**
   * @private
   * @type {number|undefined}
   */
  this.lastAngle_ = undefined;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 250;
};
ol.inherits(ol.interaction.DragRotate, ol.interaction.Pointer);


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.DragRotate}
 * @private
 */
ol.interaction.DragRotate.handleDragEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return;
  }

  var map = mapBrowserEvent.map;
  var size = map.getSize();
  var offset = mapBrowserEvent.pixel;
  var theta =
      Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2);
  if (this.lastAngle_ !== undefined) {
    var delta = theta - this.lastAngle_;
    var view = map.getView();
    var rotation = view.getRotation();
    ol.interaction.Interaction.rotateWithoutConstraints(
        map, view, rotation - delta);
  }
  this.lastAngle_ = theta;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.DragRotate}
 * @private
 */
ol.interaction.DragRotate.handleUpEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return true;
  }

  var map = mapBrowserEvent.map;
  var view = map.getView();
  view.setHint(ol.View.Hint.INTERACTING, -1);
  var rotation = view.getRotation();
  ol.interaction.Interaction.rotate(map, view, rotation,
      undefined, this.duration_);
  return false;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.DragRotate}
 * @private
 */
ol.interaction.DragRotate.handleDownEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return false;
  }

  if (ol.events.condition.mouseActionButton(mapBrowserEvent) &&
      this.condition_(mapBrowserEvent)) {
    var map = mapBrowserEvent.map;
    map.getView().setHint(ol.View.Hint.INTERACTING, 1);
    this.lastAngle_ = undefined;
    return true;
  } else {
    return false;
  }
};


/**
 * @inheritDoc
 */
ol.interaction.DragRotate.prototype.shouldStopEvent = ol.functions.FALSE;

// FIXME add rotation

goog.provide('ol.render.Box');

goog.require('ol');
goog.require('ol.Disposable');
goog.require('ol.geom.Polygon');


/**
 * @constructor
 * @extends {ol.Disposable}
 * @param {string} className CSS class name.
 */
ol.render.Box = function(className) {

  /**
   * @type {ol.geom.Polygon}
   * @private
   */
  this.geometry_ = null;

  /**
   * @type {HTMLDivElement}
   * @private
   */
  this.element_ = /** @type {HTMLDivElement} */ (document.createElement('div'));
  this.element_.style.position = 'absolute';
  this.element_.className = 'ol-box ' + className;

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  /**
   * @private
   * @type {ol.Pixel}
   */
  this.startPixel_ = null;

  /**
   * @private
   * @type {ol.Pixel}
   */
  this.endPixel_ = null;

};
ol.inherits(ol.render.Box, ol.Disposable);


/**
 * @inheritDoc
 */
ol.render.Box.prototype.disposeInternal = function() {
  this.setMap(null);
};


/**
 * @private
 */
ol.render.Box.prototype.render_ = function() {
  var startPixel = this.startPixel_;
  var endPixel = this.endPixel_;
  var px = 'px';
  var style = this.element_.style;
  style.left = Math.min(startPixel[0], endPixel[0]) + px;
  style.top = Math.min(startPixel[1], endPixel[1]) + px;
  style.width = Math.abs(endPixel[0] - startPixel[0]) + px;
  style.height = Math.abs(endPixel[1] - startPixel[1]) + px;
};


/**
 * @param {ol.Map} map Map.
 */
ol.render.Box.prototype.setMap = function(map) {
  if (this.map_) {
    this.map_.getOverlayContainer().removeChild(this.element_);
    var style = this.element_.style;
    style.left = style.top = style.width = style.height = 'inherit';
  }
  this.map_ = map;
  if (this.map_) {
    this.map_.getOverlayContainer().appendChild(this.element_);
  }
};


/**
 * @param {ol.Pixel} startPixel Start pixel.
 * @param {ol.Pixel} endPixel End pixel.
 */
ol.render.Box.prototype.setPixels = function(startPixel, endPixel) {
  this.startPixel_ = startPixel;
  this.endPixel_ = endPixel;
  this.createOrUpdateGeometry();
  this.render_();
};


/**
 * Creates or updates the cached geometry.
 */
ol.render.Box.prototype.createOrUpdateGeometry = function() {
  var startPixel = this.startPixel_;
  var endPixel = this.endPixel_;
  var pixels = [
    startPixel,
    [startPixel[0], endPixel[1]],
    endPixel,
    [endPixel[0], startPixel[1]]
  ];
  var coordinates = pixels.map(this.map_.getCoordinateFromPixel, this.map_);
  // close the polygon
  coordinates[4] = coordinates[0].slice();
  if (!this.geometry_) {
    this.geometry_ = new ol.geom.Polygon([coordinates]);
  } else {
    this.geometry_.setCoordinates([coordinates]);
  }
};


/**
 * @return {ol.geom.Polygon} Geometry.
 */
ol.render.Box.prototype.getGeometry = function() {
  return this.geometry_;
};

// FIXME draw drag box
goog.provide('ol.interaction.DragBox');
goog.provide('ol.DragBoxEvent');

goog.require('ol.events.Event');
goog.require('ol');
goog.require('ol.events.condition');
goog.require('ol.interaction.Pointer');
goog.require('ol.render.Box');


/**
 * @const
 * @type {number}
 */
ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED =
    ol.DRAG_BOX_HYSTERESIS_PIXELS *
    ol.DRAG_BOX_HYSTERESIS_PIXELS;


/**
 * @enum {string}
 */
ol.DragBoxEventType = {
  /**
   * Triggered upon drag box start.
   * @event ol.DragBoxEvent#boxstart
   * @api stable
   */
  BOXSTART: 'boxstart',

  /**
   * Triggered on drag when box is active.
   * @event ol.DragBoxEvent#boxdrag
   * @api
   */
  BOXDRAG: 'boxdrag',

  /**
   * Triggered upon drag box end.
   * @event ol.DragBoxEvent#boxend
   * @api stable
   */
  BOXEND: 'boxend'
};


/**
 * @classdesc
 * Events emitted by {@link ol.interaction.DragBox} instances are instances of
 * this type.
 *
 * @param {string} type The event type.
 * @param {ol.Coordinate} coordinate The event coordinate.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Originating event.
 * @extends {ol.events.Event}
 * @constructor
 * @implements {oli.DragBoxEvent}
 */
ol.DragBoxEvent = function(type, coordinate, mapBrowserEvent) {
  ol.events.Event.call(this, type);

  /**
   * The coordinate of the drag event.
   * @const
   * @type {ol.Coordinate}
   * @api stable
   */
  this.coordinate = coordinate;

  /**
   * @const
   * @type {ol.MapBrowserEvent}
   * @api
   */
  this.mapBrowserEvent = mapBrowserEvent;

};
ol.inherits(ol.DragBoxEvent, ol.events.Event);


/**
 * @classdesc
 * Allows the user to draw a vector box by clicking and dragging on the map,
 * normally combined with an {@link ol.events.condition} that limits
 * it to when the shift or other key is held down. This is used, for example,
 * for zooming to a specific area of the map
 * (see {@link ol.interaction.DragZoom} and
 * {@link ol.interaction.DragRotateAndZoom}).
 *
 * This interaction is only supported for mouse devices.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @fires ol.DragBoxEvent
 * @param {olx.interaction.DragBoxOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DragBox = function(opt_options) {

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.DragBox.handleDownEvent_,
    handleDragEvent: ol.interaction.DragBox.handleDragEvent_,
    handleUpEvent: ol.interaction.DragBox.handleUpEvent_
  });

  var options = opt_options ? opt_options : {};

  /**
   * @type {ol.render.Box}
   * @private
   */
  this.box_ = new ol.render.Box(options.className || 'ol-dragbox');

  /**
   * @type {ol.Pixel}
   * @private
   */
  this.startPixel_ = null;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.always;

  /**
   * @private
   * @type {ol.DragBoxEndConditionType}
   */
  this.boxEndCondition_ = options.boxEndCondition ?
      options.boxEndCondition : ol.interaction.DragBox.defaultBoxEndCondition;
};
ol.inherits(ol.interaction.DragBox, ol.interaction.Pointer);


/**
 * The default condition for determining whether the boxend event
 * should fire.
 * @param  {ol.MapBrowserEvent} mapBrowserEvent The originating MapBrowserEvent
 *  leading to the box end.
 * @param  {ol.Pixel} startPixel      The starting pixel of the box.
 * @param  {ol.Pixel} endPixel        The end pixel of the box.
 * @return {boolean} Whether or not the boxend condition should be fired.
 */
ol.interaction.DragBox.defaultBoxEndCondition = function(mapBrowserEvent,
    startPixel, endPixel) {
  var width = endPixel[0] - startPixel[0];
  var height = endPixel[1] - startPixel[1];
  return width * width + height * height >=
      ol.DRAG_BOX_HYSTERESIS_PIXELS_SQUARED;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.DragBox}
 * @private
 */
ol.interaction.DragBox.handleDragEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return;
  }

  this.box_.setPixels(this.startPixel_, mapBrowserEvent.pixel);

  this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXDRAG,
    mapBrowserEvent.coordinate, mapBrowserEvent));
};


/**
 * Returns geometry of last drawn box.
 * @return {ol.geom.Polygon} Geometry.
 * @api stable
 */
ol.interaction.DragBox.prototype.getGeometry = function() {
  return this.box_.getGeometry();
};


/**
 * To be overriden by child classes.
 * FIXME: use constructor option instead of relying on overridding.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @protected
 */
ol.interaction.DragBox.prototype.onBoxEnd = ol.nullFunction;


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.DragBox}
 * @private
 */
ol.interaction.DragBox.handleUpEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return true;
  }

  this.box_.setMap(null);

  if (this.boxEndCondition_(mapBrowserEvent,
      this.startPixel_, mapBrowserEvent.pixel)) {
    this.onBoxEnd(mapBrowserEvent);
    this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXEND,
        mapBrowserEvent.coordinate, mapBrowserEvent));
  }
  return false;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.DragBox}
 * @private
 */
ol.interaction.DragBox.handleDownEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return false;
  }

  if (ol.events.condition.mouseActionButton(mapBrowserEvent) &&
      this.condition_(mapBrowserEvent)) {
    this.startPixel_ = mapBrowserEvent.pixel;
    this.box_.setMap(mapBrowserEvent.map);
    this.box_.setPixels(this.startPixel_, this.startPixel_);
    this.dispatchEvent(new ol.DragBoxEvent(ol.DragBoxEventType.BOXSTART,
        mapBrowserEvent.coordinate, mapBrowserEvent));
    return true;
  } else {
    return false;
  }
};

goog.provide('ol.interaction.DragZoom');

goog.require('ol');
goog.require('ol.animation');
goog.require('ol.easing');
goog.require('ol.events.condition');
goog.require('ol.extent');
goog.require('ol.interaction.DragBox');


/**
 * @classdesc
 * Allows the user to zoom the map by clicking and dragging on the map,
 * normally combined with an {@link ol.events.condition} that limits
 * it to when a key, shift by default, is held down.
 *
 * To change the style of the box, use CSS and the `.ol-dragzoom` selector, or
 * your custom one configured with `className`.
 *
 * @constructor
 * @extends {ol.interaction.DragBox}
 * @param {olx.interaction.DragZoomOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DragZoom = function(opt_options) {
  var options = opt_options ? opt_options : {};

  var condition = options.condition ?
      options.condition : ol.events.condition.shiftKeyOnly;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 200;

  /**
   * @private
   * @type {boolean}
   */
  this.out_ = options.out !== undefined ? options.out : false;

  ol.interaction.DragBox.call(this, {
    condition: condition,
    className: options.className || 'ol-dragzoom'
  });

};
ol.inherits(ol.interaction.DragZoom, ol.interaction.DragBox);


/**
 * @inheritDoc
 */
ol.interaction.DragZoom.prototype.onBoxEnd = function() {
  var map = this.getMap();

  var view = /** @type {!ol.View} */ (map.getView());

  var size = /** @type {!ol.Size} */ (map.getSize());

  var extent = this.getGeometry().getExtent();

  if (this.out_) {
    var mapExtent = view.calculateExtent(size);
    var boxPixelExtent = ol.extent.createOrUpdateFromCoordinates([
      map.getPixelFromCoordinate(ol.extent.getBottomLeft(extent)),
      map.getPixelFromCoordinate(ol.extent.getTopRight(extent))]);
    var factor = view.getResolutionForExtent(boxPixelExtent, size);

    ol.extent.scaleFromCenter(mapExtent, 1 / factor);
    extent = mapExtent;
  }

  var resolution = view.constrainResolution(
      view.getResolutionForExtent(extent, size));

  var currentResolution = /** @type {number} */ (view.getResolution());

  var currentCenter = /** @type {!ol.Coordinate} */ (view.getCenter());

  map.beforeRender(ol.animation.zoom({
    resolution: currentResolution,
    duration: this.duration_,
    easing: ol.easing.easeOut
  }));
  map.beforeRender(ol.animation.pan({
    source: currentCenter,
    duration: this.duration_,
    easing: ol.easing.easeOut
  }));

  view.setCenter(ol.extent.getCenter(extent));
  view.setResolution(resolution);
};

goog.provide('ol.events.KeyCode');

/**
 * @enum {number}
 * @const
 */
ol.events.KeyCode = {
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40
};

goog.provide('ol.interaction.KeyboardPan');

goog.require('ol');
goog.require('ol.coordinate');
goog.require('ol.events.EventType');
goog.require('ol.events.KeyCode');
goog.require('ol.events.condition');
goog.require('ol.interaction.Interaction');


/**
 * @classdesc
 * Allows the user to pan the map using keyboard arrows.
 * Note that, although this interaction is by default included in maps,
 * the keys can only be used when browser focus is on the element to which
 * the keyboard events are attached. By default, this is the map div,
 * though you can change this with the `keyboardEventTarget` in
 * {@link ol.Map}. `document` never loses focus but, for any other element,
 * focus will have to be on, and returned to, this element if the keys are to
 * function.
 * See also {@link ol.interaction.KeyboardZoom}.
 *
 * @constructor
 * @extends {ol.interaction.Interaction}
 * @param {olx.interaction.KeyboardPanOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.KeyboardPan = function(opt_options) {

  ol.interaction.Interaction.call(this, {
    handleEvent: ol.interaction.KeyboardPan.handleEvent
  });

  var options = opt_options || {};

  /**
   * @private
   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
   * @return {boolean} Combined condition result.
   */
  this.defaultCondition_ = function(mapBrowserEvent) {
    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
      ol.events.condition.targetNotEditable(mapBrowserEvent);
  };

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition !== undefined ?
      options.condition : this.defaultCondition_;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 100;

  /**
   * @private
   * @type {number}
   */
  this.pixelDelta_ = options.pixelDelta !== undefined ?
      options.pixelDelta : 128;

};
ol.inherits(ol.interaction.KeyboardPan, ol.interaction.Interaction);

/**
 * Handles the {@link ol.MapBrowserEvent map browser event} if it was a
 * `KeyEvent`, and decides the direction to pan to (if an arrow key was
 * pressed).
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.KeyboardPan}
 * @api
 */
ol.interaction.KeyboardPan.handleEvent = function(mapBrowserEvent) {
  var stopEvent = false;
  if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN) {
    var keyEvent = mapBrowserEvent.originalEvent;
    var keyCode = keyEvent.keyCode;
    if (this.condition_(mapBrowserEvent) &&
        (keyCode == ol.events.KeyCode.DOWN ||
        keyCode == ol.events.KeyCode.LEFT ||
        keyCode == ol.events.KeyCode.RIGHT ||
        keyCode == ol.events.KeyCode.UP)) {
      var map = mapBrowserEvent.map;
      var view = map.getView();
      var mapUnitsDelta = view.getResolution() * this.pixelDelta_;
      var deltaX = 0, deltaY = 0;
      if (keyCode == ol.events.KeyCode.DOWN) {
        deltaY = -mapUnitsDelta;
      } else if (keyCode == ol.events.KeyCode.LEFT) {
        deltaX = -mapUnitsDelta;
      } else if (keyCode == ol.events.KeyCode.RIGHT) {
        deltaX = mapUnitsDelta;
      } else {
        deltaY = mapUnitsDelta;
      }
      var delta = [deltaX, deltaY];
      ol.coordinate.rotate(delta, view.getRotation());
      ol.interaction.Interaction.pan(map, view, delta, this.duration_);
      mapBrowserEvent.preventDefault();
      stopEvent = true;
    }
  }
  return !stopEvent;
};

goog.provide('ol.interaction.KeyboardZoom');

goog.require('ol');
goog.require('ol.events.EventType');
goog.require('ol.events.condition');
goog.require('ol.interaction.Interaction');


/**
 * @classdesc
 * Allows the user to zoom the map using keyboard + and -.
 * Note that, although this interaction is by default included in maps,
 * the keys can only be used when browser focus is on the element to which
 * the keyboard events are attached. By default, this is the map div,
 * though you can change this with the `keyboardEventTarget` in
 * {@link ol.Map}. `document` never loses focus but, for any other element,
 * focus will have to be on, and returned to, this element if the keys are to
 * function.
 * See also {@link ol.interaction.KeyboardPan}.
 *
 * @constructor
 * @param {olx.interaction.KeyboardZoomOptions=} opt_options Options.
 * @extends {ol.interaction.Interaction}
 * @api stable
 */
ol.interaction.KeyboardZoom = function(opt_options) {

  ol.interaction.Interaction.call(this, {
    handleEvent: ol.interaction.KeyboardZoom.handleEvent
  });

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ? options.condition :
          ol.events.condition.targetNotEditable;

  /**
   * @private
   * @type {number}
   */
  this.delta_ = options.delta ? options.delta : 1;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 100;

};
ol.inherits(ol.interaction.KeyboardZoom, ol.interaction.Interaction);


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} if it was a
 * `KeyEvent`, and decides whether to zoom in or out (depending on whether the
 * key pressed was '+' or '-').
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.KeyboardZoom}
 * @api
 */
ol.interaction.KeyboardZoom.handleEvent = function(mapBrowserEvent) {
  var stopEvent = false;
  if (mapBrowserEvent.type == ol.events.EventType.KEYDOWN ||
      mapBrowserEvent.type == ol.events.EventType.KEYPRESS) {
    var keyEvent = mapBrowserEvent.originalEvent;
    var charCode = keyEvent.charCode;
    if (this.condition_(mapBrowserEvent) &&
        (charCode == '+'.charCodeAt(0) || charCode == '-'.charCodeAt(0))) {
      var map = mapBrowserEvent.map;
      var delta = (charCode == '+'.charCodeAt(0)) ? this.delta_ : -this.delta_;
      var view = map.getView();
      ol.interaction.Interaction.zoomByDelta(
          map, view, delta, undefined, this.duration_);
      mapBrowserEvent.preventDefault();
      stopEvent = true;
    }
  }
  return !stopEvent;
};

goog.provide('ol.interaction.MouseWheelZoom');

goog.require('ol');
goog.require('ol.events.EventType');
goog.require('ol.interaction.Interaction');
goog.require('ol.math');


/**
 * @classdesc
 * Allows the user to zoom the map by scrolling the mouse wheel.
 *
 * @constructor
 * @extends {ol.interaction.Interaction}
 * @param {olx.interaction.MouseWheelZoomOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.MouseWheelZoom = function(opt_options) {

  ol.interaction.Interaction.call(this, {
    handleEvent: ol.interaction.MouseWheelZoom.handleEvent
  });

  var options = opt_options || {};

  /**
   * @private
   * @type {number}
   */
  this.delta_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 250;

  /**
   * @private
   * @type {boolean}
   */
  this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;

  /**
   * @private
   * @type {?ol.Coordinate}
   */
  this.lastAnchor_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.startTime_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.timeoutId_ = undefined;

};
ol.inherits(ol.interaction.MouseWheelZoom, ol.interaction.Interaction);


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} (if it was a
 * mousewheel-event) and eventually zooms the map.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.MouseWheelZoom}
 * @api
 */
ol.interaction.MouseWheelZoom.handleEvent = function(mapBrowserEvent) {
  var stopEvent = false;
  if (mapBrowserEvent.type == ol.events.EventType.WHEEL ||
      mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) {
    var map = mapBrowserEvent.map;
    var wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent);

    if (this.useAnchor_) {
      this.lastAnchor_ = mapBrowserEvent.coordinate;
    }

    // Delta normalisation inspired by
    // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js
    //TODO There's more good stuff in there for inspiration to improve this interaction.
    var delta;
    if (mapBrowserEvent.type == ol.events.EventType.WHEEL) {
      delta = wheelEvent.deltaY;
      if (ol.has.FIREFOX &&
          wheelEvent.deltaMode === ol.global.WheelEvent.DOM_DELTA_PIXEL) {
        delta /= ol.has.DEVICE_PIXEL_RATIO;
      }
      if (wheelEvent.deltaMode === ol.global.WheelEvent.DOM_DELTA_LINE) {
        delta *= 40;
      }
    } else if (mapBrowserEvent.type == ol.events.EventType.MOUSEWHEEL) {
      delta = -wheelEvent.wheelDeltaY;
      if (ol.has.SAFARI) {
        delta /= 3;
      }
    }

    this.delta_ += delta;

    if (this.startTime_ === undefined) {
      this.startTime_ = Date.now();
    }

    var duration = ol.MOUSEWHEELZOOM_TIMEOUT_DURATION;
    var timeLeft = Math.max(duration - (Date.now() - this.startTime_), 0);

    ol.global.clearTimeout(this.timeoutId_);
    this.timeoutId_ = ol.global.setTimeout(
        this.doZoom_.bind(this, map), timeLeft);

    mapBrowserEvent.preventDefault();
    stopEvent = true;
  }
  return !stopEvent;
};


/**
 * @private
 * @param {ol.Map} map Map.
 */
ol.interaction.MouseWheelZoom.prototype.doZoom_ = function(map) {
  var maxDelta = ol.MOUSEWHEELZOOM_MAXDELTA;
  var delta = ol.math.clamp(this.delta_, -maxDelta, maxDelta);

  var view = map.getView();

  ol.interaction.Interaction.zoomByDelta(map, view, -delta, this.lastAnchor_,
      this.duration_);

  this.delta_ = 0;
  this.lastAnchor_ = null;
  this.startTime_ = undefined;
  this.timeoutId_ = undefined;
};


/**
 * Enable or disable using the mouse's location as an anchor when zooming
 * @param {boolean} useAnchor true to zoom to the mouse's location, false
 * to zoom to the center of the map
 * @api
 */
ol.interaction.MouseWheelZoom.prototype.setMouseAnchor = function(useAnchor) {
  this.useAnchor_ = useAnchor;
  if (!useAnchor) {
    this.lastAnchor_ = null;
  }
};

goog.provide('ol.interaction.PinchRotate');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.functions');
goog.require('ol.interaction.Interaction');
goog.require('ol.interaction.Pointer');


/**
 * @classdesc
 * Allows the user to rotate the map by twisting with two fingers
 * on a touch screen.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.PinchRotateOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.PinchRotate = function(opt_options) {

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.PinchRotate.handleDownEvent_,
    handleDragEvent: ol.interaction.PinchRotate.handleDragEvent_,
    handleUpEvent: ol.interaction.PinchRotate.handleUpEvent_
  });

  var options = opt_options || {};

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.anchor_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.lastAngle_ = undefined;

  /**
   * @private
   * @type {boolean}
   */
  this.rotating_ = false;

  /**
   * @private
   * @type {number}
   */
  this.rotationDelta_ = 0.0;

  /**
   * @private
   * @type {number}
   */
  this.threshold_ = options.threshold !== undefined ? options.threshold : 0.3;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 250;

};
ol.inherits(ol.interaction.PinchRotate, ol.interaction.Pointer);


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.PinchRotate}
 * @private
 */
ol.interaction.PinchRotate.handleDragEvent_ = function(mapBrowserEvent) {
  goog.DEBUG && console.assert(this.targetPointers.length >= 2,
      'length of this.targetPointers should be greater than or equal to 2');
  var rotationDelta = 0.0;

  var touch0 = this.targetPointers[0];
  var touch1 = this.targetPointers[1];

  // angle between touches
  var angle = Math.atan2(
      touch1.clientY - touch0.clientY,
      touch1.clientX - touch0.clientX);

  if (this.lastAngle_ !== undefined) {
    var delta = angle - this.lastAngle_;
    this.rotationDelta_ += delta;
    if (!this.rotating_ &&
        Math.abs(this.rotationDelta_) > this.threshold_) {
      this.rotating_ = true;
    }
    rotationDelta = delta;
  }
  this.lastAngle_ = angle;

  var map = mapBrowserEvent.map;

  // rotate anchor point.
  // FIXME: should be the intersection point between the lines:
  //     touch0,touch1 and previousTouch0,previousTouch1
  var viewportPosition = map.getViewport().getBoundingClientRect();
  var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
  centroid[0] -= viewportPosition.left;
  centroid[1] -= viewportPosition.top;
  this.anchor_ = map.getCoordinateFromPixel(centroid);

  // rotate
  if (this.rotating_) {
    var view = map.getView();
    var rotation = view.getRotation();
    map.render();
    ol.interaction.Interaction.rotateWithoutConstraints(map, view,
        rotation + rotationDelta, this.anchor_);
  }
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.PinchRotate}
 * @private
 */
ol.interaction.PinchRotate.handleUpEvent_ = function(mapBrowserEvent) {
  if (this.targetPointers.length < 2) {
    var map = mapBrowserEvent.map;
    var view = map.getView();
    view.setHint(ol.View.Hint.INTERACTING, -1);
    if (this.rotating_) {
      var rotation = view.getRotation();
      ol.interaction.Interaction.rotate(
          map, view, rotation, this.anchor_, this.duration_);
    }
    return false;
  } else {
    return true;
  }
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.PinchRotate}
 * @private
 */
ol.interaction.PinchRotate.handleDownEvent_ = function(mapBrowserEvent) {
  if (this.targetPointers.length >= 2) {
    var map = mapBrowserEvent.map;
    this.anchor_ = null;
    this.lastAngle_ = undefined;
    this.rotating_ = false;
    this.rotationDelta_ = 0.0;
    if (!this.handlingDownUpSequence) {
      map.getView().setHint(ol.View.Hint.INTERACTING, 1);
    }
    map.render();
    return true;
  } else {
    return false;
  }
};


/**
 * @inheritDoc
 */
ol.interaction.PinchRotate.prototype.shouldStopEvent = ol.functions.FALSE;

goog.provide('ol.interaction.PinchZoom');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.functions');
goog.require('ol.interaction.Interaction');
goog.require('ol.interaction.Pointer');


/**
 * @classdesc
 * Allows the user to zoom the map by pinching with two fingers
 * on a touch screen.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.PinchZoomOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.PinchZoom = function(opt_options) {

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.PinchZoom.handleDownEvent_,
    handleDragEvent: ol.interaction.PinchZoom.handleDragEvent_,
    handleUpEvent: ol.interaction.PinchZoom.handleUpEvent_
  });

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.anchor_ = null;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 400;

  /**
   * @private
   * @type {number|undefined}
   */
  this.lastDistance_ = undefined;

  /**
   * @private
   * @type {number}
   */
  this.lastScaleDelta_ = 1;

};
ol.inherits(ol.interaction.PinchZoom, ol.interaction.Pointer);


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.PinchZoom}
 * @private
 */
ol.interaction.PinchZoom.handleDragEvent_ = function(mapBrowserEvent) {
  goog.DEBUG && console.assert(this.targetPointers.length >= 2,
      'length of this.targetPointers should be 2 or more');
  var scaleDelta = 1.0;

  var touch0 = this.targetPointers[0];
  var touch1 = this.targetPointers[1];
  var dx = touch0.clientX - touch1.clientX;
  var dy = touch0.clientY - touch1.clientY;

  // distance between touches
  var distance = Math.sqrt(dx * dx + dy * dy);

  if (this.lastDistance_ !== undefined) {
    scaleDelta = this.lastDistance_ / distance;
  }
  this.lastDistance_ = distance;
  if (scaleDelta != 1.0) {
    this.lastScaleDelta_ = scaleDelta;
  }

  var map = mapBrowserEvent.map;
  var view = map.getView();
  var resolution = view.getResolution();

  // scale anchor point.
  var viewportPosition = map.getViewport().getBoundingClientRect();
  var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
  centroid[0] -= viewportPosition.left;
  centroid[1] -= viewportPosition.top;
  this.anchor_ = map.getCoordinateFromPixel(centroid);

  // scale, bypass the resolution constraint
  map.render();
  ol.interaction.Interaction.zoomWithoutConstraints(
      map, view, resolution * scaleDelta, this.anchor_);

};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.PinchZoom}
 * @private
 */
ol.interaction.PinchZoom.handleUpEvent_ = function(mapBrowserEvent) {
  if (this.targetPointers.length < 2) {
    var map = mapBrowserEvent.map;
    var view = map.getView();
    view.setHint(ol.View.Hint.INTERACTING, -1);
    var resolution = view.getResolution();
    // Zoom to final resolution, with an animation, and provide a
    // direction not to zoom out/in if user was pinching in/out.
    // Direction is > 0 if pinching out, and < 0 if pinching in.
    var direction = this.lastScaleDelta_ - 1;
    ol.interaction.Interaction.zoom(map, view, resolution,
        this.anchor_, this.duration_, direction);
    return false;
  } else {
    return true;
  }
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.PinchZoom}
 * @private
 */
ol.interaction.PinchZoom.handleDownEvent_ = function(mapBrowserEvent) {
  if (this.targetPointers.length >= 2) {
    var map = mapBrowserEvent.map;
    this.anchor_ = null;
    this.lastDistance_ = undefined;
    this.lastScaleDelta_ = 1;
    if (!this.handlingDownUpSequence) {
      map.getView().setHint(ol.View.Hint.INTERACTING, 1);
    }
    map.render();
    return true;
  } else {
    return false;
  }
};


/**
 * @inheritDoc
 */
ol.interaction.PinchZoom.prototype.shouldStopEvent = ol.functions.FALSE;

goog.provide('ol.interaction');

goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.Kinetic');
goog.require('ol.interaction.DoubleClickZoom');
goog.require('ol.interaction.DragPan');
goog.require('ol.interaction.DragRotate');
goog.require('ol.interaction.DragZoom');
goog.require('ol.interaction.KeyboardPan');
goog.require('ol.interaction.KeyboardZoom');
goog.require('ol.interaction.MouseWheelZoom');
goog.require('ol.interaction.PinchRotate');
goog.require('ol.interaction.PinchZoom');


/**
 * Set of interactions included in maps by default. Specific interactions can be
 * excluded by setting the appropriate option to false in the constructor
 * options, but the order of the interactions is fixed.  If you want to specify
 * a different order for interactions, you will need to create your own
 * {@link ol.interaction.Interaction} instances and insert them into a
 * {@link ol.Collection} in the order you want before creating your
 * {@link ol.Map} instance. The default set of interactions, in sequence, is:
 * * {@link ol.interaction.DragRotate}
 * * {@link ol.interaction.DoubleClickZoom}
 * * {@link ol.interaction.DragPan}
 * * {@link ol.interaction.PinchRotate}
 * * {@link ol.interaction.PinchZoom}
 * * {@link ol.interaction.KeyboardPan}
 * * {@link ol.interaction.KeyboardZoom}
 * * {@link ol.interaction.MouseWheelZoom}
 * * {@link ol.interaction.DragZoom}
 *
 * @param {olx.interaction.DefaultsOptions=} opt_options Defaults options.
 * @return {ol.Collection.<ol.interaction.Interaction>} A collection of
 * interactions to be used with the ol.Map constructor's interactions option.
 * @api stable
 */
ol.interaction.defaults = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var interactions = new ol.Collection();

  var kinetic = new ol.Kinetic(-0.005, 0.05, 100);

  var altShiftDragRotate = options.altShiftDragRotate !== undefined ?
      options.altShiftDragRotate : true;
  if (altShiftDragRotate) {
    interactions.push(new ol.interaction.DragRotate());
  }

  var doubleClickZoom = options.doubleClickZoom !== undefined ?
      options.doubleClickZoom : true;
  if (doubleClickZoom) {
    interactions.push(new ol.interaction.DoubleClickZoom({
      delta: options.zoomDelta,
      duration: options.zoomDuration
    }));
  }

  var dragPan = options.dragPan !== undefined ? options.dragPan : true;
  if (dragPan) {
    interactions.push(new ol.interaction.DragPan({
      kinetic: kinetic
    }));
  }

  var pinchRotate = options.pinchRotate !== undefined ? options.pinchRotate :
      true;
  if (pinchRotate) {
    interactions.push(new ol.interaction.PinchRotate());
  }

  var pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true;
  if (pinchZoom) {
    interactions.push(new ol.interaction.PinchZoom({
      duration: options.zoomDuration
    }));
  }

  var keyboard = options.keyboard !== undefined ? options.keyboard : true;
  if (keyboard) {
    interactions.push(new ol.interaction.KeyboardPan());
    interactions.push(new ol.interaction.KeyboardZoom({
      delta: options.zoomDelta,
      duration: options.zoomDuration
    }));
  }

  var mouseWheelZoom = options.mouseWheelZoom !== undefined ?
      options.mouseWheelZoom : true;
  if (mouseWheelZoom) {
    interactions.push(new ol.interaction.MouseWheelZoom({
      duration: options.zoomDuration
    }));
  }

  var shiftDragZoom = options.shiftDragZoom !== undefined ?
      options.shiftDragZoom : true;
  if (shiftDragZoom) {
    interactions.push(new ol.interaction.DragZoom({
      duration: options.zoomDuration
    }));
  }

  return interactions;

};

goog.provide('ol.layer.Group');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.Collection');
goog.require('ol.Object');
goog.require('ol.ObjectEventType');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.layer.Base');
goog.require('ol.obj');
goog.require('ol.source.State');


/**
 * @enum {string}
 */
ol.layer.GroupProperty = {
  LAYERS: 'layers'
};


/**
 * @classdesc
 * A {@link ol.Collection} of layers that are handled together.
 *
 * A generic `change` event is triggered when the group/Collection changes.
 *
 * @constructor
 * @extends {ol.layer.Base}
 * @param {olx.layer.GroupOptions=} opt_options Layer options.
 * @api stable
 */
ol.layer.Group = function(opt_options) {

  var options = opt_options || {};
  var baseOptions = /** @type {olx.layer.GroupOptions} */
      (ol.obj.assign({}, options));
  delete baseOptions.layers;

  var layers = options.layers;

  ol.layer.Base.call(this, baseOptions);

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.layersListenerKeys_ = [];

  /**
   * @private
   * @type {Object.<string, Array.<ol.EventsKey>>}
   */
  this.listenerKeys_ = {};

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.layer.GroupProperty.LAYERS),
      this.handleLayersChanged_, this);

  if (layers) {
    if (Array.isArray(layers)) {
      layers = new ol.Collection(layers.slice());
    } else {
      ol.asserts.assert(layers instanceof ol.Collection,
          43); // Expected `layers` to be an array or an `ol.Collection`
      layers = layers;
    }
  } else {
    layers = new ol.Collection();
  }

  this.setLayers(layers);

};
ol.inherits(ol.layer.Group, ol.layer.Base);


/**
 * @private
 */
ol.layer.Group.prototype.handleLayerChange_ = function() {
  if (this.getVisible()) {
    this.changed();
  }
};


/**
 * @param {ol.events.Event} event Event.
 * @private
 */
ol.layer.Group.prototype.handleLayersChanged_ = function(event) {
  this.layersListenerKeys_.forEach(ol.events.unlistenByKey);
  this.layersListenerKeys_.length = 0;

  var layers = this.getLayers();
  this.layersListenerKeys_.push(
      ol.events.listen(layers, ol.Collection.EventType.ADD,
          this.handleLayersAdd_, this),
      ol.events.listen(layers, ol.Collection.EventType.REMOVE,
          this.handleLayersRemove_, this));

  for (var id in this.listenerKeys_) {
    this.listenerKeys_[id].forEach(ol.events.unlistenByKey);
  }
  ol.obj.clear(this.listenerKeys_);

  var layersArray = layers.getArray();
  var i, ii, layer;
  for (i = 0, ii = layersArray.length; i < ii; i++) {
    layer = layersArray[i];
    this.listenerKeys_[ol.getUid(layer).toString()] = [
      ol.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE,
          this.handleLayerChange_, this),
      ol.events.listen(layer, ol.events.EventType.CHANGE,
          this.handleLayerChange_, this)
    ];
  }

  this.changed();
};


/**
 * @param {ol.Collection.Event} collectionEvent Collection event.
 * @private
 */
ol.layer.Group.prototype.handleLayersAdd_ = function(collectionEvent) {
  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
  var key = ol.getUid(layer).toString();
  goog.DEBUG && console.assert(!(key in this.listenerKeys_),
      'listeners already registered');
  this.listenerKeys_[key] = [
    ol.events.listen(layer, ol.ObjectEventType.PROPERTYCHANGE,
        this.handleLayerChange_, this),
    ol.events.listen(layer, ol.events.EventType.CHANGE,
        this.handleLayerChange_, this)
  ];
  this.changed();
};


/**
 * @param {ol.Collection.Event} collectionEvent Collection event.
 * @private
 */
ol.layer.Group.prototype.handleLayersRemove_ = function(collectionEvent) {
  var layer = /** @type {ol.layer.Base} */ (collectionEvent.element);
  var key = ol.getUid(layer).toString();
  goog.DEBUG && console.assert(key in this.listenerKeys_, 'no listeners to unregister');
  this.listenerKeys_[key].forEach(ol.events.unlistenByKey);
  delete this.listenerKeys_[key];
  this.changed();
};


/**
 * Returns the {@link ol.Collection collection} of {@link ol.layer.Layer layers}
 * in this group.
 * @return {!ol.Collection.<ol.layer.Base>} Collection of
 *   {@link ol.layer.Base layers} that are part of this group.
 * @observable
 * @api stable
 */
ol.layer.Group.prototype.getLayers = function() {
  return /** @type {!ol.Collection.<ol.layer.Base>} */ (this.get(
      ol.layer.GroupProperty.LAYERS));
};


/**
 * Set the {@link ol.Collection collection} of {@link ol.layer.Layer layers}
 * in this group.
 * @param {!ol.Collection.<ol.layer.Base>} layers Collection of
 *   {@link ol.layer.Base layers} that are part of this group.
 * @observable
 * @api stable
 */
ol.layer.Group.prototype.setLayers = function(layers) {
  this.set(ol.layer.GroupProperty.LAYERS, layers);
};


/**
 * @inheritDoc
 */
ol.layer.Group.prototype.getLayersArray = function(opt_array) {
  var array = opt_array !== undefined ? opt_array : [];
  this.getLayers().forEach(function(layer) {
    layer.getLayersArray(array);
  });
  return array;
};


/**
 * @inheritDoc
 */
ol.layer.Group.prototype.getLayerStatesArray = function(opt_states) {
  var states = opt_states !== undefined ? opt_states : [];

  var pos = states.length;

  this.getLayers().forEach(function(layer) {
    layer.getLayerStatesArray(states);
  });

  var ownLayerState = this.getLayerState();
  var i, ii, layerState;
  for (i = pos, ii = states.length; i < ii; i++) {
    layerState = states[i];
    layerState.opacity *= ownLayerState.opacity;
    layerState.visible = layerState.visible && ownLayerState.visible;
    layerState.maxResolution = Math.min(
        layerState.maxResolution, ownLayerState.maxResolution);
    layerState.minResolution = Math.max(
        layerState.minResolution, ownLayerState.minResolution);
    if (ownLayerState.extent !== undefined) {
      if (layerState.extent !== undefined) {
        layerState.extent = ol.extent.getIntersection(
            layerState.extent, ownLayerState.extent);
      } else {
        layerState.extent = ownLayerState.extent;
      }
    }
  }

  return states;
};


/**
 * @inheritDoc
 */
ol.layer.Group.prototype.getSourceState = function() {
  return ol.source.State.READY;
};

goog.provide('ol.proj.EPSG3857');

goog.require('ol');
goog.require('ol.math');
goog.require('ol.proj');
goog.require('ol.proj.Projection');
goog.require('ol.proj.Units');


/**
 * @classdesc
 * Projection object for web/spherical Mercator (EPSG:3857).
 *
 * @constructor
 * @extends {ol.proj.Projection}
 * @param {string} code Code.
 * @private
 */
ol.proj.EPSG3857_ = function(code) {
  ol.proj.Projection.call(this, {
    code: code,
    units: ol.proj.Units.METERS,
    extent: ol.proj.EPSG3857.EXTENT,
    global: true,
    worldExtent: ol.proj.EPSG3857.WORLD_EXTENT
  });
};
ol.inherits(ol.proj.EPSG3857_, ol.proj.Projection);


/**
 * @inheritDoc
 */
ol.proj.EPSG3857_.prototype.getPointResolution = function(resolution, point) {
  return resolution / ol.math.cosh(point[1] / ol.proj.EPSG3857.RADIUS);
};


/**
 * @const
 * @type {number}
 */
ol.proj.EPSG3857.RADIUS = 6378137;


/**
 * @const
 * @type {number}
 */
ol.proj.EPSG3857.HALF_SIZE = Math.PI * ol.proj.EPSG3857.RADIUS;


/**
 * @const
 * @type {ol.Extent}
 */
ol.proj.EPSG3857.EXTENT = [
  -ol.proj.EPSG3857.HALF_SIZE, -ol.proj.EPSG3857.HALF_SIZE,
  ol.proj.EPSG3857.HALF_SIZE, ol.proj.EPSG3857.HALF_SIZE
];


/**
 * @const
 * @type {ol.Extent}
 */
ol.proj.EPSG3857.WORLD_EXTENT = [-180, -85, 180, 85];


/**
 * Lists several projection codes with the same meaning as EPSG:3857.
 *
 * @type {Array.<string>}
 */
ol.proj.EPSG3857.CODES = [
  'EPSG:3857',
  'EPSG:102100',
  'EPSG:102113',
  'EPSG:900913',
  'urn:ogc:def:crs:EPSG:6.18:3:3857',
  'urn:ogc:def:crs:EPSG::3857',
  'http://www.opengis.net/gml/srs/epsg.xml#3857'
];


/**
 * Projections equal to EPSG:3857.
 *
 * @const
 * @type {Array.<ol.proj.Projection>}
 */
ol.proj.EPSG3857.PROJECTIONS = ol.proj.EPSG3857.CODES.map(function(code) {
  return new ol.proj.EPSG3857_(code);
});


/**
 * Transformation from EPSG:4326 to EPSG:3857.
 *
 * @param {Array.<number>} input Input array of coordinate values.
 * @param {Array.<number>=} opt_output Output array of coordinate values.
 * @param {number=} opt_dimension Dimension (default is `2`).
 * @return {Array.<number>} Output array of coordinate values.
 */
ol.proj.EPSG3857.fromEPSG4326 = function(input, opt_output, opt_dimension) {
  var length = input.length,
      dimension = opt_dimension > 1 ? opt_dimension : 2,
      output = opt_output;
  if (output === undefined) {
    if (dimension > 2) {
      // preserve values beyond second dimension
      output = input.slice();
    } else {
      output = new Array(length);
    }
  }
  goog.DEBUG && console.assert(output.length % dimension === 0,
      'modulus of output.length with dimension should be 0');
  for (var i = 0; i < length; i += dimension) {
    output[i] = ol.proj.EPSG3857.RADIUS * Math.PI * input[i] / 180;
    output[i + 1] = ol.proj.EPSG3857.RADIUS *
        Math.log(Math.tan(Math.PI * (input[i + 1] + 90) / 360));
  }
  return output;
};


/**
 * Transformation from EPSG:3857 to EPSG:4326.
 *
 * @param {Array.<number>} input Input array of coordinate values.
 * @param {Array.<number>=} opt_output Output array of coordinate values.
 * @param {number=} opt_dimension Dimension (default is `2`).
 * @return {Array.<number>} Output array of coordinate values.
 */
ol.proj.EPSG3857.toEPSG4326 = function(input, opt_output, opt_dimension) {
  var length = input.length,
      dimension = opt_dimension > 1 ? opt_dimension : 2,
      output = opt_output;
  if (output === undefined) {
    if (dimension > 2) {
      // preserve values beyond second dimension
      output = input.slice();
    } else {
      output = new Array(length);
    }
  }
  goog.DEBUG && console.assert(output.length % dimension === 0,
      'modulus of output.length with dimension should be 0');
  for (var i = 0; i < length; i += dimension) {
    output[i] = 180 * input[i] / (ol.proj.EPSG3857.RADIUS * Math.PI);
    output[i + 1] = 360 * Math.atan(
        Math.exp(input[i + 1] / ol.proj.EPSG3857.RADIUS)) / Math.PI - 90;
  }
  return output;
};

goog.provide('ol.sphere.WGS84');

goog.require('ol.Sphere');


/**
 * A sphere with radius equal to the semi-major axis of the WGS84 ellipsoid.
 * @const
 * @type {ol.Sphere}
 */
ol.sphere.WGS84 = new ol.Sphere(6378137);

goog.provide('ol.proj.EPSG4326');

goog.require('ol');
goog.require('ol.proj');
goog.require('ol.proj.Projection');
goog.require('ol.proj.Units');
goog.require('ol.sphere.WGS84');


/**
 * @classdesc
 * Projection object for WGS84 geographic coordinates (EPSG:4326).
 *
 * Note that OpenLayers does not strictly comply with the EPSG definition.
 * The EPSG registry defines 4326 as a CRS for Latitude,Longitude (y,x).
 * OpenLayers treats EPSG:4326 as a pseudo-projection, with x,y coordinates.
 *
 * @constructor
 * @extends {ol.proj.Projection}
 * @param {string} code Code.
 * @param {string=} opt_axisOrientation Axis orientation.
 * @private
 */
ol.proj.EPSG4326_ = function(code, opt_axisOrientation) {
  ol.proj.Projection.call(this, {
    code: code,
    units: ol.proj.Units.DEGREES,
    extent: ol.proj.EPSG4326.EXTENT,
    axisOrientation: opt_axisOrientation,
    global: true,
    metersPerUnit: ol.proj.EPSG4326.METERS_PER_UNIT,
    worldExtent: ol.proj.EPSG4326.EXTENT
  });
};
ol.inherits(ol.proj.EPSG4326_, ol.proj.Projection);


/**
 * @inheritDoc
 */
ol.proj.EPSG4326_.prototype.getPointResolution = function(resolution, point) {
  return resolution;
};


/**
 * Extent of the EPSG:4326 projection which is the whole world.
 *
 * @const
 * @type {ol.Extent}
 */
ol.proj.EPSG4326.EXTENT = [-180, -90, 180, 90];


/**
 * @const
 * @type {number}
 */
ol.proj.EPSG4326.METERS_PER_UNIT = Math.PI * ol.sphere.WGS84.radius / 180;


/**
 * Projections equal to EPSG:4326.
 *
 * @const
 * @type {Array.<ol.proj.Projection>}
 */
ol.proj.EPSG4326.PROJECTIONS = [
  new ol.proj.EPSG4326_('CRS:84'),
  new ol.proj.EPSG4326_('EPSG:4326', 'neu'),
  new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG::4326', 'neu'),
  new ol.proj.EPSG4326_('urn:ogc:def:crs:EPSG:6.6:4326', 'neu'),
  new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:1.3:CRS84'),
  new ol.proj.EPSG4326_('urn:ogc:def:crs:OGC:2:84'),
  new ol.proj.EPSG4326_('http://www.opengis.net/gml/srs/epsg.xml#4326', 'neu'),
  new ol.proj.EPSG4326_('urn:x-ogc:def:crs:EPSG:4326', 'neu')
];

goog.provide('ol.proj.common');

goog.require('ol.proj');
goog.require('ol.proj.EPSG3857');
goog.require('ol.proj.EPSG4326');


/**
 * FIXME empty description for jsdoc
 * @api
 */
ol.proj.common.add = function() {
  // Add transformations that don't alter coordinates to convert within set of
  // projections with equal meaning.
  ol.proj.addEquivalentProjections(ol.proj.EPSG3857.PROJECTIONS);
  ol.proj.addEquivalentProjections(ol.proj.EPSG4326.PROJECTIONS);
  // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like
  // coordinates and back.
  ol.proj.addEquivalentTransforms(
      ol.proj.EPSG4326.PROJECTIONS,
      ol.proj.EPSG3857.PROJECTIONS,
      ol.proj.EPSG3857.fromEPSG4326,
      ol.proj.EPSG3857.toEPSG4326);
};

goog.provide('ol.layer.Image');

goog.require('ol');
goog.require('ol.layer.Layer');


/**
 * @classdesc
 * Server-rendered images that are available for arbitrary extents and
 * resolutions.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Layer}
 * @fires ol.render.Event
 * @param {olx.layer.ImageOptions=} opt_options Layer options.
 * @api stable
 */
ol.layer.Image = function(opt_options) {
  var options = opt_options ? opt_options : {};
  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (options));
};
ol.inherits(ol.layer.Image, ol.layer.Layer);


/**
 * Return the associated {@link ol.source.Image source} of the image layer.
 * @function
 * @return {ol.source.Image} Source.
 * @api stable
 */
ol.layer.Image.prototype.getSource;

goog.provide('ol.layer.Tile');

goog.require('ol');
goog.require('ol.layer.Layer');
goog.require('ol.obj');


/**
 * @enum {string}
 */
ol.layer.TileProperty = {
  PRELOAD: 'preload',
  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
};


/**
 * @classdesc
 * For layer sources that provide pre-rendered, tiled images in grids that are
 * organized by zoom levels for specific resolutions.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Layer}
 * @fires ol.render.Event
 * @param {olx.layer.TileOptions=} opt_options Tile layer options.
 * @api stable
 */
ol.layer.Tile = function(opt_options) {
  var options = opt_options ? opt_options : {};

  var baseOptions = ol.obj.assign({}, options);

  delete baseOptions.preload;
  delete baseOptions.useInterimTilesOnError;
  ol.layer.Layer.call(this,  /** @type {olx.layer.LayerOptions} */ (baseOptions));

  this.setPreload(options.preload !== undefined ? options.preload : 0);
  this.setUseInterimTilesOnError(options.useInterimTilesOnError !== undefined ?
      options.useInterimTilesOnError : true);
};
ol.inherits(ol.layer.Tile, ol.layer.Layer);


/**
 * Return the level as number to which we will preload tiles up to.
 * @return {number} The level to preload tiles up to.
 * @observable
 * @api
 */
ol.layer.Tile.prototype.getPreload = function() {
  return /** @type {number} */ (this.get(ol.layer.TileProperty.PRELOAD));
};


/**
 * Return the associated {@link ol.source.Tile tilesource} of the layer.
 * @function
 * @return {ol.source.Tile} Source.
 * @api stable
 */
ol.layer.Tile.prototype.getSource;


/**
 * Set the level as number to which we will preload tiles up to.
 * @param {number} preload The level to preload tiles up to.
 * @observable
 * @api
 */
ol.layer.Tile.prototype.setPreload = function(preload) {
  this.set(ol.layer.TileProperty.PRELOAD, preload);
};


/**
 * Whether we use interim tiles on error.
 * @return {boolean} Use interim tiles on error.
 * @observable
 * @api
 */
ol.layer.Tile.prototype.getUseInterimTilesOnError = function() {
  return /** @type {boolean} */ (
      this.get(ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR));
};


/**
 * Set whether we use interim tiles on error.
 * @param {boolean} useInterimTilesOnError Use interim tiles on error.
 * @observable
 * @api
 */
ol.layer.Tile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
  this.set(
      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
};

goog.provide('ol.render.canvas');


/**
 * @const
 * @type {string}
 */
ol.render.canvas.defaultFont = '10px sans-serif';


/**
 * @const
 * @type {ol.Color}
 */
ol.render.canvas.defaultFillStyle = [0, 0, 0, 1];


/**
 * @const
 * @type {string}
 */
ol.render.canvas.defaultLineCap = 'round';


/**
 * @const
 * @type {Array.<number>}
 */
ol.render.canvas.defaultLineDash = [];


/**
 * @const
 * @type {string}
 */
ol.render.canvas.defaultLineJoin = 'round';


/**
 * @const
 * @type {number}
 */
ol.render.canvas.defaultMiterLimit = 10;


/**
 * @const
 * @type {ol.Color}
 */
ol.render.canvas.defaultStrokeStyle = [0, 0, 0, 1];


/**
 * @const
 * @type {string}
 */
ol.render.canvas.defaultTextAlign = 'center';


/**
 * @const
 * @type {string}
 */
ol.render.canvas.defaultTextBaseline = 'middle';


/**
 * @const
 * @type {number}
 */
ol.render.canvas.defaultLineWidth = 1;


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {number} rotation Rotation.
 * @param {number} offsetX X offset.
 * @param {number} offsetY Y offset.
 */
ol.render.canvas.rotateAtOffset = function(context, rotation, offsetX, offsetY) {
  if (rotation !== 0) {
    context.translate(offsetX, offsetY);
    context.rotate(rotation);
    context.translate(-offsetX, -offsetY);
  }
};

goog.provide('ol.style.Image');
goog.provide('ol.style.ImageState');


/**
 * @enum {number}
 */
ol.style.ImageState = {
  IDLE: 0,
  LOADING: 1,
  LOADED: 2,
  ERROR: 3
};


/**
 * @classdesc
 * A base class used for creating subclasses and not instantiated in
 * apps. Base class for {@link ol.style.Icon}, {@link ol.style.Circle} and
 * {@link ol.style.RegularShape}.
 *
 * @constructor
 * @param {ol.StyleImageOptions} options Options.
 * @api
 */
ol.style.Image = function(options) {

  /**
   * @private
   * @type {number}
   */
  this.opacity_ = options.opacity;

  /**
   * @private
   * @type {boolean}
   */
  this.rotateWithView_ = options.rotateWithView;

  /**
   * @private
   * @type {number}
   */
  this.rotation_ = options.rotation;

  /**
   * @private
   * @type {number}
   */
  this.scale_ = options.scale;

  /**
   * @private
   * @type {boolean}
   */
  this.snapToPixel_ = options.snapToPixel;

};


/**
 * Get the symbolizer opacity.
 * @return {number} Opacity.
 * @api
 */
ol.style.Image.prototype.getOpacity = function() {
  return this.opacity_;
};


/**
 * Determine whether the symbolizer rotates with the map.
 * @return {boolean} Rotate with map.
 * @api
 */
ol.style.Image.prototype.getRotateWithView = function() {
  return this.rotateWithView_;
};


/**
 * Get the symoblizer rotation.
 * @return {number} Rotation.
 * @api
 */
ol.style.Image.prototype.getRotation = function() {
  return this.rotation_;
};


/**
 * Get the symbolizer scale.
 * @return {number} Scale.
 * @api
 */
ol.style.Image.prototype.getScale = function() {
  return this.scale_;
};


/**
 * Determine whether the symbolizer should be snapped to a pixel.
 * @return {boolean} The symbolizer should snap to a pixel.
 * @api
 */
ol.style.Image.prototype.getSnapToPixel = function() {
  return this.snapToPixel_;
};


/**
 * Get the anchor point in pixels. The anchor determines the center point for the
 * symbolizer.
 * @abstract
 * @return {Array.<number>} Anchor.
 */
ol.style.Image.prototype.getAnchor = function() {};


/**
 * Get the image element for the symbolizer.
 * @abstract
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
 */
ol.style.Image.prototype.getImage = function(pixelRatio) {};


/**
 * @abstract
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Image element.
 */
ol.style.Image.prototype.getHitDetectionImage = function(pixelRatio) {};


/**
 * @abstract
 * @return {ol.style.ImageState} Image state.
 */
ol.style.Image.prototype.getImageState = function() {};


/**
 * @abstract
 * @return {ol.Size} Image size.
 */
ol.style.Image.prototype.getImageSize = function() {};


/**
 * @abstract
 * @return {ol.Size} Size of the hit-detection image.
 */
ol.style.Image.prototype.getHitDetectionImageSize = function() {};


/**
 * Get the origin of the symbolizer.
 * @abstract
 * @return {Array.<number>} Origin.
 */
ol.style.Image.prototype.getOrigin = function() {};


/**
 * Get the size of the symbolizer (in pixels).
 * @abstract
 * @return {ol.Size} Size.
 */
ol.style.Image.prototype.getSize = function() {};


/**
 * Set the opacity.
 *
 * @param {number} opacity Opacity.
 * @api
 */
ol.style.Image.prototype.setOpacity = function(opacity) {
  this.opacity_ = opacity;
};


/**
 * Set whether to rotate the style with the view.
 *
 * @param {boolean} rotateWithView Rotate with map.
 */
ol.style.Image.prototype.setRotateWithView = function(rotateWithView) {
  this.rotateWithView_ = rotateWithView;
};


/**
 * Set the rotation.
 *
 * @param {number} rotation Rotation.
 * @api
 */
ol.style.Image.prototype.setRotation = function(rotation) {
  this.rotation_ = rotation;
};


/**
 * Set the scale.
 *
 * @param {number} scale Scale.
 * @api
 */
ol.style.Image.prototype.setScale = function(scale) {
  this.scale_ = scale;
};


/**
 * Set whether to snap the image to the closest pixel.
 *
 * @param {boolean} snapToPixel Snap to pixel?
 */
ol.style.Image.prototype.setSnapToPixel = function(snapToPixel) {
  this.snapToPixel_ = snapToPixel;
};


/**
 * @abstract
 * @param {function(this: T, ol.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @return {ol.EventsKey|undefined} Listener key.
 * @template T
 */
ol.style.Image.prototype.listenImageChange = function(listener, thisArg) {};


/**
 * Load not yet loaded URI.
 * @abstract
 */
ol.style.Image.prototype.load = function() {};


/**
 * @abstract
 * @param {function(this: T, ol.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @template T
 */
ol.style.Image.prototype.unlistenImageChange = function(listener, thisArg) {};

goog.provide('ol.style.Circle');

goog.require('ol');
goog.require('ol.color');
goog.require('ol.colorlike');
goog.require('ol.dom');
goog.require('ol.has');
goog.require('ol.render.canvas');
goog.require('ol.style.Image');
goog.require('ol.style.ImageState');


/**
 * @classdesc
 * Set circle style for vector features.
 *
 * @constructor
 * @param {olx.style.CircleOptions=} opt_options Options.
 * @extends {ol.style.Image}
 * @api
 */
ol.style.Circle = function(opt_options) {

  var options = opt_options || {};

  /**
   * @private
   * @type {Array.<string>}
   */
  this.checksums_ = null;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = null;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.hitDetectionCanvas_ = null;

  /**
   * @private
   * @type {ol.style.Fill}
   */
  this.fill_ = options.fill !== undefined ? options.fill : null;

  /**
   * @private
   * @type {ol.style.Stroke}
   */
  this.stroke_ = options.stroke !== undefined ? options.stroke : null;

  /**
   * @private
   * @type {number}
   */
  this.radius_ = options.radius;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.origin_ = [0, 0];

  /**
   * @private
   * @type {Array.<number>}
   */
  this.anchor_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.size_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.imageSize_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.hitDetectionImageSize_ = null;

  this.render_(options.atlasManager);

  /**
   * @type {boolean}
   */
  var snapToPixel = options.snapToPixel !== undefined ?
      options.snapToPixel : true;

  ol.style.Image.call(this, {
    opacity: 1,
    rotateWithView: false,
    rotation: 0,
    scale: 1,
    snapToPixel: snapToPixel
  });

};
ol.inherits(ol.style.Circle, ol.style.Image);


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getAnchor = function() {
  return this.anchor_;
};


/**
 * Get the fill style for the circle.
 * @return {ol.style.Fill} Fill style.
 * @api
 */
ol.style.Circle.prototype.getFill = function() {
  return this.fill_;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getHitDetectionImage = function(pixelRatio) {
  return this.hitDetectionCanvas_;
};


/**
 * Get the image used to render the circle.
 * @param {number} pixelRatio Pixel ratio.
 * @return {HTMLCanvasElement} Canvas element.
 * @api
 */
ol.style.Circle.prototype.getImage = function(pixelRatio) {
  return this.canvas_;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getImageState = function() {
  return ol.style.ImageState.LOADED;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getImageSize = function() {
  return this.imageSize_;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getHitDetectionImageSize = function() {
  return this.hitDetectionImageSize_;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getOrigin = function() {
  return this.origin_;
};


/**
 * Get the circle radius.
 * @return {number} Radius.
 * @api
 */
ol.style.Circle.prototype.getRadius = function() {
  return this.radius_;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.getSize = function() {
  return this.size_;
};


/**
 * Get the stroke style for the circle.
 * @return {ol.style.Stroke} Stroke style.
 * @api
 */
ol.style.Circle.prototype.getStroke = function() {
  return this.stroke_;
};


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.listenImageChange = ol.nullFunction;


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.load = ol.nullFunction;


/**
 * @inheritDoc
 */
ol.style.Circle.prototype.unlistenImageChange = ol.nullFunction;


/**
 * @private
 * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
 */
ol.style.Circle.prototype.render_ = function(atlasManager) {
  var imageSize;
  var lineDash = null;
  var strokeStyle;
  var strokeWidth = 0;

  if (this.stroke_) {
    strokeStyle = ol.color.asString(this.stroke_.getColor());
    strokeWidth = this.stroke_.getWidth();
    if (strokeWidth === undefined) {
      strokeWidth = ol.render.canvas.defaultLineWidth;
    }
    lineDash = this.stroke_.getLineDash();
    if (!ol.has.CANVAS_LINE_DASH) {
      lineDash = null;
    }
  }


  var size = 2 * (this.radius_ + strokeWidth) + 1;

  /** @type {ol.CircleRenderOptions} */
  var renderOptions = {
    strokeStyle: strokeStyle,
    strokeWidth: strokeWidth,
    size: size,
    lineDash: lineDash
  };

  if (atlasManager === undefined) {
    // no atlas manager is used, create a new canvas
    var context = ol.dom.createCanvasContext2D(size, size);
    this.canvas_ = context.canvas;

    // canvas.width and height are rounded to the closest integer
    size = this.canvas_.width;
    imageSize = size;

    // draw the circle on the canvas
    this.draw_(renderOptions, context, 0, 0);

    this.createHitDetectionCanvas_(renderOptions);
  } else {
    // an atlas manager is used, add the symbol to an atlas
    size = Math.round(size);

    var hasCustomHitDetectionImage = !this.fill_;
    var renderHitDetectionCallback;
    if (hasCustomHitDetectionImage) {
      // render the hit-detection image into a separate atlas image
      renderHitDetectionCallback =
          this.drawHitDetectionCanvas_.bind(this, renderOptions);
    }

    var id = this.getChecksum();
    var info = atlasManager.add(
        id, size, size, this.draw_.bind(this, renderOptions),
        renderHitDetectionCallback);
    goog.DEBUG && console.assert(info, 'circle radius is too large');

    this.canvas_ = info.image;
    this.origin_ = [info.offsetX, info.offsetY];
    imageSize = info.image.width;

    if (hasCustomHitDetectionImage) {
      this.hitDetectionCanvas_ = info.hitImage;
      this.hitDetectionImageSize_ =
          [info.hitImage.width, info.hitImage.height];
    } else {
      this.hitDetectionCanvas_ = this.canvas_;
      this.hitDetectionImageSize_ = [imageSize, imageSize];
    }
  }

  this.anchor_ = [size / 2, size / 2];
  this.size_ = [size, size];
  this.imageSize_ = [imageSize, imageSize];
};


/**
 * @private
 * @param {ol.CircleRenderOptions} renderOptions Render options.
 * @param {CanvasRenderingContext2D} context The rendering context.
 * @param {number} x The origin for the symbol (x).
 * @param {number} y The origin for the symbol (y).
 */
ol.style.Circle.prototype.draw_ = function(renderOptions, context, x, y) {
  // reset transform
  context.setTransform(1, 0, 0, 1, 0, 0);

  // then move to (x, y)
  context.translate(x, y);

  context.beginPath();
  context.arc(
      renderOptions.size / 2, renderOptions.size / 2,
      this.radius_, 0, 2 * Math.PI, true);

  if (this.fill_) {
    context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor());
    context.fill();
  }
  if (this.stroke_) {
    context.strokeStyle = renderOptions.strokeStyle;
    context.lineWidth = renderOptions.strokeWidth;
    if (renderOptions.lineDash) {
      context.setLineDash(renderOptions.lineDash);
    }
    context.stroke();
  }
  context.closePath();
};


/**
 * @private
 * @param {ol.CircleRenderOptions} renderOptions Render options.
 */
ol.style.Circle.prototype.createHitDetectionCanvas_ = function(renderOptions) {
  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
  if (this.fill_) {
    this.hitDetectionCanvas_ = this.canvas_;
    return;
  }

  // if no fill style is set, create an extra hit-detection image with a
  // default fill style
  var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
  this.hitDetectionCanvas_ = context.canvas;

  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
};


/**
 * @private
 * @param {ol.CircleRenderOptions} renderOptions Render options.
 * @param {CanvasRenderingContext2D} context The context.
 * @param {number} x The origin for the symbol (x).
 * @param {number} y The origin for the symbol (y).
 */
ol.style.Circle.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) {
  // reset transform
  context.setTransform(1, 0, 0, 1, 0, 0);

  // then move to (x, y)
  context.translate(x, y);

  context.beginPath();
  context.arc(
      renderOptions.size / 2, renderOptions.size / 2,
      this.radius_, 0, 2 * Math.PI, true);

  context.fillStyle = ol.color.asString(ol.render.canvas.defaultFillStyle);
  context.fill();
  if (this.stroke_) {
    context.strokeStyle = renderOptions.strokeStyle;
    context.lineWidth = renderOptions.strokeWidth;
    if (renderOptions.lineDash) {
      context.setLineDash(renderOptions.lineDash);
    }
    context.stroke();
  }
  context.closePath();
};


/**
 * @return {string} The checksum.
 */
ol.style.Circle.prototype.getChecksum = function() {
  var strokeChecksum = this.stroke_ ?
      this.stroke_.getChecksum() : '-';
  var fillChecksum = this.fill_ ?
      this.fill_.getChecksum() : '-';

  var recalculate = !this.checksums_ ||
      (strokeChecksum != this.checksums_[1] ||
      fillChecksum != this.checksums_[2] ||
      this.radius_ != this.checksums_[3]);

  if (recalculate) {
    var checksum = 'c' + strokeChecksum + fillChecksum +
        (this.radius_ !== undefined ? this.radius_.toString() : '-');
    this.checksums_ = [checksum, strokeChecksum, fillChecksum, this.radius_];
  }

  return this.checksums_[0];
};

goog.provide('ol.style.Fill');

goog.require('ol');
goog.require('ol.color');


/**
 * @classdesc
 * Set fill style for vector features.
 *
 * @constructor
 * @param {olx.style.FillOptions=} opt_options Options.
 * @api
 */
ol.style.Fill = function(opt_options) {

  var options = opt_options || {};

  /**
   * @private
   * @type {ol.Color|ol.ColorLike}
   */
  this.color_ = options.color !== undefined ? options.color : null;

  /**
   * @private
   * @type {string|undefined}
   */
  this.checksum_ = undefined;
};


/**
 * Get the fill color.
 * @return {ol.Color|ol.ColorLike} Color.
 * @api
 */
ol.style.Fill.prototype.getColor = function() {
  return this.color_;
};


/**
 * Set the color.
 *
 * @param {ol.Color|ol.ColorLike} color Color.
 * @api
 */
ol.style.Fill.prototype.setColor = function(color) {
  this.color_ = color;
  this.checksum_ = undefined;
};


/**
 * @return {string} The checksum.
 */
ol.style.Fill.prototype.getChecksum = function() {
  if (this.checksum_ === undefined) {
    if (
        this.color_ instanceof CanvasPattern ||
        this.color_ instanceof CanvasGradient
    ) {
      this.checksum_ = ol.getUid(this.color_).toString();
    } else {
      this.checksum_ = 'f' + (this.color_ ?
          ol.color.asString(this.color_) : '-');
    }
  }

  return this.checksum_;
};

// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Namespace with crypto related helper functions.
 */

goog.provide('goog.crypt');

goog.require('goog.array');
goog.require('goog.asserts');


/**
 * Turns a string into an array of bytes; a "byte" being a JS number in the
 * range 0-255.
 * @param {string} str String value to arrify.
 * @return {!Array<number>} Array of numbers corresponding to the
 *     UCS character codes of each character in str.
 */
goog.crypt.stringToByteArray = function(str) {
  var output = [], p = 0;
  for (var i = 0; i < str.length; i++) {
    var c = str.charCodeAt(i);
    while (c > 0xff) {
      output[p++] = c & 0xff;
      c >>= 8;
    }
    output[p++] = c;
  }
  return output;
};


/**
 * Turns an array of numbers into the string given by the concatenation of the
 * characters to which the numbers correspond.
 * @param {!Uint8Array|!Array<number>} bytes Array of numbers representing
 *     characters.
 * @return {string} Stringification of the array.
 */
goog.crypt.byteArrayToString = function(bytes) {
  var CHUNK_SIZE = 8192;

  // Special-case the simple case for speed's sake.
  if (bytes.length <= CHUNK_SIZE) {
    return String.fromCharCode.apply(null, bytes);
  }

  // The remaining logic splits conversion by chunks since
  // Function#apply() has a maximum parameter count.
  // See discussion: http://goo.gl/LrWmZ9

  var str = '';
  for (var i = 0; i < bytes.length; i += CHUNK_SIZE) {
    var chunk = goog.array.slice(bytes, i, i + CHUNK_SIZE);
    str += String.fromCharCode.apply(null, chunk);
  }
  return str;
};


/**
 * Turns an array of numbers into the hex string given by the concatenation of
 * the hex values to which the numbers correspond.
 * @param {Uint8Array|Array<number>} array Array of numbers representing
 *     characters.
 * @return {string} Hex string.
 */
goog.crypt.byteArrayToHex = function(array) {
  return goog.array
      .map(
          array,
          function(numByte) {
            var hexByte = numByte.toString(16);
            return hexByte.length > 1 ? hexByte : '0' + hexByte;
          })
      .join('');
};


/**
 * Converts a hex string into an integer array.
 * @param {string} hexString Hex string of 16-bit integers (two characters
 *     per integer).
 * @return {!Array<number>} Array of {0,255} integers for the given string.
 */
goog.crypt.hexToByteArray = function(hexString) {
  goog.asserts.assert(
      hexString.length % 2 == 0, 'Key string length must be multiple of 2');
  var arr = [];
  for (var i = 0; i < hexString.length; i += 2) {
    arr.push(parseInt(hexString.substring(i, i + 2), 16));
  }
  return arr;
};


/**
 * Converts a JS string to a UTF-8 "byte" array.
 * @param {string} str 16-bit unicode string.
 * @return {!Array<number>} UTF-8 byte array.
 */
goog.crypt.stringToUtf8ByteArray = function(str) {
  // TODO(user): Use native implementations if/when available
  var out = [], p = 0;
  for (var i = 0; i < str.length; i++) {
    var c = str.charCodeAt(i);
    if (c < 128) {
      out[p++] = c;
    } else if (c < 2048) {
      out[p++] = (c >> 6) | 192;
      out[p++] = (c & 63) | 128;
    } else if (
        ((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&
        ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {
      // Surrogate Pair
      c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);
      out[p++] = (c >> 18) | 240;
      out[p++] = ((c >> 12) & 63) | 128;
      out[p++] = ((c >> 6) & 63) | 128;
      out[p++] = (c & 63) | 128;
    } else {
      out[p++] = (c >> 12) | 224;
      out[p++] = ((c >> 6) & 63) | 128;
      out[p++] = (c & 63) | 128;
    }
  }
  return out;
};


/**
 * Converts a UTF-8 byte array to JavaScript's 16-bit Unicode.
 * @param {Uint8Array|Array<number>} bytes UTF-8 byte array.
 * @return {string} 16-bit Unicode string.
 */
goog.crypt.utf8ByteArrayToString = function(bytes) {
  // TODO(user): Use native implementations if/when available
  var out = [], pos = 0, c = 0;
  while (pos < bytes.length) {
    var c1 = bytes[pos++];
    if (c1 < 128) {
      out[c++] = String.fromCharCode(c1);
    } else if (c1 > 191 && c1 < 224) {
      var c2 = bytes[pos++];
      out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63);
    } else if (c1 > 239 && c1 < 365) {
      // Surrogate Pair
      var c2 = bytes[pos++];
      var c3 = bytes[pos++];
      var c4 = bytes[pos++];
      var u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) -
          0x10000;
      out[c++] = String.fromCharCode(0xD800 + (u >> 10));
      out[c++] = String.fromCharCode(0xDC00 + (u & 1023));
    } else {
      var c2 = bytes[pos++];
      var c3 = bytes[pos++];
      out[c++] =
          String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
    }
  }
  return out.join('');
};


/**
 * XOR two byte arrays.
 * @param {!Uint8Array|!Int8Array|!Array<number>} bytes1 Byte array 1.
 * @param {!Uint8Array|!Int8Array|!Array<number>} bytes2 Byte array 2.
 * @return {!Array<number>} Resulting XOR of the two byte arrays.
 */
goog.crypt.xorByteArray = function(bytes1, bytes2) {
  goog.asserts.assert(
      bytes1.length == bytes2.length, 'XOR array lengths must match');

  var result = [];
  for (var i = 0; i < bytes1.length; i++) {
    result.push(bytes1[i] ^ bytes2[i]);
  }
  return result;
};

// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview Abstract cryptographic hash interface.
 *
 * See goog.crypt.Sha1 and goog.crypt.Md5 for sample implementations.
 *
 */

goog.provide('goog.crypt.Hash');



/**
 * Create a cryptographic hash instance.
 *
 * @constructor
 * @struct
 */
goog.crypt.Hash = function() {
  /**
   * The block size for the hasher.
   * @type {number}
   */
  this.blockSize = -1;
};


/**
 * Resets the internal accumulator.
 */
goog.crypt.Hash.prototype.reset = goog.abstractMethod;


/**
 * Adds a byte array (array with values in [0-255] range) or a string (might
 * only contain 8-bit, i.e., Latin1 characters) to the internal accumulator.
 *
 * Many hash functions operate on blocks of data and implement optimizations
 * when a full chunk of data is readily available. Hence it is often preferable
 * to provide large chunks of data (a kilobyte or more) than to repeatedly
 * call the update method with few tens of bytes. If this is not possible, or
 * not feasible, it might be good to provide data in multiplies of hash block
 * size (often 64 bytes). Please see the implementation and performance tests
 * of your favourite hash.
 *
 * @param {Array<number>|Uint8Array|string} bytes Data used for the update.
 * @param {number=} opt_length Number of bytes to use.
 */
goog.crypt.Hash.prototype.update = goog.abstractMethod;


/**
 * @return {!Array<number>} The finalized hash computed
 *     from the internal accumulator.
 */
goog.crypt.Hash.prototype.digest = goog.abstractMethod;

// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.

/**
 * @fileoverview MD5 cryptographic hash.
 * Implementation of http://tools.ietf.org/html/rfc1321 with common
 * optimizations and tweaks (see http://en.wikipedia.org/wiki/MD5).
 *
 * Usage:
 *   var md5 = new goog.crypt.Md5();
 *   md5.update(bytes);
 *   var hash = md5.digest();
 *
 * Performance:
 *   Chrome 23              ~680 Mbit/s
 *   Chrome 13 (in a VM)    ~250 Mbit/s
 *   Firefox 6.0 (in a VM)  ~100 Mbit/s
 *   IE9 (in a VM)           ~27 Mbit/s
 *   Firefox 3.6             ~15 Mbit/s
 *   IE8 (in a VM)           ~13 Mbit/s
 *
 */

goog.provide('goog.crypt.Md5');

goog.require('goog.crypt.Hash');



/**
 * MD5 cryptographic hash constructor.
 * @constructor
 * @extends {goog.crypt.Hash}
 * @final
 * @struct
 */
goog.crypt.Md5 = function() {
  goog.crypt.Md5.base(this, 'constructor');

  this.blockSize = 512 / 8;

  /**
   * Holds the current values of accumulated A-D variables (MD buffer).
   * @type {!Array<number>}
   * @private
   */
  this.chain_ = new Array(4);

  /**
   * A buffer holding the data until the whole block can be processed.
   * @type {!Array<number>}
   * @private
   */
  this.block_ = new Array(this.blockSize);

  /**
   * The length of yet-unprocessed data as collected in the block.
   * @type {number}
   * @private
   */
  this.blockLength_ = 0;

  /**
   * The total length of the message so far.
   * @type {number}
   * @private
   */
  this.totalLength_ = 0;

  this.reset();
};
goog.inherits(goog.crypt.Md5, goog.crypt.Hash);


/**
 * Integer rotation constants used by the abbreviated implementation.
 * They are hardcoded in the unrolled implementation, so it is left
 * here commented out.
 * @type {Array<number>}
 * @private
 *
goog.crypt.Md5.S_ = [
  7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
  5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
  4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
  6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
];
 */

/**
 * Sine function constants used by the abbreviated implementation.
 * They are hardcoded in the unrolled implementation, so it is left
 * here commented out.
 * @type {Array<number>}
 * @private
 *
goog.crypt.Md5.T_ = [
  0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
  0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
  0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
  0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
  0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
  0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
  0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
  0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
  0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
  0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
  0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
  0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
  0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
  0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
  0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
  0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
];
 */


/** @override */
goog.crypt.Md5.prototype.reset = function() {
  this.chain_[0] = 0x67452301;
  this.chain_[1] = 0xefcdab89;
  this.chain_[2] = 0x98badcfe;
  this.chain_[3] = 0x10325476;

  this.blockLength_ = 0;
  this.totalLength_ = 0;
};


/**
 * Internal compress helper function. It takes a block of data (64 bytes)
 * and updates the accumulator.
 * @param {Array<number>|Uint8Array|string} buf The block to compress.
 * @param {number=} opt_offset Offset of the block in the buffer.
 * @private
 */
goog.crypt.Md5.prototype.compress_ = function(buf, opt_offset) {
  if (!opt_offset) {
    opt_offset = 0;
  }

  // We allocate the array every time, but it's cheap in practice.
  var X = new Array(16);

  // Get 16 little endian words. It is not worth unrolling this for Chrome 11.
  if (goog.isString(buf)) {
    for (var i = 0; i < 16; ++i) {
      X[i] = (buf.charCodeAt(opt_offset++)) |
          (buf.charCodeAt(opt_offset++) << 8) |
          (buf.charCodeAt(opt_offset++) << 16) |
          (buf.charCodeAt(opt_offset++) << 24);
    }
  } else {
    for (var i = 0; i < 16; ++i) {
      X[i] = (buf[opt_offset++]) | (buf[opt_offset++] << 8) |
          (buf[opt_offset++] << 16) | (buf[opt_offset++] << 24);
    }
  }

  var A = this.chain_[0];
  var B = this.chain_[1];
  var C = this.chain_[2];
  var D = this.chain_[3];
  var sum = 0;

  /*
   * This is an abbreviated implementation, it is left here commented out for
   * reference purposes. See below for an unrolled version in use.
   *
  var f, n, tmp;
  for (var i = 0; i < 64; ++i) {

    if (i < 16) {
      f = (D ^ (B & (C ^ D)));
      n = i;
    } else if (i < 32) {
      f = (C ^ (D & (B ^ C)));
      n = (5 * i + 1) % 16;
    } else if (i < 48) {
      f = (B ^ C ^ D);
      n = (3 * i + 5) % 16;
    } else {
      f = (C ^ (B | (~D)));
      n = (7 * i) % 16;
    }

    tmp = D;
    D = C;
    C = B;
    sum = (A + f + goog.crypt.Md5.T_[i] + X[n]) & 0xffffffff;
    B += ((sum << goog.crypt.Md5.S_[i]) & 0xffffffff) |
         (sum >>> (32 - goog.crypt.Md5.S_[i]));
    A = tmp;
  }
   */

  /*
   * This is an unrolled MD5 implementation, which gives ~30% speedup compared
   * to the abbreviated implementation above, as measured on Chrome 11. It is
   * important to keep 32-bit croppings to minimum and inline the integer
   * rotation.
   */
  sum = (A + (D ^ (B & (C ^ D))) + X[0] + 0xd76aa478) & 0xffffffff;
  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
  sum = (D + (C ^ (A & (B ^ C))) + X[1] + 0xe8c7b756) & 0xffffffff;
  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
  sum = (C + (B ^ (D & (A ^ B))) + X[2] + 0x242070db) & 0xffffffff;
  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
  sum = (B + (A ^ (C & (D ^ A))) + X[3] + 0xc1bdceee) & 0xffffffff;
  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
  sum = (A + (D ^ (B & (C ^ D))) + X[4] + 0xf57c0faf) & 0xffffffff;
  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
  sum = (D + (C ^ (A & (B ^ C))) + X[5] + 0x4787c62a) & 0xffffffff;
  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
  sum = (C + (B ^ (D & (A ^ B))) + X[6] + 0xa8304613) & 0xffffffff;
  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
  sum = (B + (A ^ (C & (D ^ A))) + X[7] + 0xfd469501) & 0xffffffff;
  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
  sum = (A + (D ^ (B & (C ^ D))) + X[8] + 0x698098d8) & 0xffffffff;
  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
  sum = (D + (C ^ (A & (B ^ C))) + X[9] + 0x8b44f7af) & 0xffffffff;
  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
  sum = (C + (B ^ (D & (A ^ B))) + X[10] + 0xffff5bb1) & 0xffffffff;
  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
  sum = (B + (A ^ (C & (D ^ A))) + X[11] + 0x895cd7be) & 0xffffffff;
  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
  sum = (A + (D ^ (B & (C ^ D))) + X[12] + 0x6b901122) & 0xffffffff;
  A = B + (((sum << 7) & 0xffffffff) | (sum >>> 25));
  sum = (D + (C ^ (A & (B ^ C))) + X[13] + 0xfd987193) & 0xffffffff;
  D = A + (((sum << 12) & 0xffffffff) | (sum >>> 20));
  sum = (C + (B ^ (D & (A ^ B))) + X[14] + 0xa679438e) & 0xffffffff;
  C = D + (((sum << 17) & 0xffffffff) | (sum >>> 15));
  sum = (B + (A ^ (C & (D ^ A))) + X[15] + 0x49b40821) & 0xffffffff;
  B = C + (((sum << 22) & 0xffffffff) | (sum >>> 10));
  sum = (A + (C ^ (D & (B ^ C))) + X[1] + 0xf61e2562) & 0xffffffff;
  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
  sum = (D + (B ^ (C & (A ^ B))) + X[6] + 0xc040b340) & 0xffffffff;
  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
  sum = (C + (A ^ (B & (D ^ A))) + X[11] + 0x265e5a51) & 0xffffffff;
  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
  sum = (B + (D ^ (A & (C ^ D))) + X[0] + 0xe9b6c7aa) & 0xffffffff;
  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
  sum = (A + (C ^ (D & (B ^ C))) + X[5] + 0xd62f105d) & 0xffffffff;
  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
  sum = (D + (B ^ (C & (A ^ B))) + X[10] + 0x02441453) & 0xffffffff;
  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
  sum = (C + (A ^ (B & (D ^ A))) + X[15] + 0xd8a1e681) & 0xffffffff;
  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
  sum = (B + (D ^ (A & (C ^ D))) + X[4] + 0xe7d3fbc8) & 0xffffffff;
  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
  sum = (A + (C ^ (D & (B ^ C))) + X[9] + 0x21e1cde6) & 0xffffffff;
  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
  sum = (D + (B ^ (C & (A ^ B))) + X[14] + 0xc33707d6) & 0xffffffff;
  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
  sum = (C + (A ^ (B & (D ^ A))) + X[3] + 0xf4d50d87) & 0xffffffff;
  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
  sum = (B + (D ^ (A & (C ^ D))) + X[8] + 0x455a14ed) & 0xffffffff;
  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
  sum = (A + (C ^ (D & (B ^ C))) + X[13] + 0xa9e3e905) & 0xffffffff;
  A = B + (((sum << 5) & 0xffffffff) | (sum >>> 27));
  sum = (D + (B ^ (C & (A ^ B))) + X[2] + 0xfcefa3f8) & 0xffffffff;
  D = A + (((sum << 9) & 0xffffffff) | (sum >>> 23));
  sum = (C + (A ^ (B & (D ^ A))) + X[7] + 0x676f02d9) & 0xffffffff;
  C = D + (((sum << 14) & 0xffffffff) | (sum >>> 18));
  sum = (B + (D ^ (A & (C ^ D))) + X[12] + 0x8d2a4c8a) & 0xffffffff;
  B = C + (((sum << 20) & 0xffffffff) | (sum >>> 12));
  sum = (A + (B ^ C ^ D) + X[5] + 0xfffa3942) & 0xffffffff;
  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
  sum = (D + (A ^ B ^ C) + X[8] + 0x8771f681) & 0xffffffff;
  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
  sum = (C + (D ^ A ^ B) + X[11] + 0x6d9d6122) & 0xffffffff;
  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
  sum = (B + (C ^ D ^ A) + X[14] + 0xfde5380c) & 0xffffffff;
  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
  sum = (A + (B ^ C ^ D) + X[1] + 0xa4beea44) & 0xffffffff;
  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
  sum = (D + (A ^ B ^ C) + X[4] + 0x4bdecfa9) & 0xffffffff;
  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
  sum = (C + (D ^ A ^ B) + X[7] + 0xf6bb4b60) & 0xffffffff;
  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
  sum = (B + (C ^ D ^ A) + X[10] + 0xbebfbc70) & 0xffffffff;
  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
  sum = (A + (B ^ C ^ D) + X[13] + 0x289b7ec6) & 0xffffffff;
  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
  sum = (D + (A ^ B ^ C) + X[0] + 0xeaa127fa) & 0xffffffff;
  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
  sum = (C + (D ^ A ^ B) + X[3] + 0xd4ef3085) & 0xffffffff;
  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
  sum = (B + (C ^ D ^ A) + X[6] + 0x04881d05) & 0xffffffff;
  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
  sum = (A + (B ^ C ^ D) + X[9] + 0xd9d4d039) & 0xffffffff;
  A = B + (((sum << 4) & 0xffffffff) | (sum >>> 28));
  sum = (D + (A ^ B ^ C) + X[12] + 0xe6db99e5) & 0xffffffff;
  D = A + (((sum << 11) & 0xffffffff) | (sum >>> 21));
  sum = (C + (D ^ A ^ B) + X[15] + 0x1fa27cf8) & 0xffffffff;
  C = D + (((sum << 16) & 0xffffffff) | (sum >>> 16));
  sum = (B + (C ^ D ^ A) + X[2] + 0xc4ac5665) & 0xffffffff;
  B = C + (((sum << 23) & 0xffffffff) | (sum >>> 9));
  sum = (A + (C ^ (B | (~D))) + X[0] + 0xf4292244) & 0xffffffff;
  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
  sum = (D + (B ^ (A | (~C))) + X[7] + 0x432aff97) & 0xffffffff;
  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
  sum = (C + (A ^ (D | (~B))) + X[14] + 0xab9423a7) & 0xffffffff;
  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
  sum = (B + (D ^ (C | (~A))) + X[5] + 0xfc93a039) & 0xffffffff;
  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
  sum = (A + (C ^ (B | (~D))) + X[12] + 0x655b59c3) & 0xffffffff;
  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
  sum = (D + (B ^ (A | (~C))) + X[3] + 0x8f0ccc92) & 0xffffffff;
  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
  sum = (C + (A ^ (D | (~B))) + X[10] + 0xffeff47d) & 0xffffffff;
  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
  sum = (B + (D ^ (C | (~A))) + X[1] + 0x85845dd1) & 0xffffffff;
  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
  sum = (A + (C ^ (B | (~D))) + X[8] + 0x6fa87e4f) & 0xffffffff;
  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
  sum = (D + (B ^ (A | (~C))) + X[15] + 0xfe2ce6e0) & 0xffffffff;
  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
  sum = (C + (A ^ (D | (~B))) + X[6] + 0xa3014314) & 0xffffffff;
  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
  sum = (B + (D ^ (C | (~A))) + X[13] + 0x4e0811a1) & 0xffffffff;
  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));
  sum = (A + (C ^ (B | (~D))) + X[4] + 0xf7537e82) & 0xffffffff;
  A = B + (((sum << 6) & 0xffffffff) | (sum >>> 26));
  sum = (D + (B ^ (A | (~C))) + X[11] + 0xbd3af235) & 0xffffffff;
  D = A + (((sum << 10) & 0xffffffff) | (sum >>> 22));
  sum = (C + (A ^ (D | (~B))) + X[2] + 0x2ad7d2bb) & 0xffffffff;
  C = D + (((sum << 15) & 0xffffffff) | (sum >>> 17));
  sum = (B + (D ^ (C | (~A))) + X[9] + 0xeb86d391) & 0xffffffff;
  B = C + (((sum << 21) & 0xffffffff) | (sum >>> 11));

  this.chain_[0] = (this.chain_[0] + A) & 0xffffffff;
  this.chain_[1] = (this.chain_[1] + B) & 0xffffffff;
  this.chain_[2] = (this.chain_[2] + C) & 0xffffffff;
  this.chain_[3] = (this.chain_[3] + D) & 0xffffffff;
};


/** @override */
goog.crypt.Md5.prototype.update = function(bytes, opt_length) {
  if (!goog.isDef(opt_length)) {
    opt_length = bytes.length;
  }
  var lengthMinusBlock = opt_length - this.blockSize;

  // Copy some object properties to local variables in order to save on access
  // time from inside the loop (~10% speedup was observed on Chrome 11).
  var block = this.block_;
  var blockLength = this.blockLength_;
  var i = 0;

  // The outer while loop should execute at most twice.
  while (i < opt_length) {
    // When we have no data in the block to top up, we can directly process the
    // input buffer (assuming it contains sufficient data). This gives ~30%
    // speedup on Chrome 14 and ~70% speedup on Firefox 6.0, but requires that
    // the data is provided in large chunks (or in multiples of 64 bytes).
    if (blockLength == 0) {
      while (i <= lengthMinusBlock) {
        this.compress_(bytes, i);
        i += this.blockSize;
      }
    }

    if (goog.isString(bytes)) {
      while (i < opt_length) {
        block[blockLength++] = bytes.charCodeAt(i++);
        if (blockLength == this.blockSize) {
          this.compress_(block);
          blockLength = 0;
          // Jump to the outer loop so we use the full-block optimization.
          break;
        }
      }
    } else {
      while (i < opt_length) {
        block[blockLength++] = bytes[i++];
        if (blockLength == this.blockSize) {
          this.compress_(block);
          blockLength = 0;
          // Jump to the outer loop so we use the full-block optimization.
          break;
        }
      }
    }
  }

  this.blockLength_ = blockLength;
  this.totalLength_ += opt_length;
};


/** @override */
goog.crypt.Md5.prototype.digest = function() {
  // This must accommodate at least 1 padding byte (0x80), 8 bytes of
  // total bitlength, and must end at a 64-byte boundary.
  var pad = new Array(
      (this.blockLength_ < 56 ? this.blockSize : this.blockSize * 2) -
      this.blockLength_);

  // Add padding: 0x80 0x00*
  pad[0] = 0x80;
  for (var i = 1; i < pad.length - 8; ++i) {
    pad[i] = 0;
  }
  // Add the total number of bits, little endian 64-bit integer.
  var totalBits = this.totalLength_ * 8;
  for (var i = pad.length - 8; i < pad.length; ++i) {
    pad[i] = totalBits & 0xff;
    totalBits /= 0x100;  // Don't use bit-shifting here!
  }
  this.update(pad);

  var digest = new Array(16);
  var n = 0;
  for (var i = 0; i < 4; ++i) {
    for (var j = 0; j < 32; j += 8) {
      digest[n++] = (this.chain_[i] >>> j) & 0xff;
    }
  }
  return digest;
};

goog.provide('ol.style.Stroke');

goog.require('goog.crypt');
goog.require('goog.crypt.Md5');
goog.require('ol.color');


/**
 * @classdesc
 * Set stroke style for vector features.
 * Note that the defaults given are the Canvas defaults, which will be used if
 * option is not defined. The `get` functions return whatever was entered in
 * the options; they will not return the default.
 *
 * @constructor
 * @param {olx.style.StrokeOptions=} opt_options Options.
 * @api
 */
ol.style.Stroke = function(opt_options) {

  var options = opt_options || {};

  /**
   * @private
   * @type {ol.Color|string}
   */
  this.color_ = options.color !== undefined ? options.color : null;

  /**
   * @private
   * @type {string|undefined}
   */
  this.lineCap_ = options.lineCap;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.lineDash_ = options.lineDash !== undefined ? options.lineDash : null;

  /**
   * @private
   * @type {string|undefined}
   */
  this.lineJoin_ = options.lineJoin;

  /**
   * @private
   * @type {number|undefined}
   */
  this.miterLimit_ = options.miterLimit;

  /**
   * @private
   * @type {number|undefined}
   */
  this.width_ = options.width;

  /**
   * @private
   * @type {string|undefined}
   */
  this.checksum_ = undefined;
};


/**
 * Get the stroke color.
 * @return {ol.Color|string} Color.
 * @api
 */
ol.style.Stroke.prototype.getColor = function() {
  return this.color_;
};


/**
 * Get the line cap type for the stroke.
 * @return {string|undefined} Line cap.
 * @api
 */
ol.style.Stroke.prototype.getLineCap = function() {
  return this.lineCap_;
};


/**
 * Get the line dash style for the stroke.
 * @return {Array.<number>} Line dash.
 * @api
 */
ol.style.Stroke.prototype.getLineDash = function() {
  return this.lineDash_;
};


/**
 * Get the line join type for the stroke.
 * @return {string|undefined} Line join.
 * @api
 */
ol.style.Stroke.prototype.getLineJoin = function() {
  return this.lineJoin_;
};


/**
 * Get the miter limit for the stroke.
 * @return {number|undefined} Miter limit.
 * @api
 */
ol.style.Stroke.prototype.getMiterLimit = function() {
  return this.miterLimit_;
};


/**
 * Get the stroke width.
 * @return {number|undefined} Width.
 * @api
 */
ol.style.Stroke.prototype.getWidth = function() {
  return this.width_;
};


/**
 * Set the color.
 *
 * @param {ol.Color|string} color Color.
 * @api
 */
ol.style.Stroke.prototype.setColor = function(color) {
  this.color_ = color;
  this.checksum_ = undefined;
};


/**
 * Set the line cap.
 *
 * @param {string|undefined} lineCap Line cap.
 * @api
 */
ol.style.Stroke.prototype.setLineCap = function(lineCap) {
  this.lineCap_ = lineCap;
  this.checksum_ = undefined;
};


/**
 * Set the line dash.
 *
 * Please note that Internet Explorer 10 and lower [do not support][mdn] the
 * `setLineDash` method on the `CanvasRenderingContext2D` and therefore this
 * property will have no visual effect in these browsers.
 *
 * [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility
 *
 * @param {Array.<number>} lineDash Line dash.
 * @api
 */
ol.style.Stroke.prototype.setLineDash = function(lineDash) {
  this.lineDash_ = lineDash;
  this.checksum_ = undefined;
};


/**
 * Set the line join.
 *
 * @param {string|undefined} lineJoin Line join.
 * @api
 */
ol.style.Stroke.prototype.setLineJoin = function(lineJoin) {
  this.lineJoin_ = lineJoin;
  this.checksum_ = undefined;
};


/**
 * Set the miter limit.
 *
 * @param {number|undefined} miterLimit Miter limit.
 * @api
 */
ol.style.Stroke.prototype.setMiterLimit = function(miterLimit) {
  this.miterLimit_ = miterLimit;
  this.checksum_ = undefined;
};


/**
 * Set the width.
 *
 * @param {number|undefined} width Width.
 * @api
 */
ol.style.Stroke.prototype.setWidth = function(width) {
  this.width_ = width;
  this.checksum_ = undefined;
};


/**
 * @return {string} The checksum.
 */
ol.style.Stroke.prototype.getChecksum = function() {
  if (this.checksum_ === undefined) {
    var raw = 's' +
        (this.color_ ?
            ol.color.asString(this.color_) : '-') + ',' +
        (this.lineCap_ !== undefined ?
            this.lineCap_.toString() : '-') + ',' +
        (this.lineDash_ ?
            this.lineDash_.toString() : '-') + ',' +
        (this.lineJoin_ !== undefined ?
            this.lineJoin_ : '-') + ',' +
        (this.miterLimit_ !== undefined ?
            this.miterLimit_.toString() : '-') + ',' +
        (this.width_ !== undefined ?
            this.width_.toString() : '-');

    var md5 = new goog.crypt.Md5();
    md5.update(raw);
    this.checksum_ = goog.crypt.byteArrayToString(md5.digest());
  }

  return this.checksum_;
};

goog.provide('ol.style.Style');

goog.require('ol.asserts');
goog.require('ol.geom.GeometryType');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');


/**
 * @classdesc
 * Container for vector feature rendering styles. Any changes made to the style
 * or its children through `set*()` methods will not take effect until the
 * feature or layer that uses the style is re-rendered.
 *
 * @constructor
 * @struct
 * @param {olx.style.StyleOptions=} opt_options Style options.
 * @api
 */
ol.style.Style = function(opt_options) {

  var options = opt_options || {};

  /**
   * @private
   * @type {string|ol.geom.Geometry|ol.StyleGeometryFunction}
   */
  this.geometry_ = null;

  /**
   * @private
   * @type {!ol.StyleGeometryFunction}
   */
  this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;

  if (options.geometry !== undefined) {
    this.setGeometry(options.geometry);
  }

  /**
   * @private
   * @type {ol.style.Fill}
   */
  this.fill_ = options.fill !== undefined ? options.fill : null;

  /**
   * @private
   * @type {ol.style.Image}
   */
  this.image_ = options.image !== undefined ? options.image : null;

  /**
   * @private
   * @type {ol.style.Stroke}
   */
  this.stroke_ = options.stroke !== undefined ? options.stroke : null;

  /**
   * @private
   * @type {ol.style.Text}
   */
  this.text_ = options.text !== undefined ? options.text : null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.zIndex_ = options.zIndex;

};


/**
 * Get the geometry to be rendered.
 * @return {string|ol.geom.Geometry|ol.StyleGeometryFunction}
 * Feature property or geometry or function that returns the geometry that will
 * be rendered with this style.
 * @api
 */
ol.style.Style.prototype.getGeometry = function() {
  return this.geometry_;
};


/**
 * Get the function used to generate a geometry for rendering.
 * @return {!ol.StyleGeometryFunction} Function that is called with a feature
 * and returns the geometry to render instead of the feature's geometry.
 * @api
 */
ol.style.Style.prototype.getGeometryFunction = function() {
  return this.geometryFunction_;
};


/**
 * Get the fill style.
 * @return {ol.style.Fill} Fill style.
 * @api
 */
ol.style.Style.prototype.getFill = function() {
  return this.fill_;
};


/**
 * Get the image style.
 * @return {ol.style.Image} Image style.
 * @api
 */
ol.style.Style.prototype.getImage = function() {
  return this.image_;
};


/**
 * Get the stroke style.
 * @return {ol.style.Stroke} Stroke style.
 * @api
 */
ol.style.Style.prototype.getStroke = function() {
  return this.stroke_;
};


/**
 * Get the text style.
 * @return {ol.style.Text} Text style.
 * @api
 */
ol.style.Style.prototype.getText = function() {
  return this.text_;
};


/**
 * Get the z-index for the style.
 * @return {number|undefined} ZIndex.
 * @api
 */
ol.style.Style.prototype.getZIndex = function() {
  return this.zIndex_;
};


/**
 * Set a geometry that is rendered instead of the feature's geometry.
 *
 * @param {string|ol.geom.Geometry|ol.StyleGeometryFunction} geometry
 *     Feature property or geometry or function returning a geometry to render
 *     for this style.
 * @api
 */
ol.style.Style.prototype.setGeometry = function(geometry) {
  if (typeof geometry === 'function') {
    this.geometryFunction_ = geometry;
  } else if (typeof geometry === 'string') {
    this.geometryFunction_ = function(feature) {
      return /** @type {ol.geom.Geometry} */ (feature.get(geometry));
    };
  } else if (!geometry) {
    this.geometryFunction_ = ol.style.Style.defaultGeometryFunction;
  } else if (geometry !== undefined) {
    this.geometryFunction_ = function() {
      return /** @type {ol.geom.Geometry} */ (geometry);
    };
  }
  this.geometry_ = geometry;
};


/**
 * Set the z-index.
 *
 * @param {number|undefined} zIndex ZIndex.
 * @api
 */
ol.style.Style.prototype.setZIndex = function(zIndex) {
  this.zIndex_ = zIndex;
};


/**
 * Convert the provided object into a style function.  Functions passed through
 * unchanged.  Arrays of ol.style.Style or single style objects wrapped in a
 * new style function.
 * @param {ol.StyleFunction|Array.<ol.style.Style>|ol.style.Style} obj
 *     A style function, a single style, or an array of styles.
 * @return {ol.StyleFunction} A style function.
 */
ol.style.Style.createFunction = function(obj) {
  var styleFunction;

  if (typeof obj === 'function') {
    styleFunction = obj;
  } else {
    /**
     * @type {Array.<ol.style.Style>}
     */
    var styles;
    if (Array.isArray(obj)) {
      styles = obj;
    } else {
      ol.asserts.assert(obj instanceof ol.style.Style,
          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
      styles = [obj];
    }
    styleFunction = function() {
      return styles;
    };
  }
  return styleFunction;
};


/**
 * @type {Array.<ol.style.Style>}
 * @private
 */
ol.style.Style.default_ = null;


/**
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {number} resolution Resolution.
 * @return {Array.<ol.style.Style>} Style.
 */
ol.style.Style.defaultFunction = function(feature, resolution) {
  // We don't use an immediately-invoked function
  // and a closure so we don't get an error at script evaluation time in
  // browsers that do not support Canvas. (ol.style.Circle does
  // canvas.getContext('2d') at construction time, which will cause an.error
  // in such browsers.)
  if (!ol.style.Style.default_) {
    var fill = new ol.style.Fill({
      color: 'rgba(255,255,255,0.4)'
    });
    var stroke = new ol.style.Stroke({
      color: '#3399CC',
      width: 1.25
    });
    ol.style.Style.default_ = [
      new ol.style.Style({
        image: new ol.style.Circle({
          fill: fill,
          stroke: stroke,
          radius: 5
        }),
        fill: fill,
        stroke: stroke
      })
    ];
  }
  return ol.style.Style.default_;
};


/**
 * Default styles for editing features.
 * @return {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} Styles
 */
ol.style.Style.createDefaultEditing = function() {
  /** @type {Object.<ol.geom.GeometryType, Array.<ol.style.Style>>} */
  var styles = {};
  var white = [255, 255, 255, 1];
  var blue = [0, 153, 255, 1];
  var width = 3;
  styles[ol.geom.GeometryType.POLYGON] = [
    new ol.style.Style({
      fill: new ol.style.Fill({
        color: [255, 255, 255, 0.5]
      })
    })
  ];
  styles[ol.geom.GeometryType.MULTI_POLYGON] =
      styles[ol.geom.GeometryType.POLYGON];

  styles[ol.geom.GeometryType.LINE_STRING] = [
    new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: white,
        width: width + 2
      })
    }),
    new ol.style.Style({
      stroke: new ol.style.Stroke({
        color: blue,
        width: width
      })
    })
  ];
  styles[ol.geom.GeometryType.MULTI_LINE_STRING] =
      styles[ol.geom.GeometryType.LINE_STRING];

  styles[ol.geom.GeometryType.CIRCLE] =
      styles[ol.geom.GeometryType.POLYGON].concat(
          styles[ol.geom.GeometryType.LINE_STRING]
      );


  styles[ol.geom.GeometryType.POINT] = [
    new ol.style.Style({
      image: new ol.style.Circle({
        radius: width * 2,
        fill: new ol.style.Fill({
          color: blue
        }),
        stroke: new ol.style.Stroke({
          color: white,
          width: width / 2
        })
      }),
      zIndex: Infinity
    })
  ];
  styles[ol.geom.GeometryType.MULTI_POINT] =
      styles[ol.geom.GeometryType.POINT];

  styles[ol.geom.GeometryType.GEOMETRY_COLLECTION] =
      styles[ol.geom.GeometryType.POLYGON].concat(
          styles[ol.geom.GeometryType.LINE_STRING],
          styles[ol.geom.GeometryType.POINT]
      );

  return styles;
};


/**
 * Function that is called with a feature and returns its default geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry
 *     for.
 * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render.
 */
ol.style.Style.defaultGeometryFunction = function(feature) {
  return feature.getGeometry();
};

goog.provide('ol.layer.Vector');

goog.require('ol');
goog.require('ol.layer.Layer');
goog.require('ol.obj');
goog.require('ol.style.Style');


/**
 * @enum {string}
 */
ol.layer.VectorProperty = {
  RENDER_ORDER: 'renderOrder'
};


/**
 * @classdesc
 * Vector data that is rendered client-side.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Layer}
 * @fires ol.render.Event
 * @param {olx.layer.VectorOptions=} opt_options Options.
 * @api stable
 */
ol.layer.Vector = function(opt_options) {

  var options = opt_options ?
      opt_options : /** @type {olx.layer.VectorOptions} */ ({});

  goog.DEBUG && console.assert(
      options.renderOrder === undefined || !options.renderOrder ||
      typeof options.renderOrder === 'function',
      'renderOrder must be a comparator function');

  var baseOptions = ol.obj.assign({}, options);

  delete baseOptions.style;
  delete baseOptions.renderBuffer;
  delete baseOptions.updateWhileAnimating;
  delete baseOptions.updateWhileInteracting;
  ol.layer.Layer.call(this, /** @type {olx.layer.LayerOptions} */ (baseOptions));

  /**
   * @type {number}
   * @private
   */
  this.renderBuffer_ = options.renderBuffer !== undefined ?
      options.renderBuffer : 100;

  /**
   * User provided style.
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
   * @private
   */
  this.style_ = null;

  /**
   * Style function for use within the library.
   * @type {ol.StyleFunction|undefined}
   * @private
   */
  this.styleFunction_ = undefined;

  this.setStyle(options.style);

  /**
   * @type {boolean}
   * @private
   */
  this.updateWhileAnimating_ = options.updateWhileAnimating !== undefined ?
      options.updateWhileAnimating : false;

  /**
   * @type {boolean}
   * @private
   */
  this.updateWhileInteracting_ = options.updateWhileInteracting !== undefined ?
      options.updateWhileInteracting : false;

};
ol.inherits(ol.layer.Vector, ol.layer.Layer);


/**
 * @return {number|undefined} Render buffer.
 */
ol.layer.Vector.prototype.getRenderBuffer = function() {
  return this.renderBuffer_;
};


/**
 * @return {function(ol.Feature, ol.Feature): number|null|undefined} Render
 *     order.
 */
ol.layer.Vector.prototype.getRenderOrder = function() {
  return /** @type {function(ol.Feature, ol.Feature):number|null|undefined} */ (
      this.get(ol.layer.VectorProperty.RENDER_ORDER));
};


/**
 * Return the associated {@link ol.source.Vector vectorsource} of the layer.
 * @function
 * @return {ol.source.Vector} Source.
 * @api stable
 */
ol.layer.Vector.prototype.getSource;


/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
 *     Layer style.
 * @api stable
 */
ol.layer.Vector.prototype.getStyle = function() {
  return this.style_;
};


/**
 * Get the style function.
 * @return {ol.StyleFunction|undefined} Layer style function.
 * @api stable
 */
ol.layer.Vector.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};


/**
 * @return {boolean} Whether the rendered layer should be updated while
 *     animating.
 */
ol.layer.Vector.prototype.getUpdateWhileAnimating = function() {
  return this.updateWhileAnimating_;
};


/**
 * @return {boolean} Whether the rendered layer should be updated while
 *     interacting.
 */
ol.layer.Vector.prototype.getUpdateWhileInteracting = function() {
  return this.updateWhileInteracting_;
};


/**
 * @param {function(ol.Feature, ol.Feature):number|null|undefined} renderOrder
 *     Render order.
 */
ol.layer.Vector.prototype.setRenderOrder = function(renderOrder) {
  goog.DEBUG && console.assert(
      renderOrder === undefined || !renderOrder ||
      typeof renderOrder === 'function',
      'renderOrder must be a comparator function');
  this.set(ol.layer.VectorProperty.RENDER_ORDER, renderOrder);
};


/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles. If it is `undefined` the default style is used. If
 * it is `null` the layer has no style (a `null` style), so only features
 * that have their own styles will be rendered in the layer. See
 * {@link ol.style} for information on the default style.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|null|undefined}
 *     style Layer style.
 * @api stable
 */
ol.layer.Vector.prototype.setStyle = function(style) {
  this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction;
  this.styleFunction_ = style === null ?
      undefined : ol.style.Style.createFunction(this.style_);
  this.changed();
};

goog.provide('ol.layer.VectorTile');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.layer.Vector');
goog.require('ol.obj');


/**
 * @enum {string}
 */
ol.layer.VectorTileProperty = {
  PRELOAD: 'preload',
  USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError'
};


/**
 * @enum {string}
 * Render mode for vector tiles:
 *  * `'image'`: Vector tiles are rendered as images. Great performance, but
 *    point symbols and texts are always rotated with the view and pixels are
 *    scaled during zoom animations.
 *  * `'hybrid'`: Polygon and line elements are rendered as images, so pixels
 *    are scaled during zoom animations. Point symbols and texts are accurately
 *    rendered as vectors and can stay upright on rotated views.
 *  * `'vector'`: Vector tiles are rendered as vectors. Most accurate rendering
 *    even during animations, but slower performance than the other options.
 * @api
 */
ol.layer.VectorTileRenderType = {
  IMAGE: 'image',
  HYBRID: 'hybrid',
  VECTOR: 'vector'
};


/**
 * @classdesc
 * Layer for vector tile data that is rendered client-side.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Vector}
 * @param {olx.layer.VectorTileOptions=} opt_options Options.
 * @api
 */
ol.layer.VectorTile = function(opt_options) {
  var options = opt_options ? opt_options : {};

  var baseOptions = ol.obj.assign({}, options);

  delete baseOptions.preload;
  delete baseOptions.useInterimTilesOnError;
  ol.layer.Vector.call(this,  /** @type {olx.layer.VectorOptions} */ (baseOptions));

  this.setPreload(options.preload ? options.preload : 0);
  this.setUseInterimTilesOnError(options.useInterimTilesOnError ?
      options.useInterimTilesOnError : true);

  ol.asserts.assert(options.renderMode == undefined ||
      options.renderMode == ol.layer.VectorTileRenderType.IMAGE ||
      options.renderMode == ol.layer.VectorTileRenderType.HYBRID ||
      options.renderMode == ol.layer.VectorTileRenderType.VECTOR,
      28); // `renderMode` must be `'image'`, `'hybrid'` or `'vector'`

  /**
   * @private
   * @type {ol.layer.VectorTileRenderType|string}
   */
  this.renderMode_ = options.renderMode || ol.layer.VectorTileRenderType.HYBRID;

};
ol.inherits(ol.layer.VectorTile, ol.layer.Vector);


/**
 * Return the level as number to which we will preload tiles up to.
 * @return {number} The level to preload tiles up to.
 * @observable
 * @api
 */
ol.layer.VectorTile.prototype.getPreload = function() {
  return /** @type {number} */ (this.get(ol.layer.VectorTileProperty.PRELOAD));
};


/**
 * @return {ol.layer.VectorTileRenderType|string} The render mode.
 */
ol.layer.VectorTile.prototype.getRenderMode = function() {
  return this.renderMode_;
};


/**
 * Whether we use interim tiles on error.
 * @return {boolean} Use interim tiles on error.
 * @observable
 * @api
 */
ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() {
  return /** @type {boolean} */ (
      this.get(ol.layer.VectorTileProperty.USE_INTERIM_TILES_ON_ERROR));
};


/**
 * Set the level as number to which we will preload tiles up to.
 * @param {number} preload The level to preload tiles up to.
 * @observable
 * @api
 */
ol.layer.VectorTile.prototype.setPreload = function(preload) {
  this.set(ol.layer.TileProperty.PRELOAD, preload);
};


/**
 * Set whether we use interim tiles on error.
 * @param {boolean} useInterimTilesOnError Use interim tiles on error.
 * @observable
 * @api
 */
ol.layer.VectorTile.prototype.setUseInterimTilesOnError = function(useInterimTilesOnError) {
  this.set(
      ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError);
};

goog.provide('ol.render.VectorContext');


/**
 * Context for drawing geometries.  A vector context is available on render
 * events and does not need to be constructed directly.
 * @constructor
 * @struct
 * @api
 */
ol.render.VectorContext = function() {
};


/**
 * Render a geometry.
 *
 * @abstract
 * @param {ol.geom.Geometry} geometry The geometry to render.
 */
ol.render.VectorContext.prototype.drawGeometry = function(geometry) {};


/**
 * Set the rendering style.
 *
 * @abstract
 * @param {ol.style.Style} style The rendering style.
 */
ol.render.VectorContext.prototype.setStyle = function(style) {};


/**
 * @abstract
 * @param {ol.geom.Circle} circleGeometry Circle geometry.
 * @param {ol.Feature} feature Feature,
 */
ol.render.VectorContext.prototype.drawCircle = function(circleGeometry, feature) {};


/**
 * @abstract
 * @param {ol.Feature} feature Feature.
 * @param {ol.style.Style} style Style.
 */
ol.render.VectorContext.prototype.drawFeature = function(feature, style) {};


/**
 * @abstract
 * @param {ol.geom.GeometryCollection} geometryCollectionGeometry Geometry
 *     collection.
 * @param {ol.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawGeometryCollection = function(geometryCollectionGeometry, feature) {};


/**
 * @abstract
 * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line
 *     string geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawLineString = function(lineStringGeometry, feature) {};


/**
 * @abstract
 * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry
 *     MultiLineString geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {};


/**
 * @abstract
 * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint
 *     geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawMultiPoint = function(multiPointGeometry, feature) {};


/**
 * @abstract
 * @param {ol.geom.MultiPolygon} multiPolygonGeometry MultiPolygon geometry.
 * @param {ol.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {};


/**
 * @abstract
 * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawPoint = function(pointGeometry, feature) {};


/**
 * @abstract
 * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon
 *     geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawPolygon = function(polygonGeometry, feature) {};


/**
 * @abstract
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.VectorContext.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {};


/**
 * @abstract
 * @param {ol.style.Fill} fillStyle Fill style.
 * @param {ol.style.Stroke} strokeStyle Stroke style.
 */
ol.render.VectorContext.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {};


/**
 * @abstract
 * @param {ol.style.Image} imageStyle Image style.
 */
ol.render.VectorContext.prototype.setImageStyle = function(imageStyle) {};


/**
 * @abstract
 * @param {ol.style.Text} textStyle Text style.
 */
ol.render.VectorContext.prototype.setTextStyle = function(textStyle) {};

// FIXME test, especially polygons with holes and multipolygons
// FIXME need to handle large thick features (where pixel size matters)
// FIXME add offset and end to ol.geom.flat.transform.transform2D?

goog.provide('ol.render.canvas.Immediate');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.color');
goog.require('ol.colorlike');
goog.require('ol.extent');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.transform');
goog.require('ol.has');
goog.require('ol.render.VectorContext');
goog.require('ol.render.canvas');
goog.require('ol.transform');


/**
 * @classdesc
 * A concrete subclass of {@link ol.render.VectorContext} that implements
 * direct rendering of features and geometries to an HTML5 Canvas context.
 * Instances of this class are created internally by the library and
 * provided to application code as vectorContext member of the
 * {@link ol.render.Event} object associated with postcompose, precompose and
 * render events emitted by layers and maps.
 *
 * @constructor
 * @extends {ol.render.VectorContext}
 * @param {CanvasRenderingContext2D} context Context.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Extent} extent Extent.
 * @param {ol.Transform} transform Transform.
 * @param {number} viewRotation View rotation.
 * @struct
 */
ol.render.canvas.Immediate = function(context, pixelRatio, extent, transform, viewRotation) {
  ol.render.VectorContext.call(this);

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = context;

  /**
   * @private
   * @type {number}
   */
  this.pixelRatio_ = pixelRatio;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.extent_ = extent;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = transform;

  /**
   * @private
   * @type {number}
   */
  this.viewRotation_ = viewRotation;

  /**
   * @private
   * @type {?ol.CanvasFillState}
   */
  this.contextFillState_ = null;

  /**
   * @private
   * @type {?ol.CanvasStrokeState}
   */
  this.contextStrokeState_ = null;

  /**
   * @private
   * @type {?ol.CanvasTextState}
   */
  this.contextTextState_ = null;

  /**
   * @private
   * @type {?ol.CanvasFillState}
   */
  this.fillState_ = null;

  /**
   * @private
   * @type {?ol.CanvasStrokeState}
   */
  this.strokeState_ = null;

  /**
   * @private
   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
   */
  this.image_ = null;

  /**
   * @private
   * @type {number}
   */
  this.imageAnchorX_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.imageAnchorY_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.imageHeight_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.imageOpacity_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.imageOriginX_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.imageOriginY_ = 0;

  /**
   * @private
   * @type {boolean}
   */
  this.imageRotateWithView_ = false;

  /**
   * @private
   * @type {number}
   */
  this.imageRotation_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.imageScale_ = 0;

  /**
   * @private
   * @type {boolean}
   */
  this.imageSnapToPixel_ = false;

  /**
   * @private
   * @type {number}
   */
  this.imageWidth_ = 0;

  /**
   * @private
   * @type {string}
   */
  this.text_ = '';

  /**
   * @private
   * @type {number}
   */
  this.textOffsetX_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.textOffsetY_ = 0;

  /**
   * @private
   * @type {boolean}
   */
  this.textRotateWithView_ = false;

  /**
   * @private
   * @type {number}
   */
  this.textRotation_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.textScale_ = 0;

  /**
   * @private
   * @type {?ol.CanvasFillState}
   */
  this.textFillState_ = null;

  /**
   * @private
   * @type {?ol.CanvasStrokeState}
   */
  this.textStrokeState_ = null;

  /**
   * @private
   * @type {?ol.CanvasTextState}
   */
  this.textState_ = null;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.pixelCoordinates_ = [];

  /**
   * @private
   * @type {ol.Transform}
   */
  this.tmpLocalTransform_ = ol.transform.create();

};
ol.inherits(ol.render.canvas.Immediate, ol.render.VectorContext);


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @private
 */
ol.render.canvas.Immediate.prototype.drawImages_ = function(flatCoordinates, offset, end, stride) {
  if (!this.image_) {
    return;
  }
  goog.DEBUG && console.assert(offset === 0, 'offset should be 0');
  goog.DEBUG && console.assert(end == flatCoordinates.length,
      'end should be equal to the length of flatCoordinates');
  var pixelCoordinates = ol.geom.flat.transform.transform2D(
      flatCoordinates, offset, end, 2, this.transform_,
      this.pixelCoordinates_);
  var context = this.context_;
  var localTransform = this.tmpLocalTransform_;
  var alpha = context.globalAlpha;
  if (this.imageOpacity_ != 1) {
    context.globalAlpha = alpha * this.imageOpacity_;
  }
  var rotation = this.imageRotation_;
  if (this.imageRotateWithView_) {
    rotation += this.viewRotation_;
  }
  var i, ii;
  for (i = 0, ii = pixelCoordinates.length; i < ii; i += 2) {
    var x = pixelCoordinates[i] - this.imageAnchorX_;
    var y = pixelCoordinates[i + 1] - this.imageAnchorY_;
    if (this.imageSnapToPixel_) {
      x = Math.round(x);
      y = Math.round(y);
    }
    if (rotation !== 0 || this.imageScale_ != 1) {
      var centerX = x + this.imageAnchorX_;
      var centerY = y + this.imageAnchorY_;
      ol.transform.compose(localTransform,
          centerX, centerY,
          this.imageScale_, this.imageScale_,
          rotation,
          -centerX, -centerY);
      context.setTransform.apply(context, localTransform);
    }
    context.drawImage(this.image_, this.imageOriginX_, this.imageOriginY_,
        this.imageWidth_, this.imageHeight_, x, y,
        this.imageWidth_, this.imageHeight_);
  }
  if (rotation !== 0 || this.imageScale_ != 1) {
    context.setTransform(1, 0, 0, 1, 0, 0);
  }
  if (this.imageOpacity_ != 1) {
    context.globalAlpha = alpha;
  }
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @private
 */
ol.render.canvas.Immediate.prototype.drawText_ = function(flatCoordinates, offset, end, stride) {
  if (!this.textState_ || this.text_ === '') {
    return;
  }
  if (this.textFillState_) {
    this.setContextFillState_(this.textFillState_);
  }
  if (this.textStrokeState_) {
    this.setContextStrokeState_(this.textStrokeState_);
  }
  this.setContextTextState_(this.textState_);
  goog.DEBUG && console.assert(offset === 0, 'offset should be 0');
  goog.DEBUG && console.assert(end == flatCoordinates.length,
      'end should be equal to the length of flatCoordinates');
  var pixelCoordinates = ol.geom.flat.transform.transform2D(
      flatCoordinates, offset, end, stride, this.transform_,
      this.pixelCoordinates_);
  var context = this.context_;
  var rotation = this.textRotation_;
  if (this.textRotateWithView_) {
    rotation += this.viewRotation_;
  }
  for (; offset < end; offset += stride) {
    var x = pixelCoordinates[offset] + this.textOffsetX_;
    var y = pixelCoordinates[offset + 1] + this.textOffsetY_;
    if (rotation !== 0 || this.textScale_ != 1) {
      var localTransform = ol.transform.compose(this.tmpLocalTransform_,
          x, y,
          this.textScale_, this.textScale_,
          rotation,
          -x, -y);
      context.setTransform.apply(context, localTransform);
    }
    if (this.textStrokeState_) {
      context.strokeText(this.text_, x, y);
    }
    if (this.textFillState_) {
      context.fillText(this.text_, x, y);
    }
  }
  if (rotation !== 0 || this.textScale_ != 1) {
    context.setTransform(1, 0, 0, 1, 0, 0);
  }
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {boolean} close Close.
 * @private
 * @return {number} end End.
 */
ol.render.canvas.Immediate.prototype.moveToLineTo_ = function(flatCoordinates, offset, end, stride, close) {
  var context = this.context_;
  var pixelCoordinates = ol.geom.flat.transform.transform2D(
      flatCoordinates, offset, end, stride, this.transform_,
      this.pixelCoordinates_);
  context.moveTo(pixelCoordinates[0], pixelCoordinates[1]);
  var length = pixelCoordinates.length;
  if (close) {
    length -= 2;
  }
  for (var i = 2; i < length; i += 2) {
    context.lineTo(pixelCoordinates[i], pixelCoordinates[i + 1]);
  }
  if (close) {
    context.closePath();
  }
  return end;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @private
 * @return {number} End.
 */
ol.render.canvas.Immediate.prototype.drawRings_ = function(flatCoordinates, offset, ends, stride) {
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    offset = this.moveToLineTo_(
        flatCoordinates, offset, ends[i], stride, true);
  }
  return offset;
};


/**
 * Render a circle geometry into the canvas.  Rendering is immediate and uses
 * the current fill and stroke styles.
 *
 * @param {ol.geom.Circle} geometry Circle geometry.
 * @api
 */
ol.render.canvas.Immediate.prototype.drawCircle = function(geometry) {
  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
    return;
  }
  if (this.fillState_ || this.strokeState_) {
    if (this.fillState_) {
      this.setContextFillState_(this.fillState_);
    }
    if (this.strokeState_) {
      this.setContextStrokeState_(this.strokeState_);
    }
    var pixelCoordinates = ol.geom.SimpleGeometry.transform2D(
        geometry, this.transform_, this.pixelCoordinates_);
    var dx = pixelCoordinates[2] - pixelCoordinates[0];
    var dy = pixelCoordinates[3] - pixelCoordinates[1];
    var radius = Math.sqrt(dx * dx + dy * dy);
    var context = this.context_;
    context.beginPath();
    context.arc(
        pixelCoordinates[0], pixelCoordinates[1], radius, 0, 2 * Math.PI);
    if (this.fillState_) {
      context.fill();
    }
    if (this.strokeState_) {
      context.stroke();
    }
  }
  if (this.text_ !== '') {
    this.drawText_(geometry.getCenter(), 0, 2, 2);
  }
};


/**
 * Set the rendering style.  Note that since this is an immediate rendering API,
 * any `zIndex` on the provided style will be ignored.
 *
 * @param {ol.style.Style} style The rendering style.
 * @api
 */
ol.render.canvas.Immediate.prototype.setStyle = function(style) {
  this.setFillStrokeStyle(style.getFill(), style.getStroke());
  this.setImageStyle(style.getImage());
  this.setTextStyle(style.getText());
};


/**
 * Render a geometry into the canvas.  Call
 * {@link ol.render.canvas.Immediate#setStyle} first to set the rendering style.
 *
 * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
 * @api
 */
ol.render.canvas.Immediate.prototype.drawGeometry = function(geometry) {
  var type = geometry.getType();
  switch (type) {
    case ol.geom.GeometryType.POINT:
      this.drawPoint(/** @type {ol.geom.Point} */ (geometry));
      break;
    case ol.geom.GeometryType.LINE_STRING:
      this.drawLineString(/** @type {ol.geom.LineString} */ (geometry));
      break;
    case ol.geom.GeometryType.POLYGON:
      this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry));
      break;
    case ol.geom.GeometryType.MULTI_POINT:
      this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry));
      break;
    case ol.geom.GeometryType.MULTI_LINE_STRING:
      this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry));
      break;
    case ol.geom.GeometryType.MULTI_POLYGON:
      this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry));
      break;
    case ol.geom.GeometryType.GEOMETRY_COLLECTION:
      this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry));
      break;
    case ol.geom.GeometryType.CIRCLE:
      this.drawCircle(/** @type {ol.geom.Circle} */ (geometry));
      break;
    default:
      goog.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type);
  }
};


/**
 * Render a feature into the canvas.  Note that any `zIndex` on the provided
 * style will be ignored - features are rendered immediately in the order that
 * this method is called.  If you need `zIndex` support, you should be using an
 * {@link ol.layer.Vector} instead.
 *
 * @param {ol.Feature} feature Feature.
 * @param {ol.style.Style} style Style.
 * @api
 */
ol.render.canvas.Immediate.prototype.drawFeature = function(feature, style) {
  var geometry = style.getGeometryFunction()(feature);
  if (!geometry ||
      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
    return;
  }
  this.setStyle(style);
  this.drawGeometry(geometry);
};


/**
 * Render a GeometryCollection to the canvas.  Rendering is immediate and
 * uses the current styles appropriate for each geometry in the collection.
 *
 * @param {ol.geom.GeometryCollection} geometry Geometry collection.
 */
ol.render.canvas.Immediate.prototype.drawGeometryCollection = function(geometry) {
  var geometries = geometry.getGeometriesArray();
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    this.drawGeometry(geometries[i]);
  }
};


/**
 * Render a Point geometry into the canvas.  Rendering is immediate and uses
 * the current style.
 *
 * @param {ol.geom.Point|ol.render.Feature} geometry Point geometry.
 */
ol.render.canvas.Immediate.prototype.drawPoint = function(geometry) {
  var flatCoordinates = geometry.getFlatCoordinates();
  var stride = geometry.getStride();
  if (this.image_) {
    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
  }
  if (this.text_ !== '') {
    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
  }
};


/**
 * Render a MultiPoint geometry  into the canvas.  Rendering is immediate and
 * uses the current style.
 *
 * @param {ol.geom.MultiPoint|ol.render.Feature} geometry MultiPoint geometry.
 */
ol.render.canvas.Immediate.prototype.drawMultiPoint = function(geometry) {
  var flatCoordinates = geometry.getFlatCoordinates();
  var stride = geometry.getStride();
  if (this.image_) {
    this.drawImages_(flatCoordinates, 0, flatCoordinates.length, stride);
  }
  if (this.text_ !== '') {
    this.drawText_(flatCoordinates, 0, flatCoordinates.length, stride);
  }
};


/**
 * Render a LineString into the canvas.  Rendering is immediate and uses
 * the current style.
 *
 * @param {ol.geom.LineString|ol.render.Feature} geometry LineString geometry.
 */
ol.render.canvas.Immediate.prototype.drawLineString = function(geometry) {
  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
    return;
  }
  if (this.strokeState_) {
    this.setContextStrokeState_(this.strokeState_);
    var context = this.context_;
    var flatCoordinates = geometry.getFlatCoordinates();
    context.beginPath();
    this.moveToLineTo_(flatCoordinates, 0, flatCoordinates.length,
        geometry.getStride(), false);
    context.stroke();
  }
  if (this.text_ !== '') {
    var flatMidpoint = geometry.getFlatMidpoint();
    this.drawText_(flatMidpoint, 0, 2, 2);
  }
};


/**
 * Render a MultiLineString geometry into the canvas.  Rendering is immediate
 * and uses the current style.
 *
 * @param {ol.geom.MultiLineString|ol.render.Feature} geometry MultiLineString
 *     geometry.
 */
ol.render.canvas.Immediate.prototype.drawMultiLineString = function(geometry) {
  var geometryExtent = geometry.getExtent();
  if (!ol.extent.intersects(this.extent_, geometryExtent)) {
    return;
  }
  if (this.strokeState_) {
    this.setContextStrokeState_(this.strokeState_);
    var context = this.context_;
    var flatCoordinates = geometry.getFlatCoordinates();
    var offset = 0;
    var ends = geometry.getEnds();
    var stride = geometry.getStride();
    context.beginPath();
    var i, ii;
    for (i = 0, ii = ends.length; i < ii; ++i) {
      offset = this.moveToLineTo_(
          flatCoordinates, offset, ends[i], stride, false);
    }
    context.stroke();
  }
  if (this.text_ !== '') {
    var flatMidpoints = geometry.getFlatMidpoints();
    this.drawText_(flatMidpoints, 0, flatMidpoints.length, 2);
  }
};


/**
 * Render a Polygon geometry into the canvas.  Rendering is immediate and uses
 * the current style.
 *
 * @param {ol.geom.Polygon|ol.render.Feature} geometry Polygon geometry.
 */
ol.render.canvas.Immediate.prototype.drawPolygon = function(geometry) {
  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
    return;
  }
  if (this.strokeState_ || this.fillState_) {
    if (this.fillState_) {
      this.setContextFillState_(this.fillState_);
    }
    if (this.strokeState_) {
      this.setContextStrokeState_(this.strokeState_);
    }
    var context = this.context_;
    context.beginPath();
    this.drawRings_(geometry.getOrientedFlatCoordinates(),
        0, geometry.getEnds(), geometry.getStride());
    if (this.fillState_) {
      context.fill();
    }
    if (this.strokeState_) {
      context.stroke();
    }
  }
  if (this.text_ !== '') {
    var flatInteriorPoint = geometry.getFlatInteriorPoint();
    this.drawText_(flatInteriorPoint, 0, 2, 2);
  }
};


/**
 * Render MultiPolygon geometry into the canvas.  Rendering is immediate and
 * uses the current style.
 * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
 */
ol.render.canvas.Immediate.prototype.drawMultiPolygon = function(geometry) {
  if (!ol.extent.intersects(this.extent_, geometry.getExtent())) {
    return;
  }
  if (this.strokeState_ || this.fillState_) {
    if (this.fillState_) {
      this.setContextFillState_(this.fillState_);
    }
    if (this.strokeState_) {
      this.setContextStrokeState_(this.strokeState_);
    }
    var context = this.context_;
    var flatCoordinates = geometry.getOrientedFlatCoordinates();
    var offset = 0;
    var endss = geometry.getEndss();
    var stride = geometry.getStride();
    var i, ii;
    for (i = 0, ii = endss.length; i < ii; ++i) {
      var ends = endss[i];
      context.beginPath();
      offset = this.drawRings_(flatCoordinates, offset, ends, stride);
      if (this.fillState_) {
        context.fill();
      }
      if (this.strokeState_) {
        context.stroke();
      }
    }
  }
  if (this.text_ !== '') {
    var flatInteriorPoints = geometry.getFlatInteriorPoints();
    this.drawText_(flatInteriorPoints, 0, flatInteriorPoints.length, 2);
  }
};


/**
 * @param {ol.CanvasFillState} fillState Fill state.
 * @private
 */
ol.render.canvas.Immediate.prototype.setContextFillState_ = function(fillState) {
  var context = this.context_;
  var contextFillState = this.contextFillState_;
  if (!contextFillState) {
    context.fillStyle = fillState.fillStyle;
    this.contextFillState_ = {
      fillStyle: fillState.fillStyle
    };
  } else {
    if (contextFillState.fillStyle != fillState.fillStyle) {
      contextFillState.fillStyle = context.fillStyle = fillState.fillStyle;
    }
  }
};


/**
 * @param {ol.CanvasStrokeState} strokeState Stroke state.
 * @private
 */
ol.render.canvas.Immediate.prototype.setContextStrokeState_ = function(strokeState) {
  var context = this.context_;
  var contextStrokeState = this.contextStrokeState_;
  if (!contextStrokeState) {
    context.lineCap = strokeState.lineCap;
    if (ol.has.CANVAS_LINE_DASH) {
      context.setLineDash(strokeState.lineDash);
    }
    context.lineJoin = strokeState.lineJoin;
    context.lineWidth = strokeState.lineWidth;
    context.miterLimit = strokeState.miterLimit;
    context.strokeStyle = strokeState.strokeStyle;
    this.contextStrokeState_ = {
      lineCap: strokeState.lineCap,
      lineDash: strokeState.lineDash,
      lineJoin: strokeState.lineJoin,
      lineWidth: strokeState.lineWidth,
      miterLimit: strokeState.miterLimit,
      strokeStyle: strokeState.strokeStyle
    };
  } else {
    if (contextStrokeState.lineCap != strokeState.lineCap) {
      contextStrokeState.lineCap = context.lineCap = strokeState.lineCap;
    }
    if (ol.has.CANVAS_LINE_DASH) {
      if (!ol.array.equals(
          contextStrokeState.lineDash, strokeState.lineDash)) {
        context.setLineDash(contextStrokeState.lineDash = strokeState.lineDash);
      }
    }
    if (contextStrokeState.lineJoin != strokeState.lineJoin) {
      contextStrokeState.lineJoin = context.lineJoin = strokeState.lineJoin;
    }
    if (contextStrokeState.lineWidth != strokeState.lineWidth) {
      contextStrokeState.lineWidth = context.lineWidth = strokeState.lineWidth;
    }
    if (contextStrokeState.miterLimit != strokeState.miterLimit) {
      contextStrokeState.miterLimit = context.miterLimit =
          strokeState.miterLimit;
    }
    if (contextStrokeState.strokeStyle != strokeState.strokeStyle) {
      contextStrokeState.strokeStyle = context.strokeStyle =
          strokeState.strokeStyle;
    }
  }
};


/**
 * @param {ol.CanvasTextState} textState Text state.
 * @private
 */
ol.render.canvas.Immediate.prototype.setContextTextState_ = function(textState) {
  var context = this.context_;
  var contextTextState = this.contextTextState_;
  if (!contextTextState) {
    context.font = textState.font;
    context.textAlign = textState.textAlign;
    context.textBaseline = textState.textBaseline;
    this.contextTextState_ = {
      font: textState.font,
      textAlign: textState.textAlign,
      textBaseline: textState.textBaseline
    };
  } else {
    if (contextTextState.font != textState.font) {
      contextTextState.font = context.font = textState.font;
    }
    if (contextTextState.textAlign != textState.textAlign) {
      contextTextState.textAlign = context.textAlign = textState.textAlign;
    }
    if (contextTextState.textBaseline != textState.textBaseline) {
      contextTextState.textBaseline = context.textBaseline =
          textState.textBaseline;
    }
  }
};


/**
 * Set the fill and stroke style for subsequent draw operations.  To clear
 * either fill or stroke styles, pass null for the appropriate parameter.
 *
 * @param {ol.style.Fill} fillStyle Fill style.
 * @param {ol.style.Stroke} strokeStyle Stroke style.
 */
ol.render.canvas.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
  if (!fillStyle) {
    this.fillState_ = null;
  } else {
    var fillStyleColor = fillStyle.getColor();
    this.fillState_ = {
      fillStyle: ol.colorlike.asColorLike(fillStyleColor ?
          fillStyleColor : ol.render.canvas.defaultFillStyle)
    };
  }
  if (!strokeStyle) {
    this.strokeState_ = null;
  } else {
    var strokeStyleColor = strokeStyle.getColor();
    var strokeStyleLineCap = strokeStyle.getLineCap();
    var strokeStyleLineDash = strokeStyle.getLineDash();
    var strokeStyleLineJoin = strokeStyle.getLineJoin();
    var strokeStyleWidth = strokeStyle.getWidth();
    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
    this.strokeState_ = {
      lineCap: strokeStyleLineCap !== undefined ?
          strokeStyleLineCap : ol.render.canvas.defaultLineCap,
      lineDash: strokeStyleLineDash ?
          strokeStyleLineDash : ol.render.canvas.defaultLineDash,
      lineJoin: strokeStyleLineJoin !== undefined ?
          strokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
      lineWidth: this.pixelRatio_ * (strokeStyleWidth !== undefined ?
          strokeStyleWidth : ol.render.canvas.defaultLineWidth),
      miterLimit: strokeStyleMiterLimit !== undefined ?
          strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
      strokeStyle: ol.color.asString(strokeStyleColor ?
          strokeStyleColor : ol.render.canvas.defaultStrokeStyle)
    };
  }
};


/**
 * Set the image style for subsequent draw operations.  Pass null to remove
 * the image style.
 *
 * @param {ol.style.Image} imageStyle Image style.
 */
ol.render.canvas.Immediate.prototype.setImageStyle = function(imageStyle) {
  if (!imageStyle) {
    this.image_ = null;
  } else {
    var imageAnchor = imageStyle.getAnchor();
    // FIXME pixel ratio
    var imageImage = imageStyle.getImage(1);
    var imageOrigin = imageStyle.getOrigin();
    var imageSize = imageStyle.getSize();
    goog.DEBUG && console.assert(imageImage, 'imageImage must be truthy');
    this.imageAnchorX_ = imageAnchor[0];
    this.imageAnchorY_ = imageAnchor[1];
    this.imageHeight_ = imageSize[1];
    this.image_ = imageImage;
    this.imageOpacity_ = imageStyle.getOpacity();
    this.imageOriginX_ = imageOrigin[0];
    this.imageOriginY_ = imageOrigin[1];
    this.imageRotateWithView_ = imageStyle.getRotateWithView();
    this.imageRotation_ = imageStyle.getRotation();
    this.imageScale_ = imageStyle.getScale();
    this.imageSnapToPixel_ = imageStyle.getSnapToPixel();
    this.imageWidth_ = imageSize[0];
  }
};


/**
 * Set the text style for subsequent draw operations.  Pass null to
 * remove the text style.
 *
 * @param {ol.style.Text} textStyle Text style.
 */
ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) {
  if (!textStyle) {
    this.text_ = '';
  } else {
    var textFillStyle = textStyle.getFill();
    if (!textFillStyle) {
      this.textFillState_ = null;
    } else {
      var textFillStyleColor = textFillStyle.getColor();
      this.textFillState_ = {
        fillStyle: ol.colorlike.asColorLike(textFillStyleColor ?
            textFillStyleColor : ol.render.canvas.defaultFillStyle)
      };
    }
    var textStrokeStyle = textStyle.getStroke();
    if (!textStrokeStyle) {
      this.textStrokeState_ = null;
    } else {
      var textStrokeStyleColor = textStrokeStyle.getColor();
      var textStrokeStyleLineCap = textStrokeStyle.getLineCap();
      var textStrokeStyleLineDash = textStrokeStyle.getLineDash();
      var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin();
      var textStrokeStyleWidth = textStrokeStyle.getWidth();
      var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit();
      this.textStrokeState_ = {
        lineCap: textStrokeStyleLineCap !== undefined ?
            textStrokeStyleLineCap : ol.render.canvas.defaultLineCap,
        lineDash: textStrokeStyleLineDash ?
            textStrokeStyleLineDash : ol.render.canvas.defaultLineDash,
        lineJoin: textStrokeStyleLineJoin !== undefined ?
            textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin,
        lineWidth: textStrokeStyleWidth !== undefined ?
            textStrokeStyleWidth : ol.render.canvas.defaultLineWidth,
        miterLimit: textStrokeStyleMiterLimit !== undefined ?
            textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit,
        strokeStyle: ol.color.asString(textStrokeStyleColor ?
            textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle)
      };
    }
    var textFont = textStyle.getFont();
    var textOffsetX = textStyle.getOffsetX();
    var textOffsetY = textStyle.getOffsetY();
    var textRotateWithView = textStyle.getRotateWithView();
    var textRotation = textStyle.getRotation();
    var textScale = textStyle.getScale();
    var textText = textStyle.getText();
    var textTextAlign = textStyle.getTextAlign();
    var textTextBaseline = textStyle.getTextBaseline();
    this.textState_ = {
      font: textFont !== undefined ?
          textFont : ol.render.canvas.defaultFont,
      textAlign: textTextAlign !== undefined ?
          textTextAlign : ol.render.canvas.defaultTextAlign,
      textBaseline: textTextBaseline !== undefined ?
          textTextBaseline : ol.render.canvas.defaultTextBaseline
    };
    this.text_ = textText !== undefined ? textText : '';
    this.textOffsetX_ =
        textOffsetX !== undefined ? (this.pixelRatio_ * textOffsetX) : 0;
    this.textOffsetY_ =
        textOffsetY !== undefined ? (this.pixelRatio_ * textOffsetY) : 0;
    this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false;
    this.textRotation_ = textRotation !== undefined ? textRotation : 0;
    this.textScale_ = this.pixelRatio_ * (textScale !== undefined ?
        textScale : 1);
  }
};

goog.provide('ol.ImageBase');
goog.provide('ol.ImageState');

goog.require('ol');
goog.require('ol.events.EventTarget');
goog.require('ol.events.EventType');


/**
 * @enum {number}
 */
ol.ImageState = {
  IDLE: 0,
  LOADING: 1,
  LOADED: 2,
  ERROR: 3
};


/**
 * @constructor
 * @extends {ol.events.EventTarget}
 * @param {ol.Extent} extent Extent.
 * @param {number|undefined} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.ImageState} state State.
 * @param {Array.<ol.Attribution>} attributions Attributions.
 */
ol.ImageBase = function(extent, resolution, pixelRatio, state, attributions) {

  ol.events.EventTarget.call(this);

  /**
   * @private
   * @type {Array.<ol.Attribution>}
   */
  this.attributions_ = attributions;

  /**
   * @protected
   * @type {ol.Extent}
   */
  this.extent = extent;

  /**
   * @private
   * @type {number}
   */
  this.pixelRatio_ = pixelRatio;

  /**
   * @protected
   * @type {number|undefined}
   */
  this.resolution = resolution;

  /**
   * @protected
   * @type {ol.ImageState}
   */
  this.state = state;

};
ol.inherits(ol.ImageBase, ol.events.EventTarget);


/**
 * @protected
 */
ol.ImageBase.prototype.changed = function() {
  this.dispatchEvent(ol.events.EventType.CHANGE);
};


/**
 * @return {Array.<ol.Attribution>} Attributions.
 */
ol.ImageBase.prototype.getAttributions = function() {
  return this.attributions_;
};


/**
 * @return {ol.Extent} Extent.
 */
ol.ImageBase.prototype.getExtent = function() {
  return this.extent;
};


/**
 * @abstract
 * @param {Object=} opt_context Object.
 * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
 */
ol.ImageBase.prototype.getImage = function(opt_context) {};


/**
 * @return {number} PixelRatio.
 */
ol.ImageBase.prototype.getPixelRatio = function() {
  return this.pixelRatio_;
};


/**
 * @return {number} Resolution.
 */
ol.ImageBase.prototype.getResolution = function() {
  goog.DEBUG && console.assert(this.resolution !== undefined, 'resolution not yet set');
  return /** @type {number} */ (this.resolution);
};


/**
 * @return {ol.ImageState} State.
 */
ol.ImageBase.prototype.getState = function() {
  return this.state;
};


/**
 * Load not yet loaded URI.
 * @abstract
 */
ol.ImageBase.prototype.load = function() {};

goog.provide('ol.renderer.Layer');

goog.require('ol');
goog.require('ol.ImageState');
goog.require('ol.Observable');
goog.require('ol.Tile');
goog.require('ol.asserts');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.functions');
goog.require('ol.source.State');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.Observable}
 * @param {ol.layer.Layer} layer Layer.
 * @struct
 */
ol.renderer.Layer = function(layer) {

  ol.Observable.call(this);

  /**
   * @private
   * @type {ol.layer.Layer}
   */
  this.layer_ = layer;


};
ol.inherits(ol.renderer.Layer, ol.Observable);


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {olx.FrameState} frameState Frame state.
 * @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T}
 *     callback Feature callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T
 */
ol.renderer.Layer.prototype.forEachFeatureAtCoordinate = ol.nullFunction;


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState Frame state.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T
 */
ol.renderer.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
  var coordinate = ol.transform.apply(
      frameState.pixelToCoordinateTransform, pixel.slice());

  var hasFeature = this.forEachFeatureAtCoordinate(
      coordinate, frameState, ol.functions.TRUE, this);

  if (hasFeature) {
    return callback.call(thisArg, this.layer_, null);
  } else {
    return undefined;
  }
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {olx.FrameState} frameState Frame state.
 * @return {boolean} Is there a feature at the given coordinate?
 */
ol.renderer.Layer.prototype.hasFeatureAtCoordinate = ol.functions.FALSE;


/**
 * Create a function that adds loaded tiles to the tile lookup.
 * @param {ol.source.Tile} source Tile source.
 * @param {ol.proj.Projection} projection Projection of the tiles.
 * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded
 *     tiles by zoom level.
 * @return {function(number, ol.TileRange):boolean} A function that can be
 *     called with a zoom level and a tile range to add loaded tiles to the
 *     lookup.
 * @protected
 */
ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
  return (
      /**
       * @param {number} zoom Zoom level.
       * @param {ol.TileRange} tileRange Tile range.
       * @return {boolean} The tile range is fully loaded.
       */
      function(zoom, tileRange) {
        function callback(tile) {
          if (!tiles[zoom]) {
            tiles[zoom] = {};
          }
          tiles[zoom][tile.tileCoord.toString()] = tile;
        }
        return source.forEachLoadedTile(projection, zoom, tileRange, callback);
      });
};


/**
 * @return {ol.layer.Layer} Layer.
 */
ol.renderer.Layer.prototype.getLayer = function() {
  return this.layer_;
};


/**
 * Handle changes in image state.
 * @param {ol.events.Event} event Image change event.
 * @private
 */
ol.renderer.Layer.prototype.handleImageChange_ = function(event) {
  var image = /** @type {ol.Image} */ (event.target);
  if (image.getState() === ol.ImageState.LOADED) {
    this.renderIfReadyAndVisible();
  }
};


/**
 * Load the image if not already loaded, and register the image change
 * listener if needed.
 * @param {ol.ImageBase} image Image.
 * @return {boolean} `true` if the image is already loaded, `false`
 *     otherwise.
 * @protected
 */
ol.renderer.Layer.prototype.loadImage = function(image) {
  var imageState = image.getState();
  if (imageState != ol.ImageState.LOADED &&
      imageState != ol.ImageState.ERROR) {
    // the image is either "idle" or "loading", register the change
    // listener (a noop if the listener was already registered)
    goog.DEBUG && console.assert(imageState == ol.ImageState.IDLE ||
        imageState == ol.ImageState.LOADING,
        'imageState is "idle" or "loading"');
    ol.events.listen(image, ol.events.EventType.CHANGE,
        this.handleImageChange_, this);
  }
  if (imageState == ol.ImageState.IDLE) {
    image.load();
    imageState = image.getState();
    goog.DEBUG && console.assert(imageState == ol.ImageState.LOADING ||
        imageState == ol.ImageState.LOADED,
        'imageState is "loading" or "loaded"');
  }
  return imageState == ol.ImageState.LOADED;
};


/**
 * @protected
 */
ol.renderer.Layer.prototype.renderIfReadyAndVisible = function() {
  var layer = this.getLayer();
  if (layer.getVisible() && layer.getSourceState() == ol.source.State.READY) {
    this.changed();
  }
};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Tile} tileSource Tile source.
 * @protected
 */
ol.renderer.Layer.prototype.scheduleExpireCache = function(frameState, tileSource) {
  if (tileSource.canExpireCache()) {
    /**
     * @param {ol.source.Tile} tileSource Tile source.
     * @param {ol.Map} map Map.
     * @param {olx.FrameState} frameState Frame state.
     */
    var postRenderFunction = function(tileSource, map, frameState) {
      var tileSourceKey = ol.getUid(tileSource).toString();
      tileSource.expireCache(frameState.viewState.projection,
                             frameState.usedTiles[tileSourceKey]);
    }.bind(null, tileSource);

    frameState.postRenderFunctions.push(
      /** @type {ol.PostRenderFunction} */ (postRenderFunction)
    );
  }
};


/**
 * @param {Object.<string, ol.Attribution>} attributionsSet Attributions
 *     set (target).
 * @param {Array.<ol.Attribution>} attributions Attributions (source).
 * @protected
 */
ol.renderer.Layer.prototype.updateAttributions = function(attributionsSet, attributions) {
  if (attributions) {
    var attribution, i, ii;
    for (i = 0, ii = attributions.length; i < ii; ++i) {
      attribution = attributions[i];
      attributionsSet[ol.getUid(attribution).toString()] = attribution;
    }
  }
};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Source} source Source.
 * @protected
 */
ol.renderer.Layer.prototype.updateLogos = function(frameState, source) {
  var logo = source.getLogo();
  if (logo !== undefined) {
    if (typeof logo === 'string') {
      frameState.logos[logo] = '';
    } else if (logo) {
      ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
      ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
      frameState.logos[logo.src] = logo.href;
    }
  }
};


/**
 * @param {Object.<string, Object.<string, ol.TileRange>>} usedTiles Used tiles.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {number} z Z.
 * @param {ol.TileRange} tileRange Tile range.
 * @protected
 */
ol.renderer.Layer.prototype.updateUsedTiles = function(usedTiles, tileSource, z, tileRange) {
  // FIXME should we use tilesToDrawByZ instead?
  var tileSourceKey = ol.getUid(tileSource).toString();
  var zKey = z.toString();
  if (tileSourceKey in usedTiles) {
    if (zKey in usedTiles[tileSourceKey]) {
      usedTiles[tileSourceKey][zKey].extend(tileRange);
    } else {
      usedTiles[tileSourceKey][zKey] = tileRange;
    }
  } else {
    usedTiles[tileSourceKey] = {};
    usedTiles[tileSourceKey][zKey] = tileRange;
  }
};


/**
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {ol.Size} size Size.
 * @protected
 * @return {ol.Coordinate} Snapped center.
 */
ol.renderer.Layer.prototype.snapCenterToPixel = function(center, resolution, size) {
  return [
    resolution * (Math.round(center[0] / resolution) + (size[0] % 2) / 2),
    resolution * (Math.round(center[1] / resolution) + (size[1] % 2) / 2)
  ];
};


/**
 * Manage tile pyramid.
 * This function performs a number of functions related to the tiles at the
 * current zoom and lower zoom levels:
 * - registers idle tiles in frameState.wantedTiles so that they are not
 *   discarded by the tile queue
 * - enqueues missing tiles
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.source.Tile} tileSource Tile source.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {ol.Extent} extent Extent.
 * @param {number} currentZ Current Z.
 * @param {number} preload Load low resolution tiles up to 'preload' levels.
 * @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback.
 * @param {T=} opt_this Object to use as `this` in `opt_tileCallback`.
 * @protected
 * @template T
 */
ol.renderer.Layer.prototype.manageTilePyramid = function(
    frameState, tileSource, tileGrid, pixelRatio, projection, extent,
    currentZ, preload, opt_tileCallback, opt_this) {
  var tileSourceKey = ol.getUid(tileSource).toString();
  if (!(tileSourceKey in frameState.wantedTiles)) {
    frameState.wantedTiles[tileSourceKey] = {};
  }
  var wantedTiles = frameState.wantedTiles[tileSourceKey];
  var tileQueue = frameState.tileQueue;
  var minZoom = tileGrid.getMinZoom();
  var tile, tileRange, tileResolution, x, y, z;
  for (z = currentZ; z >= minZoom; --z) {
    tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
    tileResolution = tileGrid.getResolution(z);
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
        if (currentZ - z <= preload) {
          tile = tileSource.getTile(z, x, y, pixelRatio, projection);
          if (tile.getState() == ol.Tile.State.IDLE) {
            wantedTiles[tile.getKey()] = true;
            if (!tileQueue.isKeyQueued(tile.getKey())) {
              tileQueue.enqueue([tile, tileSourceKey,
                tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
            }
          }
          if (opt_tileCallback !== undefined) {
            opt_tileCallback.call(opt_this, tile);
          }
        } else {
          tileSource.useTile(z, x, y, projection);
        }
      }
    }
  }
};

goog.provide('ol.renderer.canvas.Layer');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas');
goog.require('ol.render.canvas.Immediate');
goog.require('ol.renderer.Layer');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.renderer.Layer}
 * @param {ol.layer.Layer} layer Layer.
 */
ol.renderer.canvas.Layer = function(layer) {

  ol.renderer.Layer.call(this, layer);

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

};
ol.inherits(ol.renderer.canvas.Layer, ol.renderer.Layer);


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Extent} extent Clip extent.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.clip = function(context, frameState, extent) {
  var pixelRatio = frameState.pixelRatio;
  var width = frameState.size[0] * pixelRatio;
  var height = frameState.size[1] * pixelRatio;
  var rotation = frameState.viewState.rotation;
  var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent));
  var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent));
  var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent));
  var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent));

  ol.transform.apply(frameState.coordinateToPixelTransform, topLeft);
  ol.transform.apply(frameState.coordinateToPixelTransform, topRight);
  ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight);
  ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft);

  context.save();
  ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
  context.beginPath();
  context.moveTo(topLeft[0] * pixelRatio, topLeft[1] * pixelRatio);
  context.lineTo(topRight[0] * pixelRatio, topRight[1] * pixelRatio);
  context.lineTo(bottomRight[0] * pixelRatio, bottomRight[1] * pixelRatio);
  context.lineTo(bottomLeft[0] * pixelRatio, bottomLeft[1] * pixelRatio);
  context.clip();
  ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {CanvasRenderingContext2D} context Context.
 */
ol.renderer.canvas.Layer.prototype.composeFrame = function(frameState, layerState, context) {

  this.dispatchPreComposeEvent(context, frameState);

  var image = this.getImage();
  if (image) {

    // clipped rendering if layer extent is set
    var extent = layerState.extent;
    var clipped = extent !== undefined;
    if (clipped) {
      this.clip(context, frameState, /** @type {ol.Extent} */ (extent));
    }

    var imageTransform = this.getImageTransform();
    // for performance reasons, context.save / context.restore is not used
    // to save and restore the transformation matrix and the opacity.
    // see http://jsperf.com/context-save-restore-versus-variable
    var alpha = context.globalAlpha;
    context.globalAlpha = layerState.opacity;

    // for performance reasons, context.setTransform is only used
    // when the view is rotated. see http://jsperf.com/canvas-transform
    var dx = imageTransform[4];
    var dy = imageTransform[5];
    var dw = image.width * imageTransform[0];
    var dh = image.height * imageTransform[3];
    context.drawImage(image, 0, 0, +image.width, +image.height,
        Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh));
    context.globalAlpha = alpha;

    if (clipped) {
      context.restore();
    }
  }

  this.dispatchPostComposeEvent(context, frameState);

};


/**
 * @param {ol.render.EventType} type Event type.
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform=} opt_transform Transform.
 * @private
 */
ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState, opt_transform) {
  var layer = this.getLayer();
  if (layer.hasListener(type)) {
    var width = frameState.size[0] * frameState.pixelRatio;
    var height = frameState.size[1] * frameState.pixelRatio;
    var rotation = frameState.viewState.rotation;
    ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);
    var transform = opt_transform !== undefined ?
        opt_transform : this.getTransform(frameState, 0);
    var render = new ol.render.canvas.Immediate(
        context, frameState.pixelRatio, frameState.extent, transform,
        frameState.viewState.rotation);
    var composeEvent = new ol.render.Event(type, render, frameState,
        context, null);
    layer.dispatchEvent(composeEvent);
    ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);
  }
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform=} opt_transform Transform.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.dispatchPostComposeEvent = function(context, frameState, opt_transform) {
  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, context,
      frameState, opt_transform);
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform=} opt_transform Transform.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.dispatchPreComposeEvent = function(context, frameState, opt_transform) {
  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, context,
      frameState, opt_transform);
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform=} opt_transform Transform.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.dispatchRenderEvent = function(context, frameState, opt_transform) {
  this.dispatchComposeEvent_(ol.render.EventType.RENDER, context,
      frameState, opt_transform);
};


/**
 * @abstract
 * @return {HTMLCanvasElement|HTMLVideoElement|Image} Canvas.
 */
ol.renderer.canvas.Layer.prototype.getImage = function() {};


/**
 * @abstract
 * @return {!ol.Transform} Image transform.
 */
ol.renderer.canvas.Layer.prototype.getImageTransform = function() {};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {number} offsetX Offset on the x-axis in view coordinates.
 * @protected
 * @return {!ol.Transform} Transform.
 */
ol.renderer.canvas.Layer.prototype.getTransform = function(frameState, offsetX) {
  var viewState = frameState.viewState;
  var pixelRatio = frameState.pixelRatio;
  var dx1 = pixelRatio * frameState.size[0] / 2;
  var dy1 = pixelRatio * frameState.size[1] / 2;
  var sx = pixelRatio / viewState.resolution;
  var sy = -sx;
  var angle = -viewState.rotation;
  var dx2 = -viewState.center[0] + offsetX;
  var dy2 = -viewState.center[1];
  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2);
};


/**
 * @abstract
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @return {boolean} whether composeFrame should be called.
 */
ol.renderer.canvas.Layer.prototype.prepareFrame = function(frameState, layerState) {};


/**
 * @param {ol.Pixel} pixelOnMap Pixel.
 * @param {ol.Transform} imageTransformInv The transformation matrix
 *        to convert from a map pixel to a canvas pixel.
 * @return {ol.Pixel} The pixel.
 * @protected
 */
ol.renderer.canvas.Layer.prototype.getPixelOnCanvas = function(pixelOnMap, imageTransformInv) {
  return ol.transform.apply(imageTransformInv, pixelOnMap.slice());
};

goog.provide('ol.render.ReplayGroup');


/**
 * Base class for replay groups.
 * @constructor
 */
ol.render.ReplayGroup = function() {};


/**
 * @abstract
 * @param {number|undefined} zIndex Z index.
 * @param {ol.render.ReplayType} replayType Replay type.
 * @return {ol.render.VectorContext} Replay.
 */
ol.render.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {};


/**
 * @abstract
 * @return {boolean} Is empty.
 */
ol.render.ReplayGroup.prototype.isEmpty = function() {};

goog.provide('ol.render.ReplayType');


/**
 * @enum {string}
 */
ol.render.ReplayType = {
  IMAGE: 'Image',
  LINE_STRING: 'LineString',
  POLYGON: 'Polygon',
  TEXT: 'Text'
};

goog.provide('ol.render.replay');

goog.require('ol.render.ReplayType');


/**
 * @const
 * @type {Array.<ol.render.ReplayType>}
 */
ol.render.replay.ORDER = [
  ol.render.ReplayType.POLYGON,
  ol.render.ReplayType.LINE_STRING,
  ol.render.ReplayType.IMAGE,
  ol.render.ReplayType.TEXT
];

// FIXME add option to apply snapToPixel to all coordinates?
// FIXME can eliminate empty set styles and strokes (when all geoms skipped)

goog.provide('ol.render.canvas.Replay');
goog.provide('ol.render.canvas.ImageReplay');
goog.provide('ol.render.canvas.LineStringReplay');
goog.provide('ol.render.canvas.PolygonReplay');
goog.provide('ol.render.canvas.ReplayGroup');
goog.provide('ol.render.canvas.TextReplay');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.color');
goog.require('ol.colorlike');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.extent.Relationship');
goog.require('ol.geom.flat.simplify');
goog.require('ol.geom.flat.transform');
goog.require('ol.has');
goog.require('ol.obj');
goog.require('ol.render.ReplayGroup');
goog.require('ol.render.VectorContext');
goog.require('ol.render.canvas');
goog.require('ol.render.replay');
goog.require('ol.transform');


/**
 * @enum {number}
 */
ol.render.canvas.Instruction = {
  BEGIN_GEOMETRY: 0,
  BEGIN_PATH: 1,
  CIRCLE: 2,
  CLOSE_PATH: 3,
  DRAW_IMAGE: 4,
  DRAW_TEXT: 5,
  END_GEOMETRY: 6,
  FILL: 7,
  MOVE_TO_LINE_TO: 8,
  SET_FILL_STYLE: 9,
  SET_STROKE_STYLE: 10,
  SET_TEXT_STYLE: 11,
  STROKE: 12
};


/**
 * @constructor
 * @extends {ol.render.VectorContext}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Maximum extent.
 * @param {number} resolution Resolution.
 * @param {boolean} overlaps The replay can have overlapping geometries.
 * @protected
 * @struct
 */
ol.render.canvas.Replay = function(tolerance, maxExtent, resolution, overlaps) {
  ol.render.VectorContext.call(this);

  /**
   * @protected
   * @type {number}
   */
  this.tolerance = tolerance;

  /**
   * @protected
   * @const
   * @type {ol.Extent}
   */
  this.maxExtent = maxExtent;

  /**
   * @protected
   * @type {boolean}
   */
  this.overlaps = overlaps;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.bufferedMaxExtent_ = null;

  /**
   * @protected
   * @type {number}
   */
  this.maxLineWidth = 0;

  /**
   * @protected
   * @const
   * @type {number}
   */
  this.resolution = resolution;

  /**
   * @private
   * @type {Array.<*>}
   */
  this.beginGeometryInstruction1_ = null;

  /**
   * @private
   * @type {Array.<*>}
   */
  this.beginGeometryInstruction2_ = null;

  /**
   * @protected
   * @type {Array.<*>}
   */
  this.instructions = [];

  /**
   * @protected
   * @type {Array.<number>}
   */
  this.coordinates = [];

  /**
   * @private
   * @type {ol.Transform}
   */
  this.renderedTransform_ = ol.transform.create();

  /**
   * @protected
   * @type {Array.<*>}
   */
  this.hitDetectionInstructions = [];

  /**
   * @private
   * @type {Array.<number>}
   */
  this.pixelCoordinates_ = [];

  /**
   * @private
   * @type {ol.Transform}
   */
  this.tmpLocalTransform_ = ol.transform.create();

  /**
   * @private
   * @type {ol.Transform}
   */
  this.tmpLocalTransformInv_ = ol.transform.create();
};
ol.inherits(ol.render.canvas.Replay, ol.render.VectorContext);


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {boolean} close Close.
 * @protected
 * @return {number} My end.
 */
ol.render.canvas.Replay.prototype.appendFlatCoordinates = function(flatCoordinates, offset, end, stride, close) {

  var myEnd = this.coordinates.length;
  var extent = this.getBufferedMaxExtent();
  var lastCoord = [flatCoordinates[offset], flatCoordinates[offset + 1]];
  var nextCoord = [NaN, NaN];
  var skipped = true;

  var i, lastRel, nextRel;
  for (i = offset + stride; i < end; i += stride) {
    nextCoord[0] = flatCoordinates[i];
    nextCoord[1] = flatCoordinates[i + 1];
    nextRel = ol.extent.coordinateRelationship(extent, nextCoord);
    if (nextRel !== lastRel) {
      if (skipped) {
        this.coordinates[myEnd++] = lastCoord[0];
        this.coordinates[myEnd++] = lastCoord[1];
      }
      this.coordinates[myEnd++] = nextCoord[0];
      this.coordinates[myEnd++] = nextCoord[1];
      skipped = false;
    } else if (nextRel === ol.extent.Relationship.INTERSECTING) {
      this.coordinates[myEnd++] = nextCoord[0];
      this.coordinates[myEnd++] = nextCoord[1];
      skipped = false;
    } else {
      skipped = true;
    }
    lastCoord[0] = nextCoord[0];
    lastCoord[1] = nextCoord[1];
    lastRel = nextRel;
  }

  // handle case where there is only one point to append
  if (i === offset + stride) {
    this.coordinates[myEnd++] = lastCoord[0];
    this.coordinates[myEnd++] = lastCoord[1];
  }

  if (close) {
    this.coordinates[myEnd++] = flatCoordinates[offset];
    this.coordinates[myEnd++] = flatCoordinates[offset + 1];
  }
  return myEnd;
};


/**
 * @protected
 * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) {
  this.beginGeometryInstruction1_ =
      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
  this.instructions.push(this.beginGeometryInstruction1_);
  this.beginGeometryInstruction2_ =
      [ol.render.canvas.Instruction.BEGIN_GEOMETRY, feature, 0];
  this.hitDetectionInstructions.push(this.beginGeometryInstruction2_);
};


/**
 * @private
 * @param {CanvasRenderingContext2D} context Context.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Transform} transform Transform.
 * @param {number} viewRotation View rotation.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *     to skip.
 * @param {Array.<*>} instructions Instructions array.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined}
 *     featureCallback Feature callback.
 * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
 *     extent.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.canvas.Replay.prototype.replay_ = function(
    context, pixelRatio, transform, viewRotation, skippedFeaturesHash,
    instructions, featureCallback, opt_hitExtent) {
  /** @type {Array.<number>} */
  var pixelCoordinates;
  if (ol.array.equals(transform, this.renderedTransform_)) {
    pixelCoordinates = this.pixelCoordinates_;
  } else {
    pixelCoordinates = ol.geom.flat.transform.transform2D(
        this.coordinates, 0, this.coordinates.length, 2,
        transform, this.pixelCoordinates_);
    ol.transform.setFromArray(this.renderedTransform_, transform);
    goog.DEBUG && console.assert(pixelCoordinates === this.pixelCoordinates_,
        'pixelCoordinates should be the same as this.pixelCoordinates_');
  }
  var skipFeatures = !ol.obj.isEmpty(skippedFeaturesHash);
  var i = 0; // instruction index
  var ii = instructions.length; // end of instructions
  var d = 0; // data index
  var dd; // end of per-instruction data
  var localTransform = this.tmpLocalTransform_;
  var localTransformInv = this.tmpLocalTransformInv_;
  var prevX, prevY, roundX, roundY;
  var pendingFill = 0;
  var pendingStroke = 0;
  // When the batch size gets too big, performance decreases. 200 is a good
  // balance between batch size and number of fill/stroke instructions.
  var batchSize =
      this.instructions != instructions || this.overlaps ? 0 : 200;
  while (i < ii) {
    var instruction = instructions[i];
    var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
    var feature, fill, stroke, text, x, y;
    switch (type) {
      case ol.render.canvas.Instruction.BEGIN_GEOMETRY:
        feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
        if ((skipFeatures &&
            skippedFeaturesHash[ol.getUid(feature).toString()]) ||
            !feature.getGeometry()) {
          i = /** @type {number} */ (instruction[2]);
        } else if (opt_hitExtent !== undefined && !ol.extent.intersects(
            opt_hitExtent, feature.getGeometry().getExtent())) {
          i = /** @type {number} */ (instruction[2]) + 1;
        } else {
          ++i;
        }
        break;
      case ol.render.canvas.Instruction.BEGIN_PATH:
        if (pendingFill > batchSize) {
          context.fill();
          pendingFill = 0;
        }
        if (pendingStroke > batchSize) {
          context.stroke();
          pendingStroke = 0;
        }
        if (!pendingFill && !pendingStroke) {
          context.beginPath();
        }
        ++i;
        break;
      case ol.render.canvas.Instruction.CIRCLE:
        goog.DEBUG && console.assert(typeof instruction[1] === 'number',
            'second instruction should be a number');
        d = /** @type {number} */ (instruction[1]);
        var x1 = pixelCoordinates[d];
        var y1 = pixelCoordinates[d + 1];
        var x2 = pixelCoordinates[d + 2];
        var y2 = pixelCoordinates[d + 3];
        var dx = x2 - x1;
        var dy = y2 - y1;
        var r = Math.sqrt(dx * dx + dy * dy);
        context.moveTo(x1 + r, y1);
        context.arc(x1, y1, r, 0, 2 * Math.PI, true);
        ++i;
        break;
      case ol.render.canvas.Instruction.CLOSE_PATH:
        context.closePath();
        ++i;
        break;
      case ol.render.canvas.Instruction.DRAW_IMAGE:
        goog.DEBUG && console.assert(typeof instruction[1] === 'number',
            'second instruction should be a number');
        d = /** @type {number} */ (instruction[1]);
        goog.DEBUG && console.assert(typeof instruction[2] === 'number',
            'third instruction should be a number');
        dd = /** @type {number} */ (instruction[2]);
        var image =  /** @type {HTMLCanvasElement|HTMLVideoElement|Image} */
            (instruction[3]);
        // Remaining arguments in DRAW_IMAGE are in alphabetical order
        var anchorX = /** @type {number} */ (instruction[4]) * pixelRatio;
        var anchorY = /** @type {number} */ (instruction[5]) * pixelRatio;
        var height = /** @type {number} */ (instruction[6]);
        var opacity = /** @type {number} */ (instruction[7]);
        var originX = /** @type {number} */ (instruction[8]);
        var originY = /** @type {number} */ (instruction[9]);
        var rotateWithView = /** @type {boolean} */ (instruction[10]);
        var rotation = /** @type {number} */ (instruction[11]);
        var scale = /** @type {number} */ (instruction[12]);
        var snapToPixel = /** @type {boolean} */ (instruction[13]);
        var width = /** @type {number} */ (instruction[14]);
        if (rotateWithView) {
          rotation += viewRotation;
        }
        for (; d < dd; d += 2) {
          x = pixelCoordinates[d] - anchorX;
          y = pixelCoordinates[d + 1] - anchorY;
          if (snapToPixel) {
            x = Math.round(x);
            y = Math.round(y);
          }
          if (scale != 1 || rotation !== 0) {
            var centerX = x + anchorX;
            var centerY = y + anchorY;
            ol.transform.compose(localTransform,
                centerX, centerY, scale, scale, rotation, -centerX, -centerY);
            context.transform.apply(context, localTransform);
          }
          var alpha = context.globalAlpha;
          if (opacity != 1) {
            context.globalAlpha = alpha * opacity;
          }

          var w = (width + originX > image.width) ? image.width - originX : width;
          var h = (height + originY > image.height) ? image.height - originY : height;

          context.drawImage(image, originX, originY, w, h,
              x, y, w * pixelRatio, h * pixelRatio);

          if (opacity != 1) {
            context.globalAlpha = alpha;
          }
          if (scale != 1 || rotation !== 0) {
            ol.transform.invert(ol.transform.setFromArray(localTransformInv, localTransform));
            context.transform.apply(context, localTransformInv);
          }
        }
        ++i;
        break;
      case ol.render.canvas.Instruction.DRAW_TEXT:
        goog.DEBUG && console.assert(typeof instruction[1] === 'number',
            '2nd instruction should be a number');
        d = /** @type {number} */ (instruction[1]);
        goog.DEBUG && console.assert(typeof instruction[2] === 'number',
            '3rd instruction should be a number');
        dd = /** @type {number} */ (instruction[2]);
        goog.DEBUG && console.assert(typeof instruction[3] === 'string',
            '4th instruction should be a string');
        text = /** @type {string} */ (instruction[3]);
        goog.DEBUG && console.assert(typeof instruction[4] === 'number',
            '5th instruction should be a number');
        var offsetX = /** @type {number} */ (instruction[4]) * pixelRatio;
        goog.DEBUG && console.assert(typeof instruction[5] === 'number',
            '6th instruction should be a number');
        var offsetY = /** @type {number} */ (instruction[5]) * pixelRatio;
        goog.DEBUG && console.assert(typeof instruction[6] === 'number',
            '7th instruction should be a number');
        rotation = /** @type {number} */ (instruction[6]);
        goog.DEBUG && console.assert(typeof instruction[7] === 'number',
            '8th instruction should be a number');
        scale = /** @type {number} */ (instruction[7]) * pixelRatio;
        goog.DEBUG && console.assert(typeof instruction[8] === 'boolean',
            '9th instruction should be a boolean');
        fill = /** @type {boolean} */ (instruction[8]);
        goog.DEBUG && console.assert(typeof instruction[9] === 'boolean',
            '10th instruction should be a boolean');
        stroke = /** @type {boolean} */ (instruction[9]);
        rotateWithView = /** @type {boolean} */ (instruction[10]);
        if (rotateWithView) {
          rotation += viewRotation;
        }
        for (; d < dd; d += 2) {
          x = pixelCoordinates[d] + offsetX;
          y = pixelCoordinates[d + 1] + offsetY;
          if (scale != 1 || rotation !== 0) {
            ol.transform.compose(localTransform, x, y, scale, scale, rotation, -x, -y);
            context.transform.apply(context, localTransform);
          }

          // Support multiple lines separated by \n
          var lines = text.split('\n');
          var numLines = lines.length;
          var fontSize, lineY;
          if (numLines > 1) {
            // Estimate line height using width of capital M, and add padding
            fontSize = Math.round(context.measureText('M').width * 1.5);
            lineY = y - (((numLines - 1) / 2) * fontSize);
          } else {
            // No need to calculate line height/offset for a single line
            fontSize = 0;
            lineY = y;
          }

          for (var lineIndex = 0; lineIndex < numLines; lineIndex++) {
            var line = lines[lineIndex];
            if (stroke) {
              context.strokeText(line, x, lineY);
            }
            if (fill) {
              context.fillText(line, x, lineY);
            }

            // Move next line down by fontSize px
            lineY = lineY + fontSize;
          }

          if (scale != 1 || rotation !== 0) {
            ol.transform.invert(ol.transform.setFromArray(localTransformInv, localTransform));
            context.transform.apply(context, localTransformInv);
          }
        }
        ++i;
        break;
      case ol.render.canvas.Instruction.END_GEOMETRY:
        if (featureCallback !== undefined) {
          feature =
              /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]);
          var result = featureCallback(feature);
          if (result) {
            return result;
          }
        }
        ++i;
        break;
      case ol.render.canvas.Instruction.FILL:
        if (batchSize) {
          pendingFill++;
        } else {
          context.fill();
        }
        ++i;
        break;
      case ol.render.canvas.Instruction.MOVE_TO_LINE_TO:
        goog.DEBUG && console.assert(typeof instruction[1] === 'number',
            '2nd instruction should be a number');
        d = /** @type {number} */ (instruction[1]);
        goog.DEBUG && console.assert(typeof instruction[2] === 'number',
            '3rd instruction should be a number');
        dd = /** @type {number} */ (instruction[2]);
        x = pixelCoordinates[d];
        y = pixelCoordinates[d + 1];
        roundX = (x + 0.5) | 0;
        roundY = (y + 0.5) | 0;
        if (roundX !== prevX || roundY !== prevY) {
          context.moveTo(x, y);
          prevX = roundX;
          prevY = roundY;
        }
        for (d += 2; d < dd; d += 2) {
          x = pixelCoordinates[d];
          y = pixelCoordinates[d + 1];
          roundX = (x + 0.5) | 0;
          roundY = (y + 0.5) | 0;
          if (d == dd - 2 || roundX !== prevX || roundY !== prevY) {
            context.lineTo(x, y);
            prevX = roundX;
            prevY = roundY;
          }
        }
        ++i;
        break;
      case ol.render.canvas.Instruction.SET_FILL_STYLE:
        goog.DEBUG && console.assert(
            ol.colorlike.isColorLike(instruction[1]),
            '2nd instruction should be a string, ' +
            'CanvasPattern, or CanvasGradient');
        if (pendingFill) {
          context.fill();
          pendingFill = 0;
        }

        context.fillStyle = /** @type {ol.ColorLike} */ (instruction[1]);
        ++i;
        break;
      case ol.render.canvas.Instruction.SET_STROKE_STYLE:
        goog.DEBUG && console.assert(typeof instruction[1] === 'string',
            '2nd instruction should be a string');
        goog.DEBUG && console.assert(typeof instruction[2] === 'number',
            '3rd instruction should be a number');
        goog.DEBUG && console.assert(typeof instruction[3] === 'string',
            '4rd instruction should be a string');
        goog.DEBUG && console.assert(typeof instruction[4] === 'string',
            '5th instruction should be a string');
        goog.DEBUG && console.assert(typeof instruction[5] === 'number',
            '6th instruction should be a number');
        goog.DEBUG && console.assert(instruction[6],
            '7th instruction should not be null');
        var usePixelRatio = instruction[7] !== undefined ?
            instruction[7] : true;
        var lineWidth = /** @type {number} */ (instruction[2]);
        if (pendingStroke) {
          context.stroke();
          pendingStroke = 0;
        }
        context.strokeStyle = /** @type {string} */ (instruction[1]);
        context.lineWidth = usePixelRatio ? lineWidth * pixelRatio : lineWidth;
        context.lineCap = /** @type {string} */ (instruction[3]);
        context.lineJoin = /** @type {string} */ (instruction[4]);
        context.miterLimit = /** @type {number} */ (instruction[5]);
        if (ol.has.CANVAS_LINE_DASH) {
          context.setLineDash(/** @type {Array.<number>} */ (instruction[6]));
        }
        prevX = NaN;
        prevY = NaN;
        ++i;
        break;
      case ol.render.canvas.Instruction.SET_TEXT_STYLE:
        goog.DEBUG && console.assert(typeof instruction[1] === 'string',
            '2nd instruction should be a string');
        goog.DEBUG && console.assert(typeof instruction[2] === 'string',
            '3rd instruction should be a string');
        goog.DEBUG && console.assert(typeof instruction[3] === 'string',
            '4th instruction should be a string');
        context.font = /** @type {string} */ (instruction[1]);
        context.textAlign = /** @type {string} */ (instruction[2]);
        context.textBaseline = /** @type {string} */ (instruction[3]);
        ++i;
        break;
      case ol.render.canvas.Instruction.STROKE:
        if (batchSize) {
          pendingStroke++;
        } else {
          context.stroke();
        }
        ++i;
        break;
      default:
        goog.DEBUG && console.assert(false, 'Unknown canvas render instruction');
        ++i; // consume the instruction anyway, to avoid an infinite loop
        break;
    }
  }
  if (pendingFill) {
    context.fill();
  }
  if (pendingStroke) {
    context.stroke();
  }
  // assert that all instructions were consumed
  goog.DEBUG && console.assert(i == instructions.length,
      'all instructions should be consumed');
  return undefined;
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Transform} transform Transform.
 * @param {number} viewRotation View rotation.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *     to skip.
 */
ol.render.canvas.Replay.prototype.replay = function(
    context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
  var instructions = this.instructions;
  this.replay_(context, pixelRatio, transform, viewRotation,
      skippedFeaturesHash, instructions, undefined, undefined);
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {ol.Transform} transform Transform.
 * @param {number} viewRotation View rotation.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *     to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T=} opt_featureCallback
 *     Feature callback.
 * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
 *     extent.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.canvas.Replay.prototype.replayHitDetection = function(
    context, transform, viewRotation, skippedFeaturesHash,
    opt_featureCallback, opt_hitExtent) {
  var instructions = this.hitDetectionInstructions;
  return this.replay_(context, 1, transform, viewRotation,
      skippedFeaturesHash, instructions, opt_featureCallback, opt_hitExtent);
};


/**
 * @private
 */
ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ = function() {
  var hitDetectionInstructions = this.hitDetectionInstructions;
  // step 1 - reverse array
  hitDetectionInstructions.reverse();
  // step 2 - reverse instructions within geometry blocks
  var i;
  var n = hitDetectionInstructions.length;
  var instruction;
  var type;
  var begin = -1;
  for (i = 0; i < n; ++i) {
    instruction = hitDetectionInstructions[i];
    type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]);
    if (type == ol.render.canvas.Instruction.END_GEOMETRY) {
      goog.DEBUG && console.assert(begin == -1, 'begin should be -1');
      begin = i;
    } else if (type == ol.render.canvas.Instruction.BEGIN_GEOMETRY) {
      instruction[2] = i;
      goog.DEBUG && console.assert(begin >= 0,
          'begin should be larger than or equal to 0');
      ol.array.reverseSubArray(this.hitDetectionInstructions, begin, i);
      begin = -1;
    }
  }
};


/**
 * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 */
ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) {
  goog.DEBUG && console.assert(this.beginGeometryInstruction1_,
      'this.beginGeometryInstruction1_ should not be null');
  this.beginGeometryInstruction1_[2] = this.instructions.length;
  this.beginGeometryInstruction1_ = null;
  goog.DEBUG && console.assert(this.beginGeometryInstruction2_,
      'this.beginGeometryInstruction2_ should not be null');
  this.beginGeometryInstruction2_[2] = this.hitDetectionInstructions.length;
  this.beginGeometryInstruction2_ = null;
  var endGeometryInstruction =
      [ol.render.canvas.Instruction.END_GEOMETRY, feature];
  this.instructions.push(endGeometryInstruction);
  this.hitDetectionInstructions.push(endGeometryInstruction);
};


/**
 * FIXME empty description for jsdoc
 */
ol.render.canvas.Replay.prototype.finish = ol.nullFunction;


/**
 * Get the buffered rendering extent.  Rendering will be clipped to the extent
 * provided to the constructor.  To account for symbolizers that may intersect
 * this extent, we calculate a buffered extent (e.g. based on stroke width).
 * @return {ol.Extent} The buffered rendering extent.
 * @protected
 */
ol.render.canvas.Replay.prototype.getBufferedMaxExtent = function() {
  return this.maxExtent;
};


/**
 * @constructor
 * @extends {ol.render.canvas.Replay}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Maximum extent.
 * @param {number} resolution Resolution.
 * @param {boolean} overlaps The replay can have overlapping geometries.
 * @protected
 * @struct
 */
ol.render.canvas.ImageReplay = function(tolerance, maxExtent, resolution, overlaps) {
  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);

  /**
   * @private
   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
   */
  this.hitDetectionImage_ = null;

  /**
   * @private
   * @type {HTMLCanvasElement|HTMLVideoElement|Image}
   */
  this.image_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.anchorX_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.anchorY_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.height_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.opacity_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.originX_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.originY_ = undefined;

  /**
   * @private
   * @type {boolean|undefined}
   */
  this.rotateWithView_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.rotation_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.scale_ = undefined;

  /**
   * @private
   * @type {boolean|undefined}
   */
  this.snapToPixel_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.width_ = undefined;

};
ol.inherits(ol.render.canvas.ImageReplay, ol.render.canvas.Replay);


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @private
 * @return {number} My end.
 */
ol.render.canvas.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
  return this.appendFlatCoordinates(
      flatCoordinates, offset, end, stride, false);
};


/**
 * @inheritDoc
 */
ol.render.canvas.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) {
  if (!this.image_) {
    return;
  }
  goog.DEBUG && console.assert(this.anchorX_ !== undefined,
      'this.anchorX_ should be defined');
  goog.DEBUG && console.assert(this.anchorY_ !== undefined,
      'this.anchorY_ should be defined');
  goog.DEBUG && console.assert(this.height_ !== undefined,
      'this.height_ should be defined');
  goog.DEBUG && console.assert(this.opacity_ !== undefined,
      'this.opacity_ should be defined');
  goog.DEBUG && console.assert(this.originX_ !== undefined,
      'this.originX_ should be defined');
  goog.DEBUG && console.assert(this.originY_ !== undefined,
      'this.originY_ should be defined');
  goog.DEBUG && console.assert(this.rotateWithView_ !== undefined,
      'this.rotateWithView_ should be defined');
  goog.DEBUG && console.assert(this.rotation_ !== undefined,
      'this.rotation_ should be defined');
  goog.DEBUG && console.assert(this.scale_ !== undefined,
      'this.scale_ should be defined');
  goog.DEBUG && console.assert(this.width_ !== undefined,
      'this.width_ should be defined');
  this.beginGeometry(pointGeometry, feature);
  var flatCoordinates = pointGeometry.getFlatCoordinates();
  var stride = pointGeometry.getStride();
  var myBegin = this.coordinates.length;
  var myEnd = this.drawCoordinates_(
      flatCoordinates, 0, flatCoordinates.length, stride);
  this.instructions.push([
    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
    // Remaining arguments to DRAW_IMAGE are in alphabetical order
    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
    this.scale_, this.snapToPixel_, this.width_
  ]);
  this.hitDetectionInstructions.push([
    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
    this.hitDetectionImage_,
    // Remaining arguments to DRAW_IMAGE are in alphabetical order
    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
    this.scale_, this.snapToPixel_, this.width_
  ]);
  this.endGeometry(pointGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) {
  if (!this.image_) {
    return;
  }
  goog.DEBUG && console.assert(this.anchorX_ !== undefined,
      'this.anchorX_ should be defined');
  goog.DEBUG && console.assert(this.anchorY_ !== undefined,
      'this.anchorY_ should be defined');
  goog.DEBUG && console.assert(this.height_ !== undefined,
      'this.height_ should be defined');
  goog.DEBUG && console.assert(this.opacity_ !== undefined,
      'this.opacity_ should be defined');
  goog.DEBUG && console.assert(this.originX_ !== undefined,
      'this.originX_ should be defined');
  goog.DEBUG && console.assert(this.originY_ !== undefined,
      'this.originY_ should be defined');
  goog.DEBUG && console.assert(this.rotateWithView_ !== undefined,
      'this.rotateWithView_ should be defined');
  goog.DEBUG && console.assert(this.rotation_ !== undefined,
      'this.rotation_ should be defined');
  goog.DEBUG && console.assert(this.scale_ !== undefined,
      'this.scale_ should be defined');
  goog.DEBUG && console.assert(this.width_ !== undefined,
      'this.width_ should be defined');
  this.beginGeometry(multiPointGeometry, feature);
  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
  var stride = multiPointGeometry.getStride();
  var myBegin = this.coordinates.length;
  var myEnd = this.drawCoordinates_(
      flatCoordinates, 0, flatCoordinates.length, stride);
  this.instructions.push([
    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd, this.image_,
    // Remaining arguments to DRAW_IMAGE are in alphabetical order
    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
    this.scale_, this.snapToPixel_, this.width_
  ]);
  this.hitDetectionInstructions.push([
    ol.render.canvas.Instruction.DRAW_IMAGE, myBegin, myEnd,
    this.hitDetectionImage_,
    // Remaining arguments to DRAW_IMAGE are in alphabetical order
    this.anchorX_, this.anchorY_, this.height_, this.opacity_,
    this.originX_, this.originY_, this.rotateWithView_, this.rotation_,
    this.scale_, this.snapToPixel_, this.width_
  ]);
  this.endGeometry(multiPointGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.ImageReplay.prototype.finish = function() {
  this.reverseHitDetectionInstructions_();
  // FIXME this doesn't really protect us against further calls to draw*Geometry
  this.anchorX_ = undefined;
  this.anchorY_ = undefined;
  this.hitDetectionImage_ = null;
  this.image_ = null;
  this.height_ = undefined;
  this.scale_ = undefined;
  this.opacity_ = undefined;
  this.originX_ = undefined;
  this.originY_ = undefined;
  this.rotateWithView_ = undefined;
  this.rotation_ = undefined;
  this.snapToPixel_ = undefined;
  this.width_ = undefined;
};


/**
 * @inheritDoc
 */
ol.render.canvas.ImageReplay.prototype.setImageStyle = function(imageStyle) {
  goog.DEBUG && console.assert(imageStyle, 'imageStyle should not be null');
  var anchor = imageStyle.getAnchor();
  goog.DEBUG && console.assert(anchor, 'anchor should not be null');
  var size = imageStyle.getSize();
  goog.DEBUG && console.assert(size, 'size should not be null');
  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
  goog.DEBUG && console.assert(hitDetectionImage,
      'hitDetectionImage should not be null');
  var image = imageStyle.getImage(1);
  goog.DEBUG && console.assert(image, 'image should not be null');
  var origin = imageStyle.getOrigin();
  goog.DEBUG && console.assert(origin, 'origin should not be null');
  this.anchorX_ = anchor[0];
  this.anchorY_ = anchor[1];
  this.hitDetectionImage_ = hitDetectionImage;
  this.image_ = image;
  this.height_ = size[1];
  this.opacity_ = imageStyle.getOpacity();
  this.originX_ = origin[0];
  this.originY_ = origin[1];
  this.rotateWithView_ = imageStyle.getRotateWithView();
  this.rotation_ = imageStyle.getRotation();
  this.scale_ = imageStyle.getScale();
  this.snapToPixel_ = imageStyle.getSnapToPixel();
  this.width_ = size[0];
};


/**
 * @constructor
 * @extends {ol.render.canvas.Replay}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Maximum extent.
 * @param {number} resolution Resolution.
 * @param {boolean} overlaps The replay can have overlapping geometries.
 * @protected
 * @struct
 */
ol.render.canvas.LineStringReplay = function(tolerance, maxExtent, resolution, overlaps) {

  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);

  /**
   * @private
   * @type {{currentStrokeStyle: (string|undefined),
   *         currentLineCap: (string|undefined),
   *         currentLineDash: Array.<number>,
   *         currentLineJoin: (string|undefined),
   *         currentLineWidth: (number|undefined),
   *         currentMiterLimit: (number|undefined),
   *         lastStroke: number,
   *         strokeStyle: (string|undefined),
   *         lineCap: (string|undefined),
   *         lineDash: Array.<number>,
   *         lineJoin: (string|undefined),
   *         lineWidth: (number|undefined),
   *         miterLimit: (number|undefined)}|null}
   */
  this.state_ = {
    currentStrokeStyle: undefined,
    currentLineCap: undefined,
    currentLineDash: null,
    currentLineJoin: undefined,
    currentLineWidth: undefined,
    currentMiterLimit: undefined,
    lastStroke: 0,
    strokeStyle: undefined,
    lineCap: undefined,
    lineDash: null,
    lineJoin: undefined,
    lineWidth: undefined,
    miterLimit: undefined
  };

};
ol.inherits(ol.render.canvas.LineStringReplay, ol.render.canvas.Replay);


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @private
 * @return {number} end.
 */
ol.render.canvas.LineStringReplay.prototype.drawFlatCoordinates_ = function(flatCoordinates, offset, end, stride) {
  var myBegin = this.coordinates.length;
  var myEnd = this.appendFlatCoordinates(
      flatCoordinates, offset, end, stride, false);
  var moveToLineToInstruction =
      [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
  this.instructions.push(moveToLineToInstruction);
  this.hitDetectionInstructions.push(moveToLineToInstruction);
  return end;
};


/**
 * @inheritDoc
 */
ol.render.canvas.LineStringReplay.prototype.getBufferedMaxExtent = function() {
  if (!this.bufferedMaxExtent_) {
    this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
    if (this.maxLineWidth > 0) {
      var width = this.resolution * (this.maxLineWidth + 1) / 2;
      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
    }
  }
  return this.bufferedMaxExtent_;
};


/**
 * @private
 */
ol.render.canvas.LineStringReplay.prototype.setStrokeStyle_ = function() {
  var state = this.state_;
  var strokeStyle = state.strokeStyle;
  var lineCap = state.lineCap;
  var lineDash = state.lineDash;
  var lineJoin = state.lineJoin;
  var lineWidth = state.lineWidth;
  var miterLimit = state.miterLimit;
  goog.DEBUG && console.assert(strokeStyle !== undefined,
      'strokeStyle should be defined');
  goog.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined');
  goog.DEBUG && console.assert(lineDash, 'lineDash should not be null');
  goog.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined');
  goog.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined');
  goog.DEBUG && console.assert(miterLimit !== undefined, 'miterLimit should be defined');
  if (state.currentStrokeStyle != strokeStyle ||
      state.currentLineCap != lineCap ||
      !ol.array.equals(state.currentLineDash, lineDash) ||
      state.currentLineJoin != lineJoin ||
      state.currentLineWidth != lineWidth ||
      state.currentMiterLimit != miterLimit) {
    if (state.lastStroke != this.coordinates.length) {
      this.instructions.push(
          [ol.render.canvas.Instruction.STROKE]);
      state.lastStroke = this.coordinates.length;
    }
    this.instructions.push(
        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
         strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash],
        [ol.render.canvas.Instruction.BEGIN_PATH]);
    state.currentStrokeStyle = strokeStyle;
    state.currentLineCap = lineCap;
    state.currentLineDash = lineDash;
    state.currentLineJoin = lineJoin;
    state.currentLineWidth = lineWidth;
    state.currentMiterLimit = miterLimit;
  }
};


/**
 * @inheritDoc
 */
ol.render.canvas.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) {
  var state = this.state_;
  goog.DEBUG && console.assert(state, 'state should not be null');
  var strokeStyle = state.strokeStyle;
  var lineWidth = state.lineWidth;
  if (strokeStyle === undefined || lineWidth === undefined) {
    return;
  }
  this.setStrokeStyle_();
  this.beginGeometry(lineStringGeometry, feature);
  this.hitDetectionInstructions.push(
      [ol.render.canvas.Instruction.SET_STROKE_STYLE,
       state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
       state.miterLimit, state.lineDash],
      [ol.render.canvas.Instruction.BEGIN_PATH]);
  var flatCoordinates = lineStringGeometry.getFlatCoordinates();
  var stride = lineStringGeometry.getStride();
  this.drawFlatCoordinates_(
      flatCoordinates, 0, flatCoordinates.length, stride);
  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
  this.endGeometry(lineStringGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) {
  var state = this.state_;
  goog.DEBUG && console.assert(state, 'state should not be null');
  var strokeStyle = state.strokeStyle;
  var lineWidth = state.lineWidth;
  if (strokeStyle === undefined || lineWidth === undefined) {
    return;
  }
  this.setStrokeStyle_();
  this.beginGeometry(multiLineStringGeometry, feature);
  this.hitDetectionInstructions.push(
      [ol.render.canvas.Instruction.SET_STROKE_STYLE,
       state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
       state.miterLimit, state.lineDash],
      [ol.render.canvas.Instruction.BEGIN_PATH]);
  var ends = multiLineStringGeometry.getEnds();
  var flatCoordinates = multiLineStringGeometry.getFlatCoordinates();
  var stride = multiLineStringGeometry.getStride();
  var offset = 0;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    offset = this.drawFlatCoordinates_(
        flatCoordinates, offset, ends[i], stride);
  }
  this.hitDetectionInstructions.push([ol.render.canvas.Instruction.STROKE]);
  this.endGeometry(multiLineStringGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.LineStringReplay.prototype.finish = function() {
  var state = this.state_;
  goog.DEBUG && console.assert(state, 'state should not be null');
  if (state.lastStroke != this.coordinates.length) {
    this.instructions.push([ol.render.canvas.Instruction.STROKE]);
  }
  this.reverseHitDetectionInstructions_();
  this.state_ = null;
};


/**
 * @inheritDoc
 */
ol.render.canvas.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
  goog.DEBUG && console.assert(this.state_, 'this.state_ should not be null');
  goog.DEBUG && console.assert(!fillStyle, 'fillStyle should be null');
  goog.DEBUG && console.assert(strokeStyle, 'strokeStyle should not be null');
  var strokeStyleColor = strokeStyle.getColor();
  this.state_.strokeStyle = ol.color.asString(strokeStyleColor ?
      strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
  var strokeStyleLineCap = strokeStyle.getLineCap();
  this.state_.lineCap = strokeStyleLineCap !== undefined ?
      strokeStyleLineCap : ol.render.canvas.defaultLineCap;
  var strokeStyleLineDash = strokeStyle.getLineDash();
  this.state_.lineDash = strokeStyleLineDash ?
      strokeStyleLineDash : ol.render.canvas.defaultLineDash;
  var strokeStyleLineJoin = strokeStyle.getLineJoin();
  this.state_.lineJoin = strokeStyleLineJoin !== undefined ?
      strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
  var strokeStyleWidth = strokeStyle.getWidth();
  this.state_.lineWidth = strokeStyleWidth !== undefined ?
      strokeStyleWidth : ol.render.canvas.defaultLineWidth;
  var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
  this.state_.miterLimit = strokeStyleMiterLimit !== undefined ?
      strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;

  if (this.state_.lineWidth > this.maxLineWidth) {
    this.maxLineWidth = this.state_.lineWidth;
    // invalidate the buffered max extent cache
    this.bufferedMaxExtent_ = null;
  }
};


/**
 * @constructor
 * @extends {ol.render.canvas.Replay}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Maximum extent.
 * @param {number} resolution Resolution.
 * @param {boolean} overlaps The replay can have overlapping geometries.
 * @protected
 * @struct
 */
ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution, overlaps) {

  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);

  /**
   * @private
   * @type {{currentFillStyle: (ol.ColorLike|undefined),
   *         currentStrokeStyle: (string|undefined),
   *         currentLineCap: (string|undefined),
   *         currentLineDash: Array.<number>,
   *         currentLineJoin: (string|undefined),
   *         currentLineWidth: (number|undefined),
   *         currentMiterLimit: (number|undefined),
   *         fillStyle: (ol.ColorLike|undefined),
   *         strokeStyle: (string|undefined),
   *         lineCap: (string|undefined),
   *         lineDash: Array.<number>,
   *         lineJoin: (string|undefined),
   *         lineWidth: (number|undefined),
   *         miterLimit: (number|undefined)}|null}
   */
  this.state_ = {
    currentFillStyle: undefined,
    currentStrokeStyle: undefined,
    currentLineCap: undefined,
    currentLineDash: null,
    currentLineJoin: undefined,
    currentLineWidth: undefined,
    currentMiterLimit: undefined,
    fillStyle: undefined,
    strokeStyle: undefined,
    lineCap: undefined,
    lineDash: null,
    lineJoin: undefined,
    lineWidth: undefined,
    miterLimit: undefined
  };

};
ol.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @private
 * @return {number} End.
 */
ol.render.canvas.PolygonReplay.prototype.drawFlatCoordinatess_ = function(flatCoordinates, offset, ends, stride) {
  var state = this.state_;
  var fill = state.fillStyle !== undefined;
  var stroke = state.strokeStyle != undefined;
  var numEnds = ends.length;
  if (!fill && !stroke) {
    return ends[numEnds - 1];
  }
  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
  this.instructions.push(beginPathInstruction);
  this.hitDetectionInstructions.push(beginPathInstruction);
  for (var i = 0; i < numEnds; ++i) {
    var end = ends[i];
    var myBegin = this.coordinates.length;
    var myEnd = this.appendFlatCoordinates(
        flatCoordinates, offset, end, stride, true);
    var moveToLineToInstruction =
        [ol.render.canvas.Instruction.MOVE_TO_LINE_TO, myBegin, myEnd];
    this.instructions.push(moveToLineToInstruction);
    this.hitDetectionInstructions.push(moveToLineToInstruction);
    if (stroke) {
      // Performance optimization: only call closePath() when we have a stroke.
      // Otherwise the ring is closed already (see appendFlatCoordinates above).
      var closePathInstruction = [ol.render.canvas.Instruction.CLOSE_PATH];
      this.instructions.push(closePathInstruction);
      this.hitDetectionInstructions.push(closePathInstruction);
    }
    offset = end;
  }
  var fillInstruction = [ol.render.canvas.Instruction.FILL];
  this.hitDetectionInstructions.push(fillInstruction);
  if (fill) {
    this.instructions.push(fillInstruction);
  }
  if (stroke) {
    goog.DEBUG && console.assert(state.lineWidth !== undefined,
        'state.lineWidth should be defined');
    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
    this.instructions.push(strokeInstruction);
    this.hitDetectionInstructions.push(strokeInstruction);
  }
  return offset;
};


/**
 * @inheritDoc
 */
ol.render.canvas.PolygonReplay.prototype.drawCircle = function(circleGeometry, feature) {
  var state = this.state_;
  goog.DEBUG && console.assert(state, 'state should not be null');
  var fillStyle = state.fillStyle;
  var strokeStyle = state.strokeStyle;
  if (fillStyle === undefined && strokeStyle === undefined) {
    return;
  }
  if (strokeStyle !== undefined) {
    goog.DEBUG && console.assert(state.lineWidth !== undefined,
        'state.lineWidth should be defined');
  }
  this.setFillStrokeStyles_();
  this.beginGeometry(circleGeometry, feature);
  // always fill the circle for hit detection
  this.hitDetectionInstructions.push(
      [ol.render.canvas.Instruction.SET_FILL_STYLE,
       ol.color.asString(ol.render.canvas.defaultFillStyle)]);
  if (state.strokeStyle !== undefined) {
    this.hitDetectionInstructions.push(
        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
         state.miterLimit, state.lineDash]);
  }
  var flatCoordinates = circleGeometry.getFlatCoordinates();
  var stride = circleGeometry.getStride();
  var myBegin = this.coordinates.length;
  this.appendFlatCoordinates(
      flatCoordinates, 0, flatCoordinates.length, stride, false);
  var beginPathInstruction = [ol.render.canvas.Instruction.BEGIN_PATH];
  var circleInstruction = [ol.render.canvas.Instruction.CIRCLE, myBegin];
  this.instructions.push(beginPathInstruction, circleInstruction);
  this.hitDetectionInstructions.push(beginPathInstruction, circleInstruction);
  var fillInstruction = [ol.render.canvas.Instruction.FILL];
  this.hitDetectionInstructions.push(fillInstruction);
  if (state.fillStyle !== undefined) {
    this.instructions.push(fillInstruction);
  }
  if (state.strokeStyle !== undefined) {
    goog.DEBUG && console.assert(state.lineWidth !== undefined,
        'state.lineWidth should be defined');
    var strokeInstruction = [ol.render.canvas.Instruction.STROKE];
    this.instructions.push(strokeInstruction);
    this.hitDetectionInstructions.push(strokeInstruction);
  }
  this.endGeometry(circleGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) {
  var state = this.state_;
  goog.DEBUG && console.assert(state, 'state should not be null');
  var fillStyle = state.fillStyle;
  var strokeStyle = state.strokeStyle;
  if (fillStyle === undefined && strokeStyle === undefined) {
    return;
  }
  if (strokeStyle !== undefined) {
    goog.DEBUG && console.assert(state.lineWidth !== undefined,
        'state.lineWidth should be defined');
  }
  this.setFillStrokeStyles_();
  this.beginGeometry(polygonGeometry, feature);
  // always fill the polygon for hit detection
  this.hitDetectionInstructions.push(
      [ol.render.canvas.Instruction.SET_FILL_STYLE,
       ol.color.asString(ol.render.canvas.defaultFillStyle)]);
  if (state.strokeStyle !== undefined) {
    this.hitDetectionInstructions.push(
        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
         state.miterLimit, state.lineDash]);
  }
  var ends = polygonGeometry.getEnds();
  var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
  var stride = polygonGeometry.getStride();
  this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
  this.endGeometry(polygonGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) {
  var state = this.state_;
  goog.DEBUG && console.assert(state, 'state should not be null');
  var fillStyle = state.fillStyle;
  var strokeStyle = state.strokeStyle;
  if (fillStyle === undefined && strokeStyle === undefined) {
    return;
  }
  if (strokeStyle !== undefined) {
    goog.DEBUG && console.assert(state.lineWidth !== undefined,
        'state.lineWidth should be defined');
  }
  this.setFillStrokeStyles_();
  this.beginGeometry(multiPolygonGeometry, feature);
  // always fill the multi-polygon for hit detection
  this.hitDetectionInstructions.push(
      [ol.render.canvas.Instruction.SET_FILL_STYLE,
       ol.color.asString(ol.render.canvas.defaultFillStyle)]);
  if (state.strokeStyle !== undefined) {
    this.hitDetectionInstructions.push(
        [ol.render.canvas.Instruction.SET_STROKE_STYLE,
         state.strokeStyle, state.lineWidth, state.lineCap, state.lineJoin,
         state.miterLimit, state.lineDash]);
  }
  var endss = multiPolygonGeometry.getEndss();
  var flatCoordinates = multiPolygonGeometry.getOrientedFlatCoordinates();
  var stride = multiPolygonGeometry.getStride();
  var offset = 0;
  var i, ii;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    offset = this.drawFlatCoordinatess_(
        flatCoordinates, offset, endss[i], stride);
  }
  this.endGeometry(multiPolygonGeometry, feature);
};


/**
 * @inheritDoc
 */
ol.render.canvas.PolygonReplay.prototype.finish = function() {
  goog.DEBUG && console.assert(this.state_, 'this.state_ should not be null');
  this.reverseHitDetectionInstructions_();
  this.state_ = null;
  // We want to preserve topology when drawing polygons.  Polygons are
  // simplified using quantization and point elimination. However, we might
  // have received a mix of quantized and non-quantized geometries, so ensure
  // that all are quantized by quantizing all coordinates in the batch.
  var tolerance = this.tolerance;
  if (tolerance !== 0) {
    var coordinates = this.coordinates;
    var i, ii;
    for (i = 0, ii = coordinates.length; i < ii; ++i) {
      coordinates[i] = ol.geom.flat.simplify.snap(coordinates[i], tolerance);
    }
  }
};


/**
 * @inheritDoc
 */
ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
  if (!this.bufferedMaxExtent_) {
    this.bufferedMaxExtent_ = ol.extent.clone(this.maxExtent);
    if (this.maxLineWidth > 0) {
      var width = this.resolution * (this.maxLineWidth + 1) / 2;
      ol.extent.buffer(this.bufferedMaxExtent_, width, this.bufferedMaxExtent_);
    }
  }
  return this.bufferedMaxExtent_;
};


/**
 * @inheritDoc
 */
ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) {
  goog.DEBUG && console.assert(this.state_, 'this.state_ should not be null');
  goog.DEBUG && console.assert(fillStyle || strokeStyle,
      'fillStyle or strokeStyle should not be null');
  var state = this.state_;
  if (fillStyle) {
    var fillStyleColor = fillStyle.getColor();
    state.fillStyle = ol.colorlike.asColorLike(fillStyleColor ?
        fillStyleColor : ol.render.canvas.defaultFillStyle);
  } else {
    state.fillStyle = undefined;
  }
  if (strokeStyle) {
    var strokeStyleColor = strokeStyle.getColor();
    state.strokeStyle = ol.color.asString(strokeStyleColor ?
        strokeStyleColor : ol.render.canvas.defaultStrokeStyle);
    var strokeStyleLineCap = strokeStyle.getLineCap();
    state.lineCap = strokeStyleLineCap !== undefined ?
        strokeStyleLineCap : ol.render.canvas.defaultLineCap;
    var strokeStyleLineDash = strokeStyle.getLineDash();
    state.lineDash = strokeStyleLineDash ?
        strokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
    var strokeStyleLineJoin = strokeStyle.getLineJoin();
    state.lineJoin = strokeStyleLineJoin !== undefined ?
        strokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
    var strokeStyleWidth = strokeStyle.getWidth();
    state.lineWidth = strokeStyleWidth !== undefined ?
        strokeStyleWidth : ol.render.canvas.defaultLineWidth;
    var strokeStyleMiterLimit = strokeStyle.getMiterLimit();
    state.miterLimit = strokeStyleMiterLimit !== undefined ?
        strokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;

    if (state.lineWidth > this.maxLineWidth) {
      this.maxLineWidth = state.lineWidth;
      // invalidate the buffered max extent cache
      this.bufferedMaxExtent_ = null;
    }
  } else {
    state.strokeStyle = undefined;
    state.lineCap = undefined;
    state.lineDash = null;
    state.lineJoin = undefined;
    state.lineWidth = undefined;
    state.miterLimit = undefined;
  }
};


/**
 * @private
 */
ol.render.canvas.PolygonReplay.prototype.setFillStrokeStyles_ = function() {
  var state = this.state_;
  var fillStyle = state.fillStyle;
  var strokeStyle = state.strokeStyle;
  var lineCap = state.lineCap;
  var lineDash = state.lineDash;
  var lineJoin = state.lineJoin;
  var lineWidth = state.lineWidth;
  var miterLimit = state.miterLimit;
  if (fillStyle !== undefined && state.currentFillStyle != fillStyle) {
    this.instructions.push(
        [ol.render.canvas.Instruction.SET_FILL_STYLE, fillStyle]);
    state.currentFillStyle = state.fillStyle;
  }
  if (strokeStyle !== undefined) {
    goog.DEBUG && console.assert(lineCap !== undefined, 'lineCap should be defined');
    goog.DEBUG && console.assert(lineDash, 'lineDash should not be null');
    goog.DEBUG && console.assert(lineJoin !== undefined, 'lineJoin should be defined');
    goog.DEBUG && console.assert(lineWidth !== undefined, 'lineWidth should be defined');
    goog.DEBUG && console.assert(miterLimit !== undefined,
        'miterLimit should be defined');
    if (state.currentStrokeStyle != strokeStyle ||
        state.currentLineCap != lineCap ||
        state.currentLineDash != lineDash ||
        state.currentLineJoin != lineJoin ||
        state.currentLineWidth != lineWidth ||
        state.currentMiterLimit != miterLimit) {
      this.instructions.push(
          [ol.render.canvas.Instruction.SET_STROKE_STYLE,
           strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, lineDash]);
      state.currentStrokeStyle = strokeStyle;
      state.currentLineCap = lineCap;
      state.currentLineDash = lineDash;
      state.currentLineJoin = lineJoin;
      state.currentLineWidth = lineWidth;
      state.currentMiterLimit = miterLimit;
    }
  }
};


/**
 * @constructor
 * @extends {ol.render.canvas.Replay}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Maximum extent.
 * @param {number} resolution Resolution.
 * @param {boolean} overlaps The replay can have overlapping geometries.
 * @protected
 * @struct
 */
ol.render.canvas.TextReplay = function(tolerance, maxExtent, resolution, overlaps) {

  ol.render.canvas.Replay.call(this, tolerance, maxExtent, resolution, overlaps);

  /**
   * @private
   * @type {?ol.CanvasFillState}
   */
  this.replayFillState_ = null;

  /**
   * @private
   * @type {?ol.CanvasStrokeState}
   */
  this.replayStrokeState_ = null;

  /**
   * @private
   * @type {?ol.CanvasTextState}
   */
  this.replayTextState_ = null;

  /**
   * @private
   * @type {string}
   */
  this.text_ = '';

  /**
   * @private
   * @type {number}
   */
  this.textOffsetX_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.textOffsetY_ = 0;

  /**
   * @private
   * @type {boolean|undefined}
   */
  this.textRotateWithView_ = undefined;

  /**
   * @private
   * @type {number}
   */
  this.textRotation_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.textScale_ = 0;

  /**
   * @private
   * @type {?ol.CanvasFillState}
   */
  this.textFillState_ = null;

  /**
   * @private
   * @type {?ol.CanvasStrokeState}
   */
  this.textStrokeState_ = null;

  /**
   * @private
   * @type {?ol.CanvasTextState}
   */
  this.textState_ = null;

};
ol.inherits(ol.render.canvas.TextReplay, ol.render.canvas.Replay);


/**
 * @inheritDoc
 */
ol.render.canvas.TextReplay.prototype.drawText = function(flatCoordinates, offset, end, stride, geometry, feature) {
  if (this.text_ === '' || !this.textState_ ||
      (!this.textFillState_ && !this.textStrokeState_)) {
    return;
  }
  if (this.textFillState_) {
    this.setReplayFillState_(this.textFillState_);
  }
  if (this.textStrokeState_) {
    this.setReplayStrokeState_(this.textStrokeState_);
  }
  this.setReplayTextState_(this.textState_);
  this.beginGeometry(geometry, feature);
  var myBegin = this.coordinates.length;
  var myEnd =
      this.appendFlatCoordinates(flatCoordinates, offset, end, stride, false);
  var fill = !!this.textFillState_;
  var stroke = !!this.textStrokeState_;
  var drawTextInstruction = [
    ol.render.canvas.Instruction.DRAW_TEXT, myBegin, myEnd, this.text_,
    this.textOffsetX_, this.textOffsetY_, this.textRotation_, this.textScale_,
    fill, stroke, this.textRotateWithView_];
  this.instructions.push(drawTextInstruction);
  this.hitDetectionInstructions.push(drawTextInstruction);
  this.endGeometry(geometry, feature);
};


/**
 * @param {ol.CanvasFillState} fillState Fill state.
 * @private
 */
ol.render.canvas.TextReplay.prototype.setReplayFillState_ = function(fillState) {
  var replayFillState = this.replayFillState_;
  if (replayFillState &&
      replayFillState.fillStyle == fillState.fillStyle) {
    return;
  }
  var setFillStyleInstruction =
      [ol.render.canvas.Instruction.SET_FILL_STYLE, fillState.fillStyle];
  this.instructions.push(setFillStyleInstruction);
  this.hitDetectionInstructions.push(setFillStyleInstruction);
  if (!replayFillState) {
    this.replayFillState_ = {
      fillStyle: fillState.fillStyle
    };
  } else {
    replayFillState.fillStyle = fillState.fillStyle;
  }
};


/**
 * @param {ol.CanvasStrokeState} strokeState Stroke state.
 * @private
 */
ol.render.canvas.TextReplay.prototype.setReplayStrokeState_ = function(strokeState) {
  var replayStrokeState = this.replayStrokeState_;
  if (replayStrokeState &&
      replayStrokeState.lineCap == strokeState.lineCap &&
      replayStrokeState.lineDash == strokeState.lineDash &&
      replayStrokeState.lineJoin == strokeState.lineJoin &&
      replayStrokeState.lineWidth == strokeState.lineWidth &&
      replayStrokeState.miterLimit == strokeState.miterLimit &&
      replayStrokeState.strokeStyle == strokeState.strokeStyle) {
    return;
  }
  var setStrokeStyleInstruction = [
    ol.render.canvas.Instruction.SET_STROKE_STYLE, strokeState.strokeStyle,
    strokeState.lineWidth, strokeState.lineCap, strokeState.lineJoin,
    strokeState.miterLimit, strokeState.lineDash, false
  ];
  this.instructions.push(setStrokeStyleInstruction);
  this.hitDetectionInstructions.push(setStrokeStyleInstruction);
  if (!replayStrokeState) {
    this.replayStrokeState_ = {
      lineCap: strokeState.lineCap,
      lineDash: strokeState.lineDash,
      lineJoin: strokeState.lineJoin,
      lineWidth: strokeState.lineWidth,
      miterLimit: strokeState.miterLimit,
      strokeStyle: strokeState.strokeStyle
    };
  } else {
    replayStrokeState.lineCap = strokeState.lineCap;
    replayStrokeState.lineDash = strokeState.lineDash;
    replayStrokeState.lineJoin = strokeState.lineJoin;
    replayStrokeState.lineWidth = strokeState.lineWidth;
    replayStrokeState.miterLimit = strokeState.miterLimit;
    replayStrokeState.strokeStyle = strokeState.strokeStyle;
  }
};


/**
 * @param {ol.CanvasTextState} textState Text state.
 * @private
 */
ol.render.canvas.TextReplay.prototype.setReplayTextState_ = function(textState) {
  var replayTextState = this.replayTextState_;
  if (replayTextState &&
      replayTextState.font == textState.font &&
      replayTextState.textAlign == textState.textAlign &&
      replayTextState.textBaseline == textState.textBaseline) {
    return;
  }
  var setTextStyleInstruction = [ol.render.canvas.Instruction.SET_TEXT_STYLE,
    textState.font, textState.textAlign, textState.textBaseline];
  this.instructions.push(setTextStyleInstruction);
  this.hitDetectionInstructions.push(setTextStyleInstruction);
  if (!replayTextState) {
    this.replayTextState_ = {
      font: textState.font,
      textAlign: textState.textAlign,
      textBaseline: textState.textBaseline
    };
  } else {
    replayTextState.font = textState.font;
    replayTextState.textAlign = textState.textAlign;
    replayTextState.textBaseline = textState.textBaseline;
  }
};


/**
 * @inheritDoc
 */
ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
  if (!textStyle) {
    this.text_ = '';
  } else {
    var textFillStyle = textStyle.getFill();
    if (!textFillStyle) {
      this.textFillState_ = null;
    } else {
      var textFillStyleColor = textFillStyle.getColor();
      var fillStyle = ol.colorlike.asColorLike(textFillStyleColor ?
          textFillStyleColor : ol.render.canvas.defaultFillStyle);
      if (!this.textFillState_) {
        this.textFillState_ = {
          fillStyle: fillStyle
        };
      } else {
        var textFillState = this.textFillState_;
        textFillState.fillStyle = fillStyle;
      }
    }
    var textStrokeStyle = textStyle.getStroke();
    if (!textStrokeStyle) {
      this.textStrokeState_ = null;
    } else {
      var textStrokeStyleColor = textStrokeStyle.getColor();
      var textStrokeStyleLineCap = textStrokeStyle.getLineCap();
      var textStrokeStyleLineDash = textStrokeStyle.getLineDash();
      var textStrokeStyleLineJoin = textStrokeStyle.getLineJoin();
      var textStrokeStyleWidth = textStrokeStyle.getWidth();
      var textStrokeStyleMiterLimit = textStrokeStyle.getMiterLimit();
      var lineCap = textStrokeStyleLineCap !== undefined ?
          textStrokeStyleLineCap : ol.render.canvas.defaultLineCap;
      var lineDash = textStrokeStyleLineDash ?
          textStrokeStyleLineDash.slice() : ol.render.canvas.defaultLineDash;
      var lineJoin = textStrokeStyleLineJoin !== undefined ?
          textStrokeStyleLineJoin : ol.render.canvas.defaultLineJoin;
      var lineWidth = textStrokeStyleWidth !== undefined ?
          textStrokeStyleWidth : ol.render.canvas.defaultLineWidth;
      var miterLimit = textStrokeStyleMiterLimit !== undefined ?
          textStrokeStyleMiterLimit : ol.render.canvas.defaultMiterLimit;
      var strokeStyle = ol.color.asString(textStrokeStyleColor ?
          textStrokeStyleColor : ol.render.canvas.defaultStrokeStyle);
      if (!this.textStrokeState_) {
        this.textStrokeState_ = {
          lineCap: lineCap,
          lineDash: lineDash,
          lineJoin: lineJoin,
          lineWidth: lineWidth,
          miterLimit: miterLimit,
          strokeStyle: strokeStyle
        };
      } else {
        var textStrokeState = this.textStrokeState_;
        textStrokeState.lineCap = lineCap;
        textStrokeState.lineDash = lineDash;
        textStrokeState.lineJoin = lineJoin;
        textStrokeState.lineWidth = lineWidth;
        textStrokeState.miterLimit = miterLimit;
        textStrokeState.strokeStyle = strokeStyle;
      }
    }
    var textFont = textStyle.getFont();
    var textOffsetX = textStyle.getOffsetX();
    var textOffsetY = textStyle.getOffsetY();
    var textRotateWithView = textStyle.getRotateWithView();
    var textRotation = textStyle.getRotation();
    var textScale = textStyle.getScale();
    var textText = textStyle.getText();
    var textTextAlign = textStyle.getTextAlign();
    var textTextBaseline = textStyle.getTextBaseline();
    var font = textFont !== undefined ?
        textFont : ol.render.canvas.defaultFont;
    var textAlign = textTextAlign !== undefined ?
        textTextAlign : ol.render.canvas.defaultTextAlign;
    var textBaseline = textTextBaseline !== undefined ?
        textTextBaseline : ol.render.canvas.defaultTextBaseline;
    if (!this.textState_) {
      this.textState_ = {
        font: font,
        textAlign: textAlign,
        textBaseline: textBaseline
      };
    } else {
      var textState = this.textState_;
      textState.font = font;
      textState.textAlign = textAlign;
      textState.textBaseline = textBaseline;
    }
    this.text_ = textText !== undefined ? textText : '';
    this.textOffsetX_ = textOffsetX !== undefined ? textOffsetX : 0;
    this.textOffsetY_ = textOffsetY !== undefined ? textOffsetY : 0;
    this.textRotateWithView_ = textRotateWithView !== undefined ? textRotateWithView : false;
    this.textRotation_ = textRotation !== undefined ? textRotation : 0;
    this.textScale_ = textScale !== undefined ? textScale : 1;
  }
};


/**
 * @constructor
 * @extends {ol.render.ReplayGroup}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Max extent.
 * @param {number} resolution Resolution.
 * @param {boolean} overlaps The replay group can have overlapping geometries.
 * @param {number=} opt_renderBuffer Optional rendering buffer.
 * @struct
 */
ol.render.canvas.ReplayGroup = function(
    tolerance, maxExtent, resolution, overlaps, opt_renderBuffer) {
  ol.render.ReplayGroup.call(this);

  /**
   * @private
   * @type {number}
   */
  this.tolerance_ = tolerance;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.maxExtent_ = maxExtent;

  /**
   * @private
   * @type {boolean}
   */
  this.overlaps_ = overlaps;

  /**
   * @private
   * @type {number}
   */
  this.resolution_ = resolution;

  /**
   * @private
   * @type {number|undefined}
   */
  this.renderBuffer_ = opt_renderBuffer;

  /**
   * @private
   * @type {!Object.<string,
   *        Object.<ol.render.ReplayType, ol.render.canvas.Replay>>}
   */
  this.replaysByZIndex_ = {};

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.hitDetectionContext_ = ol.dom.createCanvasContext2D(1, 1);

  /**
   * @private
   * @type {ol.Transform}
   */
  this.hitDetectionTransform_ = ol.transform.create();

};
ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);


/**
 * FIXME empty description for jsdoc
 */
ol.render.canvas.ReplayGroup.prototype.finish = function() {
  var zKey;
  for (zKey in this.replaysByZIndex_) {
    var replays = this.replaysByZIndex_[zKey];
    var replayKey;
    for (replayKey in replays) {
      replays[replayKey].finish();
    }
  }
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *     to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
 *     callback.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
    coordinate, resolution, rotation, skippedFeaturesHash, callback) {

  var transform = ol.transform.compose(this.hitDetectionTransform_,
      0.5, 0.5,
      1 / resolution, -1 / resolution,
      -rotation,
      -coordinate[0], -coordinate[1]);

  var context = this.hitDetectionContext_;
  context.clearRect(0, 0, 1, 1);

  /**
   * @type {ol.Extent}
   */
  var hitExtent;
  if (this.renderBuffer_ !== undefined) {
    hitExtent = ol.extent.createEmpty();
    ol.extent.extendCoordinate(hitExtent, coordinate);
    ol.extent.buffer(hitExtent, resolution * this.renderBuffer_, hitExtent);
  }

  return this.replayHitDetection_(context, transform, rotation,
      skippedFeaturesHash,
      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {?} Callback result.
       */
      function(feature) {
        var imageData = context.getImageData(0, 0, 1, 1).data;
        if (imageData[3] > 0) {
          var result = callback(feature);
          if (result) {
            return result;
          }
          context.clearRect(0, 0, 1, 1);
        }
      }, hitExtent);
};


/**
 * @inheritDoc
 */
ol.render.canvas.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {
  var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0';
  var replays = this.replaysByZIndex_[zIndexKey];
  if (replays === undefined) {
    replays = {};
    this.replaysByZIndex_[zIndexKey] = replays;
  }
  var replay = replays[replayType];
  if (replay === undefined) {
    var Constructor = ol.render.canvas.BATCH_CONSTRUCTORS_[replayType];
    goog.DEBUG && console.assert(Constructor !== undefined,
        replayType +
        ' constructor missing from ol.render.canvas.BATCH_CONSTRUCTORS_');
    replay = new Constructor(this.tolerance_, this.maxExtent_,
        this.resolution_, this.overlaps_);
    replays[replayType] = replay;
  }
  return replay;
};


/**
 * @inheritDoc
 */
ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
  return ol.obj.isEmpty(this.replaysByZIndex_);
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Transform} transform Transform.
 * @param {number} viewRotation View rotation.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *     to skip.
 * @param {Array.<ol.render.ReplayType>=} opt_replayTypes Ordered replay types
 *     to replay. Default is {@link ol.render.replay.ORDER}
 */
ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio,
    transform, viewRotation, skippedFeaturesHash, opt_replayTypes) {

  /** @type {Array.<number>} */
  var zs = Object.keys(this.replaysByZIndex_).map(Number);
  zs.sort(ol.array.numberSafeCompareFunction);

  // setup clipping so that the parts of over-simplified geometries are not
  // visible outside the current extent when panning
  var maxExtent = this.maxExtent_;
  var minX = maxExtent[0];
  var minY = maxExtent[1];
  var maxX = maxExtent[2];
  var maxY = maxExtent[3];
  var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
  ol.geom.flat.transform.transform2D(
      flatClipCoords, 0, 8, 2, transform, flatClipCoords);
  context.save();
  context.beginPath();
  context.moveTo(flatClipCoords[0], flatClipCoords[1]);
  context.lineTo(flatClipCoords[2], flatClipCoords[3]);
  context.lineTo(flatClipCoords[4], flatClipCoords[5]);
  context.lineTo(flatClipCoords[6], flatClipCoords[7]);
  context.clip();

  var replayTypes = opt_replayTypes ? opt_replayTypes : ol.render.replay.ORDER;
  var i, ii, j, jj, replays, replay;
  for (i = 0, ii = zs.length; i < ii; ++i) {
    replays = this.replaysByZIndex_[zs[i].toString()];
    for (j = 0, jj = replayTypes.length; j < jj; ++j) {
      replay = replays[replayTypes[j]];
      if (replay !== undefined) {
        replay.replay(context, pixelRatio, transform, viewRotation,
            skippedFeaturesHash);
      }
    }
  }

  context.restore();
};


/**
 * @private
 * @param {CanvasRenderingContext2D} context Context.
 * @param {ol.Transform} transform Transform.
 * @param {number} viewRotation View rotation.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *     to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T} featureCallback
 *     Feature callback.
 * @param {ol.Extent=} opt_hitExtent Only check features that intersect this
 *     extent.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.canvas.ReplayGroup.prototype.replayHitDetection_ = function(
    context, transform, viewRotation, skippedFeaturesHash,
    featureCallback, opt_hitExtent) {
  /** @type {Array.<number>} */
  var zs = Object.keys(this.replaysByZIndex_).map(Number);
  zs.sort(function(a, b) {
    return b - a;
  });

  var i, ii, j, replays, replay, result;
  for (i = 0, ii = zs.length; i < ii; ++i) {
    replays = this.replaysByZIndex_[zs[i].toString()];
    for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) {
      replay = replays[ol.render.replay.ORDER[j]];
      if (replay !== undefined) {
        result = replay.replayHitDetection(context, transform, viewRotation,
            skippedFeaturesHash, featureCallback, opt_hitExtent);
        if (result) {
          return result;
        }
      }
    }
  }
  return undefined;
};


/**
 * @const
 * @private
 * @type {Object.<ol.render.ReplayType,
 *                function(new: ol.render.canvas.Replay, number, ol.Extent,
 *                number, boolean)>}
 */
ol.render.canvas.BATCH_CONSTRUCTORS_ = {
  'Image': ol.render.canvas.ImageReplay,
  'LineString': ol.render.canvas.LineStringReplay,
  'Polygon': ol.render.canvas.PolygonReplay,
  'Text': ol.render.canvas.TextReplay
};

goog.provide('ol.renderer.vector');

goog.require('ol');
goog.require('ol.render.ReplayType');
goog.require('ol.style.ImageState');


/**
 * @param {ol.Feature|ol.render.Feature} feature1 Feature 1.
 * @param {ol.Feature|ol.render.Feature} feature2 Feature 2.
 * @return {number} Order.
 */
ol.renderer.vector.defaultOrder = function(feature1, feature2) {
  return ol.getUid(feature1) - ol.getUid(feature2);
};


/**
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @return {number} Squared pixel tolerance.
 */
ol.renderer.vector.getSquaredTolerance = function(resolution, pixelRatio) {
  var tolerance = ol.renderer.vector.getTolerance(resolution, pixelRatio);
  return tolerance * tolerance;
};


/**
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @return {number} Pixel tolerance.
 */
ol.renderer.vector.getTolerance = function(resolution, pixelRatio) {
  return ol.SIMPLIFY_TOLERANCE * resolution / pixelRatio;
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.Circle} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) {
  var fillStyle = style.getFill();
  var strokeStyle = style.getStroke();
  if (fillStyle || strokeStyle) {
    var polygonReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.POLYGON);
    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
    polygonReplay.drawCircle(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    textReplay.drawText(geometry.getCenter(), 0, 2, 2, geometry, feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {ol.style.Style} style Style.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {function(this: T, ol.events.Event)} listener Listener function.
 * @param {T} thisArg Value to use as `this` when executing `listener`.
 * @return {boolean} `true` if style is loading.
 * @template T
 */
ol.renderer.vector.renderFeature = function(
    replayGroup, feature, style, squaredTolerance, listener, thisArg) {
  var loading = false;
  var imageStyle, imageState;
  imageStyle = style.getImage();
  if (imageStyle) {
    imageState = imageStyle.getImageState();
    if (imageState == ol.style.ImageState.LOADED ||
        imageState == ol.style.ImageState.ERROR) {
      imageStyle.unlistenImageChange(listener, thisArg);
    } else {
      if (imageState == ol.style.ImageState.IDLE) {
        imageStyle.load();
      }
      imageState = imageStyle.getImageState();
      goog.DEBUG && console.assert(imageState == ol.style.ImageState.LOADING,
          'imageState should be LOADING');
      imageStyle.listenImageChange(listener, thisArg);
      loading = true;
    }
  }
  ol.renderer.vector.renderFeature_(replayGroup, feature, style,
      squaredTolerance);
  return loading;
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {ol.style.Style} style Style.
 * @param {number} squaredTolerance Squared tolerance.
 * @private
 */
ol.renderer.vector.renderFeature_ = function(
    replayGroup, feature, style, squaredTolerance) {
  var geometry = style.getGeometryFunction()(feature);
  if (!geometry) {
    return;
  }
  var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
  var geometryRenderer =
      ol.renderer.vector.GEOMETRY_RENDERERS_[simplifiedGeometry.getType()];
  geometryRenderer(replayGroup, simplifiedGeometry, style, feature);
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.GeometryCollection} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) {
  var geometries = geometry.getGeometriesArray();
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    var geometryRenderer =
        ol.renderer.vector.GEOMETRY_RENDERERS_[geometries[i].getType()];
    geometryRenderer(replayGroup, geometries[i], style, feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
  var strokeStyle = style.getStroke();
  if (strokeStyle) {
    var lineStringReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
    lineStringReplay.drawLineString(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    textReplay.drawText(geometry.getFlatMidpoint(), 0, 2, 2, geometry, feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) {
  var strokeStyle = style.getStroke();
  if (strokeStyle) {
    var lineStringReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.LINE_STRING);
    lineStringReplay.setFillStrokeStyle(null, strokeStyle);
    lineStringReplay.drawMultiLineString(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    var flatMidpointCoordinates = geometry.getFlatMidpoints();
    textReplay.drawText(flatMidpointCoordinates, 0,
        flatMidpointCoordinates.length, 2, geometry, feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.MultiPolygon} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
  var fillStyle = style.getFill();
  var strokeStyle = style.getStroke();
  if (strokeStyle || fillStyle) {
    var polygonReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.POLYGON);
    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
    polygonReplay.drawMultiPolygon(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    var flatInteriorPointCoordinates = geometry.getFlatInteriorPoints();
    textReplay.drawText(flatInteriorPointCoordinates, 0,
        flatInteriorPointCoordinates.length, 2, geometry, feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.Point|ol.render.Feature} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) {
  var imageStyle = style.getImage();
  if (imageStyle) {
    if (imageStyle.getImageState() != ol.style.ImageState.LOADED) {
      return;
    }
    var imageReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.IMAGE);
    imageReplay.setImageStyle(imageStyle);
    imageReplay.drawPoint(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry,
        feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) {
  var imageStyle = style.getImage();
  if (imageStyle) {
    if (imageStyle.getImageState() != ol.style.ImageState.LOADED) {
      return;
    }
    var imageReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.IMAGE);
    imageReplay.setImageStyle(imageStyle);
    imageReplay.drawMultiPoint(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    var flatCoordinates = geometry.getFlatCoordinates();
    textReplay.drawText(flatCoordinates, 0, flatCoordinates.length,
        geometry.getStride(), geometry, feature);
  }
};


/**
 * @param {ol.render.ReplayGroup} replayGroup Replay group.
 * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry.
 * @param {ol.style.Style} style Style.
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @private
 */
ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) {
  var fillStyle = style.getFill();
  var strokeStyle = style.getStroke();
  if (fillStyle || strokeStyle) {
    var polygonReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.POLYGON);
    polygonReplay.setFillStrokeStyle(fillStyle, strokeStyle);
    polygonReplay.drawPolygon(geometry, feature);
  }
  var textStyle = style.getText();
  if (textStyle) {
    var textReplay = replayGroup.getReplay(
        style.getZIndex(), ol.render.ReplayType.TEXT);
    textReplay.setTextStyle(textStyle);
    textReplay.drawText(
        geometry.getFlatInteriorPoint(), 0, 2, 2, geometry, feature);
  }
};


/**
 * @const
 * @private
 * @type {Object.<ol.geom.GeometryType,
 *                function(ol.render.ReplayGroup, ol.geom.Geometry,
 *                         ol.style.Style, Object)>}
 */
ol.renderer.vector.GEOMETRY_RENDERERS_ = {
  'Point': ol.renderer.vector.renderPointGeometry_,
  'LineString': ol.renderer.vector.renderLineStringGeometry_,
  'Polygon': ol.renderer.vector.renderPolygonGeometry_,
  'MultiPoint': ol.renderer.vector.renderMultiPointGeometry_,
  'MultiLineString': ol.renderer.vector.renderMultiLineStringGeometry_,
  'MultiPolygon': ol.renderer.vector.renderMultiPolygonGeometry_,
  'GeometryCollection': ol.renderer.vector.renderGeometryCollectionGeometry_,
  'Circle': ol.renderer.vector.renderCircleGeometry_
};

goog.provide('ol.ImageCanvas');

goog.require('ol');
goog.require('ol.ImageBase');
goog.require('ol.ImageState');


/**
 * @constructor
 * @extends {ol.ImageBase}
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {Array.<ol.Attribution>} attributions Attributions.
 * @param {HTMLCanvasElement} canvas Canvas.
 * @param {ol.ImageCanvasLoader=} opt_loader Optional loader function to
 *     support asynchronous canvas drawing.
 */
ol.ImageCanvas = function(extent, resolution, pixelRatio, attributions,
    canvas, opt_loader) {

  /**
   * Optional canvas loader function.
   * @type {?ol.ImageCanvasLoader}
   * @private
   */
  this.loader_ = opt_loader !== undefined ? opt_loader : null;

  var state = opt_loader !== undefined ?
      ol.ImageState.IDLE : ol.ImageState.LOADED;

  ol.ImageBase.call(this, extent, resolution, pixelRatio, state, attributions);

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = canvas;

  /**
   * @private
   * @type {Error}
   */
  this.error_ = null;

};
ol.inherits(ol.ImageCanvas, ol.ImageBase);


/**
 * Get any error associated with asynchronous rendering.
 * @return {Error} Any error that occurred during rendering.
 */
ol.ImageCanvas.prototype.getError = function() {
  return this.error_;
};


/**
 * Handle async drawing complete.
 * @param {Error} err Any error during drawing.
 * @private
 */
ol.ImageCanvas.prototype.handleLoad_ = function(err) {
  if (err) {
    this.error_ = err;
    this.state = ol.ImageState.ERROR;
  } else {
    this.state = ol.ImageState.LOADED;
  }
  this.changed();
};


/**
 * Trigger drawing on canvas.
 */
ol.ImageCanvas.prototype.load = function() {
  if (this.state == ol.ImageState.IDLE) {
    goog.DEBUG && console.assert(this.loader_, 'this.loader_ must be set');
    this.state = ol.ImageState.LOADING;
    this.changed();
    this.loader_(this.handleLoad_.bind(this));
  }
};


/**
 * @inheritDoc
 */
ol.ImageCanvas.prototype.getImage = function(opt_context) {
  return this.canvas_;
};

goog.provide('ol.reproj');

goog.require('ol');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.proj');


/**
 * We need to employ more sophisticated solution
 * if the web browser antialiases clipping edges on canvas.
 *
 * Currently only Chrome does not antialias the edges, but this is probably
 * going to be "fixed" in the future: http://crbug.com/424291
 *
 * @type {boolean}
 * @private
 */
ol.reproj.browserAntialiasesClip_ = (function(global) {
  // Adapted from http://stackoverflow.com/questions/4565112/javascript-how-to-find-out-if-the-user-browser-is-chrome
  var isOpera = global.navigator.userAgent.indexOf('OPR') > -1;
  var isIEedge = global.navigator.userAgent.indexOf('Edge') > -1;
  return !(
    !global.navigator.userAgent.match('CriOS') &&  // Not Chrome on iOS
    global.chrome !== null && global.chrome !== undefined && // Has chrome in window
    global.navigator.vendor === 'Google Inc.' && // Vendor is Google.
    isOpera == false && // Not Opera
    isIEedge == false // Not Edge
  );
})(ol.global);


/**
 * Calculates ideal resolution to use from the source in order to achieve
 * pixel mapping as close as possible to 1:1 during reprojection.
 * The resolution is calculated regardless of what resolutions
 * are actually available in the dataset (TileGrid, Image, ...).
 *
 * @param {ol.proj.Projection} sourceProj Source projection.
 * @param {ol.proj.Projection} targetProj Target projection.
 * @param {ol.Coordinate} targetCenter Target center.
 * @param {number} targetResolution Target resolution.
 * @return {number} The best resolution to use. Can be +-Infinity, NaN or 0.
 */
ol.reproj.calculateSourceResolution = function(sourceProj, targetProj,
    targetCenter, targetResolution) {

  var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj);

  // calculate the ideal resolution of the source data
  var sourceResolution =
      targetProj.getPointResolution(targetResolution, targetCenter);

  var targetMetersPerUnit = targetProj.getMetersPerUnit();
  if (targetMetersPerUnit !== undefined) {
    sourceResolution *= targetMetersPerUnit;
  }
  var sourceMetersPerUnit = sourceProj.getMetersPerUnit();
  if (sourceMetersPerUnit !== undefined) {
    sourceResolution /= sourceMetersPerUnit;
  }

  // Based on the projection properties, the point resolution at the specified
  // coordinates may be slightly different. We need to reverse-compensate this
  // in order to achieve optimal results.

  var compensationFactor =
      sourceProj.getPointResolution(sourceResolution, sourceCenter) /
      sourceResolution;

  if (isFinite(compensationFactor) && compensationFactor > 0) {
    sourceResolution /= compensationFactor;
  }

  return sourceResolution;
};


/**
 * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap
 * in order to mask gaps caused by antialiasing.
 *
 * @param {number} centroidX Centroid of the triangle (x coordinate in pixels).
 * @param {number} centroidY Centroid of the triangle (y coordinate in pixels).
 * @param {number} x X coordinate of the point (in pixels).
 * @param {number} y Y coordinate of the point (in pixels).
 * @return {ol.Coordinate} New point 1 px farther from the centroid.
 * @private
 */
ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, x, y) {
  var dX = x - centroidX, dY = y - centroidY;
  var distance = Math.sqrt(dX * dX + dY * dY);
  return [Math.round(x + dX / distance), Math.round(y + dY / distance)];
};


/**
 * Renders the source data into new canvas based on the triangulation.
 *
 * @param {number} width Width of the canvas.
 * @param {number} height Height of the canvas.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} sourceResolution Source resolution.
 * @param {ol.Extent} sourceExtent Extent of the data source.
 * @param {number} targetResolution Target resolution.
 * @param {ol.Extent} targetExtent Target extent.
 * @param {ol.reproj.Triangulation} triangulation Calculated triangulation.
 * @param {Array.<{extent: ol.Extent,
 *                 image: (HTMLCanvasElement|Image|HTMLVideoElement)}>} sources
 *             Array of sources.
 * @param {number} gutter Gutter of the sources.
 * @param {boolean=} opt_renderEdges Render reprojection edges.
 * @return {HTMLCanvasElement} Canvas with reprojected data.
 */
ol.reproj.render = function(width, height, pixelRatio,
    sourceResolution, sourceExtent, targetResolution, targetExtent,
    triangulation, sources, gutter, opt_renderEdges) {

  var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width),
                                             Math.round(pixelRatio * height));

  if (sources.length === 0) {
    return context.canvas;
  }

  context.scale(pixelRatio, pixelRatio);

  var sourceDataExtent = ol.extent.createEmpty();
  sources.forEach(function(src, i, arr) {
    ol.extent.extend(sourceDataExtent, src.extent);
  });

  var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent);
  var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent);
  var stitchContext = ol.dom.createCanvasContext2D(
      Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
      Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));

  var stitchScale = pixelRatio / sourceResolution;

  sources.forEach(function(src, i, arr) {
    var xPos = src.extent[0] - sourceDataExtent[0];
    var yPos = -(src.extent[3] - sourceDataExtent[3]);
    var srcWidth = ol.extent.getWidth(src.extent);
    var srcHeight = ol.extent.getHeight(src.extent);

    stitchContext.drawImage(
        src.image,
        gutter, gutter,
        src.image.width - 2 * gutter, src.image.height - 2 * gutter,
        xPos * stitchScale, yPos * stitchScale,
        srcWidth * stitchScale, srcHeight * stitchScale);
  });

  var targetTopLeft = ol.extent.getTopLeft(targetExtent);

  triangulation.getTriangles().forEach(function(triangle, i, arr) {
    /* Calculate affine transform (src -> dst)
     * Resulting matrix can be used to transform coordinate
     * from `sourceProjection` to destination pixels.
     *
     * To optimize number of context calls and increase numerical stability,
     * we also do the following operations:
     * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1)
     * here before solving the linear system so [ui, vi] are pixel coordinates.
     *
     * Src points: xi, yi
     * Dst points: ui, vi
     * Affine coefficients: aij
     *
     * | x0 y0 1  0  0 0 |   |a00|   |u0|
     * | x1 y1 1  0  0 0 |   |a01|   |u1|
     * | x2 y2 1  0  0 0 | x |a02| = |u2|
     * |  0  0 0 x0 y0 1 |   |a10|   |v0|
     * |  0  0 0 x1 y1 1 |   |a11|   |v1|
     * |  0  0 0 x2 y2 1 |   |a12|   |v2|
     */
    var source = triangle.source, target = triangle.target;
    var x0 = source[0][0], y0 = source[0][1],
        x1 = source[1][0], y1 = source[1][1],
        x2 = source[2][0], y2 = source[2][1];
    var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution,
        v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
    var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution,
        v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
    var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
        v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;

    // Shift all the source points to improve numerical stability
    // of all the subsequent calculations. The [x0, y0] is used here.
    // This is also used to simplify the linear system.
    var sourceNumericalShiftX = x0, sourceNumericalShiftY = y0;
    x0 = 0;
    y0 = 0;
    x1 -= sourceNumericalShiftX;
    y1 -= sourceNumericalShiftY;
    x2 -= sourceNumericalShiftX;
    y2 -= sourceNumericalShiftY;

    var augmentedMatrix = [
      [x1, y1, 0, 0, u1 - u0],
      [x2, y2, 0, 0, u2 - u0],
      [0, 0, x1, y1, v1 - v0],
      [0, 0, x2, y2, v2 - v0]
    ];
    var affineCoefs = ol.math.solveLinearSystem(augmentedMatrix);
    if (!affineCoefs) {
      return;
    }

    context.save();
    context.beginPath();
    if (ol.reproj.browserAntialiasesClip_) {
      var centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3;
      var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0);
      var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1);
      var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2);

      context.moveTo(p1[0], p1[1]);
      context.lineTo(p0[0], p0[1]);
      context.lineTo(p2[0], p2[1]);
    } else {
      context.moveTo(u1, v1);
      context.lineTo(u0, v0);
      context.lineTo(u2, v2);
    }
    context.clip();

    context.transform(
        affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);

    context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
                      sourceDataExtent[3] - sourceNumericalShiftY);

    context.scale(sourceResolution / pixelRatio,
                  -sourceResolution / pixelRatio);

    context.drawImage(stitchContext.canvas, 0, 0);
    context.restore();
  });

  if (opt_renderEdges) {
    context.save();

    context.strokeStyle = 'black';
    context.lineWidth = 1;

    triangulation.getTriangles().forEach(function(triangle, i, arr) {
      var target = triangle.target;
      var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution,
          v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
      var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution,
          v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
      var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
          v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;

      context.beginPath();
      context.moveTo(u1, v1);
      context.lineTo(u0, v0);
      context.lineTo(u2, v2);
      context.closePath();
      context.stroke();
    });

    context.restore();
  }
  return context.canvas;
};

goog.provide('ol.reproj.Triangulation');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.proj');


/**
 * @classdesc
 * Class containing triangulation of the given target extent.
 * Used for determining source data and the reprojection itself.
 *
 * @param {ol.proj.Projection} sourceProj Source projection.
 * @param {ol.proj.Projection} targetProj Target projection.
 * @param {ol.Extent} targetExtent Target extent to triangulate.
 * @param {ol.Extent} maxSourceExtent Maximal source extent that can be used.
 * @param {number} errorThreshold Acceptable error (in source units).
 * @constructor
 */
ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
    maxSourceExtent, errorThreshold) {

  /**
   * @type {ol.proj.Projection}
   * @private
   */
  this.sourceProj_ = sourceProj;

  /**
   * @type {ol.proj.Projection}
   * @private
   */
  this.targetProj_ = targetProj;

  /** @type {!Object.<string, ol.Coordinate>} */
  var transformInvCache = {};
  var transformInv = ol.proj.getTransform(this.targetProj_, this.sourceProj_);

  /**
   * @param {ol.Coordinate} c A coordinate.
   * @return {ol.Coordinate} Transformed coordinate.
   * @private
   */
  this.transformInv_ = function(c) {
    var key = c[0] + '/' + c[1];
    if (!transformInvCache[key]) {
      transformInvCache[key] = transformInv(c);
    }
    return transformInvCache[key];
  };

  /**
   * @type {ol.Extent}
   * @private
   */
  this.maxSourceExtent_ = maxSourceExtent;

  /**
   * @type {number}
   * @private
   */
  this.errorThresholdSquared_ = errorThreshold * errorThreshold;

  /**
   * @type {Array.<ol.ReprojTriangle>}
   * @private
   */
  this.triangles_ = [];

  /**
   * Indicates that the triangulation crosses edge of the source projection.
   * @type {boolean}
   * @private
   */
  this.wrapsXInSource_ = false;

  /**
   * @type {boolean}
   * @private
   */
  this.canWrapXInSource_ = this.sourceProj_.canWrapX() &&
      !!maxSourceExtent &&
      !!this.sourceProj_.getExtent() &&
      (ol.extent.getWidth(maxSourceExtent) ==
       ol.extent.getWidth(this.sourceProj_.getExtent()));

  /**
   * @type {?number}
   * @private
   */
  this.sourceWorldWidth_ = this.sourceProj_.getExtent() ?
      ol.extent.getWidth(this.sourceProj_.getExtent()) : null;

  /**
   * @type {?number}
   * @private
   */
  this.targetWorldWidth_ = this.targetProj_.getExtent() ?
      ol.extent.getWidth(this.targetProj_.getExtent()) : null;

  var destinationTopLeft = ol.extent.getTopLeft(targetExtent);
  var destinationTopRight = ol.extent.getTopRight(targetExtent);
  var destinationBottomRight = ol.extent.getBottomRight(targetExtent);
  var destinationBottomLeft = ol.extent.getBottomLeft(targetExtent);
  var sourceTopLeft = this.transformInv_(destinationTopLeft);
  var sourceTopRight = this.transformInv_(destinationTopRight);
  var sourceBottomRight = this.transformInv_(destinationBottomRight);
  var sourceBottomLeft = this.transformInv_(destinationBottomLeft);

  this.addQuad_(
      destinationTopLeft, destinationTopRight,
      destinationBottomRight, destinationBottomLeft,
      sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
      ol.RASTER_REPROJECTION_MAX_SUBDIVISION);

  if (this.wrapsXInSource_) {
    // Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here).
    // This significantly simplifies the rest of the reprojection process.

    goog.DEBUG && console.assert(this.sourceWorldWidth_ !== null);
    var leftBound = Infinity;
    this.triangles_.forEach(function(triangle, i, arr) {
      leftBound = Math.min(leftBound,
          triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]);
    });

    // Shift triangles to be as close to `leftBound` as possible
    // (if the distance is more than `worldWidth / 2` it can be closer.
    this.triangles_.forEach(function(triangle) {
      if (Math.max(triangle.source[0][0], triangle.source[1][0],
          triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) {
        var newTriangle = [[triangle.source[0][0], triangle.source[0][1]],
                           [triangle.source[1][0], triangle.source[1][1]],
                           [triangle.source[2][0], triangle.source[2][1]]];
        if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) {
          newTriangle[0][0] -= this.sourceWorldWidth_;
        }
        if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) {
          newTriangle[1][0] -= this.sourceWorldWidth_;
        }
        if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) {
          newTriangle[2][0] -= this.sourceWorldWidth_;
        }

        // Rarely (if the extent contains both the dateline and prime meridian)
        // the shift can in turn break some triangles.
        // Detect this here and don't shift in such cases.
        var minX = Math.min(
            newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
        var maxX = Math.max(
            newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
        if ((maxX - minX) < this.sourceWorldWidth_ / 2) {
          triangle.source = newTriangle;
        }
      }
    }, this);
  }

  transformInvCache = {};
};


/**
 * Adds triangle to the triangulation.
 * @param {ol.Coordinate} a The target a coordinate.
 * @param {ol.Coordinate} b The target b coordinate.
 * @param {ol.Coordinate} c The target c coordinate.
 * @param {ol.Coordinate} aSrc The source a coordinate.
 * @param {ol.Coordinate} bSrc The source b coordinate.
 * @param {ol.Coordinate} cSrc The source c coordinate.
 * @private
 */
ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c,
    aSrc, bSrc, cSrc) {
  this.triangles_.push({
    source: [aSrc, bSrc, cSrc],
    target: [a, b, c]
  });
};


/**
 * Adds quad (points in clock-wise order) to the triangulation
 * (and reprojects the vertices) if valid.
 * Performs quad subdivision if needed to increase precision.
 *
 * @param {ol.Coordinate} a The target a coordinate.
 * @param {ol.Coordinate} b The target b coordinate.
 * @param {ol.Coordinate} c The target c coordinate.
 * @param {ol.Coordinate} d The target d coordinate.
 * @param {ol.Coordinate} aSrc The source a coordinate.
 * @param {ol.Coordinate} bSrc The source b coordinate.
 * @param {ol.Coordinate} cSrc The source c coordinate.
 * @param {ol.Coordinate} dSrc The source d coordinate.
 * @param {number} maxSubdivision Maximal allowed subdivision of the quad.
 * @private
 */
ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d,
    aSrc, bSrc, cSrc, dSrc, maxSubdivision) {

  var sourceQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]);
  var sourceCoverageX = this.sourceWorldWidth_ ?
      ol.extent.getWidth(sourceQuadExtent) / this.sourceWorldWidth_ : null;
  var sourceWorldWidth = /** @type {number} */ (this.sourceWorldWidth_);

  // when the quad is wrapped in the source projection
  // it covers most of the projection extent, but not fully
  var wrapsX = this.sourceProj_.canWrapX() &&
               sourceCoverageX > 0.5 && sourceCoverageX < 1;

  var needsSubdivision = false;

  if (maxSubdivision > 0) {
    if (this.targetProj_.isGlobal() && this.targetWorldWidth_) {
      var targetQuadExtent = ol.extent.boundingExtent([a, b, c, d]);
      var targetCoverageX =
          ol.extent.getWidth(targetQuadExtent) / this.targetWorldWidth_;
      needsSubdivision |=
          targetCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
    }
    if (!wrapsX && this.sourceProj_.isGlobal() && sourceCoverageX) {
      needsSubdivision |=
          sourceCoverageX > ol.RASTER_REPROJECTION_MAX_TRIANGLE_WIDTH;
    }
  }

  if (!needsSubdivision && this.maxSourceExtent_) {
    if (!ol.extent.intersects(sourceQuadExtent, this.maxSourceExtent_)) {
      // whole quad outside source projection extent -> ignore
      return;
    }
  }

  if (!needsSubdivision) {
    if (!isFinite(aSrc[0]) || !isFinite(aSrc[1]) ||
        !isFinite(bSrc[0]) || !isFinite(bSrc[1]) ||
        !isFinite(cSrc[0]) || !isFinite(cSrc[1]) ||
        !isFinite(dSrc[0]) || !isFinite(dSrc[1])) {
      if (maxSubdivision > 0) {
        needsSubdivision = true;
      } else {
        return;
      }
    }
  }

  if (maxSubdivision > 0) {
    if (!needsSubdivision) {
      var center = [(a[0] + c[0]) / 2, (a[1] + c[1]) / 2];
      var centerSrc = this.transformInv_(center);

      var dx;
      if (wrapsX) {
        var centerSrcEstimX =
            (ol.math.modulo(aSrc[0], sourceWorldWidth) +
             ol.math.modulo(cSrc[0], sourceWorldWidth)) / 2;
        dx = centerSrcEstimX -
            ol.math.modulo(centerSrc[0], sourceWorldWidth);
      } else {
        dx = (aSrc[0] + cSrc[0]) / 2 - centerSrc[0];
      }
      var dy = (aSrc[1] + cSrc[1]) / 2 - centerSrc[1];
      var centerSrcErrorSquared = dx * dx + dy * dy;
      needsSubdivision = centerSrcErrorSquared > this.errorThresholdSquared_;
    }
    if (needsSubdivision) {
      if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) {
        // split horizontally (top & bottom)
        var bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2];
        var bcSrc = this.transformInv_(bc);
        var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2];
        var daSrc = this.transformInv_(da);

        this.addQuad_(
            a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdivision - 1);
        this.addQuad_(
            da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdivision - 1);
      } else {
        // split vertically (left & right)
        var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
        var abSrc = this.transformInv_(ab);
        var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2];
        var cdSrc = this.transformInv_(cd);

        this.addQuad_(
            a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdivision - 1);
        this.addQuad_(
            ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdivision - 1);
      }
      return;
    }
  }

  if (wrapsX) {
    if (!this.canWrapXInSource_) {
      return;
    }
    this.wrapsXInSource_ = true;
  }

  this.addTriangle_(a, c, d, aSrc, cSrc, dSrc);
  this.addTriangle_(a, b, c, aSrc, bSrc, cSrc);
};


/**
 * Calculates extent of the 'source' coordinates from all the triangles.
 *
 * @return {ol.Extent} Calculated extent.
 */
ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
  var extent = ol.extent.createEmpty();

  this.triangles_.forEach(function(triangle, i, arr) {
    var src = triangle.source;
    ol.extent.extendCoordinate(extent, src[0]);
    ol.extent.extendCoordinate(extent, src[1]);
    ol.extent.extendCoordinate(extent, src[2]);
  });

  return extent;
};


/**
 * @return {Array.<ol.ReprojTriangle>} Array of the calculated triangles.
 */
ol.reproj.Triangulation.prototype.getTriangles = function() {
  return this.triangles_;
};

goog.provide('ol.reproj.Image');

goog.require('ol');
goog.require('ol.ImageBase');
goog.require('ol.ImageState');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.reproj');
goog.require('ol.reproj.Triangulation');


/**
 * @classdesc
 * Class encapsulating single reprojected image.
 * See {@link ol.source.Image}.
 *
 * @constructor
 * @extends {ol.ImageBase}
 * @param {ol.proj.Projection} sourceProj Source projection (of the data).
 * @param {ol.proj.Projection} targetProj Target projection.
 * @param {ol.Extent} targetExtent Target extent.
 * @param {number} targetResolution Target resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.ReprojImageFunctionType} getImageFunction
 *     Function returning source images (extent, resolution, pixelRatio).
 */
ol.reproj.Image = function(sourceProj, targetProj,
    targetExtent, targetResolution, pixelRatio, getImageFunction) {

  /**
   * @private
   * @type {ol.proj.Projection}
   */
  this.targetProj_ = targetProj;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.maxSourceExtent_ = sourceProj.getExtent();
  var maxTargetExtent = targetProj.getExtent();

  var limitedTargetExtent = maxTargetExtent ?
      ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;

  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
  var sourceResolution = ol.reproj.calculateSourceResolution(
      sourceProj, targetProj, targetCenter, targetResolution);

  var errorThresholdInPixels = ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;

  /**
   * @private
   * @type {!ol.reproj.Triangulation}
   */
  this.triangulation_ = new ol.reproj.Triangulation(
      sourceProj, targetProj, limitedTargetExtent, this.maxSourceExtent_,
      sourceResolution * errorThresholdInPixels);

  /**
   * @private
   * @type {number}
   */
  this.targetResolution_ = targetResolution;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.targetExtent_ = targetExtent;

  var sourceExtent = this.triangulation_.calculateSourceExtent();

  /**
   * @private
   * @type {ol.ImageBase}
   */
  this.sourceImage_ =
      getImageFunction(sourceExtent, sourceResolution, pixelRatio);

  /**
   * @private
   * @type {number}
   */
  this.sourcePixelRatio_ =
      this.sourceImage_ ? this.sourceImage_.getPixelRatio() : 1;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = null;

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.sourceListenerKey_ = null;


  var state = ol.ImageState.LOADED;
  var attributions = [];

  if (this.sourceImage_) {
    state = ol.ImageState.IDLE;
    attributions = this.sourceImage_.getAttributions();
  }

  ol.ImageBase.call(this, targetExtent, targetResolution, this.sourcePixelRatio_,
            state, attributions);
};
ol.inherits(ol.reproj.Image, ol.ImageBase);


/**
 * @inheritDoc
 */
ol.reproj.Image.prototype.disposeInternal = function() {
  if (this.state == ol.ImageState.LOADING) {
    this.unlistenSource_();
  }
  ol.ImageBase.prototype.disposeInternal.call(this);
};


/**
 * @inheritDoc
 */
ol.reproj.Image.prototype.getImage = function(opt_context) {
  return this.canvas_;
};


/**
 * @return {ol.proj.Projection} Projection.
 */
ol.reproj.Image.prototype.getProjection = function() {
  return this.targetProj_;
};


/**
 * @private
 */
ol.reproj.Image.prototype.reproject_ = function() {
  var sourceState = this.sourceImage_.getState();
  if (sourceState == ol.ImageState.LOADED) {
    var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_;
    var height =
        ol.extent.getHeight(this.targetExtent_) / this.targetResolution_;

    this.canvas_ = ol.reproj.render(width, height, this.sourcePixelRatio_,
        this.sourceImage_.getResolution(), this.maxSourceExtent_,
        this.targetResolution_, this.targetExtent_, this.triangulation_, [{
          extent: this.sourceImage_.getExtent(),
          image: this.sourceImage_.getImage()
        }], 0);
  }
  this.state = sourceState;
  this.changed();
};


/**
 * @inheritDoc
 */
ol.reproj.Image.prototype.load = function() {
  if (this.state == ol.ImageState.IDLE) {
    this.state = ol.ImageState.LOADING;
    this.changed();

    var sourceState = this.sourceImage_.getState();
    if (sourceState == ol.ImageState.LOADED ||
        sourceState == ol.ImageState.ERROR) {
      this.reproject_();
    } else {
      this.sourceListenerKey_ = ol.events.listen(this.sourceImage_,
          ol.events.EventType.CHANGE, function(e) {
            var sourceState = this.sourceImage_.getState();
            if (sourceState == ol.ImageState.LOADED ||
                sourceState == ol.ImageState.ERROR) {
              this.unlistenSource_();
              this.reproject_();
            }
          }, this);
      this.sourceImage_.load();
    }
  }
};


/**
 * @private
 */
ol.reproj.Image.prototype.unlistenSource_ = function() {
  goog.DEBUG && console.assert(this.sourceListenerKey_,
      'this.sourceListenerKey_ should not be null');
  ol.events.unlistenByKey(/** @type {!ol.EventsKey} */ (this.sourceListenerKey_));
  this.sourceListenerKey_ = null;
};

goog.provide('ol.source.Image');
goog.provide('ol.source.ImageEvent');

goog.require('ol');
goog.require('ol.ImageState');
goog.require('ol.array');
goog.require('ol.events.Event');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.reproj.Image');
goog.require('ol.source.Source');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for sources providing a single image.
 *
 * @constructor
 * @extends {ol.source.Source}
 * @param {ol.SourceImageOptions} options Single image source options.
 * @api
 */
ol.source.Image = function(options) {

  ol.source.Source.call(this, {
    attributions: options.attributions,
    extent: options.extent,
    logo: options.logo,
    projection: options.projection,
    state: options.state
  });

  /**
   * @private
   * @type {Array.<number>}
   */
  this.resolutions_ = options.resolutions !== undefined ?
      options.resolutions : null;
  goog.DEBUG && console.assert(!this.resolutions_ ||
      ol.array.isSorted(this.resolutions_,
          function(a, b) {
            return b - a;
          }, true), 'resolutions must be null or sorted in descending order');


  /**
   * @private
   * @type {ol.reproj.Image}
   */
  this.reprojectedImage_ = null;


  /**
   * @private
   * @type {number}
   */
  this.reprojectedRevision_ = 0;

};
ol.inherits(ol.source.Image, ol.source.Source);


/**
 * @return {Array.<number>} Resolutions.
 */
ol.source.Image.prototype.getResolutions = function() {
  return this.resolutions_;
};


/**
 * @protected
 * @param {number} resolution Resolution.
 * @return {number} Resolution.
 */
ol.source.Image.prototype.findNearestResolution = function(resolution) {
  if (this.resolutions_) {
    var idx = ol.array.linearFindNearest(this.resolutions_, resolution, 0);
    resolution = this.resolutions_[idx];
  }
  return resolution;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @return {ol.ImageBase} Single image.
 */
ol.source.Image.prototype.getImage = function(extent, resolution, pixelRatio, projection) {
  var sourceProjection = this.getProjection();
  if (!ol.ENABLE_RASTER_REPROJECTION ||
      !sourceProjection ||
      !projection ||
      ol.proj.equivalent(sourceProjection, projection)) {
    if (sourceProjection) {
      projection = sourceProjection;
    }
    return this.getImageInternal(extent, resolution, pixelRatio, projection);
  } else {
    if (this.reprojectedImage_) {
      if (this.reprojectedRevision_ == this.getRevision() &&
          ol.proj.equivalent(
              this.reprojectedImage_.getProjection(), projection) &&
          this.reprojectedImage_.getResolution() == resolution &&
          this.reprojectedImage_.getPixelRatio() == pixelRatio &&
          ol.extent.equals(this.reprojectedImage_.getExtent(), extent)) {
        return this.reprojectedImage_;
      }
      this.reprojectedImage_.dispose();
      this.reprojectedImage_ = null;
    }

    this.reprojectedImage_ = new ol.reproj.Image(
        sourceProjection, projection, extent, resolution, pixelRatio,
        function(extent, resolution, pixelRatio) {
          return this.getImageInternal(extent, resolution,
              pixelRatio, sourceProjection);
        }.bind(this));
    this.reprojectedRevision_ = this.getRevision();

    return this.reprojectedImage_;
  }
};


/**
 * @abstract
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @return {ol.ImageBase} Single image.
 * @protected
 */
ol.source.Image.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {};


/**
 * Handle image change events.
 * @param {ol.events.Event} event Event.
 * @protected
 */
ol.source.Image.prototype.handleImageChange = function(event) {
  var image = /** @type {ol.Image} */ (event.target);
  switch (image.getState()) {
    case ol.ImageState.LOADING:
      this.dispatchEvent(
          new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADSTART,
              image));
      break;
    case ol.ImageState.LOADED:
      this.dispatchEvent(
          new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADEND,
              image));
      break;
    case ol.ImageState.ERROR:
      this.dispatchEvent(
          new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADERROR,
              image));
      break;
    default:
      // pass
  }
};


/**
 * Default image load function for image sources that use ol.Image image
 * instances.
 * @param {ol.Image} image Image.
 * @param {string} src Source.
 */
ol.source.Image.defaultImageLoadFunction = function(image, src) {
  image.getImage().src = src;
};


/**
 * @classdesc
 * Events emitted by {@link ol.source.Image} instances are instances of this
 * type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.source.ImageEvent}
 * @param {string} type Type.
 * @param {ol.Image} image The image.
 */
ol.source.ImageEvent = function(type, image) {

  ol.events.Event.call(this, type);

  /**
   * The image related to the event.
   * @type {ol.Image}
   * @api
   */
  this.image = image;

};
ol.inherits(ol.source.ImageEvent, ol.events.Event);


/**
 * @enum {string}
 */
ol.source.ImageEventType = {

  /**
   * Triggered when an image starts loading.
   * @event ol.source.ImageEvent#imageloadstart
   * @api
   */
  IMAGELOADSTART: 'imageloadstart',

  /**
   * Triggered when an image finishes loading.
   * @event ol.source.ImageEvent#imageloadend
   * @api
   */
  IMAGELOADEND: 'imageloadend',

  /**
   * Triggered if image loading results in an error.
   * @event ol.source.ImageEvent#imageloaderror
   * @api
   */
  IMAGELOADERROR: 'imageloaderror'

};

goog.provide('ol.source.ImageCanvas');

goog.require('ol');
goog.require('ol.ImageCanvas');
goog.require('ol.extent');
goog.require('ol.source.Image');


/**
 * @classdesc
 * Base class for image sources where a canvas element is the image.
 *
 * @constructor
 * @extends {ol.source.Image}
 * @param {olx.source.ImageCanvasOptions} options Constructor options.
 * @api
 */
ol.source.ImageCanvas = function(options) {

  ol.source.Image.call(this, {
    attributions: options.attributions,
    logo: options.logo,
    projection: options.projection,
    resolutions: options.resolutions,
    state: options.state
  });

  /**
   * @private
   * @type {ol.CanvasFunctionType}
   */
  this.canvasFunction_ = options.canvasFunction;

  /**
   * @private
   * @type {ol.ImageCanvas}
   */
  this.canvas_ = null;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.ratio_ = options.ratio !== undefined ?
      options.ratio : 1.5;

};
ol.inherits(ol.source.ImageCanvas, ol.source.Image);


/**
 * @inheritDoc
 */
ol.source.ImageCanvas.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
  resolution = this.findNearestResolution(resolution);

  var canvas = this.canvas_;
  if (canvas &&
      this.renderedRevision_ == this.getRevision() &&
      canvas.getResolution() == resolution &&
      canvas.getPixelRatio() == pixelRatio &&
      ol.extent.containsExtent(canvas.getExtent(), extent)) {
    return canvas;
  }

  extent = extent.slice();
  ol.extent.scaleFromCenter(extent, this.ratio_);
  var width = ol.extent.getWidth(extent) / resolution;
  var height = ol.extent.getHeight(extent) / resolution;
  var size = [width * pixelRatio, height * pixelRatio];

  var canvasElement = this.canvasFunction_(
      extent, resolution, pixelRatio, size, projection);
  if (canvasElement) {
    canvas = new ol.ImageCanvas(extent, resolution, pixelRatio,
        this.getAttributions(), canvasElement);
  }
  this.canvas_ = canvas;
  this.renderedRevision_ = this.getRevision();

  return canvas;
};

goog.provide('ol.source.ImageVector');

goog.require('ol');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.renderer.vector');
goog.require('ol.source.ImageCanvas');
goog.require('ol.style.Style');
goog.require('ol.transform');


/**
 * @classdesc
 * An image source whose images are canvas elements into which vector features
 * read from a vector source (`ol.source.Vector`) are drawn. An
 * `ol.source.ImageVector` object is to be used as the `source` of an image
 * layer (`ol.layer.Image`). Image layers are rotated, scaled, and translated,
 * as opposed to being re-rendered, during animations and interactions. So, like
 * any other image layer, an image layer configured with an
 * `ol.source.ImageVector` will exhibit this behaviour. This is in contrast to a
 * vector layer, where vector features are re-drawn during animations and
 * interactions.
 *
 * @constructor
 * @extends {ol.source.ImageCanvas}
 * @param {olx.source.ImageVectorOptions} options Options.
 * @api
 */
ol.source.ImageVector = function(options) {

  /**
   * @private
   * @type {ol.source.Vector}
   */
  this.source_ = options.source;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.canvasContext_ = ol.dom.createCanvasContext2D();

  /**
   * @private
   * @type {ol.Size}
   */
  this.canvasSize_ = [0, 0];

  /**
   * @private
   * @type {number}
   */
  this.renderBuffer_ = options.renderBuffer == undefined ? 100 : options.renderBuffer;

  /**
   * @private
   * @type {ol.render.canvas.ReplayGroup}
   */
  this.replayGroup_ = null;

  ol.source.ImageCanvas.call(this, {
    attributions: options.attributions,
    canvasFunction: this.canvasFunctionInternal_.bind(this),
    logo: options.logo,
    projection: options.projection,
    ratio: options.ratio,
    resolutions: options.resolutions,
    state: this.source_.getState()
  });

  /**
   * User provided style.
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
   * @private
   */
  this.style_ = null;

  /**
   * Style function for use within the library.
   * @type {ol.StyleFunction|undefined}
   * @private
   */
  this.styleFunction_ = undefined;

  this.setStyle(options.style);

  ol.events.listen(this.source_, ol.events.EventType.CHANGE,
      this.handleSourceChange_, this);

};
ol.inherits(ol.source.ImageVector, ol.source.ImageCanvas);


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Size} size Size.
 * @param {ol.proj.Projection} projection Projection;
 * @return {HTMLCanvasElement} Canvas element.
 * @private
 */
ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resolution, pixelRatio, size, projection) {

  var replayGroup = new ol.render.canvas.ReplayGroup(
      ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
      resolution, this.source_.getOverlaps(), this.renderBuffer_);

  this.source_.loadFeatures(extent, resolution, projection);

  var loading = false;
  this.source_.forEachFeatureInExtent(extent,
      /**
       * @param {ol.Feature} feature Feature.
       */
      function(feature) {
        loading = loading ||
            this.renderFeature_(feature, resolution, pixelRatio, replayGroup);
      }, this);
  replayGroup.finish();

  if (loading) {
    return null;
  }

  if (this.canvasSize_[0] != size[0] || this.canvasSize_[1] != size[1]) {
    this.canvasContext_.canvas.width = size[0];
    this.canvasContext_.canvas.height = size[1];
    this.canvasSize_[0] = size[0];
    this.canvasSize_[1] = size[1];
  } else {
    this.canvasContext_.clearRect(0, 0, size[0], size[1]);
  }

  var transform = this.getTransform_(ol.extent.getCenter(extent),
      resolution, pixelRatio, size);
  replayGroup.replay(this.canvasContext_, pixelRatio, transform, 0, {});

  this.replayGroup_ = replayGroup;

  return this.canvasContext_.canvas;
};


/**
 * @inheritDoc
 */
ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
    coordinate, resolution, rotation, skippedFeatureUids, callback) {
  if (!this.replayGroup_) {
    return undefined;
  } else {
    /** @type {Object.<string, boolean>} */
    var features = {};
    return this.replayGroup_.forEachFeatureAtCoordinate(
        coordinate, resolution, 0, skippedFeatureUids,
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {?} Callback result.
         */
        function(feature) {
          var key = ol.getUid(feature).toString();
          if (!(key in features)) {
            features[key] = true;
            return callback(feature);
          }
        });
  }
};


/**
 * Get a reference to the wrapped source.
 * @return {ol.source.Vector} Source.
 * @api
 */
ol.source.ImageVector.prototype.getSource = function() {
  return this.source_;
};


/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction}
 *     Layer style.
 * @api stable
 */
ol.source.ImageVector.prototype.getStyle = function() {
  return this.style_;
};


/**
 * Get the style function.
 * @return {ol.StyleFunction|undefined} Layer style function.
 * @api stable
 */
ol.source.ImageVector.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};


/**
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Size} size Size.
 * @return {!ol.Transform} Transform.
 * @private
 */
ol.source.ImageVector.prototype.getTransform_ = function(center, resolution, pixelRatio, size) {
  var dx1 = size[0] / 2;
  var dy1 = size[1] / 2;
  var sx = pixelRatio / resolution;
  var sy = -sx;
  var dx2 = -center[0];
  var dy2 = -center[1];

  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, 0, dx2, dy2);
};


/**
 * Handle changes in image style state.
 * @param {ol.events.Event} event Image style change event.
 * @private
 */
ol.source.ImageVector.prototype.handleImageChange_ = function(event) {
  this.changed();
};


/**
 * @private
 */
ol.source.ImageVector.prototype.handleSourceChange_ = function() {
  // setState will trigger a CHANGE event, so we always rely
  // change events by calling setState.
  this.setState(this.source_.getState());
};


/**
 * @param {ol.Feature} feature Feature.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 * @private
 */
ol.source.ImageVector.prototype.renderFeature_ = function(feature, resolution, pixelRatio, replayGroup) {
  var styles;
  var styleFunction = feature.getStyleFunction();
  if (styleFunction) {
    styles = styleFunction.call(feature, resolution);
  } else if (this.styleFunction_) {
    styles = this.styleFunction_(feature, resolution);
  }
  if (!styles) {
    return false;
  }
  var i, ii, loading = false;
  if (!Array.isArray(styles)) {
    styles = [styles];
  }
  for (i = 0, ii = styles.length; i < ii; ++i) {
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles[i],
        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
        this.handleImageChange_, this) || loading;
  }
  return loading;
};


/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles. If it is `undefined` the default style is used. If
 * it is `null` the layer has no style (a `null` style), so only features
 * that have their own styles will be rendered in the layer. See
 * {@link ol.style} for information on the default style.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.StyleFunction|undefined}
 *     style Layer style.
 * @api stable
 */
ol.source.ImageVector.prototype.setStyle = function(style) {
  this.style_ = style !== undefined ? style : ol.style.Style.defaultFunction;
  this.styleFunction_ = !style ?
      undefined : ol.style.Style.createFunction(this.style_);
  this.changed();
};

goog.provide('ol.renderer.canvas.ImageLayer');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.functions');
goog.require('ol.proj');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.source.ImageVector');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.renderer.canvas.Layer}
 * @param {ol.layer.Image} imageLayer Single image layer.
 */
ol.renderer.canvas.ImageLayer = function(imageLayer) {

  ol.renderer.canvas.Layer.call(this, imageLayer);

  /**
   * @private
   * @type {?ol.ImageBase}
   */
  this.image_ = null;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.imageTransform_ = ol.transform.create();

  /**
   * @private
   * @type {?ol.Transform}
   */
  this.imageTransformInv_ = null;

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.hitCanvasContext_ = null;

};
ol.inherits(ol.renderer.canvas.ImageLayer, ol.renderer.canvas.Layer);


/**
 * @inheritDoc
 */
ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  var layer = this.getLayer();
  var source = layer.getSource();
  var resolution = frameState.viewState.resolution;
  var rotation = frameState.viewState.rotation;
  var skippedFeatureUids = frameState.skippedFeatureUids;
  return source.forEachFeatureAtCoordinate(
      coordinate, resolution, rotation, skippedFeatureUids,
      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {?} Callback result.
       */
      function(feature) {
        return callback.call(thisArg, feature, layer);
      });
};


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.canvas.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
  if (!this.getImage()) {
    return undefined;
  }

  if (this.getLayer().getSource() instanceof ol.source.ImageVector) {
    // for ImageVector sources use the original hit-detection logic,
    // so that for example also transparent polygons are detected
    var coordinate = ol.transform.apply(
        frameState.pixelToCoordinateTransform, pixel.slice());
    var hasFeature = this.forEachFeatureAtCoordinate(
        coordinate, frameState, ol.functions.TRUE, this);

    if (hasFeature) {
      return callback.call(thisArg, this.getLayer(), null);
    } else {
      return undefined;
    }
  } else {
    // for all other image sources directly check the image
    if (!this.imageTransformInv_) {
      this.imageTransformInv_ = ol.transform.invert(this.imageTransform_.slice());
    }

    var pixelOnCanvas =
        this.getPixelOnCanvas(pixel, this.imageTransformInv_);

    if (!this.hitCanvasContext_) {
      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
    }

    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
    this.hitCanvasContext_.drawImage(
        this.getImage(), pixelOnCanvas[0], pixelOnCanvas[1], 1, 1, 0, 0, 1, 1);

    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
    if (imageData[3] > 0) {
      return callback.call(thisArg, this.getLayer(),  imageData);
    } else {
      return undefined;
    }
  }
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.ImageLayer.prototype.getImage = function() {
  return !this.image_ ? null : this.image_.getImage();
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.ImageLayer.prototype.getImageTransform = function() {
  return this.imageTransform_;
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {

  var pixelRatio = frameState.pixelRatio;
  var viewState = frameState.viewState;
  var viewCenter = viewState.center;
  var viewResolution = viewState.resolution;

  var image;
  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
  var imageSource = imageLayer.getSource();

  var hints = frameState.viewHints;

  var renderedExtent = frameState.extent;
  if (layerState.extent !== undefined) {
    renderedExtent = ol.extent.getIntersection(
        renderedExtent, layerState.extent);
  }

  if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] &&
      !ol.extent.isEmpty(renderedExtent)) {
    var projection = viewState.projection;
    if (!ol.ENABLE_RASTER_REPROJECTION) {
      var sourceProjection = imageSource.getProjection();
      if (sourceProjection) {
        goog.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection),
            'projection and sourceProjection are equivalent');
        projection = sourceProjection;
      }
    }
    image = imageSource.getImage(
        renderedExtent, viewResolution, pixelRatio, projection);
    if (image) {
      var loaded = this.loadImage(image);
      if (loaded) {
        this.image_ = image;
      }
    }
  }

  if (this.image_) {
    image = this.image_;
    var imageExtent = image.getExtent();
    var imageResolution = image.getResolution();
    var imagePixelRatio = image.getPixelRatio();
    var scale = pixelRatio * imageResolution /
        (viewResolution * imagePixelRatio);
    var transform = ol.transform.reset(this.imageTransform_);
    ol.transform.translate(transform,
        pixelRatio * frameState.size[0] / 2,
        pixelRatio * frameState.size[1] / 2);
    ol.transform.scale(transform, scale, scale);
    ol.transform.translate(transform,
        imagePixelRatio * (imageExtent[0] - viewCenter[0]) / imageResolution,
        imagePixelRatio * (viewCenter[1] - imageExtent[3]) / imageResolution);
    this.imageTransformInv_ = null;
    this.updateAttributions(frameState.attributions, image.getAttributions());
    this.updateLogos(frameState, imageSource);
  }

  return !!this.image_;
};

// FIXME find correct globalCompositeOperation

goog.provide('ol.renderer.canvas.TileLayer');

goog.require('ol');
goog.require('ol.transform');
goog.require('ol.TileRange');
goog.require('ol.Tile');
goog.require('ol.array');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.render.canvas');
goog.require('ol.render.EventType');
goog.require('ol.renderer.canvas.Layer');


/**
 * @constructor
 * @extends {ol.renderer.canvas.Layer}
 * @param {ol.layer.Tile|ol.layer.VectorTile} tileLayer Tile layer.
 */
ol.renderer.canvas.TileLayer = function(tileLayer) {

  ol.renderer.canvas.Layer.call(this, tileLayer);

  /**
   * @protected
   * @type {CanvasRenderingContext2D}
   */
  this.context = ol.dom.createCanvasContext2D();

  /**
   * @protected
   * @type {Array.<ol.Tile|undefined>}
   */
  this.renderedTiles = null;

  /**
   * @protected
   * @type {ol.Extent}
   */
  this.tmpExtent = ol.extent.createEmpty();

  /**
   * @private
   * @type {ol.TileCoord}
   */
  this.tmpTileCoord_ = [0, 0, 0];

  /**
   * @private
   * @type {ol.Transform}
   */
  this.imageTransform_ = ol.transform.create();

  /**
   * @protected
   * @type {number}
   */
  this.zDirection = 0;

};
ol.inherits(ol.renderer.canvas.TileLayer, ol.renderer.canvas.Layer);


/**
 * @inheritDoc
 */
ol.renderer.canvas.TileLayer.prototype.composeFrame = function(
    frameState, layerState, context) {
  var transform = this.getTransform(frameState, 0);
  this.dispatchPreComposeEvent(context, frameState, transform);
  this.renderTileImages(context, frameState, layerState);
  this.dispatchPostComposeEvent(context, frameState, transform);
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.TileLayer.prototype.prepareFrame = function(
    frameState, layerState) {

  var pixelRatio = frameState.pixelRatio;
  var viewState = frameState.viewState;
  var projection = viewState.projection;

  var tileLayer = this.getLayer();
  var tileSource = /** @type {ol.source.Tile} */ (tileLayer.getSource());
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var z = tileGrid.getZForResolution(viewState.resolution, this.zDirection);
  var tileResolution = tileGrid.getResolution(z);
  var center = viewState.center;
  var extent;
  if (tileResolution == viewState.resolution) {
    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
    extent = ol.extent.getForViewAndSize(
        center, tileResolution, viewState.rotation, frameState.size);
  } else {
    extent = frameState.extent;
  }

  if (layerState.extent !== undefined) {
    extent = ol.extent.getIntersection(extent, layerState.extent);
  }
  if (ol.extent.isEmpty(extent)) {
    // Return false to prevent the rendering of the layer.
    return false;
  }

  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      extent, tileResolution);

  /**
   * @type {Object.<number, Object.<string, ol.Tile>>}
   */
  var tilesToDrawByZ = {};
  tilesToDrawByZ[z] = {};

  var findLoadedTiles = this.createLoadedTileFinder(
      tileSource, projection, tilesToDrawByZ);

  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();

  var tmpExtent = ol.extent.createEmpty();
  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
  var childTileRange, fullyLoaded, tile, x, y;
  var drawableTile = (
      /**
       * @param {!ol.Tile} tile Tile.
       * @return {boolean} Tile is selected.
       */
      function(tile) {
        var tileState = tile.getState();
        return tileState == ol.Tile.State.LOADED ||
            tileState == ol.Tile.State.EMPTY ||
            tileState == ol.Tile.State.ERROR && !useInterimTilesOnError;
      });
  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
      if (!drawableTile(tile) && tile.interimTile) {
        tile = tile.interimTile;
      }
      if (drawableTile(tile)) {
        tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
        continue;
      }
      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
      if (!fullyLoaded) {
        childTileRange = tileGrid.getTileCoordChildTileRange(
            tile.tileCoord, tmpTileRange, tmpExtent);
        if (childTileRange) {
          findLoadedTiles(z + 1, childTileRange);
        }
      }

    }
  }

  /** @type {Array.<number>} */
  var zs = Object.keys(tilesToDrawByZ).map(Number);
  zs.sort(ol.array.numberSafeCompareFunction);
  var renderables = [];
  var i, ii, currentZ, tileCoordKey, tilesToDraw;
  for (i = 0, ii = zs.length; i < ii; ++i) {
    currentZ = zs[i];
    tilesToDraw = tilesToDrawByZ[currentZ];
    for (tileCoordKey in tilesToDraw) {
      tile = tilesToDraw[tileCoordKey];
      if (tile.getState() == ol.Tile.State.LOADED) {
        renderables.push(tile);
      }
    }
  }
  this.renderedTiles = renderables;

  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
  this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
      projection, extent, z, tileLayer.getPreload());
  this.scheduleExpireCache(frameState, tileSource);
  this.updateLogos(frameState, tileSource);

  return true;
};


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.canvas.TileLayer.prototype.forEachLayerAtPixel = function(
    pixel, frameState, callback, thisArg) {
  var canvas = this.context.canvas;
  var size = frameState.size;
  var pixelRatio = frameState.pixelRatio;
  canvas.width = size[0] * pixelRatio;
  canvas.height = size[1] * pixelRatio;
  this.composeFrame(frameState, this.getLayer().getLayerState(), this.context);

  var imageData = this.context.getImageData(
      pixel[0], pixel[1], 1, 1).data;

  if (imageData[3] > 0) {
    return callback.call(thisArg, this.getLayer(),  imageData);
  } else {
    return undefined;
  }
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @protected
 */
ol.renderer.canvas.TileLayer.prototype.renderTileImages = function(context, frameState, layerState) {
  var pixelRatio = frameState.pixelRatio;
  var viewState = frameState.viewState;
  var center = viewState.center;
  var projection = viewState.projection;
  var resolution = viewState.resolution;
  var rotation = viewState.rotation;
  var size = frameState.size;
  var offsetX = Math.round(pixelRatio * size[0] / 2);
  var offsetY = Math.round(pixelRatio * size[1] / 2);
  var pixelScale = pixelRatio / resolution;
  var layer = this.getLayer();
  var source = /** @type {ol.source.Tile} */ (layer.getSource());
  var tileGutter = pixelRatio * source.getGutter(projection);
  var tileGrid = source.getTileGridForProjection(projection);

  var hasRenderListeners = layer.hasListener(ol.render.EventType.RENDER);
  var renderContext = context;
  var drawScale = 1;
  var drawOffsetX, drawOffsetY, drawSize;
  if (rotation || hasRenderListeners) {
    renderContext = this.context;
    var renderCanvas = renderContext.canvas;
    drawScale = source.getTilePixelRatio(pixelRatio) / pixelRatio;
    var width = context.canvas.width * drawScale;
    var height = context.canvas.height * drawScale;
    // Make sure the canvas is big enough for all possible rotation angles
    drawSize = Math.round(Math.sqrt(width * width + height * height));
    if (renderCanvas.width != drawSize) {
      renderCanvas.width = renderCanvas.height = drawSize;
    } else {
      renderContext.clearRect(0, 0, drawSize, drawSize);
    }
    drawOffsetX = (drawSize - width) / 2 / drawScale;
    drawOffsetY = (drawSize - height) / 2 / drawScale;
    pixelScale *= drawScale;
    offsetX = Math.round(drawScale * (offsetX + drawOffsetX));
    offsetY = Math.round(drawScale * (offsetY + drawOffsetY));
  }
  // for performance reasons, context.save / context.restore is not used
  // to save and restore the transformation matrix and the opacity.
  // see http://jsperf.com/context-save-restore-versus-variable
  var alpha = renderContext.globalAlpha;
  renderContext.globalAlpha = layerState.opacity;

  var tilesToDraw = this.renderedTiles;

  var pixelExtents;
  var opaque = source.getOpaque(projection) && layerState.opacity == 1;
  if (!opaque) {
    tilesToDraw.reverse();
    pixelExtents = [];
  }

  var extent = layerState.extent;
  var clipped = extent !== undefined;
  if (clipped) {
    var topLeft = ol.extent.getTopLeft(/** @type {ol.Extent} */ (extent));
    var topRight = ol.extent.getTopRight(/** @type {ol.Extent} */ (extent));
    var bottomRight = ol.extent.getBottomRight(/** @type {ol.Extent} */ (extent));
    var bottomLeft = ol.extent.getBottomLeft(/** @type {ol.Extent} */ (extent));

    ol.transform.apply(frameState.coordinateToPixelTransform, topLeft);
    ol.transform.apply(frameState.coordinateToPixelTransform, topRight);
    ol.transform.apply(frameState.coordinateToPixelTransform, bottomRight);
    ol.transform.apply(frameState.coordinateToPixelTransform, bottomLeft);

    var ox = drawOffsetX || 0;
    var oy = drawOffsetY || 0;
    renderContext.save();
    var cx = (renderContext.canvas.width) / 2;
    var cy = (renderContext.canvas.height) / 2;
    ol.render.canvas.rotateAtOffset(renderContext, -rotation, cx, cy);
    renderContext.beginPath();
    renderContext.moveTo(drawScale * (topLeft[0] * pixelRatio + ox),
        drawScale * (topLeft[1] * pixelRatio + oy));
    renderContext.lineTo(drawScale * (topRight[0] * pixelRatio + ox),
        drawScale * (topRight[1] * pixelRatio + oy));
    renderContext.lineTo(drawScale * (bottomRight[0] * pixelRatio + ox),
        drawScale * (bottomRight[1] * pixelRatio + oy));
    renderContext.lineTo(drawScale * (bottomLeft[0] * pixelRatio + ox),
        drawScale * (bottomLeft[1] * pixelRatio + oy));
    renderContext.clip();
    ol.render.canvas.rotateAtOffset(renderContext, rotation, cx, cy);
  }

  for (var i = 0, ii = tilesToDraw.length; i < ii; ++i) {
    var tile = tilesToDraw[i];
    var tileCoord = tile.getTileCoord();
    var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
    var currentZ = tileCoord[0];
    // Calculate all insert points by tile widths from a common origin to avoid
    // gaps caused by rounding
    var origin = ol.extent.getBottomLeft(tileGrid.getTileCoordExtent(
        tileGrid.getTileCoordForCoordAndZ(center, currentZ, this.tmpTileCoord_)));
    var w = Math.round(ol.extent.getWidth(tileExtent) * pixelScale);
    var h = Math.round(ol.extent.getHeight(tileExtent) * pixelScale);
    var left = Math.round((tileExtent[0] - origin[0]) * pixelScale / w) * w +
        offsetX + Math.round((origin[0] - center[0]) * pixelScale);
    var top = Math.round((origin[1] - tileExtent[3]) * pixelScale / h) * h +
        offsetY + Math.round((center[1] - origin[1]) * pixelScale);
    if (!opaque) {
      var pixelExtent = [left, top, left + w, top + h];
      // Create a clip mask for regions in this low resolution tile that are
      // already filled by a higher resolution tile
      renderContext.save();
      for (var j = 0, jj = pixelExtents.length; j < jj; ++j) {
        var clipExtent = pixelExtents[j];
        if (ol.extent.intersects(pixelExtent, clipExtent)) {
          renderContext.beginPath();
          // counter-clockwise (outer ring) for current tile
          renderContext.moveTo(pixelExtent[0], pixelExtent[1]);
          renderContext.lineTo(pixelExtent[0], pixelExtent[3]);
          renderContext.lineTo(pixelExtent[2], pixelExtent[3]);
          renderContext.lineTo(pixelExtent[2], pixelExtent[1]);
          // clockwise (inner ring) for higher resolution tile
          renderContext.moveTo(clipExtent[0], clipExtent[1]);
          renderContext.lineTo(clipExtent[2], clipExtent[1]);
          renderContext.lineTo(clipExtent[2], clipExtent[3]);
          renderContext.lineTo(clipExtent[0], clipExtent[3]);
          renderContext.closePath();
          renderContext.clip();
        }
      }
      pixelExtents.push(pixelExtent);
    }
    var tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection);
    renderContext.drawImage(tile.getImage(), tileGutter, tileGutter,
        tilePixelSize[0], tilePixelSize[1], left, top, w, h);
    if (!opaque) {
      renderContext.restore();
    }
  }

  if (clipped) {
    renderContext.restore();
  }

  if (hasRenderListeners) {
    var dX = drawOffsetX - offsetX / drawScale + offsetX;
    var dY = drawOffsetY - offsetY / drawScale + offsetY;
    var imageTransform = ol.transform.compose(this.imageTransform_,
        drawSize / 2 - dX, drawSize / 2 - dY,
        pixelScale, -pixelScale,
        -rotation,
        -center[0] + dX / pixelScale, -center[1] - dY / pixelScale);
    this.dispatchRenderEvent(renderContext, frameState, imageTransform);
  }
  if (rotation || hasRenderListeners) {
    context.drawImage(renderContext.canvas, -Math.round(drawOffsetX),
        -Math.round(drawOffsetY), drawSize / drawScale, drawSize / drawScale);
  }
  renderContext.globalAlpha = alpha;
};


/**
 * @function
 * @return {ol.layer.Tile|ol.layer.VectorTile}
 */
ol.renderer.canvas.TileLayer.prototype.getLayer;

goog.provide('ol.renderer.canvas.VectorLayer');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas');
goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.vector');


/**
 * @constructor
 * @extends {ol.renderer.canvas.Layer}
 * @param {ol.layer.Vector} vectorLayer Vector layer.
 */
ol.renderer.canvas.VectorLayer = function(vectorLayer) {

  ol.renderer.canvas.Layer.call(this, vectorLayer);

  /**
   * @private
   * @type {boolean}
   */
  this.dirty_ = false;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.renderedResolution_ = NaN;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedExtent_ = ol.extent.createEmpty();

  /**
   * @private
   * @type {function(ol.Feature, ol.Feature): number|null}
   */
  this.renderedRenderOrder_ = null;

  /**
   * @private
   * @type {ol.render.canvas.ReplayGroup}
   */
  this.replayGroup_ = null;

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = ol.dom.createCanvasContext2D();

};
ol.inherits(ol.renderer.canvas.VectorLayer, ol.renderer.canvas.Layer);


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {

  var extent = frameState.extent;
  var pixelRatio = frameState.pixelRatio;
  var skippedFeatureUids = layerState.managed ?
      frameState.skippedFeatureUids : {};
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var rotation = viewState.rotation;
  var projectionExtent = projection.getExtent();
  var vectorSource = /** @type {ol.source.Vector} */ (this.getLayer().getSource());

  var transform = this.getTransform(frameState, 0);

  this.dispatchPreComposeEvent(context, frameState, transform);

  // clipped rendering if layer extent is set
  var clipExtent = layerState.extent;
  var clipped = clipExtent !== undefined;
  if (clipped) {
    this.clip(context, frameState,  /** @type {ol.Extent} */ (clipExtent));
  }
  var replayGroup = this.replayGroup_;
  if (replayGroup && !replayGroup.isEmpty()) {
    var layer = this.getLayer();
    var drawOffsetX = 0;
    var drawOffsetY = 0;
    var replayContext;
    if (layer.hasListener(ol.render.EventType.RENDER)) {
      var drawWidth = context.canvas.width;
      var drawHeight = context.canvas.height;
      if (rotation) {
        var drawSize = Math.round(Math.sqrt(drawWidth * drawWidth + drawHeight * drawHeight));
        drawOffsetX = (drawSize - drawWidth) / 2;
        drawOffsetY = (drawSize - drawHeight) / 2;
        drawWidth = drawHeight = drawSize;
      }
      // resize and clear
      this.context_.canvas.width = drawWidth;
      this.context_.canvas.height = drawHeight;
      replayContext = this.context_;
    } else {
      replayContext = context;
    }
    // for performance reasons, context.save / context.restore is not used
    // to save and restore the transformation matrix and the opacity.
    // see http://jsperf.com/context-save-restore-versus-variable
    var alpha = replayContext.globalAlpha;
    replayContext.globalAlpha = layerState.opacity;
    if (replayContext != context) {
      replayContext.translate(drawOffsetX, drawOffsetY);
    }

    var width = frameState.size[0] * pixelRatio;
    var height = frameState.size[1] * pixelRatio;
    ol.render.canvas.rotateAtOffset(replayContext, -rotation,
        width / 2, height / 2);
    replayGroup.replay(replayContext, pixelRatio, transform, rotation,
        skippedFeatureUids);
    if (vectorSource.getWrapX() && projection.canWrapX() &&
        !ol.extent.containsExtent(projectionExtent, extent)) {
      var startX = extent[0];
      var worldWidth = ol.extent.getWidth(projectionExtent);
      var world = 0;
      var offsetX;
      while (startX < projectionExtent[0]) {
        --world;
        offsetX = worldWidth * world;
        transform = this.getTransform(frameState, offsetX);
        replayGroup.replay(replayContext, pixelRatio, transform, rotation,
            skippedFeatureUids);
        startX += worldWidth;
      }
      world = 0;
      startX = extent[2];
      while (startX > projectionExtent[2]) {
        ++world;
        offsetX = worldWidth * world;
        transform = this.getTransform(frameState, offsetX);
        replayGroup.replay(replayContext, pixelRatio, transform, rotation,
            skippedFeatureUids);
        startX -= worldWidth;
      }
      // restore original transform for render and compose events
      transform = this.getTransform(frameState, 0);
    }
    ol.render.canvas.rotateAtOffset(replayContext, rotation,
        width / 2, height / 2);

    if (replayContext != context) {
      this.dispatchRenderEvent(replayContext, frameState, transform);
      context.drawImage(replayContext.canvas, -drawOffsetX, -drawOffsetY);
      replayContext.translate(-drawOffsetX, -drawOffsetY);
    }
    replayContext.globalAlpha = alpha;
  }

  if (clipped) {
    context.restore();
  }
  this.dispatchPostComposeEvent(context, frameState, transform);

};


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  if (!this.replayGroup_) {
    return undefined;
  } else {
    var resolution = frameState.viewState.resolution;
    var rotation = frameState.viewState.rotation;
    var layer = this.getLayer();
    /** @type {Object.<string, boolean>} */
    var features = {};
    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
        rotation, {},
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {?} Callback result.
         */
        function(feature) {
          var key = ol.getUid(feature).toString();
          if (!(key in features)) {
            features[key] = true;
            return callback.call(thisArg, feature, layer);
          }
        });
  }
};


/**
 * Handle changes in image style state.
 * @param {ol.events.Event} event Image style change event.
 * @private
 */
ol.renderer.canvas.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
  this.renderIfReadyAndVisible();
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {

  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
  var vectorSource = vectorLayer.getSource();

  this.updateAttributions(
      frameState.attributions, vectorSource.getAttributions());
  this.updateLogos(frameState, vectorSource);

  var animating = frameState.viewHints[ol.View.Hint.ANIMATING];
  var interacting = frameState.viewHints[ol.View.Hint.INTERACTING];
  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();

  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
      (!updateWhileInteracting && interacting)) {
    return true;
  }

  var frameStateExtent = frameState.extent;
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var resolution = viewState.resolution;
  var pixelRatio = frameState.pixelRatio;
  var vectorLayerRevision = vectorLayer.getRevision();
  var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer();
  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();

  if (vectorLayerRenderOrder === undefined) {
    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
  }

  var extent = ol.extent.buffer(frameStateExtent,
      vectorLayerRenderBuffer * resolution);
  var projectionExtent = viewState.projection.getExtent();

  if (vectorSource.getWrapX() && viewState.projection.canWrapX() &&
      !ol.extent.containsExtent(projectionExtent, frameState.extent)) {
    // For the replay group, we need an extent that intersects the real world
    // (-180° to +180°). To support geometries in a coordinate range from -540°
    // to +540°, we add at least 1 world width on each side of the projection
    // extent. If the viewport is wider than the world, we need to add half of
    // the viewport width to make sure we cover the whole viewport.
    var worldWidth = ol.extent.getWidth(projectionExtent);
    var buffer = Math.max(ol.extent.getWidth(extent) / 2, worldWidth);
    extent[0] = projectionExtent[0] - buffer;
    extent[2] = projectionExtent[2] + buffer;
  }

  if (!this.dirty_ &&
      this.renderedResolution_ == resolution &&
      this.renderedRevision_ == vectorLayerRevision &&
      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
      ol.extent.containsExtent(this.renderedExtent_, extent)) {
    return true;
  }

  this.replayGroup_ = null;

  this.dirty_ = false;

  var replayGroup =
      new ol.render.canvas.ReplayGroup(
          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
          resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer());
  vectorSource.loadFeatures(extent, resolution, projection);
  /**
   * @param {ol.Feature} feature Feature.
   * @this {ol.renderer.canvas.VectorLayer}
   */
  var renderFeature = function(feature) {
    var styles;
    var styleFunction = feature.getStyleFunction();
    if (styleFunction) {
      styles = styleFunction.call(feature, resolution);
    } else {
      styleFunction = vectorLayer.getStyleFunction();
      if (styleFunction) {
        styles = styleFunction(feature, resolution);
      }
    }
    if (styles) {
      var dirty = this.renderFeature(
          feature, resolution, pixelRatio, styles, replayGroup);
      this.dirty_ = this.dirty_ || dirty;
    }
  };
  if (vectorLayerRenderOrder) {
    /** @type {Array.<ol.Feature>} */
    var features = [];
    vectorSource.forEachFeatureInExtent(extent,
        /**
         * @param {ol.Feature} feature Feature.
         */
        function(feature) {
          features.push(feature);
        }, this);
    features.sort(vectorLayerRenderOrder);
    features.forEach(renderFeature, this);
  } else {
    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
  }
  replayGroup.finish();

  this.renderedResolution_ = resolution;
  this.renderedRevision_ = vectorLayerRevision;
  this.renderedRenderOrder_ = vectorLayerRenderOrder;
  this.renderedExtent_ = extent;
  this.replayGroup_ = replayGroup;

  return true;
};


/**
 * @param {ol.Feature} feature Feature.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
ol.renderer.canvas.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
  if (!styles) {
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    for (var i = 0, ii = styles.length; i < ii; ++i) {
      loading = ol.renderer.vector.renderFeature(
          replayGroup, feature, styles[i],
          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
          this.handleStyleImageChange_, this) || loading;
    }
  } else {
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles,
        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
        this.handleStyleImageChange_, this) || loading;
  }
  return loading;
};

goog.provide('ol.renderer.canvas.VectorTileLayer');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.proj.Units');
goog.require('ol.render.EventType');
goog.require('ol.render.ReplayType');
goog.require('ol.render.canvas');
goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.render.replay');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.vector');
goog.require('ol.size');
goog.require('ol.transform');


/**
 * @const
 * @type {!Object.<string, Array.<ol.render.ReplayType>>}
 */
ol.renderer.canvas.IMAGE_REPLAYS = {
  'image': ol.render.replay.ORDER,
  'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
};


/**
 * @const
 * @type {!Object.<string, Array.<ol.render.ReplayType>>}
 */
ol.renderer.canvas.VECTOR_REPLAYS = {
  'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
  'vector': ol.render.replay.ORDER
};


/**
 * @constructor
 * @extends {ol.renderer.canvas.TileLayer}
 * @param {ol.layer.VectorTile} layer VectorTile layer.
 */
ol.renderer.canvas.VectorTileLayer = function(layer) {

  ol.renderer.canvas.TileLayer.call(this, layer);

  /**
   * @private
   * @type {boolean}
   */
  this.dirty_ = false;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.tmpTransform_ = ol.transform.create();

  // Use lower resolution for pure vector rendering. Closest resolution otherwise.
  this.zDirection =
      layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;

};
ol.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(
    frameState, layerState, context) {
  var transform = this.getTransform(frameState, 0);
  this.dispatchPreComposeEvent(context, frameState, transform);

  // clipped rendering if layer extent is set
  var extent = layerState.extent;
  var clipped = extent !== undefined;
  if (clipped) {
    this.clip(context, frameState,  /** @type {ol.Extent} */ (extent));
  }

  var renderMode = this.getLayer().getRenderMode();
  if (renderMode !== ol.layer.VectorTileRenderType.VECTOR) {
    this.renderTileImages(context, frameState, layerState);
  }
  if (renderMode !== ol.layer.VectorTileRenderType.IMAGE) {
    this.renderTileReplays_(context, frameState, layerState);
  }

  if (clipped) {
    context.restore();
  }

  this.dispatchPostComposeEvent(context, frameState, transform);
};


/**
 * @param {CanvasRenderingContext2D} context Context.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @private
 */
ol.renderer.canvas.VectorTileLayer.prototype.renderTileReplays_ = function(
    context, frameState, layerState) {

  var layer = this.getLayer();
  var replays = ol.renderer.canvas.VECTOR_REPLAYS[layer.getRenderMode()];
  var pixelRatio = frameState.pixelRatio;
  var skippedFeatureUids = layerState.managed ?
      frameState.skippedFeatureUids : {};
  var viewState = frameState.viewState;
  var center = viewState.center;
  var resolution = viewState.resolution;
  var rotation = viewState.rotation;
  var size = frameState.size;
  var pixelScale = pixelRatio / resolution;
  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
  var tilePixelRatio = source.getTilePixelRatio();

  var transform = this.getTransform(frameState, 0);

  var replayContext;
  if (layer.hasListener(ol.render.EventType.RENDER)) {
    // resize and clear
    this.context.canvas.width = context.canvas.width;
    this.context.canvas.height = context.canvas.height;
    replayContext = this.context;
  } else {
    replayContext = context;
  }
  // for performance reasons, context.save / context.restore is not used
  // to save and restore the transformation matrix and the opacity.
  // see http://jsperf.com/context-save-restore-versus-variable
  var alpha = replayContext.globalAlpha;
  replayContext.globalAlpha = layerState.opacity;

  var tilesToDraw = this.renderedTiles;
  var tileGrid = source.getTileGrid();

  var currentZ, i, ii, offsetX, offsetY, origin, pixelSpace, replayState;
  var tile, tileExtent, tilePixelResolution, tileResolution, tileTransform;
  for (i = 0, ii = tilesToDraw.length; i < ii; ++i) {
    tile = tilesToDraw[i];
    replayState = tile.getReplayState();
    tileExtent = tileGrid.getTileCoordExtent(
        tile.getTileCoord(), this.tmpExtent);
    currentZ = tile.getTileCoord()[0];
    pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
    tileResolution = tileGrid.getResolution(currentZ);
    tilePixelResolution = tileResolution / tilePixelRatio;
    offsetX = Math.round(pixelRatio * size[0] / 2);
    offsetY = Math.round(pixelRatio * size[1] / 2);

    if (pixelSpace) {
      origin = ol.extent.getTopLeft(tileExtent);
      tileTransform = ol.transform.reset(this.tmpTransform_);
      tileTransform = ol.transform.compose(this.tmpTransform_,
          offsetX, offsetY,
          pixelScale * tilePixelResolution, pixelScale * tilePixelResolution,
          rotation,
          (origin[0] - center[0]) / tilePixelResolution, (center[1] - origin[1]) / tilePixelResolution);
    } else {
      tileTransform = transform;
    }
    ol.render.canvas.rotateAtOffset(replayContext, -rotation, offsetX, offsetY);
    replayState.replayGroup.replay(replayContext, pixelRatio,
        tileTransform, rotation, skippedFeatureUids, replays);
    ol.render.canvas.rotateAtOffset(replayContext, rotation, offsetX, offsetY);
  }

  if (replayContext != context) {
    this.dispatchRenderEvent(replayContext, frameState, transform);
    context.drawImage(replayContext.canvas, 0, 0);
  }
  replayContext.globalAlpha = alpha;
};


/**
 * @param {ol.VectorTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 */
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
    frameState) {
  var layer = this.getLayer();
  var pixelRatio = frameState.pixelRatio;
  var projection = frameState.viewState.projection;
  var revision = layer.getRevision();
  var renderOrder = layer.getRenderOrder() || null;

  var replayState = tile.getReplayState();
  if (!replayState.dirty && replayState.renderedRevision == revision &&
      replayState.renderedRenderOrder == renderOrder) {
    return;
  }

  replayState.replayGroup = null;
  replayState.dirty = false;

  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
  var tileGrid = source.getTileGrid();
  var tileCoord = tile.getTileCoord();
  var tileProjection = tile.getProjection();
  var pixelSpace = tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS;
  var resolution = tileGrid.getResolution(tileCoord[0]);
  var extent, reproject, tileResolution;
  if (pixelSpace) {
    var tilePixelRatio = tileResolution = source.getTilePixelRatio();
    var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0]));
    extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio];
  } else {
    tileResolution = resolution;
    extent = tileGrid.getTileCoordExtent(tileCoord);
    if (!ol.proj.equivalent(projection, tileProjection)) {
      reproject = true;
      tile.setProjection(projection);
    }
  }
  replayState.dirty = false;
  var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
      tileResolution, source.getOverlaps(), layer.getRenderBuffer());
  var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
      tileResolution, pixelRatio);

  /**
   * @param {ol.Feature|ol.render.Feature} feature Feature.
   * @this {ol.renderer.canvas.VectorTileLayer}
   */
  function renderFeature(feature) {
    var styles;
    var styleFunction = feature.getStyleFunction();
    if (styleFunction) {
      styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
    } else {
      styleFunction = layer.getStyleFunction();
      if (styleFunction) {
        styles = styleFunction(feature, resolution);
      }
    }
    if (styles) {
      if (!Array.isArray(styles)) {
        styles = [styles];
      }
      var dirty = this.renderFeature(feature, squaredTolerance, styles,
          replayGroup);
      this.dirty_ = this.dirty_ || dirty;
      replayState.dirty = replayState.dirty || dirty;
    }
  }

  var features = tile.getFeatures();
  if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
    features.sort(renderOrder);
  }
  var feature;
  for (var i = 0, ii = features.length; i < ii; ++i) {
    feature = features[i];
    if (reproject) {
      feature.getGeometry().transform(tileProjection, projection);
    }
    renderFeature.call(this, feature);
  }

  replayGroup.finish();

  replayState.renderedRevision = revision;
  replayState.renderedRenderOrder = renderOrder;
  replayState.replayGroup = replayGroup;
  replayState.resolution = NaN;
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  var resolution = frameState.viewState.resolution;
  var rotation = frameState.viewState.rotation;
  var layer = this.getLayer();
  /** @type {Object.<string, boolean>} */
  var features = {};

  var replayables = this.renderedTiles;
  var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
  var tileGrid = source.getTileGrid();
  var found, tileSpaceCoordinate;
  var i, ii, origin, replayGroup;
  var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution;
  for (i = 0, ii = replayables.length; i < ii; ++i) {
    tile = replayables[i];
    tileCoord = tile.getTileCoord();
    tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord,
        this.tmpExtent);
    if (!ol.extent.containsCoordinate(tileExtent, coordinate)) {
      continue;
    }
    if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
      origin = ol.extent.getTopLeft(tileExtent);
      tilePixelRatio = source.getTilePixelRatio();
      tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio;
      tileSpaceCoordinate = [
        (coordinate[0] - origin[0]) / tileResolution,
        (origin[1] - coordinate[1]) / tileResolution
      ];
      resolution = tilePixelRatio;
    } else {
      tileSpaceCoordinate = coordinate;
    }
    replayGroup = tile.getReplayState().replayGroup;
    found = found || replayGroup.forEachFeatureAtCoordinate(
        tileSpaceCoordinate, resolution, rotation, {},
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {?} Callback result.
         */
        function(feature) {
          var key = ol.getUid(feature).toString();
          if (!(key in features)) {
            features[key] = true;
            return callback.call(thisArg, feature, layer);
          }
        });
  }
  return found;
};


/**
 * Handle changes in image style state.
 * @param {ol.events.Event} event Image style change event.
 * @private
 */
ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(event) {
  this.renderIfReadyAndVisible();
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, layerState) {
  var prepared = ol.renderer.canvas.TileLayer.prototype.prepareFrame.call(this, frameState, layerState);
  if (prepared) {
    var skippedFeatures = Object.keys(frameState.skippedFeatureUids_ || {});
    for (var i = 0, ii = this.renderedTiles.length; i < ii; ++i) {
      var tile = /** @type {ol.VectorTile} */ (this.renderedTiles[i]);
      this.createReplayGroup(tile, frameState);
      this.renderTileImage_(tile, frameState, layerState, skippedFeatures);
    }
  }
  return prepared;
};


/**
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) {
  if (!styles) {
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    for (var i = 0, ii = styles.length; i < ii; ++i) {
      loading = ol.renderer.vector.renderFeature(
          replayGroup, feature, styles[i], squaredTolerance,
          this.handleStyleImageChange_, this) || loading;
    }
  } else {
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles, squaredTolerance,
        this.handleStyleImageChange_, this) || loading;
  }
  return loading;
};


/**
 * @param {ol.VectorTile} tile Tile.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {Array.<string>} skippedFeatures Skipped features.
 * @private
 */
ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
    tile, frameState, layerState, skippedFeatures) {
  var layer = this.getLayer();
  var replays = ol.renderer.canvas.IMAGE_REPLAYS[layer.getRenderMode()];
  if (!replays) {
    // do not create an image in 'vector' mode
    return;
  }
  var pixelRatio = frameState.pixelRatio;
  var replayState = tile.getReplayState();
  var revision = layer.getRevision();
  if (!ol.array.equals(replayState.skippedFeatures, skippedFeatures) ||
      replayState.renderedTileRevision !== revision) {
    replayState.skippedFeatures = skippedFeatures;
    replayState.renderedTileRevision = revision;
    var tileContext = tile.getContext();
    var source = layer.getSource();
    var tileGrid = source.getTileGrid();
    var currentZ = tile.getTileCoord()[0];
    var resolution = tileGrid.getResolution(currentZ);
    var tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ));
    var tileResolution = tileGrid.getResolution(currentZ);
    var resolutionRatio = tileResolution / resolution;
    var width = tileSize[0] * pixelRatio * resolutionRatio;
    var height = tileSize[1] * pixelRatio * resolutionRatio;
    tileContext.canvas.width = width / resolutionRatio + 0.5;
    tileContext.canvas.height = height / resolutionRatio + 0.5;
    tileContext.scale(1 / resolutionRatio, 1 / resolutionRatio);
    tileContext.translate(width / 2, height / 2);
    var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
    var pixelScale = pixelRatio / resolution;
    var tilePixelRatio = source.getTilePixelRatio();
    var tilePixelResolution = tileResolution / tilePixelRatio;
    var tileExtent = tileGrid.getTileCoordExtent(
        tile.getTileCoord(), this.tmpExtent);
    var tileTransform = ol.transform.reset(this.tmpTransform_);
    if (pixelSpace) {
      ol.transform.scale(tileTransform,
          pixelScale * tilePixelResolution, pixelScale * tilePixelResolution);
      ol.transform.translate(tileTransform,
          -tileSize[0] * tilePixelRatio / 2, -tileSize[1] * tilePixelRatio / 2);
    } else {
      var tileCenter = ol.extent.getCenter(tileExtent);
      ol.transform.scale(tileTransform, pixelScale, -pixelScale);
      ol.transform.translate(tileTransform, -tileCenter[0], -tileCenter[1]);
    }

    replayState.replayGroup.replay(tileContext, pixelRatio,
        tileTransform, 0, frameState.skippedFeatureUids || {}, replays);
  }
};

// FIXME offset panning

goog.provide('ol.renderer.canvas.Map');

goog.require('ol.transform');
goog.require('ol');
goog.require('ol.RendererType');
goog.require('ol.array');
goog.require('ol.css');
goog.require('ol.dom');
goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorTile');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas');
goog.require('ol.render.canvas.Immediate');
goog.require('ol.renderer.Map');
goog.require('ol.renderer.canvas.ImageLayer');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.canvas.VectorLayer');
goog.require('ol.renderer.canvas.VectorTileLayer');
goog.require('ol.source.State');


/**
 * @constructor
 * @extends {ol.renderer.Map}
 * @param {Element} container Container.
 * @param {ol.Map} map Map.
 */
ol.renderer.canvas.Map = function(container, map) {

  ol.renderer.Map.call(this, container, map);

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = ol.dom.createCanvasContext2D();

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = this.context_.canvas;

  this.canvas_.style.width = '100%';
  this.canvas_.style.height = '100%';
  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
  container.insertBefore(this.canvas_, container.childNodes[0] || null);

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = true;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

};
ol.inherits(ol.renderer.canvas.Map, ol.renderer.Map);


/**
 * @inheritDoc
 */
ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
    return new ol.renderer.canvas.ImageLayer(layer);
  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
    return new ol.renderer.canvas.TileLayer(layer);
  } else if (ol.ENABLE_VECTOR_TILE && layer instanceof ol.layer.VectorTile) {
    return new ol.renderer.canvas.VectorTileLayer(layer);
  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
    return new ol.renderer.canvas.VectorLayer(layer);
  } else {
    goog.DEBUG && console.assert(false, 'unexpected layer configuration');
    return null;
  }
};


/**
 * @param {ol.render.EventType} type Event type.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.canvas.Map.prototype.dispatchComposeEvent_ = function(type, frameState) {
  var map = this.getMap();
  var context = this.context_;
  if (map.hasListener(type)) {
    var extent = frameState.extent;
    var pixelRatio = frameState.pixelRatio;
    var viewState = frameState.viewState;
    var rotation = viewState.rotation;

    var transform = this.getTransform(frameState);

    var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
        extent, transform, rotation);
    var composeEvent = new ol.render.Event(type, vectorContext,
        frameState, context, null);
    map.dispatchEvent(composeEvent);
  }
};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @protected
 * @return {!ol.Transform} Transform.
 */
ol.renderer.canvas.Map.prototype.getTransform = function(frameState) {
  var viewState = frameState.viewState;
  var dx1 = this.canvas_.width / 2;
  var dy1 = this.canvas_.height / 2;
  var sx = frameState.pixelRatio / viewState.resolution;
  var sy = -sx;
  var angle = -viewState.rotation;
  var dx2 = -viewState.center[0];
  var dy2 = -viewState.center[1];
  return ol.transform.compose(this.transform_, dx1, dy1, sx, sy, angle, dx2, dy2);
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.Map.prototype.getType = function() {
  return ol.RendererType.CANVAS;
};


/**
 * @inheritDoc
 */
ol.renderer.canvas.Map.prototype.renderFrame = function(frameState) {

  if (!frameState) {
    if (this.renderedVisible_) {
      this.canvas_.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return;
  }

  var context = this.context_;
  var pixelRatio = frameState.pixelRatio;
  var width = Math.round(frameState.size[0] * pixelRatio);
  var height = Math.round(frameState.size[1] * pixelRatio);
  if (this.canvas_.width != width || this.canvas_.height != height) {
    this.canvas_.width = width;
    this.canvas_.height = height;
  } else {
    context.clearRect(0, 0, width, height);
  }

  var rotation = frameState.viewState.rotation;

  this.calculateMatrices2D(frameState);

  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);

  var layerStatesArray = frameState.layerStatesArray;
  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);

  ol.render.canvas.rotateAtOffset(context, rotation, width / 2, height / 2);

  var viewResolution = frameState.viewState.resolution;
  var i, ii, layer, layerRenderer, layerState;
  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
    layerState = layerStatesArray[i];
    layer = layerState.layer;
    layerRenderer = /** @type {ol.renderer.canvas.Layer} */ (this.getLayerRenderer(layer));
    if (!ol.layer.Layer.visibleAtResolution(layerState, viewResolution) ||
        layerState.sourceState != ol.source.State.READY) {
      continue;
    }
    if (layerRenderer.prepareFrame(frameState, layerState)) {
      layerRenderer.composeFrame(frameState, layerState, context);
    }
  }

  ol.render.canvas.rotateAtOffset(context, -rotation, width / 2, height / 2);

  this.dispatchComposeEvent_(
      ol.render.EventType.POSTCOMPOSE, frameState);

  if (!this.renderedVisible_) {
    this.canvas_.style.display = '';
    this.renderedVisible_ = true;
  }

  this.scheduleRemoveUnusedLayerRenderers(frameState);
  this.scheduleExpireIconCache(frameState);
};

goog.provide('ol.renderer.dom.Layer');

goog.require('ol');
goog.require('ol.renderer.Layer');


/**
 * @constructor
 * @extends {ol.renderer.Layer}
 * @param {ol.layer.Layer} layer Layer.
 * @param {!Element} target Target.
 */
ol.renderer.dom.Layer = function(layer, target) {

  ol.renderer.Layer.call(this, layer);

  /**
   * @type {!Element}
   * @protected
   */
  this.target = target;

};
ol.inherits(ol.renderer.dom.Layer, ol.renderer.Layer);


/**
 * Clear rendered elements.
 */
ol.renderer.dom.Layer.prototype.clearFrame = ol.nullFunction;


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 */
ol.renderer.dom.Layer.prototype.composeFrame = ol.nullFunction;


/**
 * @return {!Element} Target.
 */
ol.renderer.dom.Layer.prototype.getTarget = function() {
  return this.target;
};


/**
 * @abstract
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @return {boolean} whether composeFrame should be called.
 */
ol.renderer.dom.Layer.prototype.prepareFrame = function(frameState, layerState) {};

goog.provide('ol.renderer.dom.ImageLayer');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.array');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.renderer.dom.Layer');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.renderer.dom.Layer}
 * @param {ol.layer.Image} imageLayer Image layer.
 */
ol.renderer.dom.ImageLayer = function(imageLayer) {
  var target = document.createElement('DIV');
  target.style.position = 'absolute';

  ol.renderer.dom.Layer.call(this, imageLayer, target);

  /**
   * The last rendered image.
   * @private
   * @type {?ol.ImageBase}
   */
  this.image_ = null;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

};
ol.inherits(ol.renderer.dom.ImageLayer, ol.renderer.dom.Layer);


/**
 * @inheritDoc
 */
ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  var layer = this.getLayer();
  var source = layer.getSource();
  var resolution = frameState.viewState.resolution;
  var rotation = frameState.viewState.rotation;
  var skippedFeatureUids = frameState.skippedFeatureUids;
  return source.forEachFeatureAtCoordinate(
      coordinate, resolution, rotation, skippedFeatureUids,
      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {?} Callback result.
       */
      function(feature) {
        return callback.call(thisArg, feature, layer);
      });
};


/**
 * @inheritDoc
 */
ol.renderer.dom.ImageLayer.prototype.clearFrame = function() {
  ol.dom.removeChildren(this.target);
  this.image_ = null;
};


/**
 * @inheritDoc
 */
ol.renderer.dom.ImageLayer.prototype.prepareFrame = function(frameState, layerState) {

  var viewState = frameState.viewState;
  var viewCenter = viewState.center;
  var viewResolution = viewState.resolution;
  var viewRotation = viewState.rotation;

  var image = this.image_;
  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
  var imageSource = imageLayer.getSource();

  var hints = frameState.viewHints;

  var renderedExtent = frameState.extent;
  if (layerState.extent !== undefined) {
    renderedExtent = ol.extent.getIntersection(
        renderedExtent, layerState.extent);
  }

  if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] &&
      !ol.extent.isEmpty(renderedExtent)) {
    var projection = viewState.projection;
    if (!ol.ENABLE_RASTER_REPROJECTION) {
      var sourceProjection = imageSource.getProjection();
      if (sourceProjection) {
        goog.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection),
            'projection and sourceProjection are equivalent');
        projection = sourceProjection;
      }
    }
    var image_ = imageSource.getImage(renderedExtent, viewResolution,
        frameState.pixelRatio, projection);
    if (image_) {
      var loaded = this.loadImage(image_);
      if (loaded) {
        image = image_;
      }
    }
  }

  if (image) {
    var imageExtent = image.getExtent();
    var imageResolution = image.getResolution();
    var transform = ol.transform.create();
    ol.transform.compose(transform,
        frameState.size[0] / 2, frameState.size[1] / 2,
        imageResolution / viewResolution, imageResolution / viewResolution,
        viewRotation,
        (imageExtent[0] - viewCenter[0]) / imageResolution,
        (viewCenter[1] - imageExtent[3]) / imageResolution);

    if (image != this.image_) {
      var imageElement = image.getImage(this);
      // Bootstrap sets the style max-width: 100% for all images, which breaks
      // prevents the image from being displayed in FireFox.  Workaround by
      // overriding the max-width style.
      imageElement.style.maxWidth = 'none';
      imageElement.style.position = 'absolute';
      ol.dom.removeChildren(this.target);
      this.target.appendChild(imageElement);
      this.image_ = image;
    }
    this.setTransform_(transform);
    this.updateAttributions(frameState.attributions, image.getAttributions());
    this.updateLogos(frameState, imageSource);
  }

  return true;
};


/**
 * @param {ol.Transform} transform Transform.
 * @private
 */
ol.renderer.dom.ImageLayer.prototype.setTransform_ = function(transform) {
  if (!ol.array.equals(transform, this.transform_)) {
    ol.dom.transformElement2D(this.target, transform, 6);
    ol.transform.setFromArray(this.transform_, transform);
  }
};

// FIXME probably need to reset TileLayerZ if offsets get too large
// FIXME when zooming out, preserve higher Z divs to avoid white flash

goog.provide('ol.renderer.dom.TileLayer');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.TileRange');
goog.require('ol.View');
goog.require('ol.array');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.renderer.dom.Layer');
goog.require('ol.size');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.renderer.dom.Layer}
 * @param {ol.layer.Tile} tileLayer Tile layer.
 */
ol.renderer.dom.TileLayer = function(tileLayer) {

  var target = document.createElement('DIV');
  target.style.position = 'absolute';

  ol.renderer.dom.Layer.call(this, tileLayer, target);

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = true;

  /**
   * @private
   * @type {number}
   */
  this.renderedOpacity_ = 1;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = 0;

  /**
   * @private
   * @type {!Object.<number, ol.renderer.dom.TileLayerZ_>}
   */
  this.tileLayerZs_ = {};

};
ol.inherits(ol.renderer.dom.TileLayer, ol.renderer.dom.Layer);


/**
 * @inheritDoc
 */
ol.renderer.dom.TileLayer.prototype.clearFrame = function() {
  ol.dom.removeChildren(this.target);
  this.renderedRevision_ = 0;
};


/**
 * @inheritDoc
 */
ol.renderer.dom.TileLayer.prototype.prepareFrame = function(frameState, layerState) {

  if (!layerState.visible) {
    if (this.renderedVisible_) {
      this.target.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return true;
  }

  var pixelRatio = frameState.pixelRatio;
  var viewState = frameState.viewState;
  var projection = viewState.projection;

  var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer());
  var tileSource = tileLayer.getSource();
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var tileGutter = pixelRatio * tileSource.getGutter(projection);
  var z = tileGrid.getZForResolution(viewState.resolution);
  var tileResolution = tileGrid.getResolution(z);
  var center = viewState.center;
  var extent;
  if (tileResolution == viewState.resolution) {
    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
    extent = ol.extent.getForViewAndSize(
        center, tileResolution, viewState.rotation, frameState.size);
  } else {
    extent = frameState.extent;
  }

  if (layerState.extent !== undefined) {
    extent = ol.extent.getIntersection(extent, layerState.extent);
  }

  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      extent, tileResolution);

  /** @type {Object.<number, Object.<string, ol.Tile>>} */
  var tilesToDrawByZ = {};
  tilesToDrawByZ[z] = {};

  var findLoadedTiles = this.createLoadedTileFinder(
      tileSource, projection, tilesToDrawByZ);

  var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();

  var tmpExtent = ol.extent.createEmpty();
  var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
  var childTileRange, drawable, fullyLoaded, tile, tileState, x, y;
  for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
    for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
      tile = tileSource.getTile(z, x, y, pixelRatio, projection);
      tileState = tile.getState();
      drawable = tileState == ol.Tile.State.LOADED ||
          tileState == ol.Tile.State.EMPTY ||
          tileState == ol.Tile.State.ERROR && !useInterimTilesOnError;
      if (!drawable && tile.interimTile) {
        tile = tile.interimTile;
      }
      tileState = tile.getState();
      if (tileState == ol.Tile.State.LOADED) {
        tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
        continue;
      } else if (tileState == ol.Tile.State.EMPTY ||
                 (tileState == ol.Tile.State.ERROR &&
                  !useInterimTilesOnError)) {
        continue;
      }
      fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
          tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
      if (!fullyLoaded) {
        childTileRange = tileGrid.getTileCoordChildTileRange(
            tile.tileCoord, tmpTileRange, tmpExtent);
        if (childTileRange) {
          findLoadedTiles(z + 1, childTileRange);
        }
      }

    }

  }

  // If the tile source revision changes, we destroy the existing DOM structure
  // so that a new one will be created.  It would be more efficient to modify
  // the existing structure.
  var tileLayerZ, tileLayerZKey;
  if (this.renderedRevision_ != tileSource.getRevision()) {
    for (tileLayerZKey in this.tileLayerZs_) {
      tileLayerZ = this.tileLayerZs_[+tileLayerZKey];
      ol.dom.removeNode(tileLayerZ.target);
    }
    this.tileLayerZs_ = {};
    this.renderedRevision_ = tileSource.getRevision();
  }

  /** @type {Array.<number>} */
  var zs = Object.keys(tilesToDrawByZ).map(Number);
  zs.sort(ol.array.numberSafeCompareFunction);

  /** @type {Object.<number, boolean>} */
  var newTileLayerZKeys = {};

  var iz, iziz, tileCoordKey, tileCoordOrigin, tilesToDraw;
  for (iz = 0, iziz = zs.length; iz < iziz; ++iz) {
    tileLayerZKey = zs[iz];
    if (tileLayerZKey in this.tileLayerZs_) {
      tileLayerZ = this.tileLayerZs_[tileLayerZKey];
    } else {
      tileCoordOrigin =
          tileGrid.getTileCoordForCoordAndZ(center, tileLayerZKey);
      tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin);
      newTileLayerZKeys[tileLayerZKey] = true;
      this.tileLayerZs_[tileLayerZKey] = tileLayerZ;
    }
    tilesToDraw = tilesToDrawByZ[tileLayerZKey];
    for (tileCoordKey in tilesToDraw) {
      tileLayerZ.addTile(tilesToDraw[tileCoordKey], tileGutter);
    }
    tileLayerZ.finalizeAddTiles();
  }

  /** @type {Array.<number>} */
  var tileLayerZKeys = Object.keys(this.tileLayerZs_).map(Number);
  tileLayerZKeys.sort(ol.array.numberSafeCompareFunction);

  var i, ii, j, origin, resolution;
  var transform = ol.transform.create();
  for (i = 0, ii = tileLayerZKeys.length; i < ii; ++i) {
    tileLayerZKey = tileLayerZKeys[i];
    tileLayerZ = this.tileLayerZs_[tileLayerZKey];
    if (!(tileLayerZKey in tilesToDrawByZ)) {
      ol.dom.removeNode(tileLayerZ.target);
      delete this.tileLayerZs_[tileLayerZKey];
      continue;
    }
    resolution = tileLayerZ.getResolution();
    origin = tileLayerZ.getOrigin();

    ol.transform.compose(transform,
        frameState.size[0] / 2, frameState.size[1] / 2,
        resolution / viewState.resolution, resolution / viewState.resolution,
        viewState.rotation,
        (origin[0] - center[0]) / resolution, (center[1] - origin[1]) / resolution);

    tileLayerZ.setTransform(transform);
    if (tileLayerZKey in newTileLayerZKeys) {
      for (j = tileLayerZKey - 1; j >= 0; --j) {
        if (j in this.tileLayerZs_) {
          if (this.tileLayerZs_[j].target.parentNode) {
            this.tileLayerZs_[j].target.parentNode.insertBefore(tileLayerZ.target, this.tileLayerZs_[j].target.nextSibling);
          }
          break;
        }
      }
      if (j < 0) {
        this.target.insertBefore(tileLayerZ.target, this.target.childNodes[0] || null);
      }
    } else {
      if (!frameState.viewHints[ol.View.Hint.ANIMATING] &&
          !frameState.viewHints[ol.View.Hint.INTERACTING]) {
        tileLayerZ.removeTilesOutsideExtent(extent, tmpTileRange);
      }
    }
  }

  if (layerState.opacity != this.renderedOpacity_) {
    this.target.style.opacity = layerState.opacity;
    this.renderedOpacity_ = layerState.opacity;
  }

  if (layerState.visible && !this.renderedVisible_) {
    this.target.style.display = '';
    this.renderedVisible_ = true;
  }

  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
  this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
      projection, extent, z, tileLayer.getPreload());
  this.scheduleExpireCache(frameState, tileSource);
  this.updateLogos(frameState, tileSource);

  return true;
};


/**
 * @constructor
 * @private
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @param {ol.TileCoord} tileCoordOrigin Tile coord origin.
 */
ol.renderer.dom.TileLayerZ_ = function(tileGrid, tileCoordOrigin) {

  /**
   * @type {!Element}
   */
  this.target = document.createElement('DIV');
  this.target.style.position = 'absolute';
  this.target.style.width = '100%';
  this.target.style.height = '100%';

  /**
   * @private
   * @type {ol.tilegrid.TileGrid}
   */
  this.tileGrid_ = tileGrid;

  /**
   * @private
   * @type {ol.TileCoord}
   */
  this.tileCoordOrigin_ = tileCoordOrigin;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.origin_ =
      ol.extent.getTopLeft(tileGrid.getTileCoordExtent(tileCoordOrigin));

  /**
   * @private
   * @type {number}
   */
  this.resolution_ = tileGrid.getResolution(tileCoordOrigin[0]);

  /**
   * @private
   * @type {Object.<string, ol.Tile>}
   */
  this.tiles_ = {};

  /**
   * @private
   * @type {DocumentFragment}
   */
  this.documentFragment_ = null;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

  /**
   * @private
   * @type {ol.Size}
   */
  this.tmpSize_ = [0, 0];

};


/**
 * @param {ol.Tile} tile Tile.
 * @param {number} tileGutter Tile gutter.
 */
ol.renderer.dom.TileLayerZ_.prototype.addTile = function(tile, tileGutter) {
  var tileCoord = tile.tileCoord;
  var tileCoordZ = tileCoord[0];
  var tileCoordX = tileCoord[1];
  var tileCoordY = tileCoord[2];
  goog.DEBUG && console.assert(tileCoordZ == this.tileCoordOrigin_[0],
      'tileCoordZ matches z of tileCoordOrigin');
  var tileCoordKey = tileCoord.toString();
  if (tileCoordKey in this.tiles_) {
    return;
  }
  var tileSize = ol.size.toSize(
      this.tileGrid_.getTileSize(tileCoordZ), this.tmpSize_);
  var image = tile.getImage(this);
  var imageStyle = image.style;
  // Bootstrap sets the style max-width: 100% for all images, which
  // prevents the tile from being displayed in FireFox.  Workaround
  // by overriding the max-width style.
  imageStyle.maxWidth = 'none';
  var tileElement;
  var tileElementStyle;
  if (tileGutter > 0) {
    tileElement = document.createElement('DIV');
    tileElementStyle = tileElement.style;
    tileElementStyle.overflow = 'hidden';
    tileElementStyle.width = tileSize[0] + 'px';
    tileElementStyle.height = tileSize[1] + 'px';
    imageStyle.position = 'absolute';
    imageStyle.left = -tileGutter + 'px';
    imageStyle.top = -tileGutter + 'px';
    imageStyle.width = (tileSize[0] + 2 * tileGutter) + 'px';
    imageStyle.height = (tileSize[1] + 2 * tileGutter) + 'px';
    tileElement.appendChild(image);
  } else {
    imageStyle.width = tileSize[0] + 'px';
    imageStyle.height = tileSize[1] + 'px';
    tileElement = image;
    tileElementStyle = imageStyle;
  }
  tileElementStyle.position = 'absolute';
  tileElementStyle.left =
      ((tileCoordX - this.tileCoordOrigin_[1]) * tileSize[0]) + 'px';
  tileElementStyle.top =
      ((this.tileCoordOrigin_[2] - tileCoordY) * tileSize[1]) + 'px';
  if (!this.documentFragment_) {
    this.documentFragment_ = document.createDocumentFragment();
  }
  this.documentFragment_.appendChild(tileElement);
  this.tiles_[tileCoordKey] = tile;
};


/**
 * FIXME empty description for jsdoc
 */
ol.renderer.dom.TileLayerZ_.prototype.finalizeAddTiles = function() {
  if (this.documentFragment_) {
    this.target.appendChild(this.documentFragment_);
    this.documentFragment_ = null;
  }
};


/**
 * @return {ol.Coordinate} Origin.
 */
ol.renderer.dom.TileLayerZ_.prototype.getOrigin = function() {
  return this.origin_;
};


/**
 * @return {number} Resolution.
 */
ol.renderer.dom.TileLayerZ_.prototype.getResolution = function() {
  return this.resolution_;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.TileRange=} opt_tileRange Temporary ol.TileRange object.
 */
ol.renderer.dom.TileLayerZ_.prototype.removeTilesOutsideExtent = function(extent, opt_tileRange) {
  var tileRange = this.tileGrid_.getTileRangeForExtentAndZ(
      extent, this.tileCoordOrigin_[0], opt_tileRange);
  /** @type {Array.<ol.Tile>} */
  var tilesToRemove = [];
  var tile, tileCoordKey;
  for (tileCoordKey in this.tiles_) {
    tile = this.tiles_[tileCoordKey];
    if (!tileRange.contains(tile.tileCoord)) {
      tilesToRemove.push(tile);
    }
  }
  var i, ii;
  for (i = 0, ii = tilesToRemove.length; i < ii; ++i) {
    tile = tilesToRemove[i];
    tileCoordKey = tile.tileCoord.toString();
    ol.dom.removeNode(tile.getImage(this));
    delete this.tiles_[tileCoordKey];
  }
};


/**
 * @param {ol.Transform} transform Transform.
 */
ol.renderer.dom.TileLayerZ_.prototype.setTransform = function(transform) {
  if (!ol.array.equals(transform, this.transform_)) {
    ol.dom.transformElement2D(this.target, transform, 6);
    ol.transform.setFromArray(this.transform_, transform);
  }
};

goog.provide('ol.renderer.dom.VectorLayer');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas.Immediate');
goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.renderer.dom.Layer');
goog.require('ol.renderer.vector');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.renderer.dom.Layer}
 * @param {ol.layer.Vector} vectorLayer Vector layer.
 */
ol.renderer.dom.VectorLayer = function(vectorLayer) {

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = ol.dom.createCanvasContext2D();

  var target = this.context_.canvas;
  // Bootstrap sets the style max-width: 100% for all images, which breaks
  // prevents the image from being displayed in FireFox.  Workaround by
  // overriding the max-width style.
  target.style.maxWidth = 'none';
  target.style.position = 'absolute';

  ol.renderer.dom.Layer.call(this, vectorLayer, target);

  /**
   * @private
   * @type {boolean}
   */
  this.dirty_ = false;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.renderedResolution_ = NaN;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedExtent_ = ol.extent.createEmpty();

  /**
   * @private
   * @type {function(ol.Feature, ol.Feature): number|null}
   */
  this.renderedRenderOrder_ = null;

  /**
   * @private
   * @type {ol.render.canvas.ReplayGroup}
   */
  this.replayGroup_ = null;

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

  /**
   * @private
   * @type {ol.Transform}
   */
  this.elementTransform_ = ol.transform.create();

};
ol.inherits(ol.renderer.dom.VectorLayer, ol.renderer.dom.Layer);


/**
 * @inheritDoc
 */
ol.renderer.dom.VectorLayer.prototype.clearFrame = function() {
  // Clear the canvas
  var canvas = this.context_.canvas;
  canvas.width = canvas.width;
  this.renderedRevision_ = 0;
};


/**
 * @inheritDoc
 */
ol.renderer.dom.VectorLayer.prototype.composeFrame = function(frameState, layerState) {
  var viewState = frameState.viewState;
  var viewCenter = viewState.center;
  var viewRotation = viewState.rotation;
  var viewResolution = viewState.resolution;
  var pixelRatio = frameState.pixelRatio;
  var viewWidth = frameState.size[0];
  var viewHeight = frameState.size[1];
  var imageWidth = viewWidth * pixelRatio;
  var imageHeight = viewHeight * pixelRatio;

  var transform = ol.transform.compose(this.transform_,
      pixelRatio * viewWidth / 2, pixelRatio * viewHeight / 2,
      pixelRatio / viewResolution, -pixelRatio / viewResolution,
      -viewRotation,
      -viewCenter[0], -viewCenter[1]);

  var context = this.context_;

  // Clear the canvas and set the correct size
  context.canvas.width = imageWidth;
  context.canvas.height = imageHeight;

  var elementTransform = ol.transform.reset(this.elementTransform_);
  ol.transform.scale(elementTransform, 1 / pixelRatio, 1 / pixelRatio);
  ol.transform.translate(elementTransform,
      -(imageWidth - viewWidth) / 2 * pixelRatio,
      -(imageHeight - viewHeight) / 2 * pixelRatio);
  ol.dom.transformElement2D(context.canvas, elementTransform, 6);

  this.dispatchEvent_(ol.render.EventType.PRECOMPOSE, frameState, transform);

  var replayGroup = this.replayGroup_;

  if (replayGroup && !replayGroup.isEmpty()) {

    context.globalAlpha = layerState.opacity;
    replayGroup.replay(context, pixelRatio, transform, viewRotation,
        layerState.managed ? frameState.skippedFeatureUids : {});

    this.dispatchEvent_(ol.render.EventType.RENDER, frameState, transform);
  }

  this.dispatchEvent_(ol.render.EventType.POSTCOMPOSE, frameState, transform);
};


/**
 * @param {ol.render.EventType} type Event type.
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.Transform} transform Transform.
 * @private
 */
ol.renderer.dom.VectorLayer.prototype.dispatchEvent_ = function(type, frameState, transform) {
  var context = this.context_;
  var layer = this.getLayer();
  if (layer.hasListener(type)) {
    var render = new ol.render.canvas.Immediate(
        context, frameState.pixelRatio, frameState.extent, transform,
        frameState.viewState.rotation);
    var event = new ol.render.Event(type, render, frameState, context, null);
    layer.dispatchEvent(event);
  }
};


/**
 * @inheritDoc
 */
ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  if (!this.replayGroup_) {
    return undefined;
  } else {
    var resolution = frameState.viewState.resolution;
    var rotation = frameState.viewState.rotation;
    var layer = this.getLayer();
    /** @type {Object.<string, boolean>} */
    var features = {};
    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
        rotation, {},
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {?} Callback result.
         */
        function(feature) {
          var key = ol.getUid(feature).toString();
          if (!(key in features)) {
            features[key] = true;
            return callback.call(thisArg, feature, layer);
          }
        });
  }
};


/**
 * Handle changes in image style state.
 * @param {ol.events.Event} event Image style change event.
 * @private
 */
ol.renderer.dom.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
  this.renderIfReadyAndVisible();
};


/**
 * @inheritDoc
 */
ol.renderer.dom.VectorLayer.prototype.prepareFrame = function(frameState, layerState) {

  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
  var vectorSource = vectorLayer.getSource();

  this.updateAttributions(
      frameState.attributions, vectorSource.getAttributions());
  this.updateLogos(frameState, vectorSource);

  var animating = frameState.viewHints[ol.View.Hint.ANIMATING];
  var interacting = frameState.viewHints[ol.View.Hint.INTERACTING];
  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();

  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
      (!updateWhileInteracting && interacting)) {
    return true;
  }

  var frameStateExtent = frameState.extent;
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var resolution = viewState.resolution;
  var pixelRatio = frameState.pixelRatio;
  var vectorLayerRevision = vectorLayer.getRevision();
  var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer();
  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();

  if (vectorLayerRenderOrder === undefined) {
    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
  }

  var extent = ol.extent.buffer(frameStateExtent,
      vectorLayerRenderBuffer * resolution);

  if (!this.dirty_ &&
      this.renderedResolution_ == resolution &&
      this.renderedRevision_ == vectorLayerRevision &&
      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
      ol.extent.containsExtent(this.renderedExtent_, extent)) {
    return true;
  }

  this.replayGroup_ = null;

  this.dirty_ = false;

  var replayGroup =
      new ol.render.canvas.ReplayGroup(
          ol.renderer.vector.getTolerance(resolution, pixelRatio), extent,
          resolution, vectorSource.getOverlaps(), vectorLayer.getRenderBuffer());
  vectorSource.loadFeatures(extent, resolution, projection);
  /**
   * @param {ol.Feature} feature Feature.
   * @this {ol.renderer.dom.VectorLayer}
   */
  var renderFeature = function(feature) {
    var styles;
    var styleFunction = feature.getStyleFunction();
    if (styleFunction) {
      styles = styleFunction.call(feature, resolution);
    } else {
      styleFunction = vectorLayer.getStyleFunction();
      if (styleFunction) {
        styles = styleFunction(feature, resolution);
      }
    }
    if (styles) {
      var dirty = this.renderFeature(
          feature, resolution, pixelRatio, styles, replayGroup);
      this.dirty_ = this.dirty_ || dirty;
    }
  };
  if (vectorLayerRenderOrder) {
    /** @type {Array.<ol.Feature>} */
    var features = [];
    vectorSource.forEachFeatureInExtent(extent,
        /**
         * @param {ol.Feature} feature Feature.
         */
        function(feature) {
          features.push(feature);
        }, this);
    features.sort(vectorLayerRenderOrder);
    features.forEach(renderFeature, this);
  } else {
    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
  }
  replayGroup.finish();

  this.renderedResolution_ = resolution;
  this.renderedRevision_ = vectorLayerRevision;
  this.renderedRenderOrder_ = vectorLayerRenderOrder;
  this.renderedExtent_ = extent;
  this.replayGroup_ = replayGroup;

  return true;
};


/**
 * @param {ol.Feature} feature Feature.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
ol.renderer.dom.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
  if (!styles) {
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    for (var i = 0, ii = styles.length; i < ii; ++i) {
      loading = ol.renderer.vector.renderFeature(
          replayGroup, feature, styles[i],
          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
          this.handleStyleImageChange_, this) || loading;
    }
  } else {
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles,
        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
        this.handleStyleImageChange_, this) || loading;
  }
  return loading;
};

goog.provide('ol.renderer.dom.Map');

goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.transform');
goog.require('ol');
goog.require('ol.RendererType');
goog.require('ol.array');
goog.require('ol.css');
goog.require('ol.dom');
goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas.Immediate');
goog.require('ol.renderer.Map');
goog.require('ol.renderer.dom.ImageLayer');
goog.require('ol.renderer.dom.TileLayer');
goog.require('ol.renderer.dom.VectorLayer');
goog.require('ol.source.State');


/**
 * @constructor
 * @extends {ol.renderer.Map}
 * @param {Element} container Container.
 * @param {ol.Map} map Map.
 */
ol.renderer.dom.Map = function(container, map) {

  ol.renderer.Map.call(this, container, map);

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = ol.dom.createCanvasContext2D();
  var canvas = this.context_.canvas;
  canvas.style.position = 'absolute';
  canvas.style.width = '100%';
  canvas.style.height = '100%';
  canvas.className = ol.css.CLASS_UNSELECTABLE;
  container.insertBefore(canvas, container.childNodes[0] || null);

  /**
   * @private
   * @type {ol.Transform}
   */
  this.transform_ = ol.transform.create();

  /**
   * @type {!Element}
   * @private
   */
  this.layersPane_ = document.createElement('DIV');
  this.layersPane_.className = ol.css.CLASS_UNSELECTABLE;
  var style = this.layersPane_.style;
  style.position = 'absolute';
  style.width = '100%';
  style.height = '100%';

  // prevent the img context menu on mobile devices
  ol.events.listen(this.layersPane_, ol.events.EventType.TOUCHSTART,
      ol.events.Event.preventDefault);

  container.insertBefore(this.layersPane_, container.childNodes[0] || null);

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = true;

};
ol.inherits(ol.renderer.dom.Map, ol.renderer.Map);


/**
 * @inheritDoc
 */
ol.renderer.dom.Map.prototype.disposeInternal = function() {
  ol.dom.removeNode(this.layersPane_);
  ol.renderer.Map.prototype.disposeInternal.call(this);
};


/**
 * @inheritDoc
 */
ol.renderer.dom.Map.prototype.createLayerRenderer = function(layer) {
  var layerRenderer;
  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
    layerRenderer = new ol.renderer.dom.ImageLayer(layer);
  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
    layerRenderer = new ol.renderer.dom.TileLayer(layer);
  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
    layerRenderer = new ol.renderer.dom.VectorLayer(layer);
  } else {
    goog.DEBUG && console.assert(false, 'unexpected layer configuration');
    return null;
  }
  return layerRenderer;
};


/**
 * @param {ol.render.EventType} type Event type.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.dom.Map.prototype.dispatchComposeEvent_ = function(type, frameState) {
  var map = this.getMap();
  if (map.hasListener(type)) {
    var extent = frameState.extent;
    var pixelRatio = frameState.pixelRatio;
    var viewState = frameState.viewState;
    var rotation = viewState.rotation;
    var context = this.context_;
    var canvas = context.canvas;

    var transform = ol.transform.compose(this.transform_,
        canvas.width / 2, canvas.height / 2,
        pixelRatio / viewState.resolution, -pixelRatio / viewState.resolution,
        -rotation,
        -viewState.center[0], -viewState.center[1]);

    var vectorContext = new ol.render.canvas.Immediate(context, pixelRatio,
        extent, transform, rotation);
    var composeEvent = new ol.render.Event(type, vectorContext,
        frameState, context, null);
    map.dispatchEvent(composeEvent);
  }
};


/**
 * @inheritDoc
 */
ol.renderer.dom.Map.prototype.getType = function() {
  return ol.RendererType.DOM;
};


/**
 * @inheritDoc
 */
ol.renderer.dom.Map.prototype.renderFrame = function(frameState) {

  if (!frameState) {
    if (this.renderedVisible_) {
      this.layersPane_.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return;
  }

  var map = this.getMap();
  if (map.hasListener(ol.render.EventType.PRECOMPOSE) ||
      map.hasListener(ol.render.EventType.POSTCOMPOSE)) {
    var canvas = this.context_.canvas;
    var pixelRatio = frameState.pixelRatio;
    canvas.width = frameState.size[0] * pixelRatio;
    canvas.height = frameState.size[1] * pixelRatio;
  }

  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);

  var layerStatesArray = frameState.layerStatesArray;
  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);

  var viewResolution = frameState.viewState.resolution;
  var i, ii, layer, layerRenderer, layerState;
  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
    layerState = layerStatesArray[i];
    layer = layerState.layer;
    layerRenderer = /** @type {ol.renderer.dom.Layer} */ (
        this.getLayerRenderer(layer));
    this.layersPane_.insertBefore(layerRenderer.getTarget(), this.layersPane_.childNodes[i] || null);
    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
        layerState.sourceState == ol.source.State.READY) {
      if (layerRenderer.prepareFrame(frameState, layerState)) {
        layerRenderer.composeFrame(frameState, layerState);
      }
    } else {
      layerRenderer.clearFrame();
    }
  }

  var layerStates = frameState.layerStates;
  var layerKey;
  for (layerKey in this.getLayerRenderers()) {
    if (!(layerKey in layerStates)) {
      layerRenderer = /** @type {ol.renderer.dom.Layer} */ (this.getLayerRendererByKey(layerKey));
      ol.dom.removeNode(layerRenderer.getTarget());
    }
  }

  if (!this.renderedVisible_) {
    this.layersPane_.style.display = '';
    this.renderedVisible_ = true;
  }

  this.calculateMatrices2D(frameState);
  this.scheduleRemoveUnusedLayerRenderers(frameState);
  this.scheduleExpireIconCache(frameState);

  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);
};

goog.provide('ol.webgl.Shader');

goog.require('ol.functions');
goog.require('ol.webgl');


/**
 * @constructor
 * @param {string} source Source.
 * @struct
 */
ol.webgl.Shader = function(source) {

  /**
   * @private
   * @type {string}
   */
  this.source_ = source;

};


/**
 * @abstract
 * @return {number} Type.
 */
ol.webgl.Shader.prototype.getType = function() {};


/**
 * @return {string} Source.
 */
ol.webgl.Shader.prototype.getSource = function() {
  return this.source_;
};


/**
 * @return {boolean} Is animated?
 */
ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE;

goog.provide('ol.webgl.Fragment');

goog.require('ol');
goog.require('ol.webgl');
goog.require('ol.webgl.Shader');


/**
 * @constructor
 * @extends {ol.webgl.Shader}
 * @param {string} source Source.
 * @struct
 */
ol.webgl.Fragment = function(source) {
  ol.webgl.Shader.call(this, source);
};
ol.inherits(ol.webgl.Fragment, ol.webgl.Shader);


/**
 * @inheritDoc
 */
ol.webgl.Fragment.prototype.getType = function() {
  return ol.webgl.FRAGMENT_SHADER;
};

goog.provide('ol.webgl.Vertex');

goog.require('ol');
goog.require('ol.webgl');
goog.require('ol.webgl.Shader');


/**
 * @constructor
 * @extends {ol.webgl.Shader}
 * @param {string} source Source.
 * @struct
 */
ol.webgl.Vertex = function(source) {
  ol.webgl.Shader.call(this, source);
};
ol.inherits(ol.webgl.Vertex, ol.webgl.Shader);


/**
 * @inheritDoc
 */
ol.webgl.Vertex.prototype.getType = function() {
  return ol.webgl.VERTEX_SHADER;
};

// This file is automatically generated, do not edit
goog.provide('ol.render.webgl.imagereplay.defaultshader');

goog.require('ol');
goog.require('ol.webgl.Fragment');
goog.require('ol.webgl.Vertex');


/**
 * @constructor
 * @extends {ol.webgl.Fragment}
 * @struct
 */
ol.render.webgl.imagereplay.defaultshader.Fragment = function() {
  ol.webgl.Fragment.call(this, ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE);
};
ol.inherits(ol.render.webgl.imagereplay.defaultshader.Fragment, ol.webgl.Fragment);


/**
 * @const
 * @type {string}
 */
ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_image, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  float alpha = texColor.a * v_opacity * u_opacity;\n  if (alpha == 0.0) {\n    discard;\n  }\n  gl_FragColor.a = alpha;\n}\n';


/**
 * @const
 * @type {string}
 */
ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}';


/**
 * @const
 * @type {string}
 */
ol.render.webgl.imagereplay.defaultshader.Fragment.SOURCE = goog.DEBUG ?
    ol.render.webgl.imagereplay.defaultshader.Fragment.DEBUG_SOURCE :
    ol.render.webgl.imagereplay.defaultshader.Fragment.OPTIMIZED_SOURCE;


ol.render.webgl.imagereplay.defaultshader.fragment = new ol.render.webgl.imagereplay.defaultshader.Fragment();


/**
 * @constructor
 * @extends {ol.webgl.Vertex}
 * @struct
 */
ol.render.webgl.imagereplay.defaultshader.Vertex = function() {
  ol.webgl.Vertex.call(this, ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE);
};
ol.inherits(ol.render.webgl.imagereplay.defaultshader.Vertex, ol.webgl.Vertex);


/**
 * @const
 * @type {string}
 */
ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n  mat4 offsetMatrix = u_offsetScaleMatrix;\n  if (a_rotateWithView == 1.0) {\n    offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n  }\n  vec4 offsets = offsetMatrix * vec4(a_offsets, 0., 0.);\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n  v_texCoord = a_texCoord;\n  v_opacity = a_opacity;\n}\n\n\n';


/**
 * @const
 * @type {string}
 */
ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.,0.);gl_Position=h*vec4(c,0.,1.)+offsets;a=d;b=f;}';


/**
 * @const
 * @type {string}
 */
ol.render.webgl.imagereplay.defaultshader.Vertex.SOURCE = goog.DEBUG ?
    ol.render.webgl.imagereplay.defaultshader.Vertex.DEBUG_SOURCE :
    ol.render.webgl.imagereplay.defaultshader.Vertex.OPTIMIZED_SOURCE;


ol.render.webgl.imagereplay.defaultshader.vertex = new ol.render.webgl.imagereplay.defaultshader.Vertex();


/**
 * @constructor
 * @param {WebGLRenderingContext} gl GL.
 * @param {WebGLProgram} program Program.
 * @struct
 */
ol.render.webgl.imagereplay.defaultshader.Locations = function(gl, program) {

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_image = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_image' : 'l');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_offsetRotateMatrix = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'j');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_offsetScaleMatrix = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_opacity = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_opacity' : 'k');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_projectionMatrix = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_projectionMatrix' : 'h');

  /**
   * @type {number}
   */
  this.a_offsets = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_offsets' : 'e');

  /**
   * @type {number}
   */
  this.a_opacity = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_opacity' : 'f');

  /**
   * @type {number}
   */
  this.a_position = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_position' : 'c');

  /**
   * @type {number}
   */
  this.a_rotateWithView = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_rotateWithView' : 'g');

  /**
   * @type {number}
   */
  this.a_texCoord = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_texCoord' : 'd');
};

goog.provide('ol.webgl.Buffer');

goog.require('ol');
goog.require('ol.webgl');


/**
 * @constructor
 * @param {Array.<number>=} opt_arr Array.
 * @param {number=} opt_usage Usage.
 * @struct
 */
ol.webgl.Buffer = function(opt_arr, opt_usage) {

  /**
   * @private
   * @type {Array.<number>}
   */
  this.arr_ = opt_arr !== undefined ? opt_arr : [];

  /**
   * @private
   * @type {number}
   */
  this.usage_ = opt_usage !== undefined ?
      opt_usage : ol.webgl.Buffer.Usage.STATIC_DRAW;

};


/**
 * @return {Array.<number>} Array.
 */
ol.webgl.Buffer.prototype.getArray = function() {
  return this.arr_;
};


/**
 * @return {number} Usage.
 */
ol.webgl.Buffer.prototype.getUsage = function() {
  return this.usage_;
};


/**
 * @enum {number}
 */
ol.webgl.Buffer.Usage = {
  STATIC_DRAW: ol.webgl.STATIC_DRAW,
  STREAM_DRAW: ol.webgl.STREAM_DRAW,
  DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW
};

goog.provide('ol.webgl.ContextEventType');


/**
 * @enum {string}
 */
ol.webgl.ContextEventType = {
  LOST: 'webglcontextlost',
  RESTORED: 'webglcontextrestored'
};

goog.provide('ol.webgl.Context');

goog.require('ol');
goog.require('ol.Disposable');
goog.require('ol.array');
goog.require('ol.events');
goog.require('ol.obj');
goog.require('ol.webgl');
goog.require('ol.webgl.ContextEventType');


/**
 * @classdesc
 * A WebGL context for accessing low-level WebGL capabilities.
 *
 * @constructor
 * @extends {ol.Disposable}
 * @param {HTMLCanvasElement} canvas Canvas.
 * @param {WebGLRenderingContext} gl GL.
 */
ol.webgl.Context = function(canvas, gl) {

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = canvas;

  /**
   * @private
   * @type {WebGLRenderingContext}
   */
  this.gl_ = gl;

  /**
   * @private
   * @type {Object.<string, ol.WebglBufferCacheEntry>}
   */
  this.bufferCache_ = {};

  /**
   * @private
   * @type {Object.<string, WebGLShader>}
   */
  this.shaderCache_ = {};

  /**
   * @private
   * @type {Object.<string, WebGLProgram>}
   */
  this.programCache_ = {};

  /**
   * @private
   * @type {WebGLProgram}
   */
  this.currentProgram_ = null;

  /**
   * @private
   * @type {WebGLFramebuffer}
   */
  this.hitDetectionFramebuffer_ = null;

  /**
   * @private
   * @type {WebGLTexture}
   */
  this.hitDetectionTexture_ = null;

  /**
   * @private
   * @type {WebGLRenderbuffer}
   */
  this.hitDetectionRenderbuffer_ = null;

  /**
   * @type {boolean}
   */
  this.hasOESElementIndexUint = ol.array.includes(
      ol.WEBGL_EXTENSIONS, 'OES_element_index_uint');

  // use the OES_element_index_uint extension if available
  if (this.hasOESElementIndexUint) {
    var ext = gl.getExtension('OES_element_index_uint');
    goog.DEBUG && console.assert(ext,
        'Failed to get extension "OES_element_index_uint"');
  }

  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
      this.handleWebGLContextLost, this);
  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
      this.handleWebGLContextRestored, this);

};
ol.inherits(ol.webgl.Context, ol.Disposable);


/**
 * Just bind the buffer if it's in the cache. Otherwise create
 * the WebGL buffer, bind it, populate it, and add an entry to
 * the cache.
 * @param {number} target Target.
 * @param {ol.webgl.Buffer} buf Buffer.
 */
ol.webgl.Context.prototype.bindBuffer = function(target, buf) {
  var gl = this.getGL();
  var arr = buf.getArray();
  var bufferKey = String(ol.getUid(buf));
  if (bufferKey in this.bufferCache_) {
    var bufferCacheEntry = this.bufferCache_[bufferKey];
    gl.bindBuffer(target, bufferCacheEntry.buffer);
  } else {
    var buffer = gl.createBuffer();
    gl.bindBuffer(target, buffer);
    goog.DEBUG && console.assert(target == ol.webgl.ARRAY_BUFFER ||
        target == ol.webgl.ELEMENT_ARRAY_BUFFER,
        'target is supposed to be an ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER');
    var /** @type {ArrayBufferView} */ arrayBuffer;
    if (target == ol.webgl.ARRAY_BUFFER) {
      arrayBuffer = new Float32Array(arr);
    } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) {
      arrayBuffer = this.hasOESElementIndexUint ?
          new Uint32Array(arr) : new Uint16Array(arr);
    }
    gl.bufferData(target, arrayBuffer, buf.getUsage());
    this.bufferCache_[bufferKey] = {
      buf: buf,
      buffer: buffer
    };
  }
};


/**
 * @param {ol.webgl.Buffer} buf Buffer.
 */
ol.webgl.Context.prototype.deleteBuffer = function(buf) {
  var gl = this.getGL();
  var bufferKey = String(ol.getUid(buf));
  goog.DEBUG && console.assert(bufferKey in this.bufferCache_,
      'attempted to delete uncached buffer');
  var bufferCacheEntry = this.bufferCache_[bufferKey];
  if (!gl.isContextLost()) {
    gl.deleteBuffer(bufferCacheEntry.buffer);
  }
  delete this.bufferCache_[bufferKey];
};


/**
 * @inheritDoc
 */
ol.webgl.Context.prototype.disposeInternal = function() {
  ol.events.unlistenAll(this.canvas_);
  var gl = this.getGL();
  if (!gl.isContextLost()) {
    var key;
    for (key in this.bufferCache_) {
      gl.deleteBuffer(this.bufferCache_[key].buffer);
    }
    for (key in this.programCache_) {
      gl.deleteProgram(this.programCache_[key]);
    }
    for (key in this.shaderCache_) {
      gl.deleteShader(this.shaderCache_[key]);
    }
    // delete objects for hit-detection
    gl.deleteFramebuffer(this.hitDetectionFramebuffer_);
    gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_);
    gl.deleteTexture(this.hitDetectionTexture_);
  }
};


/**
 * @return {HTMLCanvasElement} Canvas.
 */
ol.webgl.Context.prototype.getCanvas = function() {
  return this.canvas_;
};


/**
 * Get the WebGL rendering context
 * @return {WebGLRenderingContext} The rendering context.
 * @api
 */
ol.webgl.Context.prototype.getGL = function() {
  return this.gl_;
};


/**
 * Get the frame buffer for hit detection.
 * @return {WebGLFramebuffer} The hit detection frame buffer.
 */
ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() {
  if (!this.hitDetectionFramebuffer_) {
    this.initHitDetectionFramebuffer_();
  }
  return this.hitDetectionFramebuffer_;
};


/**
 * Get shader from the cache if it's in the cache. Otherwise, create
 * the WebGL shader, compile it, and add entry to cache.
 * @param {ol.webgl.Shader} shaderObject Shader object.
 * @return {WebGLShader} Shader.
 */
ol.webgl.Context.prototype.getShader = function(shaderObject) {
  var shaderKey = String(ol.getUid(shaderObject));
  if (shaderKey in this.shaderCache_) {
    return this.shaderCache_[shaderKey];
  } else {
    var gl = this.getGL();
    var shader = gl.createShader(shaderObject.getType());
    gl.shaderSource(shader, shaderObject.getSource());
    gl.compileShader(shader);
    goog.DEBUG && console.assert(
        gl.getShaderParameter(shader, ol.webgl.COMPILE_STATUS) ||
        gl.isContextLost(),
        gl.getShaderInfoLog(shader) || 'illegal state, shader not compiled or context lost');
    this.shaderCache_[shaderKey] = shader;
    return shader;
  }
};


/**
 * Get the program from the cache if it's in the cache. Otherwise create
 * the WebGL program, attach the shaders to it, and add an entry to the
 * cache.
 * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader.
 * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader.
 * @return {WebGLProgram} Program.
 */
ol.webgl.Context.prototype.getProgram = function(
    fragmentShaderObject, vertexShaderObject) {
  var programKey =
      ol.getUid(fragmentShaderObject) + '/' + ol.getUid(vertexShaderObject);
  if (programKey in this.programCache_) {
    return this.programCache_[programKey];
  } else {
    var gl = this.getGL();
    var program = gl.createProgram();
    gl.attachShader(program, this.getShader(fragmentShaderObject));
    gl.attachShader(program, this.getShader(vertexShaderObject));
    gl.linkProgram(program);
    goog.DEBUG && console.assert(
        gl.getProgramParameter(program, ol.webgl.LINK_STATUS) ||
        gl.isContextLost(),
        gl.getProgramInfoLog(program) || 'illegal state, shader not linked or context lost');
    this.programCache_[programKey] = program;
    return program;
  }
};


/**
 * FIXME empy description for jsdoc
 */
ol.webgl.Context.prototype.handleWebGLContextLost = function() {
  ol.obj.clear(this.bufferCache_);
  ol.obj.clear(this.shaderCache_);
  ol.obj.clear(this.programCache_);
  this.currentProgram_ = null;
  this.hitDetectionFramebuffer_ = null;
  this.hitDetectionTexture_ = null;
  this.hitDetectionRenderbuffer_ = null;
};


/**
 * FIXME empy description for jsdoc
 */
ol.webgl.Context.prototype.handleWebGLContextRestored = function() {
};


/**
 * Creates a 1x1 pixel framebuffer for the hit-detection.
 * @private
 */
ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() {
  var gl = this.gl_;
  var framebuffer = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

  var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1);
  var renderbuffer = gl.createRenderbuffer();
  gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1);
  gl.framebufferTexture2D(
      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
      gl.RENDERBUFFER, renderbuffer);

  gl.bindTexture(gl.TEXTURE_2D, null);
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);

  this.hitDetectionFramebuffer_ = framebuffer;
  this.hitDetectionTexture_ = texture;
  this.hitDetectionRenderbuffer_ = renderbuffer;
};


/**
 * Use a program.  If the program is already in use, this will return `false`.
 * @param {WebGLProgram} program Program.
 * @return {boolean} Changed.
 * @api
 */
ol.webgl.Context.prototype.useProgram = function(program) {
  if (program == this.currentProgram_) {
    return false;
  } else {
    var gl = this.getGL();
    gl.useProgram(program);
    this.currentProgram_ = program;
    return true;
  }
};


/**
 * @param {WebGLRenderingContext} gl WebGL rendering context.
 * @param {number=} opt_wrapS wrapS.
 * @param {number=} opt_wrapT wrapT.
 * @return {WebGLTexture} The texture.
 * @private
 */
ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) {
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

  if (opt_wrapS !== undefined) {
    gl.texParameteri(
        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS);
  }
  if (opt_wrapT !== undefined) {
    gl.texParameteri(
        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT);
  }

  return texture;
};


/**
 * @param {WebGLRenderingContext} gl WebGL rendering context.
 * @param {number} width Width.
 * @param {number} height Height.
 * @param {number=} opt_wrapS wrapS.
 * @param {number=} opt_wrapT wrapT.
 * @return {WebGLTexture} The texture.
 */
ol.webgl.Context.createEmptyTexture = function(
    gl, width, height, opt_wrapS, opt_wrapT) {
  var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT);
  gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE,
      null);

  return texture;
};


/**
 * @param {WebGLRenderingContext} gl WebGL rendering context.
 * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image.
 * @param {number=} opt_wrapS wrapS.
 * @param {number=} opt_wrapT wrapT.
 * @return {WebGLTexture} The texture.
 */
ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) {
  var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT);
  gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

  return texture;
};

goog.provide('ol.render.webgl.ImageReplay');
goog.provide('ol.render.webgl.ReplayGroup');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.render.ReplayGroup');
goog.require('ol.render.VectorContext');
goog.require('ol.render.replay');
goog.require('ol.render.webgl.imagereplay.defaultshader');
goog.require('ol.transform');
goog.require('ol.vec.Mat4');
goog.require('ol.webgl');
goog.require('ol.webgl.Buffer');
goog.require('ol.webgl.Context');


/**
 * @constructor
 * @extends {ol.render.VectorContext}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Max extent.
 * @protected
 * @struct
 */
ol.render.webgl.ImageReplay = function(tolerance, maxExtent) {
  ol.render.VectorContext.call(this);

  /**
   * @type {number|undefined}
   * @private
   */
  this.anchorX_ = undefined;

  /**
   * @type {number|undefined}
   * @private
   */
  this.anchorY_ = undefined;

  /**
   * The origin of the coordinate system for the point coordinates sent to
   * the GPU. To eliminate jitter caused by precision problems in the GPU
   * we use the "Rendering Relative to Eye" technique described in the "3D
   * Engine Design for Virtual Globes" book.
   * @private
   * @type {ol.Coordinate}
   */
  this.origin_ = ol.extent.getCenter(maxExtent);

  /**
   * @type {Array.<number>}
   * @private
   */
  this.groupIndices_ = [];

  /**
   * @type {Array.<number>}
   * @private
   */
  this.hitDetectionGroupIndices_ = [];

  /**
   * @type {number|undefined}
   * @private
   */
  this.height_ = undefined;

  /**
   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
   * @private
   */
  this.images_ = [];

  /**
   * @type {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
   * @private
   */
  this.hitDetectionImages_ = [];

  /**
   * @type {number|undefined}
   * @private
   */
  this.imageHeight_ = undefined;

  /**
   * @type {number|undefined}
   * @private
   */
  this.imageWidth_ = undefined;

  /**
   * @type {Array.<number>}
   * @private
   */
  this.indices_ = [];

  /**
   * @type {ol.webgl.Buffer}
   * @private
   */
  this.indicesBuffer_ = null;

  /**
   * @private
   * @type {ol.render.webgl.imagereplay.defaultshader.Locations}
   */
  this.defaultLocations_ = null;

  /**
   * @private
   * @type {number|undefined}
   */
  this.opacity_ = undefined;

  /**
   * @type {ol.Transform}
   * @private
   */
  this.offsetRotateMatrix_ = ol.transform.create();

  /**
   * @type {ol.Transform}
   * @private
   */
  this.offsetScaleMatrix_ = ol.transform.create();

  /**
   * @type {number|undefined}
   * @private
   */
  this.originX_ = undefined;

  /**
   * @type {number|undefined}
   * @private
   */
  this.originY_ = undefined;

  /**
   * @type {ol.Transform}
   * @private
   */
  this.projectionMatrix_ = ol.transform.create();

  /**
   * @type {Array.<number>}
   * @private
   */
  this.tmpMat4_ = ol.vec.Mat4.create();

  /**
   * @private
   * @type {boolean|undefined}
   */
  this.rotateWithView_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.rotation_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.scale_ = undefined;

  /**
   * @type {Array.<WebGLTexture>}
   * @private
   */
  this.textures_ = [];

  /**
   * @type {Array.<WebGLTexture>}
   * @private
   */
  this.hitDetectionTextures_ = [];

  /**
   * @type {Array.<number>}
   * @private
   */
  this.vertices_ = [];

  /**
   * @type {ol.webgl.Buffer}
   * @private
   */
  this.verticesBuffer_ = null;

  /**
   * Start index per feature (the index).
   * @type {Array.<number>}
   * @private
   */
  this.startIndices_ = [];

  /**
   * Start index per feature (the feature).
   * @type {Array.<ol.Feature|ol.render.Feature>}
   * @private
   */
  this.startIndicesFeature_ = [];

  /**
   * @type {number|undefined}
   * @private
   */
  this.width_ = undefined;
};
ol.inherits(ol.render.webgl.ImageReplay, ol.render.VectorContext);


/**
 * @param {ol.webgl.Context} context WebGL context.
 * @return {function()} Delete resources function.
 */
ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) {
  // We only delete our stuff here. The shaders and the program may
  // be used by other ImageReplay instances (for other layers). And
  // they will be deleted when disposing of the ol.webgl.Context
  // object.
  goog.DEBUG && console.assert(this.verticesBuffer_,
      'verticesBuffer must not be null');
  goog.DEBUG && console.assert(this.indicesBuffer_,
      'indicesBuffer must not be null');
  var verticesBuffer = this.verticesBuffer_;
  var indicesBuffer = this.indicesBuffer_;
  var textures = this.textures_;
  var hitDetectionTextures = this.hitDetectionTextures_;
  var gl = context.getGL();
  return function() {
    if (!gl.isContextLost()) {
      var i, ii;
      for (i = 0, ii = textures.length; i < ii; ++i) {
        gl.deleteTexture(textures[i]);
      }
      for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) {
        gl.deleteTexture(hitDetectionTextures[i]);
      }
    }
    context.deleteBuffer(verticesBuffer);
    context.deleteBuffer(indicesBuffer);
  };
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @return {number} My end.
 * @private
 */
ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) {
  goog.DEBUG && console.assert(this.anchorX_ !== undefined, 'anchorX is defined');
  goog.DEBUG && console.assert(this.anchorY_ !== undefined, 'anchorY is defined');
  goog.DEBUG && console.assert(this.height_ !== undefined, 'height is defined');
  goog.DEBUG && console.assert(this.imageHeight_ !== undefined,
      'imageHeight is defined');
  goog.DEBUG && console.assert(this.imageWidth_ !== undefined, 'imageWidth is defined');
  goog.DEBUG && console.assert(this.opacity_ !== undefined, 'opacity is defined');
  goog.DEBUG && console.assert(this.originX_ !== undefined, 'originX is defined');
  goog.DEBUG && console.assert(this.originY_ !== undefined, 'originY is defined');
  goog.DEBUG && console.assert(this.rotateWithView_ !== undefined,
      'rotateWithView is defined');
  goog.DEBUG && console.assert(this.rotation_ !== undefined, 'rotation is defined');
  goog.DEBUG && console.assert(this.scale_ !== undefined, 'scale is defined');
  goog.DEBUG && console.assert(this.width_ !== undefined, 'width is defined');
  var anchorX = /** @type {number} */ (this.anchorX_);
  var anchorY = /** @type {number} */ (this.anchorY_);
  var height = /** @type {number} */ (this.height_);
  var imageHeight = /** @type {number} */ (this.imageHeight_);
  var imageWidth = /** @type {number} */ (this.imageWidth_);
  var opacity = /** @type {number} */ (this.opacity_);
  var originX = /** @type {number} */ (this.originX_);
  var originY = /** @type {number} */ (this.originY_);
  var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0;
  var rotation = /** @type {number} */ (this.rotation_);
  var scale = /** @type {number} */ (this.scale_);
  var width = /** @type {number} */ (this.width_);
  var cos = Math.cos(rotation);
  var sin = Math.sin(rotation);
  var numIndices = this.indices_.length;
  var numVertices = this.vertices_.length;
  var i, n, offsetX, offsetY, x, y;
  for (i = offset; i < end; i += stride) {
    x = flatCoordinates[i] - this.origin_[0];
    y = flatCoordinates[i + 1] - this.origin_[1];

    // There are 4 vertices per [x, y] point, one for each corner of the
    // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if
    // WebGL supported Geometry Shaders (which can emit new vertices), but that
    // is not currently the case.
    //
    // And each vertex includes 8 values: the x and y coordinates, the x and
    // y offsets used to calculate the position of the corner, the u and
    // v texture coordinates for the corner, the opacity, and whether the
    // the image should be rotated with the view (rotateWithView).

    n = numVertices / 8;

    // bottom-left corner
    offsetX = -scale * anchorX;
    offsetY = -scale * (height - anchorY);
    this.vertices_[numVertices++] = x;
    this.vertices_[numVertices++] = y;
    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
    this.vertices_[numVertices++] = originX / imageWidth;
    this.vertices_[numVertices++] = (originY + height) / imageHeight;
    this.vertices_[numVertices++] = opacity;
    this.vertices_[numVertices++] = rotateWithView;

    // bottom-right corner
    offsetX = scale * (width - anchorX);
    offsetY = -scale * (height - anchorY);
    this.vertices_[numVertices++] = x;
    this.vertices_[numVertices++] = y;
    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
    this.vertices_[numVertices++] = (originX + width) / imageWidth;
    this.vertices_[numVertices++] = (originY + height) / imageHeight;
    this.vertices_[numVertices++] = opacity;
    this.vertices_[numVertices++] = rotateWithView;

    // top-right corner
    offsetX = scale * (width - anchorX);
    offsetY = scale * anchorY;
    this.vertices_[numVertices++] = x;
    this.vertices_[numVertices++] = y;
    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
    this.vertices_[numVertices++] = (originX + width) / imageWidth;
    this.vertices_[numVertices++] = originY / imageHeight;
    this.vertices_[numVertices++] = opacity;
    this.vertices_[numVertices++] = rotateWithView;

    // top-left corner
    offsetX = -scale * anchorX;
    offsetY = scale * anchorY;
    this.vertices_[numVertices++] = x;
    this.vertices_[numVertices++] = y;
    this.vertices_[numVertices++] = offsetX * cos - offsetY * sin;
    this.vertices_[numVertices++] = offsetX * sin + offsetY * cos;
    this.vertices_[numVertices++] = originX / imageWidth;
    this.vertices_[numVertices++] = originY / imageHeight;
    this.vertices_[numVertices++] = opacity;
    this.vertices_[numVertices++] = rotateWithView;

    this.indices_[numIndices++] = n;
    this.indices_[numIndices++] = n + 1;
    this.indices_[numIndices++] = n + 2;
    this.indices_[numIndices++] = n;
    this.indices_[numIndices++] = n + 2;
    this.indices_[numIndices++] = n + 3;
  }

  return numVertices;
};


/**
 * @inheritDoc
 */
ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) {
  this.startIndices_.push(this.indices_.length);
  this.startIndicesFeature_.push(feature);
  var flatCoordinates = multiPointGeometry.getFlatCoordinates();
  var stride = multiPointGeometry.getStride();
  this.drawCoordinates_(
      flatCoordinates, 0, flatCoordinates.length, stride);
};


/**
 * @inheritDoc
 */
ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) {
  this.startIndices_.push(this.indices_.length);
  this.startIndicesFeature_.push(feature);
  var flatCoordinates = pointGeometry.getFlatCoordinates();
  var stride = pointGeometry.getStride();
  this.drawCoordinates_(
      flatCoordinates, 0, flatCoordinates.length, stride);
};


/**
 * @param {ol.webgl.Context} context Context.
 */
ol.render.webgl.ImageReplay.prototype.finish = function(context) {
  var gl = context.getGL();

  this.groupIndices_.push(this.indices_.length);
  goog.DEBUG && console.assert(this.images_.length === this.groupIndices_.length,
      'number of images and groupIndices match');
  this.hitDetectionGroupIndices_.push(this.indices_.length);
  goog.DEBUG && console.assert(this.hitDetectionImages_.length ===
      this.hitDetectionGroupIndices_.length,
      'number of hitDetectionImages and hitDetectionGroupIndices match');

  // create, bind, and populate the vertices buffer
  this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_);
  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_);

  var indices = this.indices_;
  var bits = context.hasOESElementIndexUint ? 32 : 16;
  goog.DEBUG && console.assert(indices[indices.length - 1] < Math.pow(2, bits),
      'Too large element index detected [%s] (OES_element_index_uint "%s")',
      indices[indices.length - 1], context.hasOESElementIndexUint);

  // create, bind, and populate the indices buffer
  this.indicesBuffer_ = new ol.webgl.Buffer(indices);
  context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);

  // create textures
  /** @type {Object.<string, WebGLTexture>} */
  var texturePerImage = {};

  this.createTextures_(this.textures_, this.images_, texturePerImage, gl);
  goog.DEBUG && console.assert(this.textures_.length === this.groupIndices_.length,
      'number of textures and groupIndices match');

  this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_,
      texturePerImage, gl);
  goog.DEBUG && console.assert(this.hitDetectionTextures_.length ===
      this.hitDetectionGroupIndices_.length,
      'number of hitDetectionTextures and hitDetectionGroupIndices match');

  this.anchorX_ = undefined;
  this.anchorY_ = undefined;
  this.height_ = undefined;
  this.images_ = null;
  this.hitDetectionImages_ = null;
  this.imageHeight_ = undefined;
  this.imageWidth_ = undefined;
  this.indices_ = null;
  this.opacity_ = undefined;
  this.originX_ = undefined;
  this.originY_ = undefined;
  this.rotateWithView_ = undefined;
  this.rotation_ = undefined;
  this.scale_ = undefined;
  this.vertices_ = null;
  this.width_ = undefined;
};


/**
 * @private
 * @param {Array.<WebGLTexture>} textures Textures.
 * @param {Array.<HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>} images
 *    Images.
 * @param {Object.<string, WebGLTexture>} texturePerImage Texture cache.
 * @param {WebGLRenderingContext} gl Gl.
 */
ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, images, texturePerImage, gl) {
  goog.DEBUG && console.assert(textures.length === 0,
      'upon creation, textures is empty');

  var texture, image, uid, i;
  var ii = images.length;
  for (i = 0; i < ii; ++i) {
    image = images[i];

    uid = ol.getUid(image).toString();
    if (uid in texturePerImage) {
      texture = texturePerImage[uid];
    } else {
      texture = ol.webgl.Context.createTexture(
          gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
      texturePerImage[uid] = texture;
    }
    textures[i] = texture;
  }
};


/**
 * @param {ol.webgl.Context} context Context.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} opacity Global opacity.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
 * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
 * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
 *  this extent are checked.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.webgl.ImageReplay.prototype.replay = function(context,
    center, resolution, rotation, size, pixelRatio,
    opacity, skippedFeaturesHash,
    featureCallback, oneByOne, opt_hitExtent) {
  var gl = context.getGL();

  // bind the vertices buffer
  goog.DEBUG && console.assert(this.verticesBuffer_,
      'verticesBuffer must not be null');
  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_);

  // bind the indices buffer
  goog.DEBUG && console.assert(this.indicesBuffer_,
      'indecesBuffer must not be null');
  context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);

  // get the program
  var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment;
  var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex;
  var program = context.getProgram(fragmentShader, vertexShader);

  // get the locations
  var locations;
  if (!this.defaultLocations_) {
    locations =
        new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program);
    this.defaultLocations_ = locations;
  } else {
    locations = this.defaultLocations_;
  }

  // use the program (FIXME: use the return value)
  context.useProgram(program);

  // enable the vertex attrib arrays
  gl.enableVertexAttribArray(locations.a_position);
  gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT,
      false, 32, 0);

  gl.enableVertexAttribArray(locations.a_offsets);
  gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT,
      false, 32, 8);

  gl.enableVertexAttribArray(locations.a_texCoord);
  gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT,
      false, 32, 16);

  gl.enableVertexAttribArray(locations.a_opacity);
  gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT,
      false, 32, 24);

  gl.enableVertexAttribArray(locations.a_rotateWithView);
  gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT,
      false, 32, 28);

  // set the "uniform" values
  var projectionMatrix = ol.transform.reset(this.projectionMatrix_);
  ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1]));
  ol.transform.rotate(projectionMatrix, -rotation);
  ol.transform.translate(projectionMatrix, -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1]));

  var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_);
  ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]);

  var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_);
  if (rotation !== 0) {
    ol.transform.rotate(offsetRotateMatrix, -rotation);
  }

  gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
      ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix));
  gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false,
      ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix));
  gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false,
      ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix));
  gl.uniform1f(locations.u_opacity, opacity);

  // draw!
  var result;
  if (featureCallback === undefined) {
    this.drawReplay_(gl, context, skippedFeaturesHash,
        this.textures_, this.groupIndices_);
  } else {
    // draw feature by feature for the hit-detection
    result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash,
        featureCallback, oneByOne, opt_hitExtent);
  }

  // disable the vertex attrib arrays
  gl.disableVertexAttribArray(locations.a_position);
  gl.disableVertexAttribArray(locations.a_offsets);
  gl.disableVertexAttribArray(locations.a_texCoord);
  gl.disableVertexAttribArray(locations.a_opacity);
  gl.disableVertexAttribArray(locations.a_rotateWithView);

  return result;
};


/**
 * @private
 * @param {WebGLRenderingContext} gl gl.
 * @param {ol.webgl.Context} context Context.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {Array.<WebGLTexture>} textures Textures.
 * @param {Array.<number>} groupIndices Texture group indices.
 */
ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, textures, groupIndices) {
  goog.DEBUG && console.assert(textures.length === groupIndices.length,
      'number of textures and groupIndeces match');
  var elementType = context.hasOESElementIndexUint ?
      ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT;
  var elementSize = context.hasOESElementIndexUint ? 4 : 2;

  if (!ol.obj.isEmpty(skippedFeaturesHash)) {
    this.drawReplaySkipping_(
        gl, skippedFeaturesHash, textures, groupIndices,
        elementType, elementSize);
  } else {
    var i, ii, start;
    for (i = 0, ii = textures.length, start = 0; i < ii; ++i) {
      gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]);
      var end = groupIndices[i];
      this.drawElements_(gl, start, end, elementType, elementSize);
      start = end;
    }
  }
};


/**
 * Draw the replay while paying attention to skipped features.
 *
 * This functions creates groups of features that can be drawn to together,
 * so that the number of `drawElements` calls is minimized.
 *
 * For example given the following texture groups:
 *
 *    Group 1: A B C
 *    Group 2: D [E] F G
 *
 * If feature E should be skipped, the following `drawElements` calls will be
 * made:
 *
 *    drawElements with feature A, B and C
 *    drawElements with feature D
 *    drawElements with feature F and G
 *
 * @private
 * @param {WebGLRenderingContext} gl gl.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {Array.<WebGLTexture>} textures Textures.
 * @param {Array.<number>} groupIndices Texture group indices.
 * @param {number} elementType Element type.
 * @param {number} elementSize Element Size.
 */
ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, skippedFeaturesHash, textures, groupIndices,
    elementType, elementSize) {
  var featureIndex = 0;

  var i, ii;
  for (i = 0, ii = textures.length; i < ii; ++i) {
    gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]);
    var groupStart = (i > 0) ? groupIndices[i - 1] : 0;
    var groupEnd = groupIndices[i];

    var start = groupStart;
    var end = groupStart;
    while (featureIndex < this.startIndices_.length &&
        this.startIndices_[featureIndex] <= groupEnd) {
      var feature = this.startIndicesFeature_[featureIndex];

      var featureUid = ol.getUid(feature).toString();
      if (skippedFeaturesHash[featureUid] !== undefined) {
        // feature should be skipped
        if (start !== end) {
          // draw the features so far
          this.drawElements_(gl, start, end, elementType, elementSize);
        }
        // continue with the next feature
        start = (featureIndex === this.startIndices_.length - 1) ?
            groupEnd : this.startIndices_[featureIndex + 1];
        end = start;
      } else {
        // the feature is not skipped, augment the end index
        end = (featureIndex === this.startIndices_.length - 1) ?
            groupEnd : this.startIndices_[featureIndex + 1];
      }
      featureIndex++;
    }

    if (start !== end) {
      // draw the remaining features (in case there was no skipped feature
      // in this texture group, all features of a group are drawn together)
      this.drawElements_(gl, start, end, elementType, elementSize);
    }
  }
};


/**
 * @private
 * @param {WebGLRenderingContext} gl gl.
 * @param {number} start Start index.
 * @param {number} end End index.
 * @param {number} elementType Element type.
 * @param {number} elementSize Element Size.
 */
ol.render.webgl.ImageReplay.prototype.drawElements_ = function(
    gl, start, end, elementType, elementSize) {
  var numItems = end - start;
  var offsetInBytes = start * elementSize;
  gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes);
};


/**
 * @private
 * @param {WebGLRenderingContext} gl gl.
 * @param {ol.webgl.Context} context Context.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
 * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
 * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
 *  this extent are checked.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, featureCallback, oneByOne,
    opt_hitExtent) {
  if (!oneByOne) {
    // draw all hit-detection features in "once" (by texture group)
    return this.drawHitDetectionReplayAll_(gl, context,
        skippedFeaturesHash, featureCallback);
  } else {
    // draw hit-detection features one by one
    return this.drawHitDetectionReplayOneByOne_(gl, context,
        skippedFeaturesHash, featureCallback, opt_hitExtent);
  }
};


/**
 * @private
 * @param {WebGLRenderingContext} gl gl.
 * @param {ol.webgl.Context} context Context.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, featureCallback) {
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  this.drawReplay_(gl, context, skippedFeaturesHash,
      this.hitDetectionTextures_, this.hitDetectionGroupIndices_);

  var result = featureCallback(null);
  if (result) {
    return result;
  } else {
    return undefined;
  }
};


/**
 * @private
 * @param {WebGLRenderingContext} gl gl.
 * @param {ol.webgl.Context} context Context.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
 * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
 *  this extent are checked.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, featureCallback,
    opt_hitExtent) {
  goog.DEBUG && console.assert(this.hitDetectionTextures_.length ===
      this.hitDetectionGroupIndices_.length,
      'number of hitDetectionTextures and hitDetectionGroupIndices match');
  var elementType = context.hasOESElementIndexUint ?
      ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT;
  var elementSize = context.hasOESElementIndexUint ? 4 : 2;

  var i, groupStart, start, end, feature, featureUid;
  var featureIndex = this.startIndices_.length - 1;
  for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) {
    gl.bindTexture(ol.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]);
    groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0;
    end = this.hitDetectionGroupIndices_[i];

    // draw all features for this texture group
    while (featureIndex >= 0 &&
        this.startIndices_[featureIndex] >= groupStart) {
      start = this.startIndices_[featureIndex];
      feature = this.startIndicesFeature_[featureIndex];
      featureUid = ol.getUid(feature).toString();

      if (skippedFeaturesHash[featureUid] === undefined &&
          feature.getGeometry() &&
          (opt_hitExtent === undefined || ol.extent.intersects(
              /** @type {Array<number>} */ (opt_hitExtent),
              feature.getGeometry().getExtent()))) {
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
        this.drawElements_(gl, start, end, elementType, elementSize);

        var result = featureCallback(feature);
        if (result) {
          return result;
        }
      }

      end = start;
      featureIndex--;
    }
  }
  return undefined;
};


/**
 * @inheritDoc
 * @abstract
 */
ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = function() {};


/**
 * @inheritDoc
 */
ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) {
  var anchor = imageStyle.getAnchor();
  var image = imageStyle.getImage(1);
  var imageSize = imageStyle.getImageSize();
  var hitDetectionImage = imageStyle.getHitDetectionImage(1);
  var hitDetectionImageSize = imageStyle.getHitDetectionImageSize();
  var opacity = imageStyle.getOpacity();
  var origin = imageStyle.getOrigin();
  var rotateWithView = imageStyle.getRotateWithView();
  var rotation = imageStyle.getRotation();
  var size = imageStyle.getSize();
  var scale = imageStyle.getScale();
  goog.DEBUG && console.assert(anchor, 'imageStyle anchor is not null');
  goog.DEBUG && console.assert(image, 'imageStyle image is not null');
  goog.DEBUG && console.assert(imageSize,
      'imageStyle imageSize is not null');
  goog.DEBUG && console.assert(hitDetectionImage,
      'imageStyle hitDetectionImage is not null');
  goog.DEBUG && console.assert(hitDetectionImageSize,
      'imageStyle hitDetectionImageSize is not null');
  goog.DEBUG && console.assert(opacity !== undefined, 'imageStyle opacity is defined');
  goog.DEBUG && console.assert(origin, 'imageStyle origin is not null');
  goog.DEBUG && console.assert(rotateWithView !== undefined,
      'imageStyle rotateWithView is defined');
  goog.DEBUG && console.assert(rotation !== undefined, 'imageStyle rotation is defined');
  goog.DEBUG && console.assert(size, 'imageStyle size is not null');
  goog.DEBUG && console.assert(scale !== undefined, 'imageStyle scale is defined');

  var currentImage;
  if (this.images_.length === 0) {
    this.images_.push(image);
  } else {
    currentImage = this.images_[this.images_.length - 1];
    if (ol.getUid(currentImage) != ol.getUid(image)) {
      this.groupIndices_.push(this.indices_.length);
      goog.DEBUG && console.assert(this.groupIndices_.length === this.images_.length,
          'number of groupIndices and images match');
      this.images_.push(image);
    }
  }

  if (this.hitDetectionImages_.length === 0) {
    this.hitDetectionImages_.push(hitDetectionImage);
  } else {
    currentImage =
        this.hitDetectionImages_[this.hitDetectionImages_.length - 1];
    if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) {
      this.hitDetectionGroupIndices_.push(this.indices_.length);
      goog.DEBUG && console.assert(this.hitDetectionGroupIndices_.length ===
          this.hitDetectionImages_.length,
          'number of hitDetectionGroupIndices and hitDetectionImages match');
      this.hitDetectionImages_.push(hitDetectionImage);
    }
  }

  this.anchorX_ = anchor[0];
  this.anchorY_ = anchor[1];
  this.height_ = size[1];
  this.imageHeight_ = imageSize[1];
  this.imageWidth_ = imageSize[0];
  this.opacity_ = opacity;
  this.originX_ = origin[0];
  this.originY_ = origin[1];
  this.rotation_ = rotation;
  this.rotateWithView_ = rotateWithView;
  this.scale_ = scale;
  this.width_ = size[0];
};


/**
 * @constructor
 * @extends {ol.render.ReplayGroup}
 * @param {number} tolerance Tolerance.
 * @param {ol.Extent} maxExtent Max extent.
 * @param {number=} opt_renderBuffer Render buffer.
 * @struct
 */
ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) {
  ol.render.ReplayGroup.call(this);

  /**
   * @type {ol.Extent}
   * @private
   */
  this.maxExtent_ = maxExtent;

  /**
   * @type {number}
   * @private
   */
  this.tolerance_ = tolerance;

  /**
   * @type {number|undefined}
   * @private
   */
  this.renderBuffer_ = opt_renderBuffer;

  /**
   * ImageReplay only is supported at this point.
   * @type {Object.<ol.render.ReplayType, ol.render.webgl.ImageReplay>}
   * @private
   */
  this.replays_ = {};

};
ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup);


/**
 * @param {ol.webgl.Context} context WebGL context.
 * @return {function()} Delete resources function.
 */
ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) {
  var functions = [];
  var replayKey;
  for (replayKey in this.replays_) {
    functions.push(
        this.replays_[replayKey].getDeleteResourcesFunction(context));
  }
  return function() {
    var length = functions.length;
    var result;
    for (var i = 0; i < length; i++) {
      result = functions[i].apply(this, arguments);
    }
    return result;
  };
};


/**
 * @param {ol.webgl.Context} context Context.
 */
ol.render.webgl.ReplayGroup.prototype.finish = function(context) {
  var replayKey;
  for (replayKey in this.replays_) {
    this.replays_[replayKey].finish(context);
  }
};


/**
 * @inheritDoc
 */
ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) {
  var replay = this.replays_[replayType];
  if (replay === undefined) {
    var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType];
    replay = new constructor(this.tolerance_, this.maxExtent_);
    this.replays_[replayType] = replay;
  }
  return replay;
};


/**
 * @inheritDoc
 */
ol.render.webgl.ReplayGroup.prototype.isEmpty = function() {
  return ol.obj.isEmpty(this.replays_);
};


/**
 * @param {ol.webgl.Context} context Context.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} opacity Global opacity.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 */
ol.render.webgl.ReplayGroup.prototype.replay = function(context,
    center, resolution, rotation, size, pixelRatio,
    opacity, skippedFeaturesHash) {
  var i, ii, replay;
  for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) {
    replay = this.replays_[ol.render.replay.ORDER[i]];
    if (replay !== undefined) {
      replay.replay(context,
          center, resolution, rotation, size, pixelRatio,
          opacity, skippedFeaturesHash,
          undefined, false);
    }
  }
};


/**
 * @private
 * @param {ol.webgl.Context} context Context.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} opacity Global opacity.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback.
 * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion.
 * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting
 *  this extent are checked.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context,
    center, resolution, rotation, size, pixelRatio, opacity,
    skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) {
  var i, replay, result;
  for (i = ol.render.replay.ORDER.length - 1; i >= 0; --i) {
    replay = this.replays_[ol.render.replay.ORDER[i]];
    if (replay !== undefined) {
      result = replay.replay(context,
          center, resolution, rotation, size, pixelRatio, opacity,
          skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent);
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.webgl.Context} context Context.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} opacity Global opacity.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback.
 * @return {T|undefined} Callback result.
 * @template T
 */
ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
    coordinate, context, center, resolution, rotation, size, pixelRatio,
    opacity, skippedFeaturesHash,
    callback) {
  var gl = context.getGL();
  gl.bindFramebuffer(
      gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());


  /**
   * @type {ol.Extent}
   */
  var hitExtent;
  if (this.renderBuffer_ !== undefined) {
    // build an extent around the coordinate, so that only features that
    // intersect this extent are checked
    hitExtent = ol.extent.buffer(
        ol.extent.createOrUpdateFromCoordinate(coordinate),
        resolution * this.renderBuffer_);
  }

  return this.replayHitDetection_(context,
      coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_,
      pixelRatio, opacity, skippedFeaturesHash,
      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {?} Callback result.
       */
      function(feature) {
        var imageData = new Uint8Array(4);
        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);

        if (imageData[3] > 0) {
          var result = callback(feature);
          if (result) {
            return result;
          }
        }
      }, true, hitExtent);
};


/**
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {ol.webgl.Context} context Context.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} opacity Global opacity.
 * @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
 *  to skip.
 * @return {boolean} Is there a feature at the given coordinate?
 */
ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function(
    coordinate, context, center, resolution, rotation, size, pixelRatio,
    opacity, skippedFeaturesHash) {
  var gl = context.getGL();
  gl.bindFramebuffer(
      gl.FRAMEBUFFER, context.getHitDetectionFramebuffer());

  var hasFeature = this.replayHitDetection_(context,
      coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_,
      pixelRatio, opacity, skippedFeaturesHash,
      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {boolean} Is there a feature?
       */
      function(feature) {
        var imageData = new Uint8Array(4);
        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData);
        return imageData[3] > 0;
      }, false);

  return hasFeature !== undefined;
};


/**
 * @const
 * @private
 * @type {Object.<ol.render.ReplayType,
 *                function(new: ol.render.webgl.ImageReplay, number,
 *                ol.Extent)>}
 */
ol.render.webgl.BATCH_CONSTRUCTORS_ = {
  'Image': ol.render.webgl.ImageReplay
};


/**
 * @const
 * @private
 * @type {Array.<number>}
 */
ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1];

goog.provide('ol.render.webgl.Immediate');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.geom.GeometryType');
goog.require('ol.render.ReplayType');
goog.require('ol.render.VectorContext');
goog.require('ol.render.webgl.ReplayGroup');


/**
 * @constructor
 * @extends {ol.render.VectorContext}
 * @param {ol.webgl.Context} context Context.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} rotation Rotation.
 * @param {ol.Size} size Size.
 * @param {ol.Extent} extent Extent.
 * @param {number} pixelRatio Pixel ratio.
 * @struct
 */
ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) {
  ol.render.VectorContext.call(this);

  /**
   * @private
   */
  this.context_ = context;

  /**
   * @private
   */
  this.center_ = center;

  /**
   * @private
   */
  this.extent_ = extent;

  /**
   * @private
   */
  this.pixelRatio_ = pixelRatio;

  /**
   * @private
   */
  this.size_ = size;

  /**
   * @private
   */
  this.rotation_ = rotation;

  /**
   * @private
   */
  this.resolution_ = resolution;

  /**
   * @private
   * @type {ol.style.Image}
   */
  this.imageStyle_ = null;

};
ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext);


/**
 * Set the rendering style.  Note that since this is an immediate rendering API,
 * any `zIndex` on the provided style will be ignored.
 *
 * @param {ol.style.Style} style The rendering style.
 * @api
 */
ol.render.webgl.Immediate.prototype.setStyle = function(style) {
  this.setImageStyle(style.getImage());
};


/**
 * Render a geometry into the canvas.  Call
 * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style.
 *
 * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render.
 * @api
 */
ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) {
  var type = geometry.getType();
  switch (type) {
    case ol.geom.GeometryType.POINT:
      this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null);
      break;
    case ol.geom.GeometryType.MULTI_POINT:
      this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null);
      break;
    case ol.geom.GeometryType.GEOMETRY_COLLECTION:
      this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null);
      break;
    default:
      goog.DEBUG && console.assert(false, 'Unsupported geometry type: ' + type);
  }
};


/**
 * @inheritDoc
 * @api
 */
ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) {
  var geometry = style.getGeometryFunction()(feature);
  if (!geometry ||
      !ol.extent.intersects(this.extent_, geometry.getExtent())) {
    return;
  }
  this.setStyle(style);
  goog.DEBUG && console.assert(geometry, 'geometry must be truthy');
  this.drawGeometry(geometry);
};


/**
 * @inheritDoc
 */
ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) {
  var geometries = geometry.getGeometriesArray();
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    this.drawGeometry(geometries[i]);
  }
};


/**
 * @inheritDoc
 */
ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) {
  var context = this.context_;
  var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
  var replay = /** @type {ol.render.webgl.ImageReplay} */ (
      replayGroup.getReplay(0, ol.render.ReplayType.IMAGE));
  replay.setImageStyle(this.imageStyle_);
  replay.drawPoint(geometry, data);
  replay.finish(context);
  // default colors
  var opacity = 1;
  var skippedFeatures = {};
  var featureCallback;
  var oneByOne = false;
  replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
      this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback,
      oneByOne);
  replay.getDeleteResourcesFunction(context)();
};


/**
 * @inheritDoc
 */
ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) {
  var context = this.context_;
  var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_);
  var replay = /** @type {ol.render.webgl.ImageReplay} */ (
      replayGroup.getReplay(0, ol.render.ReplayType.IMAGE));
  replay.setImageStyle(this.imageStyle_);
  replay.drawMultiPoint(geometry, data);
  replay.finish(context);
  var opacity = 1;
  var skippedFeatures = {};
  var featureCallback;
  var oneByOne = false;
  replay.replay(this.context_, this.center_, this.resolution_, this.rotation_,
      this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback,
      oneByOne);
  replay.getDeleteResourcesFunction(context)();
};


/**
 * @inheritDoc
 */
ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) {
  this.imageStyle_ = imageStyle;
};

// This file is automatically generated, do not edit
goog.provide('ol.renderer.webgl.defaultmapshader');

goog.require('ol');
goog.require('ol.webgl.Fragment');
goog.require('ol.webgl.Vertex');


/**
 * @constructor
 * @extends {ol.webgl.Fragment}
 * @struct
 */
ol.renderer.webgl.defaultmapshader.Fragment = function() {
  ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE);
};
ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment);


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  vec4 texColor = texture2D(u_texture, v_texCoord);\n  gl_FragColor.rgb = texColor.rgb;\n  gl_FragColor.a = texColor.a * u_opacity;\n}\n';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = goog.DEBUG ?
    ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE :
    ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE;


ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment();


/**
 * @constructor
 * @extends {ol.webgl.Vertex}
 * @struct
 */
ol.renderer.webgl.defaultmapshader.Vertex = function() {
  ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE);
};
ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex);


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n  gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n  v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = goog.DEBUG ?
    ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE :
    ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE;


ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex();


/**
 * @constructor
 * @param {WebGLRenderingContext} gl GL.
 * @param {WebGLProgram} program Program.
 * @struct
 */
ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) {

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_opacity = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_opacity' : 'f');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_projectionMatrix = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_projectionMatrix' : 'e');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_texCoordMatrix = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_texCoordMatrix' : 'd');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_texture = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_texture' : 'g');

  /**
   * @type {number}
   */
  this.a_position = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_position' : 'b');

  /**
   * @type {number}
   */
  this.a_texCoord = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_texCoord' : 'c');
};

goog.provide('ol.renderer.webgl.Layer');

goog.require('ol');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.webgl.Immediate');
goog.require('ol.renderer.Layer');
goog.require('ol.renderer.webgl.defaultmapshader');
goog.require('ol.transform');
goog.require('ol.vec.Mat4');
goog.require('ol.webgl');
goog.require('ol.webgl.Buffer');
goog.require('ol.webgl.Context');


/**
 * @constructor
 * @extends {ol.renderer.Layer}
 * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
 * @param {ol.layer.Layer} layer Layer.
 */
ol.renderer.webgl.Layer = function(mapRenderer, layer) {

  ol.renderer.Layer.call(this, layer);

  /**
   * @protected
   * @type {ol.renderer.webgl.Map}
   */
  this.mapRenderer = mapRenderer;

  /**
   * @private
   * @type {ol.webgl.Buffer}
   */
  this.arrayBuffer_ = new ol.webgl.Buffer([
    -1, -1, 0, 0,
    1, -1, 1, 0,
    -1, 1, 0, 1,
    1, 1, 1, 1
  ]);

  /**
   * @protected
   * @type {WebGLTexture}
   */
  this.texture = null;

  /**
   * @protected
   * @type {WebGLFramebuffer}
   */
  this.framebuffer = null;

  /**
   * @protected
   * @type {number|undefined}
   */
  this.framebufferDimension = undefined;

  /**
   * @protected
   * @type {ol.Transform}
   */
  this.texCoordMatrix = ol.transform.create();

  /**
   * @protected
   * @type {ol.Transform}
   */
  this.projectionMatrix = ol.transform.create();

  /**
   * @type {Array.<number>}
   * @private
   */
  this.tmpMat4_ = ol.vec.Mat4.create();

  /**
   * @private
   * @type {ol.renderer.webgl.defaultmapshader.Locations}
   */
  this.defaultLocations_ = null;

};
ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {number} framebufferDimension Framebuffer dimension.
 * @protected
 */
ol.renderer.webgl.Layer.prototype.bindFramebuffer = function(frameState, framebufferDimension) {

  var gl = this.mapRenderer.getGL();

  if (this.framebufferDimension === undefined ||
      this.framebufferDimension != framebufferDimension) {
    /**
     * @param {WebGLRenderingContext} gl GL.
     * @param {WebGLFramebuffer} framebuffer Framebuffer.
     * @param {WebGLTexture} texture Texture.
     */
    var postRenderFunction = function(gl, framebuffer, texture) {
      if (!gl.isContextLost()) {
        gl.deleteFramebuffer(framebuffer);
        gl.deleteTexture(texture);
      }
    }.bind(null, gl, this.framebuffer, this.texture);

    frameState.postRenderFunctions.push(
      /** @type {ol.PostRenderFunction} */ (postRenderFunction)
    );

    var texture = ol.webgl.Context.createEmptyTexture(
        gl, framebufferDimension, framebufferDimension);

    var framebuffer = gl.createFramebuffer();
    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer);
    gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER,
        ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0);

    this.texture = texture;
    this.framebuffer = framebuffer;
    this.framebufferDimension = framebufferDimension;

  } else {
    gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer);
  }

};


/**
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {ol.webgl.Context} context Context.
 */
ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) {

  this.dispatchComposeEvent_(
      ol.render.EventType.PRECOMPOSE, context, frameState);

  context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_);

  var gl = context.getGL();

  var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment;
  var vertexShader = ol.renderer.webgl.defaultmapshader.vertex;

  var program = context.getProgram(fragmentShader, vertexShader);

  var locations;
  if (!this.defaultLocations_) {
    locations =
        new ol.renderer.webgl.defaultmapshader.Locations(gl, program);
    this.defaultLocations_ = locations;
  } else {
    locations = this.defaultLocations_;
  }

  if (context.useProgram(program)) {
    gl.enableVertexAttribArray(locations.a_position);
    gl.vertexAttribPointer(
        locations.a_position, 2, ol.webgl.FLOAT, false, 16, 0);
    gl.enableVertexAttribArray(locations.a_texCoord);
    gl.vertexAttribPointer(
        locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8);
    gl.uniform1i(locations.u_texture, 0);
  }

  gl.uniformMatrix4fv(locations.u_texCoordMatrix, false,
      ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix()));
  gl.uniformMatrix4fv(locations.u_projectionMatrix, false,
      ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix()));
  gl.uniform1f(locations.u_opacity, layerState.opacity);
  gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture());
  gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4);

  this.dispatchComposeEvent_(
      ol.render.EventType.POSTCOMPOSE, context, frameState);

};


/**
 * @param {ol.render.EventType} type Event type.
 * @param {ol.webgl.Context} context WebGL context.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState) {
  var layer = this.getLayer();
  if (layer.hasListener(type)) {
    var viewState = frameState.viewState;
    var resolution = viewState.resolution;
    var pixelRatio = frameState.pixelRatio;
    var extent = frameState.extent;
    var center = viewState.center;
    var rotation = viewState.rotation;
    var size = frameState.size;

    var render = new ol.render.webgl.Immediate(
        context, center, resolution, rotation, size, extent, pixelRatio);
    var composeEvent = new ol.render.Event(
        type, render, frameState, null, context);
    layer.dispatchEvent(composeEvent);
  }
};


/**
 * @return {!ol.Transform} Matrix.
 */
ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
  return this.texCoordMatrix;
};


/**
 * @return {WebGLTexture} Texture.
 */
ol.renderer.webgl.Layer.prototype.getTexture = function() {
  return this.texture;
};


/**
 * @return {!ol.Transform} Matrix.
 */
ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
  return this.projectionMatrix;
};


/**
 * Handle webglcontextlost.
 */
ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
  this.texture = null;
  this.framebuffer = null;
  this.framebufferDimension = undefined;
};


/**
 * @abstract
 * @param {olx.FrameState} frameState Frame state.
 * @param {ol.LayerState} layerState Layer state.
 * @param {ol.webgl.Context} context Context.
 * @return {boolean} whether composeFrame should be called.
 */
ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {};

goog.provide('ol.renderer.webgl.ImageLayer');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.functions');
goog.require('ol.proj');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.source.ImageVector');
goog.require('ol.transform');
goog.require('ol.webgl');
goog.require('ol.webgl.Context');


/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
 * @param {ol.layer.Image} imageLayer Tile layer.
 */
ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {

  ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer);

  /**
   * The last rendered image.
   * @private
   * @type {?ol.ImageBase}
   */
  this.image_ = null;

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.hitCanvasContext_ = null;

  /**
   * @private
   * @type {?ol.Transform}
   */
  this.hitTransformationMatrix_ = null;

};
ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);


/**
 * @param {ol.ImageBase} image Image.
 * @private
 * @return {WebGLTexture} Texture.
 */
ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {

  // We meet the conditions to work with non-power of two textures.
  // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support
  // http://learningwebgl.com/blog/?p=2101

  var imageElement = image.getImage();
  var gl = this.mapRenderer.getGL();

  return ol.webgl.Context.createTexture(
      gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE);
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  var layer = this.getLayer();
  var source = layer.getSource();
  var resolution = frameState.viewState.resolution;
  var rotation = frameState.viewState.rotation;
  var skippedFeatureUids = frameState.skippedFeatureUids;
  return source.forEachFeatureAtCoordinate(
      coordinate, resolution, rotation, skippedFeatureUids,

      /**
       * @param {ol.Feature|ol.render.Feature} feature Feature.
       * @return {?} Callback result.
       */
      function(feature) {
        return callback.call(thisArg, feature, layer);
      });
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) {

  var gl = this.mapRenderer.getGL();

  var pixelRatio = frameState.pixelRatio;
  var viewState = frameState.viewState;
  var viewCenter = viewState.center;
  var viewResolution = viewState.resolution;
  var viewRotation = viewState.rotation;

  var image = this.image_;
  var texture = this.texture;
  var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer());
  var imageSource = imageLayer.getSource();

  var hints = frameState.viewHints;

  var renderedExtent = frameState.extent;
  if (layerState.extent !== undefined) {
    renderedExtent = ol.extent.getIntersection(
        renderedExtent, layerState.extent);
  }
  if (!hints[ol.View.Hint.ANIMATING] && !hints[ol.View.Hint.INTERACTING] &&
      !ol.extent.isEmpty(renderedExtent)) {
    var projection = viewState.projection;
    if (!ol.ENABLE_RASTER_REPROJECTION) {
      var sourceProjection = imageSource.getProjection();
      if (sourceProjection) {
        goog.DEBUG && console.assert(ol.proj.equivalent(projection, sourceProjection),
            'projection and sourceProjection are equivalent');
        projection = sourceProjection;
      }
    }
    var image_ = imageSource.getImage(renderedExtent, viewResolution,
        pixelRatio, projection);
    if (image_) {
      var loaded = this.loadImage(image_);
      if (loaded) {
        image = image_;
        texture = this.createTexture_(image_);
        if (this.texture) {
          /**
           * @param {WebGLRenderingContext} gl GL.
           * @param {WebGLTexture} texture Texture.
           */
          var postRenderFunction = function(gl, texture) {
            if (!gl.isContextLost()) {
              gl.deleteTexture(texture);
            }
          }.bind(null, gl, this.texture);
          frameState.postRenderFunctions.push(
            /** @type {ol.PostRenderFunction} */ (postRenderFunction)
          );
        }
      }
    }
  }

  if (image) {
    goog.DEBUG && console.assert(texture, 'texture is truthy');

    var canvas = this.mapRenderer.getContext().getCanvas();

    this.updateProjectionMatrix_(canvas.width, canvas.height,
        pixelRatio, viewCenter, viewResolution, viewRotation,
        image.getExtent());
    this.hitTransformationMatrix_ = null;

    // Translate and scale to flip the Y coord.
    var texCoordMatrix = this.texCoordMatrix;
    ol.transform.reset(texCoordMatrix);
    ol.transform.scale(texCoordMatrix, 1, -1);
    ol.transform.translate(texCoordMatrix, 0, -1);

    this.image_ = image;
    this.texture = texture;

    this.updateAttributions(frameState.attributions, image.getAttributions());
    this.updateLogos(frameState, imageSource);
  }

  return true;
};


/**
 * @param {number} canvasWidth Canvas width.
 * @param {number} canvasHeight Canvas height.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.Coordinate} viewCenter View center.
 * @param {number} viewResolution View resolution.
 * @param {number} viewRotation View rotation.
 * @param {ol.Extent} imageExtent Image extent.
 * @private
 */
ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio,
        viewCenter, viewResolution, viewRotation, imageExtent) {

  var canvasExtentWidth = canvasWidth * viewResolution;
  var canvasExtentHeight = canvasHeight * viewResolution;

  var projectionMatrix = this.projectionMatrix;
  ol.transform.reset(projectionMatrix);
  ol.transform.scale(projectionMatrix,
      pixelRatio * 2 / canvasExtentWidth,
      pixelRatio * 2 / canvasExtentHeight);
  ol.transform.rotate(projectionMatrix, -viewRotation);
  ol.transform.translate(projectionMatrix,
      imageExtent[0] - viewCenter[0],
      imageExtent[1] - viewCenter[1]);
  ol.transform.scale(projectionMatrix,
      (imageExtent[2] - imageExtent[0]) / 2,
      (imageExtent[3] - imageExtent[1]) / 2);
  ol.transform.translate(projectionMatrix, 1, 1);

};


/**
 * @inheritDoc
 */
ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
  var hasFeature = this.forEachFeatureAtCoordinate(
      coordinate, frameState, ol.functions.TRUE, this);
  return hasFeature !== undefined;
};


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
  if (!this.image_ || !this.image_.getImage()) {
    return undefined;
  }

  if (this.getLayer().getSource() instanceof ol.source.ImageVector) {
    // for ImageVector sources use the original hit-detection logic,
    // so that for example also transparent polygons are detected
    var coordinate = ol.transform.apply(
        frameState.pixelToCoordinateTransform, pixel.slice());
    var hasFeature = this.forEachFeatureAtCoordinate(
        coordinate, frameState, ol.functions.TRUE, this);

    if (hasFeature) {
      return callback.call(thisArg, this.getLayer(), null);
    } else {
      return undefined;
    }
  } else {
    var imageSize =
        [this.image_.getImage().width, this.image_.getImage().height];

    if (!this.hitTransformationMatrix_) {
      this.hitTransformationMatrix_ = this.getHitTransformationMatrix_(
          frameState.size, imageSize);
    }

    var pixelOnFrameBuffer = ol.transform.apply(
        this.hitTransformationMatrix_, pixel.slice());

    if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] ||
        pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) {
      // outside the image, no need to check
      return undefined;
    }

    if (!this.hitCanvasContext_) {
      this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1);
    }

    this.hitCanvasContext_.clearRect(0, 0, 1, 1);
    this.hitCanvasContext_.drawImage(this.image_.getImage(),
        pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1);

    var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data;
    if (imageData[3] > 0) {
      return callback.call(thisArg, this.getLayer(),  imageData);
    } else {
      return undefined;
    }
  }
};


/**
 * The transformation matrix to get the pixel on the image for a
 * pixel on the map.
 * @param {ol.Size} mapSize The map size.
 * @param {ol.Size} imageSize The image size.
 * @return {ol.Transform} The transformation matrix.
 * @private
 */
ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = function(mapSize, imageSize) {
  // the first matrix takes a map pixel, flips the y-axis and scales to
  // a range between -1 ... 1
  var mapCoordTransform = ol.transform.create();
  ol.transform.translate(mapCoordTransform, -1, -1);
  ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]);
  ol.transform.translate(mapCoordTransform, 0, mapSize[1]);
  ol.transform.scale(mapCoordTransform, 1, -1);

  // the second matrix is the inverse of the projection matrix used in the
  // shader for drawing
  var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice());

  // the third matrix scales to the image dimensions and flips the y-axis again
  var transform = ol.transform.create();
  ol.transform.translate(transform, 0, imageSize[1]);
  ol.transform.scale(transform, 1, -1);
  ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2);
  ol.transform.translate(transform, 1, 1);

  ol.transform.multiply(transform, projectionMatrixInv);
  ol.transform.multiply(transform, mapCoordTransform);

  return transform;
};

// This file is automatically generated, do not edit
goog.provide('ol.renderer.webgl.tilelayershader');

goog.require('ol');
goog.require('ol.webgl.Fragment');
goog.require('ol.webgl.Vertex');


/**
 * @constructor
 * @extends {ol.webgl.Fragment}
 * @struct
 */
ol.renderer.webgl.tilelayershader.Fragment = function() {
  ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE);
};
ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment);


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n  gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.tilelayershader.Fragment.SOURCE = goog.DEBUG ?
    ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE :
    ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE;


ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment();


/**
 * @constructor
 * @extends {ol.webgl.Vertex}
 * @struct
 */
ol.renderer.webgl.tilelayershader.Vertex = function() {
  ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE);
};
ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex);


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n  gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n  v_texCoord = a_texCoord;\n}\n\n\n';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}';


/**
 * @const
 * @type {string}
 */
ol.renderer.webgl.tilelayershader.Vertex.SOURCE = goog.DEBUG ?
    ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE :
    ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE;


ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex();


/**
 * @constructor
 * @param {WebGLRenderingContext} gl GL.
 * @param {WebGLProgram} program Program.
 * @struct
 */
ol.renderer.webgl.tilelayershader.Locations = function(gl, program) {

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_texture = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_texture' : 'e');

  /**
   * @type {WebGLUniformLocation}
   */
  this.u_tileOffset = gl.getUniformLocation(
      program, goog.DEBUG ? 'u_tileOffset' : 'd');

  /**
   * @type {number}
   */
  this.a_position = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_position' : 'b');

  /**
   * @type {number}
   */
  this.a_texCoord = gl.getAttribLocation(
      program, goog.DEBUG ? 'a_texCoord' : 'c');
};

// FIXME large resolutions lead to too large framebuffers :-(
// FIXME animated shaders! check in redraw

goog.provide('ol.renderer.webgl.TileLayer');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.TileRange');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.tilelayershader');
goog.require('ol.size');
goog.require('ol.transform');
goog.require('ol.webgl');
goog.require('ol.webgl.Buffer');


/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
 * @param {ol.layer.Tile} tileLayer Tile layer.
 */
ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {

  ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer);

  /**
   * @private
   * @type {ol.webgl.Fragment}
   */
  this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment;

  /**
   * @private
   * @type {ol.webgl.Vertex}
   */
  this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex;

  /**
   * @private
   * @type {ol.renderer.webgl.tilelayershader.Locations}
   */
  this.locations_ = null;

  /**
   * @private
   * @type {ol.webgl.Buffer}
   */
  this.renderArrayBuffer_ = new ol.webgl.Buffer([
    0, 0, 0, 1,
    1, 0, 1, 1,
    0, 1, 0, 0,
    1, 1, 1, 0
  ]);

  /**
   * @private
   * @type {ol.TileRange}
   */
  this.renderedTileRange_ = null;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedFramebufferExtent_ = null;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

  /**
   * @private
   * @type {ol.Size}
   */
  this.tmpSize_ = [0, 0];

};
ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);


/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
  var context = this.mapRenderer.getContext();
  context.deleteBuffer(this.renderArrayBuffer_);
  ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
};


/**
 * Create a function that adds loaded tiles to the tile lookup.
 * @param {ol.source.Tile} source Tile source.
 * @param {ol.proj.Projection} projection Projection of the tiles.
 * @param {Object.<number, Object.<string, ol.Tile>>} tiles Lookup of loaded
 *     tiles by zoom level.
 * @return {function(number, ol.TileRange):boolean} A function that can be
 *     called with a zoom level and a tile range to add loaded tiles to the
 *     lookup.
 * @protected
 */
ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) {
  var mapRenderer = this.mapRenderer;

  return (
      /**
       * @param {number} zoom Zoom level.
       * @param {ol.TileRange} tileRange Tile range.
       * @return {boolean} The tile range is fully loaded.
       */
      function(zoom, tileRange) {
        function callback(tile) {
          var loaded = mapRenderer.isTileTextureLoaded(tile);
          if (loaded) {
            if (!tiles[zoom]) {
              tiles[zoom] = {};
            }
            tiles[zoom][tile.tileCoord.toString()] = tile;
          }
          return loaded;
        }
        return source.forEachLoadedTile(projection, zoom, tileRange, callback);
      });
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
  ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this);
  this.locations_ = null;
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) {

  var mapRenderer = this.mapRenderer;
  var gl = context.getGL();

  var viewState = frameState.viewState;
  var projection = viewState.projection;

  var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer());
  var tileSource = tileLayer.getSource();
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var z = tileGrid.getZForResolution(viewState.resolution);
  var tileResolution = tileGrid.getResolution(z);

  var tilePixelSize =
      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
  var pixelRatio = tilePixelSize[0] /
      ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0];
  var tilePixelResolution = tileResolution / pixelRatio;
  var tileGutter = frameState.pixelRatio * tileSource.getGutter(projection);

  var center = viewState.center;
  var extent;
  if (tileResolution == viewState.resolution) {
    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
    extent = ol.extent.getForViewAndSize(
        center, tileResolution, viewState.rotation, frameState.size);
  } else {
    extent = frameState.extent;
  }
  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      extent, tileResolution);

  var framebufferExtent;
  if (this.renderedTileRange_ &&
      this.renderedTileRange_.equals(tileRange) &&
      this.renderedRevision_ == tileSource.getRevision()) {
    framebufferExtent = this.renderedFramebufferExtent_;
  } else {

    var tileRangeSize = tileRange.getSize();

    var maxDimension = Math.max(
        tileRangeSize[0] * tilePixelSize[0],
        tileRangeSize[1] * tilePixelSize[1]);
    var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
    var origin = tileGrid.getOrigin(z);
    var minX = origin[0] +
        tileRange.minX * tilePixelSize[0] * tilePixelResolution;
    var minY = origin[1] +
        tileRange.minY * tilePixelSize[1] * tilePixelResolution;
    framebufferExtent = [
      minX, minY,
      minX + framebufferExtentDimension, minY + framebufferExtentDimension
    ];

    this.bindFramebuffer(frameState, framebufferDimension);
    gl.viewport(0, 0, framebufferDimension, framebufferDimension);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(ol.webgl.COLOR_BUFFER_BIT);
    gl.disable(ol.webgl.BLEND);

    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
    context.useProgram(program);
    if (!this.locations_) {
      this.locations_ =
          new ol.renderer.webgl.tilelayershader.Locations(gl, program);
    }

    context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
    gl.enableVertexAttribArray(this.locations_.a_position);
    gl.vertexAttribPointer(
        this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0);
    gl.enableVertexAttribArray(this.locations_.a_texCoord);
    gl.vertexAttribPointer(
        this.locations_.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8);
    gl.uniform1i(this.locations_.u_texture, 0);

    /**
     * @type {Object.<number, Object.<string, ol.Tile>>}
     */
    var tilesToDrawByZ = {};
    tilesToDrawByZ[z] = {};

    var findLoadedTiles = this.createLoadedTileFinder(
        tileSource, projection, tilesToDrawByZ);

    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
    var allTilesLoaded = true;
    var tmpExtent = ol.extent.createEmpty();
    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
    var childTileRange, drawable, fullyLoaded, tile, tileState;
    var x, y, tileExtent;
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {

        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
        if (layerState.extent !== undefined) {
          // ignore tiles outside layer extent
          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
          if (!ol.extent.intersects(tileExtent, layerState.extent)) {
            continue;
          }
        }
        tileState = tile.getState();
        drawable = tileState == ol.Tile.State.LOADED ||
            tileState == ol.Tile.State.EMPTY ||
            tileState == ol.Tile.State.ERROR && !useInterimTilesOnError;
        if (!drawable && tile.interimTile) {
          tile = tile.interimTile;
        }
        tileState = tile.getState();
        if (tileState == ol.Tile.State.LOADED) {
          if (mapRenderer.isTileTextureLoaded(tile)) {
            tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
            continue;
          }
        } else if (tileState == ol.Tile.State.EMPTY ||
                   (tileState == ol.Tile.State.ERROR &&
                    !useInterimTilesOnError)) {
          continue;
        }

        allTilesLoaded = false;
        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
        if (!fullyLoaded) {
          childTileRange = tileGrid.getTileCoordChildTileRange(
              tile.tileCoord, tmpTileRange, tmpExtent);
          if (childTileRange) {
            findLoadedTiles(z + 1, childTileRange);
          }
        }

      }

    }

    /** @type {Array.<number>} */
    var zs = Object.keys(tilesToDrawByZ).map(Number);
    zs.sort(ol.array.numberSafeCompareFunction);
    var u_tileOffset = new Float32Array(4);
    var i, ii, tileKey, tilesToDraw;
    for (i = 0, ii = zs.length; i < ii; ++i) {
      tilesToDraw = tilesToDrawByZ[zs[i]];
      for (tileKey in tilesToDraw) {
        tile = tilesToDraw[tileKey];
        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
        u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) /
            framebufferExtentDimension;
        u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) /
            framebufferExtentDimension;
        u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) /
            framebufferExtentDimension - 1;
        u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) /
            framebufferExtentDimension - 1;
        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
        mapRenderer.bindTileTexture(tile, tilePixelSize,
            tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR);
        gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4);
      }
    }

    if (allTilesLoaded) {
      this.renderedTileRange_ = tileRange;
      this.renderedFramebufferExtent_ = framebufferExtent;
      this.renderedRevision_ = tileSource.getRevision();
    } else {
      this.renderedTileRange_ = null;
      this.renderedFramebufferExtent_ = null;
      this.renderedRevision_ = -1;
      frameState.animate = true;
    }

  }

  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
  var tileTextureQueue = mapRenderer.getTileTextureQueue();
  this.manageTilePyramid(
      frameState, tileSource, tileGrid, pixelRatio, projection, extent, z,
      tileLayer.getPreload(),
      /**
       * @param {ol.Tile} tile Tile.
       */
      function(tile) {
        if (tile.getState() == ol.Tile.State.LOADED &&
            !mapRenderer.isTileTextureLoaded(tile) &&
            !tileTextureQueue.isKeyQueued(tile.getKey())) {
          tileTextureQueue.enqueue([
            tile,
            tileGrid.getTileCoordCenter(tile.tileCoord),
            tileGrid.getResolution(tile.tileCoord[0]),
            tilePixelSize, tileGutter * pixelRatio
          ]);
        }
      }, this);
  this.scheduleExpireCache(frameState, tileSource);
  this.updateLogos(frameState, tileSource);

  var texCoordMatrix = this.texCoordMatrix;
  ol.transform.reset(texCoordMatrix);
  ol.transform.translate(texCoordMatrix,
      (center[0] - framebufferExtent[0]) /
          (framebufferExtent[2] - framebufferExtent[0]),
      (center[1] - framebufferExtent[1]) /
          (framebufferExtent[3] - framebufferExtent[1]));
  if (viewState.rotation !== 0) {
    ol.transform.rotate(texCoordMatrix, viewState.rotation);
  }
  ol.transform.scale(texCoordMatrix,
      frameState.size[0] * viewState.resolution /
          (framebufferExtent[2] - framebufferExtent[0]),
      frameState.size[1] * viewState.resolution /
          (framebufferExtent[3] - framebufferExtent[1]));
  ol.transform.translate(texCoordMatrix, -0.5, -0.5);

  return true;
};


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
  if (!this.framebuffer) {
    return undefined;
  }

  var pixelOnMapScaled = [
    pixel[0] / frameState.size[0],
    (frameState.size[1] - pixel[1]) / frameState.size[1]];

  var pixelOnFrameBufferScaled = ol.transform.apply(
      this.texCoordMatrix, pixelOnMapScaled.slice());
  var pixelOnFrameBuffer = [
    pixelOnFrameBufferScaled[0] * this.framebufferDimension,
    pixelOnFrameBufferScaled[1] * this.framebufferDimension];

  var gl = this.mapRenderer.getContext().getGL();
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
  var imageData = new Uint8Array(4);
  gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1,
      gl.RGBA, gl.UNSIGNED_BYTE, imageData);

  if (imageData[3] > 0) {
    return callback.call(thisArg, this.getLayer(),  imageData);
  } else {
    return undefined;
  }
};

goog.provide('ol.renderer.webgl.VectorLayer');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.extent');
goog.require('ol.render.webgl.ReplayGroup');
goog.require('ol.renderer.vector');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.transform');


/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.webgl.Map} mapRenderer Map renderer.
 * @param {ol.layer.Vector} vectorLayer Vector layer.
 */
ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) {

  ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer);

  /**
   * @private
   * @type {boolean}
   */
  this.dirty_ = false;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.renderedResolution_ = NaN;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedExtent_ = ol.extent.createEmpty();

  /**
   * @private
   * @type {function(ol.Feature, ol.Feature): number|null}
   */
  this.renderedRenderOrder_ = null;

  /**
   * @private
   * @type {ol.render.webgl.ReplayGroup}
   */
  this.replayGroup_ = null;

  /**
   * The last layer state.
   * @private
   * @type {?ol.LayerState}
   */
  this.layerState_ = null;

};
ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer);


/**
 * @inheritDoc
 */
ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) {
  this.layerState_ = layerState;
  var viewState = frameState.viewState;
  var replayGroup = this.replayGroup_;
  if (replayGroup && !replayGroup.isEmpty()) {
    replayGroup.replay(context,
        viewState.center, viewState.resolution, viewState.rotation,
        frameState.size, frameState.pixelRatio, layerState.opacity,
        layerState.managed ? frameState.skippedFeatureUids : {});
  }

};


/**
 * @inheritDoc
 */
ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
  var replayGroup = this.replayGroup_;
  if (replayGroup) {
    var context = this.mapRenderer.getContext();
    replayGroup.getDeleteResourcesFunction(context)();
    this.replayGroup_ = null;
  }
  ol.renderer.webgl.Layer.prototype.disposeInternal.call(this);
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
  if (!this.replayGroup_ || !this.layerState_) {
    return undefined;
  } else {
    var context = this.mapRenderer.getContext();
    var viewState = frameState.viewState;
    var layer = this.getLayer();
    var layerState = this.layerState_;
    /** @type {Object.<string, boolean>} */
    var features = {};
    return this.replayGroup_.forEachFeatureAtCoordinate(coordinate,
        context, viewState.center, viewState.resolution, viewState.rotation,
        frameState.size, frameState.pixelRatio, layerState.opacity,
        {},
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @return {?} Callback result.
         */
        function(feature) {
          var key = ol.getUid(feature).toString();
          if (!(key in features)) {
            features[key] = true;
            return callback.call(thisArg, feature, layer);
          }
        });
  }
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
  if (!this.replayGroup_ || !this.layerState_) {
    return false;
  } else {
    var context = this.mapRenderer.getContext();
    var viewState = frameState.viewState;
    var layerState = this.layerState_;
    return this.replayGroup_.hasFeatureAtCoordinate(coordinate,
        context, viewState.center, viewState.resolution, viewState.rotation,
        frameState.size, frameState.pixelRatio, layerState.opacity,
        frameState.skippedFeatureUids);
  }
};


/**
 * @param {ol.Pixel} pixel Pixel.
 * @param {olx.FrameState} frameState FrameState.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback.
 * @param {S} thisArg Value to use as `this` when executing `callback`.
 * @return {T|undefined} Callback result.
 * @template S,T,U
 */
ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {
  var coordinate = ol.transform.apply(
      frameState.pixelToCoordinateTransform, pixel.slice());
  var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState);

  if (hasFeature) {
    return callback.call(thisArg, this.getLayer(), null);
  } else {
    return undefined;
  }
};


/**
 * Handle changes in image style state.
 * @param {ol.events.Event} event Image style change event.
 * @private
 */
ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) {
  this.renderIfReadyAndVisible();
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) {

  var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
  var vectorSource = vectorLayer.getSource();

  this.updateAttributions(
      frameState.attributions, vectorSource.getAttributions());
  this.updateLogos(frameState, vectorSource);

  var animating = frameState.viewHints[ol.View.Hint.ANIMATING];
  var interacting = frameState.viewHints[ol.View.Hint.INTERACTING];
  var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating();
  var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting();

  if (!this.dirty_ && (!updateWhileAnimating && animating) ||
      (!updateWhileInteracting && interacting)) {
    return true;
  }

  var frameStateExtent = frameState.extent;
  var viewState = frameState.viewState;
  var projection = viewState.projection;
  var resolution = viewState.resolution;
  var pixelRatio = frameState.pixelRatio;
  var vectorLayerRevision = vectorLayer.getRevision();
  var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer();
  var vectorLayerRenderOrder = vectorLayer.getRenderOrder();

  if (vectorLayerRenderOrder === undefined) {
    vectorLayerRenderOrder = ol.renderer.vector.defaultOrder;
  }

  var extent = ol.extent.buffer(frameStateExtent,
      vectorLayerRenderBuffer * resolution);

  if (!this.dirty_ &&
      this.renderedResolution_ == resolution &&
      this.renderedRevision_ == vectorLayerRevision &&
      this.renderedRenderOrder_ == vectorLayerRenderOrder &&
      ol.extent.containsExtent(this.renderedExtent_, extent)) {
    return true;
  }

  if (this.replayGroup_) {
    frameState.postRenderFunctions.push(
        this.replayGroup_.getDeleteResourcesFunction(context));
  }

  this.dirty_ = false;

  var replayGroup = new ol.render.webgl.ReplayGroup(
      ol.renderer.vector.getTolerance(resolution, pixelRatio),
      extent, vectorLayer.getRenderBuffer());
  vectorSource.loadFeatures(extent, resolution, projection);
  /**
   * @param {ol.Feature} feature Feature.
   * @this {ol.renderer.webgl.VectorLayer}
   */
  var renderFeature = function(feature) {
    var styles;
    var styleFunction = feature.getStyleFunction();
    if (styleFunction) {
      styles = styleFunction.call(feature, resolution);
    } else {
      styleFunction = vectorLayer.getStyleFunction();
      if (styleFunction) {
        styles = styleFunction(feature, resolution);
      }
    }
    if (styles) {
      var dirty = this.renderFeature(
          feature, resolution, pixelRatio, styles, replayGroup);
      this.dirty_ = this.dirty_ || dirty;
    }
  };
  if (vectorLayerRenderOrder) {
    /** @type {Array.<ol.Feature>} */
    var features = [];
    vectorSource.forEachFeatureInExtent(extent,
        /**
         * @param {ol.Feature} feature Feature.
         */
        function(feature) {
          features.push(feature);
        }, this);
    features.sort(vectorLayerRenderOrder);
    features.forEach(renderFeature, this);
  } else {
    vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
  }
  replayGroup.finish(context);

  this.renderedResolution_ = resolution;
  this.renderedRevision_ = vectorLayerRevision;
  this.renderedRenderOrder_ = vectorLayerRenderOrder;
  this.renderedExtent_ = extent;
  this.replayGroup_ = replayGroup;

  return true;
};


/**
 * @param {ol.Feature} feature Feature.
 * @param {number} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {(ol.style.Style|Array.<ol.style.Style>)} styles The style or array of
 *     styles.
 * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group.
 * @return {boolean} `true` if an image is loading.
 */
ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) {
  if (!styles) {
    return false;
  }
  var loading = false;
  if (Array.isArray(styles)) {
    for (var i = 0, ii = styles.length; i < ii; ++i) {
      loading = ol.renderer.vector.renderFeature(
          replayGroup, feature, styles[i],
          ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
          this.handleStyleImageChange_, this) || loading;
    }
  } else {
    loading = ol.renderer.vector.renderFeature(
        replayGroup, feature, styles,
        ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio),
        this.handleStyleImageChange_, this) || loading;
  }
  return loading;
};

goog.provide('ol.structs.LRUCache');

goog.require('ol.asserts');
goog.require('ol.obj');


/**
 * Implements a Least-Recently-Used cache where the keys do not conflict with
 * Object's properties (e.g. 'hasOwnProperty' is not allowed as a key). Expiring
 * items from the cache is the responsibility of the user.
 * @constructor
 * @struct
 * @template T
 */
ol.structs.LRUCache = function() {

  /**
   * @private
   * @type {number}
   */
  this.count_ = 0;

  /**
   * @private
   * @type {!Object.<string, ol.LRUCacheEntry>}
   */
  this.entries_ = {};

  /**
   * @private
   * @type {?ol.LRUCacheEntry}
   */
  this.oldest_ = null;

  /**
   * @private
   * @type {?ol.LRUCacheEntry}
   */
  this.newest_ = null;

};


if (goog.DEBUG) {
  /**
   * FIXME empty description for jsdoc
   */
  ol.structs.LRUCache.prototype.assertValid = function() {
    if (this.count_ === 0) {
      console.assert(ol.obj.isEmpty(this.entries_),
          'entries must be an empty object (count = 0)');
      console.assert(!this.oldest_,
          'oldest must be null (count = 0)');
      console.assert(!this.newest_,
          'newest must be null (count = 0)');
    } else {
      console.assert(Object.keys(this.entries_).length == this.count_,
          'number of entries matches count');
      console.assert(this.oldest_,
          'we have an oldest entry');
      console.assert(!this.oldest_.older,
          'no entry is older than oldest');
      console.assert(this.newest_,
          'we have a newest entry');
      console.assert(!this.newest_.newer,
          'no entry is newer than newest');
      var i, entry;
      var older = null;
      i = 0;
      for (entry = this.oldest_; entry; entry = entry.newer) {
        console.assert(entry.older === older,
            'entry.older links to correct older');
        older = entry;
        ++i;
      }
      console.assert(i == this.count_, 'iterated correct amount of times');
      var newer = null;
      i = 0;
      for (entry = this.newest_; entry; entry = entry.older) {
        console.assert(entry.newer === newer,
            'entry.newer links to correct newer');
        newer = entry;
        ++i;
      }
      console.assert(i == this.count_, 'iterated correct amount of times');
    }
  };
}


/**
 * FIXME empty description for jsdoc
 */
ol.structs.LRUCache.prototype.clear = function() {
  this.count_ = 0;
  this.entries_ = {};
  this.oldest_ = null;
  this.newest_ = null;
};


/**
 * @param {string} key Key.
 * @return {boolean} Contains key.
 */
ol.structs.LRUCache.prototype.containsKey = function(key) {
  return this.entries_.hasOwnProperty(key);
};


/**
 * @param {function(this: S, T, string, ol.structs.LRUCache): ?} f The function
 *     to call for every entry from the oldest to the newer. This function takes
 *     3 arguments (the entry value, the entry key and the LRUCache object).
 *     The return value is ignored.
 * @param {S=} opt_this The object to use as `this` in `f`.
 * @template S
 */
ol.structs.LRUCache.prototype.forEach = function(f, opt_this) {
  var entry = this.oldest_;
  while (entry) {
    f.call(opt_this, entry.value_, entry.key_, this);
    entry = entry.newer;
  }
};


/**
 * @param {string} key Key.
 * @return {T} Value.
 */
ol.structs.LRUCache.prototype.get = function(key) {
  var entry = this.entries_[key];
  ol.asserts.assert(entry !== undefined,
      15); // Tried to get a value for a key that does not exist in the cache
  if (entry === this.newest_) {
    return entry.value_;
  } else if (entry === this.oldest_) {
    this.oldest_ = /** @type {ol.LRUCacheEntry} */ (this.oldest_.newer);
    this.oldest_.older = null;
  } else {
    entry.newer.older = entry.older;
    entry.older.newer = entry.newer;
  }
  entry.newer = null;
  entry.older = this.newest_;
  this.newest_.newer = entry;
  this.newest_ = entry;
  return entry.value_;
};


/**
 * @return {number} Count.
 */
ol.structs.LRUCache.prototype.getCount = function() {
  return this.count_;
};


/**
 * @return {Array.<string>} Keys.
 */
ol.structs.LRUCache.prototype.getKeys = function() {
  var keys = new Array(this.count_);
  var i = 0;
  var entry;
  for (entry = this.newest_; entry; entry = entry.older) {
    keys[i++] = entry.key_;
  }
  goog.DEBUG && console.assert(i == this.count_, 'iterated correct number of times');
  return keys;
};


/**
 * @return {Array.<T>} Values.
 */
ol.structs.LRUCache.prototype.getValues = function() {
  var values = new Array(this.count_);
  var i = 0;
  var entry;
  for (entry = this.newest_; entry; entry = entry.older) {
    values[i++] = entry.value_;
  }
  goog.DEBUG && console.assert(i == this.count_, 'iterated correct number of times');
  return values;
};


/**
 * @return {T} Last value.
 */
ol.structs.LRUCache.prototype.peekLast = function() {
  goog.DEBUG && console.assert(this.oldest_, 'oldest must not be null');
  return this.oldest_.value_;
};


/**
 * @return {string} Last key.
 */
ol.structs.LRUCache.prototype.peekLastKey = function() {
  goog.DEBUG && console.assert(this.oldest_, 'oldest must not be null');
  return this.oldest_.key_;
};


/**
 * @return {T} value Value.
 */
ol.structs.LRUCache.prototype.pop = function() {
  goog.DEBUG && console.assert(this.oldest_, 'oldest must not be null');
  goog.DEBUG && console.assert(this.newest_, 'newest must not be null');
  var entry = this.oldest_;
  goog.DEBUG && console.assert(entry.key_ in this.entries_,
      'oldest is indexed in entries');
  delete this.entries_[entry.key_];
  if (entry.newer) {
    entry.newer.older = null;
  }
  this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
  if (!this.oldest_) {
    this.newest_ = null;
  }
  --this.count_;
  return entry.value_;
};


/**
 * @param {string} key Key.
 * @param {T} value Value.
 */
ol.structs.LRUCache.prototype.replace = function(key, value) {
  this.get(key);  // update `newest_`
  this.entries_[key].value_ = value;
};


/**
 * @param {string} key Key.
 * @param {T} value Value.
 */
ol.structs.LRUCache.prototype.set = function(key, value) {
  goog.DEBUG && console.assert(!(key in {}),
      'key is not a standard property of objects (e.g. "__proto__")');
  ol.asserts.assert(!(key in this.entries_),
      16); // Tried to set a value for a key that is used already
  var entry = /** @type {ol.LRUCacheEntry} */ ({
    key_: key,
    newer: null,
    older: this.newest_,
    value_: value
  });
  if (!this.newest_) {
    this.oldest_ = entry;
  } else {
    this.newest_.newer = entry;
  }
  this.newest_ = entry;
  this.entries_[key] = entry;
  ++this.count_;
};

// FIXME check against gl.getParameter(webgl.MAX_TEXTURE_SIZE)

goog.provide('ol.renderer.webgl.Map');

goog.require('ol');
goog.require('ol.RendererType');
goog.require('ol.array');
goog.require('ol.css');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.webgl.Immediate');
goog.require('ol.renderer.Map');
goog.require('ol.renderer.webgl.ImageLayer');
goog.require('ol.renderer.webgl.TileLayer');
goog.require('ol.renderer.webgl.VectorLayer');
goog.require('ol.source.State');
goog.require('ol.structs.LRUCache');
goog.require('ol.structs.PriorityQueue');
goog.require('ol.webgl');
goog.require('ol.webgl.Context');
goog.require('ol.webgl.ContextEventType');


/**
 * @constructor
 * @extends {ol.renderer.Map}
 * @param {Element} container Container.
 * @param {ol.Map} map Map.
 */
ol.renderer.webgl.Map = function(container, map) {

  ol.renderer.Map.call(this, container, map);

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = /** @type {HTMLCanvasElement} */
      (document.createElement('CANVAS'));
  this.canvas_.style.width = '100%';
  this.canvas_.style.height = '100%';
  this.canvas_.className = ol.css.CLASS_UNSELECTABLE;
  container.insertBefore(this.canvas_, container.childNodes[0] || null);

  /**
   * @private
   * @type {number}
   */
  this.clipTileCanvasWidth_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.clipTileCanvasHeight_ = 0;

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.clipTileContext_ = ol.dom.createCanvasContext2D();

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = true;

  /**
   * @private
   * @type {WebGLRenderingContext}
   */
  this.gl_ = ol.webgl.getContext(this.canvas_, {
    antialias: true,
    depth: false,
    failIfMajorPerformanceCaveat: true,
    preserveDrawingBuffer: false,
    stencil: true
  });
  goog.DEBUG && console.assert(this.gl_, 'got a WebGLRenderingContext');

  /**
   * @private
   * @type {ol.webgl.Context}
   */
  this.context_ = new ol.webgl.Context(this.canvas_, this.gl_);

  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST,
      this.handleWebGLContextLost, this);
  ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED,
      this.handleWebGLContextRestored, this);

  /**
   * @private
   * @type {ol.structs.LRUCache.<ol.WebglTextureCacheEntry|null>}
   */
  this.textureCache_ = new ol.structs.LRUCache();

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.focus_ = null;

  /**
   * @private
   * @type {ol.structs.PriorityQueue.<Array>}
   */
  this.tileTextureQueue_ = new ol.structs.PriorityQueue(
      /**
       * @param {Array.<*>} element Element.
       * @return {number} Priority.
       * @this {ol.renderer.webgl.Map}
       */
      (function(element) {
        var tileCenter = /** @type {ol.Coordinate} */ (element[1]);
        var tileResolution = /** @type {number} */ (element[2]);
        var deltaX = tileCenter[0] - this.focus_[0];
        var deltaY = tileCenter[1] - this.focus_[1];
        return 65536 * Math.log(tileResolution) +
            Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
      }).bind(this),
      /**
       * @param {Array.<*>} element Element.
       * @return {string} Key.
       */
      function(element) {
        return /** @type {ol.Tile} */ (element[0]).getKey();
      });


 /**
  * @param {ol.Map} map Map.
  * @param {?olx.FrameState} frameState Frame state.
  * @return {boolean} false.
  * @this {ol.renderer.webgl.Map}
  */
  this.loadNextTileTexture_ =
      function(map, frameState) {
        if (!this.tileTextureQueue_.isEmpty()) {
          this.tileTextureQueue_.reprioritize();
          var element = this.tileTextureQueue_.dequeue();
          var tile = /** @type {ol.Tile} */ (element[0]);
          var tileSize = /** @type {ol.Size} */ (element[3]);
          var tileGutter = /** @type {number} */ (element[4]);
          this.bindTileTexture(
              tile, tileSize, tileGutter, ol.webgl.LINEAR, ol.webgl.LINEAR);
        }
        return false;
      }.bind(this);


  /**
   * @private
   * @type {number}
   */
  this.textureCacheFrameMarkerCount_ = 0;

  this.initializeGL_();

};
ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map);


/**
 * @param {ol.Tile} tile Tile.
 * @param {ol.Size} tileSize Tile size.
 * @param {number} tileGutter Tile gutter.
 * @param {number} magFilter Mag filter.
 * @param {number} minFilter Min filter.
 */
ol.renderer.webgl.Map.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) {
  var gl = this.getGL();
  var tileKey = tile.getKey();
  if (this.textureCache_.containsKey(tileKey)) {
    var textureCacheEntry = this.textureCache_.get(tileKey);
    gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture);
    if (textureCacheEntry.magFilter != magFilter) {
      gl.texParameteri(
          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter);
      textureCacheEntry.magFilter = magFilter;
    }
    if (textureCacheEntry.minFilter != minFilter) {
      gl.texParameteri(
          ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter);
      textureCacheEntry.minFilter = minFilter;
    }
  } else {
    var texture = gl.createTexture();
    gl.bindTexture(ol.webgl.TEXTURE_2D, texture);
    if (tileGutter > 0) {
      var clipTileCanvas = this.clipTileContext_.canvas;
      var clipTileContext = this.clipTileContext_;
      if (this.clipTileCanvasWidth_ !== tileSize[0] ||
          this.clipTileCanvasHeight_ !== tileSize[1]) {
        clipTileCanvas.width = tileSize[0];
        clipTileCanvas.height = tileSize[1];
        this.clipTileCanvasWidth_ = tileSize[0];
        this.clipTileCanvasHeight_ = tileSize[1];
      } else {
        clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]);
      }
      clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter,
          tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]);
      gl.texImage2D(ol.webgl.TEXTURE_2D, 0,
          ol.webgl.RGBA, ol.webgl.RGBA,
          ol.webgl.UNSIGNED_BYTE, clipTileCanvas);
    } else {
      gl.texImage2D(ol.webgl.TEXTURE_2D, 0,
          ol.webgl.RGBA, ol.webgl.RGBA,
          ol.webgl.UNSIGNED_BYTE, tile.getImage());
    }
    gl.texParameteri(
        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter);
    gl.texParameteri(
        ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter);
    gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S,
        ol.webgl.CLAMP_TO_EDGE);
    gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T,
        ol.webgl.CLAMP_TO_EDGE);
    this.textureCache_.set(tileKey, {
      texture: texture,
      magFilter: magFilter,
      minFilter: minFilter
    });
  }
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) {
  if (ol.ENABLE_IMAGE && layer instanceof ol.layer.Image) {
    return new ol.renderer.webgl.ImageLayer(this, layer);
  } else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
    return new ol.renderer.webgl.TileLayer(this, layer);
  } else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
    return new ol.renderer.webgl.VectorLayer(this, layer);
  } else {
    goog.DEBUG && console.assert(false, 'unexpected layer configuration');
    return null;
  }
};


/**
 * @param {ol.render.EventType} type Event type.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = function(type, frameState) {
  var map = this.getMap();
  if (map.hasListener(type)) {
    var context = this.context_;

    var extent = frameState.extent;
    var size = frameState.size;
    var viewState = frameState.viewState;
    var pixelRatio = frameState.pixelRatio;

    var resolution = viewState.resolution;
    var center = viewState.center;
    var rotation = viewState.rotation;

    var vectorContext = new ol.render.webgl.Immediate(context,
        center, resolution, rotation, size, extent, pixelRatio);
    var composeEvent = new ol.render.Event(type, vectorContext,
        frameState, null, context);
    map.dispatchEvent(composeEvent);
  }
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.disposeInternal = function() {
  var gl = this.getGL();
  if (!gl.isContextLost()) {
    this.textureCache_.forEach(
        /**
         * @param {?ol.WebglTextureCacheEntry} textureCacheEntry
         *     Texture cache entry.
         */
        function(textureCacheEntry) {
          if (textureCacheEntry) {
            gl.deleteTexture(textureCacheEntry.texture);
          }
        });
  }
  this.context_.dispose();
  ol.renderer.Map.prototype.disposeInternal.call(this);
};


/**
 * @param {ol.Map} map Map.
 * @param {olx.FrameState} frameState Frame state.
 * @private
 */
ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) {
  var gl = this.getGL();
  var textureCacheEntry;
  while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
    textureCacheEntry = this.textureCache_.peekLast();
    if (!textureCacheEntry) {
      if (+this.textureCache_.peekLastKey() == frameState.index) {
        break;
      } else {
        --this.textureCacheFrameMarkerCount_;
      }
    } else {
      gl.deleteTexture(textureCacheEntry.texture);
    }
    this.textureCache_.pop();
  }
};


/**
 * @return {ol.webgl.Context} The context.
 */
ol.renderer.webgl.Map.prototype.getContext = function() {
  return this.context_;
};


/**
 * @return {WebGLRenderingContext} GL.
 */
ol.renderer.webgl.Map.prototype.getGL = function() {
  return this.gl_;
};


/**
 * @return {ol.structs.PriorityQueue.<Array>} Tile texture queue.
 */
ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
  return this.tileTextureQueue_;
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.getType = function() {
  return ol.RendererType.WEBGL;
};


/**
 * @param {ol.events.Event} event Event.
 * @protected
 */
ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
  event.preventDefault();
  this.textureCache_.clear();
  this.textureCacheFrameMarkerCount_ = 0;

  var renderers = this.getLayerRenderers();
  for (var id in renderers) {
    var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]);
    renderer.handleWebGLContextLost();
  }
};


/**
 * @protected
 */
ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() {
  this.initializeGL_();
  this.getMap().render();
};


/**
 * @private
 */
ol.renderer.webgl.Map.prototype.initializeGL_ = function() {
  var gl = this.gl_;
  gl.activeTexture(ol.webgl.TEXTURE0);
  gl.blendFuncSeparate(
      ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA,
      ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA);
  gl.disable(ol.webgl.CULL_FACE);
  gl.disable(ol.webgl.DEPTH_TEST);
  gl.disable(ol.webgl.SCISSOR_TEST);
  gl.disable(ol.webgl.STENCIL_TEST);
};


/**
 * @param {ol.Tile} tile Tile.
 * @return {boolean} Is tile texture loaded.
 */
ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) {
  return this.textureCache_.containsKey(tile.getKey());
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {

  var context = this.getContext();
  var gl = this.getGL();

  if (gl.isContextLost()) {
    return false;
  }

  if (!frameState) {
    if (this.renderedVisible_) {
      this.canvas_.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return false;
  }

  this.focus_ = frameState.focus;

  this.textureCache_.set((-frameState.index).toString(), null);
  ++this.textureCacheFrameMarkerCount_;

  this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState);

  /** @type {Array.<ol.LayerState>} */
  var layerStatesToDraw = [];
  var layerStatesArray = frameState.layerStatesArray;
  ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex);

  var viewResolution = frameState.viewState.resolution;
  var i, ii, layerRenderer, layerState;
  for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
    layerState = layerStatesArray[i];
    if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) &&
        layerState.sourceState == ol.source.State.READY) {
      layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer));
      if (layerRenderer.prepareFrame(frameState, layerState, context)) {
        layerStatesToDraw.push(layerState);
      }
    }
  }

  var width = frameState.size[0] * frameState.pixelRatio;
  var height = frameState.size[1] * frameState.pixelRatio;
  if (this.canvas_.width != width || this.canvas_.height != height) {
    this.canvas_.width = width;
    this.canvas_.height = height;
  }

  gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null);

  gl.clearColor(0, 0, 0, 0);
  gl.clear(ol.webgl.COLOR_BUFFER_BIT);
  gl.enable(ol.webgl.BLEND);
  gl.viewport(0, 0, this.canvas_.width, this.canvas_.height);

  for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) {
    layerState = layerStatesToDraw[i];
    layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer));
    layerRenderer.composeFrame(frameState, layerState, context);
  }

  if (!this.renderedVisible_) {
    this.canvas_.style.display = '';
    this.renderedVisible_ = true;
  }

  this.calculateMatrices2D(frameState);

  if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ >
      ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) {
    frameState.postRenderFunctions.push(
      /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this))
    );
  }

  if (!this.tileTextureQueue_.isEmpty()) {
    frameState.postRenderFunctions.push(this.loadNextTileTexture_);
    frameState.animate = true;
  }

  this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState);

  this.scheduleRemoveUnusedLayerRenderers(frameState);
  this.scheduleExpireIconCache(frameState);

};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg,
        layerFilter, thisArg2) {
  var result;

  if (this.getGL().isContextLost()) {
    return false;
  }

  var viewState = frameState.viewState;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
        layerFilter.call(thisArg2, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      result = layerRenderer.forEachFeatureAtCoordinate(
          coordinate, frameState, callback, thisArg);
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) {
  var hasFeature = false;

  if (this.getGL().isContextLost()) {
    return false;
  }

  var viewState = frameState.viewState;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
        layerFilter.call(thisArg, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      hasFeature =
          layerRenderer.hasFeatureAtCoordinate(coordinate, frameState);
      if (hasFeature) {
        return true;
      }
    }
  }
  return hasFeature;
};


/**
 * @inheritDoc
 */
ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg,
        layerFilter, thisArg2) {
  if (this.getGL().isContextLost()) {
    return false;
  }

  var viewState = frameState.viewState;
  var result;

  var layerStates = frameState.layerStatesArray;
  var numLayers = layerStates.length;
  var i;
  for (i = numLayers - 1; i >= 0; --i) {
    var layerState = layerStates[i];
    var layer = layerState.layer;
    if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) &&
        layerFilter.call(thisArg, layer)) {
      var layerRenderer = this.getLayerRenderer(layer);
      result = layerRenderer.forEachLayerAtPixel(
          pixel, frameState, callback, thisArg);
      if (result) {
        return result;
      }
    }
  }
  return undefined;
};

// FIXME recheck layer/map projection compatibility when projection changes
// FIXME layer renderers should skip when they can't reproject
// FIXME add tilt and height?

goog.provide('ol.Map');
goog.provide('ol.MapProperty');

goog.require('goog.async.nextTick');
goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.MapBrowserEvent');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.MapBrowserEventHandler');
goog.require('ol.MapEvent');
goog.require('ol.MapEventType');
goog.require('ol.Object');
goog.require('ol.ObjectEventType');
goog.require('ol.RendererType');
goog.require('ol.TileQueue');
goog.require('ol.View');
goog.require('ol.array');
goog.require('ol.asserts');
goog.require('ol.control');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.functions');
goog.require('ol.has');
goog.require('ol.interaction');
goog.require('ol.layer.Group');
goog.require('ol.obj');
goog.require('ol.proj.common');
goog.require('ol.renderer.Map');
goog.require('ol.renderer.canvas.Map');
goog.require('ol.renderer.dom.Map');
goog.require('ol.renderer.webgl.Map');
goog.require('ol.size');
goog.require('ol.structs.PriorityQueue');
goog.require('ol.transform');


/**
 * @const
 * @type {string}
 */
ol.OL3_URL = 'http://openlayers.org/';


/**
 * @const
 * @type {string}
 */
ol.OL3_LOGO_URL = 'data:image/png;base64,' +
    'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBI' +
    'WXMAAAHGAAABxgEXwfpGAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAA' +
    'AhNQTFRF////AP//AICAgP//AFVVQECA////K1VVSbbbYL/fJ05idsTYJFtbbcjbJllmZszW' +
    'WMTOIFhoHlNiZszTa9DdUcHNHlNlV8XRIVdiasrUHlZjIVZjaMnVH1RlIFRkH1RkH1ZlasvY' +
    'asvXVsPQH1VkacnVa8vWIVZjIFRjVMPQa8rXIVVkXsXRsNveIFVkIFZlIVVj3eDeh6GmbMvX' +
    'H1ZkIFRka8rWbMvXIFVkIFVjIFVkbMvWH1VjbMvWIFVlbcvWIFVla8vVIFVkbMvWbMvVH1Vk' +
    'bMvWIFVlbcvWIFVkbcvVbMvWjNPbIFVkU8LPwMzNIFVkbczWIFVkbsvWbMvXIFVkRnB8bcvW' +
    '2+TkW8XRIFVkIlZlJVloJlpoKlxrLl9tMmJwOWd0Omh1RXF8TneCT3iDUHiDU8LPVMLPVcLP' +
    'VcPQVsPPVsPQV8PQWMTQWsTQW8TQXMXSXsXRX4SNX8bSYMfTYcfTYsfTY8jUZcfSZsnUaIqT' +
    'acrVasrVa8jTa8rWbI2VbMvWbcvWdJObdcvUdszUd8vVeJaee87Yfc3WgJyjhqGnitDYjaar' +
    'ldPZnrK2oNbborW5o9bbo9fbpLa6q9ndrL3ArtndscDDutzfu8fJwN7gwt7gxc/QyuHhy+Hi' +
    'zeHi0NfX0+Pj19zb1+Tj2uXk29/e3uLg3+Lh3+bl4uXj4ufl4+fl5Ofl5ufl5ujm5+jmySDn' +
    'BAAAAFp0Uk5TAAECAgMEBAYHCA0NDg4UGRogIiMmKSssLzU7PkJJT1JTVFliY2hrdHZ3foSF' +
    'hYeJjY2QkpugqbG1tre5w8zQ09XY3uXn6+zx8vT09vf4+Pj5+fr6/P39/f3+gz7SsAAAAVVJ' +
    'REFUOMtjYKA7EBDnwCPLrObS1BRiLoJLnte6CQy8FLHLCzs2QUG4FjZ5GbcmBDDjxJBXDWxC' +
    'Brb8aM4zbkIDzpLYnAcE9VXlJSWlZRU13koIeW57mGx5XjoMZEUqwxWYQaQbSzLSkYGfKFSe' +
    '0QMsX5WbjgY0YS4MBplemI4BdGBW+DQ11eZiymfqQuXZIjqwyadPNoSZ4L+0FVM6e+oGI6g8' +
    'a9iKNT3o8kVzNkzRg5lgl7p4wyRUL9Yt2jAxVh6mQCogae6GmflI8p0r13VFWTHBQ0rWPW7a' +
    'hgWVcPm+9cuLoyy4kCJDzCm6d8PSFoh0zvQNC5OjDJhQopPPJqph1doJBUD5tnkbZiUEqaCn' +
    'B3bTqLTFG1bPn71kw4b+GFdpLElKIzRxxgYgWNYc5SCENVHKeUaltHdXx0dZ8uBI1hJ2UUDg' +
    'q82CM2MwKeibqAvSO7MCABq0wXEPiqWEAAAAAElFTkSuQmCC';


/**
 * @type {Array.<ol.RendererType>}
 * @const
 */
ol.DEFAULT_RENDERER_TYPES = [
  ol.RendererType.CANVAS,
  ol.RendererType.WEBGL,
  ol.RendererType.DOM
];


/**
 * @enum {string}
 */
ol.MapProperty = {
  LAYERGROUP: 'layergroup',
  SIZE: 'size',
  TARGET: 'target',
  VIEW: 'view'
};


/**
 * @classdesc
 * The map is the core component of OpenLayers. For a map to render, a view,
 * one or more layers, and a target container are needed:
 *
 *     var map = new ol.Map({
 *       view: new ol.View({
 *         center: [0, 0],
 *         zoom: 1
 *       }),
 *       layers: [
 *         new ol.layer.Tile({
 *           source: new ol.source.OSM()
 *         })
 *       ],
 *       target: 'map'
 *     });
 *
 * The above snippet creates a map using a {@link ol.layer.Tile} to display
 * {@link ol.source.OSM} OSM data and render it to a DOM element with the
 * id `map`.
 *
 * The constructor places a viewport container (with CSS class name
 * `ol-viewport`) in the target element (see `getViewport()`), and then two
 * further elements within the viewport: one with CSS class name
 * `ol-overlaycontainer-stopevent` for controls and some overlays, and one with
 * CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent`
 * option of {@link ol.Overlay} for the difference). The map itself is placed in
 * a further element within the viewport, either DOM or Canvas, depending on the
 * renderer.
 *
 * Layers are stored as a `ol.Collection` in layerGroups. A top-level group is
 * provided by the library. This is what is accessed by `getLayerGroup` and
 * `setLayerGroup`. Layers entered in the options are added to this group, and
 * `addLayer` and `removeLayer` change the layer collection in the group.
 * `getLayers` is a convenience function for `getLayerGroup().getLayers()`.
 * Note that `ol.layer.Group` is a subclass of `ol.layer.Base`, so layers
 * entered in the options or added with `addLayer` can be groups, which can
 * contain further groups, and so on.
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.MapOptions} options Map options.
 * @fires ol.MapBrowserEvent
 * @fires ol.MapEvent
 * @fires ol.render.Event#postcompose
 * @fires ol.render.Event#precompose
 * @api stable
 */
ol.Map = function(options) {

  ol.Object.call(this);

  var optionsInternal = ol.Map.createOptionsInternal(options);

  /**
   * @type {boolean}
   * @private
   */
  this.loadTilesWhileAnimating_ =
      options.loadTilesWhileAnimating !== undefined ?
          options.loadTilesWhileAnimating : false;

  /**
   * @type {boolean}
   * @private
   */
  this.loadTilesWhileInteracting_ =
      options.loadTilesWhileInteracting !== undefined ?
          options.loadTilesWhileInteracting : false;

  /**
   * @private
   * @type {number}
   */
  this.pixelRatio_ = options.pixelRatio !== undefined ?
      options.pixelRatio : ol.has.DEVICE_PIXEL_RATIO;

  /**
   * @private
   * @type {Object.<string, string>}
   */
  this.logos_ = optionsInternal.logos;

  /**
   * @private
   * @type {number|undefined}
   */
  this.animationDelayKey_;

  /**
   * @private
   */
  this.animationDelay_ = function() {
    this.animationDelayKey_ = undefined;
    this.renderFrame_.call(this, Date.now());
  }.bind(this);

  /**
   * @private
   * @type {ol.Transform}
   */
  this.coordinateToPixelTransform_ = ol.transform.create();

  /**
   * @private
   * @type {ol.Transform}
   */
  this.pixelToCoordinateTransform_ = ol.transform.create();

  /**
   * @private
   * @type {number}
   */
  this.frameIndex_ = 0;

  /**
   * @private
   * @type {?olx.FrameState}
   */
  this.frameState_ = null;

  /**
   * The extent at the previous 'moveend' event.
   * @private
   * @type {ol.Extent}
   */
  this.previousExtent_ = ol.extent.createEmpty();

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.viewPropertyListenerKey_ = null;

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.layerGroupPropertyListenerKeys_ = null;

  /**
   * @private
   * @type {Element}
   */
  this.viewport_ = document.createElement('DIV');
  this.viewport_.className = 'ol-viewport' + (ol.has.TOUCH ? ' ol-touch' : '');
  this.viewport_.style.position = 'relative';
  this.viewport_.style.overflow = 'hidden';
  this.viewport_.style.width = '100%';
  this.viewport_.style.height = '100%';
  // prevent page zoom on IE >= 10 browsers
  this.viewport_.style.msTouchAction = 'none';
  this.viewport_.style.touchAction = 'none';

  /**
   * @private
   * @type {!Element}
   */
  this.overlayContainer_ = document.createElement('DIV');
  this.overlayContainer_.className = 'ol-overlaycontainer';
  this.viewport_.appendChild(this.overlayContainer_);

  /**
   * @private
   * @type {!Element}
   */
  this.overlayContainerStopEvent_ = document.createElement('DIV');
  this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
  var overlayEvents = [
    ol.events.EventType.CLICK,
    ol.events.EventType.DBLCLICK,
    ol.events.EventType.MOUSEDOWN,
    ol.events.EventType.TOUCHSTART,
    ol.events.EventType.MSPOINTERDOWN,
    ol.MapBrowserEvent.EventType.POINTERDOWN,
    ol.events.EventType.MOUSEWHEEL,
    ol.events.EventType.WHEEL
  ];
  for (var i = 0, ii = overlayEvents.length; i < ii; ++i) {
    ol.events.listen(this.overlayContainerStopEvent_, overlayEvents[i],
        ol.events.Event.stopPropagation);
  }
  this.viewport_.appendChild(this.overlayContainerStopEvent_);

  /**
   * @private
   * @type {ol.MapBrowserEventHandler}
   */
  this.mapBrowserEventHandler_ = new ol.MapBrowserEventHandler(this);
  for (var key in ol.MapBrowserEvent.EventType) {
    ol.events.listen(this.mapBrowserEventHandler_, ol.MapBrowserEvent.EventType[key],
        this.handleMapBrowserEvent, this);
  }

  /**
   * @private
   * @type {Element|Document}
   */
  this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.keyHandlerKeys_ = null;

  ol.events.listen(this.viewport_, ol.events.EventType.WHEEL,
      this.handleBrowserEvent, this);
  ol.events.listen(this.viewport_, ol.events.EventType.MOUSEWHEEL,
      this.handleBrowserEvent, this);

  /**
   * @type {ol.Collection.<ol.control.Control>}
   * @private
   */
  this.controls_ = optionsInternal.controls;

  /**
   * @type {ol.Collection.<ol.interaction.Interaction>}
   * @private
   */
  this.interactions_ = optionsInternal.interactions;

  /**
   * @type {ol.Collection.<ol.Overlay>}
   * @private
   */
  this.overlays_ = optionsInternal.overlays;

  /**
   * A lookup of overlays by id.
   * @private
   * @type {Object.<string, ol.Overlay>}
   */
  this.overlayIdIndex_ = {};

  /**
   * @type {ol.renderer.Map}
   * @private
   */
  this.renderer_ = new optionsInternal.rendererConstructor(this.viewport_, this);

  /**
   * @type {function(Event)|undefined}
   * @private
   */
  this.handleResize_;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.focus_ = null;

  /**
   * @private
   * @type {Array.<ol.PreRenderFunction>}
   */
  this.preRenderFunctions_ = [];

  /**
   * @private
   * @type {Array.<ol.PostRenderFunction>}
   */
  this.postRenderFunctions_ = [];

  /**
   * @private
   * @type {ol.TileQueue}
   */
  this.tileQueue_ = new ol.TileQueue(
      this.getTilePriority.bind(this),
      this.handleTileChange_.bind(this));

  /**
   * Uids of features to skip at rendering time.
   * @type {Object.<string, boolean>}
   * @private
   */
  this.skippedFeatureUids_ = {};

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.MapProperty.LAYERGROUP),
      this.handleLayerGroupChanged_, this);
  ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.VIEW),
      this.handleViewChanged_, this);
  ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.SIZE),
      this.handleSizeChanged_, this);
  ol.events.listen(this, ol.Object.getChangeEventType(ol.MapProperty.TARGET),
      this.handleTargetChanged_, this);

  // setProperties will trigger the rendering of the map if the map
  // is "defined" already.
  this.setProperties(optionsInternal.values);

  this.controls_.forEach(
      /**
       * @param {ol.control.Control} control Control.
       * @this {ol.Map}
       */
      function(control) {
        control.setMap(this);
      }, this);

  ol.events.listen(this.controls_, ol.Collection.EventType.ADD,
      /**
       * @param {ol.Collection.Event} event Collection event.
       */
      function(event) {
        event.element.setMap(this);
      }, this);

  ol.events.listen(this.controls_, ol.Collection.EventType.REMOVE,
      /**
       * @param {ol.Collection.Event} event Collection event.
       */
      function(event) {
        event.element.setMap(null);
      }, this);

  this.interactions_.forEach(
      /**
       * @param {ol.interaction.Interaction} interaction Interaction.
       * @this {ol.Map}
       */
      function(interaction) {
        interaction.setMap(this);
      }, this);

  ol.events.listen(this.interactions_, ol.Collection.EventType.ADD,
      /**
       * @param {ol.Collection.Event} event Collection event.
       */
      function(event) {
        event.element.setMap(this);
      }, this);

  ol.events.listen(this.interactions_, ol.Collection.EventType.REMOVE,
      /**
       * @param {ol.Collection.Event} event Collection event.
       */
      function(event) {
        event.element.setMap(null);
      }, this);

  this.overlays_.forEach(this.addOverlayInternal_, this);

  ol.events.listen(this.overlays_, ol.Collection.EventType.ADD,
      /**
       * @param {ol.Collection.Event} event Collection event.
       */
      function(event) {
        this.addOverlayInternal_(/** @type {ol.Overlay} */ (event.element));
      }, this);

  ol.events.listen(this.overlays_, ol.Collection.EventType.REMOVE,
      /**
       * @param {ol.Collection.Event} event Collection event.
       */
      function(event) {
        var id = event.element.getId();
        if (id !== undefined) {
          delete this.overlayIdIndex_[id.toString()];
        }
        event.element.setMap(null);
      }, this);

};
ol.inherits(ol.Map, ol.Object);


/**
 * Add the given control to the map.
 * @param {ol.control.Control} control Control.
 * @api stable
 */
ol.Map.prototype.addControl = function(control) {
  this.getControls().push(control);
};


/**
 * Add the given interaction to the map.
 * @param {ol.interaction.Interaction} interaction Interaction to add.
 * @api stable
 */
ol.Map.prototype.addInteraction = function(interaction) {
  this.getInteractions().push(interaction);
};


/**
 * Adds the given layer to the top of this map. If you want to add a layer
 * elsewhere in the stack, use `getLayers()` and the methods available on
 * {@link ol.Collection}.
 * @param {ol.layer.Base} layer Layer.
 * @api stable
 */
ol.Map.prototype.addLayer = function(layer) {
  var layers = this.getLayerGroup().getLayers();
  layers.push(layer);
};


/**
 * Add the given overlay to the map.
 * @param {ol.Overlay} overlay Overlay.
 * @api stable
 */
ol.Map.prototype.addOverlay = function(overlay) {
  this.getOverlays().push(overlay);
};


/**
 * This deals with map's overlay collection changes.
 * @param {ol.Overlay} overlay Overlay.
 * @private
 */
ol.Map.prototype.addOverlayInternal_ = function(overlay) {
  var id = overlay.getId();
  if (id !== undefined) {
    this.overlayIdIndex_[id.toString()] = overlay;
  }
  overlay.setMap(this);
};


/**
 * Add functions to be called before rendering. This can be used for attaching
 * animations before updating the map's view.  The {@link ol.animation}
 * namespace provides several static methods for creating prerender functions.
 * @param {...ol.PreRenderFunction} var_args Any number of pre-render functions.
 * @api
 */
ol.Map.prototype.beforeRender = function(var_args) {
  this.render();
  Array.prototype.push.apply(this.preRenderFunctions_, arguments);
};


/**
 * @param {ol.PreRenderFunction} preRenderFunction Pre-render function.
 * @return {boolean} Whether the preRenderFunction has been found and removed.
 */
ol.Map.prototype.removePreRenderFunction = function(preRenderFunction) {
  return ol.array.remove(this.preRenderFunctions_, preRenderFunction);
};


/**
 *
 * @inheritDoc
 */
ol.Map.prototype.disposeInternal = function() {
  this.mapBrowserEventHandler_.dispose();
  this.renderer_.dispose();
  ol.events.unlisten(this.viewport_, ol.events.EventType.WHEEL,
      this.handleBrowserEvent, this);
  ol.events.unlisten(this.viewport_, ol.events.EventType.MOUSEWHEEL,
      this.handleBrowserEvent, this);
  if (this.handleResize_ !== undefined) {
    ol.global.removeEventListener(ol.events.EventType.RESIZE,
        this.handleResize_, false);
    this.handleResize_ = undefined;
  }
  if (this.animationDelayKey_) {
    ol.global.cancelAnimationFrame(this.animationDelayKey_);
    this.animationDelayKey_ = undefined;
  }
  this.setTarget(null);
  ol.Object.prototype.disposeInternal.call(this);
};


/**
 * Detect features that intersect a pixel on the viewport, and execute a
 * callback with each intersecting feature. Layers included in the detection can
 * be configured through `opt_layerFilter`.
 * @param {ol.Pixel} pixel Pixel.
 * @param {function(this: S, (ol.Feature|ol.render.Feature),
 *     ol.layer.Layer): T} callback Feature callback. The callback will be
 *     called with two arguments. The first argument is one
 *     {@link ol.Feature feature} or
 *     {@link ol.render.Feature render feature} at the pixel, the second is
 *     the {@link ol.layer.Layer layer} of the feature and will be null for
 *     unmanaged layers. To stop detection, callback functions can return a
 *     truthy value.
 * @param {S=} opt_this Value to use as `this` when executing `callback`.
 * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
 *     filter function. The filter function will receive one argument, the
 *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
 *     value. Only layers which are visible and for which this function returns
 *     `true` will be tested for features. By default, all visible layers will
 *     be tested.
 * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
 * @return {T|undefined} Callback result, i.e. the return value of last
 * callback execution, or the first truthy callback return value.
 * @template S,T,U
 * @api stable
 */
ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
  if (!this.frameState_) {
    return;
  }
  var coordinate = this.getCoordinateFromPixel(pixel);
  var thisArg = opt_this !== undefined ? opt_this : null;
  var layerFilter = opt_layerFilter !== undefined ?
      opt_layerFilter : ol.functions.TRUE;
  var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
  return this.renderer_.forEachFeatureAtCoordinate(
      coordinate, this.frameState_, callback, thisArg,
      layerFilter, thisArg2);
};


/**
 * Detect layers that have a color value at a pixel on the viewport, and
 * execute a callback with each matching layer. Layers included in the
 * detection can be configured through `opt_layerFilter`.
 * @param {ol.Pixel} pixel Pixel.
 * @param {function(this: S, ol.layer.Layer, ol.Color): T} callback Layer
 *     callback. This callback will recieve two arguments: first is the
 *     {@link ol.layer.Layer layer}, second argument is {@link ol.Color}
 *     and will be null for layer types that do not currently support this
 *     argument. To stop detection callback functions can return a truthy value.
 * @param {S=} opt_this Value to use as `this` when executing `callback`.
 * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
 *     filter function. The filter function will receive one argument, the
 *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
 *     value. Only layers which are visible and for which this function returns
 *     `true` will be tested for features. By default, all visible layers will
 *     be tested.
 * @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
 * @return {T|undefined} Callback result, i.e. the return value of last
 * callback execution, or the first truthy callback return value.
 * @template S,T,U
 * @api stable
 */
ol.Map.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
  if (!this.frameState_) {
    return;
  }
  var thisArg = opt_this !== undefined ? opt_this : null;
  var layerFilter = opt_layerFilter !== undefined ?
      opt_layerFilter : ol.functions.TRUE;
  var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
  return this.renderer_.forEachLayerAtPixel(
      pixel, this.frameState_, callback, thisArg,
      layerFilter, thisArg2);
};


/**
 * Detect if features intersect a pixel on the viewport. Layers included in the
 * detection can be configured through `opt_layerFilter`.
 * @param {ol.Pixel} pixel Pixel.
 * @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
 *     filter function. The filter function will receive one argument, the
 *     {@link ol.layer.Layer layer-candidate} and it should return a boolean
 *     value. Only layers which are visible and for which this function returns
 *     `true` will be tested for features. By default, all visible layers will
 *     be tested.
 * @param {U=} opt_this Value to use as `this` when executing `layerFilter`.
 * @return {boolean} Is there a feature at the given pixel?
 * @template U
 * @api
 */
ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_layerFilter, opt_this) {
  if (!this.frameState_) {
    return false;
  }
  var coordinate = this.getCoordinateFromPixel(pixel);
  var layerFilter = opt_layerFilter !== undefined ?
      opt_layerFilter : ol.functions.TRUE;
  var thisArg = opt_this !== undefined ? opt_this : null;
  return this.renderer_.hasFeatureAtCoordinate(
      coordinate, this.frameState_, layerFilter, thisArg);
};


/**
 * Returns the geographical coordinate for a browser event.
 * @param {Event} event Event.
 * @return {ol.Coordinate} Coordinate.
 * @api stable
 */
ol.Map.prototype.getEventCoordinate = function(event) {
  return this.getCoordinateFromPixel(this.getEventPixel(event));
};


/**
 * Returns the map pixel position for a browser event relative to the viewport.
 * @param {Event} event Event.
 * @return {ol.Pixel} Pixel.
 * @api stable
 */
ol.Map.prototype.getEventPixel = function(event) {
  var viewportPosition = this.viewport_.getBoundingClientRect();
  var eventPosition = event.changedTouches ? event.changedTouches[0] : event;
  return [
    eventPosition.clientX - viewportPosition.left,
    eventPosition.clientY - viewportPosition.top
  ];
};


/**
 * Get the target in which this map is rendered.
 * Note that this returns what is entered as an option or in setTarget:
 * if that was an element, it returns an element; if a string, it returns that.
 * @return {Element|string|undefined} The Element or id of the Element that the
 *     map is rendered in.
 * @observable
 * @api stable
 */
ol.Map.prototype.getTarget = function() {
  return /** @type {Element|string|undefined} */ (
      this.get(ol.MapProperty.TARGET));
};


/**
 * Get the DOM element into which this map is rendered. In contrast to
 * `getTarget` this method always return an `Element`, or `null` if the
 * map has no target.
 * @return {Element} The element that the map is rendered in.
 * @api
 */
ol.Map.prototype.getTargetElement = function() {
  var target = this.getTarget();
  if (target !== undefined) {
    return typeof target === 'string' ?
      document.getElementById(target) :
      target;
  } else {
    return null;
  }
};


/**
 * Get the coordinate for a given pixel.  This returns a coordinate in the
 * map view projection.
 * @param {ol.Pixel} pixel Pixel position in the map viewport.
 * @return {ol.Coordinate} The coordinate for the pixel position.
 * @api stable
 */
ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
  var frameState = this.frameState_;
  if (!frameState) {
    return null;
  } else {
    return ol.transform.apply(frameState.pixelToCoordinateTransform, pixel.slice());
  }
};


/**
 * Get the map controls. Modifying this collection changes the controls
 * associated with the map.
 * @return {ol.Collection.<ol.control.Control>} Controls.
 * @api stable
 */
ol.Map.prototype.getControls = function() {
  return this.controls_;
};


/**
 * Get the map overlays. Modifying this collection changes the overlays
 * associated with the map.
 * @return {ol.Collection.<ol.Overlay>} Overlays.
 * @api stable
 */
ol.Map.prototype.getOverlays = function() {
  return this.overlays_;
};


/**
 * Get an overlay by its identifier (the value returned by overlay.getId()).
 * Note that the index treats string and numeric identifiers as the same. So
 * `map.getOverlayById(2)` will return an overlay with id `'2'` or `2`.
 * @param {string|number} id Overlay identifier.
 * @return {ol.Overlay} Overlay.
 * @api
 */
ol.Map.prototype.getOverlayById = function(id) {
  var overlay = this.overlayIdIndex_[id.toString()];
  return overlay !== undefined ? overlay : null;
};


/**
 * Get the map interactions. Modifying this collection changes the interactions
 * associated with the map.
 *
 * Interactions are used for e.g. pan, zoom and rotate.
 * @return {ol.Collection.<ol.interaction.Interaction>} Interactions.
 * @api stable
 */
ol.Map.prototype.getInteractions = function() {
  return this.interactions_;
};


/**
 * Get the layergroup associated with this map.
 * @return {ol.layer.Group} A layer group containing the layers in this map.
 * @observable
 * @api stable
 */
ol.Map.prototype.getLayerGroup = function() {
  return /** @type {ol.layer.Group} */ (this.get(ol.MapProperty.LAYERGROUP));
};


/**
 * Get the collection of layers associated with this map.
 * @return {!ol.Collection.<ol.layer.Base>} Layers.
 * @api stable
 */
ol.Map.prototype.getLayers = function() {
  var layers = this.getLayerGroup().getLayers();
  return layers;
};


/**
 * Get the pixel for a coordinate.  This takes a coordinate in the map view
 * projection and returns the corresponding pixel.
 * @param {ol.Coordinate} coordinate A map coordinate.
 * @return {ol.Pixel} A pixel position in the map viewport.
 * @api stable
 */
ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
  var frameState = this.frameState_;
  if (!frameState) {
    return null;
  } else {
    return ol.transform.apply(frameState.coordinateToPixelTransform,
        coordinate.slice(0, 2));
  }
};


/**
 * Get the map renderer.
 * @return {ol.renderer.Map} Renderer
 */
ol.Map.prototype.getRenderer = function() {
  return this.renderer_;
};


/**
 * Get the size of this map.
 * @return {ol.Size|undefined} The size in pixels of the map in the DOM.
 * @observable
 * @api stable
 */
ol.Map.prototype.getSize = function() {
  return /** @type {ol.Size|undefined} */ (this.get(ol.MapProperty.SIZE));
};


/**
 * Get the view associated with this map. A view manages properties such as
 * center and resolution.
 * @return {ol.View} The view that controls this map.
 * @observable
 * @api stable
 */
ol.Map.prototype.getView = function() {
  return /** @type {ol.View} */ (this.get(ol.MapProperty.VIEW));
};


/**
 * Get the element that serves as the map viewport.
 * @return {Element} Viewport.
 * @api stable
 */
ol.Map.prototype.getViewport = function() {
  return this.viewport_;
};


/**
 * Get the element that serves as the container for overlays.  Elements added to
 * this container will let mousedown and touchstart events through to the map,
 * so clicks and gestures on an overlay will trigger {@link ol.MapBrowserEvent}
 * events.
 * @return {!Element} The map's overlay container.
 */
ol.Map.prototype.getOverlayContainer = function() {
  return this.overlayContainer_;
};


/**
 * Get the element that serves as a container for overlays that don't allow
 * event propagation. Elements added to this container won't let mousedown and
 * touchstart events through to the map, so clicks and gestures on an overlay
 * don't trigger any {@link ol.MapBrowserEvent}.
 * @return {!Element} The map's overlay container that stops events.
 */
ol.Map.prototype.getOverlayContainerStopEvent = function() {
  return this.overlayContainerStopEvent_;
};


/**
 * @param {ol.Tile} tile Tile.
 * @param {string} tileSourceKey Tile source key.
 * @param {ol.Coordinate} tileCenter Tile center.
 * @param {number} tileResolution Tile resolution.
 * @return {number} Tile priority.
 */
ol.Map.prototype.getTilePriority = function(tile, tileSourceKey, tileCenter, tileResolution) {
  // Filter out tiles at higher zoom levels than the current zoom level, or that
  // are outside the visible extent.
  var frameState = this.frameState_;
  if (!frameState || !(tileSourceKey in frameState.wantedTiles)) {
    return ol.structs.PriorityQueue.DROP;
  }
  if (!frameState.wantedTiles[tileSourceKey][tile.getKey()]) {
    return ol.structs.PriorityQueue.DROP;
  }
  // Prioritize the highest zoom level tiles closest to the focus.
  // Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
  // Within a zoom level, tiles are prioritized by the distance in pixels
  // between the center of the tile and the focus.  The factor of 65536 means
  // that the prioritization should behave as desired for tiles up to
  // 65536 * Math.log(2) = 45426 pixels from the focus.
  var deltaX = tileCenter[0] - frameState.focus[0];
  var deltaY = tileCenter[1] - frameState.focus[1];
  return 65536 * Math.log(tileResolution) +
      Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
};


/**
 * @param {Event} browserEvent Browser event.
 * @param {string=} opt_type Type.
 */
ol.Map.prototype.handleBrowserEvent = function(browserEvent, opt_type) {
  var type = opt_type || browserEvent.type;
  var mapBrowserEvent = new ol.MapBrowserEvent(type, this, browserEvent);
  this.handleMapBrowserEvent(mapBrowserEvent);
};


/**
 * @param {ol.MapBrowserEvent} mapBrowserEvent The event to handle.
 */
ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) {
  if (!this.frameState_) {
    // With no view defined, we cannot translate pixels into geographical
    // coordinates so interactions cannot be used.
    return;
  }
  this.focus_ = mapBrowserEvent.coordinate;
  mapBrowserEvent.frameState = this.frameState_;
  var interactionsArray = this.getInteractions().getArray();
  var i;
  if (this.dispatchEvent(mapBrowserEvent) !== false) {
    for (i = interactionsArray.length - 1; i >= 0; i--) {
      var interaction = interactionsArray[i];
      if (!interaction.getActive()) {
        continue;
      }
      var cont = interaction.handleEvent(mapBrowserEvent);
      if (!cont) {
        break;
      }
    }
  }
};


/**
 * @protected
 */
ol.Map.prototype.handlePostRender = function() {

  var frameState = this.frameState_;

  // Manage the tile queue
  // Image loads are expensive and a limited resource, so try to use them
  // efficiently:
  // * When the view is static we allow a large number of parallel tile loads
  //   to complete the frame as quickly as possible.
  // * When animating or interacting, image loads can cause janks, so we reduce
  //   the maximum number of loads per frame and limit the number of parallel
  //   tile loads to remain reactive to view changes and to reduce the chance of
  //   loading tiles that will quickly disappear from view.
  var tileQueue = this.tileQueue_;
  if (!tileQueue.isEmpty()) {
    var maxTotalLoading = 16;
    var maxNewLoads = maxTotalLoading;
    if (frameState) {
      var hints = frameState.viewHints;
      if (hints[ol.View.Hint.ANIMATING]) {
        maxTotalLoading = this.loadTilesWhileAnimating_ ? 8 : 0;
        maxNewLoads = 2;
      }
      if (hints[ol.View.Hint.INTERACTING]) {
        maxTotalLoading = this.loadTilesWhileInteracting_ ? 8 : 0;
        maxNewLoads = 2;
      }
    }
    if (tileQueue.getTilesLoading() < maxTotalLoading) {
      tileQueue.reprioritize(); // FIXME only call if view has changed
      tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
    }
  }

  var postRenderFunctions = this.postRenderFunctions_;
  var i, ii;
  for (i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
    postRenderFunctions[i](this, frameState);
  }
  postRenderFunctions.length = 0;
};


/**
 * @private
 */
ol.Map.prototype.handleSizeChanged_ = function() {
  this.render();
};


/**
 * @private
 */
ol.Map.prototype.handleTargetChanged_ = function() {
  // target may be undefined, null, a string or an Element.
  // If it's a string we convert it to an Element before proceeding.
  // If it's not now an Element we remove the viewport from the DOM.
  // If it's an Element we append the viewport element to it.

  var targetElement;
  if (this.getTarget()) {
    targetElement = this.getTargetElement();
    goog.DEBUG && console.assert(targetElement !== null,
        'expects a non-null value for targetElement');
  }

  if (this.keyHandlerKeys_) {
    for (var i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
      ol.events.unlistenByKey(this.keyHandlerKeys_[i]);
    }
    this.keyHandlerKeys_ = null;
  }

  if (!targetElement) {
    ol.dom.removeNode(this.viewport_);
    if (this.handleResize_ !== undefined) {
      ol.global.removeEventListener(ol.events.EventType.RESIZE,
          this.handleResize_, false);
      this.handleResize_ = undefined;
    }
  } else {
    targetElement.appendChild(this.viewport_);

    var keyboardEventTarget = !this.keyboardEventTarget_ ?
        targetElement : this.keyboardEventTarget_;
    this.keyHandlerKeys_ = [
      ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYDOWN,
          this.handleBrowserEvent, this),
      ol.events.listen(keyboardEventTarget, ol.events.EventType.KEYPRESS,
          this.handleBrowserEvent, this)
    ];

    if (!this.handleResize_) {
      this.handleResize_ = this.updateSize.bind(this);
      ol.global.addEventListener(ol.events.EventType.RESIZE,
          this.handleResize_, false);
    }
  }

  this.updateSize();
  // updateSize calls setSize, so no need to call this.render
  // ourselves here.
};


/**
 * @private
 */
ol.Map.prototype.handleTileChange_ = function() {
  this.render();
};


/**
 * @private
 */
ol.Map.prototype.handleViewPropertyChanged_ = function() {
  this.render();
};


/**
 * @private
 */
ol.Map.prototype.handleViewChanged_ = function() {
  if (this.viewPropertyListenerKey_) {
    ol.events.unlistenByKey(this.viewPropertyListenerKey_);
    this.viewPropertyListenerKey_ = null;
  }
  var view = this.getView();
  if (view) {
    this.viewPropertyListenerKey_ = ol.events.listen(
        view, ol.ObjectEventType.PROPERTYCHANGE,
        this.handleViewPropertyChanged_, this);
  }
  this.render();
};


/**
 * @private
 */
ol.Map.prototype.handleLayerGroupChanged_ = function() {
  if (this.layerGroupPropertyListenerKeys_) {
    this.layerGroupPropertyListenerKeys_.forEach(ol.events.unlistenByKey);
    this.layerGroupPropertyListenerKeys_ = null;
  }
  var layerGroup = this.getLayerGroup();
  if (layerGroup) {
    this.layerGroupPropertyListenerKeys_ = [
      ol.events.listen(
          layerGroup, ol.ObjectEventType.PROPERTYCHANGE,
          this.render, this),
      ol.events.listen(
          layerGroup, ol.events.EventType.CHANGE,
          this.render, this)
    ];
  }
  this.render();
};


/**
 * @return {boolean} Is rendered.
 */
ol.Map.prototype.isRendered = function() {
  return !!this.frameState_;
};


/**
 * Requests an immediate render in a synchronous manner.
 * @api stable
 */
ol.Map.prototype.renderSync = function() {
  if (this.animationDelayKey_) {
    ol.global.cancelAnimationFrame(this.animationDelayKey_);
  }
  this.animationDelay_();
};


/**
 * Request a map rendering (at the next animation frame).
 * @api stable
 */
ol.Map.prototype.render = function() {
  if (this.animationDelayKey_ === undefined) {
    this.animationDelayKey_ = ol.global.requestAnimationFrame(
        this.animationDelay_);
  }
};


/**
 * Remove the given control from the map.
 * @param {ol.control.Control} control Control.
 * @return {ol.control.Control|undefined} The removed control (or undefined
 *     if the control was not found).
 * @api stable
 */
ol.Map.prototype.removeControl = function(control) {
  return this.getControls().remove(control);
};


/**
 * Remove the given interaction from the map.
 * @param {ol.interaction.Interaction} interaction Interaction to remove.
 * @return {ol.interaction.Interaction|undefined} The removed interaction (or
 *     undefined if the interaction was not found).
 * @api stable
 */
ol.Map.prototype.removeInteraction = function(interaction) {
  return this.getInteractions().remove(interaction);
};


/**
 * Removes the given layer from the map.
 * @param {ol.layer.Base} layer Layer.
 * @return {ol.layer.Base|undefined} The removed layer (or undefined if the
 *     layer was not found).
 * @api stable
 */
ol.Map.prototype.removeLayer = function(layer) {
  var layers = this.getLayerGroup().getLayers();
  return layers.remove(layer);
};


/**
 * Remove the given overlay from the map.
 * @param {ol.Overlay} overlay Overlay.
 * @return {ol.Overlay|undefined} The removed overlay (or undefined
 *     if the overlay was not found).
 * @api stable
 */
ol.Map.prototype.removeOverlay = function(overlay) {
  return this.getOverlays().remove(overlay);
};


/**
 * @param {number} time Time.
 * @private
 */
ol.Map.prototype.renderFrame_ = function(time) {

  var i, ii, viewState;

  var size = this.getSize();
  var view = this.getView();
  var extent = ol.extent.createEmpty();
  /** @type {?olx.FrameState} */
  var frameState = null;
  if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) {
    var viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
    var layerStatesArray = this.getLayerGroup().getLayerStatesArray();
    var layerStates = {};
    for (i = 0, ii = layerStatesArray.length; i < ii; ++i) {
      layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
    }
    viewState = view.getState();
    frameState = /** @type {olx.FrameState} */ ({
      animate: false,
      attributions: {},
      coordinateToPixelTransform: this.coordinateToPixelTransform_,
      extent: extent,
      focus: !this.focus_ ? viewState.center : this.focus_,
      index: this.frameIndex_++,
      layerStates: layerStates,
      layerStatesArray: layerStatesArray,
      logos: ol.obj.assign({}, this.logos_),
      pixelRatio: this.pixelRatio_,
      pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
      postRenderFunctions: [],
      size: size,
      skippedFeatureUids: this.skippedFeatureUids_,
      tileQueue: this.tileQueue_,
      time: time,
      usedTiles: {},
      viewState: viewState,
      viewHints: viewHints,
      wantedTiles: {}
    });
  }

  if (frameState) {
    var preRenderFunctions = this.preRenderFunctions_;
    var n = 0, preRenderFunction;
    for (i = 0, ii = preRenderFunctions.length; i < ii; ++i) {
      preRenderFunction = preRenderFunctions[i];
      if (preRenderFunction(this, frameState)) {
        preRenderFunctions[n++] = preRenderFunction;
      }
    }
    preRenderFunctions.length = n;

    frameState.extent = ol.extent.getForViewAndSize(viewState.center,
        viewState.resolution, viewState.rotation, frameState.size, extent);
  }

  this.frameState_ = frameState;
  this.renderer_.renderFrame(frameState);

  if (frameState) {
    if (frameState.animate) {
      this.render();
    }
    Array.prototype.push.apply(
        this.postRenderFunctions_, frameState.postRenderFunctions);

    var idle = this.preRenderFunctions_.length === 0 &&
        !frameState.viewHints[ol.View.Hint.ANIMATING] &&
        !frameState.viewHints[ol.View.Hint.INTERACTING] &&
        !ol.extent.equals(frameState.extent, this.previousExtent_);

    if (idle) {
      this.dispatchEvent(
          new ol.MapEvent(ol.MapEventType.MOVEEND, this, frameState));
      ol.extent.clone(frameState.extent, this.previousExtent_);
    }
  }

  this.dispatchEvent(
      new ol.MapEvent(ol.MapEventType.POSTRENDER, this, frameState));

  goog.async.nextTick(this.handlePostRender, this);

};


/**
 * Sets the layergroup of this map.
 * @param {ol.layer.Group} layerGroup A layer group containing the layers in
 *     this map.
 * @observable
 * @api stable
 */
ol.Map.prototype.setLayerGroup = function(layerGroup) {
  this.set(ol.MapProperty.LAYERGROUP, layerGroup);
};


/**
 * Set the size of this map.
 * @param {ol.Size|undefined} size The size in pixels of the map in the DOM.
 * @observable
 * @api
 */
ol.Map.prototype.setSize = function(size) {
  this.set(ol.MapProperty.SIZE, size);
};


/**
 * Set the target element to render this map into.
 * @param {Element|string|undefined} target The Element or id of the Element
 *     that the map is rendered in.
 * @observable
 * @api stable
 */
ol.Map.prototype.setTarget = function(target) {
  this.set(ol.MapProperty.TARGET, target);
};


/**
 * Set the view for this map.
 * @param {ol.View} view The view that controls this map.
 * @observable
 * @api stable
 */
ol.Map.prototype.setView = function(view) {
  this.set(ol.MapProperty.VIEW, view);
};


/**
 * @param {ol.Feature} feature Feature.
 */
ol.Map.prototype.skipFeature = function(feature) {
  var featureUid = ol.getUid(feature).toString();
  this.skippedFeatureUids_[featureUid] = true;
  this.render();
};


/**
 * Force a recalculation of the map viewport size.  This should be called when
 * third-party code changes the size of the map viewport.
 * @api stable
 */
ol.Map.prototype.updateSize = function() {
  var targetElement = this.getTargetElement();

  if (!targetElement) {
    this.setSize(undefined);
  } else {
    var computedStyle = ol.global.getComputedStyle(targetElement);
    this.setSize([
      targetElement.offsetWidth -
          parseFloat(computedStyle['borderLeftWidth']) -
          parseFloat(computedStyle['paddingLeft']) -
          parseFloat(computedStyle['paddingRight']) -
          parseFloat(computedStyle['borderRightWidth']),
      targetElement.offsetHeight -
          parseFloat(computedStyle['borderTopWidth']) -
          parseFloat(computedStyle['paddingTop']) -
          parseFloat(computedStyle['paddingBottom']) -
          parseFloat(computedStyle['borderBottomWidth'])
    ]);
  }
};


/**
 * @param {ol.Feature} feature Feature.
 */
ol.Map.prototype.unskipFeature = function(feature) {
  var featureUid = ol.getUid(feature).toString();
  delete this.skippedFeatureUids_[featureUid];
  this.render();
};


/**
 * @param {olx.MapOptions} options Map options.
 * @return {ol.MapOptionsInternal} Internal map options.
 */
ol.Map.createOptionsInternal = function(options) {

  /**
   * @type {Element|Document}
   */
  var keyboardEventTarget = null;
  if (options.keyboardEventTarget !== undefined) {
    keyboardEventTarget = typeof options.keyboardEventTarget === 'string' ?
        document.getElementById(options.keyboardEventTarget) :
        options.keyboardEventTarget;
  }

  /**
   * @type {Object.<string, *>}
   */
  var values = {};

  var logos = {};
  if (options.logo === undefined ||
      (typeof options.logo === 'boolean' && options.logo)) {
    logos[ol.OL3_LOGO_URL] = ol.OL3_URL;
  } else {
    var logo = options.logo;
    if (typeof logo === 'string') {
      logos[logo] = '';
    } else if (logo instanceof HTMLElement) {
      logos[ol.getUid(logo).toString()] = logo;
    } else if (logo) {
      ol.asserts.assert(typeof logo.href == 'string', 44); // `logo.href` should be a string.
      ol.asserts.assert(typeof logo.src == 'string', 45); // `logo.src` should be a string.
      logos[logo.src] = logo.href;
    }
  }

  var layerGroup = (options.layers instanceof ol.layer.Group) ?
      options.layers : new ol.layer.Group({layers: options.layers});
  values[ol.MapProperty.LAYERGROUP] = layerGroup;

  values[ol.MapProperty.TARGET] = options.target;

  values[ol.MapProperty.VIEW] = options.view !== undefined ?
      options.view : new ol.View();

  /**
   * @type {function(new: ol.renderer.Map, Element, ol.Map)}
   */
  var rendererConstructor = ol.renderer.Map;

  /**
   * @type {Array.<ol.RendererType>}
   */
  var rendererTypes;
  if (options.renderer !== undefined) {
    if (Array.isArray(options.renderer)) {
      rendererTypes = options.renderer;
    } else if (typeof options.renderer === 'string') {
      rendererTypes = [options.renderer];
    } else {
      ol.asserts.assert(false, 46); // Incorrect format for `renderer` option
    }
  } else {
    rendererTypes = ol.DEFAULT_RENDERER_TYPES;
  }

  var i, ii;
  for (i = 0, ii = rendererTypes.length; i < ii; ++i) {
    /** @type {ol.RendererType} */
    var rendererType = rendererTypes[i];
    if (ol.ENABLE_CANVAS && rendererType == ol.RendererType.CANVAS) {
      if (ol.has.CANVAS) {
        rendererConstructor = ol.renderer.canvas.Map;
        break;
      }
    } else if (ol.ENABLE_DOM && rendererType == ol.RendererType.DOM) {
      if (ol.has.DOM) {
        rendererConstructor = ol.renderer.dom.Map;
        break;
      }
    } else if (ol.ENABLE_WEBGL && rendererType == ol.RendererType.WEBGL) {
      if (ol.has.WEBGL) {
        rendererConstructor = ol.renderer.webgl.Map;
        break;
      }
    }
  }

  var controls;
  if (options.controls !== undefined) {
    if (Array.isArray(options.controls)) {
      controls = new ol.Collection(options.controls.slice());
    } else {
      ol.asserts.assert(options.controls instanceof ol.Collection,
          47); // Expected `controls` to be an array or an `ol.Collection`
      controls = options.controls;
    }
  } else {
    controls = ol.control.defaults();
  }

  var interactions;
  if (options.interactions !== undefined) {
    if (Array.isArray(options.interactions)) {
      interactions = new ol.Collection(options.interactions.slice());
    } else {
      ol.asserts.assert(options.interactions instanceof ol.Collection,
          48); // Expected `interactions` to be an array or an `ol.Collection`
      interactions = options.interactions;
    }
  } else {
    interactions = ol.interaction.defaults();
  }

  var overlays;
  if (options.overlays !== undefined) {
    if (Array.isArray(options.overlays)) {
      overlays = new ol.Collection(options.overlays.slice());
    } else {
      ol.asserts.assert(options.overlays instanceof ol.Collection,
          49); // Expected `overlays` to be an array or an `ol.Collection`
      overlays = options.overlays;
    }
  } else {
    overlays = new ol.Collection();
  }

  return {
    controls: controls,
    interactions: interactions,
    keyboardEventTarget: keyboardEventTarget,
    logos: logos,
    overlays: overlays,
    rendererConstructor: rendererConstructor,
    values: values
  };

};


ol.proj.common.add();

goog.provide('ol.Overlay');
goog.provide('ol.OverlayPositioning');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.MapEventType');
goog.require('ol.Object');
goog.require('ol.animation');
goog.require('ol.dom');
goog.require('ol.extent');


/**
 * @enum {string}
 */
ol.OverlayProperty = {
  ELEMENT: 'element',
  MAP: 'map',
  OFFSET: 'offset',
  POSITION: 'position',
  POSITIONING: 'positioning'
};


/**
 * Overlay position: `'bottom-left'`, `'bottom-center'`,  `'bottom-right'`,
 * `'center-left'`, `'center-center'`, `'center-right'`, `'top-left'`,
 * `'top-center'`, `'top-right'`
 * @enum {string}
 */
ol.OverlayPositioning = {
  BOTTOM_LEFT: 'bottom-left',
  BOTTOM_CENTER: 'bottom-center',
  BOTTOM_RIGHT: 'bottom-right',
  CENTER_LEFT: 'center-left',
  CENTER_CENTER: 'center-center',
  CENTER_RIGHT: 'center-right',
  TOP_LEFT: 'top-left',
  TOP_CENTER: 'top-center',
  TOP_RIGHT: 'top-right'
};


/**
 * @classdesc
 * An element to be displayed over the map and attached to a single map
 * location.  Like {@link ol.control.Control}, Overlays are visible widgets.
 * Unlike Controls, they are not in a fixed position on the screen, but are tied
 * to a geographical coordinate, so panning the map will move an Overlay but not
 * a Control.
 *
 * Example:
 *
 *     var popup = new ol.Overlay({
 *       element: document.getElementById('popup')
 *     });
 *     popup.setPosition(coordinate);
 *     map.addOverlay(popup);
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.OverlayOptions} options Overlay options.
 * @api stable
 */
ol.Overlay = function(options) {

  ol.Object.call(this);

  /**
   * @private
   * @type {number|string|undefined}
   */
  this.id_ = options.id;

  /**
   * @private
   * @type {boolean}
   */
  this.insertFirst_ = options.insertFirst !== undefined ?
      options.insertFirst : true;

  /**
   * @private
   * @type {boolean}
   */
  this.stopEvent_ = options.stopEvent !== undefined ? options.stopEvent : true;

  /**
   * @private
   * @type {Element}
   */
  this.element_ = document.createElement('DIV');
  this.element_.className = 'ol-overlay-container';
  this.element_.style.position = 'absolute';

  /**
   * @protected
   * @type {boolean}
   */
  this.autoPan = options.autoPan !== undefined ? options.autoPan : false;

  /**
   * @private
   * @type {olx.animation.PanOptions}
   */
  this.autoPanAnimation_ = options.autoPanAnimation !== undefined ?
      options.autoPanAnimation : /** @type {olx.animation.PanOptions} */ ({});

  /**
   * @private
   * @type {number}
   */
  this.autoPanMargin_ = options.autoPanMargin !== undefined ?
      options.autoPanMargin : 20;

  /**
   * @private
   * @type {{bottom_: string,
   *         left_: string,
   *         right_: string,
   *         top_: string,
   *         visible: boolean}}
   */
  this.rendered_ = {
    bottom_: '',
    left_: '',
    right_: '',
    top_: '',
    visible: true
  };

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.mapPostrenderListenerKey_ = null;

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.OverlayProperty.ELEMENT),
      this.handleElementChanged, this);

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.OverlayProperty.MAP),
      this.handleMapChanged, this);

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.OverlayProperty.OFFSET),
      this.handleOffsetChanged, this);

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITION),
      this.handlePositionChanged, this);

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.OverlayProperty.POSITIONING),
      this.handlePositioningChanged, this);

  if (options.element !== undefined) {
    this.setElement(options.element);
  }

  this.setOffset(options.offset !== undefined ? options.offset : [0, 0]);

  this.setPositioning(options.positioning !== undefined ?
      /** @type {ol.OverlayPositioning} */ (options.positioning) :
      ol.OverlayPositioning.TOP_LEFT);

  if (options.position !== undefined) {
    this.setPosition(options.position);
  }

};
ol.inherits(ol.Overlay, ol.Object);


/**
 * Get the DOM element of this overlay.
 * @return {Element|undefined} The Element containing the overlay.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.getElement = function() {
  return /** @type {Element|undefined} */ (
      this.get(ol.OverlayProperty.ELEMENT));
};


/**
 * Get the overlay identifier which is set on constructor.
 * @return {number|string|undefined} Id.
 * @api
 */
ol.Overlay.prototype.getId = function() {
  return this.id_;
};


/**
 * Get the map associated with this overlay.
 * @return {ol.Map|undefined} The map that the overlay is part of.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.getMap = function() {
  return /** @type {ol.Map|undefined} */ (
      this.get(ol.OverlayProperty.MAP));
};


/**
 * Get the offset of this overlay.
 * @return {Array.<number>} The offset.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.getOffset = function() {
  return /** @type {Array.<number>} */ (
      this.get(ol.OverlayProperty.OFFSET));
};


/**
 * Get the current position of this overlay.
 * @return {ol.Coordinate|undefined} The spatial point that the overlay is
 *     anchored at.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.getPosition = function() {
  return /** @type {ol.Coordinate|undefined} */ (
      this.get(ol.OverlayProperty.POSITION));
};


/**
 * Get the current positioning of this overlay.
 * @return {ol.OverlayPositioning} How the overlay is positioned
 *     relative to its point on the map.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.getPositioning = function() {
  return /** @type {ol.OverlayPositioning} */ (
      this.get(ol.OverlayProperty.POSITIONING));
};


/**
 * @protected
 */
ol.Overlay.prototype.handleElementChanged = function() {
  ol.dom.removeChildren(this.element_);
  var element = this.getElement();
  if (element) {
    this.element_.appendChild(element);
  }
};


/**
 * @protected
 */
ol.Overlay.prototype.handleMapChanged = function() {
  if (this.mapPostrenderListenerKey_) {
    ol.dom.removeNode(this.element_);
    ol.events.unlistenByKey(this.mapPostrenderListenerKey_);
    this.mapPostrenderListenerKey_ = null;
  }
  var map = this.getMap();
  if (map) {
    this.mapPostrenderListenerKey_ = ol.events.listen(map,
        ol.MapEventType.POSTRENDER, this.render, this);
    this.updatePixelPosition();
    var container = this.stopEvent_ ?
        map.getOverlayContainerStopEvent() : map.getOverlayContainer();
    if (this.insertFirst_) {
      container.insertBefore(this.element_, container.childNodes[0] || null);
    } else {
      container.appendChild(this.element_);
    }
  }
};


/**
 * @protected
 */
ol.Overlay.prototype.render = function() {
  this.updatePixelPosition();
};


/**
 * @protected
 */
ol.Overlay.prototype.handleOffsetChanged = function() {
  this.updatePixelPosition();
};


/**
 * @protected
 */
ol.Overlay.prototype.handlePositionChanged = function() {
  this.updatePixelPosition();
  if (this.get(ol.OverlayProperty.POSITION) !== undefined && this.autoPan) {
    this.panIntoView_();
  }
};


/**
 * @protected
 */
ol.Overlay.prototype.handlePositioningChanged = function() {
  this.updatePixelPosition();
};


/**
 * Set the DOM element to be associated with this overlay.
 * @param {Element|undefined} element The Element containing the overlay.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.setElement = function(element) {
  this.set(ol.OverlayProperty.ELEMENT, element);
};


/**
 * Set the map to be associated with this overlay.
 * @param {ol.Map|undefined} map The map that the overlay is part of.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.setMap = function(map) {
  this.set(ol.OverlayProperty.MAP, map);
};


/**
 * Set the offset for this overlay.
 * @param {Array.<number>} offset Offset.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.setOffset = function(offset) {
  this.set(ol.OverlayProperty.OFFSET, offset);
};


/**
 * Set the position for this overlay. If the position is `undefined` the
 * overlay is hidden.
 * @param {ol.Coordinate|undefined} position The spatial point that the overlay
 *     is anchored at.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.setPosition = function(position) {
  this.set(ol.OverlayProperty.POSITION, position);
};


/**
 * Pan the map so that the overlay is entirely visible in the current viewport
 * (if necessary).
 * @private
 */
ol.Overlay.prototype.panIntoView_ = function() {
  var map = this.getMap();

  if (map === undefined || !map.getTargetElement()) {
    return;
  }

  var mapRect = this.getRect_(map.getTargetElement(), map.getSize());
  var element = /** @type {!Element} */ (this.getElement());
  var overlayRect = this.getRect_(element,
      [ol.dom.outerWidth(element), ol.dom.outerHeight(element)]);

  var margin = this.autoPanMargin_;
  if (!ol.extent.containsExtent(mapRect, overlayRect)) {
    // the overlay is not completely inside the viewport, so pan the map
    var offsetLeft = overlayRect[0] - mapRect[0];
    var offsetRight = mapRect[2] - overlayRect[2];
    var offsetTop = overlayRect[1] - mapRect[1];
    var offsetBottom = mapRect[3] - overlayRect[3];

    var delta = [0, 0];
    if (offsetLeft < 0) {
      // move map to the left
      delta[0] = offsetLeft - margin;
    } else if (offsetRight < 0) {
      // move map to the right
      delta[0] = Math.abs(offsetRight) + margin;
    }
    if (offsetTop < 0) {
      // move map up
      delta[1] = offsetTop - margin;
    } else if (offsetBottom < 0) {
      // move map down
      delta[1] = Math.abs(offsetBottom) + margin;
    }

    if (delta[0] !== 0 || delta[1] !== 0) {
      var center = /** @type {ol.Coordinate} */ (map.getView().getCenter());
      var centerPx = map.getPixelFromCoordinate(center);
      var newCenterPx = [
        centerPx[0] + delta[0],
        centerPx[1] + delta[1]
      ];

      if (this.autoPanAnimation_) {
        this.autoPanAnimation_.source = center;
        map.beforeRender(ol.animation.pan(this.autoPanAnimation_));
      }
      map.getView().setCenter(map.getCoordinateFromPixel(newCenterPx));
    }
  }
};


/**
 * Get the extent of an element relative to the document
 * @param {Element|undefined} element The element.
 * @param {ol.Size|undefined} size The size of the element.
 * @return {ol.Extent} The extent.
 * @private
 */
ol.Overlay.prototype.getRect_ = function(element, size) {
  var box = element.getBoundingClientRect();
  var offsetX = box.left + ol.global.pageXOffset;
  var offsetY = box.top + ol.global.pageYOffset;
  return [
    offsetX,
    offsetY,
    offsetX + size[0],
    offsetY + size[1]
  ];
};


/**
 * Set the positioning for this overlay.
 * @param {ol.OverlayPositioning} positioning how the overlay is
 *     positioned relative to its point on the map.
 * @observable
 * @api stable
 */
ol.Overlay.prototype.setPositioning = function(positioning) {
  this.set(ol.OverlayProperty.POSITIONING, positioning);
};


/**
 * Modify the visibility of the element.
 * @param {boolean} visible Element visibility.
 * @protected
 */
ol.Overlay.prototype.setVisible = function(visible) {
  if (this.rendered_.visible !== visible) {
    this.element_.style.display = visible ? '' : 'none';
    this.rendered_.visible = visible;
  }
};


/**
 * Update pixel position.
 * @protected
 */
ol.Overlay.prototype.updatePixelPosition = function() {
  var map = this.getMap();
  var position = this.getPosition();
  if (map === undefined || !map.isRendered() || position === undefined) {
    this.setVisible(false);
    return;
  }

  var pixel = map.getPixelFromCoordinate(position);
  var mapSize = map.getSize();
  this.updateRenderedPosition(pixel, mapSize);
};


/**
 * @param {ol.Pixel} pixel The pixel location.
 * @param {ol.Size|undefined} mapSize The map size.
 * @protected
 */
ol.Overlay.prototype.updateRenderedPosition = function(pixel, mapSize) {
  var style = this.element_.style;
  var offset = this.getOffset();

  var positioning = this.getPositioning();
  goog.DEBUG && console.assert(positioning !== undefined,
      'positioning should be defined');

  var offsetX = offset[0];
  var offsetY = offset[1];
  if (positioning == ol.OverlayPositioning.BOTTOM_RIGHT ||
      positioning == ol.OverlayPositioning.CENTER_RIGHT ||
      positioning == ol.OverlayPositioning.TOP_RIGHT) {
    if (this.rendered_.left_ !== '') {
      this.rendered_.left_ = style.left = '';
    }
    var right = Math.round(mapSize[0] - pixel[0] - offsetX) + 'px';
    if (this.rendered_.right_ != right) {
      this.rendered_.right_ = style.right = right;
    }
  } else {
    if (this.rendered_.right_ !== '') {
      this.rendered_.right_ = style.right = '';
    }
    if (positioning == ol.OverlayPositioning.BOTTOM_CENTER ||
        positioning == ol.OverlayPositioning.CENTER_CENTER ||
        positioning == ol.OverlayPositioning.TOP_CENTER) {
      offsetX -= this.element_.offsetWidth / 2;
    }
    var left = Math.round(pixel[0] + offsetX) + 'px';
    if (this.rendered_.left_ != left) {
      this.rendered_.left_ = style.left = left;
    }
  }
  if (positioning == ol.OverlayPositioning.BOTTOM_LEFT ||
      positioning == ol.OverlayPositioning.BOTTOM_CENTER ||
      positioning == ol.OverlayPositioning.BOTTOM_RIGHT) {
    if (this.rendered_.top_ !== '') {
      this.rendered_.top_ = style.top = '';
    }
    var bottom = Math.round(mapSize[1] - pixel[1] - offsetY) + 'px';
    if (this.rendered_.bottom_ != bottom) {
      this.rendered_.bottom_ = style.bottom = bottom;
    }
  } else {
    if (this.rendered_.bottom_ !== '') {
      this.rendered_.bottom_ = style.bottom = '';
    }
    if (positioning == ol.OverlayPositioning.CENTER_LEFT ||
        positioning == ol.OverlayPositioning.CENTER_CENTER ||
        positioning == ol.OverlayPositioning.CENTER_RIGHT) {
      offsetY -= this.element_.offsetHeight / 2;
    }
    var top = Math.round(pixel[1] + offsetY) + 'px';
    if (this.rendered_.top_ != top) {
      this.rendered_.top_ = style.top = top;
    }
  }

  this.setVisible(true);
};

goog.provide('ol.control.OverviewMap');

goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.Map');
goog.require('ol.MapEventType');
goog.require('ol.Object');
goog.require('ol.ObjectEventType');
goog.require('ol.Overlay');
goog.require('ol.OverlayPositioning');
goog.require('ol.View');
goog.require('ol.control.Control');
goog.require('ol.coordinate');
goog.require('ol.css');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');


/**
 * Create a new control with a map acting as an overview map for an other
 * defined map.
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.OverviewMapOptions=} opt_options OverviewMap options.
 * @api
 */
ol.control.OverviewMap = function(opt_options) {

  var options = opt_options ? opt_options : {};

  /**
   * @type {boolean}
   * @private
   */
  this.collapsed_ = options.collapsed !== undefined ? options.collapsed : true;

  /**
   * @private
   * @type {boolean}
   */
  this.collapsible_ = options.collapsible !== undefined ?
      options.collapsible : true;

  if (!this.collapsible_) {
    this.collapsed_ = false;
  }

  var className = options.className !== undefined ? options.className : 'ol-overviewmap';

  var tipLabel = options.tipLabel !== undefined ? options.tipLabel : 'Overview map';

  var collapseLabel = options.collapseLabel !== undefined ? options.collapseLabel : '\u00AB';

  if (typeof collapseLabel === 'string') {
    /**
     * @private
     * @type {Node}
     */
    this.collapseLabel_ = document.createElement('span');
    this.collapseLabel_.textContent = collapseLabel;
  } else {
    this.collapseLabel_ = collapseLabel;
  }

  var label = options.label !== undefined ? options.label : '\u00BB';


  if (typeof label === 'string') {
    /**
     * @private
     * @type {Node}
     */
    this.label_ = document.createElement('span');
    this.label_.textContent = label;
  } else {
    this.label_ = label;
  }

  var activeLabel = (this.collapsible_ && !this.collapsed_) ?
      this.collapseLabel_ : this.label_;
  var button = document.createElement('button');
  button.setAttribute('type', 'button');
  button.title = tipLabel;
  button.appendChild(activeLabel);

  ol.events.listen(button, ol.events.EventType.CLICK,
      this.handleClick_, this);

  var ovmapDiv = document.createElement('DIV');
  ovmapDiv.className = 'ol-overviewmap-map';

  /**
   * @type {ol.Map}
   * @private
   */
  this.ovmap_ = new ol.Map({
    controls: new ol.Collection(),
    interactions: new ol.Collection(),
    target: ovmapDiv,
    view: options.view
  });
  var ovmap = this.ovmap_;

  if (options.layers) {
    options.layers.forEach(
        /**
       * @param {ol.layer.Layer} layer Layer.
       */
        function(layer) {
          ovmap.addLayer(layer);
        }, this);
  }

  var box = document.createElement('DIV');
  box.className = 'ol-overviewmap-box';
  box.style.boxSizing = 'border-box';

  /**
   * @type {ol.Overlay}
   * @private
   */
  this.boxOverlay_ = new ol.Overlay({
    position: [0, 0],
    positioning: ol.OverlayPositioning.BOTTOM_LEFT,
    element: box
  });
  this.ovmap_.addOverlay(this.boxOverlay_);

  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
      ol.css.CLASS_CONTROL +
      (this.collapsed_ && this.collapsible_ ? ' ol-collapsed' : '') +
      (this.collapsible_ ? '' : ' ol-uncollapsible');
  var element = document.createElement('div');
  element.className = cssClasses;
  element.appendChild(ovmapDiv);
  element.appendChild(button);

  var render = options.render ? options.render : ol.control.OverviewMap.render;

  ol.control.Control.call(this, {
    element: element,
    render: render,
    target: options.target
  });
};
ol.inherits(ol.control.OverviewMap, ol.control.Control);


/**
 * @inheritDoc
 * @api
 */
ol.control.OverviewMap.prototype.setMap = function(map) {
  var oldMap = this.getMap();
  if (map === oldMap) {
    return;
  }
  if (oldMap) {
    var oldView = oldMap.getView();
    if (oldView) {
      this.unbindView_(oldView);
    }
  }
  ol.control.Control.prototype.setMap.call(this, map);

  if (map) {
    this.listenerKeys.push(ol.events.listen(
        map, ol.ObjectEventType.PROPERTYCHANGE,
        this.handleMapPropertyChange_, this));

    // TODO: to really support map switching, this would need to be reworked
    if (this.ovmap_.getLayers().getLength() === 0) {
      this.ovmap_.setLayerGroup(map.getLayerGroup());
    }

    var view = map.getView();
    if (view) {
      this.bindView_(view);
      if (view.isDef()) {
        this.ovmap_.updateSize();
        this.resetExtent_();
      }
    }
  }
};


/**
 * Handle map property changes.  This only deals with changes to the map's view.
 * @param {ol.ObjectEvent} event The propertychange event.
 * @private
 */
ol.control.OverviewMap.prototype.handleMapPropertyChange_ = function(event) {
  if (event.key === ol.MapProperty.VIEW) {
    var oldView = /** @type {ol.View} */ (event.oldValue);
    if (oldView) {
      this.unbindView_(oldView);
    }
    var newView = this.getMap().getView();
    this.bindView_(newView);
  }
};


/**
 * Register listeners for view property changes.
 * @param {ol.View} view The view.
 * @private
 */
ol.control.OverviewMap.prototype.bindView_ = function(view) {
  ol.events.listen(view,
      ol.Object.getChangeEventType(ol.View.Property.ROTATION),
      this.handleRotationChanged_, this);
};


/**
 * Unregister listeners for view property changes.
 * @param {ol.View} view The view.
 * @private
 */
ol.control.OverviewMap.prototype.unbindView_ = function(view) {
  ol.events.unlisten(view,
      ol.Object.getChangeEventType(ol.View.Property.ROTATION),
      this.handleRotationChanged_, this);
};


/**
 * Handle rotation changes to the main map.
 * TODO: This should rotate the extent rectrangle instead of the
 * overview map's view.
 * @private
 */
ol.control.OverviewMap.prototype.handleRotationChanged_ = function() {
  this.ovmap_.getView().setRotation(this.getMap().getView().getRotation());
};


/**
 * Update the overview map element.
 * @param {ol.MapEvent} mapEvent Map event.
 * @this {ol.control.OverviewMap}
 * @api
 */
ol.control.OverviewMap.render = function(mapEvent) {
  this.validateExtent_();
  this.updateBox_();
};


/**
 * Reset the overview map extent if the box size (width or
 * height) is less than the size of the overview map size times minRatio
 * or is greater than the size of the overview size times maxRatio.
 *
 * If the map extent was not reset, the box size can fits in the defined
 * ratio sizes. This method then checks if is contained inside the overview
 * map current extent. If not, recenter the overview map to the current
 * main map center location.
 * @private
 */
ol.control.OverviewMap.prototype.validateExtent_ = function() {
  var map = this.getMap();
  var ovmap = this.ovmap_;

  if (!map.isRendered() || !ovmap.isRendered()) {
    return;
  }

  var mapSize = /** @type {ol.Size} */ (map.getSize());

  var view = map.getView();
  var extent = view.calculateExtent(mapSize);

  var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize());

  var ovview = ovmap.getView();
  var ovextent = ovview.calculateExtent(ovmapSize);

  var topLeftPixel =
      ovmap.getPixelFromCoordinate(ol.extent.getTopLeft(extent));
  var bottomRightPixel =
      ovmap.getPixelFromCoordinate(ol.extent.getBottomRight(extent));

  var boxWidth = Math.abs(topLeftPixel[0] - bottomRightPixel[0]);
  var boxHeight = Math.abs(topLeftPixel[1] - bottomRightPixel[1]);

  var ovmapWidth = ovmapSize[0];
  var ovmapHeight = ovmapSize[1];

  if (boxWidth < ovmapWidth * ol.OVERVIEWMAP_MIN_RATIO ||
      boxHeight < ovmapHeight * ol.OVERVIEWMAP_MIN_RATIO ||
      boxWidth > ovmapWidth * ol.OVERVIEWMAP_MAX_RATIO ||
      boxHeight > ovmapHeight * ol.OVERVIEWMAP_MAX_RATIO) {
    this.resetExtent_();
  } else if (!ol.extent.containsExtent(ovextent, extent)) {
    this.recenter_();
  }
};


/**
 * Reset the overview map extent to half calculated min and max ratio times
 * the extent of the main map.
 * @private
 */
ol.control.OverviewMap.prototype.resetExtent_ = function() {
  if (ol.OVERVIEWMAP_MAX_RATIO === 0 || ol.OVERVIEWMAP_MIN_RATIO === 0) {
    return;
  }

  var map = this.getMap();
  var ovmap = this.ovmap_;

  var mapSize = /** @type {ol.Size} */ (map.getSize());

  var view = map.getView();
  var extent = view.calculateExtent(mapSize);

  var ovmapSize = /** @type {ol.Size} */ (ovmap.getSize());

  var ovview = ovmap.getView();

  // get how many times the current map overview could hold different
  // box sizes using the min and max ratio, pick the step in the middle used
  // to calculate the extent from the main map to set it to the overview map,
  var steps = Math.log(
      ol.OVERVIEWMAP_MAX_RATIO / ol.OVERVIEWMAP_MIN_RATIO) / Math.LN2;
  var ratio = 1 / (Math.pow(2, steps / 2) * ol.OVERVIEWMAP_MIN_RATIO);
  ol.extent.scaleFromCenter(extent, ratio);
  ovview.fit(extent, ovmapSize);
};


/**
 * Set the center of the overview map to the map center without changing its
 * resolution.
 * @private
 */
ol.control.OverviewMap.prototype.recenter_ = function() {
  var map = this.getMap();
  var ovmap = this.ovmap_;

  var view = map.getView();

  var ovview = ovmap.getView();

  ovview.setCenter(view.getCenter());
};


/**
 * Update the box using the main map extent
 * @private
 */
ol.control.OverviewMap.prototype.updateBox_ = function() {
  var map = this.getMap();
  var ovmap = this.ovmap_;

  if (!map.isRendered() || !ovmap.isRendered()) {
    return;
  }

  var mapSize = /** @type {ol.Size} */ (map.getSize());

  var view = map.getView();

  var ovview = ovmap.getView();

  var rotation = view.getRotation();

  var overlay = this.boxOverlay_;
  var box = this.boxOverlay_.getElement();
  var extent = view.calculateExtent(mapSize);
  var ovresolution = ovview.getResolution();
  var bottomLeft = ol.extent.getBottomLeft(extent);
  var topRight = ol.extent.getTopRight(extent);

  // set position using bottom left coordinates
  var rotateBottomLeft = this.calculateCoordinateRotate_(rotation, bottomLeft);
  overlay.setPosition(rotateBottomLeft);

  // set box size calculated from map extent size and overview map resolution
  if (box) {
    box.style.width = Math.abs((bottomLeft[0] - topRight[0]) / ovresolution) + 'px';
    box.style.height = Math.abs((topRight[1] - bottomLeft[1]) / ovresolution) + 'px';
  }
};


/**
 * @param {number} rotation Target rotation.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @return {ol.Coordinate|undefined} Coordinate for rotation and center anchor.
 * @private
 */
ol.control.OverviewMap.prototype.calculateCoordinateRotate_ = function(
    rotation, coordinate) {
  var coordinateRotate;

  var map = this.getMap();
  var view = map.getView();

  var currentCenter = view.getCenter();

  if (currentCenter) {
    coordinateRotate = [
      coordinate[0] - currentCenter[0],
      coordinate[1] - currentCenter[1]
    ];
    ol.coordinate.rotate(coordinateRotate, rotation);
    ol.coordinate.add(coordinateRotate, currentCenter);
  }
  return coordinateRotate;
};


/**
 * @param {Event} event The event to handle
 * @private
 */
ol.control.OverviewMap.prototype.handleClick_ = function(event) {
  event.preventDefault();
  this.handleToggle_();
};


/**
 * @private
 */
ol.control.OverviewMap.prototype.handleToggle_ = function() {
  this.element.classList.toggle('ol-collapsed');
  if (this.collapsed_) {
    ol.dom.replaceNode(this.collapseLabel_, this.label_);
  } else {
    ol.dom.replaceNode(this.label_, this.collapseLabel_);
  }
  this.collapsed_ = !this.collapsed_;

  // manage overview map if it had not been rendered before and control
  // is expanded
  var ovmap = this.ovmap_;
  if (!this.collapsed_ && !ovmap.isRendered()) {
    ovmap.updateSize();
    this.resetExtent_();
    ol.events.listenOnce(ovmap, ol.MapEventType.POSTRENDER,
        function(event) {
          this.updateBox_();
        },
        this);
  }
};


/**
 * Return `true` if the overview map is collapsible, `false` otherwise.
 * @return {boolean} True if the widget is collapsible.
 * @api stable
 */
ol.control.OverviewMap.prototype.getCollapsible = function() {
  return this.collapsible_;
};


/**
 * Set whether the overview map should be collapsible.
 * @param {boolean} collapsible True if the widget is collapsible.
 * @api stable
 */
ol.control.OverviewMap.prototype.setCollapsible = function(collapsible) {
  if (this.collapsible_ === collapsible) {
    return;
  }
  this.collapsible_ = collapsible;
  this.element.classList.toggle('ol-uncollapsible');
  if (!collapsible && this.collapsed_) {
    this.handleToggle_();
  }
};


/**
 * Collapse or expand the overview map according to the passed parameter. Will
 * not do anything if the overview map isn't collapsible or if the current
 * collapsed state is already the one requested.
 * @param {boolean} collapsed True if the widget is collapsed.
 * @api stable
 */
ol.control.OverviewMap.prototype.setCollapsed = function(collapsed) {
  if (!this.collapsible_ || this.collapsed_ === collapsed) {
    return;
  }
  this.handleToggle_();
};


/**
 * Determine if the overview map is collapsed.
 * @return {boolean} The overview map is collapsed.
 * @api stable
 */
ol.control.OverviewMap.prototype.getCollapsed = function() {
  return this.collapsed_;
};


/**
 * Return the overview map.
 * @return {ol.Map} Overview map.
 * @api
 */
ol.control.OverviewMap.prototype.getOverviewMap = function() {
  return this.ovmap_;
};

goog.provide('ol.control.ScaleLine');

goog.require('ol');
goog.require('ol.Object');
goog.require('ol.asserts');
goog.require('ol.control.Control');
goog.require('ol.css');
goog.require('ol.events');
goog.require('ol.proj.METERS_PER_UNIT');
goog.require('ol.proj.Units');


/**
 * @classdesc
 * A control displaying rough y-axis distances, calculated for the center of the
 * viewport. For conformal projections (e.g. EPSG:3857, the default view
 * projection in OpenLayers), the scale is valid for all directions.
 * No scale line will be shown when the y-axis distance of a pixel at the
 * viewport center cannot be calculated in the view projection.
 * By default the scale line will show in the bottom left portion of the map,
 * but this can be changed by using the css selector `.ol-scale-line`.
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.ScaleLineOptions=} opt_options Scale line options.
 * @api stable
 */
ol.control.ScaleLine = function(opt_options) {

  var options = opt_options ? opt_options : {};

  var className = options.className !== undefined ? options.className : 'ol-scale-line';

  /**
   * @private
   * @type {Element}
   */
  this.innerElement_ = document.createElement('DIV');
  this.innerElement_.className = className + '-inner';

  /**
   * @private
   * @type {Element}
   */
  this.element_ = document.createElement('DIV');
  this.element_.className = className + ' ' + ol.css.CLASS_UNSELECTABLE;
  this.element_.appendChild(this.innerElement_);

  /**
   * @private
   * @type {?olx.ViewState}
   */
  this.viewState_ = null;

  /**
   * @private
   * @type {number}
   */
  this.minWidth_ = options.minWidth !== undefined ? options.minWidth : 64;

  /**
   * @private
   * @type {boolean}
   */
  this.renderedVisible_ = false;

  /**
   * @private
   * @type {number|undefined}
   */
  this.renderedWidth_ = undefined;

  /**
   * @private
   * @type {string}
   */
  this.renderedHTML_ = '';

  var render = options.render ? options.render : ol.control.ScaleLine.render;

  ol.control.Control.call(this, {
    element: this.element_,
    render: render,
    target: options.target
  });

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.control.ScaleLine.Property.UNITS),
      this.handleUnitsChanged_, this);

  this.setUnits(/** @type {ol.control.ScaleLine.Units} */ (options.units) ||
      ol.control.ScaleLine.Units.METRIC);

};
ol.inherits(ol.control.ScaleLine, ol.control.Control);


/**
 * @const
 * @type {Array.<number>}
 */
ol.control.ScaleLine.LEADING_DIGITS = [1, 2, 5];


/**
 * Return the units to use in the scale line.
 * @return {ol.control.ScaleLine.Units|undefined} The units to use in the scale
 *     line.
 * @observable
 * @api stable
 */
ol.control.ScaleLine.prototype.getUnits = function() {
  return /** @type {ol.control.ScaleLine.Units|undefined} */ (
      this.get(ol.control.ScaleLine.Property.UNITS));
};


/**
 * Update the scale line element.
 * @param {ol.MapEvent} mapEvent Map event.
 * @this {ol.control.ScaleLine}
 * @api
 */
ol.control.ScaleLine.render = function(mapEvent) {
  var frameState = mapEvent.frameState;
  if (!frameState) {
    this.viewState_ = null;
  } else {
    this.viewState_ = frameState.viewState;
  }
  this.updateElement_();
};


/**
 * @private
 */
ol.control.ScaleLine.prototype.handleUnitsChanged_ = function() {
  this.updateElement_();
};


/**
 * Set the units to use in the scale line.
 * @param {ol.control.ScaleLine.Units} units The units to use in the scale line.
 * @observable
 * @api stable
 */
ol.control.ScaleLine.prototype.setUnits = function(units) {
  this.set(ol.control.ScaleLine.Property.UNITS, units);
};


/**
 * @private
 */
ol.control.ScaleLine.prototype.updateElement_ = function() {
  var viewState = this.viewState_;

  if (!viewState) {
    if (this.renderedVisible_) {
      this.element_.style.display = 'none';
      this.renderedVisible_ = false;
    }
    return;
  }

  var center = viewState.center;
  var projection = viewState.projection;
  var metersPerUnit = projection.getMetersPerUnit();
  var pointResolution =
      projection.getPointResolution(viewState.resolution, center) *
      metersPerUnit;

  var nominalCount = this.minWidth_ * pointResolution;
  var suffix = '';
  var units = this.getUnits();
  if (units == ol.control.ScaleLine.Units.DEGREES) {
    var metersPerDegree = ol.proj.METERS_PER_UNIT[ol.proj.Units.DEGREES];
    pointResolution /= metersPerDegree;
    if (nominalCount < metersPerDegree / 60) {
      suffix = '\u2033'; // seconds
      pointResolution *= 3600;
    } else if (nominalCount < metersPerDegree) {
      suffix = '\u2032'; // minutes
      pointResolution *= 60;
    } else {
      suffix = '\u00b0'; // degrees
    }
  } else if (units == ol.control.ScaleLine.Units.IMPERIAL) {
    if (nominalCount < 0.9144) {
      suffix = 'in';
      pointResolution /= 0.0254;
    } else if (nominalCount < 1609.344) {
      suffix = 'ft';
      pointResolution /= 0.3048;
    } else {
      suffix = 'mi';
      pointResolution /= 1609.344;
    }
  } else if (units == ol.control.ScaleLine.Units.NAUTICAL) {
    pointResolution /= 1852;
    suffix = 'nm';
  } else if (units == ol.control.ScaleLine.Units.METRIC) {
    if (nominalCount < 1) {
      suffix = 'mm';
      pointResolution *= 1000;
    } else if (nominalCount < 1000) {
      suffix = 'm';
    } else {
      suffix = 'km';
      pointResolution /= 1000;
    }
  } else if (units == ol.control.ScaleLine.Units.US) {
    if (nominalCount < 0.9144) {
      suffix = 'in';
      pointResolution *= 39.37;
    } else if (nominalCount < 1609.344) {
      suffix = 'ft';
      pointResolution /= 0.30480061;
    } else {
      suffix = 'mi';
      pointResolution /= 1609.3472;
    }
  } else {
    ol.asserts.assert(false, 33); // Invalid units
  }

  var i = 3 * Math.floor(
      Math.log(this.minWidth_ * pointResolution) / Math.log(10));
  var count, width;
  while (true) {
    count = ol.control.ScaleLine.LEADING_DIGITS[((i % 3) + 3) % 3] *
        Math.pow(10, Math.floor(i / 3));
    width = Math.round(count / pointResolution);
    if (isNaN(width)) {
      this.element_.style.display = 'none';
      this.renderedVisible_ = false;
      return;
    } else if (width >= this.minWidth_) {
      break;
    }
    ++i;
  }

  var html = count + ' ' + suffix;
  if (this.renderedHTML_ != html) {
    this.innerElement_.innerHTML = html;
    this.renderedHTML_ = html;
  }

  if (this.renderedWidth_ != width) {
    this.innerElement_.style.width = width + 'px';
    this.renderedWidth_ = width;
  }

  if (!this.renderedVisible_) {
    this.element_.style.display = '';
    this.renderedVisible_ = true;
  }

};


/**
 * @enum {string}
 * @api
 */
ol.control.ScaleLine.Property = {
  UNITS: 'units'
};


/**
 * Units for the scale line. Supported values are `'degrees'`, `'imperial'`,
 * `'nautical'`, `'metric'`, `'us'`.
 * @enum {string}
 */
ol.control.ScaleLine.Units = {
  DEGREES: 'degrees',
  IMPERIAL: 'imperial',
  NAUTICAL: 'nautical',
  METRIC: 'metric',
  US: 'us'
};

// FIXME should possibly show tooltip when dragging?

goog.provide('ol.control.ZoomSlider');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.animation');
goog.require('ol.control.Control');
goog.require('ol.css');
goog.require('ol.easing');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.math');
goog.require('ol.pointer.PointerEventHandler');


/**
 * @classdesc
 * A slider type of control for zooming.
 *
 * Example:
 *
 *     map.addControl(new ol.control.ZoomSlider());
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options.
 * @api stable
 */
ol.control.ZoomSlider = function(opt_options) {

  var options = opt_options ? opt_options : {};

  /**
   * Will hold the current resolution of the view.
   *
   * @type {number|undefined}
   * @private
   */
  this.currentResolution_ = undefined;

  /**
   * The direction of the slider. Will be determined from actual display of the
   * container and defaults to ol.control.ZoomSlider.direction.VERTICAL.
   *
   * @type {ol.control.ZoomSlider.direction}
   * @private
   */
  this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;

  /**
   * @type {boolean}
   * @private
   */
  this.dragging_;

  /**
   * @type {!Array.<ol.EventsKey>}
   * @private
   */
  this.dragListenerKeys_ = [];

  /**
   * @type {number}
   * @private
   */
  this.heightLimit_ = 0;

  /**
   * @type {number}
   * @private
   */
  this.widthLimit_ = 0;

  /**
   * @type {number|undefined}
   * @private
   */
  this.previousX_;

  /**
   * @type {number|undefined}
   * @private
   */
  this.previousY_;

  /**
   * The calculated thumb size (border box plus margins).  Set when initSlider_
   * is called.
   * @type {ol.Size}
   * @private
   */
  this.thumbSize_ = null;

  /**
   * Whether the slider is initialized.
   * @type {boolean}
   * @private
   */
  this.sliderInitialized_ = false;

  /**
   * @type {number}
   * @private
   */
  this.duration_ = options.duration !== undefined ? options.duration : 200;

  var className = options.className !== undefined ? options.className : 'ol-zoomslider';
  var thumbElement = document.createElement('button');
  thumbElement.setAttribute('type', 'button');
  thumbElement.className = className + '-thumb ' + ol.css.CLASS_UNSELECTABLE;
  var containerElement = document.createElement('div');
  containerElement.className = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + ol.css.CLASS_CONTROL;
  containerElement.appendChild(thumbElement);
  /**
   * @type {ol.pointer.PointerEventHandler}
   * @private
   */
  this.dragger_ = new ol.pointer.PointerEventHandler(containerElement);

  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERDOWN,
      this.handleDraggerStart_, this);
  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERMOVE,
      this.handleDraggerDrag_, this);
  ol.events.listen(this.dragger_, ol.pointer.EventType.POINTERUP,
      this.handleDraggerEnd_, this);

  ol.events.listen(containerElement, ol.events.EventType.CLICK,
      this.handleContainerClick_, this);
  ol.events.listen(thumbElement, ol.events.EventType.CLICK,
      ol.events.Event.stopPropagation);

  var render = options.render ? options.render : ol.control.ZoomSlider.render;

  ol.control.Control.call(this, {
    element: containerElement,
    render: render
  });
};
ol.inherits(ol.control.ZoomSlider, ol.control.Control);


/**
 * @inheritDoc
 */
ol.control.ZoomSlider.prototype.disposeInternal = function() {
  this.dragger_.dispose();
  ol.control.Control.prototype.disposeInternal.call(this);
};


/**
 * The enum for available directions.
 *
 * @enum {number}
 */
ol.control.ZoomSlider.direction = {
  VERTICAL: 0,
  HORIZONTAL: 1
};


/**
 * @inheritDoc
 */
ol.control.ZoomSlider.prototype.setMap = function(map) {
  ol.control.Control.prototype.setMap.call(this, map);
  if (map) {
    map.render();
  }
};


/**
 * Initializes the slider element. This will determine and set this controls
 * direction_ and also constrain the dragging of the thumb to always be within
 * the bounds of the container.
 *
 * @private
 */
ol.control.ZoomSlider.prototype.initSlider_ = function() {
  var container = this.element;
  var containerSize = {
    width: container.offsetWidth, height: container.offsetHeight
  };

  var thumb = container.firstElementChild;
  var computedStyle = ol.global.getComputedStyle(thumb);
  var thumbWidth = thumb.offsetWidth +
      parseFloat(computedStyle['marginRight']) +
      parseFloat(computedStyle['marginLeft']);
  var thumbHeight = thumb.offsetHeight +
      parseFloat(computedStyle['marginTop']) +
      parseFloat(computedStyle['marginBottom']);
  this.thumbSize_ = [thumbWidth, thumbHeight];

  if (containerSize.width > containerSize.height) {
    this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL;
    this.widthLimit_ = containerSize.width - thumbWidth;
  } else {
    this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
    this.heightLimit_ = containerSize.height - thumbHeight;
  }
  this.sliderInitialized_ = true;
};


/**
 * Update the zoomslider element.
 * @param {ol.MapEvent} mapEvent Map event.
 * @this {ol.control.ZoomSlider}
 * @api
 */
ol.control.ZoomSlider.render = function(mapEvent) {
  if (!mapEvent.frameState) {
    return;
  }
  if (!this.sliderInitialized_) {
    this.initSlider_();
  }
  var res = mapEvent.frameState.viewState.resolution;
  if (res !== this.currentResolution_) {
    this.currentResolution_ = res;
    this.setThumbPosition_(res);
  }
};


/**
 * @param {Event} event The browser event to handle.
 * @private
 */
ol.control.ZoomSlider.prototype.handleContainerClick_ = function(event) {
  var map = this.getMap();
  var view = map.getView();
  var currentResolution = view.getResolution();
  map.beforeRender(ol.animation.zoom({
    resolution: /** @type {number} */ (currentResolution),
    duration: this.duration_,
    easing: ol.easing.easeOut
  }));
  var relativePosition = this.getRelativePosition_(
      event.offsetX - this.thumbSize_[0] / 2,
      event.offsetY - this.thumbSize_[1] / 2);
  var resolution = this.getResolutionForPosition_(relativePosition);
  view.setResolution(view.constrainResolution(resolution));
};


/**
 * Handle dragger start events.
 * @param {ol.pointer.PointerEvent} event The drag event.
 * @private
 */
ol.control.ZoomSlider.prototype.handleDraggerStart_ = function(event) {
  if (!this.dragging_ &&
      event.originalEvent.target === this.element.firstElementChild) {
    this.getMap().getView().setHint(ol.View.Hint.INTERACTING, 1);
    this.previousX_ = event.clientX;
    this.previousY_ = event.clientY;
    this.dragging_ = true;

    if (this.dragListenerKeys_.length === 0) {
      var drag = this.handleDraggerDrag_;
      var end = this.handleDraggerEnd_;
      this.dragListenerKeys_.push(
        ol.events.listen(document, ol.events.EventType.MOUSEMOVE, drag, this),
        ol.events.listen(document, ol.events.EventType.TOUCHMOVE, drag, this),
        ol.events.listen(document, ol.pointer.EventType.POINTERMOVE, drag, this),
        ol.events.listen(document, ol.events.EventType.MOUSEUP, end, this),
        ol.events.listen(document, ol.events.EventType.TOUCHEND, end, this),
        ol.events.listen(document, ol.pointer.EventType.POINTERUP, end, this)
      );
    }
  }
};


/**
 * Handle dragger drag events.
 *
 * @param {ol.pointer.PointerEvent|Event} event The drag event.
 * @private
 */
ol.control.ZoomSlider.prototype.handleDraggerDrag_ = function(event) {
  if (this.dragging_) {
    var element = this.element.firstElementChild;
    var deltaX = event.clientX - this.previousX_ + parseInt(element.style.left, 10);
    var deltaY = event.clientY - this.previousY_ + parseInt(element.style.top, 10);
    var relativePosition = this.getRelativePosition_(deltaX, deltaY);
    this.currentResolution_ = this.getResolutionForPosition_(relativePosition);
    this.getMap().getView().setResolution(this.currentResolution_);
    this.setThumbPosition_(this.currentResolution_);
    this.previousX_ = event.clientX;
    this.previousY_ = event.clientY;
  }
};


/**
 * Handle dragger end events.
 * @param {ol.pointer.PointerEvent|Event} event The drag event.
 * @private
 */
ol.control.ZoomSlider.prototype.handleDraggerEnd_ = function(event) {
  if (this.dragging_) {
    var map = this.getMap();
    var view = map.getView();
    view.setHint(ol.View.Hint.INTERACTING, -1);
    map.beforeRender(ol.animation.zoom({
      resolution: /** @type {number} */ (this.currentResolution_),
      duration: this.duration_,
      easing: ol.easing.easeOut
    }));
    var resolution = view.constrainResolution(this.currentResolution_);
    view.setResolution(resolution);
    this.dragging_ = false;
    this.previousX_ = undefined;
    this.previousY_ = undefined;
    this.dragListenerKeys_.forEach(ol.events.unlistenByKey);
    this.dragListenerKeys_.length = 0;
  }
};


/**
 * Positions the thumb inside its container according to the given resolution.
 *
 * @param {number} res The res.
 * @private
 */
ol.control.ZoomSlider.prototype.setThumbPosition_ = function(res) {
  var position = this.getPositionForResolution_(res);
  var thumb = this.element.firstElementChild;

  if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) {
    thumb.style.left = this.widthLimit_ * position + 'px';
  } else {
    thumb.style.top = this.heightLimit_ * position + 'px';
  }
};


/**
 * Calculates the relative position of the thumb given x and y offsets.  The
 * relative position scales from 0 to 1.  The x and y offsets are assumed to be
 * in pixel units within the dragger limits.
 *
 * @param {number} x Pixel position relative to the left of the slider.
 * @param {number} y Pixel position relative to the top of the slider.
 * @return {number} The relative position of the thumb.
 * @private
 */
ol.control.ZoomSlider.prototype.getRelativePosition_ = function(x, y) {
  var amount;
  if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) {
    amount = x / this.widthLimit_;
  } else {
    amount = y / this.heightLimit_;
  }
  return ol.math.clamp(amount, 0, 1);
};


/**
 * Calculates the corresponding resolution of the thumb given its relative
 * position (where 0 is the minimum and 1 is the maximum).
 *
 * @param {number} position The relative position of the thumb.
 * @return {number} The corresponding resolution.
 * @private
 */
ol.control.ZoomSlider.prototype.getResolutionForPosition_ = function(position) {
  var fn = this.getMap().getView().getResolutionForValueFunction();
  return fn(1 - position);
};


/**
 * Determines the relative position of the slider for the given resolution.  A
 * relative position of 0 corresponds to the minimum view resolution.  A
 * relative position of 1 corresponds to the maximum view resolution.
 *
 * @param {number} res The resolution.
 * @return {number} The relative position value (between 0 and 1).
 * @private
 */
ol.control.ZoomSlider.prototype.getPositionForResolution_ = function(res) {
  var fn = this.getMap().getView().getValueForResolutionFunction();
  return 1 - fn(res);
};

goog.provide('ol.control.ZoomToExtent');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.control.Control');
goog.require('ol.css');


/**
 * @classdesc
 * A button control which, when pressed, changes the map view to a specific
 * extent. To style this control use the css selector `.ol-zoom-extent`.
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {olx.control.ZoomToExtentOptions=} opt_options Options.
 * @api stable
 */
ol.control.ZoomToExtent = function(opt_options) {
  var options = opt_options ? opt_options : {};

  /**
   * @type {ol.Extent}
   * @private
   */
  this.extent_ = options.extent ? options.extent : null;

  var className = options.className !== undefined ? options.className :
      'ol-zoom-extent';

  var label = options.label !== undefined ? options.label : 'E';
  var tipLabel = options.tipLabel !== undefined ?
      options.tipLabel : 'Fit to extent';
  var button = document.createElement('button');
  button.setAttribute('type', 'button');
  button.title = tipLabel;
  button.appendChild(
    typeof label === 'string' ? document.createTextNode(label) : label
  );

  ol.events.listen(button, ol.events.EventType.CLICK,
      this.handleClick_, this);

  var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' +
      ol.css.CLASS_CONTROL;
  var element = document.createElement('div');
  element.className = cssClasses;
  element.appendChild(button);

  ol.control.Control.call(this, {
    element: element,
    target: options.target
  });
};
ol.inherits(ol.control.ZoomToExtent, ol.control.Control);


/**
 * @param {Event} event The event to handle
 * @private
 */
ol.control.ZoomToExtent.prototype.handleClick_ = function(event) {
  event.preventDefault();
  this.handleZoomToExtent_();
};


/**
 * @private
 */
ol.control.ZoomToExtent.prototype.handleZoomToExtent_ = function() {
  var map = this.getMap();
  var view = map.getView();
  var extent = !this.extent_ ? view.getProjection().getExtent() : this.extent_;
  var size = /** @type {ol.Size} */ (map.getSize());
  view.fit(extent, size);
};

goog.provide('ol.DeviceOrientation');

goog.require('ol.events');
goog.require('ol');
goog.require('ol.Object');
goog.require('ol.has');
goog.require('ol.math');


/**
 * @enum {string}
 */
ol.DeviceOrientationProperty = {
  ALPHA: 'alpha',
  BETA: 'beta',
  GAMMA: 'gamma',
  HEADING: 'heading',
  TRACKING: 'tracking'
};


/**
 * @classdesc
 * The ol.DeviceOrientation class provides access to information from
 * DeviceOrientation events.  See the [HTML 5 DeviceOrientation Specification](
 * http://www.w3.org/TR/orientation-event/) for more details.
 *
 * Many new computers, and especially mobile phones
 * and tablets, provide hardware support for device orientation. Web
 * developers targeting mobile devices will be especially interested in this
 * class.
 *
 * Device orientation data are relative to a common starting point. For mobile
 * devices, the starting point is to lay your phone face up on a table with the
 * top of the phone pointing north. This represents the zero state. All
 * angles are then relative to this state. For computers, it is the same except
 * the screen is open at 90 degrees.
 *
 * Device orientation is reported as three angles - `alpha`, `beta`, and
 * `gamma` - relative to the starting position along the three planar axes X, Y
 * and Z. The X axis runs from the left edge to the right edge through the
 * middle of the device. Similarly, the Y axis runs from the bottom to the top
 * of the device through the middle. The Z axis runs from the back to the front
 * through the middle. In the starting position, the X axis points to the
 * right, the Y axis points away from you and the Z axis points straight up
 * from the device lying flat.
 *
 * The three angles representing the device orientation are relative to the
 * three axes. `alpha` indicates how much the device has been rotated around the
 * Z axis, which is commonly interpreted as the compass heading (see note
 * below). `beta` indicates how much the device has been rotated around the X
 * axis, or how much it is tilted from front to back.  `gamma` indicates how
 * much the device has been rotated around the Y axis, or how much it is tilted
 * from left to right.
 *
 * For most browsers, the `alpha` value returns the compass heading so if the
 * device points north, it will be 0.  With Safari on iOS, the 0 value of
 * `alpha` is calculated from when device orientation was first requested.
 * ol.DeviceOrientation provides the `heading` property which normalizes this
 * behavior across all browsers for you.
 *
 * It is important to note that the HTML 5 DeviceOrientation specification
 * indicates that `alpha`, `beta` and `gamma` are in degrees while the
 * equivalent properties in ol.DeviceOrientation are in radians for consistency
 * with all other uses of angles throughout OpenLayers.
 *
 * To get notified of device orientation changes, register a listener for the
 * generic `change` event on your `ol.DeviceOrientation` instance.
 *
 * @see {@link http://www.w3.org/TR/orientation-event/}
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.DeviceOrientationOptions=} opt_options Options.
 * @api
 */
ol.DeviceOrientation = function(opt_options) {

  ol.Object.call(this);

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.listenerKey_ = null;

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.DeviceOrientationProperty.TRACKING),
      this.handleTrackingChanged_, this);

  this.setTracking(options.tracking !== undefined ? options.tracking : false);

};
ol.inherits(ol.DeviceOrientation, ol.Object);


/**
 * @inheritDoc
 */
ol.DeviceOrientation.prototype.disposeInternal = function() {
  this.setTracking(false);
  ol.Object.prototype.disposeInternal.call(this);
};


/**
 * @private
 * @param {Event} originalEvent Event.
 */
ol.DeviceOrientation.prototype.orientationChange_ = function(originalEvent) {
  var event = /** @type {DeviceOrientationEvent} */ (originalEvent);
  if (event.alpha !== null) {
    var alpha = ol.math.toRadians(event.alpha);
    this.set(ol.DeviceOrientationProperty.ALPHA, alpha);
    // event.absolute is undefined in iOS.
    if (typeof event.absolute === 'boolean' && event.absolute) {
      this.set(ol.DeviceOrientationProperty.HEADING, alpha);
    } else if (typeof event.webkitCompassHeading === 'number' &&
               event.webkitCompassAccuracy != -1) {
      var heading = ol.math.toRadians(event.webkitCompassHeading);
      this.set(ol.DeviceOrientationProperty.HEADING, heading);
    }
  }
  if (event.beta !== null) {
    this.set(ol.DeviceOrientationProperty.BETA,
        ol.math.toRadians(event.beta));
  }
  if (event.gamma !== null) {
    this.set(ol.DeviceOrientationProperty.GAMMA,
        ol.math.toRadians(event.gamma));
  }
  this.changed();
};


/**
 * Rotation around the device z-axis (in radians).
 * @return {number|undefined} The euler angle in radians of the device from the
 *     standard Z axis.
 * @observable
 * @api
 */
ol.DeviceOrientation.prototype.getAlpha = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.DeviceOrientationProperty.ALPHA));
};


/**
 * Rotation around the device x-axis (in radians).
 * @return {number|undefined} The euler angle in radians of the device from the
 *     planar X axis.
 * @observable
 * @api
 */
ol.DeviceOrientation.prototype.getBeta = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.DeviceOrientationProperty.BETA));
};


/**
 * Rotation around the device y-axis (in radians).
 * @return {number|undefined} The euler angle in radians of the device from the
 *     planar Y axis.
 * @observable
 * @api
 */
ol.DeviceOrientation.prototype.getGamma = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.DeviceOrientationProperty.GAMMA));
};


/**
 * The heading of the device relative to north (in radians).
 * @return {number|undefined} The heading of the device relative to north, in
 *     radians, normalizing for different browser behavior.
 * @observable
 * @api
 */
ol.DeviceOrientation.prototype.getHeading = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.DeviceOrientationProperty.HEADING));
};


/**
 * Determine if orientation is being tracked.
 * @return {boolean} Changes in device orientation are being tracked.
 * @observable
 * @api
 */
ol.DeviceOrientation.prototype.getTracking = function() {
  return /** @type {boolean} */ (
      this.get(ol.DeviceOrientationProperty.TRACKING));
};


/**
 * @private
 */
ol.DeviceOrientation.prototype.handleTrackingChanged_ = function() {
  if (ol.has.DEVICE_ORIENTATION) {
    var tracking = this.getTracking();
    if (tracking && !this.listenerKey_) {
      this.listenerKey_ = ol.events.listen(ol.global, 'deviceorientation',
          this.orientationChange_, this);
    } else if (!tracking && this.listenerKey_ !== null) {
      ol.events.unlistenByKey(this.listenerKey_);
      this.listenerKey_ = null;
    }
  }
};


/**
 * Enable or disable tracking of device orientation events.
 * @param {boolean} tracking The status of tracking changes to alpha, beta and
 *     gamma. If true, changes are tracked and reported immediately.
 * @observable
 * @api
 */
ol.DeviceOrientation.prototype.setTracking = function(tracking) {
  this.set(ol.DeviceOrientationProperty.TRACKING, tracking);
};

goog.provide('ol.Feature');

goog.require('ol.asserts');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol');
goog.require('ol.Object');
goog.require('ol.geom.Geometry');
goog.require('ol.style.Style');


/**
 * @classdesc
 * A vector object for geographic features with a geometry and other
 * attribute properties, similar to the features in vector file formats like
 * GeoJSON.
 *
 * Features can be styled individually with `setStyle`; otherwise they use the
 * style of their vector layer.
 *
 * Note that attribute properties are set as {@link ol.Object} properties on
 * the feature object, so they are observable, and have get/set accessors.
 *
 * Typically, a feature has a single geometry property. You can set the
 * geometry using the `setGeometry` method and get it with `getGeometry`.
 * It is possible to store more than one geometry on a feature using attribute
 * properties. By default, the geometry used for rendering is identified by
 * the property name `geometry`. If you want to use another geometry property
 * for rendering, use the `setGeometryName` method to change the attribute
 * property associated with the geometry for the feature.  For example:
 *
 * ```js
 * var feature = new ol.Feature({
 *   geometry: new ol.geom.Polygon(polyCoords),
 *   labelPoint: new ol.geom.Point(labelCoords),
 *   name: 'My Polygon'
 * });
 *
 * // get the polygon geometry
 * var poly = feature.getGeometry();
 *
 * // Render the feature as a point using the coordinates from labelPoint
 * feature.setGeometryName('labelPoint');
 *
 * // get the point geometry
 * var point = feature.getGeometry();
 * ```
 *
 * @constructor
 * @extends {ol.Object}
 * @param {ol.geom.Geometry|Object.<string, *>=} opt_geometryOrProperties
 *     You may pass a Geometry object directly, or an object literal
 *     containing properties.  If you pass an object literal, you may
 *     include a Geometry associated with a `geometry` key.
 * @api stable
 */
ol.Feature = function(opt_geometryOrProperties) {

  ol.Object.call(this);

  /**
   * @private
   * @type {number|string|undefined}
   */
  this.id_ = undefined;

  /**
   * @type {string}
   * @private
   */
  this.geometryName_ = 'geometry';

  /**
   * User provided style.
   * @private
   * @type {ol.style.Style|Array.<ol.style.Style>|
   *     ol.FeatureStyleFunction}
   */
  this.style_ = null;

  /**
   * @private
   * @type {ol.FeatureStyleFunction|undefined}
   */
  this.styleFunction_ = undefined;

  /**
   * @private
   * @type {?ol.EventsKey}
   */
  this.geometryChangeKey_ = null;

  ol.events.listen(
      this, ol.Object.getChangeEventType(this.geometryName_),
      this.handleGeometryChanged_, this);

  if (opt_geometryOrProperties !== undefined) {
    if (opt_geometryOrProperties instanceof ol.geom.Geometry ||
        !opt_geometryOrProperties) {
      var geometry = opt_geometryOrProperties;
      this.setGeometry(geometry);
    } else {
      /** @type {Object.<string, *>} */
      var properties = opt_geometryOrProperties;
      this.setProperties(properties);
    }
  }
};
ol.inherits(ol.Feature, ol.Object);


/**
 * Clone this feature. If the original feature has a geometry it
 * is also cloned. The feature id is not set in the clone.
 * @return {ol.Feature} The clone.
 * @api stable
 */
ol.Feature.prototype.clone = function() {
  var clone = new ol.Feature(this.getProperties());
  clone.setGeometryName(this.getGeometryName());
  var geometry = this.getGeometry();
  if (geometry) {
    clone.setGeometry(geometry.clone());
  }
  var style = this.getStyle();
  if (style) {
    clone.setStyle(style);
  }
  return clone;
};


/**
 * Get the feature's default geometry.  A feature may have any number of named
 * geometries.  The "default" geometry (the one that is rendered by default) is
 * set when calling {@link ol.Feature#setGeometry}.
 * @return {ol.geom.Geometry|undefined} The default geometry for the feature.
 * @api stable
 * @observable
 */
ol.Feature.prototype.getGeometry = function() {
  return /** @type {ol.geom.Geometry|undefined} */ (
      this.get(this.geometryName_));
};


/**
 * Get the feature identifier.  This is a stable identifier for the feature and
 * is either set when reading data from a remote source or set explicitly by
 * calling {@link ol.Feature#setId}.
 * @return {number|string|undefined} Id.
 * @api stable
 * @observable
 */
ol.Feature.prototype.getId = function() {
  return this.id_;
};


/**
 * Get the name of the feature's default geometry.  By default, the default
 * geometry is named `geometry`.
 * @return {string} Get the property name associated with the default geometry
 *     for this feature.
 * @api stable
 */
ol.Feature.prototype.getGeometryName = function() {
  return this.geometryName_;
};


/**
 * Get the feature's style.  This return for this method depends on what was
 * provided to the {@link ol.Feature#setStyle} method.
 * @return {ol.style.Style|Array.<ol.style.Style>|
 *     ol.FeatureStyleFunction} The feature style.
 * @api stable
 * @observable
 */
ol.Feature.prototype.getStyle = function() {
  return this.style_;
};


/**
 * Get the feature's style function.
 * @return {ol.FeatureStyleFunction|undefined} Return a function
 * representing the current style of this feature.
 * @api stable
 */
ol.Feature.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};


/**
 * @private
 */
ol.Feature.prototype.handleGeometryChange_ = function() {
  this.changed();
};


/**
 * @private
 */
ol.Feature.prototype.handleGeometryChanged_ = function() {
  if (this.geometryChangeKey_) {
    ol.events.unlistenByKey(this.geometryChangeKey_);
    this.geometryChangeKey_ = null;
  }
  var geometry = this.getGeometry();
  if (geometry) {
    this.geometryChangeKey_ = ol.events.listen(geometry,
        ol.events.EventType.CHANGE, this.handleGeometryChange_, this);
  }
  this.changed();
};


/**
 * Set the default geometry for the feature.  This will update the property
 * with the name returned by {@link ol.Feature#getGeometryName}.
 * @param {ol.geom.Geometry|undefined} geometry The new geometry.
 * @api stable
 * @observable
 */
ol.Feature.prototype.setGeometry = function(geometry) {
  this.set(this.geometryName_, geometry);
};


/**
 * Set the style for the feature.  This can be a single style object, an array
 * of styles, or a function that takes a resolution and returns an array of
 * styles. If it is `null` the feature has no style (a `null` style).
 * @param {ol.style.Style|Array.<ol.style.Style>|
 *     ol.FeatureStyleFunction} style Style for this feature.
 * @api stable
 * @observable
 */
ol.Feature.prototype.setStyle = function(style) {
  this.style_ = style;
  this.styleFunction_ = !style ?
      undefined : ol.Feature.createStyleFunction(style);
  this.changed();
};


/**
 * Set the feature id.  The feature id is considered stable and may be used when
 * requesting features or comparing identifiers returned from a remote source.
 * The feature id can be used with the {@link ol.source.Vector#getFeatureById}
 * method.
 * @param {number|string|undefined} id The feature id.
 * @api stable
 * @observable
 */
ol.Feature.prototype.setId = function(id) {
  this.id_ = id;
  this.changed();
};


/**
 * Set the property name to be used when getting the feature's default geometry.
 * When calling {@link ol.Feature#getGeometry}, the value of the property with
 * this name will be returned.
 * @param {string} name The property name of the default geometry.
 * @api stable
 */
ol.Feature.prototype.setGeometryName = function(name) {
  ol.events.unlisten(
      this, ol.Object.getChangeEventType(this.geometryName_),
      this.handleGeometryChanged_, this);
  this.geometryName_ = name;
  ol.events.listen(
      this, ol.Object.getChangeEventType(this.geometryName_),
      this.handleGeometryChanged_, this);
  this.handleGeometryChanged_();
};


/**
 * Convert the provided object into a feature style function.  Functions passed
 * through unchanged.  Arrays of ol.style.Style or single style objects wrapped
 * in a new feature style function.
 * @param {ol.FeatureStyleFunction|!Array.<ol.style.Style>|!ol.style.Style} obj
 *     A feature style function, a single style, or an array of styles.
 * @return {ol.FeatureStyleFunction} A style function.
 */
ol.Feature.createStyleFunction = function(obj) {
  var styleFunction;

  if (typeof obj === 'function') {
    styleFunction = obj;
  } else {
    /**
     * @type {Array.<ol.style.Style>}
     */
    var styles;
    if (Array.isArray(obj)) {
      styles = obj;
    } else {
      ol.asserts.assert(obj instanceof ol.style.Style,
          41); // Expected an `ol.style.Style` or an array of `ol.style.Style`
      styles = [obj];
    }
    styleFunction = function() {
      return styles;
    };
  }
  return styleFunction;
};

goog.provide('ol.format.FormatType');


/**
 * @enum {string}
 */
ol.format.FormatType = {
  ARRAY_BUFFER: 'arraybuffer',
  JSON: 'json',
  TEXT: 'text',
  XML: 'xml'
};

goog.provide('ol.xml');

goog.require('ol');
goog.require('ol.array');


/**
 * This document should be used when creating nodes for XML serializations. This
 * document is also used by {@link ol.xml.createElementNS} and
 * {@link ol.xml.setAttributeNS}
 * @const
 * @type {Document}
 */
ol.xml.DOCUMENT = document.implementation.createDocument('', '', null);


/**
 * @param {string} namespaceURI Namespace URI.
 * @param {string} qualifiedName Qualified name.
 * @return {Node} Node.
 */
ol.xml.createElementNS = function(namespaceURI, qualifiedName) {
  return ol.xml.DOCUMENT.createElementNS(namespaceURI, qualifiedName);
};


/**
 * Recursively grab all text content of child nodes into a single string.
 * @param {Node} node Node.
 * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
 * breaks.
 * @return {string} All text content.
 * @api
 */
ol.xml.getAllTextContent = function(node, normalizeWhitespace) {
  return ol.xml.getAllTextContent_(node, normalizeWhitespace, []).join('');
};


/**
 * Recursively grab all text content of child nodes into a single string.
 * @param {Node} node Node.
 * @param {boolean} normalizeWhitespace Normalize whitespace: remove all line
 * breaks.
 * @param {Array.<string>} accumulator Accumulator.
 * @private
 * @return {Array.<string>} Accumulator.
 */
ol.xml.getAllTextContent_ = function(node, normalizeWhitespace, accumulator) {
  if (node.nodeType == Node.CDATA_SECTION_NODE ||
      node.nodeType == Node.TEXT_NODE) {
    if (normalizeWhitespace) {
      accumulator.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
    } else {
      accumulator.push(node.nodeValue);
    }
  } else {
    var n;
    for (n = node.firstChild; n; n = n.nextSibling) {
      ol.xml.getAllTextContent_(n, normalizeWhitespace, accumulator);
    }
  }
  return accumulator;
};


/**
 * @param {?} value Value.
 * @return {boolean} Is document.
 */
ol.xml.isDocument = function(value) {
  return value instanceof Document;
};


/**
 * @param {?} value Value.
 * @return {boolean} Is node.
 */
ol.xml.isNode = function(value) {
  return value instanceof Node;
};


/**
 * @param {Node} node Node.
 * @param {?string} namespaceURI Namespace URI.
 * @param {string} name Attribute name.
 * @return {string} Value
 */
ol.xml.getAttributeNS = function(node, namespaceURI, name) {
  return node.getAttributeNS(namespaceURI, name) || '';
};


/**
 * @param {Node} node Node.
 * @param {?string} namespaceURI Namespace URI.
 * @param {string} name Attribute name.
 * @param {string|number} value Value.
 */
ol.xml.setAttributeNS = function(node, namespaceURI, name, value) {
  node.setAttributeNS(namespaceURI, name, value);
};


/**
 * Parse an XML string to an XML Document.
 * @param {string} xml XML.
 * @return {Document} Document.
 * @api
 */
ol.xml.parse = function(xml) {
  return new DOMParser().parseFromString(xml, 'application/xml');
};


/**
 * Make an array extender function for extending the array at the top of the
 * object stack.
 * @param {function(this: T, Node, Array.<*>): (Array.<*>|undefined)}
 *     valueReader Value reader.
 * @param {T=} opt_this The object to use as `this` in `valueReader`.
 * @return {ol.XmlParser} Parser.
 * @template T
 */
ol.xml.makeArrayExtender = function(valueReader, opt_this) {
  return (
      /**
       * @param {Node} node Node.
       * @param {Array.<*>} objectStack Object stack.
       */
      function(node, objectStack) {
        var value = valueReader.call(opt_this, node, objectStack);
        if (value !== undefined) {
          goog.DEBUG && console.assert(Array.isArray(value),
              'valueReader function is expected to return an array of values');
          var array = /** @type {Array.<*>} */
              (objectStack[objectStack.length - 1]);
          goog.DEBUG && console.assert(Array.isArray(array),
              'objectStack is supposed to be an array of arrays');
          ol.array.extend(array, value);
        }
      });
};


/**
 * Make an array pusher function for pushing to the array at the top of the
 * object stack.
 * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
 * @param {T=} opt_this The object to use as `this` in `valueReader`.
 * @return {ol.XmlParser} Parser.
 * @template T
 */
ol.xml.makeArrayPusher = function(valueReader, opt_this) {
  return (
      /**
       * @param {Node} node Node.
       * @param {Array.<*>} objectStack Object stack.
       */
      function(node, objectStack) {
        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
            node, objectStack);
        if (value !== undefined) {
          var array = objectStack[objectStack.length - 1];
          goog.DEBUG && console.assert(Array.isArray(array),
              'objectStack is supposed to be an array of arrays');
          array.push(value);
        }
      });
};


/**
 * Make an object stack replacer function for replacing the object at the
 * top of the stack.
 * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
 * @param {T=} opt_this The object to use as `this` in `valueReader`.
 * @return {ol.XmlParser} Parser.
 * @template T
 */
ol.xml.makeReplacer = function(valueReader, opt_this) {
  return (
      /**
       * @param {Node} node Node.
       * @param {Array.<*>} objectStack Object stack.
       */
      function(node, objectStack) {
        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
            node, objectStack);
        if (value !== undefined) {
          objectStack[objectStack.length - 1] = value;
        }
      });
};


/**
 * Make an object property pusher function for adding a property to the
 * object at the top of the stack.
 * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
 * @param {string=} opt_property Property.
 * @param {T=} opt_this The object to use as `this` in `valueReader`.
 * @return {ol.XmlParser} Parser.
 * @template T
 */
ol.xml.makeObjectPropertyPusher = function(valueReader, opt_property, opt_this) {
  goog.DEBUG && console.assert(valueReader !== undefined,
      'undefined valueReader, expected function(this: T, Node, Array.<*>)');
  return (
      /**
       * @param {Node} node Node.
       * @param {Array.<*>} objectStack Object stack.
       */
      function(node, objectStack) {
        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
            node, objectStack);
        if (value !== undefined) {
          var object = /** @type {Object} */
              (objectStack[objectStack.length - 1]);
          var property = opt_property !== undefined ?
              opt_property : node.localName;
          var array;
          if (property in object) {
            array = object[property];
          } else {
            array = object[property] = [];
          }
          array.push(value);
        }
      });
};


/**
 * Make an object property setter function.
 * @param {function(this: T, Node, Array.<*>): *} valueReader Value reader.
 * @param {string=} opt_property Property.
 * @param {T=} opt_this The object to use as `this` in `valueReader`.
 * @return {ol.XmlParser} Parser.
 * @template T
 */
ol.xml.makeObjectPropertySetter = function(valueReader, opt_property, opt_this) {
  goog.DEBUG && console.assert(valueReader !== undefined,
      'undefined valueReader, expected function(this: T, Node, Array.<*>)');
  return (
      /**
       * @param {Node} node Node.
       * @param {Array.<*>} objectStack Object stack.
       */
      function(node, objectStack) {
        var value = valueReader.call(opt_this !== undefined ? opt_this : this,
            node, objectStack);
        if (value !== undefined) {
          var object = /** @type {Object} */
              (objectStack[objectStack.length - 1]);
          var property = opt_property !== undefined ?
              opt_property : node.localName;
          object[property] = value;
        }
      });
};


/**
 * Create a serializer that appends nodes written by its `nodeWriter` to its
 * designated parent. The parent is the `node` of the
 * {@link ol.XmlNodeStackItem} at the top of the `objectStack`.
 * @param {function(this: T, Node, V, Array.<*>)}
 *     nodeWriter Node writer.
 * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
 * @return {ol.XmlSerializer} Serializer.
 * @template T, V
 */
ol.xml.makeChildAppender = function(nodeWriter, opt_this) {
  return function(node, value, objectStack) {
    nodeWriter.call(opt_this !== undefined ? opt_this : this,
        node, value, objectStack);
    var parent = objectStack[objectStack.length - 1];
    var parentNode = parent.node;
    goog.DEBUG && console.assert(ol.xml.isNode(parentNode) ||
        ol.xml.isDocument(parentNode),
        'expected parentNode %s to be a Node or a Document', parentNode);
    parentNode.appendChild(node);
  };
};


/**
 * Create a serializer that calls the provided `nodeWriter` from
 * {@link ol.xml.serialize}. This can be used by the parent writer to have the
 * 'nodeWriter' called with an array of values when the `nodeWriter` was
 * designed to serialize a single item. An example would be a LineString
 * geometry writer, which could be reused for writing MultiLineString
 * geometries.
 * @param {function(this: T, Node, V, Array.<*>)}
 *     nodeWriter Node writer.
 * @param {T=} opt_this The object to use as `this` in `nodeWriter`.
 * @return {ol.XmlSerializer} Serializer.
 * @template T, V
 */
ol.xml.makeArraySerializer = function(nodeWriter, opt_this) {
  var serializersNS, nodeFactory;
  return function(node, value, objectStack) {
    if (serializersNS === undefined) {
      serializersNS = {};
      var serializers = {};
      serializers[node.localName] = nodeWriter;
      serializersNS[node.namespaceURI] = serializers;
      nodeFactory = ol.xml.makeSimpleNodeFactory(node.localName);
    }
    ol.xml.serialize(serializersNS, nodeFactory, value, objectStack);
  };
};


/**
 * Create a node factory which can use the `opt_keys` passed to
 * {@link ol.xml.serialize} or {@link ol.xml.pushSerializeAndPop} as node names,
 * or a fixed node name. The namespace of the created nodes can either be fixed,
 * or the parent namespace will be used.
 * @param {string=} opt_nodeName Fixed node name which will be used for all
 *     created nodes. If not provided, the 3rd argument to the resulting node
 *     factory needs to be provided and will be the nodeName.
 * @param {string=} opt_namespaceURI Fixed namespace URI which will be used for
 *     all created nodes. If not provided, the namespace of the parent node will
 *     be used.
 * @return {function(*, Array.<*>, string=): (Node|undefined)} Node factory.
 */
ol.xml.makeSimpleNodeFactory = function(opt_nodeName, opt_namespaceURI) {
  var fixedNodeName = opt_nodeName;
  return (
      /**
       * @param {*} value Value.
       * @param {Array.<*>} objectStack Object stack.
       * @param {string=} opt_nodeName Node name.
       * @return {Node} Node.
       */
      function(value, objectStack, opt_nodeName) {
        var context = objectStack[objectStack.length - 1];
        var node = context.node;
        goog.DEBUG && console.assert(ol.xml.isNode(node) || ol.xml.isDocument(node),
            'expected node %s to be a Node or a Document', node);
        var nodeName = fixedNodeName;
        if (nodeName === undefined) {
          nodeName = opt_nodeName;
        }
        var namespaceURI = opt_namespaceURI;
        if (opt_namespaceURI === undefined) {
          namespaceURI = node.namespaceURI;
        }
        goog.DEBUG && console.assert(nodeName !== undefined, 'nodeName was undefined');
        return ol.xml.createElementNS(namespaceURI, /** @type {string} */ (nodeName));
      }
  );
};


/**
 * A node factory that creates a node using the parent's `namespaceURI` and the
 * `nodeName` passed by {@link ol.xml.serialize} or
 * {@link ol.xml.pushSerializeAndPop} to the node factory.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 */
ol.xml.OBJECT_PROPERTY_NODE_FACTORY = ol.xml.makeSimpleNodeFactory();


/**
 * Create an array of `values` to be used with {@link ol.xml.serialize} or
 * {@link ol.xml.pushSerializeAndPop}, where `orderedKeys` has to be provided as
 * `opt_key` argument.
 * @param {Object.<string, V>} object Key-value pairs for the sequence. Keys can
 *     be a subset of the `orderedKeys`.
 * @param {Array.<string>} orderedKeys Keys in the order of the sequence.
 * @return {Array.<V>} Values in the order of the sequence. The resulting array
 *     has the same length as the `orderedKeys` array. Values that are not
 *     present in `object` will be `undefined` in the resulting array.
 * @template V
 */
ol.xml.makeSequence = function(object, orderedKeys) {
  var length = orderedKeys.length;
  var sequence = new Array(length);
  for (var i = 0; i < length; ++i) {
    sequence[i] = object[orderedKeys[i]];
  }
  return sequence;
};


/**
 * Create a namespaced structure, using the same values for each namespace.
 * This can be used as a starting point for versioned parsers, when only a few
 * values are version specific.
 * @param {Array.<string>} namespaceURIs Namespace URIs.
 * @param {T} structure Structure.
 * @param {Object.<string, T>=} opt_structureNS Namespaced structure to add to.
 * @return {Object.<string, T>} Namespaced structure.
 * @template T
 */
ol.xml.makeStructureNS = function(namespaceURIs, structure, opt_structureNS) {
  /**
   * @type {Object.<string, *>}
   */
  var structureNS = opt_structureNS !== undefined ? opt_structureNS : {};
  var i, ii;
  for (i = 0, ii = namespaceURIs.length; i < ii; ++i) {
    structureNS[namespaceURIs[i]] = structure;
  }
  return structureNS;
};


/**
 * Parse a node using the parsers and object stack.
 * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS
 *     Parsers by namespace.
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @param {*=} opt_this The object to use as `this`.
 */
ol.xml.parseNode = function(parsersNS, node, objectStack, opt_this) {
  var n;
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    var parsers = parsersNS[n.namespaceURI];
    if (parsers !== undefined) {
      var parser = parsers[n.localName];
      if (parser !== undefined) {
        parser.call(opt_this, n, objectStack);
      }
    }
  }
};


/**
 * Push an object on top of the stack, parse and return the popped object.
 * @param {T} object Object.
 * @param {Object.<string, Object.<string, ol.XmlParser>>} parsersNS
 *     Parsers by namespace.
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @param {*=} opt_this The object to use as `this`.
 * @return {T} Object.
 * @template T
 */
ol.xml.pushParseAndPop = function(
    object, parsersNS, node, objectStack, opt_this) {
  objectStack.push(object);
  ol.xml.parseNode(parsersNS, node, objectStack, opt_this);
  return objectStack.pop();
};


/**
 * Walk through an array of `values` and call a serializer for each value.
 * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS
 *     Namespaced serializers.
 * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
 *     Node factory. The `nodeFactory` creates the node whose namespace and name
 *     will be used to choose a node writer from `serializersNS`. This
 *     separation allows us to decide what kind of node to create, depending on
 *     the value we want to serialize. An example for this would be different
 *     geometry writers based on the geometry type.
 * @param {Array.<*>} values Values to serialize. An example would be an array
 *     of {@link ol.Feature} instances.
 * @param {Array.<*>} objectStack Node stack.
 * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
 *     `nodeFactory`. This is used for serializing object literals where the
 *     node name relates to the property key. The array length of `opt_keys` has
 *     to match the length of `values`. For serializing a sequence, `opt_keys`
 *     determines the order of the sequence.
 * @param {T=} opt_this The object to use as `this` for the node factory and
 *     serializers.
 * @template T
 */
ol.xml.serialize = function(
    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
  var length = (opt_keys !== undefined ? opt_keys : values).length;
  var value, node;
  for (var i = 0; i < length; ++i) {
    value = values[i];
    if (value !== undefined) {
      node = nodeFactory.call(opt_this, value, objectStack,
          opt_keys !== undefined ? opt_keys[i] : undefined);
      if (node !== undefined) {
        serializersNS[node.namespaceURI][node.localName]
            .call(opt_this, node, value, objectStack);
      }
    }
  }
};


/**
 * @param {O} object Object.
 * @param {Object.<string, Object.<string, ol.XmlSerializer>>} serializersNS
 *     Namespaced serializers.
 * @param {function(this: T, *, Array.<*>, (string|undefined)): (Node|undefined)} nodeFactory
 *     Node factory. The `nodeFactory` creates the node whose namespace and name
 *     will be used to choose a node writer from `serializersNS`. This
 *     separation allows us to decide what kind of node to create, depending on
 *     the value we want to serialize. An example for this would be different
 *     geometry writers based on the geometry type.
 * @param {Array.<*>} values Values to serialize. An example would be an array
 *     of {@link ol.Feature} instances.
 * @param {Array.<*>} objectStack Node stack.
 * @param {Array.<string>=} opt_keys Keys of the `values`. Will be passed to the
 *     `nodeFactory`. This is used for serializing object literals where the
 *     node name relates to the property key. The array length of `opt_keys` has
 *     to match the length of `values`. For serializing a sequence, `opt_keys`
 *     determines the order of the sequence.
 * @param {T=} opt_this The object to use as `this` for the node factory and
 *     serializers.
 * @return {O|undefined} Object.
 * @template O, T
 */
ol.xml.pushSerializeAndPop = function(object,
    serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this) {
  objectStack.push(object);
  ol.xml.serialize(
      serializersNS, nodeFactory, values, objectStack, opt_keys, opt_this);
  return objectStack.pop();
};

goog.provide('ol.featureloader');

goog.require('ol.Tile');
goog.require('ol.format.FormatType');
goog.require('ol.xml');


/**
 * @param {string|ol.FeatureUrlFunction} url Feature URL service.
 * @param {ol.format.Feature} format Feature format.
 * @param {function(this:ol.VectorTile, Array.<ol.Feature>, ol.proj.Projection)|function(this:ol.source.Vector, Array.<ol.Feature>)} success
 *     Function called with the loaded features and optionally with the data
 *     projection. Called with the vector tile or source as `this`.
 * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure
 *     Function called when loading failed. Called with the vector tile or
 *     source as `this`.
 * @return {ol.FeatureLoader} The feature loader.
 */
ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) {
  return (
      /**
       * @param {ol.Extent} extent Extent.
       * @param {number} resolution Resolution.
       * @param {ol.proj.Projection} projection Projection.
       * @this {ol.source.Vector|ol.VectorTile}
       */
      function(extent, resolution, projection) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET',
            typeof url === 'function' ? url(extent, resolution, projection) : url,
            true);
        if (format.getType() == ol.format.FormatType.ARRAY_BUFFER) {
          xhr.responseType = 'arraybuffer';
        }
        /**
         * @param {Event} event Event.
         * @private
         */
        xhr.onload = function(event) {
          // status will be 0 for file:// urls
          if (!xhr.status || xhr.status >= 200 && xhr.status < 300) {
            var type = format.getType();
            /** @type {Document|Node|Object|string|undefined} */
            var source;
            if (type == ol.format.FormatType.JSON ||
                type == ol.format.FormatType.TEXT) {
              source = xhr.responseText;
            } else if (type == ol.format.FormatType.XML) {
              source = xhr.responseXML;
              if (!source) {
                source = ol.xml.parse(xhr.responseText);
              }
            } else if (type == ol.format.FormatType.ARRAY_BUFFER) {
              source = /** @type {ArrayBuffer} */ (xhr.response);
            }
            if (source) {
              success.call(this, format.readFeatures(source,
                  {featureProjection: projection}),
                  format.readProjection(source));
            } else {
              failure.call(this);
            }
          } else {
            failure.call(this);
          }
        }.bind(this);
        xhr.send();
      });
};


/**
 * Create an XHR feature loader for a `url` and `format`. The feature loader
 * loads features (with XHR), parses the features, and adds them to the
 * vector tile.
 * @param {string|ol.FeatureUrlFunction} url Feature URL service.
 * @param {ol.format.Feature} format Feature format.
 * @return {ol.FeatureLoader} The feature loader.
 * @api
 */
ol.featureloader.tile = function(url, format) {
  return ol.featureloader.loadFeaturesXhr(url, format,
      /**
       * @param {Array.<ol.Feature>} features The loaded features.
       * @param {ol.proj.Projection} dataProjection Data projection.
       * @this {ol.VectorTile}
       */
      function(features, dataProjection) {
        this.setProjection(dataProjection);
        this.setFeatures(features);
      },
      /**
       * @this {ol.VectorTile}
       */
      function() {
        this.setState(ol.Tile.State.ERROR);
      });
};


/**
 * Create an XHR feature loader for a `url` and `format`. The feature loader
 * loads features (with XHR), parses the features, and adds them to the
 * vector source.
 * @param {string|ol.FeatureUrlFunction} url Feature URL service.
 * @param {ol.format.Feature} format Feature format.
 * @return {ol.FeatureLoader} The feature loader.
 * @api
 */
ol.featureloader.xhr = function(url, format) {
  return ol.featureloader.loadFeaturesXhr(url, format,
      /**
       * @param {Array.<ol.Feature>} features The loaded features.
       * @param {ol.proj.Projection} dataProjection Data projection.
       * @this {ol.source.Vector}
       */
      function(features, dataProjection) {
        this.addFeatures(features);
      }, /* FIXME handle error */ ol.nullFunction);
};

goog.provide('ol.format.Feature');

goog.require('ol.geom.Geometry');
goog.require('ol.proj');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for feature formats.
 * {ol.format.Feature} subclasses provide the ability to decode and encode
 * {@link ol.Feature} objects from a variety of commonly used geospatial
 * file formats.  See the documentation for each format for more details.
 *
 * @constructor
 * @api stable
 */
ol.format.Feature = function() {

  /**
   * @protected
   * @type {ol.proj.Projection}
   */
  this.defaultDataProjection = null;

};


/**
 * @abstract
 * @return {Array.<string>} Extensions.
 */
ol.format.Feature.prototype.getExtensions = function() {};


/**
 * Adds the data projection to the read options.
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @return {olx.format.ReadOptions|undefined} Options.
 * @protected
 */
ol.format.Feature.prototype.getReadOptions = function(source, opt_options) {
  var options;
  if (opt_options) {
    options = {
      dataProjection: opt_options.dataProjection ?
          opt_options.dataProjection : this.readProjection(source),
      featureProjection: opt_options.featureProjection
    };
  }
  return this.adaptOptions(options);
};


/**
 * Sets the `defaultDataProjection` on the options, if no `dataProjection`
 * is set.
 * @param {olx.format.WriteOptions|olx.format.ReadOptions|undefined} options
 *     Options.
 * @protected
 * @return {olx.format.WriteOptions|olx.format.ReadOptions|undefined}
 *     Updated options.
 */
ol.format.Feature.prototype.adaptOptions = function(options) {
  var updatedOptions;
  if (options) {
    updatedOptions = {
      featureProjection: options.featureProjection,
      dataProjection: options.dataProjection ?
          options.dataProjection : this.defaultDataProjection,
      rightHanded: options.rightHanded
    };
    if (options.decimals) {
      updatedOptions.decimals = options.decimals;
    }
  }
  return updatedOptions;
};


/**
 * @abstract
 * @return {ol.format.FormatType} Format.
 */
ol.format.Feature.prototype.getType = function() {};


/**
 * Read a single feature from a source.
 *
 * @abstract
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 */
ol.format.Feature.prototype.readFeature = function(source, opt_options) {};


/**
 * Read all features from a source.
 *
 * @abstract
 * @param {Document|Node|ArrayBuffer|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 */
ol.format.Feature.prototype.readFeatures = function(source, opt_options) {};


/**
 * Read a single geometry from a source.
 *
 * @abstract
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.Feature.prototype.readGeometry = function(source, opt_options) {};


/**
 * Read the projection from a source.
 *
 * @abstract
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 */
ol.format.Feature.prototype.readProjection = function(source) {};


/**
 * Encode a feature in this format.
 *
 * @abstract
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} Result.
 */
ol.format.Feature.prototype.writeFeature = function(feature, opt_options) {};


/**
 * Encode an array of features in this format.
 *
 * @abstract
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} Result.
 */
ol.format.Feature.prototype.writeFeatures = function(features, opt_options) {};


/**
 * Write a single geometry in this format.
 *
 * @abstract
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} Result.
 */
ol.format.Feature.prototype.writeGeometry = function(geometry, opt_options) {};


/**
 * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
 * @param {boolean} write Set to true for writing, false for reading.
 * @param {(olx.format.WriteOptions|olx.format.ReadOptions)=} opt_options
 *     Options.
 * @return {ol.geom.Geometry|ol.Extent} Transformed geometry.
 * @protected
 */
ol.format.Feature.transformWithOptions = function(
    geometry, write, opt_options) {
  var featureProjection = opt_options ?
      ol.proj.get(opt_options.featureProjection) : null;
  var dataProjection = opt_options ?
      ol.proj.get(opt_options.dataProjection) : null;
  /**
   * @type {ol.geom.Geometry|ol.Extent}
   */
  var transformed;
  if (featureProjection && dataProjection &&
      !ol.proj.equivalent(featureProjection, dataProjection)) {
    if (geometry instanceof ol.geom.Geometry) {
      transformed = (write ? geometry.clone() : geometry).transform(
          write ? featureProjection : dataProjection,
          write ? dataProjection : featureProjection);
    } else {
      // FIXME this is necessary because ol.format.GML treats extents
      // as geometries
      transformed = ol.proj.transformExtent(
          write ? geometry.slice() : geometry,
          write ? featureProjection : dataProjection,
          write ? dataProjection : featureProjection);
    }
  } else {
    transformed = geometry;
  }
  if (write && opt_options && opt_options.decimals) {
    var power = Math.pow(10, opt_options.decimals);
    // if decimals option on write, round each coordinate appropriately
    /**
     * @param {Array.<number>} coordinates Coordinates.
     * @return {Array.<number>} Transformed coordinates.
     */
    var transform = function(coordinates) {
      for (var i = 0, ii = coordinates.length; i < ii; ++i) {
        coordinates[i] = Math.round(coordinates[i] * power) / power;
      }
      return coordinates;
    };
    if (Array.isArray(transformed)) {
      transform(transformed);
    } else {
      transformed.applyTransform(transform);
    }
  }
  return transformed;
};

goog.provide('ol.format.JSONFeature');

goog.require('ol');
goog.require('ol.format.Feature');
goog.require('ol.format.FormatType');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for JSON feature formats.
 *
 * @constructor
 * @extends {ol.format.Feature}
 */
ol.format.JSONFeature = function() {
  ol.format.Feature.call(this);
};
ol.inherits(ol.format.JSONFeature, ol.format.Feature);


/**
 * @param {Document|Node|Object|string} source Source.
 * @private
 * @return {Object} Object.
 */
ol.format.JSONFeature.prototype.getObject_ = function(source) {
  if (typeof source === 'string') {
    var object = JSON.parse(source);
    return object ? /** @type {Object} */ (object) : null;
  } else if (source !== null) {
    return source;
  } else {
    return null;
  }
};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.getType = function() {
  return ol.format.FormatType.JSON;
};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.readFeature = function(source, opt_options) {
  return this.readFeatureFromObject(
      this.getObject_(source), this.getReadOptions(source, opt_options));
};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.readFeatures = function(source, opt_options) {
  return this.readFeaturesFromObject(
      this.getObject_(source), this.getReadOptions(source, opt_options));
};


/**
 * @abstract
 * @param {Object} object Object.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @protected
 * @return {ol.Feature} Feature.
 */
ol.format.JSONFeature.prototype.readFeatureFromObject = function(object, opt_options) {};


/**
 * @abstract
 * @param {Object} object Object.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @protected
 * @return {Array.<ol.Feature>} Features.
 */
ol.format.JSONFeature.prototype.readFeaturesFromObject = function(object, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.readGeometry = function(source, opt_options) {
  return this.readGeometryFromObject(
      this.getObject_(source), this.getReadOptions(source, opt_options));
};


/**
 * @abstract
 * @param {Object} object Object.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @protected
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.JSONFeature.prototype.readGeometryFromObject = function(object, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.readProjection = function(source) {
  return this.readProjectionFromObject(this.getObject_(source));
};


/**
 * @abstract
 * @param {Object} object Object.
 * @protected
 * @return {ol.proj.Projection} Projection.
 */
ol.format.JSONFeature.prototype.readProjectionFromObject = function(object) {};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.writeFeature = function(feature, opt_options) {
  return JSON.stringify(this.writeFeatureObject(feature, opt_options));
};


/**
 * @abstract
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {Object} Object.
 */
ol.format.JSONFeature.prototype.writeFeatureObject = function(feature, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.writeFeatures = function(features, opt_options) {
  return JSON.stringify(this.writeFeaturesObject(features, opt_options));
};


/**
 * @abstract
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {Object} Object.
 */
ol.format.JSONFeature.prototype.writeFeaturesObject = function(features, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.JSONFeature.prototype.writeGeometry = function(geometry, opt_options) {
  return JSON.stringify(this.writeGeometryObject(geometry, opt_options));
};


/**
 * @abstract
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {Object} Object.
 */
ol.format.JSONFeature.prototype.writeGeometryObject = function(geometry, opt_options) {};

goog.provide('ol.geom.flat.interpolate');

goog.require('ol.array');
goog.require('ol.math');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} fraction Fraction.
 * @param {Array.<number>=} opt_dest Destination.
 * @return {Array.<number>} Destination.
 */
ol.geom.flat.interpolate.lineString = function(flatCoordinates, offset, end, stride, fraction, opt_dest) {
  // FIXME does not work when vertices are repeated
  // FIXME interpolate extra dimensions
  goog.DEBUG && console.assert(0 <= fraction && fraction <= 1,
      'fraction should be in between 0 and 1');
  var pointX = NaN;
  var pointY = NaN;
  var n = (end - offset) / stride;
  if (n === 0) {
    goog.DEBUG && console.assert(false, 'n cannot be 0');
  } else if (n == 1) {
    pointX = flatCoordinates[offset];
    pointY = flatCoordinates[offset + 1];
  } else if (n == 2) {
    pointX = (1 - fraction) * flatCoordinates[offset] +
        fraction * flatCoordinates[offset + stride];
    pointY = (1 - fraction) * flatCoordinates[offset + 1] +
        fraction * flatCoordinates[offset + stride + 1];
  } else {
    var x1 = flatCoordinates[offset];
    var y1 = flatCoordinates[offset + 1];
    var length = 0;
    var cumulativeLengths = [0];
    var i;
    for (i = offset + stride; i < end; i += stride) {
      var x2 = flatCoordinates[i];
      var y2 = flatCoordinates[i + 1];
      length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
      cumulativeLengths.push(length);
      x1 = x2;
      y1 = y2;
    }
    var target = fraction * length;
    var index = ol.array.binarySearch(cumulativeLengths, target);
    if (index < 0) {
      var t = (target - cumulativeLengths[-index - 2]) /
          (cumulativeLengths[-index - 1] - cumulativeLengths[-index - 2]);
      var o = offset + (-index - 2) * stride;
      pointX = ol.math.lerp(
          flatCoordinates[o], flatCoordinates[o + stride], t);
      pointY = ol.math.lerp(
          flatCoordinates[o + 1], flatCoordinates[o + stride + 1], t);
    } else {
      pointX = flatCoordinates[offset + index * stride];
      pointY = flatCoordinates[offset + index * stride + 1];
    }
  }
  if (opt_dest) {
    opt_dest[0] = pointX;
    opt_dest[1] = pointY;
    return opt_dest;
  } else {
    return [pointX, pointY];
  }
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {number} m M.
 * @param {boolean} extrapolate Extrapolate.
 * @return {ol.Coordinate} Coordinate.
 */
ol.geom.flat.lineStringCoordinateAtM = function(flatCoordinates, offset, end, stride, m, extrapolate) {
  if (end == offset) {
    return null;
  }
  var coordinate;
  if (m < flatCoordinates[offset + stride - 1]) {
    if (extrapolate) {
      coordinate = flatCoordinates.slice(offset, offset + stride);
      coordinate[stride - 1] = m;
      return coordinate;
    } else {
      return null;
    }
  } else if (flatCoordinates[end - 1] < m) {
    if (extrapolate) {
      coordinate = flatCoordinates.slice(end - stride, end);
      coordinate[stride - 1] = m;
      return coordinate;
    } else {
      return null;
    }
  }
  // FIXME use O(1) search
  if (m == flatCoordinates[offset + stride - 1]) {
    return flatCoordinates.slice(offset, offset + stride);
  }
  var lo = offset / stride;
  var hi = end / stride;
  while (lo < hi) {
    var mid = (lo + hi) >> 1;
    if (m < flatCoordinates[(mid + 1) * stride - 1]) {
      hi = mid;
    } else {
      lo = mid + 1;
    }
  }
  var m0 = flatCoordinates[lo * stride - 1];
  if (m == m0) {
    return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride);
  }
  var m1 = flatCoordinates[(lo + 1) * stride - 1];
  goog.DEBUG && console.assert(m0 < m, 'm0 should be less than m');
  goog.DEBUG && console.assert(m <= m1, 'm should be less than or equal to m1');
  var t = (m - m0) / (m1 - m0);
  coordinate = [];
  var i;
  for (i = 0; i < stride - 1; ++i) {
    coordinate.push(ol.math.lerp(flatCoordinates[(lo - 1) * stride + i],
        flatCoordinates[lo * stride + i], t));
  }
  coordinate.push(m);
  goog.DEBUG && console.assert(coordinate.length == stride,
      'length of coordinate array should match stride');
  return coordinate;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<number>} ends Ends.
 * @param {number} stride Stride.
 * @param {number} m M.
 * @param {boolean} extrapolate Extrapolate.
 * @param {boolean} interpolate Interpolate.
 * @return {ol.Coordinate} Coordinate.
 */
ol.geom.flat.lineStringsCoordinateAtM = function(
    flatCoordinates, offset, ends, stride, m, extrapolate, interpolate) {
  if (interpolate) {
    return ol.geom.flat.lineStringCoordinateAtM(
        flatCoordinates, offset, ends[ends.length - 1], stride, m, extrapolate);
  }
  var coordinate;
  if (m < flatCoordinates[stride - 1]) {
    if (extrapolate) {
      coordinate = flatCoordinates.slice(0, stride);
      coordinate[stride - 1] = m;
      return coordinate;
    } else {
      return null;
    }
  }
  if (flatCoordinates[flatCoordinates.length - 1] < m) {
    if (extrapolate) {
      coordinate = flatCoordinates.slice(flatCoordinates.length - stride);
      coordinate[stride - 1] = m;
      return coordinate;
    } else {
      return null;
    }
  }
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    if (offset == end) {
      continue;
    }
    if (m < flatCoordinates[offset + stride - 1]) {
      return null;
    } else if (m <= flatCoordinates[end - 1]) {
      return ol.geom.flat.lineStringCoordinateAtM(
          flatCoordinates, offset, end, stride, m, false);
    }
    offset = end;
  }
  goog.DEBUG && console.assert(false,
      'ol.geom.flat.lineStringsCoordinateAtM should have returned');
  return null;
};

goog.provide('ol.geom.flat.length');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @return {number} Length.
 */
ol.geom.flat.length.lineString = function(flatCoordinates, offset, end, stride) {
  var x1 = flatCoordinates[offset];
  var y1 = flatCoordinates[offset + 1];
  var length = 0;
  var i;
  for (i = offset + stride; i < end; i += stride) {
    var x2 = flatCoordinates[i];
    var y2 = flatCoordinates[i + 1];
    length += Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
    x1 = x2;
    y1 = y2;
  }
  return length;
};


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @return {number} Perimeter.
 */
ol.geom.flat.length.linearRing = function(flatCoordinates, offset, end, stride) {
  var perimeter =
      ol.geom.flat.length.lineString(flatCoordinates, offset, end, stride);
  var dx = flatCoordinates[end - stride] - flatCoordinates[offset];
  var dy = flatCoordinates[end - stride + 1] - flatCoordinates[offset + 1];
  perimeter += Math.sqrt(dx * dx + dy * dy);
  return perimeter;
};

goog.provide('ol.geom.LineString');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.closest');
goog.require('ol.geom.flat.deflate');
goog.require('ol.geom.flat.inflate');
goog.require('ol.geom.flat.interpolate');
goog.require('ol.geom.flat.intersectsextent');
goog.require('ol.geom.flat.length');
goog.require('ol.geom.flat.segments');
goog.require('ol.geom.flat.simplify');


/**
 * @classdesc
 * Linestring geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.LineString = function(coordinates, opt_layout) {

  ol.geom.SimpleGeometry.call(this);

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.flatMidpoint_ = null;

  /**
   * @private
   * @type {number}
   */
  this.flatMidpointRevision_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.maxDelta_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.maxDeltaRevision_ = -1;

  this.setCoordinates(coordinates, opt_layout);

};
ol.inherits(ol.geom.LineString, ol.geom.SimpleGeometry);


/**
 * Append the passed coordinate to the coordinates of the linestring.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @api stable
 */
ol.geom.LineString.prototype.appendCoordinate = function(coordinate) {
  goog.DEBUG && console.assert(coordinate.length == this.stride,
      'length of coordinate array should match stride');
  if (!this.flatCoordinates) {
    this.flatCoordinates = coordinate.slice();
  } else {
    ol.array.extend(this.flatCoordinates, coordinate);
  }
  this.changed();
};


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.LineString} Clone.
 * @api stable
 */
ol.geom.LineString.prototype.clone = function() {
  var lineString = new ol.geom.LineString(null);
  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
  return lineString;
};


/**
 * @inheritDoc
 */
ol.geom.LineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  if (this.maxDeltaRevision_ != this.getRevision()) {
    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getMaxSquaredDelta(
        this.flatCoordinates, 0, this.flatCoordinates.length, this.stride, 0));
    this.maxDeltaRevision_ = this.getRevision();
  }
  return ol.geom.flat.closest.getClosestPoint(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
};


/**
 * Iterate over each segment, calling the provided callback.
 * If the callback returns a truthy value the function returns that
 * value immediately. Otherwise the function returns `false`.
 *
 * @param {function(this: S, ol.Coordinate, ol.Coordinate): T} callback Function
 *     called for each segment.
 * @param {S=} opt_this The object to be used as the value of 'this'
 *     within callback.
 * @return {T|boolean} Value.
 * @template T,S
 * @api
 */
ol.geom.LineString.prototype.forEachSegment = function(callback, opt_this) {
  return ol.geom.flat.segments.forEach(this.flatCoordinates, 0,
      this.flatCoordinates.length, this.stride, callback, opt_this);
};


/**
 * Returns the coordinate at `m` using linear interpolation, or `null` if no
 * such coordinate exists.
 *
 * `opt_extrapolate` controls extrapolation beyond the range of Ms in the
 * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first
 * M will return the first coordinate and Ms greater than the last M will
 * return the last coordinate.
 *
 * @param {number} m M.
 * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
 * @return {ol.Coordinate} Coordinate.
 * @api stable
 */
ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) {
  if (this.layout != ol.geom.GeometryLayout.XYM &&
      this.layout != ol.geom.GeometryLayout.XYZM) {
    return null;
  }
  var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
  return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0,
      this.flatCoordinates.length, this.stride, m, extrapolate);
};


/**
 * Return the coordinates of the linestring.
 * @return {Array.<ol.Coordinate>} Coordinates.
 * @api stable
 */
ol.geom.LineString.prototype.getCoordinates = function() {
  return ol.geom.flat.inflate.coordinates(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
};


/**
 * Return the coordinate at the provided fraction along the linestring.
 * The `fraction` is a number between 0 and 1, where 0 is the start of the
 * linestring and 1 is the end.
 * @param {number} fraction Fraction.
 * @param {ol.Coordinate=} opt_dest Optional coordinate whose values will
 *     be modified. If not provided, a new coordinate will be returned.
 * @return {ol.Coordinate} Coordinate of the interpolated point.
 * @api
 */
ol.geom.LineString.prototype.getCoordinateAt = function(fraction, opt_dest) {
  return ol.geom.flat.interpolate.lineString(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      fraction, opt_dest);
};


/**
 * Return the length of the linestring on projected plane.
 * @return {number} Length (on projected plane).
 * @api stable
 */
ol.geom.LineString.prototype.getLength = function() {
  return ol.geom.flat.length.lineString(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
};


/**
 * @return {Array.<number>} Flat midpoint.
 */
ol.geom.LineString.prototype.getFlatMidpoint = function() {
  if (this.flatMidpointRevision_ != this.getRevision()) {
    this.flatMidpoint_ = this.getCoordinateAt(0.5, this.flatMidpoint_);
    this.flatMidpointRevision_ = this.getRevision();
  }
  return this.flatMidpoint_;
};


/**
 * @inheritDoc
 */
ol.geom.LineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
  var simplifiedFlatCoordinates = [];
  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeucker(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      squaredTolerance, simplifiedFlatCoordinates, 0);
  var simplifiedLineString = new ol.geom.LineString(null);
  simplifiedLineString.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates);
  return simplifiedLineString;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.LineString.prototype.getType = function() {
  return ol.geom.GeometryType.LINE_STRING;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.LineString.prototype.intersectsExtent = function(extent) {
  return ol.geom.flat.intersectsextent.lineString(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride,
      extent);
};


/**
 * Set the coordinates of the linestring.
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.LineString.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
  } else {
    this.setLayout(opt_layout, coordinates, 1);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
        this.flatCoordinates, 0, coordinates, this.stride);
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 */
ol.geom.LineString.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.changed();
};

goog.provide('ol.geom.MultiLineString');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.closest');
goog.require('ol.geom.flat.deflate');
goog.require('ol.geom.flat.inflate');
goog.require('ol.geom.flat.interpolate');
goog.require('ol.geom.flat.intersectsextent');
goog.require('ol.geom.flat.simplify');


/**
 * @classdesc
 * Multi-linestring geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.MultiLineString = function(coordinates, opt_layout) {

  ol.geom.SimpleGeometry.call(this);

  /**
   * @type {Array.<number>}
   * @private
   */
  this.ends_ = [];

  /**
   * @private
   * @type {number}
   */
  this.maxDelta_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.maxDeltaRevision_ = -1;

  this.setCoordinates(coordinates, opt_layout);

};
ol.inherits(ol.geom.MultiLineString, ol.geom.SimpleGeometry);


/**
 * Append the passed linestring to the multilinestring.
 * @param {ol.geom.LineString} lineString LineString.
 * @api stable
 */
ol.geom.MultiLineString.prototype.appendLineString = function(lineString) {
  goog.DEBUG && console.assert(lineString.getLayout() == this.layout,
      'layout of lineString should match the layout');
  if (!this.flatCoordinates) {
    this.flatCoordinates = lineString.getFlatCoordinates().slice();
  } else {
    ol.array.extend(
        this.flatCoordinates, lineString.getFlatCoordinates().slice());
  }
  this.ends_.push(this.flatCoordinates.length);
  this.changed();
};


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.MultiLineString} Clone.
 * @api stable
 */
ol.geom.MultiLineString.prototype.clone = function() {
  var multiLineString = new ol.geom.MultiLineString(null);
  multiLineString.setFlatCoordinates(
      this.layout, this.flatCoordinates.slice(), this.ends_.slice());
  return multiLineString;
};


/**
 * @inheritDoc
 */
ol.geom.MultiLineString.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  if (this.maxDeltaRevision_ != this.getRevision()) {
    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getsMaxSquaredDelta(
        this.flatCoordinates, 0, this.ends_, this.stride, 0));
    this.maxDeltaRevision_ = this.getRevision();
  }
  return ol.geom.flat.closest.getsClosestPoint(
      this.flatCoordinates, 0, this.ends_, this.stride,
      this.maxDelta_, false, x, y, closestPoint, minSquaredDistance);
};


/**
 * Returns the coordinate at `m` using linear interpolation, or `null` if no
 * such coordinate exists.
 *
 * `opt_extrapolate` controls extrapolation beyond the range of Ms in the
 * MultiLineString. If `opt_extrapolate` is `true` then Ms less than the first
 * M will return the first coordinate and Ms greater than the last M will
 * return the last coordinate.
 *
 * `opt_interpolate` controls interpolation between consecutive LineStrings
 * within the MultiLineString. If `opt_interpolate` is `true` the coordinates
 * will be linearly interpolated between the last coordinate of one LineString
 * and the first coordinate of the next LineString.  If `opt_interpolate` is
 * `false` then the function will return `null` for Ms falling between
 * LineStrings.
 *
 * @param {number} m M.
 * @param {boolean=} opt_extrapolate Extrapolate. Default is `false`.
 * @param {boolean=} opt_interpolate Interpolate. Default is `false`.
 * @return {ol.Coordinate} Coordinate.
 * @api stable
 */
ol.geom.MultiLineString.prototype.getCoordinateAtM = function(m, opt_extrapolate, opt_interpolate) {
  if ((this.layout != ol.geom.GeometryLayout.XYM &&
       this.layout != ol.geom.GeometryLayout.XYZM) ||
      this.flatCoordinates.length === 0) {
    return null;
  }
  var extrapolate = opt_extrapolate !== undefined ? opt_extrapolate : false;
  var interpolate = opt_interpolate !== undefined ? opt_interpolate : false;
  return ol.geom.flat.lineStringsCoordinateAtM(this.flatCoordinates, 0,
      this.ends_, this.stride, m, extrapolate, interpolate);
};


/**
 * Return the coordinates of the multilinestring.
 * @return {Array.<Array.<ol.Coordinate>>} Coordinates.
 * @api stable
 */
ol.geom.MultiLineString.prototype.getCoordinates = function() {
  return ol.geom.flat.inflate.coordinatess(
      this.flatCoordinates, 0, this.ends_, this.stride);
};


/**
 * @return {Array.<number>} Ends.
 */
ol.geom.MultiLineString.prototype.getEnds = function() {
  return this.ends_;
};


/**
 * Return the linestring at the specified index.
 * @param {number} index Index.
 * @return {ol.geom.LineString} LineString.
 * @api stable
 */
ol.geom.MultiLineString.prototype.getLineString = function(index) {
  goog.DEBUG && console.assert(0 <= index && index < this.ends_.length,
      'index should be in between 0 and length of the this.ends_ array');
  if (index < 0 || this.ends_.length <= index) {
    return null;
  }
  var lineString = new ol.geom.LineString(null);
  lineString.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
      index === 0 ? 0 : this.ends_[index - 1], this.ends_[index]));
  return lineString;
};


/**
 * Return the linestrings of this multilinestring.
 * @return {Array.<ol.geom.LineString>} LineStrings.
 * @api stable
 */
ol.geom.MultiLineString.prototype.getLineStrings = function() {
  var flatCoordinates = this.flatCoordinates;
  var ends = this.ends_;
  var layout = this.layout;
  /** @type {Array.<ol.geom.LineString>} */
  var lineStrings = [];
  var offset = 0;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    var lineString = new ol.geom.LineString(null);
    lineString.setFlatCoordinates(layout, flatCoordinates.slice(offset, end));
    lineStrings.push(lineString);
    offset = end;
  }
  return lineStrings;
};


/**
 * @return {Array.<number>} Flat midpoints.
 */
ol.geom.MultiLineString.prototype.getFlatMidpoints = function() {
  var midpoints = [];
  var flatCoordinates = this.flatCoordinates;
  var offset = 0;
  var ends = this.ends_;
  var stride = this.stride;
  var i, ii;
  for (i = 0, ii = ends.length; i < ii; ++i) {
    var end = ends[i];
    var midpoint = ol.geom.flat.interpolate.lineString(
        flatCoordinates, offset, end, stride, 0.5);
    ol.array.extend(midpoints, midpoint);
    offset = end;
  }
  return midpoints;
};


/**
 * @inheritDoc
 */
ol.geom.MultiLineString.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
  var simplifiedFlatCoordinates = [];
  var simplifiedEnds = [];
  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.douglasPeuckers(
      this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance,
      simplifiedFlatCoordinates, 0, simplifiedEnds);
  var simplifiedMultiLineString = new ol.geom.MultiLineString(null);
  simplifiedMultiLineString.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEnds);
  return simplifiedMultiLineString;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.MultiLineString.prototype.getType = function() {
  return ol.geom.GeometryType.MULTI_LINE_STRING;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.MultiLineString.prototype.intersectsExtent = function(extent) {
  return ol.geom.flat.intersectsextent.lineStrings(
      this.flatCoordinates, 0, this.ends_, this.stride, extent);
};


/**
 * Set the coordinates of the multilinestring.
 * @param {Array.<Array.<ol.Coordinate>>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.MultiLineString.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.ends_);
  } else {
    this.setLayout(opt_layout, coordinates, 2);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    var ends = ol.geom.flat.deflate.coordinatess(
        this.flatCoordinates, 0, coordinates, this.stride, this.ends_);
    this.flatCoordinates.length = ends.length === 0 ? 0 : ends[ends.length - 1];
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {Array.<number>} ends Ends.
 */
ol.geom.MultiLineString.prototype.setFlatCoordinates = function(layout, flatCoordinates, ends) {
  if (!flatCoordinates) {
    goog.DEBUG && console.assert(ends && ends.length === 0,
        'ends must be truthy and ends.length should be 0');
  } else if (ends.length === 0) {
    goog.DEBUG && console.assert(flatCoordinates.length === 0,
        'flatCoordinates should be an empty array');
  } else {
    goog.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1],
        'length of flatCoordinates array should match the last value of ends');
  }
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.ends_ = ends;
  this.changed();
};


/**
 * @param {Array.<ol.geom.LineString>} lineStrings LineStrings.
 */
ol.geom.MultiLineString.prototype.setLineStrings = function(lineStrings) {
  var layout = this.getLayout();
  var flatCoordinates = [];
  var ends = [];
  var i, ii;
  for (i = 0, ii = lineStrings.length; i < ii; ++i) {
    var lineString = lineStrings[i];
    if (i === 0) {
      layout = lineString.getLayout();
    } else {
      // FIXME better handle the case of non-matching layouts
      goog.DEBUG && console.assert(lineString.getLayout() == layout,
          'layout of lineString should match layout');
    }
    ol.array.extend(flatCoordinates, lineString.getFlatCoordinates());
    ends.push(flatCoordinates.length);
  }
  this.setFlatCoordinates(layout, flatCoordinates, ends);
};

goog.provide('ol.geom.MultiPoint');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.Point');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.deflate');
goog.require('ol.geom.flat.inflate');
goog.require('ol.math');


/**
 * @classdesc
 * Multi-point geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.MultiPoint = function(coordinates, opt_layout) {
  ol.geom.SimpleGeometry.call(this);
  this.setCoordinates(coordinates, opt_layout);
};
ol.inherits(ol.geom.MultiPoint, ol.geom.SimpleGeometry);


/**
 * Append the passed point to this multipoint.
 * @param {ol.geom.Point} point Point.
 * @api stable
 */
ol.geom.MultiPoint.prototype.appendPoint = function(point) {
  goog.DEBUG && console.assert(point.getLayout() == this.layout,
      'the layout of point should match layout');
  if (!this.flatCoordinates) {
    this.flatCoordinates = point.getFlatCoordinates().slice();
  } else {
    ol.array.extend(this.flatCoordinates, point.getFlatCoordinates());
  }
  this.changed();
};


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.MultiPoint} Clone.
 * @api stable
 */
ol.geom.MultiPoint.prototype.clone = function() {
  var multiPoint = new ol.geom.MultiPoint(null);
  multiPoint.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
  return multiPoint;
};


/**
 * @inheritDoc
 */
ol.geom.MultiPoint.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  var flatCoordinates = this.flatCoordinates;
  var stride = this.stride;
  var i, ii, j;
  for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
    var squaredDistance = ol.math.squaredDistance(
        x, y, flatCoordinates[i], flatCoordinates[i + 1]);
    if (squaredDistance < minSquaredDistance) {
      minSquaredDistance = squaredDistance;
      for (j = 0; j < stride; ++j) {
        closestPoint[j] = flatCoordinates[i + j];
      }
      closestPoint.length = stride;
    }
  }
  return minSquaredDistance;
};


/**
 * Return the coordinates of the multipoint.
 * @return {Array.<ol.Coordinate>} Coordinates.
 * @api stable
 */
ol.geom.MultiPoint.prototype.getCoordinates = function() {
  return ol.geom.flat.inflate.coordinates(
      this.flatCoordinates, 0, this.flatCoordinates.length, this.stride);
};


/**
 * Return the point at the specified index.
 * @param {number} index Index.
 * @return {ol.geom.Point} Point.
 * @api stable
 */
ol.geom.MultiPoint.prototype.getPoint = function(index) {
  var n = !this.flatCoordinates ?
      0 : this.flatCoordinates.length / this.stride;
  goog.DEBUG && console.assert(0 <= index && index < n,
      'index should be in between 0 and n');
  if (index < 0 || n <= index) {
    return null;
  }
  var point = new ol.geom.Point(null);
  point.setFlatCoordinates(this.layout, this.flatCoordinates.slice(
      index * this.stride, (index + 1) * this.stride));
  return point;
};


/**
 * Return the points of this multipoint.
 * @return {Array.<ol.geom.Point>} Points.
 * @api stable
 */
ol.geom.MultiPoint.prototype.getPoints = function() {
  var flatCoordinates = this.flatCoordinates;
  var layout = this.layout;
  var stride = this.stride;
  /** @type {Array.<ol.geom.Point>} */
  var points = [];
  var i, ii;
  for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
    var point = new ol.geom.Point(null);
    point.setFlatCoordinates(layout, flatCoordinates.slice(i, i + stride));
    points.push(point);
  }
  return points;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.MultiPoint.prototype.getType = function() {
  return ol.geom.GeometryType.MULTI_POINT;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.MultiPoint.prototype.intersectsExtent = function(extent) {
  var flatCoordinates = this.flatCoordinates;
  var stride = this.stride;
  var i, ii, x, y;
  for (i = 0, ii = flatCoordinates.length; i < ii; i += stride) {
    x = flatCoordinates[i];
    y = flatCoordinates[i + 1];
    if (ol.extent.containsXY(extent, x, y)) {
      return true;
    }
  }
  return false;
};


/**
 * Set the coordinates of the multipoint.
 * @param {Array.<ol.Coordinate>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.MultiPoint.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
  } else {
    this.setLayout(opt_layout, coordinates, 1);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    this.flatCoordinates.length = ol.geom.flat.deflate.coordinates(
        this.flatCoordinates, 0, coordinates, this.stride);
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 */
ol.geom.MultiPoint.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.changed();
};

goog.provide('ol.geom.flat.center');

goog.require('ol.extent');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {Array.<Array.<number>>} endss Endss.
 * @param {number} stride Stride.
 * @return {Array.<number>} Flat centers.
 */
ol.geom.flat.center.linearRingss = function(flatCoordinates, offset, endss, stride) {
  var flatCenters = [];
  var i, ii;
  var extent = ol.extent.createEmpty();
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i];
    extent = ol.extent.createOrUpdateFromFlatCoordinates(
        flatCoordinates, offset, ends[0], stride);
    flatCenters.push((extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2);
    offset = ends[ends.length - 1];
  }
  return flatCenters;
};

goog.provide('ol.geom.MultiPolygon');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.Polygon');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.area');
goog.require('ol.geom.flat.center');
goog.require('ol.geom.flat.closest');
goog.require('ol.geom.flat.contains');
goog.require('ol.geom.flat.deflate');
goog.require('ol.geom.flat.inflate');
goog.require('ol.geom.flat.interiorpoint');
goog.require('ol.geom.flat.intersectsextent');
goog.require('ol.geom.flat.orient');
goog.require('ol.geom.flat.simplify');


/**
 * @classdesc
 * Multi-polygon geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.MultiPolygon = function(coordinates, opt_layout) {

  ol.geom.SimpleGeometry.call(this);

  /**
   * @type {Array.<Array.<number>>}
   * @private
   */
  this.endss_ = [];

  /**
   * @private
   * @type {number}
   */
  this.flatInteriorPointsRevision_ = -1;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.flatInteriorPoints_ = null;

  /**
   * @private
   * @type {number}
   */
  this.maxDelta_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.maxDeltaRevision_ = -1;

  /**
   * @private
   * @type {number}
   */
  this.orientedRevision_ = -1;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.orientedFlatCoordinates_ = null;

  this.setCoordinates(coordinates, opt_layout);

};
ol.inherits(ol.geom.MultiPolygon, ol.geom.SimpleGeometry);


/**
 * Append the passed polygon to this multipolygon.
 * @param {ol.geom.Polygon} polygon Polygon.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.appendPolygon = function(polygon) {
  goog.DEBUG && console.assert(polygon.getLayout() == this.layout,
      'layout of polygon should match layout');
  /** @type {Array.<number>} */
  var ends;
  if (!this.flatCoordinates) {
    this.flatCoordinates = polygon.getFlatCoordinates().slice();
    ends = polygon.getEnds().slice();
    this.endss_.push();
  } else {
    var offset = this.flatCoordinates.length;
    ol.array.extend(this.flatCoordinates, polygon.getFlatCoordinates());
    ends = polygon.getEnds().slice();
    var i, ii;
    for (i = 0, ii = ends.length; i < ii; ++i) {
      ends[i] += offset;
    }
  }
  this.endss_.push(ends);
  this.changed();
};


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.MultiPolygon} Clone.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.clone = function() {
  var multiPolygon = new ol.geom.MultiPolygon(null);

  var len = this.endss_.length;
  var newEndss = new Array(len);
  for (var i = 0; i < len; ++i) {
    newEndss[i] = this.endss_[i].slice();
  }

  multiPolygon.setFlatCoordinates(
      this.layout, this.flatCoordinates.slice(), newEndss);
  return multiPolygon;
};


/**
 * @inheritDoc
 */
ol.geom.MultiPolygon.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  if (this.maxDeltaRevision_ != this.getRevision()) {
    this.maxDelta_ = Math.sqrt(ol.geom.flat.closest.getssMaxSquaredDelta(
        this.flatCoordinates, 0, this.endss_, this.stride, 0));
    this.maxDeltaRevision_ = this.getRevision();
  }
  return ol.geom.flat.closest.getssClosestPoint(
      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride,
      this.maxDelta_, true, x, y, closestPoint, minSquaredDistance);
};


/**
 * @inheritDoc
 */
ol.geom.MultiPolygon.prototype.containsXY = function(x, y) {
  return ol.geom.flat.contains.linearRingssContainsXY(
      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, x, y);
};


/**
 * Return the area of the multipolygon on projected plane.
 * @return {number} Area (on projected plane).
 * @api stable
 */
ol.geom.MultiPolygon.prototype.getArea = function() {
  return ol.geom.flat.area.linearRingss(
      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride);
};


/**
 * Get the coordinate array for this geometry.  This array has the structure
 * of a GeoJSON coordinate array for multi-polygons.
 *
 * @param {boolean=} opt_right Orient coordinates according to the right-hand
 *     rule (counter-clockwise for exterior and clockwise for interior rings).
 *     If `false`, coordinates will be oriented according to the left-hand rule
 *     (clockwise for exterior and counter-clockwise for interior rings).
 *     By default, coordinate orientation will depend on how the geometry was
 *     constructed.
 * @return {Array.<Array.<Array.<ol.Coordinate>>>} Coordinates.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.getCoordinates = function(opt_right) {
  var flatCoordinates;
  if (opt_right !== undefined) {
    flatCoordinates = this.getOrientedFlatCoordinates().slice();
    ol.geom.flat.orient.orientLinearRingss(
        flatCoordinates, 0, this.endss_, this.stride, opt_right);
  } else {
    flatCoordinates = this.flatCoordinates;
  }

  return ol.geom.flat.inflate.coordinatesss(
      flatCoordinates, 0, this.endss_, this.stride);
};


/**
 * @return {Array.<Array.<number>>} Endss.
 */
ol.geom.MultiPolygon.prototype.getEndss = function() {
  return this.endss_;
};


/**
 * @return {Array.<number>} Flat interior points.
 */
ol.geom.MultiPolygon.prototype.getFlatInteriorPoints = function() {
  if (this.flatInteriorPointsRevision_ != this.getRevision()) {
    var flatCenters = ol.geom.flat.center.linearRingss(
        this.flatCoordinates, 0, this.endss_, this.stride);
    this.flatInteriorPoints_ = ol.geom.flat.interiorpoint.linearRingss(
        this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride,
        flatCenters);
    this.flatInteriorPointsRevision_ = this.getRevision();
  }
  return this.flatInteriorPoints_;
};


/**
 * Return the interior points as {@link ol.geom.MultiPoint multipoint}.
 * @return {ol.geom.MultiPoint} Interior points.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.getInteriorPoints = function() {
  var interiorPoints = new ol.geom.MultiPoint(null);
  interiorPoints.setFlatCoordinates(ol.geom.GeometryLayout.XY,
      this.getFlatInteriorPoints().slice());
  return interiorPoints;
};


/**
 * @return {Array.<number>} Oriented flat coordinates.
 */
ol.geom.MultiPolygon.prototype.getOrientedFlatCoordinates = function() {
  if (this.orientedRevision_ != this.getRevision()) {
    var flatCoordinates = this.flatCoordinates;
    if (ol.geom.flat.orient.linearRingssAreOriented(
        flatCoordinates, 0, this.endss_, this.stride)) {
      this.orientedFlatCoordinates_ = flatCoordinates;
    } else {
      this.orientedFlatCoordinates_ = flatCoordinates.slice();
      this.orientedFlatCoordinates_.length =
          ol.geom.flat.orient.orientLinearRingss(
              this.orientedFlatCoordinates_, 0, this.endss_, this.stride);
    }
    this.orientedRevision_ = this.getRevision();
  }
  return this.orientedFlatCoordinates_;
};


/**
 * @inheritDoc
 */
ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal = function(squaredTolerance) {
  var simplifiedFlatCoordinates = [];
  var simplifiedEndss = [];
  simplifiedFlatCoordinates.length = ol.geom.flat.simplify.quantizess(
      this.flatCoordinates, 0, this.endss_, this.stride,
      Math.sqrt(squaredTolerance),
      simplifiedFlatCoordinates, 0, simplifiedEndss);
  var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null);
  simplifiedMultiPolygon.setFlatCoordinates(
      ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss);
  return simplifiedMultiPolygon;
};


/**
 * Return the polygon at the specified index.
 * @param {number} index Index.
 * @return {ol.geom.Polygon} Polygon.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.getPolygon = function(index) {
  goog.DEBUG && console.assert(0 <= index && index < this.endss_.length,
      'index should be in between 0 and the length of this.endss_');
  if (index < 0 || this.endss_.length <= index) {
    return null;
  }
  var offset;
  if (index === 0) {
    offset = 0;
  } else {
    var prevEnds = this.endss_[index - 1];
    offset = prevEnds[prevEnds.length - 1];
  }
  var ends = this.endss_[index].slice();
  var end = ends[ends.length - 1];
  if (offset !== 0) {
    var i, ii;
    for (i = 0, ii = ends.length; i < ii; ++i) {
      ends[i] -= offset;
    }
  }
  var polygon = new ol.geom.Polygon(null);
  polygon.setFlatCoordinates(
      this.layout, this.flatCoordinates.slice(offset, end), ends);
  return polygon;
};


/**
 * Return the polygons of this multipolygon.
 * @return {Array.<ol.geom.Polygon>} Polygons.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.getPolygons = function() {
  var layout = this.layout;
  var flatCoordinates = this.flatCoordinates;
  var endss = this.endss_;
  var polygons = [];
  var offset = 0;
  var i, ii, j, jj;
  for (i = 0, ii = endss.length; i < ii; ++i) {
    var ends = endss[i].slice();
    var end = ends[ends.length - 1];
    if (offset !== 0) {
      for (j = 0, jj = ends.length; j < jj; ++j) {
        ends[j] -= offset;
      }
    }
    var polygon = new ol.geom.Polygon(null);
    polygon.setFlatCoordinates(
        layout, flatCoordinates.slice(offset, end), ends);
    polygons.push(polygon);
    offset = end;
  }
  return polygons;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.MultiPolygon.prototype.getType = function() {
  return ol.geom.GeometryType.MULTI_POLYGON;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.MultiPolygon.prototype.intersectsExtent = function(extent) {
  return ol.geom.flat.intersectsextent.linearRingss(
      this.getOrientedFlatCoordinates(), 0, this.endss_, this.stride, extent);
};


/**
 * Set the coordinates of the multipolygon.
 * @param {Array.<Array.<Array.<ol.Coordinate>>>} coordinates Coordinates.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api stable
 */
ol.geom.MultiPolygon.prototype.setCoordinates = function(coordinates, opt_layout) {
  if (!coordinates) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null, this.endss_);
  } else {
    this.setLayout(opt_layout, coordinates, 3);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    var endss = ol.geom.flat.deflate.coordinatesss(
        this.flatCoordinates, 0, coordinates, this.stride, this.endss_);
    if (endss.length === 0) {
      this.flatCoordinates.length = 0;
    } else {
      var lastEnds = endss[endss.length - 1];
      this.flatCoordinates.length = lastEnds.length === 0 ?
          0 : lastEnds[lastEnds.length - 1];
    }
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {Array.<Array.<number>>} endss Endss.
 */
ol.geom.MultiPolygon.prototype.setFlatCoordinates = function(layout, flatCoordinates, endss) {
  goog.DEBUG && console.assert(endss, 'endss must be truthy');
  if (!flatCoordinates || flatCoordinates.length === 0) {
    goog.DEBUG && console.assert(endss.length === 0, 'the length of endss should be 0');
  } else {
    goog.DEBUG && console.assert(endss.length > 0, 'endss cannot be an empty array');
    var ends = endss[endss.length - 1];
    goog.DEBUG && console.assert(flatCoordinates.length == ends[ends.length - 1],
        'the length of flatCoordinates should be the last value of ends');
  }
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.endss_ = endss;
  this.changed();
};


/**
 * @param {Array.<ol.geom.Polygon>} polygons Polygons.
 */
ol.geom.MultiPolygon.prototype.setPolygons = function(polygons) {
  var layout = this.getLayout();
  var flatCoordinates = [];
  var endss = [];
  var i, ii, ends;
  for (i = 0, ii = polygons.length; i < ii; ++i) {
    var polygon = polygons[i];
    if (i === 0) {
      layout = polygon.getLayout();
    } else {
      // FIXME better handle the case of non-matching layouts
      goog.DEBUG && console.assert(polygon.getLayout() == layout,
          'layout of polygon should be layout');
    }
    var offset = flatCoordinates.length;
    ends = polygon.getEnds();
    var j, jj;
    for (j = 0, jj = ends.length; j < jj; ++j) {
      ends[j] += offset;
    }
    ol.array.extend(flatCoordinates, polygon.getFlatCoordinates());
    endss.push(ends);
  }
  this.setFlatCoordinates(layout, flatCoordinates, endss);
};

goog.provide('ol.format.EsriJSON');

goog.require('ol');
goog.require('ol.Feature');
goog.require('ol.array');
goog.require('ol.asserts');
goog.require('ol.extent');
goog.require('ol.format.Feature');
goog.require('ol.format.JSONFeature');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.geom.flat.orient');
goog.require('ol.obj');
goog.require('ol.proj');


/**
 * @classdesc
 * Feature format for reading and writing data in the EsriJSON format.
 *
 * @constructor
 * @extends {ol.format.JSONFeature}
 * @param {olx.format.EsriJSONOptions=} opt_options Options.
 * @api
 */
ol.format.EsriJSON = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.JSONFeature.call(this);

  /**
   * Name of the geometry attribute for features.
   * @type {string|undefined}
   * @private
   */
  this.geometryName_ = options.geometryName;

};
ol.inherits(ol.format.EsriJSON, ol.format.JSONFeature);


/**
 * @param {EsriJSONGeometry} object Object.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @private
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.EsriJSON.readGeometry_ = function(object, opt_options) {
  if (!object) {
    return null;
  }
  /** @type {ol.geom.GeometryType} */
  var type;
  if (typeof object.x === 'number' && typeof object.y === 'number') {
    type = ol.geom.GeometryType.POINT;
  } else if (object.points) {
    type = ol.geom.GeometryType.MULTI_POINT;
  } else if (object.paths) {
    if (object.paths.length === 1) {
      type = ol.geom.GeometryType.LINE_STRING;
    } else {
      type = ol.geom.GeometryType.MULTI_LINE_STRING;
    }
  } else if (object.rings) {
    var layout = ol.format.EsriJSON.getGeometryLayout_(object);
    var rings = ol.format.EsriJSON.convertRings_(object.rings, layout);
    object = /** @type {EsriJSONGeometry} */(ol.obj.assign({}, object));
    if (rings.length === 1) {
      type = ol.geom.GeometryType.POLYGON;
      object.rings = rings[0];
    } else {
      type = ol.geom.GeometryType.MULTI_POLYGON;
      object.rings = rings;
    }
  }
  var geometryReader = ol.format.EsriJSON.GEOMETRY_READERS_[type];
  return /** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(
          geometryReader(object), false, opt_options));
};


/**
 * Determines inner and outer rings.
 * Checks if any polygons in this array contain any other polygons in this
 * array. It is used for checking for holes.
 * Logic inspired by: https://github.com/Esri/terraformer-arcgis-parser
 * @param {Array.<!Array.<!Array.<number>>>} rings Rings.
 * @param {ol.geom.GeometryLayout} layout Geometry layout.
 * @private
 * @return {Array.<!Array.<!Array.<number>>>} Transoformed rings.
 */
ol.format.EsriJSON.convertRings_ = function(rings, layout) {
  var outerRings = [];
  var holes = [];
  var i, ii;
  for (i = 0, ii = rings.length; i < ii; ++i) {
    var flatRing = ol.array.flatten(rings[i]);
    // is this ring an outer ring? is it clockwise?
    var clockwise = ol.geom.flat.orient.linearRingIsClockwise(flatRing, 0,
        flatRing.length, layout.length);
    if (clockwise) {
      outerRings.push([rings[i]]);
    } else {
      holes.push(rings[i]);
    }
  }
  while (holes.length) {
    var hole = holes.shift();
    var matched = false;
    // loop over all outer rings and see if they contain our hole.
    for (i = outerRings.length - 1; i >= 0; i--) {
      var outerRing = outerRings[i][0];
      if (ol.extent.containsExtent(new ol.geom.LinearRing(
          outerRing).getExtent(),
          new ol.geom.LinearRing(hole).getExtent())) {
        // the hole is contained push it into our polygon
        outerRings[i].push(hole);
        matched = true;
        break;
      }
    }
    if (!matched) {
      // no outer rings contain this hole turn it into and outer
      // ring (reverse it)
      outerRings.push([hole.reverse()]);
    }
  }
  return outerRings;
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Geometry} Point.
 */
ol.format.EsriJSON.readPointGeometry_ = function(object) {
  goog.DEBUG && console.assert(typeof object.x === 'number', 'object.x should be number');
  goog.DEBUG && console.assert(typeof object.y === 'number', 'object.y should be number');
  var point;
  if (object.m !== undefined && object.z !== undefined) {
    point = new ol.geom.Point([object.x, object.y, object.z, object.m],
        ol.geom.GeometryLayout.XYZM);
  } else if (object.z !== undefined) {
    point = new ol.geom.Point([object.x, object.y, object.z],
        ol.geom.GeometryLayout.XYZ);
  } else if (object.m !== undefined) {
    point = new ol.geom.Point([object.x, object.y, object.m],
        ol.geom.GeometryLayout.XYM);
  } else {
    point = new ol.geom.Point([object.x, object.y]);
  }
  return point;
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Geometry} LineString.
 */
ol.format.EsriJSON.readLineStringGeometry_ = function(object) {
  goog.DEBUG && console.assert(Array.isArray(object.paths),
      'object.paths should be an array');
  goog.DEBUG && console.assert(object.paths.length === 1,
      'object.paths array length should be 1');
  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
  return new ol.geom.LineString(object.paths[0], layout);
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Geometry} MultiLineString.
 */
ol.format.EsriJSON.readMultiLineStringGeometry_ = function(object) {
  goog.DEBUG && console.assert(Array.isArray(object.paths),
      'object.paths should be an array');
  goog.DEBUG && console.assert(object.paths.length > 1,
      'object.paths array length should be more than 1');
  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
  return new ol.geom.MultiLineString(object.paths, layout);
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.GeometryLayout} The geometry layout to use.
 */
ol.format.EsriJSON.getGeometryLayout_ = function(object) {
  var layout = ol.geom.GeometryLayout.XY;
  if (object.hasZ === true && object.hasM === true) {
    layout = ol.geom.GeometryLayout.XYZM;
  } else if (object.hasZ === true) {
    layout = ol.geom.GeometryLayout.XYZ;
  } else if (object.hasM === true) {
    layout = ol.geom.GeometryLayout.XYM;
  }
  return layout;
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Geometry} MultiPoint.
 */
ol.format.EsriJSON.readMultiPointGeometry_ = function(object) {
  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
  return new ol.geom.MultiPoint(object.points, layout);
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Geometry} MultiPolygon.
 */
ol.format.EsriJSON.readMultiPolygonGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.rings.length > 1,
      'object.rings should have length larger than 1');
  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
  return new ol.geom.MultiPolygon(
      /** @type {Array.<Array.<Array.<Array.<number>>>>} */(object.rings),
      layout);
};


/**
 * @param {EsriJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Geometry} Polygon.
 */
ol.format.EsriJSON.readPolygonGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.rings);
  var layout = ol.format.EsriJSON.getGeometryLayout_(object);
  return new ol.geom.Polygon(object.rings, layout);
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONGeometry} EsriJSON geometry.
 */
ol.format.EsriJSON.writePointGeometry_ = function(geometry, opt_options) {
  var coordinates = /** @type {ol.geom.Point} */ (geometry).getCoordinates();
  var esriJSON;
  var layout = /** @type {ol.geom.Point} */ (geometry).getLayout();
  if (layout === ol.geom.GeometryLayout.XYZ) {
    esriJSON = /** @type {EsriJSONPoint} */ ({
      x: coordinates[0],
      y: coordinates[1],
      z: coordinates[2]
    });
  } else if (layout === ol.geom.GeometryLayout.XYM) {
    esriJSON = /** @type {EsriJSONPoint} */ ({
      x: coordinates[0],
      y: coordinates[1],
      m: coordinates[2]
    });
  } else if (layout === ol.geom.GeometryLayout.XYZM) {
    esriJSON = /** @type {EsriJSONPoint} */ ({
      x: coordinates[0],
      y: coordinates[1],
      z: coordinates[2],
      m: coordinates[3]
    });
  } else if (layout === ol.geom.GeometryLayout.XY) {
    esriJSON = /** @type {EsriJSONPoint} */ ({
      x: coordinates[0],
      y: coordinates[1]
    });
  } else {
    ol.asserts.assert(false, 34); // Invalid geometry layout
  }
  return /** @type {EsriJSONGeometry} */ (esriJSON);
};


/**
 * @param {ol.geom.SimpleGeometry} geometry Geometry.
 * @private
 * @return {Object} Object with boolean hasZ and hasM keys.
 */
ol.format.EsriJSON.getHasZM_ = function(geometry) {
  var layout = geometry.getLayout();
  return {
    hasZ: (layout === ol.geom.GeometryLayout.XYZ ||
        layout === ol.geom.GeometryLayout.XYZM),
    hasM: (layout === ol.geom.GeometryLayout.XYM ||
        layout === ol.geom.GeometryLayout.XYZM)
  };
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONPolyline} EsriJSON geometry.
 */
ol.format.EsriJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.LineString} */ (geometry));
  return /** @type {EsriJSONPolyline} */ ({
    hasZ: hasZM.hasZ,
    hasM: hasZM.hasM,
    paths: [
      /** @type {ol.geom.LineString} */ (geometry).getCoordinates()
    ]
  });
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONPolygon} EsriJSON geometry.
 */
ol.format.EsriJSON.writePolygonGeometry_ = function(geometry, opt_options) {
  // Esri geometries use the left-hand rule
  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.Polygon} */ (geometry));
  return /** @type {EsriJSONPolygon} */ ({
    hasZ: hasZM.hasZ,
    hasM: hasZM.hasM,
    rings: /** @type {ol.geom.Polygon} */ (geometry).getCoordinates(false)
  });
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONPolyline} EsriJSON geometry.
 */
ol.format.EsriJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiLineString} */ (geometry));
  return /** @type {EsriJSONPolyline} */ ({
    hasZ: hasZM.hasZ,
    hasM: hasZM.hasM,
    paths: /** @type {ol.geom.MultiLineString} */ (geometry).getCoordinates()
  });
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONMultipoint} EsriJSON geometry.
 */
ol.format.EsriJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPoint} */ (geometry));
  return /** @type {EsriJSONMultipoint} */ ({
    hasZ: hasZM.hasZ,
    hasM: hasZM.hasM,
    points: /** @type {ol.geom.MultiPoint} */ (geometry).getCoordinates()
  });
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONPolygon} EsriJSON geometry.
 */
ol.format.EsriJSON.writeMultiPolygonGeometry_ = function(geometry,
    opt_options) {
  var hasZM = ol.format.EsriJSON.getHasZM_(/** @type {ol.geom.MultiPolygon} */ (geometry));
  var coordinates = /** @type {ol.geom.MultiPolygon} */ (geometry).getCoordinates(false);
  var output = [];
  for (var i = 0; i < coordinates.length; i++) {
    for (var x = coordinates[i].length - 1; x >= 0; x--) {
      output.push(coordinates[i][x]);
    }
  }
  return /** @type {EsriJSONPolygon} */ ({
    hasZ: hasZM.hasZ,
    hasM: hasZM.hasM,
    rings: output
  });
};


/**
 * @const
 * @private
 * @type {Object.<ol.geom.GeometryType, function(EsriJSONGeometry): ol.geom.Geometry>}
 */
ol.format.EsriJSON.GEOMETRY_READERS_ = {};
ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POINT] =
    ol.format.EsriJSON.readPointGeometry_;
ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.LINE_STRING] =
    ol.format.EsriJSON.readLineStringGeometry_;
ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.POLYGON] =
    ol.format.EsriJSON.readPolygonGeometry_;
ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POINT] =
    ol.format.EsriJSON.readMultiPointGeometry_;
ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_LINE_STRING] =
    ol.format.EsriJSON.readMultiLineStringGeometry_;
ol.format.EsriJSON.GEOMETRY_READERS_[ol.geom.GeometryType.MULTI_POLYGON] =
    ol.format.EsriJSON.readMultiPolygonGeometry_;


/**
 * @const
 * @private
 * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (EsriJSONGeometry)>}
 */
ol.format.EsriJSON.GEOMETRY_WRITERS_ = {};
ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POINT] =
    ol.format.EsriJSON.writePointGeometry_;
ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.LINE_STRING] =
    ol.format.EsriJSON.writeLineStringGeometry_;
ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.POLYGON] =
    ol.format.EsriJSON.writePolygonGeometry_;
ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POINT] =
    ol.format.EsriJSON.writeMultiPointGeometry_;
ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_LINE_STRING] =
    ol.format.EsriJSON.writeMultiLineStringGeometry_;
ol.format.EsriJSON.GEOMETRY_WRITERS_[ol.geom.GeometryType.MULTI_POLYGON] =
    ol.format.EsriJSON.writeMultiPolygonGeometry_;


/**
 * Read a feature from a EsriJSON Feature source.  Only works for Feature,
 * use `readFeatures` to read FeatureCollection source.
 *
 * @function
 * @param {ArrayBuffer|Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api
 */
ol.format.EsriJSON.prototype.readFeature;


/**
 * Read all features from a EsriJSON source.  Works with both Feature and
 * FeatureCollection sources.
 *
 * @function
 * @param {ArrayBuffer|Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api
 */
ol.format.EsriJSON.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.EsriJSON.prototype.readFeatureFromObject = function(
    object, opt_options) {
  var esriJSONFeature = /** @type {EsriJSONFeature} */ (object);
  goog.DEBUG && console.assert(esriJSONFeature.geometry ||
      esriJSONFeature.attributes,
      'geometry or attributes should be defined');
  var geometry = ol.format.EsriJSON.readGeometry_(esriJSONFeature.geometry,
      opt_options);
  var feature = new ol.Feature();
  if (this.geometryName_) {
    feature.setGeometryName(this.geometryName_);
  }
  feature.setGeometry(geometry);
  if (opt_options && opt_options.idField &&
      esriJSONFeature.attributes[opt_options.idField]) {
    goog.DEBUG && console.assert(
        typeof esriJSONFeature.attributes[opt_options.idField] === 'number',
        'objectIdFieldName value should be a number');
    feature.setId(/** @type {number} */(
        esriJSONFeature.attributes[opt_options.idField]));
  }
  if (esriJSONFeature.attributes) {
    feature.setProperties(esriJSONFeature.attributes);
  }
  return feature;
};


/**
 * @inheritDoc
 */
ol.format.EsriJSON.prototype.readFeaturesFromObject = function(
    object, opt_options) {
  var esriJSONObject = /** @type {EsriJSONObject} */ (object);
  var options = opt_options ? opt_options : {};
  if (esriJSONObject.features) {
    var esriJSONFeatureCollection = /** @type {EsriJSONFeatureCollection} */
        (object);
    /** @type {Array.<ol.Feature>} */
    var features = [];
    var esriJSONFeatures = esriJSONFeatureCollection.features;
    var i, ii;
    options.idField = object.objectIdFieldName;
    for (i = 0, ii = esriJSONFeatures.length; i < ii; ++i) {
      features.push(this.readFeatureFromObject(esriJSONFeatures[i],
          options));
    }
    return features;
  } else {
    return [this.readFeatureFromObject(object, options)];
  }
};


/**
 * Read a geometry from a EsriJSON source.
 *
 * @function
 * @param {ArrayBuffer|Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.geom.Geometry} Geometry.
 * @api
 */
ol.format.EsriJSON.prototype.readGeometry;


/**
 * @inheritDoc
 */
ol.format.EsriJSON.prototype.readGeometryFromObject = function(
    object, opt_options) {
  return ol.format.EsriJSON.readGeometry_(
      /** @type {EsriJSONGeometry} */ (object), opt_options);
};


/**
 * Read the projection from a EsriJSON source.
 *
 * @function
 * @param {ArrayBuffer|Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api
 */
ol.format.EsriJSON.prototype.readProjection;


/**
 * @inheritDoc
 */
ol.format.EsriJSON.prototype.readProjectionFromObject = function(object) {
  var esriJSONObject = /** @type {EsriJSONObject} */ (object);
  if (esriJSONObject.spatialReference && esriJSONObject.spatialReference.wkid) {
    var crs = esriJSONObject.spatialReference.wkid;
    return ol.proj.get('EPSG:' + crs);
  } else {
    return null;
  }
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {EsriJSONGeometry} EsriJSON geometry.
 */
ol.format.EsriJSON.writeGeometry_ = function(geometry, opt_options) {
  var geometryWriter = ol.format.EsriJSON.GEOMETRY_WRITERS_[geometry.getType()];
  return geometryWriter(/** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
      opt_options);
};


/**
 * Encode a geometry as a EsriJSON string.
 *
 * @function
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} EsriJSON.
 * @api
 */
ol.format.EsriJSON.prototype.writeGeometry;


/**
 * Encode a geometry as a EsriJSON object.
 *
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {EsriJSONGeometry} Object.
 * @api
 */
ol.format.EsriJSON.prototype.writeGeometryObject = function(geometry,
    opt_options) {
  return ol.format.EsriJSON.writeGeometry_(geometry,
      this.adaptOptions(opt_options));
};


/**
 * Encode a feature as a EsriJSON Feature string.
 *
 * @function
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} EsriJSON.
 * @api
 */
ol.format.EsriJSON.prototype.writeFeature;


/**
 * Encode a feature as a esriJSON Feature object.
 *
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {Object} Object.
 * @api
 */
ol.format.EsriJSON.prototype.writeFeatureObject = function(
    feature, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  var object = {};
  var geometry = feature.getGeometry();
  if (geometry) {
    object['geometry'] =
        ol.format.EsriJSON.writeGeometry_(geometry, opt_options);
  }
  var properties = feature.getProperties();
  delete properties[feature.getGeometryName()];
  if (!ol.obj.isEmpty(properties)) {
    object['attributes'] = properties;
  } else {
    object['attributes'] = {};
  }
  if (opt_options && opt_options.featureProjection) {
    object['spatialReference'] = /** @type {EsriJSONCRS} */({
      wkid: ol.proj.get(
          opt_options.featureProjection).getCode().split(':').pop()
    });
  }
  return object;
};


/**
 * Encode an array of features as EsriJSON.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} EsriJSON.
 * @api
 */
ol.format.EsriJSON.prototype.writeFeatures;


/**
 * Encode an array of features as a EsriJSON object.
 *
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {Object} EsriJSON Object.
 * @api
 */
ol.format.EsriJSON.prototype.writeFeaturesObject = function(features, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  var objects = [];
  var i, ii;
  for (i = 0, ii = features.length; i < ii; ++i) {
    objects.push(this.writeFeatureObject(features[i], opt_options));
  }
  return /** @type {EsriJSONFeatureCollection} */ ({
    'features': objects
  });
};

goog.provide('ol.geom.GeometryCollection');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryType');
goog.require('ol.obj');


/**
 * @classdesc
 * An array of {@link ol.geom.Geometry} objects.
 *
 * @constructor
 * @extends {ol.geom.Geometry}
 * @param {Array.<ol.geom.Geometry>=} opt_geometries Geometries.
 * @api stable
 */
ol.geom.GeometryCollection = function(opt_geometries) {

  ol.geom.Geometry.call(this);

  /**
   * @private
   * @type {Array.<ol.geom.Geometry>}
   */
  this.geometries_ = opt_geometries ? opt_geometries : null;

  this.listenGeometriesChange_();
};
ol.inherits(ol.geom.GeometryCollection, ol.geom.Geometry);


/**
 * @param {Array.<ol.geom.Geometry>} geometries Geometries.
 * @private
 * @return {Array.<ol.geom.Geometry>} Cloned geometries.
 */
ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) {
  var clonedGeometries = [];
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    clonedGeometries.push(geometries[i].clone());
  }
  return clonedGeometries;
};


/**
 * @private
 */
ol.geom.GeometryCollection.prototype.unlistenGeometriesChange_ = function() {
  var i, ii;
  if (!this.geometries_) {
    return;
  }
  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
    ol.events.unlisten(
        this.geometries_[i], ol.events.EventType.CHANGE,
        this.changed, this);
  }
};


/**
 * @private
 */
ol.geom.GeometryCollection.prototype.listenGeometriesChange_ = function() {
  var i, ii;
  if (!this.geometries_) {
    return;
  }
  for (i = 0, ii = this.geometries_.length; i < ii; ++i) {
    ol.events.listen(
        this.geometries_[i], ol.events.EventType.CHANGE,
        this.changed, this);
  }
};


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.GeometryCollection} Clone.
 * @api stable
 */
ol.geom.GeometryCollection.prototype.clone = function() {
  var geometryCollection = new ol.geom.GeometryCollection(null);
  geometryCollection.setGeometries(this.geometries_);
  return geometryCollection;
};


/**
 * @inheritDoc
 */
ol.geom.GeometryCollection.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  if (minSquaredDistance <
      ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
    return minSquaredDistance;
  }
  var geometries = this.geometries_;
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    minSquaredDistance = geometries[i].closestPointXY(
        x, y, closestPoint, minSquaredDistance);
  }
  return minSquaredDistance;
};


/**
 * @inheritDoc
 */
ol.geom.GeometryCollection.prototype.containsXY = function(x, y) {
  var geometries = this.geometries_;
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    if (geometries[i].containsXY(x, y)) {
      return true;
    }
  }
  return false;
};


/**
 * @inheritDoc
 */
ol.geom.GeometryCollection.prototype.computeExtent = function(extent) {
  ol.extent.createOrUpdateEmpty(extent);
  var geometries = this.geometries_;
  for (var i = 0, ii = geometries.length; i < ii; ++i) {
    ol.extent.extend(extent, geometries[i].getExtent());
  }
  return extent;
};


/**
 * Return the geometries that make up this geometry collection.
 * @return {Array.<ol.geom.Geometry>} Geometries.
 * @api stable
 */
ol.geom.GeometryCollection.prototype.getGeometries = function() {
  return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
};


/**
 * @return {Array.<ol.geom.Geometry>} Geometries.
 */
ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
  return this.geometries_;
};


/**
 * @inheritDoc
 */
ol.geom.GeometryCollection.prototype.getSimplifiedGeometry = function(squaredTolerance) {
  if (this.simplifiedGeometryRevision != this.getRevision()) {
    ol.obj.clear(this.simplifiedGeometryCache);
    this.simplifiedGeometryMaxMinSquaredTolerance = 0;
    this.simplifiedGeometryRevision = this.getRevision();
  }
  if (squaredTolerance < 0 ||
      (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
       squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) {
    return this;
  }
  var key = squaredTolerance.toString();
  if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
    return this.simplifiedGeometryCache[key];
  } else {
    var simplifiedGeometries = [];
    var geometries = this.geometries_;
    var simplified = false;
    var i, ii;
    for (i = 0, ii = geometries.length; i < ii; ++i) {
      var geometry = geometries[i];
      var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
      simplifiedGeometries.push(simplifiedGeometry);
      if (simplifiedGeometry !== geometry) {
        simplified = true;
      }
    }
    if (simplified) {
      var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null);
      simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries);
      this.simplifiedGeometryCache[key] = simplifiedGeometryCollection;
      return simplifiedGeometryCollection;
    } else {
      this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
      return this;
    }
  }
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.GeometryCollection.prototype.getType = function() {
  return ol.geom.GeometryType.GEOMETRY_COLLECTION;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.GeometryCollection.prototype.intersectsExtent = function(extent) {
  var geometries = this.geometries_;
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    if (geometries[i].intersectsExtent(extent)) {
      return true;
    }
  }
  return false;
};


/**
 * @return {boolean} Is empty.
 */
ol.geom.GeometryCollection.prototype.isEmpty = function() {
  return this.geometries_.length === 0;
};


/**
 * @inheritDoc
 * @api
 */
ol.geom.GeometryCollection.prototype.rotate = function(angle, anchor) {
  var geometries = this.geometries_;
  for (var i = 0, ii = geometries.length; i < ii; ++i) {
    geometries[i].rotate(angle, anchor);
  }
  this.changed();
};


/**
 * @inheritDoc
 * @api
 */
ol.geom.GeometryCollection.prototype.scale = function(sx, opt_sy, opt_anchor) {
  var anchor = opt_anchor;
  if (!anchor) {
    anchor = ol.extent.getCenter(this.getExtent());
  }
  var geometries = this.geometries_;
  for (var i = 0, ii = geometries.length; i < ii; ++i) {
    geometries[i].scale(sx, opt_sy, anchor);
  }
  this.changed();
};


/**
 * Set the geometries that make up this geometry collection.
 * @param {Array.<ol.geom.Geometry>} geometries Geometries.
 * @api stable
 */
ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
  this.setGeometriesArray(
      ol.geom.GeometryCollection.cloneGeometries_(geometries));
};


/**
 * @param {Array.<ol.geom.Geometry>} geometries Geometries.
 */
ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
  this.unlistenGeometriesChange_();
  this.geometries_ = geometries;
  this.listenGeometriesChange_();
  this.changed();
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.GeometryCollection.prototype.applyTransform = function(transformFn) {
  var geometries = this.geometries_;
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    geometries[i].applyTransform(transformFn);
  }
  this.changed();
};


/**
 * Translate the geometry.
 * @param {number} deltaX Delta X.
 * @param {number} deltaY Delta Y.
 * @api
 */
ol.geom.GeometryCollection.prototype.translate = function(deltaX, deltaY) {
  var geometries = this.geometries_;
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    geometries[i].translate(deltaX, deltaY);
  }
  this.changed();
};


/**
 * @inheritDoc
 */
ol.geom.GeometryCollection.prototype.disposeInternal = function() {
  this.unlistenGeometriesChange_();
  ol.geom.Geometry.prototype.disposeInternal.call(this);
};

// TODO: serialize dataProjection as crs member when writing
// see https://github.com/openlayers/ol3/issues/2078

goog.provide('ol.format.GeoJSON');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.JSONFeature');
goog.require('ol.geom.GeometryCollection');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.obj');
goog.require('ol.proj');


/**
 * @classdesc
 * Feature format for reading and writing data in the GeoJSON format.
 *
 * @constructor
 * @extends {ol.format.JSONFeature}
 * @param {olx.format.GeoJSONOptions=} opt_options Options.
 * @api stable
 */
ol.format.GeoJSON = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.JSONFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get(
      options.defaultDataProjection ?
          options.defaultDataProjection : 'EPSG:4326');


  /**
   * Name of the geometry attribute for features.
   * @type {string|undefined}
   * @private
   */
  this.geometryName_ = options.geometryName;

};
ol.inherits(ol.format.GeoJSON, ol.format.JSONFeature);


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.GeoJSON.EXTENSIONS_ = ['.geojson'];


/**
 * @param {GeoJSONGeometry|GeoJSONGeometryCollection} object Object.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @private
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.GeoJSON.readGeometry_ = function(object, opt_options) {
  if (!object) {
    return null;
  }
  var geometryReader = ol.format.GeoJSON.GEOMETRY_READERS_[object.type];
  return /** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(
          geometryReader(object), false, opt_options));
};


/**
 * @param {GeoJSONGeometryCollection} object Object.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @private
 * @return {ol.geom.GeometryCollection} Geometry collection.
 */
ol.format.GeoJSON.readGeometryCollectionGeometry_ = function(
    object, opt_options) {
  goog.DEBUG && console.assert(object.type == 'GeometryCollection',
      'object.type should be GeometryCollection');
  var geometries = object.geometries.map(
      /**
       * @param {GeoJSONGeometry} geometry Geometry.
       * @return {ol.geom.Geometry} geometry Geometry.
       */
      function(geometry) {
        return ol.format.GeoJSON.readGeometry_(geometry, opt_options);
      });
  return new ol.geom.GeometryCollection(geometries);
};


/**
 * @param {GeoJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Point} Point.
 */
ol.format.GeoJSON.readPointGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.type == 'Point',
      'object.type should be Point');
  return new ol.geom.Point(object.coordinates);
};


/**
 * @param {GeoJSONGeometry} object Object.
 * @private
 * @return {ol.geom.LineString} LineString.
 */
ol.format.GeoJSON.readLineStringGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.type == 'LineString',
      'object.type should be LineString');
  return new ol.geom.LineString(object.coordinates);
};


/**
 * @param {GeoJSONGeometry} object Object.
 * @private
 * @return {ol.geom.MultiLineString} MultiLineString.
 */
ol.format.GeoJSON.readMultiLineStringGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.type == 'MultiLineString',
      'object.type should be MultiLineString');
  return new ol.geom.MultiLineString(object.coordinates);
};


/**
 * @param {GeoJSONGeometry} object Object.
 * @private
 * @return {ol.geom.MultiPoint} MultiPoint.
 */
ol.format.GeoJSON.readMultiPointGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.type == 'MultiPoint',
      'object.type should be MultiPoint');
  return new ol.geom.MultiPoint(object.coordinates);
};


/**
 * @param {GeoJSONGeometry} object Object.
 * @private
 * @return {ol.geom.MultiPolygon} MultiPolygon.
 */
ol.format.GeoJSON.readMultiPolygonGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.type == 'MultiPolygon',
      'object.type should be MultiPolygon');
  return new ol.geom.MultiPolygon(object.coordinates);
};


/**
 * @param {GeoJSONGeometry} object Object.
 * @private
 * @return {ol.geom.Polygon} Polygon.
 */
ol.format.GeoJSON.readPolygonGeometry_ = function(object) {
  goog.DEBUG && console.assert(object.type == 'Polygon',
      'object.type should be Polygon');
  return new ol.geom.Polygon(object.coordinates);
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry|GeoJSONGeometryCollection} GeoJSON geometry.
 */
ol.format.GeoJSON.writeGeometry_ = function(geometry, opt_options) {
  var geometryWriter = ol.format.GeoJSON.GEOMETRY_WRITERS_[geometry.getType()];
  return geometryWriter(/** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(geometry, true, opt_options)),
      opt_options);
};


/**
 * @param {ol.geom.Geometry} geometry Geometry.
 * @private
 * @return {GeoJSONGeometryCollection} Empty GeoJSON geometry collection.
 */
ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_ = function(geometry) {
  return /** @type {GeoJSONGeometryCollection} */ ({
    type: 'GeometryCollection',
    geometries: []
  });
};


/**
 * @param {ol.geom.GeometryCollection} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometryCollection} GeoJSON geometry collection.
 */
ol.format.GeoJSON.writeGeometryCollectionGeometry_ = function(
    geometry, opt_options) {
  var geometries = geometry.getGeometriesArray().map(function(geometry) {
    var options = ol.obj.assign({}, opt_options);
    delete options.featureProjection;
    return ol.format.GeoJSON.writeGeometry_(geometry, options);
  });
  return /** @type {GeoJSONGeometryCollection} */ ({
    type: 'GeometryCollection',
    geometries: geometries
  });
};


/**
 * @param {ol.geom.LineString} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry} GeoJSON geometry.
 */
ol.format.GeoJSON.writeLineStringGeometry_ = function(geometry, opt_options) {
  return /** @type {GeoJSONGeometry} */ ({
    type: 'LineString',
    coordinates: geometry.getCoordinates()
  });
};


/**
 * @param {ol.geom.MultiLineString} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry} GeoJSON geometry.
 */
ol.format.GeoJSON.writeMultiLineStringGeometry_ = function(geometry, opt_options) {
  return /** @type {GeoJSONGeometry} */ ({
    type: 'MultiLineString',
    coordinates: geometry.getCoordinates()
  });
};


/**
 * @param {ol.geom.MultiPoint} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry} GeoJSON geometry.
 */
ol.format.GeoJSON.writeMultiPointGeometry_ = function(geometry, opt_options) {
  return /** @type {GeoJSONGeometry} */ ({
    type: 'MultiPoint',
    coordinates: geometry.getCoordinates()
  });
};


/**
 * @param {ol.geom.MultiPolygon} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry} GeoJSON geometry.
 */
ol.format.GeoJSON.writeMultiPolygonGeometry_ = function(geometry, opt_options) {
  var right;
  if (opt_options) {
    right = opt_options.rightHanded;
  }
  return /** @type {GeoJSONGeometry} */ ({
    type: 'MultiPolygon',
    coordinates: geometry.getCoordinates(right)
  });
};


/**
 * @param {ol.geom.Point} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry} GeoJSON geometry.
 */
ol.format.GeoJSON.writePointGeometry_ = function(geometry, opt_options) {
  return /** @type {GeoJSONGeometry} */ ({
    type: 'Point',
    coordinates: geometry.getCoordinates()
  });
};


/**
 * @param {ol.geom.Polygon} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @private
 * @return {GeoJSONGeometry} GeoJSON geometry.
 */
ol.format.GeoJSON.writePolygonGeometry_ = function(geometry, opt_options) {
  var right;
  if (opt_options) {
    right = opt_options.rightHanded;
  }
  return /** @type {GeoJSONGeometry} */ ({
    type: 'Polygon',
    coordinates: geometry.getCoordinates(right)
  });
};


/**
 * @const
 * @private
 * @type {Object.<string, function(GeoJSONObject): ol.geom.Geometry>}
 */
ol.format.GeoJSON.GEOMETRY_READERS_ = {
  'Point': ol.format.GeoJSON.readPointGeometry_,
  'LineString': ol.format.GeoJSON.readLineStringGeometry_,
  'Polygon': ol.format.GeoJSON.readPolygonGeometry_,
  'MultiPoint': ol.format.GeoJSON.readMultiPointGeometry_,
  'MultiLineString': ol.format.GeoJSON.readMultiLineStringGeometry_,
  'MultiPolygon': ol.format.GeoJSON.readMultiPolygonGeometry_,
  'GeometryCollection': ol.format.GeoJSON.readGeometryCollectionGeometry_
};


/**
 * @const
 * @private
 * @type {Object.<string, function(ol.geom.Geometry, olx.format.WriteOptions=): (GeoJSONGeometry|GeoJSONGeometryCollection)>}
 */
ol.format.GeoJSON.GEOMETRY_WRITERS_ = {
  'Point': ol.format.GeoJSON.writePointGeometry_,
  'LineString': ol.format.GeoJSON.writeLineStringGeometry_,
  'Polygon': ol.format.GeoJSON.writePolygonGeometry_,
  'MultiPoint': ol.format.GeoJSON.writeMultiPointGeometry_,
  'MultiLineString': ol.format.GeoJSON.writeMultiLineStringGeometry_,
  'MultiPolygon': ol.format.GeoJSON.writeMultiPolygonGeometry_,
  'GeometryCollection': ol.format.GeoJSON.writeGeometryCollectionGeometry_,
  'Circle': ol.format.GeoJSON.writeEmptyGeometryCollectionGeometry_
};


/**
 * @inheritDoc
 */
ol.format.GeoJSON.prototype.getExtensions = function() {
  return ol.format.GeoJSON.EXTENSIONS_;
};


/**
 * Read a feature from a GeoJSON Feature source.  Only works for Feature,
 * use `readFeatures` to read FeatureCollection source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api stable
 */
ol.format.GeoJSON.prototype.readFeature;


/**
 * Read all features from a GeoJSON source.  Works with both Feature and
 * FeatureCollection sources.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.GeoJSON.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.GeoJSON.prototype.readFeatureFromObject = function(
    object, opt_options) {
  var geoJSONFeature = /** @type {GeoJSONFeature} */ (object);
  goog.DEBUG && console.assert(geoJSONFeature.type == 'Feature',
      'geoJSONFeature.type should be Feature');
  var geometry = ol.format.GeoJSON.readGeometry_(geoJSONFeature.geometry,
      opt_options);
  var feature = new ol.Feature();
  if (this.geometryName_) {
    feature.setGeometryName(this.geometryName_);
  }
  feature.setGeometry(geometry);
  if (geoJSONFeature.id !== undefined) {
    feature.setId(geoJSONFeature.id);
  }
  if (geoJSONFeature.properties) {
    feature.setProperties(geoJSONFeature.properties);
  }
  return feature;
};


/**
 * @inheritDoc
 */
ol.format.GeoJSON.prototype.readFeaturesFromObject = function(
    object, opt_options) {
  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
  /** @type {Array.<ol.Feature>} */
  var features;
  if (geoJSONObject.type == 'Feature') {
    features = [this.readFeatureFromObject(object, opt_options)];
  } else if (geoJSONObject.type == 'FeatureCollection') {
    var geoJSONFeatureCollection = /** @type {GeoJSONFeatureCollection} */
        (object);
    features = [];
    var geoJSONFeatures = geoJSONFeatureCollection.features;
    var i, ii;
    for (i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
      features.push(this.readFeatureFromObject(geoJSONFeatures[i],
          opt_options));
    }
  } else {
    ol.asserts.assert(false, 35); // Unknown GeoJSON object type
  }
  return /** Array.<ol.Feature> */ (features);
};


/**
 * Read a geometry from a GeoJSON source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.geom.Geometry} Geometry.
 * @api stable
 */
ol.format.GeoJSON.prototype.readGeometry;


/**
 * @inheritDoc
 */
ol.format.GeoJSON.prototype.readGeometryFromObject = function(
    object, opt_options) {
  return ol.format.GeoJSON.readGeometry_(
      /** @type {GeoJSONGeometry} */ (object), opt_options);
};


/**
 * Read the projection from a GeoJSON source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.GeoJSON.prototype.readProjection;


/**
 * @inheritDoc
 */
ol.format.GeoJSON.prototype.readProjectionFromObject = function(object) {
  var geoJSONObject = /** @type {GeoJSONObject} */ (object);
  var crs = geoJSONObject.crs;
  var projection;
  if (crs) {
    if (crs.type == 'name') {
      projection = ol.proj.get(crs.properties.name);
    } else if (crs.type == 'EPSG') {
      // 'EPSG' is not part of the GeoJSON specification, but is generated by
      // GeoServer.
      // TODO: remove this when http://jira.codehaus.org/browse/GEOS-5996
      // is fixed and widely deployed.
      projection = ol.proj.get('EPSG:' + crs.properties.code);
    } else {
      ol.asserts.assert(false, 36); // Unknown SRS type
    }
  } else {
    projection = this.defaultDataProjection;
  }
  return /** @type {ol.proj.Projection} */ (projection);
};


/**
 * Encode a feature as a GeoJSON Feature string.
 *
 * @function
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} GeoJSON.
 * @api stable
 */
ol.format.GeoJSON.prototype.writeFeature;


/**
 * Encode a feature as a GeoJSON Feature object.
 *
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {GeoJSONFeature} Object.
 * @api stable
 */
ol.format.GeoJSON.prototype.writeFeatureObject = function(feature, opt_options) {
  opt_options = this.adaptOptions(opt_options);

  var object = /** @type {GeoJSONFeature} */ ({
    'type': 'Feature'
  });
  var id = feature.getId();
  if (id !== undefined) {
    object.id = id;
  }
  var geometry = feature.getGeometry();
  if (geometry) {
    object.geometry =
        ol.format.GeoJSON.writeGeometry_(geometry, opt_options);
  } else {
    object.geometry = null;
  }
  var properties = feature.getProperties();
  delete properties[feature.getGeometryName()];
  if (!ol.obj.isEmpty(properties)) {
    object.properties = properties;
  } else {
    object.properties = null;
  }
  return object;
};


/**
 * Encode an array of features as GeoJSON.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} GeoJSON.
 * @api stable
 */
ol.format.GeoJSON.prototype.writeFeatures;


/**
 * Encode an array of features as a GeoJSON object.
 *
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {GeoJSONFeatureCollection} GeoJSON Object.
 * @api stable
 */
ol.format.GeoJSON.prototype.writeFeaturesObject = function(features, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  var objects = [];
  var i, ii;
  for (i = 0, ii = features.length; i < ii; ++i) {
    objects.push(this.writeFeatureObject(features[i], opt_options));
  }
  return /** @type {GeoJSONFeatureCollection} */ ({
    type: 'FeatureCollection',
    features: objects
  });
};


/**
 * Encode a geometry as a GeoJSON string.
 *
 * @function
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} GeoJSON.
 * @api stable
 */
ol.format.GeoJSON.prototype.writeGeometry;


/**
 * Encode a geometry as a GeoJSON object.
 *
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {GeoJSONGeometry|GeoJSONGeometryCollection} Object.
 * @api stable
 */
ol.format.GeoJSON.prototype.writeGeometryObject = function(geometry,
    opt_options) {
  return ol.format.GeoJSON.writeGeometry_(geometry,
      this.adaptOptions(opt_options));
};

goog.provide('ol.format.XMLFeature');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.format.Feature');
goog.require('ol.format.FormatType');
goog.require('ol.xml');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for XML feature formats.
 *
 * @constructor
 * @extends {ol.format.Feature}
 */
ol.format.XMLFeature = function() {

  /**
   * @type {XMLSerializer}
   * @private
   */
  this.xmlSerializer_ = new XMLSerializer();

  ol.format.Feature.call(this);
};
ol.inherits(ol.format.XMLFeature, ol.format.Feature);


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.getType = function() {
  return ol.format.FormatType.XML;
};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.readFeature = function(source, opt_options) {
  if (ol.xml.isDocument(source)) {
    return this.readFeatureFromDocument(
        /** @type {Document} */ (source), opt_options);
  } else if (ol.xml.isNode(source)) {
    return this.readFeatureFromNode(/** @type {Node} */ (source), opt_options);
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readFeatureFromDocument(doc, opt_options);
  } else {
    return null;
  }
};


/**
 * @param {Document} doc Document.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @return {ol.Feature} Feature.
 */
ol.format.XMLFeature.prototype.readFeatureFromDocument = function(
    doc, opt_options) {
  var features = this.readFeaturesFromDocument(doc, opt_options);
  if (features.length > 0) {
    return features[0];
  } else {
    return null;
  }
};


/**
 * @abstract
 * @param {Node} node Node.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @return {ol.Feature} Feature.
 */
ol.format.XMLFeature.prototype.readFeatureFromNode = function(node, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.readFeatures = function(source, opt_options) {
  if (ol.xml.isDocument(source)) {
    return this.readFeaturesFromDocument(
        /** @type {Document} */ (source), opt_options);
  } else if (ol.xml.isNode(source)) {
    return this.readFeaturesFromNode(/** @type {Node} */ (source), opt_options);
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readFeaturesFromDocument(doc, opt_options);
  } else {
    return [];
  }
};


/**
 * @param {Document} doc Document.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @protected
 * @return {Array.<ol.Feature>} Features.
 */
ol.format.XMLFeature.prototype.readFeaturesFromDocument = function(
    doc, opt_options) {
  /** @type {Array.<ol.Feature>} */
  var features = [];
  var n;
  for (n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      ol.array.extend(features, this.readFeaturesFromNode(n, opt_options));
    }
  }
  return features;
};


/**
 * @abstract
 * @param {Node} node Node.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @protected
 * @return {Array.<ol.Feature>} Features.
 */
ol.format.XMLFeature.prototype.readFeaturesFromNode = function(node, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.readGeometry = function(source, opt_options) {
  if (ol.xml.isDocument(source)) {
    return this.readGeometryFromDocument(
        /** @type {Document} */ (source), opt_options);
  } else if (ol.xml.isNode(source)) {
    return this.readGeometryFromNode(/** @type {Node} */ (source), opt_options);
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readGeometryFromDocument(doc, opt_options);
  } else {
    return null;
  }
};


/**
 * @abstract
 * @param {Document} doc Document.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @protected
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.XMLFeature.prototype.readGeometryFromDocument = function(doc, opt_options) {};


/**
 * @abstract
 * @param {Node} node Node.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @protected
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.XMLFeature.prototype.readGeometryFromNode = function(node, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.readProjection = function(source) {
  if (ol.xml.isDocument(source)) {
    return this.readProjectionFromDocument(/** @type {Document} */ (source));
  } else if (ol.xml.isNode(source)) {
    return this.readProjectionFromNode(/** @type {Node} */ (source));
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readProjectionFromDocument(doc);
  } else {
    return null;
  }
};


/**
 * @param {Document} doc Document.
 * @protected
 * @return {ol.proj.Projection} Projection.
 */
ol.format.XMLFeature.prototype.readProjectionFromDocument = function(doc) {
  return this.defaultDataProjection;
};


/**
 * @param {Node} node Node.
 * @protected
 * @return {ol.proj.Projection} Projection.
 */
ol.format.XMLFeature.prototype.readProjectionFromNode = function(node) {
  return this.defaultDataProjection;
};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.writeFeature = function(feature, opt_options) {
  var node = this.writeFeatureNode(feature, opt_options);
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return this.xmlSerializer_.serializeToString(node);
};


/**
 * @abstract
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @protected
 * @return {Node} Node.
 */
ol.format.XMLFeature.prototype.writeFeatureNode = function(feature, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.writeFeatures = function(features, opt_options) {
  var node = this.writeFeaturesNode(features, opt_options);
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return this.xmlSerializer_.serializeToString(node);
};


/**
 * @abstract
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 */
ol.format.XMLFeature.prototype.writeFeaturesNode = function(features, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.XMLFeature.prototype.writeGeometry = function(geometry, opt_options) {
  var node = this.writeGeometryNode(geometry, opt_options);
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return this.xmlSerializer_.serializeToString(node);
};


/**
 * @abstract
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 */
ol.format.XMLFeature.prototype.writeGeometryNode = function(geometry, opt_options) {};

// FIXME Envelopes should not be treated as geometries! readEnvelope_ is part
// of GEOMETRY_PARSERS_ and methods using GEOMETRY_PARSERS_ do not expect
// envelopes/extents, only geometries!
goog.provide('ol.format.GMLBase');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.XMLFeature');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.xml');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Feature base format for reading and writing data in the GML format.
 * This class cannot be instantiated, it contains only base content that
 * is shared with versioned format classes ol.format.GML2 and
 * ol.format.GML3.
 *
 * @constructor
 * @param {olx.format.GMLOptions=} opt_options
 *     Optional configuration object.
 * @extends {ol.format.XMLFeature}
 */
ol.format.GMLBase = function(opt_options) {
  var options = /** @type {olx.format.GMLOptions} */
      (opt_options ? opt_options : {});

  /**
   * @protected
   * @type {Array.<string>|string|undefined}
   */
  this.featureType = options.featureType;

  /**
   * @protected
   * @type {Object.<string, string>|string|undefined}
   */
  this.featureNS = options.featureNS;

  /**
   * @protected
   * @type {string}
   */
  this.srsName = options.srsName;

  /**
   * @protected
   * @type {string}
   */
  this.schemaLocation = '';

  /**
   * @type {Object.<string, Object.<string, Object>>}
   */
  this.FEATURE_COLLECTION_PARSERS = {};
  this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS] = {
    'featureMember': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readFeaturesInternal),
    'featureMembers': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readFeaturesInternal)
  };

  ol.format.XMLFeature.call(this);
};
ol.inherits(ol.format.GMLBase, ol.format.XMLFeature);


/**
 * @const
 * @type {string}
 */
ol.format.GMLBase.GMLNS = 'http://www.opengis.net/gml';


/**
 * A regular expression that matches if a string only contains whitespace
 * characters. It will e.g. match `''`, `' '`, `'\n'` etc. The non-breaking
 * space (0xa0) is explicitly included as IE doesn't include it in its
 * definition of `\s`.
 *
 * Information from `goog.string.isEmptyOrWhitespace`: https://github.com/google/closure-library/blob/e877b1e/closure/goog/string/string.js#L156-L160
 *
 * @const
 * @type {RegExp}
 * @private
 */
ol.format.GMLBase.ONLY_WHITESPACE_RE_ = /^[\s\xa0]*$/;


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Array.<ol.Feature> | undefined} Features.
 */
ol.format.GMLBase.prototype.readFeaturesInternal = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  var localName = node.localName;
  var features = null;
  if (localName == 'FeatureCollection') {
    if (node.namespaceURI === 'http://www.opengis.net/wfs') {
      features = ol.xml.pushParseAndPop([],
          this.FEATURE_COLLECTION_PARSERS, node,
          objectStack, this);
    } else {
      features = ol.xml.pushParseAndPop(null,
          this.FEATURE_COLLECTION_PARSERS, node,
          objectStack, this);
    }
  } else if (localName == 'featureMembers' || localName == 'featureMember') {
    var context = objectStack[0];
    var featureType = context['featureType'];
    var featureNS = context['featureNS'];
    var i, ii, prefix = 'p', defaultPrefix = 'p0';
    if (!featureType && node.childNodes) {
      featureType = [], featureNS = {};
      for (i = 0, ii = node.childNodes.length; i < ii; ++i) {
        var child = node.childNodes[i];
        if (child.nodeType === 1) {
          var ft = child.nodeName.split(':').pop();
          if (featureType.indexOf(ft) === -1) {
            var key = '';
            var count = 0;
            var uri = child.namespaceURI;
            for (var candidate in featureNS) {
              if (featureNS[candidate] === uri) {
                key = candidate;
                break;
              }
              ++count;
            }
            if (!key) {
              key = prefix + count;
              featureNS[key] = uri;
            }
            featureType.push(key + ':' + ft);
          }
        }
      }
      if (localName != 'featureMember') {
        // recheck featureType for each featureMember
        context['featureType'] = featureType;
        context['featureNS'] = featureNS;
      }
    }
    if (typeof featureNS === 'string') {
      var ns = featureNS;
      featureNS = {};
      featureNS[defaultPrefix] = ns;
    }
    var parsersNS = {};
    var featureTypes = Array.isArray(featureType) ? featureType : [featureType];
    for (var p in featureNS) {
      var parsers = {};
      for (i = 0, ii = featureTypes.length; i < ii; ++i) {
        var featurePrefix = featureTypes[i].indexOf(':') === -1 ?
            defaultPrefix : featureTypes[i].split(':')[0];
        if (featurePrefix === p) {
          parsers[featureTypes[i].split(':').pop()] =
              (localName == 'featureMembers') ?
              ol.xml.makeArrayPusher(this.readFeatureElement, this) :
              ol.xml.makeReplacer(this.readFeatureElement, this);
        }
      }
      parsersNS[featureNS[p]] = parsers;
    }
    if (localName == 'featureMember') {
      features = ol.xml.pushParseAndPop(undefined, parsersNS, node, objectStack);
    } else {
      features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack);
    }
  }
  if (features === null) {
    features = [];
  }
  return features;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.Geometry|undefined} Geometry.
 */
ol.format.GMLBase.prototype.readGeometryElement = function(node, objectStack) {
  var context = /** @type {Object} */ (objectStack[0]);
  context['srsName'] = node.firstElementChild.getAttribute('srsName');
  /** @type {ol.geom.Geometry} */
  var geometry = ol.xml.pushParseAndPop(null,
      this.GEOMETRY_PARSERS_, node, objectStack, this);
  if (geometry) {
    return /** @type {ol.geom.Geometry} */ (
        ol.format.Feature.transformWithOptions(geometry, false, context));
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.Feature} Feature.
 */
ol.format.GMLBase.prototype.readFeatureElement = function(node, objectStack) {
  var n;
  var fid = node.getAttribute('fid') ||
      ol.xml.getAttributeNS(node, ol.format.GMLBase.GMLNS, 'id');
  var values = {}, geometryName;
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    var localName = n.localName;
    // Assume attribute elements have one child node and that the child
    // is a text or CDATA node (to be treated as text).
    // Otherwise assume it is a geometry node.
    if (n.childNodes.length === 0 ||
        (n.childNodes.length === 1 &&
        (n.firstChild.nodeType === 3 || n.firstChild.nodeType === 4))) {
      var value = ol.xml.getAllTextContent(n, false);
      if (ol.format.GMLBase.ONLY_WHITESPACE_RE_.test(value)) {
        value = undefined;
      }
      values[localName] = value;
    } else {
      // boundedBy is an extent and must not be considered as a geometry
      if (localName !== 'boundedBy') {
        geometryName = localName;
      }
      values[localName] = this.readGeometryElement(n, objectStack);
    }
  }
  var feature = new ol.Feature(values);
  if (geometryName) {
    feature.setGeometryName(geometryName);
  }
  if (fid) {
    feature.setId(fid);
  }
  return feature;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.Point|undefined} Point.
 */
ol.format.GMLBase.prototype.readPoint = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point');
  var flatCoordinates =
      this.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var point = new ol.geom.Point(null);
    goog.DEBUG && console.assert(flatCoordinates.length == 3,
        'flatCoordinates should have a length of 3');
    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
    return point;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.MultiPoint|undefined} MultiPoint.
 */
ol.format.GMLBase.prototype.readMultiPoint = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MultiPoint',
      'localName should be MultiPoint');
  /** @type {Array.<Array.<number>>} */
  var coordinates = ol.xml.pushParseAndPop([],
      this.MULTIPOINT_PARSERS_, node, objectStack, this);
  if (coordinates) {
    return new ol.geom.MultiPoint(coordinates);
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.MultiLineString|undefined} MultiLineString.
 */
ol.format.GMLBase.prototype.readMultiLineString = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MultiLineString',
      'localName should be MultiLineString');
  /** @type {Array.<ol.geom.LineString>} */
  var lineStrings = ol.xml.pushParseAndPop([],
      this.MULTILINESTRING_PARSERS_, node, objectStack, this);
  if (lineStrings) {
    var multiLineString = new ol.geom.MultiLineString(null);
    multiLineString.setLineStrings(lineStrings);
    return multiLineString;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
 */
ol.format.GMLBase.prototype.readMultiPolygon = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MultiPolygon',
      'localName should be MultiPolygon');
  /** @type {Array.<ol.geom.Polygon>} */
  var polygons = ol.xml.pushParseAndPop([],
      this.MULTIPOLYGON_PARSERS_, node, objectStack, this);
  if (polygons) {
    var multiPolygon = new ol.geom.MultiPolygon(null);
    multiPolygon.setPolygons(polygons);
    return multiPolygon;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GMLBase.prototype.pointMemberParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'pointMember' ||
      node.localName == 'pointMembers',
      'localName should be pointMember or pointMembers');
  ol.xml.parseNode(this.POINTMEMBER_PARSERS_,
      node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GMLBase.prototype.lineStringMemberParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'lineStringMember' ||
      node.localName == 'lineStringMembers',
      'localName should be LineStringMember or LineStringMembers');
  ol.xml.parseNode(this.LINESTRINGMEMBER_PARSERS_,
      node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GMLBase.prototype.polygonMemberParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'polygonMember' ||
      node.localName == 'polygonMembers',
      'localName should be polygonMember or polygonMembers');
  ol.xml.parseNode(this.POLYGONMEMBER_PARSERS_, node,
      objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.LineString|undefined} LineString.
 */
ol.format.GMLBase.prototype.readLineString = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LineString',
      'localName should be LineString');
  var flatCoordinates =
      this.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var lineString = new ol.geom.LineString(null);
    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
    return lineString;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} LinearRing flat coordinates.
 */
ol.format.GMLBase.prototype.readFlatLinearRing_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LinearRing',
      'localName should be LinearRing');
  var ring = ol.xml.pushParseAndPop(null,
      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
      objectStack, this);
  if (ring) {
    return ring;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.LinearRing|undefined} LinearRing.
 */
ol.format.GMLBase.prototype.readLinearRing = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LinearRing',
      'localName should be LinearRing');
  var flatCoordinates =
      this.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var ring = new ol.geom.LinearRing(null);
    ring.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
    return ring;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.geom.Polygon|undefined} Polygon.
 */
ol.format.GMLBase.prototype.readPolygon = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Polygon',
      'localName should be Polygon');
  /** @type {Array.<Array.<number>>} */
  var flatLinearRings = ol.xml.pushParseAndPop([null],
      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
  if (flatLinearRings && flatLinearRings[0]) {
    var polygon = new ol.geom.Polygon(null);
    var flatCoordinates = flatLinearRings[0];
    var ends = [flatCoordinates.length];
    var i, ii;
    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
      ol.array.extend(flatCoordinates, flatLinearRings[i]);
      ends.push(flatCoordinates.length);
    }
    polygon.setFlatCoordinates(
        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
    return polygon;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>} Flat coordinates.
 */
ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return ol.xml.pushParseAndPop(null,
      this.GEOMETRY_FLAT_COORDINATES_PARSERS_, node,
      objectStack, this);
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GMLBase.prototype.MULTIPOINT_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'pointMember': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.pointMemberParser_),
    'pointMembers': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.pointMemberParser_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GMLBase.prototype.MULTILINESTRING_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'lineStringMember': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.lineStringMemberParser_),
    'lineStringMembers': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.lineStringMemberParser_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GMLBase.prototype.MULTIPOLYGON_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'polygonMember': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.polygonMemberParser_),
    'polygonMembers': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.polygonMemberParser_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GMLBase.prototype.POINTMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'Point': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.readFlatCoordinatesFromNode_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GMLBase.prototype.LINESTRINGMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'LineString': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.readLineString)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GMLBase.prototype.POLYGONMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'Polygon': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.readPolygon)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @protected
 */
ol.format.GMLBase.prototype.RING_PARSERS = {
  'http://www.opengis.net/gml' : {
    'LinearRing': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readFlatLinearRing_)
  }
};


/**
 * @inheritDoc
 */
ol.format.GMLBase.prototype.readGeometryFromNode = function(node, opt_options) {
  var geometry = this.readGeometryElement(node,
      [this.getReadOptions(node, opt_options ? opt_options : {})]);
  return geometry ? geometry : null;
};


/**
 * Read all features from a GML FeatureCollection.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.GMLBase.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.GMLBase.prototype.readFeaturesFromNode = function(node, opt_options) {
  var options = {
    featureType: this.featureType,
    featureNS: this.featureNS
  };
  if (opt_options) {
    ol.obj.assign(options, this.getReadOptions(node, opt_options));
  }
  var features = this.readFeaturesInternal(node, [options]);
  return features || [];
};


/**
 * @inheritDoc
 */
ol.format.GMLBase.prototype.readProjectionFromNode = function(node) {
  return ol.proj.get(this.srsName ? this.srsName :
      node.firstElementChild.getAttribute('srsName'));
};

goog.provide('ol.format.XSD');

goog.require('ol');
goog.require('ol.xml');
goog.require('ol.string');


/**
 * @const
 * @type {string}
 */
ol.format.XSD.NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema';


/**
 * @param {Node} node Node.
 * @return {boolean|undefined} Boolean.
 */
ol.format.XSD.readBoolean = function(node) {
  var s = ol.xml.getAllTextContent(node, false);
  return ol.format.XSD.readBooleanString(s);
};


/**
 * @param {string} string String.
 * @return {boolean|undefined} Boolean.
 */
ol.format.XSD.readBooleanString = function(string) {
  var m = /^\s*(true|1)|(false|0)\s*$/.exec(string);
  if (m) {
    return m[1] !== undefined || false;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @return {number|undefined} DateTime in seconds.
 */
ol.format.XSD.readDateTime = function(node) {
  var s = ol.xml.getAllTextContent(node, false);
  var dateTime = Date.parse(s);
  return isNaN(dateTime) ? undefined : dateTime / 1000;
};


/**
 * @param {Node} node Node.
 * @return {number|undefined} Decimal.
 */
ol.format.XSD.readDecimal = function(node) {
  var s = ol.xml.getAllTextContent(node, false);
  return ol.format.XSD.readDecimalString(s);
};


/**
 * @param {string} string String.
 * @return {number|undefined} Decimal.
 */
ol.format.XSD.readDecimalString = function(string) {
  // FIXME check spec
  var m = /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*$/i.exec(string);
  if (m) {
    return parseFloat(m[1]);
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @return {number|undefined} Non negative integer.
 */
ol.format.XSD.readNonNegativeInteger = function(node) {
  var s = ol.xml.getAllTextContent(node, false);
  return ol.format.XSD.readNonNegativeIntegerString(s);
};


/**
 * @param {string} string String.
 * @return {number|undefined} Non negative integer.
 */
ol.format.XSD.readNonNegativeIntegerString = function(string) {
  var m = /^\s*(\d+)\s*$/.exec(string);
  if (m) {
    return parseInt(m[1], 10);
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @return {string|undefined} String.
 */
ol.format.XSD.readString = function(node) {
  return ol.xml.getAllTextContent(node, false).trim();
};


/**
 * @param {Node} node Node to append a TextNode with the boolean to.
 * @param {boolean} bool Boolean.
 */
ol.format.XSD.writeBooleanTextNode = function(node, bool) {
  ol.format.XSD.writeStringTextNode(node, (bool) ? '1' : '0');
};


/**
 * @param {Node} node Node to append a TextNode with the dateTime to.
 * @param {number} dateTime DateTime in seconds.
 */
ol.format.XSD.writeDateTimeTextNode = function(node, dateTime) {
  var date = new Date(dateTime * 1000);
  var string = date.getUTCFullYear() + '-' +
      ol.string.padNumber(date.getUTCMonth() + 1, 2) + '-' +
      ol.string.padNumber(date.getUTCDate(), 2) + 'T' +
      ol.string.padNumber(date.getUTCHours(), 2) + ':' +
      ol.string.padNumber(date.getUTCMinutes(), 2) + ':' +
      ol.string.padNumber(date.getUTCSeconds(), 2) + 'Z';
  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
};


/**
 * @param {Node} node Node to append a TextNode with the decimal to.
 * @param {number} decimal Decimal.
 */
ol.format.XSD.writeDecimalTextNode = function(node, decimal) {
  var string = decimal.toPrecision();
  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
};


/**
 * @param {Node} node Node to append a TextNode with the decimal to.
 * @param {number} nonNegativeInteger Non negative integer.
 */
ol.format.XSD.writeNonNegativeIntegerTextNode = function(node, nonNegativeInteger) {
  goog.DEBUG && console.assert(nonNegativeInteger >= 0, 'value should be more than 0');
  goog.DEBUG && console.assert(nonNegativeInteger == (nonNegativeInteger | 0),
      'value should be an integer value');
  var string = nonNegativeInteger.toString();
  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
};


/**
 * @param {Node} node Node to append a TextNode with the string to.
 * @param {string} string String.
 */
ol.format.XSD.writeStringTextNode = function(node, string) {
  node.appendChild(ol.xml.DOCUMENT.createTextNode(string));
};

goog.provide('ol.format.GML3');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.format.Feature');
goog.require('ol.format.GMLBase');
goog.require('ol.format.XSD');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Polygon');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.xml');


/**
 * @classdesc
 * Feature format for reading and writing data in the GML format
 * version 3.1.1.
 * Currently only supports GML 3.1.1 Simple Features profile.
 *
 * @constructor
 * @param {olx.format.GMLOptions=} opt_options
 *     Optional configuration object.
 * @extends {ol.format.GMLBase}
 * @api
 */
ol.format.GML3 = function(opt_options) {
  var options = /** @type {olx.format.GMLOptions} */
      (opt_options ? opt_options : {});

  ol.format.GMLBase.call(this, options);

  /**
   * @private
   * @type {boolean}
   */
  this.surface_ = options.surface !== undefined ? options.surface : false;

  /**
   * @private
   * @type {boolean}
   */
  this.curve_ = options.curve !== undefined ? options.curve : false;

  /**
   * @private
   * @type {boolean}
   */
  this.multiCurve_ = options.multiCurve !== undefined ?
      options.multiCurve : true;

  /**
   * @private
   * @type {boolean}
   */
  this.multiSurface_ = options.multiSurface !== undefined ?
      options.multiSurface : true;

  /**
   * @inheritDoc
   */
  this.schemaLocation = options.schemaLocation ?
      options.schemaLocation : ol.format.GML3.schemaLocation_;

};
ol.inherits(ol.format.GML3, ol.format.GMLBase);


/**
 * @const
 * @type {string}
 * @private
 */
ol.format.GML3.schemaLocation_ = ol.format.GMLBase.GMLNS +
    ' http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/' +
    '1.0.0/gmlsf.xsd';


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.MultiLineString|undefined} MultiLineString.
 */
ol.format.GML3.prototype.readMultiCurve_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MultiCurve',
      'localName should be MultiCurve');
  /** @type {Array.<ol.geom.LineString>} */
  var lineStrings = ol.xml.pushParseAndPop([],
      this.MULTICURVE_PARSERS_, node, objectStack, this);
  if (lineStrings) {
    var multiLineString = new ol.geom.MultiLineString(null);
    multiLineString.setLineStrings(lineStrings);
    return multiLineString;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.MultiPolygon|undefined} MultiPolygon.
 */
ol.format.GML3.prototype.readMultiSurface_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MultiSurface',
      'localName should be MultiSurface');
  /** @type {Array.<ol.geom.Polygon>} */
  var polygons = ol.xml.pushParseAndPop([],
      this.MULTISURFACE_PARSERS_, node, objectStack, this);
  if (polygons) {
    var multiPolygon = new ol.geom.MultiPolygon(null);
    multiPolygon.setPolygons(polygons);
    return multiPolygon;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GML3.prototype.curveMemberParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'curveMember' ||
      node.localName == 'curveMembers',
      'localName should be curveMember or curveMembers');
  ol.xml.parseNode(this.CURVEMEMBER_PARSERS_, node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GML3.prototype.surfaceMemberParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'surfaceMember' ||
      node.localName == 'surfaceMembers',
      'localName should be surfaceMember or surfaceMembers');
  ol.xml.parseNode(this.SURFACEMEMBER_PARSERS_,
      node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
 */
ol.format.GML3.prototype.readPatch_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'patches',
      'localName should be patches');
  return ol.xml.pushParseAndPop([null],
      this.PATCHES_PARSERS_, node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} flat coordinates.
 */
ol.format.GML3.prototype.readSegment_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'segments',
      'localName should be segments');
  return ol.xml.pushParseAndPop([null],
      this.SEGMENTS_PARSERS_, node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<(Array.<number>)>|undefined} flat coordinates.
 */
ol.format.GML3.prototype.readPolygonPatch_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'npde.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'PolygonPatch',
      'localName should be PolygonPatch');
  return ol.xml.pushParseAndPop([null],
      this.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} flat coordinates.
 */
ol.format.GML3.prototype.readLineStringSegment_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LineStringSegment',
      'localName should be LineStringSegment');
  return ol.xml.pushParseAndPop([null],
      this.GEOMETRY_FLAT_COORDINATES_PARSERS_,
      node, objectStack, this);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GML3.prototype.interiorParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'interior',
      'localName should be interior');
  /** @type {Array.<number>|undefined} */
  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
      this.RING_PARSERS, node, objectStack, this);
  if (flatLinearRing) {
    var flatLinearRings = /** @type {Array.<Array.<number>>} */
        (objectStack[objectStack.length - 1]);
    goog.DEBUG && console.assert(Array.isArray(flatLinearRings),
        'flatLinearRings should be an array');
    goog.DEBUG && console.assert(flatLinearRings.length > 0,
        'flatLinearRings should have an array length of 1 or more');
    flatLinearRings.push(flatLinearRing);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GML3.prototype.exteriorParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'exterior',
      'localName should be exterior');
   /** @type {Array.<number>|undefined} */
  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
      this.RING_PARSERS, node, objectStack, this);
  if (flatLinearRing) {
    var flatLinearRings = /** @type {Array.<Array.<number>>} */
        (objectStack[objectStack.length - 1]);
    goog.DEBUG && console.assert(Array.isArray(flatLinearRings),
        'flatLinearRings should be an array');
    goog.DEBUG && console.assert(flatLinearRings.length > 0,
        'flatLinearRings should have an array length of 1 or more');
    flatLinearRings[0] = flatLinearRing;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.Polygon|undefined} Polygon.
 */
ol.format.GML3.prototype.readSurface_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Surface',
      'localName should be Surface');
  /** @type {Array.<Array.<number>>} */
  var flatLinearRings = ol.xml.pushParseAndPop([null],
      this.SURFACE_PARSERS_, node, objectStack, this);
  if (flatLinearRings && flatLinearRings[0]) {
    var polygon = new ol.geom.Polygon(null);
    var flatCoordinates = flatLinearRings[0];
    var ends = [flatCoordinates.length];
    var i, ii;
    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
      ol.array.extend(flatCoordinates, flatLinearRings[i]);
      ends.push(flatCoordinates.length);
    }
    polygon.setFlatCoordinates(
        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
    return polygon;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.LineString|undefined} LineString.
 */
ol.format.GML3.prototype.readCurve_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Curve', 'localName should be Curve');
  /** @type {Array.<number>} */
  var flatCoordinates = ol.xml.pushParseAndPop([null],
      this.CURVE_PARSERS_, node, objectStack, this);
  if (flatCoordinates) {
    var lineString = new ol.geom.LineString(null);
    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
    return lineString;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.Extent|undefined} Envelope.
 */
ol.format.GML3.prototype.readEnvelope_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Envelope',
      'localName should be Envelope');
  /** @type {Array.<number>} */
  var flatCoordinates = ol.xml.pushParseAndPop([null],
      this.ENVELOPE_PARSERS_, node, objectStack, this);
  return ol.extent.createOrUpdate(flatCoordinates[1][0],
      flatCoordinates[1][1], flatCoordinates[2][0],
      flatCoordinates[2][1]);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} Flat coordinates.
 */
ol.format.GML3.prototype.readFlatPos_ = function(node, objectStack) {
  var s = ol.xml.getAllTextContent(node, false);
  var re = /^\s*([+\-]?\d*\.?\d+(?:[eE][+\-]?\d+)?)\s*/;
  /** @type {Array.<number>} */
  var flatCoordinates = [];
  var m;
  while ((m = re.exec(s))) {
    flatCoordinates.push(parseFloat(m[1]));
    s = s.substr(m[0].length);
  }
  if (s !== '') {
    return undefined;
  }
  var context = objectStack[0];
  var containerSrs = context['srsName'];
  var axisOrientation = 'enu';
  if (containerSrs) {
    var proj = ol.proj.get(containerSrs);
    axisOrientation = proj.getAxisOrientation();
  }
  if (axisOrientation === 'neu') {
    var i, ii;
    for (i = 0, ii = flatCoordinates.length; i < ii; i += 3) {
      var y = flatCoordinates[i];
      var x = flatCoordinates[i + 1];
      flatCoordinates[i] = x;
      flatCoordinates[i + 1] = y;
    }
  }
  var len = flatCoordinates.length;
  if (len == 2) {
    flatCoordinates.push(0);
  }
  if (len === 0) {
    return undefined;
  }
  return flatCoordinates;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} Flat coordinates.
 */
ol.format.GML3.prototype.readFlatPosList_ = function(node, objectStack) {
  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
  var context = objectStack[0];
  var containerSrs = context['srsName'];
  var containerDimension = node.parentNode.getAttribute('srsDimension');
  var axisOrientation = 'enu';
  if (containerSrs) {
    var proj = ol.proj.get(containerSrs);
    axisOrientation = proj.getAxisOrientation();
  }
  var coords = s.split(/\s+/);
  // The "dimension" attribute is from the GML 3.0.1 spec.
  var dim = 2;
  if (node.getAttribute('srsDimension')) {
    dim = ol.format.XSD.readNonNegativeIntegerString(
        node.getAttribute('srsDimension'));
  } else if (node.getAttribute('dimension')) {
    dim = ol.format.XSD.readNonNegativeIntegerString(
        node.getAttribute('dimension'));
  } else if (containerDimension) {
    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
  }
  var x, y, z;
  var flatCoordinates = [];
  for (var i = 0, ii = coords.length; i < ii; i += dim) {
    x = parseFloat(coords[i]);
    y = parseFloat(coords[i + 1]);
    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
    if (axisOrientation.substr(0, 2) === 'en') {
      flatCoordinates.push(x, y, z);
    } else {
      flatCoordinates.push(y, x, z);
    }
  }
  return flatCoordinates;
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'pos': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPos_),
    'posList': ol.xml.makeReplacer(ol.format.GML3.prototype.readFlatPosList_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.FLAT_LINEAR_RINGS_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'interior': ol.format.GML3.prototype.interiorParser_,
    'exterior': ol.format.GML3.prototype.exteriorParser_
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.GEOMETRY_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
    'MultiPoint': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readMultiPoint),
    'LineString': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readLineString),
    'MultiLineString': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readMultiLineString),
    'LinearRing' : ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readLinearRing),
    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
    'MultiPolygon': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readMultiPolygon),
    'Surface': ol.xml.makeReplacer(ol.format.GML3.prototype.readSurface_),
    'MultiSurface': ol.xml.makeReplacer(
        ol.format.GML3.prototype.readMultiSurface_),
    'Curve': ol.xml.makeReplacer(ol.format.GML3.prototype.readCurve_),
    'MultiCurve': ol.xml.makeReplacer(
        ol.format.GML3.prototype.readMultiCurve_),
    'Envelope': ol.xml.makeReplacer(ol.format.GML3.prototype.readEnvelope_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.MULTICURVE_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'curveMember': ol.xml.makeArrayPusher(
        ol.format.GML3.prototype.curveMemberParser_),
    'curveMembers': ol.xml.makeArrayPusher(
        ol.format.GML3.prototype.curveMemberParser_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.MULTISURFACE_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'surfaceMember': ol.xml.makeArrayPusher(
        ol.format.GML3.prototype.surfaceMemberParser_),
    'surfaceMembers': ol.xml.makeArrayPusher(
        ol.format.GML3.prototype.surfaceMemberParser_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.CURVEMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'LineString': ol.xml.makeArrayPusher(
        ol.format.GMLBase.prototype.readLineString),
    'Curve': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readCurve_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.SURFACEMEMBER_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'Polygon': ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readPolygon),
    'Surface': ol.xml.makeArrayPusher(ol.format.GML3.prototype.readSurface_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.SURFACE_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'patches': ol.xml.makeReplacer(ol.format.GML3.prototype.readPatch_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.CURVE_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'segments': ol.xml.makeReplacer(ol.format.GML3.prototype.readSegment_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.ENVELOPE_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'lowerCorner': ol.xml.makeArrayPusher(
        ol.format.GML3.prototype.readFlatPosList_),
    'upperCorner': ol.xml.makeArrayPusher(
        ol.format.GML3.prototype.readFlatPosList_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.PATCHES_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'PolygonPatch': ol.xml.makeReplacer(
        ol.format.GML3.prototype.readPolygonPatch_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML3.prototype.SEGMENTS_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'LineStringSegment': ol.xml.makeReplacer(
        ol.format.GML3.prototype.readLineStringSegment_)
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Point} value Point geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writePos_ = function(node, value, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  var axisOrientation = 'enu';
  if (srsName) {
    axisOrientation = ol.proj.get(srsName).getAxisOrientation();
  }
  var point = value.getCoordinates();
  var coords;
  // only 2d for simple features profile
  if (axisOrientation.substr(0, 2) === 'en') {
    coords = (point[0] + ' ' + point[1]);
  } else {
    coords = (point[1] + ' ' + point[0]);
  }
  ol.format.XSD.writeStringTextNode(node, coords);
};


/**
 * @param {Array.<number>} point Point geometry.
 * @param {string=} opt_srsName Optional srsName
 * @return {string} The coords string.
 * @private
 */
ol.format.GML3.prototype.getCoords_ = function(point, opt_srsName) {
  var axisOrientation = 'enu';
  if (opt_srsName) {
    axisOrientation = ol.proj.get(opt_srsName).getAxisOrientation();
  }
  return ((axisOrientation.substr(0, 2) === 'en') ?
      point[0] + ' ' + point[1] :
      point[1] + ' ' + point[0]);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LineString|ol.geom.LinearRing} value Geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writePosList_ = function(node, value, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  // only 2d for simple features profile
  var points = value.getCoordinates();
  var len = points.length;
  var parts = new Array(len);
  var point;
  for (var i = 0; i < len; ++i) {
    point = points[i];
    parts[i] = this.getCoords_(point, srsName);
  }
  ol.format.XSD.writeStringTextNode(node, parts.join(' '));
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Point} geometry Point geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writePoint_ = function(node, geometry, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  var pos = ol.xml.createElementNS(node.namespaceURI, 'pos');
  node.appendChild(pos);
  this.writePos_(pos, geometry, objectStack);
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GML3.ENVELOPE_SERIALIZERS_ = {
  'http://www.opengis.net/gml': {
    'lowerCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
    'upperCorner': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.Extent} extent Extent.
 * @param {Array.<*>} objectStack Node stack.
 */
ol.format.GML3.prototype.writeEnvelope = function(node, extent, objectStack) {
  goog.DEBUG && console.assert(extent.length == 4, 'extent should have 4 items');
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  var keys = ['lowerCorner', 'upperCorner'];
  var values = [extent[0] + ' ' + extent[1], extent[2] + ' ' + extent[3]];
  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
      ({node: node}), ol.format.GML3.ENVELOPE_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
      values,
      objectStack, keys, this);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LinearRing} geometry LinearRing geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeLinearRing_ = function(node, geometry, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
  node.appendChild(posList);
  this.writePosList_(posList, geometry, objectStack);
};


/**
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node} Node.
 * @private
 */
ol.format.GML3.prototype.RING_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
  var context = objectStack[objectStack.length - 1];
  var parentNode = context.node;
  var exteriorWritten = context['exteriorWritten'];
  if (exteriorWritten === undefined) {
    context['exteriorWritten'] = true;
  }
  return ol.xml.createElementNS(parentNode.namespaceURI,
      exteriorWritten !== undefined ? 'interior' : 'exterior');
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Polygon} geometry Polygon geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeSurfaceOrPolygon_ = function(node, geometry, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  if (node.nodeName !== 'PolygonPatch' && srsName) {
    node.setAttribute('srsName', srsName);
  }
  if (node.nodeName === 'Polygon' || node.nodeName === 'PolygonPatch') {
    var rings = geometry.getLinearRings();
    ol.xml.pushSerializeAndPop(
        {node: node, srsName: srsName},
        ol.format.GML3.RING_SERIALIZERS_,
        this.RING_NODE_FACTORY_,
        rings, objectStack, undefined, this);
  } else if (node.nodeName === 'Surface') {
    var patches = ol.xml.createElementNS(node.namespaceURI, 'patches');
    node.appendChild(patches);
    this.writeSurfacePatches_(
        patches, geometry, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LineString} geometry LineString geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeCurveOrLineString_ = function(node, geometry, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  if (node.nodeName !== 'LineStringSegment' && srsName) {
    node.setAttribute('srsName', srsName);
  }
  if (node.nodeName === 'LineString' ||
      node.nodeName === 'LineStringSegment') {
    var posList = ol.xml.createElementNS(node.namespaceURI, 'posList');
    node.appendChild(posList);
    this.writePosList_(posList, geometry, objectStack);
  } else if (node.nodeName === 'Curve') {
    var segments = ol.xml.createElementNS(node.namespaceURI, 'segments');
    node.appendChild(segments);
    this.writeCurveSegments_(segments,
        geometry, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.MultiPolygon} geometry MultiPolygon geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_ = function(node, geometry, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  var surface = context['surface'];
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  var polygons = geometry.getPolygons();
  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, surface: surface},
      ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_,
      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, polygons,
      objectStack, undefined, this);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.MultiPoint} geometry MultiPoint geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeMultiPoint_ = function(node, geometry,
    objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  var points = geometry.getPoints();
  ol.xml.pushSerializeAndPop({node: node, srsName: srsName},
      ol.format.GML3.POINTMEMBER_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory('pointMember'), points,
      objectStack, undefined, this);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.MultiLineString} geometry MultiLineString geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeMultiCurveOrLineString_ = function(node, geometry, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var srsName = context['srsName'];
  var curve = context['curve'];
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  var lines = geometry.getLineStrings();
  ol.xml.pushSerializeAndPop({node: node, srsName: srsName, curve: curve},
      ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_,
      this.MULTIGEOMETRY_MEMBER_NODE_FACTORY_, lines,
      objectStack, undefined, this);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LinearRing} ring LinearRing geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeRing_ = function(node, ring, objectStack) {
  var linearRing = ol.xml.createElementNS(node.namespaceURI, 'LinearRing');
  node.appendChild(linearRing);
  this.writeLinearRing_(linearRing, ring, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Polygon} polygon Polygon geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeSurfaceOrPolygonMember_ = function(node, polygon, objectStack) {
  var child = this.GEOMETRY_NODE_FACTORY_(
      polygon, objectStack);
  if (child) {
    node.appendChild(child);
    this.writeSurfaceOrPolygon_(child, polygon, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Point} point Point geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writePointMember_ = function(node, point, objectStack) {
  var child = ol.xml.createElementNS(node.namespaceURI, 'Point');
  node.appendChild(child);
  this.writePoint_(child, point, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LineString} line LineString geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeLineStringOrCurveMember_ = function(node, line, objectStack) {
  var child = this.GEOMETRY_NODE_FACTORY_(line, objectStack);
  if (child) {
    node.appendChild(child);
    this.writeCurveOrLineString_(child, line, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Polygon} polygon Polygon geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeSurfacePatches_ = function(node, polygon, objectStack) {
  var child = ol.xml.createElementNS(node.namespaceURI, 'PolygonPatch');
  node.appendChild(child);
  this.writeSurfaceOrPolygon_(child, polygon, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LineString} line LineString geometry.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeCurveSegments_ = function(node, line, objectStack) {
  var child = ol.xml.createElementNS(node.namespaceURI,
      'LineStringSegment');
  node.appendChild(child);
  this.writeCurveOrLineString_(child, line, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Geometry|ol.Extent} geometry Geometry.
 * @param {Array.<*>} objectStack Node stack.
 */
ol.format.GML3.prototype.writeGeometryElement = function(node, geometry, objectStack) {
  var context = /** @type {olx.format.WriteOptions} */ (objectStack[objectStack.length - 1]);
  var item = ol.obj.assign({}, context);
  item.node = node;
  var value;
  if (Array.isArray(geometry)) {
    if (context.dataProjection) {
      value = ol.proj.transformExtent(
          geometry, context.featureProjection, context.dataProjection);
    } else {
      value = geometry;
    }
  } else {
    value =
        ol.format.Feature.transformWithOptions(/** @type {ol.geom.Geometry} */ (geometry), true, context);
  }
  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
      (item), ol.format.GML3.GEOMETRY_SERIALIZERS_,
      this.GEOMETRY_NODE_FACTORY_, [value],
      objectStack, undefined, this);
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Node stack.
 */
ol.format.GML3.prototype.writeFeatureElement = function(node, feature, objectStack) {
  var fid = feature.getId();
  if (fid) {
    node.setAttribute('fid', fid);
  }
  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var featureNS = context['featureNS'];
  var geometryName = feature.getGeometryName();
  if (!context.serializers) {
    context.serializers = {};
    context.serializers[featureNS] = {};
  }
  var properties = feature.getProperties();
  var keys = [], values = [];
  for (var key in properties) {
    var value = properties[key];
    if (value !== null) {
      keys.push(key);
      values.push(value);
      if (key == geometryName || value instanceof ol.geom.Geometry) {
        if (!(key in context.serializers[featureNS])) {
          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
              this.writeGeometryElement, this);
        }
      } else {
        if (!(key in context.serializers[featureNS])) {
          context.serializers[featureNS][key] = ol.xml.makeChildAppender(
              ol.format.XSD.writeStringTextNode);
        }
      }
    }
  }
  var item = ol.obj.assign({}, context);
  item.node = node;
  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
      (item), context.serializers,
      ol.xml.makeSimpleNodeFactory(undefined, featureNS),
      values,
      objectStack, keys);
};


/**
 * @param {Node} node Node.
 * @param {Array.<ol.Feature>} features Features.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GML3.prototype.writeFeatureMembers_ = function(node, features, objectStack) {
  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var featureType = context['featureType'];
  var featureNS = context['featureNS'];
  var serializers = {};
  serializers[featureNS] = {};
  serializers[featureNS][featureType] = ol.xml.makeChildAppender(
      this.writeFeatureElement, this);
  var item = ol.obj.assign({}, context);
  item.node = node;
  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
      (item),
      serializers,
      ol.xml.makeSimpleNodeFactory(featureType, featureNS), features,
      objectStack);
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GML3.SURFACEORPOLYGONMEMBER_SERIALIZERS_ = {
  'http://www.opengis.net/gml': {
    'surfaceMember': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeSurfaceOrPolygonMember_),
    'polygonMember': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeSurfaceOrPolygonMember_)
  }
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GML3.POINTMEMBER_SERIALIZERS_ = {
  'http://www.opengis.net/gml': {
    'pointMember': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writePointMember_)
  }
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GML3.LINESTRINGORCURVEMEMBER_SERIALIZERS_ = {
  'http://www.opengis.net/gml': {
    'lineStringMember': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeLineStringOrCurveMember_),
    'curveMember': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeLineStringOrCurveMember_)
  }
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GML3.RING_SERIALIZERS_ = {
  'http://www.opengis.net/gml': {
    'exterior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_),
    'interior': ol.xml.makeChildAppender(ol.format.GML3.prototype.writeRing_)
  }
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GML3.GEOMETRY_SERIALIZERS_ = {
  'http://www.opengis.net/gml': {
    'Curve': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeCurveOrLineString_),
    'MultiCurve': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeMultiCurveOrLineString_),
    'Point': ol.xml.makeChildAppender(ol.format.GML3.prototype.writePoint_),
    'MultiPoint': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeMultiPoint_),
    'LineString': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeCurveOrLineString_),
    'MultiLineString': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeMultiCurveOrLineString_),
    'LinearRing': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeLinearRing_),
    'Polygon': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeSurfaceOrPolygon_),
    'MultiPolygon': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_),
    'Surface': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeSurfaceOrPolygon_),
    'MultiSurface': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeMultiSurfaceOrPolygon_),
    'Envelope': ol.xml.makeChildAppender(
        ol.format.GML3.prototype.writeEnvelope)
  }
};


/**
 * @const
 * @type {Object.<string, string>}
 * @private
 */
ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_ = {
  'MultiLineString': 'lineStringMember',
  'MultiCurve': 'curveMember',
  'MultiPolygon': 'polygonMember',
  'MultiSurface': 'surfaceMember'
};


/**
 * @const
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node|undefined} Node.
 * @private
 */
ol.format.GML3.prototype.MULTIGEOMETRY_MEMBER_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
  var parentNode = objectStack[objectStack.length - 1].node;
  goog.DEBUG && console.assert(ol.xml.isNode(parentNode),
      'parentNode should be a node');
  return ol.xml.createElementNS('http://www.opengis.net/gml',
      ol.format.GML3.MULTIGEOMETRY_TO_MEMBER_NODENAME_[parentNode.nodeName]);
};


/**
 * @const
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node|undefined} Node.
 * @private
 */
ol.format.GML3.prototype.GEOMETRY_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
  var context = objectStack[objectStack.length - 1];
  var multiSurface = context['multiSurface'];
  var surface = context['surface'];
  var curve = context['curve'];
  var multiCurve = context['multiCurve'];
  var parentNode = objectStack[objectStack.length - 1].node;
  goog.DEBUG && console.assert(ol.xml.isNode(parentNode),
      'parentNode should be a node');
  var nodeName;
  if (!Array.isArray(value)) {
    nodeName = /** @type {ol.geom.Geometry} */ (value).getType();
    if (nodeName === 'MultiPolygon' && multiSurface === true) {
      nodeName = 'MultiSurface';
    } else if (nodeName === 'Polygon' && surface === true) {
      nodeName = 'Surface';
    } else if (nodeName === 'LineString' && curve === true) {
      nodeName = 'Curve';
    } else if (nodeName === 'MultiLineString' && multiCurve === true) {
      nodeName = 'MultiCurve';
    }
  } else {
    nodeName = 'Envelope';
  }
  return ol.xml.createElementNS('http://www.opengis.net/gml',
      nodeName);
};


/**
 * Encode a geometry in GML 3.1.1 Simple Features.
 *
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 * @api
 */
ol.format.GML3.prototype.writeGeometryNode = function(geometry, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  var geom = ol.xml.createElementNS('http://www.opengis.net/gml', 'geom');
  var context = {node: geom, srsName: this.srsName,
    curve: this.curve_, surface: this.surface_,
    multiSurface: this.multiSurface_, multiCurve: this.multiCurve_};
  if (opt_options) {
    ol.obj.assign(context, opt_options);
  }
  this.writeGeometryElement(geom, geometry, [context]);
  return geom;
};


/**
 * Encode an array of features in GML 3.1.1 Simple Features.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {string} Result.
 * @api stable
 */
ol.format.GML3.prototype.writeFeatures;


/**
 * Encode an array of features in the GML 3.1.1 format as an XML node.
 *
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 * @api
 */
ol.format.GML3.prototype.writeFeaturesNode = function(features, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  var node = ol.xml.createElementNS('http://www.opengis.net/gml',
      'featureMembers');
  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
      'xsi:schemaLocation', this.schemaLocation);
  var context = {
    srsName: this.srsName,
    curve: this.curve_,
    surface: this.surface_,
    multiSurface: this.multiSurface_,
    multiCurve: this.multiCurve_,
    featureNS: this.featureNS,
    featureType: this.featureType
  };
  if (opt_options) {
    ol.obj.assign(context, opt_options);
  }
  this.writeFeatureMembers_(node, features, [context]);
  return node;
};

goog.provide('ol.format.GML');

goog.require('ol.format.GML3');


/**
 * @classdesc
 * Feature format for reading and writing data in the GML format
 * version 3.1.1.
 * Currently only supports GML 3.1.1 Simple Features profile.
 *
 * @constructor
 * @param {olx.format.GMLOptions=} opt_options
 *     Optional configuration object.
 * @extends {ol.format.GMLBase}
 * @api stable
 */
ol.format.GML = ol.format.GML3;


/**
 * Encode an array of features in GML 3.1.1 Simple Features.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {string} Result.
 * @api stable
 */
ol.format.GML.prototype.writeFeatures;


/**
 * Encode an array of features in the GML 3.1.1 format as an XML node.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 * @api
 */
ol.format.GML.prototype.writeFeaturesNode;

goog.provide('ol.format.GML2');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.format.GMLBase');
goog.require('ol.format.XSD');
goog.require('ol.proj');
goog.require('ol.xml');


/**
 * @classdesc
 * Feature format for reading and writing data in the GML format,
 * version 2.1.2.
 *
 * @constructor
 * @param {olx.format.GMLOptions=} opt_options Optional configuration object.
 * @extends {ol.format.GMLBase}
 * @api
 */
ol.format.GML2 = function(opt_options) {
  var options = /** @type {olx.format.GMLOptions} */
      (opt_options ? opt_options : {});

  ol.format.GMLBase.call(this, options);

  this.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][
      'featureMember'] =
      ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal);

  /**
   * @inheritDoc
   */
  this.schemaLocation = options.schemaLocation ?
      options.schemaLocation : ol.format.GML2.schemaLocation_;

};
ol.inherits(ol.format.GML2, ol.format.GMLBase);


/**
 * @const
 * @type {string}
 * @private
 */
ol.format.GML2.schemaLocation_ = ol.format.GMLBase.GMLNS +
    ' http://schemas.opengis.net/gml/2.1.2/feature.xsd';


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>|undefined} Flat coordinates.
 */
ol.format.GML2.prototype.readFlatCoordinates_ = function(node, objectStack) {
  var s = ol.xml.getAllTextContent(node, false).replace(/^\s*|\s*$/g, '');
  var context = /** @type {ol.XmlNodeStackItem} */ (objectStack[0]);
  var containerSrs = context['srsName'];
  var containerDimension = node.parentNode.getAttribute('srsDimension');
  var axisOrientation = 'enu';
  if (containerSrs) {
    var proj = ol.proj.get(containerSrs);
    if (proj) {
      axisOrientation = proj.getAxisOrientation();
    }
  }
  var coords = s.split(/[\s,]+/);
  // The "dimension" attribute is from the GML 3.0.1 spec.
  var dim = 2;
  if (node.getAttribute('srsDimension')) {
    dim = ol.format.XSD.readNonNegativeIntegerString(
        node.getAttribute('srsDimension'));
  } else if (node.getAttribute('dimension')) {
    dim = ol.format.XSD.readNonNegativeIntegerString(
        node.getAttribute('dimension'));
  } else if (containerDimension) {
    dim = ol.format.XSD.readNonNegativeIntegerString(containerDimension);
  }
  var x, y, z;
  var flatCoordinates = [];
  for (var i = 0, ii = coords.length; i < ii; i += dim) {
    x = parseFloat(coords[i]);
    y = parseFloat(coords[i + 1]);
    z = (dim === 3) ? parseFloat(coords[i + 2]) : 0;
    if (axisOrientation.substr(0, 2) === 'en') {
      flatCoordinates.push(x, y, z);
    } else {
      flatCoordinates.push(y, x, z);
    }
  }
  return flatCoordinates;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.Extent|undefined} Envelope.
 */
ol.format.GML2.prototype.readBox_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Box', 'localName should be Box');
  /** @type {Array.<number>} */
  var flatCoordinates = ol.xml.pushParseAndPop([null],
      this.BOX_PARSERS_, node, objectStack, this);
  return ol.extent.createOrUpdate(flatCoordinates[1][0],
      flatCoordinates[1][1], flatCoordinates[1][3],
      flatCoordinates[1][4]);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GML2.prototype.innerBoundaryIsParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'innerBoundaryIs',
      'localName should be innerBoundaryIs');
  /** @type {Array.<number>|undefined} */
  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
      this.RING_PARSERS, node, objectStack, this);
  if (flatLinearRing) {
    var flatLinearRings = /** @type {Array.<Array.<number>>} */
        (objectStack[objectStack.length - 1]);
    goog.DEBUG && console.assert(Array.isArray(flatLinearRings),
        'flatLinearRings should be an array');
    goog.DEBUG && console.assert(flatLinearRings.length > 0,
        'flatLinearRings should have an array length larger than 0');
    flatLinearRings.push(flatLinearRing);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GML2.prototype.outerBoundaryIsParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'outerBoundaryIs',
      'localName should be outerBoundaryIs');
  /** @type {Array.<number>|undefined} */
  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
      this.RING_PARSERS, node, objectStack, this);
  if (flatLinearRing) {
    var flatLinearRings = /** @type {Array.<Array.<number>>} */
        (objectStack[objectStack.length - 1]);
    goog.DEBUG && console.assert(Array.isArray(flatLinearRings),
        'flatLinearRings should be an array');
    goog.DEBUG && console.assert(flatLinearRings.length > 0,
        'flatLinearRings should have an array length larger than 0');
    flatLinearRings[0] = flatLinearRing;
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML2.prototype.GEOMETRY_FLAT_COORDINATES_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'coordinates': ol.xml.makeReplacer(
        ol.format.GML2.prototype.readFlatCoordinates_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML2.prototype.FLAT_LINEAR_RINGS_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'innerBoundaryIs': ol.format.GML2.prototype.innerBoundaryIsParser_,
    'outerBoundaryIs': ol.format.GML2.prototype.outerBoundaryIsParser_
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML2.prototype.BOX_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'coordinates': ol.xml.makeArrayPusher(
        ol.format.GML2.prototype.readFlatCoordinates_)
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GML2.prototype.GEOMETRY_PARSERS_ = {
  'http://www.opengis.net/gml' : {
    'Point': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPoint),
    'MultiPoint': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readMultiPoint),
    'LineString': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readLineString),
    'MultiLineString': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readMultiLineString),
    'LinearRing' : ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readLinearRing),
    'Polygon': ol.xml.makeReplacer(ol.format.GMLBase.prototype.readPolygon),
    'MultiPolygon': ol.xml.makeReplacer(
        ol.format.GMLBase.prototype.readMultiPolygon),
    'Box': ol.xml.makeReplacer(ol.format.GML2.prototype.readBox_)
  }
};

goog.provide('ol.format.GPX');

goog.require('ol');
goog.require('ol.Feature');
goog.require('ol.array');
goog.require('ol.format.Feature');
goog.require('ol.format.XMLFeature');
goog.require('ol.format.XSD');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.Point');
goog.require('ol.proj');
goog.require('ol.xml');


/**
 * @classdesc
 * Feature format for reading and writing data in the GPX format.
 *
 * @constructor
 * @extends {ol.format.XMLFeature}
 * @param {olx.format.GPXOptions=} opt_options Options.
 * @api stable
 */
ol.format.GPX = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.XMLFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get('EPSG:4326');

  /**
   * @type {function(ol.Feature, Node)|undefined}
   * @private
   */
  this.readExtensions_ = options.readExtensions;
};
ol.inherits(ol.format.GPX, ol.format.XMLFeature);


/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.GPX.NAMESPACE_URIS_ = [
  null,
  'http://www.topografix.com/GPX/1/0',
  'http://www.topografix.com/GPX/1/1'
];


/**
 * @const
 * @type {string}
 * @private
 */
ol.format.GPX.SCHEMA_LOCATION_ = 'http://www.topografix.com/GPX/1/1 ' +
    'http://www.topografix.com/GPX/1/1/gpx.xsd';


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {Node} node Node.
 * @param {Object} values Values.
 * @private
 * @return {Array.<number>} Flat coordinates.
 */
ol.format.GPX.appendCoordinate_ = function(flatCoordinates, node, values) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  flatCoordinates.push(
      parseFloat(node.getAttribute('lon')),
      parseFloat(node.getAttribute('lat')));
  if ('ele' in values) {
    flatCoordinates.push(/** @type {number} */ (values['ele']));
    delete values['ele'];
  } else {
    flatCoordinates.push(0);
  }
  if ('time' in values) {
    flatCoordinates.push(/** @type {number} */ (values['time']));
    delete values['time'];
  } else {
    flatCoordinates.push(0);
  }
  return flatCoordinates;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.parseLink_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'link', 'localName should be link');
  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var href = node.getAttribute('href');
  if (href !== null) {
    values['link'] = href;
  }
  ol.xml.parseNode(ol.format.GPX.LINK_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.parseExtensions_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'extensions',
      'localName should be extensions');
  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  values['extensionsNode_'] = node;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.parseRtePt_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'rtept', 'localName should be rtept');
  var values = ol.xml.pushParseAndPop(
      {}, ol.format.GPX.RTEPT_PARSERS_, node, objectStack);
  if (values) {
    var rteValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
    var flatCoordinates = /** @type {Array.<number>} */
        (rteValues['flatCoordinates']);
    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.parseTrkPt_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'trkpt', 'localName should be trkpt');
  var values = ol.xml.pushParseAndPop(
      {}, ol.format.GPX.TRKPT_PARSERS_, node, objectStack);
  if (values) {
    var trkValues = /** @type {Object} */ (objectStack[objectStack.length - 1]);
    var flatCoordinates = /** @type {Array.<number>} */
        (trkValues['flatCoordinates']);
    ol.format.GPX.appendCoordinate_(flatCoordinates, node, values);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.parseTrkSeg_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'trkseg',
      'localName should be trkseg');
  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  ol.xml.parseNode(ol.format.GPX.TRKSEG_PARSERS_, node, objectStack);
  var flatCoordinates = /** @type {Array.<number>} */
      (values['flatCoordinates']);
  var ends = /** @type {Array.<number>} */ (values['ends']);
  ends.push(flatCoordinates.length);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.Feature|undefined} Track.
 */
ol.format.GPX.readRte_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'rte', 'localName should be rte');
  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
  var values = ol.xml.pushParseAndPop({
    'flatCoordinates': []
  }, ol.format.GPX.RTE_PARSERS_, node, objectStack);
  if (!values) {
    return undefined;
  }
  var flatCoordinates = /** @type {Array.<number>} */
      (values['flatCoordinates']);
  delete values['flatCoordinates'];
  var geometry = new ol.geom.LineString(null);
  geometry.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
  ol.format.Feature.transformWithOptions(geometry, false, options);
  var feature = new ol.Feature(geometry);
  feature.setProperties(values);
  return feature;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.Feature|undefined} Track.
 */
ol.format.GPX.readTrk_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'trk', 'localName should be trk');
  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
  var values = ol.xml.pushParseAndPop({
    'flatCoordinates': [],
    'ends': []
  }, ol.format.GPX.TRK_PARSERS_, node, objectStack);
  if (!values) {
    return undefined;
  }
  var flatCoordinates = /** @type {Array.<number>} */
      (values['flatCoordinates']);
  delete values['flatCoordinates'];
  var ends = /** @type {Array.<number>} */ (values['ends']);
  delete values['ends'];
  var geometry = new ol.geom.MultiLineString(null);
  geometry.setFlatCoordinates(
      ol.geom.GeometryLayout.XYZM, flatCoordinates, ends);
  ol.format.Feature.transformWithOptions(geometry, false, options);
  var feature = new ol.Feature(geometry);
  feature.setProperties(values);
  return feature;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.Feature|undefined} Waypoint.
 */
ol.format.GPX.readWpt_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'wpt', 'localName should be wpt');
  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
  var values = ol.xml.pushParseAndPop(
      {}, ol.format.GPX.WPT_PARSERS_, node, objectStack);
  if (!values) {
    return undefined;
  }
  var coordinates = ol.format.GPX.appendCoordinate_([], node, values);
  var geometry = new ol.geom.Point(
      coordinates, ol.geom.GeometryLayout.XYZM);
  ol.format.Feature.transformWithOptions(geometry, false, options);
  var feature = new ol.Feature(geometry);
  feature.setProperties(values);
  return feature;
};


/**
 * @const
 * @type {Object.<string, function(Node, Array.<*>): (ol.Feature|undefined)>}
 * @private
 */
ol.format.GPX.FEATURE_READER_ = {
  'rte': ol.format.GPX.readRte_,
  'trk': ol.format.GPX.readTrk_,
  'wpt': ol.format.GPX.readWpt_
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.GPX_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'rte': ol.xml.makeArrayPusher(ol.format.GPX.readRte_),
      'trk': ol.xml.makeArrayPusher(ol.format.GPX.readTrk_),
      'wpt': ol.xml.makeArrayPusher(ol.format.GPX.readWpt_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.LINK_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'text':
          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkText'),
      'type':
          ol.xml.makeObjectPropertySetter(ol.format.XSD.readString, 'linkType')
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.RTE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'link': ol.format.GPX.parseLink_,
      'number':
          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
      'extensions': ol.format.GPX.parseExtensions_,
      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'rtept': ol.format.GPX.parseRtePt_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.RTEPT_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.TRK_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'link': ol.format.GPX.parseLink_,
      'number':
          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'extensions': ol.format.GPX.parseExtensions_,
      'trkseg': ol.format.GPX.parseTrkSeg_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.TRKSEG_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'trkpt': ol.format.GPX.parseTrkPt_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.TRKPT_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.GPX.WPT_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'ele': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'time': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDateTime),
      'magvar': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'geoidheight': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'cmt': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'desc': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'src': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'link': ol.format.GPX.parseLink_,
      'sym': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'type': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'fix': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'sat': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger),
      'hdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'vdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'pdop': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'ageofdgpsdata':
          ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'dgpsid':
          ol.xml.makeObjectPropertySetter(ol.format.XSD.readNonNegativeInteger),
      'extensions': ol.format.GPX.parseExtensions_
    });


/**
 * @param {Array.<ol.Feature>} features List of features.
 * @private
 */
ol.format.GPX.prototype.handleReadExtensions_ = function(features) {
  if (!features) {
    features = [];
  }
  for (var i = 0, ii = features.length; i < ii; ++i) {
    var feature = features[i];
    if (this.readExtensions_) {
      var extensionsNode = feature.get('extensionsNode_') || null;
      this.readExtensions_(feature, extensionsNode);
    }
    feature.set('extensionsNode_', undefined);
  }
};


/**
 * Read the first feature from a GPX source.
 * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`)
 * into MultiLineString. Any properties on route and track waypoints are ignored.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api stable
 */
ol.format.GPX.prototype.readFeature;


/**
 * @inheritDoc
 */
ol.format.GPX.prototype.readFeatureFromNode = function(node, opt_options) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
    return null;
  }
  var featureReader = ol.format.GPX.FEATURE_READER_[node.localName];
  if (!featureReader) {
    return null;
  }
  var feature = featureReader(node, [this.getReadOptions(node, opt_options)]);
  if (!feature) {
    return null;
  }
  this.handleReadExtensions_([feature]);
  return feature;
};


/**
 * Read all features from a GPX source.
 * Routes (`<rte>`) are converted into LineString geometries, and tracks (`<trk>`)
 * into MultiLineString. Any properties on route and track waypoints are ignored.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.GPX.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.GPX.prototype.readFeaturesFromNode = function(node, opt_options) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  if (!ol.array.includes(ol.format.GPX.NAMESPACE_URIS_, node.namespaceURI)) {
    return [];
  }
  if (node.localName == 'gpx') {
    /** @type {Array.<ol.Feature>} */
    var features = ol.xml.pushParseAndPop([], ol.format.GPX.GPX_PARSERS_,
        node, [this.getReadOptions(node, opt_options)]);
    if (features) {
      this.handleReadExtensions_(features);
      return features;
    } else {
      return [];
    }
  }
  return [];
};


/**
 * Read the projection from a GPX source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.GPX.prototype.readProjection;


/**
 * @param {Node} node Node.
 * @param {string} value Value for the link's `href` attribute.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.GPX.writeLink_ = function(node, value, objectStack) {
  node.setAttribute('href', value);
  var context = objectStack[objectStack.length - 1];
  var properties = context['properties'];
  var link = [
    properties['linkText'],
    properties['linkType']
  ];
  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ ({node: node}),
      ol.format.GPX.LINK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
      link, objectStack, ol.format.GPX.LINK_SEQUENCE_);
};


/**
 * @param {Node} node Node.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.writeWptType_ = function(node, coordinate, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var parentNode = context.node;
  goog.DEBUG && console.assert(ol.xml.isNode(parentNode),
      'parentNode should be an XML node');
  var namespaceURI = parentNode.namespaceURI;
  var properties = context['properties'];
  //FIXME Projection handling
  ol.xml.setAttributeNS(node, null, 'lat', coordinate[1]);
  ol.xml.setAttributeNS(node, null, 'lon', coordinate[0]);
  var geometryLayout = context['geometryLayout'];
  switch (geometryLayout) {
    case ol.geom.GeometryLayout.XYZM:
      if (coordinate[3] !== 0) {
        properties['time'] = coordinate[3];
      }
      // fall through
    case ol.geom.GeometryLayout.XYZ:
      if (coordinate[2] !== 0) {
        properties['ele'] = coordinate[2];
      }
      break;
    case ol.geom.GeometryLayout.XYM:
      if (coordinate[2] !== 0) {
        properties['time'] = coordinate[2];
      }
      break;
    default:
      // pass
  }
  var orderedKeys = (node.nodeName == 'rtept') ?
      ol.format.GPX.RTEPT_TYPE_SEQUENCE_[namespaceURI] :
      ol.format.GPX.WPT_TYPE_SEQUENCE_[namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
      ({node: node, 'properties': properties}),
      ol.format.GPX.WPT_TYPE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
      values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.writeRte_ = function(node, feature, objectStack) {
  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
  var properties = feature.getProperties();
  var context = {node: node, 'properties': properties};
  var geometry = feature.getGeometry();
  if (geometry) {
    geometry = /** @type {ol.geom.LineString} */
        (ol.format.Feature.transformWithOptions(geometry, true, options));
    context['geometryLayout'] = geometry.getLayout();
    properties['rtept'] = geometry.getCoordinates();
  }
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.GPX.RTE_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context,
      ol.format.GPX.RTE_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
      values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.writeTrk_ = function(node, feature, objectStack) {
  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
  var properties = feature.getProperties();
  /** @type {ol.XmlNodeStackItem} */
  var context = {node: node, 'properties': properties};
  var geometry = feature.getGeometry();
  if (geometry) {
    geometry = /** @type {ol.geom.MultiLineString} */
        (ol.format.Feature.transformWithOptions(geometry, true, options));
    properties['trkseg'] = geometry.getLineStrings();
  }
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.GPX.TRK_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context,
      ol.format.GPX.TRK_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
      values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LineString} lineString LineString.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.writeTrkSeg_ = function(node, lineString, objectStack) {
  /** @type {ol.XmlNodeStackItem} */
  var context = {node: node, 'geometryLayout': lineString.getLayout(),
    'properties': {}};
  ol.xml.pushSerializeAndPop(context,
      ol.format.GPX.TRKSEG_SERIALIZERS_, ol.format.GPX.TRKSEG_NODE_FACTORY_,
      lineString.getCoordinates(), objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.GPX.writeWpt_ = function(node, feature, objectStack) {
  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
  var context = objectStack[objectStack.length - 1];
  context['properties'] = feature.getProperties();
  var geometry = feature.getGeometry();
  if (geometry) {
    geometry = /** @type {ol.geom.Point} */
        (ol.format.Feature.transformWithOptions(geometry, true, options));
    context['geometryLayout'] = geometry.getLayout();
    ol.format.GPX.writeWptType_(node, geometry.getCoordinates(), objectStack);
  }
};


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.GPX.LINK_SEQUENCE_ = ['text', 'type'];


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GPX.LINK_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'text': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.GPX.RTE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, [
      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'rtept'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GPX.RTE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
      'number': ol.xml.makeChildAppender(
          ol.format.XSD.writeNonNegativeIntegerTextNode),
      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'rtept': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
          ol.format.GPX.writeWptType_))
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.GPX.RTEPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, [
      'ele', 'time'
    ]);


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.GPX.TRK_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, [
      'name', 'cmt', 'desc', 'src', 'link', 'number', 'type', 'trkseg'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GPX.TRK_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
      'number': ol.xml.makeChildAppender(
          ol.format.XSD.writeNonNegativeIntegerTextNode),
      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'trkseg': ol.xml.makeArraySerializer(ol.xml.makeChildAppender(
          ol.format.GPX.writeTrkSeg_))
    });


/**
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.GPX.TRKSEG_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('trkpt');


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GPX.TRKSEG_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'trkpt': ol.xml.makeChildAppender(ol.format.GPX.writeWptType_)
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.GPX.WPT_TYPE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, [
      'ele', 'time', 'magvar', 'geoidheight', 'name', 'cmt', 'desc', 'src',
      'link', 'sym', 'type', 'fix', 'sat', 'hdop', 'vdop', 'pdop',
      'ageofdgpsdata', 'dgpsid'
    ]);


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GPX.WPT_TYPE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'ele': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
      'time': ol.xml.makeChildAppender(ol.format.XSD.writeDateTimeTextNode),
      'magvar': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
      'geoidheight': ol.xml.makeChildAppender(
          ol.format.XSD.writeDecimalTextNode),
      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'cmt': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'desc': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'src': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'link': ol.xml.makeChildAppender(ol.format.GPX.writeLink_),
      'sym': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'type': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'fix': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'sat': ol.xml.makeChildAppender(
          ol.format.XSD.writeNonNegativeIntegerTextNode),
      'hdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
      'vdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
      'pdop': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
      'ageofdgpsdata': ol.xml.makeChildAppender(
          ol.format.XSD.writeDecimalTextNode),
      'dgpsid': ol.xml.makeChildAppender(
          ol.format.XSD.writeNonNegativeIntegerTextNode)
    });


/**
 * @const
 * @type {Object.<string, string>}
 * @private
 */
ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_ = {
  'Point': 'wpt',
  'LineString': 'rte',
  'MultiLineString': 'trk'
};


/**
 * @const
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node|undefined} Node.
 * @private
 */
ol.format.GPX.GPX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
  var geometry = /** @type {ol.Feature} */ (value).getGeometry();
  if (geometry) {
    var nodeName = ol.format.GPX.GEOMETRY_TYPE_TO_NODENAME_[geometry.getType()];
    if (nodeName) {
      var parentNode = objectStack[objectStack.length - 1].node;
      goog.DEBUG && console.assert(ol.xml.isNode(parentNode),
          'parentNode should be an XML node');
      return ol.xml.createElementNS(parentNode.namespaceURI, nodeName);
    }
  }
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.GPX.GPX_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.GPX.NAMESPACE_URIS_, {
      'rte': ol.xml.makeChildAppender(ol.format.GPX.writeRte_),
      'trk': ol.xml.makeChildAppender(ol.format.GPX.writeTrk_),
      'wpt': ol.xml.makeChildAppender(ol.format.GPX.writeWpt_)
    });


/**
 * Encode an array of features in the GPX format.
 * LineString geometries are output as routes (`<rte>`), and MultiLineString
 * as tracks (`<trk>`).
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} Result.
 * @api stable
 */
ol.format.GPX.prototype.writeFeatures;


/**
 * Encode an array of features in the GPX format as an XML node.
 * LineString geometries are output as routes (`<rte>`), and MultiLineString
 * as tracks (`<trk>`).
 *
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 * @api
 */
ol.format.GPX.prototype.writeFeaturesNode = function(features, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  //FIXME Serialize metadata
  var gpx = ol.xml.createElementNS('http://www.topografix.com/GPX/1/1', 'gpx');
  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
  ol.xml.setAttributeNS(gpx, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
  ol.xml.setAttributeNS(gpx, xmlSchemaInstanceUri, 'xsi:schemaLocation',
      ol.format.GPX.SCHEMA_LOCATION_);
  gpx.setAttribute('version', '1.1');
  gpx.setAttribute('creator', 'OpenLayers 3');

  ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */
      ({node: gpx}), ol.format.GPX.GPX_SERIALIZERS_,
      ol.format.GPX.GPX_NODE_FACTORY_, features, [opt_options]);
  return gpx;
};

goog.provide('ol.format.TextFeature');

goog.require('ol');
goog.require('ol.format.Feature');
goog.require('ol.format.FormatType');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for text feature formats.
 *
 * @constructor
 * @extends {ol.format.Feature}
 */
ol.format.TextFeature = function() {
  ol.format.Feature.call(this);
};
ol.inherits(ol.format.TextFeature, ol.format.Feature);


/**
 * @param {Document|Node|Object|string} source Source.
 * @private
 * @return {string} Text.
 */
ol.format.TextFeature.prototype.getText_ = function(source) {
  if (typeof source === 'string') {
    return source;
  } else {
    return '';
  }
};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.getType = function() {
  return ol.format.FormatType.TEXT;
};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.readFeature = function(source, opt_options) {
  return this.readFeatureFromText(
      this.getText_(source), this.adaptOptions(opt_options));
};


/**
 * @abstract
 * @param {string} text Text.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @protected
 * @return {ol.Feature} Feature.
 */
ol.format.TextFeature.prototype.readFeatureFromText = function(text, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.readFeatures = function(source, opt_options) {
  return this.readFeaturesFromText(
      this.getText_(source), this.adaptOptions(opt_options));
};


/**
 * @abstract
 * @param {string} text Text.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @protected
 * @return {Array.<ol.Feature>} Features.
 */
ol.format.TextFeature.prototype.readFeaturesFromText = function(text, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.readGeometry = function(source, opt_options) {
  return this.readGeometryFromText(
      this.getText_(source), this.adaptOptions(opt_options));
};


/**
 * @abstract
 * @param {string} text Text.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @protected
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.TextFeature.prototype.readGeometryFromText = function(text, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.readProjection = function(source) {
  return this.readProjectionFromText(this.getText_(source));
};


/**
 * @param {string} text Text.
 * @protected
 * @return {ol.proj.Projection} Projection.
 */
ol.format.TextFeature.prototype.readProjectionFromText = function(text) {
  return this.defaultDataProjection;
};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.writeFeature = function(feature, opt_options) {
  return this.writeFeatureText(feature, this.adaptOptions(opt_options));
};


/**
 * @abstract
 * @param {ol.Feature} feature Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @protected
 * @return {string} Text.
 */
ol.format.TextFeature.prototype.writeFeatureText = function(feature, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.writeFeatures = function(
    features, opt_options) {
  return this.writeFeaturesText(features, this.adaptOptions(opt_options));
};


/**
 * @abstract
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @protected
 * @return {string} Text.
 */
ol.format.TextFeature.prototype.writeFeaturesText = function(features, opt_options) {};


/**
 * @inheritDoc
 */
ol.format.TextFeature.prototype.writeGeometry = function(
    geometry, opt_options) {
  return this.writeGeometryText(geometry, this.adaptOptions(opt_options));
};


/**
 * @abstract
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @protected
 * @return {string} Text.
 */
ol.format.TextFeature.prototype.writeGeometryText = function(geometry, opt_options) {};

goog.provide('ol.format.IGC');
goog.provide('ol.format.IGCZ');

goog.require('ol');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.TextFeature');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.proj');


/**
 * IGC altitude/z. One of 'barometric', 'gps', 'none'.
 * @enum {string}
 */
ol.format.IGCZ = {
  BAROMETRIC: 'barometric',
  GPS: 'gps',
  NONE: 'none'
};


/**
 * @classdesc
 * Feature format for `*.igc` flight recording files.
 *
 * @constructor
 * @extends {ol.format.TextFeature}
 * @param {olx.format.IGCOptions=} opt_options Options.
 * @api
 */
ol.format.IGC = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.TextFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get('EPSG:4326');

  /**
   * @private
   * @type {ol.format.IGCZ}
   */
  this.altitudeMode_ = options.altitudeMode ?
      options.altitudeMode : ol.format.IGCZ.NONE;

};
ol.inherits(ol.format.IGC, ol.format.TextFeature);


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.IGC.EXTENSIONS_ = ['.igc'];


/**
 * @const
 * @type {RegExp}
 * @private
 */
ol.format.IGC.B_RECORD_RE_ =
    /^B(\d{2})(\d{2})(\d{2})(\d{2})(\d{5})([NS])(\d{3})(\d{5})([EW])([AV])(\d{5})(\d{5})/;


/**
 * @const
 * @type {RegExp}
 * @private
 */
ol.format.IGC.H_RECORD_RE_ = /^H.([A-Z]{3}).*?:(.*)/;


/**
 * @const
 * @type {RegExp}
 * @private
 */
ol.format.IGC.HFDTE_RECORD_RE_ = /^HFDTE(\d{2})(\d{2})(\d{2})/;


/**
 * A regular expression matching the newline characters `\r\n`, `\r` and `\n`.
 *
 * @const
 * @type {RegExp}
 * @private
 */
ol.format.IGC.NEWLINE_RE_ = /\r\n|\r|\n/;


/**
 * @inheritDoc
 */
ol.format.IGC.prototype.getExtensions = function() {
  return ol.format.IGC.EXTENSIONS_;
};


/**
 * Read the feature from the IGC source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api
 */
ol.format.IGC.prototype.readFeature;


/**
 * @inheritDoc
 */
ol.format.IGC.prototype.readFeatureFromText = function(text, opt_options) {
  var altitudeMode = this.altitudeMode_;
  var lines = text.split(ol.format.IGC.NEWLINE_RE_);
  /** @type {Object.<string, string>} */
  var properties = {};
  var flatCoordinates = [];
  var year = 2000;
  var month = 0;
  var day = 1;
  var lastDateTime = -1;
  var i, ii;
  for (i = 0, ii = lines.length; i < ii; ++i) {
    var line = lines[i];
    var m;
    if (line.charAt(0) == 'B') {
      m = ol.format.IGC.B_RECORD_RE_.exec(line);
      if (m) {
        var hour = parseInt(m[1], 10);
        var minute = parseInt(m[2], 10);
        var second = parseInt(m[3], 10);
        var y = parseInt(m[4], 10) + parseInt(m[5], 10) / 60000;
        if (m[6] == 'S') {
          y = -y;
        }
        var x = parseInt(m[7], 10) + parseInt(m[8], 10) / 60000;
        if (m[9] == 'W') {
          x = -x;
        }
        flatCoordinates.push(x, y);
        if (altitudeMode != ol.format.IGCZ.NONE) {
          var z;
          if (altitudeMode == ol.format.IGCZ.GPS) {
            z = parseInt(m[11], 10);
          } else if (altitudeMode == ol.format.IGCZ.BAROMETRIC) {
            z = parseInt(m[12], 10);
          } else {
            goog.DEBUG && console.assert(false, 'Unknown altitude mode.');
            z = 0;
          }
          flatCoordinates.push(z);
        }
        var dateTime = Date.UTC(year, month, day, hour, minute, second);
        // Detect UTC midnight wrap around.
        if (dateTime < lastDateTime) {
          dateTime = Date.UTC(year, month, day + 1, hour, minute, second);
        }
        flatCoordinates.push(dateTime / 1000);
        lastDateTime = dateTime;
      }
    } else if (line.charAt(0) == 'H') {
      m = ol.format.IGC.HFDTE_RECORD_RE_.exec(line);
      if (m) {
        day = parseInt(m[1], 10);
        month = parseInt(m[2], 10) - 1;
        year = 2000 + parseInt(m[3], 10);
      } else {
        m = ol.format.IGC.H_RECORD_RE_.exec(line);
        if (m) {
          properties[m[1]] = m[2].trim();
        }
      }
    }
  }
  if (flatCoordinates.length === 0) {
    return null;
  }
  var lineString = new ol.geom.LineString(null);
  var layout = altitudeMode == ol.format.IGCZ.NONE ?
      ol.geom.GeometryLayout.XYM : ol.geom.GeometryLayout.XYZM;
  lineString.setFlatCoordinates(layout, flatCoordinates);
  var feature = new ol.Feature(ol.format.Feature.transformWithOptions(
      lineString, false, opt_options));
  feature.setProperties(properties);
  return feature;
};


/**
 * Read the feature from the source. As IGC sources contain a single
 * feature, this will return the feature in an array.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api
 */
ol.format.IGC.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.IGC.prototype.readFeaturesFromText = function(text, opt_options) {
  var feature = this.readFeatureFromText(text, opt_options);
  if (feature) {
    return [feature];
  } else {
    return [];
  }
};


/**
 * Read the projection from the IGC source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api
 */
ol.format.IGC.prototype.readProjection;

goog.provide('ol.style.IconImage');

goog.require('ol');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.events.EventTarget');
goog.require('ol.events.EventType');
goog.require('ol.style');
goog.require('ol.style.ImageState');


/**
 * @constructor
 * @param {Image|HTMLCanvasElement} image Image.
 * @param {string|undefined} src Src.
 * @param {ol.Size} size Size.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.style.ImageState} imageState Image state.
 * @param {ol.Color} color Color.
 * @extends {ol.events.EventTarget}
 */
ol.style.IconImage = function(image, src, size, crossOrigin, imageState,
                               color) {

  ol.events.EventTarget.call(this);

  /**
   * @private
   * @type {Image|HTMLCanvasElement}
   */
  this.hitDetectionImage_ = null;

  /**
   * @private
   * @type {Image|HTMLCanvasElement}
   */
  this.image_ = !image ? new Image() : image;

  if (crossOrigin !== null) {
    this.image_.crossOrigin = crossOrigin;
  }

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = color ?
      /** @type {HTMLCanvasElement} */ (document.createElement('CANVAS')) :
      null;

  /**
   * @private
   * @type {ol.Color}
   */
  this.color_ = color;

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.imageListenerKeys_ = null;

  /**
   * @private
   * @type {ol.style.ImageState}
   */
  this.imageState_ = imageState;

  /**
   * @private
   * @type {ol.Size}
   */
  this.size_ = size;

  /**
   * @private
   * @type {string|undefined}
   */
  this.src_ = src;

  /**
   * @private
   * @type {boolean}
   */
  this.tainting_ = false;
  if (this.imageState_ == ol.style.ImageState.LOADED) {
    this.determineTainting_();
  }

};
ol.inherits(ol.style.IconImage, ol.events.EventTarget);


/**
 * @param {Image|HTMLCanvasElement} image Image.
 * @param {string} src Src.
 * @param {ol.Size} size Size.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.style.ImageState} imageState Image state.
 * @param {ol.Color} color Color.
 * @return {ol.style.IconImage} Icon image.
 */
ol.style.IconImage.get = function(image, src, size, crossOrigin, imageState,
                                   color) {
  var iconImageCache = ol.style.iconImageCache;
  var iconImage = iconImageCache.get(src, crossOrigin, color);
  if (!iconImage) {
    iconImage = new ol.style.IconImage(
        image, src, size, crossOrigin, imageState, color);
    iconImageCache.set(src, crossOrigin, color, iconImage);
  }
  return iconImage;
};


/**
 * @private
 */
ol.style.IconImage.prototype.determineTainting_ = function() {
  var context = ol.dom.createCanvasContext2D(1, 1);
  try {
    context.drawImage(this.image_, 0, 0);
    context.getImageData(0, 0, 1, 1);
  } catch (e) {
    this.tainting_ = true;
  }
};


/**
 * @private
 */
ol.style.IconImage.prototype.dispatchChangeEvent_ = function() {
  this.dispatchEvent(ol.events.EventType.CHANGE);
};


/**
 * @private
 */
ol.style.IconImage.prototype.handleImageError_ = function() {
  this.imageState_ = ol.style.ImageState.ERROR;
  this.unlistenImage_();
  this.dispatchChangeEvent_();
};


/**
 * @private
 */
ol.style.IconImage.prototype.handleImageLoad_ = function() {
  this.imageState_ = ol.style.ImageState.LOADED;
  if (this.size_) {
    this.image_.width = this.size_[0];
    this.image_.height = this.size_[1];
  }
  this.size_ = [this.image_.width, this.image_.height];
  this.unlistenImage_();
  this.determineTainting_();
  this.replaceColor_();
  this.dispatchChangeEvent_();
};


/**
 * @param {number} pixelRatio Pixel ratio.
 * @return {Image|HTMLCanvasElement} Image or Canvas element.
 */
ol.style.IconImage.prototype.getImage = function(pixelRatio) {
  return this.canvas_ ? this.canvas_ : this.image_;
};


/**
 * @return {ol.style.ImageState} Image state.
 */
ol.style.IconImage.prototype.getImageState = function() {
  return this.imageState_;
};


/**
 * @param {number} pixelRatio Pixel ratio.
 * @return {Image|HTMLCanvasElement} Image element.
 */
ol.style.IconImage.prototype.getHitDetectionImage = function(pixelRatio) {
  if (!this.hitDetectionImage_) {
    if (this.tainting_) {
      var width = this.size_[0];
      var height = this.size_[1];
      var context = ol.dom.createCanvasContext2D(width, height);
      context.fillRect(0, 0, width, height);
      this.hitDetectionImage_ = context.canvas;
    } else {
      this.hitDetectionImage_ = this.image_;
    }
  }
  return this.hitDetectionImage_;
};


/**
 * @return {ol.Size} Image size.
 */
ol.style.IconImage.prototype.getSize = function() {
  return this.size_;
};


/**
 * @return {string|undefined} Image src.
 */
ol.style.IconImage.prototype.getSrc = function() {
  return this.src_;
};


/**
 * Load not yet loaded URI.
 */
ol.style.IconImage.prototype.load = function() {
  if (this.imageState_ == ol.style.ImageState.IDLE) {
    goog.DEBUG && console.assert(this.src_ !== undefined,
        'this.src_ must not be undefined');
    goog.DEBUG && console.assert(!this.imageListenerKeys_,
        'no listener keys existing');
    this.imageState_ = ol.style.ImageState.LOADING;
    this.imageListenerKeys_ = [
      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
          this.handleImageError_, this),
      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
          this.handleImageLoad_, this)
    ];
    try {
      this.image_.src = this.src_;
    } catch (e) {
      this.handleImageError_();
    }
  }
};


/**
 * @private
 */
ol.style.IconImage.prototype.replaceColor_ = function() {
  if (this.tainting_ || this.color_ === null) {
    return;
  }

  this.canvas_.width = this.image_.width;
  this.canvas_.height = this.image_.height;

  var ctx = this.canvas_.getContext('2d');
  ctx.drawImage(this.image_, 0, 0);

  var imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
  var data = imgData.data;
  var r = this.color_[0] / 255.0;
  var g = this.color_[1] / 255.0;
  var b = this.color_[2] / 255.0;

  for (var i = 0, ii = data.length; i < ii; i += 4) {
    data[i] *= r;
    data[i + 1] *= g;
    data[i + 2] *= b;
  }
  ctx.putImageData(imgData, 0, 0);
};


/**
 * Discards event handlers which listen for load completion or errors.
 *
 * @private
 */
ol.style.IconImage.prototype.unlistenImage_ = function() {
  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
  this.imageListenerKeys_ = null;
};

goog.provide('ol.style.Icon');
goog.provide('ol.style.IconAnchorUnits');
goog.provide('ol.style.IconOrigin');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.color');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.style.IconImage');
goog.require('ol.style.Image');
goog.require('ol.style.ImageState');


/**
 * Icon anchor units. One of 'fraction', 'pixels'.
 * @enum {string}
 */
ol.style.IconAnchorUnits = {
  FRACTION: 'fraction',
  PIXELS: 'pixels'
};


/**
 * Icon origin. One of 'bottom-left', 'bottom-right', 'top-left', 'top-right'.
 * @enum {string}
 */
ol.style.IconOrigin = {
  BOTTOM_LEFT: 'bottom-left',
  BOTTOM_RIGHT: 'bottom-right',
  TOP_LEFT: 'top-left',
  TOP_RIGHT: 'top-right'
};


/**
 * @classdesc
 * Set icon style for vector features.
 *
 * @constructor
 * @param {olx.style.IconOptions=} opt_options Options.
 * @extends {ol.style.Image}
 * @api
 */
ol.style.Icon = function(opt_options) {

  var options = opt_options || {};

  /**
   * @private
   * @type {Array.<number>}
   */
  this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];

  /**
   * @private
   * @type {Array.<number>}
   */
  this.normalizedAnchor_ = null;

  /**
   * @private
   * @type {ol.style.IconOrigin}
   */
  this.anchorOrigin_ = options.anchorOrigin !== undefined ?
      options.anchorOrigin : ol.style.IconOrigin.TOP_LEFT;

  /**
   * @private
   * @type {ol.style.IconAnchorUnits}
   */
  this.anchorXUnits_ = options.anchorXUnits !== undefined ?
      options.anchorXUnits : ol.style.IconAnchorUnits.FRACTION;

  /**
   * @private
   * @type {ol.style.IconAnchorUnits}
   */
  this.anchorYUnits_ = options.anchorYUnits !== undefined ?
      options.anchorYUnits : ol.style.IconAnchorUnits.FRACTION;

  /**
   * @type {?string}
   */
  var crossOrigin =
      options.crossOrigin !== undefined ? options.crossOrigin : null;

  /**
   * @type {Image|HTMLCanvasElement}
   */
  var image = options.img !== undefined ? options.img : null;

  /**
   * @type {ol.Size}
   */
  var imgSize = options.imgSize !== undefined ? options.imgSize : null;

  /**
   * @type {string|undefined}
   */
  var src = options.src;

  ol.asserts.assert(!(src !== undefined && image),
      4); // `image` and `src` cannot be provided at the same time
  ol.asserts.assert(!image || (image && imgSize),
      5); // `imgSize` must be set when `image` is provided

  if ((src === undefined || src.length === 0) && image) {
    src = image.src || ol.getUid(image).toString();
  }
  ol.asserts.assert(src !== undefined && src.length > 0,
      6); // A defined and non-empty `src` or `image` must be provided

  /**
   * @type {ol.style.ImageState}
   */
  var imageState = options.src !== undefined ?
      ol.style.ImageState.IDLE : ol.style.ImageState.LOADED;

  /**
   * @type {ol.Color}
   */
  var color = options.color !== undefined ? ol.color.asArray(options.color) :
      null;

  /**
   * @private
   * @type {ol.style.IconImage}
   */
  this.iconImage_ = ol.style.IconImage.get(
      image, /** @type {string} */ (src), imgSize, crossOrigin, imageState, color);

  /**
   * @private
   * @type {Array.<number>}
   */
  this.offset_ = options.offset !== undefined ? options.offset : [0, 0];

  /**
   * @private
   * @type {ol.style.IconOrigin}
   */
  this.offsetOrigin_ = options.offsetOrigin !== undefined ?
      options.offsetOrigin : ol.style.IconOrigin.TOP_LEFT;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.origin_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.size_ = options.size !== undefined ? options.size : null;

  /**
   * @type {number}
   */
  var opacity = options.opacity !== undefined ? options.opacity : 1;

  /**
   * @type {boolean}
   */
  var rotateWithView = options.rotateWithView !== undefined ?
      options.rotateWithView : false;

  /**
   * @type {number}
   */
  var rotation = options.rotation !== undefined ? options.rotation : 0;

  /**
   * @type {number}
   */
  var scale = options.scale !== undefined ? options.scale : 1;

  /**
   * @type {boolean}
   */
  var snapToPixel = options.snapToPixel !== undefined ?
      options.snapToPixel : true;

  ol.style.Image.call(this, {
    opacity: opacity,
    rotation: rotation,
    scale: scale,
    snapToPixel: snapToPixel,
    rotateWithView: rotateWithView
  });

};
ol.inherits(ol.style.Icon, ol.style.Image);


/**
 * @inheritDoc
 * @api
 */
ol.style.Icon.prototype.getAnchor = function() {
  if (this.normalizedAnchor_) {
    return this.normalizedAnchor_;
  }
  var anchor = this.anchor_;
  var size = this.getSize();
  if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION ||
      this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
    if (!size) {
      return null;
    }
    anchor = this.anchor_.slice();
    if (this.anchorXUnits_ == ol.style.IconAnchorUnits.FRACTION) {
      anchor[0] *= size[0];
    }
    if (this.anchorYUnits_ == ol.style.IconAnchorUnits.FRACTION) {
      anchor[1] *= size[1];
    }
  }

  if (this.anchorOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
    if (!size) {
      return null;
    }
    if (anchor === this.anchor_) {
      anchor = this.anchor_.slice();
    }
    if (this.anchorOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
        this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
      anchor[0] = -anchor[0] + size[0];
    }
    if (this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
        this.anchorOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
      anchor[1] = -anchor[1] + size[1];
    }
  }
  this.normalizedAnchor_ = anchor;
  return this.normalizedAnchor_;
};


/**
 * Get the image icon.
 * @param {number} pixelRatio Pixel ratio.
 * @return {Image|HTMLCanvasElement} Image or Canvas element.
 * @api
 */
ol.style.Icon.prototype.getImage = function(pixelRatio) {
  return this.iconImage_.getImage(pixelRatio);
};


/**
 * Real Image size used.
 * @return {ol.Size} Size.
 */
ol.style.Icon.prototype.getImageSize = function() {
  return this.iconImage_.getSize();
};


/**
 * @inheritDoc
 */
ol.style.Icon.prototype.getHitDetectionImageSize = function() {
  return this.getImageSize();
};


/**
 * @inheritDoc
 */
ol.style.Icon.prototype.getImageState = function() {
  return this.iconImage_.getImageState();
};


/**
 * @inheritDoc
 */
ol.style.Icon.prototype.getHitDetectionImage = function(pixelRatio) {
  return this.iconImage_.getHitDetectionImage(pixelRatio);
};


/**
 * @inheritDoc
 * @api
 */
ol.style.Icon.prototype.getOrigin = function() {
  if (this.origin_) {
    return this.origin_;
  }
  var offset = this.offset_;

  if (this.offsetOrigin_ != ol.style.IconOrigin.TOP_LEFT) {
    var size = this.getSize();
    var iconImageSize = this.iconImage_.getSize();
    if (!size || !iconImageSize) {
      return null;
    }
    offset = offset.slice();
    if (this.offsetOrigin_ == ol.style.IconOrigin.TOP_RIGHT ||
        this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
      offset[0] = iconImageSize[0] - size[0] - offset[0];
    }
    if (this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_LEFT ||
        this.offsetOrigin_ == ol.style.IconOrigin.BOTTOM_RIGHT) {
      offset[1] = iconImageSize[1] - size[1] - offset[1];
    }
  }
  this.origin_ = offset;
  return this.origin_;
};


/**
 * Get the image URL.
 * @return {string|undefined} Image src.
 * @api
 */
ol.style.Icon.prototype.getSrc = function() {
  return this.iconImage_.getSrc();
};


/**
 * @inheritDoc
 * @api
 */
ol.style.Icon.prototype.getSize = function() {
  return !this.size_ ? this.iconImage_.getSize() : this.size_;
};


/**
 * @inheritDoc
 */
ol.style.Icon.prototype.listenImageChange = function(listener, thisArg) {
  return ol.events.listen(this.iconImage_, ol.events.EventType.CHANGE,
      listener, thisArg);
};


/**
 * Load not yet loaded URI.
 * When rendering a feature with an icon style, the vector renderer will
 * automatically call this method. However, you might want to call this
 * method yourself for preloading or other purposes.
 * @api
 */
ol.style.Icon.prototype.load = function() {
  this.iconImage_.load();
};


/**
 * @inheritDoc
 */
ol.style.Icon.prototype.unlistenImageChange = function(listener, thisArg) {
  ol.events.unlisten(this.iconImage_, ol.events.EventType.CHANGE,
      listener, thisArg);
};

goog.provide('ol.style.Text');


goog.require('ol.style.Fill');


/**
 * @classdesc
 * Set text style for vector features.
 *
 * @constructor
 * @param {olx.style.TextOptions=} opt_options Options.
 * @api
 */
ol.style.Text = function(opt_options) {

  var options = opt_options || {};

  /**
   * @private
   * @type {string|undefined}
   */
  this.font_ = options.font;

  /**
   * @private
   * @type {number|undefined}
   */
  this.rotation_ = options.rotation;

  /**
   * @private
   * @type {boolean|undefined}
   */
  this.rotateWithView_ = options.rotateWithView;

  /**
   * @private
   * @type {number|undefined}
   */
  this.scale_ = options.scale;

  /**
   * @private
   * @type {string|undefined}
   */
  this.text_ = options.text;

  /**
   * @private
   * @type {string|undefined}
   */
  this.textAlign_ = options.textAlign;

  /**
   * @private
   * @type {string|undefined}
   */
  this.textBaseline_ = options.textBaseline;

  /**
   * @private
   * @type {ol.style.Fill}
   */
  this.fill_ = options.fill !== undefined ? options.fill :
      new ol.style.Fill({color: ol.style.Text.DEFAULT_FILL_COLOR_});

  /**
   * @private
   * @type {ol.style.Stroke}
   */
  this.stroke_ = options.stroke !== undefined ? options.stroke : null;

  /**
   * @private
   * @type {number}
   */
  this.offsetX_ = options.offsetX !== undefined ? options.offsetX : 0;

  /**
   * @private
   * @type {number}
   */
  this.offsetY_ = options.offsetY !== undefined ? options.offsetY : 0;
};


/**
 * The default fill color to use if no fill was set at construction time; a
 * blackish `#333`.
 *
 * @const {string}
 * @private
 */
ol.style.Text.DEFAULT_FILL_COLOR_ = '#333';


/**
 * Get the font name.
 * @return {string|undefined} Font.
 * @api
 */
ol.style.Text.prototype.getFont = function() {
  return this.font_;
};


/**
 * Get the x-offset for the text.
 * @return {number} Horizontal text offset.
 * @api
 */
ol.style.Text.prototype.getOffsetX = function() {
  return this.offsetX_;
};


/**
 * Get the y-offset for the text.
 * @return {number} Vertical text offset.
 * @api
 */
ol.style.Text.prototype.getOffsetY = function() {
  return this.offsetY_;
};


/**
 * Get the fill style for the text.
 * @return {ol.style.Fill} Fill style.
 * @api
 */
ol.style.Text.prototype.getFill = function() {
  return this.fill_;
};


/**
 * Determine whether the text rotates with the map.
 * @return {boolean|undefined} Rotate with map.
 * @api
 */
ol.style.Text.prototype.getRotateWithView = function() {
  return this.rotateWithView_;
};


/**
 * Get the text rotation.
 * @return {number|undefined} Rotation.
 * @api
 */
ol.style.Text.prototype.getRotation = function() {
  return this.rotation_;
};


/**
 * Get the text scale.
 * @return {number|undefined} Scale.
 * @api
 */
ol.style.Text.prototype.getScale = function() {
  return this.scale_;
};


/**
 * Get the stroke style for the text.
 * @return {ol.style.Stroke} Stroke style.
 * @api
 */
ol.style.Text.prototype.getStroke = function() {
  return this.stroke_;
};


/**
 * Get the text to be rendered.
 * @return {string|undefined} Text.
 * @api
 */
ol.style.Text.prototype.getText = function() {
  return this.text_;
};


/**
 * Get the text alignment.
 * @return {string|undefined} Text align.
 * @api
 */
ol.style.Text.prototype.getTextAlign = function() {
  return this.textAlign_;
};


/**
 * Get the text baseline.
 * @return {string|undefined} Text baseline.
 * @api
 */
ol.style.Text.prototype.getTextBaseline = function() {
  return this.textBaseline_;
};


/**
 * Set the font.
 *
 * @param {string|undefined} font Font.
 * @api
 */
ol.style.Text.prototype.setFont = function(font) {
  this.font_ = font;
};


/**
 * Set the x offset.
 *
 * @param {number} offsetX Horizontal text offset.
 * @api
 */
ol.style.Text.prototype.setOffsetX = function(offsetX) {
  this.offsetX_ = offsetX;
};


/**
 * Set the y offset.
 *
 * @param {number} offsetY Vertical text offset.
 * @api
 */
ol.style.Text.prototype.setOffsetY = function(offsetY) {
  this.offsetY_ = offsetY;
};


/**
 * Set the fill.
 *
 * @param {ol.style.Fill} fill Fill style.
 * @api
 */
ol.style.Text.prototype.setFill = function(fill) {
  this.fill_ = fill;
};


/**
 * Set the rotation.
 *
 * @param {number|undefined} rotation Rotation.
 * @api
 */
ol.style.Text.prototype.setRotation = function(rotation) {
  this.rotation_ = rotation;
};


/**
 * Set the scale.
 *
 * @param {number|undefined} scale Scale.
 * @api
 */
ol.style.Text.prototype.setScale = function(scale) {
  this.scale_ = scale;
};


/**
 * Set the stroke.
 *
 * @param {ol.style.Stroke} stroke Stroke style.
 * @api
 */
ol.style.Text.prototype.setStroke = function(stroke) {
  this.stroke_ = stroke;
};


/**
 * Set the text.
 *
 * @param {string|undefined} text Text.
 * @api
 */
ol.style.Text.prototype.setText = function(text) {
  this.text_ = text;
};


/**
 * Set the text alignment.
 *
 * @param {string|undefined} textAlign Text align.
 * @api
 */
ol.style.Text.prototype.setTextAlign = function(textAlign) {
  this.textAlign_ = textAlign;
};


/**
 * Set the text baseline.
 *
 * @param {string|undefined} textBaseline Text baseline.
 * @api
 */
ol.style.Text.prototype.setTextBaseline = function(textBaseline) {
  this.textBaseline_ = textBaseline;
};

// FIXME http://earth.google.com/kml/1.0 namespace?
// FIXME why does node.getAttribute return an unknown type?
// FIXME serialize arbitrary feature properties
// FIXME don't parse style if extractStyles is false

goog.provide('ol.format.KML');

goog.require('goog.object');
goog.require('ol');
goog.require('ol.Feature');
goog.require('ol.array');
goog.require('ol.asserts');
goog.require('ol.color');
goog.require('ol.format.Feature');
goog.require('ol.format.XMLFeature');
goog.require('ol.format.XSD');
goog.require('ol.geom.GeometryCollection');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.math');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.style.Fill');
goog.require('ol.style.Icon');
goog.require('ol.style.IconAnchorUnits');
goog.require('ol.style.IconOrigin');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
goog.require('ol.style.Text');
goog.require('ol.xml');


/**
 * @classdesc
 * Feature format for reading and writing data in the KML format.
 *
 * Note that the KML format uses the URL() constructor. Older browsers such as IE
 * which do not support this will need a URL polyfill to be loaded before use.
 *
 * @constructor
 * @extends {ol.format.XMLFeature}
 * @param {olx.format.KMLOptions=} opt_options Options.
 * @api stable
 */
ol.format.KML = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.XMLFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get('EPSG:4326');

  /**
   * @private
   * @type {Array.<ol.style.Style>}
   */
  this.defaultStyle_ = options.defaultStyle ?
      options.defaultStyle :
      (ol.format.KML.DEFAULT_STYLE_ARRAY_ || ol.format.KML.createStyleDefaults_());

  /**
   * @private
   * @type {boolean}
   */
  this.extractStyles_ = options.extractStyles !== undefined ?
      options.extractStyles : true;

  /**
   * @private
   * @type {boolean}
   */
  this.writeStyles_ = options.writeStyles !== undefined ?
      options.writeStyles : true;

  /**
   * @private
   * @type {Object.<string, (Array.<ol.style.Style>|string)>}
   */
  this.sharedStyles_ = {};

  /**
   * @private
   * @type {boolean}
   */
  this.showPointNames_ = options.showPointNames !== undefined ?
      options.showPointNames : true;

};
ol.inherits(ol.format.KML, ol.format.XMLFeature);


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.KML.EXTENSIONS_ = ['.kml'];


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.KML.GX_NAMESPACE_URIS_ = [
  'http://www.google.com/kml/ext/2.2'
];


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.KML.NAMESPACE_URIS_ = [
  null,
  'http://earth.google.com/kml/2.0',
  'http://earth.google.com/kml/2.1',
  'http://earth.google.com/kml/2.2',
  'http://www.opengis.net/kml/2.2'
];


/**
 * @const
 * @type {string}
 * @private
 */
ol.format.KML.SCHEMA_LOCATION_ = 'http://www.opengis.net/kml/2.2 ' +
    'https://developers.google.com/kml/schema/kml22gx.xsd';


/**
 * @return {Array.<ol.style.Style>} Default style.
 * @private
 */
ol.format.KML.createStyleDefaults_ = function() {
  /**
   * @const
   * @type {ol.Color}
   * @private
   */
  ol.format.KML.DEFAULT_COLOR_ = [255, 255, 255, 1];

  /**
   * @const
   * @type {ol.style.Fill}
   * @private
   */
  ol.format.KML.DEFAULT_FILL_STYLE_ = new ol.style.Fill({
    color: ol.format.KML.DEFAULT_COLOR_
  });

  /**
   * @const
   * @type {ol.Size}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_ = [20, 2]; // FIXME maybe [8, 32] ?

  /**
   * @const
   * @type {ol.style.IconAnchorUnits}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_ =
      ol.style.IconAnchorUnits.PIXELS;

  /**
   * @const
   * @type {ol.style.IconAnchorUnits}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_ =
      ol.style.IconAnchorUnits.PIXELS;

  /**
   * @const
   * @type {ol.Size}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_ = [64, 64];

  /**
   * @const
   * @type {string}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_ =
      'https://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png';

  /**
   * @const
   * @type {number}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_ = 0.5;

  /**
   * @const
   * @type {ol.style.Image}
   * @private
   */
  ol.format.KML.DEFAULT_IMAGE_STYLE_ = new ol.style.Icon({
    anchor: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_,
    anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
    anchorXUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_,
    anchorYUnits: ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_,
    crossOrigin: 'anonymous',
    rotation: 0,
    scale: ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_,
    size: ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_,
    src: ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_
  });

  /**
   * @const
   * @type {ol.style.Stroke}
   * @private
   */
  ol.format.KML.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
    color: ol.format.KML.DEFAULT_COLOR_,
    width: 1
  });

  /**
   * @const
   * @type {ol.style.Stroke}
   * @private
   */
  ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_ = new ol.style.Stroke({
    color: [51, 51, 51, 1],
    width: 2
  });

  /**
   * @const
   * @type {ol.style.Text}
   * @private
   */
  ol.format.KML.DEFAULT_TEXT_STYLE_ = new ol.style.Text({
    font: 'bold 16px Helvetica',
    fill: ol.format.KML.DEFAULT_FILL_STYLE_,
    stroke: ol.format.KML.DEFAULT_TEXT_STROKE_STYLE_,
    scale: 0.8
  });

  /**
   * @const
   * @type {ol.style.Style}
   * @private
   */
  ol.format.KML.DEFAULT_STYLE_ = new ol.style.Style({
    fill: ol.format.KML.DEFAULT_FILL_STYLE_,
    image: ol.format.KML.DEFAULT_IMAGE_STYLE_,
    text: ol.format.KML.DEFAULT_TEXT_STYLE_,
    stroke: ol.format.KML.DEFAULT_STROKE_STYLE_,
    zIndex: 0
  });

  /**
   * @const
   * @type {Array.<ol.style.Style>}
   * @private
   */
  ol.format.KML.DEFAULT_STYLE_ARRAY_ = [ol.format.KML.DEFAULT_STYLE_];

  return ol.format.KML.DEFAULT_STYLE_ARRAY_;
};


/**
 * @const
 * @type {Object.<string, ol.style.IconAnchorUnits>}
 * @private
 */
ol.format.KML.ICON_ANCHOR_UNITS_MAP_ = {
  'fraction': ol.style.IconAnchorUnits.FRACTION,
  'pixels': ol.style.IconAnchorUnits.PIXELS
};


/**
 * @param {ol.style.Style|undefined} foundStyle Style.
 * @param {string} name Name.
 * @return {ol.style.Style} style Style.
 * @private
 */
ol.format.KML.createNameStyleFunction_ = function(foundStyle, name) {
  var textStyle = null;
  var textOffset = [0, 0];
  var textAlign = 'start';
  if (foundStyle.getImage()) {
    var imageSize = foundStyle.getImage().getImageSize();
    if (imageSize && imageSize.length == 2) {
      // Offset the label to be centered to the right of the icon, if there is
      // one.
      textOffset[0] = foundStyle.getImage().getScale() * imageSize[0] / 2;
      textOffset[1] = -foundStyle.getImage().getScale() * imageSize[1] / 2;
      textAlign = 'left';
    }
  }
  if (!ol.obj.isEmpty(foundStyle.getText())) {
    textStyle = /** @type {ol.style.Text} */
        (goog.object.clone(foundStyle.getText()));
    textStyle.setText(name);
    textStyle.setTextAlign(textAlign);
    textStyle.setOffsetX(textOffset[0]);
    textStyle.setOffsetY(textOffset[1]);
  } else {
    textStyle = new ol.style.Text({
      text: name,
      offsetX: textOffset[0],
      offsetY: textOffset[1],
      textAlign: textAlign
    });
  }
  var nameStyle = new ol.style.Style({
    text: textStyle
  });
  return nameStyle;
};


/**
 * @param {Array.<ol.style.Style>|undefined} style Style.
 * @param {string} styleUrl Style URL.
 * @param {Array.<ol.style.Style>} defaultStyle Default style.
 * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles Shared
 *          styles.
 * @param {boolean|undefined} showPointNames true to show names for point
 *          placemarks.
 * @return {ol.FeatureStyleFunction} Feature style function.
 * @private
 */
ol.format.KML.createFeatureStyleFunction_ = function(style, styleUrl,
    defaultStyle, sharedStyles, showPointNames) {

  return (
      /**
       * @param {number} resolution Resolution.
       * @return {Array.<ol.style.Style>} Style.
       * @this {ol.Feature}
       */
      function(resolution) {
        var drawName = showPointNames;
        /** @type {ol.style.Style|undefined} */
        var nameStyle;
        var name = '';
        if (drawName) {
          if (this.getGeometry()) {
            drawName = (this.getGeometry().getType() ===
                        ol.geom.GeometryType.POINT);
          }
        }

        if (drawName) {
          name = /** @type {string} */ (this.get('name'));
          drawName = drawName && name;
        }

        if (style) {
          if (drawName) {
            nameStyle = ol.format.KML.createNameStyleFunction_(style[0],
                name);
            return style.concat(nameStyle);
          }
          return style;
        }
        if (styleUrl) {
          var foundStyle = ol.format.KML.findStyle_(styleUrl, defaultStyle,
              sharedStyles);
          if (drawName) {
            nameStyle = ol.format.KML.createNameStyleFunction_(foundStyle[0],
                name);
            return foundStyle.concat(nameStyle);
          }
          return foundStyle;
        }
        if (drawName) {
          nameStyle = ol.format.KML.createNameStyleFunction_(defaultStyle[0],
              name);
          return defaultStyle.concat(nameStyle);
        }
        return defaultStyle;
      });
};


/**
 * @param {Array.<ol.style.Style>|string|undefined} styleValue Style value.
 * @param {Array.<ol.style.Style>} defaultStyle Default style.
 * @param {Object.<string, (Array.<ol.style.Style>|string)>} sharedStyles
 * Shared styles.
 * @return {Array.<ol.style.Style>} Style.
 * @private
 */
ol.format.KML.findStyle_ = function(styleValue, defaultStyle, sharedStyles) {
  if (Array.isArray(styleValue)) {
    return styleValue;
  } else if (typeof styleValue === 'string') {
    // KML files in the wild occasionally forget the leading `#` on styleUrls
    // defined in the same document.  Add a leading `#` if it enables to find
    // a style.
    if (!(styleValue in sharedStyles) && ('#' + styleValue in sharedStyles)) {
      styleValue = '#' + styleValue;
    }
    return ol.format.KML.findStyle_(
        sharedStyles[styleValue], defaultStyle, sharedStyles);
  } else {
    return defaultStyle;
  }
};


/**
 * @param {Node} node Node.
 * @private
 * @return {ol.Color|undefined} Color.
 */
ol.format.KML.readColor_ = function(node) {
  var s = ol.xml.getAllTextContent(node, false);
  // The KML specification states that colors should not include a leading `#`
  // but we tolerate them.
  var m = /^\s*#?\s*([0-9A-Fa-f]{8})\s*$/.exec(s);
  if (m) {
    var hexColor = m[1];
    return [
      parseInt(hexColor.substr(6, 2), 16),
      parseInt(hexColor.substr(4, 2), 16),
      parseInt(hexColor.substr(2, 2), 16),
      parseInt(hexColor.substr(0, 2), 16) / 255
    ];

  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @private
 * @return {Array.<number>|undefined} Flat coordinates.
 */
ol.format.KML.readFlatCoordinates_ = function(node) {
  var s = ol.xml.getAllTextContent(node, false);
  var flatCoordinates = [];
  // The KML specification states that coordinate tuples should not include
  // spaces, but we tolerate them.
  var re =
      /^\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?)(?:\s*,\s*([+\-]?\d*\.?\d+(?:e[+\-]?\d+)?))?\s*/i;
  var m;
  while ((m = re.exec(s))) {
    var x = parseFloat(m[1]);
    var y = parseFloat(m[2]);
    var z = m[3] ? parseFloat(m[3]) : 0;
    flatCoordinates.push(x, y, z);
    s = s.substr(m[0].length);
  }
  if (s !== '') {
    return undefined;
  }
  return flatCoordinates;
};


/**
 * @param {Node} node Node.
 * @private
 * @return {string} URI.
 */
ol.format.KML.readURI_ = function(node) {
  var s = ol.xml.getAllTextContent(node, false).trim();
  if (node.baseURI) {
    var url = new URL(s, node.baseURI);
    return url.href;
  } else {
    return s;
  }
};


/**
 * @param {Node} node Node.
 * @private
 * @return {ol.KMLVec2_} Vec2.
 */
ol.format.KML.readVec2_ = function(node) {
  var xunits = node.getAttribute('xunits');
  var yunits = node.getAttribute('yunits');
  return {
    x: parseFloat(node.getAttribute('x')),
    xunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[xunits],
    y: parseFloat(node.getAttribute('y')),
    yunits: ol.format.KML.ICON_ANCHOR_UNITS_MAP_[yunits]
  };
};


/**
 * @param {Node} node Node.
 * @private
 * @return {number|undefined} Scale.
 */
ol.format.KML.readScale_ = function(node) {
  var number = ol.format.XSD.readDecimal(node);
  if (number !== undefined) {
    return Math.sqrt(number);
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<ol.style.Style>|string|undefined} StyleMap.
 */
ol.format.KML.readStyleMapValue_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(undefined,
      ol.format.KML.STYLE_MAP_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.IconStyleParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be an ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'IconStyle',
      'localName should be IconStyle');
  // FIXME refreshMode
  // FIXME refreshInterval
  // FIXME viewRefreshTime
  // FIXME viewBoundScale
  // FIXME viewFormat
  // FIXME httpQuery
  var object = ol.xml.pushParseAndPop(
      {}, ol.format.KML.ICON_STYLE_PARSERS_, node, objectStack);
  if (!object) {
    return;
  }
  var styleObject = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var IconObject = 'Icon' in object ? object['Icon'] : {};
  var src;
  var href = /** @type {string|undefined} */
      (IconObject['href']);
  if (href) {
    src = href;
  } else {
    src = ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_;
  }
  var anchor, anchorXUnits, anchorYUnits;
  var hotSpot = /** @type {ol.KMLVec2_|undefined} */
      (object['hotSpot']);
  if (hotSpot) {
    anchor = [hotSpot.x, hotSpot.y];
    anchorXUnits = hotSpot.xunits;
    anchorYUnits = hotSpot.yunits;
  } else if (src === ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
    anchor = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_;
    anchorXUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_X_UNITS_;
    anchorYUnits = ol.format.KML.DEFAULT_IMAGE_STYLE_ANCHOR_Y_UNITS_;
  } else if (/^http:\/\/maps\.(?:google|gstatic)\.com\//.test(src)) {
    anchor = [0.5, 0];
    anchorXUnits = ol.style.IconAnchorUnits.FRACTION;
    anchorYUnits = ol.style.IconAnchorUnits.FRACTION;
  }

  var offset;
  var x = /** @type {number|undefined} */
      (IconObject['x']);
  var y = /** @type {number|undefined} */
      (IconObject['y']);
  if (x !== undefined && y !== undefined) {
    offset = [x, y];
  }

  var size;
  var w = /** @type {number|undefined} */
      (IconObject['w']);
  var h = /** @type {number|undefined} */
      (IconObject['h']);
  if (w !== undefined && h !== undefined) {
    size = [w, h];
  }

  var rotation;
  var heading = /** @type {number} */
      (object['heading']);
  if (heading !== undefined) {
    rotation = ol.math.toRadians(heading);
  }

  var scale = /** @type {number|undefined} */
      (object['scale']);
  if (src == ol.format.KML.DEFAULT_IMAGE_STYLE_SRC_) {
    size = ol.format.KML.DEFAULT_IMAGE_STYLE_SIZE_;
    if (scale === undefined) {
      scale = ol.format.KML.DEFAULT_IMAGE_SCALE_MULTIPLIER_;
    }
  }

  var imageStyle = new ol.style.Icon({
    anchor: anchor,
    anchorOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
    anchorXUnits: anchorXUnits,
    anchorYUnits: anchorYUnits,
    crossOrigin: 'anonymous', // FIXME should this be configurable?
    offset: offset,
    offsetOrigin: ol.style.IconOrigin.BOTTOM_LEFT,
    rotation: rotation,
    scale: scale,
    size: size,
    src: src
  });
  styleObject['imageStyle'] = imageStyle;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.LabelStyleParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LabelStyle',
      'localName should be LabelStyle');
  // FIXME colorMode
  var object = ol.xml.pushParseAndPop(
      {}, ol.format.KML.LABEL_STYLE_PARSERS_, node, objectStack);
  if (!object) {
    return;
  }
  var styleObject = objectStack[objectStack.length - 1];
  var textStyle = new ol.style.Text({
    fill: new ol.style.Fill({
      color: /** @type {ol.Color} */
          ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_)
    }),
    scale: /** @type {number|undefined} */
        (object['scale'])
  });
  styleObject['textStyle'] = textStyle;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.LineStyleParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LineStyle',
      'localName should be LineStyle');
  // FIXME colorMode
  // FIXME gx:outerColor
  // FIXME gx:outerWidth
  // FIXME gx:physicalWidth
  // FIXME gx:labelVisibility
  var object = ol.xml.pushParseAndPop(
      {}, ol.format.KML.LINE_STYLE_PARSERS_, node, objectStack);
  if (!object) {
    return;
  }
  var styleObject = objectStack[objectStack.length - 1];
  var strokeStyle = new ol.style.Stroke({
    color: /** @type {ol.Color} */
        ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_),
    width: /** @type {number} */ ('width' in object ? object['width'] : 1)
  });
  styleObject['strokeStyle'] = strokeStyle;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.PolyStyleParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'PolyStyle',
      'localName should be PolyStyle');
  // FIXME colorMode
  var object = ol.xml.pushParseAndPop(
      {}, ol.format.KML.POLY_STYLE_PARSERS_, node, objectStack);
  if (!object) {
    return;
  }
  var styleObject = objectStack[objectStack.length - 1];
  var fillStyle = new ol.style.Fill({
    color: /** @type {ol.Color} */
        ('color' in object ? object['color'] : ol.format.KML.DEFAULT_COLOR_)
  });
  styleObject['fillStyle'] = fillStyle;
  var fill = /** @type {boolean|undefined} */ (object['fill']);
  if (fill !== undefined) {
    styleObject['fill'] = fill;
  }
  var outline =
      /** @type {boolean|undefined} */ (object['outline']);
  if (outline !== undefined) {
    styleObject['outline'] = outline;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>} LinearRing flat coordinates.
 */
ol.format.KML.readFlatLinearRing_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LinearRing',
      'localName should be LinearRing');
  return ol.xml.pushParseAndPop(null,
      ol.format.KML.FLAT_LINEAR_RING_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.gxCoordParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(ol.array.includes(
      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI),
      'namespaceURI of the node should be known to the KML parser');
  goog.DEBUG && console.assert(node.localName == 'coord', 'localName should be coord');
  var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */
      (objectStack[objectStack.length - 1]);
  var flatCoordinates = gxTrackObject.flatCoordinates;
  var s = ol.xml.getAllTextContent(node, false);
  var re =
      /^\s*([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s+([+\-]?\d+(?:\.\d*)?(?:e[+\-]?\d*)?)\s*$/i;
  var m = re.exec(s);
  if (m) {
    var x = parseFloat(m[1]);
    var y = parseFloat(m[2]);
    var z = parseFloat(m[3]);
    flatCoordinates.push(x, y, z, 0);
  } else {
    flatCoordinates.push(0, 0, 0, 0);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.MultiLineString|undefined} MultiLineString.
 */
ol.format.KML.readGxMultiTrack_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(ol.array.includes(
      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI),
      'namespaceURI of the node should be known to the KML parser');
  goog.DEBUG && console.assert(node.localName == 'MultiTrack',
      'localName should be MultiTrack');
  var lineStrings = ol.xml.pushParseAndPop([],
      ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_, node, objectStack);
  if (!lineStrings) {
    return undefined;
  }
  var multiLineString = new ol.geom.MultiLineString(null);
  multiLineString.setLineStrings(lineStrings);
  return multiLineString;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.LineString|undefined} LineString.
 */
ol.format.KML.readGxTrack_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(ol.array.includes(
      ol.format.KML.GX_NAMESPACE_URIS_, node.namespaceURI),
      'namespaceURI of the node should be known to the KML parser');
  goog.DEBUG && console.assert(node.localName == 'Track', 'localName should be Track');
  var gxTrackObject = ol.xml.pushParseAndPop(
      /** @type {ol.KMLGxTrackObject_} */ ({
        flatCoordinates: [],
        whens: []
      }), ol.format.KML.GX_TRACK_PARSERS_, node, objectStack);
  if (!gxTrackObject) {
    return undefined;
  }
  var flatCoordinates = gxTrackObject.flatCoordinates;
  var whens = gxTrackObject.whens;
  goog.DEBUG && console.assert(flatCoordinates.length / 4 == whens.length,
      'the length of the flatCoordinates array divided by 4 should be the ' +
      'length of the whens array');
  var i, ii;
  for (i = 0, ii = Math.min(flatCoordinates.length, whens.length); i < ii;
       ++i) {
    flatCoordinates[4 * i + 3] = whens[i];
  }
  var lineString = new ol.geom.LineString(null);
  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZM, flatCoordinates);
  return lineString;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object} Icon object.
 */
ol.format.KML.readIcon_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Icon', 'localName should be Icon');
  var iconObject = ol.xml.pushParseAndPop(
      {}, ol.format.KML.ICON_PARSERS_, node, objectStack);
  if (iconObject) {
    return iconObject;
  } else {
    return null;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<number>} Flat coordinates.
 */
ol.format.KML.readFlatCoordinatesFromNode_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return ol.xml.pushParseAndPop(null,
      ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.LineString|undefined} LineString.
 */
ol.format.KML.readLineString_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LineString',
      'localName should be LineString');
  var properties = ol.xml.pushParseAndPop({},
      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
      objectStack);
  var flatCoordinates =
      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var lineString = new ol.geom.LineString(null);
    lineString.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
    lineString.setProperties(properties);
    return lineString;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.Polygon|undefined} Polygon.
 */
ol.format.KML.readLinearRing_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'LinearRing',
      'localName should be LinearRing');
  var properties = ol.xml.pushParseAndPop({},
      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
      objectStack);
  var flatCoordinates =
      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var polygon = new ol.geom.Polygon(null);
    polygon.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates,
        [flatCoordinates.length]);
    polygon.setProperties(properties);
    return polygon;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.KML.readMultiGeometry_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MultiGeometry',
      'localName should be MultiGeometry');
  var geometries = ol.xml.pushParseAndPop([],
      ol.format.KML.MULTI_GEOMETRY_PARSERS_, node, objectStack);
  if (!geometries) {
    return null;
  }
  if (geometries.length === 0) {
    return new ol.geom.GeometryCollection(geometries);
  }
  /** @type {ol.geom.Geometry} */
  var multiGeometry;
  var homogeneous = true;
  var type = geometries[0].getType();
  var geometry, i, ii;
  for (i = 1, ii = geometries.length; i < ii; ++i) {
    geometry = geometries[i];
    if (geometry.getType() != type) {
      homogeneous = false;
      break;
    }
  }
  if (homogeneous) {
    var layout;
    var flatCoordinates;
    if (type == ol.geom.GeometryType.POINT) {
      var point = geometries[0];
      layout = point.getLayout();
      flatCoordinates = point.getFlatCoordinates();
      for (i = 1, ii = geometries.length; i < ii; ++i) {
        geometry = geometries[i];
        goog.DEBUG && console.assert(geometry.getLayout() == layout,
            'geometry layout should be consistent');
        ol.array.extend(flatCoordinates, geometry.getFlatCoordinates());
      }
      multiGeometry = new ol.geom.MultiPoint(null);
      multiGeometry.setFlatCoordinates(layout, flatCoordinates);
      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
    } else if (type == ol.geom.GeometryType.LINE_STRING) {
      multiGeometry = new ol.geom.MultiLineString(null);
      multiGeometry.setLineStrings(geometries);
      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
    } else if (type == ol.geom.GeometryType.POLYGON) {
      multiGeometry = new ol.geom.MultiPolygon(null);
      multiGeometry.setPolygons(geometries);
      ol.format.KML.setCommonGeometryProperties_(multiGeometry, geometries);
    } else if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
      multiGeometry = new ol.geom.GeometryCollection(geometries);
    } else {
      ol.asserts.assert(false, 37); // Unknown geometry type found
    }
  } else {
    multiGeometry = new ol.geom.GeometryCollection(geometries);
  }
  return /** @type {ol.geom.Geometry} */ (multiGeometry);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.Point|undefined} Point.
 */
ol.format.KML.readPoint_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Point', 'localName should be Point');
  var properties = ol.xml.pushParseAndPop({},
      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
      objectStack);
  var flatCoordinates =
      ol.format.KML.readFlatCoordinatesFromNode_(node, objectStack);
  if (flatCoordinates) {
    var point = new ol.geom.Point(null);
    goog.DEBUG && console.assert(flatCoordinates.length == 3,
        'flatCoordinates should have a length of 3');
    point.setFlatCoordinates(ol.geom.GeometryLayout.XYZ, flatCoordinates);
    point.setProperties(properties);
    return point;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.geom.Polygon|undefined} Polygon.
 */
ol.format.KML.readPolygon_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Polygon',
      'localName should be Polygon');
  var properties = ol.xml.pushParseAndPop(/** @type {Object<string,*>} */ ({}),
      ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_, node,
      objectStack);
  var flatLinearRings = ol.xml.pushParseAndPop([null],
      ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_, node, objectStack);
  if (flatLinearRings && flatLinearRings[0]) {
    var polygon = new ol.geom.Polygon(null);
    var flatCoordinates = flatLinearRings[0];
    var ends = [flatCoordinates.length];
    var i, ii;
    for (i = 1, ii = flatLinearRings.length; i < ii; ++i) {
      ol.array.extend(flatCoordinates, flatLinearRings[i]);
      ends.push(flatCoordinates.length);
    }
    polygon.setFlatCoordinates(
        ol.geom.GeometryLayout.XYZ, flatCoordinates, ends);
    polygon.setProperties(properties);
    return polygon;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<ol.style.Style>} Style.
 */
ol.format.KML.readStyle_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style');
  var styleObject = ol.xml.pushParseAndPop(
      {}, ol.format.KML.STYLE_PARSERS_, node, objectStack);
  if (!styleObject) {
    return null;
  }
  var fillStyle = /** @type {ol.style.Fill} */
      ('fillStyle' in styleObject ?
          styleObject['fillStyle'] : ol.format.KML.DEFAULT_FILL_STYLE_);
  var fill = /** @type {boolean|undefined} */ (styleObject['fill']);
  if (fill !== undefined && !fill) {
    fillStyle = null;
  }
  var imageStyle = /** @type {ol.style.Image} */
      ('imageStyle' in styleObject ?
          styleObject['imageStyle'] : ol.format.KML.DEFAULT_IMAGE_STYLE_);
  var textStyle = /** @type {ol.style.Text} */
      ('textStyle' in styleObject ?
          styleObject['textStyle'] : ol.format.KML.DEFAULT_TEXT_STYLE_);
  var strokeStyle = /** @type {ol.style.Stroke} */
      ('strokeStyle' in styleObject ?
          styleObject['strokeStyle'] : ol.format.KML.DEFAULT_STROKE_STYLE_);
  var outline = /** @type {boolean|undefined} */
      (styleObject['outline']);
  if (outline !== undefined && !outline) {
    strokeStyle = null;
  }
  return [new ol.style.Style({
    fill: fillStyle,
    image: imageStyle,
    stroke: strokeStyle,
    text: textStyle,
    zIndex: undefined // FIXME
  })];
};


/**
 * Reads an array of geometries and creates arrays for common geometry
 * properties. Then sets them to the multi geometry.
 * @param {ol.geom.MultiPoint|ol.geom.MultiLineString|ol.geom.MultiPolygon}
 *     multiGeometry A multi-geometry.
 * @param {Array.<ol.geom.Geometry>} geometries List of geometries.
 * @private
 */
ol.format.KML.setCommonGeometryProperties_ = function(multiGeometry,
    geometries) {
  var ii = geometries.length;
  var extrudes = new Array(geometries.length);
  var altitudeModes = new Array(geometries.length);
  var geometry, i, hasExtrude, hasAltitudeMode;
  hasExtrude = hasAltitudeMode = false;
  for (i = 0; i < ii; ++i) {
    geometry = geometries[i];
    extrudes[i] = geometry.get('extrude');
    altitudeModes[i] = geometry.get('altitudeMode');
    hasExtrude = hasExtrude || extrudes[i] !== undefined;
    hasAltitudeMode = hasAltitudeMode || altitudeModes[i];
  }
  if (hasExtrude) {
    multiGeometry.set('extrude', extrudes);
  }
  if (hasAltitudeMode) {
    multiGeometry.set('altitudeMode', altitudeModes);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.DataParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Data', 'localName should be Data');
  var name = node.getAttribute('name');
  if (name !== null) {
    var data = ol.xml.pushParseAndPop(
        undefined, ol.format.KML.DATA_PARSERS_, node, objectStack);
    if (data) {
      var featureObject =
          /** @type {Object} */ (objectStack[objectStack.length - 1]);
      featureObject[name] = data;
    }
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.ExtendedDataParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ExtendedData',
      'localName should be ExtendedData');
  ol.xml.parseNode(ol.format.KML.EXTENDED_DATA_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.PairDataParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Pair', 'localName should be Pair');
  var pairObject = ol.xml.pushParseAndPop(
      {}, ol.format.KML.PAIR_PARSERS_, node, objectStack);
  if (!pairObject) {
    return;
  }
  var key = /** @type {string|undefined} */
      (pairObject['key']);
  if (key && key == 'normal') {
    var styleUrl = /** @type {string|undefined} */
        (pairObject['styleUrl']);
    if (styleUrl) {
      objectStack[objectStack.length - 1] = styleUrl;
    }
    var Style = /** @type {ol.style.Style} */
        (pairObject['Style']);
    if (Style) {
      objectStack[objectStack.length - 1] = Style;
    }
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.PlacemarkStyleMapParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'StyleMap',
      'localName should be StyleMap');
  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
  if (!styleMapValue) {
    return;
  }
  var placemarkObject = objectStack[objectStack.length - 1];
  if (Array.isArray(styleMapValue)) {
    placemarkObject['Style'] = styleMapValue;
  } else if (typeof styleMapValue === 'string') {
    placemarkObject['styleUrl'] = styleMapValue;
  } else {
    ol.asserts.assert(false, 38); // `styleMapValue` has an unknown type
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.SchemaDataParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'SchemaData',
      'localName should be SchemaData');
  ol.xml.parseNode(ol.format.KML.SCHEMA_DATA_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.SimpleDataParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'SimpleData',
      'localName should be SimpleData');
  var name = node.getAttribute('name');
  if (name !== null) {
    var data = ol.format.XSD.readString(node);
    var featureObject =
        /** @type {Object} */ (objectStack[objectStack.length - 1]);
    featureObject[name] = data;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.innerBoundaryIsParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'innerBoundaryIs',
      'localName should be innerBoundaryIs');
  /** @type {Array.<number>|undefined} */
  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
      ol.format.KML.INNER_BOUNDARY_IS_PARSERS_, node, objectStack);
  if (flatLinearRing) {
    var flatLinearRings = /** @type {Array.<Array.<number>>} */
        (objectStack[objectStack.length - 1]);
    goog.DEBUG && console.assert(Array.isArray(flatLinearRings),
        'flatLinearRings should be an array');
    goog.DEBUG && console.assert(flatLinearRings.length > 0,
        'flatLinearRings array should not be empty');
    flatLinearRings.push(flatLinearRing);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.outerBoundaryIsParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'outerBoundaryIs',
      'localName should be outerBoundaryIs');
  /** @type {Array.<number>|undefined} */
  var flatLinearRing = ol.xml.pushParseAndPop(undefined,
      ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_, node, objectStack);
  if (flatLinearRing) {
    var flatLinearRings = /** @type {Array.<Array.<number>>} */
        (objectStack[objectStack.length - 1]);
    goog.DEBUG && console.assert(Array.isArray(flatLinearRings),
        'flatLinearRings should be an array');
    goog.DEBUG && console.assert(flatLinearRings.length > 0,
        'flatLinearRings array should not be empty');
    flatLinearRings[0] = flatLinearRing;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.LinkParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Link', 'localName should be Link');
  ol.xml.parseNode(ol.format.KML.LINK_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.whenParser_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'when', 'localName should be when');
  var gxTrackObject = /** @type {ol.KMLGxTrackObject_} */
      (objectStack[objectStack.length - 1]);
  var whens = gxTrackObject.whens;
  var s = ol.xml.getAllTextContent(node, false);
  var when = Date.parse(s);
  whens.push(isNaN(when) ? 0 : when);
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.DATA_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'value': ol.xml.makeReplacer(ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.EXTENDED_DATA_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Data': ol.format.KML.DataParser_,
      'SchemaData': ol.format.KML.SchemaDataParser_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.EXTRUDE_AND_ALTITUDE_MODE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'extrude': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
      'altitudeMode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.FLAT_LINEAR_RING_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.FLAT_LINEAR_RINGS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'innerBoundaryIs': ol.format.KML.innerBoundaryIsParser_,
      'outerBoundaryIs': ol.format.KML.outerBoundaryIsParser_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.GX_TRACK_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'when': ol.format.KML.whenParser_
    }, ol.xml.makeStructureNS(
        ol.format.KML.GX_NAMESPACE_URIS_, {
          'coord': ol.format.KML.gxCoordParser_
        }));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.GEOMETRY_FLAT_COORDINATES_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'coordinates': ol.xml.makeReplacer(ol.format.KML.readFlatCoordinates_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.ICON_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
    }, ol.xml.makeStructureNS(
        ol.format.KML.GX_NAMESPACE_URIS_, {
          'x': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
          'y': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
          'w': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
          'h': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
        }));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.ICON_STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Icon': ol.xml.makeObjectPropertySetter(ol.format.KML.readIcon_),
      'heading': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal),
      'hotSpot': ol.xml.makeObjectPropertySetter(ol.format.KML.readVec2_),
      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.INNER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.LABEL_STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
      'scale': ol.xml.makeObjectPropertySetter(ol.format.KML.readScale_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.LINE_STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
      'width': ol.xml.makeObjectPropertySetter(ol.format.XSD.readDecimal)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.MULTI_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'LineString': ol.xml.makeArrayPusher(ol.format.KML.readLineString_),
      'LinearRing': ol.xml.makeArrayPusher(ol.format.KML.readLinearRing_),
      'MultiGeometry': ol.xml.makeArrayPusher(ol.format.KML.readMultiGeometry_),
      'Point': ol.xml.makeArrayPusher(ol.format.KML.readPoint_),
      'Polygon': ol.xml.makeArrayPusher(ol.format.KML.readPolygon_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.GX_MULTITRACK_GEOMETRY_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.GX_NAMESPACE_URIS_, {
      'Track': ol.xml.makeArrayPusher(ol.format.KML.readGxTrack_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.NETWORK_LINK_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'ExtendedData': ol.format.KML.ExtendedDataParser_,
      'Link': ol.format.KML.LinkParser_,
      'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
      'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.LINK_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'href': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.OUTER_BOUNDARY_IS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'LinearRing': ol.xml.makeReplacer(ol.format.KML.readFlatLinearRing_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.PAIR_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_),
      'key': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.PLACEMARK_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'ExtendedData': ol.format.KML.ExtendedDataParser_,
      'MultiGeometry': ol.xml.makeObjectPropertySetter(
          ol.format.KML.readMultiGeometry_, 'geometry'),
      'LineString': ol.xml.makeObjectPropertySetter(
          ol.format.KML.readLineString_, 'geometry'),
      'LinearRing': ol.xml.makeObjectPropertySetter(
          ol.format.KML.readLinearRing_, 'geometry'),
      'Point': ol.xml.makeObjectPropertySetter(
          ol.format.KML.readPoint_, 'geometry'),
      'Polygon': ol.xml.makeObjectPropertySetter(
          ol.format.KML.readPolygon_, 'geometry'),
      'Style': ol.xml.makeObjectPropertySetter(ol.format.KML.readStyle_),
      'StyleMap': ol.format.KML.PlacemarkStyleMapParser_,
      'address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'description': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'open': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
      'phoneNumber': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'styleUrl': ol.xml.makeObjectPropertySetter(ol.format.KML.readURI_),
      'visibility': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
    }, ol.xml.makeStructureNS(
        ol.format.KML.GX_NAMESPACE_URIS_, {
          'MultiTrack': ol.xml.makeObjectPropertySetter(
              ol.format.KML.readGxMultiTrack_, 'geometry'),
          'Track': ol.xml.makeObjectPropertySetter(
              ol.format.KML.readGxTrack_, 'geometry')
        }
    ));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.POLY_STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'color': ol.xml.makeObjectPropertySetter(ol.format.KML.readColor_),
      'fill': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean),
      'outline': ol.xml.makeObjectPropertySetter(ol.format.XSD.readBoolean)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.SCHEMA_DATA_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'SimpleData': ol.format.KML.SimpleDataParser_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'IconStyle': ol.format.KML.IconStyleParser_,
      'LabelStyle': ol.format.KML.LabelStyleParser_,
      'LineStyle': ol.format.KML.LineStyleParser_,
      'PolyStyle': ol.format.KML.PolyStyleParser_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.KML.STYLE_MAP_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Pair': ol.format.KML.PairDataParser_
    });


/**
 * @inheritDoc
 */
ol.format.KML.prototype.getExtensions = function() {
  return ol.format.KML.EXTENSIONS_;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<ol.Feature>|undefined} Features.
 */
ol.format.KML.prototype.readDocumentOrFolder_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  var localName = node.localName;
  goog.DEBUG && console.assert(localName == 'Document' || localName == 'Folder',
      'localName should be Document or Folder');
  // FIXME use scope somehow
  var parsersNS = ol.xml.makeStructureNS(
      ol.format.KML.NAMESPACE_URIS_, {
        'Document': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
        'Folder': ol.xml.makeArrayExtender(this.readDocumentOrFolder_, this),
        'Placemark': ol.xml.makeArrayPusher(this.readPlacemark_, this),
        'Style': this.readSharedStyle_.bind(this),
        'StyleMap': this.readSharedStyleMap_.bind(this)
      });
  /** @type {Array.<ol.Feature>} */
  var features = ol.xml.pushParseAndPop([], parsersNS, node, objectStack, this);
  if (features) {
    return features;
  } else {
    return undefined;
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {ol.Feature|undefined} Feature.
 */
ol.format.KML.prototype.readPlacemark_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Placemark',
      'localName should be Placemark');
  var object = ol.xml.pushParseAndPop({'geometry': null},
      ol.format.KML.PLACEMARK_PARSERS_, node, objectStack);
  if (!object) {
    return undefined;
  }
  var feature = new ol.Feature();
  var id = node.getAttribute('id');
  if (id !== null) {
    feature.setId(id);
  }
  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);

  var geometry = object['geometry'];
  if (geometry) {
    ol.format.Feature.transformWithOptions(geometry, false, options);
  }
  feature.setGeometry(geometry);
  delete object['geometry'];

  if (this.extractStyles_) {
    var style = object['Style'];
    var styleUrl = object['styleUrl'];
    var styleFunction = ol.format.KML.createFeatureStyleFunction_(
        style, styleUrl, this.defaultStyle_, this.sharedStyles_,
        this.showPointNames_);
    feature.setStyle(styleFunction);
  }
  delete object['Style'];
  // we do not remove the styleUrl property from the object, so it
  // gets stored on feature when setProperties is called

  feature.setProperties(object);

  return feature;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.prototype.readSharedStyle_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style');
  var id = node.getAttribute('id');
  if (id !== null) {
    var style = ol.format.KML.readStyle_(node, objectStack);
    if (style) {
      var styleUri;
      if (node.baseURI) {
        var url = new URL('#' + id, node.baseURI);
        styleUri = url.href;
      } else {
        styleUri = '#' + id;
      }
      this.sharedStyles_[styleUri] = style;
    }
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.prototype.readSharedStyleMap_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'StyleMap',
      'localName should be StyleMap');
  var id = node.getAttribute('id');
  if (id === null) {
    return;
  }
  var styleMapValue = ol.format.KML.readStyleMapValue_(node, objectStack);
  if (!styleMapValue) {
    return;
  }
  var styleUri;
  if (node.baseURI) {
    var url = new URL('#' + id, node.baseURI);
    styleUri = url.href;
  } else {
    styleUri = '#' + id;
  }
  this.sharedStyles_[styleUri] = styleMapValue;
};


/**
 * Read the first feature from a KML source. MultiGeometries are converted into
 * GeometryCollections if they are a mix of geometry types, and into MultiPoint/
 * MultiLineString/MultiPolygon if they are all of the same type.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api stable
 */
ol.format.KML.prototype.readFeature;


/**
 * @inheritDoc
 */
ol.format.KML.prototype.readFeatureFromNode = function(node, opt_options) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
    return null;
  }
  goog.DEBUG && console.assert(node.localName == 'Placemark',
      'localName should be Placemark');
  var feature = this.readPlacemark_(
      node, [this.getReadOptions(node, opt_options)]);
  if (feature) {
    return feature;
  } else {
    return null;
  }
};


/**
 * Read all features from a KML source. MultiGeometries are converted into
 * GeometryCollections if they are a mix of geometry types, and into MultiPoint/
 * MultiLineString/MultiPolygon if they are all of the same type.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.KML.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.KML.prototype.readFeaturesFromNode = function(node, opt_options) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  if (!ol.array.includes(ol.format.KML.NAMESPACE_URIS_, node.namespaceURI)) {
    return [];
  }
  var features;
  var localName = node.localName;
  if (localName == 'Document' || localName == 'Folder') {
    features = this.readDocumentOrFolder_(
        node, [this.getReadOptions(node, opt_options)]);
    if (features) {
      return features;
    } else {
      return [];
    }
  } else if (localName == 'Placemark') {
    var feature = this.readPlacemark_(
        node, [this.getReadOptions(node, opt_options)]);
    if (feature) {
      return [feature];
    } else {
      return [];
    }
  } else if (localName == 'kml') {
    features = [];
    var n;
    for (n = node.firstElementChild; n; n = n.nextElementSibling) {
      var fs = this.readFeaturesFromNode(n, opt_options);
      if (fs) {
        ol.array.extend(features, fs);
      }
    }
    return features;
  } else {
    return [];
  }
};


/**
 * Read the name of the KML.
 *
 * @param {Document|Node|string} source Souce.
 * @return {string|undefined} Name.
 * @api stable
 */
ol.format.KML.prototype.readName = function(source) {
  if (ol.xml.isDocument(source)) {
    return this.readNameFromDocument(/** @type {Document} */ (source));
  } else if (ol.xml.isNode(source)) {
    return this.readNameFromNode(/** @type {Node} */ (source));
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readNameFromDocument(doc);
  } else {
    return undefined;
  }
};


/**
 * @param {Document} doc Document.
 * @return {string|undefined} Name.
 */
ol.format.KML.prototype.readNameFromDocument = function(doc) {
  var n;
  for (n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      var name = this.readNameFromNode(n);
      if (name) {
        return name;
      }
    }
  }
  return undefined;
};


/**
 * @param {Node} node Node.
 * @return {string|undefined} Name.
 */
ol.format.KML.prototype.readNameFromNode = function(node) {
  var n;
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
        n.localName == 'name') {
      return ol.format.XSD.readString(n);
    }
  }
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    var localName = n.localName;
    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
        (localName == 'Document' ||
         localName == 'Folder' ||
         localName == 'Placemark' ||
         localName == 'kml')) {
      var name = this.readNameFromNode(n);
      if (name) {
        return name;
      }
    }
  }
  return undefined;
};


/**
 * Read the network links of the KML.
 *
 * @param {Document|Node|string} source Source.
 * @return {Array.<Object>} Network links.
 * @api
 */
ol.format.KML.prototype.readNetworkLinks = function(source) {
  var networkLinks = [];
  if (ol.xml.isDocument(source)) {
    ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(
        /** @type {Document} */ (source)));
  } else if (ol.xml.isNode(source)) {
    ol.array.extend(networkLinks, this.readNetworkLinksFromNode(
        /** @type {Node} */ (source)));
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    ol.array.extend(networkLinks, this.readNetworkLinksFromDocument(doc));
  }
  return networkLinks;
};


/**
 * @param {Document} doc Document.
 * @return {Array.<Object>} Network links.
 */
ol.format.KML.prototype.readNetworkLinksFromDocument = function(doc) {
  var n, networkLinks = [];
  for (n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
    }
  }
  return networkLinks;
};


/**
 * @param {Node} node Node.
 * @return {Array.<Object>} Network links.
 */
ol.format.KML.prototype.readNetworkLinksFromNode = function(node) {
  var n, networkLinks = [];
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
        n.localName == 'NetworkLink') {
      var obj = ol.xml.pushParseAndPop({}, ol.format.KML.NETWORK_LINK_PARSERS_,
          n, []);
      networkLinks.push(obj);
    }
  }
  for (n = node.firstElementChild; n; n = n.nextElementSibling) {
    var localName = n.localName;
    if (ol.array.includes(ol.format.KML.NAMESPACE_URIS_, n.namespaceURI) &&
        (localName == 'Document' ||
         localName == 'Folder' ||
         localName == 'kml')) {
      ol.array.extend(networkLinks, this.readNetworkLinksFromNode(n));
    }
  }
  return networkLinks;
};


/**
 * Read the projection from a KML source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.KML.prototype.readProjection;


/**
 * @param {Node} node Node to append a TextNode with the color to.
 * @param {ol.Color|string} color Color.
 * @private
 */
ol.format.KML.writeColorTextNode_ = function(node, color) {
  var rgba = ol.color.asArray(color);
  var opacity = (rgba.length == 4) ? rgba[3] : 1;
  var abgr = [opacity * 255, rgba[2], rgba[1], rgba[0]];
  var i;
  for (i = 0; i < 4; ++i) {
    var hex = parseInt(abgr[i], 10).toString(16);
    abgr[i] = (hex.length == 1) ? '0' + hex : hex;
  }
  ol.format.XSD.writeStringTextNode(node, abgr.join(''));
};


/**
 * @param {Node} node Node to append a TextNode with the coordinates to.
 * @param {Array.<number>} coordinates Coordinates.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeCoordinatesTextNode_ = function(node, coordinates, objectStack) {
  var context = objectStack[objectStack.length - 1];

  var layout = context['layout'];
  var stride = context['stride'];

  var dimension;
  if (layout == ol.geom.GeometryLayout.XY ||
      layout == ol.geom.GeometryLayout.XYM) {
    dimension = 2;
  } else if (layout == ol.geom.GeometryLayout.XYZ ||
      layout == ol.geom.GeometryLayout.XYZM) {
    dimension = 3;
  } else {
    ol.asserts.assert(false, 34); // Invalid geometry layout
  }

  var d, i;
  var ii = coordinates.length;
  var text = '';
  if (ii > 0) {
    text += coordinates[0];
    for (d = 1; d < dimension; ++d) {
      text += ',' + coordinates[d];
    }
    for (i = stride; i < ii; i += stride) {
      text += ' ' + coordinates[i];
      for (d = 1; d < dimension; ++d) {
        text += ',' + coordinates[i + d];
      }
    }
  }
  ol.format.XSD.writeStringTextNode(node, text);
};


/**
 * @param {Node} node Node.
 * @param {Array.<ol.Feature>} features Features.
 * @param {Array.<*>} objectStack Object stack.
 * @this {ol.format.KML}
 * @private
 */
ol.format.KML.writeDocument_ = function(node, features, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  ol.xml.pushSerializeAndPop(context, ol.format.KML.DOCUMENT_SERIALIZERS_,
      ol.format.KML.DOCUMENT_NODE_FACTORY_, features, objectStack, undefined,
      this);
};


/**
 * @param {Node} node Node.
 * @param {Object} icon Icon object.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeIcon_ = function(node, icon, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.KML.ICON_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(icon, orderedKeys);
  ol.xml.pushSerializeAndPop(context,
      ol.format.KML.ICON_SERIALIZERS_, ol.xml.OBJECT_PROPERTY_NODE_FACTORY,
      values, objectStack, orderedKeys);
  orderedKeys =
      ol.format.KML.ICON_SEQUENCE_[ol.format.KML.GX_NAMESPACE_URIS_[0]];
  values = ol.xml.makeSequence(icon, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_SERIALIZERS_,
      ol.format.KML.GX_NODE_FACTORY_, values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.style.Icon} style Icon style.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeIconStyle_ = function(node, style, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  var properties = {};
  var src = style.getSrc();
  var size = style.getSize();
  var iconImageSize = style.getImageSize();
  var iconProperties = {
    'href': src
  };

  if (size) {
    iconProperties['w'] = size[0];
    iconProperties['h'] = size[1];
    var anchor = style.getAnchor(); // top-left
    var origin = style.getOrigin(); // top-left

    if (origin && iconImageSize && origin[0] !== 0 && origin[1] !== size[1]) {
      iconProperties['x'] = origin[0];
      iconProperties['y'] = iconImageSize[1] - (origin[1] + size[1]);
    }

    if (anchor && anchor[0] !== 0 && anchor[1] !== size[1]) {
      var /** @type {ol.KMLVec2_} */ hotSpot = {
        x: anchor[0],
        xunits: ol.style.IconAnchorUnits.PIXELS,
        y: size[1] - anchor[1],
        yunits: ol.style.IconAnchorUnits.PIXELS
      };
      properties['hotSpot'] = hotSpot;
    }
  }

  properties['Icon'] = iconProperties;

  var scale = style.getScale();
  if (scale !== 1) {
    properties['scale'] = scale;
  }

  var rotation = style.getRotation();
  if (rotation !== 0) {
    properties['heading'] = rotation; // 0-360
  }

  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.KML.ICON_STYLE_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.ICON_STYLE_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.style.Text} style style.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeLabelStyle_ = function(node, style, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  var properties = {};
  var fill = style.getFill();
  if (fill) {
    properties['color'] = fill.getColor();
  }
  var scale = style.getScale();
  if (scale && scale !== 1) {
    properties['scale'] = scale;
  }
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys =
      ol.format.KML.LABEL_STYLE_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.LABEL_STYLE_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.style.Stroke} style style.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeLineStyle_ = function(node, style, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  var properties = {
    'color': style.getColor(),
    'width': style.getWidth()
  };
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.KML.LINE_STYLE_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.LINE_STYLE_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeMultiGeometry_ = function(node, geometry, objectStack) {
  /** @type {ol.XmlNodeStackItem} */
  var context = {node: node};
  var type = geometry.getType();
  /** @type {Array.<ol.geom.Geometry>} */
  var geometries;
  /** @type {function(*, Array.<*>, string=): (Node|undefined)} */
  var factory;
  if (type == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
    geometries = /** @type {ol.geom.GeometryCollection} */ (geometry).getGeometries();
    factory = ol.format.KML.GEOMETRY_NODE_FACTORY_;
  } else if (type == ol.geom.GeometryType.MULTI_POINT) {
    geometries = /** @type {ol.geom.MultiPoint} */ (geometry).getPoints();
    factory = ol.format.KML.POINT_NODE_FACTORY_;
  } else if (type == ol.geom.GeometryType.MULTI_LINE_STRING) {
    geometries =
        (/** @type {ol.geom.MultiLineString} */ (geometry)).getLineStrings();
    factory = ol.format.KML.LINE_STRING_NODE_FACTORY_;
  } else if (type == ol.geom.GeometryType.MULTI_POLYGON) {
    geometries =
        (/** @type {ol.geom.MultiPolygon} */ (geometry)).getPolygons();
    factory = ol.format.KML.POLYGON_NODE_FACTORY_;
  } else {
    ol.asserts.assert(false, 39); // Unknown geometry type
  }
  ol.xml.pushSerializeAndPop(context,
      ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_, factory,
      geometries, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.LinearRing} linearRing Linear ring.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeBoundaryIs_ = function(node, linearRing, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  ol.xml.pushSerializeAndPop(context,
      ol.format.KML.BOUNDARY_IS_SERIALIZERS_,
      ol.format.KML.LINEAR_RING_NODE_FACTORY_, [linearRing], objectStack);
};


/**
 * FIXME currently we do serialize arbitrary/custom feature properties
 * (ExtendedData).
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Object stack.
 * @this {ol.format.KML}
 * @private
 */
ol.format.KML.writePlacemark_ = function(node, feature, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};

  // set id
  if (feature.getId()) {
    node.setAttribute('id', feature.getId());
  }

  // serialize properties (properties unknown to KML are not serialized)
  var properties = feature.getProperties();

  var styleFunction = feature.getStyleFunction();
  if (styleFunction) {
    // FIXME the styles returned by the style function are supposed to be
    // resolution-independent here
    var styles = styleFunction.call(feature, 0);
    if (styles) {
      var style = Array.isArray(styles) ? styles[0] : styles;
      if (this.writeStyles_) {
        properties['Style'] = style;
      }
      var textStyle = style.getText();
      if (textStyle) {
        properties['name'] = textStyle.getText();
      }
    }
  }
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.KML.PLACEMARK_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);

  // serialize geometry
  var options = /** @type {olx.format.WriteOptions} */ (objectStack[0]);
  var geometry = feature.getGeometry();
  if (geometry) {
    geometry =
        ol.format.Feature.transformWithOptions(geometry, true, options);
  }
  ol.xml.pushSerializeAndPop(context, ol.format.KML.PLACEMARK_SERIALIZERS_,
      ol.format.KML.GEOMETRY_NODE_FACTORY_, [geometry], objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.SimpleGeometry} geometry Geometry.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writePrimitiveGeometry_ = function(node, geometry, objectStack) {
  goog.DEBUG && console.assert(
      (geometry instanceof ol.geom.Point) ||
      (geometry instanceof ol.geom.LineString) ||
      (geometry instanceof ol.geom.LinearRing),
      'geometry should be one of ol.geom.Point, ol.geom.LineString ' +
      'or ol.geom.LinearRing');
  var flatCoordinates = geometry.getFlatCoordinates();
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  context['layout'] = geometry.getLayout();
  context['stride'] = geometry.getStride();
  ol.xml.pushSerializeAndPop(context,
      ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_,
      ol.format.KML.COORDINATES_NODE_FACTORY_,
      [flatCoordinates], objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.geom.Polygon} polygon Polygon.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writePolygon_ = function(node, polygon, objectStack) {
  var linearRings = polygon.getLinearRings();
  goog.DEBUG && console.assert(linearRings.length > 0,
      'linearRings should not be empty');
  var outerRing = linearRings.shift();
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  // inner rings
  ol.xml.pushSerializeAndPop(context,
      ol.format.KML.POLYGON_SERIALIZERS_,
      ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_,
      linearRings, objectStack);
  // outer ring
  ol.xml.pushSerializeAndPop(context,
      ol.format.KML.POLYGON_SERIALIZERS_,
      ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_,
      [outerRing], objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.style.Fill} style Style.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writePolyStyle_ = function(node, style, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  ol.xml.pushSerializeAndPop(context, ol.format.KML.POLY_STYLE_SERIALIZERS_,
      ol.format.KML.COLOR_NODE_FACTORY_, [style.getColor()], objectStack);
};


/**
 * @param {Node} node Node to append a TextNode with the scale to.
 * @param {number|undefined} scale Scale.
 * @private
 */
ol.format.KML.writeScaleTextNode_ = function(node, scale) {
  // the Math is to remove any excess decimals created by float arithmetic
  ol.format.XSD.writeDecimalTextNode(node,
      Math.round(scale * scale * 1e6) / 1e6);
};


/**
 * @param {Node} node Node.
 * @param {ol.style.Style} style Style.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.KML.writeStyle_ = function(node, style, objectStack) {
  var /** @type {ol.XmlNodeStackItem} */ context = {node: node};
  var properties = {};
  var fillStyle = style.getFill();
  var strokeStyle = style.getStroke();
  var imageStyle = style.getImage();
  var textStyle = style.getText();
  if (imageStyle instanceof ol.style.Icon) {
    properties['IconStyle'] = imageStyle;
  }
  if (textStyle) {
    properties['LabelStyle'] = textStyle;
  }
  if (strokeStyle) {
    properties['LineStyle'] = strokeStyle;
  }
  if (fillStyle) {
    properties['PolyStyle'] = fillStyle;
  }
  var parentNode = objectStack[objectStack.length - 1].node;
  var orderedKeys = ol.format.KML.STYLE_SEQUENCE_[parentNode.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.STYLE_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
};


/**
 * @param {Node} node Node to append a TextNode with the Vec2 to.
 * @param {ol.KMLVec2_} vec2 Vec2.
 * @private
 */
ol.format.KML.writeVec2_ = function(node, vec2) {
  node.setAttribute('x', vec2.x);
  node.setAttribute('y', vec2.y);
  node.setAttribute('xunits', vec2.xunits);
  node.setAttribute('yunits', vec2.yunits);
};


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.KML_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'Document', 'Placemark'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.KML_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Document': ol.xml.makeChildAppender(ol.format.KML.writeDocument_),
      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.DOCUMENT_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Placemark': ol.xml.makeChildAppender(ol.format.KML.writePlacemark_)
    });


/**
 * @const
 * @type {Object.<string, string>}
 * @private
 */
ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_ = {
  'Point': 'Point',
  'LineString': 'LineString',
  'LinearRing': 'LinearRing',
  'Polygon': 'Polygon',
  'MultiPoint': 'MultiGeometry',
  'MultiLineString': 'MultiGeometry',
  'MultiPolygon': 'MultiGeometry',
  'GeometryCollection': 'MultiGeometry'
};


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.ICON_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'href'
    ],
    ol.xml.makeStructureNS(ol.format.KML.GX_NAMESPACE_URIS_, [
      'x', 'y', 'w', 'h'
    ]));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.ICON_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'href': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
    }, ol.xml.makeStructureNS(
        ol.format.KML.GX_NAMESPACE_URIS_, {
          'x': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
          'y': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
          'w': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
          'h': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
        }));


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.ICON_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'scale', 'heading', 'Icon', 'hotSpot'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.ICON_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'Icon': ol.xml.makeChildAppender(ol.format.KML.writeIcon_),
      'heading': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode),
      'hotSpot': ol.xml.makeChildAppender(ol.format.KML.writeVec2_),
      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.LABEL_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'color', 'scale'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.LABEL_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
      'scale': ol.xml.makeChildAppender(ol.format.KML.writeScaleTextNode_)
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.LINE_STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'color', 'width'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.LINE_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_),
      'width': ol.xml.makeChildAppender(ol.format.XSD.writeDecimalTextNode)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.BOUNDARY_IS_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'LinearRing': ol.xml.makeChildAppender(
          ol.format.KML.writePrimitiveGeometry_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.MULTI_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'LineString': ol.xml.makeChildAppender(
          ol.format.KML.writePrimitiveGeometry_),
      'Point': ol.xml.makeChildAppender(
          ol.format.KML.writePrimitiveGeometry_),
      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_),
      'GeometryCollection': ol.xml.makeChildAppender(
          ol.format.KML.writeMultiGeometry_)
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.PLACEMARK_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'name', 'open', 'visibility', 'address', 'phoneNumber', 'description',
      'styleUrl', 'Style'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.PLACEMARK_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'MultiGeometry': ol.xml.makeChildAppender(
          ol.format.KML.writeMultiGeometry_),
      'LineString': ol.xml.makeChildAppender(
          ol.format.KML.writePrimitiveGeometry_),
      'LinearRing': ol.xml.makeChildAppender(
          ol.format.KML.writePrimitiveGeometry_),
      'Point': ol.xml.makeChildAppender(
          ol.format.KML.writePrimitiveGeometry_),
      'Polygon': ol.xml.makeChildAppender(ol.format.KML.writePolygon_),
      'Style': ol.xml.makeChildAppender(ol.format.KML.writeStyle_),
      'address': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'description': ol.xml.makeChildAppender(
          ol.format.XSD.writeStringTextNode),
      'name': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'open': ol.xml.makeChildAppender(ol.format.XSD.writeBooleanTextNode),
      'phoneNumber': ol.xml.makeChildAppender(
          ol.format.XSD.writeStringTextNode),
      'styleUrl': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode),
      'visibility': ol.xml.makeChildAppender(
          ol.format.XSD.writeBooleanTextNode)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.PRIMITIVE_GEOMETRY_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'coordinates': ol.xml.makeChildAppender(
          ol.format.KML.writeCoordinatesTextNode_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.POLYGON_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'outerBoundaryIs': ol.xml.makeChildAppender(
          ol.format.KML.writeBoundaryIs_),
      'innerBoundaryIs': ol.xml.makeChildAppender(
          ol.format.KML.writeBoundaryIs_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.POLY_STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'color': ol.xml.makeChildAppender(ol.format.KML.writeColorTextNode_)
    });


/**
 * @const
 * @type {Object.<string, Array.<string>>}
 * @private
 */
ol.format.KML.STYLE_SEQUENCE_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, [
      'IconStyle', 'LabelStyle', 'LineStyle', 'PolyStyle'
    ]);


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.KML.STYLE_SERIALIZERS_ = ol.xml.makeStructureNS(
    ol.format.KML.NAMESPACE_URIS_, {
      'IconStyle': ol.xml.makeChildAppender(ol.format.KML.writeIconStyle_),
      'LabelStyle': ol.xml.makeChildAppender(ol.format.KML.writeLabelStyle_),
      'LineStyle': ol.xml.makeChildAppender(ol.format.KML.writeLineStyle_),
      'PolyStyle': ol.xml.makeChildAppender(ol.format.KML.writePolyStyle_)
    });


/**
 * @const
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node|undefined} Node.
 * @private
 */
ol.format.KML.GX_NODE_FACTORY_ = function(value, objectStack, opt_nodeName) {
  return ol.xml.createElementNS(ol.format.KML.GX_NAMESPACE_URIS_[0],
      'gx:' + opt_nodeName);
};


/**
 * @const
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node|undefined} Node.
 * @private
 */
ol.format.KML.DOCUMENT_NODE_FACTORY_ = function(value, objectStack,
    opt_nodeName) {
  var parentNode = objectStack[objectStack.length - 1].node;
  goog.DEBUG && console.assert(ol.xml.isNode(parentNode),
      'parentNode should be an XML node');
  return ol.xml.createElementNS(parentNode.namespaceURI, 'Placemark');
};


/**
 * @const
 * @param {*} value Value.
 * @param {Array.<*>} objectStack Object stack.
 * @param {string=} opt_nodeName Node name.
 * @return {Node|undefined} Node.
 * @private
 */
ol.format.KML.GEOMETRY_NODE_FACTORY_ = function(value, objectStack,
    opt_nodeName) {
  if (value) {
    var parentNode = objectStack[objectStack.length - 1].node;
    goog.DEBUG && console.assert(ol.xml.isNode(parentNode),
        'parentNode should be an XML node');
    return ol.xml.createElementNS(parentNode.namespaceURI,
        ol.format.KML.GEOMETRY_TYPE_TO_NODENAME_[/** @type {ol.geom.Geometry} */ (value).getType()]);
  }
};


/**
 * A factory for creating coordinates nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.COLOR_NODE_FACTORY_ = ol.xml.makeSimpleNodeFactory('color');


/**
 * A factory for creating coordinates nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.COORDINATES_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('coordinates');


/**
 * A factory for creating innerBoundaryIs nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.INNER_BOUNDARY_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('innerBoundaryIs');


/**
 * A factory for creating Point nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.POINT_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('Point');


/**
 * A factory for creating LineString nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.LINE_STRING_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('LineString');


/**
 * A factory for creating LinearRing nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.LINEAR_RING_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('LinearRing');


/**
 * A factory for creating Polygon nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.POLYGON_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('Polygon');


/**
 * A factory for creating outerBoundaryIs nodes.
 * @const
 * @type {function(*, Array.<*>, string=): (Node|undefined)}
 * @private
 */
ol.format.KML.OUTER_BOUNDARY_NODE_FACTORY_ =
    ol.xml.makeSimpleNodeFactory('outerBoundaryIs');


/**
 * Encode an array of features in the KML format. GeometryCollections, MultiPoints,
 * MultiLineStrings, and MultiPolygons are output as MultiGeometries.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {string} Result.
 * @api stable
 */
ol.format.KML.prototype.writeFeatures;


/**
 * Encode an array of features in the KML format as an XML node. GeometryCollections,
 * MultiPoints, MultiLineStrings, and MultiPolygons are output as MultiGeometries.
 *
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Options.
 * @return {Node} Node.
 * @api
 */
ol.format.KML.prototype.writeFeaturesNode = function(features, opt_options) {
  opt_options = this.adaptOptions(opt_options);
  var kml = ol.xml.createElementNS(ol.format.KML.NAMESPACE_URIS_[4], 'kml');
  var xmlnsUri = 'http://www.w3.org/2000/xmlns/';
  var xmlSchemaInstanceUri = 'http://www.w3.org/2001/XMLSchema-instance';
  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:gx',
      ol.format.KML.GX_NAMESPACE_URIS_[0]);
  ol.xml.setAttributeNS(kml, xmlnsUri, 'xmlns:xsi', xmlSchemaInstanceUri);
  ol.xml.setAttributeNS(kml, xmlSchemaInstanceUri, 'xsi:schemaLocation',
      ol.format.KML.SCHEMA_LOCATION_);

  var /** @type {ol.XmlNodeStackItem} */ context = {node: kml};
  var properties = {};
  if (features.length > 1) {
    properties['Document'] = features;
  } else if (features.length == 1) {
    properties['Placemark'] = features[0];
  }
  var orderedKeys = ol.format.KML.KML_SEQUENCE_[kml.namespaceURI];
  var values = ol.xml.makeSequence(properties, orderedKeys);
  ol.xml.pushSerializeAndPop(context, ol.format.KML.KML_SERIALIZERS_,
      ol.xml.OBJECT_PROPERTY_NODE_FACTORY, values, [opt_options], orderedKeys,
      this);
  return kml;
};

goog.provide('ol.ext.pbf');
/** @typedef {function(*)} */
ol.ext.pbf;
(function() {
var exports = {};
var module = {exports: exports};
var define;
/**
 * @fileoverview
 * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
 */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pbf = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
exports.read = function (buffer, offset, isLE, mLen, nBytes) {
  var e, m
  var eLen = nBytes * 8 - mLen - 1
  var eMax = (1 << eLen) - 1
  var eBias = eMax >> 1
  var nBits = -7
  var i = isLE ? (nBytes - 1) : 0
  var d = isLE ? -1 : 1
  var s = buffer[offset + i]

  i += d

  e = s & ((1 << (-nBits)) - 1)
  s >>= (-nBits)
  nBits += eLen
  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}

  m = e & ((1 << (-nBits)) - 1)
  e >>= (-nBits)
  nBits += mLen
  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}

  if (e === 0) {
    e = 1 - eBias
  } else if (e === eMax) {
    return m ? NaN : ((s ? -1 : 1) * Infinity)
  } else {
    m = m + Math.pow(2, mLen)
    e = e - eBias
  }
  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
}

exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
  var e, m, c
  var eLen = nBytes * 8 - mLen - 1
  var eMax = (1 << eLen) - 1
  var eBias = eMax >> 1
  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
  var i = isLE ? 0 : (nBytes - 1)
  var d = isLE ? 1 : -1
  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0

  value = Math.abs(value)

  if (isNaN(value) || value === Infinity) {
    m = isNaN(value) ? 1 : 0
    e = eMax
  } else {
    e = Math.floor(Math.log(value) / Math.LN2)
    if (value * (c = Math.pow(2, -e)) < 1) {
      e--
      c *= 2
    }
    if (e + eBias >= 1) {
      value += rt / c
    } else {
      value += rt * Math.pow(2, 1 - eBias)
    }
    if (value * c >= 2) {
      e++
      c /= 2
    }

    if (e + eBias >= eMax) {
      m = 0
      e = eMax
    } else if (e + eBias >= 1) {
      m = (value * c - 1) * Math.pow(2, mLen)
      e = e + eBias
    } else {
      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
      e = 0
    }
  }

  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}

  e = (e << mLen) | m
  eLen += mLen
  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}

  buffer[offset + i - d] |= s * 128
}

},{}],2:[function(_dereq_,module,exports){
'use strict';

// lightweight Buffer shim for pbf browser build
// based on code from github.com/feross/buffer (MIT-licensed)

module.exports = Buffer;

var ieee754 = _dereq_('ieee754');

var BufferMethods;

function Buffer(length) {
    var arr;
    if (length && length.length) {
        arr = length;
        length = arr.length;
    }
    var buf = new Uint8Array(length || 0);
    if (arr) buf.set(arr);

    buf.readUInt32LE = BufferMethods.readUInt32LE;
    buf.writeUInt32LE = BufferMethods.writeUInt32LE;
    buf.readInt32LE = BufferMethods.readInt32LE;
    buf.writeInt32LE = BufferMethods.writeInt32LE;
    buf.readFloatLE = BufferMethods.readFloatLE;
    buf.writeFloatLE = BufferMethods.writeFloatLE;
    buf.readDoubleLE = BufferMethods.readDoubleLE;
    buf.writeDoubleLE = BufferMethods.writeDoubleLE;
    buf.toString = BufferMethods.toString;
    buf.write = BufferMethods.write;
    buf.slice = BufferMethods.slice;
    buf.copy = BufferMethods.copy;

    buf._isBuffer = true;
    return buf;
}

var lastStr, lastStrEncoded;

BufferMethods = {
    readUInt32LE: function(pos) {
        return ((this[pos]) |
            (this[pos + 1] << 8) |
            (this[pos + 2] << 16)) +
            (this[pos + 3] * 0x1000000);
    },

    writeUInt32LE: function(val, pos) {
        this[pos] = val;
        this[pos + 1] = (val >>> 8);
        this[pos + 2] = (val >>> 16);
        this[pos + 3] = (val >>> 24);
    },

    readInt32LE: function(pos) {
        return ((this[pos]) |
            (this[pos + 1] << 8) |
            (this[pos + 2] << 16)) +
            (this[pos + 3] << 24);
    },

    readFloatLE:  function(pos) { return ieee754.read(this, pos, true, 23, 4); },
    readDoubleLE: function(pos) { return ieee754.read(this, pos, true, 52, 8); },

    writeFloatLE:  function(val, pos) { return ieee754.write(this, val, pos, true, 23, 4); },
    writeDoubleLE: function(val, pos) { return ieee754.write(this, val, pos, true, 52, 8); },

    toString: function(encoding, start, end) {
        var str = '',
            tmp = '';

        start = start || 0;
        end = Math.min(this.length, end || this.length);

        for (var i = start; i < end; i++) {
            var ch = this[i];
            if (ch <= 0x7F) {
                str += decodeURIComponent(tmp) + String.fromCharCode(ch);
                tmp = '';
            } else {
                tmp += '%' + ch.toString(16);
            }
        }

        str += decodeURIComponent(tmp);

        return str;
    },

    write: function(str, pos) {
        var bytes = str === lastStr ? lastStrEncoded : encodeString(str);
        for (var i = 0; i < bytes.length; i++) {
            this[pos + i] = bytes[i];
        }
    },

    slice: function(start, end) {
        return this.subarray(start, end);
    },

    copy: function(buf, pos) {
        pos = pos || 0;
        for (var i = 0; i < this.length; i++) {
            buf[pos + i] = this[i];
        }
    }
};

BufferMethods.writeInt32LE = BufferMethods.writeUInt32LE;

Buffer.byteLength = function(str) {
    lastStr = str;
    lastStrEncoded = encodeString(str);
    return lastStrEncoded.length;
};

Buffer.isBuffer = function(buf) {
    return !!(buf && buf._isBuffer);
};

function encodeString(str) {
    var length = str.length,
        bytes = [];

    for (var i = 0, c, lead; i < length; i++) {
        c = str.charCodeAt(i); // code point

        if (c > 0xD7FF && c < 0xE000) {

            if (lead) {
                if (c < 0xDC00) {
                    bytes.push(0xEF, 0xBF, 0xBD);
                    lead = c;
                    continue;

                } else {
                    c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
                    lead = null;
                }

            } else {
                if (c > 0xDBFF || (i + 1 === length)) bytes.push(0xEF, 0xBF, 0xBD);
                else lead = c;

                continue;
            }

        } else if (lead) {
            bytes.push(0xEF, 0xBF, 0xBD);
            lead = null;
        }

        if (c < 0x80) bytes.push(c);
        else if (c < 0x800) bytes.push(c >> 0x6 | 0xC0, c & 0x3F | 0x80);
        else if (c < 0x10000) bytes.push(c >> 0xC | 0xE0, c >> 0x6 & 0x3F | 0x80, c & 0x3F | 0x80);
        else bytes.push(c >> 0x12 | 0xF0, c >> 0xC & 0x3F | 0x80, c >> 0x6 & 0x3F | 0x80, c & 0x3F | 0x80);
    }
    return bytes;
}

},{"ieee754":1}],3:[function(_dereq_,module,exports){
(function (global){
'use strict';

module.exports = Pbf;

var Buffer = global.Buffer || _dereq_('./buffer');

function Pbf(buf) {
    this.buf = !Buffer.isBuffer(buf) ? new Buffer(buf || 0) : buf;
    this.pos = 0;
    this.length = this.buf.length;
}

Pbf.Varint  = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
Pbf.Bytes   = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32

var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
    SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32,
    POW_2_63 = Math.pow(2, 63);

Pbf.prototype = {

    destroy: function() {
        this.buf = null;
    },

    // === READING =================================================================

    readFields: function(readField, result, end) {
        end = end || this.length;

        while (this.pos < end) {
            var val = this.readVarint(),
                tag = val >> 3,
                startPos = this.pos;

            readField(tag, result, this);

            if (this.pos === startPos) this.skip(val);
        }
        return result;
    },

    readMessage: function(readField, result) {
        return this.readFields(readField, result, this.readVarint() + this.pos);
    },

    readFixed32: function() {
        var val = this.buf.readUInt32LE(this.pos);
        this.pos += 4;
        return val;
    },

    readSFixed32: function() {
        var val = this.buf.readInt32LE(this.pos);
        this.pos += 4;
        return val;
    },

    // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)

    readFixed64: function() {
        var val = this.buf.readUInt32LE(this.pos) + this.buf.readUInt32LE(this.pos + 4) * SHIFT_LEFT_32;
        this.pos += 8;
        return val;
    },

    readSFixed64: function() {
        var val = this.buf.readUInt32LE(this.pos) + this.buf.readInt32LE(this.pos + 4) * SHIFT_LEFT_32;
        this.pos += 8;
        return val;
    },

    readFloat: function() {
        var val = this.buf.readFloatLE(this.pos);
        this.pos += 4;
        return val;
    },

    readDouble: function() {
        var val = this.buf.readDoubleLE(this.pos);
        this.pos += 8;
        return val;
    },

    readVarint: function() {
        var buf = this.buf,
            val, b;

        b = buf[this.pos++]; val  =  b & 0x7f;        if (b < 0x80) return val;
        b = buf[this.pos++]; val |= (b & 0x7f) << 7;  if (b < 0x80) return val;
        b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val;
        b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val;

        return readVarintRemainder(val, this);
    },

    readVarint64: function() {
        var startPos = this.pos,
            val = this.readVarint();

        if (val < POW_2_63) return val;

        var pos = this.pos - 2;
        while (this.buf[pos] === 0xff) pos--;
        if (pos < startPos) pos = startPos;

        val = 0;
        for (var i = 0; i < pos - startPos + 1; i++) {
            var b = ~this.buf[startPos + i] & 0x7f;
            val += i < 4 ? b << i * 7 : b * Math.pow(2, i * 7);
        }

        return -val - 1;
    },

    readSVarint: function() {
        var num = this.readVarint();
        return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
    },

    readBoolean: function() {
        return Boolean(this.readVarint());
    },

    readString: function() {
        var end = this.readVarint() + this.pos,
            str = this.buf.toString('utf8', this.pos, end);
        this.pos = end;
        return str;
    },

    readBytes: function() {
        var end = this.readVarint() + this.pos,
            buffer = this.buf.slice(this.pos, end);
        this.pos = end;
        return buffer;
    },

    // verbose for performance reasons; doesn't affect gzipped size

    readPackedVarint: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readVarint());
        return arr;
    },
    readPackedSVarint: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readSVarint());
        return arr;
    },
    readPackedBoolean: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readBoolean());
        return arr;
    },
    readPackedFloat: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readFloat());
        return arr;
    },
    readPackedDouble: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readDouble());
        return arr;
    },
    readPackedFixed32: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readFixed32());
        return arr;
    },
    readPackedSFixed32: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readSFixed32());
        return arr;
    },
    readPackedFixed64: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readFixed64());
        return arr;
    },
    readPackedSFixed64: function() {
        var end = this.readVarint() + this.pos, arr = [];
        while (this.pos < end) arr.push(this.readSFixed64());
        return arr;
    },

    skip: function(val) {
        var type = val & 0x7;
        if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {}
        else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;
        else if (type === Pbf.Fixed32) this.pos += 4;
        else if (type === Pbf.Fixed64) this.pos += 8;
        else throw new Error('Unimplemented type: ' + type);
    },

    // === WRITING =================================================================

    writeTag: function(tag, type) {
        this.writeVarint((tag << 3) | type);
    },

    realloc: function(min) {
        var length = this.length || 16;

        while (length < this.pos + min) length *= 2;

        if (length !== this.length) {
            var buf = new Buffer(length);
            this.buf.copy(buf);
            this.buf = buf;
            this.length = length;
        }
    },

    finish: function() {
        this.length = this.pos;
        this.pos = 0;
        return this.buf.slice(0, this.length);
    },

    writeFixed32: function(val) {
        this.realloc(4);
        this.buf.writeUInt32LE(val, this.pos);
        this.pos += 4;
    },

    writeSFixed32: function(val) {
        this.realloc(4);
        this.buf.writeInt32LE(val, this.pos);
        this.pos += 4;
    },

    writeFixed64: function(val) {
        this.realloc(8);
        this.buf.writeInt32LE(val & -1, this.pos);
        this.buf.writeUInt32LE(Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
        this.pos += 8;
    },

    writeSFixed64: function(val) {
        this.realloc(8);
        this.buf.writeInt32LE(val & -1, this.pos);
        this.buf.writeInt32LE(Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
        this.pos += 8;
    },

    writeVarint: function(val) {
        val = +val;

        if (val > 0xfffffff) {
            writeBigVarint(val, this);
            return;
        }

        this.realloc(4);

        this.buf[this.pos++] =           val & 0x7f  | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
        this.buf[this.pos++] =   (val >>> 7) & 0x7f;
    },

    writeSVarint: function(val) {
        this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
    },

    writeBoolean: function(val) {
        this.writeVarint(Boolean(val));
    },

    writeString: function(str) {
        str = String(str);
        var bytes = Buffer.byteLength(str);
        this.writeVarint(bytes);
        this.realloc(bytes);
        this.buf.write(str, this.pos);
        this.pos += bytes;
    },

    writeFloat: function(val) {
        this.realloc(4);
        this.buf.writeFloatLE(val, this.pos);
        this.pos += 4;
    },

    writeDouble: function(val) {
        this.realloc(8);
        this.buf.writeDoubleLE(val, this.pos);
        this.pos += 8;
    },

    writeBytes: function(buffer) {
        var len = buffer.length;
        this.writeVarint(len);
        this.realloc(len);
        for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
    },

    writeRawMessage: function(fn, obj) {
        this.pos++; // reserve 1 byte for short message length

        // write the message directly to the buffer and see how much was written
        var startPos = this.pos;
        fn(obj, this);
        var len = this.pos - startPos;

        if (len >= 0x80) reallocForRawMessage(startPos, len, this);

        // finally, write the message length in the reserved place and restore the position
        this.pos = startPos - 1;
        this.writeVarint(len);
        this.pos += len;
    },

    writeMessage: function(tag, fn, obj) {
        this.writeTag(tag, Pbf.Bytes);
        this.writeRawMessage(fn, obj);
    },

    writePackedVarint:   function(tag, arr) { this.writeMessage(tag, writePackedVarint, arr);   },
    writePackedSVarint:  function(tag, arr) { this.writeMessage(tag, writePackedSVarint, arr);  },
    writePackedBoolean:  function(tag, arr) { this.writeMessage(tag, writePackedBoolean, arr);  },
    writePackedFloat:    function(tag, arr) { this.writeMessage(tag, writePackedFloat, arr);    },
    writePackedDouble:   function(tag, arr) { this.writeMessage(tag, writePackedDouble, arr);   },
    writePackedFixed32:  function(tag, arr) { this.writeMessage(tag, writePackedFixed32, arr);  },
    writePackedSFixed32: function(tag, arr) { this.writeMessage(tag, writePackedSFixed32, arr); },
    writePackedFixed64:  function(tag, arr) { this.writeMessage(tag, writePackedFixed64, arr);  },
    writePackedSFixed64: function(tag, arr) { this.writeMessage(tag, writePackedSFixed64, arr); },

    writeBytesField: function(tag, buffer) {
        this.writeTag(tag, Pbf.Bytes);
        this.writeBytes(buffer);
    },
    writeFixed32Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed32);
        this.writeFixed32(val);
    },
    writeSFixed32Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed32);
        this.writeSFixed32(val);
    },
    writeFixed64Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed64);
        this.writeFixed64(val);
    },
    writeSFixed64Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed64);
        this.writeSFixed64(val);
    },
    writeVarintField: function(tag, val) {
        this.writeTag(tag, Pbf.Varint);
        this.writeVarint(val);
    },
    writeSVarintField: function(tag, val) {
        this.writeTag(tag, Pbf.Varint);
        this.writeSVarint(val);
    },
    writeStringField: function(tag, str) {
        this.writeTag(tag, Pbf.Bytes);
        this.writeString(str);
    },
    writeFloatField: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed32);
        this.writeFloat(val);
    },
    writeDoubleField: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed64);
        this.writeDouble(val);
    },
    writeBooleanField: function(tag, val) {
        this.writeVarintField(tag, Boolean(val));
    }
};

function readVarintRemainder(val, pbf) {
    var buf = pbf.buf, b;

    b = buf[pbf.pos++]; val += (b & 0x7f) * 0x10000000;         if (b < 0x80) return val;
    b = buf[pbf.pos++]; val += (b & 0x7f) * 0x800000000;        if (b < 0x80) return val;
    b = buf[pbf.pos++]; val += (b & 0x7f) * 0x40000000000;      if (b < 0x80) return val;
    b = buf[pbf.pos++]; val += (b & 0x7f) * 0x2000000000000;    if (b < 0x80) return val;
    b = buf[pbf.pos++]; val += (b & 0x7f) * 0x100000000000000;  if (b < 0x80) return val;
    b = buf[pbf.pos++]; val += (b & 0x7f) * 0x8000000000000000; if (b < 0x80) return val;

    throw new Error('Expected varint not more than 10 bytes');
}

function writeBigVarint(val, pbf) {
    pbf.realloc(10);

    var maxPos = pbf.pos + 10;

    while (val >= 1) {
        if (pbf.pos >= maxPos) throw new Error('Given varint doesn\'t fit into 10 bytes');
        var b = val & 0xff;
        pbf.buf[pbf.pos++] = b | (val >= 0x80 ? 0x80 : 0);
        val /= 0x80;
    }
}

function reallocForRawMessage(startPos, len, pbf) {
    var extraLen =
        len <= 0x3fff ? 1 :
        len <= 0x1fffff ? 2 :
        len <= 0xfffffff ? 3 : Math.ceil(Math.log(len) / (Math.LN2 * 7));

    // if 1 byte isn't enough for encoding message length, shift the data to the right
    pbf.realloc(extraLen);
    for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
}

function writePackedVarint(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]);   }
function writePackedSVarint(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]);  }
function writePackedFloat(arr, pbf)    { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]);    }
function writePackedDouble(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]);   }
function writePackedBoolean(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]);  }
function writePackedFixed32(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]);  }
function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); }
function writePackedFixed64(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]);  }
function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); }

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./buffer":2}]},{},[3])(3)
});
ol.ext.pbf = module.exports;
})();

goog.provide('ol.ext.vectortile');
/** @typedef {function(*)} */
ol.ext.vectortile;
(function() {
var exports = {};
var module = {exports: exports};
var define;
/**
 * @fileoverview
 * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
 */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vectortile = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
'use strict';

module.exports = Point;

function Point(x, y) {
    this.x = x;
    this.y = y;
}

Point.prototype = {
    clone: function() { return new Point(this.x, this.y); },

    add:     function(p) { return this.clone()._add(p);     },
    sub:     function(p) { return this.clone()._sub(p);     },
    mult:    function(k) { return this.clone()._mult(k);    },
    div:     function(k) { return this.clone()._div(k);     },
    rotate:  function(a) { return this.clone()._rotate(a);  },
    matMult: function(m) { return this.clone()._matMult(m); },
    unit:    function() { return this.clone()._unit(); },
    perp:    function() { return this.clone()._perp(); },
    round:   function() { return this.clone()._round(); },

    mag: function() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    },

    equals: function(p) {
        return this.x === p.x &&
               this.y === p.y;
    },

    dist: function(p) {
        return Math.sqrt(this.distSqr(p));
    },

    distSqr: function(p) {
        var dx = p.x - this.x,
            dy = p.y - this.y;
        return dx * dx + dy * dy;
    },

    angle: function() {
        return Math.atan2(this.y, this.x);
    },

    angleTo: function(b) {
        return Math.atan2(this.y - b.y, this.x - b.x);
    },

    angleWith: function(b) {
        return this.angleWithSep(b.x, b.y);
    },

    // Find the angle of the two vectors, solving the formula for the cross product a x b = |a||b|sin(θ) for θ.
    angleWithSep: function(x, y) {
        return Math.atan2(
            this.x * y - this.y * x,
            this.x * x + this.y * y);
    },

    _matMult: function(m) {
        var x = m[0] * this.x + m[1] * this.y,
            y = m[2] * this.x + m[3] * this.y;
        this.x = x;
        this.y = y;
        return this;
    },

    _add: function(p) {
        this.x += p.x;
        this.y += p.y;
        return this;
    },

    _sub: function(p) {
        this.x -= p.x;
        this.y -= p.y;
        return this;
    },

    _mult: function(k) {
        this.x *= k;
        this.y *= k;
        return this;
    },

    _div: function(k) {
        this.x /= k;
        this.y /= k;
        return this;
    },

    _unit: function() {
        this._div(this.mag());
        return this;
    },

    _perp: function() {
        var y = this.y;
        this.y = this.x;
        this.x = -y;
        return this;
    },

    _rotate: function(angle) {
        var cos = Math.cos(angle),
            sin = Math.sin(angle),
            x = cos * this.x - sin * this.y,
            y = sin * this.x + cos * this.y;
        this.x = x;
        this.y = y;
        return this;
    },

    _round: function() {
        this.x = Math.round(this.x);
        this.y = Math.round(this.y);
        return this;
    }
};

// constructs Point from an array if necessary
Point.convert = function (a) {
    if (a instanceof Point) {
        return a;
    }
    if (Array.isArray(a)) {
        return new Point(a[0], a[1]);
    }
    return a;
};

},{}],2:[function(_dereq_,module,exports){
module.exports.VectorTile = _dereq_('./lib/vectortile.js');
module.exports.VectorTileFeature = _dereq_('./lib/vectortilefeature.js');
module.exports.VectorTileLayer = _dereq_('./lib/vectortilelayer.js');

},{"./lib/vectortile.js":3,"./lib/vectortilefeature.js":4,"./lib/vectortilelayer.js":5}],3:[function(_dereq_,module,exports){
'use strict';

var VectorTileLayer = _dereq_('./vectortilelayer');

module.exports = VectorTile;

function VectorTile(pbf, end) {
    this.layers = pbf.readFields(readTile, {}, end);
}

function readTile(tag, layers, pbf) {
    if (tag === 3) {
        var layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos);
        if (layer.length) layers[layer.name] = layer;
    }
}


},{"./vectortilelayer":5}],4:[function(_dereq_,module,exports){
'use strict';

var Point = _dereq_('point-geometry');

module.exports = VectorTileFeature;

function VectorTileFeature(pbf, end, extent, keys, values) {
    // Public
    this.properties = {};
    this.extent = extent;
    this.type = 0;

    // Private
    this._pbf = pbf;
    this._geometry = -1;
    this._keys = keys;
    this._values = values;

    pbf.readFields(readFeature, this, end);
}

function readFeature(tag, feature, pbf) {
    if (tag == 1) feature.id = pbf.readVarint();
    else if (tag == 2) readTag(pbf, feature);
    else if (tag == 3) feature.type = pbf.readVarint();
    else if (tag == 4) feature._geometry = pbf.pos;
}

function readTag(pbf, feature) {
    var end = pbf.readVarint() + pbf.pos;

    while (pbf.pos < end) {
        var key = feature._keys[pbf.readVarint()],
            value = feature._values[pbf.readVarint()];
        feature.properties[key] = value;
    }
}

VectorTileFeature.types = ['Unknown', 'Point', 'LineString', 'Polygon'];

VectorTileFeature.prototype.loadGeometry = function() {
    var pbf = this._pbf;
    pbf.pos = this._geometry;

    var end = pbf.readVarint() + pbf.pos,
        cmd = 1,
        length = 0,
        x = 0,
        y = 0,
        lines = [],
        line;

    while (pbf.pos < end) {
        if (!length) {
            var cmdLen = pbf.readVarint();
            cmd = cmdLen & 0x7;
            length = cmdLen >> 3;
        }

        length--;

        if (cmd === 1 || cmd === 2) {
            x += pbf.readSVarint();
            y += pbf.readSVarint();

            if (cmd === 1) { // moveTo
                if (line) lines.push(line);
                line = [];
            }

            line.push(new Point(x, y));

        } else if (cmd === 7) {

            // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90
            if (line) {
                line.push(line[0].clone()); // closePolygon
            }

        } else {
            throw new Error('unknown command ' + cmd);
        }
    }

    if (line) lines.push(line);

    return lines;
};

VectorTileFeature.prototype.bbox = function() {
    var pbf = this._pbf;
    pbf.pos = this._geometry;

    var end = pbf.readVarint() + pbf.pos,
        cmd = 1,
        length = 0,
        x = 0,
        y = 0,
        x1 = Infinity,
        x2 = -Infinity,
        y1 = Infinity,
        y2 = -Infinity;

    while (pbf.pos < end) {
        if (!length) {
            var cmdLen = pbf.readVarint();
            cmd = cmdLen & 0x7;
            length = cmdLen >> 3;
        }

        length--;

        if (cmd === 1 || cmd === 2) {
            x += pbf.readSVarint();
            y += pbf.readSVarint();
            if (x < x1) x1 = x;
            if (x > x2) x2 = x;
            if (y < y1) y1 = y;
            if (y > y2) y2 = y;

        } else if (cmd !== 7) {
            throw new Error('unknown command ' + cmd);
        }
    }

    return [x1, y1, x2, y2];
};

VectorTileFeature.prototype.toGeoJSON = function(x, y, z) {
    var size = this.extent * Math.pow(2, z),
        x0 = this.extent * x,
        y0 = this.extent * y,
        coords = this.loadGeometry(),
        type = VectorTileFeature.types[this.type],
        i, j;

    function project(line) {
        for (var j = 0; j < line.length; j++) {
            var p = line[j], y2 = 180 - (p.y + y0) * 360 / size;
            line[j] = [
                (p.x + x0) * 360 / size - 180,
                360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90
            ];
        }
    }

    switch (this.type) {
    case 1:
        var points = [];
        for (i = 0; i < coords.length; i++) {
            points[i] = coords[i][0];
        }
        coords = points;
        project(coords);
        break;

    case 2:
        for (i = 0; i < coords.length; i++) {
            project(coords[i]);
        }
        break;

    case 3:
        coords = classifyRings(coords);
        for (i = 0; i < coords.length; i++) {
            for (j = 0; j < coords[i].length; j++) {
                project(coords[i][j]);
            }
        }
        break;
    }

    if (coords.length === 1) {
        coords = coords[0];
    } else {
        type = 'Multi' + type;
    }

    var result = {
        type: "Feature",
        geometry: {
            type: type,
            coordinates: coords
        },
        properties: this.properties
    };

    if ('id' in this) {
        result.id = this.id;
    }

    return result;
};

// classifies an array of rings into polygons with outer rings and holes

function classifyRings(rings) {
    var len = rings.length;

    if (len <= 1) return [rings];

    var polygons = [],
        polygon,
        ccw;

    for (var i = 0; i < len; i++) {
        var area = signedArea(rings[i]);
        if (area === 0) continue;

        if (ccw === undefined) ccw = area < 0;

        if (ccw === area < 0) {
            if (polygon) polygons.push(polygon);
            polygon = [rings[i]];

        } else {
            polygon.push(rings[i]);
        }
    }
    if (polygon) polygons.push(polygon);

    return polygons;
}

function signedArea(ring) {
    var sum = 0;
    for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
        p1 = ring[i];
        p2 = ring[j];
        sum += (p2.x - p1.x) * (p1.y + p2.y);
    }
    return sum;
}

},{"point-geometry":1}],5:[function(_dereq_,module,exports){
'use strict';

var VectorTileFeature = _dereq_('./vectortilefeature.js');

module.exports = VectorTileLayer;

function VectorTileLayer(pbf, end) {
    // Public
    this.version = 1;
    this.name = null;
    this.extent = 4096;
    this.length = 0;

    // Private
    this._pbf = pbf;
    this._keys = [];
    this._values = [];
    this._features = [];

    pbf.readFields(readLayer, this, end);

    this.length = this._features.length;
}

function readLayer(tag, layer, pbf) {
    if (tag === 15) layer.version = pbf.readVarint();
    else if (tag === 1) layer.name = pbf.readString();
    else if (tag === 5) layer.extent = pbf.readVarint();
    else if (tag === 2) layer._features.push(pbf.pos);
    else if (tag === 3) layer._keys.push(pbf.readString());
    else if (tag === 4) layer._values.push(readValueMessage(pbf));
}

function readValueMessage(pbf) {
    var value = null,
        end = pbf.readVarint() + pbf.pos;

    while (pbf.pos < end) {
        var tag = pbf.readVarint() >> 3;

        value = tag === 1 ? pbf.readString() :
            tag === 2 ? pbf.readFloat() :
            tag === 3 ? pbf.readDouble() :
            tag === 4 ? pbf.readVarint64() :
            tag === 5 ? pbf.readVarint() :
            tag === 6 ? pbf.readSVarint() :
            tag === 7 ? pbf.readBoolean() : null;
    }

    return value;
}

// return feature `i` from this layer as a `VectorTileFeature`
VectorTileLayer.prototype.feature = function(i) {
    if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');

    this._pbf.pos = this._features[i];

    var end = this._pbf.readVarint() + this._pbf.pos;
    return new VectorTileFeature(this._pbf, end, this.extent, this._keys, this._values);
};

},{"./vectortilefeature.js":4}]},{},[2])(2)
});
ol.ext.vectortile = module.exports;
})();

goog.provide('ol.render.Feature');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.geom.GeometryType');


/**
 * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like
 * structure, optimized for rendering and styling. Geometry access through the
 * API is limited to getting the type and extent of the geometry.
 *
 * @constructor
 * @param {ol.geom.GeometryType} type Geometry type.
 * @param {Array.<number>} flatCoordinates Flat coordinates. These always need
 *     to be right-handed for polygons.
 * @param {Array.<number>|Array.<Array.<number>>} ends Ends or Endss.
 * @param {Object.<string, *>} properties Properties.
 */
ol.render.Feature = function(type, flatCoordinates, ends, properties) {

  /**
   * @private
   * @type {ol.Extent|undefined}
   */
  this.extent_;

  goog.DEBUG && console.assert(type === ol.geom.GeometryType.POINT ||
      type === ol.geom.GeometryType.MULTI_POINT ||
      type === ol.geom.GeometryType.LINE_STRING ||
      type === ol.geom.GeometryType.MULTI_LINE_STRING ||
      type === ol.geom.GeometryType.POLYGON,
      'Need a Point, MultiPoint, LineString, MultiLineString or Polygon type');

  /**
   * @private
   * @type {ol.geom.GeometryType}
   */
  this.type_ = type;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.flatCoordinates_ = flatCoordinates;

  /**
   * @private
   * @type {Array.<number>|Array.<Array.<number>>}
   */
  this.ends_ = ends;

  /**
   * @private
   * @type {Object.<string, *>}
   */
  this.properties_ = properties;

};


/**
 * Get a feature property by its key.
 * @param {string} key Key
 * @return {*} Value for the requested key.
 * @api
 */
ol.render.Feature.prototype.get = function(key) {
  return this.properties_[key];
};


/**
 * @return {Array.<number>|Array.<Array.<number>>} Ends or endss.
 */
ol.render.Feature.prototype.getEnds = function() {
  return this.ends_;
};


/**
 * Get the extent of this feature's geometry.
 * @return {ol.Extent} Extent.
 * @api
 */
ol.render.Feature.prototype.getExtent = function() {
  if (!this.extent_) {
    this.extent_ = this.type_ === ol.geom.GeometryType.POINT ?
        ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) :
        ol.extent.createOrUpdateFromFlatCoordinates(
            this.flatCoordinates_, 0, this.flatCoordinates_.length, 2);

  }
  return this.extent_;
};


/**
 * @return {Array.<number>} Flat coordinates.
 */
ol.render.Feature.prototype.getOrientedFlatCoordinates = function() {
  return this.flatCoordinates_;
};


/**
 * @return {Array.<number>} Flat coordinates.
 */
ol.render.Feature.prototype.getFlatCoordinates =
    ol.render.Feature.prototype.getOrientedFlatCoordinates;


/**
 * Get the feature for working with its geometry.
 * @return {ol.render.Feature} Feature.
 * @api
 */
ol.render.Feature.prototype.getGeometry = function() {
  return this;
};


/**
 * Get the feature properties.
 * @return {Object.<string, *>} Feature properties.
 * @api
 */
ol.render.Feature.prototype.getProperties = function() {
  return this.properties_;
};


/**
 * Get the feature for working with its geometry.
 * @return {ol.render.Feature} Feature.
 */
ol.render.Feature.prototype.getSimplifiedGeometry =
    ol.render.Feature.prototype.getGeometry;


/**
 * @return {number} Stride.
 */
ol.render.Feature.prototype.getStride = function() {
  return 2;
};


/**
 * @return {undefined}
 */
ol.render.Feature.prototype.getStyleFunction = ol.nullFunction;


/**
 * Get the type of this feature's geometry.
 * @return {ol.geom.GeometryType} Geometry type.
 * @api
 */
ol.render.Feature.prototype.getType = function() {
  return this.type_;
};

//FIXME Implement projection handling

goog.provide('ol.format.MVT');

goog.require('ol');
goog.require('ol.ext.pbf');
goog.require('ol.ext.vectortile');
goog.require('ol.format.Feature');
goog.require('ol.format.FormatType');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.proj.Projection');
goog.require('ol.proj.Units');
goog.require('ol.render.Feature');


/**
 * @classdesc
 * Feature format for reading data in the Mapbox MVT format.
 *
 * @constructor
 * @extends {ol.format.Feature}
 * @param {olx.format.MVTOptions=} opt_options Options.
 * @api
 */
ol.format.MVT = function(opt_options) {

  ol.format.Feature.call(this);

  var options = opt_options ? opt_options : {};

  /**
   * @type {ol.proj.Projection}
   */
  this.defaultDataProjection = new ol.proj.Projection({
    code: '',
    units: ol.proj.Units.TILE_PIXELS
  });

  /**
   * @private
   * @type {function((ol.geom.Geometry|Object.<string, *>)=)|
   *     function(ol.geom.GeometryType,Array.<number>,
   *         (Array.<number>|Array.<Array.<number>>),Object.<string, *>)}
   */
  this.featureClass_ = options.featureClass ?
      options.featureClass : ol.render.Feature;

  /**
   * @private
   * @type {string}
   */
  this.geometryName_ = options.geometryName ?
      options.geometryName : 'geometry';

  /**
   * @private
   * @type {string}
   */
  this.layerName_ = options.layerName ? options.layerName : 'layer';

  /**
   * @private
   * @type {Array.<string>}
   */
  this.layers_ = options.layers ? options.layers : null;

};
ol.inherits(ol.format.MVT, ol.format.Feature);


/**
 * @inheritDoc
 */
ol.format.MVT.prototype.getType = function() {
  return ol.format.FormatType.ARRAY_BUFFER;
};


/**
 * @private
 * @param {Object} rawFeature Raw Mapbox feature.
 * @param {string} layer Layer.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 */
ol.format.MVT.prototype.readFeature_ = function(
    rawFeature, layer, opt_options) {
  var feature = new this.featureClass_();
  var id = rawFeature.id;
  var values = rawFeature.properties;
  values[this.layerName_] = layer;
  var geometry = ol.format.Feature.transformWithOptions(
      ol.format.MVT.readGeometry_(rawFeature), false,
      this.adaptOptions(opt_options));
  if (geometry) {
    values[this.geometryName_] = geometry;
  }
  feature.setId(id);
  feature.setProperties(values);
  feature.setGeometryName(this.geometryName_);
  return feature;
};


/**
 * @private
 * @param {Object} rawFeature Raw Mapbox feature.
 * @param {string} layer Layer.
 * @return {ol.render.Feature} Feature.
 */
ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) {
  var coords = rawFeature.loadGeometry();
  var ends = [];
  var flatCoordinates = [];
  ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);

  var type = rawFeature.type;
  /** @type {ol.geom.GeometryType} */
  var geometryType;
  if (type === 1) {
    geometryType = coords.length === 1 ?
        ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT;
  } else if (type === 2) {
    if (coords.length === 1) {
      geometryType = ol.geom.GeometryType.LINE_STRING;
    } else {
      geometryType = ol.geom.GeometryType.MULTI_LINE_STRING;
    }
  } else if (type === 3) {
    geometryType = ol.geom.GeometryType.POLYGON;
  }

  var values = rawFeature.properties;
  values[this.layerName_] = layer;

  return new this.featureClass_(geometryType, flatCoordinates, ends, values);
};


/**
 * @inheritDoc
 * @api
 */
ol.format.MVT.prototype.readFeatures = function(source, opt_options) {
  var layers = this.layers_;

  var pbf = new ol.ext.pbf(/** @type {ArrayBuffer} */ (source));
  var tile = new ol.ext.vectortile.VectorTile(pbf);
  var features = [];
  var featureClass = this.featureClass_;
  var layer, feature;
  for (var name in tile.layers) {
    if (layers && layers.indexOf(name) == -1) {
      continue;
    }
    layer = tile.layers[name];

    for (var i = 0, ii = layer.length; i < ii; ++i) {
      if (featureClass === ol.render.Feature) {
        feature = this.readRenderFeature_(layer.feature(i), name);
      } else {
        feature = this.readFeature_(layer.feature(i), name, opt_options);
      }
      features.push(feature);
    }
  }

  return features;
};


/**
 * @inheritDoc
 * @api
 */
ol.format.MVT.prototype.readProjection = function(source) {
  return this.defaultDataProjection;
};


/**
 * Sets the layers that features will be read from.
 * @param {Array.<string>} layers Layers.
 * @api
 */
ol.format.MVT.prototype.setLayers = function(layers) {
  this.layers_ = layers;
};


/**
 * @private
 * @param {Object} coords Raw feature coordinates.
 * @param {Array.<number>} flatCoordinates Flat coordinates to be populated by
 *     this function.
 * @param {Array.<number>} ends Ends to be populated by this function.
 */
ol.format.MVT.calculateFlatCoordinates_ = function(
    coords, flatCoordinates, ends) {
  var end = 0;
  for (var i = 0, ii = coords.length; i < ii; ++i) {
    var line = coords[i];
    var j, jj;
    for (j = 0, jj = line.length; j < jj; ++j) {
      var coord = line[j];
      // Non-tilespace coords can be calculated here when a TileGrid and
      // TileCoord are known.
      flatCoordinates.push(coord.x, coord.y);
    }
    end += 2 * j;
    ends.push(end);
  }
};


/**
 * @private
 * @param {Object} rawFeature Raw Mapbox feature.
 * @return {ol.geom.Geometry} Geometry.
 */
ol.format.MVT.readGeometry_ = function(rawFeature) {
  var type = rawFeature.type;
  if (type === 0) {
    return null;
  }

  var coords = rawFeature.loadGeometry();
  var ends = [];
  var flatCoordinates = [];
  ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);

  var geom;
  if (type === 1) {
    geom = coords.length === 1 ?
        new ol.geom.Point(null) : new ol.geom.MultiPoint(null);
  } else if (type === 2) {
    if (coords.length === 1) {
      geom = new ol.geom.LineString(null);
    } else {
      geom = new ol.geom.MultiLineString(null);
    }
  } else if (type === 3) {
    geom = new ol.geom.Polygon(null);
  }

  geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
      ends);

  return geom;
};

goog.provide('ol.format.ogc.filter.Filter');

goog.require('ol');


/**
 * @classdesc
 * Abstract class; normally only used for creating subclasses and not instantiated in apps.
 * Base class for WFS GetFeature filters.
 *
 * @constructor
 * @param {!string} tagName The XML tag name for this filter.
 * @struct
 * @api
 */
ol.format.ogc.filter.Filter = function(tagName) {

  /**
   * @private
   * @type {!string}
   */
  this.tagName_ = tagName;
};

/**
 * The XML tag name for a filter.
 * @returns {!string} Name.
 */
ol.format.ogc.filter.Filter.prototype.getTagName = function() {
  return this.tagName_;
};

goog.provide('ol.format.ogc.filter.Logical');

goog.require('ol');
goog.require('ol.format.ogc.filter.Filter');


/**
 * @classdesc
 * Abstract class; normally only used for creating subclasses and not instantiated in apps.
 * Base class for WFS GetFeature logical filters.
 *
 * @constructor
 * @param {!string} tagName The XML tag name for this filter.
 * @extends {ol.format.ogc.filter.Filter}
 */
ol.format.ogc.filter.Logical = function(tagName) {
  ol.format.ogc.filter.Filter.call(this, tagName);
};
ol.inherits(ol.format.ogc.filter.Logical, ol.format.ogc.filter.Filter);

goog.provide('ol.format.ogc.filter.LogicalBinary');

goog.require('ol');
goog.require('ol.format.ogc.filter.Logical');


/**
 * @classdesc
 * Abstract class; normally only used for creating subclasses and not instantiated in apps.
 * Base class for WFS GetFeature binary logical filters.
 *
 * @constructor
 * @param {!string} tagName The XML tag name for this filter.
 * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition.
 * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition.
 * @extends {ol.format.ogc.filter.Logical}
 */
ol.format.ogc.filter.LogicalBinary = function(tagName, conditionA, conditionB) {

  ol.format.ogc.filter.Logical.call(this, tagName);

  /**
   * @public
   * @type {!ol.format.ogc.filter.Filter}
   */
  this.conditionA = conditionA;

  /**
   * @public
   * @type {!ol.format.ogc.filter.Filter}
   */
  this.conditionB = conditionB;

};
ol.inherits(ol.format.ogc.filter.LogicalBinary, ol.format.ogc.filter.Logical);

goog.provide('ol.format.ogc.filter.And');

goog.require('ol');
goog.require('ol.format.ogc.filter.LogicalBinary');

/**
 * @classdesc
 * Represents a logical `<And>` operator between two filter conditions.
 *
 * @constructor
 * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition.
 * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition.
 * @extends {ol.format.ogc.filter.LogicalBinary}
 * @api
 */
ol.format.ogc.filter.And = function(conditionA, conditionB) {
  ol.format.ogc.filter.LogicalBinary.call(this, 'And', conditionA, conditionB);
};
ol.inherits(ol.format.ogc.filter.And, ol.format.ogc.filter.LogicalBinary);

goog.provide('ol.format.ogc.filter.Bbox');

goog.require('ol');
goog.require('ol.format.ogc.filter.Filter');


/**
 * @classdesc
 * Represents a `<BBOX>` operator to test whether a geometry-valued property
 * intersects a fixed bounding box
 *
 * @constructor
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.Extent} extent Extent.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @extends {ol.format.ogc.filter.Filter}
 * @api
 */
ol.format.ogc.filter.Bbox = function(geometryName, extent, opt_srsName) {

  ol.format.ogc.filter.Filter.call(this, 'BBOX');

  /**
   * @public
   * @type {!string}
   */
  this.geometryName = geometryName;

  /**
   * @public
   * @type {ol.Extent}
   */
  this.extent = extent;

  /**
   * @public
   * @type {string|undefined}
   */
  this.srsName = opt_srsName;
};
ol.inherits(ol.format.ogc.filter.Bbox, ol.format.ogc.filter.Filter);

goog.provide('ol.format.ogc.filter.Comparison');

goog.require('ol');
goog.require('ol.format.ogc.filter.Filter');


/**
 * @classdesc
 * Abstract class; normally only used for creating subclasses and not instantiated in apps.
 * Base class for WFS GetFeature property comparison filters.
 *
 * @constructor
 * @param {!string} tagName The XML tag name for this filter.
 * @param {!string} propertyName Name of the context property to compare.
 * @extends {ol.format.ogc.filter.Filter}
 * @api
 */
ol.format.ogc.filter.Comparison = function(tagName, propertyName) {

  ol.format.ogc.filter.Filter.call(this, tagName);

  /**
   * @public
   * @type {!string}
   */
  this.propertyName = propertyName;
};
ol.inherits(ol.format.ogc.filter.Comparison, ol.format.ogc.filter.Filter);

goog.provide('ol.format.ogc.filter.ComparisonBinary');

goog.require('ol');
goog.require('ol.format.ogc.filter.Comparison');


/**
 * @classdesc
 * Abstract class; normally only used for creating subclasses and not instantiated in apps.
 * Base class for WFS GetFeature property binary comparison filters.
 *
 * @constructor
 * @param {!string} tagName The XML tag name for this filter.
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!(string|number)} expression The value to compare.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @extends {ol.format.ogc.filter.Comparison}
 * @api
 */
ol.format.ogc.filter.ComparisonBinary = function(
    tagName, propertyName, expression, opt_matchCase) {

  ol.format.ogc.filter.Comparison.call(this, tagName, propertyName);

  /**
   * @public
   * @type {!(string|number)}
   */
  this.expression = expression;

  /**
   * @public
   * @type {boolean|undefined}
   */
  this.matchCase = opt_matchCase;
};
ol.inherits(ol.format.ogc.filter.ComparisonBinary, ol.format.ogc.filter.Comparison);

goog.provide('ol.format.ogc.filter.EqualTo');

goog.require('ol');
goog.require('ol.format.ogc.filter.ComparisonBinary');


/**
 * @classdesc
 * Represents a `<PropertyIsEqualTo>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!(string|number)} expression The value to compare.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @extends {ol.format.ogc.filter.ComparisonBinary}
 * @api
 */
ol.format.ogc.filter.EqualTo = function(propertyName, expression, opt_matchCase) {
  ol.format.ogc.filter.ComparisonBinary.call(this, 'PropertyIsEqualTo', propertyName, expression, opt_matchCase);
};
ol.inherits(ol.format.ogc.filter.EqualTo, ol.format.ogc.filter.ComparisonBinary);

goog.provide('ol.format.ogc.filter.GreaterThan');

goog.require('ol');
goog.require('ol.format.ogc.filter.ComparisonBinary');


/**
 * @classdesc
 * Represents a `<PropertyIsGreaterThan>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @extends {ol.format.ogc.filter.ComparisonBinary}
 * @api
 */
ol.format.ogc.filter.GreaterThan = function(propertyName, expression) {
  ol.format.ogc.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThan', propertyName, expression);
};
ol.inherits(ol.format.ogc.filter.GreaterThan, ol.format.ogc.filter.ComparisonBinary);

goog.provide('ol.format.ogc.filter.GreaterThanOrEqualTo');

goog.require('ol');
goog.require('ol.format.ogc.filter.ComparisonBinary');


/**
 * @classdesc
 * Represents a `<PropertyIsGreaterThanOrEqualTo>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @extends {ol.format.ogc.filter.ComparisonBinary}
 * @api
 */
ol.format.ogc.filter.GreaterThanOrEqualTo = function(propertyName, expression) {
  ol.format.ogc.filter.ComparisonBinary.call(this, 'PropertyIsGreaterThanOrEqualTo', propertyName, expression);
};
ol.inherits(ol.format.ogc.filter.GreaterThanOrEqualTo, ol.format.ogc.filter.ComparisonBinary);

goog.provide('ol.format.ogc.filter.Spatial');

goog.require('ol');
goog.require('ol.format.ogc.filter.Filter');


/**
 * @classdesc
 * Represents a spatial operator to test whether a geometry-valued property
 * relates to a given geometry.
 *
 * @constructor
 * @param {!string} tagName The XML tag name for this filter.
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.geom.Geometry} geometry Geometry.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @extends {ol.format.ogc.filter.Filter}
 * @api
 */
ol.format.ogc.filter.Spatial = function(tagName, geometryName, geometry, opt_srsName) {

  ol.format.ogc.filter.Filter.call(this, tagName);

  /**
   * @public
   * @type {!string}
   */
  this.geometryName = geometryName || 'the_geom';

  /**
   * @public
   * @type {ol.geom.Geometry}
   */
  this.geometry = geometry;

  /**
   * @public
   * @type {string|undefined}
   */
  this.srsName = opt_srsName;
};
ol.inherits(ol.format.ogc.filter.Spatial, ol.format.ogc.filter.Filter);

goog.provide('ol.format.ogc.filter.Intersects');

goog.require('ol');
goog.require('ol.format.ogc.filter.Spatial');


/**
 * @classdesc
 * Represents a `<Intersects>` operator to test whether a geometry-valued property
 * intersects a given geometry.
 *
 * @constructor
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.geom.Geometry} geometry Geometry.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @extends {ol.format.ogc.filter.Spatial}
 * @api
 */
ol.format.ogc.filter.Intersects = function(geometryName, geometry, opt_srsName) {

  ol.format.ogc.filter.Spatial.call(this, 'Intersects', geometryName, geometry, opt_srsName);

};
ol.inherits(ol.format.ogc.filter.Intersects, ol.format.ogc.filter.Spatial);

goog.provide('ol.format.ogc.filter.IsBetween');

goog.require('ol');
goog.require('ol.format.ogc.filter.Comparison');


/**
 * @classdesc
 * Represents a `<PropertyIsBetween>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} lowerBoundary The lower bound of the range.
 * @param {!number} upperBoundary The upper bound of the range.
 * @extends {ol.format.ogc.filter.Comparison}
 * @api
 */
ol.format.ogc.filter.IsBetween = function(propertyName, lowerBoundary, upperBoundary) {
  ol.format.ogc.filter.Comparison.call(this, 'PropertyIsBetween', propertyName);

  /**
   * @public
   * @type {!number}
   */
  this.lowerBoundary = lowerBoundary;

  /**
   * @public
   * @type {!number}
   */
  this.upperBoundary = upperBoundary;
};
ol.inherits(ol.format.ogc.filter.IsBetween, ol.format.ogc.filter.Comparison);

goog.provide('ol.format.ogc.filter.IsLike');

goog.require('ol');
goog.require('ol.format.ogc.filter.Comparison');


/**
 * @classdesc
 * Represents a `<PropertyIsLike>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!string} pattern Text pattern.
 * @param {string=} opt_wildCard Pattern character which matches any sequence of
 *    zero or more string characters. Default is '*'.
 * @param {string=} opt_singleChar pattern character which matches any single
 *    string character. Default is '.'.
 * @param {string=} opt_escapeChar Escape character which can be used to escape
 *    the pattern characters. Default is '!'.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @extends {ol.format.ogc.filter.Comparison}
 * @api
 */
ol.format.ogc.filter.IsLike = function(propertyName, pattern,
    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) {
  ol.format.ogc.filter.Comparison.call(this, 'PropertyIsLike', propertyName);

  /**
   * @public
   * @type {!string}
   */
  this.pattern = pattern;

  /**
   * @public
   * @type {!string}
   */
  this.wildCard = (opt_wildCard !== undefined) ? opt_wildCard : '*';

  /**
   * @public
   * @type {!string}
   */
  this.singleChar = (opt_singleChar !== undefined) ? opt_singleChar : '.';

  /**
   * @public
   * @type {!string}
   */
  this.escapeChar = (opt_escapeChar !== undefined) ? opt_escapeChar : '!';

  /**
   * @public
   * @type {boolean|undefined}
   */
  this.matchCase = opt_matchCase;
};
ol.inherits(ol.format.ogc.filter.IsLike, ol.format.ogc.filter.Comparison);

goog.provide('ol.format.ogc.filter.IsNull');

goog.require('ol');
goog.require('ol.format.ogc.filter.Comparison');


/**
 * @classdesc
 * Represents a `<PropertyIsNull>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @extends {ol.format.ogc.filter.Comparison}
 * @api
 */
ol.format.ogc.filter.IsNull = function(propertyName) {
  ol.format.ogc.filter.Comparison.call(this, 'PropertyIsNull', propertyName);
};
ol.inherits(ol.format.ogc.filter.IsNull, ol.format.ogc.filter.Comparison);

goog.provide('ol.format.ogc.filter.LessThan');

goog.require('ol');
goog.require('ol.format.ogc.filter.ComparisonBinary');


/**
 * @classdesc
 * Represents a `<PropertyIsLessThan>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @extends {ol.format.ogc.filter.ComparisonBinary}
 * @api
 */
ol.format.ogc.filter.LessThan = function(propertyName, expression) {
  ol.format.ogc.filter.ComparisonBinary.call(this, 'PropertyIsLessThan', propertyName, expression);
};
ol.inherits(ol.format.ogc.filter.LessThan, ol.format.ogc.filter.ComparisonBinary);

goog.provide('ol.format.ogc.filter.LessThanOrEqualTo');

goog.require('ol');
goog.require('ol.format.ogc.filter.ComparisonBinary');


/**
 * @classdesc
 * Represents a `<PropertyIsLessThanOrEqualTo>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @extends {ol.format.ogc.filter.ComparisonBinary}
 * @api
 */
ol.format.ogc.filter.LessThanOrEqualTo = function(propertyName, expression) {
  ol.format.ogc.filter.ComparisonBinary.call(this, 'PropertyIsLessThanOrEqualTo', propertyName, expression);
};
ol.inherits(ol.format.ogc.filter.LessThanOrEqualTo, ol.format.ogc.filter.ComparisonBinary);

goog.provide('ol.format.ogc.filter.Not');

goog.require('ol');
goog.require('ol.format.ogc.filter.Logical');


/**
 * @classdesc
 * Represents a logical `<Not>` operator for a filter condition.
 *
 * @constructor
 * @param {!ol.format.ogc.filter.Filter} condition Filter condition.
 * @extends {ol.format.ogc.filter.Logical}
 * @api
 */
ol.format.ogc.filter.Not = function(condition) {

  ol.format.ogc.filter.Logical.call(this, 'Not');

  /**
   * @public
   * @type {!ol.format.ogc.filter.Filter}
   */
  this.condition = condition;
};
ol.inherits(ol.format.ogc.filter.Not, ol.format.ogc.filter.Logical);

goog.provide('ol.format.ogc.filter.NotEqualTo');

goog.require('ol');
goog.require('ol.format.ogc.filter.ComparisonBinary');


/**
 * @classdesc
 * Represents a `<PropertyIsNotEqualTo>` comparison operator.
 *
 * @constructor
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!(string|number)} expression The value to compare.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @extends {ol.format.ogc.filter.ComparisonBinary}
 * @api
 */
ol.format.ogc.filter.NotEqualTo = function(propertyName, expression, opt_matchCase) {
  ol.format.ogc.filter.ComparisonBinary.call(this, 'PropertyIsNotEqualTo', propertyName, expression, opt_matchCase);
};
ol.inherits(ol.format.ogc.filter.NotEqualTo, ol.format.ogc.filter.ComparisonBinary);

goog.provide('ol.format.ogc.filter.Or');

goog.require('ol');
goog.require('ol.format.ogc.filter.LogicalBinary');


/**
 * @classdesc
 * Represents a logical `<Or>` operator between two filter conditions.
 *
 * @constructor
 * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition.
 * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition.
 * @extends {ol.format.ogc.filter.LogicalBinary}
 * @api
 */
ol.format.ogc.filter.Or = function(conditionA, conditionB) {
  ol.format.ogc.filter.LogicalBinary.call(this, 'Or', conditionA, conditionB);
};
ol.inherits(ol.format.ogc.filter.Or, ol.format.ogc.filter.LogicalBinary);

goog.provide('ol.format.ogc.filter.Within');

goog.require('ol');
goog.require('ol.format.ogc.filter.Spatial');


/**
 * @classdesc
 * Represents a `<Within>` operator to test whether a geometry-valued property
 * is within a given geometry.
 *
 * @constructor
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.geom.Geometry} geometry Geometry.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @extends {ol.format.ogc.filter.Spatial}
 * @api
 */
ol.format.ogc.filter.Within = function(geometryName, geometry, opt_srsName) {

  ol.format.ogc.filter.Spatial.call(this, 'Within', geometryName, geometry, opt_srsName);

};
ol.inherits(ol.format.ogc.filter.Within, ol.format.ogc.filter.Spatial);

goog.provide('ol.format.ogc.filter');

goog.require('ol');
goog.require('ol.format.ogc.filter.And');
goog.require('ol.format.ogc.filter.Bbox');
goog.require('ol.format.ogc.filter.EqualTo');
goog.require('ol.format.ogc.filter.GreaterThan');
goog.require('ol.format.ogc.filter.GreaterThanOrEqualTo');
goog.require('ol.format.ogc.filter.Intersects');
goog.require('ol.format.ogc.filter.IsBetween');
goog.require('ol.format.ogc.filter.IsLike');
goog.require('ol.format.ogc.filter.IsNull');
goog.require('ol.format.ogc.filter.LessThan');
goog.require('ol.format.ogc.filter.LessThanOrEqualTo');
goog.require('ol.format.ogc.filter.Not');
goog.require('ol.format.ogc.filter.NotEqualTo');
goog.require('ol.format.ogc.filter.Or');
goog.require('ol.format.ogc.filter.Within');


/**
 * Create a logical `<And>` operator between two filter conditions.
 *
 * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition.
 * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition.
 * @returns {!ol.format.ogc.filter.And} `<And>` operator.
 * @api
 */
ol.format.ogc.filter.and = function(conditionA, conditionB) {
  return new ol.format.ogc.filter.And(conditionA, conditionB);
};


/**
 * Create a logical `<Or>` operator between two filter conditions.
 *
 * @param {!ol.format.ogc.filter.Filter} conditionA First filter condition.
 * @param {!ol.format.ogc.filter.Filter} conditionB Second filter condition.
 * @returns {!ol.format.ogc.filter.Or} `<Or>` operator.
 * @api
 */
ol.format.ogc.filter.or = function(conditionA, conditionB) {
  return new ol.format.ogc.filter.Or(conditionA, conditionB);
};


/**
 * Represents a logical `<Not>` operator for a filter condition.
 *
 * @param {!ol.format.ogc.filter.Filter} condition Filter condition.
 * @returns {!ol.format.ogc.filter.Not} `<Not>` operator.
 * @api
 */
ol.format.ogc.filter.not = function(condition) {
  return new ol.format.ogc.filter.Not(condition);
};


/**
 * Create a `<BBOX>` operator to test whether a geometry-valued property
 * intersects a fixed bounding box
 *
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.Extent} extent Extent.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @returns {!ol.format.ogc.filter.Bbox} `<BBOX>` operator.
 * @api
 */
ol.format.ogc.filter.bbox = function(geometryName, extent, opt_srsName) {
  return new ol.format.ogc.filter.Bbox(geometryName, extent, opt_srsName);
};

/**
 * Create a `<Intersects>` operator to test whether a geometry-valued property
 * intersects a given geometry.
 *
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.geom.Geometry} geometry Geometry.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @returns {!ol.format.ogc.filter.Intersects} `<Intersects>` operator.
 * @api
 */
ol.format.ogc.filter.intersects = function(geometryName, geometry, opt_srsName) {
  return new ol.format.ogc.filter.Intersects(geometryName, geometry, opt_srsName);
};

/**
 * Create a `<Within>` operator to test whether a geometry-valued property
 * is within a given geometry.
 *
 * @param {!string} geometryName Geometry name to use.
 * @param {!ol.geom.Geometry} geometry Geometry.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @returns {!ol.format.ogc.filter.Within} `<Within>` operator.
 * @api
 */
ol.format.ogc.filter.within = function(geometryName, geometry, opt_srsName) {
  return new ol.format.ogc.filter.Within(geometryName, geometry, opt_srsName);
};


/**
 * Creates a `<PropertyIsEqualTo>` comparison operator.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!(string|number)} expression The value to compare.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @returns {!ol.format.ogc.filter.EqualTo} `<PropertyIsEqualTo>` operator.
 * @api
 */
ol.format.ogc.filter.equalTo = function(propertyName, expression, opt_matchCase) {
  return new ol.format.ogc.filter.EqualTo(propertyName, expression, opt_matchCase);
};


/**
 * Creates a `<PropertyIsNotEqualTo>` comparison operator.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!(string|number)} expression The value to compare.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @returns {!ol.format.ogc.filter.NotEqualTo} `<PropertyIsNotEqualTo>` operator.
 * @api
 */
ol.format.ogc.filter.notEqualTo = function(propertyName, expression, opt_matchCase) {
  return new ol.format.ogc.filter.NotEqualTo(propertyName, expression, opt_matchCase);
};


/**
 * Creates a `<PropertyIsLessThan>` comparison operator.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @returns {!ol.format.ogc.filter.LessThan} `<PropertyIsLessThan>` operator.
 * @api
 */
ol.format.ogc.filter.lessThan = function(propertyName, expression) {
  return new ol.format.ogc.filter.LessThan(propertyName, expression);
};


/**
 * Creates a `<PropertyIsLessThanOrEqualTo>` comparison operator.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @returns {!ol.format.ogc.filter.LessThanOrEqualTo} `<PropertyIsLessThanOrEqualTo>` operator.
 * @api
 */
ol.format.ogc.filter.lessThanOrEqualTo = function(propertyName, expression) {
  return new ol.format.ogc.filter.LessThanOrEqualTo(propertyName, expression);
};


/**
 * Creates a `<PropertyIsGreaterThan>` comparison operator.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @returns {!ol.format.ogc.filter.GreaterThan} `<PropertyIsGreaterThan>` operator.
 * @api
 */
ol.format.ogc.filter.greaterThan = function(propertyName, expression) {
  return new ol.format.ogc.filter.GreaterThan(propertyName, expression);
};


/**
 * Creates a `<PropertyIsGreaterThanOrEqualTo>` comparison operator.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} expression The value to compare.
 * @returns {!ol.format.ogc.filter.GreaterThanOrEqualTo} `<PropertyIsGreaterThanOrEqualTo>` operator.
 * @api
 */
ol.format.ogc.filter.greaterThanOrEqualTo = function(propertyName, expression) {
  return new ol.format.ogc.filter.GreaterThanOrEqualTo(propertyName, expression);
};


/**
 * Creates a `<PropertyIsNull>` comparison operator to test whether a property value
 * is null.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @returns {!ol.format.ogc.filter.IsNull} `<PropertyIsNull>` operator.
 * @api
 */
ol.format.ogc.filter.isNull = function(propertyName) {
  return new ol.format.ogc.filter.IsNull(propertyName);
};


/**
 * Creates a `<PropertyIsBetween>` comparison operator to test whether an expression
 * value lies within a range given by a lower and upper bound (inclusive).
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!number} lowerBoundary The lower bound of the range.
 * @param {!number} upperBoundary The upper bound of the range.
 * @returns {!ol.format.ogc.filter.IsBetween} `<PropertyIsBetween>` operator.
 * @api
 */
ol.format.ogc.filter.between = function(propertyName, lowerBoundary, upperBoundary) {
  return new ol.format.ogc.filter.IsBetween(propertyName, lowerBoundary, upperBoundary);
};


/**
 * Represents a `<PropertyIsLike>` comparison operator that matches a string property
 * value against a text pattern.
 *
 * @param {!string} propertyName Name of the context property to compare.
 * @param {!string} pattern Text pattern.
 * @param {string=} opt_wildCard Pattern character which matches any sequence of
 *    zero or more string characters. Default is '*'.
 * @param {string=} opt_singleChar pattern character which matches any single
 *    string character. Default is '.'.
 * @param {string=} opt_escapeChar Escape character which can be used to escape
 *    the pattern characters. Default is '!'.
 * @param {boolean=} opt_matchCase Case-sensitive?
 * @returns {!ol.format.ogc.filter.IsLike} `<PropertyIsLike>` operator.
 * @api
 */
ol.format.ogc.filter.like = function(propertyName, pattern,
    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase) {
  return new ol.format.ogc.filter.IsLike(propertyName, pattern,
    opt_wildCard, opt_singleChar, opt_escapeChar, opt_matchCase);
};

// FIXME add typedef for stack state objects
goog.provide('ol.format.OSMXML');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.XMLFeature');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.xml');


/**
 * @classdesc
 * Feature format for reading data in the
 * [OSMXML format](http://wiki.openstreetmap.org/wiki/OSM_XML).
 *
 * @constructor
 * @extends {ol.format.XMLFeature}
 * @api stable
 */
ol.format.OSMXML = function() {
  ol.format.XMLFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get('EPSG:4326');
};
ol.inherits(ol.format.OSMXML, ol.format.XMLFeature);


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.OSMXML.EXTENSIONS_ = ['.osm'];


/**
 * @inheritDoc
 */
ol.format.OSMXML.prototype.getExtensions = function() {
  return ol.format.OSMXML.EXTENSIONS_;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.OSMXML.readNode_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'node', 'localName should be node');
  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var id = node.getAttribute('id');
  /** @type {ol.Coordinate} */
  var coordinates = [
    parseFloat(node.getAttribute('lon')),
    parseFloat(node.getAttribute('lat'))
  ];
  state.nodes[id] = coordinates;

  var values = ol.xml.pushParseAndPop({
    tags: {}
  }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack);
  if (!ol.obj.isEmpty(values.tags)) {
    var geometry = new ol.geom.Point(coordinates);
    ol.format.Feature.transformWithOptions(geometry, false, options);
    var feature = new ol.Feature(geometry);
    feature.setId(id);
    feature.setProperties(values.tags);
    state.features.push(feature);
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.OSMXML.readWay_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'way', 'localName should be way');
  var options = /** @type {olx.format.ReadOptions} */ (objectStack[0]);
  var id = node.getAttribute('id');
  var values = ol.xml.pushParseAndPop({
    ndrefs: [],
    tags: {}
  }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack);
  var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  /** @type {Array.<number>} */
  var flatCoordinates = [];
  for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
    var point = state.nodes[values.ndrefs[i]];
    ol.array.extend(flatCoordinates, point);
  }
  var geometry;
  if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) {
    // closed way
    geometry = new ol.geom.Polygon(null);
    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
        [flatCoordinates.length]);
  } else {
    geometry = new ol.geom.LineString(null);
    geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
  }
  ol.format.Feature.transformWithOptions(geometry, false, options);
  var feature = new ol.Feature(geometry);
  feature.setId(id);
  feature.setProperties(values.tags);
  state.features.push(feature);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.OSMXML.readNd_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'nd', 'localName should be nd');
  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  values.ndrefs.push(node.getAttribute('ref'));
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.OSMXML.readTag_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'tag', 'localName should be tag');
  var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  values.tags[node.getAttribute('k')] = node.getAttribute('v');
};


/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.OSMXML.NAMESPACE_URIS_ = [
  null
];


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OSMXML.NAMESPACE_URIS_, {
      'nd': ol.format.OSMXML.readNd_,
      'tag': ol.format.OSMXML.readTag_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OSMXML.PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OSMXML.NAMESPACE_URIS_, {
      'node': ol.format.OSMXML.readNode_,
      'way': ol.format.OSMXML.readWay_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OSMXML.NAMESPACE_URIS_, {
      'tag': ol.format.OSMXML.readTag_
    });


/**
 * Read all features from an OSM source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.OSMXML.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.OSMXML.prototype.readFeaturesFromNode = function(node, opt_options) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  var options = this.getReadOptions(node, opt_options);
  if (node.localName == 'osm') {
    var state = ol.xml.pushParseAndPop({
      nodes: {},
      features: []
    }, ol.format.OSMXML.PARSERS_, node, [options]);
    if (state.features) {
      return state.features;
    }
  }
  return [];
};


/**
 * Read the projection from an OSM source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.OSMXML.prototype.readProjection;

goog.provide('ol.format.XLink');


/**
 * @const
 * @type {string}
 */
ol.format.XLink.NAMESPACE_URI = 'http://www.w3.org/1999/xlink';


/**
 * @param {Node} node Node.
 * @return {boolean|undefined} Boolean.
 */
ol.format.XLink.readHref = function(node) {
  return node.getAttributeNS(ol.format.XLink.NAMESPACE_URI, 'href');
};

goog.provide('ol.format.XML');

goog.require('ol.xml');


/**
 * @classdesc
 * Generic format for reading non-feature XML data
 *
 * @constructor
 * @struct
 */
ol.format.XML = function() {
};


/**
 * @param {Document|Node|string} source Source.
 * @return {Object} The parsed result.
 */
ol.format.XML.prototype.read = function(source) {
  if (ol.xml.isDocument(source)) {
    return this.readFromDocument(/** @type {Document} */ (source));
  } else if (ol.xml.isNode(source)) {
    return this.readFromNode(/** @type {Node} */ (source));
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readFromDocument(doc);
  } else {
    return null;
  }
};


/**
 * @abstract
 * @param {Document} doc Document.
 * @return {Object} Object
 */
ol.format.XML.prototype.readFromDocument = function(doc) {};


/**
 * @abstract
 * @param {Node} node Node.
 * @return {Object} Object
 */
ol.format.XML.prototype.readFromNode = function(node) {};

goog.provide('ol.format.OWS');

goog.require('ol');
goog.require('ol.format.XLink');
goog.require('ol.format.XML');
goog.require('ol.format.XSD');
goog.require('ol.xml');


/**
 * @constructor
 * @extends {ol.format.XML}
 */
ol.format.OWS = function() {
  ol.format.XML.call(this);
};
ol.inherits(ol.format.OWS, ol.format.XML);


/**
 * @param {Document} doc Document.
 * @return {Object} OWS object.
 */
ol.format.OWS.prototype.readFromDocument = function(doc) {
  goog.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
      'doc.nodeType should be DOCUMENT');
  for (var n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readFromNode(n);
    }
  }
  return null;
};


/**
 * @param {Node} node Node.
 * @return {Object} OWS object.
 */
ol.format.OWS.prototype.readFromNode = function(node) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  var owsObject = ol.xml.pushParseAndPop({},
      ol.format.OWS.PARSERS_, node, []);
  return owsObject ? owsObject : null;
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The address.
 */
ol.format.OWS.readAddress_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Address',
      'localName should be Address');
  return ol.xml.pushParseAndPop({},
      ol.format.OWS.ADDRESS_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The values.
 */
ol.format.OWS.readAllowedValues_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'AllowedValues',
      'localName should be AllowedValues');
  return ol.xml.pushParseAndPop({},
      ol.format.OWS.ALLOWED_VALUES_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The constraint.
 */
ol.format.OWS.readConstraint_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Constraint',
      'localName should be Constraint');
  var name = node.getAttribute('name');
  if (!name) {
    return undefined;
  }
  return ol.xml.pushParseAndPop({'name': name},
      ol.format.OWS.CONSTRAINT_PARSERS_, node,
      objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The contact info.
 */
ol.format.OWS.readContactInfo_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ContactInfo',
      'localName should be ContactInfo');
  return ol.xml.pushParseAndPop({},
      ol.format.OWS.CONTACT_INFO_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The DCP.
 */
ol.format.OWS.readDcp_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'DCP', 'localName should be DCP');
  return ol.xml.pushParseAndPop({},
      ol.format.OWS.DCP_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The GET object.
 */
ol.format.OWS.readGet_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Get', 'localName should be Get');
  var href = ol.format.XLink.readHref(node);
  if (!href) {
    return undefined;
  }
  return ol.xml.pushParseAndPop({'href': href},
      ol.format.OWS.REQUEST_METHOD_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The HTTP object.
 */
ol.format.OWS.readHttp_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP');
  return ol.xml.pushParseAndPop({}, ol.format.OWS.HTTP_PARSERS_,
      node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The operation.
 */
ol.format.OWS.readOperation_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Operation',
      'localName should be Operation');
  var name = node.getAttribute('name');
  var value = ol.xml.pushParseAndPop({},
      ol.format.OWS.OPERATION_PARSERS_, node, objectStack);
  if (!value) {
    return undefined;
  }
  var object = /** @type {Object} */
      (objectStack[objectStack.length - 1]);
  object[name] = value;

};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The operations metadata.
 */
ol.format.OWS.readOperationsMetadata_ = function(node,
    objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'OperationsMetadata',
      'localName should be OperationsMetadata');
  return ol.xml.pushParseAndPop({},
      ol.format.OWS.OPERATIONS_METADATA_PARSERS_, node,
      objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The phone.
 */
ol.format.OWS.readPhone_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Phone', 'localName should be Phone');
  return ol.xml.pushParseAndPop({},
      ol.format.OWS.PHONE_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The service identification.
 */
ol.format.OWS.readServiceIdentification_ = function(node,
    objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ServiceIdentification',
      'localName should be ServiceIdentification');
  return ol.xml.pushParseAndPop(
      {}, ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_, node,
      objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The service contact.
 */
ol.format.OWS.readServiceContact_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ServiceContact',
      'localName should be ServiceContact');
  return ol.xml.pushParseAndPop(
      {}, ol.format.OWS.SERVICE_CONTACT_PARSERS_, node,
      objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} The service provider.
 */
ol.format.OWS.readServiceProvider_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ServiceProvider',
      'localName should be ServiceProvider');
  return ol.xml.pushParseAndPop(
      {}, ol.format.OWS.SERVICE_PROVIDER_PARSERS_, node,
      objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {string|undefined} The value.
 */
ol.format.OWS.readValue_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Value', 'localName should be Value');
  return ol.format.XSD.readString(node);
};


/**
 * @const
 * @type {Array.<string>}
 * @private
 */
ol.format.OWS.NAMESPACE_URIS_ = [
  null,
  'http://www.opengis.net/ows/1.1'
];


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'ServiceIdentification': ol.xml.makeObjectPropertySetter(
          ol.format.OWS.readServiceIdentification_),
      'ServiceProvider': ol.xml.makeObjectPropertySetter(
          ol.format.OWS.readServiceProvider_),
      'OperationsMetadata': ol.xml.makeObjectPropertySetter(
          ol.format.OWS.readOperationsMetadata_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'DeliveryPoint': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'AdministrativeArea': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'PostalCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'ElectronicMailAddress': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.ALLOWED_VALUES_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Value': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readValue_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.CONSTRAINT_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'AllowedValues': ol.xml.makeObjectPropertySetter(
          ol.format.OWS.readAllowedValues_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.CONTACT_INFO_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Phone': ol.xml.makeObjectPropertySetter(ol.format.OWS.readPhone_),
      'Address': ol.xml.makeObjectPropertySetter(ol.format.OWS.readAddress_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.DCP_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'HTTP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readHttp_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.HTTP_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Get': ol.xml.makeObjectPropertyPusher(ol.format.OWS.readGet_),
      'Post': undefined // TODO
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.OPERATION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'DCP': ol.xml.makeObjectPropertySetter(ol.format.OWS.readDcp_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.OPERATIONS_METADATA_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Operation': ol.format.OWS.readOperation_
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.PHONE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Voice': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Facsimile': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.REQUEST_METHOD_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Constraint': ol.xml.makeObjectPropertyPusher(
          ol.format.OWS.readConstraint_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.SERVICE_CONTACT_PARSERS_ =
    ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'IndividualName': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'PositionName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'ContactInfo': ol.xml.makeObjectPropertySetter(
          ol.format.OWS.readContactInfo_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.SERVICE_IDENTIFICATION_PARSERS_ =
    ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'ServiceTypeVersion': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'ServiceType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.OWS.SERVICE_PROVIDER_PARSERS_ =
    ol.xml.makeStructureNS(
    ol.format.OWS.NAMESPACE_URIS_, {
      'ProviderName': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'ProviderSite': ol.xml.makeObjectPropertySetter(ol.format.XLink.readHref),
      'ServiceContact': ol.xml.makeObjectPropertySetter(
          ol.format.OWS.readServiceContact_)
    });

goog.provide('ol.geom.flat.flip');


/**
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 * @param {number} offset Offset.
 * @param {number} end End.
 * @param {number} stride Stride.
 * @param {Array.<number>=} opt_dest Destination.
 * @param {number=} opt_destOffset Destination offset.
 * @return {Array.<number>} Flat coordinates.
 */
ol.geom.flat.flip.flipXY = function(flatCoordinates, offset, end, stride, opt_dest, opt_destOffset) {
  var dest, destOffset;
  if (opt_dest !== undefined) {
    dest = opt_dest;
    destOffset = opt_destOffset !== undefined ? opt_destOffset : 0;
  } else {
    dest = [];
    destOffset = 0;
  }
  var j = offset;
  while (j < end) {
    var x = flatCoordinates[j++];
    dest[destOffset++] = flatCoordinates[j++];
    dest[destOffset++] = x;
    for (var k = 2; k < stride; ++k) {
      dest[destOffset++] = flatCoordinates[j++];
    }
  }
  dest.length = destOffset;
  return dest;
};

goog.provide('ol.format.Polyline');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.TextFeature');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.flip');
goog.require('ol.geom.flat.inflate');
goog.require('ol.proj');


/**
 * @classdesc
 * Feature format for reading and writing data in the Encoded
 * Polyline Algorithm Format.
 *
 * @constructor
 * @extends {ol.format.TextFeature}
 * @param {olx.format.PolylineOptions=} opt_options
 *     Optional configuration object.
 * @api stable
 */
ol.format.Polyline = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.TextFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get('EPSG:4326');

  /**
   * @private
   * @type {number}
   */
  this.factor_ = options.factor ? options.factor : 1e5;

  /**
   * @private
   * @type {ol.geom.GeometryLayout}
   */
  this.geometryLayout_ = options.geometryLayout ?
      options.geometryLayout : ol.geom.GeometryLayout.XY;
};
ol.inherits(ol.format.Polyline, ol.format.TextFeature);


/**
 * Encode a list of n-dimensional points and return an encoded string
 *
 * Attention: This function will modify the passed array!
 *
 * @param {Array.<number>} numbers A list of n-dimensional points.
 * @param {number} stride The number of dimension of the points in the list.
 * @param {number=} opt_factor The factor by which the numbers will be
 *     multiplied. The remaining decimal places will get rounded away.
 *     Default is `1e5`.
 * @return {string} The encoded string.
 * @api
 */
ol.format.Polyline.encodeDeltas = function(numbers, stride, opt_factor) {
  var factor = opt_factor ? opt_factor : 1e5;
  var d;

  var lastNumbers = new Array(stride);
  for (d = 0; d < stride; ++d) {
    lastNumbers[d] = 0;
  }

  var i, ii;
  for (i = 0, ii = numbers.length; i < ii;) {
    for (d = 0; d < stride; ++d, ++i) {
      var num = numbers[i];
      var delta = num - lastNumbers[d];
      lastNumbers[d] = num;

      numbers[i] = delta;
    }
  }

  return ol.format.Polyline.encodeFloats(numbers, factor);
};


/**
 * Decode a list of n-dimensional points from an encoded string
 *
 * @param {string} encoded An encoded string.
 * @param {number} stride The number of dimension of the points in the
 *     encoded string.
 * @param {number=} opt_factor The factor by which the resulting numbers will
 *     be divided. Default is `1e5`.
 * @return {Array.<number>} A list of n-dimensional points.
 * @api
 */
ol.format.Polyline.decodeDeltas = function(encoded, stride, opt_factor) {
  var factor = opt_factor ? opt_factor : 1e5;
  var d;

  /** @type {Array.<number>} */
  var lastNumbers = new Array(stride);
  for (d = 0; d < stride; ++d) {
    lastNumbers[d] = 0;
  }

  var numbers = ol.format.Polyline.decodeFloats(encoded, factor);

  var i, ii;
  for (i = 0, ii = numbers.length; i < ii;) {
    for (d = 0; d < stride; ++d, ++i) {
      lastNumbers[d] += numbers[i];

      numbers[i] = lastNumbers[d];
    }
  }

  return numbers;
};


/**
 * Encode a list of floating point numbers and return an encoded string
 *
 * Attention: This function will modify the passed array!
 *
 * @param {Array.<number>} numbers A list of floating point numbers.
 * @param {number=} opt_factor The factor by which the numbers will be
 *     multiplied. The remaining decimal places will get rounded away.
 *     Default is `1e5`.
 * @return {string} The encoded string.
 * @api
 */
ol.format.Polyline.encodeFloats = function(numbers, opt_factor) {
  var factor = opt_factor ? opt_factor : 1e5;
  var i, ii;
  for (i = 0, ii = numbers.length; i < ii; ++i) {
    numbers[i] = Math.round(numbers[i] * factor);
  }

  return ol.format.Polyline.encodeSignedIntegers(numbers);
};


/**
 * Decode a list of floating point numbers from an encoded string
 *
 * @param {string} encoded An encoded string.
 * @param {number=} opt_factor The factor by which the result will be divided.
 *     Default is `1e5`.
 * @return {Array.<number>} A list of floating point numbers.
 * @api
 */
ol.format.Polyline.decodeFloats = function(encoded, opt_factor) {
  var factor = opt_factor ? opt_factor : 1e5;
  var numbers = ol.format.Polyline.decodeSignedIntegers(encoded);
  var i, ii;
  for (i = 0, ii = numbers.length; i < ii; ++i) {
    numbers[i] /= factor;
  }
  return numbers;
};


/**
 * Encode a list of signed integers and return an encoded string
 *
 * Attention: This function will modify the passed array!
 *
 * @param {Array.<number>} numbers A list of signed integers.
 * @return {string} The encoded string.
 */
ol.format.Polyline.encodeSignedIntegers = function(numbers) {
  var i, ii;
  for (i = 0, ii = numbers.length; i < ii; ++i) {
    var num = numbers[i];
    numbers[i] = (num < 0) ? ~(num << 1) : (num << 1);
  }
  return ol.format.Polyline.encodeUnsignedIntegers(numbers);
};


/**
 * Decode a list of signed integers from an encoded string
 *
 * @param {string} encoded An encoded string.
 * @return {Array.<number>} A list of signed integers.
 */
ol.format.Polyline.decodeSignedIntegers = function(encoded) {
  var numbers = ol.format.Polyline.decodeUnsignedIntegers(encoded);
  var i, ii;
  for (i = 0, ii = numbers.length; i < ii; ++i) {
    var num = numbers[i];
    numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
  }
  return numbers;
};


/**
 * Encode a list of unsigned integers and return an encoded string
 *
 * @param {Array.<number>} numbers A list of unsigned integers.
 * @return {string} The encoded string.
 */
ol.format.Polyline.encodeUnsignedIntegers = function(numbers) {
  var encoded = '';
  var i, ii;
  for (i = 0, ii = numbers.length; i < ii; ++i) {
    encoded += ol.format.Polyline.encodeUnsignedInteger(numbers[i]);
  }
  return encoded;
};


/**
 * Decode a list of unsigned integers from an encoded string
 *
 * @param {string} encoded An encoded string.
 * @return {Array.<number>} A list of unsigned integers.
 */
ol.format.Polyline.decodeUnsignedIntegers = function(encoded) {
  var numbers = [];
  var current = 0;
  var shift = 0;
  var i, ii;
  for (i = 0, ii = encoded.length; i < ii; ++i) {
    var b = encoded.charCodeAt(i) - 63;
    current |= (b & 0x1f) << shift;
    if (b < 0x20) {
      numbers.push(current);
      current = 0;
      shift = 0;
    } else {
      shift += 5;
    }
  }
  return numbers;
};


/**
 * Encode one single unsigned integer and return an encoded string
 *
 * @param {number} num Unsigned integer that should be encoded.
 * @return {string} The encoded string.
 */
ol.format.Polyline.encodeUnsignedInteger = function(num) {
  var value, encoded = '';
  while (num >= 0x20) {
    value = (0x20 | (num & 0x1f)) + 63;
    encoded += String.fromCharCode(value);
    num >>= 5;
  }
  value = num + 63;
  encoded += String.fromCharCode(value);
  return encoded;
};


/**
 * Read the feature from the Polyline source. The coordinates are assumed to be
 * in two dimensions and in latitude, longitude order.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api stable
 */
ol.format.Polyline.prototype.readFeature;


/**
 * @inheritDoc
 */
ol.format.Polyline.prototype.readFeatureFromText = function(text, opt_options) {
  var geometry = this.readGeometryFromText(text, opt_options);
  return new ol.Feature(geometry);
};


/**
 * Read the feature from the source. As Polyline sources contain a single
 * feature, this will return the feature in an array.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.Polyline.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.Polyline.prototype.readFeaturesFromText = function(text, opt_options) {
  var feature = this.readFeatureFromText(text, opt_options);
  return [feature];
};


/**
 * Read the geometry from the source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.geom.Geometry} Geometry.
 * @api stable
 */
ol.format.Polyline.prototype.readGeometry;


/**
 * @inheritDoc
 */
ol.format.Polyline.prototype.readGeometryFromText = function(text, opt_options) {
  var stride = ol.geom.SimpleGeometry.getStrideForLayout(this.geometryLayout_);
  var flatCoordinates = ol.format.Polyline.decodeDeltas(
      text, stride, this.factor_);
  ol.geom.flat.flip.flipXY(
      flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates);
  var coordinates = ol.geom.flat.inflate.coordinates(
      flatCoordinates, 0, flatCoordinates.length, stride);

  return /** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(
          new ol.geom.LineString(coordinates, this.geometryLayout_), false,
          this.adaptOptions(opt_options)));
};


/**
 * Read the projection from a Polyline source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.Polyline.prototype.readProjection;


/**
 * @inheritDoc
 */
ol.format.Polyline.prototype.writeFeatureText = function(feature, opt_options) {
  var geometry = feature.getGeometry();
  if (geometry) {
    return this.writeGeometryText(geometry, opt_options);
  } else {
    ol.asserts.assert(false, 40); // Expected `feature` to have a geometry
    return '';
  }
};


/**
 * @inheritDoc
 */
ol.format.Polyline.prototype.writeFeaturesText = function(features, opt_options) {
  goog.DEBUG && console.assert(features.length == 1,
      'features array should have 1 item');
  return this.writeFeatureText(features[0], opt_options);
};


/**
 * Write a single geometry in Polyline format.
 *
 * @function
 * @param {ol.geom.Geometry} geometry Geometry.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} Geometry.
 * @api stable
 */
ol.format.Polyline.prototype.writeGeometry;


/**
 * @inheritDoc
 */
ol.format.Polyline.prototype.writeGeometryText = function(geometry, opt_options) {
  geometry = /** @type {ol.geom.LineString} */
      (ol.format.Feature.transformWithOptions(
          geometry, true, this.adaptOptions(opt_options)));
  var flatCoordinates = geometry.getFlatCoordinates();
  var stride = geometry.getStride();
  ol.geom.flat.flip.flipXY(
      flatCoordinates, 0, flatCoordinates.length, stride, flatCoordinates);
  return ol.format.Polyline.encodeDeltas(flatCoordinates, stride, this.factor_);
};

goog.provide('ol.format.TopoJSON');

goog.require('ol');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.JSONFeature');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.obj');
goog.require('ol.proj');


/**
 * @classdesc
 * Feature format for reading data in the TopoJSON format.
 *
 * @constructor
 * @extends {ol.format.JSONFeature}
 * @param {olx.format.TopoJSONOptions=} opt_options Options.
 * @api stable
 */
ol.format.TopoJSON = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.JSONFeature.call(this);

  /**
   * @inheritDoc
   */
  this.defaultDataProjection = ol.proj.get(
      options.defaultDataProjection ?
          options.defaultDataProjection : 'EPSG:4326');

};
ol.inherits(ol.format.TopoJSON, ol.format.JSONFeature);


/**
 * @const {Array.<string>}
 * @private
 */
ol.format.TopoJSON.EXTENSIONS_ = ['.topojson'];


/**
 * Concatenate arcs into a coordinate array.
 * @param {Array.<number>} indices Indices of arcs to concatenate.  Negative
 *     values indicate arcs need to be reversed.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs (already
 *     transformed).
 * @return {Array.<ol.Coordinate>} Coordinates array.
 * @private
 */
ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) {
  /** @type {Array.<ol.Coordinate>} */
  var coordinates = [];
  var index, arc;
  var i, ii;
  var j, jj;
  for (i = 0, ii = indices.length; i < ii; ++i) {
    index = indices[i];
    if (i > 0) {
      // splicing together arcs, discard last point
      coordinates.pop();
    }
    if (index >= 0) {
      // forward arc
      arc = arcs[index];
    } else {
      // reverse arc
      arc = arcs[~index].slice().reverse();
    }
    coordinates.push.apply(coordinates, arc);
  }
  // provide fresh copies of coordinate arrays
  for (j = 0, jj = coordinates.length; j < jj; ++j) {
    coordinates[j] = coordinates[j].slice();
  }
  return coordinates;
};


/**
 * Create a point from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON object.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @return {ol.geom.Point} Geometry.
 * @private
 */
ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) {
  var coordinates = object.coordinates;
  if (scale && translate) {
    ol.format.TopoJSON.transformVertex_(coordinates, scale, translate);
  }
  return new ol.geom.Point(coordinates);
};


/**
 * Create a multi-point from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON object.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @return {ol.geom.MultiPoint} Geometry.
 * @private
 */
ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale,
    translate) {
  var coordinates = object.coordinates;
  var i, ii;
  if (scale && translate) {
    for (i = 0, ii = coordinates.length; i < ii; ++i) {
      ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate);
    }
  }
  return new ol.geom.MultiPoint(coordinates);
};


/**
 * Create a linestring from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON object.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @return {ol.geom.LineString} Geometry.
 * @private
 */
ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) {
  var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs);
  return new ol.geom.LineString(coordinates);
};


/**
 * Create a multi-linestring from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON object.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @return {ol.geom.MultiLineString} Geometry.
 * @private
 */
ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) {
  var coordinates = [];
  var i, ii;
  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
  }
  return new ol.geom.MultiLineString(coordinates);
};


/**
 * Create a polygon from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON object.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @return {ol.geom.Polygon} Geometry.
 * @private
 */
ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) {
  var coordinates = [];
  var i, ii;
  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
    coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs);
  }
  return new ol.geom.Polygon(coordinates);
};


/**
 * Create a multi-polygon from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON object.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @return {ol.geom.MultiPolygon} Geometry.
 * @private
 */
ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) {
  var coordinates = [];
  var polyArray, ringCoords, j, jj;
  var i, ii;
  for (i = 0, ii = object.arcs.length; i < ii; ++i) {
    // for each polygon
    polyArray = object.arcs[i];
    ringCoords = [];
    for (j = 0, jj = polyArray.length; j < jj; ++j) {
      // for each ring
      ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs);
    }
    coordinates[i] = ringCoords;
  }
  return new ol.geom.MultiPolygon(coordinates);
};


/**
 * @inheritDoc
 */
ol.format.TopoJSON.prototype.getExtensions = function() {
  return ol.format.TopoJSON.EXTENSIONS_;
};


/**
 * Create features from a TopoJSON GeometryCollection object.
 *
 * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry
 *     object.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Array of features.
 * @private
 */
ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function(
    collection, arcs, scale, translate, opt_options) {
  var geometries = collection.geometries;
  var features = [];
  var i, ii;
  for (i = 0, ii = geometries.length; i < ii; ++i) {
    features[i] = ol.format.TopoJSON.readFeatureFromGeometry_(
        geometries[i], arcs, scale, translate, opt_options);
  }
  return features;
};


/**
 * Create a feature from a TopoJSON geometry object.
 *
 * @param {TopoJSONGeometry} object TopoJSON geometry object.
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @private
 */
ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs,
    scale, translate, opt_options) {
  var geometry;
  var type = object.type;
  var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type];
  if ((type === 'Point') || (type === 'MultiPoint')) {
    geometry = geometryReader(object, scale, translate);
  } else {
    geometry = geometryReader(object, arcs);
  }
  var feature = new ol.Feature();
  feature.setGeometry(/** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(geometry, false, opt_options)));
  if (object.id !== undefined) {
    feature.setId(object.id);
  }
  if (object.properties) {
    feature.setProperties(object.properties);
  }
  return feature;
};


/**
 * Read all features from a TopoJSON source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.TopoJSON.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.TopoJSON.prototype.readFeaturesFromObject = function(
    object, opt_options) {
  if (object.type == 'Topology') {
    var topoJSONTopology = /** @type {TopoJSONTopology} */ (object);
    var transform, scale = null, translate = null;
    if (topoJSONTopology.transform) {
      transform = topoJSONTopology.transform;
      scale = transform.scale;
      translate = transform.translate;
    }
    var arcs = topoJSONTopology.arcs;
    if (transform) {
      ol.format.TopoJSON.transformArcs_(arcs, scale, translate);
    }
    /** @type {Array.<ol.Feature>} */
    var features = [];
    var topoJSONFeatures = ol.obj.getValues(topoJSONTopology.objects);
    var i, ii;
    var feature;
    for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) {
      if (topoJSONFeatures[i].type === 'GeometryCollection') {
        feature = /** @type {TopoJSONGeometryCollection} */
            (topoJSONFeatures[i]);
        features.push.apply(features,
            ol.format.TopoJSON.readFeaturesFromGeometryCollection_(
                feature, arcs, scale, translate, opt_options));
      } else {
        feature = /** @type {TopoJSONGeometry} */
            (topoJSONFeatures[i]);
        features.push(ol.format.TopoJSON.readFeatureFromGeometry_(
            feature, arcs, scale, translate, opt_options));
      }
    }
    return features;
  } else {
    return [];
  }
};


/**
 * Apply a linear transform to array of arcs.  The provided array of arcs is
 * modified in place.
 *
 * @param {Array.<Array.<ol.Coordinate>>} arcs Array of arcs.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @private
 */
ol.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) {
  var i, ii;
  for (i = 0, ii = arcs.length; i < ii; ++i) {
    ol.format.TopoJSON.transformArc_(arcs[i], scale, translate);
  }
};


/**
 * Apply a linear transform to an arc.  The provided arc is modified in place.
 *
 * @param {Array.<ol.Coordinate>} arc Arc.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @private
 */
ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) {
  var x = 0;
  var y = 0;
  var vertex;
  var i, ii;
  for (i = 0, ii = arc.length; i < ii; ++i) {
    vertex = arc[i];
    x += vertex[0];
    y += vertex[1];
    vertex[0] = x;
    vertex[1] = y;
    ol.format.TopoJSON.transformVertex_(vertex, scale, translate);
  }
};


/**
 * Apply a linear transform to a vertex.  The provided vertex is modified in
 * place.
 *
 * @param {ol.Coordinate} vertex Vertex.
 * @param {Array.<number>} scale Scale for each dimension.
 * @param {Array.<number>} translate Translation for each dimension.
 * @private
 */
ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) {
  vertex[0] = vertex[0] * scale[0] + translate[0];
  vertex[1] = vertex[1] * scale[1] + translate[1];
};


/**
 * Read the projection from a TopoJSON source.
 *
 * @function
 * @param {Document|Node|Object|string} object Source.
 * @return {ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.TopoJSON.prototype.readProjection = function(object) {
  return this.defaultDataProjection;
};


/**
 * @const
 * @private
 * @type {Object.<string, function(TopoJSONGeometry, Array, ...Array): ol.geom.Geometry>}
 */
ol.format.TopoJSON.GEOMETRY_READERS_ = {
  'Point': ol.format.TopoJSON.readPointGeometry_,
  'LineString': ol.format.TopoJSON.readLineStringGeometry_,
  'Polygon': ol.format.TopoJSON.readPolygonGeometry_,
  'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_,
  'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_,
  'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_
};

goog.provide('ol.format.WFS');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.format.GML3');
goog.require('ol.format.GMLBase');
goog.require('ol.format.ogc.filter');
goog.require('ol.format.XMLFeature');
goog.require('ol.format.XSD');
goog.require('ol.geom.Geometry');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.xml');


/**
 * @classdesc
 * Feature format for reading and writing data in the WFS format.
 * By default, supports WFS version 1.1.0. You can pass a GML format
 * as option if you want to read a WFS that contains GML2 (WFS 1.0.0).
 * Also see {@link ol.format.GMLBase} which is used by this format.
 *
 * @constructor
 * @param {olx.format.WFSOptions=} opt_options
 *     Optional configuration object.
 * @extends {ol.format.XMLFeature}
 * @api stable
 */
ol.format.WFS = function(opt_options) {
  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {Array.<string>|string|undefined}
   */
  this.featureType_ = options.featureType;

  /**
   * @private
   * @type {Object.<string, string>|string|undefined}
   */
  this.featureNS_ = options.featureNS;

  /**
   * @private
   * @type {ol.format.GMLBase}
   */
  this.gmlFormat_ = options.gmlFormat ?
      options.gmlFormat : new ol.format.GML3();

  /**
   * @private
   * @type {string}
   */
  this.schemaLocation_ = options.schemaLocation ?
      options.schemaLocation : ol.format.WFS.SCHEMA_LOCATION;

  ol.format.XMLFeature.call(this);
};
ol.inherits(ol.format.WFS, ol.format.XMLFeature);


/**
 * @const
 * @type {string}
 */
ol.format.WFS.FEATURE_PREFIX = 'feature';


/**
 * @const
 * @type {string}
 */
ol.format.WFS.XMLNS = 'http://www.w3.org/2000/xmlns/';


/**
 * @const
 * @type {string}
 */
ol.format.WFS.SCHEMA_LOCATION = 'http://www.opengis.net/wfs ' +
    'http://schemas.opengis.net/wfs/1.1.0/wfs.xsd';


/**
 * Read all features from a WFS FeatureCollection.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.WFS.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.WFS.prototype.readFeaturesFromNode = function(node, opt_options) {
  var context = /** @type {ol.XmlNodeStackItem} */ ({
    'featureType': this.featureType_,
    'featureNS': this.featureNS_
  });
  ol.obj.assign(context, this.getReadOptions(node,
      opt_options ? opt_options : {}));
  var objectStack = [context];
  this.gmlFormat_.FEATURE_COLLECTION_PARSERS[ol.format.GMLBase.GMLNS][
      'featureMember'] =
      ol.xml.makeArrayPusher(ol.format.GMLBase.prototype.readFeaturesInternal);
  var features = ol.xml.pushParseAndPop([],
      this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
      objectStack, this.gmlFormat_);
  if (!features) {
    features = [];
  }
  return features;
};


/**
 * Read transaction response of the source.
 *
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.WFSTransactionResponse|undefined} Transaction response.
 * @api stable
 */
ol.format.WFS.prototype.readTransactionResponse = function(source) {
  if (ol.xml.isDocument(source)) {
    return this.readTransactionResponseFromDocument(
        /** @type {Document} */ (source));
  } else if (ol.xml.isNode(source)) {
    return this.readTransactionResponseFromNode(/** @type {Node} */ (source));
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readTransactionResponseFromDocument(doc);
  } else {
    return undefined;
  }
};


/**
 * Read feature collection metadata of the source.
 *
 * @param {Document|Node|Object|string} source Source.
 * @return {ol.WFSFeatureCollectionMetadata|undefined}
 *     FeatureCollection metadata.
 * @api stable
 */
ol.format.WFS.prototype.readFeatureCollectionMetadata = function(source) {
  if (ol.xml.isDocument(source)) {
    return this.readFeatureCollectionMetadataFromDocument(
        /** @type {Document} */ (source));
  } else if (ol.xml.isNode(source)) {
    return this.readFeatureCollectionMetadataFromNode(
        /** @type {Node} */ (source));
  } else if (typeof source === 'string') {
    var doc = ol.xml.parse(source);
    return this.readFeatureCollectionMetadataFromDocument(doc);
  } else {
    return undefined;
  }
};


/**
 * @param {Document} doc Document.
 * @return {ol.WFSFeatureCollectionMetadata|undefined}
 *     FeatureCollection metadata.
 */
ol.format.WFS.prototype.readFeatureCollectionMetadataFromDocument = function(doc) {
  goog.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
      'doc.nodeType should be DOCUMENT');
  for (var n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readFeatureCollectionMetadataFromNode(n);
    }
  }
  return undefined;
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFS.FEATURE_COLLECTION_PARSERS_ = {
  'http://www.opengis.net/gml': {
    'boundedBy': ol.xml.makeObjectPropertySetter(
        ol.format.GMLBase.prototype.readGeometryElement, 'bounds')
  }
};


/**
 * @param {Node} node Node.
 * @return {ol.WFSFeatureCollectionMetadata|undefined}
 *     FeatureCollection metadata.
 */
ol.format.WFS.prototype.readFeatureCollectionMetadataFromNode = function(node) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'FeatureCollection',
      'localName should be FeatureCollection');
  var result = {};
  var value = ol.format.XSD.readNonNegativeIntegerString(
      node.getAttribute('numberOfFeatures'));
  result['numberOfFeatures'] = value;
  return ol.xml.pushParseAndPop(
      /** @type {ol.WFSFeatureCollectionMetadata} */ (result),
      ol.format.WFS.FEATURE_COLLECTION_PARSERS_, node, [], this.gmlFormat_);
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_ = {
  'http://www.opengis.net/wfs': {
    'totalInserted': ol.xml.makeObjectPropertySetter(
        ol.format.XSD.readNonNegativeInteger),
    'totalUpdated': ol.xml.makeObjectPropertySetter(
        ol.format.XSD.readNonNegativeInteger),
    'totalDeleted': ol.xml.makeObjectPropertySetter(
        ol.format.XSD.readNonNegativeInteger)
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Transaction Summary.
 * @private
 */
ol.format.WFS.readTransactionSummary_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(
      {}, ol.format.WFS.TRANSACTION_SUMMARY_PARSERS_, node, objectStack);
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFS.OGC_FID_PARSERS_ = {
  'http://www.opengis.net/ogc': {
    'FeatureId': ol.xml.makeArrayPusher(function(node, objectStack) {
      return node.getAttribute('fid');
    })
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 */
ol.format.WFS.fidParser_ = function(node, objectStack) {
  ol.xml.parseNode(ol.format.WFS.OGC_FID_PARSERS_, node, objectStack);
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFS.INSERT_RESULTS_PARSERS_ = {
  'http://www.opengis.net/wfs': {
    'Feature': ol.format.WFS.fidParser_
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Array.<string>|undefined} Insert results.
 * @private
 */
ol.format.WFS.readInsertResults_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop(
      [], ol.format.WFS.INSERT_RESULTS_PARSERS_, node, objectStack);
};


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_ = {
  'http://www.opengis.net/wfs': {
    'TransactionSummary': ol.xml.makeObjectPropertySetter(
        ol.format.WFS.readTransactionSummary_, 'transactionSummary'),
    'InsertResults': ol.xml.makeObjectPropertySetter(
        ol.format.WFS.readInsertResults_, 'insertIds')
  }
};


/**
 * @param {Document} doc Document.
 * @return {ol.WFSTransactionResponse|undefined} Transaction response.
 */
ol.format.WFS.prototype.readTransactionResponseFromDocument = function(doc) {
  goog.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
      'doc.nodeType should be DOCUMENT');
  for (var n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readTransactionResponseFromNode(n);
    }
  }
  return undefined;
};


/**
 * @param {Node} node Node.
 * @return {ol.WFSTransactionResponse|undefined} Transaction response.
 */
ol.format.WFS.prototype.readTransactionResponseFromNode = function(node) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should  be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'TransactionResponse',
      'localName should be TransactionResponse');
  return ol.xml.pushParseAndPop(
      /** @type {ol.WFSTransactionResponse} */({}),
      ol.format.WFS.TRANSACTION_RESPONSE_PARSERS_, node, []);
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.WFS.QUERY_SERIALIZERS_ = {
  'http://www.opengis.net/wfs': {
    'PropertyName': ol.xml.makeChildAppender(ol.format.XSD.writeStringTextNode)
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeFeature_ = function(node, feature, objectStack) {
  var context = objectStack[objectStack.length - 1];
  var featureType = context['featureType'];
  var featureNS = context['featureNS'];
  var child = ol.xml.createElementNS(featureNS, featureType);
  node.appendChild(child);
  ol.format.GML3.prototype.writeFeatureElement(child, feature, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {number|string} fid Feature identifier.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeOgcFidFilter_ = function(node, fid, objectStack) {
  var filter = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
  var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'FeatureId');
  filter.appendChild(child);
  child.setAttribute('fid', fid);
  node.appendChild(filter);
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeDelete_ = function(node, feature, objectStack) {
  var context = objectStack[objectStack.length - 1];
  ol.asserts.assert(feature.getId() !== undefined, 26); // Features must have an id set
  var featureType = context['featureType'];
  var featurePrefix = context['featurePrefix'];
  featurePrefix = featurePrefix ? featurePrefix :
      ol.format.WFS.FEATURE_PREFIX;
  var featureNS = context['featureNS'];
  node.setAttribute('typeName', featurePrefix + ':' + featureType);
  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
      featureNS);
  var fid = feature.getId();
  if (fid !== undefined) {
    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.Feature} feature Feature.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeUpdate_ = function(node, feature, objectStack) {
  var context = objectStack[objectStack.length - 1];
  ol.asserts.assert(feature.getId() !== undefined, 27); // Features must have an id set
  var featureType = context['featureType'];
  var featurePrefix = context['featurePrefix'];
  featurePrefix = featurePrefix ? featurePrefix :
      ol.format.WFS.FEATURE_PREFIX;
  var featureNS = context['featureNS'];
  node.setAttribute('typeName', featurePrefix + ':' + featureType);
  ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
      featureNS);
  var fid = feature.getId();
  if (fid !== undefined) {
    var keys = feature.getKeys();
    var values = [];
    for (var i = 0, ii = keys.length; i < ii; i++) {
      var value = feature.get(keys[i]);
      if (value !== undefined) {
        values.push({name: keys[i], value: value});
      }
    }
    ol.xml.pushSerializeAndPop(/** @type {ol.XmlNodeStackItem} */ (
        {node: node, 'srsName': context['srsName']}),
        ol.format.WFS.TRANSACTION_SERIALIZERS_,
        ol.xml.makeSimpleNodeFactory('Property'), values,
        objectStack);
    ol.format.WFS.writeOgcFidFilter_(node, fid, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {Object} pair Property name and value.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeProperty_ = function(node, pair, objectStack) {
  var name = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Name');
  node.appendChild(name);
  ol.format.XSD.writeStringTextNode(name, pair.name);
  if (pair.value !== undefined && pair.value !== null) {
    var value = ol.xml.createElementNS('http://www.opengis.net/wfs', 'Value');
    node.appendChild(value);
    if (pair.value instanceof ol.geom.Geometry) {
      ol.format.GML3.prototype.writeGeometryElement(value,
          pair.value, objectStack);
    } else {
      ol.format.XSD.writeStringTextNode(value, pair.value);
    }
  }
};


/**
 * @param {Node} node Node.
 * @param {{vendorId: string, safeToIgnore: boolean, value: string}}
 *     nativeElement The native element.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeNative_ = function(node, nativeElement, objectStack) {
  if (nativeElement.vendorId) {
    node.setAttribute('vendorId', nativeElement.vendorId);
  }
  if (nativeElement.safeToIgnore !== undefined) {
    node.setAttribute('safeToIgnore', nativeElement.safeToIgnore);
  }
  if (nativeElement.value !== undefined) {
    ol.format.XSD.writeStringTextNode(node, nativeElement.value);
  }
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.WFS.TRANSACTION_SERIALIZERS_ = {
  'http://www.opengis.net/wfs': {
    'Insert': ol.xml.makeChildAppender(ol.format.WFS.writeFeature_),
    'Update': ol.xml.makeChildAppender(ol.format.WFS.writeUpdate_),
    'Delete': ol.xml.makeChildAppender(ol.format.WFS.writeDelete_),
    'Property': ol.xml.makeChildAppender(ol.format.WFS.writeProperty_),
    'Native': ol.xml.makeChildAppender(ol.format.WFS.writeNative_)
  }
};


/**
 * @param {Node} node Node.
 * @param {string} featureType Feature type.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeQuery_ = function(node, featureType, objectStack) {
  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var featurePrefix = context['featurePrefix'];
  var featureNS = context['featureNS'];
  var propertyNames = context['propertyNames'];
  var srsName = context['srsName'];
  var prefix = featurePrefix ? featurePrefix + ':' : '';
  node.setAttribute('typeName', prefix + featureType);
  if (srsName) {
    node.setAttribute('srsName', srsName);
  }
  if (featureNS) {
    ol.xml.setAttributeNS(node, ol.format.WFS.XMLNS, 'xmlns:' + featurePrefix,
        featureNS);
  }
  var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context));
  item.node = node;
  ol.xml.pushSerializeAndPop(item,
      ol.format.WFS.QUERY_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory('PropertyName'), propertyNames,
      objectStack);
  var filter = context['filter'];
  if (filter) {
    var child = ol.xml.createElementNS('http://www.opengis.net/ogc', 'Filter');
    node.appendChild(child);
    ol.format.WFS.writeFilterCondition_(child, filter, objectStack);
  }
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.Filter} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeFilterCondition_ = function(node, filter, objectStack) {
  /** @type {ol.XmlNodeStackItem} */
  var item = {node: node};
  ol.xml.pushSerializeAndPop(item,
      ol.format.WFS.GETFEATURE_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory(filter.getTagName()),
      [filter], objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.Bbox} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeBboxFilter_ = function(node, filter, objectStack) {
  var context = objectStack[objectStack.length - 1];
  context['srsName'] = filter.srsName;

  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
  ol.format.GML3.prototype.writeGeometryElement(node, filter.extent, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.Intersects} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeIntersectsFilter_ = function(node, filter, objectStack) {
  var context = objectStack[objectStack.length - 1];
  context['srsName'] = filter.srsName;

  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.Within} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeWithinFilter_ = function(node, filter, objectStack) {
  var context = objectStack[objectStack.length - 1];
  context['srsName'] = filter.srsName;

  ol.format.WFS.writeOgcPropertyName_(node, filter.geometryName);
  ol.format.GML3.prototype.writeGeometryElement(node, filter.geometry, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.LogicalBinary} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeLogicalFilter_ = function(node, filter, objectStack) {
  /** @type {ol.XmlNodeStackItem} */
  var item = {node: node};
  var conditionA = filter.conditionA;
  ol.xml.pushSerializeAndPop(item,
      ol.format.WFS.GETFEATURE_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory(conditionA.getTagName()),
      [conditionA], objectStack);
  var conditionB = filter.conditionB;
  ol.xml.pushSerializeAndPop(item,
      ol.format.WFS.GETFEATURE_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory(conditionB.getTagName()),
      [conditionB], objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.Not} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeNotFilter_ = function(node, filter, objectStack) {
  /** @type {ol.XmlNodeStackItem} */
  var item = {node: node};
  var condition = filter.condition;
  ol.xml.pushSerializeAndPop(item,
      ol.format.WFS.GETFEATURE_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory(condition.getTagName()),
      [condition], objectStack);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.ComparisonBinary} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeComparisonFilter_ = function(node, filter, objectStack) {
  if (filter.matchCase !== undefined) {
    node.setAttribute('matchCase', filter.matchCase.toString());
  }
  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
  ol.format.WFS.writeOgcLiteral_(node, '' + filter.expression);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.IsNull} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeIsNullFilter_ = function(node, filter, objectStack) {
  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.IsBetween} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeIsBetweenFilter_ = function(node, filter, objectStack) {
  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
  ol.format.WFS.writeOgcExpression_('LowerBoundary', node, '' + filter.lowerBoundary);
  ol.format.WFS.writeOgcExpression_('UpperBoundary', node, '' + filter.upperBoundary);
};


/**
 * @param {Node} node Node.
 * @param {ol.format.ogc.filter.IsLike} filter Filter.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeIsLikeFilter_ = function(node, filter, objectStack) {
  node.setAttribute('wildCard', filter.wildCard);
  node.setAttribute('singleChar', filter.singleChar);
  node.setAttribute('escapeChar', filter.escapeChar);
  if (filter.matchCase !== undefined) {
    node.setAttribute('matchCase', filter.matchCase.toString());
  }
  ol.format.WFS.writeOgcPropertyName_(node, filter.propertyName);
  ol.format.WFS.writeOgcLiteral_(node, '' + filter.pattern);
};


/**
 * @param {string} tagName Tag name.
 * @param {Node} node Node.
 * @param {string} value Value.
 * @private
 */
ol.format.WFS.writeOgcExpression_ = function(tagName, node, value) {
  var property = ol.xml.createElementNS('http://www.opengis.net/ogc', tagName);
  ol.format.XSD.writeStringTextNode(property, value);
  node.appendChild(property);
};


/**
 * @param {Node} node Node.
 * @param {string} value PropertyName value.
 * @private
 */
ol.format.WFS.writeOgcPropertyName_ = function(node, value) {
  ol.format.WFS.writeOgcExpression_('PropertyName', node, value);
};


/**
 * @param {Node} node Node.
 * @param {string} value PropertyName value.
 * @private
 */
ol.format.WFS.writeOgcLiteral_ = function(node, value) {
  ol.format.WFS.writeOgcExpression_('Literal', node, value);
};


/**
 * @type {Object.<string, Object.<string, ol.XmlSerializer>>}
 * @private
 */
ol.format.WFS.GETFEATURE_SERIALIZERS_ = {
  'http://www.opengis.net/wfs': {
    'Query': ol.xml.makeChildAppender(ol.format.WFS.writeQuery_)
  },
  'http://www.opengis.net/ogc': {
    'And': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_),
    'Or': ol.xml.makeChildAppender(ol.format.WFS.writeLogicalFilter_),
    'Not': ol.xml.makeChildAppender(ol.format.WFS.writeNotFilter_),
    'BBOX': ol.xml.makeChildAppender(ol.format.WFS.writeBboxFilter_),
    'Intersects': ol.xml.makeChildAppender(ol.format.WFS.writeIntersectsFilter_),
    'Within': ol.xml.makeChildAppender(ol.format.WFS.writeWithinFilter_),
    'PropertyIsEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
    'PropertyIsNotEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
    'PropertyIsLessThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
    'PropertyIsLessThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
    'PropertyIsGreaterThan': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
    'PropertyIsGreaterThanOrEqualTo': ol.xml.makeChildAppender(ol.format.WFS.writeComparisonFilter_),
    'PropertyIsNull': ol.xml.makeChildAppender(ol.format.WFS.writeIsNullFilter_),
    'PropertyIsBetween': ol.xml.makeChildAppender(ol.format.WFS.writeIsBetweenFilter_),
    'PropertyIsLike': ol.xml.makeChildAppender(ol.format.WFS.writeIsLikeFilter_)
  }
};


/**
 * @param {Node} node Node.
 * @param {Array.<string>} featureTypes Feature types.
 * @param {Array.<*>} objectStack Node stack.
 * @private
 */
ol.format.WFS.writeGetFeature_ = function(node, featureTypes, objectStack) {
  var context = /** @type {Object} */ (objectStack[objectStack.length - 1]);
  var item = /** @type {ol.XmlNodeStackItem} */ (ol.obj.assign({}, context));
  item.node = node;
  ol.xml.pushSerializeAndPop(item,
      ol.format.WFS.GETFEATURE_SERIALIZERS_,
      ol.xml.makeSimpleNodeFactory('Query'), featureTypes,
      objectStack);
};


/**
 * Encode format as WFS `GetFeature` and return the Node.
 *
 * @param {olx.format.WFSWriteGetFeatureOptions} options Options.
 * @return {Node} Result.
 * @api stable
 */
ol.format.WFS.prototype.writeGetFeature = function(options) {
  var node = ol.xml.createElementNS('http://www.opengis.net/wfs',
      'GetFeature');
  node.setAttribute('service', 'WFS');
  node.setAttribute('version', '1.1.0');
  var filter;
  if (options) {
    if (options.handle) {
      node.setAttribute('handle', options.handle);
    }
    if (options.outputFormat) {
      node.setAttribute('outputFormat', options.outputFormat);
    }
    if (options.maxFeatures !== undefined) {
      node.setAttribute('maxFeatures', options.maxFeatures);
    }
    if (options.resultType) {
      node.setAttribute('resultType', options.resultType);
    }
    if (options.startIndex !== undefined) {
      node.setAttribute('startIndex', options.startIndex);
    }
    if (options.count !== undefined) {
      node.setAttribute('count', options.count);
    }
    filter = options.filter;
    if (options.bbox) {
      ol.asserts.assert(options.geometryName,
          12); // `options.geometryName` must also be provided when `options.bbox` is set
      var bbox = ol.format.ogc.filter.bbox(
          /** @type {string} */ (options.geometryName), options.bbox, options.srsName);
      if (filter) {
        // if bbox and filter are both set, combine the two into a single filter
        filter = ol.format.ogc.filter.and(filter, bbox);
      } else {
        filter = bbox;
      }
    }
  }
  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
      'xsi:schemaLocation', this.schemaLocation_);
  /** @type {ol.XmlNodeStackItem} */
  var context = {
    node: node,
    'srsName': options.srsName,
    'featureNS': options.featureNS ? options.featureNS : this.featureNS_,
    'featurePrefix': options.featurePrefix,
    'geometryName': options.geometryName,
    'filter': filter,
    'propertyNames': options.propertyNames ? options.propertyNames : []
  };
  ol.asserts.assert(Array.isArray(options.featureTypes),
      11); // `options.featureTypes` should be an Array
  ol.format.WFS.writeGetFeature_(node, /** @type {!Array.<string>} */ (options.featureTypes), [context]);
  return node;
};


/**
 * Encode format as WFS `Transaction` and return the Node.
 *
 * @param {Array.<ol.Feature>} inserts The features to insert.
 * @param {Array.<ol.Feature>} updates The features to update.
 * @param {Array.<ol.Feature>} deletes The features to delete.
 * @param {olx.format.WFSWriteTransactionOptions} options Write options.
 * @return {Node} Result.
 * @api stable
 */
ol.format.WFS.prototype.writeTransaction = function(inserts, updates, deletes,
    options) {
  var objectStack = [];
  var node = ol.xml.createElementNS('http://www.opengis.net/wfs',
      'Transaction');
  node.setAttribute('service', 'WFS');
  node.setAttribute('version', '1.1.0');
  var baseObj;
  /** @type {ol.XmlNodeStackItem} */
  var obj;
  if (options) {
    baseObj = options.gmlOptions ? options.gmlOptions : {};
    if (options.handle) {
      node.setAttribute('handle', options.handle);
    }
  }
  ol.xml.setAttributeNS(node, 'http://www.w3.org/2001/XMLSchema-instance',
      'xsi:schemaLocation', this.schemaLocation_);
  if (inserts) {
    obj = {node: node, 'featureNS': options.featureNS,
      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
      'srsName': options.srsName};
    ol.obj.assign(obj, baseObj);
    ol.xml.pushSerializeAndPop(obj,
        ol.format.WFS.TRANSACTION_SERIALIZERS_,
        ol.xml.makeSimpleNodeFactory('Insert'), inserts,
        objectStack);
  }
  if (updates) {
    obj = {node: node, 'featureNS': options.featureNS,
      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
      'srsName': options.srsName};
    ol.obj.assign(obj, baseObj);
    ol.xml.pushSerializeAndPop(obj,
        ol.format.WFS.TRANSACTION_SERIALIZERS_,
        ol.xml.makeSimpleNodeFactory('Update'), updates,
        objectStack);
  }
  if (deletes) {
    ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS,
      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
      'srsName': options.srsName},
    ol.format.WFS.TRANSACTION_SERIALIZERS_,
    ol.xml.makeSimpleNodeFactory('Delete'), deletes,
    objectStack);
  }
  if (options.nativeElements) {
    ol.xml.pushSerializeAndPop({node: node, 'featureNS': options.featureNS,
      'featureType': options.featureType, 'featurePrefix': options.featurePrefix,
      'srsName': options.srsName},
    ol.format.WFS.TRANSACTION_SERIALIZERS_,
    ol.xml.makeSimpleNodeFactory('Native'), options.nativeElements,
    objectStack);
  }
  return node;
};


/**
 * Read the projection from a WFS source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @return {?ol.proj.Projection} Projection.
 * @api stable
 */
ol.format.WFS.prototype.readProjection;


/**
 * @inheritDoc
 */
ol.format.WFS.prototype.readProjectionFromDocument = function(doc) {
  goog.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
      'doc.nodeType should be a DOCUMENT');
  for (var n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readProjectionFromNode(n);
    }
  }
  return null;
};


/**
 * @inheritDoc
 */
ol.format.WFS.prototype.readProjectionFromNode = function(node) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'FeatureCollection',
      'localName should be FeatureCollection');

  if (node.firstElementChild &&
      node.firstElementChild.firstElementChild) {
    node = node.firstElementChild.firstElementChild;
    for (var n = node.firstElementChild; n; n = n.nextElementSibling) {
      if (!(n.childNodes.length === 0 ||
          (n.childNodes.length === 1 &&
          n.firstChild.nodeType === 3))) {
        var objectStack = [{}];
        this.gmlFormat_.readGeometryElement(n, objectStack);
        return ol.proj.get(objectStack.pop().srsName);
      }
    }
  }

  return null;
};

goog.provide('ol.format.WKT');

goog.require('ol');
goog.require('ol.Feature');
goog.require('ol.format.Feature');
goog.require('ol.format.TextFeature');
goog.require('ol.geom.GeometryCollection');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');


/**
 * @classdesc
 * Geometry format for reading and writing data in the `WellKnownText` (WKT)
 * format.
 *
 * @constructor
 * @extends {ol.format.TextFeature}
 * @param {olx.format.WKTOptions=} opt_options Options.
 * @api stable
 */
ol.format.WKT = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.format.TextFeature.call(this);

  /**
   * Split GeometryCollection into multiple features.
   * @type {boolean}
   * @private
   */
  this.splitCollection_ = options.splitCollection !== undefined ?
      options.splitCollection : false;

};
ol.inherits(ol.format.WKT, ol.format.TextFeature);


/**
 * @const
 * @type {string}
 */
ol.format.WKT.EMPTY = 'EMPTY';


/**
 * @param {ol.geom.Point} geom Point geometry.
 * @return {string} Coordinates part of Point as WKT.
 * @private
 */
ol.format.WKT.encodePointGeometry_ = function(geom) {
  var coordinates = geom.getCoordinates();
  if (coordinates.length === 0) {
    return '';
  }
  return coordinates[0] + ' ' + coordinates[1];
};


/**
 * @param {ol.geom.MultiPoint} geom MultiPoint geometry.
 * @return {string} Coordinates part of MultiPoint as WKT.
 * @private
 */
ol.format.WKT.encodeMultiPointGeometry_ = function(geom) {
  var array = [];
  var components = geom.getPoints();
  for (var i = 0, ii = components.length; i < ii; ++i) {
    array.push('(' + ol.format.WKT.encodePointGeometry_(components[i]) + ')');
  }
  return array.join(',');
};


/**
 * @param {ol.geom.GeometryCollection} geom GeometryCollection geometry.
 * @return {string} Coordinates part of GeometryCollection as WKT.
 * @private
 */
ol.format.WKT.encodeGeometryCollectionGeometry_ = function(geom) {
  var array = [];
  var geoms = geom.getGeometries();
  for (var i = 0, ii = geoms.length; i < ii; ++i) {
    array.push(ol.format.WKT.encode_(geoms[i]));
  }
  return array.join(',');
};


/**
 * @param {ol.geom.LineString|ol.geom.LinearRing} geom LineString geometry.
 * @return {string} Coordinates part of LineString as WKT.
 * @private
 */
ol.format.WKT.encodeLineStringGeometry_ = function(geom) {
  var coordinates = geom.getCoordinates();
  var array = [];
  for (var i = 0, ii = coordinates.length; i < ii; ++i) {
    array.push(coordinates[i][0] + ' ' + coordinates[i][1]);
  }
  return array.join(',');
};


/**
 * @param {ol.geom.MultiLineString} geom MultiLineString geometry.
 * @return {string} Coordinates part of MultiLineString as WKT.
 * @private
 */
ol.format.WKT.encodeMultiLineStringGeometry_ = function(geom) {
  var array = [];
  var components = geom.getLineStrings();
  for (var i = 0, ii = components.length; i < ii; ++i) {
    array.push('(' + ol.format.WKT.encodeLineStringGeometry_(
        components[i]) + ')');
  }
  return array.join(',');
};


/**
 * @param {ol.geom.Polygon} geom Polygon geometry.
 * @return {string} Coordinates part of Polygon as WKT.
 * @private
 */
ol.format.WKT.encodePolygonGeometry_ = function(geom) {
  var array = [];
  var rings = geom.getLinearRings();
  for (var i = 0, ii = rings.length; i < ii; ++i) {
    array.push('(' + ol.format.WKT.encodeLineStringGeometry_(
        rings[i]) + ')');
  }
  return array.join(',');
};


/**
 * @param {ol.geom.MultiPolygon} geom MultiPolygon geometry.
 * @return {string} Coordinates part of MultiPolygon as WKT.
 * @private
 */
ol.format.WKT.encodeMultiPolygonGeometry_ = function(geom) {
  var array = [];
  var components = geom.getPolygons();
  for (var i = 0, ii = components.length; i < ii; ++i) {
    array.push('(' + ol.format.WKT.encodePolygonGeometry_(
        components[i]) + ')');
  }
  return array.join(',');
};


/**
 * Encode a geometry as WKT.
 * @param {ol.geom.Geometry} geom The geometry to encode.
 * @return {string} WKT string for the geometry.
 * @private
 */
ol.format.WKT.encode_ = function(geom) {
  var type = geom.getType();
  var geometryEncoder = ol.format.WKT.GeometryEncoder_[type];
  goog.DEBUG && console.assert(geometryEncoder, 'geometryEncoder should be defined');
  var enc = geometryEncoder(geom);
  type = type.toUpperCase();
  if (enc.length === 0) {
    return type + ' ' + ol.format.WKT.EMPTY;
  }
  return type + '(' + enc + ')';
};


/**
 * @const
 * @type {Object.<string, function(ol.geom.Geometry): string>}
 * @private
 */
ol.format.WKT.GeometryEncoder_ = {
  'Point': ol.format.WKT.encodePointGeometry_,
  'LineString': ol.format.WKT.encodeLineStringGeometry_,
  'Polygon': ol.format.WKT.encodePolygonGeometry_,
  'MultiPoint': ol.format.WKT.encodeMultiPointGeometry_,
  'MultiLineString': ol.format.WKT.encodeMultiLineStringGeometry_,
  'MultiPolygon': ol.format.WKT.encodeMultiPolygonGeometry_,
  'GeometryCollection': ol.format.WKT.encodeGeometryCollectionGeometry_
};


/**
 * Parse a WKT string.
 * @param {string} wkt WKT string.
 * @return {ol.geom.Geometry|ol.geom.GeometryCollection|undefined}
 *     The geometry created.
 * @private
 */
ol.format.WKT.prototype.parse_ = function(wkt) {
  var lexer = new ol.format.WKT.Lexer(wkt);
  var parser = new ol.format.WKT.Parser(lexer);
  return parser.parse();
};


/**
 * Read a feature from a WKT source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.Feature} Feature.
 * @api stable
 */
ol.format.WKT.prototype.readFeature;


/**
 * @inheritDoc
 */
ol.format.WKT.prototype.readFeatureFromText = function(text, opt_options) {
  var geom = this.readGeometryFromText(text, opt_options);
  if (geom) {
    var feature = new ol.Feature();
    feature.setGeometry(geom);
    return feature;
  }
  return null;
};


/**
 * Read all features from a WKT source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.WKT.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.WKT.prototype.readFeaturesFromText = function(text, opt_options) {
  var geometries = [];
  var geometry = this.readGeometryFromText(text, opt_options);
  if (this.splitCollection_ &&
      geometry.getType() == ol.geom.GeometryType.GEOMETRY_COLLECTION) {
    geometries = (/** @type {ol.geom.GeometryCollection} */ (geometry))
        .getGeometriesArray();
  } else {
    geometries = [geometry];
  }
  var feature, features = [];
  for (var i = 0, ii = geometries.length; i < ii; ++i) {
    feature = new ol.Feature();
    feature.setGeometry(geometries[i]);
    features.push(feature);
  }
  return features;
};


/**
 * Read a single geometry from a WKT source.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Read options.
 * @return {ol.geom.Geometry} Geometry.
 * @api stable
 */
ol.format.WKT.prototype.readGeometry;


/**
 * @inheritDoc
 */
ol.format.WKT.prototype.readGeometryFromText = function(text, opt_options) {
  var geometry = this.parse_(text);
  if (geometry) {
    return /** @type {ol.geom.Geometry} */ (
        ol.format.Feature.transformWithOptions(geometry, false, opt_options));
  } else {
    return null;
  }
};


/**
 * Encode a feature as a WKT string.
 *
 * @function
 * @param {ol.Feature} feature Feature.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} WKT string.
 * @api stable
 */
ol.format.WKT.prototype.writeFeature;


/**
 * @inheritDoc
 */
ol.format.WKT.prototype.writeFeatureText = function(feature, opt_options) {
  var geometry = feature.getGeometry();
  if (geometry) {
    return this.writeGeometryText(geometry, opt_options);
  }
  return '';
};


/**
 * Encode an array of features as a WKT string.
 *
 * @function
 * @param {Array.<ol.Feature>} features Features.
 * @param {olx.format.WriteOptions=} opt_options Write options.
 * @return {string} WKT string.
 * @api stable
 */
ol.format.WKT.prototype.writeFeatures;


/**
 * @inheritDoc
 */
ol.format.WKT.prototype.writeFeaturesText = function(features, opt_options) {
  if (features.length == 1) {
    return this.writeFeatureText(features[0], opt_options);
  }
  var geometries = [];
  for (var i = 0, ii = features.length; i < ii; ++i) {
    geometries.push(features[i].getGeometry());
  }
  var collection = new ol.geom.GeometryCollection(geometries);
  return this.writeGeometryText(collection, opt_options);
};


/**
 * Write a single geometry as a WKT string.
 *
 * @function
 * @param {ol.geom.Geometry} geometry Geometry.
 * @return {string} WKT string.
 * @api stable
 */
ol.format.WKT.prototype.writeGeometry;


/**
 * @inheritDoc
 */
ol.format.WKT.prototype.writeGeometryText = function(geometry, opt_options) {
  return ol.format.WKT.encode_(/** @type {ol.geom.Geometry} */ (
      ol.format.Feature.transformWithOptions(geometry, true, opt_options)));
};


/**
 * @const
 * @enum {number}
 */
ol.format.WKT.TokenType = {
  TEXT: 1,
  LEFT_PAREN: 2,
  RIGHT_PAREN: 3,
  NUMBER: 4,
  COMMA: 5,
  EOF: 6
};


/**
 * Class to tokenize a WKT string.
 * @param {string} wkt WKT string.
 * @constructor
 * @protected
 */
ol.format.WKT.Lexer = function(wkt) {

  /**
   * @type {string}
   */
  this.wkt = wkt;

  /**
   * @type {number}
   * @private
   */
  this.index_ = -1;
};


/**
 * @param {string} c Character.
 * @return {boolean} Whether the character is alphabetic.
 * @private
 */
ol.format.WKT.Lexer.prototype.isAlpha_ = function(c) {
  return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
};


/**
 * @param {string} c Character.
 * @param {boolean=} opt_decimal Whether the string number
 *     contains a dot, i.e. is a decimal number.
 * @return {boolean} Whether the character is numeric.
 * @private
 */
ol.format.WKT.Lexer.prototype.isNumeric_ = function(c, opt_decimal) {
  var decimal = opt_decimal !== undefined ? opt_decimal : false;
  return c >= '0' && c <= '9' || c == '.' && !decimal;
};


/**
 * @param {string} c Character.
 * @return {boolean} Whether the character is whitespace.
 * @private
 */
ol.format.WKT.Lexer.prototype.isWhiteSpace_ = function(c) {
  return c == ' ' || c == '\t' || c == '\r' || c == '\n';
};


/**
 * @return {string} Next string character.
 * @private
 */
ol.format.WKT.Lexer.prototype.nextChar_ = function() {
  return this.wkt.charAt(++this.index_);
};


/**
 * Fetch and return the next token.
 * @return {!ol.WKTToken} Next string token.
 */
ol.format.WKT.Lexer.prototype.nextToken = function() {
  var c = this.nextChar_();
  var token = {position: this.index_, value: c};

  if (c == '(') {
    token.type = ol.format.WKT.TokenType.LEFT_PAREN;
  } else if (c == ',') {
    token.type = ol.format.WKT.TokenType.COMMA;
  } else if (c == ')') {
    token.type = ol.format.WKT.TokenType.RIGHT_PAREN;
  } else if (this.isNumeric_(c) || c == '-') {
    token.type = ol.format.WKT.TokenType.NUMBER;
    token.value = this.readNumber_();
  } else if (this.isAlpha_(c)) {
    token.type = ol.format.WKT.TokenType.TEXT;
    token.value = this.readText_();
  } else if (this.isWhiteSpace_(c)) {
    return this.nextToken();
  } else if (c === '') {
    token.type = ol.format.WKT.TokenType.EOF;
  } else {
    throw new Error('Unexpected character: ' + c);
  }

  return token;
};


/**
 * @return {number} Numeric token value.
 * @private
 */
ol.format.WKT.Lexer.prototype.readNumber_ = function() {
  var c, index = this.index_;
  var decimal = false;
  var scientificNotation = false;
  do {
    if (c == '.') {
      decimal = true;
    } else if (c == 'e' || c == 'E') {
      scientificNotation = true;
    }
    c = this.nextChar_();
  } while (
      this.isNumeric_(c, decimal) ||
      // if we haven't detected a scientific number before, 'e' or 'E'
      // hint that we should continue to read
      !scientificNotation && (c == 'e' || c == 'E') ||
      // once we know that we have a scientific number, both '-' and '+'
      // are allowed
      scientificNotation && (c == '-' || c == '+')
  );
  return parseFloat(this.wkt.substring(index, this.index_--));
};


/**
 * @return {string} String token value.
 * @private
 */
ol.format.WKT.Lexer.prototype.readText_ = function() {
  var c, index = this.index_;
  do {
    c = this.nextChar_();
  } while (this.isAlpha_(c));
  return this.wkt.substring(index, this.index_--).toUpperCase();
};


/**
 * Class to parse the tokens from the WKT string.
 * @param {ol.format.WKT.Lexer} lexer The lexer.
 * @constructor
 * @protected
 */
ol.format.WKT.Parser = function(lexer) {

  /**
   * @type {ol.format.WKT.Lexer}
   * @private
   */
  this.lexer_ = lexer;

  /**
   * @type {ol.WKTToken}
   * @private
   */
  this.token_;

  /**
   * @type {number}
   * @private
   */
  this.dimension_ = 2;
};


/**
 * Fetch the next token form the lexer and replace the active token.
 * @private
 */
ol.format.WKT.Parser.prototype.consume_ = function() {
  this.token_ = this.lexer_.nextToken();
};


/**
 * If the given type matches the current token, consume it.
 * @param {ol.format.WKT.TokenType} type Token type.
 * @return {boolean} Whether the token matches the given type.
 */
ol.format.WKT.Parser.prototype.match = function(type) {
  var isMatch = this.token_.type == type;
  if (isMatch) {
    this.consume_();
  }
  return isMatch;
};


/**
 * Try to parse the tokens provided by the lexer.
 * @return {ol.geom.Geometry|ol.geom.GeometryCollection} The geometry.
 */
ol.format.WKT.Parser.prototype.parse = function() {
  this.consume_();
  var geometry = this.parseGeometry_();
  goog.DEBUG && console.assert(this.token_.type == ol.format.WKT.TokenType.EOF,
      'token type should be end of file');
  return geometry;
};


/**
 * @return {!(ol.geom.Geometry|ol.geom.GeometryCollection)} The geometry.
 * @private
 */
ol.format.WKT.Parser.prototype.parseGeometry_ = function() {
  var token = this.token_;
  if (this.match(ol.format.WKT.TokenType.TEXT)) {
    var geomType = token.value;
    if (geomType == ol.geom.GeometryType.GEOMETRY_COLLECTION.toUpperCase()) {
      var geometries = this.parseGeometryCollectionText_();
      return new ol.geom.GeometryCollection(geometries);
    } else {
      var parser = ol.format.WKT.Parser.GeometryParser_[geomType];
      var ctor = ol.format.WKT.Parser.GeometryConstructor_[geomType];
      if (!parser || !ctor) {
        throw new Error('Invalid geometry type: ' + geomType);
      }
      var coordinates = parser.call(this);
      return new ctor(coordinates);
    }
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<ol.geom.Geometry>} A collection of geometries.
 * @private
 */
ol.format.WKT.Parser.prototype.parseGeometryCollectionText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var geometries = [];
    do {
      geometries.push(this.parseGeometry_());
    } while (this.match(ol.format.WKT.TokenType.COMMA));
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return geometries;
    }
  } else if (this.isEmptyGeometry_()) {
    return [];
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {Array.<number>} All values in a point.
 * @private
 */
ol.format.WKT.Parser.prototype.parsePointText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var coordinates = this.parsePoint_();
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return coordinates;
    }
  } else if (this.isEmptyGeometry_()) {
    return null;
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<!Array.<number>>} All points in a linestring.
 * @private
 */
ol.format.WKT.Parser.prototype.parseLineStringText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var coordinates = this.parsePointList_();
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return coordinates;
    }
  } else if (this.isEmptyGeometry_()) {
    return [];
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<!Array.<number>>} All points in a polygon.
 * @private
 */
ol.format.WKT.Parser.prototype.parsePolygonText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var coordinates = this.parseLineStringTextList_();
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return coordinates;
    }
  } else if (this.isEmptyGeometry_()) {
    return [];
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<!Array.<number>>} All points in a multipoint.
 * @private
 */
ol.format.WKT.Parser.prototype.parseMultiPointText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var coordinates;
    if (this.token_.type == ol.format.WKT.TokenType.LEFT_PAREN) {
      coordinates = this.parsePointTextList_();
    } else {
      coordinates = this.parsePointList_();
    }
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return coordinates;
    }
  } else if (this.isEmptyGeometry_()) {
    return [];
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<!Array.<number>>} All linestring points
 *                                        in a multilinestring.
 * @private
 */
ol.format.WKT.Parser.prototype.parseMultiLineStringText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var coordinates = this.parseLineStringTextList_();
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return coordinates;
    }
  } else if (this.isEmptyGeometry_()) {
    return [];
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<!Array.<number>>} All polygon points in a multipolygon.
 * @private
 */
ol.format.WKT.Parser.prototype.parseMultiPolygonText_ = function() {
  if (this.match(ol.format.WKT.TokenType.LEFT_PAREN)) {
    var coordinates = this.parsePolygonTextList_();
    if (this.match(ol.format.WKT.TokenType.RIGHT_PAREN)) {
      return coordinates;
    }
  } else if (this.isEmptyGeometry_()) {
    return [];
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<number>} A point.
 * @private
 */
ol.format.WKT.Parser.prototype.parsePoint_ = function() {
  var coordinates = [];
  for (var i = 0; i < this.dimension_; ++i) {
    var token = this.token_;
    if (this.match(ol.format.WKT.TokenType.NUMBER)) {
      coordinates.push(token.value);
    } else {
      break;
    }
  }
  if (coordinates.length == this.dimension_) {
    return coordinates;
  }
  throw new Error(this.formatErrorMessage_());
};


/**
 * @return {!Array.<!Array.<number>>} An array of points.
 * @private
 */
ol.format.WKT.Parser.prototype.parsePointList_ = function() {
  var coordinates = [this.parsePoint_()];
  while (this.match(ol.format.WKT.TokenType.COMMA)) {
    coordinates.push(this.parsePoint_());
  }
  return coordinates;
};


/**
 * @return {!Array.<!Array.<number>>} An array of points.
 * @private
 */
ol.format.WKT.Parser.prototype.parsePointTextList_ = function() {
  var coordinates = [this.parsePointText_()];
  while (this.match(ol.format.WKT.TokenType.COMMA)) {
    coordinates.push(this.parsePointText_());
  }
  return coordinates;
};


/**
 * @return {!Array.<!Array.<number>>} An array of points.
 * @private
 */
ol.format.WKT.Parser.prototype.parseLineStringTextList_ = function() {
  var coordinates = [this.parseLineStringText_()];
  while (this.match(ol.format.WKT.TokenType.COMMA)) {
    coordinates.push(this.parseLineStringText_());
  }
  return coordinates;
};


/**
 * @return {!Array.<!Array.<number>>} An array of points.
 * @private
 */
ol.format.WKT.Parser.prototype.parsePolygonTextList_ = function() {
  var coordinates = [this.parsePolygonText_()];
  while (this.match(ol.format.WKT.TokenType.COMMA)) {
    coordinates.push(this.parsePolygonText_());
  }
  return coordinates;
};


/**
 * @return {boolean} Whether the token implies an empty geometry.
 * @private
 */
ol.format.WKT.Parser.prototype.isEmptyGeometry_ = function() {
  var isEmpty = this.token_.type == ol.format.WKT.TokenType.TEXT &&
      this.token_.value == ol.format.WKT.EMPTY;
  if (isEmpty) {
    this.consume_();
  }
  return isEmpty;
};


/**
 * Create an error message for an unexpected token error.
 * @return {string} Error message.
 * @private
 */
ol.format.WKT.Parser.prototype.formatErrorMessage_ = function() {
  return 'Unexpected `' + this.token_.value + '` at position ' +
      this.token_.position + ' in `' + this.lexer_.wkt + '`';
};


/**
 * @enum {function (new:ol.geom.Geometry, Array, ol.geom.GeometryLayout)}
 * @private
 */
ol.format.WKT.Parser.GeometryConstructor_ = {
  'POINT': ol.geom.Point,
  'LINESTRING': ol.geom.LineString,
  'POLYGON': ol.geom.Polygon,
  'MULTIPOINT': ol.geom.MultiPoint,
  'MULTILINESTRING': ol.geom.MultiLineString,
  'MULTIPOLYGON': ol.geom.MultiPolygon
};


/**
 * @enum {(function(): Array)}
 * @private
 */
ol.format.WKT.Parser.GeometryParser_ = {
  'POINT': ol.format.WKT.Parser.prototype.parsePointText_,
  'LINESTRING': ol.format.WKT.Parser.prototype.parseLineStringText_,
  'POLYGON': ol.format.WKT.Parser.prototype.parsePolygonText_,
  'MULTIPOINT': ol.format.WKT.Parser.prototype.parseMultiPointText_,
  'MULTILINESTRING': ol.format.WKT.Parser.prototype.parseMultiLineStringText_,
  'MULTIPOLYGON': ol.format.WKT.Parser.prototype.parseMultiPolygonText_
};

goog.provide('ol.format.WMSCapabilities');

goog.require('ol');
goog.require('ol.format.XLink');
goog.require('ol.format.XML');
goog.require('ol.format.XSD');
goog.require('ol.xml');


/**
 * @classdesc
 * Format for reading WMS capabilities data
 *
 * @constructor
 * @extends {ol.format.XML}
 * @api
 */
ol.format.WMSCapabilities = function() {

  ol.format.XML.call(this);

  /**
   * @type {string|undefined}
   */
  this.version = undefined;
};
ol.inherits(ol.format.WMSCapabilities, ol.format.XML);


/**
 * Read a WMS capabilities document.
 *
 * @function
 * @param {Document|Node|string} source The XML source.
 * @return {Object} An object representing the WMS capabilities.
 * @api
 */
ol.format.WMSCapabilities.prototype.read;


/**
 * @param {Document} doc Document.
 * @return {Object} WMS Capability object.
 */
ol.format.WMSCapabilities.prototype.readFromDocument = function(doc) {
  goog.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
      'doc.nodeType should be DOCUMENT');
  for (var n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readFromNode(n);
    }
  }
  return null;
};


/**
 * @param {Node} node Node.
 * @return {Object} WMS Capability object.
 */
ol.format.WMSCapabilities.prototype.readFromNode = function(node) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'WMS_Capabilities' ||
      node.localName == 'WMT_MS_Capabilities',
      'localName should be WMS_Capabilities or WMT_MS_Capabilities');
  this.version = node.getAttribute('version').trim();
  var wmsCapabilityObject = ol.xml.pushParseAndPop({
    'version': this.version
  }, ol.format.WMSCapabilities.PARSERS_, node, []);
  return wmsCapabilityObject ? wmsCapabilityObject : null;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Attribution object.
 */
ol.format.WMSCapabilities.readAttribution_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Attribution',
      'localName should be Attribution');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object} Bounding box object.
 */
ol.format.WMSCapabilities.readBoundingBox_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'BoundingBox',
      'localName should be BoundingBox');

  var extent = [
    ol.format.XSD.readDecimalString(node.getAttribute('minx')),
    ol.format.XSD.readDecimalString(node.getAttribute('miny')),
    ol.format.XSD.readDecimalString(node.getAttribute('maxx')),
    ol.format.XSD.readDecimalString(node.getAttribute('maxy'))
  ];

  var resolutions = [
    ol.format.XSD.readDecimalString(node.getAttribute('resx')),
    ol.format.XSD.readDecimalString(node.getAttribute('resy'))
  ];

  return {
    'crs': node.getAttribute('CRS'),
    'extent': extent,
    'res': resolutions
  };
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {ol.Extent|undefined} Bounding box object.
 */
ol.format.WMSCapabilities.readEXGeographicBoundingBox_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'EX_GeographicBoundingBox',
      'localName should be EX_GeographicBoundingBox');
  var geographicBoundingBox = ol.xml.pushParseAndPop(
      {},
      ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_,
      node, objectStack);
  if (!geographicBoundingBox) {
    return undefined;
  }
  var westBoundLongitude = /** @type {number|undefined} */
      (geographicBoundingBox['westBoundLongitude']);
  var southBoundLatitude = /** @type {number|undefined} */
      (geographicBoundingBox['southBoundLatitude']);
  var eastBoundLongitude = /** @type {number|undefined} */
      (geographicBoundingBox['eastBoundLongitude']);
  var northBoundLatitude = /** @type {number|undefined} */
      (geographicBoundingBox['northBoundLatitude']);
  if (westBoundLongitude === undefined || southBoundLatitude === undefined ||
      eastBoundLongitude === undefined || northBoundLatitude === undefined) {
    return undefined;
  }
  return [
    westBoundLongitude, southBoundLatitude,
    eastBoundLongitude, northBoundLatitude
  ];
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} Capability object.
 */
ol.format.WMSCapabilities.readCapability_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Capability',
      'localName should be Capability');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.CAPABILITY_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} Service object.
 */
ol.format.WMSCapabilities.readService_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Service',
      'localName should be Service');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.SERVICE_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} Contact information object.
 */
ol.format.WMSCapabilities.readContactInformation_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType shpuld be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ContactInformation',
      'localName should be ContactInformation');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_,
      node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} Contact person object.
 */
ol.format.WMSCapabilities.readContactPersonPrimary_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ContactPersonPrimary',
      'localName should be ContactPersonPrimary');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_,
      node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} Contact address object.
 */
ol.format.WMSCapabilities.readContactAddress_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'ContactAddress',
      'localName should be ContactAddress');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_,
      node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Array.<string>|undefined} Format array.
 */
ol.format.WMSCapabilities.readException_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Exception',
      'localName should be Exception');
  return ol.xml.pushParseAndPop(
      [], ol.format.WMSCapabilities.EXCEPTION_PARSERS_, node, objectStack);
};


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @private
 * @return {Object|undefined} Layer object.
 */
ol.format.WMSCapabilities.readCapabilityLayer_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Layer object.
 */
ol.format.WMSCapabilities.readLayer_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer');
  var parentLayerObject = /**  @type {Object.<string,*>} */
      (objectStack[objectStack.length - 1]);

  var layerObject = ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.LAYER_PARSERS_, node, objectStack);

  if (!layerObject) {
    return undefined;
  }
  var queryable =
      ol.format.XSD.readBooleanString(node.getAttribute('queryable'));
  if (queryable === undefined) {
    queryable = parentLayerObject['queryable'];
  }
  layerObject['queryable'] = queryable !== undefined ? queryable : false;

  var cascaded = ol.format.XSD.readNonNegativeIntegerString(
      node.getAttribute('cascaded'));
  if (cascaded === undefined) {
    cascaded = parentLayerObject['cascaded'];
  }
  layerObject['cascaded'] = cascaded;

  var opaque = ol.format.XSD.readBooleanString(node.getAttribute('opaque'));
  if (opaque === undefined) {
    opaque = parentLayerObject['opaque'];
  }
  layerObject['opaque'] = opaque !== undefined ? opaque : false;

  var noSubsets =
      ol.format.XSD.readBooleanString(node.getAttribute('noSubsets'));
  if (noSubsets === undefined) {
    noSubsets = parentLayerObject['noSubsets'];
  }
  layerObject['noSubsets'] = noSubsets !== undefined ? noSubsets : false;

  var fixedWidth =
      ol.format.XSD.readDecimalString(node.getAttribute('fixedWidth'));
  if (!fixedWidth) {
    fixedWidth = parentLayerObject['fixedWidth'];
  }
  layerObject['fixedWidth'] = fixedWidth;

  var fixedHeight =
      ol.format.XSD.readDecimalString(node.getAttribute('fixedHeight'));
  if (!fixedHeight) {
    fixedHeight = parentLayerObject['fixedHeight'];
  }
  layerObject['fixedHeight'] = fixedHeight;

  // See 7.2.4.8
  var addKeys = ['Style', 'CRS', 'AuthorityURL'];
  addKeys.forEach(function(key) {
    if (key in parentLayerObject) {
      var childValue = layerObject[key] || [];
      layerObject[key] = childValue.concat(parentLayerObject[key]);
    }
  });

  var replaceKeys = ['EX_GeographicBoundingBox', 'BoundingBox', 'Dimension',
    'Attribution', 'MinScaleDenominator', 'MaxScaleDenominator'];
  replaceKeys.forEach(function(key) {
    if (!(key in layerObject)) {
      var parentValue = parentLayerObject[key];
      layerObject[key] = parentValue;
    }
  });

  return layerObject;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object} Dimension object.
 */
ol.format.WMSCapabilities.readDimension_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Dimension',
      'localName should be Dimension');
  var dimensionObject = {
    'name': node.getAttribute('name'),
    'units': node.getAttribute('units'),
    'unitSymbol': node.getAttribute('unitSymbol'),
    'default': node.getAttribute('default'),
    'multipleValues': ol.format.XSD.readBooleanString(
        node.getAttribute('multipleValues')),
    'nearestValue': ol.format.XSD.readBooleanString(
        node.getAttribute('nearestValue')),
    'current': ol.format.XSD.readBooleanString(node.getAttribute('current')),
    'values': ol.format.XSD.readString(node)
  };
  return dimensionObject;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Online resource object.
 */
ol.format.WMSCapabilities.readFormatOnlineresource_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_,
      node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Request object.
 */
ol.format.WMSCapabilities.readRequest_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Request',
      'localName should be Request');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.REQUEST_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} DCP type object.
 */
ol.format.WMSCapabilities.readDCPType_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'DCPType',
      'localName should be DCPType');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.DCPTYPE_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} HTTP object.
 */
ol.format.WMSCapabilities.readHTTP_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'HTTP', 'localName should be HTTP');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.HTTP_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Operation type object.
 */
ol.format.WMSCapabilities.readOperationType_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Online resource object.
 */
ol.format.WMSCapabilities.readSizedFormatOnlineresource_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  var formatOnlineresource =
      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
  if (formatOnlineresource) {
    var size = [
      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('width')),
      ol.format.XSD.readNonNegativeIntegerString(node.getAttribute('height'))
    ];
    formatOnlineresource['size'] = size;
    return formatOnlineresource;
  }
  return undefined;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Authority URL object.
 */
ol.format.WMSCapabilities.readAuthorityURL_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'AuthorityURL',
      'localName should be AuthorityURL');
  var authorityObject =
      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
  if (authorityObject) {
    authorityObject['name'] = node.getAttribute('name');
    return authorityObject;
  }
  return undefined;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Metadata URL object.
 */
ol.format.WMSCapabilities.readMetadataURL_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'MetadataURL',
      'localName should be MetadataURL');
  var metadataObject =
      ol.format.WMSCapabilities.readFormatOnlineresource_(node, objectStack);
  if (metadataObject) {
    metadataObject['type'] = node.getAttribute('type');
    return metadataObject;
  }
  return undefined;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Style object.
 */
ol.format.WMSCapabilities.readStyle_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Style', 'localName should be Style');
  return ol.xml.pushParseAndPop(
      {}, ol.format.WMSCapabilities.STYLE_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Array.<string>|undefined} Keyword list.
 */
ol.format.WMSCapabilities.readKeywordList_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'KeywordList',
      'localName should be KeywordList');
  return ol.xml.pushParseAndPop(
      [], ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_, node, objectStack);
};


/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.WMSCapabilities.NAMESPACE_URIS_ = [
  null,
  'http://www.opengis.net/wms'
];


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Service': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readService_),
      'Capability': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readCapability_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.CAPABILITY_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Request': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readRequest_),
      'Exception': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readException_),
      'Layer': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readCapabilityLayer_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.SERVICE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'KeywordList': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readKeywordList_),
      'OnlineResource': ol.xml.makeObjectPropertySetter(
          ol.format.XLink.readHref),
      'ContactInformation': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readContactInformation_),
      'Fees': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'AccessConstraints': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'LayerLimit': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger),
      'MaxWidth': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger),
      'MaxHeight': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.CONTACT_INFORMATION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'ContactPersonPrimary': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readContactPersonPrimary_),
      'ContactPosition': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'ContactAddress': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readContactAddress_),
      'ContactVoiceTelephone': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'ContactFacsimileTelephone': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'ContactElectronicMailAddress': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.CONTACT_PERSON_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'ContactPerson': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'ContactOrganization': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.CONTACT_ADDRESS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'AddressType': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Address': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'City': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'StateOrProvince': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'PostCode': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Country': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.EXCEPTION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Format': ol.xml.makeArrayPusher(ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'KeywordList': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readKeywordList_),
      'CRS': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
      'EX_GeographicBoundingBox': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readEXGeographicBoundingBox_),
      'BoundingBox': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readBoundingBox_),
      'Dimension': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readDimension_),
      'Attribution': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readAttribution_),
      'AuthorityURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readAuthorityURL_),
      'Identifier': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
      'MetadataURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readMetadataURL_),
      'DataURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readFormatOnlineresource_),
      'FeatureListURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readFormatOnlineresource_),
      'Style': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readStyle_),
      'MinScaleDenominator': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal),
      'MaxScaleDenominator': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal),
      'Layer': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readLayer_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.ATTRIBUTION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'OnlineResource': ol.xml.makeObjectPropertySetter(
          ol.format.XLink.readHref),
      'LogoURL': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readSizedFormatOnlineresource_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.EX_GEOGRAPHIC_BOUNDING_BOX_PARSERS_ =
    ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'westBoundLongitude': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal),
      'eastBoundLongitude': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal),
      'southBoundLatitude': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal),
      'northBoundLatitude': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.REQUEST_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'GetCapabilities': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readOperationType_),
      'GetMap': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readOperationType_),
      'GetFeatureInfo': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readOperationType_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.OPERATIONTYPE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Format': ol.xml.makeObjectPropertyPusher(ol.format.XSD.readString),
      'DCPType': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readDCPType_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.DCPTYPE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'HTTP': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readHTTP_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.HTTP_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Get': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readFormatOnlineresource_),
      'Post': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readFormatOnlineresource_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Name': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Title': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'Abstract': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'LegendURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMSCapabilities.readSizedFormatOnlineresource_),
      'StyleSheetURL': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readFormatOnlineresource_),
      'StyleURL': ol.xml.makeObjectPropertySetter(
          ol.format.WMSCapabilities.readFormatOnlineresource_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.FORMAT_ONLINERESOURCE_PARSERS_ =
    ol.xml.makeStructureNS(ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Format': ol.xml.makeObjectPropertySetter(ol.format.XSD.readString),
      'OnlineResource': ol.xml.makeObjectPropertySetter(
          ol.format.XLink.readHref)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMSCapabilities.KEYWORDLIST_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMSCapabilities.NAMESPACE_URIS_, {
      'Keyword': ol.xml.makeArrayPusher(ol.format.XSD.readString)
    });

goog.provide('ol.format.WMSGetFeatureInfo');

goog.require('ol');
goog.require('ol.array');
goog.require('ol.format.GML2');
goog.require('ol.format.XMLFeature');
goog.require('ol.obj');
goog.require('ol.xml');


/**
 * @classdesc
 * Format for reading WMSGetFeatureInfo format. It uses
 * {@link ol.format.GML2} to read features.
 *
 * @constructor
 * @extends {ol.format.XMLFeature}
 * @param {olx.format.WMSGetFeatureInfoOptions=} opt_options Options.
 * @api
 */
ol.format.WMSGetFeatureInfo = function(opt_options) {

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {string}
   */
  this.featureNS_ = 'http://mapserver.gis.umn.edu/mapserver';


  /**
   * @private
   * @type {ol.format.GML2}
   */
  this.gmlFormat_ = new ol.format.GML2();


  /**
   * @private
   * @type {Array.<string>}
   */
  this.layers_ = options.layers ? options.layers : null;

  ol.format.XMLFeature.call(this);
};
ol.inherits(ol.format.WMSGetFeatureInfo, ol.format.XMLFeature);


/**
 * @const
 * @type {string}
 * @private
 */
ol.format.WMSGetFeatureInfo.featureIdentifier_ = '_feature';


/**
 * @const
 * @type {string}
 * @private
 */
ol.format.WMSGetFeatureInfo.layerIdentifier_ = '_layer';


/**
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Array.<ol.Feature>} Features.
 * @private
 */
ol.format.WMSGetFeatureInfo.prototype.readFeatures_ = function(node, objectStack) {

  node.setAttribute('namespaceURI', this.featureNS_);
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  var localName = node.localName;
  /** @type {Array.<ol.Feature>} */
  var features = [];
  if (node.childNodes.length === 0) {
    return features;
  }
  if (localName == 'msGMLOutput') {
    for (var i = 0, ii = node.childNodes.length; i < ii; i++) {
      var layer = node.childNodes[i];
      if (layer.nodeType !== Node.ELEMENT_NODE) {
        continue;
      }
      var context = objectStack[0];

      goog.DEBUG && console.assert(layer.localName.indexOf(
          ol.format.WMSGetFeatureInfo.layerIdentifier_) >= 0,
          'localName of layer node should match layerIdentifier');

      var toRemove = ol.format.WMSGetFeatureInfo.layerIdentifier_;
      var layerName = layer.localName.replace(toRemove, '');

      if (this.layers_ && !ol.array.includes(this.layers_, layerName)) {
        continue;
      }

      var featureType = layerName +
          ol.format.WMSGetFeatureInfo.featureIdentifier_;

      context['featureType'] = featureType;
      context['featureNS'] = this.featureNS_;

      var parsers = {};
      parsers[featureType] = ol.xml.makeArrayPusher(
          this.gmlFormat_.readFeatureElement, this.gmlFormat_);
      var parsersNS = ol.xml.makeStructureNS(
          [context['featureNS'], null], parsers);
      layer.setAttribute('namespaceURI', this.featureNS_);
      var layerFeatures = ol.xml.pushParseAndPop(
          [], parsersNS, layer, objectStack, this.gmlFormat_);
      if (layerFeatures) {
        ol.array.extend(features, layerFeatures);
      }
    }
  }
  if (localName == 'FeatureCollection') {
    var gmlFeatures = ol.xml.pushParseAndPop([],
        this.gmlFormat_.FEATURE_COLLECTION_PARSERS, node,
        [{}], this.gmlFormat_);
    if (gmlFeatures) {
      features = gmlFeatures;
    }
  }
  return features;
};


/**
 * Read all features from a WMSGetFeatureInfo response.
 *
 * @function
 * @param {Document|Node|Object|string} source Source.
 * @param {olx.format.ReadOptions=} opt_options Options.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.format.WMSGetFeatureInfo.prototype.readFeatures;


/**
 * @inheritDoc
 */
ol.format.WMSGetFeatureInfo.prototype.readFeaturesFromNode = function(node, opt_options) {
  var options = {};
  if (opt_options) {
    ol.obj.assign(options, this.getReadOptions(node, opt_options));
  }
  return this.readFeatures_(node, [options]);
};

goog.provide('ol.format.WMTSCapabilities');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.format.OWS');
goog.require('ol.format.XLink');
goog.require('ol.format.XML');
goog.require('ol.format.XSD');
goog.require('ol.xml');


/**
 * @classdesc
 * Format for reading WMTS capabilities data.
 *
 * @constructor
 * @extends {ol.format.XML}
 * @api
 */
ol.format.WMTSCapabilities = function() {
  ol.format.XML.call(this);

  /**
   * @type {ol.format.OWS}
   * @private
   */
  this.owsParser_ = new ol.format.OWS();
};
ol.inherits(ol.format.WMTSCapabilities, ol.format.XML);


/**
 * Read a WMTS capabilities document.
 *
 * @function
 * @param {Document|Node|string} source The XML source.
 * @return {Object} An object representing the WMTS capabilities.
 * @api
 */
ol.format.WMTSCapabilities.prototype.read;


/**
 * @param {Document} doc Document.
 * @return {Object} WMTS Capability object.
 */
ol.format.WMTSCapabilities.prototype.readFromDocument = function(doc) {
  goog.DEBUG && console.assert(doc.nodeType == Node.DOCUMENT_NODE,
      'doc.nodeType should be DOCUMENT');
  for (var n = doc.firstChild; n; n = n.nextSibling) {
    if (n.nodeType == Node.ELEMENT_NODE) {
      return this.readFromNode(n);
    }
  }
  return null;
};


/**
 * @param {Node} node Node.
 * @return {Object} WMTS Capability object.
 */
ol.format.WMTSCapabilities.prototype.readFromNode = function(node) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Capabilities',
      'localName should be Capabilities');
  var version = node.getAttribute('version').trim();
  var WMTSCapabilityObject = this.owsParser_.readFromNode(node);
  if (!WMTSCapabilityObject) {
    return null;
  }
  WMTSCapabilityObject['version'] = version;
  WMTSCapabilityObject = ol.xml.pushParseAndPop(WMTSCapabilityObject,
      ol.format.WMTSCapabilities.PARSERS_, node, []);
  return WMTSCapabilityObject ? WMTSCapabilityObject : null;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Attribution object.
 */
ol.format.WMTSCapabilities.readContents_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Contents',
      'localName should be Contents');

  return ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.CONTENTS_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Layers object.
 */
ol.format.WMTSCapabilities.readLayer_ = function(node, objectStack) {
  goog.DEBUG && console.assert(node.nodeType == Node.ELEMENT_NODE,
      'node.nodeType should be ELEMENT');
  goog.DEBUG && console.assert(node.localName == 'Layer', 'localName should be Layer');
  return ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.LAYER_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Tile Matrix Set object.
 */
ol.format.WMTSCapabilities.readTileMatrixSet_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.TMS_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Style object.
 */
ol.format.WMTSCapabilities.readStyle_ = function(node, objectStack) {
  var style = ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.STYLE_PARSERS_, node, objectStack);
  if (!style) {
    return undefined;
  }
  var isDefault = node.getAttribute('isDefault') === 'true';
  style['isDefault'] = isDefault;
  return style;

};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Tile Matrix Set Link object.
 */
ol.format.WMTSCapabilities.readTileMatrixSetLink_ = function(node,
    objectStack) {
  return ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Dimension object.
 */
ol.format.WMTSCapabilities.readDimensions_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.DIMENSION_PARSERS_, node, objectStack);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Resource URL object.
 */
ol.format.WMTSCapabilities.readResourceUrl_ = function(node, objectStack) {
  var format = node.getAttribute('format');
  var template = node.getAttribute('template');
  var resourceType = node.getAttribute('resourceType');
  var resource = {};
  if (format) {
    resource['format'] = format;
  }
  if (template) {
    resource['template'] = template;
  }
  if (resourceType) {
    resource['resourceType'] = resourceType;
  }
  return resource;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} WGS84 BBox object.
 */
ol.format.WMTSCapabilities.readWgs84BoundingBox_ = function(node, objectStack) {
  var coordinates = ol.xml.pushParseAndPop([],
      ol.format.WMTSCapabilities.WGS84_BBOX_READERS_, node, objectStack);
  if (coordinates.length != 2) {
    return undefined;
  }
  return ol.extent.boundingExtent(coordinates);
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Legend object.
 */
ol.format.WMTSCapabilities.readLegendUrl_ = function(node, objectStack) {
  var legend = {};
  legend['format'] = node.getAttribute('format');
  legend['href'] = ol.format.XLink.readHref(node);
  return legend;
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} Coordinates object.
 */
ol.format.WMTSCapabilities.readCoordinates_ = function(node, objectStack) {
  var coordinates = ol.format.XSD.readString(node).split(' ');
  if (!coordinates || coordinates.length != 2) {
    return undefined;
  }
  var x = +coordinates[0];
  var y = +coordinates[1];
  if (isNaN(x) || isNaN(y)) {
    return undefined;
  }
  return [x, y];
};


/**
 * @private
 * @param {Node} node Node.
 * @param {Array.<*>} objectStack Object stack.
 * @return {Object|undefined} TileMatrix object.
 */
ol.format.WMTSCapabilities.readTileMatrix_ = function(node, objectStack) {
  return ol.xml.pushParseAndPop({},
      ol.format.WMTSCapabilities.TM_PARSERS_, node, objectStack);
};


/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.WMTSCapabilities.NAMESPACE_URIS_ = [
  null,
  'http://www.opengis.net/wmts/1.0'
];


/**
 * @const
 * @private
 * @type {Array.<string>}
 */
ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_ = [
  null,
  'http://www.opengis.net/ows/1.1'
];


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'Contents': ol.xml.makeObjectPropertySetter(
          ol.format.WMTSCapabilities.readContents_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.CONTENTS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'Layer': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readLayer_),
      'TileMatrixSet': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readTileMatrixSet_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.LAYER_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'Style': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readStyle_),
      'Format': ol.xml.makeObjectPropertyPusher(
          ol.format.XSD.readString),
      'TileMatrixSetLink': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readTileMatrixSetLink_),
      'Dimension': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readDimensions_),
      'ResourceURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readResourceUrl_)
    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
      'Title': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'Abstract': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'WGS84BoundingBox': ol.xml.makeObjectPropertySetter(
          ol.format.WMTSCapabilities.readWgs84BoundingBox_),
      'Identifier': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    }));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.STYLE_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'LegendURL': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readLegendUrl_)
    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
      'Title': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'Identifier': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    }));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.TMS_LINKS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'TileMatrixSet': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.DIMENSION_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'Default': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'Value': ol.xml.makeObjectPropertyPusher(
          ol.format.XSD.readString)
    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
      'Identifier': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    }));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.WGS84_BBOX_READERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
      'LowerCorner': ol.xml.makeArrayPusher(
          ol.format.WMTSCapabilities.readCoordinates_),
      'UpperCorner': ol.xml.makeArrayPusher(
          ol.format.WMTSCapabilities.readCoordinates_)
    });


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.TMS_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'WellKnownScaleSet': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'TileMatrix': ol.xml.makeObjectPropertyPusher(
          ol.format.WMTSCapabilities.readTileMatrix_)
    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
      'SupportedCRS': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString),
      'Identifier': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    }));


/**
 * @const
 * @type {Object.<string, Object.<string, ol.XmlParser>>}
 * @private
 */
ol.format.WMTSCapabilities.TM_PARSERS_ = ol.xml.makeStructureNS(
    ol.format.WMTSCapabilities.NAMESPACE_URIS_, {
      'TopLeftCorner': ol.xml.makeObjectPropertySetter(
          ol.format.WMTSCapabilities.readCoordinates_),
      'ScaleDenominator': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readDecimal),
      'TileWidth': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger),
      'TileHeight': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger),
      'MatrixWidth': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger),
      'MatrixHeight': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readNonNegativeInteger)
    }, ol.xml.makeStructureNS(ol.format.WMTSCapabilities.OWS_NAMESPACE_URIS_, {
      'Identifier': ol.xml.makeObjectPropertySetter(
          ol.format.XSD.readString)
    }));

// FIXME handle geolocation not supported

goog.provide('ol.Geolocation');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.Object');
goog.require('ol.geom.Polygon');
goog.require('ol.has');
goog.require('ol.math');
goog.require('ol.proj');
goog.require('ol.sphere.WGS84');


/**
 * @enum {string}
 */
ol.GeolocationProperty = {
  ACCURACY: 'accuracy',
  ACCURACY_GEOMETRY: 'accuracyGeometry',
  ALTITUDE: 'altitude',
  ALTITUDE_ACCURACY: 'altitudeAccuracy',
  HEADING: 'heading',
  POSITION: 'position',
  PROJECTION: 'projection',
  SPEED: 'speed',
  TRACKING: 'tracking',
  TRACKING_OPTIONS: 'trackingOptions'
};


/**
 * @classdesc
 * Helper class for providing HTML5 Geolocation capabilities.
 * The [Geolocation API](http://www.w3.org/TR/geolocation-API/)
 * is used to locate a user's position.
 *
 * To get notified of position changes, register a listener for the generic
 * `change` event on your instance of `ol.Geolocation`.
 *
 * Example:
 *
 *     var geolocation = new ol.Geolocation({
 *       // take the projection to use from the map's view
 *       projection: view.getProjection()
 *     });
 *     // listen to changes in position
 *     geolocation.on('change', function(evt) {
 *       window.console.log(geolocation.getPosition());
 *     });
 *
 * @fires error
 * @constructor
 * @extends {ol.Object}
 * @param {olx.GeolocationOptions=} opt_options Options.
 * @api stable
 */
ol.Geolocation = function(opt_options) {

  ol.Object.call(this);

  var options = opt_options || {};

  /**
   * The unprojected (EPSG:4326) device position.
   * @private
   * @type {ol.Coordinate}
   */
  this.position_ = null;

  /**
   * @private
   * @type {ol.TransformFunction}
   */
  this.transform_ = ol.proj.identityTransform;

  /**
   * @private
   * @type {number|undefined}
   */
  this.watchId_ = undefined;

  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.GeolocationProperty.PROJECTION),
      this.handleProjectionChanged_, this);
  ol.events.listen(
      this, ol.Object.getChangeEventType(ol.GeolocationProperty.TRACKING),
      this.handleTrackingChanged_, this);

  if (options.projection !== undefined) {
    this.setProjection(ol.proj.get(options.projection));
  }
  if (options.trackingOptions !== undefined) {
    this.setTrackingOptions(options.trackingOptions);
  }

  this.setTracking(options.tracking !== undefined ? options.tracking : false);

};
ol.inherits(ol.Geolocation, ol.Object);


/**
 * @inheritDoc
 */
ol.Geolocation.prototype.disposeInternal = function() {
  this.setTracking(false);
  ol.Object.prototype.disposeInternal.call(this);
};


/**
 * @private
 */
ol.Geolocation.prototype.handleProjectionChanged_ = function() {
  var projection = this.getProjection();
  if (projection) {
    this.transform_ = ol.proj.getTransformFromProjections(
        ol.proj.get('EPSG:4326'), projection);
    if (this.position_) {
      this.set(
          ol.GeolocationProperty.POSITION, this.transform_(this.position_));
    }
  }
};


/**
 * @private
 */
ol.Geolocation.prototype.handleTrackingChanged_ = function() {
  if (ol.has.GEOLOCATION) {
    var tracking = this.getTracking();
    if (tracking && this.watchId_ === undefined) {
      this.watchId_ = ol.global.navigator.geolocation.watchPosition(
          this.positionChange_.bind(this),
          this.positionError_.bind(this),
          this.getTrackingOptions());
    } else if (!tracking && this.watchId_ !== undefined) {
      ol.global.navigator.geolocation.clearWatch(this.watchId_);
      this.watchId_ = undefined;
    }
  }
};


/**
 * @private
 * @param {GeolocationPosition} position position event.
 */
ol.Geolocation.prototype.positionChange_ = function(position) {
  var coords = position.coords;
  this.set(ol.GeolocationProperty.ACCURACY, coords.accuracy);
  this.set(ol.GeolocationProperty.ALTITUDE,
      coords.altitude === null ? undefined : coords.altitude);
  this.set(ol.GeolocationProperty.ALTITUDE_ACCURACY,
      coords.altitudeAccuracy === null ?
      undefined : coords.altitudeAccuracy);
  this.set(ol.GeolocationProperty.HEADING, coords.heading === null ?
      undefined : ol.math.toRadians(coords.heading));
  if (!this.position_) {
    this.position_ = [coords.longitude, coords.latitude];
  } else {
    this.position_[0] = coords.longitude;
    this.position_[1] = coords.latitude;
  }
  var projectedPosition = this.transform_(this.position_);
  this.set(ol.GeolocationProperty.POSITION, projectedPosition);
  this.set(ol.GeolocationProperty.SPEED,
      coords.speed === null ? undefined : coords.speed);
  var geometry = ol.geom.Polygon.circular(
      ol.sphere.WGS84, this.position_, coords.accuracy);
  geometry.applyTransform(this.transform_);
  this.set(ol.GeolocationProperty.ACCURACY_GEOMETRY, geometry);
  this.changed();
};

/**
 * Triggered when the Geolocation returns an error.
 * @event error
 * @api
 */

/**
 * @private
 * @param {GeolocationPositionError} error error object.
 */
ol.Geolocation.prototype.positionError_ = function(error) {
  error.type = ol.events.EventType.ERROR;
  this.setTracking(false);
  this.dispatchEvent(/** @type {{type: string, target: undefined}} */ (error));
};


/**
 * Get the accuracy of the position in meters.
 * @return {number|undefined} The accuracy of the position measurement in
 *     meters.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getAccuracy = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.GeolocationProperty.ACCURACY));
};


/**
 * Get a geometry of the position accuracy.
 * @return {?ol.geom.Geometry} A geometry of the position accuracy.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getAccuracyGeometry = function() {
  return /** @type {?ol.geom.Geometry} */ (
      this.get(ol.GeolocationProperty.ACCURACY_GEOMETRY) || null);
};


/**
 * Get the altitude associated with the position.
 * @return {number|undefined} The altitude of the position in meters above mean
 *     sea level.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getAltitude = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.GeolocationProperty.ALTITUDE));
};


/**
 * Get the altitude accuracy of the position.
 * @return {number|undefined} The accuracy of the altitude measurement in
 *     meters.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getAltitudeAccuracy = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.GeolocationProperty.ALTITUDE_ACCURACY));
};


/**
 * Get the heading as radians clockwise from North.
 * @return {number|undefined} The heading of the device in radians from north.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getHeading = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.GeolocationProperty.HEADING));
};


/**
 * Get the position of the device.
 * @return {ol.Coordinate|undefined} The current position of the device reported
 *     in the current projection.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getPosition = function() {
  return /** @type {ol.Coordinate|undefined} */ (
      this.get(ol.GeolocationProperty.POSITION));
};


/**
 * Get the projection associated with the position.
 * @return {ol.proj.Projection|undefined} The projection the position is
 *     reported in.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getProjection = function() {
  return /** @type {ol.proj.Projection|undefined} */ (
      this.get(ol.GeolocationProperty.PROJECTION));
};


/**
 * Get the speed in meters per second.
 * @return {number|undefined} The instantaneous speed of the device in meters
 *     per second.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getSpeed = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.GeolocationProperty.SPEED));
};


/**
 * Determine if the device location is being tracked.
 * @return {boolean} The device location is being tracked.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getTracking = function() {
  return /** @type {boolean} */ (
      this.get(ol.GeolocationProperty.TRACKING));
};


/**
 * Get the tracking options.
 * @see http://www.w3.org/TR/geolocation-API/#position-options
 * @return {GeolocationPositionOptions|undefined} PositionOptions as defined by
 *     the [HTML5 Geolocation spec
 *     ](http://www.w3.org/TR/geolocation-API/#position_options_interface).
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.getTrackingOptions = function() {
  return /** @type {GeolocationPositionOptions|undefined} */ (
      this.get(ol.GeolocationProperty.TRACKING_OPTIONS));
};


/**
 * Set the projection to use for transforming the coordinates.
 * @param {ol.proj.Projection} projection The projection the position is
 *     reported in.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.setProjection = function(projection) {
  this.set(ol.GeolocationProperty.PROJECTION, projection);
};


/**
 * Enable or disable tracking.
 * @param {boolean} tracking Enable tracking.
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.setTracking = function(tracking) {
  this.set(ol.GeolocationProperty.TRACKING, tracking);
};


/**
 * Set the tracking options.
 * @see http://www.w3.org/TR/geolocation-API/#position-options
 * @param {GeolocationPositionOptions} options PositionOptions as defined by the
 *     [HTML5 Geolocation spec
 *     ](http://www.w3.org/TR/geolocation-API/#position_options_interface).
 * @observable
 * @api stable
 */
ol.Geolocation.prototype.setTrackingOptions = function(options) {
  this.set(ol.GeolocationProperty.TRACKING_OPTIONS, options);
};

goog.provide('ol.geom.Circle');

goog.require('ol');
goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.geom.flat.deflate');


/**
 * @classdesc
 * Circle geometry.
 *
 * @constructor
 * @extends {ol.geom.SimpleGeometry}
 * @param {ol.Coordinate} center Center.
 * @param {number=} opt_radius Radius.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api
 */
ol.geom.Circle = function(center, opt_radius, opt_layout) {
  ol.geom.SimpleGeometry.call(this);
  var radius = opt_radius ? opt_radius : 0;
  this.setCenterAndRadius(center, radius, opt_layout);
};
ol.inherits(ol.geom.Circle, ol.geom.SimpleGeometry);


/**
 * Make a complete copy of the geometry.
 * @return {!ol.geom.Circle} Clone.
 * @api
 */
ol.geom.Circle.prototype.clone = function() {
  var circle = new ol.geom.Circle(null);
  circle.setFlatCoordinates(this.layout, this.flatCoordinates.slice());
  return circle;
};


/**
 * @inheritDoc
 */
ol.geom.Circle.prototype.closestPointXY = function(x, y, closestPoint, minSquaredDistance) {
  var flatCoordinates = this.flatCoordinates;
  var dx = x - flatCoordinates[0];
  var dy = y - flatCoordinates[1];
  var squaredDistance = dx * dx + dy * dy;
  if (squaredDistance < minSquaredDistance) {
    var i;
    if (squaredDistance === 0) {
      for (i = 0; i < this.stride; ++i) {
        closestPoint[i] = flatCoordinates[i];
      }
    } else {
      var delta = this.getRadius() / Math.sqrt(squaredDistance);
      closestPoint[0] = flatCoordinates[0] + delta * dx;
      closestPoint[1] = flatCoordinates[1] + delta * dy;
      for (i = 2; i < this.stride; ++i) {
        closestPoint[i] = flatCoordinates[i];
      }
    }
    closestPoint.length = this.stride;
    return squaredDistance;
  } else {
    return minSquaredDistance;
  }
};


/**
 * @inheritDoc
 */
ol.geom.Circle.prototype.containsXY = function(x, y) {
  var flatCoordinates = this.flatCoordinates;
  var dx = x - flatCoordinates[0];
  var dy = y - flatCoordinates[1];
  return dx * dx + dy * dy <= this.getRadiusSquared_();
};


/**
 * Return the center of the circle as {@link ol.Coordinate coordinate}.
 * @return {ol.Coordinate} Center.
 * @api
 */
ol.geom.Circle.prototype.getCenter = function() {
  return this.flatCoordinates.slice(0, this.stride);
};


/**
 * @inheritDoc
 */
ol.geom.Circle.prototype.computeExtent = function(extent) {
  var flatCoordinates = this.flatCoordinates;
  var radius = flatCoordinates[this.stride] - flatCoordinates[0];
  return ol.extent.createOrUpdate(
      flatCoordinates[0] - radius, flatCoordinates[1] - radius,
      flatCoordinates[0] + radius, flatCoordinates[1] + radius,
      extent);
};


/**
 * Return the radius of the circle.
 * @return {number} Radius.
 * @api
 */
ol.geom.Circle.prototype.getRadius = function() {
  return Math.sqrt(this.getRadiusSquared_());
};


/**
 * @private
 * @return {number} Radius squared.
 */
ol.geom.Circle.prototype.getRadiusSquared_ = function() {
  var dx = this.flatCoordinates[this.stride] - this.flatCoordinates[0];
  var dy = this.flatCoordinates[this.stride + 1] - this.flatCoordinates[1];
  return dx * dx + dy * dy;
};


/**
 * @inheritDoc
 * @api
 */
ol.geom.Circle.prototype.getType = function() {
  return ol.geom.GeometryType.CIRCLE;
};


/**
 * @inheritDoc
 * @api stable
 */
ol.geom.Circle.prototype.intersectsExtent = function(extent) {
  var circleExtent = this.getExtent();
  if (ol.extent.intersects(extent, circleExtent)) {
    var center = this.getCenter();

    if (extent[0] <= center[0] && extent[2] >= center[0]) {
      return true;
    }
    if (extent[1] <= center[1] && extent[3] >= center[1]) {
      return true;
    }

    return ol.extent.forEachCorner(extent, this.intersectsCoordinate, this);
  }
  return false;

};


/**
 * Set the center of the circle as {@link ol.Coordinate coordinate}.
 * @param {ol.Coordinate} center Center.
 * @api
 */
ol.geom.Circle.prototype.setCenter = function(center) {
  var stride = this.stride;
  goog.DEBUG && console.assert(center.length == stride,
      'center array length should match stride');
  var radius = this.flatCoordinates[stride] - this.flatCoordinates[0];
  var flatCoordinates = center.slice();
  flatCoordinates[stride] = flatCoordinates[0] + radius;
  var i;
  for (i = 1; i < stride; ++i) {
    flatCoordinates[stride + i] = center[i];
  }
  this.setFlatCoordinates(this.layout, flatCoordinates);
};


/**
 * Set the center (as {@link ol.Coordinate coordinate}) and the radius (as
 * number) of the circle.
 * @param {ol.Coordinate} center Center.
 * @param {number} radius Radius.
 * @param {ol.geom.GeometryLayout=} opt_layout Layout.
 * @api
 */
ol.geom.Circle.prototype.setCenterAndRadius = function(center, radius, opt_layout) {
  if (!center) {
    this.setFlatCoordinates(ol.geom.GeometryLayout.XY, null);
  } else {
    this.setLayout(opt_layout, center, 0);
    if (!this.flatCoordinates) {
      this.flatCoordinates = [];
    }
    /** @type {Array.<number>} */
    var flatCoordinates = this.flatCoordinates;
    var offset = ol.geom.flat.deflate.coordinate(
        flatCoordinates, 0, center, this.stride);
    flatCoordinates[offset++] = flatCoordinates[0] + radius;
    var i, ii;
    for (i = 1, ii = this.stride; i < ii; ++i) {
      flatCoordinates[offset++] = flatCoordinates[i];
    }
    flatCoordinates.length = offset;
    this.changed();
  }
};


/**
 * @param {ol.geom.GeometryLayout} layout Layout.
 * @param {Array.<number>} flatCoordinates Flat coordinates.
 */
ol.geom.Circle.prototype.setFlatCoordinates = function(layout, flatCoordinates) {
  this.setFlatCoordinatesInternal(layout, flatCoordinates);
  this.changed();
};


/**
 * Set the radius of the circle. The radius is in the units of the projection.
 * @param {number} radius Radius.
 * @api
 */
ol.geom.Circle.prototype.setRadius = function(radius) {
  this.flatCoordinates[this.stride] = this.flatCoordinates[0] + radius;
  this.changed();
};


/**
 * Transform each coordinate of the circle from one coordinate reference system
 * to another. The geometry is modified in place.
 * If you do not want the geometry modified in place, first clone() it and
 * then use this function on the clone.
 *
 * Internally a circle is currently represented by two points: the center of
 * the circle `[cx, cy]`, and the point to the right of the circle
 * `[cx + r, cy]`. This `transform` function just transforms these two points.
 * So the resulting geometry is also a circle, and that circle does not
 * correspond to the shape that would be obtained by transforming every point
 * of the original circle.
 *
 * @param {ol.ProjectionLike} source The current projection.  Can be a
 *     string identifier or a {@link ol.proj.Projection} object.
 * @param {ol.ProjectionLike} destination The desired projection.  Can be a
 *     string identifier or a {@link ol.proj.Projection} object.
 * @return {ol.geom.Circle} This geometry.  Note that original geometry is
 *     modified in place.
 * @function
 * @api stable
 */
ol.geom.Circle.prototype.transform;

goog.provide('ol.geom.flat.geodesic');

goog.require('ol.math');
goog.require('ol.proj');


/**
 * @private
 * @param {function(number): ol.Coordinate} interpolate Interpolate function.
 * @param {ol.TransformFunction} transform Transform from longitude/latitude to
 *     projected coordinates.
 * @param {number} squaredTolerance Squared tolerance.
 * @return {Array.<number>} Flat coordinates.
 */
ol.geom.flat.geodesic.line_ = function(interpolate, transform, squaredTolerance) {
  // FIXME reduce garbage generation
  // FIXME optimize stack operations

  /** @type {Array.<number>} */
  var flatCoordinates = [];

  var geoA = interpolate(0);
  var geoB = interpolate(1);

  var a = transform(geoA);
  var b = transform(geoB);

  /** @type {Array.<ol.Coordinate>} */
  var geoStack = [geoB, geoA];
  /** @type {Array.<ol.Coordinate>} */
  var stack = [b, a];
  /** @type {Array.<number>} */
  var fractionStack = [1, 0];

  /** @type {Object.<string, boolean>} */
  var fractions = {};

  var maxIterations = 1e5;
  var geoM, m, fracA, fracB, fracM, key;

  while (--maxIterations > 0 && fractionStack.length > 0) {
    // Pop the a coordinate off the stack
    fracA = fractionStack.pop();
    geoA = geoStack.pop();
    a = stack.pop();
    // Add the a coordinate if it has not been added yet
    key = fracA.toString();
    if (!(key in fractions)) {
      flatCoordinates.push(a[0], a[1]);
      fractions[key] = true;
    }
    // Pop the b coordinate off the stack
    fracB = fractionStack.pop();
    geoB = geoStack.pop();
    b = stack.pop();
    // Find the m point between the a and b coordinates
    fracM = (fracA + fracB) / 2;
    geoM = interpolate(fracM);
    m = transform(geoM);
    if (ol.math.squaredSegmentDistance(m[0], m[1], a[0], a[1],
        b[0], b[1]) < squaredTolerance) {
      // If the m point is sufficiently close to the straight line, then we
      // discard it.  Just use the b coordinate and move on to the next line
      // segment.
      flatCoordinates.push(b[0], b[1]);
      key = fracB.toString();
      goog.DEBUG && console.assert(!(key in fractions),
          'fractions object should contain key : ' + key);
      fractions[key] = true;
    } else {
      // Otherwise, we need to subdivide the current line segment.  Split it
      // into two and push the two line segments onto the stack.
      fractionStack.push(fracB, fracM, fracM, fracA);
      stack.push(b, m, m, a);
      geoStack.push(geoB, geoM, geoM, geoA);
    }
  }
  goog.DEBUG && console.assert(maxIterations > 0,
      'maxIterations should be more than 0');

  return flatCoordinates;
};


/**
* Generate a great-circle arcs between two lat/lon points.
* @param {number} lon1 Longitude 1 in degrees.
* @param {number} lat1 Latitude 1 in degrees.
* @param {number} lon2 Longitude 2 in degrees.
* @param {number} lat2 Latitude 2 in degrees.
 * @param {ol.proj.Projection} projection Projection.
* @param {number} squaredTolerance Squared tolerance.
* @return {Array.<number>} Flat coordinates.
*/
ol.geom.flat.geodesic.greatCircleArc = function(
    lon1, lat1, lon2, lat2, projection, squaredTolerance) {

  var geoProjection = ol.proj.get('EPSG:4326');

  var cosLat1 = Math.cos(ol.math.toRadians(lat1));
  var sinLat1 = Math.sin(ol.math.toRadians(lat1));
  var cosLat2 = Math.cos(ol.math.toRadians(lat2));
  var sinLat2 = Math.sin(ol.math.toRadians(lat2));
  var cosDeltaLon = Math.cos(ol.math.toRadians(lon2 - lon1));
  var sinDeltaLon = Math.sin(ol.math.toRadians(lon2 - lon1));
  var d = sinLat1 * sinLat2 + cosLat1 * cosLat2 * cosDeltaLon;

  return ol.geom.flat.geodesic.line_(
      /**
       * @param {number} frac Fraction.
       * @return {ol.Coordinate} Coordinate.
       */
      function(frac) {
        if (1 <= d) {
          return [lon2, lat2];
        }
        var D = frac * Math.acos(d);
        var cosD = Math.cos(D);
        var sinD = Math.sin(D);
        var y = sinDeltaLon * cosLat2;
        var x = cosLat1 * sinLat2 - sinLat1 * cosLat2 * cosDeltaLon;
        var theta = Math.atan2(y, x);
        var lat = Math.asin(sinLat1 * cosD + cosLat1 * sinD * Math.cos(theta));
        var lon = ol.math.toRadians(lon1) +
            Math.atan2(Math.sin(theta) * sinD * cosLat1,
                       cosD - sinLat1 * Math.sin(lat));
        return [ol.math.toDegrees(lon), ol.math.toDegrees(lat)];
      }, ol.proj.getTransform(geoProjection, projection), squaredTolerance);
};


/**
 * Generate a meridian (line at constant longitude).
 * @param {number} lon Longitude.
 * @param {number} lat1 Latitude 1.
 * @param {number} lat2 Latitude 2.
 * @param {ol.proj.Projection} projection Projection.
 * @param {number} squaredTolerance Squared tolerance.
 * @return {Array.<number>} Flat coordinates.
 */
ol.geom.flat.geodesic.meridian = function(lon, lat1, lat2, projection, squaredTolerance) {
  var epsg4326Projection = ol.proj.get('EPSG:4326');
  return ol.geom.flat.geodesic.line_(
      /**
       * @param {number} frac Fraction.
       * @return {ol.Coordinate} Coordinate.
       */
      function(frac) {
        return [lon, lat1 + ((lat2 - lat1) * frac)];
      },
      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
};


/**
 * Generate a parallel (line at constant latitude).
 * @param {number} lat Latitude.
 * @param {number} lon1 Longitude 1.
 * @param {number} lon2 Longitude 2.
 * @param {ol.proj.Projection} projection Projection.
 * @param {number} squaredTolerance Squared tolerance.
 * @return {Array.<number>} Flat coordinates.
 */
ol.geom.flat.geodesic.parallel = function(lat, lon1, lon2, projection, squaredTolerance) {
  var epsg4326Projection = ol.proj.get('EPSG:4326');
  return ol.geom.flat.geodesic.line_(
      /**
       * @param {number} frac Fraction.
       * @return {ol.Coordinate} Coordinate.
       */
      function(frac) {
        return [lon1 + ((lon2 - lon1) * frac), lat];
      },
      ol.proj.getTransform(epsg4326Projection, projection), squaredTolerance);
};

goog.provide('ol.Graticule');

goog.require('ol.extent');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.LineString');
goog.require('ol.geom.flat.geodesic');
goog.require('ol.math');
goog.require('ol.proj');
goog.require('ol.render.EventType');
goog.require('ol.style.Stroke');


/**
 * Render a grid for a coordinate system on a map.
 * @constructor
 * @param {olx.GraticuleOptions=} opt_options Options.
 * @api
 */
ol.Graticule = function(opt_options) {

  var options = opt_options || {};

  /**
   * @type {ol.Map}
   * @private
   */
  this.map_ = null;

  /**
   * @type {ol.proj.Projection}
   * @private
   */
  this.projection_ = null;

  /**
   * @type {number}
   * @private
   */
  this.maxLat_ = Infinity;

  /**
   * @type {number}
   * @private
   */
  this.maxLon_ = Infinity;

  /**
   * @type {number}
   * @private
   */
  this.minLat_ = -Infinity;

  /**
   * @type {number}
   * @private
   */
  this.minLon_ = -Infinity;

  /**
   * @type {number}
   * @private
   */
  this.maxLatP_ = Infinity;

  /**
   * @type {number}
   * @private
   */
  this.maxLonP_ = Infinity;

  /**
   * @type {number}
   * @private
   */
  this.minLatP_ = -Infinity;

  /**
   * @type {number}
   * @private
   */
  this.minLonP_ = -Infinity;

  /**
   * @type {number}
   * @private
   */
  this.targetSize_ = options.targetSize !== undefined ?
      options.targetSize : 100;

  /**
   * @type {number}
   * @private
   */
  this.maxLines_ = options.maxLines !== undefined ? options.maxLines : 100;
  goog.DEBUG && console.assert(this.maxLines_ > 0,
      'this.maxLines_ should be more than 0');

  /**
   * @type {Array.<ol.geom.LineString>}
   * @private
   */
  this.meridians_ = [];

  /**
   * @type {Array.<ol.geom.LineString>}
   * @private
   */
  this.parallels_ = [];

  /**
   * @type {ol.style.Stroke}
   * @private
   */
  this.strokeStyle_ = options.strokeStyle !== undefined ?
      options.strokeStyle : ol.Graticule.DEFAULT_STROKE_STYLE_;

  /**
   * @type {ol.TransformFunction|undefined}
   * @private
   */
  this.fromLonLatTransform_ = undefined;

  /**
   * @type {ol.TransformFunction|undefined}
   * @private
   */
  this.toLonLatTransform_ = undefined;

  /**
   * @type {ol.Coordinate}
   * @private
   */
  this.projectionCenterLonLat_ = null;

  this.setMap(options.map !== undefined ? options.map : null);
};


/**
 * @type {ol.style.Stroke}
 * @private
 * @const
 */
ol.Graticule.DEFAULT_STROKE_STYLE_ = new ol.style.Stroke({
  color: 'rgba(0,0,0,0.2)'
});


/**
 * TODO can be configurable
 * @type {Array.<number>}
 * @private
 */
ol.Graticule.intervals_ = [90, 45, 30, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05,
  0.01, 0.005, 0.002, 0.001];


/**
 * @param {number} lon Longitude.
 * @param {number} minLat Minimal latitude.
 * @param {number} maxLat Maximal latitude.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {ol.Extent} extent Extent.
 * @param {number} index Index.
 * @return {number} Index.
 * @private
 */
ol.Graticule.prototype.addMeridian_ = function(lon, minLat, maxLat, squaredTolerance, extent, index) {
  var lineString = this.getMeridian_(lon, minLat, maxLat,
      squaredTolerance, index);
  if (ol.extent.intersects(lineString.getExtent(), extent)) {
    this.meridians_[index++] = lineString;
  }
  return index;
};


/**
 * @param {number} lat Latitude.
 * @param {number} minLon Minimal longitude.
 * @param {number} maxLon Maximal longitude.
 * @param {number} squaredTolerance Squared tolerance.
 * @param {ol.Extent} extent Extent.
 * @param {number} index Index.
 * @return {number} Index.
 * @private
 */
ol.Graticule.prototype.addParallel_ = function(lat, minLon, maxLon, squaredTolerance, extent, index) {
  var lineString = this.getParallel_(lat, minLon, maxLon, squaredTolerance,
      index);
  if (ol.extent.intersects(lineString.getExtent(), extent)) {
    this.parallels_[index++] = lineString;
  }
  return index;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.Coordinate} center Center.
 * @param {number} resolution Resolution.
 * @param {number} squaredTolerance Squared tolerance.
 * @private
 */
ol.Graticule.prototype.createGraticule_ = function(extent, center, resolution, squaredTolerance) {

  var interval = this.getInterval_(resolution);
  if (interval == -1) {
    this.meridians_.length = this.parallels_.length = 0;
    return;
  }

  var centerLonLat = this.toLonLatTransform_(center);
  var centerLon = centerLonLat[0];
  var centerLat = centerLonLat[1];
  var maxLines = this.maxLines_;
  var cnt, idx, lat, lon;

  var validExtent = [
    Math.max(extent[0], this.minLonP_),
    Math.max(extent[1], this.minLatP_),
    Math.min(extent[2], this.maxLonP_),
    Math.min(extent[3], this.maxLatP_)
  ];

  validExtent = ol.proj.transformExtent(validExtent, this.projection_,
      'EPSG:4326');
  var maxLat = validExtent[3];
  var maxLon = validExtent[2];
  var minLat = validExtent[1];
  var minLon = validExtent[0];

  // Create meridians

  centerLon = Math.floor(centerLon / interval) * interval;
  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);

  idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, 0);

  cnt = 0;
  while (lon != this.minLon_ && cnt++ < maxLines) {
    lon = Math.max(lon - interval, this.minLon_);
    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
  }

  lon = ol.math.clamp(centerLon, this.minLon_, this.maxLon_);

  cnt = 0;
  while (lon != this.maxLon_ && cnt++ < maxLines) {
    lon = Math.min(lon + interval, this.maxLon_);
    idx = this.addMeridian_(lon, minLat, maxLat, squaredTolerance, extent, idx);
  }

  this.meridians_.length = idx;

  // Create parallels

  centerLat = Math.floor(centerLat / interval) * interval;
  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);

  idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, 0);

  cnt = 0;
  while (lat != this.minLat_ && cnt++ < maxLines) {
    lat = Math.max(lat - interval, this.minLat_);
    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
  }

  lat = ol.math.clamp(centerLat, this.minLat_, this.maxLat_);

  cnt = 0;
  while (lat != this.maxLat_ && cnt++ < maxLines) {
    lat = Math.min(lat + interval, this.maxLat_);
    idx = this.addParallel_(lat, minLon, maxLon, squaredTolerance, extent, idx);
  }

  this.parallels_.length = idx;

};


/**
 * @param {number} resolution Resolution.
 * @return {number} The interval in degrees.
 * @private
 */
ol.Graticule.prototype.getInterval_ = function(resolution) {
  var centerLon = this.projectionCenterLonLat_[0];
  var centerLat = this.projectionCenterLonLat_[1];
  var interval = -1;
  var i, ii, delta, dist;
  var target = Math.pow(this.targetSize_ * resolution, 2);
  /** @type {Array.<number>} **/
  var p1 = [];
  /** @type {Array.<number>} **/
  var p2 = [];
  for (i = 0, ii = ol.Graticule.intervals_.length; i < ii; ++i) {
    delta = ol.Graticule.intervals_[i] / 2;
    p1[0] = centerLon - delta;
    p1[1] = centerLat - delta;
    p2[0] = centerLon + delta;
    p2[1] = centerLat + delta;
    this.fromLonLatTransform_(p1, p1);
    this.fromLonLatTransform_(p2, p2);
    dist = Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2);
    if (dist <= target) {
      break;
    }
    interval = ol.Graticule.intervals_[i];
  }
  return interval;
};


/**
 * Get the map associated with this graticule.
 * @return {ol.Map} The map.
 * @api
 */
ol.Graticule.prototype.getMap = function() {
  return this.map_;
};


/**
 * @param {number} lon Longitude.
 * @param {number} minLat Minimal latitude.
 * @param {number} maxLat Maximal latitude.
 * @param {number} squaredTolerance Squared tolerance.
 * @return {ol.geom.LineString} The meridian line string.
 * @param {number} index Index.
 * @private
 */
ol.Graticule.prototype.getMeridian_ = function(lon, minLat, maxLat,
                                               squaredTolerance, index) {
  goog.DEBUG && console.assert(lon >= this.minLon_,
      'lon should be larger than or equal to this.minLon_');
  goog.DEBUG && console.assert(lon <= this.maxLon_,
      'lon should be smaller than or equal to this.maxLon_');
  var flatCoordinates = ol.geom.flat.geodesic.meridian(lon,
      minLat, maxLat, this.projection_, squaredTolerance);
  goog.DEBUG && console.assert(flatCoordinates.length > 0,
      'flatCoordinates cannot be empty');
  var lineString = this.meridians_[index] !== undefined ?
      this.meridians_[index] : new ol.geom.LineString(null);
  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
  return lineString;
};


/**
 * Get the list of meridians.  Meridians are lines of equal longitude.
 * @return {Array.<ol.geom.LineString>} The meridians.
 * @api
 */
ol.Graticule.prototype.getMeridians = function() {
  return this.meridians_;
};


/**
 * @param {number} lat Latitude.
 * @param {number} minLon Minimal longitude.
 * @param {number} maxLon Maximal longitude.
 * @param {number} squaredTolerance Squared tolerance.
 * @return {ol.geom.LineString} The parallel line string.
 * @param {number} index Index.
 * @private
 */
ol.Graticule.prototype.getParallel_ = function(lat, minLon, maxLon,
                                               squaredTolerance, index) {
  goog.DEBUG && console.assert(lat >= this.minLat_,
      'lat should be larger than or equal to this.minLat_');
  goog.DEBUG && console.assert(lat <= this.maxLat_,
      'lat should be smaller than or equal to this.maxLat_');
  var flatCoordinates = ol.geom.flat.geodesic.parallel(lat,
      this.minLon_, this.maxLon_, this.projection_, squaredTolerance);
  goog.DEBUG && console.assert(flatCoordinates.length > 0,
      'flatCoordinates cannot be empty');
  var lineString = this.parallels_[index] !== undefined ?
      this.parallels_[index] : new ol.geom.LineString(null);
  lineString.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
  return lineString;
};


/**
 * Get the list of parallels.  Pallels are lines of equal latitude.
 * @return {Array.<ol.geom.LineString>} The parallels.
 * @api
 */
ol.Graticule.prototype.getParallels = function() {
  return this.parallels_;
};


/**
 * @param {ol.render.Event} e Event.
 * @private
 */
ol.Graticule.prototype.handlePostCompose_ = function(e) {
  var vectorContext = e.vectorContext;
  var frameState = e.frameState;
  var extent = frameState.extent;
  var viewState = frameState.viewState;
  var center = viewState.center;
  var projection = viewState.projection;
  var resolution = viewState.resolution;
  var pixelRatio = frameState.pixelRatio;
  var squaredTolerance =
      resolution * resolution / (4 * pixelRatio * pixelRatio);

  var updateProjectionInfo = !this.projection_ ||
      !ol.proj.equivalent(this.projection_, projection);

  if (updateProjectionInfo) {
    this.updateProjectionInfo_(projection);
  }

  //Fix the extent if wrapped.
  //(note: this is the same extent as vectorContext.extent_)
  var offsetX = 0;
  if (projection.canWrapX()) {
    var projectionExtent = projection.getExtent();
    var worldWidth = ol.extent.getWidth(projectionExtent);
    var x = frameState.focus[0];
    if (x < projectionExtent[0] || x > projectionExtent[2]) {
      var worldsAway = Math.ceil((projectionExtent[0] - x) / worldWidth);
      offsetX = worldWidth * worldsAway;
      extent = [
        extent[0] + offsetX, extent[1],
        extent[2] + offsetX, extent[3]
      ];
    }
  }

  this.createGraticule_(extent, center, resolution, squaredTolerance);

  // Draw the lines
  vectorContext.setFillStrokeStyle(null, this.strokeStyle_);
  var i, l, line;
  for (i = 0, l = this.meridians_.length; i < l; ++i) {
    line = this.meridians_[i];
    vectorContext.drawLineString(line, null);
  }
  for (i = 0, l = this.parallels_.length; i < l; ++i) {
    line = this.parallels_[i];
    vectorContext.drawLineString(line, null);
  }
};


/**
 * @param {ol.proj.Projection} projection Projection.
 * @private
 */
ol.Graticule.prototype.updateProjectionInfo_ = function(projection) {
  var epsg4326Projection = ol.proj.get('EPSG:4326');

  var extent = projection.getExtent();
  var worldExtent = projection.getWorldExtent();
  var worldExtentP = ol.proj.transformExtent(worldExtent,
      epsg4326Projection, projection);

  var maxLat = worldExtent[3];
  var maxLon = worldExtent[2];
  var minLat = worldExtent[1];
  var minLon = worldExtent[0];

  var maxLatP = worldExtentP[3];
  var maxLonP = worldExtentP[2];
  var minLatP = worldExtentP[1];
  var minLonP = worldExtentP[0];

  goog.DEBUG && console.assert(maxLat !== undefined, 'maxLat should be defined');
  goog.DEBUG && console.assert(maxLon !== undefined, 'maxLon should be defined');
  goog.DEBUG && console.assert(minLat !== undefined, 'minLat should be defined');
  goog.DEBUG && console.assert(minLon !== undefined, 'minLon should be defined');

  goog.DEBUG && console.assert(maxLatP !== undefined,
      'projected maxLat should be defined');
  goog.DEBUG && console.assert(maxLonP !== undefined,
      'projected maxLon should be defined');
  goog.DEBUG && console.assert(minLatP !== undefined,
      'projected minLat should be defined');
  goog.DEBUG && console.assert(minLonP !== undefined,
      'projected minLon should be defined');

  this.maxLat_ = maxLat;
  this.maxLon_ = maxLon;
  this.minLat_ = minLat;
  this.minLon_ = minLon;

  this.maxLatP_ = maxLatP;
  this.maxLonP_ = maxLonP;
  this.minLatP_ = minLatP;
  this.minLonP_ = minLonP;


  this.fromLonLatTransform_ = ol.proj.getTransform(
      epsg4326Projection, projection);

  this.toLonLatTransform_ = ol.proj.getTransform(
      projection, epsg4326Projection);

  this.projectionCenterLonLat_ = this.toLonLatTransform_(
      ol.extent.getCenter(extent));

  this.projection_ = projection;
};


/**
 * Set the map for this graticule.  The graticule will be rendered on the
 * provided map.
 * @param {ol.Map} map Map.
 * @api
 */
ol.Graticule.prototype.setMap = function(map) {
  if (this.map_) {
    this.map_.un(ol.render.EventType.POSTCOMPOSE,
        this.handlePostCompose_, this);
    this.map_.render();
  }
  if (map) {
    map.on(ol.render.EventType.POSTCOMPOSE,
        this.handlePostCompose_, this);
    map.render();
  }
  this.map_ = map;
};

goog.provide('ol.Image');

goog.require('ol');
goog.require('ol.ImageBase');
goog.require('ol.ImageState');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.obj');


/**
 * @constructor
 * @extends {ol.ImageBase}
 * @param {ol.Extent} extent Extent.
 * @param {number|undefined} resolution Resolution.
 * @param {number} pixelRatio Pixel ratio.
 * @param {Array.<ol.Attribution>} attributions Attributions.
 * @param {string} src Image source URI.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
 */
ol.Image = function(extent, resolution, pixelRatio, attributions, src,
    crossOrigin, imageLoadFunction) {

  ol.ImageBase.call(this, extent, resolution, pixelRatio, ol.ImageState.IDLE,
      attributions);

  /**
   * @private
   * @type {string}
   */
  this.src_ = src;

  /**
   * @private
   * @type {HTMLCanvasElement|Image|HTMLVideoElement}
   */
  this.image_ = new Image();
  if (crossOrigin !== null) {
    this.image_.crossOrigin = crossOrigin;
  }

  /**
   * @private
   * @type {Object.<number, (HTMLCanvasElement|Image|HTMLVideoElement)>}
   */
  this.imageByContext_ = {};

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.imageListenerKeys_ = null;

  /**
   * @protected
   * @type {ol.ImageState}
   */
  this.state = ol.ImageState.IDLE;

  /**
   * @private
   * @type {ol.ImageLoadFunctionType}
   */
  this.imageLoadFunction_ = imageLoadFunction;

};
ol.inherits(ol.Image, ol.ImageBase);


/**
 * Get the HTML image element (may be a Canvas, Image, or Video).
 * @param {Object=} opt_context Object.
 * @return {HTMLCanvasElement|Image|HTMLVideoElement} Image.
 * @api
 */
ol.Image.prototype.getImage = function(opt_context) {
  if (opt_context !== undefined) {
    var image;
    var key = ol.getUid(opt_context);
    if (key in this.imageByContext_) {
      return this.imageByContext_[key];
    } else if (ol.obj.isEmpty(this.imageByContext_)) {
      image = this.image_;
    } else {
      image = /** @type {Image} */ (this.image_.cloneNode(false));
    }
    this.imageByContext_[key] = image;
    return image;
  } else {
    return this.image_;
  }
};


/**
 * Tracks loading or read errors.
 *
 * @private
 */
ol.Image.prototype.handleImageError_ = function() {
  this.state = ol.ImageState.ERROR;
  this.unlistenImage_();
  this.changed();
};


/**
 * Tracks successful image load.
 *
 * @private
 */
ol.Image.prototype.handleImageLoad_ = function() {
  if (this.resolution === undefined) {
    this.resolution = ol.extent.getHeight(this.extent) / this.image_.height;
  }
  this.state = ol.ImageState.LOADED;
  this.unlistenImage_();
  this.changed();
};


/**
 * Load the image or retry if loading previously failed.
 * Loading is taken care of by the tile queue, and calling this method is
 * only needed for preloading or for reloading in case of an error.
 * @api
 */
ol.Image.prototype.load = function() {
  if (this.state == ol.ImageState.IDLE || this.state == ol.ImageState.ERROR) {
    this.state = ol.ImageState.LOADING;
    this.changed();
    goog.DEBUG && console.assert(!this.imageListenerKeys_,
        'this.imageListenerKeys_ should be null');
    this.imageListenerKeys_ = [
      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
          this.handleImageError_, this),
      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
          this.handleImageLoad_, this)
    ];
    this.imageLoadFunction_(this, this.src_);
  }
};


/**
 * @param {HTMLCanvasElement|Image|HTMLVideoElement} image Image.
 */
ol.Image.prototype.setImage = function(image) {
  this.image_ = image;
};


/**
 * Discards event handlers which listen for load completion or errors.
 *
 * @private
 */
ol.Image.prototype.unlistenImage_ = function() {
  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
  this.imageListenerKeys_ = null;
};

goog.provide('ol.ImageTile');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.obj');


/**
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Tile.State} state State.
 * @param {string} src Image source URI.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
 */
ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) {

  ol.Tile.call(this, tileCoord, state);

  /**
   * Image URI
   *
   * @private
   * @type {string}
   */
  this.src_ = src;

  /**
   * @private
   * @type {Image}
   */
  this.image_ = new Image();
  if (crossOrigin !== null) {
    this.image_.crossOrigin = crossOrigin;
  }

  /**
   * @private
   * @type {Object.<number, Image>}
   */
  this.imageByContext_ = {};

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.imageListenerKeys_ = null;

  /**
   * @private
   * @type {ol.TileLoadFunctionType}
   */
  this.tileLoadFunction_ = tileLoadFunction;

};
ol.inherits(ol.ImageTile, ol.Tile);


/**
 * @inheritDoc
 */
ol.ImageTile.prototype.disposeInternal = function() {
  if (this.state == ol.Tile.State.LOADING) {
    this.unlistenImage_();
  }
  if (this.interimTile) {
    this.interimTile.dispose();
  }
  this.state = ol.Tile.State.ABORT;
  this.changed();
  ol.Tile.prototype.disposeInternal.call(this);
};


/**
 * Get the image element for this tile.
 * @inheritDoc
 * @api
 */
ol.ImageTile.prototype.getImage = function(opt_context) {
  if (opt_context !== undefined) {
    var image;
    var key = ol.getUid(opt_context);
    if (key in this.imageByContext_) {
      return this.imageByContext_[key];
    } else if (ol.obj.isEmpty(this.imageByContext_)) {
      image = this.image_;
    } else {
      image = /** @type {Image} */ (this.image_.cloneNode(false));
    }
    this.imageByContext_[key] = image;
    return image;
  } else {
    return this.image_;
  }
};


/**
 * @inheritDoc
 */
ol.ImageTile.prototype.getKey = function() {
  return this.src_;
};


/**
 * Tracks loading or read errors.
 *
 * @private
 */
ol.ImageTile.prototype.handleImageError_ = function() {
  this.state = ol.Tile.State.ERROR;
  this.unlistenImage_();
  this.changed();
};


/**
 * Tracks successful image load.
 *
 * @private
 */
ol.ImageTile.prototype.handleImageLoad_ = function() {
  if (this.image_.naturalWidth && this.image_.naturalHeight) {
    this.state = ol.Tile.State.LOADED;
  } else {
    this.state = ol.Tile.State.EMPTY;
  }
  this.unlistenImage_();
  this.changed();
};


/**
 * Load the image or retry if loading previously failed.
 * Loading is taken care of by the tile queue, and calling this method is
 * only needed for preloading or for reloading in case of an error.
 * @api
 */
ol.ImageTile.prototype.load = function() {
  if (this.state == ol.Tile.State.IDLE || this.state == ol.Tile.State.ERROR) {
    this.state = ol.Tile.State.LOADING;
    this.changed();
    goog.DEBUG && console.assert(!this.imageListenerKeys_,
        'this.imageListenerKeys_ should be null');
    this.imageListenerKeys_ = [
      ol.events.listenOnce(this.image_, ol.events.EventType.ERROR,
          this.handleImageError_, this),
      ol.events.listenOnce(this.image_, ol.events.EventType.LOAD,
          this.handleImageLoad_, this)
    ];
    this.tileLoadFunction_(this, this.src_);
  }
};


/**
 * Discards event handlers which listen for load completion or errors.
 *
 * @private
 */
ol.ImageTile.prototype.unlistenImage_ = function() {
  this.imageListenerKeys_.forEach(ol.events.unlistenByKey);
  this.imageListenerKeys_ = null;
};

// FIXME should handle all geo-referenced data, not just vector data

goog.provide('ol.interaction.DragAndDrop');
goog.provide('ol.interaction.DragAndDropEvent');

goog.require('ol');
goog.require('ol.functions');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.interaction.Interaction');
goog.require('ol.proj');


/**
 * @classdesc
 * Handles input of vector data by drag and drop.
 *
 * @constructor
 * @extends {ol.interaction.Interaction}
 * @fires ol.interaction.DragAndDropEvent
 * @param {olx.interaction.DragAndDropOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DragAndDrop = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.interaction.Interaction.call(this, {
    handleEvent: ol.interaction.DragAndDrop.handleEvent
  });

  /**
   * @private
   * @type {Array.<function(new: ol.format.Feature)>}
   */
  this.formatConstructors_ = options.formatConstructors ?
      options.formatConstructors : [];

  /**
   * @private
   * @type {ol.proj.Projection}
   */
  this.projection_ = options.projection ?
      ol.proj.get(options.projection) : null;

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.dropListenKeys_ = null;

  /**
   * @private
   * @type {Element}
   */
  this.target = options.target ? options.target : null;

};
ol.inherits(ol.interaction.DragAndDrop, ol.interaction.Interaction);


/**
 * @param {Event} event Event.
 * @this {ol.interaction.DragAndDrop}
 * @private
 */
ol.interaction.DragAndDrop.handleDrop_ = function(event) {
  var files = event.dataTransfer.files;
  var i, ii, file;
  for (i = 0, ii = files.length; i < ii; ++i) {
    file = files.item(i);
    var reader = new FileReader();
    reader.addEventListener(ol.events.EventType.LOAD,
        this.handleResult_.bind(this, file));
    reader.readAsText(file);
  }
};


/**
 * @param {Event} event Event.
 * @private
 */
ol.interaction.DragAndDrop.handleStop_ = function(event) {
  event.stopPropagation();
  event.preventDefault();
  event.dataTransfer.dropEffect = 'copy';
};


/**
 * @param {File} file File.
 * @param {Event} event Load event.
 * @private
 */
ol.interaction.DragAndDrop.prototype.handleResult_ = function(file, event) {
  var result = event.target.result;
  var map = this.getMap();
  var projection = this.projection_;
  if (!projection) {
    var view = map.getView();
    projection = view.getProjection();
    goog.DEBUG && console.assert(projection !== undefined,
        'projection should be defined');
  }
  var formatConstructors = this.formatConstructors_;
  var features = [];
  var i, ii;
  for (i = 0, ii = formatConstructors.length; i < ii; ++i) {
    var formatConstructor = formatConstructors[i];
    var format = new formatConstructor();
    features = this.tryReadFeatures_(format, result, {
      featureProjection: projection
    });
    if (features && features.length > 0) {
      break;
    }
  }
  this.dispatchEvent(
      new ol.interaction.DragAndDropEvent(
          ol.interaction.DragAndDropEventType.ADD_FEATURES, file,
          features, projection));
};


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} unconditionally and
 * neither prevents the browser default nor stops event propagation.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.DragAndDrop}
 * @api
 */
ol.interaction.DragAndDrop.handleEvent = ol.functions.TRUE;


/**
 * @inheritDoc
 */
ol.interaction.DragAndDrop.prototype.setMap = function(map) {
  if (this.dropListenKeys_) {
    this.dropListenKeys_.forEach(ol.events.unlistenByKey);
    this.dropListenKeys_ = null;
  }
  ol.interaction.Interaction.prototype.setMap.call(this, map);
  if (map) {
    var dropArea = this.target ? this.target : map.getViewport();
    this.dropListenKeys_ = [
      ol.events.listen(dropArea, ol.events.EventType.DROP,
          ol.interaction.DragAndDrop.handleDrop_, this),
      ol.events.listen(dropArea, ol.events.EventType.DRAGENTER,
          ol.interaction.DragAndDrop.handleStop_, this),
      ol.events.listen(dropArea, ol.events.EventType.DRAGOVER,
          ol.interaction.DragAndDrop.handleStop_, this),
      ol.events.listen(dropArea, ol.events.EventType.DROP,
          ol.interaction.DragAndDrop.handleStop_, this)
    ];
  }
};


/**
 * @param {ol.format.Feature} format Format.
 * @param {string} text Text.
 * @param {olx.format.ReadOptions} options Read options.
 * @private
 * @return {Array.<ol.Feature>} Features.
 */
ol.interaction.DragAndDrop.prototype.tryReadFeatures_ = function(format, text, options) {
  try {
    return format.readFeatures(text, options);
  } catch (e) {
    return null;
  }
};


/**
 * @enum {string}
 */
ol.interaction.DragAndDropEventType = {
  /**
   * Triggered when features are added
   * @event ol.interaction.DragAndDropEvent#addfeatures
   * @api stable
   */
  ADD_FEATURES: 'addfeatures'
};


/**
 * @classdesc
 * Events emitted by {@link ol.interaction.DragAndDrop} instances are instances
 * of this type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.interaction.DragAndDropEvent}
 * @param {ol.interaction.DragAndDropEventType} type Type.
 * @param {File} file File.
 * @param {Array.<ol.Feature>=} opt_features Features.
 * @param {ol.proj.Projection=} opt_projection Projection.
 */
ol.interaction.DragAndDropEvent = function(type, file, opt_features, opt_projection) {

  ol.events.Event.call(this, type);

  /**
   * The features parsed from dropped data.
   * @type {Array.<ol.Feature>|undefined}
   * @api stable
   */
  this.features = opt_features;

  /**
   * The dropped file.
   * @type {File}
   * @api stable
   */
  this.file = file;

  /**
   * The feature projection.
   * @type {ol.proj.Projection|undefined}
   * @api
   */
  this.projection = opt_projection;

};
ol.inherits(ol.interaction.DragAndDropEvent, ol.events.Event);

goog.provide('ol.interaction.DragRotateAndZoom');

goog.require('ol');
goog.require('ol.View');
goog.require('ol.events.condition');
goog.require('ol.interaction.Interaction');
goog.require('ol.interaction.Pointer');


/**
 * @classdesc
 * Allows the user to zoom and rotate the map by clicking and dragging
 * on the map.  By default, this interaction is limited to when the shift
 * key is held down.
 *
 * This interaction is only supported for mouse devices.
 *
 * And this interaction is not included in the default interactions.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.DragRotateAndZoomOptions=} opt_options Options.
 * @api stable
 */
ol.interaction.DragRotateAndZoom = function(opt_options) {

  var options = opt_options ? opt_options : {};

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.DragRotateAndZoom.handleDownEvent_,
    handleDragEvent: ol.interaction.DragRotateAndZoom.handleDragEvent_,
    handleUpEvent: ol.interaction.DragRotateAndZoom.handleUpEvent_
  });

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.shiftKeyOnly;

  /**
   * @private
   * @type {number|undefined}
   */
  this.lastAngle_ = undefined;

  /**
   * @private
   * @type {number|undefined}
   */
  this.lastMagnitude_ = undefined;

  /**
   * @private
   * @type {number}
   */
  this.lastScaleDelta_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.duration_ = options.duration !== undefined ? options.duration : 400;

};
ol.inherits(ol.interaction.DragRotateAndZoom, ol.interaction.Pointer);


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @this {ol.interaction.DragRotateAndZoom}
 * @private
 */
ol.interaction.DragRotateAndZoom.handleDragEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return;
  }

  var map = mapBrowserEvent.map;
  var size = map.getSize();
  var offset = mapBrowserEvent.pixel;
  var deltaX = offset[0] - size[0] / 2;
  var deltaY = size[1] / 2 - offset[1];
  var theta = Math.atan2(deltaY, deltaX);
  var magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
  var view = map.getView();
  if (this.lastAngle_ !== undefined) {
    var angleDelta = theta - this.lastAngle_;
    ol.interaction.Interaction.rotateWithoutConstraints(
        map, view, view.getRotation() - angleDelta);
  }
  this.lastAngle_ = theta;
  if (this.lastMagnitude_ !== undefined) {
    var resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
    ol.interaction.Interaction.zoomWithoutConstraints(map, view, resolution);
  }
  if (this.lastMagnitude_ !== undefined) {
    this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
  }
  this.lastMagnitude_ = magnitude;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.DragRotateAndZoom}
 * @private
 */
ol.interaction.DragRotateAndZoom.handleUpEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return true;
  }

  var map = mapBrowserEvent.map;
  var view = map.getView();
  view.setHint(ol.View.Hint.INTERACTING, -1);
  var direction = this.lastScaleDelta_ - 1;
  ol.interaction.Interaction.rotate(map, view, view.getRotation());
  ol.interaction.Interaction.zoom(map, view, view.getResolution(),
      undefined, this.duration_, direction);
  this.lastScaleDelta_ = 0;
  return false;
};


/**
 * @param {ol.MapBrowserPointerEvent} mapBrowserEvent Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.DragRotateAndZoom}
 * @private
 */
ol.interaction.DragRotateAndZoom.handleDownEvent_ = function(mapBrowserEvent) {
  if (!ol.events.condition.mouseOnly(mapBrowserEvent)) {
    return false;
  }

  if (this.condition_(mapBrowserEvent)) {
    mapBrowserEvent.map.getView().setHint(ol.View.Hint.INTERACTING, 1);
    this.lastAngle_ = undefined;
    this.lastMagnitude_ = undefined;
    return true;
  } else {
    return false;
  }
};

goog.provide('ol.loadingstrategy');


/**
 * Strategy function for loading all features with a single request.
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @return {Array.<ol.Extent>} Extents.
 * @api
 */
ol.loadingstrategy.all = function(extent, resolution) {
  return [[-Infinity, -Infinity, Infinity, Infinity]];
};


/**
 * Strategy function for loading features based on the view's extent and
 * resolution.
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @return {Array.<ol.Extent>} Extents.
 * @api
 */
ol.loadingstrategy.bbox = function(extent, resolution) {
  return [extent];
};


/**
 * Creates a strategy function for loading features based on a tile grid.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @return {function(ol.Extent, number): Array.<ol.Extent>} Loading strategy.
 * @api
 */
ol.loadingstrategy.tile = function(tileGrid) {
  return (
      /**
       * @param {ol.Extent} extent Extent.
       * @param {number} resolution Resolution.
       * @return {Array.<ol.Extent>} Extents.
       */
      function(extent, resolution) {
        var z = tileGrid.getZForResolution(resolution);
        var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
        /** @type {Array.<ol.Extent>} */
        var extents = [];
        /** @type {ol.TileCoord} */
        var tileCoord = [z, 0, 0];
        for (tileCoord[1] = tileRange.minX; tileCoord[1] <= tileRange.maxX;
             ++tileCoord[1]) {
          for (tileCoord[2] = tileRange.minY; tileCoord[2] <= tileRange.maxY;
               ++tileCoord[2]) {
            extents.push(tileGrid.getTileCoordExtent(tileCoord));
          }
        }
        return extents;
      });
};

goog.provide('ol.ext.rbush');
/** @typedef {function(*)} */
ol.ext.rbush;
(function() {
var exports = {};
var module = {exports: exports};
var define;
/**
 * @fileoverview
 * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
 */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.rbush = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
'use strict';

module.exports = partialSort;

// Floyd-Rivest selection algorithm:
// Rearrange items so that all items in the [left, k] range are smaller than all items in (k, right];
// The k-th element will have the (k - left + 1)th smallest value in [left, right]

function partialSort(arr, k, left, right, compare) {
    left = left || 0;
    right = right || (arr.length - 1);
    compare = compare || defaultCompare;

    while (right > left) {
        if (right - left > 600) {
            var n = right - left + 1;
            var m = k - left + 1;
            var z = Math.log(n);
            var s = 0.5 * Math.exp(2 * z / 3);
            var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
            var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
            var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
            partialSort(arr, k, newLeft, newRight, compare);
        }

        var t = arr[k];
        var i = left;
        var j = right;

        swap(arr, left, k);
        if (compare(arr[right], t) > 0) swap(arr, left, right);

        while (i < j) {
            swap(arr, i, j);
            i++;
            j--;
            while (compare(arr[i], t) < 0) i++;
            while (compare(arr[j], t) > 0) j--;
        }

        if (compare(arr[left], t) === 0) swap(arr, left, j);
        else {
            j++;
            swap(arr, j, right);
        }

        if (j <= k) left = j + 1;
        if (k <= j) right = j - 1;
    }
}

function swap(arr, i, j) {
    var tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

function defaultCompare(a, b) {
    return a < b ? -1 : a > b ? 1 : 0;
}

},{}],2:[function(_dereq_,module,exports){
'use strict';

module.exports = rbush;

var quickselect = _dereq_('quickselect');

function rbush(maxEntries, format) {
    if (!(this instanceof rbush)) return new rbush(maxEntries, format);

    // max entries in a node is 9 by default; min node fill is 40% for best performance
    this._maxEntries = Math.max(4, maxEntries || 9);
    this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));

    if (format) {
        this._initFormat(format);
    }

    this.clear();
}

rbush.prototype = {

    all: function () {
        return this._all(this.data, []);
    },

    search: function (bbox) {

        var node = this.data,
            result = [],
            toBBox = this.toBBox;

        if (!intersects(bbox, node)) return result;

        var nodesToSearch = [],
            i, len, child, childBBox;

        while (node) {
            for (i = 0, len = node.children.length; i < len; i++) {

                child = node.children[i];
                childBBox = node.leaf ? toBBox(child) : child;

                if (intersects(bbox, childBBox)) {
                    if (node.leaf) result.push(child);
                    else if (contains(bbox, childBBox)) this._all(child, result);
                    else nodesToSearch.push(child);
                }
            }
            node = nodesToSearch.pop();
        }

        return result;
    },

    collides: function (bbox) {

        var node = this.data,
            toBBox = this.toBBox;

        if (!intersects(bbox, node)) return false;

        var nodesToSearch = [],
            i, len, child, childBBox;

        while (node) {
            for (i = 0, len = node.children.length; i < len; i++) {

                child = node.children[i];
                childBBox = node.leaf ? toBBox(child) : child;

                if (intersects(bbox, childBBox)) {
                    if (node.leaf || contains(bbox, childBBox)) return true;
                    nodesToSearch.push(child);
                }
            }
            node = nodesToSearch.pop();
        }

        return false;
    },

    load: function (data) {
        if (!(data && data.length)) return this;

        if (data.length < this._minEntries) {
            for (var i = 0, len = data.length; i < len; i++) {
                this.insert(data[i]);
            }
            return this;
        }

        // recursively build the tree with the given data from stratch using OMT algorithm
        var node = this._build(data.slice(), 0, data.length - 1, 0);

        if (!this.data.children.length) {
            // save as is if tree is empty
            this.data = node;

        } else if (this.data.height === node.height) {
            // split root if trees have the same height
            this._splitRoot(this.data, node);

        } else {
            if (this.data.height < node.height) {
                // swap trees if inserted one is bigger
                var tmpNode = this.data;
                this.data = node;
                node = tmpNode;
            }

            // insert the small tree into the large tree at appropriate level
            this._insert(node, this.data.height - node.height - 1, true);
        }

        return this;
    },

    insert: function (item) {
        if (item) this._insert(item, this.data.height - 1);
        return this;
    },

    clear: function () {
        this.data = createNode([]);
        return this;
    },

    remove: function (item, equalsFn) {
        if (!item) return this;

        var node = this.data,
            bbox = this.toBBox(item),
            path = [],
            indexes = [],
            i, parent, index, goingUp;

        // depth-first iterative tree traversal
        while (node || path.length) {

            if (!node) { // go up
                node = path.pop();
                parent = path[path.length - 1];
                i = indexes.pop();
                goingUp = true;
            }

            if (node.leaf) { // check current node
                index = findItem(item, node.children, equalsFn);

                if (index !== -1) {
                    // item found, remove the item and condense tree upwards
                    node.children.splice(index, 1);
                    path.push(node);
                    this._condense(path);
                    return this;
                }
            }

            if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
                path.push(node);
                indexes.push(i);
                i = 0;
                parent = node;
                node = node.children[0];

            } else if (parent) { // go right
                i++;
                node = parent.children[i];
                goingUp = false;

            } else node = null; // nothing found
        }

        return this;
    },

    toBBox: function (item) { return item; },

    compareMinX: compareNodeMinX,
    compareMinY: compareNodeMinY,

    toJSON: function () { return this.data; },

    fromJSON: function (data) {
        this.data = data;
        return this;
    },

    _all: function (node, result) {
        var nodesToSearch = [];
        while (node) {
            if (node.leaf) result.push.apply(result, node.children);
            else nodesToSearch.push.apply(nodesToSearch, node.children);

            node = nodesToSearch.pop();
        }
        return result;
    },

    _build: function (items, left, right, height) {

        var N = right - left + 1,
            M = this._maxEntries,
            node;

        if (N <= M) {
            // reached leaf level; return leaf
            node = createNode(items.slice(left, right + 1));
            calcBBox(node, this.toBBox);
            return node;
        }

        if (!height) {
            // target height of the bulk-loaded tree
            height = Math.ceil(Math.log(N) / Math.log(M));

            // target number of root entries to maximize storage utilization
            M = Math.ceil(N / Math.pow(M, height - 1));
        }

        node = createNode([]);
        node.leaf = false;
        node.height = height;

        // split the items into M mostly square tiles

        var N2 = Math.ceil(N / M),
            N1 = N2 * Math.ceil(Math.sqrt(M)),
            i, j, right2, right3;

        multiSelect(items, left, right, N1, this.compareMinX);

        for (i = left; i <= right; i += N1) {

            right2 = Math.min(i + N1 - 1, right);

            multiSelect(items, i, right2, N2, this.compareMinY);

            for (j = i; j <= right2; j += N2) {

                right3 = Math.min(j + N2 - 1, right2);

                // pack each entry recursively
                node.children.push(this._build(items, j, right3, height - 1));
            }
        }

        calcBBox(node, this.toBBox);

        return node;
    },

    _chooseSubtree: function (bbox, node, level, path) {

        var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;

        while (true) {
            path.push(node);

            if (node.leaf || path.length - 1 === level) break;

            minArea = minEnlargement = Infinity;

            for (i = 0, len = node.children.length; i < len; i++) {
                child = node.children[i];
                area = bboxArea(child);
                enlargement = enlargedArea(bbox, child) - area;

                // choose entry with the least area enlargement
                if (enlargement < minEnlargement) {
                    minEnlargement = enlargement;
                    minArea = area < minArea ? area : minArea;
                    targetNode = child;

                } else if (enlargement === minEnlargement) {
                    // otherwise choose one with the smallest area
                    if (area < minArea) {
                        minArea = area;
                        targetNode = child;
                    }
                }
            }

            node = targetNode || node.children[0];
        }

        return node;
    },

    _insert: function (item, level, isNode) {

        var toBBox = this.toBBox,
            bbox = isNode ? item : toBBox(item),
            insertPath = [];

        // find the best node for accommodating the item, saving all nodes along the path too
        var node = this._chooseSubtree(bbox, this.data, level, insertPath);

        // put the item into the node
        node.children.push(item);
        extend(node, bbox);

        // split on node overflow; propagate upwards if necessary
        while (level >= 0) {
            if (insertPath[level].children.length > this._maxEntries) {
                this._split(insertPath, level);
                level--;
            } else break;
        }

        // adjust bboxes along the insertion path
        this._adjustParentBBoxes(bbox, insertPath, level);
    },

    // split overflowed node into two
    _split: function (insertPath, level) {

        var node = insertPath[level],
            M = node.children.length,
            m = this._minEntries;

        this._chooseSplitAxis(node, m, M);

        var splitIndex = this._chooseSplitIndex(node, m, M);

        var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
        newNode.height = node.height;
        newNode.leaf = node.leaf;

        calcBBox(node, this.toBBox);
        calcBBox(newNode, this.toBBox);

        if (level) insertPath[level - 1].children.push(newNode);
        else this._splitRoot(node, newNode);
    },

    _splitRoot: function (node, newNode) {
        // split root node
        this.data = createNode([node, newNode]);
        this.data.height = node.height + 1;
        this.data.leaf = false;
        calcBBox(this.data, this.toBBox);
    },

    _chooseSplitIndex: function (node, m, M) {

        var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;

        minOverlap = minArea = Infinity;

        for (i = m; i <= M - m; i++) {
            bbox1 = distBBox(node, 0, i, this.toBBox);
            bbox2 = distBBox(node, i, M, this.toBBox);

            overlap = intersectionArea(bbox1, bbox2);
            area = bboxArea(bbox1) + bboxArea(bbox2);

            // choose distribution with minimum overlap
            if (overlap < minOverlap) {
                minOverlap = overlap;
                index = i;

                minArea = area < minArea ? area : minArea;

            } else if (overlap === minOverlap) {
                // otherwise choose distribution with minimum area
                if (area < minArea) {
                    minArea = area;
                    index = i;
                }
            }
        }

        return index;
    },

    // sorts node children by the best axis for split
    _chooseSplitAxis: function (node, m, M) {

        var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX,
            compareMinY = node.leaf ? this.compareMinY : compareNodeMinY,
            xMargin = this._allDistMargin(node, m, M, compareMinX),
            yMargin = this._allDistMargin(node, m, M, compareMinY);

        // if total distributions margin value is minimal for x, sort by minX,
        // otherwise it's already sorted by minY
        if (xMargin < yMargin) node.children.sort(compareMinX);
    },

    // total margin of all possible split distributions where each node is at least m full
    _allDistMargin: function (node, m, M, compare) {

        node.children.sort(compare);

        var toBBox = this.toBBox,
            leftBBox = distBBox(node, 0, m, toBBox),
            rightBBox = distBBox(node, M - m, M, toBBox),
            margin = bboxMargin(leftBBox) + bboxMargin(rightBBox),
            i, child;

        for (i = m; i < M - m; i++) {
            child = node.children[i];
            extend(leftBBox, node.leaf ? toBBox(child) : child);
            margin += bboxMargin(leftBBox);
        }

        for (i = M - m - 1; i >= m; i--) {
            child = node.children[i];
            extend(rightBBox, node.leaf ? toBBox(child) : child);
            margin += bboxMargin(rightBBox);
        }

        return margin;
    },

    _adjustParentBBoxes: function (bbox, path, level) {
        // adjust bboxes along the given tree path
        for (var i = level; i >= 0; i--) {
            extend(path[i], bbox);
        }
    },

    _condense: function (path) {
        // go through the path, removing empty nodes and updating bboxes
        for (var i = path.length - 1, siblings; i >= 0; i--) {
            if (path[i].children.length === 0) {
                if (i > 0) {
                    siblings = path[i - 1].children;
                    siblings.splice(siblings.indexOf(path[i]), 1);

                } else this.clear();

            } else calcBBox(path[i], this.toBBox);
        }
    },

    _initFormat: function (format) {
        // data format (minX, minY, maxX, maxY accessors)

        // uses eval-type function compilation instead of just accepting a toBBox function
        // because the algorithms are very sensitive to sorting functions performance,
        // so they should be dead simple and without inner calls

        var compareArr = ['return a', ' - b', ';'];

        this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
        this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));

        this.toBBox = new Function('a',
            'return {minX: a' + format[0] +
            ', minY: a' + format[1] +
            ', maxX: a' + format[2] +
            ', maxY: a' + format[3] + '};');
    }
};

function findItem(item, items, equalsFn) {
    if (!equalsFn) return items.indexOf(item);

    for (var i = 0; i < items.length; i++) {
        if (equalsFn(item, items[i])) return i;
    }
    return -1;
}

// calculate node's bbox from bboxes of its children
function calcBBox(node, toBBox) {
    distBBox(node, 0, node.children.length, toBBox, node);
}

// min bounding rectangle of node children from k to p-1
function distBBox(node, k, p, toBBox, destNode) {
    if (!destNode) destNode = createNode(null);
    destNode.minX = Infinity;
    destNode.minY = Infinity;
    destNode.maxX = -Infinity;
    destNode.maxY = -Infinity;

    for (var i = k, child; i < p; i++) {
        child = node.children[i];
        extend(destNode, node.leaf ? toBBox(child) : child);
    }

    return destNode;
}

function extend(a, b) {
    a.minX = Math.min(a.minX, b.minX);
    a.minY = Math.min(a.minY, b.minY);
    a.maxX = Math.max(a.maxX, b.maxX);
    a.maxY = Math.max(a.maxY, b.maxY);
    return a;
}

function compareNodeMinX(a, b) { return a.minX - b.minX; }
function compareNodeMinY(a, b) { return a.minY - b.minY; }

function bboxArea(a)   { return (a.maxX - a.minX) * (a.maxY - a.minY); }
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }

function enlargedArea(a, b) {
    return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
           (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
}

function intersectionArea(a, b) {
    var minX = Math.max(a.minX, b.minX),
        minY = Math.max(a.minY, b.minY),
        maxX = Math.min(a.maxX, b.maxX),
        maxY = Math.min(a.maxY, b.maxY);

    return Math.max(0, maxX - minX) *
           Math.max(0, maxY - minY);
}

function contains(a, b) {
    return a.minX <= b.minX &&
           a.minY <= b.minY &&
           b.maxX <= a.maxX &&
           b.maxY <= a.maxY;
}

function intersects(a, b) {
    return b.minX <= a.maxX &&
           b.minY <= a.maxY &&
           b.maxX >= a.minX &&
           b.maxY >= a.minY;
}

function createNode(children) {
    return {
        children: children,
        height: 1,
        leaf: true,
        minX: Infinity,
        minY: Infinity,
        maxX: -Infinity,
        maxY: -Infinity
    };
}

// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
// combines selection algorithm with binary divide & conquer approach

function multiSelect(arr, left, right, n, compare) {
    var stack = [left, right],
        mid;

    while (stack.length) {
        right = stack.pop();
        left = stack.pop();

        if (right - left <= n) continue;

        mid = left + Math.ceil((right - left) / n / 2) * n;
        quickselect(arr, mid, left, right, compare);

        stack.push(left, mid, mid, right);
    }
}

},{"quickselect":1}]},{},[2])(2)
});
ol.ext.rbush = module.exports;
})();

goog.provide('ol.structs.RBush');

goog.require('ol');
goog.require('ol.ext.rbush');
goog.require('ol.extent');
goog.require('ol.obj');


/**
 * Wrapper around the RBush by Vladimir Agafonkin.
 *
 * @constructor
 * @param {number=} opt_maxEntries Max entries.
 * @see https://github.com/mourner/rbush
 * @struct
 * @template T
 */
ol.structs.RBush = function(opt_maxEntries) {

  /**
   * @private
   */
  this.rbush_ = ol.ext.rbush(opt_maxEntries);

  /**
   * A mapping between the objects added to this rbush wrapper
   * and the objects that are actually added to the internal rbush.
   * @private
   * @type {Object.<number, ol.RBushEntry>}
   */
  this.items_ = {};

  if (goog.DEBUG) {
    /**
     * @private
     * @type {number}
     */
    this.readers_ = 0;
  }
};


/**
 * Insert a value into the RBush.
 * @param {ol.Extent} extent Extent.
 * @param {T} value Value.
 */
ol.structs.RBush.prototype.insert = function(extent, value) {
  if (goog.DEBUG && this.readers_) {
    throw new Error('Can not insert value while reading');
  }
  /** @type {ol.RBushEntry} */
  var item = {
    minX: extent[0],
    minY: extent[1],
    maxX: extent[2],
    maxY: extent[3],
    value: value
  };

  this.rbush_.insert(item);
  // remember the object that was added to the internal rbush
  goog.DEBUG && console.assert(!(ol.getUid(value) in this.items_),
      'uid (%s) of value (%s) already exists', ol.getUid(value), value);
  this.items_[ol.getUid(value)] = item;
};


/**
 * Bulk-insert values into the RBush.
 * @param {Array.<ol.Extent>} extents Extents.
 * @param {Array.<T>} values Values.
 */
ol.structs.RBush.prototype.load = function(extents, values) {
  if (goog.DEBUG && this.readers_) {
    throw new Error('Can not insert values while reading');
  }
  goog.DEBUG && console.assert(extents.length === values.length,
      'extens and values must have same length (%s === %s)',
      extents.length, values.length);

  var items = new Array(values.length);
  for (var i = 0, l = values.length; i < l; i++) {
    var extent = extents[i];
    var value = values[i];

    /** @type {ol.RBushEntry} */
    var item = {
      minX: extent[0],
      minY: extent[1],
      maxX: extent[2],
      maxY: extent[3],
      value: value
    };
    items[i] = item;
    goog.DEBUG && console.assert(!(ol.getUid(value) in this.items_),
        'uid (%s) of value (%s) already exists', ol.getUid(value), value);
    this.items_[ol.getUid(value)] = item;
  }
  this.rbush_.load(items);
};


/**
 * Remove a value from the RBush.
 * @param {T} value Value.
 * @return {boolean} Removed.
 */
ol.structs.RBush.prototype.remove = function(value) {
  if (goog.DEBUG && this.readers_) {
    throw new Error('Can not remove value while reading');
  }
  var uid = ol.getUid(value);
  goog.DEBUG && console.assert(uid in this.items_,
      'uid (%s) of value (%s) does not exist', uid, value);

  // get the object in which the value was wrapped when adding to the
  // internal rbush. then use that object to do the removal.
  var item = this.items_[uid];
  delete this.items_[uid];
  return this.rbush_.remove(item) !== null;
};


/**
 * Update the extent of a value in the RBush.
 * @param {ol.Extent} extent Extent.
 * @param {T} value Value.
 */
ol.structs.RBush.prototype.update = function(extent, value) {
  goog.DEBUG && console.assert(ol.getUid(value) in this.items_,
      'uid (%s) of value (%s) does not exist', ol.getUid(value), value);

  var item = this.items_[ol.getUid(value)];
  var bbox = [item.minX, item.minY, item.maxX, item.maxY];
  if (!ol.extent.equals(bbox, extent)) {
    if (goog.DEBUG && this.readers_) {
      throw new Error('Can not update extent while reading');
    }
    this.remove(value);
    this.insert(extent, value);
  }
};


/**
 * Return all values in the RBush.
 * @return {Array.<T>} All.
 */
ol.structs.RBush.prototype.getAll = function() {
  var items = this.rbush_.all();
  return items.map(function(item) {
    return item.value;
  });
};


/**
 * Return all values in the given extent.
 * @param {ol.Extent} extent Extent.
 * @return {Array.<T>} All in extent.
 */
ol.structs.RBush.prototype.getInExtent = function(extent) {
  /** @type {ol.RBushEntry} */
  var bbox = {
    minX: extent[0],
    minY: extent[1],
    maxX: extent[2],
    maxY: extent[3]
  };
  var items = this.rbush_.search(bbox);
  return items.map(function(item) {
    return item.value;
  });
};


/**
 * Calls a callback function with each value in the tree.
 * If the callback returns a truthy value, this value is returned without
 * checking the rest of the tree.
 * @param {function(this: S, T): *} callback Callback.
 * @param {S=} opt_this The object to use as `this` in `callback`.
 * @return {*} Callback return value.
 * @template S
 */
ol.structs.RBush.prototype.forEach = function(callback, opt_this) {
  if (goog.DEBUG) {
    ++this.readers_;
    try {
      return this.forEach_(this.getAll(), callback, opt_this);
    } finally {
      --this.readers_;
    }
  } else {
    return this.forEach_(this.getAll(), callback, opt_this);
  }
};


/**
 * Calls a callback function with each value in the provided extent.
 * @param {ol.Extent} extent Extent.
 * @param {function(this: S, T): *} callback Callback.
 * @param {S=} opt_this The object to use as `this` in `callback`.
 * @return {*} Callback return value.
 * @template S
 */
ol.structs.RBush.prototype.forEachInExtent = function(extent, callback, opt_this) {
  if (goog.DEBUG) {
    ++this.readers_;
    try {
      return this.forEach_(this.getInExtent(extent), callback, opt_this);
    } finally {
      --this.readers_;
    }
  } else {
    return this.forEach_(this.getInExtent(extent), callback, opt_this);
  }
};


/**
 * @param {Array.<T>} values Values.
 * @param {function(this: S, T): *} callback Callback.
 * @param {S=} opt_this The object to use as `this` in `callback`.
 * @private
 * @return {*} Callback return value.
 * @template S
 */
ol.structs.RBush.prototype.forEach_ = function(values, callback, opt_this) {
  var result;
  for (var i = 0, l = values.length; i < l; i++) {
    result = callback.call(opt_this, values[i]);
    if (result) {
      return result;
    }
  }
  return result;
};


/**
 * @return {boolean} Is empty.
 */
ol.structs.RBush.prototype.isEmpty = function() {
  return ol.obj.isEmpty(this.items_);
};


/**
 * Remove all values from the RBush.
 */
ol.structs.RBush.prototype.clear = function() {
  this.rbush_.clear();
  this.items_ = {};
};


/**
 * @param {ol.Extent=} opt_extent Extent.
 * @return {!ol.Extent} Extent.
 */
ol.structs.RBush.prototype.getExtent = function(opt_extent) {
  // FIXME add getExtent() to rbush
  var data = this.rbush_.data;
  return [data.minX, data.minY, data.maxX, data.maxY];
};

// FIXME bulk feature upload - suppress events
// FIXME make change-detection more refined (notably, geometry hint)

goog.provide('ol.source.Vector');
goog.provide('ol.source.VectorEvent');
goog.provide('ol.source.VectorEventType');

goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.ObjectEventType');
goog.require('ol.array');
goog.require('ol.asserts');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.featureloader');
goog.require('ol.functions');
goog.require('ol.loadingstrategy');
goog.require('ol.obj');
goog.require('ol.source.Source');
goog.require('ol.source.State');
goog.require('ol.structs.RBush');


/**
 * @enum {string}
 */
ol.source.VectorEventType = {
  /**
   * Triggered when a feature is added to the source.
   * @event ol.source.VectorEvent#addfeature
   * @api stable
   */
  ADDFEATURE: 'addfeature',

  /**
   * Triggered when a feature is updated.
   * @event ol.source.VectorEvent#changefeature
   * @api
   */
  CHANGEFEATURE: 'changefeature',

  /**
   * Triggered when the clear method is called on the source.
   * @event ol.source.VectorEvent#clear
   * @api
   */
  CLEAR: 'clear',

  /**
   * Triggered when a feature is removed from the source.
   * See {@link ol.source.Vector#clear source.clear()} for exceptions.
   * @event ol.source.VectorEvent#removefeature
   * @api stable
   */
  REMOVEFEATURE: 'removefeature'
};


/**
 * @classdesc
 * Provides a source of features for vector layers. Vector features provided
 * by this source are suitable for editing. See {@link ol.source.VectorTile} for
 * vector data that is optimized for rendering.
 *
 * @constructor
 * @extends {ol.source.Source}
 * @fires ol.source.VectorEvent
 * @param {olx.source.VectorOptions=} opt_options Vector source options.
 * @api stable
 */
ol.source.Vector = function(opt_options) {

  var options = opt_options || {};

  ol.source.Source.call(this, {
    attributions: options.attributions,
    logo: options.logo,
    projection: undefined,
    state: ol.source.State.READY,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

  /**
   * @private
   * @type {ol.FeatureLoader}
   */
  this.loader_ = ol.nullFunction;

  /**
   * @private
   * @type {ol.format.Feature|undefined}
   */
  this.format_ = options.format;

  /**
   * @private
   * @type {boolean}
   */
  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;

  /**
   * @private
   * @type {string|ol.FeatureUrlFunction|undefined}
   */
  this.url_ = options.url;

  if (options.loader !== undefined) {
    this.loader_ = options.loader;
  } else if (this.url_ !== undefined) {
    ol.asserts.assert(this.format_, 7); // `format` must be set when `url` is set
    // create a XHR feature loader for "url" and "format"
    this.loader_ = ol.featureloader.xhr(this.url_, /** @type {ol.format.Feature} */ (this.format_));
  }

  /**
   * @private
   * @type {ol.LoadingStrategy}
   */
  this.strategy_ = options.strategy !== undefined ? options.strategy :
      ol.loadingstrategy.all;

  var useSpatialIndex =
      options.useSpatialIndex !== undefined ? options.useSpatialIndex : true;

  /**
   * @private
   * @type {ol.structs.RBush.<ol.Feature>}
   */
  this.featuresRtree_ = useSpatialIndex ? new ol.structs.RBush() : null;

  /**
   * @private
   * @type {ol.structs.RBush.<{extent: ol.Extent}>}
   */
  this.loadedExtentsRtree_ = new ol.structs.RBush();

  /**
   * @private
   * @type {Object.<string, ol.Feature>}
   */
  this.nullGeometryFeatures_ = {};

  /**
   * A lookup of features by id (the return from feature.getId()).
   * @private
   * @type {Object.<string, ol.Feature>}
   */
  this.idIndex_ = {};

  /**
   * A lookup of features without id (keyed by ol.getUid(feature)).
   * @private
   * @type {Object.<string, ol.Feature>}
   */
  this.undefIdIndex_ = {};

  /**
   * @private
   * @type {Object.<string, Array.<ol.EventsKey>>}
   */
  this.featureChangeKeys_ = {};

  /**
   * @private
   * @type {ol.Collection.<ol.Feature>}
   */
  this.featuresCollection_ = null;

  var collection, features;
  if (options.features instanceof ol.Collection) {
    collection = options.features;
    features = collection.getArray();
  } else if (Array.isArray(options.features)) {
    features = options.features;
  }
  if (!useSpatialIndex && collection === undefined) {
    collection = new ol.Collection(features);
  }
  if (features !== undefined) {
    this.addFeaturesInternal(features);
  }
  if (collection !== undefined) {
    this.bindFeaturesCollection_(collection);
  }

};
ol.inherits(ol.source.Vector, ol.source.Source);


/**
 * Add a single feature to the source.  If you want to add a batch of features
 * at once, call {@link ol.source.Vector#addFeatures source.addFeatures()}
 * instead.
 * @param {ol.Feature} feature Feature to add.
 * @api stable
 */
ol.source.Vector.prototype.addFeature = function(feature) {
  this.addFeatureInternal(feature);
  this.changed();
};


/**
 * Add a feature without firing a `change` event.
 * @param {ol.Feature} feature Feature.
 * @protected
 */
ol.source.Vector.prototype.addFeatureInternal = function(feature) {
  var featureKey = ol.getUid(feature).toString();

  if (!this.addToIndex_(featureKey, feature)) {
    return;
  }

  this.setupChangeEvents_(featureKey, feature);

  var geometry = feature.getGeometry();
  if (geometry) {
    var extent = geometry.getExtent();
    if (this.featuresRtree_) {
      this.featuresRtree_.insert(extent, feature);
    }
  } else {
    this.nullGeometryFeatures_[featureKey] = feature;
  }

  this.dispatchEvent(
      new ol.source.VectorEvent(ol.source.VectorEventType.ADDFEATURE, feature));
};


/**
 * @param {string} featureKey Unique identifier for the feature.
 * @param {ol.Feature} feature The feature.
 * @private
 */
ol.source.Vector.prototype.setupChangeEvents_ = function(featureKey, feature) {
  goog.DEBUG && console.assert(!(featureKey in this.featureChangeKeys_),
      'key (%s) not yet registered in featureChangeKey', featureKey);
  this.featureChangeKeys_[featureKey] = [
    ol.events.listen(feature, ol.events.EventType.CHANGE,
        this.handleFeatureChange_, this),
    ol.events.listen(feature, ol.ObjectEventType.PROPERTYCHANGE,
        this.handleFeatureChange_, this)
  ];
};


/**
 * @param {string} featureKey Unique identifier for the feature.
 * @param {ol.Feature} feature The feature.
 * @return {boolean} The feature is "valid", in the sense that it is also a
 *     candidate for insertion into the Rtree.
 * @private
 */
ol.source.Vector.prototype.addToIndex_ = function(featureKey, feature) {
  var valid = true;
  var id = feature.getId();
  if (id !== undefined) {
    if (!(id.toString() in this.idIndex_)) {
      this.idIndex_[id.toString()] = feature;
    } else {
      valid = false;
    }
  } else {
    ol.asserts.assert(!(featureKey in this.undefIdIndex_),
        30); // The passed `feature` was already added to the source
    this.undefIdIndex_[featureKey] = feature;
  }
  return valid;
};


/**
 * Add a batch of features to the source.
 * @param {Array.<ol.Feature>} features Features to add.
 * @api stable
 */
ol.source.Vector.prototype.addFeatures = function(features) {
  this.addFeaturesInternal(features);
  this.changed();
};


/**
 * Add features without firing a `change` event.
 * @param {Array.<ol.Feature>} features Features.
 * @protected
 */
ol.source.Vector.prototype.addFeaturesInternal = function(features) {
  var featureKey, i, length, feature;

  var extents = [];
  var newFeatures = [];
  var geometryFeatures = [];

  for (i = 0, length = features.length; i < length; i++) {
    feature = features[i];
    featureKey = ol.getUid(feature).toString();
    if (this.addToIndex_(featureKey, feature)) {
      newFeatures.push(feature);
    }
  }

  for (i = 0, length = newFeatures.length; i < length; i++) {
    feature = newFeatures[i];
    featureKey = ol.getUid(feature).toString();
    this.setupChangeEvents_(featureKey, feature);

    var geometry = feature.getGeometry();
    if (geometry) {
      var extent = geometry.getExtent();
      extents.push(extent);
      geometryFeatures.push(feature);
    } else {
      this.nullGeometryFeatures_[featureKey] = feature;
    }
  }
  if (this.featuresRtree_) {
    this.featuresRtree_.load(extents, geometryFeatures);
  }

  for (i = 0, length = newFeatures.length; i < length; i++) {
    this.dispatchEvent(new ol.source.VectorEvent(
        ol.source.VectorEventType.ADDFEATURE, newFeatures[i]));
  }
};


/**
 * @param {!ol.Collection.<ol.Feature>} collection Collection.
 * @private
 */
ol.source.Vector.prototype.bindFeaturesCollection_ = function(collection) {
  goog.DEBUG && console.assert(!this.featuresCollection_,
      'bindFeaturesCollection can only be called once');
  var modifyingCollection = false;
  ol.events.listen(this, ol.source.VectorEventType.ADDFEATURE,
      function(evt) {
        if (!modifyingCollection) {
          modifyingCollection = true;
          collection.push(evt.feature);
          modifyingCollection = false;
        }
      });
  ol.events.listen(this, ol.source.VectorEventType.REMOVEFEATURE,
      function(evt) {
        if (!modifyingCollection) {
          modifyingCollection = true;
          collection.remove(evt.feature);
          modifyingCollection = false;
        }
      });
  ol.events.listen(collection, ol.Collection.EventType.ADD,
      function(evt) {
        if (!modifyingCollection) {
          modifyingCollection = true;
          this.addFeature(/** @type {ol.Feature} */ (evt.element));
          modifyingCollection = false;
        }
      }, this);
  ol.events.listen(collection, ol.Collection.EventType.REMOVE,
      function(evt) {
        if (!modifyingCollection) {
          modifyingCollection = true;
          this.removeFeature(/** @type {ol.Feature} */ (evt.element));
          modifyingCollection = false;
        }
      }, this);
  this.featuresCollection_ = collection;
};


/**
 * Remove all features from the source.
 * @param {boolean=} opt_fast Skip dispatching of {@link removefeature} events.
 * @api stable
 */
ol.source.Vector.prototype.clear = function(opt_fast) {
  if (opt_fast) {
    for (var featureId in this.featureChangeKeys_) {
      var keys = this.featureChangeKeys_[featureId];
      keys.forEach(ol.events.unlistenByKey);
    }
    if (!this.featuresCollection_) {
      this.featureChangeKeys_ = {};
      this.idIndex_ = {};
      this.undefIdIndex_ = {};
    }
  } else {
    if (this.featuresRtree_) {
      this.featuresRtree_.forEach(this.removeFeatureInternal, this);
      for (var id in this.nullGeometryFeatures_) {
        this.removeFeatureInternal(this.nullGeometryFeatures_[id]);
      }
    }
  }
  if (this.featuresCollection_) {
    this.featuresCollection_.clear();
  }
  goog.DEBUG && console.assert(ol.obj.isEmpty(this.featureChangeKeys_),
      'featureChangeKeys is an empty object now');
  goog.DEBUG && console.assert(ol.obj.isEmpty(this.idIndex_),
      'idIndex is an empty object now');
  goog.DEBUG && console.assert(ol.obj.isEmpty(this.undefIdIndex_),
      'undefIdIndex is an empty object now');

  if (this.featuresRtree_) {
    this.featuresRtree_.clear();
  }
  this.loadedExtentsRtree_.clear();
  this.nullGeometryFeatures_ = {};

  var clearEvent = new ol.source.VectorEvent(ol.source.VectorEventType.CLEAR);
  this.dispatchEvent(clearEvent);
  this.changed();
};


/**
 * Iterate through all features on the source, calling the provided callback
 * with each one.  If the callback returns any "truthy" value, iteration will
 * stop and the function will return the same value.
 *
 * @param {function(this: T, ol.Feature): S} callback Called with each feature
 *     on the source.  Return a truthy value to stop iteration.
 * @param {T=} opt_this The object to use as `this` in the callback.
 * @return {S|undefined} The return value from the last call to the callback.
 * @template T,S
 * @api stable
 */
ol.source.Vector.prototype.forEachFeature = function(callback, opt_this) {
  if (this.featuresRtree_) {
    return this.featuresRtree_.forEach(callback, opt_this);
  } else if (this.featuresCollection_) {
    return this.featuresCollection_.forEach(callback, opt_this);
  }
};


/**
 * Iterate through all features whose geometries contain the provided
 * coordinate, calling the callback with each feature.  If the callback returns
 * a "truthy" value, iteration will stop and the function will return the same
 * value.
 *
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {function(this: T, ol.Feature): S} callback Called with each feature
 *     whose goemetry contains the provided coordinate.
 * @param {T=} opt_this The object to use as `this` in the callback.
 * @return {S|undefined} The return value from the last call to the callback.
 * @template T,S
 */
ol.source.Vector.prototype.forEachFeatureAtCoordinateDirect = function(coordinate, callback, opt_this) {
  var extent = [coordinate[0], coordinate[1], coordinate[0], coordinate[1]];
  return this.forEachFeatureInExtent(extent, function(feature) {
    var geometry = feature.getGeometry();
    goog.DEBUG && console.assert(geometry, 'feature geometry is defined and not null');
    if (geometry.intersectsCoordinate(coordinate)) {
      return callback.call(opt_this, feature);
    } else {
      return undefined;
    }
  });
};


/**
 * Iterate through all features whose bounding box intersects the provided
 * extent (note that the feature's geometry may not intersect the extent),
 * calling the callback with each feature.  If the callback returns a "truthy"
 * value, iteration will stop and the function will return the same value.
 *
 * If you are interested in features whose geometry intersects an extent, call
 * the {@link ol.source.Vector#forEachFeatureIntersectingExtent
 * source.forEachFeatureIntersectingExtent()} method instead.
 *
 * When `useSpatialIndex` is set to false, this method will loop through all
 * features, equivalent to {@link ol.source.Vector#forEachFeature}.
 *
 * @param {ol.Extent} extent Extent.
 * @param {function(this: T, ol.Feature): S} callback Called with each feature
 *     whose bounding box intersects the provided extent.
 * @param {T=} opt_this The object to use as `this` in the callback.
 * @return {S|undefined} The return value from the last call to the callback.
 * @template T,S
 * @api
 */
ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, callback, opt_this) {
  if (this.featuresRtree_) {
    return this.featuresRtree_.forEachInExtent(extent, callback, opt_this);
  } else if (this.featuresCollection_) {
    return this.featuresCollection_.forEach(callback, opt_this);
  }
};


/**
 * Iterate through all features whose geometry intersects the provided extent,
 * calling the callback with each feature.  If the callback returns a "truthy"
 * value, iteration will stop and the function will return the same value.
 *
 * If you only want to test for bounding box intersection, call the
 * {@link ol.source.Vector#forEachFeatureInExtent
 * source.forEachFeatureInExtent()} method instead.
 *
 * @param {ol.Extent} extent Extent.
 * @param {function(this: T, ol.Feature): S} callback Called with each feature
 *     whose geometry intersects the provided extent.
 * @param {T=} opt_this The object to use as `this` in the callback.
 * @return {S|undefined} The return value from the last call to the callback.
 * @template T,S
 * @api
 */
ol.source.Vector.prototype.forEachFeatureIntersectingExtent = function(extent, callback, opt_this) {
  return this.forEachFeatureInExtent(extent,
      /**
       * @param {ol.Feature} feature Feature.
       * @return {S|undefined} The return value from the last call to the callback.
       * @template S
       */
      function(feature) {
        var geometry = feature.getGeometry();
        goog.DEBUG && console.assert(geometry,
            'feature geometry is defined and not null');
        if (geometry.intersectsExtent(extent)) {
          var result = callback.call(opt_this, feature);
          if (result) {
            return result;
          }
        }
      });
};


/**
 * Get the features collection associated with this source. Will be `null`
 * unless the source was configured with `useSpatialIndex` set to `false`, or
 * with an {@link ol.Collection} as `features`.
 * @return {ol.Collection.<ol.Feature>} The collection of features.
 * @api
 */
ol.source.Vector.prototype.getFeaturesCollection = function() {
  return this.featuresCollection_;
};


/**
 * Get all features on the source.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.source.Vector.prototype.getFeatures = function() {
  var features;
  if (this.featuresCollection_) {
    features = this.featuresCollection_.getArray();
  } else if (this.featuresRtree_) {
    features = this.featuresRtree_.getAll();
    if (!ol.obj.isEmpty(this.nullGeometryFeatures_)) {
      ol.array.extend(
          features, ol.obj.getValues(this.nullGeometryFeatures_));
    }
  }
  return /** @type {Array.<ol.Feature>} */ (features);
};


/**
 * Get all features whose geometry intersects the provided coordinate.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @return {Array.<ol.Feature>} Features.
 * @api stable
 */
ol.source.Vector.prototype.getFeaturesAtCoordinate = function(coordinate) {
  var features = [];
  this.forEachFeatureAtCoordinateDirect(coordinate, function(feature) {
    features.push(feature);
  });
  return features;
};


/**
 * Get all features in the provided extent.  Note that this returns all features
 * whose bounding boxes intersect the given extent (so it may include features
 * whose geometries do not intersect the extent).
 *
 * This method is not available when the source is configured with
 * `useSpatialIndex` set to `false`.
 * @param {ol.Extent} extent Extent.
 * @return {Array.<ol.Feature>} Features.
 * @api
 */
ol.source.Vector.prototype.getFeaturesInExtent = function(extent) {
  goog.DEBUG && console.assert(this.featuresRtree_,
      'getFeaturesInExtent does not work when useSpatialIndex is set to false');
  return this.featuresRtree_.getInExtent(extent);
};


/**
 * Get the closest feature to the provided coordinate.
 *
 * This method is not available when the source is configured with
 * `useSpatialIndex` set to `false`.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {function(ol.Feature):boolean=} opt_filter Feature filter function.
 *     The filter function will receive one argument, the {@link ol.Feature feature}
 *     and it should return a boolean value. By default, no filtering is made.
 * @return {ol.Feature} Closest feature.
 * @api stable
 */
ol.source.Vector.prototype.getClosestFeatureToCoordinate = function(coordinate, opt_filter) {
  // Find the closest feature using branch and bound.  We start searching an
  // infinite extent, and find the distance from the first feature found.  This
  // becomes the closest feature.  We then compute a smaller extent which any
  // closer feature must intersect.  We continue searching with this smaller
  // extent, trying to find a closer feature.  Every time we find a closer
  // feature, we update the extent being searched so that any even closer
  // feature must intersect it.  We continue until we run out of features.
  var x = coordinate[0];
  var y = coordinate[1];
  var closestFeature = null;
  var closestPoint = [NaN, NaN];
  var minSquaredDistance = Infinity;
  var extent = [-Infinity, -Infinity, Infinity, Infinity];
  goog.DEBUG && console.assert(this.featuresRtree_,
      'getClosestFeatureToCoordinate does not work with useSpatialIndex set ' +
      'to false');
  var filter = opt_filter ? opt_filter : ol.functions.TRUE;
  this.featuresRtree_.forEachInExtent(extent,
      /**
       * @param {ol.Feature} feature Feature.
       */
      function(feature) {
        if (filter(feature)) {
          var geometry = feature.getGeometry();
          goog.DEBUG && console.assert(geometry,
              'feature geometry is defined and not null');
          var previousMinSquaredDistance = minSquaredDistance;
          minSquaredDistance = geometry.closestPointXY(
              x, y, closestPoint, minSquaredDistance);
          if (minSquaredDistance < previousMinSquaredDistance) {
            closestFeature = feature;
            // This is sneaky.  Reduce the extent that it is currently being
            // searched while the R-Tree traversal using this same extent object
            // is still in progress.  This is safe because the new extent is
            // strictly contained by the old extent.
            var minDistance = Math.sqrt(minSquaredDistance);
            extent[0] = x - minDistance;
            extent[1] = y - minDistance;
            extent[2] = x + minDistance;
            extent[3] = y + minDistance;
          }
        }
      });
  return closestFeature;
};


/**
 * Get the extent of the features currently in the source.
 *
 * This method is not available when the source is configured with
 * `useSpatialIndex` set to `false`.
 * @return {!ol.Extent} Extent.
 * @api stable
 */
ol.source.Vector.prototype.getExtent = function() {
  goog.DEBUG && console.assert(this.featuresRtree_,
      'getExtent does not work when useSpatialIndex is set to false');
  return this.featuresRtree_.getExtent();
};


/**
 * Get a feature by its identifier (the value returned by feature.getId()).
 * Note that the index treats string and numeric identifiers as the same.  So
 * `source.getFeatureById(2)` will return a feature with id `'2'` or `2`.
 *
 * @param {string|number} id Feature identifier.
 * @return {ol.Feature} The feature (or `null` if not found).
 * @api stable
 */
ol.source.Vector.prototype.getFeatureById = function(id) {
  var feature = this.idIndex_[id.toString()];
  return feature !== undefined ? feature : null;
};


/**
 * Get the format associated with this source.
 *
 * @return {ol.format.Feature|undefined} The feature format.
 * @api
 */
ol.source.Vector.prototype.getFormat = function() {
  return this.format_;
};


/**
 * @return {boolean} The source can have overlapping geometries.
 */
ol.source.Vector.prototype.getOverlaps = function() {
  return this.overlaps_;
};


/**
 * Get the url associated with this source.
 *
 * @return {string|ol.FeatureUrlFunction|undefined} The url.
 * @api
 */
ol.source.Vector.prototype.getUrl = function() {
  return this.url_;
};


/**
 * @param {ol.events.Event} event Event.
 * @private
 */
ol.source.Vector.prototype.handleFeatureChange_ = function(event) {
  var feature = /** @type {ol.Feature} */ (event.target);
  var featureKey = ol.getUid(feature).toString();
  var geometry = feature.getGeometry();
  if (!geometry) {
    if (!(featureKey in this.nullGeometryFeatures_)) {
      if (this.featuresRtree_) {
        this.featuresRtree_.remove(feature);
      }
      this.nullGeometryFeatures_[featureKey] = feature;
    }
  } else {
    var extent = geometry.getExtent();
    if (featureKey in this.nullGeometryFeatures_) {
      delete this.nullGeometryFeatures_[featureKey];
      if (this.featuresRtree_) {
        this.featuresRtree_.insert(extent, feature);
      }
    } else {
      if (this.featuresRtree_) {
        this.featuresRtree_.update(extent, feature);
      }
    }
  }
  var id = feature.getId();
  var removed;
  if (id !== undefined) {
    var sid = id.toString();
    if (featureKey in this.undefIdIndex_) {
      delete this.undefIdIndex_[featureKey];
      this.idIndex_[sid] = feature;
    } else {
      if (this.idIndex_[sid] !== feature) {
        removed = this.removeFromIdIndex_(feature);
        goog.DEBUG && console.assert(removed,
            'Expected feature to be removed from index');
        this.idIndex_[sid] = feature;
      }
    }
  } else {
    if (!(featureKey in this.undefIdIndex_)) {
      removed = this.removeFromIdIndex_(feature);
      goog.DEBUG && console.assert(removed,
          'Expected feature to be removed from index');
      this.undefIdIndex_[featureKey] = feature;
    } else {
      goog.DEBUG && console.assert(this.undefIdIndex_[featureKey] === feature,
          'feature keyed under %s in undefIdKeys', featureKey);
    }
  }
  this.changed();
  this.dispatchEvent(new ol.source.VectorEvent(
      ol.source.VectorEventType.CHANGEFEATURE, feature));
};


/**
 * @return {boolean} Is empty.
 */
ol.source.Vector.prototype.isEmpty = function() {
  return this.featuresRtree_.isEmpty() &&
      ol.obj.isEmpty(this.nullGeometryFeatures_);
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {number} resolution Resolution.
 * @param {ol.proj.Projection} projection Projection.
 */
ol.source.Vector.prototype.loadFeatures = function(
    extent, resolution, projection) {
  var loadedExtentsRtree = this.loadedExtentsRtree_;
  var extentsToLoad = this.strategy_(extent, resolution);
  var i, ii;
  for (i = 0, ii = extentsToLoad.length; i < ii; ++i) {
    var extentToLoad = extentsToLoad[i];
    var alreadyLoaded = loadedExtentsRtree.forEachInExtent(extentToLoad,
        /**
         * @param {{extent: ol.Extent}} object Object.
         * @return {boolean} Contains.
         */
        function(object) {
          return ol.extent.containsExtent(object.extent, extentToLoad);
        });
    if (!alreadyLoaded) {
      this.loader_.call(this, extentToLoad, resolution, projection);
      loadedExtentsRtree.insert(extentToLoad, {extent: extentToLoad.slice()});
    }
  }
};


/**
 * Remove a single feature from the source.  If you want to remove all features
 * at once, use the {@link ol.source.Vector#clear source.clear()} method
 * instead.
 * @param {ol.Feature} feature Feature to remove.
 * @api stable
 */
ol.source.Vector.prototype.removeFeature = function(feature) {
  var featureKey = ol.getUid(feature).toString();
  if (featureKey in this.nullGeometryFeatures_) {
    delete this.nullGeometryFeatures_[featureKey];
  } else {
    if (this.featuresRtree_) {
      this.featuresRtree_.remove(feature);
    }
  }
  this.removeFeatureInternal(feature);
  this.changed();
};


/**
 * Remove feature without firing a `change` event.
 * @param {ol.Feature} feature Feature.
 * @protected
 */
ol.source.Vector.prototype.removeFeatureInternal = function(feature) {
  var featureKey = ol.getUid(feature).toString();
  goog.DEBUG && console.assert(featureKey in this.featureChangeKeys_,
      'featureKey exists in featureChangeKeys');
  this.featureChangeKeys_[featureKey].forEach(ol.events.unlistenByKey);
  delete this.featureChangeKeys_[featureKey];
  var id = feature.getId();
  if (id !== undefined) {
    delete this.idIndex_[id.toString()];
  } else {
    delete this.undefIdIndex_[featureKey];
  }
  this.dispatchEvent(new ol.source.VectorEvent(
      ol.source.VectorEventType.REMOVEFEATURE, feature));
};


/**
 * Remove a feature from the id index.  Called internally when the feature id
 * may have changed.
 * @param {ol.Feature} feature The feature.
 * @return {boolean} Removed the feature from the index.
 * @private
 */
ol.source.Vector.prototype.removeFromIdIndex_ = function(feature) {
  var removed = false;
  for (var id in this.idIndex_) {
    if (this.idIndex_[id] === feature) {
      delete this.idIndex_[id];
      removed = true;
      break;
    }
  }
  return removed;
};


/**
 * @classdesc
 * Events emitted by {@link ol.source.Vector} instances are instances of this
 * type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.source.VectorEvent}
 * @param {string} type Type.
 * @param {ol.Feature=} opt_feature Feature.
 */
ol.source.VectorEvent = function(type, opt_feature) {

  ol.events.Event.call(this, type);

  /**
   * The feature being added or removed.
   * @type {ol.Feature|undefined}
   * @api stable
   */
  this.feature = opt_feature;

};
ol.inherits(ol.source.VectorEvent, ol.events.Event);

goog.provide('ol.interaction.Draw');
goog.provide('ol.interaction.DrawEvent');
goog.provide('ol.interaction.DrawEventType');
goog.provide('ol.interaction.DrawMode');

goog.require('ol');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.Feature');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.Object');
goog.require('ol.coordinate');
goog.require('ol.functions');
goog.require('ol.events.condition');
goog.require('ol.geom.Circle');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.InteractionProperty');
goog.require('ol.interaction.Pointer');
goog.require('ol.layer.Vector');
goog.require('ol.source.Vector');
goog.require('ol.style.Style');


/**
 * @enum {string}
 */
ol.interaction.DrawEventType = {
  /**
   * Triggered upon feature draw start
   * @event ol.interaction.DrawEvent#drawstart
   * @api stable
   */
  DRAWSTART: 'drawstart',
  /**
   * Triggered upon feature draw end
   * @event ol.interaction.DrawEvent#drawend
   * @api stable
   */
  DRAWEND: 'drawend'
};


/**
 * @classdesc
 * Events emitted by {@link ol.interaction.Draw} instances are instances of
 * this type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.DrawEvent}
 * @param {ol.interaction.DrawEventType} type Type.
 * @param {ol.Feature} feature The feature drawn.
 */
ol.interaction.DrawEvent = function(type, feature) {

  ol.events.Event.call(this, type);

  /**
   * The feature being drawn.
   * @type {ol.Feature}
   * @api stable
   */
  this.feature = feature;

};
ol.inherits(ol.interaction.DrawEvent, ol.events.Event);


/**
 * @classdesc
 * Interaction for drawing feature geometries.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @fires ol.interaction.DrawEvent
 * @param {olx.interaction.DrawOptions} options Options.
 * @api stable
 */
ol.interaction.Draw = function(options) {

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.Draw.handleDownEvent_,
    handleEvent: ol.interaction.Draw.handleEvent,
    handleUpEvent: ol.interaction.Draw.handleUpEvent_
  });

  /**
   * @type {ol.Pixel}
   * @private
   */
  this.downPx_ = null;

  /**
   * @type {boolean}
   * @private
   */
  this.freehand_ = false;

  /**
   * Target source for drawn features.
   * @type {ol.source.Vector}
   * @private
   */
  this.source_ = options.source ? options.source : null;

  /**
   * Target collection for drawn features.
   * @type {ol.Collection.<ol.Feature>}
   * @private
   */
  this.features_ = options.features ? options.features : null;

  /**
   * Pixel distance for snapping.
   * @type {number}
   * @private
   */
  this.snapTolerance_ = options.snapTolerance ? options.snapTolerance : 12;

  /**
   * Geometry type.
   * @type {ol.geom.GeometryType}
   * @private
   */
  this.type_ = options.type;

  /**
   * Drawing mode (derived from geometry type.
   * @type {ol.interaction.DrawMode}
   * @private
   */
  this.mode_ = ol.interaction.Draw.getMode_(this.type_);

  /**
   * The number of points that must be drawn before a polygon ring or line
   * string can be finished.  The default is 3 for polygon rings and 2 for
   * line strings.
   * @type {number}
   * @private
   */
  this.minPoints_ = options.minPoints ?
      options.minPoints :
      (this.mode_ === ol.interaction.DrawMode.POLYGON ? 3 : 2);

  /**
   * The number of points that can be drawn before a polygon ring or line string
   * is finished. The default is no restriction.
   * @type {number}
   * @private
   */
  this.maxPoints_ = options.maxPoints ? options.maxPoints : Infinity;

  /**
   * A function to decide if a potential finish coordinate is permissable
   * @private
   * @type {ol.EventsConditionType}
   */
  this.finishCondition_ = options.finishCondition ? options.finishCondition : ol.functions.TRUE;

  var geometryFunction = options.geometryFunction;
  if (!geometryFunction) {
    if (this.type_ === ol.geom.GeometryType.CIRCLE) {
      /**
       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
       *     The coordinates.
       * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry.
       * @return {ol.geom.SimpleGeometry} A geometry.
       */
      geometryFunction = function(coordinates, opt_geometry) {
        var circle = opt_geometry ? /** @type {ol.geom.Circle} */ (opt_geometry) :
            new ol.geom.Circle([NaN, NaN]);
        var squaredLength = ol.coordinate.squaredDistance(
            coordinates[0], coordinates[1]);
        circle.setCenterAndRadius(coordinates[0], Math.sqrt(squaredLength));
        return circle;
      };
    } else {
      var Constructor;
      var mode = this.mode_;
      if (mode === ol.interaction.DrawMode.POINT) {
        Constructor = ol.geom.Point;
      } else if (mode === ol.interaction.DrawMode.LINE_STRING) {
        Constructor = ol.geom.LineString;
      } else if (mode === ol.interaction.DrawMode.POLYGON) {
        Constructor = ol.geom.Polygon;
      }
      /**
       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
       *     The coordinates.
       * @param {ol.geom.SimpleGeometry=} opt_geometry Optional geometry.
       * @return {ol.geom.SimpleGeometry} A geometry.
       */
      geometryFunction = function(coordinates, opt_geometry) {
        var geometry = opt_geometry;
        if (geometry) {
          geometry.setCoordinates(coordinates);
        } else {
          geometry = new Constructor(coordinates);
        }
        return geometry;
      };
    }
  }

  /**
   * @type {ol.DrawGeometryFunctionType}
   * @private
   */
  this.geometryFunction_ = geometryFunction;

  /**
   * Finish coordinate for the feature (first point for polygons, last point for
   * linestrings).
   * @type {ol.Coordinate}
   * @private
   */
  this.finishCoordinate_ = null;

  /**
   * Sketch feature.
   * @type {ol.Feature}
   * @private
   */
  this.sketchFeature_ = null;

  /**
   * Sketch point.
   * @type {ol.Feature}
   * @private
   */
  this.sketchPoint_ = null;

  /**
   * Sketch coordinates. Used when drawing a line or polygon.
   * @type {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>}
   * @private
   */
  this.sketchCoords_ = null;

  /**
   * Sketch line. Used when drawing polygon.
   * @type {ol.Feature}
   * @private
   */
  this.sketchLine_ = null;

  /**
   * Sketch line coordinates. Used when drawing a polygon or circle.
   * @type {Array.<ol.Coordinate>}
   * @private
   */
  this.sketchLineCoords_ = null;

  /**
   * Squared tolerance for handling up events.  If the squared distance
   * between a down and up event is greater than this tolerance, up events
   * will not be handled.
   * @type {number}
   * @private
   */
  this.squaredClickTolerance_ = options.clickTolerance ?
      options.clickTolerance * options.clickTolerance : 36;

  /**
   * Draw overlay where our sketch features are drawn.
   * @type {ol.layer.Vector}
   * @private
   */
  this.overlay_ = new ol.layer.Vector({
    source: new ol.source.Vector({
      useSpatialIndex: false,
      wrapX: options.wrapX ? options.wrapX : false
    }),
    style: options.style ? options.style :
        ol.interaction.Draw.getDefaultStyleFunction()
  });

  /**
   * Name of the geometry attribute for newly created features.
   * @type {string|undefined}
   * @private
   */
  this.geometryName_ = options.geometryName;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.noModifierKeys;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.freehandCondition_ = options.freehandCondition ?
      options.freehandCondition : ol.events.condition.shiftKeyOnly;

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.interaction.InteractionProperty.ACTIVE),
      this.updateState_, this);

};
ol.inherits(ol.interaction.Draw, ol.interaction.Pointer);


/**
 * @return {ol.StyleFunction} Styles.
 */
ol.interaction.Draw.getDefaultStyleFunction = function() {
  var styles = ol.style.Style.createDefaultEditing();
  return function(feature, resolution) {
    return styles[feature.getGeometry().getType()];
  };
};


/**
 * @inheritDoc
 */
ol.interaction.Draw.prototype.setMap = function(map) {
  ol.interaction.Pointer.prototype.setMap.call(this, map);
  this.updateState_();
};


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} and may actually
 * draw or finish the drawing.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.Draw}
 * @api
 */
ol.interaction.Draw.handleEvent = function(mapBrowserEvent) {
  if ((this.mode_ === ol.interaction.DrawMode.LINE_STRING ||
    this.mode_ === ol.interaction.DrawMode.POLYGON) &&
    this.freehandCondition_(mapBrowserEvent)) {
    this.freehand_ = true;
  }
  var pass = !this.freehand_;
  if (this.freehand_ &&
      mapBrowserEvent.type === ol.MapBrowserEvent.EventType.POINTERDRAG) {
    this.addToDrawing_(mapBrowserEvent);
    pass = false;
  } else if (mapBrowserEvent.type ===
      ol.MapBrowserEvent.EventType.POINTERMOVE) {
    pass = this.handlePointerMove_(mapBrowserEvent);
  } else if (mapBrowserEvent.type === ol.MapBrowserEvent.EventType.DBLCLICK) {
    pass = false;
  }
  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) && pass;
};


/**
 * @param {ol.MapBrowserPointerEvent} event Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.Draw}
 * @private
 */
ol.interaction.Draw.handleDownEvent_ = function(event) {
  if (this.condition_(event)) {
    this.downPx_ = event.pixel;
    return true;
  } else if (this.freehand_) {
    this.downPx_ = event.pixel;
    if (!this.finishCoordinate_) {
      this.startDrawing_(event);
    }
    return true;
  } else {
    return false;
  }
};


/**
 * @param {ol.MapBrowserPointerEvent} event Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.Draw}
 * @private
 */
ol.interaction.Draw.handleUpEvent_ = function(event) {
  this.freehand_ = false;
  var downPx = this.downPx_;
  var clickPx = event.pixel;
  var dx = downPx[0] - clickPx[0];
  var dy = downPx[1] - clickPx[1];
  var squaredDistance = dx * dx + dy * dy;
  var pass = true;
  if (squaredDistance <= this.squaredClickTolerance_) {
    this.handlePointerMove_(event);
    if (!this.finishCoordinate_) {
      this.startDrawing_(event);
      if (this.mode_ === ol.interaction.DrawMode.POINT) {
        this.finishDrawing();
      }
    } else if (this.mode_ === ol.interaction.DrawMode.CIRCLE) {
      this.finishDrawing();
    } else if (this.atFinish_(event)) {
      if (this.finishCondition_(event)) {
        this.finishDrawing();
      }
    } else {
      this.addToDrawing_(event);
    }
    pass = false;
  }
  return pass;
};


/**
 * Handle move events.
 * @param {ol.MapBrowserEvent} event A move event.
 * @return {boolean} Pass the event to other interactions.
 * @private
 */
ol.interaction.Draw.prototype.handlePointerMove_ = function(event) {
  if (this.finishCoordinate_) {
    this.modifyDrawing_(event);
  } else {
    this.createOrUpdateSketchPoint_(event);
  }
  return true;
};


/**
 * Determine if an event is within the snapping tolerance of the start coord.
 * @param {ol.MapBrowserEvent} event Event.
 * @return {boolean} The event is within the snapping tolerance of the start.
 * @private
 */
ol.interaction.Draw.prototype.atFinish_ = function(event) {
  var at = false;
  if (this.sketchFeature_) {
    var potentiallyDone = false;
    var potentiallyFinishCoordinates = [this.finishCoordinate_];
    if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
      potentiallyDone = this.sketchCoords_.length > this.minPoints_;
    } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
      potentiallyDone = this.sketchCoords_[0].length >
          this.minPoints_;
      potentiallyFinishCoordinates = [this.sketchCoords_[0][0],
        this.sketchCoords_[0][this.sketchCoords_[0].length - 2]];
    }
    if (potentiallyDone) {
      var map = event.map;
      for (var i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) {
        var finishCoordinate = potentiallyFinishCoordinates[i];
        var finishPixel = map.getPixelFromCoordinate(finishCoordinate);
        var pixel = event.pixel;
        var dx = pixel[0] - finishPixel[0];
        var dy = pixel[1] - finishPixel[1];
        var freehand = this.freehand_ && this.freehandCondition_(event);
        var snapTolerance = freehand ? 1 : this.snapTolerance_;
        at = Math.sqrt(dx * dx + dy * dy) <= snapTolerance;
        if (at) {
          this.finishCoordinate_ = finishCoordinate;
          break;
        }
      }
    }
  }
  return at;
};


/**
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.interaction.Draw.prototype.createOrUpdateSketchPoint_ = function(event) {
  var coordinates = event.coordinate.slice();
  if (!this.sketchPoint_) {
    this.sketchPoint_ = new ol.Feature(new ol.geom.Point(coordinates));
    this.updateSketchFeatures_();
  } else {
    var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry());
    sketchPointGeom.setCoordinates(coordinates);
  }
};


/**
 * Start the drawing.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.interaction.Draw.prototype.startDrawing_ = function(event) {
  var start = event.coordinate;
  this.finishCoordinate_ = start;
  if (this.mode_ === ol.interaction.DrawMode.POINT) {
    this.sketchCoords_ = start.slice();
  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
    this.sketchCoords_ = [[start.slice(), start.slice()]];
    this.sketchLineCoords_ = this.sketchCoords_[0];
  } else {
    this.sketchCoords_ = [start.slice(), start.slice()];
    if (this.mode_ === ol.interaction.DrawMode.CIRCLE) {
      this.sketchLineCoords_ = this.sketchCoords_;
    }
  }
  if (this.sketchLineCoords_) {
    this.sketchLine_ = new ol.Feature(
        new ol.geom.LineString(this.sketchLineCoords_));
  }
  var geometry = this.geometryFunction_(this.sketchCoords_);
  goog.DEBUG && console.assert(geometry !== undefined, 'geometry should be defined');
  this.sketchFeature_ = new ol.Feature();
  if (this.geometryName_) {
    this.sketchFeature_.setGeometryName(this.geometryName_);
  }
  this.sketchFeature_.setGeometry(geometry);
  this.updateSketchFeatures_();
  this.dispatchEvent(new ol.interaction.DrawEvent(
      ol.interaction.DrawEventType.DRAWSTART, this.sketchFeature_));
};


/**
 * Modify the drawing.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.interaction.Draw.prototype.modifyDrawing_ = function(event) {
  var coordinate = event.coordinate;
  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
  var coordinates, last;
  if (this.mode_ === ol.interaction.DrawMode.POINT) {
    last = this.sketchCoords_;
  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
    coordinates = this.sketchCoords_[0];
    last = coordinates[coordinates.length - 1];
    if (this.atFinish_(event)) {
      // snap to finish
      coordinate = this.finishCoordinate_.slice();
    }
  } else {
    coordinates = this.sketchCoords_;
    last = coordinates[coordinates.length - 1];
  }
  last[0] = coordinate[0];
  last[1] = coordinate[1];
  goog.DEBUG && console.assert(this.sketchCoords_, 'sketchCoords_ expected');
  this.geometryFunction_(
      /** @type {!ol.Coordinate|!Array.<ol.Coordinate>|!Array.<Array.<ol.Coordinate>>} */ (this.sketchCoords_),
      geometry);
  if (this.sketchPoint_) {
    var sketchPointGeom = /** @type {ol.geom.Point} */ (this.sketchPoint_.getGeometry());
    sketchPointGeom.setCoordinates(coordinate);
  }
  var sketchLineGeom;
  if (geometry instanceof ol.geom.Polygon &&
      this.mode_ !== ol.interaction.DrawMode.POLYGON) {
    if (!this.sketchLine_) {
      this.sketchLine_ = new ol.Feature(new ol.geom.LineString(null));
    }
    var ring = geometry.getLinearRing(0);
    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
    sketchLineGeom.setFlatCoordinates(
        ring.getLayout(), ring.getFlatCoordinates());
  } else if (this.sketchLineCoords_) {
    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
    sketchLineGeom.setCoordinates(this.sketchLineCoords_);
  }
  this.updateSketchFeatures_();
};


/**
 * Add a new coordinate to the drawing.
 * @param {ol.MapBrowserEvent} event Event.
 * @private
 */
ol.interaction.Draw.prototype.addToDrawing_ = function(event) {
  var coordinate = event.coordinate;
  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
  var done;
  var coordinates;
  if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
    this.finishCoordinate_ = coordinate.slice();
    coordinates = this.sketchCoords_;
    coordinates.push(coordinate.slice());
    done = coordinates.length > this.maxPoints_;
    this.geometryFunction_(coordinates, geometry);
  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
    coordinates = this.sketchCoords_[0];
    coordinates.push(coordinate.slice());
    done = coordinates.length > this.maxPoints_;
    if (done) {
      this.finishCoordinate_ = coordinates[0];
    }
    this.geometryFunction_(this.sketchCoords_, geometry);
  }
  this.updateSketchFeatures_();
  if (done) {
    this.finishDrawing();
  }
};


/**
 * Remove last point of the feature currently being drawn.
 * @api
 */
ol.interaction.Draw.prototype.removeLastPoint = function() {
  var geometry = /** @type {ol.geom.SimpleGeometry} */ (this.sketchFeature_.getGeometry());
  var coordinates, sketchLineGeom;
  if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
    coordinates = this.sketchCoords_;
    coordinates.splice(-2, 1);
    this.geometryFunction_(coordinates, geometry);
  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
    coordinates = this.sketchCoords_[0];
    coordinates.splice(-2, 1);
    sketchLineGeom = /** @type {ol.geom.LineString} */ (this.sketchLine_.getGeometry());
    sketchLineGeom.setCoordinates(coordinates);
    this.geometryFunction_(this.sketchCoords_, geometry);
  }

  if (coordinates.length === 0) {
    this.finishCoordinate_ = null;
  }

  this.updateSketchFeatures_();
};


/**
 * Stop drawing and add the sketch feature to the target layer.
 * The {@link ol.interaction.DrawEventType.DRAWEND} event is dispatched before
 * inserting the feature.
 * @api
 */
ol.interaction.Draw.prototype.finishDrawing = function() {
  var sketchFeature = this.abortDrawing_();
  var coordinates = this.sketchCoords_;
  var geometry = /** @type {ol.geom.SimpleGeometry} */ (sketchFeature.getGeometry());
  if (this.mode_ === ol.interaction.DrawMode.LINE_STRING) {
    // remove the redundant last point
    coordinates.pop();
    this.geometryFunction_(coordinates, geometry);
  } else if (this.mode_ === ol.interaction.DrawMode.POLYGON) {
    // When we finish drawing a polygon on the last point,
    // the last coordinate is duplicated as for LineString
    // we force the replacement by the first point
    coordinates[0].pop();
    coordinates[0].push(coordinates[0][0]);
    this.geometryFunction_(coordinates, geometry);
  }

  // cast multi-part geometries
  if (this.type_ === ol.geom.GeometryType.MULTI_POINT) {
    sketchFeature.setGeometry(new ol.geom.MultiPoint([coordinates]));
  } else if (this.type_ === ol.geom.GeometryType.MULTI_LINE_STRING) {
    sketchFeature.setGeometry(new ol.geom.MultiLineString([coordinates]));
  } else if (this.type_ === ol.geom.GeometryType.MULTI_POLYGON) {
    sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates]));
  }

  // First dispatch event to allow full set up of feature
  this.dispatchEvent(new ol.interaction.DrawEvent(
      ol.interaction.DrawEventType.DRAWEND, sketchFeature));

  // Then insert feature
  if (this.features_) {
    this.features_.push(sketchFeature);
  }
  if (this.source_) {
    this.source_.addFeature(sketchFeature);
  }
};


/**
 * Stop drawing without adding the sketch feature to the target layer.
 * @return {ol.Feature} The sketch feature (or null if none).
 * @private
 */
ol.interaction.Draw.prototype.abortDrawing_ = function() {
  this.finishCoordinate_ = null;
  var sketchFeature = this.sketchFeature_;
  if (sketchFeature) {
    this.sketchFeature_ = null;
    this.sketchPoint_ = null;
    this.sketchLine_ = null;
    this.overlay_.getSource().clear(true);
  }
  return sketchFeature;
};


/**
 * Extend an existing geometry by adding additional points. This only works
 * on features with `LineString` geometries, where the interaction will
 * extend lines by adding points to the end of the coordinates array.
 * @param {!ol.Feature} feature Feature to be extended.
 * @api
 */
ol.interaction.Draw.prototype.extend = function(feature) {
  var geometry = feature.getGeometry();
  goog.DEBUG && console.assert(this.mode_ == ol.interaction.DrawMode.LINE_STRING,
      'interaction mode must be "line"');
  goog.DEBUG && console.assert(geometry.getType() == ol.geom.GeometryType.LINE_STRING,
      'feature geometry must be a line string');
  var lineString = /** @type {ol.geom.LineString} */ (geometry);
  this.sketchFeature_ = feature;
  this.sketchCoords_ = lineString.getCoordinates();
  var last = this.sketchCoords_[this.sketchCoords_.length - 1];
  this.finishCoordinate_ = last.slice();
  this.sketchCoords_.push(last.slice());
  this.updateSketchFeatures_();
  this.dispatchEvent(new ol.interaction.DrawEvent(
      ol.interaction.DrawEventType.DRAWSTART, this.sketchFeature_));
};


/**
 * @inheritDoc
 */
ol.interaction.Draw.prototype.shouldStopEvent = ol.functions.FALSE;


/**
 * Redraw the sketch features.
 * @private
 */
ol.interaction.Draw.prototype.updateSketchFeatures_ = function() {
  var sketchFeatures = [];
  if (this.sketchFeature_) {
    sketchFeatures.push(this.sketchFeature_);
  }
  if (this.sketchLine_) {
    sketchFeatures.push(this.sketchLine_);
  }
  if (this.sketchPoint_) {
    sketchFeatures.push(this.sketchPoint_);
  }
  var overlaySource = this.overlay_.getSource();
  overlaySource.clear(true);
  overlaySource.addFeatures(sketchFeatures);
};


/**
 * @private
 */
ol.interaction.Draw.prototype.updateState_ = function() {
  var map = this.getMap();
  var active = this.getActive();
  if (!map || !active) {
    this.abortDrawing_();
  }
  this.overlay_.setMap(active ? map : null);
};


/**
 * Create a `geometryFunction` for `mode: 'Circle'` that will create a regular
 * polygon with a user specified number of sides and start angle instead of an
 * `ol.geom.Circle` geometry.
 * @param {number=} opt_sides Number of sides of the regular polygon. Default is
 *     32.
 * @param {number=} opt_angle Angle of the first point in radians. 0 means East.
 *     Default is the angle defined by the heading from the center of the
 *     regular polygon to the current pointer position.
 * @return {ol.DrawGeometryFunctionType} Function that draws a
 *     polygon.
 * @api
 */
ol.interaction.Draw.createRegularPolygon = function(opt_sides, opt_angle) {
  return (
      /**
       * @param {ol.Coordinate|Array.<ol.Coordinate>|Array.<Array.<ol.Coordinate>>} coordinates
       * @param {ol.geom.SimpleGeometry=} opt_geometry
       * @return {ol.geom.SimpleGeometry}
       */
      function(coordinates, opt_geometry) {
        var center = coordinates[0];
        var end = coordinates[1];
        var radius = Math.sqrt(
            ol.coordinate.squaredDistance(center, end));
        var geometry = opt_geometry ? /** @type {ol.geom.Polygon} */ (opt_geometry) :
            ol.geom.Polygon.fromCircle(new ol.geom.Circle(center), opt_sides);
        var angle = opt_angle ? opt_angle :
            Math.atan((end[1] - center[1]) / (end[0] - center[0]));
        ol.geom.Polygon.makeRegular(geometry, center, radius, angle);
        return geometry;
      }
  );
};


/**
 * Get the drawing mode.  The mode for mult-part geometries is the same as for
 * their single-part cousins.
 * @param {ol.geom.GeometryType} type Geometry type.
 * @return {ol.interaction.DrawMode} Drawing mode.
 * @private
 */
ol.interaction.Draw.getMode_ = function(type) {
  var mode;
  if (type === ol.geom.GeometryType.POINT ||
      type === ol.geom.GeometryType.MULTI_POINT) {
    mode = ol.interaction.DrawMode.POINT;
  } else if (type === ol.geom.GeometryType.LINE_STRING ||
      type === ol.geom.GeometryType.MULTI_LINE_STRING) {
    mode = ol.interaction.DrawMode.LINE_STRING;
  } else if (type === ol.geom.GeometryType.POLYGON ||
      type === ol.geom.GeometryType.MULTI_POLYGON) {
    mode = ol.interaction.DrawMode.POLYGON;
  } else if (type === ol.geom.GeometryType.CIRCLE) {
    mode = ol.interaction.DrawMode.CIRCLE;
  }
  return /** @type {!ol.interaction.DrawMode} */ (mode);
};


/**
 * Draw mode.  This collapses multi-part geometry types with their single-part
 * cousins.
 * @enum {string}
 */
ol.interaction.DrawMode = {
  POINT: 'Point',
  LINE_STRING: 'LineString',
  POLYGON: 'Polygon',
  CIRCLE: 'Circle'
};

goog.provide('ol.interaction.Modify');
goog.provide('ol.interaction.ModifyEvent');

goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.Feature');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.MapBrowserPointerEvent');
goog.require('ol.View');
goog.require('ol.array');
goog.require('ol.coordinate');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.events.condition');
goog.require('ol.extent');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.Point');
goog.require('ol.interaction.Pointer');
goog.require('ol.layer.Vector');
goog.require('ol.source.Vector');
goog.require('ol.structs.RBush');
goog.require('ol.style.Style');


/**
 * @enum {string}
 */
ol.ModifyEventType = {
  /**
   * Triggered upon feature modification start
   * @event ol.interaction.ModifyEvent#modifystart
   * @api
   */
  MODIFYSTART: 'modifystart',
  /**
   * Triggered upon feature modification end
   * @event ol.interaction.ModifyEvent#modifyend
   * @api
   */
  MODIFYEND: 'modifyend'
};


/**
 * @classdesc
 * Events emitted by {@link ol.interaction.Modify} instances are instances of
 * this type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.ModifyEvent}
 * @param {ol.ModifyEventType} type Type.
 * @param {ol.Collection.<ol.Feature>} features The features modified.
 * @param {ol.MapBrowserPointerEvent} mapBrowserPointerEvent Associated
 *     {@link ol.MapBrowserPointerEvent}.
 */
ol.interaction.ModifyEvent = function(type, features, mapBrowserPointerEvent) {

  ol.events.Event.call(this, type);

  /**
   * The features being modified.
   * @type {ol.Collection.<ol.Feature>}
   * @api
   */
  this.features = features;

  /**
   * Associated {@link ol.MapBrowserEvent}.
   * @type {ol.MapBrowserEvent}
   * @api
   */
  this.mapBrowserEvent = mapBrowserPointerEvent;
};
ol.inherits(ol.interaction.ModifyEvent, ol.events.Event);


/**
 * @classdesc
 * Interaction for modifying feature geometries.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.ModifyOptions} options Options.
 * @fires ol.interaction.ModifyEvent
 * @api
 */
ol.interaction.Modify = function(options) {

  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.Modify.handleDownEvent_,
    handleDragEvent: ol.interaction.Modify.handleDragEvent_,
    handleEvent: ol.interaction.Modify.handleEvent,
    handleUpEvent: ol.interaction.Modify.handleUpEvent_
  });

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.primaryAction;


  /**
   * @private
   * @param {ol.MapBrowserEvent} mapBrowserEvent Browser event.
   * @return {boolean} Combined condition result.
   */
  this.defaultDeleteCondition_ = function(mapBrowserEvent) {
    return ol.events.condition.noModifierKeys(mapBrowserEvent) &&
      ol.events.condition.singleClick(mapBrowserEvent);
  };

  /**
   * @type {ol.EventsConditionType}
   * @private
   */
  this.deleteCondition_ = options.deleteCondition ?
      options.deleteCondition : this.defaultDeleteCondition_;

  /**
   * Editing vertex.
   * @type {ol.Feature}
   * @private
   */
  this.vertexFeature_ = null;

  /**
   * Segments intersecting {@link this.vertexFeature_} by segment uid.
   * @type {Object.<string, boolean>}
   * @private
   */
  this.vertexSegments_ = null;

  /**
   * @type {ol.Pixel}
   * @private
   */
  this.lastPixel_ = [0, 0];

  /**
   * Tracks if the next `singleclick` event should be ignored to prevent
   * accidental deletion right after vertex creation.
   * @type {boolean}
   * @private
   */
  this.ignoreNextSingleClick_ = false;

  /**
   * @type {boolean}
   * @private
   */
  this.modified_ = false;

  /**
   * Segment RTree for each layer
   * @type {ol.structs.RBush.<ol.ModifySegmentDataType>}
   * @private
   */
  this.rBush_ = new ol.structs.RBush();

  /**
   * @type {number}
   * @private
   */
  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
      options.pixelTolerance : 10;

  /**
   * @type {boolean}
   * @private
   */
  this.snappedToVertex_ = false;

  /**
   * Indicate whether the interaction is currently changing a feature's
   * coordinates.
   * @type {boolean}
   * @private
   */
  this.changingFeature_ = false;

  /**
   * @type {Array}
   * @private
   */
  this.dragSegments_ = [];

  /**
   * Draw overlay where sketch features are drawn.
   * @type {ol.layer.Vector}
   * @private
   */
  this.overlay_ = new ol.layer.Vector({
    source: new ol.source.Vector({
      useSpatialIndex: false,
      wrapX: !!options.wrapX
    }),
    style: options.style ? options.style :
        ol.interaction.Modify.getDefaultStyleFunction(),
    updateWhileAnimating: true,
    updateWhileInteracting: true
  });

  /**
  * @const
  * @private
  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>}
  */
  this.SEGMENT_WRITERS_ = {
    'Point': this.writePointGeometry_,
    'LineString': this.writeLineStringGeometry_,
    'LinearRing': this.writeLineStringGeometry_,
    'Polygon': this.writePolygonGeometry_,
    'MultiPoint': this.writeMultiPointGeometry_,
    'MultiLineString': this.writeMultiLineStringGeometry_,
    'MultiPolygon': this.writeMultiPolygonGeometry_,
    'GeometryCollection': this.writeGeometryCollectionGeometry_
  };

  /**
   * @type {ol.Collection.<ol.Feature>}
   * @private
   */
  this.features_ = options.features;

  this.features_.forEach(this.addFeature_, this);
  ol.events.listen(this.features_, ol.Collection.EventType.ADD,
      this.handleFeatureAdd_, this);
  ol.events.listen(this.features_, ol.Collection.EventType.REMOVE,
      this.handleFeatureRemove_, this);

  /**
   * @type {ol.MapBrowserPointerEvent}
   * @private
   */
  this.lastPointerEvent_ = null;

};
ol.inherits(ol.interaction.Modify, ol.interaction.Pointer);


/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.interaction.Modify.prototype.addFeature_ = function(feature) {
  var geometry = feature.getGeometry();
  if (geometry && geometry.getType() in this.SEGMENT_WRITERS_) {
    this.SEGMENT_WRITERS_[geometry.getType()].call(this, feature, geometry);
  }
  var map = this.getMap();
  if (map) {
    this.handlePointerAtPixel_(this.lastPixel_, map);
  }
  ol.events.listen(feature, ol.events.EventType.CHANGE,
      this.handleFeatureChange_, this);
};


/**
 * @param {ol.MapBrowserPointerEvent} evt Map browser event
 * @private
 */
ol.interaction.Modify.prototype.willModifyFeatures_ = function(evt) {
  if (!this.modified_) {
    this.modified_ = true;
    this.dispatchEvent(new ol.interaction.ModifyEvent(
        ol.ModifyEventType.MODIFYSTART, this.features_, evt));
  }
};


/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.interaction.Modify.prototype.removeFeature_ = function(feature) {
  this.removeFeatureSegmentData_(feature);
  // Remove the vertex feature if the collection of canditate features
  // is empty.
  if (this.vertexFeature_ && this.features_.getLength() === 0) {
    this.overlay_.getSource().removeFeature(this.vertexFeature_);
    this.vertexFeature_ = null;
  }
  ol.events.unlisten(feature, ol.events.EventType.CHANGE,
      this.handleFeatureChange_, this);
};


/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.interaction.Modify.prototype.removeFeatureSegmentData_ = function(feature) {
  var rBush = this.rBush_;
  var /** @type {Array.<ol.ModifySegmentDataType>} */ nodesToRemove = [];
  rBush.forEach(
      /**
       * @param {ol.ModifySegmentDataType} node RTree node.
       */
      function(node) {
        if (feature === node.feature) {
          nodesToRemove.push(node);
        }
      });
  for (var i = nodesToRemove.length - 1; i >= 0; --i) {
    rBush.remove(nodesToRemove[i]);
  }
};


/**
 * @inheritDoc
 */
ol.interaction.Modify.prototype.setMap = function(map) {
  this.overlay_.setMap(map);
  ol.interaction.Pointer.prototype.setMap.call(this, map);
};


/**
 * @param {ol.Collection.Event} evt Event.
 * @private
 */
ol.interaction.Modify.prototype.handleFeatureAdd_ = function(evt) {
  this.addFeature_(/** @type {ol.Feature} */ (evt.element));
};


/**
 * @param {ol.events.Event} evt Event.
 * @private
 */
ol.interaction.Modify.prototype.handleFeatureChange_ = function(evt) {
  if (!this.changingFeature_) {
    var feature = /** @type {ol.Feature} */ (evt.target);
    this.removeFeature_(feature);
    this.addFeature_(feature);
  }
};


/**
 * @param {ol.Collection.Event} evt Event.
 * @private
 */
ol.interaction.Modify.prototype.handleFeatureRemove_ = function(evt) {
  var feature = /** @type {ol.Feature} */ (evt.element);
  this.removeFeature_(feature);
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.Point} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writePointGeometry_ = function(feature, geometry) {
  var coordinates = geometry.getCoordinates();
  var segmentData = /** @type {ol.ModifySegmentDataType} */ ({
    feature: feature,
    geometry: geometry,
    segment: [coordinates, coordinates]
  });
  this.rBush_.insert(geometry.getExtent(), segmentData);
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiPoint} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writeMultiPointGeometry_ = function(feature, geometry) {
  var points = geometry.getCoordinates();
  var coordinates, i, ii, segmentData;
  for (i = 0, ii = points.length; i < ii; ++i) {
    coordinates = points[i];
    segmentData = /** @type {ol.ModifySegmentDataType} */ ({
      feature: feature,
      geometry: geometry,
      depth: [i],
      index: i,
      segment: [coordinates, coordinates]
    });
    this.rBush_.insert(geometry.getExtent(), segmentData);
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.LineString} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writeLineStringGeometry_ = function(feature, geometry) {
  var coordinates = geometry.getCoordinates();
  var i, ii, segment, segmentData;
  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
    segment = coordinates.slice(i, i + 2);
    segmentData = /** @type {ol.ModifySegmentDataType} */ ({
      feature: feature,
      geometry: geometry,
      index: i,
      segment: segment
    });
    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiLineString} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) {
  var lines = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, segment, segmentData;
  for (j = 0, jj = lines.length; j < jj; ++j) {
    coordinates = lines[j];
    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = /** @type {ol.ModifySegmentDataType} */ ({
        feature: feature,
        geometry: geometry,
        depth: [j],
        index: i,
        segment: segment
      });
      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
    }
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.Polygon} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writePolygonGeometry_ = function(feature, geometry) {
  var rings = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, segment, segmentData;
  for (j = 0, jj = rings.length; j < jj; ++j) {
    coordinates = rings[j];
    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = /** @type {ol.ModifySegmentDataType} */ ({
        feature: feature,
        geometry: geometry,
        depth: [j],
        index: i,
        segment: segment
      });
      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
    }
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiPolygon} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) {
  var polygons = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
  for (k = 0, kk = polygons.length; k < kk; ++k) {
    rings = polygons[k];
    for (j = 0, jj = rings.length; j < jj; ++j) {
      coordinates = rings[j];
      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
        segment = coordinates.slice(i, i + 2);
        segmentData = /** @type {ol.ModifySegmentDataType} */ ({
          feature: feature,
          geometry: geometry,
          depth: [j, k],
          index: i,
          segment: segment
        });
        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
      }
    }
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.GeometryCollection} geometry Geometry.
 * @private
 */
ol.interaction.Modify.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) {
  var i, geometries = geometry.getGeometriesArray();
  for (i = 0; i < geometries.length; ++i) {
    this.SEGMENT_WRITERS_[geometries[i].getType()].call(
        this, feature, geometries[i]);
  }
};


/**
 * @param {ol.Coordinate} coordinates Coordinates.
 * @return {ol.Feature} Vertex feature.
 * @private
 */
ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = function(coordinates) {
  var vertexFeature = this.vertexFeature_;
  if (!vertexFeature) {
    vertexFeature = new ol.Feature(new ol.geom.Point(coordinates));
    this.vertexFeature_ = vertexFeature;
    this.overlay_.getSource().addFeature(vertexFeature);
  } else {
    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
    geometry.setCoordinates(coordinates);
  }
  return vertexFeature;
};


/**
 * @param {ol.ModifySegmentDataType} a The first segment data.
 * @param {ol.ModifySegmentDataType} b The second segment data.
 * @return {number} The difference in indexes.
 * @private
 */
ol.interaction.Modify.compareIndexes_ = function(a, b) {
  return a.index - b.index;
};


/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.Modify}
 * @private
 */
ol.interaction.Modify.handleDownEvent_ = function(evt) {
  if (!this.condition_(evt)) {
    return false;
  }
  this.handlePointerAtPixel_(evt.pixel, evt.map);
  this.dragSegments_.length = 0;
  this.modified_ = false;
  var vertexFeature = this.vertexFeature_;
  if (vertexFeature) {
    var insertVertices = [];
    var geometry = /** @type {ol.geom.Point} */ (vertexFeature.getGeometry());
    var vertex = geometry.getCoordinates();
    var vertexExtent = ol.extent.boundingExtent([vertex]);
    var segmentDataMatches = this.rBush_.getInExtent(vertexExtent);
    var componentSegments = {};
    segmentDataMatches.sort(ol.interaction.Modify.compareIndexes_);
    for (var i = 0, ii = segmentDataMatches.length; i < ii; ++i) {
      var segmentDataMatch = segmentDataMatches[i];
      var segment = segmentDataMatch.segment;
      var uid = ol.getUid(segmentDataMatch.feature);
      var depth = segmentDataMatch.depth;
      if (depth) {
        uid += '-' + depth.join('-'); // separate feature components
      }
      if (!componentSegments[uid]) {
        componentSegments[uid] = new Array(2);
      }
      if (ol.coordinate.equals(segment[0], vertex) &&
          !componentSegments[uid][0]) {
        this.dragSegments_.push([segmentDataMatch, 0]);
        componentSegments[uid][0] = segmentDataMatch;
      } else if (ol.coordinate.equals(segment[1], vertex) &&
          !componentSegments[uid][1]) {

        // prevent dragging closed linestrings by the connecting node
        if ((segmentDataMatch.geometry.getType() ===
            ol.geom.GeometryType.LINE_STRING ||
            segmentDataMatch.geometry.getType() ===
            ol.geom.GeometryType.MULTI_LINE_STRING) &&
            componentSegments[uid][0] &&
            componentSegments[uid][0].index === 0) {
          continue;
        }

        this.dragSegments_.push([segmentDataMatch, 1]);
        componentSegments[uid][1] = segmentDataMatch;
      } else if (ol.getUid(segment) in this.vertexSegments_ &&
          (!componentSegments[uid][0] && !componentSegments[uid][1])) {
        insertVertices.push([segmentDataMatch, vertex]);
      }
    }
    if (insertVertices.length) {
      this.willModifyFeatures_(evt);
    }
    for (var j = insertVertices.length - 1; j >= 0; --j) {
      this.insertVertex_.apply(this, insertVertices[j]);
    }
  }
  return !!this.vertexFeature_;
};


/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @this {ol.interaction.Modify}
 * @private
 */
ol.interaction.Modify.handleDragEvent_ = function(evt) {
  this.ignoreNextSingleClick_ = false;
  this.willModifyFeatures_(evt);

  var vertex = evt.coordinate;
  for (var i = 0, ii = this.dragSegments_.length; i < ii; ++i) {
    var dragSegment = this.dragSegments_[i];
    var segmentData = dragSegment[0];
    var depth = segmentData.depth;
    var geometry = segmentData.geometry;
    var coordinates = geometry.getCoordinates();
    var segment = segmentData.segment;
    var index = dragSegment[1];

    while (vertex.length < geometry.getStride()) {
      vertex.push(0);
    }

    switch (geometry.getType()) {
      case ol.geom.GeometryType.POINT:
        coordinates = vertex;
        segment[0] = segment[1] = vertex;
        break;
      case ol.geom.GeometryType.MULTI_POINT:
        coordinates[segmentData.index] = vertex;
        segment[0] = segment[1] = vertex;
        break;
      case ol.geom.GeometryType.LINE_STRING:
        coordinates[segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case ol.geom.GeometryType.MULTI_LINE_STRING:
        coordinates[depth[0]][segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case ol.geom.GeometryType.POLYGON:
        coordinates[depth[0]][segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      case ol.geom.GeometryType.MULTI_POLYGON:
        coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex;
        segment[index] = vertex;
        break;
      default:
        // pass
    }

    this.setGeometryCoordinates_(geometry, coordinates);
  }
  this.createOrUpdateVertexFeature_(vertex);
};


/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.Modify}
 * @private
 */
ol.interaction.Modify.handleUpEvent_ = function(evt) {
  var segmentData;
  for (var i = this.dragSegments_.length - 1; i >= 0; --i) {
    segmentData = this.dragSegments_[i][0];
    this.rBush_.update(ol.extent.boundingExtent(segmentData.segment),
        segmentData);
  }
  if (this.modified_) {
    this.dispatchEvent(new ol.interaction.ModifyEvent(
        ol.ModifyEventType.MODIFYEND, this.features_, evt));
    this.modified_ = false;
  }
  return false;
};


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} and may modify the
 * geometry.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.Modify}
 * @api
 */
ol.interaction.Modify.handleEvent = function(mapBrowserEvent) {
  if (!(mapBrowserEvent instanceof ol.MapBrowserPointerEvent)) {
    return true;
  }
  this.lastPointerEvent_ = mapBrowserEvent;

  var handled;
  if (!mapBrowserEvent.map.getView().getHints()[ol.View.Hint.INTERACTING] &&
      mapBrowserEvent.type == ol.MapBrowserEvent.EventType.POINTERMOVE &&
      !this.handlingDownUpSequence) {
    this.handlePointerMove_(mapBrowserEvent);
  }
  if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) {
    if (mapBrowserEvent.type != ol.MapBrowserEvent.EventType.SINGLECLICK ||
        !this.ignoreNextSingleClick_) {
      handled = this.removePoint();
    } else {
      handled = true;
    }
  }

  if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.SINGLECLICK) {
    this.ignoreNextSingleClick_ = false;
  }

  return ol.interaction.Pointer.handleEvent.call(this, mapBrowserEvent) &&
      !handled;
};


/**
 * @param {ol.MapBrowserEvent} evt Event.
 * @private
 */
ol.interaction.Modify.prototype.handlePointerMove_ = function(evt) {
  this.lastPixel_ = evt.pixel;
  this.handlePointerAtPixel_(evt.pixel, evt.map);
};


/**
 * @param {ol.Pixel} pixel Pixel
 * @param {ol.Map} map Map.
 * @private
 */
ol.interaction.Modify.prototype.handlePointerAtPixel_ = function(pixel, map) {
  var pixelCoordinate = map.getCoordinateFromPixel(pixel);
  var sortByDistance = function(a, b) {
    return ol.coordinate.squaredDistanceToSegment(pixelCoordinate, a.segment) -
        ol.coordinate.squaredDistanceToSegment(pixelCoordinate, b.segment);
  };

  var lowerLeft = map.getCoordinateFromPixel(
      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
  var upperRight = map.getCoordinateFromPixel(
      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);

  var rBush = this.rBush_;
  var nodes = rBush.getInExtent(box);
  if (nodes.length > 0) {
    nodes.sort(sortByDistance);
    var node = nodes[0];
    var closestSegment = node.segment;
    var vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
        closestSegment));
    var vertexPixel = map.getPixelFromCoordinate(vertex);
    if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
        this.pixelTolerance_) {
      var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
      var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
      var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
      var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
      this.snappedToVertex_ = dist <= this.pixelTolerance_;
      if (this.snappedToVertex_) {
        vertex = squaredDist1 > squaredDist2 ?
            closestSegment[1] : closestSegment[0];
      }
      this.createOrUpdateVertexFeature_(vertex);
      var vertexSegments = {};
      vertexSegments[ol.getUid(closestSegment)] = true;
      var segment;
      for (var i = 1, ii = nodes.length; i < ii; ++i) {
        segment = nodes[i].segment;
        if ((ol.coordinate.equals(closestSegment[0], segment[0]) &&
            ol.coordinate.equals(closestSegment[1], segment[1]) ||
            (ol.coordinate.equals(closestSegment[0], segment[1]) &&
            ol.coordinate.equals(closestSegment[1], segment[0])))) {
          vertexSegments[ol.getUid(segment)] = true;
        } else {
          break;
        }
      }
      this.vertexSegments_ = vertexSegments;
      return;
    }
  }
  if (this.vertexFeature_) {
    this.overlay_.getSource().removeFeature(this.vertexFeature_);
    this.vertexFeature_ = null;
  }
};


/**
 * @param {ol.ModifySegmentDataType} segmentData Segment data.
 * @param {ol.Coordinate} vertex Vertex.
 * @private
 */
ol.interaction.Modify.prototype.insertVertex_ = function(segmentData, vertex) {
  var segment = segmentData.segment;
  var feature = segmentData.feature;
  var geometry = segmentData.geometry;
  var depth = segmentData.depth;
  var index = /** @type {number} */ (segmentData.index);
  var coordinates;

  while (vertex.length < geometry.getStride()) {
    vertex.push(0);
  }

  switch (geometry.getType()) {
    case ol.geom.GeometryType.MULTI_LINE_STRING:
      coordinates = geometry.getCoordinates();
      coordinates[depth[0]].splice(index + 1, 0, vertex);
      break;
    case ol.geom.GeometryType.POLYGON:
      coordinates = geometry.getCoordinates();
      coordinates[depth[0]].splice(index + 1, 0, vertex);
      break;
    case ol.geom.GeometryType.MULTI_POLYGON:
      coordinates = geometry.getCoordinates();
      coordinates[depth[1]][depth[0]].splice(index + 1, 0, vertex);
      break;
    case ol.geom.GeometryType.LINE_STRING:
      coordinates = geometry.getCoordinates();
      coordinates.splice(index + 1, 0, vertex);
      break;
    default:
      return;
  }

  this.setGeometryCoordinates_(geometry, coordinates);
  var rTree = this.rBush_;
  rTree.remove(segmentData);
  this.updateSegmentIndices_(geometry, index, depth, 1);
  var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
    segment: [segment[0], vertex],
    feature: feature,
    geometry: geometry,
    depth: depth,
    index: index
  });
  rTree.insert(ol.extent.boundingExtent(newSegmentData.segment),
      newSegmentData);
  this.dragSegments_.push([newSegmentData, 1]);

  var newSegmentData2 = /** @type {ol.ModifySegmentDataType} */ ({
    segment: [vertex, segment[1]],
    feature: feature,
    geometry: geometry,
    depth: depth,
    index: index + 1
  });
  rTree.insert(ol.extent.boundingExtent(newSegmentData2.segment),
      newSegmentData2);
  this.dragSegments_.push([newSegmentData2, 0]);
  this.ignoreNextSingleClick_ = true;
};

/**
 * Removes the vertex currently being pointed.
 * @return {boolean} True when a vertex was removed.
 * @api
 */
ol.interaction.Modify.prototype.removePoint = function() {
  var handled = false;
  if (this.lastPointerEvent_ && this.lastPointerEvent_.type != ol.MapBrowserEvent.EventType.POINTERDRAG) {
    var evt = this.lastPointerEvent_;
    this.willModifyFeatures_(evt);
    handled = this.removeVertex_();
    this.dispatchEvent(new ol.interaction.ModifyEvent(
        ol.ModifyEventType.MODIFYEND, this.features_, evt));
    this.modified_ = false;
  }
  return handled;
};

/**
 * Removes a vertex from all matching features.
 * @return {boolean} True when a vertex was removed.
 * @private
 */
ol.interaction.Modify.prototype.removeVertex_ = function() {
  var dragSegments = this.dragSegments_;
  var segmentsByFeature = {};
  var deleted = false;
  var component, coordinates, dragSegment, geometry, i, index, left;
  var newIndex, right, segmentData, uid;
  for (i = dragSegments.length - 1; i >= 0; --i) {
    dragSegment = dragSegments[i];
    segmentData = dragSegment[0];
    uid = ol.getUid(segmentData.feature);
    if (segmentData.depth) {
      // separate feature components
      uid += '-' + segmentData.depth.join('-');
    }
    if (!(uid in segmentsByFeature)) {
      segmentsByFeature[uid] = {};
    }
    if (dragSegment[1] === 0) {
      segmentsByFeature[uid].right = segmentData;
      segmentsByFeature[uid].index = segmentData.index;
    } else if (dragSegment[1] == 1) {
      segmentsByFeature[uid].left = segmentData;
      segmentsByFeature[uid].index = segmentData.index + 1;
    }

  }
  for (uid in segmentsByFeature) {
    right = segmentsByFeature[uid].right;
    left = segmentsByFeature[uid].left;
    index = segmentsByFeature[uid].index;
    newIndex = index - 1;
    if (left !== undefined) {
      segmentData = left;
    } else {
      segmentData = right;
    }
    if (newIndex < 0) {
      newIndex = 0;
    }
    geometry = segmentData.geometry;
    coordinates = geometry.getCoordinates();
    component = coordinates;
    deleted = false;
    switch (geometry.getType()) {
      case ol.geom.GeometryType.MULTI_LINE_STRING:
        if (coordinates[segmentData.depth[0]].length > 2) {
          coordinates[segmentData.depth[0]].splice(index, 1);
          deleted = true;
        }
        break;
      case ol.geom.GeometryType.LINE_STRING:
        if (coordinates.length > 2) {
          coordinates.splice(index, 1);
          deleted = true;
        }
        break;
      case ol.geom.GeometryType.MULTI_POLYGON:
        component = component[segmentData.depth[1]];
        /* falls through */
      case ol.geom.GeometryType.POLYGON:
        component = component[segmentData.depth[0]];
        if (component.length > 4) {
          if (index == component.length - 1) {
            index = 0;
          }
          component.splice(index, 1);
          deleted = true;
          if (index === 0) {
            // close the ring again
            component.pop();
            component.push(component[0]);
            newIndex = component.length - 1;
          }
        }
        break;
      default:
        // pass
    }

    if (deleted) {
      this.setGeometryCoordinates_(geometry, coordinates);
      var segments = [];
      if (left !== undefined) {
        this.rBush_.remove(left);
        segments.push(left.segment[0]);
      }
      if (right !== undefined) {
        this.rBush_.remove(right);
        segments.push(right.segment[1]);
      }
      if (left !== undefined && right !== undefined) {
        goog.DEBUG && console.assert(newIndex >= 0, 'newIndex should be larger than 0');

        var newSegmentData = /** @type {ol.ModifySegmentDataType} */ ({
          depth: segmentData.depth,
          feature: segmentData.feature,
          geometry: segmentData.geometry,
          index: newIndex,
          segment: segments
        });
        this.rBush_.insert(ol.extent.boundingExtent(newSegmentData.segment),
            newSegmentData);
      }
      this.updateSegmentIndices_(geometry, index, segmentData.depth, -1);
      if (this.vertexFeature_) {
        this.overlay_.getSource().removeFeature(this.vertexFeature_);
        this.vertexFeature_ = null;
      }
    }

  }
  return deleted;
};


/**
 * @param {ol.geom.SimpleGeometry} geometry Geometry.
 * @param {Array} coordinates Coordinates.
 * @private
 */
ol.interaction.Modify.prototype.setGeometryCoordinates_ = function(geometry, coordinates) {
  this.changingFeature_ = true;
  geometry.setCoordinates(coordinates);
  this.changingFeature_ = false;
};


/**
 * @param {ol.geom.SimpleGeometry} geometry Geometry.
 * @param {number} index Index.
 * @param {Array.<number>|undefined} depth Depth.
 * @param {number} delta Delta (1 or -1).
 * @private
 */
ol.interaction.Modify.prototype.updateSegmentIndices_ = function(
    geometry, index, depth, delta) {
  this.rBush_.forEachInExtent(geometry.getExtent(), function(segmentDataMatch) {
    if (segmentDataMatch.geometry === geometry &&
        (depth === undefined || segmentDataMatch.depth === undefined ||
        ol.array.equals(segmentDataMatch.depth, depth)) &&
        segmentDataMatch.index > index) {
      segmentDataMatch.index += delta;
    }
  });
};


/**
 * @return {ol.StyleFunction} Styles.
 */
ol.interaction.Modify.getDefaultStyleFunction = function() {
  var style = ol.style.Style.createDefaultEditing();
  return function(feature, resolution) {
    return style[ol.geom.GeometryType.POINT];
  };
};

goog.provide('ol.interaction.Select');
goog.provide('ol.interaction.SelectEvent');
goog.provide('ol.interaction.SelectEventType');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.functions');
goog.require('ol.Collection');
goog.require('ol.Feature');
goog.require('ol.array');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.condition');
goog.require('ol.geom.GeometryType');
goog.require('ol.interaction.Interaction');
goog.require('ol.layer.Vector');
goog.require('ol.obj');
goog.require('ol.source.Vector');
goog.require('ol.style.Style');


/**
 * @enum {string}
 */
ol.interaction.SelectEventType = {
  /**
   * Triggered when feature(s) has been (de)selected.
   * @event ol.interaction.SelectEvent#select
   * @api
   */
  SELECT: 'select'
};


/**
 * @classdesc
 * Events emitted by {@link ol.interaction.Select} instances are instances of
 * this type.
 *
 * @param {string} type The event type.
 * @param {Array.<ol.Feature>} selected Selected features.
 * @param {Array.<ol.Feature>} deselected Deselected features.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Associated
 *     {@link ol.MapBrowserEvent}.
 * @implements {oli.SelectEvent}
 * @extends {ol.events.Event}
 * @constructor
 */
ol.interaction.SelectEvent = function(type, selected, deselected, mapBrowserEvent) {
  ol.events.Event.call(this, type);

  /**
   * Selected features array.
   * @type {Array.<ol.Feature>}
   * @api
   */
  this.selected = selected;

  /**
   * Deselected features array.
   * @type {Array.<ol.Feature>}
   * @api
   */
  this.deselected = deselected;

  /**
   * Associated {@link ol.MapBrowserEvent}.
   * @type {ol.MapBrowserEvent}
   * @api
   */
  this.mapBrowserEvent = mapBrowserEvent;
};
ol.inherits(ol.interaction.SelectEvent, ol.events.Event);


/**
 * @classdesc
 * Interaction for selecting vector features. By default, selected features are
 * styled differently, so this interaction can be used for visual highlighting,
 * as well as selecting features for other actions, such as modification or
 * output. There are three ways of controlling which features are selected:
 * using the browser event as defined by the `condition` and optionally the
 * `toggle`, `add`/`remove`, and `multi` options; a `layers` filter; and a
 * further feature filter using the `filter` option.
 *
 * Selected features are added to an internal unmanaged layer.
 *
 * @constructor
 * @extends {ol.interaction.Interaction}
 * @param {olx.interaction.SelectOptions=} opt_options Options.
 * @fires ol.interaction.SelectEvent
 * @api stable
 */
ol.interaction.Select = function(opt_options) {

  ol.interaction.Interaction.call(this, {
    handleEvent: ol.interaction.Select.handleEvent
  });

  var options = opt_options ? opt_options : {};

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.condition_ = options.condition ?
      options.condition : ol.events.condition.singleClick;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.addCondition_ = options.addCondition ?
      options.addCondition : ol.events.condition.never;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.removeCondition_ = options.removeCondition ?
      options.removeCondition : ol.events.condition.never;

  /**
   * @private
   * @type {ol.EventsConditionType}
   */
  this.toggleCondition_ = options.toggleCondition ?
      options.toggleCondition : ol.events.condition.shiftKeyOnly;

  /**
   * @private
   * @type {boolean}
   */
  this.multi_ = options.multi ? options.multi : false;

  /**
   * @private
   * @type {ol.SelectFilterFunction}
   */
  this.filter_ = options.filter ? options.filter :
      ol.functions.TRUE;

  var featureOverlay = new ol.layer.Vector({
    source: new ol.source.Vector({
      useSpatialIndex: false,
      features: options.features,
      wrapX: options.wrapX
    }),
    style: options.style ? options.style :
        ol.interaction.Select.getDefaultStyleFunction(),
    updateWhileAnimating: true,
    updateWhileInteracting: true
  });

  /**
   * @private
   * @type {ol.layer.Vector}
   */
  this.featureOverlay_ = featureOverlay;

  /** @type {function(ol.layer.Layer): boolean} */
  var layerFilter;
  if (options.layers) {
    if (typeof options.layers === 'function') {
      layerFilter = options.layers;
    } else {
      var layers = options.layers;
      layerFilter = function(layer) {
        return ol.array.includes(layers, layer);
      };
    }
  } else {
    layerFilter = ol.functions.TRUE;
  }

  /**
   * @private
   * @type {function(ol.layer.Layer): boolean}
   */
  this.layerFilter_ = layerFilter;

  /**
   * An association between selected feature (key)
   * and layer (value)
   * @private
   * @type {Object.<number, ol.layer.Layer>}
   */
  this.featureLayerAssociation_ = {};

  var features = this.featureOverlay_.getSource().getFeaturesCollection();
  ol.events.listen(features, ol.Collection.EventType.ADD,
      this.addFeature_, this);
  ol.events.listen(features, ol.Collection.EventType.REMOVE,
      this.removeFeature_, this);

};
ol.inherits(ol.interaction.Select, ol.interaction.Interaction);


/**
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @param {ol.layer.Layer} layer Layer.
 * @private
 */
ol.interaction.Select.prototype.addFeatureLayerAssociation_ = function(feature, layer) {
  var key = ol.getUid(feature);
  this.featureLayerAssociation_[key] = layer;
};


/**
 * Get the selected features.
 * @return {ol.Collection.<ol.Feature>} Features collection.
 * @api stable
 */
ol.interaction.Select.prototype.getFeatures = function() {
  return this.featureOverlay_.getSource().getFeaturesCollection();
};


/**
 * Returns the associated {@link ol.layer.Vector vectorlayer} of
 * the (last) selected feature. Note that this will not work with any
 * programmatic method like pushing features to
 * {@link ol.interaction.Select#getFeatures collection}.
 * @param {ol.Feature|ol.render.Feature} feature Feature
 * @return {ol.layer.Vector} Layer.
 * @api
 */
ol.interaction.Select.prototype.getLayer = function(feature) {
  ol.asserts.assert(feature instanceof ol.Feature,
      42); // Expected an `ol.Feature`, but got an `ol.RenderFeature`
  var key = ol.getUid(feature);
  return /** @type {ol.layer.Vector} */ (this.featureLayerAssociation_[key]);
};


/**
 * Handles the {@link ol.MapBrowserEvent map browser event} and may change the
 * selected state of features.
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} `false` to stop event propagation.
 * @this {ol.interaction.Select}
 * @api
 */
ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
  if (!this.condition_(mapBrowserEvent)) {
    return true;
  }
  var add = this.addCondition_(mapBrowserEvent);
  var remove = this.removeCondition_(mapBrowserEvent);
  var toggle = this.toggleCondition_(mapBrowserEvent);
  var set = !add && !remove && !toggle;
  var map = mapBrowserEvent.map;
  var features = this.featureOverlay_.getSource().getFeaturesCollection();
  var deselected = [];
  var selected = [];
  if (set) {
    // Replace the currently selected feature(s) with the feature(s) at the
    // pixel, or clear the selected feature(s) if there is no feature at
    // the pixel.
    ol.obj.clear(this.featureLayerAssociation_);
    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @param {ol.layer.Layer} layer Layer.
         * @return {boolean|undefined} Continue to iterate over the features.
         */
        function(feature, layer) {
          if (this.filter_(feature, layer)) {
            selected.push(feature);
            this.addFeatureLayerAssociation_(feature, layer);
            return !this.multi_;
          }
        }, this, this.layerFilter_);
    if (selected.length > 0 && features.getLength() == 1 && features.item(0) == selected[0]) {
      // No change; an already selected feature is selected again
      selected.length = 0;
    } else {
      if (features.getLength() !== 0) {
        deselected = Array.prototype.concat(features.getArray());
        features.clear();
      }
      features.extend(selected);
    }
  } else {
    // Modify the currently selected feature(s).
    map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
        /**
         * @param {ol.Feature|ol.render.Feature} feature Feature.
         * @param {ol.layer.Layer} layer Layer.
         * @return {boolean|undefined} Continue to iterate over the features.
         */
        function(feature, layer) {
          if (this.filter_(feature, layer)) {
            if ((add || toggle) &&
                !ol.array.includes(features.getArray(), feature)) {
              selected.push(feature);
              this.addFeatureLayerAssociation_(feature, layer);
            } else if ((remove || toggle) &&
                ol.array.includes(features.getArray(), feature)) {
              deselected.push(feature);
              this.removeFeatureLayerAssociation_(feature);
            }
            return !this.multi_;
          }
        }, this, this.layerFilter_);
    var i;
    for (i = deselected.length - 1; i >= 0; --i) {
      features.remove(deselected[i]);
    }
    features.extend(selected);
  }
  if (selected.length > 0 || deselected.length > 0) {
    this.dispatchEvent(
        new ol.interaction.SelectEvent(ol.interaction.SelectEventType.SELECT,
            selected, deselected, mapBrowserEvent));
  }
  return ol.events.condition.pointerMove(mapBrowserEvent);
};


/**
 * Remove the interaction from its current map, if any,  and attach it to a new
 * map, if any. Pass `null` to just remove the interaction from the current map.
 * @param {ol.Map} map Map.
 * @api stable
 */
ol.interaction.Select.prototype.setMap = function(map) {
  var currentMap = this.getMap();
  var selectedFeatures =
      this.featureOverlay_.getSource().getFeaturesCollection();
  if (currentMap) {
    selectedFeatures.forEach(currentMap.unskipFeature, currentMap);
  }
  ol.interaction.Interaction.prototype.setMap.call(this, map);
  this.featureOverlay_.setMap(map);
  if (map) {
    selectedFeatures.forEach(map.skipFeature, map);
  }
};


/**
 * @return {ol.StyleFunction} Styles.
 */
ol.interaction.Select.getDefaultStyleFunction = function() {
  var styles = ol.style.Style.createDefaultEditing();
  ol.array.extend(styles[ol.geom.GeometryType.POLYGON],
      styles[ol.geom.GeometryType.LINE_STRING]);
  ol.array.extend(styles[ol.geom.GeometryType.GEOMETRY_COLLECTION],
      styles[ol.geom.GeometryType.LINE_STRING]);

  return function(feature, resolution) {
    return styles[feature.getGeometry().getType()];
  };
};


/**
 * @param {ol.Collection.Event} evt Event.
 * @private
 */
ol.interaction.Select.prototype.addFeature_ = function(evt) {
  var map = this.getMap();
  if (map) {
    map.skipFeature(/** @type {ol.Feature} */ (evt.element));
  }
};


/**
 * @param {ol.Collection.Event} evt Event.
 * @private
 */
ol.interaction.Select.prototype.removeFeature_ = function(evt) {
  var map = this.getMap();
  if (map) {
    map.unskipFeature(/** @type {ol.Feature} */ (evt.element));
  }
};


/**
 * @param {ol.Feature|ol.render.Feature} feature Feature.
 * @private
 */
ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = function(feature) {
  var key = ol.getUid(feature);
  delete this.featureLayerAssociation_[key];
};

goog.provide('ol.interaction.Snap');
goog.provide('ol.interaction.SnapProperty');

goog.require('ol');
goog.require('ol.Collection');
goog.require('ol.Object');
goog.require('ol.Observable');
goog.require('ol.coordinate');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.interaction.Pointer');
goog.require('ol.functions');
goog.require('ol.obj');
goog.require('ol.source.VectorEvent');
goog.require('ol.source.VectorEventType');
goog.require('ol.structs.RBush');


/**
 * @classdesc
 * Handles snapping of vector features while modifying or drawing them.  The
 * features can come from a {@link ol.source.Vector} or {@link ol.Collection}
 * Any interaction object that allows the user to interact
 * with the features using the mouse can benefit from the snapping, as long
 * as it is added before.
 *
 * The snap interaction modifies map browser event `coordinate` and `pixel`
 * properties to force the snap to occur to any interaction that them.
 *
 * Example:
 *
 *     var snap = new ol.interaction.Snap({
 *       source: source
 *     });
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @param {olx.interaction.SnapOptions=} opt_options Options.
 * @api
 */
ol.interaction.Snap = function(opt_options) {

  ol.interaction.Pointer.call(this, {
    handleEvent: ol.interaction.Snap.handleEvent_,
    handleDownEvent: ol.functions.TRUE,
    handleUpEvent: ol.interaction.Snap.handleUpEvent_
  });

  var options = opt_options ? opt_options : {};

  /**
   * @type {ol.source.Vector}
   * @private
   */
  this.source_ = options.source ? options.source : null;

  /**
   * @private
   * @type {boolean}
   */
  this.vertex_ = options.vertex !== undefined ? options.vertex : true;

  /**
   * @private
   * @type {boolean}
   */
  this.edge_ = options.edge !== undefined ? options.edge : true;

  /**
   * @type {ol.Collection.<ol.Feature>}
   * @private
   */
  this.features_ = options.features ? options.features : null;

  /**
   * @type {Array.<ol.EventsKey>}
   * @private
   */
  this.featuresListenerKeys_ = [];

  /**
   * @type {Object.<number, ol.EventsKey>}
   * @private
   */
  this.geometryChangeListenerKeys_ = {};

  /**
   * @type {Object.<number, ol.EventsKey>}
   * @private
   */
  this.geometryModifyListenerKeys_ = {};

  /**
   * Extents are preserved so indexed segment can be quickly removed
   * when its feature geometry changes
   * @type {Object.<number, ol.Extent>}
   * @private
   */
  this.indexedFeaturesExtents_ = {};

  /**
   * If a feature geometry changes while a pointer drag|move event occurs, the
   * feature doesn't get updated right away.  It will be at the next 'pointerup'
   * event fired.
   * @type {Object.<number, ol.Feature>}
   * @private
   */
  this.pendingFeatures_ = {};

  /**
   * Used for distance sorting in sortByDistance_
   * @type {ol.Coordinate}
   * @private
   */
  this.pixelCoordinate_ = null;

  /**
   * @type {number}
   * @private
   */
  this.pixelTolerance_ = options.pixelTolerance !== undefined ?
      options.pixelTolerance : 10;

  /**
   * @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number}
   * @private
   */
  this.sortByDistance_ = ol.interaction.Snap.sortByDistance.bind(this);


  /**
  * Segment RTree for each layer
  * @type {ol.structs.RBush.<ol.SnapSegmentDataType>}
  * @private
  */
  this.rBush_ = new ol.structs.RBush();


  /**
  * @const
  * @private
  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)>}
  */
  this.SEGMENT_WRITERS_ = {
    'Point': this.writePointGeometry_,
    'LineString': this.writeLineStringGeometry_,
    'LinearRing': this.writeLineStringGeometry_,
    'Polygon': this.writePolygonGeometry_,
    'MultiPoint': this.writeMultiPointGeometry_,
    'MultiLineString': this.writeMultiLineStringGeometry_,
    'MultiPolygon': this.writeMultiPolygonGeometry_,
    'GeometryCollection': this.writeGeometryCollectionGeometry_
  };
};
ol.inherits(ol.interaction.Snap, ol.interaction.Pointer);


/**
 * Add a feature to the collection of features that we may snap to.
 * @param {ol.Feature} feature Feature.
 * @param {boolean=} opt_listen Whether to listen to the geometry change or not
 *     Defaults to `true`.
 * @api
 */
ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) {
  var listen = opt_listen !== undefined ? opt_listen : true;
  var feature_uid = ol.getUid(feature);
  var geometry = feature.getGeometry();
  if (geometry) {
    var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
    if (segmentWriter) {
      this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent(
          ol.extent.createEmpty());
      segmentWriter.call(this, feature, geometry);

      if (listen) {
        this.geometryModifyListenerKeys_[feature_uid] = ol.events.listen(
            geometry,
            ol.events.EventType.CHANGE,
            this.handleGeometryModify_.bind(this, feature),
            this);
      }
    }
  }

  if (listen) {
    this.geometryChangeListenerKeys_[feature_uid] = ol.events.listen(
        feature,
        ol.Object.getChangeEventType(feature.getGeometryName()),
        this.handleGeometryChange_, this);
  }
};


/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
  this.addFeature(feature);
};


/**
 * @param {ol.Feature} feature Feature.
 * @private
 */
ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
  this.removeFeature(feature);
};


/**
 * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>} Features.
 * @private
 */
ol.interaction.Snap.prototype.getFeatures_ = function() {
  var features;
  if (this.features_) {
    features = this.features_;
  } else if (this.source_) {
    features = this.source_.getFeatures();
  }
  return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features);
};


/**
 * @param {ol.source.VectorEvent|ol.Collection.Event} evt Event.
 * @private
 */
ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) {
  var feature;
  if (evt instanceof ol.source.VectorEvent) {
    feature = evt.feature;
  } else if (evt instanceof ol.Collection.Event) {
    feature = evt.element;
  }
  this.addFeature(/** @type {ol.Feature} */ (feature));
};


/**
 * @param {ol.source.VectorEvent|ol.Collection.Event} evt Event.
 * @private
 */
ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) {
  var feature;
  if (evt instanceof ol.source.VectorEvent) {
    feature = evt.feature;
  } else if (evt instanceof ol.Collection.Event) {
    feature = evt.element;
  }
  this.removeFeature(/** @type {ol.Feature} */ (feature));
};


/**
 * @param {ol.events.Event} evt Event.
 * @private
 */
ol.interaction.Snap.prototype.handleGeometryChange_ = function(evt) {
  var feature = /** @type {ol.Feature} */ (evt.target);
  this.removeFeature(feature, true);
  this.addFeature(feature, true);
};


/**
 * @param {ol.Feature} feature Feature which geometry was modified.
 * @param {ol.events.Event} evt Event.
 * @private
 */
ol.interaction.Snap.prototype.handleGeometryModify_ = function(feature, evt) {
  if (this.handlingDownUpSequence) {
    var uid = ol.getUid(feature);
    if (!(uid in this.pendingFeatures_)) {
      this.pendingFeatures_[uid] = feature;
    }
  } else {
    this.updateFeature_(feature);
  }
};


/**
 * Remove a feature from the collection of features that we may snap to.
 * @param {ol.Feature} feature Feature
 * @param {boolean=} opt_unlisten Whether to unlisten to the geometry change
 *     or not. Defaults to `true`.
 * @api
 */
ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) {
  var unlisten = opt_unlisten !== undefined ? opt_unlisten : true;
  var feature_uid = ol.getUid(feature);
  var extent = this.indexedFeaturesExtents_[feature_uid];
  if (extent) {
    var rBush = this.rBush_;
    var i, nodesToRemove = [];
    rBush.forEachInExtent(extent, function(node) {
      if (feature === node.feature) {
        nodesToRemove.push(node);
      }
    });
    for (i = nodesToRemove.length - 1; i >= 0; --i) {
      rBush.remove(nodesToRemove[i]);
    }
    if (unlisten) {
      ol.Observable.unByKey(this.geometryModifyListenerKeys_[feature_uid]);
      delete this.geometryModifyListenerKeys_[feature_uid];
    }
  }

  if (unlisten) {
    ol.Observable.unByKey(this.geometryChangeListenerKeys_[feature_uid]);
    delete this.geometryChangeListenerKeys_[feature_uid];
  }
};


/**
 * @inheritDoc
 */
ol.interaction.Snap.prototype.setMap = function(map) {
  var currentMap = this.getMap();
  var keys = this.featuresListenerKeys_;
  var features = this.getFeatures_();

  if (currentMap) {
    keys.forEach(ol.Observable.unByKey);
    keys.length = 0;
    features.forEach(this.forEachFeatureRemove_, this);
  }
  ol.interaction.Pointer.prototype.setMap.call(this, map);

  if (map) {
    if (this.features_) {
      keys.push(
        ol.events.listen(this.features_, ol.Collection.EventType.ADD,
            this.handleFeatureAdd_, this),
        ol.events.listen(this.features_, ol.Collection.EventType.REMOVE,
            this.handleFeatureRemove_, this)
      );
    } else if (this.source_) {
      keys.push(
        ol.events.listen(this.source_, ol.source.VectorEventType.ADDFEATURE,
            this.handleFeatureAdd_, this),
        ol.events.listen(this.source_, ol.source.VectorEventType.REMOVEFEATURE,
            this.handleFeatureRemove_, this)
      );
    }
    features.forEach(this.forEachFeatureAdd_, this);
  }
};


/**
 * @inheritDoc
 */
ol.interaction.Snap.prototype.shouldStopEvent = ol.functions.FALSE;


/**
 * @param {ol.Pixel} pixel Pixel
 * @param {ol.Coordinate} pixelCoordinate Coordinate
 * @param {ol.Map} map Map.
 * @return {ol.SnapResultType} Snap result
 */
ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) {

  var lowerLeft = map.getCoordinateFromPixel(
      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
  var upperRight = map.getCoordinateFromPixel(
      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);

  var segments = this.rBush_.getInExtent(box);
  var snappedToVertex = false;
  var snapped = false;
  var vertex = null;
  var vertexPixel = null;
  var dist, pixel1, pixel2, squaredDist1, squaredDist2;
  if (segments.length > 0) {
    this.pixelCoordinate_ = pixelCoordinate;
    segments.sort(this.sortByDistance_);
    var closestSegment = segments[0].segment;
    if (this.vertex_ && !this.edge_) {
      pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
      pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
      squaredDist1 = ol.coordinate.squaredDistance(pixel, pixel1);
      squaredDist2 = ol.coordinate.squaredDistance(pixel, pixel2);
      dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
      snappedToVertex = dist <= this.pixelTolerance_;
      if (snappedToVertex) {
        snapped = true;
        vertex = squaredDist1 > squaredDist2 ?
            closestSegment[1] : closestSegment[0];
        vertexPixel = map.getPixelFromCoordinate(vertex);
      }
    } else if (this.edge_) {
      vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
          closestSegment));
      vertexPixel = map.getPixelFromCoordinate(vertex);
      if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
          this.pixelTolerance_) {
        snapped = true;
        if (this.vertex_) {
          pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
          pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
          squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
          squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
          dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
          snappedToVertex = dist <= this.pixelTolerance_;
          if (snappedToVertex) {
            vertex = squaredDist1 > squaredDist2 ?
                closestSegment[1] : closestSegment[0];
            vertexPixel = map.getPixelFromCoordinate(vertex);
          }
        }
      }
    }
    if (snapped) {
      vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
    }
  }
  return /** @type {ol.SnapResultType} */ ({
    snapped: snapped,
    vertex: vertex,
    vertexPixel: vertexPixel
  });
};


/**
 * @param {ol.Feature} feature Feature
 * @private
 */
ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
  this.removeFeature(feature, false);
  this.addFeature(feature, false);
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.GeometryCollection} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ = function(feature, geometry) {
  var i, geometries = geometry.getGeometriesArray();
  for (i = 0; i < geometries.length; ++i) {
    this.SEGMENT_WRITERS_[geometries[i].getType()].call(
        this, feature, geometries[i]);
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.LineString} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writeLineStringGeometry_ = function(feature, geometry) {
  var coordinates = geometry.getCoordinates();
  var i, ii, segment, segmentData;
  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
    segment = coordinates.slice(i, i + 2);
    segmentData = /** @type {ol.SnapSegmentDataType} */ ({
      feature: feature,
      segment: segment
    });
    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiLineString} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ = function(feature, geometry) {
  var lines = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, segment, segmentData;
  for (j = 0, jj = lines.length; j < jj; ++j) {
    coordinates = lines[j];
    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = /** @type {ol.SnapSegmentDataType} */ ({
        feature: feature,
        segment: segment
      });
      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
    }
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiPoint} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writeMultiPointGeometry_ = function(feature, geometry) {
  var points = geometry.getCoordinates();
  var coordinates, i, ii, segmentData;
  for (i = 0, ii = points.length; i < ii; ++i) {
    coordinates = points[i];
    segmentData = /** @type {ol.SnapSegmentDataType} */ ({
      feature: feature,
      segment: [coordinates, coordinates]
    });
    this.rBush_.insert(geometry.getExtent(), segmentData);
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.MultiPolygon} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ = function(feature, geometry) {
  var polygons = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
  for (k = 0, kk = polygons.length; k < kk; ++k) {
    rings = polygons[k];
    for (j = 0, jj = rings.length; j < jj; ++j) {
      coordinates = rings[j];
      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
        segment = coordinates.slice(i, i + 2);
        segmentData = /** @type {ol.SnapSegmentDataType} */ ({
          feature: feature,
          segment: segment
        });
        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
      }
    }
  }
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.Point} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writePointGeometry_ = function(feature, geometry) {
  var coordinates = geometry.getCoordinates();
  var segmentData = /** @type {ol.SnapSegmentDataType} */ ({
    feature: feature,
    segment: [coordinates, coordinates]
  });
  this.rBush_.insert(geometry.getExtent(), segmentData);
};


/**
 * @param {ol.Feature} feature Feature
 * @param {ol.geom.Polygon} geometry Geometry.
 * @private
 */
ol.interaction.Snap.prototype.writePolygonGeometry_ = function(feature, geometry) {
  var rings = geometry.getCoordinates();
  var coordinates, i, ii, j, jj, segment, segmentData;
  for (j = 0, jj = rings.length; j < jj; ++j) {
    coordinates = rings[j];
    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
      segment = coordinates.slice(i, i + 2);
      segmentData = /** @type {ol.SnapSegmentDataType} */ ({
        feature: feature,
        segment: segment
      });
      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
    }
  }
};


/**
 * Handle all pointer events events.
 * @param {ol.MapBrowserEvent} evt A move event.
 * @return {boolean} Pass the event to other interactions.
 * @this {ol.interaction.Snap}
 * @private
 */
ol.interaction.Snap.handleEvent_ = function(evt) {
  var result = this.snapTo(evt.pixel, evt.coordinate, evt.map);
  if (result.snapped) {
    evt.coordinate = result.vertex.slice(0, 2);
    evt.pixel = result.vertexPixel;
  }
  return ol.interaction.Pointer.handleEvent.call(this, evt);
};


/**
 * @param {ol.MapBrowserPointerEvent} evt Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.Snap}
 * @private
 */
ol.interaction.Snap.handleUpEvent_ = function(evt) {
  var featuresToUpdate = ol.obj.getValues(this.pendingFeatures_);
  if (featuresToUpdate.length) {
    featuresToUpdate.forEach(this.updateFeature_, this);
    this.pendingFeatures_ = {};
  }
  return false;
};


/**
 * Sort segments by distance, helper function
 * @param {ol.SnapSegmentDataType} a The first segment data.
 * @param {ol.SnapSegmentDataType} b The second segment data.
 * @return {number} The difference in distance.
 * @this {ol.interaction.Snap}
 */
ol.interaction.Snap.sortByDistance = function(a, b) {
  return ol.coordinate.squaredDistanceToSegment(
      this.pixelCoordinate_, a.segment) -
      ol.coordinate.squaredDistanceToSegment(
      this.pixelCoordinate_, b.segment);
};

goog.provide('ol.interaction.Translate');
goog.provide('ol.interaction.TranslateEvent');

goog.require('ol');
goog.require('ol.events.Event');
goog.require('ol.array');
goog.require('ol.interaction.Pointer');


/**
 * @enum {string}
 */
ol.interaction.TranslateEventType = {
  /**
   * Triggered upon feature translation start.
   * @event ol.interaction.TranslateEvent#translatestart
   * @api
   */
  TRANSLATESTART: 'translatestart',
  /**
   * Triggered upon feature translation.
   * @event ol.interaction.TranslateEvent#translating
   * @api
   */
  TRANSLATING: 'translating',
  /**
   * Triggered upon feature translation end.
   * @event ol.interaction.TranslateEvent#translateend
   * @api
   */
  TRANSLATEEND: 'translateend'
};


/**
 * @classdesc
 * Events emitted by {@link ol.interaction.Translate} instances are instances of
 * this type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.interaction.TranslateEvent}
 * @param {ol.interaction.TranslateEventType} type Type.
 * @param {ol.Collection.<ol.Feature>} features The features translated.
 * @param {ol.Coordinate} coordinate The event coordinate.
 */
ol.interaction.TranslateEvent = function(type, features, coordinate) {

  ol.events.Event.call(this, type);

  /**
   * The features being translated.
   * @type {ol.Collection.<ol.Feature>}
   * @api
   */
  this.features = features;

  /**
   * The coordinate of the drag event.
   * @const
   * @type {ol.Coordinate}
   * @api
   */
  this.coordinate = coordinate;
};
ol.inherits(ol.interaction.TranslateEvent, ol.events.Event);


/**
 * @classdesc
 * Interaction for translating (moving) features.
 *
 * @constructor
 * @extends {ol.interaction.Pointer}
 * @fires ol.interaction.TranslateEvent
 * @param {olx.interaction.TranslateOptions} options Options.
 * @api
 */
ol.interaction.Translate = function(options) {
  ol.interaction.Pointer.call(this, {
    handleDownEvent: ol.interaction.Translate.handleDownEvent_,
    handleDragEvent: ol.interaction.Translate.handleDragEvent_,
    handleMoveEvent: ol.interaction.Translate.handleMoveEvent_,
    handleUpEvent: ol.interaction.Translate.handleUpEvent_
  });


  /**
   * @type {string|undefined}
   * @private
   */
  this.previousCursor_ = undefined;


  /**
   * The last position we translated to.
   * @type {ol.Coordinate}
   * @private
   */
  this.lastCoordinate_ = null;


  /**
   * @type {ol.Collection.<ol.Feature>}
   * @private
   */
  this.features_ = options.features !== undefined ? options.features : null;

  /** @type {function(ol.layer.Layer): boolean} */
  var layerFilter;
  if (options.layers) {
    if (typeof options.layers === 'function') {
      layerFilter = options.layers;
    } else {
      var layers = options.layers;
      layerFilter = function(layer) {
        return ol.array.includes(layers, layer);
      };
    }
  } else {
    layerFilter = ol.functions.TRUE;
  }

  /**
   * @private
   * @type {function(ol.layer.Layer): boolean}
   */
  this.layerFilter_ = layerFilter;

  /**
   * @type {ol.Feature}
   * @private
   */
  this.lastFeature_ = null;
};
ol.inherits(ol.interaction.Translate, ol.interaction.Pointer);


/**
 * @param {ol.MapBrowserPointerEvent} event Event.
 * @return {boolean} Start drag sequence?
 * @this {ol.interaction.Translate}
 * @private
 */
ol.interaction.Translate.handleDownEvent_ = function(event) {
  this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map);
  if (!this.lastCoordinate_ && this.lastFeature_) {
    this.lastCoordinate_ = event.coordinate;
    ol.interaction.Translate.handleMoveEvent_.call(this, event);
    this.dispatchEvent(
        new ol.interaction.TranslateEvent(
            ol.interaction.TranslateEventType.TRANSLATESTART, this.features_,
            event.coordinate));
    return true;
  }
  return false;
};


/**
 * @param {ol.MapBrowserPointerEvent} event Event.
 * @return {boolean} Stop drag sequence?
 * @this {ol.interaction.Translate}
 * @private
 */
ol.interaction.Translate.handleUpEvent_ = function(event) {
  if (this.lastCoordinate_) {
    this.lastCoordinate_ = null;
    ol.interaction.Translate.handleMoveEvent_.call(this, event);
    this.dispatchEvent(
        new ol.interaction.TranslateEvent(
            ol.interaction.TranslateEventType.TRANSLATEEND, this.features_,
            event.coordinate));
    return true;
  }
  return false;
};


/**
 * @param {ol.MapBrowserPointerEvent} event Event.
 * @this {ol.interaction.Translate}
 * @private
 */
ol.interaction.Translate.handleDragEvent_ = function(event) {
  if (this.lastCoordinate_) {
    var newCoordinate = event.coordinate;
    var deltaX = newCoordinate[0] - this.lastCoordinate_[0];
    var deltaY = newCoordinate[1] - this.lastCoordinate_[1];

    if (this.features_) {
      this.features_.forEach(function(feature) {
        var geom = feature.getGeometry();
        geom.translate(deltaX, deltaY);
        feature.setGeometry(geom);
      });
    } else if (this.lastFeature_) {
      var geom = this.lastFeature_.getGeometry();
      geom.translate(deltaX, deltaY);
      this.lastFeature_.setGeometry(geom);
    }

    this.lastCoordinate_ = newCoordinate;
    this.dispatchEvent(
        new ol.interaction.TranslateEvent(
            ol.interaction.TranslateEventType.TRANSLATING, this.features_,
            newCoordinate));
  }
};


/**
 * @param {ol.MapBrowserEvent} event Event.
 * @this {ol.interaction.Translate}
 * @private
 */
ol.interaction.Translate.handleMoveEvent_ = function(event) {
  var elem = event.map.getTargetElement();

  // Change the cursor to grab/grabbing if hovering any of the features managed
  // by the interaction
  if (this.featuresAtPixel_(event.pixel, event.map)) {
    this.previousCursor_ = elem.style.cursor;
    // WebKit browsers don't support the grab icons without a prefix
    elem.style.cursor = this.lastCoordinate_ ?
        '-webkit-grabbing' : '-webkit-grab';

    // Thankfully, attempting to set the standard ones will silently fail,
    // keeping the prefixed icons
    elem.style.cursor = this.lastCoordinate_ ?  'grabbing' : 'grab';
  } else {
    elem.style.cursor = this.previousCursor_ !== undefined ?
        this.previousCursor_ : '';
    this.previousCursor_ = undefined;
  }
};


/**
 * Tests to see if the given coordinates intersects any of our selected
 * features.
 * @param {ol.Pixel} pixel Pixel coordinate to test for intersection.
 * @param {ol.Map} map Map to test the intersection on.
 * @return {ol.Feature} Returns the feature found at the specified pixel
 * coordinates.
 * @private
 */
ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) {
  var found = null;

  var intersectingFeature = map.forEachFeatureAtPixel(pixel,
      function(feature) {
        return feature;
      }, this, this.layerFilter_);

  if (this.features_ &&
      ol.array.includes(this.features_.getArray(), intersectingFeature)) {
    found = intersectingFeature;
  }

  return found;
};

goog.provide('ol.layer.Heatmap');

goog.require('ol.events');
goog.require('ol');
goog.require('ol.Object');
goog.require('ol.dom');
goog.require('ol.layer.Vector');
goog.require('ol.math');
goog.require('ol.obj');
goog.require('ol.render.EventType');
goog.require('ol.style.Icon');
goog.require('ol.style.Style');


/**
 * @enum {string}
 */
ol.layer.HeatmapLayerProperty = {
  BLUR: 'blur',
  GRADIENT: 'gradient',
  RADIUS: 'radius'
};


/**
 * @classdesc
 * Layer for rendering vector data as a heatmap.
 * Note that any property set in the options is set as a {@link ol.Object}
 * property on the layer object; for example, setting `title: 'My Title'` in the
 * options means that `title` is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.layer.Vector}
 * @fires ol.render.Event
 * @param {olx.layer.HeatmapOptions=} opt_options Options.
 * @api
 */
ol.layer.Heatmap = function(opt_options) {
  var options = opt_options ? opt_options : {};

  var baseOptions = ol.obj.assign({}, options);

  delete baseOptions.gradient;
  delete baseOptions.radius;
  delete baseOptions.blur;
  delete baseOptions.shadow;
  delete baseOptions.weight;
  ol.layer.Vector.call(this, /** @type {olx.layer.VectorOptions} */ (baseOptions));

  /**
   * @private
   * @type {Uint8ClampedArray}
   */
  this.gradient_ = null;

  /**
   * @private
   * @type {number}
   */
  this.shadow_ = options.shadow !== undefined ? options.shadow : 250;

  /**
   * @private
   * @type {string|undefined}
   */
  this.circleImage_ = undefined;

  /**
   * @private
   * @type {Array.<Array.<ol.style.Style>>}
   */
  this.styleCache_ = null;

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.GRADIENT),
      this.handleGradientChanged_, this);

  this.setGradient(options.gradient ?
      options.gradient : ol.layer.Heatmap.DEFAULT_GRADIENT);

  this.setBlur(options.blur !== undefined ? options.blur : 15);

  this.setRadius(options.radius !== undefined ? options.radius : 8);

  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.BLUR),
      this.handleStyleChanged_, this);
  ol.events.listen(this,
      ol.Object.getChangeEventType(ol.layer.HeatmapLayerProperty.RADIUS),
      this.handleStyleChanged_, this);

  this.handleStyleChanged_();

  var weight = options.weight ? options.weight : 'weight';
  var weightFunction;
  if (typeof weight === 'string') {
    weightFunction = function(feature) {
      return feature.get(weight);
    };
  } else {
    weightFunction = weight;
  }
  goog.DEBUG && console.assert(typeof weightFunction === 'function',
      'weightFunction should be a function');

  this.setStyle(function(feature, resolution) {
    goog.DEBUG && console.assert(this.styleCache_, 'this.styleCache_ expected');
    goog.DEBUG && console.assert(this.circleImage_ !== undefined,
        'this.circleImage_ should be defined');
    var weight = weightFunction(feature);
    var opacity = weight !== undefined ? ol.math.clamp(weight, 0, 1) : 1;
    // cast to 8 bits
    var index = (255 * opacity) | 0;
    var style = this.styleCache_[index];
    if (!style) {
      style = [
        new ol.style.Style({
          image: new ol.style.Icon({
            opacity: opacity,
            src: this.circleImage_
          })
        })
      ];
      this.styleCache_[index] = style;
    }
    return style;
  }.bind(this));

  // For performance reasons, don't sort the features before rendering.
  // The render order is not relevant for a heatmap representation.
  this.setRenderOrder(null);

  ol.events.listen(this, ol.render.EventType.RENDER, this.handleRender_, this);

};
ol.inherits(ol.layer.Heatmap, ol.layer.Vector);


/**
 * @const
 * @type {Array.<string>}
 */
ol.layer.Heatmap.DEFAULT_GRADIENT = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];


/**
 * @param {Array.<string>} colors A list of colored.
 * @return {Uint8ClampedArray} An array.
 * @private
 */
ol.layer.Heatmap.createGradient_ = function(colors) {
  var width = 1;
  var height = 256;
  var context = ol.dom.createCanvasContext2D(width, height);

  var gradient = context.createLinearGradient(0, 0, width, height);
  var step = 1 / (colors.length - 1);
  for (var i = 0, ii = colors.length; i < ii; ++i) {
    gradient.addColorStop(i * step, colors[i]);
  }

  context.fillStyle = gradient;
  context.fillRect(0, 0, width, height);

  return context.getImageData(0, 0, width, height).data;
};


/**
 * @return {string} Data URL for a circle.
 * @private
 */
ol.layer.Heatmap.prototype.createCircle_ = function() {
  var radius = this.getRadius();
  var blur = this.getBlur();
  goog.DEBUG && console.assert(radius !== undefined && blur !== undefined,
      'radius and blur should be defined');
  var halfSize = radius + blur + 1;
  var size = 2 * halfSize;
  var context = ol.dom.createCanvasContext2D(size, size);
  context.shadowOffsetX = context.shadowOffsetY = this.shadow_;
  context.shadowBlur = blur;
  context.shadowColor = '#000';
  context.beginPath();
  var center = halfSize - this.shadow_;
  context.arc(center, center, radius, 0, Math.PI * 2, true);
  context.fill();
  return context.canvas.toDataURL();
};


/**
 * Return the blur size in pixels.
 * @return {number} Blur size in pixels.
 * @api
 * @observable
 */
ol.layer.Heatmap.prototype.getBlur = function() {
  return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.BLUR));
};


/**
 * Return the gradient colors as array of strings.
 * @return {Array.<string>} Colors.
 * @api
 * @observable
 */
ol.layer.Heatmap.prototype.getGradient = function() {
  return /** @type {Array.<string>} */ (
      this.get(ol.layer.HeatmapLayerProperty.GRADIENT));
};


/**
 * Return the size of the radius in pixels.
 * @return {number} Radius size in pixel.
 * @api
 * @observable
 */
ol.layer.Heatmap.prototype.getRadius = function() {
  return /** @type {number} */ (this.get(ol.layer.HeatmapLayerProperty.RADIUS));
};


/**
 * @private
 */
ol.layer.Heatmap.prototype.handleGradientChanged_ = function() {
  this.gradient_ = ol.layer.Heatmap.createGradient_(this.getGradient());
};


/**
 * @private
 */
ol.layer.Heatmap.prototype.handleStyleChanged_ = function() {
  this.circleImage_ = this.createCircle_();
  this.styleCache_ = new Array(256);
  this.changed();
};


/**
 * @param {ol.render.Event} event Post compose event
 * @private
 */
ol.layer.Heatmap.prototype.handleRender_ = function(event) {
  goog.DEBUG && console.assert(event.type == ol.render.EventType.RENDER,
      'event.type should be RENDER');
  goog.DEBUG && console.assert(this.gradient_, 'this.gradient_ expected');
  var context = event.context;
  var canvas = context.canvas;
  var image = context.getImageData(0, 0, canvas.width, canvas.height);
  var view8 = image.data;
  var i, ii, alpha;
  for (i = 0, ii = view8.length; i < ii; i += 4) {
    alpha = view8[i + 3] * 4;
    if (alpha) {
      view8[i] = this.gradient_[alpha];
      view8[i + 1] = this.gradient_[alpha + 1];
      view8[i + 2] = this.gradient_[alpha + 2];
    }
  }
  context.putImageData(image, 0, 0);
};


/**
 * Set the blur size in pixels.
 * @param {number} blur Blur size in pixels.
 * @api
 * @observable
 */
ol.layer.Heatmap.prototype.setBlur = function(blur) {
  this.set(ol.layer.HeatmapLayerProperty.BLUR, blur);
};


/**
 * Set the gradient colors as array of strings.
 * @param {Array.<string>} colors Gradient.
 * @api
 * @observable
 */
ol.layer.Heatmap.prototype.setGradient = function(colors) {
  this.set(ol.layer.HeatmapLayerProperty.GRADIENT, colors);
};


/**
 * Set the size of the radius in pixels.
 * @param {number} radius Radius size in pixel.
 * @api
 * @observable
 */
ol.layer.Heatmap.prototype.setRadius = function(radius) {
  this.set(ol.layer.HeatmapLayerProperty.RADIUS, radius);
};

goog.provide('ol.net');

goog.require('ol');


/**
 * Simple JSONP helper. Supports error callbacks and a custom callback param.
 * The error callback will be called when no JSONP is executed after 10 seconds.
 *
 * @param {string} url Request url. A 'callback' query parameter will be
 *     appended.
 * @param {Function} callback Callback on success.
 * @param {function()=} opt_errback Callback on error.
 * @param {string=} opt_callbackParam Custom query parameter for the JSONP
 *     callback. Default is 'callback'.
 */
ol.net.jsonp = function(url, callback, opt_errback, opt_callbackParam) {
  var script = ol.global.document.createElement('script');
  var key = 'olc_' + ol.getUid(callback);
  function cleanup() {
    delete ol.global[key];
    script.parentNode.removeChild(script);
  }
  script.async = true;
  script.src = url + (url.indexOf('?') == -1 ? '?' : '&') +
      (opt_callbackParam || 'callback') + '=' + key;
  var timer = ol.global.setTimeout(function() {
    cleanup();
    if (opt_errback) {
      opt_errback();
    }
  }, 10000);
  ol.global[key] = function(data) {
    ol.global.clearTimeout(timer);
    cleanup();
    callback(data);
  };
  ol.global.document.getElementsByTagName('head')[0].appendChild(script);
};

goog.provide('ol.render');

goog.require('ol.has');
goog.require('ol.transform');
goog.require('ol.render.canvas.Immediate');


/**
 * Binds a Canvas Immediate API to a canvas context, to allow drawing geometries
 * to the context's canvas.
 *
 * The units for geometry coordinates are css pixels relative to the top left
 * corner of the canvas element.
 * ```js
 * var canvas = document.createElement('canvas');
 * var render = ol.render.toContext(canvas.getContext('2d'),
 *     { size: [100, 100] });
 * render.setFillStrokeStyle(new ol.style.Fill({ color: blue }));
 * render.drawPolygon(
 *     new ol.geom.Polygon([[[0, 0], [100, 100], [100, 0], [0, 0]]]));
 * ```
 *
 * @param {CanvasRenderingContext2D} context Canvas context.
 * @param {olx.render.ToContextOptions=} opt_options Options.
 * @return {ol.render.canvas.Immediate} Canvas Immediate.
 * @api
 */
ol.render.toContext = function(context, opt_options) {
  var canvas = context.canvas;
  var options = opt_options ? opt_options : {};
  var pixelRatio = options.pixelRatio || ol.has.DEVICE_PIXEL_RATIO;
  var size = options.size;
  if (size) {
    canvas.width = size[0] * pixelRatio;
    canvas.height = size[1] * pixelRatio;
    canvas.style.width = size[0] + 'px';
    canvas.style.height = size[1] + 'px';
  }
  var extent = [0, 0, canvas.width, canvas.height];
  var transform = ol.transform.scale(ol.transform.create(), pixelRatio, pixelRatio);
  return new ol.render.canvas.Immediate(context, pixelRatio, extent, transform,
      0);
};

goog.provide('ol.reproj.Tile');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.obj');
goog.require('ol.reproj');
goog.require('ol.reproj.Triangulation');


/**
 * @classdesc
 * Class encapsulating single reprojected tile.
 * See {@link ol.source.TileImage}.
 *
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.proj.Projection} sourceProj Source projection.
 * @param {ol.tilegrid.TileGrid} sourceTileGrid Source tile grid.
 * @param {ol.proj.Projection} targetProj Target projection.
 * @param {ol.tilegrid.TileGrid} targetTileGrid Target tile grid.
 * @param {ol.TileCoord} tileCoord Coordinate of the tile.
 * @param {ol.TileCoord} wrappedTileCoord Coordinate of the tile wrapped in X.
 * @param {number} pixelRatio Pixel ratio.
 * @param {number} gutter Gutter of the source tiles.
 * @param {ol.ReprojTileFunctionType} getTileFunction
 *     Function returning source tiles (z, x, y, pixelRatio).
 * @param {number=} opt_errorThreshold Acceptable reprojection error (in px).
 * @param {boolean=} opt_renderEdges Render reprojection edges.
 */
ol.reproj.Tile = function(sourceProj, sourceTileGrid,
    targetProj, targetTileGrid, tileCoord, wrappedTileCoord,
    pixelRatio, gutter, getTileFunction,
    opt_errorThreshold,
    opt_renderEdges) {
  ol.Tile.call(this, tileCoord, ol.Tile.State.IDLE);

  /**
   * @private
   * @type {boolean}
   */
  this.renderEdges_ = opt_renderEdges !== undefined ? opt_renderEdges : false;

  /**
   * @private
   * @type {number}
   */
  this.pixelRatio_ = pixelRatio;

  /**
   * @private
   * @type {number}
   */
  this.gutter_ = gutter;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = null;

  /**
   * @private
   * @type {Object.<number, HTMLCanvasElement>}
   */
  this.canvasByContext_ = {};

  /**
   * @private
   * @type {ol.tilegrid.TileGrid}
   */
  this.sourceTileGrid_ = sourceTileGrid;

  /**
   * @private
   * @type {ol.tilegrid.TileGrid}
   */
  this.targetTileGrid_ = targetTileGrid;

  /**
   * @private
   * @type {ol.TileCoord}
   */
  this.wrappedTileCoord_ = wrappedTileCoord ? wrappedTileCoord : tileCoord;

  /**
   * @private
   * @type {!Array.<ol.Tile>}
   */
  this.sourceTiles_ = [];

  /**
   * @private
   * @type {Array.<ol.EventsKey>}
   */
  this.sourcesListenerKeys_ = null;

  /**
   * @private
   * @type {number}
   */
  this.sourceZ_ = 0;

  var targetExtent = targetTileGrid.getTileCoordExtent(this.wrappedTileCoord_);
  var maxTargetExtent = this.targetTileGrid_.getExtent();
  var maxSourceExtent = this.sourceTileGrid_.getExtent();

  var limitedTargetExtent = maxTargetExtent ?
      ol.extent.getIntersection(targetExtent, maxTargetExtent) : targetExtent;

  if (ol.extent.getArea(limitedTargetExtent) === 0) {
    // Tile is completely outside range -> EMPTY
    // TODO: is it actually correct that the source even creates the tile ?
    this.state = ol.Tile.State.EMPTY;
    return;
  }

  var sourceProjExtent = sourceProj.getExtent();
  if (sourceProjExtent) {
    if (!maxSourceExtent) {
      maxSourceExtent = sourceProjExtent;
    } else {
      maxSourceExtent = ol.extent.getIntersection(
          maxSourceExtent, sourceProjExtent);
    }
  }

  var targetResolution = targetTileGrid.getResolution(
      this.wrappedTileCoord_[0]);

  var targetCenter = ol.extent.getCenter(limitedTargetExtent);
  var sourceResolution = ol.reproj.calculateSourceResolution(
      sourceProj, targetProj, targetCenter, targetResolution);

  if (!isFinite(sourceResolution) || sourceResolution <= 0) {
    // invalid sourceResolution -> EMPTY
    // probably edges of the projections when no extent is defined
    this.state = ol.Tile.State.EMPTY;
    return;
  }

  var errorThresholdInPixels = opt_errorThreshold !== undefined ?
      opt_errorThreshold : ol.DEFAULT_RASTER_REPROJECTION_ERROR_THRESHOLD;

  /**
   * @private
   * @type {!ol.reproj.Triangulation}
   */
  this.triangulation_ = new ol.reproj.Triangulation(
      sourceProj, targetProj, limitedTargetExtent, maxSourceExtent,
      sourceResolution * errorThresholdInPixels);

  if (this.triangulation_.getTriangles().length === 0) {
    // no valid triangles -> EMPTY
    this.state = ol.Tile.State.EMPTY;
    return;
  }

  this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
  var sourceExtent = this.triangulation_.calculateSourceExtent();

  if (maxSourceExtent) {
    if (sourceProj.canWrapX()) {
      sourceExtent[1] = ol.math.clamp(
          sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]);
      sourceExtent[3] = ol.math.clamp(
          sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]);
    } else {
      sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent);
    }
  }

  if (!ol.extent.getArea(sourceExtent)) {
    this.state = ol.Tile.State.EMPTY;
  } else {
    var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
        sourceExtent, this.sourceZ_);

    var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight();
    if (goog.DEBUG && !(tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES)) {
      console.assert(false, 'reasonable number of tiles is required');
      this.state = ol.Tile.State.ERROR;
      return;
    }
    for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) {
      for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) {
        var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
        if (tile) {
          this.sourceTiles_.push(tile);
        }
      }
    }

    if (this.sourceTiles_.length === 0) {
      this.state = ol.Tile.State.EMPTY;
    }
  }
};
ol.inherits(ol.reproj.Tile, ol.Tile);


/**
 * @inheritDoc
 */
ol.reproj.Tile.prototype.disposeInternal = function() {
  if (this.state == ol.Tile.State.LOADING) {
    this.unlistenSources_();
  }
  ol.Tile.prototype.disposeInternal.call(this);
};


/**
 * @inheritDoc
 */
ol.reproj.Tile.prototype.getImage = function(opt_context) {
  if (opt_context !== undefined) {
    var image;
    var key = ol.getUid(opt_context);
    if (key in this.canvasByContext_) {
      return this.canvasByContext_[key];
    } else if (ol.obj.isEmpty(this.canvasByContext_)) {
      image = this.canvas_;
    } else {
      image = /** @type {HTMLCanvasElement} */ (this.canvas_.cloneNode(false));
    }
    this.canvasByContext_[key] = image;
    return image;
  } else {
    return this.canvas_;
  }
};


/**
 * @private
 */
ol.reproj.Tile.prototype.reproject_ = function() {
  var sources = [];
  this.sourceTiles_.forEach(function(tile, i, arr) {
    if (tile && tile.getState() == ol.Tile.State.LOADED) {
      sources.push({
        extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord),
        image: tile.getImage()
      });
    }
  }, this);
  this.sourceTiles_.length = 0;

  if (sources.length === 0) {
    this.state = ol.Tile.State.ERROR;
  } else {
    var z = this.wrappedTileCoord_[0];
    var size = this.targetTileGrid_.getTileSize(z);
    var width = typeof size === 'number' ? size : size[0];
    var height = typeof size === 'number' ? size : size[1];
    var targetResolution = this.targetTileGrid_.getResolution(z);
    var sourceResolution = this.sourceTileGrid_.getResolution(this.sourceZ_);

    var targetExtent = this.targetTileGrid_.getTileCoordExtent(
        this.wrappedTileCoord_);
    this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_,
        sourceResolution, this.sourceTileGrid_.getExtent(),
        targetResolution, targetExtent, this.triangulation_, sources,
        this.gutter_, this.renderEdges_);

    this.state = ol.Tile.State.LOADED;
  }
  this.changed();
};


/**
 * @inheritDoc
 */
ol.reproj.Tile.prototype.load = function() {
  if (this.state == ol.Tile.State.IDLE) {
    this.state = ol.Tile.State.LOADING;
    this.changed();

    var leftToLoad = 0;

    goog.DEBUG && console.assert(!this.sourcesListenerKeys_,
        'this.sourcesListenerKeys_ should be null');

    this.sourcesListenerKeys_ = [];
    this.sourceTiles_.forEach(function(tile, i, arr) {
      var state = tile.getState();
      if (state == ol.Tile.State.IDLE || state == ol.Tile.State.LOADING) {
        leftToLoad++;

        var sourceListenKey;
        sourceListenKey = ol.events.listen(tile, ol.events.EventType.CHANGE,
            function(e) {
              var state = tile.getState();
              if (state == ol.Tile.State.LOADED ||
                  state == ol.Tile.State.ERROR ||
                  state == ol.Tile.State.EMPTY) {
                ol.events.unlistenByKey(sourceListenKey);
                leftToLoad--;
                goog.DEBUG && console.assert(leftToLoad >= 0,
                    'leftToLoad should not be negative');
                if (leftToLoad === 0) {
                  this.unlistenSources_();
                  this.reproject_();
                }
              }
            }, this);
        this.sourcesListenerKeys_.push(sourceListenKey);
      }
    }, this);

    this.sourceTiles_.forEach(function(tile, i, arr) {
      var state = tile.getState();
      if (state == ol.Tile.State.IDLE) {
        tile.load();
      }
    });

    if (leftToLoad === 0) {
      var global = ol.global;
      global.setTimeout(this.reproject_.bind(this), 0);
    }
  }
};


/**
 * @private
 */
ol.reproj.Tile.prototype.unlistenSources_ = function() {
  this.sourcesListenerKeys_.forEach(ol.events.unlistenByKey);
  this.sourcesListenerKeys_ = null;
};

goog.provide('ol.TileUrlFunction');

goog.require('ol.asserts');
goog.require('ol.math');
goog.require('ol.tilecoord');


/**
 * @param {string} template Template.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTemplate = function(template, tileGrid) {
  var zRegEx = /\{z\}/g;
  var xRegEx = /\{x\}/g;
  var yRegEx = /\{y\}/g;
  var dashYRegEx = /\{-y\}/g;
  return (
      /**
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {number} pixelRatio Pixel ratio.
       * @param {ol.proj.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, pixelRatio, projection) {
        if (!tileCoord) {
          return undefined;
        } else {
          return template.replace(zRegEx, tileCoord[0].toString())
              .replace(xRegEx, tileCoord[1].toString())
              .replace(yRegEx, function() {
                var y = -tileCoord[2] - 1;
                return y.toString();
              })
              .replace(dashYRegEx, function() {
                var z = tileCoord[0];
                var range = tileGrid.getFullTileRange(z);
                ol.asserts.assert(range, 55); // The {-y} placeholder requires a tile grid with extent
                var y = range.getHeight() + tileCoord[2];
                return y.toString();
              });
        }
      });
};


/**
 * @param {Array.<string>} templates Templates.
 * @param {ol.tilegrid.TileGrid} tileGrid Tile grid.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTemplates = function(templates, tileGrid) {
  var len = templates.length;
  var tileUrlFunctions = new Array(len);
  for (var i = 0; i < len; ++i) {
    tileUrlFunctions[i] = ol.TileUrlFunction.createFromTemplate(
        templates[i], tileGrid);
  }
  return ol.TileUrlFunction.createFromTileUrlFunctions(tileUrlFunctions);
};


/**
 * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
  goog.DEBUG && console.assert(tileUrlFunctions.length > 0,
      'Length of tile url functions should be greater than 0');
  if (tileUrlFunctions.length === 1) {
    return tileUrlFunctions[0];
  }
  return (
      /**
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {number} pixelRatio Pixel ratio.
       * @param {ol.proj.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, pixelRatio, projection) {
        if (!tileCoord) {
          return undefined;
        } else {
          var h = ol.tilecoord.hash(tileCoord);
          var index = ol.math.modulo(h, tileUrlFunctions.length);
          return tileUrlFunctions[index](tileCoord, pixelRatio, projection);
        }
      });
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @return {string|undefined} Tile URL.
 */
ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, pixelRatio, projection) {
  return undefined;
};


/**
 * @param {string} url URL.
 * @return {Array.<string>} Array of urls.
 */
ol.TileUrlFunction.expandUrl = function(url) {
  var urls = [];
  var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url);
  if (match) {
    var startCharCode = match[1].charCodeAt(0);
    var stopCharCode = match[2].charCodeAt(0);
    var charCode;
    for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
      urls.push(url.replace(match[0], String.fromCharCode(charCode)));
    }
  } else {
    urls.push(url);
  }
  return urls;
};

goog.provide('ol.TileCache');

goog.require('ol');
goog.require('ol.structs.LRUCache');
goog.require('ol.tilecoord');


/**
 * @constructor
 * @extends {ol.structs.LRUCache.<ol.Tile>}
 * @param {number=} opt_highWaterMark High water mark.
 * @struct
 */
ol.TileCache = function(opt_highWaterMark) {

  ol.structs.LRUCache.call(this);

  /**
   * @private
   * @type {number}
   */
  this.highWaterMark_ = opt_highWaterMark !== undefined ? opt_highWaterMark : 2048;

};
ol.inherits(ol.TileCache, ol.structs.LRUCache);


/**
 * @return {boolean} Can expire cache.
 */
ol.TileCache.prototype.canExpireCache = function() {
  return this.getCount() > this.highWaterMark_;
};


/**
 * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
 */
ol.TileCache.prototype.expireCache = function(usedTiles) {
  var tile, zKey;
  while (this.canExpireCache()) {
    tile = this.peekLast();
    zKey = tile.tileCoord[0].toString();
    if (zKey in usedTiles && usedTiles[zKey].contains(tile.tileCoord)) {
      break;
    } else {
      this.pop().dispose();
    }
  }
};


/**
 * Remove a tile range from the cache, e.g. to invalidate tiles.
 * @param {ol.TileRange} tileRange The tile range to prune.
 */
ol.TileCache.prototype.pruneTileRange = function(tileRange) {
  var i = this.getCount(),
      key;
  while (i--) {
    key = this.peekLastKey();
    if (tileRange.contains(ol.tilecoord.createFromString(key))) {
      this.pop().dispose();
    } else {
      this.get(key);
    }
  }
};

goog.provide('ol.source.Tile');
goog.provide('ol.source.TileEvent');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.TileCache');
goog.require('ol.events.Event');
goog.require('ol.proj');
goog.require('ol.size');
goog.require('ol.source.Source');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Base class for sources providing images divided into a tile grid.
 *
 * @constructor
 * @extends {ol.source.Source}
 * @param {ol.SourceTileOptions} options Tile source options.
 * @api
 */
ol.source.Tile = function(options) {

  ol.source.Source.call(this, {
    attributions: options.attributions,
    extent: options.extent,
    logo: options.logo,
    projection: options.projection,
    state: options.state,
    wrapX: options.wrapX
  });

  /**
   * @private
   * @type {boolean}
   */
  this.opaque_ = options.opaque !== undefined ? options.opaque : false;

  /**
   * @private
   * @type {number}
   */
  this.tilePixelRatio_ = options.tilePixelRatio !== undefined ?
      options.tilePixelRatio : 1;

  /**
   * @protected
   * @type {ol.tilegrid.TileGrid}
   */
  this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null;

  /**
   * @protected
   * @type {ol.TileCache}
   */
  this.tileCache = new ol.TileCache(options.cacheSize);

  /**
   * @protected
   * @type {ol.Size}
   */
  this.tmpSize = [0, 0];

  /**
   * @private
   * @type {string}
   */
  this.key_ = '';

};
ol.inherits(ol.source.Tile, ol.source.Source);


/**
 * @return {boolean} Can expire cache.
 */
ol.source.Tile.prototype.canExpireCache = function() {
  return this.tileCache.canExpireCache();
};


/**
 * @param {ol.proj.Projection} projection Projection.
 * @param {Object.<string, ol.TileRange>} usedTiles Used tiles.
 */
ol.source.Tile.prototype.expireCache = function(projection, usedTiles) {
  var tileCache = this.getTileCacheForProjection(projection);
  if (tileCache) {
    tileCache.expireCache(usedTiles);
  }
};


/**
 * @param {ol.proj.Projection} projection Projection.
 * @param {number} z Zoom level.
 * @param {ol.TileRange} tileRange Tile range.
 * @param {function(ol.Tile):(boolean|undefined)} callback Called with each
 *     loaded tile.  If the callback returns `false`, the tile will not be
 *     considered loaded.
 * @return {boolean} The tile range is fully covered with loaded tiles.
 */
ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange, callback) {
  var tileCache = this.getTileCacheForProjection(projection);
  if (!tileCache) {
    return false;
  }

  var covered = true;
  var tile, tileCoordKey, loaded;
  for (var x = tileRange.minX; x <= tileRange.maxX; ++x) {
    for (var y = tileRange.minY; y <= tileRange.maxY; ++y) {
      tileCoordKey = this.getKeyZXY(z, x, y);
      loaded = false;
      if (tileCache.containsKey(tileCoordKey)) {
        tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey));
        loaded = tile.getState() === ol.Tile.State.LOADED;
        if (loaded) {
          loaded = (callback(tile) !== false);
        }
      }
      if (!loaded) {
        covered = false;
      }
    }
  }
  return covered;
};


/**
 * @param {ol.proj.Projection} projection Projection.
 * @return {number} Gutter.
 */
ol.source.Tile.prototype.getGutter = function(projection) {
  return 0;
};


/**
 * Return the key to be used for all tiles in the source.
 * @return {string} The key for all tiles.
 * @protected
 */
ol.source.Tile.prototype.getKey = function() {
  return this.key_;
};


/**
 * Set the value to be used as the key for all tiles in the source.
 * @param {string} key The key for tiles.
 * @protected
 */
ol.source.Tile.prototype.setKey = function(key) {
  if (this.key_ !== key) {
    this.key_ = key;
    this.changed();
  }
};


/**
 * @param {number} z Z.
 * @param {number} x X.
 * @param {number} y Y.
 * @return {string} Key.
 * @protected
 */
ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;


/**
 * @param {ol.proj.Projection} projection Projection.
 * @return {boolean} Opaque.
 */
ol.source.Tile.prototype.getOpaque = function(projection) {
  return this.opaque_;
};


/**
 * @inheritDoc
 */
ol.source.Tile.prototype.getResolutions = function() {
  return this.tileGrid.getResolutions();
};


/**
 * @abstract
 * @param {number} z Tile coordinate z.
 * @param {number} x Tile coordinate x.
 * @param {number} y Tile coordinate y.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @return {!ol.Tile} Tile.
 */
ol.source.Tile.prototype.getTile = function(z, x, y, pixelRatio, projection) {};


/**
 * Return the tile grid of the tile source.
 * @return {ol.tilegrid.TileGrid} Tile grid.
 * @api stable
 */
ol.source.Tile.prototype.getTileGrid = function() {
  return this.tileGrid;
};


/**
 * @param {ol.proj.Projection} projection Projection.
 * @return {!ol.tilegrid.TileGrid} Tile grid.
 */
ol.source.Tile.prototype.getTileGridForProjection = function(projection) {
  if (!this.tileGrid) {
    return ol.tilegrid.getForProjection(projection);
  } else {
    return this.tileGrid;
  }
};


/**
 * @param {ol.proj.Projection} projection Projection.
 * @return {ol.TileCache} Tile cache.
 * @protected
 */
ol.source.Tile.prototype.getTileCacheForProjection = function(projection) {
  var thisProj = this.getProjection();
  if (thisProj && !ol.proj.equivalent(thisProj, projection)) {
    return null;
  } else {
    return this.tileCache;
  }
};


/**
 * Get the tile pixel ratio for this source. Subclasses may override this
 * method, which is meant to return a supported pixel ratio that matches the
 * provided `opt_pixelRatio` as close as possible. When no `opt_pixelRatio` is
 * provided, it is meant to return `this.tilePixelRatio_`.
 * @param {number=} opt_pixelRatio Pixel ratio.
 * @return {number} Tile pixel ratio.
 */
ol.source.Tile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
  return this.tilePixelRatio_;
};


/**
 * @param {number} z Z.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @return {ol.Size} Tile size.
 */
ol.source.Tile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
  var tileGrid = this.getTileGridForProjection(projection);
  var tilePixelRatio = this.getTilePixelRatio(pixelRatio);
  var tileSize = ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize);
  if (tilePixelRatio == 1) {
    return tileSize;
  } else {
    return ol.size.scale(tileSize, tilePixelRatio, this.tmpSize);
  }
};


/**
 * Returns a tile coordinate wrapped around the x-axis. When the tile coordinate
 * is outside the resolution and extent range of the tile grid, `null` will be
 * returned.
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.proj.Projection=} opt_projection Projection.
 * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or
 *     null if no tile URL should be created for the passed `tileCoord`.
 */
ol.source.Tile.prototype.getTileCoordForTileUrlFunction = function(tileCoord, opt_projection) {
  var projection = opt_projection !== undefined ?
      opt_projection : this.getProjection();
  var tileGrid = this.getTileGridForProjection(projection);
  if (this.getWrapX() && projection.isGlobal()) {
    tileCoord = ol.tilegrid.wrapX(tileGrid, tileCoord, projection);
  }
  return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? tileCoord : null;
};


/**
 * @inheritDoc
 */
ol.source.Tile.prototype.refresh = function() {
  this.tileCache.clear();
  this.changed();
};


/**
 * Marks a tile coord as being used, without triggering a load.
 * @param {number} z Tile coordinate z.
 * @param {number} x Tile coordinate x.
 * @param {number} y Tile coordinate y.
 * @param {ol.proj.Projection} projection Projection.
 */
ol.source.Tile.prototype.useTile = ol.nullFunction;


/**
 * @classdesc
 * Events emitted by {@link ol.source.Tile} instances are instances of this
 * type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.source.TileEvent}
 * @param {string} type Type.
 * @param {ol.Tile} tile The tile.
 */
ol.source.TileEvent = function(type, tile) {

  ol.events.Event.call(this, type);

  /**
   * The tile related to the event.
   * @type {ol.Tile}
   * @api
   */
  this.tile = tile;

};
ol.inherits(ol.source.TileEvent, ol.events.Event);


/**
 * @enum {string}
 */
ol.source.TileEventType = {

  /**
   * Triggered when a tile starts loading.
   * @event ol.source.TileEvent#tileloadstart
   * @api stable
   */
  TILELOADSTART: 'tileloadstart',

  /**
   * Triggered when a tile finishes loading.
   * @event ol.source.TileEvent#tileloadend
   * @api stable
   */
  TILELOADEND: 'tileloadend',

  /**
   * Triggered if tile loading results in an error.
   * @event ol.source.TileEvent#tileloaderror
   * @api stable
   */
  TILELOADERROR: 'tileloaderror'

};

goog.provide('ol.source.UrlTile');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.TileUrlFunction');
goog.require('ol.source.Tile');
goog.require('ol.source.TileEvent');


/**
 * @classdesc
 * Base class for sources providing tiles divided into a tile grid over http.
 *
 * @constructor
 * @fires ol.source.TileEvent
 * @extends {ol.source.Tile}
 * @param {ol.SourceUrlTileOptions} options Image tile options.
 */
ol.source.UrlTile = function(options) {

  ol.source.Tile.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    extent: options.extent,
    logo: options.logo,
    opaque: options.opaque,
    projection: options.projection,
    state: options.state,
    tileGrid: options.tileGrid,
    tilePixelRatio: options.tilePixelRatio,
    wrapX: options.wrapX
  });

  /**
   * @protected
   * @type {ol.TileLoadFunctionType}
   */
  this.tileLoadFunction = options.tileLoadFunction;

  /**
   * @protected
   * @type {ol.TileUrlFunctionType}
   */
  this.tileUrlFunction = this.fixedTileUrlFunction ?
      this.fixedTileUrlFunction.bind(this) :
      ol.TileUrlFunction.nullTileUrlFunction;

  /**
   * @protected
   * @type {!Array.<string>|null}
   */
  this.urls = null;

  if (options.urls) {
    this.setUrls(options.urls);
  } else if (options.url) {
    this.setUrl(options.url);
  }
  if (options.tileUrlFunction) {
    this.setTileUrlFunction(options.tileUrlFunction);
  }

};
ol.inherits(ol.source.UrlTile, ol.source.Tile);


/**
 * @type {ol.TileUrlFunctionType|undefined}
 * @protected
 */
ol.source.UrlTile.prototype.fixedTileUrlFunction;

/**
 * Return the tile load function of the source.
 * @return {ol.TileLoadFunctionType} TileLoadFunction
 * @api
 */
ol.source.UrlTile.prototype.getTileLoadFunction = function() {
  return this.tileLoadFunction;
};


/**
 * Return the tile URL function of the source.
 * @return {ol.TileUrlFunctionType} TileUrlFunction
 * @api
 */
ol.source.UrlTile.prototype.getTileUrlFunction = function() {
  return this.tileUrlFunction;
};


/**
 * Return the URLs used for this source.
 * When a tileUrlFunction is used instead of url or urls,
 * null will be returned.
 * @return {!Array.<string>|null} URLs.
 * @api
 */
ol.source.UrlTile.prototype.getUrls = function() {
  return this.urls;
};


/**
 * Handle tile change events.
 * @param {ol.events.Event} event Event.
 * @protected
 */
ol.source.UrlTile.prototype.handleTileChange = function(event) {
  var tile = /** @type {ol.Tile} */ (event.target);
  switch (tile.getState()) {
    case ol.Tile.State.LOADING:
      this.dispatchEvent(
          new ol.source.TileEvent(ol.source.TileEventType.TILELOADSTART, tile));
      break;
    case ol.Tile.State.LOADED:
      this.dispatchEvent(
          new ol.source.TileEvent(ol.source.TileEventType.TILELOADEND, tile));
      break;
    case ol.Tile.State.ERROR:
      this.dispatchEvent(
          new ol.source.TileEvent(ol.source.TileEventType.TILELOADERROR, tile));
      break;
    default:
      // pass
  }
};


/**
 * Set the tile load function of the source.
 * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
 * @api
 */
ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
  this.tileCache.clear();
  this.tileLoadFunction = tileLoadFunction;
  this.changed();
};


/**
 * Set the tile URL function of the source.
 * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
 * @param {string=} opt_key Optional new tile key for the source.
 * @api
 */
ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
  this.tileUrlFunction = tileUrlFunction;
  if (typeof opt_key !== 'undefined') {
    this.setKey(opt_key);
  } else {
    this.changed();
  }
};


/**
 * Set the URL to use for requests.
 * @param {string} url URL.
 * @api stable
 */
ol.source.UrlTile.prototype.setUrl = function(url) {
  var urls = this.urls = ol.TileUrlFunction.expandUrl(url);
  this.setTileUrlFunction(this.fixedTileUrlFunction ?
      this.fixedTileUrlFunction.bind(this) :
      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url);
};


/**
 * Set the URLs to use for requests.
 * @param {Array.<string>} urls URLs.
 * @api stable
 */
ol.source.UrlTile.prototype.setUrls = function(urls) {
  this.urls = urls;
  var key = urls.join('\n');
  this.setTileUrlFunction(this.fixedTileUrlFunction ?
      this.fixedTileUrlFunction.bind(this) :
      ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), key);
};


/**
 * @inheritDoc
 */
ol.source.UrlTile.prototype.useTile = function(z, x, y) {
  var tileCoordKey = this.getKeyZXY(z, x, y);
  if (this.tileCache.containsKey(tileCoordKey)) {
    this.tileCache.get(tileCoordKey);
  }
};

goog.provide('ol.source.TileImage');

goog.require('ol');
goog.require('ol.ImageTile');
goog.require('ol.Tile');
goog.require('ol.TileCache');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.proj');
goog.require('ol.reproj.Tile');
goog.require('ol.source.UrlTile');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * Base class for sources providing images divided into a tile grid.
 *
 * @constructor
 * @fires ol.source.TileEvent
 * @extends {ol.source.UrlTile}
 * @param {olx.source.TileImageOptions} options Image tile options.
 * @api
 */
ol.source.TileImage = function(options) {

  ol.source.UrlTile.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    extent: options.extent,
    logo: options.logo,
    opaque: options.opaque,
    projection: options.projection,
    state: options.state,
    tileGrid: options.tileGrid,
    tileLoadFunction: options.tileLoadFunction ?
        options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction,
    tilePixelRatio: options.tilePixelRatio,
    tileUrlFunction: options.tileUrlFunction,
    url: options.url,
    urls: options.urls,
    wrapX: options.wrapX
  });

  /**
   * @protected
   * @type {?string}
   */
  this.crossOrigin =
      options.crossOrigin !== undefined ? options.crossOrigin : null;

  /**
   * @protected
   * @type {function(new: ol.ImageTile, ol.TileCoord, ol.Tile.State, string,
   *        ?string, ol.TileLoadFunctionType)}
   */
  this.tileClass = options.tileClass !== undefined ?
      options.tileClass : ol.ImageTile;

  /**
   * @protected
   * @type {Object.<string, ol.TileCache>}
   */
  this.tileCacheForProjection = {};

  /**
   * @protected
   * @type {Object.<string, ol.tilegrid.TileGrid>}
   */
  this.tileGridForProjection = {};

  /**
   * @private
   * @type {number|undefined}
   */
  this.reprojectionErrorThreshold_ = options.reprojectionErrorThreshold;

  /**
   * @private
   * @type {boolean}
   */
  this.renderReprojectionEdges_ = false;
};
ol.inherits(ol.source.TileImage, ol.source.UrlTile);


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.canExpireCache = function() {
  if (!ol.ENABLE_RASTER_REPROJECTION) {
    return ol.source.UrlTile.prototype.canExpireCache.call(this);
  }
  if (this.tileCache.canExpireCache()) {
    return true;
  } else {
    for (var key in this.tileCacheForProjection) {
      if (this.tileCacheForProjection[key].canExpireCache()) {
        return true;
      }
    }
  }
  return false;
};


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.expireCache = function(projection, usedTiles) {
  if (!ol.ENABLE_RASTER_REPROJECTION) {
    ol.source.UrlTile.prototype.expireCache.call(this, projection, usedTiles);
    return;
  }
  var usedTileCache = this.getTileCacheForProjection(projection);

  this.tileCache.expireCache(this.tileCache == usedTileCache ? usedTiles : {});
  for (var id in this.tileCacheForProjection) {
    var tileCache = this.tileCacheForProjection[id];
    tileCache.expireCache(tileCache == usedTileCache ? usedTiles : {});
  }
};


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.getGutter = function(projection) {
  if (ol.ENABLE_RASTER_REPROJECTION &&
      this.getProjection() && projection &&
      !ol.proj.equivalent(this.getProjection(), projection)) {
    return 0;
  } else {
    return this.getGutterInternal();
  }
};


/**
 * @protected
 * @return {number} Gutter.
 */
ol.source.TileImage.prototype.getGutterInternal = function() {
  return 0;
};


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.getOpaque = function(projection) {
  if (ol.ENABLE_RASTER_REPROJECTION &&
      this.getProjection() && projection &&
      !ol.proj.equivalent(this.getProjection(), projection)) {
    return false;
  } else {
    return ol.source.UrlTile.prototype.getOpaque.call(this, projection);
  }
};


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.getTileGridForProjection = function(projection) {
  if (!ol.ENABLE_RASTER_REPROJECTION) {
    return ol.source.UrlTile.prototype.getTileGridForProjection.call(this, projection);
  }
  var thisProj = this.getProjection();
  if (this.tileGrid &&
      (!thisProj || ol.proj.equivalent(thisProj, projection))) {
    return this.tileGrid;
  } else {
    var projKey = ol.getUid(projection).toString();
    if (!(projKey in this.tileGridForProjection)) {
      this.tileGridForProjection[projKey] =
          ol.tilegrid.getForProjection(projection);
    }
    return /** @type {!ol.tilegrid.TileGrid} */ (this.tileGridForProjection[projKey]);
  }
};


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) {
  if (!ol.ENABLE_RASTER_REPROJECTION) {
    return ol.source.UrlTile.prototype.getTileCacheForProjection.call(this, projection);
  }
  var thisProj = this.getProjection();
  if (!thisProj || ol.proj.equivalent(thisProj, projection)) {
    return this.tileCache;
  } else {
    var projKey = ol.getUid(projection).toString();
    if (!(projKey in this.tileCacheForProjection)) {
      this.tileCacheForProjection[projKey] = new ol.TileCache();
    }
    return this.tileCacheForProjection[projKey];
  }
};


/**
 * @param {number} z Tile coordinate z.
 * @param {number} x Tile coordinate x.
 * @param {number} y Tile coordinate y.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {string} key The key set on the tile.
 * @return {!ol.Tile} Tile.
 * @private
 */
ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projection, key) {
  var tileCoord = [z, x, y];
  var urlTileCoord = this.getTileCoordForTileUrlFunction(
      tileCoord, projection);
  var tileUrl = urlTileCoord ?
      this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
  var tile = new this.tileClass(
      tileCoord,
      tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY,
      tileUrl !== undefined ? tileUrl : '',
      this.crossOrigin,
      this.tileLoadFunction);
  tile.key = key;
  ol.events.listen(tile, ol.events.EventType.CHANGE,
      this.handleTileChange, this);
  return tile;
};


/**
 * @inheritDoc
 */
ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection) {
  if (!ol.ENABLE_RASTER_REPROJECTION ||
      !this.getProjection() ||
      !projection ||
      ol.proj.equivalent(this.getProjection(), projection)) {
    return this.getTileInternal(z, x, y, pixelRatio, /** @type {!ol.proj.Projection} */ (projection));
  } else {
    var cache = this.getTileCacheForProjection(projection);
    var tileCoord = [z, x, y];
    var tile;
    var tileCoordKey = this.getKeyZXY.apply(this, tileCoord);
    if (cache.containsKey(tileCoordKey)) {
      tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
    }
    var key = this.getKey();
    if (tile && tile.key == key) {
      return tile;
    } else {
      var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection());
      var sourceTileGrid = this.getTileGridForProjection(sourceProjection);
      var targetTileGrid = this.getTileGridForProjection(projection);
      var wrappedTileCoord =
          this.getTileCoordForTileUrlFunction(tileCoord, projection);
      var newTile = new ol.reproj.Tile(
          sourceProjection, sourceTileGrid,
          projection, targetTileGrid,
          tileCoord, wrappedTileCoord, this.getTilePixelRatio(pixelRatio),
          this.getGutterInternal(),
          function(z, x, y, pixelRatio) {
            return this.getTileInternal(z, x, y, pixelRatio, sourceProjection);
          }.bind(this), this.reprojectionErrorThreshold_,
          this.renderReprojectionEdges_);
      newTile.key = key;

      if (tile) {
        newTile.interimTile = tile;
        cache.replace(tileCoordKey, newTile);
      } else {
        cache.set(tileCoordKey, newTile);
      }
      return newTile;
    }
  }
};


/**
 * @param {number} z Tile coordinate z.
 * @param {number} x Tile coordinate x.
 * @param {number} y Tile coordinate y.
 * @param {number} pixelRatio Pixel ratio.
 * @param {!ol.proj.Projection} projection Projection.
 * @return {!ol.Tile} Tile.
 * @protected
 */
ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
  var tile = null;
  var tileCoordKey = this.getKeyZXY(z, x, y);
  var key = this.getKey();
  if (!this.tileCache.containsKey(tileCoordKey)) {
    tile = this.createTile_(z, x, y, pixelRatio, projection, key);
    this.tileCache.set(tileCoordKey, tile);
  } else {
    tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
    if (tile.key != key) {
      // The source's params changed. If the tile has an interim tile and if we
      // can use it then we use it. Otherwise we create a new tile.  In both
      // cases we attempt to assign an interim tile to the new tile.
      var /** @type {ol.Tile} */ interimTile = tile;
      if (tile.interimTile && tile.interimTile.key == key) {
        goog.DEBUG && console.assert(tile.interimTile.getState() == ol.Tile.State.LOADED);
        goog.DEBUG && console.assert(tile.interimTile.interimTile === null);
        tile = tile.interimTile;
        if (interimTile.getState() == ol.Tile.State.LOADED) {
          tile.interimTile = interimTile;
        }
      } else {
        tile = this.createTile_(z, x, y, pixelRatio, projection, key);
        if (interimTile.getState() == ol.Tile.State.LOADED) {
          tile.interimTile = interimTile;
        } else if (interimTile.interimTile &&
            interimTile.interimTile.getState() == ol.Tile.State.LOADED) {
          tile.interimTile = interimTile.interimTile;
          interimTile.interimTile = null;
        }
      }
      if (tile.interimTile) {
        tile.interimTile.interimTile = null;
      }
      this.tileCache.replace(tileCoordKey, tile);
    }
  }
  return tile;
};


/**
 * Sets whether to render reprojection edges or not (usually for debugging).
 * @param {boolean} render Render the edges.
 * @api
 */
ol.source.TileImage.prototype.setRenderReprojectionEdges = function(render) {
  if (!ol.ENABLE_RASTER_REPROJECTION ||
      this.renderReprojectionEdges_ == render) {
    return;
  }
  this.renderReprojectionEdges_ = render;
  for (var id in this.tileCacheForProjection) {
    this.tileCacheForProjection[id].clear();
  }
  this.changed();
};


/**
 * Sets the tile grid to use when reprojecting the tiles to the given
 * projection instead of the default tile grid for the projection.
 *
 * This can be useful when the default tile grid cannot be created
 * (e.g. projection has no extent defined) or
 * for optimization reasons (custom tile size, resolutions, ...).
 *
 * @param {ol.ProjectionLike} projection Projection.
 * @param {ol.tilegrid.TileGrid} tilegrid Tile grid to use for the projection.
 * @api
 */
ol.source.TileImage.prototype.setTileGridForProjection = function(projection, tilegrid) {
  if (ol.ENABLE_RASTER_REPROJECTION) {
    var proj = ol.proj.get(projection);
    if (proj) {
      var projKey = ol.getUid(proj).toString();
      if (!(projKey in this.tileGridForProjection)) {
        this.tileGridForProjection[projKey] = tilegrid;
      }
    }
  }
};


/**
 * @param {ol.ImageTile} imageTile Image tile.
 * @param {string} src Source.
 */
ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
  imageTile.getImage().src = src;
};

goog.provide('ol.source.BingMaps');

goog.require('ol');
goog.require('ol.Attribution');
goog.require('ol.TileUrlFunction');
goog.require('ol.extent');
goog.require('ol.net');
goog.require('ol.proj');
goog.require('ol.source.State');
goog.require('ol.source.TileImage');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * Layer source for Bing Maps tile data.
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.BingMapsOptions} options Bing Maps options.
 * @api stable
 */
ol.source.BingMaps = function(options) {

  ol.source.TileImage.call(this, {
    cacheSize: options.cacheSize,
    crossOrigin: 'anonymous',
    opaque: true,
    projection: ol.proj.get('EPSG:3857'),
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    state: ol.source.State.LOADING,
    tileLoadFunction: options.tileLoadFunction,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

  /**
   * @private
   * @type {string}
   */
  this.culture_ = options.culture !== undefined ? options.culture : 'en-us';

  /**
   * @private
   * @type {number}
   */
  this.maxZoom_ = options.maxZoom !== undefined ? options.maxZoom : -1;

  var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/' +
      options.imagerySet +
      '?uriScheme=https&include=ImageryProviders&key=' + options.key;

  ol.net.jsonp(url, this.handleImageryMetadataResponse.bind(this), undefined,
      'jsonp');

};
ol.inherits(ol.source.BingMaps, ol.source.TileImage);


/**
 * The attribution containing a link to the Microsoft® Bing™ Maps Platform APIs’
 * Terms Of Use.
 * @const
 * @type {ol.Attribution}
 * @api
 */
ol.source.BingMaps.TOS_ATTRIBUTION = new ol.Attribution({
  html: '<a class="ol-attribution-bing-tos" ' +
      'href="http://www.microsoft.com/maps/product/terms.html">' +
      'Terms of Use</a>'
});


/**
 * @param {BingMapsImageryMetadataResponse} response Response.
 */
ol.source.BingMaps.prototype.handleImageryMetadataResponse = function(response) {

  if (response.statusCode != 200 ||
      response.statusDescription != 'OK' ||
      response.authenticationResultCode != 'ValidCredentials' ||
      response.resourceSets.length != 1 ||
      response.resourceSets[0].resources.length != 1) {
    this.setState(ol.source.State.ERROR);
    return;
  }

  var brandLogoUri = response.brandLogoUri;
  if (brandLogoUri.indexOf('https') == -1) {
    brandLogoUri = brandLogoUri.replace('http', 'https');
  }
  //var copyright = response.copyright;  // FIXME do we need to display this?
  var resource = response.resourceSets[0].resources[0];
  goog.DEBUG && console.assert(resource.imageWidth == resource.imageHeight,
      'resource has imageWidth equal to imageHeight, i.e. is square');
  var maxZoom = this.maxZoom_ == -1 ? resource.zoomMax : this.maxZoom_;

  var sourceProjection = this.getProjection();
  var extent = ol.tilegrid.extentFromProjection(sourceProjection);
  var tileSize = resource.imageWidth == resource.imageHeight ?
      resource.imageWidth : [resource.imageWidth, resource.imageHeight];
  var tileGrid = ol.tilegrid.createXYZ({
    extent: extent,
    minZoom: resource.zoomMin,
    maxZoom: maxZoom,
    tileSize: tileSize
  });
  this.tileGrid = tileGrid;

  var culture = this.culture_;
  this.tileUrlFunction = ol.TileUrlFunction.createFromTileUrlFunctions(
      resource.imageUrlSubdomains.map(function(subdomain) {
        var quadKeyTileCoord = [0, 0, 0];
        var imageUrl = resource.imageUrl
            .replace('{subdomain}', subdomain)
            .replace('{culture}', culture);
        return (
            /**
             * @param {ol.TileCoord} tileCoord Tile coordinate.
             * @param {number} pixelRatio Pixel ratio.
             * @param {ol.proj.Projection} projection Projection.
             * @return {string|undefined} Tile URL.
             */
            function(tileCoord, pixelRatio, projection) {
              goog.DEBUG && console.assert(ol.proj.equivalent(
                  projection, sourceProjection),
                  'projections are equivalent');
              if (!tileCoord) {
                return undefined;
              } else {
                ol.tilecoord.createOrUpdate(tileCoord[0], tileCoord[1],
                    -tileCoord[2] - 1, quadKeyTileCoord);
                return imageUrl.replace('{quadkey}', ol.tilecoord.quadKey(
                    quadKeyTileCoord));
              }
            });
      }));

  if (resource.imageryProviders) {
    var transform = ol.proj.getTransformFromProjections(
        ol.proj.get('EPSG:4326'), this.getProjection());

    var attributions = resource.imageryProviders.map(function(imageryProvider) {
      var html = imageryProvider.attribution;
      /** @type {Object.<string, Array.<ol.TileRange>>} */
      var tileRanges = {};
      imageryProvider.coverageAreas.forEach(function(coverageArea) {
        var minZ = coverageArea.zoomMin;
        var maxZ = Math.min(coverageArea.zoomMax, maxZoom);
        var bbox = coverageArea.bbox;
        var epsg4326Extent = [bbox[1], bbox[0], bbox[3], bbox[2]];
        var extent = ol.extent.applyTransform(epsg4326Extent, transform);
        var tileRange, z, zKey;
        for (z = minZ; z <= maxZ; ++z) {
          zKey = z.toString();
          tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
          if (zKey in tileRanges) {
            tileRanges[zKey].push(tileRange);
          } else {
            tileRanges[zKey] = [tileRange];
          }
        }
      });
      return new ol.Attribution({html: html, tileRanges: tileRanges});
    });
    attributions.push(ol.source.BingMaps.TOS_ATTRIBUTION);
    this.setAttributions(attributions);
  }

  this.setLogo(brandLogoUri);

  this.setState(ol.source.State.READY);

};

goog.provide('ol.source.XYZ');

goog.require('ol');
goog.require('ol.source.TileImage');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * Layer source for tile data with URLs in a set XYZ format that are
 * defined in a URL template. By default, this follows the widely-used
 * Google grid where `x` 0 and `y` 0 are in the top left. Grids like
 * TMS where `x` 0 and `y` 0 are in the bottom left can be used by
 * using the `{-y}` placeholder in the URL template, so long as the
 * source does not have a custom tile grid. In this case,
 * {@link ol.source.TileImage} can be used with a `tileUrlFunction`
 * such as:
 *
 *  tileUrlFunction: function(coordinate) {
 *    return 'http://mapserver.com/' + coordinate[0] + '/' +
 *        coordinate[1] + '/' + coordinate[2] + '.png';
 *    }
 *
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.XYZOptions=} opt_options XYZ options.
 * @api stable
 */
ol.source.XYZ = function(opt_options) {
  var options = opt_options || {};
  var projection = options.projection !== undefined ?
      options.projection : 'EPSG:3857';

  var tileGrid = options.tileGrid !== undefined ? options.tileGrid :
      ol.tilegrid.createXYZ({
        extent: ol.tilegrid.extentFromProjection(projection),
        maxZoom: options.maxZoom,
        minZoom: options.minZoom,
        tileSize: options.tileSize
      });

  ol.source.TileImage.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    logo: options.logo,
    opaque: options.opaque,
    projection: projection,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileGrid: tileGrid,
    tileLoadFunction: options.tileLoadFunction,
    tilePixelRatio: options.tilePixelRatio,
    tileUrlFunction: options.tileUrlFunction,
    url: options.url,
    urls: options.urls,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

};
ol.inherits(ol.source.XYZ, ol.source.TileImage);

goog.provide('ol.source.CartoDB');

goog.require('ol');
goog.require('ol.obj');
goog.require('ol.source.State');
goog.require('ol.source.XYZ');


/**
 * @classdesc
 * Layer source for the CartoDB tiles.
 *
 * @constructor
 * @extends {ol.source.XYZ}
 * @param {olx.source.CartoDBOptions} options CartoDB options.
 * @api
 */
ol.source.CartoDB = function(options) {

  /**
   * @type {string}
   * @private
   */
  this.account_ = options.account;

  /**
   * @type {string}
   * @private
   */
  this.mapId_ = options.map || '';

  /**
   * @type {!Object}
   * @private
   */
  this.config_ = options.config || {};

  /**
   * @type {!Object.<string, CartoDBLayerInfo>}
   * @private
   */
  this.templateCache_ = {};

  ol.source.XYZ.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    logo: options.logo,
    maxZoom: options.maxZoom !== undefined ? options.maxZoom : 18,
    minZoom: options.minZoom,
    projection: options.projection,
    state: ol.source.State.LOADING,
    wrapX: options.wrapX
  });
  this.initializeMap_();
};
ol.inherits(ol.source.CartoDB, ol.source.XYZ);


/**
 * Returns the current config.
 * @return {!Object} The current configuration.
 * @api
 */
ol.source.CartoDB.prototype.getConfig = function() {
  return this.config_;
};


/**
 * Updates the carto db config.
 * @param {Object} config a key-value lookup. Values will replace current values
 *     in the config.
 * @api
 */
ol.source.CartoDB.prototype.updateConfig = function(config) {
  ol.obj.assign(this.config_, config);
  this.initializeMap_();
};


/**
 * Sets the CartoDB config
 * @param {Object} config In the case of anonymous maps, a CartoDB configuration
 *     object.
 * If using named maps, a key-value lookup with the template parameters.
 * @api
 */
ol.source.CartoDB.prototype.setConfig = function(config) {
  this.config_ = config || {};
  this.initializeMap_();
};


/**
 * Issue a request to initialize the CartoDB map.
 * @private
 */
ol.source.CartoDB.prototype.initializeMap_ = function() {
  var paramHash = JSON.stringify(this.config_);
  if (this.templateCache_[paramHash]) {
    this.applyTemplate_(this.templateCache_[paramHash]);
    return;
  }
  var mapUrl = 'https://' + this.account_ + '.cartodb.com/api/v1/map';

  if (this.mapId_) {
    mapUrl += '/named/' + this.mapId_;
  }

  var client = new XMLHttpRequest();
  client.addEventListener('load', this.handleInitResponse_.bind(this, paramHash));
  client.addEventListener('error', this.handleInitError_.bind(this));
  client.open('POST', mapUrl);
  client.setRequestHeader('Content-type', 'application/json');
  client.send(JSON.stringify(this.config_));
};


/**
 * Handle map initialization response.
 * @param {string} paramHash a hash representing the parameter set that was used
 *     for the request
 * @param {Event} event Event.
 * @private
 */
ol.source.CartoDB.prototype.handleInitResponse_ = function(paramHash, event) {
  var client = /** @type {XMLHttpRequest} */ (event.target);
  // status will be 0 for file:// urls
  if (!client.status || client.status >= 200 && client.status < 300) {
    var response;
    try {
      response = /** @type {CartoDBLayerInfo} */(JSON.parse(client.responseText));
    } catch (err) {
      this.setState(ol.source.State.ERROR);
      return;
    }
    this.applyTemplate_(response);
    this.templateCache_[paramHash] = response;
    this.setState(ol.source.State.READY);
  } else {
    this.setState(ol.source.State.ERROR);
  }
};


/**
 * @private
 * @param {Event} event Event.
 */
ol.source.CartoDB.prototype.handleInitError_ = function(event) {
  this.setState(ol.source.State.ERROR);
};


/**
 * Apply the new tile urls returned by carto db
 * @param {CartoDBLayerInfo} data Result of carto db call.
 * @private
 */
ol.source.CartoDB.prototype.applyTemplate_ = function(data) {
  var tilesUrl = 'https://' + data.cdn_url.https + '/' + this.account_ +
      '/api/v1/map/' + data.layergroupid + '/{z}/{x}/{y}.png';
  this.setUrl(tilesUrl);
};

// FIXME keep cluster cache by resolution ?
// FIXME distance not respected because of the centroid

goog.provide('ol.source.Cluster');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.Feature');
goog.require('ol.coordinate');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.geom.Point');
goog.require('ol.source.Vector');


/**
 * @classdesc
 * Layer source to cluster vector data. Works out of the box with point
 * geometries. For other geometry types, or if not all geometries should be
 * considered for clustering, a custom `geometryFunction` can be defined.
 *
 * @constructor
 * @param {olx.source.ClusterOptions} options Constructor options.
 * @extends {ol.source.Vector}
 * @api
 */
ol.source.Cluster = function(options) {
  ol.source.Vector.call(this, {
    attributions: options.attributions,
    extent: options.extent,
    logo: options.logo,
    projection: options.projection,
    wrapX: options.wrapX
  });

  /**
   * @type {number|undefined}
   * @private
   */
  this.resolution_ = undefined;

  /**
   * @type {number}
   * @private
   */
  this.distance_ = options.distance !== undefined ? options.distance : 20;

  /**
   * @type {Array.<ol.Feature>}
   * @private
   */
  this.features_ = [];

  /**
   * @param {ol.Feature} feature Feature.
   * @return {ol.geom.Point} Cluster calculation point.
   */
  this.geometryFunction_ = options.geometryFunction || function(feature) {
    var geometry = /** @type {ol.geom.Point} */ (feature.getGeometry());
    ol.asserts.assert(geometry instanceof ol.geom.Point,
        10); // The default `geometryFunction` can only handle `ol.geom.Point` geometries
    return geometry;
  };

  /**
   * @type {ol.source.Vector}
   * @private
   */
  this.source_ = options.source;

  this.source_.on(ol.events.EventType.CHANGE,
      ol.source.Cluster.prototype.refresh_, this);
};
ol.inherits(ol.source.Cluster, ol.source.Vector);


/**
 * Get a reference to the wrapped source.
 * @return {ol.source.Vector} Source.
 * @api
 */
ol.source.Cluster.prototype.getSource = function() {
  return this.source_;
};


/**
 * @inheritDoc
 */
ol.source.Cluster.prototype.loadFeatures = function(extent, resolution,
    projection) {
  this.source_.loadFeatures(extent, resolution, projection);
  if (resolution !== this.resolution_) {
    this.clear();
    this.resolution_ = resolution;
    this.cluster_();
    this.addFeatures(this.features_);
  }
};


/**
 * Set the distance in pixels between clusters.
 * @param {number} distance The distance in pixels.
 * @api
 */
ol.source.Cluster.prototype.setDistance = function(distance) {
  this.distance_ = distance;
  this.refresh_();
};


/**
 * handle the source changing
 * @private
 */
ol.source.Cluster.prototype.refresh_ = function() {
  this.clear();
  this.cluster_();
  this.addFeatures(this.features_);
  this.changed();
};


/**
 * @private
 */
ol.source.Cluster.prototype.cluster_ = function() {
  if (this.resolution_ === undefined) {
    return;
  }
  this.features_.length = 0;
  var extent = ol.extent.createEmpty();
  var mapDistance = this.distance_ * this.resolution_;
  var features = this.source_.getFeatures();

  /**
   * @type {!Object.<string, boolean>}
   */
  var clustered = {};

  for (var i = 0, ii = features.length; i < ii; i++) {
    var feature = features[i];
    if (!(ol.getUid(feature).toString() in clustered)) {
      var geometry = this.geometryFunction_(feature);
      if (geometry) {
        var coordinates = geometry.getCoordinates();
        ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
        ol.extent.buffer(extent, mapDistance, extent);

        var neighbors = this.source_.getFeaturesInExtent(extent);
        goog.DEBUG && console.assert(neighbors.length >= 1, 'at least one neighbor found');
        neighbors = neighbors.filter(function(neighbor) {
          var uid = ol.getUid(neighbor).toString();
          if (!(uid in clustered)) {
            clustered[uid] = true;
            return true;
          } else {
            return false;
          }
        });
        this.features_.push(this.createCluster_(neighbors));
      }
    }
  }
  goog.DEBUG && console.assert(
      Object.keys(clustered).length == this.source_.getFeatures().length,
      'number of clustered equals number of features in the source');
};


/**
 * @param {Array.<ol.Feature>} features Features
 * @return {ol.Feature} The cluster feature.
 * @private
 */
ol.source.Cluster.prototype.createCluster_ = function(features) {
  var centroid = [0, 0];
  for (var i = features.length - 1; i >= 0; --i) {
    var geometry = this.geometryFunction_(features[i]);
    if (geometry) {
      ol.coordinate.add(centroid, geometry.getCoordinates());
    } else {
      features.splice(i, 1);
    }
  }
  ol.coordinate.scale(centroid, 1 / features.length);

  var cluster = new ol.Feature(new ol.geom.Point(centroid));
  cluster.set('features', features);
  return cluster;
};

goog.provide('ol.uri');


/**
 * Appends query parameters to a URI.
 *
 * @param {string} uri The original URI, which may already have query data.
 * @param {!Object} params An object where keys are URI-encoded parameter keys,
 *     and the values are arbitrary types or arrays.
 * @return {string} The new URI.
 */
ol.uri.appendParams = function(uri, params) {
  var keyParams = [];
  // Skip any null or undefined parameter values
  Object.keys(params).forEach(function(k) {
    if (params[k] !== null && params[k] !== undefined) {
      keyParams.push(k + '=' + encodeURIComponent(params[k]));
    }
  });
  var qs = keyParams.join('&');
  // remove any trailing ? or &
  uri = uri.replace(/[?&]$/, '');
  // append ? or & depending on whether uri has existing parameters
  uri = uri.indexOf('?') === -1 ? uri + '?' : uri + '&';
  return uri + qs;
};

goog.provide('ol.source.ImageArcGISRest');

goog.require('ol');
goog.require('ol.Image');
goog.require('ol.asserts');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.source.Image');
goog.require('ol.uri');


/**
 * @classdesc
 * Source for data from ArcGIS Rest services providing single, untiled images.
 * Useful when underlying map service has labels.
 *
 * If underlying map service is not using labels,
 * take advantage of ol image caching and use
 * {@link ol.source.TileArcGISRest} data source.
 *
 * @constructor
 * @fires ol.source.ImageEvent
 * @extends {ol.source.Image}
 * @param {olx.source.ImageArcGISRestOptions=} opt_options Image ArcGIS Rest Options.
 * @api
 */
ol.source.ImageArcGISRest = function(opt_options) {

  var options = opt_options || {};

  ol.source.Image.call(this, {
    attributions: options.attributions,
    logo: options.logo,
    projection: options.projection,
    resolutions: options.resolutions
  });

  /**
   * @private
   * @type {?string}
   */
  this.crossOrigin_ =
      options.crossOrigin !== undefined ? options.crossOrigin : null;

  /**
   * @private
   * @type {string|undefined}
   */
  this.url_ = options.url;

  /**
   * @private
   * @type {ol.ImageLoadFunctionType}
   */
  this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;


  /**
   * @private
   * @type {!Object}
   */
  this.params_ = options.params || {};

  /**
   * @private
   * @type {ol.Image}
   */
  this.image_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.imageSize_ = [0, 0];


  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;

};
ol.inherits(ol.source.ImageArcGISRest, ol.source.Image);


/**
 * Get the user-provided params, i.e. those passed to the constructor through
 * the "params" option, and possibly updated using the updateParams method.
 * @return {Object} Params.
 * @api stable
 */
ol.source.ImageArcGISRest.prototype.getParams = function() {
  return this.params_;
};


/**
 * @inheritDoc
 */
ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {

  if (this.url_ === undefined) {
    return null;
  }

  resolution = this.findNearestResolution(resolution);

  var image = this.image_;
  if (image &&
      this.renderedRevision_ == this.getRevision() &&
      image.getResolution() == resolution &&
      image.getPixelRatio() == pixelRatio &&
      ol.extent.containsExtent(image.getExtent(), extent)) {
    return image;
  }

  var params = {
    'F': 'image',
    'FORMAT': 'PNG32',
    'TRANSPARENT': true
  };
  ol.obj.assign(params, this.params_);

  extent = extent.slice();
  var centerX = (extent[0] + extent[2]) / 2;
  var centerY = (extent[1] + extent[3]) / 2;
  if (this.ratio_ != 1) {
    var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2;
    var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2;
    extent[0] = centerX - halfWidth;
    extent[1] = centerY - halfHeight;
    extent[2] = centerX + halfWidth;
    extent[3] = centerY + halfHeight;
  }

  var imageResolution = resolution / pixelRatio;

  // Compute an integer width and height.
  var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution);
  var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution);

  // Modify the extent to match the integer width and height.
  extent[0] = centerX - imageResolution * width / 2;
  extent[2] = centerX + imageResolution * width / 2;
  extent[1] = centerY - imageResolution * height / 2;
  extent[3] = centerY + imageResolution * height / 2;

  this.imageSize_[0] = width;
  this.imageSize_[1] = height;

  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
      projection, params);

  this.image_ = new ol.Image(extent, resolution, pixelRatio,
      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);

  this.renderedRevision_ = this.getRevision();

  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
      this.handleImageChange, this);

  return this.image_;

};


/**
 * Return the image load function of the source.
 * @return {ol.ImageLoadFunctionType} The image load function.
 * @api
 */
ol.source.ImageArcGISRest.prototype.getImageLoadFunction = function() {
  return this.imageLoadFunction_;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {Object} params Params.
 * @return {string} Request URL.
 * @private
 */
ol.source.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {

  goog.DEBUG && console.assert(this.url_ !== undefined, 'url is defined');

  // ArcGIS Server only wants the numeric portion of the projection ID.
  var srid = projection.getCode().split(':').pop();

  params['SIZE'] = size[0] + ',' + size[1];
  params['BBOX'] = extent.join(',');
  params['BBOXSR'] = srid;
  params['IMAGESR'] = srid;
  params['DPI'] = 90 * pixelRatio;

  var url = this.url_;

  var modifiedUrl = url
    .replace(/MapServer\/?$/, 'MapServer/export')
    .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
  if (modifiedUrl == url) {
    ol.asserts.assert(false, 50); // `options.featureTypes` should be an Array
  }
  return ol.uri.appendParams(modifiedUrl, params);
};


/**
 * Return the URL used for this ArcGIS source.
 * @return {string|undefined} URL.
 * @api stable
 */
ol.source.ImageArcGISRest.prototype.getUrl = function() {
  return this.url_;
};


/**
 * Set the image load function of the source.
 * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
 * @api
 */
ol.source.ImageArcGISRest.prototype.setImageLoadFunction = function(imageLoadFunction) {
  this.image_ = null;
  this.imageLoadFunction_ = imageLoadFunction;
  this.changed();
};


/**
 * Set the URL to use for requests.
 * @param {string|undefined} url URL.
 * @api stable
 */
ol.source.ImageArcGISRest.prototype.setUrl = function(url) {
  if (url != this.url_) {
    this.url_ = url;
    this.image_ = null;
    this.changed();
  }
};


/**
 * Update the user-provided params.
 * @param {Object} params Params.
 * @api stable
 */
ol.source.ImageArcGISRest.prototype.updateParams = function(params) {
  ol.obj.assign(this.params_, params);
  this.image_ = null;
  this.changed();
};

goog.provide('ol.source.ImageMapGuide');

goog.require('ol');
goog.require('ol.Image');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.source.Image');
goog.require('ol.uri');


/**
 * @classdesc
 * Source for images from Mapguide servers
 *
 * @constructor
 * @fires ol.source.ImageEvent
 * @extends {ol.source.Image}
 * @param {olx.source.ImageMapGuideOptions} options Options.
 * @api stable
 */
ol.source.ImageMapGuide = function(options) {

  ol.source.Image.call(this, {
    projection: options.projection,
    resolutions: options.resolutions
  });

  /**
   * @private
   * @type {?string}
   */
  this.crossOrigin_ =
      options.crossOrigin !== undefined ? options.crossOrigin : null;

  /**
   * @private
   * @type {number}
   */
  this.displayDpi_ = options.displayDpi !== undefined ?
      options.displayDpi : 96;

  /**
   * @private
   * @type {!Object}
   */
  this.params_ = options.params || {};

  /**
   * @private
   * @type {string|undefined}
   */
  this.url_ = options.url;

  /**
   * @private
   * @type {ol.ImageLoadFunctionType}
   */
  this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;

  /**
   * @private
   * @type {boolean}
   */
  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;

  /**
   * @private
   * @type {number}
   */
  this.metersPerUnit_ = options.metersPerUnit !== undefined ?
      options.metersPerUnit : 1;

  /**
   * @private
   * @type {number}
   */
  this.ratio_ = options.ratio !== undefined ? options.ratio : 1;

  /**
   * @private
   * @type {boolean}
   */
  this.useOverlay_ = options.useOverlay !== undefined ?
      options.useOverlay : false;

  /**
   * @private
   * @type {ol.Image}
   */
  this.image_ = null;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = 0;

};
ol.inherits(ol.source.ImageMapGuide, ol.source.Image);


/**
 * Get the user-provided params, i.e. those passed to the constructor through
 * the "params" option, and possibly updated using the updateParams method.
 * @return {Object} Params.
 * @api stable
 */
ol.source.ImageMapGuide.prototype.getParams = function() {
  return this.params_;
};


/**
 * @inheritDoc
 */
ol.source.ImageMapGuide.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
  resolution = this.findNearestResolution(resolution);
  pixelRatio = this.hidpi_ ? pixelRatio : 1;

  var image = this.image_;
  if (image &&
      this.renderedRevision_ == this.getRevision() &&
      image.getResolution() == resolution &&
      image.getPixelRatio() == pixelRatio &&
      ol.extent.containsExtent(image.getExtent(), extent)) {
    return image;
  }

  if (this.ratio_ != 1) {
    extent = extent.slice();
    ol.extent.scaleFromCenter(extent, this.ratio_);
  }
  var width = ol.extent.getWidth(extent) / resolution;
  var height = ol.extent.getHeight(extent) / resolution;
  var size = [width * pixelRatio, height * pixelRatio];

  if (this.url_ !== undefined) {
    var imageUrl = this.getUrl(this.url_, this.params_, extent, size,
        projection);
    image = new ol.Image(extent, resolution, pixelRatio,
        this.getAttributions(), imageUrl, this.crossOrigin_,
        this.imageLoadFunction_);
    ol.events.listen(image, ol.events.EventType.CHANGE,
        this.handleImageChange, this);
  } else {
    image = null;
  }
  this.image_ = image;
  this.renderedRevision_ = this.getRevision();

  return image;
};


/**
 * Return the image load function of the source.
 * @return {ol.ImageLoadFunctionType} The image load function.
 * @api
 */
ol.source.ImageMapGuide.prototype.getImageLoadFunction = function() {
  return this.imageLoadFunction_;
};


/**
 * @param {ol.Extent} extent The map extents.
 * @param {ol.Size} size The viewport size.
 * @param {number} metersPerUnit The meters-per-unit value.
 * @param {number} dpi The display resolution.
 * @return {number} The computed map scale.
 */
ol.source.ImageMapGuide.getScale = function(extent, size, metersPerUnit, dpi) {
  var mcsW = ol.extent.getWidth(extent);
  var mcsH = ol.extent.getHeight(extent);
  var devW = size[0];
  var devH = size[1];
  var mpp = 0.0254 / dpi;
  if (devH * mcsW > devW * mcsH) {
    return mcsW * metersPerUnit / (devW * mpp); // width limited
  } else {
    return mcsH * metersPerUnit / (devH * mpp); // height limited
  }
};


/**
 * Update the user-provided params.
 * @param {Object} params Params.
 * @api stable
 */
ol.source.ImageMapGuide.prototype.updateParams = function(params) {
  ol.obj.assign(this.params_, params);
  this.changed();
};


/**
 * @param {string} baseUrl The mapagent url.
 * @param {Object.<string, string|number>} params Request parameters.
 * @param {ol.Extent} extent Extent.
 * @param {ol.Size} size Size.
 * @param {ol.proj.Projection} projection Projection.
 * @return {string} The mapagent map image request URL.
 */
ol.source.ImageMapGuide.prototype.getUrl = function(baseUrl, params, extent, size, projection) {
  var scale = ol.source.ImageMapGuide.getScale(extent, size,
      this.metersPerUnit_, this.displayDpi_);
  var center = ol.extent.getCenter(extent);
  var baseParams = {
    'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE',
    'VERSION': '2.0.0',
    'LOCALE': 'en',
    'CLIENTAGENT': 'ol.source.ImageMapGuide source',
    'CLIP': '1',
    'SETDISPLAYDPI': this.displayDpi_,
    'SETDISPLAYWIDTH': Math.round(size[0]),
    'SETDISPLAYHEIGHT': Math.round(size[1]),
    'SETVIEWSCALE': scale,
    'SETVIEWCENTERX': center[0],
    'SETVIEWCENTERY': center[1]
  };
  ol.obj.assign(baseParams, params);
  return ol.uri.appendParams(baseUrl, baseParams);
};


/**
 * Set the image load function of the MapGuide source.
 * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
 * @api
 */
ol.source.ImageMapGuide.prototype.setImageLoadFunction = function(
    imageLoadFunction) {
  this.image_ = null;
  this.imageLoadFunction_ = imageLoadFunction;
  this.changed();
};

goog.provide('ol.source.ImageStatic');

goog.require('ol');
goog.require('ol.Image');
goog.require('ol.ImageState');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.source.Image');


/**
 * @classdesc
 * A layer source for displaying a single, static image.
 *
 * @constructor
 * @extends {ol.source.Image}
 * @param {olx.source.ImageStaticOptions} options Options.
 * @api stable
 */
ol.source.ImageStatic = function(options) {
  var imageExtent = options.imageExtent;

  var crossOrigin = options.crossOrigin !== undefined ?
      options.crossOrigin : null;

  var /** @type {ol.ImageLoadFunctionType} */ imageLoadFunction =
      options.imageLoadFunction !== undefined ?
      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;

  ol.source.Image.call(this, {
    attributions: options.attributions,
    logo: options.logo,
    projection: ol.proj.get(options.projection)
  });

  /**
   * @private
   * @type {ol.Image}
   */
  this.image_ = new ol.Image(imageExtent, undefined, 1, this.getAttributions(),
      options.url, crossOrigin, imageLoadFunction);

  /**
   * @private
   * @type {ol.Size}
   */
  this.imageSize_ = options.imageSize ? options.imageSize : null;

  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
      this.handleImageChange, this);

};
ol.inherits(ol.source.ImageStatic, ol.source.Image);


/**
 * @inheritDoc
 */
ol.source.ImageStatic.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
  if (ol.extent.intersects(extent, this.image_.getExtent())) {
    return this.image_;
  }
  return null;
};


/**
 * @inheritDoc
 */
ol.source.ImageStatic.prototype.handleImageChange = function(evt) {
  if (this.image_.getState() == ol.ImageState.LOADED) {
    var imageExtent = this.image_.getExtent();
    var image = this.image_.getImage();
    var imageWidth, imageHeight;
    if (this.imageSize_) {
      imageWidth = this.imageSize_[0];
      imageHeight = this.imageSize_[1];
    } else {
      // TODO: remove the type cast when a closure-compiler > 20160315 is used.
      // see: https://github.com/google/closure-compiler/pull/1664
      imageWidth = /** @type {number} */ (image.width);
      imageHeight = /** @type {number} */ (image.height);
    }
    var resolution = ol.extent.getHeight(imageExtent) / imageHeight;
    var targetWidth = Math.ceil(ol.extent.getWidth(imageExtent) / resolution);
    if (targetWidth != imageWidth) {
      var context = ol.dom.createCanvasContext2D(targetWidth, imageHeight);
      var canvas = context.canvas;
      context.drawImage(image, 0, 0, imageWidth, imageHeight,
          0, 0, canvas.width, canvas.height);
      this.image_.setImage(canvas);
    }
  }
  ol.source.Image.prototype.handleImageChange.call(this, evt);
};

goog.provide('ol.source.wms');
goog.provide('ol.source.wms.ServerType');


/**
 * Available server types: `'carmentaserver'`, `'geoserver'`, `'mapserver'`,
 *     `'qgis'`. These are servers that have vendor parameters beyond the WMS
 *     specification that OpenLayers can make use of.
 * @enum {string}
 */
ol.source.wms.ServerType = {
  CARMENTA_SERVER: 'carmentaserver',
  GEOSERVER: 'geoserver',
  MAPSERVER: 'mapserver',
  QGIS: 'qgis'
};

// FIXME cannot be shared between maps with different projections

goog.provide('ol.source.ImageWMS');

goog.require('ol');
goog.require('ol.Image');
goog.require('ol.asserts');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.source.Image');
goog.require('ol.source.wms.ServerType');
goog.require('ol.string');
goog.require('ol.uri');


/**
 * @classdesc
 * Source for WMS servers providing single, untiled images.
 *
 * @constructor
 * @fires ol.source.ImageEvent
 * @extends {ol.source.Image}
 * @param {olx.source.ImageWMSOptions=} opt_options Options.
 * @api stable
 */
ol.source.ImageWMS = function(opt_options) {

  var options = opt_options || {};

  ol.source.Image.call(this, {
    attributions: options.attributions,
    logo: options.logo,
    projection: options.projection,
    resolutions: options.resolutions
  });

  /**
   * @private
   * @type {?string}
   */
  this.crossOrigin_ =
      options.crossOrigin !== undefined ? options.crossOrigin : null;

  /**
   * @private
   * @type {string|undefined}
   */
  this.url_ = options.url;

  /**
   * @private
   * @type {ol.ImageLoadFunctionType}
   */
  this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
      options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;

  /**
   * @private
   * @type {!Object}
   */
  this.params_ = options.params || {};

  /**
   * @private
   * @type {boolean}
   */
  this.v13_ = true;
  this.updateV13_();

  /**
   * @private
   * @type {ol.source.wms.ServerType|undefined}
   */
  this.serverType_ =
      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);

  /**
   * @private
   * @type {boolean}
   */
  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;

  /**
   * @private
   * @type {ol.Image}
   */
  this.image_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.imageSize_ = [0, 0];

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = 0;

  /**
   * @private
   * @type {number}
   */
  this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;

};
ol.inherits(ol.source.ImageWMS, ol.source.Image);


/**
 * @const
 * @type {ol.Size}
 * @private
 */
ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_ = [101, 101];


/**
 * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
 * projection. Return `undefined` if the GetFeatureInfo URL cannot be
 * constructed.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {ol.ProjectionLike} projection Projection.
 * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
 *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
 *     in the `LAYERS` parameter will be used. `VERSION` should not be
 *     specified here.
 * @return {string|undefined} GetFeatureInfo URL.
 * @api stable
 */
ol.source.ImageWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) {

  goog.DEBUG && console.assert(!('VERSION' in params),
      'key VERSION is not allowed in params');

  if (this.url_ === undefined) {
    return undefined;
  }

  var extent = ol.extent.getForViewAndSize(
      coordinate, resolution, 0,
      ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_);

  var baseParams = {
    'SERVICE': 'WMS',
    'VERSION': ol.DEFAULT_WMS_VERSION,
    'REQUEST': 'GetFeatureInfo',
    'FORMAT': 'image/png',
    'TRANSPARENT': true,
    'QUERY_LAYERS': this.params_['LAYERS']
  };
  ol.obj.assign(baseParams, this.params_, params);

  var x = Math.floor((coordinate[0] - extent[0]) / resolution);
  var y = Math.floor((extent[3] - coordinate[1]) / resolution);
  baseParams[this.v13_ ? 'I' : 'X'] = x;
  baseParams[this.v13_ ? 'J' : 'Y'] = y;

  return this.getRequestUrl_(
      extent, ol.source.ImageWMS.GETFEATUREINFO_IMAGE_SIZE_,
      1, ol.proj.get(projection), baseParams);
};


/**
 * Get the user-provided params, i.e. those passed to the constructor through
 * the "params" option, and possibly updated using the updateParams method.
 * @return {Object} Params.
 * @api stable
 */
ol.source.ImageWMS.prototype.getParams = function() {
  return this.params_;
};


/**
 * @inheritDoc
 */
ol.source.ImageWMS.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {

  if (this.url_ === undefined) {
    return null;
  }

  resolution = this.findNearestResolution(resolution);

  if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) {
    pixelRatio = 1;
  }

  extent = extent.slice();
  var centerX = (extent[0] + extent[2]) / 2;
  var centerY = (extent[1] + extent[3]) / 2;

  var imageResolution = resolution / pixelRatio;
  var imageWidth = ol.extent.getWidth(extent) / imageResolution;
  var imageHeight = ol.extent.getHeight(extent) / imageResolution;

  var image = this.image_;
  if (image &&
      this.renderedRevision_ == this.getRevision() &&
      image.getResolution() == resolution &&
      image.getPixelRatio() == pixelRatio &&
      ol.extent.containsExtent(image.getExtent(), extent)) {
    return image;
  }

  if (this.ratio_ != 1) {
    var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2;
    var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2;
    extent[0] = centerX - halfWidth;
    extent[1] = centerY - halfHeight;
    extent[2] = centerX + halfWidth;
    extent[3] = centerY + halfHeight;
  }

  var params = {
    'SERVICE': 'WMS',
    'VERSION': ol.DEFAULT_WMS_VERSION,
    'REQUEST': 'GetMap',
    'FORMAT': 'image/png',
    'TRANSPARENT': true
  };
  ol.obj.assign(params, this.params_);

  this.imageSize_[0] = Math.ceil(imageWidth * this.ratio_);
  this.imageSize_[1] = Math.ceil(imageHeight * this.ratio_);

  var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
      projection, params);

  this.image_ = new ol.Image(extent, resolution, pixelRatio,
      this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);

  this.renderedRevision_ = this.getRevision();

  ol.events.listen(this.image_, ol.events.EventType.CHANGE,
      this.handleImageChange, this);

  return this.image_;

};


/**
 * Return the image load function of the source.
 * @return {ol.ImageLoadFunctionType} The image load function.
 * @api
 */
ol.source.ImageWMS.prototype.getImageLoadFunction = function() {
  return this.imageLoadFunction_;
};


/**
 * @param {ol.Extent} extent Extent.
 * @param {ol.Size} size Size.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {Object} params Params.
 * @return {string} Request URL.
 * @private
 */
ol.source.ImageWMS.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {

  ol.asserts.assert(this.url_ !== undefined, 9); // `url` must be configured or set using `#setUrl()`

  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();

  if (!('STYLES' in this.params_)) {
    params['STYLES'] = '';
  }

  if (pixelRatio != 1) {
    switch (this.serverType_) {
      case ol.source.wms.ServerType.GEOSERVER:
        var dpi = (90 * pixelRatio + 0.5) | 0;
        if ('FORMAT_OPTIONS' in params) {
          params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
        } else {
          params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
        }
        break;
      case ol.source.wms.ServerType.MAPSERVER:
        params['MAP_RESOLUTION'] = 90 * pixelRatio;
        break;
      case ol.source.wms.ServerType.CARMENTA_SERVER:
      case ol.source.wms.ServerType.QGIS:
        params['DPI'] = 90 * pixelRatio;
        break;
      default:
        ol.asserts.assert(false, 8); // Unknown `serverType` configured
        break;
    }
  }

  params['WIDTH'] = size[0];
  params['HEIGHT'] = size[1];

  var axisOrientation = projection.getAxisOrientation();
  var bbox;
  if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') {
    bbox = [extent[1], extent[0], extent[3], extent[2]];
  } else {
    bbox = extent;
  }
  params['BBOX'] = bbox.join(',');

  return ol.uri.appendParams(/** @type {string} */ (this.url_), params);
};


/**
 * Return the URL used for this WMS source.
 * @return {string|undefined} URL.
 * @api stable
 */
ol.source.ImageWMS.prototype.getUrl = function() {
  return this.url_;
};


/**
 * Set the image load function of the source.
 * @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
 * @api
 */
ol.source.ImageWMS.prototype.setImageLoadFunction = function(
    imageLoadFunction) {
  this.image_ = null;
  this.imageLoadFunction_ = imageLoadFunction;
  this.changed();
};


/**
 * Set the URL to use for requests.
 * @param {string|undefined} url URL.
 * @api stable
 */
ol.source.ImageWMS.prototype.setUrl = function(url) {
  if (url != this.url_) {
    this.url_ = url;
    this.image_ = null;
    this.changed();
  }
};


/**
 * Update the user-provided params.
 * @param {Object} params Params.
 * @api stable
 */
ol.source.ImageWMS.prototype.updateParams = function(params) {
  ol.obj.assign(this.params_, params);
  this.updateV13_();
  this.image_ = null;
  this.changed();
};


/**
 * @private
 */
ol.source.ImageWMS.prototype.updateV13_ = function() {
  var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION;
  this.v13_ = ol.string.compareVersions(version, '1.3') >= 0;
};

goog.provide('ol.source.OSM');

goog.require('ol');
goog.require('ol.Attribution');
goog.require('ol.source.XYZ');


/**
 * @classdesc
 * Layer source for the OpenStreetMap tile server.
 *
 * @constructor
 * @extends {ol.source.XYZ}
 * @param {olx.source.OSMOptions=} opt_options Open Street Map options.
 * @api stable
 */
ol.source.OSM = function(opt_options) {

  var options = opt_options || {};

  var attributions;
  if (options.attributions !== undefined) {
    attributions = options.attributions;
  } else {
    attributions = [ol.source.OSM.ATTRIBUTION];
  }

  var crossOrigin = options.crossOrigin !== undefined ?
      options.crossOrigin : 'anonymous';

  var url = options.url !== undefined ?
      options.url : 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png';

  ol.source.XYZ.call(this, {
    attributions: attributions,
    cacheSize: options.cacheSize,
    crossOrigin: crossOrigin,
    opaque: options.opaque !== undefined ? options.opaque : true,
    maxZoom: options.maxZoom !== undefined ? options.maxZoom : 19,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileLoadFunction: options.tileLoadFunction,
    url: url,
    wrapX: options.wrapX
  });

};
ol.inherits(ol.source.OSM, ol.source.XYZ);


/**
 * The attribution containing a link to the OpenStreetMap Copyright and License
 * page.
 * @const
 * @type {ol.Attribution}
 * @api
 */
ol.source.OSM.ATTRIBUTION = new ol.Attribution({
  html: '&copy; ' +
      '<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> ' +
      'contributors.'
});

goog.provide('ol.ext.pixelworks');
/** @typedef {function(*)} */
ol.ext.pixelworks;
(function() {
var exports = {};
var module = {exports: exports};
var define;
/**
 * @fileoverview
 * @suppress {accessControls, ambiguousFunctionDecl, checkDebuggerStatement, checkRegExp, checkTypes, checkVars, const, constantProperty, deprecated, duplicate, es5Strict, fileoverviewTags, missingProperties, nonStandardJsDocs, strictModuleDepCheck, suspiciousCode, undefinedNames, undefinedVars, unknownDefines, uselessCode, visibility}
 */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pixelworks = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
var Processor = _dereq_('./processor');

exports.Processor = Processor;

},{"./processor":2}],2:[function(_dereq_,module,exports){
var newImageData = _dereq_('./util').newImageData;

/**
 * Create a function for running operations.  This function is serialized for
 * use in a worker.
 * @param {function(Array, Object):*} operation The operation.
 * @return {function(Object):ArrayBuffer} A function that takes an object with
 * buffers, meta, imageOps, width, and height properties and returns an array
 * buffer.
 */
function createMinion(operation) {
  var workerHasImageData = true;
  try {
    new ImageData(10, 10);
  } catch (_) {
    workerHasImageData = false;
  }

  function newWorkerImageData(data, width, height) {
    if (workerHasImageData) {
      return new ImageData(data, width, height);
    } else {
      return {data: data, width: width, height: height};
    }
  }

  return function(data) {
    // bracket notation for minification support
    var buffers = data['buffers'];
    var meta = data['meta'];
    var imageOps = data['imageOps'];
    var width = data['width'];
    var height = data['height'];

    var numBuffers = buffers.length;
    var numBytes = buffers[0].byteLength;
    var output, b;

    if (imageOps) {
      var images = new Array(numBuffers);
      for (b = 0; b < numBuffers; ++b) {
        images[b] = newWorkerImageData(
            new Uint8ClampedArray(buffers[b]), width, height);
      }
      output = operation(images, meta).data;
    } else {
      output = new Uint8ClampedArray(numBytes);
      var arrays = new Array(numBuffers);
      var pixels = new Array(numBuffers);
      for (b = 0; b < numBuffers; ++b) {
        arrays[b] = new Uint8ClampedArray(buffers[b]);
        pixels[b] = [0, 0, 0, 0];
      }
      for (var i = 0; i < numBytes; i += 4) {
        for (var j = 0; j < numBuffers; ++j) {
          var array = arrays[j];
          pixels[j][0] = array[i];
          pixels[j][1] = array[i + 1];
          pixels[j][2] = array[i + 2];
          pixels[j][3] = array[i + 3];
        }
        var pixel = operation(pixels, meta);
        output[i] = pixel[0];
        output[i + 1] = pixel[1];
        output[i + 2] = pixel[2];
        output[i + 3] = pixel[3];
      }
    }
    return output.buffer;
  };
}

/**
 * Create a worker for running operations.
 * @param {Object} config Configuration.
 * @param {function(MessageEvent)} onMessage Called with a message event.
 * @return {Worker} The worker.
 */
function createWorker(config, onMessage) {
  var lib = Object.keys(config.lib || {}).map(function(name) {
    return 'var ' + name + ' = ' + config.lib[name].toString() + ';';
  });

  var lines = lib.concat([
    'var __minion__ = (' + createMinion.toString() + ')(', config.operation.toString(), ');',
    'self.addEventListener("message", function(event) {',
    '  var buffer = __minion__(event.data);',
    '  self.postMessage({buffer: buffer, meta: event.data.meta}, [buffer]);',
    '});'
  ]);

  var blob = new Blob(lines, {type: 'text/javascript'});
  var source = URL.createObjectURL(blob);
  var worker = new Worker(source);
  worker.addEventListener('message', onMessage);
  return worker;
}

/**
 * Create a faux worker for running operations.
 * @param {Object} config Configuration.
 * @param {function(MessageEvent)} onMessage Called with a message event.
 * @return {Object} The faux worker.
 */
function createFauxWorker(config, onMessage) {
  var minion = createMinion(config.operation);
  return {
    postMessage: function(data) {
      setTimeout(function() {
        onMessage({'data': {'buffer': minion(data), 'meta': data['meta']}});
      }, 0);
    }
  };
}

/**
 * A processor runs pixel or image operations in workers.
 * @param {Object} config Configuration.
 */
function Processor(config) {
  this._imageOps = !!config.imageOps;
  var threads;
  if (config.threads === 0) {
    threads = 0;
  } else if (this._imageOps) {
    threads = 1;
  } else {
    threads = config.threads || 1;
  }
  var workers = [];
  if (threads) {
    for (var i = 0; i < threads; ++i) {
      workers[i] = createWorker(config, this._onWorkerMessage.bind(this, i));
    }
  } else {
    workers[0] = createFauxWorker(config, this._onWorkerMessage.bind(this, 0));
  }
  this._workers = workers;
  this._queue = [];
  this._maxQueueLength = config.queue || Infinity;
  this._running = 0;
  this._dataLookup = {};
  this._job = null;
}

/**
 * Run operation on input data.
 * @param {Array.<Array|ImageData>} inputs Array of pixels or image data
 *     (depending on the operation type).
 * @param {Object} meta A user data object.  This is passed to all operations
 *     and must be serializable.
 * @param {function(Error, ImageData, Object)} callback Called when work
 *     completes.  The first argument is any error.  The second is the ImageData
 *     generated by operations.  The third is the user data object.
 */
Processor.prototype.process = function(inputs, meta, callback) {
  this._enqueue({
    inputs: inputs,
    meta: meta,
    callback: callback
  });
  this._dispatch();
};

/**
 * Stop responding to any completed work and destroy the processor.
 */
Processor.prototype.destroy = function() {
  for (var key in this) {
    this[key] = null;
  }
  this._destroyed = true;
};

/**
 * Add a job to the queue.
 * @param {Object} job The job.
 */
Processor.prototype._enqueue = function(job) {
  this._queue.push(job);
  while (this._queue.length > this._maxQueueLength) {
    this._queue.shift().callback(null, null);
  }
};

/**
 * Dispatch a job.
 */
Processor.prototype._dispatch = function() {
  if (this._running === 0 && this._queue.length > 0) {
    var job = this._job = this._queue.shift();
    var width = job.inputs[0].width;
    var height = job.inputs[0].height;
    var buffers = job.inputs.map(function(input) {
      return input.data.buffer;
    });
    var threads = this._workers.length;
    this._running = threads;
    if (threads === 1) {
      this._workers[0].postMessage({
        'buffers': buffers,
        'meta': job.meta,
        'imageOps': this._imageOps,
        'width': width,
        'height': height
      }, buffers);
    } else {
      var length = job.inputs[0].data.length;
      var segmentLength = 4 * Math.ceil(length / 4 / threads);
      for (var i = 0; i < threads; ++i) {
        var offset = i * segmentLength;
        var slices = [];
        for (var j = 0, jj = buffers.length; j < jj; ++j) {
          slices.push(buffers[i].slice(offset, offset + segmentLength));
        }
        this._workers[i].postMessage({
          'buffers': slices,
          'meta': job.meta,
          'imageOps': this._imageOps,
          'width': width,
          'height': height
        }, slices);
      }
    }
  }
};

/**
 * Handle messages from the worker.
 * @param {number} index The worker index.
 * @param {MessageEvent} event The message event.
 */
Processor.prototype._onWorkerMessage = function(index, event) {
  if (this._destroyed) {
    return;
  }
  this._dataLookup[index] = event.data;
  --this._running;
  if (this._running === 0) {
    this._resolveJob();
  }
};

/**
 * Resolve a job.  If there are no more worker threads, the processor callback
 * will be called.
 */
Processor.prototype._resolveJob = function() {
  var job = this._job;
  var threads = this._workers.length;
  var data, meta;
  if (threads === 1) {
    data = new Uint8ClampedArray(this._dataLookup[0]['buffer']);
    meta = this._dataLookup[0]['meta'];
  } else {
    var length = job.inputs[0].data.length;
    data = new Uint8ClampedArray(length);
    meta = new Array(length);
    var segmentLength = 4 * Math.ceil(length / 4 / threads);
    for (var i = 0; i < threads; ++i) {
      var buffer = this._dataLookup[i]['buffer'];
      var offset = i * segmentLength;
      data.set(new Uint8ClampedArray(buffer), offset);
      meta[i] = this._dataLookup[i]['meta'];
    }
  }
  this._job = null;
  this._dataLookup = {};
  job.callback(null,
      newImageData(data, job.inputs[0].width, job.inputs[0].height), meta);
  this._dispatch();
};

module.exports = Processor;

},{"./util":3}],3:[function(_dereq_,module,exports){
var hasImageData = true;
try {
  new ImageData(10, 10);
} catch (_) {
  hasImageData = false;
}

var context = document.createElement('canvas').getContext('2d');

function newImageData(data, width, height) {
  if (hasImageData) {
    return new ImageData(data, width, height);
  } else {
    var imageData = context.createImageData(width, height);
    imageData.data.set(data);
    return imageData;
  }
}

exports.newImageData = newImageData;

},{}]},{},[1])(1)
});
ol.ext.pixelworks = module.exports;
})();

goog.provide('ol.source.Raster');
goog.provide('ol.RasterOperationType');
goog.provide('ol.source.RasterEvent');
goog.provide('ol.source.RasterEventType');

goog.require('ol');
goog.require('ol.transform');
goog.require('ol.ImageCanvas');
goog.require('ol.TileQueue');
goog.require('ol.dom');
goog.require('ol.events');
goog.require('ol.events.Event');
goog.require('ol.events.EventType');
goog.require('ol.ext.pixelworks');
goog.require('ol.extent');
goog.require('ol.layer.Image');
goog.require('ol.layer.Tile');
goog.require('ol.obj');
goog.require('ol.renderer.canvas.ImageLayer');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.source.Image');
goog.require('ol.source.State');
goog.require('ol.source.Tile');


/**
 * Raster operation type. Supported values are `'pixel'` and `'image'`.
 * @enum {string}
 */
ol.RasterOperationType = {
  PIXEL: 'pixel',
  IMAGE: 'image'
};


/**
 * @classdesc
 * A source that transforms data from any number of input sources using an array
 * of {@link ol.RasterOperation} functions to transform input pixel values into
 * output pixel values.
 *
 * @constructor
 * @extends {ol.source.Image}
 * @fires ol.source.RasterEvent
 * @param {olx.source.RasterOptions} options Options.
 * @api
 */
ol.source.Raster = function(options) {

  /**
   * @private
   * @type {*}
   */
  this.worker_ = null;

  /**
   * @private
   * @type {ol.RasterOperationType}
   */
  this.operationType_ = options.operationType !== undefined ?
      options.operationType : ol.RasterOperationType.PIXEL;

  /**
   * @private
   * @type {number}
   */
  this.threads_ = options.threads !== undefined ? options.threads : 1;

  /**
   * @private
   * @type {Array.<ol.renderer.canvas.Layer>}
   */
  this.renderers_ = ol.source.Raster.createRenderers_(options.sources);

  for (var r = 0, rr = this.renderers_.length; r < rr; ++r) {
    ol.events.listen(this.renderers_[r], ol.events.EventType.CHANGE,
        this.changed, this);
  }

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.canvasContext_ = ol.dom.createCanvasContext2D();

  /**
   * @private
   * @type {ol.TileQueue}
   */
  this.tileQueue_ = new ol.TileQueue(
      function() {
        return 1;
      },
      this.changed.bind(this));

  var layerStatesArray = ol.source.Raster.getLayerStatesArray_(this.renderers_);
  var layerStates = {};
  for (var i = 0, ii = layerStatesArray.length; i < ii; ++i) {
    layerStates[ol.getUid(layerStatesArray[i].layer)] = layerStatesArray[i];
  }

  /**
   * The most recently rendered state.
   * @type {?ol.SourceRasterRenderedState}
   * @private
   */
  this.renderedState_ = null;

  /**
   * The most recently rendered image canvas.
   * @type {ol.ImageCanvas}
   * @private
   */
  this.renderedImageCanvas_ = null;

  /**
   * @private
   * @type {olx.FrameState}
   */
  this.frameState_ = {
    animate: false,
    attributions: {},
    coordinateToPixelTransform: ol.transform.create(),
    extent: null,
    focus: null,
    index: 0,
    layerStates: layerStates,
    layerStatesArray: layerStatesArray,
    logos: {},
    pixelRatio: 1,
    pixelToCoordinateTransform: ol.transform.create(),
    postRenderFunctions: [],
    size: [0, 0],
    skippedFeatureUids: {},
    tileQueue: this.tileQueue_,
    time: Date.now(),
    usedTiles: {},
    viewState: /** @type {olx.ViewState} */ ({
      rotation: 0
    }),
    viewHints: [],
    wantedTiles: {}
  };

  ol.source.Image.call(this, {});

  if (options.operation !== undefined) {
    this.setOperation(options.operation, options.lib);
  }

};
ol.inherits(ol.source.Raster, ol.source.Image);


/**
 * Set the operation.
 * @param {ol.RasterOperation} operation New operation.
 * @param {Object=} opt_lib Functions that will be available to operations run
 *     in a worker.
 * @api
 */
ol.source.Raster.prototype.setOperation = function(operation, opt_lib) {
  this.worker_ = new ol.ext.pixelworks.Processor({
    operation: operation,
    imageOps: this.operationType_ === ol.RasterOperationType.IMAGE,
    queue: 1,
    lib: opt_lib,
    threads: this.threads_
  });
  this.changed();
};


/**
 * Update the stored frame state.
 * @param {ol.Extent} extent The view extent (in map units).
 * @param {number} resolution The view resolution.
 * @param {ol.proj.Projection} projection The view projection.
 * @return {olx.FrameState} The updated frame state.
 * @private
 */
ol.source.Raster.prototype.updateFrameState_ = function(extent, resolution, projection) {

  var frameState = /** @type {olx.FrameState} */ (
      ol.obj.assign({}, this.frameState_));

  frameState.viewState = /** @type {olx.ViewState} */ (
      ol.obj.assign({}, frameState.viewState));

  var center = ol.extent.getCenter(extent);
  var width = Math.round(ol.extent.getWidth(extent) / resolution);
  var height = Math.round(ol.extent.getHeight(extent) / resolution);

  frameState.extent = extent;
  frameState.focus = ol.extent.getCenter(extent);
  frameState.size[0] = width;
  frameState.size[1] = height;

  var viewState = frameState.viewState;
  viewState.center = center;
  viewState.projection = projection;
  viewState.resolution = resolution;
  return frameState;
};


/**
 * Determine if the most recently rendered image canvas is dirty.
 * @param {ol.Extent} extent The requested extent.
 * @param {number} resolution The requested resolution.
 * @return {boolean} The image is dirty.
 * @private
 */
ol.source.Raster.prototype.isDirty_ = function(extent, resolution) {
  var state = this.renderedState_;
  return !state ||
      this.getRevision() !== state.revision ||
      resolution !== state.resolution ||
      !ol.extent.equals(extent, state.extent);
};


/**
 * @inheritDoc
 */
ol.source.Raster.prototype.getImage = function(extent, resolution, pixelRatio, projection) {

  if (!this.allSourcesReady_()) {
    return null;
  }

  var currentExtent = extent.slice();
  if (!this.isDirty_(currentExtent, resolution)) {
    return this.renderedImageCanvas_;
  }

  var context = this.canvasContext_;
  var canvas = context.canvas;

  var width = Math.round(ol.extent.getWidth(currentExtent) / resolution);
  var height = Math.round(ol.extent.getHeight(currentExtent) / resolution);

  if (width !== canvas.width ||
      height !== canvas.height) {
    canvas.width = width;
    canvas.height = height;
  }

  var frameState = this.updateFrameState_(currentExtent, resolution, projection);

  var imageCanvas = new ol.ImageCanvas(
      currentExtent, resolution, 1, this.getAttributions(), canvas,
      this.composeFrame_.bind(this, frameState));

  this.renderedImageCanvas_ = imageCanvas;

  this.renderedState_ = {
    extent: currentExtent,
    resolution: resolution,
    revision: this.getRevision()
  };

  return imageCanvas;
};


/**
 * Determine if all sources are ready.
 * @return {boolean} All sources are ready.
 * @private
 */
ol.source.Raster.prototype.allSourcesReady_ = function() {
  var ready = true;
  var source;
  for (var i = 0, ii = this.renderers_.length; i < ii; ++i) {
    source = this.renderers_[i].getLayer().getSource();
    if (source.getState() !== ol.source.State.READY) {
      ready = false;
      break;
    }
  }
  return ready;
};


/**
 * Compose the frame.  This renders data from all sources, runs pixel-wise
 * operations, and renders the result to the stored canvas context.
 * @param {olx.FrameState} frameState The frame state.
 * @param {function(Error)} callback Called when composition is complete.
 * @private
 */
ol.source.Raster.prototype.composeFrame_ = function(frameState, callback) {
  var len = this.renderers_.length;
  var imageDatas = new Array(len);
  for (var i = 0; i < len; ++i) {
    var imageData = ol.source.Raster.getImageData_(
        this.renderers_[i], frameState, frameState.layerStatesArray[i]);
    if (imageData) {
      imageDatas[i] = imageData;
    } else {
      // image not yet ready
      return;
    }
  }

  var data = {};
  this.dispatchEvent(new ol.source.RasterEvent(
      ol.source.RasterEventType.BEFOREOPERATIONS, frameState, data));

  this.worker_.process(imageDatas, data,
      this.onWorkerComplete_.bind(this, frameState, callback));

  frameState.tileQueue.loadMoreTiles(16, 16);
};


/**
 * Called when pixel processing is complete.
 * @param {olx.FrameState} frameState The frame state.
 * @param {function(Error)} callback Called when rendering is complete.
 * @param {Error} err Any error during processing.
 * @param {ImageData} output The output image data.
 * @param {Object} data The user data.
 * @private
 */
ol.source.Raster.prototype.onWorkerComplete_ = function(frameState, callback, err, output, data) {
  if (err) {
    callback(err);
    return;
  }
  if (!output) {
    // job aborted
    return;
  }

  this.dispatchEvent(new ol.source.RasterEvent(
      ol.source.RasterEventType.AFTEROPERATIONS, frameState, data));

  var resolution = frameState.viewState.resolution / frameState.pixelRatio;
  if (!this.isDirty_(frameState.extent, resolution)) {
    this.canvasContext_.putImageData(output, 0, 0);
  }

  callback(null);
};


/**
 * Get image data from a renderer.
 * @param {ol.renderer.canvas.Layer} renderer Layer renderer.
 * @param {olx.FrameState} frameState The frame state.
 * @param {ol.LayerState} layerState The layer state.
 * @return {ImageData} The image data.
 * @private
 */
ol.source.Raster.getImageData_ = function(renderer, frameState, layerState) {
  if (!renderer.prepareFrame(frameState, layerState)) {
    return null;
  }
  var width = frameState.size[0];
  var height = frameState.size[1];
  if (!ol.source.Raster.context_) {
    ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height);
  } else {
    var canvas = ol.source.Raster.context_.canvas;
    if (canvas.width !== width || canvas.height !== height) {
      ol.source.Raster.context_ = ol.dom.createCanvasContext2D(width, height);
    } else {
      ol.source.Raster.context_.clearRect(0, 0, width, height);
    }
  }
  renderer.composeFrame(frameState, layerState, ol.source.Raster.context_);
  return ol.source.Raster.context_.getImageData(0, 0, width, height);
};


/**
 * A reusable canvas context.
 * @type {CanvasRenderingContext2D}
 * @private
 */
ol.source.Raster.context_ = null;


/**
 * Get a list of layer states from a list of renderers.
 * @param {Array.<ol.renderer.canvas.Layer>} renderers Layer renderers.
 * @return {Array.<ol.LayerState>} The layer states.
 * @private
 */
ol.source.Raster.getLayerStatesArray_ = function(renderers) {
  return renderers.map(function(renderer) {
    return renderer.getLayer().getLayerState();
  });
};


/**
 * Create renderers for all sources.
 * @param {Array.<ol.source.Source>} sources The sources.
 * @return {Array.<ol.renderer.canvas.Layer>} Array of layer renderers.
 * @private
 */
ol.source.Raster.createRenderers_ = function(sources) {
  var len = sources.length;
  var renderers = new Array(len);
  for (var i = 0; i < len; ++i) {
    renderers[i] = ol.source.Raster.createRenderer_(sources[i]);
  }
  return renderers;
};


/**
 * Create a renderer for the provided source.
 * @param {ol.source.Source} source The source.
 * @return {ol.renderer.canvas.Layer} The renderer.
 * @private
 */
ol.source.Raster.createRenderer_ = function(source) {
  var renderer = null;
  if (source instanceof ol.source.Tile) {
    renderer = ol.source.Raster.createTileRenderer_(source);
  } else if (source instanceof ol.source.Image) {
    renderer = ol.source.Raster.createImageRenderer_(source);
  } else {
    goog.DEBUG && console.assert(false, 'Unsupported source type: ' + source);
  }
  return renderer;
};


/**
 * Create an image renderer for the provided source.
 * @param {ol.source.Image} source The source.
 * @return {ol.renderer.canvas.Layer} The renderer.
 * @private
 */
ol.source.Raster.createImageRenderer_ = function(source) {
  var layer = new ol.layer.Image({source: source});
  return new ol.renderer.canvas.ImageLayer(layer);
};


/**
 * Create a tile renderer for the provided source.
 * @param {ol.source.Tile} source The source.
 * @return {ol.renderer.canvas.Layer} The renderer.
 * @private
 */
ol.source.Raster.createTileRenderer_ = function(source) {
  var layer = new ol.layer.Tile({source: source});
  return new ol.renderer.canvas.TileLayer(layer);
};


/**
 * @classdesc
 * Events emitted by {@link ol.source.Raster} instances are instances of this
 * type.
 *
 * @constructor
 * @extends {ol.events.Event}
 * @implements {oli.source.RasterEvent}
 * @param {string} type Type.
 * @param {olx.FrameState} frameState The frame state.
 * @param {Object} data An object made available to operations.
 */
ol.source.RasterEvent = function(type, frameState, data) {
  ol.events.Event.call(this, type);

  /**
   * The raster extent.
   * @type {ol.Extent}
   * @api
   */
  this.extent = frameState.extent;

  /**
   * The pixel resolution (map units per pixel).
   * @type {number}
   * @api
   */
  this.resolution = frameState.viewState.resolution / frameState.pixelRatio;

  /**
   * An object made available to all operations.  This can be used by operations
   * as a storage object (e.g. for calculating statistics).
   * @type {Object}
   * @api
   */
  this.data = data;

};
ol.inherits(ol.source.RasterEvent, ol.events.Event);


/**
 * @enum {string}
 */
ol.source.RasterEventType = {
  /**
   * Triggered before operations are run.
   * @event ol.source.RasterEvent#beforeoperations
   * @api
   */
  BEFOREOPERATIONS: 'beforeoperations',

  /**
   * Triggered after operations are run.
   * @event ol.source.RasterEvent#afteroperations
   * @api
   */
  AFTEROPERATIONS: 'afteroperations'
};

goog.provide('ol.source.Stamen');

goog.require('ol');
goog.require('ol.Attribution');
goog.require('ol.source.OSM');
goog.require('ol.source.XYZ');


/**
 * @type {Object.<string, {extension: string, opaque: boolean}>}
 */
ol.source.StamenLayerConfig = {
  'terrain': {
    extension: 'jpg',
    opaque: true
  },
  'terrain-background': {
    extension: 'jpg',
    opaque: true
  },
  'terrain-labels': {
    extension: 'png',
    opaque: false
  },
  'terrain-lines': {
    extension: 'png',
    opaque: false
  },
  'toner-background': {
    extension: 'png',
    opaque: true
  },
  'toner': {
    extension: 'png',
    opaque: true
  },
  'toner-hybrid': {
    extension: 'png',
    opaque: false
  },
  'toner-labels': {
    extension: 'png',
    opaque: false
  },
  'toner-lines': {
    extension: 'png',
    opaque: false
  },
  'toner-lite': {
    extension: 'png',
    opaque: true
  },
  'watercolor': {
    extension: 'jpg',
    opaque: true
  }
};


/**
 * @type {Object.<string, {minZoom: number, maxZoom: number}>}
 */
ol.source.StamenProviderConfig = {
  'terrain': {
    minZoom: 4,
    maxZoom: 18
  },
  'toner': {
    minZoom: 0,
    maxZoom: 20
  },
  'watercolor': {
    minZoom: 1,
    maxZoom: 16
  }
};


/**
 * @classdesc
 * Layer source for the Stamen tile server.
 *
 * @constructor
 * @extends {ol.source.XYZ}
 * @param {olx.source.StamenOptions} options Stamen options.
 * @api stable
 */
ol.source.Stamen = function(options) {

  var i = options.layer.indexOf('-');
  var provider = i == -1 ? options.layer : options.layer.slice(0, i);
  goog.DEBUG && console.assert(provider in ol.source.StamenProviderConfig,
      'known provider configured');
  var providerConfig = ol.source.StamenProviderConfig[provider];

  goog.DEBUG && console.assert(options.layer in ol.source.StamenLayerConfig,
      'known layer configured');
  var layerConfig = ol.source.StamenLayerConfig[options.layer];

  var url = options.url !== undefined ? options.url :
      'https://stamen-tiles-{a-d}.a.ssl.fastly.net/' + options.layer +
      '/{z}/{x}/{y}.' + layerConfig.extension;

  ol.source.XYZ.call(this, {
    attributions: ol.source.Stamen.ATTRIBUTIONS,
    cacheSize: options.cacheSize,
    crossOrigin: 'anonymous',
    maxZoom: options.maxZoom != undefined ? options.maxZoom : providerConfig.maxZoom,
    minZoom: options.minZoom != undefined ? options.minZoom : providerConfig.minZoom,
    opaque: layerConfig.opaque,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileLoadFunction: options.tileLoadFunction,
    url: url
  });

};
ol.inherits(ol.source.Stamen, ol.source.XYZ);


/**
 * @const
 * @type {Array.<ol.Attribution>}
 */
ol.source.Stamen.ATTRIBUTIONS = [
  new ol.Attribution({
    html: 'Map tiles by <a href="http://stamen.com/">Stamen Design</a>, ' +
        'under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY' +
        ' 3.0</a>.'
  }),
  ol.source.OSM.ATTRIBUTION
];

goog.provide('ol.source.TileArcGISRest');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.obj');
goog.require('ol.size');
goog.require('ol.source.TileImage');
goog.require('ol.tilecoord');
goog.require('ol.uri');


/**
 * @classdesc
 * Layer source for tile data from ArcGIS Rest services. Map and Image
 * Services are supported.
 *
 * For cached ArcGIS services, better performance is available using the
 * {@link ol.source.XYZ} data source.
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.TileArcGISRestOptions=} opt_options Tile ArcGIS Rest
 *     options.
 * @api
 */
ol.source.TileArcGISRest = function(opt_options) {

  var options = opt_options || {};

  ol.source.TileImage.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    logo: options.logo,
    projection: options.projection,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileGrid: options.tileGrid,
    tileLoadFunction: options.tileLoadFunction,
    url: options.url,
    urls: options.urls,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

  /**
   * @private
   * @type {!Object}
   */
  this.params_ = options.params || {};

  /**
   * @private
   * @type {ol.Extent}
   */
  this.tmpExtent_ = ol.extent.createEmpty();

};
ol.inherits(ol.source.TileArcGISRest, ol.source.TileImage);


/**
 * Get the user-provided params, i.e. those passed to the constructor through
 * the "params" option, and possibly updated using the updateParams method.
 * @return {Object} Params.
 * @api
 */
ol.source.TileArcGISRest.prototype.getParams = function() {
  return this.params_;
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Size} tileSize Tile size.
 * @param {ol.Extent} tileExtent Tile extent.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {Object} params Params.
 * @return {string|undefined} Request URL.
 * @private
 */
ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
        pixelRatio, projection, params) {

  var urls = this.urls;
  if (!urls) {
    return undefined;
  }

  // ArcGIS Server only wants the numeric portion of the projection ID.
  var srid = projection.getCode().split(':').pop();

  params['SIZE'] = tileSize[0] + ',' + tileSize[1];
  params['BBOX'] = tileExtent.join(',');
  params['BBOXSR'] = srid;
  params['IMAGESR'] = srid;
  params['DPI'] = Math.round(
      params['DPI'] ? params['DPI'] * pixelRatio : 90 * pixelRatio
      );

  var url;
  if (urls.length == 1) {
    url = urls[0];
  } else {
    var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
    url = urls[index];
  }

  var modifiedUrl = url
      .replace(/MapServer\/?$/, 'MapServer/export')
      .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
  if (modifiedUrl == url) {
    ol.asserts.assert(false, 50); // Cannot determine Rest Service from url
  }
  return ol.uri.appendParams(modifiedUrl, params);
};


/**
 * @inheritDoc
 */
ol.source.TileArcGISRest.prototype.getTilePixelRatio = function(pixelRatio) {
  return /** @type {number} */ (pixelRatio);
};


/**
 * @inheritDoc
 */
ol.source.TileArcGISRest.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) {

  var tileGrid = this.getTileGrid();
  if (!tileGrid) {
    tileGrid = this.getTileGridForProjection(projection);
  }

  if (tileGrid.getResolutions().length <= tileCoord[0]) {
    return undefined;
  }

  var tileExtent = tileGrid.getTileCoordExtent(
      tileCoord, this.tmpExtent_);
  var tileSize = ol.size.toSize(
      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);

  if (pixelRatio != 1) {
    tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize);
  }

  // Apply default params and override with user specified values.
  var baseParams = {
    'F': 'image',
    'FORMAT': 'PNG32',
    'TRANSPARENT': true
  };
  ol.obj.assign(baseParams, this.params_);

  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
      pixelRatio, projection, baseParams);
};


/**
 * Update the user-provided params.
 * @param {Object} params Params.
 * @api stable
 */
ol.source.TileArcGISRest.prototype.updateParams = function(params) {
  ol.obj.assign(this.params_, params);
  this.changed();
};

goog.provide('ol.source.TileDebug');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.dom');
goog.require('ol.size');
goog.require('ol.source.Tile');


/**
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Size} tileSize Tile size.
 * @param {string} text Text.
 * @private
 */
ol.DebugTile_ = function(tileCoord, tileSize, text) {

  ol.Tile.call(this, tileCoord, ol.Tile.State.LOADED);

  /**
   * @private
   * @type {ol.Size}
   */
  this.tileSize_ = tileSize;

  /**
   * @private
   * @type {string}
   */
  this.text_ = text;

  /**
   * @private
   * @type {Object.<number, HTMLCanvasElement>}
   */
  this.canvasByContext_ = {};

};
ol.inherits(ol.DebugTile_, ol.Tile);


/**
 * Get the image element for this tile.
 * @param {Object=} opt_context Optional context. Only used by the DOM
 *     renderer.
 * @return {HTMLCanvasElement} Image.
 */
ol.DebugTile_.prototype.getImage = function(opt_context) {
  var key = opt_context !== undefined ? ol.getUid(opt_context) : -1;
  if (key in this.canvasByContext_) {
    return this.canvasByContext_[key];
  } else {

    var tileSize = this.tileSize_;
    var context = ol.dom.createCanvasContext2D(tileSize[0], tileSize[1]);

    context.strokeStyle = 'black';
    context.strokeRect(0.5, 0.5, tileSize[0] + 0.5, tileSize[1] + 0.5);

    context.fillStyle = 'black';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.font = '24px sans-serif';
    context.fillText(this.text_, tileSize[0] / 2, tileSize[1] / 2);

    this.canvasByContext_[key] = context.canvas;
    return context.canvas;

  }
};


/**
 * @classdesc
 * A pseudo tile source, which does not fetch tiles from a server, but renders
 * a grid outline for the tile grid/projection along with the coordinates for
 * each tile. See examples/canvas-tiles for an example.
 *
 * Uses Canvas context2d, so requires Canvas support.
 *
 * @constructor
 * @extends {ol.source.Tile}
 * @param {olx.source.TileDebugOptions} options Debug tile options.
 * @api
 */
ol.source.TileDebug = function(options) {

  ol.source.Tile.call(this, {
    opaque: false,
    projection: options.projection,
    tileGrid: options.tileGrid,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

};
ol.inherits(ol.source.TileDebug, ol.source.Tile);


/**
 * @inheritDoc
 */
ol.source.TileDebug.prototype.getTile = function(z, x, y) {
  var tileCoordKey = this.getKeyZXY(z, x, y);
  if (this.tileCache.containsKey(tileCoordKey)) {
    return /** @type {!ol.DebugTile_} */ (this.tileCache.get(tileCoordKey));
  } else {
    var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z));
    var tileCoord = [z, x, y];
    var textTileCoord = this.getTileCoordForTileUrlFunction(tileCoord);
    var text = !textTileCoord ? '' :
        this.getTileCoordForTileUrlFunction(textTileCoord).toString();
    var tile = new ol.DebugTile_(tileCoord, tileSize, text);
    this.tileCache.set(tileCoordKey, tile);
    return tile;
  }
};

// FIXME check order of async callbacks

/**
 * @see http://mapbox.com/developers/api/
 */

goog.provide('ol.source.TileJSON');
goog.provide('ol.tilejson');

goog.require('ol');
goog.require('ol.Attribution');
goog.require('ol.TileUrlFunction');
goog.require('ol.extent');
goog.require('ol.net');
goog.require('ol.proj');
goog.require('ol.source.State');
goog.require('ol.source.TileImage');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * Layer source for tile data in TileJSON format.
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.TileJSONOptions} options TileJSON options.
 * @api stable
 */
ol.source.TileJSON = function(options) {

  /**
   * @type {TileJSON}
   * @private
   */
  this.tileJSON_ = null;

  ol.source.TileImage.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    projection: ol.proj.get('EPSG:3857'),
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    state: ol.source.State.LOADING,
    tileLoadFunction: options.tileLoadFunction,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

  if (options.jsonp) {
    ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this),
        this.handleTileJSONError.bind(this));
  } else {
    var client = new XMLHttpRequest();
    client.addEventListener('load', this.onXHRLoad_.bind(this));
    client.addEventListener('error', this.onXHRError_.bind(this));
    client.open('GET', options.url);
    client.send();
  }

};
ol.inherits(ol.source.TileJSON, ol.source.TileImage);


/**
 * @private
 * @param {Event} event The load event.
 */
ol.source.TileJSON.prototype.onXHRLoad_ = function(event) {
  var client = /** @type {XMLHttpRequest} */ (event.target);
  // status will be 0 for file:// urls
  if (!client.status || client.status >= 200 && client.status < 300) {
    var response;
    try {
      response = /** @type {TileJSON} */(JSON.parse(client.responseText));
    } catch (err) {
      this.handleTileJSONError();
      return;
    }
    this.handleTileJSONResponse(response);
  } else {
    this.handleTileJSONError();
  }
};


/**
 * @private
 * @param {Event} event The error event.
 */
ol.source.TileJSON.prototype.onXHRError_ = function(event) {
  this.handleTileJSONError();
};


/**
 * @return {TileJSON} The tilejson object.
 * @api
 */
ol.source.TileJSON.prototype.getTileJSON = function() {
  return this.tileJSON_;
};


/**
 * @protected
 * @param {TileJSON} tileJSON Tile JSON.
 */
ol.source.TileJSON.prototype.handleTileJSONResponse = function(tileJSON) {

  var epsg4326Projection = ol.proj.get('EPSG:4326');

  var sourceProjection = this.getProjection();
  var extent;
  if (tileJSON.bounds !== undefined) {
    var transform = ol.proj.getTransformFromProjections(
        epsg4326Projection, sourceProjection);
    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
  }

  if (tileJSON.scheme !== undefined) {
    goog.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"');
  }
  var minZoom = tileJSON.minzoom || 0;
  var maxZoom = tileJSON.maxzoom || 22;
  var tileGrid = ol.tilegrid.createXYZ({
    extent: ol.tilegrid.extentFromProjection(sourceProjection),
    maxZoom: maxZoom,
    minZoom: minZoom
  });
  this.tileGrid = tileGrid;

  this.tileUrlFunction =
      ol.TileUrlFunction.createFromTemplates(tileJSON.tiles, tileGrid);

  if (tileJSON.attribution !== undefined && !this.getAttributions()) {
    var attributionExtent = extent !== undefined ?
        extent : epsg4326Projection.getExtent();
    /** @type {Object.<string, Array.<ol.TileRange>>} */
    var tileRanges = {};
    var z, zKey;
    for (z = minZoom; z <= maxZoom; ++z) {
      zKey = z.toString();
      tileRanges[zKey] =
          [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
    }
    this.setAttributions([
      new ol.Attribution({
        html: tileJSON.attribution,
        tileRanges: tileRanges
      })
    ]);
  }
  this.tileJSON_ = tileJSON;
  this.setState(ol.source.State.READY);

};


/**
 * @protected
 */
ol.source.TileJSON.prototype.handleTileJSONError = function() {
  this.setState(ol.source.State.ERROR);
};

goog.provide('ol.source.TileUTFGrid');

goog.require('goog.async.nextTick');
goog.require('ol');
goog.require('ol.Attribution');
goog.require('ol.Tile');
goog.require('ol.TileUrlFunction');
goog.require('ol.asserts');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.net');
goog.require('ol.proj');
goog.require('ol.source.State');
goog.require('ol.source.Tile');
goog.require('ol.tilegrid');


/**
 * @classdesc
 * Layer source for UTFGrid interaction data loaded from TileJSON format.
 *
 * @constructor
 * @extends {ol.source.Tile}
 * @param {olx.source.TileUTFGridOptions} options Source options.
 * @api
 */
ol.source.TileUTFGrid = function(options) {
  ol.source.Tile.call(this, {
    projection: ol.proj.get('EPSG:3857'),
    state: ol.source.State.LOADING
  });

  /**
   * @private
   * @type {boolean}
   */
  this.preemptive_ = options.preemptive !== undefined ?
      options.preemptive : true;

  /**
   * @private
   * @type {!ol.TileUrlFunctionType}
   */
  this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction;

  /**
   * @private
   * @type {string|undefined}
   */
  this.template_ = undefined;

  /**
   * @private
   * @type {boolean}
   */
  this.jsonp_ = options.jsonp || false;

  if (options.url) {
    if (this.jsonp_) {
      ol.net.jsonp(options.url, this.handleTileJSONResponse.bind(this),
          this.handleTileJSONError.bind(this));
    } else {
      var client = new XMLHttpRequest();
      client.addEventListener('load', this.onXHRLoad_.bind(this));
      client.addEventListener('error', this.onXHRError_.bind(this));
      client.open('GET', options.url);
      client.send();
    }
  } else if (options.tileJSON) {
    this.handleTileJSONResponse(options.tileJSON);
  } else {
    ol.asserts.assert(false, 51); // Either `url` or `tileJSON` options must be provided
  }
};
ol.inherits(ol.source.TileUTFGrid, ol.source.Tile);


/**
 * @private
 * @param {Event} event The load event.
 */
ol.source.TileUTFGrid.prototype.onXHRLoad_ = function(event) {
  var client = /** @type {XMLHttpRequest} */ (event.target);
  // status will be 0 for file:// urls
  if (!client.status || client.status >= 200 && client.status < 300) {
    var response;
    try {
      response = /** @type {TileJSON} */(JSON.parse(client.responseText));
    } catch (err) {
      this.handleTileJSONError();
      return;
    }
    this.handleTileJSONResponse(response);
  } else {
    this.handleTileJSONError();
  }
};


/**
 * @private
 * @param {Event} event The error event.
 */
ol.source.TileUTFGrid.prototype.onXHRError_ = function(event) {
  this.handleTileJSONError();
};


/**
 * Return the template from TileJSON.
 * @return {string|undefined} The template from TileJSON.
 * @api
 */
ol.source.TileUTFGrid.prototype.getTemplate = function() {
  return this.template_;
};


/**
 * Calls the callback (synchronously by default) with the available data
 * for given coordinate and resolution (or `null` if not yet loaded or
 * in case of an error).
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {function(this: T, *)} callback Callback.
 * @param {T=} opt_this The object to use as `this` in the callback.
 * @param {boolean=} opt_request If `true` the callback is always async.
 *                               The tile data is requested if not yet loaded.
 * @template T
 * @api
 */
ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution = function(
    coordinate, resolution, callback, opt_this, opt_request) {
  if (this.tileGrid) {
    var tileCoord = this.tileGrid.getTileCoordForCoordAndResolution(
        coordinate, resolution);
    var tile = /** @type {!ol.source.TileUTFGridTile_} */(this.getTile(
        tileCoord[0], tileCoord[1], tileCoord[2], 1, this.getProjection()));
    tile.forDataAtCoordinate(coordinate, callback, opt_this, opt_request);
  } else {
    if (opt_request === true) {
      goog.async.nextTick(function() {
        callback.call(opt_this, null);
      });
    } else {
      callback.call(opt_this, null);
    }
  }
};


/**
 * @protected
 */
ol.source.TileUTFGrid.prototype.handleTileJSONError = function() {
  this.setState(ol.source.State.ERROR);
};


/**
 * TODO: very similar to ol.source.TileJSON#handleTileJSONResponse
 * @protected
 * @param {TileJSON} tileJSON Tile JSON.
 */
ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {

  var epsg4326Projection = ol.proj.get('EPSG:4326');

  var sourceProjection = this.getProjection();
  var extent;
  if (tileJSON.bounds !== undefined) {
    var transform = ol.proj.getTransformFromProjections(
        epsg4326Projection, sourceProjection);
    extent = ol.extent.applyTransform(tileJSON.bounds, transform);
  }

  if (tileJSON.scheme !== undefined) {
    goog.DEBUG && console.assert(tileJSON.scheme == 'xyz', 'tileJSON-scheme is "xyz"');
  }
  var minZoom = tileJSON.minzoom || 0;
  var maxZoom = tileJSON.maxzoom || 22;
  var tileGrid = ol.tilegrid.createXYZ({
    extent: ol.tilegrid.extentFromProjection(sourceProjection),
    maxZoom: maxZoom,
    minZoom: minZoom
  });
  this.tileGrid = tileGrid;

  this.template_ = tileJSON.template;

  var grids = tileJSON.grids;
  if (!grids) {
    this.setState(ol.source.State.ERROR);
    return;
  }

  this.tileUrlFunction_ =
      ol.TileUrlFunction.createFromTemplates(grids, tileGrid);

  if (tileJSON.attribution !== undefined) {
    var attributionExtent = extent !== undefined ?
        extent : epsg4326Projection.getExtent();
    /** @type {Object.<string, Array.<ol.TileRange>>} */
    var tileRanges = {};
    var z, zKey;
    for (z = minZoom; z <= maxZoom; ++z) {
      zKey = z.toString();
      tileRanges[zKey] =
          [tileGrid.getTileRangeForExtentAndZ(attributionExtent, z)];
    }
    this.setAttributions([
      new ol.Attribution({
        html: tileJSON.attribution,
        tileRanges: tileRanges
      })
    ]);
  }

  this.setState(ol.source.State.READY);

};


/**
 * @inheritDoc
 */
ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) {
  var tileCoordKey = this.getKeyZXY(z, x, y);
  if (this.tileCache.containsKey(tileCoordKey)) {
    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
  } else {
    goog.DEBUG && console.assert(projection, 'argument projection is truthy');
    var tileCoord = [z, x, y];
    var urlTileCoord =
        this.getTileCoordForTileUrlFunction(tileCoord, projection);
    var tileUrl = this.tileUrlFunction_(urlTileCoord, pixelRatio, projection);
    var tile = new ol.source.TileUTFGridTile_(
        tileCoord,
        tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY,
        tileUrl !== undefined ? tileUrl : '',
        this.tileGrid.getTileCoordExtent(tileCoord),
        this.preemptive_,
        this.jsonp_);
    this.tileCache.set(tileCoordKey, tile);
    return tile;
  }
};


/**
 * @inheritDoc
 */
ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
  var tileCoordKey = this.getKeyZXY(z, x, y);
  if (this.tileCache.containsKey(tileCoordKey)) {
    this.tileCache.get(tileCoordKey);
  }
};


/**
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Tile.State} state State.
 * @param {string} src Image source URI.
 * @param {ol.Extent} extent Extent of the tile.
 * @param {boolean} preemptive Load the tile when visible (before it's needed).
 * @param {boolean} jsonp Load the tile as a script.
 * @private
 */
ol.source.TileUTFGridTile_ = function(tileCoord, state, src, extent, preemptive, jsonp) {

  ol.Tile.call(this, tileCoord, state);

  /**
   * @private
   * @type {string}
   */
  this.src_ = src;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.extent_ = extent;

  /**
   * @private
   * @type {boolean}
   */
  this.preemptive_ = preemptive;

  /**
   * @private
   * @type {Array.<string>}
   */
  this.grid_ = null;

  /**
   * @private
   * @type {Array.<string>}
   */
  this.keys_ = null;

  /**
   * @private
   * @type {Object.<string, Object>|undefined}
   */
  this.data_ = null;


  /**
   * @private
   * @type {boolean}
   */
  this.jsonp_ = jsonp;

};
ol.inherits(ol.source.TileUTFGridTile_, ol.Tile);


/**
 * Get the image element for this tile.
 * @param {Object=} opt_context Optional context. Only used for the DOM
 *     renderer.
 * @return {Image} Image.
 */
ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) {
  return null;
};


/**
 * Synchronously returns data at given coordinate (if available).
 * @param {ol.Coordinate} coordinate Coordinate.
 * @return {*} The data.
 */
ol.source.TileUTFGridTile_.prototype.getData = function(coordinate) {
  if (!this.grid_ || !this.keys_) {
    return null;
  }
  var xRelative = (coordinate[0] - this.extent_[0]) /
      (this.extent_[2] - this.extent_[0]);
  var yRelative = (coordinate[1] - this.extent_[1]) /
      (this.extent_[3] - this.extent_[1]);

  var row = this.grid_[Math.floor((1 - yRelative) * this.grid_.length)];

  if (typeof row !== 'string') {
    return null;
  }

  var code = row.charCodeAt(Math.floor(xRelative * row.length));
  if (code >= 93) {
    code--;
  }
  if (code >= 35) {
    code--;
  }
  code -= 32;

  var data = null;
  if (code in this.keys_) {
    var id = this.keys_[code];
    if (this.data_ && id in this.data_) {
      data = this.data_[id];
    } else {
      data = id;
    }
  }
  return data;
};


/**
 * Calls the callback (synchronously by default) with the available data
 * for given coordinate (or `null` if not yet loaded).
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {function(this: T, *)} callback Callback.
 * @param {T=} opt_this The object to use as `this` in the callback.
 * @param {boolean=} opt_request If `true` the callback is always async.
 *                               The tile data is requested if not yet loaded.
 * @template T
 */
ol.source.TileUTFGridTile_.prototype.forDataAtCoordinate = function(coordinate, callback, opt_this, opt_request) {
  if (this.state == ol.Tile.State.IDLE && opt_request === true) {
    ol.events.listenOnce(this, ol.events.EventType.CHANGE, function(e) {
      callback.call(opt_this, this.getData(coordinate));
    }, this);
    this.loadInternal_();
  } else {
    if (opt_request === true) {
      goog.async.nextTick(function() {
        callback.call(opt_this, this.getData(coordinate));
      }, this);
    } else {
      callback.call(opt_this, this.getData(coordinate));
    }
  }
};


/**
 * @inheritDoc
 */
ol.source.TileUTFGridTile_.prototype.getKey = function() {
  return this.src_;
};


/**
 * @private
 */
ol.source.TileUTFGridTile_.prototype.handleError_ = function() {
  this.state = ol.Tile.State.ERROR;
  this.changed();
};


/**
 * @param {!UTFGridJSON} json UTFGrid data.
 * @private
 */
ol.source.TileUTFGridTile_.prototype.handleLoad_ = function(json) {
  this.grid_ = json.grid;
  this.keys_ = json.keys;
  this.data_ = json.data;

  this.state = ol.Tile.State.EMPTY;
  this.changed();
};


/**
 * @private
 */
ol.source.TileUTFGridTile_.prototype.loadInternal_ = function() {
  if (this.state == ol.Tile.State.IDLE) {
    this.state = ol.Tile.State.LOADING;
    if (this.jsonp_) {
      ol.net.jsonp(this.src_, this.handleLoad_.bind(this),
          this.handleError_.bind(this));
    } else {
      var client = new XMLHttpRequest();
      client.addEventListener('load', this.onXHRLoad_.bind(this));
      client.addEventListener('error', this.onXHRError_.bind(this));
      client.open('GET', this.src_);
      client.send();
    }
  }
};


/**
 * @private
 * @param {Event} event The load event.
 */
ol.source.TileUTFGridTile_.prototype.onXHRLoad_ = function(event) {
  var client = /** @type {XMLHttpRequest} */ (event.target);
  // status will be 0 for file:// urls
  if (!client.status || client.status >= 200 && client.status < 300) {
    var response;
    try {
      response = /** @type {!UTFGridJSON} */(JSON.parse(client.responseText));
    } catch (err) {
      this.handleError_();
      return;
    }
    this.handleLoad_(response);
  } else {
    this.handleError_();
  }
};


/**
 * @private
 * @param {Event} event The error event.
 */
ol.source.TileUTFGridTile_.prototype.onXHRError_ = function(event) {
  this.handleError_();
};


/**
 * Load not yet loaded URI.
 */
ol.source.TileUTFGridTile_.prototype.load = function() {
  if (this.preemptive_) {
    this.loadInternal_();
  }
};

// FIXME add minZoom support
// FIXME add date line wrap (tile coord transform)
// FIXME cannot be shared between maps with different projections

goog.provide('ol.source.TileWMS');

goog.require('ol');
goog.require('ol.asserts');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.math');
goog.require('ol.proj');
goog.require('ol.size');
goog.require('ol.source.TileImage');
goog.require('ol.source.wms.ServerType');
goog.require('ol.tilecoord');
goog.require('ol.string');
goog.require('ol.uri');

/**
 * @classdesc
 * Layer source for tile data from WMS servers.
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.TileWMSOptions=} opt_options Tile WMS options.
 * @api stable
 */
ol.source.TileWMS = function(opt_options) {

  var options = opt_options || {};

  var params = options.params || {};

  var transparent = 'TRANSPARENT' in params ? params['TRANSPARENT'] : true;

  ol.source.TileImage.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    logo: options.logo,
    opaque: !transparent,
    projection: options.projection,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileGrid: options.tileGrid,
    tileLoadFunction: options.tileLoadFunction,
    url: options.url,
    urls: options.urls,
    wrapX: options.wrapX !== undefined ? options.wrapX : true
  });

  /**
   * @private
   * @type {number}
   */
  this.gutter_ = options.gutter !== undefined ? options.gutter : 0;

  /**
   * @private
   * @type {!Object}
   */
  this.params_ = params;

  /**
   * @private
   * @type {boolean}
   */
  this.v13_ = true;

  /**
   * @private
   * @type {ol.source.wms.ServerType|undefined}
   */
  this.serverType_ =
      /** @type {ol.source.wms.ServerType|undefined} */ (options.serverType);

  /**
   * @private
   * @type {boolean}
   */
  this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;

  /**
   * @private
   * @type {string}
   */
  this.coordKeyPrefix_ = '';
  this.resetCoordKeyPrefix_();

  /**
   * @private
   * @type {ol.Extent}
   */
  this.tmpExtent_ = ol.extent.createEmpty();

  this.updateV13_();
  this.setKey(this.getKeyForParams_());

};
ol.inherits(ol.source.TileWMS, ol.source.TileImage);


/**
 * Return the GetFeatureInfo URL for the passed coordinate, resolution, and
 * projection. Return `undefined` if the GetFeatureInfo URL cannot be
 * constructed.
 * @param {ol.Coordinate} coordinate Coordinate.
 * @param {number} resolution Resolution.
 * @param {ol.ProjectionLike} projection Projection.
 * @param {!Object} params GetFeatureInfo params. `INFO_FORMAT` at least should
 *     be provided. If `QUERY_LAYERS` is not provided then the layers specified
 *     in the `LAYERS` parameter will be used. `VERSION` should not be
 *     specified here.
 * @return {string|undefined} GetFeatureInfo URL.
 * @api stable
 */
ol.source.TileWMS.prototype.getGetFeatureInfoUrl = function(coordinate, resolution, projection, params) {

  goog.DEBUG && console.assert(!('VERSION' in params),
      'key VERSION is not allowed in params');

  var projectionObj = ol.proj.get(projection);

  var tileGrid = this.getTileGrid();
  if (!tileGrid) {
    tileGrid = this.getTileGridForProjection(projectionObj);
  }

  var tileCoord = tileGrid.getTileCoordForCoordAndResolution(
      coordinate, resolution);

  if (tileGrid.getResolutions().length <= tileCoord[0]) {
    return undefined;
  }

  var tileResolution = tileGrid.getResolution(tileCoord[0]);
  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_);
  var tileSize = ol.size.toSize(
      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);

  var gutter = this.gutter_;
  if (gutter !== 0) {
    tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize);
    tileExtent = ol.extent.buffer(tileExtent,
        tileResolution * gutter, tileExtent);
  }

  var baseParams = {
    'SERVICE': 'WMS',
    'VERSION': ol.DEFAULT_WMS_VERSION,
    'REQUEST': 'GetFeatureInfo',
    'FORMAT': 'image/png',
    'TRANSPARENT': true,
    'QUERY_LAYERS': this.params_['LAYERS']
  };
  ol.obj.assign(baseParams, this.params_, params);

  var x = Math.floor((coordinate[0] - tileExtent[0]) / tileResolution);
  var y = Math.floor((tileExtent[3] - coordinate[1]) / tileResolution);

  baseParams[this.v13_ ? 'I' : 'X'] = x;
  baseParams[this.v13_ ? 'J' : 'Y'] = y;

  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
      1, projectionObj, baseParams);
};


/**
 * @inheritDoc
 */
ol.source.TileWMS.prototype.getGutterInternal = function() {
  return this.gutter_;
};


/**
 * @inheritDoc
 */
ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
  return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y);
};


/**
 * Get the user-provided params, i.e. those passed to the constructor through
 * the "params" option, and possibly updated using the updateParams method.
 * @return {Object} Params.
 * @api stable
 */
ol.source.TileWMS.prototype.getParams = function() {
  return this.params_;
};


/**
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Size} tileSize Tile size.
 * @param {ol.Extent} tileExtent Tile extent.
 * @param {number} pixelRatio Pixel ratio.
 * @param {ol.proj.Projection} projection Projection.
 * @param {Object} params Params.
 * @return {string|undefined} Request URL.
 * @private
 */
ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
        pixelRatio, projection, params) {

  var urls = this.urls;
  if (!urls) {
    return undefined;
  }

  params['WIDTH'] = tileSize[0];
  params['HEIGHT'] = tileSize[1];

  params[this.v13_ ? 'CRS' : 'SRS'] = projection.getCode();

  if (!('STYLES' in this.params_)) {
    params['STYLES'] = '';
  }

  if (pixelRatio != 1) {
    switch (this.serverType_) {
      case ol.source.wms.ServerType.GEOSERVER:
        var dpi = (90 * pixelRatio + 0.5) | 0;
        if ('FORMAT_OPTIONS' in params) {
          params['FORMAT_OPTIONS'] += ';dpi:' + dpi;
        } else {
          params['FORMAT_OPTIONS'] = 'dpi:' + dpi;
        }
        break;
      case ol.source.wms.ServerType.MAPSERVER:
        params['MAP_RESOLUTION'] = 90 * pixelRatio;
        break;
      case ol.source.wms.ServerType.CARMENTA_SERVER:
      case ol.source.wms.ServerType.QGIS:
        params['DPI'] = 90 * pixelRatio;
        break;
      default:
        ol.asserts.assert(false, 52); // Unknown `serverType` configured
        break;
    }
  }

  var axisOrientation = projection.getAxisOrientation();
  var bbox = tileExtent;
  if (this.v13_ && axisOrientation.substr(0, 2) == 'ne') {
    var tmp;
    tmp = tileExtent[0];
    bbox[0] = tileExtent[1];
    bbox[1] = tmp;
    tmp = tileExtent[2];
    bbox[2] = tileExtent[3];
    bbox[3] = tmp;
  }
  params['BBOX'] = bbox.join(',');

  var url;
  if (urls.length == 1) {
    url = urls[0];
  } else {
    var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
    url = urls[index];
  }
  return ol.uri.appendParams(url, params);
};


/**
 * @inheritDoc
 */
ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) {
  return (!this.hidpi_ || this.serverType_ === undefined) ? 1 :
      /** @type {number} */ (pixelRatio);
};


/**
 * @private
 */
ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
  var i = 0;
  var res = [];

  if (this.urls) {
    var j, jj;
    for (j = 0, jj = this.urls.length; j < jj; ++j) {
      res[i++] = this.urls[j];
    }
  }

  this.coordKeyPrefix_ = res.join('#');
};


/**
 * @private
 * @return {string} The key for the current params.
 */
ol.source.TileWMS.prototype.getKeyForParams_ = function() {
  var i = 0;
  var res = [];
  for (var key in this.params_) {
    res[i++] = key + '-' + this.params_[key];
  }
  return res.join('/');
};


/**
 * @inheritDoc
 */
ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) {

  var tileGrid = this.getTileGrid();
  if (!tileGrid) {
    tileGrid = this.getTileGridForProjection(projection);
  }

  if (tileGrid.getResolutions().length <= tileCoord[0]) {
    return undefined;
  }

  if (pixelRatio != 1 && (!this.hidpi_ || this.serverType_ === undefined)) {
    pixelRatio = 1;
  }

  var tileResolution = tileGrid.getResolution(tileCoord[0]);
  var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent_);
  var tileSize = ol.size.toSize(
      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);

  var gutter = this.gutter_;
  if (gutter !== 0) {
    tileSize = ol.size.buffer(tileSize, gutter, this.tmpSize);
    tileExtent = ol.extent.buffer(tileExtent,
        tileResolution * gutter, tileExtent);
  }

  if (pixelRatio != 1) {
    tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize);
  }

  var baseParams = {
    'SERVICE': 'WMS',
    'VERSION': ol.DEFAULT_WMS_VERSION,
    'REQUEST': 'GetMap',
    'FORMAT': 'image/png',
    'TRANSPARENT': true
  };
  ol.obj.assign(baseParams, this.params_);

  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
      pixelRatio, projection, baseParams);
};

/**
 * @inheritDoc
 */
ol.source.TileWMS.prototype.setUrls = function(urls) {
  ol.source.TileImage.prototype.setUrls.call(this, urls);
  this.resetCoordKeyPrefix_();
};


/**
 * Update the user-provided params.
 * @param {Object} params Params.
 * @api stable
 */
ol.source.TileWMS.prototype.updateParams = function(params) {
  ol.obj.assign(this.params_, params);
  this.resetCoordKeyPrefix_();
  this.updateV13_();
  this.setKey(this.getKeyForParams_());
};


/**
 * @private
 */
ol.source.TileWMS.prototype.updateV13_ = function() {
  var version = this.params_['VERSION'] || ol.DEFAULT_WMS_VERSION;
  this.v13_ = ol.string.compareVersions(version, '1.3') >= 0;
};

goog.provide('ol.VectorTile');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.dom');


/**
 * @constructor
 * @extends {ol.Tile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Tile.State} state State.
 * @param {string} src Data source url.
 * @param {ol.format.Feature} format Feature format.
 * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
 */
ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) {

  ol.Tile.call(this, tileCoord, state);

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = ol.dom.createCanvasContext2D();

  /**
   * @private
   * @type {ol.format.Feature}
   */
  this.format_ = format;

  /**
   * @private
   * @type {Array.<ol.Feature>}
   */
  this.features_ = null;

  /**
   * @private
   * @type {ol.FeatureLoader}
   */
  this.loader_;

  /**
   * Data projection
   * @private
   * @type {ol.proj.Projection}
   */
  this.projection_;

  /**
   * @private
   * @type {ol.TileReplayState}
   */
  this.replayState_ = {
    dirty: false,
    renderedRenderOrder: null,
    renderedRevision: -1,
    renderedTileRevision: -1,
    replayGroup: null,
    skippedFeatures: []
  };

  /**
   * @private
   * @type {ol.TileLoadFunctionType}
   */
  this.tileLoadFunction_ = tileLoadFunction;

  /**
   * @private
   * @type {string}
   */
  this.url_ = src;

};
ol.inherits(ol.VectorTile, ol.Tile);


/**
 * @return {CanvasRenderingContext2D} The rendering context.
 */
ol.VectorTile.prototype.getContext = function() {
  return this.context_;
};


/**
 * @inheritDoc
 */
ol.VectorTile.prototype.getImage = function() {
  return this.replayState_.renderedTileRevision == -1 ?
      null : this.context_.canvas;
};


/**
 * Get the feature format assigned for reading this tile's features.
 * @return {ol.format.Feature} Feature format.
 * @api
 */
ol.VectorTile.prototype.getFormat = function() {
  return this.format_;
};


/**
 * @return {Array.<ol.Feature>} Features.
 */
ol.VectorTile.prototype.getFeatures = function() {
  return this.features_;
};


/**
 * @return {ol.TileReplayState} The replay state.
 */
ol.VectorTile.prototype.getReplayState = function() {
  return this.replayState_;
};


/**
 * @inheritDoc
 */
ol.VectorTile.prototype.getKey = function() {
  return this.url_;
};


/**
 * @return {ol.proj.Projection} Feature projection.
 */
ol.VectorTile.prototype.getProjection = function() {
  return this.projection_;
};


/**
 * Load the tile.
 */
ol.VectorTile.prototype.load = function() {
  if (this.state == ol.Tile.State.IDLE) {
    this.setState(ol.Tile.State.LOADING);
    this.tileLoadFunction_(this, this.url_);
    this.loader_(null, NaN, null);
  }
};


/**
 * @param {Array.<ol.Feature>} features Features.
 * @api
 */
ol.VectorTile.prototype.setFeatures = function(features) {
  this.features_ = features;
  this.setState(ol.Tile.State.LOADED);
};


/**
 * Set the projection of the features that were added with {@link #setFeatures}.
 * @param {ol.proj.Projection} projection Feature projection.
 * @api
 */
ol.VectorTile.prototype.setProjection = function(projection) {
  this.projection_ = projection;
};


/**
 * @param {ol.Tile.State} tileState Tile state.
 */
ol.VectorTile.prototype.setState = function(tileState) {
  this.state = tileState;
  this.changed();
};


/**
 * Set the feature loader for reading this tile's features.
 * @param {ol.FeatureLoader} loader Feature loader.
 * @api
 */
ol.VectorTile.prototype.setLoader = function(loader) {
  this.loader_ = loader;
};

goog.provide('ol.source.VectorTile');

goog.require('ol');
goog.require('ol.Tile');
goog.require('ol.VectorTile');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.featureloader');
goog.require('ol.size');
goog.require('ol.source.UrlTile');


/**
 * @classdesc
 * Class for layer sources providing vector data divided into a tile grid, to be
 * used with {@link ol.layer.VectorTile}. Although this source receives tiles
 * with vector features from the server, it is not meant for feature editing.
 * Features are optimized for rendering, their geometries are clipped at or near
 * tile boundaries and simplified for a view resolution. See
 * {@link ol.source.Vector} for vector sources that are suitable for feature
 * editing.
 *
 * @constructor
 * @fires ol.source.TileEvent
 * @extends {ol.source.UrlTile}
 * @param {olx.source.VectorTileOptions} options Vector tile options.
 * @api
 */
ol.source.VectorTile = function(options) {

  ol.source.UrlTile.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize !== undefined ? options.cacheSize : 128,
    extent: options.extent,
    logo: options.logo,
    opaque: false,
    projection: options.projection,
    state: options.state,
    tileGrid: options.tileGrid,
    tileLoadFunction: options.tileLoadFunction ?
        options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction,
    tileUrlFunction: options.tileUrlFunction,
    tilePixelRatio: options.tilePixelRatio,
    url: options.url,
    urls: options.urls,
    wrapX: options.wrapX === undefined ? true : options.wrapX
  });

  /**
   * @private
   * @type {ol.format.Feature}
   */
  this.format_ = options.format ? options.format : null;

  /**
   * @private
   * @type {boolean}
   */
  this.overlaps_ = options.overlaps == undefined ? true : options.overlaps;

  /**
   * @protected
   * @type {function(new: ol.VectorTile, ol.TileCoord, ol.Tile.State, string,
   *        ol.format.Feature, ol.TileLoadFunctionType)}
   */
  this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile;

};
ol.inherits(ol.source.VectorTile, ol.source.UrlTile);


/**
 * @return {boolean} The source can have overlapping geometries.
 */
ol.source.VectorTile.prototype.getOverlaps = function() {
  return this.overlaps_;
};


/**
 * @inheritDoc
 */
ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) {
  var tileCoordKey = this.getKeyZXY(z, x, y);
  if (this.tileCache.containsKey(tileCoordKey)) {
    return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
  } else {
    var tileCoord = [z, x, y];
    var urlTileCoord = this.getTileCoordForTileUrlFunction(
        tileCoord, projection);
    var tileUrl = urlTileCoord ?
        this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined;
    var tile = new this.tileClass(
        tileCoord,
        tileUrl !== undefined ? ol.Tile.State.IDLE : ol.Tile.State.EMPTY,
        tileUrl !== undefined ? tileUrl : '',
        this.format_, this.tileLoadFunction);
    ol.events.listen(tile, ol.events.EventType.CHANGE,
        this.handleTileChange, this);

    this.tileCache.set(tileCoordKey, tile);
    return tile;
  }
};


/**
 * @inheritDoc
 */
ol.source.VectorTile.prototype.getTilePixelRatio = function(opt_pixelRatio) {
  return opt_pixelRatio == undefined ?
      ol.source.UrlTile.prototype.getTilePixelRatio.call(this, opt_pixelRatio) :
      opt_pixelRatio;
};


/**
 * @inheritDoc
 */
ol.source.VectorTile.prototype.getTilePixelSize = function(z, pixelRatio, projection) {
  var tileSize = ol.size.toSize(this.tileGrid.getTileSize(z));
  return [Math.round(tileSize[0] * pixelRatio), Math.round(tileSize[1] * pixelRatio)];
};


/**
 * @param {ol.VectorTile} vectorTile Vector tile.
 * @param {string} url URL.
 */
ol.source.VectorTile.defaultTileLoadFunction = function(vectorTile, url) {
  vectorTile.setLoader(ol.featureloader.tile(url, vectorTile.getFormat()));
};

goog.provide('ol.tilegrid.WMTS');

goog.require('ol');
goog.require('ol.proj');
goog.require('ol.tilegrid.TileGrid');


/**
 * @classdesc
 * Set the grid pattern for sources accessing WMTS tiled-image servers.
 *
 * @constructor
 * @extends {ol.tilegrid.TileGrid}
 * @param {olx.tilegrid.WMTSOptions} options WMTS options.
 * @struct
 * @api
 */
ol.tilegrid.WMTS = function(options) {

  goog.DEBUG && console.assert(
      options.resolutions.length == options.matrixIds.length,
      'options resolutions and matrixIds must have equal length (%s == %s)',
      options.resolutions.length, options.matrixIds.length);

  /**
   * @private
   * @type {!Array.<string>}
   */
  this.matrixIds_ = options.matrixIds;
  // FIXME: should the matrixIds become optionnal?

  ol.tilegrid.TileGrid.call(this, {
    extent: options.extent,
    origin: options.origin,
    origins: options.origins,
    resolutions: options.resolutions,
    tileSize: options.tileSize,
    tileSizes: options.tileSizes,
    sizes: options.sizes
  });

};
ol.inherits(ol.tilegrid.WMTS, ol.tilegrid.TileGrid);


/**
 * @param {number} z Z.
 * @return {string} MatrixId..
 */
ol.tilegrid.WMTS.prototype.getMatrixId = function(z) {
  goog.DEBUG && console.assert(0 <= z && z < this.matrixIds_.length,
      'attempted to retrieve matrixId for illegal z (%s)', z);
  return this.matrixIds_[z];
};


/**
 * Get the list of matrix identifiers.
 * @return {Array.<string>} MatrixIds.
 * @api
 */
ol.tilegrid.WMTS.prototype.getMatrixIds = function() {
  return this.matrixIds_;
};


/**
 * Create a tile grid from a WMTS capabilities matrix set.
 * @param {Object} matrixSet An object representing a matrixSet in the
 *     capabilities document.
 * @param {ol.Extent=} opt_extent An optional extent to restrict the tile
 *     ranges the server provides.
 * @return {ol.tilegrid.WMTS} WMTS tileGrid instance.
 * @api
 */
ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet = function(matrixSet, opt_extent) {

  /** @type {!Array.<number>} */
  var resolutions = [];
  /** @type {!Array.<string>} */
  var matrixIds = [];
  /** @type {!Array.<ol.Coordinate>} */
  var origins = [];
  /** @type {!Array.<ol.Size>} */
  var tileSizes = [];
  /** @type {!Array.<ol.Size>} */
  var sizes = [];

  var supportedCRSPropName = 'SupportedCRS';
  var matrixIdsPropName = 'TileMatrix';
  var identifierPropName = 'Identifier';
  var scaleDenominatorPropName = 'ScaleDenominator';
  var topLeftCornerPropName = 'TopLeftCorner';
  var tileWidthPropName = 'TileWidth';
  var tileHeightPropName = 'TileHeight';

  var projection;
  projection = ol.proj.get(matrixSet[supportedCRSPropName].replace(
      /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'));
  var metersPerUnit = projection.getMetersPerUnit();
  // swap origin x and y coordinates if axis orientation is lat/long
  var switchOriginXY = projection.getAxisOrientation().substr(0, 2) == 'ne';

  matrixSet[matrixIdsPropName].sort(function(a, b) {
    return b[scaleDenominatorPropName] - a[scaleDenominatorPropName];
  });

  matrixSet[matrixIdsPropName].forEach(function(elt, index, array) {
    matrixIds.push(elt[identifierPropName]);
    var resolution = elt[scaleDenominatorPropName] * 0.28E-3 / metersPerUnit;
    var tileWidth = elt[tileWidthPropName];
    var tileHeight = elt[tileHeightPropName];
    if (switchOriginXY) {
      origins.push([elt[topLeftCornerPropName][1],
        elt[topLeftCornerPropName][0]]);
    } else {
      origins.push(elt[topLeftCornerPropName]);
    }
    resolutions.push(resolution);
    tileSizes.push(tileWidth == tileHeight ?
        tileWidth : [tileWidth, tileHeight]);
    // top-left origin, so height is negative
    sizes.push([elt['MatrixWidth'], -elt['MatrixHeight']]);
  });

  return new ol.tilegrid.WMTS({
    extent: opt_extent,
    origins: origins,
    resolutions: resolutions,
    matrixIds: matrixIds,
    tileSizes: tileSizes,
    sizes: sizes
  });
};

goog.provide('ol.source.WMTS');
goog.provide('ol.source.WMTSRequestEncoding');

goog.require('ol');
goog.require('ol.TileUrlFunction');
goog.require('ol.array');
goog.require('ol.extent');
goog.require('ol.obj');
goog.require('ol.proj');
goog.require('ol.source.TileImage');
goog.require('ol.tilegrid.WMTS');
goog.require('ol.uri');


/**
 * Request encoding. One of 'KVP', 'REST'.
 * @enum {string}
 */
ol.source.WMTSRequestEncoding = {
  KVP: 'KVP',  // see spec §8
  REST: 'REST' // see spec §10
};


/**
 * @classdesc
 * Layer source for tile data from WMTS servers.
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.WMTSOptions} options WMTS options.
 * @api stable
 */
ol.source.WMTS = function(options) {

  // TODO: add support for TileMatrixLimits

  /**
   * @private
   * @type {string}
   */
  this.version_ = options.version !== undefined ? options.version : '1.0.0';

  /**
   * @private
   * @type {string}
   */
  this.format_ = options.format !== undefined ? options.format : 'image/jpeg';

  /**
   * @private
   * @type {!Object}
   */
  this.dimensions_ = options.dimensions !== undefined ? options.dimensions : {};

  /**
   * @private
   * @type {string}
   */
  this.layer_ = options.layer;

  /**
   * @private
   * @type {string}
   */
  this.matrixSet_ = options.matrixSet;

  /**
   * @private
   * @type {string}
   */
  this.style_ = options.style;

  var urls = options.urls;
  if (urls === undefined && options.url !== undefined) {
    urls = ol.TileUrlFunction.expandUrl(options.url);
  }

  // FIXME: should we guess this requestEncoding from options.url(s)
  //        structure? that would mean KVP only if a template is not provided.

  /**
   * @private
   * @type {ol.source.WMTSRequestEncoding}
   */
  this.requestEncoding_ = options.requestEncoding !== undefined ?
      /** @type {ol.source.WMTSRequestEncoding} */ (options.requestEncoding) :
      ol.source.WMTSRequestEncoding.KVP;

  var requestEncoding = this.requestEncoding_;

  // FIXME: should we create a default tileGrid?
  // we could issue a getCapabilities xhr to retrieve missing configuration
  var tileGrid = options.tileGrid;

  // context property names are lower case to allow for a case insensitive
  // replacement as some services use different naming conventions
  var context = {
    'layer': this.layer_,
    'style': this.style_,
    'tilematrixset': this.matrixSet_
  };

  if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
    ol.obj.assign(context, {
      'Service': 'WMTS',
      'Request': 'GetTile',
      'Version': this.version_,
      'Format': this.format_
    });
  }

  var dimensions = this.dimensions_;

  /**
   * @param {string} template Template.
   * @return {ol.TileUrlFunctionType} Tile URL function.
   */
  function createFromWMTSTemplate(template) {

    // TODO: we may want to create our own appendParams function so that params
    // order conforms to wmts spec guidance, and so that we can avoid to escape
    // special template params

    template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
        ol.uri.appendParams(template, context) :
        template.replace(/\{(\w+?)\}/g, function(m, p) {
          return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
        });

    return (
        /**
         * @param {ol.TileCoord} tileCoord Tile coordinate.
         * @param {number} pixelRatio Pixel ratio.
         * @param {ol.proj.Projection} projection Projection.
         * @return {string|undefined} Tile URL.
         */
        function(tileCoord, pixelRatio, projection) {
          if (!tileCoord) {
            return undefined;
          } else {
            var localContext = {
              'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
              'TileCol': tileCoord[1],
              'TileRow': -tileCoord[2] - 1
            };
            ol.obj.assign(localContext, dimensions);
            var url = template;
            if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
              url = ol.uri.appendParams(url, localContext);
            } else {
              url = url.replace(/\{(\w+?)\}/g, function(m, p) {
                return localContext[p];
              });
            }
            return url;
          }
        });
  }

  var tileUrlFunction = (urls && urls.length > 0) ?
      ol.TileUrlFunction.createFromTileUrlFunctions(
          urls.map(createFromWMTSTemplate)) :
      ol.TileUrlFunction.nullTileUrlFunction;

  ol.source.TileImage.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    logo: options.logo,
    projection: options.projection,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileClass: options.tileClass,
    tileGrid: tileGrid,
    tileLoadFunction: options.tileLoadFunction,
    tilePixelRatio: options.tilePixelRatio,
    tileUrlFunction: tileUrlFunction,
    urls: urls,
    wrapX: options.wrapX !== undefined ? options.wrapX : false
  });

  this.setKey(this.getKeyForDimensions_());

};
ol.inherits(ol.source.WMTS, ol.source.TileImage);


/**
 * Get the dimensions, i.e. those passed to the constructor through the
 * "dimensions" option, and possibly updated using the updateDimensions
 * method.
 * @return {!Object} Dimensions.
 * @api
 */
ol.source.WMTS.prototype.getDimensions = function() {
  return this.dimensions_;
};


/**
 * Return the image format of the WMTS source.
 * @return {string} Format.
 * @api
 */
ol.source.WMTS.prototype.getFormat = function() {
  return this.format_;
};


/**
 * Return the layer of the WMTS source.
 * @return {string} Layer.
 * @api
 */
ol.source.WMTS.prototype.getLayer = function() {
  return this.layer_;
};


/**
 * Return the matrix set of the WMTS source.
 * @return {string} MatrixSet.
 * @api
 */
ol.source.WMTS.prototype.getMatrixSet = function() {
  return this.matrixSet_;
};


/**
 * Return the request encoding, either "KVP" or "REST".
 * @return {ol.source.WMTSRequestEncoding} Request encoding.
 * @api
 */
ol.source.WMTS.prototype.getRequestEncoding = function() {
  return this.requestEncoding_;
};


/**
 * Return the style of the WMTS source.
 * @return {string} Style.
 * @api
 */
ol.source.WMTS.prototype.getStyle = function() {
  return this.style_;
};


/**
 * Return the version of the WMTS source.
 * @return {string} Version.
 * @api
 */
ol.source.WMTS.prototype.getVersion = function() {
  return this.version_;
};


/**
 * @private
 * @return {string} The key for the current dimensions.
 */
ol.source.WMTS.prototype.getKeyForDimensions_ = function() {
  var i = 0;
  var res = [];
  for (var key in this.dimensions_) {
    res[i++] = key + '-' + this.dimensions_[key];
  }
  return res.join('/');
};


/**
 * Update the dimensions.
 * @param {Object} dimensions Dimensions.
 * @api
 */
ol.source.WMTS.prototype.updateDimensions = function(dimensions) {
  ol.obj.assign(this.dimensions_, dimensions);
  this.setKey(this.getKeyForDimensions_());
};


/**
 * Generate source options from a capabilities object.
 * @param {Object} wmtsCap An object representing the capabilities document.
 * @param {Object} config Configuration properties for the layer.  Defaults for
 *                  the layer will apply if not provided.
 *
 * Required config properties:
 *  - layer - {string} The layer identifier.
 *
 * Optional config properties:
 *  - matrixSet - {string} The matrix set identifier, required if there is
 *       more than one matrix set in the layer capabilities.
 *  - projection - {string} The desired CRS when no matrixSet is specified.
 *       eg: "EPSG:3857". If the desired projection is not available,
 *       an error is thrown.
 *  - requestEncoding - {string} url encoding format for the layer. Default is
 *       the first tile url format found in the GetCapabilities response.
 *  - style - {string} The name of the style
 *  - format - {string} Image format for the layer. Default is the first
 *       format returned in the GetCapabilities response.
 * @return {olx.source.WMTSOptions} WMTS source options object.
 * @api
 */
ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {

  // TODO: add support for TileMatrixLimits
  goog.DEBUG && console.assert(config['layer'],
      'config "layer" must not be null');

  var layers = wmtsCap['Contents']['Layer'];
  var l = ol.array.find(layers, function(elt, index, array) {
    return elt['Identifier'] == config['layer'];
  });
  goog.DEBUG && console.assert(l, 'found a matching layer in Contents/Layer');

  goog.DEBUG && console.assert(l['TileMatrixSetLink'].length > 0,
      'layer has TileMatrixSetLink');
  var tileMatrixSets = wmtsCap['Contents']['TileMatrixSet'];
  var idx, matrixSet;
  if (l['TileMatrixSetLink'].length > 1) {
    if ('projection' in config) {
      idx = ol.array.findIndex(l['TileMatrixSetLink'],
          function(elt, index, array) {
            var tileMatrixSet = ol.array.find(tileMatrixSets, function(el) {
              return el['Identifier'] == elt['TileMatrixSet'];
            });
            return tileMatrixSet['SupportedCRS'].replace(
                /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'
                   ) == config['projection'];
          });
    } else {
      idx = ol.array.findIndex(l['TileMatrixSetLink'],
          function(elt, index, array) {
            return elt['TileMatrixSet'] == config['matrixSet'];
          });
    }
  } else {
    idx = 0;
  }
  if (idx < 0) {
    idx = 0;
  }
  matrixSet = /** @type {string} */
      (l['TileMatrixSetLink'][idx]['TileMatrixSet']);

  goog.DEBUG && console.assert(matrixSet, 'TileMatrixSet must not be null');

  var format = /** @type {string} */ (l['Format'][0]);
  if ('format' in config) {
    format = config['format'];
  }
  idx = ol.array.findIndex(l['Style'], function(elt, index, array) {
    if ('style' in config) {
      return elt['Title'] == config['style'];
    } else {
      return elt['isDefault'];
    }
  });
  if (idx < 0) {
    idx = 0;
  }
  var style = /** @type {string} */ (l['Style'][idx]['Identifier']);

  var dimensions = {};
  if ('Dimension' in l) {
    l['Dimension'].forEach(function(elt, index, array) {
      var key = elt['Identifier'];
      var value = elt['Default'];
      if (value !== undefined) {
        goog.DEBUG && console.assert(ol.array.includes(elt['Value'], value),
            'default value contained in values');
      } else {
        value = elt['Value'][0];
      }
      goog.DEBUG && console.assert(value !== undefined, 'value could be found');
      dimensions[key] = value;
    });
  }

  var matrixSets = wmtsCap['Contents']['TileMatrixSet'];
  var matrixSetObj = ol.array.find(matrixSets, function(elt, index, array) {
    return elt['Identifier'] == matrixSet;
  });
  goog.DEBUG && console.assert(matrixSetObj,
      'found matrixSet in Contents/TileMatrixSet');

  var projection;
  if ('projection' in config) {
    projection = ol.proj.get(config['projection']);
  } else {
    projection = ol.proj.get(matrixSetObj['SupportedCRS'].replace(
        /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, '$1:$3'));
  }

  var wgs84BoundingBox = l['WGS84BoundingBox'];
  var extent, wrapX;
  if (wgs84BoundingBox !== undefined) {
    var wgs84ProjectionExtent = ol.proj.get('EPSG:4326').getExtent();
    wrapX = (wgs84BoundingBox[0] == wgs84ProjectionExtent[0] &&
        wgs84BoundingBox[2] == wgs84ProjectionExtent[2]);
    extent = ol.proj.transformExtent(
        wgs84BoundingBox, 'EPSG:4326', projection);
    var projectionExtent = projection.getExtent();
    if (projectionExtent) {
      // If possible, do a sanity check on the extent - it should never be
      // bigger than the validity extent of the projection of a matrix set.
      if (!ol.extent.containsExtent(projectionExtent, extent)) {
        extent = undefined;
      }
    }
  }

  var tileGrid = ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet(
      matrixSetObj, extent);

  /** @type {!Array.<string>} */
  var urls = [];
  var requestEncoding = config['requestEncoding'];
  requestEncoding = requestEncoding !== undefined ? requestEncoding : '';

  goog.DEBUG && console.assert(
      ol.array.includes(['REST', 'RESTful', 'KVP', ''], requestEncoding),
      'requestEncoding (%s) is one of "REST", "RESTful", "KVP" or ""',
      requestEncoding);

  if ('OperationsMetadata' in wmtsCap && 'GetTile' in wmtsCap['OperationsMetadata']) {
    var gets = wmtsCap['OperationsMetadata']['GetTile']['DCP']['HTTP']['Get'];
    goog.DEBUG && console.assert(gets.length >= 1);

    for (var i = 0, ii = gets.length; i < ii; ++i) {
      var constraint = ol.array.find(gets[i]['Constraint'], function(element) {
        return element['name'] == 'GetEncoding';
      });
      var encodings = constraint['AllowedValues']['Value'];
      goog.DEBUG && console.assert(encodings.length >= 1);

      if (requestEncoding === '') {
        // requestEncoding not provided, use the first encoding from the list
        requestEncoding = encodings[0];
      }
      if (requestEncoding === ol.source.WMTSRequestEncoding.KVP) {
        if (ol.array.includes(encodings, ol.source.WMTSRequestEncoding.KVP)) {
          urls.push(/** @type {string} */ (gets[i]['href']));
        }
      } else {
        break;
      }
    }
  }
  if (urls.length === 0) {
    requestEncoding = ol.source.WMTSRequestEncoding.REST;
    l['ResourceURL'].forEach(function(element) {
      if (element['resourceType'] === 'tile') {
        format = element['format'];
        urls.push(/** @type {string} */ (element['template']));
      }
    });
  }
  goog.DEBUG && console.assert(urls.length > 0, 'At least one URL found');

  return {
    urls: urls,
    layer: config['layer'],
    matrixSet: matrixSet,
    format: format,
    projection: projection,
    requestEncoding: requestEncoding,
    tileGrid: tileGrid,
    style: style,
    dimensions: dimensions,
    wrapX: wrapX
  };

};

goog.provide('ol.source.Zoomify');

goog.require('ol');
goog.require('ol.ImageTile');
goog.require('ol.Tile');
goog.require('ol.asserts');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.source.TileImage');
goog.require('ol.tilegrid.TileGrid');


/**
 * @enum {string}
 */
ol.source.ZoomifyTierSizeCalculation = {
  DEFAULT: 'default',
  TRUNCATED: 'truncated'
};


/**
 * @classdesc
 * Layer source for tile data in Zoomify format.
 *
 * @constructor
 * @extends {ol.source.TileImage}
 * @param {olx.source.ZoomifyOptions=} opt_options Options.
 * @api stable
 */
ol.source.Zoomify = function(opt_options) {

  var options = opt_options || {};

  var size = options.size;
  var tierSizeCalculation = options.tierSizeCalculation !== undefined ?
      options.tierSizeCalculation :
      ol.source.ZoomifyTierSizeCalculation.DEFAULT;

  var imageWidth = size[0];
  var imageHeight = size[1];
  var tierSizeInTiles = [];
  var tileSize = ol.DEFAULT_TILE_SIZE;

  switch (tierSizeCalculation) {
    case ol.source.ZoomifyTierSizeCalculation.DEFAULT:
      while (imageWidth > tileSize || imageHeight > tileSize) {
        tierSizeInTiles.push([
          Math.ceil(imageWidth / tileSize),
          Math.ceil(imageHeight / tileSize)
        ]);
        tileSize += tileSize;
      }
      break;
    case ol.source.ZoomifyTierSizeCalculation.TRUNCATED:
      var width = imageWidth;
      var height = imageHeight;
      while (width > tileSize || height > tileSize) {
        tierSizeInTiles.push([
          Math.ceil(width / tileSize),
          Math.ceil(height / tileSize)
        ]);
        width >>= 1;
        height >>= 1;
      }
      break;
    default:
      ol.asserts.assert(false, 53); // Unknown `tierSizeCalculation` configured
      break;
  }

  tierSizeInTiles.push([1, 1]);
  tierSizeInTiles.reverse();

  var resolutions = [1];
  var tileCountUpToTier = [0];
  var i, ii;
  for (i = 1, ii = tierSizeInTiles.length; i < ii; i++) {
    resolutions.push(1 << i);
    tileCountUpToTier.push(
        tierSizeInTiles[i - 1][0] * tierSizeInTiles[i - 1][1] +
        tileCountUpToTier[i - 1]
    );
  }
  resolutions.reverse();

  var extent = [0, -size[1], size[0], 0];
  var tileGrid = new ol.tilegrid.TileGrid({
    extent: extent,
    origin: ol.extent.getTopLeft(extent),
    resolutions: resolutions
  });

  var url = options.url;

  /**
   * @this {ol.source.TileImage}
   * @param {ol.TileCoord} tileCoord Tile Coordinate.
   * @param {number} pixelRatio Pixel ratio.
   * @param {ol.proj.Projection} projection Projection.
   * @return {string|undefined} Tile URL.
   */
  function tileUrlFunction(tileCoord, pixelRatio, projection) {
    if (!tileCoord) {
      return undefined;
    } else {
      var tileCoordZ = tileCoord[0];
      var tileCoordX = tileCoord[1];
      var tileCoordY = -tileCoord[2] - 1;
      var tileIndex =
          tileCoordX +
          tileCoordY * tierSizeInTiles[tileCoordZ][0] +
          tileCountUpToTier[tileCoordZ];
      var tileGroup = (tileIndex / ol.DEFAULT_TILE_SIZE) | 0;
      return url + 'TileGroup' + tileGroup + '/' +
          tileCoordZ + '-' + tileCoordX + '-' + tileCoordY + '.jpg';
    }
  }

  ol.source.TileImage.call(this, {
    attributions: options.attributions,
    cacheSize: options.cacheSize,
    crossOrigin: options.crossOrigin,
    logo: options.logo,
    reprojectionErrorThreshold: options.reprojectionErrorThreshold,
    tileClass: ol.source.ZoomifyTile_,
    tileGrid: tileGrid,
    tileUrlFunction: tileUrlFunction
  });

};
ol.inherits(ol.source.Zoomify, ol.source.TileImage);


/**
 * @constructor
 * @extends {ol.ImageTile}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Tile.State} state State.
 * @param {string} src Image source URI.
 * @param {?string} crossOrigin Cross origin.
 * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function.
 * @private
 */
ol.source.ZoomifyTile_ = function(
    tileCoord, state, src, crossOrigin, tileLoadFunction) {

  ol.ImageTile.call(this, tileCoord, state, src, crossOrigin, tileLoadFunction);

  /**
   * @private
   * @type {Object.<string,
   *                HTMLCanvasElement|HTMLImageElement|HTMLVideoElement>}
   */
  this.zoomifyImageByContext_ = {};

};
ol.inherits(ol.source.ZoomifyTile_, ol.ImageTile);


/**
 * @inheritDoc
 */
ol.source.ZoomifyTile_.prototype.getImage = function(opt_context) {
  var tileSize = ol.DEFAULT_TILE_SIZE;
  var key = opt_context !== undefined ?
      ol.getUid(opt_context).toString() : '';
  if (key in this.zoomifyImageByContext_) {
    return this.zoomifyImageByContext_[key];
  } else {
    var image = ol.ImageTile.prototype.getImage.call(this, opt_context);
    if (this.state == ol.Tile.State.LOADED) {
      if (image.width == tileSize && image.height == tileSize) {
        this.zoomifyImageByContext_[key] = image;
        return image;
      } else {
        var context = ol.dom.createCanvasContext2D(tileSize, tileSize);
        context.drawImage(image, 0, 0);
        this.zoomifyImageByContext_[key] = context.canvas;
        return context.canvas;
      }
    } else {
      return image;
    }
  }
};

goog.provide('ol.style.AtlasManager');
goog.provide('ol.style.Atlas');

goog.require('ol');
goog.require('ol.dom');


/**
 * Manages the creation of image atlases.
 *
 * Images added to this manager will be inserted into an atlas, which
 * will be used for rendering.
 * The `size` given in the constructor is the size for the first
 * atlas. After that, when new atlases are created, they will have
 * twice the size as the latest atlas (until `maxSize` is reached).
 *
 * If an application uses many images or very large images, it is recommended
 * to set a higher `size` value to avoid the creation of too many atlases.
 *
 * @constructor
 * @struct
 * @api
 * @param {olx.style.AtlasManagerOptions=} opt_options Options.
 */
ol.style.AtlasManager = function(opt_options) {

  var options = opt_options || {};

  /**
   * The size in pixels of the latest atlas image.
   * @private
   * @type {number}
   */
  this.currentSize_ = options.initialSize !== undefined ?
      options.initialSize : ol.INITIAL_ATLAS_SIZE;

  /**
   * The maximum size in pixels of atlas images.
   * @private
   * @type {number}
   */
  this.maxSize_ = options.maxSize !== undefined ?
      options.maxSize : ol.MAX_ATLAS_SIZE != -1 ?
          ol.MAX_ATLAS_SIZE : ol.WEBGL_MAX_TEXTURE_SIZE !== undefined ?
              ol.WEBGL_MAX_TEXTURE_SIZE : 2048;

  /**
   * The size in pixels between images.
   * @private
   * @type {number}
   */
  this.space_ = options.space !== undefined ? options.space : 1;

  /**
   * @private
   * @type {Array.<ol.style.Atlas>}
   */
  this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];

  /**
   * The size in pixels of the latest atlas image for hit-detection images.
   * @private
   * @type {number}
   */
  this.currentHitSize_ = this.currentSize_;

  /**
   * @private
   * @type {Array.<ol.style.Atlas>}
   */
  this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
};


/**
 * @param {string} id The identifier of the entry to check.
 * @return {?ol.AtlasManagerInfo} The position and atlas image for the
 *    entry, or `null` if the entry is not part of the atlas manager.
 */
ol.style.AtlasManager.prototype.getInfo = function(id) {
  /** @type {?ol.AtlasInfo} */
  var info = this.getInfo_(this.atlases_, id);

  if (!info) {
    return null;
  }
  var hitInfo = /** @type {ol.AtlasInfo} */ (this.getInfo_(this.hitAtlases_, id));

  return this.mergeInfos_(info, hitInfo);
};


/**
 * @private
 * @param {Array.<ol.style.Atlas>} atlases The atlases to search.
 * @param {string} id The identifier of the entry to check.
 * @return {?ol.AtlasInfo} The position and atlas image for the entry,
 *    or `null` if the entry is not part of the atlases.
 */
ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
  var atlas, info, i, ii;
  for (i = 0, ii = atlases.length; i < ii; ++i) {
    atlas = atlases[i];
    info = atlas.get(id);
    if (info) {
      return info;
    }
  }
  return null;
};


/**
 * @private
 * @param {ol.AtlasInfo} info The info for the real image.
 * @param {ol.AtlasInfo} hitInfo The info for the hit-detection
 *    image.
 * @return {?ol.AtlasManagerInfo} The position and atlas image for the
 *    entry, or `null` if the entry is not part of the atlases.
 */
ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
  goog.DEBUG && console.assert(info.offsetX === hitInfo.offsetX,
      'in order to merge, offsetX of info and hitInfo must be equal');
  goog.DEBUG && console.assert(info.offsetY === hitInfo.offsetY,
      'in order to merge, offsetY of info and hitInfo must be equal');
  return /** @type {ol.AtlasManagerInfo} */ ({
    offsetX: info.offsetX,
    offsetY: info.offsetY,
    image: info.image,
    hitImage: hitInfo.image
  });
};


/**
 * Add an image to the atlas manager.
 *
 * If an entry for the given id already exists, the entry will
 * be overridden (but the space on the atlas graphic will not be freed).
 *
 * If `renderHitCallback` is provided, the image (or the hit-detection version
 * of the image) will be rendered into a separate hit-detection atlas image.
 *
 * @param {string} id The identifier of the entry to add.
 * @param {number} width The width.
 * @param {number} height The height.
 * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
 *    Called to render the new image onto an atlas image.
 * @param {function(CanvasRenderingContext2D, number, number)=}
 *    opt_renderHitCallback Called to render a hit-detection image onto a hit
 *    detection atlas image.
 * @param {Object=} opt_this Value to use as `this` when executing
 *    `renderCallback` and `renderHitCallback`.
 * @return {?ol.AtlasManagerInfo}  The position and atlas image for the
 *    entry, or `null` if the image is too big.
 */
ol.style.AtlasManager.prototype.add = function(id, width, height,
        renderCallback, opt_renderHitCallback, opt_this) {
  if (width + this.space_ > this.maxSize_ ||
      height + this.space_ > this.maxSize_) {
    return null;
  }

  /** @type {?ol.AtlasInfo} */
  var info = this.add_(false,
      id, width, height, renderCallback, opt_this);
  if (!info) {
    return null;
  }

  // even if no hit-detection entry is requested, we insert a fake entry into
  // the hit-detection atlas, to make sure that the offset is the same for
  // the original image and the hit-detection image.
  var renderHitCallback = opt_renderHitCallback !== undefined ?
      opt_renderHitCallback : ol.nullFunction;

  var hitInfo = /** @type {ol.AtlasInfo} */ (this.add_(true,
      id, width, height, renderHitCallback, opt_this));

  return this.mergeInfos_(info, hitInfo);
};


/**
 * @private
 * @param {boolean} isHitAtlas If the hit-detection atlases are used.
 * @param {string} id The identifier of the entry to add.
 * @param {number} width The width.
 * @param {number} height The height.
 * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
 *    Called to render the new image onto an atlas image.
 * @param {Object=} opt_this Value to use as `this` when executing
 *    `renderCallback` and `renderHitCallback`.
 * @return {?ol.AtlasInfo}  The position and atlas image for the entry,
 *    or `null` if the image is too big.
 */
ol.style.AtlasManager.prototype.add_ = function(isHitAtlas, id, width, height,
        renderCallback, opt_this) {
  var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
  var atlas, info, i, ii;
  for (i = 0, ii = atlases.length; i < ii; ++i) {
    atlas = atlases[i];
    info = atlas.add(id, width, height, renderCallback, opt_this);
    if (info) {
      return info;
    } else if (!info && i === ii - 1) {
      // the entry could not be added to one of the existing atlases,
      // create a new atlas that is twice as big and try to add to this one.
      var size;
      if (isHitAtlas) {
        size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
        this.currentHitSize_ = size;
      } else {
        size = Math.min(this.currentSize_ * 2, this.maxSize_);
        this.currentSize_ = size;
      }
      atlas = new ol.style.Atlas(size, this.space_);
      atlases.push(atlas);
      // run the loop another time
      ++ii;
    }
  }
  goog.DEBUG && console.assert(false, 'Failed to add to atlasmanager');
  return null;
};


/**
 * This class facilitates the creation of image atlases.
 *
 * Images added to an atlas will be rendered onto a single
 * atlas canvas. The distribution of images on the canvas is
 * managed with the bin packing algorithm described in:
 * http://www.blackpawn.com/texts/lightmaps/
 *
 * @constructor
 * @struct
 * @param {number} size The size in pixels of the sprite image.
 * @param {number} space The space in pixels between images.
 *    Because texture coordinates are float values, the edges of
 *    images might not be completely correct (in a way that the
 *    edges overlap when being rendered). To avoid this we add a
 *    padding around each image.
 */
ol.style.Atlas = function(size, space) {

  /**
   * @private
   * @type {number}
   */
  this.space_ = space;

  /**
   * @private
   * @type {Array.<ol.AtlasBlock>}
   */
  this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];

  /**
   * @private
   * @type {Object.<string, ol.AtlasInfo>}
   */
  this.entries_ = {};

  /**
   * @private
   * @type {CanvasRenderingContext2D}
   */
  this.context_ = ol.dom.createCanvasContext2D(size, size);

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = this.context_.canvas;
};


/**
 * @param {string} id The identifier of the entry to check.
 * @return {?ol.AtlasInfo} The atlas info.
 */
ol.style.Atlas.prototype.get = function(id) {
  return this.entries_[id] || null;
};


/**
 * @param {string} id The identifier of the entry to add.
 * @param {number} width The width.
 * @param {number} height The height.
 * @param {function(CanvasRenderingContext2D, number, number)} renderCallback
 *    Called to render the new image onto an atlas image.
 * @param {Object=} opt_this Value to use as `this` when executing
 *    `renderCallback`.
 * @return {?ol.AtlasInfo} The position and atlas image for the entry.
 */
ol.style.Atlas.prototype.add = function(id, width, height, renderCallback, opt_this) {
  var block, i, ii;
  for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
    block = this.emptyBlocks_[i];
    if (block.width >= width + this.space_ &&
        block.height >= height + this.space_) {
      // we found a block that is big enough for our entry
      var entry = {
        offsetX: block.x + this.space_,
        offsetY: block.y + this.space_,
        image: this.canvas_
      };
      this.entries_[id] = entry;

      // render the image on the atlas image
      renderCallback.call(opt_this, this.context_,
          block.x + this.space_, block.y + this.space_);

      // split the block after the insertion, either horizontally or vertically
      this.split_(i, block, width + this.space_, height + this.space_);

      return entry;
    }
  }

  // there is no space for the new entry in this atlas
  return null;
};


/**
 * @private
 * @param {number} index The index of the block.
 * @param {ol.AtlasBlock} block The block to split.
 * @param {number} width The width of the entry to insert.
 * @param {number} height The height of the entry to insert.
 */
ol.style.Atlas.prototype.split_ = function(index, block, width, height) {
  var deltaWidth = block.width - width;
  var deltaHeight = block.height - height;

  /** @type {ol.AtlasBlock} */
  var newBlock1;
  /** @type {ol.AtlasBlock} */
  var newBlock2;

  if (deltaWidth > deltaHeight) {
    // split vertically
    // block right of the inserted entry
    newBlock1 = {
      x: block.x + width,
      y: block.y,
      width: block.width - width,
      height: block.height
    };

    // block below the inserted entry
    newBlock2 = {
      x: block.x,
      y: block.y + height,
      width: width,
      height: block.height - height
    };
    this.updateBlocks_(index, newBlock1, newBlock2);
  } else {
    // split horizontally
    // block right of the inserted entry
    newBlock1 = {
      x: block.x + width,
      y: block.y,
      width: block.width - width,
      height: height
    };

    // block below the inserted entry
    newBlock2 = {
      x: block.x,
      y: block.y + height,
      width: block.width,
      height: block.height - height
    };
    this.updateBlocks_(index, newBlock1, newBlock2);
  }
};


/**
 * Remove the old block and insert new blocks at the same array position.
 * The new blocks are inserted at the same position, so that splitted
 * blocks (that are potentially smaller) are filled first.
 * @private
 * @param {number} index The index of the block to remove.
 * @param {ol.AtlasBlock} newBlock1 The 1st block to add.
 * @param {ol.AtlasBlock} newBlock2 The 2nd block to add.
 */
ol.style.Atlas.prototype.updateBlocks_ = function(index, newBlock1, newBlock2) {
  var args = [index, 1];
  if (newBlock1.width > 0 && newBlock1.height > 0) {
    args.push(newBlock1);
  }
  if (newBlock2.width > 0 && newBlock2.height > 0) {
    args.push(newBlock2);
  }
  this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
};

goog.provide('ol.style.RegularShape');

goog.require('ol');
goog.require('ol.color');
goog.require('ol.colorlike');
goog.require('ol.dom');
goog.require('ol.has');
goog.require('ol.render.canvas');
goog.require('ol.style.Image');
goog.require('ol.style.ImageState');


/**
 * @classdesc
 * Set regular shape style for vector features. The resulting shape will be
 * a regular polygon when `radius` is provided, or a star when `radius1` and
 * `radius2` are provided.
 *
 * @constructor
 * @param {olx.style.RegularShapeOptions} options Options.
 * @extends {ol.style.Image}
 * @api
 */
ol.style.RegularShape = function(options) {

  goog.DEBUG && console.assert(
      options.radius !== undefined || options.radius1 !== undefined,
      'must provide either "radius" or "radius1"');

  /**
   * @private
   * @type {Array.<string>}
   */
  this.checksums_ = null;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.canvas_ = null;

  /**
   * @private
   * @type {HTMLCanvasElement}
   */
  this.hitDetectionCanvas_ = null;

  /**
   * @private
   * @type {ol.style.Fill}
   */
  this.fill_ = options.fill !== undefined ? options.fill : null;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.origin_ = [0, 0];

  /**
   * @private
   * @type {number}
   */
  this.points_ = options.points;

  /**
   * @private
   * @type {number}
   */
  this.radius_ = /** @type {number} */ (options.radius !== undefined ?
      options.radius : options.radius1);

  /**
   * @private
   * @type {number}
   */
  this.radius2_ =
      options.radius2 !== undefined ? options.radius2 : this.radius_;

  /**
   * @private
   * @type {number}
   */
  this.angle_ = options.angle !== undefined ? options.angle : 0;

  /**
   * @private
   * @type {ol.style.Stroke}
   */
  this.stroke_ = options.stroke !== undefined ? options.stroke : null;

  /**
   * @private
   * @type {Array.<number>}
   */
  this.anchor_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.size_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.imageSize_ = null;

  /**
   * @private
   * @type {ol.Size}
   */
  this.hitDetectionImageSize_ = null;

  this.render_(options.atlasManager);

  /**
   * @type {boolean}
   */
  var snapToPixel = options.snapToPixel !== undefined ?
      options.snapToPixel : true;

  /**
   * @type {boolean}
   */
  var rotateWithView = options.rotateWithView !== undefined ?
      options.rotateWithView : false;

  ol.style.Image.call(this, {
    opacity: 1,
    rotateWithView: rotateWithView,
    rotation: options.rotation !== undefined ? options.rotation : 0,
    scale: 1,
    snapToPixel: snapToPixel
  });

};
ol.inherits(ol.style.RegularShape, ol.style.Image);


/**
 * @inheritDoc
 * @api
 */
ol.style.RegularShape.prototype.getAnchor = function() {
  return this.anchor_;
};


/**
 * Get the angle used in generating the shape.
 * @return {number} Shape's rotation in radians.
 * @api
 */
ol.style.RegularShape.prototype.getAngle = function() {
  return this.angle_;
};


/**
 * Get the fill style for the shape.
 * @return {ol.style.Fill} Fill style.
 * @api
 */
ol.style.RegularShape.prototype.getFill = function() {
  return this.fill_;
};


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.getHitDetectionImage = function(pixelRatio) {
  return this.hitDetectionCanvas_;
};


/**
 * @inheritDoc
 * @api
 */
ol.style.RegularShape.prototype.getImage = function(pixelRatio) {
  return this.canvas_;
};


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.getImageSize = function() {
  return this.imageSize_;
};


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.getHitDetectionImageSize = function() {
  return this.hitDetectionImageSize_;
};


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.getImageState = function() {
  return ol.style.ImageState.LOADED;
};


/**
 * @inheritDoc
 * @api
 */
ol.style.RegularShape.prototype.getOrigin = function() {
  return this.origin_;
};


/**
 * Get the number of points for generating the shape.
 * @return {number} Number of points for stars and regular polygons.
 * @api
 */
ol.style.RegularShape.prototype.getPoints = function() {
  return this.points_;
};


/**
 * Get the (primary) radius for the shape.
 * @return {number} Radius.
 * @api
 */
ol.style.RegularShape.prototype.getRadius = function() {
  return this.radius_;
};


/**
 * Get the secondary radius for the shape.
 * @return {number} Radius2.
 * @api
 */
ol.style.RegularShape.prototype.getRadius2 = function() {
  return this.radius2_;
};


/**
 * @inheritDoc
 * @api
 */
ol.style.RegularShape.prototype.getSize = function() {
  return this.size_;
};


/**
 * Get the stroke style for the shape.
 * @return {ol.style.Stroke} Stroke style.
 * @api
 */
ol.style.RegularShape.prototype.getStroke = function() {
  return this.stroke_;
};


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.listenImageChange = ol.nullFunction;


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.load = ol.nullFunction;


/**
 * @inheritDoc
 */
ol.style.RegularShape.prototype.unlistenImageChange = ol.nullFunction;


/**
 * @private
 * @param {ol.style.AtlasManager|undefined} atlasManager An atlas manager.
 */
ol.style.RegularShape.prototype.render_ = function(atlasManager) {
  var imageSize;
  var lineCap = '';
  var lineJoin = '';
  var miterLimit = 0;
  var lineDash = null;
  var strokeStyle;
  var strokeWidth = 0;

  if (this.stroke_) {
    strokeStyle = ol.color.asString(this.stroke_.getColor());
    strokeWidth = this.stroke_.getWidth();
    if (strokeWidth === undefined) {
      strokeWidth = ol.render.canvas.defaultLineWidth;
    }
    lineDash = this.stroke_.getLineDash();
    if (!ol.has.CANVAS_LINE_DASH) {
      lineDash = null;
    }
    lineJoin = this.stroke_.getLineJoin();
    if (lineJoin === undefined) {
      lineJoin = ol.render.canvas.defaultLineJoin;
    }
    lineCap = this.stroke_.getLineCap();
    if (lineCap === undefined) {
      lineCap = ol.render.canvas.defaultLineCap;
    }
    miterLimit = this.stroke_.getMiterLimit();
    if (miterLimit === undefined) {
      miterLimit = ol.render.canvas.defaultMiterLimit;
    }
  }

  var size = 2 * (this.radius_ + strokeWidth) + 1;

  /** @type {ol.RegularShapeRenderOptions} */
  var renderOptions = {
    strokeStyle: strokeStyle,
    strokeWidth: strokeWidth,
    size: size,
    lineCap: lineCap,
    lineDash: lineDash,
    lineJoin: lineJoin,
    miterLimit: miterLimit
  };

  if (atlasManager === undefined) {
    // no atlas manager is used, create a new canvas
    var context = ol.dom.createCanvasContext2D(size, size);
    this.canvas_ = context.canvas;

    // canvas.width and height are rounded to the closest integer
    size = this.canvas_.width;
    imageSize = size;

    this.draw_(renderOptions, context, 0, 0);

    this.createHitDetectionCanvas_(renderOptions);
  } else {
    // an atlas manager is used, add the symbol to an atlas
    size = Math.round(size);

    var hasCustomHitDetectionImage = !this.fill_;
    var renderHitDetectionCallback;
    if (hasCustomHitDetectionImage) {
      // render the hit-detection image into a separate atlas image
      renderHitDetectionCallback =
          this.drawHitDetectionCanvas_.bind(this, renderOptions);
    }

    var id = this.getChecksum();
    var info = atlasManager.add(
        id, size, size, this.draw_.bind(this, renderOptions),
        renderHitDetectionCallback);
    goog.DEBUG && console.assert(info, 'shape size is too large');

    this.canvas_ = info.image;
    this.origin_ = [info.offsetX, info.offsetY];
    imageSize = info.image.width;

    if (hasCustomHitDetectionImage) {
      this.hitDetectionCanvas_ = info.hitImage;
      this.hitDetectionImageSize_ =
          [info.hitImage.width, info.hitImage.height];
    } else {
      this.hitDetectionCanvas_ = this.canvas_;
      this.hitDetectionImageSize_ = [imageSize, imageSize];
    }
  }

  this.anchor_ = [size / 2, size / 2];
  this.size_ = [size, size];
  this.imageSize_ = [imageSize, imageSize];
};


/**
 * @private
 * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
 * @param {CanvasRenderingContext2D} context The rendering context.
 * @param {number} x The origin for the symbol (x).
 * @param {number} y The origin for the symbol (y).
 */
ol.style.RegularShape.prototype.draw_ = function(renderOptions, context, x, y) {
  var i, angle0, radiusC;
  // reset transform
  context.setTransform(1, 0, 0, 1, 0, 0);

  // then move to (x, y)
  context.translate(x, y);

  context.beginPath();
  if (this.radius2_ !== this.radius_) {
    this.points_ = 2 * this.points_;
  }
  for (i = 0; i <= this.points_; i++) {
    angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
    radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
    context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
                   renderOptions.size / 2 + radiusC * Math.sin(angle0));
  }

  if (this.fill_) {
    context.fillStyle = ol.colorlike.asColorLike(this.fill_.getColor());
    context.fill();
  }
  if (this.stroke_) {
    context.strokeStyle = renderOptions.strokeStyle;
    context.lineWidth = renderOptions.strokeWidth;
    if (renderOptions.lineDash) {
      context.setLineDash(renderOptions.lineDash);
    }
    context.lineCap = renderOptions.lineCap;
    context.lineJoin = renderOptions.lineJoin;
    context.miterLimit = renderOptions.miterLimit;
    context.stroke();
  }
  context.closePath();
};


/**
 * @private
 * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
 */
ol.style.RegularShape.prototype.createHitDetectionCanvas_ = function(renderOptions) {
  this.hitDetectionImageSize_ = [renderOptions.size, renderOptions.size];
  if (this.fill_) {
    this.hitDetectionCanvas_ = this.canvas_;
    return;
  }

  // if no fill style is set, create an extra hit-detection image with a
  // default fill style
  var context = ol.dom.createCanvasContext2D(renderOptions.size, renderOptions.size);
  this.hitDetectionCanvas_ = context.canvas;

  this.drawHitDetectionCanvas_(renderOptions, context, 0, 0);
};


/**
 * @private
 * @param {ol.RegularShapeRenderOptions} renderOptions Render options.
 * @param {CanvasRenderingContext2D} context The context.
 * @param {number} x The origin for the symbol (x).
 * @param {number} y The origin for the symbol (y).
 */
ol.style.RegularShape.prototype.drawHitDetectionCanvas_ = function(renderOptions, context, x, y) {
  // reset transform
  context.setTransform(1, 0, 0, 1, 0, 0);

  // then move to (x, y)
  context.translate(x, y);

  context.beginPath();
  if (this.radius2_ !== this.radius_) {
    this.points_ = 2 * this.points_;
  }
  var i, radiusC, angle0;
  for (i = 0; i <= this.points_; i++) {
    angle0 = i * 2 * Math.PI / this.points_ - Math.PI / 2 + this.angle_;
    radiusC = i % 2 === 0 ? this.radius_ : this.radius2_;
    context.lineTo(renderOptions.size / 2 + radiusC * Math.cos(angle0),
                   renderOptions.size / 2 + radiusC * Math.sin(angle0));
  }

  context.fillStyle = ol.render.canvas.defaultFillStyle;
  context.fill();
  if (this.stroke_) {
    context.strokeStyle = renderOptions.strokeStyle;
    context.lineWidth = renderOptions.strokeWidth;
    if (renderOptions.lineDash) {
      context.setLineDash(renderOptions.lineDash);
    }
    context.stroke();
  }
  context.closePath();
};


/**
 * @return {string} The checksum.
 */
ol.style.RegularShape.prototype.getChecksum = function() {
  var strokeChecksum = this.stroke_ ?
      this.stroke_.getChecksum() : '-';
  var fillChecksum = this.fill_ ?
      this.fill_.getChecksum() : '-';

  var recalculate = !this.checksums_ ||
      (strokeChecksum != this.checksums_[1] ||
      fillChecksum != this.checksums_[2] ||
      this.radius_ != this.checksums_[3] ||
      this.radius2_ != this.checksums_[4] ||
      this.angle_ != this.checksums_[5] ||
      this.points_ != this.checksums_[6]);

  if (recalculate) {
    var checksum = 'r' + strokeChecksum + fillChecksum +
        (this.radius_ !== undefined ? this.radius_.toString() : '-') +
        (this.radius2_ !== undefined ? this.radius2_.toString() : '-') +
        (this.angle_ !== undefined ? this.angle_.toString() : '-') +
        (this.points_ !== undefined ? this.points_.toString() : '-');
    this.checksums_ = [checksum, strokeChecksum, fillChecksum,
      this.radius_, this.radius2_, this.angle_, this.points_];
  }

  return this.checksums_[0];
};

// Copyright 2009 The Closure Library Authors.
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file 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.
//
// This file has been auto-generated by GenJsDeps, please do not edit.

goog.addDependency(
    'demos/editor/equationeditor.js', ['goog.demos.editor.EquationEditor'],
    ['goog.ui.equation.EquationEditorDialog']);
goog.addDependency(
    'demos/editor/helloworld.js', ['goog.demos.editor.HelloWorld'],
    ['goog.dom', 'goog.dom.TagName', 'goog.editor.Plugin']);
goog.addDependency(
    'demos/editor/helloworlddialog.js',
    [
      'goog.demos.editor.HelloWorldDialog',
      'goog.demos.editor.HelloWorldDialog.OkEvent'
    ],
    [
      'goog.dom.TagName', 'goog.events.Event', 'goog.string',
      'goog.ui.editor.AbstractDialog', 'goog.ui.editor.AbstractDialog.Builder',
      'goog.ui.editor.AbstractDialog.EventType'
    ]);
goog.addDependency(
    'demos/editor/helloworlddialogplugin.js',
    [
      'goog.demos.editor.HelloWorldDialogPlugin',
      'goog.demos.editor.HelloWorldDialogPlugin.Command'
    ],
    [
      'goog.demos.editor.HelloWorldDialog', 'goog.dom.TagName',
      'goog.editor.plugins.AbstractDialogPlugin', 'goog.editor.range',
      'goog.functions', 'goog.ui.editor.AbstractDialog.EventType'
    ]);

/**
 * @fileoverview Custom exports file.
 * @suppress {checkVars,extraRequire}
 */

goog.require('ol');
goog.require('ol.AssertionError');
goog.require('ol.Attribution');
goog.require('ol.Collection');
goog.require('ol.DeviceOrientation');
goog.require('ol.DragBoxEvent');
goog.require('ol.Feature');
goog.require('ol.Geolocation');
goog.require('ol.Graticule');
goog.require('ol.Image');
goog.require('ol.ImageTile');
goog.require('ol.Kinetic');
goog.require('ol.Map');
goog.require('ol.MapBrowserEvent');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.MapBrowserEventHandler');
goog.require('ol.MapBrowserPointerEvent');
goog.require('ol.MapEvent');
goog.require('ol.MapEventType');
goog.require('ol.MapProperty');
goog.require('ol.Object');
goog.require('ol.ObjectEvent');
goog.require('ol.ObjectEventType');
goog.require('ol.Observable');
goog.require('ol.Overlay');
goog.require('ol.OverlayPositioning');
goog.require('ol.RasterOperationType');
goog.require('ol.Sphere');
goog.require('ol.Tile');
goog.require('ol.VectorTile');
goog.require('ol.View');
goog.require('ol.animation');
goog.require('ol.color');
goog.require('ol.colorlike');
goog.require('ol.control');
goog.require('ol.control.Attribution');
goog.require('ol.control.Control');
goog.require('ol.control.FullScreen');
goog.require('ol.control.MousePosition');
goog.require('ol.control.OverviewMap');
goog.require('ol.control.Rotate');
goog.require('ol.control.ScaleLine');
goog.require('ol.control.Zoom');
goog.require('ol.control.ZoomSlider');
goog.require('ol.control.ZoomToExtent');
goog.require('ol.coordinate');
goog.require('ol.easing');
goog.require('ol.events.Event');
goog.require('ol.events.condition');
goog.require('ol.extent');
goog.require('ol.extent.Corner');
goog.require('ol.extent.Relationship');
goog.require('ol.featureloader');
goog.require('ol.format.EsriJSON');
goog.require('ol.format.Feature');
goog.require('ol.format.GML');
goog.require('ol.format.GML2');
goog.require('ol.format.GML3');
goog.require('ol.format.GMLBase');
goog.require('ol.format.GPX');
goog.require('ol.format.GeoJSON');
goog.require('ol.format.IGC');
goog.require('ol.format.IGCZ');
goog.require('ol.format.KML');
goog.require('ol.format.MVT');
goog.require('ol.format.OSMXML');
goog.require('ol.format.Polyline');
goog.require('ol.format.TopoJSON');
goog.require('ol.format.WFS');
goog.require('ol.format.WKT');
goog.require('ol.format.WMSCapabilities');
goog.require('ol.format.WMSGetFeatureInfo');
goog.require('ol.format.WMTSCapabilities');
goog.require('ol.format.ogc.filter');
goog.require('ol.format.ogc.filter.And');
goog.require('ol.format.ogc.filter.Bbox');
goog.require('ol.format.ogc.filter.Comparison');
goog.require('ol.format.ogc.filter.ComparisonBinary');
goog.require('ol.format.ogc.filter.EqualTo');
goog.require('ol.format.ogc.filter.Filter');
goog.require('ol.format.ogc.filter.GreaterThan');
goog.require('ol.format.ogc.filter.GreaterThanOrEqualTo');
goog.require('ol.format.ogc.filter.Intersects');
goog.require('ol.format.ogc.filter.IsBetween');
goog.require('ol.format.ogc.filter.IsLike');
goog.require('ol.format.ogc.filter.IsNull');
goog.require('ol.format.ogc.filter.LessThan');
goog.require('ol.format.ogc.filter.LessThanOrEqualTo');
goog.require('ol.format.ogc.filter.Not');
goog.require('ol.format.ogc.filter.NotEqualTo');
goog.require('ol.format.ogc.filter.Or');
goog.require('ol.format.ogc.filter.Spatial');
goog.require('ol.format.ogc.filter.Within');
goog.require('ol.geom.Circle');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryCollection');
goog.require('ol.geom.GeometryLayout');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.LinearRing');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.geom.SimpleGeometry');
goog.require('ol.has');
goog.require('ol.interaction');
goog.require('ol.interaction.DoubleClickZoom');
goog.require('ol.interaction.DragAndDrop');
goog.require('ol.interaction.DragAndDropEvent');
goog.require('ol.interaction.DragBox');
goog.require('ol.interaction.DragPan');
goog.require('ol.interaction.DragRotate');
goog.require('ol.interaction.DragRotateAndZoom');
goog.require('ol.interaction.DragZoom');
goog.require('ol.interaction.Draw');
goog.require('ol.interaction.DrawEvent');
goog.require('ol.interaction.DrawEventType');
goog.require('ol.interaction.DrawMode');
goog.require('ol.interaction.Interaction');
goog.require('ol.interaction.InteractionProperty');
goog.require('ol.interaction.KeyboardPan');
goog.require('ol.interaction.KeyboardZoom');
goog.require('ol.interaction.Modify');
goog.require('ol.interaction.ModifyEvent');
goog.require('ol.interaction.MouseWheelZoom');
goog.require('ol.interaction.PinchRotate');
goog.require('ol.interaction.PinchZoom');
goog.require('ol.interaction.Pointer');
goog.require('ol.interaction.Select');
goog.require('ol.interaction.SelectEvent');
goog.require('ol.interaction.SelectEventType');
goog.require('ol.interaction.Snap');
goog.require('ol.interaction.SnapProperty');
goog.require('ol.interaction.Translate');
goog.require('ol.interaction.TranslateEvent');
goog.require('ol.layer.Base');
goog.require('ol.layer.Group');
goog.require('ol.layer.Heatmap');
goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.LayerProperty');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorTile');
goog.require('ol.loadingstrategy');
goog.require('ol.proj');
goog.require('ol.proj.METERS_PER_UNIT');
goog.require('ol.proj.Projection');
goog.require('ol.proj.Units');
goog.require('ol.proj.common');
goog.require('ol.render');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.Feature');
goog.require('ol.render.VectorContext');
goog.require('ol.render.canvas.Immediate');
goog.require('ol.render.webgl.Immediate');
goog.require('ol.size');
goog.require('ol.source.BingMaps');
goog.require('ol.source.CartoDB');
goog.require('ol.source.Cluster');
goog.require('ol.source.Image');
goog.require('ol.source.ImageArcGISRest');
goog.require('ol.source.ImageCanvas');
goog.require('ol.source.ImageEvent');
goog.require('ol.source.ImageMapGuide');
goog.require('ol.source.ImageStatic');
goog.require('ol.source.ImageVector');
goog.require('ol.source.ImageWMS');
goog.require('ol.source.OSM');
goog.require('ol.source.Raster');
goog.require('ol.source.RasterEvent');
goog.require('ol.source.RasterEventType');
goog.require('ol.source.Source');
goog.require('ol.source.Stamen');
goog.require('ol.source.State');
goog.require('ol.source.Tile');
goog.require('ol.source.TileArcGISRest');
goog.require('ol.source.TileDebug');
goog.require('ol.source.TileEvent');
goog.require('ol.source.TileImage');
goog.require('ol.source.TileJSON');
goog.require('ol.source.TileUTFGrid');
goog.require('ol.source.TileWMS');
goog.require('ol.source.UrlTile');
goog.require('ol.source.Vector');
goog.require('ol.source.VectorEvent');
goog.require('ol.source.VectorEventType');
goog.require('ol.source.VectorTile');
goog.require('ol.source.WMTS');
goog.require('ol.source.WMTSRequestEncoding');
goog.require('ol.source.XYZ');
goog.require('ol.source.Zoomify');
goog.require('ol.style.Atlas');
goog.require('ol.style.AtlasManager');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Icon');
goog.require('ol.style.IconAnchorUnits');
goog.require('ol.style.IconOrigin');
goog.require('ol.style.Image');
goog.require('ol.style.ImageState');
goog.require('ol.style.RegularShape');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
goog.require('ol.style.Text');
goog.require('ol.tilegrid');
goog.require('ol.tilegrid.TileGrid');
goog.require('ol.tilegrid.WMTS');
goog.require('ol.tilejson');
goog.require('ol.webgl.Context');
goog.require('ol.xml');


goog.exportSymbol(
    'ol.animation.bounce',
    ol.animation.bounce,
    OPENLAYERS);

goog.exportSymbol(
    'ol.animation.pan',
    ol.animation.pan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.animation.rotate',
    ol.animation.rotate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.animation.zoom',
    ol.animation.zoom,
    OPENLAYERS);

goog.exportProperty(
    ol.AssertionError.prototype,
    'code',
    ol.AssertionError.prototype.code);

goog.exportSymbol(
    'ol.Attribution',
    ol.Attribution,
    OPENLAYERS);

goog.exportProperty(
    ol.Attribution.prototype,
    'getHTML',
    ol.Attribution.prototype.getHTML);

goog.exportSymbol(
    'ol.Collection',
    ol.Collection,
    OPENLAYERS);

goog.exportProperty(
    ol.Collection.prototype,
    'clear',
    ol.Collection.prototype.clear);

goog.exportProperty(
    ol.Collection.prototype,
    'extend',
    ol.Collection.prototype.extend);

goog.exportProperty(
    ol.Collection.prototype,
    'forEach',
    ol.Collection.prototype.forEach);

goog.exportProperty(
    ol.Collection.prototype,
    'getArray',
    ol.Collection.prototype.getArray);

goog.exportProperty(
    ol.Collection.prototype,
    'item',
    ol.Collection.prototype.item);

goog.exportProperty(
    ol.Collection.prototype,
    'getLength',
    ol.Collection.prototype.getLength);

goog.exportProperty(
    ol.Collection.prototype,
    'insertAt',
    ol.Collection.prototype.insertAt);

goog.exportProperty(
    ol.Collection.prototype,
    'pop',
    ol.Collection.prototype.pop);

goog.exportProperty(
    ol.Collection.prototype,
    'push',
    ol.Collection.prototype.push);

goog.exportProperty(
    ol.Collection.prototype,
    'remove',
    ol.Collection.prototype.remove);

goog.exportProperty(
    ol.Collection.prototype,
    'removeAt',
    ol.Collection.prototype.removeAt);

goog.exportProperty(
    ol.Collection.prototype,
    'setAt',
    ol.Collection.prototype.setAt);

goog.exportProperty(
    ol.Collection.Event.prototype,
    'element',
    ol.Collection.Event.prototype.element);

goog.exportSymbol(
    'ol.color.asArray',
    ol.color.asArray,
    OPENLAYERS);

goog.exportSymbol(
    'ol.color.asString',
    ol.color.asString,
    OPENLAYERS);

goog.exportSymbol(
    'ol.colorlike.asColorLike',
    ol.colorlike.asColorLike,
    OPENLAYERS);

goog.exportSymbol(
    'ol.coordinate.add',
    ol.coordinate.add,
    OPENLAYERS);

goog.exportSymbol(
    'ol.coordinate.createStringXY',
    ol.coordinate.createStringXY,
    OPENLAYERS);

goog.exportSymbol(
    'ol.coordinate.format',
    ol.coordinate.format,
    OPENLAYERS);

goog.exportSymbol(
    'ol.coordinate.rotate',
    ol.coordinate.rotate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.coordinate.toStringHDMS',
    ol.coordinate.toStringHDMS,
    OPENLAYERS);

goog.exportSymbol(
    'ol.coordinate.toStringXY',
    ol.coordinate.toStringXY,
    OPENLAYERS);

goog.exportSymbol(
    'ol.DeviceOrientation',
    ol.DeviceOrientation,
    OPENLAYERS);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getAlpha',
    ol.DeviceOrientation.prototype.getAlpha);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getBeta',
    ol.DeviceOrientation.prototype.getBeta);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getGamma',
    ol.DeviceOrientation.prototype.getGamma);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getHeading',
    ol.DeviceOrientation.prototype.getHeading);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getTracking',
    ol.DeviceOrientation.prototype.getTracking);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'setTracking',
    ol.DeviceOrientation.prototype.setTracking);

goog.exportSymbol(
    'ol.easing.easeIn',
    ol.easing.easeIn,
    OPENLAYERS);

goog.exportSymbol(
    'ol.easing.easeOut',
    ol.easing.easeOut,
    OPENLAYERS);

goog.exportSymbol(
    'ol.easing.inAndOut',
    ol.easing.inAndOut,
    OPENLAYERS);

goog.exportSymbol(
    'ol.easing.linear',
    ol.easing.linear,
    OPENLAYERS);

goog.exportSymbol(
    'ol.easing.upAndDown',
    ol.easing.upAndDown,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.boundingExtent',
    ol.extent.boundingExtent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.buffer',
    ol.extent.buffer,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.containsCoordinate',
    ol.extent.containsCoordinate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.containsExtent',
    ol.extent.containsExtent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.containsXY',
    ol.extent.containsXY,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.createEmpty',
    ol.extent.createEmpty,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.equals',
    ol.extent.equals,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.extend',
    ol.extent.extend,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getBottomLeft',
    ol.extent.getBottomLeft,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getBottomRight',
    ol.extent.getBottomRight,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getCenter',
    ol.extent.getCenter,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getHeight',
    ol.extent.getHeight,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getIntersection',
    ol.extent.getIntersection,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getSize',
    ol.extent.getSize,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getTopLeft',
    ol.extent.getTopLeft,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getTopRight',
    ol.extent.getTopRight,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.getWidth',
    ol.extent.getWidth,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.intersects',
    ol.extent.intersects,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.isEmpty',
    ol.extent.isEmpty,
    OPENLAYERS);

goog.exportSymbol(
    'ol.extent.applyTransform',
    ol.extent.applyTransform,
    OPENLAYERS);

goog.exportSymbol(
    'ol.Feature',
    ol.Feature,
    OPENLAYERS);

goog.exportProperty(
    ol.Feature.prototype,
    'clone',
    ol.Feature.prototype.clone);

goog.exportProperty(
    ol.Feature.prototype,
    'getGeometry',
    ol.Feature.prototype.getGeometry);

goog.exportProperty(
    ol.Feature.prototype,
    'getId',
    ol.Feature.prototype.getId);

goog.exportProperty(
    ol.Feature.prototype,
    'getGeometryName',
    ol.Feature.prototype.getGeometryName);

goog.exportProperty(
    ol.Feature.prototype,
    'getStyle',
    ol.Feature.prototype.getStyle);

goog.exportProperty(
    ol.Feature.prototype,
    'getStyleFunction',
    ol.Feature.prototype.getStyleFunction);

goog.exportProperty(
    ol.Feature.prototype,
    'setGeometry',
    ol.Feature.prototype.setGeometry);

goog.exportProperty(
    ol.Feature.prototype,
    'setStyle',
    ol.Feature.prototype.setStyle);

goog.exportProperty(
    ol.Feature.prototype,
    'setId',
    ol.Feature.prototype.setId);

goog.exportProperty(
    ol.Feature.prototype,
    'setGeometryName',
    ol.Feature.prototype.setGeometryName);

goog.exportSymbol(
    'ol.featureloader.tile',
    ol.featureloader.tile,
    OPENLAYERS);

goog.exportSymbol(
    'ol.featureloader.xhr',
    ol.featureloader.xhr,
    OPENLAYERS);

goog.exportSymbol(
    'ol.Geolocation',
    ol.Geolocation,
    OPENLAYERS);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getAccuracy',
    ol.Geolocation.prototype.getAccuracy);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getAccuracyGeometry',
    ol.Geolocation.prototype.getAccuracyGeometry);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getAltitude',
    ol.Geolocation.prototype.getAltitude);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getAltitudeAccuracy',
    ol.Geolocation.prototype.getAltitudeAccuracy);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getHeading',
    ol.Geolocation.prototype.getHeading);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getPosition',
    ol.Geolocation.prototype.getPosition);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getProjection',
    ol.Geolocation.prototype.getProjection);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getSpeed',
    ol.Geolocation.prototype.getSpeed);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getTracking',
    ol.Geolocation.prototype.getTracking);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getTrackingOptions',
    ol.Geolocation.prototype.getTrackingOptions);

goog.exportProperty(
    ol.Geolocation.prototype,
    'setProjection',
    ol.Geolocation.prototype.setProjection);

goog.exportProperty(
    ol.Geolocation.prototype,
    'setTracking',
    ol.Geolocation.prototype.setTracking);

goog.exportProperty(
    ol.Geolocation.prototype,
    'setTrackingOptions',
    ol.Geolocation.prototype.setTrackingOptions);

goog.exportSymbol(
    'ol.Graticule',
    ol.Graticule,
    OPENLAYERS);

goog.exportProperty(
    ol.Graticule.prototype,
    'getMap',
    ol.Graticule.prototype.getMap);

goog.exportProperty(
    ol.Graticule.prototype,
    'getMeridians',
    ol.Graticule.prototype.getMeridians);

goog.exportProperty(
    ol.Graticule.prototype,
    'getParallels',
    ol.Graticule.prototype.getParallels);

goog.exportProperty(
    ol.Graticule.prototype,
    'setMap',
    ol.Graticule.prototype.setMap);

goog.exportSymbol(
    'ol.has.DEVICE_PIXEL_RATIO',
    ol.has.DEVICE_PIXEL_RATIO,
    OPENLAYERS);

goog.exportSymbol(
    'ol.has.CANVAS',
    ol.has.CANVAS,
    OPENLAYERS);

goog.exportSymbol(
    'ol.has.DEVICE_ORIENTATION',
    ol.has.DEVICE_ORIENTATION,
    OPENLAYERS);

goog.exportSymbol(
    'ol.has.GEOLOCATION',
    ol.has.GEOLOCATION,
    OPENLAYERS);

goog.exportSymbol(
    'ol.has.TOUCH',
    ol.has.TOUCH,
    OPENLAYERS);

goog.exportSymbol(
    'ol.has.WEBGL',
    ol.has.WEBGL,
    OPENLAYERS);

goog.exportProperty(
    ol.Image.prototype,
    'getImage',
    ol.Image.prototype.getImage);

goog.exportProperty(
    ol.Image.prototype,
    'load',
    ol.Image.prototype.load);

goog.exportProperty(
    ol.ImageTile.prototype,
    'getImage',
    ol.ImageTile.prototype.getImage);

goog.exportProperty(
    ol.ImageTile.prototype,
    'load',
    ol.ImageTile.prototype.load);

goog.exportSymbol(
    'ol.inherits',
    ol.inherits,
    OPENLAYERS);

goog.exportSymbol(
    'ol.Kinetic',
    ol.Kinetic,
    OPENLAYERS);

goog.exportSymbol(
    'ol.loadingstrategy.all',
    ol.loadingstrategy.all,
    OPENLAYERS);

goog.exportSymbol(
    'ol.loadingstrategy.bbox',
    ol.loadingstrategy.bbox,
    OPENLAYERS);

goog.exportSymbol(
    'ol.loadingstrategy.tile',
    ol.loadingstrategy.tile,
    OPENLAYERS);

goog.exportSymbol(
    'ol.Map',
    ol.Map,
    OPENLAYERS);

goog.exportProperty(
    ol.Map.prototype,
    'addControl',
    ol.Map.prototype.addControl);

goog.exportProperty(
    ol.Map.prototype,
    'addInteraction',
    ol.Map.prototype.addInteraction);

goog.exportProperty(
    ol.Map.prototype,
    'addLayer',
    ol.Map.prototype.addLayer);

goog.exportProperty(
    ol.Map.prototype,
    'addOverlay',
    ol.Map.prototype.addOverlay);

goog.exportProperty(
    ol.Map.prototype,
    'beforeRender',
    ol.Map.prototype.beforeRender);

goog.exportProperty(
    ol.Map.prototype,
    'forEachFeatureAtPixel',
    ol.Map.prototype.forEachFeatureAtPixel);

goog.exportProperty(
    ol.Map.prototype,
    'forEachLayerAtPixel',
    ol.Map.prototype.forEachLayerAtPixel);

goog.exportProperty(
    ol.Map.prototype,
    'hasFeatureAtPixel',
    ol.Map.prototype.hasFeatureAtPixel);

goog.exportProperty(
    ol.Map.prototype,
    'getEventCoordinate',
    ol.Map.prototype.getEventCoordinate);

goog.exportProperty(
    ol.Map.prototype,
    'getEventPixel',
    ol.Map.prototype.getEventPixel);

goog.exportProperty(
    ol.Map.prototype,
    'getTarget',
    ol.Map.prototype.getTarget);

goog.exportProperty(
    ol.Map.prototype,
    'getTargetElement',
    ol.Map.prototype.getTargetElement);

goog.exportProperty(
    ol.Map.prototype,
    'getCoordinateFromPixel',
    ol.Map.prototype.getCoordinateFromPixel);

goog.exportProperty(
    ol.Map.prototype,
    'getControls',
    ol.Map.prototype.getControls);

goog.exportProperty(
    ol.Map.prototype,
    'getOverlays',
    ol.Map.prototype.getOverlays);

goog.exportProperty(
    ol.Map.prototype,
    'getOverlayById',
    ol.Map.prototype.getOverlayById);

goog.exportProperty(
    ol.Map.prototype,
    'getInteractions',
    ol.Map.prototype.getInteractions);

goog.exportProperty(
    ol.Map.prototype,
    'getLayerGroup',
    ol.Map.prototype.getLayerGroup);

goog.exportProperty(
    ol.Map.prototype,
    'getLayers',
    ol.Map.prototype.getLayers);

goog.exportProperty(
    ol.Map.prototype,
    'getPixelFromCoordinate',
    ol.Map.prototype.getPixelFromCoordinate);

goog.exportProperty(
    ol.Map.prototype,
    'getSize',
    ol.Map.prototype.getSize);

goog.exportProperty(
    ol.Map.prototype,
    'getView',
    ol.Map.prototype.getView);

goog.exportProperty(
    ol.Map.prototype,
    'getViewport',
    ol.Map.prototype.getViewport);

goog.exportProperty(
    ol.Map.prototype,
    'renderSync',
    ol.Map.prototype.renderSync);

goog.exportProperty(
    ol.Map.prototype,
    'render',
    ol.Map.prototype.render);

goog.exportProperty(
    ol.Map.prototype,
    'removeControl',
    ol.Map.prototype.removeControl);

goog.exportProperty(
    ol.Map.prototype,
    'removeInteraction',
    ol.Map.prototype.removeInteraction);

goog.exportProperty(
    ol.Map.prototype,
    'removeLayer',
    ol.Map.prototype.removeLayer);

goog.exportProperty(
    ol.Map.prototype,
    'removeOverlay',
    ol.Map.prototype.removeOverlay);

goog.exportProperty(
    ol.Map.prototype,
    'setLayerGroup',
    ol.Map.prototype.setLayerGroup);

goog.exportProperty(
    ol.Map.prototype,
    'setSize',
    ol.Map.prototype.setSize);

goog.exportProperty(
    ol.Map.prototype,
    'setTarget',
    ol.Map.prototype.setTarget);

goog.exportProperty(
    ol.Map.prototype,
    'setView',
    ol.Map.prototype.setView);

goog.exportProperty(
    ol.Map.prototype,
    'updateSize',
    ol.Map.prototype.updateSize);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'originalEvent',
    ol.MapBrowserEvent.prototype.originalEvent);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'pixel',
    ol.MapBrowserEvent.prototype.pixel);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'coordinate',
    ol.MapBrowserEvent.prototype.coordinate);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'dragging',
    ol.MapBrowserEvent.prototype.dragging);

goog.exportProperty(
    ol.MapEvent.prototype,
    'map',
    ol.MapEvent.prototype.map);

goog.exportProperty(
    ol.MapEvent.prototype,
    'frameState',
    ol.MapEvent.prototype.frameState);

goog.exportProperty(
    ol.ObjectEvent.prototype,
    'key',
    ol.ObjectEvent.prototype.key);

goog.exportProperty(
    ol.ObjectEvent.prototype,
    'oldValue',
    ol.ObjectEvent.prototype.oldValue);

goog.exportSymbol(
    'ol.Object',
    ol.Object,
    OPENLAYERS);

goog.exportProperty(
    ol.Object.prototype,
    'get',
    ol.Object.prototype.get);

goog.exportProperty(
    ol.Object.prototype,
    'getKeys',
    ol.Object.prototype.getKeys);

goog.exportProperty(
    ol.Object.prototype,
    'getProperties',
    ol.Object.prototype.getProperties);

goog.exportProperty(
    ol.Object.prototype,
    'set',
    ol.Object.prototype.set);

goog.exportProperty(
    ol.Object.prototype,
    'setProperties',
    ol.Object.prototype.setProperties);

goog.exportProperty(
    ol.Object.prototype,
    'unset',
    ol.Object.prototype.unset);

goog.exportSymbol(
    'ol.Observable',
    ol.Observable,
    OPENLAYERS);

goog.exportSymbol(
    'ol.Observable.unByKey',
    ol.Observable.unByKey,
    OPENLAYERS);

goog.exportProperty(
    ol.Observable.prototype,
    'changed',
    ol.Observable.prototype.changed);

goog.exportProperty(
    ol.Observable.prototype,
    'dispatchEvent',
    ol.Observable.prototype.dispatchEvent);

goog.exportProperty(
    ol.Observable.prototype,
    'getRevision',
    ol.Observable.prototype.getRevision);

goog.exportProperty(
    ol.Observable.prototype,
    'on',
    ol.Observable.prototype.on);

goog.exportProperty(
    ol.Observable.prototype,
    'once',
    ol.Observable.prototype.once);

goog.exportProperty(
    ol.Observable.prototype,
    'un',
    ol.Observable.prototype.un);

goog.exportProperty(
    ol.Observable.prototype,
    'unByKey',
    ol.Observable.prototype.unByKey);

goog.exportSymbol(
    'ol.Overlay',
    ol.Overlay,
    OPENLAYERS);

goog.exportProperty(
    ol.Overlay.prototype,
    'getElement',
    ol.Overlay.prototype.getElement);

goog.exportProperty(
    ol.Overlay.prototype,
    'getId',
    ol.Overlay.prototype.getId);

goog.exportProperty(
    ol.Overlay.prototype,
    'getMap',
    ol.Overlay.prototype.getMap);

goog.exportProperty(
    ol.Overlay.prototype,
    'getOffset',
    ol.Overlay.prototype.getOffset);

goog.exportProperty(
    ol.Overlay.prototype,
    'getPosition',
    ol.Overlay.prototype.getPosition);

goog.exportProperty(
    ol.Overlay.prototype,
    'getPositioning',
    ol.Overlay.prototype.getPositioning);

goog.exportProperty(
    ol.Overlay.prototype,
    'setElement',
    ol.Overlay.prototype.setElement);

goog.exportProperty(
    ol.Overlay.prototype,
    'setMap',
    ol.Overlay.prototype.setMap);

goog.exportProperty(
    ol.Overlay.prototype,
    'setOffset',
    ol.Overlay.prototype.setOffset);

goog.exportProperty(
    ol.Overlay.prototype,
    'setPosition',
    ol.Overlay.prototype.setPosition);

goog.exportProperty(
    ol.Overlay.prototype,
    'setPositioning',
    ol.Overlay.prototype.setPositioning);

goog.exportSymbol(
    'ol.render.toContext',
    ol.render.toContext,
    OPENLAYERS);

goog.exportSymbol(
    'ol.size.toSize',
    ol.size.toSize,
    OPENLAYERS);

goog.exportProperty(
    ol.Tile.prototype,
    'getTileCoord',
    ol.Tile.prototype.getTileCoord);

goog.exportProperty(
    ol.Tile.prototype,
    'load',
    ol.Tile.prototype.load);

goog.exportProperty(
    ol.VectorTile.prototype,
    'getFormat',
    ol.VectorTile.prototype.getFormat);

goog.exportProperty(
    ol.VectorTile.prototype,
    'setFeatures',
    ol.VectorTile.prototype.setFeatures);

goog.exportProperty(
    ol.VectorTile.prototype,
    'setProjection',
    ol.VectorTile.prototype.setProjection);

goog.exportProperty(
    ol.VectorTile.prototype,
    'setLoader',
    ol.VectorTile.prototype.setLoader);

goog.exportSymbol(
    'ol.View',
    ol.View,
    OPENLAYERS);

goog.exportProperty(
    ol.View.prototype,
    'constrainCenter',
    ol.View.prototype.constrainCenter);

goog.exportProperty(
    ol.View.prototype,
    'constrainResolution',
    ol.View.prototype.constrainResolution);

goog.exportProperty(
    ol.View.prototype,
    'constrainRotation',
    ol.View.prototype.constrainRotation);

goog.exportProperty(
    ol.View.prototype,
    'getCenter',
    ol.View.prototype.getCenter);

goog.exportProperty(
    ol.View.prototype,
    'calculateExtent',
    ol.View.prototype.calculateExtent);

goog.exportProperty(
    ol.View.prototype,
    'getMaxResolution',
    ol.View.prototype.getMaxResolution);

goog.exportProperty(
    ol.View.prototype,
    'getMinResolution',
    ol.View.prototype.getMinResolution);

goog.exportProperty(
    ol.View.prototype,
    'getProjection',
    ol.View.prototype.getProjection);

goog.exportProperty(
    ol.View.prototype,
    'getResolution',
    ol.View.prototype.getResolution);

goog.exportProperty(
    ol.View.prototype,
    'getResolutions',
    ol.View.prototype.getResolutions);

goog.exportProperty(
    ol.View.prototype,
    'getRotation',
    ol.View.prototype.getRotation);

goog.exportProperty(
    ol.View.prototype,
    'getZoom',
    ol.View.prototype.getZoom);

goog.exportProperty(
    ol.View.prototype,
    'fit',
    ol.View.prototype.fit);

goog.exportProperty(
    ol.View.prototype,
    'centerOn',
    ol.View.prototype.centerOn);

goog.exportProperty(
    ol.View.prototype,
    'rotate',
    ol.View.prototype.rotate);

goog.exportProperty(
    ol.View.prototype,
    'setCenter',
    ol.View.prototype.setCenter);

goog.exportProperty(
    ol.View.prototype,
    'setResolution',
    ol.View.prototype.setResolution);

goog.exportProperty(
    ol.View.prototype,
    'setRotation',
    ol.View.prototype.setRotation);

goog.exportProperty(
    ol.View.prototype,
    'setZoom',
    ol.View.prototype.setZoom);

goog.exportSymbol(
    'ol.xml.getAllTextContent',
    ol.xml.getAllTextContent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.xml.parse',
    ol.xml.parse,
    OPENLAYERS);

goog.exportProperty(
    ol.webgl.Context.prototype,
    'getGL',
    ol.webgl.Context.prototype.getGL);

goog.exportProperty(
    ol.webgl.Context.prototype,
    'useProgram',
    ol.webgl.Context.prototype.useProgram);

goog.exportSymbol(
    'ol.tilegrid.createXYZ',
    ol.tilegrid.createXYZ,
    OPENLAYERS);

goog.exportSymbol(
    'ol.tilegrid.TileGrid',
    ol.tilegrid.TileGrid,
    OPENLAYERS);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'forEachTileCoord',
    ol.tilegrid.TileGrid.prototype.forEachTileCoord);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getMaxZoom',
    ol.tilegrid.TileGrid.prototype.getMaxZoom);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getMinZoom',
    ol.tilegrid.TileGrid.prototype.getMinZoom);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getOrigin',
    ol.tilegrid.TileGrid.prototype.getOrigin);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getResolution',
    ol.tilegrid.TileGrid.prototype.getResolution);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getResolutions',
    ol.tilegrid.TileGrid.prototype.getResolutions);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getTileCoordExtent',
    ol.tilegrid.TileGrid.prototype.getTileCoordExtent);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getTileCoordForCoordAndResolution',
    ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndResolution);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getTileCoordForCoordAndZ',
    ol.tilegrid.TileGrid.prototype.getTileCoordForCoordAndZ);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getTileSize',
    ol.tilegrid.TileGrid.prototype.getTileSize);

goog.exportProperty(
    ol.tilegrid.TileGrid.prototype,
    'getZForResolution',
    ol.tilegrid.TileGrid.prototype.getZForResolution);

goog.exportSymbol(
    'ol.tilegrid.WMTS',
    ol.tilegrid.WMTS,
    OPENLAYERS);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getMatrixIds',
    ol.tilegrid.WMTS.prototype.getMatrixIds);

goog.exportSymbol(
    'ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet',
    ol.tilegrid.WMTS.createFromCapabilitiesMatrixSet,
    OPENLAYERS);

goog.exportSymbol(
    'ol.style.AtlasManager',
    ol.style.AtlasManager,
    OPENLAYERS);

goog.exportSymbol(
    'ol.style.Circle',
    ol.style.Circle,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getFill',
    ol.style.Circle.prototype.getFill);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getImage',
    ol.style.Circle.prototype.getImage);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getRadius',
    ol.style.Circle.prototype.getRadius);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getStroke',
    ol.style.Circle.prototype.getStroke);

goog.exportSymbol(
    'ol.style.Fill',
    ol.style.Fill,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Fill.prototype,
    'getColor',
    ol.style.Fill.prototype.getColor);

goog.exportProperty(
    ol.style.Fill.prototype,
    'setColor',
    ol.style.Fill.prototype.setColor);

goog.exportSymbol(
    'ol.style.Icon',
    ol.style.Icon,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getAnchor',
    ol.style.Icon.prototype.getAnchor);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getImage',
    ol.style.Icon.prototype.getImage);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getOrigin',
    ol.style.Icon.prototype.getOrigin);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getSrc',
    ol.style.Icon.prototype.getSrc);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getSize',
    ol.style.Icon.prototype.getSize);

goog.exportProperty(
    ol.style.Icon.prototype,
    'load',
    ol.style.Icon.prototype.load);

goog.exportSymbol(
    'ol.style.Image',
    ol.style.Image,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Image.prototype,
    'getOpacity',
    ol.style.Image.prototype.getOpacity);

goog.exportProperty(
    ol.style.Image.prototype,
    'getRotateWithView',
    ol.style.Image.prototype.getRotateWithView);

goog.exportProperty(
    ol.style.Image.prototype,
    'getRotation',
    ol.style.Image.prototype.getRotation);

goog.exportProperty(
    ol.style.Image.prototype,
    'getScale',
    ol.style.Image.prototype.getScale);

goog.exportProperty(
    ol.style.Image.prototype,
    'getSnapToPixel',
    ol.style.Image.prototype.getSnapToPixel);

goog.exportProperty(
    ol.style.Image.prototype,
    'setOpacity',
    ol.style.Image.prototype.setOpacity);

goog.exportProperty(
    ol.style.Image.prototype,
    'setRotation',
    ol.style.Image.prototype.setRotation);

goog.exportProperty(
    ol.style.Image.prototype,
    'setScale',
    ol.style.Image.prototype.setScale);

goog.exportSymbol(
    'ol.style.RegularShape',
    ol.style.RegularShape,
    OPENLAYERS);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getAnchor',
    ol.style.RegularShape.prototype.getAnchor);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getAngle',
    ol.style.RegularShape.prototype.getAngle);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getFill',
    ol.style.RegularShape.prototype.getFill);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getImage',
    ol.style.RegularShape.prototype.getImage);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getOrigin',
    ol.style.RegularShape.prototype.getOrigin);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getPoints',
    ol.style.RegularShape.prototype.getPoints);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getRadius',
    ol.style.RegularShape.prototype.getRadius);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getRadius2',
    ol.style.RegularShape.prototype.getRadius2);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getSize',
    ol.style.RegularShape.prototype.getSize);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getStroke',
    ol.style.RegularShape.prototype.getStroke);

goog.exportSymbol(
    'ol.style.Stroke',
    ol.style.Stroke,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'getColor',
    ol.style.Stroke.prototype.getColor);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'getLineCap',
    ol.style.Stroke.prototype.getLineCap);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'getLineDash',
    ol.style.Stroke.prototype.getLineDash);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'getLineJoin',
    ol.style.Stroke.prototype.getLineJoin);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'getMiterLimit',
    ol.style.Stroke.prototype.getMiterLimit);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'getWidth',
    ol.style.Stroke.prototype.getWidth);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'setColor',
    ol.style.Stroke.prototype.setColor);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'setLineCap',
    ol.style.Stroke.prototype.setLineCap);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'setLineDash',
    ol.style.Stroke.prototype.setLineDash);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'setLineJoin',
    ol.style.Stroke.prototype.setLineJoin);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'setMiterLimit',
    ol.style.Stroke.prototype.setMiterLimit);

goog.exportProperty(
    ol.style.Stroke.prototype,
    'setWidth',
    ol.style.Stroke.prototype.setWidth);

goog.exportSymbol(
    'ol.style.Style',
    ol.style.Style,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Style.prototype,
    'getGeometry',
    ol.style.Style.prototype.getGeometry);

goog.exportProperty(
    ol.style.Style.prototype,
    'getGeometryFunction',
    ol.style.Style.prototype.getGeometryFunction);

goog.exportProperty(
    ol.style.Style.prototype,
    'getFill',
    ol.style.Style.prototype.getFill);

goog.exportProperty(
    ol.style.Style.prototype,
    'getImage',
    ol.style.Style.prototype.getImage);

goog.exportProperty(
    ol.style.Style.prototype,
    'getStroke',
    ol.style.Style.prototype.getStroke);

goog.exportProperty(
    ol.style.Style.prototype,
    'getText',
    ol.style.Style.prototype.getText);

goog.exportProperty(
    ol.style.Style.prototype,
    'getZIndex',
    ol.style.Style.prototype.getZIndex);

goog.exportProperty(
    ol.style.Style.prototype,
    'setGeometry',
    ol.style.Style.prototype.setGeometry);

goog.exportProperty(
    ol.style.Style.prototype,
    'setZIndex',
    ol.style.Style.prototype.setZIndex);

goog.exportSymbol(
    'ol.style.Text',
    ol.style.Text,
    OPENLAYERS);

goog.exportProperty(
    ol.style.Text.prototype,
    'getFont',
    ol.style.Text.prototype.getFont);

goog.exportProperty(
    ol.style.Text.prototype,
    'getOffsetX',
    ol.style.Text.prototype.getOffsetX);

goog.exportProperty(
    ol.style.Text.prototype,
    'getOffsetY',
    ol.style.Text.prototype.getOffsetY);

goog.exportProperty(
    ol.style.Text.prototype,
    'getFill',
    ol.style.Text.prototype.getFill);

goog.exportProperty(
    ol.style.Text.prototype,
    'getRotateWithView',
    ol.style.Text.prototype.getRotateWithView);

goog.exportProperty(
    ol.style.Text.prototype,
    'getRotation',
    ol.style.Text.prototype.getRotation);

goog.exportProperty(
    ol.style.Text.prototype,
    'getScale',
    ol.style.Text.prototype.getScale);

goog.exportProperty(
    ol.style.Text.prototype,
    'getStroke',
    ol.style.Text.prototype.getStroke);

goog.exportProperty(
    ol.style.Text.prototype,
    'getText',
    ol.style.Text.prototype.getText);

goog.exportProperty(
    ol.style.Text.prototype,
    'getTextAlign',
    ol.style.Text.prototype.getTextAlign);

goog.exportProperty(
    ol.style.Text.prototype,
    'getTextBaseline',
    ol.style.Text.prototype.getTextBaseline);

goog.exportProperty(
    ol.style.Text.prototype,
    'setFont',
    ol.style.Text.prototype.setFont);

goog.exportProperty(
    ol.style.Text.prototype,
    'setOffsetX',
    ol.style.Text.prototype.setOffsetX);

goog.exportProperty(
    ol.style.Text.prototype,
    'setOffsetY',
    ol.style.Text.prototype.setOffsetY);

goog.exportProperty(
    ol.style.Text.prototype,
    'setFill',
    ol.style.Text.prototype.setFill);

goog.exportProperty(
    ol.style.Text.prototype,
    'setRotation',
    ol.style.Text.prototype.setRotation);

goog.exportProperty(
    ol.style.Text.prototype,
    'setScale',
    ol.style.Text.prototype.setScale);

goog.exportProperty(
    ol.style.Text.prototype,
    'setStroke',
    ol.style.Text.prototype.setStroke);

goog.exportProperty(
    ol.style.Text.prototype,
    'setText',
    ol.style.Text.prototype.setText);

goog.exportProperty(
    ol.style.Text.prototype,
    'setTextAlign',
    ol.style.Text.prototype.setTextAlign);

goog.exportProperty(
    ol.style.Text.prototype,
    'setTextBaseline',
    ol.style.Text.prototype.setTextBaseline);

goog.exportSymbol(
    'ol.Sphere',
    ol.Sphere,
    OPENLAYERS);

goog.exportProperty(
    ol.Sphere.prototype,
    'geodesicArea',
    ol.Sphere.prototype.geodesicArea);

goog.exportProperty(
    ol.Sphere.prototype,
    'haversineDistance',
    ol.Sphere.prototype.haversineDistance);

goog.exportSymbol(
    'ol.source.BingMaps',
    ol.source.BingMaps,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.BingMaps.TOS_ATTRIBUTION',
    ol.source.BingMaps.TOS_ATTRIBUTION,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.CartoDB',
    ol.source.CartoDB,
    OPENLAYERS);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getConfig',
    ol.source.CartoDB.prototype.getConfig);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'updateConfig',
    ol.source.CartoDB.prototype.updateConfig);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setConfig',
    ol.source.CartoDB.prototype.setConfig);

goog.exportSymbol(
    'ol.source.Cluster',
    ol.source.Cluster,
    OPENLAYERS);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getSource',
    ol.source.Cluster.prototype.getSource);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'setDistance',
    ol.source.Cluster.prototype.setDistance);

goog.exportSymbol(
    'ol.source.Image',
    ol.source.Image,
    OPENLAYERS);

goog.exportProperty(
    ol.source.ImageEvent.prototype,
    'image',
    ol.source.ImageEvent.prototype.image);

goog.exportSymbol(
    'ol.source.ImageArcGISRest',
    ol.source.ImageArcGISRest,
    OPENLAYERS);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getParams',
    ol.source.ImageArcGISRest.prototype.getParams);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getImageLoadFunction',
    ol.source.ImageArcGISRest.prototype.getImageLoadFunction);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getUrl',
    ol.source.ImageArcGISRest.prototype.getUrl);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'setImageLoadFunction',
    ol.source.ImageArcGISRest.prototype.setImageLoadFunction);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'setUrl',
    ol.source.ImageArcGISRest.prototype.setUrl);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'updateParams',
    ol.source.ImageArcGISRest.prototype.updateParams);

goog.exportSymbol(
    'ol.source.ImageCanvas',
    ol.source.ImageCanvas,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.ImageMapGuide',
    ol.source.ImageMapGuide,
    OPENLAYERS);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getParams',
    ol.source.ImageMapGuide.prototype.getParams);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getImageLoadFunction',
    ol.source.ImageMapGuide.prototype.getImageLoadFunction);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'updateParams',
    ol.source.ImageMapGuide.prototype.updateParams);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'setImageLoadFunction',
    ol.source.ImageMapGuide.prototype.setImageLoadFunction);

goog.exportSymbol(
    'ol.source.ImageStatic',
    ol.source.ImageStatic,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.ImageVector',
    ol.source.ImageVector,
    OPENLAYERS);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getSource',
    ol.source.ImageVector.prototype.getSource);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getStyle',
    ol.source.ImageVector.prototype.getStyle);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getStyleFunction',
    ol.source.ImageVector.prototype.getStyleFunction);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'setStyle',
    ol.source.ImageVector.prototype.setStyle);

goog.exportSymbol(
    'ol.source.ImageWMS',
    ol.source.ImageWMS,
    OPENLAYERS);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getGetFeatureInfoUrl',
    ol.source.ImageWMS.prototype.getGetFeatureInfoUrl);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getParams',
    ol.source.ImageWMS.prototype.getParams);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getImageLoadFunction',
    ol.source.ImageWMS.prototype.getImageLoadFunction);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getUrl',
    ol.source.ImageWMS.prototype.getUrl);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'setImageLoadFunction',
    ol.source.ImageWMS.prototype.setImageLoadFunction);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'setUrl',
    ol.source.ImageWMS.prototype.setUrl);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'updateParams',
    ol.source.ImageWMS.prototype.updateParams);

goog.exportSymbol(
    'ol.source.OSM',
    ol.source.OSM,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.OSM.ATTRIBUTION',
    ol.source.OSM.ATTRIBUTION,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.Raster',
    ol.source.Raster,
    OPENLAYERS);

goog.exportProperty(
    ol.source.Raster.prototype,
    'setOperation',
    ol.source.Raster.prototype.setOperation);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'extent',
    ol.source.RasterEvent.prototype.extent);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'resolution',
    ol.source.RasterEvent.prototype.resolution);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'data',
    ol.source.RasterEvent.prototype.data);

goog.exportSymbol(
    'ol.source.Source',
    ol.source.Source,
    OPENLAYERS);

goog.exportProperty(
    ol.source.Source.prototype,
    'getAttributions',
    ol.source.Source.prototype.getAttributions);

goog.exportProperty(
    ol.source.Source.prototype,
    'getLogo',
    ol.source.Source.prototype.getLogo);

goog.exportProperty(
    ol.source.Source.prototype,
    'getProjection',
    ol.source.Source.prototype.getProjection);

goog.exportProperty(
    ol.source.Source.prototype,
    'getState',
    ol.source.Source.prototype.getState);

goog.exportProperty(
    ol.source.Source.prototype,
    'refresh',
    ol.source.Source.prototype.refresh);

goog.exportProperty(
    ol.source.Source.prototype,
    'setAttributions',
    ol.source.Source.prototype.setAttributions);

goog.exportSymbol(
    'ol.source.Stamen',
    ol.source.Stamen,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.Tile',
    ol.source.Tile,
    OPENLAYERS);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getTileGrid',
    ol.source.Tile.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileEvent.prototype,
    'tile',
    ol.source.TileEvent.prototype.tile);

goog.exportSymbol(
    'ol.source.TileArcGISRest',
    ol.source.TileArcGISRest,
    OPENLAYERS);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getParams',
    ol.source.TileArcGISRest.prototype.getParams);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'updateParams',
    ol.source.TileArcGISRest.prototype.updateParams);

goog.exportSymbol(
    'ol.source.TileDebug',
    ol.source.TileDebug,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.TileImage',
    ol.source.TileImage,
    OPENLAYERS);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setRenderReprojectionEdges',
    ol.source.TileImage.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setTileGridForProjection',
    ol.source.TileImage.prototype.setTileGridForProjection);

goog.exportSymbol(
    'ol.source.TileJSON',
    ol.source.TileJSON,
    OPENLAYERS);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getTileJSON',
    ol.source.TileJSON.prototype.getTileJSON);

goog.exportSymbol(
    'ol.source.TileUTFGrid',
    ol.source.TileUTFGrid,
    OPENLAYERS);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getTemplate',
    ol.source.TileUTFGrid.prototype.getTemplate);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'forDataAtCoordinateAndResolution',
    ol.source.TileUTFGrid.prototype.forDataAtCoordinateAndResolution);

goog.exportSymbol(
    'ol.source.TileWMS',
    ol.source.TileWMS,
    OPENLAYERS);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getGetFeatureInfoUrl',
    ol.source.TileWMS.prototype.getGetFeatureInfoUrl);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getParams',
    ol.source.TileWMS.prototype.getParams);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'updateParams',
    ol.source.TileWMS.prototype.updateParams);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getTileLoadFunction',
    ol.source.UrlTile.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getTileUrlFunction',
    ol.source.UrlTile.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getUrls',
    ol.source.UrlTile.prototype.getUrls);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'setTileLoadFunction',
    ol.source.UrlTile.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'setTileUrlFunction',
    ol.source.UrlTile.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'setUrl',
    ol.source.UrlTile.prototype.setUrl);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'setUrls',
    ol.source.UrlTile.prototype.setUrls);

goog.exportSymbol(
    'ol.source.Vector',
    ol.source.Vector,
    OPENLAYERS);

goog.exportProperty(
    ol.source.Vector.prototype,
    'addFeature',
    ol.source.Vector.prototype.addFeature);

goog.exportProperty(
    ol.source.Vector.prototype,
    'addFeatures',
    ol.source.Vector.prototype.addFeatures);

goog.exportProperty(
    ol.source.Vector.prototype,
    'clear',
    ol.source.Vector.prototype.clear);

goog.exportProperty(
    ol.source.Vector.prototype,
    'forEachFeature',
    ol.source.Vector.prototype.forEachFeature);

goog.exportProperty(
    ol.source.Vector.prototype,
    'forEachFeatureInExtent',
    ol.source.Vector.prototype.forEachFeatureInExtent);

goog.exportProperty(
    ol.source.Vector.prototype,
    'forEachFeatureIntersectingExtent',
    ol.source.Vector.prototype.forEachFeatureIntersectingExtent);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getFeaturesCollection',
    ol.source.Vector.prototype.getFeaturesCollection);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getFeatures',
    ol.source.Vector.prototype.getFeatures);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getFeaturesAtCoordinate',
    ol.source.Vector.prototype.getFeaturesAtCoordinate);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getFeaturesInExtent',
    ol.source.Vector.prototype.getFeaturesInExtent);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getClosestFeatureToCoordinate',
    ol.source.Vector.prototype.getClosestFeatureToCoordinate);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getExtent',
    ol.source.Vector.prototype.getExtent);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getFeatureById',
    ol.source.Vector.prototype.getFeatureById);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getFormat',
    ol.source.Vector.prototype.getFormat);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getUrl',
    ol.source.Vector.prototype.getUrl);

goog.exportProperty(
    ol.source.Vector.prototype,
    'removeFeature',
    ol.source.Vector.prototype.removeFeature);

goog.exportProperty(
    ol.source.VectorEvent.prototype,
    'feature',
    ol.source.VectorEvent.prototype.feature);

goog.exportSymbol(
    'ol.source.VectorTile',
    ol.source.VectorTile,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.WMTS',
    ol.source.WMTS,
    OPENLAYERS);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getDimensions',
    ol.source.WMTS.prototype.getDimensions);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getFormat',
    ol.source.WMTS.prototype.getFormat);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getLayer',
    ol.source.WMTS.prototype.getLayer);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getMatrixSet',
    ol.source.WMTS.prototype.getMatrixSet);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getRequestEncoding',
    ol.source.WMTS.prototype.getRequestEncoding);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getStyle',
    ol.source.WMTS.prototype.getStyle);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getVersion',
    ol.source.WMTS.prototype.getVersion);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'updateDimensions',
    ol.source.WMTS.prototype.updateDimensions);

goog.exportSymbol(
    'ol.source.WMTS.optionsFromCapabilities',
    ol.source.WMTS.optionsFromCapabilities,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.XYZ',
    ol.source.XYZ,
    OPENLAYERS);

goog.exportSymbol(
    'ol.source.Zoomify',
    ol.source.Zoomify,
    OPENLAYERS);

goog.exportProperty(
    ol.render.Event.prototype,
    'vectorContext',
    ol.render.Event.prototype.vectorContext);

goog.exportProperty(
    ol.render.Event.prototype,
    'frameState',
    ol.render.Event.prototype.frameState);

goog.exportProperty(
    ol.render.Event.prototype,
    'context',
    ol.render.Event.prototype.context);

goog.exportProperty(
    ol.render.Event.prototype,
    'glContext',
    ol.render.Event.prototype.glContext);

goog.exportProperty(
    ol.render.Feature.prototype,
    'get',
    ol.render.Feature.prototype.get);

goog.exportProperty(
    ol.render.Feature.prototype,
    'getExtent',
    ol.render.Feature.prototype.getExtent);

goog.exportProperty(
    ol.render.Feature.prototype,
    'getGeometry',
    ol.render.Feature.prototype.getGeometry);

goog.exportProperty(
    ol.render.Feature.prototype,
    'getProperties',
    ol.render.Feature.prototype.getProperties);

goog.exportProperty(
    ol.render.Feature.prototype,
    'getType',
    ol.render.Feature.prototype.getType);

goog.exportSymbol(
    'ol.render.VectorContext',
    ol.render.VectorContext,
    OPENLAYERS);

goog.exportProperty(
    ol.render.webgl.Immediate.prototype,
    'setStyle',
    ol.render.webgl.Immediate.prototype.setStyle);

goog.exportProperty(
    ol.render.webgl.Immediate.prototype,
    'drawGeometry',
    ol.render.webgl.Immediate.prototype.drawGeometry);

goog.exportProperty(
    ol.render.webgl.Immediate.prototype,
    'drawFeature',
    ol.render.webgl.Immediate.prototype.drawFeature);

goog.exportProperty(
    ol.render.canvas.Immediate.prototype,
    'drawCircle',
    ol.render.canvas.Immediate.prototype.drawCircle);

goog.exportProperty(
    ol.render.canvas.Immediate.prototype,
    'setStyle',
    ol.render.canvas.Immediate.prototype.setStyle);

goog.exportProperty(
    ol.render.canvas.Immediate.prototype,
    'drawGeometry',
    ol.render.canvas.Immediate.prototype.drawGeometry);

goog.exportProperty(
    ol.render.canvas.Immediate.prototype,
    'drawFeature',
    ol.render.canvas.Immediate.prototype.drawFeature);

goog.exportSymbol(
    'ol.proj.common.add',
    ol.proj.common.add,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.METERS_PER_UNIT',
    ol.proj.METERS_PER_UNIT,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.Projection',
    ol.proj.Projection,
    OPENLAYERS);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'getCode',
    ol.proj.Projection.prototype.getCode);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'getExtent',
    ol.proj.Projection.prototype.getExtent);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'getUnits',
    ol.proj.Projection.prototype.getUnits);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'getMetersPerUnit',
    ol.proj.Projection.prototype.getMetersPerUnit);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'getWorldExtent',
    ol.proj.Projection.prototype.getWorldExtent);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'isGlobal',
    ol.proj.Projection.prototype.isGlobal);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'setGlobal',
    ol.proj.Projection.prototype.setGlobal);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'setExtent',
    ol.proj.Projection.prototype.setExtent);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'setWorldExtent',
    ol.proj.Projection.prototype.setWorldExtent);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'setGetPointResolution',
    ol.proj.Projection.prototype.setGetPointResolution);

goog.exportProperty(
    ol.proj.Projection.prototype,
    'getPointResolution',
    ol.proj.Projection.prototype.getPointResolution);

goog.exportSymbol(
    'ol.proj.setProj4',
    ol.proj.setProj4,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.addEquivalentProjections',
    ol.proj.addEquivalentProjections,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.addProjection',
    ol.proj.addProjection,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.addCoordinateTransforms',
    ol.proj.addCoordinateTransforms,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.fromLonLat',
    ol.proj.fromLonLat,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.toLonLat',
    ol.proj.toLonLat,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.get',
    ol.proj.get,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.equivalent',
    ol.proj.equivalent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.getTransform',
    ol.proj.getTransform,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.transform',
    ol.proj.transform,
    OPENLAYERS);

goog.exportSymbol(
    'ol.proj.transformExtent',
    ol.proj.transformExtent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.layer.Base',
    ol.layer.Base,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getExtent',
    ol.layer.Base.prototype.getExtent);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getMaxResolution',
    ol.layer.Base.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getMinResolution',
    ol.layer.Base.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getOpacity',
    ol.layer.Base.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getVisible',
    ol.layer.Base.prototype.getVisible);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getZIndex',
    ol.layer.Base.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setExtent',
    ol.layer.Base.prototype.setExtent);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setMaxResolution',
    ol.layer.Base.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setMinResolution',
    ol.layer.Base.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setOpacity',
    ol.layer.Base.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setVisible',
    ol.layer.Base.prototype.setVisible);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setZIndex',
    ol.layer.Base.prototype.setZIndex);

goog.exportSymbol(
    'ol.layer.Group',
    ol.layer.Group,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getLayers',
    ol.layer.Group.prototype.getLayers);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setLayers',
    ol.layer.Group.prototype.setLayers);

goog.exportSymbol(
    'ol.layer.Heatmap',
    ol.layer.Heatmap,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getBlur',
    ol.layer.Heatmap.prototype.getBlur);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getGradient',
    ol.layer.Heatmap.prototype.getGradient);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getRadius',
    ol.layer.Heatmap.prototype.getRadius);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setBlur',
    ol.layer.Heatmap.prototype.setBlur);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setGradient',
    ol.layer.Heatmap.prototype.setGradient);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setRadius',
    ol.layer.Heatmap.prototype.setRadius);

goog.exportSymbol(
    'ol.layer.Image',
    ol.layer.Image,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getSource',
    ol.layer.Image.prototype.getSource);

goog.exportSymbol(
    'ol.layer.Layer',
    ol.layer.Layer,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getSource',
    ol.layer.Layer.prototype.getSource);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setMap',
    ol.layer.Layer.prototype.setMap);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setSource',
    ol.layer.Layer.prototype.setSource);

goog.exportSymbol(
    'ol.layer.Tile',
    ol.layer.Tile,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getPreload',
    ol.layer.Tile.prototype.getPreload);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getSource',
    ol.layer.Tile.prototype.getSource);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setPreload',
    ol.layer.Tile.prototype.setPreload);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getUseInterimTilesOnError',
    ol.layer.Tile.prototype.getUseInterimTilesOnError);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setUseInterimTilesOnError',
    ol.layer.Tile.prototype.setUseInterimTilesOnError);

goog.exportSymbol(
    'ol.layer.Vector',
    ol.layer.Vector,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getSource',
    ol.layer.Vector.prototype.getSource);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getStyle',
    ol.layer.Vector.prototype.getStyle);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getStyleFunction',
    ol.layer.Vector.prototype.getStyleFunction);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setStyle',
    ol.layer.Vector.prototype.setStyle);

goog.exportSymbol(
    'ol.layer.VectorTile',
    ol.layer.VectorTile,
    OPENLAYERS);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getPreload',
    ol.layer.VectorTile.prototype.getPreload);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getUseInterimTilesOnError',
    ol.layer.VectorTile.prototype.getUseInterimTilesOnError);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setPreload',
    ol.layer.VectorTile.prototype.setPreload);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setUseInterimTilesOnError',
    ol.layer.VectorTile.prototype.setUseInterimTilesOnError);

goog.exportSymbol(
    'ol.interaction.DoubleClickZoom',
    ol.interaction.DoubleClickZoom,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.DoubleClickZoom.handleEvent',
    ol.interaction.DoubleClickZoom.handleEvent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.DragAndDrop',
    ol.interaction.DragAndDrop,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.DragAndDrop.handleEvent',
    ol.interaction.DragAndDrop.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'features',
    ol.interaction.DragAndDropEvent.prototype.features);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'file',
    ol.interaction.DragAndDropEvent.prototype.file);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'projection',
    ol.interaction.DragAndDropEvent.prototype.projection);

goog.exportProperty(
    ol.DragBoxEvent.prototype,
    'coordinate',
    ol.DragBoxEvent.prototype.coordinate);

goog.exportProperty(
    ol.DragBoxEvent.prototype,
    'mapBrowserEvent',
    ol.DragBoxEvent.prototype.mapBrowserEvent);

goog.exportSymbol(
    'ol.interaction.DragBox',
    ol.interaction.DragBox,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'getGeometry',
    ol.interaction.DragBox.prototype.getGeometry);

goog.exportSymbol(
    'ol.interaction.DragPan',
    ol.interaction.DragPan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.DragRotate',
    ol.interaction.DragRotate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.DragRotateAndZoom',
    ol.interaction.DragRotateAndZoom,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.DragZoom',
    ol.interaction.DragZoom,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.DrawEvent.prototype,
    'feature',
    ol.interaction.DrawEvent.prototype.feature);

goog.exportSymbol(
    'ol.interaction.Draw',
    ol.interaction.Draw,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.Draw.handleEvent',
    ol.interaction.Draw.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'removeLastPoint',
    ol.interaction.Draw.prototype.removeLastPoint);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'finishDrawing',
    ol.interaction.Draw.prototype.finishDrawing);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'extend',
    ol.interaction.Draw.prototype.extend);

goog.exportSymbol(
    'ol.interaction.Draw.createRegularPolygon',
    ol.interaction.Draw.createRegularPolygon,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.defaults',
    ol.interaction.defaults,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.Interaction',
    ol.interaction.Interaction,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'getActive',
    ol.interaction.Interaction.prototype.getActive);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'getMap',
    ol.interaction.Interaction.prototype.getMap);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'setActive',
    ol.interaction.Interaction.prototype.setActive);

goog.exportSymbol(
    'ol.interaction.KeyboardPan',
    ol.interaction.KeyboardPan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.KeyboardPan.handleEvent',
    ol.interaction.KeyboardPan.handleEvent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.KeyboardZoom',
    ol.interaction.KeyboardZoom,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.KeyboardZoom.handleEvent',
    ol.interaction.KeyboardZoom.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.ModifyEvent.prototype,
    'features',
    ol.interaction.ModifyEvent.prototype.features);

goog.exportProperty(
    ol.interaction.ModifyEvent.prototype,
    'mapBrowserEvent',
    ol.interaction.ModifyEvent.prototype.mapBrowserEvent);

goog.exportSymbol(
    'ol.interaction.Modify',
    ol.interaction.Modify,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.Modify.handleEvent',
    ol.interaction.Modify.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'removePoint',
    ol.interaction.Modify.prototype.removePoint);

goog.exportSymbol(
    'ol.interaction.MouseWheelZoom',
    ol.interaction.MouseWheelZoom,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.MouseWheelZoom.handleEvent',
    ol.interaction.MouseWheelZoom.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'setMouseAnchor',
    ol.interaction.MouseWheelZoom.prototype.setMouseAnchor);

goog.exportSymbol(
    'ol.interaction.PinchRotate',
    ol.interaction.PinchRotate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.PinchZoom',
    ol.interaction.PinchZoom,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.Pointer',
    ol.interaction.Pointer,
    OPENLAYERS);

goog.exportSymbol(
    'ol.interaction.Pointer.handleEvent',
    ol.interaction.Pointer.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'selected',
    ol.interaction.SelectEvent.prototype.selected);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'deselected',
    ol.interaction.SelectEvent.prototype.deselected);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'mapBrowserEvent',
    ol.interaction.SelectEvent.prototype.mapBrowserEvent);

goog.exportSymbol(
    'ol.interaction.Select',
    ol.interaction.Select,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getFeatures',
    ol.interaction.Select.prototype.getFeatures);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getLayer',
    ol.interaction.Select.prototype.getLayer);

goog.exportSymbol(
    'ol.interaction.Select.handleEvent',
    ol.interaction.Select.handleEvent,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'setMap',
    ol.interaction.Select.prototype.setMap);

goog.exportSymbol(
    'ol.interaction.Snap',
    ol.interaction.Snap,
    OPENLAYERS);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'addFeature',
    ol.interaction.Snap.prototype.addFeature);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'removeFeature',
    ol.interaction.Snap.prototype.removeFeature);

goog.exportProperty(
    ol.interaction.TranslateEvent.prototype,
    'features',
    ol.interaction.TranslateEvent.prototype.features);

goog.exportProperty(
    ol.interaction.TranslateEvent.prototype,
    'coordinate',
    ol.interaction.TranslateEvent.prototype.coordinate);

goog.exportSymbol(
    'ol.interaction.Translate',
    ol.interaction.Translate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.geom.Circle',
    ol.geom.Circle,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'clone',
    ol.geom.Circle.prototype.clone);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getCenter',
    ol.geom.Circle.prototype.getCenter);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getRadius',
    ol.geom.Circle.prototype.getRadius);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getType',
    ol.geom.Circle.prototype.getType);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'intersectsExtent',
    ol.geom.Circle.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'setCenter',
    ol.geom.Circle.prototype.setCenter);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'setCenterAndRadius',
    ol.geom.Circle.prototype.setCenterAndRadius);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'setRadius',
    ol.geom.Circle.prototype.setRadius);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'transform',
    ol.geom.Circle.prototype.transform);

goog.exportSymbol(
    'ol.geom.Geometry',
    ol.geom.Geometry,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'getClosestPoint',
    ol.geom.Geometry.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'intersectsCoordinate',
    ol.geom.Geometry.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'getExtent',
    ol.geom.Geometry.prototype.getExtent);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'rotate',
    ol.geom.Geometry.prototype.rotate);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'scale',
    ol.geom.Geometry.prototype.scale);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'simplify',
    ol.geom.Geometry.prototype.simplify);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'transform',
    ol.geom.Geometry.prototype.transform);

goog.exportSymbol(
    'ol.geom.GeometryCollection',
    ol.geom.GeometryCollection,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'clone',
    ol.geom.GeometryCollection.prototype.clone);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getGeometries',
    ol.geom.GeometryCollection.prototype.getGeometries);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getType',
    ol.geom.GeometryCollection.prototype.getType);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'intersectsExtent',
    ol.geom.GeometryCollection.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'setGeometries',
    ol.geom.GeometryCollection.prototype.setGeometries);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'applyTransform',
    ol.geom.GeometryCollection.prototype.applyTransform);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'translate',
    ol.geom.GeometryCollection.prototype.translate);

goog.exportSymbol(
    'ol.geom.LinearRing',
    ol.geom.LinearRing,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'clone',
    ol.geom.LinearRing.prototype.clone);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getArea',
    ol.geom.LinearRing.prototype.getArea);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getCoordinates',
    ol.geom.LinearRing.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getType',
    ol.geom.LinearRing.prototype.getType);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'setCoordinates',
    ol.geom.LinearRing.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.LineString',
    ol.geom.LineString,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'appendCoordinate',
    ol.geom.LineString.prototype.appendCoordinate);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'clone',
    ol.geom.LineString.prototype.clone);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'forEachSegment',
    ol.geom.LineString.prototype.forEachSegment);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getCoordinateAtM',
    ol.geom.LineString.prototype.getCoordinateAtM);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getCoordinates',
    ol.geom.LineString.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getCoordinateAt',
    ol.geom.LineString.prototype.getCoordinateAt);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getLength',
    ol.geom.LineString.prototype.getLength);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getType',
    ol.geom.LineString.prototype.getType);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'intersectsExtent',
    ol.geom.LineString.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'setCoordinates',
    ol.geom.LineString.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.MultiLineString',
    ol.geom.MultiLineString,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'appendLineString',
    ol.geom.MultiLineString.prototype.appendLineString);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'clone',
    ol.geom.MultiLineString.prototype.clone);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getCoordinateAtM',
    ol.geom.MultiLineString.prototype.getCoordinateAtM);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getCoordinates',
    ol.geom.MultiLineString.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getLineString',
    ol.geom.MultiLineString.prototype.getLineString);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getLineStrings',
    ol.geom.MultiLineString.prototype.getLineStrings);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getType',
    ol.geom.MultiLineString.prototype.getType);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'intersectsExtent',
    ol.geom.MultiLineString.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'setCoordinates',
    ol.geom.MultiLineString.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.MultiPoint',
    ol.geom.MultiPoint,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'appendPoint',
    ol.geom.MultiPoint.prototype.appendPoint);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'clone',
    ol.geom.MultiPoint.prototype.clone);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getCoordinates',
    ol.geom.MultiPoint.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getPoint',
    ol.geom.MultiPoint.prototype.getPoint);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getPoints',
    ol.geom.MultiPoint.prototype.getPoints);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getType',
    ol.geom.MultiPoint.prototype.getType);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'intersectsExtent',
    ol.geom.MultiPoint.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'setCoordinates',
    ol.geom.MultiPoint.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.MultiPolygon',
    ol.geom.MultiPolygon,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'appendPolygon',
    ol.geom.MultiPolygon.prototype.appendPolygon);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'clone',
    ol.geom.MultiPolygon.prototype.clone);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getArea',
    ol.geom.MultiPolygon.prototype.getArea);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getCoordinates',
    ol.geom.MultiPolygon.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getInteriorPoints',
    ol.geom.MultiPolygon.prototype.getInteriorPoints);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getPolygon',
    ol.geom.MultiPolygon.prototype.getPolygon);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getPolygons',
    ol.geom.MultiPolygon.prototype.getPolygons);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getType',
    ol.geom.MultiPolygon.prototype.getType);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'intersectsExtent',
    ol.geom.MultiPolygon.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'setCoordinates',
    ol.geom.MultiPolygon.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.Point',
    ol.geom.Point,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.Point.prototype,
    'clone',
    ol.geom.Point.prototype.clone);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getCoordinates',
    ol.geom.Point.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getType',
    ol.geom.Point.prototype.getType);

goog.exportProperty(
    ol.geom.Point.prototype,
    'intersectsExtent',
    ol.geom.Point.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.Point.prototype,
    'setCoordinates',
    ol.geom.Point.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.Polygon',
    ol.geom.Polygon,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'appendLinearRing',
    ol.geom.Polygon.prototype.appendLinearRing);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'clone',
    ol.geom.Polygon.prototype.clone);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getArea',
    ol.geom.Polygon.prototype.getArea);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getCoordinates',
    ol.geom.Polygon.prototype.getCoordinates);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getInteriorPoint',
    ol.geom.Polygon.prototype.getInteriorPoint);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getLinearRingCount',
    ol.geom.Polygon.prototype.getLinearRingCount);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getLinearRing',
    ol.geom.Polygon.prototype.getLinearRing);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getLinearRings',
    ol.geom.Polygon.prototype.getLinearRings);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getType',
    ol.geom.Polygon.prototype.getType);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'intersectsExtent',
    ol.geom.Polygon.prototype.intersectsExtent);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'setCoordinates',
    ol.geom.Polygon.prototype.setCoordinates);

goog.exportSymbol(
    'ol.geom.Polygon.circular',
    ol.geom.Polygon.circular,
    OPENLAYERS);

goog.exportSymbol(
    'ol.geom.Polygon.fromExtent',
    ol.geom.Polygon.fromExtent,
    OPENLAYERS);

goog.exportSymbol(
    'ol.geom.Polygon.fromCircle',
    ol.geom.Polygon.fromCircle,
    OPENLAYERS);

goog.exportSymbol(
    'ol.geom.SimpleGeometry',
    ol.geom.SimpleGeometry,
    OPENLAYERS);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getFirstCoordinate',
    ol.geom.SimpleGeometry.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getLastCoordinate',
    ol.geom.SimpleGeometry.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getLayout',
    ol.geom.SimpleGeometry.prototype.getLayout);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'applyTransform',
    ol.geom.SimpleGeometry.prototype.applyTransform);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'translate',
    ol.geom.SimpleGeometry.prototype.translate);

goog.exportSymbol(
    'ol.format.EsriJSON',
    ol.format.EsriJSON,
    OPENLAYERS);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'readFeature',
    ol.format.EsriJSON.prototype.readFeature);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'readFeatures',
    ol.format.EsriJSON.prototype.readFeatures);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'readGeometry',
    ol.format.EsriJSON.prototype.readGeometry);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'readProjection',
    ol.format.EsriJSON.prototype.readProjection);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'writeGeometry',
    ol.format.EsriJSON.prototype.writeGeometry);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'writeGeometryObject',
    ol.format.EsriJSON.prototype.writeGeometryObject);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'writeFeature',
    ol.format.EsriJSON.prototype.writeFeature);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'writeFeatureObject',
    ol.format.EsriJSON.prototype.writeFeatureObject);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'writeFeatures',
    ol.format.EsriJSON.prototype.writeFeatures);

goog.exportProperty(
    ol.format.EsriJSON.prototype,
    'writeFeaturesObject',
    ol.format.EsriJSON.prototype.writeFeaturesObject);

goog.exportSymbol(
    'ol.format.Feature',
    ol.format.Feature,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.GeoJSON',
    ol.format.GeoJSON,
    OPENLAYERS);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'readFeature',
    ol.format.GeoJSON.prototype.readFeature);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'readFeatures',
    ol.format.GeoJSON.prototype.readFeatures);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'readGeometry',
    ol.format.GeoJSON.prototype.readGeometry);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'readProjection',
    ol.format.GeoJSON.prototype.readProjection);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'writeFeature',
    ol.format.GeoJSON.prototype.writeFeature);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'writeFeatureObject',
    ol.format.GeoJSON.prototype.writeFeatureObject);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'writeFeatures',
    ol.format.GeoJSON.prototype.writeFeatures);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'writeFeaturesObject',
    ol.format.GeoJSON.prototype.writeFeaturesObject);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'writeGeometry',
    ol.format.GeoJSON.prototype.writeGeometry);

goog.exportProperty(
    ol.format.GeoJSON.prototype,
    'writeGeometryObject',
    ol.format.GeoJSON.prototype.writeGeometryObject);

goog.exportSymbol(
    'ol.format.GML',
    ol.format.GML,
    OPENLAYERS);

goog.exportProperty(
    ol.format.GML.prototype,
    'writeFeatures',
    ol.format.GML.prototype.writeFeatures);

goog.exportProperty(
    ol.format.GML.prototype,
    'writeFeaturesNode',
    ol.format.GML.prototype.writeFeaturesNode);

goog.exportSymbol(
    'ol.format.GML2',
    ol.format.GML2,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.GML3',
    ol.format.GML3,
    OPENLAYERS);

goog.exportProperty(
    ol.format.GML3.prototype,
    'writeGeometryNode',
    ol.format.GML3.prototype.writeGeometryNode);

goog.exportProperty(
    ol.format.GML3.prototype,
    'writeFeatures',
    ol.format.GML3.prototype.writeFeatures);

goog.exportProperty(
    ol.format.GML3.prototype,
    'writeFeaturesNode',
    ol.format.GML3.prototype.writeFeaturesNode);

goog.exportProperty(
    ol.format.GMLBase.prototype,
    'readFeatures',
    ol.format.GMLBase.prototype.readFeatures);

goog.exportSymbol(
    'ol.format.GPX',
    ol.format.GPX,
    OPENLAYERS);

goog.exportProperty(
    ol.format.GPX.prototype,
    'readFeature',
    ol.format.GPX.prototype.readFeature);

goog.exportProperty(
    ol.format.GPX.prototype,
    'readFeatures',
    ol.format.GPX.prototype.readFeatures);

goog.exportProperty(
    ol.format.GPX.prototype,
    'readProjection',
    ol.format.GPX.prototype.readProjection);

goog.exportProperty(
    ol.format.GPX.prototype,
    'writeFeatures',
    ol.format.GPX.prototype.writeFeatures);

goog.exportProperty(
    ol.format.GPX.prototype,
    'writeFeaturesNode',
    ol.format.GPX.prototype.writeFeaturesNode);

goog.exportSymbol(
    'ol.format.IGC',
    ol.format.IGC,
    OPENLAYERS);

goog.exportProperty(
    ol.format.IGC.prototype,
    'readFeature',
    ol.format.IGC.prototype.readFeature);

goog.exportProperty(
    ol.format.IGC.prototype,
    'readFeatures',
    ol.format.IGC.prototype.readFeatures);

goog.exportProperty(
    ol.format.IGC.prototype,
    'readProjection',
    ol.format.IGC.prototype.readProjection);

goog.exportSymbol(
    'ol.format.KML',
    ol.format.KML,
    OPENLAYERS);

goog.exportProperty(
    ol.format.KML.prototype,
    'readFeature',
    ol.format.KML.prototype.readFeature);

goog.exportProperty(
    ol.format.KML.prototype,
    'readFeatures',
    ol.format.KML.prototype.readFeatures);

goog.exportProperty(
    ol.format.KML.prototype,
    'readName',
    ol.format.KML.prototype.readName);

goog.exportProperty(
    ol.format.KML.prototype,
    'readNetworkLinks',
    ol.format.KML.prototype.readNetworkLinks);

goog.exportProperty(
    ol.format.KML.prototype,
    'readProjection',
    ol.format.KML.prototype.readProjection);

goog.exportProperty(
    ol.format.KML.prototype,
    'writeFeatures',
    ol.format.KML.prototype.writeFeatures);

goog.exportProperty(
    ol.format.KML.prototype,
    'writeFeaturesNode',
    ol.format.KML.prototype.writeFeaturesNode);

goog.exportSymbol(
    'ol.format.MVT',
    ol.format.MVT,
    OPENLAYERS);

goog.exportProperty(
    ol.format.MVT.prototype,
    'readFeatures',
    ol.format.MVT.prototype.readFeatures);

goog.exportProperty(
    ol.format.MVT.prototype,
    'readProjection',
    ol.format.MVT.prototype.readProjection);

goog.exportProperty(
    ol.format.MVT.prototype,
    'setLayers',
    ol.format.MVT.prototype.setLayers);

goog.exportSymbol(
    'ol.format.OSMXML',
    ol.format.OSMXML,
    OPENLAYERS);

goog.exportProperty(
    ol.format.OSMXML.prototype,
    'readFeatures',
    ol.format.OSMXML.prototype.readFeatures);

goog.exportProperty(
    ol.format.OSMXML.prototype,
    'readProjection',
    ol.format.OSMXML.prototype.readProjection);

goog.exportSymbol(
    'ol.format.Polyline',
    ol.format.Polyline,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.Polyline.encodeDeltas',
    ol.format.Polyline.encodeDeltas,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.Polyline.decodeDeltas',
    ol.format.Polyline.decodeDeltas,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.Polyline.encodeFloats',
    ol.format.Polyline.encodeFloats,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.Polyline.decodeFloats',
    ol.format.Polyline.decodeFloats,
    OPENLAYERS);

goog.exportProperty(
    ol.format.Polyline.prototype,
    'readFeature',
    ol.format.Polyline.prototype.readFeature);

goog.exportProperty(
    ol.format.Polyline.prototype,
    'readFeatures',
    ol.format.Polyline.prototype.readFeatures);

goog.exportProperty(
    ol.format.Polyline.prototype,
    'readGeometry',
    ol.format.Polyline.prototype.readGeometry);

goog.exportProperty(
    ol.format.Polyline.prototype,
    'readProjection',
    ol.format.Polyline.prototype.readProjection);

goog.exportProperty(
    ol.format.Polyline.prototype,
    'writeGeometry',
    ol.format.Polyline.prototype.writeGeometry);

goog.exportSymbol(
    'ol.format.TopoJSON',
    ol.format.TopoJSON,
    OPENLAYERS);

goog.exportProperty(
    ol.format.TopoJSON.prototype,
    'readFeatures',
    ol.format.TopoJSON.prototype.readFeatures);

goog.exportProperty(
    ol.format.TopoJSON.prototype,
    'readProjection',
    ol.format.TopoJSON.prototype.readProjection);

goog.exportSymbol(
    'ol.format.WFS',
    ol.format.WFS,
    OPENLAYERS);

goog.exportProperty(
    ol.format.WFS.prototype,
    'readFeatures',
    ol.format.WFS.prototype.readFeatures);

goog.exportProperty(
    ol.format.WFS.prototype,
    'readTransactionResponse',
    ol.format.WFS.prototype.readTransactionResponse);

goog.exportProperty(
    ol.format.WFS.prototype,
    'readFeatureCollectionMetadata',
    ol.format.WFS.prototype.readFeatureCollectionMetadata);

goog.exportProperty(
    ol.format.WFS.prototype,
    'writeGetFeature',
    ol.format.WFS.prototype.writeGetFeature);

goog.exportProperty(
    ol.format.WFS.prototype,
    'writeTransaction',
    ol.format.WFS.prototype.writeTransaction);

goog.exportProperty(
    ol.format.WFS.prototype,
    'readProjection',
    ol.format.WFS.prototype.readProjection);

goog.exportSymbol(
    'ol.format.WKT',
    ol.format.WKT,
    OPENLAYERS);

goog.exportProperty(
    ol.format.WKT.prototype,
    'readFeature',
    ol.format.WKT.prototype.readFeature);

goog.exportProperty(
    ol.format.WKT.prototype,
    'readFeatures',
    ol.format.WKT.prototype.readFeatures);

goog.exportProperty(
    ol.format.WKT.prototype,
    'readGeometry',
    ol.format.WKT.prototype.readGeometry);

goog.exportProperty(
    ol.format.WKT.prototype,
    'writeFeature',
    ol.format.WKT.prototype.writeFeature);

goog.exportProperty(
    ol.format.WKT.prototype,
    'writeFeatures',
    ol.format.WKT.prototype.writeFeatures);

goog.exportProperty(
    ol.format.WKT.prototype,
    'writeGeometry',
    ol.format.WKT.prototype.writeGeometry);

goog.exportSymbol(
    'ol.format.WMSCapabilities',
    ol.format.WMSCapabilities,
    OPENLAYERS);

goog.exportProperty(
    ol.format.WMSCapabilities.prototype,
    'read',
    ol.format.WMSCapabilities.prototype.read);

goog.exportSymbol(
    'ol.format.WMSGetFeatureInfo',
    ol.format.WMSGetFeatureInfo,
    OPENLAYERS);

goog.exportProperty(
    ol.format.WMSGetFeatureInfo.prototype,
    'readFeatures',
    ol.format.WMSGetFeatureInfo.prototype.readFeatures);

goog.exportSymbol(
    'ol.format.WMTSCapabilities',
    ol.format.WMTSCapabilities,
    OPENLAYERS);

goog.exportProperty(
    ol.format.WMTSCapabilities.prototype,
    'read',
    ol.format.WMTSCapabilities.prototype.read);

goog.exportSymbol(
    'ol.format.ogc.filter.And',
    ol.format.ogc.filter.And,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Bbox',
    ol.format.ogc.filter.Bbox,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Comparison',
    ol.format.ogc.filter.Comparison,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.ComparisonBinary',
    ol.format.ogc.filter.ComparisonBinary,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.EqualTo',
    ol.format.ogc.filter.EqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Filter',
    ol.format.ogc.filter.Filter,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.GreaterThan',
    ol.format.ogc.filter.GreaterThan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.GreaterThanOrEqualTo',
    ol.format.ogc.filter.GreaterThanOrEqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.and',
    ol.format.ogc.filter.and,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.or',
    ol.format.ogc.filter.or,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.not',
    ol.format.ogc.filter.not,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.bbox',
    ol.format.ogc.filter.bbox,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.intersects',
    ol.format.ogc.filter.intersects,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.within',
    ol.format.ogc.filter.within,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.equalTo',
    ol.format.ogc.filter.equalTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.notEqualTo',
    ol.format.ogc.filter.notEqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.lessThan',
    ol.format.ogc.filter.lessThan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.lessThanOrEqualTo',
    ol.format.ogc.filter.lessThanOrEqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.greaterThan',
    ol.format.ogc.filter.greaterThan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.greaterThanOrEqualTo',
    ol.format.ogc.filter.greaterThanOrEqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.isNull',
    ol.format.ogc.filter.isNull,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.between',
    ol.format.ogc.filter.between,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.like',
    ol.format.ogc.filter.like,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Intersects',
    ol.format.ogc.filter.Intersects,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.IsBetween',
    ol.format.ogc.filter.IsBetween,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.IsLike',
    ol.format.ogc.filter.IsLike,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.IsNull',
    ol.format.ogc.filter.IsNull,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.LessThan',
    ol.format.ogc.filter.LessThan,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.LessThanOrEqualTo',
    ol.format.ogc.filter.LessThanOrEqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Not',
    ol.format.ogc.filter.Not,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.NotEqualTo',
    ol.format.ogc.filter.NotEqualTo,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Or',
    ol.format.ogc.filter.Or,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Spatial',
    ol.format.ogc.filter.Spatial,
    OPENLAYERS);

goog.exportSymbol(
    'ol.format.ogc.filter.Within',
    ol.format.ogc.filter.Within,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.altKeyOnly',
    ol.events.condition.altKeyOnly,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.altShiftKeysOnly',
    ol.events.condition.altShiftKeysOnly,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.always',
    ol.events.condition.always,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.click',
    ol.events.condition.click,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.never',
    ol.events.condition.never,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.pointerMove',
    ol.events.condition.pointerMove,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.singleClick',
    ol.events.condition.singleClick,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.doubleClick',
    ol.events.condition.doubleClick,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.noModifierKeys',
    ol.events.condition.noModifierKeys,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.platformModifierKeyOnly',
    ol.events.condition.platformModifierKeyOnly,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.shiftKeyOnly',
    ol.events.condition.shiftKeyOnly,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.targetNotEditable',
    ol.events.condition.targetNotEditable,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.mouseOnly',
    ol.events.condition.mouseOnly,
    OPENLAYERS);

goog.exportSymbol(
    'ol.events.condition.primaryAction',
    ol.events.condition.primaryAction,
    OPENLAYERS);

goog.exportProperty(
    ol.events.Event.prototype,
    'type',
    ol.events.Event.prototype.type);

goog.exportProperty(
    ol.events.Event.prototype,
    'target',
    ol.events.Event.prototype.target);

goog.exportProperty(
    ol.events.Event.prototype,
    'preventDefault',
    ol.events.Event.prototype.preventDefault);

goog.exportProperty(
    ol.events.Event.prototype,
    'stopPropagation',
    ol.events.Event.prototype.stopPropagation);

goog.exportSymbol(
    'ol.control.Attribution',
    ol.control.Attribution,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.Attribution.render',
    ol.control.Attribution.render,
    OPENLAYERS);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'getCollapsible',
    ol.control.Attribution.prototype.getCollapsible);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'setCollapsible',
    ol.control.Attribution.prototype.setCollapsible);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'setCollapsed',
    ol.control.Attribution.prototype.setCollapsed);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'getCollapsed',
    ol.control.Attribution.prototype.getCollapsed);

goog.exportSymbol(
    'ol.control.Control',
    ol.control.Control,
    OPENLAYERS);

goog.exportProperty(
    ol.control.Control.prototype,
    'getMap',
    ol.control.Control.prototype.getMap);

goog.exportProperty(
    ol.control.Control.prototype,
    'setMap',
    ol.control.Control.prototype.setMap);

goog.exportProperty(
    ol.control.Control.prototype,
    'setTarget',
    ol.control.Control.prototype.setTarget);

goog.exportSymbol(
    'ol.control.FullScreen',
    ol.control.FullScreen,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.defaults',
    ol.control.defaults,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.MousePosition',
    ol.control.MousePosition,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.MousePosition.render',
    ol.control.MousePosition.render,
    OPENLAYERS);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'getCoordinateFormat',
    ol.control.MousePosition.prototype.getCoordinateFormat);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'getProjection',
    ol.control.MousePosition.prototype.getProjection);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'setCoordinateFormat',
    ol.control.MousePosition.prototype.setCoordinateFormat);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'setProjection',
    ol.control.MousePosition.prototype.setProjection);

goog.exportSymbol(
    'ol.control.OverviewMap',
    ol.control.OverviewMap,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.OverviewMap.render',
    ol.control.OverviewMap.render,
    OPENLAYERS);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getCollapsible',
    ol.control.OverviewMap.prototype.getCollapsible);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'setCollapsible',
    ol.control.OverviewMap.prototype.setCollapsible);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'setCollapsed',
    ol.control.OverviewMap.prototype.setCollapsed);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getCollapsed',
    ol.control.OverviewMap.prototype.getCollapsed);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getOverviewMap',
    ol.control.OverviewMap.prototype.getOverviewMap);

goog.exportSymbol(
    'ol.control.Rotate',
    ol.control.Rotate,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.Rotate.render',
    ol.control.Rotate.render,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.ScaleLine',
    ol.control.ScaleLine,
    OPENLAYERS);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'getUnits',
    ol.control.ScaleLine.prototype.getUnits);

goog.exportSymbol(
    'ol.control.ScaleLine.render',
    ol.control.ScaleLine.render,
    OPENLAYERS);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'setUnits',
    ol.control.ScaleLine.prototype.setUnits);

goog.exportSymbol(
    'ol.control.Zoom',
    ol.control.Zoom,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.ZoomSlider',
    ol.control.ZoomSlider,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.ZoomSlider.render',
    ol.control.ZoomSlider.render,
    OPENLAYERS);

goog.exportSymbol(
    'ol.control.ZoomToExtent',
    ol.control.ZoomToExtent,
    OPENLAYERS);

goog.exportProperty(
    ol.Object.prototype,
    'changed',
    ol.Object.prototype.changed);

goog.exportProperty(
    ol.Object.prototype,
    'dispatchEvent',
    ol.Object.prototype.dispatchEvent);

goog.exportProperty(
    ol.Object.prototype,
    'getRevision',
    ol.Object.prototype.getRevision);

goog.exportProperty(
    ol.Object.prototype,
    'on',
    ol.Object.prototype.on);

goog.exportProperty(
    ol.Object.prototype,
    'once',
    ol.Object.prototype.once);

goog.exportProperty(
    ol.Object.prototype,
    'un',
    ol.Object.prototype.un);

goog.exportProperty(
    ol.Object.prototype,
    'unByKey',
    ol.Object.prototype.unByKey);

goog.exportProperty(
    ol.Collection.prototype,
    'get',
    ol.Collection.prototype.get);

goog.exportProperty(
    ol.Collection.prototype,
    'getKeys',
    ol.Collection.prototype.getKeys);

goog.exportProperty(
    ol.Collection.prototype,
    'getProperties',
    ol.Collection.prototype.getProperties);

goog.exportProperty(
    ol.Collection.prototype,
    'set',
    ol.Collection.prototype.set);

goog.exportProperty(
    ol.Collection.prototype,
    'setProperties',
    ol.Collection.prototype.setProperties);

goog.exportProperty(
    ol.Collection.prototype,
    'unset',
    ol.Collection.prototype.unset);

goog.exportProperty(
    ol.Collection.prototype,
    'changed',
    ol.Collection.prototype.changed);

goog.exportProperty(
    ol.Collection.prototype,
    'dispatchEvent',
    ol.Collection.prototype.dispatchEvent);

goog.exportProperty(
    ol.Collection.prototype,
    'getRevision',
    ol.Collection.prototype.getRevision);

goog.exportProperty(
    ol.Collection.prototype,
    'on',
    ol.Collection.prototype.on);

goog.exportProperty(
    ol.Collection.prototype,
    'once',
    ol.Collection.prototype.once);

goog.exportProperty(
    ol.Collection.prototype,
    'un',
    ol.Collection.prototype.un);

goog.exportProperty(
    ol.Collection.prototype,
    'unByKey',
    ol.Collection.prototype.unByKey);

goog.exportProperty(
    ol.Collection.Event.prototype,
    'type',
    ol.Collection.Event.prototype.type);

goog.exportProperty(
    ol.Collection.Event.prototype,
    'target',
    ol.Collection.Event.prototype.target);

goog.exportProperty(
    ol.Collection.Event.prototype,
    'preventDefault',
    ol.Collection.Event.prototype.preventDefault);

goog.exportProperty(
    ol.Collection.Event.prototype,
    'stopPropagation',
    ol.Collection.Event.prototype.stopPropagation);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'get',
    ol.DeviceOrientation.prototype.get);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getKeys',
    ol.DeviceOrientation.prototype.getKeys);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getProperties',
    ol.DeviceOrientation.prototype.getProperties);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'set',
    ol.DeviceOrientation.prototype.set);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'setProperties',
    ol.DeviceOrientation.prototype.setProperties);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'unset',
    ol.DeviceOrientation.prototype.unset);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'changed',
    ol.DeviceOrientation.prototype.changed);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'dispatchEvent',
    ol.DeviceOrientation.prototype.dispatchEvent);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'getRevision',
    ol.DeviceOrientation.prototype.getRevision);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'on',
    ol.DeviceOrientation.prototype.on);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'once',
    ol.DeviceOrientation.prototype.once);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'un',
    ol.DeviceOrientation.prototype.un);

goog.exportProperty(
    ol.DeviceOrientation.prototype,
    'unByKey',
    ol.DeviceOrientation.prototype.unByKey);

goog.exportProperty(
    ol.Feature.prototype,
    'get',
    ol.Feature.prototype.get);

goog.exportProperty(
    ol.Feature.prototype,
    'getKeys',
    ol.Feature.prototype.getKeys);

goog.exportProperty(
    ol.Feature.prototype,
    'getProperties',
    ol.Feature.prototype.getProperties);

goog.exportProperty(
    ol.Feature.prototype,
    'set',
    ol.Feature.prototype.set);

goog.exportProperty(
    ol.Feature.prototype,
    'setProperties',
    ol.Feature.prototype.setProperties);

goog.exportProperty(
    ol.Feature.prototype,
    'unset',
    ol.Feature.prototype.unset);

goog.exportProperty(
    ol.Feature.prototype,
    'changed',
    ol.Feature.prototype.changed);

goog.exportProperty(
    ol.Feature.prototype,
    'dispatchEvent',
    ol.Feature.prototype.dispatchEvent);

goog.exportProperty(
    ol.Feature.prototype,
    'getRevision',
    ol.Feature.prototype.getRevision);

goog.exportProperty(
    ol.Feature.prototype,
    'on',
    ol.Feature.prototype.on);

goog.exportProperty(
    ol.Feature.prototype,
    'once',
    ol.Feature.prototype.once);

goog.exportProperty(
    ol.Feature.prototype,
    'un',
    ol.Feature.prototype.un);

goog.exportProperty(
    ol.Feature.prototype,
    'unByKey',
    ol.Feature.prototype.unByKey);

goog.exportProperty(
    ol.Geolocation.prototype,
    'get',
    ol.Geolocation.prototype.get);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getKeys',
    ol.Geolocation.prototype.getKeys);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getProperties',
    ol.Geolocation.prototype.getProperties);

goog.exportProperty(
    ol.Geolocation.prototype,
    'set',
    ol.Geolocation.prototype.set);

goog.exportProperty(
    ol.Geolocation.prototype,
    'setProperties',
    ol.Geolocation.prototype.setProperties);

goog.exportProperty(
    ol.Geolocation.prototype,
    'unset',
    ol.Geolocation.prototype.unset);

goog.exportProperty(
    ol.Geolocation.prototype,
    'changed',
    ol.Geolocation.prototype.changed);

goog.exportProperty(
    ol.Geolocation.prototype,
    'dispatchEvent',
    ol.Geolocation.prototype.dispatchEvent);

goog.exportProperty(
    ol.Geolocation.prototype,
    'getRevision',
    ol.Geolocation.prototype.getRevision);

goog.exportProperty(
    ol.Geolocation.prototype,
    'on',
    ol.Geolocation.prototype.on);

goog.exportProperty(
    ol.Geolocation.prototype,
    'once',
    ol.Geolocation.prototype.once);

goog.exportProperty(
    ol.Geolocation.prototype,
    'un',
    ol.Geolocation.prototype.un);

goog.exportProperty(
    ol.Geolocation.prototype,
    'unByKey',
    ol.Geolocation.prototype.unByKey);

goog.exportProperty(
    ol.ImageTile.prototype,
    'getTileCoord',
    ol.ImageTile.prototype.getTileCoord);

goog.exportProperty(
    ol.Map.prototype,
    'get',
    ol.Map.prototype.get);

goog.exportProperty(
    ol.Map.prototype,
    'getKeys',
    ol.Map.prototype.getKeys);

goog.exportProperty(
    ol.Map.prototype,
    'getProperties',
    ol.Map.prototype.getProperties);

goog.exportProperty(
    ol.Map.prototype,
    'set',
    ol.Map.prototype.set);

goog.exportProperty(
    ol.Map.prototype,
    'setProperties',
    ol.Map.prototype.setProperties);

goog.exportProperty(
    ol.Map.prototype,
    'unset',
    ol.Map.prototype.unset);

goog.exportProperty(
    ol.Map.prototype,
    'changed',
    ol.Map.prototype.changed);

goog.exportProperty(
    ol.Map.prototype,
    'dispatchEvent',
    ol.Map.prototype.dispatchEvent);

goog.exportProperty(
    ol.Map.prototype,
    'getRevision',
    ol.Map.prototype.getRevision);

goog.exportProperty(
    ol.Map.prototype,
    'on',
    ol.Map.prototype.on);

goog.exportProperty(
    ol.Map.prototype,
    'once',
    ol.Map.prototype.once);

goog.exportProperty(
    ol.Map.prototype,
    'un',
    ol.Map.prototype.un);

goog.exportProperty(
    ol.Map.prototype,
    'unByKey',
    ol.Map.prototype.unByKey);

goog.exportProperty(
    ol.MapEvent.prototype,
    'type',
    ol.MapEvent.prototype.type);

goog.exportProperty(
    ol.MapEvent.prototype,
    'target',
    ol.MapEvent.prototype.target);

goog.exportProperty(
    ol.MapEvent.prototype,
    'preventDefault',
    ol.MapEvent.prototype.preventDefault);

goog.exportProperty(
    ol.MapEvent.prototype,
    'stopPropagation',
    ol.MapEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'map',
    ol.MapBrowserEvent.prototype.map);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'frameState',
    ol.MapBrowserEvent.prototype.frameState);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'type',
    ol.MapBrowserEvent.prototype.type);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'target',
    ol.MapBrowserEvent.prototype.target);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'preventDefault',
    ol.MapBrowserEvent.prototype.preventDefault);

goog.exportProperty(
    ol.MapBrowserEvent.prototype,
    'stopPropagation',
    ol.MapBrowserEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'originalEvent',
    ol.MapBrowserPointerEvent.prototype.originalEvent);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'pixel',
    ol.MapBrowserPointerEvent.prototype.pixel);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'coordinate',
    ol.MapBrowserPointerEvent.prototype.coordinate);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'dragging',
    ol.MapBrowserPointerEvent.prototype.dragging);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'preventDefault',
    ol.MapBrowserPointerEvent.prototype.preventDefault);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'stopPropagation',
    ol.MapBrowserPointerEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'map',
    ol.MapBrowserPointerEvent.prototype.map);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'frameState',
    ol.MapBrowserPointerEvent.prototype.frameState);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'type',
    ol.MapBrowserPointerEvent.prototype.type);

goog.exportProperty(
    ol.MapBrowserPointerEvent.prototype,
    'target',
    ol.MapBrowserPointerEvent.prototype.target);

goog.exportProperty(
    ol.ObjectEvent.prototype,
    'type',
    ol.ObjectEvent.prototype.type);

goog.exportProperty(
    ol.ObjectEvent.prototype,
    'target',
    ol.ObjectEvent.prototype.target);

goog.exportProperty(
    ol.ObjectEvent.prototype,
    'preventDefault',
    ol.ObjectEvent.prototype.preventDefault);

goog.exportProperty(
    ol.ObjectEvent.prototype,
    'stopPropagation',
    ol.ObjectEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.Overlay.prototype,
    'get',
    ol.Overlay.prototype.get);

goog.exportProperty(
    ol.Overlay.prototype,
    'getKeys',
    ol.Overlay.prototype.getKeys);

goog.exportProperty(
    ol.Overlay.prototype,
    'getProperties',
    ol.Overlay.prototype.getProperties);

goog.exportProperty(
    ol.Overlay.prototype,
    'set',
    ol.Overlay.prototype.set);

goog.exportProperty(
    ol.Overlay.prototype,
    'setProperties',
    ol.Overlay.prototype.setProperties);

goog.exportProperty(
    ol.Overlay.prototype,
    'unset',
    ol.Overlay.prototype.unset);

goog.exportProperty(
    ol.Overlay.prototype,
    'changed',
    ol.Overlay.prototype.changed);

goog.exportProperty(
    ol.Overlay.prototype,
    'dispatchEvent',
    ol.Overlay.prototype.dispatchEvent);

goog.exportProperty(
    ol.Overlay.prototype,
    'getRevision',
    ol.Overlay.prototype.getRevision);

goog.exportProperty(
    ol.Overlay.prototype,
    'on',
    ol.Overlay.prototype.on);

goog.exportProperty(
    ol.Overlay.prototype,
    'once',
    ol.Overlay.prototype.once);

goog.exportProperty(
    ol.Overlay.prototype,
    'un',
    ol.Overlay.prototype.un);

goog.exportProperty(
    ol.Overlay.prototype,
    'unByKey',
    ol.Overlay.prototype.unByKey);

goog.exportProperty(
    ol.VectorTile.prototype,
    'getTileCoord',
    ol.VectorTile.prototype.getTileCoord);

goog.exportProperty(
    ol.View.prototype,
    'get',
    ol.View.prototype.get);

goog.exportProperty(
    ol.View.prototype,
    'getKeys',
    ol.View.prototype.getKeys);

goog.exportProperty(
    ol.View.prototype,
    'getProperties',
    ol.View.prototype.getProperties);

goog.exportProperty(
    ol.View.prototype,
    'set',
    ol.View.prototype.set);

goog.exportProperty(
    ol.View.prototype,
    'setProperties',
    ol.View.prototype.setProperties);

goog.exportProperty(
    ol.View.prototype,
    'unset',
    ol.View.prototype.unset);

goog.exportProperty(
    ol.View.prototype,
    'changed',
    ol.View.prototype.changed);

goog.exportProperty(
    ol.View.prototype,
    'dispatchEvent',
    ol.View.prototype.dispatchEvent);

goog.exportProperty(
    ol.View.prototype,
    'getRevision',
    ol.View.prototype.getRevision);

goog.exportProperty(
    ol.View.prototype,
    'on',
    ol.View.prototype.on);

goog.exportProperty(
    ol.View.prototype,
    'once',
    ol.View.prototype.once);

goog.exportProperty(
    ol.View.prototype,
    'un',
    ol.View.prototype.un);

goog.exportProperty(
    ol.View.prototype,
    'unByKey',
    ol.View.prototype.unByKey);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'forEachTileCoord',
    ol.tilegrid.WMTS.prototype.forEachTileCoord);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getMaxZoom',
    ol.tilegrid.WMTS.prototype.getMaxZoom);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getMinZoom',
    ol.tilegrid.WMTS.prototype.getMinZoom);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getOrigin',
    ol.tilegrid.WMTS.prototype.getOrigin);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getResolution',
    ol.tilegrid.WMTS.prototype.getResolution);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getResolutions',
    ol.tilegrid.WMTS.prototype.getResolutions);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getTileCoordExtent',
    ol.tilegrid.WMTS.prototype.getTileCoordExtent);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getTileCoordForCoordAndResolution',
    ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndResolution);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getTileCoordForCoordAndZ',
    ol.tilegrid.WMTS.prototype.getTileCoordForCoordAndZ);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getTileSize',
    ol.tilegrid.WMTS.prototype.getTileSize);

goog.exportProperty(
    ol.tilegrid.WMTS.prototype,
    'getZForResolution',
    ol.tilegrid.WMTS.prototype.getZForResolution);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getOpacity',
    ol.style.Circle.prototype.getOpacity);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getRotateWithView',
    ol.style.Circle.prototype.getRotateWithView);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getRotation',
    ol.style.Circle.prototype.getRotation);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getScale',
    ol.style.Circle.prototype.getScale);

goog.exportProperty(
    ol.style.Circle.prototype,
    'getSnapToPixel',
    ol.style.Circle.prototype.getSnapToPixel);

goog.exportProperty(
    ol.style.Circle.prototype,
    'setOpacity',
    ol.style.Circle.prototype.setOpacity);

goog.exportProperty(
    ol.style.Circle.prototype,
    'setRotation',
    ol.style.Circle.prototype.setRotation);

goog.exportProperty(
    ol.style.Circle.prototype,
    'setScale',
    ol.style.Circle.prototype.setScale);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getOpacity',
    ol.style.Icon.prototype.getOpacity);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getRotateWithView',
    ol.style.Icon.prototype.getRotateWithView);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getRotation',
    ol.style.Icon.prototype.getRotation);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getScale',
    ol.style.Icon.prototype.getScale);

goog.exportProperty(
    ol.style.Icon.prototype,
    'getSnapToPixel',
    ol.style.Icon.prototype.getSnapToPixel);

goog.exportProperty(
    ol.style.Icon.prototype,
    'setOpacity',
    ol.style.Icon.prototype.setOpacity);

goog.exportProperty(
    ol.style.Icon.prototype,
    'setRotation',
    ol.style.Icon.prototype.setRotation);

goog.exportProperty(
    ol.style.Icon.prototype,
    'setScale',
    ol.style.Icon.prototype.setScale);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getOpacity',
    ol.style.RegularShape.prototype.getOpacity);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getRotateWithView',
    ol.style.RegularShape.prototype.getRotateWithView);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getRotation',
    ol.style.RegularShape.prototype.getRotation);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getScale',
    ol.style.RegularShape.prototype.getScale);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'getSnapToPixel',
    ol.style.RegularShape.prototype.getSnapToPixel);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'setOpacity',
    ol.style.RegularShape.prototype.setOpacity);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'setRotation',
    ol.style.RegularShape.prototype.setRotation);

goog.exportProperty(
    ol.style.RegularShape.prototype,
    'setScale',
    ol.style.RegularShape.prototype.setScale);

goog.exportProperty(
    ol.source.Source.prototype,
    'get',
    ol.source.Source.prototype.get);

goog.exportProperty(
    ol.source.Source.prototype,
    'getKeys',
    ol.source.Source.prototype.getKeys);

goog.exportProperty(
    ol.source.Source.prototype,
    'getProperties',
    ol.source.Source.prototype.getProperties);

goog.exportProperty(
    ol.source.Source.prototype,
    'set',
    ol.source.Source.prototype.set);

goog.exportProperty(
    ol.source.Source.prototype,
    'setProperties',
    ol.source.Source.prototype.setProperties);

goog.exportProperty(
    ol.source.Source.prototype,
    'unset',
    ol.source.Source.prototype.unset);

goog.exportProperty(
    ol.source.Source.prototype,
    'changed',
    ol.source.Source.prototype.changed);

goog.exportProperty(
    ol.source.Source.prototype,
    'dispatchEvent',
    ol.source.Source.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Source.prototype,
    'getRevision',
    ol.source.Source.prototype.getRevision);

goog.exportProperty(
    ol.source.Source.prototype,
    'on',
    ol.source.Source.prototype.on);

goog.exportProperty(
    ol.source.Source.prototype,
    'once',
    ol.source.Source.prototype.once);

goog.exportProperty(
    ol.source.Source.prototype,
    'un',
    ol.source.Source.prototype.un);

goog.exportProperty(
    ol.source.Source.prototype,
    'unByKey',
    ol.source.Source.prototype.unByKey);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getAttributions',
    ol.source.Tile.prototype.getAttributions);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getLogo',
    ol.source.Tile.prototype.getLogo);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getProjection',
    ol.source.Tile.prototype.getProjection);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getState',
    ol.source.Tile.prototype.getState);

goog.exportProperty(
    ol.source.Tile.prototype,
    'refresh',
    ol.source.Tile.prototype.refresh);

goog.exportProperty(
    ol.source.Tile.prototype,
    'setAttributions',
    ol.source.Tile.prototype.setAttributions);

goog.exportProperty(
    ol.source.Tile.prototype,
    'get',
    ol.source.Tile.prototype.get);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getKeys',
    ol.source.Tile.prototype.getKeys);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getProperties',
    ol.source.Tile.prototype.getProperties);

goog.exportProperty(
    ol.source.Tile.prototype,
    'set',
    ol.source.Tile.prototype.set);

goog.exportProperty(
    ol.source.Tile.prototype,
    'setProperties',
    ol.source.Tile.prototype.setProperties);

goog.exportProperty(
    ol.source.Tile.prototype,
    'unset',
    ol.source.Tile.prototype.unset);

goog.exportProperty(
    ol.source.Tile.prototype,
    'changed',
    ol.source.Tile.prototype.changed);

goog.exportProperty(
    ol.source.Tile.prototype,
    'dispatchEvent',
    ol.source.Tile.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Tile.prototype,
    'getRevision',
    ol.source.Tile.prototype.getRevision);

goog.exportProperty(
    ol.source.Tile.prototype,
    'on',
    ol.source.Tile.prototype.on);

goog.exportProperty(
    ol.source.Tile.prototype,
    'once',
    ol.source.Tile.prototype.once);

goog.exportProperty(
    ol.source.Tile.prototype,
    'un',
    ol.source.Tile.prototype.un);

goog.exportProperty(
    ol.source.Tile.prototype,
    'unByKey',
    ol.source.Tile.prototype.unByKey);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getTileGrid',
    ol.source.UrlTile.prototype.getTileGrid);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'refresh',
    ol.source.UrlTile.prototype.refresh);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getAttributions',
    ol.source.UrlTile.prototype.getAttributions);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getLogo',
    ol.source.UrlTile.prototype.getLogo);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getProjection',
    ol.source.UrlTile.prototype.getProjection);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getState',
    ol.source.UrlTile.prototype.getState);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'setAttributions',
    ol.source.UrlTile.prototype.setAttributions);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'get',
    ol.source.UrlTile.prototype.get);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getKeys',
    ol.source.UrlTile.prototype.getKeys);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getProperties',
    ol.source.UrlTile.prototype.getProperties);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'set',
    ol.source.UrlTile.prototype.set);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'setProperties',
    ol.source.UrlTile.prototype.setProperties);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'unset',
    ol.source.UrlTile.prototype.unset);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'changed',
    ol.source.UrlTile.prototype.changed);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'dispatchEvent',
    ol.source.UrlTile.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'getRevision',
    ol.source.UrlTile.prototype.getRevision);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'on',
    ol.source.UrlTile.prototype.on);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'once',
    ol.source.UrlTile.prototype.once);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'un',
    ol.source.UrlTile.prototype.un);

goog.exportProperty(
    ol.source.UrlTile.prototype,
    'unByKey',
    ol.source.UrlTile.prototype.unByKey);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getTileLoadFunction',
    ol.source.TileImage.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getTileUrlFunction',
    ol.source.TileImage.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getUrls',
    ol.source.TileImage.prototype.getUrls);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setTileLoadFunction',
    ol.source.TileImage.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setTileUrlFunction',
    ol.source.TileImage.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setUrl',
    ol.source.TileImage.prototype.setUrl);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setUrls',
    ol.source.TileImage.prototype.setUrls);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getTileGrid',
    ol.source.TileImage.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'refresh',
    ol.source.TileImage.prototype.refresh);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getAttributions',
    ol.source.TileImage.prototype.getAttributions);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getLogo',
    ol.source.TileImage.prototype.getLogo);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getProjection',
    ol.source.TileImage.prototype.getProjection);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getState',
    ol.source.TileImage.prototype.getState);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setAttributions',
    ol.source.TileImage.prototype.setAttributions);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'get',
    ol.source.TileImage.prototype.get);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getKeys',
    ol.source.TileImage.prototype.getKeys);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getProperties',
    ol.source.TileImage.prototype.getProperties);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'set',
    ol.source.TileImage.prototype.set);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'setProperties',
    ol.source.TileImage.prototype.setProperties);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'unset',
    ol.source.TileImage.prototype.unset);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'changed',
    ol.source.TileImage.prototype.changed);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'dispatchEvent',
    ol.source.TileImage.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'getRevision',
    ol.source.TileImage.prototype.getRevision);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'on',
    ol.source.TileImage.prototype.on);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'once',
    ol.source.TileImage.prototype.once);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'un',
    ol.source.TileImage.prototype.un);

goog.exportProperty(
    ol.source.TileImage.prototype,
    'unByKey',
    ol.source.TileImage.prototype.unByKey);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setRenderReprojectionEdges',
    ol.source.BingMaps.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setTileGridForProjection',
    ol.source.BingMaps.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getTileLoadFunction',
    ol.source.BingMaps.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getTileUrlFunction',
    ol.source.BingMaps.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getUrls',
    ol.source.BingMaps.prototype.getUrls);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setTileLoadFunction',
    ol.source.BingMaps.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setTileUrlFunction',
    ol.source.BingMaps.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setUrl',
    ol.source.BingMaps.prototype.setUrl);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setUrls',
    ol.source.BingMaps.prototype.setUrls);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getTileGrid',
    ol.source.BingMaps.prototype.getTileGrid);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'refresh',
    ol.source.BingMaps.prototype.refresh);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getAttributions',
    ol.source.BingMaps.prototype.getAttributions);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getLogo',
    ol.source.BingMaps.prototype.getLogo);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getProjection',
    ol.source.BingMaps.prototype.getProjection);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getState',
    ol.source.BingMaps.prototype.getState);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setAttributions',
    ol.source.BingMaps.prototype.setAttributions);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'get',
    ol.source.BingMaps.prototype.get);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getKeys',
    ol.source.BingMaps.prototype.getKeys);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getProperties',
    ol.source.BingMaps.prototype.getProperties);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'set',
    ol.source.BingMaps.prototype.set);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'setProperties',
    ol.source.BingMaps.prototype.setProperties);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'unset',
    ol.source.BingMaps.prototype.unset);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'changed',
    ol.source.BingMaps.prototype.changed);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'dispatchEvent',
    ol.source.BingMaps.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'getRevision',
    ol.source.BingMaps.prototype.getRevision);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'on',
    ol.source.BingMaps.prototype.on);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'once',
    ol.source.BingMaps.prototype.once);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'un',
    ol.source.BingMaps.prototype.un);

goog.exportProperty(
    ol.source.BingMaps.prototype,
    'unByKey',
    ol.source.BingMaps.prototype.unByKey);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setRenderReprojectionEdges',
    ol.source.XYZ.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setTileGridForProjection',
    ol.source.XYZ.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getTileLoadFunction',
    ol.source.XYZ.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getTileUrlFunction',
    ol.source.XYZ.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getUrls',
    ol.source.XYZ.prototype.getUrls);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setTileLoadFunction',
    ol.source.XYZ.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setTileUrlFunction',
    ol.source.XYZ.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setUrl',
    ol.source.XYZ.prototype.setUrl);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setUrls',
    ol.source.XYZ.prototype.setUrls);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getTileGrid',
    ol.source.XYZ.prototype.getTileGrid);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'refresh',
    ol.source.XYZ.prototype.refresh);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getAttributions',
    ol.source.XYZ.prototype.getAttributions);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getLogo',
    ol.source.XYZ.prototype.getLogo);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getProjection',
    ol.source.XYZ.prototype.getProjection);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getState',
    ol.source.XYZ.prototype.getState);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setAttributions',
    ol.source.XYZ.prototype.setAttributions);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'get',
    ol.source.XYZ.prototype.get);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getKeys',
    ol.source.XYZ.prototype.getKeys);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getProperties',
    ol.source.XYZ.prototype.getProperties);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'set',
    ol.source.XYZ.prototype.set);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'setProperties',
    ol.source.XYZ.prototype.setProperties);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'unset',
    ol.source.XYZ.prototype.unset);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'changed',
    ol.source.XYZ.prototype.changed);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'dispatchEvent',
    ol.source.XYZ.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'getRevision',
    ol.source.XYZ.prototype.getRevision);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'on',
    ol.source.XYZ.prototype.on);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'once',
    ol.source.XYZ.prototype.once);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'un',
    ol.source.XYZ.prototype.un);

goog.exportProperty(
    ol.source.XYZ.prototype,
    'unByKey',
    ol.source.XYZ.prototype.unByKey);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setRenderReprojectionEdges',
    ol.source.CartoDB.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setTileGridForProjection',
    ol.source.CartoDB.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getTileLoadFunction',
    ol.source.CartoDB.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getTileUrlFunction',
    ol.source.CartoDB.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getUrls',
    ol.source.CartoDB.prototype.getUrls);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setTileLoadFunction',
    ol.source.CartoDB.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setTileUrlFunction',
    ol.source.CartoDB.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setUrl',
    ol.source.CartoDB.prototype.setUrl);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setUrls',
    ol.source.CartoDB.prototype.setUrls);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getTileGrid',
    ol.source.CartoDB.prototype.getTileGrid);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'refresh',
    ol.source.CartoDB.prototype.refresh);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getAttributions',
    ol.source.CartoDB.prototype.getAttributions);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getLogo',
    ol.source.CartoDB.prototype.getLogo);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getProjection',
    ol.source.CartoDB.prototype.getProjection);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getState',
    ol.source.CartoDB.prototype.getState);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setAttributions',
    ol.source.CartoDB.prototype.setAttributions);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'get',
    ol.source.CartoDB.prototype.get);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getKeys',
    ol.source.CartoDB.prototype.getKeys);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getProperties',
    ol.source.CartoDB.prototype.getProperties);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'set',
    ol.source.CartoDB.prototype.set);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'setProperties',
    ol.source.CartoDB.prototype.setProperties);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'unset',
    ol.source.CartoDB.prototype.unset);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'changed',
    ol.source.CartoDB.prototype.changed);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'dispatchEvent',
    ol.source.CartoDB.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'getRevision',
    ol.source.CartoDB.prototype.getRevision);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'on',
    ol.source.CartoDB.prototype.on);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'once',
    ol.source.CartoDB.prototype.once);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'un',
    ol.source.CartoDB.prototype.un);

goog.exportProperty(
    ol.source.CartoDB.prototype,
    'unByKey',
    ol.source.CartoDB.prototype.unByKey);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getAttributions',
    ol.source.Vector.prototype.getAttributions);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getLogo',
    ol.source.Vector.prototype.getLogo);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getProjection',
    ol.source.Vector.prototype.getProjection);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getState',
    ol.source.Vector.prototype.getState);

goog.exportProperty(
    ol.source.Vector.prototype,
    'refresh',
    ol.source.Vector.prototype.refresh);

goog.exportProperty(
    ol.source.Vector.prototype,
    'setAttributions',
    ol.source.Vector.prototype.setAttributions);

goog.exportProperty(
    ol.source.Vector.prototype,
    'get',
    ol.source.Vector.prototype.get);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getKeys',
    ol.source.Vector.prototype.getKeys);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getProperties',
    ol.source.Vector.prototype.getProperties);

goog.exportProperty(
    ol.source.Vector.prototype,
    'set',
    ol.source.Vector.prototype.set);

goog.exportProperty(
    ol.source.Vector.prototype,
    'setProperties',
    ol.source.Vector.prototype.setProperties);

goog.exportProperty(
    ol.source.Vector.prototype,
    'unset',
    ol.source.Vector.prototype.unset);

goog.exportProperty(
    ol.source.Vector.prototype,
    'changed',
    ol.source.Vector.prototype.changed);

goog.exportProperty(
    ol.source.Vector.prototype,
    'dispatchEvent',
    ol.source.Vector.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Vector.prototype,
    'getRevision',
    ol.source.Vector.prototype.getRevision);

goog.exportProperty(
    ol.source.Vector.prototype,
    'on',
    ol.source.Vector.prototype.on);

goog.exportProperty(
    ol.source.Vector.prototype,
    'once',
    ol.source.Vector.prototype.once);

goog.exportProperty(
    ol.source.Vector.prototype,
    'un',
    ol.source.Vector.prototype.un);

goog.exportProperty(
    ol.source.Vector.prototype,
    'unByKey',
    ol.source.Vector.prototype.unByKey);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'addFeature',
    ol.source.Cluster.prototype.addFeature);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'addFeatures',
    ol.source.Cluster.prototype.addFeatures);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'clear',
    ol.source.Cluster.prototype.clear);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'forEachFeature',
    ol.source.Cluster.prototype.forEachFeature);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'forEachFeatureInExtent',
    ol.source.Cluster.prototype.forEachFeatureInExtent);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'forEachFeatureIntersectingExtent',
    ol.source.Cluster.prototype.forEachFeatureIntersectingExtent);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getFeaturesCollection',
    ol.source.Cluster.prototype.getFeaturesCollection);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getFeatures',
    ol.source.Cluster.prototype.getFeatures);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getFeaturesAtCoordinate',
    ol.source.Cluster.prototype.getFeaturesAtCoordinate);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getFeaturesInExtent',
    ol.source.Cluster.prototype.getFeaturesInExtent);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getClosestFeatureToCoordinate',
    ol.source.Cluster.prototype.getClosestFeatureToCoordinate);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getExtent',
    ol.source.Cluster.prototype.getExtent);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getFeatureById',
    ol.source.Cluster.prototype.getFeatureById);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getFormat',
    ol.source.Cluster.prototype.getFormat);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getUrl',
    ol.source.Cluster.prototype.getUrl);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'removeFeature',
    ol.source.Cluster.prototype.removeFeature);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getAttributions',
    ol.source.Cluster.prototype.getAttributions);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getLogo',
    ol.source.Cluster.prototype.getLogo);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getProjection',
    ol.source.Cluster.prototype.getProjection);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getState',
    ol.source.Cluster.prototype.getState);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'refresh',
    ol.source.Cluster.prototype.refresh);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'setAttributions',
    ol.source.Cluster.prototype.setAttributions);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'get',
    ol.source.Cluster.prototype.get);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getKeys',
    ol.source.Cluster.prototype.getKeys);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getProperties',
    ol.source.Cluster.prototype.getProperties);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'set',
    ol.source.Cluster.prototype.set);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'setProperties',
    ol.source.Cluster.prototype.setProperties);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'unset',
    ol.source.Cluster.prototype.unset);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'changed',
    ol.source.Cluster.prototype.changed);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'dispatchEvent',
    ol.source.Cluster.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'getRevision',
    ol.source.Cluster.prototype.getRevision);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'on',
    ol.source.Cluster.prototype.on);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'once',
    ol.source.Cluster.prototype.once);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'un',
    ol.source.Cluster.prototype.un);

goog.exportProperty(
    ol.source.Cluster.prototype,
    'unByKey',
    ol.source.Cluster.prototype.unByKey);

goog.exportProperty(
    ol.source.Image.prototype,
    'getAttributions',
    ol.source.Image.prototype.getAttributions);

goog.exportProperty(
    ol.source.Image.prototype,
    'getLogo',
    ol.source.Image.prototype.getLogo);

goog.exportProperty(
    ol.source.Image.prototype,
    'getProjection',
    ol.source.Image.prototype.getProjection);

goog.exportProperty(
    ol.source.Image.prototype,
    'getState',
    ol.source.Image.prototype.getState);

goog.exportProperty(
    ol.source.Image.prototype,
    'refresh',
    ol.source.Image.prototype.refresh);

goog.exportProperty(
    ol.source.Image.prototype,
    'setAttributions',
    ol.source.Image.prototype.setAttributions);

goog.exportProperty(
    ol.source.Image.prototype,
    'get',
    ol.source.Image.prototype.get);

goog.exportProperty(
    ol.source.Image.prototype,
    'getKeys',
    ol.source.Image.prototype.getKeys);

goog.exportProperty(
    ol.source.Image.prototype,
    'getProperties',
    ol.source.Image.prototype.getProperties);

goog.exportProperty(
    ol.source.Image.prototype,
    'set',
    ol.source.Image.prototype.set);

goog.exportProperty(
    ol.source.Image.prototype,
    'setProperties',
    ol.source.Image.prototype.setProperties);

goog.exportProperty(
    ol.source.Image.prototype,
    'unset',
    ol.source.Image.prototype.unset);

goog.exportProperty(
    ol.source.Image.prototype,
    'changed',
    ol.source.Image.prototype.changed);

goog.exportProperty(
    ol.source.Image.prototype,
    'dispatchEvent',
    ol.source.Image.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Image.prototype,
    'getRevision',
    ol.source.Image.prototype.getRevision);

goog.exportProperty(
    ol.source.Image.prototype,
    'on',
    ol.source.Image.prototype.on);

goog.exportProperty(
    ol.source.Image.prototype,
    'once',
    ol.source.Image.prototype.once);

goog.exportProperty(
    ol.source.Image.prototype,
    'un',
    ol.source.Image.prototype.un);

goog.exportProperty(
    ol.source.Image.prototype,
    'unByKey',
    ol.source.Image.prototype.unByKey);

goog.exportProperty(
    ol.source.ImageEvent.prototype,
    'type',
    ol.source.ImageEvent.prototype.type);

goog.exportProperty(
    ol.source.ImageEvent.prototype,
    'target',
    ol.source.ImageEvent.prototype.target);

goog.exportProperty(
    ol.source.ImageEvent.prototype,
    'preventDefault',
    ol.source.ImageEvent.prototype.preventDefault);

goog.exportProperty(
    ol.source.ImageEvent.prototype,
    'stopPropagation',
    ol.source.ImageEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getAttributions',
    ol.source.ImageArcGISRest.prototype.getAttributions);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getLogo',
    ol.source.ImageArcGISRest.prototype.getLogo);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getProjection',
    ol.source.ImageArcGISRest.prototype.getProjection);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getState',
    ol.source.ImageArcGISRest.prototype.getState);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'refresh',
    ol.source.ImageArcGISRest.prototype.refresh);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'setAttributions',
    ol.source.ImageArcGISRest.prototype.setAttributions);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'get',
    ol.source.ImageArcGISRest.prototype.get);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getKeys',
    ol.source.ImageArcGISRest.prototype.getKeys);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getProperties',
    ol.source.ImageArcGISRest.prototype.getProperties);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'set',
    ol.source.ImageArcGISRest.prototype.set);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'setProperties',
    ol.source.ImageArcGISRest.prototype.setProperties);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'unset',
    ol.source.ImageArcGISRest.prototype.unset);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'changed',
    ol.source.ImageArcGISRest.prototype.changed);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'dispatchEvent',
    ol.source.ImageArcGISRest.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'getRevision',
    ol.source.ImageArcGISRest.prototype.getRevision);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'on',
    ol.source.ImageArcGISRest.prototype.on);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'once',
    ol.source.ImageArcGISRest.prototype.once);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'un',
    ol.source.ImageArcGISRest.prototype.un);

goog.exportProperty(
    ol.source.ImageArcGISRest.prototype,
    'unByKey',
    ol.source.ImageArcGISRest.prototype.unByKey);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getAttributions',
    ol.source.ImageCanvas.prototype.getAttributions);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getLogo',
    ol.source.ImageCanvas.prototype.getLogo);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getProjection',
    ol.source.ImageCanvas.prototype.getProjection);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getState',
    ol.source.ImageCanvas.prototype.getState);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'refresh',
    ol.source.ImageCanvas.prototype.refresh);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'setAttributions',
    ol.source.ImageCanvas.prototype.setAttributions);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'get',
    ol.source.ImageCanvas.prototype.get);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getKeys',
    ol.source.ImageCanvas.prototype.getKeys);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getProperties',
    ol.source.ImageCanvas.prototype.getProperties);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'set',
    ol.source.ImageCanvas.prototype.set);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'setProperties',
    ol.source.ImageCanvas.prototype.setProperties);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'unset',
    ol.source.ImageCanvas.prototype.unset);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'changed',
    ol.source.ImageCanvas.prototype.changed);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'dispatchEvent',
    ol.source.ImageCanvas.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'getRevision',
    ol.source.ImageCanvas.prototype.getRevision);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'on',
    ol.source.ImageCanvas.prototype.on);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'once',
    ol.source.ImageCanvas.prototype.once);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'un',
    ol.source.ImageCanvas.prototype.un);

goog.exportProperty(
    ol.source.ImageCanvas.prototype,
    'unByKey',
    ol.source.ImageCanvas.prototype.unByKey);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getAttributions',
    ol.source.ImageMapGuide.prototype.getAttributions);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getLogo',
    ol.source.ImageMapGuide.prototype.getLogo);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getProjection',
    ol.source.ImageMapGuide.prototype.getProjection);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getState',
    ol.source.ImageMapGuide.prototype.getState);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'refresh',
    ol.source.ImageMapGuide.prototype.refresh);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'setAttributions',
    ol.source.ImageMapGuide.prototype.setAttributions);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'get',
    ol.source.ImageMapGuide.prototype.get);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getKeys',
    ol.source.ImageMapGuide.prototype.getKeys);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getProperties',
    ol.source.ImageMapGuide.prototype.getProperties);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'set',
    ol.source.ImageMapGuide.prototype.set);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'setProperties',
    ol.source.ImageMapGuide.prototype.setProperties);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'unset',
    ol.source.ImageMapGuide.prototype.unset);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'changed',
    ol.source.ImageMapGuide.prototype.changed);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'dispatchEvent',
    ol.source.ImageMapGuide.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'getRevision',
    ol.source.ImageMapGuide.prototype.getRevision);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'on',
    ol.source.ImageMapGuide.prototype.on);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'once',
    ol.source.ImageMapGuide.prototype.once);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'un',
    ol.source.ImageMapGuide.prototype.un);

goog.exportProperty(
    ol.source.ImageMapGuide.prototype,
    'unByKey',
    ol.source.ImageMapGuide.prototype.unByKey);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getAttributions',
    ol.source.ImageStatic.prototype.getAttributions);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getLogo',
    ol.source.ImageStatic.prototype.getLogo);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getProjection',
    ol.source.ImageStatic.prototype.getProjection);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getState',
    ol.source.ImageStatic.prototype.getState);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'refresh',
    ol.source.ImageStatic.prototype.refresh);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'setAttributions',
    ol.source.ImageStatic.prototype.setAttributions);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'get',
    ol.source.ImageStatic.prototype.get);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getKeys',
    ol.source.ImageStatic.prototype.getKeys);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getProperties',
    ol.source.ImageStatic.prototype.getProperties);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'set',
    ol.source.ImageStatic.prototype.set);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'setProperties',
    ol.source.ImageStatic.prototype.setProperties);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'unset',
    ol.source.ImageStatic.prototype.unset);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'changed',
    ol.source.ImageStatic.prototype.changed);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'dispatchEvent',
    ol.source.ImageStatic.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'getRevision',
    ol.source.ImageStatic.prototype.getRevision);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'on',
    ol.source.ImageStatic.prototype.on);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'once',
    ol.source.ImageStatic.prototype.once);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'un',
    ol.source.ImageStatic.prototype.un);

goog.exportProperty(
    ol.source.ImageStatic.prototype,
    'unByKey',
    ol.source.ImageStatic.prototype.unByKey);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getAttributions',
    ol.source.ImageVector.prototype.getAttributions);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getLogo',
    ol.source.ImageVector.prototype.getLogo);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getProjection',
    ol.source.ImageVector.prototype.getProjection);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getState',
    ol.source.ImageVector.prototype.getState);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'refresh',
    ol.source.ImageVector.prototype.refresh);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'setAttributions',
    ol.source.ImageVector.prototype.setAttributions);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'get',
    ol.source.ImageVector.prototype.get);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getKeys',
    ol.source.ImageVector.prototype.getKeys);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getProperties',
    ol.source.ImageVector.prototype.getProperties);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'set',
    ol.source.ImageVector.prototype.set);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'setProperties',
    ol.source.ImageVector.prototype.setProperties);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'unset',
    ol.source.ImageVector.prototype.unset);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'changed',
    ol.source.ImageVector.prototype.changed);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'dispatchEvent',
    ol.source.ImageVector.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'getRevision',
    ol.source.ImageVector.prototype.getRevision);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'on',
    ol.source.ImageVector.prototype.on);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'once',
    ol.source.ImageVector.prototype.once);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'un',
    ol.source.ImageVector.prototype.un);

goog.exportProperty(
    ol.source.ImageVector.prototype,
    'unByKey',
    ol.source.ImageVector.prototype.unByKey);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getAttributions',
    ol.source.ImageWMS.prototype.getAttributions);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getLogo',
    ol.source.ImageWMS.prototype.getLogo);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getProjection',
    ol.source.ImageWMS.prototype.getProjection);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getState',
    ol.source.ImageWMS.prototype.getState);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'refresh',
    ol.source.ImageWMS.prototype.refresh);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'setAttributions',
    ol.source.ImageWMS.prototype.setAttributions);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'get',
    ol.source.ImageWMS.prototype.get);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getKeys',
    ol.source.ImageWMS.prototype.getKeys);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getProperties',
    ol.source.ImageWMS.prototype.getProperties);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'set',
    ol.source.ImageWMS.prototype.set);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'setProperties',
    ol.source.ImageWMS.prototype.setProperties);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'unset',
    ol.source.ImageWMS.prototype.unset);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'changed',
    ol.source.ImageWMS.prototype.changed);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'dispatchEvent',
    ol.source.ImageWMS.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'getRevision',
    ol.source.ImageWMS.prototype.getRevision);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'on',
    ol.source.ImageWMS.prototype.on);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'once',
    ol.source.ImageWMS.prototype.once);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'un',
    ol.source.ImageWMS.prototype.un);

goog.exportProperty(
    ol.source.ImageWMS.prototype,
    'unByKey',
    ol.source.ImageWMS.prototype.unByKey);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setRenderReprojectionEdges',
    ol.source.OSM.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setTileGridForProjection',
    ol.source.OSM.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getTileLoadFunction',
    ol.source.OSM.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getTileUrlFunction',
    ol.source.OSM.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getUrls',
    ol.source.OSM.prototype.getUrls);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setTileLoadFunction',
    ol.source.OSM.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setTileUrlFunction',
    ol.source.OSM.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setUrl',
    ol.source.OSM.prototype.setUrl);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setUrls',
    ol.source.OSM.prototype.setUrls);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getTileGrid',
    ol.source.OSM.prototype.getTileGrid);

goog.exportProperty(
    ol.source.OSM.prototype,
    'refresh',
    ol.source.OSM.prototype.refresh);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getAttributions',
    ol.source.OSM.prototype.getAttributions);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getLogo',
    ol.source.OSM.prototype.getLogo);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getProjection',
    ol.source.OSM.prototype.getProjection);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getState',
    ol.source.OSM.prototype.getState);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setAttributions',
    ol.source.OSM.prototype.setAttributions);

goog.exportProperty(
    ol.source.OSM.prototype,
    'get',
    ol.source.OSM.prototype.get);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getKeys',
    ol.source.OSM.prototype.getKeys);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getProperties',
    ol.source.OSM.prototype.getProperties);

goog.exportProperty(
    ol.source.OSM.prototype,
    'set',
    ol.source.OSM.prototype.set);

goog.exportProperty(
    ol.source.OSM.prototype,
    'setProperties',
    ol.source.OSM.prototype.setProperties);

goog.exportProperty(
    ol.source.OSM.prototype,
    'unset',
    ol.source.OSM.prototype.unset);

goog.exportProperty(
    ol.source.OSM.prototype,
    'changed',
    ol.source.OSM.prototype.changed);

goog.exportProperty(
    ol.source.OSM.prototype,
    'dispatchEvent',
    ol.source.OSM.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.OSM.prototype,
    'getRevision',
    ol.source.OSM.prototype.getRevision);

goog.exportProperty(
    ol.source.OSM.prototype,
    'on',
    ol.source.OSM.prototype.on);

goog.exportProperty(
    ol.source.OSM.prototype,
    'once',
    ol.source.OSM.prototype.once);

goog.exportProperty(
    ol.source.OSM.prototype,
    'un',
    ol.source.OSM.prototype.un);

goog.exportProperty(
    ol.source.OSM.prototype,
    'unByKey',
    ol.source.OSM.prototype.unByKey);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getAttributions',
    ol.source.Raster.prototype.getAttributions);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getLogo',
    ol.source.Raster.prototype.getLogo);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getProjection',
    ol.source.Raster.prototype.getProjection);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getState',
    ol.source.Raster.prototype.getState);

goog.exportProperty(
    ol.source.Raster.prototype,
    'refresh',
    ol.source.Raster.prototype.refresh);

goog.exportProperty(
    ol.source.Raster.prototype,
    'setAttributions',
    ol.source.Raster.prototype.setAttributions);

goog.exportProperty(
    ol.source.Raster.prototype,
    'get',
    ol.source.Raster.prototype.get);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getKeys',
    ol.source.Raster.prototype.getKeys);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getProperties',
    ol.source.Raster.prototype.getProperties);

goog.exportProperty(
    ol.source.Raster.prototype,
    'set',
    ol.source.Raster.prototype.set);

goog.exportProperty(
    ol.source.Raster.prototype,
    'setProperties',
    ol.source.Raster.prototype.setProperties);

goog.exportProperty(
    ol.source.Raster.prototype,
    'unset',
    ol.source.Raster.prototype.unset);

goog.exportProperty(
    ol.source.Raster.prototype,
    'changed',
    ol.source.Raster.prototype.changed);

goog.exportProperty(
    ol.source.Raster.prototype,
    'dispatchEvent',
    ol.source.Raster.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Raster.prototype,
    'getRevision',
    ol.source.Raster.prototype.getRevision);

goog.exportProperty(
    ol.source.Raster.prototype,
    'on',
    ol.source.Raster.prototype.on);

goog.exportProperty(
    ol.source.Raster.prototype,
    'once',
    ol.source.Raster.prototype.once);

goog.exportProperty(
    ol.source.Raster.prototype,
    'un',
    ol.source.Raster.prototype.un);

goog.exportProperty(
    ol.source.Raster.prototype,
    'unByKey',
    ol.source.Raster.prototype.unByKey);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'type',
    ol.source.RasterEvent.prototype.type);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'target',
    ol.source.RasterEvent.prototype.target);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'preventDefault',
    ol.source.RasterEvent.prototype.preventDefault);

goog.exportProperty(
    ol.source.RasterEvent.prototype,
    'stopPropagation',
    ol.source.RasterEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setRenderReprojectionEdges',
    ol.source.Stamen.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setTileGridForProjection',
    ol.source.Stamen.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getTileLoadFunction',
    ol.source.Stamen.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getTileUrlFunction',
    ol.source.Stamen.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getUrls',
    ol.source.Stamen.prototype.getUrls);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setTileLoadFunction',
    ol.source.Stamen.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setTileUrlFunction',
    ol.source.Stamen.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setUrl',
    ol.source.Stamen.prototype.setUrl);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setUrls',
    ol.source.Stamen.prototype.setUrls);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getTileGrid',
    ol.source.Stamen.prototype.getTileGrid);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'refresh',
    ol.source.Stamen.prototype.refresh);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getAttributions',
    ol.source.Stamen.prototype.getAttributions);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getLogo',
    ol.source.Stamen.prototype.getLogo);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getProjection',
    ol.source.Stamen.prototype.getProjection);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getState',
    ol.source.Stamen.prototype.getState);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setAttributions',
    ol.source.Stamen.prototype.setAttributions);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'get',
    ol.source.Stamen.prototype.get);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getKeys',
    ol.source.Stamen.prototype.getKeys);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getProperties',
    ol.source.Stamen.prototype.getProperties);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'set',
    ol.source.Stamen.prototype.set);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'setProperties',
    ol.source.Stamen.prototype.setProperties);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'unset',
    ol.source.Stamen.prototype.unset);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'changed',
    ol.source.Stamen.prototype.changed);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'dispatchEvent',
    ol.source.Stamen.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'getRevision',
    ol.source.Stamen.prototype.getRevision);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'on',
    ol.source.Stamen.prototype.on);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'once',
    ol.source.Stamen.prototype.once);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'un',
    ol.source.Stamen.prototype.un);

goog.exportProperty(
    ol.source.Stamen.prototype,
    'unByKey',
    ol.source.Stamen.prototype.unByKey);

goog.exportProperty(
    ol.source.TileEvent.prototype,
    'type',
    ol.source.TileEvent.prototype.type);

goog.exportProperty(
    ol.source.TileEvent.prototype,
    'target',
    ol.source.TileEvent.prototype.target);

goog.exportProperty(
    ol.source.TileEvent.prototype,
    'preventDefault',
    ol.source.TileEvent.prototype.preventDefault);

goog.exportProperty(
    ol.source.TileEvent.prototype,
    'stopPropagation',
    ol.source.TileEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setRenderReprojectionEdges',
    ol.source.TileArcGISRest.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setTileGridForProjection',
    ol.source.TileArcGISRest.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getTileLoadFunction',
    ol.source.TileArcGISRest.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getTileUrlFunction',
    ol.source.TileArcGISRest.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getUrls',
    ol.source.TileArcGISRest.prototype.getUrls);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setTileLoadFunction',
    ol.source.TileArcGISRest.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setTileUrlFunction',
    ol.source.TileArcGISRest.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setUrl',
    ol.source.TileArcGISRest.prototype.setUrl);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setUrls',
    ol.source.TileArcGISRest.prototype.setUrls);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getTileGrid',
    ol.source.TileArcGISRest.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'refresh',
    ol.source.TileArcGISRest.prototype.refresh);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getAttributions',
    ol.source.TileArcGISRest.prototype.getAttributions);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getLogo',
    ol.source.TileArcGISRest.prototype.getLogo);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getProjection',
    ol.source.TileArcGISRest.prototype.getProjection);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getState',
    ol.source.TileArcGISRest.prototype.getState);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setAttributions',
    ol.source.TileArcGISRest.prototype.setAttributions);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'get',
    ol.source.TileArcGISRest.prototype.get);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getKeys',
    ol.source.TileArcGISRest.prototype.getKeys);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getProperties',
    ol.source.TileArcGISRest.prototype.getProperties);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'set',
    ol.source.TileArcGISRest.prototype.set);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'setProperties',
    ol.source.TileArcGISRest.prototype.setProperties);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'unset',
    ol.source.TileArcGISRest.prototype.unset);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'changed',
    ol.source.TileArcGISRest.prototype.changed);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'dispatchEvent',
    ol.source.TileArcGISRest.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'getRevision',
    ol.source.TileArcGISRest.prototype.getRevision);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'on',
    ol.source.TileArcGISRest.prototype.on);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'once',
    ol.source.TileArcGISRest.prototype.once);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'un',
    ol.source.TileArcGISRest.prototype.un);

goog.exportProperty(
    ol.source.TileArcGISRest.prototype,
    'unByKey',
    ol.source.TileArcGISRest.prototype.unByKey);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getTileGrid',
    ol.source.TileDebug.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'refresh',
    ol.source.TileDebug.prototype.refresh);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getAttributions',
    ol.source.TileDebug.prototype.getAttributions);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getLogo',
    ol.source.TileDebug.prototype.getLogo);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getProjection',
    ol.source.TileDebug.prototype.getProjection);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getState',
    ol.source.TileDebug.prototype.getState);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'setAttributions',
    ol.source.TileDebug.prototype.setAttributions);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'get',
    ol.source.TileDebug.prototype.get);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getKeys',
    ol.source.TileDebug.prototype.getKeys);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getProperties',
    ol.source.TileDebug.prototype.getProperties);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'set',
    ol.source.TileDebug.prototype.set);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'setProperties',
    ol.source.TileDebug.prototype.setProperties);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'unset',
    ol.source.TileDebug.prototype.unset);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'changed',
    ol.source.TileDebug.prototype.changed);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'dispatchEvent',
    ol.source.TileDebug.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'getRevision',
    ol.source.TileDebug.prototype.getRevision);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'on',
    ol.source.TileDebug.prototype.on);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'once',
    ol.source.TileDebug.prototype.once);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'un',
    ol.source.TileDebug.prototype.un);

goog.exportProperty(
    ol.source.TileDebug.prototype,
    'unByKey',
    ol.source.TileDebug.prototype.unByKey);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setRenderReprojectionEdges',
    ol.source.TileJSON.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setTileGridForProjection',
    ol.source.TileJSON.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getTileLoadFunction',
    ol.source.TileJSON.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getTileUrlFunction',
    ol.source.TileJSON.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getUrls',
    ol.source.TileJSON.prototype.getUrls);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setTileLoadFunction',
    ol.source.TileJSON.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setTileUrlFunction',
    ol.source.TileJSON.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setUrl',
    ol.source.TileJSON.prototype.setUrl);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setUrls',
    ol.source.TileJSON.prototype.setUrls);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getTileGrid',
    ol.source.TileJSON.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'refresh',
    ol.source.TileJSON.prototype.refresh);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getAttributions',
    ol.source.TileJSON.prototype.getAttributions);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getLogo',
    ol.source.TileJSON.prototype.getLogo);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getProjection',
    ol.source.TileJSON.prototype.getProjection);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getState',
    ol.source.TileJSON.prototype.getState);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setAttributions',
    ol.source.TileJSON.prototype.setAttributions);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'get',
    ol.source.TileJSON.prototype.get);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getKeys',
    ol.source.TileJSON.prototype.getKeys);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getProperties',
    ol.source.TileJSON.prototype.getProperties);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'set',
    ol.source.TileJSON.prototype.set);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'setProperties',
    ol.source.TileJSON.prototype.setProperties);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'unset',
    ol.source.TileJSON.prototype.unset);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'changed',
    ol.source.TileJSON.prototype.changed);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'dispatchEvent',
    ol.source.TileJSON.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'getRevision',
    ol.source.TileJSON.prototype.getRevision);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'on',
    ol.source.TileJSON.prototype.on);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'once',
    ol.source.TileJSON.prototype.once);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'un',
    ol.source.TileJSON.prototype.un);

goog.exportProperty(
    ol.source.TileJSON.prototype,
    'unByKey',
    ol.source.TileJSON.prototype.unByKey);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getTileGrid',
    ol.source.TileUTFGrid.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'refresh',
    ol.source.TileUTFGrid.prototype.refresh);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getAttributions',
    ol.source.TileUTFGrid.prototype.getAttributions);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getLogo',
    ol.source.TileUTFGrid.prototype.getLogo);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getProjection',
    ol.source.TileUTFGrid.prototype.getProjection);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getState',
    ol.source.TileUTFGrid.prototype.getState);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'setAttributions',
    ol.source.TileUTFGrid.prototype.setAttributions);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'get',
    ol.source.TileUTFGrid.prototype.get);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getKeys',
    ol.source.TileUTFGrid.prototype.getKeys);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getProperties',
    ol.source.TileUTFGrid.prototype.getProperties);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'set',
    ol.source.TileUTFGrid.prototype.set);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'setProperties',
    ol.source.TileUTFGrid.prototype.setProperties);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'unset',
    ol.source.TileUTFGrid.prototype.unset);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'changed',
    ol.source.TileUTFGrid.prototype.changed);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'dispatchEvent',
    ol.source.TileUTFGrid.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'getRevision',
    ol.source.TileUTFGrid.prototype.getRevision);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'on',
    ol.source.TileUTFGrid.prototype.on);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'once',
    ol.source.TileUTFGrid.prototype.once);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'un',
    ol.source.TileUTFGrid.prototype.un);

goog.exportProperty(
    ol.source.TileUTFGrid.prototype,
    'unByKey',
    ol.source.TileUTFGrid.prototype.unByKey);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setRenderReprojectionEdges',
    ol.source.TileWMS.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setTileGridForProjection',
    ol.source.TileWMS.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getTileLoadFunction',
    ol.source.TileWMS.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getTileUrlFunction',
    ol.source.TileWMS.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getUrls',
    ol.source.TileWMS.prototype.getUrls);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setTileLoadFunction',
    ol.source.TileWMS.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setTileUrlFunction',
    ol.source.TileWMS.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setUrl',
    ol.source.TileWMS.prototype.setUrl);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setUrls',
    ol.source.TileWMS.prototype.setUrls);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getTileGrid',
    ol.source.TileWMS.prototype.getTileGrid);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'refresh',
    ol.source.TileWMS.prototype.refresh);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getAttributions',
    ol.source.TileWMS.prototype.getAttributions);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getLogo',
    ol.source.TileWMS.prototype.getLogo);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getProjection',
    ol.source.TileWMS.prototype.getProjection);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getState',
    ol.source.TileWMS.prototype.getState);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setAttributions',
    ol.source.TileWMS.prototype.setAttributions);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'get',
    ol.source.TileWMS.prototype.get);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getKeys',
    ol.source.TileWMS.prototype.getKeys);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getProperties',
    ol.source.TileWMS.prototype.getProperties);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'set',
    ol.source.TileWMS.prototype.set);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'setProperties',
    ol.source.TileWMS.prototype.setProperties);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'unset',
    ol.source.TileWMS.prototype.unset);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'changed',
    ol.source.TileWMS.prototype.changed);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'dispatchEvent',
    ol.source.TileWMS.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'getRevision',
    ol.source.TileWMS.prototype.getRevision);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'on',
    ol.source.TileWMS.prototype.on);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'once',
    ol.source.TileWMS.prototype.once);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'un',
    ol.source.TileWMS.prototype.un);

goog.exportProperty(
    ol.source.TileWMS.prototype,
    'unByKey',
    ol.source.TileWMS.prototype.unByKey);

goog.exportProperty(
    ol.source.VectorEvent.prototype,
    'type',
    ol.source.VectorEvent.prototype.type);

goog.exportProperty(
    ol.source.VectorEvent.prototype,
    'target',
    ol.source.VectorEvent.prototype.target);

goog.exportProperty(
    ol.source.VectorEvent.prototype,
    'preventDefault',
    ol.source.VectorEvent.prototype.preventDefault);

goog.exportProperty(
    ol.source.VectorEvent.prototype,
    'stopPropagation',
    ol.source.VectorEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getTileLoadFunction',
    ol.source.VectorTile.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getTileUrlFunction',
    ol.source.VectorTile.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getUrls',
    ol.source.VectorTile.prototype.getUrls);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'setTileLoadFunction',
    ol.source.VectorTile.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'setTileUrlFunction',
    ol.source.VectorTile.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'setUrl',
    ol.source.VectorTile.prototype.setUrl);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'setUrls',
    ol.source.VectorTile.prototype.setUrls);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getTileGrid',
    ol.source.VectorTile.prototype.getTileGrid);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'refresh',
    ol.source.VectorTile.prototype.refresh);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getAttributions',
    ol.source.VectorTile.prototype.getAttributions);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getLogo',
    ol.source.VectorTile.prototype.getLogo);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getProjection',
    ol.source.VectorTile.prototype.getProjection);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getState',
    ol.source.VectorTile.prototype.getState);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'setAttributions',
    ol.source.VectorTile.prototype.setAttributions);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'get',
    ol.source.VectorTile.prototype.get);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getKeys',
    ol.source.VectorTile.prototype.getKeys);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getProperties',
    ol.source.VectorTile.prototype.getProperties);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'set',
    ol.source.VectorTile.prototype.set);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'setProperties',
    ol.source.VectorTile.prototype.setProperties);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'unset',
    ol.source.VectorTile.prototype.unset);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'changed',
    ol.source.VectorTile.prototype.changed);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'dispatchEvent',
    ol.source.VectorTile.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'getRevision',
    ol.source.VectorTile.prototype.getRevision);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'on',
    ol.source.VectorTile.prototype.on);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'once',
    ol.source.VectorTile.prototype.once);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'un',
    ol.source.VectorTile.prototype.un);

goog.exportProperty(
    ol.source.VectorTile.prototype,
    'unByKey',
    ol.source.VectorTile.prototype.unByKey);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setRenderReprojectionEdges',
    ol.source.WMTS.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setTileGridForProjection',
    ol.source.WMTS.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getTileLoadFunction',
    ol.source.WMTS.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getTileUrlFunction',
    ol.source.WMTS.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getUrls',
    ol.source.WMTS.prototype.getUrls);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setTileLoadFunction',
    ol.source.WMTS.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setTileUrlFunction',
    ol.source.WMTS.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setUrl',
    ol.source.WMTS.prototype.setUrl);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setUrls',
    ol.source.WMTS.prototype.setUrls);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getTileGrid',
    ol.source.WMTS.prototype.getTileGrid);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'refresh',
    ol.source.WMTS.prototype.refresh);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getAttributions',
    ol.source.WMTS.prototype.getAttributions);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getLogo',
    ol.source.WMTS.prototype.getLogo);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getProjection',
    ol.source.WMTS.prototype.getProjection);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getState',
    ol.source.WMTS.prototype.getState);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setAttributions',
    ol.source.WMTS.prototype.setAttributions);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'get',
    ol.source.WMTS.prototype.get);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getKeys',
    ol.source.WMTS.prototype.getKeys);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getProperties',
    ol.source.WMTS.prototype.getProperties);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'set',
    ol.source.WMTS.prototype.set);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'setProperties',
    ol.source.WMTS.prototype.setProperties);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'unset',
    ol.source.WMTS.prototype.unset);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'changed',
    ol.source.WMTS.prototype.changed);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'dispatchEvent',
    ol.source.WMTS.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'getRevision',
    ol.source.WMTS.prototype.getRevision);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'on',
    ol.source.WMTS.prototype.on);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'once',
    ol.source.WMTS.prototype.once);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'un',
    ol.source.WMTS.prototype.un);

goog.exportProperty(
    ol.source.WMTS.prototype,
    'unByKey',
    ol.source.WMTS.prototype.unByKey);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setRenderReprojectionEdges',
    ol.source.Zoomify.prototype.setRenderReprojectionEdges);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setTileGridForProjection',
    ol.source.Zoomify.prototype.setTileGridForProjection);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getTileLoadFunction',
    ol.source.Zoomify.prototype.getTileLoadFunction);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getTileUrlFunction',
    ol.source.Zoomify.prototype.getTileUrlFunction);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getUrls',
    ol.source.Zoomify.prototype.getUrls);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setTileLoadFunction',
    ol.source.Zoomify.prototype.setTileLoadFunction);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setTileUrlFunction',
    ol.source.Zoomify.prototype.setTileUrlFunction);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setUrl',
    ol.source.Zoomify.prototype.setUrl);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setUrls',
    ol.source.Zoomify.prototype.setUrls);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getTileGrid',
    ol.source.Zoomify.prototype.getTileGrid);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'refresh',
    ol.source.Zoomify.prototype.refresh);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getAttributions',
    ol.source.Zoomify.prototype.getAttributions);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getLogo',
    ol.source.Zoomify.prototype.getLogo);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getProjection',
    ol.source.Zoomify.prototype.getProjection);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getState',
    ol.source.Zoomify.prototype.getState);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setAttributions',
    ol.source.Zoomify.prototype.setAttributions);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'get',
    ol.source.Zoomify.prototype.get);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getKeys',
    ol.source.Zoomify.prototype.getKeys);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getProperties',
    ol.source.Zoomify.prototype.getProperties);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'set',
    ol.source.Zoomify.prototype.set);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'setProperties',
    ol.source.Zoomify.prototype.setProperties);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'unset',
    ol.source.Zoomify.prototype.unset);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'changed',
    ol.source.Zoomify.prototype.changed);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'dispatchEvent',
    ol.source.Zoomify.prototype.dispatchEvent);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'getRevision',
    ol.source.Zoomify.prototype.getRevision);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'on',
    ol.source.Zoomify.prototype.on);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'once',
    ol.source.Zoomify.prototype.once);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'un',
    ol.source.Zoomify.prototype.un);

goog.exportProperty(
    ol.source.Zoomify.prototype,
    'unByKey',
    ol.source.Zoomify.prototype.unByKey);

goog.exportProperty(
    ol.reproj.Tile.prototype,
    'getTileCoord',
    ol.reproj.Tile.prototype.getTileCoord);

goog.exportProperty(
    ol.reproj.Tile.prototype,
    'load',
    ol.reproj.Tile.prototype.load);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'changed',
    ol.renderer.Layer.prototype.changed);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'dispatchEvent',
    ol.renderer.Layer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'getRevision',
    ol.renderer.Layer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'on',
    ol.renderer.Layer.prototype.on);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'once',
    ol.renderer.Layer.prototype.once);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'un',
    ol.renderer.Layer.prototype.un);

goog.exportProperty(
    ol.renderer.Layer.prototype,
    'unByKey',
    ol.renderer.Layer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'changed',
    ol.renderer.webgl.Layer.prototype.changed);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'dispatchEvent',
    ol.renderer.webgl.Layer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'getRevision',
    ol.renderer.webgl.Layer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'on',
    ol.renderer.webgl.Layer.prototype.on);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'once',
    ol.renderer.webgl.Layer.prototype.once);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'un',
    ol.renderer.webgl.Layer.prototype.un);

goog.exportProperty(
    ol.renderer.webgl.Layer.prototype,
    'unByKey',
    ol.renderer.webgl.Layer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'changed',
    ol.renderer.webgl.ImageLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'dispatchEvent',
    ol.renderer.webgl.ImageLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'getRevision',
    ol.renderer.webgl.ImageLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'on',
    ol.renderer.webgl.ImageLayer.prototype.on);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'once',
    ol.renderer.webgl.ImageLayer.prototype.once);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'un',
    ol.renderer.webgl.ImageLayer.prototype.un);

goog.exportProperty(
    ol.renderer.webgl.ImageLayer.prototype,
    'unByKey',
    ol.renderer.webgl.ImageLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'changed',
    ol.renderer.webgl.TileLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'dispatchEvent',
    ol.renderer.webgl.TileLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'getRevision',
    ol.renderer.webgl.TileLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'on',
    ol.renderer.webgl.TileLayer.prototype.on);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'once',
    ol.renderer.webgl.TileLayer.prototype.once);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'un',
    ol.renderer.webgl.TileLayer.prototype.un);

goog.exportProperty(
    ol.renderer.webgl.TileLayer.prototype,
    'unByKey',
    ol.renderer.webgl.TileLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'changed',
    ol.renderer.webgl.VectorLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'dispatchEvent',
    ol.renderer.webgl.VectorLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'getRevision',
    ol.renderer.webgl.VectorLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'on',
    ol.renderer.webgl.VectorLayer.prototype.on);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'once',
    ol.renderer.webgl.VectorLayer.prototype.once);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'un',
    ol.renderer.webgl.VectorLayer.prototype.un);

goog.exportProperty(
    ol.renderer.webgl.VectorLayer.prototype,
    'unByKey',
    ol.renderer.webgl.VectorLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'changed',
    ol.renderer.dom.Layer.prototype.changed);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'dispatchEvent',
    ol.renderer.dom.Layer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'getRevision',
    ol.renderer.dom.Layer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'on',
    ol.renderer.dom.Layer.prototype.on);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'once',
    ol.renderer.dom.Layer.prototype.once);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'un',
    ol.renderer.dom.Layer.prototype.un);

goog.exportProperty(
    ol.renderer.dom.Layer.prototype,
    'unByKey',
    ol.renderer.dom.Layer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'changed',
    ol.renderer.dom.ImageLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'dispatchEvent',
    ol.renderer.dom.ImageLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'getRevision',
    ol.renderer.dom.ImageLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'on',
    ol.renderer.dom.ImageLayer.prototype.on);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'once',
    ol.renderer.dom.ImageLayer.prototype.once);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'un',
    ol.renderer.dom.ImageLayer.prototype.un);

goog.exportProperty(
    ol.renderer.dom.ImageLayer.prototype,
    'unByKey',
    ol.renderer.dom.ImageLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'changed',
    ol.renderer.dom.TileLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'dispatchEvent',
    ol.renderer.dom.TileLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'getRevision',
    ol.renderer.dom.TileLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'on',
    ol.renderer.dom.TileLayer.prototype.on);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'once',
    ol.renderer.dom.TileLayer.prototype.once);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'un',
    ol.renderer.dom.TileLayer.prototype.un);

goog.exportProperty(
    ol.renderer.dom.TileLayer.prototype,
    'unByKey',
    ol.renderer.dom.TileLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'changed',
    ol.renderer.dom.VectorLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'dispatchEvent',
    ol.renderer.dom.VectorLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'getRevision',
    ol.renderer.dom.VectorLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'on',
    ol.renderer.dom.VectorLayer.prototype.on);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'once',
    ol.renderer.dom.VectorLayer.prototype.once);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'un',
    ol.renderer.dom.VectorLayer.prototype.un);

goog.exportProperty(
    ol.renderer.dom.VectorLayer.prototype,
    'unByKey',
    ol.renderer.dom.VectorLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'changed',
    ol.renderer.canvas.Layer.prototype.changed);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'dispatchEvent',
    ol.renderer.canvas.Layer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'getRevision',
    ol.renderer.canvas.Layer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'on',
    ol.renderer.canvas.Layer.prototype.on);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'once',
    ol.renderer.canvas.Layer.prototype.once);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'un',
    ol.renderer.canvas.Layer.prototype.un);

goog.exportProperty(
    ol.renderer.canvas.Layer.prototype,
    'unByKey',
    ol.renderer.canvas.Layer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'changed',
    ol.renderer.canvas.ImageLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'dispatchEvent',
    ol.renderer.canvas.ImageLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'getRevision',
    ol.renderer.canvas.ImageLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'on',
    ol.renderer.canvas.ImageLayer.prototype.on);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'once',
    ol.renderer.canvas.ImageLayer.prototype.once);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'un',
    ol.renderer.canvas.ImageLayer.prototype.un);

goog.exportProperty(
    ol.renderer.canvas.ImageLayer.prototype,
    'unByKey',
    ol.renderer.canvas.ImageLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'changed',
    ol.renderer.canvas.TileLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'dispatchEvent',
    ol.renderer.canvas.TileLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'getRevision',
    ol.renderer.canvas.TileLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'on',
    ol.renderer.canvas.TileLayer.prototype.on);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'once',
    ol.renderer.canvas.TileLayer.prototype.once);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'un',
    ol.renderer.canvas.TileLayer.prototype.un);

goog.exportProperty(
    ol.renderer.canvas.TileLayer.prototype,
    'unByKey',
    ol.renderer.canvas.TileLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'changed',
    ol.renderer.canvas.VectorLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'dispatchEvent',
    ol.renderer.canvas.VectorLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'getRevision',
    ol.renderer.canvas.VectorLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'on',
    ol.renderer.canvas.VectorLayer.prototype.on);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'once',
    ol.renderer.canvas.VectorLayer.prototype.once);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'un',
    ol.renderer.canvas.VectorLayer.prototype.un);

goog.exportProperty(
    ol.renderer.canvas.VectorLayer.prototype,
    'unByKey',
    ol.renderer.canvas.VectorLayer.prototype.unByKey);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'changed',
    ol.renderer.canvas.VectorTileLayer.prototype.changed);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'dispatchEvent',
    ol.renderer.canvas.VectorTileLayer.prototype.dispatchEvent);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'getRevision',
    ol.renderer.canvas.VectorTileLayer.prototype.getRevision);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'on',
    ol.renderer.canvas.VectorTileLayer.prototype.on);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'once',
    ol.renderer.canvas.VectorTileLayer.prototype.once);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'un',
    ol.renderer.canvas.VectorTileLayer.prototype.un);

goog.exportProperty(
    ol.renderer.canvas.VectorTileLayer.prototype,
    'unByKey',
    ol.renderer.canvas.VectorTileLayer.prototype.unByKey);

goog.exportProperty(
    ol.render.Event.prototype,
    'type',
    ol.render.Event.prototype.type);

goog.exportProperty(
    ol.render.Event.prototype,
    'target',
    ol.render.Event.prototype.target);

goog.exportProperty(
    ol.render.Event.prototype,
    'preventDefault',
    ol.render.Event.prototype.preventDefault);

goog.exportProperty(
    ol.render.Event.prototype,
    'stopPropagation',
    ol.render.Event.prototype.stopPropagation);

goog.exportProperty(
    ol.pointer.PointerEvent.prototype,
    'type',
    ol.pointer.PointerEvent.prototype.type);

goog.exportProperty(
    ol.pointer.PointerEvent.prototype,
    'target',
    ol.pointer.PointerEvent.prototype.target);

goog.exportProperty(
    ol.pointer.PointerEvent.prototype,
    'preventDefault',
    ol.pointer.PointerEvent.prototype.preventDefault);

goog.exportProperty(
    ol.pointer.PointerEvent.prototype,
    'stopPropagation',
    ol.pointer.PointerEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.layer.Base.prototype,
    'get',
    ol.layer.Base.prototype.get);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getKeys',
    ol.layer.Base.prototype.getKeys);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getProperties',
    ol.layer.Base.prototype.getProperties);

goog.exportProperty(
    ol.layer.Base.prototype,
    'set',
    ol.layer.Base.prototype.set);

goog.exportProperty(
    ol.layer.Base.prototype,
    'setProperties',
    ol.layer.Base.prototype.setProperties);

goog.exportProperty(
    ol.layer.Base.prototype,
    'unset',
    ol.layer.Base.prototype.unset);

goog.exportProperty(
    ol.layer.Base.prototype,
    'changed',
    ol.layer.Base.prototype.changed);

goog.exportProperty(
    ol.layer.Base.prototype,
    'dispatchEvent',
    ol.layer.Base.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Base.prototype,
    'getRevision',
    ol.layer.Base.prototype.getRevision);

goog.exportProperty(
    ol.layer.Base.prototype,
    'on',
    ol.layer.Base.prototype.on);

goog.exportProperty(
    ol.layer.Base.prototype,
    'once',
    ol.layer.Base.prototype.once);

goog.exportProperty(
    ol.layer.Base.prototype,
    'un',
    ol.layer.Base.prototype.un);

goog.exportProperty(
    ol.layer.Base.prototype,
    'unByKey',
    ol.layer.Base.prototype.unByKey);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getExtent',
    ol.layer.Group.prototype.getExtent);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getMaxResolution',
    ol.layer.Group.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getMinResolution',
    ol.layer.Group.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getOpacity',
    ol.layer.Group.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getVisible',
    ol.layer.Group.prototype.getVisible);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getZIndex',
    ol.layer.Group.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setExtent',
    ol.layer.Group.prototype.setExtent);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setMaxResolution',
    ol.layer.Group.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setMinResolution',
    ol.layer.Group.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setOpacity',
    ol.layer.Group.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setVisible',
    ol.layer.Group.prototype.setVisible);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setZIndex',
    ol.layer.Group.prototype.setZIndex);

goog.exportProperty(
    ol.layer.Group.prototype,
    'get',
    ol.layer.Group.prototype.get);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getKeys',
    ol.layer.Group.prototype.getKeys);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getProperties',
    ol.layer.Group.prototype.getProperties);

goog.exportProperty(
    ol.layer.Group.prototype,
    'set',
    ol.layer.Group.prototype.set);

goog.exportProperty(
    ol.layer.Group.prototype,
    'setProperties',
    ol.layer.Group.prototype.setProperties);

goog.exportProperty(
    ol.layer.Group.prototype,
    'unset',
    ol.layer.Group.prototype.unset);

goog.exportProperty(
    ol.layer.Group.prototype,
    'changed',
    ol.layer.Group.prototype.changed);

goog.exportProperty(
    ol.layer.Group.prototype,
    'dispatchEvent',
    ol.layer.Group.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Group.prototype,
    'getRevision',
    ol.layer.Group.prototype.getRevision);

goog.exportProperty(
    ol.layer.Group.prototype,
    'on',
    ol.layer.Group.prototype.on);

goog.exportProperty(
    ol.layer.Group.prototype,
    'once',
    ol.layer.Group.prototype.once);

goog.exportProperty(
    ol.layer.Group.prototype,
    'un',
    ol.layer.Group.prototype.un);

goog.exportProperty(
    ol.layer.Group.prototype,
    'unByKey',
    ol.layer.Group.prototype.unByKey);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getExtent',
    ol.layer.Layer.prototype.getExtent);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getMaxResolution',
    ol.layer.Layer.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getMinResolution',
    ol.layer.Layer.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getOpacity',
    ol.layer.Layer.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getVisible',
    ol.layer.Layer.prototype.getVisible);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getZIndex',
    ol.layer.Layer.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setExtent',
    ol.layer.Layer.prototype.setExtent);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setMaxResolution',
    ol.layer.Layer.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setMinResolution',
    ol.layer.Layer.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setOpacity',
    ol.layer.Layer.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setVisible',
    ol.layer.Layer.prototype.setVisible);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setZIndex',
    ol.layer.Layer.prototype.setZIndex);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'get',
    ol.layer.Layer.prototype.get);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getKeys',
    ol.layer.Layer.prototype.getKeys);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getProperties',
    ol.layer.Layer.prototype.getProperties);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'set',
    ol.layer.Layer.prototype.set);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'setProperties',
    ol.layer.Layer.prototype.setProperties);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'unset',
    ol.layer.Layer.prototype.unset);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'changed',
    ol.layer.Layer.prototype.changed);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'dispatchEvent',
    ol.layer.Layer.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'getRevision',
    ol.layer.Layer.prototype.getRevision);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'on',
    ol.layer.Layer.prototype.on);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'once',
    ol.layer.Layer.prototype.once);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'un',
    ol.layer.Layer.prototype.un);

goog.exportProperty(
    ol.layer.Layer.prototype,
    'unByKey',
    ol.layer.Layer.prototype.unByKey);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setMap',
    ol.layer.Vector.prototype.setMap);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setSource',
    ol.layer.Vector.prototype.setSource);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getExtent',
    ol.layer.Vector.prototype.getExtent);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getMaxResolution',
    ol.layer.Vector.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getMinResolution',
    ol.layer.Vector.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getOpacity',
    ol.layer.Vector.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getVisible',
    ol.layer.Vector.prototype.getVisible);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getZIndex',
    ol.layer.Vector.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setExtent',
    ol.layer.Vector.prototype.setExtent);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setMaxResolution',
    ol.layer.Vector.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setMinResolution',
    ol.layer.Vector.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setOpacity',
    ol.layer.Vector.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setVisible',
    ol.layer.Vector.prototype.setVisible);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setZIndex',
    ol.layer.Vector.prototype.setZIndex);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'get',
    ol.layer.Vector.prototype.get);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getKeys',
    ol.layer.Vector.prototype.getKeys);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getProperties',
    ol.layer.Vector.prototype.getProperties);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'set',
    ol.layer.Vector.prototype.set);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'setProperties',
    ol.layer.Vector.prototype.setProperties);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'unset',
    ol.layer.Vector.prototype.unset);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'changed',
    ol.layer.Vector.prototype.changed);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'dispatchEvent',
    ol.layer.Vector.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'getRevision',
    ol.layer.Vector.prototype.getRevision);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'on',
    ol.layer.Vector.prototype.on);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'once',
    ol.layer.Vector.prototype.once);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'un',
    ol.layer.Vector.prototype.un);

goog.exportProperty(
    ol.layer.Vector.prototype,
    'unByKey',
    ol.layer.Vector.prototype.unByKey);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getSource',
    ol.layer.Heatmap.prototype.getSource);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getStyle',
    ol.layer.Heatmap.prototype.getStyle);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getStyleFunction',
    ol.layer.Heatmap.prototype.getStyleFunction);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setStyle',
    ol.layer.Heatmap.prototype.setStyle);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setMap',
    ol.layer.Heatmap.prototype.setMap);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setSource',
    ol.layer.Heatmap.prototype.setSource);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getExtent',
    ol.layer.Heatmap.prototype.getExtent);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getMaxResolution',
    ol.layer.Heatmap.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getMinResolution',
    ol.layer.Heatmap.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getOpacity',
    ol.layer.Heatmap.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getVisible',
    ol.layer.Heatmap.prototype.getVisible);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getZIndex',
    ol.layer.Heatmap.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setExtent',
    ol.layer.Heatmap.prototype.setExtent);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setMaxResolution',
    ol.layer.Heatmap.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setMinResolution',
    ol.layer.Heatmap.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setOpacity',
    ol.layer.Heatmap.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setVisible',
    ol.layer.Heatmap.prototype.setVisible);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setZIndex',
    ol.layer.Heatmap.prototype.setZIndex);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'get',
    ol.layer.Heatmap.prototype.get);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getKeys',
    ol.layer.Heatmap.prototype.getKeys);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getProperties',
    ol.layer.Heatmap.prototype.getProperties);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'set',
    ol.layer.Heatmap.prototype.set);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'setProperties',
    ol.layer.Heatmap.prototype.setProperties);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'unset',
    ol.layer.Heatmap.prototype.unset);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'changed',
    ol.layer.Heatmap.prototype.changed);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'dispatchEvent',
    ol.layer.Heatmap.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'getRevision',
    ol.layer.Heatmap.prototype.getRevision);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'on',
    ol.layer.Heatmap.prototype.on);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'once',
    ol.layer.Heatmap.prototype.once);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'un',
    ol.layer.Heatmap.prototype.un);

goog.exportProperty(
    ol.layer.Heatmap.prototype,
    'unByKey',
    ol.layer.Heatmap.prototype.unByKey);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setMap',
    ol.layer.Image.prototype.setMap);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setSource',
    ol.layer.Image.prototype.setSource);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getExtent',
    ol.layer.Image.prototype.getExtent);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getMaxResolution',
    ol.layer.Image.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getMinResolution',
    ol.layer.Image.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getOpacity',
    ol.layer.Image.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getVisible',
    ol.layer.Image.prototype.getVisible);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getZIndex',
    ol.layer.Image.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setExtent',
    ol.layer.Image.prototype.setExtent);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setMaxResolution',
    ol.layer.Image.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setMinResolution',
    ol.layer.Image.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setOpacity',
    ol.layer.Image.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setVisible',
    ol.layer.Image.prototype.setVisible);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setZIndex',
    ol.layer.Image.prototype.setZIndex);

goog.exportProperty(
    ol.layer.Image.prototype,
    'get',
    ol.layer.Image.prototype.get);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getKeys',
    ol.layer.Image.prototype.getKeys);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getProperties',
    ol.layer.Image.prototype.getProperties);

goog.exportProperty(
    ol.layer.Image.prototype,
    'set',
    ol.layer.Image.prototype.set);

goog.exportProperty(
    ol.layer.Image.prototype,
    'setProperties',
    ol.layer.Image.prototype.setProperties);

goog.exportProperty(
    ol.layer.Image.prototype,
    'unset',
    ol.layer.Image.prototype.unset);

goog.exportProperty(
    ol.layer.Image.prototype,
    'changed',
    ol.layer.Image.prototype.changed);

goog.exportProperty(
    ol.layer.Image.prototype,
    'dispatchEvent',
    ol.layer.Image.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Image.prototype,
    'getRevision',
    ol.layer.Image.prototype.getRevision);

goog.exportProperty(
    ol.layer.Image.prototype,
    'on',
    ol.layer.Image.prototype.on);

goog.exportProperty(
    ol.layer.Image.prototype,
    'once',
    ol.layer.Image.prototype.once);

goog.exportProperty(
    ol.layer.Image.prototype,
    'un',
    ol.layer.Image.prototype.un);

goog.exportProperty(
    ol.layer.Image.prototype,
    'unByKey',
    ol.layer.Image.prototype.unByKey);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setMap',
    ol.layer.Tile.prototype.setMap);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setSource',
    ol.layer.Tile.prototype.setSource);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getExtent',
    ol.layer.Tile.prototype.getExtent);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getMaxResolution',
    ol.layer.Tile.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getMinResolution',
    ol.layer.Tile.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getOpacity',
    ol.layer.Tile.prototype.getOpacity);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getVisible',
    ol.layer.Tile.prototype.getVisible);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getZIndex',
    ol.layer.Tile.prototype.getZIndex);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setExtent',
    ol.layer.Tile.prototype.setExtent);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setMaxResolution',
    ol.layer.Tile.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setMinResolution',
    ol.layer.Tile.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setOpacity',
    ol.layer.Tile.prototype.setOpacity);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setVisible',
    ol.layer.Tile.prototype.setVisible);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setZIndex',
    ol.layer.Tile.prototype.setZIndex);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'get',
    ol.layer.Tile.prototype.get);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getKeys',
    ol.layer.Tile.prototype.getKeys);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getProperties',
    ol.layer.Tile.prototype.getProperties);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'set',
    ol.layer.Tile.prototype.set);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'setProperties',
    ol.layer.Tile.prototype.setProperties);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'unset',
    ol.layer.Tile.prototype.unset);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'changed',
    ol.layer.Tile.prototype.changed);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'dispatchEvent',
    ol.layer.Tile.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'getRevision',
    ol.layer.Tile.prototype.getRevision);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'on',
    ol.layer.Tile.prototype.on);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'once',
    ol.layer.Tile.prototype.once);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'un',
    ol.layer.Tile.prototype.un);

goog.exportProperty(
    ol.layer.Tile.prototype,
    'unByKey',
    ol.layer.Tile.prototype.unByKey);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getSource',
    ol.layer.VectorTile.prototype.getSource);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getStyle',
    ol.layer.VectorTile.prototype.getStyle);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getStyleFunction',
    ol.layer.VectorTile.prototype.getStyleFunction);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setStyle',
    ol.layer.VectorTile.prototype.setStyle);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setMap',
    ol.layer.VectorTile.prototype.setMap);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setSource',
    ol.layer.VectorTile.prototype.setSource);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getExtent',
    ol.layer.VectorTile.prototype.getExtent);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getMaxResolution',
    ol.layer.VectorTile.prototype.getMaxResolution);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getMinResolution',
    ol.layer.VectorTile.prototype.getMinResolution);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getOpacity',
    ol.layer.VectorTile.prototype.getOpacity);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getVisible',
    ol.layer.VectorTile.prototype.getVisible);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getZIndex',
    ol.layer.VectorTile.prototype.getZIndex);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setExtent',
    ol.layer.VectorTile.prototype.setExtent);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setMaxResolution',
    ol.layer.VectorTile.prototype.setMaxResolution);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setMinResolution',
    ol.layer.VectorTile.prototype.setMinResolution);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setOpacity',
    ol.layer.VectorTile.prototype.setOpacity);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setVisible',
    ol.layer.VectorTile.prototype.setVisible);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setZIndex',
    ol.layer.VectorTile.prototype.setZIndex);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'get',
    ol.layer.VectorTile.prototype.get);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getKeys',
    ol.layer.VectorTile.prototype.getKeys);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getProperties',
    ol.layer.VectorTile.prototype.getProperties);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'set',
    ol.layer.VectorTile.prototype.set);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'setProperties',
    ol.layer.VectorTile.prototype.setProperties);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'unset',
    ol.layer.VectorTile.prototype.unset);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'changed',
    ol.layer.VectorTile.prototype.changed);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'dispatchEvent',
    ol.layer.VectorTile.prototype.dispatchEvent);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'getRevision',
    ol.layer.VectorTile.prototype.getRevision);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'on',
    ol.layer.VectorTile.prototype.on);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'once',
    ol.layer.VectorTile.prototype.once);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'un',
    ol.layer.VectorTile.prototype.un);

goog.exportProperty(
    ol.layer.VectorTile.prototype,
    'unByKey',
    ol.layer.VectorTile.prototype.unByKey);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'get',
    ol.interaction.Interaction.prototype.get);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'getKeys',
    ol.interaction.Interaction.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'getProperties',
    ol.interaction.Interaction.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'set',
    ol.interaction.Interaction.prototype.set);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'setProperties',
    ol.interaction.Interaction.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'unset',
    ol.interaction.Interaction.prototype.unset);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'changed',
    ol.interaction.Interaction.prototype.changed);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'dispatchEvent',
    ol.interaction.Interaction.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'getRevision',
    ol.interaction.Interaction.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'on',
    ol.interaction.Interaction.prototype.on);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'once',
    ol.interaction.Interaction.prototype.once);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'un',
    ol.interaction.Interaction.prototype.un);

goog.exportProperty(
    ol.interaction.Interaction.prototype,
    'unByKey',
    ol.interaction.Interaction.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'getActive',
    ol.interaction.DoubleClickZoom.prototype.getActive);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'getMap',
    ol.interaction.DoubleClickZoom.prototype.getMap);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'setActive',
    ol.interaction.DoubleClickZoom.prototype.setActive);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'get',
    ol.interaction.DoubleClickZoom.prototype.get);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'getKeys',
    ol.interaction.DoubleClickZoom.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'getProperties',
    ol.interaction.DoubleClickZoom.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'set',
    ol.interaction.DoubleClickZoom.prototype.set);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'setProperties',
    ol.interaction.DoubleClickZoom.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'unset',
    ol.interaction.DoubleClickZoom.prototype.unset);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'changed',
    ol.interaction.DoubleClickZoom.prototype.changed);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'dispatchEvent',
    ol.interaction.DoubleClickZoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'getRevision',
    ol.interaction.DoubleClickZoom.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'on',
    ol.interaction.DoubleClickZoom.prototype.on);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'once',
    ol.interaction.DoubleClickZoom.prototype.once);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'un',
    ol.interaction.DoubleClickZoom.prototype.un);

goog.exportProperty(
    ol.interaction.DoubleClickZoom.prototype,
    'unByKey',
    ol.interaction.DoubleClickZoom.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'getActive',
    ol.interaction.DragAndDrop.prototype.getActive);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'getMap',
    ol.interaction.DragAndDrop.prototype.getMap);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'setActive',
    ol.interaction.DragAndDrop.prototype.setActive);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'get',
    ol.interaction.DragAndDrop.prototype.get);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'getKeys',
    ol.interaction.DragAndDrop.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'getProperties',
    ol.interaction.DragAndDrop.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'set',
    ol.interaction.DragAndDrop.prototype.set);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'setProperties',
    ol.interaction.DragAndDrop.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'unset',
    ol.interaction.DragAndDrop.prototype.unset);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'changed',
    ol.interaction.DragAndDrop.prototype.changed);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'dispatchEvent',
    ol.interaction.DragAndDrop.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'getRevision',
    ol.interaction.DragAndDrop.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'on',
    ol.interaction.DragAndDrop.prototype.on);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'once',
    ol.interaction.DragAndDrop.prototype.once);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'un',
    ol.interaction.DragAndDrop.prototype.un);

goog.exportProperty(
    ol.interaction.DragAndDrop.prototype,
    'unByKey',
    ol.interaction.DragAndDrop.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'type',
    ol.interaction.DragAndDropEvent.prototype.type);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'target',
    ol.interaction.DragAndDropEvent.prototype.target);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'preventDefault',
    ol.interaction.DragAndDropEvent.prototype.preventDefault);

goog.exportProperty(
    ol.interaction.DragAndDropEvent.prototype,
    'stopPropagation',
    ol.interaction.DragAndDropEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.DragBoxEvent.prototype,
    'type',
    ol.DragBoxEvent.prototype.type);

goog.exportProperty(
    ol.DragBoxEvent.prototype,
    'target',
    ol.DragBoxEvent.prototype.target);

goog.exportProperty(
    ol.DragBoxEvent.prototype,
    'preventDefault',
    ol.DragBoxEvent.prototype.preventDefault);

goog.exportProperty(
    ol.DragBoxEvent.prototype,
    'stopPropagation',
    ol.DragBoxEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'getActive',
    ol.interaction.Pointer.prototype.getActive);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'getMap',
    ol.interaction.Pointer.prototype.getMap);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'setActive',
    ol.interaction.Pointer.prototype.setActive);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'get',
    ol.interaction.Pointer.prototype.get);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'getKeys',
    ol.interaction.Pointer.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'getProperties',
    ol.interaction.Pointer.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'set',
    ol.interaction.Pointer.prototype.set);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'setProperties',
    ol.interaction.Pointer.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'unset',
    ol.interaction.Pointer.prototype.unset);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'changed',
    ol.interaction.Pointer.prototype.changed);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'dispatchEvent',
    ol.interaction.Pointer.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'getRevision',
    ol.interaction.Pointer.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'on',
    ol.interaction.Pointer.prototype.on);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'once',
    ol.interaction.Pointer.prototype.once);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'un',
    ol.interaction.Pointer.prototype.un);

goog.exportProperty(
    ol.interaction.Pointer.prototype,
    'unByKey',
    ol.interaction.Pointer.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'getActive',
    ol.interaction.DragBox.prototype.getActive);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'getMap',
    ol.interaction.DragBox.prototype.getMap);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'setActive',
    ol.interaction.DragBox.prototype.setActive);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'get',
    ol.interaction.DragBox.prototype.get);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'getKeys',
    ol.interaction.DragBox.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'getProperties',
    ol.interaction.DragBox.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'set',
    ol.interaction.DragBox.prototype.set);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'setProperties',
    ol.interaction.DragBox.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'unset',
    ol.interaction.DragBox.prototype.unset);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'changed',
    ol.interaction.DragBox.prototype.changed);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'dispatchEvent',
    ol.interaction.DragBox.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'getRevision',
    ol.interaction.DragBox.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'on',
    ol.interaction.DragBox.prototype.on);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'once',
    ol.interaction.DragBox.prototype.once);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'un',
    ol.interaction.DragBox.prototype.un);

goog.exportProperty(
    ol.interaction.DragBox.prototype,
    'unByKey',
    ol.interaction.DragBox.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'getActive',
    ol.interaction.DragPan.prototype.getActive);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'getMap',
    ol.interaction.DragPan.prototype.getMap);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'setActive',
    ol.interaction.DragPan.prototype.setActive);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'get',
    ol.interaction.DragPan.prototype.get);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'getKeys',
    ol.interaction.DragPan.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'getProperties',
    ol.interaction.DragPan.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'set',
    ol.interaction.DragPan.prototype.set);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'setProperties',
    ol.interaction.DragPan.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'unset',
    ol.interaction.DragPan.prototype.unset);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'changed',
    ol.interaction.DragPan.prototype.changed);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'dispatchEvent',
    ol.interaction.DragPan.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'getRevision',
    ol.interaction.DragPan.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'on',
    ol.interaction.DragPan.prototype.on);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'once',
    ol.interaction.DragPan.prototype.once);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'un',
    ol.interaction.DragPan.prototype.un);

goog.exportProperty(
    ol.interaction.DragPan.prototype,
    'unByKey',
    ol.interaction.DragPan.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'getActive',
    ol.interaction.DragRotate.prototype.getActive);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'getMap',
    ol.interaction.DragRotate.prototype.getMap);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'setActive',
    ol.interaction.DragRotate.prototype.setActive);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'get',
    ol.interaction.DragRotate.prototype.get);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'getKeys',
    ol.interaction.DragRotate.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'getProperties',
    ol.interaction.DragRotate.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'set',
    ol.interaction.DragRotate.prototype.set);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'setProperties',
    ol.interaction.DragRotate.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'unset',
    ol.interaction.DragRotate.prototype.unset);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'changed',
    ol.interaction.DragRotate.prototype.changed);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'dispatchEvent',
    ol.interaction.DragRotate.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'getRevision',
    ol.interaction.DragRotate.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'on',
    ol.interaction.DragRotate.prototype.on);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'once',
    ol.interaction.DragRotate.prototype.once);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'un',
    ol.interaction.DragRotate.prototype.un);

goog.exportProperty(
    ol.interaction.DragRotate.prototype,
    'unByKey',
    ol.interaction.DragRotate.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'getActive',
    ol.interaction.DragRotateAndZoom.prototype.getActive);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'getMap',
    ol.interaction.DragRotateAndZoom.prototype.getMap);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'setActive',
    ol.interaction.DragRotateAndZoom.prototype.setActive);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'get',
    ol.interaction.DragRotateAndZoom.prototype.get);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'getKeys',
    ol.interaction.DragRotateAndZoom.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'getProperties',
    ol.interaction.DragRotateAndZoom.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'set',
    ol.interaction.DragRotateAndZoom.prototype.set);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'setProperties',
    ol.interaction.DragRotateAndZoom.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'unset',
    ol.interaction.DragRotateAndZoom.prototype.unset);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'changed',
    ol.interaction.DragRotateAndZoom.prototype.changed);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'dispatchEvent',
    ol.interaction.DragRotateAndZoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'getRevision',
    ol.interaction.DragRotateAndZoom.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'on',
    ol.interaction.DragRotateAndZoom.prototype.on);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'once',
    ol.interaction.DragRotateAndZoom.prototype.once);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'un',
    ol.interaction.DragRotateAndZoom.prototype.un);

goog.exportProperty(
    ol.interaction.DragRotateAndZoom.prototype,
    'unByKey',
    ol.interaction.DragRotateAndZoom.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'getGeometry',
    ol.interaction.DragZoom.prototype.getGeometry);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'getActive',
    ol.interaction.DragZoom.prototype.getActive);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'getMap',
    ol.interaction.DragZoom.prototype.getMap);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'setActive',
    ol.interaction.DragZoom.prototype.setActive);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'get',
    ol.interaction.DragZoom.prototype.get);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'getKeys',
    ol.interaction.DragZoom.prototype.getKeys);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'getProperties',
    ol.interaction.DragZoom.prototype.getProperties);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'set',
    ol.interaction.DragZoom.prototype.set);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'setProperties',
    ol.interaction.DragZoom.prototype.setProperties);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'unset',
    ol.interaction.DragZoom.prototype.unset);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'changed',
    ol.interaction.DragZoom.prototype.changed);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'dispatchEvent',
    ol.interaction.DragZoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'getRevision',
    ol.interaction.DragZoom.prototype.getRevision);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'on',
    ol.interaction.DragZoom.prototype.on);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'once',
    ol.interaction.DragZoom.prototype.once);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'un',
    ol.interaction.DragZoom.prototype.un);

goog.exportProperty(
    ol.interaction.DragZoom.prototype,
    'unByKey',
    ol.interaction.DragZoom.prototype.unByKey);

goog.exportProperty(
    ol.interaction.DrawEvent.prototype,
    'type',
    ol.interaction.DrawEvent.prototype.type);

goog.exportProperty(
    ol.interaction.DrawEvent.prototype,
    'target',
    ol.interaction.DrawEvent.prototype.target);

goog.exportProperty(
    ol.interaction.DrawEvent.prototype,
    'preventDefault',
    ol.interaction.DrawEvent.prototype.preventDefault);

goog.exportProperty(
    ol.interaction.DrawEvent.prototype,
    'stopPropagation',
    ol.interaction.DrawEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'getActive',
    ol.interaction.Draw.prototype.getActive);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'getMap',
    ol.interaction.Draw.prototype.getMap);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'setActive',
    ol.interaction.Draw.prototype.setActive);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'get',
    ol.interaction.Draw.prototype.get);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'getKeys',
    ol.interaction.Draw.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'getProperties',
    ol.interaction.Draw.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'set',
    ol.interaction.Draw.prototype.set);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'setProperties',
    ol.interaction.Draw.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'unset',
    ol.interaction.Draw.prototype.unset);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'changed',
    ol.interaction.Draw.prototype.changed);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'dispatchEvent',
    ol.interaction.Draw.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'getRevision',
    ol.interaction.Draw.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'on',
    ol.interaction.Draw.prototype.on);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'once',
    ol.interaction.Draw.prototype.once);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'un',
    ol.interaction.Draw.prototype.un);

goog.exportProperty(
    ol.interaction.Draw.prototype,
    'unByKey',
    ol.interaction.Draw.prototype.unByKey);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'getActive',
    ol.interaction.KeyboardPan.prototype.getActive);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'getMap',
    ol.interaction.KeyboardPan.prototype.getMap);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'setActive',
    ol.interaction.KeyboardPan.prototype.setActive);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'get',
    ol.interaction.KeyboardPan.prototype.get);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'getKeys',
    ol.interaction.KeyboardPan.prototype.getKeys);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'getProperties',
    ol.interaction.KeyboardPan.prototype.getProperties);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'set',
    ol.interaction.KeyboardPan.prototype.set);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'setProperties',
    ol.interaction.KeyboardPan.prototype.setProperties);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'unset',
    ol.interaction.KeyboardPan.prototype.unset);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'changed',
    ol.interaction.KeyboardPan.prototype.changed);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'dispatchEvent',
    ol.interaction.KeyboardPan.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'getRevision',
    ol.interaction.KeyboardPan.prototype.getRevision);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'on',
    ol.interaction.KeyboardPan.prototype.on);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'once',
    ol.interaction.KeyboardPan.prototype.once);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'un',
    ol.interaction.KeyboardPan.prototype.un);

goog.exportProperty(
    ol.interaction.KeyboardPan.prototype,
    'unByKey',
    ol.interaction.KeyboardPan.prototype.unByKey);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'getActive',
    ol.interaction.KeyboardZoom.prototype.getActive);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'getMap',
    ol.interaction.KeyboardZoom.prototype.getMap);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'setActive',
    ol.interaction.KeyboardZoom.prototype.setActive);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'get',
    ol.interaction.KeyboardZoom.prototype.get);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'getKeys',
    ol.interaction.KeyboardZoom.prototype.getKeys);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'getProperties',
    ol.interaction.KeyboardZoom.prototype.getProperties);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'set',
    ol.interaction.KeyboardZoom.prototype.set);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'setProperties',
    ol.interaction.KeyboardZoom.prototype.setProperties);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'unset',
    ol.interaction.KeyboardZoom.prototype.unset);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'changed',
    ol.interaction.KeyboardZoom.prototype.changed);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'dispatchEvent',
    ol.interaction.KeyboardZoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'getRevision',
    ol.interaction.KeyboardZoom.prototype.getRevision);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'on',
    ol.interaction.KeyboardZoom.prototype.on);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'once',
    ol.interaction.KeyboardZoom.prototype.once);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'un',
    ol.interaction.KeyboardZoom.prototype.un);

goog.exportProperty(
    ol.interaction.KeyboardZoom.prototype,
    'unByKey',
    ol.interaction.KeyboardZoom.prototype.unByKey);

goog.exportProperty(
    ol.interaction.ModifyEvent.prototype,
    'type',
    ol.interaction.ModifyEvent.prototype.type);

goog.exportProperty(
    ol.interaction.ModifyEvent.prototype,
    'target',
    ol.interaction.ModifyEvent.prototype.target);

goog.exportProperty(
    ol.interaction.ModifyEvent.prototype,
    'preventDefault',
    ol.interaction.ModifyEvent.prototype.preventDefault);

goog.exportProperty(
    ol.interaction.ModifyEvent.prototype,
    'stopPropagation',
    ol.interaction.ModifyEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'getActive',
    ol.interaction.Modify.prototype.getActive);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'getMap',
    ol.interaction.Modify.prototype.getMap);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'setActive',
    ol.interaction.Modify.prototype.setActive);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'get',
    ol.interaction.Modify.prototype.get);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'getKeys',
    ol.interaction.Modify.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'getProperties',
    ol.interaction.Modify.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'set',
    ol.interaction.Modify.prototype.set);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'setProperties',
    ol.interaction.Modify.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'unset',
    ol.interaction.Modify.prototype.unset);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'changed',
    ol.interaction.Modify.prototype.changed);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'dispatchEvent',
    ol.interaction.Modify.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'getRevision',
    ol.interaction.Modify.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'on',
    ol.interaction.Modify.prototype.on);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'once',
    ol.interaction.Modify.prototype.once);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'un',
    ol.interaction.Modify.prototype.un);

goog.exportProperty(
    ol.interaction.Modify.prototype,
    'unByKey',
    ol.interaction.Modify.prototype.unByKey);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'getActive',
    ol.interaction.MouseWheelZoom.prototype.getActive);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'getMap',
    ol.interaction.MouseWheelZoom.prototype.getMap);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'setActive',
    ol.interaction.MouseWheelZoom.prototype.setActive);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'get',
    ol.interaction.MouseWheelZoom.prototype.get);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'getKeys',
    ol.interaction.MouseWheelZoom.prototype.getKeys);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'getProperties',
    ol.interaction.MouseWheelZoom.prototype.getProperties);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'set',
    ol.interaction.MouseWheelZoom.prototype.set);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'setProperties',
    ol.interaction.MouseWheelZoom.prototype.setProperties);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'unset',
    ol.interaction.MouseWheelZoom.prototype.unset);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'changed',
    ol.interaction.MouseWheelZoom.prototype.changed);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'dispatchEvent',
    ol.interaction.MouseWheelZoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'getRevision',
    ol.interaction.MouseWheelZoom.prototype.getRevision);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'on',
    ol.interaction.MouseWheelZoom.prototype.on);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'once',
    ol.interaction.MouseWheelZoom.prototype.once);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'un',
    ol.interaction.MouseWheelZoom.prototype.un);

goog.exportProperty(
    ol.interaction.MouseWheelZoom.prototype,
    'unByKey',
    ol.interaction.MouseWheelZoom.prototype.unByKey);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'getActive',
    ol.interaction.PinchRotate.prototype.getActive);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'getMap',
    ol.interaction.PinchRotate.prototype.getMap);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'setActive',
    ol.interaction.PinchRotate.prototype.setActive);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'get',
    ol.interaction.PinchRotate.prototype.get);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'getKeys',
    ol.interaction.PinchRotate.prototype.getKeys);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'getProperties',
    ol.interaction.PinchRotate.prototype.getProperties);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'set',
    ol.interaction.PinchRotate.prototype.set);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'setProperties',
    ol.interaction.PinchRotate.prototype.setProperties);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'unset',
    ol.interaction.PinchRotate.prototype.unset);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'changed',
    ol.interaction.PinchRotate.prototype.changed);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'dispatchEvent',
    ol.interaction.PinchRotate.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'getRevision',
    ol.interaction.PinchRotate.prototype.getRevision);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'on',
    ol.interaction.PinchRotate.prototype.on);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'once',
    ol.interaction.PinchRotate.prototype.once);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'un',
    ol.interaction.PinchRotate.prototype.un);

goog.exportProperty(
    ol.interaction.PinchRotate.prototype,
    'unByKey',
    ol.interaction.PinchRotate.prototype.unByKey);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'getActive',
    ol.interaction.PinchZoom.prototype.getActive);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'getMap',
    ol.interaction.PinchZoom.prototype.getMap);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'setActive',
    ol.interaction.PinchZoom.prototype.setActive);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'get',
    ol.interaction.PinchZoom.prototype.get);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'getKeys',
    ol.interaction.PinchZoom.prototype.getKeys);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'getProperties',
    ol.interaction.PinchZoom.prototype.getProperties);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'set',
    ol.interaction.PinchZoom.prototype.set);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'setProperties',
    ol.interaction.PinchZoom.prototype.setProperties);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'unset',
    ol.interaction.PinchZoom.prototype.unset);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'changed',
    ol.interaction.PinchZoom.prototype.changed);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'dispatchEvent',
    ol.interaction.PinchZoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'getRevision',
    ol.interaction.PinchZoom.prototype.getRevision);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'on',
    ol.interaction.PinchZoom.prototype.on);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'once',
    ol.interaction.PinchZoom.prototype.once);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'un',
    ol.interaction.PinchZoom.prototype.un);

goog.exportProperty(
    ol.interaction.PinchZoom.prototype,
    'unByKey',
    ol.interaction.PinchZoom.prototype.unByKey);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'type',
    ol.interaction.SelectEvent.prototype.type);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'target',
    ol.interaction.SelectEvent.prototype.target);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'preventDefault',
    ol.interaction.SelectEvent.prototype.preventDefault);

goog.exportProperty(
    ol.interaction.SelectEvent.prototype,
    'stopPropagation',
    ol.interaction.SelectEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getActive',
    ol.interaction.Select.prototype.getActive);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getMap',
    ol.interaction.Select.prototype.getMap);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'setActive',
    ol.interaction.Select.prototype.setActive);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'get',
    ol.interaction.Select.prototype.get);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getKeys',
    ol.interaction.Select.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getProperties',
    ol.interaction.Select.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'set',
    ol.interaction.Select.prototype.set);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'setProperties',
    ol.interaction.Select.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'unset',
    ol.interaction.Select.prototype.unset);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'changed',
    ol.interaction.Select.prototype.changed);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'dispatchEvent',
    ol.interaction.Select.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'getRevision',
    ol.interaction.Select.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'on',
    ol.interaction.Select.prototype.on);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'once',
    ol.interaction.Select.prototype.once);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'un',
    ol.interaction.Select.prototype.un);

goog.exportProperty(
    ol.interaction.Select.prototype,
    'unByKey',
    ol.interaction.Select.prototype.unByKey);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'getActive',
    ol.interaction.Snap.prototype.getActive);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'getMap',
    ol.interaction.Snap.prototype.getMap);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'setActive',
    ol.interaction.Snap.prototype.setActive);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'get',
    ol.interaction.Snap.prototype.get);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'getKeys',
    ol.interaction.Snap.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'getProperties',
    ol.interaction.Snap.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'set',
    ol.interaction.Snap.prototype.set);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'setProperties',
    ol.interaction.Snap.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'unset',
    ol.interaction.Snap.prototype.unset);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'changed',
    ol.interaction.Snap.prototype.changed);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'dispatchEvent',
    ol.interaction.Snap.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'getRevision',
    ol.interaction.Snap.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'on',
    ol.interaction.Snap.prototype.on);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'once',
    ol.interaction.Snap.prototype.once);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'un',
    ol.interaction.Snap.prototype.un);

goog.exportProperty(
    ol.interaction.Snap.prototype,
    'unByKey',
    ol.interaction.Snap.prototype.unByKey);

goog.exportProperty(
    ol.interaction.TranslateEvent.prototype,
    'type',
    ol.interaction.TranslateEvent.prototype.type);

goog.exportProperty(
    ol.interaction.TranslateEvent.prototype,
    'target',
    ol.interaction.TranslateEvent.prototype.target);

goog.exportProperty(
    ol.interaction.TranslateEvent.prototype,
    'preventDefault',
    ol.interaction.TranslateEvent.prototype.preventDefault);

goog.exportProperty(
    ol.interaction.TranslateEvent.prototype,
    'stopPropagation',
    ol.interaction.TranslateEvent.prototype.stopPropagation);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'getActive',
    ol.interaction.Translate.prototype.getActive);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'getMap',
    ol.interaction.Translate.prototype.getMap);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'setActive',
    ol.interaction.Translate.prototype.setActive);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'get',
    ol.interaction.Translate.prototype.get);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'getKeys',
    ol.interaction.Translate.prototype.getKeys);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'getProperties',
    ol.interaction.Translate.prototype.getProperties);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'set',
    ol.interaction.Translate.prototype.set);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'setProperties',
    ol.interaction.Translate.prototype.setProperties);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'unset',
    ol.interaction.Translate.prototype.unset);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'changed',
    ol.interaction.Translate.prototype.changed);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'dispatchEvent',
    ol.interaction.Translate.prototype.dispatchEvent);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'getRevision',
    ol.interaction.Translate.prototype.getRevision);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'on',
    ol.interaction.Translate.prototype.on);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'once',
    ol.interaction.Translate.prototype.once);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'un',
    ol.interaction.Translate.prototype.un);

goog.exportProperty(
    ol.interaction.Translate.prototype,
    'unByKey',
    ol.interaction.Translate.prototype.unByKey);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'get',
    ol.geom.Geometry.prototype.get);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'getKeys',
    ol.geom.Geometry.prototype.getKeys);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'getProperties',
    ol.geom.Geometry.prototype.getProperties);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'set',
    ol.geom.Geometry.prototype.set);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'setProperties',
    ol.geom.Geometry.prototype.setProperties);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'unset',
    ol.geom.Geometry.prototype.unset);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'changed',
    ol.geom.Geometry.prototype.changed);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'dispatchEvent',
    ol.geom.Geometry.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'getRevision',
    ol.geom.Geometry.prototype.getRevision);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'on',
    ol.geom.Geometry.prototype.on);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'once',
    ol.geom.Geometry.prototype.once);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'un',
    ol.geom.Geometry.prototype.un);

goog.exportProperty(
    ol.geom.Geometry.prototype,
    'unByKey',
    ol.geom.Geometry.prototype.unByKey);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getClosestPoint',
    ol.geom.SimpleGeometry.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'intersectsCoordinate',
    ol.geom.SimpleGeometry.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getExtent',
    ol.geom.SimpleGeometry.prototype.getExtent);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'rotate',
    ol.geom.SimpleGeometry.prototype.rotate);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'scale',
    ol.geom.SimpleGeometry.prototype.scale);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'simplify',
    ol.geom.SimpleGeometry.prototype.simplify);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'transform',
    ol.geom.SimpleGeometry.prototype.transform);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'get',
    ol.geom.SimpleGeometry.prototype.get);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getKeys',
    ol.geom.SimpleGeometry.prototype.getKeys);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getProperties',
    ol.geom.SimpleGeometry.prototype.getProperties);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'set',
    ol.geom.SimpleGeometry.prototype.set);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'setProperties',
    ol.geom.SimpleGeometry.prototype.setProperties);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'unset',
    ol.geom.SimpleGeometry.prototype.unset);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'changed',
    ol.geom.SimpleGeometry.prototype.changed);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'dispatchEvent',
    ol.geom.SimpleGeometry.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'getRevision',
    ol.geom.SimpleGeometry.prototype.getRevision);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'on',
    ol.geom.SimpleGeometry.prototype.on);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'once',
    ol.geom.SimpleGeometry.prototype.once);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'un',
    ol.geom.SimpleGeometry.prototype.un);

goog.exportProperty(
    ol.geom.SimpleGeometry.prototype,
    'unByKey',
    ol.geom.SimpleGeometry.prototype.unByKey);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getFirstCoordinate',
    ol.geom.Circle.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getLastCoordinate',
    ol.geom.Circle.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getLayout',
    ol.geom.Circle.prototype.getLayout);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'rotate',
    ol.geom.Circle.prototype.rotate);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'scale',
    ol.geom.Circle.prototype.scale);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getClosestPoint',
    ol.geom.Circle.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'intersectsCoordinate',
    ol.geom.Circle.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getExtent',
    ol.geom.Circle.prototype.getExtent);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'simplify',
    ol.geom.Circle.prototype.simplify);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'get',
    ol.geom.Circle.prototype.get);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getKeys',
    ol.geom.Circle.prototype.getKeys);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getProperties',
    ol.geom.Circle.prototype.getProperties);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'set',
    ol.geom.Circle.prototype.set);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'setProperties',
    ol.geom.Circle.prototype.setProperties);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'unset',
    ol.geom.Circle.prototype.unset);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'changed',
    ol.geom.Circle.prototype.changed);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'dispatchEvent',
    ol.geom.Circle.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'getRevision',
    ol.geom.Circle.prototype.getRevision);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'on',
    ol.geom.Circle.prototype.on);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'once',
    ol.geom.Circle.prototype.once);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'un',
    ol.geom.Circle.prototype.un);

goog.exportProperty(
    ol.geom.Circle.prototype,
    'unByKey',
    ol.geom.Circle.prototype.unByKey);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getClosestPoint',
    ol.geom.GeometryCollection.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'intersectsCoordinate',
    ol.geom.GeometryCollection.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getExtent',
    ol.geom.GeometryCollection.prototype.getExtent);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'rotate',
    ol.geom.GeometryCollection.prototype.rotate);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'scale',
    ol.geom.GeometryCollection.prototype.scale);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'simplify',
    ol.geom.GeometryCollection.prototype.simplify);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'transform',
    ol.geom.GeometryCollection.prototype.transform);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'get',
    ol.geom.GeometryCollection.prototype.get);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getKeys',
    ol.geom.GeometryCollection.prototype.getKeys);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getProperties',
    ol.geom.GeometryCollection.prototype.getProperties);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'set',
    ol.geom.GeometryCollection.prototype.set);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'setProperties',
    ol.geom.GeometryCollection.prototype.setProperties);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'unset',
    ol.geom.GeometryCollection.prototype.unset);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'changed',
    ol.geom.GeometryCollection.prototype.changed);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'dispatchEvent',
    ol.geom.GeometryCollection.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'getRevision',
    ol.geom.GeometryCollection.prototype.getRevision);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'on',
    ol.geom.GeometryCollection.prototype.on);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'once',
    ol.geom.GeometryCollection.prototype.once);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'un',
    ol.geom.GeometryCollection.prototype.un);

goog.exportProperty(
    ol.geom.GeometryCollection.prototype,
    'unByKey',
    ol.geom.GeometryCollection.prototype.unByKey);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getFirstCoordinate',
    ol.geom.LinearRing.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getLastCoordinate',
    ol.geom.LinearRing.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getLayout',
    ol.geom.LinearRing.prototype.getLayout);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'rotate',
    ol.geom.LinearRing.prototype.rotate);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'scale',
    ol.geom.LinearRing.prototype.scale);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getClosestPoint',
    ol.geom.LinearRing.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'intersectsCoordinate',
    ol.geom.LinearRing.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getExtent',
    ol.geom.LinearRing.prototype.getExtent);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'simplify',
    ol.geom.LinearRing.prototype.simplify);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'transform',
    ol.geom.LinearRing.prototype.transform);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'get',
    ol.geom.LinearRing.prototype.get);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getKeys',
    ol.geom.LinearRing.prototype.getKeys);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getProperties',
    ol.geom.LinearRing.prototype.getProperties);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'set',
    ol.geom.LinearRing.prototype.set);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'setProperties',
    ol.geom.LinearRing.prototype.setProperties);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'unset',
    ol.geom.LinearRing.prototype.unset);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'changed',
    ol.geom.LinearRing.prototype.changed);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'dispatchEvent',
    ol.geom.LinearRing.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'getRevision',
    ol.geom.LinearRing.prototype.getRevision);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'on',
    ol.geom.LinearRing.prototype.on);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'once',
    ol.geom.LinearRing.prototype.once);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'un',
    ol.geom.LinearRing.prototype.un);

goog.exportProperty(
    ol.geom.LinearRing.prototype,
    'unByKey',
    ol.geom.LinearRing.prototype.unByKey);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getFirstCoordinate',
    ol.geom.LineString.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getLastCoordinate',
    ol.geom.LineString.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getLayout',
    ol.geom.LineString.prototype.getLayout);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'rotate',
    ol.geom.LineString.prototype.rotate);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'scale',
    ol.geom.LineString.prototype.scale);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getClosestPoint',
    ol.geom.LineString.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'intersectsCoordinate',
    ol.geom.LineString.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getExtent',
    ol.geom.LineString.prototype.getExtent);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'simplify',
    ol.geom.LineString.prototype.simplify);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'transform',
    ol.geom.LineString.prototype.transform);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'get',
    ol.geom.LineString.prototype.get);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getKeys',
    ol.geom.LineString.prototype.getKeys);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getProperties',
    ol.geom.LineString.prototype.getProperties);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'set',
    ol.geom.LineString.prototype.set);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'setProperties',
    ol.geom.LineString.prototype.setProperties);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'unset',
    ol.geom.LineString.prototype.unset);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'changed',
    ol.geom.LineString.prototype.changed);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'dispatchEvent',
    ol.geom.LineString.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'getRevision',
    ol.geom.LineString.prototype.getRevision);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'on',
    ol.geom.LineString.prototype.on);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'once',
    ol.geom.LineString.prototype.once);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'un',
    ol.geom.LineString.prototype.un);

goog.exportProperty(
    ol.geom.LineString.prototype,
    'unByKey',
    ol.geom.LineString.prototype.unByKey);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getFirstCoordinate',
    ol.geom.MultiLineString.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getLastCoordinate',
    ol.geom.MultiLineString.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getLayout',
    ol.geom.MultiLineString.prototype.getLayout);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'rotate',
    ol.geom.MultiLineString.prototype.rotate);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'scale',
    ol.geom.MultiLineString.prototype.scale);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getClosestPoint',
    ol.geom.MultiLineString.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'intersectsCoordinate',
    ol.geom.MultiLineString.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getExtent',
    ol.geom.MultiLineString.prototype.getExtent);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'simplify',
    ol.geom.MultiLineString.prototype.simplify);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'transform',
    ol.geom.MultiLineString.prototype.transform);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'get',
    ol.geom.MultiLineString.prototype.get);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getKeys',
    ol.geom.MultiLineString.prototype.getKeys);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getProperties',
    ol.geom.MultiLineString.prototype.getProperties);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'set',
    ol.geom.MultiLineString.prototype.set);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'setProperties',
    ol.geom.MultiLineString.prototype.setProperties);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'unset',
    ol.geom.MultiLineString.prototype.unset);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'changed',
    ol.geom.MultiLineString.prototype.changed);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'dispatchEvent',
    ol.geom.MultiLineString.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'getRevision',
    ol.geom.MultiLineString.prototype.getRevision);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'on',
    ol.geom.MultiLineString.prototype.on);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'once',
    ol.geom.MultiLineString.prototype.once);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'un',
    ol.geom.MultiLineString.prototype.un);

goog.exportProperty(
    ol.geom.MultiLineString.prototype,
    'unByKey',
    ol.geom.MultiLineString.prototype.unByKey);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getFirstCoordinate',
    ol.geom.MultiPoint.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getLastCoordinate',
    ol.geom.MultiPoint.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getLayout',
    ol.geom.MultiPoint.prototype.getLayout);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'rotate',
    ol.geom.MultiPoint.prototype.rotate);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'scale',
    ol.geom.MultiPoint.prototype.scale);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getClosestPoint',
    ol.geom.MultiPoint.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'intersectsCoordinate',
    ol.geom.MultiPoint.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getExtent',
    ol.geom.MultiPoint.prototype.getExtent);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'simplify',
    ol.geom.MultiPoint.prototype.simplify);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'transform',
    ol.geom.MultiPoint.prototype.transform);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'get',
    ol.geom.MultiPoint.prototype.get);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getKeys',
    ol.geom.MultiPoint.prototype.getKeys);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getProperties',
    ol.geom.MultiPoint.prototype.getProperties);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'set',
    ol.geom.MultiPoint.prototype.set);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'setProperties',
    ol.geom.MultiPoint.prototype.setProperties);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'unset',
    ol.geom.MultiPoint.prototype.unset);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'changed',
    ol.geom.MultiPoint.prototype.changed);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'dispatchEvent',
    ol.geom.MultiPoint.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'getRevision',
    ol.geom.MultiPoint.prototype.getRevision);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'on',
    ol.geom.MultiPoint.prototype.on);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'once',
    ol.geom.MultiPoint.prototype.once);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'un',
    ol.geom.MultiPoint.prototype.un);

goog.exportProperty(
    ol.geom.MultiPoint.prototype,
    'unByKey',
    ol.geom.MultiPoint.prototype.unByKey);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getFirstCoordinate',
    ol.geom.MultiPolygon.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getLastCoordinate',
    ol.geom.MultiPolygon.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getLayout',
    ol.geom.MultiPolygon.prototype.getLayout);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'rotate',
    ol.geom.MultiPolygon.prototype.rotate);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'scale',
    ol.geom.MultiPolygon.prototype.scale);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getClosestPoint',
    ol.geom.MultiPolygon.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'intersectsCoordinate',
    ol.geom.MultiPolygon.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getExtent',
    ol.geom.MultiPolygon.prototype.getExtent);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'simplify',
    ol.geom.MultiPolygon.prototype.simplify);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'transform',
    ol.geom.MultiPolygon.prototype.transform);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'get',
    ol.geom.MultiPolygon.prototype.get);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getKeys',
    ol.geom.MultiPolygon.prototype.getKeys);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getProperties',
    ol.geom.MultiPolygon.prototype.getProperties);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'set',
    ol.geom.MultiPolygon.prototype.set);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'setProperties',
    ol.geom.MultiPolygon.prototype.setProperties);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'unset',
    ol.geom.MultiPolygon.prototype.unset);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'changed',
    ol.geom.MultiPolygon.prototype.changed);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'dispatchEvent',
    ol.geom.MultiPolygon.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'getRevision',
    ol.geom.MultiPolygon.prototype.getRevision);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'on',
    ol.geom.MultiPolygon.prototype.on);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'once',
    ol.geom.MultiPolygon.prototype.once);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'un',
    ol.geom.MultiPolygon.prototype.un);

goog.exportProperty(
    ol.geom.MultiPolygon.prototype,
    'unByKey',
    ol.geom.MultiPolygon.prototype.unByKey);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getFirstCoordinate',
    ol.geom.Point.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getLastCoordinate',
    ol.geom.Point.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getLayout',
    ol.geom.Point.prototype.getLayout);

goog.exportProperty(
    ol.geom.Point.prototype,
    'rotate',
    ol.geom.Point.prototype.rotate);

goog.exportProperty(
    ol.geom.Point.prototype,
    'scale',
    ol.geom.Point.prototype.scale);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getClosestPoint',
    ol.geom.Point.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.Point.prototype,
    'intersectsCoordinate',
    ol.geom.Point.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getExtent',
    ol.geom.Point.prototype.getExtent);

goog.exportProperty(
    ol.geom.Point.prototype,
    'simplify',
    ol.geom.Point.prototype.simplify);

goog.exportProperty(
    ol.geom.Point.prototype,
    'transform',
    ol.geom.Point.prototype.transform);

goog.exportProperty(
    ol.geom.Point.prototype,
    'get',
    ol.geom.Point.prototype.get);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getKeys',
    ol.geom.Point.prototype.getKeys);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getProperties',
    ol.geom.Point.prototype.getProperties);

goog.exportProperty(
    ol.geom.Point.prototype,
    'set',
    ol.geom.Point.prototype.set);

goog.exportProperty(
    ol.geom.Point.prototype,
    'setProperties',
    ol.geom.Point.prototype.setProperties);

goog.exportProperty(
    ol.geom.Point.prototype,
    'unset',
    ol.geom.Point.prototype.unset);

goog.exportProperty(
    ol.geom.Point.prototype,
    'changed',
    ol.geom.Point.prototype.changed);

goog.exportProperty(
    ol.geom.Point.prototype,
    'dispatchEvent',
    ol.geom.Point.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.Point.prototype,
    'getRevision',
    ol.geom.Point.prototype.getRevision);

goog.exportProperty(
    ol.geom.Point.prototype,
    'on',
    ol.geom.Point.prototype.on);

goog.exportProperty(
    ol.geom.Point.prototype,
    'once',
    ol.geom.Point.prototype.once);

goog.exportProperty(
    ol.geom.Point.prototype,
    'un',
    ol.geom.Point.prototype.un);

goog.exportProperty(
    ol.geom.Point.prototype,
    'unByKey',
    ol.geom.Point.prototype.unByKey);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getFirstCoordinate',
    ol.geom.Polygon.prototype.getFirstCoordinate);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getLastCoordinate',
    ol.geom.Polygon.prototype.getLastCoordinate);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getLayout',
    ol.geom.Polygon.prototype.getLayout);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'rotate',
    ol.geom.Polygon.prototype.rotate);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'scale',
    ol.geom.Polygon.prototype.scale);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getClosestPoint',
    ol.geom.Polygon.prototype.getClosestPoint);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'intersectsCoordinate',
    ol.geom.Polygon.prototype.intersectsCoordinate);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getExtent',
    ol.geom.Polygon.prototype.getExtent);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'simplify',
    ol.geom.Polygon.prototype.simplify);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'transform',
    ol.geom.Polygon.prototype.transform);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'get',
    ol.geom.Polygon.prototype.get);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getKeys',
    ol.geom.Polygon.prototype.getKeys);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getProperties',
    ol.geom.Polygon.prototype.getProperties);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'set',
    ol.geom.Polygon.prototype.set);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'setProperties',
    ol.geom.Polygon.prototype.setProperties);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'unset',
    ol.geom.Polygon.prototype.unset);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'changed',
    ol.geom.Polygon.prototype.changed);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'dispatchEvent',
    ol.geom.Polygon.prototype.dispatchEvent);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'getRevision',
    ol.geom.Polygon.prototype.getRevision);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'on',
    ol.geom.Polygon.prototype.on);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'once',
    ol.geom.Polygon.prototype.once);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'un',
    ol.geom.Polygon.prototype.un);

goog.exportProperty(
    ol.geom.Polygon.prototype,
    'unByKey',
    ol.geom.Polygon.prototype.unByKey);

goog.exportProperty(
    ol.format.GML.prototype,
    'readFeatures',
    ol.format.GML.prototype.readFeatures);

goog.exportProperty(
    ol.format.GML2.prototype,
    'readFeatures',
    ol.format.GML2.prototype.readFeatures);

goog.exportProperty(
    ol.format.GML3.prototype,
    'readFeatures',
    ol.format.GML3.prototype.readFeatures);

goog.exportProperty(
    ol.control.Control.prototype,
    'get',
    ol.control.Control.prototype.get);

goog.exportProperty(
    ol.control.Control.prototype,
    'getKeys',
    ol.control.Control.prototype.getKeys);

goog.exportProperty(
    ol.control.Control.prototype,
    'getProperties',
    ol.control.Control.prototype.getProperties);

goog.exportProperty(
    ol.control.Control.prototype,
    'set',
    ol.control.Control.prototype.set);

goog.exportProperty(
    ol.control.Control.prototype,
    'setProperties',
    ol.control.Control.prototype.setProperties);

goog.exportProperty(
    ol.control.Control.prototype,
    'unset',
    ol.control.Control.prototype.unset);

goog.exportProperty(
    ol.control.Control.prototype,
    'changed',
    ol.control.Control.prototype.changed);

goog.exportProperty(
    ol.control.Control.prototype,
    'dispatchEvent',
    ol.control.Control.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.Control.prototype,
    'getRevision',
    ol.control.Control.prototype.getRevision);

goog.exportProperty(
    ol.control.Control.prototype,
    'on',
    ol.control.Control.prototype.on);

goog.exportProperty(
    ol.control.Control.prototype,
    'once',
    ol.control.Control.prototype.once);

goog.exportProperty(
    ol.control.Control.prototype,
    'un',
    ol.control.Control.prototype.un);

goog.exportProperty(
    ol.control.Control.prototype,
    'unByKey',
    ol.control.Control.prototype.unByKey);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'getMap',
    ol.control.Attribution.prototype.getMap);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'setMap',
    ol.control.Attribution.prototype.setMap);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'setTarget',
    ol.control.Attribution.prototype.setTarget);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'get',
    ol.control.Attribution.prototype.get);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'getKeys',
    ol.control.Attribution.prototype.getKeys);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'getProperties',
    ol.control.Attribution.prototype.getProperties);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'set',
    ol.control.Attribution.prototype.set);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'setProperties',
    ol.control.Attribution.prototype.setProperties);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'unset',
    ol.control.Attribution.prototype.unset);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'changed',
    ol.control.Attribution.prototype.changed);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'dispatchEvent',
    ol.control.Attribution.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'getRevision',
    ol.control.Attribution.prototype.getRevision);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'on',
    ol.control.Attribution.prototype.on);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'once',
    ol.control.Attribution.prototype.once);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'un',
    ol.control.Attribution.prototype.un);

goog.exportProperty(
    ol.control.Attribution.prototype,
    'unByKey',
    ol.control.Attribution.prototype.unByKey);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'getMap',
    ol.control.FullScreen.prototype.getMap);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'setMap',
    ol.control.FullScreen.prototype.setMap);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'setTarget',
    ol.control.FullScreen.prototype.setTarget);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'get',
    ol.control.FullScreen.prototype.get);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'getKeys',
    ol.control.FullScreen.prototype.getKeys);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'getProperties',
    ol.control.FullScreen.prototype.getProperties);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'set',
    ol.control.FullScreen.prototype.set);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'setProperties',
    ol.control.FullScreen.prototype.setProperties);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'unset',
    ol.control.FullScreen.prototype.unset);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'changed',
    ol.control.FullScreen.prototype.changed);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'dispatchEvent',
    ol.control.FullScreen.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'getRevision',
    ol.control.FullScreen.prototype.getRevision);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'on',
    ol.control.FullScreen.prototype.on);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'once',
    ol.control.FullScreen.prototype.once);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'un',
    ol.control.FullScreen.prototype.un);

goog.exportProperty(
    ol.control.FullScreen.prototype,
    'unByKey',
    ol.control.FullScreen.prototype.unByKey);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'getMap',
    ol.control.MousePosition.prototype.getMap);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'setMap',
    ol.control.MousePosition.prototype.setMap);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'setTarget',
    ol.control.MousePosition.prototype.setTarget);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'get',
    ol.control.MousePosition.prototype.get);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'getKeys',
    ol.control.MousePosition.prototype.getKeys);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'getProperties',
    ol.control.MousePosition.prototype.getProperties);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'set',
    ol.control.MousePosition.prototype.set);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'setProperties',
    ol.control.MousePosition.prototype.setProperties);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'unset',
    ol.control.MousePosition.prototype.unset);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'changed',
    ol.control.MousePosition.prototype.changed);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'dispatchEvent',
    ol.control.MousePosition.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'getRevision',
    ol.control.MousePosition.prototype.getRevision);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'on',
    ol.control.MousePosition.prototype.on);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'once',
    ol.control.MousePosition.prototype.once);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'un',
    ol.control.MousePosition.prototype.un);

goog.exportProperty(
    ol.control.MousePosition.prototype,
    'unByKey',
    ol.control.MousePosition.prototype.unByKey);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getMap',
    ol.control.OverviewMap.prototype.getMap);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'setMap',
    ol.control.OverviewMap.prototype.setMap);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'setTarget',
    ol.control.OverviewMap.prototype.setTarget);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'get',
    ol.control.OverviewMap.prototype.get);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getKeys',
    ol.control.OverviewMap.prototype.getKeys);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getProperties',
    ol.control.OverviewMap.prototype.getProperties);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'set',
    ol.control.OverviewMap.prototype.set);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'setProperties',
    ol.control.OverviewMap.prototype.setProperties);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'unset',
    ol.control.OverviewMap.prototype.unset);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'changed',
    ol.control.OverviewMap.prototype.changed);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'dispatchEvent',
    ol.control.OverviewMap.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'getRevision',
    ol.control.OverviewMap.prototype.getRevision);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'on',
    ol.control.OverviewMap.prototype.on);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'once',
    ol.control.OverviewMap.prototype.once);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'un',
    ol.control.OverviewMap.prototype.un);

goog.exportProperty(
    ol.control.OverviewMap.prototype,
    'unByKey',
    ol.control.OverviewMap.prototype.unByKey);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'getMap',
    ol.control.Rotate.prototype.getMap);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'setMap',
    ol.control.Rotate.prototype.setMap);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'setTarget',
    ol.control.Rotate.prototype.setTarget);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'get',
    ol.control.Rotate.prototype.get);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'getKeys',
    ol.control.Rotate.prototype.getKeys);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'getProperties',
    ol.control.Rotate.prototype.getProperties);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'set',
    ol.control.Rotate.prototype.set);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'setProperties',
    ol.control.Rotate.prototype.setProperties);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'unset',
    ol.control.Rotate.prototype.unset);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'changed',
    ol.control.Rotate.prototype.changed);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'dispatchEvent',
    ol.control.Rotate.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'getRevision',
    ol.control.Rotate.prototype.getRevision);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'on',
    ol.control.Rotate.prototype.on);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'once',
    ol.control.Rotate.prototype.once);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'un',
    ol.control.Rotate.prototype.un);

goog.exportProperty(
    ol.control.Rotate.prototype,
    'unByKey',
    ol.control.Rotate.prototype.unByKey);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'getMap',
    ol.control.ScaleLine.prototype.getMap);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'setMap',
    ol.control.ScaleLine.prototype.setMap);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'setTarget',
    ol.control.ScaleLine.prototype.setTarget);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'get',
    ol.control.ScaleLine.prototype.get);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'getKeys',
    ol.control.ScaleLine.prototype.getKeys);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'getProperties',
    ol.control.ScaleLine.prototype.getProperties);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'set',
    ol.control.ScaleLine.prototype.set);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'setProperties',
    ol.control.ScaleLine.prototype.setProperties);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'unset',
    ol.control.ScaleLine.prototype.unset);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'changed',
    ol.control.ScaleLine.prototype.changed);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'dispatchEvent',
    ol.control.ScaleLine.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'getRevision',
    ol.control.ScaleLine.prototype.getRevision);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'on',
    ol.control.ScaleLine.prototype.on);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'once',
    ol.control.ScaleLine.prototype.once);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'un',
    ol.control.ScaleLine.prototype.un);

goog.exportProperty(
    ol.control.ScaleLine.prototype,
    'unByKey',
    ol.control.ScaleLine.prototype.unByKey);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'getMap',
    ol.control.Zoom.prototype.getMap);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'setMap',
    ol.control.Zoom.prototype.setMap);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'setTarget',
    ol.control.Zoom.prototype.setTarget);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'get',
    ol.control.Zoom.prototype.get);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'getKeys',
    ol.control.Zoom.prototype.getKeys);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'getProperties',
    ol.control.Zoom.prototype.getProperties);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'set',
    ol.control.Zoom.prototype.set);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'setProperties',
    ol.control.Zoom.prototype.setProperties);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'unset',
    ol.control.Zoom.prototype.unset);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'changed',
    ol.control.Zoom.prototype.changed);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'dispatchEvent',
    ol.control.Zoom.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'getRevision',
    ol.control.Zoom.prototype.getRevision);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'on',
    ol.control.Zoom.prototype.on);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'once',
    ol.control.Zoom.prototype.once);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'un',
    ol.control.Zoom.prototype.un);

goog.exportProperty(
    ol.control.Zoom.prototype,
    'unByKey',
    ol.control.Zoom.prototype.unByKey);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'getMap',
    ol.control.ZoomSlider.prototype.getMap);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'setMap',
    ol.control.ZoomSlider.prototype.setMap);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'setTarget',
    ol.control.ZoomSlider.prototype.setTarget);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'get',
    ol.control.ZoomSlider.prototype.get);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'getKeys',
    ol.control.ZoomSlider.prototype.getKeys);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'getProperties',
    ol.control.ZoomSlider.prototype.getProperties);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'set',
    ol.control.ZoomSlider.prototype.set);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'setProperties',
    ol.control.ZoomSlider.prototype.setProperties);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'unset',
    ol.control.ZoomSlider.prototype.unset);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'changed',
    ol.control.ZoomSlider.prototype.changed);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'dispatchEvent',
    ol.control.ZoomSlider.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'getRevision',
    ol.control.ZoomSlider.prototype.getRevision);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'on',
    ol.control.ZoomSlider.prototype.on);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'once',
    ol.control.ZoomSlider.prototype.once);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'un',
    ol.control.ZoomSlider.prototype.un);

goog.exportProperty(
    ol.control.ZoomSlider.prototype,
    'unByKey',
    ol.control.ZoomSlider.prototype.unByKey);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'getMap',
    ol.control.ZoomToExtent.prototype.getMap);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'setMap',
    ol.control.ZoomToExtent.prototype.setMap);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'setTarget',
    ol.control.ZoomToExtent.prototype.setTarget);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'get',
    ol.control.ZoomToExtent.prototype.get);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'getKeys',
    ol.control.ZoomToExtent.prototype.getKeys);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'getProperties',
    ol.control.ZoomToExtent.prototype.getProperties);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'set',
    ol.control.ZoomToExtent.prototype.set);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'setProperties',
    ol.control.ZoomToExtent.prototype.setProperties);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'unset',
    ol.control.ZoomToExtent.prototype.unset);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'changed',
    ol.control.ZoomToExtent.prototype.changed);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'dispatchEvent',
    ol.control.ZoomToExtent.prototype.dispatchEvent);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'getRevision',
    ol.control.ZoomToExtent.prototype.getRevision);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'on',
    ol.control.ZoomToExtent.prototype.on);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'once',
    ol.control.ZoomToExtent.prototype.once);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'un',
    ol.control.ZoomToExtent.prototype.un);

goog.exportProperty(
    ol.control.ZoomToExtent.prototype,
    'unByKey',
    ol.control.ZoomToExtent.prototype.unByKey);
ol.VERSION = 'v3.18.2';
OPENLAYERS.ol = ol;

  return OPENLAYERS.ol;
}));

/**
 * OpenLayers 3 Layer Switcher Control.
 * See [the examples](./examples) for usage.
 * @constructor
 * @extends {ol.control.Control}
 * @param {Object} opt_options Control options, extends olx.control.ControlOptions adding:
 *                              **`tipLabel`** `String` - the button tooltip.
 */
ol.control.LayerSwitcher = function(opt_options) {

    var options = opt_options || {};

    var tipLabel = options.tipLabel ?
      options.tipLabel : 'Legend';

    this.mapListeners = [];

    this.hiddenClassName = 'ol-unselectable ol-control layer-switcher';
    if (ol.control.LayerSwitcher.isTouchDevice_()) {
        this.hiddenClassName += ' touch';
    }
    this.shownClassName = this.hiddenClassName + ' shown';

    var element = document.createElement('div');
    element.className = this.hiddenClassName;

    var button = document.createElement('button');
    button.setAttribute('title', tipLabel);
    element.appendChild(button);

    this.panel = document.createElement('div');
    this.panel.className = 'panel';
    element.appendChild(this.panel);
    ol.control.LayerSwitcher.enableTouchScroll_(this.panel);

    var this_ = this;

    button.onmouseover = function(e) {
        this_.showPanel();
    };

    button.onclick = function(e) {
        e = e || window.event;
        this_.showPanel();
        e.preventDefault();
    };

    this_.panel.onmouseout = function(e) {
        e = e || window.event;
        if (!this_.panel.contains(e.toElement || e.relatedTarget)) {
            this_.hidePanel();
        }
    };

    ol.control.Control.call(this, {
        element: element,
        target: options.target
    });

};

ol.inherits(ol.control.LayerSwitcher, ol.control.Control);

/**
 * Show the layer panel.
 */
ol.control.LayerSwitcher.prototype.showPanel = function() {
    if (this.element.className != this.shownClassName) {
        this.element.className = this.shownClassName;
        this.renderPanel();
    }
};

/**
 * Hide the layer panel.
 */
ol.control.LayerSwitcher.prototype.hidePanel = function() {
    if (this.element.className != this.hiddenClassName) {
        this.element.className = this.hiddenClassName;
    }
};

/**
 * Re-draw the layer panel to represent the current state of the layers.
 */
ol.control.LayerSwitcher.prototype.renderPanel = function() {

    this.ensureTopVisibleBaseLayerShown_();

    while(this.panel.firstChild) {
        this.panel.removeChild(this.panel.firstChild);
    }

    var ul = document.createElement('ul');
    this.panel.appendChild(ul);
    this.renderLayers_(this.getMap(), ul);

};

/**
 * Set the map instance the control is associated with.
 * @param {ol.Map} map The map instance.
 */
ol.control.LayerSwitcher.prototype.setMap = function(map) {
    // Clean up listeners associated with the previous map
    for (var i = 0, key; i < this.mapListeners.length; i++) {
        this.getMap().unByKey(this.mapListeners[i]);
    }
    this.mapListeners.length = 0;
    // Wire up listeners etc. and store reference to new map
    ol.control.Control.prototype.setMap.call(this, map);
    if (map) {
        var this_ = this;
        this.mapListeners.push(map.on('pointerdown', function() {
            this_.hidePanel();
        }));
        this.renderPanel();
    }
};

/**
 * Ensure only the top-most base layer is visible if more than one is visible.
 * @private
 */
ol.control.LayerSwitcher.prototype.ensureTopVisibleBaseLayerShown_ = function() {
    var lastVisibleBaseLyr;
    ol.control.LayerSwitcher.forEachRecursive(this.getMap(), function(l, idx, a) {
        if (l.get('type') === 'base' && l.getVisible()) {
            lastVisibleBaseLyr = l;
        }
    });
    if (lastVisibleBaseLyr) this.setVisible_(lastVisibleBaseLyr, true);
};

/**
 * Toggle the visible state of a layer.
 * Takes care of hiding other layers in the same exclusive group if the layer
 * is toggle to visible.
 * @private
 * @param {ol.layer.Base} The layer whos visibility will be toggled.
 */
ol.control.LayerSwitcher.prototype.setVisible_ = function(lyr, visible) {
    var map = this.getMap();
    lyr.setVisible(visible);
    if (visible && lyr.get('type') === 'base') {
        // Hide all other base layers regardless of grouping
        ol.control.LayerSwitcher.forEachRecursive(map, function(l, idx, a) {
            if (l != lyr && l.get('type') === 'base') {
                l.setVisible(false);
            }
        });
    }
};

/**
 * Render all layers that are children of a group.
 * @private
 * @param {ol.layer.Base} lyr Layer to be rendered (should have a title property).
 * @param {Number} idx Position in parent group list.
 */
ol.control.LayerSwitcher.prototype.renderLayer_ = function(lyr, idx) {

    var this_ = this;

    var li = document.createElement('li');

    var lyrTitle = lyr.get('title');
    var lyrId = lyr.get('title').replace(/\s+/g, '-') + '_' + idx;

    var label = document.createElement('label');

    if (lyr.getLayers) {

        li.className = 'group';
        label.innerHTML = lyrTitle;
        li.appendChild(label);
        var ul = document.createElement('ul');
        li.appendChild(ul);

        this.renderLayers_(lyr, ul);

    } else {

        li.className = 'layer';
        var input = document.createElement('input');
        if (lyr.get('type') === 'base') {
            input.type = 'radio';
            input.name = 'base';
        } else {
            input.type = 'checkbox';
        }
        input.id = lyrId;
        input.checked = lyr.get('visible');
        input.onchange = function(e) {
            this_.setVisible_(lyr, e.target.checked);
        };
        li.appendChild(input);

        label.htmlFor = lyrId;
        label.innerHTML = lyrTitle;
        li.appendChild(label);

    }

    return li;

};

/**
 * Render all layers that are children of a group.
 * @private
 * @param {ol.layer.Group} lyr Group layer whos children will be rendered.
 * @param {Element} elm DOM element that children will be appended to.
 */
ol.control.LayerSwitcher.prototype.renderLayers_ = function(lyr, elm) {
    var lyrs = lyr.getLayers().getArray().slice().reverse();
    for (var i = 0, l; i < lyrs.length; i++) {
        l = lyrs[i];
        if (l.get('title')) {
            elm.appendChild(this.renderLayer_(l, i));
        }
    }
};

/**
 * **Static** Call the supplied function for each layer in the passed layer group
 * recursing nested groups.
 * @param {ol.layer.Group} lyr The layer group to start iterating from.
 * @param {Function} fn Callback which will be called for each `ol.layer.Base`
 * found under `lyr`. The signature for `fn` is the same as `ol.Collection#forEach`
 */
ol.control.LayerSwitcher.forEachRecursive = function(lyr, fn) {
    lyr.getLayers().forEach(function(lyr, idx, a) {
        fn(lyr, idx, a);
        if (lyr.getLayers) {
            ol.control.LayerSwitcher.forEachRecursive(lyr, fn);
        }
    });
};

/**
* @private
* @desc Apply workaround to enable scrolling of overflowing content within an
* element. Adapted from https://gist.github.com/chrismbarr/4107472
*/
ol.control.LayerSwitcher.enableTouchScroll_ = function(elm) {
   if(ol.control.LayerSwitcher.isTouchDevice_()){
       var scrollStartPos = 0;
       elm.addEventListener("touchstart", function(event) {
           scrollStartPos = this.scrollTop + event.touches[0].pageY;
       }, false);
       elm.addEventListener("touchmove", function(event) {
           this.scrollTop = scrollStartPos - event.touches[0].pageY;
       }, false);
   }
};

/**
 * @private
 * @desc Determine if the current browser supports touch events. Adapted from
 * https://gist.github.com/chrismbarr/4107472
 */
ol.control.LayerSwitcher.isTouchDevice_ = function() {
    try {
        document.createEvent("TouchEvent");
        return true;
    } catch(e) {
        return false;
    }
};

/*
 Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
 (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
(function (window, document, undefined) {
var L = {
	version: "1.0.3"
};

function expose() {
	var oldL = window.L;

	L.noConflict = function () {
		window.L = oldL;
		return this;
	};

	window.L = L;
}

// define Leaflet for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
	module.exports = L;

// define Leaflet as an AMD module
} else if (typeof define === 'function' && define.amd) {
	define(L);
}

// define Leaflet as a global L variable, saving the original L to restore later if needed
if (typeof window !== 'undefined') {
	expose();
}



/*
 * @namespace Util
 *
 * Various utility functions, used by Leaflet internally.
 */

L.Util = {

	// @function extend(dest: Object, src?: Object): Object
	// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
	extend: function (dest) {
		var i, j, len, src;

		for (j = 1, len = arguments.length; j < len; j++) {
			src = arguments[j];
			for (i in src) {
				dest[i] = src[i];
			}
		}
		return dest;
	},

	// @function create(proto: Object, properties?: Object): Object
	// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
	create: Object.create || (function () {
		function F() {}
		return function (proto) {
			F.prototype = proto;
			return new F();
		};
	})(),

	// @function bind(fn: Function, …): Function
	// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
	// Has a `L.bind()` shortcut.
	bind: function (fn, obj) {
		var slice = Array.prototype.slice;

		if (fn.bind) {
			return fn.bind.apply(fn, slice.call(arguments, 1));
		}

		var args = slice.call(arguments, 2);

		return function () {
			return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
		};
	},

	// @function stamp(obj: Object): Number
	// Returns the unique ID of an object, assiging it one if it doesn't have it.
	stamp: function (obj) {
		/*eslint-disable */
		obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
		return obj._leaflet_id;
		/*eslint-enable */
	},

	// @property lastId: Number
	// Last unique ID used by [`stamp()`](#util-stamp)
	lastId: 0,

	// @function throttle(fn: Function, time: Number, context: Object): Function
	// Returns a function which executes function `fn` with the given scope `context`
	// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
	// `fn` will be called no more than one time per given amount of `time`. The arguments
	// received by the bound function will be any arguments passed when binding the
	// function, followed by any arguments passed when invoking the bound function.
	// Has an `L.bind` shortcut.
	throttle: function (fn, time, context) {
		var lock, args, wrapperFn, later;

		later = function () {
			// reset lock and call if queued
			lock = false;
			if (args) {
				wrapperFn.apply(context, args);
				args = false;
			}
		};

		wrapperFn = function () {
			if (lock) {
				// called too soon, queue to call later
				args = arguments;

			} else {
				// call and lock until later
				fn.apply(context, arguments);
				setTimeout(later, time);
				lock = true;
			}
		};

		return wrapperFn;
	},

	// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
	// Returns the number `num` modulo `range` in such a way so it lies within
	// `range[0]` and `range[1]`. The returned value will be always smaller than
	// `range[1]` unless `includeMax` is set to `true`.
	wrapNum: function (x, range, includeMax) {
		var max = range[1],
		    min = range[0],
		    d = max - min;
		return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
	},

	// @function falseFn(): Function
	// Returns a function which always returns `false`.
	falseFn: function () { return false; },

	// @function formatNum(num: Number, digits?: Number): Number
	// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
	formatNum: function (num, digits) {
		var pow = Math.pow(10, digits || 5);
		return Math.round(num * pow) / pow;
	},

	// @function trim(str: String): String
	// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
	trim: function (str) {
		return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
	},

	// @function splitWords(str: String): String[]
	// Trims and splits the string on whitespace and returns the array of parts.
	splitWords: function (str) {
		return L.Util.trim(str).split(/\s+/);
	},

	// @function setOptions(obj: Object, options: Object): Object
	// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
	setOptions: function (obj, options) {
		if (!obj.hasOwnProperty('options')) {
			obj.options = obj.options ? L.Util.create(obj.options) : {};
		}
		for (var i in options) {
			obj.options[i] = options[i];
		}
		return obj.options;
	},

	// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
	// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
	// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
	// be appended at the end. If `uppercase` is `true`, the parameter names will
	// be uppercased (e.g. `'?A=foo&B=bar'`)
	getParamString: function (obj, existingUrl, uppercase) {
		var params = [];
		for (var i in obj) {
			params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
		}
		return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
	},

	// @function template(str: String, data: Object): String
	// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
	// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
	// `('Hello foo, bar')`. You can also specify functions instead of strings for
	// data values — they will be evaluated passing `data` as an argument.
	template: function (str, data) {
		return str.replace(L.Util.templateRe, function (str, key) {
			var value = data[key];

			if (value === undefined) {
				throw new Error('No value provided for variable ' + str);

			} else if (typeof value === 'function') {
				value = value(data);
			}
			return value;
		});
	},

	templateRe: /\{ *([\w_\-]+) *\}/g,

	// @function isArray(obj): Boolean
	// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
	isArray: Array.isArray || function (obj) {
		return (Object.prototype.toString.call(obj) === '[object Array]');
	},

	// @function indexOf(array: Array, el: Object): Number
	// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
	indexOf: function (array, el) {
		for (var i = 0; i < array.length; i++) {
			if (array[i] === el) { return i; }
		}
		return -1;
	},

	// @property emptyImageUrl: String
	// Data URI string containing a base64-encoded empty GIF image.
	// Used as a hack to free memory from unused images on WebKit-powered
	// mobile devices (by setting image `src` to this string).
	emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
};

(function () {
	// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/

	function getPrefixed(name) {
		return window['webkit' + name] || window['moz' + name] || window['ms' + name];
	}

	var lastTime = 0;

	// fallback for IE 7-8
	function timeoutDefer(fn) {
		var time = +new Date(),
		    timeToCall = Math.max(0, 16 - (time - lastTime));

		lastTime = time + timeToCall;
		return window.setTimeout(fn, timeToCall);
	}

	var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
	    cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
	               getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };


	// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
	// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
	// `context` if given. When `immediate` is set, `fn` is called immediately if
	// the browser doesn't have native support for
	// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
	// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
	L.Util.requestAnimFrame = function (fn, context, immediate) {
		if (immediate && requestFn === timeoutDefer) {
			fn.call(context);
		} else {
			return requestFn.call(window, L.bind(fn, context));
		}
	};

	// @function cancelAnimFrame(id: Number): undefined
	// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
	L.Util.cancelAnimFrame = function (id) {
		if (id) {
			cancelFn.call(window, id);
		}
	};
})();

// shortcuts for most used utility functions
L.extend = L.Util.extend;
L.bind = L.Util.bind;
L.stamp = L.Util.stamp;
L.setOptions = L.Util.setOptions;




// @class Class
// @aka L.Class

// @section
// @uninheritable

// Thanks to John Resig and Dean Edwards for inspiration!

L.Class = function () {};

L.Class.extend = function (props) {

	// @function extend(props: Object): Function
	// [Extends the current class](#class-inheritance) given the properties to be included.
	// Returns a Javascript function that is a class constructor (to be called with `new`).
	var NewClass = function () {

		// call the constructor
		if (this.initialize) {
			this.initialize.apply(this, arguments);
		}

		// call all constructor hooks
		this.callInitHooks();
	};

	var parentProto = NewClass.__super__ = this.prototype;

	var proto = L.Util.create(parentProto);
	proto.constructor = NewClass;

	NewClass.prototype = proto;

	// inherit parent's statics
	for (var i in this) {
		if (this.hasOwnProperty(i) && i !== 'prototype') {
			NewClass[i] = this[i];
		}
	}

	// mix static properties into the class
	if (props.statics) {
		L.extend(NewClass, props.statics);
		delete props.statics;
	}

	// mix includes into the prototype
	if (props.includes) {
		L.Util.extend.apply(null, [proto].concat(props.includes));
		delete props.includes;
	}

	// merge options
	if (proto.options) {
		props.options = L.Util.extend(L.Util.create(proto.options), props.options);
	}

	// mix given properties into the prototype
	L.extend(proto, props);

	proto._initHooks = [];

	// add method for calling all hooks
	proto.callInitHooks = function () {

		if (this._initHooksCalled) { return; }

		if (parentProto.callInitHooks) {
			parentProto.callInitHooks.call(this);
		}

		this._initHooksCalled = true;

		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
			proto._initHooks[i].call(this);
		}
	};

	return NewClass;
};


// @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class.
L.Class.include = function (props) {
	L.extend(this.prototype, props);
	return this;
};

// @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class.
L.Class.mergeOptions = function (options) {
	L.extend(this.prototype.options, options);
	return this;
};

// @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class.
L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
	var args = Array.prototype.slice.call(arguments, 1);

	var init = typeof fn === 'function' ? fn : function () {
		this[fn].apply(this, args);
	};

	this.prototype._initHooks = this.prototype._initHooks || [];
	this.prototype._initHooks.push(init);
	return this;
};



/*
 * @class Evented
 * @aka L.Evented
 * @inherits Class
 *
 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
 *
 * @example
 *
 * ```js
 * map.on('click', function(e) {
 * 	alert(e.latlng);
 * } );
 * ```
 *
 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
 *
 * ```js
 * function onClick(e) { ... }
 *
 * map.on('click', onClick);
 * map.off('click', onClick);
 * ```
 */


L.Evented = L.Class.extend({

	/* @method on(type: String, fn: Function, context?: Object): this
	 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
	 *
	 * @alternative
	 * @method on(eventMap: Object): this
	 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
	 */
	on: function (types, fn, context) {

		// types can be a map of types/handlers
		if (typeof types === 'object') {
			for (var type in types) {
				// we don't process space-separated events here for performance;
				// it's a hot path since Layer uses the on(obj) syntax
				this._on(type, types[type], fn);
			}

		} else {
			// types can be a string of space-separated words
			types = L.Util.splitWords(types);

			for (var i = 0, len = types.length; i < len; i++) {
				this._on(types[i], fn, context);
			}
		}

		return this;
	},

	/* @method off(type: String, fn?: Function, context?: Object): this
	 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
	 *
	 * @alternative
	 * @method off(eventMap: Object): this
	 * Removes a set of type/listener pairs.
	 *
	 * @alternative
	 * @method off: this
	 * Removes all listeners to all events on the object.
	 */
	off: function (types, fn, context) {

		if (!types) {
			// clear all listeners if called without arguments
			delete this._events;

		} else if (typeof types === 'object') {
			for (var type in types) {
				this._off(type, types[type], fn);
			}

		} else {
			types = L.Util.splitWords(types);

			for (var i = 0, len = types.length; i < len; i++) {
				this._off(types[i], fn, context);
			}
		}

		return this;
	},

	// attach listener (without syntactic sugar now)
	_on: function (type, fn, context) {
		this._events = this._events || {};

		/* get/init listeners for type */
		var typeListeners = this._events[type];
		if (!typeListeners) {
			typeListeners = [];
			this._events[type] = typeListeners;
		}

		if (context === this) {
			// Less memory footprint.
			context = undefined;
		}
		var newListener = {fn: fn, ctx: context},
		    listeners = typeListeners;

		// check if fn already there
		for (var i = 0, len = listeners.length; i < len; i++) {
			if (listeners[i].fn === fn && listeners[i].ctx === context) {
				return;
			}
		}

		listeners.push(newListener);
	},

	_off: function (type, fn, context) {
		var listeners,
		    i,
		    len;

		if (!this._events) { return; }

		listeners = this._events[type];

		if (!listeners) {
			return;
		}

		if (!fn) {
			// Set all removed listeners to noop so they are not called if remove happens in fire
			for (i = 0, len = listeners.length; i < len; i++) {
				listeners[i].fn = L.Util.falseFn;
			}
			// clear all listeners for a type if function isn't specified
			delete this._events[type];
			return;
		}

		if (context === this) {
			context = undefined;
		}

		if (listeners) {

			// find fn and remove it
			for (i = 0, len = listeners.length; i < len; i++) {
				var l = listeners[i];
				if (l.ctx !== context) { continue; }
				if (l.fn === fn) {

					// set the removed listener to noop so that's not called if remove happens in fire
					l.fn = L.Util.falseFn;

					if (this._firingCount) {
						/* copy array in case events are being fired */
						this._events[type] = listeners = listeners.slice();
					}
					listeners.splice(i, 1);

					return;
				}
			}
		}
	},

	// @method fire(type: String, data?: Object, propagate?: Boolean): this
	// Fires an event of the specified type. You can optionally provide an data
	// object — the first argument of the listener function will contain its
	// properties. The event can optionally be propagated to event parents.
	fire: function (type, data, propagate) {
		if (!this.listens(type, propagate)) { return this; }

		var event = L.Util.extend({}, data, {type: type, target: this});

		if (this._events) {
			var listeners = this._events[type];

			if (listeners) {
				this._firingCount = (this._firingCount + 1) || 1;
				for (var i = 0, len = listeners.length; i < len; i++) {
					var l = listeners[i];
					l.fn.call(l.ctx || this, event);
				}

				this._firingCount--;
			}
		}

		if (propagate) {
			// propagate the event to parents (set with addEventParent)
			this._propagateEvent(event);
		}

		return this;
	},

	// @method listens(type: String): Boolean
	// Returns `true` if a particular event type has any listeners attached to it.
	listens: function (type, propagate) {
		var listeners = this._events && this._events[type];
		if (listeners && listeners.length) { return true; }

		if (propagate) {
			// also check parents for listeners if event propagates
			for (var id in this._eventParents) {
				if (this._eventParents[id].listens(type, propagate)) { return true; }
			}
		}
		return false;
	},

	// @method once(…): this
	// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
	once: function (types, fn, context) {

		if (typeof types === 'object') {
			for (var type in types) {
				this.once(type, types[type], fn);
			}
			return this;
		}

		var handler = L.bind(function () {
			this
			    .off(types, fn, context)
			    .off(types, handler, context);
		}, this);

		// add a listener that's executed once and removed after that
		return this
		    .on(types, fn, context)
		    .on(types, handler, context);
	},

	// @method addEventParent(obj: Evented): this
	// Adds an event parent - an `Evented` that will receive propagated events
	addEventParent: function (obj) {
		this._eventParents = this._eventParents || {};
		this._eventParents[L.stamp(obj)] = obj;
		return this;
	},

	// @method removeEventParent(obj: Evented): this
	// Removes an event parent, so it will stop receiving propagated events
	removeEventParent: function (obj) {
		if (this._eventParents) {
			delete this._eventParents[L.stamp(obj)];
		}
		return this;
	},

	_propagateEvent: function (e) {
		for (var id in this._eventParents) {
			this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
		}
	}
});

var proto = L.Evented.prototype;

// aliases; we should ditch those eventually

// @method addEventListener(…): this
// Alias to [`on(…)`](#evented-on)
proto.addEventListener = proto.on;

// @method removeEventListener(…): this
// Alias to [`off(…)`](#evented-off)

// @method clearAllEventListeners(…): this
// Alias to [`off()`](#evented-off)
proto.removeEventListener = proto.clearAllEventListeners = proto.off;

// @method addOneTimeEventListener(…): this
// Alias to [`once(…)`](#evented-once)
proto.addOneTimeEventListener = proto.once;

// @method fireEvent(…): this
// Alias to [`fire(…)`](#evented-fire)
proto.fireEvent = proto.fire;

// @method hasEventListeners(…): Boolean
// Alias to [`listens(…)`](#evented-listens)
proto.hasEventListeners = proto.listens;

L.Mixin = {Events: proto};



/*
 * @namespace Browser
 * @aka L.Browser
 *
 * A namespace with static properties for browser/feature detection used by Leaflet internally.
 *
 * @example
 *
 * ```js
 * if (L.Browser.ielt9) {
 *   alert('Upgrade your browser, dude!');
 * }
 * ```
 */

(function () {

	var ua = navigator.userAgent.toLowerCase(),
	    doc = document.documentElement,

	    ie = 'ActiveXObject' in window,

	    webkit    = ua.indexOf('webkit') !== -1,
	    phantomjs = ua.indexOf('phantom') !== -1,
	    android23 = ua.search('android [23]') !== -1,
	    chrome    = ua.indexOf('chrome') !== -1,
	    gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,

	    win = navigator.platform.indexOf('Win') === 0,

	    mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
	    msPointer = !window.PointerEvent && window.MSPointerEvent,
	    pointer = window.PointerEvent || msPointer,

	    ie3d = ie && ('transition' in doc.style),
	    webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
	    gecko3d = 'MozPerspective' in doc.style,
	    opera12 = 'OTransition' in doc.style;


	var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
			(window.DocumentTouch && document instanceof window.DocumentTouch));

	L.Browser = {

		// @property ie: Boolean
		// `true` for all Internet Explorer versions (not Edge).
		ie: ie,

		// @property ielt9: Boolean
		// `true` for Internet Explorer versions less than 9.
		ielt9: ie && !document.addEventListener,

		// @property edge: Boolean
		// `true` for the Edge web browser.
		edge: 'msLaunchUri' in navigator && !('documentMode' in document),

		// @property webkit: Boolean
		// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
		webkit: webkit,

		// @property gecko: Boolean
		// `true` for gecko-based browsers like Firefox.
		gecko: gecko,

		// @property android: Boolean
		// `true` for any browser running on an Android platform.
		android: ua.indexOf('android') !== -1,

		// @property android23: Boolean
		// `true` for browsers running on Android 2 or Android 3.
		android23: android23,

		// @property chrome: Boolean
		// `true` for the Chrome browser.
		chrome: chrome,

		// @property safari: Boolean
		// `true` for the Safari browser.
		safari: !chrome && ua.indexOf('safari') !== -1,


		// @property win: Boolean
		// `true` when the browser is running in a Windows platform
		win: win,


		// @property ie3d: Boolean
		// `true` for all Internet Explorer versions supporting CSS transforms.
		ie3d: ie3d,

		// @property webkit3d: Boolean
		// `true` for webkit-based browsers supporting CSS transforms.
		webkit3d: webkit3d,

		// @property gecko3d: Boolean
		// `true` for gecko-based browsers supporting CSS transforms.
		gecko3d: gecko3d,

		// @property opera12: Boolean
		// `true` for the Opera browser supporting CSS transforms (version 12 or later).
		opera12: opera12,

		// @property any3d: Boolean
		// `true` for all browsers supporting CSS transforms.
		any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,


		// @property mobile: Boolean
		// `true` for all browsers running in a mobile device.
		mobile: mobile,

		// @property mobileWebkit: Boolean
		// `true` for all webkit-based browsers in a mobile device.
		mobileWebkit: mobile && webkit,

		// @property mobileWebkit3d: Boolean
		// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
		mobileWebkit3d: mobile && webkit3d,

		// @property mobileOpera: Boolean
		// `true` for the Opera browser in a mobile device.
		mobileOpera: mobile && window.opera,

		// @property mobileGecko: Boolean
		// `true` for gecko-based browsers running in a mobile device.
		mobileGecko: mobile && gecko,


		// @property touch: Boolean
		// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
		// This does not necessarily mean that the browser is running in a computer with
		// a touchscreen, it only means that the browser is capable of understanding
		// touch events.
		touch: !!touch,

		// @property msPointer: Boolean
		// `true` for browsers implementing the Microsoft touch events model (notably IE10).
		msPointer: !!msPointer,

		// @property pointer: Boolean
		// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
		pointer: !!pointer,


		// @property retina: Boolean
		// `true` for browsers on a high-resolution "retina" screen.
		retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
	};

}());



/*
 * @class Point
 * @aka L.Point
 *
 * Represents a point with `x` and `y` coordinates in pixels.
 *
 * @example
 *
 * ```js
 * var point = L.point(200, 300);
 * ```
 *
 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
 *
 * ```js
 * map.panBy([200, 300]);
 * map.panBy(L.point(200, 300));
 * ```
 */

L.Point = function (x, y, round) {
	// @property x: Number; The `x` coordinate of the point
	this.x = (round ? Math.round(x) : x);
	// @property y: Number; The `y` coordinate of the point
	this.y = (round ? Math.round(y) : y);
};

L.Point.prototype = {

	// @method clone(): Point
	// Returns a copy of the current point.
	clone: function () {
		return new L.Point(this.x, this.y);
	},

	// @method add(otherPoint: Point): Point
	// Returns the result of addition of the current and the given points.
	add: function (point) {
		// non-destructive, returns a new point
		return this.clone()._add(L.point(point));
	},

	_add: function (point) {
		// destructive, used directly for performance in situations where it's safe to modify existing point
		this.x += point.x;
		this.y += point.y;
		return this;
	},

	// @method subtract(otherPoint: Point): Point
	// Returns the result of subtraction of the given point from the current.
	subtract: function (point) {
		return this.clone()._subtract(L.point(point));
	},

	_subtract: function (point) {
		this.x -= point.x;
		this.y -= point.y;
		return this;
	},

	// @method divideBy(num: Number): Point
	// Returns the result of division of the current point by the given number.
	divideBy: function (num) {
		return this.clone()._divideBy(num);
	},

	_divideBy: function (num) {
		this.x /= num;
		this.y /= num;
		return this;
	},

	// @method multiplyBy(num: Number): Point
	// Returns the result of multiplication of the current point by the given number.
	multiplyBy: function (num) {
		return this.clone()._multiplyBy(num);
	},

	_multiplyBy: function (num) {
		this.x *= num;
		this.y *= num;
		return this;
	},

	// @method scaleBy(scale: Point): Point
	// Multiply each coordinate of the current point by each coordinate of
	// `scale`. In linear algebra terms, multiply the point by the
	// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
	// defined by `scale`.
	scaleBy: function (point) {
		return new L.Point(this.x * point.x, this.y * point.y);
	},

	// @method unscaleBy(scale: Point): Point
	// Inverse of `scaleBy`. Divide each coordinate of the current point by
	// each coordinate of `scale`.
	unscaleBy: function (point) {
		return new L.Point(this.x / point.x, this.y / point.y);
	},

	// @method round(): Point
	// Returns a copy of the current point with rounded coordinates.
	round: function () {
		return this.clone()._round();
	},

	_round: function () {
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);
		return this;
	},

	// @method floor(): Point
	// Returns a copy of the current point with floored coordinates (rounded down).
	floor: function () {
		return this.clone()._floor();
	},

	_floor: function () {
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		return this;
	},

	// @method ceil(): Point
	// Returns a copy of the current point with ceiled coordinates (rounded up).
	ceil: function () {
		return this.clone()._ceil();
	},

	_ceil: function () {
		this.x = Math.ceil(this.x);
		this.y = Math.ceil(this.y);
		return this;
	},

	// @method distanceTo(otherPoint: Point): Number
	// Returns the cartesian distance between the current and the given points.
	distanceTo: function (point) {
		point = L.point(point);

		var x = point.x - this.x,
		    y = point.y - this.y;

		return Math.sqrt(x * x + y * y);
	},

	// @method equals(otherPoint: Point): Boolean
	// Returns `true` if the given point has the same coordinates.
	equals: function (point) {
		point = L.point(point);

		return point.x === this.x &&
		       point.y === this.y;
	},

	// @method contains(otherPoint: Point): Boolean
	// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
	contains: function (point) {
		point = L.point(point);

		return Math.abs(point.x) <= Math.abs(this.x) &&
		       Math.abs(point.y) <= Math.abs(this.y);
	},

	// @method toString(): String
	// Returns a string representation of the point for debugging purposes.
	toString: function () {
		return 'Point(' +
		        L.Util.formatNum(this.x) + ', ' +
		        L.Util.formatNum(this.y) + ')';
	}
};

// @factory L.point(x: Number, y: Number, round?: Boolean)
// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.

// @alternative
// @factory L.point(coords: Number[])
// Expects an array of the form `[x, y]` instead.

// @alternative
// @factory L.point(coords: Object)
// Expects a plain object of the form `{x: Number, y: Number}` instead.
L.point = function (x, y, round) {
	if (x instanceof L.Point) {
		return x;
	}
	if (L.Util.isArray(x)) {
		return new L.Point(x[0], x[1]);
	}
	if (x === undefined || x === null) {
		return x;
	}
	if (typeof x === 'object' && 'x' in x && 'y' in x) {
		return new L.Point(x.x, x.y);
	}
	return new L.Point(x, y, round);
};



/*
 * @class Bounds
 * @aka L.Bounds
 *
 * Represents a rectangular area in pixel coordinates.
 *
 * @example
 *
 * ```js
 * var p1 = L.point(10, 10),
 * p2 = L.point(40, 60),
 * bounds = L.bounds(p1, p2);
 * ```
 *
 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
 *
 * ```js
 * otherBounds.intersects([[10, 10], [40, 60]]);
 * ```
 */

L.Bounds = function (a, b) {
	if (!a) { return; }

	var points = b ? [a, b] : a;

	for (var i = 0, len = points.length; i < len; i++) {
		this.extend(points[i]);
	}
};

L.Bounds.prototype = {
	// @method extend(point: Point): this
	// Extends the bounds to contain the given point.
	extend: function (point) { // (Point)
		point = L.point(point);

		// @property min: Point
		// The top left corner of the rectangle.
		// @property max: Point
		// The bottom right corner of the rectangle.
		if (!this.min && !this.max) {
			this.min = point.clone();
			this.max = point.clone();
		} else {
			this.min.x = Math.min(point.x, this.min.x);
			this.max.x = Math.max(point.x, this.max.x);
			this.min.y = Math.min(point.y, this.min.y);
			this.max.y = Math.max(point.y, this.max.y);
		}
		return this;
	},

	// @method getCenter(round?: Boolean): Point
	// Returns the center point of the bounds.
	getCenter: function (round) {
		return new L.Point(
		        (this.min.x + this.max.x) / 2,
		        (this.min.y + this.max.y) / 2, round);
	},

	// @method getBottomLeft(): Point
	// Returns the bottom-left point of the bounds.
	getBottomLeft: function () {
		return new L.Point(this.min.x, this.max.y);
	},

	// @method getTopRight(): Point
	// Returns the top-right point of the bounds.
	getTopRight: function () { // -> Point
		return new L.Point(this.max.x, this.min.y);
	},

	// @method getSize(): Point
	// Returns the size of the given bounds
	getSize: function () {
		return this.max.subtract(this.min);
	},

	// @method contains(otherBounds: Bounds): Boolean
	// Returns `true` if the rectangle contains the given one.
	// @alternative
	// @method contains(point: Point): Boolean
	// Returns `true` if the rectangle contains the given point.
	contains: function (obj) {
		var min, max;

		if (typeof obj[0] === 'number' || obj instanceof L.Point) {
			obj = L.point(obj);
		} else {
			obj = L.bounds(obj);
		}

		if (obj instanceof L.Bounds) {
			min = obj.min;
			max = obj.max;
		} else {
			min = max = obj;
		}

		return (min.x >= this.min.x) &&
		       (max.x <= this.max.x) &&
		       (min.y >= this.min.y) &&
		       (max.y <= this.max.y);
	},

	// @method intersects(otherBounds: Bounds): Boolean
	// Returns `true` if the rectangle intersects the given bounds. Two bounds
	// intersect if they have at least one point in common.
	intersects: function (bounds) { // (Bounds) -> Boolean
		bounds = L.bounds(bounds);

		var min = this.min,
		    max = this.max,
		    min2 = bounds.min,
		    max2 = bounds.max,
		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);

		return xIntersects && yIntersects;
	},

	// @method overlaps(otherBounds: Bounds): Boolean
	// Returns `true` if the rectangle overlaps the given bounds. Two bounds
	// overlap if their intersection is an area.
	overlaps: function (bounds) { // (Bounds) -> Boolean
		bounds = L.bounds(bounds);

		var min = this.min,
		    max = this.max,
		    min2 = bounds.min,
		    max2 = bounds.max,
		    xOverlaps = (max2.x > min.x) && (min2.x < max.x),
		    yOverlaps = (max2.y > min.y) && (min2.y < max.y);

		return xOverlaps && yOverlaps;
	},

	isValid: function () {
		return !!(this.min && this.max);
	}
};


// @factory L.bounds(topLeft: Point, bottomRight: Point)
// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
// @alternative
// @factory L.bounds(points: Point[])
// Creates a Bounds object from the points it contains
L.bounds = function (a, b) {
	if (!a || a instanceof L.Bounds) {
		return a;
	}
	return new L.Bounds(a, b);
};



/*
 * @class Transformation
 * @aka L.Transformation
 *
 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
 * the reverse. Used by Leaflet in its projections code.
 *
 * @example
 *
 * ```js
 * var transformation = new L.Transformation(2, 5, -1, 10),
 * 	p = L.point(1, 2),
 * 	p2 = transformation.transform(p), //  L.point(7, 8)
 * 	p3 = transformation.untransform(p2); //  L.point(1, 2)
 * ```
 */


// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
// Creates a `Transformation` object with the given coefficients.
L.Transformation = function (a, b, c, d) {
	this._a = a;
	this._b = b;
	this._c = c;
	this._d = d;
};

L.Transformation.prototype = {
	// @method transform(point: Point, scale?: Number): Point
	// Returns a transformed point, optionally multiplied by the given scale.
	// Only accepts actual `L.Point` instances, not arrays.
	transform: function (point, scale) { // (Point, Number) -> Point
		return this._transform(point.clone(), scale);
	},

	// destructive transform (faster)
	_transform: function (point, scale) {
		scale = scale || 1;
		point.x = scale * (this._a * point.x + this._b);
		point.y = scale * (this._c * point.y + this._d);
		return point;
	},

	// @method untransform(point: Point, scale?: Number): Point
	// Returns the reverse transformation of the given point, optionally divided
	// by the given scale. Only accepts actual `L.Point` instances, not arrays.
	untransform: function (point, scale) {
		scale = scale || 1;
		return new L.Point(
		        (point.x / scale - this._b) / this._a,
		        (point.y / scale - this._d) / this._c);
	}
};



/*
 * @namespace DomUtil
 *
 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
 * tree, used by Leaflet internally.
 *
 * Most functions expecting or returning a `HTMLElement` also work for
 * SVG elements. The only difference is that classes refer to CSS classes
 * in HTML and SVG classes in SVG.
 */

L.DomUtil = {

	// @function get(id: String|HTMLElement): HTMLElement
	// Returns an element given its DOM id, or returns the element itself
	// if it was passed directly.
	get: function (id) {
		return typeof id === 'string' ? document.getElementById(id) : id;
	},

	// @function getStyle(el: HTMLElement, styleAttrib: String): String
	// Returns the value for a certain style attribute on an element,
	// including computed values or values set through CSS.
	getStyle: function (el, style) {

		var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);

		if ((!value || value === 'auto') && document.defaultView) {
			var css = document.defaultView.getComputedStyle(el, null);
			value = css ? css[style] : null;
		}

		return value === 'auto' ? null : value;
	},

	// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
	// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
	create: function (tagName, className, container) {

		var el = document.createElement(tagName);
		el.className = className || '';

		if (container) {
			container.appendChild(el);
		}

		return el;
	},

	// @function remove(el: HTMLElement)
	// Removes `el` from its parent element
	remove: function (el) {
		var parent = el.parentNode;
		if (parent) {
			parent.removeChild(el);
		}
	},

	// @function empty(el: HTMLElement)
	// Removes all of `el`'s children elements from `el`
	empty: function (el) {
		while (el.firstChild) {
			el.removeChild(el.firstChild);
		}
	},

	// @function toFront(el: HTMLElement)
	// Makes `el` the last children of its parent, so it renders in front of the other children.
	toFront: function (el) {
		el.parentNode.appendChild(el);
	},

	// @function toBack(el: HTMLElement)
	// Makes `el` the first children of its parent, so it renders back from the other children.
	toBack: function (el) {
		var parent = el.parentNode;
		parent.insertBefore(el, parent.firstChild);
	},

	// @function hasClass(el: HTMLElement, name: String): Boolean
	// Returns `true` if the element's class attribute contains `name`.
	hasClass: function (el, name) {
		if (el.classList !== undefined) {
			return el.classList.contains(name);
		}
		var className = L.DomUtil.getClass(el);
		return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
	},

	// @function addClass(el: HTMLElement, name: String)
	// Adds `name` to the element's class attribute.
	addClass: function (el, name) {
		if (el.classList !== undefined) {
			var classes = L.Util.splitWords(name);
			for (var i = 0, len = classes.length; i < len; i++) {
				el.classList.add(classes[i]);
			}
		} else if (!L.DomUtil.hasClass(el, name)) {
			var className = L.DomUtil.getClass(el);
			L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
		}
	},

	// @function removeClass(el: HTMLElement, name: String)
	// Removes `name` from the element's class attribute.
	removeClass: function (el, name) {
		if (el.classList !== undefined) {
			el.classList.remove(name);
		} else {
			L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
		}
	},

	// @function setClass(el: HTMLElement, name: String)
	// Sets the element's class.
	setClass: function (el, name) {
		if (el.className.baseVal === undefined) {
			el.className = name;
		} else {
			// in case of SVG element
			el.className.baseVal = name;
		}
	},

	// @function getClass(el: HTMLElement): String
	// Returns the element's class.
	getClass: function (el) {
		return el.className.baseVal === undefined ? el.className : el.className.baseVal;
	},

	// @function setOpacity(el: HTMLElement, opacity: Number)
	// Set the opacity of an element (including old IE support).
	// `opacity` must be a number from `0` to `1`.
	setOpacity: function (el, value) {

		if ('opacity' in el.style) {
			el.style.opacity = value;

		} else if ('filter' in el.style) {
			L.DomUtil._setOpacityIE(el, value);
		}
	},

	_setOpacityIE: function (el, value) {
		var filter = false,
		    filterName = 'DXImageTransform.Microsoft.Alpha';

		// filters collection throws an error if we try to retrieve a filter that doesn't exist
		try {
			filter = el.filters.item(filterName);
		} catch (e) {
			// don't set opacity to 1 if we haven't already set an opacity,
			// it isn't needed and breaks transparent pngs.
			if (value === 1) { return; }
		}

		value = Math.round(value * 100);

		if (filter) {
			filter.Enabled = (value !== 100);
			filter.Opacity = value;
		} else {
			el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
		}
	},

	// @function testProp(props: String[]): String|false
	// Goes through the array of style names and returns the first name
	// that is a valid style name for an element. If no such name is found,
	// it returns false. Useful for vendor-prefixed styles like `transform`.
	testProp: function (props) {

		var style = document.documentElement.style;

		for (var i = 0; i < props.length; i++) {
			if (props[i] in style) {
				return props[i];
			}
		}
		return false;
	},

	// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
	// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
	// and optionally scaled by `scale`. Does not have an effect if the
	// browser doesn't support 3D CSS transforms.
	setTransform: function (el, offset, scale) {
		var pos = offset || new L.Point(0, 0);

		el.style[L.DomUtil.TRANSFORM] =
			(L.Browser.ie3d ?
				'translate(' + pos.x + 'px,' + pos.y + 'px)' :
				'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
			(scale ? ' scale(' + scale + ')' : '');
	},

	// @function setPosition(el: HTMLElement, position: Point)
	// Sets the position of `el` to coordinates specified by `position`,
	// using CSS translate or top/left positioning depending on the browser
	// (used by Leaflet internally to position its layers).
	setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])

		/*eslint-disable */
		el._leaflet_pos = point;
		/*eslint-enable */

		if (L.Browser.any3d) {
			L.DomUtil.setTransform(el, point);
		} else {
			el.style.left = point.x + 'px';
			el.style.top = point.y + 'px';
		}
	},

	// @function getPosition(el: HTMLElement): Point
	// Returns the coordinates of an element previously positioned with setPosition.
	getPosition: function (el) {
		// this method is only used for elements previously positioned using setPosition,
		// so it's safe to cache the position for performance

		return el._leaflet_pos || new L.Point(0, 0);
	}
};


(function () {
	// prefix style property names

	// @property TRANSFORM: String
	// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
	L.DomUtil.TRANSFORM = L.DomUtil.testProp(
			['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);


	// webkitTransition comes first because some browser versions that drop vendor prefix don't do
	// the same for the transitionend event, in particular the Android 4.1 stock browser

	// @property TRANSITION: String
	// Vendor-prefixed transform style name.
	var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
			['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);

	L.DomUtil.TRANSITION_END =
			transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';

	// @function disableTextSelection()
	// Prevents the user from generating `selectstart` DOM events, usually generated
	// when the user drags the mouse through a page with text. Used internally
	// by Leaflet to override the behaviour of any click-and-drag interaction on
	// the map. Affects drag interactions on the whole document.

	// @function enableTextSelection()
	// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
	if ('onselectstart' in document) {
		L.DomUtil.disableTextSelection = function () {
			L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
		};
		L.DomUtil.enableTextSelection = function () {
			L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
		};

	} else {
		var userSelectProperty = L.DomUtil.testProp(
			['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);

		L.DomUtil.disableTextSelection = function () {
			if (userSelectProperty) {
				var style = document.documentElement.style;
				this._userSelect = style[userSelectProperty];
				style[userSelectProperty] = 'none';
			}
		};
		L.DomUtil.enableTextSelection = function () {
			if (userSelectProperty) {
				document.documentElement.style[userSelectProperty] = this._userSelect;
				delete this._userSelect;
			}
		};
	}

	// @function disableImageDrag()
	// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
	// for `dragstart` DOM events, usually generated when the user drags an image.
	L.DomUtil.disableImageDrag = function () {
		L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
	};

	// @function enableImageDrag()
	// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
	L.DomUtil.enableImageDrag = function () {
		L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
	};

	// @function preventOutline(el: HTMLElement)
	// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
	// of the element `el` invisible. Used internally by Leaflet to prevent
	// focusable elements from displaying an outline when the user performs a
	// drag interaction on them.
	L.DomUtil.preventOutline = function (element) {
		while (element.tabIndex === -1) {
			element = element.parentNode;
		}
		if (!element || !element.style) { return; }
		L.DomUtil.restoreOutline();
		this._outlineElement = element;
		this._outlineStyle = element.style.outline;
		element.style.outline = 'none';
		L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
	};

	// @function restoreOutline()
	// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
	L.DomUtil.restoreOutline = function () {
		if (!this._outlineElement) { return; }
		this._outlineElement.style.outline = this._outlineStyle;
		delete this._outlineElement;
		delete this._outlineStyle;
		L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
	};
})();



/* @class LatLng
 * @aka L.LatLng
 *
 * Represents a geographical point with a certain latitude and longitude.
 *
 * @example
 *
 * ```
 * var latlng = L.latLng(50.5, 30.5);
 * ```
 *
 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
 *
 * ```
 * map.panTo([50, 30]);
 * map.panTo({lon: 30, lat: 50});
 * map.panTo({lat: 50, lng: 30});
 * map.panTo(L.latLng(50, 30));
 * ```
 */

L.LatLng = function (lat, lng, alt) {
	if (isNaN(lat) || isNaN(lng)) {
		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
	}

	// @property lat: Number
	// Latitude in degrees
	this.lat = +lat;

	// @property lng: Number
	// Longitude in degrees
	this.lng = +lng;

	// @property alt: Number
	// Altitude in meters (optional)
	if (alt !== undefined) {
		this.alt = +alt;
	}
};

L.LatLng.prototype = {
	// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
	// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
	equals: function (obj, maxMargin) {
		if (!obj) { return false; }

		obj = L.latLng(obj);

		var margin = Math.max(
		        Math.abs(this.lat - obj.lat),
		        Math.abs(this.lng - obj.lng));

		return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
	},

	// @method toString(): String
	// Returns a string representation of the point (for debugging purposes).
	toString: function (precision) {
		return 'LatLng(' +
		        L.Util.formatNum(this.lat, precision) + ', ' +
		        L.Util.formatNum(this.lng, precision) + ')';
	},

	// @method distanceTo(otherLatLng: LatLng): Number
	// Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
	distanceTo: function (other) {
		return L.CRS.Earth.distance(this, L.latLng(other));
	},

	// @method wrap(): LatLng
	// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
	wrap: function () {
		return L.CRS.Earth.wrapLatLng(this);
	},

	// @method toBounds(sizeInMeters: Number): LatLngBounds
	// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
	toBounds: function (sizeInMeters) {
		var latAccuracy = 180 * sizeInMeters / 40075017,
		    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);

		return L.latLngBounds(
		        [this.lat - latAccuracy, this.lng - lngAccuracy],
		        [this.lat + latAccuracy, this.lng + lngAccuracy]);
	},

	clone: function () {
		return new L.LatLng(this.lat, this.lng, this.alt);
	}
};



// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).

// @alternative
// @factory L.latLng(coords: Array): LatLng
// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.

// @alternative
// @factory L.latLng(coords: Object): LatLng
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.

L.latLng = function (a, b, c) {
	if (a instanceof L.LatLng) {
		return a;
	}
	if (L.Util.isArray(a) && typeof a[0] !== 'object') {
		if (a.length === 3) {
			return new L.LatLng(a[0], a[1], a[2]);
		}
		if (a.length === 2) {
			return new L.LatLng(a[0], a[1]);
		}
		return null;
	}
	if (a === undefined || a === null) {
		return a;
	}
	if (typeof a === 'object' && 'lat' in a) {
		return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
	}
	if (b === undefined) {
		return null;
	}
	return new L.LatLng(a, b, c);
};



/*
 * @class LatLngBounds
 * @aka L.LatLngBounds
 *
 * Represents a rectangular geographical area on a map.
 *
 * @example
 *
 * ```js
 * var corner1 = L.latLng(40.712, -74.227),
 * corner2 = L.latLng(40.774, -74.125),
 * bounds = L.latLngBounds(corner1, corner2);
 * ```
 *
 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
 *
 * ```js
 * map.fitBounds([
 * 	[40.712, -74.227],
 * 	[40.774, -74.125]
 * ]);
 * ```
 *
 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
 */

L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
	if (!corner1) { return; }

	var latlngs = corner2 ? [corner1, corner2] : corner1;

	for (var i = 0, len = latlngs.length; i < len; i++) {
		this.extend(latlngs[i]);
	}
};

L.LatLngBounds.prototype = {

	// @method extend(latlng: LatLng): this
	// Extend the bounds to contain the given point

	// @alternative
	// @method extend(otherBounds: LatLngBounds): this
	// Extend the bounds to contain the given bounds
	extend: function (obj) {
		var sw = this._southWest,
		    ne = this._northEast,
		    sw2, ne2;

		if (obj instanceof L.LatLng) {
			sw2 = obj;
			ne2 = obj;

		} else if (obj instanceof L.LatLngBounds) {
			sw2 = obj._southWest;
			ne2 = obj._northEast;

			if (!sw2 || !ne2) { return this; }

		} else {
			return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
		}

		if (!sw && !ne) {
			this._southWest = new L.LatLng(sw2.lat, sw2.lng);
			this._northEast = new L.LatLng(ne2.lat, ne2.lng);
		} else {
			sw.lat = Math.min(sw2.lat, sw.lat);
			sw.lng = Math.min(sw2.lng, sw.lng);
			ne.lat = Math.max(ne2.lat, ne.lat);
			ne.lng = Math.max(ne2.lng, ne.lng);
		}

		return this;
	},

	// @method pad(bufferRatio: Number): LatLngBounds
	// Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
	pad: function (bufferRatio) {
		var sw = this._southWest,
		    ne = this._northEast,
		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;

		return new L.LatLngBounds(
		        new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
		        new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
	},

	// @method getCenter(): LatLng
	// Returns the center point of the bounds.
	getCenter: function () {
		return new L.LatLng(
		        (this._southWest.lat + this._northEast.lat) / 2,
		        (this._southWest.lng + this._northEast.lng) / 2);
	},

	// @method getSouthWest(): LatLng
	// Returns the south-west point of the bounds.
	getSouthWest: function () {
		return this._southWest;
	},

	// @method getNorthEast(): LatLng
	// Returns the north-east point of the bounds.
	getNorthEast: function () {
		return this._northEast;
	},

	// @method getNorthWest(): LatLng
	// Returns the north-west point of the bounds.
	getNorthWest: function () {
		return new L.LatLng(this.getNorth(), this.getWest());
	},

	// @method getSouthEast(): LatLng
	// Returns the south-east point of the bounds.
	getSouthEast: function () {
		return new L.LatLng(this.getSouth(), this.getEast());
	},

	// @method getWest(): Number
	// Returns the west longitude of the bounds
	getWest: function () {
		return this._southWest.lng;
	},

	// @method getSouth(): Number
	// Returns the south latitude of the bounds
	getSouth: function () {
		return this._southWest.lat;
	},

	// @method getEast(): Number
	// Returns the east longitude of the bounds
	getEast: function () {
		return this._northEast.lng;
	},

	// @method getNorth(): Number
	// Returns the north latitude of the bounds
	getNorth: function () {
		return this._northEast.lat;
	},

	// @method contains(otherBounds: LatLngBounds): Boolean
	// Returns `true` if the rectangle contains the given one.

	// @alternative
	// @method contains (latlng: LatLng): Boolean
	// Returns `true` if the rectangle contains the given point.
	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
		if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
			obj = L.latLng(obj);
		} else {
			obj = L.latLngBounds(obj);
		}

		var sw = this._southWest,
		    ne = this._northEast,
		    sw2, ne2;

		if (obj instanceof L.LatLngBounds) {
			sw2 = obj.getSouthWest();
			ne2 = obj.getNorthEast();
		} else {
			sw2 = ne2 = obj;
		}

		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
	},

	// @method intersects(otherBounds: LatLngBounds): Boolean
	// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
	intersects: function (bounds) {
		bounds = L.latLngBounds(bounds);

		var sw = this._southWest,
		    ne = this._northEast,
		    sw2 = bounds.getSouthWest(),
		    ne2 = bounds.getNorthEast(),

		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);

		return latIntersects && lngIntersects;
	},

	// @method overlaps(otherBounds: Bounds): Boolean
	// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
	overlaps: function (bounds) {
		bounds = L.latLngBounds(bounds);

		var sw = this._southWest,
		    ne = this._northEast,
		    sw2 = bounds.getSouthWest(),
		    ne2 = bounds.getNorthEast(),

		    latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
		    lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);

		return latOverlaps && lngOverlaps;
	},

	// @method toBBoxString(): String
	// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
	toBBoxString: function () {
		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
	},

	// @method equals(otherBounds: LatLngBounds): Boolean
	// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
	equals: function (bounds) {
		if (!bounds) { return false; }

		bounds = L.latLngBounds(bounds);

		return this._southWest.equals(bounds.getSouthWest()) &&
		       this._northEast.equals(bounds.getNorthEast());
	},

	// @method isValid(): Boolean
	// Returns `true` if the bounds are properly initialized.
	isValid: function () {
		return !!(this._southWest && this._northEast);
	}
};

// TODO International date line?

// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.

// @alternative
// @factory L.latLngBounds(latlngs: LatLng[])
// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
L.latLngBounds = function (a, b) {
	if (a instanceof L.LatLngBounds) {
		return a;
	}
	return new L.LatLngBounds(a, b);
};



/*
 * @namespace Projection
 * @section
 * Leaflet comes with a set of already defined Projections out of the box:
 *
 * @projection L.Projection.LonLat
 *
 * Equirectangular, or Plate Carree projection — the most simple projection,
 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
 * `EPSG:3395` and `Simple` CRS.
 */

L.Projection = {};

L.Projection.LonLat = {
	project: function (latlng) {
		return new L.Point(latlng.lng, latlng.lat);
	},

	unproject: function (point) {
		return new L.LatLng(point.y, point.x);
	},

	bounds: L.bounds([-180, -90], [180, 90])
};



/*
 * @namespace Projection
 * @projection L.Projection.SphericalMercator
 *
 * Spherical Mercator projection — the most common projection for online maps,
 * used by almost all free and commercial tile providers. Assumes that Earth is
 * a sphere. Used by the `EPSG:3857` CRS.
 */

L.Projection.SphericalMercator = {

	R: 6378137,
	MAX_LATITUDE: 85.0511287798,

	project: function (latlng) {
		var d = Math.PI / 180,
		    max = this.MAX_LATITUDE,
		    lat = Math.max(Math.min(max, latlng.lat), -max),
		    sin = Math.sin(lat * d);

		return new L.Point(
				this.R * latlng.lng * d,
				this.R * Math.log((1 + sin) / (1 - sin)) / 2);
	},

	unproject: function (point) {
		var d = 180 / Math.PI;

		return new L.LatLng(
			(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
			point.x * d / this.R);
	},

	bounds: (function () {
		var d = 6378137 * Math.PI;
		return L.bounds([-d, -d], [d, d]);
	})()
};



/*
 * @class CRS
 * @aka L.CRS
 * Abstract class that defines coordinate reference systems for projecting
 * geographical points into pixel (screen) coordinates and back (and to
 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
 *
 * Leaflet defines the most usual CRSs by default. If you want to use a
 * CRS not defined by default, take a look at the
 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
 */

L.CRS = {
	// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
	// Projects geographical coordinates into pixel coordinates for a given zoom.
	latLngToPoint: function (latlng, zoom) {
		var projectedPoint = this.projection.project(latlng),
		    scale = this.scale(zoom);

		return this.transformation._transform(projectedPoint, scale);
	},

	// @method pointToLatLng(point: Point, zoom: Number): LatLng
	// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
	// zoom into geographical coordinates.
	pointToLatLng: function (point, zoom) {
		var scale = this.scale(zoom),
		    untransformedPoint = this.transformation.untransform(point, scale);

		return this.projection.unproject(untransformedPoint);
	},

	// @method project(latlng: LatLng): Point
	// Projects geographical coordinates into coordinates in units accepted for
	// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
	project: function (latlng) {
		return this.projection.project(latlng);
	},

	// @method unproject(point: Point): LatLng
	// Given a projected coordinate returns the corresponding LatLng.
	// The inverse of `project`.
	unproject: function (point) {
		return this.projection.unproject(point);
	},

	// @method scale(zoom: Number): Number
	// Returns the scale used when transforming projected coordinates into
	// pixel coordinates for a particular zoom. For example, it returns
	// `256 * 2^zoom` for Mercator-based CRS.
	scale: function (zoom) {
		return 256 * Math.pow(2, zoom);
	},

	// @method zoom(scale: Number): Number
	// Inverse of `scale()`, returns the zoom level corresponding to a scale
	// factor of `scale`.
	zoom: function (scale) {
		return Math.log(scale / 256) / Math.LN2;
	},

	// @method getProjectedBounds(zoom: Number): Bounds
	// Returns the projection's bounds scaled and transformed for the provided `zoom`.
	getProjectedBounds: function (zoom) {
		if (this.infinite) { return null; }

		var b = this.projection.bounds,
		    s = this.scale(zoom),
		    min = this.transformation.transform(b.min, s),
		    max = this.transformation.transform(b.max, s);

		return L.bounds(min, max);
	},

	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
	// Returns the distance between two geographical coordinates.

	// @property code: String
	// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
	//
	// @property wrapLng: Number[]
	// An array of two numbers defining whether the longitude (horizontal) coordinate
	// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
	// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
	//
	// @property wrapLat: Number[]
	// Like `wrapLng`, but for the latitude (vertical) axis.

	// wrapLng: [min, max],
	// wrapLat: [min, max],

	// @property infinite: Boolean
	// If true, the coordinate space will be unbounded (infinite in both axes)
	infinite: false,

	// @method wrapLatLng(latlng: LatLng): LatLng
	// Returns a `LatLng` where lat and lng has been wrapped according to the
	// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
	// Only accepts actual `L.LatLng` instances, not arrays.
	wrapLatLng: function (latlng) {
		var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
		    lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
		    alt = latlng.alt;

		return L.latLng(lat, lng, alt);
	},

	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
	// Returns a `LatLngBounds` with the same size as the given one, ensuring
	// that its center is within the CRS's bounds.
	// Only accepts actual `L.LatLngBounds` instances, not arrays.
	wrapLatLngBounds: function (bounds) {
		var center = bounds.getCenter(),
		    newCenter = this.wrapLatLng(center),
		    latShift = center.lat - newCenter.lat,
		    lngShift = center.lng - newCenter.lng;

		if (latShift === 0 && lngShift === 0) {
			return bounds;
		}

		var sw = bounds.getSouthWest(),
		    ne = bounds.getNorthEast(),
		    newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
		    newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});

		return new L.LatLngBounds(newSw, newNe);
	}
};



/*
 * @namespace CRS
 * @crs L.CRS.Simple
 *
 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
 * axis should still be inverted (going from bottom to top). `distance()` returns
 * simple euclidean distance.
 */

L.CRS.Simple = L.extend({}, L.CRS, {
	projection: L.Projection.LonLat,
	transformation: new L.Transformation(1, 0, -1, 0),

	scale: function (zoom) {
		return Math.pow(2, zoom);
	},

	zoom: function (scale) {
		return Math.log(scale) / Math.LN2;
	},

	distance: function (latlng1, latlng2) {
		var dx = latlng2.lng - latlng1.lng,
		    dy = latlng2.lat - latlng1.lat;

		return Math.sqrt(dx * dx + dy * dy);
	},

	infinite: true
});



/*
 * @namespace CRS
 * @crs L.CRS.Earth
 *
 * Serves as the base for CRS that are global such that they cover the earth.
 * Can only be used as the base for other CRS and cannot be used directly,
 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
 * meters.
 */

L.CRS.Earth = L.extend({}, L.CRS, {
	wrapLng: [-180, 180],

	// Mean Earth Radius, as recommended for use by
	// the International Union of Geodesy and Geophysics,
	// see http://rosettacode.org/wiki/Haversine_formula
	R: 6371000,

	// distance between two geographical points using spherical law of cosines approximation
	distance: function (latlng1, latlng2) {
		var rad = Math.PI / 180,
		    lat1 = latlng1.lat * rad,
		    lat2 = latlng2.lat * rad,
		    a = Math.sin(lat1) * Math.sin(lat2) +
		        Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);

		return this.R * Math.acos(Math.min(a, 1));
	}
});



/*
 * @namespace CRS
 * @crs L.CRS.EPSG3857
 *
 * The most common CRS for online maps, used by almost all free and commercial
 * tile providers. Uses Spherical Mercator projection. Set in by default in
 * Map's `crs` option.
 */

L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
	code: 'EPSG:3857',
	projection: L.Projection.SphericalMercator,

	transformation: (function () {
		var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
		return new L.Transformation(scale, 0.5, -scale, 0.5);
	}())
});

L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
	code: 'EPSG:900913'
});



/*
 * @namespace CRS
 * @crs L.CRS.EPSG4326
 *
 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
 *
 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
 * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
 */

L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
	code: 'EPSG:4326',
	projection: L.Projection.LonLat,
	transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
});



/*
 * @class Map
 * @aka L.Map
 * @inherits Evented
 *
 * The central class of the API — it is used to create a map on a page and manipulate it.
 *
 * @example
 *
 * ```js
 * // initialize the map on the "map" div with a given center and zoom
 * var map = L.map('map', {
 * 	center: [51.505, -0.09],
 * 	zoom: 13
 * });
 * ```
 *
 */

L.Map = L.Evented.extend({

	options: {
		// @section Map State Options
		// @option crs: CRS = L.CRS.EPSG3857
		// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
		// sure what it means.
		crs: L.CRS.EPSG3857,

		// @option center: LatLng = undefined
		// Initial geographic center of the map
		center: undefined,

		// @option zoom: Number = undefined
		// Initial map zoom level
		zoom: undefined,

		// @option minZoom: Number = undefined
		// Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
		minZoom: undefined,

		// @option maxZoom: Number = undefined
		// Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
		maxZoom: undefined,

		// @option layers: Layer[] = []
		// Array of layers that will be added to the map initially
		layers: [],

		// @option maxBounds: LatLngBounds = null
		// When this option is set, the map restricts the view to the given
		// geographical bounds, bouncing the user back if the user tries to pan
		// outside the view. To set the restriction dynamically, use
		// [`setMaxBounds`](#map-setmaxbounds) method.
		maxBounds: undefined,

		// @option renderer: Renderer = *
		// The default method for drawing vector layers on the map. `L.SVG`
		// or `L.Canvas` by default depending on browser support.
		renderer: undefined,


		// @section Animation Options
		// @option zoomAnimation: Boolean = true
		// Whether the map zoom animation is enabled. By default it's enabled
		// in all browsers that support CSS3 Transitions except Android.
		zoomAnimation: true,

		// @option zoomAnimationThreshold: Number = 4
		// Won't animate zoom if the zoom difference exceeds this value.
		zoomAnimationThreshold: 4,

		// @option fadeAnimation: Boolean = true
		// Whether the tile fade animation is enabled. By default it's enabled
		// in all browsers that support CSS3 Transitions except Android.
		fadeAnimation: true,

		// @option markerZoomAnimation: Boolean = true
		// Whether markers animate their zoom with the zoom animation, if disabled
		// they will disappear for the length of the animation. By default it's
		// enabled in all browsers that support CSS3 Transitions except Android.
		markerZoomAnimation: true,

		// @option transform3DLimit: Number = 2^23
		// Defines the maximum size of a CSS translation transform. The default
		// value should not be changed unless a web browser positions layers in
		// the wrong place after doing a large `panBy`.
		transform3DLimit: 8388608, // Precision limit of a 32-bit float

		// @section Interaction Options
		// @option zoomSnap: Number = 1
		// Forces the map's zoom level to always be a multiple of this, particularly
		// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
		// By default, the zoom level snaps to the nearest integer; lower values
		// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
		// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
		zoomSnap: 1,

		// @option zoomDelta: Number = 1
		// Controls how much the map's zoom level will change after a
		// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
		// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
		// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
		zoomDelta: 1,

		// @option trackResize: Boolean = true
		// Whether the map automatically handles browser window resize to update itself.
		trackResize: true
	},

	initialize: function (id, options) { // (HTMLElement or String, Object)
		options = L.setOptions(this, options);

		this._initContainer(id);
		this._initLayout();

		// hack for https://github.com/Leaflet/Leaflet/issues/1980
		this._onResize = L.bind(this._onResize, this);

		this._initEvents();

		if (options.maxBounds) {
			this.setMaxBounds(options.maxBounds);
		}

		if (options.zoom !== undefined) {
			this._zoom = this._limitZoom(options.zoom);
		}

		if (options.center && options.zoom !== undefined) {
			this.setView(L.latLng(options.center), options.zoom, {reset: true});
		}

		this._handlers = [];
		this._layers = {};
		this._zoomBoundLayers = {};
		this._sizeChanged = true;

		this.callInitHooks();

		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
		this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
				this.options.zoomAnimation;

		// zoom transitions run with the same duration for all layers, so if one of transitionend events
		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
		if (this._zoomAnimated) {
			this._createAnimProxy();
			L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
		}

		this._addLayers(this.options.layers);
	},


	// @section Methods for modifying map state

	// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
	// Sets the view of the map (geographical center and zoom) with the given
	// animation options.
	setView: function (center, zoom, options) {

		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
		center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
		options = options || {};

		this._stop();

		if (this._loaded && !options.reset && options !== true) {

			if (options.animate !== undefined) {
				options.zoom = L.extend({animate: options.animate}, options.zoom);
				options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
			}

			// try animating pan or zoom
			var moved = (this._zoom !== zoom) ?
				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
				this._tryAnimatedPan(center, options.pan);

			if (moved) {
				// prevent resize handler call, the view will refresh after animation anyway
				clearTimeout(this._sizeTimer);
				return this;
			}
		}

		// animation didn't start, just reset the map view
		this._resetView(center, zoom);

		return this;
	},

	// @method setZoom(zoom: Number, options: Zoom/pan options): this
	// Sets the zoom of the map.
	setZoom: function (zoom, options) {
		if (!this._loaded) {
			this._zoom = zoom;
			return this;
		}
		return this.setView(this.getCenter(), zoom, {zoom: options});
	},

	// @method zoomIn(delta?: Number, options?: Zoom options): this
	// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
	zoomIn: function (delta, options) {
		delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
		return this.setZoom(this._zoom + delta, options);
	},

	// @method zoomOut(delta?: Number, options?: Zoom options): this
	// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
	zoomOut: function (delta, options) {
		delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
		return this.setZoom(this._zoom - delta, options);
	},

	// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
	// Zooms the map while keeping a specified geographical point on the map
	// stationary (e.g. used internally for scroll zoom and double-click zoom).
	// @alternative
	// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
	// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
	setZoomAround: function (latlng, zoom, options) {
		var scale = this.getZoomScale(zoom),
		    viewHalf = this.getSize().divideBy(2),
		    containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),

		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));

		return this.setView(newCenter, zoom, {zoom: options});
	},

	_getBoundsCenterZoom: function (bounds, options) {

		options = options || {};
		bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);

		var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
		    paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),

		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));

		zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;

		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),

		    swPoint = this.project(bounds.getSouthWest(), zoom),
		    nePoint = this.project(bounds.getNorthEast(), zoom),
		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);

		return {
			center: center,
			zoom: zoom
		};
	},

	// @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
	// Sets a map view that contains the given geographical bounds with the
	// maximum zoom level possible.
	fitBounds: function (bounds, options) {

		bounds = L.latLngBounds(bounds);

		if (!bounds.isValid()) {
			throw new Error('Bounds are not valid.');
		}

		var target = this._getBoundsCenterZoom(bounds, options);
		return this.setView(target.center, target.zoom, options);
	},

	// @method fitWorld(options?: fitBounds options): this
	// Sets a map view that mostly contains the whole world with the maximum
	// zoom level possible.
	fitWorld: function (options) {
		return this.fitBounds([[-90, -180], [90, 180]], options);
	},

	// @method panTo(latlng: LatLng, options?: Pan options): this
	// Pans the map to a given center.
	panTo: function (center, options) { // (LatLng)
		return this.setView(center, this._zoom, {pan: options});
	},

	// @method panBy(offset: Point): this
	// Pans the map by a given number of pixels (animated).
	panBy: function (offset, options) {
		offset = L.point(offset).round();
		options = options || {};

		if (!offset.x && !offset.y) {
			return this.fire('moveend');
		}
		// If we pan too far, Chrome gets issues with tiles
		// and makes them disappear or appear in the wrong place (slightly offset) #2602
		if (options.animate !== true && !this.getSize().contains(offset)) {
			this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
			return this;
		}

		if (!this._panAnim) {
			this._panAnim = new L.PosAnimation();

			this._panAnim.on({
				'step': this._onPanTransitionStep,
				'end': this._onPanTransitionEnd
			}, this);
		}

		// don't fire movestart if animating inertia
		if (!options.noMoveStart) {
			this.fire('movestart');
		}

		// animate pan unless animate: false specified
		if (options.animate !== false) {
			L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');

			var newPos = this._getMapPanePos().subtract(offset).round();
			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
		} else {
			this._rawPanBy(offset);
			this.fire('move').fire('moveend');
		}

		return this;
	},

	// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
	// Sets the view of the map (geographical center and zoom) performing a smooth
	// pan-zoom animation.
	flyTo: function (targetCenter, targetZoom, options) {

		options = options || {};
		if (options.animate === false || !L.Browser.any3d) {
			return this.setView(targetCenter, targetZoom, options);
		}

		this._stop();

		var from = this.project(this.getCenter()),
		    to = this.project(targetCenter),
		    size = this.getSize(),
		    startZoom = this._zoom;

		targetCenter = L.latLng(targetCenter);
		targetZoom = targetZoom === undefined ? startZoom : targetZoom;

		var w0 = Math.max(size.x, size.y),
		    w1 = w0 * this.getZoomScale(startZoom, targetZoom),
		    u1 = (to.distanceTo(from)) || 1,
		    rho = 1.42,
		    rho2 = rho * rho;

		function r(i) {
			var s1 = i ? -1 : 1,
			    s2 = i ? w1 : w0,
			    t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
			    b1 = 2 * s2 * rho2 * u1,
			    b = t1 / b1,
			    sq = Math.sqrt(b * b + 1) - b;

			    // workaround for floating point precision bug when sq = 0, log = -Infinite,
			    // thus triggering an infinite loop in flyTo
			    var log = sq < 0.000000001 ? -18 : Math.log(sq);

			return log;
		}

		function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
		function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
		function tanh(n) { return sinh(n) / cosh(n); }

		var r0 = r(0);

		function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
		function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }

		function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }

		var start = Date.now(),
		    S = (r(1) - r0) / rho,
		    duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;

		function frame() {
			var t = (Date.now() - start) / duration,
			    s = easeOut(t) * S;

			if (t <= 1) {
				this._flyToFrame = L.Util.requestAnimFrame(frame, this);

				this._move(
					this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
					this.getScaleZoom(w0 / w(s), startZoom),
					{flyTo: true});

			} else {
				this
					._move(targetCenter, targetZoom)
					._moveEnd(true);
			}
		}

		this._moveStart(true);

		frame.call(this);
		return this;
	},

	// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
	// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
	// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
	flyToBounds: function (bounds, options) {
		var target = this._getBoundsCenterZoom(bounds, options);
		return this.flyTo(target.center, target.zoom, options);
	},

	// @method setMaxBounds(bounds: Bounds): this
	// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
	setMaxBounds: function (bounds) {
		bounds = L.latLngBounds(bounds);

		if (!bounds.isValid()) {
			this.options.maxBounds = null;
			return this.off('moveend', this._panInsideMaxBounds);
		} else if (this.options.maxBounds) {
			this.off('moveend', this._panInsideMaxBounds);
		}

		this.options.maxBounds = bounds;

		if (this._loaded) {
			this._panInsideMaxBounds();
		}

		return this.on('moveend', this._panInsideMaxBounds);
	},

	// @method setMinZoom(zoom: Number): this
	// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
	setMinZoom: function (zoom) {
		this.options.minZoom = zoom;

		if (this._loaded && this.getZoom() < this.options.minZoom) {
			return this.setZoom(zoom);
		}

		return this;
	},

	// @method setMaxZoom(zoom: Number): this
	// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
	setMaxZoom: function (zoom) {
		this.options.maxZoom = zoom;

		if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
			return this.setZoom(zoom);
		}

		return this;
	},

	// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
	// Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
	panInsideBounds: function (bounds, options) {
		this._enforcingBounds = true;
		var center = this.getCenter(),
		    newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));

		if (!center.equals(newCenter)) {
			this.panTo(newCenter, options);
		}

		this._enforcingBounds = false;
		return this;
	},

	// @method invalidateSize(options: Zoom/Pan options): this
	// Checks if the map container size changed and updates the map if so —
	// call it after you've changed the map size dynamically, also animating
	// pan by default. If `options.pan` is `false`, panning will not occur.
	// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
	// that it doesn't happen often even if the method is called many
	// times in a row.

	// @alternative
	// @method invalidateSize(animate: Boolean): this
	// Checks if the map container size changed and updates the map if so —
	// call it after you've changed the map size dynamically, also animating
	// pan by default.
	invalidateSize: function (options) {
		if (!this._loaded) { return this; }

		options = L.extend({
			animate: false,
			pan: true
		}, options === true ? {animate: true} : options);

		var oldSize = this.getSize();
		this._sizeChanged = true;
		this._lastCenter = null;

		var newSize = this.getSize(),
		    oldCenter = oldSize.divideBy(2).round(),
		    newCenter = newSize.divideBy(2).round(),
		    offset = oldCenter.subtract(newCenter);

		if (!offset.x && !offset.y) { return this; }

		if (options.animate && options.pan) {
			this.panBy(offset);

		} else {
			if (options.pan) {
				this._rawPanBy(offset);
			}

			this.fire('move');

			if (options.debounceMoveend) {
				clearTimeout(this._sizeTimer);
				this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
			} else {
				this.fire('moveend');
			}
		}

		// @section Map state change events
		// @event resize: ResizeEvent
		// Fired when the map is resized.
		return this.fire('resize', {
			oldSize: oldSize,
			newSize: newSize
		});
	},

	// @section Methods for modifying map state
	// @method stop(): this
	// Stops the currently running `panTo` or `flyTo` animation, if any.
	stop: function () {
		this.setZoom(this._limitZoom(this._zoom));
		if (!this.options.zoomSnap) {
			this.fire('viewreset');
		}
		return this._stop();
	},

	// @section Geolocation methods
	// @method locate(options?: Locate options): this
	// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
	// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
	// and optionally sets the map view to the user's location with respect to
	// detection accuracy (or to the world view if geolocation failed).
	// Note that, if your page doesn't use HTTPS, this method will fail in
	// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
	// See `Locate options` for more details.
	locate: function (options) {

		options = this._locateOptions = L.extend({
			timeout: 10000,
			watch: false
			// setView: false
			// maxZoom: <Number>
			// maximumAge: 0
			// enableHighAccuracy: false
		}, options);

		if (!('geolocation' in navigator)) {
			this._handleGeolocationError({
				code: 0,
				message: 'Geolocation not supported.'
			});
			return this;
		}

		var onResponse = L.bind(this._handleGeolocationResponse, this),
		    onError = L.bind(this._handleGeolocationError, this);

		if (options.watch) {
			this._locationWatchId =
			        navigator.geolocation.watchPosition(onResponse, onError, options);
		} else {
			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
		}
		return this;
	},

	// @method stopLocate(): this
	// Stops watching location previously initiated by `map.locate({watch: true})`
	// and aborts resetting the map view if map.locate was called with
	// `{setView: true}`.
	stopLocate: function () {
		if (navigator.geolocation && navigator.geolocation.clearWatch) {
			navigator.geolocation.clearWatch(this._locationWatchId);
		}
		if (this._locateOptions) {
			this._locateOptions.setView = false;
		}
		return this;
	},

	_handleGeolocationError: function (error) {
		var c = error.code,
		    message = error.message ||
		            (c === 1 ? 'permission denied' :
		            (c === 2 ? 'position unavailable' : 'timeout'));

		if (this._locateOptions.setView && !this._loaded) {
			this.fitWorld();
		}

		// @section Location events
		// @event locationerror: ErrorEvent
		// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
		this.fire('locationerror', {
			code: c,
			message: 'Geolocation error: ' + message + '.'
		});
	},

	_handleGeolocationResponse: function (pos) {
		var lat = pos.coords.latitude,
		    lng = pos.coords.longitude,
		    latlng = new L.LatLng(lat, lng),
		    bounds = latlng.toBounds(pos.coords.accuracy),
		    options = this._locateOptions;

		if (options.setView) {
			var zoom = this.getBoundsZoom(bounds);
			this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
		}

		var data = {
			latlng: latlng,
			bounds: bounds,
			timestamp: pos.timestamp
		};

		for (var i in pos.coords) {
			if (typeof pos.coords[i] === 'number') {
				data[i] = pos.coords[i];
			}
		}

		// @event locationfound: LocationEvent
		// Fired when geolocation (using the [`locate`](#map-locate) method)
		// went successfully.
		this.fire('locationfound', data);
	},

	// TODO handler.addTo
	// TODO Appropiate docs section?
	// @section Other Methods
	// @method addHandler(name: String, HandlerClass: Function): this
	// Adds a new `Handler` to the map, given its name and constructor function.
	addHandler: function (name, HandlerClass) {
		if (!HandlerClass) { return this; }

		var handler = this[name] = new HandlerClass(this);

		this._handlers.push(handler);

		if (this.options[name]) {
			handler.enable();
		}

		return this;
	},

	// @method remove(): this
	// Destroys the map and clears all related event listeners.
	remove: function () {

		this._initEvents(true);

		if (this._containerId !== this._container._leaflet_id) {
			throw new Error('Map container is being reused by another instance');
		}

		try {
			// throws error in IE6-8
			delete this._container._leaflet_id;
			delete this._containerId;
		} catch (e) {
			/*eslint-disable */
			this._container._leaflet_id = undefined;
			/*eslint-enable */
			this._containerId = undefined;
		}

		L.DomUtil.remove(this._mapPane);

		if (this._clearControlPos) {
			this._clearControlPos();
		}

		this._clearHandlers();

		if (this._loaded) {
			// @section Map state change events
			// @event unload: Event
			// Fired when the map is destroyed with [remove](#map-remove) method.
			this.fire('unload');
		}

		for (var i in this._layers) {
			this._layers[i].remove();
		}

		return this;
	},

	// @section Other Methods
	// @method createPane(name: String, container?: HTMLElement): HTMLElement
	// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
	// then returns it. The pane is created as a children of `container`, or
	// as a children of the main map pane if not set.
	createPane: function (name, container) {
		var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
		    pane = L.DomUtil.create('div', className, container || this._mapPane);

		if (name) {
			this._panes[name] = pane;
		}
		return pane;
	},

	// @section Methods for Getting Map State

	// @method getCenter(): LatLng
	// Returns the geographical center of the map view
	getCenter: function () {
		this._checkIfLoaded();

		if (this._lastCenter && !this._moved()) {
			return this._lastCenter;
		}
		return this.layerPointToLatLng(this._getCenterLayerPoint());
	},

	// @method getZoom(): Number
	// Returns the current zoom level of the map view
	getZoom: function () {
		return this._zoom;
	},

	// @method getBounds(): LatLngBounds
	// Returns the geographical bounds visible in the current map view
	getBounds: function () {
		var bounds = this.getPixelBounds(),
		    sw = this.unproject(bounds.getBottomLeft()),
		    ne = this.unproject(bounds.getTopRight());

		return new L.LatLngBounds(sw, ne);
	},

	// @method getMinZoom(): Number
	// Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
	getMinZoom: function () {
		return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
	},

	// @method getMaxZoom(): Number
	// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
	getMaxZoom: function () {
		return this.options.maxZoom === undefined ?
			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
			this.options.maxZoom;
	},

	// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
	// Returns the maximum zoom level on which the given bounds fit to the map
	// view in its entirety. If `inside` (optional) is set to `true`, the method
	// instead returns the minimum zoom level on which the map view fits into
	// the given bounds in its entirety.
	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
		bounds = L.latLngBounds(bounds);
		padding = L.point(padding || [0, 0]);

		var zoom = this.getZoom() || 0,
		    min = this.getMinZoom(),
		    max = this.getMaxZoom(),
		    nw = bounds.getNorthWest(),
		    se = bounds.getSouthEast(),
		    size = this.getSize().subtract(padding),
		    boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
		    snap = L.Browser.any3d ? this.options.zoomSnap : 1;

		var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
		zoom = this.getScaleZoom(scale, zoom);

		if (snap) {
			zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
			zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
		}

		return Math.max(min, Math.min(max, zoom));
	},

	// @method getSize(): Point
	// Returns the current size of the map container (in pixels).
	getSize: function () {
		if (!this._size || this._sizeChanged) {
			this._size = new L.Point(
				this._container.clientWidth || 0,
				this._container.clientHeight || 0);

			this._sizeChanged = false;
		}
		return this._size.clone();
	},

	// @method getPixelBounds(): Bounds
	// Returns the bounds of the current map view in projected pixel
	// coordinates (sometimes useful in layer and overlay implementations).
	getPixelBounds: function (center, zoom) {
		var topLeftPoint = this._getTopLeftPoint(center, zoom);
		return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
	},

	// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
	// the map pane? "left point of the map layer" can be confusing, specially
	// since there can be negative offsets.
	// @method getPixelOrigin(): Point
	// Returns the projected pixel coordinates of the top left point of
	// the map layer (useful in custom layer and overlay implementations).
	getPixelOrigin: function () {
		this._checkIfLoaded();
		return this._pixelOrigin;
	},

	// @method getPixelWorldBounds(zoom?: Number): Bounds
	// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
	// If `zoom` is omitted, the map's current zoom level is used.
	getPixelWorldBounds: function (zoom) {
		return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
	},

	// @section Other Methods

	// @method getPane(pane: String|HTMLElement): HTMLElement
	// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
	getPane: function (pane) {
		return typeof pane === 'string' ? this._panes[pane] : pane;
	},

	// @method getPanes(): Object
	// Returns a plain object containing the names of all [panes](#map-pane) as keys and
	// the panes as values.
	getPanes: function () {
		return this._panes;
	},

	// @method getContainer: HTMLElement
	// Returns the HTML element that contains the map.
	getContainer: function () {
		return this._container;
	},


	// @section Conversion Methods

	// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
	// Returns the scale factor to be applied to a map transition from zoom level
	// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
	getZoomScale: function (toZoom, fromZoom) {
		// TODO replace with universal implementation after refactoring projections
		var crs = this.options.crs;
		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
		return crs.scale(toZoom) / crs.scale(fromZoom);
	},

	// @method getScaleZoom(scale: Number, fromZoom: Number): Number
	// Returns the zoom level that the map would end up at, if it is at `fromZoom`
	// level and everything is scaled by a factor of `scale`. Inverse of
	// [`getZoomScale`](#map-getZoomScale).
	getScaleZoom: function (scale, fromZoom) {
		var crs = this.options.crs;
		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
		var zoom = crs.zoom(scale * crs.scale(fromZoom));
		return isNaN(zoom) ? Infinity : zoom;
	},

	// @method project(latlng: LatLng, zoom: Number): Point
	// Projects a geographical coordinate `LatLng` according to the projection
	// of the map's CRS, then scales it according to `zoom` and the CRS's
	// `Transformation`. The result is pixel coordinate relative to
	// the CRS origin.
	project: function (latlng, zoom) {
		zoom = zoom === undefined ? this._zoom : zoom;
		return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
	},

	// @method unproject(point: Point, zoom: Number): LatLng
	// Inverse of [`project`](#map-project).
	unproject: function (point, zoom) {
		zoom = zoom === undefined ? this._zoom : zoom;
		return this.options.crs.pointToLatLng(L.point(point), zoom);
	},

	// @method layerPointToLatLng(point: Point): LatLng
	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
	// returns the corresponding geographical coordinate (for the current zoom level).
	layerPointToLatLng: function (point) {
		var projectedPoint = L.point(point).add(this.getPixelOrigin());
		return this.unproject(projectedPoint);
	},

	// @method latLngToLayerPoint(latlng: LatLng): Point
	// Given a geographical coordinate, returns the corresponding pixel coordinate
	// relative to the [origin pixel](#map-getpixelorigin).
	latLngToLayerPoint: function (latlng) {
		var projectedPoint = this.project(L.latLng(latlng))._round();
		return projectedPoint._subtract(this.getPixelOrigin());
	},

	// @method wrapLatLng(latlng: LatLng): LatLng
	// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
	// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
	// CRS's bounds.
	// By default this means longitude is wrapped around the dateline so its
	// value is between -180 and +180 degrees.
	wrapLatLng: function (latlng) {
		return this.options.crs.wrapLatLng(L.latLng(latlng));
	},

	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
	// Returns a `LatLngBounds` with the same size as the given one, ensuring that
	// its center is within the CRS's bounds.
	// By default this means the center longitude is wrapped around the dateline so its
	// value is between -180 and +180 degrees, and the majority of the bounds
	// overlaps the CRS's bounds.
	wrapLatLngBounds: function (latlng) {
		return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
	},

	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
	// Returns the distance between two geographical coordinates according to
	// the map's CRS. By default this measures distance in meters.
	distance: function (latlng1, latlng2) {
		return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
	},

	// @method containerPointToLayerPoint(point: Point): Point
	// Given a pixel coordinate relative to the map container, returns the corresponding
	// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
	containerPointToLayerPoint: function (point) { // (Point)
		return L.point(point).subtract(this._getMapPanePos());
	},

	// @method layerPointToContainerPoint(point: Point): Point
	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
	// returns the corresponding pixel coordinate relative to the map container.
	layerPointToContainerPoint: function (point) { // (Point)
		return L.point(point).add(this._getMapPanePos());
	},

	// @method containerPointToLatLng(point: Point): LatLng
	// Given a pixel coordinate relative to the map container, returns
	// the corresponding geographical coordinate (for the current zoom level).
	containerPointToLatLng: function (point) {
		var layerPoint = this.containerPointToLayerPoint(L.point(point));
		return this.layerPointToLatLng(layerPoint);
	},

	// @method latLngToContainerPoint(latlng: LatLng): Point
	// Given a geographical coordinate, returns the corresponding pixel coordinate
	// relative to the map container.
	latLngToContainerPoint: function (latlng) {
		return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
	},

	// @method mouseEventToContainerPoint(ev: MouseEvent): Point
	// Given a MouseEvent object, returns the pixel coordinate relative to the
	// map container where the event took place.
	mouseEventToContainerPoint: function (e) {
		return L.DomEvent.getMousePosition(e, this._container);
	},

	// @method mouseEventToLayerPoint(ev: MouseEvent): Point
	// Given a MouseEvent object, returns the pixel coordinate relative to
	// the [origin pixel](#map-getpixelorigin) where the event took place.
	mouseEventToLayerPoint: function (e) {
		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
	},

	// @method mouseEventToLatLng(ev: MouseEvent): LatLng
	// Given a MouseEvent object, returns geographical coordinate where the
	// event took place.
	mouseEventToLatLng: function (e) { // (MouseEvent)
		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
	},


	// map initialization methods

	_initContainer: function (id) {
		var container = this._container = L.DomUtil.get(id);

		if (!container) {
			throw new Error('Map container not found.');
		} else if (container._leaflet_id) {
			throw new Error('Map container is already initialized.');
		}

		L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
		this._containerId = L.Util.stamp(container);
	},

	_initLayout: function () {
		var container = this._container;

		this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;

		L.DomUtil.addClass(container, 'leaflet-container' +
			(L.Browser.touch ? ' leaflet-touch' : '') +
			(L.Browser.retina ? ' leaflet-retina' : '') +
			(L.Browser.ielt9 ? ' leaflet-oldie' : '') +
			(L.Browser.safari ? ' leaflet-safari' : '') +
			(this._fadeAnimated ? ' leaflet-fade-anim' : ''));

		var position = L.DomUtil.getStyle(container, 'position');

		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
			container.style.position = 'relative';
		}

		this._initPanes();

		if (this._initControlPos) {
			this._initControlPos();
		}
	},

	_initPanes: function () {
		var panes = this._panes = {};
		this._paneRenderers = {};

		// @section
		//
		// Panes are DOM elements used to control the ordering of layers on the map. You
		// can access panes with [`map.getPane`](#map-getpane) or
		// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
		// [`map.createPane`](#map-createpane) method.
		//
		// Every map has the following default panes that differ only in zIndex.
		//
		// @pane mapPane: HTMLElement = 'auto'
		// Pane that contains all other map panes

		this._mapPane = this.createPane('mapPane', this._container);
		L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));

		// @pane tilePane: HTMLElement = 200
		// Pane for `GridLayer`s and `TileLayer`s
		this.createPane('tilePane');
		// @pane overlayPane: HTMLElement = 400
		// Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
		this.createPane('shadowPane');
		// @pane shadowPane: HTMLElement = 500
		// Pane for overlay shadows (e.g. `Marker` shadows)
		this.createPane('overlayPane');
		// @pane markerPane: HTMLElement = 600
		// Pane for `Icon`s of `Marker`s
		this.createPane('markerPane');
		// @pane tooltipPane: HTMLElement = 650
		// Pane for tooltip.
		this.createPane('tooltipPane');
		// @pane popupPane: HTMLElement = 700
		// Pane for `Popup`s.
		this.createPane('popupPane');

		if (!this.options.markerZoomAnimation) {
			L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
			L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
		}
	},


	// private methods that modify map state

	// @section Map state change events
	_resetView: function (center, zoom) {
		L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));

		var loading = !this._loaded;
		this._loaded = true;
		zoom = this._limitZoom(zoom);

		this.fire('viewprereset');

		var zoomChanged = this._zoom !== zoom;
		this
			._moveStart(zoomChanged)
			._move(center, zoom)
			._moveEnd(zoomChanged);

		// @event viewreset: Event
		// Fired when the map needs to redraw its content (this usually happens
		// on map zoom or load). Very useful for creating custom overlays.
		this.fire('viewreset');

		// @event load: Event
		// Fired when the map is initialized (when its center and zoom are set
		// for the first time).
		if (loading) {
			this.fire('load');
		}
	},

	_moveStart: function (zoomChanged) {
		// @event zoomstart: Event
		// Fired when the map zoom is about to change (e.g. before zoom animation).
		// @event movestart: Event
		// Fired when the view of the map starts changing (e.g. user starts dragging the map).
		if (zoomChanged) {
			this.fire('zoomstart');
		}
		return this.fire('movestart');
	},

	_move: function (center, zoom, data) {
		if (zoom === undefined) {
			zoom = this._zoom;
		}
		var zoomChanged = this._zoom !== zoom;

		this._zoom = zoom;
		this._lastCenter = center;
		this._pixelOrigin = this._getNewPixelOrigin(center);

		// @event zoom: Event
		// Fired repeatedly during any change in zoom level, including zoom
		// and fly animations.
		if (zoomChanged || (data && data.pinch)) {	// Always fire 'zoom' if pinching because #3530
			this.fire('zoom', data);
		}

		// @event move: Event
		// Fired repeatedly during any movement of the map, including pan and
		// fly animations.
		return this.fire('move', data);
	},

	_moveEnd: function (zoomChanged) {
		// @event zoomend: Event
		// Fired when the map has changed, after any animations.
		if (zoomChanged) {
			this.fire('zoomend');
		}

		// @event moveend: Event
		// Fired when the center of the map stops changing (e.g. user stopped
		// dragging the map).
		return this.fire('moveend');
	},

	_stop: function () {
		L.Util.cancelAnimFrame(this._flyToFrame);
		if (this._panAnim) {
			this._panAnim.stop();
		}
		return this;
	},

	_rawPanBy: function (offset) {
		L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
	},

	_getZoomSpan: function () {
		return this.getMaxZoom() - this.getMinZoom();
	},

	_panInsideMaxBounds: function () {
		if (!this._enforcingBounds) {
			this.panInsideBounds(this.options.maxBounds);
		}
	},

	_checkIfLoaded: function () {
		if (!this._loaded) {
			throw new Error('Set map center and zoom first.');
		}
	},

	// DOM event handling

	// @section Interaction events
	_initEvents: function (remove) {
		if (!L.DomEvent) { return; }

		this._targets = {};
		this._targets[L.stamp(this._container)] = this;

		var onOff = remove ? 'off' : 'on';

		// @event click: MouseEvent
		// Fired when the user clicks (or taps) the map.
		// @event dblclick: MouseEvent
		// Fired when the user double-clicks (or double-taps) the map.
		// @event mousedown: MouseEvent
		// Fired when the user pushes the mouse button on the map.
		// @event mouseup: MouseEvent
		// Fired when the user releases the mouse button on the map.
		// @event mouseover: MouseEvent
		// Fired when the mouse enters the map.
		// @event mouseout: MouseEvent
		// Fired when the mouse leaves the map.
		// @event mousemove: MouseEvent
		// Fired while the mouse moves over the map.
		// @event contextmenu: MouseEvent
		// Fired when the user pushes the right mouse button on the map, prevents
		// default browser context menu from showing if there are listeners on
		// this event. Also fired on mobile when the user holds a single touch
		// for a second (also called long press).
		// @event keypress: KeyboardEvent
		// Fired when the user presses a key from the keyboard while the map is focused.
		L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
			'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);

		if (this.options.trackResize) {
			L.DomEvent[onOff](window, 'resize', this._onResize, this);
		}

		if (L.Browser.any3d && this.options.transform3DLimit) {
			this[onOff]('moveend', this._onMoveEnd);
		}
	},

	_onResize: function () {
		L.Util.cancelAnimFrame(this._resizeRequest);
		this._resizeRequest = L.Util.requestAnimFrame(
		        function () { this.invalidateSize({debounceMoveend: true}); }, this);
	},

	_onScroll: function () {
		this._container.scrollTop  = 0;
		this._container.scrollLeft = 0;
	},

	_onMoveEnd: function () {
		var pos = this._getMapPanePos();
		if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
			// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
			// a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
			this._resetView(this.getCenter(), this.getZoom());
		}
	},

	_findEventTargets: function (e, type) {
		var targets = [],
		    target,
		    isHover = type === 'mouseout' || type === 'mouseover',
		    src = e.target || e.srcElement,
		    dragging = false;

		while (src) {
			target = this._targets[L.stamp(src)];
			if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
				// Prevent firing click after you just dragged an object.
				dragging = true;
				break;
			}
			if (target && target.listens(type, true)) {
				if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
				targets.push(target);
				if (isHover) { break; }
			}
			if (src === this._container) { break; }
			src = src.parentNode;
		}
		if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
			targets = [this];
		}
		return targets;
	},

	_handleDOMEvent: function (e) {
		if (!this._loaded || L.DomEvent._skipped(e)) { return; }

		var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;

		if (type === 'mousedown') {
			// prevents outline when clicking on keyboard-focusable element
			L.DomUtil.preventOutline(e.target || e.srcElement);
		}

		this._fireDOMEvent(e, type);
	},

	_fireDOMEvent: function (e, type, targets) {

		if (e.type === 'click') {
			// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
			// @event preclick: MouseEvent
			// Fired before mouse click on the map (sometimes useful when you
			// want something to happen on click before any existing click
			// handlers start running).
			var synth = L.Util.extend({}, e);
			synth.type = 'preclick';
			this._fireDOMEvent(synth, synth.type, targets);
		}

		if (e._stopped) { return; }

		// Find the layer the event is propagating from and its parents.
		targets = (targets || []).concat(this._findEventTargets(e, type));

		if (!targets.length) { return; }

		var target = targets[0];
		if (type === 'contextmenu' && target.listens(type, true)) {
			L.DomEvent.preventDefault(e);
		}

		var data = {
			originalEvent: e
		};

		if (e.type !== 'keypress') {
			var isMarker = target instanceof L.Marker;
			data.containerPoint = isMarker ?
					this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
			data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
			data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
		}

		for (var i = 0; i < targets.length; i++) {
			targets[i].fire(type, data, true);
			if (data.originalEvent._stopped ||
				(targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
		}
	},

	_draggableMoved: function (obj) {
		obj = obj.dragging && obj.dragging.enabled() ? obj : this;
		return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
	},

	_clearHandlers: function () {
		for (var i = 0, len = this._handlers.length; i < len; i++) {
			this._handlers[i].disable();
		}
	},

	// @section Other Methods

	// @method whenReady(fn: Function, context?: Object): this
	// Runs the given function `fn` when the map gets initialized with
	// a view (center and zoom) and at least one layer, or immediately
	// if it's already initialized, optionally passing a function context.
	whenReady: function (callback, context) {
		if (this._loaded) {
			callback.call(context || this, {target: this});
		} else {
			this.on('load', callback, context);
		}
		return this;
	},


	// private methods for getting map state

	_getMapPanePos: function () {
		return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
	},

	_moved: function () {
		var pos = this._getMapPanePos();
		return pos && !pos.equals([0, 0]);
	},

	_getTopLeftPoint: function (center, zoom) {
		var pixelOrigin = center && zoom !== undefined ?
			this._getNewPixelOrigin(center, zoom) :
			this.getPixelOrigin();
		return pixelOrigin.subtract(this._getMapPanePos());
	},

	_getNewPixelOrigin: function (center, zoom) {
		var viewHalf = this.getSize()._divideBy(2);
		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
	},

	_latLngToNewLayerPoint: function (latlng, zoom, center) {
		var topLeft = this._getNewPixelOrigin(center, zoom);
		return this.project(latlng, zoom)._subtract(topLeft);
	},

	_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
		var topLeft = this._getNewPixelOrigin(center, zoom);
		return L.bounds([
			this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
			this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
			this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
			this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
		]);
	},

	// layer point of the current center
	_getCenterLayerPoint: function () {
		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
	},

	// offset of the specified place to the current center in pixels
	_getCenterOffset: function (latlng) {
		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
	},

	// adjust center for view to get inside bounds
	_limitCenter: function (center, zoom, bounds) {

		if (!bounds) { return center; }

		var centerPoint = this.project(center, zoom),
		    viewHalf = this.getSize().divideBy(2),
		    viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);

		// If offset is less than a pixel, ignore.
		// This prevents unstable projections from getting into
		// an infinite loop of tiny offsets.
		if (offset.round().equals([0, 0])) {
			return center;
		}

		return this.unproject(centerPoint.add(offset), zoom);
	},

	// adjust offset for view to get inside bounds
	_limitOffset: function (offset, bounds) {
		if (!bounds) { return offset; }

		var viewBounds = this.getPixelBounds(),
		    newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));

		return offset.add(this._getBoundsOffset(newBounds, bounds));
	},

	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
		var projectedMaxBounds = L.bounds(
		        this.project(maxBounds.getNorthEast(), zoom),
		        this.project(maxBounds.getSouthWest(), zoom)
		    ),
		    minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
		    maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),

		    dx = this._rebound(minOffset.x, -maxOffset.x),
		    dy = this._rebound(minOffset.y, -maxOffset.y);

		return new L.Point(dx, dy);
	},

	_rebound: function (left, right) {
		return left + right > 0 ?
			Math.round(left - right) / 2 :
			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
	},

	_limitZoom: function (zoom) {
		var min = this.getMinZoom(),
		    max = this.getMaxZoom(),
		    snap = L.Browser.any3d ? this.options.zoomSnap : 1;
		if (snap) {
			zoom = Math.round(zoom / snap) * snap;
		}
		return Math.max(min, Math.min(max, zoom));
	},

	_onPanTransitionStep: function () {
		this.fire('move');
	},

	_onPanTransitionEnd: function () {
		L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
		this.fire('moveend');
	},

	_tryAnimatedPan: function (center, options) {
		// difference between the new and current centers in pixels
		var offset = this._getCenterOffset(center)._floor();

		// don't animate too far unless animate: true specified in options
		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }

		this.panBy(offset, options);

		return true;
	},

	_createAnimProxy: function () {

		var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
		this._panes.mapPane.appendChild(proxy);

		this.on('zoomanim', function (e) {
			var prop = L.DomUtil.TRANSFORM,
			    transform = proxy.style[prop];

			L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));

			// workaround for case when transform is the same and so transitionend event is not fired
			if (transform === proxy.style[prop] && this._animatingZoom) {
				this._onZoomTransitionEnd();
			}
		}, this);

		this.on('load moveend', function () {
			var c = this.getCenter(),
			    z = this.getZoom();
			L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
		}, this);
	},

	_catchTransitionEnd: function (e) {
		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
			this._onZoomTransitionEnd();
		}
	},

	_nothingToAnimate: function () {
		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
	},

	_tryAnimatedZoom: function (center, zoom, options) {

		if (this._animatingZoom) { return true; }

		options = options || {};

		// don't animate if disabled, not supported or zoom difference is too large
		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }

		// offset is the pixel coords of the zoom origin relative to the current center
		var scale = this.getZoomScale(zoom),
		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);

		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }

		L.Util.requestAnimFrame(function () {
			this
			    ._moveStart(true)
			    ._animateZoom(center, zoom, true);
		}, this);

		return true;
	},

	_animateZoom: function (center, zoom, startAnim, noUpdate) {
		if (startAnim) {
			this._animatingZoom = true;

			// remember what center/zoom to set after animation
			this._animateToCenter = center;
			this._animateToZoom = zoom;

			L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
		}

		// @event zoomanim: ZoomAnimEvent
		// Fired on every frame of a zoom animation
		this.fire('zoomanim', {
			center: center,
			zoom: zoom,
			noUpdate: noUpdate
		});

		// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
		setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
	},

	_onZoomTransitionEnd: function () {
		if (!this._animatingZoom) { return; }

		L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');

		this._animatingZoom = false;

		this._move(this._animateToCenter, this._animateToZoom);

		// This anim frame should prevent an obscure iOS webkit tile loading race condition.
		L.Util.requestAnimFrame(function () {
			this._moveEnd(true);
		}, this);
	}
});

// @section

// @factory L.map(id: String, options?: Map options)
// Instantiates a map object given the DOM ID of a `<div>` element
// and optionally an object literal with `Map options`.
//
// @alternative
// @factory L.map(el: HTMLElement, options?: Map options)
// Instantiates a map object given an instance of a `<div>` HTML element
// and optionally an object literal with `Map options`.
L.map = function (id, options) {
	return new L.Map(id, options);
};




/*
 * @class Layer
 * @inherits Evented
 * @aka L.Layer
 * @aka ILayer
 *
 * A set of methods from the Layer base class that all Leaflet layers use.
 * Inherits all methods, options and events from `L.Evented`.
 *
 * @example
 *
 * ```js
 * var layer = L.Marker(latlng).addTo(map);
 * layer.addTo(map);
 * layer.remove();
 * ```
 *
 * @event add: Event
 * Fired after the layer is added to a map
 *
 * @event remove: Event
 * Fired after the layer is removed from a map
 */


L.Layer = L.Evented.extend({

	// Classes extending `L.Layer` will inherit the following options:
	options: {
		// @option pane: String = 'overlayPane'
		// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
		pane: 'overlayPane',
		nonBubblingEvents: [],  // Array of events that should not be bubbled to DOM parents (like the map),

		// @option attribution: String = null
		// String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
		attribution: null
	},

	/* @section
	 * Classes extending `L.Layer` will inherit the following methods:
	 *
	 * @method addTo(map: Map): this
	 * Adds the layer to the given map
	 */
	addTo: function (map) {
		map.addLayer(this);
		return this;
	},

	// @method remove: this
	// Removes the layer from the map it is currently active on.
	remove: function () {
		return this.removeFrom(this._map || this._mapToAdd);
	},

	// @method removeFrom(map: Map): this
	// Removes the layer from the given map
	removeFrom: function (obj) {
		if (obj) {
			obj.removeLayer(this);
		}
		return this;
	},

	// @method getPane(name? : String): HTMLElement
	// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
	getPane: function (name) {
		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
	},

	addInteractiveTarget: function (targetEl) {
		this._map._targets[L.stamp(targetEl)] = this;
		return this;
	},

	removeInteractiveTarget: function (targetEl) {
		delete this._map._targets[L.stamp(targetEl)];
		return this;
	},

	// @method getAttribution: String
	// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
	getAttribution: function () {
		return this.options.attribution;
	},

	_layerAdd: function (e) {
		var map = e.target;

		// check in case layer gets added and then removed before the map is ready
		if (!map.hasLayer(this)) { return; }

		this._map = map;
		this._zoomAnimated = map._zoomAnimated;

		if (this.getEvents) {
			var events = this.getEvents();
			map.on(events, this);
			this.once('remove', function () {
				map.off(events, this);
			}, this);
		}

		this.onAdd(map);

		if (this.getAttribution && map.attributionControl) {
			map.attributionControl.addAttribution(this.getAttribution());
		}

		this.fire('add');
		map.fire('layeradd', {layer: this});
	}
});

/* @section Extension methods
 * @uninheritable
 *
 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
 *
 * @method onAdd(map: Map): this
 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
 *
 * @method onRemove(map: Map): this
 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
 *
 * @method getEvents(): Object
 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
 *
 * @method getAttribution(): String
 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
 *
 * @method beforeAdd(map: Map): this
 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
 */


/* @namespace Map
 * @section Layer events
 *
 * @event layeradd: LayerEvent
 * Fired when a new layer is added to the map.
 *
 * @event layerremove: LayerEvent
 * Fired when some layer is removed from the map
 *
 * @section Methods for Layers and Controls
 */
L.Map.include({
	// @method addLayer(layer: Layer): this
	// Adds the given layer to the map
	addLayer: function (layer) {
		var id = L.stamp(layer);
		if (this._layers[id]) { return this; }
		this._layers[id] = layer;

		layer._mapToAdd = this;

		if (layer.beforeAdd) {
			layer.beforeAdd(this);
		}

		this.whenReady(layer._layerAdd, layer);

		return this;
	},

	// @method removeLayer(layer: Layer): this
	// Removes the given layer from the map.
	removeLayer: function (layer) {
		var id = L.stamp(layer);

		if (!this._layers[id]) { return this; }

		if (this._loaded) {
			layer.onRemove(this);
		}

		if (layer.getAttribution && this.attributionControl) {
			this.attributionControl.removeAttribution(layer.getAttribution());
		}

		delete this._layers[id];

		if (this._loaded) {
			this.fire('layerremove', {layer: layer});
			layer.fire('remove');
		}

		layer._map = layer._mapToAdd = null;

		return this;
	},

	// @method hasLayer(layer: Layer): Boolean
	// Returns `true` if the given layer is currently added to the map
	hasLayer: function (layer) {
		return !!layer && (L.stamp(layer) in this._layers);
	},

	/* @method eachLayer(fn: Function, context?: Object): this
	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
	 * ```
	 * map.eachLayer(function(layer){
	 *     layer.bindPopup('Hello');
	 * });
	 * ```
	 */
	eachLayer: function (method, context) {
		for (var i in this._layers) {
			method.call(context, this._layers[i]);
		}
		return this;
	},

	_addLayers: function (layers) {
		layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];

		for (var i = 0, len = layers.length; i < len; i++) {
			this.addLayer(layers[i]);
		}
	},

	_addZoomLimit: function (layer) {
		if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
			this._zoomBoundLayers[L.stamp(layer)] = layer;
			this._updateZoomLevels();
		}
	},

	_removeZoomLimit: function (layer) {
		var id = L.stamp(layer);

		if (this._zoomBoundLayers[id]) {
			delete this._zoomBoundLayers[id];
			this._updateZoomLevels();
		}
	},

	_updateZoomLevels: function () {
		var minZoom = Infinity,
		    maxZoom = -Infinity,
		    oldZoomSpan = this._getZoomSpan();

		for (var i in this._zoomBoundLayers) {
			var options = this._zoomBoundLayers[i].options;

			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
		}

		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;

		// @section Map state change events
		// @event zoomlevelschange: Event
		// Fired when the number of zoomlevels on the map is changed due
		// to adding or removing a layer.
		if (oldZoomSpan !== this._getZoomSpan()) {
			this.fire('zoomlevelschange');
		}

		if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
			this.setZoom(this._layersMaxZoom);
		}
		if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
			this.setZoom(this._layersMinZoom);
		}
	}
});



/*
 * @namespace DomEvent
 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
 */

// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.



var eventsKey = '_leaflet_events';

L.DomEvent = {

	// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
	// Adds a listener function (`fn`) to a particular DOM event type of the
	// element `el`. You can optionally specify the context of the listener
	// (object the `this` keyword will point to). You can also pass several
	// space-separated types (e.g. `'click dblclick'`).

	// @alternative
	// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
	// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
	on: function (obj, types, fn, context) {

		if (typeof types === 'object') {
			for (var type in types) {
				this._on(obj, type, types[type], fn);
			}
		} else {
			types = L.Util.splitWords(types);

			for (var i = 0, len = types.length; i < len; i++) {
				this._on(obj, types[i], fn, context);
			}
		}

		return this;
	},

	// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
	// Removes a previously added listener function. If no function is specified,
	// it will remove all the listeners of that particular DOM event from the element.
	// Note that if you passed a custom context to on, you must pass the same
	// context to `off` in order to remove the listener.

	// @alternative
	// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
	// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
	off: function (obj, types, fn, context) {

		if (typeof types === 'object') {
			for (var type in types) {
				this._off(obj, type, types[type], fn);
			}
		} else {
			types = L.Util.splitWords(types);

			for (var i = 0, len = types.length; i < len; i++) {
				this._off(obj, types[i], fn, context);
			}
		}

		return this;
	},

	_on: function (obj, type, fn, context) {
		var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');

		if (obj[eventsKey] && obj[eventsKey][id]) { return this; }

		var handler = function (e) {
			return fn.call(context || obj, e || window.event);
		};

		var originalHandler = handler;

		if (L.Browser.pointer && type.indexOf('touch') === 0) {
			this.addPointerListener(obj, type, handler, id);

		} else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
		           !(L.Browser.pointer && L.Browser.chrome)) {
			// Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
			// See #5180
			this.addDoubleTapListener(obj, handler, id);

		} else if ('addEventListener' in obj) {

			if (type === 'mousewheel') {
				obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);

			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
				handler = function (e) {
					e = e || window.event;
					if (L.DomEvent._isExternalTarget(obj, e)) {
						originalHandler(e);
					}
				};
				obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);

			} else {
				if (type === 'click' && L.Browser.android) {
					handler = function (e) {
						return L.DomEvent._filterClick(e, originalHandler);
					};
				}
				obj.addEventListener(type, handler, false);
			}

		} else if ('attachEvent' in obj) {
			obj.attachEvent('on' + type, handler);
		}

		obj[eventsKey] = obj[eventsKey] || {};
		obj[eventsKey][id] = handler;

		return this;
	},

	_off: function (obj, type, fn, context) {

		var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
		    handler = obj[eventsKey] && obj[eventsKey][id];

		if (!handler) { return this; }

		if (L.Browser.pointer && type.indexOf('touch') === 0) {
			this.removePointerListener(obj, type, id);

		} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
			this.removeDoubleTapListener(obj, id);

		} else if ('removeEventListener' in obj) {

			if (type === 'mousewheel') {
				obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);

			} else {
				obj.removeEventListener(
					type === 'mouseenter' ? 'mouseover' :
					type === 'mouseleave' ? 'mouseout' : type, handler, false);
			}

		} else if ('detachEvent' in obj) {
			obj.detachEvent('on' + type, handler);
		}

		obj[eventsKey][id] = null;

		return this;
	},

	// @function stopPropagation(ev: DOMEvent): this
	// Stop the given event from propagation to parent elements. Used inside the listener functions:
	// ```js
	// L.DomEvent.on(div, 'click', function (ev) {
	// 	L.DomEvent.stopPropagation(ev);
	// });
	// ```
	stopPropagation: function (e) {

		if (e.stopPropagation) {
			e.stopPropagation();
		} else if (e.originalEvent) {  // In case of Leaflet event.
			e.originalEvent._stopped = true;
		} else {
			e.cancelBubble = true;
		}
		L.DomEvent._skipped(e);

		return this;
	},

	// @function disableScrollPropagation(el: HTMLElement): this
	// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
	disableScrollPropagation: function (el) {
		return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
	},

	// @function disableClickPropagation(el: HTMLElement): this
	// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
	// `'mousedown'` and `'touchstart'` events (plus browser variants).
	disableClickPropagation: function (el) {
		var stop = L.DomEvent.stopPropagation;

		L.DomEvent.on(el, L.Draggable.START.join(' '), stop);

		return L.DomEvent.on(el, {
			click: L.DomEvent._fakeStop,
			dblclick: stop
		});
	},

	// @function preventDefault(ev: DOMEvent): this
	// Prevents the default action of the DOM Event `ev` from happening (such as
	// following a link in the href of the a element, or doing a POST request
	// with page reload when a `<form>` is submitted).
	// Use it inside listener functions.
	preventDefault: function (e) {

		if (e.preventDefault) {
			e.preventDefault();
		} else {
			e.returnValue = false;
		}
		return this;
	},

	// @function stop(ev): this
	// Does `stopPropagation` and `preventDefault` at the same time.
	stop: function (e) {
		return L.DomEvent
			.preventDefault(e)
			.stopPropagation(e);
	},

	// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
	// Gets normalized mouse position from a DOM event relative to the
	// `container` or to the whole page if not specified.
	getMousePosition: function (e, container) {
		if (!container) {
			return new L.Point(e.clientX, e.clientY);
		}

		var rect = container.getBoundingClientRect();

		return new L.Point(
			e.clientX - rect.left - container.clientLeft,
			e.clientY - rect.top - container.clientTop);
	},

	// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
	// and Firefox scrolls device pixels, not CSS pixels
	_wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
	                L.Browser.gecko ? window.devicePixelRatio :
	                1,

	// @function getWheelDelta(ev: DOMEvent): Number
	// Gets normalized wheel delta from a mousewheel DOM event, in vertical
	// pixels scrolled (negative if scrolling down).
	// Events from pointing devices without precise scrolling are mapped to
	// a best guess of 60 pixels.
	getWheelDelta: function (e) {
		return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
		       (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
		       (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
		       (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
		       (e.deltaX || e.deltaZ) ? 0 :	// Skip horizontal/depth wheel events
		       e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
		       (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
		       e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
		       0;
	},

	_skipEvents: {},

	_fakeStop: function (e) {
		// fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
		L.DomEvent._skipEvents[e.type] = true;
	},

	_skipped: function (e) {
		var skipped = this._skipEvents[e.type];
		// reset when checking, as it's only used in map container and propagates outside of the map
		this._skipEvents[e.type] = false;
		return skipped;
	},

	// check if element really left/entered the event target (for mouseenter/mouseleave)
	_isExternalTarget: function (el, e) {

		var related = e.relatedTarget;

		if (!related) { return true; }

		try {
			while (related && (related !== el)) {
				related = related.parentNode;
			}
		} catch (err) {
			return false;
		}
		return (related !== el);
	},

	// this is a horrible workaround for a bug in Android where a single touch triggers two click events
	_filterClick: function (e, handler) {
		var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
		    elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);

		// are they closer together than 500ms yet more than 100ms?
		// Android typically triggers them ~300ms apart while multiple listeners
		// on the same event should be triggered far faster;
		// or check if click is simulated on the element, and if it is, reject any non-simulated events

		if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
			L.DomEvent.stop(e);
			return;
		}
		L.DomEvent._lastClick = timeStamp;

		handler(e);
	}
};

// @function addListener(…): this
// Alias to [`L.DomEvent.on`](#domevent-on)
L.DomEvent.addListener = L.DomEvent.on;

// @function removeListener(…): this
// Alias to [`L.DomEvent.off`](#domevent-off)
L.DomEvent.removeListener = L.DomEvent.off;



/*
 * @class PosAnimation
 * @aka L.PosAnimation
 * @inherits Evented
 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
 *
 * @example
 * ```js
 * var fx = new L.PosAnimation();
 * fx.run(el, [300, 500], 0.5);
 * ```
 *
 * @constructor L.PosAnimation()
 * Creates a `PosAnimation` object.
 *
 */

L.PosAnimation = L.Evented.extend({

	// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
	// Run an animation of a given element to a new position, optionally setting
	// duration in seconds (`0.25` by default) and easing linearity factor (3rd
	// argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
	// `0.5` by default).
	run: function (el, newPos, duration, easeLinearity) {
		this.stop();

		this._el = el;
		this._inProgress = true;
		this._duration = duration || 0.25;
		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);

		this._startPos = L.DomUtil.getPosition(el);
		this._offset = newPos.subtract(this._startPos);
		this._startTime = +new Date();

		// @event start: Event
		// Fired when the animation starts
		this.fire('start');

		this._animate();
	},

	// @method stop()
	// Stops the animation (if currently running).
	stop: function () {
		if (!this._inProgress) { return; }

		this._step(true);
		this._complete();
	},

	_animate: function () {
		// animation loop
		this._animId = L.Util.requestAnimFrame(this._animate, this);
		this._step();
	},

	_step: function (round) {
		var elapsed = (+new Date()) - this._startTime,
		    duration = this._duration * 1000;

		if (elapsed < duration) {
			this._runFrame(this._easeOut(elapsed / duration), round);
		} else {
			this._runFrame(1);
			this._complete();
		}
	},

	_runFrame: function (progress, round) {
		var pos = this._startPos.add(this._offset.multiplyBy(progress));
		if (round) {
			pos._round();
		}
		L.DomUtil.setPosition(this._el, pos);

		// @event step: Event
		// Fired continuously during the animation.
		this.fire('step');
	},

	_complete: function () {
		L.Util.cancelAnimFrame(this._animId);

		this._inProgress = false;
		// @event end: Event
		// Fired when the animation ends.
		this.fire('end');
	},

	_easeOut: function (t) {
		return 1 - Math.pow(1 - t, this._easeOutPower);
	}
});



/*
 * @namespace Projection
 * @projection L.Projection.Mercator
 *
 * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
 */

L.Projection.Mercator = {
	R: 6378137,
	R_MINOR: 6356752.314245179,

	bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),

	project: function (latlng) {
		var d = Math.PI / 180,
		    r = this.R,
		    y = latlng.lat * d,
		    tmp = this.R_MINOR / r,
		    e = Math.sqrt(1 - tmp * tmp),
		    con = e * Math.sin(y);

		var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
		y = -r * Math.log(Math.max(ts, 1E-10));

		return new L.Point(latlng.lng * d * r, y);
	},

	unproject: function (point) {
		var d = 180 / Math.PI,
		    r = this.R,
		    tmp = this.R_MINOR / r,
		    e = Math.sqrt(1 - tmp * tmp),
		    ts = Math.exp(-point.y / r),
		    phi = Math.PI / 2 - 2 * Math.atan(ts);

		for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
			con = e * Math.sin(phi);
			con = Math.pow((1 - con) / (1 + con), e / 2);
			dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
			phi += dphi;
		}

		return new L.LatLng(phi * d, point.x * d / r);
	}
};



/*
 * @namespace CRS
 * @crs L.CRS.EPSG3395
 *
 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
 */

L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
	code: 'EPSG:3395',
	projection: L.Projection.Mercator,

	transformation: (function () {
		var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
		return new L.Transformation(scale, 0.5, -scale, 0.5);
	}())
});



/*
 * @class GridLayer
 * @inherits Layer
 * @aka L.GridLayer
 *
 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
 *
 *
 * @section Synchronous usage
 * @example
 *
 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
 *
 * ```js
 * var CanvasLayer = L.GridLayer.extend({
 *     createTile: function(coords){
 *         // create a <canvas> element for drawing
 *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
 *
 *         // setup tile width and height according to the options
 *         var size = this.getTileSize();
 *         tile.width = size.x;
 *         tile.height = size.y;
 *
 *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
 *         var ctx = tile.getContext('2d');
 *
 *         // return the tile so it can be rendered on screen
 *         return tile;
 *     }
 * });
 * ```
 *
 * @section Asynchronous usage
 * @example
 *
 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
 *
 * ```js
 * var CanvasLayer = L.GridLayer.extend({
 *     createTile: function(coords, done){
 *         var error;
 *
 *         // create a <canvas> element for drawing
 *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
 *
 *         // setup tile width and height according to the options
 *         var size = this.getTileSize();
 *         tile.width = size.x;
 *         tile.height = size.y;
 *
 *         // draw something asynchronously and pass the tile to the done() callback
 *         setTimeout(function() {
 *             done(error, tile);
 *         }, 1000);
 *
 *         return tile;
 *     }
 * });
 * ```
 *
 * @section
 */


L.GridLayer = L.Layer.extend({

	// @section
	// @aka GridLayer options
	options: {
		// @option tileSize: Number|Point = 256
		// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
		tileSize: 256,

		// @option opacity: Number = 1.0
		// Opacity of the tiles. Can be used in the `createTile()` function.
		opacity: 1,

		// @option updateWhenIdle: Boolean = depends
		// If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
		updateWhenIdle: L.Browser.mobile,

		// @option updateWhenZooming: Boolean = true
		// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
		updateWhenZooming: true,

		// @option updateInterval: Number = 200
		// Tiles will not update more than once every `updateInterval` milliseconds when panning.
		updateInterval: 200,

		// @option zIndex: Number = 1
		// The explicit zIndex of the tile layer.
		zIndex: 1,

		// @option bounds: LatLngBounds = undefined
		// If set, tiles will only be loaded inside the set `LatLngBounds`.
		bounds: null,

		// @option minZoom: Number = 0
		// The minimum zoom level that tiles will be loaded at. By default the entire map.
		minZoom: 0,

		// @option maxZoom: Number = undefined
		// The maximum zoom level that tiles will be loaded at.
		maxZoom: undefined,

		// @option noWrap: Boolean = false
		// Whether the layer is wrapped around the antimeridian. If `true`, the
		// GridLayer will only be displayed once at low zoom levels. Has no
		// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
		// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
		// tiles outside the CRS limits.
		noWrap: false,

		// @option pane: String = 'tilePane'
		// `Map pane` where the grid layer will be added.
		pane: 'tilePane',

		// @option className: String = ''
		// A custom class name to assign to the tile layer. Empty by default.
		className: '',

		// @option keepBuffer: Number = 2
		// When panning the map, keep this many rows and columns of tiles before unloading them.
		keepBuffer: 2
	},

	initialize: function (options) {
		L.setOptions(this, options);
	},

	onAdd: function () {
		this._initContainer();

		this._levels = {};
		this._tiles = {};

		this._resetView();
		this._update();
	},

	beforeAdd: function (map) {
		map._addZoomLimit(this);
	},

	onRemove: function (map) {
		this._removeAllTiles();
		L.DomUtil.remove(this._container);
		map._removeZoomLimit(this);
		this._container = null;
		this._tileZoom = null;
	},

	// @method bringToFront: this
	// Brings the tile layer to the top of all tile layers.
	bringToFront: function () {
		if (this._map) {
			L.DomUtil.toFront(this._container);
			this._setAutoZIndex(Math.max);
		}
		return this;
	},

	// @method bringToBack: this
	// Brings the tile layer to the bottom of all tile layers.
	bringToBack: function () {
		if (this._map) {
			L.DomUtil.toBack(this._container);
			this._setAutoZIndex(Math.min);
		}
		return this;
	},

	// @method getContainer: HTMLElement
	// Returns the HTML element that contains the tiles for this layer.
	getContainer: function () {
		return this._container;
	},

	// @method setOpacity(opacity: Number): this
	// Changes the [opacity](#gridlayer-opacity) of the grid layer.
	setOpacity: function (opacity) {
		this.options.opacity = opacity;
		this._updateOpacity();
		return this;
	},

	// @method setZIndex(zIndex: Number): this
	// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
	setZIndex: function (zIndex) {
		this.options.zIndex = zIndex;
		this._updateZIndex();

		return this;
	},

	// @method isLoading: Boolean
	// Returns `true` if any tile in the grid layer has not finished loading.
	isLoading: function () {
		return this._loading;
	},

	// @method redraw: this
	// Causes the layer to clear all the tiles and request them again.
	redraw: function () {
		if (this._map) {
			this._removeAllTiles();
			this._update();
		}
		return this;
	},

	getEvents: function () {
		var events = {
			viewprereset: this._invalidateAll,
			viewreset: this._resetView,
			zoom: this._resetView,
			moveend: this._onMoveEnd
		};

		if (!this.options.updateWhenIdle) {
			// update tiles on move, but not more often than once per given interval
			if (!this._onMove) {
				this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
			}

			events.move = this._onMove;
		}

		if (this._zoomAnimated) {
			events.zoomanim = this._animateZoom;
		}

		return events;
	},

	// @section Extension methods
	// Layers extending `GridLayer` shall reimplement the following method.
	// @method createTile(coords: Object, done?: Function): HTMLElement
	// Called only internally, must be overriden by classes extending `GridLayer`.
	// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
	// is specified, it must be called when the tile has finished loading and drawing.
	createTile: function () {
		return document.createElement('div');
	},

	// @section
	// @method getTileSize: Point
	// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
	getTileSize: function () {
		var s = this.options.tileSize;
		return s instanceof L.Point ? s : new L.Point(s, s);
	},

	_updateZIndex: function () {
		if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
			this._container.style.zIndex = this.options.zIndex;
		}
	},

	_setAutoZIndex: function (compare) {
		// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)

		var layers = this.getPane().children,
		    edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min

		for (var i = 0, len = layers.length, zIndex; i < len; i++) {

			zIndex = layers[i].style.zIndex;

			if (layers[i] !== this._container && zIndex) {
				edgeZIndex = compare(edgeZIndex, +zIndex);
			}
		}

		if (isFinite(edgeZIndex)) {
			this.options.zIndex = edgeZIndex + compare(-1, 1);
			this._updateZIndex();
		}
	},

	_updateOpacity: function () {
		if (!this._map) { return; }

		// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
		if (L.Browser.ielt9) { return; }

		L.DomUtil.setOpacity(this._container, this.options.opacity);

		var now = +new Date(),
		    nextFrame = false,
		    willPrune = false;

		for (var key in this._tiles) {
			var tile = this._tiles[key];
			if (!tile.current || !tile.loaded) { continue; }

			var fade = Math.min(1, (now - tile.loaded) / 200);

			L.DomUtil.setOpacity(tile.el, fade);
			if (fade < 1) {
				nextFrame = true;
			} else {
				if (tile.active) { willPrune = true; }
				tile.active = true;
			}
		}

		if (willPrune && !this._noPrune) { this._pruneTiles(); }

		if (nextFrame) {
			L.Util.cancelAnimFrame(this._fadeFrame);
			this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
		}
	},

	_initContainer: function () {
		if (this._container) { return; }

		this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
		this._updateZIndex();

		if (this.options.opacity < 1) {
			this._updateOpacity();
		}

		this.getPane().appendChild(this._container);
	},

	_updateLevels: function () {

		var zoom = this._tileZoom,
		    maxZoom = this.options.maxZoom;

		if (zoom === undefined) { return undefined; }

		for (var z in this._levels) {
			if (this._levels[z].el.children.length || z === zoom) {
				this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
			} else {
				L.DomUtil.remove(this._levels[z].el);
				this._removeTilesAtZoom(z);
				delete this._levels[z];
			}
		}

		var level = this._levels[zoom],
		    map = this._map;

		if (!level) {
			level = this._levels[zoom] = {};

			level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
			level.el.style.zIndex = maxZoom;

			level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
			level.zoom = zoom;

			this._setZoomTransform(level, map.getCenter(), map.getZoom());

			// force the browser to consider the newly added element for transition
			L.Util.falseFn(level.el.offsetWidth);
		}

		this._level = level;

		return level;
	},

	_pruneTiles: function () {
		if (!this._map) {
			return;
		}

		var key, tile;

		var zoom = this._map.getZoom();
		if (zoom > this.options.maxZoom ||
			zoom < this.options.minZoom) {
			this._removeAllTiles();
			return;
		}

		for (key in this._tiles) {
			tile = this._tiles[key];
			tile.retain = tile.current;
		}

		for (key in this._tiles) {
			tile = this._tiles[key];
			if (tile.current && !tile.active) {
				var coords = tile.coords;
				if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
					this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
				}
			}
		}

		for (key in this._tiles) {
			if (!this._tiles[key].retain) {
				this._removeTile(key);
			}
		}
	},

	_removeTilesAtZoom: function (zoom) {
		for (var key in this._tiles) {
			if (this._tiles[key].coords.z !== zoom) {
				continue;
			}
			this._removeTile(key);
		}
	},

	_removeAllTiles: function () {
		for (var key in this._tiles) {
			this._removeTile(key);
		}
	},

	_invalidateAll: function () {
		for (var z in this._levels) {
			L.DomUtil.remove(this._levels[z].el);
			delete this._levels[z];
		}
		this._removeAllTiles();

		this._tileZoom = null;
	},

	_retainParent: function (x, y, z, minZoom) {
		var x2 = Math.floor(x / 2),
		    y2 = Math.floor(y / 2),
		    z2 = z - 1,
		    coords2 = new L.Point(+x2, +y2);
		coords2.z = +z2;

		var key = this._tileCoordsToKey(coords2),
		    tile = this._tiles[key];

		if (tile && tile.active) {
			tile.retain = true;
			return true;

		} else if (tile && tile.loaded) {
			tile.retain = true;
		}

		if (z2 > minZoom) {
			return this._retainParent(x2, y2, z2, minZoom);
		}

		return false;
	},

	_retainChildren: function (x, y, z, maxZoom) {

		for (var i = 2 * x; i < 2 * x + 2; i++) {
			for (var j = 2 * y; j < 2 * y + 2; j++) {

				var coords = new L.Point(i, j);
				coords.z = z + 1;

				var key = this._tileCoordsToKey(coords),
				    tile = this._tiles[key];

				if (tile && tile.active) {
					tile.retain = true;
					continue;

				} else if (tile && tile.loaded) {
					tile.retain = true;
				}

				if (z + 1 < maxZoom) {
					this._retainChildren(i, j, z + 1, maxZoom);
				}
			}
		}
	},

	_resetView: function (e) {
		var animating = e && (e.pinch || e.flyTo);
		this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
	},

	_animateZoom: function (e) {
		this._setView(e.center, e.zoom, true, e.noUpdate);
	},

	_setView: function (center, zoom, noPrune, noUpdate) {
		var tileZoom = Math.round(zoom);
		if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
		    (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
			tileZoom = undefined;
		}

		var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);

		if (!noUpdate || tileZoomChanged) {

			this._tileZoom = tileZoom;

			if (this._abortLoading) {
				this._abortLoading();
			}

			this._updateLevels();
			this._resetGrid();

			if (tileZoom !== undefined) {
				this._update(center);
			}

			if (!noPrune) {
				this._pruneTiles();
			}

			// Flag to prevent _updateOpacity from pruning tiles during
			// a zoom anim or a pinch gesture
			this._noPrune = !!noPrune;
		}

		this._setZoomTransforms(center, zoom);
	},

	_setZoomTransforms: function (center, zoom) {
		for (var i in this._levels) {
			this._setZoomTransform(this._levels[i], center, zoom);
		}
	},

	_setZoomTransform: function (level, center, zoom) {
		var scale = this._map.getZoomScale(zoom, level.zoom),
		    translate = level.origin.multiplyBy(scale)
		        .subtract(this._map._getNewPixelOrigin(center, zoom)).round();

		if (L.Browser.any3d) {
			L.DomUtil.setTransform(level.el, translate, scale);
		} else {
			L.DomUtil.setPosition(level.el, translate);
		}
	},

	_resetGrid: function () {
		var map = this._map,
		    crs = map.options.crs,
		    tileSize = this._tileSize = this.getTileSize(),
		    tileZoom = this._tileZoom;

		var bounds = this._map.getPixelWorldBounds(this._tileZoom);
		if (bounds) {
			this._globalTileRange = this._pxBoundsToTileRange(bounds);
		}

		this._wrapX = crs.wrapLng && !this.options.noWrap && [
			Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
			Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
		];
		this._wrapY = crs.wrapLat && !this.options.noWrap && [
			Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
			Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
		];
	},

	_onMoveEnd: function () {
		if (!this._map || this._map._animatingZoom) { return; }

		this._update();
	},

	_getTiledPixelBounds: function (center) {
		var map = this._map,
		    mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
		    scale = map.getZoomScale(mapZoom, this._tileZoom),
		    pixelCenter = map.project(center, this._tileZoom).floor(),
		    halfSize = map.getSize().divideBy(scale * 2);

		return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
	},

	// Private method to load tiles in the grid's active zoom level according to map bounds
	_update: function (center) {
		var map = this._map;
		if (!map) { return; }
		var zoom = map.getZoom();

		if (center === undefined) { center = map.getCenter(); }
		if (this._tileZoom === undefined) { return; }	// if out of minzoom/maxzoom

		var pixelBounds = this._getTiledPixelBounds(center),
		    tileRange = this._pxBoundsToTileRange(pixelBounds),
		    tileCenter = tileRange.getCenter(),
		    queue = [],
		    margin = this.options.keepBuffer,
		    noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
		                              tileRange.getTopRight().add([margin, -margin]));

		for (var key in this._tiles) {
			var c = this._tiles[key].coords;
			if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
				this._tiles[key].current = false;
			}
		}

		// _update just loads more tiles. If the tile zoom level differs too much
		// from the map's, let _setView reset levels and prune old tiles.
		if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }

		// create a queue of coordinates to load tiles from
		for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
			for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
				var coords = new L.Point(i, j);
				coords.z = this._tileZoom;

				if (!this._isValidTile(coords)) { continue; }

				var tile = this._tiles[this._tileCoordsToKey(coords)];
				if (tile) {
					tile.current = true;
				} else {
					queue.push(coords);
				}
			}
		}

		// sort tile queue to load tiles in order of their distance to center
		queue.sort(function (a, b) {
			return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
		});

		if (queue.length !== 0) {
			// if it's the first batch of tiles to load
			if (!this._loading) {
				this._loading = true;
				// @event loading: Event
				// Fired when the grid layer starts loading tiles.
				this.fire('loading');
			}

			// create DOM fragment to append tiles in one batch
			var fragment = document.createDocumentFragment();

			for (i = 0; i < queue.length; i++) {
				this._addTile(queue[i], fragment);
			}

			this._level.el.appendChild(fragment);
		}
	},

	_isValidTile: function (coords) {
		var crs = this._map.options.crs;

		if (!crs.infinite) {
			// don't load tile if it's out of bounds and not wrapped
			var bounds = this._globalTileRange;
			if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
			    (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
		}

		if (!this.options.bounds) { return true; }

		// don't load tile if it doesn't intersect the bounds in options
		var tileBounds = this._tileCoordsToBounds(coords);
		return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
	},

	_keyToBounds: function (key) {
		return this._tileCoordsToBounds(this._keyToTileCoords(key));
	},

	// converts tile coordinates to its geographical bounds
	_tileCoordsToBounds: function (coords) {

		var map = this._map,
		    tileSize = this.getTileSize(),

		    nwPoint = coords.scaleBy(tileSize),
		    sePoint = nwPoint.add(tileSize),

		    nw = map.unproject(nwPoint, coords.z),
		    se = map.unproject(sePoint, coords.z),
		    bounds = new L.LatLngBounds(nw, se);

		if (!this.options.noWrap) {
			map.wrapLatLngBounds(bounds);
		}

		return bounds;
	},

	// converts tile coordinates to key for the tile cache
	_tileCoordsToKey: function (coords) {
		return coords.x + ':' + coords.y + ':' + coords.z;
	},

	// converts tile cache key to coordinates
	_keyToTileCoords: function (key) {
		var k = key.split(':'),
		    coords = new L.Point(+k[0], +k[1]);
		coords.z = +k[2];
		return coords;
	},

	_removeTile: function (key) {
		var tile = this._tiles[key];
		if (!tile) { return; }

		L.DomUtil.remove(tile.el);

		delete this._tiles[key];

		// @event tileunload: TileEvent
		// Fired when a tile is removed (e.g. when a tile goes off the screen).
		this.fire('tileunload', {
			tile: tile.el,
			coords: this._keyToTileCoords(key)
		});
	},

	_initTile: function (tile) {
		L.DomUtil.addClass(tile, 'leaflet-tile');

		var tileSize = this.getTileSize();
		tile.style.width = tileSize.x + 'px';
		tile.style.height = tileSize.y + 'px';

		tile.onselectstart = L.Util.falseFn;
		tile.onmousemove = L.Util.falseFn;

		// update opacity on tiles in IE7-8 because of filter inheritance problems
		if (L.Browser.ielt9 && this.options.opacity < 1) {
			L.DomUtil.setOpacity(tile, this.options.opacity);
		}

		// without this hack, tiles disappear after zoom on Chrome for Android
		// https://github.com/Leaflet/Leaflet/issues/2078
		if (L.Browser.android && !L.Browser.android23) {
			tile.style.WebkitBackfaceVisibility = 'hidden';
		}
	},

	_addTile: function (coords, container) {
		var tilePos = this._getTilePos(coords),
		    key = this._tileCoordsToKey(coords);

		var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));

		this._initTile(tile);

		// if createTile is defined with a second argument ("done" callback),
		// we know that tile is async and will be ready later; otherwise
		if (this.createTile.length < 2) {
			// mark tile as ready, but delay one frame for opacity animation to happen
			L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
		}

		L.DomUtil.setPosition(tile, tilePos);

		// save tile in cache
		this._tiles[key] = {
			el: tile,
			coords: coords,
			current: true
		};

		container.appendChild(tile);
		// @event tileloadstart: TileEvent
		// Fired when a tile is requested and starts loading.
		this.fire('tileloadstart', {
			tile: tile,
			coords: coords
		});
	},

	_tileReady: function (coords, err, tile) {
		if (!this._map) { return; }

		if (err) {
			// @event tileerror: TileErrorEvent
			// Fired when there is an error loading a tile.
			this.fire('tileerror', {
				error: err,
				tile: tile,
				coords: coords
			});
		}

		var key = this._tileCoordsToKey(coords);

		tile = this._tiles[key];
		if (!tile) { return; }

		tile.loaded = +new Date();
		if (this._map._fadeAnimated) {
			L.DomUtil.setOpacity(tile.el, 0);
			L.Util.cancelAnimFrame(this._fadeFrame);
			this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
		} else {
			tile.active = true;
			this._pruneTiles();
		}

		if (!err) {
			L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');

			// @event tileload: TileEvent
			// Fired when a tile loads.
			this.fire('tileload', {
				tile: tile.el,
				coords: coords
			});
		}

		if (this._noTilesToLoad()) {
			this._loading = false;
			// @event load: Event
			// Fired when the grid layer loaded all visible tiles.
			this.fire('load');

			if (L.Browser.ielt9 || !this._map._fadeAnimated) {
				L.Util.requestAnimFrame(this._pruneTiles, this);
			} else {
				// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
				// to trigger a pruning.
				setTimeout(L.bind(this._pruneTiles, this), 250);
			}
		}
	},

	_getTilePos: function (coords) {
		return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
	},

	_wrapCoords: function (coords) {
		var newCoords = new L.Point(
			this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
			this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
		newCoords.z = coords.z;
		return newCoords;
	},

	_pxBoundsToTileRange: function (bounds) {
		var tileSize = this.getTileSize();
		return new L.Bounds(
			bounds.min.unscaleBy(tileSize).floor(),
			bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
	},

	_noTilesToLoad: function () {
		for (var key in this._tiles) {
			if (!this._tiles[key].loaded) { return false; }
		}
		return true;
	}
});

// @factory L.gridLayer(options?: GridLayer options)
// Creates a new instance of GridLayer with the supplied options.
L.gridLayer = function (options) {
	return new L.GridLayer(options);
};



/*
 * @class TileLayer
 * @inherits GridLayer
 * @aka L.TileLayer
 * Used to load and display tile layers on the map. Extends `GridLayer`.
 *
 * @example
 *
 * ```js
 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
 * ```
 *
 * @section URL template
 * @example
 *
 * A string of the following form:
 *
 * ```
 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
 * ```
 *
 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles.
 *
 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
 *
 * ```
 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
 * ```
 */


L.TileLayer = L.GridLayer.extend({

	// @section
	// @aka TileLayer options
	options: {
		// @option minZoom: Number = 0
		// Minimum zoom number.
		minZoom: 0,

		// @option maxZoom: Number = 18
		// Maximum zoom number.
		maxZoom: 18,

		// @option maxNativeZoom: Number = null
		// Maximum zoom number the tile source has available. If it is specified,
		// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
		// from `maxNativeZoom` level and auto-scaled.
		maxNativeZoom: null,

		// @option minNativeZoom: Number = null
		// Minimum zoom number the tile source has available. If it is specified,
		// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
		// from `minNativeZoom` level and auto-scaled.
		minNativeZoom: null,

		// @option subdomains: String|String[] = 'abc'
		// Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
		subdomains: 'abc',

		// @option errorTileUrl: String = ''
		// URL to the tile image to show in place of the tile that failed to load.
		errorTileUrl: '',

		// @option zoomOffset: Number = 0
		// The zoom number used in tile URLs will be offset with this value.
		zoomOffset: 0,

		// @option tms: Boolean = false
		// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
		tms: false,

		// @option zoomReverse: Boolean = false
		// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
		zoomReverse: false,

		// @option detectRetina: Boolean = false
		// If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
		detectRetina: false,

		// @option crossOrigin: Boolean = false
		// If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
		crossOrigin: false
	},

	initialize: function (url, options) {

		this._url = url;

		options = L.setOptions(this, options);

		// detecting retina displays, adjusting tileSize and zoom levels
		if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {

			options.tileSize = Math.floor(options.tileSize / 2);

			if (!options.zoomReverse) {
				options.zoomOffset++;
				options.maxZoom--;
			} else {
				options.zoomOffset--;
				options.minZoom++;
			}

			options.minZoom = Math.max(0, options.minZoom);
		}

		if (typeof options.subdomains === 'string') {
			options.subdomains = options.subdomains.split('');
		}

		// for https://github.com/Leaflet/Leaflet/issues/137
		if (!L.Browser.android) {
			this.on('tileunload', this._onTileRemove);
		}
	},

	// @method setUrl(url: String, noRedraw?: Boolean): this
	// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
	setUrl: function (url, noRedraw) {
		this._url = url;

		if (!noRedraw) {
			this.redraw();
		}
		return this;
	},

	// @method createTile(coords: Object, done?: Function): HTMLElement
	// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
	// to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
	// callback is called when the tile has been loaded.
	createTile: function (coords, done) {
		var tile = document.createElement('img');

		L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
		L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));

		if (this.options.crossOrigin) {
			tile.crossOrigin = '';
		}

		/*
		 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
		 http://www.w3.org/TR/WCAG20-TECHS/H67
		*/
		tile.alt = '';

		/*
		 Set role="presentation" to force screen readers to ignore this
		 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
		*/
		tile.setAttribute('role', 'presentation');

		tile.src = this.getTileUrl(coords);

		return tile;
	},

	// @section Extension methods
	// @uninheritable
	// Layers extending `TileLayer` might reimplement the following method.
	// @method getTileUrl(coords: Object): String
	// Called only internally, returns the URL for a tile given its coordinates.
	// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
	getTileUrl: function (coords) {
		var data = {
			r: L.Browser.retina ? '@2x' : '',
			s: this._getSubdomain(coords),
			x: coords.x,
			y: coords.y,
			z: this._getZoomForUrl()
		};
		if (this._map && !this._map.options.crs.infinite) {
			var invertedY = this._globalTileRange.max.y - coords.y;
			if (this.options.tms) {
				data['y'] = invertedY;
			}
			data['-y'] = invertedY;
		}

		return L.Util.template(this._url, L.extend(data, this.options));
	},

	_tileOnLoad: function (done, tile) {
		// For https://github.com/Leaflet/Leaflet/issues/3332
		if (L.Browser.ielt9) {
			setTimeout(L.bind(done, this, null, tile), 0);
		} else {
			done(null, tile);
		}
	},

	_tileOnError: function (done, tile, e) {
		var errorUrl = this.options.errorTileUrl;
		if (errorUrl && tile.src !== errorUrl) {
			tile.src = errorUrl;
		}
		done(e, tile);
	},

	getTileSize: function () {
		var map = this._map,
		tileSize = L.GridLayer.prototype.getTileSize.call(this),
		zoom = this._tileZoom + this.options.zoomOffset,
		minNativeZoom = this.options.minNativeZoom,
		maxNativeZoom = this.options.maxNativeZoom;

		// decrease tile size when scaling below minNativeZoom
		if (minNativeZoom !== null && zoom < minNativeZoom) {
			return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
		}

		// increase tile size when scaling above maxNativeZoom
		if (maxNativeZoom !== null && zoom > maxNativeZoom) {
			return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
		}

		return tileSize;
	},

	_onTileRemove: function (e) {
		e.tile.onload = null;
	},

	_getZoomForUrl: function () {
		var zoom = this._tileZoom,
		maxZoom = this.options.maxZoom,
		zoomReverse = this.options.zoomReverse,
		zoomOffset = this.options.zoomOffset,
		minNativeZoom = this.options.minNativeZoom,
		maxNativeZoom = this.options.maxNativeZoom;

		if (zoomReverse) {
			zoom = maxZoom - zoom;
		}

		zoom += zoomOffset;

		if (minNativeZoom !== null && zoom < minNativeZoom) {
			return minNativeZoom;
		}

		if (maxNativeZoom !== null && zoom > maxNativeZoom) {
			return maxNativeZoom;
		}

		return zoom;
	},

	_getSubdomain: function (tilePoint) {
		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
		return this.options.subdomains[index];
	},

	// stops loading all tiles in the background layer
	_abortLoading: function () {
		var i, tile;
		for (i in this._tiles) {
			if (this._tiles[i].coords.z !== this._tileZoom) {
				tile = this._tiles[i].el;

				tile.onload = L.Util.falseFn;
				tile.onerror = L.Util.falseFn;

				if (!tile.complete) {
					tile.src = L.Util.emptyImageUrl;
					L.DomUtil.remove(tile);
				}
			}
		}
	}
});


// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
// Instantiates a tile layer object given a `URL template` and optionally an options object.

L.tileLayer = function (url, options) {
	return new L.TileLayer(url, options);
};



/*
 * @class TileLayer.WMS
 * @inherits TileLayer
 * @aka L.TileLayer.WMS
 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
 *
 * @example
 *
 * ```js
 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
 * 	layers: 'nexrad-n0r-900913',
 * 	format: 'image/png',
 * 	transparent: true,
 * 	attribution: "Weather data © 2012 IEM Nexrad"
 * });
 * ```
 */

L.TileLayer.WMS = L.TileLayer.extend({

	// @section
	// @aka TileLayer.WMS options
	// If any custom options not documented here are used, they will be sent to the
	// WMS server as extra parameters in each request URL. This can be useful for
	// [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
	defaultWmsParams: {
		service: 'WMS',
		request: 'GetMap',

		// @option layers: String = ''
		// **(required)** Comma-separated list of WMS layers to show.
		layers: '',

		// @option styles: String = ''
		// Comma-separated list of WMS styles.
		styles: '',

		// @option format: String = 'image/jpeg'
		// WMS image format (use `'image/png'` for layers with transparency).
		format: 'image/jpeg',

		// @option transparent: Boolean = false
		// If `true`, the WMS service will return images with transparency.
		transparent: false,

		// @option version: String = '1.1.1'
		// Version of the WMS service to use
		version: '1.1.1'
	},

	options: {
		// @option crs: CRS = null
		// Coordinate Reference System to use for the WMS requests, defaults to
		// map CRS. Don't change this if you're not sure what it means.
		crs: null,

		// @option uppercase: Boolean = false
		// If `true`, WMS request parameter keys will be uppercase.
		uppercase: false
	},

	initialize: function (url, options) {

		this._url = url;

		var wmsParams = L.extend({}, this.defaultWmsParams);

		// all keys that are not TileLayer options go to WMS params
		for (var i in options) {
			if (!(i in this.options)) {
				wmsParams[i] = options[i];
			}
		}

		options = L.setOptions(this, options);

		wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);

		this.wmsParams = wmsParams;
	},

	onAdd: function (map) {

		this._crs = this.options.crs || map.options.crs;
		this._wmsVersion = parseFloat(this.wmsParams.version);

		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
		this.wmsParams[projectionKey] = this._crs.code;

		L.TileLayer.prototype.onAdd.call(this, map);
	},

	getTileUrl: function (coords) {

		var tileBounds = this._tileCoordsToBounds(coords),
		    nw = this._crs.project(tileBounds.getNorthWest()),
		    se = this._crs.project(tileBounds.getSouthEast()),

		    bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
			    [se.y, nw.x, nw.y, se.x] :
			    [nw.x, se.y, se.x, nw.y]).join(','),

		    url = L.TileLayer.prototype.getTileUrl.call(this, coords);

		return url +
			L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
			(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
	},

	// @method setParams(params: Object, noRedraw?: Boolean): this
	// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
	setParams: function (params, noRedraw) {

		L.extend(this.wmsParams, params);

		if (!noRedraw) {
			this.redraw();
		}

		return this;
	}
});


// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
L.tileLayer.wms = function (url, options) {
	return new L.TileLayer.WMS(url, options);
};



/*
 * @class ImageOverlay
 * @aka L.ImageOverlay
 * @inherits Interactive layer
 *
 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
 *
 * @example
 *
 * ```js
 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
 * 	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
 * ```
 */

L.ImageOverlay = L.Layer.extend({

	// @section
	// @aka ImageOverlay options
	options: {
		// @option opacity: Number = 1.0
		// The opacity of the image overlay.
		opacity: 1,

		// @option alt: String = ''
		// Text for the `alt` attribute of the image (useful for accessibility).
		alt: '',

		// @option interactive: Boolean = false
		// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
		interactive: false,

		// @option crossOrigin: Boolean = false
		// If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
		crossOrigin: false
	},

	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
		this._url = url;
		this._bounds = L.latLngBounds(bounds);

		L.setOptions(this, options);
	},

	onAdd: function () {
		if (!this._image) {
			this._initImage();

			if (this.options.opacity < 1) {
				this._updateOpacity();
			}
		}

		if (this.options.interactive) {
			L.DomUtil.addClass(this._image, 'leaflet-interactive');
			this.addInteractiveTarget(this._image);
		}

		this.getPane().appendChild(this._image);
		this._reset();
	},

	onRemove: function () {
		L.DomUtil.remove(this._image);
		if (this.options.interactive) {
			this.removeInteractiveTarget(this._image);
		}
	},

	// @method setOpacity(opacity: Number): this
	// Sets the opacity of the overlay.
	setOpacity: function (opacity) {
		this.options.opacity = opacity;

		if (this._image) {
			this._updateOpacity();
		}
		return this;
	},

	setStyle: function (styleOpts) {
		if (styleOpts.opacity) {
			this.setOpacity(styleOpts.opacity);
		}
		return this;
	},

	// @method bringToFront(): this
	// Brings the layer to the top of all overlays.
	bringToFront: function () {
		if (this._map) {
			L.DomUtil.toFront(this._image);
		}
		return this;
	},

	// @method bringToBack(): this
	// Brings the layer to the bottom of all overlays.
	bringToBack: function () {
		if (this._map) {
			L.DomUtil.toBack(this._image);
		}
		return this;
	},

	// @method setUrl(url: String): this
	// Changes the URL of the image.
	setUrl: function (url) {
		this._url = url;

		if (this._image) {
			this._image.src = url;
		}
		return this;
	},

	// @method setBounds(bounds: LatLngBounds): this
	// Update the bounds that this ImageOverlay covers
	setBounds: function (bounds) {
		this._bounds = bounds;

		if (this._map) {
			this._reset();
		}
		return this;
	},

	getEvents: function () {
		var events = {
			zoom: this._reset,
			viewreset: this._reset
		};

		if (this._zoomAnimated) {
			events.zoomanim = this._animateZoom;
		}

		return events;
	},

	// @method getBounds(): LatLngBounds
	// Get the bounds that this ImageOverlay covers
	getBounds: function () {
		return this._bounds;
	},

	// @method getElement(): HTMLElement
	// Get the img element that represents the ImageOverlay on the map
	getElement: function () {
		return this._image;
	},

	_initImage: function () {
		var img = this._image = L.DomUtil.create('img',
				'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));

		img.onselectstart = L.Util.falseFn;
		img.onmousemove = L.Util.falseFn;

		img.onload = L.bind(this.fire, this, 'load');

		if (this.options.crossOrigin) {
			img.crossOrigin = '';
		}

		img.src = this._url;
		img.alt = this.options.alt;
	},

	_animateZoom: function (e) {
		var scale = this._map.getZoomScale(e.zoom),
		    offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;

		L.DomUtil.setTransform(this._image, offset, scale);
	},

	_reset: function () {
		var image = this._image,
		    bounds = new L.Bounds(
		        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
		        this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
		    size = bounds.getSize();

		L.DomUtil.setPosition(image, bounds.min);

		image.style.width  = size.x + 'px';
		image.style.height = size.y + 'px';
	},

	_updateOpacity: function () {
		L.DomUtil.setOpacity(this._image, this.options.opacity);
	}
});

// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
// Instantiates an image overlay object given the URL of the image and the
// geographical bounds it is tied to.
L.imageOverlay = function (url, bounds, options) {
	return new L.ImageOverlay(url, bounds, options);
};



/*
 * @class Icon
 * @aka L.Icon
 * @inherits Layer
 *
 * Represents an icon to provide when creating a marker.
 *
 * @example
 *
 * ```js
 * var myIcon = L.icon({
 *     iconUrl: 'my-icon.png',
 *     iconRetinaUrl: 'my-icon@2x.png',
 *     iconSize: [38, 95],
 *     iconAnchor: [22, 94],
 *     popupAnchor: [-3, -76],
 *     shadowUrl: 'my-icon-shadow.png',
 *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
 *     shadowSize: [68, 95],
 *     shadowAnchor: [22, 94]
 * });
 *
 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
 * ```
 *
 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
 *
 */

L.Icon = L.Class.extend({

	/* @section
	 * @aka Icon options
	 *
	 * @option iconUrl: String = null
	 * **(required)** The URL to the icon image (absolute or relative to your script path).
	 *
	 * @option iconRetinaUrl: String = null
	 * The URL to a retina sized version of the icon image (absolute or relative to your
	 * script path). Used for Retina screen devices.
	 *
	 * @option iconSize: Point = null
	 * Size of the icon image in pixels.
	 *
	 * @option iconAnchor: Point = null
	 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
	 * will be aligned so that this point is at the marker's geographical location. Centered
	 * by default if size is specified, also can be set in CSS with negative margins.
	 *
	 * @option popupAnchor: Point = null
	 * The coordinates of the point from which popups will "open", relative to the icon anchor.
	 *
	 * @option shadowUrl: String = null
	 * The URL to the icon shadow image. If not specified, no shadow image will be created.
	 *
	 * @option shadowRetinaUrl: String = null
	 *
	 * @option shadowSize: Point = null
	 * Size of the shadow image in pixels.
	 *
	 * @option shadowAnchor: Point = null
	 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
	 * as iconAnchor if not specified).
	 *
	 * @option className: String = ''
	 * A custom class name to assign to both icon and shadow images. Empty by default.
	 */

	initialize: function (options) {
		L.setOptions(this, options);
	},

	// @method createIcon(oldIcon?: HTMLElement): HTMLElement
	// Called internally when the icon has to be shown, returns a `<img>` HTML element
	// styled according to the options.
	createIcon: function (oldIcon) {
		return this._createIcon('icon', oldIcon);
	},

	// @method createShadow(oldIcon?: HTMLElement): HTMLElement
	// As `createIcon`, but for the shadow beneath it.
	createShadow: function (oldIcon) {
		return this._createIcon('shadow', oldIcon);
	},

	_createIcon: function (name, oldIcon) {
		var src = this._getIconUrl(name);

		if (!src) {
			if (name === 'icon') {
				throw new Error('iconUrl not set in Icon options (see the docs).');
			}
			return null;
		}

		var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
		this._setIconStyles(img, name);

		return img;
	},

	_setIconStyles: function (img, name) {
		var options = this.options;
		var sizeOption = options[name + 'Size'];

		if (typeof sizeOption === 'number') {
			sizeOption = [sizeOption, sizeOption];
		}

		var size = L.point(sizeOption),
		    anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
		            size && size.divideBy(2, true));

		img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');

		if (anchor) {
			img.style.marginLeft = (-anchor.x) + 'px';
			img.style.marginTop  = (-anchor.y) + 'px';
		}

		if (size) {
			img.style.width  = size.x + 'px';
			img.style.height = size.y + 'px';
		}
	},

	_createImg: function (src, el) {
		el = el || document.createElement('img');
		el.src = src;
		return el;
	},

	_getIconUrl: function (name) {
		return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
	}
});


// @factory L.icon(options: Icon options)
// Creates an icon instance with the given options.
L.icon = function (options) {
	return new L.Icon(options);
};



/*
 * @miniclass Icon.Default (Icon)
 * @aka L.Icon.Default
 * @section
 *
 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
 * no icon is specified. Points to the blue marker image distributed with Leaflet
 * releases.
 *
 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
 * (which is a set of `Icon options`).
 *
 * If you want to _completely_ replace the default icon, override the
 * `L.Marker.prototype.options.icon` with your own icon instead.
 */

L.Icon.Default = L.Icon.extend({

	options: {
		iconUrl:       'marker-icon.png',
		iconRetinaUrl: 'marker-icon-2x.png',
		shadowUrl:     'marker-shadow.png',
		iconSize:    [25, 41],
		iconAnchor:  [12, 41],
		popupAnchor: [1, -34],
		tooltipAnchor: [16, -28],
		shadowSize:  [41, 41]
	},

	_getIconUrl: function (name) {
		if (!L.Icon.Default.imagePath) {	// Deprecated, backwards-compatibility only
			L.Icon.Default.imagePath = this._detectIconPath();
		}

		// @option imagePath: String
		// `L.Icon.Default` will try to auto-detect the absolute location of the
		// blue icon images. If you are placing these images in a non-standard
		// way, set this option to point to the right absolute path.
		return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
	},

	_detectIconPath: function () {
		var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
		var path = L.DomUtil.getStyle(el, 'background-image') ||
		           L.DomUtil.getStyle(el, 'backgroundImage');	// IE8

		document.body.removeChild(el);

		return path.indexOf('url') === 0 ?
			path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
	}
});



/*
 * @class Marker
 * @inherits Interactive layer
 * @aka L.Marker
 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
 *
 * @example
 *
 * ```js
 * L.marker([50.5, 30.5]).addTo(map);
 * ```
 */

L.Marker = L.Layer.extend({

	// @section
	// @aka Marker options
	options: {
		// @option icon: Icon = *
		// Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used.
		icon: new L.Icon.Default(),

		// Option inherited from "Interactive layer" abstract class
		interactive: true,

		// @option draggable: Boolean = false
		// Whether the marker is draggable with mouse/touch or not.
		draggable: false,

		// @option keyboard: Boolean = true
		// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
		keyboard: true,

		// @option title: String = ''
		// Text for the browser tooltip that appear on marker hover (no tooltip by default).
		title: '',

		// @option alt: String = ''
		// Text for the `alt` attribute of the icon image (useful for accessibility).
		alt: '',

		// @option zIndexOffset: Number = 0
		// By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
		zIndexOffset: 0,

		// @option opacity: Number = 1.0
		// The opacity of the marker.
		opacity: 1,

		// @option riseOnHover: Boolean = false
		// If `true`, the marker will get on top of others when you hover the mouse over it.
		riseOnHover: false,

		// @option riseOffset: Number = 250
		// The z-index offset used for the `riseOnHover` feature.
		riseOffset: 250,

		// @option pane: String = 'markerPane'
		// `Map pane` where the markers icon will be added.
		pane: 'markerPane',

		// FIXME: shadowPane is no longer a valid option
		nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
	},

	/* @section
	 *
	 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
	 */

	initialize: function (latlng, options) {
		L.setOptions(this, options);
		this._latlng = L.latLng(latlng);
	},

	onAdd: function (map) {
		this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;

		if (this._zoomAnimated) {
			map.on('zoomanim', this._animateZoom, this);
		}

		this._initIcon();
		this.update();
	},

	onRemove: function (map) {
		if (this.dragging && this.dragging.enabled()) {
			this.options.draggable = true;
			this.dragging.removeHooks();
		}

		if (this._zoomAnimated) {
			map.off('zoomanim', this._animateZoom, this);
		}

		this._removeIcon();
		this._removeShadow();
	},

	getEvents: function () {
		return {
			zoom: this.update,
			viewreset: this.update
		};
	},

	// @method getLatLng: LatLng
	// Returns the current geographical position of the marker.
	getLatLng: function () {
		return this._latlng;
	},

	// @method setLatLng(latlng: LatLng): this
	// Changes the marker position to the given point.
	setLatLng: function (latlng) {
		var oldLatLng = this._latlng;
		this._latlng = L.latLng(latlng);
		this.update();

		// @event move: Event
		// Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
	},

	// @method setZIndexOffset(offset: Number): this
	// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
	setZIndexOffset: function (offset) {
		this.options.zIndexOffset = offset;
		return this.update();
	},

	// @method setIcon(icon: Icon): this
	// Changes the marker icon.
	setIcon: function (icon) {

		this.options.icon = icon;

		if (this._map) {
			this._initIcon();
			this.update();
		}

		if (this._popup) {
			this.bindPopup(this._popup, this._popup.options);
		}

		return this;
	},

	getElement: function () {
		return this._icon;
	},

	update: function () {

		if (this._icon) {
			var pos = this._map.latLngToLayerPoint(this._latlng).round();
			this._setPos(pos);
		}

		return this;
	},

	_initIcon: function () {
		var options = this.options,
		    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

		var icon = options.icon.createIcon(this._icon),
		    addIcon = false;

		// if we're not reusing the icon, remove the old one and init new one
		if (icon !== this._icon) {
			if (this._icon) {
				this._removeIcon();
			}
			addIcon = true;

			if (options.title) {
				icon.title = options.title;
			}
			if (options.alt) {
				icon.alt = options.alt;
			}
		}

		L.DomUtil.addClass(icon, classToAdd);

		if (options.keyboard) {
			icon.tabIndex = '0';
		}

		this._icon = icon;

		if (options.riseOnHover) {
			this.on({
				mouseover: this._bringToFront,
				mouseout: this._resetZIndex
			});
		}

		var newShadow = options.icon.createShadow(this._shadow),
		    addShadow = false;

		if (newShadow !== this._shadow) {
			this._removeShadow();
			addShadow = true;
		}

		if (newShadow) {
			L.DomUtil.addClass(newShadow, classToAdd);
			newShadow.alt = '';
		}
		this._shadow = newShadow;


		if (options.opacity < 1) {
			this._updateOpacity();
		}


		if (addIcon) {
			this.getPane().appendChild(this._icon);
		}
		this._initInteraction();
		if (newShadow && addShadow) {
			this.getPane('shadowPane').appendChild(this._shadow);
		}
	},

	_removeIcon: function () {
		if (this.options.riseOnHover) {
			this.off({
				mouseover: this._bringToFront,
				mouseout: this._resetZIndex
			});
		}

		L.DomUtil.remove(this._icon);
		this.removeInteractiveTarget(this._icon);

		this._icon = null;
	},

	_removeShadow: function () {
		if (this._shadow) {
			L.DomUtil.remove(this._shadow);
		}
		this._shadow = null;
	},

	_setPos: function (pos) {
		L.DomUtil.setPosition(this._icon, pos);

		if (this._shadow) {
			L.DomUtil.setPosition(this._shadow, pos);
		}

		this._zIndex = pos.y + this.options.zIndexOffset;

		this._resetZIndex();
	},

	_updateZIndex: function (offset) {
		this._icon.style.zIndex = this._zIndex + offset;
	},

	_animateZoom: function (opt) {
		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();

		this._setPos(pos);
	},

	_initInteraction: function () {

		if (!this.options.interactive) { return; }

		L.DomUtil.addClass(this._icon, 'leaflet-interactive');

		this.addInteractiveTarget(this._icon);

		if (L.Handler.MarkerDrag) {
			var draggable = this.options.draggable;
			if (this.dragging) {
				draggable = this.dragging.enabled();
				this.dragging.disable();
			}

			this.dragging = new L.Handler.MarkerDrag(this);

			if (draggable) {
				this.dragging.enable();
			}
		}
	},

	// @method setOpacity(opacity: Number): this
	// Changes the opacity of the marker.
	setOpacity: function (opacity) {
		this.options.opacity = opacity;
		if (this._map) {
			this._updateOpacity();
		}

		return this;
	},

	_updateOpacity: function () {
		var opacity = this.options.opacity;

		L.DomUtil.setOpacity(this._icon, opacity);

		if (this._shadow) {
			L.DomUtil.setOpacity(this._shadow, opacity);
		}
	},

	_bringToFront: function () {
		this._updateZIndex(this.options.riseOffset);
	},

	_resetZIndex: function () {
		this._updateZIndex(0);
	},

	_getPopupAnchor: function () {
		return this.options.icon.options.popupAnchor || [0, 0];
	},

	_getTooltipAnchor: function () {
		return this.options.icon.options.tooltipAnchor || [0, 0];
	}
});


// factory L.marker(latlng: LatLng, options? : Marker options)

// @factory L.marker(latlng: LatLng, options? : Marker options)
// Instantiates a Marker object given a geographical point and optionally an options object.
L.marker = function (latlng, options) {
	return new L.Marker(latlng, options);
};



/*
 * @class DivIcon
 * @aka L.DivIcon
 * @inherits Icon
 *
 * Represents a lightweight icon for markers that uses a simple `<div>`
 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
 *
 * @example
 * ```js
 * var myIcon = L.divIcon({className: 'my-div-icon'});
 * // you can set .my-div-icon styles in CSS
 *
 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
 * ```
 *
 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
 */

L.DivIcon = L.Icon.extend({
	options: {
		// @section
		// @aka DivIcon options
		iconSize: [12, 12], // also can be set through CSS

		// iconAnchor: (Point),
		// popupAnchor: (Point),

		// @option html: String = ''
		// Custom HTML code to put inside the div element, empty by default.
		html: false,

		// @option bgPos: Point = [0, 0]
		// Optional relative position of the background, in pixels
		bgPos: null,

		className: 'leaflet-div-icon'
	},

	createIcon: function (oldIcon) {
		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
		    options = this.options;

		div.innerHTML = options.html !== false ? options.html : '';

		if (options.bgPos) {
			var bgPos = L.point(options.bgPos);
			div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
		}
		this._setIconStyles(div, 'icon');

		return div;
	},

	createShadow: function () {
		return null;
	}
});

// @factory L.divIcon(options: DivIcon options)
// Creates a `DivIcon` instance with the given options.
L.divIcon = function (options) {
	return new L.DivIcon(options);
};



/*
 * @class DivOverlay
 * @inherits Layer
 * @aka L.DivOverlay
 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
 */

// @namespace DivOverlay
L.DivOverlay = L.Layer.extend({

	// @section
	// @aka DivOverlay options
	options: {
		// @option offset: Point = Point(0, 7)
		// The offset of the popup position. Useful to control the anchor
		// of the popup when opening it on some overlays.
		offset: [0, 7],

		// @option className: String = ''
		// A custom CSS class name to assign to the popup.
		className: '',

		// @option pane: String = 'popupPane'
		// `Map pane` where the popup will be added.
		pane: 'popupPane'
	},

	initialize: function (options, source) {
		L.setOptions(this, options);

		this._source = source;
	},

	onAdd: function (map) {
		this._zoomAnimated = map._zoomAnimated;

		if (!this._container) {
			this._initLayout();
		}

		if (map._fadeAnimated) {
			L.DomUtil.setOpacity(this._container, 0);
		}

		clearTimeout(this._removeTimeout);
		this.getPane().appendChild(this._container);
		this.update();

		if (map._fadeAnimated) {
			L.DomUtil.setOpacity(this._container, 1);
		}

		this.bringToFront();
	},

	onRemove: function (map) {
		if (map._fadeAnimated) {
			L.DomUtil.setOpacity(this._container, 0);
			this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
		} else {
			L.DomUtil.remove(this._container);
		}
	},

	// @namespace Popup
	// @method getLatLng: LatLng
	// Returns the geographical point of popup.
	getLatLng: function () {
		return this._latlng;
	},

	// @method setLatLng(latlng: LatLng): this
	// Sets the geographical point where the popup will open.
	setLatLng: function (latlng) {
		this._latlng = L.latLng(latlng);
		if (this._map) {
			this._updatePosition();
			this._adjustPan();
		}
		return this;
	},

	// @method getContent: String|HTMLElement
	// Returns the content of the popup.
	getContent: function () {
		return this._content;
	},

	// @method setContent(htmlContent: String|HTMLElement|Function): this
	// Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
	setContent: function (content) {
		this._content = content;
		this.update();
		return this;
	},

	// @method getElement: String|HTMLElement
	// Alias for [getContent()](#popup-getcontent)
	getElement: function () {
		return this._container;
	},

	// @method update: null
	// Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
	update: function () {
		if (!this._map) { return; }

		this._container.style.visibility = 'hidden';

		this._updateContent();
		this._updateLayout();
		this._updatePosition();

		this._container.style.visibility = '';

		this._adjustPan();
	},

	getEvents: function () {
		var events = {
			zoom: this._updatePosition,
			viewreset: this._updatePosition
		};

		if (this._zoomAnimated) {
			events.zoomanim = this._animateZoom;
		}
		return events;
	},

	// @method isOpen: Boolean
	// Returns `true` when the popup is visible on the map.
	isOpen: function () {
		return !!this._map && this._map.hasLayer(this);
	},

	// @method bringToFront: this
	// Brings this popup in front of other popups (in the same map pane).
	bringToFront: function () {
		if (this._map) {
			L.DomUtil.toFront(this._container);
		}
		return this;
	},

	// @method bringToBack: this
	// Brings this popup to the back of other popups (in the same map pane).
	bringToBack: function () {
		if (this._map) {
			L.DomUtil.toBack(this._container);
		}
		return this;
	},

	_updateContent: function () {
		if (!this._content) { return; }

		var node = this._contentNode;
		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;

		if (typeof content === 'string') {
			node.innerHTML = content;
		} else {
			while (node.hasChildNodes()) {
				node.removeChild(node.firstChild);
			}
			node.appendChild(content);
		}
		this.fire('contentupdate');
	},

	_updatePosition: function () {
		if (!this._map) { return; }

		var pos = this._map.latLngToLayerPoint(this._latlng),
		    offset = L.point(this.options.offset),
		    anchor = this._getAnchor();

		if (this._zoomAnimated) {
			L.DomUtil.setPosition(this._container, pos.add(anchor));
		} else {
			offset = offset.add(pos).add(anchor);
		}

		var bottom = this._containerBottom = -offset.y,
		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;

		// bottom position the popup in case the height of the popup changes (images loading etc)
		this._container.style.bottom = bottom + 'px';
		this._container.style.left = left + 'px';
	},

	_getAnchor: function () {
		return [0, 0];
	}

});



/*
 * @class Popup
 * @inherits DivOverlay
 * @aka L.Popup
 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
 * open popups while making sure that only one popup is open at one time
 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
 *
 * @example
 *
 * If you want to just bind a popup to marker click and then open it, it's really easy:
 *
 * ```js
 * marker.bindPopup(popupContent).openPopup();
 * ```
 * Path overlays like polylines also have a `bindPopup` method.
 * Here's a more complicated way to open a popup on a map:
 *
 * ```js
 * var popup = L.popup()
 * 	.setLatLng(latlng)
 * 	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
 * 	.openOn(map);
 * ```
 */


// @namespace Popup
L.Popup = L.DivOverlay.extend({

	// @section
	// @aka Popup options
	options: {
		// @option maxWidth: Number = 300
		// Max width of the popup, in pixels.
		maxWidth: 300,

		// @option minWidth: Number = 50
		// Min width of the popup, in pixels.
		minWidth: 50,

		// @option maxHeight: Number = null
		// If set, creates a scrollable container of the given height
		// inside a popup if its content exceeds it.
		maxHeight: null,

		// @option autoPan: Boolean = true
		// Set it to `false` if you don't want the map to do panning animation
		// to fit the opened popup.
		autoPan: true,

		// @option autoPanPaddingTopLeft: Point = null
		// The margin between the popup and the top left corner of the map
		// view after autopanning was performed.
		autoPanPaddingTopLeft: null,

		// @option autoPanPaddingBottomRight: Point = null
		// The margin between the popup and the bottom right corner of the map
		// view after autopanning was performed.
		autoPanPaddingBottomRight: null,

		// @option autoPanPadding: Point = Point(5, 5)
		// Equivalent of setting both top left and bottom right autopan padding to the same value.
		autoPanPadding: [5, 5],

		// @option keepInView: Boolean = false
		// Set it to `true` if you want to prevent users from panning the popup
		// off of the screen while it is open.
		keepInView: false,

		// @option closeButton: Boolean = true
		// Controls the presence of a close button in the popup.
		closeButton: true,

		// @option autoClose: Boolean = true
		// Set it to `false` if you want to override the default behavior of
		// the popup closing when user clicks the map (set globally by
		// the Map's [closePopupOnClick](#map-closepopuponclick) option).
		autoClose: true,

		// @option className: String = ''
		// A custom CSS class name to assign to the popup.
		className: ''
	},

	// @namespace Popup
	// @method openOn(map: Map): this
	// Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
	openOn: function (map) {
		map.openPopup(this);
		return this;
	},

	onAdd: function (map) {
		L.DivOverlay.prototype.onAdd.call(this, map);

		// @namespace Map
		// @section Popup events
		// @event popupopen: PopupEvent
		// Fired when a popup is opened in the map
		map.fire('popupopen', {popup: this});

		if (this._source) {
			// @namespace Layer
			// @section Popup events
			// @event popupopen: PopupEvent
			// Fired when a popup bound to this layer is opened
			this._source.fire('popupopen', {popup: this}, true);
			// For non-path layers, we toggle the popup when clicking
			// again the layer, so prevent the map to reopen it.
			if (!(this._source instanceof L.Path)) {
				this._source.on('preclick', L.DomEvent.stopPropagation);
			}
		}
	},

	onRemove: function (map) {
		L.DivOverlay.prototype.onRemove.call(this, map);

		// @namespace Map
		// @section Popup events
		// @event popupclose: PopupEvent
		// Fired when a popup in the map is closed
		map.fire('popupclose', {popup: this});

		if (this._source) {
			// @namespace Layer
			// @section Popup events
			// @event popupclose: PopupEvent
			// Fired when a popup bound to this layer is closed
			this._source.fire('popupclose', {popup: this}, true);
			if (!(this._source instanceof L.Path)) {
				this._source.off('preclick', L.DomEvent.stopPropagation);
			}
		}
	},

	getEvents: function () {
		var events = L.DivOverlay.prototype.getEvents.call(this);

		if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
			events.preclick = this._close;
		}

		if (this.options.keepInView) {
			events.moveend = this._adjustPan;
		}

		return events;
	},

	_close: function () {
		if (this._map) {
			this._map.closePopup(this);
		}
	},

	_initLayout: function () {
		var prefix = 'leaflet-popup',
		    container = this._container = L.DomUtil.create('div',
			prefix + ' ' + (this.options.className || '') +
			' leaflet-zoom-animated');

		if (this.options.closeButton) {
			var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
			closeButton.href = '#close';
			closeButton.innerHTML = '&#215;';

			L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
		}

		var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
		this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);

		L.DomEvent
			.disableClickPropagation(wrapper)
			.disableScrollPropagation(this._contentNode)
			.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);

		this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
		this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
	},

	_updateLayout: function () {
		var container = this._contentNode,
		    style = container.style;

		style.width = '';
		style.whiteSpace = 'nowrap';

		var width = container.offsetWidth;
		width = Math.min(width, this.options.maxWidth);
		width = Math.max(width, this.options.minWidth);

		style.width = (width + 1) + 'px';
		style.whiteSpace = '';

		style.height = '';

		var height = container.offsetHeight,
		    maxHeight = this.options.maxHeight,
		    scrolledClass = 'leaflet-popup-scrolled';

		if (maxHeight && height > maxHeight) {
			style.height = maxHeight + 'px';
			L.DomUtil.addClass(container, scrolledClass);
		} else {
			L.DomUtil.removeClass(container, scrolledClass);
		}

		this._containerWidth = this._container.offsetWidth;
	},

	_animateZoom: function (e) {
		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
		    anchor = this._getAnchor();
		L.DomUtil.setPosition(this._container, pos.add(anchor));
	},

	_adjustPan: function () {
		if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }

		var map = this._map,
		    marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
		    containerHeight = this._container.offsetHeight + marginBottom,
		    containerWidth = this._containerWidth,
		    layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);

		layerPos._add(L.DomUtil.getPosition(this._container));

		var containerPos = map.layerPointToContainerPoint(layerPos),
		    padding = L.point(this.options.autoPanPadding),
		    paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
		    paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
		    size = map.getSize(),
		    dx = 0,
		    dy = 0;

		if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
			dx = containerPos.x + containerWidth - size.x + paddingBR.x;
		}
		if (containerPos.x - dx - paddingTL.x < 0) { // left
			dx = containerPos.x - paddingTL.x;
		}
		if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
			dy = containerPos.y + containerHeight - size.y + paddingBR.y;
		}
		if (containerPos.y - dy - paddingTL.y < 0) { // top
			dy = containerPos.y - paddingTL.y;
		}

		// @namespace Map
		// @section Popup events
		// @event autopanstart: Event
		// Fired when the map starts autopanning when opening a popup.
		if (dx || dy) {
			map
			    .fire('autopanstart')
			    .panBy([dx, dy]);
		}
	},

	_onCloseButtonClick: function (e) {
		this._close();
		L.DomEvent.stop(e);
	},

	_getAnchor: function () {
		// Where should we anchor the popup on the source layer?
		return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
	}

});

// @namespace Popup
// @factory L.popup(options?: Popup options, source?: Layer)
// Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
L.popup = function (options, source) {
	return new L.Popup(options, source);
};


/* @namespace Map
 * @section Interaction Options
 * @option closePopupOnClick: Boolean = true
 * Set it to `false` if you don't want popups to close when user clicks the map.
 */
L.Map.mergeOptions({
	closePopupOnClick: true
});


// @namespace Map
// @section Methods for Layers and Controls
L.Map.include({
	// @method openPopup(popup: Popup): this
	// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
	// @alternative
	// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
	// Creates a popup with the specified content and options and opens it in the given point on a map.
	openPopup: function (popup, latlng, options) {
		if (!(popup instanceof L.Popup)) {
			popup = new L.Popup(options).setContent(popup);
		}

		if (latlng) {
			popup.setLatLng(latlng);
		}

		if (this.hasLayer(popup)) {
			return this;
		}

		if (this._popup && this._popup.options.autoClose) {
			this.closePopup();
		}

		this._popup = popup;
		return this.addLayer(popup);
	},

	// @method closePopup(popup?: Popup): this
	// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
	closePopup: function (popup) {
		if (!popup || popup === this._popup) {
			popup = this._popup;
			this._popup = null;
		}
		if (popup) {
			this.removeLayer(popup);
		}
		return this;
	}
});

/*
 * @namespace Layer
 * @section Popup methods example
 *
 * All layers share a set of methods convenient for binding popups to it.
 *
 * ```js
 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
 * layer.openPopup();
 * layer.closePopup();
 * ```
 *
 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
 */

// @section Popup methods
L.Layer.include({

	// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
	// Binds a popup to the layer with the passed `content` and sets up the
	// neccessary event listeners. If a `Function` is passed it will receive
	// the layer as the first argument and should return a `String` or `HTMLElement`.
	bindPopup: function (content, options) {

		if (content instanceof L.Popup) {
			L.setOptions(content, options);
			this._popup = content;
			content._source = this;
		} else {
			if (!this._popup || options) {
				this._popup = new L.Popup(options, this);
			}
			this._popup.setContent(content);
		}

		if (!this._popupHandlersAdded) {
			this.on({
				click: this._openPopup,
				remove: this.closePopup,
				move: this._movePopup
			});
			this._popupHandlersAdded = true;
		}

		return this;
	},

	// @method unbindPopup(): this
	// Removes the popup previously bound with `bindPopup`.
	unbindPopup: function () {
		if (this._popup) {
			this.off({
				click: this._openPopup,
				remove: this.closePopup,
				move: this._movePopup
			});
			this._popupHandlersAdded = false;
			this._popup = null;
		}
		return this;
	},

	// @method openPopup(latlng?: LatLng): this
	// Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
	openPopup: function (layer, latlng) {
		if (!(layer instanceof L.Layer)) {
			latlng = layer;
			layer = this;
		}

		if (layer instanceof L.FeatureGroup) {
			for (var id in this._layers) {
				layer = this._layers[id];
				break;
			}
		}

		if (!latlng) {
			latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
		}

		if (this._popup && this._map) {
			// set popup source to this layer
			this._popup._source = layer;

			// update the popup (content, layout, ect...)
			this._popup.update();

			// open the popup on the map
			this._map.openPopup(this._popup, latlng);
		}

		return this;
	},

	// @method closePopup(): this
	// Closes the popup bound to this layer if it is open.
	closePopup: function () {
		if (this._popup) {
			this._popup._close();
		}
		return this;
	},

	// @method togglePopup(): this
	// Opens or closes the popup bound to this layer depending on its current state.
	togglePopup: function (target) {
		if (this._popup) {
			if (this._popup._map) {
				this.closePopup();
			} else {
				this.openPopup(target);
			}
		}
		return this;
	},

	// @method isPopupOpen(): boolean
	// Returns `true` if the popup bound to this layer is currently open.
	isPopupOpen: function () {
		return (this._popup ? this._popup.isOpen() : false);
	},

	// @method setPopupContent(content: String|HTMLElement|Popup): this
	// Sets the content of the popup bound to this layer.
	setPopupContent: function (content) {
		if (this._popup) {
			this._popup.setContent(content);
		}
		return this;
	},

	// @method getPopup(): Popup
	// Returns the popup bound to this layer.
	getPopup: function () {
		return this._popup;
	},

	_openPopup: function (e) {
		var layer = e.layer || e.target;

		if (!this._popup) {
			return;
		}

		if (!this._map) {
			return;
		}

		// prevent map click
		L.DomEvent.stop(e);

		// if this inherits from Path its a vector and we can just
		// open the popup at the new location
		if (layer instanceof L.Path) {
			this.openPopup(e.layer || e.target, e.latlng);
			return;
		}

		// otherwise treat it like a marker and figure out
		// if we should toggle it open/closed
		if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
			this.closePopup();
		} else {
			this.openPopup(layer, e.latlng);
		}
	},

	_movePopup: function (e) {
		this._popup.setLatLng(e.latlng);
	}
});



/*
 * @class Tooltip
 * @inherits DivOverlay
 * @aka L.Tooltip
 * Used to display small texts on top of map layers.
 *
 * @example
 *
 * ```js
 * marker.bindTooltip("my tooltip text").openTooltip();
 * ```
 * Note about tooltip offset. Leaflet takes two options in consideration
 * for computing tooltip offseting:
 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
 *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
 *   move it to the bottom. Negatives will move to the left and top.
 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
 *   should adapt this value if you use a custom icon.
 */


// @namespace Tooltip
L.Tooltip = L.DivOverlay.extend({

	// @section
	// @aka Tooltip options
	options: {
		// @option pane: String = 'tooltipPane'
		// `Map pane` where the tooltip will be added.
		pane: 'tooltipPane',

		// @option offset: Point = Point(0, 0)
		// Optional offset of the tooltip position.
		offset: [0, 0],

		// @option direction: String = 'auto'
		// Direction where to open the tooltip. Possible values are: `right`, `left`,
		// `top`, `bottom`, `center`, `auto`.
		// `auto` will dynamicaly switch between `right` and `left` according to the tooltip
		// position on the map.
		direction: 'auto',

		// @option permanent: Boolean = false
		// Whether to open the tooltip permanently or only on mouseover.
		permanent: false,

		// @option sticky: Boolean = false
		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
		sticky: false,

		// @option interactive: Boolean = false
		// If true, the tooltip will listen to the feature events.
		interactive: false,

		// @option opacity: Number = 0.9
		// Tooltip container opacity.
		opacity: 0.9
	},

	onAdd: function (map) {
		L.DivOverlay.prototype.onAdd.call(this, map);
		this.setOpacity(this.options.opacity);

		// @namespace Map
		// @section Tooltip events
		// @event tooltipopen: TooltipEvent
		// Fired when a tooltip is opened in the map.
		map.fire('tooltipopen', {tooltip: this});

		if (this._source) {
			// @namespace Layer
			// @section Tooltip events
			// @event tooltipopen: TooltipEvent
			// Fired when a tooltip bound to this layer is opened.
			this._source.fire('tooltipopen', {tooltip: this}, true);
		}
	},

	onRemove: function (map) {
		L.DivOverlay.prototype.onRemove.call(this, map);

		// @namespace Map
		// @section Tooltip events
		// @event tooltipclose: TooltipEvent
		// Fired when a tooltip in the map is closed.
		map.fire('tooltipclose', {tooltip: this});

		if (this._source) {
			// @namespace Layer
			// @section Tooltip events
			// @event tooltipclose: TooltipEvent
			// Fired when a tooltip bound to this layer is closed.
			this._source.fire('tooltipclose', {tooltip: this}, true);
		}
	},

	getEvents: function () {
		var events = L.DivOverlay.prototype.getEvents.call(this);

		if (L.Browser.touch && !this.options.permanent) {
			events.preclick = this._close;
		}

		return events;
	},

	_close: function () {
		if (this._map) {
			this._map.closeTooltip(this);
		}
	},

	_initLayout: function () {
		var prefix = 'leaflet-tooltip',
		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

		this._contentNode = this._container = L.DomUtil.create('div', className);
	},

	_updateLayout: function () {},

	_adjustPan: function () {},

	_setPosition: function (pos) {
		var map = this._map,
		    container = this._container,
		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
		    tooltipPoint = map.layerPointToContainerPoint(pos),
		    direction = this.options.direction,
		    tooltipWidth = container.offsetWidth,
		    tooltipHeight = container.offsetHeight,
		    offset = L.point(this.options.offset),
		    anchor = this._getAnchor();

		if (direction === 'top') {
			pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
		} else if (direction === 'bottom') {
			pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
		} else if (direction === 'center') {
			pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
		} else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
			direction = 'right';
			pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
		} else {
			direction = 'left';
			pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
		}

		L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
		L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
		L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
		L.DomUtil.setPosition(container, pos);
	},

	_updatePosition: function () {
		var pos = this._map.latLngToLayerPoint(this._latlng);
		this._setPosition(pos);
	},

	setOpacity: function (opacity) {
		this.options.opacity = opacity;

		if (this._container) {
			L.DomUtil.setOpacity(this._container, opacity);
		}
	},

	_animateZoom: function (e) {
		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
		this._setPosition(pos);
	},

	_getAnchor: function () {
		// Where should we anchor the tooltip on the source layer?
		return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
	}

});

// @namespace Tooltip
// @factory L.tooltip(options?: Tooltip options, source?: Layer)
// Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
L.tooltip = function (options, source) {
	return new L.Tooltip(options, source);
};

// @namespace Map
// @section Methods for Layers and Controls
L.Map.include({

	// @method openTooltip(tooltip: Tooltip): this
	// Opens the specified tooltip.
	// @alternative
	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
	// Creates a tooltip with the specified content and options and open it.
	openTooltip: function (tooltip, latlng, options) {
		if (!(tooltip instanceof L.Tooltip)) {
			tooltip = new L.Tooltip(options).setContent(tooltip);
		}

		if (latlng) {
			tooltip.setLatLng(latlng);
		}

		if (this.hasLayer(tooltip)) {
			return this;
		}

		return this.addLayer(tooltip);
	},

	// @method closeTooltip(tooltip?: Tooltip): this
	// Closes the tooltip given as parameter.
	closeTooltip: function (tooltip) {
		if (tooltip) {
			this.removeLayer(tooltip);
		}
		return this;
	}

});

/*
 * @namespace Layer
 * @section Tooltip methods example
 *
 * All layers share a set of methods convenient for binding tooltips to it.
 *
 * ```js
 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
 * layer.openTooltip();
 * layer.closeTooltip();
 * ```
 */

// @section Tooltip methods
L.Layer.include({

	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
	// Binds a tooltip to the layer with the passed `content` and sets up the
	// neccessary event listeners. If a `Function` is passed it will receive
	// the layer as the first argument and should return a `String` or `HTMLElement`.
	bindTooltip: function (content, options) {

		if (content instanceof L.Tooltip) {
			L.setOptions(content, options);
			this._tooltip = content;
			content._source = this;
		} else {
			if (!this._tooltip || options) {
				this._tooltip = L.tooltip(options, this);
			}
			this._tooltip.setContent(content);

		}

		this._initTooltipInteractions();

		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
			this.openTooltip();
		}

		return this;
	},

	// @method unbindTooltip(): this
	// Removes the tooltip previously bound with `bindTooltip`.
	unbindTooltip: function () {
		if (this._tooltip) {
			this._initTooltipInteractions(true);
			this.closeTooltip();
			this._tooltip = null;
		}
		return this;
	},

	_initTooltipInteractions: function (remove) {
		if (!remove && this._tooltipHandlersAdded) { return; }
		var onOff = remove ? 'off' : 'on',
		    events = {
			remove: this.closeTooltip,
			move: this._moveTooltip
		    };
		if (!this._tooltip.options.permanent) {
			events.mouseover = this._openTooltip;
			events.mouseout = this.closeTooltip;
			if (this._tooltip.options.sticky) {
				events.mousemove = this._moveTooltip;
			}
			if (L.Browser.touch) {
				events.click = this._openTooltip;
			}
		} else {
			events.add = this._openTooltip;
		}
		this[onOff](events);
		this._tooltipHandlersAdded = !remove;
	},

	// @method openTooltip(latlng?: LatLng): this
	// Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
	openTooltip: function (layer, latlng) {
		if (!(layer instanceof L.Layer)) {
			latlng = layer;
			layer = this;
		}

		if (layer instanceof L.FeatureGroup) {
			for (var id in this._layers) {
				layer = this._layers[id];
				break;
			}
		}

		if (!latlng) {
			latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
		}

		if (this._tooltip && this._map) {

			// set tooltip source to this layer
			this._tooltip._source = layer;

			// update the tooltip (content, layout, ect...)
			this._tooltip.update();

			// open the tooltip on the map
			this._map.openTooltip(this._tooltip, latlng);

			// Tooltip container may not be defined if not permanent and never
			// opened.
			if (this._tooltip.options.interactive && this._tooltip._container) {
				L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
				this.addInteractiveTarget(this._tooltip._container);
			}
		}

		return this;
	},

	// @method closeTooltip(): this
	// Closes the tooltip bound to this layer if it is open.
	closeTooltip: function () {
		if (this._tooltip) {
			this._tooltip._close();
			if (this._tooltip.options.interactive && this._tooltip._container) {
				L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
				this.removeInteractiveTarget(this._tooltip._container);
			}
		}
		return this;
	},

	// @method toggleTooltip(): this
	// Opens or closes the tooltip bound to this layer depending on its current state.
	toggleTooltip: function (target) {
		if (this._tooltip) {
			if (this._tooltip._map) {
				this.closeTooltip();
			} else {
				this.openTooltip(target);
			}
		}
		return this;
	},

	// @method isTooltipOpen(): boolean
	// Returns `true` if the tooltip bound to this layer is currently open.
	isTooltipOpen: function () {
		return this._tooltip.isOpen();
	},

	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
	// Sets the content of the tooltip bound to this layer.
	setTooltipContent: function (content) {
		if (this._tooltip) {
			this._tooltip.setContent(content);
		}
		return this;
	},

	// @method getTooltip(): Tooltip
	// Returns the tooltip bound to this layer.
	getTooltip: function () {
		return this._tooltip;
	},

	_openTooltip: function (e) {
		var layer = e.layer || e.target;

		if (!this._tooltip || !this._map) {
			return;
		}
		this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
	},

	_moveTooltip: function (e) {
		var latlng = e.latlng, containerPoint, layerPoint;
		if (this._tooltip.options.sticky && e.originalEvent) {
			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
			latlng = this._map.layerPointToLatLng(layerPoint);
		}
		this._tooltip.setLatLng(latlng);
	}
});



/*
 * @class LayerGroup
 * @aka L.LayerGroup
 * @inherits Layer
 *
 * Used to group several layers and handle them as one. If you add it to the map,
 * any layers added or removed from the group will be added/removed on the map as
 * well. Extends `Layer`.
 *
 * @example
 *
 * ```js
 * L.layerGroup([marker1, marker2])
 * 	.addLayer(polyline)
 * 	.addTo(map);
 * ```
 */

L.LayerGroup = L.Layer.extend({

	initialize: function (layers) {
		this._layers = {};

		var i, len;

		if (layers) {
			for (i = 0, len = layers.length; i < len; i++) {
				this.addLayer(layers[i]);
			}
		}
	},

	// @method addLayer(layer: Layer): this
	// Adds the given layer to the group.
	addLayer: function (layer) {
		var id = this.getLayerId(layer);

		this._layers[id] = layer;

		if (this._map) {
			this._map.addLayer(layer);
		}

		return this;
	},

	// @method removeLayer(layer: Layer): this
	// Removes the given layer from the group.
	// @alternative
	// @method removeLayer(id: Number): this
	// Removes the layer with the given internal ID from the group.
	removeLayer: function (layer) {
		var id = layer in this._layers ? layer : this.getLayerId(layer);

		if (this._map && this._layers[id]) {
			this._map.removeLayer(this._layers[id]);
		}

		delete this._layers[id];

		return this;
	},

	// @method hasLayer(layer: Layer): Boolean
	// Returns `true` if the given layer is currently added to the group.
	hasLayer: function (layer) {
		return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
	},

	// @method clearLayers(): this
	// Removes all the layers from the group.
	clearLayers: function () {
		for (var i in this._layers) {
			this.removeLayer(this._layers[i]);
		}
		return this;
	},

	// @method invoke(methodName: String, …): this
	// Calls `methodName` on every layer contained in this group, passing any
	// additional parameters. Has no effect if the layers contained do not
	// implement `methodName`.
	invoke: function (methodName) {
		var args = Array.prototype.slice.call(arguments, 1),
		    i, layer;

		for (i in this._layers) {
			layer = this._layers[i];

			if (layer[methodName]) {
				layer[methodName].apply(layer, args);
			}
		}

		return this;
	},

	onAdd: function (map) {
		for (var i in this._layers) {
			map.addLayer(this._layers[i]);
		}
	},

	onRemove: function (map) {
		for (var i in this._layers) {
			map.removeLayer(this._layers[i]);
		}
	},

	// @method eachLayer(fn: Function, context?: Object): this
	// Iterates over the layers of the group, optionally specifying context of the iterator function.
	// ```js
	// group.eachLayer(function (layer) {
	// 	layer.bindPopup('Hello');
	// });
	// ```
	eachLayer: function (method, context) {
		for (var i in this._layers) {
			method.call(context, this._layers[i]);
		}
		return this;
	},

	// @method getLayer(id: Number): Layer
	// Returns the layer with the given internal ID.
	getLayer: function (id) {
		return this._layers[id];
	},

	// @method getLayers(): Layer[]
	// Returns an array of all the layers added to the group.
	getLayers: function () {
		var layers = [];

		for (var i in this._layers) {
			layers.push(this._layers[i]);
		}
		return layers;
	},

	// @method setZIndex(zIndex: Number): this
	// Calls `setZIndex` on every layer contained in this group, passing the z-index.
	setZIndex: function (zIndex) {
		return this.invoke('setZIndex', zIndex);
	},

	// @method getLayerId(layer: Layer): Number
	// Returns the internal ID for a layer
	getLayerId: function (layer) {
		return L.stamp(layer);
	}
});


// @factory L.layerGroup(layers: Layer[])
// Create a layer group, optionally given an initial set of layers.
L.layerGroup = function (layers) {
	return new L.LayerGroup(layers);
};



/*
 * @class FeatureGroup
 * @aka L.FeatureGroup
 * @inherits LayerGroup
 *
 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
 *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
 *  * Events are propagated to the `FeatureGroup`, so if the group has an event
 * handler, it will handle events from any of the layers. This includes mouse events
 * and custom events.
 *  * Has `layeradd` and `layerremove` events
 *
 * @example
 *
 * ```js
 * L.featureGroup([marker1, marker2, polyline])
 * 	.bindPopup('Hello world!')
 * 	.on('click', function() { alert('Clicked on a member of the group!'); })
 * 	.addTo(map);
 * ```
 */

L.FeatureGroup = L.LayerGroup.extend({

	addLayer: function (layer) {
		if (this.hasLayer(layer)) {
			return this;
		}

		layer.addEventParent(this);

		L.LayerGroup.prototype.addLayer.call(this, layer);

		// @event layeradd: LayerEvent
		// Fired when a layer is added to this `FeatureGroup`
		return this.fire('layeradd', {layer: layer});
	},

	removeLayer: function (layer) {
		if (!this.hasLayer(layer)) {
			return this;
		}
		if (layer in this._layers) {
			layer = this._layers[layer];
		}

		layer.removeEventParent(this);

		L.LayerGroup.prototype.removeLayer.call(this, layer);

		// @event layerremove: LayerEvent
		// Fired when a layer is removed from this `FeatureGroup`
		return this.fire('layerremove', {layer: layer});
	},

	// @method setStyle(style: Path options): this
	// Sets the given path options to each layer of the group that has a `setStyle` method.
	setStyle: function (style) {
		return this.invoke('setStyle', style);
	},

	// @method bringToFront(): this
	// Brings the layer group to the top of all other layers
	bringToFront: function () {
		return this.invoke('bringToFront');
	},

	// @method bringToBack(): this
	// Brings the layer group to the top of all other layers
	bringToBack: function () {
		return this.invoke('bringToBack');
	},

	// @method getBounds(): LatLngBounds
	// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
	getBounds: function () {
		var bounds = new L.LatLngBounds();

		for (var id in this._layers) {
			var layer = this._layers[id];
			bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
		}
		return bounds;
	}
});

// @factory L.featureGroup(layers: Layer[])
// Create a feature group, optionally given an initial set of layers.
L.featureGroup = function (layers) {
	return new L.FeatureGroup(layers);
};



/*
 * @class Renderer
 * @inherits Layer
 * @aka L.Renderer
 *
 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
 * DOM container of the renderer, its bounds, and its zoom animation.
 *
 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
 * itself can be added or removed to the map. All paths use a renderer, which can
 * be implicit (the map will decide the type of renderer and use it automatically)
 * or explicit (using the [`renderer`](#path-renderer) option of the path).
 *
 * Do not use this class directly, use `SVG` and `Canvas` instead.
 *
 * @event update: Event
 * Fired when the renderer updates its bounds, center and zoom, for example when
 * its map has moved
 */

L.Renderer = L.Layer.extend({

	// @section
	// @aka Renderer options
	options: {
		// @option padding: Number = 0.1
		// How much to extend the clip area around the map view (relative to its size)
		// e.g. 0.1 would be 10% of map view in each direction
		padding: 0.1
	},

	initialize: function (options) {
		L.setOptions(this, options);
		L.stamp(this);
		this._layers = this._layers || {};
	},

	onAdd: function () {
		if (!this._container) {
			this._initContainer(); // defined by renderer implementations

			if (this._zoomAnimated) {
				L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
			}
		}

		this.getPane().appendChild(this._container);
		this._update();
		this.on('update', this._updatePaths, this);
	},

	onRemove: function () {
		L.DomUtil.remove(this._container);
		this.off('update', this._updatePaths, this);
	},

	getEvents: function () {
		var events = {
			viewreset: this._reset,
			zoom: this._onZoom,
			moveend: this._update,
			zoomend: this._onZoomEnd
		};
		if (this._zoomAnimated) {
			events.zoomanim = this._onAnimZoom;
		}
		return events;
	},

	_onAnimZoom: function (ev) {
		this._updateTransform(ev.center, ev.zoom);
	},

	_onZoom: function () {
		this._updateTransform(this._map.getCenter(), this._map.getZoom());
	},

	_updateTransform: function (center, zoom) {
		var scale = this._map.getZoomScale(zoom, this._zoom),
		    position = L.DomUtil.getPosition(this._container),
		    viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
		    currentCenterPoint = this._map.project(this._center, zoom),
		    destCenterPoint = this._map.project(center, zoom),
		    centerOffset = destCenterPoint.subtract(currentCenterPoint),

		    topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);

		if (L.Browser.any3d) {
			L.DomUtil.setTransform(this._container, topLeftOffset, scale);
		} else {
			L.DomUtil.setPosition(this._container, topLeftOffset);
		}
	},

	_reset: function () {
		this._update();
		this._updateTransform(this._center, this._zoom);

		for (var id in this._layers) {
			this._layers[id]._reset();
		}
	},

	_onZoomEnd: function () {
		for (var id in this._layers) {
			this._layers[id]._project();
		}
	},

	_updatePaths: function () {
		for (var id in this._layers) {
			this._layers[id]._update();
		}
	},

	_update: function () {
		// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
		// Subclasses are responsible of firing the 'update' event.
		var p = this.options.padding,
		    size = this._map.getSize(),
		    min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();

		this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());

		this._center = this._map.getCenter();
		this._zoom = this._map.getZoom();
	}
});


L.Map.include({
	// @namespace Map; @method getRenderer(layer: Path): Renderer
	// Returns the instance of `Renderer` that should be used to render the given
	// `Path`. It will ensure that the `renderer` options of the map and paths
	// are respected, and that the renderers do exist on the map.
	getRenderer: function (layer) {
		// @namespace Path; @option renderer: Renderer
		// Use this specific instance of `Renderer` for this path. Takes
		// precedence over the map's [default renderer](#map-renderer).
		var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;

		if (!renderer) {
			// @namespace Map; @option preferCanvas: Boolean = false
			// Whether `Path`s should be rendered on a `Canvas` renderer.
			// By default, all `Path`s are rendered in a `SVG` renderer.
			renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
		}

		if (!this.hasLayer(renderer)) {
			this.addLayer(renderer);
		}
		return renderer;
	},

	_getPaneRenderer: function (name) {
		if (name === 'overlayPane' || name === undefined) {
			return false;
		}

		var renderer = this._paneRenderers[name];
		if (renderer === undefined) {
			renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
			this._paneRenderers[name] = renderer;
		}
		return renderer;
	}
});



/*
 * @class Path
 * @aka L.Path
 * @inherits Interactive layer
 *
 * An abstract class that contains options and constants shared between vector
 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
 */

L.Path = L.Layer.extend({

	// @section
	// @aka Path options
	options: {
		// @option stroke: Boolean = true
		// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
		stroke: true,

		// @option color: String = '#3388ff'
		// Stroke color
		color: '#3388ff',

		// @option weight: Number = 3
		// Stroke width in pixels
		weight: 3,

		// @option opacity: Number = 1.0
		// Stroke opacity
		opacity: 1,

		// @option lineCap: String= 'round'
		// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
		lineCap: 'round',

		// @option lineJoin: String = 'round'
		// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
		lineJoin: 'round',

		// @option dashArray: String = null
		// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
		dashArray: null,

		// @option dashOffset: String = null
		// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
		dashOffset: null,

		// @option fill: Boolean = depends
		// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
		fill: false,

		// @option fillColor: String = *
		// Fill color. Defaults to the value of the [`color`](#path-color) option
		fillColor: null,

		// @option fillOpacity: Number = 0.2
		// Fill opacity.
		fillOpacity: 0.2,

		// @option fillRule: String = 'evenodd'
		// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
		fillRule: 'evenodd',

		// className: '',

		// Option inherited from "Interactive layer" abstract class
		interactive: true
	},

	beforeAdd: function (map) {
		// Renderer is set here because we need to call renderer.getEvents
		// before this.getEvents.
		this._renderer = map.getRenderer(this);
	},

	onAdd: function () {
		this._renderer._initPath(this);
		this._reset();
		this._renderer._addPath(this);
	},

	onRemove: function () {
		this._renderer._removePath(this);
	},

	// @method redraw(): this
	// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
	redraw: function () {
		if (this._map) {
			this._renderer._updatePath(this);
		}
		return this;
	},

	// @method setStyle(style: Path options): this
	// Changes the appearance of a Path based on the options in the `Path options` object.
	setStyle: function (style) {
		L.setOptions(this, style);
		if (this._renderer) {
			this._renderer._updateStyle(this);
		}
		return this;
	},

	// @method bringToFront(): this
	// Brings the layer to the top of all path layers.
	bringToFront: function () {
		if (this._renderer) {
			this._renderer._bringToFront(this);
		}
		return this;
	},

	// @method bringToBack(): this
	// Brings the layer to the bottom of all path layers.
	bringToBack: function () {
		if (this._renderer) {
			this._renderer._bringToBack(this);
		}
		return this;
	},

	getElement: function () {
		return this._path;
	},

	_reset: function () {
		// defined in children classes
		this._project();
		this._update();
	},

	_clickTolerance: function () {
		// used when doing hit detection for Canvas layers
		return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
	}
});



/*
 * @namespace LineUtil
 *
 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
 */

L.LineUtil = {

	// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
	// Improves rendering performance dramatically by lessening the number of points to draw.

	// @function simplify(points: Point[], tolerance: Number): Point[]
	// Dramatically reduces the number of points in a polyline while retaining
	// its shape and returns a new array of simplified points, using the
	// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
	// Used for a huge performance boost when processing/displaying Leaflet polylines for
	// each zoom level and also reducing visual noise. tolerance affects the amount of
	// simplification (lesser value means higher quality but slower and with more points).
	// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
	simplify: function (points, tolerance) {
		if (!tolerance || !points.length) {
			return points.slice();
		}

		var sqTolerance = tolerance * tolerance;

		// stage 1: vertex reduction
		points = this._reducePoints(points, sqTolerance);

		// stage 2: Douglas-Peucker simplification
		points = this._simplifyDP(points, sqTolerance);

		return points;
	},

	// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
	// Returns the distance between point `p` and segment `p1` to `p2`.
	pointToSegmentDistance:  function (p, p1, p2) {
		return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
	},

	// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
	// Returns the closest point from a point `p` on a segment `p1` to `p2`.
	closestPointOnSegment: function (p, p1, p2) {
		return this._sqClosestPointOnSegment(p, p1, p2);
	},

	// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
	_simplifyDP: function (points, sqTolerance) {

		var len = points.length,
		    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
		    markers = new ArrayConstructor(len);

		markers[0] = markers[len - 1] = 1;

		this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);

		var i,
		    newPoints = [];

		for (i = 0; i < len; i++) {
			if (markers[i]) {
				newPoints.push(points[i]);
			}
		}

		return newPoints;
	},

	_simplifyDPStep: function (points, markers, sqTolerance, first, last) {

		var maxSqDist = 0,
		    index, i, sqDist;

		for (i = first + 1; i <= last - 1; i++) {
			sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);

			if (sqDist > maxSqDist) {
				index = i;
				maxSqDist = sqDist;
			}
		}

		if (maxSqDist > sqTolerance) {
			markers[index] = 1;

			this._simplifyDPStep(points, markers, sqTolerance, first, index);
			this._simplifyDPStep(points, markers, sqTolerance, index, last);
		}
	},

	// reduce points that are too close to each other to a single point
	_reducePoints: function (points, sqTolerance) {
		var reducedPoints = [points[0]];

		for (var i = 1, prev = 0, len = points.length; i < len; i++) {
			if (this._sqDist(points[i], points[prev]) > sqTolerance) {
				reducedPoints.push(points[i]);
				prev = i;
			}
		}
		if (prev < len - 1) {
			reducedPoints.push(points[len - 1]);
		}
		return reducedPoints;
	},


	// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
	// Clips the segment a to b by rectangular bounds with the
	// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
	// (modifying the segment points directly!). Used by Leaflet to only show polyline
	// points that are on the screen or near, increasing performance.
	clipSegment: function (a, b, bounds, useLastCode, round) {
		var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
		    codeB = this._getBitCode(b, bounds),

		    codeOut, p, newCode;

		// save 2nd code to avoid calculating it on the next segment
		this._lastCode = codeB;

		while (true) {
			// if a,b is inside the clip window (trivial accept)
			if (!(codeA | codeB)) {
				return [a, b];
			}

			// if a,b is outside the clip window (trivial reject)
			if (codeA & codeB) {
				return false;
			}

			// other cases
			codeOut = codeA || codeB;
			p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
			newCode = this._getBitCode(p, bounds);

			if (codeOut === codeA) {
				a = p;
				codeA = newCode;
			} else {
				b = p;
				codeB = newCode;
			}
		}
	},

	_getEdgeIntersection: function (a, b, code, bounds, round) {
		var dx = b.x - a.x,
		    dy = b.y - a.y,
		    min = bounds.min,
		    max = bounds.max,
		    x, y;

		if (code & 8) { // top
			x = a.x + dx * (max.y - a.y) / dy;
			y = max.y;

		} else if (code & 4) { // bottom
			x = a.x + dx * (min.y - a.y) / dy;
			y = min.y;

		} else if (code & 2) { // right
			x = max.x;
			y = a.y + dy * (max.x - a.x) / dx;

		} else if (code & 1) { // left
			x = min.x;
			y = a.y + dy * (min.x - a.x) / dx;
		}

		return new L.Point(x, y, round);
	},

	_getBitCode: function (p, bounds) {
		var code = 0;

		if (p.x < bounds.min.x) { // left
			code |= 1;
		} else if (p.x > bounds.max.x) { // right
			code |= 2;
		}

		if (p.y < bounds.min.y) { // bottom
			code |= 4;
		} else if (p.y > bounds.max.y) { // top
			code |= 8;
		}

		return code;
	},

	// square distance (to avoid unnecessary Math.sqrt calls)
	_sqDist: function (p1, p2) {
		var dx = p2.x - p1.x,
		    dy = p2.y - p1.y;
		return dx * dx + dy * dy;
	},

	// return closest point on segment or distance to that point
	_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
		var x = p1.x,
		    y = p1.y,
		    dx = p2.x - x,
		    dy = p2.y - y,
		    dot = dx * dx + dy * dy,
		    t;

		if (dot > 0) {
			t = ((p.x - x) * dx + (p.y - y) * dy) / dot;

			if (t > 1) {
				x = p2.x;
				y = p2.y;
			} else if (t > 0) {
				x += dx * t;
				y += dy * t;
			}
		}

		dx = p.x - x;
		dy = p.y - y;

		return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
	}
};



/*
 * @class Polyline
 * @aka L.Polyline
 * @inherits Path
 *
 * A class for drawing polyline overlays on a map. Extends `Path`.
 *
 * @example
 *
 * ```js
 * // create a red polyline from an array of LatLng points
 * var latlngs = [
 * 	[45.51, -122.68],
 * 	[37.77, -122.43],
 * 	[34.04, -118.2]
 * ];
 *
 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
 *
 * // zoom the map to the polyline
 * map.fitBounds(polyline.getBounds());
 * ```
 *
 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
 *
 * ```js
 * // create a red polyline from an array of arrays of LatLng points
 * var latlngs = [
 * 	[[45.51, -122.68],
 * 	 [37.77, -122.43],
 * 	 [34.04, -118.2]],
 * 	[[40.78, -73.91],
 * 	 [41.83, -87.62],
 * 	 [32.76, -96.72]]
 * ];
 * ```
 */

L.Polyline = L.Path.extend({

	// @section
	// @aka Polyline options
	options: {
		// @option smoothFactor: Number = 1.0
		// How much to simplify the polyline on each zoom level. More means
		// better performance and smoother look, and less means more accurate representation.
		smoothFactor: 1.0,

		// @option noClip: Boolean = false
		// Disable polyline clipping.
		noClip: false
	},

	initialize: function (latlngs, options) {
		L.setOptions(this, options);
		this._setLatLngs(latlngs);
	},

	// @method getLatLngs(): LatLng[]
	// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
	getLatLngs: function () {
		return this._latlngs;
	},

	// @method setLatLngs(latlngs: LatLng[]): this
	// Replaces all the points in the polyline with the given array of geographical points.
	setLatLngs: function (latlngs) {
		this._setLatLngs(latlngs);
		return this.redraw();
	},

	// @method isEmpty(): Boolean
	// Returns `true` if the Polyline has no LatLngs.
	isEmpty: function () {
		return !this._latlngs.length;
	},

	closestLayerPoint: function (p) {
		var minDistance = Infinity,
		    minPoint = null,
		    closest = L.LineUtil._sqClosestPointOnSegment,
		    p1, p2;

		for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
			var points = this._parts[j];

			for (var i = 1, len = points.length; i < len; i++) {
				p1 = points[i - 1];
				p2 = points[i];

				var sqDist = closest(p, p1, p2, true);

				if (sqDist < minDistance) {
					minDistance = sqDist;
					minPoint = closest(p, p1, p2);
				}
			}
		}
		if (minPoint) {
			minPoint.distance = Math.sqrt(minDistance);
		}
		return minPoint;
	},

	// @method getCenter(): LatLng
	// Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
	getCenter: function () {
		// throws error when not yet added to map as this center calculation requires projected coordinates
		if (!this._map) {
			throw new Error('Must add layer to map before using getCenter()');
		}

		var i, halfDist, segDist, dist, p1, p2, ratio,
		    points = this._rings[0],
		    len = points.length;

		if (!len) { return null; }

		// polyline centroid algorithm; only uses the first ring if there are multiple

		for (i = 0, halfDist = 0; i < len - 1; i++) {
			halfDist += points[i].distanceTo(points[i + 1]) / 2;
		}

		// The line is so small in the current view that all points are on the same pixel.
		if (halfDist === 0) {
			return this._map.layerPointToLatLng(points[0]);
		}

		for (i = 0, dist = 0; i < len - 1; i++) {
			p1 = points[i];
			p2 = points[i + 1];
			segDist = p1.distanceTo(p2);
			dist += segDist;

			if (dist > halfDist) {
				ratio = (dist - halfDist) / segDist;
				return this._map.layerPointToLatLng([
					p2.x - ratio * (p2.x - p1.x),
					p2.y - ratio * (p2.y - p1.y)
				]);
			}
		}
	},

	// @method getBounds(): LatLngBounds
	// Returns the `LatLngBounds` of the path.
	getBounds: function () {
		return this._bounds;
	},

	// @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
	// Adds a given point to the polyline. By default, adds to the first ring of
	// the polyline in case of a multi-polyline, but can be overridden by passing
	// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
	addLatLng: function (latlng, latlngs) {
		latlngs = latlngs || this._defaultShape();
		latlng = L.latLng(latlng);
		latlngs.push(latlng);
		this._bounds.extend(latlng);
		return this.redraw();
	},

	_setLatLngs: function (latlngs) {
		this._bounds = new L.LatLngBounds();
		this._latlngs = this._convertLatLngs(latlngs);
	},

	_defaultShape: function () {
		return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
	},

	// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
	_convertLatLngs: function (latlngs) {
		var result = [],
		    flat = L.Polyline._flat(latlngs);

		for (var i = 0, len = latlngs.length; i < len; i++) {
			if (flat) {
				result[i] = L.latLng(latlngs[i]);
				this._bounds.extend(result[i]);
			} else {
				result[i] = this._convertLatLngs(latlngs[i]);
			}
		}

		return result;
	},

	_project: function () {
		var pxBounds = new L.Bounds();
		this._rings = [];
		this._projectLatlngs(this._latlngs, this._rings, pxBounds);

		var w = this._clickTolerance(),
		    p = new L.Point(w, w);

		if (this._bounds.isValid() && pxBounds.isValid()) {
			pxBounds.min._subtract(p);
			pxBounds.max._add(p);
			this._pxBounds = pxBounds;
		}
	},

	// recursively turns latlngs into a set of rings with projected coordinates
	_projectLatlngs: function (latlngs, result, projectedBounds) {
		var flat = latlngs[0] instanceof L.LatLng,
		    len = latlngs.length,
		    i, ring;

		if (flat) {
			ring = [];
			for (i = 0; i < len; i++) {
				ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
				projectedBounds.extend(ring[i]);
			}
			result.push(ring);
		} else {
			for (i = 0; i < len; i++) {
				this._projectLatlngs(latlngs[i], result, projectedBounds);
			}
		}
	},

	// clip polyline by renderer bounds so that we have less to render for performance
	_clipPoints: function () {
		var bounds = this._renderer._bounds;

		this._parts = [];
		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
			return;
		}

		if (this.options.noClip) {
			this._parts = this._rings;
			return;
		}

		var parts = this._parts,
		    i, j, k, len, len2, segment, points;

		for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
			points = this._rings[i];

			for (j = 0, len2 = points.length; j < len2 - 1; j++) {
				segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);

				if (!segment) { continue; }

				parts[k] = parts[k] || [];
				parts[k].push(segment[0]);

				// if segment goes out of screen, or it's the last one, it's the end of the line part
				if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
					parts[k].push(segment[1]);
					k++;
				}
			}
		}
	},

	// simplify each clipped part of the polyline for performance
	_simplifyPoints: function () {
		var parts = this._parts,
		    tolerance = this.options.smoothFactor;

		for (var i = 0, len = parts.length; i < len; i++) {
			parts[i] = L.LineUtil.simplify(parts[i], tolerance);
		}
	},

	_update: function () {
		if (!this._map) { return; }

		this._clipPoints();
		this._simplifyPoints();
		this._updatePath();
	},

	_updatePath: function () {
		this._renderer._updatePoly(this);
	}
});

// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
// Instantiates a polyline object given an array of geographical points and
// optionally an options object. You can create a `Polyline` object with
// multiple separate lines (`MultiPolyline`) by passing an array of arrays
// of geographic points.
L.polyline = function (latlngs, options) {
	return new L.Polyline(latlngs, options);
};

L.Polyline._flat = function (latlngs) {
	// true if it's a flat array of latlngs; false if nested
	return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
};



/*
 * @namespace PolyUtil
 * Various utility functions for polygon geometries.
 */

L.PolyUtil = {};

/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
 * performance. Note that polygon points needs different algorithm for clipping
 * than polyline, so there's a seperate method for it.
 */
L.PolyUtil.clipPolygon = function (points, bounds, round) {
	var clippedPoints,
	    edges = [1, 4, 2, 8],
	    i, j, k,
	    a, b,
	    len, edge, p,
	    lu = L.LineUtil;

	for (i = 0, len = points.length; i < len; i++) {
		points[i]._code = lu._getBitCode(points[i], bounds);
	}

	// for each edge (left, bottom, right, top)
	for (k = 0; k < 4; k++) {
		edge = edges[k];
		clippedPoints = [];

		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
			a = points[i];
			b = points[j];

			// if a is inside the clip window
			if (!(a._code & edge)) {
				// if b is outside the clip window (a->b goes out of screen)
				if (b._code & edge) {
					p = lu._getEdgeIntersection(b, a, edge, bounds, round);
					p._code = lu._getBitCode(p, bounds);
					clippedPoints.push(p);
				}
				clippedPoints.push(a);

			// else if b is inside the clip window (a->b enters the screen)
			} else if (!(b._code & edge)) {
				p = lu._getEdgeIntersection(b, a, edge, bounds, round);
				p._code = lu._getBitCode(p, bounds);
				clippedPoints.push(p);
			}
		}
		points = clippedPoints;
	}

	return points;
};



/*
 * @class Polygon
 * @aka L.Polygon
 * @inherits Polyline
 *
 * A class for drawing polygon overlays on a map. Extends `Polyline`.
 *
 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
 *
 *
 * @example
 *
 * ```js
 * // create a red polygon from an array of LatLng points
 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
 *
 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
 *
 * // zoom the map to the polygon
 * map.fitBounds(polygon.getBounds());
 * ```
 *
 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
 *
 * ```js
 * var latlngs = [
 *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
 *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
 * ];
 * ```
 *
 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
 *
 * ```js
 * var latlngs = [
 *   [ // first polygon
 *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
 *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
 *   ],
 *   [ // second polygon
 *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
 *   ]
 * ];
 * ```
 */

L.Polygon = L.Polyline.extend({

	options: {
		fill: true
	},

	isEmpty: function () {
		return !this._latlngs.length || !this._latlngs[0].length;
	},

	getCenter: function () {
		// throws error when not yet added to map as this center calculation requires projected coordinates
		if (!this._map) {
			throw new Error('Must add layer to map before using getCenter()');
		}

		var i, j, p1, p2, f, area, x, y, center,
		    points = this._rings[0],
		    len = points.length;

		if (!len) { return null; }

		// polygon centroid algorithm; only uses the first ring if there are multiple

		area = x = y = 0;

		for (i = 0, j = len - 1; i < len; j = i++) {
			p1 = points[i];
			p2 = points[j];

			f = p1.y * p2.x - p2.y * p1.x;
			x += (p1.x + p2.x) * f;
			y += (p1.y + p2.y) * f;
			area += f * 3;
		}

		if (area === 0) {
			// Polygon is so small that all points are on same pixel.
			center = points[0];
		} else {
			center = [x / area, y / area];
		}
		return this._map.layerPointToLatLng(center);
	},

	_convertLatLngs: function (latlngs) {
		var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
		    len = result.length;

		// remove last point if it equals first one
		if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
			result.pop();
		}
		return result;
	},

	_setLatLngs: function (latlngs) {
		L.Polyline.prototype._setLatLngs.call(this, latlngs);
		if (L.Polyline._flat(this._latlngs)) {
			this._latlngs = [this._latlngs];
		}
	},

	_defaultShape: function () {
		return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
	},

	_clipPoints: function () {
		// polygons need a different clipping algorithm so we redefine that

		var bounds = this._renderer._bounds,
		    w = this.options.weight,
		    p = new L.Point(w, w);

		// increase clip padding by stroke width to avoid stroke on clip edges
		bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));

		this._parts = [];
		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
			return;
		}

		if (this.options.noClip) {
			this._parts = this._rings;
			return;
		}

		for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
			clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
			if (clipped.length) {
				this._parts.push(clipped);
			}
		}
	},

	_updatePath: function () {
		this._renderer._updatePoly(this, true);
	}
});


// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
L.polygon = function (latlngs, options) {
	return new L.Polygon(latlngs, options);
};



/*
 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
 */

/*
 * @class Rectangle
 * @aka L.Retangle
 * @inherits Polygon
 *
 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
 *
 * @example
 *
 * ```js
 * // define rectangle geographical bounds
 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
 *
 * // create an orange rectangle
 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
 *
 * // zoom the map to the rectangle bounds
 * map.fitBounds(bounds);
 * ```
 *
 */


L.Rectangle = L.Polygon.extend({
	initialize: function (latLngBounds, options) {
		L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
	},

	// @method setBounds(latLngBounds: LatLngBounds): this
	// Redraws the rectangle with the passed bounds.
	setBounds: function (latLngBounds) {
		return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
	},

	_boundsToLatLngs: function (latLngBounds) {
		latLngBounds = L.latLngBounds(latLngBounds);
		return [
			latLngBounds.getSouthWest(),
			latLngBounds.getNorthWest(),
			latLngBounds.getNorthEast(),
			latLngBounds.getSouthEast()
		];
	}
});


// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
L.rectangle = function (latLngBounds, options) {
	return new L.Rectangle(latLngBounds, options);
};



/*
 * @class CircleMarker
 * @aka L.CircleMarker
 * @inherits Path
 *
 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
 */

L.CircleMarker = L.Path.extend({

	// @section
	// @aka CircleMarker options
	options: {
		fill: true,

		// @option radius: Number = 10
		// Radius of the circle marker, in pixels
		radius: 10
	},

	initialize: function (latlng, options) {
		L.setOptions(this, options);
		this._latlng = L.latLng(latlng);
		this._radius = this.options.radius;
	},

	// @method setLatLng(latLng: LatLng): this
	// Sets the position of a circle marker to a new location.
	setLatLng: function (latlng) {
		this._latlng = L.latLng(latlng);
		this.redraw();
		return this.fire('move', {latlng: this._latlng});
	},

	// @method getLatLng(): LatLng
	// Returns the current geographical position of the circle marker
	getLatLng: function () {
		return this._latlng;
	},

	// @method setRadius(radius: Number): this
	// Sets the radius of a circle marker. Units are in pixels.
	setRadius: function (radius) {
		this.options.radius = this._radius = radius;
		return this.redraw();
	},

	// @method getRadius(): Number
	// Returns the current radius of the circle
	getRadius: function () {
		return this._radius;
	},

	setStyle : function (options) {
		var radius = options && options.radius || this._radius;
		L.Path.prototype.setStyle.call(this, options);
		this.setRadius(radius);
		return this;
	},

	_project: function () {
		this._point = this._map.latLngToLayerPoint(this._latlng);
		this._updateBounds();
	},

	_updateBounds: function () {
		var r = this._radius,
		    r2 = this._radiusY || r,
		    w = this._clickTolerance(),
		    p = [r + w, r2 + w];
		this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
	},

	_update: function () {
		if (this._map) {
			this._updatePath();
		}
	},

	_updatePath: function () {
		this._renderer._updateCircle(this);
	},

	_empty: function () {
		return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
	}
});


// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
// Instantiates a circle marker object given a geographical point, and an optional options object.
L.circleMarker = function (latlng, options) {
	return new L.CircleMarker(latlng, options);
};



/*
 * @class Circle
 * @aka L.Circle
 * @inherits CircleMarker
 *
 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
 *
 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
 *
 * @example
 *
 * ```js
 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
 * ```
 */

L.Circle = L.CircleMarker.extend({

	initialize: function (latlng, options, legacyOptions) {
		if (typeof options === 'number') {
			// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
			options = L.extend({}, legacyOptions, {radius: options});
		}
		L.setOptions(this, options);
		this._latlng = L.latLng(latlng);

		if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }

		// @section
		// @aka Circle options
		// @option radius: Number; Radius of the circle, in meters.
		this._mRadius = this.options.radius;
	},

	// @method setRadius(radius: Number): this
	// Sets the radius of a circle. Units are in meters.
	setRadius: function (radius) {
		this._mRadius = radius;
		return this.redraw();
	},

	// @method getRadius(): Number
	// Returns the current radius of a circle. Units are in meters.
	getRadius: function () {
		return this._mRadius;
	},

	// @method getBounds(): LatLngBounds
	// Returns the `LatLngBounds` of the path.
	getBounds: function () {
		var half = [this._radius, this._radiusY || this._radius];

		return new L.LatLngBounds(
			this._map.layerPointToLatLng(this._point.subtract(half)),
			this._map.layerPointToLatLng(this._point.add(half)));
	},

	setStyle: L.Path.prototype.setStyle,

	_project: function () {

		var lng = this._latlng.lng,
		    lat = this._latlng.lat,
		    map = this._map,
		    crs = map.options.crs;

		if (crs.distance === L.CRS.Earth.distance) {
			var d = Math.PI / 180,
			    latR = (this._mRadius / L.CRS.Earth.R) / d,
			    top = map.project([lat + latR, lng]),
			    bottom = map.project([lat - latR, lng]),
			    p = top.add(bottom).divideBy(2),
			    lat2 = map.unproject(p).lat,
			    lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
			            (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;

			if (isNaN(lngR) || lngR === 0) {
				lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
			}

			this._point = p.subtract(map.getPixelOrigin());
			this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
			this._radiusY = Math.max(Math.round(p.y - top.y), 1);

		} else {
			var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));

			this._point = map.latLngToLayerPoint(this._latlng);
			this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
		}

		this._updateBounds();
	}
});

// @factory L.circle(latlng: LatLng, options?: Circle options)
// Instantiates a circle object given a geographical point, and an options object
// which contains the circle radius.
// @alternative
// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
// Do not use in new applications or plugins.
L.circle = function (latlng, options, legacyOptions) {
	return new L.Circle(latlng, options, legacyOptions);
};



/*
 * @class SVG
 * @inherits Renderer
 * @aka L.SVG
 *
 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
 * Inherits `Renderer`.
 *
 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
 * available in all web browsers, notably Android 2.x and 3.x.
 *
 * Although SVG is not available on IE7 and IE8, these browsers support
 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
 * (a now deprecated technology), and the SVG renderer will fall back to VML in
 * this case.
 *
 * @example
 *
 * Use SVG by default for all paths in the map:
 *
 * ```js
 * var map = L.map('map', {
 * 	renderer: L.svg()
 * });
 * ```
 *
 * Use a SVG renderer with extra padding for specific vector geometries:
 *
 * ```js
 * var map = L.map('map');
 * var myRenderer = L.svg({ padding: 0.5 });
 * var line = L.polyline( coordinates, { renderer: myRenderer } );
 * var circle = L.circle( center, { renderer: myRenderer } );
 * ```
 */

L.SVG = L.Renderer.extend({

	getEvents: function () {
		var events = L.Renderer.prototype.getEvents.call(this);
		events.zoomstart = this._onZoomStart;
		return events;
	},

	_initContainer: function () {
		this._container = L.SVG.create('svg');

		// makes it possible to click through svg root; we'll reset it back in individual paths
		this._container.setAttribute('pointer-events', 'none');

		this._rootGroup = L.SVG.create('g');
		this._container.appendChild(this._rootGroup);
	},

	_onZoomStart: function () {
		// Drag-then-pinch interactions might mess up the center and zoom.
		// In this case, the easiest way to prevent this is re-do the renderer
		//   bounds and padding when the zooming starts.
		this._update();
	},

	_update: function () {
		if (this._map._animatingZoom && this._bounds) { return; }

		L.Renderer.prototype._update.call(this);

		var b = this._bounds,
		    size = b.getSize(),
		    container = this._container;

		// set size of svg-container if changed
		if (!this._svgSize || !this._svgSize.equals(size)) {
			this._svgSize = size;
			container.setAttribute('width', size.x);
			container.setAttribute('height', size.y);
		}

		// movement: update container viewBox so that we don't have to change coordinates of individual layers
		L.DomUtil.setPosition(container, b.min);
		container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));

		this.fire('update');
	},

	// methods below are called by vector layers implementations

	_initPath: function (layer) {
		var path = layer._path = L.SVG.create('path');

		// @namespace Path
		// @option className: String = null
		// Custom class name set on an element. Only for SVG renderer.
		if (layer.options.className) {
			L.DomUtil.addClass(path, layer.options.className);
		}

		if (layer.options.interactive) {
			L.DomUtil.addClass(path, 'leaflet-interactive');
		}

		this._updateStyle(layer);
		this._layers[L.stamp(layer)] = layer;
	},

	_addPath: function (layer) {
		this._rootGroup.appendChild(layer._path);
		layer.addInteractiveTarget(layer._path);
	},

	_removePath: function (layer) {
		L.DomUtil.remove(layer._path);
		layer.removeInteractiveTarget(layer._path);
		delete this._layers[L.stamp(layer)];
	},

	_updatePath: function (layer) {
		layer._project();
		layer._update();
	},

	_updateStyle: function (layer) {
		var path = layer._path,
		    options = layer.options;

		if (!path) { return; }

		if (options.stroke) {
			path.setAttribute('stroke', options.color);
			path.setAttribute('stroke-opacity', options.opacity);
			path.setAttribute('stroke-width', options.weight);
			path.setAttribute('stroke-linecap', options.lineCap);
			path.setAttribute('stroke-linejoin', options.lineJoin);

			if (options.dashArray) {
				path.setAttribute('stroke-dasharray', options.dashArray);
			} else {
				path.removeAttribute('stroke-dasharray');
			}

			if (options.dashOffset) {
				path.setAttribute('stroke-dashoffset', options.dashOffset);
			} else {
				path.removeAttribute('stroke-dashoffset');
			}
		} else {
			path.setAttribute('stroke', 'none');
		}

		if (options.fill) {
			path.setAttribute('fill', options.fillColor || options.color);
			path.setAttribute('fill-opacity', options.fillOpacity);
			path.setAttribute('fill-rule', options.fillRule || 'evenodd');
		} else {
			path.setAttribute('fill', 'none');
		}
	},

	_updatePoly: function (layer, closed) {
		this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
	},

	_updateCircle: function (layer) {
		var p = layer._point,
		    r = layer._radius,
		    r2 = layer._radiusY || r,
		    arc = 'a' + r + ',' + r2 + ' 0 1,0 ';

		// drawing a circle with two half-arcs
		var d = layer._empty() ? 'M0 0' :
				'M' + (p.x - r) + ',' + p.y +
				arc + (r * 2) + ',0 ' +
				arc + (-r * 2) + ',0 ';

		this._setPath(layer, d);
	},

	_setPath: function (layer, path) {
		layer._path.setAttribute('d', path);
	},

	// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
	_bringToFront: function (layer) {
		L.DomUtil.toFront(layer._path);
	},

	_bringToBack: function (layer) {
		L.DomUtil.toBack(layer._path);
	}
});


// @namespace SVG; @section
// There are several static functions which can be called without instantiating L.SVG:
L.extend(L.SVG, {
	// @function create(name: String): SVGElement
	// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
	// corresponding to the class name passed. For example, using 'line' will return
	// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
	create: function (name) {
		return document.createElementNS('http://www.w3.org/2000/svg', name);
	},

	// @function pointsToPath(rings: Point[], closed: Boolean): String
	// Generates a SVG path string for multiple rings, with each ring turning
	// into "M..L..L.." instructions
	pointsToPath: function (rings, closed) {
		var str = '',
		    i, j, len, len2, points, p;

		for (i = 0, len = rings.length; i < len; i++) {
			points = rings[i];

			for (j = 0, len2 = points.length; j < len2; j++) {
				p = points[j];
				str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
			}

			// closes the ring for polygons; "x" is VML syntax
			str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
		}

		// SVG complains about empty path strings
		return str || 'M0 0';
	}
});

// @namespace Browser; @property svg: Boolean
// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);


// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
L.svg = function (options) {
	return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
};



/*
 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
 */

/*
 * @class SVG
 *
 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
 *
 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
 * with old versions of Internet Explorer.
 */

// @namespace Browser; @property vml: Boolean
// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
L.Browser.vml = !L.Browser.svg && (function () {
	try {
		var div = document.createElement('div');
		div.innerHTML = '<v:shape adj="1"/>';

		var shape = div.firstChild;
		shape.style.behavior = 'url(#default#VML)';

		return shape && (typeof shape.adj === 'object');

	} catch (e) {
		return false;
	}
}());

// redefine some SVG methods to handle VML syntax which is similar but with some differences
L.SVG.include(!L.Browser.vml ? {} : {

	_initContainer: function () {
		this._container = L.DomUtil.create('div', 'leaflet-vml-container');
	},

	_update: function () {
		if (this._map._animatingZoom) { return; }
		L.Renderer.prototype._update.call(this);
		this.fire('update');
	},

	_initPath: function (layer) {
		var container = layer._container = L.SVG.create('shape');

		L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));

		container.coordsize = '1 1';

		layer._path = L.SVG.create('path');
		container.appendChild(layer._path);

		this._updateStyle(layer);
		this._layers[L.stamp(layer)] = layer;
	},

	_addPath: function (layer) {
		var container = layer._container;
		this._container.appendChild(container);

		if (layer.options.interactive) {
			layer.addInteractiveTarget(container);
		}
	},

	_removePath: function (layer) {
		var container = layer._container;
		L.DomUtil.remove(container);
		layer.removeInteractiveTarget(container);
		delete this._layers[L.stamp(layer)];
	},

	_updateStyle: function (layer) {
		var stroke = layer._stroke,
		    fill = layer._fill,
		    options = layer.options,
		    container = layer._container;

		container.stroked = !!options.stroke;
		container.filled = !!options.fill;

		if (options.stroke) {
			if (!stroke) {
				stroke = layer._stroke = L.SVG.create('stroke');
			}
			container.appendChild(stroke);
			stroke.weight = options.weight + 'px';
			stroke.color = options.color;
			stroke.opacity = options.opacity;

			if (options.dashArray) {
				stroke.dashStyle = L.Util.isArray(options.dashArray) ?
				    options.dashArray.join(' ') :
				    options.dashArray.replace(/( *, *)/g, ' ');
			} else {
				stroke.dashStyle = '';
			}
			stroke.endcap = options.lineCap.replace('butt', 'flat');
			stroke.joinstyle = options.lineJoin;

		} else if (stroke) {
			container.removeChild(stroke);
			layer._stroke = null;
		}

		if (options.fill) {
			if (!fill) {
				fill = layer._fill = L.SVG.create('fill');
			}
			container.appendChild(fill);
			fill.color = options.fillColor || options.color;
			fill.opacity = options.fillOpacity;

		} else if (fill) {
			container.removeChild(fill);
			layer._fill = null;
		}
	},

	_updateCircle: function (layer) {
		var p = layer._point.round(),
		    r = Math.round(layer._radius),
		    r2 = Math.round(layer._radiusY || r);

		this._setPath(layer, layer._empty() ? 'M0 0' :
				'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
	},

	_setPath: function (layer, path) {
		layer._path.v = path;
	},

	_bringToFront: function (layer) {
		L.DomUtil.toFront(layer._container);
	},

	_bringToBack: function (layer) {
		L.DomUtil.toBack(layer._container);
	}
});

if (L.Browser.vml) {
	L.SVG.create = (function () {
		try {
			document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
			return function (name) {
				return document.createElement('<lvml:' + name + ' class="lvml">');
			};
		} catch (e) {
			return function (name) {
				return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
			};
		}
	})();
}



/*
 * @class Canvas
 * @inherits Renderer
 * @aka L.Canvas
 *
 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
 * Inherits `Renderer`.
 *
 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
 * available in all web browsers, notably IE8, and overlapping geometries might
 * not display properly in some edge cases.
 *
 * @example
 *
 * Use Canvas by default for all paths in the map:
 *
 * ```js
 * var map = L.map('map', {
 * 	renderer: L.canvas()
 * });
 * ```
 *
 * Use a Canvas renderer with extra padding for specific vector geometries:
 *
 * ```js
 * var map = L.map('map');
 * var myRenderer = L.canvas({ padding: 0.5 });
 * var line = L.polyline( coordinates, { renderer: myRenderer } );
 * var circle = L.circle( center, { renderer: myRenderer } );
 * ```
 */

L.Canvas = L.Renderer.extend({
	getEvents: function () {
		var events = L.Renderer.prototype.getEvents.call(this);
		events.viewprereset = this._onViewPreReset;
		return events;
	},

	_onViewPreReset: function () {
		// Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
		this._postponeUpdatePaths = true;
	},

	onAdd: function () {
		L.Renderer.prototype.onAdd.call(this);

		// Redraw vectors since canvas is cleared upon removal,
		// in case of removing the renderer itself from the map.
		this._draw();
	},

	_initContainer: function () {
		var container = this._container = document.createElement('canvas');

		L.DomEvent
			.on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
			.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
			.on(container, 'mouseout', this._handleMouseOut, this);

		this._ctx = container.getContext('2d');
	},

	_updatePaths: function () {
		if (this._postponeUpdatePaths) { return; }

		var layer;
		this._redrawBounds = null;
		for (var id in this._layers) {
			layer = this._layers[id];
			layer._update();
		}
		this._redraw();
	},

	_update: function () {
		if (this._map._animatingZoom && this._bounds) { return; }

		this._drawnLayers = {};

		L.Renderer.prototype._update.call(this);

		var b = this._bounds,
		    container = this._container,
		    size = b.getSize(),
		    m = L.Browser.retina ? 2 : 1;

		L.DomUtil.setPosition(container, b.min);

		// set canvas size (also clearing it); use double size on retina
		container.width = m * size.x;
		container.height = m * size.y;
		container.style.width = size.x + 'px';
		container.style.height = size.y + 'px';

		if (L.Browser.retina) {
			this._ctx.scale(2, 2);
		}

		// translate so we use the same path coordinates after canvas element moves
		this._ctx.translate(-b.min.x, -b.min.y);

		// Tell paths to redraw themselves
		this.fire('update');
	},

	_reset: function () {
		L.Renderer.prototype._reset.call(this);

		if (this._postponeUpdatePaths) {
			this._postponeUpdatePaths = false;
			this._updatePaths();
		}
	},

	_initPath: function (layer) {
		this._updateDashArray(layer);
		this._layers[L.stamp(layer)] = layer;

		var order = layer._order = {
			layer: layer,
			prev: this._drawLast,
			next: null
		};
		if (this._drawLast) { this._drawLast.next = order; }
		this._drawLast = order;
		this._drawFirst = this._drawFirst || this._drawLast;
	},

	_addPath: function (layer) {
		this._requestRedraw(layer);
	},

	_removePath: function (layer) {
		var order = layer._order;
		var next = order.next;
		var prev = order.prev;

		if (next) {
			next.prev = prev;
		} else {
			this._drawLast = prev;
		}
		if (prev) {
			prev.next = next;
		} else {
			this._drawFirst = next;
		}

		delete layer._order;

		delete this._layers[L.stamp(layer)];

		this._requestRedraw(layer);
	},

	_updatePath: function (layer) {
		// Redraw the union of the layer's old pixel
		// bounds and the new pixel bounds.
		this._extendRedrawBounds(layer);
		layer._project();
		layer._update();
		// The redraw will extend the redraw bounds
		// with the new pixel bounds.
		this._requestRedraw(layer);
	},

	_updateStyle: function (layer) {
		this._updateDashArray(layer);
		this._requestRedraw(layer);
	},

	_updateDashArray: function (layer) {
		if (layer.options.dashArray) {
			var parts = layer.options.dashArray.split(','),
			    dashArray = [],
			    i;
			for (i = 0; i < parts.length; i++) {
				dashArray.push(Number(parts[i]));
			}
			layer.options._dashArray = dashArray;
		}
	},

	_requestRedraw: function (layer) {
		if (!this._map) { return; }

		this._extendRedrawBounds(layer);
		this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
	},

	_extendRedrawBounds: function (layer) {
		var padding = (layer.options.weight || 0) + 1;
		this._redrawBounds = this._redrawBounds || new L.Bounds();
		this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
		this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
	},

	_redraw: function () {
		this._redrawRequest = null;

		if (this._redrawBounds) {
			this._redrawBounds.min._floor();
			this._redrawBounds.max._ceil();
		}

		this._clear(); // clear layers in redraw bounds
		this._draw(); // draw layers

		this._redrawBounds = null;
	},

	_clear: function () {
		var bounds = this._redrawBounds;
		if (bounds) {
			var size = bounds.getSize();
			this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
		} else {
			this._ctx.clearRect(0, 0, this._container.width, this._container.height);
		}
	},

	_draw: function () {
		var layer, bounds = this._redrawBounds;
		this._ctx.save();
		if (bounds) {
			var size = bounds.getSize();
			this._ctx.beginPath();
			this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
			this._ctx.clip();
		}

		this._drawing = true;

		for (var order = this._drawFirst; order; order = order.next) {
			layer = order.layer;
			if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
				layer._updatePath();
			}
		}

		this._drawing = false;

		this._ctx.restore();  // Restore state before clipping.
	},

	_updatePoly: function (layer, closed) {
		if (!this._drawing) { return; }

		var i, j, len2, p,
		    parts = layer._parts,
		    len = parts.length,
		    ctx = this._ctx;

		if (!len) { return; }

		this._drawnLayers[layer._leaflet_id] = layer;

		ctx.beginPath();

		if (ctx.setLineDash) {
			ctx.setLineDash(layer.options && layer.options._dashArray || []);
		}

		for (i = 0; i < len; i++) {
			for (j = 0, len2 = parts[i].length; j < len2; j++) {
				p = parts[i][j];
				ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
			}
			if (closed) {
				ctx.closePath();
			}
		}

		this._fillStroke(ctx, layer);

		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
	},

	_updateCircle: function (layer) {

		if (!this._drawing || layer._empty()) { return; }

		var p = layer._point,
		    ctx = this._ctx,
		    r = layer._radius,
		    s = (layer._radiusY || r) / r;

		this._drawnLayers[layer._leaflet_id] = layer;

		if (s !== 1) {
			ctx.save();
			ctx.scale(1, s);
		}

		ctx.beginPath();
		ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);

		if (s !== 1) {
			ctx.restore();
		}

		this._fillStroke(ctx, layer);
	},

	_fillStroke: function (ctx, layer) {
		var options = layer.options;

		if (options.fill) {
			ctx.globalAlpha = options.fillOpacity;
			ctx.fillStyle = options.fillColor || options.color;
			ctx.fill(options.fillRule || 'evenodd');
		}

		if (options.stroke && options.weight !== 0) {
			ctx.globalAlpha = options.opacity;
			ctx.lineWidth = options.weight;
			ctx.strokeStyle = options.color;
			ctx.lineCap = options.lineCap;
			ctx.lineJoin = options.lineJoin;
			ctx.stroke();
		}
	},

	// Canvas obviously doesn't have mouse events for individual drawn objects,
	// so we emulate that by calculating what's under the mouse on mousemove/click manually

	_onClick: function (e) {
		var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;

		for (var order = this._drawFirst; order; order = order.next) {
			layer = order.layer;
			if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
				clickedLayer = layer;
			}
		}
		if (clickedLayer)  {
			L.DomEvent._fakeStop(e);
			this._fireEvent([clickedLayer], e);
		}
	},

	_onMouseMove: function (e) {
		if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }

		var point = this._map.mouseEventToLayerPoint(e);
		this._handleMouseHover(e, point);
	},


	_handleMouseOut: function (e) {
		var layer = this._hoveredLayer;
		if (layer) {
			// if we're leaving the layer, fire mouseout
			L.DomUtil.removeClass(this._container, 'leaflet-interactive');
			this._fireEvent([layer], e, 'mouseout');
			this._hoveredLayer = null;
		}
	},

	_handleMouseHover: function (e, point) {
		var layer, candidateHoveredLayer;

		for (var order = this._drawFirst; order; order = order.next) {
			layer = order.layer;
			if (layer.options.interactive && layer._containsPoint(point)) {
				candidateHoveredLayer = layer;
			}
		}

		if (candidateHoveredLayer !== this._hoveredLayer) {
			this._handleMouseOut(e);

			if (candidateHoveredLayer) {
				L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
				this._fireEvent([candidateHoveredLayer], e, 'mouseover');
				this._hoveredLayer = candidateHoveredLayer;
			}
		}

		if (this._hoveredLayer) {
			this._fireEvent([this._hoveredLayer], e);
		}
	},

	_fireEvent: function (layers, e, type) {
		this._map._fireDOMEvent(e, type || e.type, layers);
	},

	_bringToFront: function (layer) {
		var order = layer._order;
		var next = order.next;
		var prev = order.prev;

		if (next) {
			next.prev = prev;
		} else {
			// Already last
			return;
		}
		if (prev) {
			prev.next = next;
		} else if (next) {
			// Update first entry unless this is the
			// signle entry
			this._drawFirst = next;
		}

		order.prev = this._drawLast;
		this._drawLast.next = order;

		order.next = null;
		this._drawLast = order;

		this._requestRedraw(layer);
	},

	_bringToBack: function (layer) {
		var order = layer._order;
		var next = order.next;
		var prev = order.prev;

		if (prev) {
			prev.next = next;
		} else {
			// Already first
			return;
		}
		if (next) {
			next.prev = prev;
		} else if (prev) {
			// Update last entry unless this is the
			// signle entry
			this._drawLast = prev;
		}

		order.prev = null;

		order.next = this._drawFirst;
		this._drawFirst.prev = order;
		this._drawFirst = order;

		this._requestRedraw(layer);
	}
});

// @namespace Browser; @property canvas: Boolean
// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
L.Browser.canvas = (function () {
	return !!document.createElement('canvas').getContext;
}());

// @namespace Canvas
// @factory L.canvas(options?: Renderer options)
// Creates a Canvas renderer with the given options.
L.canvas = function (options) {
	return L.Browser.canvas ? new L.Canvas(options) : null;
};

L.Polyline.prototype._containsPoint = function (p, closed) {
	var i, j, k, len, len2, part,
	    w = this._clickTolerance();

	if (!this._pxBounds.contains(p)) { return false; }

	// hit detection for polylines
	for (i = 0, len = this._parts.length; i < len; i++) {
		part = this._parts[i];

		for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
			if (!closed && (j === 0)) { continue; }

			if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
				return true;
			}
		}
	}
	return false;
};

L.Polygon.prototype._containsPoint = function (p) {
	var inside = false,
	    part, p1, p2, i, j, k, len, len2;

	if (!this._pxBounds.contains(p)) { return false; }

	// ray casting algorithm for detecting if point is in polygon
	for (i = 0, len = this._parts.length; i < len; i++) {
		part = this._parts[i];

		for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
			p1 = part[j];
			p2 = part[k];

			if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
				inside = !inside;
			}
		}
	}

	// also check if it's on polygon stroke
	return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
};

L.CircleMarker.prototype._containsPoint = function (p) {
	return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
};



/*
 * @class GeoJSON
 * @aka L.GeoJSON
 * @inherits FeatureGroup
 *
 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
 *
 * @example
 *
 * ```js
 * L.geoJSON(data, {
 * 	style: function (feature) {
 * 		return {color: feature.properties.color};
 * 	}
 * }).bindPopup(function (layer) {
 * 	return layer.feature.properties.description;
 * }).addTo(map);
 * ```
 */

L.GeoJSON = L.FeatureGroup.extend({

	/* @section
	 * @aka GeoJSON options
	 *
	 * @option pointToLayer: Function = *
	 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
	 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
	 * The default is to spawn a default `Marker`:
	 * ```js
	 * function(geoJsonPoint, latlng) {
	 * 	return L.marker(latlng);
	 * }
	 * ```
	 *
	 * @option style: Function = *
	 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
	 * called internally when data is added.
	 * The default value is to not override any defaults:
	 * ```js
	 * function (geoJsonFeature) {
	 * 	return {}
	 * }
	 * ```
	 *
	 * @option onEachFeature: Function = *
	 * A `Function` that will be called once for each created `Feature`, after it has
	 * been created and styled. Useful for attaching events and popups to features.
	 * The default is to do nothing with the newly created layers:
	 * ```js
	 * function (feature, layer) {}
	 * ```
	 *
	 * @option filter: Function = *
	 * A `Function` that will be used to decide whether to include a feature or not.
	 * The default is to include all features:
	 * ```js
	 * function (geoJsonFeature) {
	 * 	return true;
	 * }
	 * ```
	 * Note: dynamically changing the `filter` option will have effect only on newly
	 * added data. It will _not_ re-evaluate already included features.
	 *
	 * @option coordsToLatLng: Function = *
	 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
	 * The default is the `coordsToLatLng` static method.
	 */

	initialize: function (geojson, options) {
		L.setOptions(this, options);

		this._layers = {};

		if (geojson) {
			this.addData(geojson);
		}
	},

	// @method addData( <GeoJSON> data ): this
	// Adds a GeoJSON object to the layer.
	addData: function (geojson) {
		var features = L.Util.isArray(geojson) ? geojson : geojson.features,
		    i, len, feature;

		if (features) {
			for (i = 0, len = features.length; i < len; i++) {
				// only add this if geometry or geometries are set and not null
				feature = features[i];
				if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
					this.addData(feature);
				}
			}
			return this;
		}

		var options = this.options;

		if (options.filter && !options.filter(geojson)) { return this; }

		var layer = L.GeoJSON.geometryToLayer(geojson, options);
		if (!layer) {
			return this;
		}
		layer.feature = L.GeoJSON.asFeature(geojson);

		layer.defaultOptions = layer.options;
		this.resetStyle(layer);

		if (options.onEachFeature) {
			options.onEachFeature(geojson, layer);
		}

		return this.addLayer(layer);
	},

	// @method resetStyle( <Path> layer ): this
	// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
	resetStyle: function (layer) {
		// reset any custom styles
		layer.options = L.Util.extend({}, layer.defaultOptions);
		this._setLayerStyle(layer, this.options.style);
		return this;
	},

	// @method setStyle( <Function> style ): this
	// Changes styles of GeoJSON vector layers with the given style function.
	setStyle: function (style) {
		return this.eachLayer(function (layer) {
			this._setLayerStyle(layer, style);
		}, this);
	},

	_setLayerStyle: function (layer, style) {
		if (typeof style === 'function') {
			style = style(layer.feature);
		}
		if (layer.setStyle) {
			layer.setStyle(style);
		}
	}
});

// @section
// There are several static functions which can be called without instantiating L.GeoJSON:
L.extend(L.GeoJSON, {
	// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
	// Creates a `Layer` from a given GeoJSON feature. Can use a custom
	// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
	// functions if provided as options.
	geometryToLayer: function (geojson, options) {

		var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
		    coords = geometry ? geometry.coordinates : null,
		    layers = [],
		    pointToLayer = options && options.pointToLayer,
		    coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
		    latlng, latlngs, i, len;

		if (!coords && !geometry) {
			return null;
		}

		switch (geometry.type) {
		case 'Point':
			latlng = coordsToLatLng(coords);
			return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);

		case 'MultiPoint':
			for (i = 0, len = coords.length; i < len; i++) {
				latlng = coordsToLatLng(coords[i]);
				layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
			}
			return new L.FeatureGroup(layers);

		case 'LineString':
		case 'MultiLineString':
			latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
			return new L.Polyline(latlngs, options);

		case 'Polygon':
		case 'MultiPolygon':
			latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
			return new L.Polygon(latlngs, options);

		case 'GeometryCollection':
			for (i = 0, len = geometry.geometries.length; i < len; i++) {
				var layer = this.geometryToLayer({
					geometry: geometry.geometries[i],
					type: 'Feature',
					properties: geojson.properties
				}, options);

				if (layer) {
					layers.push(layer);
				}
			}
			return new L.FeatureGroup(layers);

		default:
			throw new Error('Invalid GeoJSON object.');
		}
	},

	// @function coordsToLatLng(coords: Array): LatLng
	// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
	// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
	coordsToLatLng: function (coords) {
		return new L.LatLng(coords[1], coords[0], coords[2]);
	},

	// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
	// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
	// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
	// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
	coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
		var latlngs = [];

		for (var i = 0, len = coords.length, latlng; i < len; i++) {
			latlng = levelsDeep ?
			        this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
			        (coordsToLatLng || this.coordsToLatLng)(coords[i]);

			latlngs.push(latlng);
		}

		return latlngs;
	},

	// @function latLngToCoords(latlng: LatLng): Array
	// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
	latLngToCoords: function (latlng) {
		return latlng.alt !== undefined ?
				[latlng.lng, latlng.lat, latlng.alt] :
				[latlng.lng, latlng.lat];
	},

	// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
	// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
	// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
	latLngsToCoords: function (latlngs, levelsDeep, closed) {
		var coords = [];

		for (var i = 0, len = latlngs.length; i < len; i++) {
			coords.push(levelsDeep ?
				L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
				L.GeoJSON.latLngToCoords(latlngs[i]));
		}

		if (!levelsDeep && closed) {
			coords.push(coords[0]);
		}

		return coords;
	},

	getFeature: function (layer, newGeometry) {
		return layer.feature ?
				L.extend({}, layer.feature, {geometry: newGeometry}) :
				L.GeoJSON.asFeature(newGeometry);
	},

	// @function asFeature(geojson: Object): Object
	// Normalize GeoJSON geometries/features into GeoJSON features.
	asFeature: function (geojson) {
		if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
			return geojson;
		}

		return {
			type: 'Feature',
			properties: {},
			geometry: geojson
		};
	}
});

var PointToGeoJSON = {
	toGeoJSON: function () {
		return L.GeoJSON.getFeature(this, {
			type: 'Point',
			coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
		});
	}
};

// @namespace Marker
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
L.Marker.include(PointToGeoJSON);

// @namespace CircleMarker
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
L.Circle.include(PointToGeoJSON);
L.CircleMarker.include(PointToGeoJSON);


// @namespace Polyline
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
L.Polyline.prototype.toGeoJSON = function () {
	var multi = !L.Polyline._flat(this._latlngs);

	var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);

	return L.GeoJSON.getFeature(this, {
		type: (multi ? 'Multi' : '') + 'LineString',
		coordinates: coords
	});
};

// @namespace Polygon
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
L.Polygon.prototype.toGeoJSON = function () {
	var holes = !L.Polyline._flat(this._latlngs),
	    multi = holes && !L.Polyline._flat(this._latlngs[0]);

	var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);

	if (!holes) {
		coords = [coords];
	}

	return L.GeoJSON.getFeature(this, {
		type: (multi ? 'Multi' : '') + 'Polygon',
		coordinates: coords
	});
};


// @namespace LayerGroup
L.LayerGroup.include({
	toMultiPoint: function () {
		var coords = [];

		this.eachLayer(function (layer) {
			coords.push(layer.toGeoJSON().geometry.coordinates);
		});

		return L.GeoJSON.getFeature(this, {
			type: 'MultiPoint',
			coordinates: coords
		});
	},

	// @method toGeoJSON(): Object
	// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
	toGeoJSON: function () {

		var type = this.feature && this.feature.geometry && this.feature.geometry.type;

		if (type === 'MultiPoint') {
			return this.toMultiPoint();
		}

		var isGeometryCollection = type === 'GeometryCollection',
		    jsons = [];

		this.eachLayer(function (layer) {
			if (layer.toGeoJSON) {
				var json = layer.toGeoJSON();
				jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
			}
		});

		if (isGeometryCollection) {
			return L.GeoJSON.getFeature(this, {
				geometries: jsons,
				type: 'GeometryCollection'
			});
		}

		return {
			type: 'FeatureCollection',
			features: jsons
		};
	}
});

// @namespace GeoJSON
// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
// Creates a GeoJSON layer. Optionally accepts an object in
// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
// (you can alternatively add it later with `addData` method) and an `options` object.
L.geoJSON = function (geojson, options) {
	return new L.GeoJSON(geojson, options);
};
// Backward compatibility.
L.geoJson = L.geoJSON;



/*
 * @class Draggable
 * @aka L.Draggable
 * @inherits Evented
 *
 * A class for making DOM elements draggable (including touch support).
 * Used internally for map and marker dragging. Only works for elements
 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
 *
 * @example
 * ```js
 * var draggable = new L.Draggable(elementToDrag);
 * draggable.enable();
 * ```
 */

L.Draggable = L.Evented.extend({

	options: {
		// @option clickTolerance: Number = 3
		// The max number of pixels a user can shift the mouse pointer during a click
		// for it to be considered a valid click (as opposed to a mouse drag).
		clickTolerance: 3
	},

	statics: {
		START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
		END: {
			mousedown: 'mouseup',
			touchstart: 'touchend',
			pointerdown: 'touchend',
			MSPointerDown: 'touchend'
		},
		MOVE: {
			mousedown: 'mousemove',
			touchstart: 'touchmove',
			pointerdown: 'touchmove',
			MSPointerDown: 'touchmove'
		}
	},

	// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
	// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
	initialize: function (element, dragStartTarget, preventOutline) {
		this._element = element;
		this._dragStartTarget = dragStartTarget || element;
		this._preventOutline = preventOutline;
	},

	// @method enable()
	// Enables the dragging ability
	enable: function () {
		if (this._enabled) { return; }

		L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);

		this._enabled = true;
	},

	// @method disable()
	// Disables the dragging ability
	disable: function () {
		if (!this._enabled) { return; }

		// If we're currently dragging this draggable,
		// disabling it counts as first ending the drag.
		if (L.Draggable._dragging === this) {
			this.finishDrag();
		}

		L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);

		this._enabled = false;
		this._moved = false;
	},

	_onDown: function (e) {
		// Ignore simulated events, since we handle both touch and
		// mouse explicitly; otherwise we risk getting duplicates of
		// touch events, see #4315.
		// Also ignore the event if disabled; this happens in IE11
		// under some circumstances, see #3666.
		if (e._simulated || !this._enabled) { return; }

		this._moved = false;

		if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }

		if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
		L.Draggable._dragging = this;  // Prevent dragging multiple objects at once.

		if (this._preventOutline) {
			L.DomUtil.preventOutline(this._element);
		}

		L.DomUtil.disableImageDrag();
		L.DomUtil.disableTextSelection();

		if (this._moving) { return; }

		// @event down: Event
		// Fired when a drag is about to start.
		this.fire('down');

		var first = e.touches ? e.touches[0] : e;

		this._startPoint = new L.Point(first.clientX, first.clientY);

		L.DomEvent
			.on(document, L.Draggable.MOVE[e.type], this._onMove, this)
			.on(document, L.Draggable.END[e.type], this._onUp, this);
	},

	_onMove: function (e) {
		// Ignore simulated events, since we handle both touch and
		// mouse explicitly; otherwise we risk getting duplicates of
		// touch events, see #4315.
		// Also ignore the event if disabled; this happens in IE11
		// under some circumstances, see #3666.
		if (e._simulated || !this._enabled) { return; }

		if (e.touches && e.touches.length > 1) {
			this._moved = true;
			return;
		}

		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
		    newPoint = new L.Point(first.clientX, first.clientY),
		    offset = newPoint.subtract(this._startPoint);

		if (!offset.x && !offset.y) { return; }
		if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }

		L.DomEvent.preventDefault(e);

		if (!this._moved) {
			// @event dragstart: Event
			// Fired when a drag starts
			this.fire('dragstart');

			this._moved = true;
			this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);

			L.DomUtil.addClass(document.body, 'leaflet-dragging');

			this._lastTarget = e.target || e.srcElement;
			// IE and Edge do not give the <use> element, so fetch it
			// if necessary
			if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
				this._lastTarget = this._lastTarget.correspondingUseElement;
			}
			L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
		}

		this._newPos = this._startPos.add(offset);
		this._moving = true;

		L.Util.cancelAnimFrame(this._animRequest);
		this._lastEvent = e;
		this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
	},

	_updatePosition: function () {
		var e = {originalEvent: this._lastEvent};

		// @event predrag: Event
		// Fired continuously during dragging *before* each corresponding
		// update of the element's position.
		this.fire('predrag', e);
		L.DomUtil.setPosition(this._element, this._newPos);

		// @event drag: Event
		// Fired continuously during dragging.
		this.fire('drag', e);
	},

	_onUp: function (e) {
		// Ignore simulated events, since we handle both touch and
		// mouse explicitly; otherwise we risk getting duplicates of
		// touch events, see #4315.
		// Also ignore the event if disabled; this happens in IE11
		// under some circumstances, see #3666.
		if (e._simulated || !this._enabled) { return; }
		this.finishDrag();
	},

	finishDrag: function () {
		L.DomUtil.removeClass(document.body, 'leaflet-dragging');

		if (this._lastTarget) {
			L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
			this._lastTarget = null;
		}

		for (var i in L.Draggable.MOVE) {
			L.DomEvent
				.off(document, L.Draggable.MOVE[i], this._onMove, this)
				.off(document, L.Draggable.END[i], this._onUp, this);
		}

		L.DomUtil.enableImageDrag();
		L.DomUtil.enableTextSelection();

		if (this._moved && this._moving) {
			// ensure drag is not fired after dragend
			L.Util.cancelAnimFrame(this._animRequest);

			// @event dragend: DragEndEvent
			// Fired when the drag ends.
			this.fire('dragend', {
				distance: this._newPos.distanceTo(this._startPos)
			});
		}

		this._moving = false;
		L.Draggable._dragging = false;
	}

});



/*
	L.Handler is a base class for handler classes that are used internally to inject
	interaction features like dragging to classes like Map and Marker.
*/

// @class Handler
// @aka L.Handler
// Abstract class for map interaction handlers

L.Handler = L.Class.extend({
	initialize: function (map) {
		this._map = map;
	},

	// @method enable(): this
	// Enables the handler
	enable: function () {
		if (this._enabled) { return this; }

		this._enabled = true;
		this.addHooks();
		return this;
	},

	// @method disable(): this
	// Disables the handler
	disable: function () {
		if (!this._enabled) { return this; }

		this._enabled = false;
		this.removeHooks();
		return this;
	},

	// @method enabled(): Boolean
	// Returns `true` if the handler is enabled
	enabled: function () {
		return !!this._enabled;
	}

	// @section Extension methods
	// Classes inheriting from `Handler` must implement the two following methods:
	// @method addHooks()
	// Called when the handler is enabled, should add event hooks.
	// @method removeHooks()
	// Called when the handler is disabled, should remove the event hooks added previously.
});



/*
 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
 */

// @namespace Map
// @section Interaction Options
L.Map.mergeOptions({
	// @option dragging: Boolean = true
	// Whether the map be draggable with mouse/touch or not.
	dragging: true,

	// @section Panning Inertia Options
	// @option inertia: Boolean = *
	// If enabled, panning of the map will have an inertia effect where
	// the map builds momentum while dragging and continues moving in
	// the same direction for some time. Feels especially nice on touch
	// devices. Enabled by default unless running on old Android devices.
	inertia: !L.Browser.android23,

	// @option inertiaDeceleration: Number = 3000
	// The rate with which the inertial movement slows down, in pixels/second².
	inertiaDeceleration: 3400, // px/s^2

	// @option inertiaMaxSpeed: Number = Infinity
	// Max speed of the inertial movement, in pixels/second.
	inertiaMaxSpeed: Infinity, // px/s

	// @option easeLinearity: Number = 0.2
	easeLinearity: 0.2,

	// TODO refactor, move to CRS
	// @option worldCopyJump: Boolean = false
	// With this option enabled, the map tracks when you pan to another "copy"
	// of the world and seamlessly jumps to the original one so that all overlays
	// like markers and vector layers are still visible.
	worldCopyJump: false,

	// @option maxBoundsViscosity: Number = 0.0
	// If `maxBounds` is set, this option will control how solid the bounds
	// are when dragging the map around. The default value of `0.0` allows the
	// user to drag outside the bounds at normal speed, higher values will
	// slow down map dragging outside bounds, and `1.0` makes the bounds fully
	// solid, preventing the user from dragging outside the bounds.
	maxBoundsViscosity: 0.0
});

L.Map.Drag = L.Handler.extend({
	addHooks: function () {
		if (!this._draggable) {
			var map = this._map;

			this._draggable = new L.Draggable(map._mapPane, map._container);

			this._draggable.on({
				down: this._onDown,
				dragstart: this._onDragStart,
				drag: this._onDrag,
				dragend: this._onDragEnd
			}, this);

			this._draggable.on('predrag', this._onPreDragLimit, this);
			if (map.options.worldCopyJump) {
				this._draggable.on('predrag', this._onPreDragWrap, this);
				map.on('zoomend', this._onZoomEnd, this);

				map.whenReady(this._onZoomEnd, this);
			}
		}
		L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
		this._draggable.enable();
		this._positions = [];
		this._times = [];
	},

	removeHooks: function () {
		L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
		L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
		this._draggable.disable();
	},

	moved: function () {
		return this._draggable && this._draggable._moved;
	},

	moving: function () {
		return this._draggable && this._draggable._moving;
	},

	_onDown: function () {
		this._map._stop();
	},

	_onDragStart: function () {
		var map = this._map;

		if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
			var bounds = L.latLngBounds(this._map.options.maxBounds);

			this._offsetLimit = L.bounds(
				this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
				this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
					.add(this._map.getSize()));

			this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
		} else {
			this._offsetLimit = null;
		}

		map
		    .fire('movestart')
		    .fire('dragstart');

		if (map.options.inertia) {
			this._positions = [];
			this._times = [];
		}
	},

	_onDrag: function (e) {
		if (this._map.options.inertia) {
			var time = this._lastTime = +new Date(),
			    pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;

			this._positions.push(pos);
			this._times.push(time);

			if (time - this._times[0] > 50) {
				this._positions.shift();
				this._times.shift();
			}
		}

		this._map
		    .fire('move', e)
		    .fire('drag', e);
	},

	_onZoomEnd: function () {
		var pxCenter = this._map.getSize().divideBy(2),
		    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);

		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
		this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
	},

	_viscousLimit: function (value, threshold) {
		return value - (value - threshold) * this._viscosity;
	},

	_onPreDragLimit: function () {
		if (!this._viscosity || !this._offsetLimit) { return; }

		var offset = this._draggable._newPos.subtract(this._draggable._startPos);

		var limit = this._offsetLimit;
		if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
		if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
		if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
		if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }

		this._draggable._newPos = this._draggable._startPos.add(offset);
	},

	_onPreDragWrap: function () {
		// TODO refactor to be able to adjust map pane position after zoom
		var worldWidth = this._worldWidth,
		    halfWidth = Math.round(worldWidth / 2),
		    dx = this._initialWorldOffset,
		    x = this._draggable._newPos.x,
		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;

		this._draggable._absPos = this._draggable._newPos.clone();
		this._draggable._newPos.x = newX;
	},

	_onDragEnd: function (e) {
		var map = this._map,
		    options = map.options,

		    noInertia = !options.inertia || this._times.length < 2;

		map.fire('dragend', e);

		if (noInertia) {
			map.fire('moveend');

		} else {

			var direction = this._lastPos.subtract(this._positions[0]),
			    duration = (this._lastTime - this._times[0]) / 1000,
			    ease = options.easeLinearity,

			    speedVector = direction.multiplyBy(ease / duration),
			    speed = speedVector.distanceTo([0, 0]),

			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),

			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();

			if (!offset.x && !offset.y) {
				map.fire('moveend');

			} else {
				offset = map._limitOffset(offset, map.options.maxBounds);

				L.Util.requestAnimFrame(function () {
					map.panBy(offset, {
						duration: decelerationDuration,
						easeLinearity: ease,
						noMoveStart: true,
						animate: true
					});
				});
			}
		}
	}
});

// @section Handlers
// @property dragging: Handler
// Map dragging handler (by both mouse and touch).
L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);



/*
 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
 */

// @namespace Map
// @section Interaction Options

L.Map.mergeOptions({
	// @option doubleClickZoom: Boolean|String = true
	// Whether the map can be zoomed in by double clicking on it and
	// zoomed out by double clicking while holding shift. If passed
	// `'center'`, double-click zoom will zoom to the center of the
	//  view regardless of where the mouse was.
	doubleClickZoom: true
});

L.Map.DoubleClickZoom = L.Handler.extend({
	addHooks: function () {
		this._map.on('dblclick', this._onDoubleClick, this);
	},

	removeHooks: function () {
		this._map.off('dblclick', this._onDoubleClick, this);
	},

	_onDoubleClick: function (e) {
		var map = this._map,
		    oldZoom = map.getZoom(),
		    delta = map.options.zoomDelta,
		    zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;

		if (map.options.doubleClickZoom === 'center') {
			map.setZoom(zoom);
		} else {
			map.setZoomAround(e.containerPoint, zoom);
		}
	}
});

// @section Handlers
//
// Map properties include interaction handlers that allow you to control
// interaction behavior in runtime, enabling or disabling certain features such
// as dragging or touch zoom (see `Handler` methods). For example:
//
// ```js
// map.doubleClickZoom.disable();
// ```
//
// @property doubleClickZoom: Handler
// Double click zoom handler.
L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);



/*
 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
 */

// @namespace Map
// @section Interaction Options
L.Map.mergeOptions({
	// @section Mousewheel options
	// @option scrollWheelZoom: Boolean|String = true
	// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
	// it will zoom to the center of the view regardless of where the mouse was.
	scrollWheelZoom: true,

	// @option wheelDebounceTime: Number = 40
	// Limits the rate at which a wheel can fire (in milliseconds). By default
	// user can't zoom via wheel more often than once per 40 ms.
	wheelDebounceTime: 40,

	// @option wheelPxPerZoomLevel: Number = 60
	// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
	// mean a change of one full zoom level. Smaller values will make wheel-zooming
	// faster (and vice versa).
	wheelPxPerZoomLevel: 60
});

L.Map.ScrollWheelZoom = L.Handler.extend({
	addHooks: function () {
		L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);

		this._delta = 0;
	},

	removeHooks: function () {
		L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
	},

	_onWheelScroll: function (e) {
		var delta = L.DomEvent.getWheelDelta(e);

		var debounce = this._map.options.wheelDebounceTime;

		this._delta += delta;
		this._lastMousePos = this._map.mouseEventToContainerPoint(e);

		if (!this._startTime) {
			this._startTime = +new Date();
		}

		var left = Math.max(debounce - (+new Date() - this._startTime), 0);

		clearTimeout(this._timer);
		this._timer = setTimeout(L.bind(this._performZoom, this), left);

		L.DomEvent.stop(e);
	},

	_performZoom: function () {
		var map = this._map,
		    zoom = map.getZoom(),
		    snap = this._map.options.zoomSnap || 0;

		map._stop(); // stop panning and fly animations if any

		// map the delta with a sigmoid function to -4..4 range leaning on -1..1
		var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
		    d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
		    d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
		    delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;

		this._delta = 0;
		this._startTime = null;

		if (!delta) { return; }

		if (map.options.scrollWheelZoom === 'center') {
			map.setZoom(zoom + delta);
		} else {
			map.setZoomAround(this._lastMousePos, zoom + delta);
		}
	}
});

// @section Handlers
// @property scrollWheelZoom: Handler
// Scroll wheel zoom handler.
L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);



/*
 * Extends the event handling code with double tap support for mobile browsers.
 */

L.extend(L.DomEvent, {

	_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
	_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',

	// inspired by Zepto touch code by Thomas Fuchs
	addDoubleTapListener: function (obj, handler, id) {
		var last, touch,
		    doubleTap = false,
		    delay = 250;

		function onTouchStart(e) {
			var count;

			if (L.Browser.pointer) {
				if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
				count = L.DomEvent._pointersCount;
			} else {
				count = e.touches.length;
			}

			if (count > 1) { return; }

			var now = Date.now(),
			    delta = now - (last || now);

			touch = e.touches ? e.touches[0] : e;
			doubleTap = (delta > 0 && delta <= delay);
			last = now;
		}

		function onTouchEnd(e) {
			if (doubleTap && !touch.cancelBubble) {
				if (L.Browser.pointer) {
					if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }

					// work around .type being readonly with MSPointer* events
					var newTouch = {},
					    prop, i;

					for (i in touch) {
						prop = touch[i];
						newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
					}
					touch = newTouch;
				}
				touch.type = 'dblclick';
				handler(touch);
				last = null;
			}
		}

		var pre = '_leaflet_',
		    touchstart = this._touchstart,
		    touchend = this._touchend;

		obj[pre + touchstart + id] = onTouchStart;
		obj[pre + touchend + id] = onTouchEnd;
		obj[pre + 'dblclick' + id] = handler;

		obj.addEventListener(touchstart, onTouchStart, false);
		obj.addEventListener(touchend, onTouchEnd, false);

		// On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
		// the browser doesn't fire touchend/pointerup events but does fire
		// native dblclicks. See #4127.
		// Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
		obj.addEventListener('dblclick', handler, false);

		return this;
	},

	removeDoubleTapListener: function (obj, id) {
		var pre = '_leaflet_',
		    touchstart = obj[pre + this._touchstart + id],
		    touchend = obj[pre + this._touchend + id],
		    dblclick = obj[pre + 'dblclick' + id];

		obj.removeEventListener(this._touchstart, touchstart, false);
		obj.removeEventListener(this._touchend, touchend, false);
		if (!L.Browser.edge) {
			obj.removeEventListener('dblclick', dblclick, false);
		}

		return this;
	}
});



/*
 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
 */

L.extend(L.DomEvent, {

	POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
	POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
	POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
	POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
	TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],

	_pointers: {},
	_pointersCount: 0,

	// Provides a touch events wrapper for (ms)pointer events.
	// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890

	addPointerListener: function (obj, type, handler, id) {

		if (type === 'touchstart') {
			this._addPointerStart(obj, handler, id);

		} else if (type === 'touchmove') {
			this._addPointerMove(obj, handler, id);

		} else if (type === 'touchend') {
			this._addPointerEnd(obj, handler, id);
		}

		return this;
	},

	removePointerListener: function (obj, type, id) {
		var handler = obj['_leaflet_' + type + id];

		if (type === 'touchstart') {
			obj.removeEventListener(this.POINTER_DOWN, handler, false);

		} else if (type === 'touchmove') {
			obj.removeEventListener(this.POINTER_MOVE, handler, false);

		} else if (type === 'touchend') {
			obj.removeEventListener(this.POINTER_UP, handler, false);
			obj.removeEventListener(this.POINTER_CANCEL, handler, false);
		}

		return this;
	},

	_addPointerStart: function (obj, handler, id) {
		var onDown = L.bind(function (e) {
			if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
				// In IE11, some touch events needs to fire for form controls, or
				// the controls will stop working. We keep a whitelist of tag names that
				// need these events. For other target tags, we prevent default on the event.
				if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
					L.DomEvent.preventDefault(e);
				} else {
					return;
				}
			}

			this._handlePointer(e, handler);
		}, this);

		obj['_leaflet_touchstart' + id] = onDown;
		obj.addEventListener(this.POINTER_DOWN, onDown, false);

		// need to keep track of what pointers and how many are active to provide e.touches emulation
		if (!this._pointerDocListener) {
			var pointerUp = L.bind(this._globalPointerUp, this);

			// we listen documentElement as any drags that end by moving the touch off the screen get fired there
			document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
			document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
			document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
			document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);

			this._pointerDocListener = true;
		}
	},

	_globalPointerDown: function (e) {
		this._pointers[e.pointerId] = e;
		this._pointersCount++;
	},

	_globalPointerMove: function (e) {
		if (this._pointers[e.pointerId]) {
			this._pointers[e.pointerId] = e;
		}
	},

	_globalPointerUp: function (e) {
		delete this._pointers[e.pointerId];
		this._pointersCount--;
	},

	_handlePointer: function (e, handler) {
		e.touches = [];
		for (var i in this._pointers) {
			e.touches.push(this._pointers[i]);
		}
		e.changedTouches = [e];

		handler(e);
	},

	_addPointerMove: function (obj, handler, id) {
		var onMove = L.bind(function (e) {
			// don't fire touch moves when mouse isn't down
			if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }

			this._handlePointer(e, handler);
		}, this);

		obj['_leaflet_touchmove' + id] = onMove;
		obj.addEventListener(this.POINTER_MOVE, onMove, false);
	},

	_addPointerEnd: function (obj, handler, id) {
		var onUp = L.bind(function (e) {
			this._handlePointer(e, handler);
		}, this);

		obj['_leaflet_touchend' + id] = onUp;
		obj.addEventListener(this.POINTER_UP, onUp, false);
		obj.addEventListener(this.POINTER_CANCEL, onUp, false);
	}
});



/*
 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
 */

// @namespace Map
// @section Interaction Options
L.Map.mergeOptions({
	// @section Touch interaction options
	// @option touchZoom: Boolean|String = *
	// Whether the map can be zoomed by touch-dragging with two fingers. If
	// passed `'center'`, it will zoom to the center of the view regardless of
	// where the touch events (fingers) were. Enabled for touch-capable web
	// browsers except for old Androids.
	touchZoom: L.Browser.touch && !L.Browser.android23,

	// @option bounceAtZoomLimits: Boolean = true
	// Set it to false if you don't want the map to zoom beyond min/max zoom
	// and then bounce back when pinch-zooming.
	bounceAtZoomLimits: true
});

L.Map.TouchZoom = L.Handler.extend({
	addHooks: function () {
		L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
		L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
	},

	removeHooks: function () {
		L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
		L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
	},

	_onTouchStart: function (e) {
		var map = this._map;
		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }

		var p1 = map.mouseEventToContainerPoint(e.touches[0]),
		    p2 = map.mouseEventToContainerPoint(e.touches[1]);

		this._centerPoint = map.getSize()._divideBy(2);
		this._startLatLng = map.containerPointToLatLng(this._centerPoint);
		if (map.options.touchZoom !== 'center') {
			this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
		}

		this._startDist = p1.distanceTo(p2);
		this._startZoom = map.getZoom();

		this._moved = false;
		this._zooming = true;

		map._stop();

		L.DomEvent
		    .on(document, 'touchmove', this._onTouchMove, this)
		    .on(document, 'touchend', this._onTouchEnd, this);

		L.DomEvent.preventDefault(e);
	},

	_onTouchMove: function (e) {
		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }

		var map = this._map,
		    p1 = map.mouseEventToContainerPoint(e.touches[0]),
		    p2 = map.mouseEventToContainerPoint(e.touches[1]),
		    scale = p1.distanceTo(p2) / this._startDist;


		this._zoom = map.getScaleZoom(scale, this._startZoom);

		if (!map.options.bounceAtZoomLimits && (
			(this._zoom < map.getMinZoom() && scale < 1) ||
			(this._zoom > map.getMaxZoom() && scale > 1))) {
			this._zoom = map._limitZoom(this._zoom);
		}

		if (map.options.touchZoom === 'center') {
			this._center = this._startLatLng;
			if (scale === 1) { return; }
		} else {
			// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
			var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
			if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
			this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
		}

		if (!this._moved) {
			map._moveStart(true);
			this._moved = true;
		}

		L.Util.cancelAnimFrame(this._animRequest);

		var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
		this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);

		L.DomEvent.preventDefault(e);
	},

	_onTouchEnd: function () {
		if (!this._moved || !this._zooming) {
			this._zooming = false;
			return;
		}

		this._zooming = false;
		L.Util.cancelAnimFrame(this._animRequest);

		L.DomEvent
		    .off(document, 'touchmove', this._onTouchMove)
		    .off(document, 'touchend', this._onTouchEnd);

		// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
		if (this._map.options.zoomAnimation) {
			this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
		} else {
			this._map._resetView(this._center, this._map._limitZoom(this._zoom));
		}
	}
});

// @section Handlers
// @property touchZoom: Handler
// Touch zoom handler.
L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);



/*
 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
 */

// @namespace Map
// @section Interaction Options
L.Map.mergeOptions({
	// @section Touch interaction options
	// @option tap: Boolean = true
	// Enables mobile hacks for supporting instant taps (fixing 200ms click
	// delay on iOS/Android) and touch holds (fired as `contextmenu` events).
	tap: true,

	// @option tapTolerance: Number = 15
	// The max number of pixels a user can shift his finger during touch
	// for it to be considered a valid tap.
	tapTolerance: 15
});

L.Map.Tap = L.Handler.extend({
	addHooks: function () {
		L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
	},

	removeHooks: function () {
		L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
	},

	_onDown: function (e) {
		if (!e.touches) { return; }

		L.DomEvent.preventDefault(e);

		this._fireClick = true;

		// don't simulate click or track longpress if more than 1 touch
		if (e.touches.length > 1) {
			this._fireClick = false;
			clearTimeout(this._holdTimeout);
			return;
		}

		var first = e.touches[0],
		    el = first.target;

		this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);

		// if touching a link, highlight it
		if (el.tagName && el.tagName.toLowerCase() === 'a') {
			L.DomUtil.addClass(el, 'leaflet-active');
		}

		// simulate long hold but setting a timeout
		this._holdTimeout = setTimeout(L.bind(function () {
			if (this._isTapValid()) {
				this._fireClick = false;
				this._onUp();
				this._simulateEvent('contextmenu', first);
			}
		}, this), 1000);

		this._simulateEvent('mousedown', first);

		L.DomEvent.on(document, {
			touchmove: this._onMove,
			touchend: this._onUp
		}, this);
	},

	_onUp: function (e) {
		clearTimeout(this._holdTimeout);

		L.DomEvent.off(document, {
			touchmove: this._onMove,
			touchend: this._onUp
		}, this);

		if (this._fireClick && e && e.changedTouches) {

			var first = e.changedTouches[0],
			    el = first.target;

			if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
				L.DomUtil.removeClass(el, 'leaflet-active');
			}

			this._simulateEvent('mouseup', first);

			// simulate click if the touch didn't move too much
			if (this._isTapValid()) {
				this._simulateEvent('click', first);
			}
		}
	},

	_isTapValid: function () {
		return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
	},

	_onMove: function (e) {
		var first = e.touches[0];
		this._newPos = new L.Point(first.clientX, first.clientY);
		this._simulateEvent('mousemove', first);
	},

	_simulateEvent: function (type, e) {
		var simulatedEvent = document.createEvent('MouseEvents');

		simulatedEvent._simulated = true;
		e.target._simulatedClick = true;

		simulatedEvent.initMouseEvent(
		        type, true, true, window, 1,
		        e.screenX, e.screenY,
		        e.clientX, e.clientY,
		        false, false, false, false, 0, null);

		e.target.dispatchEvent(simulatedEvent);
	}
});

// @section Handlers
// @property tap: Handler
// Mobile touch hacks (quick tap and touch hold) handler.
if (L.Browser.touch && !L.Browser.pointer) {
	L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
}



/*
 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
 * (zoom to a selected bounding box), enabled by default.
 */

// @namespace Map
// @section Interaction Options
L.Map.mergeOptions({
	// @option boxZoom: Boolean = true
	// Whether the map can be zoomed to a rectangular area specified by
	// dragging the mouse while pressing the shift key.
	boxZoom: true
});

L.Map.BoxZoom = L.Handler.extend({
	initialize: function (map) {
		this._map = map;
		this._container = map._container;
		this._pane = map._panes.overlayPane;
	},

	addHooks: function () {
		L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
	},

	removeHooks: function () {
		L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
	},

	moved: function () {
		return this._moved;
	},

	_resetState: function () {
		this._moved = false;
	},

	_onMouseDown: function (e) {
		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }

		this._resetState();

		L.DomUtil.disableTextSelection();
		L.DomUtil.disableImageDrag();

		this._startPoint = this._map.mouseEventToContainerPoint(e);

		L.DomEvent.on(document, {
			contextmenu: L.DomEvent.stop,
			mousemove: this._onMouseMove,
			mouseup: this._onMouseUp,
			keydown: this._onKeyDown
		}, this);
	},

	_onMouseMove: function (e) {
		if (!this._moved) {
			this._moved = true;

			this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
			L.DomUtil.addClass(this._container, 'leaflet-crosshair');

			this._map.fire('boxzoomstart');
		}

		this._point = this._map.mouseEventToContainerPoint(e);

		var bounds = new L.Bounds(this._point, this._startPoint),
		    size = bounds.getSize();

		L.DomUtil.setPosition(this._box, bounds.min);

		this._box.style.width  = size.x + 'px';
		this._box.style.height = size.y + 'px';
	},

	_finish: function () {
		if (this._moved) {
			L.DomUtil.remove(this._box);
			L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
		}

		L.DomUtil.enableTextSelection();
		L.DomUtil.enableImageDrag();

		L.DomEvent.off(document, {
			contextmenu: L.DomEvent.stop,
			mousemove: this._onMouseMove,
			mouseup: this._onMouseUp,
			keydown: this._onKeyDown
		}, this);
	},

	_onMouseUp: function (e) {
		if ((e.which !== 1) && (e.button !== 1)) { return; }

		this._finish();

		if (!this._moved) { return; }
		// Postpone to next JS tick so internal click event handling
		// still see it as "moved".
		setTimeout(L.bind(this._resetState, this), 0);

		var bounds = new L.LatLngBounds(
		        this._map.containerPointToLatLng(this._startPoint),
		        this._map.containerPointToLatLng(this._point));

		this._map
			.fitBounds(bounds)
			.fire('boxzoomend', {boxZoomBounds: bounds});
	},

	_onKeyDown: function (e) {
		if (e.keyCode === 27) {
			this._finish();
		}
	}
});

// @section Handlers
// @property boxZoom: Handler
// Box (shift-drag with mouse) zoom handler.
L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);



/*
 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
 */

// @namespace Map
// @section Keyboard Navigation Options
L.Map.mergeOptions({
	// @option keyboard: Boolean = true
	// Makes the map focusable and allows users to navigate the map with keyboard
	// arrows and `+`/`-` keys.
	keyboard: true,

	// @option keyboardPanDelta: Number = 80
	// Amount of pixels to pan when pressing an arrow key.
	keyboardPanDelta: 80
});

L.Map.Keyboard = L.Handler.extend({

	keyCodes: {
		left:    [37],
		right:   [39],
		down:    [40],
		up:      [38],
		zoomIn:  [187, 107, 61, 171],
		zoomOut: [189, 109, 54, 173]
	},

	initialize: function (map) {
		this._map = map;

		this._setPanDelta(map.options.keyboardPanDelta);
		this._setZoomDelta(map.options.zoomDelta);
	},

	addHooks: function () {
		var container = this._map._container;

		// make the container focusable by tabbing
		if (container.tabIndex <= 0) {
			container.tabIndex = '0';
		}

		L.DomEvent.on(container, {
			focus: this._onFocus,
			blur: this._onBlur,
			mousedown: this._onMouseDown
		}, this);

		this._map.on({
			focus: this._addHooks,
			blur: this._removeHooks
		}, this);
	},

	removeHooks: function () {
		this._removeHooks();

		L.DomEvent.off(this._map._container, {
			focus: this._onFocus,
			blur: this._onBlur,
			mousedown: this._onMouseDown
		}, this);

		this._map.off({
			focus: this._addHooks,
			blur: this._removeHooks
		}, this);
	},

	_onMouseDown: function () {
		if (this._focused) { return; }

		var body = document.body,
		    docEl = document.documentElement,
		    top = body.scrollTop || docEl.scrollTop,
		    left = body.scrollLeft || docEl.scrollLeft;

		this._map._container.focus();

		window.scrollTo(left, top);
	},

	_onFocus: function () {
		this._focused = true;
		this._map.fire('focus');
	},

	_onBlur: function () {
		this._focused = false;
		this._map.fire('blur');
	},

	_setPanDelta: function (panDelta) {
		var keys = this._panKeys = {},
		    codes = this.keyCodes,
		    i, len;

		for (i = 0, len = codes.left.length; i < len; i++) {
			keys[codes.left[i]] = [-1 * panDelta, 0];
		}
		for (i = 0, len = codes.right.length; i < len; i++) {
			keys[codes.right[i]] = [panDelta, 0];
		}
		for (i = 0, len = codes.down.length; i < len; i++) {
			keys[codes.down[i]] = [0, panDelta];
		}
		for (i = 0, len = codes.up.length; i < len; i++) {
			keys[codes.up[i]] = [0, -1 * panDelta];
		}
	},

	_setZoomDelta: function (zoomDelta) {
		var keys = this._zoomKeys = {},
		    codes = this.keyCodes,
		    i, len;

		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
			keys[codes.zoomIn[i]] = zoomDelta;
		}
		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
			keys[codes.zoomOut[i]] = -zoomDelta;
		}
	},

	_addHooks: function () {
		L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
	},

	_removeHooks: function () {
		L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
	},

	_onKeyDown: function (e) {
		if (e.altKey || e.ctrlKey || e.metaKey) { return; }

		var key = e.keyCode,
		    map = this._map,
		    offset;

		if (key in this._panKeys) {

			if (map._panAnim && map._panAnim._inProgress) { return; }

			offset = this._panKeys[key];
			if (e.shiftKey) {
				offset = L.point(offset).multiplyBy(3);
			}

			map.panBy(offset);

			if (map.options.maxBounds) {
				map.panInsideBounds(map.options.maxBounds);
			}

		} else if (key in this._zoomKeys) {
			map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);

		} else if (key === 27) {
			map.closePopup();

		} else {
			return;
		}

		L.DomEvent.stop(e);
	}
});

// @section Handlers
// @section Handlers
// @property keyboard: Handler
// Keyboard navigation handler.
L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);



/*
 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
 */


/* @namespace Marker
 * @section Interaction handlers
 *
 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
 *
 * ```js
 * marker.dragging.disable();
 * ```
 *
 * @property dragging: Handler
 * Marker dragging handler (by both mouse and touch).
 */

L.Handler.MarkerDrag = L.Handler.extend({
	initialize: function (marker) {
		this._marker = marker;
	},

	addHooks: function () {
		var icon = this._marker._icon;

		if (!this._draggable) {
			this._draggable = new L.Draggable(icon, icon, true);
		}

		this._draggable.on({
			dragstart: this._onDragStart,
			drag: this._onDrag,
			dragend: this._onDragEnd
		}, this).enable();

		L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
	},

	removeHooks: function () {
		this._draggable.off({
			dragstart: this._onDragStart,
			drag: this._onDrag,
			dragend: this._onDragEnd
		}, this).disable();

		if (this._marker._icon) {
			L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
		}
	},

	moved: function () {
		return this._draggable && this._draggable._moved;
	},

	_onDragStart: function () {
		// @section Dragging events
		// @event dragstart: Event
		// Fired when the user starts dragging the marker.

		// @event movestart: Event
		// Fired when the marker starts moving (because of dragging).

		this._oldLatLng = this._marker.getLatLng();
		this._marker
		    .closePopup()
		    .fire('movestart')
		    .fire('dragstart');
	},

	_onDrag: function (e) {
		var marker = this._marker,
		    shadow = marker._shadow,
		    iconPos = L.DomUtil.getPosition(marker._icon),
		    latlng = marker._map.layerPointToLatLng(iconPos);

		// update shadow position
		if (shadow) {
			L.DomUtil.setPosition(shadow, iconPos);
		}

		marker._latlng = latlng;
		e.latlng = latlng;
		e.oldLatLng = this._oldLatLng;

		// @event drag: Event
		// Fired repeatedly while the user drags the marker.
		marker
		    .fire('move', e)
		    .fire('drag', e);
	},

	_onDragEnd: function (e) {
		// @event dragend: DragEndEvent
		// Fired when the user stops dragging the marker.

		// @event moveend: Event
		// Fired when the marker stops moving (because of dragging).
		delete this._oldLatLng;
		this._marker
		    .fire('moveend')
		    .fire('dragend', e);
	}
});



/*
 * @class Control
 * @aka L.Control
 * @inherits Class
 *
 * L.Control is a base class for implementing map controls. Handles positioning.
 * All other controls extend from this class.
 */

L.Control = L.Class.extend({
	// @section
	// @aka Control options
	options: {
		// @option position: String = 'topright'
		// The position of the control (one of the map corners). Possible values are `'topleft'`,
		// `'topright'`, `'bottomleft'` or `'bottomright'`
		position: 'topright'
	},

	initialize: function (options) {
		L.setOptions(this, options);
	},

	/* @section
	 * Classes extending L.Control will inherit the following methods:
	 *
	 * @method getPosition: string
	 * Returns the position of the control.
	 */
	getPosition: function () {
		return this.options.position;
	},

	// @method setPosition(position: string): this
	// Sets the position of the control.
	setPosition: function (position) {
		var map = this._map;

		if (map) {
			map.removeControl(this);
		}

		this.options.position = position;

		if (map) {
			map.addControl(this);
		}

		return this;
	},

	// @method getContainer: HTMLElement
	// Returns the HTMLElement that contains the control.
	getContainer: function () {
		return this._container;
	},

	// @method addTo(map: Map): this
	// Adds the control to the given map.
	addTo: function (map) {
		this.remove();
		this._map = map;

		var container = this._container = this.onAdd(map),
		    pos = this.getPosition(),
		    corner = map._controlCorners[pos];

		L.DomUtil.addClass(container, 'leaflet-control');

		if (pos.indexOf('bottom') !== -1) {
			corner.insertBefore(container, corner.firstChild);
		} else {
			corner.appendChild(container);
		}

		return this;
	},

	// @method remove: this
	// Removes the control from the map it is currently active on.
	remove: function () {
		if (!this._map) {
			return this;
		}

		L.DomUtil.remove(this._container);

		if (this.onRemove) {
			this.onRemove(this._map);
		}

		this._map = null;

		return this;
	},

	_refocusOnMap: function (e) {
		// if map exists and event is not a keyboard event
		if (this._map && e && e.screenX > 0 && e.screenY > 0) {
			this._map.getContainer().focus();
		}
	}
});

L.control = function (options) {
	return new L.Control(options);
};

/* @section Extension methods
 * @uninheritable
 *
 * Every control should extend from `L.Control` and (re-)implement the following methods.
 *
 * @method onAdd(map: Map): HTMLElement
 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
 *
 * @method onRemove(map: Map)
 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
 */

/* @namespace Map
 * @section Methods for Layers and Controls
 */
L.Map.include({
	// @method addControl(control: Control): this
	// Adds the given control to the map
	addControl: function (control) {
		control.addTo(this);
		return this;
	},

	// @method removeControl(control: Control): this
	// Removes the given control from the map
	removeControl: function (control) {
		control.remove();
		return this;
	},

	_initControlPos: function () {
		var corners = this._controlCorners = {},
		    l = 'leaflet-',
		    container = this._controlContainer =
		            L.DomUtil.create('div', l + 'control-container', this._container);

		function createCorner(vSide, hSide) {
			var className = l + vSide + ' ' + l + hSide;

			corners[vSide + hSide] = L.DomUtil.create('div', className, container);
		}

		createCorner('top', 'left');
		createCorner('top', 'right');
		createCorner('bottom', 'left');
		createCorner('bottom', 'right');
	},

	_clearControlPos: function () {
		L.DomUtil.remove(this._controlContainer);
	}
});



/*
 * @class Control.Zoom
 * @aka L.Control.Zoom
 * @inherits Control
 *
 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
 */

L.Control.Zoom = L.Control.extend({
	// @section
	// @aka Control.Zoom options
	options: {
		position: 'topleft',

		// @option zoomInText: String = '+'
		// The text set on the 'zoom in' button.
		zoomInText: '+',

		// @option zoomInTitle: String = 'Zoom in'
		// The title set on the 'zoom in' button.
		zoomInTitle: 'Zoom in',

		// @option zoomOutText: String = '-'
		// The text set on the 'zoom out' button.
		zoomOutText: '-',

		// @option zoomOutTitle: String = 'Zoom out'
		// The title set on the 'zoom out' button.
		zoomOutTitle: 'Zoom out'
	},

	onAdd: function (map) {
		var zoomName = 'leaflet-control-zoom',
		    container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
		    options = this.options;

		this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
		        zoomName + '-in',  container, this._zoomIn);
		this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
		        zoomName + '-out', container, this._zoomOut);

		this._updateDisabled();
		map.on('zoomend zoomlevelschange', this._updateDisabled, this);

		return container;
	},

	onRemove: function (map) {
		map.off('zoomend zoomlevelschange', this._updateDisabled, this);
	},

	disable: function () {
		this._disabled = true;
		this._updateDisabled();
		return this;
	},

	enable: function () {
		this._disabled = false;
		this._updateDisabled();
		return this;
	},

	_zoomIn: function (e) {
		if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
			this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
		}
	},

	_zoomOut: function (e) {
		if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
			this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
		}
	},

	_createButton: function (html, title, className, container, fn) {
		var link = L.DomUtil.create('a', className, container);
		link.innerHTML = html;
		link.href = '#';
		link.title = title;

		/*
		 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
		 */
		link.setAttribute('role', 'button');
		link.setAttribute('aria-label', title);

		L.DomEvent
		    .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
		    .on(link, 'click', L.DomEvent.stop)
		    .on(link, 'click', fn, this)
		    .on(link, 'click', this._refocusOnMap, this);

		return link;
	},

	_updateDisabled: function () {
		var map = this._map,
		    className = 'leaflet-disabled';

		L.DomUtil.removeClass(this._zoomInButton, className);
		L.DomUtil.removeClass(this._zoomOutButton, className);

		if (this._disabled || map._zoom === map.getMinZoom()) {
			L.DomUtil.addClass(this._zoomOutButton, className);
		}
		if (this._disabled || map._zoom === map.getMaxZoom()) {
			L.DomUtil.addClass(this._zoomInButton, className);
		}
	}
});

// @namespace Map
// @section Control options
// @option zoomControl: Boolean = true
// Whether a [zoom control](#control-zoom) is added to the map by default.
L.Map.mergeOptions({
	zoomControl: true
});

L.Map.addInitHook(function () {
	if (this.options.zoomControl) {
		this.zoomControl = new L.Control.Zoom();
		this.addControl(this.zoomControl);
	}
});

// @namespace Control.Zoom
// @factory L.control.zoom(options: Control.Zoom options)
// Creates a zoom control
L.control.zoom = function (options) {
	return new L.Control.Zoom(options);
};



/*
 * @class Control.Attribution
 * @aka L.Control.Attribution
 * @inherits Control
 *
 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
 */

L.Control.Attribution = L.Control.extend({
	// @section
	// @aka Control.Attribution options
	options: {
		position: 'bottomright',

		// @option prefix: String = 'Leaflet'
		// The HTML text shown before the attributions. Pass `false` to disable.
		prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
	},

	initialize: function (options) {
		L.setOptions(this, options);

		this._attributions = {};
	},

	onAdd: function (map) {
		map.attributionControl = this;
		this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
		if (L.DomEvent) {
			L.DomEvent.disableClickPropagation(this._container);
		}

		// TODO ugly, refactor
		for (var i in map._layers) {
			if (map._layers[i].getAttribution) {
				this.addAttribution(map._layers[i].getAttribution());
			}
		}

		this._update();

		return this._container;
	},

	// @method setPrefix(prefix: String): this
	// Sets the text before the attributions.
	setPrefix: function (prefix) {
		this.options.prefix = prefix;
		this._update();
		return this;
	},

	// @method addAttribution(text: String): this
	// Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
	addAttribution: function (text) {
		if (!text) { return this; }

		if (!this._attributions[text]) {
			this._attributions[text] = 0;
		}
		this._attributions[text]++;

		this._update();

		return this;
	},

	// @method removeAttribution(text: String): this
	// Removes an attribution text.
	removeAttribution: function (text) {
		if (!text) { return this; }

		if (this._attributions[text]) {
			this._attributions[text]--;
			this._update();
		}

		return this;
	},

	_update: function () {
		if (!this._map) { return; }

		var attribs = [];

		for (var i in this._attributions) {
			if (this._attributions[i]) {
				attribs.push(i);
			}
		}

		var prefixAndAttribs = [];

		if (this.options.prefix) {
			prefixAndAttribs.push(this.options.prefix);
		}
		if (attribs.length) {
			prefixAndAttribs.push(attribs.join(', '));
		}

		this._container.innerHTML = prefixAndAttribs.join(' | ');
	}
});

// @namespace Map
// @section Control options
// @option attributionControl: Boolean = true
// Whether a [attribution control](#control-attribution) is added to the map by default.
L.Map.mergeOptions({
	attributionControl: true
});

L.Map.addInitHook(function () {
	if (this.options.attributionControl) {
		new L.Control.Attribution().addTo(this);
	}
});

// @namespace Control.Attribution
// @factory L.control.attribution(options: Control.Attribution options)
// Creates an attribution control.
L.control.attribution = function (options) {
	return new L.Control.Attribution(options);
};



/*
 * @class Control.Scale
 * @aka L.Control.Scale
 * @inherits Control
 *
 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
 *
 * @example
 *
 * ```js
 * L.control.scale().addTo(map);
 * ```
 */

L.Control.Scale = L.Control.extend({
	// @section
	// @aka Control.Scale options
	options: {
		position: 'bottomleft',

		// @option maxWidth: Number = 100
		// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
		maxWidth: 100,

		// @option metric: Boolean = True
		// Whether to show the metric scale line (m/km).
		metric: true,

		// @option imperial: Boolean = True
		// Whether to show the imperial scale line (mi/ft).
		imperial: true

		// @option updateWhenIdle: Boolean = false
		// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
	},

	onAdd: function (map) {
		var className = 'leaflet-control-scale',
		    container = L.DomUtil.create('div', className),
		    options = this.options;

		this._addScales(options, className + '-line', container);

		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
		map.whenReady(this._update, this);

		return container;
	},

	onRemove: function (map) {
		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
	},

	_addScales: function (options, className, container) {
		if (options.metric) {
			this._mScale = L.DomUtil.create('div', className, container);
		}
		if (options.imperial) {
			this._iScale = L.DomUtil.create('div', className, container);
		}
	},

	_update: function () {
		var map = this._map,
		    y = map.getSize().y / 2;

		var maxMeters = map.distance(
				map.containerPointToLatLng([0, y]),
				map.containerPointToLatLng([this.options.maxWidth, y]));

		this._updateScales(maxMeters);
	},

	_updateScales: function (maxMeters) {
		if (this.options.metric && maxMeters) {
			this._updateMetric(maxMeters);
		}
		if (this.options.imperial && maxMeters) {
			this._updateImperial(maxMeters);
		}
	},

	_updateMetric: function (maxMeters) {
		var meters = this._getRoundNum(maxMeters),
		    label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';

		this._updateScale(this._mScale, label, meters / maxMeters);
	},

	_updateImperial: function (maxMeters) {
		var maxFeet = maxMeters * 3.2808399,
		    maxMiles, miles, feet;

		if (maxFeet > 5280) {
			maxMiles = maxFeet / 5280;
			miles = this._getRoundNum(maxMiles);
			this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);

		} else {
			feet = this._getRoundNum(maxFeet);
			this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
		}
	},

	_updateScale: function (scale, text, ratio) {
		scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
		scale.innerHTML = text;
	},

	_getRoundNum: function (num) {
		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
		    d = num / pow10;

		d = d >= 10 ? 10 :
		    d >= 5 ? 5 :
		    d >= 3 ? 3 :
		    d >= 2 ? 2 : 1;

		return pow10 * d;
	}
});


// @factory L.control.scale(options?: Control.Scale options)
// Creates an scale control with the given options.
L.control.scale = function (options) {
	return new L.Control.Scale(options);
};



/*
 * @class Control.Layers
 * @aka L.Control.Layers
 * @inherits Control
 *
 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control.html)). Extends `Control`.
 *
 * @example
 *
 * ```js
 * var baseLayers = {
 * 	"Mapbox": mapbox,
 * 	"OpenStreetMap": osm
 * };
 *
 * var overlays = {
 * 	"Marker": marker,
 * 	"Roads": roadsLayer
 * };
 *
 * L.control.layers(baseLayers, overlays).addTo(map);
 * ```
 *
 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
 *
 * ```js
 * {
 *     "<someName1>": layer1,
 *     "<someName2>": layer2
 * }
 * ```
 *
 * The layer names can contain HTML, which allows you to add additional styling to the items:
 *
 * ```js
 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
 * ```
 */


L.Control.Layers = L.Control.extend({
	// @section
	// @aka Control.Layers options
	options: {
		// @option collapsed: Boolean = true
		// If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
		collapsed: true,
		position: 'topright',

		// @option autoZIndex: Boolean = true
		// If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
		autoZIndex: true,

		// @option hideSingleBase: Boolean = false
		// If `true`, the base layers in the control will be hidden when there is only one.
		hideSingleBase: false,

		// @option sortLayers: Boolean = false
		// Whether to sort the layers. When `false`, layers will keep the order
		// in which they were added to the control.
		sortLayers: false,

		// @option sortFunction: Function = *
		// A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
		// that will be used for sorting the layers, when `sortLayers` is `true`.
		// The function receives both the `L.Layer` instances and their names, as in
		// `sortFunction(layerA, layerB, nameA, nameB)`.
		// By default, it sorts layers alphabetically by their name.
		sortFunction: function (layerA, layerB, nameA, nameB) {
			return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
		}
	},

	initialize: function (baseLayers, overlays, options) {
		L.setOptions(this, options);

		this._layers = [];
		this._lastZIndex = 0;
		this._handlingClick = false;

		for (var i in baseLayers) {
			this._addLayer(baseLayers[i], i);
		}

		for (i in overlays) {
			this._addLayer(overlays[i], i, true);
		}
	},

	onAdd: function (map) {
		this._initLayout();
		this._update();

		this._map = map;
		map.on('zoomend', this._checkDisabledLayers, this);

		return this._container;
	},

	onRemove: function () {
		this._map.off('zoomend', this._checkDisabledLayers, this);

		for (var i = 0; i < this._layers.length; i++) {
			this._layers[i].layer.off('add remove', this._onLayerChange, this);
		}
	},

	// @method addBaseLayer(layer: Layer, name: String): this
	// Adds a base layer (radio button entry) with the given name to the control.
	addBaseLayer: function (layer, name) {
		this._addLayer(layer, name);
		return (this._map) ? this._update() : this;
	},

	// @method addOverlay(layer: Layer, name: String): this
	// Adds an overlay (checkbox entry) with the given name to the control.
	addOverlay: function (layer, name) {
		this._addLayer(layer, name, true);
		return (this._map) ? this._update() : this;
	},

	// @method removeLayer(layer: Layer): this
	// Remove the given layer from the control.
	removeLayer: function (layer) {
		layer.off('add remove', this._onLayerChange, this);

		var obj = this._getLayer(L.stamp(layer));
		if (obj) {
			this._layers.splice(this._layers.indexOf(obj), 1);
		}
		return (this._map) ? this._update() : this;
	},

	// @method expand(): this
	// Expand the control container if collapsed.
	expand: function () {
		L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
		this._form.style.height = null;
		var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
		if (acceptableHeight < this._form.clientHeight) {
			L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
			this._form.style.height = acceptableHeight + 'px';
		} else {
			L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
		}
		this._checkDisabledLayers();
		return this;
	},

	// @method collapse(): this
	// Collapse the control container if expanded.
	collapse: function () {
		L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
		return this;
	},

	_initLayout: function () {
		var className = 'leaflet-control-layers',
		    container = this._container = L.DomUtil.create('div', className),
		    collapsed = this.options.collapsed;

		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
		container.setAttribute('aria-haspopup', true);

		L.DomEvent.disableClickPropagation(container);
		if (!L.Browser.touch) {
			L.DomEvent.disableScrollPropagation(container);
		}

		var form = this._form = L.DomUtil.create('form', className + '-list');

		if (collapsed) {
			this._map.on('click', this.collapse, this);

			if (!L.Browser.android) {
				L.DomEvent.on(container, {
					mouseenter: this.expand,
					mouseleave: this.collapse
				}, this);
			}
		}

		var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
		link.href = '#';
		link.title = 'Layers';

		if (L.Browser.touch) {
			L.DomEvent
			    .on(link, 'click', L.DomEvent.stop)
			    .on(link, 'click', this.expand, this);
		} else {
			L.DomEvent.on(link, 'focus', this.expand, this);
		}

		// work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
		L.DomEvent.on(form, 'click', function () {
			setTimeout(L.bind(this._onInputClick, this), 0);
		}, this);

		// TODO keyboard accessibility

		if (!collapsed) {
			this.expand();
		}

		this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
		this._separator = L.DomUtil.create('div', className + '-separator', form);
		this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);

		container.appendChild(form);
	},

	_getLayer: function (id) {
		for (var i = 0; i < this._layers.length; i++) {

			if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
				return this._layers[i];
			}
		}
	},

	_addLayer: function (layer, name, overlay) {
		layer.on('add remove', this._onLayerChange, this);

		this._layers.push({
			layer: layer,
			name: name,
			overlay: overlay
		});

		if (this.options.sortLayers) {
			this._layers.sort(L.bind(function (a, b) {
				return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
			}, this));
		}

		if (this.options.autoZIndex && layer.setZIndex) {
			this._lastZIndex++;
			layer.setZIndex(this._lastZIndex);
		}
	},

	_update: function () {
		if (!this._container) { return this; }

		L.DomUtil.empty(this._baseLayersList);
		L.DomUtil.empty(this._overlaysList);

		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;

		for (i = 0; i < this._layers.length; i++) {
			obj = this._layers[i];
			this._addItem(obj);
			overlaysPresent = overlaysPresent || obj.overlay;
			baseLayersPresent = baseLayersPresent || !obj.overlay;
			baseLayersCount += !obj.overlay ? 1 : 0;
		}

		// Hide base layers section if there's only one layer.
		if (this.options.hideSingleBase) {
			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
		}

		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';

		return this;
	},

	_onLayerChange: function (e) {
		if (!this._handlingClick) {
			this._update();
		}

		var obj = this._getLayer(L.stamp(e.target));

		// @namespace Map
		// @section Layer events
		// @event baselayerchange: LayersControlEvent
		// Fired when the base layer is changed through the [layer control](#control-layers).
		// @event overlayadd: LayersControlEvent
		// Fired when an overlay is selected through the [layer control](#control-layers).
		// @event overlayremove: LayersControlEvent
		// Fired when an overlay is deselected through the [layer control](#control-layers).
		// @namespace Control.Layers
		var type = obj.overlay ?
			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
			(e.type === 'add' ? 'baselayerchange' : null);

		if (type) {
			this._map.fire(type, obj);
		}
	},

	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
	_createRadioElement: function (name, checked) {

		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
				name + '"' + (checked ? ' checked="checked"' : '') + '/>';

		var radioFragment = document.createElement('div');
		radioFragment.innerHTML = radioHtml;

		return radioFragment.firstChild;
	},

	_addItem: function (obj) {
		var label = document.createElement('label'),
		    checked = this._map.hasLayer(obj.layer),
		    input;

		if (obj.overlay) {
			input = document.createElement('input');
			input.type = 'checkbox';
			input.className = 'leaflet-control-layers-selector';
			input.defaultChecked = checked;
		} else {
			input = this._createRadioElement('leaflet-base-layers', checked);
		}

		input.layerId = L.stamp(obj.layer);

		L.DomEvent.on(input, 'click', this._onInputClick, this);

		var name = document.createElement('span');
		name.innerHTML = ' ' + obj.name;

		// Helps from preventing layer control flicker when checkboxes are disabled
		// https://github.com/Leaflet/Leaflet/issues/2771
		var holder = document.createElement('div');

		label.appendChild(holder);
		holder.appendChild(input);
		holder.appendChild(name);

		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
		container.appendChild(label);

		this._checkDisabledLayers();
		return label;
	},

	_onInputClick: function () {
		var inputs = this._form.getElementsByTagName('input'),
		    input, layer, hasLayer;
		var addedLayers = [],
		    removedLayers = [];

		this._handlingClick = true;

		for (var i = inputs.length - 1; i >= 0; i--) {
			input = inputs[i];
			layer = this._getLayer(input.layerId).layer;
			hasLayer = this._map.hasLayer(layer);

			if (input.checked && !hasLayer) {
				addedLayers.push(layer);

			} else if (!input.checked && hasLayer) {
				removedLayers.push(layer);
			}
		}

		// Bugfix issue 2318: Should remove all old layers before readding new ones
		for (i = 0; i < removedLayers.length; i++) {
			this._map.removeLayer(removedLayers[i]);
		}
		for (i = 0; i < addedLayers.length; i++) {
			this._map.addLayer(addedLayers[i]);
		}

		this._handlingClick = false;

		this._refocusOnMap();
	},

	_checkDisabledLayers: function () {
		var inputs = this._form.getElementsByTagName('input'),
		    input,
		    layer,
		    zoom = this._map.getZoom();

		for (var i = inputs.length - 1; i >= 0; i--) {
			input = inputs[i];
			layer = this._getLayer(input.layerId).layer;
			input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
			                 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);

		}
	},

	_expand: function () {
		// Backward compatibility, remove me in 1.1.
		return this.expand();
	},

	_collapse: function () {
		// Backward compatibility, remove me in 1.1.
		return this.collapse();
	}

});


// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
L.control.layers = function (baseLayers, overlays, options) {
	return new L.Control.Layers(baseLayers, overlays, options);
};



}(window, document));
//# sourceMappingURL=leaflet-src.map
L.Control.Fullscreen = L.Control.extend({
    options: {
        position: 'topleft',
        title: {
            'false': 'View Fullscreen',
            'true': 'Exit Fullscreen'
        }
    },

    onAdd: function (map) {
        var container = L.DomUtil.create('div', 'leaflet-control-fullscreen leaflet-bar leaflet-control');

        this.link = L.DomUtil.create('a', 'leaflet-control-fullscreen-button leaflet-bar-part', container);
        this.link.href = '#';

        this._map = map;
        this._map.on('fullscreenchange', this._toggleTitle, this);
        this._toggleTitle();

        L.DomEvent.on(this.link, 'click', this._click, this);

        return container;
    },

    _click: function (e) {
        L.DomEvent.stopPropagation(e);
        L.DomEvent.preventDefault(e);
        this._map.toggleFullscreen(this.options);
    },

    _toggleTitle: function() {
        this.link.title = this.options.title[this._map.isFullscreen()];
    }
});

L.Map.include({
    isFullscreen: function () {
        return this._isFullscreen || false;
    },

    toggleFullscreen: function (options) {
        var container = this.getContainer();
        if (this.isFullscreen()) {
            if (options && options.pseudoFullscreen) {
                this._disablePseudoFullscreen(container);
            } else if (document.exitFullscreen) {
                document.exitFullscreen();
            } else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            } else if (document.webkitCancelFullScreen) {
                document.webkitCancelFullScreen();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            } else {
                this._disablePseudoFullscreen(container);
            }
        } else {
            if (options && options.pseudoFullscreen) {
                this._enablePseudoFullscreen(container);
            } else if (container.requestFullscreen) {
                container.requestFullscreen();
            } else if (container.mozRequestFullScreen) {
                container.mozRequestFullScreen();
            } else if (container.webkitRequestFullscreen) {
                container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
            } else if (container.msRequestFullscreen) {
                container.msRequestFullscreen();
            } else {
                this._enablePseudoFullscreen(container);
            }
        }

    },

    _enablePseudoFullscreen: function (container) {
        L.DomUtil.addClass(container, 'leaflet-pseudo-fullscreen');
        this._setFullscreen(true);
        this.fire('fullscreenchange');
    },

    _disablePseudoFullscreen: function (container) {
        L.DomUtil.removeClass(container, 'leaflet-pseudo-fullscreen');
        this._setFullscreen(false);
        this.fire('fullscreenchange');
    },

    _setFullscreen: function(fullscreen) {
        this._isFullscreen = fullscreen;
        var container = this.getContainer();
        if (fullscreen) {
            L.DomUtil.addClass(container, 'leaflet-fullscreen-on');
        } else {
            L.DomUtil.removeClass(container, 'leaflet-fullscreen-on');
        }
        this.invalidateSize();
    },

    _onFullscreenChange: function (e) {
        var fullscreenElement =
            document.fullscreenElement ||
            document.mozFullScreenElement ||
            document.webkitFullscreenElement ||
            document.msFullscreenElement;

        if (fullscreenElement === this.getContainer() && !this._isFullscreen) {
            this._setFullscreen(true);
            this.fire('fullscreenchange');
        } else if (fullscreenElement !== this.getContainer() && this._isFullscreen) {
            this._setFullscreen(false);
            this.fire('fullscreenchange');
        }
    }
});

L.Map.mergeOptions({
    fullscreenControl: false
});

L.Map.addInitHook(function () {
    if (this.options.fullscreenControl) {
        this.fullscreenControl = new L.Control.Fullscreen(this.options.fullscreenControl);
        this.addControl(this.fullscreenControl);
    }

    var fullscreenchange;

    if ('onfullscreenchange' in document) {
        fullscreenchange = 'fullscreenchange';
    } else if ('onmozfullscreenchange' in document) {
        fullscreenchange = 'mozfullscreenchange';
    } else if ('onwebkitfullscreenchange' in document) {
        fullscreenchange = 'webkitfullscreenchange';
    } else if ('onmsfullscreenchange' in document) {
        fullscreenchange = 'MSFullscreenChange';
    }

    if (fullscreenchange) {
        var onFullscreenChange = L.bind(this._onFullscreenChange, this);

        this.whenReady(function () {
            L.DomEvent.on(document, fullscreenchange, onFullscreenChange);
        });

        this.on('unload', function () {
            L.DomEvent.off(document, fullscreenchange, onFullscreenChange);
        });
    }
});

L.control.fullscreen = function (options) {
    return new L.Control.Fullscreen(options);
};

/*
 Leaflet.draw 0.4.9, a plugin that adds drawing and editing tools to Leaflet powered maps.
 (c) 2012-2017, Jacob Toye, Jon West, Smartrak, Leaflet

 https://github.com/Leaflet/Leaflet.draw
 http://leafletjs.com
 */
(function (window, document, undefined) {/**
 * Leaflet.draw assumes that you have already included the Leaflet library.
 */
L.drawVersion = "0.4.9";
/**
 * @class L.Draw
 * @aka Draw
 *
 *
 * To add the draw toolbar set the option drawControl: true in the map options.
 *
 * @example
 * ```js
 *      var map = L.map('map', {drawControl: true}).setView([51.505, -0.09], 13);
 *
 *      L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
 *          attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
 *      }).addTo(map);
 * ```
 *
 * ### Adding the edit toolbar
 * To use the edit toolbar you must initialise the Leaflet.draw control and manually add it to the map.
 *
 * ```js
 *      var map = L.map('map').setView([51.505, -0.09], 13);
 *
 *      L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
 *          attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
 *      }).addTo(map);
 *
 *      // FeatureGroup is to store editable layers
 *      var drawnItems = new L.FeatureGroup();
 *      map.addLayer(drawnItems);
 *
 *      var drawControl = new L.Control.Draw({
 *          edit: {
 *              featureGroup: drawnItems
 *          }
 *      });
 *      map.addControl(drawControl);
 * ```
 *
 * The key here is the featureGroup option. This tells the plugin which FeatureGroup contains the layers that
 * should be editable. The featureGroup can contain 0 or more features with geometry types Point, LineString, and Polygon.
 * Leaflet.draw does not work with multigeometry features such as MultiPoint, MultiLineString, MultiPolygon,
 * or GeometryCollection. If you need to add multigeometry features to the draw plugin, convert them to a
 * FeatureCollection of non-multigeometries (Points, LineStrings, or Polygons).
 */
L.Draw = {};

/**
 * @class L.drawLocal
 * @aka L.drawLocal
 *
 * The core toolbar class of the API — it is used to create the toolbar ui
 *
 * @example
 * ```js
 *      var modifiedDraw = L.drawLocal.extend({
 *          draw: {
 *              toolbar: {
 *                  buttons: {
 *                      polygon: 'Draw an awesome polygon'
 *                  }
 *              }
 *          }
 *      });
 * ```
 *
 * The default state for the control is the draw toolbar just below the zoom control.
 *  This will allow map users to draw vectors and markers.
 *  **Please note the edit toolbar is not enabled by default.**
 */
L.drawLocal = {
	draw: {
		toolbar: {
			// #TODO: this should be reorganized where actions are nested in actions
			// ex: actions.undo  or actions.cancel
			actions: {
				title: 'Cancel drawing',
				text: 'Cancel'
			},
			finish: {
				title: 'Finish drawing',
				text: 'Finish'
			},
			undo: {
				title: 'Delete last point drawn',
				text: 'Delete last point'
			},
			buttons: {
				polyline: 'Draw a polyline',
				polygon: 'Draw a polygon',
				rectangle: 'Draw a rectangle',
				circle: 'Draw a circle',
				marker: 'Draw a marker'
			}
		},
		handlers: {
			circle: {
				tooltip: {
					start: 'Click and drag to draw circle.'
				},
				radius: 'Radius'
			},
			marker: {
				tooltip: {
					start: 'Click map to place marker.'
				}
			},
			polygon: {
				tooltip: {
					start: 'Click to start drawing shape.',
					cont: 'Click to continue drawing shape.',
					end: 'Click first point to close this shape.'
				}
			},
			polyline: {
				error: '<strong>Error:</strong> shape edges cannot cross!',
				tooltip: {
					start: 'Click to start drawing line.',
					cont: 'Click to continue drawing line.',
					end: 'Click last point to finish line.'
				}
			},
			rectangle: {
				tooltip: {
					start: 'Click and drag to draw rectangle.'
				}
			},
			simpleshape: {
				tooltip: {
					end: 'Release mouse to finish drawing.'
				}
			}
		}
	},
	edit: {
		toolbar: {
			actions: {
				save: {
					title: 'Save changes.',
					text: 'Save'
				},
				cancel: {
					title: 'Cancel editing, discards all changes.',
					text: 'Cancel'
				}
			},
			buttons: {
				edit: 'Edit layers.',
				editDisabled: 'No layers to edit.',
				remove: 'Delete layers.',
				removeDisabled: 'No layers to delete.'
			}
		},
		handlers: {
			edit: {
				tooltip: {
					text: 'Drag handles, or marker to edit feature.',
					subtext: 'Click cancel to undo changes.'
				}
			},
			remove: {
				tooltip: {
					text: 'Click on a feature to remove'
				}
			}
		}
	}
};



/**
 * ### Events
 * Once you have successfully added the Leaflet.draw plugin to your map you will want to respond to the different
 * actions users can initiate. The following events will be triggered on the map:
 *
 * @class L.Draw.Event
 * @aka Draw.Event
 *
 * Use `L.Draw.Event.EVENTNAME` constants to ensure events are correct.
 *
 * @example
 * ```js
 * map.on(L.Draw.Event.CREATED; function (e) {
 *    var type = e.layerType;
 *        layer = e.layer;
 *
 *    if (type === 'marker') {
 *        // Do marker specific actions
 *    }
 *
 *    // Do whatever else you need to. (save to db; add to map etc)
 *    map.addLayer(layer);
 *});
 * ```
 */
L.Draw.Event = {};
/**
 * @event draw:created: PolyLine; Polygon; Rectangle; Circle; Marker | String
 *
 * Layer that was just created.
 * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`
 * Triggered when a new vector or marker has been created.
 *
 */
L.Draw.Event.CREATED = 'draw:created';

/**
 * @event draw:edited: LayerGroup
 *
 * List of all layers just edited on the map.
 *
 *
 * Triggered when layers in the FeatureGroup; initialised with the plugin; have been edited and saved.
 *
 * @example
 * ```js
 *      map.on('draw:edited'; function (e) {
     *          var layers = e.layers;
     *          layers.eachLayer(function (layer) {
     *              //do whatever you want; most likely save back to db
     *          });
     *      });
 * ```
 */
L.Draw.Event.EDITED = 'draw:edited';

/**
 * @event draw:deleted: LayerGroup
 *
 * List of all layers just removed from the map.
 *
 * Triggered when layers have been removed (and saved) from the FeatureGroup.
 */
L.Draw.Event.DELETED = 'draw:deleted';

/**
 * @event draw:drawstart: String
 *
 * The type of layer this is. One of:`polyline`; `polygon`; `rectangle`; `circle`; `marker`
 *
 * Triggered when the user has chosen to draw a particular vector or marker.
 */
L.Draw.Event.DRAWSTART = 'draw:drawstart';

/**
 * @event draw:drawstop: String
 *
 * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`
 *
 * Triggered when the user has finished a particular vector or marker.
 */

L.Draw.Event.DRAWSTOP = 'draw:drawstop';

/**
 * @event draw:drawvertex: LayerGroup
 *
 * List of all layers just being added from the map.
 *
 * Triggered when a vertex is created on a polyline or polygon.
 */
L.Draw.Event.DRAWVERTEX = 'draw:drawvertex';

/**
 * @event draw:editstart: String
 *
 * The type of edit this is. One of: `edit`
 *
 * Triggered when the user starts edit mode by clicking the edit tool button.
 */

L.Draw.Event.EDITSTART = 'draw:editstart';

/**
 * @event draw:editmove: ILayer
 *
 *  Layer that was just moved.
 *
 * Triggered as the user moves a rectangle; circle or marker.
 */
L.Draw.Event.EDITMOVE = 'draw:editmove';

/**
 * @event draw:editresize: ILayer
 *
 * Layer that was just moved.
 *
 * Triggered as the user resizes a rectangle or circle.
 */
L.Draw.Event.EDITRESIZE = 'draw:editresize';

/**
 * @event draw:editvertex: LayerGroup
 *
 * List of all layers just being edited from the map.
 *
 * Triggered when a vertex is edited on a polyline or polygon.
 */
L.Draw.Event.EDITVERTEX = 'draw:editvertex';

/**
 * @event draw:editstop: String
 *
 * The type of edit this is. One of: `edit`
 *
 * Triggered when the user has finshed editing (edit mode) and saves edits.
 */
L.Draw.Event.EDITSTOP = 'draw:editstop';

/**
 * @event draw:deletestart: String
 *
 * The type of edit this is. One of: `remove`
 *
 * Triggered when the user starts remove mode by clicking the remove tool button.
 */
L.Draw.Event.DELETESTART = 'draw:deletestart';

/**
 * @event draw:deletestop: String
 *
 * The type of edit this is. One of: `remove`
 *
 * Triggered when the user has finished removing shapes (remove mode) and saves.
 */
L.Draw.Event.DELETESTOP = 'draw:deletestop';


L.Draw = L.Draw || {};

/**
 * @class L.Draw.Feature
 * @aka Draw.Feature
 */
L.Draw.Feature = L.Handler.extend({
	includes: L.Mixin.Events,

	// @method initialize(): void
	initialize: function (map, options) {
		this._map = map;
		this._container = map._container;
		this._overlayPane = map._panes.overlayPane;
		this._popupPane = map._panes.popupPane;

		// Merge default shapeOptions options with custom shapeOptions
		if (options && options.shapeOptions) {
			options.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions);
		}
		L.setOptions(this, options);
	},

	// @method enable(): void
	// Enables this handler
	enable: function () {
		if (this._enabled) {
			return;
		}

		L.Handler.prototype.enable.call(this);

		this.fire('enabled', { handler: this.type });

		this._map.fire(L.Draw.Event.DRAWSTART, { layerType: this.type });
	},

	// @method initialize(): void
	disable: function () {
		if (!this._enabled) {
			return;
		}

		L.Handler.prototype.disable.call(this);

		this._map.fire(L.Draw.Event.DRAWSTOP, { layerType: this.type });

		this.fire('disabled', { handler: this.type });
	},

	// @method addHooks(): void
	// Add's event listeners to this handler
	addHooks: function () {
		var map = this._map;

		if (map) {
			L.DomUtil.disableTextSelection();

			map.getContainer().focus();

			this._tooltip = new L.Draw.Tooltip(this._map);

			L.DomEvent.on(this._container, 'keyup', this._cancelDrawing, this);
		}
	},

	// @method removeHooks(): void
	// Removes event listeners from this handler
	removeHooks: function () {
		if (this._map) {
			L.DomUtil.enableTextSelection();

			this._tooltip.dispose();
			this._tooltip = null;

			L.DomEvent.off(this._container, 'keyup', this._cancelDrawing, this);
		}
	},

	// @method setOptions(object): void
	// Sets new options to this handler
	setOptions: function (options) {
		L.setOptions(this, options);
	},

	_fireCreatedEvent: function (layer) {
		this._map.fire(L.Draw.Event.CREATED, { layer: layer, layerType: this.type });
	},

	// Cancel drawing when the escape key is pressed
	_cancelDrawing: function (e) {
		this._map.fire('draw:canceled', { layerType: this.type });
		if (e.keyCode === 27) {
			this.disable();
		}
	}
});



/**
 * @class L.Draw.Polyline
 * @aka Draw.Polyline
 * @inherits L.Draw.Feature
 */
L.Draw.Polyline = L.Draw.Feature.extend({
	statics: {
		TYPE: 'polyline'
	},

	Poly: L.Polyline,

	options: {
		allowIntersection: true,
		repeatMode: false,
		drawError: {
			color: '#b00b00',
			timeout: 2500
		},
		icon: new L.DivIcon({
			iconSize: new L.Point(8, 8),
			className: 'leaflet-div-icon leaflet-editing-icon'
		}),
		touchIcon: new L.DivIcon({
			iconSize: new L.Point(20, 20),
			className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
		}),
		guidelineDistance: 20,
		maxGuideLineLength: 4000,
		shapeOptions: {
			stroke: true,
			color: '#3388ff',
			weight: 4,
			opacity: 0.5,
			fill: false,
			clickable: true
		},
		metric: true, // Whether to use the metric measurement system or imperial
		feet: true, // When not metric, to use feet instead of yards for display.
		nautic: false, // When not metric, not feet use nautic mile for display
		showLength: true, // Whether to display distance in the tooltip
		zIndexOffset: 2000 // This should be > than the highest z-index any map layers
	},

	// @method initialize(): void
	initialize: function (map, options) {
		// if touch, switch to touch icon
		if (L.Browser.touch) {
			this.options.icon = this.options.touchIcon;
		}

		// Need to set this here to ensure the correct message is used.
		this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error;

		// Merge default drawError options with custom options
		if (options && options.drawError) {
			options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
		}

		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.Draw.Polyline.TYPE;

		L.Draw.Feature.prototype.initialize.call(this, map, options);
	},

	// @method addHooks(): void
	// Add listener hooks to this handler
	addHooks: function () {
		L.Draw.Feature.prototype.addHooks.call(this);
		if (this._map) {
			this._markers = [];

			this._markerGroup = new L.LayerGroup();
			this._map.addLayer(this._markerGroup);

			this._poly = new L.Polyline([], this.options.shapeOptions);

			this._tooltip.updateContent(this._getTooltipText());

			// Make a transparent marker that will used to catch click events. These click
			// events will create the vertices. We need to do this so we can ensure that
			// we can create vertices over other map layers (markers, vector layers). We
			// also do not want to trigger any click handlers of objects we are clicking on
			// while drawing.
			if (!this._mouseMarker) {
				this._mouseMarker = L.marker(this._map.getCenter(), {
					icon: L.divIcon({
						className: 'leaflet-mouse-marker',
						iconAnchor: [20, 20],
						iconSize: [40, 40]
					}),
					opacity: 0,
					zIndexOffset: this.options.zIndexOffset
				});
			}

			this._mouseMarker
				.on('mouseout', this._onMouseOut, this)
				.on('mousemove', this._onMouseMove, this) // Necessary to prevent 0.8 stutter
				.on('mousedown', this._onMouseDown, this)
				.on('mouseup', this._onMouseUp, this) // Necessary for 0.8 compatibility
				.addTo(this._map);

			this._map
				.on('mouseup', this._onMouseUp, this) // Necessary for 0.7 compatibility
				.on('mousemove', this._onMouseMove, this)
				.on('zoomlevelschange', this._onZoomEnd, this)
				.on('touchstart', this._onTouch, this)
				.on('zoomend', this._onZoomEnd, this);

		}
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler.
	removeHooks: function () {
		L.Draw.Feature.prototype.removeHooks.call(this);

		this._clearHideErrorTimeout();

		this._cleanUpShape();

		// remove markers from map
		this._map.removeLayer(this._markerGroup);
		delete this._markerGroup;
		delete this._markers;

		this._map.removeLayer(this._poly);
		delete this._poly;

		this._mouseMarker
			.off('mousedown', this._onMouseDown, this)
			.off('mouseout', this._onMouseOut, this)
			.off('mouseup', this._onMouseUp, this)
			.off('mousemove', this._onMouseMove, this);
		this._map.removeLayer(this._mouseMarker);
		delete this._mouseMarker;

		// clean up DOM
		this._clearGuides();

		this._map
			.off('mouseup', this._onMouseUp, this)
			.off('mousemove', this._onMouseMove, this)
			.off('zoomlevelschange', this._onZoomEnd, this)
			.off('zoomend', this._onZoomEnd, this)
			.off('touchstart', this._onTouch, this)
			.off('click', this._onTouch, this);
	},

	// @method deleteLastVertex(): void
	// Remove the last vertex from the polyline, removes polyline from map if only one point exists.
	deleteLastVertex: function () {
		if (this._markers.length <= 1) {
			return;
		}

		var lastMarker = this._markers.pop(),
			poly = this._poly,
			// Replaces .spliceLatLngs()
			latlngs = poly.getLatLngs(),
			latlng = latlngs.splice(-1, 1)[0];
		this._poly.setLatLngs(latlngs);

		this._markerGroup.removeLayer(lastMarker);

		if (poly.getLatLngs().length < 2) {
			this._map.removeLayer(poly);
		}

		this._vertexChanged(latlng, false);
	},

	// @method addVertex(): void
	// Add a vertex to the end of the polyline
	addVertex: function (latlng) {
		var markersLength = this._markers.length;
		// markersLength must be greater than or equal to 2 before intersections can occur
		if (markersLength >= 2 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) {
			this._showErrorTooltip();
			return;
		}
		else if (this._errorShown) {
			this._hideErrorTooltip();
		}

		this._markers.push(this._createMarker(latlng));

		this._poly.addLatLng(latlng);

		if (this._poly.getLatLngs().length === 2) {
			this._map.addLayer(this._poly);
		}

		this._vertexChanged(latlng, true);
	},

	// @method completeShape(): void
	// Closes the polyline between the first and last points
	completeShape: function () {
		if (this._markers.length <= 1) {
			return;
		}

		this._fireCreatedEvent();
		this.disable();

		if (this.options.repeatMode) {
			this.enable();
		}
	},

	_finishShape: function () {
		var latlngs = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs();
		var intersects = this._poly.newLatLngIntersects(latlngs[latlngs.length - 1]);

		if ((!this.options.allowIntersection && intersects) || !this._shapeIsValid()) {
			this._showErrorTooltip();
			return;
		}

		this._fireCreatedEvent();
		this.disable();
		if (this.options.repeatMode) {
			this.enable();
		}
	},

	// Called to verify the shape is valid when the user tries to finish it
	// Return false if the shape is not valid
	_shapeIsValid: function () {
		return true;
	},

	_onZoomEnd: function () {
		if (this._markers !== null) {
			this._updateGuide();
		}
	},

	_onMouseMove: function (e) {
		var newPos = this._map.mouseEventToLayerPoint(e.originalEvent);
		var latlng = this._map.layerPointToLatLng(newPos);

		// Save latlng
		// should this be moved to _updateGuide() ?
		this._currentLatLng = latlng;

		this._updateTooltip(latlng);

		// Update the guide line
		this._updateGuide(newPos);

		// Update the mouse marker position
		this._mouseMarker.setLatLng(latlng);

		L.DomEvent.preventDefault(e.originalEvent);
	},

	_vertexChanged: function (latlng, added) {
		this._map.fire(L.Draw.Event.DRAWVERTEX, { layers: this._markerGroup });
		this._updateFinishHandler();

		this._updateRunningMeasure(latlng, added);

		this._clearGuides();

		this._updateTooltip();
	},

	_onMouseDown: function (e) {
		if (!this._clickHandled && !this._touchHandled && !this._disableMarkers) {
			this._onMouseMove(e);
			this._clickHandled = true;
			this._disableNewMarkers();
			var originalEvent = e.originalEvent;
			var clientX = originalEvent.clientX;
			var clientY = originalEvent.clientY;
			this._startPoint.call(this, clientX, clientY);
		}
	},

	_startPoint: function (clientX, clientY) {
		this._mouseDownOrigin = L.point(clientX, clientY);
	},

	_onMouseUp: function (e) {
		var originalEvent = e.originalEvent;
		var clientX = originalEvent.clientX;
		var clientY = originalEvent.clientY;
		this._endPoint.call(this, clientX, clientY, e);
		this._clickHandled = null;
	},

	_endPoint: function (clientX, clientY, e) {
		if (this._mouseDownOrigin) {
			var dragCheckDistance = L.point(clientX, clientY)
				.distanceTo(this._mouseDownOrigin);
			var lastPtDistance = this._calculateFinishDistance(e.latlng);
			if (lastPtDistance < 10 && L.Browser.touch) {
				this._finishShape();
			} else if (Math.abs(dragCheckDistance) < 9 * (window.devicePixelRatio || 1)) {
				this.addVertex(e.latlng);
			}
			this._enableNewMarkers(); // after a short pause, enable new markers
		}
		this._mouseDownOrigin = null;
	},

	// ontouch prevented by clickHandled flag because some browsers fire both click/touch events,
	// causing unwanted behavior
	_onTouch: function (e) {
		var originalEvent = e.originalEvent;
		var clientX;
		var clientY;
		if (originalEvent.touches && originalEvent.touches[0] && !this._clickHandled && !this._touchHandled && !this._disableMarkers) {
			clientX = originalEvent.touches[0].clientX;
			clientY = originalEvent.touches[0].clientY;
			this._disableNewMarkers();
			this._touchHandled = true;
			this._startPoint.call(this, clientX, clientY);
			this._endPoint.call(this, clientX, clientY, e);
			this._touchHandled = null;
		}
		this._clickHandled = null;
	},

	_onMouseOut: function () {
		if (this._tooltip) {
			this._tooltip._onMouseOut.call(this._tooltip);
		}
	},

	// calculate if we are currently within close enough distance
	// of the closing point (first point for shapes, last point for lines)
	// this is semi-ugly code but the only reliable way i found to get the job done
	// note: calculating point.distanceTo between mouseDownOrigin and last marker did NOT work
	_calculateFinishDistance: function (potentialLatLng) {
		var lastPtDistance
		if (this._markers.length > 0) {
				var finishMarker;
				if (this.type === L.Draw.Polyline.TYPE) {
					finishMarker = this._markers[this._markers.length - 1];
				} else if (this.type === L.Draw.Polygon.TYPE) {
					finishMarker = this._markers[0];
				} else {
					return Infinity;
				}
				var lastMarkerPoint = this._map.latLngToContainerPoint(finishMarker.getLatLng()),
				potentialMarker = new L.Marker(potentialLatLng, {
					icon: this.options.icon,
					zIndexOffset: this.options.zIndexOffset * 2
				});
				var potentialMarkerPint = this._map.latLngToContainerPoint(potentialMarker.getLatLng());
				lastPtDistance = lastMarkerPoint.distanceTo(potentialMarkerPint);
			} else {
				lastPtDistance = Infinity;
			}
			return lastPtDistance;
	},

	_updateFinishHandler: function () {
		var markerCount = this._markers.length;
		// The last marker should have a click handler to close the polyline
		if (markerCount > 1) {
			this._markers[markerCount - 1].on('click', this._finishShape, this);
		}

		// Remove the old marker click handler (as only the last point should close the polyline)
		if (markerCount > 2) {
			this._markers[markerCount - 2].off('click', this._finishShape, this);
		}
	},

	_createMarker: function (latlng) {
		var marker = new L.Marker(latlng, {
			icon: this.options.icon,
			zIndexOffset: this.options.zIndexOffset * 2
		});

		this._markerGroup.addLayer(marker);

		return marker;
	},

	_updateGuide: function (newPos) {
		var markerCount = this._markers ? this._markers.length : 0;

		if (markerCount > 0) {
			newPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng);

			// draw the guide line
			this._clearGuides();
			this._drawGuide(
				this._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()),
				newPos
			);
		}
	},

	_updateTooltip: function (latLng) {
		var text = this._getTooltipText();

		if (latLng) {
			this._tooltip.updatePosition(latLng);
		}

		if (!this._errorShown) {
			this._tooltip.updateContent(text);
		}
	},

	_drawGuide: function (pointA, pointB) {
		var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))),
			guidelineDistance = this.options.guidelineDistance,
			maxGuideLineLength = this.options.maxGuideLineLength,
			// Only draw a guideline with a max length
			i = length > maxGuideLineLength ? length - maxGuideLineLength : guidelineDistance,
			fraction,
			dashPoint,
			dash;

		//create the guides container if we haven't yet
		if (!this._guidesContainer) {
			this._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane);
		}

		//draw a dash every GuildeLineDistance
		for (; i < length; i += this.options.guidelineDistance) {
			//work out fraction along line we are
			fraction = i / length;

			//calculate new x,y point
			dashPoint = {
				x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)),
				y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y))
			};

			//add guide dash to guide container
			dash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer);
			dash.style.backgroundColor =
				!this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color;

			L.DomUtil.setPosition(dash, dashPoint);
		}
	},

	_updateGuideColor: function (color) {
		if (this._guidesContainer) {
			for (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) {
				this._guidesContainer.childNodes[i].style.backgroundColor = color;
			}
		}
	},

	// removes all child elements (guide dashes) from the guides container
	_clearGuides: function () {
		if (this._guidesContainer) {
			while (this._guidesContainer.firstChild) {
				this._guidesContainer.removeChild(this._guidesContainer.firstChild);
			}
		}
	},

	_getTooltipText: function () {
		var showLength = this.options.showLength,
			labelText, distanceStr;
		if (L.Browser.touch) {
			showLength = false; // if there's a better place to put this, feel free to move it
		}
		if (this._markers.length === 0) {
			labelText = {
				text: L.drawLocal.draw.handlers.polyline.tooltip.start
			};
		} else {
			distanceStr = showLength ? this._getMeasurementString() : '';

			if (this._markers.length === 1) {
				labelText = {
					text: L.drawLocal.draw.handlers.polyline.tooltip.cont,
					subtext: distanceStr
				};
			} else {
				labelText = {
					text: L.drawLocal.draw.handlers.polyline.tooltip.end,
					subtext: distanceStr
				};
			}
		}
		return labelText;
	},

	_updateRunningMeasure: function (latlng, added) {
		var markersLength = this._markers.length,
			previousMarkerIndex, distance;

		if (this._markers.length === 1) {
			this._measurementRunningTotal = 0;
		} else {
			previousMarkerIndex = markersLength - (added ? 2 : 1);
			distance = latlng.distanceTo(this._markers[previousMarkerIndex].getLatLng());

			this._measurementRunningTotal += distance * (added ? 1 : -1);
		}
	},

	_getMeasurementString: function () {
		var currentLatLng = this._currentLatLng,
			previousLatLng = this._markers[this._markers.length - 1].getLatLng(),
			distance;

		// calculate the distance from the last fixed point to the mouse position
		distance = this._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng);

		return L.GeometryUtil.readableDistance(distance, this.options.metric, this.options.feet, this.options.nautic);
	},

	_showErrorTooltip: function () {
		this._errorShown = true;

		// Update tooltip
		this._tooltip
			.showAsError()
			.updateContent({ text: this.options.drawError.message });

		// Update shape
		this._updateGuideColor(this.options.drawError.color);
		this._poly.setStyle({ color: this.options.drawError.color });

		// Hide the error after 2 seconds
		this._clearHideErrorTimeout();
		this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout);
	},

	_hideErrorTooltip: function () {
		this._errorShown = false;

		this._clearHideErrorTimeout();

		// Revert tooltip
		this._tooltip
			.removeError()
			.updateContent(this._getTooltipText());

		// Revert shape
		this._updateGuideColor(this.options.shapeOptions.color);
		this._poly.setStyle({ color: this.options.shapeOptions.color });
	},

	_clearHideErrorTimeout: function () {
		if (this._hideErrorTimeout) {
			clearTimeout(this._hideErrorTimeout);
			this._hideErrorTimeout = null;
		}
	},

	// disable new markers temporarily;
	// this is to prevent duplicated touch/click events in some browsers
	_disableNewMarkers: function () {
		this._disableMarkers = true;
	},

	// see _disableNewMarkers
	_enableNewMarkers: function () {
		setTimeout(function() {
			this._disableMarkers = false;
		}.bind(this), 50);
	},

	_cleanUpShape: function () {
		if (this._markers.length > 1) {
			this._markers[this._markers.length - 1].off('click', this._finishShape, this);
		}
	},

	_fireCreatedEvent: function () {
		var poly = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions);
		L.Draw.Feature.prototype._fireCreatedEvent.call(this, poly);
	}
});



/**
 * @class L.Draw.Polygon
 * @aka Draw.Polygon
 * @inherits L.Draw.Polyline
 */
L.Draw.Polygon = L.Draw.Polyline.extend({
	statics: {
		TYPE: 'polygon'
	},

	Poly: L.Polygon,

	options: {
		showArea: false,
		shapeOptions: {
			stroke: true,
			color: '#3388ff',
			weight: 4,
			opacity: 0.5,
			fill: true,
			fillColor: null, //same as color by default
			fillOpacity: 0.2,
			clickable: true
		},
		metric: true // Whether to use the metric measurement system or imperial
	},

	// @method initialize(): void
	initialize: function (map, options) {
		L.Draw.Polyline.prototype.initialize.call(this, map, options);

		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.Draw.Polygon.TYPE;
	},

	_updateFinishHandler: function () {
		var markerCount = this._markers.length;

		// The first marker should have a click handler to close the polygon
		if (markerCount === 1) {
			this._markers[0].on('click', this._finishShape, this);
		}

		// Add and update the double click handler
		if (markerCount > 2) {
			this._markers[markerCount - 1].on('dblclick', this._finishShape, this);
			// Only need to remove handler if has been added before
			if (markerCount > 3) {
				this._markers[markerCount - 2].off('dblclick', this._finishShape, this);
			}
		}
	},

	_getTooltipText: function () {
		var text, subtext;

		if (this._markers.length === 0) {
			text = L.drawLocal.draw.handlers.polygon.tooltip.start;
		} else if (this._markers.length < 3) {
			text = L.drawLocal.draw.handlers.polygon.tooltip.cont;
		} else {
			text = L.drawLocal.draw.handlers.polygon.tooltip.end;
			subtext = this._getMeasurementString();
		}

		return {
			text: text,
			subtext: subtext
		};
	},

	_getMeasurementString: function () {
		var area = this._area;

		if (!area) {
			return null;
		}

		return L.GeometryUtil.readableArea(area, this.options.metric);
	},

	_shapeIsValid: function () {
		return this._markers.length >= 3;
	},

	_vertexChanged: function (latlng, added) {
		var latLngs;

		// Check to see if we should show the area
		if (!this.options.allowIntersection && this.options.showArea) {
			latLngs = this._poly.getLatLngs();

			this._area = L.GeometryUtil.geodesicArea(latLngs);
		}

		L.Draw.Polyline.prototype._vertexChanged.call(this, latlng, added);
	},

	_cleanUpShape: function () {
		var markerCount = this._markers.length;

		if (markerCount > 0) {
			this._markers[0].off('click', this._finishShape, this);

			if (markerCount > 2) {
				this._markers[markerCount - 1].off('dblclick', this._finishShape, this);
			}
		}
	}
});



L.SimpleShape = {};
/**
 * @class L.Draw.SimpleShape
 * @aka Draw.SimpleShape
 * @inherits L.Draw.Feature
 */
L.Draw.SimpleShape = L.Draw.Feature.extend({
	options: {
		repeatMode: false
	},

	// @method initialize(): void
	initialize: function (map, options) {
		this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end;

		L.Draw.Feature.prototype.initialize.call(this, map, options);
	},

	// @method addHooks(): void
	// Add listener hooks to this handler.
	addHooks: function () {
		L.Draw.Feature.prototype.addHooks.call(this);
		if (this._map) {
			this._mapDraggable = this._map.dragging.enabled();

			if (this._mapDraggable) {
				this._map.dragging.disable();
			}

			//TODO refactor: move cursor to styles
			this._container.style.cursor = 'crosshair';

			this._tooltip.updateContent({ text: this._initialLabelText });

			this._map
				.on('mousedown', this._onMouseDown, this)
				.on('mousemove', this._onMouseMove, this)
				.on('touchstart', this._onMouseDown, this)
				.on('touchmove', this._onMouseMove, this);
		}
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler.
	removeHooks: function () {
		L.Draw.Feature.prototype.removeHooks.call(this);
		if (this._map) {
			if (this._mapDraggable) {
				this._map.dragging.enable();
			}

			//TODO refactor: move cursor to styles
			this._container.style.cursor = '';

			this._map
				.off('mousedown', this._onMouseDown, this)
				.off('mousemove', this._onMouseMove, this)
				.off('touchstart', this._onMouseDown, this)
				.off('touchmove', this._onMouseMove, this);

			L.DomEvent.off(document, 'mouseup', this._onMouseUp, this);
			L.DomEvent.off(document, 'touchend', this._onMouseUp, this);

			// If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return
			if (this._shape) {
				this._map.removeLayer(this._shape);
				delete this._shape;
			}
		}
		this._isDrawing = false;
	},

	_getTooltipText: function () {
		return {
			text: this._endLabelText
		};
	},

	_onMouseDown: function (e) {
		this._isDrawing = true;
		this._startLatLng = e.latlng;

		L.DomEvent
			.on(document, 'mouseup', this._onMouseUp, this)
			.on(document, 'touchend', this._onMouseUp, this)
			.preventDefault(e.originalEvent);
	},

	_onMouseMove: function (e) {
		var latlng = e.latlng;

		this._tooltip.updatePosition(latlng);
		if (this._isDrawing) {
			this._tooltip.updateContent(this._getTooltipText());
			this._drawShape(latlng);
		}
	},

	_onMouseUp: function () {
		if (this._shape) {
			this._fireCreatedEvent();
		}

		this.disable();
		if (this.options.repeatMode) {
			this.enable();
		}
	}
});


/**
 * @class L.Draw.Rectangle
 * @aka Draw.Rectangle
 * @inherits L.Draw.SimpleShape
 */
L.Draw.Rectangle = L.Draw.SimpleShape.extend({
	statics: {
		TYPE: 'rectangle'
	},

	options: {
		shapeOptions: {
			stroke: true,
			color: '#3388ff',
			weight: 4,
			opacity: 0.5,
			fill: true,
			fillColor: null, //same as color by default
			fillOpacity: 0.2,
			showArea: true,
			clickable: true
		},
		metric: true // Whether to use the metric measurement system or imperial
	},

	// @method initialize(): void
	initialize: function (map, options) {
		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.Draw.Rectangle.TYPE;

		this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start;

		L.Draw.SimpleShape.prototype.initialize.call(this, map, options);
	},

	_drawShape: function (latlng) {
		if (!this._shape) {
			this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions);
			this._map.addLayer(this._shape);
		} else {
			this._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng));
		}
	},

	_fireCreatedEvent: function () {
		var rectangle = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions);
		L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle);
	},

	_getTooltipText: function () {
		var tooltipText = L.Draw.SimpleShape.prototype._getTooltipText.call(this),
			shape = this._shape,
			showArea = this.options.showArea,
			latLngs, area, subtext;

		if (shape) {
			latLngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs();
			area = L.GeometryUtil.geodesicArea(latLngs);
			subtext = showArea ? L.GeometryUtil.readableArea(area, this.options.metric) : ''
		}

		return {
			text: tooltipText.text,
			subtext: subtext
		};
	}
});



/**
 * @class L.Draw.Circle
 * @aka Draw.Circle
 * @inherits L.Draw.SimpleShape
 */
L.Draw.Circle = L.Draw.SimpleShape.extend({
	statics: {
		TYPE: 'circle'
	},

	options: {
		shapeOptions: {
			stroke: true,
			color: '#3388ff',
			weight: 4,
			opacity: 0.5,
			fill: true,
			fillColor: null, //same as color by default
			fillOpacity: 0.2,
			clickable: true
		},
		showRadius: true,
		metric: true, // Whether to use the metric measurement system or imperial
		feet: true, // When not metric, use feet instead of yards for display
		nautic: false // When not metric, not feet use nautic mile for display
	},

	// @method initialize(): void
	initialize: function (map, options) {
		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.Draw.Circle.TYPE;

		this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start;

		L.Draw.SimpleShape.prototype.initialize.call(this, map, options);
	},

	_drawShape: function (latlng) {
		if (!this._shape) {
			this._shape = new L.Circle(this._startLatLng, this._startLatLng.distanceTo(latlng), this.options.shapeOptions);
			this._map.addLayer(this._shape);
		} else {
			this._shape.setRadius(this._startLatLng.distanceTo(latlng));
		}
	},

	_fireCreatedEvent: function () {
		var circle = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions);
		L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle);
	},

	_onMouseMove: function (e) {
		var latlng = e.latlng,
			showRadius = this.options.showRadius,
			useMetric = this.options.metric,
			radius;

		this._tooltip.updatePosition(latlng);
		if (this._isDrawing) {
			this._drawShape(latlng);

			// Get the new radius (rounded to 1 dp)
			radius = this._shape.getRadius().toFixed(1);

			var subtext = '';
			if (showRadius) {
				subtext = L.drawLocal.draw.handlers.circle.radius + ': ' +
						  L.GeometryUtil.readableDistance(radius, useMetric, this.options.feet, this.options.nautic);
			}
			this._tooltip.updateContent({
				text: this._endLabelText,
				subtext: subtext
			});
		}
	}
});



/**
 * @class L.Draw.Marker
 * @aka Draw.Marker
 * @inherits L.Draw.Feature
 */
L.Draw.Marker = L.Draw.Feature.extend({
	statics: {
		TYPE: 'marker'
	},

	options: {
		icon: new L.Icon.Default(),
		repeatMode: false,
		zIndexOffset: 2000 // This should be > than the highest z-index any markers
	},

	// @method initialize(): void
	initialize: function (map, options) {
		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.Draw.Marker.TYPE;

		L.Draw.Feature.prototype.initialize.call(this, map, options);
	},

	// @method addHooks(): void
	// Add listener hooks to this handler.
	addHooks: function () {
		L.Draw.Feature.prototype.addHooks.call(this);

		if (this._map) {
			this._tooltip.updateContent({ text: L.drawLocal.draw.handlers.marker.tooltip.start });

			// Same mouseMarker as in Draw.Polyline
			if (!this._mouseMarker) {
				this._mouseMarker = L.marker(this._map.getCenter(), {
					icon: L.divIcon({
						className: 'leaflet-mouse-marker',
						iconAnchor: [20, 20],
						iconSize: [40, 40]
					}),
					opacity: 0,
					zIndexOffset: this.options.zIndexOffset
				});
			}

			this._mouseMarker
				.on('click', this._onClick, this)
				.addTo(this._map);

			this._map.on('mousemove', this._onMouseMove, this);
			this._map.on('click', this._onTouch, this);
		}
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler.
	removeHooks: function () {
		L.Draw.Feature.prototype.removeHooks.call(this);

		if (this._map) {
			if (this._marker) {
				this._marker.off('click', this._onClick, this);
				this._map
					.off('click', this._onClick, this)
					.off('click', this._onTouch, this)
					.removeLayer(this._marker);
				delete this._marker;
			}

			this._mouseMarker.off('click', this._onClick, this);
			this._map.removeLayer(this._mouseMarker);
			delete this._mouseMarker;

			this._map.off('mousemove', this._onMouseMove, this);
		}
	},

	_onMouseMove: function (e) {
		var latlng = e.latlng;

		this._tooltip.updatePosition(latlng);
		this._mouseMarker.setLatLng(latlng);

		if (!this._marker) {
			this._marker = new L.Marker(latlng, {
				icon: this.options.icon,
				zIndexOffset: this.options.zIndexOffset
			});
			// Bind to both marker and map to make sure we get the click event.
			this._marker.on('click', this._onClick, this);
			this._map
				.on('click', this._onClick, this)
				.addLayer(this._marker);
		}
		else {
			latlng = this._mouseMarker.getLatLng();
			this._marker.setLatLng(latlng);
		}
	},

	_onClick: function () {
		this._fireCreatedEvent();

		this.disable();
		if (this.options.repeatMode) {
			this.enable();
		}
	},

	_onTouch: function (e) {
		// called on click & tap, only really does any thing on tap
		this._onMouseMove(e); // creates & places marker
		this._onClick(); // permanently places marker & ends interaction
	},

	_fireCreatedEvent: function () {
		var marker = new L.Marker.Touch(this._marker.getLatLng(), { icon: this.options.icon });
		L.Draw.Feature.prototype._fireCreatedEvent.call(this, marker);
	}
});



L.Edit = L.Edit || {};

/**
 * @class L.Edit.Marker
 * @aka Edit.Marker
 */
L.Edit.Marker = L.Handler.extend({
	// @method initialize(): void
	initialize: function (marker, options) {
		this._marker = marker;
		L.setOptions(this, options);
	},

	// @method addHooks(): void
	// Add listener hooks to this handler
	addHooks: function () {
		var marker = this._marker;

		marker.dragging.enable();
		marker.on('dragend', this._onDragEnd, marker);
		this._toggleMarkerHighlight();
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler
	removeHooks: function () {
		var marker = this._marker;

		marker.dragging.disable();
		marker.off('dragend', this._onDragEnd, marker);
		this._toggleMarkerHighlight();
	},

	_onDragEnd: function (e) {
		var layer = e.target;
		layer.edited = true;
		this._map.fire(L.Draw.Event.EDITMOVE, { layer: layer });
	},

	_toggleMarkerHighlight: function () {
		var icon = this._marker._icon;

		// Don't do anything if this layer is a marker but doesn't have an icon. Markers
		// should usually have icons. If using Leaflet.draw with Leaflet.markercluster there
		// is a chance that a marker doesn't.
		if (!icon) {
			return;
		}

		// This is quite naughty, but I don't see another way of doing it. (short of setting a new icon)
		icon.style.display = 'none';

		if (L.DomUtil.hasClass(icon, 'leaflet-edit-marker-selected')) {
			L.DomUtil.removeClass(icon, 'leaflet-edit-marker-selected');
			// Offset as the border will make the icon move.
			this._offsetMarker(icon, -4);

		} else {
			L.DomUtil.addClass(icon, 'leaflet-edit-marker-selected');
			// Offset as the border will make the icon move.
			this._offsetMarker(icon, 4);
		}

		icon.style.display = '';
	},

	_offsetMarker: function (icon, offset) {
		var iconMarginTop = parseInt(icon.style.marginTop, 10) - offset,
			iconMarginLeft = parseInt(icon.style.marginLeft, 10) - offset;

		icon.style.marginTop = iconMarginTop + 'px';
		icon.style.marginLeft = iconMarginLeft + 'px';
	}
});

L.Marker.addInitHook(function () {
	if (L.Edit.Marker) {
		this.editing = new L.Edit.Marker(this);

		if (this.options.editable) {
			this.editing.enable();
		}
	}
});



L.Edit = L.Edit || {};

/**
 * @class L.Edit.Polyline
 * @aka L.Edit.Poly
 * @aka Edit.Poly
 */
L.Edit.Poly = L.Handler.extend({
	options: {},

	// @method initialize(): void
	initialize: function (poly, options) {

		this.latlngs = [poly._latlngs];
		if (poly._holes) {
			this.latlngs = this.latlngs.concat(poly._holes);
		}

		this._poly = poly;
		L.setOptions(this, options);

		this._poly.on('revert-edited', this._updateLatLngs, this);
	},

	// Compatibility method to normalize Poly* objects
	// between 0.7.x and 1.0+
	_defaultShape: function () {
		if (!L.Polyline._flat) {
			return this._poly._latlngs;
		}
		return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0];
	},

	_eachVertexHandler: function (callback) {
		for (var i = 0; i < this._verticesHandlers.length; i++) {
			callback(this._verticesHandlers[i]);
		}
	},

	// @method addHooks(): void
	// Add listener hooks to this handler
	addHooks: function () {
		this._initHandlers();
		this._eachVertexHandler(function (handler) {
			handler.addHooks();
		});
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler
	removeHooks: function () {
		this._eachVertexHandler(function (handler) {
			handler.removeHooks();
		});
	},

	// @method updateMarkers(): void
	// Fire an update for each vertex handler
	updateMarkers: function () {
		this._eachVertexHandler(function (handler) {
			handler.updateMarkers();
		});
	},

	_initHandlers: function () {
		this._verticesHandlers = [];
		for (var i = 0; i < this.latlngs.length; i++) {
			this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this.options));
		}
	},

	_updateLatLngs: function (e) {
		this.latlngs = [e.layer._latlngs];
		if (e.layer._holes) {
			this.latlngs = this.latlngs.concat(e.layer._holes);
		}
	}

});

/**
 * @class L.Edit.PolyVerticesEdit
 * @aka Edit.PolyVerticesEdit
 */
L.Edit.PolyVerticesEdit = L.Handler.extend({
	options: {
		icon: new L.DivIcon({
			iconSize: new L.Point(8, 8),
			className: 'leaflet-div-icon leaflet-editing-icon'
		}),
		touchIcon: new L.DivIcon({
			iconSize: new L.Point(20, 20),
			className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
		}),
		drawError: {
			color: '#b00b00',
			timeout: 1000
		}


	},

	// @method intialize(): void
	initialize: function (poly, latlngs, options) {
		// if touch, switch to touch icon
		if (L.Browser.touch) {
			this.options.icon = this.options.touchIcon;
		}
		this._poly = poly;

		if (options && options.drawError) {
			options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
		}

		this._latlngs = latlngs;

		L.setOptions(this, options);
	},

	// Compatibility method to normalize Poly* objects
	// between 0.7.x and 1.0+
	_defaultShape: function () {
		if (!L.Polyline._flat) {
			return this._latlngs;
		}
		return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
	},

	// @method addHooks(): void
	// Add listener hooks to this handler.
	addHooks: function () {
		var poly = this._poly;

		if (!(poly instanceof L.Polygon)) {
			poly.options.fill = false;
			if (poly.options.editing) {
				poly.options.editing.fill = false;
			}
		}

		poly.setStyle(poly.options.editing);

		if (this._poly._map) {

			this._map = this._poly._map; // Set map

			if (!this._markerGroup) {
				this._initMarkers();
			}
			this._poly._map.addLayer(this._markerGroup);
		}
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler.
	removeHooks: function () {
		var poly = this._poly;

		poly.setStyle(poly.options.original);

		if (poly._map) {
			poly._map.removeLayer(this._markerGroup);
			delete this._markerGroup;
			delete this._markers;
		}
	},

	// @method updateMarkers(): void
	// Clear markers and update their location
	updateMarkers: function () {
		this._markerGroup.clearLayers();
		this._initMarkers();
	},

	_initMarkers: function () {
		if (!this._markerGroup) {
			this._markerGroup = new L.LayerGroup();
		}
		this._markers = [];

		var latlngs = this._defaultShape(),
			i, j, len, marker;

		for (i = 0, len = latlngs.length; i < len; i++) {

			marker = this._createMarker(latlngs[i], i);
			marker.on('click', this._onMarkerClick, this);
			this._markers.push(marker);
		}

		var markerLeft, markerRight;

		for (i = 0, j = len - 1; i < len; j = i++) {
			if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
				continue;
			}

			markerLeft = this._markers[j];
			markerRight = this._markers[i];

			this._createMiddleMarker(markerLeft, markerRight);
			this._updatePrevNext(markerLeft, markerRight);
		}
	},

	_createMarker: function (latlng, index) {
		// Extending L.Marker in TouchEvents.js to include touch.
		var marker = new L.Marker.Touch(latlng, {
			draggable: true,
			icon: this.options.icon,
		});

		marker._origLatLng = latlng;
		marker._index = index;

		marker
			.on('dragstart', this._onMarkerDragStart, this)
			.on('drag', this._onMarkerDrag, this)
			.on('dragend', this._fireEdit, this)
			.on('touchmove', this._onTouchMove, this)
			.on('touchend', this._fireEdit, this)
			.on('MSPointerMove', this._onTouchMove, this)
			.on('MSPointerUp', this._fireEdit, this);

		this._markerGroup.addLayer(marker);

		return marker;
	},

	_onMarkerDragStart: function () {
		this._poly.fire('editstart');
	},

	_spliceLatLngs: function () {
		var latlngs = this._defaultShape();
		var removed = [].splice.apply(latlngs, arguments);
		this._poly._convertLatLngs(latlngs, true);
		this._poly.redraw();
		return removed;
	},

	_removeMarker: function (marker) {
		var i = marker._index;

		this._markerGroup.removeLayer(marker);
		this._markers.splice(i, 1);
		this._spliceLatLngs(i, 1);
		this._updateIndexes(i, -1);

		marker
			.off('dragstart', this._onMarkerDragStart, this)
			.off('drag', this._onMarkerDrag, this)
			.off('dragend', this._fireEdit, this)
			.off('touchmove', this._onMarkerDrag, this)
			.off('touchend', this._fireEdit, this)
			.off('click', this._onMarkerClick, this)
			.off('MSPointerMove', this._onTouchMove, this)
			.off('MSPointerUp', this._fireEdit, this);
	},

	_fireEdit: function () {
		this._poly.edited = true;
		this._poly.fire('edit');
		this._poly._map.fire(L.Draw.Event.EDITVERTEX, { layers: this._markerGroup, poly: this._poly });
	},

	_onMarkerDrag: function (e) {
		var marker = e.target;
		var poly = this._poly;

		L.extend(marker._origLatLng, marker._latlng);

		if (marker._middleLeft) {
			marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
		}
		if (marker._middleRight) {
			marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
		}

		if (poly.options.poly) {
			var tooltip = poly._map._editTooltip; // Access the tooltip

			// If we don't allow intersections and the polygon intersects
			if (!poly.options.poly.allowIntersection && poly.intersects()) {

				var originalColor = poly.options.color;
				poly.setStyle({ color: this.options.drawError.color });

				// Manually trigger 'dragend' behavior on marker we are about to remove
				// WORKAROUND: introduced in 1.0.0-rc2, may be related to #4484
				if (L.version.indexOf('0.7') !== 0) {
					marker.dragging._draggable._onUp(e);
				}
				this._onMarkerClick(e); // Remove violating marker
				// FIXME: Reset the marker to it's original position (instead of remove)

				if (tooltip) {
					tooltip.updateContent({
						text: L.drawLocal.draw.handlers.polyline.error
					});
				}

				// Reset everything back to normal after a second
				setTimeout(function () {
					poly.setStyle({ color: originalColor });
					if (tooltip) {
						tooltip.updateContent({
							text: L.drawLocal.edit.handlers.edit.tooltip.text,
							subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
						});
					}
				}, 1000);
			}
		}

		this._poly.redraw();
		this._poly.fire('editdrag');
	},

	_onMarkerClick: function (e) {

		var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3,
			marker = e.target;

		// If removing this point would create an invalid polyline/polygon don't remove
		if (this._defaultShape().length < minPoints) {
			return;
		}

		// remove the marker
		this._removeMarker(marker);

		// update prev/next links of adjacent markers
		this._updatePrevNext(marker._prev, marker._next);

		// remove ghost markers near the removed marker
		if (marker._middleLeft) {
			this._markerGroup.removeLayer(marker._middleLeft);
		}
		if (marker._middleRight) {
			this._markerGroup.removeLayer(marker._middleRight);
		}

		// create a ghost marker in place of the removed one
		if (marker._prev && marker._next) {
			this._createMiddleMarker(marker._prev, marker._next);

		} else if (!marker._prev) {
			marker._next._middleLeft = null;

		} else if (!marker._next) {
			marker._prev._middleRight = null;
		}

		this._fireEdit();
	},

	_onTouchMove: function (e) {

		var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
			latlng = this._map.layerPointToLatLng(layerPoint),
			marker = e.target;

		L.extend(marker._origLatLng, latlng);

		if (marker._middleLeft) {
			marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
		}
		if (marker._middleRight) {
			marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
		}

		this._poly.redraw();
		this.updateMarkers();
	},

	_updateIndexes: function (index, delta) {
		this._markerGroup.eachLayer(function (marker) {
			if (marker._index > index) {
				marker._index += delta;
			}
		});
	},

	_createMiddleMarker: function (marker1, marker2) {
		var latlng = this._getMiddleLatLng(marker1, marker2),
			marker = this._createMarker(latlng),
			onClick,
			onDragStart,
			onDragEnd;

		marker.setOpacity(0.6);

		marker1._middleRight = marker2._middleLeft = marker;

		onDragStart = function () {
			marker.off('touchmove', onDragStart, this);
			var i = marker2._index;

			marker._index = i;

			marker
				.off('click', onClick, this)
				.on('click', this._onMarkerClick, this);

			latlng.lat = marker.getLatLng().lat;
			latlng.lng = marker.getLatLng().lng;
			this._spliceLatLngs(i, 0, latlng);
			this._markers.splice(i, 0, marker);

			marker.setOpacity(1);

			this._updateIndexes(i, 1);
			marker2._index++;
			this._updatePrevNext(marker1, marker);
			this._updatePrevNext(marker, marker2);

			this._poly.fire('editstart');
		};

		onDragEnd = function () {
			marker.off('dragstart', onDragStart, this);
			marker.off('dragend', onDragEnd, this);
			marker.off('touchmove', onDragStart, this);

			this._createMiddleMarker(marker1, marker);
			this._createMiddleMarker(marker, marker2);
		};

		onClick = function () {
			onDragStart.call(this);
			onDragEnd.call(this);
			this._fireEdit();
		};

		marker
			.on('click', onClick, this)
			.on('dragstart', onDragStart, this)
			.on('dragend', onDragEnd, this)
			.on('touchmove', onDragStart, this);

		this._markerGroup.addLayer(marker);
	},

	_updatePrevNext: function (marker1, marker2) {
		if (marker1) {
			marker1._next = marker2;
		}
		if (marker2) {
			marker2._prev = marker1;
		}
	},

	_getMiddleLatLng: function (marker1, marker2) {
		var map = this._poly._map,
			p1 = map.project(marker1.getLatLng()),
			p2 = map.project(marker2.getLatLng());

		return map.unproject(p1._add(p2)._divideBy(2));
	}
});

L.Polyline.addInitHook(function () {

	// Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit
	if (this.editing) {
		return;
	}

	if (L.Edit.Poly) {

		this.editing = new L.Edit.Poly(this, this.options.poly);

		if (this.options.editable) {
			this.editing.enable();
		}
	}

	this.on('add', function () {
		if (this.editing && this.editing.enabled()) {
			this.editing.addHooks();
		}
	});

	this.on('remove', function () {
		if (this.editing && this.editing.enabled()) {
			this.editing.removeHooks();
		}
	});
});



L.Edit = L.Edit || {};
/**
 * @class L.Edit.SimpleShape
 * @aka Edit.SimpleShape
 */
L.Edit.SimpleShape = L.Handler.extend({
	options: {
		moveIcon: new L.DivIcon({
			iconSize: new L.Point(8, 8),
			className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move'
		}),
		resizeIcon: new L.DivIcon({
			iconSize: new L.Point(8, 8),
			className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize'
		}),
		touchMoveIcon: new L.DivIcon({
			iconSize: new L.Point(20, 20),
			className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon'
		}),
		touchResizeIcon: new L.DivIcon({
			iconSize: new L.Point(20, 20),
			className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon'
		}),
	},

	// @method intialize(): void
	initialize: function (shape, options) {
		// if touch, switch to touch icon
		if (L.Browser.touch) {
			this.options.moveIcon = this.options.touchMoveIcon;
			this.options.resizeIcon = this.options.touchResizeIcon;
		}

		this._shape = shape;
		L.Util.setOptions(this, options);
	},

	// @method addHooks(): void
	// Add listener hooks to this handler
	addHooks: function () {
		var shape = this._shape;
		if (this._shape._map) {
			this._map = this._shape._map;
			shape.setStyle(shape.options.editing);

			if (shape._map) {
				this._map = shape._map;
				if (!this._markerGroup) {
					this._initMarkers();
				}
				this._map.addLayer(this._markerGroup);
			}
		}
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler
	removeHooks: function () {
		var shape = this._shape;

		shape.setStyle(shape.options.original);

		if (shape._map) {
			this._unbindMarker(this._moveMarker);

			for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
				this._unbindMarker(this._resizeMarkers[i]);
			}
			this._resizeMarkers = null;

			this._map.removeLayer(this._markerGroup);
			delete this._markerGroup;
		}

		this._map = null;
	},

	// @method updateMarkers(): void
	// Remove the edit markers from this layer
	updateMarkers: function () {
		this._markerGroup.clearLayers();
		this._initMarkers();
	},

	_initMarkers: function () {
		if (!this._markerGroup) {
			this._markerGroup = new L.LayerGroup();
		}

		// Create center marker
		this._createMoveMarker();

		// Create edge marker
		this._createResizeMarker();
	},

	_createMoveMarker: function () {
		// Children override
	},

	_createResizeMarker: function () {
		// Children override
	},

	_createMarker: function (latlng, icon) {
		// Extending L.Marker in TouchEvents.js to include touch.
		var marker = new L.Marker.Touch(latlng, {
			draggable: true,
			icon: icon,
			zIndexOffset: 10
		});

		this._bindMarker(marker);

		this._markerGroup.addLayer(marker);

		return marker;
	},

	_bindMarker: function (marker) {
		marker
			.on('dragstart', this._onMarkerDragStart, this)
			.on('drag', this._onMarkerDrag, this)
			.on('dragend', this._onMarkerDragEnd, this)
			.on('touchstart', this._onTouchStart, this)
			.on('touchmove', this._onTouchMove, this)
			.on('MSPointerMove', this._onTouchMove, this)
			.on('touchend', this._onTouchEnd, this)
			.on('MSPointerUp', this._onTouchEnd, this);
	},

	_unbindMarker: function (marker) {
		marker
			.off('dragstart', this._onMarkerDragStart, this)
			.off('drag', this._onMarkerDrag, this)
			.off('dragend', this._onMarkerDragEnd, this)
			.off('touchstart', this._onTouchStart, this)
			.off('touchmove', this._onTouchMove, this)
			.off('MSPointerMove', this._onTouchMove, this)
			.off('touchend', this._onTouchEnd, this)
			.off('MSPointerUp', this._onTouchEnd, this);
	},

	_onMarkerDragStart: function (e) {
		var marker = e.target;
		marker.setOpacity(0);

		this._shape.fire('editstart');
	},

	_fireEdit: function () {
		this._shape.edited = true;
		this._shape.fire('edit');
	},

	_onMarkerDrag: function (e) {
		var marker = e.target,
			latlng = marker.getLatLng();

		if (marker === this._moveMarker) {
			this._move(latlng);
		} else {
			this._resize(latlng);
		}

		this._shape.redraw();
		this._shape.fire('editdrag');
	},

	_onMarkerDragEnd: function (e) {
		var marker = e.target;
		marker.setOpacity(1);

		this._fireEdit();
	},

	_onTouchStart: function (e) {
		L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);

		if (typeof(this._getCorners) === 'function') {
			// Save a reference to the opposite point
			var corners = this._getCorners(),
				marker = e.target,
				currentCornerIndex = marker._cornerIndex;

			marker.setOpacity(0);

			// Copyed from Edit.Rectangle.js line 23 _onMarkerDragStart()
			// Latlng is null otherwise.
			this._oppositeCorner = corners[(currentCornerIndex + 2) % 4];
			this._toggleCornerMarkers(0, currentCornerIndex);
		}

		this._shape.fire('editstart');
	},

	_onTouchMove: function (e) {
		var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
			latlng = this._map.layerPointToLatLng(layerPoint),
			marker = e.target;

		if (marker === this._moveMarker) {
			this._move(latlng);
		} else {
			this._resize(latlng);
		}

		this._shape.redraw();

		// prevent touchcancel in IOS
		// e.preventDefault();
		return false;
	},

	_onTouchEnd: function (e) {
		var marker = e.target;
		marker.setOpacity(1);
		this.updateMarkers();
		this._fireEdit();
	},

	_move: function () {
		// Children override
	},

	_resize: function () {
		// Children override
	}
});



L.Edit = L.Edit || {};
/**
 * @class L.Edit.Rectangle
 * @aka Edit.Rectangle
 * @inherits L.Edit.SimpleShape
 */
L.Edit.Rectangle = L.Edit.SimpleShape.extend({
	_createMoveMarker: function () {
		var bounds = this._shape.getBounds(),
			center = bounds.getCenter();

		this._moveMarker = this._createMarker(center, this.options.moveIcon);
	},

	_createResizeMarker: function () {
		var corners = this._getCorners();

		this._resizeMarkers = [];

		for (var i = 0, l = corners.length; i < l; i++) {
			this._resizeMarkers.push(this._createMarker(corners[i], this.options.resizeIcon));
			// Monkey in the corner index as we will need to know this for dragging
			this._resizeMarkers[i]._cornerIndex = i;
		}
	},

	_onMarkerDragStart: function (e) {
		L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);

		// Save a reference to the opposite point
		var corners = this._getCorners(),
			marker = e.target,
			currentCornerIndex = marker._cornerIndex;

		this._oppositeCorner = corners[(currentCornerIndex + 2) % 4];

		this._toggleCornerMarkers(0, currentCornerIndex);
	},

	_onMarkerDragEnd: function (e) {
		var marker = e.target,
			bounds, center;

		// Reset move marker position to the center
		if (marker === this._moveMarker) {
			bounds = this._shape.getBounds();
			center = bounds.getCenter();

			marker.setLatLng(center);
		}

		this._toggleCornerMarkers(1);

		this._repositionCornerMarkers();

		L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e);
	},

	_move: function (newCenter) {
		var latlngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(),
			bounds = this._shape.getBounds(),
			center = bounds.getCenter(),
			offset, newLatLngs = [];

		// Offset the latlngs to the new center
		for (var i = 0, l = latlngs.length; i < l; i++) {
			offset = [latlngs[i].lat - center.lat, latlngs[i].lng - center.lng];
			newLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]]);
		}

		this._shape.setLatLngs(newLatLngs);

		// Reposition the resize markers
		this._repositionCornerMarkers();

		this._map.fire(L.Draw.Event.EDITMOVE, { layer: this._shape });
	},

	_resize: function (latlng) {
		var bounds;

		// Update the shape based on the current position of this corner and the opposite point
		this._shape.setBounds(L.latLngBounds(latlng, this._oppositeCorner));

		// Reposition the move marker
		bounds = this._shape.getBounds();
		this._moveMarker.setLatLng(bounds.getCenter());

		this._map.fire(L.Draw.Event.EDITRESIZE, { layer: this._shape });
	},

	_getCorners: function () {
		var bounds = this._shape.getBounds(),
			nw = bounds.getNorthWest(),
			ne = bounds.getNorthEast(),
			se = bounds.getSouthEast(),
			sw = bounds.getSouthWest();

		return [nw, ne, se, sw];
	},

	_toggleCornerMarkers: function (opacity) {
		for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
			this._resizeMarkers[i].setOpacity(opacity);
		}
	},

	_repositionCornerMarkers: function () {
		var corners = this._getCorners();

		for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
			this._resizeMarkers[i].setLatLng(corners[i]);
		}
	}
});

L.Rectangle.addInitHook(function () {
	if (L.Edit.Rectangle) {
		this.editing = new L.Edit.Rectangle(this);

		if (this.options.editable) {
			this.editing.enable();
		}
	}
});



L.Edit = L.Edit || {};
/**
 * @class L.Edit.Circle
 * @aka Edit.Circle
 * @inherits L.Edit.SimpleShape
 */
L.Edit.Circle = L.Edit.SimpleShape.extend({
	_createMoveMarker: function () {
		var center = this._shape.getLatLng();

		this._moveMarker = this._createMarker(center, this.options.moveIcon);
	},

	_createResizeMarker: function () {
		var center = this._shape.getLatLng(),
			resizemarkerPoint = this._getResizeMarkerPoint(center);

		this._resizeMarkers = [];
		this._resizeMarkers.push(this._createMarker(resizemarkerPoint, this.options.resizeIcon));
	},

	_getResizeMarkerPoint: function (latlng) {
		// From L.shape.getBounds()
		var delta = this._shape._radius * Math.cos(Math.PI / 4),
			point = this._map.project(latlng);
		return this._map.unproject([point.x + delta, point.y - delta]);
	},

	_move: function (latlng) {
		var resizemarkerPoint = this._getResizeMarkerPoint(latlng);

		// Move the resize marker
		this._resizeMarkers[0].setLatLng(resizemarkerPoint);

		// Move the circle
		this._shape.setLatLng(latlng);

		this._map.fire(L.Draw.Event.EDITMOVE, { layer: this._shape });
	},

	_resize: function (latlng) {
		var moveLatLng = this._moveMarker.getLatLng(),
			radius = moveLatLng.distanceTo(latlng);

		this._shape.setRadius(radius);

		this._map.fire(L.Draw.Event.EDITRESIZE, { layer: this._shape });
	}
});

L.Circle.addInitHook(function () {
	if (L.Edit.Circle) {
		this.editing = new L.Edit.Circle(this);

		if (this.options.editable) {
			this.editing.enable();
		}
	}

	this.on('add', function () {
		if (this.editing && this.editing.enabled()) {
			this.editing.addHooks();
		}
	});

	this.on('remove', function () {
		if (this.editing && this.editing.enabled()) {
			this.editing.removeHooks();
		}
	});
});


L.Map.mergeOptions({
	touchExtend: true
});

/**
 * @class L.Map.TouchExtend
 * @aka TouchExtend
 */
L.Map.TouchExtend = L.Handler.extend({

	// @method initialize(): void
	// Sets TouchExtend private accessor variables
	initialize: function (map) {
		this._map = map;
		this._container = map._container;
		this._pane = map._panes.overlayPane;
	},

	// @method addHooks(): void
	// Adds dom listener events to the map container
	addHooks: function () {
		L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
		L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
		L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);
		if (this._detectIE()) {
			L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this);
			L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this);
			L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this);
			L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this);

		} else {
			L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this);
			L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this);
		}
	},

	// @method removeHooks(): void
	// Removes dom listener events from the map container
	removeHooks: function () {
		L.DomEvent.off(this._container, 'touchstart', this._onTouchStart);
		L.DomEvent.off(this._container, 'touchend', this._onTouchEnd);
		L.DomEvent.off(this._container, 'touchmove', this._onTouchMove);
		if (this._detectIE()) {
			L.DomEvent.off(this._container, 'MSPointerDowm', this._onTouchStart);
			L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd);
			L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove);
			L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel);
		} else {
			L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel);
			L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave);
		}
	},

	_touchEvent: function (e, type) {
		// #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events
		// _filterClick is what leaflet uses as a workaround.
		// This is a problem with more things than just android. Another problem is touchEnd has no touches in
		// its touch list.
		var touchEvent = {};
		if (typeof e.touches !== 'undefined') {
			if (!e.touches.length) {
				return;
			}
			touchEvent = e.touches[0];
		} else if (e.pointerType === 'touch') {
			touchEvent = e;
			if (!this._filterClick(e)) {
				return;
			}
		} else {
			return;
		}

		var containerPoint = this._map.mouseEventToContainerPoint(touchEvent),
			layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
			latlng = this._map.layerPointToLatLng(layerPoint);

		this._map.fire(type, {
			latlng: latlng,
			layerPoint: layerPoint,
			containerPoint: containerPoint,
			pageX: touchEvent.pageX,
			pageY: touchEvent.pageY,
			originalEvent: e
		});
	},

	/** Borrowed from Leaflet and modified for bool ops **/
	_filterClick: function (e) {
		var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
			elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);

		// are they closer together than 500ms yet more than 100ms?
		// Android typically triggers them ~300ms apart while multiple listeners
		// on the same event should be triggered far faster;
		// or check if click is simulated on the element, and if it is, reject any non-simulated events
		if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
			L.DomEvent.stop(e);
			return false;
		}
		L.DomEvent._lastClick = timeStamp;
		return true;
	},

	_onTouchStart: function (e) {
		if (!this._map._loaded) {
			return;
		}

		var type = 'touchstart';
		this._touchEvent(e, type);

	},

	_onTouchEnd: function (e) {
		if (!this._map._loaded) {
			return;
		}

		var type = 'touchend';
		this._touchEvent(e, type);
	},

	_onTouchCancel: function (e) {
		if (!this._map._loaded) {
			return;
		}

		var type = 'touchcancel';
		if (this._detectIE()) {
			type = 'pointercancel';
		}
		this._touchEvent(e, type);
	},

	_onTouchLeave: function (e) {
		if (!this._map._loaded) {
			return;
		}

		var type = 'touchleave';
		this._touchEvent(e, type);
	},

	_onTouchMove: function (e) {
		if (!this._map._loaded) {
			return;
		}

		var type = 'touchmove';
		this._touchEvent(e, type);
	},

	_detectIE: function () {
		var ua = window.navigator.userAgent;

		var msie = ua.indexOf('MSIE ');
		if (msie > 0) {
			// IE 10 or older => return version number
			return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
		}

		var trident = ua.indexOf('Trident/');
		if (trident > 0) {
			// IE 11 => return version number
			var rv = ua.indexOf('rv:');
			return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
		}

		var edge = ua.indexOf('Edge/');
		if (edge > 0) {
			// IE 12 => return version number
			return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
		}

		// other browser
		return false;
	}
});

L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);


/**
 * @class L.Marker.Touch
 * @aka Marker.Touch
 *
 * This isn't full Touch support. This is just to get markers to also support dom touch events after creation
 * #TODO: find a better way of getting markers to support touch.
 */
L.Marker.Touch = L.Marker.extend({

	_initInteraction: function () {
		if (!this.addInteractiveTarget) {
			// 0.7.x support
			return this._initInteractionLegacy();
		}
		// TODO this may need be updated to re-add touch events for 1.0+
		return L.Marker.prototype._initInteraction.apply(this);
	},

	// This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js
	// with the addition of the touch events
	_initInteractionLegacy: function () {

		if (!this.options.clickable) {
			return;
		}

		// TODO refactor into something shared with Map/Path/etc. to DRY it up

		var icon = this._icon,
			events = ['dblclick',
					  'mousedown',
					  'mouseover',
					  'mouseout',
					  'contextmenu',
					  'touchstart',
					  'touchend',
					  'touchmove'];
		if (this._detectIE) {
			events.concat(['MSPointerDown',
						   'MSPointerUp',
						   'MSPointerMove',
						   'MSPointerCancel']);
		} else {
			events.concat(['touchcancel']);
		}

		L.DomUtil.addClass(icon, 'leaflet-clickable');
		L.DomEvent.on(icon, 'click', this._onMouseClick, this);
		L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);

		for (var i = 0; i < events.length; i++) {
			L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
		}

		if (L.Handler.MarkerDrag) {
			this.dragging = new L.Handler.MarkerDrag(this);

			if (this.options.draggable) {
				this.dragging.enable();
			}
		}
	},

	_detectIE: function () {
		var ua = window.navigator.userAgent;

		var msie = ua.indexOf('MSIE ');
		if (msie > 0) {
			// IE 10 or older => return version number
			return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
		}

		var trident = ua.indexOf('Trident/');
		if (trident > 0) {
			// IE 11 => return version number
			var rv = ua.indexOf('rv:');
			return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
		}

		var edge = ua.indexOf('Edge/');
		if (edge > 0) {
			// IE 12 => return version number
			return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
		}

		// other browser
		return false;
	}
});



/**
 * @class L.LatLngUtil
 * @aka LatLngUtil
 */
L.LatLngUtil = {
	// Clones a LatLngs[], returns [][]

	// @method cloneLatLngs(LatLngs[]): L.LatLngs[]
	// Clone the latLng point or points or nested points and return an array with those points
	cloneLatLngs: function (latlngs) {
		var clone = [];
		for (var i = 0, l = latlngs.length; i < l; i++) {
			// Check for nested array (Polyline/Polygon)
			if (Array.isArray(latlngs[i])) {
				clone.push(L.LatLngUtil.cloneLatLngs(latlngs[i]));
			} else {
				clone.push(this.cloneLatLng(latlngs[i]));
			}
		}
		return clone;
	},

	// @method cloneLatLng(LatLng): L.LatLng
	// Clone the latLng and return a new LatLng object.
	cloneLatLng: function (latlng) {
		return L.latLng(latlng.lat, latlng.lng);
	}
};



/**
 * @class L.GeometryUtil
 * @aka GeometryUtil
 */
L.GeometryUtil = L.extend(L.GeometryUtil || {}, {
	// Ported from the OpenLayers implementation. See https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270

	// @method geodesicArea(): number
	geodesicArea: function (latLngs) {
		var pointsCount = latLngs.length,
			area = 0.0,
			d2r = Math.PI / 180,
			p1, p2;

		if (pointsCount > 2) {
			for (var i = 0; i < pointsCount; i++) {
				p1 = latLngs[i];
				p2 = latLngs[(i + 1) % pointsCount];
				area += ((p2.lng - p1.lng) * d2r) *
						(2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r));
			}
			area = area * 6378137.0 * 6378137.0 / 2.0;
		}

		return Math.abs(area);
	},

	// @method readableArea(area, isMetric): string
	// Returns a readable area string in yards or metric
	readableArea: function (area, isMetric) {
		var areaStr;

		if (isMetric) {
			if (area >= 10000) {
				areaStr = (area * 0.0001).toFixed(2) + ' ha';
			} else {
				areaStr = area.toFixed(2) + ' m&sup2;';
			}
		} else {
			area /= 0.836127; // Square yards in 1 meter

			if (area >= 3097600) { //3097600 square yards in 1 square mile
				areaStr = (area / 3097600).toFixed(2) + ' mi&sup2;';
			} else if (area >= 4840) {//48040 square yards in 1 acre
				areaStr = (area / 4840).toFixed(2) + ' acres';
			} else {
				areaStr = Math.ceil(area) + ' yd&sup2;';
			}
		}

		return areaStr;
	},

	// @method readableDistance(distance, units): string
	// Converts a metric distance to one of [ feet, nauticalMile, metric or yards ] string
	//
	// @alternative
	// @method readableDistance(distance, isMetric, useFeet, isNauticalMile): string
	// Converts metric distance to distance string.
	readableDistance: function (distance, isMetric, isFeet, isNauticalMile) {
		var distanceStr,
			units;

		if (typeof isMetric == "string") {
			units = isMetric;
		} else {
			if (isFeet) {
				units = 'feet';
			} else if (isNauticalMile) {
				units = 'nauticalMile';
			} else if (isMetric) {
				units = 'metric';
			} else {
				units = 'yards';
			}
		}

		switch (units) {
		case 'metric':
			// show metres when distance is < 1km, then show km
			if (distance > 1000) {
				distanceStr = (distance / 1000).toFixed(2) + ' km';
			} else {
				distanceStr = Math.ceil(distance) + ' m';
			}
			break;
		case 'feet':
			distance *= 1.09361 * 3;
			distanceStr = Math.ceil(distance) + ' ft';

			break;
		case 'nauticalMile':
			distance *= 0.53996;
			distanceStr = (distance / 1000).toFixed(2) + ' nm';
			break;
		case 'yards':
		default:
			distance *= 1.09361;

			if (distance > 1760) {
				distanceStr = (distance / 1760).toFixed(2) + ' miles';
			} else {
				distanceStr = Math.ceil(distance) + ' yd';
			}
			break;
		}
		return distanceStr;
	}
});



/**
 * @class L.LineUtil
 * @aka Util
 * @aka L.Utils
 */
L.Util.extend(L.LineUtil, {

	// @method segmentsIntersect(): boolean
	// Checks to see if two line segments intersect. Does not handle degenerate cases.
	// http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf
	segmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) {
		return this._checkCounterclockwise(p, p2, p3) !==
			   this._checkCounterclockwise(p1, p2, p3) &&
			   this._checkCounterclockwise(p, p1, p2) !==
			   this._checkCounterclockwise(p, p1, p3);
	},

	// check to see if points are in counterclockwise order
	_checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
		return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x);
	}
});


/**
 * @class L.Polyline
 * @aka Polyline
 */
L.Polyline.include({

	// @method intersects(): boolean
	// Check to see if this polyline has any linesegments that intersect.
	// NOTE: does not support detecting intersection for degenerate cases.
	intersects: function () {
		var points = this._getProjectedPoints(),
			len = points ? points.length : 0,
			i, p, p1;

		if (this._tooFewPointsForIntersection()) {
			return false;
		}

		for (i = len - 1; i >= 3; i--) {
			p = points[i - 1];
			p1 = points[i];


			if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) {
				return true;
			}
		}

		return false;
	},

	// @method newLatLngIntersects(): boolean
	// Check for intersection if new latlng was added to this polyline.
	// NOTE: does not support detecting intersection for degenerate cases.
	newLatLngIntersects: function (latlng, skipFirst) {
		// Cannot check a polyline for intersecting lats/lngs when not added to the map
		if (!this._map) {
			return false;
		}

		return this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst);
	},

	// @method newPointIntersects(): boolean
	// Check for intersection if new point was added to this polyline.
	// newPoint must be a layer point.
	// NOTE: does not support detecting intersection for degenerate cases.
	newPointIntersects: function (newPoint, skipFirst) {
		var points = this._getProjectedPoints(),
			len = points ? points.length : 0,
			lastPoint = points ? points[len - 1] : null,
			// The previous previous line segment. Previous line segment doesn't need testing.
			maxIndex = len - 2;

		if (this._tooFewPointsForIntersection(1)) {
			return false;
		}

		return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0);
	},

	// Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these).
	// Cannot have intersection when < 3 line segments (< 4 points)
	_tooFewPointsForIntersection: function (extraPoints) {
		var points = this._getProjectedPoints(),
			len = points ? points.length : 0;
		// Increment length by extraPoints if present
		len += extraPoints || 0;

		return !points || len <= 3;
	},

	// Checks a line segment intersections with any line segments before its predecessor.
	// Don't need to check the predecessor as will never intersect.
	_lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) {
		var points = this._getProjectedPoints(),
			p2, p3;

		minIndex = minIndex || 0;

		// Check all previous line segments (beside the immediately previous) for intersections
		for (var j = maxIndex; j > minIndex; j--) {
			p2 = points[j - 1];
			p3 = points[j];

			if (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) {
				return true;
			}
		}

		return false;
	},

	_getProjectedPoints: function () {
		if (!this._defaultShape) {
			return this._originalPoints;
		}
		var points = [],
			_shape = this._defaultShape();

		for (var i = 0; i < _shape.length; i++) {
			points.push(this._map.latLngToLayerPoint(_shape[i]));
		}
		return points;
	}
});



/**
 * @class L.Polygon
 * @aka Polygon
 */
L.Polygon.include({

	// @method intersects(): boolean
	// Checks a polygon for any intersecting line segments. Ignores holes.
	intersects: function () {
		var polylineIntersects,
			points = this._getProjectedPoints(),
			len, firstPoint, lastPoint, maxIndex;

		if (this._tooFewPointsForIntersection()) {
			return false;
		}

		polylineIntersects = L.Polyline.prototype.intersects.call(this);

		// If already found an intersection don't need to check for any more.
		if (polylineIntersects) {
			return true;
		}

		len = points.length;
		firstPoint = points[0];
		lastPoint = points[len - 1];
		maxIndex = len - 2;

		// Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1)
		return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1);
	}
});



/**
 * @class L.Control.Draw
 * @aka L.Draw
 */
L.Control.Draw = L.Control.extend({

	// Options
	options: {
		position: 'topleft',
		draw: {},
		edit: false
	},

	// @method initialize(): void
	// Initializes draw control, toolbars from the options
	initialize: function (options) {
		if (L.version < '0.7') {
			throw new Error('Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/');
		}

		L.Control.prototype.initialize.call(this, options);

		var toolbar;

		this._toolbars = {};

		// Initialize toolbars
		if (L.DrawToolbar && this.options.draw) {
			toolbar = new L.DrawToolbar(this.options.draw);

			this._toolbars[L.DrawToolbar.TYPE] = toolbar;

			// Listen for when toolbar is enabled
			this._toolbars[L.DrawToolbar.TYPE].on('enable', this._toolbarEnabled, this);
		}

		if (L.EditToolbar && this.options.edit) {
			toolbar = new L.EditToolbar(this.options.edit);

			this._toolbars[L.EditToolbar.TYPE] = toolbar;

			// Listen for when toolbar is enabled
			this._toolbars[L.EditToolbar.TYPE].on('enable', this._toolbarEnabled, this);
		}
		L.toolbar = this; //set global var for editing the toolbar
	},

	// @method onAdd(): container
	// Adds the toolbar container to the map
	onAdd: function (map) {
		var container = L.DomUtil.create('div', 'leaflet-draw'),
			addedTopClass = false,
			topClassName = 'leaflet-draw-toolbar-top',
			toolbarContainer;

		for (var toolbarId in this._toolbars) {
			if (this._toolbars.hasOwnProperty(toolbarId)) {
				toolbarContainer = this._toolbars[toolbarId].addToolbar(map);

				if (toolbarContainer) {
					// Add class to the first toolbar to remove the margin
					if (!addedTopClass) {
						if (!L.DomUtil.hasClass(toolbarContainer, topClassName)) {
							L.DomUtil.addClass(toolbarContainer.childNodes[0], topClassName);
						}
						addedTopClass = true;
					}

					container.appendChild(toolbarContainer);
				}
			}
		}

		return container;
	},

	// @method onRemove(): void
	// Removes the toolbars from the map toolbar container
	onRemove: function () {
		for (var toolbarId in this._toolbars) {
			if (this._toolbars.hasOwnProperty(toolbarId)) {
				this._toolbars[toolbarId].removeToolbar();
			}
		}
	},

	// @method setDrawingOptions(options): void
	// Sets options to all toolbar instances
	setDrawingOptions: function (options) {
		for (var toolbarId in this._toolbars) {
			if (this._toolbars[toolbarId] instanceof L.DrawToolbar) {
				this._toolbars[toolbarId].setOptions(options);
			}
		}
	},

	_toolbarEnabled: function (e) {
		var enabledToolbar = e.target;

		for (var toolbarId in this._toolbars) {
			if (this._toolbars[toolbarId] !== enabledToolbar) {
				this._toolbars[toolbarId].disable();
			}
		}
	}
});

L.Map.mergeOptions({
	drawControlTooltips: true,
	drawControl: false
});

L.Map.addInitHook(function () {
	if (this.options.drawControl) {
		this.drawControl = new L.Control.Draw();
		this.addControl(this.drawControl);
	}
});



/**
 * @class L.Draw.Toolbar
 * @aka Toolbar
 *
 * The toolbar class of the API — it is used to create the ui
 * This will be depreciated
 *
 * @example
 *
 * ```js
 *    var toolbar = L.Toolbar();
 *    toolbar.addToolbar(map);
 * ```
 *
 * ### Disabling a toolbar
 *
 * If you do not want a particular toolbar in your app you can turn it off by setting the toolbar to false.
 *
 * ```js
 *      var drawControl = new L.Control.Draw({
 *          draw: false,
 *          edit: {
 *              featureGroup: editableLayers
 *          }
 *      });
 * ```
 *
 * ### Disabling a toolbar item
 *
 * If you want to turn off a particular toolbar item, set it to false. The following disables drawing polygons and
 * markers. It also turns off the ability to edit layers.
 *
 * ```js
 *      var drawControl = new L.Control.Draw({
 *          draw: {
 *              polygon: false,
 *              marker: false
 *          },
 *          edit: {
 *              featureGroup: editableLayers,
 *              edit: false
 *          }
 *      });
 * ```
 */
L.Toolbar = L.Class.extend({
	includes: [L.Mixin.Events],

	// @section Methods for modifying the toolbar

	// @method initialize(options): void
	// Toolbar constructor
	initialize: function (options) {
		L.setOptions(this, options);

		this._modes = {};
		this._actionButtons = [];
		this._activeMode = null;
	},

	// @method enabled(): boolean
	// Gets a true/false of whether the toolbar is enabled
	enabled: function () {
		return this._activeMode !== null;
	},

	// @method disable(): void
	// Disables the toolbar
	disable: function () {
		if (!this.enabled()) {
			return;
		}

		this._activeMode.handler.disable();
	},

	// @method addToolbar(map): L.DomUtil
	// Adds the toolbar to the map and returns the toolbar dom element
	addToolbar: function (map) {
		var container = L.DomUtil.create('div', 'leaflet-draw-section'),
			buttonIndex = 0,
			buttonClassPrefix = this._toolbarClass || '',
			modeHandlers = this.getModeHandlers(map),
			i;

		this._toolbarContainer = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar');
		this._map = map;

		for (i = 0; i < modeHandlers.length; i++) {
			if (modeHandlers[i].enabled) {
				this._initModeHandler(
					modeHandlers[i].handler,
					this._toolbarContainer,
					buttonIndex++,
					buttonClassPrefix,
					modeHandlers[i].title
				);
			}
		}

		// if no buttons were added, do not add the toolbar
		if (!buttonIndex) {
			return;
		}

		// Save button index of the last button, -1 as we would have ++ after the last button
		this._lastButtonIndex = --buttonIndex;

		// Create empty actions part of the toolbar
		this._actionsContainer = L.DomUtil.create('ul', 'leaflet-draw-actions');

		// Add draw and cancel containers to the control container
		container.appendChild(this._toolbarContainer);
		container.appendChild(this._actionsContainer);

		return container;
	},

	// @method removeToolbar(): void
	// Removes the toolbar and drops the handler event listeners
	removeToolbar: function () {
		// Dispose each handler
		for (var handlerId in this._modes) {
			if (this._modes.hasOwnProperty(handlerId)) {
				// Unbind handler button
				this._disposeButton(
					this._modes[handlerId].button,
					this._modes[handlerId].handler.enable,
					this._modes[handlerId].handler
				);

				// Make sure is disabled
				this._modes[handlerId].handler.disable();

				// Unbind handler
				this._modes[handlerId].handler
					.off('enabled', this._handlerActivated, this)
					.off('disabled', this._handlerDeactivated, this);
			}
		}
		this._modes = {};

		// Dispose the actions toolbar
		for (var i = 0, l = this._actionButtons.length; i < l; i++) {
			this._disposeButton(
				this._actionButtons[i].button,
				this._actionButtons[i].callback,
				this
			);
		}
		this._actionButtons = [];
		this._actionsContainer = null;
	},

	_initModeHandler: function (handler, container, buttonIndex, classNamePredix, buttonTitle) {
		var type = handler.type;

		this._modes[type] = {};

		this._modes[type].handler = handler;

		this._modes[type].button = this._createButton({
			type: type,
			title: buttonTitle,
			className: classNamePredix + '-' + type,
			container: container,
			callback: this._modes[type].handler.enable,
			context: this._modes[type].handler
		});

		this._modes[type].buttonIndex = buttonIndex;

		this._modes[type].handler
			.on('enabled', this._handlerActivated, this)
			.on('disabled', this._handlerDeactivated, this);
	},

	_createButton: function (options) {

		var link = L.DomUtil.create('a', options.className || '', options.container);
		// Screen reader tag
		var sr = L.DomUtil.create('span', 'sr-only', options.container);

		link.href = '#';
		link.appendChild(sr);

		if (options.title) {
			link.title = options.title;
			sr.innerHTML = options.title;
		}

		if (options.text) {
			link.innerHTML = options.text;
			sr.innerHTML = options.text;
		}

		L.DomEvent
			.on(link, 'click', L.DomEvent.stopPropagation)
			.on(link, 'mousedown', L.DomEvent.stopPropagation)
			.on(link, 'dblclick', L.DomEvent.stopPropagation)
			.on(link, 'touchstart', L.DomEvent.stopPropagation)
			.on(link, 'click', L.DomEvent.preventDefault)
			.on(link, 'click', options.callback, options.context);

		return link;
	},

	_disposeButton: function (button, callback) {
		L.DomEvent
			.off(button, 'click', L.DomEvent.stopPropagation)
			.off(button, 'mousedown', L.DomEvent.stopPropagation)
			.off(button, 'dblclick', L.DomEvent.stopPropagation)
			.off(button, 'touchstart', L.DomEvent.stopPropagation)
			.off(button, 'click', L.DomEvent.preventDefault)
			.off(button, 'click', callback);
	},

	_handlerActivated: function (e) {
		// Disable active mode (if present)
		this.disable();

		// Cache new active feature
		this._activeMode = this._modes[e.handler];

		L.DomUtil.addClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');

		this._showActionsToolbar();

		this.fire('enable');
	},

	_handlerDeactivated: function () {
		this._hideActionsToolbar();

		L.DomUtil.removeClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');

		this._activeMode = null;

		this.fire('disable');
	},

	_createActions: function (handler) {
		var container = this._actionsContainer,
			buttons = this.getActions(handler),
			l = buttons.length,
			li, di, dl, button;

		// Dispose the actions toolbar (todo: dispose only not used buttons)
		for (di = 0, dl = this._actionButtons.length; di < dl; di++) {
			this._disposeButton(this._actionButtons[di].button, this._actionButtons[di].callback);
		}
		this._actionButtons = [];

		// Remove all old buttons
		while (container.firstChild) {
			container.removeChild(container.firstChild);
		}

		for (var i = 0; i < l; i++) {
			if ('enabled' in buttons[i] && !buttons[i].enabled) {
				continue;
			}

			li = L.DomUtil.create('li', '', container);

			button = this._createButton({
				title: buttons[i].title,
				text: buttons[i].text,
				container: li,
				callback: buttons[i].callback,
				context: buttons[i].context
			});

			this._actionButtons.push({
				button: button,
				callback: buttons[i].callback
			});
		}
	},

	_showActionsToolbar: function () {
		var buttonIndex = this._activeMode.buttonIndex,
			lastButtonIndex = this._lastButtonIndex,
			toolbarPosition = this._activeMode.button.offsetTop - 1;

		// Recreate action buttons on every click
		this._createActions(this._activeMode.handler);

		// Correctly position the cancel button
		this._actionsContainer.style.top = toolbarPosition + 'px';

		if (buttonIndex === 0) {
			L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');
			L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-top');
		}

		if (buttonIndex === lastButtonIndex) {
			L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');
			L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-bottom');
		}

		this._actionsContainer.style.display = 'block';
	},

	_hideActionsToolbar: function () {
		this._actionsContainer.style.display = 'none';

		L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');
		L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');
		L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-top');
		L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-bottom');
	}
});



L.Draw = L.Draw || {};
/**
 * @class L.Draw.Tooltip
 * @aka Tooltip
 *
 * The tooltip class — it is used to display the tooltip while drawing
 * This will be depreciated
 *
 * @example
 *
 * ```js
 *    var tooltip = L.Draw.Tooltip();
 * ```
 *
 */
L.Draw.Tooltip = L.Class.extend({

	// @section Methods for modifying draw state

	// @method initialize(map): void
	// Tooltip constructor
	initialize: function (map) {
		this._map = map;
		this._popupPane = map._panes.popupPane;

		this._container = map.options.drawControlTooltips ?
			L.DomUtil.create('div', 'leaflet-draw-tooltip', this._popupPane) : null;
		this._singleLineLabel = false;

		this._map.on('mouseout', this._onMouseOut, this);
	},

	// @method dispose(): void
	// Remove Tooltip DOM and unbind events
	dispose: function () {
		this._map.off('mouseout', this._onMouseOut, this);

		if (this._container) {
			this._popupPane.removeChild(this._container);
			this._container = null;
		}
	},

	// @method updateContent(labelText): this
	// Changes the tooltip text to string in function call
	updateContent: function (labelText) {
		if (!this._container) {
			return this;
		}
		labelText.subtext = labelText.subtext || '';

		// update the vertical position (only if changed)
		if (labelText.subtext.length === 0 && !this._singleLineLabel) {
			L.DomUtil.addClass(this._container, 'leaflet-draw-tooltip-single');
			this._singleLineLabel = true;
		}
		else if (labelText.subtext.length > 0 && this._singleLineLabel) {
			L.DomUtil.removeClass(this._container, 'leaflet-draw-tooltip-single');
			this._singleLineLabel = false;
		}

		this._container.innerHTML =
			(labelText.subtext.length > 0 ?
			'<span class="leaflet-draw-tooltip-subtext">' + labelText.subtext + '</span>' + '<br />' : '') +
			'<span>' + labelText.text + '</span>';

		return this;
	},

	// @method updatePosition(latlng): this
	// Changes the location of the tooltip
	updatePosition: function (latlng) {
		var pos = this._map.latLngToLayerPoint(latlng),
			tooltipContainer = this._container;

		if (this._container) {
			tooltipContainer.style.visibility = 'inherit';
			L.DomUtil.setPosition(tooltipContainer, pos);
		}

		return this;
	},

	// @method showAsError(): this
	// Applies error class to tooltip
	showAsError: function () {
		if (this._container) {
			L.DomUtil.addClass(this._container, 'leaflet-error-draw-tooltip');
		}
		return this;
	},

	// @method removeError(): this
	// Removes the error class from the tooltip
	removeError: function () {
		if (this._container) {
			L.DomUtil.removeClass(this._container, 'leaflet-error-draw-tooltip');
		}
		return this;
	},

	_onMouseOut: function () {
		if (this._container) {
			this._container.style.visibility = 'hidden';
		}
	}
});



/**
 * @class L.DrawToolbar
 * @aka Toolbar
 */
L.DrawToolbar = L.Toolbar.extend({

	statics: {
		TYPE: 'draw'
	},

	options: {
		polyline: {},
		polygon: {},
		rectangle: {},
		circle: {},
		marker: {}
	},

	// @method initialize(): void
	initialize: function (options) {
		// Ensure that the options are merged correctly since L.extend is only shallow
		for (var type in this.options) {
			if (this.options.hasOwnProperty(type)) {
				if (options[type]) {
					options[type] = L.extend({}, this.options[type], options[type]);
				}
			}
		}

		this._toolbarClass = 'leaflet-draw-draw';
		L.Toolbar.prototype.initialize.call(this, options);
	},

	// @method getModeHandlers(): object
	// Get mode handlers information
	getModeHandlers: function (map) {
		return [
			{
				enabled: this.options.polyline,
				handler: new L.Draw.Polyline(map, this.options.polyline),
				title: L.drawLocal.draw.toolbar.buttons.polyline
			},
			{
				enabled: this.options.polygon,
				handler: new L.Draw.Polygon(map, this.options.polygon),
				title: L.drawLocal.draw.toolbar.buttons.polygon
			},
			{
				enabled: this.options.rectangle,
				handler: new L.Draw.Rectangle(map, this.options.rectangle),
				title: L.drawLocal.draw.toolbar.buttons.rectangle
			},
			{
				enabled: this.options.circle,
				handler: new L.Draw.Circle(map, this.options.circle),
				title: L.drawLocal.draw.toolbar.buttons.circle
			},
			{
				enabled: this.options.marker,
				handler: new L.Draw.Marker(map, this.options.marker),
				title: L.drawLocal.draw.toolbar.buttons.marker
			}
		];
	},

	// @method getActions(): object
	// Get action information
	getActions: function (handler) {
		return [
			{
				enabled: handler.completeShape,
				title: L.drawLocal.draw.toolbar.finish.title,
				text: L.drawLocal.draw.toolbar.finish.text,
				callback: handler.completeShape,
				context: handler
			},
			{
				enabled: handler.deleteLastVertex,
				title: L.drawLocal.draw.toolbar.undo.title,
				text: L.drawLocal.draw.toolbar.undo.text,
				callback: handler.deleteLastVertex,
				context: handler
			},
			{
				title: L.drawLocal.draw.toolbar.actions.title,
				text: L.drawLocal.draw.toolbar.actions.text,
				callback: this.disable,
				context: this
			}
		];
	},

	// @method setOptions(): void
	// Sets the options to the toolbar
	setOptions: function (options) {
		L.setOptions(this, options);

		for (var type in this._modes) {
			if (this._modes.hasOwnProperty(type) && options.hasOwnProperty(type)) {
				this._modes[type].handler.setOptions(options[type]);
			}
		}
	}
});



/*L.Map.mergeOptions({
 editControl: true
 });*/
/**
 * @class L.EditToolbar
 * @aka EditToolbar
 */
L.EditToolbar = L.Toolbar.extend({
	statics: {
		TYPE: 'edit'
	},

	options: {
		edit: {
			selectedPathOptions: {
				dashArray: '10, 10',

				fill: true,
				fillColor: '#fe57a1',
				fillOpacity: 0.1,

				// Whether to user the existing layers color
				maintainColor: false
			}
		},
		remove: {},
		poly: null,
		featureGroup: null /* REQUIRED! TODO: perhaps if not set then all layers on the map are selectable? */
	},

	// @method intialize(): void
	initialize: function (options) {
		// Need to set this manually since null is an acceptable value here
		if (options.edit) {
			if (typeof options.edit.selectedPathOptions === 'undefined') {
				options.edit.selectedPathOptions = this.options.edit.selectedPathOptions;
			}
			options.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, options.edit.selectedPathOptions);
		}

		if (options.remove) {
			options.remove = L.extend({}, this.options.remove, options.remove);
		}

		if (options.poly) {
			options.poly = L.extend({}, this.options.poly, options.poly);
		}

		this._toolbarClass = 'leaflet-draw-edit';
		L.Toolbar.prototype.initialize.call(this, options);

		this._selectedFeatureCount = 0;
	},

	// @method getModeHandlers(): object
	// Get mode handlers information
	getModeHandlers: function (map) {
		var featureGroup = this.options.featureGroup;
		return [
			{
				enabled: this.options.edit,
				handler: new L.EditToolbar.Edit(map, {
					featureGroup: featureGroup,
					selectedPathOptions: this.options.edit.selectedPathOptions,
					poly: this.options.poly
				}),
				title: L.drawLocal.edit.toolbar.buttons.edit
			},
			{
				enabled: this.options.remove,
				handler: new L.EditToolbar.Delete(map, {
					featureGroup: featureGroup
				}),
				title: L.drawLocal.edit.toolbar.buttons.remove
			}
		];
	},

	// @method getActions(): object
	// Get actions information
	getActions: function () {
		return [
			{
				title: L.drawLocal.edit.toolbar.actions.save.title,
				text: L.drawLocal.edit.toolbar.actions.save.text,
				callback: this._save,
				context: this
			},
			{
				title: L.drawLocal.edit.toolbar.actions.cancel.title,
				text: L.drawLocal.edit.toolbar.actions.cancel.text,
				callback: this.disable,
				context: this
			}
		];
	},

	// @method addToolbar(map): L.DomUtil
	// Adds the toolbar to the map
	addToolbar: function (map) {
		var container = L.Toolbar.prototype.addToolbar.call(this, map);

		this._checkDisabled();

		this.options.featureGroup.on('layeradd layerremove', this._checkDisabled, this);

		return container;
	},

	// @method removeToolbar(): void
	// Removes the toolbar from the map
	removeToolbar: function () {
		this.options.featureGroup.off('layeradd layerremove', this._checkDisabled, this);

		L.Toolbar.prototype.removeToolbar.call(this);
	},

	// @method disable(): void
	// Disables the toolbar
	disable: function () {
		if (!this.enabled()) {
			return;
		}

		this._activeMode.handler.revertLayers();

		L.Toolbar.prototype.disable.call(this);
	},

	_save: function () {
		this._activeMode.handler.save();
		if (this._activeMode) {
			this._activeMode.handler.disable();
		}
	},

	_checkDisabled: function () {
		var featureGroup = this.options.featureGroup,
			hasLayers = featureGroup.getLayers().length !== 0,
			button;

		if (this.options.edit) {
			button = this._modes[L.EditToolbar.Edit.TYPE].button;

			if (hasLayers) {
				L.DomUtil.removeClass(button, 'leaflet-disabled');
			} else {
				L.DomUtil.addClass(button, 'leaflet-disabled');
			}

			button.setAttribute(
				'title',
				hasLayers ?
					L.drawLocal.edit.toolbar.buttons.edit
					: L.drawLocal.edit.toolbar.buttons.editDisabled
			);
		}

		if (this.options.remove) {
			button = this._modes[L.EditToolbar.Delete.TYPE].button;

			if (hasLayers) {
				L.DomUtil.removeClass(button, 'leaflet-disabled');
			} else {
				L.DomUtil.addClass(button, 'leaflet-disabled');
			}

			button.setAttribute(
				'title',
				hasLayers ?
					L.drawLocal.edit.toolbar.buttons.remove
					: L.drawLocal.edit.toolbar.buttons.removeDisabled
			);
		}
	}
});



/**
 * @class L.EditToolbar.Edit
 * @aka EditToolbar.Edit
 */
L.EditToolbar.Edit = L.Handler.extend({
	statics: {
		TYPE: 'edit'
	},

	includes: L.Mixin.Events,

	// @method intialize(): void
	initialize: function (map, options) {
		L.Handler.prototype.initialize.call(this, map);

		L.setOptions(this, options);

		// Store the selectable layer group for ease of access
		this._featureGroup = options.featureGroup;

		if (!(this._featureGroup instanceof L.FeatureGroup)) {
			throw new Error('options.featureGroup must be a L.FeatureGroup');
		}

		this._uneditedLayerProps = {};

		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.EditToolbar.Edit.TYPE;
	},

	// @method enable(): void
	// Enable the edit toolbar
	enable: function () {
		if (this._enabled || !this._hasAvailableLayers()) {
			return;
		}
		this.fire('enabled', { handler: this.type });
		//this disable other handlers

		this._map.fire(L.Draw.Event.EDITSTART, { handler: this.type });
		//allow drawLayer to be updated before beginning edition.

		L.Handler.prototype.enable.call(this);
		this._featureGroup
			.on('layeradd', this._enableLayerEdit, this)
			.on('layerremove', this._disableLayerEdit, this);
	},

	// @method disable(): void
	// Disable the edit toolbar
	disable: function () {
		if (!this._enabled) {
			return;
		}
		this._featureGroup
			.off('layeradd', this._enableLayerEdit, this)
			.off('layerremove', this._disableLayerEdit, this);
		L.Handler.prototype.disable.call(this);
		this._map.fire(L.Draw.Event.EDITSTOP, { handler: this.type });
		this.fire('disabled', { handler: this.type });
	},

	// @method addHooks(): void
	// Add listener hooks for this handler
	addHooks: function () {
		var map = this._map;

		if (map) {
			map.getContainer().focus();

			this._featureGroup.eachLayer(this._enableLayerEdit, this);

			this._tooltip = new L.Draw.Tooltip(this._map);
			this._tooltip.updateContent({
				text: L.drawLocal.edit.handlers.edit.tooltip.text,
				subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
			});

			// Quickly access the tooltip to update for intersection checking
			map._editTooltip = this._tooltip;

			this._updateTooltip();

			this._map
				.on('mousemove', this._onMouseMove, this)
				.on('touchmove', this._onMouseMove, this)
				.on('MSPointerMove', this._onMouseMove, this)
				.on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
		}
	},

	// @method removeHooks(): void
	// Remove listener hooks for this handler
	removeHooks: function () {
		if (this._map) {
			// Clean up selected layers.
			this._featureGroup.eachLayer(this._disableLayerEdit, this);

			// Clear the backups of the original layers
			this._uneditedLayerProps = {};

			this._tooltip.dispose();
			this._tooltip = null;

			this._map
				.off('mousemove', this._onMouseMove, this)
				.off('touchmove', this._onMouseMove, this)
				.off('MSPointerMove', this._onMouseMove, this)
				.off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
		}
	},

	// @method revertLayers(): void
	// Revert each layer's geometry changes
	revertLayers: function () {
		this._featureGroup.eachLayer(function (layer) {
			this._revertLayer(layer);
		}, this);
	},

	// @method save(): void
	// Save the layer geometries
	save: function () {
		var editedLayers = new L.LayerGroup();
		this._featureGroup.eachLayer(function (layer) {
			if (layer.edited) {
				editedLayers.addLayer(layer);
				layer.edited = false;
			}
		});
		this._map.fire(L.Draw.Event.EDITED, { layers: editedLayers });
	},

	_backupLayer: function (layer) {
		var id = L.Util.stamp(layer);

		if (!this._uneditedLayerProps[id]) {
			// Polyline, Polygon or Rectangle
			if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
				this._uneditedLayerProps[id] = {
					latlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs())
				};
			} else if (layer instanceof L.Circle) {
				this._uneditedLayerProps[id] = {
					latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()),
					radius: layer.getRadius()
				};
			} else if (layer instanceof L.Marker) { // Marker
				this._uneditedLayerProps[id] = {
					latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng())
				};
			}
		}
	},

	_getTooltipText: function () {
		return ({
			text: L.drawLocal.edit.handlers.edit.tooltip.text,
			subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
		});
	},

	_updateTooltip: function () {
		this._tooltip.updateContent(this._getTooltipText());
	},

	_revertLayer: function (layer) {
		var id = L.Util.stamp(layer);
		layer.edited = false;
		if (this._uneditedLayerProps.hasOwnProperty(id)) {
			// Polyline, Polygon or Rectangle
			if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
				layer.setLatLngs(this._uneditedLayerProps[id].latlngs);
			} else if (layer instanceof L.Circle) {
				layer.setLatLng(this._uneditedLayerProps[id].latlng);
				layer.setRadius(this._uneditedLayerProps[id].radius);
			} else if (layer instanceof L.Marker) { // Marker
				layer.setLatLng(this._uneditedLayerProps[id].latlng);
			}

			layer.fire('revert-edited', { layer: layer });
		}
	},

	_enableLayerEdit: function (e) {
		var layer = e.layer || e.target || e,
			pathOptions, poly;

		// Back up this layer (if haven't before)
		this._backupLayer(layer);

		if (this.options.poly) {
			poly = L.Util.extend({}, this.options.poly);
			layer.options.poly = poly;
		}

		// Set different style for editing mode
		if (this.options.selectedPathOptions) {
			pathOptions = L.Util.extend({}, this.options.selectedPathOptions);

			// Use the existing color of the layer
			if (pathOptions.maintainColor) {
				pathOptions.color = layer.options.color;
				pathOptions.fillColor = layer.options.fillColor;
			}

			layer.options.original = L.extend({}, layer.options);
			layer.options.editing = pathOptions;

		}

		if (layer instanceof L.Marker) {
			if (layer.editing) {
				layer.editing.enable();
			}
			layer.dragging.enable();
			layer
				.on('dragend', this._onMarkerDragEnd)
				// #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again.
				.on('touchmove', this._onTouchMove, this)
				.on('MSPointerMove', this._onTouchMove, this)
				.on('touchend', this._onMarkerDragEnd, this)
				.on('MSPointerUp', this._onMarkerDragEnd, this);
		} else {
			layer.editing.enable();
		}
	},

	_disableLayerEdit: function (e) {
		var layer = e.layer || e.target || e;

		layer.edited = false;
		if (layer.editing) {
			layer.editing.disable();
		}

		delete layer.options.editing;
		delete layer.options.original;
		// Reset layer styles to that of before select
		if (this._selectedPathOptions) {
			if (layer instanceof L.Marker) {
				this._toggleMarkerHighlight(layer);
			} else {
				// reset the layer style to what is was before being selected
				layer.setStyle(layer.options.previousOptions);
				// remove the cached options for the layer object
				delete layer.options.previousOptions;
			}
		}

		if (layer instanceof L.Marker) {
			layer.dragging.disable();
			layer
				.off('dragend', this._onMarkerDragEnd, this)
				.off('touchmove', this._onTouchMove, this)
				.off('MSPointerMove', this._onTouchMove, this)
				.off('touchend', this._onMarkerDragEnd, this)
				.off('MSPointerUp', this._onMarkerDragEnd, this);
		} else {
			layer.editing.disable();
		}
	},

	_onMouseMove: function (e) {
		this._tooltip.updatePosition(e.latlng);
	},

	_onMarkerDragEnd: function (e) {
		var layer = e.target;
		layer.edited = true;
		this._map.fire(L.Draw.Event.EDITMOVE, { layer: layer });
	},

	_onTouchMove: function (e) {
		var touchEvent = e.originalEvent.changedTouches[0],
			layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
			latlng = this._map.layerPointToLatLng(layerPoint);
		e.target.setLatLng(latlng);
	},

	_hasAvailableLayers: function () {
		return this._featureGroup.getLayers().length !== 0;
	}
});



/**
 * @class L.EditToolbar.Delete
 * @aka EditToolbar.Delete
 */
L.EditToolbar.Delete = L.Handler.extend({
	statics: {
		TYPE: 'remove' // not delete as delete is reserved in js
	},

	includes: L.Mixin.Events,

	// @method intialize(): void
	initialize: function (map, options) {
		L.Handler.prototype.initialize.call(this, map);

		L.Util.setOptions(this, options);

		// Store the selectable layer group for ease of access
		this._deletableLayers = this.options.featureGroup;

		if (!(this._deletableLayers instanceof L.FeatureGroup)) {
			throw new Error('options.featureGroup must be a L.FeatureGroup');
		}

		// Save the type so super can fire, need to do this as cannot do this.TYPE :(
		this.type = L.EditToolbar.Delete.TYPE;
	},

	// @method enable(): void
	// Enable the delete toolbar
	enable: function () {
		if (this._enabled || !this._hasAvailableLayers()) {
			return;
		}
		this.fire('enabled', { handler: this.type });

		this._map.fire(L.Draw.Event.DELETESTART, { handler: this.type });

		L.Handler.prototype.enable.call(this);

		this._deletableLayers
			.on('layeradd', this._enableLayerDelete, this)
			.on('layerremove', this._disableLayerDelete, this);
	},

	// @method disable(): void
	// Disable the delete toolbar
	disable: function () {
		if (!this._enabled) {
			return;
		}

		this._deletableLayers
			.off('layeradd', this._enableLayerDelete, this)
			.off('layerremove', this._disableLayerDelete, this);

		L.Handler.prototype.disable.call(this);

		this._map.fire(L.Draw.Event.DELETESTOP, { handler: this.type });

		this.fire('disabled', { handler: this.type });
	},

	// @method addHooks(): void
	// Add listener hooks to this handler
	addHooks: function () {
		var map = this._map;

		if (map) {
			map.getContainer().focus();

			this._deletableLayers.eachLayer(this._enableLayerDelete, this);
			this._deletedLayers = new L.LayerGroup();

			this._tooltip = new L.Draw.Tooltip(this._map);
			this._tooltip.updateContent({ text: L.drawLocal.edit.handlers.remove.tooltip.text });

			this._map.on('mousemove', this._onMouseMove, this);
		}
	},

	// @method removeHooks(): void
	// Remove listener hooks from this handler
	removeHooks: function () {
		if (this._map) {
			this._deletableLayers.eachLayer(this._disableLayerDelete, this);
			this._deletedLayers = null;

			this._tooltip.dispose();
			this._tooltip = null;

			this._map.off('mousemove', this._onMouseMove, this);
		}
	},

	// @method revertLayers(): void
	// Revert the deleted layers back to their prior state.
	revertLayers: function () {
		// Iterate of the deleted layers and add them back into the featureGroup
		this._deletedLayers.eachLayer(function (layer) {
			this._deletableLayers.addLayer(layer);
			layer.fire('revert-deleted', { layer: layer });
		}, this);
	},

	// @method save(): void
	// Save deleted layers
	save: function () {
		this._map.fire(L.Draw.Event.DELETED, { layers: this._deletedLayers });
	},

	_enableLayerDelete: function (e) {
		var layer = e.layer || e.target || e;

		layer.on('click', this._removeLayer, this);
	},

	_disableLayerDelete: function (e) {
		var layer = e.layer || e.target || e;

		layer.off('click', this._removeLayer, this);

		// Remove from the deleted layers so we can't accidentally revert if the user presses cancel
		this._deletedLayers.removeLayer(layer);
	},

	_removeLayer: function (e) {
		var layer = e.layer || e.target || e;

		this._deletableLayers.removeLayer(layer);

		this._deletedLayers.addLayer(layer);

		layer.fire('deleted');
	},

	_onMouseMove: function (e) {
		this._tooltip.updatePosition(e.latlng);
	},

	_hasAvailableLayers: function () {
		return this._deletableLayers.getLayers().length !== 0;
	}
});



}(window, document));
//# sourceMappingURL=leaflet.draw-src.map
///// Creating the tree component
// p_div: ID of the div where the tree will be rendered;
// p_backColor: Background color of the region where the tree is being rendered;
// p_contextMenu: Object containing all the context menus. Set null for no context menu;
function createTree(p_div,p_backColor,p_contextMenu) {
    var tree = {
        name: 'tree',
        div: p_div,
        ulElement: null,
        childNodes: [],
        backcolor: p_backColor,
        contextMenu: p_contextMenu,
        selectedNode: null,
        nodeCounter: 0,
        contextMenuDiv: null,
        rendered: false,
        ///// Creating a new node
        // p_text: Text displayed on the node;
        // p_expanded: True or false, indicating wether the node starts expanded or not;
        // p_icon: Relative path to the icon displayed with the node. Set null if the node has no icon;
        // p_parentNode: Reference to the parent node. Set null to create the node on the root;
        // p_tag: Tag is used to store additional information on the node. All node attributes are visible when programming events and context menu actions;
        // p_contextmenu: Name of the context menu, which is one of the attributes of the p_contextMenu object created with the tree;
        createNode: function(p_text, p_expanded, p_icon, p_parentNode, p_tag, p_contextmenu) {
            v_tree = this;
            node = {
                id: 'node_' + OSH.Utils.randomUUID(),
                text: p_text,
                icon: p_icon,
                parent: p_parentNode,
                expanded : p_expanded,
                childNodes : [],
                tag : p_tag,
                contextMenu: p_contextmenu,
                elementLi: null,
                ///// Removing the node and all its children
                removeNode: function() { v_tree.removeNode(this); },
                ///// Expanding or collapsing the node, depending on the expanded value
                toggleNode: function(p_event) { v_tree.toggleNode(this); },
                ///// Expanding the node
                expandNode: function(p_event) { v_tree.expandNode(this); },
                ///// Expanding the node and its children recursively
                expandSubtree: function() { v_tree.expandSubtree(this); },
                ///// Changing the node text
                // p_text: New text;
                setText: function(p_text) { v_tree.setText(this,p_text); },
                ///// Collapsing the node
                collapseNode: function() { v_tree.collapseNode(this); },
                ///// Collapsing the node and its children recursively
                collapseSubtree: function() { v_tree.collapseSubtree(this); },
                ///// Deleting all child nodes
                removeChildNodes: function() { v_tree.removeChildNodes(this); },
                ///// Creating a new child node;
                // p_text: Text displayed;
                // p_expanded: True or false, indicating wether the node starts expanded or not;
                // p_icon: Icon;
                // p_tag: Tag;
                // p_contextmenu: Context Menu;
                createChildNode: function(p_text,p_expanded,p_icon,p_tag,p_contextmenu) { return v_tree.createNode(p_text,p_expanded,p_icon,this,p_tag,p_contextmenu); }
            }

            this.nodeCounter++;

            if (this.rendered) {
                if (p_parentNode==undefined) {
                    this.drawNode(this.ulElement,node);
                    this.adjustLines(this.ulElement,false);
                }
                else {
                    var v_ul = p_parentNode.elementLi.getElementsByTagName("ul")[0];
                    if (p_parentNode.childNodes.length==0) {
                        if (p_parentNode.expanded) {
                            p_parentNode.elementLi.getElementsByTagName("ul")[0].style.display = 'block';
                            v_img = p_parentNode.elementLi.getElementsByTagName("img")[0];
                            v_img.style.visibility = "visible";
                            v_img.src = './images/tree/collapse.png';
                            v_img.id = 'toggle_off';
                        }
                        else {
                            p_parentNode.elementLi.getElementsByTagName("ul")[0].style.display = 'none';
                            v_img = p_parentNode.elementLi.getElementsByTagName("img")[0];
                            v_img.style.visibility = "visible";
                            v_img.src = './images/tree/expand.png';
                            v_img.id = 'toggle_on';
                        }
                    }
                    this.drawNode(v_ul,node);
                    this.adjustLines(v_ul,false);
                }
            }

            if (p_parentNode==undefined) {
                this.childNodes.push(node);
                node.parent=this;
            }
            else
                p_parentNode.childNodes.push(node);

            return node;
        },
        ///// Render the tree;
        drawTree: function() {

            this.rendered = true;

            var div_tree = document.getElementById(this.div);
            div_tree.innerHTML = '';

            ulElement = createSimpleElement('ul',this.name,'tree');
            this.ulElement = ulElement;

            for (var i=0; i<this.childNodes.length; i++) {
                this.drawNode(ulElement,this.childNodes[i]);
            }

            div_tree.appendChild(ulElement);

            this.adjustLines(document.getElementById(this.name),true);

        },
        ///// Drawing the node. This function is used when drawing the Tree and should not be called directly;
        // p_ulElement: Reference to the UL tag element where the node should be created;
        // p_node: Reference to the node object;
        drawNode: function(p_ulElement,p_node) {

            v_tree = this;

            var v_icon = null;

            if (p_node.icon!=null) {
                if(p_node.icon != undefined && p_node.icon != "") {
                    v_icon = createImgElement(null, 'icon_tree', p_node.icon);
                } else {
                    v_icon = createImgElement(null, 'icon_tree', "./images/tree/folder.gif");
                }
            }

            var v_li = document.createElement('li');
            p_node.elementLi = v_li;

            var v_span = createSimpleElement('span',null,'node');

            var v_exp_col = null;

            if (p_node.childNodes.length == 0) {
                v_exp_col = createImgElement('toggle_off','exp_col','./images/tree/collapse.png');
                v_exp_col.style.visibility = "hidden";
            }
            else {
                if (p_node.expanded) {
                    v_exp_col = createImgElement('toggle_off','exp_col','./images/tree/collapse.png');
                }
                else {
                    v_exp_col = createImgElement('toggle_on','exp_col','./images/tree/expand.png');
                }
            }

            v_span.ondblclick = function() {
                v_tree.doubleClickNode(p_node);
            };

            v_exp_col.onclick = function() {
                v_tree.toggleNode(p_node);
            };

            v_span.onclick = function() {
                v_tree.selectNode(p_node, true);
            };

            v_span.oncontextmenu = function(e) {
                v_tree.selectNode(p_node, false);
                v_tree.nodeContextMenu(e,p_node);
            };

            if (v_icon!=undefined)
                v_span.appendChild(v_icon);

            v_a = createSimpleElement('a',p_node.id,null);
            v_a.innerHTML=p_node.text;
            v_span.appendChild(v_a);
            v_li.appendChild(v_exp_col);
            v_li.appendChild(v_span);

            p_ulElement.appendChild(v_li);

            var v_ul = createSimpleElement('ul','ul_' + p_node.id,null);
            v_li.appendChild(v_ul);

            if (p_node.childNodes.length > 0) {

                if (!p_node.expanded)
                    v_ul.style.display = 'none';

                for (var i=0; i<p_node.childNodes.length; i++) {
                    this.drawNode(v_ul,p_node.childNodes[i]);
                }
            }
        },
        ///// Changing node text
        // p_node: Reference to the node that will have its text updated;
        // p_text: New text;
        setText: function(p_node,p_text) {
            p_node.elementLi.getElementsByTagName('span')[0].lastChild.innerHTML = p_text;
            p_node.text = p_text;
        },
        ///// Expanding all tree nodes
        expandTree: function() {
            for (var i=0; i<this.childNodes.length; i++) {
                if (this.childNodes[i].childNodes.length>0) {
                    this.expandSubtree(this.childNodes[i]);
                }
            }
        },
        ///// Expanding all nodes inside the subtree that have parameter 'p_node' as root
        // p_node: Subtree root;
        expandSubtree: function(p_node) {
            this.expandNode(p_node);
            for (var i=0; i<p_node.childNodes.length; i++) {
                if (p_node.childNodes[i].childNodes.length>0) {
                    this.expandSubtree(p_node.childNodes[i]);
                }
            }
        },
        ///// Collapsing all tree nodes
        collapseTree: function() {
            for (var i=0; i<this.childNodes.length; i++) {
                if (this.childNodes[i].childNodes.length>0) {
                    this.collapseSubtree(this.childNodes[i]);
                }
            }
        },
        ///// Collapsing all nodes inside the subtree that have parameter 'p_node' as root
        // p_node: Subtree root;
        collapseSubtree: function(p_node) {
            this.collapseNode(p_node);
            for (var i=0; i<p_node.childNodes.length; i++) {
                if (p_node.childNodes[i].childNodes.length>0) {
                    this.collapseSubtree(p_node.childNodes[i]);
                }
            }
        },
        ///// Expanding node
        // p_node: Reference to the node;
        expandNode: function(p_node) {
            if (p_node.childNodes.length>0 && p_node.expanded==false) {
                if (this.nodeBeforeOpenEvent!=undefined)
                    this.nodeBeforeOpenEvent(p_node);

                var img=p_node.elementLi.getElementsByTagName("img")[0];

                p_node.expanded = true;

                img.id="toggle_off";
                img.src = './images/tree/collapse.png';
                elem_ul = img.parentElement.getElementsByTagName("ul")[0];
                elem_ul.style.display = 'block';

                if (this.nodeAfterOpenEvent!=undefined)
                    this.nodeAfterOpenEvent(p_node);

                // check for default folden icon
                var divNode = document.getElementById(p_node.id);
                divNode.previousElementSibling.src = "./images/tree/folderopen.gif";
            }
        },
        ///// Collapsing node
        // p_node: Reference to the node;
        collapseNode: function(p_node) {
            if (p_node.childNodes.length>0 && p_node.expanded==true) {
                var img=p_node.elementLi.getElementsByTagName("img")[0];

                p_node.expanded = false;
                if (this.nodeBeforeCloseEvent!=undefined)
                    this.nodeBeforeCloseEvent(p_node);

                img.id="toggle_on";
                img.src = './images/tree/expand.png';
                elem_ul = img.parentElement.getElementsByTagName("ul")[0];
                elem_ul.style.display = 'none';

                var divNode = document.getElementById(p_node.id);
                divNode.previousElementSibling.src = "./images/tree/folder.gif";

            }
        },
        ///// Toggling node
        // p_node: Reference to the node;
        toggleNode: function(p_node) {
            if (p_node.childNodes.length>0) {
                if (p_node.expanded)
                    p_node.collapseNode();
                else
                    p_node.expandNode();
            }
        },
        ///// Double clicking node
        // p_node: Reference to the node;
        doubleClickNode: function(p_node) {
            this.toggleNode(p_node);
        },
        ///// Selecting node
        // p_node: Reference to the node;
        selectNode: function(p_node, send_event) {
            var span = p_node.elementLi.getElementsByTagName("span")[0];
            span.className = 'node_selected';
            if (this.selectedNode!=null && this.selectedNode!=p_node)
                this.selectedNode.elementLi.getElementsByTagName("span")[0].className = 'node';
            this.selectedNode = p_node;

            // if node is an entity, send selected event
            if (send_event && p_node.tag !== null && typeof(p_node.tag.id) != "undefined") {
                OSH.EventManager.fire(OSH.EventManager.EVENT.SELECT_VIEW,{
                    dataSourcesIds: ["none"],
                    entityId: p_node.tag.id,
                });
            }
        },
        ///// Deleting node
        // p_node: Reference to the node;
        removeNode: function(p_node) {
            var index = p_node.parent.childNodes.indexOf(p_node);

            if (p_node.elementLi.className=="last" && index!=0) {
                p_node.parent.childNodes[index-1].elementLi.className += "last";
                p_node.parent.childNodes[index-1].elementLi.style.backgroundColor = this.backcolor;
            }

            p_node.elementLi.parentNode.removeChild(p_node.elementLi);
            p_node.parent.childNodes.splice(index, 1);

            if (p_node.parent.childNodes.length==0) {
                var v_img = p_node.parent.elementLi.getElementsByTagName("img")[0];
                v_img.style.visibility = "hidden";
            }

        },
        ///// Deleting all node children
        // p_node: Reference to the node;
        removeChildNodes: function(p_node) {

            if (p_node.childNodes.length>0) {
                var v_ul = p_node.elementLi.getElementsByTagName("ul")[0];

                var v_img = p_node.elementLi.getElementsByTagName("img")[0];
                v_img.style.visibility = "hidden";

                p_node.childNodes = [];
                v_ul.innerHTML = "";
            }
        },
        ///// Rendering context menu when mouse right button is pressed over a node. This function should no be called directly
        // p_event: Event triggered when right clicking;
        // p_node: Reference to the node;
        nodeContextMenu: function(p_event,p_node) {
            if (p_event.button==2) {
                p_event.preventDefault();
                p_event.stopPropagation();
                if (p_node.contextMenu!=undefined) {

                    v_tree = this;

                    var v_left = p_event.pageX;
                    var v_right = p_event.pageY;

                    var divTree = document.getElementById(v_tree.div);

                    v_tree.currentContextMenu = p_node.contextMenu;
                    OSH.EventManager.fire(OSH.EventManager.EVENT.CONTEXT_MENU+"-"+p_node.contextMenu,{
                        //TODO: values have to be provided by properties
                        offsetX: 10,
                        offsetY: 10,
                        action : "show",
                        x:v_left,
                        y:v_right
                    });
                }
            }
        },
        ///// Recursive function called when rendering context menu submenus. This function should no be called directly
        // p_submenu: Reference to the submenu object;
        // p_ul: Reference to the UL tag;
        // p_node: Reference to the node;
        contextMenuLi : function(p_submenu,p_ul,p_node) {

            v_tree = this;

            for (var i=0; i<p_submenu.elements.length; i++) (function(i){

                var v_li = createSimpleElement('li',null,null);

                var v_span = createSimpleElement('span',null,null);
                v_span.onclick = function () {  p_submenu.elements[i].action(p_node) };

                var v_a = createSimpleElement('a',p_node.id,null);
                var v_ul = createSimpleElement('ul',null,'sub-menu');

                v_a.appendChild(document.createTextNode(p_submenu.elements[i].text));

                v_li.appendChild(v_span);

                if (p_submenu.elements[i].icon!=undefined) {
                    var v_img = createImgElement('null','null',p_submenu.elements[i].icon);
                    v_li.appendChild(v_img);
                }

                v_li.appendChild(v_a);
                v_li.appendChild(v_ul);
                p_ul.appendChild(v_li);

                if (p_submenu.elements[i].p_submenu!=undefined) {
                    var v_span_more = createSimpleElement('div',null,null);
                    v_span_more.appendChild(createImgElement(null,'menu_img','./images/tree/right.png'));
                    v_li.appendChild(v_span_more);
                    v_tree.contextMenuLi(p_submenu.elements[i].p_submenu,v_ul,p_node);
                }

            })(i);
        },
        ///// Adjusting tree dotted lines. This function should not be called directly
        // p_node: Reference to the node;
        adjustLines: function(p_ul,p_recursive) {
            var tree = p_ul;

            var lists = [];

            if (tree.childNodes.length>0) {
                lists = [ tree ];

                if (p_recursive) {
                    for (var i = 0; i < tree.getElementsByTagName("ul").length; i++) {
                        var check_ul = tree.getElementsByTagName("ul")[i];
                        if (check_ul.childNodes.length!=0)
                            lists[lists.length] = check_ul;
                    }
                }

            }

            for (var i = 0; i < lists.length; i++) {
                var item = lists[i].lastChild;

                while (!item.tagName || item.tagName.toLowerCase() != "li") {
                    item = item.previousSibling;
                }

                item.className += "last";
                item.style.backgroundColor = this.backcolor;

                item = item.previousSibling;

                if (item!=null)
                    if (item.tagName.toLowerCase() == "li") {
                        item.className = "";
                        item.style.backgroundColor = 'transparent';
                    }
            }
        }
    }

    window.onmousedown = function() {
        if(typeof tree.currentContextMenu != "undefined") {
            OSH.EventManager.fire(OSH.EventManager.EVENT.CONTEXT_MENU+"-"+tree.currentContextMenu,{
                action : "hide"
            });
            tree.currentContextMenu = undefined;
        }
    }

    window.onclick = function() {
        if(typeof tree.currentContextMenu != "undefined") {
            OSH.EventManager.fire(OSH.EventManager.EVENT.CONTEXT_MENU+"-"+tree.currentContextMenu,{
                action : "hide"
            });
            tree.currentContextMenu = undefined;
        }
    }
    return tree;
}

// Helper Functions

//Create a HTML element specified by parameter 'p_type'
function createSimpleElement(p_type,p_id,p_class) {
    element = document.createElement(p_type);
    if (p_id!=undefined)
        element.id = p_id;
    if (p_class!=undefined)
        element.className = p_class;
    return element;
}

//Create img element
function createImgElement(p_id,p_class,p_src) {
    element = document.createElement('img');
    if (p_id!=undefined)
        element.id = p_id;
    if (p_class!=undefined)
        element.className = p_class;
    if (p_src !=undefined) {
        element.src = p_src;
    }
    return element;
}
/*
 Copyright 2011-2013 Abdulla Abdurakhmanov
 Original sources are available at https://code.google.com/p/x2js/

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file 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.
 */

(function (root, factory) {
    if (typeof define === "function" && define.amd) {
        define([], factory);
    } else if (typeof exports === "object") {
        module.exports = factory();
    } else {
        root.X2JS = factory();
    }
}(this, function () {
    return function (config) {
        'use strict';

        var VERSION = "1.2.0";

        config = config || {};
        initConfigDefaults();
        initRequiredPolyfills();

        function initConfigDefaults() {
            if(config.escapeMode === undefined) {
                config.escapeMode = true;
            }

            config.attributePrefix = config.attributePrefix || "";
            config.arrayAccessForm = config.arrayAccessForm || "none";
            config.emptyNodeForm = config.emptyNodeForm || "text";

            if(config.enableToStringFunc === undefined) {
                config.enableToStringFunc = true;
            }
            config.arrayAccessFormPaths = config.arrayAccessFormPaths || [];

            config.numericalAccessFormPaths = config.numericalAccessFormPaths || [];

            if(config.skipEmptyTextNodesForObj === undefined) {
                config.skipEmptyTextNodesForObj = true;
            }
            if(config.stripWhitespaces === undefined) {
                config.stripWhitespaces = true;
            }
            config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || [];

            if(config.useDoubleQuotes === undefined) {
                config.useDoubleQuotes = false;
            }

            config.xmlElementsFilter = config.xmlElementsFilter || [];
            config.jsonPropertiesFilter = config.jsonPropertiesFilter || [];

            if(config.keepCData === undefined) {
                config.keepCData = false;
            }

            if(config.prefix === undefined) {
                config.prefix = true;
            }

            if(config.removeAttrPrefix === undefined) {
                config.removeAttrPrefix = false;
            }

            if(config.xmlns === undefined) {
                config.xmlns = true;
            }

            if(config.skip === undefined) {
                config.skip = [];
            }
        }

        var DOMNodeTypes = {
            ELEMENT_NODE 	   : 1,
            TEXT_NODE    	   : 3,
            CDATA_SECTION_NODE : 4,
            COMMENT_NODE	   : 8,
            DOCUMENT_NODE 	   : 9
        };

        function initRequiredPolyfills() {
        }

        function getNodeLocalName( node ) {
            var nodeLocalName = node.localName;
            if(nodeLocalName == null) // Yeah, this is IE!!
                nodeLocalName = node.baseName;
            if(nodeLocalName == null || nodeLocalName=="") // =="" is IE too
                nodeLocalName = node.nodeName;
            return nodeLocalName;
        }

        function getNodePrefix(node) {
            return node.prefix;
        }

        function escapeXmlChars(str) {
            if(typeof(str) == "string")
                return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
            else
                return str;
        }

        function unescapeXmlChars(str) {
            return str.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, '&');
        }

        function checkInStdFiltersArrayForm(stdFiltersArrayForm, obj, name, path) {
            var idx = 0;
            for(; idx < stdFiltersArrayForm.length; idx++) {
                var filterPath = stdFiltersArrayForm[idx];
                if( typeof filterPath === "string" ) {
                    if(filterPath == path)
                        break;
                }
                else
                if( filterPath instanceof RegExp) {
                    if(filterPath.test(path))
                        break;
                }
                else
                if( typeof filterPath === "function") {
                    if(filterPath(obj, name, path))
                        break;
                }
            }
            return idx!=stdFiltersArrayForm.length;
        }

        function toArrayAccessForm(obj, childName, path) {
            switch(config.arrayAccessForm) {
                case "property":
                    if(!(obj[childName] instanceof Array))
                        obj[childName+"_asArray"] = [obj[childName]];
                    else
                        obj[childName+"_asArray"] = obj[childName];
                    break;
				/*case "none":
				 break;*/
            }

            if(!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) {
                if(checkInStdFiltersArrayForm(config.arrayAccessFormPaths, obj, childName, path)) {
                    obj[childName] = [obj[childName]];
                }
            }
        }

        function checkAsNumericalValue(value,path) {
        	var displayAsNum = false;

        	for(var i=0;i < config.numericalAccessFormPaths.length;i++) {
				if((config.numericalAccessFormPaths[i] instanceof RegExp &&
                    config.numericalAccessFormPaths[i].test(path)) || (config.numericalAccessFormPaths[i] == value)) {
						displayAsNum = true;
						break;
				}
            }

            return displayAsNum;
		}

        function fromXmlDateTime(prop) {
            // Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object
            // Improved to support full spec and optional parts
            var bits = prop.split(/[-T:+Z]/g);

            var d = new Date(bits[0], bits[1]-1, bits[2]);
            var secondBits = bits[5].split("\.");
            d.setHours(bits[3], bits[4], secondBits[0]);
            if(secondBits.length>1)
                d.setMilliseconds(secondBits[1]);

            // Get supplied time zone offset in minutes
            if(bits[6] && bits[7]) {
                var offsetMinutes = bits[6] * 60 + Number(bits[7]);
                var sign = /\d\d-\d\d:\d\d$/.test(prop)? '-' : '+';

                // Apply the sign
                offsetMinutes = 0 + (sign == '-'? -1 * offsetMinutes : offsetMinutes);

                // Apply offset and local timezone
                d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset())
            }
            else
            if(prop.indexOf("Z", prop.length - 1) !== -1) {
                d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()));
            }

            // d is now a local time equivalent to the supplied time
            return d;
        }

        function checkFromXmlDateTimePaths(value, childName, fullPath) {
            if(config.datetimeAccessFormPaths.length > 0) {
                var path = fullPath.split("\.#")[0];
                if(checkInStdFiltersArrayForm(config.datetimeAccessFormPaths, value, childName, path)) {
                    return fromXmlDateTime(value);
                }
                else
                    return value;
            }
            else
                return value;
        }

        function checkXmlElementsFilter(obj, childType, childName, childPath) {
            if( childType == DOMNodeTypes.ELEMENT_NODE && config.xmlElementsFilter.length > 0) {
                return checkInStdFiltersArrayForm(config.xmlElementsFilter, obj, childName, childPath);
            }
            else
                return true;
        }

        function parseDOMChildren( node, path,pos,parentResult ) {
            if(node.nodeType == DOMNodeTypes.DOCUMENT_NODE) {
                var result = new Object;
                var nodeChildren = node.childNodes;
                // Alternative for firstElementChild which is not supported in some environments
                for(var cidx=0; cidx <nodeChildren.length; cidx++) {
                    var child = nodeChildren.item(cidx);
                    if(child.nodeType == DOMNodeTypes.ELEMENT_NODE) {
                        var childName = getNodeLocalName(child);
                        result[childName] = parseDOMChildren(child, childName,0);
                    }
                }
                return result;
            }
            else
            if(node.nodeType == DOMNodeTypes.ELEMENT_NODE) {
                var result = null;
                if(typeof parentResult != "undefined" && parentResult != null) {
                    result = parentResult;
                } else {
                    result = new Object;
                    result.__cnt=0;
                }


                var nodeChildren = node.childNodes;

                // Attributes
                for(var aidx=0; aidx <node.attributes.length; aidx++) {
                    var attr = node.attributes.item(aidx); // [aidx];
                    if(!config.xmlns && attr.prefix == "xmlns") {
                        continue;
                    }
                    result.__cnt++;
                    if(config.removeAttrPrefix && typeof attr.localName != "undefined") {
                        result[config.attributePrefix+attr.localName]=attr.value;
                    } else {
                        result[config.attributePrefix+attr.name]=attr.value;
                    }
                }

                var i = 0;

                if(typeof pos != "undefined") {
                    i = pos;
                }
                // Children nodes
                for(var cidx=i; cidx <nodeChildren.length; cidx++) {
                    var child = nodeChildren.item(cidx); // nodeChildren[cidx];
                    var childName = getNodeLocalName(child);

                    if(config.skip && config.skip.indexOf(childName) > -1){
                        continue; // skip element
                    }
                    // check if the element has to be inlined

                    if((childName.length > 0 && (childName.isFirstCharUpper()))) {
                        result['type'] = childName;
                        parseDOMChildren(child, path,0,result);

                    } else 	if(child.nodeType != DOMNodeTypes.COMMENT_NODE) {
                        var childPath = path+"."+childName;

                        if (checkXmlElementsFilter(result,child.nodeType,childName,childPath)) {
                            result.__cnt++;
                            if(result[childName] == null) {
                                result[childName] = parseDOMChildren(child, childPath,0);
                                toArrayAccessForm(result, childName, childPath);
                            } else {
                                if(result[childName] != null) {
                                    if( !(result[childName] instanceof Array)) {
                                        result[childName] = [result[childName]];
                                        toArrayAccessForm(result, childName, childPath);
                                    }
                                }
                                (result[childName])[result[childName].length] = parseDOMChildren(child, childPath,0);
                            }
                        }
                    }
                }

                // Node namespace prefix
                var nodePrefix = getNodePrefix(node);
                if(nodePrefix!=null && nodePrefix!="" && config.prefix) {
                    result.__cnt++;
                    result.__prefix=nodePrefix;
                }

                if(result["#text"]!=null) {
                    result.value = result["#text"];
                    if(result.value instanceof Array) {
                        result.value = result.value.join("\n");
                    }
                    //if(config.escapeMode)
                    //	result.value = unescapeXmlChars(result.value);
                    if(config.stripWhitespaces)
                        result.value = result.value.trim();
                    delete result["#text"];
                    if(config.arrayAccessForm=="property")
                        delete result["#text_asArray"];
                    result.value = checkFromXmlDateTimePaths(result.value, childName, path+"."+childName);
                    if(result.value.length == 0) {
                        delete  result.value;
                    }

                    if(checkAsNumericalValue(getNodeLocalName(node),path)){
                        result.value = parseFloat(result.value);
                    }
                }
                if(result["#cdata-section"]!=null) {
                    result.__cdata = result["#cdata-section"];
                    delete result["#cdata-section"];
                    if(config.arrayAccessForm=="property")
                        delete result["#cdata-section_asArray"];
                }

                if( result.__cnt == 0 && config.emptyNodeForm=="text" ) {
                    result = '';
                }
                else
                if( result.__cnt == 1 && result.value!=null  ) {
                    result = result.value;
                }
                else
                if( result.__cnt == 1 && result.__cdata!=null && !config.keepCData  ) {
                    result = result.__cdata;
                }
                else
                if ( result.__cnt > 1 && result.value!=null && config.skipEmptyTextNodesForObj) {
                    if( (config.stripWhitespaces && result.value=="") || (result.value.trim()=="")) {
                        delete result.value;
                    }
                }
                delete result.__cnt;

                if( config.enableToStringFunc && (result.value!=null || result.__cdata!=null )) {
                    result.toString = function() {
                        return (this.value!=null? this.value:'')+( this.__cdata!=null ? this.__cdata:'');
                    };
                }

                return result;
            }
            else
            if(node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) {
                return node.nodeValue;
            }
        }

        function startTag(jsonObj, element, attrList, closed) {
            var resultStr = "<"+ ( (jsonObj!=null && jsonObj.__prefix!=null)? (jsonObj.__prefix+":"):"") + element;
            if(attrList!=null) {
                for(var aidx = 0; aidx < attrList.length; aidx++) {
                    var attrName = attrList[aidx];
                    var attrVal = jsonObj[attrName];
                    if(config.escapeMode)
                        attrVal=escapeXmlChars(attrVal);
                    resultStr+=" "+attrName.substr(config.attributePrefix.length)+"=";
                    if(config.useDoubleQuotes)
                        resultStr+='"'+attrVal+'"';
                    else
                        resultStr+="'"+attrVal+"'";
                }
            }
            if(!closed)
                resultStr+=">";
            else
                resultStr+="/>";
            return resultStr;
        }

        function endTag(jsonObj,elementName) {
            return "</"+ (jsonObj.__prefix!=null? (jsonObj.__prefix+":"):"")+elementName+">";
        }

        function endsWith(str, suffix) {
            return str.indexOf(suffix, str.length - suffix.length) !== -1;
        }

        function jsonXmlSpecialElem ( jsonObj, jsonObjField ) {
            if((config.arrayAccessForm=="property" && endsWith(jsonObjField.toString(),("_asArray")))
                || jsonObjField.toString().indexOf(config.attributePrefix)==0
                || jsonObjField.toString().indexOf("__")==0
                || (jsonObj[jsonObjField] instanceof Function) )
                return true;
            else
                return false;
        }

        function jsonXmlElemCount ( jsonObj ) {
            var elementsCnt = 0;
            if(jsonObj instanceof Object ) {
                for( var it in jsonObj  ) {
                    if(jsonXmlSpecialElem ( jsonObj, it) )
                        continue;
                    elementsCnt++;
                }
            }
            return elementsCnt;
        }

        function checkJsonObjPropertiesFilter(jsonObj, propertyName, jsonObjPath) {
            return config.jsonPropertiesFilter.length == 0
                || jsonObjPath==""
                || checkInStdFiltersArrayForm(config.jsonPropertiesFilter, jsonObj, propertyName, jsonObjPath);
        }

        function parseJSONAttributes ( jsonObj ) {
            var attrList = [];
            if(jsonObj instanceof Object ) {
                for( var ait in jsonObj  ) {
                    if(ait.toString().indexOf("__")== -1 && ait.toString().indexOf(config.attributePrefix)==0) {
                        attrList.push(ait);
                    }
                }
            }
            return attrList;
        }

        function parseJSONTextAttrs ( jsonTxtObj ) {
            var result ="";

            if(jsonTxtObj.__cdata!=null) {
                result+="<![CDATA["+jsonTxtObj.__cdata+"]]>";
            }

            if(jsonTxtObj.value!=null) {
                if(config.escapeMode)
                    result+=escapeXmlChars(jsonTxtObj.value);
                else
                    result+=jsonTxtObj.value;
            }
            return result;
        }

        function parseJSONTextObject ( jsonTxtObj ) {
            var result ="";

            if( jsonTxtObj instanceof Object ) {
                result+=parseJSONTextAttrs ( jsonTxtObj );
            }
            else
            if(jsonTxtObj!=null) {
                if(config.escapeMode)
                    result+=escapeXmlChars(jsonTxtObj);
                else
                    result+=jsonTxtObj;
            }

            return result;
        }

        function getJsonPropertyPath(jsonObjPath, jsonPropName) {
            if (jsonObjPath==="") {
                return jsonPropName;
            }
            else
                return jsonObjPath+"."+jsonPropName;
        }

        function parseJSONArray ( jsonArrRoot, jsonArrObj, attrList, jsonObjPath ) {
            var result = "";
            if(jsonArrRoot.length == 0) {
                result+=startTag(jsonArrRoot, jsonArrObj, attrList, true);
            }
            else {
                for(var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
                    result+=startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
                    result+=parseJSONObject(jsonArrRoot[arIdx], getJsonPropertyPath(jsonObjPath,jsonArrObj));
                    result+=endTag(jsonArrRoot[arIdx],jsonArrObj);
                }
            }
            return result;
        }

        function parseJSONObject ( jsonObj, jsonObjPath ) {
            var result = "";

            var elementsCnt = jsonXmlElemCount ( jsonObj );

            if(elementsCnt > 0) {
                for( var it in jsonObj ) {

                    if(jsonXmlSpecialElem ( jsonObj, it) || (jsonObjPath!="" && !checkJsonObjPropertiesFilter(jsonObj, it, getJsonPropertyPath(jsonObjPath,it))) )
                        continue;

                    var subObj = jsonObj[it];

                    var attrList = parseJSONAttributes( subObj )

                    if(subObj == null || subObj == undefined) {
                        result+=startTag(subObj, it, attrList, true);
                    }
                    else
                    if(subObj instanceof Object) {

                        if(subObj instanceof Array) {
                            result+=parseJSONArray( subObj, it, attrList, jsonObjPath );
                        }
                        else if(subObj instanceof Date) {
                            result+=startTag(subObj, it, attrList, false);
                            result+=subObj.toISOString();
                            result+=endTag(subObj,it);
                        }
                        else {
                            var subObjElementsCnt = jsonXmlElemCount ( subObj );
                            if(subObjElementsCnt > 0 || subObj.value!=null || subObj.__cdata!=null) {
                                result+=startTag(subObj, it, attrList, false);
                                result+=parseJSONObject(subObj, getJsonPropertyPath(jsonObjPath,it));
                                result+=endTag(subObj,it);
                            }
                            else {
                                result+=startTag(subObj, it, attrList, true);
                            }
                        }
                    }
                    else {
                        result+=startTag(subObj, it, attrList, false);
                        result+=parseJSONTextObject(subObj);
                        result+=endTag(subObj,it);
                    }
                }
            }
            result+=parseJSONTextObject(jsonObj);

            return result;
        }

        this.parseXmlString = function(xmlDocStr) {
            var isIEParser = window.ActiveXObject || "ActiveXObject" in window;
            if (xmlDocStr === undefined) {
                return null;
            }
            var xmlDoc;
            if (window.DOMParser) {
                var parser=new window.DOMParser();
                var parsererrorNS = null;
                // IE9+ now is here
                if(!isIEParser) {
                    try {
                        parsererrorNS = parser.parseFromString("INVALID", "text/xml").getElementsByTagName("parsererror")[0].namespaceURI;
                    }
                    catch(err) {
                        parsererrorNS = null;
                    }
                }
                try {
                    xmlDoc = parser.parseFromString( xmlDocStr, "text/xml" );
                    if( parsererrorNS!= null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) {
                        //throw new Error('Error parsing XML: '+xmlDocStr);
                        xmlDoc = null;
                    }
                }
                catch(err) {
                    xmlDoc = null;
                }
            }
            else {
                // IE :(
                if(xmlDocStr.indexOf("<?")==0) {
                    xmlDocStr = xmlDocStr.substr( xmlDocStr.indexOf("?>") + 2 );
                }
                xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
                xmlDoc.async="false";
                xmlDoc.loadXML(xmlDocStr);
            }
            return xmlDoc;
        };

        this.asArray = function(prop) {
            if (prop === undefined || prop == null)
                return [];
            else
            if(prop instanceof Array)
                return prop;
            else
                return [prop];
        };

        this.toXmlDateTime = function(dt) {
            if(dt instanceof Date)
                return dt.toISOString();
            else
            if(typeof(dt) === 'number' )
                return new Date(dt).toISOString();
            else
                return null;
        };

        this.asDateTime = function(prop) {
            if(typeof(prop) == "string") {
                return fromXmlDateTime(prop);
            }
            else
                return prop;
        };

        this.xml2json = function (xmlDoc) {
            return parseDOMChildren ( xmlDoc );
        };

        this.xml_str2json = function (xmlDocStr) {
            var xmlDoc = this.parseXmlString(xmlDocStr);
            if(xmlDoc!=null)
                return this.xml2json(xmlDoc);
            else
                return null;
        };

        this.json2xml_str = function (jsonObj) {
            return parseJSONObject ( jsonObj, "" );
        };

        this.json2xml = function (jsonObj) {
            var xmlDocStr = this.json2xml_str (jsonObj);
            return this.parseXmlString(xmlDocStr);
        };

        this.getVersion = function () {
            return VERSION;
        };
    }
}));

String.prototype.isFirstCharUpper = function(){
    "use strict";

    var str = this, re = new RegExp('[^A-Z]');
    return !str.charAt(0).match(re);

};